@minniexcode/codex-switch 0.0.4 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/help.js CHANGED
@@ -9,6 +9,28 @@ const GROUP_TITLES = {
9
9
  recovery: "Diagnostics And Recovery",
10
10
  };
11
11
  const COMMANDS = [
12
+ {
13
+ name: "config-show",
14
+ group: "read",
15
+ summary: "Show the structured config profile view.",
16
+ usage: ["codexs config show [profile] [--json] [--codex-dir <path>]"],
17
+ details: [
18
+ "Returns all recognizable config profiles by default, including unmanaged and orphaned references.",
19
+ "Passing [profile] narrows the response to one profile while preserving the same shape.",
20
+ ],
21
+ examples: ["codexs config show", "codexs config show packycode --json"],
22
+ },
23
+ {
24
+ name: "config-list-profiles",
25
+ group: "read",
26
+ summary: "List recognizable config profiles with managed-state hints.",
27
+ usage: ["codexs config list-profiles [--json] [--codex-dir <path>]"],
28
+ details: [
29
+ "Lists managed, unmanaged, and orphaned config profiles in one stable view.",
30
+ "Use config show for richer single-profile details.",
31
+ ],
32
+ examples: ["codexs config list-profiles", "codexs config list-profiles --json"],
33
+ },
12
34
  {
13
35
  name: "setup",
14
36
  group: "write",
@@ -62,6 +84,7 @@ const COMMANDS = [
62
84
  usage: ["codexs status [--json] [--codex-dir <path>]"],
63
85
  details: [
64
86
  "Reports file presence, current profile, and whether the live profile is mapped.",
87
+ "Surfaces config consistency signals without mutating any files.",
65
88
  "Use doctor for deeper diagnostics.",
66
89
  ],
67
90
  examples: ["codexs status", "codexs status --json --codex-dir ./.tmp-codex"],
@@ -72,12 +95,14 @@ const COMMANDS = [
72
95
  summary: "Update fields on a single provider record.",
73
96
  usage: [
74
97
  "codexs edit <provider> [--profile <name>] [--api-key <key>] [--base-url <url>] [--note <text>] [--tag <tag> ...] [--json] [--codex-dir <path>]",
98
+ "codexs edit <provider> --profile <name> --create-profile --model <name> --base-url <url>",
75
99
  ],
76
100
  details: [
77
101
  "Passed flags replace only the selected fields and keep the rest unchanged.",
78
102
  "TTY mode can first select a provider, then prompt for fields when no editable options were provided.",
79
103
  "Interactive tags use preset multi-select plus optional custom comma-separated input.",
80
- "Backs up providers.json before writing.",
104
+ "When rebinding to a missing profile, --create-profile requires both --model and --base-url.",
105
+ "Backs up providers.json and config.toml before writing.",
81
106
  ],
82
107
  examples: ["codexs edit packycode --note primary", "codexs edit packycode --tag daily --tag paid --json"],
83
108
  },
@@ -87,6 +112,7 @@ const COMMANDS = [
87
112
  summary: "Add a provider with explicit flags or progressive TTY prompts.",
88
113
  usage: [
89
114
  "codexs add <provider> --profile <name> --api-key <key> [--base-url <url>] [--note <text>] [--tag <tag> ...]",
115
+ "codexs add <provider> --profile <name> --api-key <key> --create-profile --model <name> --base-url <url>",
90
116
  "codexs add [--profile <name>] [--api-key <key>] [--base-url <url>] [--note <text>] [--tag <tag> ...]",
91
117
  ],
92
118
  details: [
@@ -95,6 +121,7 @@ const COMMANDS = [
95
121
  "Confirm API key when prompted interactively because the hidden prompt asks twice before writing.",
96
122
  "Interactive tags use preset multi-select plus optional custom comma-separated input.",
97
123
  "Automation and non-TTY environments must pass all required values explicitly.",
124
+ "Creating a missing profile section requires --create-profile together with --model and --base-url.",
98
125
  ],
99
126
  examples: [
100
127
  "codexs add packycode --profile packycode --api-key sk-xxx",
@@ -119,12 +146,13 @@ const COMMANDS = [
119
146
  name: "remove",
120
147
  group: "write",
121
148
  summary: "Remove a provider from providers.json.",
122
- usage: ["codexs remove <provider> [--force] [--json] [--codex-dir <path>]"],
149
+ usage: ["codexs remove <provider> [--force] [--switch-to <profile>] [--json] [--codex-dir <path>]"],
123
150
  details: [
124
151
  "TTY mode can select a missing provider interactively and always asks for deletion confirmation.",
125
152
  "Non-TTY and --json automation still require both <provider> and --force.",
126
153
  "The confirmation prompt includes the provider name and cancels without writing when declined.",
127
- "Backs up providers.json before removing the record.",
154
+ "When removing the last provider linked to the active profile, pass --switch-to first.",
155
+ "Backs up providers.json and config.toml before removing the record.",
128
156
  ],
129
157
  examples: ["codexs remove freemodel", "codexs remove freemodel --force --json"],
130
158
  },
@@ -195,7 +223,15 @@ function isKnownCommandName(commandName) {
195
223
  return COMMAND_NAME_SET.has(commandName) || commandName === "backups-list";
196
224
  }
197
225
  function buildHelpText(commandName) {
198
- const normalizedCommandName = commandName === "backups-list" ? "backups" : commandName;
226
+ const normalizedCommandName = commandName === "backups-list"
227
+ ? "backups"
228
+ : commandName === "config-show"
229
+ ? "config-show"
230
+ : commandName === "config-list-profiles"
231
+ ? "config-list-profiles"
232
+ : commandName === "config"
233
+ ? "config-show"
234
+ : commandName;
199
235
  if (!commandName) {
200
236
  return [
201
237
  "codex-switch",
@@ -216,7 +252,7 @@ function buildHelpText(commandName) {
216
252
  "",
217
253
  "Environment:",
218
254
  " CODEXS_CODEX_DIR Default Codex directory when --codex-dir is not passed.",
219
- " NODE_ENV=development defaults to ./test-fixtures/sample-codex when no override is set.",
255
+ " NODE_ENV=development defaults to ./dev-codex/local-sandbox when no override is set.",
220
256
  "",
221
257
  "Interactive rules:",
222
258
  " Progressive prompts only run in a real TTY and never run under --json.",
@@ -234,6 +270,7 @@ function buildHelpText(commandName) {
234
270
  " codexs list",
235
271
  " codexs switch",
236
272
  " codexs add packycode --profile packycode --api-key sk-xxx",
273
+ " codexs config show",
237
274
  " codexs remove freemodel",
238
275
  " codexs backups list",
239
276
  " codexs rollback",
@@ -43,12 +43,16 @@ exports.getRollbackSummary = getRollbackSummary;
43
43
  exports.getRollbackSummaryById = getRollbackSummaryById;
44
44
  exports.confirmRollback = confirmRollback;
45
45
  exports.chooseSetupStrategy = chooseSetupStrategy;
46
+ exports.chooseCodexDir = chooseCodexDir;
47
+ exports.chooseSetupProfiles = chooseSetupProfiles;
46
48
  exports.collectSetupProviderDetails = collectSetupProviderDetails;
49
+ exports.collectImportRepairDetails = collectImportRepairDetails;
47
50
  exports.collectEditInput = collectEditInput;
48
51
  const fs = __importStar(require("node:fs"));
49
52
  const path = __importStar(require("node:path"));
50
53
  const errors_1 = require("../domain/errors");
51
54
  const backups_1 = require("../domain/backups");
55
+ const codex_paths_1 = require("../infra/codex-paths");
52
56
  const providers_repo_1 = require("../infra/providers-repo");
53
57
  const backup_repo_1 = require("../infra/backup-repo");
54
58
  const add_interactive_1 = require("./add-interactive");
@@ -137,6 +141,46 @@ async function chooseSetupStrategy(runtime) {
137
141
  { value: "cancel", label: "cancel", hint: "abort setup without writing" },
138
142
  ]);
139
143
  }
144
+ async function chooseCodexDir(runtime, candidates) {
145
+ if (candidates.length === 0) {
146
+ const manual = (await runtime.inputText("Codex directory path")).trim();
147
+ if (!manual) {
148
+ throw (0, errors_1.cliError)("CODEX_DIR_NOT_FOUND", "No Codex directory was provided.");
149
+ }
150
+ return (0, codex_paths_1.resolveCodexDir)(manual);
151
+ }
152
+ if (candidates.length === 1) {
153
+ return candidates[0];
154
+ }
155
+ const selected = await runtime.selectOne("Choose a Codex directory", [
156
+ ...candidates.map((candidate) => ({
157
+ value: candidate,
158
+ label: candidate,
159
+ })),
160
+ {
161
+ value: "__manual__",
162
+ label: "Enter manually",
163
+ },
164
+ ]);
165
+ if (selected !== "__manual__") {
166
+ return selected;
167
+ }
168
+ const manual = (await runtime.inputText("Codex directory path")).trim();
169
+ if (!manual) {
170
+ throw (0, errors_1.cliError)("CODEX_DIR_NOT_FOUND", "No Codex directory was provided.");
171
+ }
172
+ return (0, codex_paths_1.resolveCodexDir)(manual);
173
+ }
174
+ async function chooseSetupProfiles(runtime, profiles) {
175
+ if (profiles.length === 0) {
176
+ return [];
177
+ }
178
+ return runtime.selectMany("Choose unmanaged config profiles to adopt into providers.json.", profiles.map((profile) => ({
179
+ value: profile.name,
180
+ label: profile.name,
181
+ hint: `${profile.model} | ${profile.baseUrl}`,
182
+ })));
183
+ }
140
184
  async function collectSetupProviderDetails(runtime, profiles) {
141
185
  const result = {};
142
186
  for (const profile of profiles) {
@@ -157,6 +201,18 @@ async function collectSetupProviderDetails(runtime, profiles) {
157
201
  }
158
202
  return result;
159
203
  }
204
+ async function collectImportRepairDetails(runtime, profiles) {
205
+ const repairs = {};
206
+ for (const profile of profiles) {
207
+ const model = (await runtime.inputText(`Model for missing profile "${profile}"`)).trim();
208
+ const baseUrl = (await runtime.inputText(`Base URL for missing profile "${profile}"`)).trim();
209
+ repairs[profile] = {
210
+ model: model || undefined,
211
+ baseUrl: baseUrl || undefined,
212
+ };
213
+ }
214
+ return repairs;
215
+ }
160
216
  async function collectEditInput(runtime, current) {
161
217
  const profile = (await runtime.inputText("Profile", { defaultValue: current.profile })).trim();
162
218
  const apiKey = (await runtime.inputSecret("API key")).trim() || current.apiKey;
@@ -120,7 +120,23 @@ function renderHumanSuccess(command, data, warnings) {
120
120
  lines.push(`providersExists: ${String(data?.providersExists ?? false)}`);
121
121
  lines.push(`currentProfile: ${String(data?.currentProfile ?? "")}`);
122
122
  lines.push(`mappedProvider: ${String(data?.provider ?? "")}`);
123
+ lines.push(`issues: ${Array.isArray(data?.issues) ? (data?.issues).length : 0}`);
123
124
  break;
125
+ case "config-show": {
126
+ lines.push(`activeProfile: ${String(data?.activeProfile ?? "")}`);
127
+ const profiles = data?.profiles ?? [];
128
+ for (const profile of profiles) {
129
+ lines.push(`${String(profile.name)} managed=${String(profile.managed)} active=${String(profile.isActive)} source=${String(profile.source)} model=${String(profile.model ?? "")} baseUrl=${String(profile.baseUrl ?? "")}`);
130
+ }
131
+ break;
132
+ }
133
+ case "config-list-profiles": {
134
+ const profiles = data?.profiles ?? [];
135
+ for (const profile of profiles) {
136
+ lines.push(`${String(profile.name)} managed=${String(profile.managed)} active=${String(profile.isActive)} source=${String(profile.source)}`);
137
+ }
138
+ break;
139
+ }
124
140
  case "switch":
125
141
  lines.push(`Switched to provider ${String(data?.provider ?? "")} using profile ${String(data?.profile ?? "")}.`);
126
142
  lines.push(`Backup: ${String(data?.backupPath ?? "")}`);
@@ -144,9 +160,15 @@ function renderHumanSuccess(command, data, warnings) {
144
160
  break;
145
161
  case "add":
146
162
  lines.push(`Added provider ${String(data?.provider ?? "")}. Backup: ${String(data?.backupPath ?? "")}`);
163
+ if (Array.isArray(data?.createdProfileSections) && (data?.createdProfileSections).length > 0) {
164
+ lines.push(`Created profiles: ${(data?.createdProfileSections).join(", ")}`);
165
+ }
147
166
  break;
148
167
  case "remove":
149
168
  lines.push(`Removed provider ${String(data?.provider ?? "")}. Backup: ${String(data?.backupPath ?? "")}`);
169
+ if (Array.isArray(data?.deletedProfileSections) && (data?.deletedProfileSections).length > 0) {
170
+ lines.push(`Deleted profiles: ${(data?.deletedProfileSections).join(", ")}`);
171
+ }
150
172
  break;
151
173
  case "doctor": {
152
174
  const healthy = Boolean(data?.healthy);
package/dist/cli.js CHANGED
@@ -44,16 +44,21 @@ const export_providers_1 = require("./app/export-providers");
44
44
  const get_current_profile_1 = require("./app/get-current-profile");
45
45
  const get_status_1 = require("./app/get-status");
46
46
  const import_providers_1 = require("./app/import-providers");
47
+ const list_config_profiles_1 = require("./app/list-config-profiles");
47
48
  const list_backups_1 = require("./app/list-backups");
48
49
  const list_providers_1 = require("./app/list-providers");
49
50
  const remove_provider_1 = require("./app/remove-provider");
50
51
  const rollback_backup_1 = require("./app/rollback-backup");
51
52
  const run_doctor_1 = require("./app/run-doctor");
52
53
  const setup_codex_1 = require("./app/setup-codex");
54
+ const show_config_1 = require("./app/show-config");
53
55
  const show_provider_1 = require("./app/show-provider");
54
56
  const switch_provider_1 = require("./app/switch-provider");
57
+ const config_1 = require("./domain/config");
55
58
  const errors_1 = require("./domain/errors");
59
+ const providers_1 = require("./domain/providers");
56
60
  const config_repo_1 = require("./infra/config-repo");
61
+ const config_repo_2 = require("./infra/config-repo");
57
62
  const providers_repo_1 = require("./infra/providers-repo");
58
63
  const codex_paths_1 = require("./infra/codex-paths");
59
64
  const args_1 = require("./cli/args");
@@ -62,7 +67,7 @@ const help_1 = require("./cli/help");
62
67
  const interactive_1 = require("./cli/interactive");
63
68
  const output_1 = require("./cli/output");
64
69
  const prompt_1 = require("./cli/prompt");
65
- const VERSION = "0.0.4";
70
+ const VERSION = "0.0.5";
66
71
  /**
67
72
  * Prints the command help text to stdout.
68
73
  */
@@ -114,7 +119,8 @@ function main() {
114
119
  * Dispatches a parsed CLI command into the application layer.
115
120
  */
116
121
  async function executeCommand(ctx, parsed, runtime = (0, prompt_1.createPromptRuntime)()) {
117
- const paths = (0, codex_paths_1.createCodexPaths)(ctx.options.codexDir);
122
+ let setupPaths = (0, codex_paths_1.createCodexPaths)(ctx.options.codexDir);
123
+ const paths = setupPaths;
118
124
  switch (ctx.command) {
119
125
  case "list":
120
126
  return (0, list_providers_1.listProviders)(paths.providersPath);
@@ -136,6 +142,17 @@ async function executeCommand(ctx, parsed, runtime = (0, prompt_1.createPromptRu
136
142
  return (0, get_current_profile_1.getCurrentProfile)(paths.configPath);
137
143
  case "status":
138
144
  return (0, get_status_1.getStatus)(paths.codexDir, paths.configPath, paths.providersPath);
145
+ case "config-show":
146
+ return (0, show_config_1.showConfig)({
147
+ configPath: paths.configPath,
148
+ providersPath: paths.providersPath,
149
+ profileName: parsed.positionals[0] ?? null,
150
+ });
151
+ case "config-list-profiles":
152
+ return (0, list_config_profiles_1.listConfigProfilesView)({
153
+ configPath: paths.configPath,
154
+ providersPath: paths.providersPath,
155
+ });
139
156
  case "switch": {
140
157
  let providerName = parsed.positionals[0] ?? null;
141
158
  if (!providerName && (0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
@@ -161,16 +178,30 @@ async function executeCommand(ctx, parsed, runtime = (0, prompt_1.createPromptRu
161
178
  throw (0, errors_1.cliError)("INVALID_ARGUMENT", "Missing import file path.");
162
179
  }
163
180
  const merge = (0, args_1.hasFlag)(parsed.commandOptions, "--merge");
181
+ let repairProfiles;
164
182
  if ((0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
165
183
  await (0, interactive_1.confirmImport)(runtime, sourceFile, merge);
184
+ const document = (0, config_repo_2.readStructuredConfig)(paths.configPath);
185
+ const imported = (0, providers_1.validateProvidersShape)(JSON.parse(fs.readFileSync(sourceFile, "utf8")));
186
+ const current = (0, providers_repo_1.readProvidersFileIfExists)(paths.providersPath);
187
+ const next = merge ? (0, providers_repo_1.mergeProviders)(current, imported) : imported;
188
+ const missingProfiles = (0, config_1.buildManagedProfileViews)(document, next)
189
+ .filter((view) => view.source === "orphaned-reference")
190
+ .map((view) => view.name)
191
+ .sort();
192
+ if (missingProfiles.length > 0) {
193
+ repairProfiles = await (0, interactive_1.collectImportRepairDetails)(runtime, missingProfiles);
194
+ }
166
195
  }
167
196
  return (0, import_providers_1.importProviders)({
168
197
  codexDir: paths.codexDir,
169
198
  backupsDir: paths.backupsDir,
170
199
  latestBackupPath: paths.latestBackupPath,
171
200
  providersPath: paths.providersPath,
201
+ configPath: paths.configPath,
172
202
  sourceFile,
173
203
  merge,
204
+ repairProfiles,
174
205
  });
175
206
  }
176
207
  case "export": {
@@ -197,8 +228,10 @@ async function executeCommand(ctx, parsed, runtime = (0, prompt_1.createPromptRu
197
228
  let profile = (0, args_1.getSingleOption)(parsed.commandOptions, "--profile");
198
229
  let apiKey = (0, args_1.getSingleOption)(parsed.commandOptions, "--api-key");
199
230
  let baseUrl = (0, args_1.getSingleOption)(parsed.commandOptions, "--base-url", false);
231
+ let model = (0, args_1.getSingleOption)(parsed.commandOptions, "--model", false);
200
232
  let note = (0, args_1.getSingleOption)(parsed.commandOptions, "--note", false);
201
233
  let tags = parsed.commandOptions.get("--tag") ?? [];
234
+ const createProfile = (0, args_1.hasFlag)(parsed.commandOptions, "--create-profile");
202
235
  if (!providerName || !profile || !apiKey) {
203
236
  if (ctx.options.json || !runtime.isInteractive()) {
204
237
  throw (0, add_interactive_1.createNonInteractiveAddError)();
@@ -223,12 +256,15 @@ async function executeCommand(ctx, parsed, runtime = (0, prompt_1.createPromptRu
223
256
  backupsDir: paths.backupsDir,
224
257
  latestBackupPath: paths.latestBackupPath,
225
258
  providersPath: paths.providersPath,
259
+ configPath: paths.configPath,
226
260
  providerName,
227
261
  profile,
228
262
  apiKey,
229
263
  baseUrl,
264
+ model,
230
265
  note,
231
266
  tags,
267
+ createProfile,
232
268
  });
233
269
  }
234
270
  case "edit": {
@@ -242,13 +278,17 @@ async function executeCommand(ctx, parsed, runtime = (0, prompt_1.createPromptRu
242
278
  let profile = (0, args_1.getSingleOption)(parsed.commandOptions, "--profile", false) ?? undefined;
243
279
  let apiKey = (0, args_1.getSingleOption)(parsed.commandOptions, "--api-key", false) ?? undefined;
244
280
  let baseUrl = (0, args_1.getSingleOption)(parsed.commandOptions, "--base-url", false) ?? undefined;
281
+ let model = (0, args_1.getSingleOption)(parsed.commandOptions, "--model", false) ?? undefined;
245
282
  let note = (0, args_1.getSingleOption)(parsed.commandOptions, "--note", false) ?? undefined;
246
283
  let tags = parsed.commandOptions.has("--tag")
247
284
  ? parsed.commandOptions.get("--tag") ?? []
248
285
  : undefined;
286
+ const createProfile = (0, args_1.hasFlag)(parsed.commandOptions, "--create-profile");
287
+ const switchToProfile = (0, args_1.getSingleOption)(parsed.commandOptions, "--switch-to", false) ?? undefined;
249
288
  if (profile === undefined &&
250
289
  apiKey === undefined &&
251
290
  baseUrl === undefined &&
291
+ model === undefined &&
252
292
  note === undefined &&
253
293
  tags === undefined &&
254
294
  (0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
@@ -263,7 +303,12 @@ async function executeCommand(ctx, parsed, runtime = (0, prompt_1.createPromptRu
263
303
  note = prompted.note;
264
304
  tags = prompted.tags;
265
305
  }
266
- if (profile === undefined && apiKey === undefined && baseUrl === undefined && note === undefined && tags === undefined) {
306
+ if (profile === undefined &&
307
+ apiKey === undefined &&
308
+ baseUrl === undefined &&
309
+ model === undefined &&
310
+ note === undefined &&
311
+ tags === undefined) {
267
312
  throw (0, errors_1.cliError)("INVALID_ARGUMENT", "edit requires at least one field to update.");
268
313
  }
269
314
  return (0, edit_provider_1.editProvider)({
@@ -271,17 +316,22 @@ async function executeCommand(ctx, parsed, runtime = (0, prompt_1.createPromptRu
271
316
  backupsDir: paths.backupsDir,
272
317
  latestBackupPath: paths.latestBackupPath,
273
318
  providersPath: paths.providersPath,
319
+ configPath: paths.configPath,
274
320
  providerName,
275
321
  profile,
276
322
  apiKey,
277
323
  baseUrl,
324
+ model,
278
325
  note,
279
326
  tags,
327
+ createProfile,
328
+ switchToProfile,
280
329
  });
281
330
  }
282
331
  case "remove": {
283
332
  let providerName = parsed.positionals[0] ?? null;
284
333
  const force = (0, args_1.hasFlag)(parsed.commandOptions, "--force");
334
+ const switchToProfile = (0, args_1.getSingleOption)(parsed.commandOptions, "--switch-to", false) ?? undefined;
285
335
  if (!providerName && (0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
286
336
  providerName = await (0, interactive_1.promptForProviderSelection)(runtime, paths.providersPath, "Choose a provider to remove");
287
337
  }
@@ -299,7 +349,9 @@ async function executeCommand(ctx, parsed, runtime = (0, prompt_1.createPromptRu
299
349
  backupsDir: paths.backupsDir,
300
350
  latestBackupPath: paths.latestBackupPath,
301
351
  providersPath: paths.providersPath,
352
+ configPath: paths.configPath,
302
353
  providerName,
354
+ switchToProfile,
303
355
  });
304
356
  }
305
357
  case "doctor":
@@ -309,17 +361,39 @@ async function executeCommand(ctx, parsed, runtime = (0, prompt_1.createPromptRu
309
361
  providersPath: paths.providersPath,
310
362
  });
311
363
  case "setup": {
364
+ let codexDir = ctx.options.codexDir;
365
+ const candidates = (0, config_repo_1.findCodexDirCandidates)(ctx.options.codexDirExplicit ? ctx.options.codexDir : null);
366
+ if (!ctx.options.codexDirExplicit) {
367
+ if (candidates.length > 1) {
368
+ if (!(0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
369
+ throw (0, errors_1.cliError)("CODEX_DIR_AMBIGUOUS", "Multiple Codex directories were found.", {
370
+ candidates,
371
+ });
372
+ }
373
+ codexDir = await (0, interactive_1.chooseCodexDir)(runtime, candidates);
374
+ }
375
+ else if (candidates.length === 0) {
376
+ if (!(0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
377
+ throw (0, errors_1.cliError)("CODEX_DIR_NOT_FOUND", "No Codex directory could be found.");
378
+ }
379
+ codexDir = await (0, interactive_1.chooseCodexDir)(runtime, candidates);
380
+ }
381
+ else {
382
+ codexDir = candidates[0];
383
+ }
384
+ }
385
+ setupPaths = (0, codex_paths_1.createCodexPaths)(codexDir);
312
386
  const overwrite = (0, args_1.hasFlag)(parsed.commandOptions, "--overwrite");
313
387
  const merge = (0, args_1.hasFlag)(parsed.commandOptions, "--merge");
314
388
  if (overwrite && merge) {
315
389
  throw (0, errors_1.cliError)("INVALID_ARGUMENT", "setup does not allow both --merge and --overwrite.");
316
390
  }
317
391
  let strategy = overwrite ? "overwrite" : merge ? "merge" : null;
318
- const providersExists = fs.existsSync(paths.providersPath);
392
+ const providersExists = fs.existsSync(setupPaths.providersPath);
319
393
  if (providersExists && strategy === null) {
320
394
  if (!(0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
321
395
  throw (0, errors_1.cliError)("PROVIDERS_ALREADY_EXISTS", "providers.json already exists. Pass --merge or --overwrite.", {
322
- file: paths.providersPath,
396
+ file: setupPaths.providersPath,
323
397
  });
324
398
  }
325
399
  const selected = await (0, interactive_1.chooseSetupStrategy)(runtime);
@@ -328,19 +402,34 @@ async function executeCommand(ctx, parsed, runtime = (0, prompt_1.createPromptRu
328
402
  }
329
403
  strategy = selected;
330
404
  }
331
- const profiles = Array.from((0, config_repo_1.listConfigProfiles)(paths.configPath)).sort();
405
+ const document = (0, config_repo_2.readStructuredConfig)(setupPaths.configPath);
406
+ const adoptableProfiles = (0, config_1.buildManagedProfileViews)(document, null)
407
+ .filter((view) => view.source === "unmanaged" && view.model && view.baseUrl)
408
+ .map((view) => ({
409
+ name: view.name,
410
+ model: view.model,
411
+ baseUrl: view.baseUrl,
412
+ }))
413
+ .sort((left, right) => left.name.localeCompare(right.name));
414
+ const selectedProfiles = Array.from((0, config_repo_1.listConfigProfiles)(setupPaths.configPath)).sort();
415
+ let adoptProfiles = [];
332
416
  let providerDetailsByProfile = {};
333
417
  if ((0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
334
- providerDetailsByProfile = await (0, interactive_1.collectSetupProviderDetails)(runtime, profiles);
418
+ adoptProfiles = await (0, interactive_1.chooseSetupProfiles)(runtime, adoptableProfiles);
419
+ providerDetailsByProfile = await (0, interactive_1.collectSetupProviderDetails)(runtime, adoptProfiles);
420
+ }
421
+ else {
422
+ adoptProfiles = selectedProfiles.filter((profile) => Object.prototype.hasOwnProperty.call(providerDetailsByProfile, profile));
335
423
  }
336
424
  return (0, setup_codex_1.setupCodex)({
337
425
  codexDirOption: ctx.options.codexDir,
338
- codexDir: paths.codexDir,
339
- configPath: paths.configPath,
340
- providersPath: paths.providersPath,
341
- backupsDir: paths.backupsDir,
342
- latestBackupPath: paths.latestBackupPath,
426
+ codexDir: setupPaths.codexDir,
427
+ configPath: setupPaths.configPath,
428
+ providersPath: setupPaths.providersPath,
429
+ backupsDir: setupPaths.backupsDir,
430
+ latestBackupPath: setupPaths.latestBackupPath,
343
431
  strategy: strategy ?? "overwrite",
432
+ adoptProfiles,
344
433
  providerDetailsByProfile,
345
434
  });
346
435
  }