@minniexcode/codex-switch 0.0.5 → 0.0.6

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 (62) hide show
  1. package/README.md +35 -97
  2. package/dist/app/add-provider.js +10 -4
  3. package/dist/app/edit-provider.js +9 -9
  4. package/dist/app/export-providers.js +2 -2
  5. package/dist/app/get-current-profile.js +1 -1
  6. package/dist/app/get-status.js +2 -2
  7. package/dist/app/import-providers.js +15 -7
  8. package/dist/app/list-backups.js +1 -1
  9. package/dist/app/list-config-profiles.js +3 -2
  10. package/dist/app/list-providers.js +1 -1
  11. package/dist/app/remove-provider.js +2 -2
  12. package/dist/app/rollback-backup.js +1 -1
  13. package/dist/app/rollback-latest.js +1 -1
  14. package/dist/app/run-doctor.js +23 -6
  15. package/dist/app/run-mutation.js +2 -2
  16. package/dist/app/setup-codex.js +6 -6
  17. package/dist/app/show-config.js +2 -2
  18. package/dist/app/show-provider.js +1 -1
  19. package/dist/app/switch-provider.js +3 -3
  20. package/dist/cli/add-interactive.js +7 -106
  21. package/dist/cli/args.js +5 -137
  22. package/dist/cli/help.js +5 -313
  23. package/dist/cli/interactive.js +16 -227
  24. package/dist/cli/output.js +2 -2
  25. package/dist/cli/prompt.js +3 -108
  26. package/dist/cli.js +10 -404
  27. package/dist/commands/args.js +132 -0
  28. package/dist/commands/dispatch.js +16 -0
  29. package/dist/commands/handlers.js +391 -0
  30. package/dist/commands/help.js +119 -0
  31. package/dist/commands/registry.js +291 -0
  32. package/dist/commands/types.js +2 -0
  33. package/dist/domain/config.js +100 -23
  34. package/dist/infra/backup-repo.js +8 -208
  35. package/dist/infra/codex-cli.js +8 -128
  36. package/dist/infra/codex-paths.js +5 -69
  37. package/dist/infra/config-repo.js +59 -0
  38. package/dist/infra/fs-utils.js +7 -95
  39. package/dist/infra/lock-repo.js +3 -97
  40. package/dist/infra/providers-repo.js +7 -96
  41. package/dist/interaction/add-interactive.js +108 -0
  42. package/dist/interaction/interactive.js +216 -0
  43. package/dist/interaction/prompt.js +110 -0
  44. package/dist/runtime/codex-cli.js +130 -0
  45. package/dist/runtime/codex-probe.js +50 -0
  46. package/dist/runtime/types.js +2 -0
  47. package/dist/storage/backup-repo.js +210 -0
  48. package/dist/storage/codex-paths.js +71 -0
  49. package/dist/storage/config-repo.js +208 -0
  50. package/dist/storage/fs-utils.js +97 -0
  51. package/dist/storage/lock-repo.js +99 -0
  52. package/dist/storage/providers-repo.js +98 -0
  53. package/docs/Design/codex-switch-v0.0.5-design.md +32 -22
  54. package/docs/Design/codex-switch-v0.0.6-design.md +708 -0
  55. package/docs/PRD/codex-switch-prd-v0.0.5-to-v0.1.0.md +125 -93
  56. package/docs/PRD/codex-switch-prd-v0.1.0.md +200 -226
  57. package/docs/PRD/codex-switch-prd.md +1 -1
  58. package/docs/cli-usage.md +2 -1
  59. package/docs/codex-switch-technical-architecture.md +73 -4
  60. package/docs/test-report-0.0.5.md +163 -0
  61. package/docs/testing.md +131 -0
  62. package/package.json +1 -1
