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 +35 -1
- package/README_CN.md +35 -1
- package/config.env.example +12 -15
- package/dist/cli.mjs +114 -1
- package/dist/daemon.mjs +7118 -6870
- package/dist/ui-server.mjs +1430 -353
- package/docs/install-windows.md +40 -3
- package/package.json +9 -12
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,
|
|
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`
|
|
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`。
|
package/config.env.example
CHANGED
|
@@ -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
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
#
|
|
11
|
-
|
|
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
|
-
#
|
|
48
|
-
|
|
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) => {
|