Skip to content
Merged
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -507,10 +507,10 @@ kbagent project create --url URL [--project ALIAS] [--name NAME] [--backend snow
# delivered and may have succeeded (retryable:false, message says not to repeat -- check
# `auth status`, whose stored session + claim link is the evidence). `--sync-backend-init`
# raises that call's read timeout to 300s, which can outlast an agent's foreground shell.
kbagent project add --project NAME --url URL --token TOKEN
kbagent project add --project NAME --url URL [--token-stdin | --token-file PATH [--keep-token-file] | --token-env NAME | --token TOKEN]
kbagent project list
kbagent project remove --project NAME
kbagent project edit --project NAME [--url URL] [--token TOKEN] [--new-alias NEW]
kbagent project edit --project NAME [--url URL] [--token-stdin | --token-file PATH [--keep-token-file] | --token-env NAME | --token TOKEN] [--new-alias NEW]
kbagent project status [--project NAME]
kbagent project refresh --project ALIAS [--dry-run] [--force] [--yes] [--token-description DESC] [--token-expires-in N]
kbagent project refresh --all [--dry-run] [--force] [--yes] [--token-description DESC] [--token-expires-in N]
Expand Down
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,9 @@ with Client(url=os.environ["KBC_URL"], token=os.environ["KBC_TOKEN"]) as kbc:
![30-second demo](docs/assets/demo-readme-main.gif)

```bash
# Connect a project (Storage API token from Keboola UI)
# Connect a project (Storage API token from Keboola UI, typed at a hidden prompt)
kbagent project add --project prod \
--url https://connection.keboola.com --token YOUR_TOKEN
--url https://connection.keboola.com --token-stdin

# Find anything (table / config / flow / data app) across ALL projects in one call
kbagent search "customer_id"
Expand Down Expand Up @@ -205,9 +205,12 @@ Four ways to register projects, depending on what you have. If you are a human
at a terminal with a browser, start with **browser login** (last one below);
the token-based options are the ones to use for CI and anything unattended.

**Single project** — you have a Storage API token from the UI:
**Single project** — you have a Storage API token from the UI. `--token-stdin` shows a hidden prompt on a terminal and reads a pipe otherwise, so the token stays out of your shell history:
```bash
kbagent project add --project prod --url https://connection.keboola.com --token YOUR_TOKEN
kbagent project add --project prod --url https://connection.keboola.com --token-stdin

# CI / non-interactive: pipe the token in
printf '%s' "$KBC_TOKEN" | kbagent project add --project prod --url https://connection.keboola.com --token-stdin
```

**Many projects by ID** — you have a Manage API or Personal Access Token + the project IDs:
Expand Down
19 changes: 15 additions & 4 deletions docs/TUTORIAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,25 @@ Nothing else is needed.
```bash
kbagent project add --project prod \
--url https://connection.keboola.com \
--token YOUR_STORAGE_API_TOKEN
--token-stdin
```

`--token-stdin` shows a hidden prompt on a terminal: paste the token and press
Enter. In a script, pipe the token in instead:

```bash
printf '%s' "$KBC_TOKEN" | kbagent project add --project prod \
--url https://connection.keboola.com --token-stdin
```

Do not pass the token as `--token VALUE`. It works, but it writes the token
into your shell history and into a process listing.

Aliases are arbitrary; pick names that make sense in your head
(`prod`, `dev`, `client-a`, `my-project`). The CLI uses them on every
subsequent command (`--project prod`).

Alternative ways to pass the token (same effect, safer for shell history):
Other ways to pass the token (also safe for shell history):

```bash
# Env var
Expand Down Expand Up @@ -824,10 +835,10 @@ 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).
# Register the project once (Storage API token from the UI, typed at a hidden prompt).
kbagent project add --project prod \
--url https://connection.keboola.com \
--token YOUR_STORAGE_TOKEN
--token-stdin

