Skip to content

feat(plugin): publish kbagent through keboola/ai-kit, deprecate this repo's marketplace - #627

Merged
jordanrburger merged 3 commits into
mainfrom
feat/publish-kbagent-via-ai-kit
Aug 24, 2026
Merged

feat(plugin): publish kbagent through keboola/ai-kit, deprecate this repo's marketplace#627
jordanrburger merged 3 commits into
mainfrom
feat/publish-kbagent-via-ai-kit

Conversation

@claude

@claude claude Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Requested by Jordan Burger · Slack thread

What it does

Keboola had two Claude Code plugin marketplaces; this leaves one — keboola-claude-kit in keboola/ai-kit — and turns this repo's marketplace into a deprecated shim that still serves existing installs.

Before / After

Before. Installing the kbagent plugin meant adding this repo as a marketplace. Keboola therefore advertised two competing marketplaces, and a user had no way to tell which was the real one.

/plugin marketplace add keboola/cli
/plugin install kbagent@keboola-agent-cli

After. One marketplace, keboola-claude-kit, published from keboola/ai-kit. The old commands keep working for now; the new ones are what every doc, kbagent context, and kbagent doctor tell you.

/plugin marketplace add keboola/ai-kit
/plugin install kbagent@keboola-claude-kit

How

The plugin source stays here, in plugins/kbagent/. Four CI gates in this repo generate and validate those files against the live command tree — make skill-gen / make skill-check, scripts/sync_version.py, scripts/check_command_sync.py, make version-check — and none of them exist in ai-kit, so moving the files would trade one marketplace for a permanent drift risk. What moves is publication: ai-kit's marketplace carries an external git-subdir entry pointing at plugins/kbagent in this repo, pinned to a release tag, and a new release job repoints that entry on every stable tag.

The syncing mechanism

.github/workflows/release-kbagent.yml gains an ai-kit-marketplace job, gated exactly like the existing homebrew job (needs: [version, freeze, publish-s3], non-prerelease, refs/tags/, environment: release). It checks out keboola/ai-kit with a new write-scoped PAT, jq-rewrites the kbagent entry's version and source.ref, and opens a PR with gh pr create rather than pushing to main, so ai-kit's Tier 0 / Tier 1 evals see the change before it ships.

Properties worth knowing:

  • No-op safe. It compares the entry's version + source.ref values before touching the file (jq re-emits the whole document with its own indentation, so a git diff alone would open empty PRs), and exits 0 when they already match. Re-running a tag is free.
  • Fails loudly if the file or the kbagent entry is missing in ai-kit — that means the entry was never registered or was reverted, and silently publishing nothing would be worse.
  • A release no longer ships to plugin users by itself. Somebody has to merge the ai-kit PR. CONTRIBUTING.md's release checklist grows a step 13 saying so, and the sync map grows a row.

The deprecation shim, and how long it lives

.claude-plugin/marketplace.json is kept, mechanically intact, so existing installs keep resolving updates. The only change is that the kbagent entry's description now leads with the migration notice:

DEPRECATED — install from keboola/ai-kit: /plugin marketplace add keboola/ai-kit && /plugin install kbagent@keboola-claude-kit — …

That description string is what /plugin listings show, which makes it the only in-product channel that reaches people already installed from here. scripts/sync_version.py still round-trips the file (it only writes version; verified), so make version-check stays green and the file is untouched in the git diff --exit-code path lists in the Makefile and this workflow.

Intended lifetime: about three releases. Then drop the entry and leave a renames: {"kbagent": null} tombstone. Note that renames cannot redirect across marketplaces — it only renames within one — so there is no mechanism that migrates an installed user automatically. The migration is necessarily a manual two-line user action, which is exactly why the notice lives in the description and why doctor nags.

doctor and the docs

  • doctor's claude_plugin check now probes both cache dirs, new first: ~/.claude/plugins/cache/keboola-claude-kit/kbagent, then the legacy ~/.claude/plugins/cache/keboola-agent-cli/kbagent. A plugin found only under the legacy dir still passes — it works and it updates — but the message appends reinstall-from-ai-kit instructions. The warn message for a missing plugin prints the two new /plugin lines. Three new tests cover the legacy-dir pass, the both-dirs-present preference, and an empty legacy root.
  • Install commands updated in AGENT_CONTEXT (kbagent context), README.md, docs/TUTORIAL.md (including the cache path), docs/use-cases.md, plugins/kbagent/skills/kbagent/SKILL.md, plugins/kbagent/.claude-plugin/CLAUDE.md.
  • CLAUDE.md's "This repo doubles as a Claude Code plugin marketplace" framing is rewritten to "the plugin lives here and is published through keboola/ai-kit", including why the source stays and why the shim must not be deleted.

