@minniexcode/codex-switch 0.1.4 → 0.2.0

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 CHANGED
@@ -6,7 +6,7 @@ This file summarizes the current operational contract for AI agents, automation
6
6
 
7
7
  - Package: `@minniexcode/codex-switch`
8
8
  - CLI name: `codexs`
9
- - Current repository version: `0.1.4`
9
+ - Current repository version: `0.1.5`
10
10
  - Version status: development line
11
11
  - Runtime contract target: Codex `0.134.0+`
12
12
 
@@ -22,7 +22,7 @@ Direct provider workflow:
22
22
 
23
23
  ```bash
24
24
  codexs init
25
- codexs add <provider> --model <model> --api-key <key> [--base-url <url>]
25
+ codexs add <provider> --profile <model-provider-id> --model <model> --api-key <key> [--base-url <url>]
26
26
  codexs switch <provider>
27
27
  codexs status
28
28
  codexs doctor
@@ -33,7 +33,7 @@ GitHub Copilot workflow:
33
33
  ```bash
34
34
  codexs init
35
35
  codexs login copilot
36
- codexs add <provider> --copilot --model <model>
36
+ codexs add <provider> --copilot --profile <model-provider-id> --model <model>
37
37
  codexs switch <provider>
38
38
  codexs status
39
39
  codexs doctor
@@ -130,10 +130,11 @@ Important behavioral constraints:
130
130
  - `login copilot` requires a real TTY and does not support `--json`.
131
131
  - `login copilot` currently installs the local Copilot SDK when needed, tries the bundled runtime CLI first, falls back to `PATH` when necessary, and rechecks auth readiness before reporting success.
132
132
  - `add --copilot` assumes SDK install and upstream Copilot auth are already ready.
133
+ - Non-interactive automation should pass `--profile` explicitly. In TTY mode, `add` and `edit` can prompt for missing required fields.
133
134
  - `migrate` remains interactive when provider adoption requires human input.
134
135
  - `status` is the main dual-path summary command.
135
136
  - `doctor` is the deeper repair-oriented diagnostic command.
136
- - The current `0.1.4` line focuses on bridge stability, surfaced runtime log metadata, and stricter release-hygiene verification rather than command-surface expansion.
137
+ - The current `0.1.5` line focuses on Copilot Bridge process visibility, Responses commentary/reasoning stream events, defensive SDK-event normalization, and unknown-event redaction hardening rather than command-surface expansion.
137
138
 
138
139
  ## Safety Notes
139
140
 
package/README.CN.md CHANGED
@@ -6,9 +6,9 @@
6
6
 
7
7
  ## 版本定位
8
8
 
9
- 当前包版本:`0.1.4`
9
+ 当前包版本:`0.1.5`
10
10
 
11
- 这是当前仓库开发线。`0.1.4` 聚焦于 bridge 稳定性与可观测性,包括 bridge 复用探测加固、运行态日志元数据外显,以及更严格的 release hygiene 门禁,同时不扩展 provider 命令面。
11
+ 这是当前仓库开发线。`0.1.5` Copilot Bridge 过程可见性补丁,聚焦于 commentary/reasoning 流式信号、SDK 事件防御性归一化,以及未知运行态事件的更安全脱敏,同时不扩展 provider 命令面。
12
12
 
13
13
  ## 安装
14
14
 
@@ -34,7 +34,7 @@ Direct provider 主路径:
34
34
 
35
35
  ```bash
36
36
  codexs init
37
- codexs add my-provider --model gpt-5.5 --base-url https://gateway.example.com/v1 --api-key sk-xxx
37
+ codexs add my-provider --profile my-provider --model gpt-5.5 --base-url https://gateway.example.com/v1 --api-key sk-xxx
38
38
  codexs switch my-provider
39
39
  codexs status
40
40
  codexs doctor
@@ -45,7 +45,7 @@ GitHub Copilot 主路径:
45
45
  ```bash
46
46
  codexs init
47
47
  codexs login copilot
48
- codexs add copilot-main --copilot --model gpt-4.1
48
+ codexs add copilot-main --copilot --profile copilot-main --model gpt-4.1
49
49
  codexs switch copilot-main
50
50
  codexs status
51
51
  codexs doctor
@@ -56,6 +56,7 @@ codexs doctor
56
56
  - `init` 负责初始化 `codex-switch` 的 tool home 与受管状态文件。
57
57
  - `login copilot` 负责上游 Copilot onboarding 和登录可用性检查。
58
58
  - `add --copilot` 不负责替你登录,它假设上游 Copilot 已经 ready。
59
+ - 非交互调用请显式传入 `--profile`;在 TTY 模式下,`add` 和 `edit` 可以补问缺失的必填项。
59
60
  - `switch` 会把选中的 provider 投影到目标 Codex runtime 的顶层 `model` 与 `model_provider`。
60
61
  - `status` 是切换后的主读取命令。
61
62
  - `doctor` 是主诊断命令,用于解释问题和下一步修复动作。
@@ -116,8 +117,8 @@ codexs current
116
117
  codexs status
117
118
  codexs config show [profile]
118
119
  codexs config list-profiles
119
- codexs add <provider> --model <model> --api-key <key> [--base-url <url>]
120
- codexs add <provider> --copilot --model <model>
120
+ codexs add <provider> --profile <model-provider-id> --model <model> --api-key <key> [--base-url <url>]
121
+ codexs add <provider> --copilot --profile <model-provider-id> --model <model>
121
122
  codexs edit <provider>
122
123
  codexs switch <provider>
123
124
  codexs remove <provider> [--force] [--switch-to <provider>]
@@ -209,8 +210,8 @@ npm pack --dry-run
209
210
  - [Design 0.1.1](./docs/Design/codex-switch-v0.1.1-design.md)
210
211
  - [PRD 0.1.2](./docs/PRD/codex-switch-prd-v0.1.2.md)
211
212
  - [Design 0.1.2](./docs/Design/codex-switch-v0.1.2-design.md)
212
- - [PRD 0.1.4](./docs/PRD/codex-switch-prd-v0.1.4.md)
213
- - [Design 0.1.4](./docs/Design/codex-switch-v0.1.4-design.md)
213
+ - [PRD 0.1.5](./docs/PRD/codex-switch-prd-v0.1.5.md)
214
+ - [Design 0.1.5](./docs/Design/codex-switch-v0.1.5-design.md)
214
215
 
215
216
  ## License
216
217
 
package/README.md CHANGED
@@ -8,9 +8,9 @@ Chinese version: [README.CN.md](./README.CN.md)
8
8
 
9
9
  ## Version
10
10
 
11
- Current package version: `0.1.4`
11
+ Current package version: `0.1.5`
12
12
 
13
- This is the current repository development line. `0.1.4` is the bridge stability and observability line, focused on bridge reuse hardening, surfaced runtime log metadata, and a stricter release-hygiene gate while keeping the provider surface unchanged.
13
+ This is the current repository development line. `0.1.5` is a Copilot Bridge process-visibility patch, focused on streaming commentary/reasoning signals, defensive SDK-event normalization, and safer redaction for unknown runtime events while keeping the provider surface unchanged.
14
14
 
15
15
  ## Install
16
16
 
@@ -36,7 +36,7 @@ Direct provider workflow:
36
36
 
37
37
  ```bash
38
38
  codexs init
39
- codexs add my-provider --model gpt-5.5 --base-url https://gateway.example.com/v1 --api-key sk-xxx
39
+ codexs add my-provider --profile my-provider --model gpt-5.5 --base-url https://gateway.example.com/v1 --api-key sk-xxx
40
40
  codexs switch my-provider
41
41
  codexs status
42
42
  codexs doctor
@@ -47,7 +47,7 @@ GitHub Copilot workflow:
47
47
  ```bash
48
48
  codexs init
49
49
  codexs login copilot
50
- codexs add copilot-main --copilot --model gpt-4.1
50
+ codexs add copilot-main --copilot --profile copilot-main --model gpt-4.1
51
51
  codexs switch copilot-main
52
52
  codexs status
53
53
  codexs doctor
@@ -58,6 +58,7 @@ Notes:
58
58
  - `init` prepares the `codex-switch` tool home and managed state.
59
59
  - `login copilot` handles upstream Copilot onboarding and auth readiness.
60
60
  - `add --copilot` does not perform login for you; it assumes Copilot login is already ready.
