Files
Auree Aubert a418d76ee7 Fan the plugin out to Codex and Cursor; bundle session-lifecycle hooks (#50)
* Fan the plugin out to Codex and Cursor; bundle session-lifecycle hooks

- One plugin source, three targets: .agents/plugins/marketplace.json
  (Codex) + .cursor-plugin/marketplace.json, with .codex-plugin/ and
  .cursor-plugin/ plugin manifests version-locked to the router
  (linter-enforced across all three, negative-tested). The approval hook
  is wired per target - PreToolUse (Claude Code), PermissionRequest via
  hooks/codex-hooks.json (Codex), beforeShellExecution via
  hooks/cursor-hooks.json (Cursor) - reusing approve-cli.sh's existing
  per-agent verdict dispatch.
- Plugin-bundled lifecycle hooks (Claude Code): session-start (pinned
  CLI refresh from the plugin's own cli-version + placeholder session
  row; deliberately no skills add - the plugin owns the skills),
  session-checkpoint (Stop; jq-derived title, 45s throttle), and
  session-end (sync finalize + detached claude -p refinement). Each
  defers when the installer's counterpart exists at ~/.claude/hooks/,
  so installer + plugin never double-register a session.
- .gitignore: un-ignore .agents/plugins/ (vendored skills stay ignored).
- README: per-target install steps; router 1.11.0, plugins 1.11.0.

Verified: lint 0/0; scripts behavior-tested with stubbed npm/cargo-ai
(pin read from plugin root, defer guards, checkpoint derivation +
throttle, --finished finalize); live plugin install on Claude Code
2.1.205 shows all four hook events and both extra manifests in the
1.11.0 cache.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Make the plugin the single source of truth; agent-portable docs

- session-start.sh now also refreshes the plugin itself (marketplace
  update + plugin update, detached, effective next session) - the plugin
  channel's replacement for the installer's skills-add refresh, with
  zero duplication by construction.
- Docs made agent-portable: the pinned-install command in cargo/SKILL.md
  and prerequisites.md reads cli-version from the skill directory the
  agent loaded instead of hardcoding ~/.claude/skills/ (which silently
  degraded Codex/Cursor to @latest); manual-path session titles are
  agent-neutral; job 1 says to skip skills add when skills came from a
  plugin; the permission-prompts note covers all three agents' events.
- README Claude Code section describes the refactored installer flow
  (installs the plugin; skills add only as fallback; legacy scaffolding
  migrated away). cargo 1.12.0, plugins 1.12.0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Single-source the lifecycle scripts: dual-mode plugin/standalone

The three hooks/session-*.sh scripts are now the only copies of the
session lifecycle - the installer's fallback channel downloads them
instead of embedding heredocs. Mode auto-detects from location
(~/.claude/hooks/ = standalone; anywhere else = plugin) with an explicit
plugin|standalone arg override:

- standalone: skills add refresh, pin from ~/.claude/skills/cargo/,
  no plugin self-update, never defers.
- plugin: pin from plugin root, detached plugin self-update, no skills
  add, defers when a standalone copy exists (exactly one lifecycle runs).

Log lines carry the mode. Behavior-tested both modes of all three
scripts with stubbed npm/npx/cargo-ai/claude (mode detection, defer,
no-self-defer, per-mode pin paths, per-mode refresh, mode-aware logs).
cargo 1.13.0, plugins 1.13.0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(lifecycle): registration-aware defer, wider PATH, checkpoint hygiene

Review feedback on the lifecycle scripts:

- Defer guard now requires the standalone copy to be REGISTERED in
  ~/.claude/settings.json, not merely present - an unregistered leftover
  file no longer suppresses the plugin copy (which would silently skip
  CLI pinning and session logging). Falls back to file existence when jq
  is unavailable.
- session-start's PATH widening for the detached plugin self-update now
  matches session-end's full list (Volta, asdf shims, nvm version dirs,
  /opt/node*) so `claude` resolves under version managers and the
  update isn't silently skipped.
- Checkpoint prompt snippets strip control characters. The reported
  command-substitution injection does not exist - shell assignments and
  quoted expansions never evaluate a value's content, and the upsert
  passes --title/--summary as single argv words (verified empirically:
  $(touch ...) and backticks pass through as literal data) - but control
  chars could mangle logs/rows, so they are removed and the inertness is
  documented in a comment for future reviewers.

Verified: unregistered leftover -> plugin runs; registered standalone ->
plugin defers; clean env -> runs; injection payload inert with ESC
sequences stripped from the stored title.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(lifecycle): run when standalone registration is unverifiable

Review follow-up: the defer guard's no-jq / no-settings.json fallback
returned "owned" (defer), which silently skipped CLI pinning and session
logging whenever registration couldn't be checked - exactly the failure
the registration-awareness was meant to prevent. Flipped: when jq or
settings.json is unavailable, default to NOT owned so the plugin copy
runs. Safe direction - the session upsert is idempotent, so a duplicate
with an active standalone is harmless, whereas a wrong defer is a silent
skip. Unit-tested all five branches (registered->defer; not-registered,
no-settings, no-jq, no-file->run).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 18:33:21 -07:00

16 lines
259 B
JSON

{
"hooks": {
"PermissionRequest": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"${CLAUDE_PLUGIN_ROOT}\"/hooks/approve-cli.sh codex"
}
]
}
]
}
}