@minniexcode/codex-switch 0.0.6 → 0.0.8

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 (53) hide show
  1. package/README.AI.md +5 -2
  2. package/README.md +12 -6
  3. package/dist/app/add-provider.js +90 -5
  4. package/dist/app/edit-provider.js +39 -11
  5. package/dist/app/get-status.js +31 -1
  6. package/dist/app/init-codex.js +68 -0
  7. package/dist/app/list-providers.js +1 -0
  8. package/dist/app/run-doctor.js +96 -1
  9. package/dist/app/setup-codex.js +18 -9
  10. package/dist/app/show-config.js +9 -1
  11. package/dist/app/switch-provider.js +61 -8
  12. package/dist/cli/add-interactive.js +4 -2
  13. package/dist/cli/args.js +3 -0
  14. package/dist/cli/help.js +3 -0
  15. package/dist/cli/interactive.js +3 -0
  16. package/dist/cli/output.js +20 -5
  17. package/dist/cli/prompt.js +3 -0
  18. package/dist/cli.js +1 -1
  19. package/dist/commands/handlers.js +107 -13
  20. package/dist/commands/help.js +2 -1
  21. package/dist/commands/registry.js +87 -15
  22. package/dist/domain/config.js +137 -0
  23. package/dist/domain/providers.js +90 -2
  24. package/dist/domain/setup.js +1 -0
  25. package/dist/infra/backup-repo.js +3 -0
  26. package/dist/infra/codex-cli.js +3 -0
  27. package/dist/infra/codex-paths.js +3 -0
  28. package/dist/infra/fs-utils.js +3 -0
  29. package/dist/infra/lock-repo.js +3 -0
  30. package/dist/infra/providers-repo.js +3 -0
  31. package/dist/interaction/add-interactive.js +9 -18
  32. package/dist/interaction/interactive.js +84 -11
  33. package/dist/runtime/codex-probe.js +7 -0
  34. package/dist/runtime/copilot-adapter.js +173 -0
  35. package/dist/runtime/copilot-bridge-worker.js +25 -0
  36. package/dist/runtime/copilot-bridge.js +433 -0
  37. package/dist/runtime/copilot-installer.js +125 -0
  38. package/dist/runtime/copilot-sdk-loader.js +59 -0
  39. package/dist/storage/auth-repo.js +160 -0
  40. package/dist/storage/config-repo.js +58 -0
  41. package/dist/storage/fs-utils.js +3 -0
  42. package/dist/storage/runtime-state-repo.js +80 -0
  43. package/docs/Design/codex-switch-v0.0.7-design.md +862 -0
  44. package/docs/Design/codex-switch-v0.0.8-design.md +132 -0
  45. package/docs/Design/codex-switch-v0.0.9-to-v0.0.12-roadmap.md +413 -0
  46. package/docs/PRD/codex-switch-prd-v0.0.5-to-v0.1.0.md +131 -25
  47. package/docs/PRD/codex-switch-prd-v0.0.8.md +62 -0
  48. package/docs/Reference/codex-config-reference.md +604 -0
  49. package/docs/Reference/codex-config-reference.zh-CN.md +633 -0
  50. package/docs/cli-usage.md +77 -29
  51. package/docs/test-report-0.0.7.md +118 -0
  52. package/docs/testing.md +67 -47
  53. package/package.json +1 -1
package/README.AI.md CHANGED
@@ -17,7 +17,8 @@ Primary goals:
17
17
  ## Main Command Surface
18
18
 
19
19
  ```bash
20
- codexs setup
20
+ codexs init
21
+ codexs migrate
21
22
  codexs list
22
23
  codexs show <provider>
23
24
  codexs current
@@ -92,11 +93,12 @@ codexs status --json
92
93
  Current package version in this repository:
93
94
 
94
95
  ```text