61
+ - For non-interactive use, pass `--profile` explicitly. In TTY mode, `add` and `edit` can prompt for missing required fields.
61
62
  - Copilot support is an experimental local bridge. The managed installer defaults to `@github/copilot-sdk@1.0.2`, Copilot runtime paths require Node.js `>=20`, and runtime checks separately reject older or prerelease SDK installs while validating API shape when the client or session is used.
62
63
  - `switch` projects the selected provider into the target Codex runtime as top-level `model` plus `model_provider`.
63
64
  - `status` is the main read command after switching.
@@ -125,8 +126,8 @@ codexs current
125
126
  codexs status
126
127
  codexs config show [profile]
127
128
  codexs config list-profiles
128
- codexs add <provider> --model <model> --api-key <key> [--base-url <url>]
129
- codexs add <provider> --copilot --model <model>
129
+ codexs add <provider> --profile <model-provider-id> --model <model> --api-key <key> [--base-url <url>]
130
+ codexs add <provider> --copilot --profile <model-provider-id> --model <model>
130
131
  codexs edit <provider>
131
132
  codexs switch <provider>
132
133
  codexs remove <provider> [--force] [--switch-to <provider>]
@@ -218,10 +219,10 @@ npm pack --dry-run
218
219
  - [PRD 0.1.1](./docs/PRD/codex-switch-prd-v0.1.1.md)
219
220
  - [PRD 0.1.2](./docs/PRD/codex-switch-prd-v0.1.2.md)
220
221
  - [PRD 0.1.3](./docs/PRD/codex-switch-prd-v0.1.3.md)
221
- - [PRD 0.1.4](./docs/PRD/codex-switch-prd-v0.1.4.md)
222
+ - [PRD 0.1.5](./docs/PRD/codex-switch-prd-v0.1.5.md)
222
223
  - [Design 0.1.2](./docs/Design/codex-switch-v0.1.2-design.md)
223
224
  - [Design 0.1.3](./docs/Design/codex-switch-v0.1.3-design.md)
224
- - [Design 0.1.4](./docs/Design/codex-switch-v0.1.4-design.md)
225
+ - [Design 0.1.5](./docs/Design/codex-switch-v0.1.5-design.md)
225
226
 
226
227
  ## License
227
228
 
@@ -40,8 +40,7 @@ const errors_1 = require("../domain/errors");
40
40
  const config_repo_1 = require("../storage/config-repo");
41
41
  const fs_utils_1 = require("../storage/fs-utils");
42
42
  const providers_repo_1 = require("../storage/providers-repo");
43
- const copilot_adapter_1 = require("../runtime/copilot-adapter");
44
- const copilot_installer_1 = require("../runtime/copilot-installer");
43
+ const copilot_token_1 = require("../runtime/copilot-token");
45
44
  const run_mutation_1 = require("./run-mutation");
