@minniexcode/codex-switch 0.0.10 → 0.0.12

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 (51) hide show
  1. package/README.AI.md +68 -73
  2. package/README.CN.md +108 -111
  3. package/README.md +87 -80
  4. package/dist/app/add-provider.js +29 -15
  5. package/dist/app/bridge.js +15 -14
  6. package/dist/app/edit-provider.js +1 -1
  7. package/dist/app/get-status.js +21 -9
  8. package/dist/app/import-providers.js +1 -1
  9. package/dist/app/init-codex.js +13 -14
  10. package/dist/app/list-providers.js +48 -1
  11. package/dist/app/remove-provider.js +1 -1
  12. package/dist/app/run-doctor.js +12 -5
  13. package/dist/app/run-mutation.js +3 -2
  14. package/dist/app/setup-codex.js +3 -1
  15. package/dist/app/switch-provider.js +11 -13
  16. package/dist/cli/output.js +145 -18
  17. package/dist/cli.js +34 -2
  18. package/dist/commands/args.js +2 -2
  19. package/dist/commands/dispatch.js +40 -0
  20. package/dist/commands/handlers.js +130 -161
  21. package/dist/commands/help.js +11 -5
  22. package/dist/commands/registry.js +42 -20
  23. package/dist/domain/backups.js +4 -4
  24. package/dist/domain/config.js +110 -5
  25. package/dist/domain/providers.js +12 -0
  26. package/dist/domain/runtime-state.js +111 -13
  27. package/dist/infra/config-repo.js +16 -206
  28. package/dist/interaction/interactive.js +16 -6
  29. package/dist/runtime/copilot-adapter.js +12 -12
  30. package/dist/runtime/copilot-bridge.js +394 -45
  31. package/dist/runtime/copilot-cli.js +84 -12
  32. package/dist/runtime/copilot-installer.js +10 -9
  33. package/dist/runtime/copilot-sdk-loader.js +5 -5
  34. package/dist/storage/backup-repo.js +4 -4
  35. package/dist/storage/codex-paths.js +34 -8
  36. package/dist/storage/config-repo.js +0 -23
  37. package/dist/storage/lock-repo.js +2 -4
  38. package/dist/storage/runtime-state-repo.js +14 -13
  39. package/dist/storage/tool-config-repo.js +111 -0
  40. package/docs/Design/codex-switch-v0.0.11-design.md +824 -0
  41. package/docs/Design/codex-switch-v0.0.12-design.md +343 -0
  42. package/docs/PRD/codex-switch-prd-v0.0.11.md +577 -0
  43. package/docs/PRD/codex-switch-prd-v0.0.12.md +279 -0
  44. package/docs/PRD/codex-switch-prd-v0.1.0.md +125 -237
  45. package/docs/Tests/testing.md +39 -112
  46. package/docs/cli-usage.md +135 -565
  47. package/docs/codex-switch-command-design.md +3 -0
  48. package/docs/codex-switch-product-overview.md +52 -207
  49. package/docs/codex-switch-technical-architecture.md +3 -0
  50. package/package.json +1 -1
  51. package/dist/app/rollback-latest.js +0 -26
@@ -117,8 +117,15 @@ function parseStructuredConfig(configContent) {
117
117
  name: modelProviderHeaderMatch[1],
118
118
  sectionStart: line.start,
119
119
  sectionEnd: configContent.length,
120
+ managedFieldInsertIndex: configContent.length,
120
121
  baseUrlValueRange: null,
121
122
  baseUrl: null,
123
+ nameValueRange: null,
124
+ providerName: null,
125
+ requiresOpenAiAuthValueRange: null,
126
+ requiresOpenAiAuth: null,
127
+ wireApiValueRange: null,
128
+ wireApi: null,
122
129
  };
123
130
  modelProviders.push(currentModelProvider);
124
131
  inRoot = false;
@@ -173,6 +180,30 @@ function parseStructuredConfig(configContent) {
173
180
  end: line.start + baseUrlMatch.valueEnd,
174
181
  };
