@bman654/clodex 1.2.2 → 2.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 +1 -1
- package/dist/chunk-ZYGZ5GDI.js +1125 -0
- package/dist/chunk-ZYGZ5GDI.js.map +1 -0
- package/dist/claude-wrapper.js +1 -1
- package/dist/cli.js +1375 -902
- package/dist/cli.js.map +1 -1
- package/docs/credential-helpers.md +36 -2
- package/package.json +1 -1
- package/dist/chunk-3XM6UZWP.js +0 -540
- package/dist/chunk-3XM6UZWP.js.map +0 -1
|
@@ -24,7 +24,13 @@ previous store; remove that credential with the previous backend's tooling.
|
|
|
24
24
|
|
|
25
25
|
Clodex verifies the selected store with a disposable write, read, and delete
|
|
26
26
|
before starting device authorization. It also reads back real credential
|
|
27
|
-
writes.
|
|
27
|
+
writes. OAuth refresh returns the new access token only after the rotated
|
|
28
|
+
credential has been stored and verified. An environment override rejected by
|
|
29
|
+
the upstream service remains bypassed until its value changes or the process
|
|
30
|
+
restarts, preventing the same stale value from causing a 401 on every request.
|
|
31
|
+
For stored OAuth credentials, a rejected-access marker is cleared when refresh
|
|
32
|
+
returns a different access token. If the identity provider keeps returning the
|
|
33
|
+
same rejected token, run `clodex providers auth <provider>` to reauthenticate.
|
|
28
34
|
|
|
29
35
|
Credential storage is fail-closed. If the selected credential store fails its
|
|
30
36
|
probe, Clodex stops before device authorization and includes the backend
|
|
@@ -32,6 +38,27 @@ diagnostic in the error instead of continuing with tokens that cannot be
|
|
|
32
38
|
durably stored. Set `CLODEX_CREDENTIAL_HELPER` to the absolute path of an
|
|
33
39
|
external helper, then run the authorization command again.
|
|
34
40
|
|
|
41
|
+
Provider creation, replacement, and removal use the durable cleanup journal at
|
|
42
|
+
`~/.clodex/credential-cleanup.json` (under `CLODEX_HOME` when set). Keeping the
|
|
43
|
+
journal separate from `providers.json` prevents registry writers that only know
|
|
44
|
+
schema 1 provider fields from dropping pending cleanup. A new unreferenced
|
|
45
|
+
credential is journaled before it is written, provider changes are saved before
|
|
46
|
+
superseded credentials are deleted, and uncertain deletion outcomes remain
|
|
47
|
+
queued. Per-credential cross-process locks serialize writes, activation,
|
|
48
|
+
removal, and reconciliation for the same reference. Reconciliation is
|
|
49
|
+
best-effort and sequential: a contended credential can delay later entries
|
|
50
|
+
until its lock attempt times out, but the timeout does not turn an already-saved
|
|
51
|
+
provider into a failed creation. The next `clodex providers` command retries
|
|
52
|
+
queued deletions idempotently and never deletes a credential that is referenced
|
|
53
|
+
by an active provider. If the registry cannot be read and validated, cleanup
|
|
54
|
+
stays queued instead of treating the registry as empty.
|
|
55
|
+
|
|
56
|
+
The cleanup journal accepts only credential accounts generated for Clodex
|
|
57
|
+
provider and OAuth records, including replacement, custom-provider, and scoped
|
|
58
|
+
credential instances. It rejects symbolic links, foreign ownership, broad
|
|
59
|
+
permissions on POSIX, files over 1 MiB, and more than 1,024 queued entries
|
|
60
|
+
before attempting any credential-store deletion.
|
|
61
|
+
|
|
35
62
|
## Protocol
|
|
36
63
|
|
|
37
64
|
The helper receives one of these invocations:
|
|
@@ -56,9 +83,16 @@ contents are never passed in arguments or environment variables. Helper
|
|
|
56
83
|
standard error is not copied into Clodex diagnostics, and output and runtime
|
|
57
84
|
are bounded.
|
|
58
85
|
|
|
86
|
+
The helper protocol transports credential bytes without interpreting them.
|
|
87
|
+
For non-OAuth provider references, Clodex preserves valid opaque JSON secrets.
|
|
88
|
+
OAuth references accept only complete OAuth records or well-known token
|
|
89
|
+
records; malformed or unknown JSON is never used as a bearer token.
|
|
90
|
+
|
|
59
91
|
## Security responsibilities
|
|
60
92
|
|
|
61
|
-
|
|
93
|
+
Clodex owns OAuth parsing, refresh decisions, replacement-token serialization,
|
|
94
|
+
and in-process refresh deduplication per provider and credential reference. The
|
|
95
|
+
helper owns storage and its security properties. A helper should:
|
|
62
96
|
|
|
63
97
|
- encrypt credentials at rest using a system or user trust root;
|
|
64
98
|
- serialize concurrent updates when its backend requires it;
|
package/package.json
CHANGED
package/dist/chunk-3XM6UZWP.js
DELETED
|
@@ -1,540 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/server-runtime.ts
|
|
4
|
-
import {
|
|
5
|
-
closeSync,
|
|
6
|
-
mkdirSync as mkdirSync2,
|
|
7
|
-
openSync,
|
|
8
|
-
readFileSync,
|
|
9
|
-
renameSync,
|
|
10
|
-
rmSync,
|
|
11
|
-
unlinkSync,
|
|
12
|
-
writeFileSync
|
|
13
|
-
} from "fs";
|
|
14
|
-
import { dirname, join as join2 } from "path";
|
|
15
|
-
|
|
16
|
-
// src/paths.ts
|
|
17
|
-
import { homedir } from "os";
|
|
18
|
-
import { join } from "path";
|
|
19
|
-
import { cpSync, existsSync, mkdirSync, readdirSync } from "fs";
|
|
20
|
-
var APP_DIR_NAME = "clodex";
|
|
21
|
-
var LEGACY_APP_DIR_NAME = "relay-ai";
|
|
22
|
-
function userHome(env = process.env) {
|
|
23
|
-
return env.HOME ?? env.USERPROFILE ?? homedir();
|
|
24
|
-
}
|
|
25
|
-
function resolveAppHomeOverride(env = process.env) {
|
|
26
|
-
const override = env.CLODEX_HOME;
|
|
27
|
-
return override?.trim() || void 0;
|
|
28
|
-
}
|
|
29
|
-
function getAppHome(env = process.env) {
|
|
30
|
-
const override = resolveAppHomeOverride(env);
|
|
31
|
-
if (override) return override;
|
|
32
|
-
return join(userHome(env), `.${APP_DIR_NAME}`);
|
|
33
|
-
}
|
|
34
|
-
function getLegacyAppHome(env = process.env) {
|
|
35
|
-
return join(userHome(env), `.${LEGACY_APP_DIR_NAME}`);
|
|
36
|
-
}
|
|
37
|
-
var legacyMigrationDone = false;
|
|
38
|
-
function ensureLegacyAppHomeMigrated(env = process.env) {
|
|
39
|
-
if (legacyMigrationDone) return;
|
|
40
|
-
legacyMigrationDone = true;
|
|
41
|
-
try {
|
|
42
|
-
const appHome = getAppHome(env);
|
|
43
|
-
if (existsSync(appHome)) return;
|
|
44
|
-
const legacyHome = getLegacyAppHome(env);
|
|
45
|
-
if (!existsSync(legacyHome)) return;
|
|
46
|
-
mkdirSync(appHome, { recursive: true, mode: 448 });
|
|
47
|
-
for (const entry of readdirSync(legacyHome)) {
|
|
48
|
-
if (entry === "logs") continue;
|
|
49
|
-
cpSync(join(legacyHome, entry), join(appHome, entry), { recursive: true });
|
|
50
|
-
}
|
|
51
|
-
} catch {
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
function getConfigPath(env = process.env) {
|
|
55
|
-
return join(getAppHome(env), "config.json");
|
|
56
|
-
}
|
|
57
|
-
function getProvidersPath(env = process.env) {
|
|
58
|
-
return join(getAppHome(env), "providers.json");
|
|
59
|
-
}
|
|
60
|
-
function getLogsPath(env = process.env) {
|
|
61
|
-
return join(getAppHome(env), "logs");
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// src/server-runtime.ts
|
|
65
|
-
function getServerRuntimePath(env = process.env) {
|
|
66
|
-
return join2(getAppHome(env), "server-runtime.json");
|
|
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
|
-
}
|
|
76
|
-
function isPort(value) {
|
|
77
|
-
return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= 65535;
|
|
78
|
-
}
|
|
79
|
-
function parseServerRuntimeRecord(value) {
|
|
80
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
81
|
-
const record = value;
|
|
82
|
-
const mode = record["mode"];
|
|
83
|
-
if (mode !== "endpoint" && mode !== "proxy") return null;
|
|
84
|
-
if (!isPort(record["port"])) return null;
|
|
85
|
-
const pid = record["pid"];
|
|
86
|
-
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0) return null;
|
|
87
|
-
const startedAt = typeof record["startedAt"] === "string" ? record["startedAt"] : "";
|
|
88
|
-
const caPath = record["caPath"];
|
|
89
|
-
if (mode === "proxy") {
|
|
90
|
-
if (typeof caPath !== "string" || !caPath.trim()) return null;
|
|
91
|
-
return { mode, port: record["port"], pid, caPath, startedAt };
|
|
92
|
-
}
|
|
93
|
-
return { mode, port: record["port"], pid, startedAt };
|
|
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
|
-
}
|
|
110
|
-
function isPidAlive(pid, kill = process.kill.bind(process)) {
|
|
111
|
-
try {
|
|
112
|
-
kill(pid, 0);
|
|
113
|
-
return true;
|
|
114
|
-
} catch (err) {
|
|
115
|
-
return err?.code === "EPERM";
|
|
116
|
-
}
|
|
117
|
-
}
|
|
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
|
-
}
|
|
166
|
-
try {
|
|
167
|
-
mutate();
|
|
168
|
-
} finally {
|
|
169
|
-
release?.();
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
function readAllRecords(env) {
|
|
173
|
-
let raw;
|
|
174
|
-
try {
|
|
175
|
-
raw = readFileSync(getServerRuntimePath(env), "utf8");
|
|
176
|
-
} catch {
|
|
177
|
-
return [];
|
|
178
|
-
}
|
|
179
|
-
return parseServerRuntimeStates(raw);
|
|
180
|
-
}
|
|
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;
|
|
190
|
-
try {
|
|
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
|
-
});
|
|
198
|
-
} catch {
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
function unregisterServerRuntimeState(pid = process.pid, env = process.env, options = {}) {
|
|
202
|
-
const alive = options.isAlive ?? isPidAlive;
|
|
203
|
-
try {
|
|
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
|
-
});
|
|
214
|
-
} catch {
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
function readLiveServerRuntimeStates(env = process.env, options = {}) {
|
|
218
|
-
const alive = options.isAlive ?? isPidAlive;
|
|
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
|
-
});
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// src/config.ts
|
|
229
|
-
import { dirname as dirname2 } from "path";
|
|
230
|
-
import { mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
231
|
-
function readJsonFile(path) {
|
|
232
|
-
try {
|
|
233
|
-
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
234
|
-
return parsed && typeof parsed === "object" ? parsed : null;
|
|
235
|
-
} catch {
|
|
236
|
-
return null;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
function readConfig() {
|
|
240
|
-
ensureLegacyAppHomeMigrated();
|
|
241
|
-
return readJsonFile(getConfigPath()) ?? {};
|
|
242
|
-
}
|
|
243
|
-
function writeConfig(config) {
|
|
244
|
-
const configPath = getConfigPath();
|
|
245
|
-
mkdirSync3(dirname2(configPath), { recursive: true, mode: 448 });
|
|
246
|
-
writeFileSync2(configPath, `${JSON.stringify(config, null, 2)}
|
|
247
|
-
`, { encoding: "utf8", mode: 384 });
|
|
248
|
-
}
|
|
249
|
-
function loadPreferences() {
|
|
250
|
-
const config = readConfig();
|
|
251
|
-
return {
|
|
252
|
-
lastModel: config.lastModel,
|
|
253
|
-
lastProvider: config.lastProvider,
|
|
254
|
-
recentModelsByProvider: config.recentModelsByProvider,
|
|
255
|
-
favoriteModels: config.favoriteModels,
|
|
256
|
-
modelAliases: config.modelAliases,
|
|
257
|
-
claudeBridgeMode: config.claudeBridgeMode,
|
|
258
|
-
serverBridgeMode: config.serverBridgeMode,
|
|
259
|
-
appPathOverrides: config.appPathOverrides,
|
|
260
|
-
recentLaunchFolders: config.recentLaunchFolders,
|
|
261
|
-
server: config.server
|
|
262
|
-
};
|
|
263
|
-
}
|
|
264
|
-
function savePreferences(prefs) {
|
|
265
|
-
const config = readConfig();
|
|
266
|
-
if (prefs.lastModel !== void 0) config.lastModel = prefs.lastModel;
|
|
267
|
-
if (prefs.lastProvider !== void 0) config.lastProvider = prefs.lastProvider;
|
|
268
|
-
if (prefs.recentModelsByProvider !== void 0) config.recentModelsByProvider = prefs.recentModelsByProvider;
|
|
269
|
-
if (prefs.favoriteModels !== void 0) config.favoriteModels = prefs.favoriteModels;
|
|
270
|
-
if (prefs.modelAliases !== void 0) config.modelAliases = prefs.modelAliases;
|
|
271
|
-
if (prefs.claudeBridgeMode !== void 0) config.claudeBridgeMode = prefs.claudeBridgeMode;
|
|
272
|
-
if (prefs.serverBridgeMode !== void 0) config.serverBridgeMode = prefs.serverBridgeMode;
|
|
273
|
-
if (prefs.appPathOverrides !== void 0) config.appPathOverrides = prefs.appPathOverrides;
|
|
274
|
-
if (prefs.recentLaunchFolders !== void 0) config.recentLaunchFolders = prefs.recentLaunchFolders;
|
|
275
|
-
writeConfig(config);
|
|
276
|
-
}
|
|
277
|
-
function getAppPathOverride(appId) {
|
|
278
|
-
const value = loadPreferences().appPathOverrides?.[appId];
|
|
279
|
-
return typeof value === "string" && value.trim() ? value : void 0;
|
|
280
|
-
}
|
|
281
|
-
function resolveBridgeMode(command, explicit, opts = {}) {
|
|
282
|
-
const key = command === "claude" ? "claudeBridgeMode" : "serverBridgeMode";
|
|
283
|
-
if (explicit) {
|
|
284
|
-
if (opts.persist === true) savePreferences({ [key]: explicit });
|
|
285
|
-
return explicit;
|
|
286
|
-
}
|
|
287
|
-
return loadPreferences()[key] ?? "proxy";
|
|
288
|
-
}
|
|
289
|
-
var MAX_RECENT_MODELS = 3;
|
|
290
|
-
function recordLaunchSelection(_agent, providerId, modelId, prefs) {
|
|
291
|
-
const prevRecent = prefs.recentModelsByProvider?.[providerId] ?? [];
|
|
292
|
-
const updatedRecent = [modelId, ...prevRecent.filter((id) => id !== modelId)].slice(0, MAX_RECENT_MODELS);
|
|
293
|
-
savePreferences({
|
|
294
|
-
lastProvider: providerId,
|
|
295
|
-
lastModel: modelId,
|
|
296
|
-
recentModelsByProvider: { ...prefs.recentModelsByProvider, [providerId]: updatedRecent }
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
|
-
var SERVER_PASSWORD_SERVICE = "clodex-server-password";
|
|
300
|
-
var SERVER_PASSWORD_ACCOUNT = "server-password";
|
|
301
|
-
async function getServerPasswordKeyring() {
|
|
302
|
-
try {
|
|
303
|
-
const { Entry } = await import("@napi-rs/keyring");
|
|
304
|
-
return new Entry(SERVER_PASSWORD_SERVICE, SERVER_PASSWORD_ACCOUNT);
|
|
305
|
-
} catch {
|
|
306
|
-
return null;
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
async function getSavedServerPassword() {
|
|
310
|
-
const config = readConfig();
|
|
311
|
-
if (config.server?.savedPassword) {
|
|
312
|
-
const pwd = config.server.savedPassword;
|
|
313
|
-
const keyring2 = await getServerPasswordKeyring();
|
|
314
|
-
if (keyring2) {
|
|
315
|
-
try {
|
|
316
|
-
await keyring2.setPassword(pwd);
|
|
317
|
-
delete config.server.savedPassword;
|
|
318
|
-
if (Object.keys(config.server).length === 0) delete config.server;
|
|
319
|
-
writeConfig(config);
|
|
320
|
-
} catch {
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
return pwd;
|
|
324
|
-
}
|
|
325
|
-
const keyring = await getServerPasswordKeyring();
|
|
326
|
-
if (keyring) {
|
|
327
|
-
try {
|
|
328
|
-
return await keyring.getPassword();
|
|
329
|
-
} catch {
|
|
330
|
-
return null;
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
return null;
|
|
334
|
-
}
|
|
335
|
-
async function setSavedServerPassword(password) {
|
|
336
|
-
const keyring = await getServerPasswordKeyring();
|
|
337
|
-
if (keyring) {
|
|
338
|
-
try {
|
|
339
|
-
await keyring.setPassword(password);
|
|
340
|
-
return;
|
|
341
|
-
} catch {
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
const config = readConfig();
|
|
345
|
-
config.server = {
|
|
346
|
-
...config.server ?? {},
|
|
347
|
-
savedPassword: password
|
|
348
|
-
};
|
|
349
|
-
writeConfig(config);
|
|
350
|
-
}
|
|
351
|
-
function getServerExposedProviders() {
|
|
352
|
-
const list = readConfig().server?.exposedProviders;
|
|
353
|
-
return list && list.length > 0 ? list : null;
|
|
354
|
-
}
|
|
355
|
-
function setServerExposedProviders(providerIds) {
|
|
356
|
-
const config = readConfig();
|
|
357
|
-
config.server = {
|
|
358
|
-
...config.server ?? {},
|
|
359
|
-
exposedProviders: providerIds
|
|
360
|
-
};
|
|
361
|
-
writeConfig(config);
|
|
362
|
-
}
|
|
363
|
-
function getServerMaskGatewayIds() {
|
|
364
|
-
return readConfig().server?.maskGatewayIds ?? true;
|
|
365
|
-
}
|
|
366
|
-
function setServerMaskGatewayIds(mask) {
|
|
367
|
-
const config = readConfig();
|
|
368
|
-
config.server = {
|
|
369
|
-
...config.server ?? {},
|
|
370
|
-
maskGatewayIds: mask
|
|
371
|
-
};
|
|
372
|
-
writeConfig(config);
|
|
373
|
-
}
|
|
374
|
-
function getServerFavoritesOnly() {
|
|
375
|
-
return readConfig().server?.favoritesOnly ?? false;
|
|
376
|
-
}
|
|
377
|
-
function setServerFavoritesOnly(favoritesOnly) {
|
|
378
|
-
const config = readConfig();
|
|
379
|
-
config.server = {
|
|
380
|
-
...config.server ?? {},
|
|
381
|
-
favoritesOnly
|
|
382
|
-
};
|
|
383
|
-
writeConfig(config);
|
|
384
|
-
}
|
|
385
|
-
function getServerListenMode() {
|
|
386
|
-
return readConfig().server?.listenMode === "network" ? "network" : "local";
|
|
387
|
-
}
|
|
388
|
-
function setServerListenMode(listenMode) {
|
|
389
|
-
const config = readConfig();
|
|
390
|
-
config.server = {
|
|
391
|
-
...config.server ?? {},
|
|
392
|
-
listenMode
|
|
393
|
-
};
|
|
394
|
-
writeConfig(config);
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// src/launch.ts
|
|
398
|
-
import { execSync, spawn } from "child_process";
|
|
399
|
-
import { existsSync as existsSync3, appendFileSync } from "fs";
|
|
400
|
-
import { homedir as homedir2 } from "os";
|
|
401
|
-
import { join as join3 } from "path";
|
|
402
|
-
|
|
403
|
-
// src/binary-lookup.ts
|
|
404
|
-
import { execFileSync } from "child_process";
|
|
405
|
-
import { existsSync as existsSync2 } from "fs";
|
|
406
|
-
function findBinaryOnPath(name, fallbackPaths, options = {}) {
|
|
407
|
-
const isWindows2 = options.isWindows ?? process.platform === "win32";
|
|
408
|
-
const exists = options.exists ?? existsSync2;
|
|
409
|
-
const runWhich = options.runWhich ?? ((binary, win) => execFileSync(win ? "where.exe" : "which", [binary], {
|
|
410
|
-
encoding: "utf8",
|
|
411
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
412
|
-
}));
|
|
413
|
-
try {
|
|
414
|
-
const lines = runWhich(name, isWindows2).trim().split("\n").map((line) => line.trim()).filter(Boolean);
|
|
415
|
-
const path = (isWindows2 ? lines.find((line) => line.toLowerCase().endsWith(".cmd")) : null) ?? lines[0];
|
|
416
|
-
if (path && (!options.verifyWhichResult || exists(path))) return path;
|
|
417
|
-
} catch {
|
|
418
|
-
}
|
|
419
|
-
for (const path of fallbackPaths) {
|
|
420
|
-
if (exists(path)) return path;
|
|
421
|
-
}
|
|
422
|
-
return null;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
// src/launch.ts
|
|
426
|
-
var isWindows = process.platform === "win32";
|
|
427
|
-
var FALLBACK_PATHS = isWindows ? [
|
|
428
|
-
join3(process.env["APPDATA"] ?? homedir2(), "npm", "claude.cmd"),
|
|
429
|
-
join3(process.env["APPDATA"] ?? homedir2(), "npm", "claude"),
|
|
430
|
-
join3(homedir2(), "AppData", "Roaming", "npm", "claude.cmd")
|
|
431
|
-
] : [
|
|
432
|
-
join3(homedir2(), ".local", "bin", "claude"),
|
|
433
|
-
join3(homedir2(), ".npm", "bin", "claude"),
|
|
434
|
-
"/usr/local/bin/claude",
|
|
435
|
-
"/opt/homebrew/bin/claude"
|
|
436
|
-
];
|
|
437
|
-
function findClaudeBinary() {
|
|
438
|
-
const environmentOverride = process.env["CLODEX_CLAUDE_PATH"];
|
|
439
|
-
if (environmentOverride?.trim()) {
|
|
440
|
-
return existsSync3(environmentOverride) ? environmentOverride : null;
|
|
441
|
-
}
|
|
442
|
-
const override = getAppPathOverride("claude");
|
|
443
|
-
if (override) return existsSync3(override) ? override : null;
|
|
444
|
-
return findBinaryOnPath("claude", FALLBACK_PATHS);
|
|
445
|
-
}
|
|
446
|
-
function getInstalledClaudeVersion() {
|
|
447
|
-
try {
|
|
448
|
-
const claudePath = findClaudeBinary();
|
|
449
|
-
if (!claudePath) return "2.1.183";
|
|
450
|
-
const result = execSync(`${isWindows ? `"${claudePath}"` : claudePath} --version`, {
|
|
451
|
-
encoding: "utf8",
|
|
452
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
453
|
-
});
|
|
454
|
-
const match = result.match(/(\d+\.\d+\.\d+)/);
|
|
455
|
-
if (match) return match[1];
|
|
456
|
-
} catch {
|
|
457
|
-
}
|
|
458
|
-
return "2.1.183";
|
|
459
|
-
}
|
|
460
|
-
function buildClaudeArgs(model, extraArgs) {
|
|
461
|
-
return model ? ["--model", model, ...extraArgs] : [...extraArgs];
|
|
462
|
-
}
|
|
463
|
-
function launchClaude(env, model, extraArgs) {
|
|
464
|
-
return new Promise((resolve) => {
|
|
465
|
-
const claudePath = findClaudeBinary();
|
|
466
|
-
const args = buildClaudeArgs(model, extraArgs);
|
|
467
|
-
const debugFileIdx = extraArgs.indexOf("--debug-file");
|
|
468
|
-
const debugLogPath = debugFileIdx !== -1 && extraArgs[debugFileIdx + 1] ? extraArgs[debugFileIdx + 1] : void 0;
|
|
469
|
-
const originalStdoutWrite = process.stdout.write;
|
|
470
|
-
const originalStderrWrite = process.stderr.write;
|
|
471
|
-
const muteWrite = (chunk, encoding, callback) => {
|
|
472
|
-
if (typeof encoding === "function") {
|
|
473
|
-
callback = encoding;
|
|
474
|
-
}
|
|
475
|
-
if (debugLogPath) {
|
|
476
|
-
try {
|
|
477
|
-
const str = typeof chunk === "string" ? chunk : new TextDecoder().decode(chunk);
|
|
478
|
-
appendFileSync(debugLogPath, `[parent] ${str}`);
|
|
479
|
-
} catch {
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
if (callback) callback();
|
|
483
|
-
return true;
|
|
484
|
-
};
|
|
485
|
-
process.stdout.write = muteWrite;
|
|
486
|
-
process.stderr.write = muteWrite;
|
|
487
|
-
const restore = () => {
|
|
488
|
-
process.stdout.write = originalStdoutWrite;
|
|
489
|
-
process.stderr.write = originalStderrWrite;
|
|
490
|
-
};
|
|
491
|
-
const child = spawn(claudePath, args, {
|
|
492
|
-
stdio: "inherit",
|
|
493
|
-
env,
|
|
494
|
-
shell: isWindows
|
|
495
|
-
});
|
|
496
|
-
const forward = (signal) => {
|
|
497
|
-
child.kill(signal);
|
|
498
|
-
};
|
|
499
|
-
process.once("SIGINT", () => forward("SIGINT"));
|
|
500
|
-
process.once("SIGTERM", () => forward("SIGTERM"));
|
|
501
|
-
child.on("exit", (code) => {
|
|
502
|
-
restore();
|
|
503
|
-
resolve(code ?? 0);
|
|
504
|
-
});
|
|
505
|
-
child.on("error", (err) => {
|
|
506
|
-
restore();
|
|
507
|
-
resolve(1);
|
|
508
|
-
});
|
|
509
|
-
});
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
export {
|
|
513
|
-
getAppHome,
|
|
514
|
-
ensureLegacyAppHomeMigrated,
|
|
515
|
-
getProvidersPath,
|
|
516
|
-
getLogsPath,
|
|
517
|
-
isDiscoveryDisabled,
|
|
518
|
-
registerServerRuntimeState,
|
|
519
|
-
unregisterServerRuntimeState,
|
|
520
|
-
readLiveServerRuntimeStates,
|
|
521
|
-
orderWrapperServerCandidates,
|
|
522
|
-
loadPreferences,
|
|
523
|
-
savePreferences,
|
|
524
|
-
resolveBridgeMode,
|
|
525
|
-
recordLaunchSelection,
|
|
526
|
-
getSavedServerPassword,
|
|
527
|
-
setSavedServerPassword,
|
|
528
|
-
getServerExposedProviders,
|
|
529
|
-
setServerExposedProviders,
|
|
530
|
-
getServerMaskGatewayIds,
|
|
531
|
-
setServerMaskGatewayIds,
|
|
532
|
-
getServerFavoritesOnly,
|
|
533
|
-
setServerFavoritesOnly,
|
|
534
|
-
getServerListenMode,
|
|
535
|
-
setServerListenMode,
|
|
536
|
-
findClaudeBinary,
|
|
537
|
-
getInstalledClaudeVersion,
|
|
538
|
-
launchClaude
|
|
539
|
-
};
|
|
540
|
-
//# sourceMappingURL=chunk-3XM6UZWP.js.map
|