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.26.0",
"version": "0.27.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
9 changes: 9 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,15 @@ kbagent workspace query --project ALIAS --workspace-id ID --file query.sql
kbagent workspace gc [--project NAME ...] [--dry-run] [--yes]
kbagent workspace from-transformation --project ALIAS --component-id ID --config-id ID [--row-id ID]

kbagent data-app list [--project NAME ...] [--branch ID]
kbagent data-app detail --project NAME --app-id ID [--branch ID]
kbagent data-app create --project ALIAS --name NAME --slug SLUG --git-repo URL [--description STR | --description-file PATH] [--git-branch main] [--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]
kbagent data-app deploy --project NAME --app-id ID [--config-version N] [--wait] [--timeout SECONDS] [--branch ID]
kbagent data-app start --project NAME --app-id ID [--wait] [--timeout SECONDS]
kbagent data-app stop --project NAME --app-id ID [--wait] [--timeout SECONDS]
kbagent data-app delete --project NAME --app-id ID [--yes]
kbagent data-app password --project NAME --app-id ID

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]
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ kbagent workspace query --project prod --workspace-id WS_ID \
| **MCP tools** | Call `keboola-mcp-server` tools with auto-expand, multi-project fan-out, branch propagation, schema validation. |
| **Workspaces** | Create Snowflake/BQ workspace, load tables, run SQL. Create from transformation config for instant debugging. Orphan detection + garbage collection. |
| **Sharing** | Cross-project bucket sharing with org/project/user access control. Share, link, unlink. |
| **Data apps** | First-class lifecycle for Streamlit / Flask / Node deployments (`keboola.data-apps`). Create, deploy, start, stop, password, delete. Hides the redeploy contract and per-project KMS encryption of git PATs. |
| **Lineage** | Column-level dependency analysis across projects. SQL/Python parsing, AI-enhanced detection, interactive web browser, Mermaid/HTML/ER export. |
| **Kai (AI Assistant)** | Ask Keboola's built-in AI questions about your project. One-shot or chat sessions with full MCP context. |
| **Encryption** | Encrypt secrets (`#password`, `#api_token`) via Keboola Encryption API. Works with sync push and MCP. |
Expand Down Expand Up @@ -135,6 +136,7 @@ kbagent storage buckets | bucket-detail | create-bucket | delete-bucket
files | file-detail | file-upload | file-download | file-tag | file-delete
load-file | unload-table
kbagent sharing list | share | unshare | link | unlink | edges
kbagent data-app list | detail | create | deploy | start | stop | delete | password
kbagent lineage build | show | info | server
kbagent branch list | create | use | reset | delete | merge
metadata-list | metadata-get | metadata-set | metadata-delete
Expand Down
188 changes: 188 additions & 0 deletions docs/TUTORIAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -754,6 +754,194 @@ For the full reference including the BOOLEAN/INTEGER gotchas and the

---

## 9. Data apps lifecycle

A Keboola data app is **not one resource** -- it's a deployment record on
the Data Science API plus a configuration on the Storage API
(`keboola.data-apps`), and they have to stay in sync. Each API only owns
part of the picture, and the obvious-looking calls have four documented
footguns the platform does not surface as errors:

1. **The redeploy contract.** `PATCH /apps/{id}` with bare
`desiredState=running` silently pins to the empty shell that
`POST /apps` minted, so the runner errors `dataApp.git.repository is
required in /data/config.json` -- only visible in the UI Terminal Log,
never in any HTTP response.
2. **Per-project KMS encryption.** Encrypted git PATs (`KBC::ProjectSecure*`)
are bound to the project that minted them; ciphertext from project A
does not decrypt in project B.
3. **Cleanup-in-finally.** A failed initial deploy leaks the empty shell
from `POST /apps` if the caller does not delete it manually.
4. **Transient `state == stopped`.** During the very first deploy the
platform transitions `created -> stopped -> starting -> running`, so a
naive poll that exits on `stopped` reports a phantom failure.

