Dibbla CLI
The dibbla CLI scaffolds projects and manages applications, databases, secrets, and workflows on the Dibbla platform. Deployed apps are available at https://<alias>.dibbla.com.
Prerequisites
Install the CLI if it isn't already on the user's PATH:
| Platform |
Command |
| macOS (Homebrew) |
brew install dibbla-agents/tap/dibbla |
| macOS / Linux (shell installer) |
curl -fsSL https://install.dibbla.com/install.sh | sh |
| Windows (PowerShell) |
powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://install.dibbla.com/install.ps1 | iex" |
| Verify |
dibbla --version |
The shell installer drops the binary into ~/.local/bin and adjusts PATH if needed. Self-update is available inside task files via the same installer URL.
Installation needs install.dibbla.com and nothing else. The version list, the archive and checksums.txt all come from that one origin, and the archive's SHA-256 is verified before anything is written to disk. This matters if you are an agent running in a sandbox: inside Claude Cowork and Claude Code on the web, GitHub returns 403, so anything that fetched the binary from GitHub Releases could not install at all. If curl -fsSL https://install.dibbla.com/install.sh | sh fails in such an environment, the problem is not GitHub access — report the actual error rather than trying to work around it via git clone or go install.
dibbla update follows the same origin. The one exception is a pinned dibbla update --version <tag>, which resolves against GitHub because only the latest release is mirrored; where GitHub is blocked, drop --version.
Deploying requires a Dockerfile at the root of the directory you pass to dibbla deploy. The CLI does not auto-detect languages or generate a Dockerfile — if it's missing, the backend rejects the build with log output. All bundled templates in dibbla-agents/dibbla-public-templates ship a working Dockerfile you can copy (typically multi-stage: Node → JS build → Go → binary → small runtime image, EXPOSE 80).
Commands at a glance
| Area |
Commands |
| Run |
run [path|url], run --preview, run --env KEY=VAL, run --env-file <file>, run --work-dir <dir>, run --format plain|gh |
| Template |
template list [--refresh] [-v], template install <id> [<dir>] [--force] |
| Skills |
skills list, skills install <id> (--user, --force, --no-agents) — install AI-agent guidance into .claude/skills/ + AGENTS.md + GEMINI.md |
| Setup |
init (interactive setup wizard: update → login → install dibbla skill), update [--check] [--version vX.Y.Z] (self-update; defers to brew/apt/rpm/scoop/choco when one owns the binary), uninstall [--dry-run] [--keep-config] [--keep-skills] [--skill-only] (removes binary on script installs, keychain creds for every context, the context list and every per-context credentials file, ~/.config/dibbla/, ~/.dibbla/, and skill files at every recorded install root; for package-manager installs prints the native uninstall command instead of touching the binary) |
| Context |
context list (alias ls; --json), context use <name>, context current, context rename <old> <new>, context rm <name> [--force] — named login targets, one per API server, so you can stay logged in to prod, a customer instance and dev at once. --context <name> is a global flag on every command for a one-off switch; DIBBLA_CONTEXT does the same per shell. Precedence: DIBBLA_API_TOKEN/DIBBLA_API_URL (and ./.env) > --context > DIBBLA_CONTEXT > the selected context > https://api.dibbla.com. The non-secret list is an editable file at ~/.config/dibbla/config.yaml; tokens stay in the keyring under per-context keys. dibbla update is deliberately NOT context-aware |
| Login |
login [api_url], login --browser, login --api-key <token>, login --api-url <url>, login --context <name>, login --no-switch, login --write-env, login --no-keychain, logout [--context <name>] [--all] — login adds or refreshes a context rather than replacing the single stored credential, keyed on API URL: the same URL refreshes, a new URL creates a new context. A bare login with no target re-authenticates against the context you are on, not production. logout removes only the context in use unless --all |
| Org | org list (organizations on the active context's server, active one marked), org use <name\|slug\|id> (switch; stored on the active context, no re-login needed), org clear (back to the account default on that context). --org <id> is a global flag on every command for a one-off switch; DIBBLA_ORG_ID does the same per shell. Precedence: --org > DIBBLA_ORG_ID > the context's pin > account default. The pin is per-context because an organization id only means anything on the server that issued it — switching context switches organization with it |
| Status | status (CLI version + active context and how many are configured + resolved API URL with source + token presence with source + active organization with source + live token validation), status --no-validate (skip the network call), status --json (machine-readable). Exits 0 when logged in or token is configured + validation skipped, 3 when not logged in or token rejected |
| Feedback | feedback <message>, feedback list, feedback delete <id> |
| Deploy | deploy [path] -m "<msg>" [--alias name] [--update] [--env-file <file>] [--require-login] [--access-policy] [--google-scopes] [--target-env <env>] [--profile <p>] — deploy from directory; -m becomes the VCS commit subject. --env-file bulk-loads env vars (file is the base layer, -e overrides per key). --target-env / --profile / --no-public only apply when a dibbla.yaml is at the deploy root |
| Clone | clone <app> [--ref <sha>] [--into <dir>] — clone the Dibbla-managed git repo for a deployed app (read-only; push is rejected) |
| Manifest | manifest validate [path] — local schema check for dibbla.yaml (no network) |
| Preview | preview [path] [--target-env <env>] [--profile <p>] [--no-public] — server-authoritative dry run; full env-aware resolution + quota check, no build, no apply |
| Apps | apps list, apps get <alias> [--json] (one app: URL, status, size, health, login policy, per-service breakdown), apps update <alias> [--env-file <file>], apps delete <alias>, apps restart <alias> --service <name> (per-service rolling restart) |
| Checks | apps checks list <alias> [--json], apps checks run <alias> [--check <id>] [--async\|--follow] [--quiet\|--json], apps checks history <alias> [--check <id>] [--since 24h] [--limit N] [--json], apps checks enable\|disable <alias> [--yes] — application checks (dibbla-checks.yaml). Alias positional, check id via --check. run exits with the product outcome: 0 pass, 8 fail, 9 error, 10 indeterminate, 12 canceled, 13 skipped_concurrent (transport keeps 1/3/4/5/6/7) — gate CI on the code, not on output text. run --follow --json is NDJSON with exactly one terminal summary line carrying outcome + exit_code |
| Logs | logs <app> (last 15m, merged across all services in the deployment by default), logs <app> --since 24h, logs <app> -f (follow), logs <app> -n 200 (tail), logs <app> --grep <regex>, logs <app> --json — runtime logs from Loki; omit --service for accumulated deployment-wide logs, add logs <app> --service <name> to filter to one service; logs <app> --service <name> --pod-stream streams pod logs via the K8s API when Loki isn't available |
| Db | db list, db create, db delete, db dump, db restore, db connect |
| Storage | storage list, storage create [--size 5Gi] [--deployment <alias>], storage delete [--force], storage rotate [--no-restart], storage info, storage credentials (alias buckets) — managed S3 buckets with bucket-scoped credentials injected as STORAGE_<NAME>_* secrets |
| Secrets | secrets list, secrets set, secrets get, secrets delete, secrets import <file> [-e KEY=VAL] [--dry-run] (bulk-load a .env file, no redeploy) — all scoped global, -d <alias> for deployment-wide, or -d <alias> --service <name> for per-service |
| Admin | admin reconcile — force one orphan-resource sweep on the deploy-api instance (gated by DIBBLA_ADMIN_TOKEN) |
| MCP | mcp community [--client claude\|codex\|gemini] — print ready-to-paste MCP client config for the hosted community toolset (mcp.dibbla.com/community, Dibbla community over MCP acting as you; staff-oriented). Output references $DIBBLA_API_TOKEN, never the literal token. mcp community --check verifies the whole chain (token → initialize → whoami), names the token's source, and fails (exit 1) when DIBBLA_API_TOKEN is not exported in the current shell even if the keyring has a token — that is exactly the state where a configured MCP client gets 401. First thing to run on any 401. Reads need one prior sign-in at community.dibbla.com; posting needs write-group membership |
| Workflows | workflows list, get, create, update, delete, validate, execute [--async\|--follow], url, api-docs, logs <runId> [-f] |
| Runs | wf runs list [--workflow <name>] [--limit <N>], wf runs output <runId> — list past runs and fetch the api_response payload of a finished run |
| Nodes | nodes add <wf>, nodes remove <wf> <id> |
| Edges | edges add <wf> "<edge>", edges remove, edges list |
| Inputs | inputs set <wf> <node> <input> <value> |
| Tools | tools add <wf> <agent> <tool>, tools remove |
| Revisions | revisions list <wf>, revisions create, revisions restore |
| Functions | functions list, functions get <server> <name> |
Agent guidelines
Interactive prompts: The following commands prompt for confirmation and will block if run non-interactively. Always pass --yes (or -y) when running these as an agent:
dibbla apps delete <alias> --yes
dibbla db delete <name> --yes
dibbla secrets delete <name> --yes
dibbla workflows delete <name> --yes
dibbla nodes remove <wf> <id> --yes
dibbla feedback delete <id> --yes
Deploying an app for the first time:
- Check if the app already exists:
dibbla apps list
- If it does not exist, deploy with all required environment variables included in the deploy command — there is no app to attach them to yet:
dibbla deploy . --alias my-app -m "feat: initial deploy" \
-e DATABASE_URL=postgres://... -e API_KEY=secret -e NODE_ENV=production
- If it already exists, use
--update for a zero-downtime rolling update:dibbla deploy . --alias my-app -m "fix: resolve 500 on /search" --update
To change env vars on an existing app, use apps update instead:dibbla apps update my-app -e NEW_VAR=value
Key rules:
Every dibbla deploy must include -m "<message>". The value becomes the git commit subject in the app's Dibbla-managed VCS history (and on the GitHub mirror, if configured). Treat it like a git commit: present-tense imperative, under ~72 chars, covering what changed and why — e.g. -m "fix: handle null org in /api/me", -m "feat: add nightly db backup workflow", -m "chore: bump node to 20.14". For retries or mechanical redeploys, still say so explicitly: -m "redeploy: retry after CF 524". Max 500 chars. Never run dibbla deploy without -m; a blank deploy history is a bug, not a default.
--force causes downtime (tears down and redeploys). Prefer --update for existing apps.
--force and --update are mutually exclusive.
Environment variables set via deploy -e or apps update -e persist across updates — you only need to pass them once.
Login guard: Use --require-login to require authentication. Combine with --access-policy invite_only to restrict to invited users, or all_members for org-wide access. Use --google-scopes to request additional Google OAuth scopes (e.g. Drive, Calendar).
Use --quiet / -q on db list, db delete, db connect for machine-readable output in scripts.
db create --deployment <alias> scopes the database and its auto-created secret to a specific deployment. The scoped secret is named DATABASE_URL_<UPPERCASED_UNDERSCORED_NAME> (e.g. DATABASE_URL_MY_DB for database my_db), not a plain DATABASE_URL — app code must read the suffixed env var.
db connect prints a psql-compatible connection string via the Dibbla database proxy. Use -q for scripting: psql $(dibbla db connect mydb -q).
storage create provisions an S3 bucket with a hard quota and bucket-scoped credentials injected as STORAGE_<NAME>_* secrets (<NAME> = bucket name uppercased, hyphens → underscores; bucket my-uploads → STORAGE_MY_UPLOADS_ACCESS_KEY_ID etc.). App code reads those env vars and speaks plain S3. storage rotate restarts the bound deployment's services by design (envFrom secrets only refresh on restart) — use --no-restart only if you restart yourself. storage credentials <name> -q prints eval-able exports for aws/mc/rclone.
Document the schema in the database itself. After creating or migrating tables, set COMMENT ON DATABASE, COMMENT ON TABLE and COMMENT ON COLUMN. Postgres stores these alongside the schema, so unlike a README they cannot drift from it; the console renders them, and its database agent answers from documented meaning instead of inferring from identifiers.
A database or table comment carries a display name and a description, as Markdown frontmatter over a Markdown body. The name is free of identifier constraints — it's what a person would call the thing:
COMMENT ON TABLE articles IS $doc$---
name: Articles
---
Product master data, one row per article number. Loaded nightly from the
supplier export; rows are never deleted, superseded ones get `active = false`.
$doc$;
A column comment is one explanatory line, no frontmatter — it renders as a tooltip:
COMMENT ON COLUMN articles.confidence IS 'Classification confidence, 0-1. Below 0.6 is reviewed by hand.';
Say what a reader could not guess: units, ranges, what a status value means, which table a loose id points at, whether rows are ever deleted. Restating the column name ('The article number') satisfies nothing. Caps on save: name 80 characters, description 1000, column tooltip 300.
Plain text without frontmatter stays valid — it's read as the description with no name, so existing comments keep working. Nothing is enforced at deploy time; an undocumented schema deploys fine, it just makes every answer about it an inference.
524 on deploy ≠ failure. dibbla deploy holds a single HTTP connection during the backend build; builds over ~100s may return a Cloudflare 524 on the client even when the backend succeeds. Wait 2–5 minutes, then run dibbla apps list to check. Do not retry with --force — use --update if you must retry.
Output modes: dibbla deploy streams a live buildkit-style step view when stdout is a TTY and switches to ISO-timestamped log lines (<ts> [info] build step=N/M …) when stdout is piped or in CI. Add --quiet for a single-line success/failure (script-friendly) or --json for a single structured object on stdout. On build failure the non-TTY mode also writes one structured JSON line to stderr with shape {"event":"deploy.failed","step":"go-build","step_index":N,"step_count":M,"errors":[{file,line,col,message}],"retry_cmd":"…","api_error_code":"BUILD_FAILED"} — coding agents should read this from stderr to locate failing files without scraping the human-readable build output. Add --verbose-build to ship the full server build log instead of the elided tail when parsed compile diagnostics aren't enough. Build failures exit 2; other errors exit 1.
.dibblaignore controls Dibbla's managed VCS history — that filter alone, and nothing else. The backend always strips .env, node_modules/, dist/, *.pem, *.key and similar from VCS and reports each hit in DeployResponse.vcs_filtered as a warning. Adding those paths (or any generated/large artifact) to .dibblaignore at the deploy root silences the warning and keeps VCS clean. Per-file and per-commit size caps are hard rejections — committing a large build artifact will fail the deploy with ErrCodeVCSFiltered; the fix is to add the path to .dibblaignore. Full details in reference.md → deploy → .dibblaignore.
A separate, silent server-side filter strips regenerable directories out of the Docker build context: node_modules/, .git/, __pycache__/, .venv/, vendor/, .next/, dist/, .cache/. Never write a Dockerfile that COPYs one of these — COPY vendor/ and COPY dist/ fail on the platform while building fine locally. Regenerate them in the build (go mod download, npm ci && npm run build). COPY --from=<stage> is unaffected. .dibblaignore has no bearing on this. Full list, rationale and worked Dockerfiles in reference.md → deploy → "Build-context strip (skippedDirs)"; guardrails.md Check 9 catches it before you deploy.
App database connections go through the Dibbla database proxy with a publicly-valid TLS cert. Use the injected DATABASE_URL_<NAME> as-is (sslmode=require) — no rejectUnauthorized: false, no sslmode=no-verify. See reference.md "TLS for application database clients".
Designing a multi-service manifest: Before authoring a dibbla.yaml, work through these design questions and confirm a plan with the user. Skipping this step at design time leads to retrofits that touch every consumer service (env vars, depends_on, service-discovery references), so it's worth the 60 seconds upfront.
- Which services should exist in only some envs? (e.g. an inline DB container in dev, a managed/external DB in prod) → put
profiles: [dev] on the env-specific service. Decide this upfront because consumers will need env-aware values for the URL/host that points to it.
- Which fields differ across envs? (
replicas, image, MONGO_URL, LOG_LEVEL, …) → use env-aware field maps (§ 6 in manifest.md). Different mechanism from profiles: profiles toggle whether a service exists at all; env-aware fields shape an existing service.
- Where will the data layer live in prod? If managed/external, the consumer needs an env-aware
MONGO_URL / DATABASE_URL (default: → external value, dev: → ${DIBBLA_SVC_*}) and the inline copy needs profiles: [dev]. The two mechanisms are paired — see § 7 in manifest.md for a worked example.
- How will the user iterate locally? The platform does not run
dibbla.yaml locally — there is no dibbla up. Mirror the manifest into a docker-compose.yml next to it for tight inner-loop dev (see examples.md "Local iteration with docker-compose"). The two diverge in details (no ${DIBBLA_SVC_*}, no NetworkPolicy, no env-aware resolution) but match in shape.
- Will any public service be sensitive in prod? (admin UIs, debug consoles, mail catchers, internal dashboards) → per-service
auth: block with require_login: true and an access_policy:, or gate the whole service with profiles: [dev]. Shipping an admin UI publicly without auth is a top OWASP-class mistake; the guardrails check enforces this in guardrails.md.
- Are any services stateful — databases, brokers, anything with on-disk state? If yes, set
stateful: true and declare at least one volumes: entry. The renderer switches to a StatefulSet + headless Service so each pod has stable identity and its own PVC. To expose the service to clients outside the cluster (a mongosh from your laptop, an external Redis client), add a routes: entry with type: tcp + tls: edge. Limit: TLS-on-connect protocols only — Mongo, Redis-with-TLS, AMQPS, NATS-with-TLS, Kafka-with-TLS — Postgres and MySQL use STARTTLS-style upgrades that don't carry SNI in the first packet, so they're deferred. Footgun: replicas > 1 on a stateful service yields N independent pods each with its own PVC and its own data; the platform does not bootstrap clustering protocols. Use replicas: 1 unless you're wiring clustering yourself. Full schema in manifest.md § 10.5.
Pre-deploy guardrails (CLI-enforced): Before calling dibbla deploy, you MUST complete the pre-deploy checklist and present findings to the user. Always wait for explicit user confirmation before deploying or fixing issues — never deploy autonomously. The guardrails workflow writes a REVIEW.md file to the project root, and dibbla deploy will refuse to upload without it (and without a user handbook carrying a valid subtitle: — the gate also blocks a missing, empty, placeholder, or over-140-byte subtitle). The platform also reads REVIEW.md and displays a review status indicator in the dashboard. The --skip-review flag exists for humans making one-line fixes; agents must run the checklist instead of using the flag. See guardrails.md for the full checklist.
User handbook (MANDATORY, CLI-enforced). Every deployable app MUST ship user-facing documentation. dibbla deploy refuses to upload unless the project root contains either a docs/ folder with at least docs/index.md, or a single APP.md at the root. If neither exists, do not deploy — generate a starter handbook from the templates in user-docs.md, ask the user to confirm content, then deploy. The handbook is for the end user of the deployed app — not developers, not operators. It is rendered in app.dibbla.com under "My Apps → {alias}" and is the only documentation surface end users see. Never put dev-stack notes, framework names, deploy commands, env-var lists, infra details, or other technical metadata in there. See user-docs.md for tone, file layout, cross-linking syntax, paste-ready templates, and the full list of what NOT to include.
Workflows: A workflow is a typed DAG of function calls — nodes name a function from the registry, edges carry data port-to-port, an api node + api_response node make it callable over HTTP. Author in slim YAML (the format wf get/wf create -f consume); never hand-write the verbose React-Flow JSON. Minimal shape:
name: my_workflow
nodes:
- {id: api_input, type: api, inputs: [question], outputs: [question]}
- {id: greet, type: function, function: handlebars_template,
server: function-server, inputs: {script: "Hello {{question}}!"},
outputs: [error, output]}
- {id: api_response, type: api_response, linked_to: api_input, inputs: [response]}
edges:
- api_input.question -> greet.question
- greet.output -> api_response.response
Before authoring anything non-trivial, run dibbla fn list to see what functions exist and dibbla wf get <existing> -o yaml on a similar workflow for shape — the function registry, not the YAML, is the source of truth. Pick the iteration loop that matches the change size: small tweak → patch HEAD with nodes add/edges add/inputs set/tools add; structural change → wf get … -o yaml → edit → wf update -f. Always dibbla revisions create <wf> before either; patches are not auto-snapshotted and revisions restore overwrites HEAD (it's not a checkout). For the complete model — node-type roles, the agent+tool pattern, all 13 validator errors and their fixes, execution/HTTP semantics, and the three canonical workflow shapes (transform, agent+tools, multi-stage pipeline) — see workflows.md.
Workflow gotchas that bite once:
- Pick
reasoning_agent_function for new agents — reasoning_agent_with_thread has been observed to silently return empty responses with current Claude models. Do not wire agent.error -> api_response.error to surface failures: a wired input gates the response node and a successful run never produces error, so the workflow hangs. Use wf execute --follow or wf logs <runId>.
- One node, one role: never both tool AND data input. If a node feeds an agent's
tools: list AND has a data edge into the same agent (or transitively), the auto-generated tool-connection edge plus your data edge close a cycle — pre-flight refuses the run with 422 CYCLE_DETECTED. Pick one role; the canonical "inject this into the system prompt" shape is data -> handlebars_template -> agent.system_message, with the data source NOT in tools:. See workflows.md §6 and the worked example in examples.md.
- Use
--follow for the first execution after any workflow change. Silent function failures used to surface only after the 30-minute server timeout. The stuck-run watchdog now emits a WARN-level run is not making progress within ~30s with a per-input diagnosis field — visible immediately in dibbla wf execute … --follow. Treat --follow as the smoke-test default, not a debugging escape hatch.
wf update is no longer last-write-wins. The CLI sends If-Match with the current ETag automatically; concurrent edits return 412 with current_etag/received_etag in the body. Pull, merge, retry — or --force to overwrite (deletes whatever the other writer just shipped, so use sparingly).
- YAML types must match the function's reflected Go types. Send
triggered: true not triggered: "true"; 42 not "42". dibbla fn get is now ground truth (boolean / integer / float / string); older cached YAML may have stringly-typed slots — regenerate from fn get when in doubt.
- Production callers must use the gateway URL, not the URL
wf api-docs prints. Rewrite host: https://workflow-server.<internal>/api/execute/<name>/<urlid> (shown by api-docs, internal only) → https://api.dibbla.com/api/wf/execute/<name>/<urlid> (gateway, accepts Authorization: Bearer ak_<workflow-api-key>).
<urlid> can go silently stale on wf update. The converter preserves a node's UUID only when its semantic id (function name or label) matches one in the existing workflow. Renaming an api node, swapping its function, or restructuring its inputs: can regenerate the id with no warning. Verify with wf api-docs after any update; if the url id changed, update production callers (or rebuild + redeploy if it's baked at build time).
- Node ids collapse to the function name on
wf create. Don't pick custom ids; refer to tools by function name.
- Result cache is 1 hour on
reasoning_agent_function. During iterative testing, vary the input or use a *_no_cache variant.
- Always wrap workflow fetches in an
AbortController with a 30–60s timeout and log before/after — Node's default 5-minute timeout makes failures look like hangs.
function execution failed is opaque on its own. The CLI message is identical for panics, returned errors, and input-deserialization failures. Suspect input-type mismatch first; the actual Go error is in the go-toolserver pod's stdout (kubectl logs <go-toolserver-pod>). See workflows.md §11 "Diagnosing a hanging wf execute" for the decision tree.
Run monitoring & async execution:
dibbla wf execute is synchronous by default — it blocks until the workflow's api_response node fires (server-side timeout: 30 min). For long-running agent workflows or fire-and-forget batches, use --async to get back response_metadata immediately while the run continues in background. Tail it later with wf logs <runId> --follow and fetch the final output with wf runs output <runId>.
dibbla wf execute --follow (-f) is the one-liner for interactive debugging: starts the run async, tails live logs to stdout, then prints the api_response payload after the server-emitted run_completed sentinel. Exits 0 on completion.
dibbla wf logs <runId> works on any run. Live runs stream until completion; finished runs return historic + sentinel and exit immediately. Persistence policy: WARN/ERROR + the run_completed row are persisted; INFO/DEBUG are live-only — a quiet completed run will tail to essentially just run completed. For the full transcript of a finished run, use wf runs output <runId> instead.
dibbla wf runs list (-w <name> to filter, -n <N> to page; server caps at 500) is the way to find a recent run id without copy-pasting from the dashboard or the DB.
- Short flag
-f differs by command: on dibbla logs (app-logs) and dibbla wf logs, -f is --follow. On dibbla wf execute, -f is also --follow — but --file had to give up its short alias and uses -F instead. Don't suggest -f payload.json for wf execute; use --file payload.json or -F payload.json.
Building Go workers (sdk-go): The github.com/dibbla-agents/sdk-go Go SDK is how workers register custom functions and jobs with the platform. A worker is a long-lived gRPC client: sdk.New(...) → server.RegisterFunction(...) and server.RegisterJob(...) → server.Start() (which blocks forever). External user modules are restricted to sdk.NewSimpleFunction[In, Out] because the advanced Function[In, Out] handler signature exposes internal/types and internal/state — Go's internal/ rule blocks those imports from any module other than sdk-go. The JobHost abstraction was removed; jobs register directly via server.RegisterJob(handler). Once the worker is connected, its functions appear in dibbla functions list and become callable from workflow YAML by (server, function) pair (see workflows.md for consumer-side wiring). For the full SDK model — server options, function builders, the JobHandler interface, JobContext arg helpers, the Logger task/progress API, OAuth via gs.OAuth, capability providers (rare: replacing the platform's built-in tool_search/memory implementation — never the answer to a plain "add memory" request; see the decision gate in workflows.md §6), and gotchas — see sdk-go.md.
Non-TTY / agentic invocation:
- When running from inside Claude Code's
! prefix, an agent shell, CI with a browser, or any other non-TTY context, use dibbla login --browser instead of bare dibbla login. The interactive flow needs stdin for the survey picker; --browser skips that and goes straight to browser-based OAuth via a localhost callback. Refuses over SSH (CLI ≥ v1.2.20) — the localhost callback can't reach the user's laptop; use --api-key instead.
- For true headless (SSH sessions, cloud VMs, CI runners with no local browser), use
dibbla login --api-key <token> or set DIBBLA_API_TOKEN (and optionally DIBBLA_API_URL) env vars — the CLI reads env vars in CI automatically. Get a token at https://app.dibbla.com/api-keys — or, for a self-hosted or customer instance, at that instance's own portal (app.<its-domain>/api-keys), not Dibbla's. A token minted on the wrong instance will not work there.
- Several servers at once (named contexts, CLI ≥ the release carrying P-0011).
dibbla login --context <name> --api-key=<t> --api-url=<url> adds a context instead of replacing whatever login the machine had, so an agent session can hold prod and a customer instance simultaneously and neither destroys the other. Add --no-switch to store it without making it the one in use. Then --context <name> on any command, or DIBBLA_CONTEXT=<name> for a shell, targets it for that call without changing the selection; dibbla context use <name> changes the selection.
dibbla login --context x does NOT set a read override for that invocation. login and logout each define their own local --context, naming the context to write, which shadows the global one naming the context to read. This is deliberate and is the one place the flag means something different.
- Explicit
DIBBLA_API_TOKEN/DIBBLA_API_URL — from the shell or from ./.env — still win over every context. An agent setting those for a one-shot call is completely unaffected by contexts, and so is CI: with either set, no config file and no keyring is read at all.
- A mistyped
--context is an error, not a silent fallback to production. So is a config.yaml that will not parse.
- The context in use is mirrored to the legacy
~/.config/dibbla/credentials.env, so a dibbla binary older than contexts — and any script that sources that file — follows dibbla context use instead of silently staying on the previous server.
- Cloud VMs / SSH / Docker (no keyring): From CLI ≥ v1.2.21,
dibbla login --api-key=<t> automatically falls back to a user-level credentials file at ~/.config/dibbla/credentials.env (mode 0600) when the OS keyring is unavailable or absent (a keyring that is present but locked is a different case — see the locked-keyring bullet below) — no --no-keychain or --write-env flag needed. The file behaves like the keyring (machine-wide, persists across cd); subsequent dibbla * calls from any directory read from it. Combine with --write-env to also land creds in ./.env for project-scoped reads. On older CLIs you still need --api-key=<t> --api-url=<url> --write-env --no-keychain to land creds in ./.env only.
- Keyring present but LOCKED (distinct from absent): On Linux desktops the freedesktop Secret Service may be installed but the
login collection is locked — common in non-TTY/agent shells or before a graphical session has unlocked gnome-keyring. Symptom: failed to unlock correct collection '/org/freedesktop/secrets/collection/login'. In this state the automatic credentials-file fallback does not trigger (the keyring is detected as present), and --write-env alone still fails because the keyring write is attempted before the env write. Force the CLI to bypass the keyring entirely: dibbla login --api-key=<t> --api-url=<url> --no-keychain --write-env — this lands creds in ./.env (project-scoped) regardless of CLI version. Note --no-keychain without --write-env validates but persists nothing, so a later dibbla status reports "not logged in".
.env in CWD is read by every command, including login. Put DIBBLA_API_TOKEN=… and DIBBLA_API_URL=https://api.dibbla.com in ./.env and every dibbla invocation from that directory targets that server and token — no login call needed. Shell-exported vars still win over .env (godotenv does not overwrite). Requires CLI ≥ v1.2.4.
DIBBLA_AUTH_SERVICE_URL is an internal compat alias for DIBBLA_API_URL, injected by the steprunner into child processes launched by dibbla run. Users should put DIBBLA_API_URL in .env; DIBBLA_AUTH_SERVICE_URL exists so child processes see the same server via the desktop/steprunner convention name.
Running task files and templates:
- To scaffold a published template, reach for the sugar first:
dibbla template list to find the id, then dibbla template install <id>. Don't hand-build a github.com/.../blob/... raw URL — that's the slow, error-prone path. Drop to dibbla run <https-url> only when you genuinely have a raw bootstrap URL that isn't in the manifest. (As a safety net, dibbla run auto-rewrites a GitHub /blob/ web URL to its raw.githubusercontent.com form.)
dibbla run <path> executes a dibbla-task.yaml pipeline locally. Tool checks, shell commands, background dev servers, and browser-open side effects are all possible — the task file becomes shell under the user's account.
dibbla run <https-url> fetches and executes a yaml from the network. This is equivalent to curl | bash — only run yamls from sources the user trusts (e.g. github.com/dibbla-agents/*). Work-dir defaults to the user's invocation CWD, so bootstrap clones land in the expected directory rather than in a temp dir.
dibbla template install <id> is ergonomic sugar over mkdir ./<template-path> && cd ./<template-path> && dibbla run <bootstrap-url>. It refuses if the destination directory exists; pass --force to reuse. Use dibbla template list to see available ids.
- Prefer
dibbla run --preview or dibbla template list before actually running, so the user can see what will execute.
Installing this skill into a project (so other agents see it too):
dibbla skills install dibbla writes the skill files into ./.claude/skills/dibbla/ plus AGENTS.md and GEMINI.md pointers at the project root. Every major coding agent then picks up the guidance automatically — Claude Code via its native skill path, Cursor/Opencode/Codex/Copilot/Windsurf/Aider via AGENTS.md (the 2026 open standard), Gemini CLI via GEMINI.md.
- The skill content is embedded in the CLI binary (
go:embed), so dibbla skills install needs no network and the installed skill is locked to the CLI version the user has. Run dibbla --version to see which one.
- The same files are also published over HTTP at
https://dibbla.com/.well-known/agent-skills/index.json, for an agent that has no CLI. Each entry carries a sha256: digest — verify it. The published bytes are mirrored from a tagged CLI release, so a given URL still corresponds to one specific dibbla version rather than a floating latest.
- Flags:
--user installs into $HOME for machine-wide coverage instead of the current directory; --no-agents skips AGENTS.md and GEMINI.md (Claude Code only); --force overwrites skill files that have been edited locally. Unknown files inside .claude/skills/<id>/ are always preserved.
- The AGENTS.md / GEMINI.md pointer block is marker-delimited (
<!-- >>> dibbla skill >>> --> … <!-- <<< dibbla skill <<< -->) so existing AGENTS.md content outside the markers is preserved byte-for-byte across reruns.
- Re-running is idempotent — if nothing changed, nothing is rewritten (no mtime bump). Use
dibbla skills list to see what skills the current CLI ships.
Additional resources
- AI gateway (deployed apps): see ai-gateway.md for
https://ai.dibbla.com, the X-Dibbla-App attribution header, the auto-injected DIBBLA_AI_GATEWAY_URL env var, OpenAI/Anthropic SDK snippets that swap the base URL, and the per-org dashboard. Read this whenever an app needs to call an LLM and the user wants the call audited / attributed under their Dibbla org.
- AI gateway (laptops & IDE assistants): for pointing Claude Code, Cursor, opencode, Cline, Windsurf, or Zed at the gateway from a developer machine — and for
dibbla ai url|env|test — install the dedicated dibbla-ai-gateway skill: dibbla skills install dibbla-ai-gateway (then read its SKILL.md).
- Platform compatibility: see platform.md for the Dockerfile contract, port-matching, runtime environment, managed Postgres TLS handling, secrets/env-var injection, the auth-header contract (
X-User-* headers and Google OAuth scope brokering), the upload boundary, the multi-service runtime contract (§ 8.5), and the pre-deploy compatibility checklist. Read this when working in a Dibbla-connected project on Dockerfile, .dibblaignore, auth integration, or deploy-readiness questions.
- Multi-service manifest schema: see manifest.md for the full
dibbla.yaml schema — services, jobs, env-aware fields, profiles, service discovery (DIBBLA_SVC_*), expose_to/NetworkPolicy, volumes, stateful services + TCP routes (§ 10.5), init containers, healthchecks, multiple public services, custom domains, cron, build-time secrets, quotas, error codes, and a worked end-to-end example. Read this whenever the user is authoring or reviewing a manifest, or asking a "how do I run X alongside Y in one deploy" question, or "how do
…(truncated)
1---2name: dibbla-cli3description: Dibbla CLI4---56# Dibbla CLI78The `dibbla` CLI scaffolds projects and manages **applications**, **databases**, **secrets**, and **workflows** on the Dibbla platform. Deployed apps are available at `https://<alias>.dibbla.com`.910## Prerequisites1112**Install the CLI** if it isn't already on the user's `PATH`:1314| Platform | Command |15|----------|---------|16| macOS (Homebrew) | `brew install dibbla-agents/tap/dibbla` |17| macOS / Linux (shell installer) | `curl -fsSL https://install.dibbla.com/install.sh \| sh` |18| Windows (PowerShell) | `powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://install.dibbla.com/install.ps1 \| iex"` |19| Verify | `dibbla --version` |2021The shell installer drops the binary into `~/.local/bin` and adjusts `PATH` if needed. Self-update is available inside task files via the same installer URL.2223**Installation needs `install.dibbla.com` and nothing else.** The version list, the archive and `checksums.txt` all come from that one origin, and the archive's SHA-256 is verified before anything is written to disk. This matters if you are an agent running in a sandbox: inside Claude Cowork and Claude Code on the web, **GitHub returns 403**, so anything that fetched the binary from GitHub Releases could not install at all. If `curl -fsSL https://install.dibbla.com/install.sh | sh` fails in such an environment, the problem is not GitHub access — report the actual error rather than trying to work around it via `git clone` or `go install`.2425`dibbla update` follows the same origin. The one exception is a pinned `dibbla update --version <tag>`, which resolves against GitHub because only the latest release is mirrored; where GitHub is blocked, drop `--version`.2627**Deploying requires a `Dockerfile`** at the root of the directory you pass to `dibbla deploy`. The CLI does **not** auto-detect languages or generate a Dockerfile — if it's missing, the backend rejects the build with log output. All bundled templates in `dibbla-agents/dibbla-public-templates` ship a working Dockerfile you can copy (typically multi-stage: Node → JS build → Go → binary → small runtime image, `EXPOSE 80`).2829## Commands at a glance3031| Area | Commands |32|------------|----------|33| Run | `run [path\|url]`, `run --preview`, `run --env KEY=VAL`, `run --env-file <file>`, `run --work-dir <dir>`, `run --format plain\|gh` |34| Template | `template list [--refresh] [-v]`, `template install <id> [<dir>] [--force]` |35| Skills | `skills list`, `skills install <id>` (`--user`, `--force`, `--no-agents`) — install AI-agent guidance into `.claude/skills/` + `AGENTS.md` + `GEMINI.md` |36| Setup | `init` (interactive setup wizard: update → login → install dibbla skill), `update [--check] [--version vX.Y.Z]` (self-update; defers to brew/apt/rpm/scoop/choco when one owns the binary), `uninstall [--dry-run] [--keep-config] [--keep-skills] [--skill-only]` (removes binary on script installs, keychain creds **for every context**, the context list and every per-context credentials file, `~/.config/dibbla/`, `~/.dibbla/`, and skill files at every recorded install root; for package-manager installs prints the native uninstall command instead of touching the binary) |37| Context | `context list` (alias `ls`; `--json`), `context use <name>`, `context current`, `context rename <old> <new>`, `context rm <name> [--force]` — named login targets, one per API server, so you can stay logged in to prod, a customer instance and dev at once. `--context <name>` is a global flag on every command for a one-off switch; `DIBBLA_CONTEXT` does the same per shell. Precedence: `DIBBLA_API_TOKEN`/`DIBBLA_API_URL` (and `./.env`) > `--context` > `DIBBLA_CONTEXT` > the selected context > `https://api.dibbla.com`. The non-secret list is an editable file at `~/.config/dibbla/config.yaml`; tokens stay in the keyring under per-context keys. `dibbla update` is deliberately NOT context-aware |38| Login | `login [api_url]`, `login --browser`, `login --api-key <token>`, `login --api-url <url>`, `login --context <name>`, `login --no-switch`, `login --write-env`, `login --no-keychain`, `logout [--context <name>] [--all]` — `login` **adds or refreshes a context** rather than replacing the single stored credential, keyed on API URL: the same URL refreshes, a new URL creates a new context. A bare `login` with no target re-authenticates against the context you are on, not production. `logout` removes only the context in use unless `--all` |3940| Org | `org list` (organizations on the **active context's server**, active one marked), `org use <name\|slug\|id>` (switch; **stored on the active context**, no re-login needed), `org clear` (back to the account default on that context). `--org <id>` is a global flag on every command for a one-off switch; `DIBBLA_ORG_ID` does the same per shell. Precedence: `--org` > `DIBBLA_ORG_ID` > the context's pin > account default. The pin is per-context because an organization id only means anything on the server that issued it — switching context switches organization with it |41| Status | `status` (CLI version + **active context and how many are configured** + resolved API URL with source + token presence with source + active organization with source + live token validation), `status --no-validate` (skip the network call), `status --json` (machine-readable). Exits `0` when logged in or token is configured + validation skipped, `3` when not logged in or token rejected |42| Feedback | `feedback <message>`, `feedback list`, `feedback delete <id>` |43| Deploy | `deploy [path] -m "<msg>" [--alias name] [--update] [--env-file <file>] [--require-login] [--access-policy] [--google-scopes] [--target-env <env>] [--profile <p>]` — deploy from directory; `-m` becomes the VCS commit subject. `--env-file` bulk-loads env vars (file is the base layer, `-e` overrides per key). `--target-env` / `--profile` / `--no-public` only apply when a `dibbla.yaml` is at the deploy root |44| Clone | `clone <app> [--ref <sha>] [--into <dir>]` — clone the Dibbla-managed git repo for a deployed app (read-only; push is rejected) |45| Manifest | `manifest validate [path]` — local schema check for `dibbla.yaml` (no network) |46| Preview | `preview [path] [--target-env <env>] [--profile <p>] [--no-public]` — server-authoritative dry run; full env-aware resolution + quota check, no build, no apply |47| Apps | `apps list`, `apps get <alias> [--json]` (one app: URL, status, size, health, login policy, per-service breakdown), `apps update <alias> [--env-file <file>]`, `apps delete <alias>`, `apps restart <alias> --service <name>` (per-service rolling restart) |48| Checks | `apps checks list <alias> [--json]`, `apps checks run <alias> [--check <id>] [--async\|--follow] [--quiet\|--json]`, `apps checks history <alias> [--check <id>] [--since 24h] [--limit N] [--json]`, `apps checks enable\|disable <alias> [--yes]` — application checks (dibbla-checks.yaml). Alias positional, check id via `--check`. `run` exits with the **product outcome**: 0 pass, 8 fail, 9 error, 10 indeterminate, 12 canceled, 13 skipped_concurrent (transport keeps 1/3/4/5/6/7) — gate CI on the code, not on output text. `run --follow --json` is NDJSON with exactly one terminal `summary` line carrying `outcome` + `exit_code` |49| Logs | `logs <app>` (last 15m, **merged across all services in the deployment** by default), `logs <app> --since 24h`, `logs <app> -f` (follow), `logs <app> -n 200` (tail), `logs <app> --grep <regex>`, `logs <app> --json` — runtime logs from Loki; **omit `--service` for accumulated deployment-wide logs**, add `logs <app> --service <name>` to filter to one service; `logs <app> --service <name> --pod-stream` streams pod logs via the K8s API when Loki isn't available |50| Db | `db list`, `db create`, `db delete`, `db dump`, `db restore`, `db connect` |51| Storage | `storage list`, `storage create [--size 5Gi] [--deployment <alias>]`, `storage delete [--force]`, `storage rotate [--no-restart]`, `storage info`, `storage credentials` (alias `buckets`) — managed S3 buckets with bucket-scoped credentials injected as `STORAGE_<NAME>_*` secrets |52| Secrets | `secrets list`, `secrets set`, `secrets get`, `secrets delete`, `secrets import <file> [-e KEY=VAL] [--dry-run]` (bulk-load a `.env` file, no redeploy) — all scoped global, `-d <alias>` for deployment-wide, or `-d <alias> --service <name>` for per-service |53| Admin | `admin reconcile` — force one orphan-resource sweep on the deploy-api instance (gated by `DIBBLA_ADMIN_TOKEN`) |54| MCP | `mcp community [--client claude\|codex\|gemini]` — print ready-to-paste MCP client config for the hosted community toolset (`mcp.dibbla.com/community`, Dibbla community over MCP acting as you; staff-oriented). Output references `$DIBBLA_API_TOKEN`, never the literal token. `mcp community --check` verifies the whole chain (token → initialize → whoami), names the token's source, and **fails (exit 1) when `DIBBLA_API_TOKEN` is not exported in the current shell** even if the keyring has a token — that is exactly the state where a configured MCP client gets 401. First thing to run on any 401. Reads need one prior sign-in at community.dibbla.com; posting needs write-group membership |55| Workflows | `workflows list`, `get`, `create`, `update`, `delete`, `validate`, `execute [--async\|--follow]`, `url`, `api-docs`, `logs <runId> [-f]` |56| Runs | `wf runs list [--workflow <name>] [--limit <N>]`, `wf runs output <runId>` — list past runs and fetch the api_response payload of a finished run |57| Nodes | `nodes add <wf>`, `nodes remove <wf> <id>` |58| Edges | `edges add <wf> "<edge>"`, `edges remove`, `edges list` |59| Inputs | `inputs set <wf> <node> <input> <value>` |60| Tools | `tools add <wf> <agent> <tool>`, `tools remove` |61| Revisions | `revisions list <wf>`, `revisions create`, `revisions restore` |62| Functions | `functions list`, `functions get <server> <name>` |6364## Agent guidelines6566**Interactive prompts:** The following commands prompt for confirmation and will block if run non-interactively. Always pass `--yes` (or `-y`) when running these as an agent:67- `dibbla apps delete <alias> --yes`68- `dibbla db delete <name> --yes`69- `dibbla secrets delete <name> --yes`70- `dibbla workflows delete <name> --yes`71- `dibbla nodes remove <wf> <id> --yes`72- `dibbla feedback delete <id> --yes`7374**Deploying an app for the first time:**751. Check if the app already exists: `dibbla apps list`762. If it does **not** exist, deploy with all required environment variables included in the deploy command — there is no app to attach them to yet:77 ```bash78 dibbla deploy . --alias my-app -m "feat: initial deploy" \79 -e DATABASE_URL=postgres://... -e API_KEY=secret -e NODE_ENV=production80 ```813. If it **already** exists, use `--update` for a zero-downtime rolling update:82 ```bash83 dibbla deploy . --alias my-app -m "fix: resolve 500 on /search" --update84 ```85 To change env vars on an existing app, use `apps update` instead:86 ```bash87 dibbla apps update my-app -e NEW_VAR=value88 ```8990**Key rules:**91- **Every `dibbla deploy` must include `-m "<message>"`.** The value becomes the git commit subject in the app's Dibbla-managed VCS history (and on the GitHub mirror, if configured). Treat it like a git commit: present-tense imperative, under ~72 chars, covering what changed and why — e.g. `-m "fix: handle null org in /api/me"`, `-m "feat: add nightly db backup workflow"`, `-m "chore: bump node to 20.14"`. For retries or mechanical redeploys, still say so explicitly: `-m "redeploy: retry after CF 524"`. Max 500 chars. Never run `dibbla deploy` without `-m`; a blank deploy history is a bug, not a default.92- `--force` causes downtime (tears down and redeploys). Prefer `--update` for existing apps.93- `--force` and `--update` are mutually exclusive.94- Environment variables set via `deploy -e` or `apps update -e` persist across updates — you only need to pass them once.95- **Login guard:** Use `--require-login` to require authentication. Combine with `--access-policy invite_only` to restrict to invited users, or `all_members` for org-wide access. Use `--google-scopes` to request additional Google OAuth scopes (e.g. Drive, Calendar).96- Use `--quiet` / `-q` on `db list`, `db delete`, `db connect` for machine-readable output in scripts.97- `db create --deployment <alias>` scopes the database and its auto-created secret to a specific deployment. The scoped secret is named `DATABASE_URL_<UPPERCASED_UNDERSCORED_NAME>` (e.g. `DATABASE_URL_MY_DB` for database `my_db`), **not** a plain `DATABASE_URL` — app code must read the suffixed env var.98- `db connect` prints a psql-compatible connection string via the Dibbla database proxy. Use `-q` for scripting: `psql $(dibbla db connect mydb -q)`.99- `storage create` provisions an S3 bucket with a **hard quota** and bucket-scoped credentials injected as `STORAGE_<NAME>_*` secrets (`<NAME>` = bucket name uppercased, hyphens → underscores; bucket `my-uploads` → `STORAGE_MY_UPLOADS_ACCESS_KEY_ID` etc.). App code reads those env vars and speaks plain S3. `storage rotate` restarts the bound deployment's services by design (envFrom secrets only refresh on restart) — use `--no-restart` only if you restart yourself. `storage credentials <name> -q` prints eval-able exports for `aws`/`mc`/`rclone`.100- **Document the schema in the database itself.** After creating or migrating tables, set `COMMENT ON DATABASE`, `COMMENT ON TABLE` and `COMMENT ON COLUMN`. Postgres stores these alongside the schema, so unlike a README they cannot drift from it; the console renders them, and its database agent answers from documented meaning instead of inferring from identifiers.101102 A **database or table** comment carries a display name and a description, as Markdown frontmatter over a Markdown body. The name is free of identifier constraints — it's what a person would call the thing:103104 ```sql105 COMMENT ON TABLE articles IS $doc$---106 name: Articles107 ---108 Product master data, one row per article number. Loaded nightly from the109 supplier export; rows are never deleted, superseded ones get `active = false`.110 $doc$;111 ```112113 A **column** comment is one explanatory line, no frontmatter — it renders as a tooltip:114115 ```sql116 COMMENT ON COLUMN articles.confidence IS 'Classification confidence, 0-1. Below 0.6 is reviewed by hand.';117 ```118119 Say what a reader could not guess: units, ranges, what a status value means, which table a loose id points at, whether rows are ever deleted. Restating the column name (`'The article number'`) satisfies nothing. Caps on save: name 80 characters, description 1000, column tooltip 300.120121 Plain text without frontmatter stays valid — it's read as the description with no name, so existing comments keep working. Nothing is enforced at deploy time; an undocumented schema deploys fine, it just makes every answer about it an inference.122123- **524 on deploy ≠ failure.** `dibbla deploy` holds a single HTTP connection during the backend build; builds over ~100s may return a Cloudflare 524 on the client even when the backend succeeds. Wait 2–5 minutes, then run `dibbla apps list` to check. Do **not** retry with `--force` — use `--update` if you must retry.124- **Output modes:** `dibbla deploy` streams a live buildkit-style step view when stdout is a TTY and switches to ISO-timestamped log lines (`<ts> [info] build step=N/M …`) when stdout is piped or in CI. Add `--quiet` for a single-line success/failure (script-friendly) or `--json` for a single structured object on stdout. On build failure the non-TTY mode also writes one structured JSON line to **stderr** with shape `{"event":"deploy.failed","step":"go-build","step_index":N,"step_count":M,"errors":[{file,line,col,message}],"retry_cmd":"…","api_error_code":"BUILD_FAILED"}` — coding agents should read this from stderr to locate failing files without scraping the human-readable build output. Add `--verbose-build` to ship the full server build log instead of the elided tail when parsed compile diagnostics aren't enough. Build failures exit `2`; other errors exit `1`.125- **`.dibblaignore` controls Dibbla's managed VCS history** — that filter alone, and nothing else. The backend always strips `.env`, `node_modules/`, `dist/`, `*.pem`, `*.key` and similar from VCS and reports each hit in `DeployResponse.vcs_filtered` as a warning. Adding those paths (or any generated/large artifact) to `.dibblaignore` at the deploy root silences the warning and keeps VCS clean. Per-file and per-commit size caps are hard rejections — committing a large build artifact will fail the deploy with `ErrCodeVCSFiltered`; the fix is to add the path to `.dibblaignore`. Full details in `reference.md` → deploy → `.dibblaignore`.126- **A separate, silent server-side filter strips regenerable directories out of the Docker build context**: `node_modules/`, `.git/`, `__pycache__/`, `.venv/`, `vendor/`, `.next/`, `dist/`, `.cache/`. Never write a Dockerfile that `COPY`s one of these — `COPY vendor/` and `COPY dist/` fail on the platform while building fine locally. Regenerate them in the build (`go mod download`, `npm ci && npm run build`). `COPY --from=<stage>` is unaffected. `.dibblaignore` has no bearing on this. Full list, rationale and worked Dockerfiles in `reference.md` → deploy → "Build-context strip (`skippedDirs`)"; `guardrails.md` Check 9 catches it before you deploy.127- App database connections go through the Dibbla **database proxy** with a **publicly-valid TLS cert**. Use the injected `DATABASE_URL_<NAME>` as-is (`sslmode=require`) — no `rejectUnauthorized: false`, no `sslmode=no-verify`. See `reference.md` "TLS for application database clients".128129**Designing a multi-service manifest:** Before authoring a `dibbla.yaml`, work through these design questions and confirm a plan with the user. Skipping this step at design time leads to retrofits that touch every consumer service (env vars, `depends_on`, service-discovery references), so it's worth the 60 seconds upfront.130131- *Which services should exist in only some envs?* (e.g. an inline DB container in dev, a managed/external DB in prod) → put `profiles: [dev]` on the env-specific service. Decide this **upfront** because consumers will need env-aware values for the URL/host that points to it.132- *Which fields differ across envs?* (`replicas`, `image`, `MONGO_URL`, `LOG_LEVEL`, …) → use **env-aware** field maps (§ 6 in [manifest.md](manifest.md)). Different mechanism from profiles: profiles toggle whether a service exists at all; env-aware fields shape an existing service.133- *Where will the data layer live in prod?* If managed/external, the consumer needs an env-aware `MONGO_URL` / `DATABASE_URL` (`default:` → external value, `dev:` → `${DIBBLA_SVC_*}`) **and** the inline copy needs `profiles: [dev]`. The two mechanisms are paired — see § 7 in [manifest.md](manifest.md) for a worked example.134- *How will the user iterate locally?* The platform does **not** run `dibbla.yaml` locally — there is no `dibbla up`. Mirror the manifest into a `docker-compose.yml` next to it for tight inner-loop dev (see [examples.md](examples.md) "Local iteration with docker-compose"). The two diverge in details (no `${DIBBLA_SVC_*}`, no NetworkPolicy, no env-aware resolution) but match in shape.135- *Will any public service be sensitive in prod?* (admin UIs, debug consoles, mail catchers, internal dashboards) → per-service `auth:` block with `require_login: true` and an `access_policy:`, or gate the whole service with `profiles: [dev]`. Shipping an admin UI publicly without auth is a top OWASP-class mistake; the guardrails check enforces this in [guardrails.md](guardrails.md).136- *Are any services stateful — databases, brokers, anything with on-disk state?* If yes, set `stateful: true` and declare at least one `volumes:` entry. The renderer switches to a StatefulSet + headless Service so each pod has stable identity and its own PVC. To expose the service to clients outside the cluster (a `mongosh` from your laptop, an external Redis client), add a `routes:` entry with `type: tcp` + `tls: edge`. **Limit:** TLS-on-connect protocols only — Mongo, Redis-with-TLS, AMQPS, NATS-with-TLS, Kafka-with-TLS — Postgres and MySQL use STARTTLS-style upgrades that don't carry SNI in the first packet, so they're deferred. **Footgun:** `replicas > 1` on a stateful service yields N independent pods each with its own PVC and its own data; the platform does **not** bootstrap clustering protocols. Use `replicas: 1` unless you're wiring clustering yourself. Full schema in [manifest.md § 10.5](manifest.md).137138**Pre-deploy guardrails (CLI-enforced):** Before calling `dibbla deploy`, you MUST complete the pre-deploy checklist and present findings to the user. Always wait for explicit user confirmation before deploying or fixing issues — never deploy autonomously. The guardrails workflow writes a `REVIEW.md` file to the project root, and **`dibbla deploy` will refuse to upload without it** (and without a user handbook carrying a valid `subtitle:` — the gate also blocks a missing, empty, placeholder, or over-140-byte subtitle). The platform also reads `REVIEW.md` and displays a review status indicator in the dashboard. The `--skip-review` flag exists for humans making one-line fixes; agents must run the checklist instead of using the flag. See [guardrails.md](guardrails.md) for the full checklist.139140**User handbook (MANDATORY, CLI-enforced).** Every deployable app MUST ship user-facing documentation. `dibbla deploy` refuses to upload unless the project root contains **either** a `docs/` folder with at least `docs/index.md`, **or** a single `APP.md` at the root. If neither exists, do **not** deploy — generate a starter handbook from the templates in [user-docs.md](user-docs.md), ask the user to confirm content, then deploy. The handbook is for the **end user of the deployed app** — not developers, not operators. It is rendered in `app.dibbla.com` under "My Apps → {alias}" and is the only documentation surface end users see. Never put dev-stack notes, framework names, deploy commands, env-var lists, infra details, or other technical metadata in there. See [user-docs.md](user-docs.md) for tone, file layout, cross-linking syntax, paste-ready templates, and the full list of what NOT to include.141142**Workflows:** A workflow is a typed DAG of function calls — nodes name a `function` from the registry, edges carry data port-to-port, an `api` node + `api_response` node make it callable over HTTP. Author in **slim YAML** (the format `wf get`/`wf create -f` consume); never hand-write the verbose React-Flow JSON. Minimal shape:143144```yaml145name: my_workflow146nodes:147 - {id: api_input, type: api, inputs: [question], outputs: [question]}148 - {id: greet, type: function, function: handlebars_template,149 server: function-server, inputs: {script: "Hello {{question}}!"},150 outputs: [error, output]}151 - {id: api_response, type: api_response, linked_to: api_input, inputs: [response]}152edges:153 - api_input.question -> greet.question154 - greet.output -> api_response.response155```156157Before authoring anything non-trivial, run `dibbla fn list` to see what functions exist and `dibbla wf get <existing> -o yaml` on a similar workflow for shape — the function registry, not the YAML, is the source of truth. Pick the iteration loop that matches the change size: small tweak → patch HEAD with `nodes add`/`edges add`/`inputs set`/`tools add`; structural change → `wf get … -o yaml` → edit → `wf update -f`. Always `dibbla revisions create <wf>` before either; patches are not auto-snapshotted and `revisions restore` overwrites HEAD (it's not a checkout). For the complete model — node-type roles, the agent+tool pattern, all 13 validator errors and their fixes, execution/HTTP semantics, and the three canonical workflow shapes (transform, agent+tools, multi-stage pipeline) — see [workflows.md](workflows.md).158159**Workflow gotchas that bite once:**160- **Pick `reasoning_agent_function` for new agents** — `reasoning_agent_with_thread` has been observed to silently return empty responses with current Claude models. Do **not** wire `agent.error -> api_response.error` to surface failures: a wired input gates the response node and a successful run never produces `error`, so the workflow hangs. Use `wf execute --follow` or `wf logs <runId>`.161- **One node, one role: never both tool AND data input.** If a node feeds an agent's `tools:` list AND has a data edge into the same agent (or transitively), the auto-generated tool-connection edge plus your data edge close a cycle — pre-flight refuses the run with `422 CYCLE_DETECTED`. Pick one role; the canonical "inject this into the system prompt" shape is `data -> handlebars_template -> agent.system_message`, with the data source NOT in `tools:`. See workflows.md §6 and the worked example in examples.md.162- **Use `--follow` for the first execution after any workflow change.** Silent function failures used to surface only after the 30-minute server timeout. The stuck-run watchdog now emits a WARN-level `run is not making progress` within ~30s with a per-input `diagnosis` field — visible immediately in `dibbla wf execute … --follow`. Treat `--follow` as the smoke-test default, not a debugging escape hatch.163- **`wf update` is no longer last-write-wins.** The CLI sends `If-Match` with the current ETag automatically; concurrent edits return `412` with `current_etag`/`received_etag` in the body. Pull, merge, retry — or `--force` to overwrite (deletes whatever the other writer just shipped, so use sparingly).164- **YAML types must match the function's reflected Go types.** Send `triggered: true` not `triggered: "true"`; `42` not `"42"`. `dibbla fn get` is now ground truth (`boolean` / `integer` / `float` / `string`); older cached YAML may have stringly-typed slots — regenerate from `fn get` when in doubt.165- **Production callers must use the gateway URL**, not the URL `wf api-docs` prints. Rewrite host: `https://workflow-server.<internal>/api/execute/<name>/<urlid>` (shown by `api-docs`, internal only) → `https://api.dibbla.com/api/wf/execute/<name>/<urlid>` (gateway, accepts `Authorization: Bearer ak_<workflow-api-key>`).166- **`<urlid>` can go silently stale on `wf update`.** The converter preserves a node's UUID only when its semantic id (function name or label) matches one in the existing workflow. Renaming an api node, swapping its function, or restructuring its `inputs:` can regenerate the id with no warning. Verify with `wf api-docs` after any update; if the url id changed, update production callers (or rebuild + redeploy if it's baked at build time).167- **Node ids collapse to the function name on `wf create`.** Don't pick custom ids; refer to tools by function name.168- **Result cache is 1 hour** on `reasoning_agent_function`. During iterative testing, vary the input or use a `*_no_cache` variant.169- **Always wrap workflow fetches in an `AbortController` with a 30–60s timeout** and log before/after — Node's default 5-minute timeout makes failures look like hangs.170- **`function execution failed` is opaque on its own.** The CLI message is identical for panics, returned errors, and input-deserialization failures. Suspect input-type mismatch first; the actual Go error is in the go-toolserver pod's stdout (`kubectl logs <go-toolserver-pod>`). See workflows.md §11 "Diagnosing a hanging `wf execute`" for the decision tree.171172**Run monitoring & async execution:**173- `dibbla wf execute` is **synchronous by default** — it blocks until the workflow's `api_response` node fires (server-side timeout: 30 min). For long-running agent workflows or fire-and-forget batches, use `--async` to get back `response_metadata` immediately while the run continues in background. Tail it later with `wf logs <runId> --follow` and fetch the final output with `wf runs output <runId>`.174- `dibbla wf execute --follow` (`-f`) is the one-liner for interactive debugging: starts the run async, tails live logs to stdout, then prints the api_response payload after the server-emitted `run_completed` sentinel. Exits 0 on completion.175- `dibbla wf logs <runId>` works on any run. Live runs stream until completion; finished runs return historic + sentinel and exit immediately. Persistence policy: WARN/ERROR + the `run_completed` row are persisted; INFO/DEBUG are live-only — a quiet completed run will tail to essentially just `run completed`. For the full transcript of a finished run, use `wf runs output <runId>` instead.176- `dibbla wf runs list` (`-w <name>` to filter, `-n <N>` to page; server caps at 500) is the way to find a recent run id without copy-pasting from the dashboard or the DB.177- **Short flag `-f` differs by command:** on `dibbla logs` (app-logs) and `dibbla wf logs`, `-f` is `--follow`. On `dibbla wf execute`, `-f` is also `--follow` — but `--file` had to give up its short alias and uses `-F` instead. Don't suggest `-f payload.json` for `wf execute`; use `--file payload.json` or `-F payload.json`.178179**Building Go workers (sdk-go):** The `github.com/dibbla-agents/sdk-go` Go SDK is how workers register custom **functions** and **jobs** with the platform. A worker is a long-lived gRPC client: `sdk.New(...)` → `server.RegisterFunction(...)` and `server.RegisterJob(...)` → `server.Start()` (which blocks forever). External user modules are restricted to `sdk.NewSimpleFunction[In, Out]` because the advanced `Function[In, Out]` handler signature exposes `internal/types` and `internal/state` — Go's `internal/` rule blocks those imports from any module other than `sdk-go`. The `JobHost` abstraction was removed; jobs register directly via `server.RegisterJob(handler)`. Once the worker is connected, its functions appear in `dibbla functions list` and become callable from workflow YAML by `(server, function)` pair (see [workflows.md](workflows.md) for consumer-side wiring). For the full SDK model — server options, function builders, the `JobHandler` interface, `JobContext` arg helpers, the `Logger` task/progress API, OAuth via `gs.OAuth`, capability providers (rare: replacing the platform's built-in tool_search/memory implementation — never the answer to a plain "add memory" request; see the decision gate in [workflows.md](workflows.md) §6), and gotchas — see [sdk-go.md](sdk-go.md).180181**Non-TTY / agentic invocation:**182- When running from inside Claude Code's `!` prefix, an agent shell, CI with a browser, or any other non-TTY context, use `dibbla login --browser` instead of bare `dibbla login`. The interactive flow needs stdin for the survey picker; `--browser` skips that and goes straight to browser-based OAuth via a localhost callback. Refuses over SSH (CLI ≥ v1.2.20) — the localhost callback can't reach the user's laptop; use `--api-key` instead.183- For true headless (SSH sessions, cloud VMs, CI runners with no local browser), use `dibbla login --api-key <token>` or set `DIBBLA_API_TOKEN` (and optionally `DIBBLA_API_URL`) env vars — the CLI reads env vars in CI automatically. Get a token at https://app.dibbla.com/api-keys — or, for a **self-hosted or customer instance, at that instance's own portal** (`app.<its-domain>/api-keys`), not Dibbla's. A token minted on the wrong instance will not work there.184- **Several servers at once (named contexts, CLI ≥ the release carrying P-0011).** `dibbla login --context <name> --api-key=<t> --api-url=<url>` *adds* a context instead of replacing whatever login the machine had, so an agent session can hold prod and a customer instance simultaneously and neither destroys the other. Add `--no-switch` to store it without making it the one in use. Then `--context <name>` on any command, or `DIBBLA_CONTEXT=<name>` for a shell, targets it for that call without changing the selection; `dibbla context use <name>` changes the selection.185 - **`dibbla login --context x` does NOT set a read override for that invocation.** `login` and `logout` each define their own local `--context`, naming the context to *write*, which shadows the global one naming the context to *read*. This is deliberate and is the one place the flag means something different.186 - **Explicit `DIBBLA_API_TOKEN`/`DIBBLA_API_URL` — from the shell or from `./.env` — still win over every context.** An agent setting those for a one-shot call is completely unaffected by contexts, and so is CI: with either set, no config file and no keyring is read at all.187 - A mistyped `--context` is an **error**, not a silent fallback to production. So is a `config.yaml` that will not parse.188 - The context in use is mirrored to the legacy `~/.config/dibbla/credentials.env`, so a `dibbla` binary older than contexts — and any script that sources that file — follows `dibbla context use` instead of silently staying on the previous server.189- **Cloud VMs / SSH / Docker (no keyring):** From CLI ≥ v1.2.21, `dibbla login --api-key=<t>` automatically falls back to a user-level credentials file at `~/.config/dibbla/credentials.env` (mode 0600) when the OS keyring is unavailable or absent (a keyring that is present but *locked* is a different case — see the locked-keyring bullet below) — no `--no-keychain` or `--write-env` flag needed. The file behaves like the keyring (machine-wide, persists across `cd`); subsequent `dibbla *` calls from any directory read from it. Combine with `--write-env` to *also* land creds in `./.env` for project-scoped reads. On older CLIs you still need `--api-key=<t> --api-url=<url> --write-env --no-keychain` to land creds in `./.env` only.190- **Keyring present but LOCKED (distinct from absent):** On Linux desktops the freedesktop Secret Service may be installed but the `login` collection is locked — common in non-TTY/agent shells or before a graphical session has unlocked gnome-keyring. Symptom: `failed to unlock correct collection '/org/freedesktop/secrets/collection/login'`. In this state the automatic credentials-file fallback does **not** trigger (the keyring is detected as present), and `--write-env` alone still fails because the keyring write is attempted before the env write. Force the CLI to bypass the keyring entirely: `dibbla login --api-key=<t> --api-url=<url> --no-keychain --write-env` — this lands creds in `./.env` (project-scoped) regardless of CLI version. Note `--no-keychain` *without* `--write-env` validates but persists nothing, so a later `dibbla status` reports "not logged in".191- **`.env` in CWD is read by every command, including `login`.** Put `DIBBLA_API_TOKEN=…` and `DIBBLA_API_URL=https://api.dibbla.com` in `./.env` and every `dibbla` invocation from that directory targets that server and token — no `login` call needed. Shell-exported vars still win over `.env` (godotenv does not overwrite). Requires CLI ≥ v1.2.4.192- `DIBBLA_AUTH_SERVICE_URL` is an internal compat alias for `DIBBLA_API_URL`, injected by the steprunner into child processes launched by `dibbla run`. Users should put `DIBBLA_API_URL` in `.env`; `DIBBLA_AUTH_SERVICE_URL` exists so child processes see the same server via the desktop/steprunner convention name.193194**Running task files and templates:**195- **To scaffold a published template, reach for the sugar first:** `dibbla template list` to find the id, then `dibbla template install <id>`. Don't hand-build a `github.com/.../blob/...` raw URL — that's the slow, error-prone path. Drop to `dibbla run <https-url>` only when you genuinely have a raw bootstrap URL that isn't in the manifest. (As a safety net, `dibbla run` auto-rewrites a GitHub `/blob/` web URL to its `raw.githubusercontent.com` form.)196- `dibbla run <path>` executes a `dibbla-task.yaml` pipeline locally. Tool checks, shell commands, background dev servers, and browser-open side effects are all possible — the task file becomes shell under the user's account.197- `dibbla run <https-url>` fetches and executes a yaml from the network. **This is equivalent to `curl | bash`** — only run yamls from sources the user trusts (e.g. `github.com/dibbla-agents/*`). Work-dir defaults to the user's invocation CWD, so bootstrap clones land in the expected directory rather than in a temp dir.198- `dibbla template install <id>` is ergonomic sugar over `mkdir ./<template-path> && cd ./<template-path> && dibbla run <bootstrap-url>`. It refuses if the destination directory exists; pass `--force` to reuse. Use `dibbla template list` to see available ids.199- Prefer `dibbla run --preview` or `dibbla template list` before actually running, so the user can see what will execute.200201**Installing this skill into a project (so other agents see it too):**202- `dibbla skills install dibbla` writes the skill files into `./.claude/skills/dibbla/` plus `AGENTS.md` and `GEMINI.md` pointers at the project root. Every major coding agent then picks up the guidance automatically — Claude Code via its native skill path, Cursor/Opencode/Codex/Copilot/Windsurf/Aider via `AGENTS.md` (the 2026 open standard), Gemini CLI via `GEMINI.md`.203- The skill content is embedded in the CLI binary (`go:embed`), so **`dibbla skills install` needs no network** and the installed skill is locked to the CLI version the user has. Run `dibbla --version` to see which one.204- The same files are also published over HTTP at `https://dibbla.com/.well-known/agent-skills/index.json`, for an agent that has no CLI. Each entry carries a `sha256:` digest — verify it. The published bytes are mirrored from a tagged CLI release, so a given URL still corresponds to one specific `dibbla` version rather than a floating latest.205- Flags: `--user` installs into `$HOME` for machine-wide coverage instead of the current directory; `--no-agents` skips `AGENTS.md` and `GEMINI.md` (Claude Code only); `--force` overwrites skill files that have been edited locally. Unknown files inside `.claude/skills/<id>/` are always preserved.206- The AGENTS.md / GEMINI.md pointer block is marker-delimited (`<!-- >>> dibbla skill >>> -->` … `<!-- <<< dibbla skill <<< -->`) so existing AGENTS.md content outside the markers is preserved byte-for-byte across reruns.207- Re-running is idempotent — if nothing changed, nothing is rewritten (no mtime bump). Use `dibbla skills list` to see what skills the current CLI ships.208209## Additional resources210211- **AI gateway (deployed apps):** see [ai-gateway.md](ai-gateway.md) for `https://ai.dibbla.com`, the `X-Dibbla-App` attribution header, the auto-injected `DIBBLA_AI_GATEWAY_URL` env var, OpenAI/Anthropic SDK snippets that swap the base URL, and the per-org dashboard. Read this whenever an app needs to call an LLM and the user wants the call audited / attributed under their Dibbla org.212- **AI gateway (laptops & IDE assistants):** for pointing Claude Code, Cursor, opencode, Cline, Windsurf, or Zed at the gateway from a developer machine — and for `dibbla ai url|env|test` — install the dedicated `dibbla-ai-gateway` skill: `dibbla skills install dibbla-ai-gateway` (then read its SKILL.md).213- **Platform compatibility:** see [platform.md](platform.md) for the Dockerfile contract, port-matching, runtime environment, managed Postgres TLS handling, secrets/env-var injection, the auth-header contract (`X-User-*` headers and Google OAuth scope brokering), the upload boundary, the multi-service runtime contract (§ 8.5), and the pre-deploy compatibility checklist. Read this when working in a Dibbla-connected project on Dockerfile, `.dibblaignore`, auth integration, or deploy-readiness questions.214- **Multi-service manifest schema:** see [manifest.md](manifest.md) for the full `dibbla.yaml` schema — services, jobs, env-aware fields, profiles, service discovery (`DIBBLA_SVC_*`), `expose_to`/NetworkPolicy, volumes, **stateful services + TCP routes (§ 10.5)**, init containers, healthchecks, multiple public services, custom domains, cron, build-time secrets, quotas, error codes, and a worked end-to-end example. Read this whenever the user is authoring or reviewing a manifest, or asking a "how do I run X alongside Y in one deploy" question, or "how do 215216…(truncated)