95
- 0.0.4
96
+ 0.0.7
96
97
  ```
97
98
 
98
99
  Recent version summary:
99
100
 
101
+ - `0.0.7`: command-surface refactor, env_key/auth-mirror model corrections, and the `setup` split into `init` plus `migrate`
100
102
  - `0.0.4`: setup/show/edit/backups list/specific rollback/import merge and clearer CLI semantics
101
103
  - `0.0.3`: interactive TTY flows and improved help
102
104
  - `0.0.2`: mutation orchestration, backups, rollback, locks, drift detection improvements
@@ -107,4 +109,5 @@ Recent version summary:
107
109
  - Prefer `--json` when invoking commands programmatically
108
110
  - Treat `providers.json` as sensitive because it may contain API keys
109
111
  - Do not assume silent write-back from runtime files into `providers.json`
112
+ - Prefer `init` for repeatable machine setup and `migrate` for human-led adopt flows
110
113
  - Use `docs/` for deeper product and architecture context
package/README.md CHANGED
@@ -10,14 +10,15 @@ It is designed for users who work with multiple Codex providers, API keys, or pr
10
10
 
11
11
  What it does:
12
12
 
13
- - Initialize `providers.json` from an existing Codex directory
13
+ - Initialize an empty managed `providers.json`
14
+ - Migrate unmanaged runtime profiles from an existing Codex directory
14
15
  - List, show, add, edit, and remove provider records
15
16
  - Switch the active provider/profile safely
16
17
  - Import and export provider definitions
17
18
  - Run diagnostics and detect local drift
18
19
  - List backups and roll back to a previous managed state
19
20
 
20
- Current version: `0.0.6`
21
+ Current version: `0.0.8`
21
22
 
22
23
  ## Install
23
24
 
@@ -44,7 +45,8 @@ codexs --help
44
45
  Take over an existing Codex directory:
45
46
 
46
47
  ```bash
47
- codexs setup
48
+ codexs init
49
+ codexs migrate
48
50
  ```
49
51
 
50
52
  Inspect managed providers:
@@ -72,7 +74,8 @@ codexs doctor
72
74
  ## Common Commands
73
75
 
74
76
  ```bash
75
- codexs setup
77
+ codexs init
78
+ codexs migrate
76
79
  codexs list
77
80
  codexs show <provider>
78
81
  codexs current
@@ -92,6 +95,8 @@ Command help:
92
95
 
93
96
  ```bash
94
97
  codexs help switch
98
+ codexs help init
99
+ codexs help migrate
95
100
  codexs help setup
96
101
  ```
97
102
 
@@ -120,8 +125,9 @@ Notes:
120
125
 
121
126
  This CLI supports both human TTY use and non-interactive automation.
122
127
 
123
- Current exception:
124
- - `setup` in `0.0.6` is intentionally TTY-only for adopt initialization. It requires interactive profile selection and provider detail collection, and non-interactive/`--json` runs fail fast with a structured error.
128
+ Current exceptions:
129
+ - `init` is automation-friendly and idempotent, but still returns a structured error in non-interactive or `--json` mode when the resolved target directory does not exist.
130
+ - `migrate` remains intentionally TTY-only for adopt initialization. It requires interactive profile selection and provider detail collection, and non-interactive/`--json` runs fail fast with a structured error.
125
131
 
126
132
  Recommended global flags:
127
133
 
@@ -1,12 +1,48 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  exports.addProvider = addProvider;
37
+ const crypto = __importStar(require("node:crypto"));
4
38
  const config_1 = require("../domain/config");
5
39
  const providers_1 = require("../domain/providers");
6
40
  const errors_1 = require("../domain/errors");
7
41
  const config_repo_1 = require("../storage/config-repo");
8
42
  const fs_utils_1 = require("../storage/fs-utils");
9
43
  const providers_repo_1 = require("../storage/providers-repo");
44
+ const auth_repo_1 = require("../storage/auth-repo");
45
+ const copilot_installer_1 = require("../runtime/copilot-installer");
10
46
  const run_mutation_1 = require("./run-mutation");
