@minniexcode/codex-switch 0.0.9 → 0.0.10
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 +5 -3
- package/README.CN.md +25 -3
- package/README.md +3 -2
- package/dist/app/add-provider.js +0 -11
- package/dist/app/bridge.js +0 -1
- package/dist/app/edit-provider.js +1 -17
- package/dist/app/get-status.js +24 -9
- package/dist/app/list-providers.js +0 -1
- package/dist/app/run-doctor.js +11 -36
- package/dist/app/setup-codex.js +27 -17
- package/dist/app/show-config.js +1 -5
- package/dist/app/switch-provider.js +5 -20
- package/dist/cli/output.js +4 -6
- package/dist/cli.js +1 -1
- package/dist/commands/handlers.js +192 -39
- package/dist/commands/registry.js +7 -5
- package/dist/domain/config.js +4 -68
- package/dist/domain/providers.js +0 -5
- package/dist/domain/runtime-state.js +2 -1
- 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 +44 -1
- package/dist/runtime/copilot-bridge.js +2 -2
- package/dist/runtime/copilot-cli.js +70 -0
- package/dist/runtime/copilot-installer.js +49 -2
- package/dist/storage/auth-repo.js +28 -77
- package/dist/storage/config-repo.js +1 -36
- package/dist/storage/runtime-state-repo.js +32 -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/PRD/codex-switch-prd-v0.0.10.md +406 -0
- package/docs/cli-usage.md +38 -14
- package/docs/codex-switch-product-overview.md +2 -2
- package/docs/codex-switch-technical-architecture.md +6 -5
- package/package.json +1 -1
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.COMMON_TAG_CHOICES = void 0;
|
|
4
4
|
exports.collectAddInput = collectAddInput;
|
|
5
|
+
exports.collectCopilotAddInput = collectCopilotAddInput;
|
|
5
6
|
exports.createNonInteractiveAddError = createNonInteractiveAddError;
|
|
6
7
|
exports.promptTags = promptTags;
|
|
7
8
|
const errors_1 = require("../domain/errors");
|
|
@@ -37,10 +38,51 @@ async function collectAddInput(runtime, defaults, providerExists, profileExists)
|
|
|
37
38
|
tags,
|
|
38
39
|
};
|
|
39
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Collects Copilot add command inputs interactively when required values are missing.
|
|
43
|
+
*/
|
|
44
|
+
async function collectCopilotAddInput(runtime, defaults, providerExists, profileExists, options) {
|
|
45
|
+
runtime.writeLine("Interactive add mode");
|
|
46
|
+
runtime.writeLine("Provide the missing Copilot provider fields. Press Enter to keep optional values empty.");
|
|
47
|
+
const providerName = defaults.providerName
|
|
48
|
+
? normalizeRequiredValue(defaults.providerName)
|
|
49
|
+
: await promptProviderName(runtime, providerExists);
|
|
50
|
+
const profile = defaults.profile ? normalizeRequiredValue(defaults.profile) : await promptRequiredValue(runtime, "Profile");
|
|
51
|
+
const createProfile = !profileExists(profile);
|
|
52
|
+
const model = createProfile
|
|
53
|
+
? defaults.model
|
|
54
|
+
? normalizeRequiredValue(defaults.model)
|
|
55
|
+
: await promptRequiredValue(runtime, `Model for new profile "${profile}"`)
|
|
56
|
+
: defaults.model ?? null;
|
|
57
|
+
const note = defaults.note ?? normalizeOptionalValue(await runtime.inputText("Note (optional)"));
|
|
58
|
+
const tags = defaults.tags.length > 0 ? defaults.tags : await promptTags(runtime);
|
|
59
|
+
const bridgeHost = options?.bridgeHost ?? normalizeOptionalValue(await runtime.inputText("Bridge host (optional)", { defaultValue: "127.0.0.1" }));
|
|
60
|
+
const bridgePortText = options?.bridgePort !== undefined && options.bridgePort !== null
|
|
61
|
+
? String(options.bridgePort)
|
|
62
|
+
: await runtime.inputText("Bridge port (optional)", { defaultValue: "41415" });
|
|
63
|
+
const bridgePort = normalizeBridgePort(runtime, bridgePortText);
|
|
64
|
+
const bridgeApiKey = options?.bridgeApiKey ?? normalizeOptionalValue(await runtime.inputSecret("Bridge API key (optional)"));
|
|
65
|
+
return {
|
|
66
|
+
providerName,
|
|
67
|
+
profile,
|
|
68
|
+
createProfile,
|
|
69
|
+
model,
|
|
70
|
+
note,
|
|
71
|
+
tags,
|
|
72
|
+
bridgeApiKey,
|
|
73
|
+
bridgeHost,
|
|
74
|
+
bridgePort,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
40
77
|
/**
|
|
41
78
|
* Throws a consistent error when interactive add is unavailable.
|
|
42
79
|
*/
|
|
43
|
-
function createNonInteractiveAddError() {
|
|
80
|
+
function createNonInteractiveAddError(options) {
|
|
81
|
+
if (options?.copilot) {
|
|
82
|
+
return (0, errors_1.cliError)("INVALID_ARGUMENT", "add --copilot requires <provider> and --profile when running without an interactive TTY.", {
|
|
83
|
+
suggestion: "Run in a terminal TTY or pass <provider>, --profile, and any optional Copilot bridge flags explicitly.",
|
|
84
|
+
});
|
|
85
|
+
}
|
|
44
86
|
return (0, errors_1.cliError)("INVALID_ARGUMENT", "add requires <provider>, --profile, and --api-key when running without an interactive TTY.", {
|
|
45
87
|
suggestion: "Run in a terminal TTY or pass all required values explicitly.",
|
|
46
88
|
});
|
|
@@ -90,6 +132,18 @@ function normalizeOptionalValue(value) {
|
|
|
90
132
|
const normalized = value.trim();
|
|
91
133
|
return normalized === "" ? null : normalized;
|
|
92
134
|
}
|
|
135
|
+
function normalizeBridgePort(runtime, value) {
|
|
136
|
+
const normalized = value.trim();
|
|
137
|
+
if (normalized === "") {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
const parsed = Number(normalized);
|
|
141
|
+
if (Number.isInteger(parsed) && parsed > 0) {
|
|
142
|
+
return parsed;
|
|
143
|
+
}
|
|
144
|
+
runtime.writeLine("Bridge port must be a positive integer. Falling back to the default port.");
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
93
147
|
async function promptTags(runtime, defaults = []) {
|
|
94
148
|
const defaultPresetTags = defaults.filter(isCommonTag);
|
|
95
149
|
return runtime.selectMany("Select tags (optional)", exports.COMMON_TAG_CHOICES.map((tag) => ({ value: tag, label: tag })), { defaultValues: defaultPresetTags });
|
|
@@ -222,7 +222,7 @@ async function chooseSetupProfiles(runtime, profiles) {
|
|
|
222
222
|
return runtime.selectMany("Choose unmanaged config profiles to adopt into providers.json.", profiles.map((profile) => ({
|
|
223
223
|
value: profile.name,
|
|
224
224
|
label: profile.name,
|
|
225
|
-
hint: `${profile.model} | ${profile.baseUrl}
|
|
225
|
+
hint: `${profile.model} | ${profile.baseUrl}`,
|
|
226
226
|
})));
|
|
227
227
|
}
|
|
228
228
|
/**
|
|
@@ -235,9 +235,6 @@ async function collectSetupProviderDetails(runtime, profiles, defaultsByProfile
|
|
|
235
235
|
const providerName = (await runtime.inputText(`Provider name for profile "${profile}"`, {
|
|
236
236
|
defaultValue: defaults.providerName ?? profile,
|
|
237
237
|
})).trim();
|
|
238
|
-
if (defaults.envKey) {
|
|
239
|
-
runtime.writeLine(`Runtime env key for "${profile}": ${defaults.envKey}`);
|
|
240
|
-
}
|
|
241
238
|
const apiKey = await promptRequiredSecret(runtime, `API key for profile "${profile}"`, defaults.apiKey?.trim() || undefined);
|
|
242
239
|
const baseUrl = (await runtime.inputText(`Base URL note for profile "${profile}" (optional)`, {
|
|
243
240
|
defaultValue: defaults.baseUrl ?? "",
|
|
@@ -249,7 +246,6 @@ async function collectSetupProviderDetails(runtime, profiles, defaultsByProfile
|
|
|
249
246
|
result[profile] = {
|
|
250
247
|
providerName: providerName || defaults.providerName || profile,
|
|
251
248
|
apiKey,
|
|
252
|
-
envKey: defaults.envKey,
|
|
253
249
|
baseUrl: baseUrl || defaults.baseUrl || undefined,
|
|
254
250
|
note: note || defaults.note || undefined,
|
|
255
251
|
// Empty selections are omitted so downstream setup validation can distinguish unset from explicit data.
|
|
@@ -114,7 +114,7 @@ async function createCopilotSession() {
|
|
|
114
114
|
throw (0, errors_1.cliError)("COPILOT_SDK_UNSUPPORTED", "The installed Copilot SDK does not expose a supported createSession API.", {});
|
|
115
115
|
}
|
|
116
116
|
try {
|
|
117
|
-
const session = (await Promise.resolve(createSession(
|
|
117
|
+
const session = (await Promise.resolve(createSession(createSessionOptions(sdk))));
|
|
118
118
|
return {
|
|
119
119
|
sdk,
|
|
120
120
|
client,
|
|
@@ -122,11 +122,30 @@ async function createCopilotSession() {
|
|
|
122
122
|
};
|
|
123
123
|
}
|
|
124
124
|
catch (error) {
|
|
125
|
+
if (classifyCopilotSessionError(error) === "unsupported") {
|
|
126
|
+
throw (0, errors_1.cliError)("COPILOT_SDK_UNSUPPORTED", "The installed Copilot SDK does not expose a compatible permission-handling session API.", {
|
|
127
|
+
cause: error instanceof Error ? error.message : String(error),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
125
130
|
throw (0, errors_1.cliError)("COPILOT_AUTH_REQUIRED", "Copilot authentication is required before the local bridge can be used.", {
|
|
126
131
|
cause: error instanceof Error ? error.message : String(error),
|
|
127
132
|
});
|
|
128
133
|
}
|
|
129
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* Builds the session options used consistently across auth probes and request execution.
|
|
137
|
+
*/
|
|
138
|
+
function createSessionOptions(sdk) {
|
|
139
|
+
const approveAll = resolveApproveAll(sdk);
|
|
140
|
+
if (approveAll) {
|
|
141
|
+
return {
|
|
142
|
+
onPermissionRequest: (request) => approveAll(request),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
onPermissionRequest: () => true,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
130
149
|
function createCopilotClient(sdk) {
|
|
131
150
|
const ClientCtor = resolveConstructor(sdk, "CopilotClient");
|
|
132
151
|
if (!ClientCtor) {
|
|
@@ -146,6 +165,16 @@ async function stopCopilotClient(client) {
|
|
|
146
165
|
await Promise.resolve(client.stop());
|
|
147
166
|
}
|
|
148
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Distinguishes true auth failures from SDK API-shape mismatches.
|
|
170
|
+
*/
|
|
171
|
+
function classifyCopilotSessionError(error) {
|
|
172
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
173
|
+
if (/onPermissionRequest/i.test(message) || /permission/i.test(message)) {
|
|
174
|
+
return "unsupported";
|
|
175
|
+
}
|
|
176
|
+
return "auth";
|
|
177
|
+
}
|
|
149
178
|
function resolveCallable(target, name) {
|
|
150
179
|
if (!target) {
|
|
151
180
|
return null;
|
|
@@ -171,3 +200,17 @@ function resolveConstructor(target, name) {
|
|
|
171
200
|
}
|
|
172
201
|
return null;
|
|
173
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* Resolves the SDK-provided permission helper when available.
|
|
205
|
+
*/
|
|
206
|
+
function resolveApproveAll(target) {
|
|
207
|
+
const direct = target.approveAll;
|
|
208
|
+
if (typeof direct === "function") {
|
|
209
|
+
return direct;
|
|
210
|
+
}
|
|
211
|
+
const nestedDefault = target.default;
|
|
212
|
+
if (nestedDefault && typeof nestedDefault.approveAll === "function") {
|
|
213
|
+
return nestedDefault.approveAll;
|
|
214
|
+
}
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
@@ -65,8 +65,8 @@ function resetCopilotBridgeSpawnImplementation() {
|
|
|
65
65
|
/**
|
|
66
66
|
* Returns the last known Copilot bridge runtime status.
|
|
67
67
|
*/
|
|
68
|
-
async function probeCopilotBridgeRuntime(provider) {
|
|
69
|
-
const state = (0, runtime_state_repo_1.readCopilotBridgeState)();
|
|
68
|
+
async function probeCopilotBridgeRuntime(provider, persistedState) {
|
|
69
|
+
const state = persistedState === undefined ? (0, runtime_state_repo_1.readCopilotBridgeState)() : persistedState;
|
|
70
70
|
if (state && (!provider || !(0, providers_1.isCopilotBridgeProvider)(provider))) {
|
|
71
71
|
return {
|
|
72
72
|
ok: false,
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setCopilotCliSpawnImplementation = setCopilotCliSpawnImplementation;
|
|
4
|
+
exports.resetCopilotCliSpawnImplementation = resetCopilotCliSpawnImplementation;
|
|
5
|
+
exports.checkCopilotCliAvailable = checkCopilotCliAvailable;
|
|
6
|
+
exports.runCopilotLogin = runCopilotLogin;
|
|
7
|
+
const node_child_process_1 = require("node:child_process");
|
|
8
|
+
let spawnImplementation = node_child_process_1.spawnSync;
|
|
9
|
+
/**
|
|
10
|
+
* Overrides the spawn implementation for Copilot CLI tests.
|
|
11
|
+
*/
|
|
12
|
+
function setCopilotCliSpawnImplementation(spawnLike) {
|
|
13
|
+
spawnImplementation = spawnLike;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Restores the default spawn implementation after tests.
|
|
17
|
+
*/
|
|
18
|
+
function resetCopilotCliSpawnImplementation() {
|
|
19
|
+
spawnImplementation = node_child_process_1.spawnSync;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Checks whether the GitHub Copilot CLI is available on PATH.
|
|
23
|
+
*/
|
|
24
|
+
function checkCopilotCliAvailable() {
|
|
25
|
+
const invocation = getCopilotInvocation(["--help"]);
|
|
26
|
+
const result = spawnImplementation(invocation.command, invocation.args, {
|
|
27
|
+
stdio: "pipe",
|
|
28
|
+
encoding: "utf8",
|
|
29
|
+
shell: false,
|
|
30
|
+
});
|
|
31
|
+
if (result.error || result.status !== 0) {
|
|
32
|
+
return {
|
|
33
|
+
ok: false,
|
|
34
|
+
cause: result.error?.message ?? (result.stderr.trim() || "Unknown failure"),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return { ok: true };
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Launches the official `copilot login` flow in the current terminal.
|
|
41
|
+
*/
|
|
42
|
+
function runCopilotLogin(options) {
|
|
43
|
+
const args = ["login"];
|
|
44
|
+
if (options?.host) {
|
|
45
|
+
args.push("--hostname", options.host);
|
|
46
|
+
}
|
|
47
|
+
const invocation = getCopilotInvocation(args);
|
|
48
|
+
const result = spawnImplementation(invocation.command, invocation.args, {
|
|
49
|
+
stdio: "inherit",
|
|
50
|
+
shell: false,
|
|
51
|
+
});
|
|
52
|
+
if (result.error || result.status !== 0) {
|
|
53
|
+
throw new Error(result.error?.message ?? `copilot login exited with status ${String(result.status)}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Resolves a cross-platform invocation for the Copilot CLI.
|
|
58
|
+
*/
|
|
59
|
+
function getCopilotInvocation(args) {
|
|
60
|
+
if (process.platform === "win32") {
|
|
61
|
+
return {
|
|
62
|
+
command: process.env.ComSpec || "cmd.exe",
|
|
63
|
+
args: ["/d", "/s", "/c", ["copilot", ...args].join(" ")],
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
command: "copilot",
|
|
68
|
+
args,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -107,19 +107,66 @@ function installCopilotSdk() {
|
|
|
107
107
|
if (!fs.existsSync(packageJsonPath)) {
|
|
108
108
|
fs.writeFileSync(packageJsonPath, `${JSON.stringify({ name: "codex-switch-copilot-runtime", private: true, version: "0.0.0" }, null, 2)}\n`, "utf8");
|
|
109
109
|
}
|
|
110
|
-
const
|
|
111
|
-
const result = spawnImplementation(command,
|
|
110
|
+
const installCommand = resolveNpmInstallCommand();
|
|
111
|
+
const result = spawnImplementation(installCommand.command, installCommand.args, {
|
|
112
112
|
cwd: installDir,
|
|
113
113
|
stdio: "pipe",
|
|
114
114
|
encoding: "utf8",
|
|
115
115
|
shell: false,
|
|
116
116
|
});
|
|
117
|
+
if (result.error) {
|
|
118
|
+
throw (0, errors_1.cliError)("COPILOT_SDK_INSTALL_FAILED", "Failed to install the optional Copilot SDK runtime.", {
|
|
119
|
+
installDir,
|
|
120
|
+
packageName: COPILOT_SDK_PACKAGE,
|
|
121
|
+
cause: result.error.message,
|
|
122
|
+
errorCode: result.error.code ?? null,
|
|
123
|
+
command: installCommand.command,
|
|
124
|
+
args: installCommand.args,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
117
127
|
if (result.status !== 0) {
|
|
118
128
|
throw (0, errors_1.cliError)("COPILOT_SDK_INSTALL_FAILED", "Failed to install the optional Copilot SDK runtime.", {
|
|
119
129
|
installDir,
|
|
120
130
|
packageName: COPILOT_SDK_PACKAGE,
|
|
121
131
|
cause: result.stderr || result.stdout || `npm exited with status ${String(result.status)}`,
|
|
132
|
+
command: installCommand.command,
|
|
133
|
+
args: installCommand.args,
|
|
122
134
|
});
|
|
123
135
|
}
|
|
124
136
|
return probeCopilotSdkInstall();
|
|
125
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* Resolves a stable npm install invocation for the optional Copilot SDK runtime.
|
|
140
|
+
*/
|
|
141
|
+
function resolveNpmInstallCommand() {
|
|
142
|
+
const installArgs = ["install", "--no-save", `${COPILOT_SDK_PACKAGE}@${COPILOT_SDK_VERSION}`];
|
|
143
|
+
const npmCliPath = resolveNpmCliPath();
|
|
144
|
+
if (npmCliPath) {
|
|
145
|
+
return {
|
|
146
|
+
command: process.execPath,
|
|
147
|
+
args: [npmCliPath, ...installArgs],
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
command: process.platform === "win32" ? "npm.cmd" : "npm",
|
|
152
|
+
args: installArgs,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Finds a locally available npm CLI script near the active Node runtime.
|
|
157
|
+
*/
|
|
158
|
+
function resolveNpmCliPath() {
|
|
159
|
+
const execDir = path.dirname(process.execPath);
|
|
160
|
+
const candidates = [
|
|
161
|
+
process.env.npm_execpath,
|
|
162
|
+
path.join(execDir, "node_modules", "npm", "bin", "npm-cli.js"),
|
|
163
|
+
path.join(execDir, "..", "node_modules", "npm", "bin", "npm-cli.js"),
|
|
164
|
+
path.join(execDir, "..", "..", "node_modules", "npm", "bin", "npm-cli.js"),
|
|
165
|
+
];
|
|
166
|
+
for (const candidate of candidates) {
|
|
167
|
+
if (candidate && fs.existsSync(candidate)) {
|
|
168
|
+
return path.resolve(candidate);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
@@ -34,15 +34,10 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.readAuthFileIfExists = readAuthFileIfExists;
|
|
37
|
-
exports.
|
|
38
|
-
exports.
|
|
39
|
-
exports.writeAuthFile = writeAuthFile;
|
|
40
|
-
exports.extractManagedAuthFingerprint = extractManagedAuthFingerprint;
|
|
41
|
-
exports.readManagedAuthState = readManagedAuthState;
|
|
37
|
+
exports.readAuthFileState = readAuthFileState;
|
|
38
|
+
exports.writeOpenAiApiKeyAuth = writeOpenAiApiKeyAuth;
|
|
42
39
|
const fs = __importStar(require("node:fs"));
|
|
43
40
|
const errors_1 = require("../domain/errors");
|
|
44
|
-
const fs_utils_1 = require("./fs-utils");
|
|
45
|
-
const LEGACY_MANAGED_SECRET_KEYS = new Set(["api_key"]);
|
|
46
41
|
/**
|
|
47
42
|
* Reads auth.json when it exists and returns null otherwise.
|
|
48
43
|
*/
|
|
@@ -61,84 +56,27 @@ function readAuthFileIfExists(authPath) {
|
|
|
61
56
|
}
|
|
62
57
|
}
|
|
63
58
|
/**
|
|
64
|
-
*
|
|
59
|
+
* Reads auth.json into a neutral file-state summary for status and doctor.
|
|
65
60
|
*/
|
|
66
|
-
function
|
|
67
|
-
return {
|
|
68
|
-
auth_mode: "apikey",
|
|
69
|
-
[provider.envKey]: provider.apiKey,
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Builds the next auth.json object while preserving unmanaged metadata.
|
|
74
|
-
*/
|
|
75
|
-
function buildManagedAuthJson(provider, existingAuthJson) {
|
|
76
|
-
const nextManaged = buildManagedAuthPayload(provider);
|
|
77
|
-
const result = {};
|
|
78
|
-
if (existingAuthJson && typeof existingAuthJson === "object" && !Array.isArray(existingAuthJson)) {
|
|
79
|
-
for (const [key, value] of Object.entries(existingAuthJson)) {
|
|
80
|
-
if (key === "auth_mode" || LEGACY_MANAGED_SECRET_KEYS.has(key) || looksLikeManagedSecretKey(key)) {
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
result[key] = value;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
result.auth_mode = nextManaged.auth_mode;
|
|
87
|
-
result[provider.envKey] = provider.apiKey;
|
|
88
|
-
return result;
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* Writes auth.json atomically using the managed mirror strategy.
|
|
92
|
-
*/
|
|
93
|
-
function writeAuthFile(authPath, provider, existingAuthJson) {
|
|
94
|
-
(0, fs_utils_1.writeTextFileAtomic)(authPath, `${JSON.stringify(buildManagedAuthJson(provider, existingAuthJson), null, 2)}\n`);
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* Extracts a lightweight fingerprint used by doctor/status.
|
|
98
|
-
*/
|
|
99
|
-
function extractManagedAuthFingerprint(input) {
|
|
100
|
-
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
101
|
-
return {
|
|
102
|
-
authMode: null,
|
|
103
|
-
managedSecretKeys: [],
|
|
104
|
-
payload: null,
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
const payload = input;
|
|
108
|
-
const authMode = typeof payload.auth_mode === "string" ? payload.auth_mode : null;
|
|
109
|
-
const managedSecretKeys = Object.keys(payload)
|
|
110
|
-
.filter((key) => key !== "auth_mode" && looksLikeManagedSecretKey(key))
|
|
111
|
-
.sort();
|
|
112
|
-
return {
|
|
113
|
-
authMode,
|
|
114
|
-
managedSecretKeys,
|
|
115
|
-
payload,
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* Reads auth.json into a doctor-friendly managed state summary.
|
|
120
|
-
*/
|
|
121
|
-
function readManagedAuthState(authPath) {
|
|
61
|
+
function readAuthFileState(authPath) {
|
|
122
62
|
if (!fs.existsSync(authPath)) {
|
|
123
63
|
return {
|
|
124
64
|
exists: false,
|
|
125
65
|
valid: false,
|
|
126
66
|
parseError: null,
|
|
127
67
|
authMode: null,
|
|
128
|
-
managedSecretKeys: [],
|
|
129
|
-
payload: null,
|
|
130
68
|
};
|
|
131
69
|
}
|
|
132
70
|
try {
|
|
133
71
|
const payload = readAuthFileIfExists(authPath);
|
|
134
|
-
const
|
|
72
|
+
const authMode = payload && typeof payload === "object" && !Array.isArray(payload) && typeof payload.auth_mode === "string"
|
|
73
|
+
? String(payload.auth_mode)
|
|
74
|
+
: null;
|
|
135
75
|
return {
|
|
136
76
|
exists: true,
|
|
137
|
-
valid: Boolean(
|
|
77
|
+
valid: Boolean(payload && typeof payload === "object" && !Array.isArray(payload)),
|
|
138
78
|
parseError: null,
|
|
139
|
-
authMode
|
|
140
|
-
managedSecretKeys: fingerprint.managedSecretKeys,
|
|
141
|
-
payload: fingerprint.payload,
|
|
79
|
+
authMode,
|
|
142
80
|
};
|
|
143
81
|
}
|
|
144
82
|
catch (error) {
|
|
@@ -147,14 +85,27 @@ function readManagedAuthState(authPath) {
|
|
|
147
85
|
valid: false,
|
|
148
86
|
parseError: (0, errors_1.normalizeError)(error).message,
|
|
149
87
|
authMode: null,
|
|
150
|
-
managedSecretKeys: [],
|
|
151
|
-
payload: null,
|
|
152
88
|
};
|
|
153
89
|
}
|
|
154
90
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
91
|
+
/**
|
|
92
|
+
* Writes the active direct-provider auth projection expected by Codex.
|
|
93
|
+
* Invalid or missing existing auth.json content is replaced with a minimal valid object.
|
|
94
|
+
*/
|
|
95
|
+
function writeOpenAiApiKeyAuth(authPath, apiKey) {
|
|
96
|
+
let next = {};
|
|
97
|
+
if (fs.existsSync(authPath)) {
|
|
98
|
+
try {
|
|
99
|
+
const payload = JSON.parse(fs.readFileSync(authPath, "utf8"));
|
|
100
|
+
if (payload && typeof payload === "object" && !Array.isArray(payload)) {
|
|
101
|
+
next = { ...payload };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
next = {};
|
|
106
|
+
}
|
|
158
107
|
}
|
|
159
|
-
|
|
108
|
+
next.auth_mode = "apikey";
|
|
109
|
+
next.OPENAI_API_KEY = apiKey;
|
|
110
|
+
fs.writeFileSync(authPath, `${JSON.stringify(next, null, 2)}\n`, "utf8");
|
|
160
111
|
}
|
|
@@ -40,7 +40,6 @@ exports.listConfigProfiles = listConfigProfiles;
|
|
|
40
40
|
exports.ensureProfileExists = ensureProfileExists;
|
|
41
41
|
exports.requireManagedProfileRuntime = requireManagedProfileRuntime;
|
|
42
42
|
exports.requireModelProviderRuntimeSection = requireModelProviderRuntimeSection;
|
|
43
|
-
exports.requireRuntimeEnvKey = requireRuntimeEnvKey;
|
|
44
43
|
exports.resolveActiveProviderName = resolveActiveProviderName;
|
|
45
44
|
exports.updateTopLevelProfile = updateTopLevelProfile;
|
|
46
45
|
exports.createConfigMutationPlan = createConfigMutationPlan;
|
|
@@ -143,13 +142,6 @@ function requireManagedProfileRuntime(document, providers, profile) {
|
|
|
143
142
|
missingFields: ["base_url"],
|
|
144
143
|
});
|
|
145
144
|
}
|
|
146
|
-
if (!modelProviderSection.envKey) {
|
|
147
|
-
throw (0, errors_1.cliError)("MODEL_PROVIDER_ENV_KEY_MISSING", `Model provider "${view.modelProvider}" requires env_key.`, {
|
|
148
|
-
profile,
|
|
149
|
-
modelProvider: view.modelProvider,
|
|
150
|
-
missingFields: ["env_key"],
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
145
|
return view;
|
|
154
146
|
}
|
|
155
147
|
/**
|
|
@@ -170,33 +162,6 @@ function requireModelProviderRuntimeSection(document, profile) {
|
|
|
170
162
|
missingFields: ["base_url"],
|
|
171
163
|
});
|
|
172
164
|
}
|
|
173
|
-
if (!modelProviderSection.envKey) {
|
|
174
|
-
throw (0, errors_1.cliError)("MODEL_PROVIDER_ENV_KEY_MISSING", `Model provider "${profile}" requires env_key.`, {
|
|
175
|
-
profile,
|
|
176
|
-
modelProvider: profile,
|
|
177
|
-
missingFields: ["env_key"],
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
/**
|
|
182
|
-
* Returns the runtime env_key for one profile or throws a typed error.
|
|
183
|
-
*/
|
|
184
|
-
function requireRuntimeEnvKey(document, profile) {
|
|
185
|
-
const modelProviderSection = document.modelProviders.find((entry) => entry.name === profile);
|
|
186
|
-
if (!modelProviderSection) {
|
|
187
|
-
throw (0, errors_1.cliError)("PROFILE_NOT_FOUND", `Model provider "${profile}" does not exist in config.toml.`, {
|
|
188
|
-
profile,
|
|
189
|
-
modelProvider: profile,
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
if (!modelProviderSection.envKey) {
|
|
193
|
-
throw (0, errors_1.cliError)("MODEL_PROVIDER_ENV_KEY_MISSING", `Model provider "${profile}" requires env_key.`, {
|
|
194
|
-
profile,
|
|
195
|
-
modelProvider: profile,
|
|
196
|
-
missingFields: ["env_key"],
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
return modelProviderSection.envKey;
|
|
200
165
|
}
|
|
201
166
|
/**
|
|
202
167
|
* Resolves the current active provider and requires the mapping to be unique.
|
|
@@ -212,7 +177,7 @@ function resolveActiveProviderName(document, providers) {
|
|
|
212
177
|
});
|
|
213
178
|
}
|
|
214
179
|
if (matches.length > 1) {
|
|
215
|
-
throw (0, errors_1.cliError)("ACTIVE_PROVIDER_UNRESOLVED", `Active profile "${document.activeProfile}" maps to multiple providers.`, {
|
|
180
|
+
throw (0, errors_1.cliError)("ACTIVE_PROVIDER_UNRESOLVED", `Active profile "${document.activeProfile}" maps to multiple providers, so the active managed provider is ambiguous.`, {
|
|
216
181
|
profile: document.activeProfile,
|
|
217
182
|
providers: matches,
|
|
218
183
|
});
|
|
@@ -35,11 +35,13 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.getCopilotBridgeStatePath = getCopilotBridgeStatePath;
|
|
37
37
|
exports.readCopilotBridgeState = readCopilotBridgeState;
|
|
38
|
+
exports.inspectCopilotBridgeState = inspectCopilotBridgeState;
|
|
38
39
|
exports.writeCopilotBridgeState = writeCopilotBridgeState;
|
|
39
40
|
exports.clearCopilotBridgeState = clearCopilotBridgeState;
|
|
40
41
|
const fs = __importStar(require("node:fs"));
|
|
41
42
|
const os = __importStar(require("node:os"));
|
|
42
43
|
const path = __importStar(require("node:path"));
|
|
44
|
+
const errors_1 = require("../domain/errors");
|
|
43
45
|
const fs_utils_1 = require("./fs-utils");
|
|
44
46
|
/**
|
|
45
47
|
* Returns the user-level runtime state file used by Copilot bridge helpers.
|
|
@@ -61,6 +63,36 @@ function readCopilotBridgeState() {
|
|
|
61
63
|
}
|
|
62
64
|
return JSON.parse(fs.readFileSync(statePath, "utf8"));
|
|
63
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Safely inspects the runtime-state file for status/doctor style read paths.
|
|
68
|
+
*/
|
|
69
|
+
function inspectCopilotBridgeState() {
|
|
70
|
+
const statePath = getCopilotBridgeStatePath();
|
|
71
|
+
if (!fs.existsSync(statePath)) {
|
|
72
|
+
return {
|
|
73
|
+
exists: false,
|
|
74
|
+
valid: false,
|
|
75
|
+
parseError: null,
|
|
76
|
+
state: null,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
return {
|
|
81
|
+
exists: true,
|
|
82
|
+
valid: true,
|
|
83
|
+
parseError: null,
|
|
84
|
+
state: readCopilotBridgeState(),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
return {
|
|
89
|
+
exists: true,
|
|
90
|
+
valid: false,
|
|
91
|
+
parseError: (0, errors_1.normalizeError)(error).message,
|
|
92
|
+
state: null,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
64
96
|
/**
|
|
65
97
|
* Persists the Copilot bridge state manifest.
|
|
66
98
|
*/
|