`kbagent data-app` (since 0.27.0) encodes all four in the service layer,
so the `--json` output you see at the CLI is what would have happened if
you had done everything right at the raw HTTP level. The eight
subcommands -- `list`, `detail`, `create`, `deploy`, `start`, `stop`,
`delete`, `password` -- cover the full lifecycle end to end.

### 9.1 Public-repo golden path

The simplest path: a public git repo, no auth gate, three commands from
zero to a running container.

```bash
# Register the project once (Storage API token from the UI).
kbagent project add --project prod \
--url https://connection.keboola.com \
--token YOUR_STORAGE_TOKEN

# Create the deployment shell + Storage config in one shot.
kbagent --json data-app create \
--project prod \
--name "Hello data app" \
--slug hello \
--git-repo https://github.com/<owner>/<small-public-repo> \
--git-public --auth public --no-deploy \
| jq -r '.data | "id=\(.id) config_id=\(.config_id) config_version=\(.config_version)"'
# id=12345678 config_id=01abcdefghijklmnopqrstuvwxyz config_version=3
```

`type: "python-js"` (the default) covers BOTH a Node app
(`package.json` + entry point) and a Python app (`requirements.txt` +
`app.py`). The runtime auto-detects from what's in the repo.

The `configVersion=3` is the Storage config version after kbagent's PUT.
Storage went `1 -> 2` (the empty shell that `POST /apps` minted, with an
auto-injected `parameters.id` back-pointer to the deployment record) and
`2 -> 3` (the full body with the git block + runtime block + auth block).

```bash
# Deploy: pins the deployment record to configVersion=3 and waits.
kbagent --json data-app deploy \
--project prod --app-id 12345678 --wait --timeout 300 \
| jq -r '.data | "state=\(.state) url=\(.url)"'
# state=running url=https://hello-12345678.hub.keboola.com
```

Visit the URL. That's the entire round-trip: ~30s for a small Node
app, longer for the first cold-boot of a heavier Python app.

### 9.2 Private-repo golden path

For a private repo you need a GitHub PAT with `repo:read` scope.
Two non-negotiables:

- **Pass it via env, not argv.** `--git-pat-env GITHUB_PAT_DATAAPP`
reads the PAT from the named environment variable. The plaintext
never appears in your shell history, in `ps aux`, or in any kbagent
output.
- **The PAT is encrypted under THIS project's KMS** before reaching
Storage. `kbagent data-app create` calls the Encryption API
(`encryption.<stack>/encrypt`) and only writes the resulting
`KBC::ProjectSecure*` ciphertext. Ciphertext from one project's
config does NOT decrypt in another -- copying an encrypted git block
across projects via raw `kbagent config update` produces a runtime
failure, not a clear error.

```bash
export GITHUB_PAT_DATAAPP=ghp_xxxxxxxxxxxxxxxxxxxx

kbagent --json data-app create \
--project prod \
--name "Internal dashboard" \
--slug internal-dashboard \
--git-repo https://github.com/<owner>/<private-repo> \
--git-username YOUR_GITHUB_USER \
--git-pat-env GITHUB_PAT_DATAAPP \
--auth password --wait --timeout 300
```

`--auth password` (the default) wraps the app in a simpleAuth gate. The
20-character hex password is auto-generated by the platform on first
deploy. To retrieve it:

```bash
# Requires KBC_MANAGE_API_TOKEN in env (org-scoped Manage API token).
kbagent --json data-app password \
--project prod --app-id 12345678 \
| jq -r '.data.password'
# <20-character hex password, e.g. a1b2c3d4e5f6a7b8c9d0>
```

