evolclaw 3.1.3 → 3.1.4

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 (38) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/assets/.env.template +4 -0
  3. package/assets/config.json.template +6 -0
  4. package/assets/wechat-group-qr.jpeg +0 -0
  5. package/dist/agents/kit-renderer.js +35 -21
  6. package/dist/aun/aid/agentmd.js +25 -54
  7. package/dist/aun/aid/client.js +22 -7
  8. package/dist/aun/aid/identity.js +314 -28
  9. package/dist/aun/aid/index.js +1 -1
  10. package/dist/aun/rpc/connection.js +8 -13
  11. package/dist/channels/aun.js +31 -72
  12. package/dist/cli/agent.js +15 -22
  13. package/dist/cli/bench.js +8 -14
  14. package/dist/cli/help.js +23 -0
  15. package/dist/cli/index.js +371 -36
  16. package/dist/cli/init-channel.js +2 -3
  17. package/dist/cli/link-rules.js +2 -1
  18. package/dist/cli/net-check.js +10 -11
  19. package/dist/core/command-handler.js +6 -7
  20. package/dist/core/message/message-processor.js +19 -18
  21. package/dist/core/relation/peer-identity.js +64 -21
  22. package/dist/core/session/session-manager.js +6 -2
  23. package/dist/core/trigger/manager.js +37 -0
  24. package/dist/index.js +4 -1
  25. package/dist/paths.js +87 -16
  26. package/dist/utils/npm-ops.js +18 -11
  27. package/kits/eck_manifest.json +8 -8
  28. package/kits/rules/05-venue.md +2 -2
  29. package/kits/templates/system-fragments/baseagent.md +7 -1
  30. package/kits/templates/system-fragments/channel.md +4 -1
  31. package/kits/templates/system-fragments/identity.md +4 -4
  32. package/kits/templates/system-fragments/relation.md +8 -5
  33. package/kits/templates/system-fragments/session.md +20 -0
  34. package/kits/templates/system-fragments/venue.md +4 -1
  35. package/package.json +4 -2
  36. package/dist/net-check.js +0 -640
  37. package/dist/watch-msg.js +0 -544
  38. package/kits/templates/system-fragments/eckruntime.md +0 -14
package/dist/cli/index.js CHANGED
@@ -11,6 +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
15
  import * as platform from '../utils/cross-platform.js';
15
16
  import { EventBus } from '../core/event-bus.js';
16
17
  import { tryUpgrade, tryUpgradeAunSdk } from '../utils/npm-ops.js';
@@ -2765,7 +2766,7 @@ Agent:
2765
2766
  process.exit(1);
2766
2767
  }
2767
2768
  // help 不需要连接服务,直接复用无参数时的帮助输出
