Welcome back!
In Chapter 2: Marketplace Manager, we learned how to find the "Grocery Store" (the marketplace) and browse its catalog. We know what plugins are available.
Now, we need to actually buy (install) them.
This might sound simpleβjust copy files, right? But what if you buy a flashlight (Plugin A), but it requires batteries (Plugin B)? What if your company has banned Plugin B for security reasons?
This is where the Installation Orchestrator comes in. It is your personal Shopping Assistant. It ensures that when you ask for a tool, you get everything you need, nothing you're forbidden from having, and it sets everything up correctly.
The Installation Orchestrator is a workflow engine. It doesn't just "download" files; it coordinates a safe installation process.
The 4-Step Workflow:
In the code, plugins can depend on other plugins. If we didn't handle this, your plugin would crash immediately because it's missing parts.
We use a tool called dependencyResolver.ts to figure this out.
react-helperreact-helper needs: node-toolsnode-tools needs: file-system-accessThe Orchestrator must find the whole chain before installing anything.
// dependencyResolver.ts
export async function resolveDependencyClosure(rootId, lookup) {
const closure = []; // The final list to install
// 1. Start with the plugin the user asked for
// 2. Look at its 'dependencies' list
// 3. Repeat for every dependency found (Recursion)
// 4. If we find a cycle (A needs B, B needs A), STOP!
return { ok: true, closure };
}
Beginner Explanation:
This function creates a "Closure"βa fancy word for "The complete list of everything you need." If Plugin A needs Plugin B, the closure is [A, B].
Before we download anything, we check the rules. In an enterprise environment, administrators might block certain plugins.
We use pluginPolicy.ts to act as the Bouncer.
// pluginPolicy.ts
import { getSettingsForSource } from '../settings/settings.js'
export function isPluginBlockedByPolicy(pluginId: string): boolean {
// Check the 'managed-settings.json' file
const policy = getSettingsForSource('policySettings');
// If the admin set this plugin to 'false', it is blocked.
return policy?.enabledPlugins?.[pluginId] === false;
}
Beginner Explanation: If the Orchestrator sees that any plugin in the chain (even a dependency) is blocked, it cancels the entire installation. It refuses to install a "safe" plugin if it drags in a "dangerous" dependency.
How do these pieces fit together? Let's visualize the installResolvedPlugin function, which is the heart of this chapter.
The heavy lifting happens in pluginInstallationHelpers.ts. This file combines resolution, policy, and file copying into one smooth operation.
Here is a simplified view of installResolvedPlugin:
// pluginInstallationHelpers.ts
export async function installResolvedPlugin({ pluginId, entry }) {
// 1. Security First: Is the main plugin blocked?
if (isPluginBlockedByPolicy(pluginId)) {
return { ok: false, reason: 'blocked-by-policy' };
}
// 2. Calculate the Shopping List (Closure)
const resolution = await resolveDependencyClosure(pluginId, ...);
// 3. Check if any *dependencies* are blocked
for (const id of resolution.closure) {
if (isPluginBlockedByPolicy(id)) return { ok: false, error: '...' };
}
// ... proceed to install ...
}
Once we know the list is safe, we need to put the files on the disk. We don't run the plugin directly from the marketplace folder; we copy it to a specific Versioned Cache.
This ensures that if the marketplace changes tomorrow, our installed version stays stable.
// pluginInstallationHelpers.ts (Simplified)
// Loop through every plugin in the list
for (const id of resolution.closure) {
// "Materialize" it: Download/Copy to ~/.claude/plugins/cache/v1/...
await cacheAndRegisterPlugin(id, entry);
}
Why copy it?
You might notice references to zipCache in the code.
// pluginInstallationHelpers.ts
if (isPluginZipCacheEnabled()) {
// Convert the folder to a .zip file
await convertDirectoryToZipInPlace(finalPath, zipPath);
}
This is an optimization. Instead of thousands of tiny files, the system bundles the plugin into one .zip file. This is much faster for the computer to move around.
Sometimes, there is no human user (e.g., a server running Claude Code). We call this "Headless Mode."
The logic is similar, but instead of asking a user for permission, it looks at a settings file and makes the disk match the settings automatically.
// headlessPluginInstall.ts
export async function installPluginsForHeadless() {
// 1. Look at the settings file (The Desired State)
// 2. Look at the disk (The Current State)
// 3. Make them match!
await reconcileMarketplaces(...);
}
This ensures that if you deploy Claude Code to 100 servers, they all install the exact same plugins automatically.
In this chapter, we learned that installing a plugin is a coordinated dance.
Now that the files are safely stored on the disk, the system needs a way to remember exactly what is installed, where it is, and which version it is. We need a permanent record.
Next Chapter: Installation Registry
Generated by Code IQ