@@ -0,0 +1,291 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.COMMANDS = void 0;
4
+ exports.getCommandDefinitions = getCommandDefinitions;
5
+ exports.getKnownCommandIds = getKnownCommandIds;
6
+ exports.findCommandDefinition = findCommandDefinition;
7
+ exports.findCommandDefinitionByTokens = findCommandDefinitionByTokens;
8
+ exports.resolveCommandFromArgv = resolveCommandFromArgv;
9
+ exports.isKnownCommandName = isKnownCommandName;
10
+ exports.isKnownHelpTopic = isKnownHelpTopic;
11
+ exports.getPublicCommandNames = getPublicCommandNames;
12
+ exports.getNestedCommandTokens = getNestedCommandTokens;
13
+ const handlers_1 = require("./handlers");
14
+ exports.COMMANDS = [
15
+ {
16
+ id: "config-show",
17
+ tokens: ["config", "show"],
18
+ handler: handlers_1.handleRegisteredCommand,
19
+ group: "read",
20
+ summary: "Show the structured config profile view.",
21
+ usage: ["codexs config show [profile] [--json] [--codex-dir <path>]"],
22
+ details: [
23
+ "Returns all recognizable config profiles by default, including unmanaged and orphaned references.",
24
+ "Passing [profile] narrows the response to one profile while preserving the same shape.",
25
+ ],
26
+ examples: ["codexs config show", "codexs config show packycode --json"],
27
+ },
28
+ {
29
+ id: "config-list-profiles",
30
+ tokens: ["config", "list-profiles"],
31
+ handler: handlers_1.handleRegisteredCommand,
32
+ group: "read",
33
+ summary: "List recognizable config profiles with managed-state hints.",
34
+ usage: ["codexs config list-profiles [--json] [--codex-dir <path>]"],
35
+ details: [
36
+ "Lists managed, unmanaged, and orphaned config profiles in one stable view.",
37
+ "Use config show for richer single-profile details.",
38
+ ],
39
+ examples: ["codexs config list-profiles", "codexs config list-profiles --json"],
40
+ },
41
+ {
42
+ id: "setup",
43
+ tokens: ["setup"],
44
+ handler: handlers_1.handleRegisteredCommand,
45
+ group: "write",
46
+ summary: "Initialize providers.json from an existing Codex directory.",
47
+ usage: ["codexs setup [--json] [--codex-dir <path>] [--merge|--overwrite]"],
48
+ details: [
49
+ "Reads config.toml profiles, collects complete provider records, then writes providers.json under managed backup flow.",
50
+ "TTY mode can collect missing provider details and choose merge or overwrite when providers.json already exists.",
51
+ "In 0.0.6, setup adopt initialization requires a real TTY because profile selection and provider details are collected interactively.",
52
+ "Non-TTY and --json runs fail fast with a structured error instead of entering partial setup behavior.",
53
+ ],
54
+ examples: ["codexs setup", "codexs setup --overwrite --json --codex-dir ~/.codex"],
55
+ },
56
+ {
57
+ id: "list",
58
+ tokens: ["list"],
59
+ handler: handlers_1.handleRegisteredCommand,
60
+ group: "read",
61
+ summary: "List configured providers from providers.json.",
62
+ usage: ["codexs list [--json] [--codex-dir <path>]"],
63
+ details: ["Reads providers.json and prints provider-to-profile mappings.", "Use --json for machine-readable automation output."],
64
+ examples: ["codexs list", "codexs list --json"],
65
+ },
66
+ {
67
+ id: "show",
68
+ tokens: ["show"],
69
+ handler: handlers_1.handleRegisteredCommand,
70
+ group: "read",
71
+ summary: "Show one provider record from providers.json.",
72
+ usage: ["codexs show <provider> [--json] [--codex-dir <path>]"],
73
+ details: [
74
+ "Human-readable output masks apiKey by default.",
75
+ "TTY mode can select a missing provider interactively before showing its record.",
76
+ "JSON mode returns the full provider payload for local automation.",
77
+ ],
78
+ examples: ["codexs show packycode", "codexs show packycode --json"],
79
+ },
80
+ {
81
+ id: "current",
82
+ tokens: ["current"],
83
+ handler: handlers_1.handleRegisteredCommand,
84
+ group: "read",
85
+ summary: "Show the active top-level profile from config.toml.",
86
+ usage: ["codexs current [--json] [--codex-dir <path>]"],
87
+ details: ["Reads the currently active top-level profile.", "Fails when config.toml is missing or has no top-level profile."],
88
+ examples: ["codexs current", "codexs current --json"],
89
+ },
90
+ {
91
+ id: "status",
92
+ tokens: ["status"],
93
+ handler: handlers_1.handleRegisteredCommand,
94
+ group: "read",
95
+ summary: "Show a quick status summary for the local Codex directory.",
96
+ usage: ["codexs status [--json] [--codex-dir <path>]"],
97
+ details: [
98
+ "Reports file presence, current profile, and whether the live profile is mapped.",
99
+ "Surfaces config consistency signals without mutating any files.",
100
+ "Use doctor for deeper diagnostics.",
101
+ ],
102
+ examples: ["codexs status", "codexs status --json --codex-dir ./.tmp-codex"],
103
+ },
104
+ {
105
+ id: "edit",
106
+ tokens: ["edit"],
107
+ handler: handlers_1.handleRegisteredCommand,
108
+ group: "write",
109
+ summary: "Update fields on a single provider record.",
110
+ usage: [
111
+ "codexs edit <provider> [--profile <name>] [--api-key <key>] [--base-url <url>] [--note <text>] [--tag <tag> ...] [--json] [--codex-dir <path>]",
112
+ "codexs edit <provider> --profile <name> --create-profile --model <name> --base-url <url>",
113
+ ],
114
+ details: [
115
+ "Passed flags replace only the selected fields and keep the rest unchanged.",
116
+ "TTY mode can first select a provider, then prompt for fields when no editable options were provided.",
117
+ "Interactive tags use preset multi-select plus optional custom comma-separated input.",
118
+ "When rebinding to a missing profile, --create-profile requires both --model and --base-url.",
119
+ "Backs up providers.json and config.toml before writing.",
120
+ ],
121
+ examples: ["codexs edit packycode --note primary", "codexs edit packycode --tag daily --tag paid --json"],
122
+ },
123
+ {
124
+ id: "add",
125
+ tokens: ["add"],
126
+ handler: handlers_1.handleRegisteredCommand,
127
+ group: "write",
128
+ summary: "Add a provider with explicit flags or progressive TTY prompts.",
129
+ usage: [
130
+ "codexs add <provider> --profile <name> --api-key <key> [--base-url <url>] [--note <text>] [--tag <tag> ...]",
131
+ "codexs add <provider> --profile <name> --api-key <key> --create-profile --model <name> --base-url <url>",
132
+ "codexs add [--profile <name>] [--api-key <key>] [--base-url <url>] [--note <text>] [--tag <tag> ...]",
133
+ ],
134
+ details: [
135
+ "Prompts only for missing required values when stdin/stdout are TTYs and --json is not set.",
136
+ "Interactive add collects provider name, profile, and apiKey progressively as plain text inputs.",
137
+ "Confirm API key when prompted interactively because the hidden prompt asks twice before writing.",
138
+ "Interactive tags use preset multi-select plus optional custom comma-separated input.",
139
+ "Automation and non-TTY environments must pass all required values explicitly.",
140
+ "Creating a missing profile section requires --create-profile together with --model and --base-url.",
141
+ ],
142
+ examples: ["codexs add packycode --profile packycode --api-key sk-xxx", "codexs add packycode --profile packycode", "codexs add"],
143
+ },
144
+ {
145
+ id: "switch",
146
+ tokens: ["switch"],
147
+ handler: handlers_1.handleRegisteredCommand,
148
+ group: "write",
149
+ summary: "Switch to a provider and optionally refresh Codex login.",
150
+ usage: ["codexs switch <provider> [--no-login] [--json] [--codex-dir <path>]"],
151
+ details: [
152
+ "When <provider> is omitted in a TTY, an interactive provider selector is shown.",
153
+ "When <provider> is passed explicitly, switch proceeds directly without extra confirmation.",
154
+ "--no-login remains explicit and is never prompted interactively.",
155
+ "Backs up config.toml and auth.json, then rolls back on failure.",
156
+ ],
157
+ examples: ["codexs switch freemodel", "codexs switch --no-login", "codexs switch freemodel --no-login --json"],
158
+ },
159
+ {
160
+ id: "remove",
161
+ tokens: ["remove"],
162
+ handler: handlers_1.handleRegisteredCommand,
163
+ group: "write",
164
+ summary: "Remove a provider from providers.json.",
165
+ usage: ["codexs remove <provider> [--force] [--switch-to <profile>] [--json] [--codex-dir <path>]"],
166
+ details: [
167
+ "TTY mode can select a missing provider interactively and always asks for deletion confirmation.",
168
+ "Non-TTY and --json automation still require both <provider> and --force.",
169
+ "The confirmation prompt includes the provider name and cancels without writing when declined.",
170
+ "When removing the last provider linked to the active profile, pass --switch-to first.",
171
+ "Backs up providers.json and config.toml before removing the record.",
172
+ ],
173
+ examples: ["codexs remove freemodel", "codexs remove freemodel --force --json"],
174
+ },
175
+ {
176
+ id: "import",
177
+ tokens: ["import"],
178
+ handler: handlers_1.handleRegisteredCommand,
179
+ group: "write",
180
+ summary: "Replace providers.json with an external JSON file.",
181
+ usage: ["codexs import <file> [--json] [--codex-dir <path>]"],
182
+ details: [
183
+ "The file path is always explicit; there is no path wizard in this release.",
184
+ "TTY mode asks for confirmation before replacing or merging into the current providers registry.",
185
+ "Non-TTY and --json runs stay non-interactive and validate the file before writing.",
186
+ ],
187
+ examples: ["codexs import ./providers.json", "codexs import ./providers.json --merge --json"],
188
+ },
189
+ {
190
+ id: "export",
191
+ tokens: ["export"],
192
+ handler: handlers_1.handleRegisteredCommand,
193
+ group: "write",
194
+ summary: "Export the current providers.json to another file.",
195
+ usage: ["codexs export <file> [--force] [--json] [--codex-dir <path>]"],
196
+ details: [
197
+ "The file path is always explicit; there is no path wizard in this release.",
198
+ "TTY mode asks before overwriting an existing target when --force is not supplied.",
199
+ "Non-TTY and --json automation require --force to overwrite an existing file.",
200
+ ],
201
+ examples: ["codexs export ./providers-backup.json", "codexs export ./providers-backup.json --force"],
202
+ },
203
+ {
204
+ id: "backups-list",
205
+ tokens: ["backups", "list"],
206
+ handler: handlers_1.handleRegisteredCommand,
207
+ group: "recovery",
208
+ summary: "List historical backup entries.",
209
+ usage: ["codexs backups list [--json] [--codex-dir <path>]"],
210
+ details: [
211
+ "Enumerates backups/ manifests and returns them newest first.",
212
+ "Corrupt backup manifests are skipped with warnings instead of failing the whole command.",
213
+ ],
214
+ examples: ["codexs backups list", "codexs backups list --json"],
215
+ },
216
+ {
217
+ id: "doctor",
218
+ tokens: ["doctor"],
219
+ handler: handlers_1.handleRegisteredCommand,
220
+ group: "recovery",
221
+ summary: "Run configuration and environment diagnostics.",
222
+ usage: ["codexs doctor [--json] [--codex-dir <path>]"],
223
+ details: ["Checks the expected config files, provider/profile consistency, and Codex CLI availability.", "Returns structured issues so users and AI agents can act on them."],
224
+ examples: ["codexs doctor", "codexs doctor --json"],
225
+ },
226
+ {
227
+ id: "rollback",
228
+ tokens: ["rollback"],
229
+ handler: handlers_1.handleRegisteredCommand,
230
+ group: "recovery",
231
+ summary: "Restore the latest managed backup or a specific backup id.",
232
+ usage: ["codexs rollback [<backup-id>] [--json] [--codex-dir <path>]"],
233
+ details: [
234
+ "TTY mode previews the target backup path and affected files, then asks for confirmation.",
235
+ "Non-TTY and --json runs stay non-interactive and execute immediately.",
236
+ "Use after a failed or undesired managed mutation.",
237
+ ],
238
+ examples: ["codexs rollback", "codexs rollback 20260511-221457-switch --json"],
239
+ },
240
+ ];
241
+ const COMMAND_NAME_SET = new Set(exports.COMMANDS.flatMap((command) => [command.id, command.tokens.join(" ")]));
242
+ const HELP_TOPIC_SET = new Set([
243
+ ...exports.COMMANDS.map((command) => command.tokens.join(" ")),
244
+ ...new Set(exports.COMMANDS.filter((command) => command.tokens.length > 1).map((command) => command.tokens[0])),
245
+ ]);
246
+ function getCommandDefinitions() {
247
+ return exports.COMMANDS.slice();
248
+ }
249
+ function getKnownCommandIds() {
250
+ return exports.COMMANDS.map((command) => command.id);
251
+ }
252
+ function findCommandDefinition(commandId) {
253
+ if (commandId === "help" || commandId === "version") {
254
+ return null;
255
+ }
256
+ return exports.COMMANDS.find((command) => command.id === commandId) ?? null;
257
+ }
258
+ function findCommandDefinitionByTokens(tokens) {
259
+ return exports.COMMANDS.find((command) => command.tokens.join(" ") === tokens.join(" ")) ?? null;
260
+ }
261
+ function resolveCommandFromArgv(argv) {
262
+ for (const command of exports.COMMANDS
263
+ .slice()
264
+ .sort((left, right) => right.tokens.length - left.tokens.length)) {
265
+ const candidate = argv.slice(0, command.tokens.length);
266
+ if (candidate.length === command.tokens.length && candidate.join(" ") === command.tokens.join(" ")) {
267
+ return {
268
+ definition: command,
269
+ consumedTokens: command.tokens.length,
270
+ };
271
+ }
272
+ }
273
+ return {
274
+ definition: null,
275
+ consumedTokens: 0,
276
+ };
277
+ }
278
+ function isKnownCommandName(commandName) {
279
+ return COMMAND_NAME_SET.has(commandName);
280
+ }
281
+ function isKnownHelpTopic(topic) {
282
+ return HELP_TOPIC_SET.has(topic);
283
+ }
284
+ function getPublicCommandNames() {
285
+ return exports.COMMANDS.map((command) => command.tokens.join(" "));
286
+ }
287
+ function getNestedCommandTokens(rootToken) {
288
+ return exports.COMMANDS
289
+ .filter((command) => command.tokens.length > 1 && command.tokens[0] === rootToken)
290
+ .map((command) => command.tokens.join(" "));
291
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -74,7 +74,9 @@ function parseStructuredConfig(configContent) {
74
74
  let activeProfile = null;
75
75
  let activeProfileRange = null;
76
76
  const profiles = [];
77
+ const modelProviders = [];
77
78
  let currentProfile = null;
79
+ let currentModelProvider = null;
78
80
  let inRoot = true;
79
81
  for (const line of lines) {
80
82
  const trimmed = line.content.trim();
@@ -83,6 +85,10 @@ function parseStructuredConfig(configContent) {
83
85
  if (currentProfile) {
84
86
  currentProfile.sectionEnd = line.start;
85
87
  }
88
+ if (currentModelProvider) {
89
+ currentModelProvider.sectionEnd = line.start;
90
+ currentModelProvider = null;
91
+ }
86
92
  currentProfile = {
87
93
  name: headerMatch[1],
88
94
  headerStart: line.start,
@@ -90,19 +96,43 @@ function parseStructuredConfig(configContent) {
90
96
  sectionEnd: configContent.length,
91
97
  managedFieldInsertIndex: configContent.length,
92
98
  modelValueRange: null,
93
- baseUrlValueRange: null,
99
+ modelProviderValueRange: null,
94
100
  model: null,
95
- baseUrl: null,
101
+ modelProvider: null,
96
102
  };
97
103
  profiles.push(currentProfile);
98
104
  inRoot = false;
99
105
  continue;
100
106
  }
107
+ const modelProviderHeaderMatch = trimmed.match(/^\[model_providers\.([^\]]+)\]$/);
108
+ if (modelProviderHeaderMatch) {
109
+ if (currentProfile) {
110
+ currentProfile.sectionEnd = line.start;
111
+ currentProfile = null;
112
+ }
113
+ if (currentModelProvider) {
114
+ currentModelProvider.sectionEnd = line.start;
115
+ }
116
+ currentModelProvider = {
117
+ name: modelProviderHeaderMatch[1],
118
+ sectionStart: line.start,
119
+ sectionEnd: configContent.length,
120
+ baseUrlValueRange: null,
121
+ baseUrl: null,
122
+ };
123
+ modelProviders.push(currentModelProvider);
124
+ inRoot = false;
125
+ continue;
126
+ }
101
127
  if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
102
128
  if (currentProfile) {
103
129
  currentProfile.sectionEnd = line.start;
104
130
  currentProfile = null;
105
131
  }
132
+ if (currentModelProvider) {
133
+ currentModelProvider.sectionEnd = line.start;
134
+ currentModelProvider = null;
135
+ }
106
136
  inRoot = false;
107
137
  continue;
108
138
  }
@@ -125,10 +155,20 @@ function parseStructuredConfig(configContent) {
125
155
  end: line.start + modelMatch.valueEnd,
126
156
  };
127
157
  }
158
+ const modelProviderMatch = matchKeyValueLine(line.content, "model_provider");
159
+ if (modelProviderMatch) {
160
+ currentProfile.modelProvider = modelProviderMatch.value;
161
+ currentProfile.modelProviderValueRange = {
162
+ start: line.start + modelProviderMatch.valueStart,
163
+ end: line.start + modelProviderMatch.valueEnd,
164
+ };
165
+ }
166
+ }
167
+ if (currentModelProvider) {
128
168
  const baseUrlMatch = matchKeyValueLine(line.content, "base_url");
129
169
  if (baseUrlMatch) {
130
- currentProfile.baseUrl = baseUrlMatch.value;
131
- currentProfile.baseUrlValueRange = {
170
+ currentModelProvider.baseUrl = baseUrlMatch.value;
171
+ currentModelProvider.baseUrlValueRange = {
132
172
  start: line.start + baseUrlMatch.valueStart,
133
173
  end: line.start + baseUrlMatch.valueEnd,
134
174
  };
@@ -144,6 +184,7 @@ function parseStructuredConfig(configContent) {
144
184
  ...profile,
145
185
  managedFieldInsertIndex: findManagedFieldInsertIndex(configContent, profile.sectionStart, profile.sectionEnd),
146
186
  })),
187
+ modelProviders,
147
188
  };