11
47
  /**
12
48
  * Adds a new provider record to the managed providers registry.
@@ -17,8 +53,37 @@ function addProvider(args) {
17
53
  if (providers.providers[args.providerName]) {
18
54
  throw (0, errors_1.cliError)("INVALID_IMPORT_FILE", `Provider "${args.providerName}" already exists.`);
19
55
  }
56
+ const bridgeHost = args.bridgeHost ?? "127.0.0.1";
57
+ const bridgePort = args.bridgePort ?? 4141;
58
+ const runtime = args.copilot
59
+ ? {
60
+ kind: "copilot-sdk-bridge",
61
+ upstream: "github-copilot",
62
+ bridgeHost,
63
+ bridgePort,
64
+ bridgePath: "/v1",
65
+ premiumRequests: true,
66
+ authSource: "official-sdk",
67
+ sdkInstallMode: "lazy",
68
+ }
69
+ : undefined;
70
+ if (args.copilot) {
71
+ const installStatus = (0, copilot_installer_1.probeCopilotSdkInstall)();
72
+ if (!installStatus.installed) {
73
+ if (!args.installCopilotSdk) {
74
+ throw (0, errors_1.cliError)(args.interactive ? "COPILOT_SDK_MISSING" : "COPILOT_SDK_INSTALL_REQUIRES_TTY", args.interactive
75
+ ? "The optional Copilot SDK runtime is not installed. Re-run with --install-copilot-sdk or confirm installation interactively."
76
+ : "The optional Copilot SDK runtime is not installed. Pass --install-copilot-sdk when running non-interactively.", {
77
+ installDir: installStatus.installDir,
78
+ packageName: installStatus.packageName,
79
+ });
80
+ }
81
+ (0, copilot_installer_1.installCopilotSdk)();
82
+ }
83
+ }
20
84
  const document = (0, config_repo_1.readStructuredConfig)(args.configPath);
21
85
  const existingProfile = document.profiles.find((profile) => profile.name === args.profile);
86
+ const existingModelProvider = document.modelProviders.find((entry) => entry.name === args.profile);
22
87
  if (!existingProfile && !args.createProfile) {
23
88
  throw (0, errors_1.cliError)("PROFILE_NOT_FOUND", `Profile "${args.profile}" does not exist in config.toml.`, {
24
89
  profile: args.profile,
@@ -33,21 +98,31 @@ function addProvider(args) {
33
98
  }),
34
99
  }
35
100
  : undefined;
36
- if (!existingProfile && args.createProfile) {
37
- (0, config_repo_1.requireModelProviderRuntimeSection)(document, args.profile);
38
- }
101
+ const upsertModelProviders = !existingModelProvider && args.createProfile
102
+ ? {
103
+ [args.profile]: {
104
+ baseUrl: args.copilot ? (0, providers_1.buildCopilotBridgeBaseUrl)(runtime) : args.baseUrl ?? undefined,
105
+ envKey: (0, config_1.buildManagedProfileEnvKey)(args.profile),
106
+ },
107
+ }
108
+ : undefined;
39
109
  if (existingProfile) {
40
110
  (0, config_repo_1.requireManagedProfileRuntime)(document, providers, args.profile);
41
111
  }
112
+ const envKey = existingModelProvider?.envKey ?? (0, config_1.buildManagedProfileEnvKey)(args.profile);
113
+ const apiKey = args.copilot ? args.bridgeApiKey ?? crypto.randomBytes(24).toString("hex") : args.apiKey;
114
+ const baseUrl = args.copilot ? (0, providers_1.buildCopilotBridgeBaseUrl)(runtime) : args.baseUrl ?? undefined;
42
115
  const next = {
43
116
  providers: {
44
117
  ...providers.providers,
45
118
  [args.providerName]: (0, providers_1.cleanProviderRecord)({
46
119
  profile: args.profile,
47
- apiKey: args.apiKey,
48
- baseUrl: args.baseUrl ?? undefined,
120
+ apiKey,
121
+ envKey,
122
+ baseUrl,
49
123
  note: args.note ?? undefined,
50
124
  tags: args.tags,
125
+ runtime,
51
126
  }),
52
127
  },
53
128
  };
@@ -59,18 +134,28 @@ function addProvider(args) {
59
134
  files: [
60
135
  { absolutePath: args.providersPath, relativePath: "providers.json" },
61
136
  { absolutePath: args.configPath, relativePath: "config.toml" },
137
+ { absolutePath: args.authPath, relativePath: "auth.json" },
62
138
  ],
63
139
  mutate: () => {
64
140
  const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {
65
141
  upsertProfiles,
142
+ upsertModelProviders,
66
143
  });
67
144
  // Persist only the normalized provider payload so later reads are deterministic.
68
145
  (0, providers_repo_1.writeProvidersFile)(args.providersPath, next);
69
146
  (0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
147
+ if (document.activeProfile === args.profile) {
148
+ const activeProviderName = (0, config_repo_1.resolveActiveProviderName)(document, next);
149
+ const existingAuth = (0, auth_repo_1.readAuthFileIfExists)(args.authPath);
150
+ (0, auth_repo_1.writeAuthFile)(args.authPath, next.providers[activeProviderName], existingAuth ?? undefined);
151
+ }
70
152
  return {
71
153
  provider: args.providerName,
72
154
  profile: args.profile,
155
+ envKey,
156
+ runtimeKind: runtime?.kind ?? null,
73
157
  createdProfileSections: configPlan.createdProfileSections,
158
+ createdModelProviderSections: configPlan.createdModelProviderSections,
74
159
  deletedProfileSections: configPlan.deletedProfileSections,
75
160
  keptSharedProfiles: [],
76
161
  switchedActiveProfile: configPlan.switchedActiveProfile,
@@ -7,6 +7,7 @@ const providers_1 = require("../domain/providers");
7
7
  const config_repo_1 = require("../storage/config-repo");
8
8
  const fs_utils_1 = require("../storage/fs-utils");
9
9
  const providers_repo_1 = require("../storage/providers-repo");
10
+ const auth_repo_1 = require("../storage/auth-repo");
10
11
  const run_mutation_1 = require("./run-mutation");
11
12
  /**
12
13
  * Updates selected fields on a single managed provider.
@@ -23,13 +24,7 @@ function editProvider(args) {
23
24
  });
24
25
  }
25
26
  const updatedFields = [];
26
- const nextRecord = (0, providers_1.cleanProviderRecord)({
27
- profile: args.profile ?? current.profile,
28
- apiKey: args.apiKey ?? current.apiKey,
29
- baseUrl: args.baseUrl === null ? undefined : args.baseUrl ?? current.baseUrl,
30
- note: args.note === null ? undefined : args.note ?? current.note,
31
- tags: args.tags ?? current.tags,
32
- });
27
+ const nextProfile = args.profile ?? current.profile;
33
28
  if (args.profile !== undefined && args.profile !== current.profile) {
34
29
  updatedFields.push("profile");
35
30
  }
@@ -46,10 +41,12 @@ function editProvider(args) {
46
41
  updatedFields.push("tags");
47
42
  }
48
43
  const oldProfile = current.profile;
49
- const newProfile = nextRecord.profile;
44
+ const newProfile = nextProfile;
50
45
  const targetSection = document.profiles.find((profile) => profile.name === newProfile) ?? null;
46
+ const targetModelProviderSection = document.modelProviders.find((entry) => entry.name === newProfile) ?? null;
51
47
  const targetProfileExists = Boolean(targetSection);
52
48
  let upsertProfiles;
49
+ let upsertModelProviders;
53
50
  if (!targetProfileExists) {
54
51
  if (!args.createProfile) {
55
52
  throw (0, errors_1.cliError)("PROFILE_NOT_FOUND", `Profile "${newProfile}" does not exist in config.toml.`, {
@@ -63,11 +60,30 @@ function editProvider(args) {
63
60
  modelProvider: newProfile,
64
61
  }),
65
62
  };
66
- (0, config_repo_1.requireModelProviderRuntimeSection)(document, newProfile);
63
+ upsertModelProviders = {
64
+ [newProfile]: {
65
+ baseUrl: args.baseUrl ?? undefined,
66
+ envKey: (0, config_1.buildManagedProfileEnvKey)(newProfile),
67
+ },
68
+ };
67
69
  }
68
70
  else {
69
71
  (0, config_repo_1.requireManagedProfileRuntime)(document, providers, newProfile);
70
72
  }
73
+ const nextEnvKey = args.profile !== undefined && args.profile !== current.profile
74
+ ? targetModelProviderSection?.envKey ?? (0, config_1.buildManagedProfileEnvKey)(newProfile)
75
+ : current.envKey;
76
+ if (nextEnvKey !== current.envKey) {
77
+ updatedFields.push("envKey");
78
+ }
79
+ const nextRecord = (0, providers_1.cleanProviderRecord)({
80
+ profile: newProfile,
81
+ apiKey: args.apiKey ?? current.apiKey,
82
+ envKey: nextEnvKey,
83
+ baseUrl: args.baseUrl === null ? undefined : args.baseUrl ?? current.baseUrl,
84
+ note: args.note === null ? undefined : args.note ?? current.note,
85
+ tags: args.tags ?? current.tags,
86
+ });
71
87
  if (targetProfileExists && args.model !== undefined) {
72
88
  upsertProfiles = {
73
89
  [newProfile]: {
@@ -78,6 +94,7 @@ function editProvider(args) {
78
94
  updatedFields.push("model");
79
95
  }
80
96
  }
97
+ // Compute profile link ownership after the edit so lifecycle planning can decide whether sections stay, move, or delete.
81
98
  const remainingLinksByProfile = new Map();
82
99
  for (const [name, provider] of Object.entries(providers.providers)) {
83
100
  if (name === args.providerName) {
@@ -108,24 +125,35 @@ function editProvider(args) {
108
125
  files: [
109
126
  { absolutePath: args.providersPath, relativePath: "providers.json" },
110
127
  { absolutePath: args.configPath, relativePath: "config.toml" },
128
+ { absolutePath: args.authPath, relativePath: "auth.json" },
111
129
  ],
112
130
  mutate: () => {
113
131
  const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {
114
132
  upsertProfiles,
133
+ upsertModelProviders,
115
134
  deleteProfiles: lifecycle.deletedProfileSections,
116
135
  setActiveProfile: lifecycle.nextActiveProfile,
117
136
  });
118
- (0, providers_repo_1.writeProvidersFile)(args.providersPath, {
137
+ const nextProviders = {
119
138
  providers: {
120
139
  ...providers.providers,
121
140
  [args.providerName]: nextRecord,
122
141
  },
123
- });
142
+ };
143
+ // Write providers first so the registry and config move together inside the managed backup boundary.
144
+ (0, providers_repo_1.writeProvidersFile)(args.providersPath, nextProviders);
124
145
  (0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
146
+ const updatedDocument = (0, config_repo_1.readStructuredConfig)(args.configPath);
147
+ if (updatedDocument.activeProfile) {
148
+ const activeProviderName = (0, config_repo_1.resolveActiveProviderName)(updatedDocument, nextProviders);
149
+ const existingAuth = (0, auth_repo_1.readAuthFileIfExists)(args.authPath);
150
+ (0, auth_repo_1.writeAuthFile)(args.authPath, nextProviders.providers[activeProviderName], existingAuth ?? undefined);
151
+ }
125
152
  return {
126
153
  provider: args.providerName,
127
154
  updatedFields,
128
155
  createdProfileSections: configPlan.createdProfileSections,
156
+ createdModelProviderSections: configPlan.createdModelProviderSections,
129
157
  deletedProfileSections: configPlan.deletedProfileSections,
130
158
  keptSharedProfiles: lifecycle.keptSharedProfiles,
131
159
  switchedActiveProfile: lifecycle.switchedActiveProfile,
@@ -37,12 +37,17 @@ exports.getStatus = getStatus;
37
37
  const fs = __importStar(require("node:fs"));
38
38
  const config_1 = require("../domain/config");
39
39
  const runtime_state_1 = require("../domain/runtime-state");
40
+ const providers_1 = require("../domain/providers");
40
41
  const config_repo_1 = require("../storage/config-repo");
41
42
  const providers_repo_1 = require("../storage/providers-repo");
43
+ const auth_repo_1 = require("../storage/auth-repo");
44
+ const copilot_installer_1 = require("../runtime/copilot-installer");
45
+ const copilot_bridge_1 = require("../runtime/copilot-bridge");
46
+ const copilot_adapter_1 = require("../runtime/copilot-adapter");
42
47
  /**
43
48
  * Reports the current on-disk runtime state and how it maps back to managed providers.
44
49
  */
