@minniexcode/codex-switch 0.2.0 → 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 -83
- package/dist/app/edit-provider.js +9 -29
- package/dist/app/get-status.js +1 -77
- package/dist/app/list-providers.js +0 -2
- package/dist/app/remove-provider.js +3 -5
- package/dist/app/run-doctor.js +2 -99
- package/dist/app/setup-codex.js +0 -2
- package/dist/app/switch-provider.js +1 -74
- package/dist/cli/output.js +3 -89
- package/dist/commands/handlers.js +20 -172
- 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.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 -303
- package/dist/runtime/copilot-adapter.js +0 -617
- package/dist/runtime/copilot-bridge-worker.js +0 -69
- package/dist/runtime/copilot-bridge.js +0 -1351
- package/dist/runtime/copilot-cli.js +0 -164
- package/dist/runtime/copilot-http-bridge-worker.js +0 -228
- package/dist/runtime/copilot-installer.js +0 -231
- package/dist/runtime/copilot-sdk-loader.js +0 -62
- package/dist/runtime/copilot-token.js +0 -294
- 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,7 +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_token_1 = require("../runtime/copilot-token");
|
|
64
62
|
const config_repo_1 = require("../storage/config-repo");
|
|
65
63
|
const codex_paths_1 = require("../storage/codex-paths");
|
|
66
64
|
const providers_repo_1 = require("../storage/providers-repo");
|
|
@@ -95,48 +93,7 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
|
|
|
95
93
|
case "current":
|
|
96
94
|
return (0, get_current_profile_1.getCurrentProfile)(paths.configPath, paths.providersPath);
|
|
97
95
|
case "status":
|
|
98
|
-
return (0, get_status_1.getStatus)(paths.codexDir, paths.configPath, paths.providersPath, paths.authPath
|
|
99
|
-
runtimeDir: paths.runtimeDir,
|
|
100
|
-
runtimesDir: paths.runtimesDir,
|
|
101
|
-
toolHomeDir: paths.toolHomeDir,
|
|
102
|
-
});
|
|
103
|
-
case "bridge-start": {
|
|
104
|
-
const providerName = parsed.positionals[0] ?? null;
|
|
105
|
-
return (0, bridge_1.startBridge)({
|
|
106
|
-
providersPath: paths.providersPath,
|
|
107
|
-
configPath: paths.configPath,
|
|
108
|
-
runtimeDir: paths.runtimeDir,
|
|
109
|
-
runtimesDir: paths.runtimesDir,
|
|
110
|
-
toolHomeDir: paths.toolHomeDir,
|
|
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,54 +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
|
-
// Check if already authenticated
|
|
160
|
-
const existingToken = (0, copilot_token_1.readGithubToken)(paths.toolHomeDir);
|
|
161
|
-
if (existingToken) {
|
|
162
|
-
try {
|
|
163
|
-
await (0, copilot_token_1.exchangeForCopilotToken)(existingToken);
|
|
164
|
-
return {
|
|
165
|
-
data: {
|
|
166
|
-
upstream: "github-copilot",
|
|
167
|
-
authReady: true,
|
|
168
|
-
loginLaunched: false,
|
|
169
|
-
authSource: "github-pat",
|
|
170
|
-
},
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
catch {
|
|
174
|
-
// Token is invalid, proceed with new login
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
// Start GitHub OAuth Device Flow
|
|
178
|
-
runtime.writeLine("Starting GitHub authentication...");
|
|
179
|
-
const deviceFlow = await (0, copilot_token_1.startDeviceFlow)();
|
|
180
|
-
runtime.writeLine(`\nPlease visit: ${deviceFlow.verificationUri}`);
|
|
181
|
-
runtime.writeLine(`And enter code: ${deviceFlow.userCode}\n`);
|
|
182
|
-
runtime.writeLine("Waiting for authorization...");
|
|
183
|
-
const githubPat = await (0, copilot_token_1.pollDeviceFlowToken)(deviceFlow.deviceCode, deviceFlow.interval, deviceFlow.expiresIn);
|
|
184
|
-
// Validate the token by doing a test exchange
|
|
185
|
-
await (0, copilot_token_1.exchangeForCopilotToken)(githubPat);
|
|
186
|
-
(0, copilot_token_1.writeGithubToken)(githubPat, paths.toolHomeDir);
|
|
187
|
-
runtime.writeLine("GitHub Copilot authentication successful!");
|
|
188
|
-
return {
|
|
189
|
-
data: {
|
|
190
|
-
upstream: "github-copilot",
|
|
191
|
-
authReady: true,
|
|
192
|
-
loginLaunched: true,
|
|
193
|
-
authSource: "github-pat",
|
|
194
|
-
},
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
106
|
case "config-show":
|
|
198
107
|
return (0, show_config_1.showConfig)({
|
|
199
108
|
configPath: paths.configPath,
|
|
@@ -213,11 +122,6 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
|
|
|
213
122
|
if (!providerName) {
|
|
214
123
|
throw (0, errors_1.cliError)("PROVIDER_NOT_FOUND", "Missing provider name for switch command.");
|
|
215
124
|
}
|
|
216
|
-
if ((0, args_1.hasFlag)(parsed.commandOptions, "--install-copilot-sdk")) {
|
|
217
|
-
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "--install-copilot-sdk is no longer supported with switch. Run `codexs login copilot` instead.", {
|
|
218
|
-
suggestion: "Run `codexs login copilot` first, then rerun switch without --install-copilot-sdk.",
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
125
|
return (0, switch_provider_1.switchProvider)({
|
|
222
126
|
codexDir: paths.codexDir,
|
|
223
127
|
lockPath: paths.lockPath,
|
|
@@ -226,9 +130,6 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
|
|
|
226
130
|
configPath: paths.configPath,
|
|
227
131
|
providersPath: paths.providersPath,
|
|
228
132
|
authPath: paths.authPath,
|
|
229
|
-
runtimeDir: paths.runtimeDir,
|
|
230
|
-
runtimesDir: paths.runtimesDir,
|
|
231
|
-
toolHomeDir: paths.toolHomeDir,
|
|
232
133
|
providerName,
|
|
233
134
|
});
|
|
234
135
|
}
|
|
@@ -289,75 +190,31 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
|
|
|
289
190
|
let note = (0, args_1.getSingleOption)(parsed.commandOptions, "--note", false);
|
|
290
191
|
let tags = parsed.commandOptions.get("--tag") ?? [];
|
|
291
192
|
let createProfile = (0, args_1.hasFlag)(parsed.commandOptions, "--create-profile");
|
|
292
|
-
|
|
293
|
-
let bridgeHost = (0, args_1.getSingleOption)(parsed.commandOptions, "--bridge-host", false);
|
|
294
|
-
const bridgePortValue = (0, args_1.getSingleOption)(parsed.commandOptions, "--bridge-port", false);
|
|
295
|
-
let bridgeApiKey = (0, args_1.getSingleOption)(parsed.commandOptions, "--bridge-api-key", false);
|
|
296
|
-
const installCopilotSdk = (0, args_1.hasFlag)(parsed.commandOptions, "--install-copilot-sdk");
|
|
297
|
-
let bridgePort = bridgePortValue ? Number(bridgePortValue) : null;
|
|
298
|
-
if (copilot && apiKey) {
|
|
299
|
-
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "--copilot does not allow --api-key. Use --bridge-api-key for the local bridge secret.");
|
|
300
|
-
}
|
|
301
|
-
if (copilot && installCopilotSdk) {
|
|
302
|
-
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "--install-copilot-sdk is no longer supported with add --copilot. Run `codexs login copilot` instead.", {
|
|
303
|
-
suggestion: "Run `codexs login copilot` first, then rerun add --copilot.",
|
|
304
|
-
});
|
|
305
|
-
}
|
|
306
|
-
if (bridgePortValue && (!Number.isInteger(bridgePort) || bridgePort === null || bridgePort <= 0)) {
|
|
307
|
-
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "--bridge-port must be a positive integer.");
|
|
308
|
-
}
|
|
309
|
-
if (!providerName || !profile || (!apiKey && !copilot)) {
|
|
193
|
+
if (!providerName || !profile || !apiKey) {
|
|
310
194
|
if (ctx.options.json || !runtime.isInteractive()) {
|
|
311
|
-
throw (0, add_interactive_1.createNonInteractiveAddError)(
|
|
312
|
-
}
|
|
313
|
-
if (copilot) {
|
|
314
|
-
const prompted = await (0, add_interactive_1.collectCopilotAddInput)(runtime, {
|
|
315
|
-
providerName,
|
|
316
|
-
profile,
|
|
317
|
-
model,
|
|
318
|
-
note,
|
|
319
|
-
tags,
|
|
320
|
-
}, (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)), {
|
|
321
|
-
bridgeHost,
|
|
322
|
-
bridgePort,
|
|
323
|
-
bridgeApiKey,
|
|
324
|
-
});
|
|
325
|
-
providerName = prompted.providerName;
|
|
326
|
-
profile = prompted.profile;
|
|
327
|
-
model = prompted.model ?? null;
|
|
328
|
-
note = prompted.note ?? null;
|
|
329
|
-
tags = prompted.tags;
|
|
330
|
-
createProfile = createProfile || prompted.createProfile;
|
|
331
|
-
baseUrl = null;
|
|
332
|
-
bridgeHost = prompted.bridgeHost ?? bridgeHost;
|
|
333
|
-
bridgePort = prompted.bridgePort ?? bridgePort;
|
|
334
|
-
bridgeApiKey = prompted.bridgeApiKey ?? bridgeApiKey;
|
|
335
|
-
}
|
|
336
|
-
else {
|
|
337
|
-
const prompted = await (0, add_interactive_1.collectAddInput)(runtime, {
|
|
338
|
-
providerName,
|
|
339
|
-
profile,
|
|
340
|
-
apiKey,
|
|
341
|
-
model,
|
|
342
|
-
baseUrl,
|
|
343
|
-
note,
|
|
344
|
-
tags,
|
|
345
|
-
}, (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)));
|
|
346
|
-
providerName = prompted.providerName;
|
|
347
|
-
profile = prompted.profile;
|
|
348
|
-
apiKey = prompted.apiKey;
|
|
349
|
-
model = prompted.model ?? null;
|
|
350
|
-
baseUrl = prompted.baseUrl ?? null;
|
|
351
|
-
note = prompted.note ?? null;
|
|
352
|
-
tags = prompted.tags;
|
|
353
|
-
createProfile = createProfile || prompted.createProfile;
|
|
195
|
+
throw (0, add_interactive_1.createNonInteractiveAddError)();
|
|
354
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;
|
|
355
214
|
}
|
|
356
215
|
return (0, add_provider_1.addProvider)({
|
|
357
216
|
codexDir: paths.codexDir,
|
|
358
|
-
toolHomeDir: paths.toolHomeDir,
|
|
359
217
|
lockPath: paths.lockPath,
|
|
360
|
-
runtimesDir: paths.runtimesDir,
|
|
361
218
|
backupsDir: paths.backupsDir,
|
|
362
219
|
latestBackupPath: paths.latestBackupPath,
|
|
363
220
|
providersPath: paths.providersPath,
|
|
@@ -371,10 +228,6 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
|
|
|
371
228
|
note,
|
|
372
229
|
tags,
|
|
373
230
|
createProfile,
|
|
374
|
-
copilot,
|
|
375
|
-
bridgeHost,
|
|
376
|
-
bridgePort,
|
|
377
|
-
bridgeApiKey,
|
|
378
231
|
});
|
|
379
232
|
}
|
|
380
233
|
case "edit": {
|
|
@@ -467,9 +320,6 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
|
|
|
467
320
|
configPath: paths.configPath,
|
|
468
321
|
providersPath: paths.providersPath,
|
|
469
322
|
authPath: paths.authPath,
|
|
470
|
-
runtimeDir: paths.runtimeDir,
|
|
471
|
-
runtimesDir: paths.runtimesDir,
|
|
472
|
-
toolHomeDir: paths.toolHomeDir,
|
|
473
323
|
});
|
|
474
324
|
case "migrate": {
|
|
475
325
|
let codexDir = ctx.options.codexDir;
|
|
@@ -569,8 +419,6 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
|
|
|
569
419
|
configPath: setupPaths.configPath,
|
|
570
420
|
providersPath: setupPaths.providersPath,
|
|
571
421
|
authPath: setupPaths.authPath,
|
|
572
|
-
runtimeDir: setupPaths.runtimeDir,
|
|
573
|
-
runtimesDir: setupPaths.runtimesDir,
|
|
574
422
|
backupsDir: setupPaths.backupsDir,
|
|
575
423
|
latestBackupPath: setupPaths.latestBackupPath,
|
|
576
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,21 +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
|
-
const cleanedRuntime = {
|
|
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
|
-
};
|
|
100
|
-
if (record.runtime.sdkInstallMode) {
|
|
101
|
-
cleanedRuntime.sdkInstallMode = record.runtime.sdkInstallMode;
|
|
102
|
-
}
|
|
103
|
-
next.runtime = cleanedRuntime;
|
|
104
|
-
}
|
|
105
77
|
return next;
|
|
106
78
|
}
|
|
107
79
|
/**
|
|
@@ -145,42 +117,12 @@ function maskSecret(value) {
|
|
|
145
117
|
return `${value.slice(0, 3)}***${value.slice(-2)}`;
|
|
146
118
|
}
|
|
147
119
|
/**
|
|
148
|
-
*
|
|
149
|
-
*/
|
|
150
|
-
function isRuntimeBackedProvider(provider) {
|
|
151
|
-
return Boolean(provider.runtime);
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* Returns whether one provider uses the GitHub Copilot bridge runtime.
|
|
155
|
-
*/
|
|
156
|
-
function isCopilotBridgeProvider(provider) {
|
|
157
|
-
return provider.runtime?.kind === "copilot-http-proxy" || provider.runtime?.kind === "copilot-sdk-bridge";
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* Builds the canonical local bridge URL for one Copilot runtime provider.
|
|
161
|
-
*/
|
|
162
|
-
function buildCopilotBridgeBaseUrl(runtime) {
|
|
163
|
-
return `http://${runtime.bridgeHost}:${runtime.bridgePort}${runtime.bridgePath}`;
|
|
164
|
-
}
|
|
165
|
-
/**
|
|
166
|
-
* Builds the Codex-facing custom model_provider projection for the managed Copilot bridge.
|
|
167
|
-
*/
|
|
168
|
-
function buildCopilotModelProviderProjection(runtime) {
|
|
169
|
-
return {
|
|
170
|
-
baseUrl: buildCopilotBridgeBaseUrl(runtime),
|
|
171
|
-
name: "copilot",
|
|
172
|
-
requiresOpenAiAuth: true,
|
|
173
|
-
wireApi: "responses",
|
|
174
|
-
streamIdleTimeoutMs: 300000,
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* Builds the Codex-facing custom model_provider projection for a direct provider.
|
|
120
|
+
* Builds the Codex-facing custom model_provider projection for a provider.
|
|
179
121
|
*/
|
|
180
|
-
function
|
|
122
|
+
function buildModelProviderProjection(profile, baseUrl) {
|
|
181
123
|
const normalizedBaseUrl = baseUrl.trim();
|
|
182
124
|
if (!normalizedBaseUrl) {
|
|
183
|
-
throw new Error(`
|
|
125
|
+
throw new Error(`Model provider "${profile}" requires a non-empty base_url.`);
|
|
184
126
|
}
|
|
185
127
|
return {
|
|
186
128
|
baseUrl: normalizedBaseUrl,
|
|
@@ -189,33 +131,3 @@ function buildDirectModelProviderProjection(profile, baseUrl) {
|
|
|
189
131
|
wireApi: "responses",
|
|
190
132
|
};
|
|
191
133
|
}
|
|
192
|
-
/**
|
|
193
|
-
* Validates one runtime-backed provider block.
|
|
194
|
-
*/
|
|
195
|
-
function validateProviderRuntime(name, runtime) {
|
|
196
|
-
if (!runtime || typeof runtime !== "object" || Array.isArray(runtime)) {
|
|
197
|
-
throw new Error(`Provider "${name}" has an invalid runtime block.`);
|
|
198
|
-
}
|
|
199
|
-
const record = runtime;
|
|
200
|
-
if (record.kind !== "copilot-http-proxy" && record.kind !== "copilot-sdk-bridge") {
|
|
201
|
-
throw new Error(`Provider "${name}" has an unsupported runtime kind.`);
|
|
202
|
-
}
|
|
203
|
-
if (record.upstream !== "github-copilot") {
|
|
204
|
-
throw new Error(`Provider "${name}" has an invalid runtime upstream.`);
|
|
205
|
-
}
|
|
206
|
-
if (typeof record.bridgeHost !== "string" || record.bridgeHost.trim() === "") {
|
|
207
|
-
throw new Error(`Provider "${name}" has an invalid runtime bridgeHost.`);
|
|
208
|
-
}
|
|
209
|
-
if (typeof record.bridgePort !== "number" || !Number.isInteger(record.bridgePort) || record.bridgePort <= 0) {
|
|
210
|
-
throw new Error(`Provider "${name}" has an invalid runtime bridgePort.`);
|
|
211
|
-
}
|
|
212
|
-
if (record.bridgePath !== "/v1") {
|
|
213
|
-
throw new Error(`Provider "${name}" has an invalid runtime bridgePath.`);
|
|
214
|
-
}
|
|
215
|
-
if (record.premiumRequests !== true) {
|
|
216
|
-
throw new Error(`Provider "${name}" must enable runtime premiumRequests.`);
|
|
217
|
-
}
|
|
218
|
-
if (record.authSource !== "github-pat" && record.authSource !== "official-sdk") {
|
|
219
|
-
throw new Error(`Provider "${name}" has an invalid runtime authSource.`);
|
|
220
|
-
}
|
|
221
|
-
}
|