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 +24 -0
- package/README_CN.md +24 -0
- package/dist/cli.mjs +114 -1
- package/docs/install-windows.md +29 -0
- package/package.json +1 -1
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) => {
|
package/docs/install-windows.md
CHANGED
|
@@ -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,当前实现要求:
|