Version and changelog — deliberately none

This PR no longer bumps the version and no longer adds a changelog.py entry. An earlier revision bumped 0.86.00.87.0 (later 0.88.0); both were dropped when this branch was rebased onto 0.89.0. If you were expecting 0.90.0 here, this is why you will not find it.

main commit 5313801 (#648, "version bumps move out of feature PRs into dedicated release PRs") now forbids precisely that: a feature PR merges with no change to pyproject.toml's version and no changelog.py entry, and a dedicated release PR batches everything merged since the last release into one bump plus one entry. Its stated rationale is this PR's own failure mode — parallel PRs each bumping the version, colliding on every merge and silently renumbering releases. Accordingly:

  • pyproject.toml stays at main's 0.89.0. plugin.json, .claude-plugin/marketplace.json and uv.lock are all in lock-step — make version-sync and make version-check are both clean no-ops.
  • src/keboola_agent_cli/changelog.py is byte-identical to origin/main. Writing the release notes for this change is the next release PR's job; docs: version bumps move out of feature PRs into dedicated release PRs #648 step 1 collects merged PRs from git log, so this one lands in its scope automatically.
  • The shim's planned-removal horizon is tagged with the vNEXT placeholder rather than a guessed number, per the same convention — the release PR rewrites it to the version that actually ships it. CONTRIBUTING.md was added to the release checklist's vNEXT grep (step 4) so that placeholder gets resolved instead of quietly rotting, since the file was not previously in that grep's path list.

Dropping the changelog entry costs nothing user-facing: the deprecation notice still ships in the shim entry's description (the one channel /plugin listings surface), in doctor's legacy-marketplace message, and in CLAUDE.md, SKILL.md and the plugin's CLAUDE.md.

Coordination

  • The ai-kit PR should merge first. It registers the kbagent entry in keboola-claude-kit. Until it lands, the commands this PR documents do not resolve, and the ai-kit-marketplace job would fail its missing-entry guard.
  • secrets.AI_KIT_TOKEN must be provisioned in the release environment before the next release, scoped to contents + pull-requests on keboola/ai-kit only. Without it the new job fails (the rest of the release still ships, and no ai-kit PR appears — which is the symptom to look for).

Overlap with #625

Draft PR #625 (feat/kbagent-setup-slash-command, adds /kbagent:setup) touches six of the same files in the same hunks: README.md, docs/TUTORIAL.md, src/keboola_agent_cli/commands/context.py, install.sh, plugins/kbagent/skills/kbagent/SKILL.md, plugins/kbagent/.claude-plugin/CLAUDE.md. I read its diff and kept the edits here surgical — in those six files this PR changes the marketplace/install command lines and nothing adjacent, and the two additions that are not command-line swaps (SKILL.md, plugin CLAUDE.md) are appended clear of #625's hunks.

#625 landing first is fine and preferred. Two notes for whoever rebases second:

Validation

Re-run after the rebase onto 0.89.0. make check minus changelog-check (that target shells out to gh, which is not installed in the environment this was authored in): lint, format-check, typecheck, skill-check, version-check, command-sync-check, check-error-codes, check-sentinel-guards and loc-check all green. Unit suite: 6009 passed, 13 skipped; the only two failures are test_config_store.py::TestPermissionDenied::{test_save_to_readonly_directory,test_load_unreadable_config_file}, confirmed to fail identically on a pristine origin/main worktree because the authoring environment runs as uid 0 and root ignores the chmod those tests rely on. ty reports its one pre-existing hatchling unresolved-import warning (downgraded in pyproject.toml, non-blocking) and exits 0 — also confirmed on pristine origin/main. make version-sync and make skill-gen both produce an empty diff. Workflow YAML parses; the new job's needs / if / environment gating is byte-identical to the existing homebrew and chocolatey jobs, and nothing needs: it, so a failure there cannot block a release. jq . .claude-plugin/marketplace.json is valid, and a full grep -rn "marketplace add" over *.md / *.py / *.sh shows every live surface on keboola/ai-kit — the only keboola/cli hits left are two immutable historical changelog.py entries.

Not in this PR

  • Removing .claude-plugin/marketplace.json or its kbagent entry, and the renames tombstone — deliberately deferred ~3 releases.
  • Any change in keboola/ai-kit (companion PR) or to feat(plugin): add /kbagent:setup one-command first-run setup #625.
  • A machine-readable "installed from the legacy marketplace" field on doctor's JSON output; the signal is prose in message only. Worth adding if anything wants to act on it programmatically.

claude Bot pushed a commit that referenced this pull request Aug 21, 2026
The next-steps hint hardcoded the Claude Code marketplace and plugin
names (`keboola/cli` / `kbagent@keboola-agent-cli`). #627 moves the
marketplace listing to keboola/ai-kit and does not touch install.sh, so
the hardcoded pair would merge silently and then advertise deprecated
names. Point at `kbagent doctor` instead, which already prints the
current `/plugin` commands and is updated by #627 -- correct under
either merge order.
@claude
claude Bot force-pushed the feat/publish-kbagent-via-ai-kit branch from 8179e1c to 13851cb Compare August 23, 2026 16:35
@claude
claude Bot marked this pull request as ready for review August 23, 2026 16:36
…repo's marketplace

Keboola had two Claude Code plugin marketplaces. This leaves one.

The plugin SOURCE stays here in plugins/kbagent/ -- four CI gates in this repo
generate and validate it against the live command tree (make skill-gen /
skill-check, scripts/sync_version.py, scripts/check_command_sync.py, make
version-check) and none of them exist in ai-kit. What moves is PUBLICATION:
keboola/ai-kit's keboola-claude-kit marketplace carries an external git-subdir
entry pointing at plugins/kbagent here, pinned to a release tag.

- New `ai-kit-marketplace` release job (release-kbagent.yml), gated exactly like
  `homebrew` (stable tag + environment: release): jq-rewrites the kbagent entry's
  version + source.ref in ai-kit and opens a PR there with `gh pr create`. A PR,
  not a push, so ai-kit's evals see the change. No-op safe -- an entry already at
  this version opens nothing, and a re-run of the same tag reuses its branch.
  Requires a new write-scoped PAT, secrets.AI_KIT_TOKEN.
- .claude-plugin/marketplace.json stays as a deprecated shim so existing installs
  keep resolving updates; its entry description now leads with the migration
  notice, which is the only in-product channel that reaches those users.
  Mechanically untouched otherwise (sync_version.py still round-trips it).
- doctor's claude_plugin check probes both cache dirs, keboola-claude-kit first.
  A legacy-only install still passes, with reinstall instructions appended.
- Install commands updated in AGENT_CONTEXT, README, TUTORIAL, use-cases, CLAUDE.md,
  CONTRIBUTING (sync-map rows + a post-tag release step), SKILL.md and the plugin's
  CLAUDE.md.

Before: /plugin marketplace add keboola/cli + /plugin install kbagent@keboola-agent-cli
After:  /plugin marketplace add keboola/ai-kit + /plugin install kbagent@keboola-claude-kit
@claude
claude Bot force-pushed the feat/publish-kbagent-via-ai-kit branch from 13851cb to 061a093 Compare August 23, 2026 22:26
@jordanrburger
jordanrburger requested a review from padak August 24, 2026 12:10

@soustruh soustruh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve. I read the code. Two points the description does not cover:

  1. doctor with both cache dirs present and the legacy one newer. _check_claude_plugin stops at the first directory that holds any version — keboola-claude-kit — and never reads the legacy directory (doctor_service.py:577). latest is then the max within that one directory only (:602). While ai-kit trails a cli release — the state this PR creates — a user with both installs, and a newer copy under keboola-agent-cli, sees the older keboola-claude-kit version, gets no migration note, and gets a drift hint aimed at the wrong copy. The new tests cover claude-kit-only, legacy-only, and claude-kit-newer. They do not cover legacy-newer. Is that report intended?

  2. The sync job has no automated test. ai-kit-marketplace is the only new code that writes to another repo, and only manual checks cover it. A shell test of the jq rewrite and the value-compare would catch a future change to ai-kit's marketplace schema before a release does.

Neither blocks approval. The sequencing and AI_KIT_TOKEN caveats in the description hold.

…che dirs

Review found a real bug in the claude_plugin check, not just a gap in the tests.

`_check_claude_plugin` probed the two cache dirs in order and broke out of the
loop at the first one holding any version dir, so `latest` was the max WITHIN
that dir only. While ai-kit trails a cli release -- the exact state publishing
through ai-kit creates -- a user with both installs and a newer copy under the
legacy `keboola-agent-cli` dir was told the OLDER `keboola-claude-kit` version,
got no migration note, and got a drift hint aimed at a copy they were not
running. Reporting a stale version as current is worse than reporting nothing.

The probe now collects every version dir under every marketplace dir that
exists and picks the single newest, so version, path, marketplace name, drift
hint and migration note all describe the same copy:

- a newest-copy-under-the-shim install gets the migration note it was missing;
- the drift hint names the marketplace of the reported copy
  (`/plugin update kbagent@<marketplace>`), unambiguous when both are installed;
- the message and a new `plugin_marketplace` result key name where the copy
  came from;
- ties (same version cached under both) resolve to the current marketplace --
  same code, nothing to migrate off.

Ordering is PEP 440 via `packaging.version`, the idiom version_service already
uses, rather than dir-name string compare: a string sort puts "0.100.0" below
"0.90.0" and would report a stale copy as newest once the minor rolls past 99,
which this repo is ten releases from. Unparseable dir names sort below every
real version but stay eligible, so a hand-made dir never hides an install.
Single-cache-dir behaviour is unchanged; all 11 pre-existing tests pass as-is.

Also adds the sync job's missing automated test. `ai-kit-marketplace` is the
only job here that writes to another repo, and its entry shape is owned by
ai-kit, so the failure worth pinning is a shape change on the far side.
tests/test_release_kbagent_ai_kit_sync.py extracts the step's real `run:` block
from the workflow and executes it with bash against a throwaway git repo --
same "load the real artifact by path, then exercise it" approach as
test_sync_version_script.py -- covering the at-target no-op (byte-identical
file, changed=false), a genuine bump rewriting both `version` and `source.ref`
while leaving the git-subdir coordinates and sibling plugins alone, either field
stale alone still counting as a bump, a missing `source.ref` getting added, a
missing `kbagent` entry failing loudly, and a `source` that is a plain string
(the shape this repo's own deprecated marketplace.json still uses) aborting with
the file untouched instead of publishing an unpinned version.

No version bump and no changelog entry, per #648.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RiYtKRNF8XDx3dDZAAeBGV
@claude

claude Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor Author

Both points were right, and both are fixed in bd9cf83.

1. doctor with both cache dirs present and the legacy one newer

No, that report was not intended — it was a bug. Your reading of it is exactly right: the loop broke out at the first cache dir holding any version dir, so latest was the max within that one dir. With both installs and a newer copy under keboola-agent-cli, doctor reported the older keboola-claude-kit version, suppressed the migration note, and pointed the drift hint at a copy the user was not running. Reproduced before fixing — the new legacy-newer test fails on the old probe with assert '0.20.0' == '0.24.0'. And you picked the worst moment for it: while ai-kit trails a cli release is precisely the window this PR opens.

The probe now collects every version dir under every marketplace dir that exists and picks the single newest, so the version, path, marketplace, drift hint and migration note all describe the same copy:

  • a newest-copy-under-the-shim install gets the migration note it was missing;
  • the drift hint names the marketplace of the reported copy — /plugin update kbagent@<marketplace> — which a bare kbagent could not disambiguate with two installs;
  • the message and a new plugin_marketplace result key say where the reported copy came from;
  • a tie (same version cached under both) resolves to the current marketplace: same code, nothing to migrate off.

One thing I changed beyond the report, flagging it so it is not silent: ordering is now PEP 440 via packaging.version — the idiom version_service already uses — rather than a dir-name string compare. A string sort puts 0.100.0 below 0.90.0, so it would report a stale copy as the newest once the minor rolls past 99, and this repo is ten releases from that. It only mattered once dirs were compared against each other. Unparseable dir names sort below every real version but stay eligible, so a hand-made dir still cannot hide a real install.

Single-cache-dir behaviour is unchanged — all 11 pre-existing tests pass untouched. New tests: legacy-newer, version tie, empty current root masking a real legacy install, PEP 440 rollover across marketplaces, several versions on both sides, unparseable name losing to a real version, a lone unparseable name still passing, and the marketplace being named in the message.

2. The sync job's test

tests/test_release_kbagent_ai_kit_sync.py. It extracts the bump step's real run: block out of the workflow and executes it with bash against a throwaway git repo, so editing the workflow is what changes the behaviour under test — same "load the real artifact by path, then exercise it" shape as tests/test_sync_version_script.py. Coverage:

  • entry already at the target version — clean exit, changed=false, and the file byte-identical, not merely value-equal (that byte check is what pins the reason the guard compares values instead of running git diff);
  • a genuine bump — rewrites both version and source.ref, while the git-subdir coordinates (repo, path), the other entry fields and sibling plugins stay put;
  • either field stale on its own — still a bump, so a guard that compared only version cannot leave the tag pin on the previous release;
  • source.ref absent — gets added rather than skipped;
  • no kbagent entry — fails loudly, and changed=true is not emitted;
  • source as a plain string rather than an object — the shape this repo's own deprecated marketplace.json still uses, so it is exactly what a copy-paste into ai-kit would produce. jq cannot index a string, so the step aborts with the committed file untouched instead of publishing a version with no tag pin;
  • no marketplace.json at all — fails loudly;
  • plus two wiring assertions: the job env the shell reads, and that the PR step is still gated on steps.bump.outputs.changed.

I mutation-tested the harness rather than trusting it: weakening the guard to compare only version fails 2 tests, and dropping .source.ref from the jq filter fails 4. So it does fail if ai-kit's entry shape changes such that the rewrite writes nothing or the wrong field.

On your approval

I checked /pulls/627/reviews after pushing and your approval survived — this repo does not dismiss stale reviews, so there is nothing you need to re-do. Had it dismissed, I would have pushed anyway and asked you to take another look: a doctor hint pointing at the wrong copy is worse than a re-approval. A fresh glance at the two new test files would be welcome whenever it suits you, but nothing is blocked on it.

Caveats unchanged

Both caveats from the description still stand exactly as written, and nothing here touches them:

  • Sequencing. A green release in this repo does not move plugin users. The ai-kit-marketplace job only opens a PR against keboola/ai-kit; merging that PR is what ships the version to keboola-claude-kit. Until it merges, /plugin install kbagent@keboola-claude-kit still serves the previous release, and nothing in this repo can see ai-kit's catalogue to tell you otherwise — the opened PR needs checking after every release.
  • AI_KIT_TOKEN. Still has to be provisioned in the release environment before the next release, scoped to contents + pull-requests on keboola/ai-kit only. Missing or expired, this job fails and no PR appears; the rest of the release still ships, which is the mode that looks green and moves nobody.

No version bump and no changelog entry, per #648. make changelog-check needs gh, which is not available where I ran this, so it is the one gate I could not execute; everything else is green apart from two pre-existing TestPermissionDenied failures (the sandbox runs as uid 0, so chmod cannot make anything unreadable) and the known hatchling ty import warning.


Generated by Claude Code

ci.yml's `build-windows` leg runs the FULL suite, and the new harness drives the
release step through `bash` + `jq` with a deliberately minimal env. The job under
test runs on `ubuntu-latest` and is POSIX shell, so there is no coverage to gain
on Windows -- only a portability failure that reads as a product problem.

Expressed as an explicit module-level POSIX-only skip, the idiom the suite
already uses for this (tests/test_file_locking.py), plus a tool-presence skip for
bash/jq. Per ci.yml's own note: a red suite nobody can act on is worse than no
suite, because it reads as coverage while hiding things.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RiYtKRNF8XDx3dDZAAeBGV
@jordanrburger
jordanrburger merged commit fc7ae7d into main Aug 24, 2026
3 checks passed
@jordanrburger
jordanrburger deleted the feat/publish-kbagent-via-ai-kit branch August 24, 2026 16:02
claude Bot pushed a commit that referenced this pull request Aug 24, 2026
Resolves the conflicts introduced by #627 (kbagent published through
keboola/ai-kit as kbagent@keboola-claude-kit).

- README.md, docs/TUTORIAL.md, commands/context.py: the /plugin fences now
  carry main's marketplace and plugin names plus this branch's
  /kbagent:setup line.
- CLAUDE.md: keeps main's rewritten "source here, publication there"
  section; its stale surfaces sentence is replaced with this branch's
  corrected list, made exact against the tree (3 skills, 3 slash
  commands, 2 subagents). Its install fence gains /kbagent:setup.
- plugins/kbagent/commands/setup.md: the claude_plugin bullet no longer
  paraphrases doctor's drift hint (main qualifies it with the
  marketplace) and now tells the agent to quote doctor's own /plugin
  lines as the single source of truth for the names.

install.sh already defers to `kbagent doctor` and hardcodes no names, so
main's rename does not reach it. No version file and no CI-gated figure
is touched.
@padak padak mentioned this pull request Aug 25, 2026
13 tasks
padak added a commit that referenced this pull request Aug 25, 2026
Bump pyproject to 0.91.0, add the changelog entry covering every PR merged since v0.90.1 (#627, #681, #691, #692, #693, #694, #695, #696, #697, #698), resolve all 54 vNEXT version-gate placeholders, and run version-sync + skill-gen.

No web/frontend changes in this batch, so no whatsnew.ts entry.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants