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.47.2",
"version": "0.48.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 @@ -382,6 +382,15 @@ kbagent sharing edges [--project NAME]
kbagent org setup --org-id ID --url URL [--dry-run] [--yes] [--token-description PREFIX] [--refresh]
kbagent org setup --project-ids 1,2,3 --url URL [--dry-run] [--yes] [--token-description PREFIX] [--refresh]

# feature: requires a super-admin Manage API token (inline hidden prompt; never persisted; --allow-env-manage-token for CI). --project resolves the stack URL (+ project_id for project ops) from config.
kbagent feature list --project ALIAS
kbagent feature project-show --project ALIAS
kbagent feature project-add --project ALIAS --feature NAME [--dry-run] [--yes]
kbagent feature project-remove --project ALIAS --feature NAME [--dry-run] [--yes]
kbagent feature user-show --project ALIAS --email EMAIL
kbagent feature user-add --project ALIAS --email EMAIL --feature NAME [--dry-run] [--yes]
kbagent feature user-remove --project ALIAS --email EMAIL --feature NAME [--dry-run] [--yes]

kbagent tool list [--project NAME] [--branch ID]
kbagent tool call TOOL_NAME [--project NAME] [--input JSON|@file|-] [--branch ID]

Expand Down
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.DEFAULT_GOAL := help

.PHONY: help install install-mcp install-server sync test test-unit test-integration test-e2e test-e2e-local test-e2e-invite test-file lint lint-fix format format-check typecheck typecheck-warn skill-check skill-gen version-sync version-check changelog changelog-check check-error-codes check clean hooks web-install web-dev-backend web-dev-frontend web-build web-clean
.PHONY: help install install-mcp install-server sync test test-unit test-integration test-e2e test-e2e-local test-e2e-invite test-e2e-feature test-file lint lint-fix format format-check typecheck typecheck-warn skill-check skill-gen version-sync version-check changelog changelog-check check-error-codes check clean hooks web-install web-dev-backend web-dev-frontend web-build web-clean

