Plugin System
A plugin is a bundle of skills / commands / hooks distributed over a git repository. One /plugin install drops someone else's workflow into AtomCode and you can use it immediately. AtomCode's plugin protocol is compatible with Claude Code — CC-ecosystem plugin repos install and run out of the box.
Core concepts
- Marketplace — a git repository whose root contains
.atomcode-plugin/marketplace.jsonor.claude-plugin/marketplace.json, declaring the plugins it provides. - Plugin — an entry in a marketplace. It can be a subdirectory of the same repo, or it can point to an external git repository (
url/github/git) or a local path (local). - Skill / Command / Hook — the three asset types a plugin contributes. Skills and commands are namespaced with
plugin-name:(to avoid collisions); hooks fire on events.
Command reference
| Command | Purpose |
|---|---|
/plugin | Open the interactive plugin manager (↑/↓ to select, Enter to confirm, Esc to go back). Browse marketplaces, install/uninstall plugins, add/remove marketplaces — all from one screen |
/plugin marketplace add <url> | Clone a git repo and register it as a marketplace |
/plugin marketplace remove <name> | Remove a registered marketplace (refuses if any of its plugins are still installed) |
/plugin marketplace update <name> | Pull the latest commit for a marketplace repo, refreshing its plugin list |
/plugin marketplace list | List all registered marketplaces |
/plugin install <plugin>@<marketplace> | Install a plugin from the named marketplace |
/plugin uninstall <plugin>@<marketplace> | Uninstall an installed plugin (the marketplace registration is kept) |
/plugin list | List all locally installed plugins |
/plugin reload | Reload all plugins (re-scan disk, refresh skill/hook registration) |
Install a plugin
Method 1: interactive manager (recommended)
Type /plugin in the TUI (no subcommand) to open a full-screen interactive manager. Navigate with arrow keys, confirm with Enter:
/plugin
# → Opens the manager home screen:
# Browse & install — Browse a marketplace's plugins, Enter to install/uninstall
# Add marketplace… — Type/paste a git URL to add a new marketplace
# Remove marketplace… — Pick a marketplace to remove
# Installed (N) — View installed plugins, Enter to uninstall
Installed plugins show a ✓ mark; pressing Enter toggles install/uninstall. Cloning and installing are async — a status line appears at the bottom during the operation and the list refreshes automatically when done.
Method 2: slash-command subcommands
# 1. Register the marketplace (clones its git repo locally)
/plugin marketplace add https://gitcode.com/gmq123/ascend-model-agent-plugin
# 2. Install one of its plugins
/plugin install ascend-model-agent-plugin@ascend-model-agent-plugin
# 3. List installed plugins
/plugin list
Method 3: CLI
atomcode plugin marketplace add <git-url>
atomcode plugin install <plugin>@<marketplace>
atomcode plugin list
atomcode plugin uninstall <plugin>@<marketplace>
atomcode plugin marketplace remove|update|list
The CLI and TUI share the ~/.atomcode/plugins/ directory; anything installed on one side is visible on the other immediately.
After install
- Skills contributed by the plugin appear in the
/menu with a<plugin-name>:prefix, e.g./ascend-model-agent-plugin:ascend-model-verification. - The model can also invoke these skills through the
UseSkilltool. - Hooks require explicit trust before they activate — run
atomcode plugin trust <name>after reviewing the printed notice; hooks load on the next session. See Hook trust flow below.
Directory layout
~/.atomcode/plugins/
├── marketplaces.json # Registered marketplace metadata
├── installed_plugins.json # Status records for installed plugins
├── marketplaces/
│ └── <marketplace-name>/ # The marketplace repository clone
│ ├── .claude-plugin/marketplace.json
│ └── <plugin-subdir>/ # Inline plugin
└── installed/
└── <marketplace>/<plugin>/ # Clones of plugins from external sources
plugin.json shape
The plugin.json in each plugin's root (or its .claude-plugin/ / .atomcode-plugin/ subdirectory):
{
"name": "my-plugin",
"version": "1.0.0",
"description": "What it does",
"skills": ["./skills"], // Also accepts the string "skills"
"commands": ["./commands"],
"hooks": { // Also accepts a path string "hooks.json"
"UserPromptSubmit": [{
"hooks": [{
"type": "command",
"command": "python \"${CLAUDE_PLUGIN_ROOT}/hook.py\"",
"timeout": 5
}]
}]
}
}
Hook events
Plugins can hook the following events (matching CC). Event names are case/style-insensitive — the loader accepts both PascalCase (PreToolUse, SessionStart) and snake_case (pre_tool_use, session_start); the two spellings are equivalent.
| Event | When it fires | Typical use |
|---|---|---|
UserPromptSubmit | After the user submits a message, before the LLM call | Inject additional context / route / block |
PreToolUse | Before a tool call | Validation, blocking, argument rewriting |
PostToolUse | After a tool call | Audit, side effects |
SessionStart | Session startup | Warm-up, logging |
SessionEnd | Session end | Cleanup, archiving |
Stop | Turn ended — normal stop, safety fuse, user cancel, or rate-limit pause | Notify the plugin the turn has ended; trigger follow-up actions |
StopFailure | Turn ended because of an API/provider error (stream failure / timeout) | Notify the plugin the turn failed; alert / log |
Notification | System notification | Forward to external alert channels |
Every turn end fires exactly one of Stop / StopFailure — never both, never none. StopFailure is reserved for turns that ended because of an API/provider failure; a tool error mid-turn does not fire it (the turn continues and ends with its real terminal instead). The stdin payload carries hook_event_name, session_id, cwd, the session's transcript_path (the append-only JSONL transcript, null for non-persistent runs), a CC-style stop_hook_active flag (true when another terminal hook is configured and fires alongside — use it to avoid a Stop hook recursively re-triggering itself), and a kernel stop_reason (e.g. Stopped, ProviderError, Timeout, Cancelled) so a hook can refine the outcome without parsing anything else.
Default-directory auto-discovery
A plugin's assets are discovered from default directories automatically — no need to declare them in plugin.json:
- Hooks — loaded from
hooks/hooks.jsonfirst (CC layout), falling back to a root-levelhooks.json. - Skills — auto-scanned from the
skills/directory. - Commands — auto-scanned from the
commands/directory.
Only when your layout deviates from these defaults (e.g. a custom subdirectory name) do you need explicit "skills" / "commands" / "hooks" fields in plugin.json.
Hook trust flow
To prevent a malicious plugin from silently running arbitrary commands, plugin hooks are protected by a trust gate:
- On install (
/plugin install/atomcode plugin install) — AtomCode prints a notice of the hook commands found. Hooks are not run yet. - Grant trust — after reviewing the notice, run:
AtomCode records a hash of the current hook command set.atomcode plugin trust <plugin-name> - Takes effect next session — trusted hooks are loaded and activated on the next startup.
- Re-trust after updates — if a plugin update changes its hook commands, the stored hash is invalidated automatically; run
trustagain to approve the new version.
To revoke trust:
atomcode plugin untrust <plugin-name>
UserPromptSubmit IO protocol
The hook receives JSON on stdin and decides what to do via stdout:
{
"session_id": "...",
"hook_event_name": "UserPromptSubmit",
"prompt": "the user's input text",
"cwd": "/working/directory"
}
The hook can respond in the following ways (highest priority first):
- Non-zero exit code — blocks the prompt; reason is taken from stderr.
- JSON on stdout
{"decision": "block", "reason": "..."}— block with an explicit reason. - JSON on stdout
{"hookSpecificOutput": {"additionalContext": "..."}}— appendadditionalContextto the user message as extra LLM context. - Plain text on stdout — the whole block is injected as
additionalContext. - Empty stdout / unparseable JSON — passthrough; the prompt reaches the LLM unchanged.
When parsing stdout, AtomCode first tries the last non-empty line as JSON; on failure it falls back to whole-text injection — so a hook script can print debug output earlier without affecting the final decision.
Path variables
Hook command strings can reference these environment variables (injected by the executor):
${CLAUDE_PLUGIN_ROOT}— absolute path of the plugin install directory (CC compatible).${ATOMCODE_PLUGIN_ROOT}— same thing, atomcode alias.
These are real environment variables expanded by the shell itself, not string substitutions — install paths with spaces, quotes, $, or ; won't break the command.
Marketplace management
# List registered marketplaces
/plugin marketplace list
# Pull the latest commit (refreshes the plugin list this marketplace exposes)
/plugin marketplace update <name>
# Remove (refuses if any plugin from this marketplace is still installed)
/plugin marketplace remove <name>
Walkthrough: installing the Ascend plugin
/plugin marketplace add https://gitcode.com/gmq123/ascend-model-agent-plugin
# > cloning marketplace from https://gitcode.com/...
# > marketplace `ascend-model-agent-plugin` added at 7a59537 (1 plugins)
/plugin install ascend-model-agent-plugin@ascend-model-agent-plugin
# > installing `...`...
# > installed `ascend-model-agent-plugin@ascend-model-agent-plugin` — 23 skills loaded, 5 skipped
# This plugin installs a UserPromptSubmit hook (workflow_planner_hook.py).
# When you say "help me verify Qwen3 adaptation on Ascend", it injects a
# workflow JSON and routes the model through the ascend-model-verification
# skill pipeline.
help me verify Qwen3 adaptation on Ascend
Known limits
- Unsupported CC events:
PreCompact/SubagentStopare silently skipped. - Multi-line indented JSON output: when a hook prints with
print(json.dumps(..., indent=2)), the last line is}, which our last-line JSON parser fails on — falling back to plain-text injection. Functionally it still works (the LLM still reads the content) but it's not on the structured path. Hook authors should use single-linejson.dumps(...)output. - Strict skill-name validation:
[a-z0-9_-], matching CC. Skill names with/or mixed case are skipped.
Next steps
- Skills — write your own skills; they work inside plugins too.
- Slash Commands — full
/plugincommand list. - MCP Integration — another path for connecting external tools; complementary to plugins.