agent-device-proxy 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # agent-device-proxy
2
2
 
3
- Reusable macOS agent-device-proxy (simple mode) for:
3
+ Reusable macOS `agent-device-proxy` sidecar for:
4
4
 
5
- - `agent-device` command execution
5
+ - native `agent-device` remote-daemon bridge
6
6
  - Metro resolve/probe
7
7
  - artifact upload/install for `.app` and `.apk`
8
8
 
@@ -93,8 +93,8 @@ export AGENT_DEVICE_DAEMON_AUTH_TOKEN="<strong-token>"
93
93
  The proxy bridge exposes `GET /agent-device/health` and `POST /agent-device/rpc`,
94
94
  and forwards them to the configured host daemon base URL.
95
95
 
96
- For artifact upload/install and Metro helper flows, the same worker can also call
97
- the `agent-device-proxy` API directly.
96
+ For artifact upload/install and Metro helper flows, the same worker can call the
97
+ `agent-device-proxy` API directly.
98
98
 
99
99
  In your workflow/agent worker:
100
100
 
@@ -166,10 +166,10 @@ import {
166
166
  } from "agent-device-proxy"
167
167
  ```
168
168
 
169
- Use `installApp()` for artifact handoff and `agentDeviceExec()` for explicit
170
- proxy-side execution from Node. This package does not install an `agent-device`
171
- bin shim; use the real `agent-device` package for CLI execution and native
172
- remote-daemon support.
169
+ Use `installApp()` for artifact handoff and `agentDeviceExec()` only when you
170
+ explicitly need the proxy API from Node. This package does not install an
171
+ `agent-device` bin shim; use the real `agent-device` package for CLI execution
172
+ and native remote-daemon support.
173
173
 
174
174
  ## Embedded server
175
175
 
package/dist/src/36.js CHANGED
@@ -1,18 +1,18 @@
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 l(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 c 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 c(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 c)throw t;throw new c(400,"invalid_request","invalid request body")}if(o<=0)throw await E(e),new c(400,"invalid_request","artifact body must not be empty");return{sizeBytes:o,sha256:i.digest("hex")}}async function _({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 c(400,"invalid_request","invalid zip archive",{exit_code:n.exitCode,stdout:n.stdout,stderr:n.stderr});if(n.stdoutTruncated)throw new c(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 c(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 c(400,"invalid_request","zip archive contains absolute paths");if(".."===i||i.startsWith("../")||i.includes("/../")||i.includes("\0"))throw new c(400,"invalid_request","zip archive contains unsafe paths");i.split("/").some(t=>t.toLowerCase().endsWith(".app"))&&(s=!0)}if(!s)throw new c(400,"invalid_request","zip archive does not contain a .app bundle")}async function p({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 c(400,"invalid_request","invalid zip archive",{exit_code:o.exitCode,stdout:o.stdout,stderr:o.stderr});if(o.stdoutTruncated)throw new c(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 c(400,"invalid_request","zip archive summary is invalid");let u=Number.parseInt(s[1].replaceAll(",",""),10);if(!Number.isFinite(u)||u<0)throw new c(400,"invalid_request","zip archive summary is invalid");return u}async function m(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 l=Date.parse(String(d?.created_at||""));if(!Number.isFinite(l)||l<r){await g(a);continue}let c=Number(d?.size_bytes)||0;n.push({artifactDir:a,createdAtMs:l,sizeBytes:c})}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="",l="",c=0,f=0,_=!1,p=!1,m=!1,h=!1,w=setTimeout(()=>{m=!0,s.kill("SIGKILL")},a);function v(t,e){let r=e.toString("utf8"),i=Buffer.byteLength(r);if("stdout"===t){if(c>=n){_=!0;return}let t=n-c,e=i>t?T(r,t):r;u+=e,c+=Buffer.byteLength(e),i>t&&(_=!0);return}if(f>=n){p=!0;return}let a=n-f,o=i>a?T(r,a):r;l+=o,f+=Buffer.byteLength(o),i>a&&(p=!0)}function y(t){h||(h=!0,clearTimeout(w),_&&(u=`${u}
2
- [output truncated]`.trim()),p&&(l=`${l}
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:m?124:"number"==typeof t?t:1,stdout:u,stderr:m?`${l}
4
- proxy timeout after ${a}ms`.trim():l,signal:e??"",timedOut:m,stdoutTruncated:_,stderrTruncated:p})}),s.on("error",t=>{y({exitCode:1,stdout:u,stderr:`${l}
5
- ${t.message}`.trim(),signal:"",timedOut:m,stdoutTruncated:_,stderrTruncated:p})})})}function A({result:t,action:e,strictCommandStatus:r}){if(r&&0!==t.exitCode)throw new c(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?D(t,e,r,i):D(t,e,{ok:!0,data:r,request_id:i},i)}function R(t,e,r,i,a,n,o=null){n?D(t,e,{error:i},a):D(t,e,{ok:!1,error:{code:r,message:i,...o?{details:o}:{}},request_id:a},a)}function D(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 C(t,e){let r=(await S(t,e)).toString("utf8");if(!r.trim())throw new c(400,"invalid_request","body must be valid JSON");try{let t=JSON.parse(r);if(!t||"object"!=typeof t||Array.isArray(t))throw new c(400,"invalid_request","body must be valid JSON object");return t}catch{throw new c(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 c(413,"invalid_request","request body too large");r.push(t)}}catch(t){if(t instanceof c)throw t;throw new c(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 c(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=B(t,e);if(!r)return null;if(r.includes(":"))throw new c(400,"invalid_request",`${e} must not include a port`);if(!/^[A-Za-z0-9._-]{1,253}$/.test(r))throw new c(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 c(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:M(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:M(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=B(t,e);if(!a)return null;try{i=new URL(a)}catch{throw new c(400,"invalid_request",`${e} must be an absolute URL`)}if(r&&!r.has(i.protocol))throw new c(400,"invalid_request",`${e} must use ${Array.from(r).join(" or ")}`);return i.toString()}function B(t,e){if(null==t||""===t)return"";if("string"!=typeof t)throw new c(400,"invalid_request",`${e} must be a string`);return t.trim()}function M(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 P(t){return!!t&&"object"==typeof t&&!Array.isArray(t)}function z(t,e){let r=j(t,e);if(!r)throw new c(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 c(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 c(400,"invalid_request",`${e} must be a string`);return t.trim()}function G(t,e,r){if(null==t||""===t)return r;if("boolean"!=typeof t)throw new c(400,"invalid_request",`${e} must be a boolean`);return t}function U(t,e){let r=t[e];return"string"==typeof r?r.trim():Array.isArray(r)&&r.length>0?String(r[0]).trim():""}function k(t={}){return Y(t).server}function X(t={}){let{server:e,config:r,artifactStore:i}=Y(t);return function(t){let e=[t,"zip","unzip"],r=e.filter(t=>{var e,r;return!("string"==typeof(e=t)&&e.trim()&&0===u("sh",["-lc",`command -v ${(r=e,`'${r.replaceAll("'","'\\''")}'`)} >/dev/null 2>&1`],{stdio:"ignore"}).status)});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.command),e.listen(r.port,r.host,()=>{process.stdout.write(`agent-device-proxy listening on http://${r.host}:${r.port}
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 l(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 c 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 c(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 c)throw t;throw new c(400,"invalid_request","invalid request body")}if(o<=0)throw await E(e),new c(400,"invalid_request","artifact body must not be empty");return{sizeBytes:o,sha256:i.digest("hex")}}async function _({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 c(400,"invalid_request","invalid zip archive",{exit_code:n.exitCode,stdout:n.stdout,stderr:n.stderr});if(n.stdoutTruncated)throw new c(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 c(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 c(400,"invalid_request","zip archive contains absolute paths");if(".."===i||i.startsWith("../")||i.includes("/../")||i.includes("\0"))throw new c(400,"invalid_request","zip archive contains unsafe paths");i.split("/").some(t=>t.toLowerCase().endsWith(".app"))&&(s=!0)}if(!s)throw new c(400,"invalid_request","zip archive does not contain a .app bundle")}async function m({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 c(400,"invalid_request","invalid zip archive",{exit_code:o.exitCode,stdout:o.stdout,stderr:o.stderr});if(o.stdoutTruncated)throw new c(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 c(400,"invalid_request","zip archive summary is invalid");let u=Number.parseInt(s[1].replaceAll(",",""),10);if(!Number.isFinite(u)||u<0)throw new c(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 l=Date.parse(String(d?.created_at||""));if(!Number.isFinite(l)||l<r){await g(a);continue}let c=Number(d?.size_bytes)||0;n.push({artifactDir:a,createdAtMs:l,sizeBytes:c})}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="",l="",c=0,f=0,_=!1,m=!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(c>=n){_=!0;return}let t=n-c,e=i>t?T(r,t):r;u+=e,c+=Buffer.byteLength(e),i>t&&(_=!0);return}if(f>=n){m=!0;return}let a=n-f,o=i>a?T(r,a):r;l+=o,f+=Buffer.byteLength(o),i>a&&(m=!0)}function y(t){h||(h=!0,clearTimeout(w),_&&(u=`${u}
2
+ [output truncated]`.trim()),m&&(l=`${l}
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?`${l}
4
+ proxy timeout after ${a}ms`.trim():l,signal:e??"",timedOut:p,stdoutTruncated:_,stderrTruncated:m})}),s.on("error",t=>{y({exitCode:1,stdout:u,stderr:`${l}
5
+ ${t.message}`.trim(),signal:"",timedOut:p,stdoutTruncated:_,stderrTruncated:m})})})}function A({result:t,action:e,strictCommandStatus:r}){if(r&&0!==t.exitCode)throw new c(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 c(400,"invalid_request","body must be valid JSON");try{let t=JSON.parse(r);if(!t||"object"!=typeof t||Array.isArray(t))throw new c(400,"invalid_request","body must be valid JSON object");return t}catch{throw new c(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 c(413,"invalid_request","request body too large");r.push(t)}}catch(t){if(t instanceof c)throw t;throw new c(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 c(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 c(400,"invalid_request",`${e} must not include a port`);if(!/^[A-Za-z0-9._-]{1,253}$/.test(r))throw new c(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 c(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 c(400,"invalid_request",`${e} must be an absolute URL`)}if(r&&!r.has(i.protocol))throw new c(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 c(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 c(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 c(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 c(400,"invalid_request",`${e} must be a string`);return t.trim()}function G(t,e,r){if(null==t||""===t)return r;if("boolean"!=typeof t)throw new c(400,"invalid_request",`${e} must be a boolean`);return t}function U(t,e){let r=t[e];return"string"==typeof r?r.trim():Array.isArray(r)&&r.length>0?String(r[0]).trim():""}function k(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=>!F(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}
7
7
  `),process.stdout.write(`api health endpoint: http://${r.host}:${r.port}/api/health
8
8
  `),process.stdout.write(`api exec endpoint: http://${r.host}:${r.port}/api/agent-device/exec
9
9
  `),process.stdout.write(`api install endpoint: http://${r.host}:${r.port}/api/agent-device/install
10
10
  `),process.stdout.write(`api artifacts endpoint: http://${r.host}:${r.port}/api/artifacts/upload
11
11
  `),process.stdout.write(`api metro resolve endpoint: http://${r.host}:${r.port}/api/metro/resolve
12
12
  `),process.stdout.write(`api metro probe endpoint: http://${r.host}:${r.port}/api/metro/probe
13
- `),process.stdout.write(`agent-device command: ${r.command}
13
+ `),process.stdout.write(`agent-device command available: ${F(r.command,r.baseCommandEnv.PATH)?"yes":"no"} (${r.command})
14
14
  `),process.stdout.write(`root dir: ${r.rootDir}
15
15
  `),process.stdout.write(`artifacts dir: ${r.artifactsDir}
16
16
  `),r.workspaceRoot&&process.stdout.write(`workspace root constraint: ${r.workspaceRoot}
17
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,D,S,$,B,M,k,X,H,F,J,W=n.config||(s=process.env.AGENT_DEVICE_PROXY_HOST??"127.0.0.1",u=l("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=l("AGENT_DEVICE_PROXY_MAX_BODY_BYTES",1048576,{min:1024,max:0x1000000}),E=l("AGENT_DEVICE_PROXY_ARTIFACT_MAX_BYTES",0x20000000,{min:1048576,max:0xffffffff}),T=l("AGENT_DEVICE_PROXY_EXEC_TIMEOUT_MS",24e4,{min:1e3,max:36e5}),D=l("AGENT_DEVICE_PROXY_COMMAND_MAX_OUTPUT_BYTES",1048576,{min:4096,max:0x4000000}),S=l("AGENT_DEVICE_PROXY_ARTIFACT_UNZIP_TIMEOUT_MS",18e4,{min:1e3,max:18e5}),$=l("AGENT_DEVICE_PROXY_ARTIFACT_TTL_HOURS",24,{min:1,max:720}),B=l("AGENT_DEVICE_PROXY_ARTIFACT_MAX_TOTAL_BYTES",0x140000000,{min:1048576,max:0x1900000000}),M=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),k=(process.env.AGENT_DEVICE_PROXY_BEARER_TOKEN??"").trim(),X=(process.env.AGENT_DEVICE_PROXY_COMMAND??"agent-device").trim()||"agent-device",H=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??""),J=e.resolve(process.env.AGENT_DEVICE_PROXY_ARTIFACTS_DIR??e.join(H,".agent-device-proxy-artifacts")),{host:s,port:u,daemonBaseUrl:d,daemonAuthToken:v,maxJsonBodyBytes:y,maxArtifactBytes:E,commandTimeoutMs:T,commandMaxOutputBytes:D,unzipTimeoutMs:S,artifactTtlHours:$,artifactMaxTotalBytes:B,strictCommandStatus:M,bearerToken:k,command:X,rootDir:H,workspaceRoot:F,artifactsDir:J,baseCommandEnv:{PATH:function(t,r){let i=[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(W.artifactsDir,{recursive:!0});let K=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(),l=e.join(t.artifactsDir,d),w=e.join(l,o.filename);await i.mkdir(l,{recursive:!0});try{s=await f(n,w,t.maxArtifactBytes)}catch(t){throw await g(l),t}if(o.expected_sha256&&o.expected_sha256!==s.sha256)throw await g(l),new c(400,"invalid_request","artifact sha256 mismatch");let v=w,y=!1,E=s.sizeBytes;if("app"===o.artifact_type){let a=e.join(l,"unpacked");await i.mkdir(a,{recursive:!0}),await _({archivePath:w,timeoutMs:t.unzipTimeoutMs,runCommand:r,env:t.baseCommandEnv});let n=await p({archivePath:w,timeoutMs:t.unzipTimeoutMs,runCommand:r,env:t.baseCommandEnv,maxOutputBytes:t.commandMaxOutputBytes});if(n>t.maxArtifactBytes)throw await g(l),new c(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:l,env:t.baseCommandEnv,timeoutMs:t.unzipTimeoutMs,maxOutputBytes:t.commandMaxOutputBytes});if(0!==o.exitCode)throw await g(l),new c(400,"invalid_request","failed to unzip app archive",{exit_code:o.exitCode,stdout:o.stdout,stderr:o.stderr});let u=await m(a);if(!u)throw await g(l),new c(400,"invalid_request","zip archive does not contain a .app bundle");let d=await h(a);if(d>t.maxArtifactBytes)throw await g(l),new c(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(l,"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 c(400,"invalid_request","artifact_id format is invalid");let s=e.join(t.artifactsDir,n,"metadata.json");if(!o(s))throw new c(404,"not_found","artifact_id not found");try{a=JSON.parse(await i.readFile(s,"utf8"))}catch{throw new c(500,"internal_error","artifact metadata is unreadable")}if(!a||"object"!=typeof a||!a.ready_path||!a.artifact_type)throw new c(500,"internal_error","artifact metadata is invalid");if(!o(a.ready_path))throw new c(404,"not_found","artifact file is missing");return a},maybeCleanupArtifacts:u}}(W,{runCommand:b});return{server:t.createServer(async(t,r)=>{var i,n;let s,u,d,l="string"==typeof(s=t.headers["x-request-id"])&&s.trim()?s.trim().slice(0,128):a(),{path:f,legacy:_}=(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 V({req:t,res:r,requestId:l,config:W,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",l),r.end(JSON.stringify(t));return}x(r,200,t,l,_);return}if("POST"!==t.method)return void R(r,404,"not_found","not found",l,_);if((i=W.bearerToken)&&(t.headers.authorization||"")!==`Bearer ${i}`)return void R(r,401,"unauthorized","unauthorized",l,_);if("/agent-device/rpc"===f)return void await V({req:t,res:r,requestId:l,config:W,endpoint:"rpc"});if("/api/artifacts/upload"===f){let e=function(t){let e=L(U(t,"x-artifact-type"),"x-artifact-type",["app","apk"]),r=L(U(t,"x-artifact-archive"),"x-artifact-archive",["zip","raw"]),i=(U(t,"x-artifact-filename")||`${e}-${Date.now()}${"zip"===r?".zip":".bin"}`).replace(/[^a-zA-Z0-9._-]/g,"_").slice(-160)||`artifact-${Date.now()}`,a=U(t,"x-artifact-sha256").toLowerCase();if(a&&!/^[a-f0-9]{64}$/.test(a))throw new c(400,"invalid_request","x-artifact-sha256 must be a 64-char lowercase hex sha256");if("app"===e&&"zip"!==r)throw new c(400,"invalid_request","x-artifact-type=app requires x-artifact-archive=zip");if("apk"===e&&"raw"!==r)throw new c(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 K.storeArtifactFromRequest({req:t,headers:e});x(r,200,i,l,_);return}if("/api/metro/resolve"===f){let e=await C(t,W.maxJsonBodyBytes),i=N(e?.ios_runtime??e),a=I(i);if(!a)throw new c(400,"invalid_metro_runtime","ios_runtime metro hints are required (metro_bundle_url or metro_host+metro_port)");x(r,200,a,l,_);return}if("/api/metro/probe"===f){let e=await C(t,W.maxJsonBodyBytes),i=N(e?.ios_runtime??e),a=I(i);if(!a)throw new c(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},l,_);return}if("/api/agent-device/exec"===f){let i,s=await C(t,W.maxJsonBodyBytes),u=function(t){if(!P(t))throw new c(400,"invalid_request","body must be valid JSON object");if(!Array.isArray(t.argv)||t.argv.some(t=>"string"!=typeof t))throw new c(400,"invalid_request","argv must be an array of strings");if(0===t.argv.length)throw new c(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 c(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 c(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:W.command,argv:d.argv,cwd:function(t,r){if(!t)return r.rootDir;if(!e.isAbsolute(t))throw new c(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 c(400,"invalid_request","cwd is outside configured workspace root");if(!o(i))throw new c(400,"invalid_request","cwd does not exist");return i}(d.cwd,W),env:(n=W.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:W.commandTimeoutMs,maxOutputBytes:W.commandMaxOutputBytes});A({result:f,action:`agent-device exec (${d.argv[0]||"command"})`,strictCommandStatus:W.strictCommandStatus}),x(r,200,{exit_code:f.exitCode,stdout:f.stdout,stderr:f.stderr,timed_out:f.timedOut},l,_);return}if("/api/agent-device/install"===f){let e=await C(t,W.maxJsonBodyBytes),i=function(t){if(!P(t))throw new c(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=G(t.reinstall,"reinstall",!1);return{app:e,artifact_id:r,platform:i,device:a,session:n,udid:o,serial:s,reinstall:u,json:G(t.json,"json",!0)}}(e),n=await K.readArtifactMetadata(i.artifact_id),o=function(t,e){if("android"===t.platform&&"apk"!==e.artifact_type)throw new c(400,"invalid_request","android install requires artifact_type=apk");if(("ios"===t.platform||"apple"===t.platform)&&"app"!==e.artifact_type)throw new c(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:W.command,argv:o,cwd:W.rootDir,env:{...W.baseCommandEnv,AGENT_DEVICE_PROXY_REQUEST_ID:a(),AGENT_DEVICE_PROXY_ARTIFACT_ID:i.artifact_id},timeoutMs:W.commandTimeoutMs,maxOutputBytes:W.commandMaxOutputBytes});A({result:s,action:"agent-device install",strictCommandStatus:W.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)},l,_);return}R(r,404,"not_found","not found",l,_)}catch(e){let t;R(r,(t=function(t,e="internal error"){return t instanceof c?t:new c(500,"internal_error",t instanceof Error?t.message:e)}(e)).statusCode,t.code,t.message,l,_,t.details)}}),config:W,artifactStore:K}}async function V(t){var e,r;let{req:i,res:a,requestId:n,config:o,endpoint:s}=t;if(!o.daemonBaseUrl)throw new c(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,l=i.headers["content-type"];"string"==typeof l&&l.trim()&&d.set("content-type",l);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 _="rpc"===s?new Uint8Array(await S(i,o.maxJsonBodyBytes)):void 0,p=await fetch(u,{method:i.method||("health"===s?"GET":"POST"),headers:d,..._?{body:_}:{}}),m=await p.text(),h=p.headers.get("content-type");a.statusCode=p.status,h?a.setHeader("content-type",h):a.setHeader("content-type","application/json; charset=utf-8"),a.setHeader("x-request-id",n),a.end(m)}export{k as createagentDeviceProxyServer,X as startAgentDeviceProxyServer};
18
+ `)}),e}function Y(n={}){let s,u,d,v,y,E,T,C,S,$,P,B,k,X,F,J,W,K=n.config||(s=process.env.AGENT_DEVICE_PROXY_HOST??"127.0.0.1",u=l("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=l("AGENT_DEVICE_PROXY_MAX_BODY_BYTES",1048576,{min:1024,max:0x1000000}),E=l("AGENT_DEVICE_PROXY_ARTIFACT_MAX_BYTES",0x20000000,{min:1048576,max:0xffffffff}),T=l("AGENT_DEVICE_PROXY_EXEC_TIMEOUT_MS",24e4,{min:1e3,max:36e5}),C=l("AGENT_DEVICE_PROXY_COMMAND_MAX_OUTPUT_BYTES",1048576,{min:4096,max:0x4000000}),S=l("AGENT_DEVICE_PROXY_ARTIFACT_UNZIP_TIMEOUT_MS",18e4,{min:1e3,max:18e5}),$=l("AGENT_DEVICE_PROXY_ARTIFACT_TTL_HOURS",24,{min:1,max:720}),P=l("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),k=(process.env.AGENT_DEVICE_PROXY_BEARER_TOKEN??"").trim(),X=(process.env.AGENT_DEVICE_PROXY_COMMAND??"agent-device").trim()||"agent-device",F=e.resolve(process.env.AGENT_DEVICE_PROXY_ROOT_DIR??process.cwd()),J=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(F,".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:k,command:X,rootDir:F,workspaceRoot:J,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(),l=e.join(t.artifactsDir,d),w=e.join(l,o.filename);await i.mkdir(l,{recursive:!0});try{s=await f(n,w,t.maxArtifactBytes)}catch(t){throw await g(l),t}if(o.expected_sha256&&o.expected_sha256!==s.sha256)throw await g(l),new c(400,"invalid_request","artifact sha256 mismatch");let v=w,y=!1,E=s.sizeBytes;if("app"===o.artifact_type){let a=e.join(l,"unpacked");await i.mkdir(a,{recursive:!0}),await _({archivePath:w,timeoutMs:t.unzipTimeoutMs,runCommand:r,env:t.baseCommandEnv});let n=await m({archivePath:w,timeoutMs:t.unzipTimeoutMs,runCommand:r,env:t.baseCommandEnv,maxOutputBytes:t.commandMaxOutputBytes});if(n>t.maxArtifactBytes)throw await g(l),new c(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:l,env:t.baseCommandEnv,timeoutMs:t.unzipTimeoutMs,maxOutputBytes:t.commandMaxOutputBytes});if(0!==o.exitCode)throw await g(l),new c(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(l),new c(400,"invalid_request","zip archive does not contain a .app bundle");let d=await h(a);if(d>t.maxArtifactBytes)throw await g(l),new c(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(l,"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 c(400,"invalid_request","artifact_id format is invalid");let s=e.join(t.artifactsDir,n,"metadata.json");if(!o(s))throw new c(404,"not_found","artifact_id not found");try{a=JSON.parse(await i.readFile(s,"utf8"))}catch{throw new c(500,"internal_error","artifact metadata is unreadable")}if(!a||"object"!=typeof a||!a.ready_path||!a.artifact_type)throw new c(500,"internal_error","artifact metadata is invalid");if(!o(a.ready_path))throw new c(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,l="string"==typeof(s=t.headers["x-request-id"])&&s.trim()?s.trim().slice(0,128):a(),{path:f,legacy:_}=(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:l,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",l),r.end(JSON.stringify(t));return}x(r,200,t,l,_);return}if("POST"!==t.method)return void R(r,404,"not_found","not found",l,_);if((i=K.bearerToken)&&(t.headers.authorization||"")!==`Bearer ${i}`)return void R(r,401,"unauthorized","unauthorized",l,_);if("/agent-device/rpc"===f)return void await H({req:t,res:r,requestId:l,config:K,endpoint:"rpc"});if("/api/artifacts/upload"===f){let e=function(t){let e=L(U(t,"x-artifact-type"),"x-artifact-type",["app","apk"]),r=L(U(t,"x-artifact-archive"),"x-artifact-archive",["zip","raw"]),i=(U(t,"x-artifact-filename")||`${e}-${Date.now()}${"zip"===r?".zip":".bin"}`).replace(/[^a-zA-Z0-9._-]/g,"_").slice(-160)||`artifact-${Date.now()}`,a=U(t,"x-artifact-sha256").toLowerCase();if(a&&!/^[a-f0-9]{64}$/.test(a))throw new c(400,"invalid_request","x-artifact-sha256 must be a 64-char lowercase hex sha256");if("app"===e&&"zip"!==r)throw new c(400,"invalid_request","x-artifact-type=app requires x-artifact-archive=zip");if("apk"===e&&"raw"!==r)throw new c(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,l,_);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 c(400,"invalid_metro_runtime","ios_runtime metro hints are required (metro_bundle_url or metro_host+metro_port)");x(r,200,a,l,_);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 c(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},l,_);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 c(400,"invalid_request","body must be valid JSON object");if(!Array.isArray(t.argv)||t.argv.some(t=>"string"!=typeof t))throw new c(400,"invalid_request","argv must be an array of strings");if(0===t.argv.length)throw new c(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 c(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 c(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 c(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 c(400,"invalid_request","cwd is outside configured workspace root");if(!o(i))throw new c(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},l,_);return}if("/api/agent-device/install"===f){V(K);let e=await D(t,K.maxJsonBodyBytes),i=function(t){if(!M(t))throw new c(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=G(t.reinstall,"reinstall",!1);return{app:e,artifact_id:r,platform:i,device:a,session:n,udid:o,serial:s,reinstall:u,json:G(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 c(400,"invalid_request","android install requires artifact_type=apk");if(("ios"===t.platform||"apple"===t.platform)&&"app"!==e.artifact_type)throw new c(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)},l,_);return}R(r,404,"not_found","not found",l,_)}catch(e){let t;R(r,(t=function(t,e="internal error"){return t instanceof c?t:new c(500,"internal_error",t instanceof Error?t.message:e)}(e)).statusCode,t.code,t.message,l,_,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 c(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,l=i.headers["content-type"];"string"==typeof l&&l.trim()&&d.set("content-type",l);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 _="rpc"===s?new Uint8Array(await S(i,o.maxJsonBodyBytes)):void 0,m=await fetch(u,{method:i.method||("health"===s?"GET":"POST"),headers:d,..._?{body:_}:{}}),p=await m.text(),h=m.headers.get("content-type");a.statusCode=m.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(!F(t.command,t.baseCommandEnv.PATH))throw new c(503,"command_unavailable",`local command unavailable for this endpoint: ${t.command}`,{command:t.command})}function F(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{k as createagentDeviceProxyServer,X as startAgentDeviceProxyServer};
package/dist/src/402.js CHANGED
@@ -1 +1 @@
1
- export{createHash,randomUUID}from"node:crypto";export{createReadStream,createWriteStream,existsSync,mkdirSync,promises}from"node:fs";export{default as node_path}from"node:path";
1
+ export{createHash,randomUUID}from"node:crypto";export{createReadStream,createWriteStream,existsSync,mkdirSync,promises,readFileSync}from"node:fs";export{default as node_path}from"node:path";
package/dist/src/bin.js CHANGED
@@ -1,2 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import{startAgentDeviceProxyServer as _}from"./36.js";_();
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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-device-proxy",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "types": "./index.d.ts",
6
6
  "exports": {