-
Notifications
You must be signed in to change notification settings - Fork 0
Agent Launch, CLI Probing & Managed Accounts
This slice is the launch contract between termsprawl and the external agent CLIs it hosts. It answers four separate questions:
-
What agent preset exists?
src/shared/agents/config.tsdeclares ids, commands, titles, and capability flags. -
Where is the executable?
src/core/command-resolver.tsturnsclaude,gemini,druk, etc. into absolute paths, including the GUI-minimal-PATH fallback. -
What can this installed CLI actually do?
src/core/agent-tool-launch.tsprobes--help/--version, selects a launch adapter, andsrc/core/agent-cli.tscontains pure help-text predicates for Claude-specific behavior. -
Which credentials does the spawn use?
src/core/agent-accounts.tsisolates managed accounts into<userData>/accounts/<id>and strips inherited auth environment variables.
The files here are core/shared helpers: mostly Electron-free and Node-filesystem/child-process based. The actual PTY/tool-server caller is not in this excerpt, so the call order below describes how the exported APIs are designed to compose, not a complete main-process trace.
The launch flow is deliberately staged. Registry data cannot know whether a CLI is installed; probing cannot know whether the user picked a managed account; account isolation must happen at spawn time, after executable resolution and argument construction.
flowchart TD
A[AgentConfig from AGENT_REGISTRY] --> B[agentCommand / config.command]
B --> C[findExecutable]
C -->|resolved absolute path| D[probeAgent: --help and --version]
C -->|null| E[unresolvedNotice / missingCommandExec]
D --> F[selectLaunchAdapter]
F --> G[prepareToolLaunch]
H[installToolRuntime: client.mjs + termsprawlctl] --> G
I[Active managed account] --> J[claudeConfigEnv + stripAuthEnv]
J --> G
K[claude --help text] --> L[claudeSupportsPermissionMode / claudeLoginArgs]
G --> M[shell command + env + IntegrationStatus]
Key nodes:
-
AgentConfig.commandis only a binary name. It must pass throughfindExecutablebefore spawning. -
probeAgentexecutes the resolved binary directly withexecFileSync, not through a shell. -
selectLaunchAdapteruses both the executable basename and the probed help text; a preset name alone is explicitly insufficient. -
installToolRuntimeproduces the launcher thatprepareToolLaunchwrites into MCP config or injects through environment variables. - Managed account handling is separate from launch construction and happens through
CLAUDE_CONFIG_DIRplus auth-env stripping.
This is the single source of truth for agent presets. AgentId is a closed union: claude, codex, gemini, grok, openclaude, custom. AGENT_REGISTRY maps each id to an AgentConfig with display name, node title, command, enabled flag, integration metadata, and an AgentCapabilities record.
Important boundaries:
-
commandis resolved to an absolute path at spawn time; the registry intentionally stores the logical name. -
geminiis kept as the command/id even though the displayed product is Antigravity. Persisted canvas nodes keep their agent identity, and executable resolution handlesagy/antigravityaliases later. -
customis a template, not an enabled preset. -
capabilitiesis a declaration, not runtime probing. For example,gemini,grok, andopenclaudehavehooks: falsebecause no hook normalizer exists for them in the hook-server path. Flipping those flags requires adding the normalizer/installer, not just changing booleans. -
integration.transcriptReaderis currentlyclaude-jsonlonly for Claude. - Helpers
agentIds,agentConfig,agentName,agentTitle, andagentCommandare the read API; callers should not reach into the object shape directly.
This module exists because desktop-launched apps often inherit a minimal PATH. findExecutable(name, home) checks, in order:
- Already-absolute names are returned as-is.
- Each non-empty directory in
process.env.PATH, split on:. - Known user-local install directories:
~/.local/bin,~/.druk/bin,~/bin. - Legacy aliases from
COMMAND_ALIASES; currently onlygemini→agy,antigravity.
resolveCommandLine preserves trailing arguments: it resolves only the first token, then reattaches the rest. If the token cannot be resolved, it returns the original line so the shell can report the failure.
The failure path is split into two helpers:
-
unresolvedNoticereturns a user-facing explanation for a missing first token, ornullif the line is empty, absolute, or resolvable. -
missingCommandExecwraps that notice inexec /bin/sh -c ...; exit 1so the message is printed by a non-interactive shell instead of being parsed by the interactive line editor as unmatched quotes or continuation prompts.
This resolver is POSIX-oriented in the fragment: it assumes :-separated PATH and /bin/sh. There is no Windows PATHEXT or ; handling here.
This is the runtime compatibility layer. It collaborates with command-resolver and the agent-tools module. probeAgent(command) resolves the executable, caches by absolute executable path, then reads --help and --version with a 3s timeout, 256 KiB max buffer, and stderr ignored. Failures become empty strings; version becomes unknown.
The cache is process-local and keyed only by executable path. A binary replaced at the same path will not be re-probed until the process restarts.
selectLaunchAdapter(probe) maps observed help text to a launch strategy:
| Observed executable/help | Adapter kind | Native MCP | Instruction route |
|---|---|---|---|
claude with --mcp-config and --append-system-prompt
|
claude-mcp |
yes | --append-system-prompt |
codex with --config and mcp in help |
codex-mcp |
yes | none selected |
agy / antigravity / gemini with --prompt-interactive
|
<name>-cli |
no | --prompt-interactive |
gemini / agy / antigravity / grok / openclaude with [PROMPT] or [prompt]
|
<name>-cli |
no | initial prompt argument |
| anything else | <name>-unverified |
no | none |
Precedence matters: the --prompt-interactive branch is checked before the generic [PROMPT] branch.
installToolRuntime(directory, executable, bundle) creates a private runtime directory, writes client.mjs from the supplied bundle, and writes an executable termsprawlctl launcher that runs the provided executable with ELECTRON_RUN_AS_NODE=1 against client.mjs. Directory modes are 0700; runtime and config files are 0600.
prepareToolLaunch(options) is the spawn builder. It:
- Validates an optional
customInstructionFlagagainst^--[a-z][a-z-]*$. - Creates the runtime directory and writes one skill folder per
TOOL_GUIDEStopic asskills/termsprawl-<topic>/SKILL.md. - For
claude-mcp, writesmcp.jsonwith atermsprawlMCP server entry and appends--mcp-config <path>. - For
codex-mcp, appends-c mcp_servers.termsprawl.command=...and-c mcp_servers.termsprawl.args=.... - Appends either
adapter.instructionFlag <orientation>, a bare orientation prompt forinitialPrompt, or nothing. - Returns a shell command string, environment entries (
TERMSPRAWL_SESSION_FILE,TERMSPRAWL_CTL), and anIntegrationStatus.
The returned command is constructed as shellQuote(probe.executable) + commandTail + quoted args. Therefore commandTail is inserted raw; the caller is responsible for making it safe. The adapter’s optional custom instruction flag is the only part validated locally.
The status state is derived from adapter availability:
- Native MCP adapters produce
state: 'needs-setup'with reason “Waiting for the agent to connect to MCP”. - CLI fallback adapters with an instruction route produce
state: 'cli-fallback'and a warning that shell tool access depends on agent permissions. - Unverified adapters produce
state: 'needs-setup'and tell the user to read generated skills and configure explicitly.
TOOL_GUIDES and IntegrationStatus come from ./agent-tools, which is outside this excerpt.
This small module is intentionally Electron-free and testable against fake claude --help output. It does not spawn anything. It interprets help text produced by a probe:
-
claudeSupportsPermissionMode(helpText)returns whether--permission-modeappears. -
claudeLoginArgs(helpText)returns['auth', 'login']ifauth loginappears,['/login']if/loginappears, otherwise[]. -
claudeLoginCommand(helpText)buildsclaude auth login,claude /login, or bareclaude.
These predicates are useful when a caller has help text, but the fragments do not show a direct call site wiring probeAgent().help into this module. Treat that as a composition point to check before modifying login or permission-mode behavior.
Managed accounts are directories under <userData>/accounts/<id>. The app does not store tokens; it only creates the directory that the agent CLI treats as its config home. For Claude, that is CLAUDE_CONFIG_DIR.
The module exposes:
-
accountConfigDir(userData, id)for path derivation. -
newAccountId()for collision-resistant ids shaped likeacc-<base36-time>-<random>. -
createManagedAccount(userData, label)which creates the directory and returns{ id, label, configDir }. -
deleteManagedAccount(userData, id)which removes the directory recursively and fails open if the directory is already gone. -
claudeConfigEnv(configDir)which returns{ CLAUDE_CONFIG_DIR: configDir }. -
stripAuthEnv(env)which removesANTHROPIC_API_KEY,CLAUDE_API_KEY, andANTHROPIC_AUTH_TOKENfrom a copy of the environment. -
activeAccount(settings)which readssettings.activeAccountIdand finds the matching member ofsettings.accounts; otherwise it returnsundefined, meaning the caller should fall back to the default~/.claude.
The security boundary is important: because the managed config directory must be the only credential source for a managed spawn, inherited auth environment variables are stripped before spawn. A stray API key in the shell cannot leak into the managed account. stripAuthEnv only covers the three listed keys; additional provider variables would need to be added deliberately.
Creating and deleting accounts only manipulates files. Persisting or removing entries from settings.accounts is outside these functions. Deletion also explicitly expects the caller to surface data loss in the UI before removing the directory.
Key state:
- Registry state is static in
AGENT_REGISTRY. - Probe state is process-local in
probeCache, keyed by absolute executable path. - Account state is split between the filesystem (
<userData>/accounts/<id>) and settings (activeAccountId,accounts). - Launch state is returned per call as
{ command, env, status }.
Important edge conditions:
- GUI-launched apps may not see user install directories on
PATH; the resolver compensates with~/.local/bin,~/.druk/bin, and~/bin. - Absolute command names bypass all resolution.
- Legacy aliases are only consulted if the original name is not found.
- Missing preset commands should use
unresolvedNoticeandmissingCommandExec, not raw injection into an interactive shell. -
probeAgentfailures are silent empty strings; adapter selection will fall back to*-unverified. -
selectLaunchAdapteris heuristic and help-text dependent. A CLI rename or help-text change can move it to a different branch. -
prepareToolLaunchwrites files with restrictive modes and expects a securelauncherpath. -
commandTailis not quoted byprepareToolLaunch; only the resolved executable and generated args are quoted. - Managed-account deletion is fail-open and does not clean settings.
- The managed-account implementation is v1 Claude-only for config-dir semantics. The creation/id/directory helpers are generic, but
claudeConfigEnvandAgentAccount.agentIdscope are not.
Extension points:
- Add a new agent by extending
AgentId, adding anAgentConfigentry, declaring honest capabilities, and deciding whethercommand-resolverneeds an alias. - If
hooks: trueis set for an agent, a matching hook normalizer and installer must exist; the registry comments explicitly call this out. - Add a new runtime adapter by extending
selectLaunchAdapterand teachingprepareToolLaunchhow to write or inject that adapter’s MCP/instruction configuration. - Add new CLI probes in
agent-cli.tsas pure functions over help text; keep them spawn-free and fixture-testable. - Add new managed-account auth variables to
AUTH_ENVonly when the isolation boundary is understood. - Add a new command alias in
COMMAND_ALIASESwhen a CLI rename must not break persisted project files.
Limits of this excerpt: the actual PTY spawn, settings persistence, hook server, and agent-tools guide/status definitions are not included here. The behavior above is limited to the five provided source fragments.
Sources: src/shared/agents/config.ts, src/core/agent-cli.ts, src/core/command-resolver.ts, src/core/agent-tool-launch.ts, src/core/agent-accounts.ts
Generated from termsprawl at 0d4393be54c6200beedd91bb636e5296c30472c5.
App Shell & Platform Foundations
- Electron Main Process & Window Lifecycle
- Preload Bridge & IPC Contract
- Shared Domain Types and File/URL Helpers
- Renderer Bootstrap & App Composition
- Build Targets & TypeScript Configuration
Canvas, Nodes & Renderer State
- Infinite Canvas Surface & Viewport Interaction
- Workspace, Project & Tab State
- Node Links, Edges & Link Inspector
- Sticky, Group, Editor & Diff Nodes
- Keyboard Canvas Navigation & Cross-Panel Requests
- Theme, Accent & Visual Language
- Boot Overlay, Onboarding & Shared UI Kit
Terminals & Session Continuity
- PTY Lifecycle & Terminal Sessions
- tmux Session Naming & Reattach
- Scrollback Snapshots & Cold Replay
- Terminal Node Rendering (xterm.js)
- SSH Remote Projects, Terminals & Files
Persistence, Projects & Files
- Workspace Store & Project File Layout
- Project Scope, Deletion & Worktree Registry
- Workspace Bundle Export/Import
- File Service & File Tree UI
Agent Runtime & Tooling
- Agent Status Model & Hook Normalization
- Hook Server & CLI Hook Installers
- Agent Launch, CLI Probing & Managed Accounts
- Agent Tool Protocol & In-Process Server
- Agent Tool Client, CLI & MCP Entry
- Transcripts, Context Discovery & Context CLI
- Agent Canvas State & Status Badges
Chat Nodes & Model Providers
- Chat Runtime, Conversation & Cost
- Model Provider Adapters & Streaming
- Chat Tool Calling & Project Tools
- Chat Node UI
Git & Source Control
Embedded Browser Nodes
- Browser Manager & Guest Runtime
- CDP Facade & Browser Agent Server
- Browser Navigation Policy & Node UI
Server Edition
- Server Bootstrap & HTTP/WebSocket Entry
- RPC Dispatch, Handlers & Service Bridges
- Renderer Shim & Server Boundary
- Server Auth & Security Boundary
Relay & Remote Access
- Relay Hub & WebSocket Frame Routing
- Relay End-to-End Cryptography
- Relay Auth, Invites, Store & Admin API
- Relay Client, Pairing & Terminal Tunneling
- Relay Trust UI
Integrations & Secondary Surfaces
- Telegram Bot, Commands & Pairing
- A2A Peers: Protocol, Client & Server
- Node Link Engine, Registry & Scheduler
- Cloud Spaces, Snapshots & Sync
Settings, Updates & Maintenance