@minniexcode/codex-switch 0.0.9 → 0.0.11
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 +52 -13
- package/README.CN.md +94 -39
- package/README.md +75 -33
- package/dist/app/add-provider.js +29 -26
- package/dist/app/bridge.js +15 -15
- package/dist/app/edit-provider.js +2 -18
- package/dist/app/get-status.js +35 -13
- package/dist/app/import-providers.js +1 -1
- package/dist/app/init-codex.js +13 -14
- package/dist/app/list-providers.js +0 -1
- package/dist/app/remove-provider.js +1 -1
- package/dist/app/run-doctor.js +21 -39
- package/dist/app/run-mutation.js +3 -2
- package/dist/app/setup-codex.js +30 -18
- package/dist/app/show-config.js +1 -5
- package/dist/app/switch-provider.js +16 -33
- package/dist/cli/output.js +4 -6
- package/dist/cli.js +35 -3
- package/dist/commands/args.js +2 -2
- package/dist/commands/dispatch.js +40 -0
- package/dist/commands/handlers.js +202 -84
- package/dist/commands/help.js +2 -0
- package/dist/commands/registry.js +33 -12
- package/dist/domain/backups.js +4 -4
- package/dist/domain/config.js +102 -61
- package/dist/domain/providers.js +12 -5
- package/dist/domain/runtime-state.js +81 -4
- package/dist/domain/setup.js +58 -3
- package/dist/interaction/add-interactive.js +55 -1
- package/dist/interaction/interactive.js +1 -5
- package/dist/runtime/copilot-adapter.js +56 -13
- package/dist/runtime/copilot-bridge.js +392 -44
- package/dist/runtime/copilot-cli.js +142 -0
- package/dist/runtime/copilot-installer.js +59 -11
- package/dist/runtime/copilot-sdk-loader.js +5 -5
- package/dist/storage/auth-repo.js +28 -77
- package/dist/storage/backup-repo.js +4 -4
- package/dist/storage/codex-paths.js +34 -8
- package/dist/storage/config-repo.js +1 -36
- package/dist/storage/lock-repo.js +2 -4
- package/dist/storage/runtime-state-repo.js +43 -10
- package/dist/storage/tool-config-repo.js +111 -0
- package/docs/Design/codex-switch-copilot-integration-design.md +517 -0
- package/docs/Design/codex-switch-v0.0.10-design.md +669 -0
- package/docs/Design/codex-switch-v0.0.11-design.md +824 -0
- package/docs/PRD/codex-switch-prd-v0.0.10.md +406 -0
- package/docs/PRD/codex-switch-prd-v0.0.11.md +577 -0
- package/docs/cli-usage.md +166 -271
- package/docs/codex-switch-product-overview.md +2 -2
- package/docs/codex-switch-technical-architecture.md +6 -5
- package/package.json +1 -1
package/dist/app/add-provider.js
CHANGED
|
@@ -41,13 +41,13 @@ const errors_1 = require("../domain/errors");
|
|
|
41
41
|
const config_repo_1 = require("../storage/config-repo");
|
|
42
42
|
const fs_utils_1 = require("../storage/fs-utils");
|
|
43
43
|
const providers_repo_1 = require("../storage/providers-repo");
|
|
44
|
-
const
|
|
44
|
+
const copilot_adapter_1 = require("../runtime/copilot-adapter");
|
|
45
45
|
const copilot_installer_1 = require("../runtime/copilot-installer");
|
|
46
46
|
const run_mutation_1 = require("./run-mutation");
|
|
47
47
|
/**
|
|
48
48
|
* Adds a new provider record to the managed providers registry.
|
|
49
49
|
*/
|
|
50
|
-
function addProvider(args) {
|
|
50
|
+
async function addProvider(args) {
|
|
51
51
|
(0, fs_utils_1.ensureDir)(args.codexDir);
|
|
52
52
|
const providers = (0, providers_repo_1.readProvidersFileIfExists)(args.providersPath);
|
|
53
53
|
if (providers.providers[args.providerName]) {
|
|
@@ -68,17 +68,26 @@ function addProvider(args) {
|
|
|
68
68
|
}
|
|
69
69
|
: undefined;
|
|
70
70
|
if (args.copilot) {
|
|
71
|
-
const installStatus = (0, copilot_installer_1.probeCopilotSdkInstall)();
|
|
71
|
+
const installStatus = (0, copilot_installer_1.probeCopilotSdkInstall)(args.runtimesDir);
|
|
72
72
|
if (!installStatus.installed) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
73
|
+
throw (0, errors_1.cliError)("COPILOT_SDK_MISSING", "The optional Copilot SDK runtime is not installed. Run `codexs login copilot` first.", {
|
|
74
|
+
installDir: installStatus.installDir,
|
|
75
|
+
packageName: installStatus.packageName,
|
|
76
|
+
suggestion: "Run `codexs login copilot` to install the Copilot SDK and complete login.",
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
await (0, copilot_adapter_1.readCopilotAuthState)(args.runtimesDir);
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
const normalized = (0, errors_1.normalizeError)(error);
|
|
84
|
+
if (normalized.code === "COPILOT_AUTH_REQUIRED") {
|
|
85
|
+
throw (0, errors_1.cliError)("COPILOT_AUTH_REQUIRED", "Copilot authentication is required before a Copilot provider can be added.", {
|
|
86
|
+
...(normalized.details ?? {}),
|
|
87
|
+
suggestion: "Run `codexs login copilot` to complete GitHub Copilot login.",
|
|
79
88
|
});
|
|
80
89
|
}
|
|
81
|
-
|
|
90
|
+
throw error;
|
|
82
91
|
}
|
|
83
92
|
}
|
|
84
93
|
const document = (0, config_repo_1.readStructuredConfig)(args.configPath);
|
|
@@ -98,18 +107,20 @@ function addProvider(args) {
|
|
|
98
107
|
}),
|
|
99
108
|
}
|
|
100
109
|
: undefined;
|
|
101
|
-
const upsertModelProviders =
|
|
110
|
+
const upsertModelProviders = args.copilot
|
|
102
111
|
? {
|
|
103
|
-
[args.profile]:
|
|
104
|
-
baseUrl: args.copilot ? (0, providers_1.buildCopilotBridgeBaseUrl)(runtime) : args.baseUrl ?? undefined,
|
|
105
|
-
envKey: (0, config_1.buildManagedProfileEnvKey)(args.profile),
|
|
106
|
-
},
|
|
112
|
+
[args.profile]: (0, providers_1.buildCopilotModelProviderProjection)(runtime),
|
|
107
113
|
}
|
|
108
|
-
:
|
|
114
|
+
: !existingModelProvider && args.createProfile
|
|
115
|
+
? {
|
|
116
|
+
[args.profile]: {
|
|
117
|
+
baseUrl: args.baseUrl ?? undefined,
|
|
118
|
+
},
|
|
119
|
+
}
|
|
120
|
+
: undefined;
|
|
109
121
|
if (existingProfile) {
|
|
110
122
|
(0, config_repo_1.requireManagedProfileRuntime)(document, providers, args.profile);
|
|
111
123
|
}
|
|
112
|
-
const envKey = existingModelProvider?.envKey ?? (0, config_1.buildManagedProfileEnvKey)(args.profile);
|
|
113
124
|
const apiKey = args.copilot ? args.bridgeApiKey ?? crypto.randomBytes(24).toString("hex") : args.apiKey;
|
|
114
125
|
const baseUrl = args.copilot ? (0, providers_1.buildCopilotBridgeBaseUrl)(runtime) : args.baseUrl ?? undefined;
|
|
115
126
|
const next = {
|
|
@@ -118,7 +129,6 @@ function addProvider(args) {
|
|
|
118
129
|
[args.providerName]: (0, providers_1.cleanProviderRecord)({
|
|
119
130
|
profile: args.profile,
|
|
120
131
|
apiKey,
|
|
121
|
-
envKey,
|
|
122
132
|
baseUrl,
|
|
123
133
|
note: args.note ?? undefined,
|
|
124
134
|
tags: args.tags,
|
|
@@ -127,14 +137,13 @@ function addProvider(args) {
|
|
|
127
137
|
},
|
|
128
138
|
};
|
|
129
139
|
return (0, run_mutation_1.runMutation)({
|
|
130
|
-
|
|
140
|
+
lockPath: args.lockPath,
|
|
131
141
|
backupsDir: args.backupsDir,
|
|
132
142
|
latestBackupPath: args.latestBackupPath,
|
|
133
143
|
operation: "add",
|
|
134
144
|
files: [
|
|
135
145
|
{ absolutePath: args.providersPath, relativePath: "providers.json" },
|
|
136
146
|
{ absolutePath: args.configPath, relativePath: "config.toml" },
|
|
137
|
-
{ absolutePath: args.authPath, relativePath: "auth.json" },
|
|
138
147
|
],
|
|
139
148
|
mutate: () => {
|
|
140
149
|
const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {
|
|
@@ -144,15 +153,9 @@ function addProvider(args) {
|
|
|
144
153
|
// Persist only the normalized provider payload so later reads are deterministic.
|
|
145
154
|
(0, providers_repo_1.writeProvidersFile)(args.providersPath, next);
|
|
146
155
|
(0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
|
|
147
|
-
if (document.activeProfile === args.profile) {
|
|
148
|
-
const activeProviderName = (0, config_repo_1.resolveActiveProviderName)(document, next);
|
|
149
|
-
const existingAuth = (0, auth_repo_1.readAuthFileIfExists)(args.authPath);
|
|
150
|
-
(0, auth_repo_1.writeAuthFile)(args.authPath, next.providers[activeProviderName], existingAuth ?? undefined);
|
|
151
|
-
}
|
|
152
156
|
return {
|
|
153
157
|
provider: args.providerName,
|
|
154
158
|
profile: args.profile,
|
|
155
|
-
envKey,
|
|
156
159
|
runtimeKind: runtime?.kind ?? null,
|
|
157
160
|
createdProfileSections: configPlan.createdProfileSections,
|
|
158
161
|
createdModelProviderSections: configPlan.createdModelProviderSections,
|
package/dist/app/bridge.js
CHANGED
|
@@ -22,13 +22,14 @@ async function startBridge(args) {
|
|
|
22
22
|
requestedProviderName: args.providerName ?? null,
|
|
23
23
|
providers,
|
|
24
24
|
configPath: args.configPath,
|
|
25
|
+
runtimeDir: args.runtimeDir,
|
|
25
26
|
runtime: args.runtime,
|
|
26
27
|
json: args.json,
|
|
27
28
|
commandName: "start",
|
|
28
29
|
preferRuntimeState: false,
|
|
29
30
|
});
|
|
30
|
-
await requireBridgeRuntimeReadiness();
|
|
31
|
-
const bridge = await (0, copilot_bridge_1.ensureCopilotBridge)(target.providerName, target.provider);
|
|
31
|
+
await requireBridgeRuntimeReadiness(args.runtimesDir);
|
|
32
|
+
const bridge = await (0, copilot_bridge_1.ensureCopilotBridge)(target.providerName, target.provider, args.runtimeDir);
|
|
32
33
|
const nextProvider = bridge.portChanged ? rewriteBridgeProviderPort(target.provider, bridge.port) : target.provider;
|
|
33
34
|
if (bridge.portChanged) {
|
|
34
35
|
try {
|
|
@@ -43,7 +44,7 @@ async function startBridge(args) {
|
|
|
43
44
|
}
|
|
44
45
|
catch (error) {
|
|
45
46
|
if (!bridge.reused) {
|
|
46
|
-
(0, copilot_bridge_1.stopCopilotBridge)();
|
|
47
|
+
(0, copilot_bridge_1.stopCopilotBridge)(args.runtimeDir);
|
|
47
48
|
}
|
|
48
49
|
throw error;
|
|
49
50
|
}
|
|
@@ -66,7 +67,7 @@ async function startBridge(args) {
|
|
|
66
67
|
*/
|
|
67
68
|
async function stopBridge(args) {
|
|
68
69
|
const providers = (0, providers_repo_1.readProvidersFile)(args.providersPath);
|
|
69
|
-
const state = (0, runtime_state_repo_1.readCopilotBridgeState)();
|
|
70
|
+
const state = (0, runtime_state_repo_1.readCopilotBridgeState)(args.runtimeDir);
|
|
70
71
|
if (!state && !args.providerName) {
|
|
71
72
|
return {
|
|
72
73
|
data: {
|
|
@@ -90,6 +91,7 @@ async function stopBridge(args) {
|
|
|
90
91
|
requestedProviderName: args.providerName ?? null,
|
|
91
92
|
providers,
|
|
92
93
|
configPath: args.configPath,
|
|
94
|
+
runtimeDir: args.runtimeDir,
|
|
93
95
|
runtime: args.runtime,
|
|
94
96
|
json: args.json,
|
|
95
97
|
commandName: "stop",
|
|
@@ -101,7 +103,7 @@ async function stopBridge(args) {
|
|
|
101
103
|
requestedProvider: args.providerName,
|
|
102
104
|
});
|
|
103
105
|
}
|
|
104
|
-
(0, copilot_bridge_1.stopCopilotBridge)();
|
|
106
|
+
(0, copilot_bridge_1.stopCopilotBridge)(args.runtimeDir);
|
|
105
107
|
return {
|
|
106
108
|
data: {
|
|
107
109
|
provider: target.providerName,
|
|
@@ -115,18 +117,19 @@ async function stopBridge(args) {
|
|
|
115
117
|
*/
|
|
116
118
|
async function statusBridge(args) {
|
|
117
119
|
const providers = (0, providers_repo_1.readProvidersFile)(args.providersPath);
|
|
118
|
-
const state = (0, runtime_state_repo_1.readCopilotBridgeState)();
|
|
120
|
+
const state = (0, runtime_state_repo_1.readCopilotBridgeState)(args.runtimeDir);
|
|
119
121
|
const target = await resolveBridgeTarget({
|
|
120
122
|
requestedProviderName: args.providerName ?? null,
|
|
121
123
|
providers,
|
|
122
124
|
configPath: args.configPath,
|
|
125
|
+
runtimeDir: args.runtimeDir,
|
|
123
126
|
runtime: args.runtime,
|
|
124
127
|
json: args.json,
|
|
125
128
|
commandName: "status",
|
|
126
129
|
preferRuntimeState: true,
|
|
127
130
|
});
|
|
128
131
|
const provider = target.provider;
|
|
129
|
-
const runtimeStatus = await (0, copilot_bridge_1.probeCopilotBridgeRuntime)(provider);
|
|
132
|
+
const runtimeStatus = await (0, copilot_bridge_1.probeCopilotBridgeRuntime)(provider, state, args.runtimeDir);
|
|
130
133
|
const expectedBaseUrl = (0, providers_1.buildCopilotBridgeBaseUrl)(provider.runtime);
|
|
131
134
|
if (args.providerName && state?.provider && state.provider !== args.providerName) {
|
|
132
135
|
throw (0, errors_1.cliError)("BRIDGE_PROVIDER_MISMATCH", `Bridge runtime state belongs to "${state.provider}" not "${args.providerName}".`, {
|
|
@@ -154,7 +157,7 @@ async function resolveBridgeTarget(args) {
|
|
|
154
157
|
return resolveNamedBridgeProvider(args.providers, args.requestedProviderName);
|
|
155
158
|
}
|
|
156
159
|
if (args.preferRuntimeState) {
|
|
157
|
-
const runtimeState = (0, runtime_state_repo_1.readCopilotBridgeState)();
|
|
160
|
+
const runtimeState = (0, runtime_state_repo_1.readCopilotBridgeState)(args.runtimeDir);
|
|
158
161
|
if (runtimeState?.provider && args.providers.providers[runtimeState.provider]) {
|
|
159
162
|
return resolveNamedBridgeProvider(args.providers, runtimeState.provider);
|
|
160
163
|
}
|
|
@@ -238,15 +241,15 @@ async function promptForCopilotBridgeSelection(runtime, targets, commandName) {
|
|
|
238
241
|
/**
|
|
239
242
|
* Verifies that the local Copilot bridge prerequisites are available before startup.
|
|
240
243
|
*/
|
|
241
|
-
async function requireBridgeRuntimeReadiness() {
|
|
242
|
-
const installStatus = (0, copilot_installer_1.probeCopilotSdkInstall)();
|
|
244
|
+
async function requireBridgeRuntimeReadiness(runtimesDir) {
|
|
245
|
+
const installStatus = (0, copilot_installer_1.probeCopilotSdkInstall)(runtimesDir);
|
|
243
246
|
if (!installStatus.installed) {
|
|
244
247
|
throw (0, errors_1.cliError)("COPILOT_SDK_MISSING", "The optional Copilot SDK runtime is not installed.", {
|
|
245
248
|
installDir: installStatus.installDir,
|
|
246
249
|
packageName: installStatus.packageName,
|
|
247
250
|
});
|
|
248
251
|
}
|
|
249
|
-
await (0, copilot_adapter_1.readCopilotAuthState)();
|
|
252
|
+
await (0, copilot_adapter_1.readCopilotAuthState)(runtimesDir);
|
|
250
253
|
}
|
|
251
254
|
/**
|
|
252
255
|
* Rewrites one Copilot bridge provider record with a recovered runtime port.
|
|
@@ -281,10 +284,7 @@ function persistRecoveredBridgePort(args) {
|
|
|
281
284
|
const document = (0, config_repo_1.readStructuredConfig)(args.configPath);
|
|
282
285
|
const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {
|
|
283
286
|
upsertModelProviders: {
|
|
284
|
-
[args.provider.profile]:
|
|
285
|
-
baseUrl: (0, providers_1.buildCopilotBridgeBaseUrl)(args.provider.runtime),
|
|
286
|
-
envKey: (0, config_repo_1.requireRuntimeEnvKey)(document, args.provider.profile),
|
|
287
|
-
},
|
|
287
|
+
[args.provider.profile]: (0, providers_1.buildCopilotModelProviderProjection)(args.provider.runtime),
|
|
288
288
|
},
|
|
289
289
|
});
|
|
290
290
|
(0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
|
|
@@ -7,7 +7,6 @@ const providers_1 = require("../domain/providers");
|
|
|
7
7
|
const config_repo_1 = require("../storage/config-repo");
|
|
8
8
|
const fs_utils_1 = require("../storage/fs-utils");
|
|
9
9
|
const providers_repo_1 = require("../storage/providers-repo");
|
|
10
|
-
const auth_repo_1 = require("../storage/auth-repo");
|
|
11
10
|
const run_mutation_1 = require("./run-mutation");
|
|
12
11
|
/**
|
|
13
12
|
* Updates selected fields on a single managed provider.
|
|
@@ -63,23 +62,15 @@ function editProvider(args) {
|
|
|
63
62
|
upsertModelProviders = {
|
|
64
63
|
[newProfile]: {
|
|
65
64
|
baseUrl: args.baseUrl ?? undefined,
|
|
66
|
-
envKey: (0, config_1.buildManagedProfileEnvKey)(newProfile),
|
|
67
65
|
},
|
|
68
66
|
};
|
|
69
67
|
}
|
|
70
68
|
else {
|
|
71
69
|
(0, config_repo_1.requireManagedProfileRuntime)(document, providers, newProfile);
|
|
72
70
|
}
|
|
73
|
-
const nextEnvKey = args.profile !== undefined && args.profile !== current.profile
|
|
74
|
-
? targetModelProviderSection?.envKey ?? (0, config_1.buildManagedProfileEnvKey)(newProfile)
|
|
75
|
-
: current.envKey;
|
|
76
|
-
if (nextEnvKey !== current.envKey) {
|
|
77
|
-
updatedFields.push("envKey");
|
|
78
|
-
}
|
|
79
71
|
const nextRecord = (0, providers_1.cleanProviderRecord)({
|
|
80
72
|
profile: newProfile,
|
|
81
73
|
apiKey: args.apiKey ?? current.apiKey,
|
|
82
|
-
envKey: nextEnvKey,
|
|
83
74
|
baseUrl: args.baseUrl === null ? undefined : args.baseUrl ?? current.baseUrl,
|
|
84
75
|
note: args.note === null ? undefined : args.note ?? current.note,
|
|
85
76
|
tags: args.tags ?? current.tags,
|
|
@@ -90,7 +81,7 @@ function editProvider(args) {
|
|
|
90
81
|
...(args.model !== undefined && args.model !== null ? { model: args.model } : {}),
|
|
91
82
|
},
|
|
92
83
|
};
|
|
93
|
-
if (args.model !== undefined &&
|
|
84
|
+
if (args.model !== undefined && targetSection?.model !== args.model && !updatedFields.includes("model")) {
|
|
94
85
|
updatedFields.push("model");
|
|
95
86
|
}
|
|
96
87
|
}
|
|
@@ -118,14 +109,13 @@ function editProvider(args) {
|
|
|
118
109
|
switchToProfile: args.switchToProfile ?? null,
|
|
119
110
|
});
|
|
120
111
|
return (0, run_mutation_1.runMutation)({
|
|
121
|
-
|
|
112
|
+
lockPath: args.lockPath,
|
|
122
113
|
backupsDir: args.backupsDir,
|
|
123
114
|
latestBackupPath: args.latestBackupPath,
|
|
124
115
|
operation: "edit",
|
|
125
116
|
files: [
|
|
126
117
|
{ absolutePath: args.providersPath, relativePath: "providers.json" },
|
|
127
118
|
{ absolutePath: args.configPath, relativePath: "config.toml" },
|
|
128
|
-
{ absolutePath: args.authPath, relativePath: "auth.json" },
|
|
129
119
|
],
|
|
130
120
|
mutate: () => {
|
|
131
121
|
const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {
|
|
@@ -143,12 +133,6 @@ function editProvider(args) {
|
|
|
143
133
|
// Write providers first so the registry and config move together inside the managed backup boundary.
|
|
144
134
|
(0, providers_repo_1.writeProvidersFile)(args.providersPath, nextProviders);
|
|
145
135
|
(0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
|
|
146
|
-
const updatedDocument = (0, config_repo_1.readStructuredConfig)(args.configPath);
|
|
147
|
-
if (updatedDocument.activeProfile) {
|
|
148
|
-
const activeProviderName = (0, config_repo_1.resolveActiveProviderName)(updatedDocument, nextProviders);
|
|
149
|
-
const existingAuth = (0, auth_repo_1.readAuthFileIfExists)(args.authPath);
|
|
150
|
-
(0, auth_repo_1.writeAuthFile)(args.authPath, nextProviders.providers[activeProviderName], existingAuth ?? undefined);
|
|
151
|
-
}
|
|
152
136
|
return {
|
|
153
137
|
provider: args.providerName,
|
|
154
138
|
updatedFields,
|
package/dist/app/get-status.js
CHANGED
|
@@ -48,7 +48,7 @@ const runtime_state_repo_1 = require("../storage/runtime-state-repo");
|
|
|
48
48
|
/**
|
|
49
49
|
* Reports the current on-disk runtime state and how it maps back to managed providers.
|
|
50
50
|
*/
|
|
51
|
-
async function getStatus(codexDir, configPath, providersPath, authPath) {
|
|
51
|
+
async function getStatus(codexDir, configPath, providersPath, authPath, options) {
|
|
52
52
|
const configExists = fs.existsSync(configPath);
|
|
53
53
|
const providersExists = fs.existsSync(providersPath);
|
|
54
54
|
let currentProfile = null;
|
|
@@ -56,7 +56,7 @@ async function getStatus(codexDir, configPath, providersPath, authPath) {
|
|
|
56
56
|
const providers = providersExists ? (0, providers_repo_1.readProvidersFile)(providersPath) : null;
|
|
57
57
|
let configViews = [];
|
|
58
58
|
let consistencyIssues = [];
|
|
59
|
-
const authState = (0, auth_repo_1.
|
|
59
|
+
const authState = (0, auth_repo_1.readAuthFileState)(authPath);
|
|
60
60
|
if (configExists) {
|
|
61
61
|
const document = (0, config_repo_1.readStructuredConfig)(configPath);
|
|
62
62
|
currentProfile = document.activeProfile;
|
|
@@ -69,23 +69,35 @@ async function getStatus(codexDir, configPath, providersPath, authPath) {
|
|
|
69
69
|
const liveState = (0, runtime_state_1.inspectLiveStateDrift)(currentProfile, providers);
|
|
70
70
|
const activeProviderCandidates = currentProfile && providers ? (0, providers_1.findProvidersByProfile)(providers, currentProfile) : [];
|
|
71
71
|
const activeProvider = activeProviderCandidates.length === 1 && providers ? providers.providers[activeProviderCandidates[0]] : null;
|
|
72
|
-
const copilotInstall = (0, copilot_installer_1.probeCopilotSdkInstall)();
|
|
73
|
-
const
|
|
72
|
+
const copilotInstall = (0, copilot_installer_1.probeCopilotSdkInstall)(options?.runtimesDir);
|
|
73
|
+
const runtimeStateInspection = (0, runtime_state_repo_1.inspectCopilotBridgeState)(options?.runtimeDir);
|
|
74
|
+
const runtimeState = runtimeStateInspection.state;
|
|
74
75
|
const runtimeStateProvider = runtimeState && providers ? providers.providers[runtimeState.provider] ?? null : null;
|
|
75
76
|
const bridgeProbeTarget = activeProvider && (0, providers_1.isCopilotBridgeProvider)(activeProvider)
|
|
76
77
|
? activeProvider
|
|
77
78
|
: runtimeStateProvider && (0, providers_1.isCopilotBridgeProvider)(runtimeStateProvider)
|
|
78
79
|
? runtimeStateProvider
|
|
79
80
|
: null;
|
|
80
|
-
const copilotBridge =
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
81
|
+
const copilotBridge = !runtimeStateInspection.valid && runtimeStateInspection.exists
|
|
82
|
+
? {
|
|
83
|
+
ok: false,
|
|
84
|
+
runtime: "copilot-bridge",
|
|
85
|
+
reason: "failed",
|
|
86
|
+
cause: runtimeStateInspection.parseError ?? "Failed to parse Copilot bridge runtime state.",
|
|
87
|
+
}
|
|
88
|
+
: bridgeProbeTarget
|
|
89
|
+
? await (0, copilot_bridge_1.probeCopilotBridgeRuntime)(bridgeProbeTarget, runtimeState, options?.runtimeDir)
|
|
90
|
+
: runtimeState
|
|
91
|
+
? {
|
|
92
|
+
ok: false,
|
|
93
|
+
runtime: "copilot-bridge",
|
|
94
|
+
reason: "failed",
|
|
95
|
+
cause: "Copilot bridge runtime state exists but no matching managed Copilot provider is active.",
|
|
96
|
+
details: runtimeState,
|
|
97
|
+
}
|
|
98
|
+
: null;
|
|
87
99
|
const copilotAuth = activeProvider && (0, providers_1.isCopilotBridgeProvider)(activeProvider)
|
|
88
|
-
? await (0, copilot_adapter_1.readCopilotAuthState)().catch((error) => ({
|
|
100
|
+
? await (0, copilot_adapter_1.readCopilotAuthState)(options?.runtimesDir).catch((error) => ({
|
|
89
101
|
ready: false,
|
|
90
102
|
source: "official-sdk",
|
|
91
103
|
mode: "session",
|
|
@@ -96,11 +108,21 @@ async function getStatus(codexDir, configPath, providersPath, authPath) {
|
|
|
96
108
|
// Surface unmanaged live state without mutating anything during a read-only status call.
|
|
97
109
|
warnings.push("Current config profile is not mapped in providers.json. Backfill would be required before treating live state as managed.");
|
|
98
110
|
}
|
|
111
|
+
if (runtimeStateInspection.exists && !runtimeStateInspection.valid) {
|
|
112
|
+
warnings.push(`Copilot bridge runtime state is unreadable: ${runtimeStateInspection.parseError ?? "unknown parse failure"}`);
|
|
113
|
+
}
|
|
99
114
|
return {
|
|
100
115
|
warnings,
|
|
101
116
|
data: {
|
|
102
117
|
codexDir,
|
|
103
|
-
storage: (0, runtime_state_1.getStorageRoles)(
|
|
118
|
+
storage: (0, runtime_state_1.getStorageRoles)({
|
|
119
|
+
codexDir,
|
|
120
|
+
providersPath,
|
|
121
|
+
configPath,
|
|
122
|
+
authPath,
|
|
123
|
+
runtimeDir: options?.runtimeDir,
|
|
124
|
+
runtimesDir: options?.runtimesDir,
|
|
125
|
+
}),
|
|
104
126
|
configExists,
|
|
105
127
|
providersExists,
|
|
106
128
|
currentProfile,
|
|
@@ -62,7 +62,7 @@ function importProviders(args) {
|
|
|
62
62
|
(0, fs_utils_1.ensureDir)(args.codexDir);
|
|
63
63
|
const document = (0, config_repo_1.readStructuredConfig)(args.configPath);
|
|
64
64
|
return (0, run_mutation_1.runMutation)({
|
|
65
|
-
|
|
65
|
+
lockPath: args.lockPath,
|
|
66
66
|
backupsDir: args.backupsDir,
|
|
67
67
|
latestBackupPath: args.latestBackupPath,
|
|
68
68
|
operation: "import",
|
package/dist/app/init-codex.js
CHANGED
|
@@ -35,34 +35,33 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.initCodex = initCodex;
|
|
37
37
|
const fs = __importStar(require("node:fs"));
|
|
38
|
-
const errors_1 = require("../domain/errors");
|
|
39
38
|
const fs_utils_1 = require("../storage/fs-utils");
|
|
40
39
|
const providers_repo_1 = require("../storage/providers-repo");
|
|
40
|
+
const tool_config_repo_1 = require("../storage/tool-config-repo");
|
|
41
41
|
/**
|
|
42
|
-
* Initializes
|
|
42
|
+
* Initializes the codex-switch tool home without requiring target Codex runtime files.
|
|
43
43
|
*/
|
|
44
44
|
function initCodex(args) {
|
|
45
|
-
const
|
|
46
|
-
if (!
|
|
47
|
-
|
|
48
|
-
codexDir: args.codexDir,
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
if (!codexDirExists) {
|
|
52
|
-
(0, fs_utils_1.ensureDir)(args.codexDir);
|
|
45
|
+
const toolHomeExists = fs.existsSync(args.toolHomeDir);
|
|
46
|
+
if (!toolHomeExists) {
|
|
47
|
+
(0, fs_utils_1.ensureDir)(args.toolHomeDir);
|
|
53
48
|
}
|
|
49
|
+
const toolConfigExists = fs.existsSync(args.toolConfigPath);
|
|
50
|
+
const ensuredConfig = (0, tool_config_repo_1.ensureToolConfig)(args.toolConfigPath, args.version, args.defaultCodexDir ?? "");
|
|
54
51
|
const providersExists = fs.existsSync(args.providersPath);
|
|
55
52
|
if (!providersExists) {
|
|
56
53
|
(0, providers_repo_1.writeProvidersFile)(args.providersPath, { providers: {} });
|
|
57
54
|
}
|
|
58
55
|
return {
|
|
59
56
|
data: {
|
|
60
|
-
|
|
61
|
-
|
|
57
|
+
toolHomeDir: args.toolHomeDir,
|
|
58
|
+
toolConfigPath: args.toolConfigPath,
|
|
59
|
+
providersPath: args.providersPath,
|
|
60
|
+
createdToolHomeDir: !toolHomeExists,
|
|
61
|
+
createdToolConfigFile: ensuredConfig.created && !toolConfigExists,
|
|
62
62
|
createdProvidersFile: !providersExists,
|
|
63
|
+
toolConfigAlreadyExisted: toolConfigExists,
|
|
63
64
|
providersAlreadyExisted: providersExists,
|
|
64
|
-
configExists: fs.existsSync(args.configPath),
|
|
65
|
-
authExists: fs.existsSync(args.authPath),
|
|
66
65
|
},
|
|
67
66
|
};
|
|
68
67
|
}
|
|
@@ -11,7 +11,6 @@ function listProviders(providersPath) {
|
|
|
11
11
|
const items = names.map((name) => ({
|
|
12
12
|
name,
|
|
13
13
|
profile: providers.providers[name].profile,
|
|
14
|
-
envKey: providers.providers[name].envKey,
|
|
15
14
|
note: providers.providers[name].note ?? null,
|
|
16
15
|
tags: providers.providers[name].tags ?? [],
|
|
17
16
|
}));
|
|
@@ -34,7 +34,7 @@ function removeProvider(args) {
|
|
|
34
34
|
switchToProfile: args.switchToProfile ?? null,
|
|
35
35
|
});
|
|
36
36
|
return (0, run_mutation_1.runMutation)({
|
|
37
|
-
|
|
37
|
+
lockPath: args.lockPath,
|
|
38
38
|
backupsDir: args.backupsDir,
|
|
39
39
|
latestBackupPath: args.latestBackupPath,
|
|
40
40
|
operation: "remove",
|
package/dist/app/run-doctor.js
CHANGED
|
@@ -102,8 +102,9 @@ async function runDoctor(args) {
|
|
|
102
102
|
});
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
|
-
const authState = (0, auth_repo_1.
|
|
106
|
-
const
|
|
105
|
+
const authState = (0, auth_repo_1.readAuthFileState)(args.authPath);
|
|
106
|
+
const runtimeStateInspection = (0, runtime_state_repo_1.inspectCopilotBridgeState)(args.runtimeDir);
|
|
107
|
+
const runtimeState = runtimeStateInspection.state;
|
|
107
108
|
if (authState.exists && !authState.valid) {
|
|
108
109
|
issues.push({
|
|
109
110
|
code: "AUTH_JSON_INVALID",
|
|
@@ -111,36 +112,18 @@ async function runDoctor(args) {
|
|
|
111
112
|
file: args.authPath,
|
|
112
113
|
});
|
|
113
114
|
}
|
|
115
|
+
if (runtimeStateInspection.exists && !runtimeStateInspection.valid) {
|
|
116
|
+
issues.push({
|
|
117
|
+
code: "BRIDGE_STATE_STALE",
|
|
118
|
+
message: `Copilot bridge runtime state is unreadable: ${runtimeStateInspection.parseError ?? "unknown parse failure"}`,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
114
121
|
if (document?.activeProfile && providers) {
|
|
115
122
|
const matches = (0, providers_1.findProvidersByProfile)(providers, document.activeProfile);
|
|
116
123
|
if (matches.length === 1) {
|
|
117
124
|
const activeProvider = providers.providers[matches[0]];
|
|
118
|
-
const payload = authState.payload ?? {};
|
|
119
|
-
const actualKeys = authState.managedSecretKeys;
|
|
120
|
-
if (authState.authMode !== null && authState.authMode !== "apikey") {
|
|
121
|
-
issues.push({
|
|
122
|
-
code: "AUTH_JSON_INVALID",
|
|
123
|
-
message: `auth.json auth_mode must be "apikey", found "${authState.authMode}".`,
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
if (!actualKeys.includes(activeProvider.envKey) || actualKeys.length !== 1) {
|
|
127
|
-
issues.push({
|
|
128
|
-
code: "AUTH_JSON_ENV_KEY_MISMATCH",
|
|
129
|
-
message: `auth.json managed env key does not match active provider "${matches[0]}".`,
|
|
130
|
-
provider: matches[0],
|
|
131
|
-
expectedEnvKey: activeProvider.envKey,
|
|
132
|
-
actualEnvKeys: actualKeys,
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
if (payload[activeProvider.envKey] !== activeProvider.apiKey) {
|
|
136
|
-
issues.push({
|
|
137
|
-
code: "AUTH_JSON_APIKEY_MISMATCH",
|
|
138
|
-
message: `auth.json secret value does not match active provider "${matches[0]}".`,
|
|
139
|
-
provider: matches[0],
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
125
|
if ((0, providers_1.isCopilotBridgeProvider)(activeProvider)) {
|
|
143
|
-
const installStatus = (0, copilot_installer_1.probeCopilotSdkInstall)();
|
|
126
|
+
const installStatus = (0, copilot_installer_1.probeCopilotSdkInstall)(args.runtimesDir);
|
|
144
127
|
if (!installStatus.installed) {
|
|
145
128
|
issues.push({
|
|
146
129
|
code: "COPILOT_SDK_MISSING",
|
|
@@ -150,7 +133,7 @@ async function runDoctor(args) {
|
|
|
150
133
|
});
|
|
151
134
|
}
|
|
152
135
|
try {
|
|
153
|
-
await (0, copilot_adapter_1.readCopilotAuthState)();
|
|
136
|
+
await (0, copilot_adapter_1.readCopilotAuthState)(args.runtimesDir);
|
|
154
137
|
}
|
|
155
138
|
catch (error) {
|
|
156
139
|
const normalized = (0, errors_1.normalizeError)(error);
|
|
@@ -160,7 +143,7 @@ async function runDoctor(args) {
|
|
|
160
143
|
...(normalized.details ?? {}),
|
|
161
144
|
});
|
|
162
145
|
}
|
|
163
|
-
const bridge = await (0, copilot_bridge_1.probeCopilotBridgeRuntime)(activeProvider);
|
|
146
|
+
const bridge = await (0, copilot_bridge_1.probeCopilotBridgeRuntime)(activeProvider, runtimeState, args.runtimeDir);
|
|
164
147
|
if (!bridge.ok) {
|
|
165
148
|
issues.push({
|
|
166
149
|
code: mapBridgeDiagnosticCode(bridge.cause),
|
|
@@ -215,7 +198,14 @@ async function runDoctor(args) {
|
|
|
215
198
|
healthy: issues.length === 0,
|
|
216
199
|
issues,
|
|
217
200
|
codexDir: args.codexDir,
|
|
218
|
-
storage: (0, runtime_state_1.getStorageRoles)(
|
|
201
|
+
storage: (0, runtime_state_1.getStorageRoles)({
|
|
202
|
+
codexDir: args.codexDir,
|
|
203
|
+
providersPath: args.providersPath,
|
|
204
|
+
configPath: args.configPath,
|
|
205
|
+
authPath: args.authPath,
|
|
206
|
+
runtimeDir: args.runtimeDir,
|
|
207
|
+
runtimesDir: args.runtimesDir,
|
|
208
|
+
}),
|
|
219
209
|
liveState: drift,
|
|
220
210
|
auth: authState,
|
|
221
211
|
},
|
|
@@ -255,18 +245,10 @@ function renderConfigIssueMessage(issue) {
|
|
|
255
245
|
return `Model provider section "${issue.modelProvider}" for profile "${issue.profile}" is missing from config.toml.`;
|
|
256
246
|
case "MODEL_PROVIDER_BASE_URL_MISSING":
|
|
257
247
|
return `Model provider section "${issue.modelProvider}" for profile "${issue.profile}" is missing base_url.`;
|
|
258
|
-
case "MODEL_PROVIDER_ENV_KEY_MISSING":
|
|
259
|
-
return `Model provider section "${issue.modelProvider}" for profile "${issue.profile}" is missing env_key.`;
|
|
260
|
-
case "PROVIDER_ENV_KEY_MISMATCH":
|
|
261
|
-
return `Provider "${issue.provider}" envKey does not match runtime env_key for profile "${issue.profile}".`;
|
|
262
248
|
case "ACTIVE_PROVIDER_UNRESOLVED":
|
|
263
|
-
return `Active profile "${issue.profile}" maps to multiple providers
|
|
249
|
+
return `Active profile "${issue.profile}" maps to multiple providers, so the active managed provider cannot be resolved uniquely.`;
|
|
264
250
|
case "AUTH_JSON_INVALID":
|
|
265
251
|
return String(issue.message ?? issue.reason ?? "auth.json is invalid.");
|
|
266
|
-
case "AUTH_JSON_ENV_KEY_MISMATCH":
|
|
267
|
-
return `auth.json managed env key does not match provider "${String(issue.provider ?? "")}".`;
|
|
268
|
-
case "AUTH_JSON_APIKEY_MISMATCH":
|
|
269
|
-
return `auth.json secret does not match provider "${String(issue.provider ?? "")}".`;
|
|
270
252
|
case "DESTRUCTIVE_REMOVE_BLOCKED":
|
|
271
253
|
return `Provider "${issue.provider}" cannot be removed while "${issue.activeProfile}" remains active.`;
|
|
272
254
|
default:
|
package/dist/app/run-mutation.js
CHANGED
|
@@ -8,8 +8,9 @@ const lock_repo_1 = require("../storage/lock-repo");
|
|
|
8
8
|
* Runs a write operation under a lock with automatic backup and rollback handling.
|
|
9
9
|
*/
|
|
10
10
|
function runMutation(args) {
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
const lockPath = args.lockPath ?? require("node:path").join(args.codexDir ?? process.cwd(), ".codex-switch.lock");
|
|
12
|
+
return (0, lock_repo_1.withCodexLock)(lockPath, args.operation, () => {
|
|
13
|
+
const backup = (0, backup_repo_1.createBackup)(args.backupsDir, args.operation, args.files);
|
|
13
14
|
try {
|
|
14
15
|
const data = args.mutate({ backup });
|
|
15
16
|
// Record the successful backup only after the mutation completes.
|