Welcome back! In Chapter 4: Generative Action Dispatch, we learned how to send complex instructions to the AI to "fix" or "edit" our animation.
However, there is a catch. Those instructions rely on a specific tool (the thinkback skill) being present on the user's machine. What if the user has never installed it?
In this chapter, we build the Plugin Installation State Machine. This is the automated wizard that ensures all necessary software is installed before the user tries to run it.
Imagine you want to move into a new house. You cannot just move your furniture (the "User Data") onto an empty lot. You need a construction process:
If you try to move in before the house is built, everything fails.
The Problem: Users might run our command think-back without having the underlying plugin installed.
The Solution: We create a component (ThinkbackInstaller) that acts as the "Construction Manager." It automatically checks each step and performs the work if something is missing.
To build this robust installer, we use a State Machine. This is a fancy term for a system that can only be in one specific "phase" at a time.
checking, installing-marketplace, installing-plugin, ready).We don't use the Installer manually. Instead, we use it as a Gatekeeper inside our main application flow.
In ThinkbackFlow (from Chapter 2: Ink-based UI Orchestration), we check if the installation is complete. If not, we block the rest of the app and show the Installer.
// Inside ThinkbackFlow
if (!installComplete) {
return (
<ThinkbackInstaller
onReady={() => setInstallComplete(true)}
onError={handleError}
/>
);
}
// ... Rest of the application (Menu, etc.)
installComplete starts as false.onReady is called, the Installer disappears, and the main menu appears.Before looking at the code, let's visualize the "Construction Manager's" checklist. This runs automatically when the component mounts.
Let's build ThinkbackInstaller in thinkback.tsx.
First, we define exactly what "phases" are possible. This prevents our code from getting confused (e.g., trying to enable a plugin that isn't installed).
type InstallState =
| { phase: 'checking' }
| { phase: 'installing-marketplace' }
| { phase: 'installing-plugin' }
| { phase: 'enabling-plugin' }
| { phase: 'ready' }
| { phase: 'error'; message: string };
TypeScript helps us here. We can't accidentally type phase: 'building-house' because it's not in the list.
We use a React useEffect hook to run our logic immediately when the component appears. This is the brain of the state machine.
We check requirements in a strict order: Marketplace -> Plugin -> Enabled.
// Inside useEffect...
const marketplaceInstalled = await checkMarketplace();
if (!marketplaceInstalled) {
setState({ phase: 'installing-marketplace' });
await addMarketplaceSource({ repo: '...' });
}
If the marketplace is missing, we pause here, update the UI state, and install it.
Once the marketplace exists, we check for the specific plugin (thinkback).
// Continuing the logic...
const pluginInstalled = isPluginInstalled(getPluginId());
if (!pluginInstalled) {
setState({ phase: 'installing-plugin' });
// This downloads the code needed for the next chapters
await installSelectedPlugins([getPluginId()]);
}
Now we are framing the house.
Finally, a plugin might be installed but "Disabled" in the settings. We must ensure it's active.
// Continuing the logic...
const { disabled } = await loadAllPlugins();
const isDisabled = disabled.some(p => p.name === 'thinkback');
if (isDisabled) {
setState({ phase: 'enabling-plugin' });
await enablePluginOp(getPluginId());
}
// If we made it here, we are done!
onReady();
We turned on the electricity. The house is ready.
While all that complex logic is happening in the background, the user needs to know what is going on. We render a simple UI based on the current phase.
// Determine the text based on the state
const message = state.phase === 'installing-marketplace'
? 'Installing marketplaceβ¦'
: state.phase === 'installing-plugin'
? 'Installing thinkback pluginβ¦'
: 'Checking installationβ¦';
// Render the spinner
return (
<Box>
<Spinner />
<Text>{message}</Text>
</Box>
);
In this chapter, we built the Plugin Installation State Machine.
Now the foundation is laid, the frame is up, and the lights are on. The environment is perfectly set up.
But waitβhow does the application know exactly where the files for this plugin are located on the user's computer? A generic path won't work.
In the final chapter, we will learn how to dynamically locate our resources.
Next Chapter: Skill Environment Resolution
Generated by Code IQ