codex-copilot-dx 0.6.21 → 0.7.1

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.
Files changed (42) hide show
  1. package/README.md +142 -323
  2. package/bin/cli.mjs +26 -130
  3. package/package.json +2 -8
  4. package/scripts/benchmark.mjs +0 -114
  5. package/src/adapter.mjs +22 -107
  6. package/src/auth-profile.mjs +18 -130
  7. package/src/cli-animation.mjs +108 -0
  8. package/src/cli-auth.mjs +44 -398
  9. package/src/cli-models.mjs +21 -49
  10. package/src/cli-options.mjs +53 -75
  11. package/src/cli-status.mjs +6 -23
  12. package/src/config.mjs +2 -8
  13. package/src/copilot.mjs +2 -2
  14. package/src/doctor.mjs +44 -291
  15. package/src/model-cache.mjs +4 -11
  16. package/src/models.mjs +0 -184
  17. package/src/observability.mjs +1 -38
  18. package/src/profile-model-runtime.mjs +61 -189
  19. package/src/profile-routing.mjs +2 -102
  20. package/src/profile-runtime.mjs +15 -68
  21. package/src/responses-handler.mjs +1 -1
  22. package/src/responses-proxy.mjs +1 -1
  23. package/src/running-adapter.mjs +2 -4
  24. package/src/stream-errors.mjs +3 -6
  25. package/src/stream-performance.mjs +1 -1
  26. package/src/terminal-activity.mjs +40 -34
  27. package/src/terminal-animation.mjs +249 -0
  28. package/src/usage.mjs +0 -31
  29. package/src/user-settings.mjs +50 -0
  30. package/src/anthropic-handler.mjs +0 -195
  31. package/src/anthropic.mjs +0 -387
  32. package/src/claude-config.mjs +0 -57
  33. package/src/claude-desktop-config.mjs +0 -317
  34. package/src/cli-error.mjs +0 -50
  35. package/src/pm-studio-asar.mjs +0 -1244
  36. package/src/pm-studio-bundle-content.mjs +0 -165
  37. package/src/pm-studio-relay.mjs +0 -563
  38. package/src/pm-studio-setup-lock.mjs +0 -336
  39. package/src/pm-studio-setup.mjs +0 -1581
  40. package/src/pm-studio-status.mjs +0 -369
  41. package/src/token-counter-service.mjs +0 -211
  42. package/src/token-counter-worker.mjs +0 -16
package/src/doctor.mjs CHANGED
@@ -4,16 +4,13 @@ import os from "node:os";
4
4
  import net from "node:net";
5
5
  import { githubTokenPath, readGithubTokenMetadata, validateGithubToken } from "./auth.mjs";
6
6
  import {
7
- AUTH_PROFILE_CLAUDE,
8
7
  AUTH_PROFILE_CODEX,
9
8
  readAuthProfileCredentials,
10
9
  } from "./auth-profile.mjs";
11
- import { claudeDesktopPaths } from "./claude-desktop-config.mjs";
12
10
  import { status } from "./status.mjs";
13
11
  import { buildHeaders, FALLBACK_VSCODE_VERSION, parseApiBase } from "./copilot.mjs";
14
12
  import { adapterBaseUrl, checkRunningAdapter } from "./running-adapter.mjs";
15
- import { CODEX_AUTO_REVIEW_MODEL, isClaudeCopilotModel } from "./models.mjs";
16
- import { loadModelCache } from "./model-cache.mjs";
13
+ import { CODEX_AUTO_REVIEW_MODEL } from "./models.mjs";
17
14
 
