evolclaw 3.1.5 → 3.1.7

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.
Files changed (51) hide show
  1. package/CHANGELOG.md +68 -3
  2. package/dist/agents/claude-runner.js +69 -24
  3. package/dist/agents/kit-renderer.js +78 -321
  4. package/dist/agents/manifest-engine.js +243 -0
  5. package/dist/agents/message-renderer.js +112 -0
  6. package/dist/aun/aid/agentmd.js +10 -3
  7. package/dist/aun/msg/group.js +2 -2
  8. package/dist/channels/aun.js +154 -18
  9. package/dist/channels/dingtalk.js +1 -1
  10. package/dist/channels/feishu.js +31 -9
  11. package/dist/channels/qqbot.js +1 -1
  12. package/dist/channels/wechat.js +1 -1
  13. package/dist/channels/wecom.js +1 -1
  14. package/dist/cli/agent.js +10 -11
  15. package/dist/cli/bench.js +1 -5
  16. package/dist/cli/help.js +8 -0
  17. package/dist/cli/index.js +91 -128
  18. package/dist/cli/init.js +37 -21
  19. package/dist/cli/link-rules.js +1 -7
  20. package/dist/cli/model.js +231 -6
  21. package/dist/config-store.js +1 -22
  22. package/dist/core/command-handler.js +181 -48
  23. package/dist/core/evolagent.js +0 -18
  24. package/dist/core/message/im-renderer.js +9 -20
  25. package/dist/core/message/message-bridge.js +9 -10
  26. package/dist/core/message/message-processor.js +188 -39
  27. package/dist/core/message/message-queue.js +15 -1
  28. package/dist/core/relation/peer-identity.js +23 -11
  29. package/dist/core/trigger/parser.js +4 -4
  30. package/dist/core/trigger/scheduler.js +43 -13
  31. package/dist/index.js +102 -52
  32. package/dist/ipc.js +1 -1
  33. package/dist/utils/error-utils.js +6 -0
  34. package/dist/utils/process-introspect.js +7 -5
  35. package/kits/docs/INDEX.md +4 -8
  36. package/kits/docs/context-assembly.md +1 -0
  37. package/kits/docs/evolclaw/INDEX.md +43 -0
  38. package/kits/docs/evolclaw/group.md +13 -6
  39. package/kits/docs/evolclaw/model.md +51 -0
  40. package/kits/docs/evolclaw/msg.md +5 -0
  41. package/kits/docs/venues/group.md +13 -1
  42. package/kits/eck_manifest.json +9 -0
  43. package/kits/eck_message_manifest.json +14 -0
  44. package/kits/rules/06-channel.md +5 -1
  45. package/kits/templates/message-fragments/item.md +2 -0
  46. package/kits/templates/system-fragments/baseagent.md +7 -1
  47. package/kits/templates/system-fragments/channel.md +7 -5
  48. package/kits/templates/system-fragments/commands.md +19 -0
  49. package/kits/templates/system-fragments/session.md +12 -0
  50. package/kits/templates/system-fragments/venue.md +15 -0
  51. package/package.json +3 -3
package/dist/cli/index.js CHANGED
@@ -11,7 +11,7 @@ import { migrateProject } from '../config-store.js';
11
11
  import { cmdInit } from './init.js';
12
12
  import { ipcQuery } from '../ipc.js';
13
13
  import { cmdInitWechat, cmdInitFeishu, cmdInitDingtalk, cmdInitQQBot, cmdInitWecom } from './init-channel.js';
14
- import { isHelpFlag, wantsHelp } from './help.js';
14
+ import { isHelpFlag, wantsHelp, getArgValue } from './help.js';
15
15
  import * as platform from '../utils/cross-platform.js';
16
16
  import { EventBus } from '../core/event-bus.js';
17
17
  import { tryUpgrade, tryUpgradeAunSdk } from '../utils/npm-ops.js';
