Welcome to Chapter 4!
In the previous chapter, Browser Extension Bridge, we built the remote control to talk to the browser. In Interactive CLI UI (React/Ink), we built the dashboard.
Now we face a new problem: Reality.
Just because we have a remote control doesn't mean the TV is plugged in, or that we are even in a room with a TV. We need a system to check our surroundings and the current status of the application. We call this Environment & State Context.
Imagine you are a pilot. Before you press the "Take Off" button, you look at your dashboard.
Our CLI needs to do the exact same thing:
Without this context, the application would crash or confuse the user by offering features that won't work.
First, we need to ask the Operating System where we are running. Some environments, like WSL, have limitations. WSL is a Linux terminal running inside Windows; it has trouble talking directly to the Windows Chrome browser in the specific way we need.
We use a helper utility to check this.
// utils/env.ts (Simplified)
export const env = {
// Returns true if we are in Windows Subsystem for Linux
isWslEnvironment: () => {
// Checks system files for "microsoft" or "wsl" keywords
return process.platform === 'linux' && isWsl();
}
};
Explanation: This function acts like a sensor. It checks the system fingerprints to see if we are in a simulated Linux environment.
Next, we need to check if the user is allowed to use this feature. We don't want to show a "Connect" button if the user isn't authorized.
// utils/auth.ts
export function isClaudeAISubscriber() {
// Checks the local session token for subscription details
const session = getSession();
return session?.isProTier === true;
}
Explanation: This is our security guard. It looks at the user's login session to verify their subscription tier.
This is the most dynamic part. We need to know if the Chrome Extension is currently talking to our CLI.
We use a system called MCP (Model Context Protocol). You don't need to know how the protocol works, just that it creates a list of "Clients" (connected tools). We need to search this list.
We use a global state store (like a central database in memory) that any component can read.
// Inside a React Component
import { useAppState } from '../../state/AppState.js';
// Subscribe to the list of connected clients
const mcpClients = useAppState(state => state.mcp.clients);
Explanation:
useAppState is a "Hook." It connects our UI component to the live heartbeat of the application. If a new client connects, this variable updates automatically.
Now, let's put these three concepts together inside our chrome.tsx file (which we started in Chapter 2).
We want to:
In our entry function, we gather the static data (Environment and Identity).
// chrome.tsx - The call() function
export const call = async function(onDone) {
// 1. Check Environment
const isWSL = env.isWslEnvironment();
// 2. Check Identity
const isSubscriber = isClaudeAISubscriber();
// Pass these facts to the UI
return <ClaudeInChromeMenu isWSL={isWSL} isSubscriber={isSubscriber} ... />;
};
Inside the component, we listen to the live App State to check for connections.
// chrome.tsx - The Component
function ClaudeInChromeMenu({ isWSL, isSubscriber }) {
// 3. Check Live State
const clients = useAppState(s => s.mcp.clients);
// Search for our specific chrome server
const chromeClient = clients.find(c => c.name === 'claude-in-chrome');
const isConnected = chromeClient?.type === 'connected';
// ... Rendering logic below ...
}
Finally, we tell the UI what to draw based on these facts.
return (
<Box flexDirection="column">
{/* Guard Clause: Error if in WSL */}
{isWSL && <Text color="error">Not supported in WSL</Text>}
{/* Guard Clause: Error if not paid */}
{!isSubscriber && <Text color="error">Subscription required</Text>}
{/* Status Indicator */}
<Text>
Status: {isConnected ? <Text color="green">Enabled</Text> : <Text color="gray">Disabled</Text>}
</Text>
</Box>
);
What happens here?
isWSL is true, the user sees a red error message.isConnected is true (the extension is talking to us), the text turns Green.isConnected is false, the text stays Gray.How does the data flow from the system to your screen?
call function runs once. It grabs "static" facts (WSL, Subscription) that won't change while the menu is open.useAppState hook registers a listener with the global store.Let's look closer at how we identify the connection. We don't just check if any client is connected; we look for a specific name.
// utils/claudeInChrome/common.ts
export const CLAUDE_IN_CHROME_MCP_SERVER_NAME = 'claude-in-chrome';
// chrome.tsx
const chromeClient = mcpClients.find(
client => client.name === CLAUDE_IN_CHROME_MCP_SERVER_NAME
);
We use a constant CLAUDE_IN_CHROME_MCP_SERVER_NAME. This prevents typos. If we typed claude-chrome (missing the "in"), our code would never find the client, and the light would never turn green.
This connects back to Command Module Definition where we defined static metadata. Here, we use static constants to ensure our dynamic state checking is accurate.
In this chapter, you learned:
env to detect limitations like WSL.auth to enforce subscription requirements.useAppState to reactively update the UI when the extension connects or disconnects.We have built a smart UI that knows where it is and what is happening. But there is one piece missing.
If the user sets the feature to "Enabled by default," sets it to true, and then closes the app... does the app remember that choice next time? Currently, no.
We need a memory system.
Next Chapter: Configuration Persistence
Generated by Code IQ