@yasaswy.nagavolu/agents
v0.19.0
Published
CLI for installing the yasaswy.nagavolu agent/skill/hook bundle. `install` opens an interactive bundle menu in a TTY (or `--bundle <key> --yes` for non-interactive installs), with hash-tracked idempotent deploys, automatic hook wiring into settings.json,
Maintainers
Readme
@yasaswy.nagavolu/agents
CLI for installing the yasaswy.nagavolu agent/skill/hook bundle (Claude agents, skills, hooks, and supporting tooling).
install opens an interactive checkbox menu of purpose-based bundles when
run in a terminal, or installs one bundle non-interactively via
--bundle <key> --yes. Every installed file's hash is recorded in a ledger
for idempotent re-runs. Bundles are declared in the repo-root manifest.json,
validated against manifest.schema.json
(schema decision: #36,
versioning decision: #37),
and CI's manifest-lint job (tools/manifest_lint.py) fails the pipeline if
any repo file is left unclaimed by a bundle or internal[].
Install
npx @yasaswy.nagavolu/agents installIn PowerShell, quote the package name (@ collides with the splatting
operator — harmless elsewhere, required here):
npx '@yasaswy.nagavolu/agents' installor install the CLI globally:
npm install -g @yasaswy.nagavolu/agents
agents-cli installCommands
agents-cli --version— print the installed CLI versionagents-cli install— in a terminal (TTY), opens the interactive bundle menu:↑/↓/j/kmove,PgUp/PgDnjump 5 rows,spacetoggles,aselects all,enterconfirms,q/Ctrl-Ccancels. A live detail pane below the list always shows what the highlighted bundle installs — item counts by kind, real destination paths, MCP servers it registers, component deps, secrets it will prompt for, and an approximate file count/size. If raw-mode keypress handling isn't available, it falls back to a numbered prompt (1,3,5/a/ blank to quit) — same underlying selection logic either way. Piped/non-interactive (CI) input instead prints the bundle list and instructs--bundleusage; exits 0. Colors and the fancy layout are on for a real TTY and off (plain text) whenNO_COLORis set — see The interactive menu below for what it looks like.agents-cli install --advanced— interactive menu over every bundle (not just the public/non-advanced basic set), and for each bundle you pick, a second checkbox lets you deselect individual items within it (includingadvancedOnlyitems like the farm-style-project routing hooks inagent-orchestration) before installing.agents-cli install --bundle <key> --yes— install one bundle non-interactively (--yesrequired to confirm). A bundle that declaresdeps(e.g.litellm) also runs that dependency's component flow — see Components (LiteLLM-backed bundles) below. After files are installed, hooks wired, and any component set up, the CLI prompts (in a TTY) for any secret the bundle (or its component) declares that isn't already set — see Secrets below.--yesalone does not skip that prompting; pass--no-secretsexplicitly if you want to.agents-cli install --bundle <key> --yes --target-dir <path>— install into<path>instead of the default~/.claude(mainly for testing — don't point this at a real~/.claudeyou care about without reviewing the diff)agents-cli install --bundle <key> --yes --target-config <path>— wire anymcp-kind items into<path>instead of the default sibling.claude.jsonnext to--target-dir(mainly for testing — see MCP servers)agents-cli secrets/agents-cli secrets --bundle <key>/agents-cli secrets --list— see Secrets below.agents-cli update --yes/agents-cli update --bundle <key> --yes— see Updating below.agents-cli --help— show usage
The interactive menu
In a color-capable terminal, agents-cli install looks roughly like this
(✓/colors omitted from this plain-text README, but selected rows get a
green check, the cursor row is highlighted, and each bundle that needs a
component or asks for a secret gets a dim ↳ hint line of its own directly
below the title — needs: ... is yellow, asks: ... is cyan (a
component-owned secret like LiteLLM's is tagged (component)), and
[passthrough] is magenta; bundles with nothing to show stay one line):
@yasaswy.nagavolu/agents v0.9.1
Select bundles to install
[ ] Session Tools — Resume past Claude Code sessions by name and archive session transcripts to markdown
[ ] Shipping — Ship code changes end-to-end: sync, test, commit, push, and file the GitLab issue update in one skill
↳ asks: GITLAB_TOKEN
❯ [✓] Reddit Reader — Read Reddit posts/threads into Claude Code from a URL, with a deterministic prompt-intent nudge
↳ asks: REDDIT_CLIENT_ID, REDDIT_CLIENT_SECRET, REDDIT_USER_AGENT
[ ] Skill Authoring — code-is-law: turn skill/agent-file prose rules into an enforced PostToolUse lint
────────────────────────────────────────────────────────────────
Reddit Reader — selected
1 skill, 2 hooks (8 files, 201.5 KB)
→ ~/.claude/skills/read-reddit-post
→ ~/.claude/proxy/reddit_intent_hook.py
Asks: REDDIT_CLIENT_ID (optional), REDDIT_CLIENT_SECRET (optional), REDDIT_USER_AGENT (optional)
────────────────────────────────────────────────────────────────
↑/↓/j/k move PgUp/PgDn jump space toggle a all enter confirm q quitOnce you confirm a selection, each bundle installs with a determinate progress bar (file counts are known up front from the manifest), then a one-line summary:
Installing Visual Review ━━━━━━━━━━━━━━━━╸─────── 42/61 files
✓ visual-review — 61 file(s) written, 0 unchanged, 1.2 MB, installed to C:/Users/you/.claude
✓ wired 3 hook(s) into C:/Users/you/.claude/settings.jsonAfter every requested bundle finishes, a final summary recaps what landed where and what's next:
Summary
✓ Reddit Reader — 8 file(s), 201.5 KB, 1 hook(s) wired
Installed to C:/Users/you/.claude. Next: agents-cli secrets to fill in any skipped secrets, agents-cli update --yes to refresh later.NO_COLOR=1 (or a non-TTY stdout) strips all color from every line above
but keeps the same layout and information; a terminal that can't do raw-mode
keypress handling at all falls back further, to the plain numbered prompt
described above.
Bundle dispositions in the menu
- installable — installs immediately, whether or not the bundle declares
deps. A bundle withdeps(LiteLLM-backed: Councils, Review Council, Agent Orchestration, Web Search, Model Routing) is shown with a(needs: litellm)hint and, on selection, also runs that component's install flow — see Components below. - passthrough —
planning-ideationonly: not shipped by this package at all. Selecting it printsnpx skills add mattpocock/skillsinstead of installing anything.
Every installed file's hash is recorded in <target-dir>/agents-ledger.json.
Re-running install with unchanged source files is a no-op; changed or
missing files are re-copied and the ledger updated.
Hook wiring
Hook items that carry wiring data in manifest.json (event, matcher, type,
command — see proxy/README.md for what each hook does) are automatically
merged into <target-dir>/settings.json's hooks section right after their
files are installed — no manual settings.json editing required for a public
install.
- Idempotent. Re-running
installnever produces duplicate hook entries — the dedupe key is (event, matcher,if, resolved command path). - Non-destructive. The whole settings.json is round-tripped through
JSON.parse/JSON.stringifyand only the specific hook blocks we own are touched — every unrelated key and hook entry you already have is preserved verbatim, in place. - Atomic. Writes go through a temp-file-then-rename, same as the ledger.
- One-time backup. Before the very first write this CLI ever makes to
your settings.json, it's copied to
settings.json.bak-<timestamp>— a marker on the ledger means this only ever happens once, not on every install. - Multi-registration hooks. An item's
wiringfield can be a single object or an array of objects, for a hook that needs registering under more than one tool matcher (e.g.html_mocker_subagent_guard.pyunder bothBashandMonitor). - Python required. Every hook shipped today is a Python script. Before
wiring a Python-backed hook, the CLI checks
python3/pythononPATH. If neither is found, that hook's files still install but it is not wired — you'll see a message telling you to install Python and re-runagents-cli install --bundle <key> --yes, which re-attempts wiring only (already-installed files are a no-op; the ledger is untouched for them). - Out of scope this slice.
statuslinekind items carry wiring data too, but their execution (writing the top-levelstatusLinekey) is deferred to a later slice.
MCP servers
mcp-kind items (e.g. docs-lookup's Context7 server) carry a wiring
object shaped {name, ...serverDef} — name is the key to write under
mcpServers, and the rest (type/url/headers, or type/command/
args/env for stdio transports) is the entry itself. install merges it
into the real user-scope MCP config file Claude Code reads (~/.claude.json
by default — a sibling of ~/.claude, overridable with --target-config for
testing) right after files are installed:
- Idempotent. Re-running
installwrites the same entry again only if it changed; an unchanged entry is reported as already-wired, not re-written. - Non-destructive. The whole config file is round-tripped through
JSON.parse/JSON.stringify— only themcpServersentries this bundle owns are touched; every other server entry and top-level key is preserved verbatim. - Atomic + one-time backup. Same guarantees as hook wiring: an
atomic temp-file-then-rename write, and a
.claude.json.bak-<timestamp>backup taken once, before this CLI's first-ever write to that file. - Secret placeholders. A string value of the form
"${SECRET_NAME}"anywhere in the entry (e.g. an auth header) is resolved at wire time via the same secrets contract described in Secrets below. An unresolved placeholder is dropped rather than written literally — e.g. Context7's HTTP MCP works keylessly (rate-limited), so installing withoutCONTEXT7_API_KEYset writes acontext7entry with noheaderskey at all, instead of blocking the install or writing a broken template string. - Uninstall.
agents-cli uninstallremoves exactly themcpServersentries this bundle wired (tracked per-bundle on the ledger). An entry hand-edited since install (its current value no longer matches what was last written) is left in place, same protection as file uninstall gives hash-mismatched files.
Components (LiteLLM-backed bundles)
Five bundles — Councils, Review Council, Agent Orchestration, Web Search,
Model Routing — declare deps: ["litellm"]. Installing any one of them also
runs the litellm component's install flow, defined once in
manifest.json's top-level components.litellm (not duplicated per bundle):
- Detect.
litellm --versionon PATH? Already set up — nothing to install. - Install. Not found:
uv tool install "litellm[proxy]"ifuvis on PATH (install uv); otherwisepipx install "litellm[proxy]"ifpipxis on PATH. Neither installer found: the bundle's own files still install, but LiteLLM itself is left pending — an actionable message tells you what to run, and re-runningagents-cli install --bundle <key> --yesonceuv/pipxis on PATH finishes the setup. - Config + start scripts.
<target-dir>/litellm/config.yaml(a public, env-parameterized template — every provider key is read via LiteLLM'sos.environ/VAR_NAMEsyntax, nothing in the file is a real secret) plus<target-dir>/litellm/start-litellm.ps1and.sh. Both, like every other installed file, are hash-ledger tracked and idempotent.
Starting the proxy is a separate, manual step (no service supervision in this release):
# Windows
powershell -ExecutionPolicy Bypass -File ~/.claude/litellm/start-litellm.ps1
# macOS/Linux
~/.claude/litellm/start-litellm.shEither script loads <target-dir>/agents.env into the process environment
first (a real exported env var of the same name always wins), then runs
litellm --config config.yaml --port 4000 in the foreground — Ctrl-C or
closing the window stops it; re-run the script to start it again.
Secrets. manifest.json's components.litellm.secrets declares
LITELLM_MASTER_KEY (required for any LiteLLM-backed bundle to work — pick
any random string) plus one optional provider key per model alias the
shipped config registers (GAISTUDIO_API_KEY for Gemini, DEEPSEEK_API_KEY,
ZAI_API_KEY for GLM, TOGETHERAI_API_KEY for Qwen, PERPLEXITY_API_KEY,
and OPENAI_API_KEY for a bonus generic alias no bundle calls yet). These are
prompted once, deduped by name, the same way a secret shared by two
bundles is — selecting both Councils and Web Search in one install run
doesn't ask for LITELLM_MASTER_KEY twice. Missing a provider key doesn't
break the proxy or the other models; only that one model 401s when called —
delete its block from config.yaml if you don't want it registered at all.
Uninstall. Uninstalling the last remaining bundle that depends on
litellm removes <target-dir>/litellm/config.yaml and the start scripts
(if untouched since install — an edited file is left in place, same
protection every other uninstall gives you). The actual litellm binary
(installed via uv/pipx) is left installed — uninstalling a shared
tool you may use elsewhere isn't this CLI's call to make; the uninstall
output tells you the exact command to remove it yourself if you want to.
Uninstalling one of several dep-bundles while another is still installed
leaves the component alone entirely.
Python environments (python-kind item field)
A kind:"skill" item whose deployed directory needs its own locked Python
dependencies (not just Claude-facing markdown/scripts) declares:
{ "path": "skills/edit-videos", "kind": "skill", "python": { "manager": "uv", "locked": true } }manager is currently only "uv"; locked must be true — the CLI never
resolves a fresh lockfile, only <manager> sync --locked. The item's folder
must ship its own pyproject.toml and uv.lock (tracked files, same as any
other content under path).
- After the item's files are copied, the CLI runs
uv sync --locked --no-devwithcwdset to the deployed directory (<target-dir>/<path or targetPath>), creating.venvthere — seepackages/cli/lib/pyenv.js. This is install-time only: no network access happens when the skill actually runs later. - Idempotent. Re-running install/update against an unchanged lockfile
is a fast no-op —
uvitself decides that, the CLI always invokes it. .venv/__pycache__are never copied from the source repo (fsutil.js'swalk()already skips dot-directories and caches) and are never treated as orphaned byupdate— the CLI never ledger-tracks.venv, so upstream-removed-file detection can't touch it.uvmissing. The item's own files still install; the CLI reports that one item asmissing-uvwith the exact install command (irm https://astral.sh/uv/install.ps1 | iex) instead of leaving a partial.venv— nothing else in the bundle is affected. Amissing-uvorfailedsync is surfaced again at the very end of the install/update run (a "Python environments needing attention" block), not just among the per-bundle bullets — easy to miss otherwise.- Uninstall removes the item's deployed
.venvalong with its files.
Secrets
Some bundles declare secrets in manifest.json (e.g. GITLAB_TOKEN for
shipping/agent-orchestration,
REDDIT_CLIENT_ID/REDDIT_CLIENT_SECRET/REDDIT_USER_AGENT for
reddit-reader), and components declare secrets of their own (e.g.
LITELLM_MASTER_KEY and the provider keys for litellm — see
Components above). For each secret a
selected bundle (or a component it depends on) declares that isn't
already resolvable, install prompts for it right after files are copied and
hooks wired:
- Masked input. Characters are echoed as
*, not shown in the clear. - Every prompt is skippable. Press Enter on an empty line to skip — the CLI records nothing for a skipped secret and moves on; nothing forces you to fill in a value you don't have yet.
- Live-validated where feasible.
GITLAB_TOKENis checked againstGET https://gitlab.com/api/v4/user(5s timeout). A rejected token offers retry / skip / keep-anyway; a network error just warns and accepts the value as entered. Every other secret is hint-only (no live check). - Re-run any time.
agents-cli secretsre-prompts for whatever's still missing across every installed bundle (oragents-cli secrets --bundle <key>for one bundle).agents-cli secrets --listshows which secret names are set vs. missing — it never prints a value.
Where secrets live: <target-dir>/agents.env (default
~/.claude/agents.env) — never settings.json, and never committed or
transmitted anywhere. They never leave this machine. The file gets
user-only permissions right after every write (icacls ... /grant:r
"%USERNAME%":F on Windows, chmod 600 on POSIX) — a permissions failure is
reported as a warning, never silently swallowed and never fatal to the write
itself.
How hooks read them back. A hook that needs a secret calls
get_secret(name) from proxy/agents_env.py (a small stdlib-only module
shipped alongside any bundle whose hooks need it). The Node CLI has the same
contract in packages/cli/lib/agentsEnv.js. Both resolve, in order: (1) a
real environment variable of that name, (2) <base>/agents.env, where base
is the AGENTS_ENV_DIR environment variable if set, else ~/.claude. A real
environment variable always wins, so exporting a secret in your shell (or CI)
works with no agents.env file at all.
Updating
agents-cli update --yes # every bundle recorded in the ledger
agents-cli update --bundle <key> --yes # one bundleOnce this package is published, the real npx flow (nothing to install first) is:
npx @yasaswy.nagavolu/agents@latest updateIn PowerShell, quote the package name (@ collides with the splatting
operator — harmless elsewhere, required here):
npx '@yasaswy.nagavolu/agents@latest' updateEvery installed file is compared three ways — its hash in the shipped
source, its hash in <target-dir>/agents-ledger.json (what was last
installed), and its actual on-disk hash:
- Untouched, source changed — silently overwritten; the ledger hash is updated. This is the common case and produces no prompt.
- Edited by you, source unchanged — left alone; noted in the summary.
- Missing on disk (e.g. a file added to the bundle since you installed) — reinstalled.
- Edited by you AND changed upstream — a genuine conflict. In a
terminal you get a unified diff (capped at ~200 lines) and a per-file
keep / overwrite / backupprompt (backupwrites<file>.bak-<cliVersion>next to it, then overwrites). Piped/non-TTY input with neither--theirsnor--keep-mineresolves nothing for conflicts — they're listed, the command exits 2, and every other (non-conflicting) change in the run still applies. Plain--yesdoes not by itself resolve a conflict.--theirs— non-interactively take the new source for every conflict.--keep-mine— non-interactively keep your on-disk version for every conflict (its ledger hash is deliberately left as-is, so the same conflict is reported again on the nextupdateuntil you reconcile it —updatenever silently treats a kept edit as the new baseline).
- No longer shipped by the bundle — removed if untouched since install, or kept in place with a warning (and dropped from the ledger, untracked) if you'd edited it.
Hook and MCP wiring are refreshed on every update to match the current
manifest: a wiring entry the manifest still declares is left as-is (or
re-wired if missing); one the manifest no longer declares is unwired the
same way uninstall would, and dropped from the ledger.
--target-dir <path>/--target-config <path>work the same as forinstall/uninstall.--jsonprints a machine-readable summary ({ targetBase, bundles, unresolvedConflicts, exitCode }) instead of prose.
0.18.0: line-ending-insensitive content hashes; existing ledgers migrate
automatically. A text file's hash now normalizes CRLF to LF first (binary
files are unaffected), so a Windows checkout no longer produces a false
conflict against an LF-deployed copy of the same content. A ledger entry
written before 0.18.0 still recognizes an unchanged file (via its old
raw-byte hash) and silently re-baselines to the new hash on the next
update — no action needed, and a real edit is never masked by this.
Uninstall
agents-cli uninstall --bundle <key> --yesRemoves everything install put down for that bundle:
- Every installed file whose on-disk hash still matches the ledger is deleted (and now-empty parent directories are cleaned up). A file you edited since install is left in place with a warning — uninstall never discards your changes — but its ledger entry is still dropped, since the bundle is no longer tracked.
- Exactly the settings.json hook entries this bundle wired (tracked per-bundle on the ledger) are removed — nothing belonging to any other bundle is touched. Now-empty hook blocks/events are pruned.
- Exactly the MCP config entries this bundle wired (tracked per-bundle on the ledger) are removed the same way — see MCP servers.
- An unknown
--bundlekey fails with an error listing every bundle key the manifest declares. A valid bundle key that was never installed at the target is a no-op (prints a message, exits 0) rather than an error. --target-dir <path>/--target-config <path>work the same as forinstall.
Development
cd packages/cli
npm testTests run against scratch directories (via --target-dir) — they never
touch a real ~/.claude. Raw-mode TTY interaction (tty-menu.js) is
deliberately not exercised in automated tests — its selection logic is
factored into lib/menu.js (pure functions, tested directly); only the
keypress/redraw plumbing is untested here.
The manifest orphan-lint (tools/manifest_lint.py) has its own pytest suite:
python -m pytest tools/tests/test_manifest_lint.py -qproxy/agents_env.py (the Python half of the secrets-resolution contract,
consumed by hooks) has its own pytest suite, isolated from any real ambient
secret via a nonce env-var name and monkeypatch/tmp_path:
python -m pytest proxy/tests/test_agents_env.py -qSource
gitlab.com/yasaswy22/my-agents (private repo; package published publicly to npm).
