agent-device 0.3.3 → 0.3.5
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 +14 -3
- package/dist/src/bin.js +4 -3
- package/dist/src/daemon.js +15 -15
- package/package.json +1 -1
- package/skills/agent-device/SKILL.md +13 -5
- package/skills/agent-device/references/session-management.md +1 -0
- package/src/core/__tests__/open-target.test.ts +16 -0
- package/src/core/dispatch.ts +2 -1
- package/src/core/open-target.ts +13 -0
- package/src/daemon/__tests__/session-store.test.ts +24 -0
- package/src/daemon/handlers/__tests__/replay-heal.test.ts +81 -0
- package/src/daemon/handlers/__tests__/session.test.ts +218 -0
- package/src/daemon/handlers/interaction.ts +1 -0
- package/src/daemon/handlers/session.ts +155 -27
- package/src/daemon/selectors.ts +0 -1
- package/src/daemon/session-store.ts +11 -0
- package/src/platforms/android/__tests__/index.test.ts +22 -1
- package/src/platforms/android/index.ts +18 -0
- package/src/platforms/ios/__tests__/index.test.ts +24 -0
- package/src/platforms/ios/index.ts +69 -4
- package/src/platforms/ios/runner-client.ts +10 -2
- package/src/utils/__tests__/args.test.ts +14 -0
- package/src/utils/args.ts +8 -2
- package/src/utils/interactors.ts +2 -2
package/README.md
CHANGED
|
@@ -101,7 +101,7 @@ Flags:
|
|
|
101
101
|
- `--device <name>`
|
|
102
102
|
- `--udid <udid>` (iOS)
|
|
103
103
|
- `--serial <serial>` (Android)
|
|
104
|
-
- `--activity <component>` (Android; package/Activity or package/.Activity)
|
|
104
|
+
- `--activity <component>` (Android app launch only; package/Activity or package/.Activity; not for URL opens)
|
|
105
105
|
- `--session <name>`
|
|
106
106
|
- `--verbose` for daemon and runner logs
|
|
107
107
|
- `--json` for structured output
|
|
@@ -117,7 +117,7 @@ npx skills add https://github.com/callstackincubator/agent-device --skill agent-
|
|
|
117
117
|
Sessions:
|
|
118
118
|
- `open` starts a session. Without args boots/activates the target device/simulator without launching an app.
|
|
119
119
|
- All interaction commands require an open session.
|
|
120
|
-
- If a session is already open, `open <app>` switches the active app
|
|
120
|
+
- If a session is already open, `open <app|url>` switches the active app or opens a deep link URL.
|
|
121
121
|
- `close` stops the session and releases device resources. Pass an app to close it explicitly, or omit to just close the session.
|
|
122
122
|
- Use `--session <name>` to manage multiple sessions.
|
|
123
123
|
- Session scripts are written to `~/.agent-device/sessions/<session>-<timestamp>.ad` when recording is enabled with `--save-script`.
|
|
@@ -126,10 +126,21 @@ Sessions:
|
|
|
126
126
|
Navigation helpers:
|
|
127
127
|
- `boot --platform ios|android` ensures the target is ready without launching an app.
|
|
128
128
|
- Use `boot` mainly when starting a new session and `open` fails because no booted simulator/emulator is available.
|
|
129
|
-
- `open [app]` already boots/activates the selected target when needed.
|
|
129
|
+
- `open [app|url]` already boots/activates the selected target when needed.
|
|
130
130
|
- `reinstall <app> <path>` uninstalls and installs the app binary in one command (Android + iOS simulator in v1).
|
|
131
131
|
- `reinstall` accepts package/bundle id style app names and supports `~` in paths.
|
|
132
132
|
|
|
133
|
+
Deep links:
|
|
134
|
+
- `open <url>` supports deep links with `scheme://...`.
|
|
135
|
+
- Android opens deep links via `VIEW` intent.
|
|
136
|
+
- iOS deep link open is simulator-only in v1.
|
|
137
|
+
- `--activity` cannot be combined with URL opens.
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
agent-device open "myapp://home" --platform android
|
|
141
|
+
agent-device open "https://example.com" --platform ios
|
|
142
|
+
```
|
|
143
|
+
|
|
133
144
|
Find (semantic):
|
|
134
145
|
- `find <text> <action> [value]` finds by any text (label/value/identifier) using a scoped snapshot.
|
|
135
146
|
- `find text|label|value|role|id <value> <action> [value]` for specific locators.
|
package/dist/src/bin.js
CHANGED
|
@@ -2,14 +2,14 @@ import{node_path as e,asAppError as t,pathToFileURL as r,runCmdDetached as n,nod
|
|
|
2
2
|
`)}function u(e){let t=e.details?`
|
|
3
3
|
${JSON.stringify(e.details,null,2)}`:"";process.stderr.write(`Error (${e.code}): ${e.message}${t}
|
|
4
4
|
`)}function p(e,t,r){let n=f(e.type??"Element"),a=function(e,t){var r,n;let a=e.label?.trim(),i=e.value?.trim();if("text-field"===(r=t)||"text-view"===r||"search"===r){if(i)return i;if(a)return a}else if(a)return a;if(i)return i;let s=e.identifier?.trim();return!s||(n=s,/^[\w.]+:id\/[\w.-]+$/i.test(n)&&("group"===t||"image"===t||"list"===t||"collection"===t))?"":s}(e,n),i=" ".repeat(t),s=e.ref?`@${e.ref}`:"",o=[!1===e.enabled?"disabled":null].filter(Boolean).join(", "),l=o?` [${o}]`:"",c=a?` "${a}"`:"";return r?`${i}${s} [${n}]${l}`.trimEnd():`${i}${s} [${n}]${c}${l}`.trimEnd()}function f(e){let t=e.replace(/XCUIElementType/gi,"").toLowerCase(),r=e.includes(".")&&(e.startsWith("android.")||e.startsWith("androidx.")||e.startsWith("com."));switch(t.startsWith("ax")&&(t=t.replace(/^ax/,"")),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"}}let m=e.join(i.homedir(),".agent-device"),h=e.join(m,"daemon.json"),w=function(){let e=process.env.AGENT_DEVICE_DAEMON_TIMEOUT_MS;if(!e)return 6e4;let t=Number(e);return Number.isFinite(t)?Math.max(1e3,Math.floor(t)):6e4}();async function y(e){let t=await v(),r={...e,token:t.token};return await $(t,r)}async function v(){let e=g(),t=l();if(e&&e.version===t&&await b(e))return e;e&&(e.version!==t||!await b(e))&&a.existsSync(h)&&a.unlinkSync(h),await x();let r=Date.now();for(;Date.now()-r<5e3;){let e=g();if(e&&await b(e))return e;await new Promise(e=>setTimeout(e,100))}throw new o("COMMAND_FAILED","Failed to start daemon",{infoPath:h,hint:"Run pnpm build, or delete ~/.agent-device/daemon.json if stale."})}function g(){if(!a.existsSync(h))return null;try{let e=JSON.parse(a.readFileSync(h,"utf8"));if(!e.port||!e.token)return null;return e}catch{return null}}async function b(e){return new Promise(t=>{let r=s.createConnection({host:"127.0.0.1",port:e.port},()=>{r.destroy(),t(!0)});r.on("error",()=>{t(!1)})})}async function x(){let t=c(),r=e.join(t,"dist","src","daemon.js"),i=e.join(t,"src","daemon.ts"),s=a.existsSync(r);if(!s&&!a.existsSync(i))throw new o("COMMAND_FAILED","Daemon entry not found",{distPath:r,srcPath:i});let l=s?[r]:["--experimental-strip-types",i];n(process.execPath,l)}async function $(e,t){return new Promise((r,n)=>{let a=s.createConnection({host:"127.0.0.1",port:e.port},()=>{a.write(`${JSON.stringify(t)}
|
|
5
|
-
`)}),i=setTimeout(()=>{a.destroy(),n(new o("COMMAND_FAILED","Daemon request timed out",{timeoutMs:w}))},w),l="";a.setEncoding("utf8"),a.on("data",e=>{let t=(l+=e).indexOf("\n");if(-1===t)return;let s=l.slice(0,t).trim();if(s)try{let e=JSON.parse(s);a.end(),clearTimeout(i),r(e)}catch(e){clearTimeout(i),n(e)}}),a.on("error",e=>{clearTimeout(i),n(e)})})}async function S(r){let n=function(e){let t={json:!1,help:!1,version:!1},r=[];for(let n=0;n<e.length;n+=1){let a=e[n];if("--json"===a){t.json=!0;continue}if("--help"===a||"-h"===a){t.help=!0;continue}if("--version"===a||"-V"===a){t.version=!0;continue}if("--verbose"===a||"-v"===a){t.verbose=!0;continue}if("-i"===a){t.snapshotInteractiveOnly=!0;continue}if("-c"===a){t.snapshotCompact=!0;continue}if("--raw"===a){t.snapshotRaw=!0;continue}if("--no-record"===a){t.noRecord=!0;continue}if("--save-script"===a){t.saveScript=!0;continue}if("--update"===a||"-u"===a){t.replayUpdate=!0;continue}if("--user-installed"===a){t.appsFilter="user-installed";continue}if("--all"===a){t.appsFilter="all";continue}if("--metadata"===a){t.appsMetadata=!0;continue}if(a.startsWith("--backend")){let r=a.includes("=")?a.split("=")[1]:e[n+1];if(a.includes("=")||(n+=1),"ax"!==r&&"xctest"!==r)throw new o("INVALID_ARGS",`Invalid backend: ${r}`);t.snapshotBackend=r;continue}if(a.startsWith("--")){let[r,i]=a.split("="),s=i??e[n+1];switch(!i&&(n+=1),r){case"--platform":if("ios"!==s&&"android"!==s)throw new o("INVALID_ARGS",`Invalid platform: ${s}`);t.platform=s;break;case"--depth":{let e=Number(s);if(!Number.isFinite(e)||e<0)throw new o("INVALID_ARGS",`Invalid depth: ${s}`);t.snapshotDepth=Math.floor(e);break}case"--scope":t.snapshotScope=s;break;case"--device":t.device=s;break;case"--udid":t.udid=s;break;case"--serial":t.serial=s;break;case"--out":t.out=s;break;case"--session":t.session=s;break;case"--activity":t.activity=s;break;default:throw new o("INVALID_ARGS",`Unknown flag: ${r}`)}continue}if("-d"===a){let r=e[n+1];n+=1;let a=Number(r);if(!Number.isFinite(a)||a<0)throw new o("INVALID_ARGS",`Invalid depth: ${r}`);t.snapshotDepth=Math.floor(a);continue}if("-s"===a){let r=e[n+1];n+=1,t.snapshotScope=r;continue}r.push(a)}return{command:r.shift()??null,positionals:r,flags:t}}(r);n.flags.version&&(process.stdout.write(`${l()}
|
|
5
|
+
`)}),i=setTimeout(()=>{a.destroy(),n(new o("COMMAND_FAILED","Daemon request timed out",{timeoutMs:w}))},w),l="";a.setEncoding("utf8"),a.on("data",e=>{let t=(l+=e).indexOf("\n");if(-1===t)return;let s=l.slice(0,t).trim();if(s)try{let e=JSON.parse(s);a.end(),clearTimeout(i),r(e)}catch(e){clearTimeout(i),n(e)}}),a.on("error",e=>{clearTimeout(i),n(e)})})}async function S(r){let n=function(e){let t={json:!1,help:!1,version:!1},r=[];for(let n=0;n<e.length;n+=1){let a=e[n];if("--json"===a){t.json=!0;continue}if("--help"===a||"-h"===a){t.help=!0;continue}if("--version"===a||"-V"===a){t.version=!0;continue}if("--verbose"===a||"-v"===a){t.verbose=!0;continue}if("-i"===a){t.snapshotInteractiveOnly=!0;continue}if("-c"===a){t.snapshotCompact=!0;continue}if("--raw"===a){t.snapshotRaw=!0;continue}if("--no-record"===a){t.noRecord=!0;continue}if("--save-script"===a){t.saveScript=!0;continue}if("--relaunch"===a){t.relaunch=!0;continue}if("--update"===a||"-u"===a){t.replayUpdate=!0;continue}if("--user-installed"===a){t.appsFilter="user-installed";continue}if("--all"===a){t.appsFilter="all";continue}if("--metadata"===a){t.appsMetadata=!0;continue}if(a.startsWith("--backend")){let r=a.includes("=")?a.split("=")[1]:e[n+1];if(a.includes("=")||(n+=1),"ax"!==r&&"xctest"!==r)throw new o("INVALID_ARGS",`Invalid backend: ${r}`);t.snapshotBackend=r;continue}if(a.startsWith("--")){let[r,i]=a.split("="),s=i??e[n+1];switch(!i&&(n+=1),r){case"--platform":if("ios"!==s&&"android"!==s)throw new o("INVALID_ARGS",`Invalid platform: ${s}`);t.platform=s;break;case"--depth":{let e=Number(s);if(!Number.isFinite(e)||e<0)throw new o("INVALID_ARGS",`Invalid depth: ${s}`);t.snapshotDepth=Math.floor(e);break}case"--scope":t.snapshotScope=s;break;case"--device":t.device=s;break;case"--udid":t.udid=s;break;case"--serial":t.serial=s;break;case"--out":t.out=s;break;case"--session":t.session=s;break;case"--activity":t.activity=s;break;default:throw new o("INVALID_ARGS",`Unknown flag: ${r}`)}continue}if("-d"===a){let r=e[n+1];n+=1;let a=Number(r);if(!Number.isFinite(a)||a<0)throw new o("INVALID_ARGS",`Invalid depth: ${r}`);t.snapshotDepth=Math.floor(a);continue}if("-s"===a){let r=e[n+1];n+=1,t.snapshotScope=r;continue}r.push(a)}return{command:r.shift()??null,positionals:r,flags:t}}(r);n.flags.version&&(process.stdout.write(`${l()}
|
|
6
6
|
`),process.exit(0)),(n.flags.help||!n.command)&&(process.stdout.write(`agent-device <command> [args] [--json]
|
|
7
7
|
|
|
8
8
|
CLI to control iOS and Android devices for AI agents.
|
|
9
9
|
|
|
10
10
|
Commands:
|
|
11
11
|
boot Ensure target device/simulator is booted and ready
|
|
12
|
-
open [app]
|
|
12
|
+
open [app|url] Boot device/simulator; optionally launch app or deep link URL
|
|
13
13
|
close [app] Close app or just end session
|
|
14
14
|
reinstall <app> <path> Uninstall + install app from binary path
|
|
15
15
|
snapshot [-i] [-c] [-d <depth>] [-s <scope>] [--raw] [--backend ax|xctest]
|
|
@@ -62,11 +62,12 @@ Flags:
|
|
|
62
62
|
--device <name> Device name to target
|
|
63
63
|
--udid <udid> iOS device UDID
|
|
64
64
|
--serial <serial> Android device serial
|
|
65
|
-
--activity <component> Android
|
|
65
|
+
--activity <component> Android app launch activity (package/Activity); not for URL opens
|
|
66
66
|
--session <name> Named session
|
|
67
67
|
--verbose Stream daemon/runner logs
|
|
68
68
|
--json JSON output
|
|
69
69
|
--save-script Save session script (.ad) on close
|
|
70
|
+
--relaunch open: terminate app process before launching it
|
|
70
71
|
--no-record Do not record this action
|
|
71
72
|
--update, -u Replay: update selectors and rewrite replay file in place
|
|
72
73
|
--user-installed Apps: list user-installed packages (Android only)
|