From 52b3b8e56981ee91097bb26e41148868300a19b6 Mon Sep 17 00:00:00 2001 From: Petr Date: Sun, 23 Aug 2026 23:16:59 +0200 Subject: [PATCH 1/2] chore(release): 0.90.0 Bumps pyproject.toml to 0.90.0 and adds the changelog entry covering every PR merged since v0.89.0 (#658, #662, #661, #663, #665, #666, #664, #668, #667, #623), resolves the vNEXT placeholders those PRs left behind, and adds the curated What's new reel for the release. --- .claude-plugin/marketplace.json | 2 +- CLAUDE.md | 4 +- docs/web-server.md | 8 +- plugins/kbagent/.claude-plugin/plugin.json | 2 +- plugins/kbagent/agents/keboola-expert.md | 8 ++ .../kbagent/references/commands-reference.md | 6 +- .../skills/kbagent/references/gotchas.md | 10 +- pyproject.toml | 2 +- src/keboola_agent_cli/changelog.py | 99 +++++++++++++++++++ src/keboola_agent_cli/commands/context.py | 4 +- uv.lock | 2 +- web/frontend/src/whatsnew.ts | 54 ++++++++++ 12 files changed, 181 insertions(+), 20 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index d18a104f..74407694 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -10,7 +10,7 @@ "plugins": [ { "name": "kbagent", - "version": "0.89.0", + "version": "0.90.0", "source": "./plugins/kbagent", "description": "AI-friendly interface to Keboola Connection projects — explore configs, jobs, lineage, sync configs as files, manage dev branches, and debug SQL in workspaces", "category": "development" diff --git a/CLAUDE.md b/CLAUDE.md index 3d2a021c..cc8c686d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -718,7 +718,7 @@ kbagent data-app git-credentials-create --project NAME --app-id ID --type ssh_ke kbagent component list [--project NAME] [--type TYPE] [--query QUERY] kbagent component detail --component-id ID [--project NAME] -# component detail (since vNEXT): the AI Service indexes the PUBLIC catalog only, so a private/ +# component detail (since 0.90.0): the AI Service indexes the PUBLIC catalog only, so a private/ # deprecated component the project can run (keboola.mcp-server-tool, keboola.data-apps) 404'd # there while `component list` showed it -- over `serve` as an HTTP 502. A NOT_FOUND now falls # back to the project's Storage component catalog; `documentation_source` ("ai_service" vs @@ -965,7 +965,7 @@ kbagent update [--beta] kbagent changelog [--limit N] [--full] # Default shows a one-line summary (first sentence) per version; --full / -v expands every note. kbagent serve [--host HOST] [--port PORT] [--ui] [--ui-dist PATH] [--reload] [--log-level LVL] [--cors-origin ORIGIN] [--config-dir DIR] [--no-banner] -# `--no-banner` (since vNEXT): suppress the web UI's "What's new" popup. The UI shows a curated +# `--no-banner` (since 0.90.0): suppress the web UI's "What's new" popup. The UI shows a curated # per-version highlights modal once per version (localStorage `kbagent.whatsnew.seen`); this flag # turns the UNSOLICITED popup off fleet-wide. Surfaced to the SPA via `GET /ui-config` # ({"banner": bool}) -- NOT injected into index.html: that injection point was removed in favour of diff --git a/docs/web-server.md b/docs/web-server.md index 50af7115..4164b07e 100644 --- a/docs/web-server.md +++ b/docs/web-server.md @@ -99,7 +99,7 @@ else lives here, with their own agents that know their projects. Auto-generated OpenAPI spec at `/openapi.json`, Swagger UI at `/docs`. An upstream Keboola failure surfaces through one global handler: a -`NOT_FOUND` answers **404** (since vNEXT — it used to be 502, which told +`NOT_FOUND` answers **404** (since 0.90.0 — it used to be 502, which told callers to retry a request that can never succeed), an expired/missing browser session answers **401**, and every other `KeboolaApiError` answers **502**. The body is always the `{"status": "error", "error": {"code", "message"}}` @@ -133,7 +133,7 @@ A NERD-themed React SPA that drives the API: - **Command palette** — `Ctrl+K` / `Cmd+K` anywhere: fuzzy jump to any page, switch the active project, toggle the theme, open Swagger `/docs`, reopen **What's new**. Arrows + enter, esc closes. -- **What's new popup** *(since vNEXT)* — a curated per-version highlights +- **What's new popup** *(since 0.90.0)* — a curated per-version highlights modal, shown once per version. See [What's-new popup](#whats-new-popup-since-vnext) below for the curated list's location, the storage key, and the `--no-banner` opt-out. @@ -324,7 +324,7 @@ session cookie, and the auth middleware accepts that cookie whenever no `Authorization` header is present. Scripted callers keep using the header. Two layers keep that cookie from going stale across server restarts -*(since vNEXT)* — previously a restart (new token) could leave a tab that +*(since 0.90.0)* — previously a restart (new token) could leave a tab that reloaded from the browser cache silently 401-ing on every API call, with each list rendering as empty: @@ -338,7 +338,7 @@ each list rendering as empty: `SESSION_NOT_FOUND` the banner carries the server message, which names the on-host `kbagent auth login` remedy). -### What's-new popup *(since vNEXT)* +### What's-new popup *(since 0.90.0)* The web UI shows a curated per-version highlights modal on load, once per version, so features like the command palette get discovered instead of diff --git a/plugins/kbagent/.claude-plugin/plugin.json b/plugins/kbagent/.claude-plugin/plugin.json index 915f12fe..10cf3389 100644 --- a/plugins/kbagent/.claude-plugin/plugin.json +++ b/plugins/kbagent/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "kbagent", - "version": "0.89.0", + "version": "0.90.0", "description": "AI-friendly interface to Keboola Connection projects — explore configs, jobs, lineage, sync configs as files, manage dev branches, and debug SQL in workspaces", "author": { "name": "Keboola", diff --git a/plugins/kbagent/agents/keboola-expert.md b/plugins/kbagent/agents/keboola-expert.md index 4af89f39..fb99e212 100644 --- a/plugins/kbagent/agents/keboola-expert.md +++ b/plugins/kbagent/agents/keboola-expert.md @@ -178,6 +178,14 @@ its absence is NOT a promise the entry is version-independent (see §1 Rule 6). Service-Account component (`keboola.ex-linkedin-ads`, ...) fails with an opaque empty-body 400 -- check the version before blaming the action or the credentials. +- `component detail` on **0.90.0+** falls back to the project's Storage catalog + when the AI Service does not index the component (private/deprecated: + `keboola.mcp-server-tool`, `keboola.data-apps`) -- it used to NOT_FOUND there + while `component list` showed the component. Read `documentation_source` + before concluding anything from counts: on `storage_catalog` the + `examples_count` is always 0 because that source carries no examples, NOT + because the component ships none. Reach for `config examples` or an existing + config in the project instead. **Reading job logs / table usage / narrow config search (0.88.0+)** - `job detail --log-tail-lines N` -- the ONLY route to an already-finished job's diff --git a/plugins/kbagent/skills/kbagent/references/commands-reference.md b/plugins/kbagent/skills/kbagent/references/commands-reference.md index 350f61a5..9f8dd0be 100644 --- a/plugins/kbagent/skills/kbagent/references/commands-reference.md +++ b/plugins/kbagent/skills/kbagent/references/commands-reference.md @@ -119,7 +119,7 @@ Requires a **super-admin** Manage API token (same kind as `org setup`). Same def - `component sync-action ACTION_NAME --component-id ID --project ALIAS (--config-id ID [--row-id ID] | --config-data JSON|@file|-) [--branch ID] [--timeout N]` (since 0.73.0) -- run a synchronous component action (`testConnection`, `getTables`, ...) on the `sync-actions.{stack}` service. `ACTION_NAME` is freeform (component-defined; discover via `component detail` `synchronous_actions`). `--row-id` shallow-merges the row over the root config at TOP level only (row `parameters`/`storage` replace root wholesale -- NOT deep merge; MCP `run_sync_action` parity). `--config-data` sends explicit `configData` verbatim. Response is action-specific pass-through. Ports the `run_sync_action` MCP tool. **Since 0.89.0 (#620)** the ROOT configuration's `authorization` and `runtime` blocks are forwarded into `configData` too -- root only (a `--row-id` never overrides them), and only when non-empty. `authorization.oauth_api.id` is the OAuth broker reference the sync-actions service resolves and decrypts, so on 0.88.0 and earlier every sync action on an OAuth / Service-Account component (`keboola.ex-linkedin-ads`, ...) failed with an opaque empty-body 400. - `config examples --component-id ID [--project NAME] [--row]` (since 0.73.0) -- sample root/row configurations from the AI-service component detail. `--json` emits `{component_id, root_examples, row_examples}`; `--row` limits to row examples. Ports the `get_config_examples` MCP tool. - `component list [--project NAME] [--type TYPE] [--query "text"]` -- list/search components (AI-powered with `--query`) -- `component detail --component-id ID [--project NAME]` -- show component schema, docs URL, examples. **Since vNEXT** a component the AI Service does not index (private/deprecated: `keboola.mcp-server-tool`, `keboola.data-apps`) no longer errors -- it falls back to the project's Storage component catalog. `documentation_source` (`"ai_service"` vs `"storage_catalog"`) is on BOTH paths and tells them apart; the fallback has NO configuration examples (`examples_count`/`row_examples_count` always 0), so read `documentation_source` before treating 0 as "this component ships none". `NOT_FOUND` is still raised when both sources miss. **Since vNEXT** omitting `--project` really does use the first configured project -- on <= 0.89.x it failed with `CONFIG_ERROR: Project 'None' not found` despite the help text, so pass `--project` explicitly there. See `gotchas.md`. +- `component detail --component-id ID [--project NAME]` -- show component schema, docs URL, examples. **Since 0.90.0** a component the AI Service does not index (private/deprecated: `keboola.mcp-server-tool`, `keboola.data-apps`) no longer errors -- it falls back to the project's Storage component catalog. `documentation_source` (`"ai_service"` vs `"storage_catalog"`) is on BOTH paths and tells them apart; the fallback has NO configuration examples (`examples_count`/`row_examples_count` always 0), so read `documentation_source` before treating 0 as "this component ships none". `NOT_FOUND` is still raised when both sources miss. **Since 0.90.0** omitting `--project` really does use the first configured project -- on <= 0.89.x it failed with `CONFIG_ERROR: Project 'None' not found` despite the help text, so pass `--project` explicitly there. See `gotchas.md`. ## Configuration Browsing - `config list [--project NAME] [--component-type TYPE] [--component-id ID] [--branch ID] [--include-rows]` -- list configs across projects (branch-aware). With `--include-rows` each row extends to include the full `configuration` and `rows` body (noticeably larger payload -- use only when the bodies are needed; the summary default covers name/description/component/last_modified/folder) @@ -131,7 +131,7 @@ Requires a **super-admin** Manage API token (same kind as `org setup`). Same def - `config delete --project NAME --component-id ID --config-id ID [--branch ID] [--dry-run]` -- SOFT-delete a configuration into the Storage trash (restorable). Since 0.89.0 it locates the config first and a config already in the trash is NOT deleted again -- the raw API purges permanently on a second DELETE (the retry-after-timeout trap); kbagent reports `already_in_trash` and exits 0 instead. `--dry-run` reports the located state without writing. Undo with `config restore`. Permission class `destructive` - `config restore --project NAME --component-id ID --config-id ID [--branch ID]` *(since v0.89.0)* -- restore a trashed configuration (versions, rows and metadata come back). Only works on a config currently in the trash. Permission class `write` - `config trash-list --project NAME [--component-id ID] [--branch ID]` *(since v0.89.0)* -- list configurations in the trash; each row carries `component_id`, `config_id`, `name`, `version` and `deleted_at`, which is exactly what `config restore` needs. Permission class `read` -- `config new --component-id ID [--project NAME] [--name NAME] [--output-dir DIR] [--push --no-files --description D --configuration JSON|@file|- --configuration-file PATH --no-validate --branch ID --dry-run --allow-plaintext-on-encrypt-failure]` -- **two modes**. **Default (no `--push`)**: scaffold new config from component schema; writes files to `--output-dir` or prints to stdout. **No Storage write calls** -- one AI Service read fetches the component schema/examples (that is what `--project` authenticates even without `--push`). **With `--push`** (0.33.0+, requires `--project` + non-empty `--name`): also POSTs to `/v2/storage/components/{cid}/configs` for a one-shot remote create. `#`-prefixed secrets in the pushed body auto-encrypt via the Encryption API first (fail-closed; since 0.54.0, #378; `--allow-plaintext-on-encrypt-failure` overrides). `--no-files` skips the filesystem step entirely (FIIA-style empty-shell pattern). `--configuration` / `--configuration-file` override the POSTed body (default is `{}`, with validation auto-skipped for the default empty shell). `--dry-run` previews the planned POST + validation result without creating. Since 0.89.0, `--push` + `--output-dir` writes the scaffold WITH `_keboola.config_id` into the subtree of the branch the config was created in (registering the branch in the manifest when missing), so the next `sync push` adopts it instead of duplicating (issue #644); with an explicit body the local file mirrors the pushed encrypted configuration. Schema validation runs by default when an explicit body is given (fail-closed: `ConfigError` exit 5 on mismatch) but skips silently if the AI Service has no schema for the component or returns an error; `--no-validate` opts out. Works for ALL component types including `keboola.snowflake-transformation`. **Since vNEXT** the scaffold mode's optional `--project` really does fall back to the first configured project when omitted -- on <= 0.89.x it failed with `CONFIG_ERROR: Project 'None' not found` (see `gotchas.md`). +- `config new --component-id ID [--project NAME] [--name NAME] [--output-dir DIR] [--push --no-files --description D --configuration JSON|@file|- --configuration-file PATH --no-validate --branch ID --dry-run --allow-plaintext-on-encrypt-failure]` -- **two modes**. **Default (no `--push`)**: scaffold new config from component schema; writes files to `--output-dir` or prints to stdout. **No Storage write calls** -- one AI Service read fetches the component schema/examples (that is what `--project` authenticates even without `--push`). **With `--push`** (0.33.0+, requires `--project` + non-empty `--name`): also POSTs to `/v2/storage/components/{cid}/configs` for a one-shot remote create. `#`-prefixed secrets in the pushed body auto-encrypt via the Encryption API first (fail-closed; since 0.54.0, #378; `--allow-plaintext-on-encrypt-failure` overrides). `--no-files` skips the filesystem step entirely (FIIA-style empty-shell pattern). `--configuration` / `--configuration-file` override the POSTed body (default is `{}`, with validation auto-skipped for the default empty shell). `--dry-run` previews the planned POST + validation result without creating. Since 0.89.0, `--push` + `--output-dir` writes the scaffold WITH `_keboola.config_id` into the subtree of the branch the config was created in (registering the branch in the manifest when missing), so the next `sync push` adopts it instead of duplicating (issue #644); with an explicit body the local file mirrors the pushed encrypted configuration. Schema validation runs by default when an explicit body is given (fail-closed: `ConfigError` exit 5 on mismatch) but skips silently if the AI Service has no schema for the component or returns an error; `--no-validate` opts out. Works for ALL component types including `keboola.snowflake-transformation`. **Since 0.90.0** the scaffold mode's optional `--project` really does fall back to the first configured project when omitted -- on <= 0.89.x it failed with `CONFIG_ERROR: Project 'None' not found` (see `gotchas.md`). - `config clone --project P --component-id ID --config-id ID --name NAME [--target-project P2] [--description D] [--set PATH=VALUE ...] [--secret PATH=VALUE ...] [--branch ID] [--target-branch ID] [--dry-run] [--allow-plaintext-on-encrypt-failure]` (0.84.2+, #587) -- duplicate a configuration **whole**. Reach for this instead of reading `config detail` and rebuilding a body: copying only `configuration["parameters"]` silently drops its siblings (`runtime`, `storage`, `authorization`), and a lost `runtime.parallelism` makes Keboola fall back to `parallelism: 1` -- the reporter's 65-row writer went sequential, 140 min instead of ~60-90, with nothing in any output pointing at it. **Same project** (default): server-side copy via `POST .../configs/{id}/versions/{v}/create`; rows and `KBC::` encrypted values travel with it (verified live). `--set PATH=VALUE` is applied as a follow-up update on the copy, so an override can never be the reason a key went missing. **Cross project** (`--target-project`): reassembled client-side and rows recreated one by one, because encrypted values **cannot** travel -- a Keboola ciphertext is scoped to the project it was encrypted in. Any `KBC::` value makes the clone **fail with exit 5**, listing every path, until re-supplied via `--secret PATH=VALUE` (encrypted in the TARGET project on write). `--dry-run` reports those paths instead of refusing -- run it first to learn what to gather. Storage bucket/table IDs are copied **verbatim, never remapped**; `sync clone` is the command that remaps. - `config variables-set --project NAME --component-id ID --config-id ID --var KEY=VALUE [--var ...] [--replace] [--variables-id ID] [--values-id ID] [--branch ID] [--dry-run] [--allow-plaintext-on-encrypt-failure] [--yes]` -- attach variable values to a config. Auto-creates a sibling `keboola.variables` config + default row on first use and links it via the parent's `runtime.variables_id` / `variables_values_id`. Defaults to merge; `--replace` drops keys not in `--var`. `#`-prefixed values encrypt via the Encryption API (fail-closed; exit non-zero on `ENCRYPTION_FAILED`). See `variables-workflow.md` - `config variables-get --project NAME --component-id ID --config-id ID [--branch ID]` -- resolve `variables_id` + `values_id` from the parent config and fetch the current KEY=VALUE map. Returns `{linked: bool, variables_id, values_id, values}`; `linked=false` means the parent has no variables attached @@ -440,7 +440,7 @@ CLI parity for the `/agents` REST surface. Reads/writes `/agents.jso - `doctor` -- health checks (no `--fix` since v0.85.0 -- it only installed the MCP server). Includes a `sync_secrets` check (since 0.55.0): when run inside a sync working tree (`.keboola/manifest.json`), warns if any in-sync config holds plaintext `#`-secrets (#378); `skip` outside a sync tree. Also an `mcp_tool_tasks` check: FAILs when `agents.json` holds tasks using the removed `mcp_tool` action, listing each task in `details.tasks[]` -- map the tool to its native command via `docs/mcp-migration.md` and recreate the task as `--type cli_command` - `version` -- show the kbagent version and check for kbagent updates (kbagent only since v0.85.0); on a standalone binary it advertises the native channel's upgrade command instead of `kbagent update` (v0.79.0+) - `context` -- full usage instructions for AI agents -- `serve [--host HOST] [--port PORT] [--ui] [--ui-dist PATH] [--reload] [--log-level LVL] [--cors-origin ORIGIN] [--config-dir DIR] [--no-banner]` -- launch the FastAPI HTTP server backing the web UI. `--ui` mounts the built SPA at `/` and authenticates the browser with an HttpOnly `kbagent_session` cookie (nothing is injected into `index.html`). `--no-banner` *(since vNEXT)* suppresses the UI's "What's new" popup -- a curated per-version highlights modal shown once per version, dismissal persisted in localStorage `kbagent.whatsnew.seen`. The SPA reads the switch from `GET /ui-config` -> `{"banner": bool}`; it gates only the UNSOLICITED popup, so the command palette's "What's new" action still opens it on request. Requires the optional `server` extra +- `serve [--host HOST] [--port PORT] [--ui] [--ui-dist PATH] [--reload] [--log-level LVL] [--cors-origin ORIGIN] [--config-dir DIR] [--no-banner]` -- launch the FastAPI HTTP server backing the web UI. `--ui` mounts the built SPA at `/` and authenticates the browser with an HttpOnly `kbagent_session` cookie (nothing is injected into `index.html`). `--no-banner` *(since 0.90.0)* suppresses the UI's "What's new" popup -- a curated per-version highlights modal shown once per version, dismissal persisted in localStorage `kbagent.whatsnew.seen`. The SPA reads the switch from `GET /ui-config` -> `{"banner": bool}`; it gates only the UNSOLICITED popup, so the command palette's "What's new" action still opens it on request. Requires the optional `server` extra ## Global Flags | Flag | Description | diff --git a/plugins/kbagent/skills/kbagent/references/gotchas.md b/plugins/kbagent/skills/kbagent/references/gotchas.md index ff5ba8ee..bc5bb998 100644 --- a/plugins/kbagent/skills/kbagent/references/gotchas.md +++ b/plugins/kbagent/skills/kbagent/references/gotchas.md @@ -4387,7 +4387,7 @@ documented multi-branch pull was enough (issue #649). non-zero `summary.orphaned` as "the manifest is pointing at another branch", not as a per-config problem. -## `component detail` falls back to the Storage catalog for un-indexed components (since vNEXT) +## `component detail` falls back to the Storage catalog for un-indexed components (since 0.90.0) `component detail` reads the AI Service (`/docs/components/{id}`), which indexes the **public** component catalog only. A private or deprecated component the @@ -4402,7 +4402,7 @@ looked like an upstream outage worth retrying. catalog** and returns the same response shape filled from the catalog entry. - **`documentation_source` is the discriminator**: `"ai_service"` (full detail) vs `"storage_catalog"` (fallback). It is present on BOTH paths, so a `--json` - consumer can branch on it without a version check once it is on vNEXT+. + consumer can branch on it without a version check once it is on 0.90.0+. - **The fallback carries no configuration examples.** `examples_count` / `row_examples_count` are always `0` there, and `schema_summary` counts are `0` unless the catalog entry itself ships a `configurationSchema`. Read @@ -4415,18 +4415,18 @@ looked like an upstream outage worth retrying. 404, not 502** (all routers, not just components). Branch on `error.code`, not on the HTTP status alone. -## `component detail` / `config new` without `--project` really uses the first project (since vNEXT) +## `component detail` / `config new` without `--project` really uses the first project (since 0.90.0) `component detail --component-id ID` and `config new --component-id ID` (scaffold-only, no `--push`) have always documented `--project` as optional ("uses first available if not set" / "for AI Service auth; required with ---push"). On every version before vNEXT that promise was broken: omitting the +--push"). On every version before 0.90.0 that promise was broken: omitting the flag failed with `CONFIG_ERROR: Project 'None' not found ...`, because the omitted alias was passed into project resolution as a literal `None` element and took the strict-lookup path instead of the "first available project" fallback (`config examples` already resolved it correctly). -- **vNEXT+**: omitting `--project` resolves to the first configured project; +- **0.90.0+**: omitting `--project` resolves to the first configured project; `component detail`'s `project_alias` reports the alias actually used (never `None`). With NO projects configured at all, the failure is an actionable `CONFIG_ERROR: No projects configured. Use 'kbagent project add' ...`. diff --git a/pyproject.toml b/pyproject.toml index 3b8687b3..f75524f6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "keboola-cli" -version = "0.89.0" +version = "0.90.0" description = "AI-friendly CLI for managing Keboola projects" readme = "README.md" requires-python = ">=3.12" diff --git a/src/keboola_agent_cli/changelog.py b/src/keboola_agent_cli/changelog.py index c2a7b70b..8276b635 100644 --- a/src/keboola_agent_cli/changelog.py +++ b/src/keboola_agent_cli/changelog.py @@ -24,6 +24,105 @@ # Ordered newest-first. Each value is a list of brief one-line descriptions. CHANGELOG: dict[str, list[str]] = { + "0.90.0": [ + "New (#658): the `kbagent serve --ui` web UI caught up with the CLI -- jobs, " + "configs, tokens and storage are now actionable, not just readable. Jobs gained " + "per-row and in-drawer re-run and terminate (terminate is offered only for " + "`created` / `waiting` / `processing`; a re-run starts from the config as it stands " + "now, because the Queue API offers no replay of the historical `configData`). The " + "config detail view became a drawer with a Run job action, a Delete behind a " + "confirm modal, and a Trash tab with per-row Restore (#643). A new Tokens page " + "creates, rotates and revokes scoped Storage tokens, shows the secret once at mint, " + "and offers the opt-in derive-last-used pass with `never` / `unknown` / `error` " + "rendered as distinct states. Storage gained the BigQuery table `definition` panel " + "(#621) and click-to-edit column descriptions (#624). Everything here is wired to " + "serve routes that already existed -- no new Python surface.", + "New (#658, #664): a Ctrl+K / Cmd+K command palette. It jumps to any page, switches " + "the active project, toggles the theme, opens the Swagger docs or the What's new " + "reel -- and finds storage buckets and tables across every registered project. " + "Nothing waits on the network while you type: the three data layers are fetched " + "once on open and matched client-side, deliberately not through `/search` (which " + "needs the `global-search` project feature many projects do not have). Enter on a " + "bucket or table lands on the Storage page with the filter applied and the drawer " + "open, switching the active project first when the object is foreign.", + "New (#665): every UI state is now a shareable link. A hash router encodes page, " + "project, branch and selected object -- " + "`#/p/alias/storage?sel=tables%2Fin.c-foo.bar` reopens that table's drawer on a " + "cold load. Hash-based by design: the REST API owns the root paths, so " + "history-mode routing would collide with `GET /projects`. Page changes push " + "history, selection changes replace it, and a link naming an alias this install " + "does not know falls back to the default project instead of erroring on every page.", + "New (#665): detail views render their payload instead of dumping it. Projects, " + "configs, components, data apps and the rest answer a click with a structured " + "Overview -- metadata grids, pills, tables -- and keep the untouched JSON one tab " + "away, so nothing the API returned is hidden.", + "New (#663): a curated What's new popup, shown once per version, with " + "`kbagent serve --no-banner` to turn it off fleet-wide. The switch reaches the SPA " + "through the new `GET /ui-config` endpoint and the UI fails closed -- no popup " + "while that request is in flight, on error, or on anything but `banner: true`. The " + "flag governs only what appears uninvited: the command palette's What's new action " + "still opens it on request. The reel is hand-maintained in " + "`web/frontend/src/whatsnew.ts`, separate from this changelog on purpose.", + "Change (#665): on `kbagent serve`, `ErrorCode.NOT_FOUND` now answers HTTP 404 on " + "every route. It used to answer 502, which told callers the upstream was broken " + "when the truth was that the thing they asked for does not exist. Any consumer " + "branching on the status code alone needs updating -- branch on `error.code`, which " + "was correct before this change and still is.", + "Fix (#665): `component detail` no longer fails for a component the AI Service does " + "not index. The AI Service covers the PUBLIC catalog only, so a private or " + "deprecated component the project can actually run -- `keboola.mcp-server-tool`, " + "`keboola.data-apps` -- returned NOT_FOUND there while `component list` happily " + "showed it, surfacing over `serve` as an HTTP 502. A 404 now falls back to the " + "project's Storage component catalog. The new `documentation_source` field " + "(`ai_service` vs `storage_catalog`) is present on both paths and tells them apart; " + "the fallback carries no configuration examples, so check it before reading " + '`examples_count: 0` as "this component ships none". A non-404 AI Service failure ' + "is never masked.", + "Fix (#668): the web UI session now survives a server restart. `serve --ui` " + "authenticates the browser with an HttpOnly cookie set on `GET /`, but a reload " + "could be served from the browser cache -- no request, no fresh cookie -- so every " + "`/api/*` call 401d and the SPA rendered empty lists with no visible auth error. " + "The shell routes now answer `Cache-Control: no-cache` (hashed build assets keep " + "their caching), and the API client treats a 401 as a possibly-stale cookie: it " + 're-fetches the shell once with `cache: "reload"`, single-flight across concurrent ' + "401s, and retries the request exactly once. Only if that retry still fails does a " + "session-expired banner appear, carrying the server's own message when it names a " + "remedy.", + "Fix (#666): `component detail` and `config new` now really do fall back to the " + "first configured project when `--project` is omitted. Both commands document that " + "flag as optional, and both broke the promise: they passed `alias=None` into a " + "resolver that only falls back on an EMPTY " + "alias list -- and `[None]` is not empty -- so the documented-as-optional flag " + "failed with `CONFIG_ERROR: Project 'None' not found`. An empty config now raises " + 'an actionable "No projects configured" error instead.', + "Fix (#662): the Config column in `kbagent job list`'s human-mode table is no longer " + "always empty. The renderer read `configId`, but the Queue API job resource names " + "that field `config` and the service returns the API row verbatim, so the key never " + "existed. The fixtures hand-wrote the same invented key, which is why the tests " + "passed while real output was blank.", + 'Fix (#623): `uv build --wheel` no longer aborts with "A second file is being added ' + 'to the wheel archive at the same path" when the bundled SPA is present. Hatchling ' + "has two independent paths that add files to a wheel -- `packages` collection and " + "`force-include` -- and from hatchling 1.30.0 the .gitignore-based exclusion stops " + "suppressing the first one for a directory-shaped force-include, so " + '`_ui_dist/` is added twice. `requires = ["hatchling"]` is unpinned, so this hits ' + "the ordinary release path rather than an edge case. Fixed with an unconditional " + "`exclude` glob on the wheel target, verified across hatchling 1.27 through 1.32; " + "the sdist target gained the symmetric guard so generated SPA output can never ship " + "as source. README now documents the Python >=3.12 install floor.", + "New (#661): frontend PRs are gated on CI. A new path-filtered `frontend.yml` " + "workflow runs `tsc --noEmit`, `npm test` and the production `vite build` on any " + "change under `web/**`, so a type error can no longer reach `main` undetected -- " + "#658 carried three commits of frontend bugs caught only by hand. Python-only PRs " + "never schedule the runner. This is safe only while the `main` ruleset has no " + "required status checks; if frontend checks ever become required, the path filter " + "needs rethinking.", + 'Note (#667): the docs\' claim that `config new` makes "zero API calls" in scaffold ' + "mode was never true. Corrected in all three surfaces that carried it: the scaffold " + "reads the component schema from the AI Service -- which is exactly " + "what `--project` authenticates without `--push`. An agent reading the old wording " + "would wrongly conclude the scaffold works offline.", + ], "0.89.0": [ "Fix (#646): `sync clone` now rejects a malformed override file instead of " "silently pushing garbage IDs. The `--bucket-map` / `--variable-values` / " diff --git a/src/keboola_agent_cli/commands/context.py b/src/keboola_agent_cli/commands/context.py index e61afeac..af456c67 100644 --- a/src/keboola_agent_cli/commands/context.py +++ b/src/keboola_agent_cli/commands/context.py @@ -341,7 +341,7 @@ kbagent component detail --component-id ID [--project NAME] Show component docs, config schema, and examples count. - (since vNEXT) The AI Service indexes the PUBLIC catalog only, so a private + (since 0.90.0) The AI Service indexes the PUBLIC catalog only, so a private or deprecated component the project can run (keboola.mcp-server-tool, keboola.data-apps) used to 404 here while `component list` showed it. A NOT_FOUND now falls back to the project's Storage component catalog. @@ -1862,7 +1862,7 @@ subprocesses). Requires the optional 'server' extra: `uv pip install -e ".[server]"`. - --no-banner (since vNEXT) suppresses the web UI's "What's new" popup -- + --no-banner (since 0.90.0) suppresses the web UI's "What's new" popup -- a curated per-version highlights modal the UI shows once per version (dismissal persisted in localStorage `kbagent.whatsnew.seen`). The SPA reads the switch from `GET /ui-config` -> {{"banner": bool}}; nothing is diff --git a/uv.lock b/uv.lock index 29a82995..cc107a56 100644 --- a/uv.lock +++ b/uv.lock @@ -581,7 +581,7 @@ wheels = [ [[package]] name = "keboola-cli" -version = "0.89.0" +version = "0.90.0" source = { editable = "." } dependencies = [ { name = "croniter" }, diff --git a/web/frontend/src/whatsnew.ts b/web/frontend/src/whatsnew.ts index 518b88a9..94d64362 100644 --- a/web/frontend/src/whatsnew.ts +++ b/web/frontend/src/whatsnew.ts @@ -36,6 +36,60 @@ export interface WhatsNewRelease { } export const WHATS_NEW: WhatsNewRelease[] = [ + { + version: "0.90.0", + items: [ + { + title: "Every view has a link", + body: + "The URL now tracks the page, project, branch and whatever you have open, so the " + + "exact state you are looking at can be pasted to a colleague. Back and Forward " + + "walk pages, and a shared link reopens the drawer on a cold load.", + }, + { + title: "The palette finds your data", + body: + "Ctrl+K searches storage buckets and tables across every registered project, not " + + "just pages and actions. Enter lands on the object with its filter applied, " + + "switching project first if it lives elsewhere. Still nothing waits on the network " + + "while you type.", + hint: "ctrl+k / \u2318k", + }, + { + title: "Details you can read", + body: + "Projects, configs, components, data apps and jobs answer a click with a rendered " + + "overview instead of a JSON dump \u2014 and keep the untouched payload one tab away, " + + "so nothing is hidden.", + }, + { + title: "Run and terminate jobs", + body: + "Re-run a job or start one straight from a configuration, and terminate anything " + + "still queued or running. A re-run uses the configuration as it stands now.", + }, + { + title: "Deleting a config is undoable", + body: + "Configs now have a Trash tab listing what was deleted, with per-row restore. " + + "Delete is soft, and the confirm dialog says so.", + }, + { + title: "Tokens, without the web UI", + body: + "A new Tokens page creates, rotates and revokes scoped Storage tokens. The secret " + + "is shown once at mint; the opt-in last-used pass sorts dormant tokens first, so " + + "reading order is cleanup order.", + }, + { + title: "Your session survives a restart", + body: + "Restarting the server no longer leaves a tab quietly showing empty lists. The UI " + + "now re-authenticates itself on the first rejected request, and tells you plainly " + + "when it genuinely cannot.", + }, + ], + }, { version: "0.89.0", items: [ From 1fa1caf1db4c2fb2b4100fa3d80ede58d74e0970 Mon Sep 17 00:00:00 2001 From: Petr Date: Sun, 23 Aug 2026 23:32:11 +0200 Subject: [PATCH 2/2] docs(web-server): keep the What's-new anchor stable across releases The '### What's-new popup *(since vNEXT)*' heading put the version gate in the heading itself, so resolving the placeholder to 0.90.0 changed the generated slug to 'whats-new-popup-since-0900' and broke the in-page link at line 138 -- and would have broken it again on every future release. Moved the '(since 0.90.0)' tag to the first body line: the anchor is now the stable 'whats-new-popup', the gate stays visible, and check_version_gates.py still sees it (it scans the whole file, not just headings). --- docs/web-server.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/web-server.md b/docs/web-server.md index 4164b07e..5f6f2f7e 100644 --- a/docs/web-server.md +++ b/docs/web-server.md @@ -135,7 +135,7 @@ A NERD-themed React SPA that drives the API: reopen **What's new**. Arrows + enter, esc closes. - **What's new popup** *(since 0.90.0)* — a curated per-version highlights modal, shown once per version. See - [What's-new popup](#whats-new-popup-since-vnext) below for the curated + [What's-new popup](#whats-new-popup) below for the curated list's location, the storage key, and the `--no-banner` opt-out. - **Dashboard** — greeting, big Kai chat input, stat tiles (projects / agents / doctor / recent jobs / PAYG credits), scheduled-agent @@ -338,11 +338,11 @@ each list rendering as empty: `SESSION_NOT_FOUND` the banner carries the server message, which names the on-host `kbagent auth login` remedy). -### What's-new popup *(since 0.90.0)* +### What's-new popup -The web UI shows a curated per-version highlights modal on load, once per -version, so features like the command palette get discovered instead of -waiting to be stumbled upon. +*(since 0.90.0)* The web UI shows a curated per-version highlights modal on +load, once per version, so features like the command palette get discovered +instead of waiting to be stumbled upon. **Curated list — `web/frontend/src/whatsnew.ts`.** A hand-maintained `WhatsNewRelease[]`, deliberately *not* the raw `changelog.py` output: the