@lapage/codex-telegram-bridge 0.1.0 → 0.1.1
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/.env.example +21 -9
- package/README.md +20 -46
- package/dist/codex-session.js +212 -39
- package/dist/config.js +2 -12
- package/dist/env.js +2 -9
- package/dist/telegram-bridge.js +290 -150
- package/dist/text.js +113 -137
- package/package.json +2 -2
- package/dist/tmux.js +0 -25
package/.env.example
CHANGED
|
@@ -1,17 +1,29 @@
|
|
|
1
|
+
# Required. Telegram bot token from BotFather.
|
|
1
2
|
TELEGRAM_BOT_TOKEN=123456:replace_me
|
|
3
|
+
|
|
4
|
+
# Required. Allowed Telegram user IDs, comma-separated.
|
|
2
5
|
TELEGRAM_ALLOWED_USER_IDS=123456789
|
|
6
|
+
|
|
7
|
+
# Codex workspace path. Options: ~, /absolute/path.
|
|
3
8
|
CODEX_CWD=~
|
|
9
|
+
|
|
10
|
+
# Codex binary. Options: codex, /absolute/path/to/codex.
|
|
4
11
|
CODEX_COMMAND=codex
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
CODEX_STARTUP_DELAY_MS=1500
|
|
12
|
+
|
|
13
|
+
# Approval mode. Options: never, on-request, on-failure, untrusted.
|
|
14
|
+
CODEX_APPROVAL_POLICY=never
|
|
15
|
+
|
|
16
|
+
# Sandbox mode. Options: danger-full-access, workspace-write, read-only.
|
|
17
|
+
CODEX_SANDBOX=danger-full-access
|
|
18
|
+
|
|
19
|
+
# Stream edit throttle in ms. Options: 500-1500 typical.
|
|
14
20
|
STREAM_EDIT_INTERVAL_MS=650
|
|
21
|
+
|
|
22
|
+
# Min chars before stream edit. Options: 10-100 typical.
|
|
15
23
|
STREAM_MIN_CHANGE_CHARS=24
|
|
24
|
+
|
|
25
|
+
# Telegram typing interval in ms. Options: 3000-5000 typical.
|
|
16
26
|
TYPING_INTERVAL_MS=4000
|
|
27
|
+
|
|
28
|
+
# Max Telegram chunk size. Options: <=3900 recommended.
|
|
17
29
|
MAX_TELEGRAM_CHARS=3500
|
package/README.md
CHANGED
|
@@ -2,20 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
Control a local **Codex CLI** session from Telegram without opening any inbound ports to the internet.
|
|
4
4
|
|
|
5
|
-
This package is useful when Codex runs on a home PC, workstation, homelab box, or private server and you want a simple mobile chat interface. The bridge starts
|
|
5
|
+
This package is useful when Codex runs on a home PC, workstation, homelab box, or private server and you want a simple mobile chat interface. The bridge starts `codex app-server` over stdio, receives Telegram messages from an allowlisted user, sends them into Codex, and streams Codex output back to Telegram.
|
|
6
6
|
|
|
7
7
|
## Why Use This
|
|
8
8
|
|
|
9
9
|
- No public SSH, HTTP, webhook, tunnel, or reverse proxy is required.
|
|
10
10
|
- Telegram provides the outbound connection path from your machine.
|
|
11
|
-
- Codex
|
|
11
|
+
- Codex uses the app-server JSON-RPC protocol instead of terminal screen scraping.
|
|
12
|
+
- Stdio transport stays local-only and avoids exposing a network listener.
|
|
12
13
|
- Access is restricted to the Telegram user IDs you allowlist.
|
|
13
14
|
|
|
14
15
|
## Requirements
|
|
15
16
|
|
|
16
17
|
- Node.js 22 or newer
|
|
17
18
|
- npm
|
|
18
|
-
- `tmux`
|
|
19
19
|
- Codex CLI available on `PATH`
|
|
20
20
|
- A Telegram bot token from [@BotFather](https://t.me/BotFather)
|
|
21
21
|
- Your numeric Telegram user ID, for example from [@userinfobot](https://t.me/userinfobot)
|
|
@@ -25,26 +25,9 @@ Check prerequisites:
|
|
|
25
25
|
```sh
|
|
26
26
|
node --version
|
|
27
27
|
npm --version
|
|
28
|
-
tmux -V
|
|
29
28
|
codex --version
|
|
30
29
|
```
|
|
31
30
|
|
|
32
|
-
Install `tmux` if it is missing:
|
|
33
|
-
|
|
34
|
-
```sh
|
|
35
|
-
# macOS
|
|
36
|
-
brew install tmux
|
|
37
|
-
|
|
38
|
-
# Ubuntu / Debian
|
|
39
|
-
sudo apt update && sudo apt install -y tmux
|
|
40
|
-
|
|
41
|
-
# Fedora
|
|
42
|
-
sudo dnf install -y tmux
|
|
43
|
-
|
|
44
|
-
# Arch Linux
|
|
45
|
-
sudo pacman -S tmux
|
|
46
|
-
```
|
|
47
|
-
|
|
48
31
|
## Quick Start
|
|
49
32
|
|
|
50
33
|
Install the package globally:
|
|
@@ -87,6 +70,10 @@ codex-telegram-bridge
|
|
|
87
70
|
|
|
88
71
|
Open your Telegram bot chat and send `/status`. Any normal message after that is sent to Codex as a prompt.
|
|
89
72
|
|
|
73
|
+
Codex output updates when app-server reports completed items. The bridge keeps a per-turn cache of completed messages, command summaries, and tool summaries, then edits/splits Telegram messages from that cache until the turn completes.
|
|
74
|
+
|
|
75
|
+
You can also send screenshots, documents, PDFs, videos, audio, or voice notes. The bridge downloads each attachment to `/tmp/codex-telegram-bridge/` with a random filename, includes the local path in the Codex prompt, and attaches images as `localImage` inputs for Codex vision.
|
|
76
|
+
|
|
90
77
|
## Configuration File
|
|
91
78
|
|
|
92
79
|
By default, the CLI reads:
|
|
@@ -111,15 +98,8 @@ CODEX_TELEGRAM_BRIDGE_ENV=/path/to/.env codex-telegram-bridge
|
|
|
111
98
|
| `TELEGRAM_ALLOWED_USER_IDS` | required | Comma-separated numeric Telegram user IDs allowed to use the bot. |
|
|
112
99
|
| `CODEX_CWD` | current directory | Working directory where Codex starts. `~` is supported. |
|
|
113
100
|
| `CODEX_COMMAND` | `codex` | Codex command or binary path. |
|
|
114
|
-
| `
|
|
115
|
-
| `
|
|
116
|
-
| `CODEX_SUBMIT_DELAY_MS` | `800` | Delay after pasting text before pressing submit. |
|
|
117
|
-
| `CODEX_STARTUP_DELAY_MS` | `1500` | Delay before first prompt if Codex has not produced output yet. |
|
|
118
|
-
| `TMUX_SESSION` | `codex-telegram-bridge` | Detached tmux session name. |
|
|
119
|
-
| `CODEX_COLS` | `120` | tmux pane width. |
|
|
120
|
-
| `CODEX_ROWS` | `40` | tmux pane height. |
|
|
121
|
-
| `POLL_INTERVAL_MS` | `500` | How often the bridge polls tmux output. |
|
|
122
|
-
| `FLUSH_INTERVAL_MS` | `1200` | Delay before flushing non-stream fallback output. |
|
|
101
|
+
| `CODEX_APPROVAL_POLICY` | `never` | App-server thread approval policy: `never`, `on-request`, `on-failure`, or `untrusted`. |
|
|
102
|
+
| `CODEX_SANDBOX` | `danger-full-access` | App-server thread sandbox: `danger-full-access`, `workspace-write`, or `read-only`. |
|
|
123
103
|
| `STREAM_EDIT_INTERVAL_MS` | `650` | Minimum interval between Telegram message edits. |
|
|
124
104
|
| `STREAM_MIN_CHANGE_CHARS` | `24` | Minimum text growth before editing mid-response. |
|
|
125
105
|
| `TYPING_INTERVAL_MS` | `4000` | How often to send Telegram typing action. |
|
|
@@ -127,38 +107,32 @@ CODEX_TELEGRAM_BRIDGE_ENV=/path/to/.env codex-telegram-bridge
|
|
|
127
107
|
|
|
128
108
|
## Telegram Commands
|
|
129
109
|
|
|
130
|
-
- `/status` — show bridge state,
|
|
110
|
+
- `/status` — show bridge state, stdio transport, working directory, and Codex command.
|
|
131
111
|
- `/flush` — force-read and relay the latest Codex response.
|
|
132
|
-
- `/interrupt` —
|
|
133
|
-
- `/restart` — restart
|
|
134
|
-
- `/stop` — stop
|
|
112
|
+
- `/interrupt` — interrupt the active Codex turn.
|
|
113
|
+
- `/restart` — restart Codex app-server.
|
|
114
|
+
- `/stop` — stop Codex app-server.
|
|
135
115
|
- Any other text is sent directly to Codex as a prompt.
|
|
136
116
|
|
|
137
117
|
## Running as a Background Service
|
|
138
118
|
|
|
139
|
-
For a long-running home server setup, run the bridge with your preferred process manager, for example `systemd`, `pm2`,
|
|
119
|
+
For a long-running home server setup, run the bridge with your preferred process manager, for example `systemd`, `pm2`, or `launchd`.
|
|
140
120
|
|
|
141
|
-
Example with `
|
|
121
|
+
Example with `pm2`:
|
|
142
122
|
|
|
143
123
|
```sh
|
|
144
|
-
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
Detach from tmux with:
|
|
148
|
-
|
|
149
|
-
```text
|
|
150
|
-
Ctrl-b, then d
|
|
124
|
+
pm2 start codex-telegram-bridge --name codex-telegram-bridge
|
|
151
125
|
```
|
|
152
126
|
|
|
153
127
|
## Debugging Codex
|
|
154
128
|
|
|
155
|
-
The bridge
|
|
129
|
+
The bridge runs Codex as a child process with stdio JSON-RPC. To inspect protocol behavior directly, run:
|
|
156
130
|
|
|
157
131
|
```sh
|
|
158
|
-
|
|
132
|
+
codex app-server --stdio
|
|
159
133
|
```
|
|
160
134
|
|
|
161
|
-
|
|
135
|
+
The bridge always starts Codex with `app-server --stdio`; `CODEX_COMMAND` only changes the binary path.
|
|
162
136
|
|
|
163
137
|
## Install From Source
|
|
164
138
|
|
|
@@ -197,7 +171,7 @@ Recommended safeguards:
|
|
|
197
171
|
- Do not share your Telegram bot token.
|
|
198
172
|
- Run the bridge under a user account with appropriate file permissions.
|
|
199
173
|
- Point `CODEX_CWD` at a workspace you are comfortable controlling remotely.
|
|
200
|
-
- Understand that `
|
|
174
|
+
- Understand that `CODEX_APPROVAL_POLICY=never` and `CODEX_SANDBOX=danger-full-access` allow Codex to act with fewer confirmations and broader machine access.
|
|
201
175
|
|
|
202
176
|
## Repository
|
|
203
177
|
|
package/dist/codex-session.js
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { EventEmitter } from 'node:events';
|
|
3
|
+
import { createInterface } from 'node:readline';
|
|
4
|
+
export class CodexSession extends EventEmitter {
|
|
3
5
|
config;
|
|
6
|
+
process = null;
|
|
4
7
|
running = false;
|
|
5
8
|
startPromise = null;
|
|
9
|
+
requestId = 1;
|
|
10
|
+
pendingRequests = new Map();
|
|
11
|
+
threadId = null;
|
|
12
|
+
activeTurnId = null;
|
|
6
13
|
constructor(config) {
|
|
14
|
+
super();
|
|
7
15
|
this.config = config;
|
|
8
16
|
}
|
|
9
17
|
get isRunning() {
|
|
@@ -13,64 +21,229 @@ export class CodexSession {
|
|
|
13
21
|
if (this.startPromise) {
|
|
14
22
|
return this.startPromise;
|
|
15
23
|
}
|
|
16
|
-
this.startPromise = this.
|
|
24
|
+
this.startPromise = this.startServer().finally(() => {
|
|
17
25
|
this.startPromise = null;
|
|
18
26
|
});
|
|
19
27
|
return this.startPromise;
|
|
20
28
|
}
|
|
21
29
|
async stop() {
|
|
22
|
-
if (await this.exists()) {
|
|
23
|
-
await runTmux(['kill-session', '-t', this.config.tmuxSession]).catch(() => undefined);
|
|
24
|
-
}
|
|
25
30
|
this.running = false;
|
|
31
|
+
this.threadId = null;
|
|
32
|
+
this.activeTurnId = null;
|
|
33
|
+
for (const pending of this.pendingRequests.values()) {
|
|
34
|
+
pending.reject(new Error('Codex app-server stopped.'));
|
|
35
|
+
}
|
|
36
|
+
this.pendingRequests.clear();
|
|
37
|
+
if (this.process && !this.process.killed) {
|
|
38
|
+
this.process.kill('SIGTERM');
|
|
39
|
+
}
|
|
40
|
+
this.process = null;
|
|
26
41
|
}
|
|
27
42
|
async restart() {
|
|
28
43
|
await this.stop();
|
|
29
44
|
await this.start();
|
|
30
45
|
}
|
|
31
|
-
async sendText(text) {
|
|
32
|
-
await
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
46
|
+
async sendText(text, attachments = []) {
|
|
47
|
+
await this.start();
|
|
48
|
+
if (!this.threadId) {
|
|
49
|
+
throw new Error('Codex thread is not ready.');
|
|
50
|
+
}
|
|
51
|
+
const input = [{
|
|
52
|
+
type: 'text',
|
|
53
|
+
text: textWithAttachmentContext(text, attachments),
|
|
54
|
+
text_elements: [],
|
|
55
|
+
}];
|
|
56
|
+
for (const attachment of attachments) {
|
|
57
|
+
if (attachment.kind === 'image') {
|
|
58
|
+
input.push({
|
|
59
|
+
type: 'localImage',
|
|
60
|
+
path: attachment.path,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const result = await this.request('turn/start', {
|
|
65
|
+
threadId: this.threadId,
|
|
66
|
+
input,
|
|
67
|
+
});
|
|
68
|
+
const turnId = getString(result?.turn?.id);
|
|
69
|
+
if (turnId) {
|
|
70
|
+
this.activeTurnId = turnId;
|
|
71
|
+
}
|
|
36
72
|
}
|
|
37
73
|
async interrupt() {
|
|
38
|
-
if (
|
|
39
|
-
|
|
74
|
+
if (!this.threadId || !this.activeTurnId) {
|
|
75
|
+
return;
|
|
40
76
|
}
|
|
77
|
+
await this.request('turn/interrupt', {
|
|
78
|
+
threadId: this.threadId,
|
|
79
|
+
turnId: this.activeTurnId,
|
|
80
|
+
}).catch(() => undefined);
|
|
41
81
|
}
|
|
42
|
-
async
|
|
43
|
-
if (this.
|
|
44
|
-
|
|
82
|
+
async startServer() {
|
|
83
|
+
if (this.running && this.process && !this.process.killed) {
|
|
84
|
+
return;
|
|
45
85
|
}
|
|
86
|
+
this.process = spawn(this.config.codexCommand, ['app-server', '--stdio'], {
|
|
87
|
+
cwd: this.config.codexCwd,
|
|
88
|
+
env: process.env,
|
|
89
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
90
|
+
});
|
|
91
|
+
this.process.on('exit', (code, signal) => {
|
|
92
|
+
this.running = false;
|
|
93
|
+
this.threadId = null;
|
|
94
|
+
this.activeTurnId = null;
|
|
95
|
+
this.process = null;
|
|
96
|
+
for (const pending of this.pendingRequests.values()) {
|
|
97
|
+
pending.reject(new Error(`Codex app-server exited${code === null ? '' : ` with code ${code}`}.`));
|
|
98
|
+
}
|
|
99
|
+
this.pendingRequests.clear();
|
|
100
|
+
this.emit('exit', code, signal);
|
|
101
|
+
});
|
|
102
|
+
this.process.stderr.on('data', (chunk) => {
|
|
103
|
+
const message = chunk.toString('utf8').trim();
|
|
104
|
+
if (message) {
|
|
105
|
+
this.emit('error', message);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
createInterface({ input: this.process.stdout }).on('line', (line) => this.handleLine(line));
|
|
109
|
+
await this.request('initialize', {
|
|
110
|
+
clientInfo: {
|
|
111
|
+
name: 'lapage-codex-telegram-bridge',
|
|
112
|
+
title: 'LaPage Codex Telegram Bridge',
|
|
113
|
+
version: '0.1.0',
|
|
114
|
+
},
|
|
115
|
+
capabilities: {
|
|
116
|
+
experimentalApi: false,
|
|
117
|
+
requestAttestation: false,
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
this.notify('initialized');
|
|
121
|
+
const threadStartResult = await this.request('thread/start', {
|
|
122
|
+
cwd: this.config.codexCwd,
|
|
123
|
+
approvalPolicy: this.config.codexApprovalPolicy,
|
|
124
|
+
sandbox: this.config.codexSandbox,
|
|
125
|
+
threadSource: 'telegram-bridge',
|
|
126
|
+
sessionStartSource: 'startup',
|
|
127
|
+
ephemeral: false,
|
|
128
|
+
});
|
|
129
|
+
const threadId = getString(threadStartResult?.thread
|
|
130
|
+
?.id);
|
|
131
|
+
if (!threadId) {
|
|
132
|
+
throw new Error('Codex app-server did not return a thread id.');
|
|
133
|
+
}
|
|
134
|
+
this.threadId = threadId;
|
|
135
|
+
this.running = true;
|
|
46
136
|
}
|
|
47
|
-
|
|
48
|
-
|
|
137
|
+
request(method, params) {
|
|
138
|
+
if (!this.process?.stdin.writable) {
|
|
139
|
+
return Promise.reject(new Error('Codex app-server is not running.'));
|
|
140
|
+
}
|
|
141
|
+
const id = this.requestId++;
|
|
142
|
+
const payload = { method, id, params };
|
|
143
|
+
return new Promise((resolve, reject) => {
|
|
144
|
+
this.pendingRequests.set(id, { resolve, reject });
|
|
145
|
+
this.process.stdin.write(`${JSON.stringify(payload)}\n`, (error) => {
|
|
146
|
+
if (error) {
|
|
147
|
+
this.pendingRequests.delete(id);
|
|
148
|
+
reject(error);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
});
|
|
49
152
|
}
|
|
50
|
-
|
|
51
|
-
|
|
153
|
+
notify(method, params) {
|
|
154
|
+
if (!this.process?.stdin.writable) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const payload = params === undefined ? { method } : { method, params };
|
|
158
|
+
this.process.stdin.write(`${JSON.stringify(payload)}\n`);
|
|
52
159
|
}
|
|
53
|
-
|
|
54
|
-
if (
|
|
160
|
+
handleLine(line) {
|
|
161
|
+
if (!line.trim()) {
|
|
55
162
|
return;
|
|
56
163
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
this.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
164
|
+
let message;
|
|
165
|
+
try {
|
|
166
|
+
message = JSON.parse(line);
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
this.emit('error', line);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if ('id' in message) {
|
|
173
|
+
this.handleResponse(message);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
this.handleNotification(message);
|
|
177
|
+
}
|
|
178
|
+
handleResponse(response) {
|
|
179
|
+
const pending = this.pendingRequests.get(response.id);
|
|
180
|
+
if (!pending) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
this.pendingRequests.delete(response.id);
|
|
184
|
+
if (response.error) {
|
|
185
|
+
pending.reject(new Error(response.error.message));
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
pending.resolve(response.result);
|
|
72
189
|
}
|
|
190
|
+
handleNotification(notification) {
|
|
191
|
+
switch (notification.method) {
|
|
192
|
+
case 'turn/started': {
|
|
193
|
+
const turnId = getString(notification.params?.turn
|
|
194
|
+
?.id);
|
|
195
|
+
if (turnId) {
|
|
196
|
+
this.activeTurnId = turnId;
|
|
197
|
+
}
|
|
198
|
+
this.emit('turnStarted');
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
case 'item/completed': {
|
|
202
|
+
const item = notification.params
|
|
203
|
+
?.item;
|
|
204
|
+
if (isCompletedItem(item)) {
|
|
205
|
+
this.emit('itemCompleted', item);
|
|
206
|
+
}
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
case 'turn/completed': {
|
|
210
|
+
this.activeTurnId = null;
|
|
211
|
+
this.emit('turnCompleted');
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
case 'error': {
|
|
215
|
+
const message = getString(notification.params?.message);
|
|
216
|
+
if (message) {
|
|
217
|
+
this.emit('error', message);
|
|
218
|
+
}
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
default:
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function isCompletedItem(value) {
|
|
227
|
+
return (typeof value === 'object' &&
|
|
228
|
+
value !== null &&
|
|
229
|
+
typeof value.type === 'string');
|
|
73
230
|
}
|
|
74
|
-
function
|
|
75
|
-
return
|
|
231
|
+
function getString(value) {
|
|
232
|
+
return typeof value === 'string' ? value : null;
|
|
233
|
+
}
|
|
234
|
+
function textWithAttachmentContext(text, attachments) {
|
|
235
|
+
const trimmed = text.trim();
|
|
236
|
+
if (attachments.length === 0) {
|
|
237
|
+
return trimmed;
|
|
238
|
+
}
|
|
239
|
+
const attachmentLines = attachments.map((attachment, index) => {
|
|
240
|
+
const mime = attachment.mimeType ? `, ${attachment.mimeType}` : '';
|
|
241
|
+
return `${index + 1}. ${attachment.name} (${attachment.kind}${mime}): ${attachment.path}`;
|
|
242
|
+
});
|
|
243
|
+
return [
|
|
244
|
+
trimmed || 'Please inspect the attached file(s).',
|
|
245
|
+
'',
|
|
246
|
+
'Attached file(s) saved locally:',
|
|
247
|
+
...attachmentLines,
|
|
248
|
+
].join('\n');
|
|
76
249
|
}
|
package/dist/config.js
CHANGED
|
@@ -11,25 +11,15 @@ export function readConfig() {
|
|
|
11
11
|
token,
|
|
12
12
|
allowedUserIds,
|
|
13
13
|
codexCommand: process.env.CODEX_COMMAND || 'codex',
|
|
14
|
-
codexArgs: parseArgs(process.env.CODEX_ARGS || '--search --yolo'),
|
|
15
|
-
codexSubmitKey: process.env.CODEX_SUBMIT_KEY || 'Enter',
|
|
16
|
-
codexSubmitDelayMs: readNumber('CODEX_SUBMIT_DELAY_MS', 800),
|
|
17
14
|
codexCwd: expandHome(process.env.CODEX_CWD || process.cwd()),
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
rows: readNumber('CODEX_ROWS', 40),
|
|
21
|
-
flushIntervalMs: readNumber('FLUSH_INTERVAL_MS', 1200),
|
|
22
|
-
pollIntervalMs: readNumber('POLL_INTERVAL_MS', 500),
|
|
23
|
-
startupDelayMs: readNumber('CODEX_STARTUP_DELAY_MS', 1500),
|
|
15
|
+
codexApprovalPolicy: process.env.CODEX_APPROVAL_POLICY || 'never',
|
|
16
|
+
codexSandbox: process.env.CODEX_SANDBOX || 'danger-full-access',
|
|
24
17
|
streamEditIntervalMs: readNumber('STREAM_EDIT_INTERVAL_MS', 650),
|
|
25
18
|
streamMinChangeChars: readNumber('STREAM_MIN_CHANGE_CHARS', 24),
|
|
26
19
|
typingIntervalMs: readNumber('TYPING_INTERVAL_MS', 4000),
|
|
27
20
|
maxTelegramChars: Math.min(readNumber('MAX_TELEGRAM_CHARS', 3500), 3900),
|
|
28
21
|
};
|
|
29
22
|
}
|
|
30
|
-
function parseArgs(value) {
|
|
31
|
-
return value.split(' ').map((part) => part.trim()).filter(Boolean);
|
|
32
|
-
}
|
|
33
23
|
function readNumber(name, fallback) {
|
|
34
24
|
const value = Number(process.env[name]);
|
|
35
25
|
return Number.isFinite(value) && value > 0 ? value : fallback;
|
package/dist/env.js
CHANGED
|
@@ -46,15 +46,8 @@ function readEnvTemplate() {
|
|
|
46
46
|
'TELEGRAM_ALLOWED_USER_IDS=123456789',
|
|
47
47
|
'CODEX_CWD=~',
|
|
48
48
|
'CODEX_COMMAND=codex',
|
|
49
|
-
'
|
|
50
|
-
'
|
|
51
|
-
'CODEX_SUBMIT_DELAY_MS=800',
|
|
52
|
-
'TMUX_SESSION=codex-telegram-bridge',
|
|
53
|
-
'CODEX_COLS=120',
|
|
54
|
-
'CODEX_ROWS=40',
|
|
55
|
-
'FLUSH_INTERVAL_MS=1200',
|
|
56
|
-
'POLL_INTERVAL_MS=500',
|
|
57
|
-
'CODEX_STARTUP_DELAY_MS=1500',
|
|
49
|
+
'CODEX_APPROVAL_POLICY=never',
|
|
50
|
+
'CODEX_SANDBOX=danger-full-access',
|
|
58
51
|
'STREAM_EDIT_INTERVAL_MS=650',
|
|
59
52
|
'STREAM_MIN_CHANGE_CHARS=24',
|
|
60
53
|
'TYPING_INTERVAL_MS=4000',
|