@@ -292,6 +292,13 @@ async function cmdStart() {
292
292
  // 旧配置自动迁移(evolclaw.json → 新结构)
293
293
  const { autoMigrateIfNeeded } = await import('../config-store.js');
294
294
  autoMigrateIfNeeded();
295
+ // 未初始化时自动引导
296
+ const defaults = loadDefaults();
297
+ if (!defaults || !defaults.baseagents || Object.keys(defaults.baseagents).length === 0) {
298
+ console.log('⚡ 未检测到初始化配置,自动启动初始化向导...\n');
299
+ await cmdInit();
300
+ return;
301
+ }
295
302
  // 检查至少有一个 self-agent
296
303
  const { agents, skipped } = loadAllAgents();
297
304
  if (agents.length === 0) {
@@ -313,7 +320,7 @@ async function cmdStart() {
313
320
  const aliveMains = status.mains.filter(m => m.alive);
314
321
  if (aliveMains.length > 0) {
315
322
  const first = aliveMains[0];
316
- console.log(`❌ EvolClaw is already running (PID: ${aliveMains.map(m => m.record.pid).join(', ')})`);
323
+ console.log(` EvolClaw is already running (PID: ${aliveMains.map(m => m.record.pid).join(', ')})`);
317
324
  console.log(` 启动于: ${new Date(first.record.startedAtIso).toLocaleString()}`);
318
325
  console.log(` 启动方式: ${first.record.launchedBy}`);
319
326
  // 报告 AID 状态
@@ -371,14 +378,7 @@ async function cmdStart() {
371
378
  const checkReady = () => {
372
379
  // ready signal 出现(优先检查,避免 Windows 上误判进程状态)
373
380
  if (fs.existsSync(p.readySignal)) {
374
- const pkg = JSON.parse(fs.readFileSync(path.join(getPackageRoot(), 'package.json'), 'utf-8'));
375
- let aunVer = 'unknown';
376
- try {
377
- const aunPkg = JSON.parse(fs.readFileSync(path.join(getPackageRoot(), 'node_modules', '@agentunion', 'fastaun', 'package.json'), 'utf-8'));
378
- aunVer = aunPkg.version;
379
- }
380
- catch { /* ignore */ }
381
- console.log(`✓ EvolClaw v${pkg.version} started successfully (PID: ${childPid}) fastaun v${aunVer}`);
381
+ console.log(`✓ EvolClaw started successfully (PID: ${childPid})`);
382
382
  console.log(` EVOLCLAW_HOME: ${resolveRoot()}`);
383
383
  console.log(` Logs: ${p.logs}/`);
384
384
  // 从主日志提取渠道连接摘要
@@ -495,7 +495,6 @@ async function cmdStop() {
495
495
  }
496
496
  async function cmdRestart(opts = {}) {
497
497
  const cmdStartedAt = Date.now();
498
- printStartupInfo();
499
498
  console.log('🔄 Restarting EvolClaw...');
500
499
  // 版本检查与自动升级
501
500
  console.log('📦 Checking for updates...');
@@ -2103,95 +2102,22 @@ async function cmdWatchAid() {
2103
2102
  platform.onShutdown(cleanup);
2104
2103
  }
2105
2104
  async function cmdWatchWeb() {
2106
- const p = resolvePaths();
2107
- fs.mkdirSync(p.instanceDir, { recursive: true });
2108
- const useColor = !!process.stdout.isTTY;
2109
- const RST = useColor ? '\x1b[0m' : '';
2110
- const DIM = useColor ? '\x1b[2m' : '';
2111
- const BOLD = useColor ? '\x1b[1m' : '';
2112
- const CYAN = useColor ? '\x1b[36m' : '';
2113
- const GREEN = useColor ? '\x1b[32m' : '';
2114
- const YELLOW = useColor ? '\x1b[33m' : '';
2115
- const logLine = (line) => {
2116
- const t = new Date();
2117
- const ts = `${String(t.getHours()).padStart(2, '0')}:${String(t.getMinutes()).padStart(2, '0')}:${String(t.getSeconds()).padStart(2, '0')}`;
2118
- process.stdout.write(`${DIM}${ts}${RST} ${line}\n`);
2119
- };
2120
- // 调试日志文件:每次运行 watch web 时清空,便于建立调试闭环
2121
- // 查看 sessions 调试日志 → 读这个文件
2122
- const logFile = path.join(p.logs, 'watch-web.log');
2123
- try {
2124
- fs.mkdirSync(p.logs, { recursive: true });
2125
- fs.writeFileSync(logFile, `# watch-web debug log\n# started ${new Date().toISOString()} pid=${process.pid}\n`);
2126
- }
2127
- catch { /* best effort */ }
2128
- const fileLog = (line) => {
2129
- const t = new Date();
2130
- const ts = `${String(t.getHours()).padStart(2, '0')}:${String(t.getMinutes()).padStart(2, '0')}:${String(t.getSeconds()).padStart(2, '0')}.${String(t.getMilliseconds()).padStart(3, '0')}`;
2105
+ // evolclaw-web 是独立插件包(可执行命令),按需安装。
2106
+ // 复用 npm-ops.npmInstallGlobal(含 EACCES→sudo 回退、Windows npm.cmd、超时)。
2107
+ const { execFileSync } = await import('child_process');
2108
+ const home = resolvePaths().root;
2109
+ if (!platform.commandExists('evolclaw-web')) {
2110
+ process.stdout.write('📦 evolclaw-web 未安装,正在从 npm 安装...\n');
2111
+ const { npmInstallGlobal } = await import('../utils/npm-ops.js');
2131
2112
  try {
2132
- fs.appendFileSync(logFile, `${ts} ${line.replace(/\x1b\[[0-9;]*m/g, '')}\n`);
2113
+ await npmInstallGlobal('evolclaw-web');
2133
2114
  }
2134
- catch { /* ignore */ }
2135
- };
2136
- // 同时输出到终端和日志文件
2137
- const log = (line) => { logLine(line); fileLog(line); };
2138
- const { startWatchWebServer } = await import('./watch-web/server.js');
2139
- let handle;
2140
- try {
2141
- handle = await startWatchWebServer({ log });
2142
- }
2143
- catch (e) {
2144
- console.error(`❌ 启动 Web 服务失败: ${e?.message || e}`);
2145
- process.exit(1);
2146
- }
2147
- // 注册 instance 文件
2148
- const instanceFile = path.join(p.instanceDir, `watch-web-${process.pid}.json`);
2149
- fs.writeFileSync(instanceFile, JSON.stringify({
2150
- pid: process.pid, startedAt: Date.now(), startedAtIso: new Date().toISOString(),
2151
- type: 'watch-web', port: handle.port,
2152
- }, null, 2));
2153
- // 列出本机访问地址
2154
- const os = await import('os');
2155
- const ifaces = os.networkInterfaces();
2156
- const lanIps = [];
2157
- for (const list of Object.values(ifaces)) {
2158
- for (const ni of list || []) {
2159
- if (ni.family === 'IPv4' && !ni.internal)
2160
- lanIps.push(ni.address);
2161
- }
2162
- }
2163
- process.stdout.write(`\n${BOLD}${CYAN}🔭 EvolClaw Watch Web${RST}\n\n`);
2164
- process.stdout.write(` ${BOLD}配对码:${RST} ${GREEN}${BOLD}${handle.pairingCode}${RST} ${DIM}(5 分钟内有效,配对后 token 缓存 24h 自动续期)${RST}\n\n`);
2165
- process.stdout.write(` ${BOLD}本机:${RST} http://localhost:${handle.port}\n`);
2166
- for (const ip of lanIps) {
2167
- process.stdout.write(` ${BOLD}局域网:${RST} http://${ip}:${handle.port}\n`);
2168
- }
2169
- process.stdout.write(`\n ${DIM}绑定 0.0.0.0,远程可访问。按任意键退出。${RST}\n`);
2170
- process.stdout.write(` ${DIM}调试日志: ${logFile}${RST}\n\n`);
2171
- const cleanup = () => {
2172
- try {
2173
- fs.unlinkSync(instanceFile);
2115
+ catch (e) {
2116
+ process.stderr.write(`❌ 安装 evolclaw-web 失败: ${e?.stderr || e?.message || e}\n 可手动安装: npm install -g evolclaw-web\n`);
2117
+ process.exit(1);
2174
2118
  }
2175
- catch { }
2176
- handle.close().finally(() => process.exit(0));
2177
- };
2178
- process.on('exit', () => { try {
2179
- fs.unlinkSync(instanceFile);
2180
- }
2181
- catch { } });
2182
- process.on('SIGINT', cleanup);
2183
- process.on('SIGTERM', cleanup);
2184
- platform.onShutdown(cleanup);
2185
- // 按任意键退出
2186
- if (process.stdin.isTTY) {
2187
- process.stdin.setRawMode(true);
2188
- process.stdin.resume();
2189
- process.stdin.on('data', (key) => {
2190
- logLine(`${YELLOW}收到退出指令,关闭服务…${RST}`);
2191
- cleanup();
2192
- });
2193
2119
  }
2194
- await new Promise(() => { });
2120
+ execFileSync('evolclaw-web', ['--home', home], { stdio: 'inherit' });
2195
2121
  }
2196
2122
  async function cmdRestartMonitor() {
2197
2123
  const p = resolvePaths();
@@ -2719,8 +2645,6 @@ async function cmdMv(oldDir, newDir) {
2719
2645
  console.log('✓ 项目目录已移动');
2720
2646
  if (r.evolclawDbUpdated > 0)
2721
2647
  console.log(`✓ EvolClaw 会话存储已更新 (${r.evolclawDbUpdated} 条记录)`);
2722
- if (r.evolclawConfigUpdated)
2723
- console.log('✓ agent config projects.list 已更新');
2724
2648
  console.log('\n迁移完成!');
2725
2649
  }
2726
2650
  catch (e) {
@@ -2894,7 +2818,7 @@ Agent:
2894
2818
  // ==================== Agent ====================
2895
2819
  async function cmdAgent(args) {
2896
2820
  const sub = args[0];
2897
- const formatJson = args.includes('--format') && args[args.indexOf('--format') + 1] === 'json';
2821
+ const formatJson = getArgValue(args, '--format') === 'json';
2898
2822
  if (!sub || isHelpFlag(sub)) {
2899
2823
  console.log(`用法: evolclaw agent <command>
2900
2824
 
@@ -3469,7 +3393,7 @@ function resolveAunPath(args) {
3469
3393
  }
3470
3394
  async function cmdAid(args) {
3471
3395
  const sub = args[0];
3472
- const formatJson = args.includes('--format') && args[args.indexOf('--format') + 1] === 'json';
3396
+ const formatJson = getArgValue(args, '--format') === 'json';
3473
3397
  const aunPath = resolveAunPath(args);
3474
3398
  if (!sub || isHelpFlag(sub)) {
3475
3399
  console.log(`用法: evolclaw aid <command>
@@ -3970,6 +3894,10 @@ async function cmdRpc(args) {
3970
3894
 
3971
3895
  每行 JSON 格式: {"method":"<namespace.method>","params":{...}}
3972
3896
 
3897
+ Options:
3898
+ --app <name> 指定应用 slot(独立消费通道)。仅对 message.pull / group.pull
3899
+ 等消费类方法有意义——隔离 seq 游标与消息过滤;默认与 daemon 共享通道。
3900
+
3973
3901
  示例:
3974
3902
  evolclaw rpc --as alice.agentid.pub --params '{"method":"message.send","params":{"to":"bob.agentid.pub","payload":{"type":"text","text":"hello"}}}'
3975
3903
  evolclaw rpc --as alice.agentid.pub --params calls.jsonl`);
@@ -3978,6 +3906,7 @@ async function cmdRpc(args) {
3978
3906
  const asIdx = args.indexOf('--as');
3979
3907
  const paramsIdx = args.indexOf('--params');
3980
3908
  const aunPath = resolveAunPath(args);
3909
+ const appSlot = getArgValue(args, '--app');
3981
3910
  if (asIdx === -1 || asIdx + 1 >= args.length) {
3982
3911
  console.error('❌ 缺少 --as <aid>');
3983
3912
  process.exit(1);
@@ -4022,11 +3951,11 @@ async function cmdRpc(args) {
4022
3951
  }
4023
3952
  const { rpcCall, rpcBatch } = await import('../aun/rpc/index.js');
4024
3953
  if (calls.length === 1) {
4025
- const result = await rpcCall(aid, calls[0].method, calls[0].params, { aunPath });
3954
+ const result = await rpcCall(aid, calls[0].method, calls[0].params, { aunPath, slotId: appSlot });
4026
3955
  console.log(JSON.stringify(result));
4027
3956
  }
4028
3957
  else {
4029
- const results = await rpcBatch(aid, calls, { aunPath });
3958
+ const results = await rpcBatch(aid, calls, { aunPath, slotId: appSlot });
4030
3959
  for (const r of results) {
4031
3960
  console.log(JSON.stringify(r));
4032
3961
  }
@@ -4036,7 +3965,7 @@ async function cmdRpc(args) {
4036
3965
  async function cmdStorage(args) {
4037
3966
  const sub = args[0];
4038
3967
  const aunPath = resolveAunPath(args);
4039
- const formatJson = args.includes('--format') && args[args.indexOf('--format') + 1] === 'json';
3968
+ const formatJson = getArgValue(args, '--format') === 'json';
4040
3969
  if (!sub || isHelpFlag(sub)) {
4041
3970
  console.log(`用法: evolclaw storage <command> <aid> [options]
4042
3971
 
@@ -4183,7 +4112,7 @@ Commands:
4183
4112
  async function cmdMsg(args) {
4184
4113
  const sub = args[0];
4185
4114
  const aunPath = resolveAunPath(args);
4186
- const formatJson = args.includes('--format') && args[args.indexOf('--format') + 1] === 'json';
4115
+ const formatJson = getArgValue(args, '--format') === 'json';
4187
4116
  const appIdx = args.indexOf('--app');
4188
4117
  const appSlot = appIdx >= 0 ? args[appIdx + 1] : undefined;
4189
4118
  if (!sub || isHelpFlag(sub)) {
@@ -4207,6 +4136,8 @@ Options:
4207
4136
  --content-type <mime> 显式覆盖 MIME(仅 --file 模式)
4208
4137
  --text <说明> 附件说明文字(仅 --file 模式)
4209
4138
  --transcript <text> 语音转写(仅 --as voice)
4139
+ -- end-of-options:其后所有参数按正文处理
4140
+ (用于发送恰好等于某 flag 的文本,如 send a b -- --encrypt)
4210
4141
 
4211
4142
  示例:
4212
4143
  evolclaw msg send alice.agentid.pub bob.agentid.pub "hello"
@@ -4465,7 +4396,7 @@ Options:
4465
4396
  async function cmdGroup(args) {
4466
4397
  const sub = args[0];
4467
4398
  const aunPath = resolveAunPath(args);
4468
- const formatJson = args.includes('--format') && args[args.indexOf('--format') + 1] === 'json';
4399
+ const formatJson = getArgValue(args, '--format') === 'json';
4469
4400
  const appIdx = args.indexOf('--app');
4470
4401
  const appSlot = appIdx >= 0 ? args[appIdx + 1] : undefined;
4471
4402
  if (!sub || isHelpFlag(sub)) {
@@ -4496,8 +4427,11 @@ async function cmdGroup(args) {
4496
4427
  Options:
4497
4428
  --app <name> 指定应用 slot(独立消费通道,不影响 daemon)
4498
4429
  --format json 输出 JSON 格式
4499
- --mention <aid> 发送时 @ 某个成员(可多次)
4430
+ --encrypt 启用端到端加密(仅 send)
4431
+ --mention <aid> 发送时 @ 某个成员(可多次,或用逗号分隔多个 aid)
4500
4432
  --mention-all 发送时 @ 所有人
4433
+ -- end-of-options:其后所有参数按正文处理
4434
+ (用于发送恰好等于某 flag 的文本,如 send a g -- --encrypt)
4501
4435
 
4502
4436
  示例:
4503
4437
  evolclaw group create alice.agentid.pub "Dev Team" --visibility private
@@ -4529,12 +4463,23 @@ Options:
4529
4463
  }
4530
4464
  return gid;
4531
4465
  };
4532
- // 收集 --mention(可多次)
4466
+ // 收集 --mention(可多次;每次的值支持逗号分隔多个 aid)
4533
4467
  const collectMentions = () => {
4534
4468
  const mentions = [];
4535
- for (let i = 0; i < args.length - 1; i++) {
4536
- if (args[i] === '--mention') {
4537
- mentions.push({ aid: args[i + 1] });
4469
+ for (let i = 0; i < args.length; i++) {
4470
+ if (args[i] !== '--mention')
4471
+ continue;
4472
+ const val = args[i + 1];
4473
+ if (val === undefined || val.startsWith('--')) {
4474
+ console.error(`❌ --mention 后面缺少 <aid>`);
4475
+ process.exit(1);
4476
+ }
4477
+ for (const aid of val.split(',').map(s => s.trim()).filter(Boolean)) {
4478
+ if (!isValidAid(aid)) {
4479
+ console.error(`❌ --mention 的 aid 无效: ${aid}`);
4480
+ process.exit(1);
4481
+ }
4482
+ mentions.push({ aid });
4538
4483
  }
4539
4484
  }
4540
4485
  if (args.includes('--mention-all')) {
@@ -4828,32 +4773,50 @@ Options:
4828
4773
  process.exit(1);
4829
4774
  }
4830
4775
  // ==================== Main ====================
4831
- function getArgValue(args, flag) {
4832
- const idx = args.indexOf(flag);
4833
- return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : undefined;
4834
- }
4835
4776
  /**
4836
- * 收集位置参数(从 startIdx 开始),跳过 flag 及其值。
4837
- * 已知"取值"的 flag 会消耗下一个 arg;已知"开关"的 flag 只占自身。
4777
+ * 收集位置参数(从 startIdx 开始)。
4778
+ *
4779
+ * flag 判定采用**精确匹配已知 flag 集合**,而非 `startsWith('--')`——
4780
+ * 这样"正文恰好以 -- 开头"(如消息文本 `--file 坏了`)不会被误当 flag 吞掉。
4781
+ * 仅当 token 精确等于某个已知 flag 时才按 flag 处理:
4782
+ * - VALUE_FLAGS:消耗自身 + 下一个 arg(flag 的值)
4783
+ * - BOOLEAN_FLAGS:仅消耗自身
4784
+ * 其余以 -- 开头但不在集合中的 token,一律视为正文。
4785
+ *
4786
+ * 另支持 POSIX `--` end-of-options 分隔符:遇到单独的 `--` 后,
4787
+ * 其后所有 token 无条件按正文处理(用于发送精确等于某 flag 的文本,如 `-- --encrypt`)。
4838
4788
  */
4789
+ const VALUE_FLAGS = new Set([
4790
+ '--format', '--app', '--after-seq', '--limit', '--file', '--link',
4791
+ '--payload', '--title', '--description', '--text', '--transcript',
4792
+ '--as', '--content-type', '--mention', '--visibility', '--join-mode',
4793
+ '--group-id', '--name', '--message', '--answer', '--page', '--size',
4794
+ '--aun-path', '--thread',
4795
+ ]);
4796
+ const BOOLEAN_FLAGS = new Set([
4797
+ '--encrypt', '--mention-all',
4798
+ ]);
4839
4799
  function collectPositional(args, startIdx) {
4840
- const VALUE_FLAGS = new Set([
4841
- '--format', '--app', '--after-seq', '--limit', '--file', '--link',
4842
- '--payload', '--title', '--description', '--text', '--transcript',
4843
- '--as', '--content-type', '--mention', '--visibility', '--join-mode',
4844
- '--group-id', '--name', '--message', '--answer', '--page', '--size',
4845
- '--aun-path',
4846
- ]);
4847
4800
  const out = [];
4801
+ let endOfFlags = false;
4848
4802
  for (let i = startIdx; i < args.length; i++) {
4849
4803
  const a = args[i];
4850
- if (a.startsWith('--')) {
4851
- if (VALUE_FLAGS.has(a))
4852
- i++; // 跳过 flag 的值
4853
- // else: 开关 flag,自身已被跳过
4804
+ if (endOfFlags) {
4805
+ out.push(a);
4806
+ continue;
4807
+ }
4808
+ if (a === '--') {
4809
+ endOfFlags = true;
4854
4810
  continue;
4855
4811
  }
4856
- out.push(a);
4812
+ if (VALUE_FLAGS.has(a)) {
4813
+ i++;
4814
+ continue;
4815
+ } // 精确匹配取值 flag:跳过其值
4816
+ if (BOOLEAN_FLAGS.has(a)) {
4817
+ continue;
4818
+ } // 精确匹配开关 flag:仅跳过自身
4819
+ out.push(a); // 其余(含以 -- 开头的未知 token)= 正文
4857
4820
  }
4858
4821
  return out;
4859
4822
  }
package/dist/cli/init.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import fs from 'fs';
2
+ import path from 'path';
2
3
  import readline from 'readline';
3
4
  import { resolvePaths, ensureDataDirs } from '../paths.js';
4
5
  import { commandExists } from '../utils/cross-platform.js';
@@ -10,11 +11,6 @@ function ask(rl, question) {
10
11
  return new Promise(resolve => rl.question(question, resolve));
11
12
  }
12
13
  const BASEAGENT_CANDIDATES = ['claude', 'codex', 'gemini'];
13
- const BASEAGENT_ENV_KEY = {
14
- claude: 'ANTHROPIC_API_KEY',
15
- codex: 'OPENAI_API_KEY',
16
- gemini: 'GEMINI_API_KEY',
17
- };
18
14
  function isBaseagentAvailable(baseagent) {
19
15
  if (baseagent === 'codex')
20
16
  return isCodexSdkAvailable();
@@ -26,16 +22,19 @@ function detectAvailable() {
26
22
  function pickDefault(available) {
27
23
  return (available.includes('claude') ? 'claude' : available[0]);
28
24
  }
29
- function buildDefaults(chosen) {
30
- const env = BASEAGENT_ENV_KEY[chosen];
25
+ function buildDefaults(chosen, available, projectsDefaultPath) {
26
+ const baseagents = {};
27
+ for (const b of available)
28
+ baseagents[b] = {};
31
29
  return {
32
30
  $schema_version: 1,
33
31
  active_baseagent: chosen,
34
- baseagents: { [chosen]: env ? { apiKey: `$ENV:${env}` } : {} },
32
+ baseagents,
33
+ ...(projectsDefaultPath ? { projects: { defaultPath: projectsDefaultPath } } : {}),
35
34
  };
36
35
  }
37
- function writeDefaults(_defaultsPath, chosen) {
38
- saveDefaultsSafe(buildDefaults(chosen));
36
+ function writeDefaults(chosen, available, projectsDefaultPath) {
37
+ saveDefaultsSafe(buildDefaults(chosen, available, projectsDefaultPath));
39
38
  }
40
39
  // ==================== Main ====================
41
40
  export async function cmdInit(options) {
@@ -85,7 +84,7 @@ export async function cmdInit(options) {
85
84
  else {
86
85
  chosen = pickDefault(available);
87
86
  }
88
- writeDefaults(defaultsPath, chosen);
87
+ writeDefaults(chosen, available);
89
88
  console.log(`✓ 已${exists ? '覆盖' : '创建'}: ${defaultsPath}`);
90
89
  console.log(` active_baseagent: ${chosen}`);
91
90
  const { agents } = loadAllAgents();
@@ -118,12 +117,33 @@ export async function cmdInit(options) {
118
117
  }
119
118
  return chosen;
120
119
  }
120
+ async function askProjectsDefaultPath() {
121
+ const defaultDir = path.join(p.root, 'projects', 'default');
122
+ const input = (await ask(rl, `项目默认目录 [${defaultDir}]: `)).trim();
123
+ const resolved = input || defaultDir;
124
+ if (!path.isAbsolute(resolved)) {
125
+ console.log(' ⚠ 需要绝对路径,已跳过');
126
+ return undefined;
127
+ }
128
+ if (!fs.existsSync(resolved)) {
129
+ const create = (await ask(rl, ` 目录不存在,是否创建?[Y/n]: `)).trim().toLowerCase();
130
+ if (create === '' || create === 'y' || create === 'yes') {
131
+ fs.mkdirSync(resolved, { recursive: true });
132
+ console.log(` ✓ 已创建 ${resolved}`);
133
+ }
134
+ else {
135
+ return undefined;
136
+ }
137
+ }
138
+ return resolved;
139
+ }
121
140
  try {
122
141
  if (exists) {
123
142
  const ans = (await ask(rl, `配置文件已存在: ${defaultsPath}\n 是否覆盖?[y/N] `)).trim().toLowerCase();
124
143
  if (ans === 'y' || ans === 'yes') {
125
144
  const chosen = await askBaseagent();
126
- writeDefaults(defaultsPath, chosen);
145
+ const projectsDefaultPath = await askProjectsDefaultPath();
146
+ writeDefaults(chosen, available, projectsDefaultPath);
127
147
  console.log(`\n✓ 已覆盖: ${defaultsPath}`);
128
148
  console.log(` active_baseagent: ${chosen}\n`);
129
149
  }
@@ -133,20 +153,16 @@ export async function cmdInit(options) {
133
153
  }
134
154
  else {
135
155
  const chosen = await askBaseagent();
136
- writeDefaults(defaultsPath, chosen);
156
+ const projectsDefaultPath = await askProjectsDefaultPath();
157
+ writeDefaults(chosen, available, projectsDefaultPath);
137
158
  console.log(`\n✓ 已创建: ${defaultsPath}`);
138
159
  console.log(` active_baseagent: ${chosen}\n`);
139
160
  }
140
- // ── 5. agent 时自动进入 agent new ──
161
+ // ── 5. 提示创建 agent ──
141
162
  const { agents } = loadAllAgents();
142
163
  if (agents.length === 0) {
143
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
144
- console.log('下一步:创建 agent\n');
145
- const { agentCreateInteractive } = await import('./agent.js');
146
- const result = await agentCreateInteractive({ rl });
147
- if (!result.ok) {
148
- console.error(`❌ ${result.error}`);
149
- }
164
+ console.log('提示:尚无 agent,运行以下命令创建:');
165
+ console.log(' evolclaw agent new <aid>.agentid.pub');
150
166
  }
151
167
  }
152
168
  finally {
@@ -2,7 +2,7 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { kitsRulesDir, resolvePaths } from '../paths.js';
4
4
  import { atomicWriteJson, atomicReadJson } from '../utils/atomic-write.js';
5
- import { wantsHelp } from './help.js';
5
+ import { wantsHelp, getArgValue } from './help.js';
6
6
  const isWindows = process.platform === 'win32';
7
7
  const KNOWN_BASEAGENTS = ['cc', 'codex', 'gemini'];
8
8
  function statePath() {
@@ -232,12 +232,6 @@ function resolveBaseAgent(input) {
232
232
  console.error(` Supported: ${KNOWN_BASEAGENTS.join(', ')}`);
233
233
  process.exit(1);
234
234
  }
235
- function getArgValue(args, flag) {
236
- const idx = args.indexOf(flag);
237
- if (idx === -1 || idx + 1 >= args.length)
238
- return undefined;
239
- return args[idx + 1];
240
- }
241
235
  function pathEquals(a, b) {
242
236
  if (isWindows) {
243
237
  return path.resolve(a).toLowerCase() === path.resolve(b).toLowerCase();