@foxden-app/foxclaw 0.2.0 → 0.2.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/.env.example +1 -1
- package/README.md +28 -20
- package/README_EN.md +28 -20
- package/dist/config.d.ts +2 -0
- package/dist/config.js +20 -2
- package/dist/main.js +221 -16
- package/docs/agent-assisted-install.md +19 -17
- package/docs/install-for-beginners.md +22 -18
- package/docs/troubleshooting.md +13 -16
- package/package.json +18 -16
- package/scripts/doctor.sh +2 -1
- package/scripts/launchd/install.sh +2 -52
- package/scripts/status.sh +2 -1
- package/scripts/systemd/install.sh +1 -80
- package/scripts/systemd/uninstall.sh +2 -13
package/.env.example
CHANGED
|
@@ -29,7 +29,7 @@ TELEGRAM_PREVIEW_THROTTLE_MS=800
|
|
|
29
29
|
THREAD_LIST_LIMIT=10
|
|
30
30
|
CODEX_CLI_BIN=/absolute/path/to/codex
|
|
31
31
|
|
|
32
|
-
# Weixin (iLink): run `
|
|
32
|
+
# Weixin (iLink): run `foxclaw weixin-login` once, then enable:
|
|
33
33
|
# WX_ENABLED=true
|
|
34
34
|
# Comma-separated iLink user ids allowed to chat (see account JSON linkedIlinkUserId)
|
|
35
35
|
# WX_ALLOWED_ILINK_USER_IDS=
|
package/README.md
CHANGED
|
@@ -31,17 +31,24 @@ FoxClaw 是 Foxden agents 的本地执行爪。
|
|
|
31
31
|
- 来自 `@BotFather` 的 Telegram bot token
|
|
32
32
|
- 你的 Telegram 数字用户 ID
|
|
33
33
|
|
|
34
|
-
## 快速设置
|
|
34
|
+
## npm 快速设置
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
37
|
+
npm install -g @foxden-app/foxclaw
|
|
38
|
+
foxclaw init
|
|
39
|
+
$EDITOR ~/.foxclaw/.env
|
|
40
|
+
foxclaw doctor
|
|
41
|
+
foxclaw serve
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
pnpm 用户可以使用:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pnpm add -g @foxden-app/foxclaw
|
|
48
|
+
foxclaw init
|
|
49
|
+
$EDITOR ~/.foxclaw/.env
|
|
50
|
+
foxclaw doctor
|
|
51
|
+
foxclaw serve
|
|
45
52
|
```
|
|
46
53
|
|
|
47
54
|
运行 `doctor` 或 `serve` 前先编辑 `.env`。私聊模式的最小配置:
|
|
@@ -54,6 +61,8 @@ DEFAULT_APPROVAL_POLICY=on-request
|
|
|
54
61
|
DEFAULT_SANDBOX_MODE=workspace-write
|
|
55
62
|
```
|
|
56
63
|
|
|
64
|
+
默认配置文件是 `~/.foxclaw/.env`。如果你想把配置放在别处,可以设置 `FOXCLAW_ENV=/path/to/.env`。
|
|
65
|
+
|
|
57
66
|
FoxClaw 只接受来自 `TG_ALLOWED_USER_ID` 的消息。把机器人放进群组并不会让每个群成员都能使用它。
|
|
58
67
|
|
|
59
68
|
<details>
|
|
@@ -79,7 +88,7 @@ FoxClaw 只接受来自 `TG_ALLOWED_USER_ID` 的消息。把机器人放进群
|
|
|
79
88
|
Linux 用户级 systemd:
|
|
80
89
|
|
|
81
90
|
```bash
|
|
82
|
-
|
|
91
|
+
foxclaw install-systemd
|
|
83
92
|
systemctl --user status foxclaw.service
|
|
84
93
|
journalctl --user -u foxclaw.service -f
|
|
85
94
|
```
|
|
@@ -87,7 +96,7 @@ journalctl --user -u foxclaw.service -f
|
|
|
87
96
|
macOS launchd:
|
|
88
97
|
|
|
89
98
|
```bash
|
|
90
|
-
|
|
99
|
+
foxclaw install-launchd
|
|
91
100
|
```
|
|
92
101
|
|
|
93
102
|
默认运行时文件存储在 `~/.foxclaw`:
|
|
@@ -109,14 +118,14 @@ FoxClaw 最初 fork 自 `Gan-Xing/telegram-codex-app-bridge`,并继续以 MIT
|
|
|
109
118
|
```bash
|
|
110
119
|
systemctl --user disable --now telegram-codex-app-bridge.service 2>/dev/null || true
|
|
111
120
|
test -e ~/.foxclaw || cp -a ~/.telegram-codex-app-bridge ~/.foxclaw
|
|
112
|
-
|
|
121
|
+
foxclaw install-systemd
|
|
113
122
|
```
|
|
114
123
|
|
|
115
124
|
如果使用 launchd 安装,先卸载旧 plist(如存在):
|
|
116
125
|
|
|
117
126
|
```bash
|
|
118
127
|
launchctl unload ~/Library/LaunchAgents/com.ganxing.telegram-codex-app-bridge.plist 2>/dev/null || true
|
|
119
|
-
|
|
128
|
+
foxclaw install-launchd
|
|
120
129
|
```
|
|
121
130
|
|
|
122
131
|
旧运行时目录不会被自动读取。如果你想保留现有绑定、缓存线程列表、审批和状态数据,请手动复制一次。
|
|
@@ -125,7 +134,7 @@ launchctl unload ~/Library/LaunchAgents/com.ganxing.telegram-codex-app-bridge.pl
|
|
|
125
134
|
|
|
126
135
|
1. 使用 `@BotFather` 创建机器人,并把 token 填入 `TG_BOT_TOKEN`。
|
|
127
136
|
2. 获取你的 Telegram 数字用户 ID,并填入 `TG_ALLOWED_USER_ID`。
|
|
128
|
-
3. 使用 `
|
|
137
|
+
3. 使用 `foxclaw serve` 或服务安装器启动 FoxClaw。
|
|
129
138
|
4. 打开与机器人的私聊并发送 `/help`。
|
|
130
139
|
|
|
131
140
|
可选的群组/话题配置:
|
|
@@ -216,7 +225,7 @@ WX_ALLOWED_ILINK_USER_IDS=
|
|
|
216
225
|
构建后运行一次二维码登录助手:
|
|
217
226
|
|
|
218
227
|
```bash
|
|
219
|
-
|
|
228
|
+
foxclaw weixin-login
|
|
220
229
|
```
|
|
221
230
|
|
|
222
231
|
微信运行时文件默认存储在 `~/.foxclaw/weixin`。
|
|
@@ -232,11 +241,10 @@ npm run weixin-login
|
|
|
232
241
|
## 运维命令
|
|
233
242
|
|
|
234
243
|
```bash
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
npm run uninstall-systemd
|
|
244
|
+
foxclaw doctor
|
|
245
|
+
foxclaw status
|
|
246
|
+
foxclaw install-systemd
|
|
247
|
+
foxclaw uninstall-systemd
|
|
240
248
|
```
|
|
241
249
|
|
|
242
250
|
## 贡献
|
package/README_EN.md
CHANGED
|
@@ -31,17 +31,24 @@ The minimum install needs only a Telegram bot token, your numeric Telegram user
|
|
|
31
31
|
- A Telegram bot token from `@BotFather`
|
|
32
32
|
- Your Telegram numeric user id
|
|
33
33
|
|
|
34
|
-
## Quick Setup
|
|
34
|
+
## npm Quick Setup
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
37
|
+
npm install -g @foxden-app/foxclaw
|
|
38
|
+
foxclaw init
|
|
39
|
+
$EDITOR ~/.foxclaw/.env
|
|
40
|
+
foxclaw doctor
|
|
41
|
+
foxclaw serve
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
pnpm users can use:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pnpm add -g @foxden-app/foxclaw
|
|
48
|
+
foxclaw init
|
|
49
|
+
$EDITOR ~/.foxclaw/.env
|
|
50
|
+
foxclaw doctor
|
|
51
|
+
foxclaw serve
|
|
45
52
|
```
|
|
46
53
|
|
|
47
54
|
Edit `.env` before running `doctor` or `serve`. Minimum private-chat config:
|
|
@@ -54,6 +61,8 @@ DEFAULT_APPROVAL_POLICY=on-request
|
|
|
54
61
|
DEFAULT_SANDBOX_MODE=workspace-write
|
|
55
62
|
```
|
|
56
63
|
|
|
64
|
+
The default config file is `~/.foxclaw/.env`. Set `FOXCLAW_ENV=/path/to/.env` if you want to keep it somewhere else.
|
|
65
|
+
|
|
57
66
|
FoxClaw accepts messages only from `TG_ALLOWED_USER_ID`. Putting the bot in a group does not make it available to every group member.
|
|
58
67
|
|
|
59
68
|
<details>
|
|
@@ -79,7 +88,7 @@ FoxClaw accepts messages only from `TG_ALLOWED_USER_ID`. Putting the bot in a gr
|
|
|
79
88
|
Linux user systemd:
|
|
80
89
|
|
|
81
90
|
```bash
|
|
82
|
-
|
|
91
|
+
foxclaw install-systemd
|
|
83
92
|
systemctl --user status foxclaw.service
|
|
84
93
|
journalctl --user -u foxclaw.service -f
|
|
85
94
|
```
|
|
@@ -87,7 +96,7 @@ journalctl --user -u foxclaw.service -f
|
|
|
87
96
|
macOS launchd:
|
|
88
97
|
|
|
89
98
|
```bash
|
|
90
|
-
|
|
99
|
+
foxclaw install-launchd
|
|
91
100
|
```
|
|
92
101
|
|
|
93
102
|
Default runtime files are stored under `~/.foxclaw`:
|
|
@@ -109,14 +118,14 @@ When upgrading an existing local install:
|
|
|
109
118
|
```bash
|
|
110
119
|
systemctl --user disable --now telegram-codex-app-bridge.service 2>/dev/null || true
|
|
111
120
|
test -e ~/.foxclaw || cp -a ~/.telegram-codex-app-bridge ~/.foxclaw
|
|
112
|
-
|
|
121
|
+
foxclaw install-systemd
|
|
113
122
|
```
|
|
114
123
|
|
|
115
124
|
For launchd installs, unload the old plist if present:
|
|
116
125
|
|
|
117
126
|
```bash
|
|
118
127
|
launchctl unload ~/Library/LaunchAgents/com.ganxing.telegram-codex-app-bridge.plist 2>/dev/null || true
|
|
119
|
-
|
|
128
|
+
foxclaw install-launchd
|
|
120
129
|
```
|
|
121
130
|
|
|
122
131
|
The old runtime directory is not read automatically. Copy it once if you want to keep existing bindings, cached thread lists, approvals, and status data.
|
|
@@ -125,7 +134,7 @@ The old runtime directory is not read automatically. Copy it once if you want to
|
|
|
125
134
|
|
|
126
135
|
1. Create a bot with `@BotFather` and copy the token into `TG_BOT_TOKEN`.
|
|
127
136
|
2. Get your Telegram numeric user id and place it into `TG_ALLOWED_USER_ID`.
|
|
128
|
-
3. Start FoxClaw with `
|
|
137
|
+
3. Start FoxClaw with `foxclaw serve` or the service installer.
|
|
129
138
|
4. Open a private chat with the bot and send `/help`.
|
|
130
139
|
|
|
131
140
|
Optional group/topic config:
|
|
@@ -216,7 +225,7 @@ WX_ALLOWED_ILINK_USER_IDS=
|
|
|
216
225
|
Run the QR login helper once after building:
|
|
217
226
|
|
|
218
227
|
```bash
|
|
219
|
-
|
|
228
|
+
foxclaw weixin-login
|
|
220
229
|
```
|
|
221
230
|
|
|
222
231
|
Weixin runtime files default to `~/.foxclaw/weixin`.
|
|
@@ -232,11 +241,10 @@ See [Troubleshooting](./docs/troubleshooting.md) for `doctor` failures, Telegram
|
|
|
232
241
|
## Operations
|
|
233
242
|
|
|
234
243
|
```bash
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
npm run uninstall-systemd
|
|
244
|
+
foxclaw doctor
|
|
245
|
+
foxclaw status
|
|
246
|
+
foxclaw install-systemd
|
|
247
|
+
foxclaw uninstall-systemd
|
|
240
248
|
```
|
|
241
249
|
|
|
242
250
|
## Contributing
|
package/dist/config.d.ts
CHANGED
|
@@ -7,6 +7,8 @@ export declare const DEFAULT_LOG_PATH: string;
|
|
|
7
7
|
export declare const DEFAULT_LOCK_PATH: string;
|
|
8
8
|
export declare const DEFAULT_CODEX_APP_SERVER_STATE_PATH: string;
|
|
9
9
|
export declare const DEFAULT_CODEX_APP_SERVER_LOG_PATH: string;
|
|
10
|
+
export declare const DEFAULT_ENV_PATH: string;
|
|
11
|
+
export declare function loadEnv(): void;
|
|
10
12
|
export interface AppConfig {
|
|
11
13
|
tgBotToken: string;
|
|
12
14
|
tgAllowedUserId: string;
|
package/dist/config.js
CHANGED
|
@@ -3,15 +3,33 @@ import os from 'node:os';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { spawnSync } from 'node:child_process';
|
|
5
5
|
import dotenv from 'dotenv';
|
|
6
|
-
export const APP_HOME = path.join(os.homedir(), '.foxclaw');
|
|
6
|
+
export const APP_HOME = path.join(process.env.HOME || os.homedir(), '.foxclaw');
|
|
7
7
|
export const DEFAULT_STORE_PATH = path.join(APP_HOME, 'data', 'bridge.sqlite');
|
|
8
8
|
export const DEFAULT_STATUS_PATH = path.join(APP_HOME, 'runtime', 'status.json');
|
|
9
9
|
export const DEFAULT_LOG_PATH = path.join(APP_HOME, 'logs', 'service.log');
|
|
10
10
|
export const DEFAULT_LOCK_PATH = path.join(APP_HOME, 'runtime', 'bridge.lock');
|
|
11
11
|
export const DEFAULT_CODEX_APP_SERVER_STATE_PATH = path.join(APP_HOME, 'runtime', 'codex-app-server.json');
|
|
12
12
|
export const DEFAULT_CODEX_APP_SERVER_LOG_PATH = path.join(APP_HOME, 'logs', 'codex-app-server.log');
|
|
13
|
+
export const DEFAULT_ENV_PATH = path.join(APP_HOME, '.env');
|
|
14
|
+
let envLoaded = false;
|
|
15
|
+
export function loadEnv() {
|
|
16
|
+
if (envLoaded)
|
|
17
|
+
return;
|
|
18
|
+
envLoaded = true;
|
|
19
|
+
const explicitPath = process.env.FOXCLAW_ENV?.trim();
|
|
20
|
+
if (explicitPath) {
|
|
21
|
+
dotenv.config({ path: explicitPath });
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const cwdEnvPath = path.join(process.cwd(), '.env');
|
|
25
|
+
if (fs.existsSync(cwdEnvPath)) {
|
|
26
|
+
dotenv.config({ path: cwdEnvPath });
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
dotenv.config({ path: DEFAULT_ENV_PATH });
|
|
30
|
+
}
|
|
13
31
|
export function loadConfig() {
|
|
14
|
-
|
|
32
|
+
loadEnv();
|
|
15
33
|
const config = {
|
|
16
34
|
tgBotToken: required('TG_BOT_TOKEN'),
|
|
17
35
|
tgAllowedUserId: required('TG_ALLOWED_USER_ID'),
|
package/dist/main.js
CHANGED
|
@@ -3,26 +3,33 @@ import fs from 'node:fs';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import process from 'node:process';
|
|
5
5
|
import { spawnSync } from 'node:child_process';
|
|
6
|
-
import
|
|
7
|
-
import { APP_HOME, DEFAULT_LOG_PATH, DEFAULT_STATUS_PATH, loadConfig } from './config.js';
|
|
8
|
-
import { BridgeMessagingRouter } from './channels/bridge_messaging_router.js';
|
|
9
|
-
import { TelegramMessagingPort } from './channels/telegram/telegram_messaging_port.js';
|
|
10
|
-
import { WeixinChannelAdapter } from './channels/weixin/weixin_channel_adapter.js';
|
|
11
|
-
import { WeixinMessagingPort } from './channels/weixin/weixin_messaging_port.js';
|
|
12
|
-
import { attachIlinkRuntimeFromBridgeLogger } from './channels/weixin/ilink/runtime_attach.js';
|
|
13
|
-
import { startWeixinLoginWithQr, waitForWeixinLogin } from './channels/weixin/ilink/login_qr.js';
|
|
14
|
-
import { accountFilePath, loadWeixinAccount, saveWeixinAccount } from './channels/weixin/account_store.js';
|
|
15
|
-
import { Logger } from './logger.js';
|
|
16
|
-
import { BridgeStore } from './store/database.js';
|
|
17
|
-
import { TelegramGateway } from './telegram/gateway.js';
|
|
18
|
-
import { CodexAppClient } from './codex_app/client.js';
|
|
19
|
-
import { BridgeSessionCore } from './controller/controller.js';
|
|
20
|
-
import { TelegramChannelAdapter } from './channels/telegram/telegram_channel_adapter.js';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { APP_HOME, DEFAULT_ENV_PATH, DEFAULT_LOG_PATH, DEFAULT_STATUS_PATH, loadConfig, loadEnv, } from './config.js';
|
|
21
8
|
import { acquireProcessLock, LockHeldError } from './lock.js';
|
|
22
9
|
import { readRuntimeStatus, writeRuntimeStatus } from './runtime.js';
|
|
23
10
|
const command = process.argv[2] || 'serve';
|
|
24
|
-
|
|
11
|
+
loadEnv();
|
|
12
|
+
const packageRoot = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
13
|
+
const entryPoint = fileURLToPath(import.meta.url);
|
|
25
14
|
async function main() {
|
|
15
|
+
if (command === 'init') {
|
|
16
|
+
initConfig();
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (command === 'install-systemd') {
|
|
20
|
+
requireNode24(command);
|
|
21
|
+
installSystemd();
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (command === 'uninstall-systemd') {
|
|
25
|
+
uninstallSystemd();
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (command === 'install-launchd') {
|
|
29
|
+
requireNode24(command);
|
|
30
|
+
installLaunchd();
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
26
33
|
if (command === 'status') {
|
|
27
34
|
const status = readRuntimeStatus(process.env.STATUS_PATH || DEFAULT_STATUS_PATH);
|
|
28
35
|
if (!status) {
|
|
@@ -73,9 +80,28 @@ async function main() {
|
|
|
73
80
|
process.exit(failed ? 1 : 0);
|
|
74
81
|
}
|
|
75
82
|
if (command === 'weixin-login') {
|
|
83
|
+
requireNode24(command);
|
|
76
84
|
await runWeixinLoginCli();
|
|
77
85
|
return;
|
|
78
86
|
}
|
|
87
|
+
requireNode24(command);
|
|
88
|
+
await runServeCli();
|
|
89
|
+
}
|
|
90
|
+
async function runServeCli() {
|
|
91
|
+
const [{ BridgeMessagingRouter }, { TelegramMessagingPort }, { WeixinChannelAdapter }, { WeixinMessagingPort }, { attachIlinkRuntimeFromBridgeLogger }, { loadWeixinAccount }, { Logger }, { BridgeStore }, { TelegramGateway }, { CodexAppClient }, { BridgeSessionCore }, { TelegramChannelAdapter },] = await Promise.all([
|
|
92
|
+
import('./channels/bridge_messaging_router.js'),
|
|
93
|
+
import('./channels/telegram/telegram_messaging_port.js'),
|
|
94
|
+
import('./channels/weixin/weixin_channel_adapter.js'),
|
|
95
|
+
import('./channels/weixin/weixin_messaging_port.js'),
|
|
96
|
+
import('./channels/weixin/ilink/runtime_attach.js'),
|
|
97
|
+
import('./channels/weixin/account_store.js'),
|
|
98
|
+
import('./logger.js'),
|
|
99
|
+
import('./store/database.js'),
|
|
100
|
+
import('./telegram/gateway.js'),
|
|
101
|
+
import('./codex_app/client.js'),
|
|
102
|
+
import('./controller/controller.js'),
|
|
103
|
+
import('./channels/telegram/telegram_channel_adapter.js'),
|
|
104
|
+
]);
|
|
79
105
|
const config = loadConfig();
|
|
80
106
|
const logger = new Logger(config.logLevel, config.logPath);
|
|
81
107
|
attachIlinkRuntimeFromBridgeLogger(logger, config.wxIlinkRouteTag);
|
|
@@ -139,7 +165,177 @@ async function main() {
|
|
|
139
165
|
throw error;
|
|
140
166
|
}
|
|
141
167
|
}
|
|
168
|
+
function initConfig() {
|
|
169
|
+
const envPath = process.env.FOXCLAW_ENV?.trim() || DEFAULT_ENV_PATH;
|
|
170
|
+
fs.mkdirSync(path.dirname(envPath), { recursive: true });
|
|
171
|
+
if (fs.existsSync(envPath)) {
|
|
172
|
+
console.log(`Config already exists: ${envPath}`);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const examplePath = path.join(packageRoot, '.env.example');
|
|
176
|
+
fs.copyFileSync(examplePath, envPath);
|
|
177
|
+
console.log(`Created ${envPath}`);
|
|
178
|
+
console.log('Edit it, then run: foxclaw doctor');
|
|
179
|
+
}
|
|
180
|
+
function installSystemd() {
|
|
181
|
+
if (!hasCommand('systemctl')) {
|
|
182
|
+
console.error('systemctl not found (need systemd)');
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
185
|
+
const unitName = 'foxclaw.service';
|
|
186
|
+
const userSystemdDir = path.join(process.env.XDG_CONFIG_HOME || path.join(process.env.HOME || '', '.config'), 'systemd', 'user');
|
|
187
|
+
const unitPath = path.join(userSystemdDir, unitName);
|
|
188
|
+
const configDir = path.dirname(process.env.FOXCLAW_ENV?.trim() || DEFAULT_ENV_PATH);
|
|
189
|
+
const nodeBin = process.execPath;
|
|
190
|
+
const nodeDir = path.dirname(nodeBin);
|
|
191
|
+
const pathValue = buildServicePath(nodeDir);
|
|
192
|
+
fs.mkdirSync(userSystemdDir, { recursive: true });
|
|
193
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
194
|
+
fs.mkdirSync(path.join(APP_HOME, 'logs'), { recursive: true });
|
|
195
|
+
const foxclawEnvLine = process.env.FOXCLAW_ENV?.trim()
|
|
196
|
+
? `Environment=FOXCLAW_ENV=${systemdEscape(process.env.FOXCLAW_ENV.trim())}\n`
|
|
197
|
+
: '';
|
|
198
|
+
fs.writeFileSync(unitPath, `[Unit]
|
|
199
|
+
Description=FoxClaw local Codex execution bridge
|
|
200
|
+
Documentation=https://github.com/foxden-app/foxclaw
|
|
201
|
+
After=network-online.target
|
|
202
|
+
Wants=network-online.target
|
|
203
|
+
StartLimitIntervalSec=300
|
|
204
|
+
StartLimitBurst=5
|
|
205
|
+
|
|
206
|
+
[Service]
|
|
207
|
+
Type=simple
|
|
208
|
+
WorkingDirectory=${systemdEscape(configDir)}
|
|
209
|
+
Environment=HOME=${systemdEscape(process.env.HOME || '')}
|
|
210
|
+
Environment=USER=${systemdEscape(process.env.USER || '')}
|
|
211
|
+
Environment=LOGNAME=${systemdEscape(process.env.LOGNAME || process.env.USER || '')}
|
|
212
|
+
Environment=PATH=${systemdEscape(pathValue)}
|
|
213
|
+
${foxclawEnvLine}ExecStart=${systemdEscape(nodeBin)} ${systemdEscape(entryPoint)} serve
|
|
214
|
+
Restart=always
|
|
215
|
+
RestartSec=10
|
|
216
|
+
TimeoutStopSec=45
|
|
217
|
+
KillMode=process
|
|
218
|
+
|
|
219
|
+
[Install]
|
|
220
|
+
WantedBy=default.target
|
|
221
|
+
`);
|
|
222
|
+
spawnChecked('systemctl', ['--user', 'daemon-reload']);
|
|
223
|
+
spawnChecked('systemctl', ['--user', 'enable', unitName]);
|
|
224
|
+
const restarted = spawnSync('systemctl', ['--user', 'restart', unitName], { stdio: 'inherit' });
|
|
225
|
+
if (restarted.status !== 0) {
|
|
226
|
+
spawnChecked('systemctl', ['--user', 'start', unitName]);
|
|
227
|
+
}
|
|
228
|
+
console.log(`Installed ${unitPath}`);
|
|
229
|
+
console.log(`Status: systemctl --user status ${unitName}`);
|
|
230
|
+
console.log(`Logs: journalctl --user -u ${unitName} -f`);
|
|
231
|
+
}
|
|
232
|
+
function uninstallSystemd() {
|
|
233
|
+
if (!hasCommand('systemctl')) {
|
|
234
|
+
console.error('systemctl not found');
|
|
235
|
+
process.exit(1);
|
|
236
|
+
}
|
|
237
|
+
const unitName = 'foxclaw.service';
|
|
238
|
+
const userSystemdDir = path.join(process.env.XDG_CONFIG_HOME || path.join(process.env.HOME || '', '.config'), 'systemd', 'user');
|
|
239
|
+
const unitPath = path.join(userSystemdDir, unitName);
|
|
240
|
+
spawnSync('systemctl', ['--user', 'disable', '--now', unitName], { stdio: 'inherit' });
|
|
241
|
+
fs.rmSync(unitPath, { force: true });
|
|
242
|
+
spawnChecked('systemctl', ['--user', 'daemon-reload']);
|
|
243
|
+
console.log(`Removed ${unitPath}`);
|
|
244
|
+
}
|
|
245
|
+
function installLaunchd() {
|
|
246
|
+
if (process.platform !== 'darwin') {
|
|
247
|
+
console.error('launchd install is only available on macOS');
|
|
248
|
+
process.exit(1);
|
|
249
|
+
}
|
|
250
|
+
const home = process.env.HOME || '';
|
|
251
|
+
const plist = path.join(home, 'Library', 'LaunchAgents', 'app.foxden.foxclaw.plist');
|
|
252
|
+
const configDir = path.dirname(process.env.FOXCLAW_ENV?.trim() || DEFAULT_ENV_PATH);
|
|
253
|
+
fs.mkdirSync(path.dirname(plist), { recursive: true });
|
|
254
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
255
|
+
fs.mkdirSync(path.join(APP_HOME, 'logs'), { recursive: true });
|
|
256
|
+
const foxclawEnvXml = process.env.FOXCLAW_ENV?.trim()
|
|
257
|
+
? ` <key>FOXCLAW_ENV</key>
|
|
258
|
+
<string>${xmlEscape(process.env.FOXCLAW_ENV.trim())}</string>
|
|
259
|
+
`
|
|
260
|
+
: '';
|
|
261
|
+
fs.writeFileSync(plist, `<?xml version="1.0" encoding="UTF-8"?>
|
|
262
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
263
|
+
<plist version="1.0">
|
|
264
|
+
<dict>
|
|
265
|
+
<key>Label</key>
|
|
266
|
+
<string>app.foxden.foxclaw</string>
|
|
267
|
+
<key>ProgramArguments</key>
|
|
268
|
+
<array>
|
|
269
|
+
<string>${xmlEscape(process.execPath)}</string>
|
|
270
|
+
<string>${xmlEscape(entryPoint)}</string>
|
|
271
|
+
<string>serve</string>
|
|
272
|
+
</array>
|
|
273
|
+
<key>WorkingDirectory</key>
|
|
274
|
+
<string>${xmlEscape(configDir)}</string>
|
|
275
|
+
<key>EnvironmentVariables</key>
|
|
276
|
+
<dict>
|
|
277
|
+
<key>PATH</key>
|
|
278
|
+
<string>${xmlEscape(process.env.PATH || '')}</string>
|
|
279
|
+
<key>HOME</key>
|
|
280
|
+
<string>${xmlEscape(home)}</string>
|
|
281
|
+
<key>USER</key>
|
|
282
|
+
<string>${xmlEscape(process.env.USER || '')}</string>
|
|
283
|
+
<key>LOGNAME</key>
|
|
284
|
+
<string>${xmlEscape(process.env.LOGNAME || process.env.USER || '')}</string>
|
|
285
|
+
${foxclawEnvXml}
|
|
286
|
+
</dict>
|
|
287
|
+
<key>RunAtLoad</key>
|
|
288
|
+
<true/>
|
|
289
|
+
<key>KeepAlive</key>
|
|
290
|
+
<true/>
|
|
291
|
+
<key>StandardOutPath</key>
|
|
292
|
+
<string>${xmlEscape(path.join(APP_HOME, 'logs', 'launchd.out.log'))}</string>
|
|
293
|
+
<key>StandardErrorPath</key>
|
|
294
|
+
<string>${xmlEscape(path.join(APP_HOME, 'logs', 'launchd.err.log'))}</string>
|
|
295
|
+
</dict>
|
|
296
|
+
</plist>
|
|
297
|
+
`);
|
|
298
|
+
spawnSync('launchctl', ['unload', plist], { stdio: 'ignore' });
|
|
299
|
+
spawnChecked('launchctl', ['load', plist]);
|
|
300
|
+
console.log(`Installed ${plist}`);
|
|
301
|
+
}
|
|
302
|
+
function buildServicePath(nodeDir) {
|
|
303
|
+
const parts = [
|
|
304
|
+
path.join(process.env.HOME || '', '.local', 'bin'),
|
|
305
|
+
nodeDir,
|
|
306
|
+
'/usr/local/sbin',
|
|
307
|
+
'/usr/local/bin',
|
|
308
|
+
'/usr/sbin',
|
|
309
|
+
'/usr/bin',
|
|
310
|
+
'/sbin',
|
|
311
|
+
'/bin',
|
|
312
|
+
];
|
|
313
|
+
return parts.filter((part, index) => part && parts.indexOf(part) === index).join(':');
|
|
314
|
+
}
|
|
315
|
+
function spawnChecked(commandName, args) {
|
|
316
|
+
const result = spawnSync(commandName, args, { stdio: 'inherit' });
|
|
317
|
+
if (result.status !== 0) {
|
|
318
|
+
process.exit(result.status || 1);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
function systemdEscape(value) {
|
|
322
|
+
return value.replace(/\\/g, '\\\\').replace(/ /g, '\\x20');
|
|
323
|
+
}
|
|
324
|
+
function xmlEscape(value) {
|
|
325
|
+
return value
|
|
326
|
+
.replace(/&/g, '&')
|
|
327
|
+
.replace(/</g, '<')
|
|
328
|
+
.replace(/>/g, '>')
|
|
329
|
+
.replace(/"/g, '"')
|
|
330
|
+
.replace(/'/g, ''');
|
|
331
|
+
}
|
|
142
332
|
async function runWeixinLoginCli() {
|
|
333
|
+
const [{ attachIlinkRuntimeFromBridgeLogger }, { startWeixinLoginWithQr, waitForWeixinLogin }, { accountFilePath, saveWeixinAccount }, { Logger },] = await Promise.all([
|
|
334
|
+
import('./channels/weixin/ilink/runtime_attach.js'),
|
|
335
|
+
import('./channels/weixin/ilink/login_qr.js'),
|
|
336
|
+
import('./channels/weixin/account_store.js'),
|
|
337
|
+
import('./logger.js'),
|
|
338
|
+
]);
|
|
143
339
|
const logPath = process.env.LOG_PATH || DEFAULT_LOG_PATH;
|
|
144
340
|
const level = process.env.LOG_LEVEL === 'debug' || process.env.LOG_LEVEL === 'warn' || process.env.LOG_LEVEL === 'error'
|
|
145
341
|
? process.env.LOG_LEVEL
|
|
@@ -228,6 +424,15 @@ function hasConfiguredCodexBin(binPath) {
|
|
|
228
424
|
return false;
|
|
229
425
|
}
|
|
230
426
|
}
|
|
427
|
+
function requireNode24(commandName) {
|
|
428
|
+
if (Number(process.versions.node.split('.')[0]) >= 24)
|
|
429
|
+
return;
|
|
430
|
+
console.error(`FoxClaw ${commandName} requires Node.js 24+. Current Node.js is ${process.version}.`);
|
|
431
|
+
console.error('Install or activate Node 24, then reinstall/re-run FoxClaw:');
|
|
432
|
+
console.error(' nvm install 24 && nvm use 24');
|
|
433
|
+
console.error(' npm install -g @foxden-app/foxclaw@latest');
|
|
434
|
+
process.exit(1);
|
|
435
|
+
}
|
|
231
436
|
function serializeError(error) {
|
|
232
437
|
if (error instanceof Error) {
|
|
233
438
|
return { message: error.message, stack: error.stack };
|
|
@@ -27,7 +27,8 @@ Send this to your agent on the target computer:
|
|
|
27
27
|
Install FoxClaw on this machine.
|
|
28
28
|
|
|
29
29
|
Repository:
|
|
30
|
-
|
|
30
|
+
Published package:
|
|
31
|
+
@foxden-app/foxclaw
|
|
31
32
|
|
|
32
33
|
Use private Telegram chat first. Do not configure group/topic mode unless I explicitly provide TG_ALLOWED_CHAT_ID or TG_ALLOWED_TOPIC_ID.
|
|
33
34
|
|
|
@@ -37,20 +38,20 @@ TG_ALLOWED_USER_ID=<paste numeric Telegram user id here>
|
|
|
37
38
|
DEFAULT_CWD=<paste absolute working directory here>
|
|
38
39
|
|
|
39
40
|
Tasks:
|
|
40
|
-
1. Inspect the machine first.
|
|
41
|
+
1. Inspect the machine first. If a FoxClaw or old telegram-codex-app-bridge service already exists, report it before changing services.
|
|
41
42
|
2. Ensure Node.js 24+ is available. If not, install or activate Node 24 with nvm.
|
|
42
43
|
3. Ensure the Codex CLI exists and is logged in. If login is required, stop and tell me exactly what I need to do.
|
|
43
|
-
4.
|
|
44
|
-
5.
|
|
45
|
-
6. Run
|
|
46
|
-
7. Start FoxClaw in the foreground
|
|
44
|
+
4. Install or update FoxClaw with npm install -g @foxden-app/foxclaw@latest.
|
|
45
|
+
5. Run foxclaw init, then write ~/.foxclaw/.env. Never print or commit the bot token.
|
|
46
|
+
6. Run foxclaw doctor.
|
|
47
|
+
7. Start FoxClaw in the foreground with foxclaw serve and ask me to send /help and /status to the Telegram bot.
|
|
47
48
|
8. Only after the foreground test works, install the background service:
|
|
48
|
-
- Linux:
|
|
49
|
-
- macOS:
|
|
49
|
+
- Linux: foxclaw install-systemd
|
|
50
|
+
- macOS: foxclaw install-launchd
|
|
50
51
|
9. Verify the final state:
|
|
51
52
|
- foxclaw.service is active/enabled on Linux
|
|
52
53
|
- old telegram-codex-app-bridge.service is inactive/disabled if present
|
|
53
|
-
-
|
|
54
|
+
- foxclaw status works
|
|
54
55
|
10. Report the commands used, the final status, and the log command I should use if something stops working. Redact TG_BOT_TOKEN and never print the full token or full .env content.
|
|
55
56
|
```
|
|
56
57
|
|
|
@@ -61,18 +62,19 @@ Use this when the target computer is still running `telegram-codex-app-bridge`:
|
|
|
61
62
|
```text
|
|
62
63
|
Migrate this machine from telegram-codex-app-bridge to FoxClaw.
|
|
63
64
|
|
|
64
|
-
New
|
|
65
|
-
|
|
65
|
+
New package:
|
|
66
|
+
@foxden-app/foxclaw
|
|
66
67
|
|
|
67
68
|
Please:
|
|
68
|
-
1.
|
|
69
|
+
1. Inspect the current install method, service file, and runtime directory before changing anything.
|
|
69
70
|
2. Stop and disable telegram-codex-app-bridge.service if it exists.
|
|
70
71
|
3. If ~/.foxclaw does not exist and ~/.telegram-codex-app-bridge exists, copy ~/.telegram-codex-app-bridge to ~/.foxclaw.
|
|
71
|
-
4.
|
|
72
|
-
5. Run
|
|
73
|
-
6.
|
|
74
|
-
7.
|
|
75
|
-
8.
|
|
72
|
+
4. Install or update FoxClaw with npm install -g @foxden-app/foxclaw@latest.
|
|
73
|
+
5. Run foxclaw init if ~/.foxclaw/.env does not exist, then verify ~/.foxclaw/.env.
|
|
74
|
+
6. Run foxclaw doctor.
|
|
75
|
+
7. Install and start foxclaw.service with foxclaw install-systemd.
|
|
76
|
+
8. Verify foxclaw.service is active and telegram-codex-app-bridge.service is inactive/disabled.
|
|
77
|
+
9. Report final status and any blockers. Redact TG_BOT_TOKEN and never print the full token or full .env content.
|
|
76
78
|
```
|
|
77
79
|
|
|
78
80
|
## Safety Notes
|
|
@@ -114,15 +114,22 @@ The easiest path:
|
|
|
114
114
|
|
|
115
115
|
Use the number, not your `@username`.
|
|
116
116
|
|
|
117
|
-
## 6.
|
|
117
|
+
## 6. Install FoxClaw
|
|
118
118
|
|
|
119
|
-
|
|
119
|
+
Install the published npm package:
|
|
120
120
|
|
|
121
121
|
```bash
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
122
|
+
npm install -g @foxden-app/foxclaw
|
|
123
|
+
foxclaw init
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
This creates the config file at `~/.foxclaw/.env`.
|
|
127
|
+
|
|
128
|
+
If you prefer pnpm:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
pnpm add -g @foxden-app/foxclaw
|
|
132
|
+
foxclaw init
|
|
126
133
|
```
|
|
127
134
|
|
|
128
135
|
## 7. Fill In `.env`
|
|
@@ -130,7 +137,7 @@ cp .env.example .env
|
|
|
130
137
|
Open `.env` in a simple editor:
|
|
131
138
|
|
|
132
139
|
```bash
|
|
133
|
-
nano
|
|
140
|
+
nano ~/.foxclaw/.env
|
|
134
141
|
```
|
|
135
142
|
|
|
136
143
|
For a first private-chat install, fill only the important values:
|
|
@@ -158,11 +165,10 @@ In `nano`, press `Ctrl+O`, Enter, then `Ctrl+X` to save and exit.
|
|
|
158
165
|
|
|
159
166
|
## 8. Run The First Check
|
|
160
167
|
|
|
161
|
-
|
|
168
|
+
Run doctor:
|
|
162
169
|
|
|
163
170
|
```bash
|
|
164
|
-
|
|
165
|
-
npm run doctor
|
|
171
|
+
foxclaw doctor
|
|
166
172
|
```
|
|
167
173
|
|
|
168
174
|
You want to see:
|
|
@@ -182,7 +188,7 @@ If you see `[FAIL]`, stop and check [Troubleshooting](./troubleshooting.md).
|
|
|
182
188
|
Run FoxClaw directly before installing it as a background service:
|
|
183
189
|
|
|
184
190
|
```bash
|
|
185
|
-
|
|
191
|
+
foxclaw serve
|
|
186
192
|
```
|
|
187
193
|
|
|
188
194
|
Leave this terminal open.
|
|
@@ -232,7 +238,7 @@ Only do this after foreground mode works.
|
|
|
232
238
|
On Linux with systemd:
|
|
233
239
|
|
|
234
240
|
```bash
|
|
235
|
-
|
|
241
|
+
foxclaw install-systemd
|
|
236
242
|
systemctl --user status foxclaw.service
|
|
237
243
|
journalctl --user -u foxclaw.service -f
|
|
238
244
|
```
|
|
@@ -246,7 +252,7 @@ loginctl enable-linger "$USER"
|
|
|
246
252
|
On macOS:
|
|
247
253
|
|
|
248
254
|
```bash
|
|
249
|
-
|
|
255
|
+
foxclaw install-launchd
|
|
250
256
|
```
|
|
251
257
|
|
|
252
258
|
launchd starts FoxClaw when you log in.
|
|
@@ -256,7 +262,7 @@ launchd starts FoxClaw when you log in.
|
|
|
256
262
|
Check current status:
|
|
257
263
|
|
|
258
264
|
```bash
|
|
259
|
-
|
|
265
|
+
foxclaw status
|
|
260
266
|
```
|
|
261
267
|
|
|
262
268
|
Restart Linux service after changing `.env`:
|
|
@@ -274,14 +280,12 @@ systemctl --user stop foxclaw.service
|
|
|
274
280
|
Uninstall Linux service:
|
|
275
281
|
|
|
276
282
|
```bash
|
|
277
|
-
|
|
283
|
+
foxclaw uninstall-systemd
|
|
278
284
|
```
|
|
279
285
|
|
|
280
286
|
Update FoxClaw later:
|
|
281
287
|
|
|
282
288
|
```bash
|
|
283
|
-
|
|
284
|
-
npm install
|
|
285
|
-
npm run build
|
|
289
|
+
npm install -g @foxden-app/foxclaw@latest
|
|
286
290
|
systemctl --user restart foxclaw.service
|
|
287
291
|
```
|
package/docs/troubleshooting.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# Troubleshooting
|
|
2
2
|
|
|
3
|
-
Start with these commands
|
|
3
|
+
Start with these commands:
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
foxclaw doctor
|
|
7
|
+
foxclaw status
|
|
8
8
|
```
|
|
9
9
|
|
|
10
10
|
If FoxClaw is installed as a Linux user service, also check:
|
|
@@ -18,7 +18,7 @@ journalctl --user -u foxclaw.service -f
|
|
|
18
18
|
|
|
19
19
|
| Symptom | Meaning | Fix |
|
|
20
20
|
| --- | --- | --- |
|
|
21
|
-
| `[FAIL] node >= 24` | Your current shell is using an older Node.js. | Run `nvm install 24 && nvm use 24`, then rerun `
|
|
21
|
+
| `[FAIL] node >= 24` | Your current shell is using an older Node.js. | Run `nvm install 24 && nvm use 24`, then rerun `foxclaw doctor`. If the service uses old Node, reinstall it from a Node 24 shell with `foxclaw install-systemd`. |
|
|
22
22
|
| `[FAIL] codex cli available` | The `codex` command is not in PATH. | Install Codex CLI or fix PATH, then confirm `codex --version` works. |
|
|
23
23
|
| `[FAIL] telegram bot token configured` | `TG_BOT_TOKEN` is missing from `.env`. | Copy the token from `@BotFather` into `.env`. |
|
|
24
24
|
| `[FAIL] telegram allowed user configured` | `TG_ALLOWED_USER_ID` is missing from `.env`. | Get your numeric id from `@userinfobot` and add it to `.env`. |
|
|
@@ -77,7 +77,7 @@ Check these in order:
|
|
|
77
77
|
1. Make sure FoxClaw is running:
|
|
78
78
|
|
|
79
79
|
```bash
|
|
80
|
-
|
|
80
|
+
foxclaw status
|
|
81
81
|
```
|
|
82
82
|
|
|
83
83
|
2. Try private chat first. Open your bot directly and send:
|
|
@@ -96,7 +96,7 @@ Check these in order:
|
|
|
96
96
|
systemctl --user restart foxclaw.service
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
-
If running foreground mode, stop with `Ctrl+C` and run `
|
|
99
|
+
If running foreground mode, stop with `Ctrl+C` and run `foxclaw serve` again.
|
|
100
100
|
|
|
101
101
|
## Group Messages Do Not Work
|
|
102
102
|
|
|
@@ -194,7 +194,7 @@ The systemd installer captures the `node` binary from your current PATH. If you
|
|
|
194
194
|
|
|
195
195
|
```bash
|
|
196
196
|
nvm use 24
|
|
197
|
-
|
|
197
|
+
foxclaw install-systemd
|
|
198
198
|
systemctl --user status foxclaw.service
|
|
199
199
|
```
|
|
200
200
|
|
|
@@ -217,7 +217,7 @@ loginctl enable-linger "$USER"
|
|
|
217
217
|
macOS launchd starts FoxClaw when you log in after running:
|
|
218
218
|
|
|
219
219
|
```bash
|
|
220
|
-
|
|
220
|
+
foxclaw install-launchd
|
|
221
221
|
```
|
|
222
222
|
|
|
223
223
|
## Migrating From The Old Project Name
|
|
@@ -227,13 +227,10 @@ If this machine still runs `telegram-codex-app-bridge`, migrate once:
|
|
|
227
227
|
```bash
|
|
228
228
|
systemctl --user disable --now telegram-codex-app-bridge.service 2>/dev/null || true
|
|
229
229
|
test -e ~/.foxclaw || cp -a ~/.telegram-codex-app-bridge ~/.foxclaw
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
npm install
|
|
235
|
-
npm run build
|
|
236
|
-
npm run install-systemd
|
|
230
|
+
npm install -g @foxden-app/foxclaw@latest
|
|
231
|
+
foxclaw init
|
|
232
|
+
foxclaw doctor
|
|
233
|
+
foxclaw install-systemd
|
|
237
234
|
```
|
|
238
235
|
|
|
239
|
-
If `
|
|
236
|
+
If `~/.foxclaw/.env` already exists, `foxclaw init` leaves it untouched.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@foxden-app/foxclaw",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Foxden local execution claw for controlling Codex from trusted chat interfaces.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/main.js",
|
|
@@ -8,13 +8,13 @@
|
|
|
8
8
|
"foxclaw": "dist/main.js"
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
|
-
"dist",
|
|
12
|
-
"docs",
|
|
13
|
-
"scripts",
|
|
11
|
+
"dist",
|
|
12
|
+
"docs",
|
|
13
|
+
"scripts",
|
|
14
14
|
"skills",
|
|
15
15
|
".env.example",
|
|
16
|
-
"README.md",
|
|
17
|
-
"README_EN.md",
|
|
16
|
+
"README.md",
|
|
17
|
+
"README_EN.md",
|
|
18
18
|
"LICENSE"
|
|
19
19
|
],
|
|
20
20
|
"private": false,
|
|
@@ -32,21 +32,23 @@
|
|
|
32
32
|
"engines": {
|
|
33
33
|
"node": ">=24"
|
|
34
34
|
},
|
|
35
|
-
"scripts": {
|
|
36
|
-
"clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
37
|
-
"build": "npm run clean && tsc -p tsconfig.build.json",
|
|
38
|
-
"dev": "tsx src/main.ts serve",
|
|
39
|
-
"serve": "node dist/main.js serve",
|
|
35
|
+
"scripts": {
|
|
36
|
+
"clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
37
|
+
"build": "npm run clean && tsc -p tsconfig.build.json",
|
|
38
|
+
"dev": "tsx src/main.ts serve",
|
|
39
|
+
"serve": "node dist/main.js serve",
|
|
40
40
|
"weixin-login": "node dist/main.js weixin-login",
|
|
41
41
|
"status": "node dist/main.js status",
|
|
42
42
|
"doctor": "node dist/main.js doctor",
|
|
43
|
-
"
|
|
44
|
-
"
|
|
43
|
+
"init": "node dist/main.js init",
|
|
44
|
+
"install-systemd": "node dist/main.js install-systemd",
|
|
45
|
+
"uninstall-systemd": "node dist/main.js uninstall-systemd",
|
|
46
|
+
"install-launchd": "node dist/main.js install-launchd",
|
|
45
47
|
"typecheck": "tsc --noEmit",
|
|
46
48
|
"lint": "eslint .",
|
|
47
|
-
"test": "node --test --import tsx \"src/**/*.test.ts\"",
|
|
48
|
-
"prepack": "npm run build"
|
|
49
|
-
},
|
|
49
|
+
"test": "node --test --import tsx \"src/**/*.test.ts\"",
|
|
50
|
+
"prepack": "npm run build"
|
|
51
|
+
},
|
|
50
52
|
"dependencies": {
|
|
51
53
|
"dotenv": "^16.6.1",
|
|
52
54
|
"qrcode-terminal": "^0.12.0"
|
package/scripts/doctor.sh
CHANGED
|
@@ -1,54 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
set -euo pipefail
|
|
3
|
-
ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
4
|
-
|
|
5
|
-
NODE_BIN="$(command -v node)"
|
|
6
|
-
PATH_VALUE="$PATH"
|
|
7
|
-
HOME_VALUE="$HOME"
|
|
8
|
-
USER_VALUE="${USER:-}"
|
|
9
|
-
LOGNAME_VALUE="${LOGNAME:-$USER_VALUE}"
|
|
10
|
-
if [[ -z "$NODE_BIN" ]]; then
|
|
11
|
-
echo "node not found in PATH" >&2
|
|
12
|
-
exit 1
|
|
13
|
-
fi
|
|
14
|
-
mkdir -p "$HOME/Library/LaunchAgents" "$HOME/.foxclaw/logs"
|
|
15
|
-
cat > "$PLIST" <<PLIST
|
|
16
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
17
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
18
|
-
<plist version="1.0">
|
|
19
|
-
<dict>
|
|
20
|
-
<key>Label</key>
|
|
21
|
-
<string>app.foxden.foxclaw</string>
|
|
22
|
-
<key>ProgramArguments</key>
|
|
23
|
-
<array>
|
|
24
|
-
<string>$NODE_BIN</string>
|
|
25
|
-
<string>$ROOT_DIR/dist/main.js</string>
|
|
26
|
-
<string>serve</string>
|
|
27
|
-
</array>
|
|
28
|
-
<key>WorkingDirectory</key>
|
|
29
|
-
<string>$ROOT_DIR</string>
|
|
30
|
-
<key>EnvironmentVariables</key>
|
|
31
|
-
<dict>
|
|
32
|
-
<key>PATH</key>
|
|
33
|
-
<string>$PATH_VALUE</string>
|
|
34
|
-
<key>HOME</key>
|
|
35
|
-
<string>$HOME_VALUE</string>
|
|
36
|
-
<key>USER</key>
|
|
37
|
-
<string>$USER_VALUE</string>
|
|
38
|
-
<key>LOGNAME</key>
|
|
39
|
-
<string>$LOGNAME_VALUE</string>
|
|
40
|
-
</dict>
|
|
41
|
-
<key>RunAtLoad</key>
|
|
42
|
-
<true/>
|
|
43
|
-
<key>KeepAlive</key>
|
|
44
|
-
<true/>
|
|
45
|
-
<key>StandardOutPath</key>
|
|
46
|
-
<string>$HOME/.foxclaw/logs/launchd.out.log</string>
|
|
47
|
-
<key>StandardErrorPath</key>
|
|
48
|
-
<string>$HOME/.foxclaw/logs/launchd.err.log</string>
|
|
49
|
-
</dict>
|
|
50
|
-
</plist>
|
|
51
|
-
PLIST
|
|
52
|
-
launchctl unload "$PLIST" >/dev/null 2>&1 || true
|
|
53
|
-
launchctl load "$PLIST"
|
|
54
|
-
echo "Installed $PLIST"
|
|
3
|
+
ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
4
|
+
node "$ROOT_DIR/dist/main.js" install-launchd
|
package/scripts/status.sh
CHANGED
|
@@ -1,83 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
4
|
-
|
|
5
|
-
USER_SYSTEMD_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user"
|
|
6
|
-
UNIT_PATH="$USER_SYSTEMD_DIR/$UNIT_NAME"
|
|
7
|
-
NODE_BIN="$(command -v node)"
|
|
8
|
-
# Do not embed full interactive $PATH: WSL often includes /mnt/c/Program Files/... which breaks
|
|
9
|
-
# systemd's unquoted Environment=PATH=... (spaces split the assignment). Use a small Linux PATH.
|
|
10
|
-
NODE_DIR="$(dirname "$NODE_BIN")"
|
|
11
|
-
PATH_VALUE="${NODE_DIR}:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
|
12
|
-
if [[ -d "${HOME}/.local/bin" ]]; then
|
|
13
|
-
PATH_VALUE="${HOME}/.local/bin:${PATH_VALUE}"
|
|
14
|
-
fi
|
|
15
|
-
HOME_VALUE="${HOME:?}"
|
|
16
|
-
USER_VALUE="${USER:-}"
|
|
17
|
-
LOGNAME_VALUE="${LOGNAME:-$USER_VALUE}"
|
|
18
|
-
|
|
19
|
-
if [[ -z "$NODE_BIN" ]]; then
|
|
20
|
-
echo "node not found in PATH" >&2
|
|
21
|
-
exit 1
|
|
22
|
-
fi
|
|
23
|
-
|
|
24
|
-
if ! command -v systemctl >/dev/null 2>&1; then
|
|
25
|
-
echo "systemctl not found (need systemd)" >&2
|
|
26
|
-
exit 1
|
|
27
|
-
fi
|
|
28
|
-
|
|
29
|
-
NO_BUILD=0
|
|
30
|
-
NO_START=0
|
|
31
|
-
for arg in "$@"; do
|
|
32
|
-
case "$arg" in
|
|
33
|
-
--no-build) NO_BUILD=1 ;;
|
|
34
|
-
--no-start) NO_START=1 ;;
|
|
35
|
-
esac
|
|
36
|
-
done
|
|
37
|
-
|
|
38
|
-
if [[ "$NO_BUILD" -eq 0 ]]; then
|
|
39
|
-
(cd "$ROOT_DIR" && npm run build)
|
|
40
|
-
fi
|
|
41
|
-
|
|
42
|
-
mkdir -p "$USER_SYSTEMD_DIR" "$HOME/.foxclaw/logs"
|
|
43
|
-
|
|
44
|
-
cat >"$UNIT_PATH" <<UNIT
|
|
45
|
-
[Unit]
|
|
46
|
-
Description=FoxClaw local Codex execution bridge
|
|
47
|
-
Documentation=https://github.com/foxden-app/foxclaw
|
|
48
|
-
After=network-online.target
|
|
49
|
-
Wants=network-online.target
|
|
50
|
-
StartLimitIntervalSec=300
|
|
51
|
-
StartLimitBurst=5
|
|
52
|
-
|
|
53
|
-
[Service]
|
|
54
|
-
Type=simple
|
|
55
|
-
WorkingDirectory=$ROOT_DIR
|
|
56
|
-
Environment=HOME=$HOME_VALUE
|
|
57
|
-
Environment=USER=$USER_VALUE
|
|
58
|
-
Environment=LOGNAME=$LOGNAME_VALUE
|
|
59
|
-
Environment=PATH=$PATH_VALUE
|
|
60
|
-
ExecStart=$NODE_BIN dist/main.js serve
|
|
61
|
-
Restart=always
|
|
62
|
-
RestartSec=10
|
|
63
|
-
TimeoutStopSec=45
|
|
64
|
-
KillMode=process
|
|
65
|
-
|
|
66
|
-
[Install]
|
|
67
|
-
WantedBy=default.target
|
|
68
|
-
UNIT
|
|
69
|
-
|
|
70
|
-
systemctl --user daemon-reload
|
|
71
|
-
systemctl --user enable "$UNIT_NAME"
|
|
72
|
-
if [[ "$NO_START" -eq 0 ]]; then
|
|
73
|
-
systemctl --user restart "$UNIT_NAME" || systemctl --user start "$UNIT_NAME"
|
|
74
|
-
fi
|
|
75
|
-
|
|
76
|
-
echo "Installed $UNIT_PATH"
|
|
77
|
-
echo "Status: systemctl --user status $UNIT_NAME"
|
|
78
|
-
echo "Logs: journalctl --user -u $UNIT_NAME -f"
|
|
79
|
-
if [[ "$(loginctl show-user "$USER" -p Linger --value 2>/dev/null || true)" != "yes" ]]; then
|
|
80
|
-
echo ""
|
|
81
|
-
echo "Tip: for this service to start at boot without an interactive login, run once:"
|
|
82
|
-
echo " loginctl enable-linger $USER"
|
|
83
|
-
fi
|
|
4
|
+
node "$ROOT_DIR/dist/main.js" install-systemd
|
|
@@ -1,15 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
set -euo pipefail
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
UNIT_PATH="$USER_SYSTEMD_DIR/$UNIT_NAME"
|
|
6
|
-
|
|
7
|
-
if ! command -v systemctl >/dev/null 2>&1; then
|
|
8
|
-
echo "systemctl not found" >&2
|
|
9
|
-
exit 1
|
|
10
|
-
fi
|
|
11
|
-
|
|
12
|
-
systemctl --user disable --now "$UNIT_NAME" 2>/dev/null || true
|
|
13
|
-
rm -f "$UNIT_PATH"
|
|
14
|
-
systemctl --user daemon-reload
|
|
15
|
-
echo "Removed $UNIT_PATH"
|
|
3
|
+
ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
4
|
+
node "$ROOT_DIR/dist/main.js" uninstall-systemd
|