@minniexcode/codex-switch 0.0.5 → 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 +44 -100
- package/dist/app/add-provider.js +28 -4
- package/dist/app/edit-provider.js +47 -19
- package/dist/app/export-providers.js +2 -2
- package/dist/app/get-current-profile.js +1 -1
- package/dist/app/get-status.js +10 -3
- package/dist/app/import-providers.js +15 -7
- package/dist/app/init-codex.js +68 -0
- package/dist/app/list-backups.js +1 -1
- package/dist/app/list-config-profiles.js +3 -2
- package/dist/app/list-providers.js +2 -1
- package/dist/app/remove-provider.js +2 -2
- package/dist/app/rollback-backup.js +1 -1
- package/dist/app/rollback-latest.js +1 -1
- package/dist/app/run-doctor.js +83 -6
- package/dist/app/run-mutation.js +2 -2
- package/dist/app/setup-codex.js +21 -12
- package/dist/app/show-config.js +11 -3
- package/dist/app/show-provider.js +1 -1
- package/dist/app/switch-provider.js +16 -9
- package/dist/cli/add-interactive.js +7 -104
- package/dist/cli/args.js +6 -135
- package/dist/cli/help.js +8 -313
- package/dist/cli/interactive.js +17 -225
- package/dist/cli/output.js +21 -6
- package/dist/cli/prompt.js +4 -106
- package/dist/cli.js +10 -404
- package/dist/commands/args.js +132 -0
- package/dist/commands/dispatch.js +16 -0
- package/dist/commands/handlers.js +460 -0
- package/dist/commands/help.js +120 -0
- package/dist/commands/registry.js +351 -0
- package/dist/commands/types.js +2 -0
- package/dist/domain/config.js +235 -21
- package/dist/domain/providers.js +16 -2
- package/dist/domain/setup.js +1 -0
- package/dist/infra/backup-repo.js +9 -206
- package/dist/infra/codex-cli.js +9 -126
- package/dist/infra/codex-paths.js +6 -67
- package/dist/infra/config-repo.js +59 -0
- package/dist/infra/fs-utils.js +8 -93
- package/dist/infra/lock-repo.js +4 -95
- package/dist/infra/providers-repo.js +8 -94
- package/dist/interaction/add-interactive.js +99 -0
- package/dist/interaction/interactive.js +289 -0
- package/dist/interaction/prompt.js +110 -0
- package/dist/runtime/codex-cli.js +130 -0
- package/dist/runtime/codex-probe.js +57 -0
- package/dist/runtime/types.js +2 -0
- package/dist/storage/auth-repo.js +160 -0
- package/dist/storage/backup-repo.js +210 -0
- package/dist/storage/codex-paths.js +71 -0
- package/dist/storage/config-repo.js +266 -0
- package/dist/storage/fs-utils.js +97 -0
- package/dist/storage/lock-repo.js +99 -0
- package/dist/storage/providers-repo.js +98 -0
- package/docs/Design/codex-switch-v0.0.5-design.md +32 -22
- package/docs/Design/codex-switch-v0.0.6-design.md +708 -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 +227 -89
- package/docs/PRD/codex-switch-prd-v0.1.0.md +200 -226
- package/docs/PRD/codex-switch-prd.md +1 -1
- 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 +78 -29
- package/docs/codex-switch-technical-architecture.md +73 -4
- package/docs/test-report-0.0.5.md +163 -0
- package/docs/test-report-0.0.7.md +118 -0
- package/docs/testing.md +151 -0
- package/package.json +1 -1
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.handleRegisteredCommand = handleRegisteredCommand;
|
|
37
|
+
const fs = __importStar(require("node:fs"));
|
|
38
|
+
const add_provider_1 = require("../app/add-provider");
|
|
39
|
+
const edit_provider_1 = require("../app/edit-provider");
|
|
40
|
+
const export_providers_1 = require("../app/export-providers");
|
|
41
|
+
const get_current_profile_1 = require("../app/get-current-profile");
|
|
42
|
+
const get_status_1 = require("../app/get-status");
|
|
43
|
+
const init_codex_1 = require("../app/init-codex");
|
|
44
|
+
const import_providers_1 = require("../app/import-providers");
|
|
45
|
+
const list_config_profiles_1 = require("../app/list-config-profiles");
|
|
46
|
+
const list_backups_1 = require("../app/list-backups");
|
|
47
|
+
const list_providers_1 = require("../app/list-providers");
|
|
48
|
+
const remove_provider_1 = require("../app/remove-provider");
|
|
49
|
+
const rollback_backup_1 = require("../app/rollback-backup");
|
|
50
|
+
const run_doctor_1 = require("../app/run-doctor");
|
|
51
|
+
const setup_codex_1 = require("../app/setup-codex");
|
|
52
|
+
const show_config_1 = require("../app/show-config");
|
|
53
|
+
const show_provider_1 = require("../app/show-provider");
|
|
54
|
+
const switch_provider_1 = require("../app/switch-provider");
|
|
55
|
+
const config_1 = require("../domain/config");
|
|
56
|
+
const errors_1 = require("../domain/errors");
|
|
57
|
+
const providers_1 = require("../domain/providers");
|
|
58
|
+
const add_interactive_1 = require("../interaction/add-interactive");
|
|
59
|
+
const interactive_1 = require("../interaction/interactive");
|
|
60
|
+
const prompt_1 = require("../interaction/prompt");
|
|
61
|
+
const config_repo_1 = require("../storage/config-repo");
|
|
62
|
+
const codex_paths_1 = require("../storage/codex-paths");
|
|
63
|
+
const providers_repo_1 = require("../storage/providers-repo");
|
|
64
|
+
const args_1 = require("./args");
|
|
65
|
+
/**
|
|
66
|
+
* Executes one command handler selected from the shared command registry.
|
|
67
|
+
*/
|
|
68
|
+
async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.createPromptRuntime)()) {
|
|
69
|
+
let setupPaths = (0, codex_paths_1.createCodexPaths)(ctx.options.codexDir);
|
|
70
|
+
const paths = setupPaths;
|
|
71
|
+
switch (ctx.command) {
|
|
72
|
+
case "list":
|
|
73
|
+
return (0, list_providers_1.listProviders)(paths.providersPath);
|
|
74
|
+
case "show": {
|
|
75
|
+
let providerName = parsed.positionals[0] ?? null;
|
|
76
|
+
if (!providerName && (0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
|
|
77
|
+
providerName = await (0, interactive_1.promptForProviderSelection)(runtime, paths.providersPath, "Choose a provider to show");
|
|
78
|
+
}
|
|
79
|
+
if (!providerName) {
|
|
80
|
+
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "Missing provider name for show command.");
|
|
81
|
+
}
|
|
82
|
+
return (0, show_provider_1.showProvider)({
|
|
83
|
+
providersPath: paths.providersPath,
|
|
84
|
+
providerName,
|
|
85
|
+
includeSecret: ctx.options.json,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
case "current":
|
|
89
|
+
return (0, get_current_profile_1.getCurrentProfile)(paths.configPath);
|
|
90
|
+
case "status":
|
|
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
|
+
}
|
|
139
|
+
case "config-show":
|
|
140
|
+
return (0, show_config_1.showConfig)({
|
|
141
|
+
configPath: paths.configPath,
|
|
142
|
+
providersPath: paths.providersPath,
|
|
143
|
+
profileName: parsed.positionals[0] ?? null,
|
|
144
|
+
});
|
|
145
|
+
case "config-list-profiles":
|
|
146
|
+
return (0, list_config_profiles_1.listConfigProfilesView)({
|
|
147
|
+
configPath: paths.configPath,
|
|
148
|
+
providersPath: paths.providersPath,
|
|
149
|
+
});
|
|
150
|
+
case "switch": {
|
|
151
|
+
let providerName = parsed.positionals[0] ?? null;
|
|
152
|
+
if (!providerName && (0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
|
|
153
|
+
providerName = await (0, interactive_1.promptForProviderSelection)(runtime, paths.providersPath, "Choose a provider to switch to");
|
|
154
|
+
}
|
|
155
|
+
if (!providerName) {
|
|
156
|
+
throw (0, errors_1.cliError)("PROVIDER_NOT_FOUND", "Missing provider name for switch command.");
|
|
157
|
+
}
|
|
158
|
+
return (0, switch_provider_1.switchProvider)({
|
|
159
|
+
codexDir: paths.codexDir,
|
|
160
|
+
backupsDir: paths.backupsDir,
|
|
161
|
+
latestBackupPath: paths.latestBackupPath,
|
|
162
|
+
configPath: paths.configPath,
|
|
163
|
+
providersPath: paths.providersPath,
|
|
164
|
+
authPath: paths.authPath,
|
|
165
|
+
providerName,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
case "import": {
|
|
169
|
+
const sourceFile = parsed.positionals[0];
|
|
170
|
+
if (!sourceFile) {
|
|
171
|
+
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "Missing import file path.");
|
|
172
|
+
}
|
|
173
|
+
const merge = (0, args_1.hasFlag)(parsed.commandOptions, "--merge");
|
|
174
|
+
if ((0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
|
|
175
|
+
await (0, interactive_1.confirmImport)(runtime, sourceFile, merge);
|
|
176
|
+
const document = (0, config_repo_1.readStructuredConfig)(paths.configPath);
|
|
177
|
+
const imported = (0, providers_1.validateProvidersShape)(JSON.parse(fs.readFileSync(sourceFile, "utf8")));
|
|
178
|
+
const current = (0, providers_repo_1.readProvidersFileIfExists)(paths.providersPath);
|
|
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.
|
|
181
|
+
(0, config_1.buildManagedProfileViews)(document, next)
|
|
182
|
+
.filter((view) => view.source === "orphaned-reference")
|
|
183
|
+
.map((view) => view.name)
|
|
184
|
+
.sort();
|
|
185
|
+
}
|
|
186
|
+
return (0, import_providers_1.importProviders)({
|
|
187
|
+
codexDir: paths.codexDir,
|
|
188
|
+
backupsDir: paths.backupsDir,
|
|
189
|
+
latestBackupPath: paths.latestBackupPath,
|
|
190
|
+
providersPath: paths.providersPath,
|
|
191
|
+
configPath: paths.configPath,
|
|
192
|
+
sourceFile,
|
|
193
|
+
merge,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
case "export": {
|
|
197
|
+
const targetFile = parsed.positionals[0];
|
|
198
|
+
if (!targetFile) {
|
|
199
|
+
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "Missing export file path.");
|
|
200
|
+
}
|
|
201
|
+
let force = (0, args_1.hasFlag)(parsed.commandOptions, "--force");
|
|
202
|
+
if (!force && (0, interactive_1.canPrompt)(runtime, ctx.options.json) && (0, interactive_1.exportTargetExists)(targetFile)) {
|
|
203
|
+
const confirmed = await (0, interactive_1.confirmExportOverwrite)(runtime, targetFile);
|
|
204
|
+
if (!confirmed) {
|
|
205
|
+
throw (0, errors_1.cliError)("PROMPT_CANCELLED", "Export cancelled.");
|
|
206
|
+
}
|
|
207
|
+
force = true;
|
|
208
|
+
}
|
|
209
|
+
return (0, export_providers_1.exportProviders)({
|
|
210
|
+
providersPath: paths.providersPath,
|
|
211
|
+
targetFile,
|
|
212
|
+
force,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
case "add": {
|
|
216
|
+
let providerName = parsed.positionals[0] ?? null;
|
|
217
|
+
let profile = (0, args_1.getSingleOption)(parsed.commandOptions, "--profile");
|
|
218
|
+
let apiKey = (0, args_1.getSingleOption)(parsed.commandOptions, "--api-key");
|
|
219
|
+
let baseUrl = (0, args_1.getSingleOption)(parsed.commandOptions, "--base-url", false);
|
|
220
|
+
let model = (0, args_1.getSingleOption)(parsed.commandOptions, "--model", false);
|
|
221
|
+
let note = (0, args_1.getSingleOption)(parsed.commandOptions, "--note", false);
|
|
222
|
+
let tags = parsed.commandOptions.get("--tag") ?? [];
|
|
223
|
+
let createProfile = (0, args_1.hasFlag)(parsed.commandOptions, "--create-profile");
|
|
224
|
+
if (!providerName || !profile || !apiKey) {
|
|
225
|
+
if (ctx.options.json || !runtime.isInteractive()) {
|
|
226
|
+
throw (0, add_interactive_1.createNonInteractiveAddError)();
|
|
227
|
+
}
|
|
228
|
+
const prompted = await (0, add_interactive_1.collectAddInput)(runtime, {
|
|
229
|
+
providerName,
|
|
230
|
+
profile,
|
|
231
|
+
apiKey,
|
|
232
|
+
baseUrl,
|
|
233
|
+
note,
|
|
234
|
+
tags,
|
|
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)));
|
|
236
|
+
providerName = prompted.providerName;
|
|
237
|
+
profile = prompted.profile;
|
|
238
|
+
apiKey = prompted.apiKey;
|
|
239
|
+
model = prompted.model ?? null;
|
|
240
|
+
baseUrl = prompted.baseUrl ?? null;
|
|
241
|
+
note = prompted.note ?? null;
|
|
242
|
+
tags = prompted.tags;
|
|
243
|
+
createProfile = createProfile || prompted.createProfile;
|
|
244
|
+
}
|
|
245
|
+
return (0, add_provider_1.addProvider)({
|
|
246
|
+
codexDir: paths.codexDir,
|
|
247
|
+
backupsDir: paths.backupsDir,
|
|
248
|
+
latestBackupPath: paths.latestBackupPath,
|
|
249
|
+
providersPath: paths.providersPath,
|
|
250
|
+
configPath: paths.configPath,
|
|
251
|
+
authPath: paths.authPath,
|
|
252
|
+
providerName,
|
|
253
|
+
profile,
|
|
254
|
+
apiKey,
|
|
255
|
+
baseUrl,
|
|
256
|
+
model,
|
|
257
|
+
note,
|
|
258
|
+
tags,
|
|
259
|
+
createProfile,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
case "edit": {
|
|
263
|
+
let providerName = parsed.positionals[0] ?? null;
|
|
264
|
+
if (!providerName && (0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
|
|
265
|
+
providerName = await (0, interactive_1.promptForProviderSelection)(runtime, paths.providersPath, "Choose a provider to edit");
|
|
266
|
+
}
|
|
267
|
+
if (!providerName) {
|
|
268
|
+
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "Missing provider name for edit command.");
|
|
269
|
+
}
|
|
270
|
+
let profile = (0, args_1.getSingleOption)(parsed.commandOptions, "--profile", false) ?? undefined;
|
|
271
|
+
let apiKey = (0, args_1.getSingleOption)(parsed.commandOptions, "--api-key", false) ?? undefined;
|
|
272
|
+
let baseUrl = (0, args_1.getSingleOption)(parsed.commandOptions, "--base-url", false) ?? undefined;
|
|
273
|
+
let model = (0, args_1.getSingleOption)(parsed.commandOptions, "--model", false) ?? undefined;
|
|
274
|
+
let note = (0, args_1.getSingleOption)(parsed.commandOptions, "--note", false) ?? undefined;
|
|
275
|
+
let tags = parsed.commandOptions.has("--tag") ? parsed.commandOptions.get("--tag") ?? [] : undefined;
|
|
276
|
+
const createProfile = (0, args_1.hasFlag)(parsed.commandOptions, "--create-profile");
|
|
277
|
+
const switchToProfile = (0, args_1.getSingleOption)(parsed.commandOptions, "--switch-to", false) ?? undefined;
|
|
278
|
+
if (profile === undefined &&
|
|
279
|
+
apiKey === undefined &&
|
|
280
|
+
baseUrl === undefined &&
|
|
281
|
+
model === undefined &&
|
|
282
|
+
note === undefined &&
|
|
283
|
+
tags === undefined &&
|
|
284
|
+
(0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
|
|
285
|
+
const provider = (0, providers_repo_1.readProvidersFileIfExists)(paths.providersPath).providers[providerName];
|
|
286
|
+
if (!provider) {
|
|
287
|
+
throw (0, errors_1.cliError)("PROVIDER_NOT_FOUND", `Provider "${providerName}" was not found.`);
|
|
288
|
+
}
|
|
289
|
+
// Prompted edit starts from the stored record so blank answers can safely preserve current values.
|
|
290
|
+
const prompted = await (0, interactive_1.collectEditInput)(runtime, provider);
|
|
291
|
+
profile = prompted.profile;
|
|
292
|
+
apiKey = prompted.apiKey;
|
|
293
|
+
baseUrl = prompted.baseUrl;
|
|
294
|
+
note = prompted.note;
|
|
295
|
+
tags = prompted.tags;
|
|
296
|
+
}
|
|
297
|
+
if (profile === undefined && apiKey === undefined && baseUrl === undefined && model === undefined && note === undefined && tags === undefined) {
|
|
298
|
+
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "edit requires at least one field to update.");
|
|
299
|
+
}
|
|
300
|
+
return (0, edit_provider_1.editProvider)({
|
|
301
|
+
codexDir: paths.codexDir,
|
|
302
|
+
backupsDir: paths.backupsDir,
|
|
303
|
+
latestBackupPath: paths.latestBackupPath,
|
|
304
|
+
providersPath: paths.providersPath,
|
|
305
|
+
configPath: paths.configPath,
|
|
306
|
+
authPath: paths.authPath,
|
|
307
|
+
providerName,
|
|
308
|
+
profile,
|
|
309
|
+
apiKey,
|
|
310
|
+
baseUrl,
|
|
311
|
+
model,
|
|
312
|
+
note,
|
|
313
|
+
tags,
|
|
314
|
+
createProfile,
|
|
315
|
+
switchToProfile,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
case "remove": {
|
|
319
|
+
let providerName = parsed.positionals[0] ?? null;
|
|
320
|
+
const force = (0, args_1.hasFlag)(parsed.commandOptions, "--force");
|
|
321
|
+
const switchToProfile = (0, args_1.getSingleOption)(parsed.commandOptions, "--switch-to", false) ?? undefined;
|
|
322
|
+
if (!providerName && (0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
|
|
323
|
+
providerName = await (0, interactive_1.promptForProviderSelection)(runtime, paths.providersPath, "Choose a provider to remove");
|
|
324
|
+
}
|
|
325
|
+
if (!providerName) {
|
|
326
|
+
throw (0, errors_1.cliError)("PROVIDER_NOT_FOUND", "Missing provider name for remove command.");
|
|
327
|
+
}
|
|
328
|
+
if (!force && !(0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
|
|
329
|
+
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "remove requires --force.");
|
|
330
|
+
}
|
|
331
|
+
if ((0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
|
|
332
|
+
await (0, interactive_1.confirmProviderRemoval)(runtime, providerName);
|
|
333
|
+
}
|
|
334
|
+
return (0, remove_provider_1.removeProvider)({
|
|
335
|
+
codexDir: paths.codexDir,
|
|
336
|
+
backupsDir: paths.backupsDir,
|
|
337
|
+
latestBackupPath: paths.latestBackupPath,
|
|
338
|
+
providersPath: paths.providersPath,
|
|
339
|
+
configPath: paths.configPath,
|
|
340
|
+
providerName,
|
|
341
|
+
switchToProfile,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
case "doctor":
|
|
345
|
+
return (0, run_doctor_1.runDoctor)({
|
|
346
|
+
codexDir: paths.codexDir,
|
|
347
|
+
configPath: paths.configPath,
|
|
348
|
+
providersPath: paths.providersPath,
|
|
349
|
+
authPath: paths.authPath,
|
|
350
|
+
});
|
|
351
|
+
case "migrate": {
|
|
352
|
+
let codexDir = ctx.options.codexDir;
|
|
353
|
+
const candidates = (0, config_repo_1.findCodexDirCandidates)(ctx.options.codexDirExplicit ? ctx.options.codexDir : null);
|
|
354
|
+
if (!ctx.options.codexDirExplicit) {
|
|
355
|
+
if (candidates.length > 1) {
|
|
356
|
+
if (!(0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
|
|
357
|
+
throw (0, errors_1.cliError)("CODEX_DIR_AMBIGUOUS", "Multiple Codex directories were found.", {
|
|
358
|
+
candidates,
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
// Ambiguous auto-discovery must be resolved before path-dependent flags are interpreted.
|
|
362
|
+
codexDir = await (0, interactive_1.chooseCodexDir)(runtime, candidates);
|
|
363
|
+
}
|
|
364
|
+
else if (candidates.length === 0) {
|
|
365
|
+
if (!(0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
|
|
366
|
+
throw (0, errors_1.cliError)("CODEX_DIR_NOT_FOUND", "No Codex directory could be found.");
|
|
367
|
+
}
|
|
368
|
+
codexDir = await (0, interactive_1.chooseCodexDir)(runtime, candidates);
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
codexDir = candidates[0];
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
setupPaths = (0, codex_paths_1.createCodexPaths)(codexDir);
|
|
375
|
+
const overwrite = (0, args_1.hasFlag)(parsed.commandOptions, "--overwrite");
|
|
376
|
+
const merge = (0, args_1.hasFlag)(parsed.commandOptions, "--merge");
|
|
377
|
+
if (overwrite && merge) {
|
|
378
|
+
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "migrate does not allow both --merge and --overwrite.");
|
|
379
|
+
}
|
|
380
|
+
let strategy = overwrite ? "overwrite" : merge ? "merge" : null;
|
|
381
|
+
const providersExists = fs.existsSync(setupPaths.providersPath);
|
|
382
|
+
if (providersExists && strategy === null) {
|
|
383
|
+
if (!(0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
|
|
384
|
+
throw (0, errors_1.cliError)("PROVIDERS_ALREADY_EXISTS", "providers.json already exists. Pass --merge or --overwrite.", {
|
|
385
|
+
file: setupPaths.providersPath,
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
const selected = await (0, interactive_1.chooseSetupStrategy)(runtime);
|
|
389
|
+
if (selected === "cancel") {
|
|
390
|
+
throw (0, errors_1.cliError)("PROMPT_CANCELLED", "Setup cancelled.");
|
|
391
|
+
}
|
|
392
|
+
strategy = selected;
|
|
393
|
+
}
|
|
394
|
+
const document = (0, config_repo_1.readStructuredConfig)(setupPaths.configPath);
|
|
395
|
+
const adoptableProfiles = (0, config_1.buildManagedProfileViews)(document, null)
|
|
396
|
+
.filter((view) => view.source === "unmanaged" && view.model && view.modelProvider === view.name && view.baseUrl && view.envKey)
|
|
397
|
+
.map((view) => ({
|
|
398
|
+
name: view.name,
|
|
399
|
+
model: view.model,
|
|
400
|
+
baseUrl: view.baseUrl,
|
|
401
|
+
envKey: view.envKey,
|
|
402
|
+
}))
|
|
403
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
404
|
+
const selectedProfiles = Array.from((0, config_repo_1.listConfigProfiles)(setupPaths.configPath)).sort();
|
|
405
|
+
let adoptProfiles = [];
|
|
406
|
+
let providerDetailsByProfile = {};
|
|
407
|
+
if ((0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
|
|
408
|
+
adoptProfiles = await (0, interactive_1.chooseSetupProfiles)(runtime, adoptableProfiles);
|
|
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
|
+
}, {}));
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "migrate currently requires an interactive TTY to choose adoptable profiles and collect provider details.", {
|
|
421
|
+
adoptableProfiles,
|
|
422
|
+
availableProfiles: selectedProfiles,
|
|
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.",
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
return (0, setup_codex_1.migrateCodex)({
|
|
427
|
+
codexDirOption: ctx.options.codexDir,
|
|
428
|
+
codexDir: setupPaths.codexDir,
|
|
429
|
+
configPath: setupPaths.configPath,
|
|
430
|
+
providersPath: setupPaths.providersPath,
|
|
431
|
+
authPath: setupPaths.authPath,
|
|
432
|
+
backupsDir: setupPaths.backupsDir,
|
|
433
|
+
latestBackupPath: setupPaths.latestBackupPath,
|
|
434
|
+
strategy: strategy ?? "overwrite",
|
|
435
|
+
adoptProfiles,
|
|
436
|
+
providerDetailsByProfile,
|
|
437
|
+
});
|
|
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
|
+
});
|
|
443
|
+
case "backups-list":
|
|
444
|
+
return (0, list_backups_1.listBackupEntries)(paths.backupsDir);
|
|
445
|
+
case "rollback":
|
|
446
|
+
if (parsed.positionals.length > 1) {
|
|
447
|
+
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "rollback accepts at most one backup id.");
|
|
448
|
+
}
|
|
449
|
+
if ((0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
|
|
450
|
+
await (0, interactive_1.confirmRollback)(runtime, paths.latestBackupPath, paths.backupsDir, parsed.positionals[0] ?? null);
|
|
451
|
+
}
|
|
452
|
+
return (0, rollback_backup_1.rollbackBackup)({
|
|
453
|
+
latestBackupPath: paths.latestBackupPath,
|
|
454
|
+
backupsDir: paths.backupsDir,
|
|
455
|
+
backupId: parsed.positionals[0] ?? null,
|
|
456
|
+
});
|
|
457
|
+
default:
|
|
458
|
+
throw (0, errors_1.cliError)("UNKNOWN_COMMAND", `Unknown command: ${ctx.command}`);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getKnownCommandNames = getKnownCommandNames;
|
|
4
|
+
exports.isKnownCommandNameForHelp = isKnownCommandNameForHelp;
|
|
5
|
+
exports.buildHelpText = buildHelpText;
|
|
6
|
+
const registry_1 = require("./registry");
|
|
7
|
+
const GROUP_TITLES = {
|
|
8
|
+
read: "Read Commands",
|
|
9
|
+
write: "Change Commands",
|
|
10
|
+
recovery: "Diagnostics And Recovery",
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Returns the known command names.
|
|
14
|
+
*/
|
|
15
|
+
function getKnownCommandNames() {
|
|
16
|
+
return (0, registry_1.getPublicCommandNames)();
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Checks whether the requested topic is a known command.
|
|
20
|
+
*/
|
|
21
|
+
function isKnownCommandNameForHelp(commandName) {
|
|
22
|
+
return (0, registry_1.isKnownHelpTopic)(commandName);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Builds the command help text for the top-level help page or a specific topic.
|
|
26
|
+
*/
|
|
27
|
+
function buildHelpText(commandName) {
|
|
28
|
+
if (!commandName) {
|
|
29
|
+
return [
|
|
30
|
+
"codex-switch",
|
|
31
|
+
"",
|
|
32
|
+
"Manage and switch local Codex provider/profile configuration safely.",
|
|
33
|
+
"",
|
|
34
|
+
"Usage:",
|
|
35
|
+
" codexs <command> [options]",
|
|
36
|
+
" codexs help <command>",
|
|
37
|
+
"",
|
|
38
|
+
...renderGroupedCommands(),
|
|
39
|
+
"",
|
|
40
|
+
"Global options:",
|
|
41
|
+
" --json Output the standard JSON envelope and disable all prompts.",
|
|
42
|
+
" --codex-dir <path> Target a specific Codex directory instead of ~/.codex.",
|
|
43
|
+
" --help Show top-level or command-specific help.",
|
|
44
|
+
" --version Print the current CLI version.",
|
|
45
|
+
"",
|
|
46
|
+
"Environment:",
|
|
47
|
+
" CODEXS_CODEX_DIR Default Codex directory when --codex-dir is not passed.",
|
|
48
|
+
" NODE_ENV=development defaults to ./dev-codex/local-sandbox when no override is set.",
|
|
49
|
+
"",
|
|
50
|
+
"Interactive rules:",
|
|
51
|
+
" Progressive prompts only run in a real TTY and never run under --json.",
|
|
52
|
+
" Human write commands may guide missing inputs or ask for dangerous-action confirmation.",
|
|
53
|
+
" Automation should pass explicit arguments and prefer --json for stable parsing.",
|
|
54
|
+
"",
|
|
55
|
+
"Dangerous commands:",
|
|
56
|
+
" remove deletes provider records.",
|
|
57
|
+
" import replaces or merges providers.json.",
|
|
58
|
+
" export may overwrite a target file.",
|
|
59
|
+
" rollback restores files from a managed backup.",
|
|
60
|
+
"",
|
|
61
|
+
"Examples:",
|
|
62
|
+
" codexs init",
|
|
63
|
+
" codexs migrate",
|
|
64
|
+
" codexs list",
|
|
65
|
+
" codexs switch",
|
|
66
|
+
" codexs add packycode --profile packycode --api-key sk-xxx",
|
|
67
|
+
" codexs config show",
|
|
68
|
+
" codexs remove freemodel",
|
|
69
|
+
" codexs backups list",
|
|
70
|
+
" codexs rollback",
|
|
71
|
+
" codexs help add",
|
|
72
|
+
].join("\n");
|
|
73
|
+
}
|
|
74
|
+
const nestedCommands = (0, registry_1.getNestedCommandTokens)(commandName);
|
|
75
|
+
if (nestedCommands.length > 0) {
|
|
76
|
+
return [
|
|
77
|
+
`codexs ${commandName}`,
|
|
78
|
+
"",
|
|
79
|
+
`Available ${commandName} commands:`,
|
|
80
|
+
...nestedCommands.map((name) => ` ${name}`),
|
|
81
|
+
"",
|
|
82
|
+
"Use `codexs help <command>` for detailed usage.",
|
|
83
|
+
].join("\n");
|
|
84
|
+
}
|
|
85
|
+
const command = (0, registry_1.findCommandDefinitionByTokens)(commandName.split(" "));
|
|
86
|
+
if (!command) {
|
|
87
|
+
return [
|
|
88
|
+
`Unknown help topic: ${commandName}`,
|
|
89
|
+
"",
|
|
90
|
+
"Available commands:",
|
|
91
|
+
...getKnownCommandNames().map((name) => ` ${name}`),
|
|
92
|
+
].join("\n");
|
|
93
|
+
}
|
|
94
|
+
return [
|
|
95
|
+
`codexs ${command.tokens.join(" ")}`,
|
|
96
|
+
"",
|
|
97
|
+
command.summary,
|
|
98
|
+
"",
|
|
99
|
+
"Usage:",
|
|
100
|
+
...command.usage.map((usage) => ` ${usage}`),
|
|
101
|
+
"",
|
|
102
|
+
"Details:",
|
|
103
|
+
...command.details.map((detail) => ` ${detail}`),
|
|
104
|
+
"",
|
|
105
|
+
"Examples:",
|
|
106
|
+
...command.examples.map((example) => ` ${example}`),
|
|
107
|
+
].join("\n");
|
|
108
|
+
}
|
|
109
|
+
function renderGroupedCommands() {
|
|
110
|
+
const lines = [];
|
|
111
|
+
for (const group of ["read", "write", "recovery"]) {
|
|
112
|
+
lines.push(`${GROUP_TITLES[group]}:`);
|
|
113
|
+
for (const command of registry_1.COMMANDS.filter((candidate) => candidate.group === group)) {
|
|
114
|
+
lines.push(` ${command.tokens.join(" ").padEnd(12, " ")} ${command.summary}`);
|
|
115
|
+
}
|
|
116
|
+
lines.push("");
|
|
117
|
+
}
|
|
118
|
+
lines.pop();
|
|
119
|
+
return lines;
|
|
120
|
+
}
|