148
189
  }
149
190
  /**
@@ -151,10 +192,12 @@ function parseStructuredConfig(configContent) {
151
192
  */
152
193
  function buildManagedProfileViews(document, providers) {
153
194
  const linkMap = buildProfileLinkMap(providers);
195
+ const modelProviderMap = new Map(document.modelProviders.map((provider) => [provider.name, provider]));
154
196
  const views = [];
155
197
  const seen = new Set();
156
198
  for (const section of document.profiles) {
157
199
  const linkInfo = linkMap.get(section.name) ?? { linkedProviders: [], managed: false };
200
+ const modelProviderSection = section.modelProvider ? modelProviderMap.get(section.modelProvider) ?? null : null;
158
201
  seen.add(section.name);
159
202
  views.push({
160
203
  name: section.name,
@@ -162,8 +205,9 @@ function buildManagedProfileViews(document, providers) {
162
205
  isActive: document.activeProfile === section.name,
163
206
  linkedProviders: [...linkInfo.linkedProviders].sort(),
164
207
  model: section.model,
165
- baseUrl: section.baseUrl,
166
- managedFields: collectManagedFields(section.model, section.baseUrl),
208
+ modelProvider: section.modelProvider,
209
+ baseUrl: modelProviderSection?.baseUrl ?? null,
210
+ managedFields: collectManagedFields(section.model, section.modelProvider),
167
211
  source: linkInfo.managed ? "managed" : "unmanaged",
168
212
  });
169
213
  }
@@ -177,6 +221,7 @@ function buildManagedProfileViews(document, providers) {
177
221
  isActive: document.activeProfile === profile,
178
222
  linkedProviders: [...linkInfo.linkedProviders].sort(),
179
223
  model: null,
224
+ modelProvider: null,
180
225
  baseUrl: null,
181
226
  managedFields: [],
182
227
  source: "orphaned-reference",
@@ -210,6 +255,38 @@ function collectConfigConsistencyIssues(document, providers) {
210
255
  providers: [...view.linkedProviders],
211
256
  });
212
257
  }
258
+ if (view.source !== "orphaned-reference") {
259
+ if (!view.modelProvider) {
260
+ issues.push({
261
+ code: "MODEL_PROVIDER_MISSING",
262
+ profile: view.name,
263
+ });
264
+ }
265
+ else {
266
+ if (view.modelProvider !== view.name) {
267
+ issues.push({
268
+ code: "MODEL_PROVIDER_NAME_MISMATCH",
269
+ profile: view.name,
270
+ modelProvider: view.modelProvider,
271
+ });
272
+ }
273
+ const modelProviderSection = document.modelProviders.find((entry) => entry.name === view.modelProvider);
274
+ if (!modelProviderSection) {
275
+ issues.push({
276
+ code: "MODEL_PROVIDER_SECTION_MISSING",
277
+ profile: view.name,
278
+ modelProvider: view.modelProvider,
279
+ });
280
+ }
281
+ else if (!modelProviderSection.baseUrl) {
282
+ issues.push({
283
+ code: "MODEL_PROVIDER_BASE_URL_MISSING",
284
+ profile: view.name,
285
+ modelProvider: view.modelProvider,
286
+ });
287
+ }
288
+ }
289
+ }
213
290
  }
