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.md
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ kbagent auth login-password --email EMAIL (--password PASSWORD | --password-stdi
kbagent auth status [--stack URL|alias]
kbagent auth logout [--stack URL|alias] [--remove-projects] [--yes]
kbagent auth register-projects [--stack URL|alias] [--all] [--project-id ID ...] [--alias ID=ALIAS ...] [--yes]
# auth login-password (0.81.0+): the deliberate unattended exception to auth login's "needs a human at
# auth login-password (0.84.0+): the deliberate unattended exception to auth login's "needs a human at
# a browser" rule -- email + password (+ TOTP if the account has MFA) grant, no browser, safe to run
# from a CI secret-backed workflow step. Prefer --password-stdin (or KBC_LOGIN_PASSWORD) over
# --password -- a value on the command line lands in shell history and process listings;
Expand Down
2 changes: 1 addition & 1 deletion plugins/kbagent/.claude-plugin/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ a clean slate per task.
back to the user and wait. For an unattended context, the answer is
NOT automatically a static Storage token: if the user has account
credentials for this purpose, `kbagent auth login-password`
(0.81.0+) is the CI-safe, headless alternative and an agent MAY run
(0.84.0+) is the CI-safe, headless alternative and an agent MAY run
it directly; fall back to a static Storage token only when no such
credentials exist.

Expand Down
6 changes: 3 additions & 3 deletions plugins/kbagent/agents/keboola-expert.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ a critical failure.
**Standalone binaries do not take `kbagent update`** -- if
`kbagent --json version` carries `kbagent.install_channel`, quote its
`upgrade_command` (or `upgrade_hint` when that is empty) instead (0.79.0+).
`auth` needs **0.80.0+**; `login-password` needs **0.81.0+** -- else
`auth` needs **0.80.0+**; `login-password` needs **0.84.0+** -- else
refuse and point at a static Storage token (`project add --token`).

7. **ALWAYS USE `--json`**. Every `kbagent` invocation MUST have
Expand Down Expand Up @@ -152,7 +152,7 @@ a critical failure.
| Bootstrap a model from a set of storage tables | `kbagent semantic-layer build --project P --tables T1,T2,... [--dry-run] [--keep-on-failure]` (0.41.0+) -- **HEURISTIC fallback only** (no AI Service JSON endpoint): synthesises one dataset + one COUNT(*) metric + one glossary entry per table; FQN derived; fields[] role-classified. Response carries `fallback_used: "heuristic"`. Use as a SCAFFOLD, then refine via `add` / `edit`. Rollback on push failure (0.41.10+): every successfully-POSTed child is DELETEd in reverse + model deleted if we created it; pass `--keep-on-failure` to preserve partial state | the `sl-build` skill in `04_AI_Kit/ai-kit` -- full AI-assisted greenfield wizard, schema discovery + SQL analysis + AI generation. Use this when you need richer metrics, relationships, and constraint shapes than the heuristic produces | hand-writing the model JSON from scratch (the `build` heuristic gets you 80% of the way for read-mostly star schemas; only fall back to manual when the heuristic refuses or you need something the skill produces) |
| Encrypt the storage token for a transformation `user_properties` (so a Python container can reach the metastore) | `kbagent semantic-layer token --encrypt --project P --component-id C` (0.41.0+) -- builds `{"#metastore_token": <token>}` from the project's already-stored Storage token and delegates to the existing EncryptService; output is the encrypted envelope ready to paste into the transformation's `user_properties` block | `kbagent encrypt values --project P --component-id C --input '{"#metastore_token": "<plaintext>"}'` (works but the operator has to manually fetch the token first -- the wrapper avoids that step) | hand-running the Encryption API and pasting plaintext into `user_properties` (no `#` prefix means it sits in the config in plaintext) |
| User asks to "log in" / "authenticate via browser" / set up programmatic auth, or to register a session's projects as aliases | **DO NOT RUN `kbagent auth login` YOURSELF** -- needs a human at the keyboard, no headless path. Tell the user to run `kbagent auth login [--register-projects]` themselves, then continue with `kbagent auth status`. To register projects from an EXISTING session (no re-login), `kbagent auth register-projects --all` or `--project-id ID` (0.80.0+) is non-interactive and agent-safe | -- | attempting `auth login`/the flagless `register-projects` picker from an unattended task; reading the token out of `auth.json`; using the numeric project id as an alias (aliases come from the project NAME) |
| CI task has account creds | `kbagent auth login-password --email E (--password-stdin\|--password P) [--totp-secret SEED]` (0.81.0+), agent-runnable | static token | `auth login` unattended |
| CI task has account creds | `kbagent auth login-password --email E (--password-stdin\|--password P) [--totp-secret SEED]` (0.84.0+), agent-runnable | static token | `auth login` unattended |

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 @@ -332,7 +332,7 @@ read it when a trigger fires. Each `(X.Y.Z+)` tag is the version floor.
- `auth login` is **human-only** -- it opens a browser or prints an RFC 8628
device code; never run it from an unattended agent task. Ask the user to
run it themselves, then use `auth status`/`auth logout` normally.
- **`auth login-password` (0.81.0+) IS the headless path** -- email +
- **`auth login-password` (0.84.0+) IS the headless path** -- email +
password (+ TOTP seed), agent-runnable. WebAuthn-only -> `AUTH_MFA_INVALID`,
fall back to `auth login`. MFA accounts get a live 3h sudo window
(`docs/auth.md`).
Expand Down
12 changes: 6 additions & 6 deletions plugins/kbagent/skills/kbagent/references/auth-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

> Audience: a human user of kbagent (or an agent relaying instructions to
> one) who wants to authenticate via a browser instead of pasting a static
> Storage API token -- or, since v0.81.0, an agent running unattended with
> Storage API token -- or, since v0.84.0, an agent running unattended with
> real account credentials for CI. Goal: sign in once, understand what got
> stored where, and know how to check on / tear down the session later.
> Since v0.80.0 (browser login), v0.81.0 (unattended `login-password`).
> Since v0.80.0 (browser login), v0.84.0 (unattended `login-password`).
> Full command reference: `commands-reference.md` > "Programmatic Auth
> (Browser Login)". Gotchas: `gotchas.md` > "Programmatic auth (browser
> login) is human-only; sentinel tokens; v1 scope" and > "`auth
Expand All @@ -30,7 +30,7 @@ Then let me know once it's done and I'll continue with `kbagent auth status`.
```

For CI, containers, or any other unattended context there are now two
options (since v0.81.0): if the task has account email + password (+ a TOTP
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
Expand Down Expand Up @@ -75,7 +75,7 @@ is still chosen the normal way (`--project`, `KBAGENT_PROJECT`, the pinned
default) -- the session just supplies the credential, and the CLI adds
`X-KBC-ProjectId` per request.

## Unattended login: `auth login-password` (since v0.81.0)
## Unattended login: `auth login-password` (since v0.84.0)

The CI-safe counterpart to `login` above: never opens a browser, completes
entirely over HTTP, and is safe to run from a secret-backed workflow step --
Expand Down Expand Up @@ -196,7 +196,7 @@ that alias rather than offered a second, colliding suggestion.
kbagent auth login --register-projects

# 1-CI. The unattended equivalent, given real account credentials
# (since v0.81.0) -- no browser, safe from a secret-backed step:
# (since v0.84.0) -- no browser, safe from a secret-backed step:
kbagent auth login-password --email "$CI_EMAIL" --password-stdin \
--totp-secret "$CI_TOTP_SEED" --register-projects <<< "$CI_PASSWORD"

Expand Down Expand Up @@ -359,7 +359,7 @@ differently on purpose:

- It does not replace static Storage tokens -- both coexist indefinitely.
Static tokens remain a supported path for CI/CD, containers, and any other
unattended context; `auth login-password` (since v0.81.0) is the other one
unattended context; `auth login-password` (since v0.84.0) is the other one
when the task has account credentials rather than a token.
- It does not manage Manage-API super-admin credentials (`feature`,
`org setup`, member administration) -- those keep demanding the existing
Expand Down
8 changes: 4 additions & 4 deletions plugins/kbagent/skills/kbagent/references/gotchas.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Versioning convention:
Never invoke it from an unattended AI agent task; if a user asks an agent
to "log in", the agent must tell the user to run `auth login` themselves in
their own terminal. Session tokens are deliberately not readable through
the CLI once issued. **`auth login-password` (since v0.81.0, below) is the
the CLI once issued. **`auth login-password` (since v0.84.0, below) is the
headless counterpart** -- it did not exist when this rule was written and
does not fall under it.
- **PKCE is the default; the device flow is a fallback, not a mode switch.**
Expand Down Expand Up @@ -167,7 +167,7 @@ Versioning convention:
- **Tokens are plaintext in `auth.json` (0600), a sibling of `config.json`.**
Deliberate RFC 8628 deviation, same posture as the static tokens already in
`config.json` (see `docs/programmatic-auth-login-plan.md` section 4.2). CI
and any headless/unattended runner has two options since v0.81.0: `auth
and any headless/unattended runner has two options since v0.84.0: `auth
login-password` (below) if account credentials for this purpose exist, or
a static Storage token otherwise -- browser login (`auth login` itself)
still has no non-interactive path by design.
Expand All @@ -177,7 +177,7 @@ Versioning convention:
- See `auth-workflow.md` for the end-to-end login -> register -> status ->
logout walkthrough and PKCE-vs-device troubleshooting.

## `auth login-password` is the CI-safe, headless exception to "browser login is human-only" (since v0.81.0)
## `auth login-password` is the CI-safe, headless exception to "browser login is human-only" (since v0.84.0)

- **Password-grant login, no browser, safe for an unattended agent task**:
`kbagent auth login-password --email E (--password-stdin | --password P |
Expand Down Expand Up @@ -1829,7 +1829,7 @@ unknown -- do not try to parse a fallback message.
| 0 | Success |
| 1 | General error |
| 2 | Usage error (invalid arguments) |
| 3 | Authentication error (invalid or expired token) -- includes `SESSION_EXPIRED` / `SESSION_NOT_FOUND` / `AUTH_FLOW_DENIED`, whose remedy is `kbagent auth login` (since v0.80.0), and `AUTH_MFA_INVALID` -- `auth login-password` cannot resolve this account's MFA type (WebAuthn/passkey-only), whose remedy is `kbagent auth login` instead (since v0.81.0) |
| 3 | Authentication error (invalid or expired token) -- includes `SESSION_EXPIRED` / `SESSION_NOT_FOUND` / `AUTH_FLOW_DENIED`, whose remedy is `kbagent auth login` (since v0.80.0), and `AUTH_MFA_INVALID` -- `auth login-password` cannot resolve this account's MFA type (WebAuthn/passkey-only), whose remedy is `kbagent auth login` instead (since v0.84.0) |
| 4 | Network error (timeout, unreachable) -- includes `QUEUE_JOB_TIMEOUT` (local gave up AND the remote-kill attempt failed; the remote job may still be running), `AUTH_FLOW_TIMEOUT`, and a session refresh that timed out or could not reach the auth service (`TIMEOUT` / `CONNECTION_ERROR`; a slow auth service is NOT a dead login -- re-run, do not re-login) (since v0.80.0) |
| 5 | Configuration error (corrupt config, missing alias) |
| 6 | Permission denied (blocked by firewall / `--deny-writes` / `--deny-destructive`) |
Expand Down
16 changes: 9 additions & 7 deletions src/keboola_agent_cli/changelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@
# Ordered newest-first. Each value is a list of brief one-line descriptions.
CHANGELOG: dict[str, list[str]] = {
"0.84.0": [
"New: `kbagent auth login-password --email EMAIL (--password PASSWORD | "
"--password-stdin) [--totp-secret SECRET]` -- the deliberate unattended exception "
'to `auth login`\'s "needs a human at a browser" rule. A password grant (no '
"browser), safe to run from a CI secret-backed workflow step. `--password-stdin` "
"New: `kbagent auth login-password` signs in unattended with an email and password "
"(plus TOTP), no browser needed. Full form: `--email EMAIL (--password PASSWORD | "
"--password-stdin) [--totp-secret SECRET]` -- the deliberate exception "
'to `auth login`\'s "needs a human at a browser" rule, safe to run from a CI '
"secret-backed workflow step. `--password-stdin` "
Comment thread
padak marked this conversation as resolved.
"(hidden prompt on a TTY, reads to EOF on a pipe) is the recommended way to supply "
"the password -- `--password` and `--password-stdin` are mutually exclusive. "
"`--email`/`--password`/`--totp-secret` also read from "
Expand All @@ -40,9 +41,10 @@
"`auth login` instead. Stores the resulting session in `auth.json` exactly like "
"`auth login` does -- same downstream command support, same "
"`--register-projects` contract.",
"New: a `kbagent-promotion-pipeline` skill that generates a from-scratch GitHub "
"Actions setup promoting configurations from a SOURCE project (dev) to a "
"DESTINATION project (prod), with a human-reviewed PR gate in between. `sync` "
"New: a `kbagent-promotion-pipeline` skill that scaffolds a PR-gated dev -> prod "
"promotion pipeline in GitHub Actions. It generates the setup from scratch, "
"promoting configurations from a SOURCE project to a DESTINATION project with a "
"human-reviewed PR gate in between. `sync` "
"targets one registered alias per invocation -- it has no 'this git branch is bound "
"to that project' magic -- so the skill builds the promotion loop out of that "
"primitive: a pull workflow syncs the source project into a scratch directory and "
Expand Down
38 changes: 37 additions & 1 deletion tests/test_changelog_render.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from rich.console import Console
from typer.testing import CliRunner

from keboola_agent_cli.changelog import format_whats_new, headline
from keboola_agent_cli.changelog import CHANGELOG, format_whats_new, headline
from keboola_agent_cli.cli import app
from keboola_agent_cli.commands.changelog import (
_PREFIX_RE,
Expand Down Expand Up @@ -168,3 +168,39 @@ def test_json_payload_has_no_full_key(self, monkeypatch) -> None:
assert result.exit_code == 0, result.output
data = json.loads(result.output)["data"]
assert list(data.keys()) == ["entries"]


class TestLiveChangelogHeadlines:
"""The real ``CHANGELOG``, unlike the renderer tests above.

Those drive synthetic entries on purpose, so they stay green as release
notes change. This one is deliberately the opposite: it checks the
authoring contract the module docstring states -- every note must lead with
a self-contained first sentence, because that sentence is what
``kbagent changelog`` and the post-update "What's new" banner show.

A first sentence over the cap is not merely shortened; the cut lands
wherever the character budget runs out, which is typically mid-clause and
before the point of the change. Two 0.84.0 notes shipped to main that way
-- "... from a SOURCE project (dev) to a …" -- and nothing failed, because
every other check treats a note as an opaque string.

Scope is the newest version only -- the one being written right now, whose
notes are still editable. Roughly 40% of the historical entries are cut the
same way; rewriting already-published release notes to satisfy a test is
not worth it, so this guards the entries an author can still fix.
"""

def test_newest_release_notes_are_not_truncated(self) -> None:
version = next(iter(CHANGELOG))
truncated = [
headline(note) for note in CHANGELOG[version] if headline(note).rstrip().endswith("…")
]

assert not truncated, (
f"These v{version} notes' first sentence exceeds "
f"{CHANGELOG_HEADLINE_MAX_CHARS} chars, so `kbagent changelog` and the "
'"What\'s new" banner show it cut off mid-clause. Lead with a short, '
"self-contained sentence and move the detail into later sentences:\n "
+ "\n ".join(truncated)
)