imtoagent 0.3.25 → 0.3.26
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/bin/imtoagent-real +190 -1
- package/modules/utils/config-manager.ts +15 -0
- package/package.json +1 -1
package/bin/imtoagent-real
CHANGED
|
@@ -16,7 +16,7 @@ import * as fs from 'fs';
|
|
|
16
16
|
import * as path from 'path';
|
|
17
17
|
import * as readline from 'readline';
|
|
18
18
|
import { spawn, execSync } from 'child_process';
|
|
19
|
-
import { getDataDir } from '../modules/utils/paths';
|
|
19
|
+
import { getDataDir, getLogsDir } from '../modules/utils/paths';
|
|
20
20
|
|
|
21
21
|
const PID_FILE = '/tmp/imtoagent.pid';
|
|
22
22
|
|
|
@@ -70,6 +70,12 @@ switch (command) {
|
|
|
70
70
|
case 'autostart':
|
|
71
71
|
await cmdAutostart();
|
|
72
72
|
break;
|
|
73
|
+
case 'logs':
|
|
74
|
+
await cmdLogs();
|
|
75
|
+
break;
|
|
76
|
+
case 'validate':
|
|
77
|
+
await cmdValidate();
|
|
78
|
+
break;
|
|
73
79
|
case undefined: {
|
|
74
80
|
const dataDir = getDataDir();
|
|
75
81
|
const configPath = path.join(dataDir, 'config.json');
|
|
@@ -137,6 +143,10 @@ Usage:
|
|
|
137
143
|
imtoagent autostart enable Enable auto-start on login (launchd)
|
|
138
144
|
imtoagent autostart disable Disable auto-start
|
|
139
145
|
imtoagent autostart status Check auto-start status
|
|
146
|
+
imtoagent logs Show last 50 lines of log
|
|
147
|
+
imtoagent logs -n N Show last N lines
|
|
148
|
+
imtoagent logs -f Follow log in real-time (tail -f)
|
|
149
|
+
imtoagent validate Validate config.json for errors
|
|
140
150
|
|
|
141
151
|
Data directory: ${getDataDir()}
|
|
142
152
|
`);
|
|
@@ -1268,6 +1278,185 @@ async function cmdAutostartStatus(plistPath: string): Promise<void> {
|
|
|
1268
1278
|
console.log();
|
|
1269
1279
|
}
|
|
1270
1280
|
|
|
1281
|
+
// ================================================================
|
|
1282
|
+
// logs — 查看日志
|
|
1283
|
+
// ================================================================
|
|
1284
|
+
async function cmdLogs(): Promise<void> {
|
|
1285
|
+
const argv = process.argv.slice(3);
|
|
1286
|
+
let n = 50;
|
|
1287
|
+
let follow = false;
|
|
1288
|
+
|
|
1289
|
+
for (let i = 0; i < argv.length; i++) {
|
|
1290
|
+
if (argv[i] === '-n' && argv[i + 1]) {
|
|
1291
|
+
n = parseInt(argv[++i], 10);
|
|
1292
|
+
if (isNaN(n) || n < 1) {
|
|
1293
|
+
console.error('❌ Invalid line count');
|
|
1294
|
+
process.exit(1);
|
|
1295
|
+
}
|
|
1296
|
+
} else if (argv[i] === '-f') {
|
|
1297
|
+
follow = true;
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
const logsDir = getLogsDir();
|
|
1302
|
+
const logFile = path.join(logsDir, 'imtoagent.log');
|
|
1303
|
+
|
|
1304
|
+
if (!fs.existsSync(logFile)) {
|
|
1305
|
+
console.log('ℹ️ No log file found.');
|
|
1306
|
+
console.log(` Expected: ${logFile}`);
|
|
1307
|
+
console.log(' Start the gateway first: imtoagent start');
|
|
1308
|
+
return;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
if (follow) {
|
|
1312
|
+
// tail -f mode — spawn tail process
|
|
1313
|
+
console.log(`📄 Following log: ${logFile} (Ctrl+C to stop)\n`);
|
|
1314
|
+
const child = spawn('tail', ['-f', '-n', String(n), logFile], {
|
|
1315
|
+
stdio: 'inherit',
|
|
1316
|
+
});
|
|
1317
|
+
|
|
1318
|
+
const cleanup = () => {
|
|
1319
|
+
try { child.kill('SIGTERM'); } catch {}
|
|
1320
|
+
};
|
|
1321
|
+
process.on('SIGINT', cleanup);
|
|
1322
|
+
process.on('SIGTERM', cleanup);
|
|
1323
|
+
|
|
1324
|
+
await new Promise<void>((resolve) => {
|
|
1325
|
+
child.on('exit', () => resolve());
|
|
1326
|
+
child.on('error', () => resolve());
|
|
1327
|
+
});
|
|
1328
|
+
} else {
|
|
1329
|
+
// Print last N lines
|
|
1330
|
+
try {
|
|
1331
|
+
const content = fs.readFileSync(logFile, 'utf-8');
|
|
1332
|
+
const lines = content.split('\n');
|
|
1333
|
+
const tail = lines.slice(-n).filter(l => l.length > 0);
|
|
1334
|
+
if (tail.length === 0) {
|
|
1335
|
+
console.log('ℹ️ Log file is empty.');
|
|
1336
|
+
} else {
|
|
1337
|
+
console.log(`📄 Last ${tail.length} lines of ${logFile}:\n`);
|
|
1338
|
+
for (const line of tail) {
|
|
1339
|
+
console.log(line);
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
} catch (e: any) {
|
|
1343
|
+
console.error(`❌ Failed to read log: ${e.message}`);
|
|
1344
|
+
process.exit(1);
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
// ================================================================
|
|
1350
|
+
// validate — 验证配置文件
|
|
1351
|
+
// ================================================================
|
|
1352
|
+
async function cmdValidate(): Promise<void> {
|
|
1353
|
+
const dataDir = getDataDir();
|
|
1354
|
+
const configPath = path.join(dataDir, 'config.json');
|
|
1355
|
+
|
|
1356
|
+
console.log(`\n🔍 imtoagent Config Validation\n`);
|
|
1357
|
+
console.log(` Config: ${configPath}\n`);
|
|
1358
|
+
|
|
1359
|
+
const issues: string[] = [];
|
|
1360
|
+
const okItems: string[] = [];
|
|
1361
|
+
|
|
1362
|
+
// ---- 1. Check file exists ----
|
|
1363
|
+
if (!fs.existsSync(configPath)) {
|
|
1364
|
+
console.error('❌ config.json not found');
|
|
1365
|
+
console.error(` Run "imtoagent setup" to create it.\n`);
|
|
1366
|
+
process.exit(1);
|
|
1367
|
+
}
|
|
1368
|
+
okItems.push('config.json exists');
|
|
1369
|
+
|
|
1370
|
+
// ---- 2. Parse JSON ----
|
|
1371
|
+
let raw: string;
|
|
1372
|
+
let config: any;
|
|
1373
|
+
try {
|
|
1374
|
+
raw = fs.readFileSync(configPath, 'utf-8');
|
|
1375
|
+
config = JSON.parse(raw);
|
|
1376
|
+
okItems.push('JSON format is valid');
|
|
1377
|
+
} catch (e: any) {
|
|
1378
|
+
console.error(`❌ Invalid JSON: ${e.message}`);
|
|
1379
|
+
console.error(` Please fix the syntax error and try again.\n`);
|
|
1380
|
+
process.exit(1);
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
// ---- 3. Validate bots array ----
|
|
1384
|
+
const validIMs = ['feishu', 'telegram', 'wecom', 'wechat'];
|
|
1385
|
+
const validBackends = ['claude', 'codex', 'opencode'];
|
|
1386
|
+
|
|
1387
|
+
if (!config.bots || !Array.isArray(config.bots)) {
|
|
1388
|
+
issues.push('bots is missing or not an array');
|
|
1389
|
+
} else if (config.bots.length === 0) {
|
|
1390
|
+
issues.push('bots array is empty — no bots configured');
|
|
1391
|
+
} else {
|
|
1392
|
+
okItems.push(`bots array has ${config.bots.length} entry/entries`);
|
|
1393
|
+
|
|
1394
|
+
config.bots.forEach((bot: any, idx: number) => {
|
|
1395
|
+
const label = `bots[${idx}]`;
|
|
1396
|
+
const botName = bot.name || `(unnamed, index ${idx})`;
|
|
1397
|
+
|
|
1398
|
+
// Check required fields
|
|
1399
|
+
if (!bot.name) issues.push(`${label}: missing required field "name"`);
|
|
1400
|
+
if (!bot.im) issues.push(`${label}: missing required field "im"`);
|
|
1401
|
+
if (!bot.appId) issues.push(`${label}: missing required field "appId"`);
|
|
1402
|
+
if (!bot.backend) issues.push(`${label}: missing required field "backend"`);
|
|
1403
|
+
|
|
1404
|
+
// Validate IM platform
|
|
1405
|
+
if (bot.im && !validIMs.includes(bot.im)) {
|
|
1406
|
+
issues.push(`${label}: invalid im value "${bot.im}" — must be one of: ${validIMs.join(', ')}`);
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
// Validate backend
|
|
1410
|
+
if (bot.backend && !validBackends.includes(bot.backend)) {
|
|
1411
|
+
issues.push(`${label}: invalid backend value "${bot.backend}" — must be one of: ${validBackends.join(', ')}`);
|
|
1412
|
+
}
|
|
1413
|
+
});
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
// ---- 4. Validate workspace config ----
|
|
1417
|
+
const ws = config.workspace || {};
|
|
1418
|
+
if (ws.mode === 'global') {
|
|
1419
|
+
if (!ws.globalPath) {
|
|
1420
|
+
issues.push('workspace: mode is "global" but globalPath is not set');
|
|
1421
|
+
} else {
|
|
1422
|
+
if (!fs.existsSync(ws.globalPath)) {
|
|
1423
|
+
issues.push(`workspace: globalPath directory does not exist: ${ws.globalPath}`);
|
|
1424
|
+
} else {
|
|
1425
|
+
okItems.push(`workspace: globalPath exists (${ws.globalPath})`);
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
} else if (ws.mode && ws.mode !== 'sandbox') {
|
|
1429
|
+
issues.push(`workspace: invalid mode "${ws.mode}" — must be "sandbox" or "global"`);
|
|
1430
|
+
} else {
|
|
1431
|
+
okItems.push(`workspace: mode is "${ws.mode || 'sandbox'}" (default)`);
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
// ---- Print results ----
|
|
1435
|
+
if (okItems.length > 0) {
|
|
1436
|
+
console.log('✅ OK:');
|
|
1437
|
+
for (const item of okItems) {
|
|
1438
|
+
console.log(` ✓ ${item}`);
|
|
1439
|
+
}
|
|
1440
|
+
console.log();
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
if (issues.length > 0) {
|
|
1444
|
+
console.log('❌ Issues:');
|
|
1445
|
+
for (const issue of issues) {
|
|
1446
|
+
console.log(` ✗ ${issue}`);
|
|
1447
|
+
}
|
|
1448
|
+
console.log();
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
// Summary
|
|
1452
|
+
if (issues.length === 0) {
|
|
1453
|
+
console.log('✅ Configuration is valid!\n');
|
|
1454
|
+
} else {
|
|
1455
|
+
console.log(`❌ ${issues.length} issue(s) found. Please fix them.\n`);
|
|
1456
|
+
process.exit(1);
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1271
1460
|
// ================================================================
|
|
1272
1461
|
// version-check — non-blocking npm registry check
|
|
1273
1462
|
// ================================================================
|
|
@@ -141,6 +141,7 @@ export async function cmdConfigShow(name: string): Promise<void> {
|
|
|
141
141
|
console.log();
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
/** @internal — exported for testing via __TEST_MASK_SECRET */
|
|
144
145
|
function maskSecret(s: string): string {
|
|
145
146
|
if (s.length <= 8) return '***';
|
|
146
147
|
return s.slice(0, 4) + '...' + s.slice(-4);
|
|
@@ -357,3 +358,17 @@ export async function cmdConfigModify(name: string): Promise<void> {
|
|
|
357
358
|
console.log(` Run "imtoagent restore" to hot-reload the gateway.\n`);
|
|
358
359
|
rl.close();
|
|
359
360
|
}
|
|
361
|
+
|
|
362
|
+
// ================================================================
|
|
363
|
+
// Test exports (NOT for production use)
|
|
364
|
+
// ================================================================
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Export maskSecret for testing only.
|
|
368
|
+
*/
|
|
369
|
+
export const __test_maskSecret = maskSecret;
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Export constants for validate command / testing.
|
|
373
|
+
*/
|
|
374
|
+
export { VALID_BACKENDS, VALID_IMS };
|