agentix-cli 0.16.0 → 0.17.0

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 (131) hide show
  1. package/dist/agent-4AOMVXRJ.js +2 -0
  2. package/dist/board-dashboard-CXIVJKDP.js +7282 -0
  3. package/dist/board-dashboard-CXIVJKDP.js.map +1 -0
  4. package/dist/chunk-34NG7UDX.js +11 -0
  5. package/dist/chunk-34NG7UDX.js.map +1 -0
  6. package/dist/chunk-4HP6UC2X.js +13 -0
  7. package/dist/chunk-4HP6UC2X.js.map +1 -0
  8. package/dist/chunk-5WNGHDFC.js +164 -0
  9. package/dist/chunk-5WNGHDFC.js.map +1 -0
  10. package/dist/{chunk-7MBTMCKY.js → chunk-63UYRJIC.js} +2 -2
  11. package/dist/chunk-6PPIFDOQ.js +1742 -0
  12. package/dist/chunk-6PPIFDOQ.js.map +1 -0
  13. package/dist/chunk-ABCHHNG6.js +625 -0
  14. package/dist/chunk-ABCHHNG6.js.map +1 -0
  15. package/dist/chunk-AFRHKA7B.js +2 -0
  16. package/dist/chunk-AFRHKA7B.js.map +1 -0
  17. package/dist/chunk-CDNWCYF7.js +2 -0
  18. package/dist/chunk-CDNWCYF7.js.map +1 -0
  19. package/dist/{chunk-6MKAXEE5.js → chunk-EKTPCSN4.js} +2 -2
  20. package/dist/{chunk-6MKAXEE5.js.map → chunk-EKTPCSN4.js.map} +1 -1
  21. package/dist/chunk-EW27SCCC.js +2 -0
  22. package/dist/chunk-EW27SCCC.js.map +1 -0
  23. package/dist/chunk-HCFSHGK4.js +46 -0
  24. package/dist/chunk-HCFSHGK4.js.map +1 -0
  25. package/dist/chunk-HHJGKD5B.js +71 -0
  26. package/dist/chunk-HHJGKD5B.js.map +1 -0
  27. package/dist/{chunk-YI2T5QYB.js → chunk-MBEQASP3.js} +2 -2
  28. package/dist/chunk-N67JZAXU.js +2 -0
  29. package/dist/chunk-N67JZAXU.js.map +1 -0
  30. package/dist/{chunk-FLOGK2U7.js → chunk-SRHLXZRF.js} +2 -2
  31. package/dist/chunk-TT2Q7YMZ.js +118 -0
  32. package/dist/chunk-TT2Q7YMZ.js.map +1 -0
  33. package/dist/{chunk-HI24KAEY.js → chunk-WJXU5F4Z.js} +3 -4
  34. package/dist/{chunk-HI24KAEY.js.map → chunk-WJXU5F4Z.js.map} +1 -1
  35. package/dist/chunk-XDJSYQST.js +6 -0
  36. package/dist/chunk-XDJSYQST.js.map +1 -0
  37. package/dist/chunk-YPOELN6Z.js +798 -0
  38. package/dist/chunk-YPOELN6Z.js.map +1 -0
  39. package/dist/{chunk-QZP65I2H.js → chunk-ZE2YFX4K.js} +2 -2
  40. package/dist/cli.js +1 -320
  41. package/dist/cli.js.map +1 -1
  42. package/dist/{compaction-JBZZ7I7Y.js → compaction-X46EQEPS.js} +6 -6
  43. package/dist/compaction-X46EQEPS.js.map +1 -0
  44. package/dist/{config-LN6JEHMF.js → config-KCYHMKCT.js} +2 -2
  45. package/dist/config-mutator-QP4GLBOE.js +2 -0
  46. package/dist/context-planner-G2J7FNNU.js +17 -0
  47. package/dist/context-planner-G2J7FNNU.js.map +1 -0
  48. package/dist/editor-chat-R54E65W5.js +137 -0
  49. package/dist/editor-chat-R54E65W5.js.map +1 -0
  50. package/dist/heal-JDXUPRET.js +2 -0
  51. package/dist/inbox-L5ZZ623Y.js +315 -0
  52. package/dist/inbox-L5ZZ623Y.js.map +1 -0
  53. package/dist/index.d.ts +2917 -1781
  54. package/dist/index.js +23 -53
  55. package/dist/index.js.map +1 -1
  56. package/dist/ingest-whatsapp-NS4OWDOU.js +6 -0
  57. package/dist/ingest-whatsapp-NS4OWDOU.js.map +1 -0
  58. package/dist/memory-extract-WH4FZ456.js +2 -0
  59. package/dist/processes-XF5CV4LK.js +256 -0
  60. package/dist/processes-XF5CV4LK.js.map +1 -0
  61. package/dist/program-FFWEILAS.js +2 -0
  62. package/dist/providers-AKQ4HTB4.js +2 -0
  63. package/dist/registry-IFMDJWQ7.js +2 -0
  64. package/dist/scheduler-63D5KFO7.js +2 -0
  65. package/dist/slack-PSQLVTGB.js +3 -0
  66. package/dist/slack-PSQLVTGB.js.map +1 -0
  67. package/dist/store-5MJOO74U.js +2 -0
  68. package/dist/store-5MJOO74U.js.map +1 -0
  69. package/dist/task-store-NM7VKNLE.js +2 -0
  70. package/dist/task-store-NM7VKNLE.js.map +1 -0
  71. package/dist/telegram-W4SLFMNY.js +3 -0
  72. package/dist/telegram-W4SLFMNY.js.map +1 -0
  73. package/dist/timers-PCB4ZIBD.js +2 -0
  74. package/dist/timers-PCB4ZIBD.js.map +1 -0
  75. package/dist/types-C7IFH42Z.js +2 -0
  76. package/dist/types-C7IFH42Z.js.map +1 -0
  77. package/dist/usage-dashboard-AXPXB2KP.js +803 -0
  78. package/dist/usage-dashboard-AXPXB2KP.js.map +1 -0
  79. package/dist/web/workflow-editor.global.js +1427 -0
  80. package/dist/web/workflow-editor.global.js.map +1 -0
  81. package/dist/whatsapp-DCHRBRVV.js +3 -0
  82. package/dist/whatsapp-DCHRBRVV.js.map +1 -0
  83. package/dist/workspace-setup-C4VB3QZV.js +2 -0
  84. package/dist/workspace-setup-C4VB3QZV.js.map +1 -0
  85. package/package.json +9 -2
  86. package/dist/agent-BCDPSPKU.js +0 -2
  87. package/dist/board-dashboard-LA7WW56M.js +0 -5668
  88. package/dist/board-dashboard-LA7WW56M.js.map +0 -1
  89. package/dist/chunk-6U2XOQHU.js +0 -7
  90. package/dist/chunk-6U2XOQHU.js.map +0 -1
  91. package/dist/chunk-7FJQGD6U.js +0 -6
  92. package/dist/chunk-7FJQGD6U.js.map +0 -1
  93. package/dist/chunk-ABDP7TDL.js +0 -71
  94. package/dist/chunk-ABDP7TDL.js.map +0 -1
  95. package/dist/chunk-CXY2RYNI.js +0 -11
  96. package/dist/chunk-CXY2RYNI.js.map +0 -1
  97. package/dist/chunk-HVC6FJNP.js +0 -108
  98. package/dist/chunk-HVC6FJNP.js.map +0 -1
  99. package/dist/chunk-K7BE4E35.js +0 -138
  100. package/dist/chunk-K7BE4E35.js.map +0 -1
  101. package/dist/chunk-KXJSLLSQ.js +0 -199
  102. package/dist/chunk-KXJSLLSQ.js.map +0 -1
  103. package/dist/chunk-KXZMYLHQ.js +0 -46
  104. package/dist/chunk-KXZMYLHQ.js.map +0 -1
  105. package/dist/chunk-M7HKBG3V.js +0 -2
  106. package/dist/chunk-M7HKBG3V.js.map +0 -1
  107. package/dist/chunk-RB7265LO.js +0 -12
  108. package/dist/chunk-RB7265LO.js.map +0 -1
  109. package/dist/compaction-JBZZ7I7Y.js.map +0 -1
  110. package/dist/config-mutator-24TH7PHM.js +0 -2
  111. package/dist/heal-WKY5I4KB.js +0 -2
  112. package/dist/memory-extract-AD7KMRPC.js +0 -2
  113. package/dist/providers-VUIWO3ZK.js +0 -2
  114. package/dist/registry-ASC2QS5K.js +0 -2
  115. package/dist/scheduler-A3TQOCKT.js +0 -2
  116. package/dist/usage-dashboard-QX3CF6KK.js +0 -404
  117. package/dist/usage-dashboard-QX3CF6KK.js.map +0 -1
  118. package/dist/workspace-setup-ZWTT42XT.js +0 -2
  119. /package/dist/{agent-BCDPSPKU.js.map → agent-4AOMVXRJ.js.map} +0 -0
  120. /package/dist/{chunk-7MBTMCKY.js.map → chunk-63UYRJIC.js.map} +0 -0
  121. /package/dist/{chunk-YI2T5QYB.js.map → chunk-MBEQASP3.js.map} +0 -0
  122. /package/dist/{chunk-FLOGK2U7.js.map → chunk-SRHLXZRF.js.map} +0 -0
  123. /package/dist/{chunk-QZP65I2H.js.map → chunk-ZE2YFX4K.js.map} +0 -0
  124. /package/dist/{config-LN6JEHMF.js.map → config-KCYHMKCT.js.map} +0 -0
  125. /package/dist/{config-mutator-24TH7PHM.js.map → config-mutator-QP4GLBOE.js.map} +0 -0
  126. /package/dist/{heal-WKY5I4KB.js.map → heal-JDXUPRET.js.map} +0 -0
  127. /package/dist/{memory-extract-AD7KMRPC.js.map → memory-extract-WH4FZ456.js.map} +0 -0
  128. /package/dist/{providers-VUIWO3ZK.js.map → program-FFWEILAS.js.map} +0 -0
  129. /package/dist/{registry-ASC2QS5K.js.map → providers-AKQ4HTB4.js.map} +0 -0
  130. /package/dist/{scheduler-A3TQOCKT.js.map → registry-IFMDJWQ7.js.map} +0 -0
  131. /package/dist/{workspace-setup-ZWTT42XT.js.map → scheduler-63D5KFO7.js.map} +0 -0
package/dist/cli.js CHANGED
@@ -1,322 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import{a as Et,b as Dt,e as Je}from"./chunk-KXJSLLSQ.js";import{f as it,g as Pt}from"./chunk-K7BE4E35.js";import"./chunk-FLOGK2U7.js";import"./chunk-QZP65I2H.js";import"./chunk-6U2XOQHU.js";import"./chunk-ABDP7TDL.js";import"./chunk-HI24KAEY.js";import"./chunk-CXY2RYNI.js";import"./chunk-5F3J55LG.js";import{b as Oe}from"./chunk-LEIYC3BF.js";import{b as jt,d as It}from"./chunk-WZEYII2S.js";import"./chunk-64P4QHSZ.js";import"./chunk-BFZUG66X.js";import"./chunk-RB7265LO.js";import{a as K,b as _,c as fe,d as Rt}from"./chunk-YI2T5QYB.js";import{e as Tt,f as U,g as Fe,i as me}from"./chunk-7FJQGD6U.js";import"./chunk-M7HKBG3V.js";import"./chunk-DSYKYZMT.js";import"./chunk-7MBTMCKY.js";import"./chunk-QOYAX2FT.js";import{a as He}from"./chunk-FIOB3MEX.js";import{Command as Jo}from"commander";import{Command as wn}from"commander";me();import S from"chalk";var ce=new wn().name("daemon").description("manage the agentx daemon \u2014 start, stop, status, logs");ce.command("start").description("start the daemon (default if no subcommand)").option("-c, --config <path>","path to agentx.json").option("-d, --detach","run in background (detached)").option("--port <port>","override bind port").action(async e=>{if(e.detach){let{spawn:d}=await import("child_process"),m=["dist/cli.js","daemon","start"];e.config&&m.push("-c",e.config),e.port&&m.push("--port",e.port);let g=d("node",m,{detached:!0,stdio:["ignore","ignore","ignore"],cwd:process.cwd()});g.unref();let{writeFileSync:p}=await import("fs");p("/tmp/agentx-daemon.pid",String(g.pid)),console.log(S.green(`Daemon started in background (PID: ${g.pid})`)),console.log(S.dim(" Logs: tail -f /tmp/agentx-daemon.log")),console.log(S.dim(" Stop: agentx daemon stop"));return}let{readFileSync:t,writeFileSync:n,mkdirSync:o,existsSync:s}=await import("fs"),{resolve:i}=await import("path"),r=i(process.cwd(),".agentx");s(r)||o(r,{recursive:!0});let c=i(r,"daemon.pid");if(s(c)){let d=parseInt(t(c,"utf-8").trim(),10);if(d&&d!==process.pid)try{process.kill(d,0),console.error(`Another agentx daemon is already running (PID ${d}). Exiting.`),process.exit(0)}catch{}}n(c,String(process.pid)),await new it(e.config).start()});ce.command("stop").description("stop the running daemon").action(async()=>{let{readFileSync:e,existsSync:t,unlinkSync:n}=await import("fs"),{resolve:o}=await import("path"),s=[o(process.cwd(),".agentx/daemon.pid"),"/tmp/agentx-daemon.pid"];for(let r of s)if(t(r))try{let c=parseInt(e(r,"utf-8").trim(),10);process.kill(c,"SIGTERM"),await new Promise(a=>setTimeout(a,2e3));try{n(r)}catch{}console.log(S.green(`Daemon stopped (PID: ${c})`));return}catch{try{n(r)}catch{}}let{execSync:i}=await import("child_process");try{i("pkill -f 'node dist/cli.js daemon'",{stdio:"ignore"}),console.log(S.green("Daemon stopped"))}catch{console.log(S.yellow("No daemon process found"))}});ce.command("status").alias("st").description("show daemon status, agents, crons, and mesh").option("-c, --config <path>","path to agentx.json").option("--json","output as JSON").action(async e=>{try{let t=U(e.config),[n,o]=t.node.bind.split(":"),s=`http://${n||"127.0.0.1"}:${o}/health`,r=await(await fetch(s,{signal:AbortSignal.timeout(3e3)})).json();if(e.json){console.log(JSON.stringify(r,null,2));return}console.log(),console.log(S.bold(` ${r.node.name}`)+S.dim(` (${r.node.id})`)),console.log(S.green(" Status: running")+S.dim(` \u2014 uptime ${$n(r.uptime)}`)),console.log(),console.log(S.bold(" Agents:"));for(let a of r.agents){let d=a.active>0?S.yellow(` [${a.active} active]`):"",m=a.errors>0?S.red(` (${a.errors} errors)`):"";console.log(` ${S.cyan(a.name)} ${S.dim(`(${a.tier})`)}${d}${m}`),console.log(S.dim(` ${a.workspace}`))}let c=r.crons.filter(a=>a.enabled);if(r.crons.length){console.log(),console.log(S.bold(` Crons: ${c.length} enabled / ${r.crons.length} total`));for(let a of r.crons){let d=a.enabled?S.green("\u25CF"):S.dim("\u25CB"),m=a.nextRun?S.dim(` next: ${new Date(a.nextRun).toLocaleTimeString()}`):"";console.log(` ${d} ${a.id}${m}`)}}if(r.mesh?.length){console.log(),console.log(S.bold(" Mesh:"));for(let a of r.mesh){let d=a.healthy?S.green("\u25CF"):S.red("\u25CF"),m=a.skills?.length?S.dim(` (${a.skills.length} agents)`):"";console.log(` ${d} ${a.peer} ${S.dim(a.peerUrl)}${m}`)}}console.log()}catch(t){if(t.cause?.code==="ECONNREFUSED"||t.name==="TimeoutError")console.log(S.red(" Daemon is not running")),console.log(S.dim(" Start with: agentx daemon start"));else try{let n=U(e.config),o=Fe(n);if(console.log(),console.log(S.bold(` ${n.node.name}`)+S.dim(` (${n.node.id})`)),console.log(S.yellow(" Status: stopped")),console.log(S.dim(` ${Object.keys(n.agents).length} agents, ${Object.keys(n.crons).length} crons configured`)),o.length){console.log();for(let s of o)console.log(S.yellow(` \u26A0 ${s}`))}console.log()}catch(n){console.log(S.red(` ${n.message}`))}}});ce.command("logs").description("tail daemon logs").option("-n, --lines <n>","number of lines","50").option("-f, --follow","follow log output",!1).action(async e=>{let{execSync:t,spawn:n}=await import("child_process"),o="/tmp/agentx-daemon.log",{existsSync:s}=await import("fs");if(!s(o)){console.log(S.yellow("No log file found at /tmp/agentx-daemon.log"));return}if(e.follow){let i=n("tail",["-f","-n",e.lines,o],{stdio:"inherit"});process.on("SIGINT",()=>{i.kill(),process.exit(0)})}else t(`tail -n ${e.lines} ${o}`,{stdio:"inherit"})});ce.command("watch").description("stream live agent activity (SSE)").option("-c, --config <path>","path to agentx.json").action(async e=>{let o=`http://${(U(e.config).node.bind||"127.0.0.1:18800").replace("0.0.0.0","127.0.0.1")}/events`;console.log(S.dim(` Connecting to ${o}...`)),console.log(S.dim(` Press Ctrl+C to stop
3
- `));try{let s=await fetch(o);if(!s.ok||!s.body){console.log(S.red(` Failed: ${s.status}`));return}let i=s.body.getReader(),r=new TextDecoder,c="";for(;;){let{done:a,value:d}=await i.read();if(a)break;c+=r.decode(d,{stream:!0});let m=c.split(`
4
- `);c=m.pop()||"";for(let g of m)if(g.startsWith("data: "))try{let p=JSON.parse(g.slice(6)),u=new Date(p.time).toLocaleTimeString(),h=p.message?.replace(/^\[agentx\]\s*/,"")||"";h.includes("executing")?console.log(`${S.dim(u)} ${S.cyan("\u25B6")} ${h}`):h.includes("completed")?console.log(`${S.dim(u)} ${S.green("\u2713")} ${h}`):h.includes("error")||h.includes("Error")?console.log(`${S.dim(u)} ${S.red("\u2717")} ${h}`):h.includes("Routing")?console.log(`${S.dim(u)} ${S.yellow("\u2192")} ${h}`):h.includes("mention")?console.log(`${S.dim(u)} ${S.magenta("@")} ${h}`):h.includes("Skipping")?console.log(`${S.dim(u)} ${S.dim("\u2298")} ${S.dim(h)}`):console.log(`${S.dim(u)} ${h}`)}catch{}else g.startsWith("event: status")}}catch(s){console.log(S.red(` Connection failed: ${s.message}`)),console.log(S.dim(" Is the daemon running? Try: agentx daemon status"))}});ce.command("send <agent> <message...>").description("send a task to an agent via the daemon API").option("-c, --config <path>","path to agentx.json").option("--peer <peer>","send to a mesh peer's agent").option("--json","output raw JSON response").action(async(e,t,n)=>{let o=t.join(" ");try{let s=U(n.config),[i,r]=s.node.bind.split(":"),c=`http://${i||"127.0.0.1"}:${r}`,a,d;n.peer?(a=`${c}/mesh/task`,d={peer:n.peer,message:o,agent:e}):(a=`${c}/task`,d={agent:e,message:o}),console.log(S.dim(` Sending to ${n.peer?`${n.peer}/${e}`:e}...`));let g=await(await fetch(a,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(d)})).json();n.json?console.log(JSON.stringify(g,null,2)):g.error?console.log(S.red(` Error: ${g.error}`)):(console.log(),console.log(g.content||g.response||"No response"),g.duration&&console.log(S.dim(`
5
- (${(g.duration/1e3).toFixed(1)}s)`)))}catch(s){console.log(S.red(` Failed: ${s.message}`)),console.log(S.dim(" Is the daemon running? Try: agentx daemon status"))}});ce.command("deploy <host>").description("deploy agentx to a remote server via rsync").option("-i, --identity <key>","SSH identity file").option("-u, --user <user>","SSH user","root").option("-p, --path <path>","remote agentx path","~/agentx").option("--restart","restart remote daemon after deploy",!1).option("--skip-checks","skip build and test before deploy",!1).action(async(e,t)=>{let{execSync:n}=await import("child_process"),o=t.identity?`-e "ssh -i ${t.identity}"`:"",s=`${t.user}@${e}:${t.path}/dist/`;if(!t.skipChecks){try{console.log(S.dim(" Running tests...")),n("npx vitest run --reporter=dot",{stdio:"pipe",cwd:process.cwd()}),console.log(S.green(" \u2713 Tests passed"))}catch{console.log(S.red(" \u2717 Tests failed \u2014 aborting deploy")),console.log(S.dim(" Use --skip-checks to deploy anyway"));return}let{existsSync:i,statSync:r}=await import("fs"),{resolve:c}=await import("path"),a=c(process.cwd(),"dist/cli.js");if(!i(a)){console.log(S.red(" \u2717 dist/ not found \u2014 run build first"));return}Date.now()-r(a).mtimeMs>3e5&&(console.log(S.yellow(" \u26A0 dist/ is older than 5 minutes \u2014 rebuilding...")),n("npx tsup --no-dts",{stdio:"pipe",cwd:process.cwd()}),console.log(S.green(" \u2713 Build complete")))}console.log(S.dim(` Deploying to ${t.user}@${e}:${t.path}...`));try{if(n(`rsync -avzL --delete ${o} dist/ ${s}`,{stdio:"inherit",cwd:process.cwd()}),console.log(S.green(" \u2713 Deploy complete")),t.restart){console.log(S.dim(" Restarting remote daemon..."));let i=t.identity?`ssh -i ${t.identity}`:"ssh";n(`${i} ${t.user}@${e} "pkill -f 'node dist/cli.js daemon' 2>/dev/null; sleep 2; cd ${t.path} && nohup node dist/cli.js daemon start > /tmp/agentx-daemon.log 2>&1 &"`,{stdio:"inherit"}),console.log(S.green(" Remote daemon restarted"))}}catch(i){console.log(S.red(` Deploy failed: ${i.message}`))}});ce.action(async e=>{await new it(e.config).start()});function $n(e){return e<60?`${Math.round(e)}s`:e<3600?`${Math.round(e/60)}m`:e<86400?`${Math.round(e/3600)}h ${Math.round(e%3600/60)}m`:`${Math.round(e/86400)}d ${Math.round(e%86400/3600)}h`}import{Command as bn}from"commander";import re from"chalk";import{existsSync as Ne,readFileSync as kn,writeFileSync as Ot,mkdirSync as Nt}from"fs";import{resolve as Ie}from"path";var Wt=new bn().name("init").description("initialize agentx configuration in the current directory").option("--force","overwrite existing config").action(async e=>{let t=process.cwd(),n=Ie(t,"agentx.json"),o=Ie(t,".env"),s=Ie(t,".agentx/sessions");if(console.log(),console.log(re.bold(" agentx init")),console.log(),Ne(n)&&!e.force){console.log(re.yellow(" agentx.json already exists. Use --force to overwrite.")),console.log();return}let i=[Ie(t,"agentx.example.json"),Ie(t,"node_modules/agentix-cli/agentx.example.json")],r;for(let a of i)if(Ne(a)){r=kn(a,"utf-8");break}r||(r=JSON.stringify({node:{id:He("os").hostname().toLowerCase().replace(/[^a-z0-9-]/g,"-"),name:He("os").hostname(),bind:"127.0.0.1:18800"},providers:{claude:{apiKey:"${ANTHROPIC_API_KEY}"}},agents:{assistant:{name:"Assistant",workspace:t,tier:"claude-code",model:"claude-sonnet-4-6",mentions:["@assistant","assistant"],maxConcurrent:2,systemPrompt:"You are a helpful assistant.",permissionMode:"default"}},channels:{telegram:{enabled:!1,accounts:{},policy:{dm:"pair",group:"mention-required"}},whatsapp:{enabled:!1}},crons:{},mesh:{enabled:!1,peers:[],discovery:"static",healthCheck:{interval:60,timeout:10}}},null,2)),Ot(n,r),console.log(re.green(" \u2713 Created agentx.json")),Ne(o)||(Ot(o,["# AgentX environment variables","# ANTHROPIC_API_KEY=sk-ant-...","# TG_BOT_TOKEN=123456:ABC...","# MESH_TOKEN=your-mesh-secret",""].join(`
6
- `)),console.log(re.green(" \u2713 Created .env template"))),Ne(s)||(Nt(s,{recursive:!0}),console.log(re.green(" \u2713 Created .agentx/sessions/")));let c=Ie(t,".claude");Ne(c)||(Nt(c,{recursive:!0}),console.log(re.green(" \u2713 Created .claude/"))),console.log(),console.log(re.bold(" Next steps:")),console.log(` 1. Edit ${re.cyan("agentx.json")} \u2014 configure agents, channels, crons`),console.log(` 2. Edit ${re.cyan(".env")} \u2014 add API keys and bot tokens`),console.log(` 3. Run ${re.cyan("agentx daemon start")} \u2014 start the daemon`),console.log(` 4. Run ${re.cyan("agentx daemon status")} \u2014 verify everything`),console.log()});import{Command as vn}from"commander";import We from"chalk";import{spawn as Lt}from"child_process";import{existsSync as xn}from"fs";import{resolve as Sn}from"path";me();var Ut=new vn().name("setup").description("open the web setup wizard to create or extend your AgentX install").option("--port <n>","dashboard port (default: from agentx.json, else 4202)").option("--no-open","don't try to open the browser").action(async e=>{let t=Dt(),n=e.port?parseInt(e.port,10):An(),o=`http://127.0.0.1:${n}/setup`;if(console.log(),t.configExists?console.log(We.dim(" Found agentx.json \u2014 the wizard will extend your current install.")):console.log(We.dim(" No agentx.json yet \u2014 the wizard will create one.")),console.log(We.bold(` Opening ${o}`)),console.log(),await jn(o).catch(()=>!1))console.log(We.dim(" Dashboard is already running.")),e.open!==!1&&Mt(o);else{console.log(We.dim(` Starting dashboard server on port ${n} (Ctrl-C to quit)\u2026`));let i=Cn(),r=Lt(process.execPath,[i,"board","serve","--port",String(n)],{stdio:"inherit",detached:!1});await new Promise(c=>setTimeout(c,1200)),e.open!==!1&&Mt(o),await new Promise(c=>r.on("exit",()=>c()))}});function An(){try{return U().dashboard.port||4202}catch{return 4202}}function Cn(){let e=Sn(__dirname,"cli.js");return xn(e)?e:process.argv[1]||e}async function jn(e){try{let t=await fetch(e,{method:"GET"});return t.ok||t.status===302}catch{return!1}}function Mt(e){let t=process.platform==="darwin"?"open":process.platform==="win32"?"start":"xdg-open";try{Lt(t,[e],{stdio:"ignore",detached:!0}).unref()}catch{}}import{Command as In}from"commander";import B from"chalk";var Me=new In().name("token").description("scoped API tokens for external access (mesh peers, integrations)");Me.command("create").description("mint a new token \u2014 prints the secret once, store it somewhere safe").requiredOption("--name <name>","human-readable label (e.g. 'Slack integration')").option("--scope <scopes>","comma-separated scopes (default: dashboard:read)","dashboard:read").option("--expires <days>","expire after N days (default: no expiry)").action(e=>{try{let t=new Je,n=String(e.scope).split(",").map(r=>r.trim()).filter(Boolean),o=e.expires?parseInt(e.expires,10):void 0;if(e.expires&&(!o||o<1))throw new Error("--expires must be a positive integer (days)");let{token:s,record:i}=t.create({name:e.name,scopes:n,expiresInDays:o});console.log(),console.log(B.bold(" Token created.")),console.log(),console.log(` id: ${B.cyan(i.id)}`),console.log(` name: ${i.name}`),console.log(` scopes: ${i.scopes.join(", ")}`),console.log(` expires: ${i.expiresAt||B.dim("never")}`),console.log(),console.log(B.yellow(" \u26A0 This is the only time the full secret will be shown.")),console.log(),console.log(` ${B.green.bold(s)}`),console.log(),console.log(B.dim(" Use it via the Authorization header:")),console.log(B.dim(` curl -H "Authorization: Bearer ${s.slice(0,16)}..." ...`)),console.log()}catch(t){console.log(B.red(` ${t.message}`)),process.exit(1)}});Me.command("list").alias("ls").description("show all issued tokens (secrets not included)").action(()=>{try{let e=new Je().list();if(e.length===0){console.log(B.dim(`
7
- No tokens issued.
8
- `));return}console.log();for(let t of e){let n=t.revokedAt?B.red("revoked"):t.expiresAt&&Date.parse(t.expiresAt)<Date.now()?B.red("expired"):B.green("active");console.log(` ${B.cyan(t.id)} ${n} ${t.name}`),console.log(B.dim(` prefix: ${t.prefix}\u2026 scopes: ${t.scopes.join(", ")}`)),console.log(B.dim(` created: ${t.createdAt}${t.expiresAt?` expires: ${t.expiresAt}`:""}${t.lastUsedAt?` last used: ${t.lastUsedAt}`:""}`))}console.log()}catch(e){console.log(B.red(` ${e.message}`)),process.exit(1)}});Me.command("revoke <id>").description("immediately invalidate a token").action(e=>{try{let t=new Je().revoke(e);t||(console.log(B.red(` Token ${e} not found.`)),process.exit(1)),console.log(B.green(`
9
- \u2713 Revoked ${e} (${t.name})
10
- `))}catch(t){console.log(B.red(` ${t.message}`)),process.exit(1)}});me();import{Command as Tn}from"commander";import te from"chalk";import{execFileSync as at}from"child_process";import{existsSync as _t,readFileSync as zt,statSync as Rn}from"fs";import{resolve as lt}from"path";var Bt=new Tn().name("doctor").description("pre-flight health check: Node, config, credentials, daemon").option("--no-running","skip the live daemon probe").option("--json","emit machine-readable JSON (stable shape for CI)").action(async e=>{let t=[];await Pn(t);let n=En(t);n&&Dn(t,n),n&&On(t,n),e.running!==!1&&n&&await Nn(t,n),e.json?process.stdout.write(JSON.stringify({checks:t,summary:ct(t)},null,2)+`
11
- `):Wn(t);let{errors:o}=ct(t);process.exit(o>0?1:0)});async function Pn(e){let t=Number(process.versions.node.split(".")[0]);e.push({severity:t>=20?"ok":"fail",group:"Environment",title:`Node.js ${process.versions.node}`,detail:t>=20?void 0:"AgentX needs Node 20 or newer.",fix:t>=20?void 0:"Install a newer Node (nvm install 20 && nvm use 20)"});let n=i=>{try{return at(process.platform==="win32"?"where":"which",[i],{encoding:"utf-8"}).trim().split(`
12
- `)[0]}catch{return null}},o=n("npm");e.push({severity:o?"ok":"fail",group:"Environment",title:o?"npm on PATH":"npm not found",detail:o||void 0});let s=n("claude");if(s){let i;try{i=at("claude",["--version"],{encoding:"utf-8",timeout:3e3}).trim()}catch{}e.push({severity:"ok",group:"Environment",title:`claude CLI ${i||"installed"}`,detail:s})}else e.push({severity:"warn",group:"Environment",title:"claude CLI not on PATH",detail:"Only required for agents on the claude-code tier.",fix:"See https://docs.anthropic.com/en/docs/claude-code"})}function En(e){let t=lt(process.cwd(),"agentx.json");if(!_t(t))return e.push({severity:"fail",group:"Config",title:"agentx.json not found",detail:`Looked in ${t}`,fix:"Run `agentx setup` to create one via the web wizard."}),null;let n;try{n=JSON.parse(zt(t,"utf-8"))}catch(s){return e.push({severity:"fail",group:"Config",title:"agentx.json is not valid JSON",detail:s.message}),null}try{U(t),e.push({severity:"ok",group:"Config",title:`agentx.json valid (${Object.keys(n.agents||{}).length} agents, ${Object.keys(n.crons||{}).length} schedules)`})}catch(s){e.push({severity:"fail",group:"Config",title:"agentx.json failed schema validation",detail:s.message,fix:"Open /admin \u2192 Advanced tab to edit and re-save."})}let o=Object.entries(n.agents||{}).filter(([,s])=>s.tier==="claude-code");return o.length>0&&((()=>{try{return at(process.platform==="win32"?"where":"which",["claude"],{stdio:"ignore"}),!0}catch{return!1}})()||e.push({severity:"fail",group:"Config",title:`${o.length} agent(s) use claude-code tier but claude CLI is missing`,detail:o.map(([i])=>i).join(", "),fix:"Install Claude Code (https://docs.anthropic.com/en/docs/claude-code) or switch these agents to tier=sdk."})),n}function Dn(e,t){let n=new Set;if(rt(t,n),n.size===0){e.push({severity:"ok",group:"Secrets",title:"No ${VAR} references in agentx.json"});return}let o=lt(process.cwd(),".env"),s={};if(_t(o))for(let c of zt(o,"utf-8").split(/\r?\n/)){let a=c.match(/^\s*([A-Z_][A-Z0-9_]*)\s*=\s*(.*)$/i);a&&(s[a[1]]=a[2])}let i=[],r=[];for(let c of n){let a=process.env[c]??s[c];a===void 0?i.push(c):(!a.trim()||/^["']?\s*["']?$/.test(a))&&r.push(c)}i.length===0&&r.length===0?e.push({severity:"ok",group:"Secrets",title:`${n.size} env reference(s) resolved`}):(i.length>0&&e.push({severity:"fail",group:"Secrets",title:`${i.length} env var(s) referenced in agentx.json but missing`,detail:i.join(", "),fix:`Add to .env \u2014 e.g. ${i[0]}=<value>`}),r.length>0&&e.push({severity:"warn",group:"Secrets",title:`${r.length} env var(s) set but empty`,detail:r.join(", ")}))}function rt(e,t){if(typeof e=="string")for(let n of e.matchAll(/\$\{([A-Z_][A-Z0-9_]*)\}/g))t.add(n[1]);else if(Array.isArray(e))for(let n of e)rt(n,t);else if(e&&typeof e=="object")for(let n of Object.values(e))rt(n,t)}function On(e,t){let n=[];for(let[o,s]of Object.entries(t.agents||{})){if(!s.workspace)continue;let i=s.workspace.startsWith("/")?s.workspace:lt(process.cwd(),s.workspace);try{Rn(i).isDirectory()||n.push(`${o}: ${i} (exists but not a directory)`)}catch{n.push(`${o}: ${i}`)}}n.length===0?e.push({severity:"ok",group:"Workspaces",title:"All agent workspace folders exist"}):e.push({severity:"warn",group:"Workspaces",title:`${n.length} agent workspace(s) missing`,detail:n.join(`
13
- `),fix:"The daemon will create missing folders on first task, but you won't see CLAUDE.md / skills until you populate them."})}async function Nn(e,t){let n=t.dashboard?.daemonUrl?.replace(/\/+$/,"")||"http://127.0.0.1:18800",o={};t.dashboard?.token&&(o.Authorization=`Bearer ${t.dashboard.token}`);try{let s=new AbortController,i=setTimeout(()=>s.abort(),2e3),r=await fetch(n+"/health",{headers:o,signal:s.signal});if(clearTimeout(i),!r.ok){e.push({severity:"warn",group:"Runtime",title:`Daemon responded ${r.status}`,detail:n});return}let c=await r.json();e.push({severity:"ok",group:"Runtime",title:`Daemon healthy \u2014 ${c.agents?.length??0} agents live, uptime ${Math.round((c.uptime??0)/60)}m`,detail:n})}catch{e.push({severity:"warn",group:"Runtime",title:"Daemon not reachable (not running?)",detail:n,fix:"Run `agentx daemon start`. Skip this check with `agentx doctor --no-running`."})}}function ct(e){return{errors:e.filter(t=>t.severity==="fail").length,warnings:e.filter(t=>t.severity==="warn").length,ok:e.filter(t=>t.severity==="ok").length}}function Wn(e){console.log(),console.log(te.bold(" agentx doctor")),console.log();let t=new Map;for(let o of e)t.has(o.group)||t.set(o.group,[]),t.get(o.group).push(o);for(let[o,s]of t){console.log(te.bold(" "+o));for(let i of s){let r=i.severity==="ok"?te.green("\u2713"):i.severity==="warn"?te.yellow("!"):te.red("\u2717");console.log(` ${r} ${i.title}`),i.detail&&console.log(te.dim(" "+i.detail.replace(/\n/g,`
14
- `))),i.fix&&console.log(te.dim(" \u2192 "+i.fix))}console.log()}let n=ct(e);n.errors>0?console.log(te.red.bold(` ${n.errors} error${n.errors===1?"":"s"}`)+(n.warnings?te.yellow(`, ${n.warnings} warning${n.warnings===1?"":"s"}`):"")+`. Fix the error${n.errors===1?"":"s"} and rerun.`):n.warnings>0?console.log(te.yellow.bold(` ${n.warnings} warning${n.warnings===1?"":"s"}.`)+te.dim(" Review but not blocking.")):console.log(te.green.bold(` All checks passed (${n.ok}).`)),console.log()}me();import{Command as ue}from"commander";import f from"chalk";import ne from"prompts";import{readFileSync as Te,writeFileSync as dt,existsSync as H,mkdirSync as ye,cpSync as gt,readdirSync as qt}from"fs";import{resolve as W,join as Ge}from"path";function Y(){let e=W(process.cwd(),"agentx.json");if(!H(e))throw new Error("No agentx.json found. Run: agentx init");return JSON.parse(Te(e,"utf-8"))}function oe(e){return K(t=>{for(let n of Object.keys(t))delete t[n];Object.assign(t,e)}).then(t=>{if(!t.success){console.error(f.red(` Config save failed: ${t.error}`));return}t.reloaded?console.error(f.dim(" (daemon hot-reloaded)")):t.reloadSkipped&&!/ECONNREFUSED|unreachable|fetch failed/i.test(t.reloadSkipped)&&console.error(f.dim(` (daemon reload skipped: ${t.reloadSkipped})`))})}var Le=new ue().name("agent").description("manage agents \u2014 add, list, remove");Le.command("list").alias("ls").description("list configured agents").action(()=>{let e=Y();console.log();for(let[t,n]of Object.entries(e.agents||{})){let o=n.mentions?.join(", ")||"";console.log(` ${f.cyan(t)} \u2014 ${n.name} (${n.tier})`),console.log(f.dim(` workspace: ${n.workspace}`)),o&&console.log(f.dim(` mentions: ${o}`))}console.log()});Le.command("add").description("add a new agent interactively").action(async()=>{let e=Y(),t=await ne([{type:"text",name:"id",message:"Agent ID (kebab-case)",validate:d=>/^[a-z0-9-]+$/.test(d)||"Use lowercase, numbers, hyphens"},{type:"text",name:"name",message:"Display name"},{type:"text",name:"workspace",message:"Workspace path",initial:W(process.cwd(),"agents/")},{type:"select",name:"tier",message:"Execution tier",choices:[{title:"claude-code (subscription, full features)",value:"claude-code"},{title:"sdk (API key, programmatic)",value:"sdk"},{title:"orchestrator (any provider)",value:"orchestrator"}]},{type:"text",name:"model",message:"Model",initial:"claude-sonnet-4-6"},{type:"list",name:"mentions",message:"Mention patterns (comma-separated)",separator:","},{type:"number",name:"maxConcurrent",message:"Max concurrent tasks",initial:2},{type:"text",name:"systemPrompt",message:"System prompt"}]);if(!t.id)return;let n=t.workspace.endsWith(t.id)?t.workspace:Ge(t.workspace,t.id);ye(W(n,".claude/skills"),{recursive:!0});let o={name:t.name,workspace:n,tier:t.tier,model:t.model,mentions:t.mentions?.map(d=>d.trim()).filter(Boolean)||[],maxConcurrent:t.maxConcurrent,systemPrompt:t.systemPrompt,permissionMode:"default"},{setupWorkspace:s}=await import("./workspace-setup-ZWTT42XT.js"),i=s(t.id,o,"19900",(...d)=>{console.log(f.dim(` ${d.join(" ")}`))}),r=W(process.cwd(),"src/wiki/SKILL.md"),c=W(__dirname,"../wiki/SKILL.md"),a=H(r)?r:c;H(a)&&(ye(W(n,".claude/skills/wiki"),{recursive:!0}),gt(a,W(n,".claude/skills/wiki/SKILL.md"))),e.agents=e.agents||{},e.agents[t.id]=o,await oe(e),console.log(f.green(`
15
- Agent "${t.id}" added`)),console.log(f.dim(` Workspace: ${n}`)),console.log(f.dim(` Created: ${i.created.length} files (CLAUDE.md, AGENTS.md, settings, rules)`)),console.log()});Le.command("remove <id>").alias("rm").description("remove an agent from config (keeps workspace)").action(async e=>{let t=Y();if(!t.agents?.[e]){console.log(f.red(` Agent "${e}" not found`));return}delete t.agents[e],await oe(t),console.log(f.green(` Agent "${e}" removed from config (workspace preserved)`))});var Ke=new ue().name("channel").description("manage channels \u2014 add telegram/whatsapp/discord, list");Ke.command("list").alias("ls").description("list configured channels and bindings").action(()=>{let e=Y();console.log();let t=e.channels?.telegram;if(t?.enabled){console.log(f.bold(" Telegram:"));for(let[i,r]of Object.entries(t.accounts||{}))console.log(` ${f.cyan(i)} -> agent: ${r.agentBinding}`)}else console.log(f.dim(" Telegram: disabled"));let n=e.channels?.whatsapp;if(n?.enabled){console.log(f.bold(" WhatsApp:")),console.log(` default agent: ${n.defaultAgent||"(none)"}`),console.log(` session: ${n.sessionDir||".agentx/whatsapp-sessions"}`);for(let i of n.routes||[]){let r=i.contact?`contact ${i.contact}`:`group "${i.group}"`;console.log(` ${r} -> agent: ${i.agent}`)}}else console.log(f.dim(" WhatsApp: disabled"));let o=e.channels?.discord;o?.enabled?(console.log(f.bold(" Discord:")),console.log(` agent: ${o.agentBinding||"(none)"}`)):console.log(f.dim(" Discord: disabled"));let s=e.channels?.gitlab;if(s?.enabled){console.log(f.bold(" GitLab:")),console.log(` host: ${s.host}`),console.log(` webhook: :${s.webhookPort}`);for(let i of s.routes||[])console.log(` ${i.project} -> agent: ${i.agent}`)}else console.log(f.dim(" GitLab: disabled"));console.log()});Ke.command("add").description("add a channel (telegram, whatsapp, or discord)").action(async()=>{let e=Y(),t=Object.keys(e.agents||{});if(!t.length){console.log(f.red(" No agents configured. Run: agentx agent add"));return}let n=t.map(s=>({title:`${s} (${e.agents[s].name})`,value:s})),{channelType:o}=await ne({type:"select",name:"channelType",message:"Channel type",choices:[{title:"Telegram \u2014 bot via BotFather token",value:"telegram"},{title:"WhatsApp \u2014 link via QR code (self-chat or contacts)",value:"whatsapp"},{title:"Discord \u2014 bot via Discord developer portal",value:"discord"},{title:"GitLab \u2014 webhook for issues, MRs, comments, pipelines",value:"gitlab"}]});if(o){if(e.channels=e.channels||{},o==="telegram"){let s=await ne([{type:"text",name:"accountName",message:"Account name (e.g. 'default', 'devops')"},{type:"text",name:"token",message:"Bot token (from @BotFather)"},{type:"select",name:"agentBinding",message:"Bind to agent",choices:n}]);if(!s.accountName||!s.token)return;try{let r=await(await fetch(`https://api.telegram.org/bot${s.token}/getMe`)).json();if(!r.ok)throw new Error(r.description);console.log(f.green(` Bot verified: @${r.result.username}`));let c=`@${r.result.username}`,a=e.agents[s.agentBinding].mentions||[];a.includes(c)||(a.push(c,r.result.username),e.agents[s.agentBinding].mentions=a)}catch(i){console.log(f.red(` Invalid token: ${i.message}`));return}e.channels.telegram=e.channels.telegram||{enabled:!1,accounts:{},policy:{dm:"pair",group:"mention-required"}},e.channels.telegram.enabled=!0,e.channels.telegram.accounts[s.accountName]={token:s.token,agentBinding:s.agentBinding},await oe(e),console.log(f.green(` Telegram "${s.accountName}" added -> ${s.agentBinding}`))}if(o==="whatsapp"){let s=await ne([{type:"select",name:"defaultAgent",message:"Default agent for WhatsApp messages",choices:n},{type:"text",name:"sessionDir",message:"Session directory",initial:".agentx/whatsapp-sessions"},{type:"confirm",name:"addRoute",message:"Add a contact/group route?",initial:!0}]);if(!s.defaultAgent)return;let i=[],r=s.addRoute;for(;r;){let c=await ne([{type:"select",name:"type",message:"Route type",choices:[{title:"Contact (phone number)",value:"contact"},{title:"Group (name match)",value:"group"}]},{type:"text",name:"value",message:a=>a==="contact"?"Phone number (e.g. +21624309128)":"Group name (partial match)"},{type:"select",name:"agent",message:"Route to agent",choices:n},{type:"confirm",name:"more",message:"Add another route?",initial:!1}]);c.value&&c.agent&&i.push({[c.type]:c.value,agent:c.agent}),r=c.more}e.channels.whatsapp={enabled:!0,sessionDir:s.sessionDir,defaultAgent:s.defaultAgent,routes:i},await oe(e),ye(W(process.cwd(),s.sessionDir),{recursive:!0}),console.log(f.green(` WhatsApp enabled (${i.length} routes, default: ${s.defaultAgent})`)),console.log(f.dim(" Start daemon to scan QR code: agentx daemon start"))}if(o==="discord"){let s=await ne([{type:"text",name:"token",message:"Discord bot token (from developer portal)"},{type:"select",name:"agentBinding",message:"Bind to agent",choices:n}]);if(!s.token)return;e.channels.discord={enabled:!0,token:s.token,agentBinding:s.agentBinding},await oe(e),console.log(f.green(` Discord added -> ${s.agentBinding}`))}if(o==="gitlab"){let s=await ne([{type:"text",name:"host",message:"GitLab instance URL",initial:"https://gitlab.example.com"},{type:"text",name:"token",message:"GitLab API token (PRIVATE-TOKEN)"},{type:"number",name:"webhookPort",message:"Webhook listen port",initial:18810},{type:"text",name:"webhookSecret",message:"Webhook secret (X-Gitlab-Token, optional)"},{type:"confirm",name:"addRoute",message:"Add a project route?",initial:!0}]);if(!s.token)return;let i=[],r=s.addRoute;for(;r;){let c=await ne([{type:"text",name:"project",message:"GitLab project path (e.g. group/project, or * for default)"},{type:"select",name:"agent",message:"Route to agent",choices:n},{type:"confirm",name:"more",message:"Add another route?",initial:!1}]);c.project&&c.agent&&i.push({project:c.project,agent:c.agent}),r=c.more}e.channels.gitlab={enabled:!0,host:s.host,token:s.token,webhookPort:s.webhookPort,webhookSecret:s.webhookSecret||void 0,routes:i},await oe(e),console.log(f.green(` GitLab enabled (${i.length} routes, webhook :${s.webhookPort})`)),console.log(f.dim(` Add webhook in GitLab: ${s.host}/<project>/-/hooks`)),console.log(f.dim(` URL: http://your-server:${s.webhookPort}/`)),s.webhookSecret&&console.log(f.dim(` Secret token: ${s.webhookSecret}`)),console.log(f.dim(" Events: Comments, Issues, Merge requests, Pipeline"))}console.log(f.dim(" Restart daemon to apply: agentx daemon stop && agentx daemon start")),console.log()}});var Re=new ue().name("cron").description("manage cron jobs \u2014 add, list, enable, disable");Re.command("list").alias("ls").description("list cron jobs").action(()=>{let e=Y();console.log();for(let[t,n]of Object.entries(e.crons||{})){let o=n.enabled?f.green("\u25CF"):f.dim("\u25CB");console.log(` ${o} ${f.cyan(t)} \u2014 ${n.schedule} (${n.timezone})`),console.log(f.dim(` agent: ${n.agent} | timeout: ${n.timeout}s`))}Object.keys(e.crons||{}).length||console.log(f.dim(" No cron jobs configured")),console.log()});Re.command("add").description("add a cron job").action(async()=>{let e=Y(),t=Object.keys(e.agents||{}),n=await ne([{type:"text",name:"id",message:"Job ID (kebab-case)"},{type:"text",name:"schedule",message:"Cron expression (e.g. '0 9 * * *')"},{type:"text",name:"timezone",message:"Timezone",initial:"UTC"},{type:"select",name:"agent",message:"Agent",choices:t.map(o=>({title:o,value:o}))},{type:"text",name:"prompt",message:"Task prompt"},{type:"number",name:"timeout",message:"Timeout (seconds)",initial:600},{type:"confirm",name:"enabled",message:"Enable now?",initial:!1}]);!n.id||!n.schedule||(e.crons=e.crons||{},e.crons[n.id]={enabled:n.enabled,schedule:n.schedule,timezone:n.timezone,agent:n.agent,prompt:n.prompt,timeout:n.timeout,onError:["log"]},await oe(e),console.log(f.green(` Cron "${n.id}" added (${n.enabled?"enabled":"disabled"})`)),console.log())});Re.command("enable <id>").description("enable a cron job").action(async e=>{let t=Y();if(!t.crons?.[e]){console.log(f.red(` Cron "${e}" not found`));return}t.crons[e].enabled=!0,await oe(t),console.log(f.green(` Cron "${e}" enabled`))});Re.command("disable <id>").description("disable a cron job").action(async e=>{let t=Y();if(!t.crons?.[e]){console.log(f.red(` Cron "${e}" not found`));return}t.crons[e].enabled=!1,await oe(t),console.log(f.green(` Cron "${e}" disabled`))});var Ue=new ue().name("mesh").description("manage mesh peers \u2014 add, list, remove");Ue.command("list").alias("ls").description("list mesh peers").action(async()=>{let e=Y();if(console.log(),!e.mesh?.enabled)console.log(f.dim(" Mesh: disabled"));else{console.log(f.bold(" Mesh peers:"));for(let t of e.mesh.peers||[]){let n=f.red("unreachable");try{let s=await(await fetch(`${t.url}/health`,{signal:AbortSignal.timeout(3e3)})).json();n=f.green(`healthy \u2014 ${s.agents?.length||0} agents`)}catch{}console.log(` ${f.cyan(t.name)} ${f.dim(t.url)} ${n}`)}}console.log()});Ue.command("add").description("add a mesh peer (another agentx server)").action(async()=>{let e=Y(),t=await ne([{type:"text",name:"name",message:"Peer name (e.g. 'server-2')"},{type:"text",name:"url",message:"Peer URL (e.g. 'http://100.67.108.119:18800')"},{type:"text",name:"token",message:"Auth token (optional, press enter to skip)"}]);if(!(!t.name||!t.url)){try{let o=await(await fetch(`${t.url}/.well-known/agent-card.json`,{signal:AbortSignal.timeout(5e3)})).json();console.log(f.green(` Connected: ${o.name} (${o.skills?.length||0} agents)`))}catch{console.log(f.yellow(" Could not reach peer (adding anyway)"))}e.mesh=e.mesh||{enabled:!1,peers:[],discovery:"static",healthCheck:{interval:60,timeout:10}},e.mesh.enabled=!0,e.mesh.peers=e.mesh.peers||[],e.mesh.peers.push({url:t.url,name:t.name,...t.token?{token:t.token}:{}}),await oe(e),console.log(f.green(` Peer "${t.name}" added`)),console.log()}});Ue.command("remove <name>").alias("rm").description("remove a mesh peer").action(async e=>{let t=Y();t.mesh?.peers&&(t.mesh.peers=t.mesh.peers.filter(n=>n.name!==e),t.mesh.peers.length===0&&(t.mesh.enabled=!1),await oe(t),console.log(f.green(` Peer "${e}" removed`)))});var _e=new ue().name("skill").description("manage skills \u2014 add to agent(s), list");_e.command("add <skillPath>").description("add a skill to agent(s)").option("-a, --agent <agents...>","target agent ID(s)").option("--all","add to all agents").action(async(e,t)=>{let n=Y(),o=W(e);if(!H(o)){console.log(f.red(` Skill not found: ${o}`));return}let s=H(Ge(o,"SKILL.md"))?o.split("/").pop():o.replace(/\/SKILL\.md$/,"").split("/").pop(),i;if(t.all)i=Object.keys(n.agents||{});else if(t.agent)i=t.agent;else{let r=Object.keys(n.agents||{});i=(await ne({type:"multiselect",name:"agents",message:"Install to which agents?",choices:r.map(a=>({title:`${a} (${n.agents[a].name})`,value:a}))})).agents||[]}for(let r of i){let c=n.agents[r];if(!c){console.log(f.yellow(` Agent "${r}" not found, skipping`));continue}let a=W(c.workspace,".claude/skills",s);ye(a,{recursive:!0}),H(Ge(o,"SKILL.md"))?gt(o,a,{recursive:!0}):o.endsWith(".md")&&gt(o,Ge(a,"SKILL.md")),console.log(f.green(` ${s} -> ${r} (${c.workspace})`))}console.log()});_e.command("list").alias("ls").description("list skills per agent").action(()=>{let e=Y();console.log();for(let[t,n]of Object.entries(e.agents||{})){let o=W(n.workspace,".claude/skills"),s=H(o)?qt(o).filter(i=>!i.startsWith(".")):[];console.log(` ${f.cyan(t)}: ${s.length?s.join(", "):f.dim("(none)")}`)}console.log()});_e.command("sync <name>").description("redeploy a skill's SKILL.md from source to agent workspaces (by default, only those that already have it)").option("--source <path>","explicit source path (defaults to src/<name>/SKILL.md or skills/<name>/SKILL.md)").option("--agent <id>","restrict to a single agent's workspace").option("--all-workspaces","also seed into workspaces that don't have the skill yet").option("--dry-run","report what would change, don't write").action(async(e,t)=>{let{createHash:n}=await import("node:crypto"),o=process.cwd(),s=t.source?[t.source]:[W(o,"src",e,"SKILL.md"),W(o,"skills",e,"SKILL.md")],i=s.find(b=>H(b));if(!i){console.log(f.red(` No SKILL.md found for "${e}". Searched:`));for(let b of s)console.log(f.dim(` ${b}`));return}let r=Te(i,"utf-8"),c=n("sha256").update(r).digest("hex").slice(0,12),a=(r.match(/^version:\s*(.+)$/m)?.[1]||"?").trim(),d;try{d=U()}catch(b){console.log(f.red(` Can't load agentx.json: ${b.message}`));return}let m=Object.entries(d.agents||{}),g=t.agent?m.filter(([b])=>b===t.agent):m;if(g.length===0){console.log(f.yellow(t.agent?` Agent "${t.agent}" not in config.`:" No agents in config."));return}console.log(),console.log(f.bold(` Skill sync \u2014 ${e}`)),console.log(f.dim(` Source: ${i}`)),console.log(f.dim(` Version: ${a} \xB7 hash ${c}`)),console.log(f.dim(` Mode: ${t.dryRun?"dry-run":"commit"}${t.allWorkspaces?" \xB7 seed missing":" \xB7 only-existing"}`)),console.log();let p=0,u=0,h=0,y=0;for(let[b,w]of g){let k=w.workspace;if(!k||!H(k)){console.log(` ${f.yellow("\u2717")} ${b.padEnd(22)} ${f.dim("(workspace missing: "+(k||"undefined")+")")}`),y++;continue}let x=W(k,".claude/skills",e),v=W(x,"SKILL.md");if(!H(v)){if(!t.allWorkspaces){console.log(` ${f.dim("\xB7")} ${b.padEnd(22)} ${f.dim("(skipped \u2014 no existing copy; use --all-workspaces to seed)")}`),y++;continue}t.dryRun||(ye(x,{recursive:!0}),dt(v,r)),console.log(` ${f.green("+")} ${b.padEnd(22)} ${f.dim("\u2192 seeded v"+a)}`),p++;continue}let C=Te(v,"utf-8");if(n("sha256").update(C).digest("hex").slice(0,12)===c){console.log(` ${f.dim("=")} ${b.padEnd(22)} ${f.dim("up to date (v"+a+")")}`),h++;continue}let E=(C.match(/^version:\s*(.+)$/m)?.[1]||"?").trim();t.dryRun||dt(v,r),console.log(` ${f.yellow("\u21BB")} ${b.padEnd(22)} ${f.dim(`v${E} \u2192 v${a}`)}`),u++}console.log();let $=t.dryRun?"would":"did";console.log(f.dim(` Summary: ${$} seed ${p}, ${$} update ${u}, ${h} already current, ${y} skipped.`)),t.dryRun&&(p>0||u>0)&&console.log(f.dim(" Dry-run \u2014 rerun without --dry-run to write.")),console.log()});var mt=new ue().name("hook").description("manage hooks \u2014 add to agent workspace");mt.command("add <agent>").description("add a hook to an agent's workspace settings").action(async e=>{let n=Y().agents?.[e];if(!n){console.log(f.red(` Agent "${e}" not found`));return}let o=W(n.workspace,".claude/settings.json"),s=H(o)?JSON.parse(Te(o,"utf-8")):{},i=await ne([{type:"select",name:"event",message:"Hook event",choices:[{title:"PreToolUse \u2014 before any tool runs",value:"PreToolUse"},{title:"PostToolUse \u2014 after any tool runs",value:"PostToolUse"},{title:"SessionStart \u2014 when session begins",value:"SessionStart"},{title:"Notification \u2014 on notifications",value:"Notification"},{title:"Stop \u2014 before session stops",value:"Stop"}]},{type:"select",name:"type",message:"Hook type",choices:[{title:"command \u2014 run a shell command",value:"command"},{title:"http \u2014 POST to a URL",value:"http"}]},{type:"text",name:"value",message:a=>a==="command"?"Shell command":"URL"},{type:"text",name:"matcher",message:"Matcher regex (optional, e.g. 'Bash' to match tool)"}]);if(!i.event||!i.value)return;s.hooks=s.hooks||{},s.hooks[i.event]=s.hooks[i.event]||[];let c={hooks:[{type:i.type,...i.type==="command"?{command:i.value}:{url:i.value}}]};i.matcher&&(c.matcher=i.matcher),s.hooks[i.event].push(c),ye(W(n.workspace,".claude"),{recursive:!0}),dt(o,JSON.stringify(s,null,2)),console.log(f.green(` Hook added to ${e}: ${i.event} -> ${i.type}`)),console.log()});var ut=new ue().name("migrate").description("import configuration from OpenClaw or other tools");ut.command("openclaw [configPath]").description("import agents, channels, and crons from OpenClaw config").option("--dry-run","show what would be imported without writing").action(async(e,t)=>{let n=[e,W(process.env.HOME||"",".openclaw/openclaw.json"),W(process.env.HOME||"",".openclaw/clawdbot.json")].filter(Boolean),o;for(let u of n)if(u&&H(u)){o=u;break}if(!o){console.log(f.red(" OpenClaw config not found. Pass the path: agentx migrate openclaw /path/to/config.json"));return}console.log(f.dim(` Reading: ${o}`));let s=JSON.parse(Te(o,"utf-8")),i=H(W(process.cwd(),"agentx.json"))?Y():{node:{id:"imported",name:"Imported",bind:"127.0.0.1:18800"},providers:{},agents:{},channels:{telegram:{enabled:!1,accounts:{},policy:{dm:"pair",group:"mention-required"}},whatsapp:{enabled:!1}},crons:{},mesh:{enabled:!1,peers:[]}},r=s.agents?.list||[],c=s.bindings||[],a={};for(let u of c)u.match?.channel==="telegram"&&(a[u.match.accountId]=u.agentId);r.find(u=>u.id==="main"||u.default)&&(a.default="main");let d=0;for(let u of r){if(u.workspace?.startsWith("/data"))continue;let h=u.id.replace(/-agent$/,"").replace(/^main$/,"main-agent"),y=u.groupChat?.mentionPatterns||[];if(y.some($=>$===h)||y.push(h),i.agents[h]={name:u.name,workspace:u.workspace,tier:"claude-code",model:"claude-sonnet-4-6",mentions:y,maxConcurrent:u.id==="main"?2:1,systemPrompt:`You are ${u.name} on AgentX.`,permissionMode:"bypassPermissions"},d++,!t.dryRun){ye(W(u.workspace,".claude/skills"),{recursive:!0});let{setupWorkspace:$}=await import("./workspace-setup-ZWTT42XT.js");$(h,i.agents[h])}}console.log(` ${f.green(d)} agents imported (workspaces set up with Claude Code best practices)`);let m=s.channels?.telegram?.accounts||{},g=0;for(let[u,h]of Object.entries(m)){if(!h.botToken)continue;let $=a[u]?.replace(/-agent$/,"").replace(/^main$/,"main-agent")||"default";i.channels.telegram.accounts[u]={token:h.botToken,agentBinding:$},i.channels.telegram.enabled=!0,g++}console.log(` ${f.green(g)} Telegram accounts imported`);let p=W(o,"../cron/jobs.json");if(H(p)){let u=JSON.parse(Te(p,"utf-8")),h=0;for(let y of u.jobs||[]){let $=y.name||y.id,b=y.agentId?.replace(/-agent$/,"").replace(/^main$/,"main-agent")||"default";i.crons[$]={enabled:y.enabled||!1,schedule:y.schedule?.expr||"0 9 * * *",timezone:y.schedule?.tz||"UTC",agent:b,prompt:y.payload?.message||"",timeout:y.payload?.timeoutSeconds||600,onError:["log"]},h++}console.log(` ${f.green(h)} cron jobs imported`)}t.dryRun?(console.log(f.yellow(`
16
- Dry run \u2014 nothing written`)),console.log(JSON.stringify(i,null,2))):(await oe(i),console.log(f.green(`
17
- Migration complete! Review agentx.json and run: agentx daemon start`))),console.log()});var we=new ue().name("config").description("validate and inspect configuration");we.command("check").description("validate agentx.json and check all workspaces").option("-c, --config <path>","path to agentx.json").action(async e=>{console.log();try{let t=U(e.config);console.log(f.green(" \u2713 Config valid")),console.log(f.dim(` Node: ${t.node.name} (${t.node.id})`)),console.log(f.dim(` Bind: ${t.node.bind}`)),console.log(f.dim(` Agents: ${Object.keys(t.agents).length}`)),console.log(f.dim(` Crons: ${Object.keys(t.crons).length}`)),console.log(f.dim(` Mesh peers: ${t.mesh.peers.length}`));let n=Object.keys(t.channels.telegram.accounts).length;console.log(f.dim(` Telegram: ${t.channels.telegram.enabled?`${n} accounts`:"disabled"}`)),console.log(f.dim(` WhatsApp: ${t.channels.whatsapp.enabled?`${t.channels.whatsapp.routes.length} routes`:"disabled"}`)),console.log(f.dim(` Discord: ${t.channels.discord?.enabled?"enabled":"disabled"}`));let o=Fe(t);if(o.length){console.log(),console.log(f.yellow(" Warnings:"));for(let s of o)console.log(f.yellow(` \u26A0 ${s}`))}else console.log(f.green(" \u2713 All workspaces valid"));console.log();for(let[s,i]of Object.entries(t.agents)){let r=i.workspace,c=H(W(r,".claude")),a=H(W(r,"CLAUDE.md")),d=H(W(r,".claude/settings.json")),m=H(W(r,".claude/skills"))?qt(W(r,".claude/skills")).filter(p=>!p.startsWith(".")).length:0,g=[c?f.green("\u2713"):f.red("\u2717"),".claude",a?f.green("\u2713"):f.yellow("\u25CB"),"CLAUDE.md",d?f.green("\u2713"):f.yellow("\u25CB"),"settings",`${m} skills`];console.log(` ${f.cyan(s)}: ${g.join(" ")}`)}}catch(t){console.log(f.red(` \u2717 ${t.message}`))}console.log()});we.command("show").description("print the resolved configuration").option("-c, --config <path>","path to agentx.json").action(e=>{try{let t=U(e.config);console.log(JSON.stringify(t,null,2))}catch(t){console.log(f.red(t.message))}});function Mn(e,t){if(t)return e;try{return JSON.parse(e)}catch{}return e.includes(",")&&!e.includes(" ")?e.split(",").map(n=>n.trim()).filter(Boolean):e}function pt(e){return e===void 0?f.dim("(unset)"):typeof e=="string"?e:JSON.stringify(e,null,2)}we.command("get <path>").description("read a config value by dot-path (e.g. agents.devops.model)").option("-c, --config <path>","path to agentx.json").option("--raw","show ${VAR} tokens instead of env-expanded values").option("--json","output as JSON (for scripting)").action(async(e,t)=>{let{readFileSync:n,existsSync:o}=await import("fs"),{resolve:s}=await import("path"),{getAtPath:i}=await import("./config-mutator-24TH7PHM.js"),{expandEnvVars:r}=await import("./config-LN6JEHMF.js"),c=t.config||s(process.cwd(),"agentx.json");o(c)||(console.log(f.red(` No config at ${c}`)),process.exit(1));let a=JSON.parse(n(c,"utf-8")),d=t.raw?a:r(a),m=i(d,e);t.json?console.log(JSON.stringify(m)):console.log(pt(m))});we.command("set <path> <value>").description("write a config value by dot-path; validates against schema and hot-reloads the daemon").option("-c, --config <path>","path to agentx.json").option("--string","treat the value as a literal string (skip JSON parsing)").option("--dry-run","validate and diff without writing").action(async(e,t,n)=>{let{applyConfigMutation:o,setAtPath:s,getAtPath:i}=await import("./config-mutator-24TH7PHM.js"),r=Mn(t,!!n.string),c=await o(d=>{s(d,e,r)},{configPath:n.config,dryRun:!!n.dryRun});c.success||(console.log(f.red(` \u2717 ${c.error}`)),process.exit(1));let a=i(c.after,e);n.dryRun?console.log(f.yellow(` (dry-run) ${f.cyan(e)} would become: ${pt(a)}`)):(console.log(f.green(` \u2713 ${f.cyan(e)} = ${pt(a)}`)),c.reloaded?console.log(f.dim(" Daemon hot-reloaded.")):c.reloadSkipped&&!/ECONNREFUSED|unreachable|fetch failed/i.test(c.reloadSkipped)&&console.log(f.dim(` (daemon reload skipped: ${c.reloadSkipped})`)))});we.command("unset <path>").description("remove a config value by dot-path (validates + hot-reloads)").option("-c, --config <path>","path to agentx.json").option("--dry-run","validate without writing").action(async(e,t)=>{let{applyConfigMutation:n,getAtPath:o,unsetAtPath:s}=await import("./config-mutator-24TH7PHM.js"),i=await n(c=>{o(c,e)!==void 0&&s(c,e)},{configPath:t.config,dryRun:!!t.dryRun});if(i.success||(console.log(f.red(` \u2717 ${i.error}`)),process.exit(1)),o(i.after,e)!==void 0){console.log(f.dim(` (no-op) ${f.cyan(e)} was not set`));return}t.dryRun?console.log(f.yellow(` (dry-run) ${f.cyan(e)} would be removed`)):(console.log(f.green(` \u2713 ${f.cyan(e)} removed`)),i.reloaded&&console.log(f.dim(" Daemon hot-reloaded.")))});import{Command as _n}from"commander";import P from"chalk";import{existsSync as zn,readFileSync as Bn}from"fs";import{resolve as qn}from"path";import Hn from"prompts";me();import Ln from"cronstrue";var ht={sun:0,sunday:0,mon:1,monday:1,tue:2,tuesday:2,tues:2,wed:3,wednesday:3,thu:4,thursday:4,thurs:4,fri:5,friday:5,sat:6,saturday:6},Ye={morning:{h:9,m:0},afternoon:{h:14,m:0},evening:{h:18,m:0},night:{h:22,m:0},noon:{h:12,m:0},midnight:{h:0,m:0}};function le(e){try{return Ln.toString(e,{use24HourTimeFormat:!1})}catch{return e}}function Un(e){if(/\b(noon|midday)\b/.test(e))return Ye.noon;if(/\bmidnight\b/.test(e))return Ye.midnight;let t=e.match(/\b(?:at|by|@)\s*(\d{1,2})(?::(\d{2}))?\s*(am|pm)?\b/);if(t){let n=parseInt(t[1],10),o=t[2]?parseInt(t[2],10):0,s=t[3];return s==="pm"&&n<12&&(n+=12),s==="am"&&n===12&&(n=0),n>23||o>59?null:{h:n,m:o}}return null}function ft(e){let t=e.trim().toLowerCase();if(!t)return null;let n;if(n=t.match(/\bevery\s+(\d+)\s*(minutes|mins|m)\b/)){let g=parseInt(n[1],10);return g<1||g>59?null:{cron:`*/${g} * * * *`,human:le(`*/${g} * * * *`),matched:n[0]}}if(/\bevery\s+minute\b/.test(t))return{cron:"* * * * *",human:le("* * * * *"),matched:"every minute"};if(n=t.match(/\bevery\s+(\d+)\s*(hours|hrs|h)\b/)){let g=parseInt(n[1],10);return g<1||g>23?null:{cron:`0 */${g} * * *`,human:le(`0 */${g} * * *`),matched:n[0]}}if(/\b(every hour|hourly)\b/.test(t))return{cron:"0 * * * *",human:le("0 * * * *"),matched:"every hour"};let o=Un(t),s=null;for(let[g,p]of Object.entries(Ye))if(!(g==="noon"||g==="midnight")&&new RegExp(`\\b(every\\s+)?${g}s?\\b`).test(t)){s=p;break}let i=o||s,r=t.match(/\b(first|1st|2nd|3rd|last|(\d+)(?:st|nd|rd|th)?)\s+(day\s+)?of\s+(every|each|the)\s+month\b/);if(r){let g=r[1],p=1;if(g==="last"||(/^\d+(st|nd|rd|th)?$/.test(g)?p=parseInt(g,10):g==="first"||g==="1st"?p=1:g==="2nd"?p=2:g==="3rd"&&(p=3),p<1||p>31))return null;let u=i||{h:9,m:0},h=`${u.m} ${u.h} ${p} * *`;return{cron:h,human:le(h),matched:r[0]}}let c=[],a=Object.keys(ht).join("|"),d=new RegExp(`\\b(?:every|on|each|by)\\s+((?:${a})s?(?:\\s*(?:,|and|&)\\s*(?:${a})s?)*)\\b`),m=t.match(d);if(m){let g=m[1];for(let p of Object.keys(ht))if(new RegExp(`\\b${p}s?\\b`).test(g)){let u=ht[p];c.includes(u)||c.push(u)}}if(c.length){let g=i||{h:9,m:0};c.sort((h,y)=>h-y);let p=c.join(","),u=`${g.m} ${g.h} * * ${p}`;return{cron:u,human:le(u),matched:`on ${p}`}}if(/\b(weekday|workday|business day)s?\b/.test(t)){let g=i||{h:9,m:0},p=`${g.m} ${g.h} * * 1-5`;return{cron:p,human:le(p),matched:"weekdays"}}if(/\bweekends?\b/.test(t)){let g=i||{h:9,m:0},p=`${g.m} ${g.h} * * 0,6`;return{cron:p,human:le(p),matched:"weekends"}}if(/\b(every day|daily|each day)\b/.test(t)||s){let g=i||{h:9,m:0},p=`${g.m} ${g.h} * * *`;return{cron:p,human:le(p),matched:s&&Object.entries(Ye).find(([,u])=>u===s)?.[0]||"daily"}}if(o){let g=`${o.m} ${o.h} * * *`;return{cron:g,human:le(g),matched:`at ${o.h}:${String(o.m).padStart(2,"0")}`}}return null}function Ht(e,t){return`${e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40)||"job"}-${t}`}function Ft(e){let t=e||qn(process.cwd(),"agentx.json");return zn(t)||(console.log(P.red(` No config at ${t}. Run: agentx init`)),process.exit(1)),JSON.parse(Bn(t,"utf-8"))}function Fn(e){return e?e.split(",").map(t=>t.trim().toLowerCase()).filter(t=>t==="log"||t==="notify"||t==="disable"):["log"]}async function Jn(e,t){if(!e)return;if(e==="me"){let o=t?.notifications?.destination;if(o?.channel&&o?.chatId)return{channel:o.channel,chatId:o.chatId,accountId:o.accountId};console.log(P.yellow(" --notify me: no notifications.destination set yet."));let{channel:s,chatId:i,accountId:r}=await Hn([{type:"text",name:"channel",message:"Destination channel (telegram/whatsapp/discord):"},{type:"text",name:"chatId",message:"Destination chatId:"},{type:"text",name:"accountId",message:"Account id (optional):"}]);return!s||!i?void 0:{channel:s,chatId:i,accountId:r||void 0}}let n=e.split(":");if(n.length>=2)return{channel:n[0],chatId:n[1],accountId:n[2]};console.log(P.red(` Unrecognized --notify value: ${e} (use "me" or "channel:chatId[:account]")`))}var he=new _n().name("schedule").description('schedule agents with natural-language cron: "every morning at 9" --agent devops --do "Post standup"');he.argument("[when]",'natural-language schedule, e.g. "every morning at 9"').option("--agent <id>","agent to run").option("--do <prompt>","prompt to run at each tick (required unless --id exists)").option("--id <name>","explicit cron id (default: auto-slug of <when>-<agent>)").option("--notify <target>",'"me" (use notifications.destination) or "channel:chatId[:accountId]"').option("--on-error <modes>",'comma list of "log|notify|disable" (default: log; notify implies "notify")').option("--timezone <tz>","IANA timezone (default: Africa/Tunis)","Africa/Tunis").option("--timeout <seconds>","max run time","600").option("--model <model>","override model").option("--disabled","create but leave disabled").option("--dry-run","print what would be written without writing").action(async(e,t)=>{if(!e){he.help();return}t.agent||(console.log(P.red(' --agent is required. Try: agentx schedule "every morning at 9" --agent devops --do "..."')),process.exit(1)),!t.do&&!t.id&&(console.log(P.red(' --do "<prompt>" is required')),process.exit(1));let n=ft(e);n||(console.log(P.red(` Couldn't parse: "${e}"`)),console.log(P.dim(' Try: "every morning at 9", "weekdays at 6pm", "every 15 minutes",')),console.log(P.dim(' "every monday at 10am", "1st of every month at noon",')),console.log(P.dim(' "daily at 9:30am", "every hour", "hourly"')),process.exit(1));let o=Ft(t.config),s=Tt(o);o.agents?.[t.agent]||(console.log(P.red(` Agent "${t.agent}" not found in agentx.json`)),console.log(P.dim(` Available: ${Object.keys(o.agents||{}).join(", ")||"(none)"}`)),process.exit(1));let i=Fn(t.onError),r=await Jn(t.notify,s);r&&!i.includes("notify")&&i.push("notify");let c=t.id||Ht(n.matched,t.agent),a={enabled:!t.disabled,schedule:n.cron,timezone:t.timezone,agent:t.agent,prompt:t.do,timeout:parseInt(t.timeout,10),onError:i};t.model&&(a.model=t.model),r&&(a.notify=r);let d=await K(g=>_(g,`crons.${c}`,a),{configPath:t.config,dryRun:!!t.dryRun});d.success||(console.log(P.red(` \u2717 ${d.error}`)),process.exit(1));let m=t.dryRun?"would add":"Added";console.log(P.green(` \u2713 ${m} cron ${P.cyan(c)}`)),console.log(P.dim(` Schedule: ${n.cron} (${n.human}, ${t.timezone})`)),console.log(P.dim(` Agent: ${t.agent}`)),r&&console.log(P.dim(` Notify: ${r.channel} ${r.chatId}`)),i.length&&console.log(P.dim(` On error: ${i.join(", ")}`)),!t.dryRun&&d.reloaded&&console.log(P.dim(" Daemon hot-reloaded."))});he.command("list").alias("ls").description("list all scheduled jobs with human-readable descriptions").option("-c, --config <path>","path to agentx.json").action(async e=>{let n=Ft(e.config).crons||{},o=Object.entries(n);if(!o.length){console.log(P.dim(" No scheduled jobs"));return}console.log();for(let[s,i]of o){let r=i.enabled?P.green("\u25CF"):P.dim("\u25CB"),c=i.schedule;try{c=(await import("cronstrue")).default.toString(i.schedule,{use24HourTimeFormat:!1})}catch{}console.log(` ${r} ${P.cyan(s.padEnd(32))} ${P.dim(i.schedule.padEnd(14))} ${P.dim("\u2192")} ${i.agent}`),console.log(P.dim(` ${c} (${i.timezone||"UTC"})`)),i.notify&&console.log(P.dim(` notify: ${i.notify.channel} ${i.notify.chatId}`))}console.log()});for(let[e,t]of[["on",!0],["off",!1]])he.command(`${e} <id>`).description(`${t?"enable":"disable"} a scheduled job`).action(async n=>{let o=await K(s=>{if(!fe(s,`crons.${n}`))throw new Error(`cron "${n}" not found`);_(s,`crons.${n}.enabled`,t)});o.success||(console.log(P.red(` \u2717 ${o.error}`)),process.exit(1)),console.log(P.green(` \u2713 ${n} ${t?"enabled":"disabled"}`)),o.reloaded&&console.log(P.dim(" Daemon hot-reloaded."))});he.command("remove <id>").alias("rm").description("remove a scheduled job").action(async e=>{let t=await K(n=>{if(!fe(n,`crons.${e}`))throw new Error(`cron "${e}" not found`);Rt(n,`crons.${e}`)});t.success||(console.log(P.red(` \u2717 ${t.error}`)),process.exit(1)),console.log(P.green(` \u2713 ${e} removed`)),t.reloaded&&console.log(P.dim(" Daemon hot-reloaded."))});he.command("parse <when...>").description("preview a natural-language parse without writing anything").action(e=>{let t=e.join(" "),n=ft(t);n||(console.log(P.red(` \u2717 could not parse: "${t}"`)),process.exit(1)),console.log(P.green(` "${t}"`)),console.log(P.dim(` \u2192 ${n.cron}`)),console.log(P.dim(` \u2192 ${n.human}`))});import{Command as wo}from"commander";import be from"chalk";import N from"chalk";import ze from"prompts";import{readFileSync as Gn,writeFileSync as Kn,existsSync as Yn}from"fs";import{resolve as Jt}from"path";var Vn=/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/;function Qn(e){let t=e.trim();return t.startsWith('"')&&t.endsWith('"')||t.startsWith("'")&&t.endsWith("'")?t.slice(1,-1):t}function yt(e){return/[\s#"'\\]/.test(e)||e===""?JSON.stringify(e):e}function Gt(e=".env"){let t=Jt(e);if(!Yn(t))return[];let n=Gn(t,"utf-8"),o=[];for(let s of n.split(/\r?\n/)){if(s.trim()===""){o.push({kind:"blank",raw:s});continue}if(s.trim().startsWith("#")){o.push({kind:"comment",raw:s});continue}let i=s.match(Vn);i?o.push({kind:"kv",key:i[1],value:Qn(i[2]),raw:s}):o.push({kind:"comment",raw:s})}return o.length&&o[o.length-1].kind==="blank"&&o.pop(),o}function Xn(e){return e.map(t=>t.kind==="kv"?`${t.key}=${yt(t.value)}`:t.raw).join(`
18
- `)+`
19
- `}function $e(e,t,n=".env"){let o=Jt(n),s=Gt(o),i=s.findIndex(r=>r.kind==="kv"&&r.key===e);i>=0?s[i]={kind:"kv",key:e,value:t,raw:`${e}=${yt(t)}`}:(s.length&&s[s.length-1].kind!=="blank"&&s.push({kind:"blank",raw:""}),s.push({kind:"kv",key:e,value:t,raw:`${e}=${yt(t)}`})),Kn(o,Xn(s))}function wt(e,t=".env"){return Gt(t).find(s=>s.kind==="kv"&&s.key===e)?.value}var Kt="https://t.me/BotFather";async function Zn(e){try{let t=await fetch(`https://api.telegram.org/bot${e}/getMe`,{signal:AbortSignal.timeout(8e3)});if(!t.ok)return null;let n=await t.json();return n?.ok?n.result:null}catch{return null}}async function eo(e,t){let n=Date.now()+t,o=0;try{let i=await(await fetch(`https://api.telegram.org/bot${e}/getUpdates?offset=-1&timeout=0`,{signal:AbortSignal.timeout(5e3)})).json();i?.result?.length&&(o=i.result[0].update_id+1)}catch{}for(;Date.now()<n;)try{let i=await(await fetch(`https://api.telegram.org/bot${e}/getUpdates?offset=${o}&timeout=10`,{signal:AbortSignal.timeout(15e3)})).json();for(let r of i?.result||[]){o=Math.max(o,r.update_id+1);let c=r.message||r.edited_message||r.channel_post;if(c?.chat?.id)return{chatId:c.chat.id,chatTitle:c.chat.title||c.chat.username||c.chat.first_name,from:c.from?.first_name||c.from?.username||"unknown"}}}catch{}return null}async function Yt(e={}){console.log(),console.log(N.bold(" Connect Telegram")),console.log(),console.log(N.dim(` 1. Open ${Kt}`)),console.log(N.dim(" /newbot (or /mybots \u2192 pick existing \u2192 API Token)")),console.log(N.dim(" 2. Copy the HTTP API token \u2014 looks like 123456789:ABCdef...")),console.log();let{openBrowser:t}=await ze({type:"confirm",name:"openBrowser",message:"Open BotFather in your browser?",initial:!0});if(t)try{let{spawn:w}=await import("child_process"),k=process.platform==="darwin"?"open":process.platform==="win32"?"start":"xdg-open";w(k,[Kt],{stdio:"ignore",detached:!0}).unref()}catch{}let{token:n}=await ze({type:"password",name:"token",message:"Paste the bot token from BotFather:",validate:w=>/^\d+:[A-Za-z0-9_-]{20,}$/.test(w.trim())||"That doesn't look like a Telegram token"});n||(console.log(N.red(" Aborted")),process.exit(1)),process.stdout.write(" Verifying token... ");let o=await Zn(n.trim());o||(console.log(N.red("\u2717 invalid or revoked")),process.exit(1)),console.log(N.green(`\u2713 ${o.first_name} (@${o.username})`));let{readFileSync:s,existsSync:i}=await import("fs"),{resolve:r}=await import("path"),c=e.configPath||r(process.cwd(),"agentx.json");i(c)||(console.log(N.red(` No agentx.json at ${c}. Run: agentx init`)),process.exit(1));let a=JSON.parse(s(c,"utf-8")),d=Object.keys(a.agents||{});d.length===0&&(console.log(N.red(" No agents configured. Run: agentx agent add")),process.exit(1));let m=e.agent;m||(m=(await ze({type:"select",name:"agent",message:"Bind this bot to which agent?",choices:d.map(k=>({title:k,value:k})),initial:0})).agent),m||(console.log(N.red(" Aborted")),process.exit(1));let g=a.channels?.telegram?.accounts||{},p=e.account||m;g[p]&&g[p].agentBinding!==m&&(p=(await ze({type:"text",name:"account",message:`Account "${p}" is already in use. Pick a different label:`,initial:`${m}-2`,validate:k=>/^[a-z0-9-]+$/i.test(k)||"lowercase letters, digits, hyphens only"})).account),p||(console.log(N.red(" Aborted")),process.exit(1));let u=`TG_${p.toUpperCase().replace(/-/g,"_")}_BOT_TOKEN`;$e(u,n.trim()),process.env[u]=n.trim();let h=await K(w=>{_(w,"channels.telegram.enabled",!0),_(w,`channels.telegram.accounts.${p}`,{token:`\${${u}}`,agentBinding:m});let k=fe(w,`agents.${m}.mentions`)||[],x=`@${o.username}`;k.includes(x)||(k.push(x),_(w,`agents.${m}.mentions`,k))},{configPath:e.configPath});if(h.success||(console.log(N.red(` \u2717 ${h.error}`)),process.exit(1)),console.log(),console.log(N.green(` \u2713 Bot @${o.username} bound to agent "${m}"`)),console.log(N.dim(` Token stored in .env as ${u}`)),console.log(N.dim(` Mentions updated to include @${o.username}`)),h.reloaded?console.log(N.dim(" Daemon hot-reloaded.")):console.log(N.dim(" Start the daemon to begin polling (agentx daemon start)")),e.skipChatCapture)return;console.log();let{capture:y}=await ze({type:"confirm",name:"capture",message:"Listen for a message so we can set this as your default --notify target?",initial:!0});if(!y)return;console.log(),console.log(N.dim(` Send any message to @${o.username} from the chat you want to use.`)),console.log(N.dim(" Waiting up to 90s..."));let $=await eo(n.trim(),9e4);if(!$){console.log(N.yellow(" No message received \u2014 skipping. You can set it later with:")),console.log(N.dim(" agentx config set notifications.destination.channel telegram")),console.log(N.dim(" agentx config set notifications.destination.chatId <chatId>")),console.log(N.dim(` agentx config set notifications.destination.accountId ${p}`));return}let b=await K(w=>{_(w,"notifications.destination",{channel:"telegram",chatId:String($.chatId),accountId:p})},{configPath:e.configPath});if(!b.success){console.log(N.red(` \u2717 failed to save destination: ${b.error}`));return}console.log(N.green(" \u2713 Default --notify target set")),console.log(N.dim(` Chat: ${$.chatTitle||$.chatId} (from ${$.from})`)),console.log(N.dim(" From now on, --notify me resolves to this chat.")),b.reloaded&&console.log(N.dim(" Daemon hot-reloaded."))}import L from"chalk";import Ve from"prompts";var Vt="https://discord.com/developers/applications",to="67584";async function no(e){try{let t=await fetch("https://discord.com/api/v10/users/@me",{headers:{Authorization:`Bot ${e}`},signal:AbortSignal.timeout(8e3)});return t.ok?await t.json():null}catch{return null}}function Qt(e){try{let{spawn:t}=He("child_process"),n=process.platform==="darwin"?"open":process.platform==="win32"?"start":"xdg-open";t(n,[e],{stdio:"ignore",detached:!0}).unref()}catch{}}async function Xt(e={}){console.log(),console.log(L.bold(" Connect Discord")),console.log(),console.log(L.dim(" 1. Open the Discord Developer Portal")),console.log(L.dim(` ${Vt}`)),console.log(L.dim(" 2. New Application \u2192 Bot \u2192 Reset Token \u2192 copy the token")),console.log(L.dim(" 3. Under Bot \u2192 Privileged Gateway Intents,")),console.log(L.dim(" enable 'MESSAGE CONTENT INTENT'")),console.log();let{openBrowser:t}=await Ve({type:"confirm",name:"openBrowser",message:"Open the Developer Portal in your browser?",initial:!0});t&&Qt(Vt);let{token:n}=await Ve({type:"password",name:"token",message:"Paste the bot token:",validate:$=>$.trim().length>20||"that looks too short for a Discord token"});n||(console.log(L.red(" Aborted")),process.exit(1)),process.stdout.write(" Verifying token... ");let o=await no(n.trim());o||(console.log(L.red("\u2717 invalid or revoked \u2014 did you enable MESSAGE CONTENT INTENT and copy the right token?")),process.exit(1));let s=o.discriminator&&o.discriminator!=="0"?`${o.username}#${o.discriminator}`:o.username;console.log(L.green(`\u2713 ${s} (bot id ${o.id})`));let{readFileSync:i,existsSync:r}=await import("fs"),{resolve:c}=await import("path"),a=e.configPath||c(process.cwd(),"agentx.json");r(a)||(console.log(L.red(` No agentx.json at ${a}. Run: agentx init`)),process.exit(1));let d=JSON.parse(i(a,"utf-8")),m=Object.keys(d.agents||{});m.length===0&&(console.log(L.red(" No agents configured. Run: agentx agent add")),process.exit(1));let g=e.agent;g||(g=(await Ve({type:"select",name:"agent",message:"Bind this bot to which agent?",choices:m.map(b=>({title:b,value:b})),initial:0})).agent),g||(console.log(L.red(" Aborted")),process.exit(1));let p="DISCORD_BOT_TOKEN";$e(p,n.trim()),process.env[p]=n.trim();let u=await K($=>{_($,"channels.discord.enabled",!0),_($,"channels.discord.token",`\${${p}}`),_($,"channels.discord.agentBinding",g)},{configPath:e.configPath});u.success||(console.log(L.red(` \u2717 ${u.error}`)),process.exit(1)),console.log(),console.log(L.green(` \u2713 Discord bot ${s} bound to agent "${g}"`)),console.log(L.dim(` Token stored in .env as ${p}`)),u.reloaded?console.log(L.dim(" Daemon hot-reloaded.")):console.log(L.dim(" Restart the daemon to start Discord polling.")),console.log(),console.log(L.bold(" Install the bot to a server")),console.log();let h=`https://discord.com/oauth2/authorize?client_id=${o.id}&scope=bot&permissions=${to}`;console.log(` ${L.cyan(h)}`),console.log();let{openInstall:y}=await Ve({type:"confirm",name:"openInstall",message:"Open the install URL in your browser now?",initial:!0});y&&Qt(h),console.log(),console.log(L.dim(" After you pick a server, the bot will come online on the next daemon restart.")),console.log(L.dim(" Mention the bot in any channel to route messages to the agent."))}import M from"chalk";import $t from"prompts";import{existsSync as kt,mkdirSync as oo,readdirSync as so,readFileSync as Zt}from"fs";import{resolve as bt}from"path";var io=".agentx/whatsapp-sessions";function ao(){let e=bt(process.cwd(),".agentx/daemon.pid");if(!kt(e))return!1;try{let t=parseInt(Zt(e,"utf-8").trim(),10);return t?(process.kill(t,0),!0):!1}catch{return!1}}function ro(e){if(!kt(e))return!1;try{return so(e).some(t=>t.includes("creds")||t.endsWith(".json"))}catch{return!1}}async function co(e,t){let n;try{n=await import("@whiskeysockets/baileys")}catch{return t(M.red(" WhatsApp pairing needs @whiskeysockets/baileys.")),t(M.red(" Install with: pnpm add @whiskeysockets/baileys (or npm install)")),!1}let{makeWASocket:o,useMultiFileAuthState:s}=n.default||n;oo(e,{recursive:!0});let{state:i,saveCreds:r}=await s(e),c;try{c=(await n.fetchLatestBaileysVersion()).version}catch{}let a={level:"silent",trace:()=>{},debug:()=>{},info:()=>{},warn:()=>{},fatal:()=>{},error:()=>{},child:()=>a},d=o({auth:{creds:i.creds,keys:n.makeCacheableSignalKeyStore?n.makeCacheableSignalKeyStore(i.keys,a):i.keys},...c?{version:c}:{},logger:a,printQRInTerminal:!1,browser:["agentx-connect","pairing","1.0"],syncFullHistory:!1,markOnlineOnConnect:!1});return d.ev.on("creds.update",r),await new Promise(m=>{let p=setTimeout(()=>{t(M.yellow(" Timed out waiting for scan (2min)."));try{d.end(void 0)}catch{}m(!1)},12e4);d.ev.on("connection.update",async u=>{if(u.qr){t(M.dim(" Scan this QR with WhatsApp \u2192 Linked devices \u2192 Link a device:")),t("");try{let{default:h}=await import("qrcode-terminal");h.generate(u.qr,{small:!0})}catch{t(` QR: ${u.qr}`)}t("")}if(u.connection==="open"){clearTimeout(p),t(M.green(" \u2713 Paired"));try{d.end(void 0)}catch{}m(!0)}u.connection==="close"&&!u.qr&&u.lastDisconnect?.error?.output?.statusCode===401&&(clearTimeout(p),t(M.red(" \u2717 Authorization failed \u2014 rescan required")),m(!1))})})}async function en(e={}){if(console.log(),console.log(M.bold(" Connect WhatsApp")),console.log(),ao()){console.log(M.yellow(" \u26A0 The daemon is running. Stop it first so pairing can open a fresh Baileys session:")),console.log(M.dim(" agentx daemon stop")),console.log();let{cont:d}=await $t({type:"confirm",name:"cont",message:"Continue anyway? (expect errors)",initial:!1});d||(console.log(M.red(" Aborted")),process.exit(1))}let t=e.configPath||bt(process.cwd(),"agentx.json");kt(t)||(console.log(M.red(` No agentx.json at ${t}. Run: agentx init`)),process.exit(1));let n=JSON.parse(Zt(t,"utf-8")),o=e.sessionDir||n.channels?.whatsapp?.sessionDir||io,s=bt(process.cwd(),o),i=Object.keys(n.agents||{});i.length===0&&(console.log(M.red(" No agents configured. Run: agentx agent add")),process.exit(1));let r=e.agent;r||(r=(await $t({type:"select",name:"agent",message:"Default agent for incoming messages:",choices:i.map(m=>({title:m,value:m})),initial:0})).agent),r||(console.log(M.red(" Aborted")),process.exit(1));let c=!e.skipPair;if(ro(s)&&c){let{keep:d}=await $t({type:"confirm",name:"keep",message:`An existing session was found at ${o}. Keep it (recommended), or pair again?`,initial:!0});c=!d,d&&console.log(M.dim(" Keeping existing session \u2014 skipping QR pair."))}c&&(await co(s,m=>console.log(m))||(console.log(M.red(" Pairing failed. You can retry with `agentx connect whatsapp`.")),process.exit(1)));let a=await K(d=>{_(d,"channels.whatsapp.enabled",!0),_(d,"channels.whatsapp.sessionDir",o),_(d,"channels.whatsapp.defaultAgent",r)},{configPath:e.configPath});a.success||(console.log(M.red(` \u2717 ${a.error}`)),process.exit(1)),console.log(),console.log(M.green(` \u2713 WhatsApp bound to agent "${r}"`)),console.log(M.dim(` Session: ${o}`)),console.log(M.dim(" Start the daemon to begin receiving:")),console.log(M.dim(" agentx daemon start")),console.log(M.dim(" Add routes later with:")),console.log(M.dim(` agentx config set channels.whatsapp.routes '[{"contact":"+1234567890","agent":"${r}"}]'`)),console.log()}import{randomBytes as lo}from"crypto";import z from"chalk";import tn from"prompts";var Qe="agentx-mesh:",Xe="//join/";function go(e){return Buffer.from(e,"utf-8").toString("base64url")}function po(e){return Buffer.from(e,"base64url").toString("utf-8")}function mo(e){return`${Qe}${Xe}${go(JSON.stringify(e))}`}function uo(e){if(!e.startsWith(`${Qe}${Xe}`))throw new Error(`Not a mesh invite link \u2014 expected ${Qe}${Xe}\u2026`);let t=e.slice(Qe.length+Xe.length),n=JSON.parse(po(t));if(n.version!==1)throw new Error(`Unsupported invite version: ${n.version}`);if(typeof n.url!="string"||typeof n.token!="string"||typeof n.name!="string")throw new Error("Invite payload missing required fields (url, token, name)");return n}function ho(){return lo(32).toString("hex")}function fo(e){let t=e?.node?.bind||"127.0.0.1:18800",[n,o]=t.split(":");return`http://${n}:${o}`}function yo(e){return e?.node?.name||e?.node?.id||"node"}async function nn(e){let{readFileSync:t,existsSync:n}=await import("fs"),{resolve:o}=await import("path"),s=e.configPath||o(process.cwd(),"agentx.json");n(s)||(console.log(z.red(` No agentx.json at ${s}. Run: agentx init`)),process.exit(1));let i=JSON.parse(t(s,"utf-8")),r=wt("MESH_TOKEN");r||(r=ho(),$e("MESH_TOKEN",r),console.log(z.dim(" Generated new MESH_TOKEN (32 bytes) and saved to .env")));let c=e.url||fo(i);if((c.includes("0.0.0.0")||c.includes("127.0.0.1")||c.includes("localhost"))&&!e.url){let{url:g}=await tn({type:"text",name:"url",message:"This node's URL as peers should reach it (e.g. http://100.x.x.x:18800):",initial:c,validate:p=>/^https?:\/\//.test(p)||"must start with http:// or https://"});g||(console.log(z.red(" Aborted")),process.exit(1)),c=g}let d=await K(g=>{_(g,"mesh.enabled",!0)},{configPath:e.configPath});d.success||(console.log(z.red(` \u2717 ${d.error}`)),process.exit(1));let m=mo({url:c,token:r,name:yo(i),version:1});console.log(),console.log(z.bold(" Mesh invite")),console.log(),console.log(` ${z.cyan(m)}`),console.log(),console.log(z.dim(" Run on the other node:")),console.log(z.dim(` agentx connect mesh join "${m}"`)),console.log(),console.log(z.yellow(" Share over a trusted channel only \u2014 whoever holds this link can join your mesh.")),console.log()}async function on(e,t={}){let n;try{n=uo(e)}catch(i){console.log(z.red(` \u2717 ${i.message}`)),process.exit(1)}let o=wt("MESH_TOKEN");if(o&&o!==n.token){let{overwrite:i}=await tn({type:"confirm",name:"overwrite",message:"A different MESH_TOKEN already exists in .env. Overwrite with the invite's token?",initial:!1});i||(console.log(z.red(" Aborted (keeping existing .env)")),process.exit(1))}$e("MESH_TOKEN",n.token);let s=await K(i=>{_(i,"mesh.enabled",!0);let r=fe(i,"mesh.peers")||[],c=r.findIndex(d=>d.name===n.name),a={name:n.name,url:n.url,token:"${MESH_TOKEN}"};c>=0?r[c]=a:r.push(a),_(i,"mesh.peers",r)},{configPath:t.configPath});s.success||(console.log(z.red(` \u2717 ${s.error}`)),process.exit(1)),console.log(z.green(" \u2713 Joined mesh")),console.log(z.dim(` Peer: ${n.name} @ ${n.url}`)),console.log(z.dim(" Shared token stored in .env as MESH_TOKEN")),s.reloaded?console.log(z.dim(" Daemon hot-reloaded.")):console.log(z.dim(" Restart the daemon to start the mesh (agentx daemon start).")),console.log();try{let i=await fetch(`${n.url.replace(/\/$/,"")}/.well-known/agent-card.json`,{signal:AbortSignal.timeout(5e3)});if(i.ok){let r=await i.json(),c=Array.isArray(r.agents)?r.agents.length:"?";console.log(z.dim(` Reachable \u2014 ${n.name} exposes ${c} agents`))}else console.log(z.yellow(` Peer returned HTTP ${i.status} \u2014 double-check the URL`))}catch(i){console.log(z.yellow(` Could not reach ${n.url} right now (${i?.cause?.code||i.message}) \u2014 verify VPN / Tailscale when the peer daemon is up`))}}var ke=new wo().name("connect").description("connect a channel or mesh peer \u2014 no manual file editing required");ke.command("telegram").description("pair a Telegram bot \u2014 persists token in .env, binds to an agent, auto-detects default chat").option("--agent <id>","agent to bind the bot to (skips the prompt)").option("--account <name>","account label in channels.telegram.accounts (defaults to agent id)").option("--skip-chat-capture","don't listen for the first message after pairing").option("-c, --config <path>","path to agentx.json").action(async e=>{await Yt({agent:e.agent,account:e.account,configPath:e.config,skipChatCapture:!!e.skipChatCapture})});ke.command("discord").description("pair a Discord bot \u2014 verify token, emit install URL, save to .env").option("--agent <id>","agent to bind the bot to").option("-c, --config <path>","path to agentx.json").action(async e=>{await Xt({agent:e.agent,configPath:e.config})});ke.command("whatsapp").description("pair WhatsApp via QR (prints in terminal), then writes channels.whatsapp config").option("--agent <id>","default agent for inbound messages").option("--session-dir <path>","session directory (default: .agentx/whatsapp-sessions)").option("--skip-pair","skip QR pairing (use when session already exists)").option("-c, --config <path>","path to agentx.json").action(async e=>{await en({agent:e.agent,sessionDir:e.sessionDir,skipPair:!!e.skipPair,configPath:e.config})});var sn=ke.command("mesh").description("mesh invite + join \u2014 replaces manual MESH_TOKEN copy-paste");sn.command("invite").description("emit a single-use join link for another AgentX node").option("--url <url>","this node's URL as peers should reach it (Tailscale IP etc.)").option("-c, --config <path>","path to agentx.json").action(async e=>{await nn({url:e.url,configPath:e.config})});sn.command("join <link>").description("accept an invite link from another AgentX node").option("-c, --config <path>","path to agentx.json").action(async(e,t)=>{await on(e,{configPath:t.config})});ke.action(()=>{console.log(),console.log(be.bold(" agentx connect \u2014 which channel?")),console.log(),console.log(` ${be.cyan("agentx connect telegram")} \u2014 pair a Telegram bot`),console.log(` ${be.cyan("agentx connect discord")} \u2014 pair a Discord bot, emit install URL`),console.log(` ${be.cyan("agentx connect whatsapp")} \u2014 pair WhatsApp via QR`),console.log(` ${be.cyan("agentx connect mesh invite")} \u2014 emit a mesh join link for another node`),console.log(` ${be.cyan("agentx connect mesh join <link>")} \u2014 accept a mesh invite`),console.log(),console.log(be.dim(" GitLab connect is the next slice.")),console.log()});me();import{Command as $o}from"commander";import Z from"chalk";var Be=new $o().name("usage").description("token usage analysis and reporting");Be.command("today",{isDefault:!0}).description("show today's token usage").action(async()=>{try{let e=U(),[t,n]=e.node.bind.split(":"),s=await(await fetch(`http://${t||"127.0.0.1"}:${n}/usage`,{signal:AbortSignal.timeout(3e3)})).json();if(console.log(),console.log(Z.bold(" Token Usage (last 7 days)")),console.log(),s.totalTasks===0){console.log(Z.dim(" No tasks recorded yet")),console.log();return}let i=c=>c.toLocaleString();console.log(` Total: ${Z.bold(i(s.totalTokens))} tokens across ${s.totalTasks} tasks`),console.log(` Input: ${i(s.totalInput)}`),console.log(` Output: ${i(s.totalOutput)}`),console.log(` Cache read: ${i(s.totalCacheRead)}`),console.log(` Cache create: ${i(s.totalCacheCreate)}`),s.cacheHitRatio>0&&console.log(` Cache hit: ${(s.cacheHitRatio*100).toFixed(1)}%`),s.totalErrors>0&&console.log(` Errors: ${Z.red(String(s.totalErrors))}`),console.log(),console.log(Z.bold(" By Agent:"));let r=Object.entries(s.byAgent||{});r.sort((c,a)=>a[1].total-c[1].total);for(let[c,a]of r){let d=i(a.total||a.input+a.output+a.cacheRead+a.cacheCreate),m=a.avgDuration?`${(a.avgDuration/1e3).toFixed(1)}s avg`:"";console.log(` ${Z.cyan(c)}: ${d} tokens (${a.tasks} tasks) ${Z.dim(m)}`)}console.log()}catch(e){e.cause?.code==="ECONNREFUSED"?console.log(Z.red(" Daemon not running. Start with: agentx daemon start")):console.log(Z.red(` ${e.message}`))}});Be.command("serve").description("start a web dashboard for token usage visualization").option("--port <n>","port number","4201").action(async e=>{let{startUsageDashboard:t}=await import("./usage-dashboard-QX3CF6KK.js");t(parseInt(e.port))});Be.command("report").description("run full session analysis (parses Claude Code JSONL files)").option("--days <n>","analyze last N days","7").action(async e=>{let{execSync:t}=await import("child_process"),{existsSync:n}=await import("fs"),{resolve:o}=await import("path"),s=o(process.cwd(),"scripts/token-report.py");if(!n(s)){console.log(Z.red(` Script not found: ${s}`)),console.log(Z.dim(" The token analyzer script should be at scripts/token-report.py"));return}console.log(Z.dim(" Analyzing Claude Code sessions..."));try{t(`SINCE_DAYS=${e.days} python3 "${s}"`,{stdio:"inherit"})}catch{console.log(Z.red(" Analysis failed. Is Python 3 installed?"))}});me();import{Command as bo}from"commander";import V from"chalk";var Pe=new bo().name("board").description("kanban board dashboard \u2014 visual work view over configured sources");Pe.command("serve",{isDefault:!0}).description("start the dashboard server (live view always; boards if configured)").option("--port <n>","override dashboard.port").option("--bind <host>","override dashboard.bind (e.g. 0.0.0.0)").action(async e=>{try{let t=ko(e.port,e.bind),{startBoardDashboard:n}=await import("./board-dashboard-LA7WW56M.js");n(t)}catch(t){console.log(V.red(` ${t.message}`)),process.env.DEBUG&&t.stack&&console.log(V.dim(t.stack)),process.exit(1)}});function ko(e,t){try{let n=U();return e&&(n.dashboard.port=parseInt(e,10)),t&&(n.dashboard.bind=t),n}catch{return console.log(V.yellow(" No agentx.json found \u2014 starting dashboard in setup-only mode.")),console.log(V.dim(" Visit http://127.0.0.1:"+(e||"4202")+` to run the wizard.
20
- `)),{node:{id:"setup",name:"Setup",bind:"127.0.0.1:18800"},providers:{},agents:{},channels:{},crons:{},mesh:{enabled:!1,peers:[],discovery:"static",healthCheck:{interval:60,timeout:10}},boards:[],dashboard:{enabled:!0,port:e?parseInt(e,10):4202,bind:t||"127.0.0.1",daemonUrl:"http://localhost:18800",daemons:[]},business:void 0,session:{staleMinutes:120}}}}Pe.command("list").description("list configured boards").action(()=>{try{let e=U();if(!e.boards?.length){console.log(V.dim(`
21
- No boards configured.
22
- `));return}console.log();for(let t of e.boards){console.log(` ${V.cyan(t.id)} \u2014 ${t.name}`);let n=t.source;console.log(V.dim(` source: ${n.type}${n.projects?" ("+n.projects.join(", ")+")":""}`)),t.primaryToolLabel&&console.log(V.dim(` primary label: ${t.primaryToolLabel}`)),console.log(V.dim(` window: ${t.timeRangeDays} days`))}console.log()}catch(e){console.log(V.red(` ${e.message}`)),process.exit(1)}});Pe.command("add <id>").description("add a GitLab board to agentx.json").requiredOption("--name <name>","human-readable board name").requiredOption("--projects <paths>","comma-separated GitLab project paths (e.g. 'mtgl/system,mtgl/website')").option("--label <label>","primary tool label ANDed into every query (e.g. 'Tool::Claude')").option("--days <n>","open-window time range in days","30").option("--closed-days <n>","closed-window in days","30").action((e,t)=>{try{an(n=>{if(Array.isArray(n.boards)||(n.boards=[]),n.boards.find(i=>i.id===e))throw new Error(`board "${e}" already exists`);let o=String(t.projects).split(",").map(i=>i.trim()).filter(Boolean);if(o.length===0)throw new Error("--projects must include at least one path");let s={id:e,name:t.name,source:{type:"gitlab",projects:o},timeRangeDays:parseInt(t.days,10)||30,closedWindowDays:parseInt(t.closedDays,10)||30};return t.label&&(s.primaryToolLabel=t.label),n.boards.push(s),`added board "${e}" (${o.join(", ")})`})}catch(n){console.log(V.red(` ${n.message}`)),process.exit(1)}});Pe.command("remove <id>").alias("rm").description("remove a board from agentx.json").action(e=>{try{an(t=>{if(!Array.isArray(t.boards)||t.boards.length===0)throw new Error("no boards configured");let n=t.boards.length;if(t.boards=t.boards.filter(o=>o.id!==e),t.boards.length===n)throw new Error(`board "${e}" not found`);return`removed board "${e}"`})}catch(t){console.log(V.red(` ${t.message}`)),process.exit(1)}});function an(e){let{summary:t,backupPath:n}=Et(o=>e(o));console.log(V.green(`
23
- \u2713 ${t}
24
- `)),n&&console.log(V.dim(` Backup: ${n}`)),console.log(V.dim(` Restart the dashboard to pick up the change if not auto-reloading.
25
- `))}import{Command as Do}from"commander";import l from"chalk";import{createServer as vo}from"http";var Ze=class{peers;cache=new Map;ttl=3e4;constructor(t){this.peers=t.map(n=>({url:n.replace(/\/$/,"")}))}async getRemoteAgents(){let t=[];return await Promise.all(this.peers.map(async n=>{try{let o=await this.fetchJson(`${n.url}/wiki/agents`);n.nodeId=o.nodeId;for(let s of o.agents||[])(s.totalEntries>0||s.totalArticles>0)&&t.push({...s,nodeId:o.nodeId,peerUrl:n.url})}catch{}})),t}async getRemoteArticles(t,n){try{return(await this.fetchJson(`${t}/wiki/articles?agent=${encodeURIComponent(n)}`)).articles||[]}catch{return[]}}async getRemoteArticle(t,n,o){try{let s=await this.fetchJson(`${t}/wiki/article?agent=${encodeURIComponent(n)}&path=${encodeURIComponent(o)}`);return{meta:{title:s.title,tags:s.tags||[],owner:s.owner,access:"public",created:s.created,lastUpdated:s.lastUpdated,sources:s.sources||[]},content:s.content,path:s.path}}catch{return null}}async fetchJson(t){let n=this.cache.get(t);if(n&&Date.now()-n.ts<this.ttl)return n.data;let o=await fetch(t,{signal:AbortSignal.timeout(5e3)});if(!o.ok)throw new Error(`HTTP ${o.status}`);let s=await o.json();return this.cache.set(t,{data:s,ts:Date.now()}),s}};function A(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function xo(e){return e.replace(/<!--\s*tags?:[^>]*-->\s*\n?/gi,"")}function rn(e,t){return`<a href="${t?`/agent/${encodeURIComponent(t)}/tag/${encodeURIComponent(e)}`:`/tag/${encodeURIComponent(e)}`}" class="tag">${A(e)}</a>`}function ve(e,t){return(e||[]).map(n=>rn(n,t)).join(" ")}function et(e,t,n=""){let o=A(xo(e));return o=o.replace(/```(\w*)\n([\s\S]*?)```/g,(s,i,r)=>`<pre><code class="lang-${i}">${r.trim()}</code></pre>`),o=o.replace(/`([^`]+)`/g,"<code>$1</code>"),o=o.replace(/^(\|.+\|)\n(\|[-| :]+\|)\n((?:\|.+\|\n?)*)/gm,(s,i,r,c)=>{let a=i.split("|").filter(m=>m.trim()).map(m=>`<th>${m.trim()}</th>`).join(""),d=c.trim().split(`
26
- `).map(m=>`<tr>${m.split("|").filter(p=>p.trim()).map(p=>`<td>${p.trim()}</td>`).join("")}</tr>`).join("");return`<table><thead><tr>${a}</tr></thead><tbody>${d}</tbody></table>`}),o=o.replace(/^#### (.+)$/gm,"<h4>$1</h4>"),o=o.replace(/^### (.+)$/gm,"<h3>$1</h3>"),o=o.replace(/^## (.+)$/gm,"<h2>$1</h2>"),o=o.replace(/^# (.+)$/gm,"<h1>$1</h1>"),o=o.replace(/\*\*(.+?)\*\*/g,"<strong>$1</strong>"),o=o.replace(/\*(.+?)\*/g,"<em>$1</em>"),o=o.replace(/\[\[([^\]]+)\]\]/g,(s,i)=>{let r=t.get(i);return r?`<a href="${n}/article/${encodeURIComponent(r)}" class="wikilink">${i}</a>`:`<a href="${n}/search?q=${encodeURIComponent(i)}" class="wikilink broken">${i}</a>`}),o=o.replace(/\[([^\]]+)\]\(([^)]+)\)/g,'<a href="$2" target="_blank">$1</a>'),o=o.replace(/^(>\s?.*(?:\n>\s?.*)*)/gm,s=>`<blockquote>${s.split(`
27
- `).map(r=>r.replace(/^>\s?/,"")).join("<br>")}</blockquote>`),o=o.replace(/^[-*] \[( |x|X)\] (.+)$/gm,(s,i,r)=>`<li class="task">${i.toLowerCase()==="x"?"\u2611":"\u2610"} ${r}</li>`),o=o.replace(/^[-*] (.+)$/gm,"<li>$1</li>"),o=o.replace(/^\d+\. (.+)$/gm,'<li data-ordered="1">$1</li>'),o=o.replace(/(<li data-ordered="1">.*?<\/li>(?:\n?<li data-ordered="1">.*?<\/li>)*)/g,"<ol>$1</ol>"),o=o.replace(/((?:<li(?! data-ordered)[^>]*>.*?<\/li>\n?)+)/g,"<ul>$1</ul>"),o=o.replace(/ data-ordered="1"/g,""),o=o.replace(/^---$/gm,"<hr>"),o=o.replace(/\n\n+/g,"</p><p>"),o=`<p>${o}</p>`,o=o.replace(/<p>\s*(<(?:h[1-6]|ul|ol|blockquote|table|pre|hr)\b[^>]*>[\s\S]*?<\/(?:h[1-6]|ul|ol|blockquote|table|pre)>)\s*<\/p>/g,"$1"),o=o.replace(/<p>\s*<hr>\s*<\/p>/g,"<hr>"),o=o.replace(/<p>\s*<\/p>/g,""),o=o.replace(/<p>(<(?:h[1-4]|pre|table|ul|hr|div))/g,"$1"),o=o.replace(/(<\/(?:h[1-4]|pre|table|ul|hr|div)>)<\/p>/g,"$1"),o=o.replace(/<p>\s*<\/p>/g,""),o}var So=`
28
- :root {
29
- --bg: #f8f9fa; --card: #ffffff; --border: #a2a9b1;
30
- --link: #0645ad; --link-visited: #0b0080; --link-broken: #ba0000;
31
- --heading-border: #a2a9b1; --text: #202122; --text-dim: #54595d;
32
- --accent: #eaecf0; --tag: #eaf3ff;
33
- }
34
- * { margin: 0; padding: 0; box-sizing: border-box; }
35
- body { font-family: -apple-system, 'Segoe UI', Roboto, sans-serif; color: var(--text); background: var(--bg); line-height: 1.6; }
36
- .layout { display: grid; grid-template-columns: 260px 1fr; min-height: 100vh; }
37
- .sidebar { background: var(--card); border-right: 1px solid var(--border); padding: 20px 16px; position: sticky; top: 0; height: 100vh; overflow-y: auto; }
38
- .sidebar h2 { font-size: 13px; text-transform: uppercase; letter-spacing: 0.5px; color: var(--text-dim); margin: 20px 0 6px; padding-bottom: 4px; border-bottom: 1px solid var(--accent); }
39
- .sidebar h2:first-child { margin-top: 0; }
40
- .sidebar a { display: block; padding: 3px 8px; color: var(--link); text-decoration: none; font-size: 14px; border-radius: 3px; }
41
- .sidebar a:hover { background: var(--accent); }
42
- .sidebar a.active { background: var(--tag); font-weight: 600; }
43
- .sidebar .logo { font-size: 20px; font-weight: 700; color: var(--text); text-decoration: none; display: block; margin-bottom: 12px; padding: 4px 8px; }
44
- .sidebar .search-box { width: 100%; padding: 6px 10px; border: 1px solid var(--border); border-radius: 4px; font-size: 14px; margin-bottom: 12px; }
45
- .sidebar .agent-badge { display: inline-block; background: #dcedc8; color: #33691e; border-radius: 3px; padding: 1px 6px; font-size: 11px; font-weight: 600; margin-left: 4px; }
46
- .main { padding: 32px 48px; max-width: 960px; }
47
- .main h1 { font-size: 28px; font-weight: 400; border-bottom: 1px solid var(--heading-border); padding-bottom: 8px; margin-bottom: 16px; }
48
- .main h2 { font-size: 22px; font-weight: 400; border-bottom: 1px solid var(--accent); padding-bottom: 4px; margin: 24px 0 12px; }
49
- .main h3 { font-size: 18px; margin: 20px 0 8px; }
50
- .main h4 { font-size: 16px; margin: 16px 0 8px; }
51
- .main p { margin: 8px 0; }
52
- .main ul { margin: 8px 0 8px 24px; }
53
- .main li { margin: 2px 0; }
54
- .main a { color: var(--link); }
55
- .main a:visited { color: var(--link-visited); }
56
- .main a.wikilink.broken { color: var(--link-broken); }
57
- .meta { background: var(--accent); border: 1px solid var(--border); border-radius: 4px; padding: 12px 16px; margin-bottom: 20px; font-size: 13px; display: grid; grid-template-columns: auto 1fr; gap: 4px 16px; }
58
- .meta dt { font-weight: 600; color: var(--text-dim); }
59
- .tag { display: inline-block; background: var(--tag); border-radius: 3px; padding: 1px 8px; font-size: 12px; margin: 1px 2px; color: inherit; text-decoration: none; }
60
- a.tag:hover { background: var(--border); cursor: pointer; }
61
- .article-row { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; padding: 2px 0; }
62
- .article-row .article-title { margin-left: 4px; }
63
- .article-list { display: flex; flex-direction: column; }
64
- table { border-collapse: collapse; margin: 12px 0; width: 100%; }
65
- th, td { border: 1px solid var(--border); padding: 6px 12px; text-align: left; font-size: 14px; }
66
- th { background: var(--accent); font-weight: 600; }
67
- tr:hover td { background: #f0f4ff; }
68
- pre { background: #f5f5f5; border: 1px solid var(--border); border-radius: 4px; padding: 12px; overflow-x: auto; font-size: 13px; margin: 12px 0; }
69
- code { font-family: 'SF Mono', Menlo, monospace; font-size: 13px; }
70
- p code { background: #f5f5f5; padding: 1px 4px; border-radius: 2px; }
71
- .entry-card { background: var(--card); border: 1px solid var(--border); border-radius: 4px; padding: 12px 16px; margin: 8px 0; }
72
- .entry-card .meta-line { font-size: 12px; color: var(--text-dim); margin-bottom: 4px; }
73
- .stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px; margin: 16px 0; }
74
- .stat-card { background: var(--card); border: 1px solid var(--border); border-radius: 6px; padding: 16px; text-align: center; }
75
- .stat-card .number { font-size: 32px; font-weight: 700; color: var(--link); }
76
- .stat-card .label { font-size: 13px; color: var(--text-dim); margin-top: 4px; }
77
- .agent-card { background: var(--card); border: 1px solid var(--border); border-radius: 8px; padding: 20px; margin: 12px 0; transition: box-shadow 0.15s; }
78
- .agent-card:hover { box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
79
- .agent-card h3 { margin: 0 0 8px; }
80
- .agent-card h3 a { text-decoration: none; font-size: 20px; }
81
- .agent-card .agent-stats { display: flex; gap: 16px; font-size: 14px; color: var(--text-dim); }
82
- .agent-card .agent-stats strong { color: var(--text); }
83
- .agent-card .article-list { margin-top: 8px; font-size: 13px; }
84
- .agent-card .article-list a { margin-right: 8px; }
85
- .backlinks { background: #fffbe6; border: 1px solid #e6d98c; border-radius: 4px; padding: 12px 16px; margin-top: 24px; font-size: 13px; }
86
- .backlinks h4 { margin-bottom: 4px; }
87
- .breadcrumb { font-size: 13px; color: var(--text-dim); margin-bottom: 12px; }
88
- .breadcrumb a { color: var(--link); text-decoration: none; }
89
- @media (max-width: 768px) { .layout { grid-template-columns: 1fr; } .sidebar { position: static; height: auto; } .main { padding: 20px; } }
90
- `;function F(e,t,n){return`<!DOCTYPE html>
91
- <html lang="en">
92
- <head>
93
- <meta charset="utf-8">
94
- <meta name="viewport" content="width=device-width, initial-scale=1">
95
- <title>${A(e)} \u2014 AgentX Wiki</title>
96
- <style>${So}</style>
97
- </head>
98
- <body>
99
- <div class="layout">
100
- <nav class="sidebar">${t}</nav>
101
- <main class="main">${n}</main>
102
- </div>
103
- </body>
104
- </html>`}function cn(e){let t=new Map;for(let n of e){let o=n.path.includes("/")?n.path.split("/").slice(0,-1).join("/"):"/",s=t.get(o)||[];s.push(n),t.set(o,s)}return new Map([...t].sort((n,o)=>n[0].localeCompare(o[0])))}function ln(e){return e==="/"?"Root":e.split("/").pop().replace(/-/g," ")}function se(e,t){let n='<a href="/" class="logo">AgentX Wiki</a>';n+='<form action="/search" method="get"><input type="text" name="q" class="search-box" placeholder="Search all wikis..."></form>',n+="<h2>Hub</h2>",n+=`<a href="/"${t?"":' class="active"'}>Home</a>`,n+='<a href="/entries">All Entries</a>';for(let o of e){let s=`/agent/${encodeURIComponent(o.agentId)}`;n+=`<h2><a href="${s}" style="color:inherit;text-decoration:none">${A(o.agentId)}</a> <span class="agent-badge">${o.totalArticles}</span></h2>`;let i=cn(o.articles);for(let[r,c]of i){n+=`<div style="font-size:11px;text-transform:uppercase;letter-spacing:0.3px;color:#888;margin:8px 8px 2px;font-weight:600">${ln(r)}</div>`;for(let a of c){let m=`${o.agentId}/${a.path}`===t;n+=`<a href="${s}/article/${encodeURIComponent(a.path)}"${m?' class="active"':""} style="padding-left:16px;font-size:13px">${A(a.title)}</a>`}}}return n}function Ao(e,t,n){let o=t||e.summary(),i=e.getSharedStore().listEntries().length,r=o.reduce((m,g)=>m+g.totalArticles,0),c=o.reduce((m,g)=>m+g.unabsorbed,0),a=new Set((n||[]).map(m=>m.agentId)),d="<h1>AgentX Wiki Hub</h1>";if(d+='<div class="stats">',d+=`<div class="stat-card"><div class="number">${o.length}</div><div class="label">Agents</div></div>`,d+=`<div class="stat-card"><div class="number">${r}</div><div class="label">Articles</div></div>`,d+=`<div class="stat-card"><div class="number">${i}</div><div class="label">Raw Entries</div></div>`,n&&n.length>0){let m=new Set(n.map(g=>g.nodeId));d+=`<div class="stat-card"><div class="number">${m.size}</div><div class="label">Mesh Nodes</div></div>`}d+="</div>",d+="<h2>Agent Wikis</h2>";for(let m of o){let g=m.unabsorbed>0?`<span style="color: #e6a700;">${m.unabsorbed} unabsorbed</span>`:'<span style="color: green;">up to date</span>',u=a.has(m.agentId)&&!e.listAgents().includes(m.agentId)?' <span class="tag" style="background:#fff3cd;color:#856404">remote</span>':"";if(d+='<div class="agent-card">',d+=`<h3><a href="/agent/${encodeURIComponent(m.agentId)}">${A(m.agentId)}</a>${u}</h3>`,d+=`<div class="agent-stats">
105
- <span><strong>${m.totalArticles}</strong> articles</span>
106
- <span><strong>${m.totalEntries}</strong> entries</span>
107
- <span>${g}</span>
108
- </div>`,m.articles.length>0){d+='<div class="article-list">';for(let h of m.articles){let y=ve((h.tags||[]).slice(0,3),m.agentId);d+=`<div class="article-row">${y} <a class="article-title" href="/agent/${encodeURIComponent(m.agentId)}/article/${encodeURIComponent(h.path)}">${A(h.title)}</a></div>`}d+="</div>"}d+="</div>"}return F("Hub",se(o),d)}function Co(e,t,n){let o=n||e.summary(),s=o.find(p=>p.agentId===t);if(!s)return F("Not Found",se(o),"<h1>Agent not found</h1>");let i=s.peerUrl!==void 0,r=e.getAgentEntries(t),c=s.articles||[],a=`<div class="breadcrumb"><a href="/">Hub</a> / ${A(t)}</div>`;if(a+=`<h1>${A(t)}</h1>`,i&&(a+=`<p><span class="tag" style="background:#fff3cd;color:#856404">remote @ ${A(s.nodeId)}</span></p>`),a+='<div class="stats">',a+=`<div class="stat-card"><div class="number">${s.totalArticles}</div><div class="label">Articles</div></div>`,a+=`<div class="stat-card"><div class="number">${s.totalEntries}</div><div class="label">Entries</div></div>`,a+=`<div class="stat-card"><div class="number">${s.unabsorbed}</div><div class="label">Unabsorbed</div></div>`,a+="</div>",c.length>0){a+="<h2>Articles</h2><table>",a+="<tr><th>Title</th><th>Tags</th></tr>";for(let p of c)a+=`<tr>
109
- <td><a href="/agent/${encodeURIComponent(t)}/article/${encodeURIComponent(p.path)}">${A(p.title)}</a></td>
110
- <td>${ve((p.tags||[]).slice(0,4),t)}</td>
111
- </tr>`;a+="</table>"}let d=r.slice(-5).reverse();if(d.length>0){a+="<h2>Recent Entries</h2>";for(let p of d)a+=`<div class="entry-card">
112
- <div class="meta-line">${p.date} via ${p.source} &middot; <code>${p.id}</code></div>
113
- <p>${A(p.content.slice(0,300))}${p.content.length>300?"...":""}</p>
114
- </div>`}let g=e.getAgentWiki(t).lint();if(g.length>0){a+=`<h2>Health Issues (${g.length})</h2><table>`,a+="<tr><th>Type</th><th>Article</th><th>Issue</th></tr>";for(let p of g)a+=`<tr><td><span class="tag">${p.type}</span></td><td>${A(p.article)}</td><td>${A(p.message)}</td></tr>`;a+="</table>"}return F(t,se(o,t),a)}function jo(e,t,n,o){let s=e.getAgentWiki(t),i=s.readArticle(n);if(!i)return null;let r=o||e.summary(),c=s.rebuildIndex(),a=new Map;for(let u of c.articles)a.set(u.title,u.path);let d=`/agent/${encodeURIComponent(t)}`,g=s.buildBacklinks()[i.meta.title]||[],p=`<div class="breadcrumb"><a href="/">Hub</a> / <a href="${d}">${A(t)}</a> / ${A(i.meta.title)}</div>`;if(p+=`<h1>${A(i.meta.title)}</h1>`,p+='<dl class="meta">',i.meta.type&&(p+=`<dt>Type</dt><dd>${rn(i.meta.type,t)}</dd>`),p+=`<dt>Tags</dt><dd>${ve(i.meta.tags,t)}</dd>`,p+=`<dt>Owner</dt><dd>${A(i.meta.owner)}</dd>`,p+=`<dt>Created</dt><dd>${i.meta.created}</dd>`,p+=`<dt>Updated</dt><dd>${i.meta.lastUpdated}</dd>`,i.meta.sources?.length&&(p+=`<dt>Sources</dt><dd>${i.meta.sources.length} entries</dd>`),p+="</dl>",p+=et(i.content,a,d),g.length>0){p+='<div class="backlinks"><h4>Pages that link here:</h4><ul>';for(let u of g){let h=s.readArticle(u);p+=`<li><a href="${d}/article/${encodeURIComponent(u)}">${A(h?.meta.title||u)}</a></li>`}p+="</ul></div>"}return F(i.meta.title,se(r,`${t}/${n}`),p)}function Io(e,t){let n=t||e.summary(),s=e.getSharedStore().listEntries(),i='<div class="breadcrumb"><a href="/">Hub</a> / Entries</div>';i+=`<h1>All Raw Entries (${s.length})</h1>`;let r=new Map;for(let c of s){let a=r.get(c.date)||[];a.push(c),r.set(c.date,a)}for(let[c,a]of[...r].reverse()){i+=`<h2>${c} (${a.length})</h2>`;for(let d of a)i+=`<div class="entry-card">
115
- <div class="meta-line">
116
- <a href="/agent/${encodeURIComponent(d.agentId)}">${A(d.agentId)}</a>
117
- via ${d.source} &middot; <code>${d.id}</code>
118
- </div>
119
- <p>${A(d.content.slice(0,400))}${d.content.length>400?"...":""}</p>
120
- </div>`}return F("All Entries",se(n),i)}function To(e,t,n){let o=n||e.summary(),s=`<h1>Search: "${A(t)}"</h1>`,i=0;for(let r of o){let a=e.getAgentWiki(r.agentId).findRelevant(t,void 0,10);if(a.length>0){s+=`<h2>${A(r.agentId)} (${a.length})</h2>`;for(let d of a){let m=`/agent/${encodeURIComponent(r.agentId)}`;s+=`<div class="entry-card">
121
- <div class="meta-line">${ve((d.meta.tags||[]).slice(0,3),r.agentId)}</div>
122
- <h3><a href="${m}/article/${encodeURIComponent(d.path)}">${A(d.meta.title)}</a></h3>
123
- <p>${A(d.content.slice(0,200))}...</p>
124
- </div>`}i+=a.length}}return i===0&&(s+="<p>No matching articles found.</p>"),F(`Search: ${t}`,se(o),s)}function Ro(e,t,n){let o=n||e.summary(),s=`<h1>Tag: <span class="tag">${A(t)}</span></h1>`,i=0;for(let r of o){let c;try{c=e.getAgentWiki(r.agentId)}catch{continue}let a=c.findByTags([t],void 0,100);if(a.length!==0){s+=`<h2>${A(r.agentId)} (${a.length})</h2>`;for(let d of a){let m=`/agent/${encodeURIComponent(r.agentId)}`;s+=`<div class="entry-card">
125
- <div class="meta-line">${ve((d.meta.tags||[]).slice(0,6),r.agentId)}</div>
126
- <h3><a href="${m}/article/${encodeURIComponent(d.path)}">${A(d.meta.title)}</a></h3>
127
- </div>`}i+=a.length}}return i===0&&(s+=`<p>No articles tagged <code>${A(t)}</code>.</p>`),F(`Tag: ${t}`,se(o),s)}function Po(e,t,n,o){let s=o||e.summary(),i;try{i=e.getAgentWiki(t)}catch{return F(`Tag: ${n}`,se(s),`<h1>Agent not found: ${A(t)}</h1>`)}let r=i.findByTags([n],void 0,100),c=`/agent/${encodeURIComponent(t)}`,a=`<div class="breadcrumb"><a href="/">Hub</a> / <a href="${c}">${A(t)}</a> / Tag: ${A(n)}</div>`;a+=`<h1>${A(t)} \xB7 Tag: <span class="tag">${A(n)}</span></h1>`,a+=`<p>${r.length} article${r.length===1?"":"s"} tagged <code>${A(n)}</code></p>`;for(let d of r)a+=`<div class="entry-card">
128
- <div class="meta-line">${ve((d.meta.tags||[]).slice(0,6),t)}</div>
129
- <h3><a href="${c}/article/${encodeURIComponent(d.path)}">${A(d.meta.title)}</a></h3>
130
- </div>`;return F(`${t} \xB7 ${n}`,se(s),a)}function Ee(e,t,n){let o=e.rebuildIndex(),s=cn(o.articles.map(r=>({title:r.title,path:r.path,tags:r.tags}))),i=`<a href="/" class="logo">${A(t)}</a>`;i+='<form action="/search" method="get"><input type="text" name="q" class="search-box" placeholder="Search..."></form>',i+="<h2>Navigation</h2>",i+=`<a href="/"${n?"":' class="active"'}>Home</a>`,i+='<a href="/lint">Health Check</a>';for(let[r,c]of s){i+=`<h2>${ln(r)}</h2>`;for(let a of c){let d=a.path===n;i+=`<a href="/article/${encodeURIComponent(a.path)}"${d?' class="active"':""}>${A(a.title)}</a>`}}return i}function dn(e,t=4200,n,o=[],s="graph"){let i=new Oe(e,void 0,s),r=o.length>0?new Ze(o):null;vo(async(a,d)=>{let m=new URL(a.url||"/",`http://localhost:${t}`),g=decodeURIComponent(m.pathname),p=null,u=200;try{if(g.startsWith("/remote/")&&r){let h=g.slice(8).split("/article/");if(h.length===2){let[y,$]=h,b=y.indexOf("/"),w=decodeURIComponent(y.slice(0,b)),k=y.slice(b+1),x=await r.getRemoteArticle(w,k,$);if(x){let v=await r.getRemoteAgents(),C=[...i.summary(),...v.map(T=>({...T,agentId:`${T.agentId} @${T.nodeId}`}))],R=`<div class="breadcrumb"><a href="/">Hub</a> / <span class="tag">remote</span> ${A(k)} @ ${A(w)}</div>`;R+=`<h1>${A(x.meta.title)}</h1>`,R+='<dl class="meta">',R+=`<dt>Tags</dt><dd>${(x.meta.tags||[]).map(T=>`<span class="tag">${T}</span>`).join(" ")}</dd>`,R+=`<dt>Owner</dt><dd>${A(x.meta.owner)}</dd>`,R+=`<dt>Source</dt><dd><span class="tag">remote</span> ${A(w)}</dd>`,R+=`<dt>Updated</dt><dd>${x.meta.lastUpdated}</dd>`,R+="</dl>";let E=new Map;R+=et(x.content,E),p=F(x.meta.title,se(C),R)}}}if(!p&&n){let h=i.getAgentWiki(n);if(g==="/"||g===""){let y=h.rebuildIndex(),$=i.getAgentEntries(n),b=`<h1>${A(n)}</h1>`;if(b+='<div class="stats">',b+=`<div class="stat-card"><div class="number">${y.articles.length}</div><div class="label">Articles</div></div>`,b+=`<div class="stat-card"><div class="number">${$.length}</div><div class="label">Entries</div></div>`,b+="</div>",y.articles.length>0){b+="<h2>Articles</h2><table><tr><th>Title</th><th>Type</th><th>Updated</th></tr>";for(let w of y.articles)b+=`<tr><td><a href="/article/${encodeURIComponent(w.path)}">${A(w.title)}</a></td><td><span class="tag">${(w.tags||[]).slice(0,3).join(", ")}</span></td><td>${w.lastUpdated||""}</td></tr>`;b+="</table>"}p=F(n,Ee(h,n),b)}else if(g.startsWith("/article/")){let y=g.slice(9),$=h.readArticle(y);if($){let b=h.rebuildIndex(),w=new Map;for(let x of b.articles)w.set(x.title,x.path);let k=`<h1>${A($.meta.title)}</h1>`;k+='<dl class="meta">',k+=`<dt>Tags</dt><dd>${($.meta.tags||[]).map(x=>`<span class="tag">${x}</span>`).join(" ")}</dd>`,k+=`<dt>Updated</dt><dd>${$.meta.lastUpdated}</dd>`,$.meta.sources?.length&&(k+=`<dt>Sources</dt><dd>${$.meta.sources.length} entries</dd>`),k+="</dl>",k+=et($.content,w),p=F($.meta.title,Ee(h,n,y),k)}}else if(g==="/lint"){let y=h.lint(),$="<h1>Health Check</h1>";if(y.length===0)$+='<p style="color:green;">No issues.</p>';else{$+="<table><tr><th>Type</th><th>Article</th><th>Issue</th></tr>";for(let b of y)$+=`<tr><td><span class="tag">${b.type}</span></td><td>${A(b.article)}</td><td>${A(b.message)}</td></tr>`;$+="</table>"}p=F("Health Check",Ee(h,n),$)}else if(g==="/search"){let y=m.searchParams.get("q")||"",$=h.findRelevant(y,void 0,20),b=`<h1>Search: "${A(y)}"</h1>`;for(let w of $)b+=`<div class="entry-card"><h3><a href="/article/${encodeURIComponent(w.path)}">${A(w.meta.title)}</a></h3><p>${A(w.content.slice(0,200))}...</p></div>`;$.length===0&&(b+="<p>No results.</p>"),p=F(`Search: ${y}`,Ee(h,n),b)}else if(g.startsWith("/tag/")){let y=decodeURIComponent(g.slice(5)),$=h.findByTags([y],void 0,100),b=`<h1>Tag: <a class="tag" href="#">${A(y)}</a></h1>`;b+=`<p>${$.length} article${$.length===1?"":"s"} tagged <code>${A(y)}</code></p>`;for(let w of $)b+=`<div class="entry-card"><h3><a href="/article/${encodeURIComponent(w.path)}">${A(w.meta.title)}</a></h3><div class="meta-line">${ve((w.meta.tags||[]).slice(0,6))}</div></div>`;$.length===0&&(b+="<p>No articles with this tag.</p>"),p=F(`Tag: ${y}`,Ee(h,n),b)}}else{let h=r?await r.getRemoteAgents():[],y=i.summary(),$=new Map(y.map(k=>[k.agentId,k])),b=[],w=new Set;for(let k of y){let x=h.find(v=>v.agentId===k.agentId);x&&x.totalArticles>k.totalArticles?b.push({...x,totalEntries:Math.max(k.totalEntries,x.totalEntries),unabsorbed:Math.max(k.unabsorbed,x.unabsorbed)}):b.push(k),w.add(k.agentId)}for(let k of h)w.has(k.agentId)||(b.push(k),w.add(k.agentId));if(g==="/"||g==="")p=Ao(i,b,h);else if(g==="/entries")p=Io(i,b);else if(g==="/search")p=To(i,m.searchParams.get("q")||"",b);else if(g.startsWith("/tag/"))p=Ro(i,decodeURIComponent(g.slice(5)),b);else if(g.match(/^\/agent\/[^/]+\/tag\/.+/)){let k=g.match(/^\/agent\/([^/]+)\/tag\/(.+)$/);p=Po(i,decodeURIComponent(k[1]),decodeURIComponent(k[2]),b)}else if(g.startsWith("/agent/")){let k=g.slice(7),x=k.indexOf("/article/");if(x===-1)p=Co(i,k,b);else{let v=k.slice(0,x),C=k.slice(x+9);if(p=jo(i,v,C,b),!p&&r){let R=h.find(E=>E.agentId===v);if(R){let E=await r.getRemoteArticle(R.peerUrl,v,C);if(E){let T=`<div class="breadcrumb"><a href="/">Hub</a> / <span class="tag">remote @ ${A(R.nodeId)}</span> / ${A(v)}</div>`;T+=`<h1>${A(E.meta.title)}</h1>`,T+='<dl class="meta">',T+=`<dt>Tags</dt><dd>${(E.meta.tags||[]).map(D=>`<span class="tag">${D}</span>`).join(" ")}</dd>`,T+=`<dt>Source</dt><dd><span class="tag">remote</span> ${A(R.nodeId)}</dd>`,T+=`<dt>Updated</dt><dd>${E.meta.lastUpdated}</dd>`,T+="</dl>",T+=et(E.content,new Map),p=F(E.meta.title,se(b),T)}}}}}}if(!p){u=404;let h=n?Ee(i.getAgentWiki(n),n):se(i.summary());p=F("Not Found",h,"<h1>Page not found</h1>")}}catch(h){u=500,p=F("Error","",`<h1>Error</h1><pre>${A(h.message)}</pre>`)}d.writeHead(u,{"Content-Type":"text/html; charset=utf-8"}),d.end(p)}).listen(t)}function gn(e,t,n,o,s,i){return Eo(t,n,o,s,i)}function Eo(e,t,n,o,s){let i=t?`
131
- ## Worldview
132
-
133
- ${t}
134
- `:"",r=new Map;for(let a of n){let d=a.type||"untyped",m=r.get(d)||[];m.push(a),r.set(d,m)}let c=n.length===0?"":`
135
- ## Existing Articles (the current catalog)
136
-
137
- `+Array.from(r.entries()).sort().map(([a,d])=>`**${a}**
138
- `+d.sort((m,g)=>m.title.localeCompare(g.title)).map(m=>`- [[${m.title}]] \u2014 ${m.path}`).join(`
139
- `)).join(`
140
-
141
- `)+`
142
- `;return`You are compiling a personal wiki for the "${e}" agent in the Karpathy / Farzapedia pattern.
143
-
144
- ## The Pattern (read this first \u2014 it shapes every decision below)
145
-
146
- The wiki is a compounding knowledge base of interlinked markdown articles that a future LLM will read by walking the graph, NOT by bag-of-words search. Your job is to produce articles that another agent can navigate via \`type\` + wikilinks. If the wikilinks don't form a coherent graph, the wiki is useless regardless of how many tags you add.
147
-
148
- **Three rules, in priority order:**
149
-
150
- 1. **\`type\` is the organizational spine.** Every article has exactly one of: \`person | project | place | concept | event | decision | pattern\`. Choose the type BEFORE writing; if you can't pick one, the article probably isn't needed.
151
- 2. **\`related\` is the navigation surface.** Every article cross-references 2\u20135 other articles via \`[[Article Title]]\` wikilinks in the body AND lists them in the \`related\` frontmatter field. Links the reader could follow to learn more.
152
- 3. **Tags are a secondary hint, not a retrieval spine.** 2\u20134 specific tags max. Do NOT tag aggressively. Do NOT tag with dates unless the article is an \`event\`. Do NOT tag with generic terms ("deploy", "work") that will match half the corpus.
153
-
154
- ## Path convention
155
-
156
- Path reflects type: \`<type>s/<slug>.md\` where slug is a kebab-case title.
157
-
158
- - People: \`people/anis-marrouchi.md\`
159
- - Projects: \`projects/mtgl-system-v2.md\`
160
- - Concepts: \`concepts/staging-deployment.md\`
161
- - Events: \`events/YYYY-MM-DD-<slug>.md\` (date in path)
162
- - Decisions: \`decisions/<slug>.md\`
163
- - Patterns: \`patterns/<slug>.md\`
164
- - Places: \`places/<slug>.md\`
165
-
166
- ## How to process entries
167
-
168
- For each of the ${s} raw entries below, ask in this order:
169
-
170
- 1. **Does it extend an existing article?** If a person/project/concept mentioned in the entry already has an article in the catalog, produce an UPDATE with the full merged content. Prefer merging over proliferating.
171
- 2. **Does it deserve a new article?** Only if the subject is a persistent entity (a person, a project, a recurring concept, a specific event/decision) that future queries will need to find. Not every conversation deserves an article.
172
- 3. **Does it belong in an existing \`event\` or \`decision\`?** Most work entries fold into one of these.
173
- 4. **Can you skip it?** If the entry is small talk, a transient status ping, or already covered elsewhere \u2014 skip. The wiki is curated, not exhaustive.
174
-
175
- ## Writing standards
176
-
177
- - **Wikipedia-style, flat, factual, encyclopedic.** This is the agent's knowledge, not a diary. Write about the *role* of the entity in our work, not a product description.
178
- - **Synthesize, don't quote.** At most 2 short quoted lines per article.
179
- - **Organize by theme, not chronology.** An article about a person lists what they do, who they work with, how they prefer to be contacted \u2014 not a log of every interaction.
180
- - **Length: 20\u2013100 lines.** Articles exceeding 100 lines should split into multiple type-specific articles.
181
- - **Every paragraph earns its place.** Cut narrative filler. If you can remove a sentence without losing a fact, remove it.
182
-
183
- ## Wikilink discipline
184
-
185
- - Use \`[[Article Title]]\` **inside the body** every time another tracked entity is referenced.
186
- - List those same targets in the \`related\` frontmatter field.
187
- - If you reference something that has no article yet (\`[[New Thing]]\`), add "New Thing" to the output \`gaps\` array so the next pass can create it.
188
-
189
- ## Article frontmatter (exact shape \u2014 do not invent fields)
190
-
191
- \`\`\`yaml
192
- ---
193
- title: "Article Title"
194
- type: person | project | place | concept | event | decision | pattern
195
- related: ["Other Article", "Another Article"]
196
- tags: ["2-4-specific-tags", "no-dates-unless-event"]
197
- owner: ${e}
198
- access: public | shared | private
199
- created: YYYY-MM-DD
200
- last_updated: YYYY-MM-DD
201
- sources: ["entry-id-1", "entry-id-2"]
202
- ---
203
- \`\`\`
204
-
205
- Access guidance: default \`public\`; \`private\` only for sensitive credentials or agent-specific learnings; \`shared\` with specific agent IDs when the article matters only to a subset.
206
- ${i}${c}
207
- ## Gap Detection
208
-
209
- After compiling, populate a \`gaps\` array: wikilink targets you referenced but for which no article exists yet. Be specific:
210
-
211
- - Good: "Karim Rahmouni \u2014 coach for Tunis Padel club, mentioned twice but no \`people/\` article"
212
- - Bad: "We need more content about deployments"
213
-
214
- ## Output \u2014 valid JSON only, no markdown fencing
215
-
216
- \`\`\`
217
- {
218
- "articles": [
219
- {
220
- "path": "projects/mtgl-system-v2.md",
221
- "title": "MTGL System V2",
222
- "type": "project",
223
- "related": ["Anis Marrouchi", "Laravel", "Staging Deployment"],
224
- "tags": ["mtgl", "laravel", "react"],
225
- "content": "MTGL System V2 is [[Anis Marrouchi]]'s production Laravel + React app for \u2026",
226
- "sources": ["entry-id-1", "entry-id-2"]
227
- }
228
- ],
229
- "gaps": [
230
- "Karim Rahmouni \u2014 Tunis Padel coach, mentioned in entries but no people/ article"
231
- ]
232
- }
233
- \`\`\`
234
-
235
- ENTRIES (${s}):
236
-
237
- ${o}`}import{resolve as O,relative as pn,dirname as mn}from"path";import{execSync as xe}from"child_process";import{writeFileSync as Se,mkdirSync as de,readFileSync as nt,readdirSync as Oo,renameSync as tt,rmSync as No,existsSync as qe}from"fs";function X(e,t){return new Oe(e||O(process.cwd(),".agentx/wiki"),void 0,t||"graph")}function xt(e){return e==="graph"?"Knowledge Graph":e==="flat"?"Karpathy Flat":"Unified"}var J=new Do().name("wiki").description("wiki knowledge base management");J.command("status").description("show wiki status per agent").option("--dir <path>","wiki directory").option("--mode <mode>","graph (default, canonical) | unified | flat (legacy, back-compat)","graph").action(e=>{let t=e.mode,n=X(e.dir,t),o=n.summary(),i=n.getSharedStore().listEntries().length;console.log(),console.log(l.bold(` Wiki Hub Status [${xt(t)}]`)),console.log(),console.log(` Total raw entries: ${i}`),console.log(` Agents: ${o.length}`),console.log();for(let c of o){let a=c.unabsorbed>0?l.yellow(`${c.unabsorbed} unabsorbed`):l.green("up to date");if(console.log(` ${l.cyan(c.agentId)}`),console.log(` Entries: ${c.totalEntries} Articles: ${c.totalArticles} ${a}`),c.articles.length>0){for(let d of c.articles.slice(0,3))console.log(l.dim(` - ${d.title} [${(d.tags||[]).slice(0,3).join(", ")}]`));c.articles.length>3&&console.log(l.dim(` ... and ${c.articles.length-3} more`))}console.log()}let r=o.reduce((c,a)=>c+a.unabsorbed,0);r>0&&(console.log(l.dim(` ${r} raw entries on disk. Absorb is deprecated \u2014 see 'agentx wiki absorb --help'.`)),console.log())});J.command("lint").description("check wiki for issues per agent").option("--dir <path>","wiki directory").option("--mode <mode>","graph (default, canonical) | unified | flat (legacy, back-compat)","graph").option("--agent <id>","lint a specific agent's wiki").action(e=>{let t=X(e.dir,e.mode),n=e.agent?[e.agent]:t.listAgents(),o=0;console.log();for(let s of n){let r=t.getAgentWiki(s).lint();if(o+=r.length,r.length===0)console.log(` ${l.cyan(s)}: ${l.green("healthy")}`);else{console.log(` ${l.cyan(s)}: ${l.yellow(`${r.length} issues`)}`);for(let c of r){let a=c.type==="broken-link"?"x":c.type==="orphan"?"?":"!";console.log(` [${a}] ${l.dim(c.type)} ${c.article}: ${c.message}`)}}}console.log(),o===0&&console.log(l.green(" All agent wikis are healthy")),console.log()});J.command("absorb").description("compile unabsorbed entries into typed per-agent wiki articles (Farzapedia-faithful)").option("--dir <path>","wiki directory").option("--mode <mode>","graph (default, canonical) | unified | flat (legacy, back-compat)","graph").option("--agent <id>","absorb only this agent").option("--dry-run","preview without running").option("--max <n>","max entries per agent","20").action(async e=>{let t=e.mode,n=X(e.dir,t),o=e.agent?[e.agent]:n.listAgents(),s=parseInt(e.max),i=0;for(let r of o){let c=n.getUnabsorbedEntries(r).slice(0,s);if(c.length===0){console.log(` ${l.cyan(r)}: ${l.green("all absorbed")}`);continue}console.log(),console.log(l.bold(` ${l.cyan(r)}: ${c.length} entries to absorb`));for(let y of c.slice(0,3))console.log(l.dim(` [${y.date} via ${y.source}] ${y.content.slice(0,80)}...`));if(c.length>3&&console.log(l.dim(` ... and ${c.length-3} more`)),e.dryRun)continue;let a=n.getAgentWiki(r),d=a.rebuildIndex(),m=a.getWorldview()||n.getSharedStore().getWorldview()||"",g=c.map(y=>`--- ENTRY ${y.id} [${y.date} ${y.agentId} via ${y.source}] ---
238
- ${y.content}
239
- --- END ENTRY ---`).join(`
240
-
241
- `),p=gn(t,r,m,d.articles,g,c.length);console.log(l.dim(` Mode: ${xt(t)}`));let u=O(a.baseDir,"_tmp");de(u,{recursive:!0});let h=O(u,"absorb-prompt.txt");Se(h,p),console.log(l.dim(" Compiling with Claude..."));try{let y;try{y=xe(`cat '${h}' | claude -p - --output-format json --max-turns 3 --model sonnet --disallowedTools "Bash Read Write Edit Glob Grep Agent WebSearch WebFetch NotebookEdit"`,{encoding:"utf-8",timeout:3e5,maxBuffer:10*1024*1024})}catch(j){if(y=j.stdout||"",!y)throw j}let $=y;try{let j=JSON.parse(y);$=j.result||j.content||"",$||(console.log(l.dim(` Claude envelope keys: ${Object.keys(j).join(", ")}`)),console.log(l.dim(` is_error: ${j.is_error}, stop_reason: ${j.stop_reason}`)))}catch{}let b,w=[],k=$.indexOf("{"),x=$.indexOf("["),v=k>=0&&(x<0||k<x)?k:x;if(v===-1){console.log(l.red(" No JSON found in response")),console.log(l.dim($.slice(0,500)));continue}let C=$[v],R=C==="{"?"}":"]",E=0,T=-1;for(let j=v;j<$.length;j++)if($[j]===C)E++;else if($[j]===R&&(E--,E===0)){T=j+1;break}if(T===-1){console.log(l.red(" Unbalanced JSON"));continue}let D=$.slice(v,T);try{let j=JSON.parse(D);Array.isArray(j)?b=j:(b=j.articles||[],w=j.gaps||[])}catch(j){console.log(l.red(` JSON parse error: ${j.message}`)),console.log(l.dim(` First 300 chars: ${D.slice(0,300)}`));continue}for(let j of b){let ie=new Date().toISOString().slice(0,10);a.writeArticle(j.path,{title:j.title,type:j.type,related:Array.isArray(j.related)?j.related:void 0,tags:j.tags||[],owner:r,access:"public",created:ie,lastUpdated:ie,sources:j.sources||[]},j.content,r);let Ae=j.type?l.magenta(`[${j.type}]`)+" ":"",Q=Array.isArray(j.related)&&j.related.length?` \u2192 ${j.related.slice(0,3).join(", ")}${j.related.length>3?", \u2026":""}`:"";console.log(` ${l.green("+")} ${Ae}${j.path}: ${j.title}${l.dim(Q)}`);let ee=(j.tags||[]).slice(0,4).join(", ");ee&&console.log(l.dim(` tags: ${ee}`)),i++}if(w.length>0){console.log(),console.log(l.yellow(` Gaps detected (${w.length} missing pieces):`));for(let j of w)console.log(l.yellow(` ? ${j}`))}a.rebuildIndex()}catch(y){console.log(l.red(` Absorb failed: ${y.message?.slice(0,200)}`)),y.stderr&&console.log(l.dim(String(y.stderr).slice(0,300))),y.stdout&&console.log(l.dim("stdout: "+String(y.stdout).slice(0,300)))}}console.log(),e.dryRun?console.log(l.dim(" Dry run \u2014 no changes made")):i>0&&console.log(l.green(` ${i} articles compiled across ${o.length} agent(s)`)),console.log()});J.command("ab-test").description("side-by-side comparison: BM25 preload (old) vs agentic query (new) on real task-history messages").option("--dir <path>","wiki directory").option("--agent <id>","which agent to test against (required)").option("--history <path>","task-history root",O(process.cwd(),".agentx/task-history")).option("--n <n>","number of messages to sample (newest first)","10").option("--out <path>","markdown output file (default: stdout)").option("--selector-model <m>","agentic selector model","haiku").option("--synth-model <m>","agentic synthesis model","sonnet").action(async e=>{let{agenticQuery:t}=await import("./query-PKGYE5HR.js"),{readdirSync:n,readFileSync:o,existsSync:s}=await import("fs"),{resolve:i,join:r}=await import("path"),c=X(e.dir),a=e.agent;if(!a){console.log(l.red(" --agent <id> is required"));return}let d=c.getAgentWiki(a),m=parseInt(e.n),g=i(e.history,a);if(!s(g)){console.log(l.yellow(` no task-history at ${g}`));return}let p=[],u=n(g).sort().reverse();for(let $ of u){let b=r(g,$),w=[];try{w=n(b).filter(k=>k.endsWith(".json")).sort().reverse()}catch{continue}for(let k of w){if(p.length>=m)break;try{let x=JSON.parse(o(r(b,k),"utf-8")),v=x.message||x.task?.message;typeof v=="string"&&v.trim().length>10&&p.push({id:x.id||k.replace(".json",""),message:v.trim(),ts:x.at||x.timestamp||$})}catch{}}if(p.length>=m)break}if(p.length===0){console.log(l.yellow(` no task messages found under ${g}`));return}console.log(),console.log(l.bold(` A/B harness \u2014 ${p.length} messages from ${a}`)),console.log(l.dim(" OLD: findRelevant() BM25 over title+tags+content, top 3 truncated")),console.log(l.dim(` NEW: agenticQuery() catalog+wikilink walk (selector=${e.selectorModel}, synth=${e.synthModel})`)),console.log();let h=[`# Wiki A/B: ${a}`,"",`_Generated ${new Date().toISOString()} \xB7 ${p.length} messages sampled_`,"","For each message, the OLD BM25 retrieval (what Layer 10 used to preload) is shown alongside the NEW agentic query's selected articles + synthesized answer. Rate each pair on relevance 0\u20132 in the notes column.",""];for(let $=0;$<p.length;$++){let b=p[$];process.stdout.write(l.dim(` [${$+1}/${p.length}] querying... `));let k=d.findRelevant(b.message,a,3).map(v=>`- **${v.meta.title}** [${v.meta.type||"?"}] (${v.path})`).join(`
242
- `)||"_(none returned)_",x;try{let v=await t(b.message,d,a,{selectorModel:e.selectorModel,synthModel:e.synthModel});if(v.status!=="ok")x=`_(status: ${v.status}${v.error?" \u2014 "+v.error:""})_`;else{let C=v.citations.map(R=>`\`${R.title}\` [${R.type||"?"}]`).join(", ");x=`**Answer:**
243
-
244
- ${v.answer}
245
-
246
- **Citations:** ${C}`}}catch(v){x=`_(error: ${v.message})_`}console.log(l.dim("done")),h.push(`## ${$+1}. ${b.id}`,"",`> ${b.message.replace(/\n/g," ").slice(0,400)}${b.message.length>400?"\u2026":""}`,"","### OLD \u2014 BM25 preload (top 3)","",k,"","### NEW \u2014 agentic query","",x,"","| Path | Rating 0\u20132 | Notes |","|---|---|---|","| OLD | | |","| NEW | | |","")}let y=h.join(`
247
- `);e.out?((await import("fs")).writeFileSync(e.out,y),console.log(),console.log(l.green(` Wrote ${y.length} bytes to ${e.out}`))):(console.log(),console.log(y))});J.command("interview").description("interactive interview session \u2014 Q&A with an LLM synthesizer, produces one typed wiki article").option("--dir <path>","wiki directory").option("--agent <id>","which agent's wiki to write to (required)").option("--topic <text>","what to interview about (e.g. 'MTGL deployment procedure')").option("--type <t>","article type hint (person|project|place|concept|event|decision|pattern)").option("--model <m>","synthesis model","sonnet").option("--no-commit","show the draft but don't write").option("--answers <path>","non-interactive: one answer per line (same order as questions); last line = save|edit|scrap").action(async e=>{let t=await import("node:readline/promises"),{randomUUID:n}=await import("node:crypto"),{WIKI_ARTICLE_TYPES:o}=await import("./types-OIFU3KSQ.js"),s=e.answers?nt(e.answers,"utf-8").split(/\r?\n/).filter(I=>I.length>0):null,i=0;if(!e.agent){console.log(l.red(" --agent <id> is required. The article will be owned by this agent."));return}let r=X(e.dir),c;try{c=r.getAgentWiki(e.agent)}catch(I){console.log(l.red(` can't open wiki for agent "${e.agent}": ${I.message}`));return}let a=s?null:t.createInterface({input:process.stdin,output:process.stdout}),d=async I=>{if(s){let G=s[i++]??"";return process.stdout.write(I+l.dim(`[scripted] ${G.slice(0,80)}${G.length>80?"\u2026":""}`)+`
248
- `),G.trim()}return(await a.question(I)).trim()};console.log(),console.log(l.bold(" Wiki Interview")),console.log(l.dim(" One article per session. Empty answer = skip field. /done = stop & synthesize. /abort = quit.")),console.log();let m=e.topic||"";if(!m&&(m=await d(l.cyan(" Topic? ")+l.dim("(a person, a procedure, a past event, \u2026) ")),!m||m==="/abort")){a?.close();return}let g=(e.type||"").toLowerCase(),p=new Set(o);if(!p.has(g)){console.log(l.dim(` Types: ${o.join(" | ")}`));let I=await d(l.cyan(" Type? "));if(I==="/abort"){a?.close();return}p.has(I)?g=I:(console.log(l.yellow(` "${I}" isn't a valid type. Falling back to "concept".`)),g="concept")}let u={person:["Full name (and any aliases / Telegram handle / GitLab username)?","Role or title?","Which org or team are they part of (use the article title if you have one)?","Key responsibilities in OUR work?","Preferred channel to reach them (Telegram, email, in-person, \u2026)?","Who do they report to? (skip if none)","Notable decisions, events, or patterns they're involved in?","Anything quirky we should remember (timezone, working hours, style)?"],project:["One-line description \u2014 what is this project?","Who owns it? (person article title)","Tech stack or key tools involved?","Current status (active / paused / archived)?","Repo / URL / path on disk?","Two or three key decisions made so far?","Any open blockers, risks, or known issues?","Related projects or patterns?"],place:["Kind \u2014 office / server / environment / domain / URL?","Address \u2014 URL, IP, hostname, or physical location?","Owner (person or team)?","How to access it (SSH host, login, VPN, \u2026)?","What lives there (services, people, data)?","Known quirks (timezone, uptime, access restrictions)?"],concept:["Define it in one sentence \u2014 what does this term mean in OUR work?","Where did we first adopt it (origin story, date)?","Two or three concrete examples from our team?","What does it replace or extend (older concept, related concept)?","Common misunderstandings to avoid?"],event:["Date (YYYY-MM-DD)?","One-line summary \u2014 what happened?","Who was involved?","Timeline \u2014 key moments (start \u2192 resolution)?","Impact \u2014 what broke, what got delivered, who was affected?","Resolution / outcome?","Follow-ups or lessons captured?"],decision:["Context \u2014 what was the situation forcing a choice?","Options that were considered?","Chosen option \u2014 and one-sentence why?","Alternatives rejected \u2014 and one-sentence why not?","Who decided, and when (date)?","Is this reversible, and what would trigger a revisit?"],pattern:["Trigger \u2014 when should someone follow this pattern?","Inputs required (what do you need before starting)?","Steps in order \u2014 keep it tight, one line per step?","Expected output \u2014 how do you know it worked?","Common failure modes?","Related patterns, decisions, or runbooks?"]},h=u[g]||u.concept;console.log(),console.log(l.bold(` Topic: ${l.cyan(m)} \xB7 Type: ${l.magenta(g)}`)),console.log(l.dim(` ${h.length} questions. Skip with empty answer. /done to stop early.`)),console.log();let y=[],$=!1;for(let I=0;I<h.length;I++){let G=h[I],ae=await d(` ${l.dim(`[${I+1}/${h.length}]`)} ${G}
249
- ${l.green(">")} `);if(ae==="/abort"){$=!0;break}if(ae==="/done")break;ae!=="/skip"&&ae&&y.push({q:G,a:ae})}if(!$){let I=await d(`
250
- ${l.dim("Anything else worth capturing?")}
251
- ${l.green(">")} `);I&&I!=="/abort"&&I!=="/done"&&y.push({q:"Anything else worth capturing?",a:I})}if($||y.length===0){console.log(l.yellow(`
252
- Nothing captured. Exiting.`)),a?.close();return}let w=c.listArticles(e.agent).slice(0,40).map(I=>`- ${I.meta.title} [${I.meta.type||"?"}]`).join(`
253
- `),k=["You are compiling one Farzapedia-style wiki article from an operator interview transcript.","",`**Topic:** ${m}`,`**Type:** ${g}`,`**Owning agent:** ${e.agent}`,"","## Transcript",...y.map(I=>`
254
- Q: ${I.q}
255
- A: ${I.a}`),"","## Existing articles (use [[Title]] wikilinks when referencing these)",w||"(none yet)","","## Output rules","- Output EXACTLY one markdown file: frontmatter (---) + body. No code fences, no preamble.","- Frontmatter fields in this exact order: title, type, related, tags, owner, access, created, last_updated, sources.",' - `title`: "\u2026"',` - \`type\`: ${g}`,' - `related`: ["Article Title", \u2026] (titles that exist in the catalog above; drop if none)'," - `tags`: 2-4 specific tags, lowercase-kebab \u2014 no dates unless type=event",` - \`owner\`: ${e.agent}`," - `access`: public (unless the content is sensitive)",` - \`created\`: ${new Date().toISOString().slice(0,10)}`,` - \`last_updated\`: ${new Date().toISOString().slice(0,10)}`,` - \`sources\`: ["interview-${new Date().toISOString().slice(0,10)}"]`,"- Body: 20-80 lines, Wikipedia-style, organized by theme (not by question), synthesized (not verbatim Q&A).","- Use [[Title]] inline every time you reference something that has an article in the catalog.","- Do NOT invent facts. If the transcript didn't cover something, skip that sub-topic."].join(`
256
- `),x=O(c.baseDir,"_tmp");de(x,{recursive:!0});let v=O(x,`interview-${n().slice(0,8)}.txt`);Se(v,k),console.log(),console.log(l.dim(` Synthesizing draft with ${e.model}...`));let C="";try{let I=`cat '${v}' | claude -p - --output-format json --max-turns 1 --model ${e.model} --disallowedTools "Bash Read Write Edit Glob Grep Agent WebSearch WebFetch NotebookEdit"`,G=xe(I,{encoding:"utf-8",timeout:18e4,maxBuffer:4*1024*1024});try{let ae=JSON.parse(G);C=String(ae.result||ae.content||"")}catch{C=G}}catch(I){console.log(l.red(` synthesis failed: ${I.message?.slice(0,200)}`)),a?.close();return}C=C.trim();let R=C.match(/```(?:markdown|md)?\s*\n([\s\S]*?)\n```\s*$/);R&&(C=R[1].trim());let E=C.indexOf("---");E>0&&(C=C.slice(E));let T=C.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);if(!T){console.log(l.red(" LLM output is not a valid frontmatter+body article:")),console.log(C.slice(0,600)),a?.close();return}let D={};for(let I of T[1].split(`
257
- `)){let G=I.match(/^(\w+):\s*(.+)$/);G&&(D[G[1]]=G[2].trim().replace(/^"(.*)"$/,"$1"))}let j=T[2],ie=D.title||m,Ae=ie.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,60),ee=`${{person:"people",project:"projects",place:"places",concept:"concepts",event:"events",decision:"decisions",pattern:"patterns"}[g]||g+"s"}/${Ae}.md`;console.log(),console.log(l.bold(" === Draft preview ===")),console.log(l.dim(` Path: ${ee}`)),console.log(),console.log(C),console.log();let Ce=await d(` ${l.green("save")} / ${l.yellow("edit (opens $EDITOR)")} / ${l.red("scrap")} ? `);if(a?.close(),Ce==="scrap"||Ce==="/abort"){console.log(l.dim(" Discarded."));return}if(!e.commit){console.log(l.dim(" --no-commit: not writing.")),console.log(l.dim(` Draft kept at ${v} (prompt only; no article file)`));return}let je=j,ge={title:ie,type:D.type||g,related:D.related?D.related.replace(/^\[|\]$/g,"").split(",").map(I=>I.trim().replace(/^"(.*)"$/,"$1")).filter(Boolean):void 0,tags:D.tags?D.tags.replace(/^\[|\]$/g,"").split(",").map(I=>I.trim().replace(/^"(.*)"$/,"$1")).filter(Boolean):[],owner:e.agent,access:D.access||"public",created:D.created||new Date().toISOString().slice(0,10),lastUpdated:D.last_updated||new Date().toISOString().slice(0,10),sources:[`interview-${new Date().toISOString().slice(0,10)}`]};if(Ce==="edit"){let I=process.env.EDITOR||"vi",G=O(x,`draft-${n().slice(0,8)}.md`);Se(G,C);try{xe(`${I} '${G}'`,{stdio:"inherit"});let ot=He("fs").readFileSync(G,"utf-8").match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);if(ot){let pe={};for(let De of ot[1].split(`
258
- `)){let st=De.match(/^(\w+):\s*(.+)$/);st&&(pe[st[1]]=st[2].trim().replace(/^"(.*)"$/,"$1"))}je=ot[2],ge={...ge,title:pe.title||ge.title,type:pe.type||ge.type,access:pe.access||ge.access,related:pe.related?pe.related.replace(/^\[|\]$/g,"").split(",").map(De=>De.trim().replace(/^"(.*)"$/,"$1")).filter(Boolean):ge.related,tags:pe.tags?pe.tags.replace(/^\[|\]$/g,"").split(",").map(De=>De.trim().replace(/^"(.*)"$/,"$1")).filter(Boolean):ge.tags}}}catch(ae){console.log(l.red(` editor failed: ${ae.message}`));return}}if(!c.writeArticle(ee,ge,je,e.agent)){console.log(l.red(" write failed (permission?)"));return}c.rebuildIndex(),console.log(l.green(` \u2713 ${ee} saved.`));let yn=c.extractWikilinks(je),At=new Map;for(let I of c.listArticles(e.agent))At.set(I.meta.title.toLowerCase(),!0);let Ct=yn.filter(I=>!At.has(I.toLowerCase()));if(Ct.length){console.log(),console.log(l.bold(" Gaps referenced but not yet covered:"));for(let I of Ct.slice(0,8))console.log(l.dim(` - ${I}`));console.log(l.dim(` Run \`agentx wiki interview --agent ${e.agent} --topic "<title>"\` for any of these.`))}});J.command("quiz").description("reverse interview \u2014 ask the wiki questions and patch cited articles with corrections, additions, or links").option("--dir <path>","wiki directory").option("--agent <id>","which agent's wiki to quiz (required)").option("--selector-model <m>","candidate-selection model","haiku").option("--synth-model <m>","answer synthesis model","sonnet").option("--patch-model <m>","article patch model","sonnet").option("--rounds <n>","stop after N rounds","20").option("--script <path>","non-interactive: question line + /verdict line per entry, entries separated by blank lines").option("--no-commit","show proposed patches but don't write").option("--out <path>","write a session transcript as markdown").action(async e=>{let t=await import("node:readline/promises"),{randomUUID:n}=await import("node:crypto"),{agenticQuery:o}=await import("./query-PKGYE5HR.js");if(!e.agent){console.log(l.red(" --agent <id> is required."));return}let s=X(e.dir),i;try{i=s.getAgentWiki(e.agent)}catch(u){console.log(l.red(` can't open wiki for agent "${e.agent}": ${u.message}`));return}let r=null;if(e.script){let h=nt(e.script,"utf-8").split(/\n\s*\n/).map(y=>y.trim()).filter(Boolean);r=[];for(let y of h){let $=y.split(/\r?\n/).filter(k=>k.trim());if($.length<2)continue;let b=$[0].replace(/^Q:\s*/i,"").trim(),w=$.slice(1).join(" ").trim();b&&w&&r.push({q:b,verdict:w})}}let c=r?null:t.createInterface({input:process.stdin,output:process.stdout}),a=async u=>c?(await c.question(u)).trim():"",d=Math.min(parseInt(e.rounds)||20,r?.length??999),m=e.commit!==!1;console.log(),console.log(l.bold(" Wiki Quiz")),console.log(l.dim(` Agent: ${e.agent} \xB7 Rounds: ${d} \xB7 ${m?"commit":"dry-run"} \xB7 ${r?`scripted (${r.length})`:"interactive"}`)),console.log(l.dim(" Verdicts: /ok /correct <note> /add <note> /link <url> /skip /done")),console.log();let g=[],p=0;for(let u=0;u<d;u++){let h,y;if(r){let Q=r[u];if(!Q)break;h=Q.q,y=Q.verdict,console.log(l.cyan(` [${u+1}] Q: `)+h)}else{if(h=await a(l.cyan(` [${u+1}] Q: `)),!h||h==="/done"||h==="/abort")break;y=""}process.stdout.write(l.dim(" querying... "));let $;try{$=await o(h,i,e.agent,{selectorModel:e.selectorModel,synthModel:e.synthModel})}catch(Q){console.log(l.red(`failed: ${Q.message?.slice(0,120)}`));continue}if(console.log(l.dim("done")),$.status!=="ok")console.log(l.yellow(` (status: ${$.status}${$.error?" \u2014 "+$.error:""})`));else{let Q=$.citations.map(ee=>`${ee.title} [${ee.type||"?"}]`).join(" \xB7 ");console.log(),console.log(l.dim(" A: ")+$.answer.replace(/\n/g,`
259
- `)),console.log(l.dim(" citations: "+Q))}r||(y=await a(l.cyan(" verdict > "))),console.log(l.dim(" verdict: ")+y);let b={q:h,answer:$.answer,citations:$.citations,verdict:y},w=y.match(/^(\/ok|\/correct|\/add|\/link|\/skip)(?:\s+(.+))?$/i);if(!w){console.log(l.yellow(" (verdict not recognised \u2014 treating as /skip)")),g.push(b);continue}let k=w[1].toLowerCase(),x=(w[2]||"").trim();if(g.push(b),k==="/ok"||k==="/skip")continue;if(!x){console.log(l.yellow(` ${k} without note \u2014 skipping.`));continue}let v=$.citations[0];if(!v){console.log(l.yellow(" no cited article to patch."));continue}let C=i.readArticle(v.path);if(!C){console.log(l.red(` can't read ${v.path} to patch.`));continue}console.log(l.dim(` patching ${v.title} (${v.path})...`));let R=Mo(C.content,k,x,C.meta.title,C.meta.type),E=O(i.baseDir,"_tmp");de(E,{recursive:!0});let T=O(E,`quiz-patch-${n().slice(0,8)}.txt`);Se(T,R);let D="";try{let Q=`cat '${T}' | claude -p - --output-format json --max-turns 1 --model ${e.patchModel} --disallowedTools "Bash Read Write Edit Glob Grep Agent WebSearch WebFetch NotebookEdit"`,ee=xe(Q,{encoding:"utf-8",timeout:12e4,maxBuffer:4*1024*1024});try{let je=JSON.parse(ee);D=String(je.result||je.content||"")}catch{D=ee}let Ce=D.trim().match(/```(?:markdown|md)?\s*\n([\s\S]*?)\n```\s*$/);Ce&&(D=Ce[1].trim()),D=D.trim()}catch(Q){console.log(l.red(` patch failed: ${Q.message?.slice(0,120)}`));continue}if(!D||D.length<20){console.log(l.yellow(" patch returned nothing usable."));continue}let j=C.content.split(`
260
- `).length,ie=D.split(`
261
- `).length;if(console.log(l.dim(` diff: ${j} \u2192 ${ie} lines (${ie-j>=0?"+":""}${ie-j})`)),!m){console.log(l.dim(" (--no-commit: patch not written)")),b.patched=v.path+" (dry)";continue}if(!i.writeArticle(v.path,{...C.meta,lastUpdated:new Date().toISOString().slice(0,10)},D,e.agent)){console.log(l.red(" write failed (permission?)"));continue}p++,b.patched=v.path,console.log(l.green(` \u2713 patched ${v.path}`))}if(c&&c.close(),p>0&&m&&i.rebuildIndex(),console.log(),console.log(l.bold(` Session done: ${g.length} rounds, ${p} patches applied.`)),e.out){let u=[`# Wiki Quiz \u2014 ${e.agent}`,"",`_Generated ${new Date().toISOString()} \xB7 ${g.length} rounds \xB7 ${p} patches_`,""];for(let h=0;h<g.length;h++){let y=g[h];u.push(`## ${h+1}. Q: ${y.q}`,"",`**A:** ${y.answer}`,"",`**Citations:** ${y.citations.map($=>`\`${$.title}\``).join(", ")||"_(none)_"}`,"",`**Verdict:** \`${y.verdict}\``+(y.patched?` \u2192 patched \`${y.patched}\``:""),"")}Se(e.out,u.join(`
262
- `)),console.log(l.dim(` Transcript written to ${e.out}`))}});J.command("edit <agent> <titleOrPath>").description("open an article in $EDITOR (resolves by title or path), rebuild catalog on exit").option("--dir <path>","wiki directory").option("--editor <cmd>","override $EDITOR for this run").action((e,t,n)=>{let o=X(n.dir),s;try{s=o.getAgentWiki(e)}catch(d){console.log(l.red(` can't open wiki for agent "${e}": ${d.message}`));return}let i=fn(s,t,e);if(!i){console.log(l.yellow(` no article matches "${t}" in ${e}'s wiki.`)),console.log(l.dim(` Tip: ${l.green("agentx wiki status")} lists agents; ${l.green(`agentx wiki search "term" --agent ${e}`)} finds by content.`));return}let r=O(s.baseDir,i),c=n.editor||process.env.EDITOR||"vi";console.log(l.dim(` opening ${i} in ${c}...`));try{xe(`${c} '${r}'`,{stdio:"inherit"})}catch(d){console.log(l.red(` editor exited with error: ${d.message?.slice(0,150)}`));return}let a=s.readArticle(i);if(a){let d=new Set,m=s.extractWikilinks(a.content).filter(u=>d.has(u)?!1:(d.add(u),!0)),g=a.meta.related||[];(m.some(u=>!g.includes(u))||g.some(u=>!m.includes(u)))&&(s.writeArticle(i,{...a.meta,related:m.length?m:void 0,lastUpdated:new Date().toISOString().slice(0,10)},a.content,e),console.log(l.dim(` (synced frontmatter.related from body: ${m.length} wikilink${m.length===1?"":"s"})`)))}try{s.rebuildIndex(),console.log(l.green(` \u2713 ${i} saved. Catalog rebuilt.`))}catch(d){console.log(l.yellow(` saved, but catalog rebuild failed: ${d.message?.slice(0,100)}`))}});J.command("patch <agent> <titleOrPath> <instruction>").description("LLM-patch an article from a free-form instruction; shows diff + confirms before writing").option("--dir <path>","wiki directory").option("--patch-model <m>","patch model","sonnet").option("--yes","skip confirmation and write immediately").option("--no-commit","show the patched body but don't write").action(async(e,t,n,o)=>{let s=await import("node:readline/promises"),{randomUUID:i}=await import("node:crypto"),r=X(o.dir),c;try{c=r.getAgentWiki(e)}catch(v){console.log(l.red(` can't open wiki for agent "${e}": ${v.message}`));return}let a=fn(c,t,e);if(!a){console.log(l.yellow(` no article matches "${t}" in ${e}'s wiki.`));return}let d=c.readArticle(a);if(!d){console.log(l.red(` can't read ${a}.`));return}let m=Wo(d.content,d.meta.title,d.meta.type,n),g=O(c.baseDir,"_tmp");de(g,{recursive:!0});let p=O(g,`patch-${i().slice(0,8)}.txt`);Se(p,m),console.log(),console.log(l.dim(` Target: ${d.meta.title} (${a})`)),console.log(l.dim(` Patching with ${o.patchModel}...`));let u="";try{let v=`cat '${p}' | claude -p - --output-format json --max-turns 1 --model ${o.patchModel} --disallowedTools "Bash Read Write Edit Glob Grep Agent WebSearch WebFetch NotebookEdit"`,C=xe(v,{encoding:"utf-8",timeout:12e4,maxBuffer:4*1024*1024});try{let E=JSON.parse(C);u=String(E.result||E.content||"")}catch{u=C}let R=u.trim().match(/```(?:markdown|md)?\s*\n([\s\S]*?)\n```\s*$/);R&&(u=R[1].trim()),u=u.trim()}catch(v){console.log(l.red(` patch failed: ${v.message?.slice(0,150)}`));return}if(!u||u.length<20){console.log(l.yellow(" patch returned nothing usable."));return}let h=d.content.split(`
263
- `).length,y=u.split(`
264
- `).length;if(console.log(),console.log(l.bold(" === Patched preview ===")),console.log(l.dim(` diff: ${h} \u2192 ${y} lines (${y-h>=0?"+":""}${y-h})`)),console.log(),console.log(u),console.log(),!o.commit){console.log(l.dim(" --no-commit: preview only, not written."));return}let $=!!o.yes;if(!$){let v=s.createInterface({input:process.stdin,output:process.stdout}),C=(await v.question(l.cyan(" apply? [y/N] "))).trim().toLowerCase();v.close(),$=C==="y"||C==="yes"}if(!$){console.log(l.dim(" aborted."));return}let b=new Set,w=c.extractWikilinks(u).filter(v=>b.has(v)?!1:(b.add(v),!0));if(!c.writeArticle(a,{...d.meta,related:w.length?w:void 0,lastUpdated:new Date().toISOString().slice(0,10)},u,e)){console.log(l.red(" write failed (permission?)"));return}c.rebuildIndex();let x=(d.meta.related||[]).filter(v=>!w.includes(v));x.length&&console.log(l.dim(` (pruned stale related: ${x.slice(0,3).join(", ")}${x.length>3?", \u2026":""})`)),console.log(l.green(` \u2713 ${a} patched.`))});function fn(e,t,n){let o=t.trim(),s=e.listArticles(n),i=o.toLowerCase(),r=s.find(d=>d.meta.title.toLowerCase()===i);if(r)return r.path;if(o.includes("/")||o.endsWith(".md")){let d=o.endsWith(".md")?o:`${o}.md`,m=s.find(g=>g.path===d||g.path===o);if(m)return m.path}let c=o.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,""),a=s.find(d=>{let m=d.meta.title.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"");return m===c||m.includes(c)||d.path.toLowerCase().includes(c)});return a?a.path:null}function Wo(e,t,n,o){return["You are editing a single wiki article to incorporate an operator instruction.","",`## Article: "${t}" (type: ${n||"untyped"})`,"","<article-body>",e,"</article-body>","","## Operator instruction",o,"","## Rules","- Output ONLY the modified article body (content between the --- frontmatter markers).","- Do NOT output frontmatter (title, type, tags, etc.) \u2014 the serializer handles that.","- Do NOT wrap the output in code fences.",`- Do NOT prepend any preamble like "Here's the updated article:".`,"- Preserve existing wikilinks, markdown formatting, and section structure unless the instruction requires changing them.","- Make the MINIMUM edit that satisfies the instruction \u2014 don't rewrite untouched sections.","- If the instruction is ambiguous or can't be applied, return the original body unchanged."].join(`
265
- `)}function Mo(e,t,n,o,s){let i={"/correct":`The operator says the article contains an error:
266
-
267
- ${n}
268
-
269
- Edit the article to correct ONLY this mistake. Do not rewrite untouched sections. Keep the existing prose style. If the correction invalidates a larger passage, revise the minimum that must change.`,"/add":`The operator wants to add this detail:
270
-
271
- ${n}
272
-
273
- Incorporate it into the most relevant existing section, or create a short new section if nothing fits. Keep the prose encyclopedic and under 100 additional words.`,"/link":`The operator wants to add this resource:
274
-
275
- ${n}
276
-
277
- Add it as a Markdown link in the most relevant section, or append a "## References" section at the end if none exists. If it looks like a URL, use it as-is; otherwise treat it as a wikilink target and use [[${n}]].`};return["You are editing a single wiki article to incorporate operator feedback.","",`## Article: "${o}" (type: ${s||"untyped"})`,"","<article-body>",e,"</article-body>","","## Edit instruction",i[t]||`Make a minimal edit per: ${n}`,"","## Rules","- Output ONLY the modified article body (content between the --- frontmatter markers).","- Do NOT output frontmatter (title, type, tags, etc.) \u2014 the serializer handles that.","- Do NOT wrap the output in code fences.",`- Do NOT prepend any preamble like "Here's the updated article:".`,"- Preserve existing wikilinks, markdown formatting, and section structure unless the edit requires changing them.","- If no meaningful edit can be made from the operator's feedback, return the original body unchanged."].join(`
278
- `)}J.command("prune").description("collapse legacy flat/unified mode dirs into graph/ (dedup by title; losers archived)").option("--dir <path>","wiki directory").option("--agent <id>","prune only this agent's wiki").option("--commit","execute moves + archives (default: dry-run)").action(async e=>{let t=X(e.dir),n=e.agent?[e.agent]:t.listAgents(),o=!!e.commit,s=e.dir?O(e.dir):O(process.cwd(),".agentx/wiki"),i="graph",r=["flat","unified"];console.log(),console.log(l.bold(o?" Pruning (commit)":" Pruning (dry-run)")),console.log(l.dim(` Canonical: ${i}/ \xB7 Legacy to collapse: ${r.join("/, ")}/`));let c=0,a=0,d=0,m=0;for(let g of n){let p=O(s,"agents",g);if(!qe(p))continue;let u=O(p,i),h=r.map(w=>({mode:w,dir:O(p,w)})).filter(w=>qe(w.dir));if(h.length===0)continue;let y=new Map;vt(u,w=>{let k=pn(u,w);if(k.startsWith("_")||k.startsWith("raw/")||k.includes("_tmp/"))return;let x=un(nt(w,"utf-8"));x?.meta.title&&y.set(x.meta.title.trim().toLowerCase(),{relPath:k,type:x.meta.type,lastUpdated:x.meta.lastUpdated||""})});let $=[];for(let w of h)vt(w.dir,k=>{let x=pn(w.dir,k);if(x.startsWith("_")||x.startsWith("raw/")||x.includes("_tmp/"))return;let v=un(nt(k,"utf-8"));v?.meta.title&&$.push({mode:w.mode,relPath:x,absPath:k,title:v.meta.title.trim(),type:v.meta.type,lastUpdated:v.meta.lastUpdated||""})});if(c+=$.length,$.length===0)continue;console.log();let b=h.map(w=>w.mode).join("+");console.log(l.bold(` ${l.cyan(g)}: ${$.length} legacy article(s) across ${b}`));for(let w of $){let k=w.title.toLowerCase(),x=y.get(k),v;x?!x.type&&w.type||!!x.type==!!w.type&&w.lastUpdated&&x.lastUpdated&&w.lastUpdated>x.lastUpdated?v="upgrade":v="archive":v="promote";let C=w.type?l.magenta(`[${w.type}]`):l.yellow("[untyped]"),R=v==="promote"?l.green("\u2192 promote "):v==="upgrade"?l.cyan("\u27F3 upgrade "):l.dim("\u2717 archive "),E=w.relPath.length>46?w.relPath.slice(0,43)+"...":w.relPath.padEnd(46);if(console.log(` ${R} ${C} ${l.dim(w.mode+"/")}${E}`),v==="upgrade"&&console.log(l.dim(` (replaces ${x.relPath}, old version kept under _versions/)`)),!o){v==="promote"?a++:v==="upgrade"?d++:m++;continue}try{if(v==="promote"){let T=O(u,w.relPath);qe(T)?(console.log(l.yellow(` ! target path already exists, archiving instead: ${w.relPath}`)),hn(p,w),m++):(de(mn(T),{recursive:!0}),tt(w.absPath,T),y.set(k,{relPath:w.relPath,type:w.type,lastUpdated:w.lastUpdated}),a++)}else if(v==="upgrade"){let T=O(u,x.relPath);if(qe(T)){let D=O(p,"_versions",x.relPath.replace(/\.md$/,""));de(D,{recursive:!0});let j=new Date().toISOString().replace(/[:.]/g,"-");tt(T,O(D,`${j}.md`))}de(mn(T),{recursive:!0}),tt(w.absPath,T),y.set(k,{relPath:x.relPath,type:w.type,lastUpdated:w.lastUpdated}),d++}else hn(p,w),m++}catch(T){console.log(l.red(` ! ${v} failed: ${T.message?.slice(0,120)}`))}}if(o){for(let w of h)try{No(w.dir,{recursive:!0,force:!0})}catch{}try{t.getAgentWiki(g).rebuildIndex()}catch(w){console.log(l.dim(` (catalog rebuild skipped: ${w.message?.slice(0,80)})`))}}}console.log(),console.log(l.dim(` Legacy scanned: ${c}`)),o?(console.log(l.green(` Promoted: ${a} \xB7 Upgraded canonical: ${d} \xB7 Archived: ${m}`)),console.log(l.dim(" Archived losers preserved under <agent>/_versions/. Legacy mode dirs deleted."))):(console.log(l.dim(` Would: promote ${a} \xB7 upgrade ${d} \xB7 archive ${m}`)),console.log(l.dim(" Dry-run \u2014 add --commit to execute"))),console.log()});function vt(e,t){if(qe(e))for(let n of Oo(e,{withFileTypes:!0})){let o=O(e,n.name);n.isDirectory()?vt(o,t):n.isFile()&&n.name.endsWith(".md")&&t(o)}}function un(e){let t=e.match(/^---\n([\s\S]*?)\n---/);if(!t)return null;let n={};for(let o of t[1].split(`
279
- `)){let s=o.match(/^(\w+):\s*(.+)$/);if(!s)continue;let i=s[1]==="last_updated"?"lastUpdated":s[1];n[i]=s[2].trim().replace(/^"(.*)"$/,"$1")}return{meta:n}}function hn(e,t){let n=O(e,"_versions",`legacy-${t.mode}`,t.relPath.replace(/\.md$/,""));de(n,{recursive:!0});let o=new Date().toISOString().replace(/[:.]/g,"-");tt(t.absPath,O(n,`${o}.md`))}J.command("migrate").description("backfill type + related on legacy articles (one-shot)").option("--dir <path>","wiki directory").option("--agent <id>","migrate only this agent's articles").option("--commit","write changes (default: dry-run, reports what would change)").option("--batch <n>","articles per LLM call","10").option("--max <n>","cap articles this run (for spot-checks)").option("--model <m>","classifier model","sonnet").action(async e=>{let t=X(e.dir),n=e.agent?[e.agent]:t.listAgents(),o=Math.max(1,parseInt(e.batch)||10),s=e.max?parseInt(e.max):1/0,i=!!e.commit;console.log(),console.log(l.bold(i?" Migrating (commit)":" Migrating (dry-run)")),console.log(l.dim(` Batch size: ${o} \xB7 Model: ${e.model}`)),console.log();let r=0,c=0,a=0;for(let d of n){let m=t.getAgentWiki(d),g=m.listArticles(d),p=g.filter(b=>!b.meta.type);if(r+=g.length,c+=p.length,p.length===0){console.log(` ${l.cyan(d)}: ${l.green("all typed")} (${g.length} articles)`);continue}let u=Math.max(0,s-a),h=Math.min(p.length,u);if(h===0){console.log(l.dim(` ${d}: skipped (--max reached)`));continue}console.log(),console.log(l.bold(` ${l.cyan(d)}: ${h}/${p.length} untyped articles`));let y=m.baseDir,$=O(y,"_tmp");de($,{recursive:!0});for(let b=0;b<h;b+=o){let w=p.slice(b,Math.min(b+o,h)),k=Uo(w),x=O($,"migrate-prompt.txt");Se(x,k);let v;try{let C=`cat '${x}' | claude -p - --output-format json --max-turns 1 --model ${e.model} --disallowedTools "Bash Read Write Edit Glob Grep Agent WebSearch WebFetch NotebookEdit"`,R=xe(C,{encoding:"utf-8",timeout:12e4,maxBuffer:4*1024*1024}),E=JSON.parse(R),D=String(E.result||E.content||"").match(/\[[\s\S]*\]/);if(!D)throw new Error("no JSON array in classifier response");v=JSON.parse(D[0])}catch(C){console.log(l.red(` LLM classify failed: ${C.message?.slice(0,150)}`));continue}for(let C of w){let R=v.find(Ae=>Ae.path===C.path),E=Lo(R?.type);if(!R||!E){console.log(l.yellow(` ? ${C.path}: unclassified${R?` (got "${R.type}")`:""}`));continue}let T=m.extractWikilinks(C.content),D={...C.meta,type:E.type,related:T.length?T:void 0,lastUpdated:new Date().toISOString().slice(0,10)},j=T.length?l.dim(` \u2192 ${T.slice(0,3).join(", ")}${T.length>3?", \u2026":""}`):"",ie=E.alias?l.dim(` (from "${E.alias}")`):"";console.log(` ${l.green("+")} ${l.magenta("["+E.type+"]")}${ie} ${C.path}${j}`),i&&(m.writeArticle(C.path,D,C.content,d),a++)}}i&&m.rebuildIndex()}console.log(),console.log(l.dim(` Scanned ${r} articles; ${c} needed migration`)),console.log(i?l.green(` Applied ${a} patches.`):l.dim(" Dry-run \u2014 add --commit to write changes.")),console.log()});function Lo(e){if(!e)return null;let t=String(e).trim().toLowerCase().replace(/^[\s"'`]+|[\s"'`]+$/g,"");if(!t)return null;let o={person:"person",project:"project",place:"place",concept:"concept",event:"event",decision:"decision",pattern:"pattern",people:"person",projects:"project",places:"place",concepts:"concept",events:"event",decisions:"decision",patterns:"pattern",agent:"project",bot:"project",service:"project",product:"project",tool:"project",repo:"project",repository:"project",app:"project",application:"project",team:"project",organization:"project",company:"project",issue:"event",mr:"event","merge-request":"event",merge_request:"event",ticket:"event",incident:"event",deploy:"event",deployment:"event",launch:"event",release:"event",process:"pattern",procedure:"pattern",workflow:"pattern",runbook:"pattern",recipe:"pattern",template:"pattern",sop:"pattern",infrastructure:"place",infra:"place",server:"place",environment:"place",env:"place",location:"place",host:"place",decisionrecord:"decision",adr:"decision","decision-record":"decision"}[t];return o?o===t?{type:o}:{type:o,alias:t}:null}function Uo(e){let t=e.map((n,o)=>{let s=n.content.replace(/\s+/g," ").slice(0,400),i=(n.meta.tags||[]).slice(0,8).join(", ");return`${o+1}. path: "${n.path}"
280
- title: "${n.meta.title}"
281
- tags: [${i}]
282
- body: ${s}${n.content.length>400?"\u2026":""}`}).join(`
283
-
284
- `);return`Classify each of these ${e.length} wiki articles into EXACTLY ONE type from this closed set (no synonyms, no plurals, no new values):
285
-
286
- person | project | place | concept | event | decision | pattern
287
-
288
- What each type means:
289
- - person: an individual human (team member, stakeholder, contact)
290
- - project: a named initiative, repo, product, service, bot, agent, team, or org
291
- - place: a physical or logical location (office, server, environment, infrastructure)
292
- - concept: a recurring idea, philosophy, methodology, or thinking pattern
293
- - event: a specific dated thing that happened (incident, deploy, launch, GitLab issue, MR)
294
- - decision: a specific choice made and why (architecture decision, policy, ADR)
295
- - pattern: a reusable workflow, template, recipe, procedure, runbook, or SOP
296
-
297
- Explicit mappings (you WILL be tempted to volunteer these \u2014 don't; use the right-hand value):
298
- - "agent" / "bot" / "service" / "repo" / "team" \u2192 project
299
- - "issue" / "MR" / "ticket" / "incident" / "deploy" \u2192 event
300
- - "process" / "procedure" / "runbook" / "workflow" \u2192 pattern
301
- - "infrastructure" / "server" / "environment" \u2192 place
302
- - "ADR" / "decision record" \u2192 decision
303
-
304
- Rules:
305
- - Return EXACTLY one type per article, by path, from the 7 allowed values above.
306
- - Do not invent new types. Do not return plurals. Do not return synonyms.
307
- - If the title is a person's name, it's person. If it's a repo/product/bot name, it's project. If it's dated + past tense, it's event.
308
- - If you genuinely cannot decide, pick "concept" as the fallback.
309
- - Output ONLY valid JSON, no markdown fencing, no prose.
310
-
311
- Articles:
312
-
313
- ${t}
314
-
315
- Output (JSON array, one entry per input article, same order):
316
- [
317
- {"path": "path/to/article.md", "type": "project"},
318
- ...
319
- ]`}J.command("entries").description("list raw entries").option("--dir <path>","wiki directory").option("--agent <id>","filter by agent").action(e=>{let o=X(e.dir).getSharedStore().listEntries();e.agent&&(o=o.filter(s=>s.agentId===e.agent)),console.log(),console.log(l.bold(` ${o.length} raw entries`)),console.log();for(let s of o.slice(-20))console.log(` ${l.dim(s.date)} ${l.cyan(s.agentId)} via ${s.source}: ${s.content.slice(0,80)}...`);o.length>20&&console.log(l.dim(` ... showing last 20 of ${o.length}`)),console.log()});J.command("serve").description("start a local web server to browse agent wikis (local + mesh)").option("--dir <path>","wiki directory").option("--mode <mode>","graph (default, canonical) | unified | flat (legacy, back-compat)","graph").option("--agent <id>","serve only this agent's wiki").option("--port <n>","port number","4200").option("--peer <urls...>","mesh peer URLs to federate").action(async e=>{let t=e.dir||O(process.cwd(),".agentx/wiki"),n=parseInt(e.port),o=e.peer||[];if(o.length===0)try{let{loadDaemonConfig:s}=await import("./config-LN6JEHMF.js");o=(s().mesh?.peers||[]).map(r=>r.url)}catch{}console.log(),console.log(l.bold(" AgentX Wiki Server")),console.log(),console.log(` ${l.green(">")} http://localhost:${n}`),e.agent?console.log(` Agent: ${l.cyan(e.agent)}`):console.log(` Mode: ${l.cyan("Hub")} (all agents)`),o.length>0&&console.log(` Mesh: ${l.cyan(o.length+" peer(s)")} \u2014 ${o.join(", ")}`),console.log(l.dim(` Wiki: ${t}`)),console.log(l.dim(" Press Ctrl+C to stop")),console.log(),dn(t,n,e.agent,o,e.mode)});J.command("query <question>").description("agentic wiki query \u2014 walks the catalog + wikilink graph, synthesizes an answer").option("--dir <path>","wiki directory").option("--agent <id>","which agent's wiki to query (default: first one with a catalog)").option("--selector-model <m>","candidate-selection model","haiku").option("--synth-model <m>","synthesis model","sonnet").option("--max-candidates <n>","candidates from selector","3").option("--max-hops <n>","wikilink hops from candidates","2").option("--max-articles <n>","cap on total articles walked","8").option("--json","emit full result as JSON (for A/B harnesses)").option("--trace","print selector + walk trace").action(async(e,t)=>{let{agenticQuery:n}=await import("./query-PKGYE5HR.js"),o=X(t.dir),s=t.agent?[t.agent]:o.listAgents(),i=null;for(let a of s){let d=o.getAgentWiki(a),m=O(d.baseDir,"_index.md");try{if((await import("fs")).existsSync(m)){i=a;break}}catch{}}if(!i){console.log(l.yellow(" No agent has a catalog yet. Run `agentx wiki status` or migrate first."));return}let r=o.getAgentWiki(i),c=await n(e,r,i,{selectorModel:t.selectorModel,synthModel:t.synthModel,maxCandidates:parseInt(t.maxCandidates),maxHops:parseInt(t.maxHops),maxArticles:parseInt(t.maxArticles)});if(t.json){console.log(JSON.stringify(c,null,2));return}if(console.log(),console.log(l.bold(` Q: ${e}`)),console.log(l.dim(` agent: ${i} \xB7 status: ${c.status} \xB7 walked: ${c.walked.length}`)),console.log(),c.status!=="ok"){console.log(l.yellow(` (no answer) ${c.error||c.status}`));return}if(console.log(c.answer),console.log(),c.citations.length){console.log(l.dim(" Citations:"));for(let a of c.citations){let d=a.type?l.magenta(` [${a.type}]`):"";console.log(l.dim(` - ${a.title}${d} (${a.path})`))}console.log()}if(t.trace&&c.trace){if(console.log(l.dim(` selector: ${c.trace.selectorMs}ms synthesis: ${c.trace.synthesisMs}ms`)),console.log(l.dim(` candidates: ${c.candidates.map(a=>a.title).join(" | ")||"(none)"}`)),c.walked.length>c.candidates.length){let a=c.walked.filter(d=>!c.candidates.some(m=>m.path===d.path));console.log(l.dim(` followed: ${a.map(d=>`${d.title}@h${d.hop}`).join(" | ")}`))}console.log()}});J.command("search <query>").description("search wiki articles").option("--dir <path>","wiki directory").option("--mode <mode>","graph (default, canonical) | unified | flat (legacy, back-compat)","graph").option("--agent <id>","search specific agent's wiki").action((e,t)=>{let n=X(t.dir,t.mode),o=t.agent?[t.agent]:n.listAgents();console.log();let s=0;for(let i of o){let c=n.getAgentWiki(i).findRelevant(e,void 0,10);if(c.length>0){console.log(l.bold(` ${l.cyan(i)}:`));for(let a of c)console.log(` ${a.meta.title} [${(a.meta.tags||[]).slice(0,3).join(", ")}]`),console.log(l.dim(` ${a.path} \u2014 ${a.content.slice(0,100)}...`));s+=c.length}}s===0&&console.log(l.dim(" No matching articles")),console.log()});J.command("sync").description("pull raw entries from mesh peers into local wiki").option("--dir <path>","wiki directory").option("--peer <url>","sync from a specific peer URL (e.g., http://100.67.108.119:19900)").option("--dry-run","show what would be synced without writing").action(async e=>{let n=X(e.dir).getSharedStore(),o=[];if(e.peer)o=[e.peer];else try{let{loadDaemonConfig:r}=await import("./config-LN6JEHMF.js");o=(r().mesh?.peers||[]).map(a=>a.url)}catch{console.log(l.red(" No --peer specified and no daemon config found")),console.log(l.dim(" Usage: agentx wiki sync --peer http://100.67.108.119:19900"));return}if(o.length===0){console.log(l.dim(" No mesh peers configured"));return}let s=new Set(n.listEntries().map(r=>r.id)),i=0;for(let r of o){console.log(),console.log(l.bold(` Syncing from ${r}...`));try{let c=await fetch(`${r}/wiki/entries`,{signal:AbortSignal.timeout(1e4)});if(!c.ok){console.log(l.red(` HTTP ${c.status}`));continue}let a=await c.json(),d=a.entries||[],m=a.nodeId||"unknown";console.log(l.dim(` Node: ${m} \u2014 ${d.length} entries`));let g=0;for(let p of d)s.has(p.id)||(e.dryRun||(n.addEntry({id:p.id,date:p.date,agentId:p.agentId,source:`${p.source}@${m}`,sourceContext:p.sourceContext,content:p.content}),s.add(p.id)),g++);g>0?console.log(l.green(` ${g} new entries${e.dryRun?" (dry run)":" synced"}`)):console.log(l.dim(" Already up to date")),i+=g;try{let p=await fetch(`${r}/wiki/agents`,{signal:AbortSignal.timeout(5e3)});if(p.ok){let u=await p.json();for(let h of u.agents||[])h.totalEntries>0&&console.log(l.dim(` ${h.agentId}: ${h.totalEntries} entries, ${h.totalArticles} articles`))}}catch{}}catch(c){c.cause?.code==="ECONNREFUSED"?console.log(l.red(" Connection refused")):console.log(l.red(` ${c.message}`))}}console.log(),i>0&&!e.dryRun?console.log(l.green(` ${i} entries synced.`)):i>0?console.log(l.dim(` ${i} entries would be synced (dry run)`)):console.log(l.dim(" All peers up to date")),console.log()});J.command("compare").description("compare all wiki compilation modes for an agent").option("--dir <path>","wiki directory").requiredOption("--agent <id>","agent to compare").action(e=>{let t=e.dir||O(process.cwd(),".agentx/wiki"),n=e.agent,s=["flat","graph","unified"].map(a=>{let d=new Oe(t,void 0,a),g=d.getAgentWiki(n).rebuildIndex(),p=d.getAgentEntries(n),u=d.getUnabsorbedEntries(n),h=new Set,y=0,$=new Set;for(let w of g.articles){for(let k of w.tags||[])h.add(k);y+=w.tags?.length||0;for(let k of w.sources||[])$.add(k)}let b=new Set(g.articles.map(w=>w.path.includes("/")?w.path.split("/")[0]:"/"));return{mode:a,label:xt(a),articles:g.articles.length,entries:p.length,unabsorbed:u.length,uniqueTags:h.size,avgTags:g.articles.length>0?(y/g.articles.length).toFixed(1):"0",dirs:b.size,coverage:$.size,articleList:g.articles}});console.log(),console.log(l.bold(` Wiki Compare: ${n}`)),console.log(` Raw entries: ${s[0].entries}`),console.log();let i=22;console.log(` ${"".padEnd(20)} ${s.map(a=>a.label.padEnd(i)).join(" ")}`),console.log(` ${"\u2500".repeat(20)} ${s.map(()=>"\u2500".repeat(i)).join(" ")}`);let r=[["Articles",a=>String(a.articles)],["Unabsorbed",a=>String(a.unabsorbed)],["Unique tags",a=>String(a.uniqueTags)],["Avg tags/article",a=>a.avgTags],["Directories",a=>String(a.dirs)],["Entry coverage",a=>`${a.coverage}/${a.entries}`]];for(let[a,d]of r){let m=s.map(g=>d(g).padEnd(i));console.log(` ${a.padEnd(20)} ${m.join(" ")}`)}console.log();let c=(a,d)=>{let m=Math.max(...s.map(p=>p[a])),g=s.filter(p=>p[a]===m).map(p=>p.label);m>0&&console.log(l.dim(` Best ${d}: ${g.join(", ")} (${m})`))};c("uniqueTags","tag richness"),c("articles","article count"),c("coverage","entry coverage"),console.log()});import{Command as _o}from"commander";import q from"chalk";import{resolve as zo}from"path";me();var St=new _o().name("graph").description("intent knowledge graph \u2014 review, inspect, manage");St.command("review").description("triage pending classifications via the configured review agent (the agent uses `wiki query` for context before deciding)").option("--agent <id>","override the review agent (defaults to graph.reviewAgent or graph.draftAgent)").option("--max <n>","cap reviews this run","20").option("--dry-run","show the agent's decision but don't apply approvals/rejections").option("--daemon-url <url>","daemon URL (default: http://localhost:18800)").action(async e=>{let t=U();if(!t.graph?.enabled){console.log(q.yellow(" graph.enabled is false \u2014 nothing to review"));return}let n=e.agent||t.graph.reviewAgent||t.graph.draftAgent;if(!n){console.log(q.red(" no review agent. Set graph.reviewAgent in agentx.json or pass --agent <id>."));return}if(!t.agents?.[n]){console.log(q.red(` agent "${n}" not in config`));return}let o=(e.daemonUrl||t.dashboard?.daemonUrl||"http://localhost:18800").replace(/\/+$/,""),s=Math.max(1,parseInt(e.max)||20),i=new jt({baseDir:zo(process.cwd(),t.graph.baseDir)}),r=i.listPendingClassifications(s);if(r.length===0){console.log(q.dim(" no pending classifications \u2014 nothing to review"));return}let c=i.loadSchema(),a=i.loadNodes().nodes;console.log(),console.log(q.bold(` Graph review \u2014 ${r.length} pending`)),console.log(q.dim(` Reviewer: ${n} \xB7 daemon: ${o} \xB7 ${e.dryRun?"dry-run":"commit"}`)),console.log();let d=0,m=0,g=0,p=0;for(let h=0;h<r.length;h++){let y=r[h],$=It(y.path,a)||y.path.join(" \u203A ");process.stdout.write(` [${h+1}/${r.length}] ${q.cyan(y.msgHash.slice(0,8))} ${q.dim($)} ... `);let b=Bo(y,c,a),w=null;try{w=await qo(o,n,b,t.dashboard?.token)}catch(v){p++,console.log(q.red(`ERR ${v.message?.slice(0,80)}`));continue}if(!w||!w.decision){p++,console.log(q.red("ERR no decision"));continue}let k=String(w.decision).toLowerCase().trim(),x=(w.reason||"").toString().slice(0,180);if(k==="approve")if(console.log(q.green("APPROVE")+" "+q.dim(x)),e.dryRun)d++;else try{Ho(i,y,c,a,n),i.updateClassificationStatus(y.msgHash,{status:"approved",reviewer:n,reviewReason:x}),d++}catch(v){console.log(q.yellow(` (commit failed, staying pending: ${v.message?.slice(0,100)})`)),p++}else k==="reject"?(console.log(q.red("REJECT")+" "+q.dim(x)),e.dryRun||i.updateClassificationStatus(y.msgHash,{status:"rejected",reviewer:n,reviewReason:x}),m++):(console.log(q.yellow("SKIP")+" "+q.dim(x||k)),g++)}console.log();let u=e.dryRun?"would":"did";console.log(q.dim(` Summary: ${u} approve ${d}, ${u} reject ${m}, ${g} skipped, ${p} errors.`)),e.dryRun&&(d>0||m>0)&&console.log(q.dim(" Dry-run \u2014 omit --dry-run to apply.")),console.log()});function Bo(e,t,n){let o={};for(let r of n)(o[r.level]||=[]).push(`${r.id}${r.axes?.name?` (${r.axes.name})`:""}`);let s=t.levels.map(r=>` ${r.id}: [${(o[r.id]||[]).slice(0,10).join(", ")||"(none yet)"}]`).join(`
320
- `),i=JSON.stringify(e.proposedAxes||{},null,2);return["You are reviewing a pending intent classification. Decide whether it is reasonable.","","--- MESSAGE (first 500 chars) ---",(e.preview||"").slice(0,500),`channel: ${e.channel||"?"} \xB7 sender: ${e.sender||"?"}`,"","--- PROPOSED PATH ---",e.path.join(" \u203A "),"","--- PROPOSED AXES (per new node) ---",i,"","--- CURRENT NODES BY LEVEL (first 10 per level) ---",s,"","--- YOUR JOB ---","Decide one of:"," approve \u2014 the path is right and the axes make sense for the message"," reject \u2014 the path is wrong (e.g. the message is NOT about the proposed org/topic, or the axes are bogus)"," skip \u2014 you can't confidently decide; leave it for a human","",'BEFORE you decide, you MAY call `agentx wiki query` (via Bash) to check institutional context \u2014 e.g. "is there an article for this project" or "who is this person". This is optional; don\'t call it for obvious cases.',"","Bias toward reject over approve when uncertain. A wrong approval pollutes the graph; a wrong rejection just keeps the entry pending.","","Return EXACTLY one JSON object on one line, no prose, no fences:",' {"decision":"approve"|"reject"|"skip","reason":"one sentence"}'].join(`
321
- `)}async function qo(e,t,n,o){let s={"Content-Type":"application/json"};o&&(s.Authorization=`Bearer ${o}`);let i=new AbortController,r=setTimeout(()=>i.abort(),12e4);try{let c=await fetch(`${e}/task`,{method:"POST",headers:s,body:JSON.stringify({agent:t,message:n,context:{channel:"a2a",sender:"graph-review"}}),signal:i.signal});if(!c.ok)throw new Error(`daemon /task HTTP ${c.status}`);let a=await c.json();if(a?.error)throw new Error(String(a.error));return Fo((a?.content||"").toString())}finally{clearTimeout(r)}}function Ho(e,t,n,o,s){e.commitNodesAlongPath(t.path,t.proposedAxes||{},n,s),e.setFingerprint(t.msgHash,{path:t.path,leaf:t.leaf||{}})}function Fo(e){if(!e)return null;try{return JSON.parse(e)}catch{}let t=e.match(/```(?:json)?\s*([\s\S]*?)```/);if(t)try{return JSON.parse(t[1])}catch{}let n=e.indexOf("{");if(n<0)return null;let o=0;for(let s=n;s<e.length;s++)if(e[s]==="{")o++;else if(e[s]==="}"&&(o--,o===0))try{return JSON.parse(e.slice(n,s+1))}catch{return null}return null}process.on("SIGINT",()=>process.exit(0));process.on("SIGTERM",()=>process.exit(0));async function Go(){let e=await Pt(),t=new Jo().name("agentx").description("self-hosted multi-agent orchestrator \u2014 Telegram, WhatsApp, crons, A2A mesh, wiki knowledge").version(e.version||"1.0.0","-v, --version","display the version number");if(t.addCommand(ce).addCommand(Ut).addCommand(Wt).addCommand(Le).addCommand(Ke).addCommand(ke).addCommand(Re).addCommand(he).addCommand(Ue).addCommand(_e).addCommand(mt).addCommand(ut).addCommand(we).addCommand(Be).addCommand(Pe).addCommand(J).addCommand(St).addCommand(Me).addCommand(Bt),process.argv.slice(2).length===0){t.outputHelp();return}t.parse()}Go();
2
+ import{a as s}from"./chunk-YPOELN6Z.js";import"./chunk-5WNGHDFC.js";import"./chunk-6PPIFDOQ.js";import"./chunk-ABCHHNG6.js";import"./chunk-4HP6UC2X.js";import"./chunk-SRHLXZRF.js";import"./chunk-MBEQASP3.js";import"./chunk-N67JZAXU.js";import"./chunk-CDNWCYF7.js";import"./chunk-AFRHKA7B.js";import"./chunk-EW27SCCC.js";import"./chunk-ZE2YFX4K.js";import"./chunk-HHJGKD5B.js";import"./chunk-5F3J55LG.js";import"./chunk-34NG7UDX.js";import"./chunk-WJXU5F4Z.js";import"./chunk-LEIYC3BF.js";import"./chunk-WZEYII2S.js";import"./chunk-64P4QHSZ.js";import"./chunk-BFZUG66X.js";import"./chunk-TT2Q7YMZ.js";import"./chunk-SFQUP3BP.js";import"./chunk-XDJSYQST.js";import"./chunk-HCFSHGK4.js";import"./chunk-MCJUAE6V.js";import"./chunk-DSYKYZMT.js";import"./chunk-63UYRJIC.js";import"./chunk-QOYAX2FT.js";import"./chunk-FIOB3MEX.js";process.on("SIGINT",()=>process.exit(0));process.on("SIGTERM",()=>process.exit(0));async function o(){let r=await s();if(process.argv.slice(2).length===0){r.outputHelp();return}r.parse()}o();
322
3
  //# sourceMappingURL=cli.js.map