agentix-cli 0.14.0 → 0.16.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 (100) hide show
  1. package/README.md +70 -34
  2. package/dist/agent-BCDPSPKU.js +2 -0
  3. package/dist/board-dashboard-LA7WW56M.js +5668 -0
  4. package/dist/board-dashboard-LA7WW56M.js.map +1 -0
  5. package/dist/chunk-5F3J55LG.js +2 -0
  6. package/dist/chunk-5F3J55LG.js.map +1 -0
  7. package/dist/chunk-64P4QHSZ.js +2 -0
  8. package/dist/chunk-64P4QHSZ.js.map +1 -0
  9. package/dist/chunk-7FJQGD6U.js +6 -0
  10. package/dist/chunk-7FJQGD6U.js.map +1 -0
  11. package/dist/{chunk-ZRYBSI5G.js → chunk-7MBTMCKY.js} +2 -2
  12. package/dist/chunk-ABDP7TDL.js +71 -0
  13. package/dist/chunk-ABDP7TDL.js.map +1 -0
  14. package/dist/chunk-BFZUG66X.js +45 -0
  15. package/dist/chunk-BFZUG66X.js.map +1 -0
  16. package/dist/chunk-CXY2RYNI.js +11 -0
  17. package/dist/chunk-CXY2RYNI.js.map +1 -0
  18. package/dist/chunk-FIOB3MEX.js +2 -0
  19. package/dist/chunk-FLOGK2U7.js +7 -0
  20. package/dist/chunk-FLOGK2U7.js.map +1 -0
  21. package/dist/{chunk-XVYMZ4M7.js → chunk-HVC6FJNP.js} +5 -5
  22. package/dist/chunk-K7BE4E35.js +138 -0
  23. package/dist/chunk-K7BE4E35.js.map +1 -0
  24. package/dist/chunk-KXJSLLSQ.js +199 -0
  25. package/dist/chunk-KXJSLLSQ.js.map +1 -0
  26. package/dist/chunk-LEIYC3BF.js +104 -0
  27. package/dist/chunk-LEIYC3BF.js.map +1 -0
  28. package/dist/{chunk-O72LPMBR.js → chunk-RB7265LO.js} +2 -2
  29. package/dist/chunk-WZEYII2S.js +6 -0
  30. package/dist/chunk-WZEYII2S.js.map +1 -0
  31. package/dist/chunk-YI2T5QYB.js +5 -0
  32. package/dist/{chunk-NJR5WF5E.js.map → chunk-YI2T5QYB.js.map} +1 -1
  33. package/dist/cli.js +209 -181
  34. package/dist/cli.js.map +1 -1
  35. package/dist/{compaction-7I3E5QUO.js → compaction-JBZZ7I7Y.js} +4 -4
  36. package/dist/config-LN6JEHMF.js +2 -0
  37. package/dist/config-mutator-24TH7PHM.js +2 -0
  38. package/dist/debug-3MEUO2P2.js +2 -0
  39. package/dist/{heal-OTGT5HHJ.js → heal-WKY5I4KB.js} +2 -2
  40. package/dist/index.d.ts +1078 -13
  41. package/dist/index.js +47 -44
  42. package/dist/index.js.map +1 -1
  43. package/dist/loader-PTCXLJEX.js +2 -0
  44. package/dist/memory-extract-AD7KMRPC.js +2 -0
  45. package/dist/{providers-G3WZ4RVJ.js → providers-VUIWO3ZK.js} +2 -2
  46. package/dist/query-PKGYE5HR.js +2 -0
  47. package/dist/registry-ASC2QS5K.js +2 -0
  48. package/dist/scheduler-A3TQOCKT.js +2 -0
  49. package/dist/scheduler-A3TQOCKT.js.map +1 -0
  50. package/dist/{subagent-WV7QKQ3L.js → subagent-2SJHSSJS.js} +2 -2
  51. package/dist/types-OIFU3KSQ.js +2 -0
  52. package/dist/types-OIFU3KSQ.js.map +1 -0
  53. package/dist/{usage-dashboard-M7AFIGMJ.js → usage-dashboard-QX3CF6KK.js} +8 -8
  54. package/dist/usage-dashboard-QX3CF6KK.js.map +1 -0
  55. package/dist/whatsapp-state-6D6FHHD2.js +2 -0
  56. package/dist/whatsapp-state-6D6FHHD2.js.map +1 -0
  57. package/dist/wiki-UDPYFJBL.js +2 -0
  58. package/dist/wiki-UDPYFJBL.js.map +1 -0
  59. package/dist/workspace-setup-ZWTT42XT.js +2 -0
  60. package/dist/workspace-setup-ZWTT42XT.js.map +1 -0
  61. package/package.json +3 -2
  62. package/dist/agent-DW3NYIIL.js +0 -2
  63. package/dist/board-dashboard-6TKE6NMV.js +0 -1435
  64. package/dist/board-dashboard-6TKE6NMV.js.map +0 -1
  65. package/dist/chunk-4YCH6IZV.js +0 -2
  66. package/dist/chunk-DYOBFJLX.js +0 -11
  67. package/dist/chunk-DYOBFJLX.js.map +0 -1
  68. package/dist/chunk-ITZEVXZ4.js +0 -2
  69. package/dist/chunk-ITZEVXZ4.js.map +0 -1
  70. package/dist/chunk-M33CB2H4.js +0 -6
  71. package/dist/chunk-M33CB2H4.js.map +0 -1
  72. package/dist/chunk-NJR5WF5E.js +0 -5
  73. package/dist/chunk-RB73FZXJ.js +0 -133
  74. package/dist/chunk-RB73FZXJ.js.map +0 -1
  75. package/dist/chunk-UPT2UMF6.js +0 -159
  76. package/dist/chunk-UPT2UMF6.js.map +0 -1
  77. package/dist/config-7QJ2QJIC.js +0 -2
  78. package/dist/config-mutator-GAEVGHDD.js +0 -2
  79. package/dist/debug-N3B5NVJU.js +0 -2
  80. package/dist/loader-YQE6XJBT.js +0 -2
  81. package/dist/memory-extract-RGPUPMKU.js +0 -2
  82. package/dist/registry-YVAG4EO5.js +0 -2
  83. package/dist/usage-dashboard-M7AFIGMJ.js.map +0 -1
  84. package/dist/workspace-setup-QQTCJY4B.js +0 -2
  85. /package/dist/{agent-DW3NYIIL.js.map → agent-BCDPSPKU.js.map} +0 -0
  86. /package/dist/{chunk-ZRYBSI5G.js.map → chunk-7MBTMCKY.js.map} +0 -0
  87. /package/dist/{chunk-4YCH6IZV.js.map → chunk-FIOB3MEX.js.map} +0 -0
  88. /package/dist/{chunk-XVYMZ4M7.js.map → chunk-HVC6FJNP.js.map} +0 -0
  89. /package/dist/{chunk-O72LPMBR.js.map → chunk-RB7265LO.js.map} +0 -0
  90. /package/dist/{compaction-7I3E5QUO.js.map → compaction-JBZZ7I7Y.js.map} +0 -0
  91. /package/dist/{config-7QJ2QJIC.js.map → config-LN6JEHMF.js.map} +0 -0
  92. /package/dist/{config-mutator-GAEVGHDD.js.map → config-mutator-24TH7PHM.js.map} +0 -0
  93. /package/dist/{debug-N3B5NVJU.js.map → debug-3MEUO2P2.js.map} +0 -0
  94. /package/dist/{heal-OTGT5HHJ.js.map → heal-WKY5I4KB.js.map} +0 -0
  95. /package/dist/{loader-YQE6XJBT.js.map → loader-PTCXLJEX.js.map} +0 -0
  96. /package/dist/{memory-extract-RGPUPMKU.js.map → memory-extract-AD7KMRPC.js.map} +0 -0
  97. /package/dist/{providers-G3WZ4RVJ.js.map → providers-VUIWO3ZK.js.map} +0 -0
  98. /package/dist/{registry-YVAG4EO5.js.map → query-PKGYE5HR.js.map} +0 -0
  99. /package/dist/{workspace-setup-QQTCJY4B.js.map → registry-ASC2QS5K.js.map} +0 -0
  100. /package/dist/{subagent-WV7QKQ3L.js.map → subagent-2SJHSSJS.js.map} +0 -0
