agentix-cli 0.17.0 → 0.18.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 (149) hide show
  1. package/dist/agent-RDVGL2TW.js +2 -0
  2. package/dist/audit-Z47AQFFD.js +3 -0
  3. package/dist/audit-Z47AQFFD.js.map +1 -0
  4. package/dist/{board-dashboard-CXIVJKDP.js → board-dashboard-KX2CNOSY.js} +44 -44
  5. package/dist/board-dashboard-KX2CNOSY.js.map +1 -0
  6. package/dist/{chunk-EKTPCSN4.js → chunk-4ITQP3XL.js} +2 -2
  7. package/dist/chunk-53NRX2NA.js +2 -0
  8. package/dist/chunk-53NRX2NA.js.map +1 -0
  9. package/dist/{chunk-5WNGHDFC.js → chunk-ASSJ4Z6Z.js} +11 -11
  10. package/dist/{chunk-5WNGHDFC.js.map → chunk-ASSJ4Z6Z.js.map} +1 -1
  11. package/dist/chunk-BLUXL6HR.js +854 -0
  12. package/dist/chunk-BLUXL6HR.js.map +1 -0
  13. package/dist/chunk-BTAEYL3P.js +72 -0
  14. package/dist/chunk-BTAEYL3P.js.map +1 -0
  15. package/dist/chunk-DJEZ2HVU.js +5 -0
  16. package/dist/chunk-DJEZ2HVU.js.map +1 -0
  17. package/dist/{chunk-SRHLXZRF.js → chunk-DSCMCVTV.js} +3 -3
  18. package/dist/chunk-DSCMCVTV.js.map +1 -0
  19. package/dist/chunk-HPDHNIA3.js +2 -0
  20. package/dist/chunk-IM376SD6.js +13 -0
  21. package/dist/chunk-IM376SD6.js.map +1 -0
  22. package/dist/chunk-J4WKMKTP.js +5 -0
  23. package/dist/{chunk-MBEQASP3.js.map → chunk-J4WKMKTP.js.map} +1 -1
  24. package/dist/chunk-JGXE2YNV.js +46 -0
  25. package/dist/chunk-JGXE2YNV.js.map +1 -0
  26. package/dist/{chunk-TT2Q7YMZ.js → chunk-JXEPJ3YD.js} +6 -6
  27. package/dist/{chunk-TT2Q7YMZ.js.map → chunk-JXEPJ3YD.js.map} +1 -1
  28. package/dist/chunk-LY7JGDV7.js +6 -0
  29. package/dist/chunk-LY7JGDV7.js.map +1 -0
  30. package/dist/chunk-SR3X25TN.js +3 -0
  31. package/dist/chunk-SR3X25TN.js.map +1 -0
  32. package/dist/{chunk-63UYRJIC.js → chunk-TRQSP67X.js} +2 -2
  33. package/dist/chunk-U364GNQX.js +719 -0
  34. package/dist/chunk-U364GNQX.js.map +1 -0
  35. package/dist/chunk-VJUTQAPJ.js +2 -0
  36. package/dist/chunk-VJUTQAPJ.js.map +1 -0
  37. package/dist/cli.js +1 -1
  38. package/dist/cli.js.map +1 -1
  39. package/dist/{compaction-X46EQEPS.js → compaction-S5H6DTOR.js} +4 -4
  40. package/dist/config-UOHEG4BW.js +2 -0
  41. package/dist/config-mutator-KSEBWKJH.js +2 -0
  42. package/dist/{context-planner-G2J7FNNU.js → context-planner-2RGHJHSO.js} +3 -3
  43. package/dist/debug-TGAP7E7W.js +2 -0
  44. package/dist/discover-OATU7SHI.js +4 -0
  45. package/dist/discover-OATU7SHI.js.map +1 -0
  46. package/dist/{editor-chat-R54E65W5.js → editor-chat-FUBQLMAQ.js} +2 -2
  47. package/dist/heal-YP7QI7D3.js +2 -0
  48. package/dist/{inbox-L5ZZ623Y.js → inbox-O5UB3YQN.js} +2 -2
  49. package/dist/index.d.ts +462 -22
  50. package/dist/index.js +8 -8
  51. package/dist/index.js.map +1 -1
  52. package/dist/{ingest-whatsapp-NS4OWDOU.js → ingest-whatsapp-C3O5UPBX.js} +2 -2
  53. package/dist/loader-BGWA7IRY.js +2 -0
  54. package/dist/loader-LBHKSXBK.js +2 -0
  55. package/dist/memory-extract-KKPKAOPH.js +2 -0
  56. package/dist/{processes-XF5CV4LK.js → processes-7W27G3J3.js} +2 -2
  57. package/dist/program-2MDELQLX.js +2 -0
  58. package/dist/providers-WCQSKTNP.js +2 -0
  59. package/dist/query-FZAL7AQD.js +2 -0
  60. package/dist/recipes-LZ56TA3X.js +2 -0
  61. package/dist/registry-ONJSILH4.js +2 -0
  62. package/dist/scheduler-XRM2RJIV.js +2 -0
  63. package/dist/{slack-PSQLVTGB.js → slack-D27BE37Q.js} +2 -2
  64. package/dist/store-4UCO56K2.js +2 -0
  65. package/dist/{subagent-2SJHSSJS.js → subagent-EPOT3QDM.js} +2 -2
  66. package/dist/{task-store-NM7VKNLE.js → task-store-3NUWLYRY.js} +2 -2
  67. package/dist/{telegram-W4SLFMNY.js → telegram-UWV67KWG.js} +2 -2
  68. package/dist/timers-QB4KYNAE.js +2 -0
  69. package/dist/types-ECMEC5BZ.js +2 -0
  70. package/dist/types-TTB6DJUE.js +2 -0
  71. package/dist/{usage-dashboard-AXPXB2KP.js → usage-dashboard-FQGPPABD.js} +2 -2
  72. package/dist/web/workflow-editor.global.js +35 -12
  73. package/dist/web/workflow-editor.global.js.map +1 -1
  74. package/dist/{whatsapp-DCHRBRVV.js → whatsapp-KJ2NRKKQ.js} +2 -2
  75. package/dist/{whatsapp-state-6D6FHHD2.js → whatsapp-state-3ZVQ6MQD.js} +2 -2
  76. package/dist/{wiki-UDPYFJBL.js → wiki-OUYKM7MB.js} +2 -2
  77. package/dist/wiki-OUYKM7MB.js.map +1 -0
  78. package/dist/workspace-setup-6GDBEORT.js +2 -0
  79. package/dist/workspace-setup-6GDBEORT.js.map +1 -0
  80. package/package.json +11 -1
  81. package/dist/agent-4AOMVXRJ.js +0 -2
  82. package/dist/board-dashboard-CXIVJKDP.js.map +0 -1
  83. package/dist/chunk-4HP6UC2X.js +0 -13
  84. package/dist/chunk-4HP6UC2X.js.map +0 -1
  85. package/dist/chunk-ABCHHNG6.js +0 -625
  86. package/dist/chunk-ABCHHNG6.js.map +0 -1
  87. package/dist/chunk-FIOB3MEX.js +0 -2
  88. package/dist/chunk-HCFSHGK4.js +0 -46
  89. package/dist/chunk-HCFSHGK4.js.map +0 -1
  90. package/dist/chunk-HHJGKD5B.js +0 -71
  91. package/dist/chunk-HHJGKD5B.js.map +0 -1
  92. package/dist/chunk-MBEQASP3.js +0 -5
  93. package/dist/chunk-MCJUAE6V.js +0 -6
  94. package/dist/chunk-MCJUAE6V.js.map +0 -1
  95. package/dist/chunk-SRHLXZRF.js.map +0 -1
  96. package/dist/chunk-XDJSYQST.js +0 -6
  97. package/dist/chunk-XDJSYQST.js.map +0 -1
  98. package/dist/chunk-YPOELN6Z.js +0 -798
  99. package/dist/chunk-YPOELN6Z.js.map +0 -1
  100. package/dist/config-KCYHMKCT.js +0 -2
  101. package/dist/config-mutator-QP4GLBOE.js +0 -2
  102. package/dist/debug-3MEUO2P2.js +0 -2
  103. package/dist/heal-JDXUPRET.js +0 -2
  104. package/dist/loader-PTCXLJEX.js +0 -2
  105. package/dist/memory-extract-WH4FZ456.js +0 -2
  106. package/dist/program-FFWEILAS.js +0 -2
  107. package/dist/providers-AKQ4HTB4.js +0 -2
  108. package/dist/query-PKGYE5HR.js +0 -2
  109. package/dist/registry-IFMDJWQ7.js +0 -2
  110. package/dist/scheduler-63D5KFO7.js +0 -2
  111. package/dist/store-5MJOO74U.js +0 -2
  112. package/dist/timers-PCB4ZIBD.js +0 -2
  113. package/dist/types-C7IFH42Z.js +0 -2
  114. package/dist/types-OIFU3KSQ.js +0 -2
  115. package/dist/workspace-setup-C4VB3QZV.js +0 -2
  116. /package/dist/{agent-4AOMVXRJ.js.map → agent-RDVGL2TW.js.map} +0 -0
  117. /package/dist/{chunk-EKTPCSN4.js.map → chunk-4ITQP3XL.js.map} +0 -0
  118. /package/dist/{chunk-FIOB3MEX.js.map → chunk-HPDHNIA3.js.map} +0 -0
  119. /package/dist/{chunk-63UYRJIC.js.map → chunk-TRQSP67X.js.map} +0 -0
  120. /package/dist/{compaction-X46EQEPS.js.map → compaction-S5H6DTOR.js.map} +0 -0
  121. /package/dist/{config-KCYHMKCT.js.map → config-UOHEG4BW.js.map} +0 -0
  122. /package/dist/{config-mutator-QP4GLBOE.js.map → config-mutator-KSEBWKJH.js.map} +0 -0
  123. /package/dist/{context-planner-G2J7FNNU.js.map → context-planner-2RGHJHSO.js.map} +0 -0
  124. /package/dist/{debug-3MEUO2P2.js.map → debug-TGAP7E7W.js.map} +0 -0
  125. /package/dist/{editor-chat-R54E65W5.js.map → editor-chat-FUBQLMAQ.js.map} +0 -0
  126. /package/dist/{heal-JDXUPRET.js.map → heal-YP7QI7D3.js.map} +0 -0
  127. /package/dist/{inbox-L5ZZ623Y.js.map → inbox-O5UB3YQN.js.map} +0 -0
  128. /package/dist/{ingest-whatsapp-NS4OWDOU.js.map → ingest-whatsapp-C3O5UPBX.js.map} +0 -0
  129. /package/dist/{loader-PTCXLJEX.js.map → loader-BGWA7IRY.js.map} +0 -0
  130. /package/dist/{memory-extract-WH4FZ456.js.map → loader-LBHKSXBK.js.map} +0 -0
  131. /package/dist/{program-FFWEILAS.js.map → memory-extract-KKPKAOPH.js.map} +0 -0
  132. /package/dist/{processes-XF5CV4LK.js.map → processes-7W27G3J3.js.map} +0 -0
  133. /package/dist/{providers-AKQ4HTB4.js.map → program-2MDELQLX.js.map} +0 -0
  134. /package/dist/{query-PKGYE5HR.js.map → providers-WCQSKTNP.js.map} +0 -0
  135. /package/dist/{registry-IFMDJWQ7.js.map → query-FZAL7AQD.js.map} +0 -0
  136. /package/dist/{scheduler-63D5KFO7.js.map → recipes-LZ56TA3X.js.map} +0 -0
  137. /package/dist/{store-5MJOO74U.js.map → registry-ONJSILH4.js.map} +0 -0
  138. /package/dist/{task-store-NM7VKNLE.js.map → scheduler-XRM2RJIV.js.map} +0 -0
  139. /package/dist/{slack-PSQLVTGB.js.map → slack-D27BE37Q.js.map} +0 -0
  140. /package/dist/{timers-PCB4ZIBD.js.map → store-4UCO56K2.js.map} +0 -0
  141. /package/dist/{subagent-2SJHSSJS.js.map → subagent-EPOT3QDM.js.map} +0 -0
  142. /package/dist/{types-C7IFH42Z.js.map → task-store-3NUWLYRY.js.map} +0 -0
  143. /package/dist/{telegram-W4SLFMNY.js.map → telegram-UWV67KWG.js.map} +0 -0
  144. /package/dist/{types-OIFU3KSQ.js.map → timers-QB4KYNAE.js.map} +0 -0
  145. /package/dist/{wiki-UDPYFJBL.js.map → types-ECMEC5BZ.js.map} +0 -0
  146. /package/dist/{workspace-setup-C4VB3QZV.js.map → types-TTB6DJUE.js.map} +0 -0
  147. /package/dist/{usage-dashboard-AXPXB2KP.js.map → usage-dashboard-FQGPPABD.js.map} +0 -0
  148. /package/dist/{whatsapp-DCHRBRVV.js.map → whatsapp-KJ2NRKKQ.js.map} +0 -0
  149. /package/dist/{whatsapp-state-6D6FHHD2.js.map → whatsapp-state-3ZVQ6MQD.js.map} +0 -0