2768
- if (args[0] === 'help') {
2769
+ if (isHelpFlag(args[0])) {
2769
2770
  return cmdCtl([]);
2770
2771
  }
2771
2772
  const sessionId = process.env.EVOLCLAW_SESSION_ID;
@@ -2800,7 +2801,7 @@ Agent:
2800
2801
  async function cmdAgent(args) {
2801
2802
  const sub = args[0];
2802
2803
  const formatJson = args.includes('--format') && args[args.indexOf('--format') + 1] === 'json';
2803
- if (!sub || sub === 'help' || sub === '--help' || sub === '-h' || args.includes('--help') || args.includes('-h')) {
2804
+ if (!sub || isHelpFlag(sub)) {
2804
2805
  console.log(`用法: evolclaw agent <command>
2805
2806
 
2806
2807
  Commands:
@@ -2818,6 +2819,7 @@ Commands:
2818
2819
 
2819
2820
  Options:
2820
2821
  --format json 输出 JSON 格式
2822
+ --help, -h 各子命令均支持,查看详细用法
2821
2823
 
2822
2824
  示例:
2823
2825
  evolclaw agent list
@@ -2832,7 +2834,13 @@ Options:
2832
2834
  }
2833
2835
  const { agentList, agentShow, agentCreateInteractive, agentCreateNonInteractive, agentReload, agentEnable, agentDisable, agentGet, agentSet, agentDelete, agentRename, } = await import('./agent.js');
2834
2836
  // --- list ---
2835
- if (!sub || sub === 'list') {
2837
+ if (sub === 'list') {
2838
+ if (wantsHelp(args)) {
2839
+ console.log(`用法: evolclaw agent list [--format json]
2840
+
2841
+ 列出所有 agent,显示名称、状态、渠道、项目、基座、最后活跃时间。`);
2842
+ return;
2843
+ }
2836
2844
  const result = await agentList();
2837
2845
  if (!result.ok) {
2838
2846
  if (formatJson) {
@@ -2893,6 +2901,24 @@ Options:
2893
2901
  }
2894
2902
  // --- new ---
2895
2903
  if (sub === 'new') {
2904
+ if (wantsHelp(args)) {
2905
+ console.log(`用法: evolclaw agent new [aid] 交互式创建
2906
+ evolclaw agent new <aid> --non-interactive [选项]
2907
+
2908
+ 非交互模式选项:
2909
+ --baseagent <claude|codex|gemini> 默认: PATH 中第一个可用
2910
+ --project <absolute path> 必填
2911
+ --owner <aid>
2912
+ --name <display-name>
2913
+ --description <text>
2914
+ --force 覆盖已有 config.json
2915
+ --format json 输出 JSON
2916
+
2917
+ 示例:
2918
+ evolclaw agent new mybot.agentid.pub
2919
+ evolclaw agent new mybot.agentid.pub --non-interactive --project /abs/path --baseagent claude`);
2920
+ return;
2921
+ }
2896
2922
  const name = args[1];
2897
2923
  const nonInteractive = args.includes('--non-interactive');
2898
2924
  if (nonInteractive) {
@@ -2989,6 +3015,14 @@ Options:
2989
3015
  // }
2990
3016
  // --- reload ---
2991
3017
  if (sub === 'reload') {
3018
+ if (wantsHelp(args)) {
3019
+ console.log(`用法: evolclaw agent reload [aid] [--format json]
3020
+
3021
+ 热重载 agent 配置。
3022
+ 无参数 全量 resync(扫磁盘,新增上线、删除下线、修改热更新)
3023
+ <aid> 仅热重载指定 agent`);
3024
+ return;
3025
+ }
2992
3026
  const target = args[1] && !args[1].startsWith('--') ? args[1] : undefined;
2993
3027
  const result = await agentReload(target);
2994
3028
  if (!result.ok) {
@@ -3016,6 +3050,12 @@ Options:
3016
3050
  }
3017
3051
  // --- enable ---
3018
3052
  if (sub === 'enable') {
3053
+ if (wantsHelp(args)) {
3054
+ console.log(`用法: evolclaw agent enable <aid> [--format json]
3055
+
3056
+ 启用 agent。若服务运行中会热重载,否则下次 evolclaw start 时生效。`);
3057
+ return;
3058
+ }
3019
3059
  const aid = args[1];
3020
3060
  if (!aid) {
3021
3061
  console.error('用法: evolclaw agent enable <aid>');
@@ -3041,6 +3081,12 @@ Options:
3041
3081
  }
3042
3082
  // --- disable ---
3043
3083
  if (sub === 'disable') {
3084
+ if (wantsHelp(args)) {
3085
+ console.log(`用法: evolclaw agent disable <aid> [--format json]
3086
+
3087
+ 停用 agent。若服务运行中会热重载离线,否则在配置中标记为禁用。`);
3088
+ return;
3089
+ }
3044
3090
  const aid = args[1];
3045
3091
  if (!aid) {
3046
3092
  console.error('用法: evolclaw agent disable <aid>');
@@ -3066,6 +3112,16 @@ Options:
3066
3112
  }
3067
3113
  // --- get ---
3068
3114
  if (sub === 'get') {
3115
+ if (wantsHelp(args)) {
3116
+ console.log(`用法: evolclaw agent get <aid> <key> [--format json]
3117
+
3118
+ 读取单个配置字段。key 支持点路径,如 "channels.aun.enabled"。
3119
+
3120
+ 示例:
3121
+ evolclaw agent get mybot.agentid.pub active_baseagent
3122
+ evolclaw agent get mybot.agentid.pub channels.aun.enabled`);
3123
+ return;
3124
+ }
3069
3125
  const aid = args[1];
3070
3126
  const key = args[2];
3071
3127
  if (!aid || !key) {
@@ -3093,6 +3149,16 @@ Options:
3093
3149
  }
3094
3150
  // --- set ---
3095
3151
  if (sub === 'set') {
3152
+ if (wantsHelp(args)) {
3153
+ console.log(`用法: evolclaw agent set <aid> <key> <value> [--format json]
3154
+
3155
+ 修改单个配置字段。key 支持点路径。修改后若服务运行中会自动热重载。
3156
+
3157
+ 示例:
3158
+ evolclaw agent set mybot.agentid.pub active_baseagent codex
3159
+ evolclaw agent set mybot.agentid.pub channels.aun.enabled true`);
3160
+ return;
3161
+ }
3096
3162
  const aid = args[1];
3097
3163
  const key = args[2];
3098
3164
  const val = args[3];
@@ -3120,6 +3186,15 @@ Options:
3120
3186
  }
3121
3187
  // --- rename ---
3122
3188
  if (sub === 'rename') {
3189
+ if (wantsHelp(args)) {
3190
+ console.log(`用法: evolclaw agent rename <aid> <name> [--format json]
3191
+
3192
+ 修改 agent 显示名称。同时更新本地 agent.md 并尝试重新上传。
3193
+
3194
+ 示例:
3195
+ evolclaw agent rename mybot.agentid.pub "My Bot"`);
3196
+ return;
3197
+ }
3123
3198
  const aid = args[1];
3124
3199
  const newName = args[2];
3125
3200
  if (!aid || !newName) {
@@ -3146,6 +3221,14 @@ Options:
3146
3221
  }
3147
3222
  // --- delete ---
3148
3223
  if (sub === 'delete') {
3224
+ if (wantsHelp(args)) {
3225
+ console.log(`用法: evolclaw agent delete <aid> [--purge] [--format json]
3226
+
3227
+ 删除 agent 的配置。
3228
+ --purge 同时清除该 agent 的会话、消息、日志等运行时数据
3229
+ 默认 仅删除 config.json,运行时数据保留`);
3230
+ return;
3231
+ }
3149
3232
  const aid = args[1];
3150
3233
  if (!aid) {
3151
3234
  console.error('用法: evolclaw agent delete <aid> [--purge]');
@@ -3172,6 +3255,12 @@ Options:
3172
3255
  }
3173
3256
  // --- show ---
3174
3257
  if (sub === 'show') {
3258
+ if (wantsHelp(args)) {
3259
+ console.log(`用法: evolclaw agent show <aid> [--format json]
3260
+
3261
+ 查看 agent 详情:身份、配置、连接状态、会话路径等。`);
3262
+ return;
3263
+ }
3175
3264
  const aid = args[1];
3176
3265
  if (!aid) {
3177
3266
  console.error('用法: evolclaw agent show <aid>');
@@ -3285,63 +3374,134 @@ function resolveAunPath(args) {
3285
3374
  return process.env.AUN_HOME || undefined;
3286
3375
  }
3287
3376
  async function cmdAid(args) {
3288
- const sub = args[0] || 'list';
3377
+ const sub = args[0];
3289
3378
  const formatJson = args.includes('--format') && args[args.indexOf('--format') + 1] === 'json';
3290
3379
  const aunPath = resolveAunPath(args);
3291
- if (!sub || sub === 'help' || sub === '--help' || sub === '-h' || args.includes('--help') || args.includes('-h')) {
3380
+ if (!sub || isHelpFlag(sub)) {
3292
3381
  console.log(`用法: evolclaw aid <command>
3293
3382
 
3294
3383
  Commands:
3295
- list 列出本地所有 AID
3296
- show <aid> 查看本地 AID 详情(证书有效期、私钥状态)
3384
+ list 列出本地所有 AID(实测 sign+verify)
3385
+ show <aid> 查看本地 AID 详情(证书、私钥、签名能力)
3297
3386
  new <aid> 创建新 AID 身份
3298
- delete <aid> 删除本地 AID(无网络注销)
3387
+ delete <aid> 删除指定本地 AID(无网络注销)
3388
+ delete --orphan 批量清理无私钥的外部 AID 缓存
3389
+ delete --no-cert 批量清理无私钥也无公钥证书的孤儿目录
3390
+ delete --unrecoverable 批量清理云端公钥已变更、本地不可恢复的 AID
3391
+ 批量删除默认 dry-run,加 --yes 执行
3299
3392
  lookup <aid> 远程探测 AID(是否存在 + 网关 + agent.md)
3300
3393
  agentmd put <aid> 读本地 agent.md → 签名 → 上传
3301
3394
  agentmd get <aid> 下载 agent.md → 验签 → 本地持久化
3302
3395
 
3303
3396
  Options:
3304
3397
  --format json 输出 JSON 格式
3398
+ --help, -h 各子命令均支持,查看详细用法
3305
3399
 
3306
3400
  示例:
3307
3401
  evolclaw aid list
3308
3402
  evolclaw aid show toleiliang2.agentid.pub
3309
3403
  evolclaw aid new reviewer.agentid.pub
3404
+ evolclaw aid delete --help
3310
3405
  evolclaw aid delete old.agentid.pub
3406
+ evolclaw aid delete --orphan
3407
+ evolclaw aid delete --unrecoverable --yes
3311
3408
  evolclaw aid lookup someone.agentid.pub
3312
3409
  evolclaw aid agentmd put mybot.agentid.pub
3313
3410
  evolclaw aid agentmd get someone.agentid.pub`);
3314
3411
  return;
3315
3412
  }
3316
- const { aidList, aidCreate, aidShow, aidDelete, aidLookup, agentmdPut, agentmdGet, buildInitialAgentMd, isValidAid } = await import('../aun/aid/index.js');
3413
+ const { aidList, aidListVerified, aidCreate, aidShow, aidDelete, aidLookup, agentmdPut, agentmdGet, buildInitialAgentMd, isValidAid } = await import('../aun/aid/index.js');
3317
3414
  if (sub === 'list') {
3318
- const aids = aidList(aunPath);
3415
+ if (wantsHelp(args)) {
3416
+ console.log(`用法: evolclaw aid list [筛选选项] [--no-verify] [--format json]
3417
+
3418
+ 列出本地 AID 并跑 sign+verify 自检。
3419
+
3420
+ 筛选选项(可组合,不指定 = 列出 mine + broken + peer-cert):
3421
+ --mine 仅本地可用身份(实测可签名+验签通过)
3422
+ --broken 仅有私钥但不可用(公钥不匹配 / 证书过期 / sign 失败)
3423
+ --peer-cert 仅对端 AID(无私钥,有公钥证书)
3424
+ --no-cert 仅无私钥无证书的目录(默认隐藏,需用 aid delete --no-cert 清理)
3425
+
3426
+ 选项:
3427
+ --no-verify 跳过 sign+verify 实测,仅静态扫描(更快,mine/broken 仅按静态判定近似)
3428
+ --format json JSON 格式输出
3429
+
3430
+ 输出图标:
3431
+ 🔑 有私钥
3432
+ ✅ 实测可签名/验签
3433
+ ❌ 不可签名(公钥不匹配 / sign 失败 / verify 失败等)
3434
+ ⌛ 证书过期
3435
+ 📜 有公钥证书
3436
+ 📄 有 agent.md
3437
+
3438
+ 示例:
3439
+ evolclaw aid list 列出 mine + broken + peer-cert
3440
+ evolclaw aid list --mine 仅可用身份
3441
+ evolclaw aid list --mine --broken 所有有私钥的 AID
3442
+ evolclaw aid list --no-cert 仅无私钥无证书的孤儿目录
3443
+ evolclaw aid list --no-verify 跳过实测,快速静态扫描`);
3444
+ return;
3445
+ }
3446
+ const wantMine = args.includes('--mine');
3447
+ const wantBroken = args.includes('--broken');
3448
+ const wantPeerCert = args.includes('--peer-cert');
3449
+ const wantNoCert = args.includes('--no-cert');
3450
+ const noVerify = args.includes('--no-verify');
3451
+ const anyFilter = wantMine || wantBroken || wantPeerCert || wantNoCert;
3452
+ // 默认: mine + broken + peer-cert(隐藏 no-cert,需显式 --no-cert 才列)
3453
+ const showMine = anyFilter ? wantMine : true;
3454
+ const showBroken = anyFilter ? wantBroken : true;
3455
+ const showPeerCert = anyFilter ? wantPeerCert : true;
3456
+ const showNoCert = anyFilter ? wantNoCert : false;
3457
+ const all = noVerify ? aidList(aunPath) : await aidListVerified(aunPath);
3458
+ const aids = all.filter(a => (showMine && a.category === 'mine') ||
3459
+ (showBroken && a.category === 'broken') ||
3460
+ (showPeerCert && a.category === 'peer-cert') ||
3461
+ (showNoCert && a.category === 'no-cert'));
3319
3462
  if (formatJson) {
3320
3463
  console.log(JSON.stringify(aids, null, 2));
3321
3464
  return;
3322
3465
  }
3323
3466
  if (aids.length === 0) {
3324
- console.log('本地无 AID');
3467
+ console.log('无匹配 AID');
3325
3468
  return;
3326
3469
  }
3327
- console.log('本地 AID:');
3470
+ console.log(`本地 AID${noVerify ? '(静态扫描,未实测)' : ''}:`);
3328
3471
  for (const a of aids) {
3329
- const icons = [
3330
- a.hasPrivateKey ? '🔑' : ' ',
3331
- a.hasAgentMd ? '📄' : ' ',
3332
- ].join('');
3333
- console.log(` ${icons} ${a.aid}`);
3334
- }
3335
- console.log('\n🔑=私钥 📄=agent.md');
3472
+ const keyIcon = a.hasPrivateKey ? '🔑' : ' ';
3473
+ let signIcon = ' ';
3474
+ // --no-verify signVerified 始终为 null,用 canSign 作为静态近似
3475
+ const effectiveOk = noVerify ? a.canSign : a.signVerified === true;
3476
+ const effectiveFail = noVerify ? (a.hasPrivateKey && !a.canSign) : (a.hasPrivateKey && a.signVerified === false);
3477
+ if (effectiveOk)
3478
+ signIcon = '';
3479
+ else if (a.hasPrivateKey && a.certExpired)
3480
+ signIcon = '⌛';
3481
+ else if (effectiveFail)
3482
+ signIcon = '❌';
3483
+ const certIcon = a.hasCert ? '📜' : ' ';
3484
+ const mdIcon = a.hasAgentMd ? '📄' : ' ';
3485
+ const tail = !noVerify && a.signVerified === false && a.signError && !(a.keyMatchesCert === false || a.certExpired || !a.hasPrivateKey || !a.hasCert)
3486
+ ? ` (${a.signError})` : '';
3487
+ console.log(` ${keyIcon} ${signIcon} ${certIcon} ${mdIcon} ${a.aid}${tail}`);
3488
+ }
3489
+ console.log('\n🔑=私钥 ✅=可签名/验签 ❌=不可签名 ⌛=证书过期 📜=公钥证书 📄=agent.md');
3336
3490
  return;
3337
3491
  }
3338
3492
  if (sub === 'show') {
3493
+ if (wantsHelp(args)) {
3494
+ console.log(`用法: evolclaw aid show <aid> [--format json]
3495
+
3496
+ 查看本地 AID 详情:私钥/证书/agent.md 状态、签名能力实测。`);
3497
+ return;
3498
+ }
3339
3499
  const aid = args[1];
3340
3500
  if (!aid) {
3341
3501
  console.error('用法: evolclaw aid show <aid>');
3342
3502
  process.exit(1);
3343
3503
  }
3344
- const info = aidShow(aid, { aunPath });
3504
+ const info = await aidShow(aid, { aunPath });
3345
3505
  if (formatJson) {
3346
3506
  console.log(JSON.stringify(info, null, 2));
3347
3507
  return;
@@ -3349,12 +3509,37 @@ Options:
3349
3509
  console.log(`AID: ${info.aid}`);
3350
3510
  console.log(` 私钥: ${info.hasPrivateKey ? '有' : '无'}`);
3351
3511
  console.log(` agent.md: ${info.hasAgentMd ? '有' : '无'}`);
3352
- console.log(` 证书到期: ${info.certExpiresAt ?? '无证书'}`);
3512
+ if (info.hasAgentMd) {
3513
+ const sigLabel = info.agentMdSignature === 'verified' ? '✓ 已验签'
3514
+ : info.agentMdSignature === 'unsigned' ? '⚠ 未签名'
3515
+ : info.agentMdSignature === 'invalid' ? `✗ 签名无效${info.agentMdSignatureReason ? ': ' + info.agentMdSignatureReason : ''}`
3516
+ : '? 未知';
3517
+ console.log(` 签名状态: ${sigLabel}`);
3518
+ }
3519
+ console.log(` 证书到期: ${info.certExpiresAt ?? '无证书'}${info.certExpired ? ' (已过期!)' : ''}`);
3353
3520
  if (info.certSubject)
3354
3521
  console.log(` 证书主体: ${info.certSubject}`);
3522
+ if (info.keyMatchesCert === false)
3523
+ console.log(` 密钥/证书: ✗ 公钥不匹配(cert.pem 与 key.json 公钥不一致)`);
3524
+ else if (info.keyMatchesCert === true)
3525
+ console.log(` 密钥/证书: ✓ 公钥一致`);
3526
+ if (info.signVerified === true)
3527
+ console.log(` 可签名/验签: ✓ 实测通过`);
3528
+ else if (info.signVerified === false)
3529
+ console.log(` 可签名/验签: ✗ 失败${info.signError ? `(${info.signError})` : ''}`);
3530
+ else
3531
+ console.log(` 可签名/验签: ? 未知`);
3355
3532
  return;
3356
3533
  }
3357
3534
  if (sub === 'new') {
3535
+ if (wantsHelp(args)) {
3536
+ console.log(`用法: evolclaw aid new <完整AID>
3537
+
3538
+ 创建新 AID 身份:生成 ECDSA 密钥对、向 Issuer 申请证书、构建并上传初始 agent.md。
3539
+
3540
+ 例: evolclaw aid new reviewer.agentid.pub`);
3541
+ return;
3542
+ }
3358
3543
  const aid = args[1];
3359
3544
  if (!aid) {
3360
3545
  console.error('用法: evolclaw aid new <完整AID>\n例: evolclaw aid new reviewer.agentid.pub');
@@ -3385,22 +3570,162 @@ Options:
3385
3570
  return;
3386
3571
  }
3387
3572
  if (sub === 'delete') {
3388
- const aid = args[1];
3389
- if (!aid) {
3390
- console.error('用法: evolclaw aid delete <aid>');
3573
+ if (wantsHelp(args)) {
3574
+ console.log(`用法: evolclaw aid delete <子命令>
3575
+
3576
+ 单个删除:
3577
+ evolclaw aid delete <aid> 删除指定 AID 的本地数据(无网络注销)
3578
+
3579
+ 批量删除(默认 dry-run,加 --yes 才真删):
3580
+ evolclaw aid delete --orphan 删除所有"无私钥"的本地缓存(外部 AID)
3581
+ evolclaw aid delete --no-cert 删除所有"无私钥也无公钥证书"的目录
3582
+ 条件:!hasPrivateKey && !hasCert
3583
+ 这些目录最多只剩 agent.md 或 SQLite 残留,
3584
+ 对验签和加密通信都没用,删除安全。
3585
+ evolclaw aid delete --unrecoverable 删除所有不可恢复的 AID
3586
+ 条件:本地 sign+verify 实测失败
3587
+ 且 PKI 探测确认云端公钥也不等本地 key.json
3588
+
3589
+ 选项:
3590
+ --yes 跳过 dry-run,立即执行
3591
+ --skip-pki --unrecoverable 时跳过 PKI 探测,仅依据本地 sign+verify 失败判断(危险,可能误删可恢复 AID)
3592
+ --format json 输出 JSON 格式
3593
+
3594
+ 示例:
3595
+ evolclaw aid delete old.agentid.pub
3596
+ evolclaw aid delete --orphan 列出会被清理的孤儿
3597
+ evolclaw aid delete --orphan --yes 实际清理
3598
+ evolclaw aid delete --no-cert 列出无证书孤儿目录
3599
+ evolclaw aid delete --no-cert --yes 实际清理
3600
+ evolclaw aid delete --unrecoverable 联网探测后列出无救 AID
3601
+ evolclaw aid delete --unrecoverable --yes`);
3602
+ return;
3603
+ }
3604
+ const yes = args.includes('--yes');
3605
+ const skipPki = args.includes('--skip-pki');
3606
+ const orphan = args.includes('--orphan');
3607
+ const noCert = args.includes('--no-cert');
3608
+ const unrecoverable = args.includes('--unrecoverable');
3609
+ const modes = [orphan, noCert, unrecoverable].filter(Boolean).length;
3610
+ if (modes > 1) {
3611
+ console.error('❌ --orphan / --no-cert / --unrecoverable 互斥,不能同时使用');
3391
3612
  process.exit(1);
3392
3613
  }
3393
- const deleted = aidDelete(aid, { aunPath });
3394
- if (deleted) {
3395
- console.log(`✓ ${aid} 已删除`);
3614
+ // 单个 aid 删除:保留原有行为
3615
+ if (modes === 0) {
3616
+ const aid = args[1];
3617
+ if (!aid) {
3618
+ console.error('用法: evolclaw aid delete <aid>\n evolclaw aid delete --orphan | --no-cert | --unrecoverable [--yes]\n evolclaw aid delete --help 查看完整用法');
3619
+ process.exit(1);
3620
+ }
3621
+ const deleted = aidDelete(aid, { aunPath });
3622
+ if (deleted) {
3623
+ console.log(`✓ ${aid} 已删除`);
3624
+ }
3625
+ else {
3626
+ console.error(`❌ 本地不存在: ${aid}`);
3627
+ process.exit(1);
3628
+ }
3629
+ return;
3630
+ }
3631
+ // 批量模式:先选出候选
3632
+ const { probePkiRecoverability } = await import('../aun/aid/index.js');
3633
+ const candidates = [];
3634
+ if (orphan) {
3635
+ const aids = aidList(aunPath);
3636
+ for (const a of aids) {
3637
+ if (!a.hasPrivateKey)
3638
+ candidates.push({ aid: a.aid, reason: 'no private key (external AID cache)' });
3639
+ }
3640
+ }
3641
+ else if (noCert) {
3642
+ const aids = aidList(aunPath);
3643
+ for (const a of aids) {
3644
+ if (!a.hasPrivateKey && !a.hasCert) {
3645
+ const traits = [a.hasAgentMd ? 'agent.md' : null].filter(Boolean).join(', ');
3646
+ candidates.push({ aid: a.aid, reason: `no private key, no cert${traits ? ` (only: ${traits})` : ''}` });
3647
+ }
3648
+ }
3396
3649
  }
3397
3650
  else {
3398
- console.error(`❌ 本地不存在: ${aid}`);
3399
- process.exit(1);
3651
+ // unrecoverable: 必须先做 sign+verify 实测
3652
+ if (!formatJson)
3653
+ console.log('扫描中: 本地签名/验签实测...');
3654
+ const aids = await aidListVerified(aunPath);
3655
+ const localBroken = aids.filter(a => a.hasPrivateKey && a.signVerified === false);
3656
+ if (skipPki) {
3657
+ for (const a of localBroken) {
3658
+ candidates.push({ aid: a.aid, reason: `sign+verify failed (${a.signError ?? 'unknown'}) [--skip-pki: 未联网验证]` });
3659
+ }
3660
+ }
3661
+ else {
3662
+ if (!formatJson)
3663
+ console.log(`扫描中: 对 ${localBroken.length} 个本地损坏 AID 做 PKI 探测...`);
3664
+ for (const a of localBroken) {
3665
+ const r = await probePkiRecoverability(a.aid, { aunPath });
3666
+ if (r.kind === 'unrecoverable') {
3667
+ candidates.push({ aid: a.aid, reason: `local broken; PKI: ${r.reason}`, pki: 'unrecoverable' });
3668
+ }
3669
+ else if (r.kind === 'no-server-record') {
3670
+ candidates.push({ aid: a.aid, reason: `local broken; PKI: ${r.reason}`, pki: 'no-server-record' });
3671
+ }
3672
+ else {
3673
+ // recoverable / no-key / unknown 一律保守不删
3674
+ if (!formatJson)
3675
+ console.log(` · 跳过 ${a.aid}: PKI=${r.kind}${('reason' in r) ? ' — ' + r.reason : ''}`);
3676
+ }
3677
+ }
3678
+ }
3679
+ }
3680
+ if (formatJson) {
3681
+ console.log(JSON.stringify({
3682
+ mode: orphan ? 'orphan' : noCert ? 'no-cert' : 'unrecoverable',
3683
+ dryRun: !yes,
3684
+ skipPki: unrecoverable ? skipPki : undefined,
3685
+ candidates,
3686
+ }, null, 2));
3687
+ if (yes) {
3688
+ for (const c of candidates)
3689
+ aidDelete(c.aid, { aunPath });
3690
+ }
3691
+ return;
3692
+ }
3693
+ if (candidates.length === 0) {
3694
+ console.log(orphan ? '✓ 无孤儿 AID' : noCert ? '✓ 无无证书孤儿目录' : '✓ 无不可恢复 AID');
3695
+ return;
3400
3696
  }
3697
+ console.log(`\n${yes ? '将删除' : '候选删除(dry-run)'}:${candidates.length} 个 AID`);
3698
+ for (const c of candidates) {
3699
+ console.log(` - ${c.aid}`);
3700
+ console.log(` ${c.reason}`);
3701
+ }
3702
+ if (!yes) {
3703
+ console.log('\n(dry-run,未真删除。加 --yes 执行真删。)');
3704
+ return;
3705
+ }
3706
+ let ok = 0;
3707
+ let fail = 0;
3708
+ for (const c of candidates) {
3709
+ const deleted = aidDelete(c.aid, { aunPath });
3710
+ if (deleted) {
3711
+ console.log(` ✓ 删除 ${c.aid}`);
3712
+ ok++;
3713
+ }
3714
+ else {
3715
+ console.log(` ✗ 失败 ${c.aid}(已不存在?)`);
3716
+ fail++;
3717
+ }
3718
+ }
3719
+ console.log(`\n完成:成功 ${ok},失败 ${fail}`);
3401
3720
  return;
3402
3721
  }
3403
3722
  if (sub === 'lookup') {
3723
+ if (wantsHelp(args)) {
3724
+ console.log(`用法: evolclaw aid lookup <aid> [--format json]
3725
+
3726
+ 远程探测 AID:是否注册、所在网关、是否有 agent.md(不验签,仅获取)。`);
3727
+ return;
3728
+ }
3404
3729
  const aid = args[1];
3405
3730
  if (!aid) {
3406
3731
  console.error('用法: evolclaw aid lookup <aid>');
@@ -3438,6 +3763,13 @@ Options:
3438
3763
  if (sub === 'agentmd') {
3439
3764
  const verb = args[1];
3440
3765
  const aid = args[2];
3766
+ if (!verb || isHelpFlag(verb) || wantsHelp(args)) {
3767
+ console.log(`用法: evolclaw aid agentmd <put|get> <aid> [--format json]
3768
+
3769
+ put <aid> 读本地 agent.md → 用本地私钥签名 → 上传到 PKI
3770
+ get <aid> 从 PKI 下载 agent.md → 验签 → 持久化到本地`);
3771
+ return;
3772
+ }
3441
3773
  if (verb === 'put') {
3442
3774
  if (!aid) {
3443
3775
  console.error('用法: evolclaw aid agentmd put <aid>');
@@ -3509,7 +3841,7 @@ Options:
3509
3841
  }
3510
3842
  // ==================== RPC ====================
3511
3843
  async function cmdRpc(args) {
3512
- if (args[0] === 'help' || args.length === 0) {
3844
+ if (args.length === 0 || isHelpFlag(args[0])) {
3513
3845
  console.log(`用法: evolclaw rpc --as <aid> --params <params>
3514
3846
 
3515
3847
  通用 AUN RPC 调用。
@@ -3588,7 +3920,7 @@ async function cmdStorage(args) {
3588
3920
  const sub = args[0];
3589
3921
  const aunPath = resolveAunPath(args);
3590
3922
  const formatJson = args.includes('--format') && args[args.indexOf('--format') + 1] === 'json';
3591
- if (!sub || sub === 'help') {
3923
+ if (!sub || isHelpFlag(sub)) {
3592
3924
  console.log(`用法: evolclaw storage <command> <aid> [options]
3593
3925
 
3594
3926
  Commands:
@@ -3738,7 +4070,7 @@ async function cmdMsg(args) {
3738
4070
  const appIdx = args.indexOf('--app');
3739
4071
  const appSlot = appIdx >= 0 ? args[appIdx + 1] : undefined;
3740
4072
  const asDaemon = args.includes('--as-daemon');
3741
- if (!sub || sub === 'help') {
4073
+ if (!sub || isHelpFlag(sub)) {
3742
4074
  console.log(`用法: evolclaw msg <command> <from-aid> [args...] [options]
3743
4075
 
3744
4076
  Commands:
@@ -4024,7 +4356,7 @@ async function cmdGroup(args) {
4024
4356
  const appIdx = args.indexOf('--app');
4025
4357
  const appSlot = appIdx >= 0 ? args[appIdx + 1] : undefined;
4026
4358
  const asDaemon = args.includes('--as-daemon');
4027
- if (!sub || sub === 'help') {
4359
+ if (!sub || isHelpFlag(sub)) {
4028
4360
  console.log(`用法: evolclaw group <command> <from-aid> [args...] [options]
4029
4361
 
4030
4362
  消息:
@@ -4424,7 +4756,7 @@ export async function main(args) {
4424
4756
  }
4425
4757
  switch (cmd) {
4426
4758
  case 'init':
4427
- if (args[1] === 'help') {
4759
+ if (isHelpFlag(args[1])) {
4428
4760
  console.log(`用法: evolclaw init [渠道] [选项]
4429
4761
 
4430
4762
  仅初始化 defaults.json:
@@ -4500,7 +4832,7 @@ export async function main(args) {
4500
4832
  await cmdWatchAid();
4501
4833
  }
4502
4834
  else if (args[1] === 'msg') {
4503
- if (args[2] === '--help' || args[2] === '-h' || args[2] === 'help') {
4835
+ if (isHelpFlag(args[2])) {
4504
4836
  console.log(`用法: evolclaw watch msg
4505
4837
 
4506
4838
  三面板交互式消息监控 TUI。
@@ -4642,10 +4974,13 @@ Commands:
4642
4974
  aid list 列出本地所有 AID
4643
4975
  aid show <aid> 查看本地 AID 详情(证书有效期、私钥状态)
4644
4976
  aid new <aid> 创建新 AID 身份
4645
- aid delete <aid> 删除本地 AID
4646
4977
  aid lookup <aid> 远程探测 AID(是否存在 + 网关 + agent.md)
4647
4978
  aid agentmd put <aid> 签名并上传 agent.md
4648
4979
  aid agentmd get <aid> 下载并验签 agent.md
4980
+ aid delete <aid> 删除指定 AID
4981
+ aid delete --orphan 清理无私钥的外部 AID 缓存
4982
+ aid delete --unrecoverable 清理云端公钥已变更、不可恢复的 AID
4983
+ 默认 dry-run,加 --yes 执行
4649
4984
  net 网络链路诊断
4650
4985
  net check [<aid>] 10 步链路检测(DNS→Discovery→TCP→TLS→WSS→Auth→Ping→Echo)
4651
4986
  net help 查看详细帮助
@@ -8,9 +8,8 @@
8
8
  import fs from 'fs';
9
9
  import readline from 'readline';
10
10
  import path from 'path';
11
- import os from 'os';
12
11
  import crypto from 'crypto';
13
- import { aidLocalDir } from '../paths.js';
12
+ import { aidLocalDir, aunPath as defaultAunPath } from '../paths.js';
14
13
  import { selectInstance } from './init.js';
15
14
  import { npmInstallGlobal } from '../utils/npm-ops.js';
16
15
  import { loadAllAgents, loadAgent } from '../config-store.js';
@@ -442,7 +441,7 @@ export async function setupAunAid(rl, _config) {
442
441
  }
443
442
  }
444
443
  // Check if AID exists locally
445
- const aunPath = path.join(os.homedir(), '.aun');
444
+ const aunPath = defaultAunPath();
446
445
  const aidDir = path.join(aunPath, 'AIDs', aid);
447
446
  if (fs.existsSync(aidDir) && fs.existsSync(path.join(aidDir, 'private'))) {
448
447
  console.log(` ✓ AID ${aid} 已存在`);
@@ -2,6 +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
6
  const isWindows = process.platform === 'win32';
6
7
  const KNOWN_BASEAGENTS = ['cc', 'codex', 'gemini'];
7
8
  function statePath() {
@@ -196,7 +197,7 @@ function disconnect(ba) {
196
197
  }
197
198
  // ── 入口 ──
198
199
  export function cmdLinkRules(args) {
199
- if (args.includes('--help') || args.includes('-h')) {
200
+ if (wantsHelp(args)) {
200
201
  showHelp();
201
202
  return;
202
203
  }