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.51.0",
"version": "0.51.1",
"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
3 changes: 3 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,9 @@ kbagent dev-portal upload-icon --app VENDOR.APP_ID --file PATH [--identity A] [-
kbagent dev-portal publish --app VENDOR.APP_ID [--identity A] [--dry-run]
kbagent dev-portal deprecate --app VENDOR.APP_ID [--identity A] [--dry-run]
# All writes require an interactive random-code TTY confirm; no --yes / no env bypass.
# Since v0.51.1: --role-hint is validated (vendor/admin) and load-bearing -- admin identities route
# `patch` to PATCH /admin/apps/{app} (permissive schema). Vendor + admin-only field => fail-fast preflight.
# --password-stdin works on TTY (hidden prompt) AND on a pipe (reads to EOF).

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.51.0",
"version": "0.51.1",
"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: 1 addition & 1 deletion plugins/kbagent/agents/keboola-expert.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ a critical failure.
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+,
`dev-portal` command group = 0.49.0+,
`dev-portal` = 0.49.0+ (admin-role PATCH = 0.51.1+),
headless `__env__` project (`KBAGENT_PROJECT_FROM_ENV=1` + `KBC_TOKEN` + `KBC_STORAGE_API_URL`) + forgiving stack-URL normalization (bare host / full project deep-link) = 0.50.0+,
`stream` command group (Data Streams / OTLP) = 0.50.0+,
`storage retype` is a future composite), you
Expand Down
12 changes: 9 additions & 3 deletions plugins/kbagent/skills/kbagent/references/commands-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,12 +214,18 @@ Requires the project to be added with its **master ('owner') Storage API token**
## Encryption
- `encrypt values --project ALIAS --component-id ID --input JSON|@file|- [--output-file PATH]` -- encrypt #-prefixed secrets via Keboola Encryption API (one-way, no decrypt). Scope: ComponentSecure (project + component). Use for MCP tool call workflows.

## Developer Portal (since v0.49.0)
## Developer Portal (since v0.49.0; admin routing in v0.51.1)

Talks to `apps-api.keboola.com`. **Reads are unrestricted; writes always require a human to type a random hex code on a real TTY (no `--yes`, no env bypass, exit 6 on non-TTY).** Use `--dry-run` for the agent-safe preview path.

`--role-hint` is **load-bearing** for `dev-portal patch` (since v0.51.1): `vendor` (default) → `PATCH /vendors/{vendor}/apps/{app}` (restricted schema, the common case); `admin` → `PATCH /admin/apps/{app}` (permissive schema, the only way to set `complexity`, `categories`, `category`, `features`, `forwardToken`, `forwardTokenDetails`, `injectEnvironment`, `processTimeout`, `requiredMemory`). A `vendor` identity with any of those 9 fields in the payload fails fast at preflight with the exact command to switch.

`--password-stdin` (since v0.51.1) works in both TTY mode (hidden line-based prompt, Enter to confirm) and pipe mode (`echo $PASS | … --password-stdin`, reads to EOF).

MFA login (since v0.51.1) sends `challenge: SOFTWARE_TOKEN_MFA` explicitly to fix a 404 on personal-account TOTP logins where the apps-api server silently rejects missing-challenge requests despite the spec calling it optional. Single attempt only; failure surfaces the actual server body with a stale-TOTP hint.

### Identity management
- `dev-portal identity add --alias A --username U [--password P | --password-stdin] [--role-hint vendor|admin] [--vendor V] [--portal-url URL]` -- store a portal login credential per-alias in `config.json` (0600 perms).
- `dev-portal identity add --alias A --username U [--password P | --password-stdin] [--role-hint vendor|admin] [--vendor V] [--portal-url URL]` -- store a portal login credential per-alias in `config.json` (0600 perms). `--role-hint` is validated (`vendor`/`admin`, case-folded) since v0.51.1.
- `dev-portal identity list` -- list stored portal identities (no passwords shown).
- `dev-portal identity remove --alias A` -- delete an identity alias.
- `dev-portal identity edit --alias A [--username U] [--password P|--password-stdin] [--role-hint H] [--vendor V] [--new-alias N]` -- update fields of an identity.
Expand All @@ -233,7 +239,7 @@ Talks to `apps-api.keboola.com`. **Reads are unrestricted; writes always require

### Write commands (require TTY random-code confirm; use `--dry-run` first)
- `dev-portal create --vendor V --data FILE [--identity A] [--dry-run]` -- register a new component from a JSON payload file.
- `dev-portal patch --app VENDOR.APP_ID (--data FILE | --property KEY (--value V | --value-file F)) [--identity A] [--dry-run]` -- update portal properties. `--data` is a full-replace of the provided keys; `--property` targets a single key.
- `dev-portal patch --app VENDOR.APP_ID (--data FILE | --property KEY (--value V | --value-file F)) [--identity A] [--dry-run]` -- update portal properties. Endpoint depends on the identity's `role_hint`: vendor → vendor endpoint, admin → admin endpoint.
- `dev-portal upload-icon --app VENDOR.APP_ID --file PATH [--identity A] [--dry-run]` -- upload a PNG/SVG icon.
- `dev-portal publish --app VENDOR.APP_ID [--identity A] [--dry-run]` -- publish the component (makes it visible in the UI).
- `dev-portal deprecate --app VENDOR.APP_ID [--identity A] [--dry-run]` -- mark the component as deprecated.
Expand Down
30 changes: 28 additions & 2 deletions plugins/kbagent/skills/kbagent/references/dev-portal-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,38 @@ as KB project tokens, under 0600 protection:
```
kbagent dev-portal identity add --alias vendor-keboola --username service.keboola.xxxxx --password ... --vendor keboola
kbagent dev-portal identity add --alias vendor-kds --username service.kds-team.xxxxx --password ... --vendor kds-team
kbagent dev-portal identity add --alias admin-foo --username admin@keboola.com --password-stdin
kbagent dev-portal identity add --alias admin-keboola --username admin@keboola.com --role-hint admin --password-stdin
kbagent dev-portal identity use vendor-keboola # default for subsequent commands
```

Service accounts (`service.{vendor}.{id}`) skip MFA. Personal admin
accounts prompt for the MFA code on /dev/tty at login time.
accounts prompt for the MFA code on `/dev/tty` at login time
(`SOFTWARE_TOKEN_MFA`, i.e. a TOTP authenticator app like 1Password / Authy /
Google Authenticator).

`--password-stdin` works in both pipe mode (`echo $PASS | … --password-stdin`,
reads to EOF) and TTY mode (hidden line-based prompt, Enter to confirm).

### `role_hint` is load-bearing (since v0.51.1)

`--role-hint` is **not** a free-text label. It picks which apps-api
endpoint kbagent uses for `dev-portal patch`:

| Role | PATCH endpoint | Schema | Use for |
|------|----------------|--------|---------|
| `vendor` (default) | `/vendors/{vendor}/apps/{app}` | `clientAppSchema` (restricted) | Cookiecutter-backed properties, schemas, UI options, descriptions, icon |
| `admin` | `/admin/apps/{app}` | `adminAppSchema` (permissive) | The 9 fields forbidden on vendor: `complexity`, `categories`, `category`, `features`, `forwardToken`, `forwardTokenDetails`, `injectEnvironment`, `processTimeout`, `requiredMemory` |

`role_hint` is validated (`vendor` or `admin`, case-folded). kbagent does
not verify the server-side role of the credential -- if you set `admin`
but the account isn't actually a portal admin, the PATCH fails at the
apps-api with an unambiguous 403.

When a vendor-role identity tries to patch one of the 9 admin-only
fields, the service **fail-fasts** with a message that names the
offending fields, explains why the server's 422 ("must be one of: ...")
is misleading, and shows the exact command to add and use an admin
identity. No portal call is made.

## Safety contract (read this before issuing any write)

Expand Down
51 changes: 51 additions & 0 deletions plugins/kbagent/skills/kbagent/references/gotchas.md
Original file line number Diff line number Diff line change
Expand Up @@ -2331,3 +2331,54 @@ materialize lazily on first import (the bucket/table appear in Storage seconds
after the first record arrives, not at create time). `create-source` / `delete`
/ sink creation are **async**: the API returns a Task that kbagent polls to
completion before returning.

