@minniexcode/codex-switch 0.1.5 → 0.2.2
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 +66 -94
- package/README.CN.md +84 -139
- package/README.md +91 -151
- package/dist/app/add-provider.js +6 -89
- package/dist/app/edit-provider.js +9 -29
- package/dist/app/get-status.js +1 -74
- package/dist/app/list-providers.js +0 -2
- package/dist/app/remove-provider.js +3 -5
- package/dist/app/run-doctor.js +2 -100
- package/dist/app/setup-codex.js +0 -2
- package/dist/app/switch-provider.js +1 -79
- package/dist/cli/output.js +3 -89
- package/dist/commands/handlers.js +20 -209
- package/dist/commands/help.js +1 -4
- package/dist/commands/registry.js +6 -74
- package/dist/domain/config.js +1 -3
- package/dist/domain/providers.js +4 -92
- package/dist/domain/runtime-state.js +0 -88
- package/dist/interaction/add-interactive.js +1 -55
- package/dist/interaction/interactive.js +1 -3
- package/dist/runtime/codex-probe.js +0 -12
- package/dist/storage/codex-paths.js +0 -2
- package/docs/Design/codex-switch-v0.2.0-design.md +56 -0
- package/docs/Design/codex-switch-v0.2.1-design.md +77 -0
- package/docs/PRD/codex-switch-prd-v0.2.1.md +82 -0
- package/docs/Tests/testing.md +32 -34
- package/docs/cli-usage.md +67 -235
- package/docs/codex-switch-command-design.md +1 -1
- package/docs/codex-switch-product-overview.md +49 -96
- package/docs/codex-switch-technical-architecture.md +37 -52
- package/package.json +1 -1
- package/dist/app/bridge.js +0 -308
- package/dist/runtime/copilot-adapter.js +0 -612
- package/dist/runtime/copilot-bridge-worker.js +0 -69
- package/dist/runtime/copilot-bridge.js +0 -1318
- package/dist/runtime/copilot-cli.js +0 -164
- package/dist/runtime/copilot-installer.js +0 -231
- package/dist/runtime/copilot-sdk-loader.js +0 -62
- package/dist/storage/runtime-state-repo.js +0 -121
|
@@ -48,7 +48,6 @@ const list_providers_1 = require("../app/list-providers");
|
|
|
48
48
|
const remove_provider_1 = require("../app/remove-provider");
|
|
49
49
|
const rollback_backup_1 = require("../app/rollback-backup");
|
|
50
50
|
const run_doctor_1 = require("../app/run-doctor");
|
|
51
|
-
const bridge_1 = require("../app/bridge");
|
|
52
51
|
const setup_codex_1 = require("../app/setup-codex");
|
|
53
52
|
const show_config_1 = require("../app/show-config");
|
|
54
53
|
const show_provider_1 = require("../app/show-provider");
|
|
@@ -60,9 +59,6 @@ const providers_1 = require("../domain/providers");
|
|
|
60
59
|
const add_interactive_1 = require("../interaction/add-interactive");
|
|
61
60
|
const interactive_1 = require("../interaction/interactive");
|
|
62
61
|
const prompt_1 = require("../interaction/prompt");
|
|
63
|
-
const copilot_adapter_1 = require("../runtime/copilot-adapter");
|
|
64
|
-
const copilot_cli_1 = require("../runtime/copilot-cli");
|
|
65
|
-
const copilot_installer_1 = require("../runtime/copilot-installer");
|
|
66
62
|
const config_repo_1 = require("../storage/config-repo");
|
|
67
63
|
const codex_paths_1 = require("../storage/codex-paths");
|
|
68
64
|
const providers_repo_1 = require("../storage/providers-repo");
|
|
@@ -97,46 +93,7 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
|
|
|
97
93
|
case "current":
|
|
98
94
|
return (0, get_current_profile_1.getCurrentProfile)(paths.configPath, paths.providersPath);
|
|
99
95
|
case "status":
|
|
100
|
-
return (0, get_status_1.getStatus)(paths.codexDir, paths.configPath, paths.providersPath, paths.authPath
|
|
101
|
-
runtimeDir: paths.runtimeDir,
|
|
102
|
-
runtimesDir: paths.runtimesDir,
|
|
103
|
-
});
|
|
104
|
-
case "bridge-start": {
|
|
105
|
-
const providerName = parsed.positionals[0] ?? null;
|
|
106
|
-
return (0, bridge_1.startBridge)({
|
|
107
|
-
providersPath: paths.providersPath,
|
|
108
|
-
configPath: paths.configPath,
|
|
109
|
-
runtimeDir: paths.runtimeDir,
|
|
110
|
-
runtimesDir: paths.runtimesDir,
|
|
111
|
-
providerName,
|
|
112
|
-
runtime,
|
|
113
|
-
json: ctx.options.json,
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
case "bridge-stop": {
|
|
117
|
-
const providerName = parsed.positionals[0] ?? null;
|
|
118
|
-
return (0, bridge_1.stopBridge)({
|
|
119
|
-
providersPath: paths.providersPath,
|
|
120
|
-
configPath: paths.configPath,
|
|
121
|
-
runtimeDir: paths.runtimeDir,
|
|
122
|
-
runtimesDir: paths.runtimesDir,
|
|
123
|
-
providerName,
|
|
124
|
-
runtime,
|
|
125
|
-
json: ctx.options.json,
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
case "bridge-status": {
|
|
129
|
-
const providerName = parsed.positionals[0] ?? null;
|
|
130
|
-
return (0, bridge_1.statusBridge)({
|
|
131
|
-
providersPath: paths.providersPath,
|
|
132
|
-
configPath: paths.configPath,
|
|
133
|
-
runtimeDir: paths.runtimeDir,
|
|
134
|
-
runtimesDir: paths.runtimesDir,
|
|
135
|
-
providerName,
|
|
136
|
-
runtime,
|
|
137
|
-
json: ctx.options.json,
|
|
138
|
-
});
|
|
139
|
-
}
|
|
96
|
+
return (0, get_status_1.getStatus)(paths.codexDir, paths.configPath, paths.providersPath, paths.authPath);
|
|
140
97
|
case "init": {
|
|
141
98
|
return (0, init_codex_1.initCodex)({
|
|
142
99
|
toolHomeDir: setupPaths.toolHomeDir,
|
|
@@ -146,93 +103,6 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
|
|
|
146
103
|
defaultCodexDir: ctx.options.codexDirExplicit ? setupPaths.codexDir : null,
|
|
147
104
|
});
|
|
148
105
|
}
|
|
149
|
-
case "login": {
|
|
150
|
-
const upstream = (parsed.positionals[0] ?? "").toLowerCase();
|
|
151
|
-
if (ctx.options.json || !runtime.isInteractive()) {
|
|
152
|
-
throw (0, errors_1.cliError)("COPILOT_LOGIN_REQUIRES_TTY", "login requires an interactive TTY and does not support --json.");
|
|
153
|
-
}
|
|
154
|
-
if (upstream !== "copilot" && upstream !== "github-copilot") {
|
|
155
|
-
throw (0, errors_1.cliError)("INVALID_ARGUMENT", `Unsupported upstream "${parsed.positionals[0] ?? ""}".`, {
|
|
156
|
-
supportedUpstreams: ["copilot", "github-copilot"],
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
(0, copilot_installer_1.assertCopilotNodeRuntimeSupported)();
|
|
160
|
-
const installed = (0, copilot_installer_1.probeCopilotSdkInstall)(paths.runtimesDir);
|
|
161
|
-
let installedNow = false;
|
|
162
|
-
if (!installed.installed) {
|
|
163
|
-
const confirmInstall = await runtime.confirmAction("The Copilot SDK runtime is not installed. Install it now?", {
|
|
164
|
-
defaultValue: true,
|
|
165
|
-
});
|
|
166
|
-
if (!confirmInstall) {
|
|
167
|
-
throw (0, errors_1.cliError)("COPILOT_SDK_MISSING", "The optional Copilot SDK runtime is not installed.", {
|
|
168
|
-
installDir: installed.installDir,
|
|
169
|
-
packageName: installed.packageName,
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
runtime.writeLine("Installing Copilot SDK runtime...");
|
|
173
|
-
(0, copilot_installer_1.installCopilotSdk)(paths.runtimesDir);
|
|
174
|
-
installedNow = true;
|
|
175
|
-
}
|
|
176
|
-
const availability = (0, copilot_cli_1.checkCopilotCliAvailable)(paths.runtimesDir);
|
|
177
|
-
try {
|
|
178
|
-
await (0, copilot_adapter_1.readCopilotAuthState)(paths.runtimesDir);
|
|
179
|
-
return {
|
|
180
|
-
data: {
|
|
181
|
-
upstream: "github-copilot",
|
|
182
|
-
sdkInstalled: true,
|
|
183
|
-
sdkInstalledNow: installedNow,
|
|
184
|
-
authReady: true,
|
|
185
|
-
loginLaunched: false,
|
|
186
|
-
cliSource: availability.ok ? availability.source ?? null : null,
|
|
187
|
-
cliCommand: availability.command ?? null,
|
|
188
|
-
},
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
catch (error) {
|
|
192
|
-
const normalized = (0, errors_1.normalizeError)(error);
|
|
193
|
-
if (normalized.code !== "COPILOT_AUTH_REQUIRED") {
|
|
194
|
-
throw error;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
if (!availability.ok) {
|
|
198
|
-
throw (0, errors_1.cliError)("COPILOT_CLI_MISSING", "The official Copilot CLI could not be resolved from the installed runtime or PATH.", {
|
|
199
|
-
cause: availability.cause,
|
|
200
|
-
source: availability.source ?? null,
|
|
201
|
-
command: availability.command ?? null,
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
try {
|
|
205
|
-
(0, copilot_cli_1.runCopilotLogin)({ runtimesDir: paths.runtimesDir });
|
|
206
|
-
}
|
|
207
|
-
catch (error) {
|
|
208
|
-
throw (0, errors_1.cliError)("COPILOT_LOGIN_LAUNCH_FAILED", "Failed to launch `copilot login`.", {
|
|
209
|
-
cause: error instanceof Error ? error.message : String(error),
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
try {
|
|
213
|
-
await (0, copilot_adapter_1.readCopilotAuthState)(paths.runtimesDir);
|
|
214
|
-
}
|
|
215
|
-
catch (error) {
|
|
216
|
-
const normalized = (0, errors_1.normalizeError)(error);
|
|
217
|
-
if (normalized.code === "COPILOT_AUTH_REQUIRED") {
|
|
218
|
-
throw (0, errors_1.cliError)("COPILOT_LOGIN_RECHECK_FAILED", "Copilot login completed but auth readiness recheck still failed.", {
|
|
219
|
-
...(normalized.details ?? {}),
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
throw error;
|
|
223
|
-
}
|
|
224
|
-
return {
|
|
225
|
-
data: {
|
|
226
|
-
upstream: "github-copilot",
|
|
227
|
-
sdkInstalled: true,
|
|
228
|
-
sdkInstalledNow: installedNow,
|
|
229
|
-
authReady: true,
|
|
230
|
-
loginLaunched: true,
|
|
231
|
-
cliSource: availability.source ?? null,
|
|
232
|
-
cliCommand: availability.command ?? null,
|
|
233
|
-
},
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
106
|
case "config-show":
|
|
237
107
|
return (0, show_config_1.showConfig)({
|
|
238
108
|
configPath: paths.configPath,
|
|
@@ -252,11 +122,6 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
|
|
|
252
122
|
if (!providerName) {
|
|
253
123
|
throw (0, errors_1.cliError)("PROVIDER_NOT_FOUND", "Missing provider name for switch command.");
|
|
254
124
|
}
|
|
255
|
-
if ((0, args_1.hasFlag)(parsed.commandOptions, "--install-copilot-sdk")) {
|
|
256
|
-
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "--install-copilot-sdk is no longer supported with switch. Run `codexs login copilot` instead.", {
|
|
257
|
-
suggestion: "Run `codexs login copilot` first, then rerun switch without --install-copilot-sdk.",
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
125
|
return (0, switch_provider_1.switchProvider)({
|
|
261
126
|
codexDir: paths.codexDir,
|
|
262
127
|
lockPath: paths.lockPath,
|
|
@@ -265,8 +130,6 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
|
|
|
265
130
|
configPath: paths.configPath,
|
|
266
131
|
providersPath: paths.providersPath,
|
|
267
132
|
authPath: paths.authPath,
|
|
268
|
-
runtimeDir: paths.runtimeDir,
|
|
269
|
-
runtimesDir: paths.runtimesDir,
|
|
270
133
|
providerName,
|
|
271
134
|
});
|
|
272
135
|
}
|
|
@@ -327,75 +190,31 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
|
|
|
327
190
|
let note = (0, args_1.getSingleOption)(parsed.commandOptions, "--note", false);
|
|
328
191
|
let tags = parsed.commandOptions.get("--tag") ?? [];
|
|
329
192
|
let createProfile = (0, args_1.hasFlag)(parsed.commandOptions, "--create-profile");
|
|
330
|
-
|
|
331
|
-
let bridgeHost = (0, args_1.getSingleOption)(parsed.commandOptions, "--bridge-host", false);
|
|
332
|
-
const bridgePortValue = (0, args_1.getSingleOption)(parsed.commandOptions, "--bridge-port", false);
|
|
333
|
-
let bridgeApiKey = (0, args_1.getSingleOption)(parsed.commandOptions, "--bridge-api-key", false);
|
|
334
|
-
const installCopilotSdk = (0, args_1.hasFlag)(parsed.commandOptions, "--install-copilot-sdk");
|
|
335
|
-
let bridgePort = bridgePortValue ? Number(bridgePortValue) : null;
|
|
336
|
-
if (copilot && apiKey) {
|
|
337
|
-
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "--copilot does not allow --api-key. Use --bridge-api-key for the local bridge secret.");
|
|
338
|
-
}
|
|
339
|
-
if (copilot && installCopilotSdk) {
|
|
340
|
-
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "--install-copilot-sdk is no longer supported with add --copilot. Run `codexs login copilot` instead.", {
|
|
341
|
-
suggestion: "Run `codexs login copilot` first, then rerun add --copilot.",
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
if (bridgePortValue && (!Number.isInteger(bridgePort) || bridgePort === null || bridgePort <= 0)) {
|
|
345
|
-
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "--bridge-port must be a positive integer.");
|
|
346
|
-
}
|
|
347
|
-
if (!providerName || !profile || (!apiKey && !copilot)) {
|
|
193
|
+
if (!providerName || !profile || !apiKey) {
|
|
348
194
|
if (ctx.options.json || !runtime.isInteractive()) {
|
|
349
|
-
throw (0, add_interactive_1.createNonInteractiveAddError)(
|
|
350
|
-
}
|
|
351
|
-
if (copilot) {
|
|
352
|
-
const prompted = await (0, add_interactive_1.collectCopilotAddInput)(runtime, {
|
|
353
|
-
providerName,
|
|
354
|
-
profile,
|
|
355
|
-
model,
|
|
356
|
-
note,
|
|
357
|
-
tags,
|
|
358
|
-
}, (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)), {
|
|
359
|
-
bridgeHost,
|
|
360
|
-
bridgePort,
|
|
361
|
-
bridgeApiKey,
|
|
362
|
-
});
|
|
363
|
-
providerName = prompted.providerName;
|
|
364
|
-
profile = prompted.profile;
|
|
365
|
-
model = prompted.model ?? null;
|
|
366
|
-
note = prompted.note ?? null;
|
|
367
|
-
tags = prompted.tags;
|
|
368
|
-
createProfile = createProfile || prompted.createProfile;
|
|
369
|
-
baseUrl = null;
|
|
370
|
-
bridgeHost = prompted.bridgeHost ?? bridgeHost;
|
|
371
|
-
bridgePort = prompted.bridgePort ?? bridgePort;
|
|
372
|
-
bridgeApiKey = prompted.bridgeApiKey ?? bridgeApiKey;
|
|
373
|
-
}
|
|
374
|
-
else {
|
|
375
|
-
const prompted = await (0, add_interactive_1.collectAddInput)(runtime, {
|
|
376
|
-
providerName,
|
|
377
|
-
profile,
|
|
378
|
-
apiKey,
|
|
379
|
-
model,
|
|
380
|
-
baseUrl,
|
|
381
|
-
note,
|
|
382
|
-
tags,
|
|
383
|
-
}, (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)));
|
|
384
|
-
providerName = prompted.providerName;
|
|
385
|
-
profile = prompted.profile;
|
|
386
|
-
apiKey = prompted.apiKey;
|
|
387
|
-
model = prompted.model ?? null;
|
|
388
|
-
baseUrl = prompted.baseUrl ?? null;
|
|
389
|
-
note = prompted.note ?? null;
|
|
390
|
-
tags = prompted.tags;
|
|
391
|
-
createProfile = createProfile || prompted.createProfile;
|
|
195
|
+
throw (0, add_interactive_1.createNonInteractiveAddError)();
|
|
392
196
|
}
|
|
197
|
+
const prompted = await (0, add_interactive_1.collectAddInput)(runtime, {
|
|
198
|
+
providerName,
|
|
199
|
+
profile,
|
|
200
|
+
apiKey,
|
|
201
|
+
model,
|
|
202
|
+
baseUrl,
|
|
203
|
+
note,
|
|
204
|
+
tags,
|
|
205
|
+
}, (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)));
|
|
206
|
+
providerName = prompted.providerName;
|
|
207
|
+
profile = prompted.profile;
|
|
208
|
+
apiKey = prompted.apiKey;
|
|
209
|
+
model = prompted.model ?? null;
|
|
210
|
+
baseUrl = prompted.baseUrl ?? null;
|
|
211
|
+
note = prompted.note ?? null;
|
|
212
|
+
tags = prompted.tags;
|
|
213
|
+
createProfile = createProfile || prompted.createProfile;
|
|
393
214
|
}
|
|
394
215
|
return (0, add_provider_1.addProvider)({
|
|
395
216
|
codexDir: paths.codexDir,
|
|
396
|
-
toolHomeDir: paths.toolHomeDir,
|
|
397
217
|
lockPath: paths.lockPath,
|
|
398
|
-
runtimesDir: paths.runtimesDir,
|
|
399
218
|
backupsDir: paths.backupsDir,
|
|
400
219
|
latestBackupPath: paths.latestBackupPath,
|
|
401
220
|
providersPath: paths.providersPath,
|
|
@@ -409,10 +228,6 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
|
|
|
409
228
|
note,
|
|
410
229
|
tags,
|
|
411
230
|
createProfile,
|
|
412
|
-
copilot,
|
|
413
|
-
bridgeHost,
|
|
414
|
-
bridgePort,
|
|
415
|
-
bridgeApiKey,
|
|
416
231
|
});
|
|
417
232
|
}
|
|
418
233
|
case "edit": {
|
|
@@ -505,8 +320,6 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
|
|
|
505
320
|
configPath: paths.configPath,
|
|
506
321
|
providersPath: paths.providersPath,
|
|
507
322
|
authPath: paths.authPath,
|
|
508
|
-
runtimeDir: paths.runtimeDir,
|
|
509
|
-
runtimesDir: paths.runtimesDir,
|
|
510
323
|
});
|
|
511
324
|
case "migrate": {
|
|
512
325
|
let codexDir = ctx.options.codexDir;
|
|
@@ -606,8 +419,6 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
|
|
|
606
419
|
configPath: setupPaths.configPath,
|
|
607
420
|
providersPath: setupPaths.providersPath,
|
|
608
421
|
authPath: setupPaths.authPath,
|
|
609
|
-
runtimeDir: setupPaths.runtimeDir,
|
|
610
|
-
runtimesDir: setupPaths.runtimesDir,
|
|
611
422
|
backupsDir: setupPaths.backupsDir,
|
|
612
423
|
latestBackupPath: setupPaths.latestBackupPath,
|
|
613
424
|
strategy: strategy ?? "overwrite",
|
package/dist/commands/help.js
CHANGED
|
@@ -30,8 +30,7 @@ function buildHelpText(commandName) {
|
|
|
30
30
|
"codex-switch",
|
|
31
31
|
"",
|
|
32
32
|
"Manage and switch local Codex provider/model-provider routing safely.",
|
|
33
|
-
"Primary
|
|
34
|
-
"Primary workflows: Copilot providers use init -> login copilot -> add --copilot -> switch -> status -> doctor.",
|
|
33
|
+
"Primary workflow: init -> add -> switch -> status -> doctor.",
|
|
35
34
|
"Advanced adopt flows use migrate only when you already have Codex runtime state to import.",
|
|
36
35
|
"Deprecated entry: setup still exists only to point callers to init or migrate.",
|
|
37
36
|
"",
|
|
@@ -69,8 +68,6 @@ function buildHelpText(commandName) {
|
|
|
69
68
|
" codexs switch packycode",
|
|
70
69
|
" codexs status",
|
|
71
70
|
" codexs doctor",
|
|
72
|
-
" codexs login copilot",
|
|
73
|
-
" codexs add copilot-main --copilot --profile copilot-main --model gpt-5",
|
|
74
71
|
" codexs migrate",
|
|
75
72
|
" codexs config show",
|
|
76
73
|
" codexs backups list",
|
|
@@ -41,48 +41,6 @@ exports.COMMANDS = [
|
|
|
41
41
|
],
|
|
42
42
|
examples: ["codexs config list-profiles", "codexs config list-profiles --json"],
|
|
43
43
|
},
|
|
44
|
-
{
|
|
45
|
-
id: "bridge-start",
|
|
46
|
-
tokens: ["bridge", "start"],
|
|
47
|
-
handler: handlers_1.handleRegisteredCommand,
|
|
48
|
-
group: "write",
|
|
49
|
-
summary: "Start or reuse the managed Copilot bridge.",
|
|
50
|
-
usage: ["codexs bridge start [provider] [--json] [--codex-dir <path>]"],
|
|
51
|
-
details: [
|
|
52
|
-
"Resolves a Copilot bridge provider by explicit name, active provider, sole provider, or TTY selection.",
|
|
53
|
-
"Reuses a healthy bridge for the same provider and replaces a different managed provider when needed.",
|
|
54
|
-
"If the preferred port is occupied, automatically selects another free 5-digit port and persists it.",
|
|
55
|
-
],
|
|
56
|
-
examples: ["codexs bridge start", "codexs bridge start copilot-main"],
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
id: "bridge-stop",
|
|
60
|
-
tokens: ["bridge", "stop"],
|
|
61
|
-
handler: handlers_1.handleRegisteredCommand,
|
|
62
|
-
group: "recovery",
|
|
63
|
-
summary: "Stop the managed Copilot bridge.",
|
|
64
|
-
usage: ["codexs bridge stop [provider] [--json] [--codex-dir <path>]"],
|
|
65
|
-
details: [
|
|
66
|
-
"Prefers the runtime-state instance when present and uses an explicit provider as a guard.",
|
|
67
|
-
"Clears the runtime-state manifest without mutating providers.json or Codex auth state.",
|
|
68
|
-
"Is idempotent when no managed bridge is currently running.",
|
|
69
|
-
],
|
|
70
|
-
examples: ["codexs bridge stop", "codexs bridge stop copilot-main"],
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
id: "bridge-status",
|
|
74
|
-
tokens: ["bridge", "status"],
|
|
75
|
-
handler: handlers_1.handleRegisteredCommand,
|
|
76
|
-
group: "read",
|
|
77
|
-
summary: "Inspect the managed Copilot bridge.",
|
|
78
|
-
usage: ["codexs bridge status [provider] [--json] [--codex-dir <path>]"],
|
|
79
|
-
details: [
|
|
80
|
-
"Reports runtime-state, provider binding, and whether the live worker matches the expected provider.",
|
|
81
|
-
"Prefers the runtime-state instance when one is present.",
|
|
82
|
-
"Uses an explicit provider as a guard instead of silently switching targets.",
|
|
83
|
-
],
|
|
84
|
-
examples: ["codexs bridge status", "codexs bridge status copilot-main"],
|
|
85
|
-
},
|
|
86
44
|
{
|
|
87
45
|
id: "init",
|
|
88
46
|
tokens: ["init"],
|
|
@@ -95,26 +53,10 @@ exports.COMMANDS = [
|
|
|
95
53
|
"Does not create or validate config.toml, auth.json, or the target Codex directory.",
|
|
96
54
|
"When --codex-dir is passed explicitly and codex-switch.json does not exist yet, init persists it as defaultCodexDir.",
|
|
97
55
|
"Otherwise init stays scoped to tool-home state and does not persist fallback Codex directory resolution.",
|
|
98
|
-
"Use init first for fresh
|
|
56
|
+
"Use init first for fresh provider-management setups.",
|
|
99
57
|
],
|
|
100
58
|
examples: ["codexs init", "codexs init --json --codex-dir ~/.codex"],
|
|
101
59
|
},
|
|
102
|
-
{
|
|
103
|
-
id: "login",
|
|
104
|
-
tokens: ["login"],
|
|
105
|
-
handler: handlers_1.handleRegisteredCommand,
|
|
106
|
-
group: "write",
|
|
107
|
-
summary: "Complete upstream onboarding for interactive providers such as GitHub Copilot.",
|
|
108
|
-
usage: ["codexs login <upstream>"],
|
|
109
|
-
details: [
|
|
110
|
-
"Currently supports copilot and github-copilot as the same upstream.",
|
|
111
|
-
"Installs the local Copilot SDK under the tool home when needed, then checks login readiness.",
|
|
112
|
-
"When login is not ready, launches the bundled Copilot CLI from the runtime when available, otherwise falls back to PATH, then rechecks before succeeding.",
|
|
113
|
-
"Copilot login is shared across the local Copilot runtime, so logging into a different GitHub account replaces the upstream auth used by all Copilot providers.",
|
|
114
|
-
"Requires an interactive TTY and does not support --json.",
|
|
115
|
-
],
|
|
116
|
-
examples: ["codexs login copilot", "codexs login github-copilot"],
|
|
117
|
-
},
|
|
118
60
|
{
|
|
119
61
|
id: "migrate",
|
|
120
62
|
tokens: ["migrate"],
|
|
@@ -153,7 +95,7 @@ exports.COMMANDS = [
|
|
|
153
95
|
summary: "List managed providers with model-provider routing and current-state hints.",
|
|
154
96
|
usage: ["codexs list [--json] [--codex-dir <path>]"],
|
|
155
97
|
details: [
|
|
156
|
-
"Reads providers.json and prints provider-to-model-provider mappings
|
|
98
|
+
"Reads providers.json and prints provider-to-model-provider mappings.",
|
|
157
99
|
"When the active model_provider is shared by multiple providers, list surfaces the ambiguity instead of inventing one current provider.",
|
|
158
100
|
"Use --json for machine-readable automation output.",
|
|
159
101
|
],
|
|
@@ -188,11 +130,10 @@ exports.COMMANDS = [
|
|
|
188
130
|
tokens: ["status"],
|
|
189
131
|
handler: handlers_1.handleRegisteredCommand,
|
|
190
132
|
group: "read",
|
|
191
|
-
summary: "Show
|
|
133
|
+
summary: "Show target Codex directory, managed route, and health status.",
|
|
192
134
|
usage: ["codexs status [--json] [--codex-dir <path>]"],
|
|
193
135
|
details: [
|
|
194
|
-
"Reports the target Codex
|
|
195
|
-
"When the active provider uses a local runtime bridge, status also reports bridge, Copilot SDK, and upstream auth state.",
|
|
136
|
+
"Reports the target Codex directory, tool-home root, current model, current model_provider, and whether the live route is mapped.",
|
|
196
137
|
"Surfaces dual-path config consistency signals without mutating any files.",
|
|
197
138
|
"Organizes the human-readable view around current state, health impact, and next step.",
|
|
198
139
|
"Use doctor for deeper diagnostics.",
|
|
@@ -224,10 +165,9 @@ exports.COMMANDS = [
|
|
|
224
165
|
tokens: ["add"],
|
|
225
166
|
handler: handlers_1.handleRegisteredCommand,
|
|
226
167
|
group: "write",
|
|
227
|
-
summary: "Add a managed provider
|
|
168
|
+
summary: "Add a managed provider.",
|
|
228
169
|
usage: [
|
|
229
170
|
"codexs add <provider> --profile <model-provider-id> --model <name> --api-key <key> [--base-url <url>] [--note <text>] [--tag <tag> ...]",
|
|
230
|
-
"codexs add <provider> --copilot --profile <model-provider-id> --model <name> [--bridge-host <host>] [--bridge-port <port>] [--bridge-api-key <secret>]",
|
|
231
171
|
"codexs add [--profile <model-provider-id>] [--model <name>] [--api-key <key>] [--base-url <url>] [--note <text>] [--tag <tag> ...]",
|
|
232
172
|
],
|
|
233
173
|
details: [
|
|
@@ -238,14 +178,9 @@ exports.COMMANDS = [
|
|
|
238
178
|
"Automation and non-TTY environments must pass all required values explicitly.",
|
|
239
179
|
"--profile is a CLI alias for the stored model_provider id.",
|
|
240
180
|
"The command projects only model_providers sections and does not create legacy profiles sections.",
|
|
241
|
-
"Use --copilot to create a GitHub Copilot bridge provider backed by the official SDK.",
|
|
242
|
-
"Copilot providers require SDK install and login readiness to already be satisfied via codexs login copilot.",
|
|
243
|
-
"For Copilot providers, provider apiKey stores only the local bridge secret; upstream GitHub Copilot auth stays shared in the official runtime login.",
|
|
244
|
-
"--install-copilot-sdk is kept only as a rejected compatibility flag that points to codexs login copilot.",
|
|
245
181
|
],
|
|
246
182
|
examples: [
|
|
247
183
|
"codexs add packycode --profile packycode --model gpt-5 --api-key sk-xxx --base-url https://api.example/v1",
|
|
248
|
-
"codexs add copilot-main --copilot --profile copilot-main --model gpt-5",
|
|
249
184
|
"codexs add",
|
|
250
185
|
],
|
|
251
186
|
},
|
|
@@ -259,9 +194,7 @@ exports.COMMANDS = [
|
|
|
259
194
|
details: [
|
|
260
195
|
"When <provider> is omitted in a TTY, an interactive provider selector is shown.",
|
|
261
196
|
"When <provider> is passed explicitly, switch proceeds directly without extra confirmation.",
|
|
262
|
-
"
|
|
263
|
-
"Copilot bridge providers also rewrite OPENAI_API_KEY to the local bridge secret while managing runtime routing and bridge state.",
|
|
264
|
-
"Copilot bridge providers probe the optional official SDK before switching and fail fast if it is missing.",
|
|
197
|
+
"Updates the active config profile and rewrites auth.json with auth_mode=apikey plus OPENAI_API_KEY.",
|
|
265
198
|
"Switch succeeds only after the managed profile projection is written to the target runtime.",
|
|
266
199
|
"Backs up config.toml and auth.json and rolls back on failure.",
|
|
267
200
|
],
|
|
@@ -333,7 +266,6 @@ exports.COMMANDS = [
|
|
|
333
266
|
usage: ["codexs doctor [--json] [--codex-dir <path>]"],
|
|
334
267
|
details: [
|
|
335
268
|
"Checks the expected config files, provider/model-provider consistency, and Codex CLI availability.",
|
|
336
|
-
"Copilot bridge providers add runtime dependency, auth, and bridge health diagnostics.",
|
|
337
269
|
"Returns structured issues so users and AI agents can act on them.",
|
|
338
270
|
],
|
|
339
271
|
examples: ["codexs doctor", "codexs doctor --json"],
|
package/dist/domain/config.js
CHANGED
|
@@ -335,8 +335,7 @@ function collectConfigConsistencyIssues(document, providers) {
|
|
|
335
335
|
.sort(([left], [right]) => left.localeCompare(right));
|
|
336
336
|
if (linkedProviders.length === 1 && activeProviderSection?.baseUrl) {
|
|
337
337
|
const [providerName, provider] = linkedProviders[0];
|
|
338
|
-
if (
|
|
339
|
-
typeof provider.baseUrl === "string" &&
|
|
338
|
+
if (typeof provider.baseUrl === "string" &&
|
|
340
339
|
provider.baseUrl.trim() !== "" &&
|
|
341
340
|
provider.baseUrl !== activeProviderSection.baseUrl) {
|
|
342
341
|
issues.push({
|
|
@@ -345,7 +344,6 @@ function collectConfigConsistencyIssues(document, providers) {
|
|
|
345
344
|
provider: providerName,
|
|
346
345
|
providerBaseUrl: provider.baseUrl,
|
|
347
346
|
configBaseUrl: activeProviderSection.baseUrl,
|
|
348
|
-
providerType: "direct",
|
|
349
347
|
});
|
|
350
348
|
}
|
|
351
349
|
}
|
package/dist/domain/providers.js
CHANGED
|
@@ -6,11 +6,7 @@ exports.sortProviders = sortProviders;
|
|
|
6
6
|
exports.findProviderByProfile = findProviderByProfile;
|
|
7
7
|
exports.findProvidersByProfile = findProvidersByProfile;
|
|
8
8
|
exports.maskSecret = maskSecret;
|
|
9
|
-
exports.
|
|
10
|
-
exports.isCopilotBridgeProvider = isCopilotBridgeProvider;
|
|
11
|
-
exports.buildCopilotBridgeBaseUrl = buildCopilotBridgeBaseUrl;
|
|
12
|
-
exports.buildCopilotModelProviderProjection = buildCopilotModelProviderProjection;
|
|
13
|
-
exports.buildDirectModelProviderProjection = buildDirectModelProviderProjection;
|
|
9
|
+
exports.buildModelProviderProjection = buildModelProviderProjection;
|
|
14
10
|
/**
|
|
15
11
|
* Validates and normalizes unknown JSON into the providers.json domain model.
|
|
16
12
|
*/
|
|
@@ -47,14 +43,6 @@ function validateProvidersShape(input) {
|
|
|
47
43
|
(!Array.isArray(provider.tags) || provider.tags.some((tag) => typeof tag !== "string"))) {
|
|
48
44
|
throw new Error(`Provider "${name}" has invalid tags.`);
|
|
49
45
|
}
|
|
50
|
-
if (provider.runtime !== undefined) {
|
|
51
|
-
validateProviderRuntime(name, provider.runtime);
|
|
52
|
-
const expectedBaseUrl = buildCopilotBridgeBaseUrl(provider.runtime);
|
|
53
|
-
if (typeof provider.baseUrl !== "string" || provider.baseUrl.trim() !== expectedBaseUrl) {
|
|
54
|
-
throw new Error(`Provider "${name}" baseUrl must match runtime bridge base URL "${expectedBaseUrl}".`);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
// Normalize provider fields during validation so the persisted format stays clean.
|
|
58
46
|
providers[name] = cleanProviderRecord({
|
|
59
47
|
profile: provider.profile,
|
|
60
48
|
apiKey: provider.apiKey,
|
|
@@ -62,7 +50,6 @@ function validateProvidersShape(input) {
|
|
|
62
50
|
baseUrl: provider.baseUrl,
|
|
63
51
|
note: provider.note,
|
|
64
52
|
tags: provider.tags,
|
|
65
|
-
runtime: provider.runtime,
|
|
66
53
|
});
|
|
67
54
|
}
|
|
68
55
|
return { providers };
|
|
@@ -87,18 +74,6 @@ function cleanProviderRecord(record) {
|
|
|
87
74
|
if (record.tags && record.tags.length > 0) {
|
|
88
75
|
next.tags = record.tags.map((tag) => tag.trim()).filter((tag) => tag.length > 0);
|
|
89
76
|
}
|
|
90
|
-
if (record.runtime) {
|
|
91
|
-
next.runtime = {
|
|
92
|
-
kind: record.runtime.kind,
|
|
93
|
-
upstream: record.runtime.upstream,
|
|
94
|
-
bridgeHost: record.runtime.bridgeHost.trim(),
|
|
95
|
-
bridgePort: record.runtime.bridgePort,
|
|
96
|
-
bridgePath: record.runtime.bridgePath,
|
|
97
|
-
premiumRequests: record.runtime.premiumRequests,
|
|
98
|
-
authSource: record.runtime.authSource,
|
|
99
|
-
sdkInstallMode: record.runtime.sdkInstallMode,
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
77
|
return next;
|
|
103
78
|
}
|
|
104
79
|
/**
|
|
@@ -142,42 +117,12 @@ function maskSecret(value) {
|
|
|
142
117
|
return `${value.slice(0, 3)}***${value.slice(-2)}`;
|
|
143
118
|
}
|
|
144
119
|
/**
|
|
145
|
-
*
|
|
146
|
-
*/
|
|
147
|
-
function isRuntimeBackedProvider(provider) {
|
|
148
|
-
return Boolean(provider.runtime);
|
|
149
|
-
}
|
|
150
|
-
/**
|
|
151
|
-
* Returns whether one provider uses the GitHub Copilot SDK bridge runtime.
|
|
152
|
-
*/
|
|
153
|
-
function isCopilotBridgeProvider(provider) {
|
|
154
|
-
return provider.runtime?.kind === "copilot-sdk-bridge";
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* Builds the canonical local bridge URL for one Copilot runtime provider.
|
|
158
|
-
*/
|
|
159
|
-
function buildCopilotBridgeBaseUrl(runtime) {
|
|
160
|
-
return `http://${runtime.bridgeHost}:${runtime.bridgePort}${runtime.bridgePath}`;
|
|
161
|
-
}
|
|
162
|
-
/**
|
|
163
|
-
* Builds the Codex-facing custom model_provider projection for the managed Copilot bridge.
|
|
164
|
-
*/
|
|
165
|
-
function buildCopilotModelProviderProjection(runtime) {
|
|
166
|
-
return {
|
|
167
|
-
baseUrl: buildCopilotBridgeBaseUrl(runtime),
|
|
168
|
-
name: "copilot",
|
|
169
|
-
requiresOpenAiAuth: true,
|
|
170
|
-
wireApi: "responses",
|
|
171
|
-
streamIdleTimeoutMs: 300000,
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
/**
|
|
175
|
-
* Builds the Codex-facing custom model_provider projection for a direct provider.
|
|
120
|
+
* Builds the Codex-facing custom model_provider projection for a provider.
|
|
176
121
|
*/
|
|
177
|
-
function
|
|
122
|
+
function buildModelProviderProjection(profile, baseUrl) {
|
|
178
123
|
const normalizedBaseUrl = baseUrl.trim();
|
|
179
124
|
if (!normalizedBaseUrl) {
|
|
180
|
-
throw new Error(`
|
|
125
|
+
throw new Error(`Model provider "${profile}" requires a non-empty base_url.`);
|
|
181
126
|
}
|
|
182
127
|
return {
|
|
183
128
|
baseUrl: normalizedBaseUrl,
|
|
@@ -186,36 +131,3 @@ function buildDirectModelProviderProjection(profile, baseUrl) {
|
|
|
186
131
|
wireApi: "responses",
|
|
187
132
|
};
|
|
188
133
|
}
|
|
189
|
-
/**
|
|
190
|
-
* Validates one runtime-backed provider block.
|
|
191
|
-
*/
|
|
192
|
-
function validateProviderRuntime(name, runtime) {
|
|
193
|
-
if (!runtime || typeof runtime !== "object" || Array.isArray(runtime)) {
|
|
194
|
-
throw new Error(`Provider "${name}" has an invalid runtime block.`);
|
|
195
|
-
}
|
|
196
|
-
const record = runtime;
|
|
197
|
-
if (record.kind !== "copilot-sdk-bridge") {
|
|
198
|
-
throw new Error(`Provider "${name}" has an unsupported runtime kind.`);
|
|
199
|
-
}
|
|
200
|
-
if (record.upstream !== "github-copilot") {
|
|
201
|
-
throw new Error(`Provider "${name}" has an invalid runtime upstream.`);
|
|
202
|
-
}
|
|
203
|
-
if (typeof record.bridgeHost !== "string" || record.bridgeHost.trim() === "") {
|
|
204
|
-
throw new Error(`Provider "${name}" has an invalid runtime bridgeHost.`);
|
|
205
|
-
}
|
|
206
|
-
if (typeof record.bridgePort !== "number" || !Number.isInteger(record.bridgePort) || record.bridgePort <= 0) {
|
|
207
|
-
throw new Error(`Provider "${name}" has an invalid runtime bridgePort.`);
|
|
208
|
-
}
|
|
209
|
-
if (record.bridgePath !== "/v1") {
|
|
210
|
-
throw new Error(`Provider "${name}" has an invalid runtime bridgePath.`);
|
|
211
|
-
}
|
|
212
|
-
if (record.premiumRequests !== true) {
|
|
213
|
-
throw new Error(`Provider "${name}" must enable runtime premiumRequests.`);
|
|
214
|
-
}
|
|
215
|
-
if (record.authSource !== "official-sdk") {
|
|
216
|
-
throw new Error(`Provider "${name}" has an invalid runtime authSource.`);
|
|
217
|
-
}
|
|
218
|
-
if (record.sdkInstallMode !== "lazy") {
|
|
219
|
-
throw new Error(`Provider "${name}" has an invalid runtime sdkInstallMode.`);
|
|
220
|
-
}
|
|
221
|
-
}
|