@foxden-app/foxclaw 0.3.5 → 0.3.6
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 +1 -1
- package/README_EN.md +1 -1
- package/dist/main.js +118 -0
- package/docs/troubleshooting.md +6 -2
- package/docs/user-manual.md +1 -1
- package/docs/zh/troubleshooting.md +6 -2
- package/docs/zh/user-manual.md +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -70,7 +70,7 @@ foxclaw doctor
|
|
|
70
70
|
foxclaw start
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
-
`foxclaw init` 会创建 `~/.foxclaw/.env`,并在终端里提示填写 Telegram bot token、Telegram 数字用户 ID
|
|
73
|
+
`foxclaw init` 会创建 `~/.foxclaw/.env`,并在终端里提示填写 Telegram bot token、Telegram 数字用户 ID 和默认工作目录。如果当前 shell 里有 `HTTP_PROXY`、`HTTPS_PROXY`、`ALL_PROXY` 等代理变量,也会询问是否写入 FoxClaw 配置,避免服务启动后 Codex 走不到同一条网络。任何一项都可以直接回车跳过,之后再用 `$EDITOR ~/.foxclaw/.env` 手动修改。
|
|
74
74
|
|
|
75
75
|
跑 `doctor` 或 `start` 之前先把 `.env` 填好。私聊模式最小配置:
|
|
76
76
|
|
package/README_EN.md
CHANGED
|
@@ -70,7 +70,7 @@ foxclaw doctor
|
|
|
70
70
|
foxclaw start
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
-
`foxclaw init` creates `~/.foxclaw/.env` and prompts for the Telegram bot token, your numeric Telegram user id, and the default workspace. Press Enter on any field to skip it and edit later with `$EDITOR ~/.foxclaw/.env`.
|
|
73
|
+
`foxclaw init` creates `~/.foxclaw/.env` and prompts for the Telegram bot token, your numeric Telegram user id, and the default workspace. If the current shell has proxy variables such as `HTTP_PROXY`, `HTTPS_PROXY`, or `ALL_PROXY`, it also asks whether to save them into the FoxClaw config so the service uses the same network path as your working Codex CLI. Press Enter on any field to skip it and edit later with `$EDITOR ~/.foxclaw/.env`.
|
|
74
74
|
|
|
75
75
|
Fill `.env` before running `doctor` or `start`. Minimum private-chat config:
|
|
76
76
|
|
package/dist/main.js
CHANGED
|
@@ -12,6 +12,16 @@ const command = process.argv[2] || 'serve';
|
|
|
12
12
|
loadEnv();
|
|
13
13
|
const packageRoot = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
14
14
|
const entryPoint = fileURLToPath(import.meta.url);
|
|
15
|
+
const PROXY_ENV_KEYS = [
|
|
16
|
+
'HTTP_PROXY',
|
|
17
|
+
'HTTPS_PROXY',
|
|
18
|
+
'ALL_PROXY',
|
|
19
|
+
'NO_PROXY',
|
|
20
|
+
'http_proxy',
|
|
21
|
+
'https_proxy',
|
|
22
|
+
'all_proxy',
|
|
23
|
+
'no_proxy',
|
|
24
|
+
];
|
|
15
25
|
async function main() {
|
|
16
26
|
if (command === 'init') {
|
|
17
27
|
await initConfig();
|
|
@@ -160,6 +170,7 @@ async function initConfig() {
|
|
|
160
170
|
console.log(`Created ${envPath}`);
|
|
161
171
|
}
|
|
162
172
|
if (!canPromptForInit()) {
|
|
173
|
+
printProxyEnvHint(envPath);
|
|
163
174
|
console.log(`Edit it manually, then run: foxclaw doctor`);
|
|
164
175
|
return;
|
|
165
176
|
}
|
|
@@ -174,6 +185,7 @@ async function configureEnvInteractively(envPath, existed) {
|
|
|
174
185
|
if (existed) {
|
|
175
186
|
const updateExisting = (await rl.question('Update Telegram/workspace setup fields now? [y/N]: ')).trim().toLowerCase();
|
|
176
187
|
if (updateExisting !== 'y' && updateExisting !== 'yes') {
|
|
188
|
+
await maybeSaveProxyEnvFromShell(rl, envPath);
|
|
177
189
|
console.log(`Edit it manually, then run: foxclaw doctor`);
|
|
178
190
|
return;
|
|
179
191
|
}
|
|
@@ -184,6 +196,7 @@ async function configureEnvInteractively(envPath, existed) {
|
|
|
184
196
|
const updates = {};
|
|
185
197
|
const skipped = [];
|
|
186
198
|
const warnings = [];
|
|
199
|
+
Object.assign(updates, await maybeSaveProxyEnvFromShell(rl, envPath));
|
|
187
200
|
const token = sanitizeEnvInput(await rl.question('Telegram bot token (TG_BOT_TOKEN): '));
|
|
188
201
|
if (token) {
|
|
189
202
|
updates.TG_BOT_TOKEN = token;
|
|
@@ -307,6 +320,64 @@ function writeEnvUpdates(envPath, updates) {
|
|
|
307
320
|
}
|
|
308
321
|
fs.writeFileSync(envPath, text);
|
|
309
322
|
}
|
|
323
|
+
async function maybeSaveProxyEnvFromShell(rl, envPath) {
|
|
324
|
+
const proxyUpdates = detectMissingProxyEnv(envPath);
|
|
325
|
+
const keys = Object.keys(proxyUpdates);
|
|
326
|
+
if (keys.length === 0) {
|
|
327
|
+
return {};
|
|
328
|
+
}
|
|
329
|
+
console.log(`Detected proxy env in this shell: ${keys.join(', ')}`);
|
|
330
|
+
const answer = (await rl.question('Save these proxy settings to FoxClaw .env for service use? [Y/n]: ')).trim().toLowerCase();
|
|
331
|
+
if (answer === 'n' || answer === 'no') {
|
|
332
|
+
console.log(`Skipped proxy env. Add it to ${envPath} if ChatGPT access needs a proxy.`);
|
|
333
|
+
return {};
|
|
334
|
+
}
|
|
335
|
+
writeEnvUpdates(envPath, proxyUpdates);
|
|
336
|
+
console.log(`Saved ${keys.join(', ')} to ${envPath}`);
|
|
337
|
+
return proxyUpdates;
|
|
338
|
+
}
|
|
339
|
+
function printProxyEnvHint(envPath) {
|
|
340
|
+
const proxyUpdates = detectMissingProxyEnv(envPath);
|
|
341
|
+
const keys = Object.keys(proxyUpdates);
|
|
342
|
+
if (keys.length === 0) {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
console.log(`[WARN] Proxy env detected in this shell but missing from ${envPath}: ${keys.join(', ')}`);
|
|
346
|
+
console.log(`[WARN] Add those proxy variables to ${envPath} if ChatGPT/Codex needs a proxy.`);
|
|
347
|
+
}
|
|
348
|
+
function detectMissingProxyEnv(envPath) {
|
|
349
|
+
const existing = readEnvFileKeys(envPath);
|
|
350
|
+
const existingCanonical = new Set(Array.from(existing, canonicalProxyEnvKey));
|
|
351
|
+
const updates = {};
|
|
352
|
+
for (const key of PROXY_ENV_KEYS) {
|
|
353
|
+
const value = process.env[key]?.trim();
|
|
354
|
+
if (!value || existing.has(key) || existingCanonical.has(canonicalProxyEnvKey(key))) {
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
updates[key] = value;
|
|
358
|
+
}
|
|
359
|
+
return updates;
|
|
360
|
+
}
|
|
361
|
+
function canonicalProxyEnvKey(key) {
|
|
362
|
+
return key.toUpperCase();
|
|
363
|
+
}
|
|
364
|
+
function readEnvFileKeys(envPath) {
|
|
365
|
+
const keys = new Set();
|
|
366
|
+
let text = '';
|
|
367
|
+
try {
|
|
368
|
+
text = fs.readFileSync(envPath, 'utf8');
|
|
369
|
+
}
|
|
370
|
+
catch {
|
|
371
|
+
return keys;
|
|
372
|
+
}
|
|
373
|
+
for (const line of text.split(/\r?\n/)) {
|
|
374
|
+
const match = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=/);
|
|
375
|
+
if (match?.[1]) {
|
|
376
|
+
keys.add(match[1]);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return keys;
|
|
380
|
+
}
|
|
310
381
|
function formatEnvValue(value) {
|
|
311
382
|
const cleaned = value.replace(/[\r\n]/g, '').trim();
|
|
312
383
|
if (!/[\s#"\\]/.test(cleaned))
|
|
@@ -376,8 +447,52 @@ function runDoctorChecks() {
|
|
|
376
447
|
console.log(`[FAIL] default cwd missing: ${cwd}`);
|
|
377
448
|
passed = false;
|
|
378
449
|
}
|
|
450
|
+
warnIfProxyEnvMissingFromLoadedEnv();
|
|
451
|
+
warnIfInstalledServiceNodeLooksWrong();
|
|
379
452
|
return passed;
|
|
380
453
|
}
|
|
454
|
+
function warnIfProxyEnvMissingFromLoadedEnv() {
|
|
455
|
+
const envPath = serviceEnvPath();
|
|
456
|
+
const proxyUpdates = detectMissingProxyEnv(envPath);
|
|
457
|
+
const keys = Object.keys(proxyUpdates);
|
|
458
|
+
if (keys.length === 0) {
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
console.log(`[WARN] proxy env is present in this shell but missing from ${envPath}: ${keys.join(', ')}`);
|
|
462
|
+
console.log(`[WARN] systemd/launchd services do not inherit your shell; add those variables to the FoxClaw env file if Codex needs a proxy.`);
|
|
463
|
+
}
|
|
464
|
+
function warnIfInstalledServiceNodeLooksWrong() {
|
|
465
|
+
if (process.platform !== 'linux') {
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
const unitPath = path.join(process.env.XDG_CONFIG_HOME || path.join(process.env.HOME || '', '.config'), 'systemd', 'user', 'foxclaw.service');
|
|
469
|
+
let text = '';
|
|
470
|
+
try {
|
|
471
|
+
text = fs.readFileSync(unitPath, 'utf8');
|
|
472
|
+
}
|
|
473
|
+
catch {
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
const execStart = text.match(/^ExecStart=(.+)$/m)?.[1]?.trim();
|
|
477
|
+
const nodePath = execStart ? systemdUnescape(execStart.split(/\s+/)[0] ?? '') : '';
|
|
478
|
+
if (!nodePath) {
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
if (!fs.existsSync(nodePath)) {
|
|
482
|
+
console.log(`[WARN] installed service node is missing: ${nodePath}`);
|
|
483
|
+
console.log('[WARN] Run foxclaw start from a Node 24 shell to refresh the service unit.');
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
const result = spawnSync(nodePath, ['-p', 'process.versions.node'], { encoding: 'utf8' });
|
|
487
|
+
const version = result.status === 0 ? result.stdout.trim() : '';
|
|
488
|
+
const major = Number.parseInt(version.split('.')[0] ?? '', 10);
|
|
489
|
+
if (Number.isFinite(major) && major >= 24) {
|
|
490
|
+
console.log(`[OK] service node >= 24: ${nodePath}`);
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
console.log(`[WARN] installed service node is older than 24: ${nodePath}${version ? ` (${version})` : ''}`);
|
|
494
|
+
console.log('[WARN] Run foxclaw start from a Node 24 shell to refresh the service unit.');
|
|
495
|
+
}
|
|
381
496
|
function installSystemd() {
|
|
382
497
|
if (!hasCommand('systemctl')) {
|
|
383
498
|
console.error('systemctl not found (need systemd)');
|
|
@@ -543,6 +658,9 @@ function spawnChecked(commandName, args) {
|
|
|
543
658
|
function systemdEscape(value) {
|
|
544
659
|
return value.replace(/\\/g, '\\\\').replace(/ /g, '\\x20');
|
|
545
660
|
}
|
|
661
|
+
function systemdUnescape(value) {
|
|
662
|
+
return value.replace(/\\x20/g, ' ').replace(/\\\\/g, '\\');
|
|
663
|
+
}
|
|
546
664
|
function xmlEscape(value) {
|
|
547
665
|
return value
|
|
548
666
|
.replace(/&/g, '&')
|
package/docs/troubleshooting.md
CHANGED
|
@@ -192,6 +192,8 @@ A common cause is that your shell or project `.env` has proxy variables, while t
|
|
|
192
192
|
systemctl --user cat foxclaw.service
|
|
193
193
|
```
|
|
194
194
|
|
|
195
|
+
`foxclaw init` detects proxy environment variables in the current shell and asks whether to save them into the FoxClaw `.env`. If you skipped that step, `foxclaw doctor` warns when it sees proxy variables in the shell but not in the FoxClaw env file.
|
|
196
|
+
|
|
195
197
|
Make sure the file referenced by `Environment=FOXCLAW_ENV=...` contains your proxy variables, for example:
|
|
196
198
|
|
|
197
199
|
```dotenv
|
|
@@ -209,7 +211,9 @@ foxclaw restart
|
|
|
209
211
|
|
|
210
212
|
## Service Starts With The Wrong Node Version
|
|
211
213
|
|
|
212
|
-
The systemd installer
|
|
214
|
+
The systemd installer records the absolute path of the Node process that is currently running FoxClaw. It does not rely on systemd loading `nvm.sh`. If you manage multiple Node versions with nvm, run `foxclaw start` from a Node 24 shell and the service will keep using that Node 24 path.
|
|
215
|
+
|
|
216
|
+
If you installed the service from a shell using Node 22 or older, reinstall it from a Node 24 shell:
|
|
213
217
|
|
|
214
218
|
```bash
|
|
215
219
|
nvm use 24
|
|
@@ -217,7 +221,7 @@ foxclaw start
|
|
|
217
221
|
systemctl --user status foxclaw.service
|
|
218
222
|
```
|
|
219
223
|
|
|
220
|
-
The status output should show a Node 24 path in `ExecStart`.
|
|
224
|
+
The status output should show a Node 24 path in `ExecStart`. `foxclaw doctor` also checks the installed service Node path and warns if it is missing or older than 24.
|
|
221
225
|
|
|
222
226
|
## Does It Run After Reboot?
|
|
223
227
|
|
package/docs/user-manual.md
CHANGED
|
@@ -100,7 +100,7 @@ Both install the same published npm package. Use one global package manager cons
|
|
|
100
100
|
|
|
101
101
|
### 1.6 Fill In The Config
|
|
102
102
|
|
|
103
|
-
`foxclaw init` creates the default config file at `~/.foxclaw/.env` and prompts for the Telegram bot token, your numeric Telegram user id, and the default workspace. Press Enter on any field to skip it, then edit manually if needed:
|
|
103
|
+
`foxclaw init` creates the default config file at `~/.foxclaw/.env` and prompts for the Telegram bot token, your numeric Telegram user id, and the default workspace. If the current shell has proxy variables such as `HTTP_PROXY`, `HTTPS_PROXY`, or `ALL_PROXY`, it also asks whether to save them into the FoxClaw config so the service-side Codex app-server uses the same network. Press Enter on any field to skip it, then edit manually if needed:
|
|
104
104
|
|
|
105
105
|
```bash
|
|
106
106
|
$EDITOR ~/.foxclaw/.env
|
|
@@ -193,6 +193,8 @@ tail -f ~/.foxclaw/logs/service.log
|
|
|
193
193
|
systemctl --user cat foxclaw.service
|
|
194
194
|
```
|
|
195
195
|
|
|
196
|
+
`foxclaw init` 会检测当前 shell 里的代理环境变量,并询问是否保存到 FoxClaw `.env`。如果你跳过了这一步,`foxclaw doctor` 会在发现“shell 有代理,但 FoxClaw env 没有代理”时给出 `[WARN]`。
|
|
197
|
+
|
|
196
198
|
确认 `Environment=FOXCLAW_ENV=...` 指向的文件里有你的代理配置,例如:
|
|
197
199
|
|
|
198
200
|
```dotenv
|
|
@@ -210,7 +212,9 @@ foxclaw restart
|
|
|
210
212
|
|
|
211
213
|
## 服务用了错误的 Node 版本
|
|
212
214
|
|
|
213
|
-
systemd
|
|
215
|
+
systemd 安装脚本会记录当时正在运行的 Node 绝对路径,不依赖 systemd 去加载 `nvm.sh`。如果你用 nvm 管多个 Node 版本,原则是:从 Node 24 的 shell 里执行 `foxclaw start`,服务之后就固定使用这个 Node 24 路径。
|
|
216
|
+
|
|
217
|
+
如果你从 Node 22 或更旧版本的 shell 里安装过服务,请从 Node 24 的 shell 重新安装:
|
|
214
218
|
|
|
215
219
|
```bash
|
|
216
220
|
nvm use 24
|
|
@@ -218,7 +222,7 @@ foxclaw start
|
|
|
218
222
|
systemctl --user status foxclaw.service
|
|
219
223
|
```
|
|
220
224
|
|
|
221
|
-
状态输出里应该能看到 Node 24
|
|
225
|
+
状态输出里应该能看到 Node 24 的路径。`foxclaw doctor` 也会检查已安装服务里的 Node 路径,如果发现路径不存在或版本低于 24,会提示重新运行 `foxclaw start`。
|
|
222
226
|
|
|
223
227
|
## 重启后是否会自动运行
|
|
224
228
|
|
package/docs/zh/user-manual.md
CHANGED
|
@@ -100,7 +100,7 @@ foxclaw init
|
|
|
100
100
|
|
|
101
101
|
### 1.6 填写配置
|
|
102
102
|
|
|
103
|
-
`foxclaw init` 会创建默认配置文件 `~/.foxclaw/.env`,并提示填写 Telegram bot token、Telegram 数字用户 ID
|
|
103
|
+
`foxclaw init` 会创建默认配置文件 `~/.foxclaw/.env`,并提示填写 Telegram bot token、Telegram 数字用户 ID 和默认工作目录。如果当前 shell 里有 `HTTP_PROXY`、`HTTPS_PROXY`、`ALL_PROXY` 等代理变量,它也会询问是否写入 FoxClaw 配置,让服务里的 Codex app-server 使用同样的网络。任何一项都可以直接回车跳过,之后再手动编辑:
|
|
104
104
|
|
|
105
105
|
```bash
|
|
106
106
|
$EDITOR ~/.foxclaw/.env
|