18
15
  function localGatewayBaseUrl(host, port) {
19
16
  const safeHost = String(host || "127.0.0.1");
@@ -24,18 +21,8 @@ function localGatewayBaseUrl(host, port) {
24
21
  function readText(filePath) {
25
22
  try {
26
23
  return { ok: true, text: fs.readFileSync(filePath, "utf8") };
27
- } catch (e) {
28
- return { ok: false, missing: e?.code === "ENOENT", error: e };
29
- }
30
- }
31
-
32
- function readJson(filePath) {
33
- const raw = readText(filePath);
34
- if (!raw.ok) return raw;
35
- try {
36
- return { ok: true, json: JSON.parse(raw.text) };
37
- } catch (e) {
38
- return { ok: false, parseError: true, error: e };
24
+ } catch (error) {
25
+ return { ok: false, missing: error?.code === "ENOENT", error };
39
26
  }
40
27
  }
41
28
 
@@ -64,21 +51,18 @@ function modelEndpoints(model) {
64
51
  return Array.isArray(model?.supported_endpoints) ? model.supported_endpoints : [];
65
52
  }
66
53
 
67
- export function selectCompatibilityModels(models, { claudeModels = models } = {}) {
68
- const data = copilotModelData(models).filter((model) => model?.model_picker_enabled !== false);
69
- const claudeData = copilotModelData(claudeModels);
70
- const responsesCandidates = data.filter((model) => {
71
- const id = String(model?.id || "");
72
- const endpoints = modelEndpoints(model);
73
- return id.startsWith("gpt-") && (endpoints.includes("/responses") || endpoints.includes("/v1/responses"));
74
- });
75
- const responsesOnly = [...responsesCandidates].reverse()
54
+ export function selectCompatibilityModels(models) {
55
+ const candidates = copilotModelData(models)
56
+ .filter((model) => model?.model_picker_enabled !== false)
57
+ .filter((model) => {
58
+ const id = String(model?.id || "");
59
+ const endpoints = modelEndpoints(model);
60
+ return id.startsWith("gpt-")
61
+ && (endpoints.includes("/responses") || endpoints.includes("/v1/responses"));
62
+ });
63
+ const responsesOnly = [...candidates].reverse()
76
64
  .find((model) => !modelEndpoints(model).includes("/chat/completions"));
77
- const claude = claudeData.find(isClaudeCopilotModel);
78
- return {
79
- responsesModel: String((responsesOnly || responsesCandidates[0])?.id || ""),
80
- claudeModel: String(claude?.id || ""),
81
- };
65
+ return { responsesModel: String((responsesOnly || candidates[0])?.id || "") };
82
66
  }
83
67
 
84
68
  async function fetchTextWithTimeout(fetchImpl, url, init, timeoutMs) {
@@ -113,8 +97,8 @@ async function runCompatibilityCheck(checks, label, task) {
113
97
  const value = await task();
114
98
  checks.push({ kind: "ok", message: `${label} passed (${Date.now() - started}ms)` });
115
99
  return value;
116
- } catch (e) {
117
- const reason = e?.name === "AbortError" ? "timed out" : e?.message || "unknown error";
100
+ } catch (error) {
101
+ const reason = error?.name === "AbortError" ? "timed out" : error?.message || "unknown error";
118
102
  checks.push({ kind: "err", message: `${label} failed: ${reason}` });
119
103
  return null;
120
104
  }
@@ -140,8 +124,6 @@ export async function inspectAdapterCompatibility({
140
124
  port = 2026,
141
125
  fetchImpl = fetch,
142
126
  timeoutMs = 120000,
143
- claudeMode = "inherited",
144
- claudeModels,
145
127
  } = {}) {
146
128
  const baseUrl = localGatewayBaseUrl(connectHost(host), port);
147
129
  const checks = [];
@@ -154,9 +136,7 @@ export async function inspectAdapterCompatibility({
154
136
  });
155
137
  if (!models) return checks;
156
138
 
157
- const { responsesModel, claudeModel } = selectCompatibilityModels(models, {
158
- claudeModels: claudeMode === "isolated" ? (claudeModels || { data: [] }) : models,
159
- });
139
+ const { responsesModel } = selectCompatibilityModels(models);
160
140
  if (!responsesModel) {
161
141
  checks.push({ kind: "err", message: "Compatibility Responses check failed: no GPT model advertises /responses" });
162
142
  return checks;
@@ -201,23 +181,6 @@ export async function inspectAdapterCompatibility({
201
181
  parseCompactionResponse(text);
202
182
  });
203
183
 
204
- if (!claudeModel) {
205
- const reason = claudeMode === "isolated"
206
- ? "the isolated Claude model cache is unavailable or advertises no Claude chat model; restart ccdx to refresh it"
207
- : "no Claude chat model was advertised";
208
- checks.push({ kind: "warn", message: `Anthropic stream compatibility was not verified because ${reason}` });
209
- return checks;
210
- }
211
-
212
- await runCompatibilityCheck(checks, `Anthropic Messages stream (${claudeModel})`, async () => {
213
- const text = await compatibilityRequest(fetchImpl, `${baseUrl}/v1/messages`, {
214
- model: claudeModel,
215
- max_tokens: 16,
216
- stream: true,
217
- messages: [{ role: "user", content: "Reply with OK only." }],
218
- }, timeoutMs);
219
- if (!/^event:\s*message_stop\s*$/m.test(text)) throw new Error("stream did not contain message_stop");
220
- });
221
184
  return checks;
222
185
  }
223
186
 
@@ -236,73 +199,36 @@ export function inspectGitHubToken({ home = os.homedir() } = {}) {
236
199
  }
237
200
 
238
201
  export function inspectAuthProfiles({ home = os.homedir() } = {}) {
239
- const checks = [
202
+ return [
240
203
  ...inspectGitHubToken({ home }),
241
204
  { kind: "ok", message: "Codex authentication profile uses the legacy path" },
242
205
  ];
243
- try {
244
- const claude = readAuthProfileCredentials(AUTH_PROFILE_CLAUDE, { home });
245
- if (!claude.configured) {
246
- checks.push({ kind: "ok", message: "Claude authentication profile is not isolated and inherits Codex" });
247
- } else if (!claude.valid) {
248
- checks.push({
249
- kind: "err",
250
- message: `Claude isolated authentication profile is invalid: ${claude.reason}`,
251
- fix: "ccdx auth login claude --reauth",
252
- });
253
- } else {
254
- const account = claude.identity?.login ? ` for ${claude.identity.login}` : "";
255
- checks.push({ kind: "ok", message: `Claude isolated authentication profile is configured${account}` });
256
- }
257
- } catch {
258
- checks.push({ kind: "err", message: "Claude isolated authentication profile could not be inspected" });
259
- }
260
- return checks;
261
206
  }
262
207
 
263
208
  function checkedDoctorProfile(profile = AUTH_PROFILE_CODEX) {
264
209
  const value = String(profile || AUTH_PROFILE_CODEX).trim().toLowerCase();
265
- if (![AUTH_PROFILE_CODEX, AUTH_PROFILE_CLAUDE, "all"].includes(value)) {
266
- throw new Error(`Doctor profile must be codex, claude, or all: ${profile}`);
210
+ if (value !== AUTH_PROFILE_CODEX) {
211
+ throw new Error(`Doctor profile must be codex: ${profile}`);
267
212
  }
268
213
  return value;
269
214
  }
270
215
 
271
- function onlineMessage(profile, message, inherited = false) {
272
- if (profile === AUTH_PROFILE_CODEX) return message;
273
- return `${inherited ? "Claude inherited Codex" : "Claude profile"}: ${message}`;
274
- }
275
-
276
216
  export async function inspectGitHubTokenOnline({
277
217
  home = os.homedir(),
278
218
  profile = AUTH_PROFILE_CODEX,
279
219
  fetchImpl = fetch,
280
220
  timeoutMs = 10000,
281
221
  } = {}) {
282
- const requestedProfile = checkedDoctorProfile(profile);
283
- if (requestedProfile === "all") throw new Error("inspectGitHubTokenOnline accepts one profile at a time");
284
- let inherited = false;
222
+ checkedDoctorProfile(profile);
285
223
  let credentials;
286
224
  try {
287
- credentials = readAuthProfileCredentials(requestedProfile, { home });
288
- if (requestedProfile === AUTH_PROFILE_CLAUDE && !credentials.configured) {
289
- inherited = true;
290
- credentials = readAuthProfileCredentials(AUTH_PROFILE_CODEX, { home });
291
- }
225
+ credentials = readAuthProfileCredentials(AUTH_PROFILE_CODEX, { home });
292
226
  } catch {
293
- return [{ kind: "warn", message: onlineMessage(requestedProfile, "Online Copilot check failed while reading the local credential", inherited) }];
227
+ return [{ kind: "warn", message: "Online Copilot check failed while reading the local credential" }];
294
228
  }
295
229
 
296
- const checks = [];
297
- if (inherited) {
298
- checks.push({ kind: "ok", message: "Claude profile is not separately configured and inherits Codex authentication" });
299
- }
300
230
  if (!credentials.valid) {
301
- const reason = requestedProfile === AUTH_PROFILE_CODEX
302
- ? "the GitHub token is missing"
303
- : credentials.reason;
304
- checks.push({ kind: "warn", message: onlineMessage(requestedProfile, `Online Copilot check skipped because ${reason}`, inherited) });
305
- return checks;
231
+ return [{ kind: "warn", message: "Online Copilot check skipped because the GitHub token is missing" }];
306
232
  }
307
233
 
308
234
  const controller = new AbortController();
@@ -314,11 +240,10 @@ export async function inspectGitHubTokenOnline({
314
240
  });
315
241
  if (!validation.ok) {
316
242
  const statusText = validation.status ? ` (HTTP ${validation.status})` : "";
317
- checks.push({
243
+ return [{
318
244
  kind: validation.transient ? "warn" : "err",
319
- message: onlineMessage(requestedProfile, `GitHub Copilot authentication failed: ${validation.reason}${statusText}`, inherited),
320
- });
321
- return checks;
245
+ message: `GitHub Copilot authentication failed: ${validation.reason}${statusText}`,
246
+ }];
322
247
  }
323
248
 
324
249
  const tokenData = validation.copilotTokenData;
@@ -331,64 +256,35 @@ export async function inspectGitHubTokenOnline({
331
256
  });
332
257
  const modelResp = await fetchImpl(`${apiBase}/models`, { headers, signal: controller.signal });
333
258
  if (!modelResp.ok) {
334
- checks.push(
335
- { kind: "ok", message: onlineMessage(requestedProfile, `GitHub Copilot access verified for ${validation.login || "current account"}`, inherited) },
336
- { kind: "err", message: onlineMessage(requestedProfile, `Copilot models endpoint returned HTTP ${modelResp.status}`, inherited) },
337
- );
338
- return checks;
259
+ return [
260
+ { kind: "ok", message: `GitHub Copilot access verified for ${validation.login || "current account"}` },
261
+ { kind: "err", message: `Copilot models endpoint returned HTTP ${modelResp.status}` },
262
+ ];
339
263
  }
340
264
  const models = await modelResp.json();
341
265
  const data = Array.isArray(models) ? models : models?.data;
342
- checks.push(
343
- { kind: "ok", message: onlineMessage(requestedProfile, `GitHub Copilot access verified for ${validation.login || "current account"}`, inherited) },
344
- { kind: "ok", message: onlineMessage(requestedProfile, `Copilot models endpoint returned ${Array.isArray(data) ? data.length : 0} models`, inherited) },
345
- );
346
- return checks;
347
- } catch (e) {
348
- const errorCode = e?.cause?.code || e?.code || "";
349
- const reason = e?.name === "AbortError"
266
+ return [
267
+ { kind: "ok", message: `GitHub Copilot access verified for ${validation.login || "current account"}` },
268
+ { kind: "ok", message: `Copilot models endpoint returned ${Array.isArray(data) ? data.length : 0} models` },
269
+ ];
270
+ } catch (error) {
271
+ const errorCode = error?.cause?.code || error?.code || "";
272
+ const reason = error?.name === "AbortError"
350
273
  ? `timed out after ${timeoutMs}ms`
351
274
  : `request failed${errorCode ? ` (${errorCode})` : ""}`;
352
- checks.push({ kind: "warn", message: onlineMessage(requestedProfile, `Online Copilot check failed: ${reason}`, inherited) });
353
- return checks;
275
+ return [{ kind: "warn", message: `Online Copilot check failed: ${reason}` }];
354
276
  } finally {
355
277
  clearTimeout(timer);
356
278
  }
357
279
  }
358
280
 
359
- export async function inspectAuthProfilesOnline({
360
- home = os.homedir(),
361
- profile = AUTH_PROFILE_CODEX,
362
- fetchImpl = fetch,
363
- timeoutMs = 10000,
364
- } = {}) {
365
- const selected = checkedDoctorProfile(profile);
366
- if (selected !== "all") {
367
- return inspectGitHubTokenOnline({ home, profile: selected, fetchImpl, timeoutMs });
368
- }
369
-
370
- let claudeConfigured = false;
371
- let claudeReadFailed = false;
372
- try {
373
- claudeConfigured = readAuthProfileCredentials(AUTH_PROFILE_CLAUDE, { home }).configured;
374
- } catch {
375
- claudeReadFailed = true;
376
- }
377
- const [codexChecks, claudeChecks] = await Promise.all([
378
- inspectGitHubTokenOnline({ home, profile: AUTH_PROFILE_CODEX, fetchImpl, timeoutMs }),
379
- claudeReadFailed
380
- ? Promise.resolve([{ kind: "err", message: "Claude isolated authentication profile could not be inspected" }])
381
- : claudeConfigured
382
- ? inspectGitHubTokenOnline({ home, profile: AUTH_PROFILE_CLAUDE, fetchImpl, timeoutMs })
383
- : Promise.resolve([{ kind: "ok", message: "Claude profile is not separately configured and inherits Codex authentication" }]),
384
- ]);
385
- return [...codexChecks, ...claudeChecks];
281
+ export async function inspectAuthProfilesOnline(options = {}) {
282
+ return inspectGitHubTokenOnline(options);
386
283
  }
387
284
 
388
285
  export function inspectCodexConfig({ home = os.homedir(), host = "127.0.0.1", port = 2026 } = {}) {
389
286
  const filePath = path.join(home, ".codex", "config.toml");
390
- const expectedAnthropicBaseUrl = adapterBaseUrl(host, port);
391
- const expectedBaseUrl = `${expectedAnthropicBaseUrl}/v1`;
287
+ const expectedBaseUrl = `${adapterBaseUrl(host, port)}/v1`;
392
288
  const config = readText(filePath);
393
289
  if (!config.ok) {
394
290
  return [{ kind: "warn", message: `Codex config not found at ${displayPath(home, filePath)}` }];
@@ -403,84 +299,12 @@ export function inspectCodexConfig({ home = os.homedir(), host = "127.0.0.1", po
403
299
  const missing = [];
404
300
  if (tomlString(config.text, "OPENAI_BASE_URL") !== expectedBaseUrl) missing.push("OPENAI_BASE_URL");
405
301
  if (tomlString(config.text, "OPENAI_API_KEY") !== "dummy") missing.push("OPENAI_API_KEY");
406
- if (tomlString(config.text, "ANTHROPIC_BASE_URL") !== expectedAnthropicBaseUrl) missing.push("ANTHROPIC_BASE_URL");
407
- if (tomlString(config.text, "ANTHROPIC_AUTH_TOKEN") !== "dummy") missing.push("ANTHROPIC_AUTH_TOKEN");
408
302
  checks.push(missing.length === 0
409
303
  ? { kind: "ok", message: "Codex shell env local API keys are configured" }
410
304
  : { kind: "warn", message: `Codex shell env local API keys need update: ${missing.join(", ")}` });
411
305
  return checks;
412
306
  }
413
307
 
414
- export function inspectClaudeCodeConfig({ home = os.homedir(), host = "127.0.0.1", port = 2026 } = {}) {
415
- const filePath = path.join(home, ".claude", "settings.json");
416
- const expectedBaseUrl = adapterBaseUrl(host, port);
417
- const settings = readJson(filePath);
418
- if (!settings.ok) {
419
- const reason = settings.parseError ? `could not parse: ${settings.error.message}` : `not found at ${displayPath(home, filePath)}`;
420
- return [{ kind: settings.parseError ? "err" : "warn", message: `Claude Code settings ${reason}` }];
421
- }
422
-
423
- const env = settings.json?.env || {};
424
- const missing = [];
425
- if (env.ANTHROPIC_BASE_URL !== expectedBaseUrl) missing.push("ANTHROPIC_BASE_URL");
426
- if (env.ANTHROPIC_AUTH_TOKEN !== "dummy") missing.push("ANTHROPIC_AUTH_TOKEN");
427
- return [missing.length === 0
428
- ? { kind: "ok", message: `Claude Code points to ${expectedBaseUrl}` }
429
- : { kind: "warn", message: `Claude Code settings need update: ${missing.join(", ")}` }];
430
- }
431
-
432
- export function inspectClaudeAppConfig({
433
- home = os.homedir(),
434
- platform = process.platform,
435
- env = process.env,
436
- host = "127.0.0.1",
437
- port = 2026,
438
- } = {}) {
439
- const paths = claudeDesktopPaths(home, platform, env);
440
- const expectedBaseUrl = localGatewayBaseUrl(host, port);
441
- const checks = [];
442
-
443
- const normalConfig = readJson(paths.normalConfigPath);
444
- const threepConfig = readJson(paths.threepConfigPath);
445
- if (normalConfig.ok && threepConfig.ok
446
- && normalConfig.json?.deploymentMode === "3p"
447
- && threepConfig.json?.deploymentMode === "3p") {
448
- checks.push({ kind: "ok", message: "Claude App deployment mode is 3p" });
449
- } else {
450
- checks.push({ kind: "warn", message: "Claude App deployment mode is not fully configured for 3p" });
451
- }
452
-
453
- const meta = readJson(paths.metaPath);
454
- const appliedId = meta.ok ? String(meta.json?.appliedId || "").trim() : "";
455
- if (!appliedId) {
456
- checks.push({
457
- kind: "warn",
458
- message: "Claude App gateway profile is not configured",
459
- fix: "ccdx start --configure-claude-app",
460
- });
461
- return checks;
462
- }
463
-
464
- const profilePath = path.join(paths.configLibraryPath, `${appliedId}.json`);
465
- const profile = readJson(profilePath);
466
- if (!profile.ok) {
467
- checks.push({ kind: "warn", message: `Claude App active gateway profile not found at ${displayPath(home, profilePath)}` });
468
- return checks;
469
- }
470
-
471
- const p = profile.json || {};
472
- const missing = [];
473
- if (p.inferenceProvider !== "gateway") missing.push("inferenceProvider");
474
- if (p.inferenceGatewayBaseUrl !== expectedBaseUrl) missing.push(`inferenceGatewayBaseUrl expected "${expectedBaseUrl}"`);
475
- if (p.inferenceGatewayAuthScheme !== "bearer") missing.push("inferenceGatewayAuthScheme");
476
- if (!String(p.inferenceGatewayApiKey || "").trim()) missing.push("inferenceGatewayApiKey");
477
- if (!String(p.inferenceModels || "").trim()) missing.push("inferenceModels");
478
- checks.push(missing.length === 0
479
- ? { kind: "ok", message: `Claude App gateway profile points to ${expectedBaseUrl}` }
480
- : { kind: "warn", message: `Claude App gateway profile needs update: ${missing.join(", ")}` });
481
- return checks;
482
- }
483
-
484
308
  function connectHost(host) {
485
309
  const normalized = String(host || "127.0.0.1").replace(/^\[(.*)\]$/, "$1");
486
310
  if (normalized === "0.0.0.0") return "127.0.0.1";
@@ -505,12 +329,9 @@ export function checkAdapterListening({ host = "127.0.0.1", port = 2026, timeout
505
329
 
506
330
  export async function collectDoctorChecks({
507
331
  home = os.homedir(),
508
- platform = process.platform,
509
- env = process.env,
510
332
  host = "127.0.0.1",
511
333
  port = 2026,
512
334
  checkAdapter = true,
513
- checkPmStudio = checkAdapter,
514
335
  checkAdapterListeningFn = checkAdapterListening,
515
336
  checkRunningAdapterFn = checkRunningAdapter,
516
337
  online = false,
@@ -520,13 +341,11 @@ export async function collectDoctorChecks({
520
341
  onlineTimeoutMs = 10000,
521
342
  compatTimeoutMs = 120000,
522
343
  inspectAdapterCompatibilityFn = inspectAdapterCompatibility,
523
- inspectPmStudioHealthFn,
524
344
  } = {}) {
345
+ checkedDoctorProfile(profile);
525
346
  const checks = [
526
347
  ...inspectAuthProfiles({ home }),
527
348
  ...inspectCodexConfig({ home, host, port }),
528
- ...inspectClaudeCodeConfig({ home, host, port }),
529
- ...inspectClaudeAppConfig({ home, platform, env, host, port }),
530
349
  ];
531
350
 
532
351
  if (online) {
@@ -564,80 +383,15 @@ export async function collectDoctorChecks({
564
383
  }
565
384
  }
566
385
 
567
- if (checkPmStudio && platform === "darwin") {
568
- const inspect = inspectPmStudioHealthFn || (async (options) => {
569
- const module = await import("./pm-studio-status.mjs");
570
- return module.inspectPmStudioStatus(options);
571
- });
572
- const pm = await inspect({ home });
573
- const version = pm.app.metadata ? `${pm.app.metadata.version} build ${pm.app.metadata.build}` : "";
574
- if (pm.app.state === "patched") {
575
- checks.push({ kind: "ok", message: `PM Studio ${version} patch is verified` });
576
- if (!pm.claude.valid) {
577
- checks.push({
578
- kind: "warn",
579
- message: "PM Studio Claude routing requires a valid isolated Claude profile",
580
- fix: "ccdx auth login claude --reauth",
581
- });
582
- }
583
- if (!pm.adapter?.ok) {
584
- checks.push({ kind: "warn", message: "PM Studio relay is not currently available", fix: "ccdx start" });
585
- } else if (!pm.runtime?.ok) {
586
- checks.push({
587
- kind: "warn",
588
- message: "PM Studio relay is running, but its isolated routing is not ready",
589
- fix: "stop the running adapter, then run ccdx start",
590
- });
591
- }
592
- } else if (pm.app.state === "predecessor") {
593
- checks.push({
594
- kind: "warn",
595
- message: `PM Studio ${version} has a verified predecessor split patch`,
596
- fix: "ccdx pms setup",
597
- });
598
- } else if (pm.app.state === "clean") {
599
- checks.push({
600
- kind: "warn",
601
- message: `PM Studio ${version} has a compatible local patch structure but is not patched`,
602
- fix: "ccdx pms setup",
603
- });
604
- } else if (pm.app.state === "unsupported") {
605
- checks.push({ kind: "warn", message: `PM Studio ${version} does not expose one uniquely compatible patch structure; no files will be changed` });
606
- } else if (pm.app.state === "drift" || pm.app.state === "error") {
607
- checks.push({ kind: "err", message: `PM Studio integrity check failed: ${pm.app.issues.join("; ")}` });
608
- }
609
- }
610
-
611
386
  if (compat) {
612
387
  if (!running?.ok) {
613
388
  checks.push({ kind: "err", message: "Compatibility checks require a running, version-compatible codex-copilot-dx adapter" });
614
389
  } else {
615
- let claudeMode = "inherited";
616
- let claudeModels;
617
- try {
618
- const claudeCredentials = readAuthProfileCredentials(AUTH_PROFILE_CLAUDE, { home });
619
- if (claudeCredentials.configured) {
620
- claudeMode = "isolated";
621
- if (claudeCredentials.valid) {
622
- claudeModels = loadModelCache({
623
- home,
624
- profile: AUTH_PROFILE_CLAUDE,
625
- credentialFingerprint: claudeCredentials.metadata?.token_fingerprint,
626
- });
627
- }
628
- }
629
- } catch {
630
- // A configured-but-unreadable optional profile must not borrow the
631
- // Codex catalog. Compatibility will report the Claude path unverified.
632
- claudeMode = "isolated";
633
- }
634
390
  checks.push(...await inspectAdapterCompatibilityFn({
635
391
  host,
636
392
  port,
637
393
  fetchImpl,
638
394
  timeoutMs: compatTimeoutMs,
639
- claudeMode,
640
- claudeModels,
641
395
  }));
642
396
  }
643
397
  }
@@ -647,11 +401,10 @@ export async function collectDoctorChecks({
647
401
 
648
402
  export async function runDoctor(options = {}) {
649
403
  const log = options.log || console.log;
650
- const profile = checkedDoctorProfile(options.profile);
404
+ checkedDoctorProfile(options.profile);
651
405
  const flags = [
652
406
  options.online ? "--online" : "",
653
407
  options.compat ? "--compat" : "",
654
- profile !== AUTH_PROFILE_CODEX ? `--profile ${profile}` : "",
655
408
  ].filter(Boolean);
656
409
  log(`${options.commandName || "ccdx"} doctor${flags.length ? ` ${flags.join(" ")}` : ""}`);
657
410
  const checks = await collectDoctorChecks(options);
@@ -4,14 +4,9 @@ import path from "node:path";
4
4
 
5
5
  export const MODEL_CACHE_SOFT_TTL_MS = 2 * 60 * 60 * 1000;
6
6
  export const MODEL_CACHE_HARD_TTL_MS = 7 * 24 * 60 * 60 * 1000;
7
- const MODEL_CACHE_PROFILES = new Set(["codex", "claude"]);
8
7
 
9
- export function modelCachePath(home = os.homedir(), profile = "codex") {
10
- if (!MODEL_CACHE_PROFILES.has(profile)) throw new Error(`Unknown model cache profile: ${profile}`);
11
- const root = path.join(home, ".local", "share", "codex-copilot-dx");
12
- return profile === "codex"
13
- ? path.join(root, "models.json")
14
- : path.join(root, "profiles", profile, "models.json");
8
+ export function modelCachePath(home = os.homedir()) {
9
+ return path.join(home, ".local", "share", "codex-copilot-dx", "models.json");
15
10
  }
16
11
 
17
12
  function modelData(models) {
@@ -30,7 +25,6 @@ function normalizedTtl(value, fallback) {
30
25
 
31
26
  export function loadModelCacheEntry({
32
27
  home = os.homedir(),
33
- profile = "codex",
34
28
  credentialFingerprint = "",
35
29
  softTtlMs = MODEL_CACHE_SOFT_TTL_MS,
36
30
  hardTtlMs,
@@ -38,7 +32,7 @@ export function loadModelCacheEntry({
38
32
  now = Date.now,
39
33
  } = {}) {
40
34
  try {
41
- const parsed = JSON.parse(fs.readFileSync(modelCachePath(home, profile), "utf8"));
35
+ const parsed = JSON.parse(fs.readFileSync(modelCachePath(home), "utf8"));
42
36
  const savedAt = Date.parse(parsed.saved_at);
43
37
  if (!Number.isFinite(savedAt)) return null;
44
38
  const expectedFingerprint = String(credentialFingerprint || "").trim();
@@ -71,14 +65,13 @@ export function loadModelCache(options = {}) {
71
65
 
72
66
  export function saveModelCache(models, {
73
67
  home = os.homedir(),
74
- profile = "codex",
75
68
  credentialFingerprint = "",
76
69
  isCurrent = () => true,
77
70
  now = Date.now,
78
71
  } = {}) {
79
72
  if (!isValidModelList(models)) return false;
80
73
  if (typeof isCurrent === "function" && !isCurrent()) return false;
81
- const filePath = modelCachePath(home, profile);
74
+ const filePath = modelCachePath(home);
82
75
  const tempPath = `${filePath}.${process.pid}.tmp`;
83
76
  const payload = { saved_at: new Date(now()).toISOString(), models };
84
77
  const fingerprint = String(credentialFingerprint || "").trim();