agentix-cli 0.18.0 → 0.19.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.
- package/README.md +75 -27
- package/dist/{agent-RDVGL2TW.js → agent-57MG4LBM.js} +2 -2
- package/dist/{audit-Z47AQFFD.js → audit-JU3HWISB.js} +2 -2
- package/dist/{board-dashboard-KX2CNOSY.js → board-dashboard-HXDOYGIG.js} +2237 -69
- package/dist/board-dashboard-HXDOYGIG.js.map +1 -0
- package/dist/chunk-23AUU4S2.js +102 -0
- package/dist/chunk-23AUU4S2.js.map +1 -0
- package/dist/chunk-4MREHZP5.js +111 -0
- package/dist/chunk-4MREHZP5.js.map +1 -0
- package/dist/chunk-4YCH6IZV.js +2 -0
- package/dist/chunk-6B74TO2J.js +743 -0
- package/dist/chunk-6B74TO2J.js.map +1 -0
- package/dist/chunk-7P5QRX5W.js +108 -0
- package/dist/chunk-7P5QRX5W.js.map +1 -0
- package/dist/chunk-A6BKSZQA.js +3 -0
- package/dist/chunk-A6BKSZQA.js.map +1 -0
- package/dist/{chunk-6PPIFDOQ.js → chunk-CNNLREHD.js} +67 -22
- package/dist/{chunk-6PPIFDOQ.js.map → chunk-CNNLREHD.js.map} +1 -1
- package/dist/{chunk-J4WKMKTP.js → chunk-DLVOHCTA.js} +2 -2
- package/dist/chunk-E4ZZILAK.js +15 -0
- package/dist/chunk-E4ZZILAK.js.map +1 -0
- package/dist/{chunk-TRQSP67X.js → chunk-IKBHHTIT.js} +2 -2
- package/dist/chunk-JTJIPNXY.js +2 -0
- package/dist/chunk-JTJIPNXY.js.map +1 -0
- package/dist/chunk-MBOLXKA2.js +6 -0
- package/dist/chunk-MBOLXKA2.js.map +1 -0
- package/dist/{inbox-O5UB3YQN.js → chunk-MTRWXRFF.js} +69 -4
- package/dist/chunk-MTRWXRFF.js.map +1 -0
- package/dist/{processes-7W27G3J3.js → chunk-NOF4HSGK.js} +3 -3
- package/dist/{processes-7W27G3J3.js.map → chunk-NOF4HSGK.js.map} +1 -1
- package/dist/chunk-QW2OYR3N.js +7 -0
- package/dist/chunk-QW2OYR3N.js.map +1 -0
- package/dist/chunk-SV3I4CI7.js +440 -0
- package/dist/chunk-SV3I4CI7.js.map +1 -0
- package/dist/{chunk-JXEPJ3YD.js → chunk-TC3BSUP7.js} +5 -5
- package/dist/chunk-VWTBHLMX.js +1 -0
- package/dist/chunk-WFHIIVLL.js +719 -0
- package/dist/chunk-WFHIIVLL.js.map +1 -0
- package/dist/chunk-WPPUOTY6.js +6 -0
- package/dist/chunk-WPPUOTY6.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/{compaction-S5H6DTOR.js → compaction-BTUJJ3LJ.js} +4 -4
- package/dist/config-QXWKEQON.js +2 -0
- package/dist/config-mutator-53XGR2NT.js +2 -0
- package/dist/conflict-detector-D7VIYPDI.js +2 -0
- package/dist/conflict-detector-D7VIYPDI.js.map +1 -0
- package/dist/{context-planner-2RGHJHSO.js → context-planner-I7ID7XF3.js} +3 -3
- package/dist/debug-N3B5NVJU.js +2 -0
- package/dist/{discover-OATU7SHI.js → discover-XJXQXXIO.js} +2 -2
- package/dist/doctor-SEOUFHSL.js +2 -0
- package/dist/{editor-chat-FUBQLMAQ.js → editor-chat-RVLCKKB7.js} +2 -2
- package/dist/{heal-YP7QI7D3.js → heal-YEQYWX2L.js} +2 -2
- package/dist/inbox-BIN2S5XN.js +2 -0
- package/dist/index.d.ts +830 -100
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/{ingest-whatsapp-C3O5UPBX.js → ingest-whatsapp-LZE62INA.js} +2 -2
- package/dist/loader-5CA3O5GM.js +2 -0
- package/dist/loader-6IHUCPVO.js +2 -0
- package/dist/memory-extract-KRCHD3OP.js +2 -0
- package/dist/migrate-v2-QO7SLKIM.js +4 -0
- package/dist/migrate-v2-QO7SLKIM.js.map +1 -0
- package/dist/processes-RJ6RDP32.js +2 -0
- package/dist/program-SASDC2GV.js +2 -0
- package/dist/{providers-WCQSKTNP.js → providers-WVCMIW67.js} +2 -2
- package/dist/query-HZYYRCLU.js +2 -0
- package/dist/recipes-EPLE2SNT.js +2 -0
- package/dist/registry-HM4ELSXS.js +2 -0
- package/dist/rotation-memo-GMTZPMNE.js +13 -0
- package/dist/rotation-memo-GMTZPMNE.js.map +1 -0
- package/dist/scheduler-XVFIK4XH.js +2 -0
- package/dist/{slack-D27BE37Q.js → slack-6P7ZJ32Y.js} +2 -2
- package/dist/store-BAPDZHXE.js +2 -0
- package/dist/{subagent-EPOT3QDM.js → subagent-WV7QKQ3L.js} +2 -2
- package/dist/{task-store-3NUWLYRY.js → task-store-RHISEU6V.js} +2 -2
- package/dist/{telegram-UWV67KWG.js → telegram-PIYDRM46.js} +2 -2
- package/dist/timers-5LPZN55J.js +2 -0
- package/dist/types-VECTMYTO.js +2 -0
- package/dist/types-VECTMYTO.js.map +1 -0
- package/dist/types-ZRLHHCGB.js +2 -0
- package/dist/types-ZRLHHCGB.js.map +1 -0
- package/dist/web/activity-graph.global.js +654 -0
- package/dist/web/activity-graph.global.js.map +1 -0
- package/dist/web/workflow-editor.global.js +1 -1
- package/dist/web/workflow-editor.global.js.map +1 -1
- package/dist/{whatsapp-KJ2NRKKQ.js → whatsapp-PFRDMUMT.js} +2 -2
- package/dist/{whatsapp-state-3ZVQ6MQD.js → whatsapp-state-XMTCSRXU.js} +2 -2
- package/dist/wiki-53W5NLZV.js +2 -0
- package/dist/wiki-53W5NLZV.js.map +1 -0
- package/dist/workspace-setup-FPGN5VNY.js +2 -0
- package/dist/workspace-setup-FPGN5VNY.js.map +1 -0
- package/package.json +3 -2
- package/dist/board-dashboard-KX2CNOSY.js.map +0 -1
- package/dist/chunk-34NG7UDX.js +0 -11
- package/dist/chunk-34NG7UDX.js.map +0 -1
- package/dist/chunk-ASSJ4Z6Z.js +0 -164
- package/dist/chunk-ASSJ4Z6Z.js.map +0 -1
- package/dist/chunk-BLUXL6HR.js +0 -854
- package/dist/chunk-BLUXL6HR.js.map +0 -1
- package/dist/chunk-BTAEYL3P.js +0 -72
- package/dist/chunk-BTAEYL3P.js.map +0 -1
- package/dist/chunk-DSCMCVTV.js +0 -7
- package/dist/chunk-DSCMCVTV.js.map +0 -1
- package/dist/chunk-HPDHNIA3.js +0 -2
- package/dist/chunk-IM376SD6.js +0 -13
- package/dist/chunk-IM376SD6.js.map +0 -1
- package/dist/chunk-LEIYC3BF.js +0 -104
- package/dist/chunk-LEIYC3BF.js.map +0 -1
- package/dist/chunk-LY7JGDV7.js +0 -6
- package/dist/chunk-LY7JGDV7.js.map +0 -1
- package/dist/chunk-U364GNQX.js +0 -719
- package/dist/chunk-U364GNQX.js.map +0 -1
- package/dist/chunk-WZEYII2S.js +0 -6
- package/dist/chunk-WZEYII2S.js.map +0 -1
- package/dist/config-UOHEG4BW.js +0 -2
- package/dist/config-mutator-KSEBWKJH.js +0 -2
- package/dist/debug-TGAP7E7W.js +0 -2
- package/dist/inbox-O5UB3YQN.js.map +0 -1
- package/dist/loader-BGWA7IRY.js +0 -2
- package/dist/loader-LBHKSXBK.js +0 -2
- package/dist/memory-extract-KKPKAOPH.js +0 -2
- package/dist/program-2MDELQLX.js +0 -2
- package/dist/query-FZAL7AQD.js +0 -2
- package/dist/recipes-LZ56TA3X.js +0 -2
- package/dist/registry-ONJSILH4.js +0 -2
- package/dist/scheduler-XRM2RJIV.js +0 -2
- package/dist/store-4UCO56K2.js +0 -2
- package/dist/timers-QB4KYNAE.js +0 -2
- package/dist/types-ECMEC5BZ.js +0 -2
- package/dist/types-TTB6DJUE.js +0 -2
- package/dist/usage-dashboard-FQGPPABD.js +0 -803
- package/dist/usage-dashboard-FQGPPABD.js.map +0 -1
- package/dist/wiki-OUYKM7MB.js +0 -2
- package/dist/workspace-setup-6GDBEORT.js +0 -2
- /package/dist/{agent-RDVGL2TW.js.map → agent-57MG4LBM.js.map} +0 -0
- /package/dist/{audit-Z47AQFFD.js.map → audit-JU3HWISB.js.map} +0 -0
- /package/dist/{chunk-HPDHNIA3.js.map → chunk-4YCH6IZV.js.map} +0 -0
- /package/dist/{chunk-J4WKMKTP.js.map → chunk-DLVOHCTA.js.map} +0 -0
- /package/dist/{chunk-TRQSP67X.js.map → chunk-IKBHHTIT.js.map} +0 -0
- /package/dist/{chunk-JXEPJ3YD.js.map → chunk-TC3BSUP7.js.map} +0 -0
- /package/dist/{config-UOHEG4BW.js.map → chunk-VWTBHLMX.js.map} +0 -0
- /package/dist/{compaction-S5H6DTOR.js.map → compaction-BTUJJ3LJ.js.map} +0 -0
- /package/dist/{config-mutator-KSEBWKJH.js.map → config-QXWKEQON.js.map} +0 -0
- /package/dist/{debug-TGAP7E7W.js.map → config-mutator-53XGR2NT.js.map} +0 -0
- /package/dist/{context-planner-2RGHJHSO.js.map → context-planner-I7ID7XF3.js.map} +0 -0
- /package/dist/{heal-YP7QI7D3.js.map → debug-N3B5NVJU.js.map} +0 -0
- /package/dist/{discover-OATU7SHI.js.map → discover-XJXQXXIO.js.map} +0 -0
- /package/dist/{loader-BGWA7IRY.js.map → doctor-SEOUFHSL.js.map} +0 -0
- /package/dist/{editor-chat-FUBQLMAQ.js.map → editor-chat-RVLCKKB7.js.map} +0 -0
- /package/dist/{loader-LBHKSXBK.js.map → heal-YEQYWX2L.js.map} +0 -0
- /package/dist/{memory-extract-KKPKAOPH.js.map → inbox-BIN2S5XN.js.map} +0 -0
- /package/dist/{ingest-whatsapp-C3O5UPBX.js.map → ingest-whatsapp-LZE62INA.js.map} +0 -0
- /package/dist/{program-2MDELQLX.js.map → loader-5CA3O5GM.js.map} +0 -0
- /package/dist/{providers-WCQSKTNP.js.map → loader-6IHUCPVO.js.map} +0 -0
- /package/dist/{query-FZAL7AQD.js.map → memory-extract-KRCHD3OP.js.map} +0 -0
- /package/dist/{recipes-LZ56TA3X.js.map → processes-RJ6RDP32.js.map} +0 -0
- /package/dist/{registry-ONJSILH4.js.map → program-SASDC2GV.js.map} +0 -0
- /package/dist/{scheduler-XRM2RJIV.js.map → providers-WVCMIW67.js.map} +0 -0
- /package/dist/{store-4UCO56K2.js.map → query-HZYYRCLU.js.map} +0 -0
- /package/dist/{task-store-3NUWLYRY.js.map → recipes-EPLE2SNT.js.map} +0 -0
- /package/dist/{timers-QB4KYNAE.js.map → registry-HM4ELSXS.js.map} +0 -0
- /package/dist/{types-ECMEC5BZ.js.map → scheduler-XVFIK4XH.js.map} +0 -0
- /package/dist/{slack-D27BE37Q.js.map → slack-6P7ZJ32Y.js.map} +0 -0
- /package/dist/{types-TTB6DJUE.js.map → store-BAPDZHXE.js.map} +0 -0
- /package/dist/{subagent-EPOT3QDM.js.map → subagent-WV7QKQ3L.js.map} +0 -0
- /package/dist/{wiki-OUYKM7MB.js.map → task-store-RHISEU6V.js.map} +0 -0
- /package/dist/{telegram-UWV67KWG.js.map → telegram-PIYDRM46.js.map} +0 -0
- /package/dist/{workspace-setup-6GDBEORT.js.map → timers-5LPZN55J.js.map} +0 -0
- /package/dist/{whatsapp-KJ2NRKKQ.js.map → whatsapp-PFRDMUMT.js.map} +0 -0
- /package/dist/{whatsapp-state-3ZVQ6MQD.js.map → whatsapp-state-XMTCSRXU.js.map} +0 -0
package/dist/chunk-34NG7UDX.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import{readFileSync as D,writeFileSync as v,appendFileSync as K,existsSync as O,mkdirSync as j}from"fs";import{resolve as S}from"path";var w={"claude-opus-4-6":{input:5,output:25,cacheRead:.5,cacheCreate:6.25},"claude-opus-4-5":{input:5,output:25,cacheRead:.5,cacheCreate:6.25},"claude-opus":{input:15,output:75,cacheRead:1.5,cacheCreate:18.75},"claude-sonnet":{input:3,output:15,cacheRead:.3,cacheCreate:3.75},"claude-haiku":{input:1,output:5,cacheRead:.1,cacheCreate:1.25}},H=2e5,F=1.5;function U(R){let e=R.toLowerCase();return e.includes("opus")?e.includes("4-6")||e.includes("4.6")||e.includes("opus-4-6")?"claude-opus-4-6":e.includes("4-5")||e.includes("4.5")||e.includes("opus-4-5")?"claude-opus-4-5":"claude-opus":e.includes("haiku")?"claude-haiku":"claude-sonnet"}var $=500,W=".agentx/pricing/custom.json",_=class{dir;cache=null;pricing;constructor(e=S(process.cwd(),".agentx/usage")){this.dir=e,j(this.dir,{recursive:!0}),this.pricing=G()}getPricing(){return this.pricing}static calculateCost(e,o,s=w){let c=U(o||e.model||"claude-sonnet"),a=s[c]||s["claude-sonnet"],i=e.tier2InputTokens||0,k=e.tier2OutputTokens||0,h=e.tier2CacheReadTokens||0,n=e.tier2CacheCreateTokens||0,l=(e.inputTokens||0)/1e6*a.input+(e.outputTokens||0)/1e6*a.output+(e.cacheReadTokens||0)/1e6*a.cacheRead+(e.cacheCreateTokens||0)/1e6*a.cacheCreate,t=i/1e6*a.input+k/1e6*a.output+h/1e6*a.cacheRead+n/1e6*a.cacheCreate;return l+t*F}cost(e,o){return _.calculateCost(e,o,this.pricing)}record(e,o,s,c,a,i,k,h,n){let l=this.today(),t=l.agents[e]||{tasks:0,inputTokens:0,outputTokens:0,cacheReadTokens:0,cacheCreateTokens:0,tier2InputTokens:0,tier2OutputTokens:0,tier2CacheReadTokens:0,tier2CacheCreateTokens:0,totalDuration:0,errors:0,byChannel:{},sessionKeys:[]};t.tasks++,t.totalDuration+=o,k&&(t.model=k);let g=0,b=0,T=0,C=0,u=0,m=0,r=0,f=0;s?s.inputTokens+s.cacheReadTokens+s.cacheCreateTokens>H?(u=s.inputTokens,m=s.outputTokens,r=s.cacheReadTokens,f=s.cacheCreateTokens):(g=s.inputTokens,b=s.outputTokens,T=s.cacheReadTokens,C=s.cacheCreateTokens):c!==void 0&&a!==void 0&&(g=Math.ceil(c/4),b=Math.ceil(a/4)),t.inputTokens+=g,t.outputTokens+=b,t.cacheReadTokens+=T,t.cacheCreateTokens+=C,t.tier2InputTokens=(t.tier2InputTokens||0)+u,t.tier2OutputTokens=(t.tier2OutputTokens||0)+m,t.tier2CacheReadTokens=(t.tier2CacheReadTokens||0)+r,t.tier2CacheCreateTokens=(t.tier2CacheCreateTokens||0)+f,i&&t.errors++,t.byChannel||(t.byChannel={});let y=(h||"other").toLowerCase(),p=t.byChannel[y]||{tasks:0,inputTokens:0,outputTokens:0,cacheReadTokens:0,cacheCreateTokens:0,tier2InputTokens:0,tier2OutputTokens:0,tier2CacheReadTokens:0,tier2CacheCreateTokens:0,totalDuration:0,errors:0};p.tasks++,p.totalDuration+=o,p.inputTokens+=g,p.outputTokens+=b,p.cacheReadTokens+=T,p.cacheCreateTokens+=C,p.tier2InputTokens+=u,p.tier2OutputTokens+=m,p.tier2CacheReadTokens+=r,p.tier2CacheCreateTokens+=f,i&&p.errors++,t.byChannel[y]=p,n&&(t.sessionKeys||(t.sessionKeys=[]),t.sessionKeys.includes(n)||(t.sessionKeys.push(n),t.sessionKeys.length>$&&t.sessionKeys.splice(0,t.sessionKeys.length-$))),l.agents[e]=t,this.save(l)}today(){let e=new Date().toISOString().slice(0,10);if(this.cache?.date===e)return this.cache;let o=this.filePath(e);if(O(o))try{return this.cache=JSON.parse(D(o,"utf-8")),this.cache}catch{}return this.cache={date:e,agents:{}},this.cache}getDate(e){let o=this.filePath(e);if(!O(o))return null;try{return JSON.parse(D(o,"utf-8"))}catch{return null}}generateDailyReport(e){let o=e||new Date().toISOString().slice(0,10),s=this.getDate(o);if(!s)return null;let c=0,a=0,i=0,k=0,h=0,n=0,l="",t=0,g={},b={},T={};for(let[C,u]of Object.entries(s.agents)){let m=this.cost(u);g[C]=m,c+=u.tasks,a+=(u.inputTokens||0)+(u.tier2InputTokens||0),i+=(u.outputTokens||0)+(u.tier2OutputTokens||0),k+=(u.cacheReadTokens||0)+(u.tier2CacheReadTokens||0),h+=(u.cacheCreateTokens||0)+(u.tier2CacheCreateTokens||0),n+=m,m>t&&(t=m,l=C);let r=u.sessionKeys?.length||0;b[C]={sessions:r,avgTasksPerSession:r>0?u.tasks/r:u.tasks};for(let[f,y]of Object.entries(u.byChannel||{})){let p=this.cost(y,u.model);T[f]=(T[f]||0)+p}}return{date:o,totalTasks:c,totalInput:a,totalOutput:i,totalCacheRead:k,totalCacheCreate:h,totalCost:n,topAgent:l,topCost:t,agentCosts:g,agentSessionStats:b,byChannel:T}}appendToTokenCosts(e,o){let s=o||S(process.cwd(),".agentx/TOKEN_COSTS.md"),c=n=>n<.01?n.toFixed(4):n.toFixed(2),a=n=>n>=1e6?`${(n/1e6).toFixed(1)}M`:n>=1e3?`${(n/1e3).toFixed(0)}K`:String(n),i=Object.entries(e.agentCosts).sort(([,n],[,l])=>l-n).map(([n,l])=>{let t=e.agentSessionStats[n],g=t&&t.sessions>0?` (${t.sessions}s, ${t.avgTasksPerSession.toFixed(1)}t/s)`:"";return`${n}: $${c(l)}${g}`}).join(", "),k=Object.entries(e.byChannel).sort(([,n],[,l])=>l-n).map(([n,l])=>`${n}: $${c(l)}`).join(", "),h=`| ${e.date} | ${e.totalTasks} | ${a(e.totalInput)} | ${a(e.totalOutput)} | ${a(e.totalCacheRead)} | ${a(e.totalCacheCreate)} | $${c(e.totalCost)} | ${i} | ${k} |`;O(s)?K(s,h+`
|
|
2
|
-
`):v(s,`# Token Costs
|
|
3
|
-
|
|
4
|
-
Daily token cost tracking. Auto-appended at midnight (Africa/Tunis).
|
|
5
|
-
Agent breakdown: \`agent: $cost (Ns, T.Tt/s)\` \u2014 sessions, tasks/session.
|
|
6
|
-
|
|
7
|
-
| Date | Tasks | Input | Output | Cache R | Cache W | Cost | Per Agent | Per Channel |
|
|
8
|
-
|------|-------|-------|--------|---------|---------|------|-----------|-------------|
|
|
9
|
-
`+h+`
|
|
10
|
-
`)}hasTokenCostsEntry(e,o){let s=o||S(process.cwd(),".agentx/TOKEN_COSTS.md");if(!O(s))return!1;try{let c=D(s,"utf-8");return new RegExp(`^\\|\\s*${e}\\s*\\|`,"m").test(c)}catch{return!1}}lastTokenCostsDate(e){let o=e||S(process.cwd(),".agentx/TOKEN_COSTS.md");if(!O(o))return null;try{let c=D(o,"utf-8").match(/^\|\s*(\d{4}-\d{2}-\d{2})\s*\|/gm)??[];if(c.length===0)return null;let a=c.map(i=>i.match(/(\d{4}-\d{2}-\d{2})/)?.[1]).filter(i=>!!i);return a.sort(),a[a.length-1]??null}catch{return null}}catchUpTokenCosts(e,o={}){let s=o.maxDays??30,c=[],a=this.lastTokenCostsDate(o.filePath),i=[],k=new Date(`${e}T00:00:00Z`);for(let h=0;h<s;h++){let n=k.toISOString().slice(0,10);if(a&&n<=a)break;i.push(n),k.setUTCDate(k.getUTCDate()-1)}i.reverse();for(let h of i){if(this.hasTokenCostsEntry(h,o.filePath))continue;let n=this.generateDailyReport(h);!n||n.totalTasks===0||(this.appendToTokenCosts(n,o.filePath),c.push(h))}return c}summary(e=7){let o=0,s=0,c=0,a=0,i=0,k=0,h=0,n={},l={};for(let T=0;T<e;T++){let C=new Date(Date.now()-T*864e5).toISOString().slice(0,10),u=this.getDate(C);if(u)for(let[m,r]of Object.entries(u.agents)){let f=this.cost(r);o+=r.tasks;let y=(r.inputTokens||0)+(r.tier2InputTokens||0),p=(r.outputTokens||0)+(r.tier2OutputTokens||0),I=(r.cacheReadTokens||0)+(r.tier2CacheReadTokens||0),E=(r.cacheCreateTokens||0)+(r.tier2CacheCreateTokens||0);s+=y,c+=p,a+=I,i+=E,k+=r.errors,h+=f;let d=n[m]||{tasks:0,input:0,output:0,cacheRead:0,cacheCreate:0,total:0,avgDuration:0,totalDuration:0,cost:0,sessions:0};d.tasks+=r.tasks,d.input+=y,d.output+=p,d.cacheRead+=I,d.cacheCreate+=E,d.total=d.input+d.output+d.cacheRead+d.cacheCreate,d.totalDuration+=r.totalDuration,d.avgDuration=d.totalDuration/d.tasks,d.cost+=f,d.sessions+=r.sessionKeys?.length||0,r.model&&(d.model=r.model),n[m]=d;for(let[P,x]of Object.entries(r.byChannel||{})){let N=this.cost(x,r.model),A=l[P]||{tasks:0,cost:0};A.tasks+=x.tasks,A.cost+=N,l[P]=A}}}let t=s+c+a+i,g=a+i>0?a/(a+i):0,b={};for(let[T,C]of Object.entries(n))b[T]={sessions:C.sessions,avgTasksPerSession:C.sessions>0?C.tasks/C.sessions:C.tasks};return{totalTasks:o,totalTokens:t,totalInput:s,totalOutput:c,totalCacheRead:a,totalCacheCreate:i,cacheHitRatio:g,totalErrors:k,totalCost:h,byAgent:n,byChannel:l,sessionStats:b}}filePath(e){return S(this.dir,`${e}.json`)}save(e){try{v(this.filePath(e.date),JSON.stringify(e,null,2))}catch{}}};function G(){let R=JSON.parse(JSON.stringify(w));try{let e=S(process.cwd(),W);if(!O(e))return R;let o=JSON.parse(D(e,"utf-8"));for(let[s,c]of Object.entries(o))R[s]||(R[s]={input:0,output:0,cacheRead:0,cacheCreate:0}),Object.assign(R[s],c)}catch{}return R}export{_ as a};
|
|
11
|
-
//# sourceMappingURL=chunk-34NG7UDX.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/daemon/token-tracker.ts"],"sourcesContent":["import { readFileSync, writeFileSync, appendFileSync, existsSync, mkdirSync } from \"fs\"\nimport { resolve } from \"path\"\n\n// --- Token usage tracker ---\n// Tracks token usage per agent per day with cache-aware, tier-aware cost\n// calculation. Persisted to .agentx/usage/{YYYY-MM-DD}.json.\n//\n// Design notes:\n// - Pricing is per-model-FAMILY (opus/sonnet/haiku). Exact model name\n// variants map via getModelFamily(). Override the table by dropping\n// .agentx/pricing/custom.json (same shape) — merged over defaults at\n// construction, lets operators adjust without a rebuild.\n// - Anthropic bills 1.5× input/output when a single request's total input\n// (input + cacheCreate + cacheRead) exceeds 200K tokens. We split each\n// recorded task into tier1/tier2 buckets and bill them separately so\n// long-context sessions are not undercounted.\n// - Per-channel breakdowns let operators see \"GitLab drove 70% of today's\n// spend\" — the lever for channel-specific session + context tuning.\n// - Session tracking (unique chatId:channel pairs seen today per agent)\n// gives an avg-tasks-per-session proxy — when it spikes, it's usually\n// retry/clarification chains, a signal the context budget or model\n// capability is under-sized for that traffic.\n\n// Anthropic pricing per million tokens (as of April 2026, 5-min ephemeral).\n// Keys here must match getModelFamily() output.\n//\n// IMPORTANT: Opus 4.6 is 3× cheaper than Opus 4.1. Haiku 4.5 is more expensive\n// than Haiku 3.5. Our getModelFamily() resolves the exact version so the cost\n// report matches what Anthropic actually bills. Source: LiteLLM pricing cache\n// at ~/.cache/codeburn/litellm-pricing.json + codeburn/src/models.ts.\n//\n// Cache multipliers (consistent across all models):\n// cache write (5-min ephemeral) = 1.25 × input\n// cache read = 0.10 × input\nexport const CACHE_AWARE_PRICING: Record<string, { input: number; output: number; cacheRead: number; cacheCreate: number }> = {\n // Opus 4.6 / 4.5 — $5/$25 per MTok\n \"claude-opus-4-6\": { input: 5, output: 25, cacheRead: 0.5, cacheCreate: 6.25 },\n \"claude-opus-4-5\": { input: 5, output: 25, cacheRead: 0.5, cacheCreate: 6.25 },\n // Opus 4.1 and earlier — $15/$75 per MTok (legacy, keep for historical runs)\n \"claude-opus\": { input: 15, output: 75, cacheRead: 1.5, cacheCreate: 18.75 },\n // Sonnet 4.6 / 4.5 — $3/$15 per MTok\n \"claude-sonnet\": { input: 3, output: 15, cacheRead: 0.3, cacheCreate: 3.75 },\n // Haiku 4.5 — $1/$5 per MTok (more expensive than Haiku 3.5)\n \"claude-haiku\": { input: 1, output: 5, cacheRead: 0.1, cacheCreate: 1.25 },\n}\n\n/** Per-request input size (input + cacheCreate + cacheRead) above this\n * threshold triggers Anthropic's extended-context tier: all four rates\n * get multiplied. Applies to Opus 4.x and Sonnet 4.x 1M variants. */\nexport const CONTEXT_TIER_THRESHOLD = 200_000\nexport const TIER2_MULTIPLIER = 1.5\n\nexport function getModelFamily(model: string): string {\n const lower = model.toLowerCase()\n if (lower.includes(\"opus\")) {\n // Opus 4.6 and 4.5 are 3× cheaper than 4.1 — must distinguish.\n if (lower.includes(\"4-6\") || lower.includes(\"4.6\") || lower.includes(\"opus-4-6\")) return \"claude-opus-4-6\"\n if (lower.includes(\"4-5\") || lower.includes(\"4.5\") || lower.includes(\"opus-4-5\")) return \"claude-opus-4-5\"\n return \"claude-opus\" // legacy 4.1 and earlier\n }\n if (lower.includes(\"haiku\")) return \"claude-haiku\"\n return \"claude-sonnet\"\n}\n\nexport interface ChannelUsage {\n tasks: number\n inputTokens: number\n outputTokens: number\n cacheReadTokens: number\n cacheCreateTokens: number\n tier2InputTokens: number\n tier2OutputTokens: number\n tier2CacheReadTokens: number\n tier2CacheCreateTokens: number\n totalDuration: number\n errors: number\n}\n\nexport interface AgentUsage {\n tasks: number\n inputTokens: number\n outputTokens: number\n cacheReadTokens: number\n cacheCreateTokens: number\n /** Tokens from requests whose input crossed CONTEXT_TIER_THRESHOLD (billed at\n * TIER2_MULTIPLIER × normal rate). Kept separate from the tier1 buckets so\n * cost math stays honest when a session accumulates past 200K. */\n tier2InputTokens?: number\n tier2OutputTokens?: number\n tier2CacheReadTokens?: number\n tier2CacheCreateTokens?: number\n totalDuration: number\n errors: number\n model?: string\n /** Per-channel breakdown so we can attribute cost to Telegram vs GitLab vs WhatsApp. */\n byChannel?: Record<string, ChannelUsage>\n /** Unique session identifiers (channel:chatId) this agent participated in\n * today. Used to derive avg tasks/session. Capped at 500 entries to keep\n * the daily JSON small. */\n sessionKeys?: string[]\n}\n\nexport interface DailyUsage {\n date: string\n agents: Record<string, AgentUsage>\n}\n\nexport interface DailyReport {\n date: string\n totalTasks: number\n totalInput: number\n totalOutput: number\n totalCacheRead: number\n totalCacheCreate: number\n totalCost: number\n topAgent: string\n topCost: number\n agentCosts: Record<string, number>\n /** Per-agent avg tasks/session — high values suggest retry chains. */\n agentSessionStats: Record<string, { sessions: number; avgTasksPerSession: number }>\n /** Aggregate cost by channel across all agents. */\n byChannel: Record<string, number>\n}\n\n/** Shape of a per-request usage report coming from the runtime. Keeps the\n * record() signature tidy and lets callers opt into tier2 accounting. */\nexport interface TaskUsage {\n inputTokens: number\n outputTokens: number\n cacheReadTokens: number\n cacheCreateTokens: number\n}\n\nconst SESSION_KEYS_MAX = 500\nconst PRICING_OVERRIDE_PATH = \".agentx/pricing/custom.json\"\n\nexport class TokenTracker {\n private dir: string\n private cache: DailyUsage | null = null\n private pricing: typeof CACHE_AWARE_PRICING\n\n constructor(baseDir: string = resolve(process.cwd(), \".agentx/usage\")) {\n this.dir = baseDir\n mkdirSync(this.dir, { recursive: true })\n this.pricing = loadPricingWithOverrides()\n }\n\n /** Returns the active pricing table (hardcoded defaults with any per-model\n * overrides merged in). Useful for UI / debugging. */\n getPricing(): typeof CACHE_AWARE_PRICING {\n return this.pricing\n }\n\n /**\n * Calculate cost for a slice of usage. Accepts either a full AgentUsage or\n * a narrowed ChannelUsage — both expose the same token buckets we price on.\n * Tier2 tokens are billed at TIER2_MULTIPLIER × the normal rate.\n */\n static calculateCost(usage: AgentUsage | ChannelUsage, model?: string, pricingTable: typeof CACHE_AWARE_PRICING = CACHE_AWARE_PRICING): number {\n const family = getModelFamily(model || (usage as AgentUsage).model || \"claude-sonnet\")\n const p = pricingTable[family] || pricingTable[\"claude-sonnet\"]\n const tier2In = (usage as any).tier2InputTokens || 0\n const tier2Out = (usage as any).tier2OutputTokens || 0\n const tier2CR = (usage as any).tier2CacheReadTokens || 0\n const tier2CW = (usage as any).tier2CacheCreateTokens || 0\n\n const tier1 =\n ((usage.inputTokens || 0) / 1_000_000) * p.input +\n ((usage.outputTokens || 0) / 1_000_000) * p.output +\n ((usage.cacheReadTokens || 0) / 1_000_000) * p.cacheRead +\n ((usage.cacheCreateTokens || 0) / 1_000_000) * p.cacheCreate\n\n const tier2 =\n (tier2In / 1_000_000) * p.input +\n (tier2Out / 1_000_000) * p.output +\n (tier2CR / 1_000_000) * p.cacheRead +\n (tier2CW / 1_000_000) * p.cacheCreate\n\n return tier1 + tier2 * TIER2_MULTIPLIER\n }\n\n /**\n * Instance-level cost calculation — uses the tracker's resolved pricing\n * table so any custom override file is honored. Prefer this over the\n * static variant when available.\n */\n cost(usage: AgentUsage | ChannelUsage, model?: string): number {\n return TokenTracker.calculateCost(usage, model, this.pricing)\n }\n\n /**\n * Record a task execution with real or estimated token counts.\n *\n * `channel` attributes the cost to a surface (telegram/gitlab/whatsapp/cron).\n * `sessionKey` is an opaque \"this chat on this day\" identifier — we use it\n * to count unique sessions so an avg-tasks-per-session metric falls out.\n */\n record(\n agentId: string,\n duration: number,\n realUsage?: TaskUsage,\n messageLength?: number,\n responseLength?: number,\n error?: boolean,\n model?: string,\n channel?: string,\n sessionKey?: string,\n ): void {\n const daily = this.today()\n const agent = daily.agents[agentId] || {\n tasks: 0, inputTokens: 0, outputTokens: 0,\n cacheReadTokens: 0, cacheCreateTokens: 0,\n tier2InputTokens: 0, tier2OutputTokens: 0,\n tier2CacheReadTokens: 0, tier2CacheCreateTokens: 0,\n totalDuration: 0, errors: 0, byChannel: {}, sessionKeys: [],\n }\n\n agent.tasks++\n agent.totalDuration += duration\n if (model) agent.model = model\n\n // Split this request into tier1/tier2 based on its TOTAL input size\n // (input + cacheCreate + cacheRead). Output inherits the tier of its\n // request. Estimated paths (no realUsage) are always tier1 — they're\n // too small to reach the threshold.\n let i1 = 0, o1 = 0, cr1 = 0, cw1 = 0, i2 = 0, o2 = 0, cr2 = 0, cw2 = 0\n if (realUsage) {\n const totalInput = realUsage.inputTokens + realUsage.cacheReadTokens + realUsage.cacheCreateTokens\n if (totalInput > CONTEXT_TIER_THRESHOLD) {\n i2 = realUsage.inputTokens\n o2 = realUsage.outputTokens\n cr2 = realUsage.cacheReadTokens\n cw2 = realUsage.cacheCreateTokens\n } else {\n i1 = realUsage.inputTokens\n o1 = realUsage.outputTokens\n cr1 = realUsage.cacheReadTokens\n cw1 = realUsage.cacheCreateTokens\n }\n } else if (messageLength !== undefined && responseLength !== undefined) {\n i1 = Math.ceil(messageLength / 4)\n o1 = Math.ceil(responseLength / 4)\n }\n\n agent.inputTokens += i1\n agent.outputTokens += o1\n agent.cacheReadTokens += cr1\n agent.cacheCreateTokens += cw1\n agent.tier2InputTokens = (agent.tier2InputTokens || 0) + i2\n agent.tier2OutputTokens = (agent.tier2OutputTokens || 0) + o2\n agent.tier2CacheReadTokens = (agent.tier2CacheReadTokens || 0) + cr2\n agent.tier2CacheCreateTokens = (agent.tier2CacheCreateTokens || 0) + cw2\n\n if (error) agent.errors++\n\n // Per-channel attribution. Unknown channel falls into \"other\".\n if (!agent.byChannel) agent.byChannel = {}\n const chKey = (channel || \"other\").toLowerCase()\n const ch = agent.byChannel[chKey] || {\n tasks: 0, inputTokens: 0, outputTokens: 0,\n cacheReadTokens: 0, cacheCreateTokens: 0,\n tier2InputTokens: 0, tier2OutputTokens: 0,\n tier2CacheReadTokens: 0, tier2CacheCreateTokens: 0,\n totalDuration: 0, errors: 0,\n }\n ch.tasks++\n ch.totalDuration += duration\n ch.inputTokens += i1; ch.outputTokens += o1\n ch.cacheReadTokens += cr1; ch.cacheCreateTokens += cw1\n ch.tier2InputTokens += i2; ch.tier2OutputTokens += o2\n ch.tier2CacheReadTokens += cr2; ch.tier2CacheCreateTokens += cw2\n if (error) ch.errors++\n agent.byChannel[chKey] = ch\n\n // Session tracking — dedup sessions by opaque key (normally channel:chatId).\n if (sessionKey) {\n if (!agent.sessionKeys) agent.sessionKeys = []\n if (!agent.sessionKeys.includes(sessionKey)) {\n agent.sessionKeys.push(sessionKey)\n if (agent.sessionKeys.length > SESSION_KEYS_MAX) {\n agent.sessionKeys.splice(0, agent.sessionKeys.length - SESSION_KEYS_MAX)\n }\n }\n }\n\n daily.agents[agentId] = agent\n this.save(daily)\n }\n\n /**\n * Get today's usage summary.\n */\n today(): DailyUsage {\n const date = new Date().toISOString().slice(0, 10)\n\n if (this.cache?.date === date) return this.cache\n\n const path = this.filePath(date)\n if (existsSync(path)) {\n try {\n this.cache = JSON.parse(readFileSync(path, \"utf-8\"))\n return this.cache!\n } catch {}\n }\n\n this.cache = { date, agents: {} }\n return this.cache\n }\n\n /**\n * Get usage for a specific date.\n */\n getDate(date: string): DailyUsage | null {\n const path = this.filePath(date)\n if (!existsSync(path)) return null\n try {\n return JSON.parse(readFileSync(path, \"utf-8\"))\n } catch {\n return null\n }\n }\n\n /**\n * Generate a daily cost report for a given date. Includes per-agent,\n * per-channel costs and session statistics.\n */\n generateDailyReport(date?: string): DailyReport | null {\n const targetDate = date || new Date().toISOString().slice(0, 10)\n const usage = this.getDate(targetDate)\n if (!usage) return null\n\n let totalTasks = 0, totalInput = 0, totalOutput = 0\n let totalCacheRead = 0, totalCacheCreate = 0, totalCost = 0\n let topAgent = \"\", topCost = 0\n const agentCosts: Record<string, number> = {}\n const agentSessionStats: Record<string, { sessions: number; avgTasksPerSession: number }> = {}\n const byChannel: Record<string, number> = {}\n\n for (const [id, agent] of Object.entries(usage.agents)) {\n const cost = this.cost(agent)\n agentCosts[id] = cost\n totalTasks += agent.tasks\n totalInput += (agent.inputTokens || 0) + (agent.tier2InputTokens || 0)\n totalOutput += (agent.outputTokens || 0) + (agent.tier2OutputTokens || 0)\n totalCacheRead += (agent.cacheReadTokens || 0) + (agent.tier2CacheReadTokens || 0)\n totalCacheCreate += (agent.cacheCreateTokens || 0) + (agent.tier2CacheCreateTokens || 0)\n totalCost += cost\n if (cost > topCost) { topCost = cost; topAgent = id }\n\n const sessions = agent.sessionKeys?.length || 0\n agentSessionStats[id] = {\n sessions,\n avgTasksPerSession: sessions > 0 ? agent.tasks / sessions : agent.tasks,\n }\n\n for (const [chKey, ch] of Object.entries(agent.byChannel || {})) {\n const chCost = this.cost(ch, agent.model)\n byChannel[chKey] = (byChannel[chKey] || 0) + chCost\n }\n }\n\n return {\n date: targetDate, totalTasks, totalInput, totalOutput,\n totalCacheRead, totalCacheCreate, totalCost, topAgent, topCost, agentCosts,\n agentSessionStats, byChannel,\n }\n }\n\n /**\n * Append a daily report row to TOKEN_COSTS.md. Adds channel + session\n * breakdown inline for at-a-glance spend attribution.\n */\n appendToTokenCosts(report: DailyReport, filePath?: string): void {\n const file = filePath || resolve(process.cwd(), \".agentx/TOKEN_COSTS.md\")\n const fmt = (n: number) => n < 0.01 ? n.toFixed(4) : n.toFixed(2)\n const fmtTok = (n: number) =>\n n >= 1_000_000 ? `${(n / 1_000_000).toFixed(1)}M` :\n n >= 1_000 ? `${(n / 1_000).toFixed(0)}K` :\n String(n)\n\n const agentBreakdown = Object.entries(report.agentCosts)\n .sort(([, a], [, b]) => b - a)\n .map(([id, cost]) => {\n const stats = report.agentSessionStats[id]\n const sessTag = stats && stats.sessions > 0 ? ` (${stats.sessions}s, ${stats.avgTasksPerSession.toFixed(1)}t/s)` : \"\"\n return `${id}: $${fmt(cost)}${sessTag}`\n })\n .join(\", \")\n\n const channelBreakdown = Object.entries(report.byChannel)\n .sort(([, a], [, b]) => b - a)\n .map(([ch, cost]) => `${ch}: $${fmt(cost)}`)\n .join(\", \")\n\n const row = `| ${report.date} | ${report.totalTasks} | ${fmtTok(report.totalInput)} | ${fmtTok(report.totalOutput)} | ${fmtTok(report.totalCacheRead)} | ${fmtTok(report.totalCacheCreate)} | $${fmt(report.totalCost)} | ${agentBreakdown} | ${channelBreakdown} |`\n\n if (!existsSync(file)) {\n const header =\n \"# Token Costs\\n\\n\" +\n \"Daily token cost tracking. Auto-appended at midnight (Africa/Tunis).\\n\" +\n \"Agent breakdown: `agent: $cost (Ns, T.Tt/s)` — sessions, tasks/session.\\n\\n\" +\n \"| Date | Tasks | Input | Output | Cache R | Cache W | Cost | Per Agent | Per Channel |\\n\" +\n \"|------|-------|-------|--------|---------|---------|------|-----------|-------------|\\n\"\n writeFileSync(file, header + row + \"\\n\")\n } else {\n appendFileSync(file, row + \"\\n\")\n }\n }\n\n /**\n * Return true if TOKEN_COSTS.md already has a row for `date`. Lets callers\n * skip duplicate appends after daemon restarts or timer-edge double fires.\n */\n hasTokenCostsEntry(date: string, filePath?: string): boolean {\n const file = filePath || resolve(process.cwd(), \".agentx/TOKEN_COSTS.md\")\n if (!existsSync(file)) return false\n try {\n const content = readFileSync(file, \"utf-8\")\n // Rows look like: `| 2026-04-18 | ...`\n return new RegExp(`^\\\\|\\\\s*${date}\\\\s*\\\\|`, \"m\").test(content)\n } catch {\n return false\n }\n }\n\n /**\n * Find the most recent date already logged to TOKEN_COSTS.md (YYYY-MM-DD).\n * Returns null when the file doesn't exist or has no rows.\n */\n lastTokenCostsDate(filePath?: string): string | null {\n const file = filePath || resolve(process.cwd(), \".agentx/TOKEN_COSTS.md\")\n if (!existsSync(file)) return null\n try {\n const content = readFileSync(file, \"utf-8\")\n const dates = content.match(/^\\|\\s*(\\d{4}-\\d{2}-\\d{2})\\s*\\|/gm) ?? []\n if (dates.length === 0) return null\n // Extract the date portion and pick the max — rows can be out of order\n // after a catch-up, so don't rely on file order.\n const parsed = dates\n .map((m) => m.match(/(\\d{4}-\\d{2}-\\d{2})/)?.[1])\n .filter((d): d is string => !!d)\n parsed.sort()\n return parsed[parsed.length - 1] ?? null\n } catch {\n return null\n }\n }\n\n /**\n * Append reports for any days between (lastLogged + 1) and `throughDate`\n * that have usage on disk but no row in TOKEN_COSTS.md. Idempotent —\n * safe to call on every daemon startup. Bounded to `maxDays` to avoid\n * a stalled daemon backfilling a year's worth on first start.\n *\n * Returns the list of dates actually appended, for logging.\n */\n catchUpTokenCosts(\n throughDate: string,\n opts: { maxDays?: number; filePath?: string } = {},\n ): string[] {\n const maxDays = opts.maxDays ?? 30\n const appended: string[] = []\n\n const last = this.lastTokenCostsDate(opts.filePath)\n // Walk backward from throughDate, collecting dates to fill. Stop at the\n // last-logged date (exclusive) or after maxDays, whichever comes first.\n const candidates: string[] = []\n const cursor = new Date(`${throughDate}T00:00:00Z`)\n for (let i = 0; i < maxDays; i++) {\n const date = cursor.toISOString().slice(0, 10)\n if (last && date <= last) break\n candidates.push(date)\n cursor.setUTCDate(cursor.getUTCDate() - 1)\n }\n // Oldest-first so the file stays chronological.\n candidates.reverse()\n\n for (const date of candidates) {\n if (this.hasTokenCostsEntry(date, opts.filePath)) continue\n const report = this.generateDailyReport(date)\n if (!report || report.totalTasks === 0) continue\n this.appendToTokenCosts(report, opts.filePath)\n appended.push(date)\n }\n return appended\n }\n\n /**\n * Get summary across last N days.\n */\n summary(days: number = 7): {\n totalTasks: number\n totalTokens: number\n totalInput: number\n totalOutput: number\n totalCacheRead: number\n totalCacheCreate: number\n cacheHitRatio: number\n totalErrors: number\n totalCost: number\n byAgent: Record<string, { tasks: number; input: number; output: number; cacheRead: number; cacheCreate: number; total: number; avgDuration: number; cost: number }>\n byChannel: Record<string, { tasks: number; cost: number }>\n sessionStats: Record<string, { sessions: number; avgTasksPerSession: number }>\n } {\n let totalTasks = 0\n let totalInput = 0\n let totalOutput = 0\n let totalCacheRead = 0\n let totalCacheCreate = 0\n let totalErrors = 0\n let totalCost = 0\n const byAgent: Record<string, { tasks: number; input: number; output: number; cacheRead: number; cacheCreate: number; total: number; avgDuration: number; totalDuration: number; cost: number; model?: string; sessions: number }> = {}\n const byChannel: Record<string, { tasks: number; cost: number }> = {}\n\n for (let i = 0; i < days; i++) {\n const date = new Date(Date.now() - i * 86400_000).toISOString().slice(0, 10)\n const usage = this.getDate(date)\n if (!usage) continue\n\n for (const [id, agent] of Object.entries(usage.agents)) {\n const cost = this.cost(agent)\n totalTasks += agent.tasks\n const agentInput = (agent.inputTokens || 0) + (agent.tier2InputTokens || 0)\n const agentOutput = (agent.outputTokens || 0) + (agent.tier2OutputTokens || 0)\n const agentCR = (agent.cacheReadTokens || 0) + (agent.tier2CacheReadTokens || 0)\n const agentCW = (agent.cacheCreateTokens || 0) + (agent.tier2CacheCreateTokens || 0)\n totalInput += agentInput\n totalOutput += agentOutput\n totalCacheRead += agentCR\n totalCacheCreate += agentCW\n totalErrors += agent.errors\n totalCost += cost\n\n const existing = byAgent[id] || { tasks: 0, input: 0, output: 0, cacheRead: 0, cacheCreate: 0, total: 0, avgDuration: 0, totalDuration: 0, cost: 0, sessions: 0 }\n existing.tasks += agent.tasks\n existing.input += agentInput\n existing.output += agentOutput\n existing.cacheRead += agentCR\n existing.cacheCreate += agentCW\n existing.total = existing.input + existing.output + existing.cacheRead + existing.cacheCreate\n existing.totalDuration += agent.totalDuration\n existing.avgDuration = existing.totalDuration / existing.tasks\n existing.cost += cost\n existing.sessions += agent.sessionKeys?.length || 0\n if (agent.model) existing.model = agent.model\n byAgent[id] = existing\n\n for (const [chKey, ch] of Object.entries(agent.byChannel || {})) {\n const chCost = this.cost(ch, agent.model)\n const acc = byChannel[chKey] || { tasks: 0, cost: 0 }\n acc.tasks += ch.tasks\n acc.cost += chCost\n byChannel[chKey] = acc\n }\n }\n }\n\n const totalTokens = totalInput + totalOutput + totalCacheRead + totalCacheCreate\n const cacheHitRatio = (totalCacheRead + totalCacheCreate) > 0\n ? totalCacheRead / (totalCacheRead + totalCacheCreate)\n : 0\n\n const sessionStats: Record<string, { sessions: number; avgTasksPerSession: number }> = {}\n for (const [id, a] of Object.entries(byAgent)) {\n sessionStats[id] = {\n sessions: a.sessions,\n avgTasksPerSession: a.sessions > 0 ? a.tasks / a.sessions : a.tasks,\n }\n }\n\n return { totalTasks, totalTokens, totalInput, totalOutput, totalCacheRead, totalCacheCreate, cacheHitRatio, totalErrors, totalCost, byAgent, byChannel, sessionStats }\n }\n\n private filePath(date: string): string {\n return resolve(this.dir, `${date}.json`)\n }\n\n private save(usage: DailyUsage): void {\n try {\n writeFileSync(this.filePath(usage.date), JSON.stringify(usage, null, 2))\n } catch {}\n }\n}\n\n/**\n * Load CACHE_AWARE_PRICING and merge any operator-supplied override. The\n * override file is optional; operators tweak rates (e.g. when Anthropic\n * publishes a price change) without rebuilding the daemon.\n */\nfunction loadPricingWithOverrides(): typeof CACHE_AWARE_PRICING {\n const result = JSON.parse(JSON.stringify(CACHE_AWARE_PRICING))\n try {\n const p = resolve(process.cwd(), PRICING_OVERRIDE_PATH)\n if (!existsSync(p)) return result\n const raw = JSON.parse(readFileSync(p, \"utf-8\"))\n for (const [family, rates] of Object.entries(raw)) {\n if (!result[family]) result[family] = { input: 0, output: 0, cacheRead: 0, cacheCreate: 0 }\n Object.assign(result[family], rates as object)\n }\n } catch {\n // Malformed override file — stay with defaults rather than crash at boot.\n }\n return result\n}\n"],"mappings":"AAAA,OAAS,gBAAAA,EAAc,iBAAAC,EAAe,kBAAAC,EAAgB,cAAAC,EAAY,aAAAC,MAAiB,KACnF,OAAS,WAAAC,MAAe,OAiCjB,IAAMC,EAAiH,CAE5H,kBAAoB,CAAE,MAAO,EAAM,OAAQ,GAAM,UAAW,GAAO,YAAa,IAAK,EACrF,kBAAoB,CAAE,MAAO,EAAM,OAAQ,GAAM,UAAW,GAAO,YAAa,IAAK,EAErF,cAAoB,CAAE,MAAO,GAAM,OAAQ,GAAM,UAAW,IAAO,YAAa,KAAM,EAEtF,gBAAoB,CAAE,MAAO,EAAM,OAAQ,GAAM,UAAW,GAAO,YAAa,IAAK,EAErF,eAAoB,CAAE,MAAO,EAAM,OAAQ,EAAM,UAAW,GAAO,YAAa,IAAK,CACvF,EAKaC,EAAyB,IACzBC,EAAmB,IAEzB,SAASC,EAAeC,EAAuB,CACpD,IAAMC,EAAQD,EAAM,YAAY,EAChC,OAAIC,EAAM,SAAS,MAAM,EAEnBA,EAAM,SAAS,KAAK,GAAKA,EAAM,SAAS,KAAK,GAAKA,EAAM,SAAS,UAAU,EAAU,kBACrFA,EAAM,SAAS,KAAK,GAAKA,EAAM,SAAS,KAAK,GAAKA,EAAM,SAAS,UAAU,EAAU,kBAClF,cAELA,EAAM,SAAS,OAAO,EAAU,eAC7B,eACT,CAuEA,IAAMC,EAAmB,IACnBC,EAAwB,8BAEjBC,EAAN,KAAmB,CAChB,IACA,MAA2B,KAC3B,QAER,YAAYC,EAAkBV,EAAQ,QAAQ,IAAI,EAAG,eAAe,EAAG,CACrE,KAAK,IAAMU,EACXX,EAAU,KAAK,IAAK,CAAE,UAAW,EAAK,CAAC,EACvC,KAAK,QAAUY,EAAyB,CAC1C,CAIA,YAAyC,CACvC,OAAO,KAAK,OACd,CAOA,OAAO,cAAcC,EAAkCP,EAAgBQ,EAA2CZ,EAA6B,CAC7I,IAAMa,EAASV,EAAeC,GAAUO,EAAqB,OAAS,eAAe,EAC/EG,EAAIF,EAAaC,CAAM,GAAKD,EAAa,eAAe,EACxDG,EAAWJ,EAAc,kBAAoB,EAC7CK,EAAYL,EAAc,mBAAqB,EAC/CM,EAAWN,EAAc,sBAAwB,EACjDO,EAAWP,EAAc,wBAA0B,EAEnDQ,GACFR,EAAM,aAAe,GAAK,IAAaG,EAAE,OACzCH,EAAM,cAAgB,GAAK,IAAaG,EAAE,QAC1CH,EAAM,iBAAmB,GAAK,IAAaG,EAAE,WAC7CH,EAAM,mBAAqB,GAAK,IAAaG,EAAE,YAE7CM,EACHL,EAAU,IAAaD,EAAE,MACzBE,EAAW,IAAaF,EAAE,OAC1BG,EAAU,IAAaH,EAAE,UACzBI,EAAU,IAAaJ,EAAE,YAE5B,OAAOK,EAAQC,EAAQlB,CACzB,CAOA,KAAKS,EAAkCP,EAAwB,CAC7D,OAAOI,EAAa,cAAcG,EAAOP,EAAO,KAAK,OAAO,CAC9D,CASA,OACEiB,EACAC,EACAC,EACAC,EACAC,EACAC,EACAtB,EACAuB,EACAC,EACM,CACN,IAAMC,EAAQ,KAAK,MAAM,EACnBC,EAAQD,EAAM,OAAOR,CAAO,GAAK,CACrC,MAAO,EAAG,YAAa,EAAG,aAAc,EACxC,gBAAiB,EAAG,kBAAmB,EACvC,iBAAkB,EAAG,kBAAmB,EACxC,qBAAsB,EAAG,uBAAwB,EACjD,cAAe,EAAG,OAAQ,EAAG,UAAW,CAAC,EAAG,YAAa,CAAC,CAC5D,EAEAS,EAAM,QACNA,EAAM,eAAiBR,EACnBlB,IAAO0B,EAAM,MAAQ1B,GAMzB,IAAI2B,EAAK,EAAGC,EAAK,EAAGC,EAAM,EAAGC,EAAM,EAAGC,EAAK,EAAGC,EAAK,EAAGC,EAAM,EAAGC,EAAM,EACjEf,EACiBA,EAAU,YAAcA,EAAU,gBAAkBA,EAAU,kBAChEtB,GACfkC,EAAKZ,EAAU,YACfa,EAAKb,EAAU,aACfc,EAAMd,EAAU,gBAChBe,EAAMf,EAAU,oBAEhBQ,EAAKR,EAAU,YACfS,EAAKT,EAAU,aACfU,EAAMV,EAAU,gBAChBW,EAAMX,EAAU,mBAETC,IAAkB,QAAaC,IAAmB,SAC3DM,EAAK,KAAK,KAAKP,EAAgB,CAAC,EAChCQ,EAAK,KAAK,KAAKP,EAAiB,CAAC,GAGnCK,EAAM,aAAeC,EACrBD,EAAM,cAAgBE,EACtBF,EAAM,iBAAmBG,EACzBH,EAAM,mBAAqBI,EAC3BJ,EAAM,kBAAoBA,EAAM,kBAAoB,GAAKK,EACzDL,EAAM,mBAAqBA,EAAM,mBAAqB,GAAKM,EAC3DN,EAAM,sBAAwBA,EAAM,sBAAwB,GAAKO,EACjEP,EAAM,wBAA0BA,EAAM,wBAA0B,GAAKQ,EAEjEZ,GAAOI,EAAM,SAGZA,EAAM,YAAWA,EAAM,UAAY,CAAC,GACzC,IAAMS,GAASZ,GAAW,SAAS,YAAY,EACzCa,EAAKV,EAAM,UAAUS,CAAK,GAAK,CACnC,MAAO,EAAG,YAAa,EAAG,aAAc,EACxC,gBAAiB,EAAG,kBAAmB,EACvC,iBAAkB,EAAG,kBAAmB,EACxC,qBAAsB,EAAG,uBAAwB,EACjD,cAAe,EAAG,OAAQ,CAC5B,EACAC,EAAG,QACHA,EAAG,eAAiBlB,EACpBkB,EAAG,aAAeT,EAAIS,EAAG,cAAgBR,EACzCQ,EAAG,iBAAmBP,EAAKO,EAAG,mBAAqBN,EACnDM,EAAG,kBAAoBL,EAAIK,EAAG,mBAAqBJ,EACnDI,EAAG,sBAAwBH,EAAKG,EAAG,wBAA0BF,EACzDZ,GAAOc,EAAG,SACdV,EAAM,UAAUS,CAAK,EAAIC,EAGrBZ,IACGE,EAAM,cAAaA,EAAM,YAAc,CAAC,GACxCA,EAAM,YAAY,SAASF,CAAU,IACxCE,EAAM,YAAY,KAAKF,CAAU,EAC7BE,EAAM,YAAY,OAASxB,GAC7BwB,EAAM,YAAY,OAAO,EAAGA,EAAM,YAAY,OAASxB,CAAgB,IAK7EuB,EAAM,OAAOR,CAAO,EAAIS,EACxB,KAAK,KAAKD,CAAK,CACjB,CAKA,OAAoB,CAClB,IAAMY,EAAO,IAAI,KAAK,EAAE,YAAY,EAAE,MAAM,EAAG,EAAE,EAEjD,GAAI,KAAK,OAAO,OAASA,EAAM,OAAO,KAAK,MAE3C,IAAMC,EAAO,KAAK,SAASD,CAAI,EAC/B,GAAI5C,EAAW6C,CAAI,EACjB,GAAI,CACF,YAAK,MAAQ,KAAK,MAAMhD,EAAagD,EAAM,OAAO,CAAC,EAC5C,KAAK,KACd,MAAE,CAAO,CAGX,YAAK,MAAQ,CAAE,KAAAD,EAAM,OAAQ,CAAC,CAAE,EACzB,KAAK,KACd,CAKA,QAAQA,EAAiC,CACvC,IAAMC,EAAO,KAAK,SAASD,CAAI,EAC/B,GAAI,CAAC5C,EAAW6C,CAAI,EAAG,OAAO,KAC9B,GAAI,CACF,OAAO,KAAK,MAAMhD,EAAagD,EAAM,OAAO,CAAC,CAC/C,MAAE,CACA,OAAO,IACT,CACF,CAMA,oBAAoBD,EAAmC,CACrD,IAAME,EAAaF,GAAQ,IAAI,KAAK,EAAE,YAAY,EAAE,MAAM,EAAG,EAAE,EACzD9B,EAAQ,KAAK,QAAQgC,CAAU,EACrC,GAAI,CAAChC,EAAO,OAAO,KAEnB,IAAIiC,EAAa,EAAGC,EAAa,EAAGC,EAAc,EAC9CC,EAAiB,EAAGC,EAAmB,EAAGC,EAAY,EACtDC,EAAW,GAAIC,EAAU,EACvBC,EAAqC,CAAC,EACtCC,EAAsF,CAAC,EACvFC,EAAoC,CAAC,EAE3C,OAAW,CAACC,EAAIzB,CAAK,IAAK,OAAO,QAAQnB,EAAM,MAAM,EAAG,CACtD,IAAM6C,EAAO,KAAK,KAAK1B,CAAK,EAC5BsB,EAAWG,CAAE,EAAIC,EACjBZ,GAAcd,EAAM,MACpBe,IAAef,EAAM,aAAe,IAAMA,EAAM,kBAAoB,GACpEgB,IAAgBhB,EAAM,cAAgB,IAAMA,EAAM,mBAAqB,GACvEiB,IAAmBjB,EAAM,iBAAmB,IAAMA,EAAM,sBAAwB,GAChFkB,IAAqBlB,EAAM,mBAAqB,IAAMA,EAAM,wBAA0B,GACtFmB,GAAaO,EACTA,EAAOL,IAAWA,EAAUK,EAAMN,EAAWK,GAEjD,IAAME,EAAW3B,EAAM,aAAa,QAAU,EAC9CuB,EAAkBE,CAAE,EAAI,CACtB,SAAAE,EACA,mBAAoBA,EAAW,EAAI3B,EAAM,MAAQ2B,EAAW3B,EAAM,KACpE,EAEA,OAAW,CAACS,EAAOC,CAAE,IAAK,OAAO,QAAQV,EAAM,WAAa,CAAC,CAAC,EAAG,CAC/D,IAAM4B,EAAS,KAAK,KAAKlB,EAAIV,EAAM,KAAK,EACxCwB,EAAUf,CAAK,GAAKe,EAAUf,CAAK,GAAK,GAAKmB,GAIjD,MAAO,CACL,KAAMf,EAAY,WAAAC,EAAY,WAAAC,EAAY,YAAAC,EAC1C,eAAAC,EAAgB,iBAAAC,EAAkB,UAAAC,EAAW,SAAAC,EAAU,QAAAC,EAAS,WAAAC,EAChE,kBAAAC,EAAmB,UAAAC,CACrB,CACF,CAMA,mBAAmBK,EAAqBC,EAAyB,CAC/D,IAAMC,EAAOD,GAAY7D,EAAQ,QAAQ,IAAI,EAAG,wBAAwB,EAClE+D,EAAO,GAAc,EAAI,IAAO,EAAE,QAAQ,CAAC,EAAI,EAAE,QAAQ,CAAC,EAC1DC,EAAU,GACd,GAAK,IAAY,IAAI,EAAI,KAAW,QAAQ,CAAC,KAC7C,GAAK,IAAQ,IAAI,EAAI,KAAO,QAAQ,CAAC,KACrC,OAAO,CAAC,EAEJC,EAAiB,OAAO,QAAQL,EAAO,UAAU,EACpD,KAAK,CAAC,CAAC,CAAEM,CAAC,EAAG,CAAC,CAAEC,CAAC,IAAMA,EAAID,CAAC,EAC5B,IAAI,CAAC,CAACV,EAAIC,CAAI,IAAM,CACnB,IAAMW,EAAQR,EAAO,kBAAkBJ,CAAE,EACnCa,EAAUD,GAASA,EAAM,SAAW,EAAI,KAAKA,EAAM,cAAcA,EAAM,mBAAmB,QAAQ,CAAC,QAAU,GACnH,MAAO,GAAGZ,OAAQO,EAAIN,CAAI,IAAIY,GAChC,CAAC,EACA,KAAK,IAAI,EAENC,EAAmB,OAAO,QAAQV,EAAO,SAAS,EACrD,KAAK,CAAC,CAAC,CAAEM,CAAC,EAAG,CAAC,CAAEC,CAAC,IAAMA,EAAID,CAAC,EAC5B,IAAI,CAAC,CAACzB,EAAIgB,CAAI,IAAM,GAAGhB,OAAQsB,EAAIN,CAAI,GAAG,EAC1C,KAAK,IAAI,EAENc,EAAM,KAAKX,EAAO,UAAUA,EAAO,gBAAgBI,EAAOJ,EAAO,UAAU,OAAOI,EAAOJ,EAAO,WAAW,OAAOI,EAAOJ,EAAO,cAAc,OAAOI,EAAOJ,EAAO,gBAAgB,QAAQG,EAAIH,EAAO,SAAS,OAAOK,OAAoBK,MAE3OxE,EAAWgE,CAAI,EASlBjE,EAAeiE,EAAMS,EAAM;AAAA,CAAI,EAF/B3E,EAAckE,EALZ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAK2BS,EAAM;AAAA,CAAI,CAI3C,CAMA,mBAAmB7B,EAAcmB,EAA4B,CAC3D,IAAMC,EAAOD,GAAY7D,EAAQ,QAAQ,IAAI,EAAG,wBAAwB,EACxE,GAAI,CAACF,EAAWgE,CAAI,EAAG,MAAO,GAC9B,GAAI,CACF,IAAMU,EAAU7E,EAAamE,EAAM,OAAO,EAE1C,OAAO,IAAI,OAAO,WAAWpB,WAAe,GAAG,EAAE,KAAK8B,CAAO,CAC/D,MAAE,CACA,MAAO,EACT,CACF,CAMA,mBAAmBX,EAAkC,CACnD,IAAMC,EAAOD,GAAY7D,EAAQ,QAAQ,IAAI,EAAG,wBAAwB,EACxE,GAAI,CAACF,EAAWgE,CAAI,EAAG,OAAO,KAC9B,GAAI,CAEF,IAAMW,EADU9E,EAAamE,EAAM,OAAO,EACpB,MAAM,kCAAkC,GAAK,CAAC,EACpE,GAAIW,EAAM,SAAW,EAAG,OAAO,KAG/B,IAAMC,EAASD,EACZ,IAAKE,GAAMA,EAAE,MAAM,qBAAqB,IAAI,CAAC,CAAC,EAC9C,OAAQC,GAAmB,CAAC,CAACA,CAAC,EACjC,OAAAF,EAAO,KAAK,EACLA,EAAOA,EAAO,OAAS,CAAC,GAAK,IACtC,MAAE,CACA,OAAO,IACT,CACF,CAUA,kBACEG,EACAC,EAAgD,CAAC,EACvC,CACV,IAAMC,EAAUD,EAAK,SAAW,GAC1BE,EAAqB,CAAC,EAEtBC,EAAO,KAAK,mBAAmBH,EAAK,QAAQ,EAG5CI,EAAuB,CAAC,EACxBC,EAAS,IAAI,KAAK,GAAGN,aAAuB,EAClD,QAASO,EAAI,EAAGA,EAAIL,EAASK,IAAK,CAChC,IAAM1C,EAAOyC,EAAO,YAAY,EAAE,MAAM,EAAG,EAAE,EAC7C,GAAIF,GAAQvC,GAAQuC,EAAM,MAC1BC,EAAW,KAAKxC,CAAI,EACpByC,EAAO,WAAWA,EAAO,WAAW,EAAI,CAAC,EAG3CD,EAAW,QAAQ,EAEnB,QAAWxC,KAAQwC,EAAY,CAC7B,GAAI,KAAK,mBAAmBxC,EAAMoC,EAAK,QAAQ,EAAG,SAClD,IAAMlB,EAAS,KAAK,oBAAoBlB,CAAI,EACxC,CAACkB,GAAUA,EAAO,aAAe,IACrC,KAAK,mBAAmBA,EAAQkB,EAAK,QAAQ,EAC7CE,EAAS,KAAKtC,CAAI,GAEpB,OAAOsC,CACT,CAKA,QAAQK,EAAe,EAarB,CACA,IAAIxC,EAAa,EACbC,EAAa,EACbC,EAAc,EACdC,EAAiB,EACjBC,EAAmB,EACnBqC,EAAc,EACdpC,EAAY,EACVqC,EAA+N,CAAC,EAChOhC,EAA6D,CAAC,EAEpE,QAAS6B,EAAI,EAAGA,EAAIC,EAAMD,IAAK,CAC7B,IAAM1C,EAAO,IAAI,KAAK,KAAK,IAAI,EAAI0C,EAAI,KAAS,EAAE,YAAY,EAAE,MAAM,EAAG,EAAE,EACrExE,EAAQ,KAAK,QAAQ8B,CAAI,EAC/B,GAAK9B,EAEL,OAAW,CAAC4C,EAAIzB,CAAK,IAAK,OAAO,QAAQnB,EAAM,MAAM,EAAG,CACtD,IAAM6C,EAAO,KAAK,KAAK1B,CAAK,EAC5Bc,GAAcd,EAAM,MACpB,IAAMyD,GAAczD,EAAM,aAAe,IAAMA,EAAM,kBAAoB,GACnE0D,GAAe1D,EAAM,cAAgB,IAAMA,EAAM,mBAAqB,GACtE2D,GAAW3D,EAAM,iBAAmB,IAAMA,EAAM,sBAAwB,GACxE4D,GAAW5D,EAAM,mBAAqB,IAAMA,EAAM,wBAA0B,GAClFe,GAAc0C,EACdzC,GAAe0C,EACfzC,GAAkB0C,EAClBzC,GAAoB0C,EACpBL,GAAevD,EAAM,OACrBmB,GAAaO,EAEb,IAAMmC,EAAWL,EAAQ/B,CAAE,GAAK,CAAE,MAAO,EAAG,MAAO,EAAG,OAAQ,EAAG,UAAW,EAAG,YAAa,EAAG,MAAO,EAAG,YAAa,EAAG,cAAe,EAAG,KAAM,EAAG,SAAU,CAAE,EAChKoC,EAAS,OAAS7D,EAAM,MACxB6D,EAAS,OAASJ,EAClBI,EAAS,QAAUH,EACnBG,EAAS,WAAaF,EACtBE,EAAS,aAAeD,EACxBC,EAAS,MAAQA,EAAS,MAAQA,EAAS,OAASA,EAAS,UAAYA,EAAS,YAClFA,EAAS,eAAiB7D,EAAM,cAChC6D,EAAS,YAAcA,EAAS,cAAgBA,EAAS,MACzDA,EAAS,MAAQnC,EACjBmC,EAAS,UAAY7D,EAAM,aAAa,QAAU,EAC9CA,EAAM,QAAO6D,EAAS,MAAQ7D,EAAM,OACxCwD,EAAQ/B,CAAE,EAAIoC,EAEd,OAAW,CAACpD,EAAOC,CAAE,IAAK,OAAO,QAAQV,EAAM,WAAa,CAAC,CAAC,EAAG,CAC/D,IAAM4B,EAAS,KAAK,KAAKlB,EAAIV,EAAM,KAAK,EAClC8D,EAAMtC,EAAUf,CAAK,GAAK,CAAE,MAAO,EAAG,KAAM,CAAE,EACpDqD,EAAI,OAASpD,EAAG,MAChBoD,EAAI,MAAQlC,EACZJ,EAAUf,CAAK,EAAIqD,IAKzB,IAAMC,EAAchD,EAAaC,EAAcC,EAAiBC,EAC1D8C,EAAiB/C,EAAiBC,EAAoB,EACxDD,GAAkBA,EAAiBC,GACnC,EAEE+C,EAAiF,CAAC,EACxF,OAAW,CAACxC,EAAIU,CAAC,IAAK,OAAO,QAAQqB,CAAO,EAC1CS,EAAaxC,CAAE,EAAI,CACjB,SAAUU,EAAE,SACZ,mBAAoBA,EAAE,SAAW,EAAIA,EAAE,MAAQA,EAAE,SAAWA,EAAE,KAChE,EAGF,MAAO,CAAE,WAAArB,EAAY,YAAAiD,EAAa,WAAAhD,EAAY,YAAAC,EAAa,eAAAC,EAAgB,iBAAAC,EAAkB,cAAA8C,EAAe,YAAAT,EAAa,UAAApC,EAAW,QAAAqC,EAAS,UAAAhC,EAAW,aAAAyC,CAAa,CACvK,CAEQ,SAAStD,EAAsB,CACrC,OAAO1C,EAAQ,KAAK,IAAK,GAAG0C,QAAW,CACzC,CAEQ,KAAK9B,EAAyB,CACpC,GAAI,CACFhB,EAAc,KAAK,SAASgB,EAAM,IAAI,EAAG,KAAK,UAAUA,EAAO,KAAM,CAAC,CAAC,CACzE,MAAE,CAAO,CACX,CACF,EAOA,SAASD,GAAuD,CAC9D,IAAMsF,EAAS,KAAK,MAAM,KAAK,UAAUhG,CAAmB,CAAC,EAC7D,GAAI,CACF,IAAMc,EAAIf,EAAQ,QAAQ,IAAI,EAAGQ,CAAqB,EACtD,GAAI,CAACV,EAAWiB,CAAC,EAAG,OAAOkF,EAC3B,IAAMC,EAAM,KAAK,MAAMvG,EAAaoB,EAAG,OAAO,CAAC,EAC/C,OAAW,CAACD,EAAQqF,CAAK,IAAK,OAAO,QAAQD,CAAG,EACzCD,EAAOnF,CAAM,IAAGmF,EAAOnF,CAAM,EAAI,CAAE,MAAO,EAAG,OAAQ,EAAG,UAAW,EAAG,YAAa,CAAE,GAC1F,OAAO,OAAOmF,EAAOnF,CAAM,EAAGqF,CAAe,CAEjD,MAAE,CAEF,CACA,OAAOF,CACT","names":["readFileSync","writeFileSync","appendFileSync","existsSync","mkdirSync","resolve","CACHE_AWARE_PRICING","CONTEXT_TIER_THRESHOLD","TIER2_MULTIPLIER","getModelFamily","model","lower","SESSION_KEYS_MAX","PRICING_OVERRIDE_PATH","TokenTracker","baseDir","loadPricingWithOverrides","usage","pricingTable","family","p","tier2In","tier2Out","tier2CR","tier2CW","tier1","tier2","agentId","duration","realUsage","messageLength","responseLength","error","channel","sessionKey","daily","agent","i1","o1","cr1","cw1","i2","o2","cr2","cw2","chKey","ch","date","path","targetDate","totalTasks","totalInput","totalOutput","totalCacheRead","totalCacheCreate","totalCost","topAgent","topCost","agentCosts","agentSessionStats","byChannel","id","cost","sessions","chCost","report","filePath","file","fmt","fmtTok","agentBreakdown","a","b","stats","sessTag","channelBreakdown","row","content","dates","parsed","m","d","throughDate","opts","maxDays","appended","last","candidates","cursor","i","days","totalErrors","byAgent","agentInput","agentOutput","agentCR","agentCW","existing","acc","totalTokens","cacheHitRatio","sessionStats","result","raw","rates"]}
|
package/dist/chunk-ASSJ4Z6Z.js
DELETED
|
@@ -1,164 +0,0 @@
|
|
|
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
|
-
<div class="ax-topbar__left">
|
|
5
|
-
<div class="ax-brand">
|
|
6
|
-
<span class="ax-brand__mark">AX</span>
|
|
7
|
-
<span class="ax-brand__name">AgentX</span>
|
|
8
|
-
<span class="ax-brand__subtitle">Setup</span>
|
|
9
|
-
</div>
|
|
10
|
-
</div>
|
|
11
|
-
<div class="ax-topbar__right">
|
|
12
|
-
<a href="/glossary" class="ax-skip">? Glossary</a>
|
|
13
|
-
<a href="/live" class="ax-skip">Skip \u2014 open dashboard</a>
|
|
14
|
-
<div class="ax-theme-switch" role="tablist" aria-label="Theme">
|
|
15
|
-
<button data-theme-opt="dark">Dark</button>
|
|
16
|
-
<button data-theme-opt="light">Light</button>
|
|
17
|
-
<button data-theme-opt="crt">CRT</button>
|
|
18
|
-
</div>
|
|
19
|
-
</div>
|
|
20
|
-
</header>`,r=p(1,"Team basics",d({name:"nodeName",label:"Team name",hint:"(what you'd call this AgentX install)",value:t,placeholder:"My Team",required:!0})),o=p(2,"First agent",b([d({name:"agentName",label:"Agent name",value:"Assistant",placeholder:"Support Bot",required:!0}),d({name:"agentId",label:"Agent id",hint:"(lowercase, no spaces)",value:"assistant",pattern:"[a-z0-9][a-z0-9_-]*",required:!0})])+d({name:"triggerWords",label:"Trigger words",hint:"(comma or space separated \u2014 e.g. <code>@support, support</code>)",value:"@assistant, assistant",required:!0})+b([d({name:"tier",label:"AI engine",type:"select",options:[{value:"claude-code",label:"Claude Code (recommended)",selected:!0},{value:"sdk",label:"Anthropic API (BYO key)"}]}),d({name:"model",label:"Model",hint:"(optional)",value:"claude-sonnet-4-6",placeholder:"claude-sonnet-4-6"})])+d({name:"personality",label:"Personality / instructions",hint:"(optional \u2014 what this agent does, in plain English)",type:"textarea",placeholder:"You are a support agent for Acme Co. Answer customer questions about our product, be friendly, keep replies short."})),i=p(3,"First channel",`
|
|
21
|
-
<label class="ax-toggle"><input type="checkbox" id="enableTelegram" /> Connect Telegram now</label>
|
|
22
|
-
<div class="ax-optional" id="telegramFields">
|
|
23
|
-
${d({name:"telegramAccountId",label:"Account id",hint:"(a label you choose \u2014 e.g. <code>support</code>)",value:"default"})}
|
|
24
|
-
${d({name:"telegramBotToken",label:"Bot token",hint:'(from <a href="https://t.me/BotFather" target="_blank">@BotFather</a>)',placeholder:"123456:ABCdef..."})}
|
|
25
|
-
${d({name:"telegramBotUsername",label:"Bot username",hint:"(optional \u2014 e.g. <code>my_support_bot</code>)",placeholder:"my_support_bot"})}
|
|
26
|
-
</div>
|
|
27
|
-
`),c=p(4,"Anthropic API key",d({name:"anthropicApiKey",label:"API key",hint:"(optional \u2014 skip if you're using Claude Code)",type:"password",placeholder:"sk-ant-\u2026",extraAttrs:'autocomplete="off"'})+`<div class="ax-hint-block">
|
|
28
|
-
The key is written to <code>.env</code> as <code>ANTHROPIC_API_KEY</code>. AgentX never transmits it \u2014 everything stays on this machine.
|
|
29
|
-
</div>`),l=e.configExists?`<div class="ax-banner">Existing install: <b>${e.agentCount}</b> agent(s), <b>${e.channelCount}</b> channel(s) already configured. This wizard will add to it.</div>`:"",g=`<div class="ax-wizard">
|
|
30
|
-
<h1 class="ax-wizard__h1">${A(n)}</h1>
|
|
31
|
-
<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>
|
|
32
|
-
|
|
33
|
-
${l}
|
|
34
|
-
|
|
35
|
-
<div id="msg" class="ax-msg"></div>
|
|
36
|
-
|
|
37
|
-
<form id="wizard" autocomplete="off">
|
|
38
|
-
${r}
|
|
39
|
-
${o}
|
|
40
|
-
${i}
|
|
41
|
-
${c}
|
|
42
|
-
<footer class="ax-actions">
|
|
43
|
-
${f("Save and continue",{type:"submit",primary:!0,id:"submitBtn"})}
|
|
44
|
-
${f("Skip, I'll do it later",{onclick:"window.location.href='/live'"})}
|
|
45
|
-
</footer>
|
|
46
|
-
</form>
|
|
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
|
-
/* Pre-dashboard topbar variant \u2014 no tab nav, the pills are the only chrome. */
|
|
49
|
-
.ax-topbar__right a.ax-skip {
|
|
50
|
-
padding: 4px 10px; border: 1px solid var(--ax-border-2); border-radius: 4px;
|
|
51
|
-
color: var(--ax-text-2); font-size: var(--ax-fs-xs); text-decoration: none;
|
|
52
|
-
}
|
|
53
|
-
.ax-topbar__right a.ax-skip:hover { color: var(--ax-accent); border-color: var(--ax-accent); }
|
|
54
|
-
|
|
55
|
-
/* Wizard frame */
|
|
56
|
-
.ax-wizard { max-width: 680px; margin: 0 auto; padding: 36px 22px 80px; }
|
|
57
|
-
.ax-wizard__h1 { font-size: 22px; font-weight: 600; margin: 0 0 4px; letter-spacing: -0.015em; }
|
|
58
|
-
.ax-wizard__lead { color: var(--ax-muted); margin: 0 0 24px; font-size: var(--ax-fs); line-height: 1.55; }
|
|
59
|
-
.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); }
|
|
60
|
-
|
|
61
|
-
/* Info banner ("You already have N agents\u2026") */
|
|
62
|
-
.ax-banner {
|
|
63
|
-
background: color-mix(in oklch, var(--ax-accent) 10%, var(--ax-bg-elev));
|
|
64
|
-
border: 1px solid color-mix(in oklch, var(--ax-accent) 30%, var(--ax-border-2));
|
|
65
|
-
border-radius: var(--ax-radius);
|
|
66
|
-
padding: 10px 14px; margin-bottom: 18px;
|
|
67
|
-
color: var(--ax-text-2); font-size: var(--ax-fs-sm);
|
|
68
|
-
}
|
|
69
|
-
.ax-banner b { color: var(--ax-text); font-weight: 600; }
|
|
70
|
-
|
|
71
|
-
/* "Connect Telegram now" toggle + collapsible fields */
|
|
72
|
-
.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; }
|
|
73
|
-
.ax-toggle input { width: auto; accent-color: var(--ax-accent); }
|
|
74
|
-
.ax-optional { border-left: 2px solid var(--ax-border-2); padding-left: 14px; margin-top: 10px; display: none; }
|
|
75
|
-
.ax-optional.is-show { display: block; }
|
|
76
|
-
.ax-hint-block { font-size: var(--ax-fs-xs); color: var(--ax-muted); margin-top: 8px; line-height: 1.55; }
|
|
77
|
-
.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); }
|
|
78
|
-
|
|
79
|
-
/* Form footer */
|
|
80
|
-
.ax-actions {
|
|
81
|
-
display: flex; gap: 10px; padding: 20px 0 0; align-items: center;
|
|
82
|
-
border-top: 1px solid var(--ax-border); margin-top: 20px;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/* Server-response messaging (success / error blocks) */
|
|
86
|
-
.ax-msg { margin: 14px 0; padding: 12px 16px; border-radius: var(--ax-radius); font-size: var(--ax-fs); display: none; }
|
|
87
|
-
.ax-msg.is-ok {
|
|
88
|
-
display: block;
|
|
89
|
-
background: color-mix(in oklch, var(--ax-accent) 12%, transparent);
|
|
90
|
-
color: var(--ax-accent);
|
|
91
|
-
border: 1px solid color-mix(in oklch, var(--ax-accent) 40%, transparent);
|
|
92
|
-
}
|
|
93
|
-
.ax-msg.is-err {
|
|
94
|
-
display: block;
|
|
95
|
-
background: color-mix(in oklch, var(--ax-err) 12%, transparent);
|
|
96
|
-
color: var(--ax-err);
|
|
97
|
-
border: 1px solid color-mix(in oklch, var(--ax-err) 40%, transparent);
|
|
98
|
-
}
|
|
99
|
-
.ax-next-steps { margin-top: 12px; padding-left: 20px; color: var(--ax-text-2); }
|
|
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); }`,K=`<script>
|
|
102
|
-
document.getElementById('enableTelegram').addEventListener('change', (e) => {
|
|
103
|
-
document.getElementById('telegramFields').classList.toggle('is-show', e.target.checked);
|
|
104
|
-
});
|
|
105
|
-
document.getElementById('wizard').addEventListener('submit', async (e) => {
|
|
106
|
-
e.preventDefault();
|
|
107
|
-
const f = e.target;
|
|
108
|
-
const msg = document.getElementById('msg');
|
|
109
|
-
const btn = document.getElementById('submitBtn');
|
|
110
|
-
msg.className = 'ax-msg'; msg.style.display = 'none';
|
|
111
|
-
btn.disabled = true; btn.textContent = 'Saving\u2026';
|
|
112
|
-
const telegramEnabled = document.getElementById('enableTelegram').checked;
|
|
113
|
-
const payload = {
|
|
114
|
-
nodeName: f.nodeName.value.trim(),
|
|
115
|
-
agent: {
|
|
116
|
-
id: f.agentId.value.trim(),
|
|
117
|
-
name: f.agentName.value.trim(),
|
|
118
|
-
triggerWords: f.triggerWords.value.trim(),
|
|
119
|
-
tier: f.tier.value,
|
|
120
|
-
model: f.model.value.trim() || undefined,
|
|
121
|
-
personality: f.personality.value.trim() || undefined,
|
|
122
|
-
},
|
|
123
|
-
anthropicApiKey: f.anthropicApiKey.value.trim() || undefined,
|
|
124
|
-
};
|
|
125
|
-
if (telegramEnabled) {
|
|
126
|
-
payload.telegram = {
|
|
127
|
-
accountId: f.telegramAccountId.value.trim() || 'default',
|
|
128
|
-
botToken: f.telegramBotToken.value.trim(),
|
|
129
|
-
botUsername: f.telegramBotUsername.value.trim() || undefined,
|
|
130
|
-
};
|
|
131
|
-
if (!payload.telegram.botToken) {
|
|
132
|
-
msg.className = 'ax-msg is-err'; msg.textContent = 'Bot token is required when Telegram is enabled.';
|
|
133
|
-
btn.disabled = false; btn.textContent = 'Save and continue'; return;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
try {
|
|
137
|
-
const r = await fetch('/api/setup', {
|
|
138
|
-
method: 'POST',
|
|
139
|
-
headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'agentx-wizard' },
|
|
140
|
-
body: JSON.stringify(payload),
|
|
141
|
-
});
|
|
142
|
-
const data = await r.json();
|
|
143
|
-
if (!r.ok) throw new Error(data.error || ('HTTP ' + r.status));
|
|
144
|
-
const steps = data.nextSteps.map((s) => '<li>' + s.replace(/\\\`([^\\\`]+)\\\`/g, '<code>$1</code>') + '</li>').join('');
|
|
145
|
-
msg.className = 'ax-msg is-ok';
|
|
146
|
-
msg.innerHTML = '<b>' + data.summary + '</b><ol class="ax-next-steps">' + steps + '</ol>';
|
|
147
|
-
btn.textContent = 'Done';
|
|
148
|
-
btn.disabled = true;
|
|
149
|
-
} catch (err) {
|
|
150
|
-
msg.className = 'ax-msg is-err'; msg.textContent = err.message;
|
|
151
|
-
btn.disabled = false; btn.textContent = 'Save and continue';
|
|
152
|
-
}
|
|
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 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
|
-
|
|
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=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
|
-
`));return}if(r.length>0){let o=a.endsWith(`
|
|
159
|
-
`)?"":`
|
|
160
|
-
`;H(e,o+r.join(`
|
|
161
|
-
`)+`
|
|
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 +0,0 @@
|
|
|
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":"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","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"]}
|