codex-to-im 0.1.0 → 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
@@ -38,7 +38,17 @@ Windows host installation guide: [docs/install-windows.md](D:/codex/Claude-to-IM
38
38
  ### Prerequisites
39
39
 
40
40
  - Node.js 20+
41
- - If you use the `codex` or `auto` runtime, install Codex CLI first
41
+ - If you use the `codex` or `auto` runtime, complete Codex authentication under the same Windows user
42
+
43
+ `codex-to-im` now ships with the required `@openai/codex-sdk` / Codex CLI platform dependency, so you do not need to install a separate global Codex CLI just to run the bridge.
44
+
45
+ You still need Codex credentials to be available for the current user. Any of these is sufficient:
46
+
47
+ - a logged-in Codex Windows App
48
+ - an existing Codex CLI login state
49
+ - `CTI_CODEX_API_KEY`, `CODEX_API_KEY`, or `OPENAI_API_KEY`
50
+
51
+ If the machine does not have any Codex login state yet, the simplest path is still to install the global CLI once and log in:
42
52
 
43
53
  ```bash
44
54
  npm install -g @openai/codex
@@ -82,6 +92,18 @@ If you forget the current address, run:
82
92
  codex-to-im url
83
93
  ```
84
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
+
85
107
  ## Main Workflow
86
108
 
87
109
  1. Open the workbench
@@ -117,6 +139,18 @@ If creating a new session fails with `Not inside a trusted directory`, either:
117
139
  - change the default working directory to a trusted Git repo, or
118
140
  - enable `Allow Codex outside trusted Git repos` in the basic settings and restart the bridge
119
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
+
120
154
  ## Optional Codex Integration
121
155
 
122
156
  The repo still includes a lightweight optional integration under `SKILL.md`.
package/README_CN.md CHANGED
@@ -38,7 +38,17 @@ Windows 主机安装说明见:[docs/install-windows.md](D:/codex/Claude-to-IM-
38
38
  ### 依赖
39
39
 
40
40
  - Node.js 20+
41
- - 如果使用 `codex` 或 `auto` 运行时:先安装 Codex CLI
41
+ - 如果使用 `codex` 或 `auto` 运行时:需要在同一 Windows 用户下完成 Codex 认证
42
+
43
+ `codex-to-im` 当前已经随包带上运行所需的 `@openai/codex-sdk` / Codex CLI 平台依赖,正常使用 bridge 时不要求你额外再全局安装一份 Codex CLI。
44
+
45
+ 但你仍然需要让 Codex 在当前用户下可用。推荐满足以下任一条件:
46
+
47
+ - 已安装并登录过 Codex Windows App
48
+ - 已经有可用的 Codex CLI 登录态
49
+ - 已配置 `CTI_CODEX_API_KEY`、`CODEX_API_KEY` 或 `OPENAI_API_KEY`
50
+
51
+ 如果你机器上还没有任何 Codex 登录态,最直接的做法仍然是临时安装一次全局 Codex CLI 并登录:
42
52
 
43
53
  ```bash
44
54
  npm install -g @openai/codex
@@ -82,6 +92,18 @@ http://127.0.0.1:4781
82
92
  codex-to-im url
83
93
  ```
84
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
+
85
107
  ## 主流程
86
108
 
87
109
  1. 打开工作台
@@ -117,6 +139,18 @@ codex-to-im url
117
139
  - 把默认工作目录改成一个你已经信任的 Git 仓库
118
140
  - 或在基础配置里打开“允许在未信任 Git 目录运行 Codex”,然后重启 Bridge
119
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
+
120
154
  ## 可选 Codex 集成
121
155
 
122
156
  仓库里仍然保留了一个很薄的可选集成,定义在 `SKILL.md`。
@@ -2,16 +2,13 @@
2
2
  # Copy to ~/.codex-to-im/config.env and edit
3
3
 
4
4
  # Runtime backend: claude | codex | auto
5
- # claude (default) — uses Claude Code CLI + @anthropic-ai/claude-agent-sdk
6
- # codex — uses @openai/codex-sdk (auth: codex auth login, or OPENAI_API_KEY)
5
+ # claude — uses Claude Code CLI + @anthropic-ai/claude-agent-sdk
6
+ # codex (default) — uses @openai/codex-sdk (auth: codex auth login, or OPENAI_API_KEY)
7
7
  # auto — tries Claude first, falls back to Codex if CLI not found
8
- # If you plan to use the codex runtime, install Codex CLI first:
9
- # npm install -g @openai/codex
10
- # codex auth login
11
- CTI_RUNTIME=claude
12
-
13
- # Enabled channels (comma-separated: telegram,discord,feishu,qq,weixin)
14
- CTI_ENABLED_CHANNELS=telegram
8
+ CTI_RUNTIME=codex
9
+
10
+ # Enabled channels (comma-separated: telegram,discord,feishu,qq,weixin)
11
+ CTI_ENABLED_CHANNELS=feishu
15
12
 
16
13
  # Default working directory for Codex / bridge sessions
17
14
  CTI_DEFAULT_WORKDIR=/path/to/your/project
@@ -39,13 +36,13 @@ CTI_DEFAULT_MODE=code
39
36
  # ANTHROPIC_BASE_URL=https://your-api-provider.com/v1
40
37
  # ANTHROPIC_AUTH_TOKEN=your-auth-token
41
38
 
42
- # ── Codex auth (optional — only if not using `codex auth login`) ──
43
- # Priority: CTI_CODEX_API_KEY > CODEX_API_KEY > OPENAI_API_KEY
44
- # CTI_CODEX_API_KEY=
45
- # CTI_CODEX_BASE_URL=
39
+ # ── Codex auth (optional — only if not using `codex auth login`) ──
40
+ # Priority: CTI_CODEX_API_KEY > CODEX_API_KEY > OPENAI_API_KEY
41
+ # CTI_CODEX_API_KEY=
42
+ # CTI_CODEX_BASE_URL=
46
43
  # Allow Codex to run when CTI_DEFAULT_WORKDIR is not inside a trusted Git repo.
47
- # Keep this off unless you intentionally want Codex to work in arbitrary folders.
48
- # CTI_CODEX_SKIP_GIT_REPO_CHECK=true
44
+ # This project defaults it to true so first-time setup works more smoothly.
45
+ CTI_CODEX_SKIP_GIT_REPO_CHECK=true
49
46
 
50
47
  # ── Telegram ──
51
48
  CTI_TG_BOT_TOKEN=your-telegram-bot-token
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) => {