@bman654/clodex 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server-runtime.ts","../src/paths.ts","../src/config.ts","../src/launch.ts","../src/binary-lookup.ts"],"sourcesContent":["// src/server-runtime.ts\n//\n// Runtime-state advertisement for the standalone `clodex server` command.\n// The server writes ~/.clodex/server-runtime.json on startup and removes it on\n// graceful shutdown so other processes (notably the `clodex-claude` wrapper\n// bin) can discover the running server's mode, port, and CA path without any\n// hardcoding. Stale detection is the READER's job: a crashed server leaves the\n// file behind, so readers must validate pid liveness before trusting it.\n//\n// NOTE: only the standalone `clodex server` command writes this file. The\n// per-session MITM proxy spawned by `clodex claude --proxy` is private to that\n// session and must NOT advertise itself here.\n\nimport { mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { getAppHome } from './paths.js';\n\nexport interface ServerRuntimeState {\n mode: 'endpoint' | 'proxy';\n port: number;\n pid: number;\n /** Proxy mode only: absolute path to the CA bundle a client must trust. */\n caPath?: string;\n startedAt: string;\n}\n\ninterface HomeEnv {\n HOME?: string;\n CLODEX_HOME?: string;\n USERPROFILE?: string;\n}\n\nexport function getServerRuntimePath(env: HomeEnv = process.env): string {\n return join(getAppHome(env), 'server-runtime.json');\n}\n\nfunction isPort(value: unknown): value is number {\n return typeof value === 'number' && Number.isInteger(value) && value >= 1 && value <= 65535;\n}\n\n/** Parse + validate a raw server-runtime.json payload. Returns null for anything malformed. */\nexport function parseServerRuntimeState(raw: string): ServerRuntimeState | null {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return null;\n }\n if (!parsed || typeof parsed !== 'object') return null;\n const record = parsed as Record<string, unknown>;\n\n const mode = record['mode'];\n if (mode !== 'endpoint' && mode !== 'proxy') return null;\n if (!isPort(record['port'])) return null;\n const pid = record['pid'];\n if (typeof pid !== 'number' || !Number.isInteger(pid) || pid <= 0) return null;\n const startedAt = typeof record['startedAt'] === 'string' ? record['startedAt'] : '';\n\n const caPath = record['caPath'];\n if (mode === 'proxy') {\n // A proxy-mode server without a CA path is unusable to clients — treat as invalid.\n if (typeof caPath !== 'string' || !caPath.trim()) return null;\n return { mode, port: record['port'], pid, caPath, startedAt };\n }\n return { mode, port: record['port'], pid, startedAt };\n}\n\n/** kill(pid, 0) liveness probe: EPERM still means the process exists. */\nexport function isPidAlive(\n pid: number,\n kill: (pid: number, signal: number) => unknown = process.kill.bind(process),\n): boolean {\n try {\n kill(pid, 0);\n return true;\n } catch (err) {\n return (err as NodeJS.ErrnoException)?.code === 'EPERM';\n }\n}\n\n/** Best-effort write — a state-file failure must never take the server down. */\nexport function writeServerRuntimeState(state: ServerRuntimeState, env: HomeEnv = process.env): void {\n try {\n const path = getServerRuntimePath(env);\n mkdirSync(dirname(path), { recursive: true, mode: 0o700 });\n writeFileSync(path, `${JSON.stringify(state, null, 2)}\\n`, { encoding: 'utf8', mode: 0o600 });\n } catch {\n // Discovery is optional; the server itself keeps running.\n }\n}\n\n/** Best-effort removal on graceful shutdown. Missing file is fine. */\nexport function removeServerRuntimeState(env: HomeEnv = process.env): void {\n try {\n rmSync(getServerRuntimePath(env), { force: true });\n } catch {\n // Stale files are handled by readers via pid liveness.\n }\n}\n\nexport interface ReadServerRuntimeOptions {\n isAlive?: (pid: number) => boolean;\n}\n\n/**\n * Read the advertised server state, returning null when the file is missing,\n * malformed, or refers to a process that is no longer alive (crashed server).\n */\nexport function readLiveServerRuntimeState(\n env: HomeEnv = process.env,\n options: ReadServerRuntimeOptions = {},\n): ServerRuntimeState | null {\n let raw: string;\n try {\n raw = readFileSync(getServerRuntimePath(env), 'utf8');\n } catch {\n return null;\n }\n const state = parseServerRuntimeState(raw);\n if (!state) return null;\n const alive = options.isAlive ?? isPidAlive;\n return alive(state.pid) ? state : null;\n}\n","import { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { cpSync, existsSync, mkdirSync, readdirSync } from 'node:fs';\n\nexport const APP_DIR_NAME = 'clodex';\n/** One-time silent migration source: the relay-ai config home. */\nexport const LEGACY_APP_DIR_NAME = 'relay-ai';\n\ninterface HomeEnv {\n HOME?: string;\n CLODEX_HOME?: string;\n USERPROFILE?: string;\n}\n\nfunction userHome(env: HomeEnv = process.env): string {\n return env.HOME ?? env.USERPROFILE ?? homedir();\n}\n\nexport function resolveAppHomeOverride(env: HomeEnv = process.env): string | undefined {\n const override = env.CLODEX_HOME;\n return override?.trim() || undefined;\n}\n\nexport function getAppHome(env: HomeEnv = process.env): string {\n const override = resolveAppHomeOverride(env);\n if (override) return override;\n return join(userHome(env), `.${APP_DIR_NAME}`);\n}\n\nexport function getLegacyAppHome(env: HomeEnv = process.env): string {\n return join(userHome(env), `.${LEGACY_APP_DIR_NAME}`);\n}\n\nlet legacyMigrationDone = false;\n\n/**\n * One-time silent migration: when the clodex home does not exist yet but a\n * legacy ~/.relay-ai does, copy its config + auth state over so existing\n * providers and OAuth credentials keep working. The legacy directory itself is\n * never modified or deleted.\n */\nexport function ensureLegacyAppHomeMigrated(env: HomeEnv = process.env): void {\n if (legacyMigrationDone) return;\n legacyMigrationDone = true;\n try {\n const appHome = getAppHome(env);\n if (existsSync(appHome)) return;\n const legacyHome = getLegacyAppHome(env);\n if (!existsSync(legacyHome)) return;\n\n mkdirSync(appHome, { recursive: true, mode: 0o700 });\n for (const entry of readdirSync(legacyHome)) {\n if (entry === 'logs') continue; // session logs are not config/auth state\n cpSync(join(legacyHome, entry), join(appHome, entry), { recursive: true });\n }\n } catch {\n // Migration is best-effort; a fresh home still works.\n }\n}\n\n/** Test hook: allow the migration to run again against a new CLODEX_HOME. */\nexport function resetLegacyMigrationForTests(): void {\n legacyMigrationDone = false;\n}\n\nexport function getConfigPath(env: HomeEnv = process.env): string {\n return join(getAppHome(env), 'config.json');\n}\n\nexport function getProvidersPath(env: HomeEnv = process.env): string {\n return join(getAppHome(env), 'providers.json');\n}\n\nexport function getLogsPath(env: HomeEnv = process.env): string {\n return join(getAppHome(env), 'logs');\n}\n","import type { UserPreferences } from './types.js';\nimport { dirname } from 'node:path';\nimport { mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { ensureLegacyAppHomeMigrated, getConfigPath } from './paths.js';\n\nfunction readJsonFile(path: string): UserPreferences | null {\n try {\n const parsed = JSON.parse(readFileSync(path, 'utf8'));\n return parsed && typeof parsed === 'object' ? parsed as UserPreferences : null;\n } catch {\n return null;\n }\n}\n\nfunction readConfig(): UserPreferences {\n ensureLegacyAppHomeMigrated();\n return readJsonFile(getConfigPath()) ?? {};\n}\n\nfunction writeConfig(config: UserPreferences): void {\n const configPath = getConfigPath();\n mkdirSync(dirname(configPath), { recursive: true, mode: 0o700 });\n writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\\n`, { encoding: 'utf8', mode: 0o600 });\n}\n\nexport function loadPreferences(): UserPreferences {\n const config = readConfig();\n return {\n lastModel: config.lastModel,\n lastProvider: config.lastProvider,\n recentModelsByProvider: config.recentModelsByProvider,\n favoriteModels: config.favoriteModels,\n modelAliases: config.modelAliases,\n claudeBridgeMode: config.claudeBridgeMode,\n serverBridgeMode: config.serverBridgeMode,\n appPathOverrides: config.appPathOverrides,\n recentLaunchFolders: config.recentLaunchFolders,\n server: config.server,\n };\n}\n\nexport function savePreferences(prefs: Partial<Pick<UserPreferences, 'lastModel' | 'lastProvider' | 'recentModelsByProvider' | 'favoriteModels' | 'modelAliases' | 'claudeBridgeMode' | 'serverBridgeMode' | 'appPathOverrides' | 'recentLaunchFolders'>>): void {\n const config = readConfig();\n if (prefs.lastModel !== undefined) config.lastModel = prefs.lastModel;\n if (prefs.lastProvider !== undefined) config.lastProvider = prefs.lastProvider;\n if (prefs.recentModelsByProvider !== undefined) config.recentModelsByProvider = prefs.recentModelsByProvider;\n if (prefs.favoriteModels !== undefined) config.favoriteModels = prefs.favoriteModels;\n if (prefs.modelAliases !== undefined) config.modelAliases = prefs.modelAliases;\n if (prefs.claudeBridgeMode !== undefined) config.claudeBridgeMode = prefs.claudeBridgeMode;\n if (prefs.serverBridgeMode !== undefined) config.serverBridgeMode = prefs.serverBridgeMode;\n if (prefs.appPathOverrides !== undefined) config.appPathOverrides = prefs.appPathOverrides;\n if (prefs.recentLaunchFolders !== undefined) config.recentLaunchFolders = prefs.recentLaunchFolders;\n writeConfig(config);\n}\n\nexport function getAppPathOverride(appId: string): string | undefined {\n const value = loadPreferences().appPathOverrides?.[appId];\n return typeof value === 'string' && value.trim() ? value : undefined;\n}\n\nexport function setAppPathOverride(appId: string, path: string | null): Record<string, string> {\n const config = readConfig();\n const next = { ...(config.appPathOverrides ?? {}) };\n const trimmed = path?.trim() ?? '';\n if (trimmed) next[appId] = trimmed;\n else delete next[appId];\n config.appPathOverrides = next;\n if (Object.keys(next).length === 0) delete config.appPathOverrides;\n writeConfig(config);\n return next;\n}\n\n/**\n * Resolve the bridge mode for a command. An explicit flag applies to that run only —\n * it is persisted as the command's default ONLY when the caller opts in (--save-mode).\n * With no flag, the saved per-command default applies; with no saved default, proxy.\n */\nexport function resolveBridgeMode(\n command: 'claude' | 'server',\n explicit: import('./types.js').BridgeMode | undefined,\n opts: { persist?: boolean } = {},\n): import('./types.js').BridgeMode {\n const key = command === 'claude' ? 'claudeBridgeMode' : 'serverBridgeMode';\n if (explicit) {\n if (opts.persist === true) savePreferences({ [key]: explicit });\n return explicit;\n }\n return loadPreferences()[key] ?? 'proxy';\n}\n\nconst MAX_RECENT_MODELS = 3;\nconst MAX_RECENT_LAUNCH_FOLDERS = 6;\n\nexport function recordLaunchFolder(folder: string): string[] {\n const trimmed = folder.trim();\n if (!trimmed) return loadPreferences().recentLaunchFolders ?? [];\n const config = readConfig();\n const prev = config.recentLaunchFolders ?? [];\n const next = [trimmed, ...prev.filter(path => path !== trimmed)].slice(0, MAX_RECENT_LAUNCH_FOLDERS);\n config.recentLaunchFolders = next;\n writeConfig(config);\n return next;\n}\n\nexport function recordLaunchSelection(\n _agent: 'claude',\n providerId: string,\n modelId: string,\n prefs: UserPreferences,\n): void {\n const prevRecent = prefs.recentModelsByProvider?.[providerId] ?? [];\n const updatedRecent = [modelId, ...prevRecent.filter(id => id !== modelId)].slice(0, MAX_RECENT_MODELS);\n savePreferences({\n lastProvider: providerId,\n lastModel: modelId,\n recentModelsByProvider: { ...prefs.recentModelsByProvider, [providerId]: updatedRecent },\n });\n}\n\nconst SERVER_PASSWORD_SERVICE = 'clodex-server-password';\nconst SERVER_PASSWORD_ACCOUNT = 'server-password';\n\nasync function getServerPasswordKeyring(): Promise<any | null> {\n try {\n const { Entry } = await import('@napi-rs/keyring');\n return new Entry(SERVER_PASSWORD_SERVICE, SERVER_PASSWORD_ACCOUNT);\n } catch {\n return null;\n }\n}\n\nexport async function getSavedServerPassword(): Promise<string | null> {\n const config = readConfig();\n if (config.server?.savedPassword) {\n const pwd = config.server.savedPassword;\n const keyring = await getServerPasswordKeyring();\n if (keyring) {\n try {\n await keyring.setPassword(pwd);\n delete config.server.savedPassword;\n if (Object.keys(config.server).length === 0) delete config.server;\n writeConfig(config);\n } catch {\n // Fallback: keep in config.json if keyring fails\n }\n }\n return pwd;\n }\n\n const keyring = await getServerPasswordKeyring();\n if (keyring) {\n try {\n return await keyring.getPassword();\n } catch {\n return null;\n }\n }\n return null;\n}\n\nexport async function setSavedServerPassword(password: string): Promise<void> {\n const keyring = await getServerPasswordKeyring();\n if (keyring) {\n try {\n await keyring.setPassword(password);\n return;\n } catch {\n // Fallback\n }\n }\n const config = readConfig();\n config.server = {\n ...(config.server ?? {}),\n savedPassword: password,\n };\n writeConfig(config);\n}\n\nexport async function clearSavedServerPassword(): Promise<void> {\n const keyring = await getServerPasswordKeyring();\n if (keyring) {\n try {\n await keyring.deletePassword();\n } catch {\n // Ignore\n }\n }\n const config = readConfig();\n if (!config.server) return;\n delete config.server.savedPassword;\n if (Object.keys(config.server).length === 0) delete config.server;\n writeConfig(config);\n}\n\nexport function getServerExposedProviders(): string[] | null {\n const list = readConfig().server?.exposedProviders;\n return list && list.length > 0 ? list : null;\n}\n\nexport function setServerExposedProviders(providerIds: string[]): void {\n const config = readConfig();\n config.server = {\n ...(config.server ?? {}),\n exposedProviders: providerIds,\n };\n writeConfig(config);\n}\n\nexport function getServerMaskGatewayIds(): boolean {\n return readConfig().server?.maskGatewayIds ?? true;\n}\n\nexport function setServerMaskGatewayIds(mask: boolean): void {\n const config = readConfig();\n config.server = {\n ...(config.server ?? {}),\n maskGatewayIds: mask,\n };\n writeConfig(config);\n}\n\nexport function getServerFavoritesOnly(): boolean {\n return readConfig().server?.favoritesOnly ?? false;\n}\n\nexport function setServerFavoritesOnly(favoritesOnly: boolean): void {\n const config = readConfig();\n config.server = {\n ...(config.server ?? {}),\n favoritesOnly,\n };\n writeConfig(config);\n}\n\nexport function getServerListenMode(): 'local' | 'network' {\n return readConfig().server?.listenMode === 'network' ? 'network' : 'local';\n}\n\nexport function setServerListenMode(listenMode: 'local' | 'network'): void {\n const config = readConfig();\n config.server = {\n ...(config.server ?? {}),\n listenMode,\n };\n writeConfig(config);\n}\n","// src/launch.ts\nimport { execSync, spawn } from 'node:child_process';\nimport { existsSync, appendFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { getAppPathOverride } from './config.js';\nimport { findBinaryOnPath } from './binary-lookup.js';\n\nconst isWindows = process.platform === 'win32';\n\nconst FALLBACK_PATHS = isWindows\n ? [\n join(process.env['APPDATA'] ?? homedir(), 'npm', 'claude.cmd'),\n join(process.env['APPDATA'] ?? homedir(), 'npm', 'claude'),\n join(homedir(), 'AppData', 'Roaming', 'npm', 'claude.cmd'),\n ]\n : [\n join(homedir(), '.local', 'bin', 'claude'),\n join(homedir(), '.npm', 'bin', 'claude'),\n '/usr/local/bin/claude',\n '/opt/homebrew/bin/claude',\n ];\n\nexport function findClaudeBinary(): string | null {\n const environmentOverride = process.env['CLODEX_CLAUDE_PATH'];\n if (environmentOverride?.trim()) {\n return existsSync(environmentOverride) ? environmentOverride : null;\n }\n\n const override = getAppPathOverride('claude');\n if (override) return existsSync(override) ? override : null;\n\n return findBinaryOnPath('claude', FALLBACK_PATHS);\n}\n\nexport function getInstalledClaudeVersion(): string {\n try {\n const claudePath = findClaudeBinary();\n if (!claudePath) return '2.1.183';\n const result = execSync(`${isWindows ? `\"${claudePath}\"` : claudePath} --version`, {\n encoding: 'utf8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n const match = result.match(/(\\d+\\.\\d+\\.\\d+)/);\n if (match) return match[1];\n } catch {\n // fallback\n }\n return '2.1.183'; // default fallback version known to work\n}\n\nexport function buildClaudeArgs(model: string | undefined, extraArgs: string[]): string[] {\n return model ? ['--model', model, ...extraArgs] : [...extraArgs];\n}\n\nexport function launchClaude(\n env: NodeJS.ProcessEnv,\n model: string | undefined,\n extraArgs: string[],\n): Promise<number> {\n return new Promise((resolve) => {\n const claudePath = findClaudeBinary()!;\n const args = buildClaudeArgs(model, extraArgs);\n\n const debugFileIdx = extraArgs.indexOf('--debug-file');\n const debugLogPath = debugFileIdx !== -1 && extraArgs[debugFileIdx + 1] ? extraArgs[debugFileIdx + 1] : undefined;\n\n const originalStdoutWrite = process.stdout.write;\n const originalStderrWrite = process.stderr.write;\n\n const muteWrite = (chunk: string | Uint8Array, encoding?: any, callback?: any) => {\n if (typeof encoding === 'function') {\n callback = encoding;\n }\n if (debugLogPath) {\n try {\n const str = typeof chunk === 'string' ? chunk : new TextDecoder().decode(chunk);\n appendFileSync(debugLogPath, `[parent] ${str}`);\n } catch {\n // ignore\n }\n }\n if (callback) callback();\n return true;\n };\n\n process.stdout.write = muteWrite as any;\n process.stderr.write = muteWrite as any;\n\n const restore = () => {\n process.stdout.write = originalStdoutWrite;\n process.stderr.write = originalStderrWrite;\n };\n\n const child = spawn(claudePath, args, {\n stdio: 'inherit',\n env,\n shell: isWindows,\n });\n\n const forward = (signal: NodeJS.Signals): void => {\n child.kill(signal);\n };\n\n process.once('SIGINT', () => forward('SIGINT'));\n process.once('SIGTERM', () => forward('SIGTERM'));\n\n child.on('exit', (code) => {\n restore();\n resolve(code ?? 0);\n });\n\n child.on('error', (err) => {\n restore();\n resolve(1);\n });\n });\n}\n","import { execFileSync } from 'node:child_process';\nimport { existsSync } from 'node:fs';\n\nexport interface FindBinaryOnPathOptions {\n verifyWhichResult?: boolean;\n isWindows?: boolean;\n exists?: (path: string) => boolean;\n runWhich?: (name: string, isWindows: boolean) => string;\n}\n\nexport function findBinaryOnPath(\n name: string,\n fallbackPaths: string[],\n options: FindBinaryOnPathOptions = {},\n): string | null {\n const isWindows = options.isWindows ?? process.platform === 'win32';\n const exists = options.exists ?? existsSync;\n // argv form, never a shell string — the binary name must not be shell-interpretable\n // (defense-in-depth originally added in d887984, must survive refactors).\n const runWhich = options.runWhich ?? ((binary, win) =>\n execFileSync(win ? 'where.exe' : 'which', [binary], {\n encoding: 'utf8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }));\n\n try {\n const lines = runWhich(name, isWindows)\n .trim()\n .split('\\n')\n .map(line => line.trim())\n .filter(Boolean);\n const path = (isWindows ? lines.find(line => line.toLowerCase().endsWith('.cmd')) : null)\n ?? lines[0];\n if (path && (!options.verifyWhichResult || exists(path))) return path;\n } catch {\n // Fall through to fallback paths.\n }\n\n for (const path of fallbackPaths) {\n if (exists(path)) return path;\n }\n return null;\n}\n"],"mappings":"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","names":["mkdirSync","join","join","mkdirSync","dirname","mkdirSync","readFileSync","writeFileSync","readFileSync","mkdirSync","dirname","writeFileSync","keyring","existsSync","homedir","join","existsSync","isWindows","join","homedir","existsSync"]}
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ findClaudeBinary,
4
+ readLiveServerRuntimeState
5
+ } from "./chunk-C7N2JI7Z.js";
6
+
7
+ // src/claude-wrapper.ts
8
+ import { spawn } from "child_process";
9
+ import { accessSync, constants as fsConstants, statSync } from "fs";
10
+ import { connect } from "net";
11
+ import { constants as osConstants } from "os";
12
+
13
+ // src/wrapper-env.ts
14
+ var PROXY_ENV_VARS = ["HTTPS_PROXY", "HTTP_PROXY", "https_proxy", "http_proxy"];
15
+ var LOCAL_GATEWAY_API_KEY = "clodex-local";
16
+ function computeWrapperEnv(baseEnv, state) {
17
+ const env = { ...baseEnv };
18
+ if (!state) return env;
19
+ if (state.mode === "proxy") {
20
+ const proxyUrl = `http://127.0.0.1:${state.port}`;
21
+ delete env["ANTHROPIC_BASE_URL"];
22
+ for (const name of PROXY_ENV_VARS) env[name] = proxyUrl;
23
+ if (state.caPath) env["NODE_EXTRA_CA_CERTS"] = state.caPath;
24
+ return env;
25
+ }
26
+ for (const name of PROXY_ENV_VARS) delete env[name];
27
+ env["ANTHROPIC_BASE_URL"] = `http://127.0.0.1:${state.port}/anthropic`;
28
+ env["ANTHROPIC_API_KEY"] = LOCAL_GATEWAY_API_KEY;
29
+ return env;
30
+ }
31
+
32
+ // src/claude-wrapper.ts
33
+ var isWindows = process.platform === "win32";
34
+ function isExecutableFile(path) {
35
+ try {
36
+ if (!statSync(path).isFile()) return false;
37
+ if (!isWindows) accessSync(path, fsConstants.X_OK);
38
+ return true;
39
+ } catch {
40
+ return false;
41
+ }
42
+ }
43
+ function portIsOpen(port, timeoutMs = 100) {
44
+ return new Promise((resolve) => {
45
+ const socket = connect({ host: "127.0.0.1", port });
46
+ const done = (result) => {
47
+ socket.destroy();
48
+ resolve(result);
49
+ };
50
+ socket.setTimeout(timeoutMs, () => done(false));
51
+ socket.once("connect", () => done(true));
52
+ socket.once("error", () => done(false));
53
+ });
54
+ }
55
+ async function main() {
56
+ const argv = process.argv.slice(2);
57
+ let claudePath;
58
+ let claudeArgs;
59
+ if (argv[0] && isExecutableFile(argv[0])) {
60
+ claudePath = argv[0];
61
+ claudeArgs = argv.slice(1);
62
+ } else {
63
+ claudePath = findClaudeBinary();
64
+ claudeArgs = argv;
65
+ }
66
+ if (!claudePath) {
67
+ process.stderr.write("clodex-claude: could not find the claude binary (set CLODEX_CLAUDE_PATH)\n");
68
+ process.exit(127);
69
+ }
70
+ let state = readLiveServerRuntimeState();
71
+ if (state && !await portIsOpen(state.port)) state = null;
72
+ const env = computeWrapperEnv(process.env, state);
73
+ const child = spawn(claudePath, claudeArgs, {
74
+ stdio: "inherit",
75
+ env,
76
+ shell: isWindows
77
+ });
78
+ const forward = (signal) => child.kill(signal);
79
+ process.once("SIGINT", () => forward("SIGINT"));
80
+ process.once("SIGTERM", () => forward("SIGTERM"));
81
+ child.on("error", (err) => {
82
+ process.stderr.write(`clodex-claude: failed to launch ${claudePath}: ${err.message}
83
+ `);
84
+ process.exit(127);
85
+ });
86
+ child.on("exit", (code, signal) => {
87
+ if (signal) {
88
+ const signum = osConstants.signals[signal];
89
+ process.exit(signum ? 128 + signum : 1);
90
+ }
91
+ process.exit(code ?? 0);
92
+ });
93
+ }
94
+ void main();
95
+ //# sourceMappingURL=claude-wrapper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/claude-wrapper.ts","../src/wrapper-env.ts"],"sourcesContent":["// src/claude-wrapper.ts — the `clodex-claude` bin.\n//\n// A tiny, fast exec-style wrapper around the Claude Code binary that injects\n// bridge env for a running standalone `clodex server` (discovered via\n// ~/.clodex/server-runtime.json). Two invocation shapes:\n//\n// 1. CLAUDE_CODE_PROCESS_WRAPPER contract: Claude Code invokes\n// `clodex-claude <claude-binary-path> <args...>` for every process it\n// spawns (agents view sessions, background agents). First arg is the\n// claude binary to exec.\n// 2. Direct terminal use: `clodex-claude [args...]` — the claude binary is\n// discovered the same way `clodex claude` discovers it\n// (CLODEX_CLAUDE_PATH override, config override, PATH, fallbacks).\n//\n// With a live proxy-mode server: HTTPS_PROXY/HTTP_PROXY + NODE_EXTRA_CA_CERTS\n// point at it and ANTHROPIC_BASE_URL is removed (claude keeps its own\n// Anthropic auth — this is the recommended mode). With a live endpoint-mode\n// server: ANTHROPIC_BASE_URL points at the gateway. With no live server the\n// env is passed through untouched, so claude always launches.\n//\n// This file must stay a thin shell over pure helpers (wrapper-env.ts,\n// server-runtime.ts) with minimal imports — it runs for every spawned agent.\n\nimport { spawn } from 'node:child_process';\nimport { accessSync, constants as fsConstants, statSync } from 'node:fs';\nimport { connect } from 'node:net';\nimport { constants as osConstants } from 'node:os';\nimport { readLiveServerRuntimeState } from './server-runtime.js';\nimport { computeWrapperEnv } from './wrapper-env.js';\nimport { findClaudeBinary } from './launch.js';\n\nconst isWindows = process.platform === 'win32';\n\nfunction isExecutableFile(path: string): boolean {\n try {\n if (!statSync(path).isFile()) return false;\n if (!isWindows) accessSync(path, fsConstants.X_OK);\n return true;\n } catch {\n return false;\n }\n}\n\n/** Fast TCP probe — the state file can outlive a SIGKILLed listener. Never hangs. */\nfunction portIsOpen(port: number, timeoutMs = 100): Promise<boolean> {\n return new Promise(resolve => {\n const socket = connect({ host: '127.0.0.1', port });\n const done = (result: boolean) => {\n socket.destroy();\n resolve(result);\n };\n socket.setTimeout(timeoutMs, () => done(false));\n socket.once('connect', () => done(true));\n socket.once('error', () => done(false));\n });\n}\n\nasync function main(): Promise<void> {\n const argv = process.argv.slice(2);\n\n let claudePath: string | null;\n let claudeArgs: string[];\n if (argv[0] && isExecutableFile(argv[0])) {\n // CLAUDE_CODE_PROCESS_WRAPPER shape: first arg is the claude binary path.\n claudePath = argv[0];\n claudeArgs = argv.slice(1);\n } else {\n claudePath = findClaudeBinary();\n claudeArgs = argv;\n }\n\n if (!claudePath) {\n process.stderr.write('clodex-claude: could not find the claude binary (set CLODEX_CLAUDE_PATH)\\n');\n process.exit(127);\n }\n\n let state = readLiveServerRuntimeState();\n if (state && !(await portIsOpen(state.port))) state = null;\n const env = computeWrapperEnv(process.env, state);\n\n const child = spawn(claudePath, claudeArgs, {\n stdio: 'inherit',\n env,\n shell: isWindows,\n });\n\n const forward = (signal: NodeJS.Signals) => child.kill(signal);\n process.once('SIGINT', () => forward('SIGINT'));\n process.once('SIGTERM', () => forward('SIGTERM'));\n\n child.on('error', err => {\n process.stderr.write(`clodex-claude: failed to launch ${claudePath}: ${err.message}\\n`);\n process.exit(127);\n });\n child.on('exit', (code, signal) => {\n if (signal) {\n const signum = osConstants.signals[signal as keyof typeof osConstants.signals];\n process.exit(signum ? 128 + signum : 1);\n }\n process.exit(code ?? 0);\n });\n}\n\nvoid main();\n","// src/wrapper-env.ts\n//\n// Pure env computation for the `clodex-claude` wrapper bin. Given the process\n// env and a live `clodex server` runtime state (or null), returns the env to\n// launch the Claude Code binary with. Kept dependency-free so the wrapper\n// stays tiny and fast — it runs for every Claude-Code-spawned agent process.\n\nimport type { ServerRuntimeState } from './server-runtime.js';\n\nconst PROXY_ENV_VARS = ['HTTPS_PROXY', 'HTTP_PROXY', 'https_proxy', 'http_proxy'] as const;\n\n/**\n * Any non-empty key satisfies the local endpoint gateway (`isAuthorized`\n * accepts everything when no server password is set, i.e. local listen mode).\n */\nexport const LOCAL_GATEWAY_API_KEY = 'clodex-local';\n\nexport function computeWrapperEnv(\n baseEnv: NodeJS.ProcessEnv,\n state: ServerRuntimeState | null,\n): NodeJS.ProcessEnv {\n const env: NodeJS.ProcessEnv = { ...baseEnv };\n // No live server: launch claude completely untouched — a down server must\n // never break launching claude.\n if (!state) return env;\n\n if (state.mode === 'proxy') {\n // Selective MITM: claude keeps its own Anthropic credentials; the proxy\n // routes clodex:/alias models to OpenAI and passes everything else through.\n const proxyUrl = `http://127.0.0.1:${state.port}`;\n delete env['ANTHROPIC_BASE_URL'];\n for (const name of PROXY_ENV_VARS) env[name] = proxyUrl;\n if (state.caPath) env['NODE_EXTRA_CA_CERTS'] = state.caPath;\n return env;\n }\n\n // Endpoint gateway: all traffic goes to the local Anthropic-format gateway.\n for (const name of PROXY_ENV_VARS) delete env[name];\n env['ANTHROPIC_BASE_URL'] = `http://127.0.0.1:${state.port}/anthropic`;\n env['ANTHROPIC_API_KEY'] = LOCAL_GATEWAY_API_KEY;\n return env;\n}\n"],"mappings":";;;;;;;AAuBA,SAAS,aAAa;AACtB,SAAS,YAAY,aAAa,aAAa,gBAAgB;AAC/D,SAAS,eAAe;AACxB,SAAS,aAAa,mBAAmB;;;ACjBzC,IAAM,iBAAiB,CAAC,eAAe,cAAc,eAAe,YAAY;AAMzE,IAAM,wBAAwB;AAE9B,SAAS,kBACd,SACA,OACmB;AACnB,QAAM,MAAyB,EAAE,GAAG,QAAQ;AAG5C,MAAI,CAAC,MAAO,QAAO;AAEnB,MAAI,MAAM,SAAS,SAAS;AAG1B,UAAM,WAAW,oBAAoB,MAAM,IAAI;AAC/C,WAAO,IAAI,oBAAoB;AAC/B,eAAW,QAAQ,eAAgB,KAAI,IAAI,IAAI;AAC/C,QAAI,MAAM,OAAQ,KAAI,qBAAqB,IAAI,MAAM;AACrD,WAAO;AAAA,EACT;AAGA,aAAW,QAAQ,eAAgB,QAAO,IAAI,IAAI;AAClD,MAAI,oBAAoB,IAAI,oBAAoB,MAAM,IAAI;AAC1D,MAAI,mBAAmB,IAAI;AAC3B,SAAO;AACT;;;ADVA,IAAM,YAAY,QAAQ,aAAa;AAEvC,SAAS,iBAAiB,MAAuB;AAC/C,MAAI;AACF,QAAI,CAAC,SAAS,IAAI,EAAE,OAAO,EAAG,QAAO;AACrC,QAAI,CAAC,UAAW,YAAW,MAAM,YAAY,IAAI;AACjD,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,SAAS,WAAW,MAAc,YAAY,KAAuB;AACnE,SAAO,IAAI,QAAQ,aAAW;AAC5B,UAAM,SAAS,QAAQ,EAAE,MAAM,aAAa,KAAK,CAAC;AAClD,UAAM,OAAO,CAAC,WAAoB;AAChC,aAAO,QAAQ;AACf,cAAQ,MAAM;AAAA,IAChB;AACA,WAAO,WAAW,WAAW,MAAM,KAAK,KAAK,CAAC;AAC9C,WAAO,KAAK,WAAW,MAAM,KAAK,IAAI,CAAC;AACvC,WAAO,KAAK,SAAS,MAAM,KAAK,KAAK,CAAC;AAAA,EACxC,CAAC;AACH;AAEA,eAAe,OAAsB;AACnC,QAAM,OAAO,QAAQ,KAAK,MAAM,CAAC;AAEjC,MAAI;AACJ,MAAI;AACJ,MAAI,KAAK,CAAC,KAAK,iBAAiB,KAAK,CAAC,CAAC,GAAG;AAExC,iBAAa,KAAK,CAAC;AACnB,iBAAa,KAAK,MAAM,CAAC;AAAA,EAC3B,OAAO;AACL,iBAAa,iBAAiB;AAC9B,iBAAa;AAAA,EACf;AAEA,MAAI,CAAC,YAAY;AACf,YAAQ,OAAO,MAAM,4EAA4E;AACjG,YAAQ,KAAK,GAAG;AAAA,EAClB;AAEA,MAAI,QAAQ,2BAA2B;AACvC,MAAI,SAAS,CAAE,MAAM,WAAW,MAAM,IAAI,EAAI,SAAQ;AACtD,QAAM,MAAM,kBAAkB,QAAQ,KAAK,KAAK;AAEhD,QAAM,QAAQ,MAAM,YAAY,YAAY;AAAA,IAC1C,OAAO;AAAA,IACP;AAAA,IACA,OAAO;AAAA,EACT,CAAC;AAED,QAAM,UAAU,CAAC,WAA2B,MAAM,KAAK,MAAM;AAC7D,UAAQ,KAAK,UAAU,MAAM,QAAQ,QAAQ,CAAC;AAC9C,UAAQ,KAAK,WAAW,MAAM,QAAQ,SAAS,CAAC;AAEhD,QAAM,GAAG,SAAS,SAAO;AACvB,YAAQ,OAAO,MAAM,mCAAmC,UAAU,KAAK,IAAI,OAAO;AAAA,CAAI;AACtF,YAAQ,KAAK,GAAG;AAAA,EAClB,CAAC;AACD,QAAM,GAAG,QAAQ,CAAC,MAAM,WAAW;AACjC,QAAI,QAAQ;AACV,YAAM,SAAS,YAAY,QAAQ,MAA0C;AAC7E,cAAQ,KAAK,SAAS,MAAM,SAAS,CAAC;AAAA,IACxC;AACA,YAAQ,KAAK,QAAQ,CAAC;AAAA,EACxB,CAAC;AACH;AAEA,KAAK,KAAK;","names":[]}