175
182
  }
183
+ const nameMatch = matchKeyValueLine(line.content, "name");
184
+ if (nameMatch) {
185
+ currentModelProvider.providerName = nameMatch.value;
186
+ currentModelProvider.nameValueRange = {
187
+ start: line.start + nameMatch.valueStart,
188
+ end: line.start + nameMatch.valueEnd,
189
+ };
190
+ }
191
+ const requiresOpenAiAuthMatch = matchBooleanKeyValueLine(line.content, "requires_openai_auth");
192
+ if (requiresOpenAiAuthMatch) {
193
+ currentModelProvider.requiresOpenAiAuth = requiresOpenAiAuthMatch.value;
194
+ currentModelProvider.requiresOpenAiAuthValueRange = {
195
+ start: line.start + requiresOpenAiAuthMatch.valueStart,
196
+ end: line.start + requiresOpenAiAuthMatch.valueEnd,
197
+ };
198
+ }
199
+ const wireApiMatch = matchKeyValueLine(line.content, "wire_api");
200
+ if (wireApiMatch) {
201
+ currentModelProvider.wireApi = wireApiMatch.value;
202
+ currentModelProvider.wireApiValueRange = {
203
+ start: line.start + wireApiMatch.valueStart,
204
+ end: line.start + wireApiMatch.valueEnd,
205
+ };
206
+ }
176
207
  }
177
208
  }
178
209
  return {
@@ -184,7 +215,10 @@ function parseStructuredConfig(configContent) {
184
215
  ...profile,
185
216
  managedFieldInsertIndex: findManagedFieldInsertIndex(configContent, profile.sectionStart, profile.sectionEnd),
186
217
  })),
187
- modelProviders,
218
+ modelProviders: modelProviders.map((provider) => ({
219
+ ...provider,
220
+ managedFieldInsertIndex: findManagedFieldInsertIndex(configContent, provider.sectionStart, provider.sectionEnd),
221
+ })),
188
222
  };
189
223
  }
190
224
  /**
@@ -445,6 +479,7 @@ function planConfigMutation(document, args) {
445
479
  const section = modelProviderSectionMap.get(profileName);
446
480
  if (!section) {
447
481
  const baseUrl = fields.baseUrl?.trim() ?? "";
482
+ const providerName = fields.name?.trim() ?? "";
448
483
  if (!baseUrl) {
449
484
  throw (0, errors_1.cliError)("MANAGED_PROFILE_FIELDS_MISSING", `Model provider "${profileName}" requires base_url.`, {
450
485
  profile: profileName,
@@ -457,11 +492,16 @@ function planConfigMutation(document, args) {
457
492
  const prefix = document.rawText.length > 0 && !document.rawText.endsWith(document.lineEnding)
458
493
  ? document.lineEnding
459
494
  : "";
495
+ const requiresOpenAiAuth = fields.requiresOpenAiAuth;
496
+ const wireApi = fields.wireApi?.trim() ?? "";
460
497
  operations.push({
461
498
  kind: "insert-at",
462
499
  index: document.rawText.length,
463
500
  text: `${prefix}[model_providers.${profileName}]${document.lineEnding}` +
464
- `base_url = ${JSON.stringify(baseUrl)}${document.lineEnding}`,
501
+ `base_url = ${JSON.stringify(baseUrl)}${document.lineEnding}` +
502
+ (providerName ? `name = ${JSON.stringify(providerName)}${document.lineEnding}` : "") +
503
+ (requiresOpenAiAuth !== undefined ? `requires_openai_auth = ${String(requiresOpenAiAuth)}${document.lineEnding}` : "") +
504
+ (wireApi ? `wire_api = ${JSON.stringify(wireApi)}${document.lineEnding}` : ""),
465
505
  });
466
506
  createdModelProviderSections.push(profileName);
467
507
  continue;
@@ -545,11 +585,14 @@ function planSectionFieldMutation(document, section, fields, operations) {
545
585
  return updated;
546
586
  }
547
587
  /**
548
- * Plans base_url updates for one model_providers section.
588
+ * Plans managed field updates for one model_providers section.
549
589
  */