@@ -1,4 +1,4 @@
1
- import{a as w,b as y,c as x}from"./chunk-SFQUP3BP.js";import{m as g}from"./chunk-HCFSHGK4.js";import{existsSync as d,promises as h}from"fs";import c from"path";import{execa as C}from"execa";var b={enabled:!0,maxAttempts:3,provider:"claude-code"},v=class{constructor(e,r,i){this.cwd=e;this.config={...b,...r},this.memory=i||new x(e)}config;memory;async detectAndHeal(e,r,i){if(!this.config.enabled)return{healed:!1,attempts:0,error:"Heal disabled",filesChanged:[]};let t=await this.runVerification();if(!t)return{healed:!0,attempts:0,error:"",filesChanged:[]};let n=0,s=t,a=[];for(;n<this.config.maxAttempts;){n++;let l=await this.generateFix(s,e,r,n);if(!l.files.length)break;for(let o of l.files){let m=c.isAbsolute(o.path)?o.path:c.resolve(this.cwd,o.path),p=c.dirname(m);await h.mkdir(p,{recursive:!0}),await h.writeFile(m,o.content,"utf8"),a.push(o.path)}let f=await this.runVerification();if(!f)return i&&await this.memory.recordHeal(i,t,!0,a),{healed:!0,attempts:n,error:t,fix:l.explanation,filesChanged:a};s=f}return i&&await this.memory.recordHeal(i,t,!1,a),{healed:!1,attempts:n,error:s,filesChanged:a}}async runVerification(){let e=[this.config.buildCommand,this.config.lintCommand,this.config.testCommand].filter(Boolean);if(!e.length){let r=await this.detectCommands();e.push(...r)}for(let r of e)try{let[i,...t]=r.split(" ");await C(i,t,{cwd:this.cwd,timeout:6e4,reject:!0})}catch(i){let t=i.stderr||"",n=i.stdout||"",s=i.message||"";return k(`Command failed: ${r}
1
+ import{a as w,b as y,c as x}from"./chunk-SFQUP3BP.js";import{l as g}from"./chunk-JGXE2YNV.js";import{existsSync as d,promises as h}from"fs";import c from"path";import{execa as C}from"execa";var b={enabled:!0,maxAttempts:3,provider:"claude-code"},v=class{constructor(e,r,i){this.cwd=e;this.config={...b,...r},this.memory=i||new x(e)}config;memory;async detectAndHeal(e,r,i){if(!this.config.enabled)return{healed:!1,attempts:0,error:"Heal disabled",filesChanged:[]};let t=await this.runVerification();if(!t)return{healed:!0,attempts:0,error:"",filesChanged:[]};let n=0,s=t,a=[];for(;n<this.config.maxAttempts;){n++;let l=await this.generateFix(s,e,r,n);if(!l.files.length)break;for(let o of l.files){let m=c.isAbsolute(o.path)?o.path:c.resolve(this.cwd,o.path),p=c.dirname(m);await h.mkdir(p,{recursive:!0}),await h.writeFile(m,o.content,"utf8"),a.push(o.path)}let f=await this.runVerification();if(!f)return i&&await this.memory.recordHeal(i,t,!0,a),{healed:!0,attempts:n,error:t,fix:l.explanation,filesChanged:a};s=f}return i&&await this.memory.recordHeal(i,t,!1,a),{healed:!1,attempts:n,error:s,filesChanged:a}}async runVerification(){let e=[this.config.buildCommand,this.config.lintCommand,this.config.testCommand].filter(Boolean);if(!e.length){let r=await this.detectCommands();e.push(...r)}for(let r of e)try{let[i,...t]=r.split(" ");await C(i,t,{cwd:this.cwd,timeout:6e4,reject:!0})}catch(i){let t=i.stderr||"",n=i.stdout||"",s=i.message||"";return k(`Command failed: ${r}
2
2
  ${t}
3
3
  ${n}
4
4
  ${s}`)}return null}async detectCommands(){let e=[],r=c.resolve(this.cwd,"package.json");if(d(r))try{let t=JSON.parse(await h.readFile(r,"utf8")).scripts||{};t.build&&e.push("npm run build"),t.typecheck&&e.push("npm run typecheck"),t.lint&&e.push("npm run lint"),t.test&&e.push("npm run test")}catch{}return d(c.resolve(this.cwd,"pyproject.toml"))&&d(c.resolve(this.cwd,"conftest.py"))&&e.push("python -m pytest --tb=short -q"),e}async generateFix(e,r,i,t){let n=g(this.config.provider,this.config.apiKey),s=[];for(let o of r.slice(0,5)){let m=c.resolve(this.cwd,o);if(d(m)){let p=await h.readFile(m,"utf8");s.push(`## ${o}
@@ -29,4 +29,4 @@ Fix the error. Use create_files with the corrected file content.`}],f=await n.ge
29
29
  `)+`
30
30
  ... (truncated)`:e.join(`
31
31
  `)}export{v as a};
32
- //# sourceMappingURL=chunk-EKTPCSN4.js.map
32
+ //# sourceMappingURL=chunk-4ITQP3XL.js.map
@@ -0,0 +1,2 @@
1
+ import{a as u}from"./chunk-VJUTQAPJ.js";import{existsSync as R,promises as y}from"fs";import m from"path";import x from"js-yaml";import I from"fast-glob";import{z as e}from"zod";var w=e.object({id:e.string(),priority:e.number().default(0),when:e.object({agentIds:e.array(e.string()).optional(),intentTags:e.array(e.string()).optional(),messageRegex:e.array(e.string()).optional(),requireTags:e.array(e.string()).optional()}).default({}),references:e.array(e.string()).default([]),skills:e.array(e.string()).default([]),maxChars:e.number().int().min(100).max(8e3).optional()}),C=e.object({recipes:e.array(w)}),v=["references/recipes",".references/recipes",".agentx/references/recipes",".agentx/recipes"];async function $(o){let t={recipes:[],sourceById:new Map};for(let r of v){let s=m.resolve(o,r);if(!R(s))continue;let n=await I.glob("**/*.{yaml,yml}",{cwd:s,deep:5});for(let d of n){let a=m.resolve(s,d);try{let f=await y.readFile(a,"utf8"),i=x.load(f),c=C.parse(i);for(let p of c.recipes){if(t.sourceById.has(p.id))throw new Error(`recipe id "${p.id}" duplicated in ${a}`);t.recipes.push(p),t.sourceById.set(p.id,a)}}catch(f){u.warn({file:a,error:f?.message},"recipes: failed to load file")}}}return t}function z(o,t,r){let s=[];for(let i of t.recipes)S(i,o)&&s.push(i);s.sort((i,c)=>c.priority-i.priority||i.id.localeCompare(c.id));let n=new Set,d=[],a=new Set,f=[];for(let i of s){for(let c of i.references){let p=b(c,r);p.length===0&&f.push(c);for(let l of p){if(n.has(l))continue;let g=r.byId.get(l);if(!g){f.push(l);continue}i.when.requireTags?.length&&!i.when.requireTags.every(h=>g.tags.includes(h))||(n.add(l),d.push(g))}}for(let c of i.skills)a.add(c)}return{matched:s,cards:d,requiredSkills:[...a],unresolvedIds:[...new Set(f)]}}function S(o,t){let r=o.when;if(r.agentIds?.length&&!r.agentIds.includes(t.agentId))return!1;if(r.intentTags?.length){let s=t.intentTags??[];if(!r.intentTags.some(d=>s.includes(d)))return!1}return!(r.messageRegex?.length&&!r.messageRegex.some(n=>{try{return new RegExp(n,"i").test(t.message)}catch{return t.message.toLowerCase().includes(n.toLowerCase())}}))}function b(o,t){if(!o.endsWith(".*"))return[o];let r=o.slice(0,-2),s=[];for(let n of t.byId.keys())(n===r||n.startsWith(`${r}.`))&&s.push(n);return s}export{$ as a,z as b};
2
+ //# sourceMappingURL=chunk-53NRX2NA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/agents/references/recipes.ts"],"sourcesContent":["import { existsSync, promises as fs } from \"fs\"\nimport path from \"path\"\nimport yaml from \"js-yaml\"\nimport fg from \"fast-glob\"\nimport { z } from \"zod\"\nimport { logger } from \"@/utils/logger\"\nimport type { ReferenceCard, ReferenceIndex } from \"./types\"\n\n// --- Recipes ---\n//\n// Map (agentId, optional intent tag, optional message keyword) to a bundle of\n// reference card ids and skill names. Pure deterministic lookup — no LLM in\n// the hot path. Multiple recipes can match a single turn; the resolver merges\n// their reference sets and de-duplicates.\n\nconst recipeSchema = z.object({\n id: z.string(),\n /** Higher = picked first when budgets bite. Default 0. */\n priority: z.number().default(0),\n when: z\n .object({\n agentIds: z.array(z.string()).optional(),\n intentTags: z.array(z.string()).optional(),\n /** OR'd, case-insensitive regex sources. */\n messageRegex: z.array(z.string()).optional(),\n /** Tags to require on the card pool. Cards must carry every tag. */\n requireTags: z.array(z.string()).optional(),\n })\n .default({}),\n /** Card ids OR \".*\" glob suffixes — \"ksi.contacts.*\" matches all under that prefix. */\n references: z.array(z.string()).default([]),\n /** Skill names that should be considered required for this intent (audit lint\n * flags FAILING when a referenced skill has no SKILL.md). */\n skills: z.array(z.string()).default([]),\n /** Per-recipe char budget. Total is enforced by the layer; this is advisory. */\n maxChars: z.number().int().min(100).max(8000).optional(),\n})\nexport type Recipe = z.infer<typeof recipeSchema>\n\nconst recipeFileSchema = z.object({\n recipes: z.array(recipeSchema),\n})\n\nexport interface RecipeIndex {\n recipes: Recipe[]\n /** Source path per recipe id. */\n sourceById: Map<string, string>\n}\n\nconst RECIPE_DIRS = [\n \"references/recipes\",\n \".references/recipes\",\n \".agentx/references/recipes\",\n \".agentx/recipes\",\n]\n\nexport async function loadRecipes(cwd: string): Promise<RecipeIndex> {\n const index: RecipeIndex = { recipes: [], sourceById: new Map() }\n for (const dir of RECIPE_DIRS) {\n const abs = path.resolve(cwd, dir)\n if (!existsSync(abs)) continue\n const files = await fg.glob(\"**/*.{yaml,yml}\", { cwd: abs, deep: 5 })\n for (const rel of files) {\n const file = path.resolve(abs, rel)\n try {\n const raw = await fs.readFile(file, \"utf8\")\n const parsed = yaml.load(raw)\n const validated = recipeFileSchema.parse(parsed)\n for (const recipe of validated.recipes) {\n if (index.sourceById.has(recipe.id)) {\n throw new Error(`recipe id \"${recipe.id}\" duplicated in ${file}`)\n }\n index.recipes.push(recipe)\n index.sourceById.set(recipe.id, file)\n }\n } catch (e: any) {\n logger.warn({ file, error: e?.message }, \"recipes: failed to load file\")\n }\n }\n }\n return index\n}\n\nexport interface RecipeContext {\n agentId: string\n intentTags?: string[]\n message: string\n}\n\nexport interface ResolvedRecipes {\n matched: Recipe[]\n cards: ReferenceCard[]\n /** Skill names required by the matched recipes. */\n requiredSkills: string[]\n /** Reference ids in the recipes that did NOT resolve — used by audit lint. */\n unresolvedIds: string[]\n}\n\n/**\n * Match recipes deterministically. AND within a `when` clause, OR across\n * recipes. A recipe with no `when` always matches (use sparingly).\n */\nexport function resolveRecipes(\n ctx: RecipeContext,\n recipeIndex: RecipeIndex,\n referenceIndex: ReferenceIndex,\n): ResolvedRecipes {\n const matched: Recipe[] = []\n for (const recipe of recipeIndex.recipes) {\n if (recipeMatches(recipe, ctx)) matched.push(recipe)\n }\n // Highest priority first, then stable by id.\n matched.sort((a, b) => b.priority - a.priority || a.id.localeCompare(b.id))\n\n const seen = new Set<string>()\n const cards: ReferenceCard[] = []\n const skills = new Set<string>()\n const unresolvedIds: string[] = []\n\n for (const recipe of matched) {\n for (const ref of recipe.references) {\n const ids = expandReferenceIds(ref, referenceIndex)\n if (ids.length === 0) unresolvedIds.push(ref)\n for (const id of ids) {\n if (seen.has(id)) continue\n const card = referenceIndex.byId.get(id)\n if (!card) {\n unresolvedIds.push(id)\n continue\n }\n if (recipe.when.requireTags?.length) {\n const has = recipe.when.requireTags.every(t => card.tags.includes(t))\n if (!has) continue\n }\n seen.add(id)\n cards.push(card)\n }\n }\n for (const s of recipe.skills) skills.add(s)\n }\n\n return {\n matched,\n cards,\n requiredSkills: [...skills],\n unresolvedIds: [...new Set(unresolvedIds)],\n }\n}\n\nfunction recipeMatches(recipe: Recipe, ctx: RecipeContext): boolean {\n const w = recipe.when\n if (w.agentIds?.length && !w.agentIds.includes(ctx.agentId)) return false\n if (w.intentTags?.length) {\n const tags = ctx.intentTags ?? []\n const hit = w.intentTags.some(t => tags.includes(t))\n if (!hit) return false\n }\n if (w.messageRegex?.length) {\n const hit = w.messageRegex.some(src => {\n try {\n return new RegExp(src, \"i\").test(ctx.message)\n } catch {\n return ctx.message.toLowerCase().includes(src.toLowerCase())\n }\n })\n if (!hit) return false\n }\n return true\n}\n\n/**\n * Expand a reference id pattern. Plain ids return [id]. A trailing `.*` matches\n * any id with that prefix in the index.\n */\nfunction expandReferenceIds(pattern: string, idx: ReferenceIndex): string[] {\n if (!pattern.endsWith(\".*\")) return [pattern]\n const prefix = pattern.slice(0, -2)\n const out: string[] = []\n for (const id of idx.byId.keys()) {\n if (id === prefix || id.startsWith(`${prefix}.`)) out.push(id)\n }\n return out\n}\n"],"mappings":"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","names":["existsSync","fs","path","yaml","fg","z","recipeSchema","z","recipeFileSchema","RECIPE_DIRS","loadRecipes","cwd","index","dir","abs","path","existsSync","files","fg","rel","file","raw","fs","parsed","yaml","validated","recipe","e","logger","resolveRecipes","ctx","recipeIndex","referenceIndex","matched","recipeMatches","a","b","seen","cards","skills","unresolvedIds","ref","ids","expandReferenceIds","id","card","t","s","w","tags","src","pattern","idx","prefix","out"]}
@@ -1,6 +1,6 @@
1
- import{a as k,b as A,c as f,d,e as b,f as p}from"./chunk-6PPIFDOQ.js";import{f as w,i as j}from"./chunk-XDJSYQST.js";import{existsSync as h,readFileSync as I,writeFileSync as P,appendFileSync as M,mkdirSync as J}from"fs";import{resolve as u}from"path";import{hostname as q}from"os";j();import{existsSync as T,readFileSync as S,writeFileSync as v,copyFileSync as U}from"fs";import{resolve as _}from"path";function C(e,t={}){let n=t.configPath||_(process.cwd(),"agentx.json");if(!T(n))throw new Error(`agentx.json not found at ${n}`);let a=JSON.parse(S(n,"utf-8")),r=e(a),o=typeof r=="string"?{summary:r}:r,i=JSON.stringify(a,null,2)+`
2
- `,c=n+".tmp";v(c,i,"utf-8");try{JSON.parse(S(c,"utf-8"))}catch(g){throw new Error(`config did not round-trip cleanly: ${g.message}`)}let l;return t.skipBackup||(l=`${n}.bak.${Date.now()}`,U(n,l)),v(n,i,"utf-8"),t.skipReload||D().catch(()=>{}),{summary:o.summary,payload:o.payload,backupPath:l}}function E(e,t={}){let n=t.configPath||_(process.cwd(),"agentx.json");if(T(n)&&!t.overwrite)throw new Error(`agentx.json already exists at ${n}. Pass overwrite=true to replace it.`);return v(n,JSON.stringify(e,null,2)+`
3
- `,"utf-8"),{file:n}}async function D(){try{let t=w().dashboard.daemonUrl?.replace(/\/+$/,"")||"http://127.0.0.1:18800";await fetch(`${t}/reload`,{method:"POST"}).catch(()=>null)}catch{}}function z(e,t){let n=e.configExists?e.agentCount===0?"Add your first agent":"Add another agent":"Set up your team",a=`<header class="ax-topbar">
1
+ import{a as k,b as A,c as f,d,e as b,f as p}from"./chunk-6PPIFDOQ.js";import{e as w}from"./chunk-LY7JGDV7.js";import{existsSync as h,readFileSync as I,writeFileSync as P,appendFileSync as H,mkdirSync as M}from"fs";import{resolve as u}from"path";import{hostname as J}from"os";import{existsSync as T,readFileSync as S,writeFileSync as v,copyFileSync as j}from"fs";import{resolve as _}from"path";function C(e,t={}){let n=t.configPath||_(process.cwd(),"agentx.json");if(!T(n))throw new Error(`agentx.json not found at ${n}`);let a=JSON.parse(S(n,"utf-8")),r=e(a),o=typeof r=="string"?{summary:r}:r,i=JSON.stringify(a,null,2)+`
2
+ `,c=n+".tmp";v(c,i,"utf-8");try{JSON.parse(S(c,"utf-8"))}catch(g){throw new Error(`config did not round-trip cleanly: ${g.message}`)}let l;return t.skipBackup||(l=`${n}.bak.${Date.now()}`,j(n,l)),v(n,i,"utf-8"),t.skipReload||U().catch(()=>{}),{summary:o.summary,payload:o.payload,backupPath:l}}function E(e,t={}){let n=t.configPath||_(process.cwd(),"agentx.json");if(T(n)&&!t.overwrite)throw new Error(`agentx.json already exists at ${n}. Pass overwrite=true to replace it.`);return v(n,JSON.stringify(e,null,2)+`
3
+ `,"utf-8"),{file:n}}async function U(){try{let t=w().dashboard.daemonUrl?.replace(/\/+$/,"")||"http://127.0.0.1:18800";await fetch(`${t}/reload`,{method:"POST"}).catch(()=>null)}catch{}}function z(e,t){let n=e.configExists?e.agentCount===0?"Add your first agent":"Add another agent":"Set up your team",a=`<header class="ax-topbar">
4
4
  <div class="ax-topbar__left">
5
5
  <div class="ax-brand">
6
6
  <span class="ax-brand__mark">AX</span>
@@ -44,7 +44,7 @@ import{a as k,b as A,c as f,d,e as b,f as p}from"./chunk-6PPIFDOQ.js";import{f a
44
44
  ${f("Skip, I'll do it later",{onclick:"window.location.href='/live'"})}
45
45
  </footer>
46
46
  </form>
47
- </div>`;return k({title:"AgentX \u2014 Setup",activeTab:"custom",subtitle:"Setup",customHeader:a,noMain:!0,body:g,css:K,scripts:H})}var K=`
47
+ </div>`;return k({title:"AgentX \u2014 Setup",activeTab:"custom",subtitle:"Setup",customHeader:a,noMain:!0,body:g,css:D,scripts:K})}var D=`
48
48
  /* Pre-dashboard topbar variant \u2014 no tab nav, the pills are the only chrome. */
49
49
  .ax-topbar__right a.ax-skip {
50
50
  padding: 4px 10px; border: 1px solid var(--ax-border-2); border-radius: 4px;
@@ -98,7 +98,7 @@ import{a as k,b as A,c as f,d,e as b,f as p}from"./chunk-6PPIFDOQ.js";import{f a
98
98
  }
99
99
  .ax-next-steps { margin-top: 12px; padding-left: 20px; color: var(--ax-text-2); }
100
100
  .ax-next-steps li { margin: 4px 0; }
101
- .ax-next-steps code { background: var(--ax-surface-2); padding: 1px 6px; border-radius: 3px; color: var(--ax-text); font-family: var(--ax-mono); font-size: var(--ax-fs-sm); }`,H=`<script>
101
+ .ax-next-steps code { background: var(--ax-surface-2); padding: 1px 6px; border-radius: 3px; color: var(--ax-text); font-family: var(--ax-mono); font-size: var(--ax-fs-sm); }`,K=`<script>
102
102
  document.getElementById('enableTelegram').addEventListener('change', (e) => {
103
103
  document.getElementById('telegramFields').classList.toggle('is-show', e.target.checked);
104
104
  });
@@ -151,14 +151,14 @@ document.getElementById('wizard').addEventListener('submit', async (e) => {
151
151
  btn.disabled = false; btn.textContent = 'Save and continue';
152
152
  }
153
153
  });
154
- </script>`;function N(e=process.cwd()){let t=u(e,"agentx.json");if(!h(t))return{configExists:!1,agentCount:0,channelCount:0};try{let n=JSON.parse(I(t,"utf-8")),a=n.agents?Object.keys(n.agents).length:0,r=n.channels?Object.values(n.channels).filter(o=>o&&o.enabled).length:0;return{configExists:!0,agentCount:a,channelCount:r,nodeName:n.node?.name}}catch{return{configExists:!1,agentCount:0,channelCount:0}}}function L(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=Y(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=u(t,"agentx.json"),r=u(t,".env"),o=u(t,"agents",e.agent.id),i=N(t);J(o,{recursive:!0});let c=u(o,"CLAUDE.md");if(!h(c)){let x=e.agent.personality?.trim()||`You are ${e.agent.name}, an assistant on the ${e.nodeName} team.`;P(c,`# ${e.agent.name}
154
+ </script>`;function N(e=process.cwd()){let t=u(e,"agentx.json");if(!h(t))return{configExists:!1,agentCount:0,channelCount:0};try{let n=JSON.parse(I(t,"utf-8")),a=n.agents?Object.keys(n.agents).length:0,r=n.channels?Object.values(n.channels).filter(o=>o&&o.enabled).length:0;return{configExists:!0,agentCount:a,channelCount:r,nodeName:n.node?.name}}catch{return{configExists:!1,agentCount:0,channelCount:0}}}function q(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=L(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=u(t,"agentx.json"),r=u(t,".env"),o=u(t,"agents",e.agent.id),i=N(t);M(o,{recursive:!0});let c=u(o,"CLAUDE.md");if(!h(c)){let x=e.agent.personality?.trim()||`You are ${e.agent.name}, an assistant on the ${e.nodeName} team.`;P(c,`# ${e.agent.name}
155
155
 
156
156
  ${x}
157
- `)}let l=e.telegram?`TG_${e.agent.id.toUpperCase()}_BOT_TOKEN`:void 0,g;if(i.configExists)g=C(s=>{if(s.node=s.node||{},s.node.name||(s.node.name=e.nodeName),s.node.id||(s.node.id=$(e.nodeName)||"team"),s.node.bind||(s.node.bind="127.0.0.1:18800"),s.node.defaultAgent=s.node.defaultAgent||e.agent.id,s.providers=s.providers||{},s.providers.claude||(s.providers.claude={apiKey:"${ANTHROPIC_API_KEY}"}),s.agents=s.agents||{},s.agents[e.agent.id])throw new Error(`Agent "${e.agent.id}" already exists.`);return s.agents[e.agent.id]=O(e,o,n),e.telegram&&(s.channels=s.channels||{},s.channels.telegram=s.channels.telegram||{enabled:!0,accounts:{},policy:{dm:"pair",group:"mention-required"}},s.channels.telegram.enabled=!0,s.channels.telegram.accounts=s.channels.telegram.accounts||{},s.channels.telegram.accounts[e.telegram.accountId]=R(e,l)),{summary:`added agent "${e.agent.id}"`+(e.telegram?" + telegram account":"")}},{configPath:a}).backupPath;else{let x=X(e,o,n,l);E(x,{configPath:a})}G(r,e,l);let m=[];return(e.anthropicApiKey||!h(u(t,".env")))&&m.push("Double-check the API keys in .env"),e.agent.tier==="claude-code"&&m.push("Run claude --version to confirm Claude Code is installed (needed for the claude-code engine)"),m.push("Start the daemon: agentx daemon start"),m.push("Open the dashboard: http://127.0.0.1:4202"),{summary:i.configExists?`Added agent "${e.agent.id}" to your team.`:`Initialized your team with first agent "${e.agent.id}".`,nextSteps:m,backupPath:g}}function Y(e){return(e||"").split(/[,\s]+/).map(t=>t.trim()).filter(Boolean).filter((t,n,a)=>a.indexOf(t)===n)}function $(e){return(e||"").toLowerCase().replace(/[^a-z0-9-]+/g,"-").replace(/^-+|-+$/g,"")}function X(e,t,n,a){let o={node:{id:$(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]:O(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?o.channels.telegram={enabled:!0,accounts:{[e.telegram.accountId]:R(e,a)},policy:{dm:"pair",group:"mention-required"}}:o.channels.telegram={enabled:!1,accounts:{},policy:{dm:"pair",group:"mention-required"}},o}function O(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 R(e,t){return{botToken:"${"+t+"}",botUsername:e.telegram?.botUsername||"",agentBinding:e.agent.id}}function G(e,t,n){let a=h(e)?I(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}`),!h(e)){P(e,[...["# AgentX environment variables","# (Never commit this file \u2014 it contains secrets.)",""],...r,""].join(`
157
+ `)}let l=e.telegram?`TG_${e.agent.id.toUpperCase()}_BOT_TOKEN`:void 0,g;if(i.configExists)g=C(s=>{if(s.node=s.node||{},s.node.name||(s.node.name=e.nodeName),s.node.id||(s.node.id=$(e.nodeName)||"team"),s.node.bind||(s.node.bind="127.0.0.1:18800"),s.node.defaultAgent=s.node.defaultAgent||e.agent.id,s.providers=s.providers||{},s.providers.claude||(s.providers.claude={apiKey:"${ANTHROPIC_API_KEY}"}),s.agents=s.agents||{},s.agents[e.agent.id])throw new Error(`Agent "${e.agent.id}" already exists.`);return s.agents[e.agent.id]=O(e,o,n),e.telegram&&(s.channels=s.channels||{},s.channels.telegram=s.channels.telegram||{enabled:!0,accounts:{},policy:{dm:"pair",group:"mention-required"}},s.channels.telegram.enabled=!0,s.channels.telegram.accounts=s.channels.telegram.accounts||{},s.channels.telegram.accounts[e.telegram.accountId]=R(e,l)),{summary:`added agent "${e.agent.id}"`+(e.telegram?" + telegram account":"")}},{configPath:a}).backupPath;else{let x=Y(e,o,n,l);E(x,{configPath:a})}X(r,e,l);let m=[];return(e.anthropicApiKey||!h(u(t,".env")))&&m.push("Double-check the API keys in .env"),e.agent.tier==="claude-code"&&m.push("Run claude --version to confirm Claude Code is installed (needed for the claude-code engine)"),m.push("Start the daemon: agentx daemon start"),m.push("Open the dashboard: http://127.0.0.1:4202"),{summary:i.configExists?`Added agent "${e.agent.id}" to your team.`:`Initialized your team with first agent "${e.agent.id}".`,nextSteps:m,backupPath:g}}function L(e){return(e||"").split(/[,\s]+/).map(t=>t.trim()).filter(Boolean).filter((t,n,a)=>a.indexOf(t)===n)}function $(e){return(e||"").toLowerCase().replace(/[^a-z0-9-]+/g,"-").replace(/^-+|-+$/g,"")}function Y(e,t,n,a){let o={node:{id:$(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]:O(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?o.channels.telegram={enabled:!0,accounts:{[e.telegram.accountId]:R(e,a)},policy:{dm:"pair",group:"mention-required"}}:o.channels.telegram={enabled:!1,accounts:{},policy:{dm:"pair",group:"mention-required"}},o}function O(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 R(e,t){return{botToken:"${"+t+"}",botUsername:e.telegram?.botUsername||"",agentBinding:e.agent.id}}function X(e,t,n){let a=h(e)?I(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}`),!h(e)){P(e,[...["# AgentX environment variables","# (Never commit this file \u2014 it contains secrets.)",""],...r,""].join(`
158
158
  `));return}if(r.length>0){let o=a.endsWith(`
159
159
  `)?"":`
160
- `;M(e,o+r.join(`
160
+ `;H(e,o+r.join(`
161
161
  `)+`
162
- `)}}function fe(e,t){let n=N(),a=n.nodeName||q().replace(/\.local$/,""),r=z(n,a);t.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),t.end(r)}async function be(e,t){let n="";await new Promise(r=>{e.on("data",o=>{n+=o}),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=L(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"}))}}import{createHash as V,randomBytes as B}from"crypto";import{existsSync as Q,mkdirSync as Z,readFileSync as ee,writeFileSync as te}from"fs";import{resolve as ne,dirname as re}from"path";var y="agx_live_",ae=".agentx/tokens.json",W=class{file;constructor(t=process.cwd(),n=ae){this.file=ne(t,n)}load(){if(!Q(this.file))return[];try{return JSON.parse(ee(this.file,"utf-8"))}catch{return[]}}save(t){Z(re(this.file),{recursive:!0}),te(this.file,JSON.stringify(t,null,2)+`
163
- `,"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 i of t.scopes)if(!oe(i))throw new Error(`Invalid scope: ${i}`);let n=B(32).toString("hex"),a=y+n,r={id:"tok_"+B(4).toString("hex"),name:t.name.trim(),prefix:y+n.slice(0,4),hash:F(a),scopes:[...t.scopes],createdAt:new Date().toISOString(),expiresAt:t.expiresInDays?new Date(Date.now()+t.expiresInDays*86400*1e3).toISOString():void 0},o=this.load();return o.push(r),this.save(o),{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(y))return null;let n=F(t),a=this.load(),r=a.find(c=>c.hash===n);if(!r||r.revokedAt||r.expiresAt&&Date.parse(r.expiresAt)<Date.now())return null;let o=Date.now(),i=r.lastUsedAt?Date.parse(r.lastUsedAt):0;if(o-i>6e4){r.lastUsedAt=new Date(o).toISOString();try{this.save(a)}catch{}}return r}};function Ae(e,t){return!!(e.scopes.includes(t)||t.startsWith("agent:")&&e.scopes.includes("agent:*")||t==="dashboard:read"&&e.scopes.includes("dashboard:write"))}function oe(e){return e==="dashboard:read"||e==="dashboard:write"||e==="agent:*"||/^agent:[a-z0-9][a-z0-9_-]*$/.test(e)||e==="mesh:peer"}function F(e){return"sha256:"+V("sha256").update(e).digest("hex")}function Se(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{C as a,N as b,fe as c,be as d,W as e,Ae as f,Se as g};
164
- //# sourceMappingURL=chunk-5WNGHDFC.js.map
162
+ `)}}function xe(e,t){let n=N(),a=n.nodeName||J().replace(/\.local$/,""),r=z(n,a);t.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),t.end(r)}async function fe(e,t){let n="";await new Promise(r=>{e.on("data",o=>{n+=o}),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=q(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"}))}}import{createHash as G,randomBytes as B}from"crypto";import{existsSync as V,mkdirSync as Q,readFileSync as Z,writeFileSync as ee}from"fs";import{resolve as te,dirname as ne}from"path";var y="agx_live_",re=".agentx/tokens.json",W=class{file;constructor(t=process.cwd(),n=re){this.file=te(t,n)}load(){if(!V(this.file))return[];try{return JSON.parse(Z(this.file,"utf-8"))}catch{return[]}}save(t){Q(ne(this.file),{recursive:!0}),ee(this.file,JSON.stringify(t,null,2)+`
163
+ `,"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 i of t.scopes)if(!ae(i))throw new Error(`Invalid scope: ${i}`);let n=B(32).toString("hex"),a=y+n,r={id:"tok_"+B(4).toString("hex"),name:t.name.trim(),prefix:y+n.slice(0,4),hash:F(a),scopes:[...t.scopes],createdAt:new Date().toISOString(),expiresAt:t.expiresInDays?new Date(Date.now()+t.expiresInDays*86400*1e3).toISOString():void 0},o=this.load();return o.push(r),this.save(o),{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(y))return null;let n=F(t),a=this.load(),r=a.find(c=>c.hash===n);if(!r||r.revokedAt||r.expiresAt&&Date.parse(r.expiresAt)<Date.now())return null;let o=Date.now(),i=r.lastUsedAt?Date.parse(r.lastUsedAt):0;if(o-i>6e4){r.lastUsedAt=new Date(o).toISOString();try{this.save(a)}catch{}}return r}};function ke(e,t){return!!(e.scopes.includes(t)||t.startsWith("agent:")&&e.scopes.includes("agent:*")||t==="dashboard:read"&&e.scopes.includes("dashboard:write"))}function ae(e){return e==="dashboard:read"||e==="dashboard:write"||e==="agent:*"||/^agent:[a-z0-9][a-z0-9_-]*$/.test(e)||e==="mesh:peer"}function F(e){return"sha256:"+G("sha256").update(e).digest("hex")}function Ae(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{C as a,N as b,xe as c,fe as d,W as e,ke as f,Ae as g};
164
+ //# sourceMappingURL=chunk-ASSJ4Z6Z.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/daemon/setup-wizard.ts","../src/daemon/config-mutate.ts","../src/daemon/ui/pages/setup.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\"\nimport { renderSetupPage } from \"./ui/pages/setup\"\n\n/** Shape of the pre-render state snapshot — exported so the page file can\n * type its props without re-importing this whole module. */\nexport interface WizardState {\n configExists: boolean\n agentCount: number\n channelCount: number\n nodeName?: string\n}\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()): WizardState {\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 defaultNode = state.nodeName || hostname().replace(/\\.local$/, \"\")\n const html = renderSetupPage(state, defaultNode)\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\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","// --- Setup wizard page ---\n//\n// First-run flow for non-technical operators: team name, first agent,\n// optional Telegram, optional Anthropic key. Writes agentx.json, scaffolds\n// the agent's workspace, writes .env, and points the operator at the next\n// step.\n//\n// The server-side state machine + form handler live in ../../setup-wizard.ts\n// — this file owns ONLY the HTML. That keeps the data flow inspectable\n// (one place) and the visual surface hot-reloadable (edit → rebuild → view).\n\nimport { renderShell, esc, stepCard, field, row, btn } from \"..\"\nimport type { WizardState } from \"../../setup-wizard\"\n\nexport function renderSetupPage(state: WizardState, defaultNode: string): string {\n const heading = state.configExists\n ? (state.agentCount === 0 ? \"Add your first agent\" : \"Add another agent\")\n : \"Set up your team\"\n\n // Custom topbar: /setup is pre-dashboard, we skip the tab nav and show\n // just brand + subtitle + theme switcher + \"Skip to dashboard\" pill.\n const customHeader = `<header class=\"ax-topbar\">\n <div class=\"ax-topbar__left\">\n <div class=\"ax-brand\">\n <span class=\"ax-brand__mark\">AX</span>\n <span class=\"ax-brand__name\">AgentX</span>\n <span class=\"ax-brand__subtitle\">Setup</span>\n </div>\n </div>\n <div class=\"ax-topbar__right\">\n <a href=\"/glossary\" class=\"ax-skip\">? Glossary</a>\n <a href=\"/live\" class=\"ax-skip\">Skip — open dashboard</a>\n <div class=\"ax-theme-switch\" role=\"tablist\" aria-label=\"Theme\">\n <button data-theme-opt=\"dark\">Dark</button>\n <button data-theme-opt=\"light\">Light</button>\n <button data-theme-opt=\"crt\">CRT</button>\n </div>\n </div>\n</header>`\n\n const step1 = stepCard(1, \"Team basics\",\n field({\n name: \"nodeName\",\n label: \"Team name\",\n hint: \"(what you'd call this AgentX install)\",\n value: defaultNode,\n placeholder: \"My Team\",\n required: true,\n })\n )\n\n const step2 = stepCard(2, \"First agent\",\n row([\n field({ name: \"agentName\", label: \"Agent name\", value: \"Assistant\", placeholder: \"Support Bot\", required: true }),\n field({ name: \"agentId\", label: \"Agent id\", hint: \"(lowercase, no spaces)\", value: \"assistant\", pattern: \"[a-z0-9][a-z0-9_-]*\", required: true }),\n ]) +\n field({\n name: \"triggerWords\",\n label: \"Trigger words\",\n hint: `(comma or space separated — e.g. <code>@support, support</code>)`,\n value: \"@assistant, assistant\",\n required: true,\n }) +\n row([\n field({\n name: \"tier\",\n label: \"AI engine\",\n type: \"select\",\n options: [\n { value: \"claude-code\", label: \"Claude Code (recommended)\", selected: true },\n { value: \"sdk\", label: \"Anthropic API (BYO key)\" },\n ],\n }),\n field({\n name: \"model\",\n label: \"Model\",\n hint: \"(optional)\",\n value: \"claude-sonnet-4-6\",\n placeholder: \"claude-sonnet-4-6\",\n }),\n ]) +\n field({\n name: \"personality\",\n label: \"Personality / instructions\",\n hint: \"(optional — what this agent does, in plain English)\",\n type: \"textarea\",\n placeholder: \"You are a support agent for Acme Co. Answer customer questions about our product, be friendly, keep replies short.\",\n })\n )\n\n const step3 = stepCard(3, \"First channel\", `\n <label class=\"ax-toggle\"><input type=\"checkbox\" id=\"enableTelegram\" /> Connect Telegram now</label>\n <div class=\"ax-optional\" id=\"telegramFields\">\n ${field({ name: \"telegramAccountId\", label: \"Account id\", hint: `(a label you choose — e.g. <code>support</code>)`, value: \"default\" })}\n ${field({ name: \"telegramBotToken\", label: \"Bot token\", hint: `(from <a href=\"https://t.me/BotFather\" target=\"_blank\">@BotFather</a>)`, placeholder: \"123456:ABCdef...\" })}\n ${field({ name: \"telegramBotUsername\", label: \"Bot username\", hint: `(optional — e.g. <code>my_support_bot</code>)`, placeholder: \"my_support_bot\" })}\n </div>\n `)\n\n const step4 = stepCard(4, \"Anthropic API key\",\n field({\n name: \"anthropicApiKey\",\n label: \"API key\",\n hint: \"(optional — skip if you're using Claude Code)\",\n type: \"password\",\n placeholder: \"sk-ant-…\",\n extraAttrs: `autocomplete=\"off\"`,\n }) +\n `<div class=\"ax-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 )\n\n const banner = state.configExists\n ? `<div class=\"ax-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\n const body = `<div class=\"ax-wizard\">\n <h1 class=\"ax-wizard__h1\">${esc(heading)}</h1>\n <p class=\"ax-wizard__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 ${banner}\n\n <div id=\"msg\" class=\"ax-msg\"></div>\n\n <form id=\"wizard\" autocomplete=\"off\">\n ${step1}\n ${step2}\n ${step3}\n ${step4}\n <footer class=\"ax-actions\">\n ${btn(\"Save and continue\", { type: \"submit\", primary: true, id: \"submitBtn\" })}\n ${btn(\"Skip, I'll do it later\", { onclick: \"window.location.href='/live'\" })}\n </footer>\n </form>\n</div>`\n\n return renderShell({\n title: \"AgentX — Setup\",\n activeTab: \"custom\",\n subtitle: \"Setup\",\n customHeader,\n noMain: true,\n body,\n css: SETUP_PAGE_CSS,\n scripts: SETUP_SCRIPT,\n })\n}\n\n/** CSS that's specific to the setup page — wizard layout + optional-fields\n * toggle + result messaging. Everything reusable (form fields, step cards,\n * buttons) comes from AX_COMPONENTS_CSS. */\nconst SETUP_PAGE_CSS = `\n/* Pre-dashboard topbar variant — no tab nav, the pills are the only chrome. */\n.ax-topbar__right a.ax-skip {\n padding: 4px 10px; border: 1px solid var(--ax-border-2); border-radius: 4px;\n color: var(--ax-text-2); font-size: var(--ax-fs-xs); text-decoration: none;\n}\n.ax-topbar__right a.ax-skip:hover { color: var(--ax-accent); border-color: var(--ax-accent); }\n\n/* Wizard frame */\n.ax-wizard { max-width: 680px; margin: 0 auto; padding: 36px 22px 80px; }\n.ax-wizard__h1 { font-size: 22px; font-weight: 600; margin: 0 0 4px; letter-spacing: -0.015em; }\n.ax-wizard__lead { color: var(--ax-muted); margin: 0 0 24px; font-size: var(--ax-fs); line-height: 1.55; }\n.ax-wizard__lead code { background: var(--ax-surface-2); padding: 1px 6px; border-radius: 3px; color: var(--ax-text); font-size: var(--ax-fs-sm); }\n\n/* Info banner (\"You already have N agents…\") */\n.ax-banner {\n background: color-mix(in oklch, var(--ax-accent) 10%, var(--ax-bg-elev));\n border: 1px solid color-mix(in oklch, var(--ax-accent) 30%, var(--ax-border-2));\n border-radius: var(--ax-radius);\n padding: 10px 14px; margin-bottom: 18px;\n color: var(--ax-text-2); font-size: var(--ax-fs-sm);\n}\n.ax-banner b { color: var(--ax-text); font-weight: 600; }\n\n/* \"Connect Telegram now\" toggle + collapsible fields */\n.ax-toggle { display: flex; align-items: center; gap: 8px; margin: 4px 0 6px; font-size: var(--ax-fs); color: var(--ax-text); cursor: pointer; user-select: none; }\n.ax-toggle input { width: auto; accent-color: var(--ax-accent); }\n.ax-optional { border-left: 2px solid var(--ax-border-2); padding-left: 14px; margin-top: 10px; display: none; }\n.ax-optional.is-show { display: block; }\n.ax-hint-block { font-size: var(--ax-fs-xs); color: var(--ax-muted); margin-top: 8px; line-height: 1.55; }\n.ax-hint-block code { background: var(--ax-surface-2); padding: 1px 6px; border-radius: 3px; color: var(--ax-text); font-family: var(--ax-mono); font-size: var(--ax-fs-xs); }\n\n/* Form footer */\n.ax-actions {\n display: flex; gap: 10px; padding: 20px 0 0; align-items: center;\n border-top: 1px solid var(--ax-border); margin-top: 20px;\n}\n\n/* Server-response messaging (success / error blocks) */\n.ax-msg { margin: 14px 0; padding: 12px 16px; border-radius: var(--ax-radius); font-size: var(--ax-fs); display: none; }\n.ax-msg.is-ok {\n display: block;\n background: color-mix(in oklch, var(--ax-accent) 12%, transparent);\n color: var(--ax-accent);\n border: 1px solid color-mix(in oklch, var(--ax-accent) 40%, transparent);\n}\n.ax-msg.is-err {\n display: block;\n background: color-mix(in oklch, var(--ax-err) 12%, transparent);\n color: var(--ax-err);\n border: 1px solid color-mix(in oklch, var(--ax-err) 40%, transparent);\n}\n.ax-next-steps { margin-top: 12px; padding-left: 20px; color: var(--ax-text-2); }\n.ax-next-steps li { margin: 4px 0; }\n.ax-next-steps code { background: var(--ax-surface-2); padding: 1px 6px; border-radius: 3px; color: var(--ax-text); font-family: var(--ax-mono); font-size: var(--ax-fs-sm); }`\n\nconst SETUP_SCRIPT = `<script>\ndocument.getElementById('enableTelegram').addEventListener('change', (e) => {\n document.getElementById('telegramFields').classList.toggle('is-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 = 'ax-msg'; 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 = 'ax-msg is-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 = 'ax-msg is-ok';\n msg.innerHTML = '<b>' + data.summary + '</b><ol class=\"ax-next-steps\">' + steps + '</ol>';\n btn.textContent = 'Done';\n btn.disabled = true;\n } catch (err) {\n msg.className = 'ax-msg is-err'; msg.textContent = err.message;\n btn.disabled = false; btn.textContent = 'Save and continue';\n }\n});\n</script>`\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":"qHAAA,OAAS,cAAAA,EAAY,gBAAAC,EAAc,iBAAAC,EAAe,kBAAAC,EAAgB,aAAAC,MAAiB,KACnF,OAAS,WAAAC,MAAe,OACxB,OAAS,YAAAC,MAAgB,KCAzBC,IAFA,OAAS,cAAAC,EAAY,gBAAAC,EAAc,iBAAAC,EAAe,gBAAAC,MAAoB,KACtE,OAAS,WAAAC,MAAe,OAiCjB,SAASC,EACdC,EACAC,EAAsB,CAAC,EACgC,CACvD,IAAMC,EAAOD,EAAK,YAAcH,EAAQ,QAAQ,IAAI,EAAG,aAAa,EACpE,GAAI,CAACJ,EAAWQ,CAAI,EAAG,MAAM,IAAI,MAAM,4BAA4BA,GAAM,EAEzE,IAAMC,EAAM,KAAK,MAAMR,EAAaO,EAAM,OAAO,CAAC,EAC5CE,EAAMJ,EAAQG,CAAG,EACjBE,EAA0B,OAAOD,GAAQ,SAAW,CAAE,QAASA,CAAI,EAAIA,EAEvEE,EAAO,KAAK,UAAUH,EAAK,KAAM,CAAC,EAAI;AAAA,EACtCI,EAAML,EAAO,OACnBN,EAAcW,EAAKD,EAAM,OAAO,EAChC,GAAI,CACF,KAAK,MAAMX,EAAaY,EAAK,OAAO,CAAC,CACvC,OAASC,EAAP,CACA,MAAM,IAAI,MAAM,sCAAsCA,EAAE,SAAS,CACnE,CAEA,IAAIC,EACJ,OAAKR,EAAK,aACRQ,EAAa,GAAGP,SAAY,KAAK,IAAI,IACrCL,EAAaK,EAAMO,CAAU,GAE/Bb,EAAcM,EAAMI,EAAM,OAAO,EAE5BL,EAAK,YAAYS,EAAmB,EAAE,MAAM,IAAM,CAA0B,CAAC,EAE3E,CAAE,QAASL,EAAO,QAAS,QAASA,EAAO,QAAS,WAAAI,CAAW,CACxE,CAQO,SAASE,EACdR,EACAF,EAAqD,CAAC,EACpC,CAClB,IAAMC,EAAOD,EAAK,YAAcH,EAAQ,QAAQ,IAAI,EAAG,aAAa,EACpE,GAAIJ,EAAWQ,CAAI,GAAK,CAACD,EAAK,UAC5B,MAAM,IAAI,MAAM,iCAAiCC,uCAA0C,EAE7F,OAAAN,EAAcM,EAAM,KAAK,UAAUC,EAAK,KAAM,CAAC,EAAI;AAAA,EAAM,OAAO,EACzD,CAAE,KAAAD,CAAK,CAChB,CAMA,eAAeQ,GAAoC,CACjD,GAAI,CAEF,IAAME,EADMC,EAAiB,EACb,UAAU,WAAW,QAAQ,OAAQ,EAAE,GAAK,yBAC5D,MAAM,MAAM,GAAGD,WAAc,CAAE,OAAQ,MAAO,CAAC,EAAE,MAAM,IAAM,IAAI,CACnE,MAAE,CAA4C,CAChD,CChFO,SAASE,EAAgBC,EAAoBC,EAA6B,CAC/E,IAAMC,EAAUF,EAAM,aACjBA,EAAM,aAAe,EAAI,uBAAyB,oBACnD,mBAIEG,EAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,WAmBfC,EAAQC,EAAS,EAAG,cACxBC,EAAM,CACJ,KAAM,WACN,MAAO,YACP,KAAM,wCACN,MAAOL,EACP,YAAa,UACb,SAAU,EACZ,CAAC,CACH,EAEMM,EAAQF,EAAS,EAAG,cACxBG,EAAI,CACFF,EAAM,CAAE,KAAM,YAAa,MAAO,aAAc,MAAO,YAAa,YAAa,cAAe,SAAU,EAAK,CAAC,EAChHA,EAAM,CAAE,KAAM,UAAW,MAAO,WAAY,KAAM,yBAA0B,MAAO,YAAa,QAAS,sBAAuB,SAAU,EAAK,CAAC,CAClJ,CAAC,EACDA,EAAM,CACJ,KAAM,eACN,MAAO,gBACP,KAAM,wEACN,MAAO,wBACP,SAAU,EACZ,CAAC,EACDE,EAAI,CACFF,EAAM,CACJ,KAAM,OACN,MAAO,YACP,KAAM,SACN,QAAS,CACP,CAAE,MAAO,cAAe,MAAO,4BAA6B,SAAU,EAAK,EAC3E,CAAE,MAAO,MAAO,MAAO,yBAA0B,CACnD,CACF,CAAC,EACDA,EAAM,CACJ,KAAM,QACN,MAAO,QACP,KAAM,aACN,MAAO,oBACP,YAAa,mBACf,CAAC,CACH,CAAC,EACDA,EAAM,CACJ,KAAM,cACN,MAAO,6BACP,KAAM,2DACN,KAAM,WACN,YAAa,oHACf,CAAC,CACH,EAEMG,EAAQJ,EAAS,EAAG,gBAAiB;AAAA;AAAA;AAAA,QAGrCC,EAAM,CAAE,KAAM,oBAAqB,MAAO,aAAc,KAAM,wDAAoD,MAAO,SAAU,CAAC;AAAA,QACpIA,EAAM,CAAE,KAAM,mBAAoB,MAAO,YAAa,KAAM,yEAA0E,YAAa,kBAAmB,CAAC;AAAA,QACvKA,EAAM,CAAE,KAAM,sBAAuB,MAAO,eAAgB,KAAM,qDAAiD,YAAa,gBAAiB,CAAC;AAAA;AAAA,GAEvJ,EAEKI,EAAQL,EAAS,EAAG,oBACxBC,EAAM,CACJ,KAAM,kBACN,MAAO,UACP,KAAM,qDACN,KAAM,WACN,YAAa,gBACb,WAAY,oBACd,CAAC,EACD;AAAA;AAAA,WAGF,EAEMK,EAASX,EAAM,aACjB,+CAA+CA,EAAM,+BAA+BA,EAAM,oFAC1F,GAEEY,EAAO;AAAA,8BACeC,EAAIX,CAAO;AAAA;AAAA;AAAA,IAGrCS;AAAA;AAAA;AAAA;AAAA;AAAA,MAKEP;AAAA,MACAG;AAAA,MACAE;AAAA,MACAC;AAAA;AAAA,QAEEI,EAAI,oBAAqB,CAAE,KAAM,SAAU,QAAS,GAAM,GAAI,WAAY,CAAC;AAAA,QAC3EA,EAAI,yBAA0B,CAAE,QAAS,8BAA+B,CAAC;AAAA;AAAA;AAAA,QAK/E,OAAOC,EAAY,CACjB,MAAO,sBACP,UAAW,SACX,SAAU,QACV,aAAAZ,EACA,OAAQ,GACR,KAAAS,EACA,IAAKI,EACL,QAASC,CACX,CAAC,CACH,CAKA,IAAMD,EAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,gLAwDjBC,EAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;WF/Jd,SAASC,EAAYC,EAAkB,QAAQ,IAAI,EAAgB,CACxE,IAAMC,EAAOC,EAAQF,EAAS,aAAa,EAC3C,GAAI,CAACG,EAAWF,CAAI,EAAG,MAAO,CAAE,aAAc,GAAO,WAAY,EAAG,aAAc,CAAE,EACpF,GAAI,CACF,IAAMG,EAAM,KAAK,MAAMC,EAAaJ,EAAM,OAAO,CAAC,EAC5CK,EAASF,EAAI,OAAS,OAAO,KAAKA,EAAI,MAAM,EAAE,OAAS,EACvDG,EAAWH,EAAI,SACjB,OAAO,OAAOA,EAAI,QAAQ,EAAE,OAAQI,GAAWA,GAAKA,EAAE,OAAO,EAAE,OAC/D,EACJ,MAAO,CAAE,aAAc,GAAM,WAAYF,EAAQ,aAAcC,EAAU,SAAUH,EAAI,MAAM,IAAK,CACpG,MAAE,CACA,MAAO,CAAE,aAAc,GAAO,WAAY,EAAG,aAAc,CAAE,CAC/D,CACF,CAOO,SAASK,EAAUC,EAAwBV,EAAkB,QAAQ,IAAI,EAI9E,CAEA,GAAI,CAACU,EAAQ,UAAU,KAAK,EAAG,MAAM,IAAI,MAAM,wBAAwB,EACvE,GAAI,CAACA,EAAQ,OAAO,IAAI,KAAK,EAAG,MAAM,IAAI,MAAM,uBAAuB,EACvE,GAAI,CAAC,wBAAwB,KAAKA,EAAQ,MAAM,EAAE,EAChD,MAAM,IAAI,MAAM,sFAAsF,EAExG,GAAI,CAACA,EAAQ,MAAM,MAAM,KAAK,EAAG,MAAM,IAAI,MAAM,yBAAyB,EAC1E,IAAMC,EAAWC,EAAcF,EAAQ,MAAM,YAAY,EACzD,GAAIC,EAAS,SAAW,EAAG,MAAM,IAAI,MAAM,wCAAwC,EACnF,GAAID,EAAQ,MAAM,OAAS,eAAiBA,EAAQ,MAAM,OAAS,MACjE,MAAM,IAAI,MAAM,uCAAuC,EAGzD,IAAMG,EAAaX,EAAQF,EAAS,aAAa,EAC3Cc,EAAUZ,EAAQF,EAAS,MAAM,EACjCe,EAAeb,EAAQF,EAAS,SAAUU,EAAQ,MAAM,EAAE,EAC1DM,EAAQjB,EAAYC,CAAO,EAGjCiB,EAAUF,EAAc,CAAE,UAAW,EAAK,CAAC,EAC3C,IAAMG,EAAWhB,EAAQa,EAAc,WAAW,EAClD,GAAI,CAACZ,EAAWe,CAAQ,EAAG,CACzB,IAAMC,EAAcT,EAAQ,MAAM,aAAa,KAAK,GAAK,WAAWA,EAAQ,MAAM,6BAA6BA,EAAQ,iBACvHU,EAAcF,EAAU,KAAKR,EAAQ,MAAM;AAAA;AAAA,EAAWS;AAAA,CAAe,EAIvE,IAAME,EAAmBX,EAAQ,SAC7B,MAAMA,EAAQ,MAAM,GAAG,YAAY,cACnC,OAEAY,EACJ,GAAKN,EAAM,aA4BTM,EAxBeC,EAAoBnB,GAAQ,CAWzC,GAVAA,EAAI,KAAOA,EAAI,MAAQ,CAAC,EACnBA,EAAI,KAAK,OAAMA,EAAI,KAAK,KAAOM,EAAQ,UACvCN,EAAI,KAAK,KAAIA,EAAI,KAAK,GAAKoB,EAAQd,EAAQ,QAAQ,GAAK,QACxDN,EAAI,KAAK,OAAMA,EAAI,KAAK,KAAO,mBACpCA,EAAI,KAAK,aAAeA,EAAI,KAAK,cAAgBM,EAAQ,MAAM,GAE/DN,EAAI,UAAYA,EAAI,WAAa,CAAC,EAC7BA,EAAI,UAAU,SAAQA,EAAI,UAAU,OAAS,CAAE,OAAQ,sBAAuB,GAEnFA,EAAI,OAASA,EAAI,QAAU,CAAC,EACxBA,EAAI,OAAOM,EAAQ,MAAM,EAAE,EAAG,MAAM,IAAI,MAAM,UAAUA,EAAQ,MAAM,qBAAqB,EAC/F,OAAAN,EAAI,OAAOM,EAAQ,MAAM,EAAE,EAAIe,EAAWf,EAASK,EAAcJ,CAAQ,EAErED,EAAQ,WACVN,EAAI,SAAWA,EAAI,UAAY,CAAC,EAChCA,EAAI,SAAS,SAAWA,EAAI,SAAS,UAAY,CAAE,QAAS,GAAM,SAAU,CAAC,EAAG,OAAQ,CAAE,GAAI,OAAQ,MAAO,kBAAmB,CAAE,EAClIA,EAAI,SAAS,SAAS,QAAU,GAChCA,EAAI,SAAS,SAAS,SAAWA,EAAI,SAAS,SAAS,UAAY,CAAC,EACpEA,EAAI,SAAS,SAAS,SAASM,EAAQ,SAAS,SAAS,EAAIgB,EAAqBhB,EAASW,CAAiB,GAGvG,CAAE,QAAS,gBAAgBX,EAAQ,MAAM,OAASA,EAAQ,SAAW,sBAAwB,GAAI,CAC1G,EAAG,CAAE,WAAAG,CAAW,CAAC,EACG,eA5BG,CACvB,IAAMT,EAAMuB,EAAiBjB,EAASK,EAAcJ,EAAUU,CAAgB,EAC9EO,EAAkBxB,EAAK,CAAE,WAAAS,CAAW,CAAC,EA8BvCgB,EAAiBf,EAASJ,EAASW,CAAgB,EAEnD,IAAMS,EAAsB,CAAC,EAC7B,OAAIpB,EAAQ,iBAAmB,CAACP,EAAWD,EAAQF,EAAS,MAAM,CAAC,IACjE8B,EAAU,KAAK,mCAAmC,EAEhDpB,EAAQ,MAAM,OAAS,eACzBoB,EAAU,KAAK,gGAAgG,EAEjHA,EAAU,KAAK,wCAAwC,EACvDA,EAAU,KAAK,4CAA4C,EAEpD,CACL,QAASd,EAAM,aACX,gBAAgBN,EAAQ,MAAM,oBAC9B,2CAA2CA,EAAQ,MAAM,OAC7D,UAAAoB,EACA,WAAAR,CACF,CACF,CAEA,SAASV,EAAcmB,EAAqB,CAC1C,OAAQA,GAAK,IACV,MAAM,QAAQ,EACd,IAAKC,GAAMA,EAAE,KAAK,CAAC,EACnB,OAAO,OAAO,EAEd,OAAO,CAACA,EAAGC,EAAG,IAAM,EAAE,QAAQD,CAAC,IAAMC,CAAC,CAC3C,CAEA,SAAST,EAAQO,EAAmB,CAClC,OAAQA,GAAK,IAAI,YAAY,EAAE,QAAQ,eAAgB,GAAG,EAAE,QAAQ,WAAY,EAAE,CACpF,CAEA,SAASJ,EAAiBjB,EAAwBK,EAAsBJ,EAAoBU,EAA2B,CAErH,IAAMjB,EAAW,CACf,KAAM,CAAE,GAFCoB,EAAQd,EAAQ,QAAQ,GAAK,OAE1B,KAAMA,EAAQ,SAAU,KAAM,kBAAmB,aAAcA,EAAQ,MAAM,EAAG,EAC5F,UAAW,CAAE,OAAQ,CAAE,OAAQ,sBAAuB,CAAE,EACxD,OAAQ,CAAE,CAACA,EAAQ,MAAM,EAAE,EAAGe,EAAWf,EAASK,EAAcJ,CAAQ,CAAE,EAC1E,SAAU,CAAC,EACX,MAAO,CAAC,EACR,KAAM,CAAE,QAAS,GAAO,MAAO,CAAC,EAAG,UAAW,SAAU,YAAa,CAAE,SAAU,GAAI,QAAS,EAAG,CAAE,EACnG,UAAW,CAAE,QAAS,GAAM,KAAM,KAAM,KAAM,YAAa,UAAW,wBAAyB,EAC/F,OAAQ,CAAC,CACX,EACA,OAAID,EAAQ,UAAYW,EACtBjB,EAAI,SAAS,SAAW,CACtB,QAAS,GACT,SAAU,CAAE,CAACM,EAAQ,SAAS,SAAS,EAAGgB,EAAqBhB,EAASW,CAAgB,CAAE,EAC1F,OAAQ,CAAE,GAAI,OAAQ,MAAO,kBAAmB,CAClD,EAEAjB,EAAI,SAAS,SAAW,CAAE,QAAS,GAAO,SAAU,CAAC,EAAG,OAAQ,CAAE,GAAI,OAAQ,MAAO,kBAAmB,CAAE,EAErGA,CACT,CAEA,SAASqB,EAAWf,EAAwBK,EAAsBJ,EAAoB,CACpF,IAAMuB,EAAa,CACjB,KAAMxB,EAAQ,MAAM,KACpB,UAAWK,EACX,KAAML,EAAQ,MAAM,KACpB,SAAAC,EACA,cAAe,EACf,eAAgB,SAClB,EACA,OAAID,EAAQ,MAAM,QAAOwB,EAAM,MAAQxB,EAAQ,MAAM,OACjDA,EAAQ,MAAM,aAAa,KAAK,IAAGwB,EAAM,aAAexB,EAAQ,MAAM,YAAY,KAAK,EAAE,MAAM,EAAG,GAAG,GAClGwB,CACT,CAEA,SAASR,EAAqBhB,EAAwByB,EAAkB,CACtE,MAAO,CACL,SAAU,KAAOA,EAAW,IAC5B,YAAazB,EAAQ,UAAU,aAAe,GAC9C,aAAcA,EAAQ,MAAM,EAC9B,CACF,CAEA,SAASmB,EAAiBf,EAAiBJ,EAAwBW,EAAiC,CAClG,IAAMe,EAAWjC,EAAWW,CAAO,EAAIT,EAAaS,EAAS,OAAO,EAAI,GAClEuB,EAAkB,CAAC,EASzB,GAPI3B,EAAQ,iBAAmB,CAAC0B,EAAS,SAAS,oBAAoB,GACpEC,EAAM,KAAK,qBAAqB3B,EAAQ,iBAAiB,EAEvDA,EAAQ,UAAYW,GAAoB,CAACe,EAAS,SAAS,GAAGf,IAAmB,GACnFgB,EAAM,KAAK,GAAGhB,KAAoBX,EAAQ,SAAS,UAAU,EAG3D,CAACP,EAAWW,CAAO,EAAG,CAMxBM,EAAcN,EAAS,CAAC,GALT,CACb,iCACA,yDACA,EACF,EACmC,GAAGuB,EAAO,EAAE,EAAE,KAAK;AAAA,CAAI,CAAC,EAC3D,OAEF,GAAIA,EAAM,OAAS,EAAG,CACpB,IAAMC,EAAMF,EAAS,SAAS;AAAA,CAAI,EAAI,GAAK;AAAA,EAC3CG,EAAezB,EAASwB,EAAMD,EAAM,KAAK;AAAA,CAAI,EAAI;AAAA,CAAI,EAEzD,CAIO,SAASG,GAAgBC,EAAuBC,EAA2B,CAChF,IAAM1B,EAAQjB,EAAY,EACpB4C,EAAc3B,EAAM,UAAY4B,EAAS,EAAE,QAAQ,WAAY,EAAE,EACjEC,EAAOC,EAAgB9B,EAAO2B,CAAW,EAC/CD,EAAI,UAAU,IAAK,CAAE,eAAgB,0BAA2B,CAAC,EACjEA,EAAI,IAAIG,CAAI,CACd,CAEA,eAAsBE,GAAiBC,EAAsBN,EAAoC,CAC/F,IAAIO,EAAM,GACV,MAAM,IAAI,QAAeC,GAAO,CAAEF,EAAI,GAAG,OAASxC,GAAM,CAAEyC,GAAOzC,CAAE,CAAC,EAAGwC,EAAI,GAAG,MAAO,IAAME,EAAG,CAAC,CAAE,CAAC,EAClG,IAAIxC,EACJ,GAAI,CAAEA,EAAU,KAAK,MAAMuC,CAAG,CAAE,MAChC,CAAQP,EAAI,UAAU,IAAK,CAAE,eAAgB,kBAAmB,CAAC,EAAGA,EAAI,IAAI,KAAK,UAAU,CAAE,MAAO,cAAe,CAAC,CAAC,EAAG,MAAO,CAC/H,GAAI,CACF,IAAMS,EAAS1C,EAAUC,CAAO,EAChCgC,EAAI,UAAU,IAAK,CAAE,eAAgB,kBAAmB,CAAC,EACzDA,EAAI,IAAI,KAAK,UAAUS,CAAM,CAAC,CAChC,OAASC,EAAP,CACAV,EAAI,UAAU,IAAK,CAAE,eAAgB,kBAAmB,CAAC,EACzDA,EAAI,IAAI,KAAK,UAAU,CAAE,MAAOU,EAAE,SAAW,eAAgB,CAAC,CAAC,CACjE,CACF,CG5QA,OAAS,cAAAC,EAAY,eAAAC,MAAmB,SACxC,OAAS,cAAAC,EAAY,aAAAC,EAAW,gBAAAC,GAAc,iBAAAC,OAAqB,KACnE,OAAS,WAAAC,GAAS,WAAAC,OAAe,OAmB1B,IAAMC,EAAe,YACtBC,GAAe,sBAcRC,EAAN,KAAiB,CACd,KAER,YAAYC,EAAkB,QAAQ,IAAI,EAAGC,EAAkBH,GAAc,CAC3E,KAAK,KAAOH,GAAQK,EAASC,CAAO,CACtC,CAEQ,MAAsB,CAC5B,GAAI,CAACV,EAAW,KAAK,IAAI,EAAG,MAAO,CAAC,EACpC,GAAI,CAAE,OAAO,KAAK,MAAME,GAAa,KAAK,KAAM,OAAO,CAAC,CAAmB,MAAE,CAAQ,MAAO,CAAC,CAAE,CACjG,CAEQ,KAAKS,EAA8B,CACzCV,EAAUI,GAAQ,KAAK,IAAI,EAAG,CAAE,UAAW,EAAK,CAAC,EACjDF,GAAc,KAAK,KAAM,KAAK,UAAUQ,EAAS,KAAM,CAAC,EAAI;AAAA,EAAM,OAAO,CAC3E,CAQA,OAAOC,EAIoC,CACzC,GAAI,CAACA,EAAM,MAAM,KAAK,EAAG,MAAM,IAAI,MAAM,yBAAyB,EAClE,GAAI,CAAC,MAAM,QAAQA,EAAM,MAAM,GAAKA,EAAM,OAAO,SAAW,EAC1D,MAAM,IAAI,MAAM,iCAAiC,EAEnD,QAAWC,KAAKD,EAAM,OACpB,GAAI,CAACE,GAAaD,CAAC,EAAG,MAAM,IAAI,MAAM,kBAAkBA,GAAG,EAE7D,IAAME,EAAOhB,EAAY,EAAE,EAAE,SAAS,KAAK,EACrCiB,EAAQV,EAAeS,EACvBE,EAAsB,CAC1B,GAAI,OAASlB,EAAY,CAAC,EAAE,SAAS,KAAK,EAC1C,KAAMa,EAAM,KAAK,KAAK,EACtB,OAAQN,EAAeS,EAAK,MAAM,EAAG,CAAC,EACtC,KAAMG,EAAUF,CAAK,EACrB,OAAQ,CAAC,GAAGJ,EAAM,MAAM,EACxB,UAAW,IAAI,KAAK,EAAE,YAAY,EAClC,UAAWA,EAAM,cACb,IAAI,KAAK,KAAK,IAAI,EAAIA,EAAM,cAAgB,MAAQ,GAAI,EAAE,YAAY,EACtE,MACN,EACMD,EAAU,KAAK,KAAK,EAC1B,OAAAA,EAAQ,KAAKM,CAAM,EACnB,KAAK,KAAKN,CAAO,EACV,CAAE,MAAAK,EAAO,OAAAC,CAAO,CACzB,CAEA,MAAsB,CACpB,OAAO,KAAK,KAAK,CACnB,CAEA,OAAOE,EAAgC,CACrC,IAAMR,EAAU,KAAK,KAAK,EACpBS,EAAMT,EAAQ,KAAM,GAAM,EAAE,KAAOQ,CAAE,EAC3C,OAAKC,GACLA,EAAI,UAAY,IAAI,KAAK,EAAE,YAAY,EACvC,KAAK,KAAKT,CAAO,EACVS,GAHU,IAInB,CAMA,OAAOJ,EAAmC,CACxC,GAAI,CAACA,GAAS,CAACA,EAAM,WAAWV,CAAY,EAAG,OAAO,KACtD,IAAMe,EAAOH,EAAUF,CAAK,EACtBL,EAAU,KAAK,KAAK,EACpBS,EAAMT,EAAQ,KAAMW,GAAMA,EAAE,OAASD,CAAI,EAG/C,GAFI,CAACD,GACDA,EAAI,WACJA,EAAI,WAAa,KAAK,MAAMA,EAAI,SAAS,EAAI,KAAK,IAAI,EAAG,OAAO,KAEpE,IAAMG,EAAM,KAAK,IAAI,EACfC,EAAOJ,EAAI,WAAa,KAAK,MAAMA,EAAI,UAAU,EAAI,EAC3D,GAAIG,EAAMC,EAAO,IAAQ,CACvBJ,EAAI,WAAa,IAAI,KAAKG,CAAG,EAAE,YAAY,EAC3C,GAAI,CAAE,KAAK,KAAKZ,CAAO,CAAE,MAAE,CAA0B,EAEvD,OAAOS,CACT,CACF,EAOO,SAASK,GAAeR,EAAqBS,EAA2B,CAG7E,MAFI,GAAAT,EAAO,OAAO,SAASS,CAAQ,GAC/BA,EAAS,WAAW,QAAQ,GAAKT,EAAO,OAAO,SAAS,SAAS,GACjES,IAAa,kBAAoBT,EAAO,OAAO,SAAS,iBAAiB,EAE/E,CAEA,SAASH,GAAaD,EAAoB,CACxC,OACEA,IAAM,kBACNA,IAAM,mBACNA,IAAM,WACN,8BAA8B,KAAKA,CAAC,GACpCA,IAAM,WAEV,CAEO,SAASK,EAAUF,EAAuB,CAC/C,MAAO,UAAYlB,EAAW,QAAQ,EAAE,OAAOkB,CAAK,EAAE,OAAO,KAAK,CACpE,CAOO,SAASW,GAAaC,EAA8F,CACzH,IAAMC,EAAOD,EAAI,QAAQ,cACnBE,EAAa,MAAM,QAAQD,CAAI,EAAIA,EAAK,CAAC,EAAIA,EACnD,GAAIC,GAAcA,EAAW,YAAY,EAAE,WAAW,SAAS,EAC7D,OAAOA,EAAW,MAAM,CAAC,EAAE,KAAK,EAElC,GAAIF,EAAI,IACN,GAAI,CAEF,IAAMG,EADI,IAAI,IAAIH,EAAI,IAAK,kBAAkB,EAC/B,aAAa,IAAI,OAAO,EACtC,GAAIG,EAAK,OAAOA,CAClB,MAAE,CAAc,CAElB,OAAO,IACT","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","renderSetupPage","state","defaultNode","heading","customHeader","step1","stepCard","field","step2","row","step3","step4","banner","body","esc","btn","renderShell","SETUP_PAGE_CSS","SETUP_SCRIPT","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","defaultNode","hostname","html","renderSetupPage","handleWizardPost","req","raw","ok","result","e","createHash","randomBytes","existsSync","mkdirSync","readFileSync","writeFileSync","resolve","dirname","TOKEN_PREFIX","DEFAULT_FILE","TokenStore","baseDir","fileRel","records","input","s","isValidScope","body","token","record","hashToken","id","rec","hash","r","now","last","recordHasScope","required","extractToken","req","auth","authHeader","tok"]}
1
+ {"version":3,"sources":["../src/daemon/setup-wizard.ts","../src/daemon/config-mutate.ts","../src/daemon/ui/pages/setup.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\"\nimport { renderSetupPage } from \"./ui/pages/setup\"\n\n/** Shape of the pre-render state snapshot — exported so the page file can\n * type its props without re-importing this whole module. */\nexport interface WizardState {\n configExists: boolean\n agentCount: number\n channelCount: number\n nodeName?: string\n}\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()): WizardState {\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 defaultNode = state.nodeName || hostname().replace(/\\.local$/, \"\")\n const html = renderSetupPage(state, defaultNode)\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\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","// --- Setup wizard page ---\n//\n// First-run flow for non-technical operators: team name, first agent,\n// optional Telegram, optional Anthropic key. Writes agentx.json, scaffolds\n// the agent's workspace, writes .env, and points the operator at the next\n// step.\n//\n// The server-side state machine + form handler live in ../../setup-wizard.ts\n// — this file owns ONLY the HTML. That keeps the data flow inspectable\n// (one place) and the visual surface hot-reloadable (edit → rebuild → view).\n\nimport { renderShell, esc, stepCard, field, row, btn } from \"..\"\nimport type { WizardState } from \"../../setup-wizard\"\n\nexport function renderSetupPage(state: WizardState, defaultNode: string): string {\n const heading = state.configExists\n ? (state.agentCount === 0 ? \"Add your first agent\" : \"Add another agent\")\n : \"Set up your team\"\n\n // Custom topbar: /setup is pre-dashboard, we skip the tab nav and show\n // just brand + subtitle + theme switcher + \"Skip to dashboard\" pill.\n const customHeader = `<header class=\"ax-topbar\">\n <div class=\"ax-topbar__left\">\n <div class=\"ax-brand\">\n <span class=\"ax-brand__mark\">AX</span>\n <span class=\"ax-brand__name\">AgentX</span>\n <span class=\"ax-brand__subtitle\">Setup</span>\n </div>\n </div>\n <div class=\"ax-topbar__right\">\n <a href=\"/glossary\" class=\"ax-skip\">? Glossary</a>\n <a href=\"/live\" class=\"ax-skip\">Skip — open dashboard</a>\n <div class=\"ax-theme-switch\" role=\"tablist\" aria-label=\"Theme\">\n <button data-theme-opt=\"dark\">Dark</button>\n <button data-theme-opt=\"light\">Light</button>\n <button data-theme-opt=\"crt\">CRT</button>\n </div>\n </div>\n</header>`\n\n const step1 = stepCard(1, \"Team basics\",\n field({\n name: \"nodeName\",\n label: \"Team name\",\n hint: \"(what you'd call this AgentX install)\",\n value: defaultNode,\n placeholder: \"My Team\",\n required: true,\n })\n )\n\n const step2 = stepCard(2, \"First agent\",\n row([\n field({ name: \"agentName\", label: \"Agent name\", value: \"Assistant\", placeholder: \"Support Bot\", required: true }),\n field({ name: \"agentId\", label: \"Agent id\", hint: \"(lowercase, no spaces)\", value: \"assistant\", pattern: \"[a-z0-9][a-z0-9_-]*\", required: true }),\n ]) +\n field({\n name: \"triggerWords\",\n label: \"Trigger words\",\n hint: `(comma or space separated — e.g. <code>@support, support</code>)`,\n value: \"@assistant, assistant\",\n required: true,\n }) +\n row([\n field({\n name: \"tier\",\n label: \"AI engine\",\n type: \"select\",\n options: [\n { value: \"claude-code\", label: \"Claude Code (recommended)\", selected: true },\n { value: \"sdk\", label: \"Anthropic API (BYO key)\" },\n ],\n }),\n field({\n name: \"model\",\n label: \"Model\",\n hint: \"(optional)\",\n value: \"claude-sonnet-4-6\",\n placeholder: \"claude-sonnet-4-6\",\n }),\n ]) +\n field({\n name: \"personality\",\n label: \"Personality / instructions\",\n hint: \"(optional — what this agent does, in plain English)\",\n type: \"textarea\",\n placeholder: \"You are a support agent for Acme Co. Answer customer questions about our product, be friendly, keep replies short.\",\n })\n )\n\n const step3 = stepCard(3, \"First channel\", `\n <label class=\"ax-toggle\"><input type=\"checkbox\" id=\"enableTelegram\" /> Connect Telegram now</label>\n <div class=\"ax-optional\" id=\"telegramFields\">\n ${field({ name: \"telegramAccountId\", label: \"Account id\", hint: `(a label you choose — e.g. <code>support</code>)`, value: \"default\" })}\n ${field({ name: \"telegramBotToken\", label: \"Bot token\", hint: `(from <a href=\"https://t.me/BotFather\" target=\"_blank\">@BotFather</a>)`, placeholder: \"123456:ABCdef...\" })}\n ${field({ name: \"telegramBotUsername\", label: \"Bot username\", hint: `(optional — e.g. <code>my_support_bot</code>)`, placeholder: \"my_support_bot\" })}\n </div>\n `)\n\n const step4 = stepCard(4, \"Anthropic API key\",\n field({\n name: \"anthropicApiKey\",\n label: \"API key\",\n hint: \"(optional — skip if you're using Claude Code)\",\n type: \"password\",\n placeholder: \"sk-ant-…\",\n extraAttrs: `autocomplete=\"off\"`,\n }) +\n `<div class=\"ax-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 )\n\n const banner = state.configExists\n ? `<div class=\"ax-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\n const body = `<div class=\"ax-wizard\">\n <h1 class=\"ax-wizard__h1\">${esc(heading)}</h1>\n <p class=\"ax-wizard__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 ${banner}\n\n <div id=\"msg\" class=\"ax-msg\"></div>\n\n <form id=\"wizard\" autocomplete=\"off\">\n ${step1}\n ${step2}\n ${step3}\n ${step4}\n <footer class=\"ax-actions\">\n ${btn(\"Save and continue\", { type: \"submit\", primary: true, id: \"submitBtn\" })}\n ${btn(\"Skip, I'll do it later\", { onclick: \"window.location.href='/live'\" })}\n </footer>\n </form>\n</div>`\n\n return renderShell({\n title: \"AgentX — Setup\",\n activeTab: \"custom\",\n subtitle: \"Setup\",\n customHeader,\n noMain: true,\n body,\n css: SETUP_PAGE_CSS,\n scripts: SETUP_SCRIPT,\n })\n}\n\n/** CSS that's specific to the setup page — wizard layout + optional-fields\n * toggle + result messaging. Everything reusable (form fields, step cards,\n * buttons) comes from AX_COMPONENTS_CSS. */\nconst SETUP_PAGE_CSS = `\n/* Pre-dashboard topbar variant — no tab nav, the pills are the only chrome. */\n.ax-topbar__right a.ax-skip {\n padding: 4px 10px; border: 1px solid var(--ax-border-2); border-radius: 4px;\n color: var(--ax-text-2); font-size: var(--ax-fs-xs); text-decoration: none;\n}\n.ax-topbar__right a.ax-skip:hover { color: var(--ax-accent); border-color: var(--ax-accent); }\n\n/* Wizard frame */\n.ax-wizard { max-width: 680px; margin: 0 auto; padding: 36px 22px 80px; }\n.ax-wizard__h1 { font-size: 22px; font-weight: 600; margin: 0 0 4px; letter-spacing: -0.015em; }\n.ax-wizard__lead { color: var(--ax-muted); margin: 0 0 24px; font-size: var(--ax-fs); line-height: 1.55; }\n.ax-wizard__lead code { background: var(--ax-surface-2); padding: 1px 6px; border-radius: 3px; color: var(--ax-text); font-size: var(--ax-fs-sm); }\n\n/* Info banner (\"You already have N agents…\") */\n.ax-banner {\n background: color-mix(in oklch, var(--ax-accent) 10%, var(--ax-bg-elev));\n border: 1px solid color-mix(in oklch, var(--ax-accent) 30%, var(--ax-border-2));\n border-radius: var(--ax-radius);\n padding: 10px 14px; margin-bottom: 18px;\n color: var(--ax-text-2); font-size: var(--ax-fs-sm);\n}\n.ax-banner b { color: var(--ax-text); font-weight: 600; }\n\n/* \"Connect Telegram now\" toggle + collapsible fields */\n.ax-toggle { display: flex; align-items: center; gap: 8px; margin: 4px 0 6px; font-size: var(--ax-fs); color: var(--ax-text); cursor: pointer; user-select: none; }\n.ax-toggle input { width: auto; accent-color: var(--ax-accent); }\n.ax-optional { border-left: 2px solid var(--ax-border-2); padding-left: 14px; margin-top: 10px; display: none; }\n.ax-optional.is-show { display: block; }\n.ax-hint-block { font-size: var(--ax-fs-xs); color: var(--ax-muted); margin-top: 8px; line-height: 1.55; }\n.ax-hint-block code { background: var(--ax-surface-2); padding: 1px 6px; border-radius: 3px; color: var(--ax-text); font-family: var(--ax-mono); font-size: var(--ax-fs-xs); }\n\n/* Form footer */\n.ax-actions {\n display: flex; gap: 10px; padding: 20px 0 0; align-items: center;\n border-top: 1px solid var(--ax-border); margin-top: 20px;\n}\n\n/* Server-response messaging (success / error blocks) */\n.ax-msg { margin: 14px 0; padding: 12px 16px; border-radius: var(--ax-radius); font-size: var(--ax-fs); display: none; }\n.ax-msg.is-ok {\n display: block;\n background: color-mix(in oklch, var(--ax-accent) 12%, transparent);\n color: var(--ax-accent);\n border: 1px solid color-mix(in oklch, var(--ax-accent) 40%, transparent);\n}\n.ax-msg.is-err {\n display: block;\n background: color-mix(in oklch, var(--ax-err) 12%, transparent);\n color: var(--ax-err);\n border: 1px solid color-mix(in oklch, var(--ax-err) 40%, transparent);\n}\n.ax-next-steps { margin-top: 12px; padding-left: 20px; color: var(--ax-text-2); }\n.ax-next-steps li { margin: 4px 0; }\n.ax-next-steps code { background: var(--ax-surface-2); padding: 1px 6px; border-radius: 3px; color: var(--ax-text); font-family: var(--ax-mono); font-size: var(--ax-fs-sm); }`\n\nconst SETUP_SCRIPT = `<script>\ndocument.getElementById('enableTelegram').addEventListener('change', (e) => {\n document.getElementById('telegramFields').classList.toggle('is-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 = 'ax-msg'; 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 = 'ax-msg is-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 = 'ax-msg is-ok';\n msg.innerHTML = '<b>' + data.summary + '</b><ol class=\"ax-next-steps\">' + steps + '</ol>';\n btn.textContent = 'Done';\n btn.disabled = true;\n } catch (err) {\n msg.className = 'ax-msg is-err'; msg.textContent = err.message;\n btn.disabled = false; btn.textContent = 'Save and continue';\n }\n});\n</script>`\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":"8GAAA,OAAS,cAAAA,EAAY,gBAAAC,EAAc,iBAAAC,EAAe,kBAAAC,EAAgB,aAAAC,MAAiB,KACnF,OAAS,WAAAC,MAAe,OACxB,OAAS,YAAAC,MAAgB,KCFzB,OAAS,cAAAC,EAAY,gBAAAC,EAAc,iBAAAC,EAAe,gBAAAC,MAAoB,KACtE,OAAS,WAAAC,MAAe,OAiCjB,SAASC,EACdC,EACAC,EAAsB,CAAC,EACgC,CACvD,IAAMC,EAAOD,EAAK,YAAcE,EAAQ,QAAQ,IAAI,EAAG,aAAa,EACpE,GAAI,CAACC,EAAWF,CAAI,EAAG,MAAM,IAAI,MAAM,4BAA4BA,GAAM,EAEzE,IAAMG,EAAM,KAAK,MAAMC,EAAaJ,EAAM,OAAO,CAAC,EAC5CK,EAAMP,EAAQK,CAAG,EACjBG,EAA0B,OAAOD,GAAQ,SAAW,CAAE,QAASA,CAAI,EAAIA,EAEvEE,EAAO,KAAK,UAAUJ,EAAK,KAAM,CAAC,EAAI;AAAA,EACtCK,EAAMR,EAAO,OACnBS,EAAcD,EAAKD,EAAM,OAAO,EAChC,GAAI,CACF,KAAK,MAAMH,EAAaI,EAAK,OAAO,CAAC,CACvC,OAASE,EAAP,CACA,MAAM,IAAI,MAAM,sCAAsCA,EAAE,SAAS,CACnE,CAEA,IAAIC,EACJ,OAAKZ,EAAK,aACRY,EAAa,GAAGX,SAAY,KAAK,IAAI,IACrCY,EAAaZ,EAAMW,CAAU,GAE/BF,EAAcT,EAAMO,EAAM,OAAO,EAE5BR,EAAK,YAAYc,EAAmB,EAAE,MAAM,IAAM,CAA0B,CAAC,EAE3E,CAAE,QAASP,EAAO,QAAS,QAASA,EAAO,QAAS,WAAAK,CAAW,CACxE,CAQO,SAASG,EACdX,EACAJ,EAAqD,CAAC,EACpC,CAClB,IAAMC,EAAOD,EAAK,YAAcE,EAAQ,QAAQ,IAAI,EAAG,aAAa,EACpE,GAAIC,EAAWF,CAAI,GAAK,CAACD,EAAK,UAC5B,MAAM,IAAI,MAAM,iCAAiCC,uCAA0C,EAE7F,OAAAS,EAAcT,EAAM,KAAK,UAAUG,EAAK,KAAM,CAAC,EAAI;AAAA,EAAM,OAAO,EACzD,CAAE,KAAAH,CAAK,CAChB,CAMA,eAAea,GAAoC,CACjD,GAAI,CAEF,IAAME,EADMC,EAAiB,EACb,UAAU,WAAW,QAAQ,OAAQ,EAAE,GAAK,yBAC5D,MAAM,MAAM,GAAGD,WAAc,CAAE,OAAQ,MAAO,CAAC,EAAE,MAAM,IAAM,IAAI,CACnE,MAAE,CAA4C,CAChD,CChFO,SAASE,EAAgBC,EAAoBC,EAA6B,CAC/E,IAAMC,EAAUF,EAAM,aACjBA,EAAM,aAAe,EAAI,uBAAyB,oBACnD,mBAIEG,EAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,WAmBfC,EAAQC,EAAS,EAAG,cACxBC,EAAM,CACJ,KAAM,WACN,MAAO,YACP,KAAM,wCACN,MAAOL,EACP,YAAa,UACb,SAAU,EACZ,CAAC,CACH,EAEMM,EAAQF,EAAS,EAAG,cACxBG,EAAI,CACFF,EAAM,CAAE,KAAM,YAAa,MAAO,aAAc,MAAO,YAAa,YAAa,cAAe,SAAU,EAAK,CAAC,EAChHA,EAAM,CAAE,KAAM,UAAW,MAAO,WAAY,KAAM,yBAA0B,MAAO,YAAa,QAAS,sBAAuB,SAAU,EAAK,CAAC,CAClJ,CAAC,EACDA,EAAM,CACJ,KAAM,eACN,MAAO,gBACP,KAAM,wEACN,MAAO,wBACP,SAAU,EACZ,CAAC,EACDE,EAAI,CACFF,EAAM,CACJ,KAAM,OACN,MAAO,YACP,KAAM,SACN,QAAS,CACP,CAAE,MAAO,cAAe,MAAO,4BAA6B,SAAU,EAAK,EAC3E,CAAE,MAAO,MAAO,MAAO,yBAA0B,CACnD,CACF,CAAC,EACDA,EAAM,CACJ,KAAM,QACN,MAAO,QACP,KAAM,aACN,MAAO,oBACP,YAAa,mBACf,CAAC,CACH,CAAC,EACDA,EAAM,CACJ,KAAM,cACN,MAAO,6BACP,KAAM,2DACN,KAAM,WACN,YAAa,oHACf,CAAC,CACH,EAEMG,EAAQJ,EAAS,EAAG,gBAAiB;AAAA;AAAA;AAAA,QAGrCC,EAAM,CAAE,KAAM,oBAAqB,MAAO,aAAc,KAAM,wDAAoD,MAAO,SAAU,CAAC;AAAA,QACpIA,EAAM,CAAE,KAAM,mBAAoB,MAAO,YAAa,KAAM,yEAA0E,YAAa,kBAAmB,CAAC;AAAA,QACvKA,EAAM,CAAE,KAAM,sBAAuB,MAAO,eAAgB,KAAM,qDAAiD,YAAa,gBAAiB,CAAC;AAAA;AAAA,GAEvJ,EAEKI,EAAQL,EAAS,EAAG,oBACxBC,EAAM,CACJ,KAAM,kBACN,MAAO,UACP,KAAM,qDACN,KAAM,WACN,YAAa,gBACb,WAAY,oBACd,CAAC,EACD;AAAA;AAAA,WAGF,EAEMK,EAASX,EAAM,aACjB,+CAA+CA,EAAM,+BAA+BA,EAAM,oFAC1F,GAEEY,EAAO;AAAA,8BACeC,EAAIX,CAAO;AAAA;AAAA;AAAA,IAGrCS;AAAA;AAAA;AAAA;AAAA;AAAA,MAKEP;AAAA,MACAG;AAAA,MACAE;AAAA,MACAC;AAAA;AAAA,QAEEI,EAAI,oBAAqB,CAAE,KAAM,SAAU,QAAS,GAAM,GAAI,WAAY,CAAC;AAAA,QAC3EA,EAAI,yBAA0B,CAAE,QAAS,8BAA+B,CAAC;AAAA;AAAA;AAAA,QAK/E,OAAOC,EAAY,CACjB,MAAO,sBACP,UAAW,SACX,SAAU,QACV,aAAAZ,EACA,OAAQ,GACR,KAAAS,EACA,IAAKI,EACL,QAASC,CACX,CAAC,CACH,CAKA,IAAMD,EAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,gLAwDjBC,EAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;WF/Jd,SAASC,EAAYC,EAAkB,QAAQ,IAAI,EAAgB,CACxE,IAAMC,EAAOC,EAAQF,EAAS,aAAa,EAC3C,GAAI,CAACG,EAAWF,CAAI,EAAG,MAAO,CAAE,aAAc,GAAO,WAAY,EAAG,aAAc,CAAE,EACpF,GAAI,CACF,IAAMG,EAAM,KAAK,MAAMC,EAAaJ,EAAM,OAAO,CAAC,EAC5CK,EAASF,EAAI,OAAS,OAAO,KAAKA,EAAI,MAAM,EAAE,OAAS,EACvDG,EAAWH,EAAI,SACjB,OAAO,OAAOA,EAAI,QAAQ,EAAE,OAAQI,GAAWA,GAAKA,EAAE,OAAO,EAAE,OAC/D,EACJ,MAAO,CAAE,aAAc,GAAM,WAAYF,EAAQ,aAAcC,EAAU,SAAUH,EAAI,MAAM,IAAK,CACpG,MAAE,CACA,MAAO,CAAE,aAAc,GAAO,WAAY,EAAG,aAAc,CAAE,CAC/D,CACF,CAOO,SAASK,EAAUC,EAAwBV,EAAkB,QAAQ,IAAI,EAI9E,CAEA,GAAI,CAACU,EAAQ,UAAU,KAAK,EAAG,MAAM,IAAI,MAAM,wBAAwB,EACvE,GAAI,CAACA,EAAQ,OAAO,IAAI,KAAK,EAAG,MAAM,IAAI,MAAM,uBAAuB,EACvE,GAAI,CAAC,wBAAwB,KAAKA,EAAQ,MAAM,EAAE,EAChD,MAAM,IAAI,MAAM,sFAAsF,EAExG,GAAI,CAACA,EAAQ,MAAM,MAAM,KAAK,EAAG,MAAM,IAAI,MAAM,yBAAyB,EAC1E,IAAMC,EAAWC,EAAcF,EAAQ,MAAM,YAAY,EACzD,GAAIC,EAAS,SAAW,EAAG,MAAM,IAAI,MAAM,wCAAwC,EACnF,GAAID,EAAQ,MAAM,OAAS,eAAiBA,EAAQ,MAAM,OAAS,MACjE,MAAM,IAAI,MAAM,uCAAuC,EAGzD,IAAMG,EAAaX,EAAQF,EAAS,aAAa,EAC3Cc,EAAUZ,EAAQF,EAAS,MAAM,EACjCe,EAAeb,EAAQF,EAAS,SAAUU,EAAQ,MAAM,EAAE,EAC1DM,EAAQjB,EAAYC,CAAO,EAGjCiB,EAAUF,EAAc,CAAE,UAAW,EAAK,CAAC,EAC3C,IAAMG,EAAWhB,EAAQa,EAAc,WAAW,EAClD,GAAI,CAACZ,EAAWe,CAAQ,EAAG,CACzB,IAAMC,EAAcT,EAAQ,MAAM,aAAa,KAAK,GAAK,WAAWA,EAAQ,MAAM,6BAA6BA,EAAQ,iBACvHU,EAAcF,EAAU,KAAKR,EAAQ,MAAM;AAAA;AAAA,EAAWS;AAAA,CAAe,EAIvE,IAAME,EAAmBX,EAAQ,SAC7B,MAAMA,EAAQ,MAAM,GAAG,YAAY,cACnC,OAEAY,EACJ,GAAKN,EAAM,aA4BTM,EAxBeC,EAAoBnB,GAAQ,CAWzC,GAVAA,EAAI,KAAOA,EAAI,MAAQ,CAAC,EACnBA,EAAI,KAAK,OAAMA,EAAI,KAAK,KAAOM,EAAQ,UACvCN,EAAI,KAAK,KAAIA,EAAI,KAAK,GAAKoB,EAAQd,EAAQ,QAAQ,GAAK,QACxDN,EAAI,KAAK,OAAMA,EAAI,KAAK,KAAO,mBACpCA,EAAI,KAAK,aAAeA,EAAI,KAAK,cAAgBM,EAAQ,MAAM,GAE/DN,EAAI,UAAYA,EAAI,WAAa,CAAC,EAC7BA,EAAI,UAAU,SAAQA,EAAI,UAAU,OAAS,CAAE,OAAQ,sBAAuB,GAEnFA,EAAI,OAASA,EAAI,QAAU,CAAC,EACxBA,EAAI,OAAOM,EAAQ,MAAM,EAAE,EAAG,MAAM,IAAI,MAAM,UAAUA,EAAQ,MAAM,qBAAqB,EAC/F,OAAAN,EAAI,OAAOM,EAAQ,MAAM,EAAE,EAAIe,EAAWf,EAASK,EAAcJ,CAAQ,EAErED,EAAQ,WACVN,EAAI,SAAWA,EAAI,UAAY,CAAC,EAChCA,EAAI,SAAS,SAAWA,EAAI,SAAS,UAAY,CAAE,QAAS,GAAM,SAAU,CAAC,EAAG,OAAQ,CAAE,GAAI,OAAQ,MAAO,kBAAmB,CAAE,EAClIA,EAAI,SAAS,SAAS,QAAU,GAChCA,EAAI,SAAS,SAAS,SAAWA,EAAI,SAAS,SAAS,UAAY,CAAC,EACpEA,EAAI,SAAS,SAAS,SAASM,EAAQ,SAAS,SAAS,EAAIgB,EAAqBhB,EAASW,CAAiB,GAGvG,CAAE,QAAS,gBAAgBX,EAAQ,MAAM,OAASA,EAAQ,SAAW,sBAAwB,GAAI,CAC1G,EAAG,CAAE,WAAAG,CAAW,CAAC,EACG,eA5BG,CACvB,IAAMT,EAAMuB,EAAiBjB,EAASK,EAAcJ,EAAUU,CAAgB,EAC9EO,EAAkBxB,EAAK,CAAE,WAAAS,CAAW,CAAC,EA8BvCgB,EAAiBf,EAASJ,EAASW,CAAgB,EAEnD,IAAMS,EAAsB,CAAC,EAC7B,OAAIpB,EAAQ,iBAAmB,CAACP,EAAWD,EAAQF,EAAS,MAAM,CAAC,IACjE8B,EAAU,KAAK,mCAAmC,EAEhDpB,EAAQ,MAAM,OAAS,eACzBoB,EAAU,KAAK,gGAAgG,EAEjHA,EAAU,KAAK,wCAAwC,EACvDA,EAAU,KAAK,4CAA4C,EAEpD,CACL,QAASd,EAAM,aACX,gBAAgBN,EAAQ,MAAM,oBAC9B,2CAA2CA,EAAQ,MAAM,OAC7D,UAAAoB,EACA,WAAAR,CACF,CACF,CAEA,SAASV,EAAcmB,EAAqB,CAC1C,OAAQA,GAAK,IACV,MAAM,QAAQ,EACd,IAAKC,GAAMA,EAAE,KAAK,CAAC,EACnB,OAAO,OAAO,EAEd,OAAO,CAACA,EAAGC,EAAG,IAAM,EAAE,QAAQD,CAAC,IAAMC,CAAC,CAC3C,CAEA,SAAST,EAAQO,EAAmB,CAClC,OAAQA,GAAK,IAAI,YAAY,EAAE,QAAQ,eAAgB,GAAG,EAAE,QAAQ,WAAY,EAAE,CACpF,CAEA,SAASJ,EAAiBjB,EAAwBK,EAAsBJ,EAAoBU,EAA2B,CAErH,IAAMjB,EAAW,CACf,KAAM,CAAE,GAFCoB,EAAQd,EAAQ,QAAQ,GAAK,OAE1B,KAAMA,EAAQ,SAAU,KAAM,kBAAmB,aAAcA,EAAQ,MAAM,EAAG,EAC5F,UAAW,CAAE,OAAQ,CAAE,OAAQ,sBAAuB,CAAE,EACxD,OAAQ,CAAE,CAACA,EAAQ,MAAM,EAAE,EAAGe,EAAWf,EAASK,EAAcJ,CAAQ,CAAE,EAC1E,SAAU,CAAC,EACX,MAAO,CAAC,EACR,KAAM,CAAE,QAAS,GAAO,MAAO,CAAC,EAAG,UAAW,SAAU,YAAa,CAAE,SAAU,GAAI,QAAS,EAAG,CAAE,EACnG,UAAW,CAAE,QAAS,GAAM,KAAM,KAAM,KAAM,YAAa,UAAW,wBAAyB,EAC/F,OAAQ,CAAC,CACX,EACA,OAAID,EAAQ,UAAYW,EACtBjB,EAAI,SAAS,SAAW,CACtB,QAAS,GACT,SAAU,CAAE,CAACM,EAAQ,SAAS,SAAS,EAAGgB,EAAqBhB,EAASW,CAAgB,CAAE,EAC1F,OAAQ,CAAE,GAAI,OAAQ,MAAO,kBAAmB,CAClD,EAEAjB,EAAI,SAAS,SAAW,CAAE,QAAS,GAAO,SAAU,CAAC,EAAG,OAAQ,CAAE,GAAI,OAAQ,MAAO,kBAAmB,CAAE,EAErGA,CACT,CAEA,SAASqB,EAAWf,EAAwBK,EAAsBJ,EAAoB,CACpF,IAAMuB,EAAa,CACjB,KAAMxB,EAAQ,MAAM,KACpB,UAAWK,EACX,KAAML,EAAQ,MAAM,KACpB,SAAAC,EACA,cAAe,EACf,eAAgB,SAClB,EACA,OAAID,EAAQ,MAAM,QAAOwB,EAAM,MAAQxB,EAAQ,MAAM,OACjDA,EAAQ,MAAM,aAAa,KAAK,IAAGwB,EAAM,aAAexB,EAAQ,MAAM,YAAY,KAAK,EAAE,MAAM,EAAG,GAAG,GAClGwB,CACT,CAEA,SAASR,EAAqBhB,EAAwByB,EAAkB,CACtE,MAAO,CACL,SAAU,KAAOA,EAAW,IAC5B,YAAazB,EAAQ,UAAU,aAAe,GAC9C,aAAcA,EAAQ,MAAM,EAC9B,CACF,CAEA,SAASmB,EAAiBf,EAAiBJ,EAAwBW,EAAiC,CAClG,IAAMe,EAAWjC,EAAWW,CAAO,EAAIT,EAAaS,EAAS,OAAO,EAAI,GAClEuB,EAAkB,CAAC,EASzB,GAPI3B,EAAQ,iBAAmB,CAAC0B,EAAS,SAAS,oBAAoB,GACpEC,EAAM,KAAK,qBAAqB3B,EAAQ,iBAAiB,EAEvDA,EAAQ,UAAYW,GAAoB,CAACe,EAAS,SAAS,GAAGf,IAAmB,GACnFgB,EAAM,KAAK,GAAGhB,KAAoBX,EAAQ,SAAS,UAAU,EAG3D,CAACP,EAAWW,CAAO,EAAG,CAMxBM,EAAcN,EAAS,CAAC,GALT,CACb,iCACA,yDACA,EACF,EACmC,GAAGuB,EAAO,EAAE,EAAE,KAAK;AAAA,CAAI,CAAC,EAC3D,OAEF,GAAIA,EAAM,OAAS,EAAG,CACpB,IAAMC,EAAMF,EAAS,SAAS;AAAA,CAAI,EAAI,GAAK;AAAA,EAC3CG,EAAezB,EAASwB,EAAMD,EAAM,KAAK;AAAA,CAAI,EAAI;AAAA,CAAI,EAEzD,CAIO,SAASG,GAAgBC,EAAuBC,EAA2B,CAChF,IAAM1B,EAAQjB,EAAY,EACpB4C,EAAc3B,EAAM,UAAY4B,EAAS,EAAE,QAAQ,WAAY,EAAE,EACjEC,EAAOC,EAAgB9B,EAAO2B,CAAW,EAC/CD,EAAI,UAAU,IAAK,CAAE,eAAgB,0BAA2B,CAAC,EACjEA,EAAI,IAAIG,CAAI,CACd,CAEA,eAAsBE,GAAiBC,EAAsBN,EAAoC,CAC/F,IAAIO,EAAM,GACV,MAAM,IAAI,QAAeC,GAAO,CAAEF,EAAI,GAAG,OAASxC,GAAM,CAAEyC,GAAOzC,CAAE,CAAC,EAAGwC,EAAI,GAAG,MAAO,IAAME,EAAG,CAAC,CAAE,CAAC,EAClG,IAAIxC,EACJ,GAAI,CAAEA,EAAU,KAAK,MAAMuC,CAAG,CAAE,MAChC,CAAQP,EAAI,UAAU,IAAK,CAAE,eAAgB,kBAAmB,CAAC,EAAGA,EAAI,IAAI,KAAK,UAAU,CAAE,MAAO,cAAe,CAAC,CAAC,EAAG,MAAO,CAC/H,GAAI,CACF,IAAMS,EAAS1C,EAAUC,CAAO,EAChCgC,EAAI,UAAU,IAAK,CAAE,eAAgB,kBAAmB,CAAC,EACzDA,EAAI,IAAI,KAAK,UAAUS,CAAM,CAAC,CAChC,OAASC,EAAP,CACAV,EAAI,UAAU,IAAK,CAAE,eAAgB,kBAAmB,CAAC,EACzDA,EAAI,IAAI,KAAK,UAAU,CAAE,MAAOU,EAAE,SAAW,eAAgB,CAAC,CAAC,CACjE,CACF,CG5QA,OAAS,cAAAC,EAAY,eAAAC,MAAmB,SACxC,OAAS,cAAAC,EAAY,aAAAC,EAAW,gBAAAC,EAAc,iBAAAC,OAAqB,KACnE,OAAS,WAAAC,GAAS,WAAAC,OAAe,OAmB1B,IAAMC,EAAe,YACtBC,GAAe,sBAcRC,EAAN,KAAiB,CACd,KAER,YAAYC,EAAkB,QAAQ,IAAI,EAAGC,EAAkBH,GAAc,CAC3E,KAAK,KAAOH,GAAQK,EAASC,CAAO,CACtC,CAEQ,MAAsB,CAC5B,GAAI,CAACV,EAAW,KAAK,IAAI,EAAG,MAAO,CAAC,EACpC,GAAI,CAAE,OAAO,KAAK,MAAME,EAAa,KAAK,KAAM,OAAO,CAAC,CAAmB,MAAE,CAAQ,MAAO,CAAC,CAAE,CACjG,CAEQ,KAAKS,EAA8B,CACzCV,EAAUI,GAAQ,KAAK,IAAI,EAAG,CAAE,UAAW,EAAK,CAAC,EACjDF,GAAc,KAAK,KAAM,KAAK,UAAUQ,EAAS,KAAM,CAAC,EAAI;AAAA,EAAM,OAAO,CAC3E,CAQA,OAAOC,EAIoC,CACzC,GAAI,CAACA,EAAM,MAAM,KAAK,EAAG,MAAM,IAAI,MAAM,yBAAyB,EAClE,GAAI,CAAC,MAAM,QAAQA,EAAM,MAAM,GAAKA,EAAM,OAAO,SAAW,EAC1D,MAAM,IAAI,MAAM,iCAAiC,EAEnD,QAAWC,KAAKD,EAAM,OACpB,GAAI,CAACE,GAAaD,CAAC,EAAG,MAAM,IAAI,MAAM,kBAAkBA,GAAG,EAE7D,IAAME,EAAOhB,EAAY,EAAE,EAAE,SAAS,KAAK,EACrCiB,EAAQV,EAAeS,EACvBE,EAAsB,CAC1B,GAAI,OAASlB,EAAY,CAAC,EAAE,SAAS,KAAK,EAC1C,KAAMa,EAAM,KAAK,KAAK,EACtB,OAAQN,EAAeS,EAAK,MAAM,EAAG,CAAC,EACtC,KAAMG,EAAUF,CAAK,EACrB,OAAQ,CAAC,GAAGJ,EAAM,MAAM,EACxB,UAAW,IAAI,KAAK,EAAE,YAAY,EAClC,UAAWA,EAAM,cACb,IAAI,KAAK,KAAK,IAAI,EAAIA,EAAM,cAAgB,MAAQ,GAAI,EAAE,YAAY,EACtE,MACN,EACMD,EAAU,KAAK,KAAK,EAC1B,OAAAA,EAAQ,KAAKM,CAAM,EACnB,KAAK,KAAKN,CAAO,EACV,CAAE,MAAAK,EAAO,OAAAC,CAAO,CACzB,CAEA,MAAsB,CACpB,OAAO,KAAK,KAAK,CACnB,CAEA,OAAOE,EAAgC,CACrC,IAAMR,EAAU,KAAK,KAAK,EACpBS,EAAMT,EAAQ,KAAM,GAAM,EAAE,KAAOQ,CAAE,EAC3C,OAAKC,GACLA,EAAI,UAAY,IAAI,KAAK,EAAE,YAAY,EACvC,KAAK,KAAKT,CAAO,EACVS,GAHU,IAInB,CAMA,OAAOJ,EAAmC,CACxC,GAAI,CAACA,GAAS,CAACA,EAAM,WAAWV,CAAY,EAAG,OAAO,KACtD,IAAMe,EAAOH,EAAUF,CAAK,EACtBL,EAAU,KAAK,KAAK,EACpBS,EAAMT,EAAQ,KAAMW,GAAMA,EAAE,OAASD,CAAI,EAG/C,GAFI,CAACD,GACDA,EAAI,WACJA,EAAI,WAAa,KAAK,MAAMA,EAAI,SAAS,EAAI,KAAK,IAAI,EAAG,OAAO,KAEpE,IAAMG,EAAM,KAAK,IAAI,EACfC,EAAOJ,EAAI,WAAa,KAAK,MAAMA,EAAI,UAAU,EAAI,EAC3D,GAAIG,EAAMC,EAAO,IAAQ,CACvBJ,EAAI,WAAa,IAAI,KAAKG,CAAG,EAAE,YAAY,EAC3C,GAAI,CAAE,KAAK,KAAKZ,CAAO,CAAE,MAAE,CAA0B,EAEvD,OAAOS,CACT,CACF,EAOO,SAASK,GAAeR,EAAqBS,EAA2B,CAG7E,MAFI,GAAAT,EAAO,OAAO,SAASS,CAAQ,GAC/BA,EAAS,WAAW,QAAQ,GAAKT,EAAO,OAAO,SAAS,SAAS,GACjES,IAAa,kBAAoBT,EAAO,OAAO,SAAS,iBAAiB,EAE/E,CAEA,SAASH,GAAaD,EAAoB,CACxC,OACEA,IAAM,kBACNA,IAAM,mBACNA,IAAM,WACN,8BAA8B,KAAKA,CAAC,GACpCA,IAAM,WAEV,CAEO,SAASK,EAAUF,EAAuB,CAC/C,MAAO,UAAYlB,EAAW,QAAQ,EAAE,OAAOkB,CAAK,EAAE,OAAO,KAAK,CACpE,CAOO,SAASW,GAAaC,EAA8F,CACzH,IAAMC,EAAOD,EAAI,QAAQ,cACnBE,EAAa,MAAM,QAAQD,CAAI,EAAIA,EAAK,CAAC,EAAIA,EACnD,GAAIC,GAAcA,EAAW,YAAY,EAAE,WAAW,SAAS,EAC7D,OAAOA,EAAW,MAAM,CAAC,EAAE,KAAK,EAElC,GAAIF,EAAI,IACN,GAAI,CAEF,IAAMG,EADI,IAAI,IAAIH,EAAI,IAAK,kBAAkB,EAC/B,aAAa,IAAI,OAAO,EACtC,GAAIG,EAAK,OAAOA,CAClB,MAAE,CAAc,CAElB,OAAO,IACT","names":["existsSync","readFileSync","writeFileSync","appendFileSync","mkdirSync","resolve","hostname","existsSync","readFileSync","writeFileSync","copyFileSync","resolve","mutateAgentxConfig","mutator","opts","file","resolve","existsSync","cfg","readFileSync","raw","result","json","tmp","writeFileSync","e","backupPath","copyFileSync","triggerLocalReload","writeAgentxConfig","url","loadDaemonConfig","renderSetupPage","state","defaultNode","heading","customHeader","step1","stepCard","field","step2","row","step3","step4","banner","body","esc","btn","renderShell","SETUP_PAGE_CSS","SETUP_SCRIPT","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","defaultNode","hostname","html","renderSetupPage","handleWizardPost","req","raw","ok","result","e","createHash","randomBytes","existsSync","mkdirSync","readFileSync","writeFileSync","resolve","dirname","TOKEN_PREFIX","DEFAULT_FILE","TokenStore","baseDir","fileRel","records","input","s","isValidScope","body","token","record","hashToken","id","rec","hash","r","now","last","recordHasScope","required","extractToken","req","auth","authHeader","tok"]}