@minniexcode/codex-switch 0.0.6 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.AI.md +5 -2
  2. package/README.md +12 -6
  3. package/dist/app/add-provider.js +90 -5
  4. package/dist/app/edit-provider.js +39 -11
  5. package/dist/app/get-status.js +31 -1
  6. package/dist/app/init-codex.js +68 -0
  7. package/dist/app/list-providers.js +1 -0
  8. package/dist/app/run-doctor.js +96 -1
  9. package/dist/app/setup-codex.js +18 -9
  10. package/dist/app/show-config.js +9 -1
  11. package/dist/app/switch-provider.js +61 -8
  12. package/dist/cli/add-interactive.js +4 -2
  13. package/dist/cli/args.js +3 -0
  14. package/dist/cli/help.js +3 -0
  15. package/dist/cli/interactive.js +3 -0
  16. package/dist/cli/output.js +20 -5
  17. package/dist/cli/prompt.js +3 -0
  18. package/dist/cli.js +1 -1
  19. package/dist/commands/handlers.js +107 -13
  20. package/dist/commands/help.js +2 -1
  21. package/dist/commands/registry.js +87 -15
  22. package/dist/domain/config.js +137 -0
  23. package/dist/domain/providers.js +90 -2
  24. package/dist/domain/setup.js +1 -0
  25. package/dist/infra/backup-repo.js +3 -0
  26. package/dist/infra/codex-cli.js +3 -0
  27. package/dist/infra/codex-paths.js +3 -0
  28. package/dist/infra/fs-utils.js +3 -0
  29. package/dist/infra/lock-repo.js +3 -0
  30. package/dist/infra/providers-repo.js +3 -0
  31. package/dist/interaction/add-interactive.js +9 -18
  32. package/dist/interaction/interactive.js +84 -11
  33. package/dist/runtime/codex-probe.js +7 -0
  34. package/dist/runtime/copilot-adapter.js +173 -0
  35. package/dist/runtime/copilot-bridge-worker.js +25 -0
  36. package/dist/runtime/copilot-bridge.js +433 -0
  37. package/dist/runtime/copilot-installer.js +125 -0
  38. package/dist/runtime/copilot-sdk-loader.js +59 -0
  39. package/dist/storage/auth-repo.js +160 -0
  40. package/dist/storage/config-repo.js +58 -0
  41. package/dist/storage/fs-utils.js +3 -0
  42. package/dist/storage/runtime-state-repo.js +80 -0
  43. package/docs/Design/codex-switch-v0.0.7-design.md +862 -0
  44. package/docs/Design/codex-switch-v0.0.8-design.md +132 -0
  45. package/docs/Design/codex-switch-v0.0.9-to-v0.0.12-roadmap.md +413 -0
  46. package/docs/PRD/codex-switch-prd-v0.0.5-to-v0.1.0.md +131 -25
  47. package/docs/PRD/codex-switch-prd-v0.0.8.md +62 -0
  48. package/docs/Reference/codex-config-reference.md +604 -0
  49. package/docs/Reference/codex-config-reference.zh-CN.md +633 -0
  50. package/docs/cli-usage.md +77 -29
  51. package/docs/test-report-0.0.7.md +118 -0
  52. package/docs/testing.md +67 -47
  53. package/package.json +1 -1
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.setupCodex = setupCodex;
36
+ exports.migrateCodex = migrateCodex;
37
37
  const fs = __importStar(require("node:fs"));
38
38
  const setup_1 = require("../domain/setup");
39
39
  const errors_1 = require("../domain/errors");
@@ -42,13 +42,14 @@ const codex_cli_1 = require("../runtime/codex-cli");
42
42
  const config_repo_1 = require("../storage/config-repo");
43
43
  const fs_utils_1 = require("../storage/fs-utils");
44
44
  const providers_repo_1 = require("../storage/providers-repo");
45
+ const auth_repo_1 = require("../storage/auth-repo");
45
46
  const run_doctor_1 = require("./run-doctor");
46
47
  const run_mutation_1 = require("./run-mutation");
47
48
  const MIN_CODEX_VERSION = "0.0.1";
48
49
  /**
49
- * Bootstraps a managed providers.json from the existing Codex directory.
50
+ * Migrates unmanaged Codex config profiles into a managed providers.json registry.
50
51
  */
