@minniexcode/codex-switch 0.0.6 → 0.0.8

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.
Files changed (53) hide show
  1. package/README.AI.md +5 -2
  2. package/README.md +12 -6
  3. package/dist/app/add-provider.js +90 -5
  4. package/dist/app/edit-provider.js +39 -11
  5. package/dist/app/get-status.js +31 -1
  6. package/dist/app/init-codex.js +68 -0
  7. package/dist/app/list-providers.js +1 -0
  8. package/dist/app/run-doctor.js +96 -1
  9. package/dist/app/setup-codex.js +18 -9
  10. package/dist/app/show-config.js +9 -1
  11. package/dist/app/switch-provider.js +61 -8
  12. package/dist/cli/add-interactive.js +4 -2
  13. package/dist/cli/args.js +3 -0
  14. package/dist/cli/help.js +3 -0
  15. package/dist/cli/interactive.js +3 -0
  16. package/dist/cli/output.js +20 -5
  17. package/dist/cli/prompt.js +3 -0
  18. package/dist/cli.js +1 -1
  19. package/dist/commands/handlers.js +107 -13
  20. package/dist/commands/help.js +2 -1
  21. package/dist/commands/registry.js +87 -15
  22. package/dist/domain/config.js +137 -0
  23. package/dist/domain/providers.js +90 -2
  24. package/dist/domain/setup.js +1 -0
  25. package/dist/infra/backup-repo.js +3 -0
  26. package/dist/infra/codex-cli.js +3 -0
  27. package/dist/infra/codex-paths.js +3 -0
  28. package/dist/infra/fs-utils.js +3 -0
  29. package/dist/infra/lock-repo.js +3 -0
  30. package/dist/infra/providers-repo.js +3 -0
  31. package/dist/interaction/add-interactive.js +9 -18
  32. package/dist/interaction/interactive.js +84 -11
  33. package/dist/runtime/codex-probe.js +7 -0
  34. package/dist/runtime/copilot-adapter.js +173 -0
  35. package/dist/runtime/copilot-bridge-worker.js +25 -0
  36. package/dist/runtime/copilot-bridge.js +433 -0
  37. package/dist/runtime/copilot-installer.js +125 -0
  38. package/dist/runtime/copilot-sdk-loader.js +59 -0
  39. package/dist/storage/auth-repo.js +160 -0
  40. package/dist/storage/config-repo.js +58 -0
  41. package/dist/storage/fs-utils.js +3 -0
  42. package/dist/storage/runtime-state-repo.js +80 -0
  43. package/docs/Design/codex-switch-v0.0.7-design.md +862 -0
  44. package/docs/Design/codex-switch-v0.0.8-design.md +132 -0
  45. package/docs/Design/codex-switch-v0.0.9-to-v0.0.12-roadmap.md +413 -0
  46. package/docs/PRD/codex-switch-prd-v0.0.5-to-v0.1.0.md +131 -25
  47. package/docs/PRD/codex-switch-prd-v0.0.8.md +62 -0
  48. package/docs/Reference/codex-config-reference.md +604 -0
  49. package/docs/Reference/codex-config-reference.zh-CN.md +633 -0
  50. package/docs/cli-usage.md +77 -29
  51. package/docs/test-report-0.0.7.md +118 -0
  52. package/docs/testing.md +67 -47
  53. package/package.json +1 -1
