@minniexcode/codex-switch 0.0.4 → 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/dist/app/add-provider.js +32 -1
- package/dist/app/edit-provider.js +74 -1
- package/dist/app/get-status.js +9 -2
- package/dist/app/import-providers.js +37 -1
- package/dist/app/list-config-profiles.js +29 -0
- package/dist/app/remove-provider.js +34 -2
- package/dist/app/run-doctor.js +22 -21
- package/dist/app/setup-codex.js +33 -16
- package/dist/app/show-config.js +34 -0
- package/dist/app/switch-provider.js +5 -2
- package/dist/cli/args.js +13 -2
- package/dist/cli/help.js +42 -5
- package/dist/cli/interactive.js +56 -0
- package/dist/cli/output.js +22 -0
- package/dist/cli.js +101 -12
- package/dist/domain/config.js +471 -39
- package/dist/infra/codex-cli.js +18 -3
- package/dist/infra/codex-discovery.js +3 -41
- package/dist/infra/codex-paths.js +1 -1
- package/dist/infra/config-repo.js +102 -9
- 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 +210 -260
- package/package.json +1 -1
- /package/docs/{codex-switch-v0.0.4-design.md → Design/codex-switch-v0.0.4-design.md} +0 -0
package/dist/cli/help.js
CHANGED
|
@@ -9,6 +9,28 @@ 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
|
+
},
|
|
12
34
|
{
|
|
13
35
|
name: "setup",
|
|
14
36
|
group: "write",
|
|
@@ -62,6 +84,7 @@ const COMMANDS = [
|
|
|
62
84
|
usage: ["codexs status [--json] [--codex-dir <path>]"],
|
|
63
85
|
details: [
|
|
64
86
|
"Reports file presence, current profile, and whether the live profile is mapped.",
|
|
87
|
+
"Surfaces config consistency signals without mutating any files.",
|
|
65
88
|
"Use doctor for deeper diagnostics.",
|
|
66
89
|
],
|
|
67
90
|
examples: ["codexs status", "codexs status --json --codex-dir ./.tmp-codex"],
|
|
@@ -72,12 +95,14 @@ const COMMANDS = [
|
|
|
72
95
|
summary: "Update fields on a single provider record.",
|
|
73
96
|
usage: [
|
|
74
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>",
|
|
75
99
|
],
|
|
76
100
|
details: [
|
|
77
101
|
"Passed flags replace only the selected fields and keep the rest unchanged.",
|
|
78
102
|
"TTY mode can first select a provider, then prompt for fields when no editable options were provided.",
|
|
79
103
|
"Interactive tags use preset multi-select plus optional custom comma-separated input.",
|
|
80
|
-
"
|
|
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.",
|
|
81
106
|
],
|
|
82
107
|
examples: ["codexs edit packycode --note primary", "codexs edit packycode --tag daily --tag paid --json"],
|
|
83
108
|
},
|
|
@@ -87,6 +112,7 @@ const COMMANDS = [
|
|
|
87
112
|
summary: "Add a provider with explicit flags or progressive TTY prompts.",
|
|
88
113
|
usage: [
|
|
89
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>",
|
|
90
116
|
"codexs add [--profile <name>] [--api-key <key>] [--base-url <url>] [--note <text>] [--tag <tag> ...]",
|
|
91
117
|
],
|
|
92
118
|
details: [
|
|
@@ -95,6 +121,7 @@ const COMMANDS = [
|
|
|
95
121
|
"Confirm API key when prompted interactively because the hidden prompt asks twice before writing.",
|
|
96
122
|
"Interactive tags use preset multi-select plus optional custom comma-separated input.",
|
|
97
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.",
|
|
98
125
|
],
|
|
99
126
|
examples: [
|
|
100
127
|
"codexs add packycode --profile packycode --api-key sk-xxx",
|
|
@@ -119,12 +146,13 @@ const COMMANDS = [
|
|
|
119
146
|
name: "remove",
|
|
120
147
|
group: "write",
|
|
121
148
|
summary: "Remove a provider from providers.json.",
|
|
122
|
-
usage: ["codexs remove <provider> [--force] [--json] [--codex-dir <path>]"],
|
|
149
|
+
usage: ["codexs remove <provider> [--force] [--switch-to <profile>] [--json] [--codex-dir <path>]"],
|
|
123
150
|
details: [
|
|
124
151
|
"TTY mode can select a missing provider interactively and always asks for deletion confirmation.",
|
|
125
152
|
"Non-TTY and --json automation still require both <provider> and --force.",
|
|
126
153
|
"The confirmation prompt includes the provider name and cancels without writing when declined.",
|
|
127
|
-
"
|
|
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.",
|
|
128
156
|
],
|
|
129
157
|
examples: ["codexs remove freemodel", "codexs remove freemodel --force --json"],
|
|
130
158
|
},
|
|
@@ -195,7 +223,15 @@ function isKnownCommandName(commandName) {
|
|
|
195
223
|
return COMMAND_NAME_SET.has(commandName) || commandName === "backups-list";
|
|
196
224
|
}
|
|
197
225
|
function buildHelpText(commandName) {
|
|
198
|
-
const normalizedCommandName = commandName === "backups-list"
|
|
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;
|
|
199
235
|
if (!commandName) {
|
|
200
236
|
return [
|
|
201
237
|
"codex-switch",
|
|
@@ -216,7 +252,7 @@ function buildHelpText(commandName) {
|
|
|
216
252
|
"",
|
|
217
253
|
"Environment:",
|
|
218
254
|
" CODEXS_CODEX_DIR Default Codex directory when --codex-dir is not passed.",
|
|
219
|
-
" NODE_ENV=development defaults to ./
|
|
255
|
+
" NODE_ENV=development defaults to ./dev-codex/local-sandbox when no override is set.",
|
|
220
256
|
"",
|
|
221
257
|
"Interactive rules:",
|
|
222
258
|
" Progressive prompts only run in a real TTY and never run under --json.",
|
|
@@ -234,6 +270,7 @@ function buildHelpText(commandName) {
|
|
|
234
270
|
" codexs list",
|
|
235
271
|
" codexs switch",
|
|
236
272
|
" codexs add packycode --profile packycode --api-key sk-xxx",
|
|
273
|
+
" codexs config show",
|
|
237
274
|
" codexs remove freemodel",
|
|
238
275
|
" codexs backups list",
|
|
239
276
|
" codexs rollback",
|
package/dist/cli/interactive.js
CHANGED
|
@@ -43,12 +43,16 @@ exports.getRollbackSummary = getRollbackSummary;
|
|
|
43
43
|
exports.getRollbackSummaryById = getRollbackSummaryById;
|
|
44
44
|
exports.confirmRollback = confirmRollback;
|
|
45
45
|
exports.chooseSetupStrategy = chooseSetupStrategy;
|
|
46
|
+
exports.chooseCodexDir = chooseCodexDir;
|
|
47
|
+
exports.chooseSetupProfiles = chooseSetupProfiles;
|
|
46
48
|
exports.collectSetupProviderDetails = collectSetupProviderDetails;
|
|
49
|
+
exports.collectImportRepairDetails = collectImportRepairDetails;
|
|
47
50
|
exports.collectEditInput = collectEditInput;
|
|
48
51
|
const fs = __importStar(require("node:fs"));
|
|
49
52
|
const path = __importStar(require("node:path"));
|
|
50
53
|
const errors_1 = require("../domain/errors");
|
|
51
54
|
const backups_1 = require("../domain/backups");
|
|
55
|
+
const codex_paths_1 = require("../infra/codex-paths");
|
|
52
56
|
const providers_repo_1 = require("../infra/providers-repo");
|
|
53
57
|
const backup_repo_1 = require("../infra/backup-repo");
|
|
54
58
|
const add_interactive_1 = require("./add-interactive");
|
|
@@ -137,6 +141,46 @@ async function chooseSetupStrategy(runtime) {
|
|
|
137
141
|
{ value: "cancel", label: "cancel", hint: "abort setup without writing" },
|
|
138
142
|
]);
|
|
139
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 [];
|
|
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
|
+
}
|
|
140
184
|
async function collectSetupProviderDetails(runtime, profiles) {
|
|
141
185
|
const result = {};
|
|
142
186
|
for (const profile of profiles) {
|
|
@@ -157,6 +201,18 @@ async function collectSetupProviderDetails(runtime, profiles) {
|
|
|
157
201
|
}
|
|
158
202
|
return result;
|
|
159
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
|
+
}
|
|
160
216
|
async function collectEditInput(runtime, current) {
|
|
161
217
|
const profile = (await runtime.inputText("Profile", { defaultValue: current.profile })).trim();
|
|
162
218
|
const apiKey = (await runtime.inputSecret("API key")).trim() || current.apiKey;
|
package/dist/cli/output.js
CHANGED
|
@@ -120,7 +120,23 @@ function renderHumanSuccess(command, data, warnings) {
|
|
|
120
120
|
lines.push(`providersExists: ${String(data?.providersExists ?? false)}`);
|
|
121
121
|
lines.push(`currentProfile: ${String(data?.currentProfile ?? "")}`);
|
|
122
122
|
lines.push(`mappedProvider: ${String(data?.provider ?? "")}`);
|
|
123
|
+
lines.push(`issues: ${Array.isArray(data?.issues) ? (data?.issues).length : 0}`);
|
|
123
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
|
+
}
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
124
140
|
case "switch":
|
|
125
141
|
lines.push(`Switched to provider ${String(data?.provider ?? "")} using profile ${String(data?.profile ?? "")}.`);
|
|
126
142
|
lines.push(`Backup: ${String(data?.backupPath ?? "")}`);
|
|
@@ -144,9 +160,15 @@ function renderHumanSuccess(command, data, warnings) {
|
|
|
144
160
|
break;
|
|
145
161
|
case "add":
|
|
146
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
|
+
}
|
|
147
166
|
break;
|
|
148
167
|
case "remove":
|
|
149
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
|
+
}
|
|
150
172
|
break;
|
|
151
173
|
case "doctor": {
|
|
152
174
|
const healthy = Boolean(data?.healthy);
|
package/dist/cli.js
CHANGED
|
@@ -44,16 +44,21 @@ const export_providers_1 = require("./app/export-providers");
|
|
|
44
44
|
const get_current_profile_1 = require("./app/get-current-profile");
|
|
45
45
|
const get_status_1 = require("./app/get-status");
|
|
46
46
|
const import_providers_1 = require("./app/import-providers");
|
|
47
|
+
const list_config_profiles_1 = require("./app/list-config-profiles");
|
|
47
48
|
const list_backups_1 = require("./app/list-backups");
|
|
48
49
|
const list_providers_1 = require("./app/list-providers");
|
|
49
50
|
const remove_provider_1 = require("./app/remove-provider");
|
|
50
51
|
const rollback_backup_1 = require("./app/rollback-backup");
|
|
51
52
|
const run_doctor_1 = require("./app/run-doctor");
|
|
52
53
|
const setup_codex_1 = require("./app/setup-codex");
|
|
54
|
+
const show_config_1 = require("./app/show-config");
|
|
53
55
|
const show_provider_1 = require("./app/show-provider");
|
|
54
56
|
const switch_provider_1 = require("./app/switch-provider");
|
|
57
|
+
const config_1 = require("./domain/config");
|
|
55
58
|
const errors_1 = require("./domain/errors");
|
|
59
|
+
const providers_1 = require("./domain/providers");
|
|
56
60
|
const config_repo_1 = require("./infra/config-repo");
|
|
61
|
+
const config_repo_2 = require("./infra/config-repo");
|
|
57
62
|
const providers_repo_1 = require("./infra/providers-repo");
|
|
58
63
|
const codex_paths_1 = require("./infra/codex-paths");
|
|
59
64
|
const args_1 = require("./cli/args");
|
|
@@ -62,7 +67,7 @@ const help_1 = require("./cli/help");
|
|
|
62
67
|
const interactive_1 = require("./cli/interactive");
|
|
63
68
|
const output_1 = require("./cli/output");
|
|
64
69
|
const prompt_1 = require("./cli/prompt");
|
|
65
|
-
const VERSION = "0.0.
|
|
70
|
+
const VERSION = "0.0.5";
|
|
66
71
|
/**
|
|
67
72
|
* Prints the command help text to stdout.
|
|
68
73
|
*/
|
|
@@ -114,7 +119,8 @@ function main() {
|
|
|
114
119
|
* Dispatches a parsed CLI command into the application layer.
|
|
115
120
|
*/
|
|
116
121
|
async function executeCommand(ctx, parsed, runtime = (0, prompt_1.createPromptRuntime)()) {
|
|
117
|
-
|
|
122
|
+
let setupPaths = (0, codex_paths_1.createCodexPaths)(ctx.options.codexDir);
|
|
123
|
+
const paths = setupPaths;
|
|
118
124
|
switch (ctx.command) {
|
|
119
125
|
case "list":
|
|
120
126
|
return (0, list_providers_1.listProviders)(paths.providersPath);
|
|
@@ -136,6 +142,17 @@ async function executeCommand(ctx, parsed, runtime = (0, prompt_1.createPromptRu
|
|
|
136
142
|
return (0, get_current_profile_1.getCurrentProfile)(paths.configPath);
|
|
137
143
|
case "status":
|
|
138
144
|
return (0, get_status_1.getStatus)(paths.codexDir, paths.configPath, paths.providersPath);
|
|
145
|
+
case "config-show":
|
|
146
|
+
return (0, show_config_1.showConfig)({
|
|
147
|
+
configPath: paths.configPath,
|
|
148
|
+
providersPath: paths.providersPath,
|
|
149
|
+
profileName: parsed.positionals[0] ?? null,
|
|
150
|
+
});
|
|
151
|
+
case "config-list-profiles":
|
|
152
|
+
return (0, list_config_profiles_1.listConfigProfilesView)({
|
|
153
|
+
configPath: paths.configPath,
|
|
154
|
+
providersPath: paths.providersPath,
|
|
155
|
+
});
|
|
139
156
|
case "switch": {
|
|
140
157
|
let providerName = parsed.positionals[0] ?? null;
|
|
141
158
|
if (!providerName && (0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
|
|
@@ -161,16 +178,30 @@ async function executeCommand(ctx, parsed, runtime = (0, prompt_1.createPromptRu
|
|
|
161
178
|
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "Missing import file path.");
|
|
162
179
|
}
|
|
163
180
|
const merge = (0, args_1.hasFlag)(parsed.commandOptions, "--merge");
|
|
181
|
+
let repairProfiles;
|
|
164
182
|
if ((0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
|
|
165
183
|
await (0, interactive_1.confirmImport)(runtime, sourceFile, merge);
|
|
184
|
+
const document = (0, config_repo_2.readStructuredConfig)(paths.configPath);
|
|
185
|
+
const imported = (0, providers_1.validateProvidersShape)(JSON.parse(fs.readFileSync(sourceFile, "utf8")));
|
|
186
|
+
const current = (0, providers_repo_1.readProvidersFileIfExists)(paths.providersPath);
|
|
187
|
+
const next = merge ? (0, providers_repo_1.mergeProviders)(current, imported) : imported;
|
|
188
|
+
const missingProfiles = (0, config_1.buildManagedProfileViews)(document, next)
|
|
189
|
+
.filter((view) => view.source === "orphaned-reference")
|
|
190
|
+
.map((view) => view.name)
|
|
191
|
+
.sort();
|
|
192
|
+
if (missingProfiles.length > 0) {
|
|
193
|
+
repairProfiles = await (0, interactive_1.collectImportRepairDetails)(runtime, missingProfiles);
|
|
194
|
+
}
|
|
166
195
|
}
|
|
167
196
|
return (0, import_providers_1.importProviders)({
|
|
168
197
|
codexDir: paths.codexDir,
|
|
169
198
|
backupsDir: paths.backupsDir,
|
|
170
199
|
latestBackupPath: paths.latestBackupPath,
|
|
171
200
|
providersPath: paths.providersPath,
|
|
201
|
+
configPath: paths.configPath,
|
|
172
202
|
sourceFile,
|
|
173
203
|
merge,
|
|
204
|
+
repairProfiles,
|
|
174
205
|
});
|
|
175
206
|
}
|
|
176
207
|
case "export": {
|
|
@@ -197,8 +228,10 @@ async function executeCommand(ctx, parsed, runtime = (0, prompt_1.createPromptRu
|
|
|
197
228
|
let profile = (0, args_1.getSingleOption)(parsed.commandOptions, "--profile");
|
|
198
229
|
let apiKey = (0, args_1.getSingleOption)(parsed.commandOptions, "--api-key");
|
|
199
230
|
let baseUrl = (0, args_1.getSingleOption)(parsed.commandOptions, "--base-url", false);
|
|
231
|
+
let model = (0, args_1.getSingleOption)(parsed.commandOptions, "--model", false);
|
|
200
232
|
let note = (0, args_1.getSingleOption)(parsed.commandOptions, "--note", false);
|
|
201
233
|
let tags = parsed.commandOptions.get("--tag") ?? [];
|
|
234
|
+
const createProfile = (0, args_1.hasFlag)(parsed.commandOptions, "--create-profile");
|
|
202
235
|
if (!providerName || !profile || !apiKey) {
|
|
203
236
|
if (ctx.options.json || !runtime.isInteractive()) {
|
|
204
237
|
throw (0, add_interactive_1.createNonInteractiveAddError)();
|
|
@@ -223,12 +256,15 @@ async function executeCommand(ctx, parsed, runtime = (0, prompt_1.createPromptRu
|
|
|
223
256
|
backupsDir: paths.backupsDir,
|
|
224
257
|
latestBackupPath: paths.latestBackupPath,
|
|
225
258
|
providersPath: paths.providersPath,
|
|
259
|
+
configPath: paths.configPath,
|
|
226
260
|
providerName,
|
|
227
261
|
profile,
|
|
228
262
|
apiKey,
|
|
229
263
|
baseUrl,
|
|
264
|
+
model,
|
|
230
265
|
note,
|
|
231
266
|
tags,
|
|
267
|
+
createProfile,
|
|
232
268
|
});
|
|
233
269
|
}
|
|
234
270
|
case "edit": {
|
|
@@ -242,13 +278,17 @@ async function executeCommand(ctx, parsed, runtime = (0, prompt_1.createPromptRu
|
|
|
242
278
|
let profile = (0, args_1.getSingleOption)(parsed.commandOptions, "--profile", false) ?? undefined;
|
|
243
279
|
let apiKey = (0, args_1.getSingleOption)(parsed.commandOptions, "--api-key", false) ?? undefined;
|
|
244
280
|
let baseUrl = (0, args_1.getSingleOption)(parsed.commandOptions, "--base-url", false) ?? undefined;
|
|
281
|
+
let model = (0, args_1.getSingleOption)(parsed.commandOptions, "--model", false) ?? undefined;
|
|
245
282
|
let note = (0, args_1.getSingleOption)(parsed.commandOptions, "--note", false) ?? undefined;
|
|
246
283
|
let tags = parsed.commandOptions.has("--tag")
|
|
247
284
|
? parsed.commandOptions.get("--tag") ?? []
|
|
248
285
|
: undefined;
|
|
286
|
+
const createProfile = (0, args_1.hasFlag)(parsed.commandOptions, "--create-profile");
|
|
287
|
+
const switchToProfile = (0, args_1.getSingleOption)(parsed.commandOptions, "--switch-to", false) ?? undefined;
|
|
249
288
|
if (profile === undefined &&
|
|
250
289
|
apiKey === undefined &&
|
|
251
290
|
baseUrl === undefined &&
|
|
291
|
+
model === undefined &&
|
|
252
292
|
note === undefined &&
|
|
253
293
|
tags === undefined &&
|
|
254
294
|
(0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
|
|
@@ -263,7 +303,12 @@ async function executeCommand(ctx, parsed, runtime = (0, prompt_1.createPromptRu
|
|
|
263
303
|
note = prompted.note;
|
|
264
304
|
tags = prompted.tags;
|
|
265
305
|
}
|
|
266
|
-
if (profile === undefined &&
|
|
306
|
+
if (profile === undefined &&
|
|
307
|
+
apiKey === undefined &&
|
|
308
|
+
baseUrl === undefined &&
|
|
309
|
+
model === undefined &&
|
|
310
|
+
note === undefined &&
|
|
311
|
+
tags === undefined) {
|
|
267
312
|
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "edit requires at least one field to update.");
|
|
268
313
|
}
|
|
269
314
|
return (0, edit_provider_1.editProvider)({
|
|
@@ -271,17 +316,22 @@ async function executeCommand(ctx, parsed, runtime = (0, prompt_1.createPromptRu
|
|
|
271
316
|
backupsDir: paths.backupsDir,
|
|
272
317
|
latestBackupPath: paths.latestBackupPath,
|
|
273
318
|
providersPath: paths.providersPath,
|
|
319
|
+
configPath: paths.configPath,
|
|
274
320
|
providerName,
|
|
275
321
|
profile,
|
|
276
322
|
apiKey,
|
|
277
323
|
baseUrl,
|
|
324
|
+
model,
|
|
278
325
|
note,
|
|
279
326
|
tags,
|
|
327
|
+
createProfile,
|
|
328
|
+
switchToProfile,
|
|
280
329
|
});
|
|
281
330
|
}
|
|
282
331
|
case "remove": {
|
|
283
332
|
let providerName = parsed.positionals[0] ?? null;
|
|
284
333
|
const force = (0, args_1.hasFlag)(parsed.commandOptions, "--force");
|
|
334
|
+
const switchToProfile = (0, args_1.getSingleOption)(parsed.commandOptions, "--switch-to", false) ?? undefined;
|
|
285
335
|
if (!providerName && (0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
|
|
286
336
|
providerName = await (0, interactive_1.promptForProviderSelection)(runtime, paths.providersPath, "Choose a provider to remove");
|
|
287
337
|
}
|
|
@@ -299,7 +349,9 @@ async function executeCommand(ctx, parsed, runtime = (0, prompt_1.createPromptRu
|
|
|
299
349
|
backupsDir: paths.backupsDir,
|
|
300
350
|
latestBackupPath: paths.latestBackupPath,
|
|
301
351
|
providersPath: paths.providersPath,
|
|
352
|
+
configPath: paths.configPath,
|
|
302
353
|
providerName,
|
|
354
|
+
switchToProfile,
|
|
303
355
|
});
|
|
304
356
|
}
|
|
305
357
|
case "doctor":
|
|
@@ -309,17 +361,39 @@ async function executeCommand(ctx, parsed, runtime = (0, prompt_1.createPromptRu
|
|
|
309
361
|
providersPath: paths.providersPath,
|
|
310
362
|
});
|
|
311
363
|
case "setup": {
|
|
364
|
+
let codexDir = ctx.options.codexDir;
|
|
365
|
+
const candidates = (0, config_repo_1.findCodexDirCandidates)(ctx.options.codexDirExplicit ? ctx.options.codexDir : null);
|
|
366
|
+
if (!ctx.options.codexDirExplicit) {
|
|
367
|
+
if (candidates.length > 1) {
|
|
368
|
+
if (!(0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
|
|
369
|
+
throw (0, errors_1.cliError)("CODEX_DIR_AMBIGUOUS", "Multiple Codex directories were found.", {
|
|
370
|
+
candidates,
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
codexDir = await (0, interactive_1.chooseCodexDir)(runtime, candidates);
|
|
374
|
+
}
|
|
375
|
+
else if (candidates.length === 0) {
|
|
376
|
+
if (!(0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
|
|
377
|
+
throw (0, errors_1.cliError)("CODEX_DIR_NOT_FOUND", "No Codex directory could be found.");
|
|
378
|
+
}
|
|
379
|
+
codexDir = await (0, interactive_1.chooseCodexDir)(runtime, candidates);
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
codexDir = candidates[0];
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
setupPaths = (0, codex_paths_1.createCodexPaths)(codexDir);
|
|
312
386
|
const overwrite = (0, args_1.hasFlag)(parsed.commandOptions, "--overwrite");
|
|
313
387
|
const merge = (0, args_1.hasFlag)(parsed.commandOptions, "--merge");
|
|
314
388
|
if (overwrite && merge) {
|
|
315
389
|
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "setup does not allow both --merge and --overwrite.");
|
|
316
390
|
}
|
|
317
391
|
let strategy = overwrite ? "overwrite" : merge ? "merge" : null;
|
|
318
|
-
const providersExists = fs.existsSync(
|
|
392
|
+
const providersExists = fs.existsSync(setupPaths.providersPath);
|
|
319
393
|
if (providersExists && strategy === null) {
|
|
320
394
|
if (!(0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
|
|
321
395
|
throw (0, errors_1.cliError)("PROVIDERS_ALREADY_EXISTS", "providers.json already exists. Pass --merge or --overwrite.", {
|
|
322
|
-
file:
|
|
396
|
+
file: setupPaths.providersPath,
|
|
323
397
|
});
|
|
324
398
|
}
|
|
325
399
|
const selected = await (0, interactive_1.chooseSetupStrategy)(runtime);
|
|
@@ -328,19 +402,34 @@ async function executeCommand(ctx, parsed, runtime = (0, prompt_1.createPromptRu
|
|
|
328
402
|
}
|
|
329
403
|
strategy = selected;
|
|
330
404
|
}
|
|
331
|
-
const
|
|
405
|
+
const document = (0, config_repo_2.readStructuredConfig)(setupPaths.configPath);
|
|
406
|
+
const adoptableProfiles = (0, config_1.buildManagedProfileViews)(document, null)
|
|
407
|
+
.filter((view) => view.source === "unmanaged" && view.model && view.baseUrl)
|
|
408
|
+
.map((view) => ({
|
|
409
|
+
name: view.name,
|
|
410
|
+
model: view.model,
|
|
411
|
+
baseUrl: view.baseUrl,
|
|
412
|
+
}))
|
|
413
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
414
|
+
const selectedProfiles = Array.from((0, config_repo_1.listConfigProfiles)(setupPaths.configPath)).sort();
|
|
415
|
+
let adoptProfiles = [];
|
|
332
416
|
let providerDetailsByProfile = {};
|
|
333
417
|
if ((0, interactive_1.canPrompt)(runtime, ctx.options.json)) {
|
|
334
|
-
|
|
418
|
+
adoptProfiles = await (0, interactive_1.chooseSetupProfiles)(runtime, adoptableProfiles);
|
|
419
|
+
providerDetailsByProfile = await (0, interactive_1.collectSetupProviderDetails)(runtime, adoptProfiles);
|
|
420
|
+
}
|
|
421
|
+
else {
|
|
422
|
+
adoptProfiles = selectedProfiles.filter((profile) => Object.prototype.hasOwnProperty.call(providerDetailsByProfile, profile));
|
|
335
423
|
}
|
|
336
424
|
return (0, setup_codex_1.setupCodex)({
|
|
337
425
|
codexDirOption: ctx.options.codexDir,
|
|
338
|
-
codexDir:
|
|
339
|
-
configPath:
|
|
340
|
-
providersPath:
|
|
341
|
-
backupsDir:
|
|
342
|
-
latestBackupPath:
|
|
426
|
+
codexDir: setupPaths.codexDir,
|
|
427
|
+
configPath: setupPaths.configPath,
|
|
428
|
+
providersPath: setupPaths.providersPath,
|
|
429
|
+
backupsDir: setupPaths.backupsDir,
|
|
430
|
+
latestBackupPath: setupPaths.latestBackupPath,
|
|
343
431
|
strategy: strategy ?? "overwrite",
|
|
432
|
+
adoptProfiles,
|
|
344
433
|
providerDetailsByProfile,
|
|
345
434
|
});
|
|
346
435
|
}
|