@minniexcode/codex-switch 0.0.3 → 0.0.5
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 +8 -3
- package/README.md +160 -91
- package/dist/app/add-provider.js +32 -1
- package/dist/app/edit-provider.js +137 -0
- package/dist/app/get-status.js +9 -2
- package/dist/app/import-providers.js +47 -2
- package/dist/app/list-backups.js +17 -0
- package/dist/app/list-config-profiles.js +29 -0
- package/dist/app/remove-provider.js +34 -2
- package/dist/app/rollback-backup.js +30 -0
- package/dist/app/run-doctor.js +22 -21
- package/dist/app/setup-codex.js +155 -0
- package/dist/app/show-config.js +34 -0
- package/dist/app/show-provider.js +22 -0
- package/dist/app/switch-provider.js +5 -2
- package/dist/cli/add-interactive.js +25 -31
- package/dist/cli/args.js +19 -5
- package/dist/cli/help.js +109 -14
- package/dist/cli/interactive.js +123 -8
- package/dist/cli/output.js +56 -1
- package/dist/cli/prompt.js +19 -2
- package/dist/cli.js +250 -13
- package/dist/domain/backups.js +103 -0
- package/dist/domain/config.js +471 -39
- package/dist/domain/errors.js +3 -3
- package/dist/domain/providers.js +10 -0
- package/dist/domain/setup.js +30 -0
- package/dist/infra/backup-repo.js +65 -6
- package/dist/infra/codex-cli.js +79 -2
- package/dist/infra/codex-discovery.js +10 -0
- package/dist/infra/codex-paths.js +14 -1
- package/dist/infra/config-repo.js +102 -9
- package/dist/infra/providers-repo.js +29 -0
- package/docs/Design/codex-switch-v0.0.4-design.md +874 -0
- package/docs/Design/codex-switch-v0.0.5-design.md +922 -0
- package/docs/PRD/codex-switch-prd-v0.0.5-to-v0.1.0.md +308 -0
- package/docs/PRD/codex-switch-prd-v0.1.0.md +343 -0
- package/docs/{codex-switch-prd.md → PRD/codex-switch-prd.md} +9 -5
- package/docs/cli-usage.md +580 -0
- package/docs/codex-switch-command-design.md +1 -1
- package/docs/codex-switch-product-overview.md +1 -1
- package/docs/codex-switch-product-research.md +2 -2
- package/docs/codex-switch-technical-architecture.md +1 -1
- package/package.json +1 -1
package/dist/cli/help.js
CHANGED
|
@@ -9,6 +9,40 @@ const GROUP_TITLES = {
|
|
|
9
9
|
recovery: "Diagnostics And Recovery",
|
|
10
10
|
};
|
|
11
11
|
const COMMANDS = [
|
|
12
|
+
{
|
|
13
|
+
name: "config-show",
|
|
14
|
+
group: "read",
|
|
15
|
+
summary: "Show the structured config profile view.",
|
|
16
|
+
usage: ["codexs config show [profile] [--json] [--codex-dir <path>]"],
|
|
17
|
+
details: [
|
|
18
|
+
"Returns all recognizable config profiles by default, including unmanaged and orphaned references.",
|
|
19
|
+
"Passing [profile] narrows the response to one profile while preserving the same shape.",
|
|
20
|
+
],
|
|
21
|
+
examples: ["codexs config show", "codexs config show packycode --json"],
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: "config-list-profiles",
|
|
25
|
+
group: "read",
|
|
26
|
+
summary: "List recognizable config profiles with managed-state hints.",
|
|
27
|
+
usage: ["codexs config list-profiles [--json] [--codex-dir <path>]"],
|
|
28
|
+
details: [
|
|
29
|
+
"Lists managed, unmanaged, and orphaned config profiles in one stable view.",
|
|
30
|
+
"Use config show for richer single-profile details.",
|
|
31
|
+
],
|
|
32
|
+
examples: ["codexs config list-profiles", "codexs config list-profiles --json"],
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: "setup",
|
|
36
|
+
group: "write",
|
|
37
|
+
summary: "Initialize providers.json from an existing Codex directory.",
|
|
38
|
+
usage: ["codexs setup [--json] [--codex-dir <path>] [--merge|--overwrite]"],
|
|
39
|
+
details: [
|
|
40
|
+
"Reads config.toml profiles, collects complete provider records, then writes providers.json under managed backup flow.",
|
|
41
|
+
"TTY mode can collect missing provider details and choose merge or overwrite when providers.json already exists.",
|
|
42
|
+
"Non-TTY and --json runs stay non-interactive and require explicit strategy when providers.json already exists.",
|
|
43
|
+
],
|
|
44
|
+
examples: ["codexs setup", "codexs setup --overwrite --json --codex-dir ~/.codex"],
|
|
45
|
+
},
|
|
12
46
|
{
|
|
13
47
|
name: "list",
|
|
14
48
|
group: "read",
|
|
@@ -20,6 +54,18 @@ const COMMANDS = [
|
|
|
20
54
|
],
|
|
21
55
|
examples: ["codexs list", "codexs list --json"],
|
|
22
56
|
},
|
|
57
|
+
{
|
|
58
|
+
name: "show",
|
|
59
|
+
group: "read",
|
|
60
|
+
summary: "Show one provider record from providers.json.",
|
|
61
|
+
usage: ["codexs show <provider> [--json] [--codex-dir <path>]"],
|
|
62
|
+
details: [
|
|
63
|
+
"Human-readable output masks apiKey by default.",
|
|
64
|
+
"TTY mode can select a missing provider interactively before showing its record.",
|
|
65
|
+
"JSON mode returns the full provider payload for local automation.",
|
|
66
|
+
],
|
|
67
|
+
examples: ["codexs show packycode", "codexs show packycode --json"],
|
|
68
|
+
},
|
|
23
69
|
{
|
|
24
70
|
name: "current",
|
|
25
71
|
group: "read",
|
|
@@ -38,23 +84,44 @@ const COMMANDS = [
|
|
|
38
84
|
usage: ["codexs status [--json] [--codex-dir <path>]"],
|
|
39
85
|
details: [
|
|
40
86
|
"Reports file presence, current profile, and whether the live profile is mapped.",
|
|
87
|
+
"Surfaces config consistency signals without mutating any files.",
|
|
41
88
|
"Use doctor for deeper diagnostics.",
|
|
42
89
|
],
|
|
43
90
|
examples: ["codexs status", "codexs status --json --codex-dir ./.tmp-codex"],
|
|
44
91
|
},
|
|
92
|
+
{
|
|
93
|
+
name: "edit",
|
|
94
|
+
group: "write",
|
|
95
|
+
summary: "Update fields on a single provider record.",
|
|
96
|
+
usage: [
|
|
97
|
+
"codexs edit <provider> [--profile <name>] [--api-key <key>] [--base-url <url>] [--note <text>] [--tag <tag> ...] [--json] [--codex-dir <path>]",
|
|
98
|
+
"codexs edit <provider> --profile <name> --create-profile --model <name> --base-url <url>",
|
|
99
|
+
],
|
|
100
|
+
details: [
|
|
101
|
+
"Passed flags replace only the selected fields and keep the rest unchanged.",
|
|
102
|
+
"TTY mode can first select a provider, then prompt for fields when no editable options were provided.",
|
|
103
|
+
"Interactive tags use preset multi-select plus optional custom comma-separated input.",
|
|
104
|
+
"When rebinding to a missing profile, --create-profile requires both --model and --base-url.",
|
|
105
|
+
"Backs up providers.json and config.toml before writing.",
|
|
106
|
+
],
|
|
107
|
+
examples: ["codexs edit packycode --note primary", "codexs edit packycode --tag daily --tag paid --json"],
|
|
108
|
+
},
|
|
45
109
|
{
|
|
46
110
|
name: "add",
|
|
47
111
|
group: "write",
|
|
48
112
|
summary: "Add a provider with explicit flags or progressive TTY prompts.",
|
|
49
113
|
usage: [
|
|
50
114
|
"codexs add <provider> --profile <name> --api-key <key> [--base-url <url>] [--note <text>] [--tag <tag> ...]",
|
|
115
|
+
"codexs add <provider> --profile <name> --api-key <key> --create-profile --model <name> --base-url <url>",
|
|
51
116
|
"codexs add [--profile <name>] [--api-key <key>] [--base-url <url>] [--note <text>] [--tag <tag> ...]",
|
|
52
117
|
],
|
|
53
118
|
details: [
|
|
54
119
|
"Prompts only for missing required values when stdin/stdout are TTYs and --json is not set.",
|
|
55
|
-
"
|
|
120
|
+
"Interactive add collects provider name, profile, and apiKey progressively as plain text inputs.",
|
|
56
121
|
"Confirm API key when prompted interactively because the hidden prompt asks twice before writing.",
|
|
122
|
+
"Interactive tags use preset multi-select plus optional custom comma-separated input.",
|
|
57
123
|
"Automation and non-TTY environments must pass all required values explicitly.",
|
|
124
|
+
"Creating a missing profile section requires --create-profile together with --model and --base-url.",
|
|
58
125
|
],
|
|
59
126
|
examples: [
|
|
60
127
|
"codexs add packycode --profile packycode --api-key sk-xxx",
|
|
@@ -79,12 +146,13 @@ const COMMANDS = [
|
|
|
79
146
|
name: "remove",
|
|
80
147
|
group: "write",
|
|
81
148
|
summary: "Remove a provider from providers.json.",
|
|
82
|
-
usage: ["codexs remove <provider> [--force] [--json] [--codex-dir <path>]"],
|
|
149
|
+
usage: ["codexs remove <provider> [--force] [--switch-to <profile>] [--json] [--codex-dir <path>]"],
|
|
83
150
|
details: [
|
|
84
151
|
"TTY mode can select a missing provider interactively and always asks for deletion confirmation.",
|
|
85
152
|
"Non-TTY and --json automation still require both <provider> and --force.",
|
|
86
153
|
"The confirmation prompt includes the provider name and cancels without writing when declined.",
|
|
87
|
-
"
|
|
154
|
+
"When removing the last provider linked to the active profile, pass --switch-to first.",
|
|
155
|
+
"Backs up providers.json and config.toml before removing the record.",
|
|
88
156
|
],
|
|
89
157
|
examples: ["codexs remove freemodel", "codexs remove freemodel --force --json"],
|
|
90
158
|
},
|
|
@@ -95,10 +163,10 @@ const COMMANDS = [
|
|
|
95
163
|
usage: ["codexs import <file> [--json] [--codex-dir <path>]"],
|
|
96
164
|
details: [
|
|
97
165
|
"The file path is always explicit; there is no path wizard in this release.",
|
|
98
|
-
"TTY mode asks for confirmation before replacing the current providers registry.",
|
|
166
|
+
"TTY mode asks for confirmation before replacing or merging into the current providers registry.",
|
|
99
167
|
"Non-TTY and --json runs stay non-interactive and validate the file before writing.",
|
|
100
168
|
],
|
|
101
|
-
examples: ["codexs import ./providers.json", "codexs import ./providers.json --json"],
|
|
169
|
+
examples: ["codexs import ./providers.json", "codexs import ./providers.json --merge --json"],
|
|
102
170
|
},
|
|
103
171
|
{
|
|
104
172
|
name: "export",
|
|
@@ -112,6 +180,17 @@ const COMMANDS = [
|
|
|
112
180
|
],
|
|
113
181
|
examples: ["codexs export ./providers-backup.json", "codexs export ./providers-backup.json --force"],
|
|
114
182
|
},
|
|
183
|
+
{
|
|
184
|
+
name: "backups",
|
|
185
|
+
group: "recovery",
|
|
186
|
+
summary: "List historical backup entries.",
|
|
187
|
+
usage: ["codexs backups list [--json] [--codex-dir <path>]"],
|
|
188
|
+
details: [
|
|
189
|
+
"Enumerates backups/ manifests and returns them newest first.",
|
|
190
|
+
"Corrupt backup manifests are skipped with warnings instead of failing the whole command.",
|
|
191
|
+
],
|
|
192
|
+
examples: ["codexs backups list", "codexs backups list --json"],
|
|
193
|
+
},
|
|
115
194
|
{
|
|
116
195
|
name: "doctor",
|
|
117
196
|
group: "recovery",
|
|
@@ -126,14 +205,14 @@ const COMMANDS = [
|
|
|
126
205
|
{
|
|
127
206
|
name: "rollback",
|
|
128
207
|
group: "recovery",
|
|
129
|
-
summary: "Restore the latest managed backup.",
|
|
130
|
-
usage: ["codexs rollback [--json] [--codex-dir <path>]"],
|
|
208
|
+
summary: "Restore the latest managed backup or a specific backup id.",
|
|
209
|
+
usage: ["codexs rollback [<backup-id>] [--json] [--codex-dir <path>]"],
|
|
131
210
|
details: [
|
|
132
|
-
"TTY mode previews the
|
|
211
|
+
"TTY mode previews the target backup path and affected files, then asks for confirmation.",
|
|
133
212
|
"Non-TTY and --json runs stay non-interactive and execute immediately.",
|
|
134
213
|
"Use after a failed or undesired managed mutation.",
|
|
135
214
|
],
|
|
136
|
-
examples: ["codexs rollback", "codexs rollback --json"],
|
|
215
|
+
examples: ["codexs rollback", "codexs rollback 20260511-221457-switch --json"],
|
|
137
216
|
},
|
|
138
217
|
];
|
|
139
218
|
const COMMAND_NAME_SET = new Set(COMMANDS.map((command) => command.name));
|
|
@@ -141,9 +220,18 @@ function getKnownCommandNames() {
|
|
|
141
220
|
return COMMANDS.map((command) => command.name);
|
|
142
221
|
}
|
|
143
222
|
function isKnownCommandName(commandName) {
|
|
144
|
-
return COMMAND_NAME_SET.has(commandName);
|
|
223
|
+
return COMMAND_NAME_SET.has(commandName) || commandName === "backups-list";
|
|
145
224
|
}
|
|
146
225
|
function buildHelpText(commandName) {
|
|
226
|
+
const normalizedCommandName = commandName === "backups-list"
|
|
227
|
+
? "backups"
|
|
228
|
+
: commandName === "config-show"
|
|
229
|
+
? "config-show"
|
|
230
|
+
: commandName === "config-list-profiles"
|
|
231
|
+
? "config-list-profiles"
|
|
232
|
+
: commandName === "config"
|
|
233
|
+
? "config-show"
|
|
234
|
+
: commandName;
|
|
147
235
|
if (!commandName) {
|
|
148
236
|
return [
|
|
149
237
|
"codex-switch",
|
|
@@ -162,6 +250,10 @@ function buildHelpText(commandName) {
|
|
|
162
250
|
" --help Show top-level or command-specific help.",
|
|
163
251
|
" --version Print the current CLI version.",
|
|
164
252
|
"",
|
|
253
|
+
"Environment:",
|
|
254
|
+
" CODEXS_CODEX_DIR Default Codex directory when --codex-dir is not passed.",
|
|
255
|
+
" NODE_ENV=development defaults to ./dev-codex/local-sandbox when no override is set.",
|
|
256
|
+
"",
|
|
165
257
|
"Interactive rules:",
|
|
166
258
|
" Progressive prompts only run in a real TTY and never run under --json.",
|
|
167
259
|
" Human write commands may guide missing inputs or ask for dangerous-action confirmation.",
|
|
@@ -169,23 +261,26 @@ function buildHelpText(commandName) {
|
|
|
169
261
|
"",
|
|
170
262
|
"Dangerous commands:",
|
|
171
263
|
" remove deletes provider records.",
|
|
172
|
-
" import replaces providers.json.",
|
|
264
|
+
" import replaces or merges providers.json.",
|
|
173
265
|
" export may overwrite a target file.",
|
|
174
|
-
" rollback restores files from
|
|
266
|
+
" rollback restores files from a managed backup.",
|
|
175
267
|
"",
|
|
176
268
|
"Examples:",
|
|
269
|
+
" codexs setup",
|
|
177
270
|
" codexs list",
|
|
178
271
|
" codexs switch",
|
|
179
272
|
" codexs add packycode --profile packycode --api-key sk-xxx",
|
|
273
|
+
" codexs config show",
|
|
180
274
|
" codexs remove freemodel",
|
|
275
|
+
" codexs backups list",
|
|
181
276
|
" codexs rollback",
|
|
182
277
|
" codexs help add",
|
|
183
278
|
].join("\n");
|
|
184
279
|
}
|
|
185
|
-
const command = COMMANDS.find((candidate) => candidate.name ===
|
|
280
|
+
const command = COMMANDS.find((candidate) => candidate.name === normalizedCommandName);
|
|
186
281
|
if (!command) {
|
|
187
282
|
return [
|
|
188
|
-
`Unknown help topic: ${
|
|
283
|
+
`Unknown help topic: ${normalizedCommandName}`,
|
|
189
284
|
"",
|
|
190
285
|
"Available commands:",
|
|
191
286
|
...getKnownCommandNames().map((name) => ` ${name}`),
|
package/dist/cli/interactive.js
CHANGED
|
@@ -40,12 +40,22 @@ exports.confirmImport = confirmImport;
|
|
|
40
40
|
exports.confirmExportOverwrite = confirmExportOverwrite;
|
|
41
41
|
exports.exportTargetExists = exportTargetExists;
|
|
42
42
|
exports.getRollbackSummary = getRollbackSummary;
|
|
43
|
+
exports.getRollbackSummaryById = getRollbackSummaryById;
|
|
43
44
|
exports.confirmRollback = confirmRollback;
|
|
45
|
+
exports.chooseSetupStrategy = chooseSetupStrategy;
|
|
46
|
+
exports.chooseCodexDir = chooseCodexDir;
|
|
47
|
+
exports.chooseSetupProfiles = chooseSetupProfiles;
|
|
48
|
+
exports.collectSetupProviderDetails = collectSetupProviderDetails;
|
|
49
|
+
exports.collectImportRepairDetails = collectImportRepairDetails;
|
|
50
|
+
exports.collectEditInput = collectEditInput;
|
|
44
51
|
const fs = __importStar(require("node:fs"));
|
|
45
52
|
const path = __importStar(require("node:path"));
|
|
46
53
|
const errors_1 = require("../domain/errors");
|
|
54
|
+
const backups_1 = require("../domain/backups");
|
|
55
|
+
const codex_paths_1 = require("../infra/codex-paths");
|
|
47
56
|
const providers_repo_1 = require("../infra/providers-repo");
|
|
48
57
|
const backup_repo_1 = require("../infra/backup-repo");
|
|
58
|
+
const add_interactive_1 = require("./add-interactive");
|
|
49
59
|
/**
|
|
50
60
|
* Keeps CLI-side interactivity rules in one place so automation paths remain explicit.
|
|
51
61
|
*/
|
|
@@ -71,13 +81,15 @@ async function confirmProviderRemoval(runtime, providerName) {
|
|
|
71
81
|
defaultValue: false,
|
|
72
82
|
});
|
|
73
83
|
if (!confirmed) {
|
|
74
|
-
throw (0, errors_1.cliError)("
|
|
84
|
+
throw (0, errors_1.cliError)("PROMPT_CANCELLED", `Removal cancelled for provider "${providerName}".`);
|
|
75
85
|
}
|
|
76
86
|
}
|
|
77
|
-
async function confirmImport(runtime, sourceFile) {
|
|
78
|
-
const confirmed = await runtime.confirmAction(
|
|
87
|
+
async function confirmImport(runtime, sourceFile, merge = false) {
|
|
88
|
+
const confirmed = await runtime.confirmAction(merge
|
|
89
|
+
? `Import providers from ${path.resolve(sourceFile)} and merge into the current registry?`
|
|
90
|
+
: `Import providers from ${path.resolve(sourceFile)} and replace the current registry?`, { defaultValue: false });
|
|
79
91
|
if (!confirmed) {
|
|
80
|
-
throw (0, errors_1.cliError)("
|
|
92
|
+
throw (0, errors_1.cliError)("PROMPT_CANCELLED", "Import cancelled.");
|
|
81
93
|
}
|
|
82
94
|
}
|
|
83
95
|
async function confirmExportOverwrite(runtime, targetFile) {
|
|
@@ -90,8 +102,16 @@ function exportTargetExists(targetFile) {
|
|
|
90
102
|
}
|
|
91
103
|
function getRollbackSummary(latestBackupPath) {
|
|
92
104
|
const manifest = (0, backup_repo_1.loadLatestManifest)(latestBackupPath);
|
|
105
|
+
return buildRollbackSummary(manifest);
|
|
106
|
+
}
|
|
107
|
+
function getRollbackSummaryById(backupsDir, backupId) {
|
|
108
|
+
const manifest = (0, backup_repo_1.loadManifestById)(backupsDir, backupId);
|
|
109
|
+
return buildRollbackSummary(manifest);
|
|
110
|
+
}
|
|
111
|
+
function buildRollbackSummary(manifest) {
|
|
93
112
|
const previewLines = [
|
|
94
113
|
"Rollback preview",
|
|
114
|
+
`Backup ID: ${(0, backups_1.getBackupId)(manifest.backupDir)}`,
|
|
95
115
|
`Backup: ${manifest.backupDir}`,
|
|
96
116
|
...manifest.files.map((file) => {
|
|
97
117
|
const suffix = file.existed ? "restore" : "remove";
|
|
@@ -100,15 +120,110 @@ function getRollbackSummary(latestBackupPath) {
|
|
|
100
120
|
];
|
|
101
121
|
return { manifest, previewLines };
|
|
102
122
|
}
|
|
103
|
-
async function confirmRollback(runtime, latestBackupPath) {
|
|
104
|
-
const { previewLines } =
|
|
123
|
+
async function confirmRollback(runtime, latestBackupPath, backupsDir, backupId) {
|
|
124
|
+
const { previewLines } = backupId && backupsDir
|
|
125
|
+
? getRollbackSummaryById(backupsDir, backupId)
|
|
126
|
+
: getRollbackSummary(latestBackupPath);
|
|
105
127
|
for (const line of previewLines) {
|
|
106
128
|
runtime.writeLine(line);
|
|
107
129
|
}
|
|
108
|
-
const confirmed = await runtime.confirmAction("Restore files from the latest backup?", {
|
|
130
|
+
const confirmed = await runtime.confirmAction(backupId ? `Restore files from backup "${backupId}"?` : "Restore files from the latest backup?", {
|
|
109
131
|
defaultValue: false,
|
|
110
132
|
});
|
|
111
133
|
if (!confirmed) {
|
|
112
|
-
throw (0, errors_1.cliError)("
|
|
134
|
+
throw (0, errors_1.cliError)("PROMPT_CANCELLED", "Rollback cancelled.");
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
async function chooseSetupStrategy(runtime) {
|
|
138
|
+
return runtime.selectOne("providers.json already exists. Choose a setup strategy.", [
|
|
139
|
+
{ value: "merge", label: "merge", hint: "keep existing providers and override by imported names" },
|
|
140
|
+
{ value: "overwrite", label: "overwrite", hint: "replace the existing registry" },
|
|
141
|
+
{ value: "cancel", label: "cancel", hint: "abort setup without writing" },
|
|
142
|
+
]);
|
|
143
|
+
}
|
|
144
|
+
async function chooseCodexDir(runtime, candidates) {
|
|
145
|
+
if (candidates.length === 0) {
|
|
146
|
+
const manual = (await runtime.inputText("Codex directory path")).trim();
|
|
147
|
+
if (!manual) {
|
|
148
|
+
throw (0, errors_1.cliError)("CODEX_DIR_NOT_FOUND", "No Codex directory was provided.");
|
|
149
|
+
}
|
|
150
|
+
return (0, codex_paths_1.resolveCodexDir)(manual);
|
|
151
|
+
}
|
|
152
|
+
if (candidates.length === 1) {
|
|
153
|
+
return candidates[0];
|
|
154
|
+
}
|
|
155
|
+
const selected = await runtime.selectOne("Choose a Codex directory", [
|
|
156
|
+
...candidates.map((candidate) => ({
|
|
157
|
+
value: candidate,
|
|
158
|
+
label: candidate,
|
|
159
|
+
})),
|
|
160
|
+
{
|
|
161
|
+
value: "__manual__",
|
|
162
|
+
label: "Enter manually",
|
|
163
|
+
},
|
|
164
|
+
]);
|
|
165
|
+
if (selected !== "__manual__") {
|
|
166
|
+
return selected;
|
|
167
|
+
}
|
|
168
|
+
const manual = (await runtime.inputText("Codex directory path")).trim();
|
|
169
|
+
if (!manual) {
|
|
170
|
+
throw (0, errors_1.cliError)("CODEX_DIR_NOT_FOUND", "No Codex directory was provided.");
|
|
171
|
+
}
|
|
172
|
+
return (0, codex_paths_1.resolveCodexDir)(manual);
|
|
173
|
+
}
|
|
174
|
+
async function chooseSetupProfiles(runtime, profiles) {
|
|
175
|
+
if (profiles.length === 0) {
|
|
176
|
+
return [];
|
|
113
177
|
}
|
|
178
|
+
return runtime.selectMany("Choose unmanaged config profiles to adopt into providers.json.", profiles.map((profile) => ({
|
|
179
|
+
value: profile.name,
|
|
180
|
+
label: profile.name,
|
|
181
|
+
hint: `${profile.model} | ${profile.baseUrl}`,
|
|
182
|
+
})));
|
|
183
|
+
}
|
|
184
|
+
async function collectSetupProviderDetails(runtime, profiles) {
|
|
185
|
+
const result = {};
|
|
186
|
+
for (const profile of profiles) {
|
|
187
|
+
const providerName = (await runtime.inputText(`Provider name for profile "${profile}"`, {
|
|
188
|
+
defaultValue: profile,
|
|
189
|
+
})).trim();
|
|
190
|
+
const apiKey = (await runtime.inputSecret(`API key for profile "${profile}"`)).trim();
|
|
191
|
+
const baseUrl = (await runtime.inputText(`Base URL for profile "${profile}" (optional)`)).trim();
|
|
192
|
+
const note = (await runtime.inputText(`Note for profile "${profile}" (optional)`)).trim();
|
|
193
|
+
const tags = await (0, add_interactive_1.promptTags)(runtime);
|
|
194
|
+
result[profile] = {
|
|
195
|
+
providerName: providerName || profile,
|
|
196
|
+
apiKey,
|
|
197
|
+
baseUrl: baseUrl || undefined,
|
|
198
|
+
note: note || undefined,
|
|
199
|
+
tags: tags.length > 0 ? tags : undefined,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
return result;
|
|
203
|
+
}
|
|
204
|
+
async function collectImportRepairDetails(runtime, profiles) {
|
|
205
|
+
const repairs = {};
|
|
206
|
+
for (const profile of profiles) {
|
|
207
|
+
const model = (await runtime.inputText(`Model for missing profile "${profile}"`)).trim();
|
|
208
|
+
const baseUrl = (await runtime.inputText(`Base URL for missing profile "${profile}"`)).trim();
|
|
209
|
+
repairs[profile] = {
|
|
210
|
+
model: model || undefined,
|
|
211
|
+
baseUrl: baseUrl || undefined,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
return repairs;
|
|
215
|
+
}
|
|
216
|
+
async function collectEditInput(runtime, current) {
|
|
217
|
+
const profile = (await runtime.inputText("Profile", { defaultValue: current.profile })).trim();
|
|
218
|
+
const apiKey = (await runtime.inputSecret("API key")).trim() || current.apiKey;
|
|
219
|
+
const baseUrl = (await runtime.inputText("Base URL (optional)", { defaultValue: current.baseUrl ?? "" })).trim();
|
|
220
|
+
const note = (await runtime.inputText("Note (optional)", { defaultValue: current.note ?? "" })).trim();
|
|
221
|
+
const tags = await (0, add_interactive_1.promptTags)(runtime, current.tags ?? []);
|
|
222
|
+
return {
|
|
223
|
+
profile,
|
|
224
|
+
apiKey,
|
|
225
|
+
baseUrl: baseUrl || undefined,
|
|
226
|
+
note: note || undefined,
|
|
227
|
+
tags,
|
|
228
|
+
};
|
|
114
229
|
}
|
package/dist/cli/output.js
CHANGED
|
@@ -95,6 +95,22 @@ function renderHumanSuccess(command, data, warnings) {
|
|
|
95
95
|
}
|
|
96
96
|
break;
|
|
97
97
|
}
|
|
98
|
+
case "show": {
|
|
99
|
+
const provider = data?.provider ?? {};
|
|
100
|
+
lines.push(`Provider: ${String(data?.providerName ?? "")}`);
|
|
101
|
+
lines.push(`profile: ${String(provider.profile ?? "")}`);
|
|
102
|
+
lines.push(`apiKey: ${String(provider.apiKey ?? "")}`);
|
|
103
|
+
if (provider.baseUrl) {
|
|
104
|
+
lines.push(`baseUrl: ${String(provider.baseUrl)}`);
|
|
105
|
+
}
|
|
106
|
+
if (provider.note) {
|
|
107
|
+
lines.push(`note: ${String(provider.note)}`);
|
|
108
|
+
}
|
|
109
|
+
if (Array.isArray(provider.tags) && provider.tags.length > 0) {
|
|
110
|
+
lines.push(`tags: ${provider.tags.join(", ")}`);
|
|
111
|
+
}
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
98
114
|
case "current":
|
|
99
115
|
lines.push(`Current profile: ${String(data?.profile ?? "")}`);
|
|
100
116
|
break;
|
|
@@ -104,23 +120,55 @@ function renderHumanSuccess(command, data, warnings) {
|
|
|
104
120
|
lines.push(`providersExists: ${String(data?.providersExists ?? false)}`);
|
|
105
121
|
lines.push(`currentProfile: ${String(data?.currentProfile ?? "")}`);
|
|
106
122
|
lines.push(`mappedProvider: ${String(data?.provider ?? "")}`);
|
|
123
|
+
lines.push(`issues: ${Array.isArray(data?.issues) ? (data?.issues).length : 0}`);
|
|
124
|
+
break;
|
|
125
|
+
case "config-show": {
|
|
126
|
+
lines.push(`activeProfile: ${String(data?.activeProfile ?? "")}`);
|
|
127
|
+
const profiles = data?.profiles ?? [];
|
|
128
|
+
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 ?? "")} baseUrl=${String(profile.baseUrl ?? "")}`);
|
|
130
|
+
}
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
case "config-list-profiles": {
|
|
134
|
+
const profiles = data?.profiles ?? [];
|
|
135
|
+
for (const profile of profiles) {
|
|
136
|
+
lines.push(`${String(profile.name)} managed=${String(profile.managed)} active=${String(profile.isActive)} source=${String(profile.source)}`);
|
|
137
|
+
}
|
|
107
138
|
break;
|
|
139
|
+
}
|
|
108
140
|
case "switch":
|
|
109
141
|
lines.push(`Switched to provider ${String(data?.provider ?? "")} using profile ${String(data?.profile ?? "")}.`);
|
|
110
142
|
lines.push(`Backup: ${String(data?.backupPath ?? "")}`);
|
|
111
143
|
lines.push(`Login performed: ${String(data?.loginPerformed ?? false)}`);
|
|
112
144
|
break;
|
|
113
145
|
case "import":
|
|
114
|
-
lines.push(`Imported providers from file. Backup: ${String(data?.backupPath ?? "")}`);
|
|
146
|
+
lines.push(`Imported providers from file using mode ${String(data?.mode ?? "replace")}. Backup: ${String(data?.backupPath ?? "")}`);
|
|
115
147
|
break;
|
|
116
148
|
case "export":
|
|
117
149
|
lines.push(`Exported providers to ${String(data?.exportedTo ?? "")}.`);
|
|
118
150
|
break;
|
|
151
|
+
case "setup":
|
|
152
|
+
lines.push(`Initialized providers in ${String(data?.codexDir ?? "")} using ${String(data?.strategy ?? "")}.`);
|
|
153
|
+
lines.push(`Providers initialized: ${String(data?.providersInitialized ?? 0)}`);
|
|
154
|
+
lines.push(`Doctor healthy: ${String(data?.doctor?.healthy ?? false)}`);
|
|
155
|
+
lines.push(`Backup: ${String(data?.backupPath ?? "")}`);
|
|
156
|
+
break;
|
|
157
|
+
case "edit":
|
|
158
|
+
lines.push(`Updated provider ${String(data?.provider ?? "")}. Backup: ${String(data?.backupPath ?? "")}`);
|
|
159
|
+
lines.push(`Updated fields: ${Array.isArray(data?.updatedFields) ? (data?.updatedFields).join(", ") : ""}`);
|
|
160
|
+
break;
|
|
119
161
|
case "add":
|
|
120
162
|
lines.push(`Added provider ${String(data?.provider ?? "")}. Backup: ${String(data?.backupPath ?? "")}`);
|
|
163
|
+
if (Array.isArray(data?.createdProfileSections) && (data?.createdProfileSections).length > 0) {
|
|
164
|
+
lines.push(`Created profiles: ${(data?.createdProfileSections).join(", ")}`);
|
|
165
|
+
}
|
|
121
166
|
break;
|
|
122
167
|
case "remove":
|
|
123
168
|
lines.push(`Removed provider ${String(data?.provider ?? "")}. Backup: ${String(data?.backupPath ?? "")}`);
|
|
169
|
+
if (Array.isArray(data?.deletedProfileSections) && (data?.deletedProfileSections).length > 0) {
|
|
170
|
+
lines.push(`Deleted profiles: ${(data?.deletedProfileSections).join(", ")}`);
|
|
171
|
+
}
|
|
124
172
|
break;
|
|
125
173
|
case "doctor": {
|
|
126
174
|
const healthy = Boolean(data?.healthy);
|
|
@@ -131,6 +179,13 @@ function renderHumanSuccess(command, data, warnings) {
|
|
|
131
179
|
}
|
|
132
180
|
break;
|
|
133
181
|
}
|
|
182
|
+
case "backups-list": {
|
|
183
|
+
const backups = data?.backups ?? [];
|
|
184
|
+
for (const backup of backups) {
|
|
185
|
+
lines.push(`${backup.backupId} ${backup.reason} ${backup.createdAt}`);
|
|
186
|
+
}
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
134
189
|
case "rollback":
|
|
135
190
|
lines.push(`Rollback restored files from ${String(data?.backupPath ?? "")}.`);
|
|
136
191
|
break;
|
package/dist/cli/prompt.js
CHANGED
|
@@ -54,6 +54,23 @@ function createPromptRuntime() {
|
|
|
54
54
|
return answer.value;
|
|
55
55
|
});
|
|
56
56
|
},
|
|
57
|
+
selectMany: async (message, choices, options) => {
|
|
58
|
+
return handlePromptCancellation(async () => {
|
|
59
|
+
const answer = await inquirer_1.default.prompt([
|
|
60
|
+
{
|
|
61
|
+
type: "checkbox",
|
|
62
|
+
name: "value",
|
|
63
|
+
message,
|
|
64
|
+
choices: choices.map((choice) => ({
|
|
65
|
+
value: choice.value,
|
|
66
|
+
name: choice.hint ? `${choice.label} (${choice.hint})` : choice.label,
|
|
67
|
+
checked: Boolean(options?.defaultValues?.includes(choice.value)),
|
|
68
|
+
})),
|
|
69
|
+
},
|
|
70
|
+
]);
|
|
71
|
+
return (Array.isArray(answer.value) ? answer.value : []);
|
|
72
|
+
});
|
|
73
|
+
},
|
|
57
74
|
confirmAction: async (message, options) => {
|
|
58
75
|
return handlePromptCancellation(async () => {
|
|
59
76
|
const answer = await inquirer_1.default.prompt([
|
|
@@ -78,9 +95,9 @@ async function handlePromptCancellation(run) {
|
|
|
78
95
|
}
|
|
79
96
|
catch (error) {
|
|
80
97
|
if (isPromptCancellation(error)) {
|
|
81
|
-
throw (0, errors_1.cliError)("
|
|
98
|
+
throw (0, errors_1.cliError)("PROMPT_CANCELLED", "Interactive prompt was cancelled.");
|
|
82
99
|
}
|
|
83
|
-
throw (0, errors_1.cliError)("
|
|
100
|
+
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "Interactive prompt failed.", {
|
|
84
101
|
cause: error instanceof Error ? error.message : String(error),
|
|
85
102
|
});
|
|
86
103
|
}
|