@@ -0,0 +1,199 @@
1
+ import{f as b,i as $}from"./chunk-7FJQGD6U.js";import{existsSync as g,readFileSync as S,writeFileSync as T,appendFileSync as R,mkdirSync as W}from"fs";import{resolve as c}from"path";import{hostname as j}from"os";$();import{existsSync as v,readFileSync as x,writeFileSync as h,copyFileSync as B}from"fs";import{resolve as y}from"path";function w(e,t={}){let n=t.configPath||y(process.cwd(),"agentx.json");if(!v(n))throw new Error(`agentx.json not found at ${n}`);let a=JSON.parse(x(n,"utf-8")),r=e(a),i=typeof r=="string"?{summary:r}:r,s=JSON.stringify(a,null,2)+`
2
+ `,d=n+".tmp";h(d,s,"utf-8");try{JSON.parse(x(d,"utf-8"))}catch(u){throw new Error(`config did not round-trip cleanly: ${u.message}`)}let l;return t.skipBackup||(l=`${n}.bak.${Date.now()}`,B(n,l)),h(n,s,"utf-8"),t.skipReload||_().catch(()=>{}),{summary:i.summary,payload:i.payload,backupPath:l}}function k(e,t={}){let n=t.configPath||y(process.cwd(),"agentx.json");if(v(n)&&!t.overwrite)throw new Error(`agentx.json already exists at ${n}. Pass overwrite=true to replace it.`);return h(n,JSON.stringify(e,null,2)+`
3
+ `,"utf-8"),{file:n}}async function _(){try{let t=b().dashboard.daemonUrl?.replace(/\/+$/,"")||"http://127.0.0.1:18800";await fetch(`${t}/reload`,{method:"POST"}).catch(()=>null)}catch{}}function C(e=process.cwd()){let t=c(e,"agentx.json");if(!g(t))return{configExists:!1,agentCount:0,channelCount:0};try{let n=JSON.parse(S(t,"utf-8")),a=n.agents?Object.keys(n.agents).length:0,r=n.channels?Object.values(n.channels).filter(i=>i&&i.enabled).length:0;return{configExists:!0,agentCount:a,channelCount:r,nodeName:n.node?.name}}catch{return{configExists:!1,agentCount:0,channelCount:0}}}function F(e,t=process.cwd()){if(!e.nodeName?.trim())throw new Error("Team name is required.");if(!e.agent?.id?.trim())throw new Error("Agent id is required.");if(!/^[a-z0-9][a-z0-9_-]*$/.test(e.agent.id))throw new Error("Agent id must be lowercase (letters, digits, -, _) and start with a letter or digit.");if(!e.agent.name?.trim())throw new Error("Agent name is required.");let n=D(e.agent.triggerWords);if(n.length===0)throw new Error("At least one trigger word is required.");if(e.agent.tier!=="claude-code"&&e.agent.tier!=="sdk")throw new Error("AI engine must be claude-code or sdk.");let a=c(t,"agentx.json"),r=c(t,".env"),i=c(t,"agents",e.agent.id),s=C(t);W(i,{recursive:!0});let d=c(i,"CLAUDE.md");if(!g(d)){let m=e.agent.personality?.trim()||`You are ${e.agent.name}, an assistant on the ${e.nodeName} team.`;T(d,`# ${e.agent.name}
4
+
5
+ ${m}
6
+ `)}let l=e.telegram?`TG_${e.agent.id.toUpperCase()}_BOT_TOKEN`:void 0,u;if(s.configExists)u=w(o=>{if(o.node=o.node||{},o.node.name||(o.node.name=e.nodeName),o.node.id||(o.node.id=E(e.nodeName)||"team"),o.node.bind||(o.node.bind="127.0.0.1:18800"),o.node.defaultAgent=o.node.defaultAgent||e.agent.id,o.providers=o.providers||{},o.providers.claude||(o.providers.claude={apiKey:"${ANTHROPIC_API_KEY}"}),o.agents=o.agents||{},o.agents[e.agent.id])throw new Error(`Agent "${e.agent.id}" already exists.`);return o.agents[e.agent.id]=I(e,i,n),e.telegram&&(o.channels=o.channels||{},o.channels.telegram=o.channels.telegram||{enabled:!0,accounts:{},policy:{dm:"pair",group:"mention-required"}},o.channels.telegram.enabled=!0,o.channels.telegram.accounts=o.channels.telegram.accounts||{},o.channels.telegram.accounts[e.telegram.accountId]=z(e,l)),{summary:`added agent "${e.agent.id}"`+(e.telegram?" + telegram account":"")}},{configPath:a}).backupPath;else{let m=U(e,i,n,l);k(m,{configPath:a})}H(r,e,l);let p=[];return(e.anthropicApiKey||!g(c(t,".env")))&&p.push("Double-check the API keys in .env"),e.agent.tier==="claude-code"&&p.push("Run claude --version to confirm Claude Code is installed (needed for the claude-code engine)"),p.push("Start the daemon: agentx daemon start"),p.push("Open the dashboard: http://127.0.0.1:4202"),{summary:s.configExists?`Added agent "${e.agent.id}" to your team.`:`Initialized your team with first agent "${e.agent.id}".`,nextSteps:p,backupPath:u}}function D(e){return(e||"").split(/[,\s]+/).map(t=>t.trim()).filter(Boolean).filter((t,n,a)=>a.indexOf(t)===n)}function E(e){return(e||"").toLowerCase().replace(/[^a-z0-9-]+/g,"-").replace(/^-+|-+$/g,"")}function U(e,t,n,a){let i={node:{id:E(e.nodeName)||"team",name:e.nodeName,bind:"127.0.0.1:18800",defaultAgent:e.agent.id},providers:{claude:{apiKey:"${ANTHROPIC_API_KEY}"}},agents:{[e.agent.id]:I(e,t,n)},channels:{},crons:{},mesh:{enabled:!1,peers:[],discovery:"static",healthCheck:{interval:60,timeout:10}},dashboard:{enabled:!0,port:4202,bind:"127.0.0.1",daemonUrl:"http://localhost:18800"},boards:[]};return e.telegram&&a?i.channels.telegram={enabled:!0,accounts:{[e.telegram.accountId]:z(e,a)},policy:{dm:"pair",group:"mention-required"}}:i.channels.telegram={enabled:!1,accounts:{},policy:{dm:"pair",group:"mention-required"}},i}function I(e,t,n){let a={name:e.agent.name,workspace:t,tier:e.agent.tier,mentions:n,maxConcurrent:2,permissionMode:"default"};return e.agent.model&&(a.model=e.agent.model),e.agent.personality?.trim()&&(a.systemPrompt=e.agent.personality.trim().slice(0,400)),a}function z(e,t){return{botToken:"${"+t+"}",botUsername:e.telegram?.botUsername||"",agentBinding:e.agent.id}}function H(e,t,n){let a=g(e)?S(e,"utf-8"):"",r=[];if(t.anthropicApiKey&&!a.includes("ANTHROPIC_API_KEY=")&&r.push(`ANTHROPIC_API_KEY=${t.anthropicApiKey}`),t.telegram&&n&&!a.includes(`${n}=`)&&r.push(`${n}=${t.telegram.botToken}`),!g(e)){T(e,[...["# AgentX environment variables","# (Never commit this file \u2014 it contains secrets.)",""],...r,""].join(`
7
+ `));return}if(r.length>0){let i=a.endsWith(`
8
+ `)?"":`
9
+ `;R(e,i+r.join(`
10
+ `)+`
11
+ `)}}function se(e,t){let n=C(),a=K(n);t.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),t.end(a)}async function de(e,t){let n="";await new Promise(r=>{e.on("data",i=>{n+=i}),e.on("end",()=>r())});let a;try{a=JSON.parse(n)}catch{t.writeHead(400,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"Invalid JSON"}));return}try{let r=F(a);t.writeHead(200,{"Content-Type":"application/json"}),t.end(JSON.stringify(r))}catch(r){t.writeHead(400,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:r.message||"wizard failed"}))}}function K(e){let t=e.configExists?e.agentCount===0?"Add your first agent":"Add another agent":"Set up your team",n=e.nodeName||j().replace(/\.local$/,"");return`<!DOCTYPE html>
12
+ <html lang="en">
13
+ <head>
14
+ <meta charset="utf-8">
15
+ <meta name="viewport" content="width=device-width, initial-scale=1">
16
+ <title>AgentX \u2014 Setup</title>
17
+ <style>
18
+ :root{--bg:#0b0d14;--card:#151823;--border:#2a2d3a;--text:#e6e8ef;--muted:#8b8fa3;--accent:#6366f1;--green:#22c55e;--red:#ef4444}
19
+ *{box-sizing:border-box}
20
+ html,body{margin:0;min-height:100%;background:var(--bg);color:var(--text);font:14px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
21
+ header{padding:18px 24px;border-bottom:1px solid var(--border);background:#10131c;display:flex;align-items:center;gap:12px}
22
+ header .brand{font-weight:600;color:var(--accent);font-size:15px}
23
+ header .sub{color:var(--muted);font-weight:500}
24
+ .spacer{flex:1}
25
+ a.link{color:var(--muted);text-decoration:none;font-size:13px;padding:4px 10px;border:1px solid var(--border);border-radius:6px}
26
+ a.link:hover{color:var(--accent);border-color:var(--accent)}
27
+ main{max-width:620px;margin:0 auto;padding:32px 22px 60px}
28
+ h1{font-size:24px;margin:0 0 6px}
29
+ .lead{color:var(--muted);margin:0 0 26px}
30
+ section.step{background:var(--card);border:1px solid var(--border);border-radius:10px;padding:20px 22px;margin:14px 0}
31
+ section.step h2{font-size:14px;font-weight:600;margin:0 0 14px;color:var(--text);display:flex;align-items:center;gap:10px}
32
+ section.step h2 .num{background:var(--accent);color:#fff;width:22px;height:22px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;font-size:11px;font-weight:700}
33
+ label{display:block;margin:10px 0 4px;font-size:12px;color:var(--muted)}
34
+ label .hint{color:var(--muted);font-weight:400;font-size:11px;margin-left:6px}
35
+ input,textarea,select{width:100%;background:#0e1119;color:var(--text);border:1px solid var(--border);border-radius:6px;padding:8px 10px;font:13px/1.4 inherit}
36
+ input:focus,textarea:focus,select:focus{outline:none;border-color:var(--accent)}
37
+ textarea{resize:vertical;min-height:70px;font-family:inherit}
38
+ .row{display:flex;gap:10px}
39
+ .row > *{flex:1}
40
+ .toggle{display:flex;align-items:center;gap:8px;margin:14px 0 4px;font-size:13px;color:var(--text);cursor:pointer}
41
+ .toggle input{width:auto}
42
+ .optional{border-left:2px solid var(--border);padding-left:14px;margin-top:10px;display:none}
43
+ .optional.show{display:block}
44
+ footer.actions{display:flex;gap:10px;padding:20px 0;align-items:center}
45
+ button.primary{background:var(--accent);color:#fff;border:none;border-radius:6px;padding:10px 20px;font-size:14px;font-weight:600;cursor:pointer}
46
+ button.primary:hover{filter:brightness(1.1)}
47
+ button.primary:disabled{opacity:0.5;cursor:not-allowed}
48
+ button.ghost{background:transparent;color:var(--muted);border:1px solid var(--border);border-radius:6px;padding:9px 16px;cursor:pointer}
49
+ .hint-block{font-size:11px;color:var(--muted);margin-top:6px;line-height:1.55}
50
+ .hint-block code{background:#10131c;padding:1px 6px;border-radius:3px;color:var(--text);font-family:ui-monospace,monospace;font-size:11px}
51
+ #msg{margin:14px 0;padding:12px 16px;border-radius:6px;font-size:13px;display:none}
52
+ #msg.ok{display:block;background:rgba(34,197,94,0.12);color:var(--green);border:1px solid rgba(34,197,94,0.3)}
53
+ #msg.err{display:block;background:rgba(239,68,68,0.12);color:var(--red);border:1px solid rgba(239,68,68,0.3)}
54
+ #next-steps{margin-top:12px;padding-left:20px}
55
+ #next-steps li{margin:4px 0}
56
+ #next-steps code{background:#10131c;padding:1px 6px;border-radius:3px;color:var(--text);font-family:ui-monospace,monospace;font-size:12px}
57
+ .config-banner{background:rgba(99,102,241,0.12);border:1px solid rgba(99,102,241,0.3);border-radius:6px;padding:10px 14px;margin-bottom:16px;color:var(--muted);font-size:12px}
58
+ .config-banner b{color:var(--text)}
59
+ </style>
60
+ </head>
61
+ <body>
62
+ <header>
63
+ <div class="brand">AgentX <span class="sub">\xB7 Setup</span></div>
64
+ <div class="spacer"></div>
65
+ <a href="/glossary" class="link">? Glossary</a>
66
+ <a href="/live" class="link">Skip \u2014 open dashboard</a>
67
+ </header>
68
+ <main>
69
+ <h1>${A(t)}</h1>
70
+ <p class="lead">Fill in the basics below. We'll write <code>agentx.json</code>, set up a folder for your first agent, and point you at the next step.</p>
71
+
72
+ ${e.configExists?`<div class="config-banner">Existing install: <b>${e.agentCount}</b> agent(s), <b>${e.channelCount}</b> channel(s) already configured. This wizard will add to it.</div>`:""}
73
+
74
+ <div id="msg"></div>
75
+
76
+ <form id="wizard" autocomplete="off">
77
+ <section class="step">
78
+ <h2><span class="num">1</span>Team basics</h2>
79
+ <label>Team name<span class="hint">(what you'd call this AgentX install)</span></label>
80
+ <input name="nodeName" value="${A(n)}" placeholder="My Team" required />
81
+ </section>
82
+
83
+ <section class="step">
84
+ <h2><span class="num">2</span>First agent</h2>
85
+ <div class="row">
86
+ <div>
87
+ <label>Agent name</label>
88
+ <input name="agentName" value="Assistant" placeholder="Support Bot" required />
89
+ </div>
90
+ <div>
91
+ <label>Agent id<span class="hint">(lowercase, no spaces)</span></label>
92
+ <input name="agentId" value="assistant" pattern="[a-z0-9][a-z0-9_-]*" required />
93
+ </div>
94
+ </div>
95
+ <label>Trigger words<span class="hint">(words that activate this agent \u2014 comma or space separated, e.g. <code>@support, support</code>)</span></label>
96
+ <input name="triggerWords" value="@assistant, assistant" required />
97
+ <div class="row">
98
+ <div>
99
+ <label>AI engine</label>
100
+ <select name="tier">
101
+ <option value="claude-code">Claude Code (recommended)</option>
102
+ <option value="sdk">Anthropic API (BYO key)</option>
103
+ </select>
104
+ </div>
105
+ <div>
106
+ <label>Model<span class="hint">(optional)</span></label>
107
+ <input name="model" value="claude-sonnet-4-6" placeholder="claude-sonnet-4-6" />
108
+ </div>
109
+ </div>
110
+ <label>Personality / instructions<span class="hint">(optional \u2014 what this agent does, in plain English)</span></label>
111
+ <textarea name="personality" placeholder="You are a support agent for Acme Co. Answer customer questions about our product, be friendly, keep replies short."></textarea>
112
+ </section>
113
+
114
+ <section class="step">
115
+ <h2><span class="num">3</span>First channel</h2>
116
+ <label class="toggle"><input type="checkbox" id="enableTelegram" /> Connect Telegram now</label>
117
+ <div class="optional" id="telegramFields">
118
+ <label>Account id<span class="hint">(a label you choose \u2014 e.g. <code>support</code>)</span></label>
119
+ <input name="telegramAccountId" value="default" />
120
+ <label>Bot token<span class="hint">(from <a href="https://t.me/BotFather" target="_blank" style="color:var(--accent)">@BotFather</a>)</span></label>
121
+ <input name="telegramBotToken" placeholder="123456:ABCdef..." />
122
+ <label>Bot username<span class="hint">(optional \u2014 e.g. <code>my_support_bot</code>)</span></label>
123
+ <input name="telegramBotUsername" placeholder="my_support_bot" />
124
+ </div>
125
+ </section>
126
+
127
+ <section class="step">
128
+ <h2><span class="num">4</span>Anthropic API key</h2>
129
+ <label>API key<span class="hint">(optional if you'll paste it into <code>.env</code> manually, or if you're only using Claude Code)</span></label>
130
+ <input name="anthropicApiKey" type="password" placeholder="sk-ant-\u2026" autocomplete="off" />
131
+ <div class="hint-block">
132
+ The key is written to <code>.env</code> as <code>ANTHROPIC_API_KEY</code>. AgentX never transmits it \u2014 everything stays on this machine.
133
+ </div>
134
+ </section>
135
+
136
+ <footer class="actions">
137
+ <button type="submit" class="primary" id="submitBtn">Save and continue</button>
138
+ <button type="button" class="ghost" onclick="window.location.href='/live'">Skip, I'll do it later</button>
139
+ </footer>
140
+ </form>
141
+ </main>
142
+ <script>
143
+ document.getElementById('enableTelegram').addEventListener('change', (e) => {
144
+ document.getElementById('telegramFields').classList.toggle('show', e.target.checked);
145
+ });
146
+ document.getElementById('wizard').addEventListener('submit', async (e) => {
147
+ e.preventDefault();
148
+ const f = e.target;
149
+ const msg = document.getElementById('msg');
150
+ const btn = document.getElementById('submitBtn');
151
+ msg.className = ''; msg.style.display = 'none';
152
+ btn.disabled = true; btn.textContent = 'Saving\u2026';
153
+ const telegramEnabled = document.getElementById('enableTelegram').checked;
154
+ const payload = {
155
+ nodeName: f.nodeName.value.trim(),
156
+ agent: {
157
+ id: f.agentId.value.trim(),
158
+ name: f.agentName.value.trim(),
159
+ triggerWords: f.triggerWords.value.trim(),
160
+ tier: f.tier.value,
161
+ model: f.model.value.trim() || undefined,
162
+ personality: f.personality.value.trim() || undefined,
163
+ },
164
+ anthropicApiKey: f.anthropicApiKey.value.trim() || undefined,
165
+ };
166
+ if (telegramEnabled) {
167
+ payload.telegram = {
168
+ accountId: f.telegramAccountId.value.trim() || 'default',
169
+ botToken: f.telegramBotToken.value.trim(),
170
+ botUsername: f.telegramBotUsername.value.trim() || undefined,
171
+ };
172
+ if (!payload.telegram.botToken) {
173
+ msg.className = 'err'; msg.textContent = 'Bot token is required when Telegram is enabled.';
174
+ btn.disabled = false; btn.textContent = 'Save and continue'; return;
175
+ }
176
+ }
177
+ try {
178
+ const r = await fetch('/api/setup', {
179
+ method: 'POST',
180
+ headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'agentx-wizard' },
181
+ body: JSON.stringify(payload),
182
+ });
183
+ const data = await r.json();
184
+ if (!r.ok) throw new Error(data.error || ('HTTP ' + r.status));
185
+ const steps = data.nextSteps.map((s) => '<li>' + s.replace(/\`([^\`]+)\`/g, '<code>$1</code>') + '</li>').join('');
186
+ msg.className = 'ok';
187
+ msg.innerHTML = '<b>' + data.summary + '</b><ol id="next-steps">' + steps + '</ol>';
188
+ btn.textContent = 'Done';
189
+ btn.disabled = true;
190
+ } catch (err) {
191
+ msg.className = 'err'; msg.textContent = err.message;
192
+ btn.disabled = false; btn.textContent = 'Save and continue';
193
+ }
194
+ });
195
+ </script>
196
+ </body>
197
+ </html>`}function A(e){return String(e||"").replace(/[&<>"']/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[t])}import{createHash as M,randomBytes as N}from"crypto";import{existsSync as q,mkdirSync as J,readFileSync as L,writeFileSync as Y}from"fs";import{resolve as X,dirname as G}from"path";var f="agx_live_",V=".agentx/tokens.json",P=class{file;constructor(t=process.cwd(),n=V){this.file=X(t,n)}load(){if(!q(this.file))return[];try{return JSON.parse(L(this.file,"utf-8"))}catch{return[]}}save(t){J(G(this.file),{recursive:!0}),Y(this.file,JSON.stringify(t,null,2)+`
198
+ `,"utf-8")}create(t){if(!t.name?.trim())throw new Error("Token name is required.");if(!Array.isArray(t.scopes)||t.scopes.length===0)throw new Error("At least one scope is required.");for(let s of t.scopes)if(!Q(s))throw new Error(`Invalid scope: ${s}`);let n=N(32).toString("hex"),a=f+n,r={id:"tok_"+N(4).toString("hex"),name:t.name.trim(),prefix:f+n.slice(0,4),hash:O(a),scopes:[...t.scopes],createdAt:new Date().toISOString(),expiresAt:t.expiresInDays?new Date(Date.now()+t.expiresInDays*86400*1e3).toISOString():void 0},i=this.load();return i.push(r),this.save(i),{token:a,record:r}}list(){return this.load()}revoke(t){let n=this.load(),a=n.find(r=>r.id===t);return a?(a.revokedAt=new Date().toISOString(),this.save(n),a):null}verify(t){if(!t||!t.startsWith(f))return null;let n=O(t),a=this.load(),r=a.find(d=>d.hash===n);if(!r||r.revokedAt||r.expiresAt&&Date.parse(r.expiresAt)<Date.now())return null;let i=Date.now(),s=r.lastUsedAt?Date.parse(r.lastUsedAt):0;if(i-s>6e4){r.lastUsedAt=new Date(i).toISOString();try{this.save(a)}catch{}}return r}};function ue(e,t){return!!(e.scopes.includes(t)||t.startsWith("agent:")&&e.scopes.includes("agent:*")||t==="dashboard:read"&&e.scopes.includes("dashboard:write"))}function Q(e){return e==="dashboard:read"||e==="dashboard:write"||e==="agent:*"||/^agent:[a-z0-9][a-z0-9_-]*$/.test(e)||e==="mesh:peer"}function O(e){return"sha256:"+M("sha256").update(e).digest("hex")}function me(e){let t=e.headers.authorization,n=Array.isArray(t)?t[0]:t;if(n&&n.toLowerCase().startsWith("bearer "))return n.slice(7).trim();if(e.url)try{let r=new URL(e.url,"http://localhost").searchParams.get("token");if(r)return r}catch{}return null}export{w as a,C as b,se as c,de as d,P as e,ue as f,me as g};
199
+ //# sourceMappingURL=chunk-KXJSLLSQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/daemon/setup-wizard.ts","../src/daemon/config-mutate.ts","../src/daemon/token-store.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync, appendFileSync, mkdirSync } from \"fs\"\nimport { resolve } from \"path\"\nimport { hostname } from \"os\"\nimport type { IncomingMessage, ServerResponse } from \"http\"\nimport { mutateAgentxConfig, writeAgentxConfig } from \"./config-mutate\"\n\n// --- /setup wizard: form-driven first-run experience for non-technical operators ---\n//\n// This is the web equivalent of `agentx init` + `agentx agent add` + `agentx\n// channel add`, collapsed into one page. It writes agentx.json and scaffolds\n// the first agent's workspace so a business operator can go from a blank\n// machine to a working AgentX install without editing JSON.\n//\n// The route lives on the board-dashboard server (port 4202) because that's the\n// surface operators actually open. It talks to the loopback daemon for the\n// post-install restart; the wizard itself never requires a running daemon.\n\nexport interface WizardPayload {\n nodeName: string\n agent: {\n id: string\n name: string\n triggerWords: string // comma/space separated; we normalise on the server\n tier: \"claude-code\" | \"sdk\"\n model?: string\n personality?: string // short free text → CLAUDE.md\n }\n telegram?: {\n accountId: string\n botToken: string\n botUsername?: string\n }\n anthropicApiKey?: string\n}\n\n/**\n * Return the current state of the working directory so the wizard can choose\n * between \"fresh install\" and \"add another\" flows.\n */\nexport function wizardState(baseDir: string = process.cwd()): {\n configExists: boolean\n agentCount: number\n channelCount: number\n nodeName?: string\n} {\n const file = resolve(baseDir, \"agentx.json\")\n if (!existsSync(file)) return { configExists: false, agentCount: 0, channelCount: 0 }\n try {\n const cfg = JSON.parse(readFileSync(file, \"utf-8\"))\n const agents = cfg.agents ? Object.keys(cfg.agents).length : 0\n const channels = cfg.channels\n ? Object.values(cfg.channels).filter((c: any) => c && c.enabled).length\n : 0\n return { configExists: true, agentCount: agents, channelCount: channels, nodeName: cfg.node?.name }\n } catch {\n return { configExists: false, agentCount: 0, channelCount: 0 }\n }\n}\n\n/**\n * Execute the wizard submission. Returns a short human-readable summary of\n * what was written. Throws on validation error — the caller surfaces the\n * message to the user.\n */\nexport function runWizard(payload: WizardPayload, baseDir: string = process.cwd()): {\n summary: string\n nextSteps: string[]\n backupPath?: string\n} {\n // --- validate ---\n if (!payload.nodeName?.trim()) throw new Error(\"Team name is required.\")\n if (!payload.agent?.id?.trim()) throw new Error(\"Agent id is required.\")\n if (!/^[a-z0-9][a-z0-9_-]*$/.test(payload.agent.id)) {\n throw new Error(\"Agent id must be lowercase (letters, digits, -, _) and start with a letter or digit.\")\n }\n if (!payload.agent.name?.trim()) throw new Error(\"Agent name is required.\")\n const mentions = splitMentions(payload.agent.triggerWords)\n if (mentions.length === 0) throw new Error(\"At least one trigger word is required.\")\n if (payload.agent.tier !== \"claude-code\" && payload.agent.tier !== \"sdk\") {\n throw new Error(\"AI engine must be claude-code or sdk.\")\n }\n\n const configPath = resolve(baseDir, \"agentx.json\")\n const envPath = resolve(baseDir, \".env\")\n const workspaceDir = resolve(baseDir, \"agents\", payload.agent.id)\n const state = wizardState(baseDir)\n\n // --- scaffold workspace ---\n mkdirSync(workspaceDir, { recursive: true })\n const claudeMd = resolve(workspaceDir, \"CLAUDE.md\")\n if (!existsSync(claudeMd)) {\n const personality = payload.agent.personality?.trim() || `You are ${payload.agent.name}, an assistant on the ${payload.nodeName} team.`\n writeFileSync(claudeMd, `# ${payload.agent.name}\\n\\n${personality}\\n`)\n }\n\n // --- build / mutate agentx.json ---\n const telegramTokenRef = payload.telegram\n ? `TG_${payload.agent.id.toUpperCase()}_BOT_TOKEN`\n : undefined\n\n let backupPath: string | undefined\n if (!state.configExists) {\n const cfg = buildFreshConfig(payload, workspaceDir, mentions, telegramTokenRef)\n writeAgentxConfig(cfg, { configPath })\n } else {\n const result = mutateAgentxConfig((cfg) => {\n cfg.node = cfg.node || {}\n if (!cfg.node.name) cfg.node.name = payload.nodeName\n if (!cfg.node.id) cfg.node.id = slugify(payload.nodeName) || \"team\"\n if (!cfg.node.bind) cfg.node.bind = \"127.0.0.1:18800\"\n cfg.node.defaultAgent = cfg.node.defaultAgent || payload.agent.id\n\n cfg.providers = cfg.providers || {}\n if (!cfg.providers.claude) cfg.providers.claude = { apiKey: \"${ANTHROPIC_API_KEY}\" }\n\n cfg.agents = cfg.agents || {}\n if (cfg.agents[payload.agent.id]) throw new Error(`Agent \"${payload.agent.id}\" already exists.`)\n cfg.agents[payload.agent.id] = agentBlock(payload, workspaceDir, mentions)\n\n if (payload.telegram) {\n cfg.channels = cfg.channels || {}\n cfg.channels.telegram = cfg.channels.telegram || { enabled: true, accounts: {}, policy: { dm: \"pair\", group: \"mention-required\" } }\n cfg.channels.telegram.enabled = true\n cfg.channels.telegram.accounts = cfg.channels.telegram.accounts || {}\n cfg.channels.telegram.accounts[payload.telegram.accountId] = telegramAccountBlock(payload, telegramTokenRef!)\n }\n\n return { summary: `added agent \"${payload.agent.id}\"` + (payload.telegram ? \" + telegram account\" : \"\") }\n }, { configPath })\n backupPath = result.backupPath\n }\n\n // --- write / append .env ---\n ensureEnvEntries(envPath, payload, telegramTokenRef)\n\n const nextSteps: string[] = []\n if (payload.anthropicApiKey || !existsSync(resolve(baseDir, \".env\"))) {\n nextSteps.push(\"Double-check the API keys in .env\")\n }\n if (payload.agent.tier === \"claude-code\") {\n nextSteps.push(\"Run claude --version to confirm Claude Code is installed (needed for the claude-code engine)\")\n }\n nextSteps.push(\"Start the daemon: agentx daemon start\")\n nextSteps.push(\"Open the dashboard: http://127.0.0.1:4202\")\n\n return {\n summary: state.configExists\n ? `Added agent \"${payload.agent.id}\" to your team.`\n : `Initialized your team with first agent \"${payload.agent.id}\".`,\n nextSteps,\n backupPath,\n }\n}\n\nfunction splitMentions(s: string): string[] {\n return (s || \"\")\n .split(/[,\\s]+/)\n .map((m) => m.trim())\n .filter(Boolean)\n // Normalise: ensure @-prefix entries stay, plain ones are kept as-is.\n .filter((m, i, a) => a.indexOf(m) === i)\n}\n\nfunction slugify(s: string): string {\n return (s || \"\").toLowerCase().replace(/[^a-z0-9-]+/g, \"-\").replace(/^-+|-+$/g, \"\")\n}\n\nfunction buildFreshConfig(payload: WizardPayload, workspaceDir: string, mentions: string[], telegramTokenRef?: string) {\n const id = slugify(payload.nodeName) || \"team\"\n const cfg: any = {\n node: { id, name: payload.nodeName, bind: \"127.0.0.1:18800\", defaultAgent: payload.agent.id },\n providers: { claude: { apiKey: \"${ANTHROPIC_API_KEY}\" } },\n agents: { [payload.agent.id]: agentBlock(payload, workspaceDir, mentions) },\n channels: {},\n crons: {},\n mesh: { enabled: false, peers: [], discovery: \"static\", healthCheck: { interval: 60, timeout: 10 } },\n dashboard: { enabled: true, port: 4202, bind: \"127.0.0.1\", daemonUrl: \"http://localhost:18800\" },\n boards: [],\n }\n if (payload.telegram && telegramTokenRef) {\n cfg.channels.telegram = {\n enabled: true,\n accounts: { [payload.telegram.accountId]: telegramAccountBlock(payload, telegramTokenRef) },\n policy: { dm: \"pair\", group: \"mention-required\" },\n }\n } else {\n cfg.channels.telegram = { enabled: false, accounts: {}, policy: { dm: \"pair\", group: \"mention-required\" } }\n }\n return cfg\n}\n\nfunction agentBlock(payload: WizardPayload, workspaceDir: string, mentions: string[]) {\n const block: any = {\n name: payload.agent.name,\n workspace: workspaceDir,\n tier: payload.agent.tier,\n mentions,\n maxConcurrent: 2,\n permissionMode: \"default\",\n }\n if (payload.agent.model) block.model = payload.agent.model\n if (payload.agent.personality?.trim()) block.systemPrompt = payload.agent.personality.trim().slice(0, 400)\n return block\n}\n\nfunction telegramAccountBlock(payload: WizardPayload, tokenRef: string) {\n return {\n botToken: \"${\" + tokenRef + \"}\",\n botUsername: payload.telegram?.botUsername || \"\",\n agentBinding: payload.agent.id,\n }\n}\n\nfunction ensureEnvEntries(envPath: string, payload: WizardPayload, telegramTokenRef?: string): void {\n const existing = existsSync(envPath) ? readFileSync(envPath, \"utf-8\") : \"\"\n const lines: string[] = []\n\n if (payload.anthropicApiKey && !existing.includes(\"ANTHROPIC_API_KEY=\")) {\n lines.push(`ANTHROPIC_API_KEY=${payload.anthropicApiKey}`)\n }\n if (payload.telegram && telegramTokenRef && !existing.includes(`${telegramTokenRef}=`)) {\n lines.push(`${telegramTokenRef}=${payload.telegram.botToken}`)\n }\n\n if (!existsSync(envPath)) {\n const header = [\n \"# AgentX environment variables\",\n \"# (Never commit this file — it contains secrets.)\",\n \"\",\n ]\n writeFileSync(envPath, [...header, ...lines, \"\"].join(\"\\n\"))\n return\n }\n if (lines.length > 0) {\n const sep = existing.endsWith(\"\\n\") ? \"\" : \"\\n\"\n appendFileSync(envPath, sep + lines.join(\"\\n\") + \"\\n\")\n }\n}\n\n// --- HTTP handlers used by board-dashboard.ts ---\n\nexport function handleWizardGet(_req: IncomingMessage, res: ServerResponse): void {\n const state = wizardState()\n const html = renderWizardHtml(state)\n res.writeHead(200, { \"Content-Type\": \"text/html; charset=utf-8\" })\n res.end(html)\n}\n\nexport async function handleWizardPost(req: IncomingMessage, res: ServerResponse): Promise<void> {\n let raw = \"\"\n await new Promise<void>((ok) => { req.on(\"data\", (c) => { raw += c }); req.on(\"end\", () => ok()) })\n let payload: WizardPayload\n try { payload = JSON.parse(raw) }\n catch { res.writeHead(400, { \"Content-Type\": \"application/json\" }); res.end(JSON.stringify({ error: \"Invalid JSON\" })); return }\n try {\n const result = runWizard(payload)\n res.writeHead(200, { \"Content-Type\": \"application/json\" })\n res.end(JSON.stringify(result))\n } catch (e: any) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" })\n res.end(JSON.stringify({ error: e.message || \"wizard failed\" }))\n }\n}\n\nfunction renderWizardHtml(state: ReturnType<typeof wizardState>): string {\n const heading = state.configExists\n ? (state.agentCount === 0 ? \"Add your first agent\" : \"Add another agent\")\n : \"Set up your team\"\n const defaultNode = state.nodeName || hostname().replace(/\\.local$/, \"\")\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>AgentX — Setup</title>\n<style>\n:root{--bg:#0b0d14;--card:#151823;--border:#2a2d3a;--text:#e6e8ef;--muted:#8b8fa3;--accent:#6366f1;--green:#22c55e;--red:#ef4444}\n*{box-sizing:border-box}\nhtml,body{margin:0;min-height:100%;background:var(--bg);color:var(--text);font:14px/1.5 -apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif}\nheader{padding:18px 24px;border-bottom:1px solid var(--border);background:#10131c;display:flex;align-items:center;gap:12px}\nheader .brand{font-weight:600;color:var(--accent);font-size:15px}\nheader .sub{color:var(--muted);font-weight:500}\n.spacer{flex:1}\na.link{color:var(--muted);text-decoration:none;font-size:13px;padding:4px 10px;border:1px solid var(--border);border-radius:6px}\na.link:hover{color:var(--accent);border-color:var(--accent)}\nmain{max-width:620px;margin:0 auto;padding:32px 22px 60px}\nh1{font-size:24px;margin:0 0 6px}\n.lead{color:var(--muted);margin:0 0 26px}\nsection.step{background:var(--card);border:1px solid var(--border);border-radius:10px;padding:20px 22px;margin:14px 0}\nsection.step h2{font-size:14px;font-weight:600;margin:0 0 14px;color:var(--text);display:flex;align-items:center;gap:10px}\nsection.step h2 .num{background:var(--accent);color:#fff;width:22px;height:22px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;font-size:11px;font-weight:700}\nlabel{display:block;margin:10px 0 4px;font-size:12px;color:var(--muted)}\nlabel .hint{color:var(--muted);font-weight:400;font-size:11px;margin-left:6px}\ninput,textarea,select{width:100%;background:#0e1119;color:var(--text);border:1px solid var(--border);border-radius:6px;padding:8px 10px;font:13px/1.4 inherit}\ninput:focus,textarea:focus,select:focus{outline:none;border-color:var(--accent)}\ntextarea{resize:vertical;min-height:70px;font-family:inherit}\n.row{display:flex;gap:10px}\n.row > *{flex:1}\n.toggle{display:flex;align-items:center;gap:8px;margin:14px 0 4px;font-size:13px;color:var(--text);cursor:pointer}\n.toggle input{width:auto}\n.optional{border-left:2px solid var(--border);padding-left:14px;margin-top:10px;display:none}\n.optional.show{display:block}\nfooter.actions{display:flex;gap:10px;padding:20px 0;align-items:center}\nbutton.primary{background:var(--accent);color:#fff;border:none;border-radius:6px;padding:10px 20px;font-size:14px;font-weight:600;cursor:pointer}\nbutton.primary:hover{filter:brightness(1.1)}\nbutton.primary:disabled{opacity:0.5;cursor:not-allowed}\nbutton.ghost{background:transparent;color:var(--muted);border:1px solid var(--border);border-radius:6px;padding:9px 16px;cursor:pointer}\n.hint-block{font-size:11px;color:var(--muted);margin-top:6px;line-height:1.55}\n.hint-block code{background:#10131c;padding:1px 6px;border-radius:3px;color:var(--text);font-family:ui-monospace,monospace;font-size:11px}\n#msg{margin:14px 0;padding:12px 16px;border-radius:6px;font-size:13px;display:none}\n#msg.ok{display:block;background:rgba(34,197,94,0.12);color:var(--green);border:1px solid rgba(34,197,94,0.3)}\n#msg.err{display:block;background:rgba(239,68,68,0.12);color:var(--red);border:1px solid rgba(239,68,68,0.3)}\n#next-steps{margin-top:12px;padding-left:20px}\n#next-steps li{margin:4px 0}\n#next-steps code{background:#10131c;padding:1px 6px;border-radius:3px;color:var(--text);font-family:ui-monospace,monospace;font-size:12px}\n.config-banner{background:rgba(99,102,241,0.12);border:1px solid rgba(99,102,241,0.3);border-radius:6px;padding:10px 14px;margin-bottom:16px;color:var(--muted);font-size:12px}\n.config-banner b{color:var(--text)}\n</style>\n</head>\n<body>\n<header>\n <div class=\"brand\">AgentX <span class=\"sub\">· Setup</span></div>\n <div class=\"spacer\"></div>\n <a href=\"/glossary\" class=\"link\">? Glossary</a>\n <a href=\"/live\" class=\"link\">Skip — open dashboard</a>\n</header>\n<main>\n <h1>${escapeHtml(heading)}</h1>\n <p class=\"lead\">Fill in the basics below. We'll write <code>agentx.json</code>, set up a folder for your first agent, and point you at the next step.</p>\n\n ${state.configExists ? `<div class=\"config-banner\">Existing install: <b>${state.agentCount}</b> agent(s), <b>${state.channelCount}</b> channel(s) already configured. This wizard will add to it.</div>` : \"\"}\n\n <div id=\"msg\"></div>\n\n <form id=\"wizard\" autocomplete=\"off\">\n <section class=\"step\">\n <h2><span class=\"num\">1</span>Team basics</h2>\n <label>Team name<span class=\"hint\">(what you'd call this AgentX install)</span></label>\n <input name=\"nodeName\" value=\"${escapeHtml(defaultNode)}\" placeholder=\"My Team\" required />\n </section>\n\n <section class=\"step\">\n <h2><span class=\"num\">2</span>First agent</h2>\n <div class=\"row\">\n <div>\n <label>Agent name</label>\n <input name=\"agentName\" value=\"Assistant\" placeholder=\"Support Bot\" required />\n </div>\n <div>\n <label>Agent id<span class=\"hint\">(lowercase, no spaces)</span></label>\n <input name=\"agentId\" value=\"assistant\" pattern=\"[a-z0-9][a-z0-9_-]*\" required />\n </div>\n </div>\n <label>Trigger words<span class=\"hint\">(words that activate this agent — comma or space separated, e.g. <code>@support, support</code>)</span></label>\n <input name=\"triggerWords\" value=\"@assistant, assistant\" required />\n <div class=\"row\">\n <div>\n <label>AI engine</label>\n <select name=\"tier\">\n <option value=\"claude-code\">Claude Code (recommended)</option>\n <option value=\"sdk\">Anthropic API (BYO key)</option>\n </select>\n </div>\n <div>\n <label>Model<span class=\"hint\">(optional)</span></label>\n <input name=\"model\" value=\"claude-sonnet-4-6\" placeholder=\"claude-sonnet-4-6\" />\n </div>\n </div>\n <label>Personality / instructions<span class=\"hint\">(optional — what this agent does, in plain English)</span></label>\n <textarea name=\"personality\" placeholder=\"You are a support agent for Acme Co. Answer customer questions about our product, be friendly, keep replies short.\"></textarea>\n </section>\n\n <section class=\"step\">\n <h2><span class=\"num\">3</span>First channel</h2>\n <label class=\"toggle\"><input type=\"checkbox\" id=\"enableTelegram\" /> Connect Telegram now</label>\n <div class=\"optional\" id=\"telegramFields\">\n <label>Account id<span class=\"hint\">(a label you choose — e.g. <code>support</code>)</span></label>\n <input name=\"telegramAccountId\" value=\"default\" />\n <label>Bot token<span class=\"hint\">(from <a href=\"https://t.me/BotFather\" target=\"_blank\" style=\"color:var(--accent)\">@BotFather</a>)</span></label>\n <input name=\"telegramBotToken\" placeholder=\"123456:ABCdef...\" />\n <label>Bot username<span class=\"hint\">(optional — e.g. <code>my_support_bot</code>)</span></label>\n <input name=\"telegramBotUsername\" placeholder=\"my_support_bot\" />\n </div>\n </section>\n\n <section class=\"step\">\n <h2><span class=\"num\">4</span>Anthropic API key</h2>\n <label>API key<span class=\"hint\">(optional if you'll paste it into <code>.env</code> manually, or if you're only using Claude Code)</span></label>\n <input name=\"anthropicApiKey\" type=\"password\" placeholder=\"sk-ant-…\" autocomplete=\"off\" />\n <div class=\"hint-block\">\n The key is written to <code>.env</code> as <code>ANTHROPIC_API_KEY</code>. AgentX never transmits it — everything stays on this machine.\n </div>\n </section>\n\n <footer class=\"actions\">\n <button type=\"submit\" class=\"primary\" id=\"submitBtn\">Save and continue</button>\n <button type=\"button\" class=\"ghost\" onclick=\"window.location.href='/live'\">Skip, I'll do it later</button>\n </footer>\n </form>\n</main>\n<script>\ndocument.getElementById('enableTelegram').addEventListener('change', (e) => {\n document.getElementById('telegramFields').classList.toggle('show', e.target.checked);\n});\ndocument.getElementById('wizard').addEventListener('submit', async (e) => {\n e.preventDefault();\n const f = e.target;\n const msg = document.getElementById('msg');\n const btn = document.getElementById('submitBtn');\n msg.className = ''; msg.style.display = 'none';\n btn.disabled = true; btn.textContent = 'Saving…';\n const telegramEnabled = document.getElementById('enableTelegram').checked;\n const payload = {\n nodeName: f.nodeName.value.trim(),\n agent: {\n id: f.agentId.value.trim(),\n name: f.agentName.value.trim(),\n triggerWords: f.triggerWords.value.trim(),\n tier: f.tier.value,\n model: f.model.value.trim() || undefined,\n personality: f.personality.value.trim() || undefined,\n },\n anthropicApiKey: f.anthropicApiKey.value.trim() || undefined,\n };\n if (telegramEnabled) {\n payload.telegram = {\n accountId: f.telegramAccountId.value.trim() || 'default',\n botToken: f.telegramBotToken.value.trim(),\n botUsername: f.telegramBotUsername.value.trim() || undefined,\n };\n if (!payload.telegram.botToken) {\n msg.className = 'err'; msg.textContent = 'Bot token is required when Telegram is enabled.';\n btn.disabled = false; btn.textContent = 'Save and continue'; return;\n }\n }\n try {\n const r = await fetch('/api/setup', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'agentx-wizard' },\n body: JSON.stringify(payload),\n });\n const data = await r.json();\n if (!r.ok) throw new Error(data.error || ('HTTP ' + r.status));\n const steps = data.nextSteps.map((s) => '<li>' + s.replace(/\\`([^\\`]+)\\`/g, '<code>$1</code>') + '</li>').join('');\n msg.className = 'ok';\n msg.innerHTML = '<b>' + data.summary + '</b><ol id=\"next-steps\">' + steps + '</ol>';\n btn.textContent = 'Done';\n btn.disabled = true;\n } catch (err) {\n msg.className = 'err'; msg.textContent = err.message;\n btn.disabled = false; btn.textContent = 'Save and continue';\n }\n});\n</script>\n</body>\n</html>`\n}\n\nfunction escapeHtml(s: string): string {\n return String(s || \"\").replace(/[&<>\"']/g, (c) => ({ \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\", '\"': \"&quot;\", \"'\": \"&#39;\" }[c] as string))\n}\n","import { existsSync, readFileSync, writeFileSync, copyFileSync } from \"fs\"\nimport { resolve } from \"path\"\nimport { loadDaemonConfig } from \"./config\"\n\n// --- Shared helpers for safe agentx.json mutation ---\n//\n// Multiple CLI commands and the dashboard admin UI all need to read → tweak →\n// write agentx.json without corrupting it on failure. This module owns the\n// atomic-write + backup + best-effort-reload pattern so every caller gets the\n// same guarantees.\n\nexport interface MutateOptions {\n /** Absolute path to the target agentx.json. Defaults to ./agentx.json. */\n configPath?: string\n /** When true, don't write a timestamped backup. Use for operations that are\n * intrinsically reversible (or when the caller already took a snapshot). */\n skipBackup?: boolean\n /** When true, don't POST /reload after writing. Handy in tests. */\n skipReload?: boolean\n}\n\nexport interface MutateResult<T = void> {\n summary: string\n payload?: T\n}\n\n/**\n * Read agentx.json, let the mutator tweak it in-place, then atomically write\n * the result back to disk. The mutator returns a short human-readable summary\n * of what changed and may optionally return a payload for the caller to relay.\n *\n * If the mutator throws, nothing is written. If writing succeeds but the file\n * doesn't round-trip as JSON, we restore from backup and rethrow.\n */\nexport function mutateAgentxConfig<T = void>(\n mutator: (cfg: any) => MutateResult<T> | string,\n opts: MutateOptions = {},\n): { summary: string; payload?: T; backupPath?: string } {\n const file = opts.configPath || resolve(process.cwd(), \"agentx.json\")\n if (!existsSync(file)) throw new Error(`agentx.json not found at ${file}`)\n\n const cfg = JSON.parse(readFileSync(file, \"utf-8\"))\n const raw = mutator(cfg)\n const result: MutateResult<T> = typeof raw === \"string\" ? { summary: raw } : raw\n\n const json = JSON.stringify(cfg, null, 2) + \"\\n\"\n const tmp = file + \".tmp\"\n writeFileSync(tmp, json, \"utf-8\")\n try {\n JSON.parse(readFileSync(tmp, \"utf-8\"))\n } catch (e: any) {\n throw new Error(`config did not round-trip cleanly: ${e.message}`)\n }\n\n let backupPath: string | undefined\n if (!opts.skipBackup) {\n backupPath = `${file}.bak.${Date.now()}`\n copyFileSync(file, backupPath)\n }\n writeFileSync(file, json, \"utf-8\")\n\n if (!opts.skipReload) triggerLocalReload().catch(() => { /* daemon may be off */ })\n\n return { summary: result.summary, payload: result.payload, backupPath }\n}\n\n/**\n * Write a fresh agentx.json from scratch. Used by the setup wizard on a truly\n * empty working directory. Fails if a file already exists (callers must pass\n * the overwrite flag explicitly — we don't want to silently clobber an\n * operator's hand-written config).\n */\nexport function writeAgentxConfig(\n cfg: unknown,\n opts: { configPath?: string; overwrite?: boolean } = {},\n): { file: string } {\n const file = opts.configPath || resolve(process.cwd(), \"agentx.json\")\n if (existsSync(file) && !opts.overwrite) {\n throw new Error(`agentx.json already exists at ${file}. Pass overwrite=true to replace it.`)\n }\n writeFileSync(file, JSON.stringify(cfg, null, 2) + \"\\n\", \"utf-8\")\n return { file }\n}\n\n/**\n * POST to the primary daemon's /reload endpoint so it picks up config changes\n * without a full restart. Silently no-ops if the daemon isn't running.\n */\nasync function triggerLocalReload(): Promise<void> {\n try {\n const cfg = loadDaemonConfig()\n const url = cfg.dashboard.daemonUrl?.replace(/\\/+$/, \"\") || \"http://127.0.0.1:18800\"\n await fetch(`${url}/reload`, { method: \"POST\" }).catch(() => null)\n } catch { /* config didn't load — leave it */ }\n}\n","import { createHash, randomBytes } from \"crypto\"\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\"\nimport { resolve, dirname } from \"path\"\n\n// --- Scoped API tokens ---\n//\n// Tokens let external services (other agentx instances, webhook senders, custom\n// apps) talk to this daemon with explicit scopes instead of sharing a single\n// all-powerful dashboard token.\n//\n// We only ever store the sha256 of each token + its printable prefix. The full\n// token is shown exactly once, at creation time, and the operator copies it.\n// Format: agx_live_<hex(32)> — prefix lets secret-scanners catch leaks.\n//\n// Scopes (string equality; \"agent:*\" grants any agent):\n// dashboard:read — /api/live, /api/agents, /api/task/history\n// dashboard:write — /api/admin/*, config mutation endpoints\n// agent:<id> — /api/public/agents/<id>/messages\n// agent:* — any agent\n// mesh:peer — cross-node mesh (forward-compatible — not enforced yet)\n\nexport const TOKEN_PREFIX = \"agx_live_\"\nconst DEFAULT_FILE = \".agentx/tokens.json\"\n\nexport interface TokenRecord {\n id: string // short public id (shown in lists)\n name: string\n prefix: string // full printable prefix (TOKEN_PREFIX + first-4 chars of body)\n hash: string // sha256 of the full token\n scopes: string[]\n createdAt: string\n expiresAt?: string\n revokedAt?: string\n lastUsedAt?: string\n}\n\nexport class TokenStore {\n private file: string\n\n constructor(baseDir: string = process.cwd(), fileRel: string = DEFAULT_FILE) {\n this.file = resolve(baseDir, fileRel)\n }\n\n private load(): TokenRecord[] {\n if (!existsSync(this.file)) return []\n try { return JSON.parse(readFileSync(this.file, \"utf-8\")) as TokenRecord[] } catch { return [] }\n }\n\n private save(records: TokenRecord[]): void {\n mkdirSync(dirname(this.file), { recursive: true })\n writeFileSync(this.file, JSON.stringify(records, null, 2) + \"\\n\", \"utf-8\")\n }\n\n /**\n * Mint a new token. Returns the full token string AND the record.\n * The full token is the only place the secret is ever exposed — the caller\n * must surface it to the operator immediately; after this call the store only\n * knows the hash.\n */\n create(input: {\n name: string\n scopes: string[]\n expiresInDays?: number\n }): { token: string; record: TokenRecord } {\n if (!input.name?.trim()) throw new Error(\"Token name is required.\")\n if (!Array.isArray(input.scopes) || input.scopes.length === 0) {\n throw new Error(\"At least one scope is required.\")\n }\n for (const s of input.scopes) {\n if (!isValidScope(s)) throw new Error(`Invalid scope: ${s}`)\n }\n const body = randomBytes(32).toString(\"hex\")\n const token = TOKEN_PREFIX + body\n const record: TokenRecord = {\n id: \"tok_\" + randomBytes(4).toString(\"hex\"),\n name: input.name.trim(),\n prefix: TOKEN_PREFIX + body.slice(0, 4),\n hash: hashToken(token),\n scopes: [...input.scopes],\n createdAt: new Date().toISOString(),\n expiresAt: input.expiresInDays\n ? new Date(Date.now() + input.expiresInDays * 86400 * 1000).toISOString()\n : undefined,\n }\n const records = this.load()\n records.push(record)\n this.save(records)\n return { token, record }\n }\n\n list(): TokenRecord[] {\n return this.load()\n }\n\n revoke(id: string): TokenRecord | null {\n const records = this.load()\n const rec = records.find((r) => r.id === id)\n if (!rec) return null\n rec.revokedAt = new Date().toISOString()\n this.save(records)\n return rec\n }\n\n /**\n * Look up a token string. Returns the record on success, null on unknown /\n * revoked / expired. Updates lastUsedAt opportunistically (write-on-success).\n */\n verify(token: string): TokenRecord | null {\n if (!token || !token.startsWith(TOKEN_PREFIX)) return null\n const hash = hashToken(token)\n const records = this.load()\n const rec = records.find((r) => r.hash === hash)\n if (!rec) return null\n if (rec.revokedAt) return null\n if (rec.expiresAt && Date.parse(rec.expiresAt) < Date.now()) return null\n // Throttle lastUsedAt updates to at most once per minute to avoid disk churn.\n const now = Date.now()\n const last = rec.lastUsedAt ? Date.parse(rec.lastUsedAt) : 0\n if (now - last > 60_000) {\n rec.lastUsedAt = new Date(now).toISOString()\n try { this.save(records) } catch { /* best-effort */ }\n }\n return rec\n }\n}\n\n/**\n * Check whether a record has a given required scope. \"agent:<id>\" required\n * matches either itself or \"agent:*\". \"dashboard:write\" implicitly grants\n * \"dashboard:read\" for convenience.\n */\nexport function recordHasScope(record: TokenRecord, required: string): boolean {\n if (record.scopes.includes(required)) return true\n if (required.startsWith(\"agent:\") && record.scopes.includes(\"agent:*\")) return true\n if (required === \"dashboard:read\" && record.scopes.includes(\"dashboard:write\")) return true\n return false\n}\n\nfunction isValidScope(s: string): boolean {\n return (\n s === \"dashboard:read\" ||\n s === \"dashboard:write\" ||\n s === \"agent:*\" ||\n /^agent:[a-z0-9][a-z0-9_-]*$/.test(s) ||\n s === \"mesh:peer\"\n )\n}\n\nexport function hashToken(token: string): string {\n return \"sha256:\" + createHash(\"sha256\").update(token).digest(\"hex\")\n}\n\n/**\n * Extract a bearer token from the request headers. Falls back to the\n * `?token=` query param so EventSource (which can't set headers in most\n * browsers) can still authenticate.\n */\nexport function extractToken(req: { headers: Record<string, string | string[] | undefined>; url?: string }): string | null {\n const auth = req.headers[\"authorization\"]\n const authHeader = Array.isArray(auth) ? auth[0] : auth\n if (authHeader && authHeader.toLowerCase().startsWith(\"bearer \")) {\n return authHeader.slice(7).trim()\n }\n if (req.url) {\n try {\n const u = new URL(req.url, \"http://localhost\")\n const tok = u.searchParams.get(\"token\")\n if (tok) return tok\n } catch { /* */ }\n }\n return null\n}\n"],"mappings":"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","names":["existsSync","readFileSync","writeFileSync","appendFileSync","mkdirSync","resolve","hostname","init_config","existsSync","readFileSync","writeFileSync","copyFileSync","resolve","mutateAgentxConfig","mutator","opts","file","cfg","raw","result","json","tmp","e","backupPath","triggerLocalReload","writeAgentxConfig","url","loadDaemonConfig","wizardState","baseDir","file","resolve","existsSync","cfg","readFileSync","agents","channels","c","runWizard","payload","mentions","splitMentions","configPath","envPath","workspaceDir","state","mkdirSync","claudeMd","personality","writeFileSync","telegramTokenRef","backupPath","mutateAgentxConfig","slugify","agentBlock","telegramAccountBlock","buildFreshConfig","writeAgentxConfig","ensureEnvEntries","nextSteps","s","m","i","block","tokenRef","existing","lines","sep","appendFileSync","handleWizardGet","_req","res","html","renderWizardHtml","handleWizardPost","req","raw","ok","result","e","heading","defaultNode","hostname","escapeHtml","createHash","randomBytes","existsSync","mkdirSync","readFileSync","writeFileSync","resolve","dirname","TOKEN_PREFIX","DEFAULT_FILE","TokenStore","baseDir","fileRel","records","input","isValidScope","body","token","record","hashToken","id","rec","hash","r","now","last","recordHasScope","required","s","extractToken","req","auth","authHeader","tok"]}
@@ -0,0 +1,104 @@
1
+ import{e as _}from"./chunk-WZEYII2S.js";import{b as L}from"./chunk-64P4QHSZ.js";import{c as D,d as S}from"./chunk-QOYAX2FT.js";import{readFileSync as y,writeFileSync as b,appendFileSync as j,existsSync as k,mkdirSync as v,readdirSync as A,statSync as R}from"fs";import{resolve as h,join as T,relative as w,dirname as O}from"path";var $=class{baseDir;rawDir;log;constructor(t=h(process.cwd(),".agentx/wiki"),e=console.error.bind(console,"[wiki]")){this.baseDir=t,this.rawDir=h(t,"raw/entries"),this.log=e,v(this.rawDir,{recursive:!0}),v(h(t,"raw"),{recursive:!0}),this.ensureSchema()}canRead(t,e){return t.access==="public"?!0:e?!!(t.owner===e||t.access==="shared"&&t.sharedWith?.includes(e)):!1}canWrite(t,e){return t.owner===e}addEntry(t){let e=`${t.date}_${t.id}.md`,r=h(this.rawDir,e),i=["---",`id: ${t.id}`,`date: ${t.date}`,`agent: ${t.agentId}`,`source: ${t.source}`];if(t.sourceContext&&i.push(`context: ${t.sourceContext}`),t.meta)for(let[n,s]of Object.entries(t.meta))i.push(`${n}: ${JSON.stringify(s)}`);return i.push("---","",t.content),b(r,i.join(`
2
+ `)),this.appendLog("ingest",`${t.id} from ${t.agentId} via ${t.source}`),e}listEntries(t){if(!k(this.rawDir))return[];let e=A(this.rawDir).filter(i=>i.endsWith(".md")).sort(),r=[];for(let i of e){let n=y(h(this.rawDir,i),"utf-8"),s=this.parseEntry(n,i);s&&(t?.agentId&&s.agentId!==t.agentId||t?.after&&s.date<t.after||t?.before&&s.date>t.before||r.push(s))}return r}parseEntry(t,e){let r=t.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);if(!r)return null;let i=r[1],n=r[2].trim(),s=o=>i.match(new RegExp(`^${o}:\\s*(.+)$`,"m"))?.[1]?.trim()||"";return{id:s("id")||e.replace(".md",""),date:s("date"),agentId:s("agent"),source:s("source"),sourceContext:s("context")||void 0,content:n}}writeArticle(t,e,r,i){let n=this.readArticle(t);if(n&&!this.canWrite(n.meta,i))return this.log(`Permission denied: "${i}" cannot write "${t}" (owner: ${n.meta.owner})`),!1;e.type&&!L(e.type)&&(this.log(`Dropping invalid article type "${e.type}" for "${t}" \u2014 enum is [person, project, place, concept, event, decision, pattern]`),e={...e,type:void 0});let s=h(this.baseDir,t);n&&this.saveVersion(t,s),v(O(s),{recursive:!0});let o=["---",`title: "${e.title}"`];e.type&&o.push(`type: ${e.type}`),e.related?.length&&o.push(`related: [${e.related.map(c=>`"${c}"`).join(", ")}]`),o.push(`tags: [${(e.tags||[]).map(c=>`"${c}"`).join(", ")}]`,`owner: ${e.owner}`,`access: ${e.access}`),e.sharedWith?.length&&o.push(`shared_with: [${e.sharedWith.map(c=>`"${c}"`).join(", ")}]`),o.push(`created: ${e.created}`,`last_updated: ${e.lastUpdated}`,`sources: [${e.sources.map(c=>`"${c}"`).join(", ")}]`),e.graphPath?.length&&o.push(`graph_path: [${e.graphPath.map(c=>`"${c}"`).join(", ")}]`),o.push("---","",r),b(s,o.join(`
3
+ `));let a=n?"update":"create";return this.appendLog(a,`${e.title} [${(e.tags||[]).slice(0,5).join(", ")}] by ${i} at ${t}`),!0}saveVersion(t,e){try{if(!k(e))return;let r=y(e,"utf-8"),i=new Date().toISOString().replace(/[:.]/g,"-"),n=h(this.baseDir,"_versions",t.replace(/\.md$/,""));v(n,{recursive:!0}),b(h(n,`${i}.md`),r)}catch{}}getVersions(t){let e=h(this.baseDir,"_versions",t.replace(/\.md$/,""));if(!k(e))return[];try{return A(e).filter(r=>r.endsWith(".md")).sort().reverse().map(r=>({timestamp:r.replace(/\.md$/,"").replace(/-/g,(i,n)=>n<19?n===10?"T":n===13||n===16?":":"-":i),path:h(e,r)}))}catch{return[]}}restoreVersion(t,e){let i=this.getVersions(t).find(n=>n.timestamp.startsWith(e));if(!i)return!1;try{let n=y(i.path,"utf-8"),s=h(this.baseDir,t);return this.saveVersion(t,s),b(s,n),this.appendLog("restore",`${t} restored to ${e}`),!0}catch{return!1}}readArticle(t){let e=h(this.baseDir,t);if(!k(e))return null;let r=y(e,"utf-8");return this.parseArticle(r,t)}readArticleAs(t,e){let r=this.readArticle(t);return!r||!this.canRead(r.meta,e)?null:r}parseArticle(t,e){let r=t.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);if(!r)return null;let i=r[1],n=r[2].trim(),s=p=>i.match(new RegExp(`^${p}:\\s*(.+)$`,"m"))?.[1]?.trim().replace(/^"(.*)"$/,"$1")||"",o=p=>{let l=i.match(new RegExp(`^${p}:\\s*\\[(.*)\\]$`,"m"));return l?l[1].split(",").map(f=>f.trim().replace(/^"(.*)"$/,"$1")).filter(Boolean):[]},a=s("type")||s("kind"),g=new Set(["person","project","place","concept","event","decision","pattern"]).has(a)?a:void 0,m=o("tags").filter((p,l,f)=>p&&f.indexOf(p)===l),d=o("related"),u=o("graph_path");return{meta:{title:s("title"),type:g,related:d.length?d:void 0,tags:m,owner:s("owner"),access:s("access")||"public",sharedWith:o("shared_with"),created:s("created"),lastUpdated:s("last_updated"),sources:o("sources"),graphPath:u.length?u:void 0},content:n,path:e}}listArticles(t){let e=[];return this.walkDir(this.baseDir,r=>{if(!r.endsWith(".md"))return;let i=w(this.baseDir,r);if(i.startsWith("raw/")||i.startsWith("_"))return;let n=this.readArticle(i);n&&this.canRead(n.meta,t)&&e.push(n)}),e}search(t,e,r=10){let i=t.toLowerCase(),n=[];if(this.walkDir(this.baseDir,d=>{if(!d.endsWith(".md"))return;let u=w(this.baseDir,d);if(u.startsWith("raw/")||u.startsWith("_"))return;let p=this.readArticle(u);!p||!this.canRead(p.meta,e)||n.push(p)}),n.length===0)return[];let s=n.map(d=>d.content),o=h(this.baseDir,"_bm25_cache.json"),a=S(s,o),c=D(t,a),g=new Map(c.map(d=>[d.docIndex,d.score]));return n.map((d,u)=>{let p=g.get(u)??0;return d.meta.title.toLowerCase().includes(i)&&(p+=10),d.meta.tags?.some(l=>l.toLowerCase().includes(i))&&(p+=5),{article:d,score:p}}).filter(d=>d.score>0).sort((d,u)=>u.score-d.score).slice(0,r).map(d=>d.article)}findRelevant(t,e,r=3,i,n={graph:.6,bm25:.4}){let s=[];if(this.walkDir(this.baseDir,l=>{if(!l.endsWith(".md"))return;let f=w(this.baseDir,l);if(f.startsWith("raw/")||f.startsWith("_"))return;let W=this.readArticle(f);!W||!this.canRead(W.meta,e)||s.push(W)}),s.length===0)return[];let o=s.map(l=>`${l.meta.title} ${(l.meta.tags||[]).join(" ")} ${l.content}`),a=h(this.baseDir,"_bm25_relevance_cache.json"),c=S(o,a),g=D(t,c),m=g.reduce((l,f)=>Math.max(l,f.score),0)||1,d=new Map(g.map(l=>[l.docIndex,l.score/m])),u=!!(i&&i.length>0);return s.map((l,f)=>{let W=d.get(f)??0,E=0;u&&l.meta.graphPath?.length&&(E=_(i,l.meta.graphPath));let I=u?n.graph*E+n.bm25*W:W;return{article:l,score:I}}).filter(l=>l.score>0).sort((l,f)=>f.score-l.score).slice(0,r).map(l=>l.article)}buildContext(t,e=4e3){if(t.length===0)return"";let r=["[Wiki Knowledge]"],i=0;for(let n of t){let s=`
4
+ ## ${n.meta.title} [${(n.meta.tags||[]).slice(0,3).join(", ")}]`,o=n.content.length>600?n.content.slice(0,600)+"...":n.content,a=s+`
5
+ `+o;if(i+a.length>e)break;r.push(a),i+=a.length}return r.push(`
6
+ [End Wiki Knowledge]`),r.join(`
7
+ `)}rebuildIndex(){let t=[],e=new Map;this.walkDir(this.baseDir,s=>{if(!s.endsWith(".md"))return;let o=w(this.baseDir,s);if(o.startsWith("raw/")||o.startsWith("_"))return;let a=this.readArticle(o);if(!a)return;let c=a.content.match(/\[\[([^\]]+)\]\]/g)||[];for(let m of c){let d=m.replace(/\[\[|\]\]/g,"");e.set(d,(e.get(d)||0)+1)}let g=[a.meta.title.toLowerCase()];a.meta.tags&&g.push(...a.meta.tags.map(m=>m.toLowerCase())),t.push({path:o,title:a.meta.title,type:a.meta.type,related:a.meta.related,tags:a.meta.tags||[],owner:a.meta.owner,access:a.meta.access,sharedWith:a.meta.sharedWith,aliases:g,backlinks:e.get(a.meta.title)||0,sources:a.meta.sources,lastUpdated:a.meta.lastUpdated})});let r={articles:t,lastRebuilt:new Date().toISOString()};b(h(this.baseDir,"_index.json"),JSON.stringify(r,null,2)),this.writeCatalog(t,r.lastRebuilt);let i=["# Wiki Index","",`Last rebuilt: ${r.lastRebuilt}`,""],n=new Map;for(let s of t){let o=s.access||"public",a=n.get(o)||[];a.push(s),n.set(o,a)}for(let[s,o]of Array.from(n.entries()).sort()){i.push(`## ${s}`,"");for(let a of o.sort((c,g)=>c.title.localeCompare(g.title))){let c=a.type?` [${a.type}]`:"";i.push(`- [${a.title}](${a.path})${c} \u2014 owner: ${a.owner}`)}i.push("")}return b(h(this.baseDir,"WIKI.md"),i.join(`
8
+ `)),this.log(`Index rebuilt: ${t.length} articles`),this.appendLog("rebuild-index",`${t.length} articles indexed`),r}writeCatalog(t,e){let r=["person","project","place","concept","event","decision","pattern","untyped"],i={person:"## People",project:"## Projects",place:"## Places",concept:"## Concepts",event:"## Events",decision:"## Decisions",pattern:"## Patterns",untyped:"## Untyped (needs migration)"},n=new Map;for(let o of t){let a=o.type||"untyped",c=n.get(a)||[];c.push(o),n.set(a,c)}let s=["# Wiki Content Catalog","",`_Last rebuilt: ${e}_`,"","Articles grouped by type. Agentic query walks this file first to pick candidates.",""];for(let o of r){let a=n.get(o);if(!(!a||a.length===0)){s.push(i[o]||`## ${o}`,"");for(let c of a.sort((g,m)=>g.title.localeCompare(m.title))){let g=c.access==="private"?" (private)":c.access==="shared"?" (shared)":"",m=c.related?.length?` \u2192 [[${c.related.slice(0,3).join("]], [[")}]]${c.related.length>3?", \u2026":""}`:"";s.push(`- [${c.title}](${c.path})${g}${m}`)}s.push("")}}b(h(this.baseDir,"_index.md"),s.join(`
9
+ `))}stats(){let t=0,e={},r={},i={};this.walkDir(this.baseDir,s=>{if(!s.endsWith(".md"))return;let o=w(this.baseDir,s);if(o.startsWith("raw/")||o.startsWith("_")||o==="WIKI.md")return;let a=this.readArticle(o);if(a){t++;for(let c of a.meta.tags||[])e[c]=(e[c]||0)+1;r[a.meta.access]=(r[a.meta.access]||0)+1,i[a.meta.owner]=(i[a.meta.owner]||0)+1}});let n=k(this.rawDir)?A(this.rawDir).filter(s=>s.endsWith(".md")).length:0;return{totalArticles:t,totalEntries:n,articlesByType:e,articlesByAccess:r,articlesByOwner:i}}appendLog(t,e){let r=h(this.baseDir,"log.md"),n=`## [${new Date().toISOString().replace("T"," ").slice(0,16)}] ${t} | ${e}
10
+
11
+ `;k(r)||b(r,`# Wiki Log
12
+
13
+ Chronological record of all wiki operations.
14
+
15
+ `),j(r,n)}getLog(t=20){let e=h(this.baseDir,"log.md");return k(e)?y(e,"utf-8").split(/^## /m).filter(n=>n.startsWith("[")).slice(-t).map(n=>n.trim()):[]}ensureSchema(){let t=h(this.baseDir,"_schema.md");if(k(t))return;b(t,`# Wiki Schema
16
+
17
+ A personal wiki maintained by LLMs. Karpathy pattern: plain files, aggressive tagging,
18
+ structure emerges from data. The wiki is a compounding artifact.
19
+
20
+ ## Structure
21
+
22
+ \`\`\`
23
+ wiki/
24
+ _schema.md # This file
25
+ worldview.md # YOUR mental model \u2014 the LLM reads this during absorb
26
+ raw/entries/ # Immutable raw sources (conversations, imports, anything)
27
+ [LLM-chosen dirs] # Structure emerges from data \u2014 the LLM picks paths
28
+ \`\`\`
29
+
30
+ The LLM decides how to organize articles. You describe YOUR world in worldview.md.
31
+
32
+ ## Article Format
33
+
34
+ \`\`\`yaml
35
+ ---
36
+ title: "Staging Deployment Process"
37
+ tags: ["deploy", "staging", "devops", "process", "2026-04-06"]
38
+ owner: devops-agent
39
+ access: public
40
+ created: 2026-04-06
41
+ last_updated: 2026-04-06
42
+ sources: ["entry-id-1", "entry-id-2"]
43
+ ---
44
+
45
+ Article content with [[wikilinks]] and section tags.
46
+
47
+ ## Deploy Steps
48
+ <!-- tags: process, runbook, staging -->
49
+ Content specific to this section...
50
+ \`\`\`
51
+
52
+ ## Tags
53
+
54
+ Tags are the primary context-narrowing mechanism. The more tags, the better.
55
+
56
+ Tag aggressively:
57
+ - **Who**: people, agents, teams involved
58
+ - **What**: project, client, topic, technology
59
+ - **When**: dates, periods, milestones
60
+ - **Where**: server, environment, channel
61
+ - **How**: process, decision, pattern
62
+ - **Your terms**: whatever vocabulary makes sense in YOUR world
63
+
64
+ Section tags (\`<!-- tags: ... -->\`) let different parts of an article
65
+ match different queries. A deploy article might have sections tagged
66
+ "runbook" and "incident" separately.
67
+
68
+ ## Operations
69
+
70
+ ### Absorb
71
+ Read raw entries \u2192 read worldview.md \u2192 tag aggressively \u2192 create/update articles.
72
+ Also identify GAPS: "We mention X but have no article for it."
73
+
74
+ ### Query
75
+ Match by tags first \u2192 then keyword within matched articles \u2192 synthesize.
76
+ If agent is working on a specific project, only send content tagged for that project.
77
+
78
+ ### Lint
79
+ Check for: orphan articles, broken [[wikilinks]], untagged articles,
80
+ articles over 100 lines that should split, mentioned-but-missing topics.
81
+
82
+ ## Worldview
83
+
84
+ Edit \`worldview.md\` to describe YOUR world. This is not a schema \u2014 it's your
85
+ mental model. The LLM reads it during absorb to understand where things go.
86
+
87
+ Example worldview.md:
88
+ \`\`\`
89
+ I'm the founder of Acme Corp.
90
+ We build software products.
91
+ Our agents: Marketing Bot, DevOps Bot, Coordinator.
92
+ Clients: ClientA (enterprise), ClientB (startup).
93
+ Infrastructure: local dev machine, cloud server.
94
+ We use GitLab for version control.
95
+ \`\`\`
96
+ `),this.log("Created _schema.md")}extractWikilinks(t){return(t.match(/\[\[([^\]]+)\]\]/g)||[]).map(r=>r.replace(/\[\[|\]\]/g,""))}buildBacklinks(){let t={};return this.walkDir(this.baseDir,e=>{if(!e.endsWith(".md"))return;let r=w(this.baseDir,e);if(r.startsWith("raw/")||r.startsWith("_")||r==="WIKI.md"||r==="log.md")return;let i=y(e,"utf-8"),n=this.extractWikilinks(i);for(let s of n)t[s]||(t[s]=[]),t[s].push(r)}),b(h(this.baseDir,"_backlinks.json"),JSON.stringify(t,null,2)),t}getBacklinks(t){let e=h(this.baseDir,"_backlinks.json");if(!k(e))return[];try{return JSON.parse(y(e,"utf-8"))[t]||[]}catch{return[]}}findByTags(t,e,r=10){let i=t.map(s=>s.toLowerCase()),n=[];return this.walkDir(this.baseDir,s=>{if(!s.endsWith(".md"))return;let o=w(this.baseDir,s);if(o.startsWith("raw/")||o.startsWith("_"))return;let a=this.readArticle(o);if(!a||e&&!this.canRead(a.meta,e))return;let c=(a.meta.tags||[]).map(u=>u.toLowerCase()),g=(a.content.match(/<!--\s*tags?:\s*([^>]+)\s*-->/gi)||[]).flatMap(u=>u.replace(/<!--\s*tags?:\s*|\s*-->/gi,"").split(",").map(p=>p.trim().toLowerCase())),m=[...c,...g],d=i.filter(u=>m.includes(u)).length;d>0&&n.push({article:a,score:d})}),n.sort((s,o)=>o.score-s.score).slice(0,r).map(s=>s.article)}getAllTags(){let t=new Map;return this.walkDir(this.baseDir,e=>{if(!e.endsWith(".md"))return;let r=w(this.baseDir,e);if(r.startsWith("raw/")||r.startsWith("_"))return;let i=this.readArticle(r);if(i?.meta.tags)for(let n of i.meta.tags)t.set(n,(t.get(n)||0)+1)}),t}getWorldview(){let t=h(this.baseDir,"worldview.md");return k(t)?y(t,"utf-8"):null}setWorldview(t){b(h(this.baseDir,"worldview.md"),t),this.appendLog("worldview","Updated worldview")}lint(){let t=[],e=new Set,r=new Map,i=new Map;this.walkDir(this.baseDir,s=>{if(!s.endsWith(".md"))return;let o=w(this.baseDir,s);if(o.startsWith("raw/")||o.startsWith("_")||o==="WIKI.md"||o==="log.md")return;let a=this.readArticle(o);if(!a)return;e.add(a.meta.title),i.set(a.meta.title,o);let c=this.extractWikilinks(a.content);r.set(o,c);let g=a.content.split(`
97
+ `).length;g>100&&t.push({type:"bloated",article:o,message:`${g} lines \u2014 consider splitting`}),a.meta.sources?.length||t.push({type:"unsourced",article:o,message:"No sources listed in frontmatter"}),g<10&&a.content.trim().length<100&&t.push({type:"stub",article:o,message:"Very short article \u2014 needs enrichment"})});for(let[s,o]of r)for(let a of o)e.has(a)||t.push({type:"broken-link",article:s,message:`[[${a}]] \u2014 target article not found`});let n=this.buildBacklinks();for(let[s,o]of i)(!n[s]||n[s].length===0)&&t.push({type:"orphan",article:o,message:`No other articles link to "${s}"`});return this.appendLog("lint",`Found ${t.length} issues`),t}getUnabsorbedEntries(){let t=new Set;return this.walkDir(this.baseDir,r=>{if(!r.endsWith(".md"))return;let i=w(this.baseDir,r);if(i.startsWith("raw/")||i.startsWith("_"))return;let n=this.readArticle(i);if(n?.meta.sources)for(let s of n.meta.sources)t.add(s)}),this.listEntries().filter(r=>!t.has(r.id))}buildAbsorbPrompt(t=20){let e=this.getUnabsorbedEntries().slice(0,t);if(e.length===0)return null;let r=e.map(i=>`[${i.date} ${i.agentId} via ${i.source}] ${i.content.slice(0,200)}`).join(`
98
+
99
+ `);return`/wiki absorb
100
+
101
+ There are ${e.length} unprocessed entries. Read each, understand what it means, and create or update wiki articles.
102
+
103
+ ${r}`}walkDir(t,e){if(k(t))for(let r of A(t)){let i=T(t,r);R(i).isDirectory()?this.walkDir(i,e):e(i)}}};import{resolve as x}from"path";import{existsSync as P,readdirSync as B,mkdirSync as U}from"fs";var M=class{baseDir;agentsDir;mode;sharedStore;agentStores=new Map;log;constructor(t=x(process.cwd(),".agentx/wiki"),e=console.error.bind(console,"[wiki-hub]"),r="graph"){this.baseDir=t,this.agentsDir=x(t,"agents"),this.mode=r,this.log=e,U(this.agentsDir,{recursive:!0}),this.sharedStore=new $(t,e)}getMode(){return this.mode}getAgentWiki(t){let e=`${t}:${this.mode}`;if(this.agentStores.has(e))return this.agentStores.get(e);let r=x(this.agentsDir,t,this.mode),i=new $(r,this.log);return this.agentStores.set(e,i),i}getSharedStore(){return this.sharedStore}listAgents(){let t=new Set,e=this.sharedStore.listEntries();for(let r of e)t.add(r.agentId);if(P(this.agentsDir))for(let r of B(this.agentsDir))!r.startsWith("_")&&!r.startsWith(".")&&t.add(r);return[...t].sort()}getAgentEntries(t){return this.sharedStore.listEntries({agentId:t})}getUnabsorbedEntries(t){let e=this.getAgentWiki(t),r=this.getAgentEntries(t),i=new Set,n=e.rebuildIndex();for(let s of n.articles)if(s.sources)for(let o of s.sources)i.add(o);return r.filter(s=>!i.has(s.id))}summary(){return this.listAgents().map(e=>{let r=this.getAgentEntries(e),n=this.getAgentWiki(e).rebuildIndex(),s=this.getUnabsorbedEntries(e);return{agentId:e,totalEntries:r.length,totalArticles:n.articles.length,unabsorbed:s.length,articles:n.articles}})}};export{$ as a,M as b};
104
+ //# sourceMappingURL=chunk-LEIYC3BF.js.map