Commit graph

3 commits

Author SHA1 Message Date
minasarustamyan
efc607f3ee
feat(cli): agnes marketplace search/detail/add/remove + retire stale subcommands (#280)
* feat(cli): agnes marketplace search/detail/add/remove + retire stale subcommands

Unified CLI surface for the v28+ marketplace: search across Curated and
Flea Market (RBAC-filtered server-side), drill into a single item's
detail, add/remove from your stack. Replaces opt-out era commands that
no longer reflect how users compose their stack.

CLI changes:
- Added: agnes marketplace {search,detail,add,remove}
- Removed: agnes my-stack toggle (opt-out semantics, curated-only)
- Removed: agnes store {list,show,install,uninstall} (consumer-side ops
  moved under marketplace; store now covers only creator-side upload,
  update, delete, mine)

ID format unifies curated and flea: marketplace_id/plugin_name (slash)
routes to /api/marketplace/curated/..., bare UUID routes to
/api/store/entities/... (flea bundles skills/agents into a synthetic
plugin server-side, so the analyst sees a single add/remove surface).

Templates:
- claude_md_template.txt: rewritten marketplace section as operational
  guidance for Claude Code (discovery, stack management, behaviour
  notes). Dropped the static {% if marketplaces %} listing — the CLI is
  the source of truth for what's in the stack at any moment, so a
  snapshot rendered at init time would lie the moment the user runs
  agnes marketplace add/remove. Same discipline already applied to
  tables and metrics.
- agnes_workspace_template.txt: cheat sheet adds 5 marketplace
  one-liners; keeps the file's reference-doc tone (the original
  commit's intent: 'what is this thing, how does it work, how do I
  uninstall it').

Docs: HOWTO/05-customizing-skills.md rewritten around the new CLI flow;
the opt-out section is replaced by 'Removing items from your stack'.

Tests: new test_cli_marketplace.py covers all four subcommands incl.
RBAC/409 paths (system plugin guard, not-approved flea entity);
test_cli_store.py trimmed to the retained creator-side commands.

* release: 0.54.1 — agnes marketplace CLI redesign + retire stale subcommands

Last commit on the PR per CLAUDE.md hard rule. Patch bump (0.54.0 →
0.54.1) bundling the BREAKING removals of `agnes my-stack toggle` and
`agnes store {list,show,install,uninstall}` plus the new unified
`agnes marketplace {search,detail,add,remove}` surface.

No DB migration; no operator-facing config change. Operators on
floating tags (`:stable`) auto-upgrade transparently. Analyst CLI
upgrade prompt fires on next `agnes pull`; users invoking the
retired commands get "No such command" with the new `agnes
marketplace` substitution called out in the BREAKING bullets.

---------

Co-authored-by: Minas Arustamyan <arustamyan.minas@gmail.com>
Co-authored-by: ZdenekSrotyr <zdenek.srotyr@keboola.com>
2026-05-13 05:20:56 +00:00
minasarustamyan
4fb2818a19
Add /marketplace browse page + Model B opt-in stack composition (#230)
* Add /marketplace browse page + Model B opt-in stack composition

New /marketplace browse surface unifies the curated marketplaces
(admin-managed git mirrors) and the community Flea Market behind
three tabs — Curated / Flea / My Stack — with per-tab category
filter, search across both sources with scope checkboxes, and
numeric pagination, all driven by URL query state. Plugin detail
at /marketplace/curated/<slug>/<plugin> and /marketplace/flea/<id>;
nested skill / agent detail at /marketplace/curated/<slug>/<plugin>/
{skill,agent}/<name> and the flea-side single-page detail.

Model B opt-in: an RBAC grant on a curated plugin is now only
*eligibility*. The user must click "Add to my stack" for it to
enter their served Claude Code marketplace. Composition flips
from (rbac ∖ opt_outs) ∪ store_installs to
(rbac ∩ subscriptions) ∪ store_installs. The legacy
user_plugin_optouts table is renamed user_curated_subscriptions
(schema v27) — same table shape, inverted semantic, repository
methods become subscribe / unsubscribe / is_subscribed.

UX vocabulary: Install → Add to my stack, Installed → In your
stack, card "Installed" badge → "In stack" (amber pill), tab
"My Subscriptions" → "My Stack". Bridges the two-step model
(server-side bookmark vs. on-laptop install) the previous label
hid. Click triggers an inline post-add hint panel under the
description with the agnes refresh-marketplace recipe + Copy
chip, dismissible per-browser via localStorage.

Per-tab info blocks above the filter row:
- Curated: trust signal — "Each plugin here has a named curator
  accountable for it." (blue accent + See-all-curators link)
- Flea: open-shelf signal — "Anyone in the company can upload
  here." (purple accent + Tips-for-sharing link)
- My Stack: personal-shelf orientation — "Your AI stack —
  everything you've added." (slate accent, no link)

Tabs carry per-tab Heroicons (shield-check / building-storefront
/ rectangle-stack) tinted to match each tab's accent; flips white
when the tab is active for contrast.

Hero illustration anchored to the right of the blue hero panel
(absolute, 47% wide, behind the search row content). Hidden
under 900px viewport.

Action-row CTAs realigned to publication intent: curated
"How to add new content" → "Submit a plugin" (links to the
guide page); flea button removed since +Upload sits next to it.
Empty-state CTAs match. /marketplace/guide/{curated,flea}
routes now host publication-flow guide pages with placeholder
ledes — full copy to be authored separately.

Categories: Heroicons-based icons mapped per category in
src/category_icons.py (zero new dependencies; SVG path strings
inlined). Marketplace cards, filter pills, and detail pages
read from the same source.

API endpoints under /api/marketplace:
- GET /items per-tab listing (curated / flea / my)
- GET /categories per-tab non-zero counts
- GET /curated/{slug}/{plugin} plugin detail
- POST/DELETE /curated/{slug}/{plugin}/install subscribe toggle
- GET /curated/{slug}/{plugin}/{skill,agent}/{name} inner item
The tab=my branch reads directly from
user_curated_subscriptions ∪ user_store_installs (not
resolve_user_marketplace, which bundles flea skills/agents into
a single store-bundle synthetic entry useful for serving the
Claude Code marketplace ZIP/git but wrong for browsing where
each item should appear as its own card).

Detail pages: plugin detail surfaces inner skills/agents as
clickable nested cards; commands/hooks/MCPs render as plain
name lists. Skill/agent detail mirrors the plugin layout with
kind-tinted accents (skill = green, agent = purple), Description
+ Details sidebar, Files + Docs sections, and the "How to call
it" copy-able invocation chip showing /<plugin>:<inner-name>
exactly as Claude Code namespaces it post-install. Curated
nested has no install button — links back to the parent plugin.

Navbar: standalone "My AI Stack" relabelled "My Stack" and
points at /marketplace?tab=my; "Store" link removed (Store
flow is reachable via the Flea Market tab's +Upload button).
The standalone /my-ai-stack and /store routes still work for
old bookmarks.

Tests cover the new browse / categories / install / RBAC paths
under tests/test_marketplace_api.py; existing marketplace and
store tests updated for Model B (explicit subscribe in fixtures).
Schema bumped v26 → v27 with idempotent migration that wipes
existing user_plugin_optouts rows on flip and adds
marketplace_plugins.created_at with registered_at backfill.

* Fix v28 migration + post-rebase test fallout

v28 ALTER TABLE marketplace_plugins ADD COLUMN created_at conflicted with
_SYSTEM_SCHEMA's earlier CREATE that already includes the column on fresh
installs (test fixtures starting at any pre-v28 version trip on it).
Switch to ADD COLUMN IF NOT EXISTS — same idiom as the upstream v27
Keboola sync-strategy migration on the same ladder.

Two test patches needed after the rebase bumped SCHEMA_VERSION 27 → 28:
- test_keboola_v27_migration.py: test_schema_version_constant_is_27 was
  pinning ==27. Loosened to >=27 (the test's purpose is to verify the
  v27 Keboola migration, not to pin the current SCHEMA_VERSION).
- test_setup_page_unified.py: was monkeypatching resolve_allowed_plugins
  but compute_default_agent_prompt now reads from resolve_user_marketplace
  (Model B-aware). Stub the right function so the test exercises the
  v28 served-set path.

* Harden curated skill/agent inner endpoints against path traversal

`_read_inner`, the `skill_dir` walk in `curated_skill_detail`, and the
`agent_path.stat` in `curated_agent_detail` joined URL path-params onto
`plugin_root` without verifying the resolved candidate stayed inside it.
Starlette's `[^/]+` on `{skill_name}` / `{agent_name}` blocks the direct
URL exploit (encoded `/` 404s before the handler), but a curator-planted
symlink inside a curated marketplace's git mirror could still dereference
outside the plugin tree on read.

Adds `_safe_join(plugin_root, *parts)` doing
`Path.resolve(strict=True)` + `relative_to(plugin_root.resolve())`, used
by all three call sites so the boundary is enforced once and consistently.
Tests cover the helper directly (normal path resolves, escaping `..`
returns None, escaping symlink returns None, missing file returns None)
plus an end-to-end check that the symlink case actually 404s on the
HTTP endpoint. Symlink tests skip on Windows where symlink creation
needs elevated permissions; they run on Linux CI.

---------

Co-authored-by: Minas Arustamyan <arustamyan.minas@gmail.com>
2026-05-08 14:22:19 +02:00
ZdenekSrotyr
16373d6b0b feat(cli): agnes store + agnes my-stack commands
Adds CLI coverage for the new REST surface introduced in this PR:

  agnes store list / show / install / uninstall / upload / delete
  agnes my-stack show / toggle

Covers 11 of the 15 new endpoints — listing, detail, install/uninstall,
upload (multipart), delete, my-stack get + curated toggle. Photo / docs
download endpoints intentionally skipped; analyst-side automation rarely
needs raw bytes back, and the web UI already covers them.

cli/v2_client.py: api_post_multipart + api_put_multipart helpers (httpx
files= passthrough). api_delete + api_put_json fillers were already
needed for non-multipart writes; added together.

Tests: tests/test_cli_store.py — help-text smoke tests + happy-path
mocked tests for list, install, upload, my-stack show, my-stack toggle.
12 new tests, all green.
2026-05-05 08:18:12 +02:00