## `dev-portal patch`: admin-only fields need an admin-role identity, vendor PATCH lies about why

`PATCH /vendors/{vendor}/apps/{app}` on apps-api `.forbidden()`s 9 fields:
`complexity`, `categories`, `category`, `features`, `forwardToken`,
`forwardTokenDetails`, `injectEnvironment`, `processTimeout`,
`requiredMemory`. Sending any of them via a vendor identity returns
`422 Parameter complexity must be one of: easy, medium, hard` (or the
analogous enum message for the other fields). **The message is a server
bug** — the enum-validation `.error()` annotation lives on the shared
admin schema before `clientAppSchema()` overrides with `.forbidden()`,
so when `.forbidden()` fires Joi reuses the unrelated enum message
instead of saying "this field is not allowed here".

To set any of these you need an admin identity that routes the PATCH
to `PATCH /admin/apps/{app}` instead (since v0.51.1):

```
kbagent dev-portal identity add --alias admin-keboola \
--username admin@keboola.com --role-hint admin --password-stdin
kbagent dev-portal patch --app keboola.ex-foo \
--data /tmp/patch.json --identity admin-keboola
```

With `role_hint: vendor` (the default), kbagent now pre-flights the
payload and fails fast with the same guidance instead of letting the
apps-api return the misleading 422 (since v0.51.1). The 9 forbidden
fields are documented in
[keboola/developer-portal:src/lib/validation.js](https://github.com/keboola/developer-portal/blob/master/src/lib/validation.js)
under `clientAppSchema()`.

## `dev-portal identity add`: MFA logins for TOTP accounts need the `challenge` field explicit (since v0.51.1)

The apiary spec calls `challenge` optional with default `SOFTWARE_TOKEN_MFA`
on the second-step `POST /auth/login`, but in practice the server 404s
when it is omitted on a personal-account TOTP login. kbagent now sends
`challenge: SOFTWARE_TOKEN_MFA` explicitly. Single attempt only:
`/auth/login` consumes the session, so any retry with a different
challenge type would always 404 with `Invalid code or auth state for
the user` and mask the real first failure. The raised error includes
the server response body and a hint about TOTP code rotation, so
"stale code" can be distinguished from "wrong code" / "expired session".

## `dev-portal identity {add,edit} --password-stdin` works in both TTY and pipe mode (since v0.51.1)

Pre-0.51.1 the flag did `sys.stdin.read().strip()` unconditionally,
which waits for EOF rather than Enter — pasting a password and pressing
Enter just hung until Ctrl-C. The helper now branches on
`sys.stdin.isatty()`: TTY uses `getpass.getpass()` (hidden, line-based,
Enter confirms); pipe (`echo $PASS | kbagent dev-portal identity add
--password-stdin`) still reads to EOF.
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.51.0"
version = "0.51.1"
description = "AI-friendly CLI for managing Keboola projects"
readme = "README.md"
requires-python = ">=3.12"
Expand Down
5 changes: 5 additions & 0 deletions src/keboola_agent_cli/changelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@

# Ordered newest-first. Each value is a list of brief one-line descriptions.
CHANGELOG: dict[str, list[str]] = {
"0.51.1": [
"Fix (dev-portal): admin-role PATCH routing. `complexity`, `categories`, `forwardToken`, `forwardTokenDetails`, `injectEnvironment`, `processTimeout`, `requiredMemory`, `features`, and `category` are `.forbidden()` on the apps-api vendor schema (`clientAppSchema` in keboola/developer-portal:src/lib/validation.js) but settable on the admin schema. The vendor PATCH returns a misleading 422 (`Parameter complexity must be one of: easy, medium, hard`) because the enum-validation `.error()` annotation is attached on the shared admin schema before `clientAppSchema()` overrides with `.forbidden()`. `DeveloperPortalIdentity.role_hint` becomes a real validator (`vendor`/`admin`, case-folded, typos raise); `DeveloperPortalClient.patch_app` now reads the role and routes admin identities to `PATCH /admin/apps/{app}` (permissive schema); `DeveloperPortalService.prepare_patch` preflights vendor-role + admin-only-field combinations with a fail-fast error that names every offending field, explains why the 422 is misleading, and tells the user the exact command to switch identity. Admin role bypasses the preflight entirely. Reads, create, upload-icon, deprecate keep vendor-endpoint behaviour -- only PATCH has a meaningful admin variant on the server.",
"Fix (dev-portal): MFA login. The apiary spec calls `challenge` optional with default `SOFTWARE_TOKEN_MFA`, but in practice the server 404s on personal-account TOTP logins when it is omitted -- users saw `Error: Developer Portal MFA login failed (HTTP 404)` with no diagnostic body. The field is now sent explicitly. Single attempt only: an earlier experiment retried with `SMS_MFA` on the same session, but `/auth/login` consumes the session, so the retry always 404'd with `Invalid code or auth state for the user` and masked the real first failure (most often a stale 30-second TOTP code). The raised `KeboolaApiError` now includes the server response body (truncated to 500 chars) plus a hint about TOTP rotation, so users can distinguish wrong-code from stale-code from expired-session.",
"Fix (dev-portal): `--password-stdin` no longer hangs interactively. The old code did `sys.stdin.read().strip()` unconditionally, which waits for EOF (Ctrl-D) rather than for Enter -- users who pasted a password and pressed Enter were stuck until they Ctrl-C'd. The new `_read_password_stdin()` helper branches on `sys.stdin.isatty()`: TTY uses `getpass.getpass()` (hidden, line-based, Enter to confirm); pipe still does `read() -> strip()`. Both `identity add --password-stdin` and `identity edit --password-stdin` route through it. Help text updated to describe the dual-mode behaviour.",
],
"0.51.0": [
"New: Data Streams web UI. The `stream` command group (OTLP/HTTP sources, shipped in 0.50.0) now has a page in the kbagent web UI (`kbagent serve --ui`) under Browse -> Data Streams: list sources, create an OTLP/HTTP source (with sink auto-provisioning + if-not-exists), inspect endpoints/destination with a reveal toggle for the masked OTLP secret, and delete. Full parity with the `kbagent stream *` CLI and the `/stream/*` REST surface.",
"Fix: `stream` is now documented in the `kbagent serve` OpenAPI schema. The router was registered and callable, but its tag was missing from `OPENAPI_TAGS`, so `/docs#/stream` rendered as a bare, description-less section outside its logical Data group. A new smoke test asserts every router tag has an OpenAPI description block, so a new router can't ship invisible in `/docs` again.",
Expand Down
22 changes: 21 additions & 1 deletion src/keboola_agent_cli/commands/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -1120,9 +1120,29 @@
**Identity management** -- portal logins are stored per-alias in `config.json`:

kbagent dev-portal identity add --alias vendor-keboola \\
--username service.keboola.xxxxx --password ... --vendor keboola
--username service.keboola.xxxxx --password ... --vendor keboola \\
--role-hint vendor # default; restricts PATCH to vendor endpoint
kbagent dev-portal identity add --alias admin-keboola \\
--username admin@keboola.com --role-hint admin --password-stdin
kbagent dev-portal identity use vendor-keboola

**`role_hint` is load-bearing (since v0.51.1)**: `vendor` (default) routes
`dev-portal patch` to `PATCH /vendors/{{vendor}}/apps/{{app}}` (restricted
schema); `admin` routes it to `PATCH /admin/apps/{{app}}` (permissive
schema). The admin endpoint is the **only** way to set the 9 fields
apps-api `.forbidden()`s on vendor: `complexity`, `categories`, `category`,
`features`, `forwardToken`, `forwardTokenDetails`, `injectEnvironment`,
`processTimeout`, `requiredMemory`. Sending any of those with a `vendor`
identity fails fast at preflight with the exact command to switch
identity (server-side it would have returned a misleading 422 saying
"must be one of: easy, medium, hard"; that message is a known apps-api
bug -- the field is actually `forbidden()`, not enum-validated).

**`--password-stdin` (since v0.51.1)** works on TTY (hidden line-based
prompt, Enter to confirm) AND on a pipe (`echo $PASS | … --password-stdin`,
reads to EOF). Pre-0.51.1 the flag hung interactively because it always
waited for EOF.

**Read commands** (unrestricted; good for peer-config research):

kbagent --json dev-portal list --vendor keboola
Expand Down
Loading
Loading