The password cannot be rotated; to change it, delete and recreate the
app. (See [§3](#3-add-a-whole-organization) for `KBC_MANAGE_API_TOKEN`
setup -- it is the same Manage token `org setup` uses.)

### 9.3 Roll out a new version: `data-app deploy` after `config update`

This is the easiest gotcha to fall into. Editing the data-app's Storage
config bumps the **Storage** version, but the deployment record's
`configVersion` is a **pinned pointer** that does NOT auto-advance:

```bash
# Bump auto-suspend from 15 minutes to 60.
kbagent --json config update \
--project prod --component-id keboola.data-apps --config-id 01abcdefghijklmnopqrstuvwxyz \
--set 'parameters.autoSuspendAfterSeconds=3600' --merge \
| jq -r '.data.version'
# 4

# At this point the running container is still at configVersion=3.
# Verify with detail:
kbagent --json data-app detail --project prod --app-id 12345678 \
| jq -r '.data | "storage=\(.config_version_storage) deployed=\(.config_version_deployed)"'
# storage=4 deployed=3 <-- not in sync

# Roll out: re-pin the deployment to the latest Storage version.
kbagent --json data-app deploy --project prod --app-id 12345678 --wait
```

`data-app deploy` reads the latest Storage version, then PATCHes the
deployment record with the trio
`{desiredState=running, configVersion=<latest>, restartIfRunning=true}`
together. Sending only one or two of those returns HTTP 422 from the
platform; the CLI always sends all three. This is the **redeploy
contract** -- the headline of the feature.

`data-app start` is a different command for a different job. It wakes a
container the platform parked due to `autoSuspendAfterSeconds` of
inactivity. It does NOT bump the deployed `configVersion` -- it just
reuses whatever was pinned. Use `start` for waking a parked container;
use `deploy` for rolling out a new code or config version.

### 9.4 `stop` is reversible, `delete` is not

```bash
# Reversible: tears down the container, preserves the URL + Storage config.
kbagent --json data-app stop --project prod --app-id 12345678 --wait
# state=stopped, desiredState=stopped

# Wake it back up at the same configVersion (no version bump).
kbagent --json data-app start --project prod --app-id 12345678 --wait
# state=running

# Or: just hit the URL. The platform typically auto-wakes parked
# containers on incoming HTTP traffic (~30-60s cold-boot). Use
# `data-app start` when you want an explicit, observable wake.
```

```bash
# Irreversible: the Data Science API cascade-deletes the deployment
# record AND the linked Storage config server-side. The URL is
# permanently retired.
kbagent --json data-app delete --project prod --app-id 12345678 --yes
```

A second `data-app list` after the delete returns the project's other
apps; the deleted app does not come back, and a fresh `create` against
the same slug mints a new numeric `id` (the URL hostname embeds the
numeric id, so even with the same slug the new URL differs).

For the full reference including the API endpoints behind each command,
the four-footgun mental model in detail, encrypted-PAT round-trip
shapes, and the `--hint client|service` code-generation contract, see
[plugins/kbagent/skills/kbagent/references/data-app-workflow.md](../plugins/kbagent/skills/kbagent/references/data-app-workflow.md).

---

## Troubleshooting

| Symptom | Fix |
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.26.0",
"version": "0.27.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
31 changes: 29 additions & 2 deletions plugins/kbagent/agents/keboola-expert.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,9 @@ a critical failure.
`kbagent --json context` and inspect the version. If missing commands
needed for the current task (e.g. `flow update` needs 0.22.0+,
`schedule find` needs 0.23.0+, `config set-default-bucket` needs
0.26.0+, `storage retype` is a future composite), you MUST refuse the
task and return a handoff message to the parent: `"Cannot proceed
0.26.0+, `data-app create / deploy / start / stop / delete / password`
need 0.27.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>. Ask user to run
kbagent update, then re-invoke me."` Do not attempt the task with
workarounds that use MCP strip-bug-prone tools.
Expand Down Expand Up @@ -96,6 +97,13 @@ a critical failure.
| Debug a failed job | `kbagent job detail --project P --job-id J --json` + `kbagent job run ... --log-tail-lines 200` | `kbagent workspace from-transformation` for SQL repro | "I think the issue is..." without reading logs |
| 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 |
| Inventory data apps | `kbagent data-app list --project P` (0.27.0+) | `tool call get_configs --component_id keboola.data-apps` (Storage view only -- no state, no URL, no configVersion) | iterating `tool call` per project to reconstruct the join with the Data Science index |
| Bring a new data app online from a git repo | `kbagent data-app create --project P --name N --slug S --git-repo URL [--git-pat-env VAR \| --git-public]` (0.27.0+) | broken into `tool call create_config keboola.data-apps` + manual `kbagent encrypt values` + raw `POST /apps` -- ONLY if you need a custom shape kbagent doesn't support | raw `POST data-science/apps` followed by `PATCH desiredState=running` without `configVersion + restartIfRunning` (the §9 footgun -- pins to v2 empty shell, runner errors `dataApp.git.repository is required in /data/config.json`) |
| Roll out a new code or config version on a data app | `kbagent data-app deploy --project P --app-id N --wait` (0.27.0+) -- always sends the §9 trio | `kbagent --hint client data-app deploy ...` to inspect the generated `patch_app(desired_state=, config_version=, restart_if_running=True)` call | `tool call update_config` then `tool call run_component` (data apps are not jobs -- the queue runner does not deploy them) |
| Wake an auto-suspended data app | `kbagent data-app start --project P --app-id N` (0.27.0+) -- does NOT bump configVersion | hitting the app's URL (auto-restart triggers a 30-60s cold boot) | `kbagent data-app deploy` (overkill -- bumps the deployed configVersion unnecessarily) |
| Pause a running data app | `kbagent data-app stop --project P --app-id N` (0.27.0+) | -- | `kbagent data-app delete` (irreversible; cascades to Storage config) |
| Read the simpleAuth password for a password-gated app | `kbagent data-app password --project P --app-id N` (0.27.0+) -- requires `KBC_MANAGE_API_TOKEN` | -- | trying to "rotate" the password (not supported by the API; delete + recreate to mint a new one) |
| Tear down a data app | `kbagent data-app delete --project P --app-id N` (0.27.0+) -- cascades to Storage config; URL retired | -- | manually `tool call delete_config keboola.data-apps` while leaving the deployment record orphaned |

If the table does not cover the user's task, **ask clarifying
questions** instead of guessing. Returning a targeted question is a
Expand Down Expand Up @@ -209,6 +217,25 @@ success, not a failure.
feature flag, not by this setting -- see the `legacy_branch_storage`
gotcha above for what the runner actually does on `--branch` writes.

- **Data apps need `data-app deploy` after `config update`** (0.27.0+):
the deployment record's `configVersion` is a pinned pointer that does
NOT auto-advance when Storage advances. Editing the `keboola.data-apps`
config via `kbagent config update` bumps the Storage version, but the
running container keeps using the OLD version until a `kbagent data-app
deploy --project P --app-id N` PATCHes the deployment with the
§9 trio `{desiredState=running, configVersion, restartIfRunning=true}`.
Sending bare `desiredState=running` (or just `configVersion`) silently
pins to v2 (the empty shell from `POST /apps`) and the runner errors
`dataApp.git.repository is required in /data/config.json` with no
top-level error surfaced -- only visible in the UI's Terminal Logs.
`kbagent data-app start` is the cheap restart for an auto-suspended
app; it does NOT bump the configVersion. Use `data-app deploy` for new
code/config rollouts, `data-app start` for waking a parked container.
PAT encryption is per-project KMS -- ciphertext does NOT cross
projects, so `kbagent data-app create` always re-encrypts plaintext via
the target project's Encryption API and refuses to write plaintext if
the round-trip does not return a `KBC::Project*` ciphertext.

- **`storage bucket-detail` is dialect-aware** (0.25.3+): the response
shape depends on the bucket's backend. Snowflake buckets carry
`snowflake_database` / `snowflake_schema` and per-table
Expand Down
Loading
Loading