51
- function setupCodex(args) {
52
+ async function migrateCodex(args) {
52
53
  const available = (0, codex_cli_1.checkCodexAvailable)();
53
54
  if (!available.ok) {
54
55
  throw (0, errors_1.cliError)("CODEX_NOT_INSTALLED", "codex CLI is not available.", {
@@ -70,8 +71,9 @@ function setupCodex(args) {
70
71
  }
71
72
  const document = (0, config_repo_1.readStructuredConfig)(args.configPath);
72
73
  const profileViews = (0, config_1.buildManagedProfileViews)(document, null);
74
+ // Migrate can only adopt unmanaged profiles that already contain enough runtime data to become managed.
73
75
  const adoptableProfiles = profileViews
74
- .filter((view) => view.source === "unmanaged" && view.model && view.modelProvider === view.name && view.baseUrl)
76
+ .filter((view) => view.source === "unmanaged" && view.model && view.modelProvider === view.name && view.baseUrl && view.envKey)
75
77
  .map((view) => view.name)
76
78
  .sort();
77
79
  if (profileViews.length === 0) {
@@ -81,20 +83,20 @@ function setupCodex(args) {
81
83
  }
82
84
  const invalidAdoptProfiles = args.adoptProfiles.filter((profile) => !adoptableProfiles.includes(profile));
83
85
  if (invalidAdoptProfiles.length > 0) {
84
- throw (0, errors_1.cliError)("INVALID_ARGUMENT", "setup only adopts unmanaged profiles that already contain model, model_provider, and a matching model_providers base_url.", {
86
+ throw (0, errors_1.cliError)("INVALID_ARGUMENT", "migrate only adopts unmanaged profiles that already contain model, model_provider, and matching model_providers base_url/env_key.", {
85
87
  invalidProfiles: invalidAdoptProfiles.sort(),
86
88
  adoptableProfiles,
87
89
  });
88
90
  }
89
91
  if (args.adoptProfiles.length === 0) {
90
- throw (0, errors_1.cliError)("INVALID_ARGUMENT", "setup requires at least one explicit profile to adopt.", {
92
+ throw (0, errors_1.cliError)("INVALID_ARGUMENT", "migrate requires at least one explicit profile to adopt.", {
91
93
  adoptableProfiles,
92
94
  });
93
95
  }
94
96
  const drafts = (0, setup_1.buildSetupDrafts)(args.adoptProfiles, args.providerDetailsByProfile);
95
97
  const incompleteProfiles = (0, setup_1.findIncompleteSetupProfiles)(drafts);
96
98
  if (incompleteProfiles.length > 0) {
97
- throw (0, errors_1.cliError)("INVALID_ARGUMENT", "setup requires complete provider data for every selected profile.", {
99
+ throw (0, errors_1.cliError)("INVALID_ARGUMENT", "migrate requires complete provider data for every selected profile.", {
98
100
  incompleteProfiles,
99
101
  });
100
102
  }
@@ -117,15 +119,20 @@ function setupCodex(args) {
117
119
  codexDir: args.codexDir,
118
120
  backupsDir: args.backupsDir,
119
121
  latestBackupPath: args.latestBackupPath,
120
- operation: "setup",
122
+ operation: "migrate",
121
123
  files: [
122
124
  { absolutePath: args.providersPath, relativePath: "providers.json" },
123
125
  { absolutePath: args.configPath, relativePath: "config.toml" },
126
+ { absolutePath: args.authPath, relativePath: "auth.json" },
124
127
  ],
125
128
  mutate: () => {
129
+ // migrate currently preserves config structure and only asserts that the file remains writable inside the mutation flow.
126
130
  const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {});
127
131
  (0, providers_repo_1.writeProvidersFile)(args.providersPath, finalProviders);
128
132
  (0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
133
+ const activeProviderName = (0, config_repo_1.resolveActiveProviderName)(document, finalProviders);
134
+ const existingAuth = (0, auth_repo_1.readAuthFileIfExists)(args.authPath);
135
+ (0, auth_repo_1.writeAuthFile)(args.authPath, finalProviders.providers[activeProviderName], existingAuth ?? undefined);
129
136
  return {
130
137
  codexDir: args.codexDir,
131
138
  strategy: args.strategy,
@@ -140,10 +147,12 @@ function setupCodex(args) {
140
147
  };
141
148
  },
142
149
  });
143
- const doctor = (0, run_doctor_1.runDoctor)({
150
+ // Re-run doctor on the final state so migrate returns immediate post-migration diagnostics.
151
+ const doctor = await (0, run_doctor_1.runDoctor)({
144
152
  codexDir: args.codexDir,
145
153
  configPath: args.configPath,
146
154
  providersPath: args.providersPath,
155
+ authPath: args.authPath,
147
156
  });
148
157
  return {
149
158
  data: {
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.showConfig = showConfig;
4
4
  const config_1 = require("../domain/config");
5
5
  const errors_1 = require("../domain/errors");
6
+ const providers_1 = require("../domain/providers");
6
7
  const config_repo_1 = require("../storage/config-repo");
7
8
  const providers_repo_1 = require("../storage/providers-repo");
8
9
  /**
@@ -28,7 +29,14 @@ function showConfig(args) {
28
29
  data: {
29
30
  activeProfile: document.activeProfile,
30
31
  selectedProfile,
31
- profiles,
32
+ profiles: profiles.map((profile) => ({
33
+ ...profile,
34
+ managedProviderEnvKeys: (0, providers_1.findProvidersByProfile)(providers, profile.name).map((providerName) => ({
35
+ providerName,
36
+ envKey: providers.providers[providerName].envKey,
37
+ matchesRuntime: providers.providers[providerName].envKey === profile.envKey,
38
+ })),
39
+ })),
32
40
  },
33
41
  };
34
42
  }
@@ -2,14 +2,18 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.switchProvider = switchProvider;
4
4
  const errors_1 = require("../domain/errors");
5
+ const providers_1 = require("../domain/providers");
5
6
  const config_repo_1 = require("../storage/config-repo");
6
7
  const providers_repo_1 = require("../storage/providers-repo");
8
+ const auth_repo_1 = require("../storage/auth-repo");
9
+ const copilot_bridge_1 = require("../runtime/copilot-bridge");
10
+ const copilot_installer_1 = require("../runtime/copilot-installer");
11
+ const copilot_adapter_1 = require("../runtime/copilot-adapter");
7
12
  const run_mutation_1 = require("./run-mutation");
8
- const codex_cli_1 = require("../runtime/codex-cli");
9
13
  /**
10
- * Switches the active Codex profile and optionally refreshes the CLI login session.
14
+ * Switches the active Codex profile and rewrites auth.json for the target provider.
11
15
  */
12
- function switchProvider(args) {
16
+ async function switchProvider(args) {
13
17
  const providers = (0, providers_repo_1.readProvidersFile)(args.providersPath);
14
18
  const provider = providers.providers[args.providerName];
15
19
  if (!provider) {
@@ -18,6 +22,57 @@ function switchProvider(args) {
18
22
  });
19
23
  }
20
24
  const document = (0, config_repo_1.ensureProfileExists)(args.configPath, provider.profile, args.providerName);
25
+ const envKey = (0, config_repo_1.requireRuntimeEnvKey)(document, provider.profile);
26
+ if (provider.envKey !== envKey) {
27
+ throw (0, errors_1.cliError)("PROVIDER_ENV_KEY_MISMATCH", `Provider "${args.providerName}" envKey does not match runtime env_key.`, {
28
+ provider: args.providerName,
29
+ profile: provider.profile,
30
+ providerEnvKey: provider.envKey,
31
+ runtimeEnvKey: envKey,
32
+ });
33
+ }
34
+ if ((0, providers_1.isCopilotBridgeProvider)(provider)) {
35
+ const installStatus = (0, copilot_installer_1.probeCopilotSdkInstall)();
36
+ if (!installStatus.installed) {
37
+ throw (0, errors_1.cliError)("COPILOT_SDK_MISSING", "The optional Copilot SDK runtime is not installed.", {
38
+ installDir: installStatus.installDir,
39
+ packageName: installStatus.packageName,
40
+ });
41
+ }
42
+ await (0, copilot_adapter_1.readCopilotAuthState)();
43
+ const bridge = await (0, copilot_bridge_1.ensureCopilotBridge)(args.providerName, provider);
44
+ try {
45
+ return (0, run_mutation_1.runMutation)({
46
+ codexDir: args.codexDir,
47
+ backupsDir: args.backupsDir,
48
+ latestBackupPath: args.latestBackupPath,
49
+ operation: "switch",
50
+ files: [
51
+ { absolutePath: args.configPath, relativePath: "config.toml" },
52
+ { absolutePath: args.authPath, relativePath: "auth.json" },
53
+ ],
54
+ mutate: () => {
55
+ const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {
56
+ setActiveProfile: provider.profile,
57
+ });
58
+ (0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
59
+ const existingAuth = (0, auth_repo_1.readAuthFileIfExists)(args.authPath);
60
+ (0, auth_repo_1.writeAuthFile)(args.authPath, provider, existingAuth ?? undefined);
61
+ return {
62
+ provider: args.providerName,
63
+ profile: provider.profile,
64
+ envKey: provider.envKey,
65
+ };
66
+ },
67
+ });
68
+ }
69
+ catch (error) {
70
+ if (!bridge.reused) {
71
+ (0, copilot_bridge_1.stopCopilotBridge)();
72
+ }
73
+ throw error;
74
+ }
75
+ }
21
76
  return (0, run_mutation_1.runMutation)({
22
77
  codexDir: args.codexDir,
23
78
  backupsDir: args.backupsDir,
@@ -31,15 +86,13 @@ function switchProvider(args) {
31
86
  const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {
32
87
  setActiveProfile: provider.profile,
33
88
  });
34
- // Update the runtime profile first so any subsequent login is associated with the new target.
35
89
  (0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
36
- if (!args.noLogin) {
37
- (0, codex_cli_1.runCodexLogin)(provider.apiKey, args.codexDir);
38
- }
90
+ const existingAuth = (0, auth_repo_1.readAuthFileIfExists)(args.authPath);
91
+ (0, auth_repo_1.writeAuthFile)(args.authPath, provider, existingAuth ?? undefined);
39
92
  return {
40
93
  provider: args.providerName,
41
94
  profile: provider.profile,
42
- loginPerformed: !args.noLogin,
95
+ envKey: provider.envKey,
43
96
  };
44
97
  },
45
98
  });
@@ -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.8";
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");
@@ -57,6 +58,7 @@ const providers_1 = require("../domain/providers");
57
58
  const add_interactive_1 = require("../interaction/add-interactive");
58
59
  const interactive_1 = require("../interaction/interactive");
59
60
  const prompt_1 = require("../interaction/prompt");
61
+ const copilot_installer_1 = require("../runtime/copilot-installer");
60
62
  const config_repo_1 = require("../storage/config-repo");
61
63
  const codex_paths_1 = require("../storage/codex-paths");
62
64
  const providers_repo_1 = require("../storage/providers-repo");
@@ -87,7 +89,54 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
87
89
  case "current":
88
90
  return (0, get_current_profile_1.getCurrentProfile)(paths.configPath);
89
91
  case "status":
90
- return (0, get_status_1.getStatus)(paths.codexDir, paths.configPath, paths.providersPath);
92
+ return (0, get_status_1.getStatus)(paths.codexDir, paths.configPath, paths.providersPath, paths.authPath);
93
+ case "init": {
94
+ let codexDir = ctx.options.codexDir;
95
+ const candidates = (0, config_repo_1.findCodexDirCandidates)(ctx.options.codexDirExplicit ? ctx.options.codexDir : null);
96
+ if (!ctx.options.codexDirExplicit) {
97
+ if (candidates.length > 1) {
98
+ if (!(0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
99
+ throw (0, errors_1.cliError)("CODEX_DIR_AMBIGUOUS", "Multiple Codex directories were found.", {
100
+ candidates,
101
+ });
102
+ }
103
+ codexDir = await (0, interactive_1.chooseCodexDir)(runtime, candidates);
104
+ }
105
+ else if (candidates.length === 0) {
106
+ if (!(0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
107
+ throw (0, errors_1.cliError)("CODEX_DIR_NOT_FOUND", "No Codex directory could be found.", {
108
+ codexDir: ctx.options.codexDir,
109
+ });
110
+ }
111
+ codexDir = await (0, interactive_1.chooseCodexDir)(runtime, candidates);
112
+ }
113
+ else {
114
+ codexDir = candidates[0];
115
+ }
116
+ }
117
+ setupPaths = (0, codex_paths_1.createCodexPaths)(codexDir);
118
+ let createCodexDir = false;
119
+ if (!fs.existsSync(setupPaths.codexDir)) {
120
+ if (!(0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
121
+ throw (0, errors_1.cliError)("CODEX_DIR_NOT_FOUND", "The requested Codex directory does not exist.", {
122
+ codexDir: setupPaths.codexDir,
123
+ });
124
+ }
125
+ createCodexDir = await (0, interactive_1.confirmCreateCodexDir)(runtime, setupPaths.codexDir);
126
+ if (!createCodexDir) {
127
+ throw (0, errors_1.cliError)("CODEX_DIR_NOT_FOUND", "The requested Codex directory does not exist.", {
128
+ codexDir: setupPaths.codexDir,
129
+ });
130
+ }
131
+ }
132
+ return (0, init_codex_1.initCodex)({
133
+ codexDir: setupPaths.codexDir,
134
+ providersPath: setupPaths.providersPath,
135
+ configPath: setupPaths.configPath,
136
+ authPath: setupPaths.authPath,
137
+ createCodexDir,
138
+ });
139
+ }
91
140
  case "config-show":
92
141
  return (0, show_config_1.showConfig)({
93
142
  configPath: paths.configPath,
@@ -107,6 +156,9 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
107
156
  if (!providerName) {
108
157
  throw (0, errors_1.cliError)("PROVIDER_NOT_FOUND", "Missing provider name for switch command.");
109
158
  }
159
+ if ((0, args_1.hasFlag)(parsed.commandOptions, "--install-copilot-sdk")) {
160
+ throw (0, errors_1.cliError)("INVALID_ARGUMENT", "--install-copilot-sdk is only supported with add --copilot.");
161
+ }
110
162
  return (0, switch_provider_1.switchProvider)({
111
163
  codexDir: paths.codexDir,
112
164
  backupsDir: paths.backupsDir,
@@ -115,7 +167,6 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
115
167
  providersPath: paths.providersPath,
116
168
  authPath: paths.authPath,
117
169
  providerName,
118
- noLogin: (0, args_1.hasFlag)(parsed.commandOptions, "--no-login"),
119
170
  });
120
171
  }
121
172
  case "import": {
@@ -130,6 +181,7 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
130
181
  const imported = (0, providers_1.validateProvidersShape)(JSON.parse(fs.readFileSync(sourceFile, "utf8")));
131
182
  const current = (0, providers_repo_1.readProvidersFileIfExists)(paths.providersPath);
132
183
  const next = merge ? (0, providers_repo_1.mergeProviders)(current, imported) : imported;
184
+ // Precompute orphaned references during confirmation so the interactive path fails before mutation.
133
185
  (0, config_1.buildManagedProfileViews)(document, next)
134
186
  .filter((view) => view.source === "orphaned-reference")
135
187
  .map((view) => view.name)
@@ -172,8 +224,23 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
172
224
  let model = (0, args_1.getSingleOption)(parsed.commandOptions, "--model", false);
173
225
  let note = (0, args_1.getSingleOption)(parsed.commandOptions, "--note", false);
174
226
  let tags = parsed.commandOptions.get("--tag") ?? [];
175
- const createProfile = (0, args_1.hasFlag)(parsed.commandOptions, "--create-profile");
176
- if (!providerName || !profile || !apiKey) {
227
+ let createProfile = (0, args_1.hasFlag)(parsed.commandOptions, "--create-profile");
228
+ const copilot = (0, args_1.hasFlag)(parsed.commandOptions, "--copilot");
229
+ const bridgeHost = (0, args_1.getSingleOption)(parsed.commandOptions, "--bridge-host", false);
230
+ const bridgePortValue = (0, args_1.getSingleOption)(parsed.commandOptions, "--bridge-port", false);
231
+ const bridgeApiKey = (0, args_1.getSingleOption)(parsed.commandOptions, "--bridge-api-key", false);
232
+ let installCopilotSdk = (0, args_1.hasFlag)(parsed.commandOptions, "--install-copilot-sdk");
233
+ const bridgePort = bridgePortValue ? Number(bridgePortValue) : null;
234
+ if (copilot && apiKey) {
235
+ throw (0, errors_1.cliError)("INVALID_ARGUMENT", "--copilot does not allow --api-key. Use --bridge-api-key for the local bridge secret.");
236
+ }
237
+ if (bridgePortValue && (!Number.isInteger(bridgePort) || bridgePort === null || bridgePort <= 0)) {
238
+ throw (0, errors_1.cliError)("INVALID_ARGUMENT", "--bridge-port must be a positive integer.");
239
+ }
240
+ if (copilot && !installCopilotSdk && (0, interactive_1.canPrompt)(runtime, ctx.options.json) && !(0, copilot_installer_1.probeCopilotSdkInstall)().installed) {
241
+ installCopilotSdk = await runtime.confirmAction("The optional Copilot SDK runtime is not installed. Install it now?");
242
+ }
243
+ if (!providerName || !profile || (!apiKey && !copilot)) {
177
244
  if (ctx.options.json || !runtime.isInteractive()) {
178
245
  throw (0, add_interactive_1.createNonInteractiveAddError)();
179
246
  }
@@ -184,13 +251,15 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
184
251
  baseUrl,
185
252
  note,
186
253
  tags,
187
- }, (candidate) => Boolean((0, providers_repo_1.readProvidersFileIfExists)(paths.providersPath).providers[candidate]));
254
+ }, (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
255
  providerName = prompted.providerName;
189
256
  profile = prompted.profile;
190
257
  apiKey = prompted.apiKey;
258
+ model = prompted.model ?? null;
191
259
  baseUrl = prompted.baseUrl ?? null;
192
260
  note = prompted.note ?? null;
193
261
  tags = prompted.tags;
262
+ createProfile = createProfile || prompted.createProfile;
194
263
  }
195
264
  return (0, add_provider_1.addProvider)({
196
265
  codexDir: paths.codexDir,
@@ -198,14 +267,21 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
198
267
  latestBackupPath: paths.latestBackupPath,
199
268
  providersPath: paths.providersPath,
200
269
  configPath: paths.configPath,
270
+ authPath: paths.authPath,
201
271
  providerName,
202
272
  profile,
203
- apiKey,
273
+ apiKey: apiKey ?? "",
204
274
  baseUrl,
205
275
  model,
206
276
  note,
207
277
  tags,
208
278
  createProfile,
279
+ copilot,
280
+ bridgeHost,
281
+ bridgePort,
282
+ bridgeApiKey,
283
+ installCopilotSdk,
284
+ interactive: (0, interactive_1.canPrompt)(runtime, ctx.options.json),
209
285
  });
210
286
  }
211
287
  case "edit": {
@@ -235,6 +311,7 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
235
311
  if (!provider) {
236
312
  throw (0, errors_1.cliError)("PROVIDER_NOT_FOUND", `Provider "${providerName}" was not found.`);
237
313
  }
314
+ // Prompted edit starts from the stored record so blank answers can safely preserve current values.
238
315
  const prompted = await (0, interactive_1.collectEditInput)(runtime, provider);
239
316
  profile = prompted.profile;
240
317
  apiKey = prompted.apiKey;
@@ -251,6 +328,7 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
251
328
  latestBackupPath: paths.latestBackupPath,
252
329
  providersPath: paths.providersPath,
253
330
  configPath: paths.configPath,
331
+ authPath: paths.authPath,
254
332
  providerName,
255
333
  profile,
256
334
  apiKey,
@@ -293,8 +371,9 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
293
371
  codexDir: paths.codexDir,
294
372
  configPath: paths.configPath,
295
373
  providersPath: paths.providersPath,
374
+ authPath: paths.authPath,
296
375
  });
297
- case "setup": {
376
+ case "migrate": {
298
377
  let codexDir = ctx.options.codexDir;
299
378
  const candidates = (0, config_repo_1.findCodexDirCandidates)(ctx.options.codexDirExplicit ? ctx.options.codexDir : null);
300
379
  if (!ctx.options.codexDirExplicit) {
@@ -304,6 +383,7 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
304
383
  candidates,
305
384
  });
306
385
  }
386
+ // Ambiguous auto-discovery must be resolved before path-dependent flags are interpreted.
307
387
  codexDir = await (0, interactive_1.chooseCodexDir)(runtime, candidates);
308
388
  }
309
389
  else if (candidates.length === 0) {
@@ -320,7 +400,7 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
320
400
  const overwrite = (0, args_1.hasFlag)(parsed.commandOptions, "--overwrite");
321
401
  const merge = (0, args_1.hasFlag)(parsed.commandOptions, "--merge");
322
402
  if (overwrite && merge) {
323
- throw (0, errors_1.cliError)("INVALID_ARGUMENT", "setup does not allow both --merge and --overwrite.");
403
+ throw (0, errors_1.cliError)("INVALID_ARGUMENT", "migrate does not allow both --merge and --overwrite.");
324
404
  }
325
405
  let strategy = overwrite ? "overwrite" : merge ? "merge" : null;
326
406
  const providersExists = fs.existsSync(setupPaths.providersPath);
@@ -338,11 +418,12 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
338
418
  }
339
419
  const document = (0, config_repo_1.readStructuredConfig)(setupPaths.configPath);
340
420
  const adoptableProfiles = (0, config_1.buildManagedProfileViews)(document, null)
341
- .filter((view) => view.source === "unmanaged" && view.model && view.modelProvider === view.name && view.baseUrl)
421
+ .filter((view) => view.source === "unmanaged" && view.model && view.modelProvider === view.name && view.baseUrl && view.envKey)
342
422
  .map((view) => ({
343
423
  name: view.name,
344
424
  model: view.model,
345
425
  baseUrl: view.baseUrl,
426
+ envKey: view.envKey,
346
427
  }))
347
428
  .sort((left, right) => left.name.localeCompare(right.name));
348
429
  const selectedProfiles = Array.from((0, config_repo_1.listConfigProfiles)(setupPaths.configPath)).sort();
@@ -350,20 +431,29 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
350
431
  let providerDetailsByProfile = {};
351
432
  if ((0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
352
433
  adoptProfiles = await (0, interactive_1.chooseSetupProfiles)(runtime, adoptableProfiles);
353
- providerDetailsByProfile = await (0, interactive_1.collectSetupProviderDetails)(runtime, adoptProfiles);
434
+ // Defaults are derived from config.toml so interactive setup only asks for missing provider metadata.
435
+ providerDetailsByProfile = await (0, interactive_1.collectSetupProviderDetails)(runtime, adoptProfiles, adoptableProfiles.reduce((accumulator, profile) => {
436
+ accumulator[profile.name] = {
437
+ providerName: profile.name,
438
+ envKey: profile.envKey,
439
+ baseUrl: profile.baseUrl,
440
+ };
441
+ return accumulator;
442
+ }, {}));
354
443
  }
355
444
  else {
356
- throw (0, errors_1.cliError)("INVALID_ARGUMENT", "setup currently requires an interactive TTY to choose adoptable profiles and collect provider details.", {
445
+ throw (0, errors_1.cliError)("INVALID_ARGUMENT", "migrate currently requires an interactive TTY to choose adoptable profiles and collect provider details.", {
357
446
  adoptableProfiles,
358
447
  availableProfiles: selectedProfiles,
359
- suggestion: "Run `codexs setup` in an interactive terminal. Non-interactive setup input flags are not available in 0.0.6.",
448
+ 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
449
  });
361
450
  }
362
- return (0, setup_codex_1.setupCodex)({
451
+ return (0, setup_codex_1.migrateCodex)({
363
452
  codexDirOption: ctx.options.codexDir,
364
453
  codexDir: setupPaths.codexDir,
365
454
  configPath: setupPaths.configPath,
366
455
  providersPath: setupPaths.providersPath,
456
+ authPath: setupPaths.authPath,
367
457
  backupsDir: setupPaths.backupsDir,
368
458
  latestBackupPath: setupPaths.latestBackupPath,
369
459
  strategy: strategy ?? "overwrite",
@@ -371,6 +461,10 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
371
461
  providerDetailsByProfile,
372
462
  });
373
463
  }
464
+ case "setup":
465
+ throw (0, errors_1.cliError)("COMMAND_DEPRECATED", "setup has been split into init and migrate.", {
466
+ replacements: ["init", "migrate"],
467
+ });
374
468
  case "backups-list":
375
469
  return (0, list_backups_1.listBackupEntries)(paths.backupsDir);
376
470
  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",