codex-to-im 0.1.1 → 0.1.2

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.md CHANGED
@@ -92,6 +92,18 @@ If you forget the current address, run:
92
92
  codex-to-im url
93
93
  ```
94
94
 
95
+ Check the current local service state:
96
+
97
+ ```bash
98
+ codex-to-im status
99
+ ```
100
+
101
+ Stop the background UI and bridge:
102
+
103
+ ```bash
104
+ codex-to-im stop
105
+ ```
106
+
95
107
  ## Main Workflow
96
108
 
97
109
  1. Open the workbench
@@ -127,6 +139,18 @@ If creating a new session fails with `Not inside a trusted directory`, either:
127
139
  - change the default working directory to a trusted Git repo, or
128
140
  - enable `Allow Codex outside trusted Git repos` in the basic settings and restart the bridge
129
141
 
142
+ ## Update
143
+
144
+ On Windows, `npm update -g codex-to-im` can fail with `EBUSY` if the background UI or bridge is still running from the global install directory.
145
+
146
+ Recommended update flow:
147
+
148
+ ```bash
149
+ codex-to-im stop
150
+ npm update -g codex-to-im
151
+ codex-to-im
152
+ ```
153
+
130
154
  ## Optional Codex Integration
131
155
 
132
156
  The repo still includes a lightweight optional integration under `SKILL.md`.
package/README_CN.md CHANGED
@@ -92,6 +92,18 @@ http://127.0.0.1:4781
92
92
  codex-to-im url
93
93
  ```
94
94
 
95
+ 查看当前本地服务状态:
96
+
97
+ ```bash
98
+ codex-to-im status
99
+ ```
100
+
101
+ 停止后台 UI 和 bridge:
102
+
103
+ ```bash
104
+ codex-to-im stop
105
+ ```
106
+
95
107
  ## 主流程
96
108
 
97
109
  1. 打开工作台
@@ -127,6 +139,18 @@ codex-to-im url
127
139
  - 把默认工作目录改成一个你已经信任的 Git 仓库
128
140
  - 或在基础配置里打开“允许在未信任 Git 目录运行 Codex”,然后重启 Bridge
129
141
 
142
+ ## 更新
143
+
144
+ Windows 上如果后台 UI 或 bridge 仍在运行,`npm update -g codex-to-im` 可能会因为安装目录被占用而报 `EBUSY`。
145
+
146
+ 推荐更新流程:
147
+
148
+ ```bash
149
+ codex-to-im stop
150
+ npm update -g codex-to-im
151
+ codex-to-im
152
+ ```
153
+
130
154
  ## 可选 Codex 集成
131
155
 
132
156
  仓库里仍然保留了一个很薄的可选集成,定义在 `SKILL.md`。
package/dist/cli.mjs CHANGED
@@ -42,6 +42,15 @@ function readJsonFile(filePath, fallback) {
42
42
  return fallback;
43
43
  }
44
44
  }