46
45
  /**
47
46
  * Adds a new provider record to the managed providers registry.
@@ -56,34 +55,29 @@ async function addProvider(args) {
56
55
  const bridgePort = args.bridgePort ?? 41415;
57
56
  const runtime = args.copilot
58
57
  ? {
59
- kind: "copilot-sdk-bridge",
58
+ kind: "copilot-http-proxy",
60
59
  upstream: "github-copilot",
61
60
  bridgeHost,
62
61
  bridgePort,
63
62
  bridgePath: "/v1",
64
63
  premiumRequests: true,
65
- authSource: "official-sdk",
66
- sdkInstallMode: "lazy",
64
+ authSource: "github-pat",
67
65
  }
68
66
  : undefined;
69
67
  if (args.copilot) {
70
- (0, copilot_installer_1.assertCopilotNodeRuntimeSupported)();
71
- const installStatus = (0, copilot_installer_1.probeCopilotSdkInstall)(args.runtimesDir);
72
- if (!installStatus.installed) {
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.",
68
+ const githubToken = (0, copilot_token_1.readGithubToken)(args.toolHomeDir);
69
+ if (!githubToken) {
70
+ throw (0, errors_1.cliError)("COPILOT_AUTH_REQUIRED", "GitHub Copilot authentication is required. Run `codexs login copilot` first.", {
71
+ suggestion: "Run `codexs login copilot` to complete GitHub Copilot login.",
77
72
  });
78
73
  }
79
74
  try {
80
- await (0, copilot_adapter_1.readCopilotAuthState)(args.runtimesDir);
75
+ await (0, copilot_token_1.exchangeForCopilotToken)(githubToken);
81
76
  }
82
77
  catch (error) {
83
78
  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 ?? {}),
79
+ if (normalized.code === "COPILOT_AUTH_REQUIRED" || normalized.code === "COPILOT_TOKEN_EXCHANGE_FAILED") {
80
+ throw (0, errors_1.cliError)("COPILOT_AUTH_REQUIRED", "GitHub token is invalid or expired. Run `codexs login copilot` to re-authenticate.", {
87
81
  suggestion: "Run `codexs login copilot` to complete GitHub Copilot login.",
88
82
  });
89
83
  }
@@ -10,8 +10,7 @@ const providers_repo_1 = require("../storage/providers-repo");
10
10
  const interactive_1 = require("../interaction/interactive");
11
11
  const copilot_bridge_1 = require("../runtime/copilot-bridge");
12
12
  const runtime_state_repo_1 = require("../storage/runtime-state-repo");
13
- const copilot_installer_1 = require("../runtime/copilot-installer");
14
- const copilot_adapter_1 = require("../runtime/copilot-adapter");
13
+ const copilot_token_1 = require("../runtime/copilot-token");
15
14
  const DEFAULT_BRIDGE_PORT = 41415;
16
15
  /**
17
16
  * Starts or reuses the managed Copilot bridge for one provider.
@@ -28,8 +27,8 @@ async function startBridge(args) {
28
27
  commandName: "start",
29
28
  preferRuntimeState: false,
30
29
  });
31
- await requireBridgeRuntimeReadiness(args.runtimesDir);
32
- const bridge = await (0, copilot_bridge_1.ensureCopilotBridge)(target.providerName, target.provider, args.runtimeDir, args.runtimesDir);
30
+ await requireBridgeRuntimeReadiness(args.runtimesDir, args.toolHomeDir);
31
+ const bridge = await (0, copilot_bridge_1.ensureCopilotBridge)(target.providerName, target.provider, args.runtimeDir, args.runtimesDir, args.toolHomeDir);
33
32
  const nextProvider = bridge.portChanged ? rewriteBridgeProviderPort(target.provider, bridge.port) : target.provider;
34
33
  if (bridge.portChanged) {
35
34
  try {
@@ -252,16 +251,12 @@ async function promptForCopilotBridgeSelection(runtime, targets, commandName) {
252
251
  /**
253
252
  * Verifies that the local Copilot bridge prerequisites are available before startup.
254
253
  */
