diff --git a/CLAUDE.md b/CLAUDE.md index 34d25987..213843a0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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; diff --git a/plugins/kbagent/.claude-plugin/CLAUDE.md b/plugins/kbagent/.claude-plugin/CLAUDE.md index 1df0fd73..3d4fa41c 100644 --- a/plugins/kbagent/.claude-plugin/CLAUDE.md +++ b/plugins/kbagent/.claude-plugin/CLAUDE.md @@ -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. diff --git a/plugins/kbagent/agents/keboola-expert.md b/plugins/kbagent/agents/keboola-expert.md index fd58b9cf..3cb236a3 100644 --- a/plugins/kbagent/agents/keboola-expert.md +++ b/plugins/kbagent/agents/keboola-expert.md @@ -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 @@ -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": }` 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": ""}'` (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 @@ -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`). diff --git a/plugins/kbagent/skills/kbagent/references/auth-workflow.md b/plugins/kbagent/skills/kbagent/references/auth-workflow.md index de30a99f..af8fd2d7 100644 --- a/plugins/kbagent/skills/kbagent/references/auth-workflow.md +++ b/plugins/kbagent/skills/kbagent/references/auth-workflow.md @@ -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 @@ -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 @@ -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 -- @@ -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" @@ -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 diff --git a/plugins/kbagent/skills/kbagent/references/gotchas.md b/plugins/kbagent/skills/kbagent/references/gotchas.md index 9a6f3904..905cfeb2 100644 --- a/plugins/kbagent/skills/kbagent/references/gotchas.md +++ b/plugins/kbagent/skills/kbagent/references/gotchas.md @@ -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.** @@ -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. @@ -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 | @@ -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`) | diff --git a/src/keboola_agent_cli/changelog.py b/src/keboola_agent_cli/changelog.py index 86390934..879907de 100644 --- a/src/keboola_agent_cli/changelog.py +++ b/src/keboola_agent_cli/changelog.py @@ -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` " "(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 " @@ -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 " diff --git a/tests/test_changelog_render.py b/tests/test_changelog_render.py index cf1f0320..fe9f6836 100644 --- a/tests/test_changelog_render.py +++ b/tests/test_changelog_render.py @@ -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, @@ -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) + )