# Create the deployment shell + Storage config in one shot.
kbagent --json data-app create \
Expand Down
10 changes: 8 additions & 2 deletions docs/auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ token (`kbc_rt_*`) that kbagent renews for you (since v0.80.0).
> hand the plain command to the user's own terminal instead.
>
> For CI, containers, cron, or any other unattended context, you have two
> options: a **static Storage token** (`kbagent project add --token ...`, or
> options: a **static Storage token** (`kbagent project add --token-env NAME`, or
> the token-only `KBAGENT_PROJECT_FROM_ENV=1` + `KBC_TOKEN` +
> `KBC_STORAGE_API_URL` path -- unaffected by anything on this page), or
> **`kbagent auth login-password`** (since v0.84.0) if you specifically need a
Expand Down Expand Up @@ -433,9 +433,15 @@ somewhere sessions do not reach — a scheduled agent, a CI step, the importable
SDK. Give that project a static token explicitly:

```bash
kbagent project edit --project my-project-9840 --token YOUR_STATIC_TOKEN
kbagent project edit --project my-project-9840 --token-stdin
```

`--token-stdin` shows a hidden prompt on a terminal, and reads a pipe
otherwise, so the token never reaches your shell history. The other ways in
are `--token-file PATH` (kbagent deletes the file once the edit succeeds) and
`--token-env NAME` for CI. `--token VALUE` still works, but it puts the token
in your shell history and in a process listing.

This is allowed and it warns while doing it: the project becomes a
static-token project, so `kbagent auth logout --remove-projects` will no longer
clean it up — remove it with `kbagent project remove` when you are done.
Expand Down
2 changes: 1 addition & 1 deletion docs/demos/demo-add-project.tape
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Enter
Sleep 200ms
Type "# kbagent project add --project prod \\"
Enter
Type "# --url https://connection.keboola.com --token <YOUR_TOKEN>"
Type "# --url https://connection.keboola.com --token-stdin"
Enter
Sleep 200ms
Type "# After it completes, the project appears in the list:"
Expand Down
2 changes: 1 addition & 1 deletion docs/use-cases.md
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ uv pip install keboola-cli
# Add your first project
kbagent project add --project analytics \
--url https://connection.keboola.com \
--token YOUR_STORAGE_TOKEN
--token-stdin

# The name you give here is used in all other commands: --project analytics
# If you omit --project, read commands query ALL connected projects.
Expand Down
2 changes: 1 addition & 1 deletion docs/web-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,7 @@ a session-backed project from the web UI at all:
schedule; it cannot be revoked from this CLI afterwards.

For a project you would rather not expose this way, register it with a static
Storage token (`kbagent project add --token`) — that path has neither property.
Storage token (`kbagent project add --token-stdin`) — that path has neither property.

### `/auth/*` — three read/audit endpoints, three deliberate gaps

Expand Down
2 changes: 1 addition & 1 deletion install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ printf ' %s(needs the plugin:%s %skbagent doctor%s %sprints the exact /plugin
info ""
printf ' %sOr do it by hand%s\n' "$BOLD" "$RESET" >&2
printf ' %skbagent project add%s --project myproject \\\n' "$BOLD" "$RESET" >&2
printf ' --url https://connection.keboola.com --token YOUR_TOKEN %s# connect a project%s\n' "$DIM" "$RESET" >&2
printf ' --url https://connection.keboola.com --token-stdin %s# connect a project%s\n' "$DIM" "$RESET" >&2
printf ' %skbagent --help%s %s# see everything you can do%s\n' "$BOLD" "$RESET" "$DIM" "$RESET" >&2
printf ' %skbagent doctor%s %s# verify your setup%s\n' "$BOLD" "$RESET" "$DIM" "$RESET" >&2
info ""
Expand Down
5 changes: 3 additions & 2 deletions plugins/kbagent/agents/keboola-expert.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ a critical failure.
`kbagent --json version` carries `kbagent.install_channel`, quote its
`upgrade_command` (or `upgrade_hint` when that is empty) instead.
`auth` needs **0.80.0+**; `login-password` needs **0.84.0+** -- else
refuse and point at a static Storage token (`project add --token`).
refuse and point at a static Storage token (`project add --token-stdin`).

7. **ALWAYS USE `--json`**. Every `kbagent` invocation MUST have
`--json` as the first flag after `kbagent`. This makes output
Expand Down Expand Up @@ -146,6 +146,7 @@ been retired, so its absence is NOT a promise (see §1 Rule 6).
| Pre-flight a data-app repo, or inspect an existing app's repo | `kbagent data-app validate-repo --git-repo URL --type python-js` BEFORE `create` (BLOCKING/WARN/OK, <=5 GitHub API calls); `data-app git-repo` afterwards for clone-URL introspection | `config detail --component-id keboola.data-apps` then read `parameters.dataApp.git` (Storage view only) | `data-app create --dry-run` as a repo check (it only echoes request bodies); `git-repo` on a never-deployed app (409); `git-credentials-create` on an external repo (409 -- managed only) |
| Developer Portal: register / inspect / update a component | reads `kbagent dev-portal list\|get` (agent-safe); writes `create\|patch\|upload-icon\|publish\|deprecate` need a HUMAN to type a random code on a real TTY -- `--dry-run` is the agent-safe preview | `kbagent serve` `GET /dev-portal/apps` for reads | raw `apps-api.keboola.com`; ANY write from a non-TTY/agent shell (no bypass; exits 6) |
| Manage project members & invitations | `kbagent project invite --email E --role admin\|guest\|readOnly\|share`, `invite --from-csv FILE [--workers N]` for bulk, `member-list [--include-pending]`, `invitation-list`, `invitation-cancel`, `member-remove --yes` (**destructive**), `member-set-role`. See [member-workflow.md](../skills/kbagent/references/member-workflow.md) | `--invitation-id ID` when an email lookup is ambiguous | a per-row shell loop over `invite` (defeats the service's parallelism + idempotency); `member-remove` without `--yes` non-interactively (prompts and hangs); reading `config.json` to infer membership |
| Give a project a Storage token (register it, or replace the one it has) | `kbagent project add|edit --project P --token-stdin` -- hidden prompt on a TTY, reads a pipe otherwise. If YOU must run it, have the person write the token to a file and pass `--token-file PATH`: kbagent reads it and deletes it once the command succeeds. In CI, `--token-env NAME` | `--token VALUE`, which lands in the shell history, the REPL history file and a process listing -- kbagent warns when you do it | telling a person to type `--token <the token>`; `project edit` does NOT read `KBC_TOKEN` (it would repoint the alias at another project on a plain `--new-alias`) |
| Rename a project alias | `kbagent project edit --project OLD --new-alias NEW [--dry-run]` -- cascades through `config.json` (`projects` + `default_project`) and the nested sync directory. **Any lineage cache is NOT updated**: re-run `kbagent lineage build` after | `project remove` + `project add` (re-enters the token; loses the nested sync workspace) | hand-editing `config.json` (no validation, easy to miss the `default_project` cascade) |
| Run kbagent headless from a daemon / container / CI with only a token in env | export `KBAGENT_PROJECT_FROM_ENV=1` + `KBC_TOKEN` + `KBC_STORAGE_API_URL`, then use `--project __env__` -- in-memory project, token NEVER persisted; same env also powers `kbagent serve` | a one-shot `project add` (works, but writes the token to `config.json`) | hand-writing a `config.json` with the token; passing `--token` per command (no such passthrough) |
| Call the running `kbagent serve` from a scheduled-agent subprocess | `kbagent http get/post/patch/delete <PATH>` -- uses the `KBAGENT_SERVE_URL` + `KBAGENT_SERVE_TOKEN` the scheduler injects; `http get /openapi.json` discovers endpoints | forking `kbagent <command>` (also fine -- `KBAGENT_CONFIG_DIR` is propagated) | `curl $KBAGENT_SERVE_URL/...` by hand (loses the auth header, error mapping and JSON formatting `kbagent http` adds) |
Expand All @@ -155,7 +156,7 @@ been retired, so its absence is NOT a promise (see §1 Rule 6).
| ANY semantic-layer write (add / edit / remove / import / promote / build / scope add|remove|set|request-create) | `kbagent semantic-layer export` FIRST (the metastore has no soft-delete and no version history -- the snapshot is the only restore path), then the write, `--dry-run` where offered. Needs a token whose Storage Admin role is `admin` on the project (a master token qualifies, but so does any project-admin user token as of PSGO-282) -- a non-admin token still gets `MISSING_MASTER_TOKEN`/403 on writes, just not on reads. `Read` [semantic-layer-workflow.md](../skills/kbagent/references/semantic-layer-workflow.md) before starting: it carries the per-verb recipes, the rename cascade and the promote classification | `semantic-layer diff` (`--project-a/-b` or `--file-a/-b`) to confirm what a write would change | raw metastore REST (no rollback, no orphan scan, no modelUUID rewrite); a write with no export taken |
| Share a semantic-layer object across projects, or make it org-wide | **ASK THE USER which project(s) before EVER passing `--scope organization\|targeted` (on `model create`/`add <kind>`) or running `scope set --scope organization`/`scope request-create` -- never guess or default to widening visibility.** Once the user names the target(s): `--scope targeted --target-project ALIAS\|ID` grants specific projects; `scope add\|remove --project P --type T --context-id ID --target-project ALIAS\|ID` adjusts an existing item's grants from the owning project (a merge), `scope set --target-project ...` / `--clear` replaces the list; org-wide needs `scope request-create` then an org-admin's `scope set --scope organization --yes` (preview with `--dry-run`) -- **irreversible, no downgrade endpoint, destructive-class**. `add <kind>` without `--scope` inherits its model's scope. `scope get --type T --context-id ID` shows the current state first. See [metastore-scope-workflow.md](../skills/kbagent/references/metastore-scope-workflow.md) | `scope request-list --type T` to find items an org-admin should act on | passing `--scope organization\|targeted` or running `scope set --scope organization` without the user having named the target project(s) first; assuming `--scope organization` succeeds on a non-admin token (it 403s -- creating directly at organization scope needs the SAME organization-admin role as elevating); forgetting that `import`/`promote`/`build --model` into a shared model also share every new item (they take the model's scope unless `import`/`promote` get `--scope`); trying to elevate an item created before 0.97.0 (schema version 1.0.0: recreate it via export, delete, create, import) |
| User asks to "log in" / authenticate via browser / register a session's projects | ATTENDED session + a background shell: run `kbagent auth login --device-code --stack URL --register-projects` in a **BACKGROUND** shell (capture stdout+stderr, human mode -- `--json` puts the panel on stderr), relay the verification URL + user code to the user, then poll `kbagent --json auth status` (exit 0 = signed in, exit 3 = not yet). The human's part is approving in the browser, not typing the command. No background shell -> hand the plain `auth login --stack URL --register-projects` to the user's terminal. To register projects from an EXISTING session, `kbagent auth register-projects --all` or `--project-id ID` is non-interactive and agent-safe | -- | running `auth login` in a FOREGROUND tool shell (~120 s timeout kills it mid-flight); running it unattended (nobody can approve); re-running it blind without checking `auth status` first (orphans a session); the flagless `register-projects` picker unattended; reading the token out of `auth.json`; using a numeric project id as an alias |
| User has NO Keboola account / project at all and asks to get started | `kbagent project create --url URL [--project ALIAS] [--name NAME] [--backend snowflake\|bigquery]` (0.95.0+) -- provisions a real project, stores its session and registers the alias in one call, agent-runnable. **Then relay the result's `confirm_url` to the human verbatim and say the project is owned by nobody until they open it**; after they confirm, the agent session is revoked by design -> `kbagent auth login --stack URL` | the user creating the project in the Keboola UI, then `auth login` / `project add --token` | calling it on a stack where a session already exists (exit 5 -- one session per stack); reporting success without the confirm link (an unclaimed project is a billable orphan); retrying it after a 5xx/429/503 without being asked (each success creates a new organization + project + credit grant) |
| User has NO Keboola account / project at all and asks to get started | `kbagent project create --url URL [--project ALIAS] [--name NAME] [--backend snowflake\|bigquery]` (0.95.0+) -- provisions a real project, stores its session and registers the alias in one call, agent-runnable. **Then relay the result's `confirm_url` to the human verbatim and say the project is owned by nobody until they open it**; after they confirm, the agent session is revoked by design -> `kbagent auth login --stack URL` | the user creating the project in the Keboola UI, then `auth login` / `project add --token-stdin` | calling it on a stack where a session already exists (exit 5 -- one session per stack); reporting success without the confirm link (an unclaimed project is a billable orphan); retrying it after a 5xx/429/503 without being asked (each success creates a new organization + project + credit grant) |
| CI task has account credentials | `kbagent auth login-password --email E (--password-stdin \| --password P) [--totp-secret SEED]` (0.84.0+), agent-runnable | a static Storage token | `auth login` unattended |

If the table does not cover the user's task, **ask clarifying
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Why every step uses `KBAGENT_PROJECT_FROM_ENV` / `__env__`

kbagent's normal mode of operation is a **registered project**: `kbagent
project add --project ALIAS --url URL --token TOKEN` writes the token into
project add --project ALIAS --url URL --token-stdin` writes the token into
`~/.config/keboola-agent-cli/config.json`, and every later command references
that alias. That's the right model for a developer's own machine, but wrong
for CI: it means a token would have to be written to disk (or the config
Expand Down
5 changes: 3 additions & 2 deletions plugins/kbagent/skills/kbagent/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -565,8 +565,9 @@ KBC_LOGIN_EMAIL=... KBC_LOGIN_PASSWORD=... KBC_LOGIN_TOTP_SECRET=... \
credentials:

```bash
# Single project
kbagent --json project add --project prod --url https://connection.keboola.com --token YOUR_TOKEN
# Single project. The token goes in on stdin, never as a --token value (that lands in the
# shell history). The person runs it and types the token at the hidden prompt; a script pipes it in:
printf '%s' "$KBC_TOKEN" | kbagent --json project add --project prod --url https://connection.keboola.com --token-stdin

# Or bulk-onboard from organization (org admin)
# Manage token: interactive prompt by default; for CI add --allow-env-manage-token
Expand Down
6 changes: 3 additions & 3 deletions plugins/kbagent/skills/kbagent/references/auth-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ For CI, containers, or any other unattended context there are two options
(since v0.84.0): if the task has account email + password (+ a TOTP seed for
MFA), use `kbagent auth login-password` -- see "Unattended login" below, an
agent MAY run it directly. Otherwise keep using a static Storage token
(`kbagent project add --token ...` or `KBAGENT_PROJECT_FROM_ENV`) -- that
(`kbagent project add --token-stdin` or `KBAGENT_PROJECT_FROM_ENV`) -- that
path is unchanged by either feature.

## No Keboola account at all: `project create`
Expand Down Expand Up @@ -362,7 +362,7 @@ If your workflow needs one of those, register the same project again under
a different alias with a static Storage token:

```
kbagent project add --project my-project-static --url <stack> --token <token>
kbagent project add --project my-project-static --url <stack> --token-stdin
```

The two aliases can coexist; only the sentinel-token one is guarded.
Expand Down Expand Up @@ -438,7 +438,7 @@ differently on purpose:
access token rotates on its own -- so the project is reported under
`skipped` with that reason rather than raising. `--force` does not convert
it either.
- **`project edit --token` converts it, with a warning.** This is the
- **`project edit --token-stdin` converts it, with a warning.** This is the
supported deliberate conversion. Once done the alias is a static-token
project, so `auth logout --remove-projects` no longer cleans it up -- use
`kbagent project remove`. The warning is identical under `--dry-run`, and
Expand Down
Loading
Loading