@minniexcode/codex-switch 0.0.6 → 0.0.7

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 (43) hide show
  1. package/README.AI.md +5 -2
  2. package/README.md +12 -6
  3. package/dist/app/add-provider.js +21 -3
  4. package/dist/app/edit-provider.js +39 -11
  5. package/dist/app/get-status.js +8 -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 +60 -0
  9. package/dist/app/setup-codex.js +17 -8
  10. package/dist/app/show-config.js +9 -1
  11. package/dist/app/switch-provider.js +14 -7
  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 +80 -11
  20. package/dist/commands/help.js +2 -1
  21. package/dist/commands/registry.js +73 -13
  22. package/dist/domain/config.js +137 -0
  23. package/dist/domain/providers.js +16 -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/storage/auth-repo.js +160 -0
  35. package/dist/storage/config-repo.js +58 -0
  36. package/docs/Design/codex-switch-v0.0.7-design.md +862 -0
  37. package/docs/PRD/codex-switch-prd-v0.0.5-to-v0.1.0.md +131 -25
  38. package/docs/Reference/codex-config-reference.md +604 -0
  39. package/docs/Reference/codex-config-reference.zh-CN.md +633 -0
  40. package/docs/cli-usage.md +77 -29
  41. package/docs/test-report-0.0.7.md +118 -0
  42. package/docs/testing.md +67 -47
  43. package/package.json +1 -1
@@ -1,9 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.promptTags = exports.parseTags = exports.createNonInteractiveAddError = exports.COMMON_TAG_CHOICES = exports.collectAddInput = void 0;
3
+ exports.promptTags = exports.createNonInteractiveAddError = exports.COMMON_TAG_CHOICES = exports.collectAddInput = void 0;
4
+ /**
5
+ * Compatibility facade that re-exports interactive add helpers for older imports.
6
+ */
4
7
  var add_interactive_1 = require("../interaction/add-interactive");
5
8
  Object.defineProperty(exports, "collectAddInput", { enumerable: true, get: function () { return add_interactive_1.collectAddInput; } });
6
9
  Object.defineProperty(exports, "COMMON_TAG_CHOICES", { enumerable: true, get: function () { return add_interactive_1.COMMON_TAG_CHOICES; } });
7
10
  Object.defineProperty(exports, "createNonInteractiveAddError", { enumerable: true, get: function () { return add_interactive_1.createNonInteractiveAddError; } });
8
- Object.defineProperty(exports, "parseTags", { enumerable: true, get: function () { return add_interactive_1.parseTags; } });
9
11
  Object.defineProperty(exports, "promptTags", { enumerable: true, get: function () { return add_interactive_1.promptTags; } });
package/dist/cli/args.js CHANGED
@@ -1,6 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseArgs = exports.hasFlag = exports.getSingleOption = void 0;
4
+ /**
5
+ * Compatibility facade that re-exports shared CLI argument parsing helpers.
6
+ */
4
7
  var args_1 = require("../commands/args");
5
8
  Object.defineProperty(exports, "getSingleOption", { enumerable: true, get: function () { return args_1.getSingleOption; } });
6
9
  Object.defineProperty(exports, "hasFlag", { enumerable: true, get: function () { return args_1.hasFlag; } });
package/dist/cli/help.js CHANGED
@@ -1,6 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.isKnownCommandName = exports.getKnownCommandNames = exports.buildHelpText = void 0;
4
+ /**
5
+ * Compatibility facade that re-exports CLI help helpers from the commands layer.
6
+ */
4
7
  var help_1 = require("../commands/help");
5
8
  Object.defineProperty(exports, "buildHelpText", { enumerable: true, get: function () { return help_1.buildHelpText; } });
6
9
  Object.defineProperty(exports, "getKnownCommandNames", { enumerable: true, get: function () { return help_1.getKnownCommandNames; } });
