agent-device-proxy 0.1.5 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/src/36.js CHANGED
@@ -1,18 +1,13 @@
1
- import t from"node:http";import{node_path as e,mkdirSync as r,promises as i,randomUUID as a,createHash as n,existsSync as o,createWriteStream as s}from"./402.js";import{spawnSync as u,spawn as d}from"./493.js";function c(t,e,r){let i=process.env[t];if(void 0===i||""===i)return e;let a=Number.parseInt(i,10);if(!Number.isInteger(a)||a<r.min||a>r.max)throw Error(`${t} must be an integer in range ${r.min}-${r.max}`);return a}class l extends Error{statusCode;code;details;constructor(t,e,r,i=null){super(r),this.statusCode=t,this.code=e,this.details=i}}async function f(t,e,r){let i=n("sha256"),a=s(e,{flags:"wx"}),o=0;try{for await(let e of t){let t=Buffer.isBuffer(e)?e:Buffer.from(e);if((o+=t.length)>r)throw new l(413,"invalid_request","request body too large");i.update(t),a.write(t)||await v(a)}await y(a)}catch(t){if(a.destroy(),await E(e),t instanceof l)throw t;throw new l(400,"invalid_request","invalid request body")}if(o<=0)throw await E(e),new l(400,"invalid_request","artifact body must not be empty");return{sizeBytes:o,sha256:i.digest("hex")}}async function m({archivePath:t,timeoutMs:r,runCommand:i,env:a}){let n=await i({command:"unzip",argv:["-Z1",t],cwd:e.dirname(t),env:a,timeoutMs:r,maxOutputBytes:2097152});if(0!==n.exitCode)throw new l(400,"invalid_request","invalid zip archive",{exit_code:n.exitCode,stdout:n.stdout,stderr:n.stderr});if(n.stdoutTruncated)throw new l(400,"invalid_request","zip entry listing is too large to validate safely");let o=n.stdout.split(/\r?\n/).map(t=>t.trim()).filter(Boolean);if(0===o.length)throw new l(400,"invalid_request","zip archive is empty");let s=!1;for(let t of o){let r=t.replaceAll("\\","/"),i=e.posix.normalize(r);if(r.startsWith("/")||/^[A-Za-z]:\//.test(r))throw new l(400,"invalid_request","zip archive contains absolute paths");if(".."===i||i.startsWith("../")||i.includes("/../")||i.includes("\0"))throw new l(400,"invalid_request","zip archive contains unsafe paths");i.split("/").some(t=>t.toLowerCase().endsWith(".app"))&&(s=!0)}if(!s)throw new l(400,"invalid_request","zip archive does not contain a .app bundle")}async function _({archivePath:t,timeoutMs:r,runCommand:i,env:a,maxOutputBytes:n}){let o=await i({command:"unzip",argv:["-Z","-t",t],cwd:e.dirname(t),env:a,timeoutMs:r,maxOutputBytes:n});if(0!==o.exitCode)throw new l(400,"invalid_request","invalid zip archive",{exit_code:o.exitCode,stdout:o.stdout,stderr:o.stderr});if(o.stdoutTruncated)throw new l(400,"invalid_request","zip archive summary is too large to validate safely");let s=o.stdout.match(/([\d,]+)\s+bytes\s+uncompressed/i);if(!s)throw new l(400,"invalid_request","zip archive summary is invalid");let u=Number.parseInt(s[1].replaceAll(",",""),10);if(!Number.isFinite(u)||u<0)throw new l(400,"invalid_request","zip archive summary is invalid");return u}async function p(t){let r=[t];for(;r.length>0;){let t=r.shift();if(!t)break;for(let a of(await i.readdir(t,{withFileTypes:!0}))){if(!a.isDirectory())continue;let i=e.join(t,a.name);if(a.name.endsWith(".app"))return i;r.push(i)}}return""}async function h(t){let r=[t],a=0;for(;r.length>0;){let t=r.shift();if(!t)break;let n=await i.lstat(t);if(n.isDirectory()){for(let a of(await i.readdir(t,{withFileTypes:!0})))r.push(e.join(t,a.name));continue}n.isFile()&&(a+=n.size)}return a}async function w(t){await i.mkdir(t.artifactsDir,{recursive:!0});let r=Date.now()-36e5*t.artifactTtlHours,a=await i.readdir(t.artifactsDir,{withFileTypes:!0}),n=[];for(let s of a){if(!s.isDirectory())continue;let a=e.join(t.artifactsDir,s.name),u=e.join(a,"metadata.json");if(!o(u)){await g(a);continue}let d=null;try{d=JSON.parse(await i.readFile(u,"utf8"))}catch{await g(a);continue}let c=Date.parse(String(d?.created_at||""));if(!Number.isFinite(c)||c<r){await g(a);continue}let l=Number(d?.size_bytes)||0;n.push({artifactDir:a,createdAtMs:c,sizeBytes:l})}n.sort((t,e)=>t.createdAtMs-e.createdAtMs);let s=n.reduce((t,e)=>t+e.sizeBytes,0);for(let e of n){if(s<=t.artifactMaxTotalBytes)break;await g(e.artifactDir),s-=e.sizeBytes}}async function v(t){await new Promise((e,r)=>{let i=()=>{n(),e()},a=t=>{n(),r(t)},n=()=>{t.off("drain",i),t.off("error",a)};t.on("drain",i),t.on("error",a)})}async function y(t){await new Promise((e,r)=>{let i=t=>{a(),r(t)},a=()=>{t.off("error",i)};t.end(t=>{if(t){a(),r(t);return}a(),e()}),t.on("error",i)})}async function E(t){try{await i.unlink(t)}catch{}}async function g(t){try{await i.rm(t,{recursive:!0,force:!0})}catch{}}async function b({command:t,argv:e,cwd:r,env:i,timeoutMs:a,maxOutputBytes:n}){return await new Promise(o=>{let s=d(t,e,{cwd:r,env:i,stdio:["ignore","pipe","pipe"]}),u="",c="",l=0,f=0,m=!1,_=!1,p=!1,h=!1,w=setTimeout(()=>{p=!0,s.kill("SIGKILL")},a);function v(t,e){let r=e.toString("utf8"),i=Buffer.byteLength(r);if("stdout"===t){if(l>=n){m=!0;return}let t=n-l,e=i>t?T(r,t):r;u+=e,l+=Buffer.byteLength(e),i>t&&(m=!0);return}if(f>=n){_=!0;return}let a=n-f,o=i>a?T(r,a):r;c+=o,f+=Buffer.byteLength(o),i>a&&(_=!0)}function y(t){h||(h=!0,clearTimeout(w),m&&(u=`${u}
2
- [output truncated]`.trim()),_&&(c=`${c}
3
- [output truncated]`.trim()),o(t))}s.stdout.on("data",t=>{v("stdout",t)}),s.stderr.on("data",t=>{v("stderr",t)}),s.on("close",(t,e)=>{y({exitCode:p?124:"number"==typeof t?t:1,stdout:u,stderr:p?`${c}
4
- proxy timeout after ${a}ms`.trim():c,signal:e??"",timedOut:p,stdoutTruncated:m,stderrTruncated:_})}),s.on("error",t=>{y({exitCode:1,stdout:u,stderr:`${c}
5
- ${t.message}`.trim(),signal:"",timedOut:p,stdoutTruncated:m,stderrTruncated:_})})})}function A({result:t,action:e,strictCommandStatus:r}){if(r&&0!==t.exitCode)throw new l(502,"command_failed",`${e} failed`,{exit_code:t.exitCode,timed_out:t.timedOut,signal:t.signal,stdout:t.stdout,stderr:t.stderr})}function T(t,e){return e<=0?"":Buffer.from(t,"utf8").subarray(0,e).toString("utf8")}let O=new Map([["/v1/agent-device/exec","/api/agent-device/exec"],["/v1/agent-device/install","/api/agent-device/install"],["/v1/artifacts/upload","/api/artifacts/upload"],["/v1/metro/resolve","/api/metro/resolve"],["/v1/metro/probe","/api/metro/probe"]]);function x(t,e,r,i,a){a?C(t,e,r,i):C(t,e,{ok:!0,data:r,request_id:i},i)}function R(t,e,r,i,a,n,o=null){n?C(t,e,{error:i},a):C(t,e,{ok:!1,error:{code:r,message:i,...o?{details:o}:{}},request_id:a},a)}function C(t,e,r,i){t.statusCode=e,t.setHeader("content-type","application/json; charset=utf-8"),t.setHeader("x-request-id",i),t.end(JSON.stringify(r))}async function D(t,e){let r=(await S(t,e)).toString("utf8");if(!r.trim())throw new l(400,"invalid_request","body must be valid JSON");try{let t=JSON.parse(r);if(!t||"object"!=typeof t||Array.isArray(t))throw new l(400,"invalid_request","body must be valid JSON object");return t}catch{throw new l(400,"invalid_request","body must be valid JSON")}}async function S(t,e){let r=[],i=0;try{for await(let a of t){let t=Buffer.isBuffer(a)?a:Buffer.from(a);if((i+=t.length)>e)throw new l(413,"invalid_request","request body too large");r.push(t)}}catch(t){if(t instanceof l)throw t;throw new l(400,"invalid_request","invalid request body")}return Buffer.concat(r)}function N(t){if(null==t||""===t)return null;if(!(t&&"object"==typeof t&&!Array.isArray(t)))throw new l(400,"invalid_request","ios_runtime must be an object");let e=$(t.launch_url,"ios_runtime.launch_url",null),r=$(t.metro_bundle_url,"ios_runtime.metro_bundle_url",new Set(["http:","https:"])),i=function(t,e){let r=P(t,e);if(!r)return null;if(r.includes(":"))throw new l(400,"invalid_request",`${e} must not include a port`);if(!/^[A-Za-z0-9._-]{1,253}$/.test(r))throw new l(400,"invalid_request",`${e} must contain only host-safe characters`);return r}(t.metro_host,"ios_runtime.metro_host"),a=function(t,e){if(null==t||""===t)return null;let r=Number.parseInt(String(t),10);if(!Number.isInteger(r)||r<1||r>65535)throw new l(400,"invalid_request",`${e} must be an integer in range 1..65535`);return r}(t.metro_port,"ios_runtime.metro_port");return e||r||i||a?{launch_url:e||"",metro_bundle_url:r||"",metro_host:i||"",metro_port:a||0}:null}function I(t){if(!t)return null;if(t.metro_bundle_url){let e=new URL(t.metro_bundle_url);return{bundle_url:e.toString(),host:e.hostname,port:function(t){if(t.port){let e=Number.parseInt(t.port,10);if(Number.isInteger(e)&&e>=1&&e<=65535)return e}return"https:"===t.protocol?443:80}(e),status_url:B(e)}}if(t.metro_host&&t.metro_port){let e=new URL(`http://${t.metro_host}:${t.metro_port}/index.bundle?platform=ios&dev=true&minify=false`);return{bundle_url:e.toString(),host:e.hostname,port:Number.parseInt(e.port,10),status_url:B(e)}}return null}async function q({statusUrl:t,timeoutMs:e}){let r=Date.now();try{let i=await fetch(t,{signal:AbortSignal.timeout(e)});return{reachable:i.ok,status_code:i.status,latency_ms:Date.now()-r,detail:i.ok?"ok":`HTTP ${i.status}`}}catch(t){return{reachable:!1,status_code:0,latency_ms:Date.now()-r,detail:t instanceof Error?t.message:String(t)}}}function $(t,e,r){let i,a=P(t,e);if(!a)return null;try{i=new URL(a)}catch{throw new l(400,"invalid_request",`${e} must be an absolute URL`)}if(r&&!r.has(i.protocol))throw new l(400,"invalid_request",`${e} must use ${Array.from(r).join(" or ")}`);return i.toString()}function P(t,e){if(null==t||""===t)return"";if("string"!=typeof t)throw new l(400,"invalid_request",`${e} must be a string`);return t.trim()}function B(t){let e=new URL(t.toString()),r=e.pathname.endsWith("/")&&e.pathname.length>1?e.pathname.slice(0,-1):e.pathname,i=r;if(r.endsWith("/index.bundle"))i=r.slice(0,-13);else if(r.endsWith(".bundle")){let t=r.lastIndexOf("/");i=t>=0?r.slice(0,t):""}return e.pathname=`${i||""}/status`.replace(/\/{2,}/g,"/"),e.search="",e.hash="",e.toString()}function M(t){return!!t&&"object"==typeof t&&!Array.isArray(t)}function z(t,e){let r=j(t,e);if(!r)throw new l(400,"invalid_request",`${e} is required`);return r}function L(t,e,r){let i=z(t,e).toLowerCase();if(!r.includes(i))throw new l(400,"invalid_request",`${e} must be one of: ${r.join(", ")}`);return i}function j(t,e){if(null==t||""===t)return"";if("string"!=typeof t)throw new l(400,"invalid_request",`${e} must be a string`);return t.trim()}function k(t,e,r){if(null==t||""===t)return r;if("boolean"!=typeof t)throw new l(400,"invalid_request",`${e} must be a boolean`);return t}function G(t,e){let r=t[e];return"string"==typeof r?r.trim():Array.isArray(r)&&r.length>0?String(r[0]).trim():""}function U(t={}){return Y(t).server}function X(t={}){let{server:e,config:r,artifactStore:i}=Y(t);return function(t){let e=["zip","unzip"],r=e.filter(e=>!J(e,t.baseCommandEnv.PATH));if(r.length>0)throw Error(`agent-device-proxy missing required commands in PATH: ${r.join(", ")}`);process.stdout.write(`agent-device-proxy dependency preflight ok: ${e.join(", ")}
6
- `)}(r),e.listen(r.port,r.host,()=>{process.stdout.write(`agent-device-proxy listening on http://${r.host}:${r.port}
1
+ import e from"node:http";import t from"node:os";import r from"node:path";import{randomUUID as n,timingSafeEqual as o}from"node:crypto";import i from"node:https";import{accessSync as a,constants as s,existsSync as u,readFileSync as l}from"node:fs";function d(e,t,r){let n=process.env[e];if(void 0===n||""===n)return t;let o=Number.parseInt(n,10);if(!Number.isInteger(o)||o<r.min||o>r.max)throw Error(`${e} must be an integer in range ${r.min}-${r.max}`);return o}function c(e,t){let r=process.env[e];if(void 0===r||""===r)return t;let n=r.trim().toLowerCase();if("1"===n||"true"===n||"yes"===n||"on"===n)return!0;if("0"===n||"false"===n||"no"===n||"off"===n)return!1;throw Error(`${e} must be a boolean-like value`)}class f extends Error{statusCode;code;details;constructor(e,t,r,n=null){super(r),this.statusCode=e,this.code=t,this.details=n}}function m(e,t,r,n){p(e,t,{ok:!0,data:r,request_id:n},n)}function h(e,t,r,n,o,i=null){p(e,t,{ok:!1,error:{code:r,message:n,...i?{details:i}:{}},request_id:o},o)}function p(e,t,r,n){e.statusCode=t,e.setHeader("content-type","application/json; charset=utf-8"),e.setHeader("x-request-id",n),e.end(JSON.stringify(r))}async function _(e,t){let r=(await g(e,t)).toString("utf8");if(!r.trim())throw new f(400,"invalid_request","body must be valid JSON");try{let e=JSON.parse(r);if(!e||"object"!=typeof e||Array.isArray(e))throw new f(400,"invalid_request","body must be valid JSON object");return e}catch(e){if(e instanceof f)throw e;throw new f(400,"invalid_request","body must be valid JSON")}}async function g(e,t){let r=[],n=0;try{for await(let o of e){let e=Buffer.isBuffer(o)?o:Buffer.from(o);if((n+=e.length)>t)throw new f(413,"invalid_request","request body too large");r.push(e)}}catch(e){if(e instanceof f)throw e;throw new f(400,"invalid_request","invalid request body")}return Buffer.concat(r)}async function y({statusUrl:e,timeoutMs:t}){let r=Date.now();try{let n=await fetch(e,{signal:AbortSignal.timeout(t)});return{reachable:n.ok,status_code:n.status,latency_ms:Date.now()-r,detail:n.ok?"ok":`HTTP ${n.status}`}}catch(e){return{reachable:!1,status_code:0,latency_ms:Date.now()-r,detail:e instanceof Error?e.message:String(e)}}}function w(e,t,r){let n,o=b(e,t);if(!o)return null;try{n=new URL(o)}catch{throw new f(400,"invalid_request",`${t} must be an absolute URL`)}if(r&&!r.has(n.protocol))throw new f(400,"invalid_request",`${t} must use ${Array.from(r).join(" or ")}`);return n.toString()}function b(e,t){if(null==e||""===e)return"";if("string"!=typeof e)throw new f(400,"invalid_request",`${t} must be a string`);return e.trim()}function E(e){let t=new URL(e.toString()),r=t.pathname.endsWith("/")&&t.pathname.length>1?t.pathname.slice(0,-1):t.pathname,n=r;if(r.endsWith("/index.bundle"))n=r.slice(0,-13);else if(r.endsWith(".bundle")){let e=r.lastIndexOf("/");n=e>=0?r.slice(0,e):""}return t.pathname=`${n||""}/status`.replace(/\/{2,}/g,"/"),t.search="",t.hash="",t.toString()}async function T(e,t){var r,n,o,i;let a=(r=e.host,n=e.port,`http://${r}:${n}`),s=new URL("/status",a).toString(),u=new URL("/index.bundle?platform=ios&dev=true&minify=false",a).toString(),l=await y({statusUrl:s,timeoutMs:Math.min(e.proxyTimeoutMs,15e3)});return{enabled:!0,base_url:a,status_url:s,bundle_url:u,ios_runtime:{metro_host:e.host,metro_port:e.port,metro_bundle_url:u},android_runtime:{metro_host:e.androidHost,metro_port:e.port,metro_bundle_url:(o=e.androidHost,i=e.port,`http://${o}:${i}/index.bundle?platform=android&dev=true&minify=false`)},upstream:t,probe:l}}let v=new Set(["connection","keep-alive","proxy-authenticate","proxy-authorization","te","trailer","transfer-encoding","upgrade","host","content-length"]);async function $(e,t,r,n){let o=new URL((e.url||"/").replace(/^\/+/,""),r.upstreamBaseUrl),i=new Headers;for(let[t,r]of Object.entries(e.headers))if(!(!r||v.has(t.toLowerCase())))if(Array.isArray(r))for(let e of r)i.append(t,e);else i.set(t,r);let a=e.method||"GET",s="GET"===a||"HEAD"===a?void 0:await g(e,n.maxBodyBytes),u=await fetch(o,{method:a,headers:i,...s?{body:s,duplex:"half"}:{},redirect:"manual",signal:AbortSignal.timeout(n.proxyTimeoutMs)});t.statusCode=u.status,u.headers.forEach((e,r)=>{v.has(r.toLowerCase())||t.setHeader(r,e)}),t.end(Buffer.from(await u.arrayBuffer()))}async function x(t,r,n,o,a){let s=new URL(S(o).upstreamBaseUrl),u=("https:"===s.protocol?i:e).request({protocol:s.protocol,hostname:s.hostname,port:s.port||void 0,method:t.method||"GET",path:t.url||"/",headers:function(e){let t={};for(let[r,n]of Object.entries(e.headers)){if(!n)continue;let e=r.toLowerCase();"host"!==e&&"content-length"!==e&&(t[r]=n)}return t}(t)});u.setTimeout(a.proxyTimeoutMs,()=>{u.destroy(new f(504,"metro_bridge_timeout","Metro bridge websocket upgrade timed out"))}),u.once("response",e=>{B(r,e),e.pipe(r)}),u.once("upgrade",(e,t,o)=>{B(r,e),o.length>0&&r.write(o),n.length>0&&t.write(n),t.pipe(r),r.pipe(t),t.on("error",()=>{r.destroy()}),r.on("error",()=>{t.destroy()})}),u.once("error",e=>{O(r,e)}),u.end()}function S(e){if(!e)throw new f(503,"metro_bridge_unconfigured","Metro bridge target is not configured");return e}function B(e,t){let r=t.statusCode||101,n=t.statusMessage||"Switching Protocols",o=[`HTTP/1.1 ${r} ${n}`];for(let[e,r]of Object.entries(t.headers))if(r)if(Array.isArray(r))for(let t of r)o.push(`${e}: ${t}`);else o.push(`${e}: ${r}`);o.push("",""),e.write(o.join("\r\n"))}function O(e,t){let r=t instanceof f?t.statusCode:502,n=t instanceof Error?t.message:"Metro bridge failed";e.destroyed||e.end(`HTTP/1.1 ${r} ${504===r?"Gateway Timeout":"Bad Gateway"}\r
2
+ content-type: text/plain; charset=utf-8\r
3
+ content-length: ${Buffer.byteLength(n,"utf8")}\r
4
+ connection: close\r
5
+ \r
6
+ `+n)}function R(e){return function(e){try{if(!u(e))return null;let t=JSON.parse(l(e,"utf8")),r=t.httpPort,n="number"==typeof r?r:"string"==typeof r?Number.parseInt(r,10):NaN,o="string"==typeof t.token?t.token.trim():"string"==typeof t.authToken?t.authToken.trim():"";if(!Number.isInteger(n)||n<=0||!o)return null;return{baseUrl:`http://127.0.0.1:${n}`,authToken:o}}catch{return null}}(e.daemonStateFilePath)}let I="/agent-device/",N=["content-type","x-artifact-type","x-artifact-filename"];async function A(e){var t;let r,{req:n,res:o,requestId:i,config:a}=e,s=R(a);if(!s)throw new f(503,"daemon_unavailable",`Upstream daemon is unavailable. Ensure ${a.daemonStateFilePath} points at a live HTTP daemon.`);let u=n.url||"/",l=new URL(u,"http://127.0.0.1").pathname,d=l.startsWith(I)?l.slice(I.length):"",c=new URL(d,`${s.baseUrl.replace(/\/+$/,"")}/`),m=u.includes("?")?u.slice(u.indexOf("?")):"";m&&(c.search=m);let h=n.method||"GET",p="GET"!==h&&"HEAD"!==h,_="rpc"===d&&"POST"===h,y=new Headers;for(let e of N){let t=n.headers[e];"string"==typeof t&&t.trim()&&y.set(e,t)}if(p&&!_){let e=n.headers["content-length"];"string"==typeof e&&e.trim()&&y.set("content-length",e)}let w=s.authToken||("string"==typeof(t=n.headers.authorization)&&t.startsWith("Bearer ")?t.slice(7):"")||("string"==typeof n.headers["x-agent-device-token"]?n.headers["x-agent-device-token"]:"");w&&(y.set("authorization",`Bearer ${w}`),y.set("x-agent-device-token",w)),p&&(r=_?function(e,t){if(!t)return e;try{let r=JSON.parse(e);if(!r||"object"!=typeof r)return e;return r.params={...r.params??{},token:t},JSON.stringify(r)}catch{return e}}((await g(n,a.maxJsonBodyBytes)).toString("utf8"),s.authToken):n);let b=await fetch(c,{method:h,headers:y,...r?{body:r,duplex:"half"}:{}});o.statusCode=b.status;let E=b.headers.get("content-type");E&&o.setHeader("content-type",E),o.setHeader("x-request-id",i);let T=Buffer.from(await b.arrayBuffer());b.status>=400&&process.stderr.write(`[agent-device-proxy] daemon ${h} ${c.pathname}${c.search} → ${b.status} (requestId=${i})
7
+ `),o.end(T)}async function D(e){let{req:t,res:r,requestId:n,config:i,routePath:a}=e;"GET"===t.method&&("/api/health"===a||"/healthz"===a)?function({res:e,requestId:t,routePath:r}){let n={status:"ok",service:"agent-device-proxy",checked_at:new Date().toISOString()};if("/healthz"===r){e.statusCode=200,e.setHeader("content-type","application/json; charset=utf-8"),e.setHeader("x-request-id",t),e.end(JSON.stringify(n));return}m(e,200,n,t)}(e):!function(e,t){if(!t)return!0;let r=e.headers.authorization||"",n=`Bearer ${t}`,i=Buffer.from(r),a=Buffer.from(n);return i.length===a.length&&o(i,a)}(t,i.bearerToken)?h(r,401,"unauthorized","unauthorized",n):"/agent-device"===a||a.startsWith(I)?await A(e):"/api/metro/resolve"===a&&"POST"===t.method?await P(e):"/api/metro/probe"===a&&"POST"===t.method?await C(e):"/api/metro/bridge"===a&&"POST"===t.method?await M(e):h(r,404,"not_found","not found",n)}async function P(e){let t=await k(e);m(e.res,200,t,e.requestId)}async function C(e){let t=await _(e.req,e.config.maxJsonBodyBytes),r=q(t),n=function(e){if(null==e||""===e)return 2500;let t=Number.parseInt(String(e),10);return!Number.isInteger(t)||t<100||t>6e4?2500:t}(t.timeout_ms),o=await y({statusUrl:r.status_url,timeoutMs:n});m(e.res,200,{...r,...o},e.requestId)}async function M(e){let t=await k(e),r=await e.metroBridge.configure(t);m(e.res,200,r,e.requestId)}async function k(e){return q(await _(e.req,e.config.maxJsonBodyBytes))}function q(e){let t=function(e){if(!e)return null;if(e.metro_bundle_url){let t=new URL(e.metro_bundle_url);return{bundle_url:t.toString(),host:t.hostname,port:function(e){if(e.port){let t=Number.parseInt(e.port,10);if(Number.isInteger(t)&&t>=1&&t<=65535)return t}return"https:"===e.protocol?443:80}(t),status_url:E(t)}}if(e.metro_host&&e.metro_port){let t=new URL(`http://${e.metro_host}:${e.metro_port}/index.bundle?platform=ios&dev=true&minify=false`);return{bundle_url:t.toString(),host:t.hostname,port:Number.parseInt(t.port,10),status_url:E(t)}}return null}(function(e){if(null==e||""===e)return null;if(!(e&&"object"==typeof e&&!Array.isArray(e)))throw new f(400,"invalid_request","ios_runtime must be an object");let t=w(e.launch_url,"ios_runtime.launch_url",null),r=w(e.metro_bundle_url,"ios_runtime.metro_bundle_url",new Set(["http:","https:"])),n=function(e,t){let r=b(e,t);if(!r)return null;if(r.includes(":"))throw new f(400,"invalid_request",`${t} must not include a port`);if(!/^[A-Za-z0-9._-]{1,253}$/.test(r))throw new f(400,"invalid_request",`${t} must contain only host-safe characters`);return r}(e.metro_host,"ios_runtime.metro_host"),o=function(e,t){if(null==e||""===e)return null;let r=Number.parseInt(String(e),10);if(!Number.isInteger(r)||r<1||r>65535)throw new f(400,"invalid_request",`${t} must be an integer in range 1..65535`);return r}(e.metro_port,"ios_runtime.metro_port");return t||r||n||o?{launch_url:t||"",metro_bundle_url:r||"",metro_host:n||"",metro_port:o||0}:null}(e.ios_runtime??e));if(!t)throw new f(400,"invalid_metro_runtime","ios_runtime metro hints are required (metro_bundle_url or metro_host+metro_port)");return t}function U(e={}){return L(e).server}function G(e={}){let{server:t,config:r}=L(e);return t.listen(r.port,r.host,()=>{process.stdout.write(`agent-device-proxy listening on http://${r.host}:${r.port}
8
+ `),process.stdout.write(`agent-device daemon bridge: http://${r.host}:${r.port}/agent-device/*
7
9
  `),process.stdout.write(`api health endpoint: http://${r.host}:${r.port}/api/health
8
- `),process.stdout.write(`api exec endpoint: http://${r.host}:${r.port}/api/agent-device/exec
9
- `),process.stdout.write(`api install endpoint: http://${r.host}:${r.port}/api/agent-device/install
10
- `),process.stdout.write(`api artifacts endpoint: http://${r.host}:${r.port}/api/artifacts/upload
11
- `),process.stdout.write(`api metro resolve endpoint: http://${r.host}:${r.port}/api/metro/resolve
12
- `),process.stdout.write(`api metro probe endpoint: http://${r.host}:${r.port}/api/metro/probe
13
- `),process.stdout.write(`agent-device command available: ${J(r.command,r.baseCommandEnv.PATH)?"yes":"no"} (${r.command})
14
- `),process.stdout.write(`root dir: ${r.rootDir}
15
- `),process.stdout.write(`artifacts dir: ${r.artifactsDir}
16
- `),r.workspaceRoot&&process.stdout.write(`workspace root constraint: ${r.workspaceRoot}
17
- `)}),i.maybeCleanupArtifacts().catch(t=>{process.stderr.write(`agent-device-proxy artifact cleanup warning: ${t instanceof Error?t.message:String(t)}
18
- `)}),e}function Y(n={}){let s,u,d,v,y,E,T,C,S,$,P,B,U,X,J,F,W,K=n.config||(s=process.env.AGENT_DEVICE_PROXY_HOST??"127.0.0.1",u=c("AGENT_DEVICE_PROXY_PORT",9123,{min:1,max:65535}),d=(process.env.AGENT_DEVICE_PROXY_DAEMON_BASE_URL??"").trim(),v=(process.env.AGENT_DEVICE_PROXY_DAEMON_AUTH_TOKEN??"").trim(),y=c("AGENT_DEVICE_PROXY_MAX_BODY_BYTES",1048576,{min:1024,max:0x1000000}),E=c("AGENT_DEVICE_PROXY_ARTIFACT_MAX_BYTES",0x20000000,{min:1048576,max:0xffffffff}),T=c("AGENT_DEVICE_PROXY_EXEC_TIMEOUT_MS",24e4,{min:1e3,max:36e5}),C=c("AGENT_DEVICE_PROXY_COMMAND_MAX_OUTPUT_BYTES",1048576,{min:4096,max:0x4000000}),S=c("AGENT_DEVICE_PROXY_ARTIFACT_UNZIP_TIMEOUT_MS",18e4,{min:1e3,max:18e5}),$=c("AGENT_DEVICE_PROXY_ARTIFACT_TTL_HOURS",24,{min:1,max:720}),P=c("AGENT_DEVICE_PROXY_ARTIFACT_MAX_TOTAL_BYTES",0x140000000,{min:1048576,max:0x1900000000}),B=function(t,e){let r=process.env[t];if(void 0===r||""===r)return e;let i=r.trim().toLowerCase();if("1"===i||"true"===i||"yes"===i||"on"===i)return!0;if("0"===i||"false"===i||"no"===i||"off"===i)return!1;throw Error(`${t} must be a boolean-like value`)}("AGENT_DEVICE_PROXY_STRICT_COMMAND_STATUS",!0),U=(process.env.AGENT_DEVICE_PROXY_BEARER_TOKEN??"").trim(),X=(process.env.AGENT_DEVICE_PROXY_COMMAND??"agent-device").trim()||"agent-device",J=e.resolve(process.env.AGENT_DEVICE_PROXY_ROOT_DIR??process.cwd()),F=function(t){let r=(t??"").trim();if(!r)return"";if(!e.isAbsolute(r))throw Error("AGENT_DEVICE_PROXY_WORKSPACE_ROOT must be absolute path");return e.resolve(r)}(process.env.AGENT_DEVICE_PROXY_WORKSPACE_ROOT??""),W=e.resolve(process.env.AGENT_DEVICE_PROXY_ARTIFACTS_DIR??e.join(J,".agent-device-proxy-artifacts")),{host:s,port:u,daemonBaseUrl:d,daemonAuthToken:v,maxJsonBodyBytes:y,maxArtifactBytes:E,commandTimeoutMs:T,commandMaxOutputBytes:C,unzipTimeoutMs:S,artifactTtlHours:$,artifactMaxTotalBytes:P,strictCommandStatus:B,bearerToken:U,command:X,rootDir:J,workspaceRoot:F,artifactsDir:W,baseCommandEnv:{PATH:function(t,r){let i=[e.dirname(process.execPath),e.join(r,"Library","Android","sdk","platform-tools"),e.join(r,"Library","Android","sdk","emulator"),"/opt/homebrew/bin","/usr/local/bin","/usr/bin","/bin"],a=new Set((t||"").split(e.delimiter).filter(Boolean));for(let t of i)o(t)&&a.add(t);return Array.from(a).join(e.delimiter)}(process.env.PATH||"",process.env.HOME||process.env.USERPROFILE||""),HOME:process.env.HOME||"",USER:process.env.USER||"",TMPDIR:process.env.TMPDIR||"/tmp",LANG:process.env.LANG||"en_US.UTF-8",LC_ALL:process.env.LC_ALL||"",LC_CTYPE:process.env.LC_CTYPE||"",SHELL:process.env.SHELL||"",TERM:process.env.TERM||"",ANDROID_HOME:process.env.ANDROID_HOME||"",ANDROID_SDK_ROOT:process.env.ANDROID_SDK_ROOT||"",JAVA_HOME:process.env.JAVA_HOME||"",XDG_RUNTIME_DIR:process.env.XDG_RUNTIME_DIR||""}});r(K.artifactsDir,{recursive:!0});let Z=function(t,{runCommand:r}){let n=null,s=0;async function u(){let e=Date.now();if(n)return await n;e-s<6e4||(n=w(t).finally(()=>{s=Date.now(),n=null}),await n)}return{storeArtifactFromRequest:async function({req:n,headers:o}){let s;await u();let d=a(),c=e.join(t.artifactsDir,d),w=e.join(c,o.filename);await i.mkdir(c,{recursive:!0});try{s=await f(n,w,t.maxArtifactBytes)}catch(t){throw await g(c),t}if(o.expected_sha256&&o.expected_sha256!==s.sha256)throw await g(c),new l(400,"invalid_request","artifact sha256 mismatch");let v=w,y=!1,E=s.sizeBytes;if("app"===o.artifact_type){let a=e.join(c,"unpacked");await i.mkdir(a,{recursive:!0}),await m({archivePath:w,timeoutMs:t.unzipTimeoutMs,runCommand:r,env:t.baseCommandEnv});let n=await _({archivePath:w,timeoutMs:t.unzipTimeoutMs,runCommand:r,env:t.baseCommandEnv,maxOutputBytes:t.commandMaxOutputBytes});if(n>t.maxArtifactBytes)throw await g(c),new l(413,"invalid_request","unzipped app archive too large",{uncompressed_bytes:n,max_bytes:t.maxArtifactBytes});let o=await r({command:"unzip",argv:["-q",w,"-d",a],cwd:c,env:t.baseCommandEnv,timeoutMs:t.unzipTimeoutMs,maxOutputBytes:t.commandMaxOutputBytes});if(0!==o.exitCode)throw await g(c),new l(400,"invalid_request","failed to unzip app archive",{exit_code:o.exitCode,stdout:o.stdout,stderr:o.stderr});let u=await p(a);if(!u)throw await g(c),new l(400,"invalid_request","zip archive does not contain a .app bundle");let d=await h(a);if(d>t.maxArtifactBytes)throw await g(c),new l(413,"invalid_request","unzipped app archive too large",{uncompressed_bytes:d,max_bytes:t.maxArtifactBytes});E=s.sizeBytes+d,v=u,y=!0}let b={artifact_id:d,artifact_type:o.artifact_type,archive:o.archive,filename:o.filename,size_bytes:E,sha256:s.sha256,created_at:new Date().toISOString(),raw_path:w,ready_path:v,extracted:y};return await i.writeFile(e.join(c,"metadata.json"),JSON.stringify(b,null,2),"utf8"),b},readArtifactMetadata:async function(r){let a,n=String(r||"").trim();if(!/^[a-z0-9-]{8,128}$/i.test(n))throw new l(400,"invalid_request","artifact_id format is invalid");let s=e.join(t.artifactsDir,n,"metadata.json");if(!o(s))throw new l(404,"not_found","artifact_id not found");try{a=JSON.parse(await i.readFile(s,"utf8"))}catch{throw new l(500,"internal_error","artifact metadata is unreadable")}if(!a||"object"!=typeof a||!a.ready_path||!a.artifact_type)throw new l(500,"internal_error","artifact metadata is invalid");if(!o(a.ready_path))throw new l(404,"not_found","artifact file is missing");return a},maybeCleanupArtifacts:u}}(K,{runCommand:b});return{server:t.createServer(async(t,r)=>{var i,n;let s,u,d,c="string"==typeof(s=t.headers["x-request-id"])&&s.trim()?s.trim().slice(0,128):a(),{path:f,legacy:m}=(d=(u=new URL(t.url||"/","http://127.0.0.1")).pathname.startsWith("/v1/"),{path:O.get(u.pathname)||u.pathname,legacy:d});try{if("GET"===t.method&&"/agent-device/health"===f)return void await H({req:t,res:r,requestId:c,config:K,endpoint:"health"});if("GET"===t.method&&("/api/health"===f||"/healthz"===f)){let t={status:"ok",service:"agent-device-proxy",checked_at:new Date().toISOString()};if("/healthz"===f){r.statusCode=200,r.setHeader("content-type","application/json; charset=utf-8"),r.setHeader("x-request-id",c),r.end(JSON.stringify(t));return}x(r,200,t,c,m);return}if("POST"!==t.method)return void R(r,404,"not_found","not found",c,m);if((i=K.bearerToken)&&(t.headers.authorization||"")!==`Bearer ${i}`)return void R(r,401,"unauthorized","unauthorized",c,m);if("/agent-device/rpc"===f)return void await H({req:t,res:r,requestId:c,config:K,endpoint:"rpc"});if("/api/artifacts/upload"===f){let e=function(t){let e=L(G(t,"x-artifact-type"),"x-artifact-type",["app","apk"]),r=L(G(t,"x-artifact-archive"),"x-artifact-archive",["zip","raw"]),i=(G(t,"x-artifact-filename")||`${e}-${Date.now()}${"zip"===r?".zip":".bin"}`).replace(/[^a-zA-Z0-9._-]/g,"_").slice(-160)||`artifact-${Date.now()}`,a=G(t,"x-artifact-sha256").toLowerCase();if(a&&!/^[a-f0-9]{64}$/.test(a))throw new l(400,"invalid_request","x-artifact-sha256 must be a 64-char lowercase hex sha256");if("app"===e&&"zip"!==r)throw new l(400,"invalid_request","x-artifact-type=app requires x-artifact-archive=zip");if("apk"===e&&"raw"!==r)throw new l(400,"invalid_request","x-artifact-type=apk requires x-artifact-archive=raw");return{artifact_type:e,archive:r,filename:i,expected_sha256:a}}(t.headers),i=await Z.storeArtifactFromRequest({req:t,headers:e});x(r,200,i,c,m);return}if("/api/metro/resolve"===f){let e=await D(t,K.maxJsonBodyBytes),i=N(e?.ios_runtime??e),a=I(i);if(!a)throw new l(400,"invalid_metro_runtime","ios_runtime metro hints are required (metro_bundle_url or metro_host+metro_port)");x(r,200,a,c,m);return}if("/api/metro/probe"===f){let e=await D(t,K.maxJsonBodyBytes),i=N(e?.ios_runtime??e),a=I(i);if(!a)throw new l(400,"invalid_metro_runtime","ios_runtime metro hints are required (metro_bundle_url or metro_host+metro_port)");let n=function(t){if(null==t||""===t)return 2500;let e=Number.parseInt(String(t),10);return!Number.isInteger(e)||e<100||e>6e4?2500:e}(e?.timeout_ms),o=await q({statusUrl:a.status_url,timeoutMs:n});x(r,200,{...a,...o},c,m);return}if("/api/agent-device/exec"===f){let i;V(K);let s=await D(t,K.maxJsonBodyBytes),u=function(t){if(!M(t))throw new l(400,"invalid_request","body must be valid JSON object");if(!Array.isArray(t.argv)||t.argv.some(t=>"string"!=typeof t))throw new l(400,"invalid_request","argv must be an array of strings");if(0===t.argv.length)throw new l(400,"invalid_request","argv must contain at least one argument");let e={argv:t.argv,cwd:j(t.cwd,"cwd"),run_id:j(t.run_id,"run_id"),ios_session_id:j(t.ios_session_id,"ios_session_id"),tenant_id:j(t.tenant_id,"tenant_id"),ios_runtime:function(t,e){if(null==t||""===t)return null;if(!t||"object"!=typeof t||Array.isArray(t))throw new l(400,"invalid_request",`${e} must be an object`);return t}(t.ios_runtime,"ios_runtime")};if(e.tenant_id&&!/^[a-z0-9][a-z0-9_-]{0,63}$/.test(e.tenant_id))throw new l(400,"invalid_request","tenant_id must match ^[a-z0-9][a-z0-9_-]{0,63}$");return e}(s),d={...u,ios_runtime:N(u.ios_runtime)},f=await b({command:K.command,argv:d.argv,cwd:function(t,r){if(!t)return r.rootDir;if(!e.isAbsolute(t))throw new l(400,"invalid_request","cwd must be absolute path");let i=e.resolve(t);if(r.workspaceRoot&&i!==r.workspaceRoot&&!i.startsWith(`${r.workspaceRoot}${e.sep}`))throw new l(400,"invalid_request","cwd is outside configured workspace root");if(!o(i))throw new l(400,"invalid_request","cwd does not exist");return i}(d.cwd,K),env:(n=K.baseCommandEnv,i={...n,AGENT_DEVICE_PROXY_REQUEST_ID:a()},d.tenant_id&&(i.AGENT_DEVICE_PROXY_TENANT_ID=d.tenant_id),d.run_id&&(i.AGENT_DEVICE_PROXY_RUN_ID=d.run_id),d.ios_session_id&&(i.AGENT_DEVICE_PROXY_IOS_SESSION_ID=d.ios_session_id),d.ios_runtime&&(d.ios_runtime.launch_url&&(i.AGENT_DEVICE_PROXY_IOS_LAUNCH_URL=d.ios_runtime.launch_url),d.ios_runtime.metro_bundle_url&&(i.AGENT_DEVICE_PROXY_METRO_BUNDLE_URL=d.ios_runtime.metro_bundle_url),d.ios_runtime.metro_host&&(i.AGENT_DEVICE_PROXY_METRO_HOST=d.ios_runtime.metro_host),d.ios_runtime.metro_port&&(i.AGENT_DEVICE_PROXY_METRO_PORT=String(d.ios_runtime.metro_port))),i),timeoutMs:K.commandTimeoutMs,maxOutputBytes:K.commandMaxOutputBytes});A({result:f,action:`agent-device exec (${d.argv[0]||"command"})`,strictCommandStatus:K.strictCommandStatus}),x(r,200,{exit_code:f.exitCode,stdout:f.stdout,stderr:f.stderr,timed_out:f.timedOut},c,m);return}if("/api/agent-device/install"===f){V(K);let e=await D(t,K.maxJsonBodyBytes),i=function(t){if(!M(t))throw new l(400,"invalid_request","body must be valid JSON object");let e=z(t.app,"app"),r=z(t.artifact_id,"artifact_id"),i=L(t.platform,"platform",["ios","android","apple"]),a=j(t.device,"device"),n=j(t.session,"session"),o=j(t.udid,"udid"),s=j(t.serial,"serial"),u=k(t.reinstall,"reinstall",!1);return{app:e,artifact_id:r,platform:i,device:a,session:n,udid:o,serial:s,reinstall:u,json:k(t.json,"json",!0)}}(e),n=await Z.readArtifactMetadata(i.artifact_id),o=function(t,e){if("android"===t.platform&&"apk"!==e.artifact_type)throw new l(400,"invalid_request","android install requires artifact_type=apk");if(("ios"===t.platform||"apple"===t.platform)&&"app"!==e.artifact_type)throw new l(400,"invalid_request","ios install requires artifact_type=app");let r=[t.reinstall?"reinstall":"install",t.app,e.ready_path,"--platform",t.platform];return t.device&&r.push("--device",t.device),t.session&&r.push("--session",t.session),t.udid&&r.push("--udid",t.udid),t.serial&&r.push("--serial",t.serial),t.json&&r.push("--json"),r}(i,n),s=await b({command:K.command,argv:o,cwd:K.rootDir,env:{...K.baseCommandEnv,AGENT_DEVICE_PROXY_REQUEST_ID:a(),AGENT_DEVICE_PROXY_ARTIFACT_ID:i.artifact_id},timeoutMs:K.commandTimeoutMs,maxOutputBytes:K.commandMaxOutputBytes});A({result:s,action:"agent-device install",strictCommandStatus:K.strictCommandStatus}),x(r,200,{artifact_id:i.artifact_id,artifact_type:n.artifact_type,artifact_path:n.ready_path,exit_code:s.exitCode,stdout:s.stdout,stderr:s.stderr,output:function(t){try{return JSON.parse(t)}catch{return null}}(s.stdout)},c,m);return}R(r,404,"not_found","not found",c,m)}catch(e){let t;R(r,(t=function(t,e="internal error"){return t instanceof l?t:new l(500,"internal_error",t instanceof Error?t.message:e)}(e)).statusCode,t.code,t.message,c,m,t.details)}}),config:K,artifactStore:Z}}async function H(t){var e,r;let{req:i,res:a,requestId:n,config:o,endpoint:s}=t;if(!o.daemonBaseUrl)throw new l(503,"daemon_unavailable","AGENT_DEVICE_PROXY_DAEMON_BASE_URL is required for daemon forwarding");let u=new URL(s,`${o.daemonBaseUrl.replace(/\/+$/,"")}/`),d=new Headers,c=i.headers["content-type"];"string"==typeof c&&c.trim()&&d.set("content-type",c);let f=o.daemonAuthToken||("string"!=typeof(e=i.headers.authorization)?"":e.startsWith("Bearer ")?e.slice(7):"")||("string"==typeof(r=i.headers["x-agent-device-token"])?r:"");f&&(d.set("authorization",`Bearer ${f}`),d.set("x-agent-device-token",f));let m="rpc"===s?function(t){let{body:e,daemonAuthToken:r}=t;if(!r)return e;try{let t=JSON.parse(e);if(!t||"object"!=typeof t)return e;return t.params={...t.params??{},token:r},JSON.stringify(t)}catch{return e}}({body:Buffer.from(await S(i,o.maxJsonBodyBytes)).toString("utf8"),daemonAuthToken:o.daemonAuthToken}):void 0,_=await fetch(u,{method:i.method||("health"===s?"GET":"POST"),headers:d,...m?{body:m}:{}}),p=await _.text(),h=_.headers.get("content-type");a.statusCode=_.status,h?a.setHeader("content-type",h):a.setHeader("content-type","application/json; charset=utf-8"),a.setHeader("x-request-id",n),a.end(p)}function V(t){if(!J(t.command,t.baseCommandEnv.PATH))throw new l(503,"command_unavailable",`local command unavailable for this endpoint: ${t.command}`,{command:t.command})}function J(t,e){var r;return"string"==typeof t&&!!t.trim()&&0===u("sh",["-lc",`command -v ${(r=t,`'${r.replaceAll("'","'\\''")}'`)} >/dev/null 2>&1`],{stdio:"ignore",env:{...process.env,...e?{PATH:e}:{}}}).status}export{U as createagentDeviceProxyServer,X as startAgentDeviceProxyServer};
10
+ `),process.stdout.write(`api metro endpoints: http://${r.host}:${r.port}/api/metro/{resolve,probe,bridge}
11
+ `),process.stdout.write(`metro bridge target (lazy): http://${r.metroBridgeHost}:${r.metroBridgePort}
12
+ `),H(r)}),t}async function H(e){if(!e.validateDaemonStartup)return;let t=R(e);if(t)try{let r=new Headers,n=t.authToken||e.bearerToken;n&&(r.set("authorization",`Bearer ${n}`),r.set("x-agent-device-token",n));let o=await fetch(new URL("health",`${t.baseUrl.replace(/\/+$/,"")}/`),{method:"GET",headers:r,signal:AbortSignal.timeout(e.daemonProbeTimeoutMs)});if(!o.ok){let e=await o.text();throw Error(`daemon health probe failed (${o.status}): ${e||"<empty>"}`)}}catch(e){process.stderr.write(["agent-device-proxy startup failed: upstream daemon is not reachable in HTTP mode.",`resolvedDaemonBaseUrl=${t.baseUrl}`,e instanceof Error?e.message:String(e),"If the daemon restarted in socket mode, restart agent-device with AGENT_DEVICE_DAEMON_SERVER_MODE=http and then restart agent-device-proxy."].join("\n")+"\n"),process.exitCode=1,setImmediate(()=>process.exit(1))}}function L(o={}){let i,a,s,u,l,m,p,_,g,y,w,b=o.config||(i=process.env.AGENT_DEVICE_PROXY_HOST??"127.0.0.1",a=d("AGENT_DEVICE_PROXY_PORT",9124,{min:1,max:65535}),s=c("AGENT_DEVICE_PROXY_METRO_BRIDGE_ENABLED",!0),u=(process.env.AGENT_DEVICE_PROXY_METRO_BRIDGE_HOST??"").trim()||"127.0.0.1",l=d("AGENT_DEVICE_PROXY_METRO_BRIDGE_PORT",8081,{min:1,max:65535}),m=(process.env.AGENT_DEVICE_PROXY_METRO_BRIDGE_ANDROID_HOST??"").trim()||"10.0.2.2",p=d("AGENT_DEVICE_PROXY_METRO_BRIDGE_TIMEOUT_MS",12e4,{min:1e3,max:36e5}),_=(process.env.AGENT_DEVICE_STATE_DIR??"").trim()||r.join(t.homedir(),".agent-device"),g=r.join(_,"daemon.json"),y=c("AGENT_DEVICE_PROXY_VALIDATE_DAEMON_STARTUP",!0),w=d("AGENT_DEVICE_PROXY_DAEMON_PROBE_TIMEOUT_MS",3e3,{min:100,max:6e4}),{host:i,port:a,metroBridgeEnabled:s,metroBridgeHost:u,metroBridgePort:l,metroBridgeAndroidHost:m,metroBridgeProxyTimeoutMs:p,daemonStateFilePath:g,validateDaemonStartup:y,daemonProbeTimeoutMs:w,maxJsonBodyBytes:d("AGENT_DEVICE_PROXY_MAX_BODY_BYTES",1048576,{min:1024,max:0x1000000}),bearerToken:(process.env.AGENT_DEVICE_PROXY_BEARER_TOKEN??"").trim()}),E=function(t){let r=null,n=null,o=null;async function i(){if(!t.enabled)throw new f(503,"metro_bridge_disabled","Metro bridge is disabled");if(n?.listening)return;if(o)return void await o;let i=new Promise((o,i)=>{let a=function({config:t,getTarget:r}){let n=e.createServer(async(e,n)=>{try{let o=S(r());await $(e,n,o,t)}catch(r){let e=r instanceof f?r.statusCode:502,t=r instanceof Error?r.message:"Metro bridge failed";n.statusCode=e,n.setHeader("content-type","text/plain; charset=utf-8"),n.end(t)}});return n.on("upgrade",(e,n,o)=>{x(e,n,o,r(),t).catch(e=>{O(n,e)})}),n}({config:t,getTarget:()=>r});a.once("error",e=>{i(new f(502,"metro_bridge_start_failed",`Failed to start Metro bridge on http://${t.host}:${t.port}`,{error:e instanceof Error?e.message:String(e)}))}),a.listen(t.port,t.host,()=>{n=a,o()})}).finally(()=>{o===i&&(o=null)});o=i,await o}return{configure:async e=>(await i(),r={upstreamBaseUrl:function(e){let t=new URL(e),r=t.pathname,n=r;if(r.endsWith("/index.bundle"))n=r.slice(0,-13);else if(r.endsWith(".bundle")){let e=r.lastIndexOf("/");n=e>=0?r.slice(0,e+1):"/"}return new URL((n.endsWith("/")?n:`${n}/`).replace(/^\/+/,""),`${t.origin}/`).toString()}(e.bundle_url)},await T(t,e))}}({enabled:b.metroBridgeEnabled,host:b.metroBridgeHost,port:b.metroBridgePort,androidHost:b.metroBridgeAndroidHost,proxyTimeoutMs:b.metroBridgeProxyTimeoutMs,maxBodyBytes:b.maxJsonBodyBytes}),v=e.createServer(async(e,t)=>{let r,o="string"==typeof(r=e.headers["x-request-id"])&&r.trim()?r.trim().slice(0,128):n(),i=new URL(e.url||"/","http://127.0.0.1").pathname;try{await D({req:e,res:t,requestId:o,config:b,routePath:i,metroBridge:E})}catch(r){let e;h(t,(e=function(e,t="internal error"){return e instanceof f?e:new f(500,"internal_error",e instanceof Error?e.message:t)}(r)).statusCode,e.code,e.message,o,e.details)}});return v.on("connection",e=>{e.on("error",t=>{j(t,e)})}),v.on("clientError",(e,t)=>{j(e,t)}),{server:v,config:b}}function j(e,t){"ECONNRESET"!==e.code&&process.stderr.write(`[agent-device-proxy] client socket error: ${e.message}
13
+ `),t.destroyed||t.destroy()}export{a as accessSync,s as constants,U as createAgentDeviceProxyServer,u as existsSync,t as node_os,r as node_path,l as readFileSync,G as startAgentDeviceProxyServer};
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/src/bin.js CHANGED
@@ -1,3 +1,15 @@
1
1
  #!/usr/bin/env node
2
- import{fileURLToPath as e}from"node:url";import{node_path as r,existsSync as o,readFileSync as i}from"./402.js";import{startAgentDeviceProxyServer as t}from"./36.js";let n=process.argv.slice(2);(n.includes("-V")||n.includes("--version"))&&(process.stdout.write(`${function(){let t=r.dirname(e(import.meta.url));for(let e of[r.resolve(t,"..","package.json"),r.resolve(t,"..","..","package.json")]){if(!o(e))continue;let r=JSON.parse(i(e,"utf8"));if("string"==typeof r.version&&r.version.trim())return r.version}throw Error("Unable to determine agent-device-proxy version")}()}
3
- `),process.exit(0)),t();
2
+ import{fileURLToPath as e}from"node:url";import{execFileSync as r,spawnSync as t}from"node:child_process";import{node_os as o,node_path as n,readFileSync as s,constants as i,accessSync as c,existsSync as a,startAgentDeviceProxyServer as E}from"./36.js";function d(e,t,o={}){try{return r(e,t,{encoding:o.encoding??"utf8",stdio:o.stdio??["ignore","pipe","ignore"]}).trim()}catch(r){if(r&&"object"==typeof r&&"code"in r&&"ENOENT"===r.code)throw Error(`Required host utility '${e}' was not found in PATH. This command currently supports macOS hosts with standard system tools installed.`);throw r}}function p(e,r){process.env[e]||(process.env[e]=r)}function u(e,r){if(!a(e))return"";try{let t=JSON.parse(s(e,"utf8"))[r];return null==t?"":String(t)}catch{return""}}function l(e){try{return c(e,i.X_OK),!0}catch{return!1}}function _(e){try{return d("ps",["-p",e,"-o","command="])}catch{return""}}function f(e){return _(e).includes("agent-device")}function v(e){let r=_(e);return r.includes("agent-device-proxy")||r.includes("agent-proxy-server.ts")||r.includes("/dist/src/bin.js")}function T(e,r,t,o){if(!e)return;try{process.kill(Number(e),0)}catch{return}if(!o(e))return;process.stdout.write(`Stopping stale ${t} (${r}, pid ${e})
3
+ `);try{process.kill(Number(e))}catch{}let n=Date.now()+5e3;for(;Date.now()<n;){try{process.kill(Number(e),0)}catch{return}Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,250)}process.stdout.write(`Force killing stale ${t} (${r}, pid ${e})
4
+ `);try{process.kill(Number(e),"SIGKILL")}catch{}}function m(e){if(!e)return[];try{return d("lsof",[`-tiTCP:${e}`,"-sTCP:LISTEN"]).split("\n").map(e=>e.trim()).filter(Boolean)}catch{return[]}}let h=process.argv.slice(2);function N(){let r=n.dirname(e(import.meta.url));for(let e of[n.resolve(r,"..","package.json"),n.resolve(r,"..","..","package.json")]){if(!a(e))continue;let r=JSON.parse(s(e,"utf8"));if("string"==typeof r.version&&r.version.trim())return r.version}throw Error("Unable to determine agent-device-proxy version")}function g(){process.stdout.write("Usage: agent-device-proxy <command>\n\nCommands:\n serve Start the proxy server using current environment variables\n dev Prepare local agent-device HTTP daemon defaults, then start the proxy server\n version Print the package version\n help Show this help\n\nExamples:\n agent-device-proxy serve\n agent-device-proxy dev\n")}function w(e){let r=!1,t=t=>{r||(r=!0,process.stdout.write(`Received ${t}, shutting down agent-device-proxy
5
+ `),e.close(e=>{e&&(process.stderr.write(`Failed to shut down agent-device-proxy cleanly: ${e.message}
6
+ `),process.exitCode=1),process.exit()}),setTimeout(()=>{process.stderr.write("Timed out waiting for agent-device-proxy to shut down cleanly\n"),process.exit(1)},5e3).unref())};process.on("SIGINT",()=>t("SIGINT")),process.on("SIGTERM",()=>t("SIGTERM"))}function I(e){if(!(e instanceof Error))return!1;let r="code"in e?String(e.code):"",t="syscall"in e?String(e.syscall):"";return("ECONNRESET"===r||"EPIPE"===r)&&(""===t||"read"===t||"write"===t)}!async function(){(h.includes("-V")||h.includes("--version"))&&(process.stdout.write(`${N()}
7
+ `),process.exit(0)),(h.includes("-h")||h.includes("--help"))&&(g(),process.exit(0));let e=h[0]??"serve";switch(process.on("uncaughtException",e=>{if(!I(e))throw e}),process.on("unhandledRejection",e=>{if(!I(e))throw e}),e){case"serve":w(E());return;case"dev":!function(){p("AGENT_DEVICE_DAEMON_SERVER_MODE","http"),p("AGENT_DEVICE_DAEMON_TRANSPORT","http"),p("AGENT_DEVICE_STATE_DIR",n.join(o.homedir(),".agent-device")),p("AGENT_DEVICE_PROXY_RESET_DAEMON","1");let e=n.join(process.env.AGENT_DEVICE_STATE_DIR,"daemon.json"),r=function(){let e,r,o=process.env.AGENT_DEVICE_BIN?.trim();if(o){if(!l(o))throw Error(`AGENT_DEVICE_BIN is not executable: ${o}`);if(0===t(o,["--version"],{stdio:"ignore"}).status)return o;throw Error(`AGENT_DEVICE_BIN is not a working agent-device binary: ${o}`)}for(let o of(e=process.env.PATH??"",r="win32"===process.platform?["",".cmd",".exe",".bat"]:[""],e.split(n.delimiter).filter(Boolean).flatMap(e=>r.map(r=>n.join(e,`agent-device${r}`)))))if(l(o)&&0===t(o,["--version"],{stdio:"ignore"}).status)return o;throw Error("Unable to resolve a working agent-device binary. Set AGENT_DEVICE_BIN explicitly.")}();!function(e){if("1"!==process.env.AGENT_DEVICE_PROXY_RESET_DAEMON)return;let r=u(e,"pid"),t=u(e,"httpPort");T(r,`recorded in ${e}`,"agent-device daemon",f);for(let e of m(t))T(e,`listener on port ${t}`,"agent-device daemon",f)}(e);if(0!==t(r,["session","list","--json","--daemon-transport","http"],{stdio:"ignore",env:process.env}).status)throw Error("Failed to start agent-device HTTP daemon");if(!a(e))throw Error(`agent-device daemon.json not found at ${e}`);let s=u(e,"httpPort");if(!s)throw Error(`Failed to resolve daemon httpPort from ${e}`);if(!process.env.AGENT_DEVICE_PROXY_BEARER_TOKEN?.trim())throw Error("AGENT_DEVICE_PROXY_BEARER_TOKEN must be set before starting agent-device-proxy");p("AGENT_DEVICE_PROXY_HOST","127.0.0.1"),p("AGENT_DEVICE_PROXY_PORT","9124");var i=process.env.AGENT_DEVICE_PROXY_PORT;for(let e of m(i))T(e,`listener on port ${i}`,"agent-device-proxy",v);process.stdout.write("Starting agent-device-proxy with:\n"),process.stdout.write(` AGENT_DEVICE_BIN=${r}
8
+ `),process.stdout.write(` upstream daemon: auto (${e}, current port ${s})
9
+ `),process.stdout.write(` AGENT_DEVICE_PROXY_RESET_DAEMON=${process.env.AGENT_DEVICE_PROXY_RESET_DAEMON}
10
+ `),process.stdout.write(` AGENT_DEVICE_PROXY_HOST=${process.env.AGENT_DEVICE_PROXY_HOST}
11
+ `),process.stdout.write(` AGENT_DEVICE_PROXY_PORT=${process.env.AGENT_DEVICE_PROXY_PORT}
12
+ `)}(),w(E());return;case"version":process.stdout.write(`${N()}
13
+ `);return;case"help":g();return;default:process.stderr.write(`Unknown command: ${e}
14
+
15
+ `),g(),process.exit(64)}}();
@@ -0,0 +1,2 @@
1
+ import type { AgentDeviceProxyClient, AgentDeviceProxyClientConfig } from "./types";
2
+ export declare function createAgentDeviceProxyClient(config: AgentDeviceProxyClientConfig): AgentDeviceProxyClient;
@@ -0,0 +1 @@
1
+ export declare function prepareDevEnvironment(): void;
@@ -0,0 +1,3 @@
1
+ export { createAgentDeviceProxyClient } from "./client";
2
+ export { createAgentDeviceProxyServer, startAgentDeviceProxyServer, } from "./server";
3
+ export type { AgentDeviceProxyClient, AgentDeviceProxyClientConfig, agentDeviceProxyClient, agentDeviceProxyClientConfig, HealthResponse, MetroBridgeResponse, MetroProbeResponse, MetroRequestPayload, MetroResolveResponse, MetroRuntimeHints, } from "./types";
package/dist/src/index.js CHANGED
@@ -1 +1 @@
1
- export{createAgentDeviceProxyClient}from"./224.js";export{createagentDeviceProxyServer,startAgentDeviceProxyServer}from"./36.js";export{ensureMetroRuntime}from"./403.js";
1
+ export{createAgentDeviceProxyClient}from"./224.js";export{createAgentDeviceProxyServer,startAgentDeviceProxyServer}from"./36.js";
@@ -0,0 +1,15 @@
1
+ export interface ServerConfig {
2
+ host: string;
3
+ port: number;
4
+ metroBridgeEnabled: boolean;
5
+ metroBridgeHost: string;
6
+ metroBridgePort: number;
7
+ metroBridgeAndroidHost: string;
8
+ metroBridgeProxyTimeoutMs: number;
9
+ daemonStateFilePath: string;
10
+ validateDaemonStartup: boolean;
11
+ daemonProbeTimeoutMs: number;
12
+ maxJsonBodyBytes: number;
13
+ bearerToken: string;
14
+ }
15
+ export declare function createServerConfig(): ServerConfig;
@@ -0,0 +1,12 @@
1
+ import type { IncomingMessage, ServerResponse } from "node:http";
2
+ import type { ServerConfig } from "./config";
3
+ export declare const DAEMON_PREFIX = "/agent-device/";
4
+ interface DaemonForwardContext {
5
+ req: IncomingMessage;
6
+ res: ServerResponse;
7
+ requestId: string;
8
+ config: ServerConfig;
9
+ }
10
+ export declare function isDaemonRoute(routePath: string): boolean;
11
+ export declare function handleDaemonForward(ctx: DaemonForwardContext): Promise<void>;
12
+ export {};
@@ -0,0 +1,6 @@
1
+ import type { ServerConfig } from "./config";
2
+ export interface ResolvedDaemonConnection {
3
+ baseUrl: string;
4
+ authToken: string;
5
+ }
6
+ export declare function resolveDaemonConnection(config: Pick<ServerConfig, "daemonStateFilePath">): ResolvedDaemonConnection | null;
@@ -0,0 +1,9 @@
1
+ export declare class AgentDeviceProxyError extends Error {
2
+ statusCode: number;
3
+ code: string;
4
+ details: unknown;
5
+ constructor(statusCode: number, code: string, message: string, details?: unknown);
6
+ }
7
+ export declare function asAgentDeviceProxyError(error: unknown, fallbackMessage?: string): AgentDeviceProxyError;
8
+ export declare const agentDeviceProxyError: typeof AgentDeviceProxyError;
9
+ export declare const asagentDeviceProxyError: typeof asAgentDeviceProxyError;
@@ -0,0 +1,9 @@
1
+ import type { IncomingMessage, ServerResponse } from "node:http";
2
+ export declare function normalizeRoute(requestUrl: string): string;
3
+ export declare function resolveRequestId(req: IncomingMessage): string;
4
+ export declare function authorizeBearer(req: IncomingMessage, expectedBearerToken: string): boolean;
5
+ export declare function sendSuccess(res: ServerResponse, statusCode: number, data: unknown, requestId: string): void;
6
+ export declare function sendRouteError(res: ServerResponse, error: unknown, requestId: string): void;
7
+ export declare function sendError(res: ServerResponse, statusCode: number, code: string, message: string, requestId: string, details?: unknown): void;
8
+ export declare function readJsonBody(req: IncomingMessage, maxBodyBytes: number): Promise<Record<string, unknown>>;
9
+ export declare function readBodyBuffer(req: IncomingMessage, maxBodyBytes: number): Promise<Buffer>;
@@ -0,0 +1,4 @@
1
+ import { type ResolvedMetroRuntime } from "./metro";
2
+ import type { MetroBridgeConfig, MetroBridgeDescriptor, MetroBridgeTarget } from "./metro-bridge-types";
3
+ export declare function createMetroBridgeTarget(resolved: ResolvedMetroRuntime): MetroBridgeTarget;
4
+ export declare function createMetroBridgeDescriptor(config: MetroBridgeConfig, resolved: ResolvedMetroRuntime): Promise<MetroBridgeDescriptor>;
@@ -0,0 +1,8 @@
1
+ import http from "node:http";
2
+ import type { MetroBridgeConfig, MetroBridgeTarget } from "./metro-bridge-types";
3
+ interface CreateMetroBridgeProxyServerOptions {
4
+ config: MetroBridgeConfig;
5
+ getTarget: () => MetroBridgeTarget | null;
6
+ }
7
+ export declare function createMetroBridgeProxyServer({ config, getTarget, }: CreateMetroBridgeProxyServerOptions): http.Server;
8
+ export {};
@@ -0,0 +1,33 @@
1
+ import type { MetroProbeResult, ResolvedMetroRuntime } from "./metro";
2
+ export interface MetroBridgeTarget {
3
+ upstreamBaseUrl: string;
4
+ }
5
+ export interface MetroBridgeConfig {
6
+ enabled: boolean;
7
+ host: string;
8
+ port: number;
9
+ androidHost: string;
10
+ proxyTimeoutMs: number;
11
+ maxBodyBytes: number;
12
+ }
13
+ export interface MetroBridgeDescriptor {
14
+ enabled: boolean;
15
+ base_url: string;
16
+ status_url: string;
17
+ bundle_url: string;
18
+ ios_runtime: {
19
+ metro_host: string;
20
+ metro_port: number;
21
+ metro_bundle_url: string;
22
+ };
23
+ android_runtime: {
24
+ metro_host: string;
25
+ metro_port: number;
26
+ metro_bundle_url: string;
27
+ };
28
+ upstream: ResolvedMetroRuntime;
29
+ probe: MetroProbeResult;
30
+ }
31
+ export interface MetroBridgeRuntime {
32
+ configure: (resolved: ResolvedMetroRuntime) => Promise<MetroBridgeDescriptor>;
33
+ }
@@ -0,0 +1,3 @@
1
+ import type { MetroBridgeConfig, MetroBridgeRuntime } from "./metro-bridge-types";
2
+ export type { MetroBridgeRuntime } from "./metro-bridge-types";
3
+ export declare function createMetroBridgeRuntime(config: MetroBridgeConfig): MetroBridgeRuntime;
@@ -0,0 +1,25 @@
1
+ export interface ParsedIosRuntime {
2
+ launch_url: string;
3
+ metro_bundle_url: string;
4
+ metro_host: string;
5
+ metro_port: number;
6
+ }
7
+ export interface ResolvedMetroRuntime {
8
+ bundle_url: string;
9
+ host: string;
10
+ port: number;
11
+ status_url: string;
12
+ }
13
+ export interface MetroProbeResult {
14
+ reachable: boolean;
15
+ status_code: number;
16
+ latency_ms: number;
17
+ detail: string;
18
+ }
19
+ export declare function parseIosRuntime(raw: unknown): ParsedIosRuntime | null;
20
+ export declare function resolveMetroRuntime(iosRuntime: ParsedIosRuntime | null): ResolvedMetroRuntime | null;
21
+ export declare function probeMetro({ statusUrl, timeoutMs, }: {
22
+ statusUrl: string;
23
+ timeoutMs: number;
24
+ }): Promise<MetroProbeResult>;
25
+ export declare function parseProbeTimeoutMs(rawValue: unknown): number;
@@ -0,0 +1,12 @@
1
+ import type { IncomingMessage, ServerResponse } from "node:http";
2
+ import type { ServerConfig } from "./config";
3
+ import type { MetroBridgeRuntime } from "./metro-bridge";
4
+ export interface RouteContext {
5
+ req: IncomingMessage;
6
+ res: ServerResponse;
7
+ requestId: string;
8
+ config: ServerConfig;
9
+ routePath: string;
10
+ metroBridge: MetroBridgeRuntime;
11
+ }
12
+ export declare function handleRouteRequest(ctx: RouteContext): Promise<void>;
@@ -0,0 +1,8 @@
1
+ import type { Server } from "node:http";
2
+ import type { ServerConfig } from "./server/config";
3
+ export declare function createAgentDeviceProxyServer(options?: {
4
+ config?: ServerConfig;
5
+ }): Server;
6
+ export declare function startAgentDeviceProxyServer(options?: {
7
+ config?: ServerConfig;
8
+ }): Server;
@@ -1 +1 @@
1
- export{createagentDeviceProxyServer,startAgentDeviceProxyServer}from"./36.js";
1
+ export{createAgentDeviceProxyServer,startAgentDeviceProxyServer}from"./36.js";
@@ -0,0 +1,60 @@
1
+ export interface MetroRuntimeHints {
2
+ metro_host: string;
3
+ metro_port: number;
4
+ metro_bundle_url?: string;
5
+ launch_url?: string;
6
+ }
7
+ export interface AgentDeviceProxyClientConfig {
8
+ baseUrl: string;
9
+ bearerToken?: string;
10
+ timeoutMs?: number;
11
+ }
12
+ export type agentDeviceProxyClientConfig = AgentDeviceProxyClientConfig;
13
+ export interface MetroRequestPayload {
14
+ ios_runtime: MetroRuntimeHints;
15
+ timeout_ms?: number;
16
+ }
17
+ export interface HealthResponse {
18
+ status: string;
19
+ service: string;
20
+ checked_at: string;
21
+ }
22
+ export interface MetroResolveResponse {
23
+ bundle_url: string;
24
+ host: string;
25
+ port: number;
26
+ status_url: string;
27
+ }
28
+ export interface MetroProbeResponse extends MetroResolveResponse {
29
+ reachable: boolean;
30
+ status_code: number;
31
+ latency_ms: number;
32
+ detail: string;
33
+ }
34
+ export interface MetroBridgeResponse {
35
+ enabled: boolean;
36
+ base_url: string;
37
+ status_url: string;
38
+ bundle_url: string;
39
+ ios_runtime: MetroRuntimeHints;
40
+ android_runtime: MetroRuntimeHints;
41
+ upstream: {
42
+ bundle_url: string;
43
+ host: string;
44
+ port: number;
45
+ status_url: string;
46
+ };
47
+ probe: {
48
+ reachable: boolean;
49
+ status_code: number;
50
+ latency_ms: number;
51
+ detail: string;
52
+ };
53
+ }
54
+ export interface AgentDeviceProxyClient {
55
+ health: () => Promise<HealthResponse>;
56
+ metroResolve: (payload: MetroRequestPayload | MetroRuntimeHints) => Promise<MetroResolveResponse>;
57
+ metroProbe: (payload: MetroRequestPayload | MetroRuntimeHints) => Promise<MetroProbeResponse>;
58
+ metroBridge: (payload: MetroRequestPayload | MetroRuntimeHints) => Promise<MetroBridgeResponse>;
59
+ }
60
+ export type agentDeviceProxyClient = AgentDeviceProxyClient;
package/package.json CHANGED
@@ -1,33 +1,31 @@
1
1
  {
2
2
  "name": "agent-device-proxy",
3
- "version": "0.1.5",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
- "types": "./index.d.ts",
5
+ "types": "./dist/src/index.d.ts",
6
6
  "exports": {
7
7
  ".": {
8
- "types": "./index.d.ts",
8
+ "types": "./dist/src/index.d.ts",
9
9
  "default": "./dist/src/index.js"
10
10
  },
11
11
  "./client": {
12
- "types": "./index.d.ts",
12
+ "types": "./dist/src/client.d.ts",
13
13
  "default": "./dist/src/client.js"
14
14
  },
15
15
  "./server": {
16
- "types": "./index.d.ts",
16
+ "types": "./dist/src/server.d.ts",
17
17
  "default": "./dist/src/server.js"
18
- },
19
- "./metro-runtime": {
20
- "types": "./index.d.ts",
21
- "default": "./dist/src/metro-runtime.js"
22
18
  }
23
19
  },
24
20
  "bin": {
25
- "agent-device-proxy": "./dist/src/bin.js"
21
+ "agent-device-proxy": "./dist/src/bin.js",
22
+ "agent-device-proxy-macos-remote-setup": "./scripts/macos-remote-setup.sh"
26
23
  },
27
24
  "scripts": {
28
25
  "build": "rslib build",
26
+ "dev": "tsx src/agent-proxy-server.ts dev",
29
27
  "lint": "tsc -p tsconfig.json --noEmit",
30
- "start": "tsx src/agent-proxy-server.ts",
28
+ "serve": "tsx src/agent-proxy-server.ts serve",
31
29
  "test": "vitest run",
32
30
  "test:watch": "vitest"
33
31
  },
@@ -38,7 +36,10 @@
38
36
  "vitest": "^4.0.18"
39
37
  },
40
38
  "files": [
41
- "dist"
39
+ "ARCHITECTURE.md",
40
+ ".env.example",
41
+ "dist",
42
+ "scripts"
42
43
  ],
43
44
  "engines": {
44
45
  "node": ">=24"