imtoagent 0.3.4 → 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 +97 -97
- package/bin/imtoagent-real +197 -153
- package/bin/imtoagent.cjs +13 -5
- package/index.ts +106 -106
- package/modules/agent/claude-adapter.ts +6 -6
- package/modules/agent/claude.ts +6 -6
- package/modules/agent/codex-adapter.ts +13 -13
- package/modules/agent/codex-exec-server.ts +11 -11
- package/modules/agent/codex.ts +29 -29
- package/modules/agent/opencode-adapter.ts +17 -17
- package/modules/agent/opencode.ts +10 -10
- package/modules/capabilities.ts +33 -33
- package/modules/cli/setup.ts +164 -164
- package/modules/core/config.ts +5 -5
- package/modules/core/error.ts +8 -8
- package/modules/core/runtime.ts +10 -10
- package/modules/core/session.ts +4 -4
- package/modules/core/stats.ts +14 -14
- package/modules/core/types.ts +7 -7
- package/modules/im/feishu.ts +56 -56
- package/modules/im/telegram.ts +23 -23
- package/modules/im/wechat.ts +54 -54
- package/modules/im/wecom.ts +50 -50
- package/modules/media/feishu-inbound-adapter.ts +4 -4
- package/modules/media/resolver.ts +11 -11
- package/modules/media/telegram-inbound-adapter.ts +8 -8
- package/modules/prompt-builder.ts +12 -12
- package/modules/proxy/anthropic-proxy.ts +31 -31
- package/modules/proxy/codex-proxy.ts +18 -18
- package/modules/utils/backend-check.ts +12 -12
- package/modules/utils/paths.ts +8 -8
- package/package.json +1 -1
- package/scripts/postinstall.cjs +10 -10
- package/scripts/postinstall.ts +13 -13
- package/templates/soul.template/identity.md +5 -5
- package/templates/soul.template/profile.md +7 -7
- package/templates/soul.template/rules.md +5 -5
- package/templates/soul.template/skills.md +2 -2
- package/templates/soul.template/workspace.md +3 -3
package/bin/imtoagent-real
CHANGED
|
@@ -1,24 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// ================================================================
|
|
3
|
-
// imtoagent CLI —
|
|
3
|
+
// imtoagent CLI — global command entry point
|
|
4
4
|
// ================================================================
|
|
5
|
-
// npm install -g imtoagent
|
|
6
|
-
// imtoagent setup —
|
|
7
|
-
// imtoagent start —
|
|
8
|
-
// imtoagent
|
|
9
|
-
// imtoagent
|
|
10
|
-
// imtoagent
|
|
11
|
-
// imtoagent
|
|
5
|
+
// Available after npm install -g imtoagent:
|
|
6
|
+
// imtoagent setup — interactive setup wizard
|
|
7
|
+
// imtoagent start — start gateway in background
|
|
8
|
+
// imtoagent run — start gateway in foreground
|
|
9
|
+
// imtoagent stop — stop gateway
|
|
10
|
+
// imtoagent status — check running status
|
|
11
|
+
// imtoagent restore — hot reload
|
|
12
|
+
// imtoagent daemon — foreground daemon (auto-restart, for launchd/systemd)
|
|
12
13
|
// ================================================================
|
|
13
14
|
|
|
14
15
|
import * as fs from 'fs';
|
|
15
16
|
import * as path from 'path';
|
|
17
|
+
import { spawn, execSync } from 'child_process';
|
|
16
18
|
import { getDataDir } from '../modules/utils/paths';
|
|
17
19
|
|
|
18
20
|
const PID_FILE = '/tmp/imtoagent.pid';
|
|
19
21
|
|
|
20
22
|
// ================================================================
|
|
21
|
-
//
|
|
23
|
+
// Command dispatch
|
|
22
24
|
// ================================================================
|
|
23
25
|
const command = process.argv[2];
|
|
24
26
|
|
|
@@ -29,6 +31,9 @@ switch (command) {
|
|
|
29
31
|
case 'start':
|
|
30
32
|
await cmdStart();
|
|
31
33
|
break;
|
|
34
|
+
case 'run':
|
|
35
|
+
await cmdRun();
|
|
36
|
+
break;
|
|
32
37
|
case 'stop':
|
|
33
38
|
await cmdStop();
|
|
34
39
|
break;
|
|
@@ -42,19 +47,17 @@ switch (command) {
|
|
|
42
47
|
await cmdDaemon();
|
|
43
48
|
break;
|
|
44
49
|
case undefined: {
|
|
45
|
-
// 无命令 → 没完成 setup 自动进向导,已完成显示帮助
|
|
46
50
|
const dataDir = getDataDir();
|
|
47
51
|
const configPath = path.join(dataDir, 'config.json');
|
|
48
52
|
let needsSetup = !fs.existsSync(configPath);
|
|
49
53
|
if (!needsSetup) {
|
|
50
54
|
try {
|
|
51
55
|
const raw = fs.readFileSync(configPath, 'utf-8');
|
|
52
|
-
// 如果配置里还有 YOUR_ 占位符,说明还没完成 setup
|
|
53
56
|
needsSetup = /YOUR_[A-Z_]+/.test(raw);
|
|
54
57
|
} catch { needsSetup = true; }
|
|
55
58
|
}
|
|
56
59
|
if (needsSetup) {
|
|
57
|
-
console.log('👋
|
|
60
|
+
console.log('👋 Welcome to imtoagent! Please run setup first.\n');
|
|
58
61
|
await cmdSetup();
|
|
59
62
|
} else {
|
|
60
63
|
printHelp();
|
|
@@ -67,7 +70,7 @@ switch (command) {
|
|
|
67
70
|
printHelp();
|
|
68
71
|
break;
|
|
69
72
|
default:
|
|
70
|
-
console.error(`❌
|
|
73
|
+
console.error(`❌ Unknown command: ${command}`);
|
|
71
74
|
printHelp();
|
|
72
75
|
process.exit(1);
|
|
73
76
|
}
|
|
@@ -77,22 +80,23 @@ switch (command) {
|
|
|
77
80
|
// ================================================================
|
|
78
81
|
function printHelp() {
|
|
79
82
|
console.log(`
|
|
80
|
-
imtoagent — IM ↔ Agent
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
imtoagent setup
|
|
84
|
-
imtoagent start
|
|
85
|
-
imtoagent stop
|
|
86
|
-
imtoagent
|
|
87
|
-
imtoagent
|
|
88
|
-
imtoagent
|
|
89
|
-
|
|
90
|
-
|
|
83
|
+
imtoagent — IM ↔ Agent Unified Gateway
|
|
84
|
+
|
|
85
|
+
Usage:
|
|
86
|
+
imtoagent setup Interactive setup wizard
|
|
87
|
+
imtoagent start Start gateway in background (returns immediately)
|
|
88
|
+
imtoagent run Start gateway in foreground (Ctrl+C to stop)
|
|
89
|
+
imtoagent stop Stop gateway
|
|
90
|
+
imtoagent status Check running status
|
|
91
|
+
imtoagent restore Hot reload
|
|
92
|
+
imtoagent daemon Foreground daemon with auto-restart (for launchd/systemd)
|
|
93
|
+
|
|
94
|
+
Data directory: ${getDataDir()}
|
|
91
95
|
`);
|
|
92
96
|
}
|
|
93
97
|
|
|
94
98
|
// ================================================================
|
|
95
|
-
// setup —
|
|
99
|
+
// setup — interactive wizard
|
|
96
100
|
// ================================================================
|
|
97
101
|
async function cmdSetup() {
|
|
98
102
|
const { runSetupWizard } = await import('../modules/cli/setup');
|
|
@@ -100,32 +104,45 @@ async function cmdSetup() {
|
|
|
100
104
|
}
|
|
101
105
|
|
|
102
106
|
// ================================================================
|
|
103
|
-
//
|
|
107
|
+
// Shared: build gateway launch args
|
|
108
|
+
// ================================================================
|
|
109
|
+
function getGatewayArgs() {
|
|
110
|
+
const pkgDir = path.resolve(import.meta.dirname, '..');
|
|
111
|
+
const indexFile = path.join(pkgDir, 'index.ts');
|
|
112
|
+
return { execPath: process.execPath, args: ['run', indexFile] };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function ensureLogDir(dataDir: string) {
|
|
116
|
+
const logsDir = path.join(dataDir, 'logs');
|
|
117
|
+
if (!fs.existsSync(logsDir)) fs.mkdirSync(logsDir, { recursive: true });
|
|
118
|
+
return path.join(logsDir, 'imtoagent.log');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ================================================================
|
|
122
|
+
// start — background mode (spawn detached, log to file, return immediately)
|
|
104
123
|
// ================================================================
|
|
105
124
|
async function cmdStart() {
|
|
106
|
-
//
|
|
125
|
+
// Check if already running
|
|
107
126
|
if (fs.existsSync(PID_FILE)) {
|
|
108
127
|
const pid = parseInt(fs.readFileSync(PID_FILE, 'utf-8').trim());
|
|
109
128
|
try {
|
|
110
129
|
process.kill(pid, 0);
|
|
111
|
-
console.error(`❌
|
|
112
|
-
console.error(`
|
|
130
|
+
console.error(`❌ Gateway already running (PID=${pid})`);
|
|
131
|
+
console.error(` Run "imtoagent stop" to stop first`);
|
|
113
132
|
process.exit(1);
|
|
114
133
|
} catch {
|
|
115
|
-
// 旧 PID 文件残留,清理
|
|
116
134
|
fs.unlinkSync(PID_FILE);
|
|
117
135
|
}
|
|
118
136
|
}
|
|
119
137
|
|
|
120
|
-
// 检查配置是否存在
|
|
121
138
|
const dataDir = getDataDir();
|
|
122
139
|
const configPath = path.join(dataDir, 'config.json');
|
|
123
140
|
if (!fs.existsSync(configPath)) {
|
|
124
|
-
console.error('❌
|
|
141
|
+
console.error('❌ No config file found. Please run "imtoagent setup" first');
|
|
125
142
|
process.exit(1);
|
|
126
143
|
}
|
|
127
144
|
|
|
128
|
-
//
|
|
145
|
+
// Backend check (non-blocking)
|
|
129
146
|
try {
|
|
130
147
|
const { checkBackend } = await import('../modules/utils/backend-check');
|
|
131
148
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
@@ -139,26 +156,90 @@ async function cmdStart() {
|
|
|
139
156
|
}
|
|
140
157
|
}
|
|
141
158
|
if (missingBackends.length > 0) {
|
|
142
|
-
console.error(`\n⚠️
|
|
143
|
-
for (const b of missingBackends) {
|
|
144
|
-
|
|
145
|
-
}
|
|
146
|
-
console.error(`\n请先安装缺失的后端,或运行 "imtoagent setup" 修改配置。\n`);
|
|
147
|
-
// 不强制退出,允许用户先启动网关再慢慢装后端
|
|
159
|
+
console.error(`\n⚠️ The following backends are configured but not installed, messages will fail after gateway starts:`);
|
|
160
|
+
for (const b of missingBackends) console.error(` ❌ ${b}`);
|
|
161
|
+
console.error(`\nPlease install the missing backends, or run "imtoagent setup" to reconfigure.\n`);
|
|
148
162
|
}
|
|
149
163
|
} catch {
|
|
150
|
-
//
|
|
164
|
+
// Check failure doesn't block startup
|
|
151
165
|
}
|
|
152
166
|
|
|
153
|
-
|
|
154
|
-
console.log(` 数据目录: ${dataDir}`);
|
|
155
|
-
console.log(` 配置文件: ${configPath}`);
|
|
167
|
+
const logFile = ensureLogDir(dataDir);
|
|
156
168
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
169
|
+
console.log('🚀 Starting imtoagent gateway (background)...');
|
|
170
|
+
console.log(` Data directory: ${dataDir}`);
|
|
171
|
+
console.log(` Log file: ${logFile}`);
|
|
172
|
+
|
|
173
|
+
const { execPath, args } = getGatewayArgs();
|
|
174
|
+
const cmdLine = `"${execPath}" run "${path.resolve(import.meta.dirname, '..', 'index.ts')}"`;
|
|
175
|
+
|
|
176
|
+
// Use a shell to launch the gateway in background — avoids event-loop blockers
|
|
177
|
+
const shellCmd = `IMTOAGENT_HOME="${dataDir}" ${cmdLine} >> "${logFile}" 2>&1 &
|
|
178
|
+
PID=$!
|
|
179
|
+
echo $PID`;
|
|
180
|
+
|
|
181
|
+
const { execSync } = await import('child_process');
|
|
182
|
+
const pidStr = execSync(shellCmd, {
|
|
183
|
+
cwd: dataDir,
|
|
184
|
+
env: { ...process.env, IMTOAGENT_HOME: dataDir },
|
|
185
|
+
encoding: 'utf-8',
|
|
186
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
187
|
+
}).trim();
|
|
188
|
+
const gatewayPid = parseInt(pidStr.split('\n').pop()!);
|
|
189
|
+
|
|
190
|
+
fs.writeFileSync(PID_FILE, String(gatewayPid));
|
|
191
|
+
console.log(`✅ Gateway started (PID=${gatewayPid})`);
|
|
192
|
+
|
|
193
|
+
// Wait for startup verification
|
|
194
|
+
await new Promise(r => setTimeout(r, 3000));
|
|
195
|
+
try {
|
|
196
|
+
process.kill(gatewayPid, 0);
|
|
197
|
+
console.log('✅ Gateway is running');
|
|
198
|
+
} catch {
|
|
199
|
+
console.error('❌ Gateway failed to start, check logs:');
|
|
200
|
+
if (fs.existsSync(logFile)) {
|
|
201
|
+
console.log(fs.readFileSync(logFile, 'utf-8').slice(-2000));
|
|
202
|
+
}
|
|
203
|
+
fs.unlinkSync(PID_FILE);
|
|
204
|
+
process.exit(1);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Explicitly exit — Bun may keep event loop alive due to inherited stdio
|
|
208
|
+
process.exit(0);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ================================================================
|
|
212
|
+
// run — foreground mode (real-time logs, Ctrl+C to stop)
|
|
213
|
+
// ================================================================
|
|
214
|
+
async function cmdRun() {
|
|
215
|
+
const dataDir = getDataDir();
|
|
216
|
+
const configPath = path.join(dataDir, 'config.json');
|
|
217
|
+
if (!fs.existsSync(configPath)) {
|
|
218
|
+
console.error('❌ No config file found. Please run "imtoagent setup" first');
|
|
219
|
+
process.exit(1);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const logFile = ensureLogDir(dataDir);
|
|
223
|
+
|
|
224
|
+
// Warn if already running in background
|
|
225
|
+
if (fs.existsSync(PID_FILE)) {
|
|
226
|
+
const pid = parseInt(fs.readFileSync(PID_FILE, 'utf-8').trim());
|
|
227
|
+
try {
|
|
228
|
+
process.kill(pid, 0);
|
|
229
|
+
console.log(`⚠️ Gateway is already running in background (PID=${pid})`);
|
|
230
|
+
console.log(` Run "imtoagent stop" first, or this may conflict.\n`);
|
|
231
|
+
} catch {
|
|
232
|
+
fs.unlinkSync(PID_FILE);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
160
235
|
|
|
161
|
-
const
|
|
236
|
+
const { execPath, args } = getGatewayArgs();
|
|
237
|
+
|
|
238
|
+
console.log('🚀 Starting imtoagent gateway (foreground mode)...');
|
|
239
|
+
console.log(' Press Ctrl+C to stop');
|
|
240
|
+
console.log('');
|
|
241
|
+
|
|
242
|
+
const child = Bun.spawn([execPath, ...args], {
|
|
162
243
|
cwd: dataDir,
|
|
163
244
|
env: { ...process.env, IMTOAGENT_HOME: dataDir },
|
|
164
245
|
stdout: 'pipe',
|
|
@@ -166,16 +247,10 @@ async function cmdStart() {
|
|
|
166
247
|
});
|
|
167
248
|
|
|
168
249
|
fs.writeFileSync(PID_FILE, String(child.pid));
|
|
169
|
-
console.log(`✅ 网关已启动 (PID=${child.pid})`);
|
|
170
250
|
|
|
171
|
-
|
|
172
|
-
const logsDir = path.join(dataDir, 'logs');
|
|
173
|
-
if (!fs.existsSync(logsDir)) fs.mkdirSync(logsDir, { recursive: true });
|
|
174
|
-
const logFile = path.join(logsDir, 'imtoagent.log');
|
|
251
|
+
const logStream = fs.createWriteStream(logFile, { flags: 'a' });
|
|
175
252
|
|
|
176
|
-
|
|
177
|
-
(async () => {
|
|
178
|
-
const logStream = fs.createWriteStream(logFile, { flags: 'a' });
|
|
253
|
+
const pumpOut = (async () => {
|
|
179
254
|
for await (const chunk of child.stdout as any) {
|
|
180
255
|
const line = new TextDecoder().decode(chunk);
|
|
181
256
|
process.stdout.write(line);
|
|
@@ -183,8 +258,7 @@ async function cmdStart() {
|
|
|
183
258
|
}
|
|
184
259
|
})().catch(() => {});
|
|
185
260
|
|
|
186
|
-
(async () => {
|
|
187
|
-
const logStream = fs.createWriteStream(logFile, { flags: 'a' });
|
|
261
|
+
const pumpErr = (async () => {
|
|
188
262
|
for await (const chunk of child.stderr as any) {
|
|
189
263
|
const line = new TextDecoder().decode(chunk);
|
|
190
264
|
process.stderr.write(line);
|
|
@@ -192,37 +266,42 @@ async function cmdStart() {
|
|
|
192
266
|
}
|
|
193
267
|
})().catch(() => {});
|
|
194
268
|
|
|
195
|
-
//
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
process.kill(child.pid,
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
269
|
+
// Ctrl+C → SIGTERM to child
|
|
270
|
+
const cleanup = () => {
|
|
271
|
+
console.log('\n🛑 Stopping gateway...');
|
|
272
|
+
try { process.kill(child.pid, 'SIGTERM'); } catch {}
|
|
273
|
+
};
|
|
274
|
+
process.on('SIGINT', cleanup);
|
|
275
|
+
process.on('SIGTERM', cleanup);
|
|
276
|
+
|
|
277
|
+
const exitCode = await child.exited;
|
|
278
|
+
await Promise.allSettled([pumpOut, pumpErr]);
|
|
279
|
+
logStream.end();
|
|
280
|
+
|
|
281
|
+
try { fs.unlinkSync(PID_FILE); } catch {}
|
|
282
|
+
|
|
283
|
+
if (exitCode === 0) {
|
|
284
|
+
console.log('✅ Gateway exited cleanly');
|
|
285
|
+
} else {
|
|
286
|
+
console.log(`⚠️ Gateway exited with code ${exitCode}`);
|
|
207
287
|
}
|
|
208
288
|
}
|
|
209
289
|
|
|
210
290
|
// ================================================================
|
|
211
|
-
// stop —
|
|
291
|
+
// stop — stop gateway
|
|
212
292
|
// ================================================================
|
|
213
293
|
async function cmdStop() {
|
|
214
294
|
if (!fs.existsSync(PID_FILE)) {
|
|
215
|
-
console.log('ℹ️
|
|
295
|
+
console.log('ℹ️ Gateway is not running');
|
|
216
296
|
return;
|
|
217
297
|
}
|
|
218
298
|
|
|
219
299
|
const pid = parseInt(fs.readFileSync(PID_FILE, 'utf-8').trim());
|
|
220
300
|
try {
|
|
221
301
|
process.kill(pid, 0);
|
|
222
|
-
console.log(`⏹
|
|
302
|
+
console.log(`⏹ Stopping gateway (PID=${pid})...`);
|
|
223
303
|
process.kill(pid, 'SIGTERM');
|
|
224
304
|
|
|
225
|
-
// 等待退出
|
|
226
305
|
for (let i = 0; i < 20; i++) {
|
|
227
306
|
try {
|
|
228
307
|
process.kill(pid, 0);
|
|
@@ -232,145 +311,127 @@ async function cmdStop() {
|
|
|
232
311
|
}
|
|
233
312
|
}
|
|
234
313
|
|
|
235
|
-
// 检查是否还在
|
|
236
314
|
try {
|
|
237
315
|
process.kill(pid, 0);
|
|
238
|
-
console.log('⚠️
|
|
316
|
+
console.log('⚠️ Process not responding, force killing...');
|
|
239
317
|
process.kill(pid, 'SIGKILL');
|
|
240
318
|
} catch {
|
|
241
|
-
console.log('✅
|
|
319
|
+
console.log('✅ Gateway stopped');
|
|
242
320
|
}
|
|
243
321
|
} catch {
|
|
244
|
-
console.log('ℹ️
|
|
322
|
+
console.log('ℹ️ Gateway not running (stale PID file cleaned up)');
|
|
245
323
|
}
|
|
246
324
|
|
|
247
325
|
try { fs.unlinkSync(PID_FILE); } catch {}
|
|
248
326
|
}
|
|
249
327
|
|
|
250
328
|
// ================================================================
|
|
251
|
-
// status —
|
|
329
|
+
// status — status check
|
|
252
330
|
// ================================================================
|
|
253
331
|
async function cmdStatus() {
|
|
254
332
|
const dataDir = getDataDir();
|
|
255
333
|
|
|
256
|
-
console.log(`\n📊 imtoagent
|
|
257
|
-
console.log(`
|
|
334
|
+
console.log(`\n📊 imtoagent Status`);
|
|
335
|
+
console.log(` Data directory: ${dataDir}`);
|
|
258
336
|
|
|
259
|
-
// 进程状态
|
|
260
337
|
if (fs.existsSync(PID_FILE)) {
|
|
261
338
|
const pid = parseInt(fs.readFileSync(PID_FILE, 'utf-8').trim());
|
|
262
339
|
try {
|
|
263
340
|
process.kill(pid, 0);
|
|
264
|
-
console.log(`
|
|
341
|
+
console.log(` Process: ✅ Running (PID=${pid})`);
|
|
265
342
|
} catch {
|
|
266
|
-
console.log(`
|
|
343
|
+
console.log(` Process: ❌ Stopped (PID=${pid} does not exist)`);
|
|
267
344
|
}
|
|
268
345
|
} else {
|
|
269
|
-
console.log(`
|
|
346
|
+
console.log(` Process: ⏸ Not running`);
|
|
270
347
|
}
|
|
271
348
|
|
|
272
|
-
// 配置文件
|
|
273
349
|
const configPath = path.join(dataDir, 'config.json');
|
|
274
350
|
if (fs.existsSync(configPath)) {
|
|
275
351
|
try {
|
|
276
352
|
const cfg = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
277
353
|
const bots = cfg.bots || [];
|
|
278
|
-
console.log(`
|
|
354
|
+
console.log(` Config: ✅ Configured (${bots.length} Bot(s))`);
|
|
279
355
|
for (const bot of bots) {
|
|
280
356
|
console.log(` - ${bot.name} (${bot.backend})`);
|
|
281
357
|
}
|
|
282
358
|
} catch {
|
|
283
|
-
console.log(`
|
|
359
|
+
console.log(` Config: ❌ Parse error`);
|
|
284
360
|
}
|
|
285
361
|
} else {
|
|
286
|
-
console.log(`
|
|
362
|
+
console.log(` Config: ❌ Not found (run "imtoagent setup")`);
|
|
287
363
|
}
|
|
288
364
|
|
|
289
|
-
// 日志
|
|
290
365
|
const logFile = path.join(dataDir, 'logs', 'imtoagent.log');
|
|
291
366
|
if (fs.existsSync(logFile)) {
|
|
292
367
|
const stats = fs.statSync(logFile);
|
|
293
368
|
const size = stats.size > 1024 * 1024
|
|
294
369
|
? (stats.size / (1024 * 1024)).toFixed(1) + ' MB'
|
|
295
370
|
: (stats.size / 1024).toFixed(1) + ' KB';
|
|
296
|
-
console.log(`
|
|
371
|
+
console.log(` Log: ${size} (${logFile})`);
|
|
297
372
|
}
|
|
298
373
|
|
|
299
374
|
console.log();
|
|
300
375
|
}
|
|
301
376
|
|
|
302
377
|
// ================================================================
|
|
303
|
-
// restore —
|
|
378
|
+
// restore — hot reload
|
|
304
379
|
// ================================================================
|
|
305
380
|
async function cmdRestore() {
|
|
306
381
|
if (!fs.existsSync(PID_FILE)) {
|
|
307
|
-
console.error('❌
|
|
382
|
+
console.error('❌ Gateway is not running, cannot hot reload');
|
|
308
383
|
process.exit(1);
|
|
309
384
|
}
|
|
310
385
|
|
|
311
386
|
const pid = parseInt(fs.readFileSync(PID_FILE, 'utf-8').trim());
|
|
312
|
-
console.log(`🔄
|
|
387
|
+
console.log(`🔄 Sending SIGHUP to gateway (PID=${pid})...`);
|
|
313
388
|
try {
|
|
314
389
|
process.kill(pid, 'SIGHUP');
|
|
315
|
-
console.log('✅
|
|
390
|
+
console.log('✅ Hot reload signal sent');
|
|
316
391
|
} catch (e: any) {
|
|
317
|
-
console.error(`❌
|
|
392
|
+
console.error(`❌ Failed to send: ${e.message}`);
|
|
318
393
|
process.exit(1);
|
|
319
394
|
}
|
|
320
395
|
}
|
|
321
396
|
|
|
322
397
|
// ================================================================
|
|
323
|
-
// daemon —
|
|
324
|
-
// ================================================================
|
|
325
|
-
// 设计用途:
|
|
326
|
-
// - 前台运行,被 launchd / systemd 等进程管理器托管
|
|
327
|
-
// - 崩溃时自动重启(指数退避,最长 30s)
|
|
328
|
-
// - 收到 SIGTERM/SIGINT 时优雅关闭,不重启
|
|
329
|
-
// - 日志写入 ~/.imtoagent/logs/imtoagent.log
|
|
398
|
+
// daemon — foreground daemon with auto-restart (for launchd/systemd)
|
|
330
399
|
// ================================================================
|
|
331
400
|
async function cmdDaemon(): Promise<void> {
|
|
332
401
|
const dataDir = getDataDir();
|
|
333
402
|
const configPath = path.join(dataDir, 'config.json');
|
|
334
403
|
|
|
335
404
|
if (!fs.existsSync(configPath)) {
|
|
336
|
-
console.error('❌
|
|
405
|
+
console.error('❌ No config file found. Please run "imtoagent setup" first');
|
|
337
406
|
process.exit(1);
|
|
338
407
|
}
|
|
339
408
|
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
const logFile = path.join(logsDir, 'imtoagent.log');
|
|
343
|
-
|
|
344
|
-
const pkgDir = path.resolve(import.meta.dirname, '..');
|
|
345
|
-
const indexFile = path.join(pkgDir, 'index.ts');
|
|
409
|
+
const logFile = ensureLogDir(dataDir);
|
|
410
|
+
const { execPath, args } = getGatewayArgs();
|
|
346
411
|
|
|
347
|
-
console.log(`🛡 imtoagent
|
|
348
|
-
console.log(`
|
|
349
|
-
console.log(`
|
|
350
|
-
console.log(`
|
|
412
|
+
console.log(`🛡 imtoagent Daemon Mode`);
|
|
413
|
+
console.log(` Data directory: ${dataDir}`);
|
|
414
|
+
console.log(` Log file: ${logFile}`);
|
|
415
|
+
console.log(` Press Ctrl+C to stop\n`);
|
|
351
416
|
|
|
352
|
-
// 优雅退出标记
|
|
353
417
|
let shuttingDown = false;
|
|
354
|
-
|
|
355
418
|
const shutdown = () => {
|
|
356
419
|
if (shuttingDown) return;
|
|
357
420
|
shuttingDown = true;
|
|
358
|
-
console.log('\n🛑
|
|
421
|
+
console.log('\n🛑 Received stop signal, shutting down...');
|
|
359
422
|
};
|
|
360
423
|
|
|
361
424
|
process.on('SIGTERM', shutdown);
|
|
362
425
|
process.on('SIGINT', shutdown);
|
|
363
426
|
|
|
364
427
|
let retryDelay = 0;
|
|
365
|
-
const MAX_RETRY_DELAY = 30_000;
|
|
428
|
+
const MAX_RETRY_DELAY = 30_000;
|
|
366
429
|
|
|
367
430
|
while (!shuttingDown) {
|
|
368
|
-
// 首次无延迟,之后指数退避
|
|
369
431
|
if (retryDelay > 0) {
|
|
370
|
-
console.log(`
|
|
432
|
+
console.log(` Waiting ${retryDelay / 1000}s before restart...`);
|
|
371
433
|
await new Promise<void>(resolve => {
|
|
372
434
|
const timer = setTimeout(resolve, retryDelay);
|
|
373
|
-
// 等待期间如果收到停止信号,立即退出
|
|
374
435
|
const check = setInterval(() => {
|
|
375
436
|
if (shuttingDown) {
|
|
376
437
|
clearTimeout(timer);
|
|
@@ -382,56 +443,39 @@ async function cmdDaemon(): Promise<void> {
|
|
|
382
443
|
if (shuttingDown) break;
|
|
383
444
|
}
|
|
384
445
|
|
|
385
|
-
|
|
446
|
+
// Open log fd for child stdout/stderr
|
|
447
|
+
const logFd = fs.openSync(logFile, 'a');
|
|
386
448
|
|
|
387
|
-
const child =
|
|
449
|
+
const child = spawn(execPath, args, {
|
|
388
450
|
cwd: dataDir,
|
|
389
451
|
env: { ...process.env, IMTOAGENT_HOME: dataDir },
|
|
390
|
-
|
|
391
|
-
|
|
452
|
+
detached: true,
|
|
453
|
+
stdio: ['ignore', logFd, logFd],
|
|
392
454
|
});
|
|
393
455
|
|
|
394
456
|
const childPid = child.pid;
|
|
395
457
|
fs.writeFileSync(PID_FILE, String(childPid));
|
|
396
|
-
console.log(`[${new Date().toISOString()}] 🚀
|
|
397
|
-
|
|
398
|
-
// 日志收集
|
|
399
|
-
const pumpStdout = (async () => {
|
|
400
|
-
for await (const chunk of child.stdout as any) {
|
|
401
|
-
const line = new TextDecoder().decode(chunk);
|
|
402
|
-
process.stdout.write(line);
|
|
403
|
-
logStream.write(line);
|
|
404
|
-
}
|
|
405
|
-
})().catch(() => {});
|
|
406
|
-
|
|
407
|
-
const pumpStderr = (async () => {
|
|
408
|
-
for await (const chunk of child.stderr as any) {
|
|
409
|
-
const line = new TextDecoder().decode(chunk);
|
|
410
|
-
process.stderr.write(line);
|
|
411
|
-
logStream.write(line);
|
|
412
|
-
}
|
|
413
|
-
})().catch(() => {});
|
|
458
|
+
console.log(`[${new Date().toISOString()}] 🚀 Starting gateway (PID=${childPid})`);
|
|
414
459
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
460
|
+
let childExitCode: number | null = null;
|
|
461
|
+
await new Promise<void>(resolve => {
|
|
462
|
+
child.on('exit', (code) => { childExitCode = code; resolve(); });
|
|
463
|
+
child.on('error', () => resolve());
|
|
464
|
+
});
|
|
419
465
|
|
|
466
|
+
fs.closeSync(logFd);
|
|
420
467
|
try { fs.unlinkSync(PID_FILE); } catch {}
|
|
421
468
|
|
|
422
469
|
if (shuttingDown) break;
|
|
423
470
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
console.log(`[${new Date().toISOString()}] ⏹ 网关正常退出 (code=0),不重启`);
|
|
471
|
+
if (childExitCode === 0) {
|
|
472
|
+
console.log(`[${new Date().toISOString()}] ⏹ Gateway exited cleanly (code=0), not restarting`);
|
|
427
473
|
break;
|
|
428
474
|
}
|
|
429
475
|
|
|
430
|
-
// 崩溃 → 指数退避重启
|
|
431
476
|
retryDelay = retryDelay === 0 ? 3_000 : Math.min(retryDelay * 2, MAX_RETRY_DELAY);
|
|
432
|
-
console.log(`[${new Date().toISOString()}] ⚠️
|
|
477
|
+
console.log(`[${new Date().toISOString()}] ⚠️ Gateway crashed (code=${childExitCode}), restarting in ${retryDelay / 1000}s`);
|
|
433
478
|
}
|
|
434
479
|
|
|
435
|
-
console.log('👋
|
|
480
|
+
console.log('👋 Daemon stopped');
|
|
436
481
|
}
|
|
437
|
-
|
package/bin/imtoagent.cjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"use strict";
|
|
4
4
|
var path = require("path");
|
|
5
5
|
var fs = require("fs");
|
|
6
|
-
var
|
|
6
|
+
var spawn = require("child_process").spawn;
|
|
7
7
|
|
|
8
8
|
var candidates = [
|
|
9
9
|
process.env.BUN_BIN,
|
|
@@ -12,7 +12,7 @@ var candidates = [
|
|
|
12
12
|
"/opt/homebrew/bin/bun",
|
|
13
13
|
];
|
|
14
14
|
try {
|
|
15
|
-
var r = spawnSync("which", ["bun"]);
|
|
15
|
+
var r = require("child_process").spawnSync("which", ["bun"]);
|
|
16
16
|
if (r.status === 0) candidates.unshift(r.stdout.toString().trim());
|
|
17
17
|
} catch (e) {}
|
|
18
18
|
|
|
@@ -25,15 +25,23 @@ for (var i = 0; i < candidates.length; i++) {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
if (!bunPath) {
|
|
28
|
-
console.error("❌ bun
|
|
28
|
+
console.error("❌ bun not found, please install: https://bun.sh");
|
|
29
29
|
console.error(" curl -fsSL https://bun.sh/install | bash");
|
|
30
30
|
process.exit(1);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
var pkgDir = path.resolve(__dirname, "..");
|
|
34
34
|
var real = path.join(pkgDir, "bin", "imtoagent-real");
|
|
35
|
-
var
|
|
35
|
+
var child = spawn(bunPath, [real].concat(process.argv.slice(2)), {
|
|
36
36
|
stdio: "inherit",
|
|
37
37
|
env: Object.assign({}, process.env),
|
|
38
38
|
});
|
|
39
|
-
|
|
39
|
+
|
|
40
|
+
child.on("exit", function (code) {
|
|
41
|
+
process.exit(code || 0);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
child.on("error", function (err) {
|
|
45
|
+
console.error("❌ Failed to start imtoagent:", err.message);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
});
|