@bman654/clodex 0.1.0 → 1.0.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.
package/README.md CHANGED
@@ -1,25 +1,64 @@
1
1
  # clodex
2
2
 
3
- **clodex** bridges [Claude Code](https://docs.anthropic.com/en/docs/claude-code) to OpenAI models — with an OpenAI API key or a ChatGPT/Codex-plan OAuth login. It translates Claude Code's Anthropic wire format to the OpenAI API through the Vercel AI SDK, with working prompt caching, accurate context windows and auto-compaction, live mid-session model switching, and an optional Claude Code binary patcher that makes your OpenAI models first-class citizens inside Claude Code (validated names, `/model` entries, correct context reporting).
3
+ [![npm version](https://img.shields.io/npm/v/%40bman654%2Fclodex.svg)](https://www.npmjs.com/package/@bman654/clodex)
4
+
5
+ **clodex** lets you use your ChatGPT/Codex plan or OpenAI models with Claude Code as if they were Anthropic models.
6
+ You can use them anywhere you use Anthropic models like Opus and Sonnet — as the main session model, and in subagents, workflows, and agent teams. Clodex integrates them directly into Claude Code, using Claude Code's system prompt.
7
+ It works with your existing Claude Code plan as well as your Codex plans WITHOUT violating Anthropic's ToS.
8
+ No messing with CMUX or child codex processes or any of that stuff.
9
+ You can finally have Fable and Sol work together to solve the hardest problems.
10
+
11
+ ![Model picker](./docs/model-picker.png)
12
+
13
+ You can also run clodex as a local OpenAI-compatible endpoint in front of your Codex plan, so any OpenAI-compatible client can use it.
4
14
 
5
15
  > clodex is derived from the original [relay-ai](https://github.com/jacob-bd/relay-ai) project, heavily modified and streamlined for this one use case, with the full commit history preserved.
6
16
 
7
- ## Get started (ChatGPT/Codex plan)
17
+ ## Quick Start (ChatGPT/Codex plan)
8
18
 
9
19
  ```bash
10
20
  npm install -g @bman654/clodex # 1. install the CLI (Node 22+)
11
21
  clodex providers auth openai # 2. sign in with your ChatGPT/Codex plan (device-code OAuth)
12
- clodex models # 3. pick favorite models and short aliases
22
+ clodex models # 3. pick favorite models and aliases
23
+ clodex models --alias sol=clodex:openai-oauth:gpt-5.6-sol
24
+ clodex models --alias luna=clodex:openai-oauth:gpt-5.6-luna
25
+ clodex models --alias terra=clodex:openai-oauth:gpt-5.6-terra
13
26
  clodex patch # 4. (optional) patch Claude Code so those models are first-class
14
27
  clodex claude # 5. launch Claude Code on an OpenAI model
15
28
  ```
16
29
 
17
30
  1. **Install** — puts the `clodex` command on your PATH.
18
31
  2. **Sign in** — opens a device-code OAuth flow for your ChatGPT/Codex plan; the token is stored in your OS credential store. (API-key users: `clodex providers add` instead.)
19
- 3. **Pick models** — an interactive manager for favorites (max 20) and short aliases like `sol`. Favorites drive the `/model` switch menu, proxy-mode routing, and the patcher.
20
- 4. **Patch** *(optional but recommended for proxy mode)* — bakes your favorites and aliases into the Claude Code binary so they pass model validation, appear in `/model`, and report their real context windows. Re-run after each `claude` update; `clodex patch --restore` undoes it.
32
+ 3. **Pick models** — an interactive manager for favorites (max 20) and short aliases like `sol` so you do not need to type the long names. Favorites drive the `/model` switch menu, proxy-mode routing, and the patcher.
33
+ 4. **Patch** *(optional but recommended for proxy mode)* — bakes your favorites and aliases into the Claude Code binary so they pass model validation, appear in `/model`, and report their real context windows. Re-run after each `claude` update; `clodex patch --restore` undoes it. This step is required if you want to use your OpenAI models as subagents via the Agent tool.
21
34
  5. **Launch** — starts Claude Code bridged to the model you choose.
22
35
 
36
+ ## Difference between Clodex and other solutions
37
+
38
+ | Feature | Clodex | relay-ai | CLIProxyAPI | Various process-based solutions |
39
+ |--------|--------|----------|-------------|----|
40
+ | url | https://github.com/bman654/clodex | https://github.com/jacob-bd/relay-ai | https://help.router-for.me/ | |
41
+ | Works with Claude Code Plans without violating Anthropic TOS | ✅ | ✅ | ❌ | ✅ |
42
+ | Can use all claude models + All Codex models together? | ✅ | ✅ | ❌ | ✅ |
43
+ | Claude Code aware of true model context window size | ✅ | ❌ | ❌ | n/a |
44
+ | Supports Agent tool | ✅ | ❌ | ✅ | ❌ |
45
+ | Supports use in Dynamic Workflows | ✅ | ✅ | ✅ | ❌ |
46
+ | OpenAI models use Claude Code skills/tools | ✅ | ✅ | ✅ | ❌ |
47
+ | OpenAI models use Claude Code system prompt | ✅ | ✅ | ✅ | ❌ |
48
+ | Supports use in skill/agent frontmatter | ✅ | ❌ | ✅ | ❌ |
49
+ | Supports OpenAI prompt caching | ✅ | ❌ | ? | ✅ |
50
+ | Uses Websockets to talk to OpenAI API | ✅ | ❌ | ? | ✅ |
51
+
52
+ ### Claude Code Plans and ToS
53
+
54
+ It's important to understand that any tool that duplicates Claude Code's OAuth login flow violates Anthropic's Terms of Service
55
+ and risks getting your account banned. Any tool that initiates a Claude Code OAuth flow from outside of the Claude Code app falls
56
+ into this category.
57
+
58
+ Another way to tell: if a tool needs you to set `ANTHROPIC_BASE_URL` to a custom value, Claude Code can't run on your plan credentials — so the only way that tool can use your plan is by duplicating Claude Code's OAuth flow, which is against the ToS.
59
+
60
+ Clodex avoids this. In proxy mode it uses an HTTP proxy to intercept requests bound for `api.anthropic.com`: requests for an Anthropic model pass through unmodified, still carrying Claude Code's own auth token untouched — your plan credentials are never duplicated or replaced.
61
+
23
62
  ## Bridge modes
24
63
 
25
64
  Both `clodex claude` and `clodex server` support two bridge modes. A mode flag applies to **that run only**; to change a command's default, add `--save-mode` (e.g. `clodex claude --endpoint --save-mode`). With no flag and nothing saved, both commands default to **proxy** mode, which works with your existing Claude auth.
@@ -27,7 +66,8 @@ Both `clodex claude` and `clodex server` support two bridge modes. A mode flag a
27
66
  - **`--proxy`** (the default): a selective man-in-the-middle proxy for `api.anthropic.com`. Claude Code keeps its normal Anthropic login — Anthropic models work untouched — while models named `clodex:<provider-id>:<model-id>` (or their saved aliases) route to OpenAI. Switch with `/model clodex:openai-oauth:gpt-5.6-sol` or `/model sol` after patching.
28
67
  - **`--endpoint`**: clodex runs a local Anthropic-format gateway and launches Claude Code with `ANTHROPIC_BASE_URL` pointed at it. All traffic goes through the gateway. With favorites saved, the gateway is multi-route and Claude Code's `/model` menu lists your starting model plus favorites for live switching.
29
68
 
30
- In proxy mode, Claude Code keeps its own Anthropic credentials and only requests naming a `clodex:` model or alias are rerouted:
69
+ > [!TIP]
70
+ > Proxy mode allows you to continue using your Claude Code plan: login to claude code like normal and the proxy will intercept requests and leave requests for Anthropic models untouched, while requests for your favorite OpenAI models will be re-routed to OpenAI.
31
71
 
32
72
  ```mermaid
33
73
  flowchart LR
@@ -47,7 +87,8 @@ flowchart LR
47
87
  GW -->|"translated request,<br/>clodex-managed OpenAI credentials"| OAI["OpenAI"]
48
88
  ```
49
89
 
50
- Using Claude Code's agents view or background agents? Ask your Claude Code agent to read [docs/background-agents.md](docs/background-agents.md) and set it up for you — one global `clodex server --proxy` plus the `clodex-claude` wrapper bin bridges every claude process automatically.
90
+ > [!TIP]
91
+ > Using Claude Code's agents view or background agents? Ask your Claude Code agent to read [docs/background-agents.md](docs/background-agents.md) and set it up for you — one global `clodex server --proxy` plus the `clodex-claude` wrapper bin bridges every claude process automatically.
51
92
 
52
93
  ## CLI reference
53
94
 
@@ -83,6 +124,7 @@ Common options (both modes):
83
124
  | `--proxy` | Proxy mode for this run: selective `api.anthropic.com` MITM proxy (default when nothing is saved; local only) |
84
125
  | `--save-mode` | With `--endpoint`/`--proxy`: save that mode as the `server` default |
85
126
  | `--port <1-65535>` | Listen port (default 17645) |
127
+ | `--no-discovery` | Don't advertise this server in `~/.clodex/server-runtime.json` (`CLODEX_NO_DISCOVERY=1` also works). Use it for a standalone endpoint the `clodex-claude` wrapper should ignore. |
86
128
  | `--ws-diagnostics` | Log sanitized request envelopes and WebSocket head decisions |
87
129
  | `--help`, `--version` | Help / version |
88
130
 
@@ -100,7 +142,7 @@ Proxy mode has no extra options — it takes only the common options.
100
142
 
101
143
  Bare `clodex server` uses the saved default mode (proxy if none saved). Proxy mode starts immediately. Endpoint mode on a TTY opens a short wizard — start from saved settings, or configure: favorites-only catalog?, which providers to expose, discovery-id masking, and listen local/network (network asks for a password). Without a TTY (or with `--quick`/any endpoint-mode option) it skips all prompts and starts from saved settings; network mode then needs a saved password or `--password`.
102
144
 
103
- **`--mask-gateway-ids`:** endpoint-mode discovery ids look like `anthropic-openai-oauth__gpt-5.6`. Some Claude clients validate model names (Claude Desktop / Cowork pickers, Claude Code skill/agent `model:` frontmatter) and reject or filter ids containing non-Anthropic vendor names. Masking reverses the provider and model segments (`anthropic-htuao-ianepo__6.5-tpg`) so vendor strings never appear literally; display names stay readable (`GPT 5.6 (OpenAI)`), and the gateway accepts both masked and unmasked ids in chat requests. Tradeoff: masked ids are unreadable — copy them exactly from the printed catalog. Masking is on by default; use `--no-mask-gateway-ids` for clients that don't need it.
145
+ **`--mask-gateway-ids`:** endpoint-mode discovery ids look like `anthropic-openai-oauth__gpt-5.6`. Some Claude clients validate model names (Claude Desktop / Cowork pickers, Claude Code skill/agent `model:` frontmatter) and reject or filter ids containing non-Anthropic vendor names. Masking reverses the provider and model segments (`anthropic-htuao-ianepo__6.5-tpg`) so vendor strings never appear literally; display names stay readable (`GPT 5.6 (OpenAI)`). As the request `model`, the gateway accepts the masked id, the unmasked id, the canonical `clodex:<provider>:<model>` id, or a saved alias (e.g. `luna`) — and the response echoes back whichever id you sent. Tradeoff: masked ids are unreadable — copy them exactly from the printed catalog. Masking is on by default; use `--no-mask-gateway-ids` for clients that don't need it.
104
146
 
105
147
  Endpoint-mode endpoints (default port 17645):
106
148
 
@@ -111,6 +153,8 @@ OPENAI_BASE_URL=http://127.0.0.1:17645/openai/v1
111
153
 
112
154
  Use any API key locally; network mode requires the server password. Proxy mode prints `HTTPS_PROXY`, `HTTP_PROXY`, and `NODE_EXTRA_CA_CERTS` values to export — do **not** set `ANTHROPIC_BASE_URL` in that mode.
113
155
 
156
+ Several `clodex server` instances can run at once — each advertises itself in `~/.clodex/server-runtime.json`, and `clodex-claude` prefers a proxy-mode server (newest first) when bridging (see [docs/background-agents.md](docs/background-agents.md)). Pass `--no-discovery` to keep a server out of that file, e.g. a dedicated endpoint you point another tool at.
157
+
114
158
  Examples:
115
159
 
116
160
  ```bash
@@ -1,7 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/server-runtime.ts
4
- import { mkdirSync as mkdirSync2, readFileSync, rmSync, writeFileSync } from "fs";
4
+ import {
5
+ closeSync,
6
+ mkdirSync as mkdirSync2,
7
+ openSync,
8
+ readFileSync,
9
+ renameSync,
10
+ rmSync,
11
+ unlinkSync,
12
+ writeFileSync
13
+ } from "fs";
5
14
  import { dirname, join as join2 } from "path";
6
15
 
7
16
  // src/paths.ts
@@ -56,18 +65,20 @@ function getLogsPath(env = process.env) {
56
65
  function getServerRuntimePath(env = process.env) {
57
66
  return join2(getAppHome(env), "server-runtime.json");
58
67
  }
68
+ function getServerRuntimeLockPath(env = process.env) {
69
+ return join2(getAppHome(env), "server-runtime.lock");
70
+ }
71
+ function isDiscoveryDisabled(flag, env = process.env) {
72
+ if (flag !== void 0) return flag;
73
+ const raw = env.CLODEX_NO_DISCOVERY?.trim().toLowerCase();
74
+ return raw === "1" || raw === "true";
75
+ }
59
76
  function isPort(value) {
60
77
  return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= 65535;
61
78
  }
62
- function parseServerRuntimeState(raw) {
63
- let parsed;
64
- try {
65
- parsed = JSON.parse(raw);
66
- } catch {
67
- return null;
68
- }
69
- if (!parsed || typeof parsed !== "object") return null;
70
- const record = parsed;
79
+ function parseServerRuntimeRecord(value) {
80
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
81
+ const record = value;
71
82
  const mode = record["mode"];
72
83
  if (mode !== "endpoint" && mode !== "proxy") return null;
73
84
  if (!isPort(record["port"])) return null;
@@ -81,6 +92,21 @@ function parseServerRuntimeState(raw) {
81
92
  }
82
93
  return { mode, port: record["port"], pid, startedAt };
83
94
  }
95
+ function parseServerRuntimeStates(raw) {
96
+ let parsed;
97
+ try {
98
+ parsed = JSON.parse(raw);
99
+ } catch {
100
+ return [];
101
+ }
102
+ const items = Array.isArray(parsed) ? parsed : [parsed];
103
+ const states = [];
104
+ for (const item of items) {
105
+ const state = parseServerRuntimeRecord(item);
106
+ if (state) states.push(state);
107
+ }
108
+ return states;
109
+ }
84
110
  function isPidAlive(pid, kill = process.kill.bind(process)) {
85
111
  try {
86
112
  kill(pid, 0);
@@ -89,32 +115,114 @@ function isPidAlive(pid, kill = process.kill.bind(process)) {
89
115
  return err?.code === "EPERM";
90
116
  }
91
117
  }
92
- function writeServerRuntimeState(state, env = process.env) {
118
+ var RUNTIME_LOCK_STALE_MS = 1e4;
119
+ var RUNTIME_LOCK_WAIT_MS = 500;
120
+ var RUNTIME_LOCK_RETRY_MS = 25;
121
+ function tryAcquireRuntimeLock(lockPath, opts = {}) {
122
+ const now = opts.now ?? Date.now();
123
+ const alive = opts.isAlive ?? isPidAlive;
124
+ mkdirSync2(dirname(lockPath), { recursive: true, mode: 448 });
125
+ for (let attempt = 0; attempt < 2; attempt++) {
126
+ try {
127
+ const fd = openSync(lockPath, "wx");
128
+ const content = { pid: process.pid, startedAt: now };
129
+ writeFileSync(fd, JSON.stringify(content));
130
+ closeSync(fd);
131
+ return () => {
132
+ try {
133
+ unlinkSync(lockPath);
134
+ } catch {
135
+ }
136
+ };
137
+ } catch {
138
+ let stale = false;
139
+ try {
140
+ const existing = JSON.parse(readFileSync(lockPath, "utf8"));
141
+ stale = !existing.pid || !alive(existing.pid) || typeof existing.startedAt === "number" && now - existing.startedAt > RUNTIME_LOCK_STALE_MS;
142
+ } catch {
143
+ stale = true;
144
+ }
145
+ if (!stale) return null;
146
+ try {
147
+ unlinkSync(lockPath);
148
+ } catch {
149
+ }
150
+ }
151
+ }
152
+ return null;
153
+ }
154
+ function sleepSync(ms) {
155
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
156
+ }
157
+ function withRuntimeWriteLock(env, mutate) {
158
+ const lockPath = getServerRuntimeLockPath(env);
159
+ let release = null;
160
+ const deadline = Date.now() + RUNTIME_LOCK_WAIT_MS;
161
+ for (; ; ) {
162
+ release = tryAcquireRuntimeLock(lockPath);
163
+ if (release || Date.now() >= deadline) break;
164
+ sleepSync(RUNTIME_LOCK_RETRY_MS);
165
+ }
93
166
  try {
94
- const path = getServerRuntimePath(env);
95
- mkdirSync2(dirname(path), { recursive: true, mode: 448 });
96
- writeFileSync(path, `${JSON.stringify(state, null, 2)}
97
- `, { encoding: "utf8", mode: 384 });
167
+ mutate();
168
+ } finally {
169
+ release?.();
170
+ }
171
+ }
172
+ function readAllRecords(env) {
173
+ let raw;
174
+ try {
175
+ raw = readFileSync(getServerRuntimePath(env), "utf8");
98
176
  } catch {
177
+ return [];
99
178
  }
179
+ return parseServerRuntimeStates(raw);
100
180
  }
101
- function removeServerRuntimeState(env = process.env) {
181
+ function atomicWriteRecords(path, records) {
182
+ mkdirSync2(dirname(path), { recursive: true, mode: 448 });
183
+ const tmpPath = `${path}.${process.pid}.tmp`;
184
+ writeFileSync(tmpPath, `${JSON.stringify(records, null, 2)}
185
+ `, { encoding: "utf8", mode: 384 });
186
+ renameSync(tmpPath, path);
187
+ }
188
+ function registerServerRuntimeState(state, env = process.env, options = {}) {
189
+ const alive = options.isAlive ?? isPidAlive;
102
190
  try {
103
- rmSync(getServerRuntimePath(env), { force: true });
191
+ withRuntimeWriteLock(env, () => {
192
+ const records = readAllRecords(env).filter(
193
+ (record) => record.pid !== state.pid && alive(record.pid)
194
+ );
195
+ records.push(state);
196
+ atomicWriteRecords(getServerRuntimePath(env), records);
197
+ });
104
198
  } catch {
105
199
  }
106
200
  }
107
- function readLiveServerRuntimeState(env = process.env, options = {}) {
108
- let raw;
201
+ function unregisterServerRuntimeState(pid = process.pid, env = process.env, options = {}) {
202
+ const alive = options.isAlive ?? isPidAlive;
109
203
  try {
110
- raw = readFileSync(getServerRuntimePath(env), "utf8");
204
+ withRuntimeWriteLock(env, () => {
205
+ const records = readAllRecords(env).filter(
206
+ (record) => record.pid !== pid && alive(record.pid)
207
+ );
208
+ if (records.length === 0) {
209
+ rmSync(getServerRuntimePath(env), { force: true });
210
+ } else {
211
+ atomicWriteRecords(getServerRuntimePath(env), records);
212
+ }
213
+ });
111
214
  } catch {
112
- return null;
113
215
  }
114
- const state = parseServerRuntimeState(raw);
115
- if (!state) return null;
216
+ }
217
+ function readLiveServerRuntimeStates(env = process.env, options = {}) {
116
218
  const alive = options.isAlive ?? isPidAlive;
117
- return alive(state.pid) ? state : null;
219
+ return readAllRecords(env).filter((state) => alive(state.pid));
220
+ }
221
+ function orderWrapperServerCandidates(records) {
222
+ return [...records].sort((a, b) => {
223
+ if (a.mode !== b.mode) return a.mode === "proxy" ? -1 : 1;
224
+ return (Date.parse(b.startedAt) || 0) - (Date.parse(a.startedAt) || 0);
225
+ });
118
226
  }
119
227
 
120
228
  // src/config.ts
@@ -406,9 +514,11 @@ export {
406
514
  ensureLegacyAppHomeMigrated,
407
515
  getProvidersPath,
408
516
  getLogsPath,
409
- writeServerRuntimeState,
410
- removeServerRuntimeState,
411
- readLiveServerRuntimeState,
517
+ isDiscoveryDisabled,
518
+ registerServerRuntimeState,
519
+ unregisterServerRuntimeState,
520
+ readLiveServerRuntimeStates,
521
+ orderWrapperServerCandidates,
412
522
  loadPreferences,
413
523
  savePreferences,
414
524
  resolveBridgeMode,
@@ -427,4 +537,4 @@ export {
427
537
  getInstalledClaudeVersion,
428
538
  launchClaude
429
539
  };
430
- //# sourceMappingURL=chunk-C7N2JI7Z.js.map
540
+ //# sourceMappingURL=chunk-3XM6UZWP.js.map
@@ -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// Each registering server ADDS its own record (keyed by pid) to\n// ~/.clodex/server-runtime.json on startup and removes ONLY its own record on\n// graceful shutdown, so other processes (notably the `clodex-claude` wrapper\n// bin) can discover every running server's mode, port, and CA path without any\n// hardcoding. The file holds an ARRAY of records; the legacy single-object\n// shape (pre multi-server) is tolerated on read as a one-element list. Stale\n// detection is the READER's job: a crashed server leaves its record behind, so\n// readers must validate pid liveness before trusting it. Writers additionally\n// prune dead-pid records while they hold the write lock.\n//\n// Concurrency: read-modify-write cycles are serialized by a short-lived pid\n// lock (~/.clodex/server-runtime.lock — same pattern as the patcher's\n// patch.lock: O_EXCL create, pid + staleness, ESRCH liveness) and the file is\n// replaced via write-temp-then-rename so a reader never sees a torn write. A\n// crashed lock holder cannot deadlock registration: the lock goes stale after\n// 10 seconds or when its pid dies, and after a brief bounded wait a writer\n// proceeds lockless (best-effort — same exposure as the old single-slot write).\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. `clodex server --no-discovery`\n// (or CLODEX_NO_DISCOVERY=1) also opts a server out of registration entirely.\n\nimport {\n closeSync,\n mkdirSync,\n openSync,\n readFileSync,\n renameSync,\n rmSync,\n unlinkSync,\n writeFileSync,\n} 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\nexport function getServerRuntimeLockPath(env: HomeEnv = process.env): string {\n return join(getAppHome(env), 'server-runtime.lock');\n}\n\n/** `--no-discovery` flag, with CLODEX_NO_DISCOVERY=1 as the env fallback. */\nexport function isDiscoveryDisabled(\n flag: boolean | undefined,\n env: { CLODEX_NO_DISCOVERY?: string } = process.env,\n): boolean {\n if (flag !== undefined) return flag;\n const raw = env.CLODEX_NO_DISCOVERY?.trim().toLowerCase();\n return raw === '1' || raw === 'true';\n}\n\nfunction isPort(value: unknown): value is number {\n return typeof value === 'number' && Number.isInteger(value) && value >= 1 && value <= 65535;\n}\n\n/** Validate one runtime record. Returns null for anything malformed. */\nexport function parseServerRuntimeRecord(value: unknown): ServerRuntimeState | null {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return null;\n const record = value 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/**\n * Parse a raw server-runtime.json payload into a list of records. Tolerates\n * BOTH shapes: the current array of records and the legacy single object\n * (wrapped as a one-element list). Malformed input or records are skipped —\n * never throws.\n */\nexport function parseServerRuntimeStates(raw: string): ServerRuntimeState[] {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return [];\n }\n const items = Array.isArray(parsed) ? parsed : [parsed];\n const states: ServerRuntimeState[] = [];\n for (const item of items) {\n const state = parseServerRuntimeRecord(item);\n if (state) states.push(state);\n }\n return states;\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// ── Write lock (pid + staleness, patcher pattern) ───────────────────────────\n\nconst RUNTIME_LOCK_STALE_MS = 10_000;\nconst RUNTIME_LOCK_WAIT_MS = 500;\nconst RUNTIME_LOCK_RETRY_MS = 25;\n\ninterface RuntimeLockContent {\n pid: number;\n startedAt: number;\n}\n\nfunction tryAcquireRuntimeLock(\n lockPath: string,\n opts: { now?: number; isAlive?: (pid: number) => boolean } = {},\n): (() => void) | null {\n const now = opts.now ?? Date.now();\n const alive = opts.isAlive ?? isPidAlive;\n mkdirSync(dirname(lockPath), { recursive: true, mode: 0o700 });\n\n for (let attempt = 0; attempt < 2; attempt++) {\n try {\n const fd = openSync(lockPath, 'wx');\n const content: RuntimeLockContent = { pid: process.pid, startedAt: now };\n writeFileSync(fd, JSON.stringify(content));\n closeSync(fd);\n return () => {\n try {\n unlinkSync(lockPath);\n } catch {\n // already gone\n }\n };\n } catch {\n // Lock exists — check staleness.\n let stale = false;\n try {\n const existing = JSON.parse(readFileSync(lockPath, 'utf8')) as RuntimeLockContent;\n stale = !existing.pid\n || !alive(existing.pid)\n || (typeof existing.startedAt === 'number' && now - existing.startedAt > RUNTIME_LOCK_STALE_MS);\n } catch {\n stale = true; // unreadable lock file → stale\n }\n if (!stale) return null;\n try {\n unlinkSync(lockPath);\n } catch {\n // raced with the owner's cleanup — retry loop handles it\n }\n }\n }\n return null;\n}\n\nfunction sleepSync(ms: number): void {\n Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);\n}\n\n/**\n * Run a read-modify-write mutation under the runtime lock. The lock is only\n * ever held for a few milliseconds, so after a short bounded wait the mutation\n * proceeds WITHOUT the lock rather than dropping a registration — the atomic\n * rename still prevents torn files; the worst case is a lost concurrent\n * update, which is no worse than the old single-slot behavior.\n */\nfunction withRuntimeWriteLock(env: HomeEnv, mutate: () => void): void {\n const lockPath = getServerRuntimeLockPath(env);\n let release: (() => void) | null = null;\n const deadline = Date.now() + RUNTIME_LOCK_WAIT_MS;\n for (;;) {\n release = tryAcquireRuntimeLock(lockPath);\n if (release || Date.now() >= deadline) break;\n sleepSync(RUNTIME_LOCK_RETRY_MS);\n }\n try {\n mutate();\n } finally {\n release?.();\n }\n}\n\nfunction readAllRecords(env: HomeEnv): ServerRuntimeState[] {\n let raw: string;\n try {\n raw = readFileSync(getServerRuntimePath(env), 'utf8');\n } catch {\n return [];\n }\n return parseServerRuntimeStates(raw);\n}\n\n/** Atomic replace: write a temp file in the same directory, then rename over. */\nfunction atomicWriteRecords(path: string, records: ServerRuntimeState[]): void {\n mkdirSync(dirname(path), { recursive: true, mode: 0o700 });\n const tmpPath = `${path}.${process.pid}.tmp`;\n writeFileSync(tmpPath, `${JSON.stringify(records, null, 2)}\\n`, { encoding: 'utf8', mode: 0o600 });\n renameSync(tmpPath, path);\n}\n\nexport interface RuntimeMutateOptions {\n isAlive?: (pid: number) => boolean;\n}\n\n/**\n * Add or update this server's own record (keyed by pid), pruning records whose\n * pids are dead. Best-effort — a state-file failure must never take the server\n * down.\n */\nexport function registerServerRuntimeState(\n state: ServerRuntimeState,\n env: HomeEnv = process.env,\n options: RuntimeMutateOptions = {},\n): void {\n const alive = options.isAlive ?? isPidAlive;\n try {\n withRuntimeWriteLock(env, () => {\n const records = readAllRecords(env).filter(\n record => record.pid !== state.pid && alive(record.pid),\n );\n records.push(state);\n atomicWriteRecords(getServerRuntimePath(env), records);\n });\n } catch {\n // Discovery is optional; the server itself keeps running.\n }\n}\n\n/**\n * Remove ONLY this server's own record (by pid) on graceful shutdown, pruning\n * dead-pid records along the way. Missing file/record is fine. When no live\n * records remain the file is removed entirely.\n */\nexport function unregisterServerRuntimeState(\n pid: number = process.pid,\n env: HomeEnv = process.env,\n options: RuntimeMutateOptions = {},\n): void {\n const alive = options.isAlive ?? isPidAlive;\n try {\n withRuntimeWriteLock(env, () => {\n const records = readAllRecords(env).filter(\n record => record.pid !== pid && alive(record.pid),\n );\n if (records.length === 0) {\n rmSync(getServerRuntimePath(env), { force: true });\n } else {\n atomicWriteRecords(getServerRuntimePath(env), records);\n }\n });\n } catch {\n // Stale records are handled by readers via pid liveness.\n }\n}\n\nexport interface ReadServerRuntimeOptions {\n isAlive?: (pid: number) => boolean;\n}\n\n/**\n * Read every advertised server record whose process is still alive. Missing or\n * malformed files yield an empty list. Read-only: stale records are ignored\n * here and physically pruned on the next registration/unregistration.\n */\nexport function readLiveServerRuntimeStates(\n env: HomeEnv = process.env,\n options: ReadServerRuntimeOptions = {},\n): ServerRuntimeState[] {\n const alive = options.isAlive ?? isPidAlive;\n return readAllRecords(env).filter(state => alive(state.pid));\n}\n\n/**\n * Wrapper selection policy: order candidate servers by preference —\n * 1. proxy mode before endpoint mode (bridging through the MITM proxy keeps\n * Claude Code's own Anthropic auth, the recommended setup);\n * 2. within a mode, newest startedAt first.\n * If only an endpoint server is live it is used; with no live server the\n * wrapper launches claude untouched (both handled by the caller).\n */\nexport function orderWrapperServerCandidates(records: ServerRuntimeState[]): ServerRuntimeState[] {\n return [...records].sort((a, b) => {\n if (a.mode !== b.mode) return a.mode === 'proxy' ? -1 : 1;\n return (Date.parse(b.startedAt) || 0) - (Date.parse(a.startedAt) || 0);\n });\n}\n\n/**\n * Read the single preferred live server (selection policy above), or null when\n * none is advertised/alive.\n */\nexport function readLiveServerRuntimeState(\n env: HomeEnv = process.env,\n options: ReadServerRuntimeOptions = {},\n): ServerRuntimeState | null {\n return orderWrapperServerCandidates(readLiveServerRuntimeStates(env, options))[0] ?? 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":";;;AA0BA;AAAA,EACE;AAAA,EACA,aAAAA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,SAAS,QAAAC,aAAY;;;ACpC9B,SAAS,eAAe;AACxB,SAAS,YAAY;AACrB,SAAS,QAAQ,YAAY,WAAW,mBAAmB;AAEpD,IAAM,eAAe;AAErB,IAAM,sBAAsB;AAQnC,SAAS,SAAS,MAAe,QAAQ,KAAa;AACpD,SAAO,IAAI,QAAQ,IAAI,eAAe,QAAQ;AAChD;AAEO,SAAS,uBAAuB,MAAe,QAAQ,KAAyB;AACrF,QAAM,WAAW,IAAI;AACrB,SAAO,UAAU,KAAK,KAAK;AAC7B;AAEO,SAAS,WAAW,MAAe,QAAQ,KAAa;AAC7D,QAAM,WAAW,uBAAuB,GAAG;AAC3C,MAAI,SAAU,QAAO;AACrB,SAAO,KAAK,SAAS,GAAG,GAAG,IAAI,YAAY,EAAE;AAC/C;AAEO,SAAS,iBAAiB,MAAe,QAAQ,KAAa;AACnE,SAAO,KAAK,SAAS,GAAG,GAAG,IAAI,mBAAmB,EAAE;AACtD;AAEA,IAAI,sBAAsB;AAQnB,SAAS,4BAA4B,MAAe,QAAQ,KAAW;AAC5E,MAAI,oBAAqB;AACzB,wBAAsB;AACtB,MAAI;AACF,UAAM,UAAU,WAAW,GAAG;AAC9B,QAAI,WAAW,OAAO,EAAG;AACzB,UAAM,aAAa,iBAAiB,GAAG;AACvC,QAAI,CAAC,WAAW,UAAU,EAAG;AAE7B,cAAU,SAAS,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AACnD,eAAW,SAAS,YAAY,UAAU,GAAG;AAC3C,UAAI,UAAU,OAAQ;AACtB,aAAO,KAAK,YAAY,KAAK,GAAG,KAAK,SAAS,KAAK,GAAG,EAAE,WAAW,KAAK,CAAC;AAAA,IAC3E;AAAA,EACF,QAAQ;AAAA,EAER;AACF;AAOO,SAAS,cAAc,MAAe,QAAQ,KAAa;AAChE,SAAO,KAAK,WAAW,GAAG,GAAG,aAAa;AAC5C;AAEO,SAAS,iBAAiB,MAAe,QAAQ,KAAa;AACnE,SAAO,KAAK,WAAW,GAAG,GAAG,gBAAgB;AAC/C;AAEO,SAAS,YAAY,MAAe,QAAQ,KAAa;AAC9D,SAAO,KAAK,WAAW,GAAG,GAAG,MAAM;AACrC;;;ADrBO,SAAS,qBAAqB,MAAe,QAAQ,KAAa;AACvE,SAAOC,MAAK,WAAW,GAAG,GAAG,qBAAqB;AACpD;AAEO,SAAS,yBAAyB,MAAe,QAAQ,KAAa;AAC3E,SAAOA,MAAK,WAAW,GAAG,GAAG,qBAAqB;AACpD;AAGO,SAAS,oBACd,MACA,MAAwC,QAAQ,KACvC;AACT,MAAI,SAAS,OAAW,QAAO;AAC/B,QAAM,MAAM,IAAI,qBAAqB,KAAK,EAAE,YAAY;AACxD,SAAO,QAAQ,OAAO,QAAQ;AAChC;AAEA,SAAS,OAAO,OAAiC;AAC/C,SAAO,OAAO,UAAU,YAAY,OAAO,UAAU,KAAK,KAAK,SAAS,KAAK,SAAS;AACxF;AAGO,SAAS,yBAAyB,OAA2C;AAClF,MAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,EAAG,QAAO;AACxE,QAAM,SAAS;AAEf,QAAM,OAAO,OAAO,MAAM;AAC1B,MAAI,SAAS,cAAc,SAAS,QAAS,QAAO;AACpD,MAAI,CAAC,OAAO,OAAO,MAAM,CAAC,EAAG,QAAO;AACpC,QAAM,MAAM,OAAO,KAAK;AACxB,MAAI,OAAO,QAAQ,YAAY,CAAC,OAAO,UAAU,GAAG,KAAK,OAAO,EAAG,QAAO;AAC1E,QAAM,YAAY,OAAO,OAAO,WAAW,MAAM,WAAW,OAAO,WAAW,IAAI;AAElF,QAAM,SAAS,OAAO,QAAQ;AAC9B,MAAI,SAAS,SAAS;AAEpB,QAAI,OAAO,WAAW,YAAY,CAAC,OAAO,KAAK,EAAG,QAAO;AACzD,WAAO,EAAE,MAAM,MAAM,OAAO,MAAM,GAAG,KAAK,QAAQ,UAAU;AAAA,EAC9D;AACA,SAAO,EAAE,MAAM,MAAM,OAAO,MAAM,GAAG,KAAK,UAAU;AACtD;AAQO,SAAS,yBAAyB,KAAmC;AAC1E,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,GAAG;AAAA,EACzB,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACA,QAAM,QAAQ,MAAM,QAAQ,MAAM,IAAI,SAAS,CAAC,MAAM;AACtD,QAAM,SAA+B,CAAC;AACtC,aAAW,QAAQ,OAAO;AACxB,UAAM,QAAQ,yBAAyB,IAAI;AAC3C,QAAI,MAAO,QAAO,KAAK,KAAK;AAAA,EAC9B;AACA,SAAO;AACT;AAGO,SAAS,WACd,KACA,OAAiD,QAAQ,KAAK,KAAK,OAAO,GACjE;AACT,MAAI;AACF,SAAK,KAAK,CAAC;AACX,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,WAAQ,KAA+B,SAAS;AAAA,EAClD;AACF;AAIA,IAAM,wBAAwB;AAC9B,IAAM,uBAAuB;AAC7B,IAAM,wBAAwB;AAO9B,SAAS,sBACP,UACA,OAA6D,CAAC,GACzC;AACrB,QAAM,MAAM,KAAK,OAAO,KAAK,IAAI;AACjC,QAAM,QAAQ,KAAK,WAAW;AAC9B,EAAAC,WAAU,QAAQ,QAAQ,GAAG,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AAE7D,WAAS,UAAU,GAAG,UAAU,GAAG,WAAW;AAC5C,QAAI;AACF,YAAM,KAAK,SAAS,UAAU,IAAI;AAClC,YAAM,UAA8B,EAAE,KAAK,QAAQ,KAAK,WAAW,IAAI;AACvE,oBAAc,IAAI,KAAK,UAAU,OAAO,CAAC;AACzC,gBAAU,EAAE;AACZ,aAAO,MAAM;AACX,YAAI;AACF,qBAAW,QAAQ;AAAA,QACrB,QAAQ;AAAA,QAER;AAAA,MACF;AAAA,IACF,QAAQ;AAEN,UAAI,QAAQ;AACZ,UAAI;AACF,cAAM,WAAW,KAAK,MAAM,aAAa,UAAU,MAAM,CAAC;AAC1D,gBAAQ,CAAC,SAAS,OACb,CAAC,MAAM,SAAS,GAAG,KAClB,OAAO,SAAS,cAAc,YAAY,MAAM,SAAS,YAAY;AAAA,MAC7E,QAAQ;AACN,gBAAQ;AAAA,MACV;AACA,UAAI,CAAC,MAAO,QAAO;AACnB,UAAI;AACF,mBAAW,QAAQ;AAAA,MACrB,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,UAAU,IAAkB;AACnC,UAAQ,KAAK,IAAI,WAAW,IAAI,kBAAkB,CAAC,CAAC,GAAG,GAAG,GAAG,EAAE;AACjE;AASA,SAAS,qBAAqB,KAAc,QAA0B;AACpE,QAAM,WAAW,yBAAyB,GAAG;AAC7C,MAAI,UAA+B;AACnC,QAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,aAAS;AACP,cAAU,sBAAsB,QAAQ;AACxC,QAAI,WAAW,KAAK,IAAI,KAAK,SAAU;AACvC,cAAU,qBAAqB;AAAA,EACjC;AACA,MAAI;AACF,WAAO;AAAA,EACT,UAAE;AACA,cAAU;AAAA,EACZ;AACF;AAEA,SAAS,eAAe,KAAoC;AAC1D,MAAI;AACJ,MAAI;AACF,UAAM,aAAa,qBAAqB,GAAG,GAAG,MAAM;AAAA,EACtD,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACA,SAAO,yBAAyB,GAAG;AACrC;AAGA,SAAS,mBAAmB,MAAc,SAAqC;AAC7E,EAAAA,WAAU,QAAQ,IAAI,GAAG,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AACzD,QAAM,UAAU,GAAG,IAAI,IAAI,QAAQ,GAAG;AACtC,gBAAc,SAAS,GAAG,KAAK,UAAU,SAAS,MAAM,CAAC,CAAC;AAAA,GAAM,EAAE,UAAU,QAAQ,MAAM,IAAM,CAAC;AACjG,aAAW,SAAS,IAAI;AAC1B;AAWO,SAAS,2BACd,OACA,MAAe,QAAQ,KACvB,UAAgC,CAAC,GAC3B;AACN,QAAM,QAAQ,QAAQ,WAAW;AACjC,MAAI;AACF,yBAAqB,KAAK,MAAM;AAC9B,YAAM,UAAU,eAAe,GAAG,EAAE;AAAA,QAClC,YAAU,OAAO,QAAQ,MAAM,OAAO,MAAM,OAAO,GAAG;AAAA,MACxD;AACA,cAAQ,KAAK,KAAK;AAClB,yBAAmB,qBAAqB,GAAG,GAAG,OAAO;AAAA,IACvD,CAAC;AAAA,EACH,QAAQ;AAAA,EAER;AACF;AAOO,SAAS,6BACd,MAAc,QAAQ,KACtB,MAAe,QAAQ,KACvB,UAAgC,CAAC,GAC3B;AACN,QAAM,QAAQ,QAAQ,WAAW;AACjC,MAAI;AACF,yBAAqB,KAAK,MAAM;AAC9B,YAAM,UAAU,eAAe,GAAG,EAAE;AAAA,QAClC,YAAU,OAAO,QAAQ,OAAO,MAAM,OAAO,GAAG;AAAA,MAClD;AACA,UAAI,QAAQ,WAAW,GAAG;AACxB,eAAO,qBAAqB,GAAG,GAAG,EAAE,OAAO,KAAK,CAAC;AAAA,MACnD,OAAO;AACL,2BAAmB,qBAAqB,GAAG,GAAG,OAAO;AAAA,MACvD;AAAA,IACF,CAAC;AAAA,EACH,QAAQ;AAAA,EAER;AACF;AAWO,SAAS,4BACd,MAAe,QAAQ,KACvB,UAAoC,CAAC,GACf;AACtB,QAAM,QAAQ,QAAQ,WAAW;AACjC,SAAO,eAAe,GAAG,EAAE,OAAO,WAAS,MAAM,MAAM,GAAG,CAAC;AAC7D;AAUO,SAAS,6BAA6B,SAAqD;AAChG,SAAO,CAAC,GAAG,OAAO,EAAE,KAAK,CAAC,GAAG,MAAM;AACjC,QAAI,EAAE,SAAS,EAAE,KAAM,QAAO,EAAE,SAAS,UAAU,KAAK;AACxD,YAAQ,KAAK,MAAM,EAAE,SAAS,KAAK,MAAM,KAAK,MAAM,EAAE,SAAS,KAAK;AAAA,EACtE,CAAC;AACH;;;AE3TA,SAAS,WAAAC,gBAAe;AACxB,SAAS,aAAAC,YAAW,gBAAAC,eAAc,iBAAAC,sBAAqB;AAGvD,SAAS,aAAa,MAAsC;AAC1D,MAAI;AACF,UAAM,SAAS,KAAK,MAAMC,cAAa,MAAM,MAAM,CAAC;AACpD,WAAO,UAAU,OAAO,WAAW,WAAW,SAA4B;AAAA,EAC5E,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,aAA8B;AACrC,8BAA4B;AAC5B,SAAO,aAAa,cAAc,CAAC,KAAK,CAAC;AAC3C;AAEA,SAAS,YAAY,QAA+B;AAClD,QAAM,aAAa,cAAc;AACjC,EAAAC,WAAUC,SAAQ,UAAU,GAAG,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AAC/D,EAAAC,eAAc,YAAY,GAAG,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAAA,GAAM,EAAE,UAAU,QAAQ,MAAM,IAAM,CAAC;AACrG;AAEO,SAAS,kBAAmC;AACjD,QAAM,SAAS,WAAW;AAC1B,SAAO;AAAA,IACL,WAAW,OAAO;AAAA,IAClB,cAAc,OAAO;AAAA,IACrB,wBAAwB,OAAO;AAAA,IAC/B,gBAAgB,OAAO;AAAA,IACvB,cAAc,OAAO;AAAA,IACrB,kBAAkB,OAAO;AAAA,IACzB,kBAAkB,OAAO;AAAA,IACzB,kBAAkB,OAAO;AAAA,IACzB,qBAAqB,OAAO;AAAA,IAC5B,QAAQ,OAAO;AAAA,EACjB;AACF;AAEO,SAAS,gBAAgB,OAAiO;AAC/P,QAAM,SAAS,WAAW;AAC1B,MAAI,MAAM,cAAc,OAAW,QAAO,YAAY,MAAM;AAC5D,MAAI,MAAM,iBAAiB,OAAW,QAAO,eAAe,MAAM;AAClE,MAAI,MAAM,2BAA2B,OAAW,QAAO,yBAAyB,MAAM;AACtF,MAAI,MAAM,mBAAmB,OAAW,QAAO,iBAAiB,MAAM;AACtE,MAAI,MAAM,iBAAiB,OAAW,QAAO,eAAe,MAAM;AAClE,MAAI,MAAM,qBAAqB,OAAW,QAAO,mBAAmB,MAAM;AAC1E,MAAI,MAAM,qBAAqB,OAAW,QAAO,mBAAmB,MAAM;AAC1E,MAAI,MAAM,qBAAqB,OAAW,QAAO,mBAAmB,MAAM;AAC1E,MAAI,MAAM,wBAAwB,OAAW,QAAO,sBAAsB,MAAM;AAChF,cAAY,MAAM;AACpB;AAEO,SAAS,mBAAmB,OAAmC;AACpE,QAAM,QAAQ,gBAAgB,EAAE,mBAAmB,KAAK;AACxD,SAAO,OAAO,UAAU,YAAY,MAAM,KAAK,IAAI,QAAQ;AAC7D;AAmBO,SAAS,kBACd,SACA,UACA,OAA8B,CAAC,GACE;AACjC,QAAM,MAAM,YAAY,WAAW,qBAAqB;AACxD,MAAI,UAAU;AACZ,QAAI,KAAK,YAAY,KAAM,iBAAgB,EAAE,CAAC,GAAG,GAAG,SAAS,CAAC;AAC9D,WAAO;AAAA,EACT;AACA,SAAO,gBAAgB,EAAE,GAAG,KAAK;AACnC;AAEA,IAAM,oBAAoB;AAcnB,SAAS,sBACd,QACA,YACA,SACA,OACM;AACN,QAAM,aAAa,MAAM,yBAAyB,UAAU,KAAK,CAAC;AAClE,QAAM,gBAAgB,CAAC,SAAS,GAAG,WAAW,OAAO,QAAM,OAAO,OAAO,CAAC,EAAE,MAAM,GAAG,iBAAiB;AACtG,kBAAgB;AAAA,IACd,cAAc;AAAA,IACd,WAAW;AAAA,IACX,wBAAwB,EAAE,GAAG,MAAM,wBAAwB,CAAC,UAAU,GAAG,cAAc;AAAA,EACzF,CAAC;AACH;AAEA,IAAM,0BAA0B;AAChC,IAAM,0BAA0B;AAEhC,eAAe,2BAAgD;AAC7D,MAAI;AACF,UAAM,EAAE,MAAM,IAAI,MAAM,OAAO,kBAAkB;AACjD,WAAO,IAAI,MAAM,yBAAyB,uBAAuB;AAAA,EACnE,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,yBAAiD;AACrE,QAAM,SAAS,WAAW;AAC1B,MAAI,OAAO,QAAQ,eAAe;AAChC,UAAM,MAAM,OAAO,OAAO;AAC1B,UAAMC,WAAU,MAAM,yBAAyB;AAC/C,QAAIA,UAAS;AACX,UAAI;AACF,cAAMA,SAAQ,YAAY,GAAG;AAC7B,eAAO,OAAO,OAAO;AACrB,YAAI,OAAO,KAAK,OAAO,MAAM,EAAE,WAAW,EAAG,QAAO,OAAO;AAC3D,oBAAY,MAAM;AAAA,MACpB,QAAQ;AAAA,MAER;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAEA,QAAM,UAAU,MAAM,yBAAyB;AAC/C,MAAI,SAAS;AACX,QAAI;AACF,aAAO,MAAM,QAAQ,YAAY;AAAA,IACnC,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAsB,uBAAuB,UAAiC;AAC5E,QAAM,UAAU,MAAM,yBAAyB;AAC/C,MAAI,SAAS;AACX,QAAI;AACF,YAAM,QAAQ,YAAY,QAAQ;AAClC;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AACA,QAAM,SAAS,WAAW;AAC1B,SAAO,SAAS;AAAA,IACd,GAAI,OAAO,UAAU,CAAC;AAAA,IACtB,eAAe;AAAA,EACjB;AACA,cAAY,MAAM;AACpB;AAkBO,SAAS,4BAA6C;AAC3D,QAAM,OAAO,WAAW,EAAE,QAAQ;AAClC,SAAO,QAAQ,KAAK,SAAS,IAAI,OAAO;AAC1C;AAEO,SAAS,0BAA0B,aAA6B;AACrE,QAAM,SAAS,WAAW;AAC1B,SAAO,SAAS;AAAA,IACd,GAAI,OAAO,UAAU,CAAC;AAAA,IACtB,kBAAkB;AAAA,EACpB;AACA,cAAY,MAAM;AACpB;AAEO,SAAS,0BAAmC;AACjD,SAAO,WAAW,EAAE,QAAQ,kBAAkB;AAChD;AAEO,SAAS,wBAAwB,MAAqB;AAC3D,QAAM,SAAS,WAAW;AAC1B,SAAO,SAAS;AAAA,IACd,GAAI,OAAO,UAAU,CAAC;AAAA,IACtB,gBAAgB;AAAA,EAClB;AACA,cAAY,MAAM;AACpB;AAEO,SAAS,yBAAkC;AAChD,SAAO,WAAW,EAAE,QAAQ,iBAAiB;AAC/C;AAEO,SAAS,uBAAuB,eAA8B;AACnE,QAAM,SAAS,WAAW;AAC1B,SAAO,SAAS;AAAA,IACd,GAAI,OAAO,UAAU,CAAC;AAAA,IACtB;AAAA,EACF;AACA,cAAY,MAAM;AACpB;AAEO,SAAS,sBAA2C;AACzD,SAAO,WAAW,EAAE,QAAQ,eAAe,YAAY,YAAY;AACrE;AAEO,SAAS,oBAAoB,YAAuC;AACzE,QAAM,SAAS,WAAW;AAC1B,SAAO,SAAS;AAAA,IACd,GAAI,OAAO,UAAU,CAAC;AAAA,IACtB;AAAA,EACF;AACA,cAAY,MAAM;AACpB;;;ACpPA,SAAS,UAAU,aAAa;AAChC,SAAS,cAAAC,aAAY,sBAAsB;AAC3C,SAAS,WAAAC,gBAAe;AACxB,SAAS,QAAAC,aAAY;;;ACJrB,SAAS,oBAAoB;AAC7B,SAAS,cAAAC,mBAAkB;AASpB,SAAS,iBACd,MACA,eACA,UAAmC,CAAC,GACrB;AACf,QAAMC,aAAY,QAAQ,aAAa,QAAQ,aAAa;AAC5D,QAAM,SAAS,QAAQ,UAAUD;AAGjC,QAAM,WAAW,QAAQ,aAAa,CAAC,QAAQ,QAC7C,aAAa,MAAM,cAAc,SAAS,CAAC,MAAM,GAAG;AAAA,IAClD,UAAU;AAAA,IACV,OAAO,CAAC,QAAQ,QAAQ,MAAM;AAAA,EAChC,CAAC;AAEH,MAAI;AACF,UAAM,QAAQ,SAAS,MAAMC,UAAS,EACnC,KAAK,EACL,MAAM,IAAI,EACV,IAAI,UAAQ,KAAK,KAAK,CAAC,EACvB,OAAO,OAAO;AACjB,UAAM,QAAQA,aAAY,MAAM,KAAK,UAAQ,KAAK,YAAY,EAAE,SAAS,MAAM,CAAC,IAAI,SAC/E,MAAM,CAAC;AACZ,QAAI,SAAS,CAAC,QAAQ,qBAAqB,OAAO,IAAI,GAAI,QAAO;AAAA,EACnE,QAAQ;AAAA,EAER;AAEA,aAAW,QAAQ,eAAe;AAChC,QAAI,OAAO,IAAI,EAAG,QAAO;AAAA,EAC3B;AACA,SAAO;AACT;;;ADlCA,IAAM,YAAY,QAAQ,aAAa;AAEvC,IAAM,iBAAiB,YACnB;AAAA,EACEC,MAAK,QAAQ,IAAI,SAAS,KAAKC,SAAQ,GAAG,OAAO,YAAY;AAAA,EAC7DD,MAAK,QAAQ,IAAI,SAAS,KAAKC,SAAQ,GAAG,OAAO,QAAQ;AAAA,EACzDD,MAAKC,SAAQ,GAAG,WAAW,WAAW,OAAO,YAAY;AAC3D,IACA;AAAA,EACED,MAAKC,SAAQ,GAAG,UAAU,OAAO,QAAQ;AAAA,EACzCD,MAAKC,SAAQ,GAAG,QAAQ,OAAO,QAAQ;AAAA,EACvC;AAAA,EACA;AACF;AAEG,SAAS,mBAAkC;AAChD,QAAM,sBAAsB,QAAQ,IAAI,oBAAoB;AAC5D,MAAI,qBAAqB,KAAK,GAAG;AAC/B,WAAOC,YAAW,mBAAmB,IAAI,sBAAsB;AAAA,EACjE;AAEA,QAAM,WAAW,mBAAmB,QAAQ;AAC5C,MAAI,SAAU,QAAOA,YAAW,QAAQ,IAAI,WAAW;AAEvD,SAAO,iBAAiB,UAAU,cAAc;AAClD;AAEO,SAAS,4BAAoC;AAClD,MAAI;AACF,UAAM,aAAa,iBAAiB;AACpC,QAAI,CAAC,WAAY,QAAO;AACxB,UAAM,SAAS,SAAS,GAAG,YAAY,IAAI,UAAU,MAAM,UAAU,cAAc;AAAA,MACjF,UAAU;AAAA,MACV,OAAO,CAAC,QAAQ,QAAQ,MAAM;AAAA,IAChC,CAAC;AACD,UAAM,QAAQ,OAAO,MAAM,iBAAiB;AAC5C,QAAI,MAAO,QAAO,MAAM,CAAC;AAAA,EAC3B,QAAQ;AAAA,EAER;AACA,SAAO;AACT;AAEO,SAAS,gBAAgB,OAA2B,WAA+B;AACxF,SAAO,QAAQ,CAAC,WAAW,OAAO,GAAG,SAAS,IAAI,CAAC,GAAG,SAAS;AACjE;AAEO,SAAS,aACd,KACA,OACA,WACiB;AACjB,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,UAAM,aAAa,iBAAiB;AACpC,UAAM,OAAO,gBAAgB,OAAO,SAAS;AAE7C,UAAM,eAAe,UAAU,QAAQ,cAAc;AACrD,UAAM,eAAe,iBAAiB,MAAM,UAAU,eAAe,CAAC,IAAI,UAAU,eAAe,CAAC,IAAI;AAExG,UAAM,sBAAsB,QAAQ,OAAO;AAC3C,UAAM,sBAAsB,QAAQ,OAAO;AAE3C,UAAM,YAAY,CAAC,OAA4B,UAAgB,aAAmB;AAChF,UAAI,OAAO,aAAa,YAAY;AAClC,mBAAW;AAAA,MACb;AACA,UAAI,cAAc;AAChB,YAAI;AACF,gBAAM,MAAM,OAAO,UAAU,WAAW,QAAQ,IAAI,YAAY,EAAE,OAAO,KAAK;AAC9E,yBAAe,cAAc,YAAY,GAAG,EAAE;AAAA,QAChD,QAAQ;AAAA,QAER;AAAA,MACF;AACA,UAAI,SAAU,UAAS;AACvB,aAAO;AAAA,IACT;AAEA,YAAQ,OAAO,QAAQ;AACvB,YAAQ,OAAO,QAAQ;AAEvB,UAAM,UAAU,MAAM;AACpB,cAAQ,OAAO,QAAQ;AACvB,cAAQ,OAAO,QAAQ;AAAA,IACzB;AAEA,UAAM,QAAQ,MAAM,YAAY,MAAM;AAAA,MACpC,OAAO;AAAA,MACP;AAAA,MACA,OAAO;AAAA,IACT,CAAC;AAED,UAAM,UAAU,CAAC,WAAiC;AAChD,YAAM,KAAK,MAAM;AAAA,IACnB;AAEA,YAAQ,KAAK,UAAU,MAAM,QAAQ,QAAQ,CAAC;AAC9C,YAAQ,KAAK,WAAW,MAAM,QAAQ,SAAS,CAAC;AAEhD,UAAM,GAAG,QAAQ,CAAC,SAAS;AACzB,cAAQ;AACR,cAAQ,QAAQ,CAAC;AAAA,IACnB,CAAC;AAED,UAAM,GAAG,SAAS,CAAC,QAAQ;AACzB,cAAQ;AACR,cAAQ,CAAC;AAAA,IACX,CAAC;AAAA,EACH,CAAC;AACH;","names":["mkdirSync","join","join","mkdirSync","dirname","mkdirSync","readFileSync","writeFileSync","readFileSync","mkdirSync","dirname","writeFileSync","keyring","existsSync","homedir","join","existsSync","isWindows","join","homedir","existsSync"]}
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  findClaudeBinary,
4
- readLiveServerRuntimeState
5
- } from "./chunk-C7N2JI7Z.js";
4
+ orderWrapperServerCandidates,
5
+ readLiveServerRuntimeStates
6
+ } from "./chunk-3XM6UZWP.js";
6
7
 
7
8
  // src/claude-wrapper.ts
8
9
  import { spawn } from "child_process";
@@ -67,8 +68,13 @@ async function main() {
67
68
  process.stderr.write("clodex-claude: could not find the claude binary (set CLODEX_CLAUDE_PATH)\n");
68
69
  process.exit(127);
69
70
  }
70
- let state = readLiveServerRuntimeState();
71
- if (state && !await portIsOpen(state.port)) state = null;
71
+ let state = null;
72
+ for (const candidate of orderWrapperServerCandidates(readLiveServerRuntimeStates())) {
73
+ if (await portIsOpen(candidate.port)) {
74
+ state = candidate;
75
+ break;
76
+ }
77
+ }
72
78
  const env = computeWrapperEnv(process.env, state);
73
79
  const child = spawn(claudePath, claudeArgs, {
74
80
  stdio: "inherit",
@@ -1 +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":[]}
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 {\n orderWrapperServerCandidates,\n readLiveServerRuntimeStates,\n type ServerRuntimeState,\n} 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 // Selection policy (see orderWrapperServerCandidates): proxy-mode servers\n // are preferred over endpoint-mode ones — bridging keeps Claude Code's own\n // Anthropic auth — with newest startedAt breaking ties within a mode. The\n // first candidate whose port answers the TCP probe wins; if only an\n // endpoint server is live it is used; with none, claude launches untouched.\n let state: ServerRuntimeState | null = null;\n for (const candidate of orderWrapperServerCandidates(readLiveServerRuntimeStates())) {\n if (await portIsOpen(candidate.port)) {\n state = candidate;\n break;\n }\n }\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":"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","names":[]}