45
+ function readPid(filePath) {
46
+ try {
47
+ const raw = fs2.readFileSync(filePath, "utf-8").trim();
48
+ const pid = Number(raw);
49
+ return Number.isFinite(pid) ? pid : void 0;
50
+ } catch {
51
+ return void 0;
52
+ }
53
+ }
45
54
  function isProcessAlive(pid) {
46
55
  if (!pid) return false;
47
56
  try {
@@ -62,6 +71,22 @@ function getCurrentUiServerUrl() {
62
71
  if (!status?.port) return void 0;
63
72
  return getUiServerUrl(status.port);
64
73
  }
74
+ function getBridgeStatus() {
75
+ const status = readJsonFile(bridgeStatusFile, { running: false });
76
+ const pid = readPid(bridgePidFile) ?? status.pid;
77
+ if (!isProcessAlive(pid)) {
78
+ return {
79
+ ...status,
80
+ pid,
81
+ running: false
82
+ };
83
+ }
84
+ return {
85
+ ...status,
86
+ pid,
87
+ running: status.running ?? true
88
+ };
89
+ }
65
90
  function getUiServerStatus() {
66
91
  const status = readJsonFile(uiStatusFile, { running: false, port: uiPort });
67
92
  if (!isProcessAlive(status.pid)) {
@@ -92,6 +117,33 @@ async function waitForUiServer(timeoutMs = 15e3) {
92
117
  }
93
118
  return getUiServerStatus();
94
119
  }
120
+ async function stopBridge() {
121
+ const status = getBridgeStatus();
122
+ if (!status.pid || !isProcessAlive(status.pid)) {
123
+ return { ...status, running: false };
124
+ }
125
+ if (process.platform === "win32") {
126
+ await new Promise((resolve) => {
127
+ const killer = spawn("cmd", ["/c", "taskkill", "/PID", String(status.pid), "/T", "/F"], {
128
+ stdio: "ignore"
129
+ });
130
+ killer.on("exit", () => resolve());
131
+ killer.on("error", () => resolve());
132
+ });
133
+ } else {
134
+ try {
135
+ process.kill(status.pid, "SIGTERM");
136
+ } catch {
137
+ }
138
+ }
139
+ const startedAt = Date.now();
140
+ while (Date.now() - startedAt < 1e4) {
141
+ const next = getBridgeStatus();
142
+ if (!next.running) return next;
143
+ await sleep(300);
144
+ }
145
+ return getBridgeStatus();
146
+ }
95
147
  async function ensureUiServerRunning() {
96
148
  ensureDirs();
97
149
  const current = getUiServerStatus();
@@ -118,6 +170,46 @@ async function ensureUiServerRunning() {
118
170
  }
119
171
  return status;
120
172
  }
173
+ async function stopUiServer() {
174
+ const status = getUiServerStatus();
175
+ if (!status.pid || !isProcessAlive(status.pid)) {
176
+ const next2 = { ...status, running: false };
177
+ writeUiServerStatus(next2);
178
+ return next2;
179
+ }
180
+ if (process.platform === "win32") {
181
+ await new Promise((resolve) => {
182
+ const killer = spawn("cmd", ["/c", "taskkill", "/PID", String(status.pid), "/T", "/F"], {
183
+ stdio: "ignore"
184
+ });
185
+ killer.on("exit", () => resolve());
186
+ killer.on("error", () => resolve());
187
+ });
188
+ } else {
189
+ try {
190
+ process.kill(status.pid, "SIGTERM");
191
+ } catch {
192
+ }
193
+ }
194
+ const startedAt = Date.now();
195
+ while (Date.now() - startedAt < 1e4) {
196
+ const next2 = getUiServerStatus();
197
+ if (!next2.running) {
198
+ writeUiServerStatus({ ...next2, running: false });
199
+ return { ...next2, running: false };
200
+ }
201
+ await sleep(300);
202
+ }
203
+ const next = getUiServerStatus();
204
+ if (!next.running) {
205
+ writeUiServerStatus({ ...next, running: false });
206
+ }
207
+ return next;
208
+ }
209
+ function writeUiServerStatus(status) {
210
+ ensureDirs();
211
+ fs2.writeFileSync(uiStatusFile, JSON.stringify(status, null, 2), "utf-8");
212
+ }
121
213
  function openBrowser(url) {
122
214
  if (process.platform === "win32") {
123
215
  const child2 = spawn("cmd", ["/c", "start", "", url], { detached: true, stdio: "ignore" });
@@ -171,8 +263,29 @@ async function main() {
171
263
  `);
172
264
  return;
173
265
  }
266
+ case "stop": {
267
+ const bridge = await stopBridge();
268
+ const ui = await stopUiServer();
269
+ process.stdout.write(
270
+ `Stopped services. UI running=${ui.running ? "yes" : "no"}, Bridge running=${bridge.running ? "yes" : "no"}
271
+ `
272
+ );
273
+ return;
274
+ }
275
+ case "status": {
276
+ const ui = getUiServerStatus();
277
+ const bridge = getBridgeStatus();
278
+ const url = getCurrentUiServerUrl();
279
+ process.stdout.write(
280
+ [
281
+ `UI: ${ui.running ? "running" : "stopped"}${url ? ` (${url})` : ""}`,
282
+ `Bridge: ${bridge.running ? "running" : "stopped"}`
283
+ ].join("\n") + "\n"
284
+ );
285
+ return;
286
+ }
174
287
  default:
175
- process.stdout.write("Usage: codex-to-im [open|url|share-feishu]\n");
288
+ process.stdout.write("Usage: codex-to-im [open|url|share-feishu|stop|status]\n");
176
289
  }
177
290
  }
178
291
  main().catch((error) => {
@@ -147,6 +147,18 @@ http://127.0.0.1:4781
147
147
  codex-to-im url
148
148
  ```
149
149
 
150
+ 查看当前本地服务状态:
151
+
152
+ ```powershell
153
+ codex-to-im status
154
+ ```
155
+
156
+ 停止后台 UI 和 bridge:
157
+
158
+ ```powershell
159
+ codex-to-im stop
160
+ ```
161
+
150
162
  ## 6. 在目标机上的首次配置
151
163
 
152
164
  进入本地工作台后,按这个顺序做:
@@ -271,6 +283,23 @@ codex-to-im url
271
283
  - 可选 Codex 集成不是主安装路径
272
284
  - 当前主路径是“本地工作台 + IM 配置 + Bridge 启动”
273
285
 
286
+ ## 10.1 Windows 上的更新方式
287
+
288
+ 如果后台 UI 或 bridge 仍在运行,Windows 可能会锁住 `%APPDATA%\\npm\\node_modules\\codex-to-im`,导致:
289
+
290
+ - `npm update -g codex-to-im`
291
+ - `npm uninstall -g codex-to-im`
292
+
293
+ 出现 `EBUSY` 或 `resource busy or locked`。
294
+
295
+ 推荐更新步骤:
296
+
297
+ ```powershell
298
+ codex-to-im stop
299
+ npm update -g codex-to-im
300
+ codex-to-im
301
+ ```
302
+
274
303
  ## 11. 发布方注意事项
275
304
 
276
305
  如果你要把这个包发布到 npm,当前实现要求:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-to-im",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Installable Codex-to-IM bridge with local setup UI and background service",
5
5
  "license": "MIT",
6
6
  "type": "module",