From 072c8337b7bb5433c5e0addec05c4cb95541c2de Mon Sep 17 00:00:00 2001 From: soustruh Date: Wed, 30 Sep 2026 15:25:43 +0200 Subject: [PATCH 1/2] chore(release): 0.96.0 --- .claude-plugin/marketplace.json | 2 +- docs/TUTORIAL.md | 2 +- plugins/kbagent/.claude-plugin/plugin.json | 2 +- plugins/kbagent/agents/keboola-expert.md | 26 +-- .../skills/kbagent-cicd-migration/SKILL.md | 2 +- .../kbagent-promotion-pipeline/SKILL.md | 2 +- .../scripts/generate_promotion_pipeline.py | 2 +- .../kbagent/references/branch-workflow.md | 2 +- .../kbagent/references/commands-reference.md | 26 +-- .../kbagent/references/data-app-workflow.md | 8 +- .../skills/kbagent/references/gotchas.md | 54 ++--- .../references/permissions-workflow.md | 4 +- .../kbagent/references/sync-rows-workflow.md | 2 +- .../kbagent/references/sync-workflow.md | 30 +-- .../kbagent/references/workspace-workflow.md | 2 +- pyproject.toml | 2 +- src/keboola_agent_cli/changelog.py | 191 ++++++++++++++++++ src/keboola_agent_cli/commands/context.py | 22 +- uv.lock | 2 +- 19 files changed, 287 insertions(+), 96 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 914eabdda..23b8d6f33 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -10,7 +10,7 @@ "plugins": [ { "name": "kbagent", - "version": "0.95.0", + "version": "0.96.0", "source": "./plugins/kbagent", "description": "DEPRECATED — install from keboola/ai-kit: /plugin marketplace add keboola/ai-kit && /plugin install kbagent@keboola-claude-kit — AI-friendly interface to Keboola Connection projects — explore configs, jobs, lineage, sync configs as files, manage dev branches, and debug SQL in workspaces", "category": "development" diff --git a/docs/TUTORIAL.md b/docs/TUTORIAL.md index 4f4734a45..98059a4ad 100644 --- a/docs/TUTORIAL.md +++ b/docs/TUTORIAL.md @@ -512,7 +512,7 @@ git add -A && git commit -m "initial sync" `manifest.json`'s `ignoredComponents` field (since 0.91.0) lets you exclude project-specific components from every sync operation, on top of the always-ignored `keboola.sandboxes` and `keboola.mcp-server-tool`. -`sync init --with-workspaces` *(since vNEXT)* opts a tree in to syncing its +`sync init --with-workspaces` *(since 0.96.0)* opts a tree in to syncing its shared SQL workspaces (`keboola.sandboxes`), config only. What you end up with on disk: diff --git a/plugins/kbagent/.claude-plugin/plugin.json b/plugins/kbagent/.claude-plugin/plugin.json index 822197443..5b5d21508 100644 --- a/plugins/kbagent/.claude-plugin/plugin.json +++ b/plugins/kbagent/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "kbagent", - "version": "0.95.0", + "version": "0.96.0", "description": "AI-friendly interface to Keboola Connection projects — explore configs, jobs, lineage, sync configs as files, manage dev branches, and debug SQL in workspaces", "author": { "name": "Keboola", diff --git a/plugins/kbagent/agents/keboola-expert.md b/plugins/kbagent/agents/keboola-expert.md index fad6f41d6..804e9d78c 100644 --- a/plugins/kbagent/agents/keboola-expert.md +++ b/plugins/kbagent/agents/keboola-expert.md @@ -116,7 +116,7 @@ been retired, so its absence is NOT a promise (see §1 Rule 6). | Duplicate a configuration | `kbagent config clone --project P --component-id C --config-id K --name N [--target-project P2] [--secret PATH=VALUE]` (0.84.2+) -- same-project is a server-side copy (rows + `KBC::` values survive); cross-project needs a `--secret` per encrypted path, listed by `--dry-run` | -- | rebuilding the body from `config detail` (drops `runtime` / `storage` / `authorization` siblings SILENTLY -- a lost `runtime.parallelism` means the job runs single-threaded) | | Override the auto-derived output bucket | `kbagent config set-default-bucket --bucket in.c-name` (read-modify-write, preserves siblings; `--clear` removes it) | `kbagent config update --set 'storage.output.default_bucket=in.c-name'` | full-config replace with `--configuration` (wipes other storage keys) | | Cross-project migration | `kbagent sync pull` + edit files locally + `kbagent sync push --dry-run` | -- | per-resource REST loops | -| Provision a new project from a golden reference | `kbagent sync clone --source ./golden --target ALIAS --target-dir ./clone [--bucket-map F --variable-values F --instance-rename F]` -- copy + parameterize + push fresh; remaps flow/orchestrator task configIds, schedule targets, variable and shared-code links; read `warnings[]` of the first run, a re-run does not repeat them (vNEXT+: tasks running a config outside the tree, copied `KBC::` values, undeployed data apps, inactive schedules); recreates the reference's storage buckets in the target by default (`--no-create-buckets` to skip; buckets only, not table data; a linked bucket is linked to the same source as in the reference and listed in `linked_buckets`); needs a FRESH target. `--branch` defaults to the target's production branch | -- | manual copy + id-surgery + `sync push` per resource | +| Provision a new project from a golden reference | `kbagent sync clone --source ./golden --target ALIAS --target-dir ./clone [--bucket-map F --variable-values F --instance-rename F]` -- copy + parameterize + push fresh; remaps flow/orchestrator task configIds, schedule targets, variable and shared-code links; read `warnings[]` of the first run, a re-run does not repeat them (0.96.0+: tasks running a config outside the tree, copied `KBC::` values, undeployed data apps, inactive schedules); recreates the reference's storage buckets in the target by default (`--no-create-buckets` to skip; buckets only, not table data; a linked bucket is linked to the same source as in the reference and listed in `linked_buckets`); needs a FRESH target. `--branch` defaults to the target's production branch | -- | manual copy + id-surgery + `sync push` per resource | | Retype table columns | fetch types via `workspace query`, write a transformation that produces a typed output table, then `kbagent storage swap-tables` to flip it into the original name. See [typify-table-workflow.md](../skills/kbagent/references/typify-table-workflow.md) | -- | `POST /v2/storage/buckets/.../tables-definition` (REST) plus manual config rewrites | | Create typed table with native types | `kbagent storage create-table --column pk:VARCHAR(40) --column amount:NUMBER(18,2) --not-null pk --default amount=0` | -- | re-creating via raw REST to `tables-definition` | | Add one column to an existing table | `kbagent storage add-column --project P --table-id in.c-foo.data --column status:VARCHAR(20) [--not-null] [--default active]` -- synchronous, same `name:TYPE(length)` grammar as `create-table` | -- | re-creating the whole table to add a field (loses data / PK / dependents) | @@ -124,7 +124,7 @@ been retired, so its absence is NOT a promise (see §1 Rule 6). | Repartition / recluster a populated BigQuery table | `kbagent storage create-table --source-table-id --time-partitioning-type DAY --time-partitioning-field created_at --clustering-field tenant_id` (BigQuery only) to copy the data into the new layout, then `swap-tables` to flip it in. `--source-table-id` derives the schema, so `--column` is forbidden. Range partitioning: all four `--range-partitioning-*` flags together. VERIFY the result with `storage table-detail --json` -> `.definition.timePartitioning` / `.clustering` (0.88.0+) -- `create-table` only echoes the layout you REQUESTED, so it proves nothing. See [storage-types-workflow.md](../skills/kbagent/references/storage-types-workflow.md) | -- | `CREATE TABLE ... AS SELECT` in a workspace (drops NOT NULL + primary key) | | Re-seed a table without losing schema / PK / dependents | `kbagent storage truncate-table --project P --table-id in.c-foo.data [--dry-run] [--yes]` -- rows only, uniformly async-via-job on every branch; batch via repeated `--table-id` | -- | drop + recreate (loses descriptions, PK, sharing edges, and breaks every downstream reference); deleting rows via raw SQL in a workspace (bypasses the Storage audit trail) | | Back up / restore a table around a risky change | `kbagent storage snapshot-create --table-id ...` then, to restore, `kbagent storage table-from-snapshot --snapshot-id ID --bucket-id B --name NEW` -- restore is always a NEW table (`--name` REQUIRED, no overwrite): verify it, then `swap-tables`. See [snapshot-workflow.md](../skills/kbagent/references/snapshot-workflow.md) | `storage snapshots` / `snapshot-detail` to find one | exporting to CSV as a "backup" (loses column types + PK); `create-table --snapshot-id` (not a thing) | -| Debug a failed job | `kbagent job detail --project P --job-id J --log-tail-lines 200 --json` | `kbagent workspace from-transformation` for SQL repro (on a dev branch it reads the branch's transformation only since vNEXT, #807; older versions load production's input mapping) | "I think the issue is..." without reading logs; a new job run only to see its logs (a writer can send its data again) | +| Debug a failed job | `kbagent job detail --project P --job-id J --log-tail-lines 200 --json` | `kbagent workspace from-transformation` for SQL repro (on a dev branch it reads the branch's transformation only since 0.96.0, #807; older versions load production's input mapping) | "I think the issue is..." without reading logs; a new job run only to see its logs (a writer can send its data again) | | Ad-hoc SQL / row-count / type audit | `kbagent workspace create` + `workspace load` (since 0.91.0 auto-CLONEs eligible tables, else COPY; `--load-type` forces one and fails loudly if ineligible; COPY > 1 GiB needs `--force` outside a TTY; on a `--timeout` [default 300s] the job keeps running server-side and now exits 4/retryable, not 1 -- retry or poll `GET /v2/storage/jobs/{id}`, don't treat it as a hard failure) + `kbagent workspace query --sql "..."` -- results are inline and fast but **capped at `--limit`, default 500**: check `statements[].truncated` / `total_rows`, use `COUNT(*)` for counts, `--full` for the complete set | `kbagent workspace from-transformation` for existing-transform debugging; `workspace list --qs-compatible` for data-app reuse; read-only input-mapping (`KBC__`) to query prod with no load at all | trusting a default `SELECT *` as the full result; querying Storage via raw Snowflake credentials outside the workspace abstraction | | Export a FILTERED or INCREMENTAL slice of a table (no workspace) | `kbagent storage download-table --table-id ... --where-column status --where-value active [--where-operator eq\|neq] [--changed-since "-2 days"]` -- server-side filter on the credential-only export path | `kbagent workspace query` with a `WHERE` clause when you need real SQL | downloading the whole table then filtering locally | | Run Keboola SQL / read-write Storage Files from INSIDE a Python process you control | `from keboola_agent_cli import Client` -- stateless `Client(url, token)`; `.query(workspace_id, sql)`, `.files.upload/.read_bytes/.list`; no subprocess, no `serve`, no config-dir. See [library-workflow.md](../skills/kbagent/references/library-workflow.md) | the CLI or `kbagent serve` REST when you are NOT already inside Python | shelling out to the `kbagent` binary from Python you control; using it for open-ended exploration (fixed set of typed ops) | @@ -141,7 +141,7 @@ been retired, so its absence is NOT a promise (see §1 Rule 6). | Bring a new data app online | `kbagent data-app create --project P --name N --slug S --git-repo URL [--git-pat-env VAR \| --git-public]` -- Storage access is ON by default on 0.87.0+ (`--no-workspace` opts out; on <= 0.86.0 patch `runtime.workspace.enabled` after create or it reads NOTHING) -- or `--use-managed-git-repo` for an empty Keboola-hosted repo (mutually exclusive; forces `--no-deploy`; then `git-credentials-create` + push + `deploy`). See [data-app-workflow.md](../skills/kbagent/references/data-app-workflow.md). **Authoring the repo itself is a different contract** (nginx `listen 8888`, no `[program:nginx]`, health check polls `GET /`) owned by Keboola's `dataapp-developer` skill in `keboola/ai-kit` -- read it before writing `keboola-config/`; `validate-repo` checks only a subset, so 0 BLOCKING does not promise the app starts | `config new --component-id keboola.data-apps` + `encrypt values` + raw `POST /apps`, only for custom shapes | `PATCH desiredState=running` without `configVersion` + `restartIfRunning` (pins to the v2 empty shell; errors `dataApp.git.repository is required`) | | Roll out / wake / pause / tear down a data app | `data-app deploy --wait` after ANY config change (sends the `{desiredState, configVersion, restartIfRunning}` trio); `data-app start` wakes a parked app without bumping the version; `data-app stop` pauses; `data-app delete` is irreversible and cascades to the Storage config | -- | `config update` then `job run` (data apps are not jobs); deleting the `keboola.data-apps` config by hand (orphans the deployment record) | | Debug a data app (failed deploy or runtime crash) | `kbagent data-app runs --app-id N` FIRST -- lists deploy attempts with `failure_reason` + `startup_logs`, and works on failed/never-started apps where `data-app logs` 400s | `kbagent data-app logs --app-id N [--lines N \| --since ISO8601]` for a running container's tail (may echo runtime secrets) | opening the UI "Terminal Log" tab; concluding anything from an empty log grep | -| User needs a data-app password (vNEXT+) | Recommend that the user runs `kbagent data-app password --project P --app-id N` (`data-app deploy ... --wait` only when a deploy is needed anyway -- it restarts the app) in their OWN terminal window (not through you, not through `!` mode) and presses `c`; or offer to run it with `--copy` (+ `--wait` on create / deploy; the password replaces the clipboard content, never enters the chat). `password_delivered_to: null` -> give the user `ui_url` | the Keboola UI page at `ui_url` (Open App); reset the password there | `--reveal` unless the user asks (warn first: the password goes into the chat history); reading the clipboard; `kbagent http` / curl / `serve` `reveal=true`; asking the user to paste it; running it on < vNEXT (it prints the password) | +| User needs a data-app password (0.96.0+) | Recommend that the user runs `kbagent data-app password --project P --app-id N` (`data-app deploy ... --wait` only when a deploy is needed anyway -- it restarts the app) in their OWN terminal window (not through you, not through `!` mode) and presses `c`; or offer to run it with `--copy` (+ `--wait` on create / deploy; the password replaces the clipboard content, never enters the chat). `password_delivered_to: null` -> give the user `ui_url` | the Keboola UI page at `ui_url` (Open App); reset the password there | `--reveal` unless the user asks (warn first: the password goes into the chat history); reading the clipboard; `kbagent http` / curl / `serve` `reveal=true`; asking the user to paste it; running it on < 0.96.0 (it prints the password) | | Manage data-app runtime secrets | `kbagent data-app secrets-set --app-id N --secret '#KEY=VAL'` then `data-app deploy --wait` -- per-project KMS, fail-closed, never auto-deploys; `secrets-list` is metadata-only; `secrets-get` yields a literal value only for a PLAIN key; `secrets-remove --yes` is idempotent | `encrypt values --component-id keboola.data-apps` + `config update`, only for a non-standard secrets shape | trying to decrypt anything (there is no decrypt endpoint); `config update --set 'parameters.dataApp.secrets={}'` (drops EVERY secret, not just the named ones) | | Pre-flight a data-app repo, or inspect an existing app's repo | `kbagent data-app validate-repo --git-repo URL --type python-js` BEFORE `create` (BLOCKING/WARN/OK, <=5 GitHub API calls); `data-app git-repo` afterwards for clone-URL introspection | `config detail --component-id keboola.data-apps` then read `parameters.dataApp.git` (Storage view only) | `data-app create --dry-run` as a repo check (it only echoes request bodies); `git-repo` on a never-deployed app (409); `git-credentials-create` on an external repo (409 -- managed only) | | Developer Portal: register / inspect / update a component | reads `kbagent dev-portal list\|get` (agent-safe); writes `create\|patch\|upload-icon\|publish\|deprecate` need a HUMAN to type a random code on a real TTY -- `--dry-run` is the agent-safe preview | `kbagent serve` `GET /dev-portal/apps` for reads | raw `apps-api.keboola.com`; ANY write from a non-TTY/agent shell (no bypass; exits 6) | @@ -200,7 +200,7 @@ its absence is NOT a promise the entry is version-independent (see §1 Rule 6). or run `project use`. On <= 0.90.1 the same commands silently used the FIRST registered project and ignored the pin (issue #684). gotchas.md. -**`--project` given a numeric project ID (vNEXT+)** +**`--project` given a numeric project ID (0.96.0+)** - `--project`, `KBAGENT_PROJECT`, `project use`, the other alias options (`config clone --target-project`, `sync clone --target`, `semantic-layer promote` / `diff` project options, `auth * --stack`) and serve `{project}` @@ -210,16 +210,16 @@ its absence is NOT a promise the entry is version-independent (see §1 Rule 6). (`CONFIG_ERROR`) listing them -- pick one; only a lone session alias on one stack wins by itself. Output names the alias. A digits-only alias that is also another project's ID wins, with a stderr warning naming that project - (also under `--json`). Below vNEXT an ID is "not found". `project add` / + (also under `--json`). Below 0.96.0 an ID is "not found". `project add` / `project create` treat the value as a NEW alias; `lineage show --project` is an offline filter, not translated. gotchas.md (CLI-22). -**Which branch did a command use? (vNEXT+)** +**Which branch did a command use? (0.96.0+)** - Every command that picks a branch names it: `Target: project 'P', branch ID (from 'kbagent branch use')` on stderr, `targets` in `--json` (`branch_source` `active_branch` = chosen by `branch use`). Read it before you report a write as done on production. No `targets` key = no branch was - chosen, NOT production. Below vNEXT, `workspace create` and most config / + chosen, NOT production. Below 0.96.0, `workspace create` and most config / flow writes applied the active branch silently: check `branch list` (Active column) first. gotchas.md (#766). @@ -320,7 +320,7 @@ its absence is NOT a promise the entry is version-independent (see §1 Rule 6). `is_disabled: true` in `_config.yml` = config disabled (absent = enabled); a `never_fetched` warning on diff/push = run `sync pull` first; a non-zero `summary.orphaned` (0.89.0+, #649) = the manifest is targeted at another - branch's tree -- `sync pull` to re-target, never push. Since vNEXT (#792) + branch's tree -- `sync pull` to re-target, never push. Since 0.96.0 (#792) `sync push` deletes only with `--force` (else `skipped_deletions`), and a `- REMOTE DELETED` diff line = deleted on the remote, push never re-creates it: `sync pull`, or `config restore` to keep it. `sync status` @@ -336,7 +336,7 @@ its absence is NOT a promise the entry is version-independent (see §1 Rule 6). action `"ignored"`, distinct from `"removed"`); a stale local dir for an already-ignored component can never classify as `DELETED` -- so delete-dir-then-push is safe for those, but on <= 0.90.1 it still deletes - the config in production. Exception (vNEXT+): a manifest with + the config in production. Exception (0.96.0+): a manifest with `"syncWorkspaces": true` (`sync init --with-workspaces`) syncs shared SQL workspaces, config only; a `push --force` delete of one also deletes its SQL editor sessions (every user's); run `push --dry-run --force` first, it lists them. @@ -386,16 +386,16 @@ its absence is NOT a promise the entry is version-independent (see §1 Rule 6). `config detail` -> `configuration.runtime` FIRST (an empty `data-app logs` grep rules nothing out). `create` defaults it ON at **0.87.0+**; <= 0.86.0 patch + redeploy. -- **`data-app password` keeps the password out of the chat (vNEXT+)**: it +- **`data-app password` keeps the password out of the chat (0.96.0+)**: it never prints it without `--reveal`; the user copies it with `c` in their own terminal, or you run `--copy`. Project token only -- no Manage token. Below - vNEXT the same command PRINTS the password and needs a Manage token: do not + 0.96.0 the same command PRINTS the password and needs a Manage token: do not run it there; send the user to the Keboola UI. Non-password apps (oidc, public) fail with `VALIDATION_ERROR`. `create --wait` / `deploy --wait` deliver the password the same way (the password exists only after a deploy); `--copy` / `--reveal` there need `--wait`. Never redeploy just to get the password. gotchas.md. -- **Data-app type in `sync`**: a `keboola.data-apps` config's runtime type (`python-js` / `streamlit`) lives only on the Data Science `/apps` record. `sync pull` records it as `_keboola.data_app_type`, and `sync push` / `sync clone` send it through `create_app`. A tree pulled before this carries no type (since 0.94.0). A type-less data app is created as `python-js`, the default, with a `data_app_type_default` push warning *(since vNEXT)*. So re-pull the source before you clone a Streamlit app, or set `_keboola.data_app_type: streamlit`. +- **Data-app type in `sync`**: a `keboola.data-apps` config's runtime type (`python-js` / `streamlit`) lives only on the Data Science `/apps` record. `sync pull` records it as `_keboola.data_app_type`, and `sync push` / `sync clone` send it through `create_app`. A tree pulled before this carries no type (since 0.94.0). A type-less data app is created as `python-js`, the default, with a `data_app_type_default` push warning *(since 0.96.0)*. So re-pull the source before you clone a Streamlit app, or set `_keboola.data_app_type: streamlit`. - **`ENCRYPTION_FAILED` on an Azure stack is a VERSION GATE, not a bad token**: <= 0.85.0 rejected the Azure `KBC::ProjectSecureKV::` cipher, so private-repo `create` and `secrets-set` could not work there at all. Upgrade to 0.86.0+; do @@ -447,7 +447,7 @@ its absence is NOT a promise the entry is version-independent (see §1 Rule 6). a failure: follow it with `auth login --stack URL`, and note the alias survives (the sentinel keys on project id + stack, never the session). - **Aliases derive from the project NAME, never the numeric id.** - `--project 9840` resolves only once project 9840 is registered (vNEXT+; + `--project 9840` resolves only once project 9840 is registered (0.96.0+; never below). Use `kbagent project list` or `auth register-projects` to find/register the real alias; it never overwrites an existing registration. - **Session auth covers almost every command** -- only three features still diff --git a/plugins/kbagent/skills/kbagent-cicd-migration/SKILL.md b/plugins/kbagent/skills/kbagent-cicd-migration/SKILL.md index f2d4aa7cb..da3742339 100644 --- a/plugins/kbagent/skills/kbagent-cicd-migration/SKILL.md +++ b/plugins/kbagent/skills/kbagent-cicd-migration/SKILL.md @@ -259,7 +259,7 @@ for the full mapping from the old `secrets.KBC_SAPI_TOKEN_*` / `vars.KBC_*` sche push is fail-closed by design. - `sync push --force` deletes remote configs removed locally. It is wired to the `allow_delete` workflow input (default off). Treat it like the old `--force`. - Without it push deletes nothing and lists the deletions (since vNEXT; older + Without it push deletes nothing and lists the deletions (since 0.96.0; older kbagent deleted without `--force`, so `allow_delete` off did not stop them). - Tokens live **only** in GitHub secrets and are injected as env vars per step; the generated workflows never write a `config.json` to disk. diff --git a/plugins/kbagent/skills/kbagent-promotion-pipeline/SKILL.md b/plugins/kbagent/skills/kbagent-promotion-pipeline/SKILL.md index 8d43324b3..bdda6d8af 100644 --- a/plugins/kbagent/skills/kbagent-promotion-pipeline/SKILL.md +++ b/plugins/kbagent/skills/kbagent-promotion-pipeline/SKILL.md @@ -54,7 +54,7 @@ two Storage API tokens (source, destination): another). Both steps pass `--force`, so a config deleted in the source is deleted in -the destination too. Without it `sync push` deletes nothing *(since vNEXT, +the destination too. Without it `sync push` deletes nothing *(since 0.96.0, #792)*; a pipeline generated before that relied on push deleting without `--force`, so regenerate it or add `--force` to both steps by hand. diff --git a/plugins/kbagent/skills/kbagent-promotion-pipeline/scripts/generate_promotion_pipeline.py b/plugins/kbagent/skills/kbagent-promotion-pipeline/scripts/generate_promotion_pipeline.py index 6a83eb7d0..f9aefa10e 100644 --- a/plugins/kbagent/skills/kbagent-promotion-pipeline/scripts/generate_promotion_pipeline.py +++ b/plugins/kbagent/skills/kbagent-promotion-pipeline/scripts/generate_promotion_pipeline.py @@ -17,7 +17,7 @@ Pushes every pipeline's directory to its DESTINATION project once the PR has merged, with `--force`: a config deleted in the SOURCE is deleted in the DESTINATION too. Without `--force`, `sync push` deletes nothing - (since vNEXT, #792), and the validate dry-run passes it for the same + (since 0.96.0, #792), and the validate dry-run passes it for the same reason, so it shows what the push does. Each pipeline needs two Storage API token secrets (`KBC_TOKEN__SOURCE` / diff --git a/plugins/kbagent/skills/kbagent/references/branch-workflow.md b/plugins/kbagent/skills/kbagent/references/branch-workflow.md index 315a9a6ad..02772eca5 100644 --- a/plugins/kbagent/skills/kbagent/references/branch-workflow.md +++ b/plugins/kbagent/skills/kbagent/references/branch-workflow.md @@ -37,7 +37,7 @@ kbagent --json branch merge --project ALIAS - **Async operations**: `branch create` and `branch delete` are async on the API. kbagent waits for completion (typically 1-3s). No need to poll. - **Merge from the CLI needs the merge-request group** *(since 0.94.0)*: `branch merge` only returns a URL for the Keboola UI (and is deprecated). On a project with the `branches-merge-requests` feature, `kbagent merge-request create` + `merge-request merge` merge via the API with review and conflict resolution -- see [merge-request-workflow.md](merge-request-workflow.md). - **Active branch persistence**: stored in kbagent config. Survives between sessions. -- **See which branch a command used** (since vNEXT, #766): every command that picks a branch prints `Target: project 'P', branch ID 'NAME' (from 'kbagent branch use')` on stderr, and `--json` output carries `targets` (`branch_source`: `explicit`, `active_branch`, `git_mapping`, `manifest`, `merge_request` or `production`). `--dry-run` reports the same target as the real run. Check it before a write. +- **See which branch a command used** (since 0.96.0, #766): every command that picks a branch prints `Target: project 'P', branch ID 'NAME' (from 'kbagent branch use')` on stderr, and `--json` output carries `targets` (`branch_source`: `explicit`, `active_branch`, `git_mapping`, `manifest`, `merge_request` or `production`). `--dry-run` reports the same target as the real run. Check it before a write. - **Config commands respect active branch**: `config list`, `config detail`, and `config search` auto-scope to the active branch. Use `--branch ID` to override. - **Workspaces respect active branch**: `workspace create`, `workspace list`, `workspace detail` and `workspace delete` operate in the active branch context. - **Sync respects active branch**: `sync pull` writes dev branch configs into a separate directory (e.g. `fix-etl/` instead of `main/`). `sync diff` and `sync push` also auto-scope to the active branch. See [sync-workflow.md](sync-workflow.md) for details. diff --git a/plugins/kbagent/skills/kbagent/references/commands-reference.md b/plugins/kbagent/skills/kbagent/references/commands-reference.md index 7aab5b687..cbebfb91d 100644 --- a/plugins/kbagent/skills/kbagent/references/commands-reference.md +++ b/plugins/kbagent/skills/kbagent/references/commands-reference.md @@ -2,7 +2,7 @@ All commands support `--json` for structured output. Multi-project flags (`--project`) can be repeated. -`--project` takes a registered alias or a registered project's numeric ID *(since vNEXT)*; an alias wins over an ID, and an ID registered under several aliases is `CONFIG_ERROR` (exit 5) listing them, unless all are on one stack and exactly one is a session alias. `KBAGENT_PROJECT`, `project use`, `config clone --target-project`, `sync clone --target`, `semantic-layer promote --from-project/--to-project`, `semantic-layer diff --project-a/--project-b`, `auth * --stack`, the `project invite --from-csv` `project` column and the `kbagent serve` `{project}` / `?project=` / `?stack=` parameters take an ID the same way (serve: path and query parameters only, not request bodies); `project add` / `project create` do not (their `--project` is the new alias), nor does `lineage show --project` (an offline graph filter). A digits-only alias that is also another project's ID wins, with a warning on stderr. See `gotchas.md`. +`--project` takes a registered alias or a registered project's numeric ID *(since 0.96.0)*; an alias wins over an ID, and an ID registered under several aliases is `CONFIG_ERROR` (exit 5) listing them, unless all are on one stack and exactly one is a session alias. `KBAGENT_PROJECT`, `project use`, `config clone --target-project`, `sync clone --target`, `semantic-layer promote --from-project/--to-project`, `semantic-layer diff --project-a/--project-b`, `auth * --stack`, the `project invite --from-csv` `project` column and the `kbagent serve` `{project}` / `?project=` / `?stack=` parameters take an ID the same way (serve: path and query parameters only, not request bodies); `project add` / `project create` do not (their `--project` is the new alias), nor does `lineage show --project` (an offline graph filter). A digits-only alias that is also another project's ID wins, with a warning on stderr. See `gotchas.md`. ## Setup & Info - `init [--from-global] [--project ALIAS ...]` -- create local `.kbagent/` workspace in current directory; `--project ALIAS` (repeatable) copies only the named project(s) from the global config and implies `--from-global` @@ -34,7 +34,7 @@ headlessly.** Both issue a USER-scoped "programmatic session" - `auth login-password --email EMAIL (--password PASSWORD | --password-stdin) [--totp-secret SECRET] [--stack URL|alias] [--register-projects]` -- sign in via a password grant, no browser. Prefer `--password-stdin` (or `KBC_LOGIN_PASSWORD`) over `--password` -- a value on the command line lands in shell history and process listings; `--password`/`--password-stdin` are mutually exclusive (`ConfigError` if both are given). `--email`/`--password`/`--totp-secret` also read from `KBC_LOGIN_EMAIL`/`KBC_LOGIN_PASSWORD`/`KBC_LOGIN_TOTP_SECRET` env vars (same convention as `KBC_TOKEN`), so a CI workflow sets them once in a step's `env:` block. `--totp-secret` is the account's base32 TOTP seed (from its authenticator enrollment), NOT a 6-digit code -- kbagent computes the current code itself (`auth/totp.py`, stdlib RFC 6238), so nothing here needs a human typing a live code. Only resolves TOTP-based MFA; a WebAuthn/passkey-only account gets `AUTH_MFA_INVALID` and must use `auth login` instead (that ceremony needs a real browser). The resulting session is stored and used identically to a browser-login session -- same `auth.json`, same `project list` "session" auth-mode, same `--register-projects` contract. Storing an account's password (and TOTP seed) as CI secrets is a bigger blast radius than one scoped project token; use a dedicated, least-privileged service account. - `auth status [--stack URL|alias]` -- show session state (`live`/`refreshed`/`degraded`/`expired`/`missing`), signed-in user, accessible projects, and token expiry. Proactively refreshes the access token if stale before reporting (a healthy session routinely shows an expired 1h access token next to a valid 30-day refresh token) -- `refreshed` means a rotation just happened, `live` means the cached token was still fresh. - `auth logout [--stack URL|alias] [--remove-projects] [--yes]` -- revoke the refresh token server-side and delete the local session from `auth.json`. `--remove-projects` also removes `config.json` aliases pointing at this session (sentinel-token projects only; a static-token project on the same stack is never touched). -- `auth register-projects [--stack URL|alias] [--all] [--project-id ID ...] [--alias ID=ALIAS ...] [--yes]` -- register an EXISTING session's accessible projects as `config.json` aliases, without re-running `login`. Fixes two usability gaps in plain `login`: nothing was registered unless `--register-projects` was passed, and the suggested alias was always slugified from the project NAME, so a project id like `9840` from the login table never resolved as `--project 9840` (since vNEXT it does, once the project is registered). `--all` selects every accessible project; `--project-id ID` (repeatable) selects specific ones (an inaccessible id raises a `ConfigError`); passing neither starts an interactive arrow-key + spacebar checkbox picker -- every not-yet-registered project preselected, up/down or `j`/`k` move, `space` toggles, `a` selects/deselects all, `enter` accepts, `q`/`esc`/`ctrl-c` cancels -- followed by a single `Edit aliases?` confirm (default no) that opens the old per-project alias prompt only if you opt in (each row already shows its suggested alias), then a final `typer.confirm`. On a piped stdin or a terminal without real interactive capabilities, the picker falls back to the original typed prompt (numbers / ranges `1-3` / `all` / `none`). In a non-TTY or `--json` context with neither `--all` nor `--project-id`, the command fails fast telling the caller to pass `--all` or `--project-id` instead of hanging on a prompt. `--alias ID=ALIAS` (repeatable) overrides the suggested alias for a given project id in every mode, including as the picker's prefilled default. `--yes` skips only the picker's final confirmation. Two collision rules, in both modes: a project already registered under an alias for this project+stack reports `status: "exists"` (no-op -- rename via `project edit --new-alias` instead of re-registering); an alias already claimed by a different project (or a static-token project) reports `status: "skipped"` with a rename-hint note -- an existing `config.json` entry is never overwritten. `auth login` (without `--register-projects`) now also offers this same picker interactively right after a successful login, when stdout is a TTY and `--json` was not used; otherwise it just prints the hint to run this command later, and a failure in that optional follow-up never changes `login`'s own (already-successful) exit code. +- `auth register-projects [--stack URL|alias] [--all] [--project-id ID ...] [--alias ID=ALIAS ...] [--yes]` -- register an EXISTING session's accessible projects as `config.json` aliases, without re-running `login`. Fixes two usability gaps in plain `login`: nothing was registered unless `--register-projects` was passed, and the suggested alias was always slugified from the project NAME, so a project id like `9840` from the login table never resolved as `--project 9840` (since 0.96.0 it does, once the project is registered). `--all` selects every accessible project; `--project-id ID` (repeatable) selects specific ones (an inaccessible id raises a `ConfigError`); passing neither starts an interactive arrow-key + spacebar checkbox picker -- every not-yet-registered project preselected, up/down or `j`/`k` move, `space` toggles, `a` selects/deselects all, `enter` accepts, `q`/`esc`/`ctrl-c` cancels -- followed by a single `Edit aliases?` confirm (default no) that opens the old per-project alias prompt only if you opt in (each row already shows its suggested alias), then a final `typer.confirm`. On a piped stdin or a terminal without real interactive capabilities, the picker falls back to the original typed prompt (numbers / ranges `1-3` / `all` / `none`). In a non-TTY or `--json` context with neither `--all` nor `--project-id`, the command fails fast telling the caller to pass `--all` or `--project-id` instead of hanging on a prompt. `--alias ID=ALIAS` (repeatable) overrides the suggested alias for a given project id in every mode, including as the picker's prefilled default. `--yes` skips only the picker's final confirmation. Two collision rules, in both modes: a project already registered under an alias for this project+stack reports `status: "exists"` (no-op -- rename via `project edit --new-alias` instead of re-registering); an alias already claimed by a different project (or a static-token project) reports `status: "skipped"` with a rename-hint note -- an existing `config.json` entry is never overwritten. `auth login` (without `--register-projects`) now also offers this same picker interactively right after a successful login, when stdout is a TTY and `--json` was not used; otherwise it just prints the hint to run this command later, and a failure in that optional follow-up never changes `login`'s own (already-successful) exit code. A session project works with almost every command -- only three features still require a static Storage token (listed below). `serve` reaches the supported @@ -83,7 +83,7 @@ end-to-end walkthrough and troubleshooting. - `project refresh --project ALIAS | --all [--dry-run] [--force] [--yes] [--token-description DESC] [--token-expires-in N]` -- mint replacement Storage tokens via the Manage API for projects whose token is expired or invalid. `--force` also replaces still-valid non-expiring tokens. Browser-login (session) projects land in `skipped` with the reason that there is no static token to replace -- their credential lives in `auth.json` and rotates on its own -- and `--force` does not convert them either; use `project edit --token` for a deliberate single-project conversion (since v0.80.0) - `project description-get --project NAME` -- read the dashboard project description (KBC.projectDescription on the default branch). Returns `{"description": ""}` if not set, not an error - `project description-set --project NAME [--text STR | --file PATH | --stdin]` -- set the dashboard project description (markdown). Pass exactly one of `--text`, `--file`, or `--stdin`. Writes to `KBC.projectDescription` on the default branch -- always the main branch, regardless of any active dev branch -- `project use ALIAS` -- pin `ALIAS` as the persistent default project. Stored as `default_project` in config.json. A registered project ID pins that project's alias *(since vNEXT)*. Overridden at runtime by `KBAGENT_PROJECT=ALIAS` (env, beats pin) and by `--project ALIAS` (CLI flag, beats both) +- `project use ALIAS` -- pin `ALIAS` as the persistent default project. Stored as `default_project` in config.json. A registered project ID pins that project's alias *(since 0.96.0)*. Overridden at runtime by `KBAGENT_PROJECT=ALIAS` (env, beats pin) and by `--project ALIAS` (CLI flag, beats both) - `project current` -- print the effective default project and its source (`env` / `pin` / `none`). Reports both the env override AND the persisted pin so misconfigurations are visible. Returns `{"alias": null, "source": "none"}` when neither is set - `project info --project NAME` -- show detailed project metadata. Also carries `auth_mode`, rendered as an `Auth` row above the Token rows (on a session project those rows describe the rotating access token). `project current`, `project add` and `project edit` deliberately do **not** carry `auth_mode` -- `current` answers which alias is effective (and reports a `KBAGENT_PROJECT` override that may name an alias absent from the config), the other two are write confirmations. The same keys appear over HTTP on `/projects`, `/projects/status` and `/projects/{alias}/info` (since v0.80.0) @@ -92,7 +92,7 @@ end-to-end walkthrough and troubleshooting. All seven commands authenticate via `KBC_MANAGE_API_TOKEN` (Manage API), not the project's Storage token. Allowed roles are exactly `admin`, `guest`, `readOnly`, `share` -- the API self-reports this list in its 400 validation error and `constants.PROJECT_ROLES` mirrors it. - `project invite --project ALIAS --email EMAIL --role admin|guest|readOnly|share [--reason TEXT] [--dry-run]` -- single-shot invitation. Returns `{"status": "ok", "invitation_id": ..., ...}`. Re-inviting an already-invited or already-member email returns `{"status": "noop", "note": "already_invited" | "already_member"}` (HTTP 400 from the Manage API, normalised to a no-op). -- `project invite --from-csv FILE [--default-role ROLE] [--workers N] [--dry-run]` -- bulk invitation. CSV header required; columns: `email`, `project` (alias, or a project ID -- an alias wins *(since vNEXT)*, and a row fails when that alias hides another project's ID) or `project_id` (numeric, ID only), `role` (optional with `--default-role`), `reason` (optional). Parallelised via `ThreadPoolExecutor` (default 8 workers). Single-stack-URL invariant per file: rows referencing different stacks raise `ConfigError` upfront. Result is `{"total","succeeded","noop","failed","rows":[...]}`; `rows[]` order is *not deterministic*. Exit 0 even with `failed > 0` -- inspect the JSON. +- `project invite --from-csv FILE [--default-role ROLE] [--workers N] [--dry-run]` -- bulk invitation. CSV header required; columns: `email`, `project` (alias, or a project ID -- an alias wins *(since 0.96.0)*, and a row fails when that alias hides another project's ID) or `project_id` (numeric, ID only), `role` (optional with `--default-role`), `reason` (optional). Parallelised via `ThreadPoolExecutor` (default 8 workers). Single-stack-URL invariant per file: rows referencing different stacks raise `ConfigError` upfront. Result is `{"total","succeeded","noop","failed","rows":[...]}`; `rows[]` order is *not deterministic*. Exit 0 even with `failed > 0` -- inspect the JSON. - `project member-list --project ALIAS [--include-pending]` -- list active members. Each member dict carries `id`, `email`, `name`, `role`, `status`, `mfa_enabled`. With `--include-pending`, the response also includes `pending_invitations: [...]`. - `project invitation-list --project ALIAS` -- list pending (unaccepted) invitations only. - `project invitation-cancel --project ALIAS --email EMAIL [--invitation-id ID] [--yes]` -- cancel a pending invitation. Without `--invitation-id`, the service resolves it by listing pending invitations and matching `--email` (case-insensitive). 204 No Content on success; `KeboolaApiError(NOT_FOUND)` if the email has no pending invitation. @@ -273,7 +273,7 @@ Non-SOX Branches 2.0: merge a dev branch into production with review. Alias `mr` ## Workspaces (SQL Debugging) - `workspace create --project ALIAS [--name NAME] [--ui] [--read-only]` -- create workspace (headless ~1s, `--ui` ~15s). Since v0.47.1: Snowflake headless workspaces return a `private_key` PEM field; `password` is empty. BigQuery workspaces keep the default password credential shape. -- `workspace list [--project NAME ...] [--orphaned] [--branch ID] [--qs-compatible]` -- list workspaces. `--project` repeatable; `--orphaned` filters to workspaces whose backing `keboola.sandboxes` config is missing. **Since v0.42.0 (#304)**: each entry carries `login_type`, `read_only`, `qs_compatible`, `database`, `warehouse`. New `Login Type` / `RO` / `QS` columns in human mode. `--qs-compatible` pre-filters to RO + whitelisted-loginType workspaces (the canonical data-app shape). **Updated v0.58.0**: `qs_compatible` is keyed by `(backend, loginType)` -- BigQuery workspaces (loginType `default`) now report `qs_compatible: true` and pass `--qs-compatible`; pre-0.58.0 every BigQuery workspace was wrongly excluded (Snowflake's own legacy `default` stays `false`). `--branch` requires exactly one `--project`; without `--branch`, it uses each alias's active branch (`branch use`), else production. **Since vNEXT (#766)** the `Target:` line / `targets` key names that branch; the earlier `Info: Using production branch for read` line was wrong +- `workspace list [--project NAME ...] [--orphaned] [--branch ID] [--qs-compatible]` -- list workspaces. `--project` repeatable; `--orphaned` filters to workspaces whose backing `keboola.sandboxes` config is missing. **Since v0.42.0 (#304)**: each entry carries `login_type`, `read_only`, `qs_compatible`, `database`, `warehouse`. New `Login Type` / `RO` / `QS` columns in human mode. `--qs-compatible` pre-filters to RO + whitelisted-loginType workspaces (the canonical data-app shape). **Updated v0.58.0**: `qs_compatible` is keyed by `(backend, loginType)` -- BigQuery workspaces (loginType `default`) now report `qs_compatible: true` and pass `--qs-compatible`; pre-0.58.0 every BigQuery workspace was wrongly excluded (Snowflake's own legacy `default` stays `false`). `--branch` requires exactly one `--project`; without `--branch`, it uses each alias's active branch (`branch use`), else production. **Since 0.96.0 (#766)** the `Target:` line / `targets` key names that branch; the earlier `Info: Using production branch for read` line was wrong - `workspace detail --project ALIAS --workspace-id ID [--branch ID]` -- show connection details. **Since v0.42.0 (#304)**: response carries `login_type`, `read_only`, `qs_compatible`; human mode adds `Login type:` / `Read-only:` / `Query Service compatible:` rows. **Updated v0.58.0**: BigQuery `default` workspaces now report `qs_compatible: true` (was `false`). `--branch` opt-in mirrors `workspace list` - `workspace delete --project ALIAS --workspace-id ID` -- delete workspace - `workspace password --project ALIAS --workspace-id ID` -- reset and return new password @@ -286,12 +286,12 @@ Non-SOX Branches 2.0: merge a dev branch into production with review. Alias `mr` Lifecycle for `keboola.data-apps`. Combines Storage API (config body, git block, encrypted secrets, runtime size) with Data Science API (`/apps` -- deployment record, state, URL, configVersion). The CLI encapsulates the §9 redeploy contract so callers cannot pin to the empty-shell v2; see `data-app-workflow.md` for the gotcha inventory and recipes. Since v0.33.0 the JSON output envelope's data-app id key is `app_id` (renamed from bare `id` for symmetry with the `--app-id` input flag); `config_id` is unchanged. - `data-app list [--project NAME ...] [--branch ID]` -- list data apps across projects (Data Science index merged with Storage names). Since v0.43.9 filters out workspace/sandbox deployments (`componentId=keboola.sandboxes`, `type=snowflake`/`bigquery`) that the Data Science `/apps` collection also returns, so the listing matches the Apps UI. Envelope carries `component_id` per app. - `data-app detail --project NAME --app-id ID [--branch ID]` -- merged view (state, desired, url, configVersion, slug, git block with PAT redacted) -- `data-app create --project ALIAS --name NAME --slug SLUG (--git-repo URL | --use-managed-git-repo) [--git-public/--no-git-public] [--git-username USER] [--git-pat-env VAR | --git-pat-file PATH | --git-pat-encrypted KBC::Project...] [--auth password|public] [--size tiny|small|medium|large] [--auto-suspend SECONDS] [--type python-js|python|streamlit|r|...] [--workspace/--no-workspace] [--branch ID] [--no-deploy] [--wait] [--timeout SECONDS] [--keep-on-failure] [--dry-run] [--copy] [--reveal]` -- POST shell + encrypt PAT + PUT Storage config (with auto-injected `parameters.id`) + PATCH deploy with the §9 trio. Cleanup-in-finally on failure unless `--keep-on-failure`. Default `--auth password` makes the platform generate a 20-char hex simpleAuth password during the deploy. With `--wait` the password is delivered like `data-app password` once the app runs (since vNEXT); `--copy` / `--reveal` need `--wait` and a deploy (exit 2 with `--no-deploy` or `--use-managed-git-repo`). `--dry-run` accepts and refuses the same flags and adds `password_delivery` (`prompt` / `clipboard` / `stdout`) plus the warning the real run would give. **Exactly one git source required.** `--use-managed-git-repo` provisions an EMPTY Keboola-hosted repo (POST `useManagedGitRepo:true`), writes NO `parameters.dataApp.git` block, and forces `--no-deploy` (empty repo, nothing to run); mutually exclusive with `--git-repo` and all `--git-*`/PAT flags. Managed-repo deploy WORKS with no credential wiring (verified live -- tic-tac-toe deployed and serving from a Keboola-managed repo). Full flow to a RUNNING app: `git-credentials-create --type http_token --permissions readWrite` -> `git push` your code to the managed repo URL (`data-app git-repo` shows it) -> `data-app deploy`. The platform injects the clone credentials at deploy time, so nothing extra goes into the config; the created credential is only used to authenticate YOUR push. **`--workspace` (since 0.87.0) is ON by default** and writes `runtime.workspace.enabled: true` -- the single switch that makes the platform provision the app's ephemeral workspace and inject `WORKSPACE_ID` / `QUERY_SERVICE_URL` / `KBC_WORKSPACE_MANIFEST_PATH`. Every app that reads Storage needs it; before 0.87.0 kbagent never wrote it, so such an app deployed, reported `state=running`, passed its health probe and read nothing, with NO platform-side diagnostic (check `config detail` -> `configuration.runtime`; a `Missing env vars: WORKSPACE_ID` log line, if any, comes from the app's own code, so its absence rules nothing out). Pass `--no-workspace` only for an app that never touches Storage -- it omits the key entirely (no `enabled: false`), leaving the body identical to 0.86.0. The block is a sibling of `runtime.backend`. Not gated on any project feature. Retrofit an existing app with `config update --merge --set 'runtime.workspace.enabled=true'` then `data-app deploy`. -- `data-app deploy --project NAME --app-id ID [--config-version N] [--wait] [--timeout SECONDS] [--branch ID] [--copy] [--reveal]` -- the §9 redeploy contract. With `--wait` on a password app (since vNEXT) the password is delivered like `data-app password`: the `c` prompt in a terminal (the command then ends after Enter or 120 s), `--copy`, or `--reveal`; both flags need `--wait` (exit 2 otherwise, before any API call). With no flag and no prompt (no terminal, a background job, `--json`) the password is not read and the output is as before. With a flag, `--json` adds only `ui_url`, `password_delivered_to` and (`--reveal`) `password`; a non-password app with a flag, or any failure to read the password after the deploy, is a `warnings[]` entry with exit 0. The `kbagent serve` create / deploy routes do not deliver the password. Default reads latest Storage version; `--config-version` pins an older version (rollback). Since 0.65.0: omits `configVersion` for a PURE managed repo (no git block -- deploys from `app.managedGitRepoId`, and the platform injects the clone credentials) and pins the LATEST Storage `configVersion` when a git block is present (external repos). An explicit `--config-version` always wins. +- `data-app create --project ALIAS --name NAME --slug SLUG (--git-repo URL | --use-managed-git-repo) [--git-public/--no-git-public] [--git-username USER] [--git-pat-env VAR | --git-pat-file PATH | --git-pat-encrypted KBC::Project...] [--auth password|public] [--size tiny|small|medium|large] [--auto-suspend SECONDS] [--type python-js|python|streamlit|r|...] [--workspace/--no-workspace] [--branch ID] [--no-deploy] [--wait] [--timeout SECONDS] [--keep-on-failure] [--dry-run] [--copy] [--reveal]` -- POST shell + encrypt PAT + PUT Storage config (with auto-injected `parameters.id`) + PATCH deploy with the §9 trio. Cleanup-in-finally on failure unless `--keep-on-failure`. Default `--auth password` makes the platform generate a 20-char hex simpleAuth password during the deploy. With `--wait` the password is delivered like `data-app password` once the app runs (since 0.96.0); `--copy` / `--reveal` need `--wait` and a deploy (exit 2 with `--no-deploy` or `--use-managed-git-repo`). `--dry-run` accepts and refuses the same flags and adds `password_delivery` (`prompt` / `clipboard` / `stdout`) plus the warning the real run would give. **Exactly one git source required.** `--use-managed-git-repo` provisions an EMPTY Keboola-hosted repo (POST `useManagedGitRepo:true`), writes NO `parameters.dataApp.git` block, and forces `--no-deploy` (empty repo, nothing to run); mutually exclusive with `--git-repo` and all `--git-*`/PAT flags. Managed-repo deploy WORKS with no credential wiring (verified live -- tic-tac-toe deployed and serving from a Keboola-managed repo). Full flow to a RUNNING app: `git-credentials-create --type http_token --permissions readWrite` -> `git push` your code to the managed repo URL (`data-app git-repo` shows it) -> `data-app deploy`. The platform injects the clone credentials at deploy time, so nothing extra goes into the config; the created credential is only used to authenticate YOUR push. **`--workspace` (since 0.87.0) is ON by default** and writes `runtime.workspace.enabled: true` -- the single switch that makes the platform provision the app's ephemeral workspace and inject `WORKSPACE_ID` / `QUERY_SERVICE_URL` / `KBC_WORKSPACE_MANIFEST_PATH`. Every app that reads Storage needs it; before 0.87.0 kbagent never wrote it, so such an app deployed, reported `state=running`, passed its health probe and read nothing, with NO platform-side diagnostic (check `config detail` -> `configuration.runtime`; a `Missing env vars: WORKSPACE_ID` log line, if any, comes from the app's own code, so its absence rules nothing out). Pass `--no-workspace` only for an app that never touches Storage -- it omits the key entirely (no `enabled: false`), leaving the body identical to 0.86.0. The block is a sibling of `runtime.backend`. Not gated on any project feature. Retrofit an existing app with `config update --merge --set 'runtime.workspace.enabled=true'` then `data-app deploy`. +- `data-app deploy --project NAME --app-id ID [--config-version N] [--wait] [--timeout SECONDS] [--branch ID] [--copy] [--reveal]` -- the §9 redeploy contract. With `--wait` on a password app (since 0.96.0) the password is delivered like `data-app password`: the `c` prompt in a terminal (the command then ends after Enter or 120 s), `--copy`, or `--reveal`; both flags need `--wait` (exit 2 otherwise, before any API call). With no flag and no prompt (no terminal, a background job, `--json`) the password is not read and the output is as before. With a flag, `--json` adds only `ui_url`, `password_delivered_to` and (`--reveal`) `password`; a non-password app with a flag, or any failure to read the password after the deploy, is a `warnings[]` entry with exit 0. The `kbagent serve` create / deploy routes do not deliver the password. Default reads latest Storage version; `--config-version` pins an older version (rollback). Since 0.65.0: omits `configVersion` for a PURE managed repo (no git block -- deploys from `app.managedGitRepoId`, and the platform injects the clone credentials) and pins the LATEST Storage `configVersion` when a git block is present (external repos). An explicit `--config-version` always wins. - `data-app start --project NAME --app-id ID [--wait] [--timeout SECONDS]` -- wake an auto-suspended app at the currently-pinned version. Distinct from deploy: does NOT bump configVersion. - `data-app stop --project NAME --app-id ID [--wait] [--timeout SECONDS]` -- stop a running app (URL and Storage config preserved). - `data-app delete --project NAME --app-id ID [--yes]` -- destructive, cascades to Storage config; URL retired permanently. -- `data-app password --project NAME --app-id ID [--copy] [--reveal] [--open]` -- give the user the password of a password-protected app without printing it (since vNEXT; older versions printed it and needed a Manage token). Project token only (static or session). In a terminal (human mode) it prints `app_url` + `ui_url`, then `c` copies the password, Enter / Esc / `q` finishes, 120 s timeout. Without a terminal or with `--json`: only `--copy` copies (clipboard tool, password on stdin; WSL falls back to `/mnt/c/Windows/System32/clip.exe`). Nothing copied -> exit 0, `password_delivered_to: null`, `ui_url` is the Keboola UI page that shows it under Open App. `--reveal` prints it (`password_delivered_to: "stdout"`); `--reveal` + `--copy` = `INVALID_ARGUMENT`. `--open` also opens `app_url` (`app_opened`). `VALIDATION_ERROR` when the app's auth is not `password`; `NOT_FOUND` when it has no password yet. The Keboola UI can reset the password. BREAKING (vNEXT): scripts that read `.data.password` must add `--reveal` (without it the key is absent and exit is 0); REST clients must pass `reveal=true`; `KBC_MANAGE_API_TOKEN` / `--allow-env-manage-token` are no longer used by this command. Agents: recommend the user runs it in their own terminal and presses `c` (`data-app deploy --wait` only when a deploy is needed anyway -- it restarts the app), or offer `--copy`; no `--reveal` unless the user asks; never read the clipboard or ask the user to paste the password. +- `data-app password --project NAME --app-id ID [--copy] [--reveal] [--open]` -- give the user the password of a password-protected app without printing it (since 0.96.0; older versions printed it and needed a Manage token). Project token only (static or session). In a terminal (human mode) it prints `app_url` + `ui_url`, then `c` copies the password, Enter / Esc / `q` finishes, 120 s timeout. Without a terminal or with `--json`: only `--copy` copies (clipboard tool, password on stdin; WSL falls back to `/mnt/c/Windows/System32/clip.exe`). Nothing copied -> exit 0, `password_delivered_to: null`, `ui_url` is the Keboola UI page that shows it under Open App. `--reveal` prints it (`password_delivered_to: "stdout"`); `--reveal` + `--copy` = `INVALID_ARGUMENT`. `--open` also opens `app_url` (`app_opened`). `VALIDATION_ERROR` when the app's auth is not `password`; `NOT_FOUND` when it has no password yet. The Keboola UI can reset the password. BREAKING (0.96.0): scripts that read `.data.password` must add `--reveal` (without it the key is absent and exit is 0); REST clients must pass `reveal=true`; `KBC_MANAGE_API_TOKEN` / `--allow-env-manage-token` are no longer used by this command. Agents: recommend the user runs it in their own terminal and presses `c` (`data-app deploy --wait` only when a deploy is needed anyway -- it restarts the app), or offer `--copy`; no `--reveal` unless the user asks; never read the clipboard or ask the user to paste the password. - `data-app logs --project NAME --app-id ID [--lines N] [--since ISO8601]` -- tail container logs (Data Science `/apps/{id}/logs/tail`). Plain-text body covering the full spin-up trace ([TIMING] git_clone, Cloning into /app, uv install, supervisord, runtime stack traces). Default `--lines 500`; pass `--lines 0` for the full current buffer (no server-side cap). `--lines` and `--since` are mutually exclusive on the server; `--since` requires a timezone (Z or +00:00). App must be running or recently-stopped — never-started apps return 400 "App X is not running" (recover with `data-app start` or `data-app deploy`). Closes the upstream `keboola-mcp-server` gap where `get_data_apps` hardcodes a 20-line cap; this CLI surface is unconstrained. The log buffer can echo runtime secrets the app printed to stdout/stderr — consider hygiene before piping `--json` output into AI agent context. - `data-app runs --project NAME --app-id ID [--limit N]` -- list deployment attempts newest-first (Data Science `/apps/{id}/runs`), each with `failure_reason` + `startup_logs`. Captures setup-phase failures (e.g. git-clone errors) that produce NO container logs, so unlike `data-app logs` it works on never-started / failed apps where `data-app logs` returns HTTP 400. This is the way to find WHY a deploy reverted to stopped. Auth: ordinary project storage token only. - `data-app secrets-set --project ALIAS --app-id ID --secret '#KEY=VALUE' [--secret ...] [--secrets-file PATH] [--branch ID] [--allow-plaintext-on-encrypt-failure] [--dry-run] [--no-hint-next]` -- encrypt and write `#`-prefixed secrets to `parameters.dataApp.secrets`. Per-project KMS encryption, fail-closed. Read-modify-write at the service layer (NOT Storage `merge=True` -- shallow). Runtime exposes each key as an env var with `#` stripped, `-` -> `_`, uppercased. Adding bumps the Storage version; the running container keeps the OLD config until the next `data-app deploy`. @@ -360,11 +360,11 @@ Requires the project to be added with its **master ('owner') Storage API token** - Exposed over `kbagent serve` as `GET /notifications`, `GET /notifications/{project}/{subscription_id}`, `POST /notifications/{project}`, `DELETE /notifications/{project}/{subscription_id}`, and `POST /notifications/{project}/{subscription_id}/replace-recipient` ## Sync (GitOps) -- `sync init --project ALIAS [--directory DIR] [--git-branching] [--adopt-existing] [--with-workspaces]` -- initialize sync working directory; `--adopt-existing` adopts a `.keboola/manifest.json` already written by the kbc Go CLI without overwriting (idempotent; validates `project_id` against the alias token). `--with-workspaces` *(since vNEXT)* sets `syncWorkspaces` in the manifest, so pull/diff/push/clone also sync shared SQL workspaces (`keboola.sandboxes` without `parameters.id`, `runtime.shared: true`), config only; a `push --force` delete removes the workspace's SQL editor sessions first (a plain push holds it back under `skipped_deletions`). With `--adopt-existing` it turns the key on in an existing manifest. See `sync-workflow.md` > "Shared SQL workspaces". -- `sync pull --project ALIAS [--all-projects] [--force] [--theirs] [--dry-run] [--with-samples] [--no-storage] [--no-jobs] [--job-limit N] [--branch ID]` -- download configs to local files. **Auto-inits:** if the target directory has no `.keboola/manifest.json`, pull runs `init` first, so a separate `sync init` is not needed for a first checkout of a project. For large projects (>100 configs), automatically fetches jobs per-config when the grouped API limit is insufficient. `--force` is conflict-aware (since 0.53.0): a locally-modified config whose remote is unchanged is **preserved** (pending delta stays pushable, never silently re-stamped); a true merge conflict (local AND remote both changed since last pull) **aborts** the pull (exit 1, `SYNC_CONFLICT`; `--json` lists `details.conflicts`); local-untouched + remote-changed takes remote. `--theirs` (since v0.72.0) is the supported "discard local, take production" reconcile path: overwrites locally-modified configs/rows, restores deleted/missing files, resolves conflicts by taking remote (no abort, no manifest surgery). Since v0.72.0 plain pull also re-materializes a tracked config whose local dir was deleted (manifest<->disk invariant), so delete-dir-then-pull refetches. Config-level `isDisabled` is kept through pull and push (since v0.72.0) as sparse `is_disabled: true` in `_config.yml` -- absent key = enabled. `--branch` (0.47.0+) per-invocation dev-branch override, beats every other branch source. Ignored components (since 0.91.0): `keboola.sandboxes` + `keboola.mcp-server-tool` are always excluded (`keboola.sandboxes` shared SQL workspaces are synced when the manifest sets `syncWorkspaces`, *since vNEXT*), unioned with the manifest's `ignoredComponents` list; a component newly ignored has its manifest entry dropped and local directory removed, reported with pull action `"ignored"` (distinct from `"removed"` = genuinely deleted on remote). Config-folder round-trip *(since 0.94.0)*: pull captures each config's UI folder (`KBC.configuration.folderName`, from the branch-only `search/component-configurations` endpoint) into the manifest, and reports `folder_lookup_failed` when that lookup fails, keeping the previously captured folder. -- `sync push --project ALIAS [--all-projects] [--dry-run] [--force] [--allow-plaintext-on-encrypt-failure] [--branch ID] [--no-name-drift-warnings]` -- push local changes (auto-encrypts secrets, fails if encryption fails). Workspace delete *(since vNEXT)*: in a `syncWorkspaces` tree, `push --force` deleting a shared SQL workspace also deletes its SQL editor sessions (every user's, push branch) and their backend workspaces, which `config restore` does not bring back, so check `push --dry-run --force` (`warnings[]` `workspace_sessions`) first; `--force` is destructive-class (`sync.push --force`, blocked by `--deny-destructive`). Fresh-CREATE writeback updates placeholder manifest entries in place (since 0.47.0) and propagates any `KBC.configuration.*` metadata via `set_config_metadata`. Fresh-CREATE variable binding (since 0.47.2): when a `keboola.variables` config + its values row are created alongside a transformation in the same push, the transformation's `variables_id` / `variables_values_id` placeholders are rebound to the assigned ULIDs and the row's `values` are hoisted even without a `_keboola` block, so `job run` succeeds with no post-push `config variables-set` step (unresolvable/ambiguous links add a `variable_link` entry in `errors[]`, never a broken link). Since vNEXT push also remaps shared-code links, flow and orchestrator task `configId`s / `configRowIds` and schedule targets to the configs created in the same push. A link it cannot set is an `errors[]` entry with `change_type` `shared_code_link`, `flow_task_link` or `schedule_target_link` (error code `LINK_UNRESOLVED` for a row id without a new row, `API_ERROR` for a failed PUT). After a failed PUT the local files already hold the new ids and the manifest hash stays stale, so the next `sync push` sends them; this also applies to `variable_link`. The result carries `link_remaps` (`flow_tasks`, `orchestrator_tasks`, `schedule_targets`, `shared_code`, `config_row_ids`); `flow_task_remaps` counts flow tasks only. Never-fetched guard (since v0.72.0): a manifest entry with an empty `pull_hash` and no local files (pre-0.72 name-collision phantom) is **never** planned as a remote DELETE -- diff/push exclude it and report it under `never_fetched` with a warning (run `sync pull` to materialize); local deletion of a properly-pulled config deletes on `push --force`; since vNEXT (#792) a plain push deletes nothing and lists the deletion under `skipped_deletions` (also in `--dry-run`), and a config or row deleted on the remote since the last pull is `remote_deleted`, which push never re-creates. Adopted-by-id writeback (since v0.72.0): pushing an untracked file whose `_keboola.config_id` resolves on the branch also writes the manifest entry, so follow-up diffs are stable. `--branch` (0.47.0+) per-invocation override; when no `/` subtree exists on disk (since 0.47.2) the local default tree (`main/`) is promoted to the target branch (API writes still target the branch id); `--no-name-drift-warnings` (0.47.0+) drops the cosmetic warnings array. Branch-scoped since v0.89.0 (issue #649): push consumes the diff's changeset, so configs tracked on another branch's tree are never planned as creates -- they ride along on the result envelope under `orphaned` instead (see `sync diff`). **Since 0.91.0 (#686)** the manifest baseline `pull_config_hash` is stamped from the API response (or a read-back), not from the files on disk, so a pushed multi-statement SQL transformation -- or anything disabled in the UI whose local YAML lacks `is_disabled` -- no longer shows permanent phantom `REMOTE MODIFIED` drift; if the config cannot be read back after the write the baseline is left UNTOUCHED and a `warnings[]` entry says to run `sync pull` (never a disk-derived fallback). One legacy change is refused per-change with `SYNC_LEGACY_BOUNDARY`: a tree pulled before statement-boundary markers existed whose only difference from the remote is the lost boundaries (pushing it would collapse separate SQL statements into one) -- run `sync pull` for that project first. Ignored components (since 0.91.0) are filtered out on both sides of the diff push builds on, so a stale local directory for an ignored component (e.g. `keboola.mcp-server-tool`) is never classified as `DELETED` and can never be pushed as a remote deletion. -- `sync clone --source DIR --target ALIAS --target-dir DIR [--bucket-map FILE] [--variable-values FILE] [--instance-rename FILE] [--no-create-buckets] [--dry-run] [--branch ID]` -- clone a reference synced project into a **fresh** target project and parameterize it. Copies the reference tree at `--source` into `--target-dir`, applies declarative overrides from JSON/YAML files (`--bucket-map` `{old_bucket_id: new_bucket_id}` rewrites storage input/output table refs; `--variable-values` `{var_name: value}` overrides `keboola.variables` rows; `--instance-rename` `{old_path_prefix: new_path_prefix}` renames config dirs + manifest paths), re-points the manifest at the target project, and pushes. Because the reference's config ids do not exist in the fresh target, every config is CREATEd fresh and **keboola.flow task `configId`s + transformation variable links are remapped reference->ULID** by push Phase C/D (the push result carries `flow_task_remaps`). Since vNEXT push also remaps shared-code links (`shared_code_id`, `shared_code_row_ids`, `{{}}` script placeholders), legacy `keboola.orchestrator` task `configId`s, task `configRowIds` and a schedule's `target.configurationId`; the clone result carries `link_remaps` per kind (see `sync push`). **`warnings[]` (since vNEXT)**: the clone result (also `--dry-run`; human mode prints them) lists `missing_task_target` (a flow or orchestrator task runs a config that is not in the tree), `encrypted_values_copied` (the `KBC::` paths the target cannot decrypt, as `_config.yml` paths: `secret_keys` get a plaintext + `sync push`, `unencryptable_keys` need `kbagent encrypt values`, `oauth_keys` a new authorization), `data_app_not_deployed` (run `data-app deploy`) and `schedule_not_active` (clone never activates a schedule; `flow schedule` does, and the hint is left out when several schedules run one flow), plus the push warnings. Only the run that creates the configs reports them: a re-run returns `warnings: []`, so keep them from the first run. **Idempotent**: re-running with an existing `--target-dir` skips copy/overrides and just pushes, reporting `no_changes` / `created: 0`. Fails fast (`CONFIG_ERROR`) if the target already contains the reference's configs -- clone requires a fresh/empty target. `SyncService.clone_project(...)` returns a typed `CloneResult` for in-process SDK callers. Override files must be flat `{id: scalar}` mappings *(since v0.89.0)* -- a nested mapping, list, or null value is rejected with `CONFIG_ERROR` (exit 5) naming the key and its actual type. `--branch` is optional on a fresh clone *(since v0.93.1)*. It defaults to the target's production branch, resolved from the API the same way `sync init` does. Pass `--branch ` only to target a dev branch. The config folder (`KBC.configuration.folderName`) is recreated in the target *(since 0.94.0)* -- clone re-points its production configs onto the branch push resolves, so the create-path writeback carries the folder for a plain, `--branch`, and git-branching production clone. **Data-app runtime type (since 0.94.0)**: a `keboola.data-apps` config's type (`python-js` / `streamlit`) lives only on the Data Science `/apps` record, so `sync pull` records it in `_keboola.data_app_type` and clone sends it through `create_app`. A config with no recorded type is created as `python-js`, the default, with a `data_app_type_default` warning *(since vNEXT)*. Re-pull a tree pulled by an older version before cloning a Streamlit app, or it is created as `python-js`. **Storage buckets (since vNEXT)**: clone copies configs, not storage, so a cloned config's input/output mappings point at buckets a fresh target lacks. Clone reads the `storage/buckets.json` pull export and creates the missing buckets in the target **by default** (`--no-create-buckets` skips it) -- idempotent (an existing bucket is skipped, a per-bucket API failure is collected in `bucket_errors`), and the created id is `--bucket-map`-remapped so it matches the rewritten config refs. Each bucket is created on the backend the export recorded. A linked (shared) bucket is linked to the same source as in the reference, under the same id, and listed in `linked_buckets` with its source (an empty bucket in its place would stay empty). The source project's sharing settings decide whether the target may link it; a refused link lands in `bucket_errors`. A tree pulled by an older version does not record which buckets are linked, so clone creates no bucket from it and records one `bucket_errors` entry -- re-pull the reference, or pass `--no-create-buckets`. Only the buckets are created, never their tables or data (the export has no table data) -- populate tables by bucket sharing / `storage upload-table`, or by running the flows. -- `sync diff --project ALIAS [--all-projects] [--branch ID]` -- 3-way diff (local vs base vs remote), detects conflicts. `--branch` (0.47.0+) per-invocation dev-branch override. Branch-scoped since v0.89.0 (issue #649): the local side is read from exactly ONE tree (the target branch's subtree, or `main/` when the target has none). Manifest entries belonging to another branch's tree -- what `sync pull --branch ` leaves behind when it re-targets the manifest -- are excluded from the changeset and reported under `orphaned` (`summary.orphaned` + details with `component_id`, `config_id`, `path`, `branch_id`, `branch_path`, `exists_on_target`, `reason`, `hint`); human mode previews the first 10. An orphaned FILE whose `_keboola.config_id` still resolves on the target is adopted (diffed as `unchanged`/`modified`), never re-created; same-tree id claims keep the #482/#497 fork-by-copy CREATE. Fix a non-zero `summary.orphaned` with `sync pull`. **Since 0.91.0 (#686)** a manifest entry without `metadata.config_hash_version` (written by a pre-0.91.0 kbagent) is compared leniently: a stored hash equal to the pre-0.91.0 hash of the SAME remote config counts as in sync, so the phantom `codes changed` entries disappear immediately; every other field is still pinned by that hash, so real remote drift is unaffected. One `sync pull` per project stamps the version and ends the leniency. Ignored components (since 0.91.0) -- `keboola.sandboxes`, `keboola.mcp-server-tool`, and anything listed in the manifest's `ignoredComponents` -- are excluded from BOTH sides of the comparison, so a stale local directory for one of them never shows up as `DELETED`. Since vNEXT (#792) a config or row the manifest fetched from the target branch and that is missing on the remote is `remote_deleted` (human: `- REMOTE DELETED`, `summary.remote_deleted`): run `sync pull`, push never re-creates it. `DELETED` changes are applied only by `sync push --force`. +- `sync init --project ALIAS [--directory DIR] [--git-branching] [--adopt-existing] [--with-workspaces]` -- initialize sync working directory; `--adopt-existing` adopts a `.keboola/manifest.json` already written by the kbc Go CLI without overwriting (idempotent; validates `project_id` against the alias token). `--with-workspaces` *(since 0.96.0)* sets `syncWorkspaces` in the manifest, so pull/diff/push/clone also sync shared SQL workspaces (`keboola.sandboxes` without `parameters.id`, `runtime.shared: true`), config only; a `push --force` delete removes the workspace's SQL editor sessions first (a plain push holds it back under `skipped_deletions`). With `--adopt-existing` it turns the key on in an existing manifest. See `sync-workflow.md` > "Shared SQL workspaces". +- `sync pull --project ALIAS [--all-projects] [--force] [--theirs] [--dry-run] [--with-samples] [--no-storage] [--no-jobs] [--job-limit N] [--branch ID]` -- download configs to local files. **Auto-inits:** if the target directory has no `.keboola/manifest.json`, pull runs `init` first, so a separate `sync init` is not needed for a first checkout of a project. For large projects (>100 configs), automatically fetches jobs per-config when the grouped API limit is insufficient. `--force` is conflict-aware (since 0.53.0): a locally-modified config whose remote is unchanged is **preserved** (pending delta stays pushable, never silently re-stamped); a true merge conflict (local AND remote both changed since last pull) **aborts** the pull (exit 1, `SYNC_CONFLICT`; `--json` lists `details.conflicts`); local-untouched + remote-changed takes remote. `--theirs` (since v0.72.0) is the supported "discard local, take production" reconcile path: overwrites locally-modified configs/rows, restores deleted/missing files, resolves conflicts by taking remote (no abort, no manifest surgery). Since v0.72.0 plain pull also re-materializes a tracked config whose local dir was deleted (manifest<->disk invariant), so delete-dir-then-pull refetches. Config-level `isDisabled` is kept through pull and push (since v0.72.0) as sparse `is_disabled: true` in `_config.yml` -- absent key = enabled. `--branch` (0.47.0+) per-invocation dev-branch override, beats every other branch source. Ignored components (since 0.91.0): `keboola.sandboxes` + `keboola.mcp-server-tool` are always excluded (`keboola.sandboxes` shared SQL workspaces are synced when the manifest sets `syncWorkspaces`, *since 0.96.0*), unioned with the manifest's `ignoredComponents` list; a component newly ignored has its manifest entry dropped and local directory removed, reported with pull action `"ignored"` (distinct from `"removed"` = genuinely deleted on remote). Config-folder round-trip *(since 0.94.0)*: pull captures each config's UI folder (`KBC.configuration.folderName`, from the branch-only `search/component-configurations` endpoint) into the manifest, and reports `folder_lookup_failed` when that lookup fails, keeping the previously captured folder. +- `sync push --project ALIAS [--all-projects] [--dry-run] [--force] [--allow-plaintext-on-encrypt-failure] [--branch ID] [--no-name-drift-warnings]` -- push local changes (auto-encrypts secrets, fails if encryption fails). Workspace delete *(since 0.96.0)*: in a `syncWorkspaces` tree, `push --force` deleting a shared SQL workspace also deletes its SQL editor sessions (every user's, push branch) and their backend workspaces, which `config restore` does not bring back, so check `push --dry-run --force` (`warnings[]` `workspace_sessions`) first; `--force` is destructive-class (`sync.push --force`, blocked by `--deny-destructive`). Fresh-CREATE writeback updates placeholder manifest entries in place (since 0.47.0) and propagates any `KBC.configuration.*` metadata via `set_config_metadata`. Fresh-CREATE variable binding (since 0.47.2): when a `keboola.variables` config + its values row are created alongside a transformation in the same push, the transformation's `variables_id` / `variables_values_id` placeholders are rebound to the assigned ULIDs and the row's `values` are hoisted even without a `_keboola` block, so `job run` succeeds with no post-push `config variables-set` step (unresolvable/ambiguous links add a `variable_link` entry in `errors[]`, never a broken link). Since 0.96.0 push also remaps shared-code links, flow and orchestrator task `configId`s / `configRowIds` and schedule targets to the configs created in the same push. A link it cannot set is an `errors[]` entry with `change_type` `shared_code_link`, `flow_task_link` or `schedule_target_link` (error code `LINK_UNRESOLVED` for a row id without a new row, `API_ERROR` for a failed PUT). After a failed PUT the local files already hold the new ids and the manifest hash stays stale, so the next `sync push` sends them; this also applies to `variable_link`. The result carries `link_remaps` (`flow_tasks`, `orchestrator_tasks`, `schedule_targets`, `shared_code`, `config_row_ids`); `flow_task_remaps` counts flow tasks only. Never-fetched guard (since v0.72.0): a manifest entry with an empty `pull_hash` and no local files (pre-0.72 name-collision phantom) is **never** planned as a remote DELETE -- diff/push exclude it and report it under `never_fetched` with a warning (run `sync pull` to materialize); local deletion of a properly-pulled config deletes on `push --force`; since 0.96.0 (#792) a plain push deletes nothing and lists the deletion under `skipped_deletions` (also in `--dry-run`), and a config or row deleted on the remote since the last pull is `remote_deleted`, which push never re-creates. Adopted-by-id writeback (since v0.72.0): pushing an untracked file whose `_keboola.config_id` resolves on the branch also writes the manifest entry, so follow-up diffs are stable. `--branch` (0.47.0+) per-invocation override; when no `/` subtree exists on disk (since 0.47.2) the local default tree (`main/`) is promoted to the target branch (API writes still target the branch id); `--no-name-drift-warnings` (0.47.0+) drops the cosmetic warnings array. Branch-scoped since v0.89.0 (issue #649): push consumes the diff's changeset, so configs tracked on another branch's tree are never planned as creates -- they ride along on the result envelope under `orphaned` instead (see `sync diff`). **Since 0.91.0 (#686)** the manifest baseline `pull_config_hash` is stamped from the API response (or a read-back), not from the files on disk, so a pushed multi-statement SQL transformation -- or anything disabled in the UI whose local YAML lacks `is_disabled` -- no longer shows permanent phantom `REMOTE MODIFIED` drift; if the config cannot be read back after the write the baseline is left UNTOUCHED and a `warnings[]` entry says to run `sync pull` (never a disk-derived fallback). One legacy change is refused per-change with `SYNC_LEGACY_BOUNDARY`: a tree pulled before statement-boundary markers existed whose only difference from the remote is the lost boundaries (pushing it would collapse separate SQL statements into one) -- run `sync pull` for that project first. Ignored components (since 0.91.0) are filtered out on both sides of the diff push builds on, so a stale local directory for an ignored component (e.g. `keboola.mcp-server-tool`) is never classified as `DELETED` and can never be pushed as a remote deletion. +- `sync clone --source DIR --target ALIAS --target-dir DIR [--bucket-map FILE] [--variable-values FILE] [--instance-rename FILE] [--no-create-buckets] [--dry-run] [--branch ID]` -- clone a reference synced project into a **fresh** target project and parameterize it. Copies the reference tree at `--source` into `--target-dir`, applies declarative overrides from JSON/YAML files (`--bucket-map` `{old_bucket_id: new_bucket_id}` rewrites storage input/output table refs; `--variable-values` `{var_name: value}` overrides `keboola.variables` rows; `--instance-rename` `{old_path_prefix: new_path_prefix}` renames config dirs + manifest paths), re-points the manifest at the target project, and pushes. Because the reference's config ids do not exist in the fresh target, every config is CREATEd fresh and **keboola.flow task `configId`s + transformation variable links are remapped reference->ULID** by push Phase C/D (the push result carries `flow_task_remaps`). Since 0.96.0 push also remaps shared-code links (`shared_code_id`, `shared_code_row_ids`, `{{}}` script placeholders), legacy `keboola.orchestrator` task `configId`s, task `configRowIds` and a schedule's `target.configurationId`; the clone result carries `link_remaps` per kind (see `sync push`). **`warnings[]` (since 0.96.0)**: the clone result (also `--dry-run`; human mode prints them) lists `missing_task_target` (a flow or orchestrator task runs a config that is not in the tree), `encrypted_values_copied` (the `KBC::` paths the target cannot decrypt, as `_config.yml` paths: `secret_keys` get a plaintext + `sync push`, `unencryptable_keys` need `kbagent encrypt values`, `oauth_keys` a new authorization), `data_app_not_deployed` (run `data-app deploy`) and `schedule_not_active` (clone never activates a schedule; `flow schedule` does, and the hint is left out when several schedules run one flow), plus the push warnings. Only the run that creates the configs reports them: a re-run returns `warnings: []`, so keep them from the first run. **Idempotent**: re-running with an existing `--target-dir` skips copy/overrides and just pushes, reporting `no_changes` / `created: 0`. Fails fast (`CONFIG_ERROR`) if the target already contains the reference's configs -- clone requires a fresh/empty target. `SyncService.clone_project(...)` returns a typed `CloneResult` for in-process SDK callers. Override files must be flat `{id: scalar}` mappings *(since v0.89.0)* -- a nested mapping, list, or null value is rejected with `CONFIG_ERROR` (exit 5) naming the key and its actual type. `--branch` is optional on a fresh clone *(since v0.93.1)*. It defaults to the target's production branch, resolved from the API the same way `sync init` does. Pass `--branch ` only to target a dev branch. The config folder (`KBC.configuration.folderName`) is recreated in the target *(since 0.94.0)* -- clone re-points its production configs onto the branch push resolves, so the create-path writeback carries the folder for a plain, `--branch`, and git-branching production clone. **Data-app runtime type (since 0.94.0)**: a `keboola.data-apps` config's type (`python-js` / `streamlit`) lives only on the Data Science `/apps` record, so `sync pull` records it in `_keboola.data_app_type` and clone sends it through `create_app`. A config with no recorded type is created as `python-js`, the default, with a `data_app_type_default` warning *(since 0.96.0)*. Re-pull a tree pulled by an older version before cloning a Streamlit app, or it is created as `python-js`. **Storage buckets (since 0.96.0)**: clone copies configs, not storage, so a cloned config's input/output mappings point at buckets a fresh target lacks. Clone reads the `storage/buckets.json` pull export and creates the missing buckets in the target **by default** (`--no-create-buckets` skips it) -- idempotent (an existing bucket is skipped, a per-bucket API failure is collected in `bucket_errors`), and the created id is `--bucket-map`-remapped so it matches the rewritten config refs. Each bucket is created on the backend the export recorded. A linked (shared) bucket is linked to the same source as in the reference, under the same id, and listed in `linked_buckets` with its source (an empty bucket in its place would stay empty). The source project's sharing settings decide whether the target may link it; a refused link lands in `bucket_errors`. A tree pulled by an older version does not record which buckets are linked, so clone creates no bucket from it and records one `bucket_errors` entry -- re-pull the reference, or pass `--no-create-buckets`. Only the buckets are created, never their tables or data (the export has no table data) -- populate tables by bucket sharing / `storage upload-table`, or by running the flows. +- `sync diff --project ALIAS [--all-projects] [--branch ID]` -- 3-way diff (local vs base vs remote), detects conflicts. `--branch` (0.47.0+) per-invocation dev-branch override. Branch-scoped since v0.89.0 (issue #649): the local side is read from exactly ONE tree (the target branch's subtree, or `main/` when the target has none). Manifest entries belonging to another branch's tree -- what `sync pull --branch ` leaves behind when it re-targets the manifest -- are excluded from the changeset and reported under `orphaned` (`summary.orphaned` + details with `component_id`, `config_id`, `path`, `branch_id`, `branch_path`, `exists_on_target`, `reason`, `hint`); human mode previews the first 10. An orphaned FILE whose `_keboola.config_id` still resolves on the target is adopted (diffed as `unchanged`/`modified`), never re-created; same-tree id claims keep the #482/#497 fork-by-copy CREATE. Fix a non-zero `summary.orphaned` with `sync pull`. **Since 0.91.0 (#686)** a manifest entry without `metadata.config_hash_version` (written by a pre-0.91.0 kbagent) is compared leniently: a stored hash equal to the pre-0.91.0 hash of the SAME remote config counts as in sync, so the phantom `codes changed` entries disappear immediately; every other field is still pinned by that hash, so real remote drift is unaffected. One `sync pull` per project stamps the version and ends the leniency. Ignored components (since 0.91.0) -- `keboola.sandboxes`, `keboola.mcp-server-tool`, and anything listed in the manifest's `ignoredComponents` -- are excluded from BOTH sides of the comparison, so a stale local directory for one of them never shows up as `DELETED`. Since 0.96.0 (#792) a config or row the manifest fetched from the target branch and that is missing on the remote is `remote_deleted` (human: `- REMOTE DELETED`, `summary.remote_deleted`): run `sync pull`, push never re-creates it. `DELETED` changes are applied only by `sync push --force`. - `sync status [--directory DIR]` -- show locally modified/added/deleted configs. Also surfaces `plaintext_secret_warnings` (since 0.55.0): in-sync configs/rows whose `#`-secrets are still plaintext on the remote (a leftover from pre-0.54.0 writes; #378). Pending (un-pushed) edits are not flagged. Fix = re-push on >=0.54.0 + rotate (version history keeps the plaintext). - `sync branch-link --project ALIAS [--branch-id ID] [--branch-name NAME]` -- link git branch to Keboola dev branch - `sync branch-unlink [--directory DIR]` -- remove git-to-Keboola branch mapping diff --git a/plugins/kbagent/skills/kbagent/references/data-app-workflow.md b/plugins/kbagent/skills/kbagent/references/data-app-workflow.md index 66ff927b8..c22a40d40 100644 --- a/plugins/kbagent/skills/kbagent/references/data-app-workflow.md +++ b/plugins/kbagent/skills/kbagent/references/data-app-workflow.md @@ -166,7 +166,7 @@ never appears in argv. The service encrypts it under THIS project's KMS via the Encryption API before writing it to Storage. `--auth password` (the default) makes the platform generate a 20-character hex simpleAuth password during the deploy, so the password exists only after a deploy. The user copies -it to the clipboard with (since vNEXT; project token only, no Manage token): +it to the clipboard with (since 0.96.0; project token only, no Manage token): ```bash kbagent data-app password --project prod --app-id @@ -196,7 +196,7 @@ a deploy is needed anyway does the agent recommend `kbagent data-app deploy app, so it is never a way to get the password. When `password_delivered_to` is `null`, `ui_url` is the Keboola UI page that shows the password under Open App. `--reveal` prints it, for scripts and CI only (a script that read -`.data.password` must add `--reveal` since vNEXT). The Keboola UI can reset +`.data.password` must add `--reveal` since 0.96.0). The Keboola UI can reset the password. ### Roll out a new code version (no Storage edit) @@ -447,7 +447,7 @@ yours at runtime. | Roll out a new Storage config | `config update` (any field) → `data-app deploy` | | Wake an auto-suspended app | `data-app start --app-id N` | | Pause a running app temporarily | `data-app stop --app-id N` | -| Give the user the simpleAuth password | `data-app password --app-id N` in the user's terminal (press `c`; `deploy --wait` does the same when a deploy is needed anyway), or `--copy` (since vNEXT; never printed without `--reveal`) | +| Give the user the simpleAuth password | `data-app password --app-id N` in the user's terminal (press `c`; `deploy --wait` does the same when a deploy is needed anyway), or `--copy` (since 0.96.0; never printed without `--reveal`) | | Set or rotate app-runtime secrets | `data-app secrets-set --app-id N --secret '#KEY=VAL'` then `data-app deploy --wait` | | Inspect what's set (secrets + plain env vars) | `data-app secrets-list --app-id N` (metadata only, never decrypts) | | Read one key | `data-app secrets-get --app-id N --key KEY` (`#` optional; encrypted → metadata only, plain → value) | @@ -484,7 +484,7 @@ yours at runtime. | `GET` | `data-science./apps/{id}` | `data-app detail`, poll loop, `data-app password` | | `PATCH` | `data-science./apps/{id}` | `data-app deploy / start / stop` | | `DELETE` | `data-science./apps/{id}` | `data-app delete` (cascades to Storage) | -| `GET` | `data-science./apps/{id}/password` | `data-app password` (project token only, since vNEXT) | +| `GET` | `data-science./apps/{id}/password` | `data-app password` (project token only, since 0.96.0) | | `GET` | `data-science./apps/{id}/logs/tail` | `data-app logs` (since 0.43.8; `lines` / `since` mutex) | | `GET` | `data-science./apps/{id}/runs` | `data-app runs` (since 0.65.0; deployment attempts + failure_reason / startup_logs) | | `POST` | `encryption./encrypt` | `data-app create` step 2 (private repo) | diff --git a/plugins/kbagent/skills/kbagent/references/gotchas.md b/plugins/kbagent/skills/kbagent/references/gotchas.md index 41b20504d..4fe1804a1 100644 --- a/plugins/kbagent/skills/kbagent/references/gotchas.md +++ b/plugins/kbagent/skills/kbagent/references/gotchas.md @@ -13,7 +13,7 @@ Versioning convention: ## Every command that picks a branch names it: `Target:` line and `targets` key -*(since vNEXT, #766)* +*(since 0.96.0, #766)* - **`kbagent branch use` sets an active branch per project, and commands apply it when `--branch` is omitted.** Before, some commands printed an @@ -62,7 +62,7 @@ Versioning convention: refused to run because it needs a branch and got none. It does not mean production. - **`--branch 0` means production.** The API clients always sent 0 to the - production endpoint. Before vNEXT the config, flow, schedule and + production endpoint. Before 0.96.0 the config, flow, schedule and notification commands used the active branch for `--branch 0` instead. - **`--dry-run` reports the same target as the real run.** `flow delete --dry-run` and `flow schedule-remove --dry-run` now put the resolved branch @@ -80,7 +80,7 @@ Versioning convention: ## `--project` takes a project ID as well as an alias -*(since vNEXT)* (CLI-22) +*(since 0.96.0)* (CLI-22) - **A registered project's numeric ID works wherever `--project` takes an alias.** `kbagent config list --project 9840` runs against the alias whose @@ -328,7 +328,7 @@ Versioning convention: `--alias ID=ALIAS`, or request a second alias for an already-registered project. An existing entry is never overwritten either way. - **Registered aliases derive from the project NAME, never the numeric - project id.** Before vNEXT `--project 9840` never resolved; since then it + project id.** Before 0.96.0 `--project 9840` never resolved; since then it resolves once the project is registered (see "`--project` takes a project ID as well as an alias"). `login`'s accessible-projects table shows a numeric `id`, but the alias @@ -826,7 +826,7 @@ Versioning convention: ## `sync pull` protects edits in `transform.sql` / `code.py` / `_description.md`, not only `_config.yml` -*(since vNEXT, #792)* +*(since 0.96.0, #792)* `sync pull` decided "locally modified" from `_config.yml` alone. An edit that lived only in a companion file -- `transform.sql`, `transform.py`, `code.py`, @@ -1008,11 +1008,11 @@ per-branch subtree *does* exist, behaviour is unchanged. ## Repeating a promote `sync push --branch ` no longer duplicates configs -*(since vNEXT)* +*(since 0.96.0)* When the dev branch lacks a production config (typically one created in production after the branch was cut), the promote push above CREATEs a dev copy -under a new id. Before vNEXT the next `sync diff --branch ` still reported +under a new id. Before 0.96.0 the next `sync diff --branch ` still reported that config as `added`, and every further `sync push --branch ` created **another** dev copy -- N pushes, N copies of the same config on the branch. Now the dev entry the first push recorded is what the `main/` directory is @@ -1100,13 +1100,13 @@ without losing data. A `keboola.data-apps` config's runtime type (`python-js` / `streamlit` / ...) lives only on the Data Science `/apps` record, never in the Storage config body. So `sync pull` used to drop it, and `sync push` / `sync clone` recreated the config through the Storage API alone. A cloned `python-js` app then deployed under the platform default, `streamlit` (since 0.94.0). -`sync pull` now reads the type from the DS `/apps` list and records it in the config's `_keboola` block as `data_app_type`. The config hash already ignores that key, so it adds no `sync diff` noise. `sync push` and `sync clone` route a `keboola.data-apps` CREATE through the Data Science `create_app` when the local config carries a `data_app_type`. That call sends the type and writes the new app's `parameters.id`. A config with no recorded type (hand-authored, or pulled before 0.94.0) is created as `python-js`, the default, through the same `create_app` call *(since vNEXT)*. Before that it went through the plain `create_config` path and the platform picked `streamlit`. The push records the type in the local `_keboola` block and adds a `data_app_type_default` warning to the result. To keep a Streamlit app a Streamlit app, re-pull the source first or set `_keboola.data_app_type: streamlit`. +`sync pull` now reads the type from the DS `/apps` list and records it in the config's `_keboola` block as `data_app_type`. The config hash already ignores that key, so it adds no `sync diff` noise. `sync push` and `sync clone` route a `keboola.data-apps` CREATE through the Data Science `create_app` when the local config carries a `data_app_type`. That call sends the type and writes the new app's `parameters.id`. A config with no recorded type (hand-authored, or pulled before 0.94.0) is created as `python-js`, the default, through the same `create_app` call *(since 0.96.0)*. Before that it went through the plain `create_config` path and the platform picked `streamlit`. The push records the type in the local `_keboola` block and adds a `data_app_type_default` warning to the result. To keep a Streamlit app a Streamlit app, re-pull the source first or set `_keboola.data_app_type: streamlit`. The DS `/apps` list also returns sandbox and workspace records. Each carries a parent component's id and a backend `type` such as `snowflake`. So kbagent builds the type map from `componentId == keboola.data-apps` records only. ## `sync clone` recreates the reference's storage buckets -`sync clone` copies component configs, not storage. The pulled `storage/` tree is a read-only snapshot, so a cloned config's input/output mappings point at buckets a fresh target project does not have. Clone *(since vNEXT)* closes that gap for the buckets **by default**: it reads the `storage/buckets.json` pull export, maps each bucket id through `--bucket-map` (so a created bucket matches what the config refs were rewritten to), and creates the ones the target is missing. Pass `--no-create-buckets` to skip it -- a clone is a complete clone by default, so this is opt-out, never opt-in. +`sync clone` copies component configs, not storage. The pulled `storage/` tree is a read-only snapshot, so a cloned config's input/output mappings point at buckets a fresh target project does not have. Clone *(since 0.96.0)* closes that gap for the buckets **by default**: it reads the `storage/buckets.json` pull export, maps each bucket id through `--bucket-map` (so a created bucket matches what the config refs were rewritten to), and creates the ones the target is missing. Pass `--no-create-buckets` to skip it -- a clone is a complete clone by default, so this is opt-out, never opt-in. Idempotent by design -- an existing bucket is skipped, and a per-bucket API failure is collected in the result's `bucket_errors` rather than aborting the clone. It runs even on an idempotent re-run (existing `--target-dir`), so a re-clone fills in any bucket the target is still missing. Bucket creation happens before the config push, and buckets are created at production level (no branch scoping). Each bucket is created on the backend the export recorded. If the target cannot list its buckets, clone records one `bucket_errors` entry, creates no bucket, and still pushes the configs. @@ -1116,9 +1116,9 @@ Only the buckets are created, never their tables or their data -- the pull expor ## `sync clone` re-points shared code, orchestrator tasks and schedules, and lists what the target still needs -*(since vNEXT)* +*(since 0.96.0)* -Before vNEXT, `sync push` set only two kinds of links to the config IDs it created in the same push: `keboola.flow` job-task `configId`s and transformation `variables_id` / `variables_values_id`. All other links kept the reference IDs, and `sync clone` still reported `status: cloned` with `errors: []`: a transformation's `shared_code_id`, `shared_code_row_ids` and `{{}}` script placeholders, a legacy `keboola.orchestrator` task's `configId`, a task's `configRowIds`, and a `keboola.scheduler` config's `target.configurationId`. +Before 0.96.0, `sync push` set only two kinds of links to the config IDs it created in the same push: `keboola.flow` job-task `configId`s and transformation `variables_id` / `variables_values_id`. All other links kept the reference IDs, and `sync clone` still reported `status: cloned` with `errors: []`: a transformation's `shared_code_id`, `shared_code_row_ids` and `{{}}` script placeholders, a legacy `keboola.orchestrator` task's `configId`, a task's `configRowIds`, and a `keboola.scheduler` config's `target.configurationId`. Now push sets these to the new IDs too, for a clone and for a plain `sync push` of a fresh tree. It rewrites the placeholders in the remote scripts and in the local `transform.sql` / `transform.py`. A row ID is looked up under its own new parent config, so two shared-code configs with the same row ID do not swap rows. Push never registers a schedule with the Scheduler service, so a cloned project starts no jobs by itself. @@ -1256,7 +1256,7 @@ is uncertain rather than confirmed-bad. ## `workspace from-transformation` reads the transformation from the active branch -*(since vNEXT, #807)* `workspace from-transformation` reads the transformation config from the +*(since 0.96.0, #807)* `workspace from-transformation` reads the transformation config from the same branch the workspace is created and loaded in: the active branch (`branch use`), or the default branch on production. Before, the workspace was created in the active branch but the config was always read from **production**, so with a dev branch active: @@ -1331,10 +1331,10 @@ kbagent --json workspace list --project prod --qs-compatible **Branch behaviour:** `workspace list` / `workspace detail` use the alias's active branch -(`branch use`) when `--branch` is omitted, like `config list`. Up to vNEXT +(`branch use`) when `--branch` is omitted, like `config list`. Up to 0.96.0 they printed `Info: Using production branch for read (active dev branch X ignored; pass --branch X to override)`, but the workspace service used the -active branch: the line was wrong, the listing was not. Since vNEXT they +active branch: the line was wrong, the listing was not. Since 0.96.0 they print `Target:` with the branch they use (see the #766 entry at the top). `storage buckets` / `storage tables` are the reads that use production under an active branch. `--branch` requires exactly one `--project`. @@ -2260,9 +2260,9 @@ config, the retry fires, and the retry destroys it for good. ## `data-app password` keeps the password out of the chat: `c` in a terminal, `--copy` elsewhere -*(since vNEXT)* +*(since 0.96.0)* -- **The password is not printed without `--reveal`.** Before vNEXT the +- **The password is not printed without `--reveal`.** Before 0.96.0 the command printed it (`Password: ...`, and a `password` key in `--json`), so it went into the context of any AI agent that ran it. On an older kbagent, do not run the command from an agent: send the user to the Keboola UI. @@ -2367,9 +2367,9 @@ config, the retry fires, and the retry destroys it for good. `componentId == keboola.data-apps` (items missing `componentId` are kept defensively); the JSON envelope carries `component_id` per app. - **`data-app list` pages through the whole `GET /apps` collection - *(since vNEXT, #798)*.** The endpoint is paginated (default page = 100 + *(since 0.96.0, #798)*.** The endpoint is paginated (default page = 100 items) and the workspace/data-app mix is filtered CLIENT-side. Before - vNEXT kbagent read only that first page, so a project with many + 0.96.0 kbagent read only that first page, so a project with many workspaces could report "No data apps found." (or a partial list) while holding dozens of data apps further down the collection -- one reporter's first data app was item #258 of 1,114. The same short read also made @@ -2470,7 +2470,7 @@ config, the retry fires, and the retry destroys it for good. - `KBC_MANAGE_API_TOKEN` is no longer auto-resolved on the three surfaces that consume it (`kbagent org setup`, `kbagent project refresh`, `kbagent data-app password` -- the last one - needs no Manage token since vNEXT). Default + needs no Manage token since 0.96.0). Default behaviour on 0.29.0+ is **default-deny**: the env var is ignored, a TTY hidden-input prompt is shown instead. With no TTY (CI / cron / systemd / `< /dev/null`) the resolver exits **2** with the message @@ -4341,7 +4341,7 @@ branch, and every later `sync push` falls back to it. Push used to treat that first manifest branch as production when it created a `keboola.data-apps` config, so it sent `branchId: null` to the Data Science API. The data app was created in PRODUCTION while every other config went to the dev branch. -*(since vNEXT, #808)* push compares the push branch against the project's real +*(since 0.96.0, #808)* push compares the push branch against the project's real default branch from the API (one extra call, made only when the push creates a data app). If that lookup fails, push sends the numeric branch id and adds a `data_app_branch_lookup_failed` warning, so it never assumes production. On an @@ -4387,7 +4387,7 @@ Four related sync-engine behaviors landed together (issues #466 / #467 / #472 / pull even when the remote is unchanged (manifest<->disk invariant). The old behavior silently reported "Already up to date". NOTE the interplay with the GitOps delete flow: delete-dir-then-PUSH still deletes the remote config - (since vNEXT only with `push --force`, #792); delete-dir-then-PULL now + (since 0.96.0 only with `push --force`, #792); delete-dir-then-PULL now restores it instead of doing nothing. - **Config-level `isDisabled` round-trips.** Pull writes a sparse `is_disabled: true` line into `_config.yml` (absent key = enabled -- old trees @@ -4401,7 +4401,7 @@ Four related sync-engine behaviors landed together (issues #466 / #467 / #472 / remote config. diff/push report it under `never_fetched` (JSON key + human warning); the next `sync pull` materializes it. A properly-pulled config (non-empty `pull_hash`) that you delete locally is still planned as a remote - DELETE on push (applied since vNEXT only with `--force`, #792) -- the guard + DELETE on push (applied since 0.96.0 only with `--force`, #792) -- the guard only protects entries that were never on disk. - **Adopted-by-id push writes the manifest.** Pushing an untracked local file whose `_keboola.config_id` resolves on the target branch (the #482 @@ -4602,7 +4602,7 @@ applied. `kbagent update` printed `(scheduled)` and every later launch printed ## Windows self-update on a OneDrive / cloud-synced profile can delete kbagent -*(since vNEXT, #786)* +*(since 0.96.0, #786)* If a Windows user reports that `kbagent` vanished after a background update, check `%LOCALAPPDATA%\keboola-agent-cli\keboola-agent-cli\pending_update.log` @@ -5116,7 +5116,7 @@ maximum as `job detail`'s. ## `logTail` was empty for nested jobs -Fixed (since vNEXT). A job inside a flow, or a child row job of a row-based component, has a +Fixed (since 0.96.0). A job inside a flow, or a child row job of a row-based component, has a dotted Queue `runId` (`..`). The Storage Events API returns zero events for that dotted value, so `job detail --log-tail-lines` and the `serve` job log stream came back with `logTail: []` for such jobs @@ -5630,7 +5630,7 @@ It carries the command name, the outcome, and the duration -- never argument val ## `sync pull` no longer deletes a re-created config or a locally edited directory (#792) -*(since vNEXT)* Two data-loss paths in pull's stale-entry sweep (the step that +*(since 0.96.0)* Two data-loss paths in pull's stale-entry sweep (the step that drops manifest entries whose config is gone from the remote) are closed: - **Remote delete + re-create under the same name.** Before, the new config was @@ -5651,7 +5651,7 @@ drops manifest entries whose config is gone from the remote) are closed: ## `sync push` deletes only with `--force` and never re-creates a config deleted on the remote (#792) -*(since vNEXT)* Two changes to what `sync push` sends: +*(since 0.96.0)* Two changes to what `sync push` sends: - **Deletions need `--force`.** Before, push deleted a remote config or row as soon as its local files were gone, with or without `--force`, although the @@ -5687,7 +5687,7 @@ drops manifest entries whose config is gone from the remote) are closed: ## `sync` can sync shared SQL workspaces, opt-in per tree (CLI-25) -*(since vNEXT)* `keboola.sandboxes` is no longer skipped when the manifest sets +*(since 0.96.0)* `keboola.sandboxes` is no longer skipped when the manifest sets `"syncWorkspaces": true` (`sync init --with-workspaces`, or `sync init --adopt-existing --with-workspaces` for an existing tree). Without the key nothing changes. Full rules: `sync-workflow.md` > "Shared SQL diff --git a/plugins/kbagent/skills/kbagent/references/permissions-workflow.md b/plugins/kbagent/skills/kbagent/references/permissions-workflow.md index 7567a2c6d..87d136da4 100644 --- a/plugins/kbagent/skills/kbagent/references/permissions-workflow.md +++ b/plugins/kbagent/skills/kbagent/references/permissions-workflow.md @@ -77,7 +77,7 @@ The agent can still pull configs and view diffs, but cannot push changes back. N kbagent permissions set --mode allow --deny "cli:destructive" ``` Blocks `branch.delete`, `workspace.delete`, `config.delete`. The agent can still create and modify resources. -*(since vNEXT)* It also blocks `sync push --force` (operation `sync.push --force`, a flag escalation like `auth.logout --remove-projects`): a forced push of a tree that syncs SQL workspaces deletes their SQL editor sessions and workspaces. A plain `sync push` stays write-class and allowed. +*(since 0.96.0)* It also blocks `sync push --force` (operation `sync.push --force`, a flag escalation like `auth.logout --remove-projects`): a forced push of a tree that syncs SQL workspaces deletes their SQL editor sessions and workspaces. A plain `sync push` stays write-class and allowed. ### Allow only specific commands (strict allowlist) ```bash @@ -88,7 +88,7 @@ kbagent permissions set --mode deny \ ``` Everything else is blocked. This is the most restrictive approach. -*(since vNEXT)* `sync push --force` is checked as its own operation, `sync.push --force`. An allow-list that names only `sync.push` allows a plain push and blocks a forced push (exit 6). To allow a forced push, add `--allow "sync.push --force"`, or use a glob such as `sync.*`. The same is true for a default-allow policy that denies `cli:write` and allows `sync.push`. +*(since 0.96.0)* `sync push --force` is checked as its own operation, `sync.push --force`. An allow-list that names only `sync.push` allows a plain push and blocks a forced push (exit 6). To allow a forced push, add `--allow "sync.push --force"`, or use a glob such as `sync.*`. The same is true for a default-allow policy that denies `cli:write` and allows `sync.push`. ## Checking permissions before acting diff --git a/plugins/kbagent/skills/kbagent/references/sync-rows-workflow.md b/plugins/kbagent/skills/kbagent/references/sync-rows-workflow.md index 210775290..261fcd030 100644 --- a/plugins/kbagent/skills/kbagent/references/sync-rows-workflow.md +++ b/plugins/kbagent/skills/kbagent/references/sync-rows-workflow.md @@ -221,7 +221,7 @@ Row diff is the same 3-way engine as parent configs, just keyed by row id: Added rows (filesystem-only) show as `added`; removed rows (manifest-only after file deletion) show as `deleted` -- `push --force` DELETEs them via `_push_delete_row`, a plain push lists them under `skipped_deletions` -*(since vNEXT, #792)*. A row deleted on the remote since the last pull shows +*(since 0.96.0, #792)*. A row deleted on the remote since the last pull shows as `remote_deleted`; push never re-creates it. `sync pull` deletes its directory, or keeps an edited one and reports it as `skipped`. diff --git a/plugins/kbagent/skills/kbagent/references/sync-workflow.md b/plugins/kbagent/skills/kbagent/references/sync-workflow.md index 360e51527..b6d2acff2 100644 --- a/plugins/kbagent/skills/kbagent/references/sync-workflow.md +++ b/plugins/kbagent/skills/kbagent/references/sync-workflow.md @@ -90,7 +90,7 @@ Related semantics (all since v0.72.0): - Plain `sync pull` re-materializes a tracked config whose local dir was deleted (delete-dir-then-pull refetches; delete-dir-then-`push --force` deletes the remote config -- the direction of the command picks the winner). - *(since vNEXT, #792)* A plain `sync push` deletes nothing: it lists the + *(since 0.96.0, #792)* A plain `sync push` deletes nothing: it lists the deletion under `skipped_deletions` and says to add `--force`. - Config-level enabled/disabled state round-trips: `_config.yml` carries `is_disabled: true` for disabled configs (absent = enabled), `sync diff` @@ -147,7 +147,7 @@ kbagent sync push --project prod --branch 388072 When a per-branch subtree *does* exist (multi-branch-directory users), the target subtree is used as before — behaviour is unchanged. -Promoting is idempotent (since vNEXT): a config the dev branch lacks is created +Promoting is idempotent (since 0.96.0): a config the dev branch lacks is created there once, and the manifest records that dev copy for the same `main/` directory. Re-running the same push creates nothing; a later edit in `main/` updates the dev copy, never production. Older versions created another dev copy @@ -416,8 +416,8 @@ Stored in `.keboola/branch-mapping.json`: | REMOTE MODIFIED | Remote changed, local unchanged | Run pull to fetch | | CONFLICT | Both sides changed | Resolve manually, then push | | ADDED | New local config | Push creates it | -| DELETED | Local file removed | `push --force` deletes from remote; a plain push lists it under `skipped_deletions` *(since vNEXT)* | -| REMOTE DELETED | Deleted on the remote since the last pull | Run pull; push never re-creates it *(since vNEXT)* | +| DELETED | Local file removed | `push --force` deletes from remote; a plain push lists it under `skipped_deletions` *(since 0.96.0)* | +| REMOTE DELETED | Deleted on the remote since the last pull | Run pull; push never re-creates it *(since 0.96.0)* | ## Key behaviors @@ -425,12 +425,12 @@ Stored in `.keboola/branch-mapping.json`: - **Pull protects local edits**: locally-modified files are skipped by default -- and "locally modified" covers the whole config, not only `_config.yml`: an edit to a companion file (`transform.sql`, `code.py`, `_description.md`, - ...) protects the config the same way *(since vNEXT, #792)*. Before, such an + ...) protects the config the same way *(since 0.96.0, #792)*. Before, such an edit was silently overwritten whenever the remote changed, plain or `--force` - **`--force` is conflict-aware**: see below -- it no longer blindly overwrites - **Push only sends local changes**: remote_modified, conflict and remote_deleted changes are skipped (`skipped` in the result), and local deletions are applied - only with `--force` (`skipped_deletions` otherwise) *(since vNEXT, #792)* + only with `--force` (`skipped_deletions` otherwise) *(since 0.96.0, #792)* - **Push records the API's own view of what it wrote (since 0.91.0, #686)**: the manifest baseline (`pull_config_hash`) comes from the API response (or a read-back), never from the files on disk. Before 0.91.0 the two producers @@ -460,7 +460,7 @@ internal state: `keboola.mcp-server-tool` (the Keboola MCP server auto-creates one empty workspace-record config per project it touches, `configuration: {}`, name like `mcp-workspace-`). This is a hardcoded floor. The one exception - *(since vNEXT)*: the manifest key `syncWorkspaces` takes `keboola.sandboxes` + *(since 0.96.0)*: the manifest key `syncWorkspaces` takes `keboola.sandboxes` off the list for its shared SQL workspaces, see [Shared SQL workspaces](#shared-sql-workspaces). `keboola.mcp-server-tool` stays ignored. @@ -490,7 +490,7 @@ internal state: ## Shared SQL workspaces -*(since vNEXT)* A tree can sync the shared SQL workspaces (Snowflake, +*(since 0.96.0)* A tree can sync the shared SQL workspaces (Snowflake, BigQuery) of a project. It is opt-in per tree, with the manifest key `syncWorkspaces`: @@ -563,7 +563,7 @@ sync skips `keboola.sandboxes` exactly as before. `--force` no longer blindly overwrites locally-modified configs. It branches on the 3-way diff state per config (and per row). "Local edited" means any file of the config -- `_config.yml` or a companion file such as `transform.sql` -*(since vNEXT, #792)*; before, a companion-only edit was never a conflict and +*(since 0.96.0, #792)*; before, a companion-only edit was never a conflict and was overwritten: - **Local edited, remote UNCHANGED** -> the file and its sync baseline are @@ -575,7 +575,7 @@ was overwritten: error code `SYNC_CONFLICT`, listing every conflicting config/row. Resolve with `sync diff`, then `sync push` your edits (or discard them), then pull again. - **Local untouched, remote changed** -> `--force` takes remote as before. -- **Local edited, remote DELETED** *(since vNEXT, #792)* -> `--force` aborts +- **Local edited, remote DELETED** *(since 0.96.0, #792)* -> `--force` aborts with `SYNC_CONFLICT` (conflict `reason: "deleted on remote"`). Plain pull keeps the edited directory and its manifest entry and reports it as `skipped` (`locally modified, deleted on remote`). Only `--theirs` deletes it. @@ -583,7 +583,7 @@ was overwritten: (it diffs as `remote_deleted`); delete the directory if the remote delete was intended, or restore the config with `kbagent config restore` to keep it. -> A config deleted and re-created remotely under the same name *(since vNEXT, +> A config deleted and re-created remotely under the same name *(since 0.96.0, > #792)* is written to a suffixed directory while the old one is removed; the > next pull renames it back. Before, the sweep deleted the new config's files > and the next push deleted the new config remotely. @@ -593,7 +593,7 @@ was overwritten: > stops you loudly instead of losing work. To intentionally drop a local edit, > run `sync pull --theirs`, or delete the whole config directory and pull. > Deleting only a companion file (`transform.sql`, ...) counts as a local edit -> *(since vNEXT, #792)* -- `sync diff` / `sync push` read it that way too -- so +> *(since 0.96.0, #792)* -- `sync diff` / `sync push` read it that way too -- so > plain pull keeps it deleted. ## Migrating a legacy sync tree (#686) @@ -657,7 +657,7 @@ nested mapping, list, or empty (`null`) value is rejected with `CONFIG_ERROR` (exit 5) naming the offending key and its actual type, instead of being silently stringified into a bogus ID. -**Storage buckets (since vNEXT):** clone copies configs, not storage — a cloned +**Storage buckets (since 0.96.0):** clone copies configs, not storage — a cloned config's input/output mappings point at buckets a fresh target does not have. By default clone reads the `storage/buckets.json` pull export and creates the missing buckets in the target. Pass `--no-create-buckets` to skip it — a clone is a @@ -684,9 +684,9 @@ id, the **Phase-C** transformation variable links **and the Phase-D `keboola.flow` task `configId`s** remap reference→ULID automatically — no manual "remap orchestrator task" pass. The push result carries `flow_task_remaps`. -Since vNEXT push also remaps a transformation's shared code (`shared_code_id`, `shared_code_row_ids` and the `{{}}` script placeholders), legacy `keboola.orchestrator` task `configId`s, task `configRowIds`, and a schedule's `target.configurationId`. The result carries `link_remaps` with one count per kind. A link push cannot set is an `errors[]` entry (`shared_code_link`, `flow_task_link`, `schedule_target_link`). After a failed PUT the next `sync push` or clone re-run sends the link again. +Since 0.96.0 push also remaps a transformation's shared code (`shared_code_id`, `shared_code_row_ids` and the `{{}}` script placeholders), legacy `keboola.orchestrator` task `configId`s, task `configRowIds`, and a schedule's `target.configurationId`. The result carries `link_remaps` with one count per kind. A link push cannot set is an `errors[]` entry (`shared_code_link`, `flow_task_link`, `schedule_target_link`). After a failed PUT the next `sync push` or clone re-run sends the link again. -**Check `warnings[]` after a clone (since vNEXT).** Some configs need an action in the target. The clone result lists each one in `warnings[]` next to the push warnings, also for `--dry-run`, and human mode prints them. Only the run that creates the configs reports them, so keep them from the first run: +**Check `warnings[]` after a clone (since 0.96.0).** Some configs need an action in the target. The clone result lists each one in `warnings[]` next to the push warnings, also for `--dry-run`, and human mode prints them. Only the run that creates the configs reports them, so keep them from the first run: - `missing_task_target`: a flow or orchestrator task runs a config that is not in the tree, for example an ignored `keboola.sandboxes` config. - `encrypted_values_copied`: `KBC::` values that only the reference project can decrypt, as `_config.yml` paths. For `secret_keys` put the plaintext into the clone's `_config.yml` and run `sync push`. `unencryptable_keys` need `kbagent encrypt values`, `oauth_keys` a new authorization. diff --git a/plugins/kbagent/skills/kbagent/references/workspace-workflow.md b/plugins/kbagent/skills/kbagent/references/workspace-workflow.md index c30800b67..dbe91cbb4 100644 --- a/plugins/kbagent/skills/kbagent/references/workspace-workflow.md +++ b/plugins/kbagent/skills/kbagent/references/workspace-workflow.md @@ -19,7 +19,7 @@ kbagent --json workspace from-transformation \ ``` The transformation is read from the active branch (`branch use`), the same branch the -workspace is created in *(since vNEXT, #807)*; before that it was always read from production. +workspace is created in *(since 0.96.0, #807)*; before that it was always read from production. ```bash # Step 2: Run the original SQL to reproduce the error diff --git a/pyproject.toml b/pyproject.toml index 48c93846a..a50503dfb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "keboola-cli" -version = "0.95.0" +version = "0.96.0" description = "AI-friendly CLI for managing Keboola projects" readme = "README.md" requires-python = ">=3.12" diff --git a/src/keboola_agent_cli/changelog.py b/src/keboola_agent_cli/changelog.py index 4c4d30d14..e4522a674 100644 --- a/src/keboola_agent_cli/changelog.py +++ b/src/keboola_agent_cli/changelog.py @@ -24,6 +24,197 @@ # Ordered newest-first. Each value is a list of brief one-line descriptions. CHANGELOG: dict[str, list[str]] = { + "0.96.0": [ + "BREAKING (#813): `kbagent data-app password` no longer prints the password unless " + "you pass `--reveal`. When an AI agent ran the command, the password went into the model " + "context and the chat history (CLI-23). The command now uses the project token (static or " + "session) and no longer needs a Manage API token (`KBC_MANAGE_API_TOKEN`). In a terminal " + "it shows the app URL and `ui_url`, and it copies the password to the clipboard only when " + "you press `c`. Without a terminal, or with `--json`, only `--copy` copies the password. " + "Without `--copy`, the result has `password_delivered_to: null` and refers to `ui_url`. " + "`--copy` together with `--reveal` fails with `INVALID_ARGUMENT`. `--open` opens the app " + "in the browser. `data-app create --wait` and `data-app deploy --wait` take the same " + "`--copy` and `--reveal`. For a password app in a terminal, they finish with the same `c` " + "prompt, which closes on Enter or after 120 seconds. The REST route `GET " + "/data-apps/{project}/{app_id}/password` returns `password` only with `reveal=true`. " + "Migration: a script that reads `.data.password` must add `--reveal`. A REST client must " + "pass `reveal=true`.", + "BREAKING (#811): `sync push` deletes a remote config or row only with `--force`, " + "as the `--force` help says. Before, push deleted a remote config or row as soon as its " + "local files were missing, also without `--force`. A plain push now deletes nothing. It " + "lists each skipped deletion under `skipped_deletions`, with `skipped_deletions_reason`, " + "also with `--dry-run`. With `--dry-run`, `summary.deleted` counts only the deletions that " + "push applies. `sync diff` marks a deletion as `to delete (push --force)`. The " + "`kbagent-promotion-pipeline` workflows now pass `--force` to the validate dry run and to " + "the push. The `kbagent-cicd-migration` workflow passes `--force` only when its " + "`allow_delete` input is set. Migration: a script or CI job that deletes configs by " + "removing their directories must add `--force`. A promotion pipeline generated before this " + "release needs `--force` in its validate and push steps.", + "BREAKING (#811): `sync push` no longer creates again a config or row that someone " + "deleted on the remote after the last pull. Before, push created it again under a new ID " + "and did not tell the user. Now `sync diff` reports it as `remote_deleted` (`- REMOTE " + "DELETED` in human output, `remote_deleted` in the summary). Push skips it and lists it " + "under `skipped` with `skipped_reason`. These two keys are now in every push result, not " + "only when the status is `no_changes`. `sync pull` deletes the local copy, or keeps a " + "locally edited copy and reports it as `skipped`. Push still creates a config that pull " + "never fetched from the target branch. Examples are a `sync clone` copy, a promote push " + "from `main/` and a placeholder entry written by hand. Migration: to keep a config that " + "someone deleted on the remote, restore it with `kbagent config restore`. The restored " + "config keeps its ID. A `sync clone` target left by a failed clone run of an older kbagent " + "can report configs as `remote_deleted`. Delete that target directory and run the clone " + "again.", + "New (#812): `--project` and the other options that take a project alias now also take a " + "project ID (CLI-22). This covers `--project` on every command, `project use`, " + "`KBAGENT_PROJECT`, `sync clone --target`, `config clone --target-project`, and `--stack` " + "on the `auth` commands. It also covers the project options of `semantic-layer promote` " + "and `semantic-layer diff`. In `kbagent serve`, it covers the project and alias parameters " + "in paths and queries, but not request bodies. An alias always wins. kbagent checks the " + "project IDs only when no alias matches. When an alias is also the ID of another " + "registered project, kbagent shows a warning on stderr. An ID that matches more than one " + "registered project fails with `CONFIG_ERROR`. When all matches are on one stack and " + "exactly one of them has a session token, kbagent uses that project. Human output prints " + "the alias that the ID resolved to on stderr. `project current` reports an ambiguous " + "`KBAGENT_PROJECT` in the new field `env_error`. Behavior change: the `project` column of " + "`project invite --from-csv` follows the same rules. A numeric value that is also an alias " + "now means that alias, and an ambiguous ID is an error.", + "Fix (#800): each command now reports the project and branch that it uses, on stderr and " + "in a `targets` key of the `--json` envelope (#766). Before, `workspace create` and most " + "config and flow commands used the active branch from `branch use` with no notice. With " + "`--json`, no command reported its branch. Now one function chooses the branch and records " + "it. The human output shows a `Target:` line before the first API call and before any " + "prompt. A command that does not use the active branch reports production and names the " + "active branch that it did not use. A `Source:` line names a branch that a command reads " + "from or merges from. The error envelope also has `targets`, and `data` and `error` do not " + "change. `kbagent serve`, the REPL and the SDK report nothing. `branch use` and `branch " + "create` now also save the branch name (`active_branch_name`). Behavior changes: " + "`workspace list` and `workspace detail` no longer print the wrong line `Using production " + "branch for read`. `config clone` within one alias, with `--branch` and an active branch, " + "now copies into that branch instead of refusing. `flow delete --dry-run` and `flow " + "schedule-remove --dry-run` show the resolved branch in `would_delete.branch_id`.", + "Change (#800): `--branch 0` now means production in every command. Before, the config, " + "flow, schedule and notification commands used the active branch for `--branch 0`. The API " + "clients always sent 0 to the production endpoint.", + "New (#815): `sync pull`, `diff`, `push` and `clone` can sync the shared SQL workspaces " + "of a project, opt-in per tree (CLI-25). Enable it with `sync init --with-workspaces`, " + "with `sync init --adopt-existing --with-workspaces` for an existing tree, or with " + '`"syncWorkspaces": true` in `.keboola/manifest.json`. Without the key, sync skips ' + "`keboola.sandboxes` as before. Only a `keboola.sandboxes` config with no `parameters.id` " + "and with `runtime.shared: true` is in scope. Python and R workspaces and legacy SQL " + "sandboxes stay skipped. Push writes only the Storage configuration. It never starts a " + "job, creates a SQL editor session or loads tables. A `parameters.backendSize` change " + "gives a `workspace_backend_size` warning, because an open session keeps its old size. " + "`push --force` deletes the SQL editor sessions of every user in the push branch before it " + "deletes the configuration. `config restore` does not restore those sessions or their " + "backend workspaces. Run `push --dry-run --force` first: its `workspace_sessions` warnings " + "list the session IDs. `sync clone` adds a `workspace_input_tables_missing` warning for " + "each cloned workspace whose input tables do not exist in the target. When you remove the " + "key, the next `sync pull` drops the workspace entries with action `ignored`. It keeps a " + "workspace with local edits that are not pushed, and reports it as `skipped`. An " + "`ignoredComponents` entry for `keboola.sandboxes` wins over the key. A manifest save by " + "the `kbc` CLI removes the key.", + "BREAKING (#815): `sync push --force` now needs the `destructive` permission " + "class, so a policy that denies `cli:destructive` blocks it. `--deny-destructive` blocks " + "it too. A plain `sync push` stays in the `write` class. `permissions list` shows the new " + "operation `sync.push --force`. The reason: in a tree that syncs shared SQL workspaces, a " + "forced push also deletes their SQL editor sessions and workspaces. An allow-list that " + "names only `sync.push` also blocks a forced push, and so does a policy that denies " + '`cli:write` and allows `sync.push`. Migration: add `--allow "sync.push --force"` or a ' + "glob such as `sync.*` to the policy.", + "Fix (#814): `sync clone` no longer leaves source project IDs in the links of shared code, " + "schedules, legacy orchestrations and task rows (CLI-24). Before, clone reported `status: " + "cloned` with no error and no warning. Push now sets these links to the new IDs, in the " + "remote configuration and in the local files. The links are `shared_code_id`, " + "`shared_code_row_ids`, the `{{}}` placeholders in scripts, the schedule " + "`target.configurationId`, the `keboola.orchestrator` tasks, and task `configRowIds`. This " + "applies to every push that creates configs, not only to clone. When push cannot set a " + "link, it adds a `LINK_UNRESOLVED` error or the API error. The next push then sends the " + "link again. The new result key `link_remaps` counts the links per kind. Clone now returns " + "`warnings[]`, also with `--dry-run`. It warns about a task that runs a config that is " + "not in the tree, and about values encrypted for the source project. It also warns about " + "each created data app, with the `data-app deploy` command. It also warns about each " + "cloned schedule, because clone does not activate schedules.", + "New (#785): `sync clone` now creates the storage buckets that the cloned configs refer to " + "and that the target project does not have. It is on by default. `--no-create-buckets` " + "disables it. `sync pull` now records the backend of each bucket in " + "`storage/buckets.json`, and the source of each linked bucket. Clone maps each bucket ID " + "through `--bucket-map`. It creates the missing buckets in production, on the recorded " + "backend, before it pushes the configs. Clone links a linked bucket to the same source " + "under the same ID. The sharing settings of the source project decide if the target can " + "link it. Clone creates buckets only, never tables or data. It skips the buckets that " + "exist, records each API failure in `bucket_errors` and continues. The result has the new " + "fields `buckets_created`, `buckets_skipped`, `bucket_errors` and `linked_buckets`. An " + "export from a pull before this release does not record linked buckets. Clone creates no " + "bucket from it and records one error, so run `sync pull` again before the clone.", + "Change (#783): `sync push` and `sync clone` now create a data app with no " + "recorded type as `python-js`, not as `streamlit`. This applies to a `keboola.data-apps` " + "config with no `_keboola.data_app_type`, for example a hand-written config or a tree " + "pulled before 0.94.0. Before, the platform created such an app as `streamlit`. Push now " + "writes the type into the local `_keboola` block and adds a `data_app_type_default` " + "warning. Configs that record a type do not change. The docs and the agent guidance now " + "present `python-js` as the default data app type. To keep a Streamlit app a " + "Streamlit app, pull the source tree again or set `_keboola.data_app_type: streamlit` " + "before the push.", + "Fix (#796): `sync pull` no longer deletes a directory that it wrote in the same run, or a " + "locally edited directory of a remote-deleted config. Before, a config deleted in the UI " + "and created again under the same name lost its directory on pull. The next `sync push` " + "then deleted the new config (#792 A). A locally edited directory whose remote config is " + "gone now stays (#792 C). Plain pull reports it as `skipped`. `pull --force` stops with " + '`SYNC_CONFLICT` and `reason: "deleted on remote"`. `pull --theirs` still deletes it. Pull ' + "still removes an unedited stale directory.", + "Fix (#795): `sync pull` now protects local edits in companion files such as " + "`transform.sql`, `code.py` and `_description.md`, not only in `_config.yml`. Before, pull " + "checked only `_config.yml`. When the remote also changed, pull overwrote an edit in a " + "companion file with no notice (#792 B). Now plain pull skips the config as `locally " + "modified`, and the edit stays pending for `sync push`. `pull --force` stops with " + "`SYNC_CONFLICT` when the remote also changed. `pull --theirs` still takes the remote " + "version. A deleted companion file now counts as a local edit, as in `sync diff` and `sync " + "push`. To drop local edits, use `--theirs` or delete the whole config directory.", + "Fix (#794): a repeated promote push (`sync push --branch ` from `main/`) no longer " + "creates one more dev copy of a config on each run. This affected production configs that " + "the dev branch did not have (#792 D). A repeated push with no local change now creates " + "nothing. `sync diff --branch ` is clean after the push. A later local edit updates " + "the one dev copy and does not change production. To delete the copies that an older " + "kbagent created, list the configs of the branch. Then delete the extra copies with " + "`kbagent config delete --branch `.", + "Fix (#810): after `sync clone --branch`, `sync push` now creates data apps in the push " + "branch, not in production (#808). Push compared the push branch with the first branch in " + "the manifest, and `sync clone --branch` writes the dev branch there. Push now reads the " + "real default branch from the API. It does this only when the push creates a data app. " + "When the lookup fails, push sends the numeric branch ID and adds a " + "`data_app_branch_lookup_failed` warning.", + "Fix (#789): on Windows, the background self-update retries once with `--link-mode copy` " + "when uv fails to hardlink a file (#786). A uv cache or tool directory on a cloud-synced " + "volume, such as OneDrive, cannot use hardlinks. The failed install removed the old " + "kbagent, and the printed recovery command failed in the same way. `pending_update.log` " + "now holds the output of both attempts. The recovery command also passes `--link-mode " + "copy`. A `UV_LINK_MODE` that the user set stays in effect, with no retry and no flag. " + "POSIX installs do not change. The update into this release still runs the old code, which " + "has no retry. On a cloud-synced Windows profile, set `UV_LINK_MODE=copy` before this " + "update. In PowerShell, run `[Environment]::SetEnvironmentVariable('UV_LINK_MODE', 'copy', " + "'User')` and open a new shell.", + "Fix (#809): `workspace from-transformation` now reads the transformation from the branch " + "where it creates the workspace (#807). Before, it read the config from production but " + "created the workspace in the active branch. A transformation that existed only in the " + "branch failed with a 404. A transformation changed in the branch loaded the input mapping " + "of production. The `serve` route gets the same fix. Production behavior does not change.", + "Fix (#803): `data-app list` now reads every page of `GET /apps`, so it finds data apps " + "beyond the first 100 deployments (#798). The endpoint returns 100 items by default, and " + "workspaces are in the same list. In a project with many workspaces, `data-app list` " + "reported no data apps or only some of them, and `sync pull` missed the runtime type of " + "the other apps. kbagent now reads pages of 500 items until a short page.", + "Fix (#788): `job detail --log-tail-lines N` and the `serve` job log stream now return the " + "events of nested jobs (#787). A job inside a flow, or a child row job of a row-based " + "component, has a dotted `runId`. The Storage events API returns no events for a dotted " + "`runId`, so these jobs returned `logTail: []`. kbagent now queries the events by the job " + "ID.", + "Note (#793, #797, #804, #806, #816): housekeeping with no user-facing change. #793 adds a " + "formal model of the sync engine (TLA+ and Lean 4, under `formal/sync/`) and one " + "regression test for each finding. The sync fixes #794, #795, #796 and #811 in this " + "release come from those findings. #797, #804 and #816 make tests fail when the behavior " + "that they guard breaks, and add tests that count the API calls of frequent read " + "commands. #806 updates `undici` in the web backend from 6.28.0 to 6.28.1, for three " + "security advisories.", + ], "0.95.0": [ "New (#775): `kbagent project create --url URL` creates a new Keboola project from a " "machine with no Keboola account and no token (DMD-1940). It stores the session and " diff --git a/src/keboola_agent_cli/commands/context.py b/src/keboola_agent_cli/commands/context.py index ab42b4436..a8d69df4b 100644 --- a/src/keboola_agent_cli/commands/context.py +++ b/src/keboola_agent_cli/commands/context.py @@ -163,7 +163,7 @@ registered unless --register-projects was passed, and where the suggested alias is slugified from the project NAME, never the numeric project id (e.g. 9840). Once registered, `--project 9840` resolves to - that alias too (since vNEXT; see Tips 3). + that alias too (since 0.96.0; see Tips 3). --all registers every accessible project. --project-id ID (repeatable) registers specific ones (an id the session cannot access raises a ConfigError naming it). Omitting both starts an interactive arrow-key + @@ -344,7 +344,7 @@ kbagent project use ALIAS Pin ALIAS as the default project. Persists to config.json. A registered - project ID pins that project's alias (since vNEXT; see Tips 3). + project ID pins that project's alias (since 0.96.0; see Tips 3). Env var KBAGENT_PROJECT=ALIAS overrides the pin for a single shell/session; an explicit --project flag overrides both. @@ -373,7 +373,7 @@ kbagent project invite --from-csv FILE [--default-role ROLE] [--workers N] [--dry-run] Bulk invite. CSV must have a header row with columns: email, project (alias or - numeric ID -- an alias wins, see Tips 3; since vNEXT) or project_id (ID only), + numeric ID -- an alias wins, see Tips 3; since 0.96.0) or project_id (ID only), role (optional if --default-role is given), reason (optional). Parallelised with ThreadPoolExecutor (default 8 workers). Per-row results in `rows[]` with status=ok|noop|failed; `failed_rows` ordering is not deterministic. @@ -1431,7 +1431,7 @@ kbagent data-app deploy --project NAME --app-id ID [--config-version N] [--wait] [--timeout SECONDS] [--branch ID] [--copy] [--reveal] - With --wait on a password app (vNEXT+): the password is delivered like + With --wait on a password app (0.96.0+): the password is delivered like `data-app password` (the c prompt in a terminal, which then waits for Enter or 120 s; --copy / --reveal need --wait). Without a flag and without the prompt (no terminal, --json) nothing is read: output as @@ -1458,7 +1458,7 @@ kbagent data-app password --project NAME --app-id ID [--copy] [--reveal] [--open] Give the user the password of a password-protected app WITHOUT printing - it (vNEXT+; older versions printed it and needed a Manage API token). + it (0.96.0+; older versions printed it and needed a Manage API token). Project token only (static or session), no Manage token. In a terminal (human mode, stdin + stdout a TTY, not a background job) it shows the app URL and `ui_url`, then waits: `c` copies the password, Enter / Esc / q @@ -1592,7 +1592,7 @@ kbagent sync init --project ALIAS [--directory DIR] [--git-branching] [--adopt-existing] [--with-workspaces] Initialize sync working directory. --git-branching enables git-to-Keboola branch mapping. - --with-workspaces (since vNEXT, CLI-25) sets "syncWorkspaces": true in the manifest + --with-workspaces (since 0.96.0, CLI-25) sets "syncWorkspaces": true in the manifest (with --adopt-existing: turns it on in an existing one). pull/diff/push/clone then also sync shared SQL workspaces: keboola.sandboxes configs with no parameters.id and runtime.shared true (Python/R and legacy SQL sandboxes carry parameters.id and stay @@ -1627,7 +1627,7 @@ Auto-detects renamed configs and renames local directories to match (uses git mv in git repos). --branch: per-invocation dev-branch override. Same semantics as sync push/diff. Ignored components (since 0.91.0, #689): keboola.sandboxes + keboola.mcp-server-tool are - always excluded (except shared SQL workspaces under syncWorkspaces, since vNEXT), + always excluded (except shared SQL workspaces under syncWorkspaces, since 0.96.0), unioned with the manifest's ignoredComponents list (.keboola/manifest.json) -- a per-tree exclusion knob honored by pull/diff/push. A component newly ignored has its manifest entry dropped and local dir removed on the next @@ -1667,7 +1667,7 @@ kbagent sync push --project ALIAS [--all-projects] [--dry-run] [--force] [--allow-plaintext-on-encrypt-failure] [--branch ID] [--no-name-drift-warnings] Push local changes. Auto-encrypts secrets. Skips conflicts (pull first). Fails if encryption fails (plaintext secrets never pushed). Use escape hatch flag only if you know what you are doing. - Workspace delete (since vNEXT, syncWorkspaces trees): a --force push that deletes a shared + Workspace delete (since 0.96.0, syncWorkspaces trees): a --force push that deletes a shared SQL workspace also deletes its SQL editor sessions (every user's, push branch) and their backend workspaces, which config restore does not bring back; check `sync push --dry-run --force` (warnings[] workspace_sessions) first. --force is destructive-class (a policy denying @@ -1706,11 +1706,11 @@ Clone a reference synced tree into a fresh target project + parameterize it (bucket_map / variable_values / instance_rename overrides), then push so every config CREATEs fresh. keboola.flow task configIds + variable links remap - reference->ULID; since vNEXT also shared-code links, legacy keboola.orchestrator + reference->ULID; since 0.96.0 also shared-code links, legacy keboola.orchestrator task configIds, task configRowIds and schedule targets (link_remaps counts each kind; an unset link is an errors[] entry, a failed PUT is sent by the next push). Idempotent (re-run -> no_changes); needs a fresh target. - Read warnings[] after a clone (also --dry-run, vNEXT+): missing_task_target (a + Read warnings[] after a clone (also --dry-run, 0.96.0+): missing_task_target (a flow/orchestrator task runs a config not in the tree), encrypted_values_copied (KBC:: paths the target cannot decrypt; secret_keys: plaintext in _config.yml + sync push, unencryptable_keys: encrypt values, oauth_keys: authorize again), @@ -2255,7 +2255,7 @@ 3. Multi-project: most read commands accept repeatable --project flag. Omit --project to query ALL connected projects in parallel. - --project takes an alias or a registered project's numeric ID (since vNEXT). + --project takes an alias or a registered project's numeric ID (since 0.96.0). An alias wins over an ID. An ID registered under several aliases fails with CONFIG_ERROR (exit 5) and lists them -- unless all are on one stack and exactly one is a session (browser-login) alias, which then wins. The same diff --git a/uv.lock b/uv.lock index a6dbbc6e1..29163cf21 100644 --- a/uv.lock +++ b/uv.lock @@ -465,7 +465,7 @@ wheels = [ [[package]] name = "keboola-cli" -version = "0.95.0" +version = "0.96.0" source = { editable = "." } dependencies = [ { name = "croniter" }, From 3d6872a5479dd962998bedbc73d78cc74243a8e3 Mon Sep 17 00:00:00 2001 From: soustruh Date: Wed, 30 Sep 2026 16:14:08 +0200 Subject: [PATCH 2/2] chore(release): add the changelog view change to 0.96.0 --- src/keboola_agent_cli/changelog.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/keboola_agent_cli/changelog.py b/src/keboola_agent_cli/changelog.py index e4522a674..5183819dc 100644 --- a/src/keboola_agent_cli/changelog.py +++ b/src/keboola_agent_cli/changelog.py @@ -207,6 +207,10 @@ "component, has a dotted `runId`. The Storage events API returns no events for a dotted " "`runId`, so these jobs returned `logTail: []`. kbagent now queries the events by the job " "ID.", + "Change (#818): `kbagent changelog` now shows the first sentence of every BREAKING note of " + "a version by default, not only of the first note. When a version has fewer than two " + "BREAKING notes, it adds the first other notes until two notes show. `--full`, `--json` " + "and the `What's new` notice after an update do not change.", "Note (#793, #797, #804, #806, #816): housekeeping with no user-facing change. #793 adds a " "formal model of the sync engine (TLA+ and Lean 4, under `formal/sync/`) and one " "regression test for each finding. The sync fixes #794, #795, #796 and #811 in this "