@@ -0,0 +1,433 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.setCopilotBridgeSpawnImplementation = setCopilotBridgeSpawnImplementation;
37
+ exports.resetCopilotBridgeSpawnImplementation = resetCopilotBridgeSpawnImplementation;
38
+ exports.probeCopilotBridgeRuntime = probeCopilotBridgeRuntime;
39
+ exports.ensureCopilotBridge = ensureCopilotBridge;
40
+ exports.createCopilotBridgeRequestHandler = createCopilotBridgeRequestHandler;
41
+ exports.startCopilotBridgeServer = startCopilotBridgeServer;
42
+ exports.waitForCopilotBridgeHealth = waitForCopilotBridgeHealth;
43
+ exports.stopCopilotBridge = stopCopilotBridge;
44
+ const http = __importStar(require("node:http"));
45
+ const net = __importStar(require("node:net"));
46
+ const node_child_process_1 = require("node:child_process");
47
+ const path = __importStar(require("node:path"));
48
+ const providers_1 = require("../domain/providers");
49
+ const errors_1 = require("../domain/errors");
50
+ const runtime_state_repo_1 = require("../storage/runtime-state-repo");
51
+ let spawnImplementation = node_child_process_1.spawn;
52
+ /**
53
+ * Overrides the spawn implementation for bridge runtime tests.
54
+ */
55
+ function setCopilotBridgeSpawnImplementation(spawnLike) {
56
+ spawnImplementation = spawnLike;
57
+ }
58
+ /**
59
+ * Restores the default spawn implementation for bridge runtime tests.
60
+ */
61
+ function resetCopilotBridgeSpawnImplementation() {
62
+ spawnImplementation = node_child_process_1.spawn;
63
+ }
64
+ /**
65
+ * Returns the last known Copilot bridge runtime status.
66
+ */
67
+ async function probeCopilotBridgeRuntime(provider) {
68
+ const state = (0, runtime_state_repo_1.readCopilotBridgeState)();
69
+ if (!provider || !(0, providers_1.isCopilotBridgeProvider)(provider)) {
70
+ return {
71
+ ok: false,
72
+ runtime: "copilot-bridge",
73
+ reason: "missing",
74
+ cause: "No active Copilot bridge provider is selected.",
75
+ };
76
+ }
77
+ const runtime = provider.runtime;
78
+ if (!runtime) {
79
+ throw (0, errors_1.cliError)("RUNTIME_PROVIDER_INVALID", "Provider runtime block is missing.", {
80
+ provider: state?.provider ?? null,
81
+ });
82
+ }
83
+ if (!state) {
84
+ return {
85
+ ok: false,
86
+ runtime: "copilot-bridge",
87
+ reason: "missing",
88
+ cause: "Copilot bridge state manifest is missing.",
89
+ details: {
90
+ expectedBaseUrl: (0, providers_1.buildCopilotBridgeBaseUrl)(runtime),
91
+ },
92
+ };
93
+ }
94
+ if (state.baseUrl !== (0, providers_1.buildCopilotBridgeBaseUrl)(runtime)) {
95
+ return {
96
+ ok: false,
97
+ runtime: "copilot-bridge",
98
+ reason: "failed",
99
+ cause: "Copilot bridge state base URL does not match the provider runtime configuration.",
100
+ details: {
101
+ stateBaseUrl: state.baseUrl,
102
+ providerBaseUrl: (0, providers_1.buildCopilotBridgeBaseUrl)(runtime),
103
+ },
104
+ };
105
+ }
106
+ const healthy = await healthcheckCopilotBridge(state.host, state.port);
107
+ if (!healthy.ok) {
108
+ return {
109
+ ok: false,
110
+ runtime: "copilot-bridge",
111
+ reason: "failed",
112
+ cause: healthy.cause,
113
+ details: state,
114
+ };
115
+ }
116
+ (0, runtime_state_repo_1.writeCopilotBridgeState)({
117
+ ...state,
118
+ lastHealthcheckAt: new Date().toISOString(),
119
+ });
120
+ return {
121
+ ok: true,
122
+ runtime: "copilot-bridge",
123
+ details: state,
124
+ };
125
+ }
126
+ /**
127
+ * Starts or reuses a Copilot bridge worker, then verifies its health before returning.
128
+ */
129
+ async function ensureCopilotBridge(providerName, provider) {
130
+ if (!(0, providers_1.isCopilotBridgeProvider)(provider)) {
131
+ throw (0, errors_1.cliError)("RUNTIME_PROVIDER_INVALID", "Provider is not backed by a Copilot bridge runtime.", {
132
+ provider: providerName,
133
+ });
134
+ }
135
+ const runtime = provider.runtime;
136
+ if (!runtime) {
137
+ throw (0, errors_1.cliError)("RUNTIME_PROVIDER_INVALID", "Provider runtime block is missing.", {
138
+ provider: providerName,
139
+ });
140
+ }
141
+ const expectedBaseUrl = (0, providers_1.buildCopilotBridgeBaseUrl)(runtime);
142
+ const current = (0, runtime_state_repo_1.readCopilotBridgeState)();
143
+ if (current && current.provider === providerName && current.baseUrl === expectedBaseUrl) {
144
+ const healthy = await healthcheckCopilotBridge(current.host, current.port);
145
+ if (healthy.ok) {
146
+ (0, runtime_state_repo_1.writeCopilotBridgeState)({
147
+ ...current,
148
+ lastHealthcheckAt: new Date().toISOString(),
149
+ });
150
+ return {
151
+ baseUrl: expectedBaseUrl,
152
+ reused: true,
153
+ };
154
+ }
155
+ }
156
+ const portCheck = await checkPortAvailability(runtime.bridgeHost, runtime.bridgePort);
157
+ if (!portCheck.ok) {
158
+ throw (0, errors_1.cliError)("BRIDGE_PORT_CONFLICT", "Copilot bridge port is already in use.", {
159
+ provider: providerName,
160
+ host: runtime.bridgeHost,
161
+ port: runtime.bridgePort,
162
+ cause: portCheck.cause,
163
+ });
164
+ }
165
+ const workerPath = path.join(__dirname, "copilot-bridge-worker.js");
166
+ let child;
167
+ try {
168
+ child = spawnImplementation(process.execPath, [workerPath], {
169
+ detached: true,
170
+ stdio: "ignore",
171
+ env: {
172
+ ...process.env,
173
+ CODEX_SWITCH_BRIDGE_PROVIDER: providerName,
174
+ CODEX_SWITCH_BRIDGE_HOST: runtime.bridgeHost,
175
+ CODEX_SWITCH_BRIDGE_PORT: String(runtime.bridgePort),
176
+ CODEX_SWITCH_BRIDGE_API_KEY: provider.apiKey,
177
+ CODEX_SWITCH_BRIDGE_BASE_URL: expectedBaseUrl,
178
+ },
179
+ });
180
+ }
181
+ catch (error) {
182
+ throw (0, errors_1.cliError)("BRIDGE_START_FAILED", "Failed to start the Copilot bridge worker.", {
183
+ provider: providerName,
184
+ host: runtime.bridgeHost,
185
+ port: runtime.bridgePort,
186
+ cause: error instanceof Error ? error.message : String(error),
187
+ });
188
+ }
189
+ child.unref();
190
+ const startedAt = new Date().toISOString();
191
+ const healthy = await waitForCopilotBridgeStartup(child, runtime.bridgeHost, runtime.bridgePort, 15, 200);
192
+ if (!healthy.ok) {
193
+ (0, runtime_state_repo_1.clearCopilotBridgeState)();
194
+ if (healthy.reason === "start-failed") {
195
+ throw (0, errors_1.cliError)("BRIDGE_START_FAILED", "Copilot bridge worker exited before becoming healthy.", {
196
+ provider: providerName,
197
+ host: runtime.bridgeHost,
198
+ port: runtime.bridgePort,
199
+ cause: healthy.cause,
200
+ });
201
+ }
202
+ throw (0, errors_1.cliError)("BRIDGE_HEALTHCHECK_FAILED", "Copilot bridge did not become healthy after startup.", {
203
+ provider: providerName,
204
+ host: runtime.bridgeHost,
205
+ port: runtime.bridgePort,
206
+ cause: healthy.cause,
207
+ });
208
+ }
209
+ const state = {
210
+ provider: providerName,
211
+ pid: child.pid ?? null,
212
+ host: runtime.bridgeHost,
213
+ port: runtime.bridgePort,
214
+ baseUrl: expectedBaseUrl,
215
+ startedAt,
216
+ lastHealthcheckAt: new Date().toISOString(),
217
+ };
218
+ (0, runtime_state_repo_1.writeCopilotBridgeState)(state);
219
+ return {
220
+ baseUrl: expectedBaseUrl,
221
+ reused: false,
222
+ };
223
+ }
224
+ /**
225
+ * Creates an HTTP request handler implementing the minimal OpenAI-compatible bridge contract.
226
+ */
227
+ function createCopilotBridgeRequestHandler(context) {
228
+ return async (request, response) => {
229
+ try {
230
+ const method = request.method ?? "GET";
231
+ const url = request.url ?? "/";
232
+ if (method === "GET" && url === "/healthz") {
233
+ response.writeHead(200, { "content-type": "application/json" });
234
+ response.end(JSON.stringify({ ok: true }));
235
+ return;
236
+ }
237
+ if (!isAuthorized(request, context.apiKey)) {
238
+ response.writeHead(401, { "content-type": "application/json" });
239
+ response.end(JSON.stringify({ error: { message: "Unauthorized" } }));
240
+ return;
241
+ }
242
+ if (method === "GET" && url === "/v1/models") {
243
+ response.writeHead(200, { "content-type": "application/json" });
244
+ response.end(JSON.stringify({ object: "list", data: [] }));
245
+ return;
246
+ }
247
+ if (method !== "POST" || url !== "/v1/chat/completions") {
248
+ response.writeHead(404, { "content-type": "application/json" });
249
+ response.end(JSON.stringify({ error: { message: "Not found" } }));
250
+ return;
251
+ }
252
+ const body = await readJsonBody(request);
253
+ const stream = Boolean(body.stream);
254
+ const payload = await context.executeChatCompletion(body);
255
+ if (stream) {
256
+ response.writeHead(200, {
257
+ "content-type": "text/event-stream",
258
+ "cache-control": "no-cache",
259
+ connection: "keep-alive",
260
+ });
261
+ response.write(`data: ${JSON.stringify(payload)}\n\n`);
262
+ response.write("data: [DONE]\n\n");
263
+ response.end();
264
+ return;
265
+ }
266
+ response.writeHead(200, { "content-type": "application/json" });
267
+ response.end(JSON.stringify(payload));
268
+ }
269
+ catch (error) {
270
+ response.writeHead(500, { "content-type": "application/json" });
271
+ response.end(JSON.stringify({ error: { message: error instanceof Error ? error.message : String(error) } }));
272
+ }
273
+ };
274
+ }
275
+ /**
276
+ * Starts an in-process local bridge server. Primarily used by the worker entrypoint and tests.
277
+ */
278
+ function startCopilotBridgeServer(args) {
279
+ return new Promise((resolve, reject) => {
280
+ const server = http.createServer(createCopilotBridgeRequestHandler({
281
+ apiKey: args.apiKey,
282
+ executeChatCompletion: args.executeChatCompletion,
283
+ }));
284
+ server.once("error", reject);
285
+ server.listen(args.port, args.host, () => {
286
+ server.off("error", reject);
287
+ resolve(server);
288
+ });
289
+ });
290
+ }
291
+ /**
292
+ * Polls the bridge health endpoint until it becomes available or the retry budget is exhausted.
293
+ */
294
+ async function waitForCopilotBridgeHealth(host, port, attempts = 10, delayMs = 150) {
295
+ for (let index = 0; index < attempts; index += 1) {
296
+ const result = await healthcheckCopilotBridge(host, port);
297
+ if (result.ok) {
298
+ return result;
299
+ }
300
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
301
+ }
302
+ return {
303
+ ok: false,
304
+ cause: "Timed out waiting for Copilot bridge health endpoint.",
305
+ };
306
+ }
307
+ /**
308
+ * Stops the currently persisted Copilot bridge worker when possible.
309
+ */
310
+ function stopCopilotBridge() {
311
+ const state = (0, runtime_state_repo_1.readCopilotBridgeState)();
312
+ if (state?.pid) {
313
+ try {
314
+ process.kill(state.pid);
315
+ }
316
+ catch {
317
+ // Ignore best-effort bridge cleanup failures.
318
+ }
319
+ }
320
+ (0, runtime_state_repo_1.clearCopilotBridgeState)();
321
+ }
322
+ async function checkPortAvailability(host, port) {
323
+ return new Promise((resolve) => {
324
+ const server = net.createServer();
325
+ server.once("error", (error) => {
326
+ resolve({
327
+ ok: false,
328
+ cause: error.message,
329
+ });
330
+ });
331
+ server.listen(port, host, () => {
332
+ server.close((error) => {
333
+ if (error) {
334
+ resolve({
335
+ ok: false,
336
+ cause: error.message,
337
+ });
338
+ return;
339
+ }
340
+ resolve({ ok: true });
341
+ });
342
+ });
343
+ });
344
+ }
345
+ async function waitForCopilotBridgeStartup(child, host, port, attempts, delayMs) {
346
+ let startupFailure = null;
347
+ const onError = (error) => {
348
+ startupFailure = error.message;
349
+ };
350
+ const onExit = (code, signal) => {
351
+ startupFailure = `Worker exited with code ${String(code)} signal ${String(signal)}.`;
352
+ };
353
+ child.once("error", onError);
354
+ child.once("exit", onExit);
355
+ try {
356
+ for (let index = 0; index < attempts; index += 1) {
357
+ if (startupFailure !== null) {
358
+ return {
359
+ ok: false,
360
+ reason: "start-failed",
361
+ cause: startupFailure,
362
+ };
363
+ }
364
+ const result = await healthcheckCopilotBridge(host, port);
365
+ if (result.ok) {
366
+ return result;
367
+ }
368
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
369
+ }
370
+ if (startupFailure !== null) {
371
+ return {
372
+ ok: false,
373
+ reason: "start-failed",
374
+ cause: startupFailure,
375
+ };
376
+ }
377
+ return {
378
+ ok: false,
379
+ reason: "healthcheck-failed",
380
+ cause: "Timed out waiting for Copilot bridge health endpoint.",
381
+ };
382
+ }
383
+ finally {
384
+ child.off("error", onError);
385
+ child.off("exit", onExit);
386
+ }
387
+ }
388
+ async function healthcheckCopilotBridge(host, port) {
389
+ return new Promise((resolve) => {
390
+ const request = http.request({
391
+ host,
392
+ port,
393
+ method: "GET",
394
+ path: "/healthz",
395
+ timeout: 1000,
396
+ }, (response) => {
397
+ response.resume();
398
+ if (response.statusCode === 200) {
399
+ resolve({ ok: true });
400
+ return;
401
+ }
402
+ resolve({
403
+ ok: false,
404
+ cause: `Health endpoint returned status ${String(response.statusCode ?? 0)}.`,
405
+ });
406
+ });
407
+ request.on("error", (error) => {
408
+ resolve({
409
+ ok: false,
410
+ cause: error.message,
411
+ });
412
+ });
413
+ request.on("timeout", () => {
414
+ request.destroy(new Error("Health endpoint timed out."));
415
+ });
416
+ request.end();
417
+ });
418
+ }
419
+ async function readJsonBody(request) {
420
+ const chunks = [];
421
+ for await (const chunk of request) {
422
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
423
+ }
424
+ const raw = Buffer.concat(chunks).toString("utf8");
425
+ return raw.trim() === "" ? {} : JSON.parse(raw);
426
+ }
427
+ function isAuthorized(request, expectedApiKey) {
428
+ const authorization = request.headers.authorization;
429
+ if (!authorization || !authorization.startsWith("Bearer ")) {
430
+ return false;
431
+ }
432
+ return authorization.slice("Bearer ".length) === expectedApiKey;
433
+ }
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.setCopilotInstallerSpawnImplementation = setCopilotInstallerSpawnImplementation;
37
+ exports.resetCopilotInstallerSpawnImplementation = resetCopilotInstallerSpawnImplementation;
38
+ exports.getCopilotRuntimeInstallDir = getCopilotRuntimeInstallDir;
39
+ exports.getCopilotSdkPackageName = getCopilotSdkPackageName;
40
+ exports.probeCopilotSdkInstall = probeCopilotSdkInstall;
41
+ exports.installCopilotSdk = installCopilotSdk;
42
+ const fs = __importStar(require("node:fs"));
43
+ const os = __importStar(require("node:os"));
44
+ const path = __importStar(require("node:path"));
45
+ const node_child_process_1 = require("node:child_process");
46
+ const errors_1 = require("../domain/errors");
47
+ const COPILOT_SDK_PACKAGE = "@github/copilot-sdk";
48
+ const COPILOT_SDK_VERSION = "latest";
49
+ let spawnImplementation = node_child_process_1.spawnSync;
50
+ /**
51
+ * Overrides the spawn implementation for runtime installer tests.
52
+ */
53
+ function setCopilotInstallerSpawnImplementation(spawnLike) {
54
+ spawnImplementation = spawnLike;
55
+ }
56
+ /**
57
+ * Restores the default spawn implementation after tests.
58
+ */
59
+ function resetCopilotInstallerSpawnImplementation() {
60
+ spawnImplementation = node_child_process_1.spawnSync;
61
+ }
62
+ /**
63
+ * Returns the user-level runtime directory used to lazily install the Copilot SDK.
64
+ */
65
+ function getCopilotRuntimeInstallDir() {
66
+ const override = process.env.CODEX_SWITCH_COPILOT_RUNTIME_DIR;
67
+ if (override && override.trim() !== "") {
68
+ return path.resolve(override);
69
+ }
70
+ return path.join(os.homedir(), ".codex-switch", "runtimes", "copilot");
71
+ }
72
+ /**
73
+ * Returns the package name used by the Copilot runtime installer.
74
+ */
75
+ function getCopilotSdkPackageName() {
76
+ return COPILOT_SDK_PACKAGE;
77
+ }
78
+ /**
79
+ * Reports whether the optional Copilot SDK runtime is currently installed.
80
+ */
81
+ function probeCopilotSdkInstall() {
82
+ const installDir = getCopilotRuntimeInstallDir();
83
+ const packageJsonPath = path.join(installDir, "node_modules", "@github", "copilot-sdk", "package.json");
84
+ if (!fs.existsSync(packageJsonPath)) {
85
+ return {
86
+ installed: false,
87
+ installDir,
88
+ packageName: COPILOT_SDK_PACKAGE,
89
+ packageVersion: null,
90
+ };
91
+ }
92
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
93
+ return {
94
+ installed: true,
95
+ installDir,
96
+ packageName: COPILOT_SDK_PACKAGE,
97
+ packageVersion: packageJson.version ?? null,
98
+ };
99
+ }
100
+ /**
101
+ * Installs the optional Copilot SDK into the user-level runtime directory.
102
+ */
103
+ function installCopilotSdk() {
104
+ const installDir = getCopilotRuntimeInstallDir();
105
+ fs.mkdirSync(installDir, { recursive: true });
106
+ const packageJsonPath = path.join(installDir, "package.json");
107
+ if (!fs.existsSync(packageJsonPath)) {
108
+ fs.writeFileSync(packageJsonPath, `${JSON.stringify({ name: "codex-switch-copilot-runtime", private: true, version: "0.0.0" }, null, 2)}\n`, "utf8");
109
+ }
110
+ const command = process.platform === "win32" ? "npm.cmd" : "npm";
111
+ const result = spawnImplementation(command, ["install", "--no-save", `${COPILOT_SDK_PACKAGE}@${COPILOT_SDK_VERSION}`], {
112
+ cwd: installDir,
113
+ stdio: "pipe",
114
+ encoding: "utf8",
115
+ shell: false,
116
+ });
117
+ if (result.status !== 0) {
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.stderr || result.stdout || `npm exited with status ${String(result.status)}`,
122
+ });
123
+ }
124
+ return probeCopilotSdkInstall();
125
+ }
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.getCopilotSdkEntrypoint = getCopilotSdkEntrypoint;
37
+ exports.loadCopilotSdk = loadCopilotSdk;
38
+ const path = __importStar(require("node:path"));
39
+ const errors_1 = require("../domain/errors");
40
+ const copilot_installer_1 = require("./copilot-installer");
41
+ /**
42
+ * Dynamically resolves the lazily installed Copilot SDK entrypoint.
43
+ */
44
+ function getCopilotSdkEntrypoint() {
45
+ return path.join((0, copilot_installer_1.getCopilotRuntimeInstallDir)(), "node_modules", "@github", "copilot-sdk");
46
+ }
47
+ /**
48
+ * Loads the Copilot SDK only when a Copilot runtime path is exercised.
49
+ */
50
+ async function loadCopilotSdk() {
51
+ const status = (0, copilot_installer_1.probeCopilotSdkInstall)();
52
+ if (!status.installed) {
53
+ throw (0, errors_1.cliError)("COPILOT_SDK_MISSING", "The optional Copilot SDK runtime is not installed.", {
54
+ installDir: status.installDir,
55
+ packageName: status.packageName,
56
+ });
57
+ }
58
+ return Promise.resolve(`${getCopilotSdkEntrypoint()}`).then(s => __importStar(require(s)));
59
+ }