@minniexcode/codex-switch 0.0.5 → 0.0.6
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.md +35 -97
- package/dist/app/add-provider.js +10 -4
- package/dist/app/edit-provider.js +9 -9
- package/dist/app/export-providers.js +2 -2
- package/dist/app/get-current-profile.js +1 -1
- package/dist/app/get-status.js +2 -2
- package/dist/app/import-providers.js +15 -7
- package/dist/app/list-backups.js +1 -1
- package/dist/app/list-config-profiles.js +3 -2
- package/dist/app/list-providers.js +1 -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 +23 -6
- package/dist/app/run-mutation.js +2 -2
- package/dist/app/setup-codex.js +6 -6
- package/dist/app/show-config.js +2 -2
- package/dist/app/show-provider.js +1 -1
- package/dist/app/switch-provider.js +3 -3
- package/dist/cli/add-interactive.js +7 -106
- package/dist/cli/args.js +5 -137
- package/dist/cli/help.js +5 -313
- package/dist/cli/interactive.js +16 -227
- package/dist/cli/output.js +2 -2
- package/dist/cli/prompt.js +3 -108
- 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 +391 -0
- package/dist/commands/help.js +119 -0
- package/dist/commands/registry.js +291 -0
- package/dist/commands/types.js +2 -0
- package/dist/domain/config.js +100 -23
- package/dist/infra/backup-repo.js +8 -208
- package/dist/infra/codex-cli.js +8 -128
- package/dist/infra/codex-paths.js +5 -69
- package/dist/infra/config-repo.js +59 -0
- package/dist/infra/fs-utils.js +7 -95
- package/dist/infra/lock-repo.js +3 -97
- package/dist/infra/providers-repo.js +7 -96
- package/dist/interaction/add-interactive.js +108 -0
- package/dist/interaction/interactive.js +216 -0
- package/dist/interaction/prompt.js +110 -0
- package/dist/runtime/codex-cli.js +130 -0
- package/dist/runtime/codex-probe.js +50 -0
- package/dist/runtime/types.js +2 -0
- package/dist/storage/backup-repo.js +210 -0
- package/dist/storage/codex-paths.js +71 -0
- package/dist/storage/config-repo.js +208 -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/PRD/codex-switch-prd-v0.0.5-to-v0.1.0.md +125 -93
- package/docs/PRD/codex-switch-prd-v0.1.0.md +200 -226
- package/docs/PRD/codex-switch-prd.md +1 -1
- package/docs/cli-usage.md +2 -1
- package/docs/codex-switch-technical-architecture.md +73 -4
- package/docs/test-report-0.0.5.md +163 -0
- package/docs/testing.md +131 -0
- package/package.json +1 -1
|
@@ -0,0 +1,391 @@
|
|
|
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 import_providers_1 = require("../app/import-providers");
|
|
44
|
+
const list_config_profiles_1 = require("../app/list-config-profiles");
|
|
45
|
+
const list_backups_1 = require("../app/list-backups");
|
|
46
|
+
const list_providers_1 = require("../app/list-providers");
|
|
47
|
+
const remove_provider_1 = require("../app/remove-provider");
|
|
48
|
+
const rollback_backup_1 = require("../app/rollback-backup");
|
|
49
|
+
const run_doctor_1 = require("../app/run-doctor");
|
|
50
|
+
const setup_codex_1 = require("../app/setup-codex");
|
|
51
|
+
const show_config_1 = require("../app/show-config");
|
|
52
|
+
const show_provider_1 = require("../app/show-provider");
|
|
53
|
+
const switch_provider_1 = require("../app/switch-provider");
|
|
54
|
+
const config_1 = require("../domain/config");
|
|
55
|
+
const errors_1 = require("../domain/errors");
|
|
56
|
+
const providers_1 = require("../domain/providers");
|
|
57
|
+
const add_interactive_1 = require("../interaction/add-interactive");
|
|
58
|
+
const interactive_1 = require("../interaction/interactive");
|
|
59
|
+
const prompt_1 = require("../interaction/prompt");
|
|
60
|
+
const config_repo_1 = require("../storage/config-repo");
|
|
61
|
+
const codex_paths_1 = require("../storage/codex-paths");
|
|
62
|
+
const providers_repo_1 = require("../storage/providers-repo");
|
|
63
|
+
const args_1 = require("./args");
|
|
64
|
+
/**
|
|
65
|
+
* Executes one command handler selected from the shared command registry.
|
|
66
|
+
*/
|
|
67
|
+
async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.createPromptRuntime)()) {
|
|
68
|
+
let setupPaths = (0, codex_paths_1.createCodexPaths)(ctx.options.codexDir);
|
|
69
|
+
const paths = setupPaths;
|
|
70
|
+
switch (ctx.command) {
|
|
71
|
+
case "list":
|
|
72
|
+
return (0, list_providers_1.listProviders)(paths.providersPath);
|
|
73
|
+
case "show": {
|
|
74
|
+
let providerName = parsed.positionals[0] ?? null;
|
|
75
|
+
if (!providerName && (0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
|
|
76
|
+
providerName = await (0, interactive_1.promptForProviderSelection)(runtime, paths.providersPath, "Choose a provider to show");
|
|
77
|
+
}
|
|
78
|
+
if (!providerName) {
|
|
79
|
+
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "Missing provider name for show command.");
|
|
80
|
+
}
|
|
81
|
+
return (0, show_provider_1.showProvider)({
|
|
82
|
+
providersPath: paths.providersPath,
|
|
83
|
+
providerName,
|
|
84
|
+
includeSecret: ctx.options.json,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
case "current":
|
|
88
|
+
return (0, get_current_profile_1.getCurrentProfile)(paths.configPath);
|
|
89
|
+
case "status":
|
|
90
|
+
return (0, get_status_1.getStatus)(paths.codexDir, paths.configPath, paths.providersPath);
|
|
91
|
+
case "config-show":
|
|
92
|
+
return (0, show_config_1.showConfig)({
|
|
93
|
+
configPath: paths.configPath,
|
|
94
|
+
providersPath: paths.providersPath,
|
|
95
|
+
profileName: parsed.positionals[0] ?? null,
|
|
96
|
+
});
|
|
97
|
+
case "config-list-profiles":
|
|
98
|
+
return (0, list_config_profiles_1.listConfigProfilesView)({
|
|
99
|
+
configPath: paths.configPath,
|
|
100
|
+
providersPath: paths.providersPath,
|
|
101
|
+
});
|
|
102
|
+
case "switch": {
|
|
103
|
+
let providerName = parsed.positionals[0] ?? null;
|
|
104
|
+
if (!providerName && (0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
|
|
105
|
+
providerName = await (0, interactive_1.promptForProviderSelection)(runtime, paths.providersPath, "Choose a provider to switch to");
|
|
106
|
+
}
|
|
107
|
+
if (!providerName) {
|
|
108
|
+
throw (0, errors_1.cliError)("PROVIDER_NOT_FOUND", "Missing provider name for switch command.");
|
|
109
|
+
}
|
|
110
|
+
return (0, switch_provider_1.switchProvider)({
|
|
111
|
+
codexDir: paths.codexDir,
|
|
112
|
+
backupsDir: paths.backupsDir,
|
|
113
|
+
latestBackupPath: paths.latestBackupPath,
|
|
114
|
+
configPath: paths.configPath,
|
|
115
|
+
providersPath: paths.providersPath,
|
|
116
|
+
authPath: paths.authPath,
|
|
117
|
+
providerName,
|
|
118
|
+
noLogin: (0, args_1.hasFlag)(parsed.commandOptions, "--no-login"),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
case "import": {
|
|
122
|
+
const sourceFile = parsed.positionals[0];
|
|
123
|
+
if (!sourceFile) {
|
|
124
|
+
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "Missing import file path.");
|
|
125
|
+
}
|
|
126
|
+
const merge = (0, args_1.hasFlag)(parsed.commandOptions, "--merge");
|
|
127
|
+
if ((0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
|
|
128
|
+
await (0, interactive_1.confirmImport)(runtime, sourceFile, merge);
|
|
129
|
+
const document = (0, config_repo_1.readStructuredConfig)(paths.configPath);
|
|
130
|
+
const imported = (0, providers_1.validateProvidersShape)(JSON.parse(fs.readFileSync(sourceFile, "utf8")));
|
|
131
|
+
const current = (0, providers_repo_1.readProvidersFileIfExists)(paths.providersPath);
|
|
132
|
+
const next = merge ? (0, providers_repo_1.mergeProviders)(current, imported) : imported;
|
|
133
|
+
(0, config_1.buildManagedProfileViews)(document, next)
|
|
134
|
+
.filter((view) => view.source === "orphaned-reference")
|
|
135
|
+
.map((view) => view.name)
|
|
136
|
+
.sort();
|
|
137
|
+
}
|
|
138
|
+
return (0, import_providers_1.importProviders)({
|
|
139
|
+
codexDir: paths.codexDir,
|
|
140
|
+
backupsDir: paths.backupsDir,
|
|
141
|
+
latestBackupPath: paths.latestBackupPath,
|
|
142
|
+
providersPath: paths.providersPath,
|
|
143
|
+
configPath: paths.configPath,
|
|
144
|
+
sourceFile,
|
|
145
|
+
merge,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
case "export": {
|
|
149
|
+
const targetFile = parsed.positionals[0];
|
|
150
|
+
if (!targetFile) {
|
|
151
|
+
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "Missing export file path.");
|
|
152
|
+
}
|
|
153
|
+
let force = (0, args_1.hasFlag)(parsed.commandOptions, "--force");
|
|
154
|
+
if (!force && (0, interactive_1.canPrompt)(runtime, ctx.options.json) && (0, interactive_1.exportTargetExists)(targetFile)) {
|
|
155
|
+
const confirmed = await (0, interactive_1.confirmExportOverwrite)(runtime, targetFile);
|
|
156
|
+
if (!confirmed) {
|
|
157
|
+
throw (0, errors_1.cliError)("PROMPT_CANCELLED", "Export cancelled.");
|
|
158
|
+
}
|
|
159
|
+
force = true;
|
|
160
|
+
}
|
|
161
|
+
return (0, export_providers_1.exportProviders)({
|
|
162
|
+
providersPath: paths.providersPath,
|
|
163
|
+
targetFile,
|
|
164
|
+
force,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
case "add": {
|
|
168
|
+
let providerName = parsed.positionals[0] ?? null;
|
|
169
|
+
let profile = (0, args_1.getSingleOption)(parsed.commandOptions, "--profile");
|
|
170
|
+
let apiKey = (0, args_1.getSingleOption)(parsed.commandOptions, "--api-key");
|
|
171
|
+
let baseUrl = (0, args_1.getSingleOption)(parsed.commandOptions, "--base-url", false);
|
|
172
|
+
let model = (0, args_1.getSingleOption)(parsed.commandOptions, "--model", false);
|
|
173
|
+
let note = (0, args_1.getSingleOption)(parsed.commandOptions, "--note", false);
|
|
174
|
+
let tags = parsed.commandOptions.get("--tag") ?? [];
|
|
175
|
+
const createProfile = (0, args_1.hasFlag)(parsed.commandOptions, "--create-profile");
|
|
176
|
+
if (!providerName || !profile || !apiKey) {
|
|
177
|
+
if (ctx.options.json || !runtime.isInteractive()) {
|
|
178
|
+
throw (0, add_interactive_1.createNonInteractiveAddError)();
|
|
179
|
+
}
|
|
180
|
+
const prompted = await (0, add_interactive_1.collectAddInput)(runtime, {
|
|
181
|
+
providerName,
|
|
182
|
+
profile,
|
|
183
|
+
apiKey,
|
|
184
|
+
baseUrl,
|
|
185
|
+
note,
|
|
186
|
+
tags,
|
|
187
|
+
}, (candidate) => Boolean((0, providers_repo_1.readProvidersFileIfExists)(paths.providersPath).providers[candidate]));
|
|
188
|
+
providerName = prompted.providerName;
|
|
189
|
+
profile = prompted.profile;
|
|
190
|
+
apiKey = prompted.apiKey;
|
|
191
|
+
baseUrl = prompted.baseUrl ?? null;
|
|
192
|
+
note = prompted.note ?? null;
|
|
193
|
+
tags = prompted.tags;
|
|
194
|
+
}
|
|
195
|
+
return (0, add_provider_1.addProvider)({
|
|
196
|
+
codexDir: paths.codexDir,
|
|
197
|
+
backupsDir: paths.backupsDir,
|
|
198
|
+
latestBackupPath: paths.latestBackupPath,
|
|
199
|
+
providersPath: paths.providersPath,
|
|
200
|
+
configPath: paths.configPath,
|
|
201
|
+
providerName,
|
|
202
|
+
profile,
|
|
203
|
+
apiKey,
|
|
204
|
+
baseUrl,
|
|
205
|
+
model,
|
|
206
|
+
note,
|
|
207
|
+
tags,
|
|
208
|
+
createProfile,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
case "edit": {
|
|
212
|
+
let providerName = parsed.positionals[0] ?? null;
|
|
213
|
+
if (!providerName && (0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
|
|
214
|
+
providerName = await (0, interactive_1.promptForProviderSelection)(runtime, paths.providersPath, "Choose a provider to edit");
|
|
215
|
+
}
|
|
216
|
+
if (!providerName) {
|
|
217
|
+
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "Missing provider name for edit command.");
|
|
218
|
+
}
|
|
219
|
+
let profile = (0, args_1.getSingleOption)(parsed.commandOptions, "--profile", false) ?? undefined;
|
|
220
|
+
let apiKey = (0, args_1.getSingleOption)(parsed.commandOptions, "--api-key", false) ?? undefined;
|
|
221
|
+
let baseUrl = (0, args_1.getSingleOption)(parsed.commandOptions, "--base-url", false) ?? undefined;
|
|
222
|
+
let model = (0, args_1.getSingleOption)(parsed.commandOptions, "--model", false) ?? undefined;
|
|
223
|
+
let note = (0, args_1.getSingleOption)(parsed.commandOptions, "--note", false) ?? undefined;
|
|
224
|
+
let tags = parsed.commandOptions.has("--tag") ? parsed.commandOptions.get("--tag") ?? [] : undefined;
|
|
225
|
+
const createProfile = (0, args_1.hasFlag)(parsed.commandOptions, "--create-profile");
|
|
226
|
+
const switchToProfile = (0, args_1.getSingleOption)(parsed.commandOptions, "--switch-to", false) ?? undefined;
|
|
227
|
+
if (profile === undefined &&
|
|
228
|
+
apiKey === undefined &&
|
|
229
|
+
baseUrl === undefined &&
|
|
230
|
+
model === undefined &&
|
|
231
|
+
note === undefined &&
|
|
232
|
+
tags === undefined &&
|
|
233
|
+
(0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
|
|
234
|
+
const provider = (0, providers_repo_1.readProvidersFileIfExists)(paths.providersPath).providers[providerName];
|
|
235
|
+
if (!provider) {
|
|
236
|
+
throw (0, errors_1.cliError)("PROVIDER_NOT_FOUND", `Provider "${providerName}" was not found.`);
|
|
237
|
+
}
|
|
238
|
+
const prompted = await (0, interactive_1.collectEditInput)(runtime, provider);
|
|
239
|
+
profile = prompted.profile;
|
|
240
|
+
apiKey = prompted.apiKey;
|
|
241
|
+
baseUrl = prompted.baseUrl;
|
|
242
|
+
note = prompted.note;
|
|
243
|
+
tags = prompted.tags;
|
|
244
|
+
}
|
|
245
|
+
if (profile === undefined && apiKey === undefined && baseUrl === undefined && model === undefined && note === undefined && tags === undefined) {
|
|
246
|
+
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "edit requires at least one field to update.");
|
|
247
|
+
}
|
|
248
|
+
return (0, edit_provider_1.editProvider)({
|
|
249
|
+
codexDir: paths.codexDir,
|
|
250
|
+
backupsDir: paths.backupsDir,
|
|
251
|
+
latestBackupPath: paths.latestBackupPath,
|
|
252
|
+
providersPath: paths.providersPath,
|
|
253
|
+
configPath: paths.configPath,
|
|
254
|
+
providerName,
|
|
255
|
+
profile,
|
|
256
|
+
apiKey,
|
|
257
|
+
baseUrl,
|
|
258
|
+
model,
|
|
259
|
+
note,
|
|
260
|
+
tags,
|
|
261
|
+
createProfile,
|
|
262
|
+
switchToProfile,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
case "remove": {
|
|
266
|
+
let providerName = parsed.positionals[0] ?? null;
|
|
267
|
+
const force = (0, args_1.hasFlag)(parsed.commandOptions, "--force");
|
|
268
|
+
const switchToProfile = (0, args_1.getSingleOption)(parsed.commandOptions, "--switch-to", false) ?? undefined;
|
|
269
|
+
if (!providerName && (0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
|
|
270
|
+
providerName = await (0, interactive_1.promptForProviderSelection)(runtime, paths.providersPath, "Choose a provider to remove");
|
|
271
|
+
}
|
|
272
|
+
if (!providerName) {
|
|
273
|
+
throw (0, errors_1.cliError)("PROVIDER_NOT_FOUND", "Missing provider name for remove command.");
|
|
274
|
+
}
|
|
275
|
+
if (!force && !(0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
|
|
276
|
+
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "remove requires --force.");
|
|
277
|
+
}
|
|
278
|
+
if ((0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
|
|
279
|
+
await (0, interactive_1.confirmProviderRemoval)(runtime, providerName);
|
|
280
|
+
}
|
|
281
|
+
return (0, remove_provider_1.removeProvider)({
|
|
282
|
+
codexDir: paths.codexDir,
|
|
283
|
+
backupsDir: paths.backupsDir,
|
|
284
|
+
latestBackupPath: paths.latestBackupPath,
|
|
285
|
+
providersPath: paths.providersPath,
|
|
286
|
+
configPath: paths.configPath,
|
|
287
|
+
providerName,
|
|
288
|
+
switchToProfile,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
case "doctor":
|
|
292
|
+
return (0, run_doctor_1.runDoctor)({
|
|
293
|
+
codexDir: paths.codexDir,
|
|
294
|
+
configPath: paths.configPath,
|
|
295
|
+
providersPath: paths.providersPath,
|
|
296
|
+
});
|
|
297
|
+
case "setup": {
|
|
298
|
+
let codexDir = ctx.options.codexDir;
|
|
299
|
+
const candidates = (0, config_repo_1.findCodexDirCandidates)(ctx.options.codexDirExplicit ? ctx.options.codexDir : null);
|
|
300
|
+
if (!ctx.options.codexDirExplicit) {
|
|
301
|
+
if (candidates.length > 1) {
|
|
302
|
+
if (!(0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
|
|
303
|
+
throw (0, errors_1.cliError)("CODEX_DIR_AMBIGUOUS", "Multiple Codex directories were found.", {
|
|
304
|
+
candidates,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
codexDir = await (0, interactive_1.chooseCodexDir)(runtime, candidates);
|
|
308
|
+
}
|
|
309
|
+
else if (candidates.length === 0) {
|
|
310
|
+
if (!(0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
|
|
311
|
+
throw (0, errors_1.cliError)("CODEX_DIR_NOT_FOUND", "No Codex directory could be found.");
|
|
312
|
+
}
|
|
313
|
+
codexDir = await (0, interactive_1.chooseCodexDir)(runtime, candidates);
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
codexDir = candidates[0];
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
setupPaths = (0, codex_paths_1.createCodexPaths)(codexDir);
|
|
320
|
+
const overwrite = (0, args_1.hasFlag)(parsed.commandOptions, "--overwrite");
|
|
321
|
+
const merge = (0, args_1.hasFlag)(parsed.commandOptions, "--merge");
|
|
322
|
+
if (overwrite && merge) {
|
|
323
|
+
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "setup does not allow both --merge and --overwrite.");
|
|
324
|
+
}
|
|
325
|
+
let strategy = overwrite ? "overwrite" : merge ? "merge" : null;
|
|
326
|
+
const providersExists = fs.existsSync(setupPaths.providersPath);
|
|
327
|
+
if (providersExists && strategy === null) {
|
|
328
|
+
if (!(0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
|
|
329
|
+
throw (0, errors_1.cliError)("PROVIDERS_ALREADY_EXISTS", "providers.json already exists. Pass --merge or --overwrite.", {
|
|
330
|
+
file: setupPaths.providersPath,
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
const selected = await (0, interactive_1.chooseSetupStrategy)(runtime);
|
|
334
|
+
if (selected === "cancel") {
|
|
335
|
+
throw (0, errors_1.cliError)("PROMPT_CANCELLED", "Setup cancelled.");
|
|
336
|
+
}
|
|
337
|
+
strategy = selected;
|
|
338
|
+
}
|
|
339
|
+
const document = (0, config_repo_1.readStructuredConfig)(setupPaths.configPath);
|
|
340
|
+
const adoptableProfiles = (0, config_1.buildManagedProfileViews)(document, null)
|
|
341
|
+
.filter((view) => view.source === "unmanaged" && view.model && view.modelProvider === view.name && view.baseUrl)
|
|
342
|
+
.map((view) => ({
|
|
343
|
+
name: view.name,
|
|
344
|
+
model: view.model,
|
|
345
|
+
baseUrl: view.baseUrl,
|
|
346
|
+
}))
|
|
347
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
348
|
+
const selectedProfiles = Array.from((0, config_repo_1.listConfigProfiles)(setupPaths.configPath)).sort();
|
|
349
|
+
let adoptProfiles = [];
|
|
350
|
+
let providerDetailsByProfile = {};
|
|
351
|
+
if ((0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
|
|
352
|
+
adoptProfiles = await (0, interactive_1.chooseSetupProfiles)(runtime, adoptableProfiles);
|
|
353
|
+
providerDetailsByProfile = await (0, interactive_1.collectSetupProviderDetails)(runtime, adoptProfiles);
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "setup currently requires an interactive TTY to choose adoptable profiles and collect provider details.", {
|
|
357
|
+
adoptableProfiles,
|
|
358
|
+
availableProfiles: selectedProfiles,
|
|
359
|
+
suggestion: "Run `codexs setup` in an interactive terminal. Non-interactive setup input flags are not available in 0.0.6.",
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
return (0, setup_codex_1.setupCodex)({
|
|
363
|
+
codexDirOption: ctx.options.codexDir,
|
|
364
|
+
codexDir: setupPaths.codexDir,
|
|
365
|
+
configPath: setupPaths.configPath,
|
|
366
|
+
providersPath: setupPaths.providersPath,
|
|
367
|
+
backupsDir: setupPaths.backupsDir,
|
|
368
|
+
latestBackupPath: setupPaths.latestBackupPath,
|
|
369
|
+
strategy: strategy ?? "overwrite",
|
|
370
|
+
adoptProfiles,
|
|
371
|
+
providerDetailsByProfile,
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
case "backups-list":
|
|
375
|
+
return (0, list_backups_1.listBackupEntries)(paths.backupsDir);
|
|
376
|
+
case "rollback":
|
|
377
|
+
if (parsed.positionals.length > 1) {
|
|
378
|
+
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "rollback accepts at most one backup id.");
|
|
379
|
+
}
|
|
380
|
+
if ((0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
|
|
381
|
+
await (0, interactive_1.confirmRollback)(runtime, paths.latestBackupPath, paths.backupsDir, parsed.positionals[0] ?? null);
|
|
382
|
+
}
|
|
383
|
+
return (0, rollback_backup_1.rollbackBackup)({
|
|
384
|
+
latestBackupPath: paths.latestBackupPath,
|
|
385
|
+
backupsDir: paths.backupsDir,
|
|
386
|
+
backupId: parsed.positionals[0] ?? null,
|
|
387
|
+
});
|
|
388
|
+
default:
|
|
389
|
+
throw (0, errors_1.cliError)("UNKNOWN_COMMAND", `Unknown command: ${ctx.command}`);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
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 setup",
|
|
63
|
+
" codexs list",
|
|
64
|
+
" codexs switch",
|
|
65
|
+
" codexs add packycode --profile packycode --api-key sk-xxx",
|
|
66
|
+
" codexs config show",
|
|
67
|
+
" codexs remove freemodel",
|
|
68
|
+
" codexs backups list",
|
|
69
|
+
" codexs rollback",
|
|
70
|
+
" codexs help add",
|
|
71
|
+
].join("\n");
|
|
72
|
+
}
|
|
73
|
+
const nestedCommands = (0, registry_1.getNestedCommandTokens)(commandName);
|
|
74
|
+
if (nestedCommands.length > 0) {
|
|
75
|
+
return [
|
|
76
|
+
`codexs ${commandName}`,
|
|
77
|
+
"",
|
|
78
|
+
`Available ${commandName} commands:`,
|
|
79
|
+
...nestedCommands.map((name) => ` ${name}`),
|
|
80
|
+
"",
|
|
81
|
+
"Use `codexs help <command>` for detailed usage.",
|
|
82
|
+
].join("\n");
|
|
83
|
+
}
|
|
84
|
+
const command = (0, registry_1.findCommandDefinitionByTokens)(commandName.split(" "));
|
|
85
|
+
if (!command) {
|
|
86
|
+
return [
|
|
87
|
+
`Unknown help topic: ${commandName}`,
|
|
88
|
+
"",
|
|
89
|
+
"Available commands:",
|
|
90
|
+
...getKnownCommandNames().map((name) => ` ${name}`),
|
|
91
|
+
].join("\n");
|
|
92
|
+
}
|
|
93
|
+
return [
|
|
94
|
+
`codexs ${command.tokens.join(" ")}`,
|
|
95
|
+
"",
|
|
96
|
+
command.summary,
|
|
97
|
+
"",
|
|
98
|
+
"Usage:",
|
|
99
|
+
...command.usage.map((usage) => ` ${usage}`),
|
|
100
|
+
"",
|
|
101
|
+
"Details:",
|
|
102
|
+
...command.details.map((detail) => ` ${detail}`),
|
|
103
|
+
"",
|
|
104
|
+
"Examples:",
|
|
105
|
+
...command.examples.map((example) => ` ${example}`),
|
|
106
|
+
].join("\n");
|
|
107
|
+
}
|
|
108
|
+
function renderGroupedCommands() {
|
|
109
|
+
const lines = [];
|
|
110
|
+
for (const group of ["read", "write", "recovery"]) {
|
|
111
|
+
lines.push(`${GROUP_TITLES[group]}:`);
|
|
112
|
+
for (const command of registry_1.COMMANDS.filter((candidate) => candidate.group === group)) {
|
|
113
|
+
lines.push(` ${command.tokens.join(" ").padEnd(12, " ")} ${command.summary}`);
|
|
114
|
+
}
|
|
115
|
+
lines.push("");
|
|
116
|
+
}
|
|
117
|
+
lines.pop();
|
|
118
|
+
return lines;
|
|
119
|
+
}
|