@launchchair/agent
Advanced tools
+4
-4
@@ -67,4 +67,4 @@ # LaunchChair Agent Bootstrap | ||
| launchchair login | ||
| launchchair setup codex --cwd /absolute/path/to/repo-or-empty-folder --write-config | ||
| launchchair setup claude --cwd /absolute/path/to/repo-or-empty-folder --write-config | ||
| launchchair setup codex --cwd /absolute/path/to/repo-or-empty-folder | ||
| launchchair setup claude --cwd /absolute/path/to/repo-or-empty-folder | ||
| launchchair projects --query LeafLore | ||
@@ -91,3 +91,3 @@ launchchair create --name SignalLift --one-liner "Distribution workspace for technical founders" --project-type b2b --idempotency-key signallift-v1 | ||
| `launchchair login` is the recommended token path. It saves the token to `~/.launchchair/config.json`, and `launchchair mcp-server` reads that saved token. Use `LAUNCHCHAIR_AGENT_TOKEN` only as a manual fallback when browser login is skipped or a per-client token override is needed. | ||
| The packaged MCP and local bridges also read the same saved CLI config as a fallback, so a desktop MCP launch should not require pasting a token into Codex or Claude config after browser login. | ||
| The packaged MCP and local bridges also read the same saved CLI config as a fallback, so a desktop MCP launch should not require pasting a token into Codex or Claude config after browser login. Guided setup verifies standards-compliant MCP initialize and tool discovery before it reports success. | ||
@@ -135,3 +135,3 @@ For Codex Desktop local runs: | ||
| Prefer `launchchair setup codex --cwd /absolute/path/to/build-folder --write-config` or `launchchair mcp-config codex --ui-fields` so the CLI prints exact local command, argument, and saved-config paths for this machine. | ||
| Prefer `launchchair setup codex --cwd /absolute/path/to/build-folder` so the CLI writes native config and verifies MCP tool discovery. Use `launchchair mcp-config codex --ui-fields` only for manual custom-form setup. | ||
@@ -138,0 +138,0 @@ ## What To Do First In A Fresh Chat |
+221
-24
@@ -12,3 +12,3 @@ #!/usr/bin/env node | ||
| import { spawn, spawnSync } from "node:child_process"; | ||
| import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; | ||
| import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs"; | ||
| import os from "node:os"; | ||
@@ -19,3 +19,3 @@ import path from "node:path"; | ||
| const VERSION = "0.4.7"; | ||
| const VERSION = "0.4.8"; | ||
| const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url)); | ||
@@ -45,2 +45,14 @@ const DEFAULT_BASE_URL = "https://www.launchchair.io"; | ||
| function writeFileAtomic(target, contents, options = {}) { | ||
| const directory = path.dirname(target); | ||
| mkdirSync(directory, { recursive: true }); | ||
| const temporary = path.join(directory, `.${path.basename(target)}.${process.pid}.${Date.now()}.tmp`); | ||
| try { | ||
| writeFileSync(temporary, contents, options); | ||
| renameSync(temporary, target); | ||
| } finally { | ||
| if (existsSync(temporary)) unlinkSync(temporary); | ||
| } | ||
| } | ||
| function paint(value, tone) { | ||
@@ -68,3 +80,3 @@ if (!COLORS_ENABLED || !color[tone]) return value; | ||
| mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 }); | ||
| writeFileSync(CONFIG_PATH, `${JSON.stringify(next, null, 2)}\n`, { mode: 0o600 }); | ||
| writeFileAtomic(CONFIG_PATH, `${JSON.stringify(next, null, 2)}\n`, { mode: 0o600 }); | ||
| try { | ||
@@ -84,3 +96,3 @@ chmodSync(CONFIG_PATH, 0o600); | ||
| mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 }); | ||
| writeFileSync(CONFIG_PATH, `${JSON.stringify(current, null, 2)}\n`, { mode: 0o600 }); | ||
| writeFileAtomic(CONFIG_PATH, `${JSON.stringify(current, null, 2)}\n`, { mode: 0o600 }); | ||
| return current; | ||
@@ -174,3 +186,3 @@ } | ||
| mcp-server Run the stdio MCP server using saved CLI config | ||
| setup codex|claude|terminal [options] | ||
| setup codex|claude|claude-desktop|claude-code|terminal [options] | ||
| Guided setup for CLI, CWD, AGENTS.md, MCP config, and bridge | ||
@@ -222,3 +234,5 @@ config Show local CLI config without printing the token | ||
| --ui-fields Print Codex custom MCP form fields instead of raw config | ||
| --write-config Write/update Codex or Claude Desktop MCP config when supported | ||
| --write-config Explicitly write/update client config (the setup default) | ||
| --print-config Print raw MCP config instead of writing client config | ||
| --skip-mcp-check Write config without verifying MCP initialize + tools/list | ||
@@ -717,4 +731,24 @@ Login options: | ||
| } | ||
| if (target === "claude" || target === "claude-code" || target === "claude_code") { | ||
| if (target === "claude-code" || target === "claude_code") { | ||
| return { | ||
| label: "Claude Code", | ||
| mcpTarget: "claude", | ||
| cwdConfigKey: "claudeCodeCwd", | ||
| cwdEnvName: "LC_AGENT_CLAUDE_CODE_CWD", | ||
| routerTarget: "claude_code", | ||
| claudeClients: ["code"], | ||
| }; | ||
| } | ||
| if (target === "claude-desktop" || target === "claude_desktop") { | ||
| return { | ||
| label: "Claude Desktop", | ||
| mcpTarget: "claude", | ||
| cwdConfigKey: "claudeCodeCwd", | ||
| cwdEnvName: "LC_AGENT_CLAUDE_CODE_CWD", | ||
| routerTarget: "claude_code", | ||
| claudeClients: ["desktop"], | ||
| }; | ||
| } | ||
| if (target === "claude") { | ||
| return { | ||
| label: "Claude Desktop + Claude Code", | ||
@@ -725,2 +759,3 @@ mcpTarget: "claude", | ||
| routerTarget: "claude_code", | ||
| claudeClients: ["desktop", "code"], | ||
| }; | ||
@@ -880,2 +915,92 @@ } | ||
| function verifyMcpServer(target, timeoutMs = 20_000) { | ||
| const server = mcpServerConfig(target, { includeTokenPlaceholder: false }); | ||
| return new Promise((resolve, reject) => { | ||
| const child = spawn(server.command, server.args, { | ||
| env: { ...process.env, ...server.env }, | ||
| stdio: ["pipe", "pipe", "pipe"], | ||
| }); | ||
| let settled = false; | ||
| let stdout = ""; | ||
| let stderr = ""; | ||
| const finish = (error, toolNames = []) => { | ||
| if (settled) return; | ||
| settled = true; | ||
| clearTimeout(timeout); | ||
| child.kill(); | ||
| if (error) reject(error); | ||
| else resolve(toolNames); | ||
| }; | ||
| const timeout = setTimeout(() => { | ||
| finish(new Error(`MCP verification timed out after ${Math.round(timeoutMs / 1000)} seconds.${stderr.trim() ? ` ${stderr.trim()}` : ""}`)); | ||
| }, timeoutMs); | ||
| const send = (message) => child.stdin.write(`${JSON.stringify(message)}\n`); | ||
| child.stdout.setEncoding("utf8"); | ||
| child.stdout.on("data", (chunk) => { | ||
| stdout += chunk; | ||
| while (true) { | ||
| const newlineIndex = stdout.indexOf("\n"); | ||
| if (newlineIndex === -1) break; | ||
| const line = stdout.slice(0, newlineIndex).replace(/\r$/, ""); | ||
| stdout = stdout.slice(newlineIndex + 1); | ||
| if (!line.trim()) continue; | ||
| let response = null; | ||
| try { | ||
| response = JSON.parse(line); | ||
| } catch { | ||
| finish(new Error("MCP verification received non-JSON output on stdout.")); | ||
| return; | ||
| } | ||
| if (response?.id === 1) { | ||
| if (response.error) { | ||
| finish(new Error(`MCP initialize failed: ${response.error.message || "unknown error"}`)); | ||
| return; | ||
| } | ||
| send({ jsonrpc: "2.0", method: "notifications/initialized", params: {} }); | ||
| send({ jsonrpc: "2.0", id: 2, method: "tools/list", params: {} }); | ||
| continue; | ||
| } | ||
| if (response?.id === 2) { | ||
| if (response.error) { | ||
| finish(new Error(`MCP tools/list failed: ${response.error.message || "unknown error"}`)); | ||
| return; | ||
| } | ||
| const toolNames = Array.isArray(response?.result?.tools) | ||
| ? response.result.tools.map((tool) => String(tool?.name || "")).filter(Boolean) | ||
| : []; | ||
| const requiredTools = ["launchchair_status", "launchchair_create_project", "launchchair_list_projects"]; | ||
| const missingTools = requiredTools.filter((name) => !toolNames.includes(name)); | ||
| if (missingTools.length) { | ||
| finish(new Error(`MCP verification did not discover required tools: ${missingTools.join(", ")}`)); | ||
| return; | ||
| } | ||
| finish(null, toolNames); | ||
| } | ||
| } | ||
| }); | ||
| child.stderr.setEncoding("utf8"); | ||
| child.stderr.on("data", (chunk) => { | ||
| stderr = `${stderr}${chunk}`.slice(-4_000); | ||
| }); | ||
| child.on("error", (error) => finish(new Error(`Could not start the MCP server: ${error.message}`))); | ||
| child.on("exit", (code) => { | ||
| if (!settled) finish(new Error(`MCP server exited before verification completed (code ${code ?? "unknown"}).${stderr.trim() ? ` ${stderr.trim()}` : ""}`)); | ||
| }); | ||
| send({ | ||
| jsonrpc: "2.0", | ||
| id: 1, | ||
| method: "initialize", | ||
| params: { | ||
| protocolVersion: "2024-11-05", | ||
| capabilities: {}, | ||
| clientInfo: { name: "launchchair-setup-check", version: VERSION }, | ||
| }, | ||
| }); | ||
| }); | ||
| } | ||
| function renderMcpConfig(target, options = {}) { | ||
@@ -946,7 +1071,18 @@ const normalized = normalizedMcpTarget(target); | ||
| function claudeDesktopConfigPath() { | ||
| return path.join(os.homedir() || process.cwd(), "Library", "Application Support", "Claude", "claude_desktop_config.json"); | ||
| const configuredPath = env("CLAUDE_DESKTOP_CONFIG_PATH"); | ||
| if (configuredPath) return path.resolve(configuredPath); | ||
| const home = os.homedir() || process.cwd(); | ||
| if (process.platform === "darwin") { | ||
| return path.join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json"); | ||
| } | ||
| if (process.platform === "win32") { | ||
| return path.join(env("APPDATA", path.join(home, "AppData", "Roaming")), "Claude", "claude_desktop_config.json"); | ||
| } | ||
| return path.join(env("XDG_CONFIG_HOME", path.join(home, ".config")), "Claude", "claude_desktop_config.json"); | ||
| } | ||
| function codexConfigPath() { | ||
| return path.join(os.homedir() || process.cwd(), ".codex", "config.toml"); | ||
| const configuredHome = env("CODEX_HOME"); | ||
| const codexHome = configuredHome ? path.resolve(configuredHome) : path.join(os.homedir() || process.cwd(), ".codex"); | ||
| return path.join(codexHome, "config.toml"); | ||
| } | ||
@@ -986,3 +1122,3 @@ | ||
| const next = `${preserved ? `${preserved}\n\n` : ""}${renderMcpConfig("codex", { includeTokenPlaceholder: false })}\n`; | ||
| writeFileSync(configPath, next); | ||
| writeFileAtomic(configPath, next); | ||
| return configPath; | ||
@@ -997,6 +1133,10 @@ } | ||
| const raw = readFileSync(configPath, "utf8"); | ||
| current = raw.trim() ? JSON.parse(raw) : {}; | ||
| const backupPath = `${configPath}.launchchair-backup-${Date.now()}`; | ||
| copyFileSync(configPath, backupPath); | ||
| ok(`Backed up existing Claude config to ${backupPath}`); | ||
| try { | ||
| current = raw.trim() ? JSON.parse(raw) : {}; | ||
| } catch (error) { | ||
| throw new Error(`Claude Desktop config is not valid JSON. The original was preserved at ${backupPath}. ${error instanceof Error ? error.message : String(error)}`); | ||
| } | ||
| } | ||
@@ -1010,6 +1150,30 @@ const next = { | ||
| }; | ||
| writeFileSync(configPath, `${JSON.stringify(next, null, 2)}\n`); | ||
| writeFileAtomic(configPath, `${JSON.stringify(next, null, 2)}\n`); | ||
| return configPath; | ||
| } | ||
| function registerClaudeCodeConfig() { | ||
| const executable = env("CLAUDE_CODE_PATH", "claude"); | ||
| const version = spawnSync(executable, ["--version"], { encoding: "utf8" }); | ||
| if (version.error?.code === "ENOENT") { | ||
| return { configured: false, reason: "Claude Code is not installed or is not on PATH." }; | ||
| } | ||
| if (version.status !== 0) { | ||
| throw new Error(`Claude Code is installed but could not run: ${(version.stderr || version.stdout || `exit ${version.status}`).trim()}`); | ||
| } | ||
| const server = { type: "stdio", ...mcpServerConfig("claude", { includeTokenPlaceholder: false }) }; | ||
| spawnSync(executable, ["mcp", "remove", "launchchair", "--scope", "user"], { encoding: "utf8" }); | ||
| const added = spawnSync( | ||
| executable, | ||
| ["mcp", "add-json", "launchchair", JSON.stringify(server), "--scope", "user"], | ||
| { encoding: "utf8" }, | ||
| ); | ||
| if (added.error) throw new Error(`Could not run Claude Code MCP setup: ${added.error.message}`); | ||
| if (added.status !== 0) { | ||
| throw new Error(`Claude Code MCP setup failed: ${(added.stderr || added.stdout || `exit ${added.status}`).trim()}`); | ||
| } | ||
| return { configured: true, output: String(added.stdout || "").trim() }; | ||
| } | ||
| function printMcpConfig(target, args = {}) { | ||
@@ -1119,2 +1283,5 @@ const output = args["ui-fields"] === true || args["print-ui-fields"] === true ? renderMcpUiFields(target) : renderMcpConfig(target); | ||
| const cwd = path.resolve(String(args.cwd || process.cwd())); | ||
| const writeConfig = | ||
| args["write-config"] === true || | ||
| (args["copy-config"] !== true && args["ui-fields"] !== true && args["print-config"] !== true); | ||
| const incomingToken = typeof args.token === "string" ? args.token.trim() : ""; | ||
@@ -1186,2 +1353,3 @@ const incomingBaseUrl = typeof args["base-url"] === "string" ? args["base-url"].trim().replace(/\/+$/, "") : undefined; | ||
| print(""); | ||
| let claudeCodeSetup = null; | ||
| if (details.terminalOnly) { | ||
@@ -1197,10 +1365,23 @@ title("terminal agent commands"); | ||
| } else { | ||
| if (details.mcpTarget === "codex" && args["write-config"] === true) { | ||
| if (details.mcpTarget === "codex" && writeConfig) { | ||
| const configPath = writeCodexConfig(); | ||
| title("Codex MCP config updated"); | ||
| ok(`Wrote LaunchChair MCP server to ${configPath}`); | ||
| } else if (details.mcpTarget === "claude" && args["write-config"] === true) { | ||
| const configPath = writeClaudeDesktopConfig(); | ||
| title("Claude MCP config updated"); | ||
| ok(`Wrote LaunchChair MCP server to ${configPath}`); | ||
| } else if (details.mcpTarget === "claude" && writeConfig) { | ||
| if (details.claudeClients?.includes("desktop")) { | ||
| const configPath = writeClaudeDesktopConfig(); | ||
| title("Claude Desktop MCP config updated"); | ||
| ok(`Wrote LaunchChair MCP server to ${configPath}`); | ||
| } | ||
| if (details.claudeClients?.includes("code")) { | ||
| claudeCodeSetup = registerClaudeCodeConfig(); | ||
| title("Claude Code MCP config updated"); | ||
| if (claudeCodeSetup.configured) { | ||
| ok("Registered LaunchChair as a user-scoped MCP server for Claude Code."); | ||
| } else if (details.claudeClients.length === 1) { | ||
| throw new Error(`${claudeCodeSetup.reason} Install Claude Code, then rerun setup claude-code.`); | ||
| } else { | ||
| warn(`${claudeCodeSetup.reason} Claude Desktop is configured; rerun setup claude-code after installing Claude Code.`); | ||
| } | ||
| } | ||
| } else { | ||
@@ -1217,2 +1398,13 @@ const output = details.mcpTarget === "codex" || args["ui-fields"] === true ? renderMcpUiFields(details.mcpTarget) : renderMcpConfig(details.mcpTarget); | ||
| if (!details.terminalOnly && token() && args["skip-mcp-check"] !== true) { | ||
| const toolNames = await verifyMcpServer(details.mcpTarget); | ||
| print(""); | ||
| title("MCP verification passed"); | ||
| ok(`Initialize and tools/list succeeded; discovered ${toolNames.length} LaunchChair tools.`); | ||
| } else if (!details.terminalOnly && !token()) { | ||
| warn("MCP verification was skipped because no LaunchChair token is configured."); | ||
| } else if (!details.terminalOnly && args["skip-mcp-check"] === true) { | ||
| warn("MCP verification was skipped by --skip-mcp-check."); | ||
| } | ||
| print(""); | ||
@@ -1231,3 +1423,3 @@ title("next steps"); | ||
| } else { | ||
| if (details.mcpTarget === "codex" && args["write-config"] === true) { | ||
| if (details.mcpTarget === "codex" && writeConfig) { | ||
| print("1. Restart or reload Codex Desktop so the MCP server is exposed to fresh chats."); | ||
@@ -1238,5 +1430,10 @@ print("2. Keep the generated config file under your user profile; it launches the saved LaunchChair CLI token."); | ||
| print("2. Restart or reload Codex Desktop so the MCP server is exposed to fresh chats."); | ||
| } else if (details.mcpTarget === "claude" && args["write-config"] === true) { | ||
| print("1. Restart or reload Claude Desktop so the MCP server is exposed to fresh chats."); | ||
| print("2. Keep the generated config file under your user profile; it launches the saved LaunchChair CLI token."); | ||
| } else if (details.mcpTarget === "claude" && writeConfig) { | ||
| if (details.claudeClients?.includes("desktop")) { | ||
| print("1. Restart or reload Claude Desktop so the MCP server is exposed to fresh chats."); | ||
| } | ||
| if (details.claudeClients?.includes("code") && claudeCodeSetup?.configured) { | ||
| print(`${details.claudeClients.includes("desktop") ? "2" : "1"}. Start a fresh Claude Code session; LaunchChair is registered at user scope for every project.`); | ||
| } | ||
| print(`${details.claudeClients?.includes("desktop") && claudeCodeSetup?.configured ? "3" : "2"}. Keep the generated config under your user profile; it launches the saved LaunchChair CLI token.`); | ||
| } else { | ||
@@ -1246,6 +1443,6 @@ print(`1. Paste the MCP config above into ${details.label}.`); | ||
| } | ||
| print("3. Use the generated command and argument fields exactly as printed; setup resolves the local Node and CLI paths for this machine."); | ||
| print("4. Keep this bridge running when you want LaunchChair's Run with agent button to work:"); | ||
| print("Use the generated command and argument fields exactly as printed; setup resolves the local Node and CLI paths for this machine."); | ||
| print("Keep this bridge running when you want LaunchChair's Run with agent button to work:"); | ||
| print(` ${paint("launchchair bridge", "cyan")}`); | ||
| print("5. Start a fresh agent chat. The agent should discover the LaunchChair skill and MCP server instructions automatically."); | ||
| print("Start a fresh agent chat. The agent should discover the LaunchChair MCP tools automatically."); | ||
| } | ||
@@ -1252,0 +1449,0 @@ print(""); |
+1
-1
@@ -20,3 +20,3 @@ #!/usr/bin/env node | ||
| const VERSION = "0.4.7"; | ||
| const VERSION = "0.4.8"; | ||
| const CLIENT_TYPES = ["codex", "chatgpt", "claude", "claude_code", "other"]; | ||
@@ -23,0 +23,0 @@ const AGENT_SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url)); |
+21
-33
@@ -22,3 +22,3 @@ #!/usr/bin/env node | ||
| const VERSION = "0.4.7"; | ||
| const VERSION = "0.4.8"; | ||
| const SERVER_NAME = "launchchair-agent-mcp"; | ||
@@ -974,5 +974,3 @@ const AGENT_SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url)); | ||
| function writeMessage(obj) { | ||
| const body = Buffer.from(JSON.stringify(obj), "utf8"); | ||
| process.stdout.write(`Content-Length: ${body.length}\r\n\r\n`); | ||
| process.stdout.write(body); | ||
| process.stdout.write(`${JSON.stringify(obj)}\n`); | ||
| } | ||
@@ -1125,34 +1123,24 @@ | ||
| let buffer = Buffer.alloc(0); | ||
| function dispatchMessage(body) { | ||
| let parsed = null; | ||
| try { | ||
| parsed = JSON.parse(body); | ||
| } catch (_error) { | ||
| return; | ||
| } | ||
| void handleRpc(parsed); | ||
| } | ||
| let buffer = ""; | ||
| process.stdin.setEncoding("utf8"); | ||
| process.stdin.on("data", (chunk) => { | ||
| buffer = Buffer.concat([buffer, chunk]); | ||
| buffer += chunk; | ||
| while (true) { | ||
| const headerEnd = buffer.indexOf("\r\n\r\n"); | ||
| if (headerEnd === -1) break; | ||
| const headerRaw = buffer.slice(0, headerEnd).toString("utf8"); | ||
| const lines = headerRaw.split("\r\n"); | ||
| let contentLength = -1; | ||
| for (const line of lines) { | ||
| const [key, value] = line.split(":"); | ||
| if (key && value && key.toLowerCase() === "content-length") { | ||
| contentLength = Number.parseInt(value.trim(), 10); | ||
| } | ||
| } | ||
| if (!Number.isFinite(contentLength) || contentLength < 0) { | ||
| buffer = Buffer.alloc(0); | ||
| break; | ||
| } | ||
| const bodyStart = headerEnd + 4; | ||
| const bodyEnd = bodyStart + contentLength; | ||
| if (buffer.length < bodyEnd) break; | ||
| const body = buffer.slice(bodyStart, bodyEnd).toString("utf8"); | ||
| buffer = buffer.slice(bodyEnd); | ||
| let parsed = null; | ||
| try { | ||
| parsed = JSON.parse(body); | ||
| } catch (_error) { | ||
| continue; | ||
| } | ||
| void handleRpc(parsed); | ||
| const newlineIndex = buffer.indexOf("\n"); | ||
| if (newlineIndex === -1) break; | ||
| const body = buffer.slice(0, newlineIndex).replace(/\r$/, ""); | ||
| buffer = buffer.slice(newlineIndex + 1); | ||
| if (!body.trim()) continue; | ||
| dispatchMessage(body); | ||
| } | ||
| }); |
+1
-1
| { | ||
| "name": "@launchchair/agent", | ||
| "version": "0.4.7", | ||
| "version": "0.4.8", | ||
| "description": "LaunchChair Agent CLI, MCP bridge, local bridge, and runners for Codex, Claude Code, ChatGPT, Claude, and Hermes workflows.", | ||
@@ -5,0 +5,0 @@ "type": "module", |
+17
-7
@@ -52,3 +52,3 @@ # LaunchChair Agent | ||
| Use `setup` as the easiest path. It saves the build folder, writes a small `AGENTS.md` pointer into that folder, starts browser login when no token is saved yet, writes or prints the MCP config for your desktop agent, and tells you exactly what to do next. | ||
| Use `setup` as the easiest path. It saves the build folder, writes a small `AGENTS.md` pointer into that folder, starts browser login when no token is saved yet, writes the MCP config for your agent, and then proves the server can complete MCP `initialize` and `tools/list` before reporting success. | ||
@@ -58,3 +58,3 @@ Codex Desktop: | ||
| ```bash | ||
| launchchair setup codex --cwd /absolute/path/to/repo-or-empty-folder --write-config | ||
| launchchair setup codex --cwd /absolute/path/to/repo-or-empty-folder | ||
| ``` | ||
@@ -68,8 +68,15 @@ | ||
| Claude Desktop + Claude Code: | ||
| Claude Desktop + Claude Code (Claude Code is registered at user scope when its CLI is installed): | ||
| ```bash | ||
| launchchair setup claude --cwd /absolute/path/to/repo-or-empty-folder --write-config | ||
| launchchair setup claude --cwd /absolute/path/to/repo-or-empty-folder | ||
| ``` | ||
| Configure only one Claude client when needed: | ||
| ```bash | ||
| launchchair setup claude-desktop --cwd /absolute/path/to/repo-or-empty-folder | ||
| launchchair setup claude-code --cwd /absolute/path/to/repo-or-empty-folder | ||
| ``` | ||
| Terminal / CLI agents: | ||
@@ -88,2 +95,4 @@ | ||
| Setup writes client configuration by default. Use `--copy-config`, `--ui-fields`, or `--print-config` only when you intentionally want manual configuration. Authenticated setup exits nonzero if the MCP handshake or required tool discovery fails; `--skip-mcp-check` is an explicit diagnostic escape hatch, not the recommended first-run path. | ||
| For a fresh project before a repo exists, point `--cwd` at an empty folder. LaunchChair can run ideation, validation, positioning, pricing, and MVP blueprint before code exists; Stack Setup and Build MVP can later use that folder as the build target. | ||
@@ -96,4 +105,5 @@ | ||
| ```bash | ||
| launchchair setup codex --cwd /absolute/path/to/repo-or-empty-folder --write-config | ||
| launchchair setup claude --cwd /absolute/path/to/repo-or-empty-folder --write-config | ||
| launchchair setup codex --cwd /absolute/path/to/repo-or-empty-folder | ||
| launchchair setup claude --cwd /absolute/path/to/repo-or-empty-folder | ||
| launchchair setup claude-code --cwd /absolute/path/to/repo-or-empty-folder | ||
| launchchair setup terminal --cwd /absolute/path/to/repo-or-empty-folder | ||
@@ -153,3 +163,3 @@ launchchair projects | ||
| The recommended config launches the MCP server through the exact Node executable and CLI file path generated by `launchchair setup` or `launchchair mcp-config`. It also pins `LAUNCHCHAIR_CONFIG_DIR` to the exact directory used by `launchchair login`, so Codex and Claude users do not need to paste tokens into MCP config after browser login—even when the desktop app starts with a different home or shell environment. | ||
| The recommended config launches the MCP server through the exact Node executable and CLI file path generated by `launchchair setup` or `launchchair mcp-config`. It also pins `LAUNCHCHAIR_CONFIG_DIR` to the exact directory used by `launchchair login`, so Codex and Claude users do not need to paste tokens into MCP config after browser login—even when the desktop app starts with a different home or shell environment. The server uses standard newline-delimited JSON-RPC over stdio, as required by MCP clients. | ||
@@ -156,0 +166,0 @@ Use `LAUNCHCHAIR_AGENT_TOKEN` only as a manual fallback when you skip `launchchair login`, need a per-client token override, or your desktop app cannot read the saved CLI config. If you use a desktop custom MCP form, run `launchchair mcp-config codex --ui-fields` or `launchchair mcp-config claude --ui-fields` and paste the printed command, each argument, env vars, and working directory exactly as shown. |
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 2 instances
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
165766
5.46%3663
5.02%209
5.03%