agent-device 0.6.1 → 0.6.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-device",
3
- "version": "0.6.1",
3
+ "version": "0.6.3",
4
4
  "description": "Unified control plane for physical and virtual devices via an agent-driven CLI.",
5
5
  "license": "MIT",
6
6
  "author": "Callstack",
@@ -86,8 +86,11 @@ agent-device is visible 'id="anchor"'
86
86
 
87
87
  ```bash
88
88
  agent-device appstate
89
+ agent-device push <bundle|package> <payload.json|inline-json>
89
90
  agent-device get text @e1
90
91
  agent-device screenshot out.png
92
+ agent-device settings permission grant notifications
93
+ agent-device settings permission reset camera
91
94
  agent-device trace start
92
95
  agent-device trace stop ./trace.log
93
96
  ```
@@ -105,9 +108,25 @@ agent-device batch --steps-file /tmp/batch-steps.json --json
105
108
  - Use refs for discovery, selectors for replay/assertions.
106
109
  - Use `fill` for clear-then-type semantics; use `type` for focused append typing.
107
110
  - iOS `appstate` is session-scoped; Android `appstate` is live foreground state.
108
- - iOS settings helpers are simulator-only; use faceid `match|nonmatch|enroll|unenroll`.
111
+ <<<<<<< HEAD
112
+ - iOS settings helpers are simulator-only; use `appearance light|dark|toggle` and faceid `match|nonmatch|enroll|unenroll`.
113
+ - `push` simulates notification delivery:
114
+ - iOS simulator uses APNs-style payload JSON.
115
+ - Android uses broadcast action + typed extras (string/boolean/number).
116
+ - Permission settings are app-scoped and require an active session app:
117
+ `settings permission <grant|deny|reset> <camera|microphone|photos|contacts|notifications> [full|limited]`
118
+ - `full|limited` mode applies only to iOS `photos`; other targets reject mode.
119
+ - On Android, non-ASCII `fill/type` may require an ADB keyboard IME on some system images; only install IME APKs from trusted sources and verify checksum/signature.
109
120
  - If using `--save-script`, prefer explicit path syntax (`--save-script=flow.ad` or `./flow.ad`).
110
121
 
122
+ ## Security and Trust Notes
123
+
124
+ - Prefer a preinstalled `agent-device` binary over on-demand package execution.
125
+ - If install is required, pin an exact version (for example: `npx --yes agent-device@<exact-version> --help`).
126
+ - Signing/provisioning environment variables are optional, sensitive, and only for iOS physical-device setup.
127
+ - Logs/artifacts are written under `~/.agent-device`; replay scripts write to explicit paths you provide.
128
+ - Keep logging off unless debugging and use least-privilege/isolated environments for autonomous runs.
129
+
111
130
  ## Common Mistakes
112
131
 
113
132
  - Mixing debug flow into normal runs (keep logs off unless debugging).
@@ -115,9 +134,6 @@ agent-device batch --steps-file /tmp/batch-steps.json --json
115
134
  - Using URL opens with Android `--activity` (unsupported combination).
116
135
  - Treating `boot` as default first step instead of fallback.
117
136
 
118
- If the CLI is not installed in environment, use:
119
- `npx -y agent-device`
120
-
121
137
  ## References
122
138
 
123
139
  - [references/snapshot-refs.md](references/snapshot-refs.md)
@@ -2,6 +2,22 @@
2
2
 
3
3
  Logging is off by default in normal flows. Enable it on demand for debugging windows. App output is written to a session-scoped file so agents can grep it instead of loading full logs into context.
4
4
 
5
+ ## Data Handling
6
+
7
+ - Default app logs are stored under `~/.agent-device/sessions/<session>/app.log`.
8
+ - Replay scripts saved with `--save-script` are written to the explicit path you provide.
9
+ - Log files may contain sensitive runtime data; review before sharing and clean up when finished.
10
+ - Use `AGENT_DEVICE_APP_LOG_REDACT_PATTERNS` to redact sensitive patterns at write time when needed.
11
+
12
+ ## Retention and Cleanup
13
+
14
+ - Keep logging scoped to active debug windows (`logs clear --restart` before repro, `logs stop` after repro).
15
+ - Prefer bounded inspection (`grep -n`, `tail -50`) instead of reading full logs into context.
16
+ - Clear session logs when finished:
17
+ `agent-device logs clear`
18
+ - Close session to stop background logging state:
19
+ `agent-device close`
20
+
5
21
  ## Quick Flow
