Skip to content

feat(install): add --skills-dir mode (Claude Code skills-directory plugin) - #10

Open
mh0pe wants to merge 3 commits into
ChristopherKahler:mainfrom
mh0pe:feat/install-skills-dir
Open

feat(install): add --skills-dir mode (Claude Code skills-directory plugin)#10
mh0pe wants to merge 3 commits into
ChristopherKahler:mainfrom
mh0pe:feat/install-skills-dir

Conversation

@mh0pe

@mh0pe mh0pe commented Jun 14, 2026

Copy link
Copy Markdown

What

Adds --skills-dir [--dir <path>] to the installer. It writes a self-contained skills-directory plugin that Claude Code auto-loads in place as carl@skills-dir:

  • .claude-plugin/plugin.json
  • hooks/carl-hook.py + hooks/hooks.json ({hooks:{...}} envelope)
  • mcp/ + .mcp.json (carl-mcp)
  • framework @-includes rewritten to ${CLAUDE_PLUGIN_ROOT}

Default target: <cwd>/.claude/skills/carl/.

Why

  • Zero install — no claude plugin install, no marketplace setup.
  • Works in Claude Code Cloud — the cloud env clones the repo's .claude/skills/ but cannot see local marketplace installs; a committed skills-dir plugin is the supported way to ship a plugin to cloud.
  • Version-pins to the project and travels with the repo.

MCP bootstrapping

A skills-dir (or marketplace) plugin has no install step, so the carl-mcp npm deps aren't present at launch and node mcp/index.js would fail with ERR_MODULE_NOT_FOUND. The emitted plugin therefore includes a fail-open SessionStart hook that idempotently installs the MCP deps into ${CLAUDE_PLUGIN_DATA} and links them onto the MCP's module-resolution path (the MCP is ESM, so a node_modules symlink is used; .mcp.json also sets NODE_PATH as a fallback for any CJS consumer). The hook short-circuits when deps already exist and never blocks the session if npm is unavailable.

Verified: after the SessionStart install, the MCP boots cleanly (CARL MCP Server running on stdio, 30 tools) with no ERR_MODULE_NOT_FOUND.

Notes

🤖 Generated with Claude Code

mh0pe and others added 3 commits June 13, 2026 22:38
…-directory plugin)

Adds --skills-dir [--dir <path>] flag that installs carl as a self-contained
Claude Code skills-directory plugin under .claude/skills/carl/ (or a custom path).
Writes .claude-plugin/plugin.json, copies hooks/ and mcp/, generates hooks.json
and .mcp.json with ${CLAUDE_PLUGIN_ROOT} references so the plugin is relocatable.
Existing --global / --local install paths are unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…, conflict guard)

- Move hooks.json from plugin root to hooks/hooks.json so auto-discovery finds it
- Wrap hooks.json content in {hooks:{...}} envelope to match framework spec
- Add conflict guard: --skills-dir with --global or --local now exits non-zero
- Fix rewriteFrameworkRefs: restore leading @ on @${CLAUDE_PLUGIN_ROOT} substitutions

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The plugin emitted by installSkillsDir() runs node mcp/index.js which
imports @modelcontextprotocol/sdk, but no node_modules are shipped.
Claude Code does not auto-install a plugin MCP's deps.

Fix: emit hooks/install-mcp-deps.py as a SessionStart hook that:
- Is idempotent (sentinel on node_modules/@modelcontextprotocol/sdk)
- Copies mcp/package.json into CLAUDE_PLUGIN_DATA and runs
  `npm install --omit=dev --prefix CLAUDE_PLUGIN_DATA`
- Creates a symlink CLAUDE_PLUGIN_ROOT/mcp/node_modules ->
  CLAUDE_PLUGIN_DATA/node_modules so Node's ESM walk-up resolver
  finds the packages (NODE_PATH is inert for ESM; the symlink is
  the operative mechanism)
- Is fail-open: any error prints a warning to stderr and exits 0

Also adds NODE_PATH=${CLAUDE_PLUGIN_DATA}/node_modules to the emitted
.mcp.json env (belt-and-suspenders for any CJS callers) alongside the
existing CLAUDE_PROJECT_DIR.

Validated:
- node --check bin/install.js passes
- Negative control: mcp/index.js exits ERR_MODULE_NOT_FOUND without deps
- After install: server logs [CARL] init + "running on stdio" (boot proof)
- Idempotent: second run exits in <0.05s (sentinel short-circuits)
- Fail-open: npm stripped from PATH -> exits 0 with warning to stderr
- Conflict guard and --global/--local paths still work

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mh0pe

mh0pe commented Jun 14, 2026

Copy link
Copy Markdown
Author

First-session caveat for the MCP bootstrap: Claude Code may spawn the carl-mcp concurrently with the SessionStart deps-install hook, so on a brand-new install the MCP becomes available from the next session (or after a restart) once its deps are linked. Subsequent sessions short-circuit (deps already present).

@mh0pe

mh0pe commented Jun 14, 2026

Copy link
Copy Markdown
Author

Related PRs — part of a coordinated cross-repo offer (--skills-dir install mode (Claude Code skills-directory plugin)) applied across the carl/base/paul/seed framework forks:

Reviewing them together is recommended; the same change pattern is mirrored per repo.

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.

1 participant