Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"plugins": [
{
"name": "kbagent",
"version": "0.32.0",
"version": "0.33.0",
"source": "./plugins/kbagent",
"description": "AI-friendly interface to Keboola Connection projects — explore configs, jobs, lineage, call MCP tools, manage dev branches, and debug SQL in workspaces",
"category": "development"
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ kbagent data-app validate-repo --git-repo URL [--git-branch BRANCH] [--git-publi

kbagent component list [--project NAME] [--type TYPE] [--query QUERY]
kbagent component detail --component-id ID [--project NAME]
kbagent config new --component-id ID [--name NAME] [--project NAME] [--output-dir DIR]
kbagent config new --component-id ID [--name NAME] [--project NAME] [--output-dir DIR] [--push --no-files --description D --configuration JSON|@file|- --configuration-file PATH --no-validate --branch ID --dry-run]

kbagent encrypt values --project ALIAS --component-id ID --input JSON|@file|- [--output-file PATH]

Expand Down
2 changes: 1 addition & 1 deletion plugins/kbagent/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "kbagent",
"version": "0.32.0",
"version": "0.33.0",
"description": "AI-friendly interface to Keboola Connection projects — explore configs, jobs, lineage, call MCP tools, manage dev branches, and debug SQL in workspaces",
"author": {
"name": "Keboola",
Expand Down
20 changes: 15 additions & 5 deletions plugins/kbagent/agents/keboola-expert.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ a critical failure.
`project edit --new-alias` (cascading rename across config.json +
nested sync dir; warns on lineage cache rebuild) needs 0.31.0+,
`storage truncate-table` needs 0.32.0+,
`data-app *` JSON output uses key `app_id` (was bare `id`) on 0.33.0+
-- pipe with `jq -r '.apps[].app_id'`, not `'.id'`,
`config new --push` (one-shot remote create) needs 0.33.0+,
`storage retype` is a future composite), you
MUST refuse the task and return a handoff message to the parent:
`"Cannot proceed safely on kbagent <version>. Missing: <commands>.
Expand All @@ -101,7 +104,7 @@ a critical failure.
|---|---|---|---|
| Update flow (rename, description, phases) | `kbagent flow update` (partial, no `--file`) | `--file` after fetching current phases, merging locally, passing full YAML | `tool call update_flow` (strips `behavior.onError` pre-MCP v1.60); partial `--file` that drops fields |
| Schedule flow | `kbagent flow schedule --cron ... [--timezone]` | `tool call create_flow_schedule` | raw REST to `/storage/configurations/keboola.scheduler` |
| Create Snowflake transformation | `kbagent config new --component-id keboola.snowflake-transformation` + `config update --set ...` | `tool call create_sql_transformation` (lower schema, avoids the component refusal) | `tool call create_config` (refuses keboola.snowflake-transformation) |
| Create Snowflake transformation | `kbagent config new --component-id keboola.snowflake-transformation --name N --project P --push --no-files` (0.33.0+; one-shot, no scaffold, body defaults to `{}` and validation auto-skips for empty shell -- then `config update --set ...` to fill in script) **or** `kbagent config new --component-id keboola.snowflake-transformation --project P --output-dir D` + `config update --set ...` (scaffold-then-patch) | `tool call create_sql_transformation` (lower schema, avoids the MCP `create_config` Snowflake refusal) | `tool call create_config` (refuses keboola.snowflake-transformation) -- note: `config new --push` does NOT inherit this refusal because it wraps the raw Storage API directly |
| Update SQL transformation body (script[]) | `kbagent config update --project P --component-id keboola.snowflake-transformation --config-id K --configuration @body.json` (0.28.0+ auto-normalizes string `script` to array; SQL gets statement-level split, Python/R gets `[script]` wrap; envelope's `normalizations: [...]` records every change. 0.31.0+ also re-splits multi-statement LIST elements -- closes the #274 ODBC `statement count 2 vs desired 1` crash that survives the 0.28.0 string fix) | `kbagent --hint client config update ...` if you need to bypass the auto-normalize for some reason | `tool call update_sql_transformation` -- still vulnerable to BOTH the #245 string-vs-array AND #274 list-element runtime crashes because it pushes raw to Storage API; raw `PUT /v2/storage/components/.../configs/...` -- same trap |
| Run a job (and wait) | `kbagent job run --project P --component-id C --config-id K --wait` | `tool call run_component` | `job run` without `--wait` when user expects the result |
| Search items by name across projects | `kbagent search QUERY [--project P] [--type table\|bucket\|config\|flow] [--limit N]` (0.30.0+) | `tool call search_tables` / `tool call search_configurations` (one resource-type per call) | chaining multiple `tool call` for different types |
Expand All @@ -118,6 +121,7 @@ a critical failure.
| Ad-hoc SQL / row-count / type audit | `kbagent workspace create` + `kbagent workspace load` + `kbagent workspace query --sql "..."` | `kbagent workspace from-transformation` for existing transform debugging | querying Keboola Storage directly via Snowflake credentials outside the workspace abstraction |
| Inspect dev branch | `kbagent branch list --project P`, `kbagent branch use --project P --branch ID` | `tool call get_branch` | acting on `main` when a dev branch exists |
| Audit project capabilities / features | `kbagent project info --project P` (0.30.0+) -- returns project ID, name, backend, enabled features, quota limits, and metrics | `tool call verify_token` (returns less structured info; no feature list) | inspecting the UI project settings manually |
| Create a new config (one-shot remote, no scaffold to disk) | `kbagent config new --project P --component-id C --name N --push --no-files [--configuration @body.json] [--branch ID]` (0.33.0+) -- single CLI call POSTs to `/v2/storage/components/{cid}/configs`; default body is `{}` (FIIA empty-shell pattern, validation auto-skips); explicit `--configuration` body is schema-validated by default (`--no-validate` opts out); works for ALL component types incl. `keboola.snowflake-transformation` | `kbagent config new --output-dir D` then edit + `kbagent sync push` (scaffold-then-push GitOps flow) | `tool call create_config` (refuses keboola.snowflake-transformation; raw MCP envelope, no validation) |
| Create a config row | `kbagent config row-create --project P --component-id C --config-id K --name NAME` (0.30.0+) | `tool call create_config_row` | `POST /v2/storage/components/C/configs/K/rows` (raw REST) |
| Update a config row | `kbagent config row-update --project P --component-id C --config-id K --row-id R [--name N] [--configuration JSON]` (0.30.0+) | `tool call update_config_row` | `PUT /v2/storage/components/C/configs/K/rows/R` (raw REST) |
| Delete a config row | `kbagent config row-delete --project P --component-id C --config-id K --row-id R [--yes]` (0.30.0+) -- destructive (gated behind `--allow-destructive`); branch-aware | `tool call delete_config_row` | `DELETE /v2/storage/components/C/configs/K/rows/R` (raw REST) |
Expand Down Expand Up @@ -160,10 +164,16 @@ success, not a failure.
`update_flow` strip bug, reached via a different door.

- **Snowflake transformation scaffolding**: `tool call create_config`
REFUSES `keboola.snowflake-transformation`. Use
`kbagent config new --component-id keboola.snowflake-transformation`
for the local scaffold, then `kbagent config update` for the body.
Or MCP `create_sql_transformation` which uses a lower-level schema.
REFUSES `keboola.snowflake-transformation`. Three options that work:
(a) `kbagent config new --component-id keboola.snowflake-transformation
--name N --project P --push --no-files` (0.33.0+) -- one-shot remote
create wrapping the raw Storage API, then `kbagent config update --set
parameters.blocks...=...` to fill in the body. **Recommended path.**
(b) `kbagent config new --component-id keboola.snowflake-transformation
--output-dir D` for the local scaffold, then `kbagent config update` for
the body. (c) MCP `tool call create_sql_transformation` which uses a
lower-level schema. `config new --push` does NOT inherit the MCP
refusal because it calls Storage API directly.

- **`script[]` string-vs-array runtime crash** (0.28.0+ auto-fix; #245):
the Storage API silently accepts `parameters.blocks[].codes[].script`
Expand Down
2 changes: 1 addition & 1 deletion plugins/kbagent/skills/kbagent/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ When working inside a git repository or project directory, run `kbagent init` (o
| Set or clear ``storage.output.default_bucket`` on a configuration | `kbagent config set-default-bucket --project PROJECT --component-id COMPONENT-ID --config-id CONFIG-ID` |
| Rename a configuration (update name via API + rename local sync directory) | `kbagent config rename --project PROJECT --component-id COMPONENT-ID --config-id CONFIG-ID --name NAME` |
| Delete a configuration from a project | `kbagent config delete --project PROJECT --component-id COMPONENT-ID --config-id CONFIG-ID` |
| Generate boilerplate configuration files for a Keboola component | `kbagent config new --component-id COMPONENT-ID` |
| Generate boilerplate configuration files for a Keboola component, optionally creating the config remotely in one shot | `kbagent config new --component-id COMPONENT-ID` |
| List all metadata entries on a configuration | `kbagent config metadata-list --project PROJECT --component-id COMPONENT-ID --config-id CONFIG-ID` |
| Read a single metadata value by key | `kbagent config get-metadata --project PROJECT --component-id COMPONENT-ID --config-id CONFIG-ID --key KEY` |
| Set a metadata key/value on a configuration (upsert) | `kbagent config set-metadata --project PROJECT --component-id COMPONENT-ID --config-id CONFIG-ID --key KEY --value VALUE` |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ All seven commands authenticate via `KBC_MANAGE_API_TOKEN` (Manage API), not the
- `config set-default-bucket --project NAME --component-id ID --config-id ID (--bucket BUCKET_ID | --clear) [--dry-run] [--branch ID]` -- set or clear `configuration.storage.output.default_bucket` on a configuration. Discoverable shortcut for the raw-mode workaround at https://keboola.atlassian.net/wiki/spaces/SUP/pages/3770155030/. Read-modify-write that preserves sibling keys; returns `{"changed": false}` when the value already matches the requested state. Honored by output tables that don't pin their own `destination`.
- `config rename --project NAME --component-id ID --config-id ID --name "New Name" [--branch ID] [--directory DIR]` -- rename a configuration (API update + local sync directory rename with git mv support)
- `config delete --project NAME --component-id ID --config-id ID [--branch ID]` -- delete a configuration
- `config new --component-id ID [--project NAME] [--name NAME] [--output-dir DIR]` -- scaffold new config from component schema
- `config new --component-id ID [--project NAME] [--name NAME] [--output-dir DIR] [--push --no-files --description D --configuration JSON|@file|- --configuration-file PATH --no-validate --branch ID --dry-run]` -- **two modes**. **Default (no `--push`)**: scaffold new config from component schema; writes files to `--output-dir` or prints to stdout. **Zero API calls.** **With `--push`** (0.33.0+, requires `--project` + non-empty `--name`): also POSTs to `/v2/storage/components/{cid}/configs` for a one-shot remote create. `--no-files` skips the filesystem step entirely (FIIA-style empty-shell pattern). `--configuration` / `--configuration-file` override the POSTed body (default is `{}`, with validation auto-skipped for the default empty shell). `--dry-run` previews the planned POST + validation result without creating. Schema validation runs by default when an explicit body is given (fail-closed: `ConfigError` exit 5 on mismatch) but skips silently if the AI Service has no schema for the component or returns an error; `--no-validate` opts out. Works for ALL component types including `keboola.snowflake-transformation` (unlike `tool call create_config`, which refuses that component).
- `config variables-set --project NAME --component-id ID --config-id ID --var KEY=VALUE [--var ...] [--replace] [--variables-id ID] [--values-id ID] [--branch ID] [--dry-run] [--allow-plaintext-on-encrypt-failure] [--yes]` -- attach variable values to a config. Auto-creates a sibling `keboola.variables` config + default row on first use and links it via the parent's `runtime.variables_id` / `variables_values_id`. Defaults to merge; `--replace` drops keys not in `--var`. `#`-prefixed values encrypt via the Encryption API (fail-closed; exit non-zero on `ENCRYPTION_FAILED`). See `variables-workflow.md`
- `config variables-get --project NAME --component-id ID --config-id ID [--branch ID]` -- resolve `variables_id` + `values_id` from the parent config and fetch the current KEY=VALUE map. Returns `{linked: bool, variables_id, values_id, values}`; `linked=false` means the parent has no variables attached
- `config variables-clear --project NAME --component-id ID --config-id ID [--branch ID] [--yes]` -- unlink variables from the parent config (strips `variables_id` + `variables_values_id`). **Does NOT delete** the backing `keboola.variables` config -- use `config delete` explicitly if you've verified nothing else references it
Expand Down Expand Up @@ -139,7 +139,7 @@ All seven commands authenticate via `KBC_MANAGE_API_TOKEN` (Manage API), not the
- `workspace from-transformation --project ALIAS --component-id ID --config-id ID [--row-id ID]` -- workspace from existing transform

## Data Apps (Streamlit / Flask / Node deployments)
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.
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 apps across projects (Data Science index merged with Storage names)
- `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 [--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|...] [--branch ID] [--no-deploy] [--wait] [--timeout SECONDS] [--keep-on-failure] [--dry-run]` -- 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` mints a 20-char hex simpleAuth password (retrievable via `data-app password`).
Expand Down
70 changes: 70 additions & 0 deletions plugins/kbagent/skills/kbagent/references/gotchas.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,75 @@
# Gotchas -- Response Parsing and Common Pitfalls

## `kbagent config new --push` is one-shot remote create; default is scaffold-only (since v0.33.0)

- **Pre-v0.33.0**, `kbagent config new` was scaffold-only -- it wrote
boilerplate files to `--output-dir` (or stdout) and made **zero API calls**.
The intended flow was scaffold → edit → `kbagent sync push`. The agent docs
in `keboola-expert.md` and SKILL.md conflated this with "create config"
intent, which was wrong if the goal was an API mutation.
- **Since v0.33.0**, `--push` adds a one-shot remote create:
`kbagent config new --component-id C --name N --project P --push` calls
`POST /v2/storage/components/C/configs` after the scaffold step. Returns
the new config ID immediately. `--no-files` skips the filesystem step
entirely (no scaffold to disk or stdout, only the API POST) -- this is
the FIIA-style "empty shell, then patch via `config update --set ...`"
pattern.
- `--push` **requires** `--project` AND a non-empty `--name`. All other
push-gated flags (`--no-files`, `--description`, `--configuration` /
`--configuration-file`, `--no-validate`, `--branch`, `--dry-run`) are
no-ops without `--push` and exit 2 if set independently.
- `--configuration` and `--configuration-file` are mutually exclusive;
`--no-files` and `--output-dir` are mutually exclusive.
- **MCP `create_config` quirk does NOT apply**: the raw MCP tool refuses
`keboola.snowflake-transformation` and routes you to
`tool call create_sql_transformation`. `kbagent config new --push` does
NOT refuse; the typed CLI wraps the raw Storage API directly. For
Snowflake transformations: one `config new --push` call works; the
MCP-typed `create_sql_transformation` shape is only needed if you
specifically want that envelope.
- **Schema validation** runs by default whenever `--configuration` /
`--configuration-file` provide an explicit body. On mismatch the create
aborts with exit 5 and a list of error paths. If the AI Service has no
schema for the component or returns an error, validation skips silently
(the result envelope shows `validation_status: "skipped"`). Use
`--no-validate` to skip the AI Service call entirely.
- **Empty-shell exception**: when no body is provided (default `{}`),
validation auto-skips. Component schemas almost always require parameters
and would reject `{}` -- skipping is the FIIA-pattern-friendly default.
Passing `--configuration '{}'` explicitly does NOT take the skip path:
the body is treated as caller-provided and validated, which typically
fails. Use `--no-validate` to suppress validation entirely.
- **`--push --dry-run`** returns the planned POST body + validation result
without making the API call (`dry_run: true` in the envelope, exit 0 even
on validation failure -- dry-run is inspection-only).
- The result envelope on success includes the full Storage API response
plus `project_alias`, `branch_id`, `validation_status`, and
`validation_errors` (always present, even if empty). Shape-symmetric with
`config detail` single-config mode and `config row-create`.
## `data-app` JSON output: key for the app's own id is `app_id` (since v0.33.0)

- Every `kbagent --json data-app <subcommand>` envelope emits the
data-app's own identifier under the key `app_id`. Prior to v0.33.0 the
same key was named bare `id`, which did not match the `--app-id` input
flag. Affects `data-app list / detail / create / deploy / start / stop /
delete / password / secrets-set / secrets-list / secrets-get /
secrets-remove`. The companion `config_id` key is unchanged.
- Pipe-friendly chain that v0.33.0 enables:
`kbagent --json data-app list | jq -r '.apps[].app_id' | xargs -I{} kbagent data-app deploy --project P --app-id {}`.
On pre-v0.33.0 you had to read `.apps[].id` (mismatched the input flag,
surprised AI agents that templated `.app_id`).
- **What is NOT renamed:** the Storage config back-pointer at
`parameters.id` inside the configuration body sent TO Storage (writeup
§5) -- that lives in the Storage config, not in kbagent's output
envelope. The auth-provider id (`auth_providers[].id == "simpleAuth"`)
is also unchanged.
- The Data Science API on every Keboola stack we've probed (europe-west3.gcp,
us-east4.gcp; 2026-05-12) serves camelCase keys on the wire (`id`,
`configId`, `desiredState`, `configVersion`, ...). kbagent reads those
camelCase keys directly and emits its own snake_case-ish output keys.
If a future API shape change introduces snake_case wire keys, this
helper will need a defensive alias pass -- not yet warranted.

## `project edit --new-alias` does NOT rewrite lineage caches (since v0.31.0)

- `kbagent project edit --project OLD --new-alias NEW` cascades the rename
Expand Down
Loading
Loading