@@ -1,6 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.promptForProviderSelection = exports.getRollbackSummaryById = exports.getRollbackSummary = exports.exportTargetExists = exports.confirmRollback = exports.confirmProviderRemoval = exports.confirmImport = exports.confirmExportOverwrite = exports.collectSetupProviderDetails = exports.collectEditInput = exports.chooseSetupStrategy = exports.chooseSetupProfiles = exports.chooseCodexDir = exports.canPrompt = void 0;
4
+ /**
5
+ * Compatibility facade that re-exports interactive command helpers.
6
+ */
4
7
  var interactive_1 = require("../interaction/interactive");
5
8
  Object.defineProperty(exports, "canPrompt", { enumerable: true, get: function () { return interactive_1.canPrompt; } });
6
9
  Object.defineProperty(exports, "chooseCodexDir", { enumerable: true, get: function () { return interactive_1.chooseCodexDir; } });
@@ -90,7 +90,8 @@ function renderHumanSuccess(command, data, warnings) {
90
90
  ? ` tags=${provider.tags.join(",")}`
91
91
  : "";
92
92
  const note = provider.note ? ` note=${provider.note}` : "";
93
- lines.push(`${provider.name} -> ${provider.profile}${tags}${note}`);
93
+ const envKey = provider.envKey ? ` envKey=${provider.envKey}` : "";
94
+ lines.push(`${provider.name} -> ${provider.profile}${envKey}${tags}${note}`);
94
95
  }
95
96
  }
96
97
  break;
