Adds end-to-end flow for installing and keeping the per-user filtered
Claude Code marketplace in sync with the user's Agnes stack
(admin RBAC grants \ MyAIStack opt-outs U /store installs).
Setup (one-liner in install prompt step 5):
`agnes refresh-marketplace --bootstrap` clones the per-user marketplace
bare repo to ~/.agnes/marketplace, strips PAT from the cloned origin
URL, registers the local path with Claude Code, and installs every
plugin in the served manifest at --scope project. Replaces a 15-line
inline shell sequence that tripped Claude Code's agent-driven `rm -rf`
permission gate.
Auto-refresh (SessionStart hook installed by `agnes init`):
`agnes refresh-marketplace --quiet` runs every Claude Code session,
fetches+resets the clone (server rebuilds as orphan commits, so
pull --ff-only is impossible), and version-aware reconciles:
- missing in workspace -> claude plugin install <name>@agnes --scope project
- version differs -> claude plugin update <name>@agnes
- matches -> skip
Don't auto-uninstall plugins that disappeared from the manifest --
a transient empty manifest from the server would wipe the stack.
Hook output: when --quiet AND something actually changed, emits Claude
Code hook JSON on stdout -- `systemMessage` (transient toast) and
`hookSpecificOutput.additionalContext` (model-side system reminder),
both carrying the change summary plus a "/exit + restart Claude Code"
instruction (Claude only scans plugins at session start).
Windows hook compatibility: the refresh-marketplace hook command is
wrapped in `bash -c "..."` because Claude Code on Windows runs hook
commands directly without invoking a shell, so `2>/dev/null || true`
would otherwise be passed as literal argv tokens.
Cross-cutting:
- cli/lib/marketplace.py: shared CLONE_DIR + MARKETPLACE_NAME constants.
- cli/lib/hooks.py: SessionStart now has two independent entries
(pull + refresh-marketplace) so a failure in one doesn't suppress
the other; legacy `da sync` and prior single-pull layouts upgrade
cleanly on re-init.
- PAT injection on every git fetch via per-invocation credential
helper (token in \$AGNES_TOKEN env, never in argv or .git/config).
- Pre-snapshot of installed plugins captured BEFORE
`claude plugin marketplace update` so silent auto-applied version
bumps still fire notifications.
- scripts/dev/agnes-client-reset.sh: cleans ~/.claude/plugins/marketplaces/agnes,
~/.claude/plugins/cache/agnes, drops uv build cache, documents
workspace-scoped residue that can't be enumerated from the script.
- app/web/setup_instructions.py: legacy AGNES_DEBUG_AUTH path also
uses clone (direct HTTPS marketplace add is broken end-to-end on
every Claude Code distribution -- stores response as single file,
plugin source paths then 404).
28 new tests (test_cli_refresh_marketplace.py) + extended hook + setup
template tests cover bootstrap, fetch+reset ordering, version-aware
reconcile, project-path filtering, hook JSON shape, and the bash-c
Windows wrapper invariant.
33 lines
1.5 KiB
Python
33 lines
1.5 KiB
Python
"""Shared constants for the Claude Code marketplace clone.
|
|
|
|
`agnes init` (via setup_instructions) clones the per-user filtered
|
|
marketplace bare-repo to `~/.agnes/marketplace`, then registers that path
|
|
with Claude Code via `claude plugin marketplace add <path>`. The marketplace
|
|
is named "agnes" inside Claude Code's registry.
|
|
|
|
Both the clone path and the registry name are referenced from multiple
|
|
places (`agnes refresh-marketplace`, future `agnes init` automation, the
|
|
clipboard-copied setup script in `app/web/setup_instructions.py`). Having
|
|
them as constants here keeps them in sync — drift between the setup script
|
|
and the refresh command would silently break the refresh flow.
|
|
|
|
The setup-instructions clipboard text MUST keep the literal string
|
|
`~/.agnes/marketplace` for the clone target so users can copy-paste without
|
|
needing the agnes CLI to be installed yet (chicken-and-egg). The CLI side
|
|
uses `Path.home() / ".agnes" / "marketplace"` for portability.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
# Filesystem location of the marketplace clone. Synchronized with
|
|
# `app/web/setup_instructions.py:_marketplace_block` which writes the
|
|
# literal `~/.agnes/marketplace` into the clipboard-copied setup script.
|
|
CLONE_DIR: Path = Path.home() / ".agnes" / "marketplace"
|
|
|
|
# The marketplace name as registered in Claude Code (`claude plugin
|
|
# marketplace list` shows this). Must match
|
|
# `app.marketplace_server.packager.MARKETPLACE_NAME` server-side and the
|
|
# `_MARKETPLACE_NAME` literal in `setup_instructions.py`.
|
|
MARKETPLACE_NAME: str = "agnes"
|