Claude Code compatibility
@alphacro/agent-claude-code-compat
Section titled “@alphacro/agent-claude-code-compat”Package: packages/agent-claude-code-compat
Overview
Section titled “Overview”Load a single Claude Code plugin from disk and return the equivalent AlphaCRO {@link Plugin}. The returned plugin contributes:
- One {@link SlashCommandDefinition} per
commands/*.md. - One {@link SlashCommandDefinition} per
skills/<name>/SKILL.md(skills are invoked the same way as commands but their body is markdown documentation; the host may also surface them in the system prompt via the returnedskillsmetadata). - One {@link SubagentDefinition} per
agents/*.md. - Hook matchers for every supported lifecycle event.
MCP server configs and LSP servers are surfaced on the returned
discovered blob but not auto-launched — that’s a downstream
MCP/LSP plugin’s job.
Contributions
Section titled “Contributions”| Contribution | Declared |
|---|---|
| Tools | ❌ |
| Hooks | ✅ |
| Sub-agents | ✅ |
| Slash commands | ✅ |
| HTTP routes | ❌ |
| RPC methods | ❌ |
| Workflows | ❌ |
| Permissions | ❌ |
| Tool cards | ❌ |
| State slice | ❌ |
| Reducer | ❌ |
| Session start handler | ❌ |
| Session end handler | ❌ |
| Startup phase | ❌ |
| Dependencies | ❌ |
PreToolUsePostToolUseSessionStartUserPromptSubmitStopSessionEndPreCompactNotificationSubagentStartSubagentStop
Package README
Section titled “Package README”Load real Claude Code
plugins straight into the AlphaCRO runtime as Plugin<TState>
contributions.
The package is part of the 4-layer architecture:
- Layer 1 (
@alphacro/agent-core) defines thePluginshape. - Layer 2 is where this package lives: a first-party plugin
adapter that converts the Claude Code file format
(
.claude-plugin/plugin.json,commands/*.md,agents/*.md,hooks/hooks.json,skills/<name>/SKILL.md,.mcp.json) into the shape the runtime understands. - Layer 3 runtime adapters (CF Worker, Node CLI) get to compose these plugins alongside their own first-party ones.
- Layer 4 apps (editor-web, agent-cli) just call
composePlugins({ plugins: [...] }).
Quick start
Section titled “Quick start”import { composePlugins } from "@alphacro/agent-core";import { loadClaudeCodePlugin } from "@alphacro/agent-claude-code-compat";
const { plugin } = await loadClaudeCodePlugin( "/abs/path/to/anthropics/claude-code/plugins/commit-commands",);const composed = composePlugins({ plugins: [plugin] });
// composed.slashCommands now includes:// "commit-commands:clean_gone"// "commit-commands:commit"// "commit-commands:commit-push-pr"To load every plugin in a marketplace-style directory in one call:
import { loadClaudeCodePluginsFrom } from "@alphacro/agent-claude-code-compat";
const all = await loadClaudeCodePluginsFrom( "/abs/path/to/anthropics/claude-code/plugins",);// → Plugin[] for commit-commands, code-review, feature-dev,// security-guidance, explanatory-output-style, …What’s covered
Section titled “What’s covered”| Claude Code surface | AlphaCRO surface | Notes |
|---|---|---|
.claude-plugin/plugin.json | Plugin.name / version / description | Plugin name is namespaced @alphacro/agent-claude-code-compat:<plugin-name>; slash commands and skills are namespaced <plugin>:<command>. |
commands/*.md | slashCommands() | Frontmatter (description, argument-hint, allowed-tools, model) is parsed; the handler returns the body with $ARGUMENTS, $1..$N, and !`shell` substituted. |
agents/*.md | subagents() | name, description, tools, disallowedTools, model, effort, maxTurns, isolation, background are honoured. hooks / mcpServers / permissionMode in plugin agents are recorded as unsupported[]. |
hooks/hooks.json (or inline) | hooks() | command-type hooks are wired into the AlphaCRO hook framework. Decision JSON on stdout (hookSpecificOutput) and exit code 2 (deny) are interpreted. Unsupported hook types (http, mcp_tool, prompt, agent) are skipped with a soft issue. |
skills/<name>/SKILL.md | slashCommands() | Each skill becomes a namespaced slash command whose handler returns the markdown body. The parsed metadata is still on DiscoveredPlugin.skills so a host that wants to inject skill descriptions into the system prompt can do so. |
.mcp.json (or inline) | DiscoveredPlugin.mcpServers | Parsed and surfaced so a downstream MCP plugin can launch them. This package does not spawn processes itself. |
Themes, output styles, monitors, LSP servers and inline channels are parsed by the manifest schema but ignored at runtime — those surfaces don’t have a direct AlphaCRO equivalent today.
Hook → AlphaCRO mapping
Section titled “Hook → AlphaCRO mapping”Claude Code’s hook event names line up almost 1:1 with AlphaCRO’s 17-hook surface. The compat layer maps:
| Claude Code event | AlphaCRO HookName |
|---|---|
SessionStart | SessionStart |
SessionEnd | SessionEnd |
UserPromptSubmit | UserPromptSubmit |
PreToolUse | PreToolUse |
PostToolUse | PostToolUse |
Stop / StopFailure | Stop |
PreCompact | PreCompact |
SubagentStart | SubagentStart |
SubagentStop | SubagentStop |
Notification | Notification |
Events that don’t have a direct equivalent (e.g. TaskCreated,
CwdChanged, WorktreeCreate, FileChanged) are parsed but not
wired in — they’re available on DiscoveredPlugin.hooks for hosts
that want to add bespoke handling.
Path variable substitution
Section titled “Path variable substitution”Inside hook commands, MCP configs, and skill bodies the loader expands the standard Claude Code variables:
${CLAUDE_PLUGIN_ROOT}— absolute path to the plugin install dir.${CLAUDE_PLUGIN_DATA}— persistent data dir (when supplied).${CLAUDE_PROJECT_DIR}— user’s cwd (when supplied).- Plus any
extramap the host passes to {@link expandPluginPathVariables}.
The same variables are also exported as environment variables to spawned hook processes by the default Node runner.
Hosting on non-Node runtimes
Section titled “Hosting on non-Node runtimes”The default hook command runner shells out via Node’s
child_process. Cloudflare Workers / edge runtimes can either:
- Skip hooks entirely (pass
hookCommandRunner: async () => ({ exitCode: 0, stdout: "", stderr: "" })). - Provide a runner that proxies the hook into a sandbox process
(e.g.
@cloudflare/sandboxstartProcess+ capture stdout).
The rest of the package is pure TypeScript with no Cloudflare or
Node-only imports outside discover.ts (which uses node:fs/promises)
and hook-executor.ts (which uses node:child_process). Hosts that
don’t need filesystem discovery can build a DiscoveredPlugin
directly from in-memory sources and feed it through the loader’s
internals.
Tested in two layers
Section titled “Tested in two layers”We don’t vendor upstream Claude Code plugins into this repo. Instead the test suite has two complementary layers:
Layer A — hand-written feature-targeted fixtures
Section titled “Layer A — hand-written feature-targeted fixtures”src/__fixtures__/plugins/ ships five small single-purpose fixtures,
one per parser/loader feature. Each fixture is the minimum content
needed to exercise its surface and stays under the package’s own
maintenance:
| Fixture | Feature it locks down |
|---|---|
commands-fixture | Slash commands, $ARGUMENTS / $1..$N substitution, !`shell` blocks, allowed-tools array, argument-hint (string + array). |
subagents-fixture | Sub-agent frontmatter — tools, disallowedTools, model, isolation: worktree, and the unsupported[] warning surface for hooks / mcpServers / permissionMode. |
pretooluse-hook-python | hooks/hooks.json with a real python3 PreToolUse hook (exit-code 2 deny path). |
sessionstart-hook-bash | hooks/hooks.json with a real bash SessionStart hook returning hookSpecificOutput.additionalContext. |
skill-fixture | skills/<name>/SKILL.md parsing + slash-command namespacing. |
Integration tests compose pretooluse-hook-python into the real
AgentRunner and drive the runner with a scripted mock model that
attempts a forbidden Write — proving the Python hook can block a
tool call end-to-end.
Layer B — pinned upstream snapshot
Section titled “Layer B — pinned upstream snapshot”src/__tests__/upstream.test.ts proves the loader can parse every
real plugin Anthropic ships at a pinned commit of
anthropics/claude-code,
without us vendoring any upstream code into the repo:
scripts/upstream-pin.jsonholds the single source of truth for the pinned commit SHA (read by bothscripts/fetch-upstream-plugins.mjsand the test). Bump it to upgrade the snapshot.pnpm --filter @alphacro/agent-claude-code-compat fetch-upstream(also wired as the package’spretesthook) downloads the tarball into.upstream-cache/<sha>/plugins/— outsidesrc/and excluded via.gitignore.- The snapshot test asserts (a) the loader surfaces no parse issues across the whole tree, and (b) each well-known plugin surface still parses correctly (security-guidance PreToolUse hook, explanatory-output-style SessionStart hook, claude-opus-4-5-migration skill, feature-dev sub-agents).
- If the cache is missing — e.g. CI has no outbound network — the suite skips itself; the Layer A fixtures still lock the parser paths down on their own.
Real upstream agent files routinely ship multi-line unquoted
description: values that strict YAML rejects (literal <example>
blocks, blank lines, even assistant: lines at column 0). When
strict YAML fails the agent loader falls back to the tolerant
key-collecting parser in parseFrontmatterLoose to mirror Claude
Code’s own line-oriented frontmatter handling.
Source
Section titled “Source”- Plugin factory:
packages/agent-claude-code-compat/src/plugin.ts