@@ -100,6 +101,7 @@ function renderHumanSuccess(command, data, warnings) {
100
101
  lines.push(`Provider: ${String(data?.providerName ?? "")}`);
101
102
  lines.push(`profile: ${String(provider.profile ?? "")}`);
102
103
  lines.push(`apiKey: ${String(provider.apiKey ?? "")}`);
104
+ lines.push(`envKey: ${String(provider.envKey ?? "")}`);
103
105
  if (provider.baseUrl) {
104
106
  lines.push(`baseUrl: ${String(provider.baseUrl)}`);
105
107
  }
@@ -120,13 +122,17 @@ function renderHumanSuccess(command, data, warnings) {
120
122
  lines.push(`providersExists: ${String(data?.providersExists ?? false)}`);
121
123
  lines.push(`currentProfile: ${String(data?.currentProfile ?? "")}`);
122
124
  lines.push(`mappedProvider: ${String(data?.provider ?? "")}`);
125
+ lines.push(`activeProviderResolvable: ${String(data?.activeProviderResolvable ?? false)}`);
126
+ const auth = data?.auth ?? {};
127
+ lines.push(`authExists: ${String(auth.exists ?? false)}`);
128
+ lines.push(`authManagedKeys: ${Array.isArray(auth.managedSecretKeys) ? auth.managedSecretKeys.join(",") : ""}`);
123
129
  lines.push(`issues: ${Array.isArray(data?.issues) ? (data?.issues).length : 0}`);
124
130
  break;
125
131
  case "config-show": {
126
132
  lines.push(`activeProfile: ${String(data?.activeProfile ?? "")}`);
127
133
  const profiles = data?.profiles ?? [];
128
134
  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 ?? "")} modelProvider=${String(profile.modelProvider ?? "")} baseUrl=${String(profile.baseUrl ?? "")}`);
135
+ lines.push(`${String(profile.name)} managed=${String(profile.managed)} active=${String(profile.isActive)} source=${String(profile.source)} model=${String(profile.model ?? "")} modelProvider=${String(profile.modelProvider ?? "")} baseUrl=${String(profile.baseUrl ?? "")} envKey=${String(profile.envKey ?? "")}`);
130
136
  }
131
137
  break;
132
138
  }
@@ -139,8 +145,8 @@ function renderHumanSuccess(command, data, warnings) {
139
145
  }
140
146
  case "switch":
141
147
  lines.push(`Switched to provider ${String(data?.provider ?? "")} using profile ${String(data?.profile ?? "")}.`);
148
+ lines.push(`envKey: ${String(data?.envKey ?? "")}`);
142
149
  lines.push(`Backup: ${String(data?.backupPath ?? "")}`);
143
- lines.push(`Login performed: ${String(data?.loginPerformed ?? false)}`);
144
150
  break;
145
151
  case "import":
146
152
  lines.push(`Imported providers from file using mode ${String(data?.mode ?? "replace")}. Backup: ${String(data?.backupPath ?? "")}`);
@@ -148,12 +154,21 @@ function renderHumanSuccess(command, data, warnings) {
148
154
  case "export":
149
155
  lines.push(`Exported providers to ${String(data?.exportedTo ?? "")}.`);
150
156
  break;
151
- case "setup":
152
- lines.push(`Initialized providers in ${String(data?.codexDir ?? "")} using ${String(data?.strategy ?? "")}.`);
157
+ case "init":
158
+ lines.push(`Initialized Codex directory ${String(data?.codexDir ?? "")}.`);
159
+ lines.push(`Created codexDir: ${String(data?.createdCodexDir ?? false)}`);
160
+ lines.push(`Created providers.json: ${String(data?.createdProvidersFile ?? false)}`);
161
+ lines.push(`providersAlreadyExisted: ${String(data?.providersAlreadyExisted ?? false)}`);
162
+ break;
163
+ case "migrate":
164
+ lines.push(`Migrated providers in ${String(data?.codexDir ?? "")} using ${String(data?.strategy ?? "")}.`);
153
165
  lines.push(`Providers initialized: ${String(data?.providersInitialized ?? 0)}`);
154
166
  lines.push(`Doctor healthy: ${String(data?.doctor?.healthy ?? false)}`);
155
167
  lines.push(`Backup: ${String(data?.backupPath ?? "")}`);
156
168
  break;
169
+ case "setup":
170
+ lines.push("setup is deprecated. Use `codexs init` or `codexs migrate`.");
171
+ break;
157
172
  case "edit":
158
173
  lines.push(`Updated provider ${String(data?.provider ?? "")}. Backup: ${String(data?.backupPath ?? "")}`);
159
174
  lines.push(`Updated fields: ${Array.isArray(data?.updatedFields) ? (data?.updatedFields).join(", ") : ""}`);
@@ -1,5 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createPromptRuntime = void 0;
4
+ /**
5
+ * Compatibility facade that re-exports the CLI prompt runtime types and factory.
6
+ */
4
7
  var prompt_1 = require("../interaction/prompt");
5
8
  Object.defineProperty(exports, "createPromptRuntime", { enumerable: true, get: function () { return prompt_1.createPromptRuntime; } });
package/dist/cli.js CHANGED
@@ -9,7 +9,7 @@ const args_1 = require("./commands/args");
9
9
  const help_1 = require("./commands/help");
10
10
  const errors_1 = require("./domain/errors");
11
11
  const output_1 = require("./cli/output");
12
- const VERSION = "0.0.6";
12
+ const VERSION = "0.0.7";
13
13
  /**
14
14
  * Prints the command help text to stdout.
15
15
  */
@@ -40,6 +40,7 @@ const edit_provider_1 = require("../app/edit-provider");
40
40
  const export_providers_1 = require("../app/export-providers");
41
41
  const get_current_profile_1 = require("../app/get-current-profile");
42
42
  const get_status_1 = require("../app/get-status");
43
+ const init_codex_1 = require("../app/init-codex");
43
44
  const import_providers_1 = require("../app/import-providers");
44
45
  const list_config_profiles_1 = require("../app/list-config-profiles");
45
46
  const list_backups_1 = require("../app/list-backups");
@@ -87,7 +88,54 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
87
88
  case "current":
88
89
  return (0, get_current_profile_1.getCurrentProfile)(paths.configPath);
89
90
  case "status":
90
- return (0, get_status_1.getStatus)(paths.codexDir, paths.configPath, paths.providersPath);
91
+ return (0, get_status_1.getStatus)(paths.codexDir, paths.configPath, paths.providersPath, paths.authPath);
92
+ case "init": {
93
+ let codexDir = ctx.options.codexDir;
94
+ const candidates = (0, config_repo_1.findCodexDirCandidates)(ctx.options.codexDirExplicit ? ctx.options.codexDir : null);
95
+ if (!ctx.options.codexDirExplicit) {
96
+ if (candidates.length > 1) {
97
+ if (!(0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
98
+ throw (0, errors_1.cliError)("CODEX_DIR_AMBIGUOUS", "Multiple Codex directories were found.", {
99
+ candidates,
100
+ });
101
+ }
102
+ codexDir = await (0, interactive_1.chooseCodexDir)(runtime, candidates);
103
+ }
104
+ else if (candidates.length === 0) {
105
+ if (!(0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
106
+ throw (0, errors_1.cliError)("CODEX_DIR_NOT_FOUND", "No Codex directory could be found.", {
107
+ codexDir: ctx.options.codexDir,
108
+ });
109
+ }
110
+ codexDir = await (0, interactive_1.chooseCodexDir)(runtime, candidates);
111
+ }
112
+ else {
113
+ codexDir = candidates[0];
114
+ }
115
+ }
116
+ setupPaths = (0, codex_paths_1.createCodexPaths)(codexDir);
117
+ let createCodexDir = false;
118
+ if (!fs.existsSync(setupPaths.codexDir)) {
119
+ if (!(0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
120
+ throw (0, errors_1.cliError)("CODEX_DIR_NOT_FOUND", "The requested Codex directory does not exist.", {
121
+ codexDir: setupPaths.codexDir,
122
+ });
123
+ }
124
+ createCodexDir = await (0, interactive_1.confirmCreateCodexDir)(runtime, setupPaths.codexDir);
125
+ if (!createCodexDir) {
126
+ throw (0, errors_1.cliError)("CODEX_DIR_NOT_FOUND", "The requested Codex directory does not exist.", {
127
+ codexDir: setupPaths.codexDir,
128
+ });
129
+ }
130
+ }
131
+ return (0, init_codex_1.initCodex)({
132
+ codexDir: setupPaths.codexDir,
133
+ providersPath: setupPaths.providersPath,
134
+ configPath: setupPaths.configPath,
135
+ authPath: setupPaths.authPath,
136
+ createCodexDir,
137
+ });
138
+ }
91
139
  case "config-show":
92
140
  return (0, show_config_1.showConfig)({
93
141
  configPath: paths.configPath,
@@ -115,7 +163,6 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
115
163
  providersPath: paths.providersPath,
116
164
  authPath: paths.authPath,
117
165
  providerName,
118
- noLogin: (0, args_1.hasFlag)(parsed.commandOptions, "--no-login"),
119
166
  });
120
167
  }
121
168
  case "import": {
@@ -130,6 +177,7 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
130
177
  const imported = (0, providers_1.validateProvidersShape)(JSON.parse(fs.readFileSync(sourceFile, "utf8")));
131
178
  const current = (0, providers_repo_1.readProvidersFileIfExists)(paths.providersPath);
132
179
  const next = merge ? (0, providers_repo_1.mergeProviders)(current, imported) : imported;
180
+ // Precompute orphaned references during confirmation so the interactive path fails before mutation.
133
181
  (0, config_1.buildManagedProfileViews)(document, next)
134
182
  .filter((view) => view.source === "orphaned-reference")
135
183
  .map((view) => view.name)
@@ -172,7 +220,7 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
172
220
  let model = (0, args_1.getSingleOption)(parsed.commandOptions, "--model", false);
173
221
  let note = (0, args_1.getSingleOption)(parsed.commandOptions, "--note", false);
174
222
  let tags = parsed.commandOptions.get("--tag") ?? [];
175
- const createProfile = (0, args_1.hasFlag)(parsed.commandOptions, "--create-profile");
223
+ let createProfile = (0, args_1.hasFlag)(parsed.commandOptions, "--create-profile");
176
224
  if (!providerName || !profile || !apiKey) {
177
225
  if (ctx.options.json || !runtime.isInteractive()) {
178
226
  throw (0, add_interactive_1.createNonInteractiveAddError)();
@@ -184,13 +232,15 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
184
232
  baseUrl,
185
233
  note,
186
234
  tags,
187
- }, (candidate) => Boolean((0, providers_repo_1.readProvidersFileIfExists)(paths.providersPath).providers[candidate]));
235
+ }, (candidate) => Boolean((0, providers_repo_1.readProvidersFileIfExists)(paths.providersPath).providers[candidate]), (candidate) => Boolean((0, config_repo_1.readStructuredConfig)(paths.configPath).profiles.find((profileView) => profileView.name === candidate)));
188
236
  providerName = prompted.providerName;
189
237
  profile = prompted.profile;
190
238
  apiKey = prompted.apiKey;
239
+ model = prompted.model ?? null;
191
240
  baseUrl = prompted.baseUrl ?? null;
192
241
  note = prompted.note ?? null;
193
242
  tags = prompted.tags;
243
+ createProfile = createProfile || prompted.createProfile;
194
244
  }
195
245
  return (0, add_provider_1.addProvider)({
196
246
  codexDir: paths.codexDir,
@@ -198,6 +248,7 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
198
248
  latestBackupPath: paths.latestBackupPath,
199
249
  providersPath: paths.providersPath,
200
250
  configPath: paths.configPath,
251
+ authPath: paths.authPath,
201
252
  providerName,
202
253
  profile,
203
254
  apiKey,
@@ -235,6 +286,7 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
235
286
  if (!provider) {
236
287
  throw (0, errors_1.cliError)("PROVIDER_NOT_FOUND", `Provider "${providerName}" was not found.`);
237
288
  }
289
+ // Prompted edit starts from the stored record so blank answers can safely preserve current values.
238
290
  const prompted = await (0, interactive_1.collectEditInput)(runtime, provider);
239
291
  profile = prompted.profile;
240
292
  apiKey = prompted.apiKey;
@@ -251,6 +303,7 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
251
303
  latestBackupPath: paths.latestBackupPath,
252
304
  providersPath: paths.providersPath,
253
305
  configPath: paths.configPath,
306
+ authPath: paths.authPath,
254
307
  providerName,
255
308
  profile,
256
309
  apiKey,
@@ -293,8 +346,9 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
293
346
  codexDir: paths.codexDir,
294
347
  configPath: paths.configPath,
295
348
  providersPath: paths.providersPath,
349
+ authPath: paths.authPath,
296
350
  });
297
- case "setup": {
351
+ case "migrate": {
298
352
  let codexDir = ctx.options.codexDir;
299
353
  const candidates = (0, config_repo_1.findCodexDirCandidates)(ctx.options.codexDirExplicit ? ctx.options.codexDir : null);
300
354
  if (!ctx.options.codexDirExplicit) {
@@ -304,6 +358,7 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
304
358
  candidates,
305
359
  });
306
360
  }
361
+ // Ambiguous auto-discovery must be resolved before path-dependent flags are interpreted.
307
362
  codexDir = await (0, interactive_1.chooseCodexDir)(runtime, candidates);
308
363
  }
309
364
  else if (candidates.length === 0) {
@@ -320,7 +375,7 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
320
375
  const overwrite = (0, args_1.hasFlag)(parsed.commandOptions, "--overwrite");
321
376
  const merge = (0, args_1.hasFlag)(parsed.commandOptions, "--merge");
322
377
  if (overwrite && merge) {
323
- throw (0, errors_1.cliError)("INVALID_ARGUMENT", "setup does not allow both --merge and --overwrite.");
378
+ throw (0, errors_1.cliError)("INVALID_ARGUMENT", "migrate does not allow both --merge and --overwrite.");
324
379
  }
325
380
  let strategy = overwrite ? "overwrite" : merge ? "merge" : null;
326
381
  const providersExists = fs.existsSync(setupPaths.providersPath);
@@ -338,11 +393,12 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
338
393
  }
339
394
  const document = (0, config_repo_1.readStructuredConfig)(setupPaths.configPath);
340
395
  const adoptableProfiles = (0, config_1.buildManagedProfileViews)(document, null)
341
- .filter((view) => view.source === "unmanaged" && view.model && view.modelProvider === view.name && view.baseUrl)
396
+ .filter((view) => view.source === "unmanaged" && view.model && view.modelProvider === view.name && view.baseUrl && view.envKey)
342
397
  .map((view) => ({
343
398
  name: view.name,
344
399
  model: view.model,
345
400
  baseUrl: view.baseUrl,
401
+ envKey: view.envKey,
346
402
  }))
347
403
  .sort((left, right) => left.name.localeCompare(right.name));
348
404
  const selectedProfiles = Array.from((0, config_repo_1.listConfigProfiles)(setupPaths.configPath)).sort();
@@ -350,20 +406,29 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
350
406
  let providerDetailsByProfile = {};
351
407
  if ((0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
352
408
  adoptProfiles = await (0, interactive_1.chooseSetupProfiles)(runtime, adoptableProfiles);
353
- providerDetailsByProfile = await (0, interactive_1.collectSetupProviderDetails)(runtime, adoptProfiles);
409
+ // Defaults are derived from config.toml so interactive setup only asks for missing provider metadata.
410
+ providerDetailsByProfile = await (0, interactive_1.collectSetupProviderDetails)(runtime, adoptProfiles, adoptableProfiles.reduce((accumulator, profile) => {
411
+ accumulator[profile.name] = {
412
+ providerName: profile.name,
413
+ envKey: profile.envKey,
414
+ baseUrl: profile.baseUrl,
415
+ };
416
+ return accumulator;
417
+ }, {}));
354
418
  }
355
419
  else {
356
- throw (0, errors_1.cliError)("INVALID_ARGUMENT", "setup currently requires an interactive TTY to choose adoptable profiles and collect provider details.", {
420
+ throw (0, errors_1.cliError)("INVALID_ARGUMENT", "migrate currently requires an interactive TTY to choose adoptable profiles and collect provider details.", {
357
421
  adoptableProfiles,
358
422
  availableProfiles: selectedProfiles,
359
- suggestion: "Run `codexs setup` in an interactive terminal. Non-interactive setup input flags are not available in 0.0.6.",
423
+ suggestion: "Run `codexs migrate` in an interactive terminal. Non-interactive migrate flags for profile selection and provider secrets are not available in this release.",
360
424
  });
361
425
  }
362
- return (0, setup_codex_1.setupCodex)({
426
+ return (0, setup_codex_1.migrateCodex)({
363
427
  codexDirOption: ctx.options.codexDir,
364
428
  codexDir: setupPaths.codexDir,
365
429
  configPath: setupPaths.configPath,
366
430
  providersPath: setupPaths.providersPath,
431
+ authPath: setupPaths.authPath,
367
432
  backupsDir: setupPaths.backupsDir,
368
433
  latestBackupPath: setupPaths.latestBackupPath,
369
434
  strategy: strategy ?? "overwrite",
@@ -371,6 +436,10 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
371
436
  providerDetailsByProfile,
372
437
  });
373
438
  }
439
+ case "setup":
440
+ throw (0, errors_1.cliError)("COMMAND_DEPRECATED", "setup has been split into init and migrate.", {
441
+ replacements: ["init", "migrate"],
442
+ });
374
443
  case "backups-list":
375
444
  return (0, list_backups_1.listBackupEntries)(paths.backupsDir);
376
445
  case "rollback":
@@ -59,7 +59,8 @@ function buildHelpText(commandName) {
59
59
  " rollback restores files from a managed backup.",
60
60
  "",
61
61
  "Examples:",
62
- " codexs setup",
62
+ " codexs init",
63
+ " codexs migrate",
63
64
  " codexs list",
64
65
  " codexs switch",
65
66
  " codexs add packycode --profile packycode --api-key sk-xxx",
@@ -11,6 +11,9 @@ exports.isKnownHelpTopic = isKnownHelpTopic;
11
11
  exports.getPublicCommandNames = getPublicCommandNames;
12
12
  exports.getNestedCommandTokens = getNestedCommandTokens;
13
13
  const handlers_1 = require("./handlers");
14
+ /**
15
+ * Canonical command registry used by parsing, help rendering, and dispatch.
16
+ */
14
17
  exports.COMMANDS = [
15
18
  {
16
19
  id: "config-show",
@@ -39,19 +42,48 @@ exports.COMMANDS = [
39
42
  examples: ["codexs config list-profiles", "codexs config list-profiles --json"],
40
43
  },
41
44
  {
42
- id: "setup",
43
- tokens: ["setup"],
45
+ id: "init",
46
+ tokens: ["init"],
47
+ handler: handlers_1.handleRegisteredCommand,
48
+ group: "write",
49
+ summary: "Initialize a Codex directory with an empty managed providers registry.",
50
+ usage: ["codexs init [--json] [--codex-dir <path>]"],
51
+ details: [
52
+ "Creates providers.json with an empty providers object when it does not exist yet.",
53
+ "Does not require codex CLI, config.toml, or auth.json, and does not run doctor.",
54
+ "TTY mode can resolve ambiguous Codex directories and confirm creating a missing directory.",
55
+ "Non-TTY and --json runs never prompt and fail when the target directory is missing.",
56
+ ],
57
+ examples: ["codexs init", "codexs init --json --codex-dir ~/.codex"],
58
+ },
59
+ {
60
+ id: "migrate",
61
+ tokens: ["migrate"],
44
62
  handler: handlers_1.handleRegisteredCommand,
45
63
  group: "write",
46
- summary: "Initialize providers.json from an existing Codex directory.",
47
- usage: ["codexs setup [--json] [--codex-dir <path>] [--merge|--overwrite]"],
64
+ summary: "Adopt unmanaged Codex config profiles into providers.json.",
65
+ usage: ["codexs migrate [--json] [--codex-dir <path>] [--merge|--overwrite]"],
48
66
  details: [
49
67
  "Reads config.toml profiles, collects complete provider records, then writes providers.json under managed backup flow.",
50
68
  "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.",
69
+ "Migrate adopts only runtime profiles that already expose model, model_provider, matching base_url, and env_key.",
70
+ "Non-TTY and --json runs still fail fast because migrate profile selection and provider details remain interactive in this release.",
71
+ ],
72
+ examples: ["codexs migrate", "codexs migrate --overwrite --json --codex-dir ~/.codex"],
73
+ },
74
+ {
75
+ id: "setup",
76
+ tokens: ["setup"],
77
+ handler: handlers_1.handleRegisteredCommand,
78
+ group: "write",
79
+ summary: "Deprecated. Use init or migrate instead.",
80
+ usage: ["codexs setup"],
81
+ details: [
82
+ "setup no longer performs initialization or migration work.",
83
+ "Use init for AI-friendly idempotent providers.json initialization.",
84
+ "Use migrate for interactive adoption from existing config.toml profiles.",
53
85
  ],
54
- examples: ["codexs setup", "codexs setup --overwrite --json --codex-dir ~/.codex"],
86
+ examples: ["codexs help init", "codexs help migrate"],
55
87
  },
56
88
  {
57
89
  id: "list",
@@ -114,7 +146,7 @@ exports.COMMANDS = [
114
146
  details: [
115
147
  "Passed flags replace only the selected fields and keep the rest unchanged.",
116
148
  "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.",
149
+ "Interactive tags use preset multi-select only.",
118
150
  "When rebinding to a missing profile, --create-profile requires both --model and --base-url.",
119
151
  "Backs up providers.json and config.toml before writing.",
120
152
  ],
@@ -135,7 +167,7 @@ exports.COMMANDS = [
135
167
  "Prompts only for missing required values when stdin/stdout are TTYs and --json is not set.",
136
168
  "Interactive add collects provider name, profile, and apiKey progressively as plain text inputs.",
137
169
  "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.",
170
+ "Interactive tags use preset multi-select only.",
139
171
  "Automation and non-TTY environments must pass all required values explicitly.",
140
172
  "Creating a missing profile section requires --create-profile together with --model and --base-url.",
141
173
  ],
@@ -146,15 +178,15 @@ exports.COMMANDS = [
146
178
  tokens: ["switch"],
147
179
  handler: handlers_1.handleRegisteredCommand,
148
180
  group: "write",
149
- summary: "Switch to a provider and optionally refresh Codex login.",
150
- usage: ["codexs switch <provider> [--no-login] [--json] [--codex-dir <path>]"],
181
+ summary: "Switch to a provider and rewrite the managed auth mirror.",
182
+ usage: ["codexs switch <provider> [--json] [--codex-dir <path>]"],
151
183
  details: [
152
184
  "When <provider> is omitted in a TTY, an interactive provider selector is shown.",
153
185
  "When <provider> is passed explicitly, switch proceeds directly without extra confirmation.",
154
- "--no-login remains explicit and is never prompted interactively.",
186
+ "Switch updates the active config profile and rewrites auth.json from the provider envKey/apiKey pair.",
155
187
  "Backs up config.toml and auth.json, then rolls back on failure.",
156
188
  ],
157
- examples: ["codexs switch freemodel", "codexs switch --no-login", "codexs switch freemodel --no-login --json"],
189
+ examples: ["codexs switch freemodel", "codexs switch packycode --json"],
158
190
  },
159
191
  {
160
192
  id: "remove",
@@ -243,22 +275,38 @@ const HELP_TOPIC_SET = new Set([
243
275
  ...exports.COMMANDS.map((command) => command.tokens.join(" ")),
244
276
  ...new Set(exports.COMMANDS.filter((command) => command.tokens.length > 1).map((command) => command.tokens[0])),
245
277
  ]);
278
+ /**
279
+ * Returns a defensive copy of the public command registry.
280
+ */
246
281
  function getCommandDefinitions() {
247
282
  return exports.COMMANDS.slice();
248
283
  }
284
+ /**
285
+ * Returns stable internal command ids in registry order.
286
+ */
249
287
  function getKnownCommandIds() {
250
288
  return exports.COMMANDS.map((command) => command.id);
251
289
  }
290
+ /**
291
+ * Resolves one command definition by its canonical internal id.
292
+ */
252
293
  function findCommandDefinition(commandId) {
253
294
  if (commandId === "help" || commandId === "version") {
254
295
  return null;
255
296
  }
256
297
  return exports.COMMANDS.find((command) => command.id === commandId) ?? null;
257
298
  }
299
+ /**
300
+ * Resolves a command definition from its tokenized CLI spelling.
301
+ */
258
302
  function findCommandDefinitionByTokens(tokens) {
259
303
  return exports.COMMANDS.find((command) => command.tokens.join(" ") === tokens.join(" ")) ?? null;
260
304
  }
305
+ /**
306
+ * Matches argv against the longest registered token sequence first.
307
+ */
261
308
  function resolveCommandFromArgv(argv) {
309
+ // Nested commands such as "config show" must win over their shorter root tokens.
262
310
  for (const command of exports.COMMANDS
263
311
  .slice()
264
312
  .sort((left, right) => right.tokens.length - left.tokens.length)) {
@@ -275,15 +323,27 @@ function resolveCommandFromArgv(argv) {
275
323
  consumedTokens: 0,
276
324
  };
277
325
  }
326
+ /**
327
+ * Reports whether a name is reserved by either a command id or its public token form.
328
+ */
278
329
  function isKnownCommandName(commandName) {
279
330
  return COMMAND_NAME_SET.has(commandName);
280
331
  }
332
+ /**
333
+ * Reports whether a help topic is recognized by the help renderer.
334
+ */
281
335
  function isKnownHelpTopic(topic) {
282
336
  return HELP_TOPIC_SET.has(topic);
283
337
  }
338
+ /**
339
+ * Returns public command names exactly as they appear in help and examples.
340
+ */
284
341
  function getPublicCommandNames() {
285
342
  return exports.COMMANDS.map((command) => command.tokens.join(" "));
286
343
  }
344
+ /**
345
+ * Returns nested command spellings for one root token such as "config" or "backups".
346
+ */
287
347
  function getNestedCommandTokens(rootToken) {
288
348
  return exports.COMMANDS
289
349
  .filter((command) => command.tokens.length > 1 && command.tokens[0] === rootToken)