help: ## Show this help message
@grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-18s\033[0m %s\n", $$1, $$2}'
Expand Down Expand Up @@ -36,6 +36,9 @@ test-e2e-local: ## Run E2E against a project in a local config.json (CONFIG_DIR=
test-e2e-invite: ## Run project invite E2E (E2E_MANAGE_TOKEN + E2E_INVITE_PROJECT_ID required)
uv run pytest tests/test_e2e.py -v -s --tb=long -m e2e_invite

test-e2e-feature: ## Run feature-flag E2E (E2E_MANAGE_TOKEN super-admin + E2E_API_TOKEN + E2E_URL required)
uv run pytest tests/test_e2e.py -v -s --tb=long -k test_feature_flags_read_e2e

test-file: ## Run a specific test file (FILE=tests/test_cli.py)
uv run pytest $(FILE) -v

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.47.2",
"version": "0.48.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
2 changes: 2 additions & 0 deletions plugins/kbagent/agents/keboola-expert.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ a critical failure.
`semantic-layer search-context|get-context`, `storage create-table --if-not-exists`, `sync push|pull|diff --branch`, `sync push --no-name-drift-warnings`, fresh-CREATE writeback + KBC.* = 0.47.0+,
Snowflake `workspace create` `private_key` = 0.47.1+,
`sync push` fresh-CREATE variable-link resolution + `--branch <id>` default-tree promote = 0.47.2+,
`feature` group (stack/project/user feature flags, Manage API) = 0.48.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 Down Expand Up @@ -150,6 +151,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; `workspace list --qs-compatible` (0.42.0+, #304) for data-app reuse | 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 |
| Manage feature flags (stack catalogue / project / user) | `kbagent feature list\|project-show\|project-add\|project-remove\|user-show\|user-add\|user-remove --project P [--email E] [--feature NAME] [--dry-run] [--yes]` (0.48.0+) -- Manage API; needs a SUPER-ADMIN manage token (interactive prompt; `--allow-env-manage-token`+`KBC_MANAGE_API_TOKEN` for CI); `--project` resolves the stack URL (+project_id for `project-*`); add=admin, remove=destructive; add body is `{"feature":NAME}` | `kbagent project info` for a project's *enabled* features (read-only, no super-admin) | raw `/manage/...` calls; manage token via a CLI flag |
| 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) |
Expand Down
11 changes: 11 additions & 0 deletions plugins/kbagent/skills/kbagent/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ description: >
list members, remove member, change role, project role,
bulk invite, invite from CSV, project access, member management,
manage token prompt, --allow-env-manage-token, KBC_MANAGE_API_TOKEN,
feature flag, feature flags, list features, project features, user features,
enable feature, disable feature, set feature flag, add feature, remove feature,
early-adopter-preview, direct-access, pay-as-you-go, /manage/features,
super admin token, super-admin feature, stack feature catalogue,
semantic-layer, semantic layer, semantic-layer model, metastore,
semantic-metric, semantic-dataset, semantic-relationship,
semantic-constraint, semantic-glossary, add metric, edit metric,
Expand Down Expand Up @@ -123,6 +127,13 @@ When working inside a git repository or project directory, run `kbagent init` (o
| Remove an active member from a project (destructive) | `kbagent project member-remove --project PROJECT --email EMAIL` |
| Change an existing member's role (PATCH) | `kbagent project member-set-role --project PROJECT --email EMAIL --role ROLE` |
| Set up projects and register them in the kbagent config | `kbagent org setup --url URL` |
| List all feature flags defined on the stack | `kbagent feature list --project PROJECT` |
| Show feature flags assigned to a project | `kbagent feature project-show --project PROJECT` |
| Enable a feature flag on a project | `kbagent feature project-add --project PROJECT --feature FEATURE` |
| Disable a feature flag on a project (destructive) | `kbagent feature project-remove --project PROJECT --feature FEATURE` |
| Show feature flags assigned to a user | `kbagent feature user-show --project PROJECT --email EMAIL` |
| Enable a feature flag on a user | `kbagent feature user-add --project PROJECT --email EMAIL --feature FEATURE` |
| Disable a feature flag on a user (destructive) | `kbagent feature user-remove --project PROJECT --email EMAIL --feature FEATURE` |
| List available components from connected projects | `kbagent component list` |
| Show detailed information about a specific component | `kbagent component detail --component-id COMPONENT-ID` |
| List configurations from connected projects | `kbagent config list` |
Expand Down
10 changes: 10 additions & 0 deletions plugins/kbagent/skills/kbagent/references/commands-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,16 @@ All seven commands authenticate via `KBC_MANAGE_API_TOKEN` (Manage API), not the
- `org setup --org-id ID --url URL [--dry-run] [--yes]` -- bulk-onboard all projects from an org (org admin; manage token via interactive prompt by default, or `--allow-env-manage-token` + `KBC_MANAGE_API_TOKEN` for CI on 0.29.0+)
- `org setup --project-ids 1,2,3 --url URL [--dry-run] [--yes]` -- onboard specific projects by ID (any project member; manage token / Personal Access Token via interactive prompt by default, or `--allow-env-manage-token` + `KBC_MANAGE_API_TOKEN` for CI on 0.29.0+)

## Feature Flags (since v0.48.0)
Requires a **super-admin** Manage API token (same kind as `org setup`). Same default-deny token policy: interactive hidden prompt by default, or `--allow-env-manage-token` + `KBC_MANAGE_API_TOKEN` for CI. `--project ALIAS` resolves the stack URL (and, for project ops, the numeric `project_id`) from config -- the alias is the only handle you pass.
- `feature list --project ALIAS` -- the stack-wide feature catalogue (`GET /manage/features`). Returns `{alias, stack_url, features: [{name, title, description, type, ...}]}`. Only `name` is a stable identifier; extra fields pass through unmodified.
- `feature project-show --project ALIAS` -- features assigned to a project, read from the project object's `features` array. Returns `{alias, project_id, project_name, features: [...]}`.
- `feature project-add --project ALIAS --feature NAME [--dry-run] [--yes]` -- enable a feature on a project (`POST /manage/projects/{id}/features`, body `{"feature": NAME}`). Permission class `admin`.
- `feature project-remove --project ALIAS --feature NAME [--dry-run] [--yes]` -- disable a feature on a project (`DELETE /manage/projects/{id}/features/{name}`). Permission class `destructive`.
- `feature user-show --project ALIAS --email EMAIL` -- features assigned to a user (`GET /manage/users/{email}`). Returns `{alias, stack_url, email, features: [...]}`.
- `feature user-add --project ALIAS --email EMAIL --feature NAME [--dry-run] [--yes]` -- enable a feature on a user (`POST /manage/users/{email}/features`).
- `feature user-remove --project ALIAS --email EMAIL --feature NAME [--dry-run] [--yes]` -- disable a feature on a user (`DELETE /manage/users/{email}/features/{name}`).

## Component Discovery
- `component list [--project NAME] [--type TYPE] [--query "text"]` -- list/search components (AI-powered with `--query`)
- `component detail --component-id ID [--project NAME]` -- show component schema, docs URL, examples
Expand Down
41 changes: 41 additions & 0 deletions plugins/kbagent/skills/kbagent/references/gotchas.md
Original file line number Diff line number Diff line change
Expand Up @@ -2202,3 +2202,44 @@ commands that never had `--hint` support.
AI agents should prefer the REST surface over `--hint` for new integrations. Do
not add new examples or workflows that teach `--hint`; point readers to
`kbagent serve` instead.

## `feature` command group: super-admin token, no per-project endpoint, opaque schema (since v0.48.0)

The `feature` group manages Keboola feature flags via the **Manage API**. Five
things trip up callers:

1. **Super-admin manage token required.** `feature list` (the stack catalogue)
and every project/user mutation need a super-admin Manage API token -- the
same kind `org setup` uses, NOT the per-project Storage token. It follows the
default-deny policy: interactive hidden prompt by default; pass top-level
`--allow-env-manage-token` + `KBC_MANAGE_API_TOKEN` for CI. Do NOT pass the
token as a CLI flag. A non-super-admin token returns 403 (exit 3).

2. **`--project` is just a handle to the stack URL.** For `feature list` and the
`user-*` commands the alias only resolves the stack URL -- the catalogue and
user features are stack-wide, not project-scoped. For `project-*` commands it
additionally resolves the numeric `project_id` from config. The alias must be
registered (`kbagent project list`); `project-*` also requires it to carry a
`project_id`.

3. **No dedicated "project features list" endpoint.** `feature project-show`
reads the `features` array off `GET /manage/projects/{id}`; `feature
user-show` reads it off `GET /manage/users/{email}`. There is no
`/projects/{id}/features` GET. Only the add (`POST .../features`, body
`{"feature": NAME}`) and remove (`DELETE .../features/{name}`) verbs are
per-resource.

4. **Request body is `{"feature": NAME}`, not `{"name": NAME}`.** The add
endpoints take the feature code under the key `feature`. (Some third-party
notes claim `name` -- that is wrong for this API.)

5. **Feature schema is opaque + shape-variable.** The Manage API publishes no
feature schema, and a `features` array may come back as a list of objects OR
a list of bare strings depending on stack/endpoint. kbagent normalises both
to `{name, title, description, type, ...}` (bare strings become
`{"name": s}`) and passes unknown keys through unmodified. Treat `name` as
the only stable field; do not depend on `title`/`type` being populated.

To inspect a project's *enabled* features without a super-admin token, use
`kbagent project info --project P` (read-only) instead -- it returns the enabled
feature list among other project metadata.
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.47.2"
version = "0.48.0"
description = "AI-friendly CLI for managing Keboola projects"
readme = "README.md"
requires-python = ">=3.12"
Expand Down
3 changes: 3 additions & 0 deletions src/keboola_agent_cli/changelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@

# Ordered newest-first. Each value is a list of brief one-line descriptions.
CHANGELOG: dict[str, list[str]] = {
"0.48.0": [
"New: `kbagent feature` command group for managing Keboola feature flags via the Manage API (requires a super-admin manage token, the same kind `org setup` uses). Seven subcommands: `feature list --project ALIAS` (the stack-wide feature catalogue, GET /manage/features); `feature project-show --project ALIAS` (features assigned to a project, read from the project object's `features` array); `feature project-add` / `feature project-remove --project ALIAS --feature NAME [--dry-run] [--yes]` (POST/DELETE /manage/projects/{id}/features); and `feature user-show` / `feature user-add` / `feature user-remove --project ALIAS --email EMAIL [--feature NAME]` for per-user features (GET/POST/DELETE /manage/users/{email}/features). The `--project ALIAS` resolves the stack URL (and, for project ops, the numeric project_id) from the kbagent config -- the alias is the only handle needed. The manage token follows the same default-deny policy as `org`: read from an interactive hidden prompt, never persisted, never a CLI argument; pass the top-level `--allow-env-manage-token` to read `KBC_MANAGE_API_TOKEN` from env (CI/CD). Write paths support `--dry-run` and an interactive confirm (skip with `--yes`). Permission classes: `list` / `*-show` = read, `*-add` = admin, `*-remove` = destructive. The Manage API has no published feature schema, so the new `Feature` model treats only `name` as stable and passes extras through unmodified; project/user `features` arrays returned as bare strings are normalised to `{name: ...}`. New layers: `ManageClient.list_features` / `add_project_feature` / `remove_project_feature` / `get_user` / `add_user_feature` / `remove_user_feature` (email + feature url-encoded in the path; the POST add paths tolerate a 204 No Content body), `FeatureService`, `commands/feature.py`, and a 1:1 `kbagent serve` REST router (`server/routers/feature.py`, 7 endpoints, each requiring the `X-Manage-Token` header). Human-mode tables are adaptive: the stack catalogue keeps Title/Type/Description, while project/user views (returned by the Manage API as bare strings) collapse to just Name. Tests: `tests/test_feature_service.py` (19), `tests/test_feature_cli.py` (21), `tests/test_manage_client.py` + `tests/test_models.py` extensions; read-only E2E `tests/test_e2e.py::test_feature_flags_read_e2e` (opt-in via `make test-e2e-feature`).",
],
"0.47.2": [
"Fix (`sync push`, fresh-CREATE variable binding): a transformation scaffolded alongside its sibling `keboola.variables` config + default-values row is now runnable after a single `sync push`. Three defects in the create pass are fixed together. (KFR-04) The row's `values: [...]` array was silently dropped because `local_row_to_api` only hoisted `values` into the API body when the row file already carried a `_keboola.component_id`; the two push callers now pass the known `component_id` explicitly, so a scaffold row without a `_keboola` block still hoists. (KFR-05) Rows whose parent `keboola.variables` config was created **in the same push** raised `PARENT_CONFIG_NOT_TRACKED` (or POSTed against a non-existent placeholder id): `push()` now runs in ordered phases -- configs first, then rows -- capturing each created config's placeholder id -> assigned ULID and remapping every row's `parent_config_id` to the ULID before the manifest lookup and `create_config_row(config_id=...)`. (KFR-03) The transformation's remote `configuration.variables_id` / `variables_values_id` stayed as placeholder dirnames (so `job run` failed with `Variable configuration \"<placeholder>\" not found`); a new Phase-C backfill resolves each placeholder to the ULID assigned this push and PUTs the corrected configuration body via `update_config` (NOT `set_variables`, which would create a second variables config), then rewrites the local `_configuration_extra` and refreshes the manifest `pull_hash` / `pull_config_hash` / `pull_extra_hashes` so a re-push is clean. When the placeholder key misses but exactly one `keboola.variables` config was created this push, it binds to that one with a warning; zero or ambiguous (>1) matches accumulate a `variable_link` error rather than writing a broken link. Downstream (FIIA) can delete its post-push `config variables-set` workaround. Tests: `tests/test_sync_config_format.py::TestLocalRowToApiComponentIdParam`, `tests/test_sync_service.py::TestFreshCreateVariableBinding` (end-to-end bindings, idempotent re-push, single-config fallback, ambiguous-config error), plus an E2E (`job run --wait` -> success) in `tests/test_e2e.py`.",
"Fix (`sync push --branch <id>`, KFR-07): pushing the local default tree to a target dev branch no longer errors with `Config file not found`. Source (where files live on disk) and target (where the API writes) are now decoupled: when no materialized `<branch_name>/` subtree exists for the target branch, the default tree (`main/`) is read as the source and promoted to the target branch; all API calls still target the branch id. A new `SyncService._resolve_source_branch_path` drives the local-read path in `push` / `diff` / `_push_create` / `_push_update` / `_push_row_change`; per-config tracked reads continue to use each entry's own `branch_id`. Backward-compatible: when the per-branch subtree exists (multi-branch-directory users), behaviour is unchanged. Tests: `tests/test_sync_service.py::TestFreshCreateVariableBinding::test_resolve_source_branch_path_promotes_default_tree`.",
Expand Down
Loading
Loading