evolclaw 2.8.0 → 2.8.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/dist/cli.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import fs from 'fs';
3
3
  import path from 'path';
4
+ import os from 'os';
4
5
  import { spawn, execFile } from 'child_process';
5
6
  import { promisify } from 'util';
6
7
  import { resolveRoot, resolvePaths, ensureDataDirs, getPackageRoot } from './paths.js';
@@ -67,13 +68,11 @@ function rotateLogs(logDir) {
67
68
  function countLines(pkgRoot, logDir) {
68
69
  const srcDir = path.join(pkgRoot, 'src');
69
70
  const statsFile = path.join(logDir, 'line-stats.log');
70
- const countDir = (dir, exclude) => {
71
+ const countDir = (dir) => {
71
72
  if (!fs.existsSync(dir))
72
73
  return 0;
73
74
  let total = 0;
74
75
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
75
- if (exclude && entry.name === exclude)
76
- continue;
77
76
  const full = path.join(dir, entry.name);
78
77
  if (entry.isDirectory()) {
79
78
  total += countDir(full);
@@ -92,12 +91,14 @@ function countLines(pkgRoot, logDir) {
92
91
  console.log('\n[launcher] 正在统计代码行数...\n');
93
92
  const core = countDir(path.join(srcDir, 'core'));
94
93
  const agents = countDir(path.join(srcDir, 'agents'));
95
- const channels = countDir(path.join(srcDir, 'channels'), 'experimental');
94
+ const channels = countDir(path.join(srcDir, 'channels'));
96
95
  const utils = countDir(path.join(srcDir, 'utils'));
97
96
  const entry = countFile(path.join(srcDir, 'index.ts'))
98
97
  + countFile(path.join(srcDir, 'config.ts'))
99
98
  + countFile(path.join(srcDir, 'types.ts'))
100
- + countFile(path.join(srcDir, 'cli.ts'));
99
+ + countFile(path.join(srcDir, 'cli.ts'))
100
+ + countFile(path.join(srcDir, 'ipc.ts'))
101
+ + countFile(path.join(srcDir, 'paths.ts'));
101
102
  const total = core + agents + channels + utils + entry;
102
103
  console.log('==================================================');
103
104
  console.log('EvolClaw 代码统计');
@@ -606,20 +607,26 @@ async function cmdStatus() {
606
607
  showConfigChannels(config);
607
608
  }
608
609
  }
609
- console.log('');
610
- console.log('📁 Log Files:');
611
- const mainLog = path.join(p.logs, 'evolclaw.log');
612
- if (fs.existsSync(mainLog)) {
613
- const stat = fs.statSync(mainLog);
614
- const sizeMB = (stat.size / 1024 / 1024).toFixed(1);
615
- console.log(` Main log: ${mainLog} (${sizeMB} MB)`);
616
- console.log('');
617
- console.log('📝 Recent activity (last 30 lines):');
618
- const content = fs.readFileSync(mainLog, 'utf-8').trim().split('\n');
619
- console.log(content.slice(-30).map(l => ` ${l}`).join('\n'));
620
- }
621
- else {
622
- console.log(' (no log file yet)');
610
+ // EvolAgent summary (via IPC, only when running)
611
+ if (pid) {
612
+ try {
613
+ const agentResult = await ipcQuery(p.socket, { type: 'evolagent.list' });
614
+ if (agentResult?.ok && agentResult.agents?.length > 0) {
615
+ const agents = agentResult.agents.filter((a) => !a.isDefault);
616
+ if (agents.length > 0) {
617
+ console.log('');
618
+ console.log('🤖 EvolAgents:');
619
+ for (const a of agents) {
620
+ const statusIcon = a.status === 'running' ? '●' : a.status === 'error' ? '✗' : a.status === 'disabled' ? '○' : '◌';
621
+ const channels = a.channels?.join(', ') || '—';
622
+ console.log(` ${statusIcon} ${a.name.padEnd(14)} ${a.status.padEnd(10)} ${channels}`);
623
+ }
624
+ }
625
+ }
626
+ }
627
+ catch {
628
+ // IPC query for agents failed — skip section
629
+ }
623
630
  }
624
631
  }
625
632
  // Log line pattern: [timestamp] [LEVEL] [Module?] message
@@ -1378,6 +1385,621 @@ async function cmdCtl(args) {
1378
1385
  process.exit(1);
1379
1386
  }
1380
1387
  }
1388
+ // ==================== Agent ====================
1389
+ async function cmdAgent(args) {
1390
+ const sub = args[0];
1391
+ if (!sub) {
1392
+ await cmdAgentList();
1393
+ return;
1394
+ }
1395
+ if (sub === 'new') {
1396
+ const name = args[1];
1397
+ const nonInteractive = args.includes('--non-interactive');
1398
+ if (nonInteractive) {
1399
+ if (!name) {
1400
+ console.error('Usage: evolclaw agent new <name> --non-interactive ...');
1401
+ process.exit(1);
1402
+ }
1403
+ await cmdAgentNewNonInteractive(name, args.slice(2));
1404
+ }
1405
+ else {
1406
+ // Interactive mode: name from CLI is suggested default; user can override at prompt
1407
+ await cmdAgentNew(name);
1408
+ }
1409
+ return;
1410
+ }
1411
+ if (sub === 'reload') {
1412
+ const name = args[1];
1413
+ if (!name) {
1414
+ console.error('Usage: evolclaw agent reload <name>');
1415
+ process.exit(1);
1416
+ }
1417
+ const p = resolvePaths();
1418
+ try {
1419
+ const result = await ipcQuery(p.socket, { type: 'evolagent.reload', name });
1420
+ if (result && result.ok) {
1421
+ console.log(`✓ Agent "${name}" reloaded`);
1422
+ }
1423
+ else {
1424
+ console.error(`✗ Reload failed: ${result?.error || 'unknown error'}`);
1425
+ process.exit(1);
1426
+ }
1427
+ }
1428
+ catch {
1429
+ console.error('⚠ evolclaw 未运行,请先 evolclaw start 后再 reload');
1430
+ console.log(' 或直接 evolclaw restart 重新加载所有 agent');
1431
+ process.exit(1);
1432
+ }
1433
+ return;
1434
+ }
1435
+ // `evolclaw agent <name>` — show detail
1436
+ await cmdAgentShow(sub);
1437
+ }
1438
+ async function cmdAgentList() {
1439
+ const p = resolvePaths();
1440
+ // Try IPC first (running process has real status)
1441
+ try {
1442
+ const result = await ipcQuery(p.socket, { type: 'evolagent.list' });
1443
+ if (result && result.ok && result.agents) {
1444
+ printAgentTable(result.agents);
1445
+ return;
1446
+ }
1447
+ }
1448
+ catch {
1449
+ // IPC unavailable — fall through to cold mode
1450
+ }
1451
+ // Cold mode: read from disk
1452
+ const { EvolAgentRegistry } = await import('./core/evolagent-registry.js');
1453
+ const { loadConfig } = await import('./config.js');
1454
+ let config;
1455
+ try {
1456
+ config = loadConfig(p.config);
1457
+ }
1458
+ catch {
1459
+ config = { agents: {}, channels: {}, projects: { defaultPath: process.cwd() } };
1460
+ }
1461
+ const registry = new EvolAgentRegistry(p.agentsDir);
1462
+ registry.loadAll(config);
1463
+ printAgentTable(registry.list());
1464
+ }
1465
+ function printAgentTable(list) {
1466
+ if (list.length === 0) {
1467
+ console.log('No agents configured.');
1468
+ return;
1469
+ }
1470
+ console.log('NAME'.padEnd(14) + 'STATUS'.padEnd(10) + 'CHANNELS'.padEnd(24) +
1471
+ 'PROJECT'.padEnd(22) + 'BASEAGENT'.padEnd(11) + 'LAST ACTIVE');
1472
+ for (const info of list) {
1473
+ const name = info.isDefault ? '[default]' : info.name;
1474
+ const status = info.status || 'stopped';
1475
+ const channels = info.channels?.length > 0 ? info.channels.join(', ').slice(0, 22) : '—';
1476
+ const project = info.projectPath ? path.basename(info.projectPath) : '—';
1477
+ const baseagent = info.baseagent || '—';
1478
+ const lastActive = info.lastActivity
1479
+ ? formatTimeAgo(Date.now() - info.lastActivity)
1480
+ : '—';
1481
+ console.log(name.padEnd(14) +
1482
+ status.padEnd(10) +
1483
+ channels.padEnd(24) +
1484
+ project.padEnd(22) +
1485
+ baseagent.padEnd(11) +
1486
+ lastActive);
1487
+ }
1488
+ }
1489
+ async function cmdAgentShow(name) {
1490
+ const p = resolvePaths();
1491
+ // Try IPC first
1492
+ try {
1493
+ const result = await ipcQuery(p.socket, { type: 'evolagent.show', name });
1494
+ if (result && result.ok && result.agent) {
1495
+ const info = result.agent;
1496
+ console.log(`${info.name} (${info.status})\n`);
1497
+ console.log(` Baseagent: ${info.baseagent}`);
1498
+ if (info.model)
1499
+ console.log(` Model: ${info.model}`);
1500
+ if (info.effort)
1501
+ console.log(` Effort: ${info.effort}`);
1502
+ console.log(` Project: ${info.projectPath}`);
1503
+ console.log(` Channels: ${info.channels?.join(', ') || '—'}`);
1504
+ if (info.activeSessions)
1505
+ console.log(` Sessions: ${info.activeSessions} active`);
1506
+ if (info.lastActivity)
1507
+ console.log(` Last active: ${formatTimeAgo(Date.now() - info.lastActivity)}`);
1508
+ if (info.error)
1509
+ console.log(` Error: ${info.error}`);
1510
+ return;
1511
+ }
1512
+ }
1513
+ catch {
1514
+ // IPC unavailable — fall through to cold mode
1515
+ }
1516
+ // Cold mode
1517
+ const { EvolAgentRegistry } = await import('./core/evolagent-registry.js');
1518
+ const { loadConfig } = await import('./config.js');
1519
+ let config;
1520
+ try {
1521
+ config = loadConfig(p.config);
1522
+ }
1523
+ catch {
1524
+ config = { agents: {}, channels: {}, projects: { defaultPath: process.cwd() } };
1525
+ }
1526
+ const registry = new EvolAgentRegistry(p.agentsDir);
1527
+ registry.loadAll(config);
1528
+ const agent = registry.get(name);
1529
+ if (!agent) {
1530
+ console.error(`Agent "${name}" not found.`);
1531
+ const allList = registry.list().filter(i => !i.isDefault);
1532
+ if (allList.length > 0) {
1533
+ console.log(`Available: ${allList.map(i => i.name).join(', ')}`);
1534
+ }
1535
+ process.exit(1);
1536
+ }
1537
+ console.log(`${agent.name} (${agent.status})\n`);
1538
+ console.log(` Baseagent: ${agent.baseagent}`);
1539
+ if (agent.model)
1540
+ console.log(` Model: ${agent.model}`);
1541
+ if (agent.effort)
1542
+ console.log(` Effort: ${agent.effort}`);
1543
+ console.log(` Project: ${agent.projectPath}`);
1544
+ console.log(` Channels: ${agent.channelInstanceNames().join(', ') || '—'}`);
1545
+ if (agent.error)
1546
+ console.log(` Error: ${agent.error}`);
1547
+ if (agent.configPath)
1548
+ console.log(` Config: ${agent.configPath}`);
1549
+ }
1550
+ async function cmdAgentNew(suggestedName) {
1551
+ const p = resolvePaths();
1552
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
1553
+ const ask = (q) => new Promise(r => rl.question(q, r));
1554
+ let name;
1555
+ try {
1556
+ const prompt = suggestedName
1557
+ ? `Agent name [${suggestedName}]: `
1558
+ : 'Agent name: ';
1559
+ const input = (await ask(prompt)).trim();
1560
+ name = input || suggestedName;
1561
+ if (!name) {
1562
+ console.error('Agent name is required.');
1563
+ process.exit(1);
1564
+ }
1565
+ // Disallow dots/slashes/spaces in agent name (used as filename + channel-name prefix)
1566
+ if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
1567
+ console.error(`Invalid agent name "${name}": only letters/digits/_/- allowed`);
1568
+ process.exit(1);
1569
+ }
1570
+ const agentPath = path.join(p.agentsDir, `${name}.json`);
1571
+ if (fs.existsSync(agentPath)) {
1572
+ console.error(`Agent "${name}" already exists: ${agentPath}`);
1573
+ process.exit(1);
1574
+ }
1575
+ console.log(`\nCreating agent: ${name}\n`);
1576
+ // Suggest project path: sibling of evolclaw.json's defaultPath, named after agent
1577
+ let suggestedProjectPath = '';
1578
+ try {
1579
+ const cfg = loadConfig(p.config);
1580
+ const defaultProjectsRoot = cfg.projects?.defaultPath
1581
+ ? path.dirname(cfg.projects.defaultPath)
1582
+ : path.join(os.homedir(), 'evolclaw-projects');
1583
+ suggestedProjectPath = path.join(defaultProjectsRoot, name);
1584
+ }
1585
+ catch {
1586
+ suggestedProjectPath = path.join(os.homedir(), 'evolclaw-projects', name);
1587
+ }
1588
+ const projectInput = (await ask(`Project path [${suggestedProjectPath}]: `)).trim();
1589
+ const projectPath = projectInput || suggestedProjectPath;
1590
+ if (!path.isAbsolute(projectPath)) {
1591
+ console.error('Project path must be an absolute path.');
1592
+ process.exit(1);
1593
+ }
1594
+ if (!fs.existsSync(projectPath)) {
1595
+ const create = (await ask(`Project path does not exist. Create ${projectPath}? [Y/n]: `)).trim().toLowerCase();
1596
+ if (create === '' || create === 'y' || create === 'yes') {
1597
+ try {
1598
+ fs.mkdirSync(projectPath, { recursive: true });
1599
+ console.log(` ✓ Created ${projectPath}`);
1600
+ }
1601
+ catch (e) {
1602
+ console.error(`Failed to create ${projectPath}: ${e?.message || e}`);
1603
+ process.exit(1);
1604
+ }
1605
+ }
1606
+ else {
1607
+ console.error('Aborted: project path does not exist.');
1608
+ process.exit(1);
1609
+ }
1610
+ }
1611
+ const baseagentChoices = ['claude', 'codex', 'gemini', 'hermes'];
1612
+ const baseagent = (await ask(`Baseagent (${baseagentChoices.join('/')}) [claude]: `)).trim() || 'claude';
1613
+ if (!baseagentChoices.includes(baseagent)) {
1614
+ console.error(`Invalid baseagent: ${baseagent}`);
1615
+ process.exit(1);
1616
+ }
1617
+ const model = (await ask('Model (leave empty for default): ')).trim() || undefined;
1618
+ const effort = (await ask('Effort (low/medium/high/max) [high]: ')).trim() || 'high';
1619
+ const chatmodeChoices = ['interactive', 'proactive'];
1620
+ console.log('\nChat mode determines how the agent responds:');
1621
+ console.log(' interactive — replies to every message; agent output sent automatically');
1622
+ console.log(' proactive — silent by default; agent decides when/whether to send via ctl send');
1623
+ const chatmodePrivate = (await ask(`Private (1-on-1) chat mode (${chatmodeChoices.join('/')}) [interactive]: `)).trim() || 'interactive';
1624
+ if (!chatmodeChoices.includes(chatmodePrivate)) {
1625
+ console.error(`Invalid chat mode: ${chatmodePrivate}`);
1626
+ process.exit(1);
1627
+ }
1628
+ const chatmodeGroup = (await ask(`Group chat mode (${chatmodeChoices.join('/')}) [proactive]: `)).trim() || 'proactive';
1629
+ if (!chatmodeChoices.includes(chatmodeGroup)) {
1630
+ console.error(`Invalid chat mode: ${chatmodeGroup}`);
1631
+ process.exit(1);
1632
+ }
1633
+ // Channels (loop to allow multiple)
1634
+ const channelsConfig = {};
1635
+ const { getChannelCredentialCollector } = await import('./utils/init-channel.js');
1636
+ // Close outer rl before channel loop (collectors create their own readline)
1637
+ rl.close();
1638
+ while (true) {
1639
+ const loopRl = readline.createInterface({ input: process.stdin, output: process.stdout });
1640
+ const loopAsk = (q) => new Promise(r => loopRl.question(q, r));
1641
+ const addChannel = (await loopAsk('\nAdd channel? (y/n) [n]: ')).trim().toLowerCase();
1642
+ if (addChannel !== 'y') {
1643
+ loopRl.close();
1644
+ break;
1645
+ }
1646
+ const channelType = (await loopAsk('Channel type (feishu/aun/wechat/wecom/dingtalk/qqbot): ')).trim();
1647
+ const collector = getChannelCredentialCollector(channelType);
1648
+ if (!collector) {
1649
+ console.error(`Unknown channel type: ${channelType}`);
1650
+ loopRl.close();
1651
+ continue;
1652
+ }
1653
+ // Close loop rl before collector opens its own
1654
+ loopRl.close();
1655
+ let creds = null;
1656
+ try {
1657
+ creds = await collector();
1658
+ }
1659
+ catch (e) {
1660
+ console.error(` Channel setup failed: ${e?.message || e}`);
1661
+ }
1662
+ if (!creds) {
1663
+ console.log(' Channel setup cancelled.');
1664
+ continue;
1665
+ }
1666
+ const nameRl = readline.createInterface({ input: process.stdin, output: process.stdout });
1667
+ const defaultEffName = `${name}-${channelType}`;
1668
+ const instName = await new Promise(r => nameRl.question(` Channel instance name (leave empty for ${defaultEffName}): `, r));
1669
+ nameRl.close();
1670
+ const trimmedName = instName.trim();
1671
+ if (trimmedName)
1672
+ creds.name = trimmedName;
1673
+ // else: omit name; effective channel name will be ${agent.name}-${type} via EvolAgent.effectiveChannelName
1674
+ if (!channelsConfig[channelType])
1675
+ channelsConfig[channelType] = [];
1676
+ channelsConfig[channelType].push(creds);
1677
+ }
1678
+ // Simplify channels: if only one instance per type, unwrap from array
1679
+ const finalChannels = {};
1680
+ for (const [type, instances] of Object.entries(channelsConfig)) {
1681
+ finalChannels[type] = instances.length === 1 ? instances[0] : instances;
1682
+ }
1683
+ const agentConfig = {
1684
+ name,
1685
+ enabled: true,
1686
+ agents: { [baseagent]: { ...(model && { model }), effort } },
1687
+ channels: finalChannels,
1688
+ projects: { defaultPath: projectPath.trim() },
1689
+ chatmode: { private: chatmodePrivate, group: chatmodeGroup },
1690
+ };
1691
+ fs.mkdirSync(p.agentsDir, { recursive: true });
1692
+ fs.writeFileSync(agentPath, JSON.stringify(agentConfig, null, 2));
1693
+ console.log(`\n✓ Created: ${agentPath}`);
1694
+ console.log(' Run `evolclaw restart` to activate.');
1695
+ }
1696
+ finally {
1697
+ // rl may already be closed if channel collector was invoked
1698
+ try {
1699
+ rl.close();
1700
+ }
1701
+ catch { }
1702
+ }
1703
+ }
1704
+ async function cmdAgentNewNonInteractive(name, args) {
1705
+ const p = resolvePaths();
1706
+ const agentPath = path.join(p.agentsDir, `${name}.json`);
1707
+ if (fs.existsSync(agentPath)) {
1708
+ console.error(`Agent "${name}" already exists: ${agentPath}`);
1709
+ process.exit(1);
1710
+ }
1711
+ // Helper: extract --flag value from args
1712
+ const getArg = (flag) => {
1713
+ const idx = args.indexOf(flag);
1714
+ return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : undefined;
1715
+ };
1716
+ // Required: baseagent + project
1717
+ const baseagent = getArg('--baseagent');
1718
+ if (!baseagent) {
1719
+ console.error('--baseagent is required (claude|codex|gemini|hermes)');
1720
+ process.exit(1);
1721
+ }
1722
+ const baseagentChoices = ['claude', 'codex', 'gemini', 'hermes'];
1723
+ if (!baseagentChoices.includes(baseagent)) {
1724
+ console.error(`Invalid --baseagent: ${baseagent}`);
1725
+ process.exit(1);
1726
+ }
1727
+ const project = getArg('--project');
1728
+ if (!project) {
1729
+ console.error('--project is required (absolute path)');
1730
+ process.exit(1);
1731
+ }
1732
+ if (!path.isAbsolute(project)) {
1733
+ console.error(`--project must be absolute: ${project}`);
1734
+ process.exit(1);
1735
+ }
1736
+ if (!fs.existsSync(project)) {
1737
+ try {
1738
+ fs.mkdirSync(project, { recursive: true });
1739
+ console.log(` ✓ Created ${project}`);
1740
+ }
1741
+ catch (e) {
1742
+ console.error(`Failed to create ${project}: ${e?.message || e}`);
1743
+ process.exit(1);
1744
+ }
1745
+ }
1746
+ // Optional: chatmode
1747
+ const chatmodePrivate = getArg('--chatmode-private') || 'interactive';
1748
+ const chatmodeGroup = getArg('--chatmode-group') || 'proactive';
1749
+ const chatmodeValid = new Set(['interactive', 'proactive']);
1750
+ if (!chatmodeValid.has(chatmodePrivate)) {
1751
+ console.error(`Invalid --chatmode-private: ${chatmodePrivate}`);
1752
+ process.exit(1);
1753
+ }
1754
+ if (!chatmodeValid.has(chatmodeGroup)) {
1755
+ console.error(`Invalid --chatmode-group: ${chatmodeGroup}`);
1756
+ process.exit(1);
1757
+ }
1758
+ // Channels
1759
+ const channelsConfig = {};
1760
+ // AUN
1761
+ const aunAid = getArg('--aun-aid');
1762
+ const aunOwner = getArg('--aun-owner');
1763
+ if (aunAid || aunOwner) {
1764
+ if (!aunAid || !aunOwner) {
1765
+ console.error('--aun-aid and --aun-owner must both be provided');
1766
+ process.exit(1);
1767
+ }
1768
+ const { isValidAid, aidCreate } = await import('./channels/aun-ops.js');
1769
+ if (!isValidAid(aunAid)) {
1770
+ console.error(`Invalid --aun-aid: ${aunAid}`);
1771
+ process.exit(1);
1772
+ }
1773
+ if (!isValidAid(aunOwner)) {
1774
+ console.error(`Invalid --aun-owner: ${aunOwner}`);
1775
+ process.exit(1);
1776
+ }
1777
+ try {
1778
+ const result = await aidCreate(aunAid);
1779
+ try {
1780
+ await result.client.close();
1781
+ }
1782
+ catch { /* ignore */ }
1783
+ console.log(`✓ AID ${result.alreadyExisted ? 'reused' : 'created'}: ${aunAid}`);
1784
+ }
1785
+ catch (e) {
1786
+ console.error(`AID creation failed: ${e?.message || e}`);
1787
+ process.exit(1);
1788
+ }
1789
+ channelsConfig.aun = { enabled: true, aid: aunAid, owner: aunOwner };
1790
+ }
1791
+ // Feishu
1792
+ const feishuAppId = getArg('--feishu-app-id');
1793
+ const feishuAppSecret = getArg('--feishu-app-secret');
1794
+ if (feishuAppId || feishuAppSecret) {
1795
+ if (!feishuAppId || !feishuAppSecret) {
1796
+ console.error('--feishu-app-id and --feishu-app-secret must both be provided');
1797
+ process.exit(1);
1798
+ }
1799
+ channelsConfig.feishu = [{
1800
+ name: `feishu-${name}`,
1801
+ enabled: true,
1802
+ appId: feishuAppId,
1803
+ appSecret: feishuAppSecret,
1804
+ }];
1805
+ }
1806
+ // WeChat
1807
+ const wechatToken = getArg('--wechat-token');
1808
+ if (wechatToken) {
1809
+ channelsConfig.wechat = { enabled: true, token: wechatToken };
1810
+ }
1811
+ // WeCom
1812
+ const wecomBotId = getArg('--wecom-bot-id');
1813
+ const wecomSecret = getArg('--wecom-secret');
1814
+ if (wecomBotId || wecomSecret) {
1815
+ if (!wecomBotId || !wecomSecret) {
1816
+ console.error('--wecom-bot-id and --wecom-secret must both be provided');
1817
+ process.exit(1);
1818
+ }
1819
+ channelsConfig.wecom = { enabled: true, botId: wecomBotId, secret: wecomSecret };
1820
+ }
1821
+ // DingTalk
1822
+ const dingtalkClientId = getArg('--dingtalk-client-id');
1823
+ const dingtalkClientSecret = getArg('--dingtalk-client-secret');
1824
+ if (dingtalkClientId || dingtalkClientSecret) {
1825
+ if (!dingtalkClientId || !dingtalkClientSecret) {
1826
+ console.error('--dingtalk-client-id and --dingtalk-client-secret must both be provided');
1827
+ process.exit(1);
1828
+ }
1829
+ channelsConfig.dingtalk = { enabled: true, clientId: dingtalkClientId, clientSecret: dingtalkClientSecret };
1830
+ }
1831
+ // QQBot
1832
+ const qqbotAppId = getArg('--qqbot-app-id');
1833
+ const qqbotClientSecret = getArg('--qqbot-client-secret');
1834
+ if (qqbotAppId || qqbotClientSecret) {
1835
+ if (!qqbotAppId || !qqbotClientSecret) {
1836
+ console.error('--qqbot-app-id and --qqbot-client-secret must both be provided');
1837
+ process.exit(1);
1838
+ }
1839
+ channelsConfig.qqbot = { enabled: true, appId: qqbotAppId, clientSecret: qqbotClientSecret };
1840
+ }
1841
+ if (Object.keys(channelsConfig).length === 0) {
1842
+ console.error('At least one channel must be configured (aun / feishu / wechat / wecom / dingtalk / qqbot)');
1843
+ process.exit(1);
1844
+ }
1845
+ const agentConfig = {
1846
+ name,
1847
+ enabled: true,
1848
+ agents: { [baseagent]: {} },
1849
+ channels: channelsConfig,
1850
+ projects: { defaultPath: project },
1851
+ chatmode: { private: chatmodePrivate, group: chatmodeGroup },
1852
+ };
1853
+ fs.mkdirSync(p.agentsDir, { recursive: true });
1854
+ fs.writeFileSync(agentPath, JSON.stringify(agentConfig, null, 2));
1855
+ console.log(`✓ Created: ${agentPath}`);
1856
+ console.log(' Run `evolclaw restart` (or `evolclaw agent reload <name>`) to activate.');
1857
+ }
1858
+ // ==================== AID ====================
1859
+ async function cmdAid(args) {
1860
+ const sub = args[0] || 'list';
1861
+ if (sub === 'help') {
1862
+ console.log(`用法: evolclaw aid <command>
1863
+
1864
+ Commands:
1865
+ list 列出本地所有 AID
1866
+ new <aid> 创建新 AID 身份
1867
+
1868
+ 示例:
1869
+ evolclaw aid list
1870
+ evolclaw aid new reviewer.agentid.pub`);
1871
+ return;
1872
+ }
1873
+ const { aidList, aidCreate, agentmdPut, buildInitialAgentMd, isValidAid } = await import('./channels/aun-ops.js');
1874
+ if (sub === 'list') {
1875
+ const aids = aidList();
1876
+ if (aids.length === 0) {
1877
+ console.log('本地无 AID');
1878
+ return;
1879
+ }
1880
+ console.log('本地 AID:');
1881
+ for (const a of aids) {
1882
+ const icons = [
1883
+ a.hasPrivateKey ? '🔑' : ' ',
1884
+ a.hasAgentMd ? '📄' : ' ',
1885
+ ].join('');
1886
+ console.log(` ${icons} ${a.aid}`);
1887
+ }
1888
+ console.log('\n🔑=私钥 📄=agent.md');
1889
+ return;
1890
+ }
1891
+ if (sub === 'new') {
1892
+ const aid = args[1];
1893
+ if (!aid) {
1894
+ console.error('用法: evolclaw aid new <完整AID>\n例: evolclaw aid new reviewer.agentid.pub');
1895
+ process.exit(1);
1896
+ }
1897
+ if (!isValidAid(aid)) {
1898
+ console.error(`❌ 无效 AID 格式: ${aid}`);
1899
+ process.exit(1);
1900
+ }
1901
+ const result = await aidCreate(aid);
1902
+ if (!result.alreadyExisted) {
1903
+ const content = buildInitialAgentMd({ aid });
1904
+ try {
1905
+ await agentmdPut(content, { aid, client: result.client });
1906
+ console.log('✓ agent.md 已发布');
1907
+ }
1908
+ catch (e) {
1909
+ console.warn(`⚠ agent.md 发布失败(首次连接将自动重试): ${String(e.message || e).slice(0, 100)}`);
1910
+ }
1911
+ }
1912
+ try {
1913
+ await result.client.close();
1914
+ }
1915
+ catch { }
1916
+ const verb = result.alreadyExisted ? '已存在' : '已创建';
1917
+ console.log(`✓ ${aid} ${verb}`);
1918
+ console.log(' 如需上线 AUN 通道,运行 evolclaw init aun');
1919
+ return;
1920
+ }
1921
+ console.error(`未知子命令: ${sub}\n用法: evolclaw aid [list|new <aid>]`);
1922
+ process.exit(1);
1923
+ }
1924
+ // ==================== AgentMd ====================
1925
+ async function cmdAgentmd(args) {
1926
+ if (args.length === 0 || args[0] === 'help') {
1927
+ console.log(`用法: evolclaw agentmd <command> <aid>
1928
+
1929
+ Commands:
1930
+ <aid> 查看指定 AID 的 agent.md
1931
+ put <aid> 上传本地 agent.md 到 AUN 网络
1932
+ set <aid> <内容> 设置并上传 agent.md
1933
+
1934
+ 示例:
1935
+ evolclaw agentmd mybot.agentid.pub
1936
+ evolclaw agentmd put mybot.agentid.pub
1937
+ evolclaw agentmd set mybot.agentid.pub "---\\naid: mybot.agentid.pub\\n---"`);
1938
+ return;
1939
+ }
1940
+ const { agentmdGet, agentmdPut, isValidAid } = await import('./channels/aun-ops.js');
1941
+ if (args[0] === 'put') {
1942
+ const aid = args[1];
1943
+ if (!aid) {
1944
+ console.error('用法: evolclaw agentmd put <aid>');
1945
+ process.exit(1);
1946
+ }
1947
+ if (!isValidAid(aid)) {
1948
+ console.error(`❌ 无效 AID 格式: ${aid}`);
1949
+ process.exit(1);
1950
+ }
1951
+ // Read local file directly (put = push local → network)
1952
+ const localPath = path.join(os.homedir(), '.aun', 'AIDs', aid, 'agent.md');
1953
+ if (!fs.existsSync(localPath)) {
1954
+ console.error(`❌ 本地无 agent.md: ${aid}`);
1955
+ process.exit(1);
1956
+ }
1957
+ const content = fs.readFileSync(localPath, 'utf-8');
1958
+ await agentmdPut(content, { aid });
1959
+ console.log('✓ agent.md 已发布');
1960
+ return;
1961
+ }
1962
+ if (args[0] === 'set') {
1963
+ const aid = args[1];
1964
+ const content = args.slice(2).join(' ');
1965
+ if (!aid || !content) {
1966
+ console.error('用法: evolclaw agentmd set <aid> <内容>');
1967
+ process.exit(1);
1968
+ }
1969
+ if (!isValidAid(aid)) {
1970
+ console.error(`❌ 无效 AID 格式: ${aid}`);
1971
+ process.exit(1);
1972
+ }
1973
+ await agentmdPut(content, { aid });
1974
+ console.log('✓ agent.md 已更新并发布');
1975
+ return;
1976
+ }
1977
+ // Default: view
1978
+ const aid = args[0];
1979
+ if (!isValidAid(aid)) {
1980
+ console.error(`❌ 无效 AID 格式: ${aid}`);
1981
+ process.exit(1);
1982
+ }
1983
+ try {
1984
+ const md = await agentmdGet(aid);
1985
+ if (!md || !md.trim()) {
1986
+ console.log(`ℹ️ ${aid} 尚未设置 agent.md`);
1987
+ }
1988
+ else {
1989
+ console.log(md);
1990
+ }
1991
+ }
1992
+ catch (e) {
1993
+ const msg = String(e.message || e);
1994
+ if (msg.includes('not found') || msg.includes('404')) {
1995
+ console.log(`ℹ️ ${aid} 尚未设置 agent.md`);
1996
+ }
1997
+ else {
1998
+ console.error(`❌ 获取失败: ${msg.slice(0, 100)}`);
1999
+ process.exit(1);
2000
+ }
2001
+ }
2002
+ }
1381
2003
  // ==================== Main ====================
1382
2004
  function getArgValue(args, flag) {
1383
2005
  const idx = args.indexOf(flag);
@@ -1446,7 +2068,7 @@ export async function main(args) {
1446
2068
  if (nonInteractive) {
1447
2069
  await cmdInit({
1448
2070
  nonInteractive: true,
1449
- defaultPath: getArgValue(args, '--default-path') || process.cwd(),
2071
+ defaultPath: getArgValue(args, '--default-path') || path.join(os.homedir(), 'projects', 'default'),
1450
2072
  channel: getArgValue(args, '--channel') || 'aun',
1451
2073
  aunAid: getArgValue(args, '--aun-aid'),
1452
2074
  aunOwner: getArgValue(args, '--aun-owner'),
@@ -1484,6 +2106,15 @@ export async function main(args) {
1484
2106
  case 'ctl':
1485
2107
  await cmdCtl(args.slice(1));
1486
2108
  break;
2109
+ case 'agent':
2110
+ await cmdAgent(args.slice(1));
2111
+ break;
2112
+ case 'aid':
2113
+ await cmdAid(args.slice(1));
2114
+ break;
2115
+ case 'agentmd':
2116
+ await cmdAgentmd(args.slice(1));
2117
+ break;
1487
2118
  default:
1488
2119
  console.log(`Usage: evolclaw {init|start|stop|restart|status|logs|ctl|diagnose|mv}
1489
2120
 
@@ -1505,6 +2136,31 @@ Commands:
1505
2136
  --raw 原始输出,不着色
1506
2137
  ctl 运行时自管理(模型切换、推理强度、压缩上下文等)
1507
2138
  evolclaw ctl help 查看完整命令列表
2139
+ agent 管理 EvolAgent
2140
+ agent 列出所有 agent
2141
+ agent <name> 查看指定 agent 详情
2142
+ agent new <name> 创建新 agent(交互式)
2143
+ agent new <name> --non-interactive ... 非交互创建(自动化)
2144
+ 必填: --baseagent <claude|codex|gemini|hermes>
2145
+ --project <absolute path>
2146
+ 可选 channel:
2147
+ --aun-aid <aid> --aun-owner <aid>
2148
+ --feishu-app-id xxx --feishu-app-secret yyy
2149
+ --wechat-token xxx
2150
+ --wecom-bot-id xxx --wecom-secret yyy
2151
+ --dingtalk-client-id xxx --dingtalk-client-secret yyy
2152
+ --qqbot-app-id xxx --qqbot-client-secret yyy
2153
+ 可选行为:
2154
+ --chatmode-private <interactive|proactive> (默认 interactive)
2155
+ --chatmode-group <interactive|proactive> (默认 proactive)
2156
+ agent reload <n> 热重载 agent 配置
2157
+ aid AID 身份管理
2158
+ aid list 列出本地所有 AID
2159
+ aid new <aid> 创建新 AID 身份
2160
+ agentmd agent.md 管理
2161
+ agentmd <aid> 查看 agent.md
2162
+ agentmd put <aid> 上传本地 agent.md
2163
+ agentmd set <aid> <内容> 设置并上传
1508
2164
  diagnose 诊断启动环境(配置、数据库、进程)
1509
2165
  mv <old> <new> 迁移项目目录(保留 Claude/Codex/EvolClaw 会话)
1510
2166