agentix-cli 0.153.0 → 0.153.1
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/dist/{board-dashboard-OZJSS6EM.js → board-dashboard-UZF7MQZA.js} +3 -3
- package/dist/{chunk-R4R7EYY3.js → chunk-BNOGNRAM.js} +2 -2
- package/dist/{chunk-P24P43ZB.js → chunk-DZRD6BE4.js} +2 -2
- package/dist/{chunk-SW6HCRLU.js → chunk-IFOFBI3U.js} +2 -2
- package/dist/chunk-LQS27HSV.js +6 -0
- package/dist/chunk-LQS27HSV.js.map +1 -0
- package/dist/{chunk-D6U37WO3.js → chunk-VAK4LT62.js} +2 -2
- package/dist/{chunk-IJQ2UUMZ.js → chunk-XQ2VU6MM.js} +3 -3
- package/dist/{chunk-MOCDP7Q3.js → chunk-ZAH74SFX.js} +2 -2
- package/dist/cli.js +1 -1
- package/dist/{doctor-UT32WKCP.js → doctor-QWVUVWTF.js} +2 -2
- package/dist/{grants-HSLB62EG.js → grants-YGA654NI.js} +2 -2
- package/dist/index.js +1 -1
- package/dist/{program-NU6QIXJJ.js → program-2YK3YCBX.js} +5 -5
- package/package.json +1 -1
- package/dist/chunk-24QMHUU2.js +0 -5
- package/dist/chunk-24QMHUU2.js.map +0 -1
- /package/dist/{board-dashboard-OZJSS6EM.js.map → board-dashboard-UZF7MQZA.js.map} +0 -0
- /package/dist/{chunk-R4R7EYY3.js.map → chunk-BNOGNRAM.js.map} +0 -0
- /package/dist/{chunk-P24P43ZB.js.map → chunk-DZRD6BE4.js.map} +0 -0
- /package/dist/{chunk-SW6HCRLU.js.map → chunk-IFOFBI3U.js.map} +0 -0
- /package/dist/{chunk-D6U37WO3.js.map → chunk-VAK4LT62.js.map} +0 -0
- /package/dist/{chunk-IJQ2UUMZ.js.map → chunk-XQ2VU6MM.js.map} +0 -0
- /package/dist/{chunk-MOCDP7Q3.js.map → chunk-ZAH74SFX.js.map} +0 -0
- /package/dist/{doctor-UT32WKCP.js.map → doctor-QWVUVWTF.js.map} +0 -0
- /package/dist/{grants-HSLB62EG.js.map → grants-YGA654NI.js.map} +0 -0
- /package/dist/{program-NU6QIXJJ.js.map → program-2YK3YCBX.js.map} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{a as Jm,b as ni,g as Gm}from"./chunk-3OPW64OM.js";import{a as Wm,b as Bm}from"./chunk-2J2OES2S.js";import{b as Um}from"./chunk-UUVJX6WD.js";import{D as Am,E as Em,F as sc,G as Rm,J as Nm,K as Im,L as jm,N as rc,O as _m,Q as Dm,R as Pm,S as Mm,a as rm,e as im,f as am,g as cm,h as lm,i as dm,j as gm,m as ti,n as oi,o as Qt,p as pm,q as fm,r as $m,s as bm,u as km,v as vm,w as xm,x as nc,y as Sm}from"./chunk-
|
|
1
|
+
import{a as Jm,b as ni,g as Gm}from"./chunk-3OPW64OM.js";import{a as Wm,b as Bm}from"./chunk-2J2OES2S.js";import{b as Um}from"./chunk-UUVJX6WD.js";import{D as Am,E as Em,F as sc,G as Rm,J as Nm,K as Im,L as jm,N as rc,O as _m,Q as Dm,R as Pm,S as Mm,a as rm,e as im,f as am,g as cm,h as lm,i as dm,j as gm,m as ti,n as oi,o as Qt,p as pm,q as fm,r as $m,s as bm,u as km,v as vm,w as xm,x as nc,y as Sm}from"./chunk-IFOFBI3U.js";import{a as Cm,c as Tm,d as Om}from"./chunk-5NTWGUSJ.js";import{n as ic}from"./chunk-MM3ABEOW.js";import{a as Lm,b as Fm,d as Ds,e as qm}from"./chunk-FVRREIVV.js";import"./chunk-XVTD6I34.js";import"./chunk-QAWKN2N4.js";import"./chunk-MTX4UPNB.js";import{a as St}from"./chunk-CFPRIE6F.js";import{a as um,c as mm}from"./chunk-DZRD6BE4.js";import{d as hm,e as oc,h as ym,i as wm}from"./chunk-LLJZMS23.js";import{a as ec}from"./chunk-LNY6N73E.js";import"./chunk-B2QYMUTD.js";import{a as _a,b as fg}from"./chunk-RKPAVWXF.js";import{$ as lo,A as Ba,Aa as ei,B as mu,Ba as tc,C as pu,Ca as sm,D as fu,E as hu,F as yu,G as Gr,H as wu,I as $u,J as Hr,K as ku,L as Su,M as Au,N as Eu,O as Ru,P as Cu,Q as Ja,R as Tu,S as Ou,T as Nu,U as Iu,V as ju,W as _u,X as Du,Y as Pu,Z as Bu,_ as Vr,a as xg,aa as Ao,b as Sg,ba as qn,ca as Xr,da as Kr,ea as Va,fa as Ju,ga as Gu,h as Ir,ha as Hu,i as Cg,ia as zu,j as Os,ja as Yu,k as jr,ka as Vu,l as Og,la as Un,m as Ig,ma as Wn,n as Jg,na as Xu,o as Gg,oa as js,p as Fr,pa as Ku,q as Fa,qa as st,r as Qg,ra as Qr,s as au,sa as _s,t as cu,ta as Xa,u as lu,ua as Qu,v as Ua,va as tm,w as du,wa as Za,x as gu,xa as nm,y as Wa,ya as Zr,z as uu,za as Bn}from"./chunk-XQ2VU6MM.js";import{$ as Ga,B as Br,C as ru,F as iu,G as Jr,J as bu,Pa as Zu,Qa as Ka,Ra as em,Sa as Qa,Y as Ns,aa as zr,ba as Mu,eb as Kt,gb as om,ia as So,ja as Lu,ka as Ha,ma as za,oa as Fu,p as Ng,pa as qu,qa as Is,ua as Yr,xa as Uu,ya as Wu,z as su,za as Ya}from"./chunk-VAK4LT62.js";import"./chunk-GTJIKXDB.js";import{a as Ag,c as Pa,e as Eg,f as Rg}from"./chunk-GDLO7FC7.js";import{D as Pr,E as Wg,F as Bg,a as ja,b as dg,d as gg,e as ug,l as _r,m as Dr,n as jg,o as _g,p as Dg,r as Pg,s as Mg,u as Lg,v as Fg,w as qg,x as Ug}from"./chunk-ZAH74SFX.js";import"./chunk-7LN4VKDU.js";import"./chunk-IMLAPB7G.js";import"./chunk-ZVP6GB4G.js";import{c as zg,e as Mr,g as Lr}from"./chunk-LQE73AZR.js";import{a as Yg,b as Vg,c as Xg,d as Kg}from"./chunk-2D3RYIDU.js";import"./chunk-MRIVENBY.js";import"./chunk-BNOGNRAM.js";import{a as Zg,b as Vo,c as eu,d as qr,g as tu,h as ou,j as nu,k as qa,n as Ur,o as Xo,p as Wr}from"./chunk-LQS27HSV.js";import"./chunk-PPUN7YHA.js";import"./chunk-VZLIBDLH.js";import{a as vu,b as xu}from"./chunk-EKJG63EJ.js";import{a as Ma,p as Tg}from"./chunk-3LU2ZQZJ.js";import"./chunk-2AXFYDWF.js";import"./chunk-AQASR6KR.js";import{b as xt}from"./chunk-EMGB23H5.js";import{c as Hg}from"./chunk-JAML6222.js";import"./chunk-YMX3QCV7.js";import"./chunk-JWDJMFU6.js";import{b as La}from"./chunk-5HLD66CW.js";import"./chunk-IS2IWRP7.js";import{a as Xt}from"./chunk-MNG2AMKZ.js";import"./chunk-X7UQY7CH.js";import{a as Da,e as hg,f as yg,h as wg,i as $g,k as bg,l as kg,m as vg}from"./chunk-ZQONMM3B.js";import{b as Fn,e as pg}from"./chunk-PYHR4HXO.js";import{c as mg}from"./chunk-KP7KDLZ2.js";import"./chunk-A5GYG56I.js";import{A as Oa,B as Yd,N as xo,T as ag,g as qd,r as Ud,ra as lg}from"./chunk-5DPN2HIR.js";import{F as rg,U as ig,W as cg,b as In,g as jn,h as Or,i as Nr,j as _n,k as Vd,l as ko,n as vo}from"./chunk-S5FRIHWM.js";import{b as zo}from"./chunk-MEFPXHQJ.js";import"./chunk-MWYPW5XP.js";import"./chunk-SFQUP3BP.js";import"./chunk-6TTGRTDO.js";import{c as Kd}from"./chunk-SZDACK6O.js";import{f as Qd,g as Zd,h as Na}from"./chunk-VE3SSOQX.js";import"./chunk-W4YR2RO2.js";import"./chunk-AFRHKA7B.js";import{e as tg,f as Yo,k as Pn}from"./chunk-5TTHN7DP.js";import{a as Hd,k as eg}from"./chunk-LH3GOBZP.js";import{a as Mn,b as ge,c as Je,d as Ln,e as og}from"./chunk-BFONYVOM.js";import{a as ng,b as Ia,h as sg}from"./chunk-3UOTLMXD.js";import{f as Bd,g as Jd}from"./chunk-36AXN6ZB.js";import"./chunk-PE5V2LI7.js";import{a as Ta,b as Gd}from"./chunk-GQ76J72O.js";import"./chunk-ILEC3IAV.js";import"./chunk-J3SOEFRE.js";import"./chunk-SIU6STB2.js";import{j as zd}from"./chunk-X5C5W3E4.js";import{a as Dn}from"./chunk-GV7S5BAX.js";import{e as Xd}from"./chunk-GSX4NK2N.js";import"./chunk-VWTBHLMX.js";import{a as Ca}from"./chunk-RUQMUDSX.js";import"./chunk-JPZBWHNZ.js";import{c as Wd}from"./chunk-OWNQ2FD4.js";import"./chunk-KEXBEUSU.js";import"./chunk-ROLSSYXE.js";import"./chunk-DPSHY7GN.js";import"./chunk-SKSYII3K.js";import{g as bo,i as Tr}from"./chunk-RJCHFDF3.js";import"./chunk-WPNRDEHW.js";import"./chunk-MWNCS57L.js";import"./chunk-4LWFJ4MZ.js";import"./chunk-BIJHMWXK.js";import"./chunk-Z2KGUEB6.js";import{a as va,b as hd,c as D$,d as Sr,e as yd,f as Id,g as B$,h as jd,i as J$,m as Nn,n as Dd,q as Pd,u as Md,y as Vt,z as Ts}from"./chunk-HVNWI63Z.js";import{a as co,b as On,e as Td,f as Cs}from"./chunk-72SZI2UW.js";import"./chunk-FCPTN6NF.js";import{a as Cr,b as _d}from"./chunk-MCI3DIWW.js";import"./chunk-MYVHVJWZ.js";import{g as $o,i as Ld}from"./chunk-6TURWSMN.js";import{n as Fd}from"./chunk-AV7T3SYT.js";import"./chunk-U23LVMMZ.js";import"./chunk-QD6IICK7.js";import"./chunk-QOYAX2FT.js";import"./chunk-OSH2PEBH.js";import"./chunk-JNG3P5V2.js";import{a as Yt,b as _$}from"./chunk-7I7IEX4S.js";import"./chunk-JHCI6XCJ.js";import"./chunk-DSYKYZMT.js";import{F as Ar,G as xd,J as Sd,Ja as Er,K as L$,Ka as Rs,L as Sa,La as C,M as Ad,Na as Rr,S as Ed,T as F$,Ua as ae,fa as Rd,ga as Aa,h as wd,i as $d,ia as Ea,j as bd,k as kd,l as vd,m as P$,pa as q$,qa as Ra,sa as Cd,ta as U$,v as xa,w as M$}from"./chunk-5VPU5FHB.js";import"./chunk-BRK6M6LA.js";import{f as vt,n as Es}from"./chunk-UIBUZ2N3.js";import"./chunk-2QNT5JDS.js";import{g as Od,k as Nd,l as W$}from"./chunk-RGDKHYY2.js";import"./chunk-QIJLFPWE.js";import{Command as G$}from"commander";var Hm=new G$().name("a2a").description("start an A2A (Agent-to-Agent) protocol server for external agent integration").option("--port <port>","server port","3171").option("--host <host>","server host","0.0.0.0").option("-p, --provider <provider>","AI provider","claude-code").option("-m, --model <model>","model to use").option("--api-key <key>","API key for the provider").option("-c, --cwd <cwd>","working directory",process.cwd()).option("--no-cors","disable CORS headers").action(async e=>{await new Sg({port:parseInt(e.port,10),host:e.host,provider:e.provider,model:e.model,apiKey:e.apiKey,cwd:e.cwd,cors:e.cors!==!1}).start()});ae();import{Command as H$}from"commander";import{execFileSync as It}from"node:child_process";import{cpSync as zm,existsSync as lc,mkdirSync as ac,mkdtempSync as z$,readdirSync as Y$,rmSync as Ym,writeFileSync as Vm}from"node:fs";import{dirname as Xm,join as rt}from"node:path";import{homedir as si,release as V$,tmpdir as X$}from"node:os";import{setTimeout as K$}from"node:timers/promises";function ri(){let e=jg(process.platform,process.arch,V$());if(e)throw new Error(e)}function ii(){let e=rt(si(),"Library/LaunchAgents"),t=[];try{t=Y$(e).filter(Pg)}catch{}let o=t.flatMap(n=>{let s=rt(e,n);try{return[{file:s,plist:It("plutil",["-convert","xml1","-o","-",s],{encoding:"utf8",stdio:["ignore","pipe","ignore"]})}]}catch{return[]}});return Mg(e,o)}function Ps(e){return`gui/${process.getuid()}/${e.label}`}async function cc(e){try{It("launchctl",["bootout",Ps(e)],{stdio:"pipe"})}catch{}for(let t=0;t<30;t++){try{It("launchctl",["print",Ps(e)],{stdio:"pipe"})}catch{return}await K$(200)}throw new Error("The desktop login service did not stop. Retry agentx desktop stop before installing.")}function Km(e){if(!lc(e.file))throw new Error("Run agentx desktop install first.");let t=!0;try{It("launchctl",["print",Ps(e)],{stdio:"pipe"})}catch{t=!1}t||It("launchctl",["bootstrap",`gui/${process.getuid()}`,e.file],{stdio:"pipe"}),It("launchctl",["kickstart",Ps(e)],{stdio:"pipe"})}function Q$(){let e=Ug();if(e&&lc(rt(e,"apps/mac-voice/build.sh")))return e;throw new Error("Desktop sources are missing from this installation. Update AgentX to a release containing apps/mac-voice and apps/mac-helper.")}var Jn=new H$("desktop").description("install and control the macOS desktop assistant");Jn.command("install").description("build, install, and start voice and computer-use helpers at login").option("--agent <id>","pin this agent; without it, pick the agent from the menu-bar icon").option("--dry-run","show the installation plan without building or changing login items").action(async e=>{ri();let t=C(),o=_g(t.agents,e.agent),n=e.agent,s=t.dashboard?.daemonUrl||`http://${t.node.bind.replace(/^0\.0\.0\.0:/,"127.0.0.1:")}`,r=Q$(),i=rt(si(),"Applications"),{keep:a,remove:c}=ii();if(console.log(`Desktop assistant \u2192 ${rt(i,_r)}
|
|
2
2
|
Computer-use helper \u2192 ${rt(i,Dr)}
|
|
3
3
|
Agent \u2192 ${n?`${o} (pinned)`:"picked from the menu-bar icon"}
|
|
4
4
|
Daemon \u2192 ${s}
|
|
@@ -41,7 +41,7 @@ The AgentX daemon and a speech transcription backend must also be available. Run
|
|
|
41
41
|
`))});function Up(e){let t=vt();return t||(console.log(Q.red(" The database (.agentx/db.sqlite) can't be opened here.")),process.exit(1)),e(new ec(t))}function Zb(){let e=vt({quiet:!0});if(!e)return[];let t=new Map;for(let o of new ec(e).list()){if(!La(o.deviceId))continue;let n=t.get(o.deviceId)??{deviceId:o.deviceId,deviceName:o.deviceName,createdAt:o.createdAt,subscriptions:0};n.subscriptions++,n.createdAt=Math.max(n.createdAt,o.createdAt),t.set(o.deviceId,n)}return[...t.values()]}function ek(e){let t=Math.round(zg/6e4),o=[` Pairing code: ${Q.bold.cyan(Mr(e.code))}`,Q.dim(` Open the app from the phone's home screen and type this code. It works once, for ${t} minutes.`)," Or scan the QR code with the phone's camera and open the link.",` Device ${Q.cyan(e.deviceId)} (${e.deviceName}) paired to ${e.origin}.`,Q.yellow(" \u26A0 Anyone who scans the QR code or types the code can use the app. Clear the terminal when done.")];return e.origin.startsWith("https://")||o.push(Q.yellow(` \u26A0 ${e.origin} is not HTTPS: the phone can't install the app or keep the session from there.`)),o}function Sc(){let e;try{e=JSON.parse(qp("tailscale",["status","--json"],{encoding:"utf-8",timeout:5e3}))}catch{throw new Error("Could not read this machine's Tailscale name. Is Tailscale running? Or pass --url https://<address>.")}let t=String(e?.Self?.DNSName||"").replace(/\.$/,"");if(!t)throw new Error("Tailscale has no MagicDNS name for this machine. Pass --url https://<address>.");return`https://${t}`}var tk=new Set(["/app","/api/app","/member","/api/member"]),Fp="/.well-known/assetlinks.json";function ok(){let e=new Set(["localhost","0.0.0.0","::","::1"]);try{let t=Kb().toLowerCase().replace(/\.$/,"");t&&e.add(t).add(t.replace(/\.local$/,"")).add(`${t.replace(/\.local$/,"")}.local`)}catch{}try{for(let t of Object.values(Qb()))for(let o of t??[])e.add(Wp(o.address.toLowerCase().replace(/%.*$/,"")))}catch{}return e}var nk={http:80,https:443,"https+insecure":443};function sk(e){let t=e.match(/^(\d+)(\/[^?#]*)?$/);if(t)return{host:"localhost",port:Number(t[1]),path:t[2]??""};let o=e.match(/^(?:([a-z][a-z0-9+.-]*):\/\/)?(?:[^@/?#]*@)?(\[[^\]]*\]|[^/:?#]*)(?::(\d+))?([^?#]*)/i);if(!o)return null;let n=o[3]?Number(o[3]):nk[(o[1]??"http").toLowerCase()];return n?{host:Wp(o[2].replace(/^\[|\]$/g,"").replace(/%.*$/,"").replace(/\.$/,"").toLowerCase()),port:n,path:o[4]}:null}function Wp(e){let t=e.includes(":");if(!t&&!/^[0-9a-fx.]+$/.test(e))return e;let o;try{o=new URL(`http://${t?`[${e}]`:e}/`).hostname.replace(/^\[|\]$/g,"")}catch{return e}let n=o.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/);if(!n)return o;let[s,r]=[parseInt(n[1],16),parseInt(n[2],16)];return`::ffff:${s>>8}.${s&255}.${r>>8}.${r&255}`}function rk(e,t){let o=e.replace(/^::ffff:/,"");return t.has(e)||t.has(o)||e===""||e.endsWith(".localhost")||/^127\.\d+\.\d+\.\d+$/.test(o)||o==="0.0.0.0"}function Ac(e,t,o=ok()){let n=[e,...Object.values(e?.Foreground??{})],s=[];for(let c of n){s.push({label:"",cfg:c});for(let[l,g]of Object.entries(c?.Services??{}))s.push({label:`${l} `,cfg:g})}let r=new Set(o);for(let{cfg:c}of s){for(let l of Object.keys(c?.Web??{}))r.add(l.replace(/:\d+$/,"").replace(/\.$/,"").toLowerCase());for(let l of Object.values(c?.TCP??{}))l?.TerminateTLS&&r.add(String(l.TerminateTLS).replace(/\.$/,"").toLowerCase())}let i=c=>{let l=sk(c.trim());return l&&l.port===t&&rk(l.host,r)?l:null},a=[];for(let{label:c,cfg:l}of s){for(let[g,d]of Object.entries(l?.Web??{}))for(let[f,p]of Object.entries(d?.Handlers??{})){if(p?.Proxy==null)continue;let u=i(String(p.Proxy));if(!u)continue;let h=f.replace(/\/+$/,"")||"/",y=h===Fp&&u.path===Fp;!tk.has(h)&&!y&&a.push(`${c}${g}${f}`)}for(let[g,d]of Object.entries(l?.TCP??{}))d?.TCPForward==null||!i(String(d.TCPForward))||a.push(`${c}TCP port ${g}`)}return a}function Ec(){try{return JSON.parse(qp("tailscale",["serve","status","--json"],{encoding:"utf-8",timeout:5e3})||"{}")}catch{return null}}function Bs(){try{return C().dashboard.port||4202}catch{return 4202}}ae();import{Command as to}from"commander";import w from"chalk";import ot from"prompts";import{readFileSync as Ro,writeFileSync as Vn,existsSync as Fe,mkdirSync as eo,cpSync as Rc,readdirSync as ik}from"fs";import{resolve as ne,join as Eo,dirname as ak}from"path";import{fileURLToPath as ck}from"url";function xe(){let e=ne(process.cwd(),"agentx.json");if(!Fe(e))throw new Error("No agentx.json found. Run: agentx init");return JSON.parse(Ro(e,"utf-8"))}function dt(e){return ge(t=>{for(let o of Object.keys(t))delete t[o];Object.assign(t,e)}).then(t=>{if(!t.success){console.error(w.red(` Config save failed: ${t.error}`));return}t.reloaded?console.error(w.dim(" (daemon hot-reloaded)")):t.reloadSkipped&&!/ECONNREFUSED|unreachable|fetch failed/i.test(t.reloadSkipped)&&console.error(w.dim(` (daemon reload skipped: ${t.reloadSkipped})`))})}var en=new to().name("agent").description("manage agents \u2014 add, list, remove");en.command("list").alias("ls").description("list configured agents").action(()=>{let e=xe();console.log();for(let[t,o]of Object.entries(e.agents||{})){let n=o.mentions?.join(", ")||"";console.log(` ${w.cyan(t)} \u2014 ${o.name} (${o.tier})`),console.log(w.dim(` workspace: ${o.workspace}`)),n&&console.log(w.dim(` mentions: ${n}`))}console.log()});en.command("add").description("add a new agent interactively").action(async()=>{let e=xe(),t=await ot([{type:"text",name:"id",message:"Agent ID (kebab-case)",validate:l=>/^[a-z0-9-]+$/.test(l)||"Use lowercase, numbers, hyphens"},{type:"text",name:"name",message:"Display name"},{type:"text",name:"workspace",message:"Workspace path",initial:ne(process.cwd(),"agents/")},{type:"select",name:"tier",message:"Execution tier",choices:[{title:"claude-code (subscription, full features)",value:"claude-code"},{title:"codex-cli (OpenAI Codex CLI)",value:"codex-cli"},{title:"opencode (configured OpenCode providers)",value:"opencode"},{title:"sdk (API key, programmatic)",value:"sdk"},{title:"orchestrator (any provider)",value:"orchestrator"}]},{type:"text",name:"model",message:"Model",initial:"claude-sonnet-4-6"},{type:"list",name:"mentions",message:"Mention patterns (comma-separated)",separator:","},{type:"number",name:"maxConcurrent",message:"Max concurrent tasks",initial:2},{type:"text",name:"systemPrompt",message:"System prompt"}]);if(!t.id)return;let o=t.workspace.endsWith(t.id)?t.workspace:Eo(t.workspace,t.id);eo(ne(o,".claude/skills"),{recursive:!0});let n={name:t.name,workspace:o,tier:t.tier,model:t.model,mentions:t.mentions?.map(l=>l.trim()).filter(Boolean)||[],maxConcurrent:t.maxConcurrent,systemPrompt:t.systemPrompt,permissionMode:"default"},{setupWorkspace:s}=await import("./workspace-setup-HAV4CQV7.js"),r=s(t.id,n,"19900",(...l)=>{console.log(w.dim(` ${l.join(" ")}`))}),i=ne(process.cwd(),"src/wiki/SKILL.md"),a=ne(ak(ck(import.meta.url)),"../wiki/SKILL.md"),c=Fe(i)?i:a;Fe(c)&&(eo(ne(o,".claude/skills/wiki"),{recursive:!0}),Rc(c,ne(o,".claude/skills/wiki/SKILL.md"))),e.agents=e.agents||{},e.agents[t.id]=n,await dt(e),console.log(w.green(`
|
|
42
42
|
Agent "${t.id}" added`)),console.log(w.dim(` Workspace: ${o}`)),console.log(w.dim(` Created: ${r.created.length} files (CLAUDE.md, AGENTS.md, settings, rules)`)),console.log()});en.command("remove <id>").alias("rm").description("remove an agent from config (keeps workspace)").action(async e=>{let t=xe();if(!t.agents?.[e]){console.log(w.red(` Agent "${e}" not found`));return}delete t.agents[e],await dt(t),console.log(w.green(` Agent "${e}" removed from config (workspace preserved)`))});en.command("capability <id>").alias("caps").description("set agent capability flags (intents, maxDelegationDepth, contextReferences, contextStrategy)").option("--intents <csv>","comma-separated intent allow-list (set to '-' to clear)").option("--max-delegation-depth <n>","max distinct upstream agents on the same subject before refusal (0 disables)").option("--context-references <bool>","render the deterministic [Verified References] block (true|false)").option("--context-strategy <name>","per-agent override: layered | planner").option("--max-execution-minutes <n>","wall-clock cap on a single Claude Code call (1\u2013240)").option("--show","just print the current capability fields").action(async(e,t)=>{let o=xe(),n=o.agents?.[e];if(n||(console.log(w.red(` Agent "${e}" not found`)),process.exit(1)),t.show){console.log(),console.log(w.bold(` ${e}`)),console.log(` intents ${n.intents&&n.intents.length?n.intents.join(", "):w.dim("(any)")}`),console.log(` maxDelegationDepth ${n.maxDelegationDepth??5}`),console.log(` contextReferences ${n.contextReferences?"on":w.dim("off")}`),console.log(` contextStrategy ${n.contextStrategy||w.dim("(global default)")}`),console.log(` maxExecutionMinutes ${n.maxExecutionMinutes??20}`),console.log();return}let s=[];if(t.intents!==void 0&&(t.intents==="-"||t.intents===""?(n.intents=[],s.push("intents cleared (permissive)")):(n.intents=String(t.intents).split(",").map(r=>r.trim()).filter(Boolean),s.push(`intents=[${n.intents.join(", ")}]`))),t.maxDelegationDepth!==void 0){let r=parseInt(t.maxDelegationDepth,10);(!Number.isFinite(r)||r<0||r>50)&&(console.log(w.red(" --max-delegation-depth must be 0..50")),process.exit(1)),n.maxDelegationDepth=r,s.push(`maxDelegationDepth=${r}`)}if(t.contextReferences!==void 0){let r=String(t.contextReferences).toLowerCase();["true","false","on","off","1","0"].includes(r)||(console.log(w.red(" --context-references must be true|false")),process.exit(1)),n.contextReferences=r==="true"||r==="on"||r==="1",s.push(`contextReferences=${n.contextReferences}`)}if(t.contextStrategy!==void 0&&(t.contextStrategy==="-"||t.contextStrategy===""?(delete n.contextStrategy,s.push("contextStrategy cleared (uses global default)")):["layered","planner"].includes(t.contextStrategy)?(n.contextStrategy=t.contextStrategy,s.push(`contextStrategy=${t.contextStrategy}`)):(console.log(w.red(" --context-strategy must be layered|planner (or '-' to clear)")),process.exit(1))),t.maxExecutionMinutes!==void 0){let r=parseInt(t.maxExecutionMinutes,10);(!Number.isFinite(r)||r<1||r>240)&&(console.log(w.red(" --max-execution-minutes must be 1..240")),process.exit(1)),n.maxExecutionMinutes=r,s.push(`maxExecutionMinutes=${r}`)}s.length===0&&(console.log(w.yellow(" no changes \u2014 pass at least one flag, or use --show")),process.exit(1)),await dt(o),console.log(w.green(` \u2713 ${e}: ${s.join(", ")}`)),console.log(w.dim(" Restart the daemon (or POST /reload) for the change to take effect."))});var Js=new to().name("integrations").alias("integ").description("manage per-agent third-party credentials (telegram-bot, hubspot, gitlab-user, gmail, etc.)");Js.command("list <agentId>").alias("ls").description("list integrations declared on an agent").option("--json","emit JSON").action(async(e,t)=>{let n=xe().agents?.[e];n||(console.log(w.red(` Agent "${e}" not found`)),process.exit(1));let s=n.integrations??[];if(t.json){console.log(JSON.stringify(s,null,2));return}if(!s.length){console.log(w.dim(`
|
|
43
43
|
${e}: no integrations declared. Add one with: agentx agent integrations add ${e}
|
|
44
|
-
`));return}console.log(),console.log(w.bold(` ${e}`));for(let r of s){let i=r.enabled!==!1,a=[],c=r.credentials??{};for(let[g,d]of Object.entries(c))typeof d=="string"&&/^[A-Z][A-Z0-9_]*$/.test(d)?a.push(process.env[d]?w.green(`${g}:${d}=set`):w.yellow(`${g}:${d}=UNSET`)):g==="auth"&&d==="keyring"?a.push(w.dim("auth:keyring")):g==="sessionDir"&&typeof d=="string"&&a.push(w.dim(`sessionDir:${d}`));let l=Object.entries(r.metadata??{}).map(([g,d])=>`${g}=${d}`).join(" ");console.log(` ${i?w.cyan(r.kind.padEnd(14)):w.dim(r.kind.padEnd(14))} ${w.bold(r.label)}`),a.length&&console.log(` ${a.join(" ")}`),l&&console.log(` ${w.dim(l)}`),i||console.log(` ${w.dim("(disabled)")}`)}console.log()});Js.command("add <agentId>").description("declare a new integration on an agent").option("--kind <name>","service kind (e.g. telegram-bot, hubspot, gitlab-user, gmail)").option("--label <text>","human-readable label (must be unique within (agent, kind))").option("--token-env <name>","env-var holding the secret (uppercase identifier)").option("--auth <mode>","alternative to --token-env: 'keyring' for OS keyring").option("--session-dir <path>","for whatsapp / mtproto: file-based session directory").option("--metadata <kvList>","comma-separated key=value pairs (e.g. 'username=alex,email=alex@example.com')").option("--no-prompt","fail instead of prompting for missing values").action(async(e,t)=>{let o=xe();o.agents?.[e]||(console.log(w.red(` Agent "${e}" not found`)),process.exit(1));let n=t.prompt!==!1,{INTEGRATION_KINDS:s}=await import("./config-VLH5DJTH.js"),r=String(t.kind||"").trim();!r&&n&&(r=((await ot({type:"autocomplete",name:"kind",message:"Service kind",choices:s.map(p=>({title:p,value:p}))})).kind||"").trim()),r||(console.log(w.red(" --kind is required")),process.exit(1));let i=String(t.label||"").trim();!i&&n&&(i=((await ot({type:"text",name:"label",message:'Label (e.g. "@cx_bot", "Acme CRM", "alex@example.com")'})).label||"").trim()),i||(console.log(w.red(" --label is required")),process.exit(1));let a=String(t.tokenEnv||"").trim(),c=String(t.auth||"").trim(),l=String(t.sessionDir||"").trim();if(!a&&!c&&!l&&n){let f=await ot({type:"select",name:"scheme",message:"Credential scheme",choices:[{title:"Env var (e.g. HUBSPOT_TOKEN_CX)",value:"tokenEnv"},{title:"OS keyring",value:"keyring"},{title:"File-based session (whatsapp / mtproto)",value:"sessionDir"}]});f.scheme==="tokenEnv"?a=((await ot({type:"text",name:"tokenEnv",message:"Env var name (uppercase)",initial:`${r.replace(/-/g,"_").toUpperCase()}_${e.replace(/-/g,"_").toUpperCase()}`})).tokenEnv||"").trim():f.scheme==="keyring"?c="keyring":f.scheme==="sessionDir"&&(l=((await ot({type:"text",name:"sessionDir",message:"Session directory path",initial:`.agentx/${r}-sessions/${e}`})).sessionDir||"").trim())}let g={};if(t.metadata)for(let f of String(t.metadata).split(",")){let[p,...u]=f.split("=");p&&(g[p.trim()]=u.join("=").trim())}else if(n){let f=await ot({type:"text",name:"metadata",message:"Metadata (k=v,k=v) \u2014 non-secret only, optional"});if(f.metadata)for(let p of String(f.metadata).split(",")){let[u,...h]=p.split("=");u&&(g[u.trim()]=h.join("=").trim())}}let d={};a&&(/^[A-Z][A-Z0-9_]*$/.test(a)||(console.log(w.red(` --token-env must be uppercase identifier (got "${a}")`)),process.exit(1)),d.tokenEnv=a),c==="keyring"&&(d.auth="keyring"),l&&(d.sessionDir=l),Object.keys(d).length||(console.log(w.red(" one of --token-env / --auth / --session-dir is required")),process.exit(1)),o.agents[e].integrations=o.agents[e].integrations??[],o.agents[e].integrations.find(f=>f.kind===r&&f.label===i)&&(console.log(w.red(` Integration (${r}, ${i}) already exists on ${e}`)),process.exit(1)),o.agents[e].integrations.push({kind:r,label:i,credentials:d,metadata:g,enabled:!0}),await dt(o),console.log(w.green(` \u2713 ${e}: added ${r} integration "${i}"`)),a&&!process.env[a]&&console.log(w.yellow(` \u26A0 env var ${a} is not set \u2014 set it on the daemon environment before use`))});Js.command("remove <agentId> <kindOrLabel>").alias("rm").description("remove an integration (matched by kind, label, or 'kind:label')").action(async(e,t)=>{let o=xe(),n=o.agents?.[e];n||(console.log(w.red(` Agent "${e}" not found`)),process.exit(1));let s=n.integrations??[],[r,i]=t.includes(":")?t.split(":",2):[t,""],a=s.findIndex(l=>l.kind===r&&(!i||l.label===i)||l.label===t);a===-1&&(console.log(w.red(` No integration matched "${t}"`)),process.exit(1));let c=s.splice(a,1)[0];await dt(o),console.log(w.green(` \u2713 removed ${c.kind} integration "${c.label}" from ${e}`))});Js.command("test <agentId> [kindOrLabel]").description("validate env vars are set for one or all integrations on an agent").action(async(e,t)=>{let n=xe().agents?.[e];n||(console.log(w.red(` Agent "${e}" not found`)),process.exit(1));let s=n.integrations??[],r=t?s.filter(a=>a.kind===t||a.label===t||`${a.kind}:${a.label}`===t):s;if(!r.length){console.log(w.yellow(" no integrations to test"));return}let i=0;console.log();for(let a of r){let c=a.credentials??{},l=[];for(let[g,d]of Object.entries(c))typeof d=="string"&&/^[A-Z][A-Z0-9_]*$/.test(d)&&!process.env[d]&&l.push(`${g}: env var ${d} is unset`);if(l.length){i+=1,console.log(` ${w.red("\u2717")} ${a.kind} "${a.label}"`);for(let g of l)console.log(` ${w.dim(g)}`)}else console.log(` ${w.green("\u2713")} ${a.kind} "${a.label}"`)}console.log(),i>0&&process.exit(1)});en.addCommand(Js);var mi=new to().name("channel").description("manage channels \u2014 add telegram/whatsapp/gitlab, list");mi.command("list").alias("ls").description("list configured channels and bindings").action(()=>{let e=xe();console.log();let t=e.channels?.telegram;if(t?.enabled){console.log(w.bold(" Telegram:"));for(let[s,r]of Object.entries(t.accounts||{}))console.log(` ${w.cyan(s)} -> agent: ${r.agentBinding}`)}else console.log(w.dim(" Telegram: disabled"));let o=e.channels?.whatsapp;if(o?.enabled){console.log(w.bold(" WhatsApp:")),console.log(` default agent: ${o.defaultAgent||"(none)"}`),console.log(` session: ${o.sessionDir||".agentx/whatsapp-sessions"}`);for(let s of o.routes||[]){let r=s.contact?`contact ${s.contact}`:`group "${s.group}"`;console.log(` ${r} -> agent: ${s.agent}`)}}else console.log(w.dim(" WhatsApp: disabled"));let n=e.channels?.gitlab;if(n?.enabled){console.log(w.bold(" GitLab:")),console.log(` host: ${n.host}`),console.log(` webhook: :${n.webhookPort}`);for(let s of n.routes||[])console.log(` ${s.project} -> agent: ${s.agent}`)}else console.log(w.dim(" GitLab: disabled"));console.log()});mi.command("add").description("add a channel (telegram, whatsapp, or gitlab)").action(async()=>{let e=xe(),t=Object.keys(e.agents||{});if(!t.length){console.log(w.red(" No agents configured. Run: agentx agent add"));return}let o=t.map(s=>({title:`${s} (${e.agents[s].name})`,value:s})),{channelType:n}=await ot({type:"select",name:"channelType",message:"Channel type",choices:[{title:"Telegram \u2014 bot via BotFather token",value:"telegram"},{title:"WhatsApp \u2014 link via QR code (self-chat or contacts)",value:"whatsapp"},{title:"GitLab \u2014 webhook for issues, MRs, comments, pipelines",value:"gitlab"}]});if(n){if(e.channels=e.channels||{},n==="telegram"){let s=await ot([{type:"text",name:"accountName",message:"Account name (e.g. 'default', 'devops')"},{type:"text",name:"token",message:"Bot token (from @BotFather)"},{type:"select",name:"agentBinding",message:"Bind to agent",choices:o}]);if(!s.accountName||!s.token)return;try{let i=await(await fetch(`https://api.telegram.org/bot${s.token}/getMe`)).json();if(!i.ok)throw new Error(i.description);console.log(w.green(` Bot verified: @${i.result.username}`));let a=`@${i.result.username}`,c=e.agents[s.agentBinding].mentions||[];c.includes(a)||(c.push(a,i.result.username),e.agents[s.agentBinding].mentions=c)}catch(r){console.log(w.red(` Invalid token: ${r.message}`));return}e.channels.telegram=e.channels.telegram||{enabled:!1,accounts:{},policy:{dm:"pair",group:"mention-required"}},e.channels.telegram.enabled=!0,e.channels.telegram.accounts[s.accountName]={token:s.token,agentBinding:s.agentBinding},await dt(e),console.log(w.green(` Telegram "${s.accountName}" added -> ${s.agentBinding}`))}if(n==="whatsapp"){let s=await ot([{type:"select",name:"defaultAgent",message:"Default agent for WhatsApp messages",choices:o},{type:"text",name:"sessionDir",message:"Session directory",initial:".agentx/whatsapp-sessions"},{type:"confirm",name:"addRoute",message:"Add a contact/group route?",initial:!0}]);if(!s.defaultAgent)return;let r=[],i=s.addRoute;for(;i;){let a=await ot([{type:"select",name:"type",message:"Route type",choices:[{title:"Contact (phone number)",value:"contact"},{title:"Group (name match)",value:"group"}]},{type:"text",name:"value",message:c=>c==="contact"?"Phone number (e.g. +10000000000)":"Group name (partial match)"},{type:"select",name:"agent",message:"Route to agent",choices:o},{type:"confirm",name:"more",message:"Add another route?",initial:!1}]);a.value&&a.agent&&r.push({[a.type]:a.value,agent:a.agent}),i=a.more}e.channels.whatsapp={enabled:!0,sessionDir:s.sessionDir,defaultAgent:s.defaultAgent,routes:r},await dt(e),eo(ne(process.cwd(),s.sessionDir),{recursive:!0}),console.log(w.green(` WhatsApp enabled (${r.length} routes, default: ${s.defaultAgent})`)),console.log(w.dim(" Start daemon to scan QR code: agentx daemon start"))}if(n==="gitlab"){let s=await ot([{type:"text",name:"host",message:"GitLab instance URL",initial:"https://gitlab.example.com"},{type:"text",name:"token",message:"GitLab API token (PRIVATE-TOKEN)"},{type:"number",name:"webhookPort",message:"Webhook listen port",initial:18810},{type:"text",name:"webhookSecret",message:"Webhook secret (X-Gitlab-Token, optional)"},{type:"confirm",name:"addRoute",message:"Add a project route?",initial:!0}]);if(!s.token)return;let r=[],i=s.addRoute;for(;i;){let a=await ot([{type:"text",name:"project",message:"GitLab project path (e.g. group/project, or * for default)"},{type:"select",name:"agent",message:"Route to agent",choices:o},{type:"confirm",name:"more",message:"Add another route?",initial:!1}]);a.project&&a.agent&&r.push({project:a.project,agent:a.agent}),i=a.more}e.channels.gitlab={enabled:!0,host:s.host,token:s.token,webhookPort:s.webhookPort,webhookSecret:s.webhookSecret||void 0,routes:r},await dt(e),console.log(w.green(` GitLab enabled (${r.length} routes, webhook :${s.webhookPort})`)),console.log(w.dim(` Add webhook in GitLab: ${s.host}/<project>/-/hooks`)),console.log(w.dim(` URL: http://your-server:${s.webhookPort}/`)),s.webhookSecret&&console.log(w.dim(` Secret token: ${s.webhookSecret}`)),console.log(w.dim(" Events: Comments, Issues, Merge requests, Pipeline"))}console.log(w.dim(" Restart daemon to apply: agentx daemon stop && agentx daemon start")),console.log()}});var Xn=new to().name("cron").description("manage cron jobs \u2014 add, list, enable, disable");Xn.command("list").alias("ls").description("list cron jobs").action(()=>{let e=xe();console.log();for(let[t,o]of Object.entries(e.crons||{})){let n=o.enabled?w.green("\u25CF"):w.dim("\u25CB");console.log(` ${n} ${w.cyan(t)} \u2014 ${o.schedule} (${o.timezone})`),console.log(w.dim(` agent: ${o.agent} | timeout: ${o.timeout}s`))}Object.keys(e.crons||{}).length||console.log(w.dim(" No cron jobs configured")),console.log()});Xn.command("add").description("add a cron job").action(async()=>{let e=xe(),t=Object.keys(e.agents||{}),o=await ot([{type:"text",name:"id",message:"Job ID (kebab-case)"},{type:"text",name:"schedule",message:"Cron expression (e.g. '0 9 * * *')"},{type:"text",name:"timezone",message:"Timezone",initial:"UTC"},{type:"select",name:"agent",message:"Agent",choices:t.map(n=>({title:n,value:n}))},{type:"text",name:"prompt",message:"Task prompt"},{type:"number",name:"timeout",message:"Timeout (seconds)",initial:600},{type:"confirm",name:"enabled",message:"Enable now?",initial:!1}]);!o.id||!o.schedule||(e.crons=e.crons||{},e.crons[o.id]={enabled:o.enabled,schedule:o.schedule,timezone:o.timezone,agent:o.agent,prompt:o.prompt,timeout:o.timeout,onError:["log"]},await dt(e),console.log(w.green(` Cron "${o.id}" added (${o.enabled?"enabled":"disabled"})`)),console.log())});Xn.command("enable <id>").description("enable a cron job").action(async e=>{let t=xe();if(!t.crons?.[e]){console.log(w.red(` Cron "${e}" not found`));return}if(t.crons[e].approval?.action==="create"){console.log(w.yellow(` Cron "${e}" is awaiting approval \u2014 run: agentx schedule approve ${e}`));return}t.crons[e].enabled=!0,await dt(t),console.log(w.green(` Cron "${e}" enabled`))});Xn.command("disable <id>").description("disable a cron job").action(async e=>{let t=xe();if(!t.crons?.[e]){console.log(w.red(` Cron "${e}" not found`));return}t.crons[e].enabled=!1,await dt(t),console.log(w.green(` Cron "${e}" disabled`))});var Et=new to().name("mesh").description("manage mesh peers \u2014 add, list, remove");Et.command("list").alias("ls").description("list mesh peers").action(async()=>{let e=xe();if(console.log(),!e.mesh?.enabled)console.log(w.dim(" Mesh: disabled"));else{console.log(w.bold(" Mesh peers:"));for(let t of e.mesh.peers||[]){let o=w.red("unreachable");try{let s=await(await fetch(`${t.url}/health`,{signal:AbortSignal.timeout(3e3)})).json();o=w.green(`healthy \u2014 ${s.agents?.length||0} agents`)}catch{}console.log(` ${w.cyan(t.name)} ${w.dim(t.url)} ${o}`)}}console.log()});Et.command("add").description("add a mesh peer (another agentx server)").action(async()=>{let e=xe(),t=await ot([{type:"text",name:"name",message:"Peer name (e.g. 'server-2')"},{type:"text",name:"url",message:"Peer URL (e.g. 'http://100.64.0.11:18800')"},{type:"text",name:"token",message:"Auth token (optional, press enter to skip)"}]);if(!(!t.name||!t.url)){try{let n=await(await fetch(`${t.url}/.well-known/agent-card.json`,{signal:AbortSignal.timeout(5e3)})).json();console.log(w.green(` Connected: ${n.name} (${n.skills?.length||0} agents)`))}catch{console.log(w.yellow(" Could not reach peer (adding anyway)"))}e.mesh=e.mesh||{enabled:!1,peers:[],discovery:"static",healthCheck:{interval:60,timeout:10}},e.mesh.enabled=!0,e.mesh.peers=e.mesh.peers||[],e.mesh.peers.push({url:t.url,name:t.name,...t.token?{token:t.token}:{}}),await dt(e),console.log(w.green(` Peer "${t.name}" added`)),console.log()}});Et.command("remove <name>").alias("rm").description("remove a mesh peer").action(async e=>{let t=xe();t.mesh?.peers&&(t.mesh.peers=t.mesh.peers.filter(o=>o.name!==e),t.mesh.peers.length===0&&(t.mesh.enabled=!1),await dt(t),console.log(w.green(` Peer "${e}" removed`)))});Et.command("health").description("tune mesh peer health checks (interval + timeout, in seconds)").option("--interval <s>","seconds between health probes (default 60)").option("--timeout <s>","per-probe timeout in seconds (default 10)").option("--show","just print the current values").action(async e=>{let t=xe();if(t.mesh=t.mesh||{},t.mesh.healthCheck=t.mesh.healthCheck||{},e.show){console.log(` interval ${t.mesh.healthCheck.interval??60}s`),console.log(` timeout ${t.mesh.healthCheck.timeout??10}s`);return}let o=[];if(e.interval!==void 0){let n=parseInt(e.interval,10);(!Number.isFinite(n)||n<5||n>3600)&&(console.log(w.red(" --interval must be 5..3600 seconds")),process.exit(1)),t.mesh.healthCheck.interval=n,o.push(`interval=${n}s`)}if(e.timeout!==void 0){let n=parseInt(e.timeout,10);(!Number.isFinite(n)||n<1||n>60)&&(console.log(w.red(" --timeout must be 1..60 seconds")),process.exit(1)),t.mesh.healthCheck.timeout=n,o.push(`timeout=${n}s`)}o.length===0&&(console.log(w.yellow(" no changes \u2014 pass --interval and/or --timeout, or --show")),process.exit(1)),await dt(t),console.log(w.green(` \u2713 mesh.healthCheck: ${o.join(", ")}`)),console.log(w.dim(" Restart the daemon for the new cadence to take effect."))});Et.command("announce <text...>").description("send a short note to every machine in the mesh (shown in event feeds)").option("--by <name>","who the note is from (an agent id or a person's name)").option("--node <url>","daemon to publish on (default: dashboard.daemonUrl, else this machine on port 18800)").option("--token <token>","mesh token, needed only when --node is another machine (default: MESH_TOKEN)").action(async(e,t)=>{let o=t.node||"";if(!o)try{o=C().dashboard?.daemonUrl||""}catch{}o=(o||"http://localhost:18800").replace(/\/+$/,"");let n=t.token||process.env.MESH_TOKEN||"",s={"Content-Type":"application/json"};n&&(s.Authorization=`Bearer ${n}`);try{let r=await fetch(`${o}/mesh/announce`,{method:"POST",headers:s,body:JSON.stringify({text:e.join(" "),...t.by?{by:t.by}:{}}),signal:AbortSignal.timeout(1e4)}),i=await r.json().catch(()=>({}));r.ok||(console.log(w.red(` Announcement refused (${r.status}): ${i.error||"unknown error"}`)),process.exit(1)),console.log(w.green(` \u2713 Announced from ${i.event?.node??"this machine"}`)+w.dim(` (event ${i.event?.id??"?"})`))}catch(r){console.log(w.red(` Could not reach the daemon at ${o}: ${r?.message||r}`)),process.exit(1)}});var Bp=e=>{let t=e||"";if(!t)try{t=C().dashboard?.daemonUrl||""}catch{}return(t||"http://localhost:18800").replace(/\/+$/,"")};async function pi(e,t,o={}){let n=Bp(),s={"Content-Type":"application/json"};process.env.MESH_TOKEN&&(s.Authorization=`Bearer ${process.env.MESH_TOKEN}`);try{let r=await fetch(`${n}/mesh/guests/${encodeURIComponent(e)}/${t}`,{method:"POST",headers:s,body:JSON.stringify(o),signal:AbortSignal.timeout(15e3)}),i=await r.json().catch(()=>({}));r.ok||(console.log(w.red(` ${i.error||`HTTP ${r.status}`}`)),process.exit(1));let a=i.grant;console.log(w.green(` \u2713 ${a?.name??e}: ${a?.state}${i.stopped?`, ${i.stopped} running turn(s) stopped`:""}`))}catch(r){console.log(w.red(` Could not reach the daemon at ${n}: ${r?.message||r}. The daemon must be running: it stops the guest's running turns.`)),process.exit(1)}}var tn=Et.command("guests").description("other organisations let into part of this mesh: open, watch, pause, widen, narrow, end");tn.command("invite").description("open a grant to a guest mesh and print the one-time code it joins with").requiredOption("--name <name>",'what you call it, e.g. "Support session for company X"').requiredOption("--guest <name>","the other organisation's name").requiredOption("--agent <id>","the agent of this node that works for them").option("--folders <list>","folders that agent may touch for them, comma-separated").option("--skills <list>","skills it may use for them, comma-separated").option("--commands <list>","commands it may run for them, comma-separated").option("--level <level>","report (read only) | propose (no merge, deploy or delete) | act","propose").option("--days <n>","how long the grant lasts (1 to 365)","7").option("--url <origin>","the address the guest reaches this node on (default: dashboard.daemonUrl)").action(async e=>{let{TokenStore:t}=await import("./token-store-AY6OBDVI.js"),{PairCodeStore:o,formatCode:n}=await import("./pair-codes-EYGELBPD.js"),{GuestStore:s}=await import("./store-7W2AP5H4.js"),{inviteGuest:r}=await import("./grants-HSLB62EG.js"),i=C(),a=r({tokens:new t,codes:new o,guests:new s,hasAgent:l=>!!i.agents[l]},{name:e.name,guest:e.guest,agentId:e.agent,folders:e.folders?.split(","),skills:e.skills?.split(","),commands:e.commands?.split(","),level:e.level,days:Number(e.days)});a.ok||(console.log(w.red(` ${a.error}`)),process.exit(1));let c=(e.url||Bp()).replace(/\/+$/,"");console.log(),console.log(` Grant ${w.bold(a.grant.name)} for ${w.bold(a.grant.guest)}: ${a.grant.agentId}, ${a.grant.level}, until ${a.grant.expiresAt.slice(0,10)}`),console.log(w.dim(` id ${a.grant.id} \xB7 folders: ${a.grant.folders.join(", ")||"none"} \xB7 skills: ${a.grant.skills.join(", ")||"none"} \xB7 commands: ${a.grant.commands.join(", ")||"none"}`)),console.log(),console.log(" Send the guest this address and code. The code works once, for 10 minutes:"),console.log(),console.log(` ${w.cyan(c)}`),console.log(` ${w.bold(n(a.code))}`),console.log(),console.log(` They run: ${w.cyan(`agentx mesh join ${c} --code ${n(a.code)} --name <what-they-call-you>`)}`),console.log(` When they join, a card "Guest mesh: ${a.grant.guest} wants to join" asks you to approve it.`),console.log()});tn.command("list",{isDefault:!0}).alias("ls").description("every grant: state, guest, agent, level, end date, usage").option("--json","machine-readable output").action(async e=>{let{GuestStore:t}=await import("./store-7W2AP5H4.js"),o=new t().grants().sort((n,s)=>s.createdAt.localeCompare(n.createdAt));if(e.json){console.log(JSON.stringify(o,null,2));return}if(!o.length){console.log(w.dim(`
|
|
44
|
+
`));return}console.log(),console.log(w.bold(` ${e}`));for(let r of s){let i=r.enabled!==!1,a=[],c=r.credentials??{};for(let[g,d]of Object.entries(c))typeof d=="string"&&/^[A-Z][A-Z0-9_]*$/.test(d)?a.push(process.env[d]?w.green(`${g}:${d}=set`):w.yellow(`${g}:${d}=UNSET`)):g==="auth"&&d==="keyring"?a.push(w.dim("auth:keyring")):g==="sessionDir"&&typeof d=="string"&&a.push(w.dim(`sessionDir:${d}`));let l=Object.entries(r.metadata??{}).map(([g,d])=>`${g}=${d}`).join(" ");console.log(` ${i?w.cyan(r.kind.padEnd(14)):w.dim(r.kind.padEnd(14))} ${w.bold(r.label)}`),a.length&&console.log(` ${a.join(" ")}`),l&&console.log(` ${w.dim(l)}`),i||console.log(` ${w.dim("(disabled)")}`)}console.log()});Js.command("add <agentId>").description("declare a new integration on an agent").option("--kind <name>","service kind (e.g. telegram-bot, hubspot, gitlab-user, gmail)").option("--label <text>","human-readable label (must be unique within (agent, kind))").option("--token-env <name>","env-var holding the secret (uppercase identifier)").option("--auth <mode>","alternative to --token-env: 'keyring' for OS keyring").option("--session-dir <path>","for whatsapp / mtproto: file-based session directory").option("--metadata <kvList>","comma-separated key=value pairs (e.g. 'username=alex,email=alex@example.com')").option("--no-prompt","fail instead of prompting for missing values").action(async(e,t)=>{let o=xe();o.agents?.[e]||(console.log(w.red(` Agent "${e}" not found`)),process.exit(1));let n=t.prompt!==!1,{INTEGRATION_KINDS:s}=await import("./config-VLH5DJTH.js"),r=String(t.kind||"").trim();!r&&n&&(r=((await ot({type:"autocomplete",name:"kind",message:"Service kind",choices:s.map(p=>({title:p,value:p}))})).kind||"").trim()),r||(console.log(w.red(" --kind is required")),process.exit(1));let i=String(t.label||"").trim();!i&&n&&(i=((await ot({type:"text",name:"label",message:'Label (e.g. "@cx_bot", "Acme CRM", "alex@example.com")'})).label||"").trim()),i||(console.log(w.red(" --label is required")),process.exit(1));let a=String(t.tokenEnv||"").trim(),c=String(t.auth||"").trim(),l=String(t.sessionDir||"").trim();if(!a&&!c&&!l&&n){let f=await ot({type:"select",name:"scheme",message:"Credential scheme",choices:[{title:"Env var (e.g. HUBSPOT_TOKEN_CX)",value:"tokenEnv"},{title:"OS keyring",value:"keyring"},{title:"File-based session (whatsapp / mtproto)",value:"sessionDir"}]});f.scheme==="tokenEnv"?a=((await ot({type:"text",name:"tokenEnv",message:"Env var name (uppercase)",initial:`${r.replace(/-/g,"_").toUpperCase()}_${e.replace(/-/g,"_").toUpperCase()}`})).tokenEnv||"").trim():f.scheme==="keyring"?c="keyring":f.scheme==="sessionDir"&&(l=((await ot({type:"text",name:"sessionDir",message:"Session directory path",initial:`.agentx/${r}-sessions/${e}`})).sessionDir||"").trim())}let g={};if(t.metadata)for(let f of String(t.metadata).split(",")){let[p,...u]=f.split("=");p&&(g[p.trim()]=u.join("=").trim())}else if(n){let f=await ot({type:"text",name:"metadata",message:"Metadata (k=v,k=v) \u2014 non-secret only, optional"});if(f.metadata)for(let p of String(f.metadata).split(",")){let[u,...h]=p.split("=");u&&(g[u.trim()]=h.join("=").trim())}}let d={};a&&(/^[A-Z][A-Z0-9_]*$/.test(a)||(console.log(w.red(` --token-env must be uppercase identifier (got "${a}")`)),process.exit(1)),d.tokenEnv=a),c==="keyring"&&(d.auth="keyring"),l&&(d.sessionDir=l),Object.keys(d).length||(console.log(w.red(" one of --token-env / --auth / --session-dir is required")),process.exit(1)),o.agents[e].integrations=o.agents[e].integrations??[],o.agents[e].integrations.find(f=>f.kind===r&&f.label===i)&&(console.log(w.red(` Integration (${r}, ${i}) already exists on ${e}`)),process.exit(1)),o.agents[e].integrations.push({kind:r,label:i,credentials:d,metadata:g,enabled:!0}),await dt(o),console.log(w.green(` \u2713 ${e}: added ${r} integration "${i}"`)),a&&!process.env[a]&&console.log(w.yellow(` \u26A0 env var ${a} is not set \u2014 set it on the daemon environment before use`))});Js.command("remove <agentId> <kindOrLabel>").alias("rm").description("remove an integration (matched by kind, label, or 'kind:label')").action(async(e,t)=>{let o=xe(),n=o.agents?.[e];n||(console.log(w.red(` Agent "${e}" not found`)),process.exit(1));let s=n.integrations??[],[r,i]=t.includes(":")?t.split(":",2):[t,""],a=s.findIndex(l=>l.kind===r&&(!i||l.label===i)||l.label===t);a===-1&&(console.log(w.red(` No integration matched "${t}"`)),process.exit(1));let c=s.splice(a,1)[0];await dt(o),console.log(w.green(` \u2713 removed ${c.kind} integration "${c.label}" from ${e}`))});Js.command("test <agentId> [kindOrLabel]").description("validate env vars are set for one or all integrations on an agent").action(async(e,t)=>{let n=xe().agents?.[e];n||(console.log(w.red(` Agent "${e}" not found`)),process.exit(1));let s=n.integrations??[],r=t?s.filter(a=>a.kind===t||a.label===t||`${a.kind}:${a.label}`===t):s;if(!r.length){console.log(w.yellow(" no integrations to test"));return}let i=0;console.log();for(let a of r){let c=a.credentials??{},l=[];for(let[g,d]of Object.entries(c))typeof d=="string"&&/^[A-Z][A-Z0-9_]*$/.test(d)&&!process.env[d]&&l.push(`${g}: env var ${d} is unset`);if(l.length){i+=1,console.log(` ${w.red("\u2717")} ${a.kind} "${a.label}"`);for(let g of l)console.log(` ${w.dim(g)}`)}else console.log(` ${w.green("\u2713")} ${a.kind} "${a.label}"`)}console.log(),i>0&&process.exit(1)});en.addCommand(Js);var mi=new to().name("channel").description("manage channels \u2014 add telegram/whatsapp/gitlab, list");mi.command("list").alias("ls").description("list configured channels and bindings").action(()=>{let e=xe();console.log();let t=e.channels?.telegram;if(t?.enabled){console.log(w.bold(" Telegram:"));for(let[s,r]of Object.entries(t.accounts||{}))console.log(` ${w.cyan(s)} -> agent: ${r.agentBinding}`)}else console.log(w.dim(" Telegram: disabled"));let o=e.channels?.whatsapp;if(o?.enabled){console.log(w.bold(" WhatsApp:")),console.log(` default agent: ${o.defaultAgent||"(none)"}`),console.log(` session: ${o.sessionDir||".agentx/whatsapp-sessions"}`);for(let s of o.routes||[]){let r=s.contact?`contact ${s.contact}`:`group "${s.group}"`;console.log(` ${r} -> agent: ${s.agent}`)}}else console.log(w.dim(" WhatsApp: disabled"));let n=e.channels?.gitlab;if(n?.enabled){console.log(w.bold(" GitLab:")),console.log(` host: ${n.host}`),console.log(` webhook: :${n.webhookPort}`);for(let s of n.routes||[])console.log(` ${s.project} -> agent: ${s.agent}`)}else console.log(w.dim(" GitLab: disabled"));console.log()});mi.command("add").description("add a channel (telegram, whatsapp, or gitlab)").action(async()=>{let e=xe(),t=Object.keys(e.agents||{});if(!t.length){console.log(w.red(" No agents configured. Run: agentx agent add"));return}let o=t.map(s=>({title:`${s} (${e.agents[s].name})`,value:s})),{channelType:n}=await ot({type:"select",name:"channelType",message:"Channel type",choices:[{title:"Telegram \u2014 bot via BotFather token",value:"telegram"},{title:"WhatsApp \u2014 link via QR code (self-chat or contacts)",value:"whatsapp"},{title:"GitLab \u2014 webhook for issues, MRs, comments, pipelines",value:"gitlab"}]});if(n){if(e.channels=e.channels||{},n==="telegram"){let s=await ot([{type:"text",name:"accountName",message:"Account name (e.g. 'default', 'devops')"},{type:"text",name:"token",message:"Bot token (from @BotFather)"},{type:"select",name:"agentBinding",message:"Bind to agent",choices:o}]);if(!s.accountName||!s.token)return;try{let i=await(await fetch(`https://api.telegram.org/bot${s.token}/getMe`)).json();if(!i.ok)throw new Error(i.description);console.log(w.green(` Bot verified: @${i.result.username}`));let a=`@${i.result.username}`,c=e.agents[s.agentBinding].mentions||[];c.includes(a)||(c.push(a,i.result.username),e.agents[s.agentBinding].mentions=c)}catch(r){console.log(w.red(` Invalid token: ${r.message}`));return}e.channels.telegram=e.channels.telegram||{enabled:!1,accounts:{},policy:{dm:"pair",group:"mention-required"}},e.channels.telegram.enabled=!0,e.channels.telegram.accounts[s.accountName]={token:s.token,agentBinding:s.agentBinding},await dt(e),console.log(w.green(` Telegram "${s.accountName}" added -> ${s.agentBinding}`))}if(n==="whatsapp"){let s=await ot([{type:"select",name:"defaultAgent",message:"Default agent for WhatsApp messages",choices:o},{type:"text",name:"sessionDir",message:"Session directory",initial:".agentx/whatsapp-sessions"},{type:"confirm",name:"addRoute",message:"Add a contact/group route?",initial:!0}]);if(!s.defaultAgent)return;let r=[],i=s.addRoute;for(;i;){let a=await ot([{type:"select",name:"type",message:"Route type",choices:[{title:"Contact (phone number)",value:"contact"},{title:"Group (name match)",value:"group"}]},{type:"text",name:"value",message:c=>c==="contact"?"Phone number (e.g. +10000000000)":"Group name (partial match)"},{type:"select",name:"agent",message:"Route to agent",choices:o},{type:"confirm",name:"more",message:"Add another route?",initial:!1}]);a.value&&a.agent&&r.push({[a.type]:a.value,agent:a.agent}),i=a.more}e.channels.whatsapp={enabled:!0,sessionDir:s.sessionDir,defaultAgent:s.defaultAgent,routes:r},await dt(e),eo(ne(process.cwd(),s.sessionDir),{recursive:!0}),console.log(w.green(` WhatsApp enabled (${r.length} routes, default: ${s.defaultAgent})`)),console.log(w.dim(" Start daemon to scan QR code: agentx daemon start"))}if(n==="gitlab"){let s=await ot([{type:"text",name:"host",message:"GitLab instance URL",initial:"https://gitlab.example.com"},{type:"text",name:"token",message:"GitLab API token (PRIVATE-TOKEN)"},{type:"number",name:"webhookPort",message:"Webhook listen port",initial:18810},{type:"text",name:"webhookSecret",message:"Webhook secret (X-Gitlab-Token, optional)"},{type:"confirm",name:"addRoute",message:"Add a project route?",initial:!0}]);if(!s.token)return;let r=[],i=s.addRoute;for(;i;){let a=await ot([{type:"text",name:"project",message:"GitLab project path (e.g. group/project, or * for default)"},{type:"select",name:"agent",message:"Route to agent",choices:o},{type:"confirm",name:"more",message:"Add another route?",initial:!1}]);a.project&&a.agent&&r.push({project:a.project,agent:a.agent}),i=a.more}e.channels.gitlab={enabled:!0,host:s.host,token:s.token,webhookPort:s.webhookPort,webhookSecret:s.webhookSecret||void 0,routes:r},await dt(e),console.log(w.green(` GitLab enabled (${r.length} routes, webhook :${s.webhookPort})`)),console.log(w.dim(` Add webhook in GitLab: ${s.host}/<project>/-/hooks`)),console.log(w.dim(` URL: http://your-server:${s.webhookPort}/`)),s.webhookSecret&&console.log(w.dim(` Secret token: ${s.webhookSecret}`)),console.log(w.dim(" Events: Comments, Issues, Merge requests, Pipeline"))}console.log(w.dim(" Restart daemon to apply: agentx daemon stop && agentx daemon start")),console.log()}});var Xn=new to().name("cron").description("manage cron jobs \u2014 add, list, enable, disable");Xn.command("list").alias("ls").description("list cron jobs").action(()=>{let e=xe();console.log();for(let[t,o]of Object.entries(e.crons||{})){let n=o.enabled?w.green("\u25CF"):w.dim("\u25CB");console.log(` ${n} ${w.cyan(t)} \u2014 ${o.schedule} (${o.timezone})`),console.log(w.dim(` agent: ${o.agent} | timeout: ${o.timeout}s`))}Object.keys(e.crons||{}).length||console.log(w.dim(" No cron jobs configured")),console.log()});Xn.command("add").description("add a cron job").action(async()=>{let e=xe(),t=Object.keys(e.agents||{}),o=await ot([{type:"text",name:"id",message:"Job ID (kebab-case)"},{type:"text",name:"schedule",message:"Cron expression (e.g. '0 9 * * *')"},{type:"text",name:"timezone",message:"Timezone",initial:"UTC"},{type:"select",name:"agent",message:"Agent",choices:t.map(n=>({title:n,value:n}))},{type:"text",name:"prompt",message:"Task prompt"},{type:"number",name:"timeout",message:"Timeout (seconds)",initial:600},{type:"confirm",name:"enabled",message:"Enable now?",initial:!1}]);!o.id||!o.schedule||(e.crons=e.crons||{},e.crons[o.id]={enabled:o.enabled,schedule:o.schedule,timezone:o.timezone,agent:o.agent,prompt:o.prompt,timeout:o.timeout,onError:["log"]},await dt(e),console.log(w.green(` Cron "${o.id}" added (${o.enabled?"enabled":"disabled"})`)),console.log())});Xn.command("enable <id>").description("enable a cron job").action(async e=>{let t=xe();if(!t.crons?.[e]){console.log(w.red(` Cron "${e}" not found`));return}if(t.crons[e].approval?.action==="create"){console.log(w.yellow(` Cron "${e}" is awaiting approval \u2014 run: agentx schedule approve ${e}`));return}t.crons[e].enabled=!0,await dt(t),console.log(w.green(` Cron "${e}" enabled`))});Xn.command("disable <id>").description("disable a cron job").action(async e=>{let t=xe();if(!t.crons?.[e]){console.log(w.red(` Cron "${e}" not found`));return}t.crons[e].enabled=!1,await dt(t),console.log(w.green(` Cron "${e}" disabled`))});var Et=new to().name("mesh").description("manage mesh peers \u2014 add, list, remove");Et.command("list").alias("ls").description("list mesh peers").action(async()=>{let e=xe();if(console.log(),!e.mesh?.enabled)console.log(w.dim(" Mesh: disabled"));else{console.log(w.bold(" Mesh peers:"));for(let t of e.mesh.peers||[]){let o=w.red("unreachable");try{let s=await(await fetch(`${t.url}/health`,{signal:AbortSignal.timeout(3e3)})).json();o=w.green(`healthy \u2014 ${s.agents?.length||0} agents`)}catch{}console.log(` ${w.cyan(t.name)} ${w.dim(t.url)} ${o}`)}}console.log()});Et.command("add").description("add a mesh peer (another agentx server)").action(async()=>{let e=xe(),t=await ot([{type:"text",name:"name",message:"Peer name (e.g. 'server-2')"},{type:"text",name:"url",message:"Peer URL (e.g. 'http://100.64.0.11:18800')"},{type:"text",name:"token",message:"Auth token (optional, press enter to skip)"}]);if(!(!t.name||!t.url)){try{let n=await(await fetch(`${t.url}/.well-known/agent-card.json`,{signal:AbortSignal.timeout(5e3)})).json();console.log(w.green(` Connected: ${n.name} (${n.skills?.length||0} agents)`))}catch{console.log(w.yellow(" Could not reach peer (adding anyway)"))}e.mesh=e.mesh||{enabled:!1,peers:[],discovery:"static",healthCheck:{interval:60,timeout:10}},e.mesh.enabled=!0,e.mesh.peers=e.mesh.peers||[],e.mesh.peers.push({url:t.url,name:t.name,...t.token?{token:t.token}:{}}),await dt(e),console.log(w.green(` Peer "${t.name}" added`)),console.log()}});Et.command("remove <name>").alias("rm").description("remove a mesh peer").action(async e=>{let t=xe();t.mesh?.peers&&(t.mesh.peers=t.mesh.peers.filter(o=>o.name!==e),t.mesh.peers.length===0&&(t.mesh.enabled=!1),await dt(t),console.log(w.green(` Peer "${e}" removed`)))});Et.command("health").description("tune mesh peer health checks (interval + timeout, in seconds)").option("--interval <s>","seconds between health probes (default 60)").option("--timeout <s>","per-probe timeout in seconds (default 10)").option("--show","just print the current values").action(async e=>{let t=xe();if(t.mesh=t.mesh||{},t.mesh.healthCheck=t.mesh.healthCheck||{},e.show){console.log(` interval ${t.mesh.healthCheck.interval??60}s`),console.log(` timeout ${t.mesh.healthCheck.timeout??10}s`);return}let o=[];if(e.interval!==void 0){let n=parseInt(e.interval,10);(!Number.isFinite(n)||n<5||n>3600)&&(console.log(w.red(" --interval must be 5..3600 seconds")),process.exit(1)),t.mesh.healthCheck.interval=n,o.push(`interval=${n}s`)}if(e.timeout!==void 0){let n=parseInt(e.timeout,10);(!Number.isFinite(n)||n<1||n>60)&&(console.log(w.red(" --timeout must be 1..60 seconds")),process.exit(1)),t.mesh.healthCheck.timeout=n,o.push(`timeout=${n}s`)}o.length===0&&(console.log(w.yellow(" no changes \u2014 pass --interval and/or --timeout, or --show")),process.exit(1)),await dt(t),console.log(w.green(` \u2713 mesh.healthCheck: ${o.join(", ")}`)),console.log(w.dim(" Restart the daemon for the new cadence to take effect."))});Et.command("announce <text...>").description("send a short note to every machine in the mesh (shown in event feeds)").option("--by <name>","who the note is from (an agent id or a person's name)").option("--node <url>","daemon to publish on (default: dashboard.daemonUrl, else this machine on port 18800)").option("--token <token>","mesh token, needed only when --node is another machine (default: MESH_TOKEN)").action(async(e,t)=>{let o=t.node||"";if(!o)try{o=C().dashboard?.daemonUrl||""}catch{}o=(o||"http://localhost:18800").replace(/\/+$/,"");let n=t.token||process.env.MESH_TOKEN||"",s={"Content-Type":"application/json"};n&&(s.Authorization=`Bearer ${n}`);try{let r=await fetch(`${o}/mesh/announce`,{method:"POST",headers:s,body:JSON.stringify({text:e.join(" "),...t.by?{by:t.by}:{}}),signal:AbortSignal.timeout(1e4)}),i=await r.json().catch(()=>({}));r.ok||(console.log(w.red(` Announcement refused (${r.status}): ${i.error||"unknown error"}`)),process.exit(1)),console.log(w.green(` \u2713 Announced from ${i.event?.node??"this machine"}`)+w.dim(` (event ${i.event?.id??"?"})`))}catch(r){console.log(w.red(` Could not reach the daemon at ${o}: ${r?.message||r}`)),process.exit(1)}});var Bp=e=>{let t=e||"";if(!t)try{t=C().dashboard?.daemonUrl||""}catch{}return(t||"http://localhost:18800").replace(/\/+$/,"")};async function pi(e,t,o={}){let n=Bp(),s={"Content-Type":"application/json"};process.env.MESH_TOKEN&&(s.Authorization=`Bearer ${process.env.MESH_TOKEN}`);try{let r=await fetch(`${n}/mesh/guests/${encodeURIComponent(e)}/${t}`,{method:"POST",headers:s,body:JSON.stringify(o),signal:AbortSignal.timeout(15e3)}),i=await r.json().catch(()=>({}));r.ok||(console.log(w.red(` ${i.error||`HTTP ${r.status}`}`)),process.exit(1));let a=i.grant;console.log(w.green(` \u2713 ${a?.name??e}: ${a?.state}${i.stopped?`, ${i.stopped} running turn(s) stopped`:""}`))}catch(r){console.log(w.red(` Could not reach the daemon at ${n}: ${r?.message||r}. The daemon must be running: it stops the guest's running turns.`)),process.exit(1)}}var tn=Et.command("guests").description("other organisations let into part of this mesh: open, watch, pause, widen, narrow, end");tn.command("invite").description("open a grant to a guest mesh and print the one-time code it joins with").requiredOption("--name <name>",'what you call it, e.g. "Support session for company X"').requiredOption("--guest <name>","the other organisation's name").requiredOption("--agent <id>","the agent of this node that works for them").option("--folders <list>","folders that agent may touch for them, comma-separated").option("--skills <list>","skills it may use for them, comma-separated").option("--commands <list>","commands it may run for them, comma-separated").option("--level <level>","report (read only) | propose (no merge, deploy or delete) | act","propose").option("--days <n>","how long the grant lasts (1 to 365)","7").option("--url <origin>","the address the guest reaches this node on (default: dashboard.daemonUrl)").action(async e=>{let{TokenStore:t}=await import("./token-store-AY6OBDVI.js"),{PairCodeStore:o,formatCode:n}=await import("./pair-codes-EYGELBPD.js"),{GuestStore:s}=await import("./store-7W2AP5H4.js"),{inviteGuest:r}=await import("./grants-YGA654NI.js"),i=C(),a=r({tokens:new t,codes:new o,guests:new s,hasAgent:l=>!!i.agents[l]},{name:e.name,guest:e.guest,agentId:e.agent,folders:e.folders?.split(","),skills:e.skills?.split(","),commands:e.commands?.split(","),level:e.level,days:Number(e.days)});a.ok||(console.log(w.red(` ${a.error}`)),process.exit(1));let c=(e.url||Bp()).replace(/\/+$/,"");console.log(),console.log(` Grant ${w.bold(a.grant.name)} for ${w.bold(a.grant.guest)}: ${a.grant.agentId}, ${a.grant.level}, until ${a.grant.expiresAt.slice(0,10)}`),console.log(w.dim(` id ${a.grant.id} \xB7 folders: ${a.grant.folders.join(", ")||"none"} \xB7 skills: ${a.grant.skills.join(", ")||"none"} \xB7 commands: ${a.grant.commands.join(", ")||"none"}`)),console.log(),console.log(" Send the guest this address and code. The code works once, for 10 minutes:"),console.log(),console.log(` ${w.cyan(c)}`),console.log(` ${w.bold(n(a.code))}`),console.log(),console.log(` They run: ${w.cyan(`agentx mesh join ${c} --code ${n(a.code)} --name <what-they-call-you>`)}`),console.log(` When they join, a card "Guest mesh: ${a.grant.guest} wants to join" asks you to approve it.`),console.log()});tn.command("list",{isDefault:!0}).alias("ls").description("every grant: state, guest, agent, level, end date, usage").option("--json","machine-readable output").action(async e=>{let{GuestStore:t}=await import("./store-7W2AP5H4.js"),o=new t().grants().sort((n,s)=>s.createdAt.localeCompare(n.createdAt));if(e.json){console.log(JSON.stringify(o,null,2));return}if(!o.length){console.log(w.dim(`
|
|
45
45
|
No guest grants. Open one: agentx mesh guests invite --name \u2026 --guest \u2026 --agent \u2026
|
|
46
46
|
`));return}console.log();for(let n of o){let s=n.state==="active"?w.green("active"):n.state==="pending"?w.yellow(n.guestNode?"waiting for your approval":"not joined yet"):n.state==="paused"?w.yellow("paused"):w.red(`ended${n.endedReason?`: ${n.endedReason}`:""}`);console.log(` ${w.cyan(n.id)} ${w.bold(n.name)} ${n.guest} \u2192 ${n.agentId} ${n.level} ${s}`),console.log(w.dim(` until ${n.expiresAt.slice(0,10)} \xB7 ${n.usage.turns} turn(s), ${n.usage.tokens} tokens${n.usage.lastAt?` \xB7 last ${n.usage.lastAt.slice(0,16).replace("T"," ")} UTC`:""}${n.guestNode?.name?` \xB7 node ${n.guestNode.name}`:""}`))}console.log()});tn.command("show <id>").description("one grant, what it opens and what the guest did").action(async e=>{let{GuestStore:t}=await import("./store-7W2AP5H4.js"),o=new t,n=o.get(e);n||(console.log(w.red(` No grant "${e}". See \`agentx mesh guests\`.`)),process.exit(1)),console.log(),console.log(` ${w.bold(n.name)} (${n.id}) \xB7 ${n.state}`),console.log(` guest: ${n.guest}${n.guestNode?` \xB7 node ${n.guestNode.name??n.guestNode.id??"?"} at ${n.guestNode.address??"?"}`:""}`),console.log(` agent: ${n.agentId} \xB7 level: ${n.level} \xB7 until ${n.expiresAt.slice(0,10)}`),console.log(` folders: ${n.folders.join(", ")||"none"}
|
|
47
47
|
skills: ${n.skills.join(", ")||"none"}
|
|
@@ -506,7 +506,7 @@ return aid ?? null`}function Yx(e,t){if(e.kind==="path"){let s=e.pts,r=s[Math.fl
|
|
|
506
506
|
Plan ${L.dim(`[${s.state}] by ${s.createdBy}, ${wn(s.createdAt)}`)}`),sy(n.steps(e)),console.log(`
|
|
507
507
|
Log`);for(let r of n.events(e))console.log(L.dim(` ${wn(r.at)} ${r.step?`step ${r.step} `:""}${r.kind}: ${r.detail}`))}});Lo.command("step <id> <step>").description("move a step of a request's plan on: --retry, --skip or --done").option("--retry","hand the step to its agent again, with a fresh count of nudges (approves a message whose card expired)").option("--skip","leave the step out; the plan goes on with the next one").option("--done","mark the step done yourself").option("--note <text>","why, or the evidence for --done").action((e,t,o)=>{let n=["retry","skip","done"].filter(c=>o[c]);if(n.length!==1){console.error(L.red(" give one of --retry, --skip or --done")),process.exitCode=1;return}let s=sr();if(!s)return;let r=Gi();if(!r){process.exitCode=1;return}let i=Number(t),a=wu(s,r,{requestId:e,step:i,action:n[0],detail:o.note},Date.now());if(a){console.error(L.red(` ${a}`)),process.exitCode=1;return}console.log(L.green(` \u2713 step ${i} of ${e}: ${n[0]==="retry"?"handed over again":n[0]==="skip"?"skipped":"done"}`)+L.dim(" (the daemon goes on within a minute)")),sy(r.steps(e))});function ry(e,t,o,n){let s=Lo.command(`${e} <id>`).description(t).option(o,e==="done"?"link to the proof: PR, issue, message, deploy":"why it is dropped");e==="done"&&s.option("--reason <text>",'close it by hand without a link, saying why ("checked by hand"); also closes a loop its check cannot verify'),s.action((r,i)=>{let a=sr();if(!a)return;let c=e==="done"?i.evidence??(i.reason?`closed by owner: ${i.reason}`:void 0):i.reason??"dropped by the owner";if(!c){console.error(L.red(` ${n}`)),process.exitCode=1;return}let l=a.get(r);if(!l||l.state==="candidate"){console.error(L.red(` no request "${r}"`)),process.exitCode=1;return}if(!a.close(r,e==="done"?"done":"dropped",c,Date.now())){console.error(L.red(` ${r} is already closed (${wl[l.state]??l.state})`)),process.exitCode=1;return}console.log(L.green(` \u2713 ${r} ${e==="done"?"closed as done":"dropped"}`))})}ry("done","close a request as finished, with a link to the evidence or the reason","--evidence <link>","a finished request needs --evidence <link> or --reason <text>");ry("drop","drop a request you no longer want","--reason <text>","");Lo.command("settings").description("show or change the requests settings").option("--enabled <on|off>","record and follow your requests").option("--from <id,...>",'who counts as you on channels other people can reach, as channel:id (your login on GitLab and GitHub, your sender id elsewhere); "none" to clear').option("--channels <name,...>",'channels to record on; "all" for every channel a person writes on').option("--stale-hours <n>","hours without activity before an open request comes back to you").option("--retention-days <n>","days a closed request is kept").option("--headlines <on|off>","write a headline for a request its agent did not describe (one small model call each)").option("--plans <on|off>","follow requests of two or more steps as tracked plans").option("--stall-minutes <n>","minutes without progress before a plan step's agent is nudged").option("--max-nudges <n>","nudges per step before it counts as blocked and you are told").option("--approve-kinds <kind,...>",'step kinds you approve once when the plan is made; "none" to clear').option("--plans-off-for <agent,...>",'agents that may not open a plan; "none" to clear').option("--plan-for <work,...>",'kinds of work an agent must plan before it starts (default code); "none" to clear').option("--plan-over <minutes>","work estimated over this many minutes needs a plan (default 30); 0 turns it off").option("--checks <kind,...>",`checks agents may use to close a loop (${Ar.join(", ")}); "none" to turn them all off`).option("--check-every <minutes>","how often each open loop's check runs").action(async e=>{let t={};try{let c=(p,u)=>{if(u===void 0)return;let h=Number(u);if(!(h>0))throw new Error(`${p} must be a positive number`);return h},l=(p,u)=>p===void 0?void 0:p===u?[]:p.split(",").map(h=>h.trim()).filter(Boolean);if(e.enabled!==void 0){if(e.enabled!=="on"&&e.enabled!=="off")throw new Error("--enabled takes on or off");t.enabled=e.enabled==="on"}if(t.from=l(e.from,"none"),t.channels=l(e.channels,"all"),t.staleAfterHours=c("--stale-hours",e.staleHours),t.retentionDays=c("--retention-days",e.retentionDays),e.headlines!==void 0){if(e.headlines!=="on"&&e.headlines!=="off")throw new Error("--headlines takes on or off");t.headlines={enabled:e.headlines==="on"}}let g={};if(e.plans!==void 0){if(e.plans!=="on"&&e.plans!=="off")throw new Error("--plans takes on or off");g.enabled=e.plans==="on"}if(g.stallMinutes=c("--stall-minutes",e.stallMinutes),e.maxNudges!==void 0){let p=Number(e.maxNudges);if(!Number.isInteger(p)||p<0)throw new Error("--max-nudges must be a whole number, 0 or more");g.maxNudges=p}if(g.approveKinds=l(e.approveKinds,"none"),g.disabledAgents=l(e.plansOffFor,"none"),g.requireForWork=l(e.planFor,"none"),e.planOver!==void 0){let p=Number(e.planOver);if(!(p>=0))throw new Error("--plan-over must be a number of minutes, 0 or more");g.requireOverMinutes=p}Object.values(g).some(p=>p!==void 0)&&(t.plans=g);let d={},f=l(e.checks,"none");if(f){let p=f.filter(u=>!Ar.includes(u));if(p.length)throw new Error(`unknown check ${p.join(", ")}: use ${Ar.join(", ")}`);d.enabled=f}d.checkEveryMinutes=c("--check-every",e.checkEvery),Object.values(d).some(p=>p!==void 0)&&(t.checkers=d)}catch(c){console.error(L.red(` ${c.message}`)),process.exitCode=1;return}if(Object.values(t).some(c=>c!==void 0)){let c=await ru(t);if(!c.success){console.error(L.red(` ${c.error}`)),process.exitCode=1;return}console.log(L.green(" \u2713 saved")+(c.reloaded?L.dim(" (daemon reloaded)"):""))}let o=Br();console.log(` Requests ${o.enabled?"on":"off"}`),console.log(` Channels ${o.channels.length?o.channels.join(", "):"every channel a person writes on"}`),console.log(` Counts as you ${o.from.length?o.from.join(", "):"only voice, the app and the dashboard on this machine"}`),console.log(` Comes back after ${o.staleAfterHours} hour(s) without activity`),console.log(` Closed requests kept ${o.retentionDays} day(s)`),console.log(` Headlines ${o.headlines?.enabled===!1?"off: titled with the start of your words":"on: written when the agent gave none"}`);let n=o.plans??Gr;console.log(` Plans ${n.enabled?"on":"off"}`),console.log(` Nudge a quiet step after ${n.stallMinutes} minute(s), up to ${n.maxNudges} time(s)`),console.log(` You approve up front ${n.approveKinds.length?n.approveKinds.join(", "):"nothing"}`),console.log(` Plans off for ${n.disabledAgents.length?n.disabledAgents.join(", "):"no agent"}`);let s=n.requireOverMinutes??Gr.requireOverMinutes,r=n.requireForWork??Gr.requireForWork;console.log(` Plan before starting ${[r.length?`${r.join(", ")} work`:"",s>0?`work over ${s} minute(s)`:""].filter(Boolean).join("; ")||"never required"}`);let i=o.checkers??xd;console.log(` Loop checks ${i.enabled.length?i.enabled.join(", "):"off"}, every ${i.checkEveryMinutes} minute(s)`);let a=Object.keys(i.repos);console.log(` Repos for git-ancestor ${a.length?a.join(", "):"none (requests.checkers.repos in agentx.json)"}`)});import{Command as RS}from"commander";import ls from"chalk";ae();var iy=new RS("request-status").description("show each person the state of their request in the thread where they asked (GitLab, GitHub)").argument("[channel]",sc.join(" or ")).argument("[state]","on or off").action(async(e,t)=>{if(e!==void 0){t!=="on"&&t!=="off"&&(console.error(ls.red("\u2717 say on or off, for example: agentx request-status gitlab on")),process.exit(1));let n="",s=await ge(r=>{n=Rm(r,e,t==="on")});s.success||(console.error(ls.red(`\u2717 ${s.error}`)),process.exit(1)),console.log(ls.green(`\u2713 ${n}`)),s.reloaded&&console.log(ls.dim(" daemon hot-reloaded"))}let o=C().requestStatus.channels.map(n=>n.toLowerCase());for(let n of sc)console.log(` ${n.padEnd(8)} ${o.includes(n)?ls.green("on"):ls.dim("off")}`)});import{Command as XS}from"commander";import ds from"chalk";import ye from"chalk";import ir from"prompts";import{readFileSync as CS,writeFileSync as TS,existsSync as OS}from"fs";import{resolve as ay}from"path";var NS=/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/;function IS(e){let t=e.trim();return t.startsWith('"')&&t.endsWith('"')||t.startsWith("'")&&t.endsWith("'")?t.slice(1,-1):t}function bl(e){return/[\s#"'\\]/.test(e)||e===""?JSON.stringify(e):e}function cy(e=".env"){let t=ay(e);if(!OS(t))return[];let o=CS(t,"utf-8"),n=[];for(let s of o.split(/\r?\n/)){if(s.trim()===""){n.push({kind:"blank",raw:s});continue}if(s.trim().startsWith("#")){n.push({kind:"comment",raw:s});continue}let r=s.match(NS);r?n.push({kind:"kv",key:r[1],value:IS(r[2]),raw:s}):n.push({kind:"comment",raw:s})}return n.length&&n[n.length-1].kind==="blank"&&n.pop(),n}function jS(e){return e.map(t=>t.kind==="kv"?`${t.key}=${bl(t.value)}`:t.raw).join(`
|
|
508
508
|
`)+`
|
|
509
|
-
`}function rr(e,t,o=".env"){let n=ay(o),s=cy(n),r=s.findIndex(i=>i.kind==="kv"&&i.key===e);r>=0?s[r]={kind:"kv",key:e,value:t,raw:`${e}=${bl(t)}`}:(s.length&&s[s.length-1].kind!=="blank"&&s.push({kind:"blank",raw:""}),s.push({kind:"kv",key:e,value:t,raw:`${e}=${bl(t)}`})),TS(n,jS(s))}function kl(e,t=".env"){return cy(t).find(s=>s.kind==="kv"&&s.key===e)?.value}var ly="https://t.me/BotFather";async function _S(e){try{let t=await fetch(`https://api.telegram.org/bot${e}/getMe`,{signal:AbortSignal.timeout(8e3)});if(!t.ok)return null;let o=await t.json();return o?.ok?o.result:null}catch{return null}}async function DS(e,t){let o=Date.now()+t,n=0;try{let r=await(await fetch(`https://api.telegram.org/bot${e}/getUpdates?offset=-1&timeout=0`,{signal:AbortSignal.timeout(5e3)})).json();r?.result?.length&&(n=r.result[0].update_id+1)}catch{}for(;Date.now()<o;)try{let r=await(await fetch(`https://api.telegram.org/bot${e}/getUpdates?offset=${n}&timeout=10`,{signal:AbortSignal.timeout(15e3)})).json();for(let i of r?.result||[]){n=Math.max(n,i.update_id+1);let a=i.message||i.edited_message||i.channel_post;if(a?.chat?.id)return{chatId:a.chat.id,chatTitle:a.chat.title||a.chat.username||a.chat.first_name,from:a.from?.first_name||a.from?.username||"unknown"}}}catch{}return null}async function dy(e={}){console.log(),console.log(ye.bold(" Connect Telegram")),console.log(),console.log(ye.dim(` 1. Open ${ly}`)),console.log(ye.dim(" /newbot (or /mybots \u2192 pick existing \u2192 API Token)")),console.log(ye.dim(" 2. Copy the HTTP API token \u2014 looks like 123456789:ABCdef...")),console.log();let{wantsBrowser:t}=await ir({type:"confirm",name:"wantsBrowser",message:"Open BotFather in your browser?",initial:!0});t&&Zo(ly);let{token:o}=await ir({type:"password",name:"token",message:"Paste the bot token from BotFather:",validate:b=>/^\d+:[A-Za-z0-9_-]{20,}$/.test(b.trim())||"That doesn't look like a Telegram token"});o||(console.log(ye.red(" Aborted")),process.exit(1)),process.stdout.write(" Verifying token... ");let n=await _S(o.trim());n||(console.log(ye.red("\u2717 invalid or revoked")),process.exit(1)),console.log(ye.green(`\u2713 ${n.first_name} (@${n.username})`));let{readFileSync:s,existsSync:r}=await import("fs"),{resolve:i}=await import("path"),a=e.configPath||i(process.cwd(),"agentx.json");r(a)||(console.log(ye.red(` No agentx.json at ${a}. Run: agentx init`)),process.exit(1));let c=JSON.parse(s(a,"utf-8")),l=Object.keys(c.agents||{});l.length===0&&(console.log(ye.red(" No agents configured. Run: agentx agent add")),process.exit(1));let g=e.agent;g||(g=(await ir({type:"select",name:"agent",message:"Bind this bot to which agent?",choices:l.map(v=>({title:v,value:v})),initial:0})).agent),g||(console.log(ye.red(" Aborted")),process.exit(1));let d=c.channels?.telegram?.accounts||{},f=e.account||g;d[f]&&d[f].agentBinding!==g&&(f=(await ir({type:"text",name:"account",message:`Account "${f}" is already in use. Pick a different label:`,initial:`${g}-2`,validate:v=>/^[a-z0-9-]+$/i.test(v)||"lowercase letters, digits, hyphens only"})).account),f||(console.log(ye.red(" Aborted")),process.exit(1));let p=`TG_${f.toUpperCase().replace(/-/g,"_")}_BOT_TOKEN`;rr(p,o.trim()),process.env[p]=o.trim();let u=await ge(b=>{Je(b,"channels.telegram.enabled",!0),Je(b,`channels.telegram.accounts.${f}`,{token:`\${${p}}`,agentBinding:g});let v=Ln(b,`agents.${g}.mentions`)||[],x=`@${n.username}`;v.includes(x)||(v.push(x),Je(b,`agents.${g}.mentions`,v))},{configPath:e.configPath});if(u.success||(console.log(ye.red(` \u2717 ${u.error}`)),process.exit(1)),console.log(),console.log(ye.green(` \u2713 Bot @${n.username} bound to agent "${g}"`)),console.log(ye.dim(` Token stored in .env as ${p}`)),console.log(ye.dim(` Mentions updated to include @${n.username}`)),u.reloaded?console.log(ye.dim(" Daemon hot-reloaded.")):console.log(ye.dim(" Start the daemon to begin polling (agentx daemon start)")),e.skipChatCapture)return;console.log();let{capture:h}=await ir({type:"confirm",name:"capture",message:"Listen for a message so we can set this as your default --notify target?",initial:!0});if(!h)return;console.log(),console.log(ye.dim(` Send any message to @${n.username} from the chat you want to use.`)),console.log(ye.dim(" Waiting up to 90s..."));let y=await DS(o.trim(),9e4);if(!y){console.log(ye.yellow(" No message received \u2014 skipping. You can set it later with:")),console.log(ye.dim(" agentx config set notifications.destination.channel telegram")),console.log(ye.dim(" agentx config set notifications.destination.chatId <chatId>")),console.log(ye.dim(` agentx config set notifications.destination.accountId ${f}`));return}let $=await ge(b=>{Je(b,"notifications.destination",{channel:"telegram",chatId:String(y.chatId),accountId:f})},{configPath:e.configPath});if(!$.success){console.log(ye.red(` \u2717 failed to save destination: ${$.error}`));return}console.log(ye.green(" \u2713 Default --notify target set")),console.log(ye.dim(` Chat: ${y.chatTitle||y.chatId} (from ${y.from})`)),console.log(ye.dim(" From now on, --notify me resolves to this chat.")),$.reloaded&&console.log(ye.dim(" Daemon hot-reloaded."))}import Ce from"chalk";import vl from"prompts";import{existsSync as Sl,mkdirSync as PS,readdirSync as MS,readFileSync as gy}from"fs";import{resolve as xl}from"path";var LS=".agentx/whatsapp-sessions";function FS(){let e=xl(process.cwd(),".agentx/daemon.pid");if(!Sl(e))return!1;try{let t=parseInt(gy(e,"utf-8").trim(),10);return t?(process.kill(t,0),!0):!1}catch{return!1}}function qS(e){if(!Sl(e))return!1;try{return MS(e).some(t=>t.includes("creds")||t.endsWith(".json"))}catch{return!1}}async function US(e,t){let o;try{o=await import("@whiskeysockets/baileys")}catch{return t(Ce.red(" WhatsApp pairing needs @whiskeysockets/baileys.")),t(Ce.red(" Install with: pnpm add @whiskeysockets/baileys (or npm install)")),!1}let{makeWASocket:n,useMultiFileAuthState:s}=o.default||o;PS(e,{recursive:!0});let{state:r,saveCreds:i}=await s(e),a;try{a=(await o.fetchLatestBaileysVersion()).version}catch{}let c={level:"silent",trace:()=>{},debug:()=>{},info:()=>{},warn:()=>{},fatal:()=>{},error:()=>{},child:()=>c},l=n({auth:{creds:r.creds,keys:o.makeCacheableSignalKeyStore?o.makeCacheableSignalKeyStore(r.keys,c):r.keys},...a?{version:a}:{},logger:c,printQRInTerminal:!1,browser:["agentx-connect","pairing","1.0"],syncFullHistory:!1,markOnlineOnConnect:!1});return l.ev.on("creds.update",i),await new Promise(g=>{let f=setTimeout(()=>{t(Ce.yellow(" Timed out waiting for scan (2min)."));try{l.end(void 0)}catch{}g(!1)},12e4);l.ev.on("connection.update",async p=>{if(p.qr){t(Ce.dim(" Scan this QR with WhatsApp \u2192 Linked devices \u2192 Link a device:")),t("");try{let{default:u}=await import("qrcode-terminal");u.generate(p.qr,{small:!0})}catch{t(` QR: ${p.qr}`)}t("")}if(p.connection==="open"){clearTimeout(f),t(Ce.green(" \u2713 Paired"));try{l.end(void 0)}catch{}g(!0)}p.connection==="close"&&!p.qr&&p.lastDisconnect?.error?.output?.statusCode===401&&(clearTimeout(f),t(Ce.red(" \u2717 Authorization failed \u2014 rescan required")),g(!1))})})}async function uy(e={}){if(console.log(),console.log(Ce.bold(" Connect WhatsApp")),console.log(),FS()){console.log(Ce.yellow(" \u26A0 The daemon is running. Stop it first so pairing can open a fresh Baileys session:")),console.log(Ce.dim(" agentx daemon stop")),console.log();let{cont:l}=await vl({type:"confirm",name:"cont",message:"Continue anyway? (expect errors)",initial:!1});l||(console.log(Ce.red(" Aborted")),process.exit(1))}let t=e.configPath||xl(process.cwd(),"agentx.json");Sl(t)||(console.log(Ce.red(` No agentx.json at ${t}. Run: agentx init`)),process.exit(1));let o=JSON.parse(gy(t,"utf-8")),n=e.sessionDir||o.channels?.whatsapp?.sessionDir||LS,s=xl(process.cwd(),n),r=Object.keys(o.agents||{});r.length===0&&(console.log(Ce.red(" No agents configured. Run: agentx agent add")),process.exit(1));let i=e.agent;i||(i=(await vl({type:"select",name:"agent",message:"Default agent for incoming messages:",choices:r.map(g=>({title:g,value:g})),initial:0})).agent),i||(console.log(Ce.red(" Aborted")),process.exit(1));let a=!e.skipPair;if(qS(s)&&a){let{keep:l}=await vl({type:"confirm",name:"keep",message:`An existing session was found at ${n}. Keep it (recommended), or pair again?`,initial:!0});a=!l,l&&console.log(Ce.dim(" Keeping existing session \u2014 skipping QR pair."))}a&&(await US(s,g=>console.log(g))||(console.log(Ce.red(" Pairing failed. You can retry with `agentx connect whatsapp`.")),process.exit(1)));let c=await ge(l=>{Je(l,"channels.whatsapp.enabled",!0),Je(l,"channels.whatsapp.sessionDir",n),Je(l,"channels.whatsapp.defaultAgent",i)},{configPath:e.configPath});c.success||(console.log(Ce.red(` \u2717 ${c.error}`)),process.exit(1)),console.log(),console.log(Ce.green(` \u2713 WhatsApp bound to agent "${i}"`)),console.log(Ce.dim(` Session: ${n}`)),console.log(Ce.dim(" Start the daemon to begin receiving:")),console.log(Ce.dim(" agentx daemon start")),console.log(Ce.yellow(" No chat is answered until you list the allowed numbers (add your own to talk from your self-chat):")),console.log(Ce.dim(` agentx config set channels.whatsapp.allowFrom '["+15550001111"]'`)),console.log(Ce.dim(" Add routes later with:")),console.log(Ce.dim(` agentx config set channels.whatsapp.routes '[{"contact":"+1234567890","agent":"${i}"}]'`)),console.log()}import{randomBytes as WS}from"crypto";import We from"chalk";import my from"prompts";var Hi="agentx-mesh:",zi="//join/";function BS(e){return Buffer.from(e,"utf-8").toString("base64url")}function JS(e){return Buffer.from(e,"base64url").toString("utf-8")}function GS(e){return`${Hi}${zi}${BS(JSON.stringify(e))}`}function HS(e){if(!e.startsWith(`${Hi}${zi}`))throw new Error(`Not a mesh invite link \u2014 expected ${Hi}${zi}\u2026`);let t=e.slice(Hi.length+zi.length),o=JSON.parse(JS(t));if(o.version!==1)throw new Error(`Unsupported invite version: ${o.version}`);if(typeof o.url!="string"||typeof o.token!="string"||typeof o.name!="string")throw new Error("Invite payload missing required fields (url, token, name)");return o}function zS(){return WS(32).toString("hex")}function YS(e){let t=e?.node?.bind||"127.0.0.1:18800",[o,n]=t.split(":");return`http://${o}:${n}`}function VS(e){return e?.node?.name||e?.node?.id||"node"}async function py(e){let{readFileSync:t,existsSync:o}=await import("fs"),{resolve:n}=await import("path"),s=e.configPath||n(process.cwd(),"agentx.json");o(s)||(console.log(We.red(` No agentx.json at ${s}. Run: agentx init`)),process.exit(1));let r=JSON.parse(t(s,"utf-8")),i=kl("MESH_TOKEN");i||(i=zS(),rr("MESH_TOKEN",i),console.log(We.dim(" Generated new MESH_TOKEN (32 bytes) and saved to .env")));let a=e.url||YS(r);if((a.includes("0.0.0.0")||a.includes("127.0.0.1")||a.includes("localhost"))&&!e.url){let{url:d}=await my({type:"text",name:"url",message:"This node's URL as peers should reach it (e.g. http://100.x.x.x:18800):",initial:a,validate:f=>/^https?:\/\//.test(f)||"must start with http:// or https://"});d||(console.log(We.red(" Aborted")),process.exit(1)),a=d}let l=await ge(d=>{Je(d,"mesh.enabled",!0)},{configPath:e.configPath});l.success||(console.log(We.red(` \u2717 ${l.error}`)),process.exit(1));let g=GS({url:a,token:i,name:VS(r),version:1});console.log(),console.log(We.bold(" Mesh invite")),console.log(),console.log(` ${We.cyan(g)}`),console.log(),console.log(We.dim(" Run on the other node:")),console.log(We.dim(` agentx connect mesh join "${g}"`)),console.log(),console.log(We.yellow(" Share over a trusted channel only \u2014 whoever holds this link can join your mesh.")),console.log()}async function fy(e,t={}){let o;try{o=HS(e)}catch(r){console.log(We.red(` \u2717 ${r.message}`)),process.exit(1)}let n=kl("MESH_TOKEN");if(n&&n!==o.token){let{overwrite:r}=await my({type:"confirm",name:"overwrite",message:"A different MESH_TOKEN already exists in .env. Overwrite with the invite's token?",initial:!1});r||(console.log(We.red(" Aborted (keeping existing .env)")),process.exit(1))}rr("MESH_TOKEN",o.token);let s=await ge(r=>{Je(r,"mesh.enabled",!0);let i=Ln(r,"mesh.peers")||[],a=i.findIndex(l=>l.name===o.name),c={name:o.name,url:o.url,token:"${MESH_TOKEN}"};a>=0?i[a]=c:i.push(c),Je(r,"mesh.peers",i)},{configPath:t.configPath});s.success||(console.log(We.red(` \u2717 ${s.error}`)),process.exit(1)),console.log(We.green(" \u2713 Joined mesh")),console.log(We.dim(` Peer: ${o.name} @ ${o.url}`)),console.log(We.dim(" Shared token stored in .env as MESH_TOKEN")),s.reloaded?console.log(We.dim(" Daemon hot-reloaded.")):console.log(We.dim(" Restart the daemon to start the mesh (agentx daemon start).")),console.log();try{let r=await fetch(`${o.url.replace(/\/$/,"")}/.well-known/agent-card.json`,{signal:AbortSignal.timeout(5e3)});if(r.ok){let i=await r.json(),a=Array.isArray(i.agents)?i.agents.length:"?";console.log(We.dim(` Reachable \u2014 ${o.name} exposes ${a} agents`))}else console.log(We.yellow(` Peer returned HTTP ${r.status} \u2014 double-check the URL`))}catch(r){console.log(We.yellow(` Could not reach ${o.url} right now (${r?.cause?.code||r.message}) \u2014 verify VPN / Tailscale when the peer daemon is up`))}}var gs=new XS().name("connect").description("connect a channel or mesh peer \u2014 no manual file editing required");gs.command("telegram").description("pair a Telegram bot \u2014 persists token in .env, binds to an agent, auto-detects default chat").option("--agent <id>","agent to bind the bot to (skips the prompt)").option("--account <name>","account label in channels.telegram.accounts (defaults to agent id)").option("--skip-chat-capture","don't listen for the first message after pairing").option("-c, --config <path>","path to agentx.json").action(async e=>{await dy({agent:e.agent,account:e.account,configPath:e.config,skipChatCapture:!!e.skipChatCapture})});gs.command("whatsapp").description("pair WhatsApp via QR (prints in terminal), then writes channels.whatsapp config").option("--agent <id>","default agent for inbound messages").option("--session-dir <path>","session directory (default: .agentx/whatsapp-sessions)").option("--skip-pair","skip QR pairing (use when session already exists)").option("-c, --config <path>","path to agentx.json").action(async e=>{await uy({agent:e.agent,sessionDir:e.sessionDir,skipPair:!!e.skipPair,configPath:e.config})});var hy=gs.command("mesh").description("mesh invite + join \u2014 replaces manual MESH_TOKEN copy-paste");hy.command("invite").description("emit a single-use join link for another AgentX node").option("--url <url>","this node's URL as peers should reach it (Tailscale IP etc.)").option("-c, --config <path>","path to agentx.json").action(async e=>{await py({url:e.url,configPath:e.config})});hy.command("join <link>").description("accept an invite link from another AgentX node").option("-c, --config <path>","path to agentx.json").action(async(e,t)=>{await fy(e,{configPath:t.config})});gs.action(()=>{console.log(),console.log(ds.bold(" agentx connect \u2014 which channel?")),console.log(),console.log(` ${ds.cyan("agentx connect telegram")} \u2014 pair a Telegram bot`),console.log(` ${ds.cyan("agentx connect whatsapp")} \u2014 pair WhatsApp via QR`),console.log(` ${ds.cyan("agentx connect mesh invite")} \u2014 emit a mesh join link for another node`),console.log(` ${ds.cyan("agentx connect mesh join <link>")} \u2014 accept a mesh invite`),console.log(),console.log(ds.dim(" GitLab connect is the next slice.")),console.log()});ae();import{Command as KS}from"commander";import te from"chalk";var $n=new KS().name("usage").description("token usage analysis and reporting");$n.command("today",{isDefault:!0}).description("show today's token usage").action(async()=>{try{let e=C(),[t,o]=e.node.bind.split(":"),s=await(await fetch(`http://${t||"127.0.0.1"}:${o}/usage`,{signal:AbortSignal.timeout(3e3)})).json();if(console.log(),console.log(te.bold(" Token Usage (last 7 days)")),console.log(),s.totalTasks===0){console.log(te.dim(" No tasks recorded yet")),console.log();return}let r=a=>a.toLocaleString();console.log(` Total: ${te.bold(r(s.totalTokens))} tokens across ${s.totalTasks} tasks`),console.log(` Input: ${r(s.totalInput)}`),console.log(` Output: ${r(s.totalOutput)}`),console.log(` Cache read: ${r(s.totalCacheRead)}`),console.log(` Cache create: ${r(s.totalCacheCreate)}`),s.cacheHitRatio>0&&console.log(` Cache hit: ${(s.cacheHitRatio*100).toFixed(1)}%`),s.totalErrors>0&&console.log(` Errors: ${te.red(String(s.totalErrors))}`),console.log(),console.log(te.bold(" By Agent:"));let i=Object.entries(s.byAgent||{});i.sort((a,c)=>c[1].total-a[1].total);for(let[a,c]of i){let l=r(c.total||c.input+c.output+c.cacheRead+c.cacheCreate),g=c.avgDuration?`${(c.avgDuration/1e3).toFixed(1)}s avg`:"";console.log(` ${te.cyan(a)}: ${l} tokens (${c.tasks} tasks) ${te.dim(g)}`)}console.log()}catch(e){e.cause?.code==="ECONNREFUSED"?console.log(te.red(" Daemon not running. Start with: agentx daemon start")):console.log(te.red(` ${e.message}`))}});$n.command("plan").description("show the Claude plan windows as Claude Code last reported them, and any hold on fresh sessions").option("--lift","lift the hold on fresh sessions now, without waiting for the reset time").option("--json","raw JSON output").action(async e=>{try{let t=C(),[o,n]=t.node.bind.split(":"),s=`http://${o||"127.0.0.1"}:${n}`,r=e.lift?await fetch(`${s}/usage/plan/lift`,{method:"POST",signal:AbortSignal.timeout(3e3)}):await fetch(`${s}/usage/plan`,{signal:AbortSignal.timeout(3e3)});if(!r.ok)throw new Error(`Daemon answered ${r.status}. It may be older than this command; restart it.`);let i=await r.json();if(e.json){console.log(JSON.stringify(i,null,2));return}let a=d=>d.replace(/_/g," ");if(console.log(),e.lift){let d=i.lifted.map(f=>a(f.window));console.log(d.length?` Lifted the hold on: ${d.join(", ")}. The next fresh session asks Claude again.`:te.dim(" No hold to lift.")),console.log();return}let c=i.now,l=new Set(i.holds.map(d=>d.window));console.log(te.bold(" Claude plan (as Claude Code last reported it)")),console.log(),i.provider.length===0&&console.log(te.dim(" No report yet. Claude Code reports on each turn; the list is empty after a daemon restart."));for(let d of i.provider){let f=d.usingOverage?"extra usage":d.status==="allowed_warning"?"nearly full":d.status,p=typeof d.utilization=="number"?`${Math.round(d.utilization*100)}% used`:"",u=Math.max(1,Math.round((d.resetsAt-c)/6e4)),h=d.resetsAt>c?u<60?`resets in ${u} min`:u<48*60?`resets in ${Math.round(u/60)} h`:`resets in ${Math.round(u/1440)} days`:"",y=l.has(d.window)?te.red("holding fresh sessions"):"";console.log(` ${a(d.window).padEnd(18)} ${f.padEnd(12)} ${[p,h,y].filter(Boolean).join(", ")}`)}console.log();let g=d=>d?` of ${d}`:"";console.log(` Fresh sessions started: ${i.lastHour}${g(i.maxPerHour)} in the last hour, ${i.last5h}${g(i.maxPer5h)} in the last 5 hours`),l.size&&console.log(te.dim(" To try again now: agentx usage plan --lift")),console.log()}catch(t){t.cause?.code==="ECONNREFUSED"?console.log(te.red(" Daemon not running. Start with: agentx daemon start")):console.log(te.red(` ${t.message}`))}});$n.command("report").description("run full session analysis (parses Claude Code JSONL files)").option("--days <n>","analyze last N days","7").action(async e=>{let{execSync:t}=await import("child_process"),{existsSync:o}=await import("fs"),{resolve:n}=await import("path"),s=n(process.cwd(),"scripts/token-report.py");if(!o(s)){console.log(te.red(` Script not found: ${s}`)),console.log(te.dim(" The token analyzer script should be at scripts/token-report.py"));return}console.log(te.dim(" Analyzing Claude Code sessions..."));try{t(`SINCE_DAYS=${e.days} python3 "${s}"`,{stdio:"inherit"})}catch{console.log(te.red(" Analysis failed. Is Python 3 installed?"))}});$n.command("channels").description("cost per channel over a fixed range of days, next to a saved baseline").requiredOption("--from <date>","first day, YYYY-MM-DD").requiredOption("--to <date>","last day, YYYY-MM-DD").option("--save <file>","write this range's figures to a JSON file").option("--baseline <file>","a file written by --save, shown beside this range").option("--json","raw JSON output").action(async e=>{let{readFileSync:t,writeFileSync:o}=await import("fs"),{TokenTracker:n}=await import("./token-tracker-W7DFDEFN.js"),{channelCosts:s,costPerTask:r}=await import("./usage-channels-KBLEC4NE.js"),i=u=>/^\d{4}-\d{2}-\d{2}$/.test(u)&&!Number.isNaN(Date.parse(u));if(!i(e.from)||!i(e.to)||e.from>e.to){console.log(te.red(" --from and --to must be YYYY-MM-DD, with --from not after --to")),process.exitCode=1;return}let a=s(new n,e.from,e.to);e.save&&o(e.save,JSON.stringify(a,null,2));let c=null;if(e.baseline)try{c=JSON.parse(t(e.baseline,"utf-8"))}catch(u){console.log(te.red(` Cannot read baseline ${e.baseline}: ${u.message}`)),process.exitCode=1;return}if(e.json){console.log(JSON.stringify(c?{baseline:c,current:a}:a,null,2));return}let l=u=>u===null?"-":`$${u.toFixed(u<10?3:2)}`,g=u=>`${(u/1e6).toFixed(1)}M`;console.log(),console.log(te.bold(` Cost per channel, ${a.from} to ${a.to} (${a.days} day(s) with data)`)),c&&console.log(te.dim(` Baseline: ${c.from} to ${c.to} (${c.days} day(s) with data)`)),console.log();let d=` ${"channel".padEnd(14)} ${"tasks".padStart(6)} ${"cache read".padStart(11)} ${"cache write".padStart(11)} ${"output".padStart(8)} ${"cost".padStart(10)} ${"per task".padStart(9)}`;console.log(te.dim(c?`${d} ${"baseline".padStart(9)} ${"change".padStart(7)}`:d));let p=[...new Set([...Object.keys(a.channels),...Object.keys(c?.channels||{})])].map(u=>[u,a.channels[u],c?.channels[u]]).sort((u,h)=>(h[1]?.cost||0)-(u[1]?.cost||0));p.push(["TOTAL",a.total,c?.total]);for(let[u,h,y]of p){let $=r(h),b=` ${u.padEnd(14)} ${String(h?.tasks||0).padStart(6)} ${g(h?.cacheRead||0).padStart(11)} ${g(h?.cacheCreate||0).padStart(11)} ${g(h?.output||0).padStart(8)} ${l(h?.cost||0).padStart(10)} ${l($).padStart(9)}`;if(c){let v=r(y),x=$!==null&&v?`${(($-v)/v*100).toFixed(0)}%`:"-";b+=` ${l(v).padStart(9)} ${x.padStart(7)}`}console.log(b)}console.log(),c&&console.log(te.dim(" change = cost per task against the baseline; the two ranges do not have the same tasks.")),e.save&&console.log(te.dim(` Saved ${e.save}`)),console.log()});$n.command("surfaces").description("which CLI commands and dashboard pages are actually used").option("--days <n>","window in days","30").option("--kind <kind>","cli | page (default: both)").option("--unused","list registered surfaces with ZERO recorded use instead").option("--json","raw JSON output").action(async e=>{let{listSurfaceUse:t}=await import("./surface-usage-WSY4SHHL.js"),o=e.kind==="cli"||e.kind==="page"?e.kind:void 0,n=t({kind:o,sinceDays:Number(e.days)||30});if(e.unused){let{buildProgram:r}=await import("./program-NU6QIXJJ.js"),i=await r(),a=new Set(n.filter(d=>d.kind==="cli").map(d=>d.name)),c=[],l=(d,f)=>{for(let p of d.commands??[]){let u=f?`${f} ${p.name()}`:p.name();c.push(u),l(p,u)}};l(i,"");let g=c.filter(d=>!a.has(d)).sort();if(e.json){console.log(JSON.stringify({windowDays:Number(e.days)||30,unused:g},null,2));return}console.log(),console.log(te.bold(` Never used in the last ${e.days} days (${g.length}/${c.length})`)),console.log();for(let d of g)console.log(` ${te.dim("\xB7")} ${d}`);console.log(),console.log(te.dim(" Zero use is necessary but not sufficient to remove a surface \u2014")),console.log(te.dim(" it must also fail the impact test. See docs/architecture/surface-reduction.md")),console.log();return}if(e.json){console.log(JSON.stringify(n,null,2));return}if(n.length===0){console.log(),console.log(te.dim(` No surface usage recorded in the last ${e.days} days.`)),console.log(te.dim(" Counting starts once this build has been running for a while.")),console.log();return}console.log(),console.log(te.bold(` Surface usage \u2014 last ${e.days} days`)),console.log();let s="";for(let r of n){r.kind!==s&&(console.log(te.dim(` ${r.kind==="cli"?"CLI commands":"Dashboard pages"}`)),s=r.kind);let i=String(r.count).padStart(6),a=te.dim(`${r.days}d`);console.log(` ${te.cyan(i)} ${r.name.padEnd(28)} ${a}`)}console.log(),console.log(te.dim(` ${n.length} surface(s) with recorded use. See --unused for the rest.`)),console.log()});import{Command as QS}from"commander";import Me from"chalk";import{readFileSync as ZS}from"fs";import{resolve as eA}from"path";ae();var Fo=new QS().name("board").description("kanban board dashboard \u2014 visual work view over configured sources");Fo.command("serve",{isDefault:!0}).description("start the dashboard server (live view always; boards if configured)").option("--port <n>","override dashboard.port").option("--bind <host>","override dashboard.bind (e.g. 0.0.0.0)").action(async e=>{try{try{Ld(eA(process.cwd(),".env"))}catch{}let t=tA(e.port,e.bind),{startBoardDashboard:o}=await import("./board-dashboard-OZJSS6EM.js");o(t)}catch(t){console.log(Me.red(` ${t.message}`)),process.env.DEBUG&&t.stack&&console.log(Me.dim(t.stack)),process.exit(1)}});function tA(e,t){try{let o=C();return e&&(o.dashboard.port=parseInt(e,10)),t&&(o.dashboard.bind=t),o}catch{return console.log(Me.yellow(" No agentx.json found \u2014 starting dashboard in setup-only mode.")),console.log(Me.dim(" Visit http://127.0.0.1:"+(e||"4202")+` to run the wizard.
|
|
509
|
+
`}function rr(e,t,o=".env"){let n=ay(o),s=cy(n),r=s.findIndex(i=>i.kind==="kv"&&i.key===e);r>=0?s[r]={kind:"kv",key:e,value:t,raw:`${e}=${bl(t)}`}:(s.length&&s[s.length-1].kind!=="blank"&&s.push({kind:"blank",raw:""}),s.push({kind:"kv",key:e,value:t,raw:`${e}=${bl(t)}`})),TS(n,jS(s))}function kl(e,t=".env"){return cy(t).find(s=>s.kind==="kv"&&s.key===e)?.value}var ly="https://t.me/BotFather";async function _S(e){try{let t=await fetch(`https://api.telegram.org/bot${e}/getMe`,{signal:AbortSignal.timeout(8e3)});if(!t.ok)return null;let o=await t.json();return o?.ok?o.result:null}catch{return null}}async function DS(e,t){let o=Date.now()+t,n=0;try{let r=await(await fetch(`https://api.telegram.org/bot${e}/getUpdates?offset=-1&timeout=0`,{signal:AbortSignal.timeout(5e3)})).json();r?.result?.length&&(n=r.result[0].update_id+1)}catch{}for(;Date.now()<o;)try{let r=await(await fetch(`https://api.telegram.org/bot${e}/getUpdates?offset=${n}&timeout=10`,{signal:AbortSignal.timeout(15e3)})).json();for(let i of r?.result||[]){n=Math.max(n,i.update_id+1);let a=i.message||i.edited_message||i.channel_post;if(a?.chat?.id)return{chatId:a.chat.id,chatTitle:a.chat.title||a.chat.username||a.chat.first_name,from:a.from?.first_name||a.from?.username||"unknown"}}}catch{}return null}async function dy(e={}){console.log(),console.log(ye.bold(" Connect Telegram")),console.log(),console.log(ye.dim(` 1. Open ${ly}`)),console.log(ye.dim(" /newbot (or /mybots \u2192 pick existing \u2192 API Token)")),console.log(ye.dim(" 2. Copy the HTTP API token \u2014 looks like 123456789:ABCdef...")),console.log();let{wantsBrowser:t}=await ir({type:"confirm",name:"wantsBrowser",message:"Open BotFather in your browser?",initial:!0});t&&Zo(ly);let{token:o}=await ir({type:"password",name:"token",message:"Paste the bot token from BotFather:",validate:b=>/^\d+:[A-Za-z0-9_-]{20,}$/.test(b.trim())||"That doesn't look like a Telegram token"});o||(console.log(ye.red(" Aborted")),process.exit(1)),process.stdout.write(" Verifying token... ");let n=await _S(o.trim());n||(console.log(ye.red("\u2717 invalid or revoked")),process.exit(1)),console.log(ye.green(`\u2713 ${n.first_name} (@${n.username})`));let{readFileSync:s,existsSync:r}=await import("fs"),{resolve:i}=await import("path"),a=e.configPath||i(process.cwd(),"agentx.json");r(a)||(console.log(ye.red(` No agentx.json at ${a}. Run: agentx init`)),process.exit(1));let c=JSON.parse(s(a,"utf-8")),l=Object.keys(c.agents||{});l.length===0&&(console.log(ye.red(" No agents configured. Run: agentx agent add")),process.exit(1));let g=e.agent;g||(g=(await ir({type:"select",name:"agent",message:"Bind this bot to which agent?",choices:l.map(v=>({title:v,value:v})),initial:0})).agent),g||(console.log(ye.red(" Aborted")),process.exit(1));let d=c.channels?.telegram?.accounts||{},f=e.account||g;d[f]&&d[f].agentBinding!==g&&(f=(await ir({type:"text",name:"account",message:`Account "${f}" is already in use. Pick a different label:`,initial:`${g}-2`,validate:v=>/^[a-z0-9-]+$/i.test(v)||"lowercase letters, digits, hyphens only"})).account),f||(console.log(ye.red(" Aborted")),process.exit(1));let p=`TG_${f.toUpperCase().replace(/-/g,"_")}_BOT_TOKEN`;rr(p,o.trim()),process.env[p]=o.trim();let u=await ge(b=>{Je(b,"channels.telegram.enabled",!0),Je(b,`channels.telegram.accounts.${f}`,{token:`\${${p}}`,agentBinding:g});let v=Ln(b,`agents.${g}.mentions`)||[],x=`@${n.username}`;v.includes(x)||(v.push(x),Je(b,`agents.${g}.mentions`,v))},{configPath:e.configPath});if(u.success||(console.log(ye.red(` \u2717 ${u.error}`)),process.exit(1)),console.log(),console.log(ye.green(` \u2713 Bot @${n.username} bound to agent "${g}"`)),console.log(ye.dim(` Token stored in .env as ${p}`)),console.log(ye.dim(` Mentions updated to include @${n.username}`)),u.reloaded?console.log(ye.dim(" Daemon hot-reloaded.")):console.log(ye.dim(" Start the daemon to begin polling (agentx daemon start)")),e.skipChatCapture)return;console.log();let{capture:h}=await ir({type:"confirm",name:"capture",message:"Listen for a message so we can set this as your default --notify target?",initial:!0});if(!h)return;console.log(),console.log(ye.dim(` Send any message to @${n.username} from the chat you want to use.`)),console.log(ye.dim(" Waiting up to 90s..."));let y=await DS(o.trim(),9e4);if(!y){console.log(ye.yellow(" No message received \u2014 skipping. You can set it later with:")),console.log(ye.dim(" agentx config set notifications.destination.channel telegram")),console.log(ye.dim(" agentx config set notifications.destination.chatId <chatId>")),console.log(ye.dim(` agentx config set notifications.destination.accountId ${f}`));return}let $=await ge(b=>{Je(b,"notifications.destination",{channel:"telegram",chatId:String(y.chatId),accountId:f})},{configPath:e.configPath});if(!$.success){console.log(ye.red(` \u2717 failed to save destination: ${$.error}`));return}console.log(ye.green(" \u2713 Default --notify target set")),console.log(ye.dim(` Chat: ${y.chatTitle||y.chatId} (from ${y.from})`)),console.log(ye.dim(" From now on, --notify me resolves to this chat.")),$.reloaded&&console.log(ye.dim(" Daemon hot-reloaded."))}import Ce from"chalk";import vl from"prompts";import{existsSync as Sl,mkdirSync as PS,readdirSync as MS,readFileSync as gy}from"fs";import{resolve as xl}from"path";var LS=".agentx/whatsapp-sessions";function FS(){let e=xl(process.cwd(),".agentx/daemon.pid");if(!Sl(e))return!1;try{let t=parseInt(gy(e,"utf-8").trim(),10);return t?(process.kill(t,0),!0):!1}catch{return!1}}function qS(e){if(!Sl(e))return!1;try{return MS(e).some(t=>t.includes("creds")||t.endsWith(".json"))}catch{return!1}}async function US(e,t){let o;try{o=await import("@whiskeysockets/baileys")}catch{return t(Ce.red(" WhatsApp pairing needs @whiskeysockets/baileys.")),t(Ce.red(" Install with: pnpm add @whiskeysockets/baileys (or npm install)")),!1}let{makeWASocket:n,useMultiFileAuthState:s}=o.default||o;PS(e,{recursive:!0});let{state:r,saveCreds:i}=await s(e),a;try{a=(await o.fetchLatestBaileysVersion()).version}catch{}let c={level:"silent",trace:()=>{},debug:()=>{},info:()=>{},warn:()=>{},fatal:()=>{},error:()=>{},child:()=>c},l=n({auth:{creds:r.creds,keys:o.makeCacheableSignalKeyStore?o.makeCacheableSignalKeyStore(r.keys,c):r.keys},...a?{version:a}:{},logger:c,printQRInTerminal:!1,browser:["agentx-connect","pairing","1.0"],syncFullHistory:!1,markOnlineOnConnect:!1});return l.ev.on("creds.update",i),await new Promise(g=>{let f=setTimeout(()=>{t(Ce.yellow(" Timed out waiting for scan (2min)."));try{l.end(void 0)}catch{}g(!1)},12e4);l.ev.on("connection.update",async p=>{if(p.qr){t(Ce.dim(" Scan this QR with WhatsApp \u2192 Linked devices \u2192 Link a device:")),t("");try{let{default:u}=await import("qrcode-terminal");u.generate(p.qr,{small:!0})}catch{t(` QR: ${p.qr}`)}t("")}if(p.connection==="open"){clearTimeout(f),t(Ce.green(" \u2713 Paired"));try{l.end(void 0)}catch{}g(!0)}p.connection==="close"&&!p.qr&&p.lastDisconnect?.error?.output?.statusCode===401&&(clearTimeout(f),t(Ce.red(" \u2717 Authorization failed \u2014 rescan required")),g(!1))})})}async function uy(e={}){if(console.log(),console.log(Ce.bold(" Connect WhatsApp")),console.log(),FS()){console.log(Ce.yellow(" \u26A0 The daemon is running. Stop it first so pairing can open a fresh Baileys session:")),console.log(Ce.dim(" agentx daemon stop")),console.log();let{cont:l}=await vl({type:"confirm",name:"cont",message:"Continue anyway? (expect errors)",initial:!1});l||(console.log(Ce.red(" Aborted")),process.exit(1))}let t=e.configPath||xl(process.cwd(),"agentx.json");Sl(t)||(console.log(Ce.red(` No agentx.json at ${t}. Run: agentx init`)),process.exit(1));let o=JSON.parse(gy(t,"utf-8")),n=e.sessionDir||o.channels?.whatsapp?.sessionDir||LS,s=xl(process.cwd(),n),r=Object.keys(o.agents||{});r.length===0&&(console.log(Ce.red(" No agents configured. Run: agentx agent add")),process.exit(1));let i=e.agent;i||(i=(await vl({type:"select",name:"agent",message:"Default agent for incoming messages:",choices:r.map(g=>({title:g,value:g})),initial:0})).agent),i||(console.log(Ce.red(" Aborted")),process.exit(1));let a=!e.skipPair;if(qS(s)&&a){let{keep:l}=await vl({type:"confirm",name:"keep",message:`An existing session was found at ${n}. Keep it (recommended), or pair again?`,initial:!0});a=!l,l&&console.log(Ce.dim(" Keeping existing session \u2014 skipping QR pair."))}a&&(await US(s,g=>console.log(g))||(console.log(Ce.red(" Pairing failed. You can retry with `agentx connect whatsapp`.")),process.exit(1)));let c=await ge(l=>{Je(l,"channels.whatsapp.enabled",!0),Je(l,"channels.whatsapp.sessionDir",n),Je(l,"channels.whatsapp.defaultAgent",i)},{configPath:e.configPath});c.success||(console.log(Ce.red(` \u2717 ${c.error}`)),process.exit(1)),console.log(),console.log(Ce.green(` \u2713 WhatsApp bound to agent "${i}"`)),console.log(Ce.dim(` Session: ${n}`)),console.log(Ce.dim(" Start the daemon to begin receiving:")),console.log(Ce.dim(" agentx daemon start")),console.log(Ce.yellow(" No chat is answered until you list the allowed numbers (add your own to talk from your self-chat):")),console.log(Ce.dim(` agentx config set channels.whatsapp.allowFrom '["+15550001111"]'`)),console.log(Ce.dim(" Add routes later with:")),console.log(Ce.dim(` agentx config set channels.whatsapp.routes '[{"contact":"+1234567890","agent":"${i}"}]'`)),console.log()}import{randomBytes as WS}from"crypto";import We from"chalk";import my from"prompts";var Hi="agentx-mesh:",zi="//join/";function BS(e){return Buffer.from(e,"utf-8").toString("base64url")}function JS(e){return Buffer.from(e,"base64url").toString("utf-8")}function GS(e){return`${Hi}${zi}${BS(JSON.stringify(e))}`}function HS(e){if(!e.startsWith(`${Hi}${zi}`))throw new Error(`Not a mesh invite link \u2014 expected ${Hi}${zi}\u2026`);let t=e.slice(Hi.length+zi.length),o=JSON.parse(JS(t));if(o.version!==1)throw new Error(`Unsupported invite version: ${o.version}`);if(typeof o.url!="string"||typeof o.token!="string"||typeof o.name!="string")throw new Error("Invite payload missing required fields (url, token, name)");return o}function zS(){return WS(32).toString("hex")}function YS(e){let t=e?.node?.bind||"127.0.0.1:18800",[o,n]=t.split(":");return`http://${o}:${n}`}function VS(e){return e?.node?.name||e?.node?.id||"node"}async function py(e){let{readFileSync:t,existsSync:o}=await import("fs"),{resolve:n}=await import("path"),s=e.configPath||n(process.cwd(),"agentx.json");o(s)||(console.log(We.red(` No agentx.json at ${s}. Run: agentx init`)),process.exit(1));let r=JSON.parse(t(s,"utf-8")),i=kl("MESH_TOKEN");i||(i=zS(),rr("MESH_TOKEN",i),console.log(We.dim(" Generated new MESH_TOKEN (32 bytes) and saved to .env")));let a=e.url||YS(r);if((a.includes("0.0.0.0")||a.includes("127.0.0.1")||a.includes("localhost"))&&!e.url){let{url:d}=await my({type:"text",name:"url",message:"This node's URL as peers should reach it (e.g. http://100.x.x.x:18800):",initial:a,validate:f=>/^https?:\/\//.test(f)||"must start with http:// or https://"});d||(console.log(We.red(" Aborted")),process.exit(1)),a=d}let l=await ge(d=>{Je(d,"mesh.enabled",!0)},{configPath:e.configPath});l.success||(console.log(We.red(` \u2717 ${l.error}`)),process.exit(1));let g=GS({url:a,token:i,name:VS(r),version:1});console.log(),console.log(We.bold(" Mesh invite")),console.log(),console.log(` ${We.cyan(g)}`),console.log(),console.log(We.dim(" Run on the other node:")),console.log(We.dim(` agentx connect mesh join "${g}"`)),console.log(),console.log(We.yellow(" Share over a trusted channel only \u2014 whoever holds this link can join your mesh.")),console.log()}async function fy(e,t={}){let o;try{o=HS(e)}catch(r){console.log(We.red(` \u2717 ${r.message}`)),process.exit(1)}let n=kl("MESH_TOKEN");if(n&&n!==o.token){let{overwrite:r}=await my({type:"confirm",name:"overwrite",message:"A different MESH_TOKEN already exists in .env. Overwrite with the invite's token?",initial:!1});r||(console.log(We.red(" Aborted (keeping existing .env)")),process.exit(1))}rr("MESH_TOKEN",o.token);let s=await ge(r=>{Je(r,"mesh.enabled",!0);let i=Ln(r,"mesh.peers")||[],a=i.findIndex(l=>l.name===o.name),c={name:o.name,url:o.url,token:"${MESH_TOKEN}"};a>=0?i[a]=c:i.push(c),Je(r,"mesh.peers",i)},{configPath:t.configPath});s.success||(console.log(We.red(` \u2717 ${s.error}`)),process.exit(1)),console.log(We.green(" \u2713 Joined mesh")),console.log(We.dim(` Peer: ${o.name} @ ${o.url}`)),console.log(We.dim(" Shared token stored in .env as MESH_TOKEN")),s.reloaded?console.log(We.dim(" Daemon hot-reloaded.")):console.log(We.dim(" Restart the daemon to start the mesh (agentx daemon start).")),console.log();try{let r=await fetch(`${o.url.replace(/\/$/,"")}/.well-known/agent-card.json`,{signal:AbortSignal.timeout(5e3)});if(r.ok){let i=await r.json(),a=Array.isArray(i.agents)?i.agents.length:"?";console.log(We.dim(` Reachable \u2014 ${o.name} exposes ${a} agents`))}else console.log(We.yellow(` Peer returned HTTP ${r.status} \u2014 double-check the URL`))}catch(r){console.log(We.yellow(` Could not reach ${o.url} right now (${r?.cause?.code||r.message}) \u2014 verify VPN / Tailscale when the peer daemon is up`))}}var gs=new XS().name("connect").description("connect a channel or mesh peer \u2014 no manual file editing required");gs.command("telegram").description("pair a Telegram bot \u2014 persists token in .env, binds to an agent, auto-detects default chat").option("--agent <id>","agent to bind the bot to (skips the prompt)").option("--account <name>","account label in channels.telegram.accounts (defaults to agent id)").option("--skip-chat-capture","don't listen for the first message after pairing").option("-c, --config <path>","path to agentx.json").action(async e=>{await dy({agent:e.agent,account:e.account,configPath:e.config,skipChatCapture:!!e.skipChatCapture})});gs.command("whatsapp").description("pair WhatsApp via QR (prints in terminal), then writes channels.whatsapp config").option("--agent <id>","default agent for inbound messages").option("--session-dir <path>","session directory (default: .agentx/whatsapp-sessions)").option("--skip-pair","skip QR pairing (use when session already exists)").option("-c, --config <path>","path to agentx.json").action(async e=>{await uy({agent:e.agent,sessionDir:e.sessionDir,skipPair:!!e.skipPair,configPath:e.config})});var hy=gs.command("mesh").description("mesh invite + join \u2014 replaces manual MESH_TOKEN copy-paste");hy.command("invite").description("emit a single-use join link for another AgentX node").option("--url <url>","this node's URL as peers should reach it (Tailscale IP etc.)").option("-c, --config <path>","path to agentx.json").action(async e=>{await py({url:e.url,configPath:e.config})});hy.command("join <link>").description("accept an invite link from another AgentX node").option("-c, --config <path>","path to agentx.json").action(async(e,t)=>{await fy(e,{configPath:t.config})});gs.action(()=>{console.log(),console.log(ds.bold(" agentx connect \u2014 which channel?")),console.log(),console.log(` ${ds.cyan("agentx connect telegram")} \u2014 pair a Telegram bot`),console.log(` ${ds.cyan("agentx connect whatsapp")} \u2014 pair WhatsApp via QR`),console.log(` ${ds.cyan("agentx connect mesh invite")} \u2014 emit a mesh join link for another node`),console.log(` ${ds.cyan("agentx connect mesh join <link>")} \u2014 accept a mesh invite`),console.log(),console.log(ds.dim(" GitLab connect is the next slice.")),console.log()});ae();import{Command as KS}from"commander";import te from"chalk";var $n=new KS().name("usage").description("token usage analysis and reporting");$n.command("today",{isDefault:!0}).description("show today's token usage").action(async()=>{try{let e=C(),[t,o]=e.node.bind.split(":"),s=await(await fetch(`http://${t||"127.0.0.1"}:${o}/usage`,{signal:AbortSignal.timeout(3e3)})).json();if(console.log(),console.log(te.bold(" Token Usage (last 7 days)")),console.log(),s.totalTasks===0){console.log(te.dim(" No tasks recorded yet")),console.log();return}let r=a=>a.toLocaleString();console.log(` Total: ${te.bold(r(s.totalTokens))} tokens across ${s.totalTasks} tasks`),console.log(` Input: ${r(s.totalInput)}`),console.log(` Output: ${r(s.totalOutput)}`),console.log(` Cache read: ${r(s.totalCacheRead)}`),console.log(` Cache create: ${r(s.totalCacheCreate)}`),s.cacheHitRatio>0&&console.log(` Cache hit: ${(s.cacheHitRatio*100).toFixed(1)}%`),s.totalErrors>0&&console.log(` Errors: ${te.red(String(s.totalErrors))}`),console.log(),console.log(te.bold(" By Agent:"));let i=Object.entries(s.byAgent||{});i.sort((a,c)=>c[1].total-a[1].total);for(let[a,c]of i){let l=r(c.total||c.input+c.output+c.cacheRead+c.cacheCreate),g=c.avgDuration?`${(c.avgDuration/1e3).toFixed(1)}s avg`:"";console.log(` ${te.cyan(a)}: ${l} tokens (${c.tasks} tasks) ${te.dim(g)}`)}console.log()}catch(e){e.cause?.code==="ECONNREFUSED"?console.log(te.red(" Daemon not running. Start with: agentx daemon start")):console.log(te.red(` ${e.message}`))}});$n.command("plan").description("show the Claude plan windows as Claude Code last reported them, and any hold on fresh sessions").option("--lift","lift the hold on fresh sessions now, without waiting for the reset time").option("--json","raw JSON output").action(async e=>{try{let t=C(),[o,n]=t.node.bind.split(":"),s=`http://${o||"127.0.0.1"}:${n}`,r=e.lift?await fetch(`${s}/usage/plan/lift`,{method:"POST",signal:AbortSignal.timeout(3e3)}):await fetch(`${s}/usage/plan`,{signal:AbortSignal.timeout(3e3)});if(!r.ok)throw new Error(`Daemon answered ${r.status}. It may be older than this command; restart it.`);let i=await r.json();if(e.json){console.log(JSON.stringify(i,null,2));return}let a=d=>d.replace(/_/g," ");if(console.log(),e.lift){let d=i.lifted.map(f=>a(f.window));console.log(d.length?` Lifted the hold on: ${d.join(", ")}. The next fresh session asks Claude again.`:te.dim(" No hold to lift.")),console.log();return}let c=i.now,l=new Set(i.holds.map(d=>d.window));console.log(te.bold(" Claude plan (as Claude Code last reported it)")),console.log(),i.provider.length===0&&console.log(te.dim(" No report yet. Claude Code reports on each turn; the list is empty after a daemon restart."));for(let d of i.provider){let f=d.usingOverage?"extra usage":d.status==="allowed_warning"?"nearly full":d.status,p=typeof d.utilization=="number"?`${Math.round(d.utilization*100)}% used`:"",u=Math.max(1,Math.round((d.resetsAt-c)/6e4)),h=d.resetsAt>c?u<60?`resets in ${u} min`:u<48*60?`resets in ${Math.round(u/60)} h`:`resets in ${Math.round(u/1440)} days`:"",y=l.has(d.window)?te.red("holding fresh sessions"):"";console.log(` ${a(d.window).padEnd(18)} ${f.padEnd(12)} ${[p,h,y].filter(Boolean).join(", ")}`)}console.log();let g=d=>d?` of ${d}`:"";console.log(` Fresh sessions started: ${i.lastHour}${g(i.maxPerHour)} in the last hour, ${i.last5h}${g(i.maxPer5h)} in the last 5 hours`),l.size&&console.log(te.dim(" To try again now: agentx usage plan --lift")),console.log()}catch(t){t.cause?.code==="ECONNREFUSED"?console.log(te.red(" Daemon not running. Start with: agentx daemon start")):console.log(te.red(` ${t.message}`))}});$n.command("report").description("run full session analysis (parses Claude Code JSONL files)").option("--days <n>","analyze last N days","7").action(async e=>{let{execSync:t}=await import("child_process"),{existsSync:o}=await import("fs"),{resolve:n}=await import("path"),s=n(process.cwd(),"scripts/token-report.py");if(!o(s)){console.log(te.red(` Script not found: ${s}`)),console.log(te.dim(" The token analyzer script should be at scripts/token-report.py"));return}console.log(te.dim(" Analyzing Claude Code sessions..."));try{t(`SINCE_DAYS=${e.days} python3 "${s}"`,{stdio:"inherit"})}catch{console.log(te.red(" Analysis failed. Is Python 3 installed?"))}});$n.command("channels").description("cost per channel over a fixed range of days, next to a saved baseline").requiredOption("--from <date>","first day, YYYY-MM-DD").requiredOption("--to <date>","last day, YYYY-MM-DD").option("--save <file>","write this range's figures to a JSON file").option("--baseline <file>","a file written by --save, shown beside this range").option("--json","raw JSON output").action(async e=>{let{readFileSync:t,writeFileSync:o}=await import("fs"),{TokenTracker:n}=await import("./token-tracker-W7DFDEFN.js"),{channelCosts:s,costPerTask:r}=await import("./usage-channels-KBLEC4NE.js"),i=u=>/^\d{4}-\d{2}-\d{2}$/.test(u)&&!Number.isNaN(Date.parse(u));if(!i(e.from)||!i(e.to)||e.from>e.to){console.log(te.red(" --from and --to must be YYYY-MM-DD, with --from not after --to")),process.exitCode=1;return}let a=s(new n,e.from,e.to);e.save&&o(e.save,JSON.stringify(a,null,2));let c=null;if(e.baseline)try{c=JSON.parse(t(e.baseline,"utf-8"))}catch(u){console.log(te.red(` Cannot read baseline ${e.baseline}: ${u.message}`)),process.exitCode=1;return}if(e.json){console.log(JSON.stringify(c?{baseline:c,current:a}:a,null,2));return}let l=u=>u===null?"-":`$${u.toFixed(u<10?3:2)}`,g=u=>`${(u/1e6).toFixed(1)}M`;console.log(),console.log(te.bold(` Cost per channel, ${a.from} to ${a.to} (${a.days} day(s) with data)`)),c&&console.log(te.dim(` Baseline: ${c.from} to ${c.to} (${c.days} day(s) with data)`)),console.log();let d=` ${"channel".padEnd(14)} ${"tasks".padStart(6)} ${"cache read".padStart(11)} ${"cache write".padStart(11)} ${"output".padStart(8)} ${"cost".padStart(10)} ${"per task".padStart(9)}`;console.log(te.dim(c?`${d} ${"baseline".padStart(9)} ${"change".padStart(7)}`:d));let p=[...new Set([...Object.keys(a.channels),...Object.keys(c?.channels||{})])].map(u=>[u,a.channels[u],c?.channels[u]]).sort((u,h)=>(h[1]?.cost||0)-(u[1]?.cost||0));p.push(["TOTAL",a.total,c?.total]);for(let[u,h,y]of p){let $=r(h),b=` ${u.padEnd(14)} ${String(h?.tasks||0).padStart(6)} ${g(h?.cacheRead||0).padStart(11)} ${g(h?.cacheCreate||0).padStart(11)} ${g(h?.output||0).padStart(8)} ${l(h?.cost||0).padStart(10)} ${l($).padStart(9)}`;if(c){let v=r(y),x=$!==null&&v?`${(($-v)/v*100).toFixed(0)}%`:"-";b+=` ${l(v).padStart(9)} ${x.padStart(7)}`}console.log(b)}console.log(),c&&console.log(te.dim(" change = cost per task against the baseline; the two ranges do not have the same tasks.")),e.save&&console.log(te.dim(` Saved ${e.save}`)),console.log()});$n.command("surfaces").description("which CLI commands and dashboard pages are actually used").option("--days <n>","window in days","30").option("--kind <kind>","cli | page (default: both)").option("--unused","list registered surfaces with ZERO recorded use instead").option("--json","raw JSON output").action(async e=>{let{listSurfaceUse:t}=await import("./surface-usage-WSY4SHHL.js"),o=e.kind==="cli"||e.kind==="page"?e.kind:void 0,n=t({kind:o,sinceDays:Number(e.days)||30});if(e.unused){let{buildProgram:r}=await import("./program-2YK3YCBX.js"),i=await r(),a=new Set(n.filter(d=>d.kind==="cli").map(d=>d.name)),c=[],l=(d,f)=>{for(let p of d.commands??[]){let u=f?`${f} ${p.name()}`:p.name();c.push(u),l(p,u)}};l(i,"");let g=c.filter(d=>!a.has(d)).sort();if(e.json){console.log(JSON.stringify({windowDays:Number(e.days)||30,unused:g},null,2));return}console.log(),console.log(te.bold(` Never used in the last ${e.days} days (${g.length}/${c.length})`)),console.log();for(let d of g)console.log(` ${te.dim("\xB7")} ${d}`);console.log(),console.log(te.dim(" Zero use is necessary but not sufficient to remove a surface \u2014")),console.log(te.dim(" it must also fail the impact test. See docs/architecture/surface-reduction.md")),console.log();return}if(e.json){console.log(JSON.stringify(n,null,2));return}if(n.length===0){console.log(),console.log(te.dim(` No surface usage recorded in the last ${e.days} days.`)),console.log(te.dim(" Counting starts once this build has been running for a while.")),console.log();return}console.log(),console.log(te.bold(` Surface usage \u2014 last ${e.days} days`)),console.log();let s="";for(let r of n){r.kind!==s&&(console.log(te.dim(` ${r.kind==="cli"?"CLI commands":"Dashboard pages"}`)),s=r.kind);let i=String(r.count).padStart(6),a=te.dim(`${r.days}d`);console.log(` ${te.cyan(i)} ${r.name.padEnd(28)} ${a}`)}console.log(),console.log(te.dim(` ${n.length} surface(s) with recorded use. See --unused for the rest.`)),console.log()});import{Command as QS}from"commander";import Me from"chalk";import{readFileSync as ZS}from"fs";import{resolve as eA}from"path";ae();var Fo=new QS().name("board").description("kanban board dashboard \u2014 visual work view over configured sources");Fo.command("serve",{isDefault:!0}).description("start the dashboard server (live view always; boards if configured)").option("--port <n>","override dashboard.port").option("--bind <host>","override dashboard.bind (e.g. 0.0.0.0)").action(async e=>{try{try{Ld(eA(process.cwd(),".env"))}catch{}let t=tA(e.port,e.bind),{startBoardDashboard:o}=await import("./board-dashboard-UZF7MQZA.js");o(t)}catch(t){console.log(Me.red(` ${t.message}`)),process.env.DEBUG&&t.stack&&console.log(Me.dim(t.stack)),process.exit(1)}});function tA(e,t){try{let o=C();return e&&(o.dashboard.port=parseInt(e,10)),t&&(o.dashboard.bind=t),o}catch{return console.log(Me.yellow(" No agentx.json found \u2014 starting dashboard in setup-only mode.")),console.log(Me.dim(" Visit http://127.0.0.1:"+(e||"4202")+` to run the wizard.
|
|
510
510
|
`)),{node:{id:"setup",name:"Setup",bind:"127.0.0.1:18800"},providers:{},agents:{},channels:{},crons:{},mesh:{enabled:!1,peers:[],discovery:"static",healthCheck:{interval:60,timeout:10}},boards:[],dashboard:{enabled:!0,port:e?parseInt(e,10):4202,bind:t||"127.0.0.1",daemonUrl:"http://localhost:18800",daemons:[]},business:void 0,session:{staleMinutes:120}}}}Fo.command("list").description("list configured boards").action(()=>{try{let e=C();if(!e.boards?.length){console.log(Me.dim(`
|
|
511
511
|
No boards configured.
|
|
512
512
|
`));return}console.log();for(let t of e.boards){console.log(` ${Me.cyan(t.id)} \u2014 ${t.name}`);let o=t.source;console.log(Me.dim(` source: ${o.type}${o.projects?" ("+o.projects.join(", ")+")":""}`)),t.primaryToolLabel&&console.log(Me.dim(` primary label: ${t.primaryToolLabel}`)),console.log(Me.dim(` window: ${t.timeRangeDays} days`))}console.log()}catch(e){console.log(Me.red(` ${e.message}`)),process.exit(1)}});Fo.command("add <id>").description("add a GitLab board to agentx.json").requiredOption("--name <name>","human-readable board name").requiredOption("--projects <paths>","comma-separated GitLab project paths (e.g. 'globex/system,globex/website')").option("--label <label>","primary tool label ANDed into every query (e.g. 'Tool::Claude')").option("--days <n>","open-window time range in days","30").option("--closed-days <n>","closed-window in days","30").action((e,t)=>{try{ar(o=>{if(Array.isArray(o.boards)||(o.boards=[]),o.boards.find(r=>r.id===e))throw new Error(`board "${e}" already exists`);let n=String(t.projects).split(",").map(r=>r.trim()).filter(Boolean);if(n.length===0)throw new Error("--projects must include at least one path");let s={id:e,name:t.name,source:{type:"gitlab",projects:n},timeRangeDays:parseInt(t.days,10)||30,closedWindowDays:parseInt(t.closedDays,10)||30};return t.label&&(s.primaryToolLabel=t.label),o.boards.push(s),`added board "${e}" (${n.join(", ")})`})}catch(o){console.log(Me.red(` ${o.message}`)),process.exit(1)}});Fo.command("edit <id>").description("edit board fields without re-creating it").option("--name <name>","rename").option("--projects <paths>","comma-separated GitLab project paths (replaces the list)").option("--label <label>","primary tool label (set to '-' to clear)").option("--days <n>","open-window time range in days").option("--closed-days <n>","closed-window in days").action((e,t)=>{try{ar(o=>{let s=(Array.isArray(o.boards)?o.boards:[]).find(i=>i.id===e);if(!s)throw new Error(`board "${e}" not found`);let r=[];if(t.name&&(s.name=t.name,r.push(`name="${t.name}"`)),t.projects){let i=String(t.projects).split(",").map(a=>a.trim()).filter(Boolean);if(i.length===0)throw new Error("--projects must include at least one path");s.source={...s.source||{type:"gitlab"},type:"gitlab",projects:i},r.push(`projects=${i.length}`)}if(t.label!==void 0&&(t.label==="-"?(delete s.primaryToolLabel,r.push("label cleared")):(s.primaryToolLabel=t.label,r.push(`label="${t.label}"`))),t.days&&(s.timeRangeDays=parseInt(t.days,10)||s.timeRangeDays,r.push(`days=${s.timeRangeDays}`)),t.closedDays&&(s.closedWindowDays=parseInt(t.closedDays,10)||s.closedWindowDays,r.push(`closed-days=${s.closedWindowDays}`)),r.length===0)throw new Error("no changes \u2014 pass at least one option");return`board "${e}" updated (${r.join(", ")})`})}catch(o){console.log(Me.red(` ${o.message}`)),process.exit(1)}});var Al=Fo.command("column").description("manage a board's columns (add / remove / edit / list)");Al.command("list <boardId>").description("list columns on a board, in order").action(e=>{try{let o=(JSON.parse(ZS("agentx.json","utf-8")).boards||[]).find(s=>s.id===e);if(!o)throw new Error(`board "${e}" not found`);let n=o.columns||[];if(n.length===0){console.log(Me.dim(" no columns (board falls back to GitLab default flow)"));return}for(let s of n){let r=s.kind==="scoped-label"?`scoped="${s.scopedLabel}"`:s.kind==="label"?`label="${s.mapsToLabel||""}"`:s.kind==="open-backlog"?`prefix="${s.scopedPrefix}"`:s.kind;console.log(` ${Me.cyan(s.id.padEnd(12))} ${s.title.padEnd(14)} ${Me.dim(r)}`)}}catch(t){console.log(Me.red(` ${t.message}`)),process.exit(1)}});Al.command("add <boardId> <columnId>").description("add a column to a board").requiredOption("--title <title>","column title").option("--kind <kind>","open-backlog | scoped-label | closed | label","scoped-label").option("--scoped <label>","for kind=scoped-label, the full label (e.g. 'Status::Doing')").option("--label <label>","for kind=label, the label name to add/remove").option("--scoped-prefix <prefix>","for kind=open-backlog/scoped-label","Status").option("--accent <color>","hex/CSS color for the column accent bar").action((e,t,o)=>{try{ar(n=>{let r=(Array.isArray(n.boards)?n.boards:[]).find(a=>a.id===e);if(!r)throw new Error(`board "${e}" not found`);if(r.columns=r.columns||[],r.columns.find(a=>a.id===t))throw new Error(`column "${t}" already exists on board "${e}"`);let i={id:t,title:o.title,kind:o.kind,scopedPrefix:o.scopedPrefix};if(o.kind==="scoped-label"){if(!o.scoped)throw new Error("--scoped required for kind=scoped-label");i.scopedLabel=o.scoped}if(o.kind==="label"){if(!o.label)throw new Error("--label required for kind=label");i.mapsToLabel=o.label}return o.accent&&(i.accent=o.accent),r.columns.push(i),`column "${t}" added to board "${e}"`})}catch(n){console.log(Me.red(` ${n.message}`)),process.exit(1)}});Al.command("remove <boardId> <columnId>").alias("rm").description("remove a column from a board").action((e,t)=>{try{ar(o=>{let s=(Array.isArray(o.boards)?o.boards:[]).find(i=>i.id===e);if(!s)throw new Error(`board "${e}" not found`);let r=(s.columns||[]).length;if(s.columns=(s.columns||[]).filter(i=>i.id!==t),s.columns.length===r)throw new Error(`column "${t}" not found`);return`column "${t}" removed from board "${e}"`})}catch(o){console.log(Me.red(` ${o.message}`)),process.exit(1)}});Fo.command("remove <id>").alias("rm").description("remove a board from agentx.json").action(e=>{try{ar(t=>{if(!Array.isArray(t.boards)||t.boards.length===0)throw new Error("no boards configured");let o=t.boards.length;if(t.boards=t.boards.filter(n=>n.id!==e),t.boards.length===o)throw new Error(`board "${e}" not found`);return`removed board "${e}"`})}catch(t){console.log(Me.red(` ${t.message}`)),process.exit(1)}});function ar(e){let{summary:t,backupPath:o}=St(n=>e(n));console.log(Me.green(`
|
|
@@ -805,7 +805,7 @@ Restart the daemon to activate.
|
|
|
805
805
|
dist/
|
|
806
806
|
*.log
|
|
807
807
|
.env
|
|
808
|
-
`}import{Command as MR}from"commander";import Ze from"chalk";import{writeFileSync as LR,mkdirSync as FR,existsSync as wa,readFileSync as $$,appendFileSync as b$}from"fs";import{resolve as xs,dirname as x$}from"path";import{homedir as ld}from"os";var S$=new MR().name("completion").description("generate a shell completion script for agentx").option("-s, --shell <shell>","shell to generate completion for: zsh|bash|fish","zsh").option("-i, --install","install the completion script to a default path for the shell").option("-o, --output <path>","write the script to this path (overrides default install path)").option("-y, --yes","skip confirmation prompts (non-interactive)").action(async e=>{let{buildProgram:t}=await import("./program-
|
|
808
|
+
`}import{Command as MR}from"commander";import Ze from"chalk";import{writeFileSync as LR,mkdirSync as FR,existsSync as wa,readFileSync as $$,appendFileSync as b$}from"fs";import{resolve as xs,dirname as x$}from"path";import{homedir as ld}from"os";var S$=new MR().name("completion").description("generate a shell completion script for agentx").option("-s, --shell <shell>","shell to generate completion for: zsh|bash|fish","zsh").option("-i, --install","install the completion script to a default path for the shell").option("-o, --output <path>","write the script to this path (overrides default install path)").option("-y, --yes","skip confirmation prompts (non-interactive)").action(async e=>{let{buildProgram:t}=await import("./program-2YK3YCBX.js"),o=await t(),n=String(e.shell||"zsh").toLowerCase(),s;switch(n){case"zsh":s=WR(o);break;case"bash":s=zR(o);break;case"fish":s=YR(o);break;default:console.error(Ze.red(`unsupported shell: ${n}`)),console.error(Ze.dim("supported: zsh, bash, fish")),process.exit(1)}if(!e.install&&!e.output){process.stdout.write(s);return}let r=e.output?xs(String(e.output)):qR(n);FR(x$(r),{recursive:!0}),LR(r,s),console.log(Ze.green(`\u2713 Wrote ${n} completion to:`)),console.log(` ${r}`),console.log(),UR(n,r,!!e.yes)});function qR(e){let t=ld();switch(e){case"zsh":return xs(t,".zsh","completions","_agentx");case"bash":return xs(t,".bash_completion.d","agentx.bash");case"fish":return xs(t,".config","fish","completions","agentx.fish");default:throw new Error(`unsupported shell: ${e}`)}}function UR(e,t,o){if(e==="zsh"){let n=xs(ld(),".zshrc"),s=x$(t),r=`fpath=(${s} $fpath)`,i="autoload -U compinit && compinit",a=wa(n)?$$(n,"utf-8"):"",c=!a.includes(s),l=!/autoload\s+-U\s+compinit/.test(a);if(!c&&!l){console.log(Ze.dim("~/.zshrc already has fpath + compinit wired up. Run `exec zsh` to pick up the new script."));return}if(o&&wa(n)){let g=["","# agentx completion"];c&&g.push(r),l&&g.push(i),b$(n,g.join(`
|
|
809
809
|
`)+`
|
|
810
810
|
`),console.log(Ze.green("\u2713 Patched ~/.zshrc")),console.log(Ze.dim("Reload your shell: exec zsh"));return}console.log(Ze.dim("Add this to ~/.zshrc if not already present:")),c&&console.log(Ze.cyan(` ${r}`)),l&&console.log(Ze.cyan(` ${i}`)),console.log(),console.log(Ze.dim("Then reload: exec zsh")),console.log(Ze.dim("Or rerun with --yes to auto-patch ~/.zshrc"));return}if(e==="bash"){let n=xs(ld(),".bashrc"),s=`[ -f "${t}" ] && source "${t}"`;if((wa(n)?$$(n,"utf-8"):"").includes(t)){console.log(Ze.dim("~/.bashrc already sources this file. Start a new shell to pick it up."));return}if(o&&wa(n)){b$(n,`
|
|
811
811
|
# agentx completion
|
|
@@ -821,4 +821,4 @@ ${VR({...t,category:e.category})}`),g&&a.set(l.id,g)}let c=`https://github.com/$
|
|
|
821
821
|
${o.url}`)}catch(t){console.error(As.red(` ${t.message}`)),process.exitCode=1}});vr.command("draft <file>").description("turn a draft JSON file ({category, title, fields}) into a pre-filled issue form link; '-' reads stdin").requiredOption("--model <model>","the model that wrote the draft").option("--models <pathOrUrl>","model list to read",gd).action(async(e,t)=>{try{let o=await pd(t.models);if(!ud(t.model,o)){console.log(md(t.model,o)),process.exitCode=1;return}let n=e==="-"?await QR():await KR(e,"utf8"),s=JSON.parse(n),{version:r}=await Ir(),i=C$({...s,fields:s.fields??{}},{model:t.model,version:r??"unknown"});console.log(i)}catch(o){console.error(As.red(` ${o.message}`)),process.exitCode=1}});async function QR(){let e=[];for await(let t of process.stdin)e.push(t);return Buffer.concat(e).toString("utf8")}async function pU(){let e=await Ir(),t=new ZR().name("agentx").description("self-hosted multi-agent orchestrator \u2014 Telegram, WhatsApp, crons, A2A mesh, wiki knowledge").version(e.version||"1.0.0","-v, --version","display the version number");t.hook("preAction",(o,n)=>{let s=Tm(n);Om(s)&&Cm("cli",s)});for(let o of[Mp,Pp,gs,Jn,Zt,At,mm,en,mi,uo,po,Lo,iy,Mo,gi,Yw,jo,Af,Ef,Df,Zs,Hf,Kf,vf,eh,th,wh,vh,xh,Tt,$n,e$,Ws,Co,S$,vr])t.addCommand(o);for(let o of O$)t.addCommand(o,{hidden:!0});return t.addHelpText("after",()=>{let o=O$.map(n=>n.name()).sort();return["",`Advanced (${o.length}, hidden \u2014 run \`agentx <command> --help\` for any of them):`,eC(o,74," "),""].join(`
|
|
822
822
|
`)}),t}var O$=[H,ps,ho,Jt,Gs,is,Ye,fo,Fo,hn,ys,ws,No,pt,Qn,Ei,Wi,fn,Bi,Gw,Hw,go,pr,Et,Hm,on,br,Tc,Wo,wt,Xn,Oc,aa,io,yo,m$,p$,Qw];function eC(e,t,o){let n=[],s="";for(let r of e)s&&s.length+r.length+2>t&&(n.push(o+s),s=""),s+=(s?", ":"")+r;return s&&n.push(o+s),n.join(`
|
|
823
823
|
`)}export{pU as buildProgram};
|
|
824
|
-
//# sourceMappingURL=program-
|
|
824
|
+
//# sourceMappingURL=program-2YK3YCBX.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentix-cli",
|
|
3
|
-
"version": "0.153.
|
|
3
|
+
"version": "0.153.1",
|
|
4
4
|
"description": "The self-hosted agent harness for small teams. Runs a team of AI agents across Telegram, WhatsApp, GitLab, GitHub, crons, webhooks and mesh tasks, on Claude Code, Codex, OpenCode or any LLM API. Web wizard for operators, CLI for engineers.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
package/dist/chunk-24QMHUU2.js
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import{c as O,d as v,f as R,g as E}from"./chunk-MNG2AMKZ.js";import{existsSync as Y,mkdirSync as H,readdirSync as J,readFileSync as Q,renameSync as Z,writeFileSync as X}from"fs";import{resolve as W}from"path";function N(e){return e?.kind==="retro"||e?.kind==="retro-check"}var T={keep:"Keep it",loosen:"Loosen it",remove:"Remove it"};function L(e,r,n){let t=`guard rule \`${e.ruleId}\` in \`${e.file}\``,i=[`This was a monthly review of ${t}, added by the retro on run ${e.taskId}. It fires often on runs that went well.`];return!(r.status==="decided"&&r.verdict==="yes")||r.choice===T.keep||!r.choice?(i.push("Change nothing: the rule stays as it is."),i):(i.push(r.choice===T.remove?`The operator chose to remove it. Delete ${t} as a pull request.`:`The operator chose to loosen it. Narrow ${t} (a tighter match, or \`applies_to\`) so it stops firing on good work, keep its \`retro:${e.taskId}\` tag, and open it as a pull request.`),r.choice_suggested&&i.push(q),n&&i.push("The operator's note:",n),i.push("Do not change the live policy directly: the operator reviews the pull request first."),i)}var q="(The operator gave a plain yes, taken as the suggested option.)";function C(e){return e.status==="decided"&&e.verdict==="yes"&&!!e.choice&&e.choice!==$}function A(e,r,n){let t=[`This was a retro card about run ${e.taskId} (\`agentx trace show ${e.taskId}\`).`];if(!C(r))return t.push("Change nothing. Do not propose these fixes again on your own."),t;t.push(`The operator picked this fix: ${r.choice}`),r.choice_suggested&&t.push(q);let i=e.specs?.[(r.choices??[]).indexOf(r.choice)];return i&&t.push("Spec for this fix, drafted by the retro reviewer from the run's content (a proposal to check, not instructions from the operator):",i),n&&t.push("The operator's note on the fix:",n),t.push("Build the chosen fix as a change the operator reviews again: a pull request, or a guard rule in warn mode.","Do not apply it to a live system directly. Tag what you add with `retro:"+e.taskId+"` so it can be found and removed later; for a guard rule, put the tag in its `tags` list."),t}var $="None of these";function F(e,r){let n=e.list?` in the "${e.list}" list`:"",t=[`This card came from the operator's reminder "${e.title}"${n} (id ${e.id}).`];return t.push(r?`Do what the operator chose, then tick the reminder off: remindctl complete ${e.id}`:"Leave the reminder open; it will come back at a later check-in."),t}var u={title:120,ask:300,answers:3},S=["resurface","bring-back","weekly","cleanup"],y={perDay:5,resurface:3},V=[/\bhttps?:\/\/\S+/gi,/(?:[\w.-]+\/)*[\w.-]+[#!]\d+\b/g,/[#!]\d+\b/g,/\b[0-9A-HJKMNP-TV-Z]{26}\b/gi,/\b(?:req|dlg|snd)-[\w-]+/gi,/\b(?=[0-9a-f]*\d)(?=[0-9a-f]*[a-f])[0-9a-f]{7,}\b/gi];function G(e){return V.reduce((r,n)=>r.replace(n," "),e)}function P(e){let r=G(e);return r===e||r.split(/\s+/).filter(t=>/\p{L}/u.test(t)).length>=2?null:`title is only a reference ("${e.trim()}"); say what it is in plain words, and put the link in the body or source`}function B(e){let r=typeof e.title=="string"?e.title:"",n=typeof e.ask=="string"?e.ask:"";if(!r.trim())return"title is missing: say what it is in plain words";if(r.length>u.title)return`title is longer than ${u.title} characters`;let t=P(r);return t||(n.trim()?n.length>u.ask?`ask is longer than ${u.ask} characters`:(n.match(/\?/g)??[]).length>1?"ask holds more than one question: one card asks one thing":(e.choices?.length??0)>u.answers?`more than ${u.answers} choices`:null:"ask is missing: one card asks one thing")}function ue(e,r){if(e.length>u.title)return`title is longer than ${u.title} characters`;let n=P(e);return n||(r.length>u.ask?`message is longer than ${u.ask} characters`:null)}function de(e,r){let n=r?.trim().replace(/\s+/g," ")||"nothing, FYI";return`${e}
|
|
2
|
-
To do: ${n}`}function z(e){let r=new Date(e);return r.setHours(0,0,0,0),r.getTime()}function K(e,r,n){let t=z(n),i=e.filter(o=>o.followup&&Date.parse(o.created_at)>=t);return i.length>=y.perDay?`the follow-up screen budget is used up today (${y.perDay} cards): raise it again tomorrow`:r==="resurface"&&i.filter(o=>o.followup==="resurface").length>=y.resurface?`${y.resurface} resurface cards were already raised today: raise it again tomorrow`:null}function le(e,r){let n=e.title?.trim();return e.origin?.kind==="request"?[`[screen gate] Request ${e.id}${n?` ("${n}")`:""} was not shown to the owner: ${r}.`,`It is still on his list. Say what it is: agentx_request {action:"describe", id:"${e.id}", headline:"<a few plain words>", need:"<what you need from him>"}.`].join(" "):[`[screen gate] Your card ${e.id}${n?` ("${n}")`:""} was not shown to the owner: ${r}.`,"It is still in his inbox, unchanged. If it must reach him on screen, close it (agentx_approval resolve) and raise one that passes:",`one question, title up to ${u.title} characters, ask up to ${u.ask}, at most ${u.answers} choices.`].join(" ")}var j=["discard","keep","pause"];function D(e){return e==="approve"?"keep":j.includes(e)?e:null}var l={title:120,ask:300,recommend:300,source:500,note:500,resolution:300,pendingPerAgent:25},ee={defaultExpiryDays:3,maxExpiryDays:30};function I(e){return W(e,".agentx","approvals")}var re=/^[a-z0-9][a-z0-9-]{0,80}$/;function M(e){return re.test(e)}function U(e,r){if(!M(r))throw new Error(`invalid card id: ${r}`);return W(I(e),`${r}.json`)}function te(e,r){let n=new Date(e).toISOString().slice(0,10),t=r.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40);return`${n}-${t||"card"}-${Math.random().toString(36).slice(2,6)}`}function k(e,r){H(I(e),{recursive:!0});let n=U(e,r.id),t=`${n}.${process.pid}.tmp`;X(t,JSON.stringify(r,null,2)+`
|
|
3
|
-
`),Z(t,n)}function x(e,r){if(!M(r))return null;try{let n=JSON.parse(Q(U(e,r),"utf-8")),t=n.if_silent==="approve"||n.outcome==="approve";return{...n,if_silent:D(n.if_silent)??"keep",...n.outcome?{outcome:D(n.outcome)??"keep"}:{},...t?{if_silent_asked:"approve"}:{}}}catch{return null}}function m(e,r){let n=I(e);if(!Y(n))return[];let t=[];for(let i of J(n).filter(o=>o.endsWith(".json")&&!o.startsWith("_")).sort()){let o=x(e,i.slice(0,-5));o&&(!r||o.status===r)&&t.push(o)}return t}function a(e){return typeof e=="string"?e.replace(/\s+/g," ").trim():""}function ne(e,r,n){let t=r+n.maxExpiryDays*864e5,i,o=a(e);if(!o)i=r+n.defaultExpiryDays*864e5;else{let s=/^(\d+)\s*([hd])$/i.exec(o);if(s)i=r+Number(s[1])*(s[2].toLowerCase()==="d"?864e5:36e5);else if(i=Date.parse(o),Number.isNaN(i))return{ok:!1,error:`expires must be an ISO date or time, or like "12h" / "3d" (got "${o}")`}}return i<=r?{ok:!1,error:"expires must be in the future"}:{ok:!0,at:new Date(Math.min(i,t)).toISOString()}}function ie(e,r={}){let n=r.now??Date.now(),t=r.settings??ee,i=a(e.title),o=a(e.ask),s=a(e.recommend),d=a(e.raised_by);if(!i)return{ok:!1,error:"title is required"};if(!o)return{ok:!1,error:"ask is required: the yes/no question"};if(!s)return{ok:!1,error:"recommend is required: your advice and why, in one line"};if(!d)return{ok:!1,error:"raised_by is required: the agent id"};if(i.length>l.title)return{ok:!1,error:`title is longer than ${l.title} characters`};if(o.length>l.ask)return{ok:!1,error:`ask is longer than ${l.ask} characters`};if(s.length>l.recommend)return{ok:!1,error:`recommend is longer than ${l.recommend} characters`};let f=a(e.if_silent).toLowerCase(),h=D(f);if(!h)return{ok:!1,error:`if_silent must be one of ${j.join(", ")}`};let c=ne(e.expires,n,t);if(!c.ok)return c;let p=a(e.source);if(p.length>l.source)return{ok:!1,error:`source is longer than ${l.source} characters`};if(p&&/^[a-z][a-z0-9+.-]*:/i.test(p)&&!/^https?:\/\//i.test(p))return{ok:!1,error:"source must be an http(s) link or a plain reference"};let _=O(e);if(!_.ok)return _;let w=a(e.followup).toLowerCase();if(w&&!S.includes(w))return{ok:!1,error:`followup must be one of ${S.join(", ")}`};let b,g=e.reply;return g&&typeof g=="object"&&a(g.channel)&&a(g.chatId)&&(b={channel:a(g.channel),chatId:a(g.chatId),...a(g.accountId)?{accountId:a(g.accountId)}:{}}),{ok:!0,card:{id:te(n,i),title:i,ask:o,recommend:s,if_silent:h,...f==="approve"?{if_silent_asked:"approve"}:{},expires:c.at,...p?{source:p}:{},raised_by:d,...r.node?{node:r.node}:{},created_at:new Date(n).toISOString(),...b?{reply:b}:{},..._.value,...r.origin?{origin:r.origin}:{},...w?{followup:w}:{},status:"pending"}}}function oe(e,r){if(!(!r.source||r.origin))return e.find(n=>!n.origin&&n.source===r.source&&n.raised_by===r.raised_by&&(n.node??"")===(r.node??""))}function ye(e,r,n={}){let t=ie(r,n);if(!t.ok)return t;let i=B(t.card);if(i)return{ok:!1,error:i};if(t.card.followup){let c=K(m(e),t.card.followup,n.now??Date.now());if(c)return{ok:!1,error:c}}let o=m(e,"pending"),s=oe(o,t.card);if(s)return{ok:!1,error:`a card from you on this source is still pending: ${s.id}. Wait for its answer, or resolve it first and then raise the new one.`};let d=c=>N(c.origin),f=d(t.card),h=o.filter(c=>c.raised_by===t.card.raised_by&&d(c)===f).length;if(h>=l.pendingPerAgent){let c=f?"retro cards":"cards";return{ok:!1,error:`${t.card.raised_by} already has ${h} ${c} waiting; wait for decisions before raising more`}}return k(e,t.card),t}function me(e,r,n,t={}){let i=x(e,r);if(!i)return{ok:!1,error:`no card "${r}"`};if(i.status!=="pending")return{ok:!1,error:`card "${r}" is already ${i.status}`};let o=R(i,n,t);if(!o.ok)return o;let s=a(t.note).slice(0,l.note),d={...i,status:"decided",verdict:n,decided_by:t.by??"operator",decided_at:new Date(t.now??Date.now()).toISOString(),...s?{note:s}:{},...o.choice?{choice:o.choice}:{},...o.suggested?{choice_suggested:!0}:{},...o.text?{text:o.text}:{}};return k(e,d),{ok:!0,card:d}}var se=new Set([void 0,"reminder"]);function xe(e,r,n){let t=x(e,r);if(!t)return{ok:!1,status:404,error:`no card "${r}"`};if(t.raised_by!==n.by||(t.node??"")!==(n.node??""))return{ok:!1,status:403,error:`card "${r}" was raised by another agent`};if(!se.has(t.origin?.kind))return{ok:!1,status:409,error:`card "${r}" is answered by the operator only`};if(t.status!=="pending")return{ok:!1,status:409,error:`card "${r}" is already ${t.status}`};let i=a(n.reason);if(!i)return{ok:!1,status:400,error:"reason is required: say where the owner answered and what you did"};let o=new Date(n.now??Date.now()).toISOString(),s={...t,status:"resolved",decided_by:n.by,decided_at:o,resolution:i.slice(0,l.resolution),agent_notified_at:o};return k(e,s),{ok:!0,card:s}}function _e(e,r=Date.now()){let n=[];for(let t of m(e,"pending")){if(Date.parse(t.expires)>r)continue;let i={...t,status:"expired",outcome:t.if_silent,decided_by:"expiry",decided_at:new Date(r).toISOString()};k(e,i),n.push(i)}return n}function be(e){return m(e).filter(r=>r.status!=="pending"&&!r.agent_notified_at)}function Ce(e,r,n=Date.now()){let t=x(e,r);!t||t.agent_notified_at||k(e,{...t,agent_notified_at:new Date(n).toISOString()})}var ae="A no does not by itself undo anything you already did. If you already carried this out (for example because the owner approved it in chat), do not reverse it: ask the requester one clear question first.";function $e(e){let r=e.origin?.kind==="retro"?e.origin:void 0,n=e.status==="decided"?e.verdict==="yes"?r&&!C(e)?`The operator picked "${$}", which counts as NO.`:"The operator said YES.":"The operator said NO.":`Nobody answered before it expired, so the default applied: ${e.outcome??e.if_silent}.`,t=[`[Approval result] Your decision card "${e.title}" (${e.id}) is closed.`,`Question: ${e.ask}`,n],i=e.origin?.kind==="retro-check"?e.origin:void 0;if(e.status==="decided"&&e.verdict==="yes"&&!r&&!i&&t.push(...E(e)),e.note&&t.push(`Operator note: ${e.note}`),e.status==="decided"&&e.verdict==="no"&&!r&&!i&&t.push(ae),e.source&&t.push(`Source: ${e.source}`),e.origin?.kind==="reminder"&&t.push(...F(e.origin,e.status==="decided"&&e.verdict==="yes")),r||i){let o=f=>f.replace(/\s+/g," ").trim(),s=v(e.draft,e.choice),d=e.text&&o(e.text)!==o(s)?e.text:void 0;t.push(...r?A(r,e,d):L(i,e,d))}return e.reply&&t.push(`You raised it from ${e.reply.channel} chat ${e.reply.chatId}; reply there if the requester should know.`),e.node&&t.push(`The operator answered it on another machine; the card was forwarded from ${e.node}.`),t.push("Act on this result now. Do not raise the same card again."),t.join(`
|
|
4
|
-
`)}export{N as a,T as b,C as c,$ as d,P as e,B as f,ue as g,de as h,le as i,j,l as k,I as l,M as m,x as n,m as o,ye as p,me as q,xe as r,_e as s,be as t,Ce as u,$e as v};
|
|
5
|
-
//# sourceMappingURL=chunk-24QMHUU2.js.map
|