45
- function getStatus(codexDir, configPath, providersPath) {
50
+ async function getStatus(codexDir, configPath, providersPath, authPath) {
46
51
  const configExists = fs.existsSync(configPath);
47
52
  const providersExists = fs.existsSync(providersPath);
48
53
  let currentProfile = null;
@@ -50,6 +55,7 @@ function getStatus(codexDir, configPath, providersPath) {
50
55
  const providers = providersExists ? (0, providers_repo_1.readProvidersFile)(providersPath) : null;
51
56
  let configViews = [];
52
57
  let consistencyIssues = [];
58
+ const authState = (0, auth_repo_1.readManagedAuthState)(authPath);
53
59
  if (configExists) {
54
60
  const document = (0, config_repo_1.readStructuredConfig)(configPath);
55
61
  currentProfile = document.activeProfile;
@@ -60,6 +66,18 @@ function getStatus(codexDir, configPath, providersPath) {
60
66
  }
61
67
  }
62
68
  const liveState = (0, runtime_state_1.inspectLiveStateDrift)(currentProfile, providers);
69
+ const activeProviderCandidates = currentProfile && providers ? (0, providers_1.findProvidersByProfile)(providers, currentProfile) : [];
70
+ const activeProvider = activeProviderCandidates.length === 1 && providers ? providers.providers[activeProviderCandidates[0]] : null;
71
+ const copilotInstall = (0, copilot_installer_1.probeCopilotSdkInstall)();
72
+ const copilotBridge = activeProvider && (0, providers_1.isCopilotBridgeProvider)(activeProvider) ? await (0, copilot_bridge_1.probeCopilotBridgeRuntime)(activeProvider) : null;
73
+ const copilotAuth = activeProvider && (0, providers_1.isCopilotBridgeProvider)(activeProvider)
74
+ ? await (0, copilot_adapter_1.readCopilotAuthState)().catch((error) => ({
75
+ ready: false,
76
+ source: "official-sdk",
77
+ mode: "session",
78
+ error: error instanceof Error ? error.message : String(error),
79
+ }))
80
+ : null;
63
81
  if (liveState.canBackfillActiveProvider) {
64
82
  // Surface unmanaged live state without mutating anything during a read-only status call.
65
83
  warnings.push("Current config profile is not mapped in providers.json. Backfill would be required before treating live state as managed.");
@@ -74,7 +92,19 @@ function getStatus(codexDir, configPath, providersPath) {
74
92
  currentProfile,
75
93
  currentProfileMapped: liveState.profileMapped,
76
94
  provider: liveState.mappedProvider,
95
+ activeProviderResolvable: activeProviderCandidates.length === 1,
96
+ activeProviderCandidates,
97
+ runtimeProvider: activeProvider && (0, providers_1.isCopilotBridgeProvider)(activeProvider) ? activeProvider.runtime?.kind ?? null : null,
98
+ copilotSdk: {
99
+ installed: copilotInstall.installed,
100
+ installDir: copilotInstall.installDir,
101
+ packageName: copilotInstall.packageName,
102
+ packageVersion: copilotInstall.packageVersion ?? null,
103
+ },
104
+ copilotAuth,
105
+ copilotBridge,
77
106
  liveState,
107
+ auth: authState,
78
108
  configProfiles: configViews,
79
109
  issues: consistencyIssues,
80
110
  },
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.initCodex = initCodex;
37
+ const fs = __importStar(require("node:fs"));
38
+ const errors_1 = require("../domain/errors");
39
+ const fs_utils_1 = require("../storage/fs-utils");
40
+ const providers_repo_1 = require("../storage/providers-repo");
41
+ /**
42
+ * Initializes a Codex directory for managed providers.json usage without requiring live Codex state.
43
+ */
44
+ function initCodex(args) {
45
+ const codexDirExists = fs.existsSync(args.codexDir);
46
+ if (!codexDirExists && !args.createCodexDir) {
47
+ throw (0, errors_1.cliError)("CODEX_DIR_NOT_FOUND", "The requested Codex directory does not exist.", {
48
+ codexDir: args.codexDir,
49
+ });
50
+ }
51
+ if (!codexDirExists) {
52
+ (0, fs_utils_1.ensureDir)(args.codexDir);
53
+ }
54
+ const providersExists = fs.existsSync(args.providersPath);
55
+ if (!providersExists) {
56
+ (0, providers_repo_1.writeProvidersFile)(args.providersPath, { providers: {} });
57
+ }
58
+ return {
59
+ data: {
60
+ codexDir: args.codexDir,
61
+ createdCodexDir: !codexDirExists,
62
+ createdProvidersFile: !providersExists,
63
+ providersAlreadyExisted: providersExists,
64
+ configExists: fs.existsSync(args.configPath),
65
+ authExists: fs.existsSync(args.authPath),
66
+ },
67
+ };
68
+ }
@@ -11,6 +11,7 @@ function listProviders(providersPath) {
11
11
  const items = names.map((name) => ({
12
12
  name,
13
13
  profile: providers.providers[name].profile,
14
+ envKey: providers.providers[name].envKey,
14
15
  note: providers.providers[name].note ?? null,
15
16
  tags: providers.providers[name].tags ?? [],
16
17
  }));
@@ -41,10 +41,15 @@ const config_repo_1 = require("../storage/config-repo");
41
41
  const providers_repo_1 = require("../storage/providers-repo");
42
42
  const errors_1 = require("../domain/errors");
43
43
  const codex_probe_1 = require("../runtime/codex-probe");
44
+ const auth_repo_1 = require("../storage/auth-repo");
45
+ const providers_1 = require("../domain/providers");
46
+ const copilot_installer_1 = require("../runtime/copilot-installer");
47
+ const copilot_bridge_1 = require("../runtime/copilot-bridge");
48
+ const copilot_adapter_1 = require("../runtime/copilot-adapter");
44
49
  /**
45
50
  * Performs consistency checks across config.toml, providers.json, and the local Codex CLI.
46
51
  */
47
- function runDoctor(args) {
52
+ async function runDoctor(args) {
48
53
  const issues = [];
49
54
  let currentProfile = null;
50
55
  let providers = null;
@@ -78,6 +83,7 @@ function runDoctor(args) {
78
83
  try {
79
84
  providers = (0, providers_repo_1.readProvidersFile)(args.providersPath);
80
85
  if (document) {
86
+ // Preserve domain issue codes while translating them into user-facing diagnostic messages.
81
87
  for (const issue of (0, config_1.collectConfigConsistencyIssues)(document, providers)) {
82
88
  issues.push({
83
89
  ...issue,
@@ -95,6 +101,77 @@ function runDoctor(args) {
95
101
  });
96
102
  }
97
103
  }
104
+ const authState = (0, auth_repo_1.readManagedAuthState)(args.authPath);
105
+ if (authState.exists && !authState.valid) {
106
+ issues.push({
107
+ code: "AUTH_JSON_INVALID",
108
+ message: authState.parseError ?? "auth.json is invalid.",
109
+ file: args.authPath,
110
+ });
111
+ }
112
+ if (document?.activeProfile && providers) {
113
+ const matches = (0, providers_1.findProvidersByProfile)(providers, document.activeProfile);
114
+ if (matches.length === 1) {
115
+ const activeProvider = providers.providers[matches[0]];
116
+ const payload = authState.payload ?? {};
117
+ const actualKeys = authState.managedSecretKeys;
118
+ if (authState.authMode !== null && authState.authMode !== "apikey") {
119
+ issues.push({
120
+ code: "AUTH_JSON_INVALID",
121
+ message: `auth.json auth_mode must be "apikey", found "${authState.authMode}".`,
122
+ });
123
+ }
124
+ if (!actualKeys.includes(activeProvider.envKey) || actualKeys.length !== 1) {
125
+ issues.push({
126
+ code: "AUTH_JSON_ENV_KEY_MISMATCH",
127
+ message: `auth.json managed env key does not match active provider "${matches[0]}".`,
128
+ provider: matches[0],
129
+ expectedEnvKey: activeProvider.envKey,
130
+ actualEnvKeys: actualKeys,
131
+ });
132
+ }
133
+ if (payload[activeProvider.envKey] !== activeProvider.apiKey) {
134
+ issues.push({
135
+ code: "AUTH_JSON_APIKEY_MISMATCH",
136
+ message: `auth.json secret value does not match active provider "${matches[0]}".`,
137
+ provider: matches[0],
138
+ });
139
+ }
140
+ if ((0, providers_1.isCopilotBridgeProvider)(activeProvider)) {
141
+ const installStatus = (0, copilot_installer_1.probeCopilotSdkInstall)();
142
+ if (!installStatus.installed) {
143
+ issues.push({
144
+ code: "COPILOT_SDK_MISSING",
145
+ message: "The optional Copilot SDK runtime is not installed.",
146
+ installDir: installStatus.installDir,
147
+ packageName: installStatus.packageName,
148
+ });
149
+ }
150
+ try {
151
+ await (0, copilot_adapter_1.readCopilotAuthState)();
152
+ }
153
+ catch (error) {
154
+ const normalized = (0, errors_1.normalizeError)(error);
155
+ issues.push({
156
+ code: normalized.code,
157
+ message: normalized.message,
158
+ ...(normalized.details ?? {}),
159
+ });
160
+ }
161
+ const bridge = await (0, copilot_bridge_1.probeCopilotBridgeRuntime)(activeProvider);
162
+ if (!bridge.ok) {
163
+ issues.push({
164
+ code: bridge.cause === "Copilot bridge state base URL does not match the provider runtime configuration."
165
+ ? "PROVIDER_BASE_URL_MISMATCH"
166
+ : "BRIDGE_HEALTHCHECK_FAILED",
167
+ message: bridge.cause,
168
+ ...(bridge.details ?? {}),
169
+ });
170
+ }
171
+ }
172
+ }
173
+ }
174
+ // Drift inspection still runs when files are missing so status output can explain partial state.
98
175
  const drift = (0, runtime_state_1.inspectLiveStateDrift)(currentProfile, providers);
99
176
  const codexCheck = (0, codex_probe_1.probeCodexRuntime)();
100
177
  if (!codexCheck.ok) {
@@ -120,10 +197,14 @@ function runDoctor(args) {
120
197
  codexDir: args.codexDir,
121
198
  storage: (0, runtime_state_1.getStorageRoles)(),
122
199
  liveState: drift,
200
+ auth: authState,
123
201
  },
124
202
  warnings: issues.length === 0 ? [] : [`doctor found ${issues.length} issue(s)`],
125
203
  };
126
204
  }
205
+ /**
206
+ * Maps structured config consistency issues onto stable human-readable diagnostic text.
207
+ */
127
208
  function renderConfigIssueMessage(issue) {
128
209
  switch (issue.code) {
129
210
  case "ORPHANED_PROFILE_REFERENCE":
@@ -142,7 +223,21 @@ function renderConfigIssueMessage(issue) {
142
223
  return `Model provider section "${issue.modelProvider}" for profile "${issue.profile}" is missing from config.toml.`;
143
224
  case "MODEL_PROVIDER_BASE_URL_MISSING":
144
225
  return `Model provider section "${issue.modelProvider}" for profile "${issue.profile}" is missing base_url.`;
226
+ case "MODEL_PROVIDER_ENV_KEY_MISSING":
227
+ return `Model provider section "${issue.modelProvider}" for profile "${issue.profile}" is missing env_key.`;
228
+ case "PROVIDER_ENV_KEY_MISMATCH":
229
+ return `Provider "${issue.provider}" envKey does not match runtime env_key for profile "${issue.profile}".`;
230
+ case "ACTIVE_PROVIDER_UNRESOLVED":
231
+ return `Active profile "${issue.profile}" maps to multiple providers and cannot determine the current auth mirror owner.`;
232
+ case "AUTH_JSON_INVALID":
233
+ return String(issue.message ?? issue.reason ?? "auth.json is invalid.");
234
+ case "AUTH_JSON_ENV_KEY_MISMATCH":
235
+ return `auth.json managed env key does not match provider "${String(issue.provider ?? "")}".`;
236
+ case "AUTH_JSON_APIKEY_MISMATCH":
237
+ return `auth.json secret does not match provider "${String(issue.provider ?? "")}".`;
145
238
  case "DESTRUCTIVE_REMOVE_BLOCKED":
146
239
  return `Provider "${issue.provider}" cannot be removed while "${issue.activeProfile}" remains active.`;
240
+ default:
241
+ return String(issue.code ?? "UNKNOWN_ISSUE");
147
242
  }
148
243
  }