Skip to content
Closed
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
8 changes: 7 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,11 @@ kbagent config rename --project NAME --component-id ID --config-id ID --name "Ne
kbagent 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]
kbagent config variables-get --project NAME --component-id ID --config-id ID [--branch ID]
kbagent config variables-clear --project NAME --component-id ID --config-id ID [--branch ID] [--yes]
kbagent config metadata-list --project NAME --component-id ID --config-id ID [--branch ID]
kbagent config get-metadata --project NAME --component-id ID --config-id ID --key KEY [--branch ID]
kbagent config set-metadata --project NAME --component-id ID --config-id ID --key KEY --value VALUE [--branch ID]
kbagent config delete-metadata --project NAME --component-id ID --config-id ID --metadata-id ID [--branch ID] [--yes]
kbagent config set-folder --project NAME --component-id ID --config-id ID --name FOLDER [--branch ID]

kbagent job list [--project NAME] [--component-id ID] [--status STATUS] [--limit N]
kbagent job detail --project NAME --job-id ID
Expand Down Expand Up @@ -309,13 +314,14 @@ kbagent branch metadata-set --project NAME --key KEY [--text STR | --file PATH |
kbagent branch metadata-delete --project NAME --metadata-id ID [--branch ID|default]

kbagent workspace create --project ALIAS [--name NAME] [--backend TYPE] [--ui] [--read-only/--no-read-only]
kbagent workspace list [--project NAME]
kbagent workspace list [--project NAME ...] [--orphaned]
kbagent workspace detail --project ALIAS --workspace-id ID
kbagent workspace delete --project ALIAS --workspace-id ID
kbagent workspace password --project ALIAS --workspace-id ID
kbagent workspace load --project ALIAS --workspace-id ID --tables TABLE_ID [--tables ...] [--preserve]
kbagent workspace query --project ALIAS --workspace-id ID --sql "SELECT ..." [--transactional]
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 component list [--project NAME] [--type TYPE] [--query QUERY]
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.21.1",
"version": "0.22.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
6 changes: 6 additions & 0 deletions plugins/kbagent/skills/kbagent/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ When working inside a git repository or project directory, run `kbagent init` (o
| 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` |
| 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` |
| Delete a configuration metadata entry by its numeric ID | `kbagent config delete-metadata --project PROJECT --component-id COMPONENT-ID --config-id CONFIG-ID --metadata-id METADATA-ID` |
| Set the folder (KBC.configuration.folderName) on a configuration | `kbagent config set-folder --project PROJECT --component-id COMPONENT-ID --config-id CONFIG-ID --name NAME` |
| Assign variables to a config (auto-creates backing keboola.variables on first call) | `kbagent config variables-set --project PROJECT --component-id COMPONENT-ID --config-id CONFIG-ID` |
| Read the current variable values attached to a config | `kbagent config variables-get --project PROJECT --component-id COMPONENT-ID --config-id CONFIG-ID` |
| Unlink variables from a config (does NOT delete the underlying keboola.variables) | `kbagent config variables-clear --project PROJECT --component-id COMPONENT-ID --config-id CONFIG-ID` |
Expand Down Expand Up @@ -150,6 +155,7 @@ When working inside a git repository or project directory, run `kbagent init` (o
| Reset workspace password and show the new one | `kbagent workspace password --project PROJECT --workspace-id WORKSPACE-ID` |
| Load tables into a workspace | `kbagent workspace load --project PROJECT --workspace-id WORKSPACE-ID --tables TABLES` |
| Execute SQL query in a workspace via Query Service | `kbagent workspace query --project PROJECT --workspace-id WORKSPACE-ID` |
| Garbage-collect orphaned workspaces | `kbagent workspace gc` |
| Create a workspace from a transformation config | `kbagent workspace from-transformation --project PROJECT --component-id COMPONENT-ID --config-id CONFIG-ID` |
| List available MCP tools from the keboola-mcp-server | `kbagent tool list` |
| Call an MCP tool on keboola-mcp-server | `kbagent tool call <TOOL-NAME>` |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ All commands support `--json` for structured output. Multi-project flags (`--pro
- `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
- `config metadata-list --project NAME --component-id ID --config-id ID [--branch ID]` -- list all metadata entries on a configuration (id, key, value, provider, timestamp). Branch-aware
- `config get-metadata --project NAME --component-id ID --config-id ID --key KEY [--branch ID]` -- read a single metadata value by key. Exits with `NOT_FOUND` (exit 1) if absent
- `config set-metadata --project NAME --component-id ID --config-id ID --key KEY --value VALUE [--branch ID]` -- set (upsert) a metadata key/value on a configuration. Common keys: `KBC.configuration.folderName`, plus any custom `KBC.*` agent-facing tags
- `config delete-metadata --project NAME --component-id ID --config-id ID --metadata-id ID [--branch ID] [--yes]` -- delete a configuration metadata entry by its numeric ID (from `metadata-list`)
- `config set-folder --project NAME --component-id ID --config-id ID --name FOLDER [--branch ID]` -- set (or clear, with empty `--name`) the `KBC.configuration.folderName` metadata, which groups configs into named folders in the Keboola UI. See `config-metadata-workflow.md`

## Job History
- `job list [--project NAME] [--component-id ID] [--config-id ID] [--status STATUS] [--limit N]` -- list jobs (default 50, max 500)
Expand Down Expand Up @@ -90,12 +95,13 @@ All commands support `--json` for structured output. Multi-project flags (`--pro

## Workspaces (SQL Debugging)
- `workspace create --project ALIAS [--name NAME] [--ui] [--read-only]` -- create workspace (headless ~1s, `--ui` ~15s)
- `workspace list [--project NAME]` -- list workspaces
- `workspace list [--project NAME ...] [--orphaned]` -- list workspaces. `--project` repeatable for multi-project; `--orphaned` filters to workspaces whose backing `keboola.sandboxes` config is missing
- `workspace detail --project ALIAS --workspace-id ID` -- show connection details
- `workspace delete --project ALIAS --workspace-id ID` -- delete workspace
- `workspace password --project ALIAS --workspace-id ID` -- reset and return new password
- `workspace load --project ALIAS --workspace-id ID --tables TABLE_ID [...] [--preserve]` -- load storage tables
- `workspace query --project ALIAS --workspace-id ID --sql "..." [--file F] [--transactional]` -- run SQL via Query Service
- `workspace gc [--project NAME ...] [--dry-run] [--yes]` -- garbage-collect orphaned workspaces (and any lingering `keboola.sandboxes` configs). `--dry-run` previews without deleting; `--project` repeatable, omit to GC across all connected projects
- `workspace from-transformation --project ALIAS --component-id ID --config-id ID [--row-id ID]` -- workspace from existing transform

## MCP Tools
Expand Down
205 changes: 205 additions & 0 deletions plugins/kbagent/skills/kbagent/references/config-metadata-workflow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
# Config Metadata Workflow -- Tags, folders, and agent breadcrumbs

Keboola stores free-form `key / value / provider` metadata on every configuration.
The Keboola UI uses a handful of `KBC.*` keys for user-facing behavior (most
notably `KBC.configuration.folderName` to group configs into folders), but the
surface is open-ended: agents can stamp their own keys to leave breadcrumbs for
later runs (e.g. `agent.owner`, `agent.lastAudit`, `agent.domain`).

kbagent exposes the CRUD surface as five commands on `kbagent config`:

```
metadata-list -- list all entries (id, key, value, provider, timestamp)
get-metadata -- read one value by key
set-metadata -- upsert a single key/value
delete-metadata -- remove an entry by its numeric id
set-folder -- convenience wrapper that writes KBC.configuration.folderName
```

All five are branch-aware; omit `--branch` to use the project's active branch.

## When to use this

- **Folder organization**: group related configs under a named folder in the
Keboola UI (`set-folder`). Works across all component types; no schema change
needed.
- **Agent breadcrumbs**: tag configs an agent has touched so later runs can
skip, re-audit, or attribute them (`set-metadata --key agent.* ...`).
- **Ownership / governance tags**: stamp `owner`, `domain`, `cost-center`,
etc. and filter via `config list --json | jq` downstream.
- **Provenance tracking**: record when a config was last generated or
refactored by an automated workflow.

## CLI cheatsheet

```bash
# List everything on a config (sorted by key)
kbagent --json config metadata-list --project prod \
--component-id keboola.snowflake-transformation --config-id 15815157

# Read a specific key (exits 1 / NOT_FOUND if absent)
kbagent --json config get-metadata --project prod \
--component-id keboola.snowflake-transformation --config-id 15815157 \
--key KBC.configuration.folderName

# Upsert (create if new, overwrite if existing)
kbagent config set-metadata --project prod \
--component-id keboola.snowflake-transformation --config-id 15815157 \
--key agent.owner --value analytics-team

# Delete by numeric ID (from metadata-list)
kbagent config delete-metadata --project prod \
--component-id keboola.snowflake-transformation --config-id 15815157 \
--metadata-id 4281 --yes

# Folder sugar (writes KBC.configuration.folderName)
kbagent config set-folder --project prod \
--component-id keboola.snowflake-transformation --config-id 15815157 \
--name "Customer 360"

# Clear the folder assignment (empty string)
kbagent config set-folder --project prod \
--component-id keboola.snowflake-transformation --config-id 15815157 \
--name ""
```

## Folder organization pattern

The Keboola UI reads `KBC.configuration.folderName` and groups configs sharing
the same value into a named folder under the component. This is purely a
presentation feature -- the config itself is unchanged, and there are no
parent/child resources to manage. That makes it ideal for programmatic
taxonomy:

```bash
# Tag every config in an onboarding flow with one folder
for cfg_id in 15815157 15815158 15815159 15815160; do
kbagent config set-folder --project prod \
--component-id keboola.snowflake-transformation \
--config-id "$cfg_id" \
--name "Customer 360 - Onboarding"
done
```

Guidelines:

- `set-folder --name ""` removes the grouping (passes an empty string to
`set-metadata`, which the UI treats as "no folder").
- Folder names are free-form strings; keep them short and stable -- the UI
sorts alphabetically.
- Prefer `set-folder` over raw `set-metadata --key KBC.configuration.folderName`;
the wrapper exists so the key spelling is not a moving target for agents.
- Folders are **per component**. Two configs under different components with
the same folder name render as two separate folders in the UI -- this is
intentional, not a bug.

## Full lifecycle example

```bash
PROJECT=prod
COMPONENT=keboola.snowflake-transformation
CONFIG=15815157

# 1. Inspect what's already on the config
kbagent --json config metadata-list \
--project "$PROJECT" --component-id "$COMPONENT" --config-id "$CONFIG"

# 2. Stamp an agent breadcrumb
kbagent config set-metadata \
--project "$PROJECT" --component-id "$COMPONENT" --config-id "$CONFIG" \
--key agent.lastAudit --value "2026-04-23"

# 3. Read it back
kbagent --json config get-metadata \
--project "$PROJECT" --component-id "$COMPONENT" --config-id "$CONFIG" \
--key agent.lastAudit

# 4. File it into the Customer 360 folder
kbagent config set-folder \
--project "$PROJECT" --component-id "$COMPONENT" --config-id "$CONFIG" \
--name "Customer 360"

# 5. Later: clean the breadcrumb (list to find its numeric id, then delete)
METADATA_ID=$(kbagent --json config metadata-list \
--project "$PROJECT" --component-id "$COMPONENT" --config-id "$CONFIG" \
| jq -r '.data.metadata[] | select(.key=="agent.lastAudit") | .id')

kbagent config delete-metadata \
--project "$PROJECT" --component-id "$COMPONENT" --config-id "$CONFIG" \
--metadata-id "$METADATA_ID" --yes
```

## Response shapes (`--json` mode)

### `metadata-list`
```json
{
"status": "ok",
"data": {
"project_alias": "prod",
"component_id": "keboola.snowflake-transformation",
"config_id": "15815157",
"branch_id": 12345,
"metadata": [
{"id": "4281", "key": "KBC.configuration.folderName", "value": "Customer 360", "provider": "user", "timestamp": "2026-04-23T10:15:00Z"},
{"id": "4282", "key": "agent.lastAudit", "value": "2026-04-23", "provider": "user", "timestamp": "2026-04-23T10:16:02Z"}
]
}
}
```

The `metadata` list is key-sorted for deterministic output.

### `get-metadata`
```json
{
"status": "ok",
"data": {
"project_alias": "prod",
"component_id": "keboola.snowflake-transformation",
"config_id": "15815157",
"branch_id": 12345,
"key": "agent.lastAudit",
"value": "2026-04-23",
"metadata_id": "4282"
}
}
```

Returns `NOT_FOUND` (exit 1) when the key is absent -- there is no sentinel
"empty" value; missing means missing.

### `set-metadata` / `set-folder` / `delete-metadata`
Each returns `project_alias`, `component_id`, `config_id`, `branch_id`, and a
human-readable `message`. `set-folder` additionally returns `folder` so
callers don't have to re-parse the message string.

## Provider semantics

The `provider` field on each metadata entry distinguishes `user` (anything you
wrote via CLI/API/UI) from `system` (set by the Keboola platform itself).
kbagent never filters these out -- `metadata-list` surfaces both. Do not
attempt to `set-metadata` on a system-provider key; the API will return a
validation error and `delete-metadata` on a system entry is likewise rejected.

## Relation to `config update`

`config update` mutates the configuration body (`parameters`, `storage`,
`processors`, etc.). `config set-metadata` mutates the sibling `metadata`
array on the same configuration resource. They touch different endpoints and
never conflict. Use `config update` for anything that affects runtime
behavior; use `set-metadata` for everything else (tags, folders, audit
breadcrumbs).

## Branch awareness

All five commands resolve `--branch` the same way the rest of `kbagent config`
does:

1. Explicit `--branch ID` wins.
2. Otherwise the project's **active branch** (set by `kbagent branch use` /
`branch create`) is used.
3. If the project has no active branch, the main / default branch is used.

Metadata in a dev branch is independent of production; it merges back via the
same merge URL as the rest of the branch. See [branch-workflow.md](branch-workflow.md).
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "keboola-agent-cli"
version = "0.21.1"
version = "0.22.0"
description = "AI-friendly CLI for managing Keboola projects"
readme = "README.md"
requires-python = ">=3.12"
Expand Down
6 changes: 6 additions & 0 deletions src/keboola_agent_cli/changelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@

# Ordered newest-first. Each value is a list of brief one-line descriptions.
CHANGELOG: dict[str, list[str]] = {
"0.22.0": [
"New: config metadata-list / get-metadata / set-metadata / delete-metadata -- CRUD for arbitrary metadata key/value pairs on any configuration, using the branch-aware Storage API metadata endpoint (FIIA P1-3)",
"New: config set-folder -- sugar over set-metadata for KBC.configuration.folderName; organises configs into named folder groups visible in the Keboola UI (FIIA P1-3)",
"New: workspace list --orphaned -- lists workspaces backed by keboola.sandboxes whose sandbox config no longer exists (FIIA P1-4)",
"New: workspace gc [--dry-run] [--yes] -- deletes all orphaned workspaces; dry-run previews without touching anything; --yes skips interactive confirmation (FIIA P1-4)",
],
"0.21.1": [
"Fix: sync pull on a newly created dev branch now writes config rows (#193) -- idempotent skip guard for rows was missing a file-existence check, causing rows to be silently skipped when the branch directory was new (hash matched main because the branch is a clone)",
],
Expand Down
Loading
Loading