6
22
 
7
23
  ```bash
@@ -13,6 +13,14 @@ Use Automatic Signing in Xcode, or provide optional overrides:
13
13
  - `AGENT_DEVICE_IOS_SIGNING_IDENTITY`
14
14
  - `AGENT_DEVICE_IOS_PROVISIONING_PROFILE`
15
15
 
16
+ Security guidance for these overrides:
17
+
18
+ - These variables are optional and only needed for physical-device XCTest setup.
19
+ - Treat values as sensitive host configuration; do not share in chat logs or commit to source control.
20
+ - Do not provide private keys or unrelated secrets; use the minimum values required for signing.
21
+ - Prefer Xcode Automatic Signing when possible to reduce manual secret/config handling.
22
+ - For autonomous/CI runs, keep these unset by default and require explicit opt-in for physical-device workflows.
23
+
16
24
  If setup/build takes long, increase:
17
25
 
18
26
  - `AGENT_DEVICE_DAEMON_TIMEOUT_MS` (default `45000`, for example `120000`)
package/dist/src/350.js DELETED
@@ -1,3 +0,0 @@
1
- import{AsyncLocalStorage as e}from"node:async_hooks";import t from"node:crypto";import r,{promises as n}from"node:fs";import o from"node:os";import i from"node:path";import{fileURLToPath as a,pathToFileURL as s}from"node:url";import{spawn as c,spawnSync as l}from"node:child_process";let d=new e,u=/(token|secret|password|authorization|cookie|api[_-]?key|access[_-]?key|private[_-]?key)/i,f=/(bearer\s+[a-z0-9._-]+|(?:api[_-]?key|token|secret|password)\s*[=:]\s*\S+)/i;function m(){return t.randomBytes(8).toString("hex")}async function p(e,r){let n={...e,diagnosticId:`${Date.now().toString(36)}-${t.randomBytes(4).toString("hex")}`,events:[]};return await d.run(n,r)}function h(){let e=d.getStore();return e?{diagnosticId:e.diagnosticId,requestId:e.requestId,session:e.session,command:e.command,debug:e.debug}:{}}function w(e){let t=d.getStore();if(!t)return;let n={ts:new Date().toISOString(),level:e.level??"info",phase:e.phase,session:t.session,requestId:t.requestId,command:t.command,durationMs:e.durationMs,data:e.data?y(e.data):void 0};if(t.events.push(n),!t.debug)return;let o=`[agent-device][diag] ${JSON.stringify(n)}
2
- `;try{t.logPath&&r.appendFile(t.logPath,o,()=>{}),t.traceLogPath&&r.appendFile(t.traceLogPath,o,()=>{}),t.logPath||t.traceLogPath||process.stderr.write(o)}catch{}}async function g(e,t,r){let n=Date.now();try{let o=await t();return w({level:"info",phase:e,durationMs:Date.now()-n,data:r}),o}catch(t){throw w({level:"error",phase:e,durationMs:Date.now()-n,data:{...r??{},error:t instanceof Error?t.message:String(t)}}),t}}function S(e={}){let t=d.getStore();if(!t||!e.force&&!t.debug||0===t.events.length)return null;try{let e=(t.session??"default").replace(/[^a-zA-Z0-9._-]/g,"_"),n=new Date().toISOString().slice(0,10),a=i.join(o.homedir(),".agent-device","logs",e,n);r.mkdirSync(a,{recursive:!0});let s=new Date().toISOString().replace(/[:.]/g,"-"),c=i.join(a,`${s}-${t.diagnosticId}.ndjson`),l=t.events.map(e=>JSON.stringify(y(e)));return r.writeFileSync(c,`${l.join("\n")}
3
- `),t.events=[],c}catch{return null}}function y(e){return function e(t,r,n){if(null==t)return t;if("string"==typeof t){var o=t,i=n;let e=o.trim();if(!e)return o;if(i&&u.test(i)||f.test(e))return"[REDACTED]";let r=function(e){try{let t=new URL(e);return t.search&&(t.search="?REDACTED"),(t.username||t.password)&&(t.username="REDACTED",t.password="REDACTED"),t.toString()}catch{return null}}(e);return r||(e.length>400?`${e.slice(0,200)}...<truncated>`:e)}if("object"!=typeof t)return t;if(r.has(t))return"[Circular]";if(r.add(t),Array.isArray(t))return t.map(t=>e(t,r));let a={};for(let[n,o]of Object.entries(t)){if(u.test(n)){a[n]="[REDACTED]";continue}a[n]=e(o,r,n)}return a}(e,new WeakSet)}class v extends Error{code;details;cause;constructor(e,t,r,n){super(t),this.code=e,this.details=r,this.cause=n}}function A(e){return e instanceof v?e:e instanceof Error?new v("UNKNOWN",e.message,void 0,e):new v("UNKNOWN","Unknown error",{err:e})}function I(e,t={}){let r=A(e),n=r.details?y(r.details):void 0,o=n&&"string"==typeof n.hint?n.hint:void 0,i=(n&&"string"==typeof n.diagnosticId?n.diagnosticId:void 0)??t.diagnosticId,a=(n&&"string"==typeof n.logPath?n.logPath:void 0)??t.logPath,s=o??function(e){switch(e){case"INVALID_ARGS":return"Check command arguments and run --help for usage examples.";case"SESSION_NOT_FOUND":return"Run open first or pass an explicit device selector.";case"TOOL_MISSING":return"Install required platform tooling and ensure it is available in PATH.";case"DEVICE_NOT_FOUND":return"Verify the target device is booted/connected and selectors match.";case"UNSUPPORTED_OPERATION":return"This command is not available for the selected platform/device.";case"COMMAND_FAILED":default:return"Retry with --debug and inspect diagnostics log for details.";case"UNAUTHORIZED":return"Refresh daemon metadata and retry the command."}}(r.code),c=function(e){if(!e)return;let t={...e};return delete t.hint,delete t.diagnosticId,delete t.logPath,Object.keys(t).length>0?t:void 0}(n);return{code:r.code,message:r.message,hint:s,diagnosticId:i,logPath:a,details:c}}function D(e){let t=[],r=[];for(let n of e){let e=n.depth??0;for(;t.length>0&&e<=t[t.length-1];)t.pop();let o=n.label?.trim()||n.value?.trim()||n.identifier?.trim()||"",i=N(n.type??"Element"),a="group"===i&&!o;a&&t.push(e);let s=a?e:Math.max(0,e-t.length);r.push({node:n,depth:s,type:i,text:b(n,s,a,i)})}return r}function b(e,t,r,n){let o=n??N(e.type??"Element"),i=E(e,o),a=" ".repeat(t),s=e.ref?`@${e.ref}`:"",c=[!1===e.enabled?"disabled":null].filter(Boolean).join(", "),l=c?` [${c}]`:"",d=i?` "${i}"`:"";return r?`${a}${s} [${o}]${l}`.trimEnd():`${a}${s} [${o}]${d}${l}`.trimEnd()}function E(e,t){var r,n;let o=e.label?.trim(),i=e.value?.trim();if("text-field"===(r=t)||"text-view"===r||"search"===r){if(i)return i;if(o)return o}else if(o)return o;if(i)return i;let a=e.identifier?.trim();return!a||(n=a,/^[\w.]+:id\/[\w.-]+$/i.test(n)&&("group"===t||"image"===t||"list"===t||"collection"===t))?"":a}function N(e){let t=e.replace(/XCUIElementType/gi,"").toLowerCase(),r=e.includes(".")&&(e.startsWith("android.")||e.startsWith("androidx.")||e.startsWith("com."));switch(t.includes(".")&&(t=t.replace(/^android\.widget\./,"").replace(/^android\.view\./,"").replace(/^android\.webkit\./,"").replace(/^androidx\./,"").replace(/^com\.google\.android\./,"").replace(/^com\.android\./,"")),t){case"application":return"application";case"navigationbar":return"navigation-bar";case"tabbar":return"tab-bar";case"button":case"imagebutton":return"button";case"link":return"link";case"cell":return"cell";case"statictext":case"checkedtextview":return"text";case"textfield":case"edittext":return"text-field";case"textview":return r?"text":"text-view";case"textarea":return"text-view";case"switch":return"switch";case"slider":return"slider";case"image":case"imageview":return"image";case"webview":return"webview";case"framelayout":case"linearlayout":case"relativelayout":case"constraintlayout":case"viewgroup":case"view":case"group":return"group";case"listview":case"recyclerview":return"list";case"collectionview":return"collection";case"searchfield":return"search";case"segmentedcontrol":return"segmented-control";case"window":return"window";case"checkbox":return"checkbox";case"radio":return"radio";case"menuitem":return"menu-item";case"toolbar":return"toolbar";case"scrollarea":case"scrollview":case"nestedscrollview":return"scroll-area";case"table":return"table";default:return t||"element"}}function $(){try{let e=_();return JSON.parse(r.readFileSync(i.join(e,"package.json"),"utf8")).version??"0.0.0"}catch{return"0.0.0"}}function _(){let e=i.dirname(a(import.meta.url)),t=e;for(let e=0;e<6;e+=1){let e=i.join(t,"package.json");if(r.existsSync(e))return t;t=i.dirname(t)}return e}async function M(e,t,r={}){return new Promise((n,o)=>{let i=c(e,t,{cwd:r.cwd,env:r.env,stdio:["pipe","pipe","pipe"],detached:r.detached}),a="",s=r.binaryStdout?Buffer.alloc(0):void 0,l="",d=!1,u=F(r.timeoutMs),f=u?setTimeout(()=>{d=!0,i.kill("SIGKILL")},u):null;r.binaryStdout||i.stdout.setEncoding("utf8"),i.stderr.setEncoding("utf8"),void 0!==r.stdin&&i.stdin.write(r.stdin),i.stdin.end(),i.stdout.on("data",e=>{r.binaryStdout?s=Buffer.concat([s??Buffer.alloc(0),Buffer.isBuffer(e)?e:Buffer.from(e)]):a+=e}),i.stderr.on("data",e=>{l+=e}),i.on("error",r=>{(f&&clearTimeout(f),"ENOENT"===r.code)?o(new v("TOOL_MISSING",`${e} not found in PATH`,{cmd:e},r)):o(new v("COMMAND_FAILED",`Failed to run ${e}`,{cmd:e,args:t},r))}),i.on("close",i=>{f&&clearTimeout(f);let c=i??1;d&&u?o(new v("COMMAND_FAILED",`${e} timed out after ${u}ms`,{cmd:e,args:t,stdout:a,stderr:l,exitCode:c,timeoutMs:u})):0===c||r.allowFailure?n({stdout:a,stderr:l,exitCode:c,stdoutBuffer:s}):o(new v("COMMAND_FAILED",`${e} exited with code ${c}`,{cmd:e,args:t,stdout:a,stderr:l,exitCode:c}))})})}async function O(e){try{var t;let{shell:r,args:n}=(t=e,"win32"===process.platform?{shell:"cmd.exe",args:["/c","where",t]}:{shell:"bash",args:["-lc",`command -v ${t}`]}),o=await M(r,n,{allowFailure:!0});return 0===o.exitCode&&o.stdout.trim().length>0}catch{return!1}}function T(e,t,r={}){let n=l(e,t,{cwd:r.cwd,env:r.env,stdio:["pipe","pipe","pipe"],encoding:r.binaryStdout?void 0:"utf8",input:r.stdin,timeout:F(r.timeoutMs)});if(n.error){let o=n.error.code;if("ETIMEDOUT"===o)throw new v("COMMAND_FAILED",`${e} timed out after ${F(r.timeoutMs)}ms`,{cmd:e,args:t,timeoutMs:F(r.timeoutMs)},n.error);if("ENOENT"===o)throw new v("TOOL_MISSING",`${e} not found in PATH`,{cmd:e},n.error);throw new v("COMMAND_FAILED",`Failed to run ${e}`,{cmd:e,args:t},n.error)}let o=r.binaryStdout?Buffer.isBuffer(n.stdout)?n.stdout:Buffer.from(n.stdout??""):void 0,i=r.binaryStdout?"":"string"==typeof n.stdout?n.stdout:(n.stdout??"").toString(),a="string"==typeof n.stderr?n.stderr:(n.stderr??"").toString(),s=n.status??1;if(0!==s&&!r.allowFailure)throw new v("COMMAND_FAILED",`${e} exited with code ${s}`,{cmd:e,args:t,stdout:i,stderr:a,exitCode:s});return{stdout:i,stderr:a,exitCode:s,stdoutBuffer:o}}function L(e,t,r={}){c(e,t,{cwd:r.cwd,env:r.env,stdio:"ignore",detached:!0}).unref()}async function x(e,t,r={}){return new Promise((n,o)=>{let i=c(e,t,{cwd:r.cwd,env:r.env,stdio:["pipe","pipe","pipe"],detached:r.detached});r.onSpawn?.(i);let a="",s="",l=r.binaryStdout?Buffer.alloc(0):void 0;r.binaryStdout||i.stdout.setEncoding("utf8"),i.stderr.setEncoding("utf8"),void 0!==r.stdin&&i.stdin.write(r.stdin),i.stdin.end(),i.stdout.on("data",e=>{if(r.binaryStdout){l=Buffer.concat([l??Buffer.alloc(0),Buffer.isBuffer(e)?e:Buffer.from(e)]);return}let t=String(e);a+=t,r.onStdoutChunk?.(t)}),i.stderr.on("data",e=>{let t=String(e);s+=t,r.onStderrChunk?.(t)}),i.on("error",r=>{"ENOENT"===r.code?o(new v("TOOL_MISSING",`${e} not found in PATH`,{cmd:e},r)):o(new v("COMMAND_FAILED",`Failed to run ${e}`,{cmd:e,args:t},r))}),i.on("close",i=>{let c=i??1;0===c||r.allowFailure?n({stdout:a,stderr:s,exitCode:c,stdoutBuffer:l}):o(new v("COMMAND_FAILED",`${e} exited with code ${c}`,{cmd:e,args:t,stdout:a,stderr:s,exitCode:c}))})})}function C(e,t,r={}){let n=c(e,t,{cwd:r.cwd,env:r.env,stdio:["ignore","pipe","pipe"],detached:r.detached}),o="",i="";n.stdout.setEncoding("utf8"),n.stderr.setEncoding("utf8"),n.stdout.on("data",e=>{o+=e}),n.stderr.on("data",e=>{i+=e});let a=new Promise((a,s)=>{n.on("error",r=>{"ENOENT"===r.code?s(new v("TOOL_MISSING",`${e} not found in PATH`,{cmd:e},r)):s(new v("COMMAND_FAILED",`Failed to run ${e}`,{cmd:e,args:t},r))}),n.on("close",n=>{let c=n??1;0===c||r.allowFailure?a({stdout:o,stderr:i,exitCode:c}):s(new v("COMMAND_FAILED",`${e} exited with code ${c}`,{cmd:e,args:t,stdout:o,stderr:i,exitCode:c}))})});return{child:n,wait:a}}function F(e){if(!Number.isFinite(e))return;let t=Math.floor(e);if(!(t<=0))return t}let P=[/(^|[\/\s"'=])dist\/src\/daemon\.js($|[\s"'])/,/(^|[\/\s"'=])src\/daemon\.ts($|[\s"'])/];function R(e){if(!Number.isInteger(e)||e<=0)return!1;try{return process.kill(e,0),!0}catch(e){return"EPERM"===e.code}}function k(e){if(!Number.isInteger(e)||e<=0)return null;try{let t=T("ps",["-p",String(e),"-o","lstart="],{allowFailure:!0,timeoutMs:1e3});if(0!==t.exitCode)return null;let r=t.stdout.trim();return r.length>0?r:null}catch{return null}}function B(e){if(!Number.isInteger(e)||e<=0)return null;try{let t=T("ps",["-p",String(e),"-o","command="],{allowFailure:!0,timeoutMs:1e3});if(0!==t.exitCode)return null;let r=t.stdout.trim();return r.length>0?r:null}catch{return null}}function G(e,t){let r;if(!R(e))return!1;if(t){let r=k(e);if(!r||r!==t)return!1}let n=B(e);return!!n&&!!(r=n.toLowerCase().replaceAll("\\","/")).includes("agent-device")&&P.some(e=>e.test(r))}function j(e,t){try{return process.kill(e,t),!0}catch(t){let e=t.code;if("ESRCH"===e||"EPERM"===e)return!1;throw t}}async function U(e,t){if(!R(e))return!0;let r=Date.now();for(;Date.now()-r<t;)if(await new Promise(e=>setTimeout(e,50)),!R(e))return!0;return!R(e)}async function V(e,t){!G(e,t.expectedStartTime)||!j(e,"SIGTERM")||await U(e,t.termTimeoutMs)||j(e,"SIGKILL")&&await U(e,t.killTimeoutMs)}let q=100,H=new Set(["batch","replay"]);function J(e){let t;try{t=JSON.parse(e)}catch{throw new v("INVALID_ARGS","Batch steps must be valid JSON.")}if(!Array.isArray(t)||0===t.length)throw new v("INVALID_ARGS","Batch steps must be a non-empty JSON array.");return t}function W(e,t){if(!Array.isArray(e)||0===e.length)throw new v("INVALID_ARGS","batch requires a non-empty batchSteps array.");if(e.length>t)throw new v("INVALID_ARGS",`batch has ${e.length} steps; max allowed is ${t}.`);let r=[];for(let t=0;t<e.length;t+=1){let n=e[t];if(!n||"object"!=typeof n)throw new v("INVALID_ARGS",`Invalid batch step at index ${t}.`);let o="string"==typeof n.command?n.command.trim().toLowerCase():"";if(!o)throw new v("INVALID_ARGS",`Batch step ${t+1} requires command.`);if(H.has(o))throw new v("INVALID_ARGS",`Batch step ${t+1} cannot run ${o}.`);if(void 0!==n.positionals&&!Array.isArray(n.positionals))throw new v("INVALID_ARGS",`Batch step ${t+1} positionals must be an array.`);let i=n.positionals??[];if(i.some(e=>"string"!=typeof e))throw new v("INVALID_ARGS",`Batch step ${t+1} positionals must contain only strings.`);if(void 0!==n.flags&&("object"!=typeof n.flags||Array.isArray(n.flags)||!n.flags))throw new v("INVALID_ARGS",`Batch step ${t+1} flags must be an object.`);r.push({command:o,positionals:i,flags:n.flags??{}})}return r}export{default as node_net}from"node:net";export{v as AppError,q as DEFAULT_BATCH_MAX_STEPS,A as asAppError,D as buildSnapshotDisplayLines,m as createRequestId,E as displayLabel,w as emitDiagnostic,a as fileURLToPath,_ as findProjectRoot,S as flushDiagnosticsToSessionFile,N as formatRole,b as formatSnapshotLine,h as getDiagnosticsMeta,G as isAgentDeviceDaemonProcess,R as isProcessAlive,t as node_crypto,r as node_fs,o as node_os,i as node_path,I as normalizeError,J as parseBatchStepsJson,s as pathToFileURL,n as promises,B as readProcessCommand,k as readProcessStartTime,$ as readVersion,M as runCmd,C as runCmdBackground,L as runCmdDetached,x as runCmdStreaming,T as runCmdSync,c as spawn,V as stopProcessForTakeover,W as validateAndNormalizeBatchSteps,O as whichCmd,g as withDiagnosticTimer,p as withDiagnosticsScope};