214
291
  if (document.activeProfile) {
215
292
  const activeLinkInfo = buildProfileLinkMap(providers).get(document.activeProfile);
@@ -232,19 +309,19 @@ function collectConfigConsistencyIssues(document, providers) {
232
309
  */
233
310
  function validateManagedProfileCreation(profile, fields) {
234
311
  const model = fields.model?.trim() ?? "";
235
- const baseUrl = fields.baseUrl?.trim() ?? "";
236
- if (!model || !baseUrl) {
237
- throw (0, errors_1.cliError)("MANAGED_PROFILE_FIELDS_MISSING", `Managed profile "${profile}" requires both model and base_url.`, {
312
+ const modelProvider = fields.modelProvider?.trim() ?? "";
313
+ if (!model || !modelProvider) {
314
+ throw (0, errors_1.cliError)("MANAGED_PROFILE_FIELDS_MISSING", `Managed profile "${profile}" requires both model and model_provider.`, {
238
315
  profile,
239
316
  missingFields: [
240
317
  !model ? "model" : null,
241
- !baseUrl ? "base_url" : null,
318
+ !modelProvider ? "model_provider" : null,
242
319
  ].filter((value) => Boolean(value)),
243
320
  });
244
321
  }
245
322
  return {
246
323
  model,
247
- baseUrl,
324
+ modelProvider,
248
325
  };
249
326
  }
250
327
  /**
@@ -344,7 +421,7 @@ function planConfigMutation(document, args) {
344
421
  index: document.rawText.length,
345
422
  text: `${prefix}[profiles.${profileName}]${document.lineEnding}` +
346
423
  `model = ${JSON.stringify(requiredFields.model)}${document.lineEnding}` +
347
- `base_url = ${JSON.stringify(requiredFields.baseUrl)}${document.lineEnding}`,
424
+ `model_provider = ${JSON.stringify(requiredFields.modelProvider)}${document.lineEnding}`,
348
425
  });
349
426
  createdProfileSections.push(profileName);
350
427
  continue;
@@ -384,7 +461,7 @@ function applyPatchOperations(rawText, operations) {
384
461
  function planSectionFieldMutation(document, section, fields, operations) {
385
462
  let updated = false;
386
463
  const modelText = fields.model !== undefined ? JSON.stringify(fields.model) : null;
387
- const baseUrlText = fields.baseUrl !== undefined ? JSON.stringify(fields.baseUrl) : null;
464
+ const modelProviderText = fields.modelProvider !== undefined ? JSON.stringify(fields.modelProvider) : null;
388
465
  const inserts = [];
389
466
  if (modelText !== null && section.modelValueRange) {
390
467
  if (section.model !== fields.model) {
@@ -401,19 +478,19 @@ function planSectionFieldMutation(document, section, fields, operations) {
401
478
  inserts.push(`model = ${modelText}${document.lineEnding}`);
402
479
  updated = true;
403
480
  }
404
- if (baseUrlText !== null && section.baseUrlValueRange) {
405
- if (section.baseUrl !== fields.baseUrl) {
481
+ if (modelProviderText !== null && section.modelProviderValueRange) {
482
+ if (section.modelProvider !== fields.modelProvider) {
406
483
  operations.push({
407
484
  kind: "replace-range",
408
- start: section.baseUrlValueRange.start,
409
- end: section.baseUrlValueRange.end,
410
- text: baseUrlText,
485
+ start: section.modelProviderValueRange.start,
486
+ end: section.modelProviderValueRange.end,
487
+ text: modelProviderText,
411
488
  });
412
489
  updated = true;
413
490
  }
414
491
  }
415
- else if (baseUrlText !== null && !section.baseUrlValueRange) {
416
- inserts.push(`base_url = ${baseUrlText}${document.lineEnding}`);
492
+ else if (modelProviderText !== null && !section.modelProviderValueRange) {
493
+ inserts.push(`model_provider = ${modelProviderText}${document.lineEnding}`);
417
494
  updated = true;
418
495
  }
419
496
  if (inserts.length > 0) {
@@ -483,13 +560,13 @@ function findManagedFieldInsertIndex(rawText, sectionStart, sectionEnd) {
483
560
  }
484
561
  return sectionStart + lines[lastMeaningfulIndex].end;
485
562
  }
486
- function collectManagedFields(model, baseUrl) {
563
+ function collectManagedFields(model, modelProvider) {
487
564
  const fields = [];
488
565
  if (model !== null) {
489
566
  fields.push("model");
490
567
  }
491
- if (baseUrl !== null) {
492
- fields.push("base_url");
568
+ if (modelProvider !== null) {
569
+ fields.push("model_provider");
493
570
  }
494
571
  return fields;
495
572
  }