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-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"plugins": [
{
"name": "kbagent",
"version": "0.42.0",
"version": "0.43.0",
"source": "./plugins/kbagent",
"description": "AI-friendly interface to Keboola Connection projects — explore configs, jobs, lineage, call MCP tools, manage dev branches, and debug SQL in workspaces",
"category": "development"
Expand Down
2 changes: 1 addition & 1 deletion plugins/kbagent/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "kbagent",
"version": "0.42.0",
"version": "0.43.0",
"description": "AI-friendly interface to Keboola Connection projects — explore configs, jobs, lineage, call MCP tools, manage dev branches, and debug SQL in workspaces",
"author": {
"name": "Keboola",
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "keboola-agent-cli"
version = "0.42.0"
version = "0.43.0"
description = "AI-friendly CLI for managing Keboola projects"
readme = "README.md"
requires-python = ">=3.12"
Expand Down
5 changes: 5 additions & 0 deletions src/keboola_agent_cli/changelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@

# Ordered newest-first. Each value is a list of brief one-line descriptions.
CHANGELOG: dict[str, list[str]] = {
"0.43.0": [
"New: full Semantic Layer management surface in `kbagent serve --ui` (closes #308). The web UI now mirrors every `kbagent semantic-layer` CLI operation 1:1 -- model CRUD (`/api/semantic-layer/models`), entity CRUD for all five kinds (metric / dataset / relationship / constraint / glossary), and the Phase-3 operations (validate, export, diff, promote, import, build, encrypt-token). The UI calls **zero** Metastore endpoints directly; every interaction goes through `/api/semantic-layer/*` on the same `kbagent serve` process so CLI parity is structural, not aspirational. Highlights: schema-driven add/edit drawers (one Pydantic schema per entity kind drives both Typer flags and the React form, no UI-side validation duplication); relationships view ships with a `flowchart TB` ERD (Mermaid) + a dataset-filter chip for hub-and-spoke drill-down + a parallel 'click to edit' edge list for hit-target reliability; constraints view groups rows by `constraintType` with collapsible `<details>` blocks and a 3-icon severity rail (critical / warning / info); datasets detail panel surfaces `fields[]` with role chips (`key=keboola/measure=green/dimension=zinc`). Builder/Importer/Promoter/Diff/Encrypt-Token are dedicated dialogs (`SemanticLayerDialogs.tsx`) with dry-run preview where the CLI offers it. The relationships ERD ships as `flowchart TB` (not `erDiagram`): erDiagram has no rankdir and laid every hub-and-spoke model out as a wide thin strip wasting ~70% of the canvas. flowchart TB puts the hub above its dependents, edge labels are trimmed to just the join type (`left` / `inner`; full relationship names live in the edge list below), auto-fit chooses `Math.min(fitX, fitY)` (cap 2.5, floor 0.4) so 80-edge overviews shrink to ~40% and 15-edge hub drill-downs land at ~63%, both fully readable.",
'Fix: `BaseHttpClient._raise_api_error` now correctly surfaces Metastore validation messages instead of printing a bare HTTP status code. The Keboola Metastore answers 422 with `{"error": 422, "description": "..."}` (int in `error`, real text in `description`); the old parser used `body.get("error")` as the priority key, which evaluated to `422` and shadowed the real message -- the CLI rendered `API error 422: 422` and the operator had no actionable text. The new walker accepts `error` ONLY when it is a non-empty string, then falls through to `exception → message → description → detail → errors → json.dumps(body)` in priority order; FastAPI\'s `{"detail": [{loc, msg}]}` and Metastore\'s `{"errors": [{loc, msg}]}` list shapes are json-serialised so the message contains every diagnostic line, not the Python list repr. Four regression tests pin the new paths (int `error`, plain `description`, both list shapes) so the bare-status-code UX cannot return silently.',
'Fix: `kbagent semantic-layer build` no longer HTTP-422s on legacy untyped Storage tables. The heuristic builder synthesises `fields[]` from a Storage `column_details[]` response; on legacy untyped tables the `basetype` is empty (`""`) and on typed tables it is warehouse-native (Snowflake `VARCHAR(255)`, `NUMBER(38,2)`, `TIMESTAMP_NTZ`, BigQuery `STRING`, ...). The Metastore only accepts a closed lowercase set (`string` / `integer` / `decimal` / `boolean` / `date` / `datetime` / `json`) for `fields[*].type`, so the heuristic builder used to push `""` or `"VARCHAR"` verbatim and 422 on every legacy table. New `_normalize_field_type(basetype)` strips parameter brackets and case-folds before mapping through `_FIELD_TYPE_MAP` (~30 warehouse aliases); empty/None falls through to `"string"` (safest default for an untyped column). A parametrized `TestNormalizeFieldType` covers every output bucket, parameterised types, case variants, and the unknown-UDT fall-through; the existing `test_heuristic_fallback` now asserts the field type was normalized to `"decimal"` end-to-end so the heuristic builder cannot regress to the pre-fix shape. Plugin docs (`plugins/kbagent/skills/kbagent/references/gotchas.md`) gain a `(since v0.41.10)` note on the normalization so any AI agent on an older kbagent has a documented escape path.',
],
"0.42.0": [
"Fix: workspace discoverability gap for data-app local dev (closes #304). David Ešner spent ~30 min and 4 wrong workspace IDs (including the `parameters.id` red herring) bringing up a Streamlit data app that reads via the Query Service -- because four different signals were missing or actively misleading. This release closes all four. (1) `kbagent workspace list` and `workspace detail` now accept `--branch` and follow the same `Info: Using production branch for read (active dev branch X ignored; pass --branch X to override)` banner as `storage buckets` / `config list`. Previously the commands silently scoped to the alias's pinned branch (carried over across sessions), returning a different workspace set than against the same alias one shell ago -- the original incident's root cause. `--branch` requires exactly one `--project` (branch IDs are per-project), mirroring the storage commands. (2) Each entry in `workspace list` / `workspace detail` JSON now carries `login_type`, `read_only`, `qs_compatible`, `database` and `warehouse`. The Storage API has always returned `connection.loginType` (snowflake-service-keypair / snowflake-person-sso / snowflake-legacy-service / default) and `readOnlyStorageAccess`; kbagent simply threw them away. Now they surface as new `Login Type` / `RO` / `QS` columns in the human-mode Rich table plus a `Login type` / `Read-only` / `Query Service compatible` block in `workspace detail`. `qs_compatible` is derived from the new conservative `QUERY_SERVICE_COMPATIBLE_LOGIN_TYPES` whitelist in `constants.py` (currently `snowflake-service-keypair` + `snowflake-person-sso`; `snowflake-legacy-service` stays OFF because the original issue confirmed it is rejected on the GCP us-east4 stack with `code: storage.executeQuery.notSupportedLoginType` even though it works on `connection.keboola.com`). False-negative-over-false-positive semantics: a `?` cell tells the caller 'not on the confirmed list, may still work' rather than blocking them. (3) New `workspace list --qs-compatible` filter pre-selects RO + whitelisted-loginType workspaces -- the canonical shape for a Streamlit / Quix data-app reading via the Query Service. (4) `config detail --component-id keboola.sandboxes --config-id <ID>` now appends a `sandbox_annotation` block with `sandbox_service_id` (the misleading `parameters.id`) and `storage_workspace_id` (the actual Storage workspace ID resolved via `WorkspaceService.resolve_sandbox_workspace_id`). The annotation is JSON-structured and Rich-rendered; it appears ONLY in single-config mode to avoid N+1 in bulk fan-out. Empirically verified on padak-2-0 (project 10539) by pinning to dev branch 1297900: pre-fix `workspace list` returned 1 row from the dev branch with no banner, post-fix returns 22 rows from production WITH the banner explaining how to opt back in via `--branch 1297900`. New module-level `_classify_qs_compatibility` helper + `WorkspaceService.resolve_sandbox_workspace_id`. Tests: 3 in `test_workspace_cli.py::TestWorkspaceListIssue304` (branch flag propagation, multi-project rejection, qs filter propagation, active-branch banner), 1 in `test_workspace_cli.py::TestWorkspaceDetailIssue304`, 3 in `test_workspace_service.py::TestIssue304WorkspaceListEnrichment`, 3 in `test_workspace_service.py::TestIssue304ResolveSandboxWorkspaceId`, 1 in `test_workspace_service.py::TestIssue304GetWorkspaceEnrichment`, 3 in `test_cli.py::TestConfigDetail` for sandbox annotation (matching + orphan + non-sandbox-component negative).",
],
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading