( prompt: string, yolo: boolean, systemPromptOverride?: string, )
| 6 | |
| 7 | /** Non-interactive `orbcode -p "prompt"` mode: prints the final response to stdout. */ |
| 8 | export async function runHeadless( |
| 9 | prompt: string, |
| 10 | yolo: boolean, |
| 11 | systemPromptOverride?: string, |
| 12 | ): Promise<void> { |
| 13 | const settings = loadSettings() |
| 14 | |
| 15 | // An unknown --model (or MATTERAI_MODEL) silently resolves to the default; say |
| 16 | // so on stderr instead of quietly running a different model than requested. |
| 17 | const requestedModel = process.env.MATTERAI_MODEL |
| 18 | if (requestedModel && !isValidAxonModel(requestedModel)) { |
| 19 | process.stderr.write( |
| 20 | `warning: unknown model "${requestedModel}"; using "${settings.model}". ` + |
| 21 | `Add it under "customModels" in settings.json (with a "provider") to use it.\n`, |
| 22 | ) |
| 23 | } |
| 24 | |
| 25 | const token = getAuthToken(settings) |
| 26 | // MatterAI/Axon models authenticate with the login token. AI-SDK providers |
| 27 | // (Anthropic, etc.) authenticate with their own key — resolved by the |
| 28 | // provider from the env (e.g. ANTHROPIC_API_KEY) or the model's `apiKey` — |
| 29 | // so they don't need a MatterAI login. Only gate on the token when the |
| 30 | // selected model actually goes through the MatterAI gateway. |
| 31 | if (!token && !usesAiSdk(getModel(settings.model))) { |
| 32 | console.error("Not signed in. Run `orbcode login`, set MATTERAI_TOKEN, or put an apiKey in settings.json.") |
| 33 | process.exit(1) |
| 34 | } |
| 35 | |
| 36 | // There's no interactive trust prompt in headless mode, so untrusted project |
| 37 | // hooks are skipped for safety. Tell the user how to enable them. |
| 38 | const pendingHooks = getPendingProjectHooks() |
| 39 | if (pendingHooks) { |
| 40 | process.stderr.write( |
| 41 | `note: ${pendingHooks.commands.length} project hook(s) in .orbcode/settings.json are untrusted and were skipped. ` + |
| 42 | `Trust them in an interactive session, or set MATTERAI_TRUST_PROJECT_HOOKS=1.\n`, |
| 43 | ) |
| 44 | } |
| 45 | |
| 46 | // Start MCP servers. In headless mode there's no interactive approval, so |
| 47 | // project-scope servers are only connected if they were previously approved |
| 48 | // (persisted in .orbcode/settings.json). Unapproved project servers are |
| 49 | // skipped with a note, matching the project-hooks behavior. |
| 50 | const mcp = new McpManager( |
| 51 | process.cwd(), |
| 52 | settings.disabledMcpServers ?? [], |
| 53 | settings.enabledMcpServers ?? [], |
| 54 | ) |
| 55 | const mcpSnapshot = await mcp.start() |
| 56 | const pendingMcp = mcp.getPendingApproval() |
| 57 | if (pendingMcp.length > 0) { |
| 58 | process.stderr.write( |
| 59 | `note: ${pendingMcp.length} project MCP server(s) in .mcp.json are unapproved and were skipped. ` + |
| 60 | `Approve them in an interactive session with /mcp.\n`, |
| 61 | ) |
| 62 | } |
| 63 | const connectedMcp = mcpSnapshot.servers.filter((s) => s.status === "connected").length |
| 64 | if (connectedMcp > 0) { |
| 65 | process.stderr.write(`MCP: ${connectedMcp}/${mcpSnapshot.servers.length} server(s) connected.\n`) |
no test coverage detected