@aicommander/agent 1.0.24 → 1.0.27
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/bin/agent.js +14 -14
- package/dist/index.js +8 -8
- package/dist/src/update-check.d.ts +7 -0
- package/dist/src/version.d.ts +1 -1
- package/package.json +1 -1
package/dist/bin/agent.js
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{program as
|
|
3
|
-
`),process.exit(127)}let
|
|
4
|
-
`),process.exit(127))}),m.on("exit",(
|
|
5
|
-
`).filter(d=>d.startsWith(`${v}:`));if(t.length===0)throw new Error(`secure exec user "${v}" not found in /etc/passwd \u2014 reinstall the agent to create it`);if(t.length>1)throw new Error(`multiple /etc/passwd entries for "${v}" \u2014 refusing to guess which is the sandbox user`);let r=t[0].split(":");if(r.length!==7)throw new Error(`malformed passwd entry for ${v}`);let s=r[2],i=r[3];if(!/^\d+$/.test(s)||!/^\d+$/.test(i))throw new Error(`could not resolve uid/gid for ${v}`);let o=Number(s),l=Number(i),a=r[5]||"/tmp";if(!Number.isInteger(o)||!Number.isInteger(l))throw new Error(`could not resolve uid/gid for ${v}`);if(o===0)throw new Error("refusing to run secure exec as uid 0");if(l===0)throw new Error("refusing to run secure exec as gid 0");return{uid:o,gid:l,home:a,name:v}}var
|
|
6
|
-
\u26A0 Ignoring malformed AICOMMANDER_SERVER (${t}); using ${
|
|
7
|
-
`),
|
|
2
|
+
import{program as _}from"commander";var O="1.0.27";import dn from"os";function un(e){try{let t=JSON.parse(e);return typeof t.code=="string"?t.code:null}catch{return null}}var we=class extends Error{constructor(r,s){super(`Registration failed (${r}): ${s}`);this.status=r;this.body=s;this.name="RegistrationError",this.code=un(s)}status;body;code};async function Re(e,t,r={}){let s={hostname:dn.hostname(),platform:process.platform,arch:process.arch,agentVersion:O,...t?{deviceId:t.deviceId,deviceSecret:t.deviceSecret}:{},...r.forceNew?{forceNew:!0}:r.currentCode?{currentCode:r.currentCode}:{}},i=await fetch(`${e}/api/register`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)});if(!i.ok){let o=await i.text();throw new we(i.status,o)}return i.json()}import _n from"ws";import kn from"os";import{spawn as ze}from"child_process";var I="https://aicommander.dev";var v="aicommander-exec";var qe="ABCDEFGHJKMNPQRSTVWXYZ0123456789";var ur=Math.floor(256/qe.length)*qe.length;var mn=/^AIC-[ABCDEFGHJKMNPQRSTVWXYZ0-9]{4}-[ABCDEFGHJKMNPQRSTVWXYZ0-9]{4}-[ABCDEFGHJKMNPQRSTVWXYZ0-9]{4}$/;function pn(e){return mn.test(e.trim().toUpperCase())}function j(e){return pn(e)?`AIC-${e.slice(4,8)}-***-***`:e}function Ze(e,t){let r=e.split(".").map(o=>parseInt(o,10)||0),s=t.split(".").map(o=>parseInt(o,10)||0),i=Math.max(r.length,s.length);for(let o=0;o<i;o++){let l=(r[o]??0)-(s[o]??0);if(l!==0)return l>0?1:-1}return 0}function Qe(e,t){return Ze(e,t)>0}var et=process.platform==="win32";function tt(e,t,r,s){let i=Date.now(),o=ze(e,[],{shell:!0,cwd:t??process.env.HOME,env:{...process.env,...r??{}},stdio:["ignore","pipe","pipe"],detached:!et}),l=!1,a=null,d=()=>{a&&(clearTimeout(a),a=null)};o.stdout?.on("data",f=>{s.onOutput(f.toString("base64"),"stdout")}),o.stderr?.on("data",f=>{s.onOutput(f.toString("base64"),"stderr")}),o.on("close",f=>{l=!0,d(),s.onDone(f??-1,Date.now()-i)}),o.on("error",f=>{l=!0,d(),s.onError(f.message)});let c=f=>{if(!(l||o.pid==null))try{et?ze("taskkill",["/pid",String(o.pid),"/T","/F"],{stdio:"ignore"}):process.kill(-o.pid,f)}catch{}};return{kill:()=>{l||(c("SIGTERM"),d(),a=setTimeout(()=>{a=null,c("SIGKILL")},5e3),a.unref?.())}}}import{spawn as En}from"node:child_process";import{readFileSync as vn}from"node:fs";import{isAbsolute as bn}from"node:path";import{spawn as fn}from"node:child_process";import{createRequire as gn}from"node:module";import{constants as Sn}from"node:os";var hn=gn(import.meta.url),Te="__secure-exec-drop",yn="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";function nt(){let e=!1;try{e=hn("node:sea").isSea()}catch{}return e}function rt(e){let t=process.argv[1],r=!nt()&&t?[t,Te,...e]:[Te,...e];return{cmd:process.execPath,args:r}}function ot(e){let t=nt()?1:2;if(e[t]!==Te)return!1;let[r,s,i,o,...l]=e.slice(t+1),a=Number(r),d=Number(s),c=process;try{if(!Number.isInteger(a)||!Number.isInteger(d)||a===0||!i||!o)throw new Error("invalid drop arguments");if(typeof c.initgroups!="function"||typeof c.setgid!="function"||typeof c.setuid!="function")throw new Error("privilege-drop syscalls unavailable on this platform");if(c.initgroups(i,d),c.setgid(d),c.setuid(a),c.getuid?.()===0||c.getgid?.()===0)throw new Error("privilege drop did not take effect");if(typeof c.getgroups=="function"){let p=c.getgroups();if(Array.isArray(p)&&p.includes(0))throw new Error("privilege drop left root's supplementary groups in place")}}catch(p){process.stderr.write(`secure-exec: privilege drop failed: ${p instanceof Error?p.message:String(p)}
|
|
3
|
+
`),process.exit(127)}let f={...process.env,PATH:yn},m=fn(o,l,{stdio:"inherit",env:f,shell:!1}),u=!1;return m.on("error",p=>{u||(u=!0,process.stderr.write(`secure-exec: spawn failed: ${p.message}
|
|
4
|
+
`),process.exit(127))}),m.on("exit",(p,S)=>{if(!u){if(u=!0,S){let A=Sn.signals[S];process.exit(128+(typeof A=="number"?A:0))}process.exit(p??0)}}),!0}function wn(){let e;try{e=vn("/etc/passwd","utf8")}catch{throw new Error("cannot read /etc/passwd to resolve the secure-exec user")}let t=e.split(`
|
|
5
|
+
`).filter(d=>d.startsWith(`${v}:`));if(t.length===0)throw new Error(`secure exec user "${v}" not found in /etc/passwd \u2014 reinstall the agent to create it`);if(t.length>1)throw new Error(`multiple /etc/passwd entries for "${v}" \u2014 refusing to guess which is the sandbox user`);let r=t[0].split(":");if(r.length!==7)throw new Error(`malformed passwd entry for ${v}`);let s=r[2],i=r[3];if(!/^\d+$/.test(s)||!/^\d+$/.test(i))throw new Error(`could not resolve uid/gid for ${v}`);let o=Number(s),l=Number(i),a=r[5]||"/tmp";if(!Number.isInteger(o)||!Number.isInteger(l))throw new Error(`could not resolve uid/gid for ${v}`);if(o===0)throw new Error("refusing to run secure exec as uid 0");if(l===0)throw new Error("refusing to run secure exec as gid 0");return{uid:o,gid:l,home:a,name:v}}var Rn="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",An=new Set(["NODE_OPTIONS","BASH_ENV","ENV","IFS","PYTHONSTARTUP","PYTHONPATH","PERL5OPT","RUBYOPT","GIT_EXTERNAL_DIFF","PATH","HOME","USER","LOGNAME"]),Tn=e=>/^LD_/.test(e)||An.has(e);function it(e,t){if(process.platform!=="linux")throw new Error("secure exec is only supported on Linux");if(typeof process.getuid=="function"&&process.getuid()!==0)throw new Error("secure exec requires the agent to run as root (to drop privilege)");let r=e.argv;if(!Array.isArray(r)||r.length===0||typeof r[0]!="string")throw new Error("secure exec requires a non-empty argv");let s=r[0];if(s.includes("/"))throw new Error(`command "${s}" must be a bare command name (no path separators)`);if(!e.allowedCommands.includes(s))throw new Error(`command "${s}" is not in this token's allowlist`);if(e.cwd!=null&&!bn(e.cwd))throw new Error(`cwd "${e.cwd}" must be an absolute path`);let i=null;if(e.input!=null&&(i=Buffer.from(e.input,"base64"),i.length>262144))throw new Error(`stdin payload ${i.length}B exceeds limit ${262144}B`);let o=wn(),l={HOME:o.home,USER:o.name,LOGNAME:o.name,PATH:Rn},a={};for(let[g,U]of Object.entries(e.env??{}))Tn(g)||(a[g]=U);let d={...a,...l},c=Date.now(),{cmd:f,args:m}=rt([String(o.uid),String(o.gid),o.name,s,...r.slice(1)]),u=En(f,m,{cwd:e.cwd??o.home,env:d,stdio:["pipe","pipe","pipe"],detached:!0,shell:!1});i&&u.stdin?(u.stdin.on("error",()=>{}),u.stdin.end(i)):u.stdin?.end();let p=!1,S=null,A=()=>{S&&(clearTimeout(S),S=null)};u.stdout?.on("data",g=>{t.onOutput(g.toString("base64"),"stdout")}),u.stderr?.on("data",g=>{t.onOutput(g.toString("base64"),"stderr")}),u.on("close",g=>{p=!0,A(),t.onDone(g??-1,Date.now()-c)}),u.on("error",g=>{p=!0,A(),t.onError(g.message)});let T=g=>{if(!(p||u.pid==null))try{process.kill(-u.pid,g)}catch{}};return{kill:()=>{p||(T("SIGTERM"),A(),S=setTimeout(()=>{S=null,T("SIGKILL")},5e3),S.unref?.())}}}import w from"chalk";function at(e,t,r=!1){console.clear(),console.log(),console.log(w.bold.cyan(" AI Commander \u2014 Remote Agent")),console.log(),console.log(w.gray(" Session code:")),console.log(),console.log(w.bold.greenBright(` ${r?e:j(e)}`)),console.log(),r||console.log(w.gray(" Reveal the full code: sudo aicommander-agent status --reveal")),console.log(w.gray(" Then give it to your admin to connect.")),console.log(w.yellow(" \u26A0 Keep this code secret \u2014 anyone who has it can run commands as root here.")),console.log(w.gray(` Server: ${t}`)),console.log(w.gray(" Press Ctrl+C to disconnect.")),console.log(),console.log(w.yellow(" Waiting for admin connection...")),console.log()}function ct(e,t){console.log(w.yellow(` Reconnecting in ${Math.round(t/1e3)}s (attempt ${e})...`))}function lt(){console.log(w.green(" Connected to relay \u2014 ready for commands."))}function xe(e){console.log(w.cyan(` > ${e}`))}function _e(e,t){let r=e===0?w.green:w.red;console.log(r(` Done. Exit ${e} in ${t}ms`))}function xn(){let e=process.env.AICOMMANDER_DEV;return e==="1"||e==="true"}function ut(e){let t=e.toLowerCase();return t==="localhost"||t==="127.0.0.1"||t==="::1"||t==="[::1]"}function dt(){return new URL(I).origin}function mt(e){let t=(e??"").trim();if(!t)return I;let r;try{r=new URL(t)}catch{return console.warn(`
|
|
6
|
+
\u26A0 Ignoring malformed AICOMMANDER_SERVER (${t}); using ${I}.
|
|
7
|
+
`),I}if(r.origin===dt())return I;let s=ut(r.hostname);return!xn()&&!s?(console.warn(`
|
|
8
8
|
\u26A0 AICOMMANDER_SERVER override to "${r.origin}" ignored for safety \u2014
|
|
9
|
-
the agent runs as root and is host-locked to ${
|
|
9
|
+
the agent runs as root and is host-locked to ${dt()}.
|
|
10
10
|
Set AICOMMANDER_DEV=1 to allow a custom relay for development.
|
|
11
|
-
`),
|
|
12
|
-
\u26A0 Refusing plaintext relay "${r.origin}" (https required); using ${
|
|
13
|
-
`),k):t.replace(/\/+$/,"")}function ut(e){let t;try{t=new URL(e)}catch{throw new Error("Refusing to connect: malformed relay WebSocket URL.")}if(t.protocol!=="wss:"&&!(t.protocol==="ws:"&<(t.hostname)))throw new Error(`Refusing to open a plaintext relay socket (${t.protocol}//${t.host}); wss:// required.`)}var Ie={capable:!1,enabled:!1,expiresAt:null},pt=360*60*1e3;function xn(e,t){return new Promise((r,s)=>{if(t.aborted){s(new DOMException("Aborted","AbortError"));return}let i=setTimeout(r,e);t.addEventListener("abort",()=>{clearTimeout(i),s(new DOMException("Aborted","AbortError"))},{once:!0})})}async function gt(e){let t=1e3,r=3e4,s=0,i=e.agentToken;for(;!e.signal?.aborted;){s++;try{let o=await In(e,i);if(t=1e3,s=0,o&&e.reauth&&!e.signal?.aborted)try{i=await e.reauth()}catch{}}catch(o){if(o.name==="AbortError")break;let l=.8+Math.random()*.4,a=Math.min(t*l,r);e.silent||it(s,a);try{await xn(a,e.signal??new AbortController().signal)}catch(d){if(d.name==="AbortError")break}t=Math.min(t*2,r)}}}async function In(e,t){let{serverUrl:r,sessionCode:s}=e,i=`${r.replace(/^http/,"ws")}/ws/agent/${s}?token=${t}`;ut(i);let o=c=>{try{e.onStatus?.(c)}catch{}},l=c=>{try{e.onActivity?.(c)}catch{}},a=c=>{if(c)try{e.onRemoteActivity?.(c)}catch{}},d=()=>{try{e.onHeartbeat?.()}catch{}};return o("connecting"),new Promise((c,p)=>{let m=new Tn(i),u=null,f=null,S=null,A=!1,T=null,g=!1,U=()=>{S&&(clearTimeout(S),S=null)},y=()=>{T&&(clearInterval(T),T=null)},V=()=>{if(!e.reauthIntervalMs||!e.reauth)return;let E=()=>{if(u){S=setTimeout(E,3e4);return}A=!0,m.close(1e3,"reauth")};S=setTimeout(E,e.reauthIntervalMs)},Z=()=>{g||(g=!0,U(),y(),c(A))},Q=E=>{g||(g=!0,U(),y(),p(E))},Xe=()=>{d(),f&&clearTimeout(f),f=setTimeout(()=>{m.terminate(),Q(new Error("Ping timeout"))},9e4)};e.signal?.addEventListener("abort",()=>m.close(1e3,"disabled"),{once:!0});let ye=()=>{if(g)return;let E={type:"agent:screen_state",screenShare:e.screenShare?.getState()??Ie};try{m.send(JSON.stringify(E))}catch{}};m.on("open",()=>{o("connected"),Xe(),V();let E={type:"agent:register",hostname:_n.hostname(),platform:process.platform,arch:process.arch,agentVersion:O,screenShare:e.screenShare?.getState()??Ie};m.send(JSON.stringify(E)),e.screenShare?.on("change",ye),e.silent||at()}),m.on("message",E=>{let R;try{R=JSON.parse(String(E))}catch{return}switch(R.type){case"do:ping":Xe();let sn={type:"agent:pong",ts:R.ts};m.send(JSON.stringify(sn));break;case"do:exec":{_e(R.command),l(!0),a(R.operator);let{commandId:D,command:W,cwd:Ee,env:ve}=R,oe=Date.now(),z=!1;y(),T=setInterval(()=>{let _=Date.now()-oe;if(_>=3e5&&!z){z=!0;let h={type:"agent:exec_idle",commandId:D,idleMs:_};m.send(JSON.stringify(h))}},3e5),T.unref?.(),u=ze(W,Ee,ve,{onOutput:(_,h)=>{oe=Date.now(),z=!1;let I={type:"agent:output",commandId:D,chunk:_,stream:h};m.send(JSON.stringify(I))},onDone:(_,h)=>{u=null,y(),l(!1),xe(_,h);let I={type:"agent:done",commandId:D,exitCode:_,durationMs:h};m.send(JSON.stringify(I))},onError:_=>{u=null,y(),l(!1);let h={type:"agent:error",commandId:D,error:_};m.send(JSON.stringify(h))}}).kill;break}case"do:secure_exec":{let{commandId:D,argv:W,allowedCommands:Ee,input:ve,cwd:oe,env:z}=R,Ye=(h,I)=>{u=null,y(),l(!1),xe(h,I);let ee={type:"agent:done",commandId:D,exitCode:h,durationMs:I};m.send(JSON.stringify(ee))},_=h=>{u=null,y(),l(!1);let I={type:"agent:error",commandId:D,error:h};m.send(JSON.stringify(I))};try{let h=Array.isArray(W)?W.join(" "):String(W);_e(h),l(!0);let I=Date.now(),ee=!1;y(),T=setInterval(()=>{let se=Date.now()-I;if(se>=3e5&&!ee){ee=!0;let be={type:"agent:exec_idle",commandId:D,idleMs:se};m.send(JSON.stringify(be))}},3e5),T.unref?.(),u=ot({argv:W,allowedCommands:Ee,input:ve,cwd:oe,env:z},{onOutput:(se,be)=>{I=Date.now(),ee=!1;let an={type:"agent:output",commandId:D,chunk:se,stream:be};m.send(JSON.stringify(an))},onDone:Ye,onError:_}).kill}catch(h){_(h instanceof Error?h.message:String(h))}break}case"do:kill":u&&(u(),u=null);break;case"do:screenshot":a(R.operator),kn(m,R.requestId,e.screenShare);break}}),m.on("close",(E,R)=>{f&&clearTimeout(f),y(),e.screenShare?.off("change",ye),u&&(u(),u=null,l(!1)),o("disconnected"),E===1e3||E===4001?Z():Q(new Error(`WS closed: ${E} ${String(R)}`))}),m.on("error",E=>{f&&clearTimeout(f),y(),e.screenShare?.off("change",ye),o("disconnected"),Q(E)}),m.on("unexpected-response",(E,R)=>{p(new Error(`Unexpected HTTP response: ${R.statusCode}`))})})}async function kn(e,t,r){let s=l=>{try{e.send(JSON.stringify(l))}catch{}},i=l=>s({type:"agent:screenshot_error",requestId:t,error:l}),o=r?.getState()??Ie;if(!r||!o.capable){i("This machine cannot share its screen (desktop macOS/Windows only).");return}if(!o.enabled){i("Screen sharing is turned off on this machine.");return}try{let{data:l,mimeType:a}=await r.capture();if(l.length>10485760){let c=(l.length/1048576).toFixed(1);i(`Screenshot is ${c} MB, which exceeds the ${10485760/(1024*1024)} MB limit.`);return}let d=l.toString("base64");for(let c=0;c<d.length;c+=524288)s({type:"agent:screenshot_chunk",requestId:t,chunk:d.slice(c,c+524288)});s({type:"agent:screenshot_done",requestId:t,mimeType:a,totalBytes:l.length})}catch(l){i(`Screenshot failed: ${l.message}`)}}import N from"node:fs/promises";import yt from"node:path";var ae="/var/run/aicommander-agent",ce=yt.join(ae,"state.json"),St=448,ht=384;async function Et(e){let t=null;try{await N.mkdir(ae,{recursive:!0,mode:St}),await N.chmod(ae,St),t=yt.join(ae,`.state.${process.pid}.${Date.now()}.tmp`);let r=await N.open(t,"wx",ht);try{await r.writeFile(JSON.stringify(e,null,2),"utf8")}finally{await r.close()}await N.rename(t,ce),t=null,await N.chmod(ce,ht)}catch{}finally{t&&await N.rm(t,{force:!0}).catch(()=>{})}}async function vt(){try{await N.rm(ce,{force:!0})}catch{}}async function C(){try{let e=await N.readFile(ce,"utf8");return JSON.parse(e)}catch{return null}}import De from"node:fs";import te from"node:path";import Cn from"node:os";import{randomUUID as Dn,randomBytes as On}from"node:crypto";var bt="/etc/aicommander-agent",wt=te.join(Cn.homedir(),".config","aicommander-agent"),le="device.json";function Rt(){return{deviceId:Dn(),deviceSecret:On(32).toString("base64url")}}function Nn(e){return typeof e=="object"&&e!==null&&typeof e.deviceId=="string"&&e.deviceId.length>0&&typeof e.deviceSecret=="string"&&e.deviceSecret.length>0}function ke(e){try{let t=De.readFileSync(e,"utf8"),r=JSON.parse(t);if(Nn(r))return{deviceId:r.deviceId,deviceSecret:r.deviceSecret}}catch{}return null}function Ce(e,t){try{return De.mkdirSync(e,{recursive:!0,mode:448}),De.writeFileSync(te.join(e,le),JSON.stringify(t,null,2),{encoding:"utf8",mode:384}),!0}catch{return!1}}function K(e){if(e){let i=ke(te.join(e,le));if(i)return i;let o=Rt();return Ce(e,o),o}let t=ke(te.join(bt,le));if(t)return t;let r=ke(te.join(wt,le));if(r)return r;let s=Rt();return Ce(bt,s)||Ce(wt,s),s}import M from"node:fs";import J from"node:path";import Mn from"node:os";var Ln="/etc/aicommander-agent",$n=J.join(Mn.homedir(),".config","aicommander-agent"),Oe="session.json",At=".rotate";function Pn(e){return typeof e=="object"&&e!==null&&typeof e.sessionCode=="string"&&e.sessionCode.length>0&&typeof e.agentToken=="string"&&e.agentToken.length>0}function Un(e){try{let t=M.readFileSync(e,"utf8"),r=JSON.parse(t);if(Pn(r))return{sessionCode:r.sessionCode,agentToken:r.agentToken}}catch{}return null}function Hn(e,t){try{return M.mkdirSync(e,{recursive:!0,mode:448}),M.writeFileSync(J.join(e,Oe),JSON.stringify(t,null,2),{encoding:"utf8",mode:384}),!0}catch{return!1}}function ne(e){return e?[e]:[Ln,$n]}function Tt(e){for(let t of ne(e)){let r=Un(J.join(t,Oe));if(r)return r}return null}function Ne(e,t){for(let r of ne(t))if(Hn(r,e))return}function _t(e){for(let t of ne(e))try{M.rmSync(J.join(t,Oe),{force:!0})}catch{}}function xt(e){for(let t of ne(e))try{M.mkdirSync(t,{recursive:!0,mode:448}),M.writeFileSync(J.join(t,At),"",{mode:384});return}catch{}}function It(e){let t=!1;for(let r of ne(e)){let s=J.join(r,At);try{M.existsSync(s)&&(t=!0,M.rmSync(s,{force:!0}))}catch{}}return t}import{spawn as Kn}from"child_process";import de from"fs";import kt from"path";import Gn from"os";function ue(e){if(e)return kt.join(e,"heartbeat");if(process.platform==="linux")try{return de.mkdirSync("/run/aicommander-agent",{recursive:!0}),"/run/aicommander-agent/heartbeat"}catch{}return kt.join(Gn.tmpdir(),"aicommander-agent-heartbeat")}function Ct(e){try{let t=`${e}.tmp`;de.writeFileSync(t,String(Date.now())),de.renameSync(t,e)}catch{}}var re=null;function Dt(e,t=5e3){Me(),Ct(e),re=setInterval(()=>Ct(e),t),re.unref?.()}function Me(){re&&(clearInterval(re),re=null)}function Ot(e){try{let t=de.readFileSync(e,"utf8").trim(),r=Number(t);return Number.isFinite(r)?r:null}catch{return null}}async function Mt(e={}){let t=e.heartbeatPath??ue(e.configDir),r=e.spawnWorker??(p=>Kn(process.execPath,process.argv.slice(1),{env:p,stdio:"inherit"})),s=null,i=0,o=!1,l=0,a=[],d=null,c=null;await new Promise(p=>{let m=null,u=null,f=()=>{d&&(clearInterval(d),d=null),c&&(clearTimeout(c),c=null),m&&(process.removeListener("SIGINT",m),m=null),u&&(process.removeListener("SIGTERM",u),u=null)},S=()=>{f(),p()},A=()=>{i=Date.now(),l++;let y={...process.env,AIC_ROLE:"worker",AIC_HEARTBEAT:t};s=r(y),s.on("exit",(V,Z)=>{if(s=null,o){S();return}if(e.maxSpawns!=null&&l>=e.maxSpawns){S();return}T(`worker exited (code=${V??"null"} signal=${Z??"null"})`)}),s.on("error",()=>{})},T=y=>{let V=Date.now();a=a.filter(Q=>V-Q<3e5),a.push(V);let Z=a.length>5?6e4:0;c&&clearTimeout(c),c=setTimeout(()=>{c=null,o||A()},Z),c.unref?.()},g=()=>{if(s)try{s.kill("SIGKILL")}catch{}};d=setInterval(()=>{if(o||!s||Date.now()-i<3e4)return;let y=Ot(t);y!=null&&Date.now()-y>3e4&&g()},1e4),d.unref?.();let U=y=>{if(o=!0,d&&(clearInterval(d),d=null),c&&(clearTimeout(c),c=null),s)try{s.kill(y)}catch{}else S()};m=()=>U("SIGINT"),u=()=>U("SIGTERM"),process.on("SIGINT",m),process.on("SIGTERM",u),A()})}function Jn(e,t){let r=e.split(".").map(o=>parseInt(o,10)||0),s=t.split(".").map(o=>parseInt(o,10)||0),i=Math.max(r.length,s.length);for(let o=0;o<i;o++){let l=(r[o]??0)-(s[o]??0);if(l!==0)return l>0?1:-1}return 0}function Lt(e,t){return Jn(e,t)>0}async function $t(e,t=1e4){let r=new AbortController,s=setTimeout(()=>r.abort(),t);try{let i=await fetch(`${e.replace(/\/+$/,"")}/dist/latest`,{signal:r.signal,headers:{Accept:"application/json"}});if(!i.ok)return null;let o=await i.json();return{version:typeof o.version=="string"?o.version:null,mac:typeof o.mac=="string"?o.mac:null,win:typeof o.win=="string"?o.win:null,agentLinuxX64:typeof o.agentLinuxX64=="string"?o.agentLinuxX64:null,agentLinuxArm64:typeof o.agentLinuxArm64=="string"?o.agentLinuxArm64:null}}catch{return null}finally{clearTimeout(s)}}var H=dt(process.env.AICOMMANDER_SERVER),Xn=1440*60*1e3;async function Pt(){let e=await $t(H);e?.version&&Lt(e.version,O)&&console.warn(`
|
|
11
|
+
`),I):r.protocol!=="https:"&&!s?(console.warn(`
|
|
12
|
+
\u26A0 Refusing plaintext relay "${r.origin}" (https required); using ${I}.
|
|
13
|
+
`),I):t.replace(/\/+$/,"")}function pt(e){let t;try{t=new URL(e)}catch{throw new Error("Refusing to connect: malformed relay WebSocket URL.")}if(t.protocol!=="wss:"&&!(t.protocol==="ws:"&&ut(t.hostname)))throw new Error(`Refusing to open a plaintext relay socket (${t.protocol}//${t.host}); wss:// required.`)}var ke={capable:!1,enabled:!1,expiresAt:null},St=360*60*1e3;function In(e,t){return new Promise((r,s)=>{if(t.aborted){s(new DOMException("Aborted","AbortError"));return}let i=setTimeout(r,e);t.addEventListener("abort",()=>{clearTimeout(i),s(new DOMException("Aborted","AbortError"))},{once:!0})})}async function ht(e){let t=1e3,r=3e4,s=0,i=e.agentToken;for(;!e.signal?.aborted;){s++;try{let o=await Cn(e,i);if(t=1e3,s=0,o&&e.reauth&&!e.signal?.aborted)try{i=await e.reauth()}catch{}}catch(o){if(o.name==="AbortError")break;let l=.8+Math.random()*.4,a=Math.min(t*l,r);e.silent||ct(s,a);try{await In(a,e.signal??new AbortController().signal)}catch(d){if(d.name==="AbortError")break}t=Math.min(t*2,r)}}}async function Cn(e,t){let{serverUrl:r,sessionCode:s}=e,i=`${r.replace(/^http/,"ws")}/ws/agent/${s}?token=${t}`;pt(i);let o=c=>{try{e.onStatus?.(c)}catch{}},l=c=>{try{e.onActivity?.(c)}catch{}},a=c=>{if(c)try{e.onRemoteActivity?.(c)}catch{}},d=()=>{try{e.onHeartbeat?.()}catch{}};return o("connecting"),new Promise((c,f)=>{let m=new _n(i),u=null,p=null,S=null,A=!1,T=null,g=!1,U=()=>{S&&(clearTimeout(S),S=null)},y=()=>{T&&(clearInterval(T),T=null)},V=()=>{if(!e.reauthIntervalMs||!e.reauth)return;let E=()=>{if(u){S=setTimeout(E,3e4);return}A=!0,m.close(1e3,"reauth")};S=setTimeout(E,e.reauthIntervalMs)},Z=()=>{g||(g=!0,U(),y(),c(A))},Q=E=>{g||(g=!0,U(),y(),f(E))},Xe=()=>{d(),p&&clearTimeout(p),p=setTimeout(()=>{m.terminate(),Q(new Error("Ping timeout"))},9e4)};e.signal?.addEventListener("abort",()=>m.close(1e3,"disabled"),{once:!0});let ye=()=>{if(g)return;let E={type:"agent:screen_state",screenShare:e.screenShare?.getState()??ke};try{m.send(JSON.stringify(E))}catch{}};m.on("open",()=>{o("connected"),Xe(),V();let E={type:"agent:register",hostname:kn.hostname(),platform:process.platform,arch:process.arch,agentVersion:O,screenShare:e.screenShare?.getState()??ke};m.send(JSON.stringify(E)),e.screenShare?.on("change",ye),e.silent||lt()}),m.on("message",E=>{let R;try{R=JSON.parse(String(E))}catch{return}switch(R.type){case"do:ping":Xe();let cn={type:"agent:pong",ts:R.ts};m.send(JSON.stringify(cn));break;case"do:exec":{xe(R.command),l(!0),a(R.operator);let{commandId:D,command:W,cwd:Ee,env:ve}=R,oe=Date.now(),z=!1;y(),T=setInterval(()=>{let x=Date.now()-oe;if(x>=3e5&&!z){z=!0;let h={type:"agent:exec_idle",commandId:D,idleMs:x};m.send(JSON.stringify(h))}},3e5),T.unref?.(),u=tt(W,Ee,ve,{onOutput:(x,h)=>{oe=Date.now(),z=!1;let k={type:"agent:output",commandId:D,chunk:x,stream:h};m.send(JSON.stringify(k))},onDone:(x,h)=>{u=null,y(),l(!1),_e(x,h);let k={type:"agent:done",commandId:D,exitCode:x,durationMs:h};m.send(JSON.stringify(k))},onError:x=>{u=null,y(),l(!1);let h={type:"agent:error",commandId:D,error:x};m.send(JSON.stringify(h))}}).kill;break}case"do:secure_exec":{let{commandId:D,argv:W,allowedCommands:Ee,input:ve,cwd:oe,env:z}=R,Ye=(h,k)=>{u=null,y(),l(!1),_e(h,k);let ee={type:"agent:done",commandId:D,exitCode:h,durationMs:k};m.send(JSON.stringify(ee))},x=h=>{u=null,y(),l(!1);let k={type:"agent:error",commandId:D,error:h};m.send(JSON.stringify(k))};try{let h=Array.isArray(W)?W.join(" "):String(W);xe(h),l(!0);let k=Date.now(),ee=!1;y(),T=setInterval(()=>{let se=Date.now()-k;if(se>=3e5&&!ee){ee=!0;let be={type:"agent:exec_idle",commandId:D,idleMs:se};m.send(JSON.stringify(be))}},3e5),T.unref?.(),u=it({argv:W,allowedCommands:Ee,input:ve,cwd:oe,env:z},{onOutput:(se,be)=>{k=Date.now(),ee=!1;let ln={type:"agent:output",commandId:D,chunk:se,stream:be};m.send(JSON.stringify(ln))},onDone:Ye,onError:x}).kill}catch(h){x(h instanceof Error?h.message:String(h))}break}case"do:kill":u&&(u(),u=null);break;case"do:screenshot":a(R.operator),Dn(m,R.requestId,e.screenShare);break}}),m.on("close",(E,R)=>{p&&clearTimeout(p),y(),e.screenShare?.off("change",ye),u&&(u(),u=null,l(!1)),o("disconnected"),E===1e3||E===4001?Z():Q(new Error(`WS closed: ${E} ${String(R)}`))}),m.on("error",E=>{p&&clearTimeout(p),y(),e.screenShare?.off("change",ye),o("disconnected"),Q(E)}),m.on("unexpected-response",(E,R)=>{f(new Error(`Unexpected HTTP response: ${R.statusCode}`))})})}async function Dn(e,t,r){let s=l=>{try{e.send(JSON.stringify(l))}catch{}},i=l=>s({type:"agent:screenshot_error",requestId:t,error:l}),o=r?.getState()??ke;if(!r||!o.capable){i("This machine cannot share its screen (desktop macOS/Windows only).");return}if(!o.enabled){i("Screen sharing is turned off on this machine.");return}try{let{data:l,mimeType:a}=await r.capture();if(l.length>10485760){let c=(l.length/1048576).toFixed(1);i(`Screenshot is ${c} MB, which exceeds the ${10485760/(1024*1024)} MB limit.`);return}let d=l.toString("base64");for(let c=0;c<d.length;c+=524288)s({type:"agent:screenshot_chunk",requestId:t,chunk:d.slice(c,c+524288)});s({type:"agent:screenshot_done",requestId:t,mimeType:a,totalBytes:l.length})}catch(l){i(`Screenshot failed: ${l.message}`)}}import N from"node:fs/promises";import vt from"node:path";var ae="/var/run/aicommander-agent",ce=vt.join(ae,"state.json"),yt=448,Et=384;async function bt(e){let t=null;try{await N.mkdir(ae,{recursive:!0,mode:yt}),await N.chmod(ae,yt),t=vt.join(ae,`.state.${process.pid}.${Date.now()}.tmp`);let r=await N.open(t,"wx",Et);try{await r.writeFile(JSON.stringify(e,null,2),"utf8")}finally{await r.close()}await N.rename(t,ce),t=null,await N.chmod(ce,Et)}catch{}finally{t&&await N.rm(t,{force:!0}).catch(()=>{})}}async function wt(){try{await N.rm(ce,{force:!0})}catch{}}async function C(){try{let e=await N.readFile(ce,"utf8");return JSON.parse(e)}catch{return null}}import De from"node:fs";import te from"node:path";import On from"node:os";import{randomUUID as Nn,randomBytes as Mn}from"node:crypto";var Rt="/etc/aicommander-agent",At=te.join(On.homedir(),".config","aicommander-agent"),le="device.json";function Tt(){return{deviceId:Nn(),deviceSecret:Mn(32).toString("base64url")}}function Ln(e){return typeof e=="object"&&e!==null&&typeof e.deviceId=="string"&&e.deviceId.length>0&&typeof e.deviceSecret=="string"&&e.deviceSecret.length>0}function Ie(e){try{let t=De.readFileSync(e,"utf8"),r=JSON.parse(t);if(Ln(r))return{deviceId:r.deviceId,deviceSecret:r.deviceSecret}}catch{}return null}function Ce(e,t){try{return De.mkdirSync(e,{recursive:!0,mode:448}),De.writeFileSync(te.join(e,le),JSON.stringify(t,null,2),{encoding:"utf8",mode:384}),!0}catch{return!1}}function K(e){if(e){let i=Ie(te.join(e,le));if(i)return i;let o=Tt();return Ce(e,o),o}let t=Ie(te.join(Rt,le));if(t)return t;let r=Ie(te.join(At,le));if(r)return r;let s=Tt();return Ce(Rt,s)||Ce(At,s),s}import M from"node:fs";import J from"node:path";import $n from"node:os";var Pn="/etc/aicommander-agent",Un=J.join($n.homedir(),".config","aicommander-agent"),Oe="session.json",xt=".rotate";function Hn(e){return typeof e=="object"&&e!==null&&typeof e.sessionCode=="string"&&e.sessionCode.length>0&&typeof e.agentToken=="string"&&e.agentToken.length>0}function Fn(e){try{let t=M.readFileSync(e,"utf8"),r=JSON.parse(t);if(Hn(r))return{sessionCode:r.sessionCode,agentToken:r.agentToken}}catch{}return null}function Gn(e,t){try{return M.mkdirSync(e,{recursive:!0,mode:448}),M.writeFileSync(J.join(e,Oe),JSON.stringify(t,null,2),{encoding:"utf8",mode:384}),!0}catch{return!1}}function ne(e){return e?[e]:[Pn,Un]}function _t(e){for(let t of ne(e)){let r=Fn(J.join(t,Oe));if(r)return r}return null}function Ne(e,t){for(let r of ne(t))if(Gn(r,e))return}function kt(e){for(let t of ne(e))try{M.rmSync(J.join(t,Oe),{force:!0})}catch{}}function It(e){for(let t of ne(e))try{M.mkdirSync(t,{recursive:!0,mode:448}),M.writeFileSync(J.join(t,xt),"",{mode:384});return}catch{}}function Ct(e){let t=!1;for(let r of ne(e)){let s=J.join(r,xt);try{M.existsSync(s)&&(t=!0,M.rmSync(s,{force:!0}))}catch{}}return t}import{spawn as Xn}from"child_process";import de from"fs";import Dt from"path";import Vn from"os";function ue(e){if(e)return Dt.join(e,"heartbeat");if(process.platform==="linux")try{return de.mkdirSync("/run/aicommander-agent",{recursive:!0}),"/run/aicommander-agent/heartbeat"}catch{}return Dt.join(Vn.tmpdir(),"aicommander-agent-heartbeat")}function Ot(e){try{let t=`${e}.tmp`;de.writeFileSync(t,String(Date.now())),de.renameSync(t,e)}catch{}}var re=null;function Nt(e,t=5e3){Me(),Ot(e),re=setInterval(()=>Ot(e),t),re.unref?.()}function Me(){re&&(clearInterval(re),re=null)}function Mt(e){try{let t=de.readFileSync(e,"utf8").trim(),r=Number(t);return Number.isFinite(r)?r:null}catch{return null}}async function $t(e={}){let t=e.heartbeatPath??ue(e.configDir),r=e.spawnWorker??(f=>Xn(process.execPath,process.argv.slice(1),{env:f,stdio:"inherit"})),s=null,i=0,o=!1,l=0,a=[],d=null,c=null;await new Promise(f=>{let m=null,u=null,p=()=>{d&&(clearInterval(d),d=null),c&&(clearTimeout(c),c=null),m&&(process.removeListener("SIGINT",m),m=null),u&&(process.removeListener("SIGTERM",u),u=null)},S=()=>{p(),f()},A=()=>{i=Date.now(),l++;let y={...process.env,AIC_ROLE:"worker",AIC_HEARTBEAT:t};s=r(y),s.on("exit",(V,Z)=>{if(s=null,o){S();return}if(e.maxSpawns!=null&&l>=e.maxSpawns){S();return}T(`worker exited (code=${V??"null"} signal=${Z??"null"})`)}),s.on("error",()=>{})},T=y=>{let V=Date.now();a=a.filter(Q=>V-Q<3e5),a.push(V);let Z=a.length>5?6e4:0;c&&clearTimeout(c),c=setTimeout(()=>{c=null,o||A()},Z),c.unref?.()},g=()=>{if(s)try{s.kill("SIGKILL")}catch{}};d=setInterval(()=>{if(o||!s||Date.now()-i<3e4)return;let y=Mt(t);y!=null&&Date.now()-y>3e4&&g()},1e4),d.unref?.();let U=y=>{if(o=!0,d&&(clearInterval(d),d=null),c&&(clearTimeout(c),c=null),s)try{s.kill(y)}catch{}else S()};m=()=>U("SIGINT"),u=()=>U("SIGTERM"),process.on("SIGINT",m),process.on("SIGTERM",u),A()})}function Pt(e,t){return Qe(e,t)}async function Ut(e,t=1e4){let r=new AbortController,s=setTimeout(()=>r.abort(),t);try{let i=await fetch(`${e.replace(/\/+$/,"")}/dist/latest`,{signal:r.signal,headers:{Accept:"application/json"}});if(!i.ok)return null;let o=await i.json();return{version:typeof o.version=="string"?o.version:null,mac:typeof o.mac=="string"?o.mac:null,macPkg:typeof o.macPkg=="string"?o.macPkg:null,win:typeof o.win=="string"?o.win:null,agentLinuxX64:typeof o.agentLinuxX64=="string"?o.agentLinuxX64:null,agentLinuxArm64:typeof o.agentLinuxArm64=="string"?o.agentLinuxArm64:null}}catch{return null}finally{clearTimeout(s)}}var H=mt(process.env.AICOMMANDER_SERVER),Yn=1440*60*1e3;async function Ht(){let e=await Ut(H);e?.version&&Pt(e.version,O)&&console.warn(`
|
|
14
14
|
\u2B06 A newer AI Commander agent is available (v${e.version}; you have v${O}).
|
|
15
15
|
Update: curl -fsSL ${H}/install | sudo bash
|
|
16
|
-
`)}async function
|
|
16
|
+
`)}async function Ft(){process.env.AIC_ROLE==="worker"?await qn():await $t()}async function qn(){let e=process.env.AIC_ROLE==="worker";e&&Nt(process.env.AIC_HEARTBEAT||ue());let t=()=>{e&&Me(),wt(),process.exit(0)};process.on("SIGINT",t),process.on("SIGTERM",t),typeof process.getuid=="function"&&process.getuid()!==0&&console.warn(`
|
|
17
17
|
\u26A0 Warning: not running as root.
|
|
18
18
|
Commands requiring sudo (apt, systemctl, brew cask\u2026) will fail.
|
|
19
19
|
Recommended: sudo aicommander-agent
|
|
20
|
-
`);let r=K(),s=
|
|
20
|
+
`);let r=K(),s=Ct(),i=s?null:_t(),o,l;try{let c=await Re(H,r,{forceNew:s,...i?{currentCode:i.sessionCode}:{}});o=c.sessionCode,l=c.agentToken,Ne({sessionCode:o,agentToken:l})}catch(c){console.error(` Failed to register: ${String(c)}`),process.exit(1)}let a=!!(process.env.INVOCATION_ID||process.env.JOURNAL_STREAM);at(o,H,process.stdout.isTTY===!0||!a),bt({sessionCode:o,pid:process.pid,startedAt:new Date().toISOString(),serverUrl:H}),Ht(),setInterval(()=>{Ht()},Yn).unref?.(),await ht({serverUrl:H,sessionCode:o,agentToken:l,reauthIntervalMs:St,reauth:async()=>{let c=await Re(H,r,{currentCode:o});return Ne({sessionCode:c.sessionCode,agentToken:c.agentToken}),c.agentToken}})}import G from"node:fs";import{execFileSync as $e}from"node:child_process";import Wt from"chalk";import{execFileSync as Le}from"node:child_process";var L="aicommander-agent";function F(e){Le("systemctl",e,{stdio:"inherit"})}function X(){try{return Le("systemctl",["is-active",L],{encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim()}catch{return"inactive"}}function Gt(){try{return Le("systemctl",["is-enabled",L],{encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim()==="enabled"}catch{return!1}}function me(){F(["start",L])}function Y(){F(["stop",L])}function pe(){F(["enable",L])}function fe(){F(["disable",L])}function q(){F(["restart",L])}function ge(){F(["daemon-reload"])}function Bt(){F(["kill","--signal=SIGKILL",L])}import $ from"chalk";import Zn from"node:readline";var n={header:e=>console.log($.bold.cyan(`
|
|
21
21
|
${e}
|
|
22
|
-
`)),ok:e=>console.log($.green(` \u2713 ${e}`)),warn:e=>console.log($.yellow(` \u26A0 ${e}`)),error:e=>console.error($.red(` \u2717 ${e}`)),info:(e,t)=>console.log(` ${$.gray(e.padEnd(14))} ${$.white(t)}`),step:e=>console.log($.gray(` ${e}`)),blank:()=>console.log()};function b(){process.getuid?.()!==0&&(n.error("This command must be run as root."),console.error($.gray(" Re-run with: sudo aicommander-agent <command>")),process.exit(1))}async function
|
|
22
|
+
`)),ok:e=>console.log($.green(` \u2713 ${e}`)),warn:e=>console.log($.yellow(` \u26A0 ${e}`)),error:e=>console.error($.red(` \u2717 ${e}`)),info:(e,t)=>console.log(` ${$.gray(e.padEnd(14))} ${$.white(t)}`),step:e=>console.log($.gray(` ${e}`)),blank:()=>console.log()};function b(){process.getuid?.()!==0&&(n.error("This command must be run as root."),console.error($.gray(" Re-run with: sudo aicommander-agent <command>")),process.exit(1))}async function Vt(e){if(!process.stdin.isTTY)return!1;let t=Zn.createInterface({input:process.stdin,output:process.stdout});try{let r=await new Promise(s=>t.question(` ${e} [y/N] `,s));return/^y(es)?$/i.test(r.trim())}finally{t.close()}}var jt="/etc/systemd/system/aicommander-agent.service",Pe="/etc/aicommander-agent";function Qn(e){/[\u0000-\u001f\u007f]/.test(e)&&(n.error(`Invalid --server URL: contains control characters: ${JSON.stringify(e)}`),process.exit(1));let t;try{t=new URL(e)}catch{n.error(`Invalid --server URL: not a valid URL: ${JSON.stringify(e)}`),process.exit(1)}return t.protocol!=="http:"&&t.protocol!=="https:"&&(n.error(`Invalid --server URL: protocol must be http: or https: (got ${t.protocol})`),process.exit(1)),e.replace(/%/g,"%%")}function Kt(e){return`"${e.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/%/g,"%%")}"`}function zn(){n.step(`Ensuring sandbox user "${v}"\u2026`);try{$e("getent",["passwd",v],{stdio:"ignore"}),n.ok(`Sandbox user "${v}" already exists.`);return}catch{}let e=["/usr/sbin/nologin","/sbin/nologin","/bin/false"].find(t=>G.existsSync(t))??"/bin/false";try{$e("useradd",["--system","--create-home","--shell",e,v],{stdio:"ignore"});try{let t=$e("getent",["passwd",v]).toString().trim().split(":")[5];t&&G.existsSync(t)&&G.chmodSync(t,448)}catch{}n.ok(`Sandbox user "${v}" created.`)}catch{n.warn(`Could not create sandbox user "${v}" \u2014 secure exec will be unavailable until it exists.`)}}async function Ue(e){b(),process.platform!=="linux"&&(n.error(`The systemd service installer supports Linux only. Detected: ${process.platform}`),n.blank(),n.step("For an ephemeral foreground run on this platform, use:"),n.step(" npx @aicommander/agent (or: aicommander-agent run)"),n.step("For a production root install on Linux, the canonical path is:"),n.step(" curl -fsSL https://aicommander.dev/install | sudo bash"),process.exit(1));let t=Qn(e.server??process.env.AICOMMANDER_SERVER??I),r=G.realpathSync(process.argv[1]),s=`${Kt(process.execPath)} ${Kt(r)} run`;n.header("AI Commander Agent \u2014 systemd install"),n.info("Server",t),n.info("ExecStart",s),n.blank(),n.step("Creating device identity dir\u2026"),G.mkdirSync(Pe,{recursive:!0}),G.chmodSync(Pe,448),n.ok(`Device identity dir ready (${Pe}).`),zn(),n.step(`Writing systemd service to ${jt}\u2026`);let i=`[Unit]
|
|
23
23
|
Description=AI Commander Remote Agent
|
|
24
24
|
After=network-online.target
|
|
25
25
|
Wants=network-online.target
|
|
@@ -43,4 +43,4 @@ KillSignal=SIGINT
|
|
|
43
43
|
|
|
44
44
|
[Install]
|
|
45
45
|
WantedBy=multi-user.target
|
|
46
|
-
`;G.writeFileSync(Vt,i,{mode:420}),n.ok("Service file written."),n.step("Reloading systemd\u2026");try{ge()}catch{n.error("daemon-reload failed. Inspect: journalctl -u aicommander-agent -n 50"),process.exit(1)}n.step("Enabling service on boot\u2026");try{fe()}catch{n.warn("Could not enable the service on boot (non-critical).")}n.step("Restarting service\u2026");try{q()}catch{n.warn("Graceful restart failed \u2014 forcing stop then start\u2026");try{Y()}catch{}try{Ft()}catch{}try{me()}catch{n.error("Could not start aicommander-agent. Inspect: journalctl -u aicommander-agent -n 50"),process.exit(1)}}X()!=="active"&&(n.error("Service is not active after restart. Inspect: journalctl -u aicommander-agent -n 50"),process.exit(1)),n.ok("Service started."),n.blank(),n.step("Waiting for session code\u2026");let o=null;for(let l=0;l<30;l++){let a=await C();if(a?.sessionCode){o=a.sessionCode;break}await new Promise(d=>setTimeout(d,500))}n.blank(),o?(n.ok(Bt.bold("AI Commander ready!")),n.blank(),n.info("Session code",Bt.bold.greenBright(j(o))),n.blank(),n.step("Reveal full code: sudo aicommander-agent status --reveal"),n.step("Then use in Claude: 'execute df -h on <code>'"),n.step("Service logs: journalctl -u aicommander-agent -f"),n.step("Manage: sudo aicommander-agent status"),n.step("Uninstall: sudo aicommander-agent uninstall --force"),n.blank(),n.warn("Keep this code secret. Anyone who has it can run commands as root on this machine.")):(n.warn("The session code did not appear within 15 seconds."),n.step("The service is running \u2014 the code should appear shortly."),n.step("Status: sudo aicommander-agent status"),n.step("Logs: journalctl -u aicommander-agent -f")),n.blank()}import P from"chalk";function zn(e){if(e<60)return`${e}s`;if(e<3600)return`${Math.floor(e/60)}m`;let t=Math.floor(e/3600),r=Math.floor(e%3600/60);return r>0?`${t}h ${r}m`:`${t}h`}async function He(e={}){n.header("AI Commander \u2014 Agent Status");let t;try{t=X()}catch{n.error("systemd is not available on this system."),process.exit(1)}let r=Ht(),s=await C();if(n.info("State",t==="active"?P.green("running"):P.red(t)),s){e.reveal?(b(),n.info("Session code",P.bold.cyan(s.sessionCode)),n.step(P.yellow(" \u26A0 Keep this secret \u2014 anyone with it can run commands as root here."))):(n.info("Session code",P.bold.cyan(j(s.sessionCode))),n.step(P.gray(" (run with --reveal to show the full code)"))),n.info("PID",String(s.pid));let i=new Date(s.startedAt),o=Math.floor((Date.now()-i.getTime())/1e3);n.info("Started",`${s.startedAt} (${zn(o)} ago)`),n.info("Server",s.serverUrl)}else t==="active"&&n.warn("Session code not yet available (agent still starting)");n.info("Enabled",r?P.green("yes"):P.yellow("no")),n.blank()}function Fe(){b(),X()==="active"?(n.warn("Service is already running \u2014 restarting to load the current binary."),q()):me(),fe(),n.ok("Service started and enabled to run on boot."),n.blank()}function Ge(){b(),Y(),pe(),n.ok("Service stopped and disabled."),n.blank()}import er from"chalk";async function Be(e={}){if(b(),!e.yes){if(!process.stdin.isTTY){n.error("Refusing to reset the code non-interactively."),n.info("To proceed","re-run with --yes (this unlinks ALL linked accounts)"),process.exitCode=1;return}if(!await Gt("Resetting the access code will UNLINK ALL linked accounts. Continue?")){n.info("Cancelled","no changes made");return}}n.step("Restarting agent to get a new session code\u2026"),xt(),_t(),q();let t=Date.now()+1e4,r=null;for(;Date.now()<t&&(await new Promise(s=>setTimeout(s,500)),r=await C(),!r););r?n.ok(`New session code: ${er.bold.greenBright(r.sessionCode)}`):(n.warn("Service restarted. Session code not yet available."),n.info("Check","aicommander-agent status")),n.blank()}import Ve from"node:fs";import tr from"chalk";var jt="/etc/systemd/system/aicommander-agent.service",Kt="/usr/local/bin/aicommander-agent",Jt="/usr/local/bin/aicommander-ctl",Xt="/var/run/aicommander-agent",Yt="/etc/aicommander-agent",qt="/etc/aicommander-agent/session.json",Zt="/etc/aicommander-agent/.rotate";function B(e,t){try{if(Ve.existsSync(e)||Ve.lstatSync(e)){Ve.rmSync(e,{recursive:!0,force:!0}),n.ok(`${t} removed.`);return}}catch{}n.warn(`${t} not found (already removed?).`)}function We(e){b(),e.force||(n.warn("This will fully remove the AI Commander agent."),n.error("Add --force to confirm: sudo aicommander-agent uninstall --force"),process.exit(1)),n.step("Stopping and disabling service\u2026");try{Y()}catch{}try{pe()}catch{}n.ok("Service stopped."),n.step("Removing service file\u2026"),B(jt,jt),n.step("Reloading systemd\u2026");try{ge(),n.ok("daemon-reload complete.")}catch{n.warn("daemon-reload failed (non-critical).")}n.step("Removing agent binary\u2026"),B(Jt,Jt),B(Kt,Kt),B(Xt,Xt),B(qt,qt),B(Zt,Zt),B(Yt,Yt),n.blank(),n.ok(tr.bold("AI Commander agent fully removed.")),n.blank()}function Qt(e){return{"X-Device-Id":e.deviceId,"X-Device-Secret":e.deviceSecret}}var zt=1e4;async function Se(e,t){let r;try{r=await fetch(`${e}/api/device/admins`,{headers:Qt(t),signal:AbortSignal.timeout(zt)})}catch{throw new Error("Failed to list linked accounts (relay unreachable or timed out).")}if(!r.ok)throw new Error(`Failed to list linked accounts (HTTP ${r.status}).`);return await r.json()}async function en(e,t,r,s){let i;try{i=await fetch(`${e}/api/device/${r}`,{method:"POST",headers:{...Qt(t),"Content-Type":"application/json"},body:JSON.stringify({userId:s}),signal:AbortSignal.timeout(zt)})}catch{return{ok:!1,error:"unreachable"}}let o;try{o=await i.json()}catch{o={ok:!1,error:`http_${i.status}`}}return{...o,ok:i.ok}}function tn(e,t,r){return en(e,t,"block",r)}function nn(e,t,r){return en(e,t,"unblock",r)}function he(e){return[...e.filter(t=>!t.blocked),...e.filter(t=>t.blocked)]}function rn(e,t){let r=t.trim();if(!r)return{kind:"not_found"};if(/^\d+$/.test(r)){let o=Number(r)-1;return o>=0&&o<e.length?{kind:"ok",admin:e[o]}:{kind:"not_found"}}let s=e.find(o=>o.userId===r);if(s)return{kind:"ok",admin:s};let i=e.filter(o=>o.userId.startsWith(r));return i.length===1?{kind:"ok",admin:i[0]}:i.length>1?{kind:"ambiguous",matches:i}:{kind:"not_found"}}async function nr(){return(await C())?.serverUrl??process.env.AICOMMANDER_SERVER??"https://aicommander.dev"}async function je(){b();let e=await nr(),t=K(),r;try{r=await Se(e,t)}catch(a){n.error(a.message),process.exitCode=1;return}if(r.admins.length===0){n.header("Linked accounts"),n.info("None","no accounts are linked to this device"),n.blank();return}let s=he(r.admins),i=(a,d)=>{let c=String(d+1).padStart(2," "),p=a.linkedAt.slice(0,10);n.info(`${c}. ${a.maskedEmail}`,`${a.alias} \xB7 linked ${p} \xB7 id ${a.userId.slice(0,8)}\u2026`)},o=s.filter(a=>!a.blocked),l=s.filter(a=>a.blocked);n.header("Linked accounts"),o.forEach(a=>i(a,s.indexOf(a))),o.length===0&&n.info("None active","every linked account is blocked"),n.blank(),l.length>0&&(n.header("Blocked"),l.forEach(a=>i(a,s.indexOf(a))),n.blank()),n.step("Block one with: aicommander-agent block-admin <number|id>"),l.length>0&&n.step("Unblock one with: aicommander-agent unblock-admin <number|id>"),n.blank()}async function rr(){return(await C())?.serverUrl??process.env.AICOMMANDER_SERVER??"https://aicommander.dev"}async function on(e,t){b();let r=await rr(),s=K(),i;try{i=await Se(r,s)}catch(c){n.error(c.message),process.exitCode=1;return}let o=he(i.admins),l=rn(o,e);if(l.kind==="not_found"){n.error(`No linked account matches "${e}".`),n.info("See options","aicommander-agent list-admins"),process.exitCode=1;return}if(l.kind==="ambiguous"){n.error(`"${e}" is ambiguous \u2014 it matches ${l.matches.length} accounts.`),n.info("Disambiguate","use the list number or a longer id"),process.exitCode=1;return}let a=l.admin,d=t==="block"?await tn(r,s,a.userId):await nn(r,s,a.userId);d.ok?t==="block"?(n.ok(`Blocked ${a.maskedEmail} (${a.alias}).`),n.info("Note","their access is cut off; run unblock-admin anytime to restore it")):n.ok(`Unblocked ${a.maskedEmail} (${a.alias}) \u2014 access restored.`):d.error==="not_found"?n.warn(t==="block"?"That account isn't linked (or is already blocked).":"That account isn't blocked."):(n.error(d.message??`Failed to ${t} the account.`),process.exitCode=1),n.blank()}function Ke(e){return on(e,"block")}function Je(e){return on(e,"unblock")}nt(process.argv)||(x.name("aicommander-agent").description("AI Commander remote agent + service controller").version(O),x.command("run",{isDefault:!0}).description("Run the agent: register with the relay and listen for commands").action(async()=>{await Ut()}),x.command("install").description("Install and start the agent as a systemd service (Linux, root)").option("--server <url>","Relay server URL baked into the service unit").action(async e=>{await Ue(e)}),x.command("status").description("Show agent status, session code, and uptime").option("--reveal","Show the full session code (otherwise masked)").action(async e=>{await He(e)}),x.command("enable").description("Start and enable the agent service").action(()=>{Fe()}),x.command("disable").description("Stop and disable the agent service").action(()=>{Ge()}),x.command("change-code").aliases(["reset-code"]).description("Reset the access code \u2014 mint a new code and remove ALL access (unlinks every linked account)").option("-y, --yes","Skip the confirmation prompt").action(async e=>{await Be(e)}),x.command("list-admins").description("List the accounts (admins) linked to this device").action(async()=>{await je()}),x.command("block-admin <account>").aliases(["revoke-admin"]).description("Block one account on this device (by list number or id) \u2014 refuses access; unblock anytime").action(async e=>{await Ke(e)}),x.command("unblock-admin <account>").description("Restore a blocked account's access (by list number or id)").action(async e=>{await Je(e)}),x.command("uninstall").description("Fully remove the agent (stop, disable, delete files)").option("-f, --force","Skip confirmation requirement").action(e=>{We(e)}),x.parseAsync().catch(e=>{console.error("Fatal error:",e),process.exit(1)}));
|
|
46
|
+
`;G.writeFileSync(jt,i,{mode:420}),n.ok("Service file written."),n.step("Reloading systemd\u2026");try{ge()}catch{n.error("daemon-reload failed. Inspect: journalctl -u aicommander-agent -n 50"),process.exit(1)}n.step("Enabling service on boot\u2026");try{pe()}catch{n.warn("Could not enable the service on boot (non-critical).")}n.step("Restarting service\u2026");try{q()}catch{n.warn("Graceful restart failed \u2014 forcing stop then start\u2026");try{Y()}catch{}try{Bt()}catch{}try{me()}catch{n.error("Could not start aicommander-agent. Inspect: journalctl -u aicommander-agent -n 50"),process.exit(1)}}X()!=="active"&&(n.error("Service is not active after restart. Inspect: journalctl -u aicommander-agent -n 50"),process.exit(1)),n.ok("Service started."),n.blank(),n.step("Waiting for session code\u2026");let o=null;for(let l=0;l<30;l++){let a=await C();if(a?.sessionCode){o=a.sessionCode;break}await new Promise(d=>setTimeout(d,500))}n.blank(),o?(n.ok(Wt.bold("AI Commander ready!")),n.blank(),n.info("Session code",Wt.bold.greenBright(j(o))),n.blank(),n.step("Reveal full code: sudo aicommander-agent status --reveal"),n.step("Then use in Claude: 'execute df -h on <code>'"),n.step("Service logs: journalctl -u aicommander-agent -f"),n.step("Manage: sudo aicommander-agent status"),n.step("Uninstall: sudo aicommander-agent uninstall --force"),n.blank(),n.warn("Keep this code secret. Anyone who has it can run commands as root on this machine.")):(n.warn("The session code did not appear within 15 seconds."),n.step("The service is running \u2014 the code should appear shortly."),n.step("Status: sudo aicommander-agent status"),n.step("Logs: journalctl -u aicommander-agent -f")),n.blank()}import P from"chalk";function er(e){if(e<60)return`${e}s`;if(e<3600)return`${Math.floor(e/60)}m`;let t=Math.floor(e/3600),r=Math.floor(e%3600/60);return r>0?`${t}h ${r}m`:`${t}h`}async function He(e={}){n.header("AI Commander \u2014 Agent Status");let t;try{t=X()}catch{n.error("systemd is not available on this system."),process.exit(1)}let r=Gt(),s=await C();if(n.info("State",t==="active"?P.green("running"):P.red(t)),s){e.reveal?(b(),n.info("Session code",P.bold.cyan(s.sessionCode)),n.step(P.yellow(" \u26A0 Keep this secret \u2014 anyone with it can run commands as root here."))):(n.info("Session code",P.bold.cyan(j(s.sessionCode))),n.step(P.gray(" (run with --reveal to show the full code)"))),n.info("PID",String(s.pid));let i=new Date(s.startedAt),o=Math.floor((Date.now()-i.getTime())/1e3);n.info("Started",`${s.startedAt} (${er(o)} ago)`),n.info("Server",s.serverUrl)}else t==="active"&&n.warn("Session code not yet available (agent still starting)");n.info("Enabled",r?P.green("yes"):P.yellow("no")),n.blank()}function Fe(){b(),X()==="active"?(n.warn("Service is already running \u2014 restarting to load the current binary."),q()):me(),pe(),n.ok("Service started and enabled to run on boot."),n.blank()}function Ge(){b(),Y(),fe(),n.ok("Service stopped and disabled."),n.blank()}import tr from"chalk";async function Be(e={}){if(b(),!e.yes){if(!process.stdin.isTTY){n.error("Refusing to reset the code non-interactively."),n.info("To proceed","re-run with --yes (this unlinks ALL linked accounts)"),process.exitCode=1;return}if(!await Vt("Resetting the access code will UNLINK ALL linked accounts. Continue?")){n.info("Cancelled","no changes made");return}}n.step("Restarting agent to get a new session code\u2026"),It(),kt(),q();let t=Date.now()+1e4,r=null;for(;Date.now()<t&&(await new Promise(s=>setTimeout(s,500)),r=await C(),!r););r?n.ok(`New session code: ${tr.bold.greenBright(r.sessionCode)}`):(n.warn("Service restarted. Session code not yet available."),n.info("Check","aicommander-agent status")),n.blank()}import Ve from"node:fs";import nr from"chalk";var Jt="/etc/systemd/system/aicommander-agent.service",Xt="/usr/local/bin/aicommander-agent",Yt="/usr/local/bin/aicommander-ctl",qt="/var/run/aicommander-agent",Zt="/etc/aicommander-agent",Qt="/etc/aicommander-agent/session.json",zt="/etc/aicommander-agent/.rotate";function B(e,t){try{if(Ve.existsSync(e)||Ve.lstatSync(e)){Ve.rmSync(e,{recursive:!0,force:!0}),n.ok(`${t} removed.`);return}}catch{}n.warn(`${t} not found (already removed?).`)}function We(e){b(),e.force||(n.warn("This will fully remove the AI Commander agent."),n.error("Add --force to confirm: sudo aicommander-agent uninstall --force"),process.exit(1)),n.step("Stopping and disabling service\u2026");try{Y()}catch{}try{fe()}catch{}n.ok("Service stopped."),n.step("Removing service file\u2026"),B(Jt,Jt),n.step("Reloading systemd\u2026");try{ge(),n.ok("daemon-reload complete.")}catch{n.warn("daemon-reload failed (non-critical).")}n.step("Removing agent binary\u2026"),B(Yt,Yt),B(Xt,Xt),B(qt,qt),B(Qt,Qt),B(zt,zt),B(Zt,Zt),n.blank(),n.ok(nr.bold("AI Commander agent fully removed.")),n.blank()}function en(e){return{"X-Device-Id":e.deviceId,"X-Device-Secret":e.deviceSecret}}var tn=1e4;async function Se(e,t){let r;try{r=await fetch(`${e}/api/device/admins`,{headers:en(t),signal:AbortSignal.timeout(tn)})}catch{throw new Error("Failed to list linked accounts (relay unreachable or timed out).")}if(!r.ok)throw new Error(`Failed to list linked accounts (HTTP ${r.status}).`);return await r.json()}async function nn(e,t,r,s){let i;try{i=await fetch(`${e}/api/device/${r}`,{method:"POST",headers:{...en(t),"Content-Type":"application/json"},body:JSON.stringify({userId:s}),signal:AbortSignal.timeout(tn)})}catch{return{ok:!1,error:"unreachable"}}let o;try{o=await i.json()}catch{o={ok:!1,error:`http_${i.status}`}}return{...o,ok:i.ok}}function rn(e,t,r){return nn(e,t,"block",r)}function on(e,t,r){return nn(e,t,"unblock",r)}function he(e){return[...e.filter(t=>!t.blocked),...e.filter(t=>t.blocked)]}function sn(e,t){let r=t.trim();if(!r)return{kind:"not_found"};if(/^\d+$/.test(r)){let o=Number(r)-1;return o>=0&&o<e.length?{kind:"ok",admin:e[o]}:{kind:"not_found"}}let s=e.find(o=>o.userId===r);if(s)return{kind:"ok",admin:s};let i=e.filter(o=>o.userId.startsWith(r));return i.length===1?{kind:"ok",admin:i[0]}:i.length>1?{kind:"ambiguous",matches:i}:{kind:"not_found"}}async function rr(){return(await C())?.serverUrl??process.env.AICOMMANDER_SERVER??"https://aicommander.dev"}async function je(){b();let e=await rr(),t=K(),r;try{r=await Se(e,t)}catch(a){n.error(a.message),process.exitCode=1;return}if(r.admins.length===0){n.header("Linked accounts"),n.info("None","no accounts are linked to this device"),n.blank();return}let s=he(r.admins),i=(a,d)=>{let c=String(d+1).padStart(2," "),f=a.linkedAt.slice(0,10);n.info(`${c}. ${a.maskedEmail}`,`${a.alias} \xB7 linked ${f} \xB7 id ${a.userId.slice(0,8)}\u2026`)},o=s.filter(a=>!a.blocked),l=s.filter(a=>a.blocked);n.header("Linked accounts"),o.forEach(a=>i(a,s.indexOf(a))),o.length===0&&n.info("None active","every linked account is blocked"),n.blank(),l.length>0&&(n.header("Blocked"),l.forEach(a=>i(a,s.indexOf(a))),n.blank()),n.step("Block one with: aicommander-agent block-admin <number|id>"),l.length>0&&n.step("Unblock one with: aicommander-agent unblock-admin <number|id>"),n.blank()}async function or(){return(await C())?.serverUrl??process.env.AICOMMANDER_SERVER??"https://aicommander.dev"}async function an(e,t){b();let r=await or(),s=K(),i;try{i=await Se(r,s)}catch(c){n.error(c.message),process.exitCode=1;return}let o=he(i.admins),l=sn(o,e);if(l.kind==="not_found"){n.error(`No linked account matches "${e}".`),n.info("See options","aicommander-agent list-admins"),process.exitCode=1;return}if(l.kind==="ambiguous"){n.error(`"${e}" is ambiguous \u2014 it matches ${l.matches.length} accounts.`),n.info("Disambiguate","use the list number or a longer id"),process.exitCode=1;return}let a=l.admin,d=t==="block"?await rn(r,s,a.userId):await on(r,s,a.userId);d.ok?t==="block"?(n.ok(`Blocked ${a.maskedEmail} (${a.alias}).`),n.info("Note","their access is cut off; run unblock-admin anytime to restore it")):n.ok(`Unblocked ${a.maskedEmail} (${a.alias}) \u2014 access restored.`):d.error==="not_found"?n.warn(t==="block"?"That account isn't linked (or is already blocked).":"That account isn't blocked."):(n.error(d.message??`Failed to ${t} the account.`),process.exitCode=1),n.blank()}function Ke(e){return an(e,"block")}function Je(e){return an(e,"unblock")}ot(process.argv)||(_.name("aicommander-agent").description("AI Commander remote agent + service controller").version(O),_.command("run",{isDefault:!0}).description("Run the agent: register with the relay and listen for commands").action(async()=>{await Ft()}),_.command("install").description("Install and start the agent as a systemd service (Linux, root)").option("--server <url>","Relay server URL baked into the service unit").action(async e=>{await Ue(e)}),_.command("status").description("Show agent status, session code, and uptime").option("--reveal","Show the full session code (otherwise masked)").action(async e=>{await He(e)}),_.command("enable").description("Start and enable the agent service").action(()=>{Fe()}),_.command("disable").description("Stop and disable the agent service").action(()=>{Ge()}),_.command("change-code").aliases(["reset-code"]).description("Reset the access code \u2014 mint a new code and remove ALL access (unlinks every linked account)").option("-y, --yes","Skip the confirmation prompt").action(async e=>{await Be(e)}),_.command("list-admins").description("List the accounts (admins) linked to this device").action(async()=>{await je()}),_.command("block-admin <account>").aliases(["revoke-admin"]).description("Block one account on this device (by list number or id) \u2014 refuses access; unblock anytime").action(async e=>{await Ke(e)}),_.command("unblock-admin <account>").description("Restore a blocked account's access (by list number or id)").action(async e=>{await Je(e)}),_.command("uninstall").description("Fully remove the agent (stop, disable, delete files)").option("-f, --force","Skip confirmation requirement").action(e=>{We(e)}),_.parseAsync().catch(e=>{console.error("Fatal error:",e),process.exit(1)}));
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
`).filter(u=>u.startsWith(`${R}:`));if(t.length===0)throw new Error(`secure exec user "${R}" not found in /etc/passwd \u2014 reinstall the agent to create it`);if(t.length>1)throw new Error(`multiple /etc/passwd entries for "${R}" \u2014 refusing to guess which is the sandbox user`);let r=t[0].split(":");if(r.length!==7)throw new Error(`malformed passwd entry for ${R}`);let o=r[2],s=r[3];if(!/^\d+$/.test(o)||!/^\d+$/.test(s))throw new Error(`could not resolve uid/gid for ${R}`);let n=Number(o),i=Number(s),c=r[5]||"/tmp";if(!Number.isInteger(n)||!Number.isInteger(i))throw new Error(`could not resolve uid/gid for ${R}`);if(n===0)throw new Error("refusing to run secure exec as uid 0");if(i===0)throw new Error("refusing to run secure exec as gid 0");return{uid:n,gid:i,home:c,name:R}}var
|
|
3
|
-
\u26A0 Ignoring malformed AICOMMANDER_SERVER (${t}); using ${
|
|
4
|
-
`),
|
|
1
|
+
import lt from"os";var U="1.0.27";var Re="device_secret_mismatch";function ut(e){try{let t=JSON.parse(e);return typeof t.code=="string"?t.code:null}catch{return null}}var O=class extends Error{constructor(r,o){super(`Registration failed (${r}): ${o}`);this.status=r;this.body=o;this.name="RegistrationError",this.code=ut(o)}status;body;code};async function J(e,t,r={}){let o={hostname:lt.hostname(),platform:process.platform,arch:process.arch,agentVersion:U,...t?{deviceId:t.deviceId,deviceSecret:t.deviceSecret}:{},...r.forceNew?{forceNew:!0}:r.currentCode?{currentCode:r.currentCode}:{}},s=await fetch(`${e}/api/register`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o)});if(!s.ok){let n=await s.text();throw new O(s.status,n)}return s.json()}import At from"ws";import bt from"os";import{spawn as Ie}from"child_process";var T="https://aicommander.dev";var R="aicommander-exec";var xe="ABCDEFGHJKMNPQRSTVWXYZ0123456789";var fr=Math.floor(256/xe.length)*xe.length;function se(e,t){let r=e.split(".").map(n=>parseInt(n,10)||0),o=t.split(".").map(n=>parseInt(n,10)||0),s=Math.max(r.length,o.length);for(let n=0;n<s;n++){let i=(r[n]??0)-(o[n]??0);if(i!==0)return i>0?1:-1}return 0}function Ce(e,t){return se(e,t)>0}var De=process.platform==="win32";function Oe(e,t,r,o){let s=Date.now(),n=Ie(e,[],{shell:!0,cwd:t??process.env.HOME,env:{...process.env,...r??{}},stdio:["ignore","pipe","pipe"],detached:!De}),i=!1,c=null,u=()=>{c&&(clearTimeout(c),c=null)};n.stdout?.on("data",d=>{o.onOutput(d.toString("base64"),"stdout")}),n.stderr?.on("data",d=>{o.onOutput(d.toString("base64"),"stderr")}),n.on("close",d=>{i=!0,u(),o.onDone(d??-1,Date.now()-s)}),n.on("error",d=>{i=!0,u(),o.onError(d.message)});let a=d=>{if(!(i||n.pid==null))try{De?Ie("taskkill",["/pid",String(n.pid),"/T","/F"],{stdio:"ignore"}):process.kill(-n.pid,d)}catch{}};return{kill:()=>{i||(a("SIGTERM"),u(),c=setTimeout(()=>{c=null,a("SIGKILL")},5e3),c.unref?.())}}}import{spawn as pt}from"node:child_process";import{readFileSync as ft}from"node:fs";import{isAbsolute as ht}from"node:path";import{createRequire as dt}from"node:module";var mt=dt(import.meta.url),Ne="__secure-exec-drop";function gt(){let e=!1;try{e=mt("node:sea").isSea()}catch{}return e}function Me(e){let t=process.argv[1],r=!gt()&&t?[t,Ne,...e]:[Ne,...e];return{cmd:process.execPath,args:r}}function St(){let e;try{e=ft("/etc/passwd","utf8")}catch{throw new Error("cannot read /etc/passwd to resolve the secure-exec user")}let t=e.split(`
|
|
2
|
+
`).filter(u=>u.startsWith(`${R}:`));if(t.length===0)throw new Error(`secure exec user "${R}" not found in /etc/passwd \u2014 reinstall the agent to create it`);if(t.length>1)throw new Error(`multiple /etc/passwd entries for "${R}" \u2014 refusing to guess which is the sandbox user`);let r=t[0].split(":");if(r.length!==7)throw new Error(`malformed passwd entry for ${R}`);let o=r[2],s=r[3];if(!/^\d+$/.test(o)||!/^\d+$/.test(s))throw new Error(`could not resolve uid/gid for ${R}`);let n=Number(o),i=Number(s),c=r[5]||"/tmp";if(!Number.isInteger(n)||!Number.isInteger(i))throw new Error(`could not resolve uid/gid for ${R}`);if(n===0)throw new Error("refusing to run secure exec as uid 0");if(i===0)throw new Error("refusing to run secure exec as gid 0");return{uid:n,gid:i,home:c,name:R}}var Et="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",yt=new Set(["NODE_OPTIONS","BASH_ENV","ENV","IFS","PYTHONSTARTUP","PYTHONPATH","PERL5OPT","RUBYOPT","GIT_EXTERNAL_DIFF","PATH","HOME","USER","LOGNAME"]),_t=e=>/^LD_/.test(e)||yt.has(e);function Pe(e,t){if(process.platform!=="linux")throw new Error("secure exec is only supported on Linux");if(typeof process.getuid=="function"&&process.getuid()!==0)throw new Error("secure exec requires the agent to run as root (to drop privilege)");let r=e.argv;if(!Array.isArray(r)||r.length===0||typeof r[0]!="string")throw new Error("secure exec requires a non-empty argv");let o=r[0];if(o.includes("/"))throw new Error(`command "${o}" must be a bare command name (no path separators)`);if(!e.allowedCommands.includes(o))throw new Error(`command "${o}" is not in this token's allowlist`);if(e.cwd!=null&&!ht(e.cwd))throw new Error(`cwd "${e.cwd}" must be an absolute path`);let s=null;if(e.input!=null&&(s=Buffer.from(e.input,"base64"),s.length>262144))throw new Error(`stdin payload ${s.length}B exceeds limit ${262144}B`);let n=St(),i={HOME:n.home,USER:n.name,LOGNAME:n.name,PATH:Et},c={};for(let[g,C]of Object.entries(e.env??{}))_t(g)||(c[g]=C);let u={...c,...i},a=Date.now(),{cmd:d,args:m}=Me([String(n.uid),String(n.gid),n.name,o,...r.slice(1)]),l=pt(d,m,{cwd:e.cwd??n.home,env:u,stdio:["pipe","pipe","pipe"],detached:!0,shell:!1});s&&l.stdin?(l.stdin.on("error",()=>{}),l.stdin.end(s)):l.stdin?.end();let E=!1,S=null,b=()=>{S&&(clearTimeout(S),S=null)};l.stdout?.on("data",g=>{t.onOutput(g.toString("base64"),"stdout")}),l.stderr?.on("data",g=>{t.onOutput(g.toString("base64"),"stderr")}),l.on("close",g=>{E=!0,b(),t.onDone(g??-1,Date.now()-a)}),l.on("error",g=>{E=!0,b(),t.onError(g.message)});let _=g=>{if(!(E||l.pid==null))try{process.kill(-l.pid,g)}catch{}};return{kill:()=>{E||(_("SIGTERM"),b(),S=setTimeout(()=>{S=null,_("SIGKILL")},5e3),S.unref?.())}}}import B from"chalk";function Le(e,t){console.log(B.yellow(` Reconnecting in ${Math.round(t/1e3)}s (attempt ${e})...`))}function He(){console.log(B.green(" Connected to relay \u2014 ready for commands."))}function ae(e){console.log(B.cyan(` > ${e}`))}function ce(e,t){let r=e===0?B.green:B.red;console.log(r(` Done. Exit ${e} in ${t}ms`))}function vt(){let e=process.env.AICOMMANDER_DEV;return e==="1"||e==="true"}function Ue(e){let t=e.toLowerCase();return t==="localhost"||t==="127.0.0.1"||t==="::1"||t==="[::1]"}function $e(){return new URL(T).origin}function Be(e){let t=(e??"").trim();if(!t)return T;let r;try{r=new URL(t)}catch{return console.warn(`
|
|
3
|
+
\u26A0 Ignoring malformed AICOMMANDER_SERVER (${t}); using ${T}.
|
|
4
|
+
`),T}if(r.origin===$e())return T;let o=Ue(r.hostname);return!vt()&&!o?(console.warn(`
|
|
5
5
|
\u26A0 AICOMMANDER_SERVER override to "${r.origin}" ignored for safety \u2014
|
|
6
|
-
the agent runs as root and is host-locked to ${
|
|
6
|
+
the agent runs as root and is host-locked to ${$e()}.
|
|
7
7
|
Set AICOMMANDER_DEV=1 to allow a custom relay for development.
|
|
8
|
-
`),
|
|
9
|
-
\u26A0 Refusing plaintext relay "${r.origin}" (https required); using ${
|
|
10
|
-
`),w):t.replace(/\/+$/,"")}function Ue(e){let t;try{t=new URL(e)}catch{throw new Error("Refusing to connect: malformed relay WebSocket URL.")}if(t.protocol!=="wss:"&&!(t.protocol==="ws:"&&He(t.hostname)))throw new Error(`Refusing to open a plaintext relay socket (${t.protocol}//${t.host}); wss:// required.`)}var ce={capable:!1,enabled:!1,expiresAt:null},Fe=360*60*1e3;function le(e,t){return new Promise((r,o)=>{if(t.aborted){o(new DOMException("Aborted","AbortError"));return}let s=setTimeout(r,e);t.addEventListener("abort",()=>{clearTimeout(s),o(new DOMException("Aborted","AbortError"))},{once:!0})})}async function ue(e){let t=1e3,r=3e4,o=0,s=e.agentToken;for(;!e.signal?.aborted;){o++;try{let n=await bt(e,s);if(t=1e3,o=0,n&&e.reauth&&!e.signal?.aborted)try{s=await e.reauth()}catch{}}catch(n){if(n.name==="AbortError")break;let i=.8+Math.random()*.4,c=Math.min(t*i,r);e.silent||ke(o,c);try{await le(c,e.signal??new AbortController().signal)}catch(u){if(u.name==="AbortError")break}t=Math.min(t*2,r)}}}async function bt(e,t){let{serverUrl:r,sessionCode:o}=e,s=`${r.replace(/^http/,"ws")}/ws/agent/${o}?token=${t}`;Ue(s);let n=a=>{try{e.onStatus?.(a)}catch{}},i=a=>{try{e.onActivity?.(a)}catch{}},c=a=>{if(a)try{e.onRemoteActivity?.(a)}catch{}},u=()=>{try{e.onHeartbeat?.()}catch{}};return n("connecting"),new Promise((a,d)=>{let m=new vt(s),l=null,E=null,S=null,b=!1,_=null,g=!1,C=()=>{S&&(clearTimeout(S),S=null)},f=()=>{_&&(clearInterval(_),_=null)},I=()=>{if(!e.reauthIntervalMs||!e.reauth)return;let h=()=>{if(l){S=setTimeout(h,3e4);return}b=!0,m.close(1e3,"reauth")};S=setTimeout(h,e.reauthIntervalMs)},P=()=>{g||(g=!0,C(),f(),a(b))},L=h=>{g||(g=!0,C(),f(),d(h))},be=()=>{u(),E&&clearTimeout(E),E=setTimeout(()=>{m.terminate(),L(new Error("Ping timeout"))},9e4)};e.signal?.addEventListener("abort",()=>m.close(1e3,"disabled"),{once:!0});let te=()=>{if(g)return;let h={type:"agent:screen_state",screenShare:e.screenShare?.getState()??ce};try{m.send(JSON.stringify(h))}catch{}};m.on("open",()=>{n("connected"),be(),I();let h={type:"agent:register",hostname:At.hostname(),platform:process.platform,arch:process.arch,agentVersion:U,screenShare:e.screenShare?.getState()??ce};m.send(JSON.stringify(h)),e.screenShare?.on("change",te),e.silent||Pe()}),m.on("message",h=>{let y;try{y=JSON.parse(String(h))}catch{return}switch(y.type){case"do:ping":be();let it={type:"agent:pong",ts:y.ts};m.send(JSON.stringify(it));break;case"do:exec":{ie(y.command),i(!0),c(y.operator);let{commandId:T,command:D,cwd:re,env:ne}=y,X=Date.now(),H=!1;f(),_=setInterval(()=>{let v=Date.now()-X;if(v>=3e5&&!H){H=!0;let p={type:"agent:exec_idle",commandId:T,idleMs:v};m.send(JSON.stringify(p))}},3e5),_.unref?.(),l=Ie(D,re,ne,{onOutput:(v,p)=>{X=Date.now(),H=!1;let A={type:"agent:output",commandId:T,chunk:v,stream:p};m.send(JSON.stringify(A))},onDone:(v,p)=>{l=null,f(),i(!1),ae(v,p);let A={type:"agent:done",commandId:T,exitCode:v,durationMs:p};m.send(JSON.stringify(A))},onError:v=>{l=null,f(),i(!1);let p={type:"agent:error",commandId:T,error:v};m.send(JSON.stringify(p))}}).kill;break}case"do:secure_exec":{let{commandId:T,argv:D,allowedCommands:re,input:ne,cwd:X,env:H}=y,Te=(p,A)=>{l=null,f(),i(!1),ae(p,A);let $={type:"agent:done",commandId:T,exitCode:p,durationMs:A};m.send(JSON.stringify($))},v=p=>{l=null,f(),i(!1);let A={type:"agent:error",commandId:T,error:p};m.send(JSON.stringify(A))};try{let p=Array.isArray(D)?D.join(" "):String(D);ie(p),i(!0);let A=Date.now(),$=!1;f(),_=setInterval(()=>{let K=Date.now()-A;if(K>=3e5&&!$){$=!0;let oe={type:"agent:exec_idle",commandId:T,idleMs:K};m.send(JSON.stringify(oe))}},3e5),_.unref?.(),l=Me({argv:D,allowedCommands:re,input:ne,cwd:X,env:H},{onOutput:(K,oe)=>{A=Date.now(),$=!1;let at={type:"agent:output",commandId:T,chunk:K,stream:oe};m.send(JSON.stringify(at))},onDone:Te,onError:v}).kill}catch(p){v(p instanceof Error?p.message:String(p))}break}case"do:kill":l&&(l(),l=null);break;case"do:screenshot":c(y.operator),Tt(m,y.requestId,e.screenShare);break}}),m.on("close",(h,y)=>{E&&clearTimeout(E),f(),e.screenShare?.off("change",te),l&&(l(),l=null,i(!1)),n("disconnected"),h===1e3||h===4001?P():L(new Error(`WS closed: ${h} ${String(y)}`))}),m.on("error",h=>{E&&clearTimeout(E),f(),e.screenShare?.off("change",te),n("disconnected"),L(h)}),m.on("unexpected-response",(h,y)=>{d(new Error(`Unexpected HTTP response: ${y.statusCode}`))})})}async function Tt(e,t,r){let o=i=>{try{e.send(JSON.stringify(i))}catch{}},s=i=>o({type:"agent:screenshot_error",requestId:t,error:i}),n=r?.getState()??ce;if(!r||!n.capable){s("This machine cannot share its screen (desktop macOS/Windows only).");return}if(!n.enabled){s("Screen sharing is turned off on this machine.");return}try{let{data:i,mimeType:c}=await r.capture();if(i.length>10485760){let a=(i.length/1048576).toFixed(1);s(`Screenshot is ${a} MB, which exceeds the ${10485760/(1024*1024)} MB limit.`);return}let u=i.toString("base64");for(let a=0;a<u.length;a+=524288)o({type:"agent:screenshot_chunk",requestId:t,chunk:u.slice(a,a+524288)});o({type:"agent:screenshot_done",requestId:t,mimeType:c,totalBytes:i.length})}catch(i){s(`Screenshot failed: ${i.message}`)}}import Ft from"node:events";import Z from"node:fs";import N from"node:path";import wt from"node:os";import{randomUUID as Rt,randomBytes as xt}from"node:crypto";var Q="/etc/aicommander-agent",q=N.join(wt.homedir(),".config","aicommander-agent"),G="device.json";function me(){return{deviceId:Rt(),deviceSecret:xt(32).toString("base64url")}}function Ct(e){return typeof e=="object"&&e!==null&&typeof e.deviceId=="string"&&e.deviceId.length>0&&typeof e.deviceSecret=="string"&&e.deviceSecret.length>0}function de(e){try{let t=Z.readFileSync(e,"utf8"),r=JSON.parse(t);if(Ct(r))return{deviceId:r.deviceId,deviceSecret:r.deviceSecret}}catch{}return null}function Ge(e){try{Z.rmSync(N.join(e,G),{force:!0})}catch{}}function M(e,t){try{return Z.mkdirSync(e,{recursive:!0,mode:448}),Z.writeFileSync(N.join(e,G),JSON.stringify(t,null,2),{encoding:"utf8",mode:384}),!0}catch{return!1}}function W(e){if(e){let s=de(N.join(e,G));if(s)return s;let n=me();return M(e,n),n}let t=de(N.join(Q,G));if(t)return t;let r=de(N.join(q,G));if(r)return r;let o=me();return M(Q,o)||M(q,o),o}function We(e){let t=me();return e?(M(e,t),t):M(Q,t)?(Ge(q),t):(M(q,t),Ge(Q),t)}function Ve(e){return{"X-Device-Id":e.deviceId,"X-Device-Secret":e.deviceSecret}}var je=1e4;async function ge(e,t){let r;try{r=await fetch(`${e}/api/device/admins`,{headers:Ve(t),signal:AbortSignal.timeout(je)})}catch{throw new Error("Failed to list linked accounts (relay unreachable or timed out).")}if(!r.ok)throw new Error(`Failed to list linked accounts (HTTP ${r.status}).`);return await r.json()}async function Xe(e,t,r,o){let s;try{s=await fetch(`${e}/api/device/${r}`,{method:"POST",headers:{...Ve(t),"Content-Type":"application/json"},body:JSON.stringify({userId:o}),signal:AbortSignal.timeout(je)})}catch{return{ok:!1,error:"unreachable"}}let n;try{n=await s.json()}catch{n={ok:!1,error:`http_${s.status}`}}return{...n,ok:s.ok}}function pe(e,t,r){return Xe(e,t,"block",r)}function fe(e,t,r){return Xe(e,t,"unblock",r)}function It(e){return[...e.filter(t=>!t.blocked),...e.filter(t=>t.blocked)]}function Dt(e,t){let r=t.trim();if(!r)return{kind:"not_found"};if(/^\d+$/.test(r)){let n=Number(r)-1;return n>=0&&n<e.length?{kind:"ok",admin:e[n]}:{kind:"not_found"}}let o=e.find(n=>n.userId===r);if(o)return{kind:"ok",admin:o};let s=e.filter(n=>n.userId.startsWith(r));return s.length===1?{kind:"ok",admin:s[0]}:s.length>1?{kind:"ambiguous",matches:s}:{kind:"not_found"}}import x from"node:fs";import k from"node:path";import Ot from"node:os";var Nt="/etc/aicommander-agent",Mt=k.join(Ot.homedir(),".config","aicommander-agent"),he="session.json",Ke=".rotate";function kt(e){return typeof e=="object"&&e!==null&&typeof e.sessionCode=="string"&&e.sessionCode.length>0&&typeof e.agentToken=="string"&&e.agentToken.length>0}function Pt(e){try{let t=x.readFileSync(e,"utf8"),r=JSON.parse(t);if(kt(r))return{sessionCode:r.sessionCode,agentToken:r.agentToken}}catch{}return null}function Lt(e,t){try{return x.mkdirSync(e,{recursive:!0,mode:448}),x.writeFileSync(k.join(e,he),JSON.stringify(t,null,2),{encoding:"utf8",mode:384}),!0}catch{return!1}}function V(e){return e?[e]:[Nt,Mt]}function Je(e){for(let t of V(e)){let r=Pt(k.join(t,he));if(r)return r}return null}function Se(e,t){for(let r of V(t))if(Lt(r,e))return}function Ee(e){for(let t of V(e))try{x.rmSync(k.join(t,he),{force:!0})}catch{}}function Ye(e){for(let t of V(e))try{x.mkdirSync(t,{recursive:!0,mode:448}),x.writeFileSync(k.join(t,Ke),"",{mode:384});return}catch{}}function Ze(e){let t=!1;for(let r of V(e)){let o=k.join(r,Ke);try{x.existsSync(o)&&(t=!0,x.rmSync(o,{force:!0}))}catch{}}return t}function Bt(e,t){if(!e)return!0;let r=t-e.lastNotifiedAt;if(r>=288e5)return!0;let o=t-e.lastActivityAt>6e5,s=r>=36e5;return o&&s}var z=class{seen=new Map;note(t,r){let o=this.seen.get(t),s=Bt(o,r);return this.seen.set(t,{lastActivityAt:r,lastNotifiedAt:s?r:o?.lastNotifiedAt??0}),s}};var ye=class extends Ft{serverUrl;configDir;screenShare;abortController=null;_status="disabled";_code=null;_active=!1;_lastHeartbeatAt=0;_remote=new z;constructor(t,r){super(),this.serverUrl=$e(t),this.configDir=r?.configDir,this.screenShare=r?.screenShare,this.on("error",()=>{})}get status(){return this._status}get sessionCode(){return this._code}get active(){return this._active}get lastHeartbeatAt(){return this._lastHeartbeatAt}start(){this.abortController||(this._setStatus("connecting"),this.abortController=new AbortController,this._runLoop(this.abortController))}async _runLoop(t){let r=1e3,o=3e4,s=W(this.configDir),n=!1,i=Ze(this.configDir);for(;!t.signal.aborted;){this._setStatus("connecting");try{let c=i?null:Je(this.configDir),{sessionCode:u,agentToken:a}=await J(this.serverUrl,s,{forceNew:i,...c?{currentCode:c.sessionCode}:{}});if(t.signal.aborted)break;Se({sessionCode:u,agentToken:a},this.configDir),i=!1,this._code=u;try{this.emit("code",u)}catch{}await ue({serverUrl:this.serverUrl,sessionCode:u,agentToken:a,signal:t.signal,silent:!0,...this.screenShare?{screenShare:this.screenShare}:{},onStatus:d=>this._setStatus(d),onActivity:d=>this._setActive(d),onRemoteActivity:d=>this._noteRemote(d),onHeartbeat:()=>this._onHeartbeat(),reauthIntervalMs:Fe,reauth:async()=>{let d=await J(this.serverUrl,s,{currentCode:u});if(t.signal.aborted||this.abortController!==t)throw new DOMException("Aborted","AbortError");return Se({sessionCode:d.sessionCode,agentToken:d.agentToken},this.configDir),d.agentToken}});break}catch(c){if(t.signal.aborted)break;if(c instanceof O&&c.status===403&&c.code===we&&!n){n=!0,console.error(`[AIC] Relay rejected this device: ${c.message}. Regenerating device identity and retrying as a new device.`),s=We(this.configDir),Ee(this.configDir),i=!1;continue}c instanceof O&&console.error(`[AIC] ${c.message}`),this._setStatus("disconnected");try{await le(r,t.signal)}catch{break}r=Math.min(r*2,o)}}this.abortController===t&&(this._setStatus("disabled"),this.abortController=null)}stop(){this.abortController?.abort(),this.abortController=null,this._code=null,this._setStatus("disabled")}async restart(){this.stop(),await Promise.resolve(),this.start()}async changeCode(){Ee(this.configDir),Ye(this.configDir),await this.restart()}async listAdmins(){let t=W(this.configDir);return ge(this.serverUrl,t)}async blockAdmin(t){let r=W(this.configDir);return pe(this.serverUrl,r,t)}async unblockAdmin(t){let r=W(this.configDir);return fe(this.serverUrl,r,t)}_setStatus(t){if(t!=="connected"&&this._active&&this._setActive(!1),this._status!==t){this._status=t;try{this.emit("status",t)}catch{}}}_noteRemote(t){let r=Date.now();if(this._remote.note(t.id,r))try{this.emit("remote-connect",{id:t.id,anonymous:t.anonymous})}catch{}}_onHeartbeat(){this._lastHeartbeatAt=Date.now();try{this.emit("heartbeat",this._lastHeartbeatAt)}catch{}}_setActive(t){if(this._active!==t){this._active=t;try{this.emit("activity",t)}catch{}}}};import{spawn as Gt}from"child_process";import{promises as _e}from"fs";import Wt from"os";import Vt from"path";import jt from"crypto";function Xt(){return process.platform==="darwin"||process.platform==="win32"}async function Kt(){if(process.platform==="darwin")return Jt();if(process.platform==="win32")return Yt();throw new Error("Screen capture is only supported on the macOS and Windows desktop apps.")}function Qe(e){return Vt.join(Wt.tmpdir(),`aic-shot-${jt.randomUUID()}.${e}`)}async function Jt(){let e=Qe("png");try{return await et("screencapture",["-x","-t","png",e]),await qe(e,"image/png")}finally{await ze(e)}}async function Yt(){let e=Qe("png"),r=["Add-Type -AssemblyName System.Windows.Forms,System.Drawing;","$b = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds;","$bmp = New-Object System.Drawing.Bitmap($b.Width, $b.Height);","$g = [System.Drawing.Graphics]::FromImage($bmp);","$g.CopyFromScreen($b.Location, [System.Drawing.Point]::Empty, $b.Size);",`$bmp.Save('${e.replace(/'/g,"''")}', [System.Drawing.Imaging.ImageFormat]::Png);`,"$g.Dispose(); $bmp.Dispose();"].join(" ");try{return await et("powershell",["-NoProfile","-NonInteractive","-Command",r]),await qe(e,"image/png")}finally{await ze(e)}}async function qe(e,t){let r=await _e.stat(e);if(r.size>10485760){let s=(r.size/1048576).toFixed(1);throw new Error(`Screenshot is ${s} MB, which exceeds the ${10485760/(1024*1024)} MB limit.`)}return{data:await _e.readFile(e),mimeType:t}}async function ze(e){try{await _e.unlink(e)}catch{}}function et(e,t){return new Promise((r,o)=>{let s=Gt(e,t,{stdio:["ignore","ignore","pipe"]}),n="";s.stderr?.on("data",i=>{n+=i.toString()}),s.on("error",i=>o(i)),s.on("close",i=>{i===0?r():o(new Error(`${e} exited with code ${i}${n?`: ${n.trim()}`:""}`))})})}import{spawn as nr}from"child_process";import ee from"fs";import tt from"path";import Qt from"os";function ve(e){if(e)return tt.join(e,"heartbeat");if(process.platform==="linux")try{return ee.mkdirSync("/run/aicommander-agent",{recursive:!0}),"/run/aicommander-agent/heartbeat"}catch{}return tt.join(Qt.tmpdir(),"aicommander-agent-heartbeat")}function rt(e){try{let t=`${e}.tmp`;ee.writeFileSync(t,String(Date.now())),ee.renameSync(t,e)}catch{}}var j=null;function qt(e,t=5e3){nt(),rt(e),j=setInterval(()=>rt(e),t),j.unref?.()}function nt(){j&&(clearInterval(j),j=null)}function Ae(e){try{let t=ee.readFileSync(e,"utf8").trim(),r=Number(t);return Number.isFinite(r)?r:null}catch{return null}}async function or(e={}){let t=e.heartbeatPath??ve(e.configDir),r=e.spawnWorker??(d=>nr(process.execPath,process.argv.slice(1),{env:d,stdio:"inherit"})),o=null,s=0,n=!1,i=0,c=[],u=null,a=null;await new Promise(d=>{let m=null,l=null,E=()=>{u&&(clearInterval(u),u=null),a&&(clearTimeout(a),a=null),m&&(process.removeListener("SIGINT",m),m=null),l&&(process.removeListener("SIGTERM",l),l=null)},S=()=>{E(),d()},b=()=>{s=Date.now(),i++;let f={...process.env,AIC_ROLE:"worker",AIC_HEARTBEAT:t};o=r(f),o.on("exit",(I,P)=>{if(o=null,n){S();return}if(e.maxSpawns!=null&&i>=e.maxSpawns){S();return}_(`worker exited (code=${I??"null"} signal=${P??"null"})`)}),o.on("error",()=>{})},_=f=>{let I=Date.now();c=c.filter(L=>I-L<3e5),c.push(I);let P=c.length>5?6e4:0;a&&clearTimeout(a),a=setTimeout(()=>{a=null,n||b()},P),a.unref?.()},g=()=>{if(o)try{o.kill("SIGKILL")}catch{}};u=setInterval(()=>{if(n||!o||Date.now()-s<3e4)return;let f=Ae(t);f!=null&&Date.now()-f>3e4&&g()},1e4),u.unref?.();let C=f=>{if(n=!0,u&&(clearInterval(u),u=null),a&&(clearTimeout(a),a=null),o)try{o.kill(f)}catch{}else S()};m=()=>C("SIGINT"),l=()=>C("SIGTERM"),process.on("SIGINT",m),process.on("SIGTERM",l),b()})}function st(e,t){let r=e.split(".").map(n=>parseInt(n,10)||0),o=t.split(".").map(n=>parseInt(n,10)||0),s=Math.max(r.length,o.length);for(let n=0;n<s;n++){let i=(r[n]??0)-(o[n]??0);if(i!==0)return i>0?1:-1}return 0}function sr(e,t){return st(e,t)>0}async function ir(e,t=1e4){let r=new AbortController,o=setTimeout(()=>r.abort(),t);try{let s=await fetch(`${e.replace(/\/+$/,"")}/dist/latest`,{signal:r.signal,headers:{Accept:"application/json"}});if(!s.ok)return null;let n=await s.json();return{version:typeof n.version=="string"?n.version:null,mac:typeof n.mac=="string"?n.mac:null,win:typeof n.win=="string"?n.win:null,agentLinuxX64:typeof n.agentLinuxX64=="string"?n.agentLinuxX64:null,agentLinuxArm64:typeof n.agentLinuxArm64=="string"?n.agentLinuxArm64:null}}catch{return null}finally{clearTimeout(o)}}export{U as AGENT_VERSION,ye as AgentController,pe as blockAdmin,Xt as canCaptureScreenshot,Kt as captureScreenshot,st as compareVersions,ve as defaultHeartbeatPath,ge as fetchAdmins,ir as fetchLatestDist,sr as isNewerVersion,It as orderAdmins,Ae as readHeartbeat,J as register,Dt as resolveAdminIdentifier,ue as runConnectionLoop,or as runSupervisor,qt as startHeartbeat,nt as stopHeartbeat,fe as unblockAdmin};
|
|
8
|
+
`),T):r.protocol!=="https:"&&!o?(console.warn(`
|
|
9
|
+
\u26A0 Refusing plaintext relay "${r.origin}" (https required); using ${T}.
|
|
10
|
+
`),T):t.replace(/\/+$/,"")}function Fe(e){let t;try{t=new URL(e)}catch{throw new Error("Refusing to connect: malformed relay WebSocket URL.")}if(t.protocol!=="wss:"&&!(t.protocol==="ws:"&&Ue(t.hostname)))throw new Error(`Refusing to open a plaintext relay socket (${t.protocol}//${t.host}); wss:// required.`)}var le={capable:!1,enabled:!1,expiresAt:null},Ve=360*60*1e3;function ue(e,t){return new Promise((r,o)=>{if(t.aborted){o(new DOMException("Aborted","AbortError"));return}let s=setTimeout(r,e);t.addEventListener("abort",()=>{clearTimeout(s),o(new DOMException("Aborted","AbortError"))},{once:!0})})}async function de(e){let t=1e3,r=3e4,o=0,s=e.agentToken;for(;!e.signal?.aborted;){o++;try{let n=await wt(e,s);if(t=1e3,o=0,n&&e.reauth&&!e.signal?.aborted)try{s=await e.reauth()}catch{}}catch(n){if(n.name==="AbortError")break;let i=.8+Math.random()*.4,c=Math.min(t*i,r);e.silent||Le(o,c);try{await ue(c,e.signal??new AbortController().signal)}catch(u){if(u.name==="AbortError")break}t=Math.min(t*2,r)}}}async function wt(e,t){let{serverUrl:r,sessionCode:o}=e,s=`${r.replace(/^http/,"ws")}/ws/agent/${o}?token=${t}`;Fe(s);let n=a=>{try{e.onStatus?.(a)}catch{}},i=a=>{try{e.onActivity?.(a)}catch{}},c=a=>{if(a)try{e.onRemoteActivity?.(a)}catch{}},u=()=>{try{e.onHeartbeat?.()}catch{}};return n("connecting"),new Promise((a,d)=>{let m=new At(s),l=null,E=null,S=null,b=!1,_=null,g=!1,C=()=>{S&&(clearTimeout(S),S=null)},f=()=>{_&&(clearInterval(_),_=null)},I=()=>{if(!e.reauthIntervalMs||!e.reauth)return;let h=()=>{if(l){S=setTimeout(h,3e4);return}b=!0,m.close(1e3,"reauth")};S=setTimeout(h,e.reauthIntervalMs)},P=()=>{g||(g=!0,C(),f(),a(b))},L=h=>{g||(g=!0,C(),f(),d(h))},we=()=>{u(),E&&clearTimeout(E),E=setTimeout(()=>{m.terminate(),L(new Error("Ping timeout"))},9e4)};e.signal?.addEventListener("abort",()=>m.close(1e3,"disabled"),{once:!0});let te=()=>{if(g)return;let h={type:"agent:screen_state",screenShare:e.screenShare?.getState()??le};try{m.send(JSON.stringify(h))}catch{}};m.on("open",()=>{n("connected"),we(),I();let h={type:"agent:register",hostname:bt.hostname(),platform:process.platform,arch:process.arch,agentVersion:U,screenShare:e.screenShare?.getState()??le};m.send(JSON.stringify(h)),e.screenShare?.on("change",te),e.silent||He()}),m.on("message",h=>{let y;try{y=JSON.parse(String(h))}catch{return}switch(y.type){case"do:ping":we();let at={type:"agent:pong",ts:y.ts};m.send(JSON.stringify(at));break;case"do:exec":{ae(y.command),i(!0),c(y.operator);let{commandId:w,command:D,cwd:re,env:ne}=y,X=Date.now(),H=!1;f(),_=setInterval(()=>{let v=Date.now()-X;if(v>=3e5&&!H){H=!0;let p={type:"agent:exec_idle",commandId:w,idleMs:v};m.send(JSON.stringify(p))}},3e5),_.unref?.(),l=Oe(D,re,ne,{onOutput:(v,p)=>{X=Date.now(),H=!1;let A={type:"agent:output",commandId:w,chunk:v,stream:p};m.send(JSON.stringify(A))},onDone:(v,p)=>{l=null,f(),i(!1),ce(v,p);let A={type:"agent:done",commandId:w,exitCode:v,durationMs:p};m.send(JSON.stringify(A))},onError:v=>{l=null,f(),i(!1);let p={type:"agent:error",commandId:w,error:v};m.send(JSON.stringify(p))}}).kill;break}case"do:secure_exec":{let{commandId:w,argv:D,allowedCommands:re,input:ne,cwd:X,env:H}=y,Te=(p,A)=>{l=null,f(),i(!1),ce(p,A);let $={type:"agent:done",commandId:w,exitCode:p,durationMs:A};m.send(JSON.stringify($))},v=p=>{l=null,f(),i(!1);let A={type:"agent:error",commandId:w,error:p};m.send(JSON.stringify(A))};try{let p=Array.isArray(D)?D.join(" "):String(D);ae(p),i(!0);let A=Date.now(),$=!1;f(),_=setInterval(()=>{let K=Date.now()-A;if(K>=3e5&&!$){$=!0;let oe={type:"agent:exec_idle",commandId:w,idleMs:K};m.send(JSON.stringify(oe))}},3e5),_.unref?.(),l=Pe({argv:D,allowedCommands:re,input:ne,cwd:X,env:H},{onOutput:(K,oe)=>{A=Date.now(),$=!1;let ct={type:"agent:output",commandId:w,chunk:K,stream:oe};m.send(JSON.stringify(ct))},onDone:Te,onError:v}).kill}catch(p){v(p instanceof Error?p.message:String(p))}break}case"do:kill":l&&(l(),l=null);break;case"do:screenshot":c(y.operator),Tt(m,y.requestId,e.screenShare);break}}),m.on("close",(h,y)=>{E&&clearTimeout(E),f(),e.screenShare?.off("change",te),l&&(l(),l=null,i(!1)),n("disconnected"),h===1e3||h===4001?P():L(new Error(`WS closed: ${h} ${String(y)}`))}),m.on("error",h=>{E&&clearTimeout(E),f(),e.screenShare?.off("change",te),n("disconnected"),L(h)}),m.on("unexpected-response",(h,y)=>{d(new Error(`Unexpected HTTP response: ${y.statusCode}`))})})}async function Tt(e,t,r){let o=i=>{try{e.send(JSON.stringify(i))}catch{}},s=i=>o({type:"agent:screenshot_error",requestId:t,error:i}),n=r?.getState()??le;if(!r||!n.capable){s("This machine cannot share its screen (desktop macOS/Windows only).");return}if(!n.enabled){s("Screen sharing is turned off on this machine.");return}try{let{data:i,mimeType:c}=await r.capture();if(i.length>10485760){let a=(i.length/1048576).toFixed(1);s(`Screenshot is ${a} MB, which exceeds the ${10485760/(1024*1024)} MB limit.`);return}let u=i.toString("base64");for(let a=0;a<u.length;a+=524288)o({type:"agent:screenshot_chunk",requestId:t,chunk:u.slice(a,a+524288)});o({type:"agent:screenshot_done",requestId:t,mimeType:c,totalBytes:i.length})}catch(i){s(`Screenshot failed: ${i.message}`)}}import Gt from"node:events";import Z from"node:fs";import N from"node:path";import Rt from"node:os";import{randomUUID as xt,randomBytes as Ct}from"node:crypto";var Q="/etc/aicommander-agent",q=N.join(Rt.homedir(),".config","aicommander-agent"),G="device.json";function ge(){return{deviceId:xt(),deviceSecret:Ct(32).toString("base64url")}}function It(e){return typeof e=="object"&&e!==null&&typeof e.deviceId=="string"&&e.deviceId.length>0&&typeof e.deviceSecret=="string"&&e.deviceSecret.length>0}function me(e){try{let t=Z.readFileSync(e,"utf8"),r=JSON.parse(t);if(It(r))return{deviceId:r.deviceId,deviceSecret:r.deviceSecret}}catch{}return null}function We(e){try{Z.rmSync(N.join(e,G),{force:!0})}catch{}}function M(e,t){try{return Z.mkdirSync(e,{recursive:!0,mode:448}),Z.writeFileSync(N.join(e,G),JSON.stringify(t,null,2),{encoding:"utf8",mode:384}),!0}catch{return!1}}function V(e){if(e){let s=me(N.join(e,G));if(s)return s;let n=ge();return M(e,n),n}let t=me(N.join(Q,G));if(t)return t;let r=me(N.join(q,G));if(r)return r;let o=ge();return M(Q,o)||M(q,o),o}function je(e){let t=ge();return e?(M(e,t),t):M(Q,t)?(We(q),t):(M(q,t),We(Q),t)}function Xe(e){return{"X-Device-Id":e.deviceId,"X-Device-Secret":e.deviceSecret}}var Ke=1e4;async function pe(e,t){let r;try{r=await fetch(`${e}/api/device/admins`,{headers:Xe(t),signal:AbortSignal.timeout(Ke)})}catch{throw new Error("Failed to list linked accounts (relay unreachable or timed out).")}if(!r.ok)throw new Error(`Failed to list linked accounts (HTTP ${r.status}).`);return await r.json()}async function Je(e,t,r,o){let s;try{s=await fetch(`${e}/api/device/${r}`,{method:"POST",headers:{...Xe(t),"Content-Type":"application/json"},body:JSON.stringify({userId:o}),signal:AbortSignal.timeout(Ke)})}catch{return{ok:!1,error:"unreachable"}}let n;try{n=await s.json()}catch{n={ok:!1,error:`http_${s.status}`}}return{...n,ok:s.ok}}function fe(e,t,r){return Je(e,t,"block",r)}function he(e,t,r){return Je(e,t,"unblock",r)}function Dt(e){return[...e.filter(t=>!t.blocked),...e.filter(t=>t.blocked)]}function Ot(e,t){let r=t.trim();if(!r)return{kind:"not_found"};if(/^\d+$/.test(r)){let n=Number(r)-1;return n>=0&&n<e.length?{kind:"ok",admin:e[n]}:{kind:"not_found"}}let o=e.find(n=>n.userId===r);if(o)return{kind:"ok",admin:o};let s=e.filter(n=>n.userId.startsWith(r));return s.length===1?{kind:"ok",admin:s[0]}:s.length>1?{kind:"ambiguous",matches:s}:{kind:"not_found"}}import x from"node:fs";import k from"node:path";import Nt from"node:os";var Mt="/etc/aicommander-agent",kt=k.join(Nt.homedir(),".config","aicommander-agent"),Se="session.json",Ye=".rotate";function Pt(e){return typeof e=="object"&&e!==null&&typeof e.sessionCode=="string"&&e.sessionCode.length>0&&typeof e.agentToken=="string"&&e.agentToken.length>0}function Lt(e){try{let t=x.readFileSync(e,"utf8"),r=JSON.parse(t);if(Pt(r))return{sessionCode:r.sessionCode,agentToken:r.agentToken}}catch{}return null}function Ht(e,t){try{return x.mkdirSync(e,{recursive:!0,mode:448}),x.writeFileSync(k.join(e,Se),JSON.stringify(t,null,2),{encoding:"utf8",mode:384}),!0}catch{return!1}}function W(e){return e?[e]:[Mt,kt]}function Ze(e){for(let t of W(e)){let r=Lt(k.join(t,Se));if(r)return r}return null}function Ee(e,t){for(let r of W(t))if(Ht(r,e))return}function ye(e){for(let t of W(e))try{x.rmSync(k.join(t,Se),{force:!0})}catch{}}function Qe(e){for(let t of W(e))try{x.mkdirSync(t,{recursive:!0,mode:448}),x.writeFileSync(k.join(t,Ye),"",{mode:384});return}catch{}}function qe(e){let t=!1;for(let r of W(e)){let o=k.join(r,Ye);try{x.existsSync(o)&&(t=!0,x.rmSync(o,{force:!0}))}catch{}}return t}function Ft(e,t){if(!e)return!0;let r=t-e.lastNotifiedAt;if(r>=288e5)return!0;let o=t-e.lastActivityAt>6e5,s=r>=36e5;return o&&s}var z=class{seen=new Map;note(t,r){let o=this.seen.get(t),s=Ft(o,r);return this.seen.set(t,{lastActivityAt:r,lastNotifiedAt:s?r:o?.lastNotifiedAt??0}),s}};var _e=class extends Gt{serverUrl;configDir;screenShare;abortController=null;_status="disabled";_code=null;_active=!1;_lastHeartbeatAt=0;_remote=new z;constructor(t,r){super(),this.serverUrl=Be(t),this.configDir=r?.configDir,this.screenShare=r?.screenShare,this.on("error",()=>{})}get status(){return this._status}get sessionCode(){return this._code}get active(){return this._active}get lastHeartbeatAt(){return this._lastHeartbeatAt}start(){this.abortController||(this._setStatus("connecting"),this.abortController=new AbortController,this._runLoop(this.abortController))}async _runLoop(t){let r=1e3,o=3e4,s=V(this.configDir),n=!1,i=qe(this.configDir);for(;!t.signal.aborted;){this._setStatus("connecting");try{let c=i?null:Ze(this.configDir),{sessionCode:u,agentToken:a}=await J(this.serverUrl,s,{forceNew:i,...c?{currentCode:c.sessionCode}:{}});if(t.signal.aborted)break;Ee({sessionCode:u,agentToken:a},this.configDir),i=!1,this._code=u;try{this.emit("code",u)}catch{}await de({serverUrl:this.serverUrl,sessionCode:u,agentToken:a,signal:t.signal,silent:!0,...this.screenShare?{screenShare:this.screenShare}:{},onStatus:d=>this._setStatus(d),onActivity:d=>this._setActive(d),onRemoteActivity:d=>this._noteRemote(d),onHeartbeat:()=>this._onHeartbeat(),reauthIntervalMs:Ve,reauth:async()=>{let d=await J(this.serverUrl,s,{currentCode:u});if(t.signal.aborted||this.abortController!==t)throw new DOMException("Aborted","AbortError");return Ee({sessionCode:d.sessionCode,agentToken:d.agentToken},this.configDir),d.agentToken}});break}catch(c){if(t.signal.aborted)break;if(c instanceof O&&c.status===403&&c.code===Re&&!n){n=!0,console.error(`[AIC] Relay rejected this device: ${c.message}. Regenerating device identity and retrying as a new device.`),s=je(this.configDir),ye(this.configDir),i=!1;continue}c instanceof O&&console.error(`[AIC] ${c.message}`),this._setStatus("disconnected");try{await ue(r,t.signal)}catch{break}r=Math.min(r*2,o)}}this.abortController===t&&(this._setStatus("disabled"),this.abortController=null)}stop(){this.abortController?.abort(),this.abortController=null,this._code=null,this._setStatus("disabled")}async restart(){this.stop(),await Promise.resolve(),this.start()}async changeCode(){ye(this.configDir),Qe(this.configDir),await this.restart()}async listAdmins(){let t=V(this.configDir);return pe(this.serverUrl,t)}async blockAdmin(t){let r=V(this.configDir);return fe(this.serverUrl,r,t)}async unblockAdmin(t){let r=V(this.configDir);return he(this.serverUrl,r,t)}_setStatus(t){if(t!=="connected"&&this._active&&this._setActive(!1),this._status!==t){this._status=t;try{this.emit("status",t)}catch{}}}_noteRemote(t){let r=Date.now();if(this._remote.note(t.id,r))try{this.emit("remote-connect",{id:t.id,anonymous:t.anonymous})}catch{}}_onHeartbeat(){this._lastHeartbeatAt=Date.now();try{this.emit("heartbeat",this._lastHeartbeatAt)}catch{}}_setActive(t){if(this._active!==t){this._active=t;try{this.emit("activity",t)}catch{}}}};import{spawn as Vt}from"child_process";import{promises as ve}from"fs";import Wt from"os";import jt from"path";import Xt from"crypto";function Kt(){return process.platform==="darwin"||process.platform==="win32"}async function Jt(){if(process.platform==="darwin")return Yt();if(process.platform==="win32")return Zt();throw new Error("Screen capture is only supported on the macOS and Windows desktop apps.")}function ze(e){return jt.join(Wt.tmpdir(),`aic-shot-${Xt.randomUUID()}.${e}`)}async function Yt(){let e=ze("png");try{return await rt("screencapture",["-x","-t","png",e]),await et(e,"image/png")}finally{await tt(e)}}async function Zt(){let e=ze("png"),r=["Add-Type -AssemblyName System.Windows.Forms,System.Drawing;","$b = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds;","$bmp = New-Object System.Drawing.Bitmap($b.Width, $b.Height);","$g = [System.Drawing.Graphics]::FromImage($bmp);","$g.CopyFromScreen($b.Location, [System.Drawing.Point]::Empty, $b.Size);",`$bmp.Save('${e.replace(/'/g,"''")}', [System.Drawing.Imaging.ImageFormat]::Png);`,"$g.Dispose(); $bmp.Dispose();"].join(" ");try{return await rt("powershell",["-NoProfile","-NonInteractive","-Command",r]),await et(e,"image/png")}finally{await tt(e)}}async function et(e,t){let r=await ve.stat(e);if(r.size>10485760){let s=(r.size/1048576).toFixed(1);throw new Error(`Screenshot is ${s} MB, which exceeds the ${10485760/(1024*1024)} MB limit.`)}return{data:await ve.readFile(e),mimeType:t}}async function tt(e){try{await ve.unlink(e)}catch{}}function rt(e,t){return new Promise((r,o)=>{let s=Vt(e,t,{stdio:["ignore","ignore","pipe"]}),n="";s.stderr?.on("data",i=>{n+=i.toString()}),s.on("error",i=>o(i)),s.on("close",i=>{i===0?r():o(new Error(`${e} exited with code ${i}${n?`: ${n.trim()}`:""}`))})})}import{spawn as or}from"child_process";import ee from"fs";import nt from"path";import qt from"os";function Ae(e){if(e)return nt.join(e,"heartbeat");if(process.platform==="linux")try{return ee.mkdirSync("/run/aicommander-agent",{recursive:!0}),"/run/aicommander-agent/heartbeat"}catch{}return nt.join(qt.tmpdir(),"aicommander-agent-heartbeat")}function ot(e){try{let t=`${e}.tmp`;ee.writeFileSync(t,String(Date.now())),ee.renameSync(t,e)}catch{}}var j=null;function zt(e,t=5e3){st(),ot(e),j=setInterval(()=>ot(e),t),j.unref?.()}function st(){j&&(clearInterval(j),j=null)}function be(e){try{let t=ee.readFileSync(e,"utf8").trim(),r=Number(t);return Number.isFinite(r)?r:null}catch{return null}}async function sr(e={}){let t=e.heartbeatPath??Ae(e.configDir),r=e.spawnWorker??(d=>or(process.execPath,process.argv.slice(1),{env:d,stdio:"inherit"})),o=null,s=0,n=!1,i=0,c=[],u=null,a=null;await new Promise(d=>{let m=null,l=null,E=()=>{u&&(clearInterval(u),u=null),a&&(clearTimeout(a),a=null),m&&(process.removeListener("SIGINT",m),m=null),l&&(process.removeListener("SIGTERM",l),l=null)},S=()=>{E(),d()},b=()=>{s=Date.now(),i++;let f={...process.env,AIC_ROLE:"worker",AIC_HEARTBEAT:t};o=r(f),o.on("exit",(I,P)=>{if(o=null,n){S();return}if(e.maxSpawns!=null&&i>=e.maxSpawns){S();return}_(`worker exited (code=${I??"null"} signal=${P??"null"})`)}),o.on("error",()=>{})},_=f=>{let I=Date.now();c=c.filter(L=>I-L<3e5),c.push(I);let P=c.length>5?6e4:0;a&&clearTimeout(a),a=setTimeout(()=>{a=null,n||b()},P),a.unref?.()},g=()=>{if(o)try{o.kill("SIGKILL")}catch{}};u=setInterval(()=>{if(n||!o||Date.now()-s<3e4)return;let f=be(t);f!=null&&Date.now()-f>3e4&&g()},1e4),u.unref?.();let C=f=>{if(n=!0,u&&(clearInterval(u),u=null),a&&(clearTimeout(a),a=null),o)try{o.kill(f)}catch{}else S()};m=()=>C("SIGINT"),l=()=>C("SIGTERM"),process.on("SIGINT",m),process.on("SIGTERM",l),b()})}function ir(e,t){return se(e,t)}function ar(e,t){return Ce(e,t)}async function cr(e,t=1e4){let r=new AbortController,o=setTimeout(()=>r.abort(),t);try{let s=await fetch(`${e.replace(/\/+$/,"")}/dist/latest`,{signal:r.signal,headers:{Accept:"application/json"}});if(!s.ok)return null;let n=await s.json();return{version:typeof n.version=="string"?n.version:null,mac:typeof n.mac=="string"?n.mac:null,macPkg:typeof n.macPkg=="string"?n.macPkg:null,win:typeof n.win=="string"?n.win:null,agentLinuxX64:typeof n.agentLinuxX64=="string"?n.agentLinuxX64:null,agentLinuxArm64:typeof n.agentLinuxArm64=="string"?n.agentLinuxArm64:null}}catch{return null}finally{clearTimeout(o)}}export{U as AGENT_VERSION,_e as AgentController,fe as blockAdmin,Kt as canCaptureScreenshot,Jt as captureScreenshot,ir as compareVersions,Ae as defaultHeartbeatPath,pe as fetchAdmins,cr as fetchLatestDist,ar as isNewerVersion,Dt as orderAdmins,be as readHeartbeat,J as register,Ot as resolveAdminIdentifier,de as runConnectionLoop,sr as runSupervisor,zt as startHeartbeat,st as stopHeartbeat,he as unblockAdmin};
|
|
@@ -10,6 +10,13 @@ export interface LatestDist {
|
|
|
10
10
|
version: string | null;
|
|
11
11
|
/** Direct download paths (relative to the server origin), null if missing. */
|
|
12
12
|
mac: string | null;
|
|
13
|
+
/**
|
|
14
|
+
* The macOS .pkg installer (the primary mac download once published) —
|
|
15
|
+
* null until a pkg release exists, or when talking to an older server.
|
|
16
|
+
* Optional so existing consumers constructing LatestDist keep compiling;
|
|
17
|
+
* `fetchLatestDist` always fills it in (with an explicit null when absent).
|
|
18
|
+
*/
|
|
19
|
+
macPkg?: string | null;
|
|
13
20
|
win: string | null;
|
|
14
21
|
agentLinuxX64: string | null;
|
|
15
22
|
agentLinuxArm64: string | null;
|
package/dist/src/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const AGENT_VERSION = "1.0.
|
|
1
|
+
export declare const AGENT_VERSION = "1.0.27";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aicommander/agent",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.27",
|
|
4
4
|
"description": "Remote-machine agent for AI Commander — runs on your server/laptop and lets your AI agent execute shell commands on it via the @aicommander/mcp server. An SSH alternative with no exposed SSH, open ports, or VPN; drive it by AIC-… session code.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"remote",
|