255
- async function requireBridgeRuntimeReadiness(runtimesDir) {
256
- (0, copilot_installer_1.assertCopilotNodeRuntimeSupported)();
257
- const installStatus = (0, copilot_installer_1.probeCopilotSdkInstall)(runtimesDir);
258
- if (!installStatus.installed) {
259
- throw (0, errors_1.cliError)("COPILOT_SDK_MISSING", "The optional Copilot SDK runtime is not installed.", {
260
- installDir: installStatus.installDir,
261
- packageName: installStatus.packageName,
262
- });
254
+ async function requireBridgeRuntimeReadiness(_runtimesDir, toolHomeDir) {
255
+ const githubToken = (0, copilot_token_1.readGithubToken)(toolHomeDir);
256
+ if (!githubToken) {
257
+ throw (0, errors_1.cliError)("COPILOT_AUTH_REQUIRED", "GitHub Copilot authentication is required. Run `codexs login copilot` first.");
263
258
  }
264
- await (0, copilot_adapter_1.readCopilotAuthState)(runtimesDir);
259
+ await (0, copilot_token_1.exchangeForCopilotToken)(githubToken);
265
260
  }
266
261
  /**
267
262
  * Rewrites one Copilot bridge provider record with a recovered runtime port.
@@ -41,9 +41,8 @@ const providers_1 = require("../domain/providers");
41
41
  const config_repo_1 = require("../storage/config-repo");
42
42
  const providers_repo_1 = require("../storage/providers-repo");
43
43
  const auth_repo_1 = require("../storage/auth-repo");
44
- const copilot_installer_1 = require("../runtime/copilot-installer");
44
+ const copilot_token_1 = require("../runtime/copilot-token");
45
45
  const copilot_bridge_1 = require("../runtime/copilot-bridge");
46
- const copilot_adapter_1 = require("../runtime/copilot-adapter");
47
46
  const runtime_state_repo_1 = require("../storage/runtime-state-repo");
48
47
  /**
49
48
  * Reports the current on-disk runtime state and how it maps back to managed providers.
@@ -73,7 +72,7 @@ async function getStatus(codexDir, configPath, providersPath, authPath, options)
73
72
  const activeProvider = liveState.providerResolvable && providers && liveState.mappedProvider
74
73
  ? providers.providers[liveState.mappedProvider]
75
74
  : null;
76
- const copilotInstall = (0, copilot_installer_1.probeCopilotSdkInstall)(options?.runtimesDir);
75
+ const copilotInstall = { installed: Boolean((0, copilot_token_1.readGithubToken)(options?.toolHomeDir)), source: "github-pat" };
77
76
  const runtimeStateInspection = (0, runtime_state_repo_1.inspectCopilotBridgeState)(options?.runtimeDir);
78
77
  const runtimeState = runtimeStateInspection.state;
79
78
  const runtimeStateProvider = runtimeState && providers ? providers.providers[runtimeState.provider] ?? null : null;
@@ -107,12 +106,18 @@ async function getStatus(codexDir, configPath, providersPath, authPath, options)
107
106
  }
108
107
  : null;
109
108
  const copilotAuth = activeProvider && (0, providers_1.isCopilotBridgeProvider)(activeProvider)
110
- ? await (0, copilot_adapter_1.readCopilotAuthState)(options?.runtimesDir).catch((error) => ({
111
- ready: false,
112
- source: "official-sdk",
113
- mode: "session",
114
- error: error instanceof Error ? error.message : String(error),
115
- }))
109
+ ? await (async () => {
110
+ const token = (0, copilot_token_1.readGithubToken)(options?.toolHomeDir);
111
+ if (!token)
112
+ return { ready: false, source: "github-pat", mode: "token", error: "No GitHub token found" };
113
+ try {
114
+ await (0, copilot_token_1.exchangeForCopilotToken)(token);
115
+ return { ready: true, source: "github-pat", mode: "token" };
116
+ }
117
+ catch (error) {
118
+ return { ready: false, source: "github-pat", mode: "token", error: error instanceof Error ? error.message : String(error) };
119
+ }
120
+ })()
116
121
  : null;
117
122
  if (liveState.canBackfillActiveProvider) {
118
123
  // Surface unmanaged live state without mutating anything during a read-only status call.
@@ -147,9 +152,7 @@ async function getStatus(codexDir, configPath, providersPath, authPath, options)
147
152
  runtimeProvider: activeProvider && (0, providers_1.isCopilotBridgeProvider)(activeProvider) ? activeProvider.runtime?.kind ?? null : null,
148
153
  copilotSdk: {
149
154
  installed: copilotInstall.installed,
150
- installDir: copilotInstall.installDir,
151
- packageName: copilotInstall.packageName,
152
- packageVersion: copilotInstall.packageVersion ?? null,
155
+ source: copilotInstall.source,
153
156
  },
154
157
  copilotAuth,
155
158
  copilotBridge,
@@ -43,9 +43,8 @@ const errors_1 = require("../domain/errors");
43
43
  const codex_probe_1 = require("../runtime/codex-probe");
44
44
  const auth_repo_1 = require("../storage/auth-repo");
45
45
  const providers_1 = require("../domain/providers");
46
- const copilot_installer_1 = require("../runtime/copilot-installer");
46
+ const copilot_token_1 = require("../runtime/copilot-token");
47
47
  const copilot_bridge_1 = require("../runtime/copilot-bridge");
48
- const copilot_adapter_1 = require("../runtime/copilot-adapter");
49
48
  const runtime_state_repo_1 = require("../storage/runtime-state-repo");
50
49
  const codex_version_1 = require("../runtime/codex-version");
51
50
  /**
@@ -125,25 +124,25 @@ async function runDoctor(args) {
125
124
  if (matches.length === 1) {
126
125
  const activeProvider = providers.providers[matches[0]];
127
126
  if ((0, providers_1.isCopilotBridgeProvider)(activeProvider)) {
128
- const installStatus = (0, copilot_installer_1.probeCopilotSdkInstall)(args.runtimesDir);
129
- if (!installStatus.installed) {
127
+ const githubToken = (0, copilot_token_1.readGithubToken)(args.toolHomeDir);
128
+ if (!githubToken) {
130
129
  issues.push({
131
- code: "COPILOT_SDK_MISSING",
132
- message: "The optional Copilot SDK runtime is not installed.",
133
- installDir: installStatus.installDir,
134
- packageName: installStatus.packageName,
130
+ code: "COPILOT_AUTH_REQUIRED",
131
+ message: "GitHub Copilot authentication is required. Run `codexs login copilot`.",
135
132
  });
136
133
  }
137
- try {
138
- await (0, copilot_adapter_1.readCopilotAuthState)(args.runtimesDir);
139
- }
140
- catch (error) {
141
- const normalized = (0, errors_1.normalizeError)(error);
142
- issues.push({
143
- code: normalized.code,
144
- message: normalized.message,
145
- ...(normalized.details ?? {}),
146
- });
134
+ else {
135
+ try {
136
+ await (0, copilot_token_1.exchangeForCopilotToken)(githubToken);
137
+ }
138
+ catch (error) {
139
+ const normalized = (0, errors_1.normalizeError)(error);
140
+ issues.push({
141
+ code: normalized.code,
142
+ message: normalized.message,
143
+ ...(normalized.details ?? {}),
144
+ });
145
+ }
147
146
  }
148
147
  const bridge = await (0, copilot_bridge_1.probeCopilotBridgeRuntime)(activeProvider, runtimeState, args.runtimeDir);
149
148
  if (!bridge.ok) {
@@ -6,8 +6,7 @@ const config_repo_1 = require("../storage/config-repo");
6
6
  const auth_repo_1 = require("../storage/auth-repo");
7
7
  const providers_repo_1 = require("../storage/providers-repo");
8
8
  const copilot_bridge_1 = require("../runtime/copilot-bridge");
9
- const copilot_installer_1 = require("../runtime/copilot-installer");
10
- const copilot_adapter_1 = require("../runtime/copilot-adapter");
9
+ const copilot_token_1 = require("../runtime/copilot-token");
11
10
  const run_mutation_1 = require("./run-mutation");
12
11
  const providers_1 = require("../domain/providers");
13
12
  /**
@@ -33,16 +32,12 @@ async function switchProvider(args) {
33
32
  });
34
33
  }
35
34
  if ((0, providers_1.isCopilotBridgeProvider)(provider)) {
36
- (0, copilot_installer_1.assertCopilotNodeRuntimeSupported)();
37
- const installStatus = (0, copilot_installer_1.probeCopilotSdkInstall)(args.runtimesDir);
38
- if (!installStatus.installed) {
39
- throw (0, errors_1.cliError)("COPILOT_SDK_MISSING", "The optional Copilot SDK runtime is not installed.", {
40
- installDir: installStatus.installDir,
41
- packageName: installStatus.packageName,
42
- });
35
+ const githubToken = (0, copilot_token_1.readGithubToken)(args.toolHomeDir);
36
+ if (!githubToken) {
37
+ throw (0, errors_1.cliError)("COPILOT_AUTH_REQUIRED", "GitHub Copilot authentication is required. Run `codexs login copilot` first.");
43
38
  }
44
- await (0, copilot_adapter_1.readCopilotAuthState)(args.runtimesDir);
45
- const bridge = await (0, copilot_bridge_1.ensureCopilotBridge)(args.providerName, provider, args.runtimeDir, args.runtimesDir);
39
+ await (0, copilot_token_1.exchangeForCopilotToken)(githubToken);
40
+ const bridge = await (0, copilot_bridge_1.ensureCopilotBridge)(args.providerName, provider, args.runtimeDir, args.runtimesDir, args.toolHomeDir);
46
41
  const nextProvider = bridge.portChanged
47
42
  ? (0, providers_1.cleanProviderRecord)({
48
43
  ...provider,
@@ -60,9 +60,7 @@ const providers_1 = require("../domain/providers");
60
60
  const add_interactive_1 = require("../interaction/add-interactive");
61
61
  const interactive_1 = require("../interaction/interactive");
62
62
  const prompt_1 = require("../interaction/prompt");
63
- const copilot_adapter_1 = require("../runtime/copilot-adapter");
64
- const copilot_cli_1 = require("../runtime/copilot-cli");
65
- const copilot_installer_1 = require("../runtime/copilot-installer");
63
+ const copilot_token_1 = require("../runtime/copilot-token");
66
64
  const config_repo_1 = require("../storage/config-repo");
67
65
  const codex_paths_1 = require("../storage/codex-paths");
68
66
  const providers_repo_1 = require("../storage/providers-repo");
@@ -100,6 +98,7 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
100
98
  return (0, get_status_1.getStatus)(paths.codexDir, paths.configPath, paths.providersPath, paths.authPath, {
101
99
  runtimeDir: paths.runtimeDir,
102
100
  runtimesDir: paths.runtimesDir,
101
+ toolHomeDir: paths.toolHomeDir,
103
102
  });
104
103
  case "bridge-start": {
105
104
  const providerName = parsed.positionals[0] ?? null;
@@ -108,6 +107,7 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
108
107
  configPath: paths.configPath,
109
108
  runtimeDir: paths.runtimeDir,
110
109
  runtimesDir: paths.runtimesDir,
110
+ toolHomeDir: paths.toolHomeDir,
111
111
  providerName,
112
112
  runtime,
113
113
  json: ctx.options.json,
@@ -156,80 +156,41 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
156
156
  supportedUpstreams: ["copilot", "github-copilot"],
157
157
  });
158
158
  }
159
- (0, copilot_installer_1.assertCopilotNodeRuntimeSupported)();
160
- const installed = (0, copilot_installer_1.probeCopilotSdkInstall)(paths.runtimesDir);
161
- let installedNow = false;
162
- if (!installed.installed) {
163
- const confirmInstall = await runtime.confirmAction("The Copilot SDK runtime is not installed. Install it now?", {
164
- defaultValue: true,
165
- });
166
- if (!confirmInstall) {
167
- throw (0, errors_1.cliError)("COPILOT_SDK_MISSING", "The optional Copilot SDK runtime is not installed.", {
168
- installDir: installed.installDir,
169
- packageName: installed.packageName,
170
- });
171
- }
172
- runtime.writeLine("Installing Copilot SDK runtime...");
173
- (0, copilot_installer_1.installCopilotSdk)(paths.runtimesDir);
174
- installedNow = true;
175
- }
176
- const availability = (0, copilot_cli_1.checkCopilotCliAvailable)(paths.runtimesDir);
177
- try {
178
- await (0, copilot_adapter_1.readCopilotAuthState)(paths.runtimesDir);
179
- return {
180
- data: {
181
- upstream: "github-copilot",
182
- sdkInstalled: true,
183
- sdkInstalledNow: installedNow,
184
- authReady: true,
185
- loginLaunched: false,
186
- cliSource: availability.ok ? availability.source ?? null : null,
187
- cliCommand: availability.command ?? null,
188
- },
189
- };
190
- }
191
- catch (error) {
192
- const normalized = (0, errors_1.normalizeError)(error);
193
- if (normalized.code !== "COPILOT_AUTH_REQUIRED") {
194
- throw error;
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
+ };
195
172
  }
196
- }
197
- if (!availability.ok) {
198
- throw (0, errors_1.cliError)("COPILOT_CLI_MISSING", "The official Copilot CLI could not be resolved from the installed runtime or PATH.", {
199
- cause: availability.cause,
200
- source: availability.source ?? null,
201
- command: availability.command ?? null,
202
- });
203
- }
204
- try {
205
- (0, copilot_cli_1.runCopilotLogin)({ runtimesDir: paths.runtimesDir });
206
- }
207
- catch (error) {
208
- throw (0, errors_1.cliError)("COPILOT_LOGIN_LAUNCH_FAILED", "Failed to launch `copilot login`.", {
209
- cause: error instanceof Error ? error.message : String(error),
210
- });
211
- }
212
- try {
213
- await (0, copilot_adapter_1.readCopilotAuthState)(paths.runtimesDir);
214
- }
215
- catch (error) {
216
- const normalized = (0, errors_1.normalizeError)(error);
217
- if (normalized.code === "COPILOT_AUTH_REQUIRED") {
218
- throw (0, errors_1.cliError)("COPILOT_LOGIN_RECHECK_FAILED", "Copilot login completed but auth readiness recheck still failed.", {
219
- ...(normalized.details ?? {}),
220
- });
173
+ catch {
174
+ // Token is invalid, proceed with new login
221
175
  }
222
- throw error;
223
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!");
224
188
  return {
225
189
  data: {
226
190
  upstream: "github-copilot",
227
- sdkInstalled: true,
228
- sdkInstalledNow: installedNow,
229
191
  authReady: true,
230
192
  loginLaunched: true,
231
- cliSource: availability.source ?? null,
232
- cliCommand: availability.command ?? null,
193
+ authSource: "github-pat",
233
194
  },
234
195
  };
235
196
  }
@@ -267,6 +228,7 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
267
228
  authPath: paths.authPath,
268
229
  runtimeDir: paths.runtimeDir,
269
230
  runtimesDir: paths.runtimesDir,
231
+ toolHomeDir: paths.toolHomeDir,
270
232
  providerName,
271
233
  });
272
234
  }
@@ -507,6 +469,7 @@ async function handleRegisteredCommand(ctx, parsed, runtime = (0, prompt_1.creat
507
469
  authPath: paths.authPath,
508
470
  runtimeDir: paths.runtimeDir,
509
471
  runtimesDir: paths.runtimesDir,
472
+ toolHomeDir: paths.toolHomeDir,
510
473
  });
511
474
  case "migrate": {
512
475
  let codexDir = ctx.options.codexDir;
@@ -88,7 +88,7 @@ function cleanProviderRecord(record) {
88
88
  next.tags = record.tags.map((tag) => tag.trim()).filter((tag) => tag.length > 0);
89
89
  }
90
90
  if (record.runtime) {
91
- next.runtime = {
91
+ const cleanedRuntime = {
92
92
  kind: record.runtime.kind,
93
93
  upstream: record.runtime.upstream,
94
94
  bridgeHost: record.runtime.bridgeHost.trim(),
@@ -96,8 +96,11 @@ function cleanProviderRecord(record) {
96
96
  bridgePath: record.runtime.bridgePath,
97
97
  premiumRequests: record.runtime.premiumRequests,
98
98
  authSource: record.runtime.authSource,
99
- sdkInstallMode: record.runtime.sdkInstallMode,
100
99
  };
100
+ if (record.runtime.sdkInstallMode) {
101
+ cleanedRuntime.sdkInstallMode = record.runtime.sdkInstallMode;
102
+ }
103
+ next.runtime = cleanedRuntime;
101
104
  }
102
105
  return next;
103
106
  }
@@ -148,10 +151,10 @@ function isRuntimeBackedProvider(provider) {
148
151
  return Boolean(provider.runtime);
149
152
  }
150
153
  /**
151
- * Returns whether one provider uses the GitHub Copilot SDK bridge runtime.
154
+ * Returns whether one provider uses the GitHub Copilot bridge runtime.
152
155
  */
153
156
  function isCopilotBridgeProvider(provider) {
154
- return provider.runtime?.kind === "copilot-sdk-bridge";
157
+ return provider.runtime?.kind === "copilot-http-proxy" || provider.runtime?.kind === "copilot-sdk-bridge";
155
158
  }
156
159
  /**
157
160
  * Builds the canonical local bridge URL for one Copilot runtime provider.
@@ -194,7 +197,7 @@ function validateProviderRuntime(name, runtime) {
194
197
  throw new Error(`Provider "${name}" has an invalid runtime block.`);
195
198
  }
196
199
  const record = runtime;
197
- if (record.kind !== "copilot-sdk-bridge") {
200
+ if (record.kind !== "copilot-http-proxy" && record.kind !== "copilot-sdk-bridge") {
198
201
  throw new Error(`Provider "${name}" has an unsupported runtime kind.`);
199
202
  }
200
203
  if (record.upstream !== "github-copilot") {
@@ -212,10 +215,7 @@ function validateProviderRuntime(name, runtime) {
212
215
  if (record.premiumRequests !== true) {
213
216
  throw new Error(`Provider "${name}" must enable runtime premiumRequests.`);
214
217
  }
215
- if (record.authSource !== "official-sdk") {
218
+ if (record.authSource !== "github-pat" && record.authSource !== "official-sdk") {
216
219
  throw new Error(`Provider "${name}" has an invalid runtime authSource.`);
217
220
  }
218
- if (record.sdkInstallMode !== "lazy") {
219
- throw new Error(`Provider "${name}" has an invalid runtime sdkInstallMode.`);
220
- }
221
221
  }