550
590
  function planModelProviderFieldMutation(section, fields, operations) {
551
591
  let updated = false;
552
592
  const baseUrlText = fields.baseUrl !== undefined ? JSON.stringify(fields.baseUrl) : null;
593
+ const nameText = fields.name !== undefined ? JSON.stringify(fields.name) : null;
594
+ const requiresOpenAiAuthText = fields.requiresOpenAiAuth !== undefined ? String(fields.requiresOpenAiAuth) : null;
595
+ const wireApiText = fields.wireApi !== undefined ? JSON.stringify(fields.wireApi) : null;
553
596
  const inserts = [];
554
597
  if (baseUrlText !== null && section.baseUrlValueRange) {
555
598
  if (section.baseUrl !== fields.baseUrl) {
@@ -564,14 +607,59 @@ function planModelProviderFieldMutation(section, fields, operations) {
564
607
  }
565
608
  else if (baseUrlText !== null) {
566
609
  inserts.push(`base_url = ${baseUrlText}`);
610
+ updated = true;
611
+ }
612
+ if (nameText !== null && section.nameValueRange) {
613
+ if (section.providerName !== fields.name) {
614
+ operations.push({
615
+ kind: "replace-range",
616
+ start: section.nameValueRange.start,
617
+ end: section.nameValueRange.end,
618
+ text: nameText,
619
+ });
620
+ updated = true;
621
+ }
622
+ }
623
+ else if (nameText !== null) {
624
+ inserts.push(`name = ${nameText}`);
625
+ updated = true;
626
+ }
627
+ if (requiresOpenAiAuthText !== null && section.requiresOpenAiAuthValueRange) {
628
+ if (section.requiresOpenAiAuth !== fields.requiresOpenAiAuth) {
629
+ operations.push({
630
+ kind: "replace-range",
631
+ start: section.requiresOpenAiAuthValueRange.start,
632
+ end: section.requiresOpenAiAuthValueRange.end,
633
+ text: requiresOpenAiAuthText,
634
+ });
635
+ updated = true;
636
+ }
637
+ }
638
+ else if (requiresOpenAiAuthText !== null) {
639
+ inserts.push(`requires_openai_auth = ${requiresOpenAiAuthText}`);
640
+ updated = true;
641
+ }
642
+ if (wireApiText !== null && section.wireApiValueRange) {
643
+ if (section.wireApi !== fields.wireApi) {
644
+ operations.push({
645
+ kind: "replace-range",
646
+ start: section.wireApiValueRange.start,
647
+ end: section.wireApiValueRange.end,
648
+ text: wireApiText,
649
+ });
650
+ updated = true;
651
+ }
652
+ }
653
+ else if (wireApiText !== null) {
654
+ inserts.push(`wire_api = ${wireApiText}`);
655
+ updated = true;
567
656
  }
568
657
  if (inserts.length > 0) {
569
658
  operations.push({
570
659
  kind: "insert-at",
571
- index: section.sectionEnd,
660
+ index: section.managedFieldInsertIndex,
572
661
  text: `${inserts.join("\n")}\n`,
573
662
  });
574
- updated = true;
575
663
  }
576
664
  return updated;
577
665
  }
@@ -616,6 +704,23 @@ function matchKeyValueLine(line, key) {
616
704
  valueEnd,
617
705
  };
618
706
  }
707
+ function matchBooleanKeyValueLine(line, key) {
708
+ const match = line.match(new RegExp(`^\\s*${escapeRegExp(key)}\\s*=\\s*(true|false)\\s*(#.*)?$`));
709
+ if (!match || match.index === undefined) {
710
+ return null;
711
+ }
712
+ const value = match[1] === "true";
713
+ const valueStart = line.indexOf(match[1], match.index);
714
+ if (valueStart === -1) {
715
+ return null;
716
+ }
717
+ const valueEnd = valueStart + match[1].length;
718
+ return {
719
+ value,
720
+ valueStart,
721
+ valueEnd,
722
+ };
723
+ }
619
724
  function findManagedFieldInsertIndex(rawText, sectionStart, sectionEnd) {
620
725
  const sectionText = rawText.slice(sectionStart, sectionEnd);
621
726
  const lines = splitWithOffsets(sectionText);
@@ -9,6 +9,7 @@ exports.maskSecret = maskSecret;
9
9
  exports.isRuntimeBackedProvider = isRuntimeBackedProvider;
10
10
  exports.isCopilotBridgeProvider = isCopilotBridgeProvider;
11
11
  exports.buildCopilotBridgeBaseUrl = buildCopilotBridgeBaseUrl;
12
+ exports.buildCopilotModelProviderProjection = buildCopilotModelProviderProjection;
12
13
  /**
13
14
  * Validates and normalizes unknown JSON into the providers.json domain model.
14
15
  */
@@ -150,6 +151,17 @@ function isCopilotBridgeProvider(provider) {
150
151
  function buildCopilotBridgeBaseUrl(runtime) {
151
152
  return `http://${runtime.bridgeHost}:${runtime.bridgePort}${runtime.bridgePath}`;
152
153
  }
154
+ /**
155
+ * Builds the Codex-facing custom model_provider projection for the managed Copilot bridge.
156
+ */
157
+ function buildCopilotModelProviderProjection(runtime) {
158
+ return {
159
+ baseUrl: buildCopilotBridgeBaseUrl(runtime),
160
+ name: "copilot",
161
+ requiresOpenAiAuth: true,
162
+ wireApi: "responses",
163
+ };
164
+ }
153
165
  /**
154
166
  * Validates one runtime-backed provider block.
155
167
  */
@@ -1,16 +1,92 @@
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.getStorageRoles = getStorageRoles;
4
37
  exports.inspectLiveStateDrift = inspectLiveStateDrift;
38
+ const path = __importStar(require("node:path"));
5
39
  /**
6
40
  * Returns the stable storage contract used by the CLI.
7
41
  */
8
- function getStorageRoles() {
42
+ function getStorageRoles(args) {
43
+ const toolHomeDir = path.dirname(path.resolve(args.providersPath));
44
+ const backupsDir = path.join(toolHomeDir, "backups");
45
+ const runtimeDir = args.runtimeDir ? path.resolve(args.runtimeDir) : path.join(toolHomeDir, "runtime");
46
+ const runtimesDir = args.runtimesDir ? path.resolve(args.runtimesDir) : path.join(toolHomeDir, "runtimes");
9
47
  return {
10
- managementSSOT: "providers.json",
11
- runtimeMirrors: ["config.toml"],
12
- authStateFile: "auth.json",
13
- rollbackState: "backups/latest.json",
48
+ toolHome: {
49
+ root: toolHomeDir,
50
+ toolConfig: path.join(toolHomeDir, "codex-switch.json"),
51
+ providers: path.resolve(args.providersPath),
52
+ backupsDir,
53
+ latestBackup: path.join(backupsDir, "latest.json"),
54
+ runtimeStateDir: runtimeDir,
55
+ runtimeInstallDir: runtimesDir,
56
+ },
57
+ targetRuntime: {
58
+ root: path.resolve(args.codexDir),
59
+ config: path.resolve(args.configPath),
60
+ auth: path.resolve(args.authPath),
61
+ },
62
+ managementSSOT: {
63
+ scope: "toolHome",
64
+ path: path.resolve(args.providersPath),
65
+ },
66
+ runtimeMirrors: [
67
+ {
68
+ scope: "targetRuntime",
69
+ path: path.resolve(args.configPath),
70
+ },
71
+ ],
72
+ authStateFile: {
73
+ scope: "targetRuntime",
74
+ path: path.resolve(args.authPath),
75
+ },
76
+ rollbackState: {
77
+ scope: "toolHome",
78
+ path: path.join(backupsDir, "latest.json"),
79
+ },
80
+ runtimeState: {
81
+ scope: "toolHome",
82
+ path: runtimeDir,
83
+ managedBackup: false,
84
+ },
85
+ runtimeInstall: {
86
+ scope: "toolHome",
87
+ path: runtimesDir,
88
+ managedBackup: false,
89
+ },
14
90
  };
15
91
  }
16
92
  /**
@@ -21,7 +97,9 @@ function inspectLiveStateDrift(currentProfile, providers) {
21
97
  return {
22
98
  currentProfile,
23
99
  mappedProvider: null,
100
+ mappedProviders: [],
24
101
  profileMapped: false,
102
+ providerResolvable: false,
25
103
  canBackfillActiveProvider: false,
26
104
  reason: providers ? "profile-missing" : "config-missing",
27
105
  };
@@ -30,27 +108,47 @@ function inspectLiveStateDrift(currentProfile, providers) {
30
108
  return {
31
109
  currentProfile,
32
110
  mappedProvider: null,
111
+ mappedProviders: [],
33
112
  profileMapped: false,
113
+ providerResolvable: false,
34
114
  canBackfillActiveProvider: false,
35
115
  reason: "providers-missing",
36
116
  };
37
117
  }
118
+ const mappedProviders = [];
38
119
  for (const [name, provider] of Object.entries(providers.providers)) {
39
- // A direct profile match means the runtime state is still managed.
40
120
  if (provider.profile === currentProfile) {
41
- return {
42
- currentProfile,
43
- mappedProvider: name,
44
- profileMapped: true,
45
- canBackfillActiveProvider: false,
46
- reason: "ok",
47
- };
121
+ mappedProviders.push(name);
48
122
  }
49
123
  }
124
+ if (mappedProviders.length === 1) {
125
+ return {
126
+ currentProfile,
127
+ mappedProvider: mappedProviders[0],
128
+ mappedProviders,
129
+ profileMapped: true,
130
+ providerResolvable: true,
131
+ canBackfillActiveProvider: false,
132
+ reason: "ok",
133
+ };
134
+ }
135
+ if (mappedProviders.length > 1) {
136
+ return {
137
+ currentProfile,
138
+ mappedProvider: null,
139
+ mappedProviders,
140
+ profileMapped: true,
141
+ providerResolvable: false,
142
+ canBackfillActiveProvider: false,
143
+ reason: "shared-profile",
144
+ };
145
+ }
50
146
  return {
51
147
  currentProfile,
52
148
  mappedProvider: null,
149
+ mappedProviders: [],
53
150
  profileMapped: false,
151
+ providerResolvable: false,
54
152
  canBackfillActiveProvider: true,
55
153
  reason: "provider-unmapped",
56
154
  };
@@ -1,208 +1,18 @@
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
- })();
35
2
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.readConfigFile = readConfigFile;
37
- exports.readStructuredConfig = readStructuredConfig;
38
- exports.readCurrentProfile = readCurrentProfile;
39
- exports.listConfigProfiles = listConfigProfiles;
40
- exports.ensureProfileExists = ensureProfileExists;
41
- exports.requireManagedProfileRuntime = requireManagedProfileRuntime;
42
- exports.requireModelProviderRuntimeSection = requireModelProviderRuntimeSection;
43
- exports.updateTopLevelProfile = updateTopLevelProfile;
44
- exports.createConfigMutationPlan = createConfigMutationPlan;
45
- exports.applyConfigMutation = applyConfigMutation;
46
- exports.findCodexDirCandidates = findCodexDirCandidates;
47
- const fs = __importStar(require("node:fs"));
48
- const os = __importStar(require("node:os"));
49
- const path = __importStar(require("node:path"));
50
- const config_1 = require("../domain/config");
51
- const errors_1 = require("../domain/errors");
52
- const codex_paths_1 = require("./codex-paths");
53
- const fs_utils_1 = require("./fs-utils");
54
- /**
55
- * Reads config.toml and throws a typed error when the file is missing.
56
- */
57
- function readConfigFile(configPath) {
58
- return (0, fs_utils_1.readRequiredFile)(configPath, "CONFIG_NOT_FOUND", "config.toml");
59
- }
60
- /**
61
- * Reads and parses config.toml into the managed structured document shape.
62
- */
63
- function readStructuredConfig(configPath) {
64
- const content = readConfigFile(configPath);
65
- try {
66
- return (0, config_1.parseStructuredConfig)(content);
67
- }
68
- catch (error) {
69
- throw (0, errors_1.cliError)("CONFIG_PARSE_ERROR", "Failed to parse config.toml.", {
70
- file: configPath,
71
- cause: (0, errors_1.normalizeError)(error).message,
72
- });
73
- }
74
- }
75
- /**
76
- * Reads the active top-level profile from config.toml.
77
- */
78
- function readCurrentProfile(configPath) {
79
- const profile = readStructuredConfig(configPath).activeProfile ?? (0, config_1.parseTopLevelProfile)(readConfigFile(configPath));
80
- if (!profile) {
81
- throw (0, errors_1.cliError)("PROFILE_NOT_FOUND", "No top-level profile is set in config.toml.", {
82
- file: configPath,
83
- });
84
- }
85
- return profile;
86
- }
87
- /**
88
- * Lists all named profile sections declared in config.toml.
89
- */
90
- function listConfigProfiles(configPath) {
91
- return new Set(readStructuredConfig(configPath).profiles.map((profile) => profile.name));
92
- }
93
- /**
94
- * Verifies that a provider's target profile exists before a switch operation proceeds.
95
- */
96
- function ensureProfileExists(configPath, profile, provider) {
97
- const document = readStructuredConfig(configPath);
98
- if (!document.profiles.some((entry) => entry.name === profile)) {
99
- throw (0, errors_1.cliError)("PROFILE_NOT_FOUND", `Profile "${profile}" does not exist in config.toml.`, {
100
- file: configPath,
101
- provider,
102
- profile,
103
- });
104
- }
105
- return document;
106
- }
107
- /**
108
- * Resolves one profile view and enforces the managed model_provider contract.
109
- */
110
- function requireManagedProfileRuntime(document, providers, profile) {
111
- const view = (0, config_1.buildManagedProfileViews)(document, providers).find((entry) => entry.name === profile);
112
- if (!view) {
113
- throw (0, errors_1.cliError)("PROFILE_NOT_FOUND", `Profile "${profile}" does not exist in config.toml.`, {
114
- profile,
115
- });
116
- }
117
- if (!view.modelProvider) {
118
- throw (0, errors_1.cliError)("MANAGED_PROFILE_FIELDS_MISSING", `Managed profile "${profile}" requires model_provider.`, {
119
- profile,
120
- missingFields: ["model_provider"],
121
- });
122
- }
123
- if (view.modelProvider !== profile) {
124
- throw (0, errors_1.cliError)("INVALID_ARGUMENT", `Managed profile "${profile}" must use the same model_provider name.`, {
125
- profile,
126
- modelProvider: view.modelProvider,
127
- });
128
- }
129
- const modelProviderSection = document.modelProviders.find((entry) => entry.name === view.modelProvider);
130
- if (!modelProviderSection) {
131
- throw (0, errors_1.cliError)("PROFILE_NOT_FOUND", `Model provider "${view.modelProvider}" does not exist in config.toml.`, {
132
- profile,
133
- modelProvider: view.modelProvider,
134
- });
135
- }
136
- if (!modelProviderSection.baseUrl) {
137
- throw (0, errors_1.cliError)("MANAGED_PROFILE_FIELDS_MISSING", `Model provider "${view.modelProvider}" requires base_url.`, {
138
- profile,
139
- modelProvider: view.modelProvider,
140
- missingFields: ["base_url"],
141
- });
142
- }
143
- return view;
144
- }
145
- /**
146
- * Verifies that a same-named model_provider runtime section exists and has base_url.
147
- */
148
- function requireModelProviderRuntimeSection(document, profile) {
149
- const modelProviderSection = document.modelProviders.find((entry) => entry.name === profile);
150
- if (!modelProviderSection) {
151
- throw (0, errors_1.cliError)("PROFILE_NOT_FOUND", `Model provider "${profile}" does not exist in config.toml.`, {
152
- profile,
153
- modelProvider: profile,
154
- });
155
- }
156
- if (!modelProviderSection.baseUrl) {
157
- throw (0, errors_1.cliError)("MANAGED_PROFILE_FIELDS_MISSING", `Model provider "${profile}" requires base_url.`, {
158
- profile,
159
- modelProvider: profile,
160
- missingFields: ["base_url"],
161
- });
162
- }
163
- }
164
- /**
165
- * Rewrites config.toml so the requested profile becomes the active top-level profile.
166
- */
167
- function updateTopLevelProfile(configPath, configContent, profile) {
168
- (0, fs_utils_1.writeTextFileAtomic)(configPath, (0, config_1.applyPatchOperations)(configContent, (0, config_1.planConfigMutation)((0, config_1.parseStructuredConfig)(configContent), {
169
- setActiveProfile: profile,
170
- }).operations));
171
- }
172
- /**
173
- * Exposes the config mutation planner to application services.
174
- */
175
- function createConfigMutationPlan(document, args) {
176
- return (0, config_1.planConfigMutation)(document, args);
177
- }
178
- /**
179
- * Applies a previously generated mutation plan to config.toml in one write.
180
- */
181
- function applyConfigMutation(configPath, document, plan) {
182
- (0, fs_utils_1.writeTextFileAtomic)(configPath, (0, config_1.applyPatchOperations)(document.rawText, plan.operations));
183
- }
184
- /**
185
- * Finds candidate Codex directories in a stable, non-recursive order.
186
- */
187
- function findCodexDirCandidates(explicitCodexDir) {
188
- if (explicitCodexDir) {
189
- return [(0, codex_paths_1.resolveCodexDir)(explicitCodexDir)];
190
- }
191
- const candidates = new Set();
192
- const ordered = [];
193
- const envCandidate = process.env[codex_paths_1.CODEX_DIR_ENV_NAME];
194
- if (envCandidate) {
195
- ordered.push((0, codex_paths_1.resolveCodexDir)(envCandidate));
196
- }
197
- if (process.env.NODE_ENV === "development") {
198
- ordered.push(path.resolve(process.cwd(), "dev-codex", "local-sandbox"));
199
- }
200
- ordered.push(path.join(os.homedir(), ".codex"));
201
- for (const candidate of ordered) {
202
- if (!candidate || candidates.has(candidate) || !fs.existsSync(candidate)) {
203
- continue;
204
- }
205
- candidates.add(candidate);
206
- }
207
- return [...candidates];
208
- }
3
+ exports.updateTopLevelProfile = exports.requireModelProviderRuntimeSection = exports.requireManagedProfileRuntime = exports.readStructuredConfig = exports.readCurrentProfile = exports.readConfigFile = exports.listConfigProfiles = exports.findCodexDirCandidates = exports.ensureProfileExists = exports.createConfigMutationPlan = exports.applyConfigMutation = void 0;
4
+ /**
5
+ * Compatibility facade that re-exports config repository helpers from storage.
6
+ */
7
+ var config_repo_1 = require("../storage/config-repo");
8
+ Object.defineProperty(exports, "applyConfigMutation", { enumerable: true, get: function () { return config_repo_1.applyConfigMutation; } });
9
+ Object.defineProperty(exports, "createConfigMutationPlan", { enumerable: true, get: function () { return config_repo_1.createConfigMutationPlan; } });
10
+ Object.defineProperty(exports, "ensureProfileExists", { enumerable: true, get: function () { return config_repo_1.ensureProfileExists; } });
11
+ Object.defineProperty(exports, "findCodexDirCandidates", { enumerable: true, get: function () { return config_repo_1.findCodexDirCandidates; } });
12
+ Object.defineProperty(exports, "listConfigProfiles", { enumerable: true, get: function () { return config_repo_1.listConfigProfiles; } });
13
+ Object.defineProperty(exports, "readConfigFile", { enumerable: true, get: function () { return config_repo_1.readConfigFile; } });
14
+ Object.defineProperty(exports, "readCurrentProfile", { enumerable: true, get: function () { return config_repo_1.readCurrentProfile; } });
15
+ Object.defineProperty(exports, "readStructuredConfig", { enumerable: true, get: function () { return config_repo_1.readStructuredConfig; } });
16
+ Object.defineProperty(exports, "requireManagedProfileRuntime", { enumerable: true, get: function () { return config_repo_1.requireManagedProfileRuntime; } });
17
+ Object.defineProperty(exports, "requireModelProviderRuntimeSection", { enumerable: true, get: function () { return config_repo_1.requireModelProviderRuntimeSection; } });
18
+ Object.defineProperty(exports, "updateTopLevelProfile", { enumerable: true, get: function () { return config_repo_1.updateTopLevelProfile; } });
@@ -52,7 +52,10 @@ const fs = __importStar(require("node:fs"));
52
52
  const path = __importStar(require("node:path"));
53
53
  const errors_1 = require("../domain/errors");
54
54
  const backups_1 = require("../domain/backups");
55
+ const providers_1 = require("../domain/providers");
56
+ const runtime_state_1 = require("../domain/runtime-state");
55
57
  const codex_paths_1 = require("../storage/codex-paths");
58
+ const config_repo_1 = require("../storage/config-repo");
56
59
  const providers_repo_1 = require("../storage/providers-repo");
57
60
  const backup_repo_1 = require("../storage/backup-repo");
58
61
  const add_interactive_1 = require("./add-interactive");
@@ -65,15 +68,22 @@ function canPrompt(runtime, jsonMode) {
65
68
  /**
66
69
  * Prompts the user to choose one configured provider when a command omitted its target.
67
70
  */
68
- async function promptForProviderSelection(runtime, providersPath, message) {
71
+ async function promptForProviderSelection(runtime, providersPath, configPath, message) {
69
72
  const providers = (0, providers_repo_1.readProvidersFile)(providersPath);
73
+ const currentProfile = fs.existsSync(configPath) ? (0, config_repo_1.readStructuredConfig)(configPath).activeProfile : null;
74
+ const liveState = (0, runtime_state_1.inspectLiveStateDrift)(currentProfile, providers);
70
75
  const choices = Object.entries(providers.providers)
71
76
  .sort(([left], [right]) => left.localeCompare(right))
72
- .map(([providerName, provider]) => ({
73
- value: providerName,
74
- label: providerName,
75
- hint: provider.profile,
76
- }));
77
+ .map(([providerName, provider]) => {
78
+ const providerType = (0, providers_1.isCopilotBridgeProvider)(provider) ? "copilot" : "direct";
79
+ const currentMarker = liveState.providerResolvable && liveState.mappedProvider === providerName ? " | current" : "";
80
+ const ambiguousMarker = !liveState.providerResolvable && liveState.mappedProviders.includes(providerName) ? " | current=ambiguous" : "";
81
+ return {
82
+ value: providerName,
83
+ label: providerName,
84
+ hint: `profile=${provider.profile} | type=${providerType}${currentMarker}${ambiguousMarker}`,
85
+ };
86
+ });
77
87
  if (choices.length === 0) {
78
88
  throw (0, errors_1.cliError)("PROVIDER_NOT_FOUND", "No providers are configured.");
79
89
  }