From a5093e2ffda5023f4bdbfee41415427bacefc332 Mon Sep 17 00:00:00 2001 From: Petr Date: Fri, 14 Aug 2026 12:12:25 +0200 Subject: [PATCH 1/2] fix(changelog): stop two 0.84.0 notes from being shown cut off mid-clause MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each note's first sentence becomes its one-line summary in `kbagent changelog` and in the post-update "What's new" banner, capped at 160 chars. Two 0.84.0 notes opened with a sentence longer than that, so the summary broke off before the point of the change -- the promotion-pipeline note ended at "from a SOURCE project (dev) to a …", losing the destination and the PR gate, and the login-password note spent its whole budget on the flag signature. Both now lead with a short, self-contained sentence and carry the detail in later ones. No information is dropped; only the order changes. A test guards the newest version's notes, and only those: roughly 40% of the historical entries are cut the same way, and rewriting already-published release notes to satisfy a test is not worth it. The entries an author can still fix are the ones worth guarding, and nothing else checks this -- every other test treats a note as an opaque string. --- src/keboola_agent_cli/changelog.py | 16 +++++++------ tests/test_changelog_render.py | 38 +++++++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 8 deletions(-) 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) + ) From 1633b8497cb1ac27e9eebe4b05a2057177dcd8f2 Mon Sep 17 00:00:00 2001 From: Petr Date: Fri, 14 Aug 2026 12:26:10 +0200 Subject: [PATCH 2/2] docs(auth): correct login-password's minimum version from 0.81.0 to 0.84.0 The command is not in any released version. It merged after v0.82.0 was cut and ships in 0.84.0, but fifteen places across CLAUDE.md, the agent prompt, the plugin CLAUDE.md and the skill references advertised it as 0.81.0+. That is the failure convention #17 exists to prevent: the agent prompt's VERSION GATE reads those numbers to decide whether a command is available, so a user on 0.81.0 or 0.82.0 would be told to run something their install does not have -- and the gate is meant to be the thing that stops exactly that. Verified against the tag rather than the changelog: `git grep login_password v0.82.0 -- src/` finds nothing, while main has it in six files. The mcp_tool_tasks doctor check, which also claims 0.81.0, is left alone -- it is present at v0.82.0 (three files), so its number is right. --- CLAUDE.md | 2 +- plugins/kbagent/.claude-plugin/CLAUDE.md | 2 +- plugins/kbagent/agents/keboola-expert.md | 6 +++--- .../skills/kbagent/references/auth-workflow.md | 12 ++++++------ plugins/kbagent/skills/kbagent/references/gotchas.md | 8 ++++---- 5 files changed, 15 insertions(+), 15 deletions(-) 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`) |