@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.
- package/README.AI.md +5 -2
- package/README.md +12 -6
- package/dist/app/add-provider.js +21 -3
- package/dist/app/edit-provider.js +39 -11
- package/dist/app/get-status.js +8 -1
- package/dist/app/init-codex.js +68 -0
- package/dist/app/list-providers.js +1 -0
- package/dist/app/run-doctor.js +60 -0
- package/dist/app/setup-codex.js +17 -8
- package/dist/app/show-config.js +9 -1
- package/dist/app/switch-provider.js +14 -7
- package/dist/cli/add-interactive.js +4 -2
- package/dist/cli/args.js +3 -0
- package/dist/cli/help.js +3 -0
- package/dist/cli/interactive.js +3 -0
- package/dist/cli/output.js +20 -5
- package/dist/cli/prompt.js +3 -0
- package/dist/cli.js +1 -1
- package/dist/commands/handlers.js +80 -11
- package/dist/commands/help.js +2 -1
- package/dist/commands/registry.js +73 -13
- package/dist/domain/config.js +137 -0
- package/dist/domain/providers.js +16 -2
- package/dist/domain/setup.js +1 -0
- package/dist/infra/backup-repo.js +3 -0
- package/dist/infra/codex-cli.js +3 -0
- package/dist/infra/codex-paths.js +3 -0
- package/dist/infra/fs-utils.js +3 -0
- package/dist/infra/lock-repo.js +3 -0
- package/dist/infra/providers-repo.js +3 -0
- package/dist/interaction/add-interactive.js +9 -18
- package/dist/interaction/interactive.js +84 -11
- package/dist/runtime/codex-probe.js +7 -0
- package/dist/storage/auth-repo.js +160 -0
- package/dist/storage/config-repo.js +58 -0
- package/docs/Design/codex-switch-v0.0.7-design.md +862 -0
- package/docs/PRD/codex-switch-prd-v0.0.5-to-v0.1.0.md +131 -25
- package/docs/Reference/codex-config-reference.md +604 -0
- package/docs/Reference/codex-config-reference.zh-CN.md +633 -0
- package/docs/cli-usage.md +77 -29
- package/docs/test-report-0.0.7.md +118 -0
- package/docs/testing.md +67 -47
- 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.
|
|
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; } });
|
package/dist/cli/interactive.js
CHANGED
|
@@ -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; } });
|
package/dist/cli/output.js
CHANGED
|
@@ -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
|
-
|
|
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 "
|
|
152
|
-
lines.push(`Initialized
|
|
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(", ") : ""}`);
|
package/dist/cli/prompt.js
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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 "
|
|
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", "
|
|
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
|
-
|
|
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", "
|
|
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
|
|
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.
|
|
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":
|
package/dist/commands/help.js
CHANGED
|
@@ -59,7 +59,8 @@ function buildHelpText(commandName) {
|
|
|
59
59
|
" rollback restores files from a managed backup.",
|
|
60
60
|
"",
|
|
61
61
|
"Examples:",
|
|
62
|
-
" codexs
|
|
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: "
|
|
43
|
-
tokens: ["
|
|
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: "
|
|
47
|
-
usage: ["codexs
|
|
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
|
-
"
|
|
52
|
-
"Non-TTY and --json runs fail fast
|
|
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
|
|
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
|
|
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
|
|
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
|
|
150
|
-
usage: ["codexs switch <provider> [--
|
|
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
|
-
"
|
|
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
|
|
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)
|