Plugins bundle the small habits and capabilities you want every project to have — a "tdd" skill, a "lint-before-push" hook, an MCP server that talks to Linear. Install one; the agent picks it up automatically next turn.
Out of the box, LingCode ships a working agent. But every developer ends up wanting the same kind of small modifications: a skill that knows your team's TDD style, a hook that runs your linter before any commit, an MCP server that lets the agent query your Linear or PagerDuty. You could paste these instructions into every project's CLAUDE.md, but they don't belong there — they're your habits, not the project's. The right answer is a plugin: a small directory of skills, agents, hooks, and MCP configs that installs once and applies everywhere you want it to apply.
LingCode follows the Claude Code plugin spec, which means a plugin is just a folder with a .claude-plugin/plugin.json manifest at the top and the components arranged underneath in conventional subdirectories. There's no build step, no runtime, no compilation. The agent sees the plugin's contents at load time, namespaces what's in it, and treats it like any other context.
This tutorial covers the two install scopes (per-user and per-project), the silent-failure that catches first-timers (the missing plugin.json), and the consent prompt you'll see when a plugin includes anything that could run shell — which is most of the useful ones.
A plugin is a directory with this shape:
my-plugin/
├── .claude-plugin/
│ └── plugin.json ← required; this is what makes it a plugin
├── skills/
│ └── <name>/SKILL.md ← optional; one folder per skill
├── agents/
│ └── <name>.md ← optional; subagent definitions
├── hooks/
│ └── hooks.json ← optional; UserPromptSubmit / PreToolUse / etc.
├── commands/
│ └── <name>.md ← optional; slash commands
└── mcp/
└── *.json ← optional; MCP server configs
None of the subdirectories are required. The only mandatory file is .claude-plugin/plugin.json — its presence is what tells LingCode "this folder is a plugin." A bare folder without the manifest is silently ignored, which is the #1 reason a freshly-installed plugin "doesn't show up."
LingCode walks two paths looking for plugins:
~/.claude/plugins/<plugin-name>/ — applies to every project on this machine. The right place for your personal habits (a "tdd" style, a personal lint hook)..claude/plugins/<plugin-name>/ in any ancestor of the project's root — applies only when you're in that project (or anything inside it). The right place for project-specific tooling (a "deploy-staging" command, a Linear MCP scoped to this product).Either way, drop the plugin folder there. No registration step, no restart required.
If you have the CLI installed, the canonical install command is:
lingcode plugin install <name-or-url-or-git-spec>
You can pass: a marketplace plugin name (looked up across registered marketplaces), a git URL (cloned and copied), or a local filesystem path. LingCode copies the plugin into ~/.claude/plugins/<name>/ — it doesn't symlink. The reason matters: symlinks dangle when the source is a temp dir from a git clone, and you'd lose the plugin the next time disk got cleaned.
~/.claude/marketplaces/<name>/.claude-plugin/marketplace.json. Run lingcode plugin search to see what's available across the marketplaces you've added. Note: there's no update flow — marketplace add snapshots at install time. To refresh, remove and re-add.
If the plugin contains hooks/hooks.json or a bin/ directory, it can run shell commands as part of the agent's normal flow — that's the whole point. LingCode prompts before installing any such plugin, because "I dropped this plugin in" should not be the same threat model as "I copied this script into ~/.local/bin."
Interactive shells get a Y/N prompt with a summary of what the plugin will do. Non-interactive (CI, scripts) refuses to install unless you pass --yes. This is deliberate friction — the kind that exists because the alternative is plugins quietly installing keyloggers.
Two plugins can each ship a skill called tdd and not collide. Plugin-loaded skills surface as <plugin>:<skill> — so one becomes jane:tdd and the other acme:tdd, and the agent picks the right one from context.
One exception: skills defined directly in your project (.claude/skills/<name>/SKILL.md, not nested under a plugin) keep their bare name. A project-local tdd shadows any plugin's tdd. This is intentional: project-local always wins, because the project author knows best.
disable-model-invocation escape hatchSometimes you want a skill to be available as a user-typed slash command but not auto-invoked by the model. Add disable-model-invocation: true to the SKILL.md frontmatter. The model stops seeing the skill in its system prompt; you still see it in the slash menu and can invoke it manually.
This is the right tool for "I want this skill on hand but I don't trust the model to pick the right moment to use it" — common for skills that involve outbound calls, irreversible actions, or third-party APIs you'd rather audit before each run.
lingcode plugin uninstall <name> removes the plugin directory and any registrations. If you installed it manually (just dropped a folder), rm -rf the folder — there's no DB to clean up.