@minniexcode/codex-switch 0.0.7 → 0.0.9

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.
@@ -0,0 +1,80 @@
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.getCopilotBridgeStatePath = getCopilotBridgeStatePath;
37
+ exports.readCopilotBridgeState = readCopilotBridgeState;
38
+ exports.writeCopilotBridgeState = writeCopilotBridgeState;
39
+ exports.clearCopilotBridgeState = clearCopilotBridgeState;
40
+ const fs = __importStar(require("node:fs"));
41
+ const os = __importStar(require("node:os"));
42
+ const path = __importStar(require("node:path"));
43
+ const fs_utils_1 = require("./fs-utils");
44
+ /**
45
+ * Returns the user-level runtime state file used by Copilot bridge helpers.
46
+ */
47
+ function getCopilotBridgeStatePath() {
48
+ const override = process.env.CODEX_SWITCH_RUNTIME_STATE_DIR;
49
+ if (override && override.trim() !== "") {
50
+ return path.join(path.resolve(override), "copilot-bridge-state.json");
51
+ }
52
+ return path.join(os.homedir(), ".codex-switch", "runtime", "copilot-bridge-state.json");
53
+ }
54
+ /**
55
+ * Reads the Copilot bridge state manifest when present.
56
+ */
57
+ function readCopilotBridgeState() {
58
+ const statePath = getCopilotBridgeStatePath();
59
+ if (!fs.existsSync(statePath)) {
60
+ return null;
61
+ }
62
+ return JSON.parse(fs.readFileSync(statePath, "utf8"));
63
+ }
64
+ /**
65
+ * Persists the Copilot bridge state manifest.
66
+ */
67
+ function writeCopilotBridgeState(state) {
68
+ const statePath = getCopilotBridgeStatePath();
69
+ (0, fs_utils_1.ensureDir)(path.dirname(statePath));
70
+ (0, fs_utils_1.writeTextFileAtomic)(statePath, `${JSON.stringify(state, null, 2)}\n`);
71
+ }
72
+ /**
73
+ * Deletes the Copilot bridge state manifest when present.
74
+ */
75
+ function clearCopilotBridgeState() {
76
+ const statePath = getCopilotBridgeStatePath();
77
+ if (fs.existsSync(statePath)) {
78
+ fs.rmSync(statePath, { force: true });
79
+ }
80
+ }
@@ -0,0 +1,132 @@
1
+ # codex-switch `0.0.8` 设计文档
2
+
3
+ ## 文档信息
4
+
5
+ - 文档类型:详细设计文档
6
+ - 适用版本:`0.0.8`
7
+ - 目标范围:`0.0.7 -> 0.0.8`
8
+ - 对应 PRD:[`../PRD/codex-switch-prd-v0.0.8.md`](../PRD/codex-switch-prd-v0.0.8.md)
9
+
10
+ ## 1. 文档目标
11
+
12
+ 本设计把 `0.0.8` 的 Copilot runtime integration 收口到可实现的范围:
13
+
14
+ - Copilot provider 的 schema 如何表达
15
+ - SDK 的 lazy-load 与自动安装如何落地
16
+ - `add`、`switch`、`status`、`doctor` 分别承担什么职责
17
+ - `providers.json`、`config.toml`、`auth.json` 与 runtime manifest 的边界是什么
18
+ - 代码和测试应该落到哪些模块
19
+
20
+ ## 2. 版本定位
21
+
22
+ `0.0.8` 是第一个 runtime-backed provider 版本。它不会把 `codex-switch` 扩成通用插件系统,而是先用 GitHub Copilot 把 runtime integration 这条线真正跑通。
23
+
24
+ ## 3. 数据模型
25
+
26
+ `ProviderRecord` 在 `0.0.8` 中扩展为兼容 direct provider 和 runtime-backed provider:
27
+
28
+ ```ts
29
+ type ProviderRuntime =
30
+ | {
31
+ kind: "copilot-sdk-bridge";
32
+ upstream: "github-copilot";
33
+ bridgeHost: string;
34
+ bridgePort: number;
35
+ bridgePath: "/v1";
36
+ premiumRequests: true;
37
+ authSource: "official-sdk";
38
+ sdkInstallMode: "lazy";
39
+ };
40
+ ```
41
+
42
+ 语义:
43
+
44
+ - 无 `runtime`:现有 direct provider
45
+ - `runtime.kind = "copilot-sdk-bridge"`:Copilot provider
46
+ - Copilot provider 的 `apiKey` 是本地 bridge 的 shared secret,不是 GitHub token
47
+ - `baseUrl` 必须与 `http://<bridgeHost>:<bridgePort>/v1` 一致
48
+
49
+ ## 4. 命令设计
50
+
51
+ 扩展现有命令面,不新增 `copilot` 或 `proxy` 子命令:
52
+
53
+ - `codexs add <provider> --copilot --profile <name> [--bridge-host <host>] [--bridge-port <port>] [--bridge-api-key <secret>] [--install-copilot-sdk]`
54
+ - `codexs switch <provider>`
55
+ - `codexs status`
56
+ - `codexs doctor`
57
+
58
+ 规则:
59
+
60
+ - `--copilot` 与 direct provider 输入模式互斥
61
+ - `--bridge-host` 默认 `127.0.0.1`
62
+ - `--bridge-port` 默认 `4141`
63
+ - `--bridge-api-key` 若缺失则自动生成本地 secret
64
+ - SDK 自动安装只允许发生在 `add --copilot`
65
+
66
+ ## 5. Runtime 模块
67
+
68
+ 新增模块:
69
+
70
+ - `src/runtime/copilot-sdk-loader.ts`
71
+ - `src/runtime/copilot-installer.ts`
72
+ - `src/runtime/copilot-adapter.ts`
73
+ - `src/runtime/copilot-bridge.ts`
74
+ - `src/storage/runtime-state-repo.ts`
75
+
76
+ 职责:
77
+
78
+ - loader:按用户级 runtime 目录懒加载 SDK
79
+ - installer:探测与安装 SDK
80
+ - adapter:优先按官方 `CopilotClient -> createSession -> sendAndWait` 形态包装 Copilot SDK 的 probe / auth / request 能力
81
+ - bridge:管理本地 OpenAI 兼容 bridge 的状态
82
+ - runtime state repo:保存 bridge runtime manifest
83
+
84
+ 前置条件:
85
+
86
+ - 本地官方 SDK 依赖已安装并可加载
87
+ - 用户机器上已有可用的 Copilot CLI / login 状态,`codex-switch` 不接管上游账号登录
88
+
89
+ ## 6. 运行态与文件边界
90
+
91
+ - `providers.json`:保存 Copilot provider 与 bridge 配置
92
+ - `config.toml`:保存 bridge 的 `base_url` 与 `env_key`
93
+ - `auth.json`:保存 Codex 到 bridge 的 shared secret
94
+ - runtime manifest:保存 bridge 进程状态,不进入 managed backup 事务
95
+
96
+ ## 7. `switch` 时序
97
+
98
+ 1. 读取 provider
99
+ 2. 校验 `runtime` 配置
100
+ 3. probe SDK
101
+ 4. 缺失则直接报错
102
+ 5. 检查 Copilot auth state
103
+ 6. 启动或复用 bridge
104
+ 7. healthcheck 通过
105
+ 8. 再进入现有 `config.toml + auth.json` 写入事务
106
+ 9. 文件写入失败时回滚,并对本次新启动 bridge 做 best-effort 清理
107
+
108
+ ## 8. 错误语义
109
+
110
+ 新增错误码:
111
+
112
+ - `COPILOT_SDK_MISSING`
113
+ - `COPILOT_SDK_INSTALL_FAILED`
114
+ - `COPILOT_SDK_INSTALL_REQUIRES_TTY`
115
+ - `COPILOT_SDK_UNSUPPORTED`
116
+ - `COPILOT_AUTH_REQUIRED`
117
+ - `COPILOT_PREMIUM_UNAVAILABLE`
118
+ - `BRIDGE_PORT_CONFLICT`
119
+ - `BRIDGE_START_FAILED`
120
+ - `BRIDGE_HEALTHCHECK_FAILED`
121
+ - `RUNTIME_PROVIDER_INVALID`
122
+ - `PROVIDER_BASE_URL_MISMATCH`
123
+
124
+ ## 9. 测试
125
+
126
+ 需要覆盖:
127
+
128
+ - `add --copilot` 参数与写入
129
+ - 非交互缺失 SDK 的失败路径
130
+ - `--install-copilot-sdk` 的允许安装路径
131
+ - Copilot provider 的 `status` / `doctor`
132
+ - direct provider 回归
@@ -0,0 +1,182 @@
1
+ # codex-switch `0.0.9` Design
2
+
3
+ ## 1. Purpose
4
+
5
+ This design turns the `0.0.9` PRD into an implementable CLI and runtime spec for the local Copilot bridge path.
6
+
7
+ The release goal is not to introduce a background service product. It is to make the existing local Copilot bridge safe to start, stop, inspect, and reuse from a single-process user-space workflow.
8
+
9
+ ## 2. Scope
10
+
11
+ ### In scope
12
+
13
+ - `codexs bridge start [provider]`
14
+ - `codexs bridge stop [provider]`
15
+ - `codexs bridge status [provider]`
16
+ - single-instance bridge reuse and replacement
17
+ - detached user-space bridge workers
18
+ - 5-digit default port selection with recovery when the preferred port is occupied
19
+ - runtime state persistence outside the managed backup transaction boundary
20
+ - Copilot-only target selection helpers
21
+ - switch-time bridge reuse through the same lifecycle code path
22
+
23
+ ### Out of scope
24
+
25
+ - boot autostart
26
+ - login autostart
27
+ - Windows Service support
28
+ - multiple concurrent managed bridge instances
29
+ - non-Copilot runtime families
30
+ - any new auth storage scheme for upstream Copilot login state
31
+
32
+ ## 3. Command Surface
33
+
34
+ ### 3.1 New commands
35
+
36
+ - `codexs bridge start [provider]`
37
+ - `codexs bridge stop [provider]`
38
+ - `codexs bridge status [provider]`
39
+
40
+ The command registry must add nested tokens under the public `bridge` namespace, with the longest token sequence winning during argument resolution.
41
+
42
+ ### 3.2 Command semantics
43
+
44
+ - `start` is a write command
45
+ - `stop` is an operational write/recovery command
46
+ - `status` is a read command
47
+
48
+ ### 3.3 Target resolution
49
+
50
+ For `bridge start`, provider resolution proceeds in this order:
51
+
52
+ 1. explicit provider argument
53
+ 2. current active provider, if it is a Copilot bridge provider
54
+ 3. sole configured Copilot bridge provider
55
+ 4. interactive Copilot-only provider picker in a TTY
56
+
57
+ If none of those paths resolves a target, the command fails with a design-level bridge target error.
58
+
59
+ For `bridge stop` and `bridge status`, resolution prefers the current runtime-state instance when present. An explicit provider argument acts as a guard and must not silently target a different provider instance.
60
+
61
+ ## 4. Runtime Model
62
+
63
+ ### 4.1 Single-instance policy
64
+
65
+ The bridge runtime is a single detached user-space worker.
66
+
67
+ - If the bridge is already healthy for the same provider, `start` and `switch` reuse it.
68
+ - If the bridge is healthy for a different provider, the current managed instance is stopped before a new one is started.
69
+ - Runtime state is stored separately from the managed file backup transaction, so the bridge can survive or be inspected outside file rollbacks.
70
+
71
+ ### 4.2 Health checks
72
+
73
+ Bridge startup must verify the worker by probing the local `/healthz` endpoint before the command reports success.
74
+
75
+ Bridge status must report:
76
+
77
+ - the last known runtime state
78
+ - whether the provider binding matches the expected provider
79
+ - whether the live worker is healthy
80
+ - the reason if the state is stale or mismatched
81
+
82
+ ### 4.3 Stop behavior
83
+
84
+ `bridge stop` is protection-first.
85
+
86
+ - It stops the managed worker when one exists.
87
+ - It clears the runtime-state manifest.
88
+ - It must not mutate `providers.json`, `config.toml`, or `auth.json`.
89
+ - If the caller supplies a provider that does not match the tracked runtime state, the command fails rather than stopping the wrong instance.
90
+
91
+ ## 5. Port Policy
92
+
93
+ - The default bridge host is `127.0.0.1`.
94
+ - The default bridge port is fixed at `41415`.
95
+ - The port must always remain in the 5-digit range.
96
+ - If the preferred port is occupied, the runtime must automatically select another free 5-digit port.
97
+ - Any recovered port must be persisted back into the provider record and the corresponding `config.toml` runtime projection before the command reports success.
98
+ - The persisted port must match the live worker port.
99
+
100
+ The key design constraint is that runtime state and managed config cannot diverge after a successful start.
101
+
102
+ ## 6. Persistence Boundaries
103
+
104
+ `bridge start` may update:
105
+
106
+ - `providers.json`
107
+ - the matching `config.toml` bridge projection
108
+ - runtime state manifest
109
+
110
+ `bridge start` must not rewrite:
111
+
112
+ - `auth.json`
113
+ - active profile state except when invoked through the shared `switch` flow
114
+
115
+ `switch` keeps using the same bridge lifecycle path and performs its own auth/config transaction afterward.
116
+
117
+ Runtime state is explicitly outside backup transactions. If file persistence fails after the worker has started, the implementation must clean up the new worker unless it was reused from a previous healthy instance.
118
+
119
+ ## 7. Error Model
120
+
121
+ The implementation should reuse existing error families where possible and add bridge-specific errors for unresolved target and provider mismatch cases.
122
+
123
+ Relevant bridge/runtime errors:
124
+
125
+ - `BRIDGE_TARGET_UNRESOLVED`
126
+ - `BRIDGE_PROVIDER_MISMATCH`
127
+ - `BRIDGE_PORT_CONFLICT`
128
+ - `BRIDGE_START_FAILED`
129
+ - `BRIDGE_HEALTHCHECK_FAILED`
130
+ - `RUNTIME_PROVIDER_INVALID`
131
+ - `PROVIDER_BASE_URL_MISMATCH`
132
+
133
+ The design intent is that the bridge commands fail clearly before mutating managed files when the provider cannot be resolved or the provider/runtime binding is inconsistent.
134
+
135
+ ## 8. Implementation Shape
136
+
137
+ The implementation should be split into three layers:
138
+
139
+ - command wiring in `src/commands/`
140
+ - app use cases in `src/app/`
141
+ - worker and healthcheck code in `src/runtime/`
142
+
143
+ The bridge-specific app layer should own:
144
+
145
+ - target resolution
146
+ - port recovery
147
+ - provider/config projection updates
148
+ - runtime-state cleanup
149
+
150
+ The runtime layer should own:
151
+
152
+ - worker spawning
153
+ - healthcheck probing
154
+ - detached-process cleanup
155
+ - runtime-state manifest read/write
156
+
157
+ ## 9. Tests
158
+
159
+ Minimum coverage for `0.0.9`:
160
+
161
+ - command parsing and help for `bridge start|stop|status`
162
+ - longest-token resolution for nested commands
163
+ - explicit provider, active-provider, sole-provider, and TTY fallback selection
164
+ - current-runtime-state preference for stop/status
165
+ - mismatch-guard behavior for stop/status
166
+ - single-instance reuse and replacement
167
+ - stale runtime-state cleanup
168
+ - port conflict recovery
169
+ - persisted port updates
170
+ - cleanup when file mutation fails after a successful bridge start
171
+ - direct-provider regressions
172
+
173
+ ## 10. Release Criteria
174
+
175
+ `0.0.9` is complete when the following are true:
176
+
177
+ - Copilot bridge can be managed manually with `bridge start|stop|status`
178
+ - `switch` reuses the same lifecycle path
179
+ - the default bridge port is 5 digits and port conflicts recover automatically
180
+ - runtime state is durable enough for status and stop behavior, but remains outside managed backup transactions
181
+ - direct providers keep their existing behavior
182
+