A plugin is an installable package that contributes one or more of:
skills, sub-agents, slash commands, MCP servers, or lifecycle hooks.
Plugins are how third-party authors extend xc without forking the CLI.
See also: Authoring a plugin · Hooks reference · Marketplace reference
# Install from a subscribed marketplace
xc plugin install linear@anthropic-marketplace
# Install from a GitHub repo
xc plugin install github:owner/repo
# Install from a local path (great for plugin development)
xc plugin install ./my-plugin
# List what's installed
xc plugin list
# Remove a plugin
xc plugin uninstall linear@anthropic-marketplaceInside an interactive CLI session, after install / uninstall /
enable / disable, run /plugin refresh to apply the new
contributions (skills / agents / commands / hooks / MCP servers)
in one shot — no restart needed. MCP servers reconnect as part of the
same pass and incur the same prompt-cache miss /mcp refresh does.
If you only want to reload MCP config without touching plugins, run
/mcp refresh on its own.
The same operations exist in both surfaces — pick whichever you're in:
| Action | Slash command (interactive) | Non-interactive |
|---|---|---|
| List plugins | /plugin list |
xc plugin list |
| Show plugin details | /plugin info <id> |
xc plugin info <id> |
| Install | /plugin install <source> |
xc plugin install [--yes] <source> |
| Uninstall | /plugin uninstall <id> |
xc plugin uninstall <id> |
| Enable / disable | /plugin enable|disable <id> |
xc plugin enable|disable <id> |
| Search marketplaces | /plugin search <keyword> |
xc plugin search <keyword> |
| Update | /plugin update <id|--all> |
xc plugin update <id|--all> |
| Diagnose problems | /plugin doctor |
xc plugin doctor |
| Manage marketplaces | /plugin marketplace … |
xc plugin marketplace … |
The non-interactive form is intended for scripts and CI. xc plugin install runs a y/N consent prompt by default; pass --yes to skip it
when you're scripting trusted installs.
xc plugin install accepts four kinds of source:
| Form | Example | Notes |
|---|---|---|
<name>@<marketplace> |
linear@anthropic-marketplace |
Looks up the entry in the cached marketplace index and installs from its declared source. Requires the marketplace to be subscribed (and refreshed). |
github:<owner>/<repo>[#ref] |
github:foo/bar, github:foo/bar#v1.2.0 |
Shallow git clone of the repo. Ref is optional and may be a branch or tag. |
https://… or git@… |
https://gitlab.example/foo/bar.git |
Any git-clone-able URL. |
| Filesystem path | ./my-plugin, /abs/path/to/plugin |
Useful for plugin authors iterating locally. |
The first three install under the local marketplace unless you used
the <name>@<marketplace> form. The marketplace name shows up in
/plugin list so you can tell at a glance where each plugin came from.
xc plugin install shows a preview before committing the install:
About to install: linear@anthropic-marketplace v1.2.0
Linear issue integration
Source: github:anthropics/linear-plugin
Marketplace: anthropic-marketplace [reserved/official] [verified]
Author: Anthropic
License: MIT
Will contribute:
- skills (added to /skill list)
- MCP servers (will be spawned as subprocesses): linear
- Lifecycle hooks (will run shell commands on: PostToolUse)
Proceed with install? [y/N]
The two red items in the list — MCP servers and lifecycle hooks —
are the load-bearing trust decisions: both run code on your machine.
Inspect the source before answering y for anything from a marketplace
you don't trust.
In non-TTY environments (CI, piped install scripts), the prompt
defaults to no. Pass --yes to opt out:
xc plugin install --yes linear@anthropic-marketplace/plugin install always runs in --yes mode — typing the command
in the chat is treated as explicit consent. Concretely it does not
show:
- the consent preview — you can't see what the plugin will contribute (commands / agents / MCP / hooks) before it lands
- userConfig prompts — plugins with
userConfigfields install with empty values, and the hook subprocess seesenventries set to<unset>
If the plugin has userConfig, or you want to review the consent preview, use the CLI form instead:
# In another terminal (omit --yes to walk the prompts)
xc plugin install <source>Then back in the interactive CLI run /plugin refresh to pick up the
new plugin without restarting.
We may add an inline modal later so the slash form can prompt too; track this as a known limitation today.
A plugin's enable flag lives in one of two scopes — same convention as
skills (see packages/core/src/skills/settings.ts):
| Scope | File | Notes |
|---|---|---|
user |
~/.x-code/settings.json |
Default for xc plugin enable|disable. |
project |
<cwd>/.x-code/settings.local.json |
Per-user, gitignored. Overrides user. |
The shape inside each file:
A plugin not listed in either scope defaults to enabled. Disable
explicitly when you want it off. project settings win over user.
Pick the scope explicitly via --scope:
# Disable a plugin in this project only, leaving other projects untouched
xc plugin disable linear@anthropic-marketplace --scope=project
# Enable in user scope (the default)
xc plugin enable linear@anthropic-marketplace --scope=userThe slash command form (/plugin enable | disable) accepts the same flag.
Everything plugin-related lives under ~/.x-code/plugins/:
~/.x-code/plugins/
├── known_marketplaces.json # subscribed marketplaces
├── marketplaces/
│ └── anthropic-marketplace/
│ └── marketplace.json # cached marketplace index
├── cache/
│ └── anthropic-marketplace/
│ └── linear/
│ └── 1.2.0/ # actual installed plugin
│ ├── .claude-plugin/plugin.json
│ ├── skills/
│ ├── mcp.json
│ └── hooks/hooks.json
├── data/
│ └── linear@anthropic-marketplace/ # persistent per-plugin data
│ # (survives uninstall+reinstall)
└── installed_plugins.json # bookkeeping
The data/ directory is preserved on uninstall, so reinstalling
recovers any state a plugin chose to save there.
Two startup escape hatches:
xc --no-plugins # skip plugin discovery entirely
xc --no-hooks # load plugins but skip hook executionUse --no-plugins when you suspect a plugin is the cause of a
problem; --no-hooks keeps skills / agents / MCP from broken plugins
active but mutes lifecycle hooks for a session.
Plugin contributions (skills / sub-agents / commands / hooks / MCP
servers) are folded into their respective registries at CLI startup.
After installing, uninstalling, enabling, or disabling a plugin
mid-session, run /plugin refresh to reload everything live
without restarting:
> /plugin refresh
Reloaded plugins — added: my-new-plugin@local; unchanged: linear@anthropic-marketplace.
Downstream: 3 skill change(s), 1 command change(s).
MCP — added: my-new-mcp-server.
Note: next message rebuilds the system prompt, so prompt-cache will miss once.
What happens internally:
- Re-scan installed plugins, re-parse every manifest.
- Rebuild PluginRegistry in place (object identity stays — every captured ref is still valid).
- Fold new skills / sub-agents / commands / hooks into their registries.
- Re-read user + project MCP config files, merge in the new plugin-contributed servers, then
McpRegistry.restartAll(...)— same restart path/mcp refreshuses. - Invalidate
systemPromptCacheso the next message rebuilds the prompt (one cache miss, expected).
/mcp refresh still works standalone: when you only want to reload
MCP config without touching plugins, run it directly — it now also
includes the current plugin-contributed servers in the merged map, so
they aren't silently dropped on reload.
/plugin list and /plugin info always reflect the live state.
# update one
xc plugin update linear@anthropic-marketplace
# update every installed plugin
xc plugin update --all--all runs sequentially, skip-on-error: one failing plugin doesn't
abort the rest. At the end you get a one-line summary
Summary: N updated, M unchanged, K failed. Bare xc plugin update
is rejected — you must explicitly pass either an <id> or
--all, so a typo can't accidentally re-clone every plugin. (Same
defensive stance as Gemini CLI's extensions update --all.)
The slash form /plugin update <id|--all> mirrors the CLI. After
updating, run /plugin refresh so the new versions' skills, agents,
commands and hooks fold back into their registries.
A plugin's manifest can declare what user-supplied configuration it needs (API keys, account ids, base URLs, …):
{
"userConfig": [
{
"key": "LINEAR_API_KEY",
"type": "string",
"sensitive": true,
"prompt": "Enter your Linear API key",
"required": true,
},
{ "key": "BASE_URL", "type": "string", "default": "https://api.example.com" },
],
}At install time (when not running with --yes) the CLI walks each
field and prompts for a value; sensitive: true fields are entered
with local echo suppressed (git-style password input).
Values are stored in ~/.x-code/plugins/user-config.json with file mode
0600 (owner-read-write only). At hook execution and at plugin-
contributed MCP server launch, they are injected into the child process
env keyed by the manifest's key. So a hook script can just read
process.env.LINEAR_API_KEY — no glue.
sensitive: true currently only controls display
during input, not the at-rest storage. Real OS keychain integration
(macOS Keychain / Windows Credential Manager / Linux libsecret) is a
followup. On Windows 0600 is effectively a no-op for ACL reasons —
plan storage accordingly.
--yes installs skip the prompt; values stay unset. To pre-seed values
for CI, hand-write ~/.x-code/plugins/user-config.json before the
install. The /plugin install slash command also skips this
(it runs as if --yes were passed), so install userConfig-bearing
plugins with xc plugin install instead — see "Limitations of the
/plugin install slash command" above.
To watch plugin loading, hook execution, and marketplace fetches live:
xc --plugin-debug
# equivalent to
XC_PLUGIN_DEBUG=1 xcThis mirrors plugins.* / plugin.* / hooks.* / marketplace.*
debug breadcrumbs to stderr, without flipping DEBUG_STDOUT=1 (which
would dump every debug tag, far noisier).
| Symptom | First thing to try |
|---|---|
| Plugin doesn't appear after install | Run /plugin refresh (inside an interactive session) or restart xc. Contributions bind on the registry. |
/plugin doctor shows load errors |
Check the file path it reports — usually a manifest typo. |
| MCP server from a plugin won't connect | Run /mcp list — plugin-contributed servers appear there too. |
| Hook fires unexpectedly | Set DEBUG_STDOUT=1, restart, then tail ~/.x-code/logs/debug.log and grep hooks.. |
| Suspect a plugin is breaking everything | Launch with xc --no-plugins. If the problem disappears, isolate with /plugin disable <id> + refresh. |
| Hook is slow / hangs | Launch with xc --no-hooks. Each hook also has a 5s default timeout. |
xc deliberately reads .claude-plugin/plugin.json in addition to its
native .x-code-plugin/plugin.json path. A plugin authored for Claude
Code will install in xc without modification — its skills, agents,
MCP servers, and hooks all wire up the same way. The two fields we
don't support (output-styles, lspServers) are silently ignored;
everything else loads.
Gemini extensions (gemini-extension.json) are not supported.
Trying to install one prints a friendly error pointing at this doc.
{ "enabledPlugins": { "linear@anthropic-marketplace": true, "k8s-debug@local": false, }, }