agent-device 0.13.0 → 0.13.1
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 +4 -0
- package/dist/src/1974.js +2 -2
- package/dist/src/320.js +1 -1
- package/dist/src/3918.js +29 -28
- package/dist/src/7651.js +1 -1
- package/dist/src/8656.js +1 -0
- package/dist/src/9542.js +2 -2
- package/dist/src/bin.js +42 -42
- package/dist/src/contracts.d.ts +12 -1
- package/dist/src/contracts.js +1 -1
- package/dist/src/daemon.js +18 -15
- package/dist/src/index.d.ts +29 -1
- package/dist/src/metro-companion.js +1 -1
- package/dist/src/metro.d.ts +21 -0
- package/dist/src/metro.js +1 -1
- package/package.json +1 -1
- package/skills/agent-device/SKILL.md +2 -0
- package/skills/agent-device/references/bootstrap-install.md +26 -6
- package/skills/agent-device/references/exploration.md +11 -0
- package/skills/agent-device/references/remote-tenancy.md +39 -19
- package/skills/react-devtools/SKILL.md +2 -0
- package/dist/src/3883.js +0 -1
- package/dist/src/8164.js +0 -1
- package/dist/src/9323.js +0 -5
package/dist/src/index.d.ts
CHANGED
|
@@ -145,6 +145,7 @@ export declare type AgentDeviceClient = {
|
|
|
145
145
|
};
|
|
146
146
|
metro: {
|
|
147
147
|
prepare: (options: MetroPrepareOptions) => Promise<MetroPrepareResult>;
|
|
148
|
+
reload: (options?: MetroReloadOptions) => Promise<MetroReloadResult>;
|
|
148
149
|
};
|
|
149
150
|
capture: {
|
|
150
151
|
snapshot: (options?: CaptureSnapshotOptions) => Promise<CaptureSnapshotResult>;
|
|
@@ -1412,7 +1413,18 @@ declare type DaemonInstallSource = {
|
|
|
1412
1413
|
} | {
|
|
1413
1414
|
kind: 'path';
|
|
1414
1415
|
path: string;
|
|
1415
|
-
}
|
|
1416
|
+
} | ({
|
|
1417
|
+
kind: 'github-actions-artifact';
|
|
1418
|
+
owner: string;
|
|
1419
|
+
repo: string;
|
|
1420
|
+
} & ({
|
|
1421
|
+
artifactId: number;
|
|
1422
|
+
} | {
|
|
1423
|
+
runId: number;
|
|
1424
|
+
artifactName: string;
|
|
1425
|
+
} | {
|
|
1426
|
+
artifactName: string;
|
|
1427
|
+
}));
|
|
1416
1428
|
|
|
1417
1429
|
declare type DaemonLockPolicy = 'reject' | 'strip';
|
|
1418
1430
|
|
|
@@ -1933,6 +1945,15 @@ export declare type MetroPrepareOptions = {
|
|
|
1933
1945
|
|
|
1934
1946
|
export declare type MetroPrepareResult = PrepareMetroRuntimeResult;
|
|
1935
1947
|
|
|
1948
|
+
export declare type MetroReloadOptions = {
|
|
1949
|
+
metroHost?: string;
|
|
1950
|
+
metroPort?: number;
|
|
1951
|
+
bundleUrl?: string;
|
|
1952
|
+
timeoutMs?: number;
|
|
1953
|
+
};
|
|
1954
|
+
|
|
1955
|
+
export declare type MetroReloadResult = ReloadMetroResult;
|
|
1956
|
+
|
|
1936
1957
|
/** Re-export of {@link SessionRuntimeHints} under the Metro-specific alias used by public API consumers. */
|
|
1937
1958
|
declare type MetroRuntimeHints = SessionRuntimeHints;
|
|
1938
1959
|
|
|
@@ -2153,6 +2174,13 @@ declare type RefTarget_2 = {
|
|
|
2153
2174
|
selector?: never;
|
|
2154
2175
|
};
|
|
2155
2176
|
|
|
2177
|
+
declare type ReloadMetroResult = {
|
|
2178
|
+
reloaded: true;
|
|
2179
|
+
reloadUrl: string;
|
|
2180
|
+
status: number;
|
|
2181
|
+
body: string;
|
|
2182
|
+
};
|
|
2183
|
+
|
|
2156
2184
|
declare type RepeatedPressOptions = {
|
|
2157
2185
|
count?: number;
|
|
2158
2186
|
intervalMs?: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import e from"node:fs";import{setTimeout as r}from"node:timers/promises";import{normalizeBaseUrl as t,
|
|
1
|
+
import e from"node:fs";import{setTimeout as r}from"node:timers/promises";import{normalizeBaseUrl as t,ENV_REGISTER_PATH as s,ENV_STATE_PATH as o,ENV_LOCAL_BASE_URL as a,ENV_SESSION as n,ENV_UNREGISTER_PATH as i,ENV_BEARER_TOKEN as c,METRO_COMPANION_RUN_ARG as f,ENV_SERVER_BASE_URL as l,ENV_LAUNCH_URL as d,ENV_SCOPE_TENANT_ID as u,ENV_SCOPE_RUN_ID as m,ENV_SCOPE_LEASE_ID as p,REACT_DEVTOOLS_COMPANION_RUN_ARG as g,ENV_DEVICE_PORT as y}from"./320.js";function w(e,r){return{authorization:`Bearer ${r}`,"content-type":"application/json",...e.includes("ngrok")?{"ngrok-skip-browser-warning":"1"}:{}}}function h(e){return{...e.bridgeScope,...e.session?{session:e.session}:{},local_base_url:t(e.localBaseUrl),...e.devicePort?{device_port:e.devicePort}:{},...e.launchUrl?{launch_url:e.launchUrl}:{}}}async function b(e){let r,s,o=e.registerPath??"/api/metro/companion/register";try{r=await fetch(`${t(e.serverBaseUrl)}${o}`,{method:"POST",headers:w(e.serverBaseUrl,e.bearerToken),body:JSON.stringify(h(e)),signal:AbortSignal.timeout(5e3)})}catch(r){if(r instanceof Error&&"TimeoutError"===r.name)throw Error(`${o} timed out after 5000ms calling ${t(e.serverBaseUrl)}${o}`);throw r}let a=await r.text();try{s=a?JSON.parse(a):{}}catch{let e;throw Error(`Failed to register companion (${r.status}): invalid JSON response: ${(e=a.replaceAll(/\s+/g," ").trim()).length>300?`${e.slice(0,300)}...`:e}`)}if(!r.ok||!0!==s.ok||"string"!=typeof s.data?.ws_url)throw Error(`Failed to register companion (${r.status}): ${JSON.stringify(s)}`);return{wsUrl:s.data.ws_url}}async function v(e){let r=e.unregisterPath??null;if(r)try{await fetch(`${t(e.serverBaseUrl)}${r}`,{method:"POST",headers:w(e.serverBaseUrl,e.bearerToken),body:JSON.stringify(h(e)),signal:AbortSignal.timeout(2e3)})}catch(e){console.error(e instanceof Error?e.message:String(e))}}async function S(e){return"string"==typeof e?Buffer.from(e,"utf8"):e instanceof ArrayBuffer?Buffer.from(e):ArrayBuffer.isView(e)?Buffer.from(e.buffer,e.byteOffset,e.byteLength):"u">typeof Blob&&e instanceof Blob?Buffer.from(await e.arrayBuffer()):Buffer.from(String(e),"utf8")}async function E(e){return JSON.parse((await S(e.data)).toString("utf8"))}function I(e,r){1===e.readyState&&e.send(JSON.stringify(r))}async function B(e,r){1!==e.readyState&&await new Promise((t,s)=>{let o=()=>{i(),t()},a=()=>{i(),s(Error(`${r} WebSocket failed before opening.`))},n=()=>{i(),s(Error(`${r} WebSocket closed before opening.`))},i=()=>{e.removeEventListener("open",o),e.removeEventListener("error",a),e.removeEventListener("close",n)};e.addEventListener("open",o,{once:!0}),e.addEventListener("error",a,{once:!0}),e.addEventListener("close",n,{once:!0})})}async function k(e){e.readyState>=WebSocket.CLOSING||await new Promise(r=>{let t=()=>{s(),r()},s=()=>{e.removeEventListener("close",t),e.removeEventListener("error",t)};e.addEventListener("close",t,{once:!0}),e.addEventListener("error",t,{once:!0}),e.readyState>=WebSocket.CLOSING&&t()})}function L(e,r,t){try{e.close(1e3===r||r>=3e3&&r<=4999?r:3001,t)}catch{}}function U(r){return!r.statePath||e.existsSync(r.statePath)}async function $(e,r,s,o){var a,n;switch(r.type){case"ping":return void I(e,{type:"pong",timestamp:r.timestamp});case"http-request":try{let o=await fetch(new URL(r.path,`${t(s.localBaseUrl)}/`),{method:r.method,headers:r.headers,...r.bodyBase64?{body:Buffer.from(r.bodyBase64,"base64")}:{}}),a=Buffer.from(await o.arrayBuffer());I(e,{type:"http-response",requestId:r.requestId,status:o.status,headers:Object.fromEntries(o.headers.entries()),...a.length>0?{bodyBase64:a.toString("base64")}:{}})}catch(t){I(e,{type:"http-error",requestId:r.requestId,message:t instanceof Error?t.message:String(t)})}return;case"ws-open":{let n,i=new WebSocket((a=s.localBaseUrl,(n=new URL(r.path,`${t(a)}/`)).protocol="https:"===n.protocol?"wss:":"ws:",n.toString()));i.binaryType="arraybuffer";let c=!1;i.addEventListener("message",t=>{(async()=>{if(!c)return;let s=await S(t.data);I(e,{type:"ws-frame",streamId:r.streamId,dataBase64:s.toString("base64"),binary:"string"!=typeof t.data})})().catch(e=>{console.error(e instanceof Error?e.message:String(e))})}),i.addEventListener("close",t=>{o.delete(r.streamId),c&&I(e,{type:"ws-close",streamId:r.streamId,code:t.code,reason:t.reason})}),i.addEventListener("error",()=>{c&&I(e,{type:"ws-close",streamId:r.streamId,code:1011,reason:"Upstream WebSocket error."})}),o.set(r.streamId,i);try{await B(i,"Upstream"),c=!0,I(e,{type:"ws-open-result",streamId:r.streamId,success:!0,headers:{}})}catch(t){o.delete(r.streamId),L(i,1011,"open failed"),I(e,{type:"ws-open-result",streamId:r.streamId,success:!1,error:t instanceof Error?t.message:String(t)})}return}case"ws-frame":{let e=o.get(r.streamId);if(!e||1!==e.readyState)return;let t=Buffer.from(r.dataBase64,"base64");e.send(r.binary?t:t.toString("utf8"));return}case"ws-close":{let e=o.get(r.streamId);if(!e)return;o.delete(r.streamId),L(e,"number"==typeof(n=r.code)&&Number.isInteger(n)&&(1e3===n||n>=3e3&&n<=4999||n>=1001&&n<=1015&&1004!==n&&1005!==n&&1006!==n)?n:1011,r.reason??"bridge requested close");return}}}async function P(e){let t=new Map,s=!1,o=null,a=!1,n=()=>{s||(s=!0,a&&v(e).finally(()=>process.exit(0)),o&&L(o,1e3,"companion stopping"),setTimeout(()=>process.exit(0),900).unref())};process.once("SIGTERM",n),process.once("SIGINT",n);let i=setInterval(()=>{U(e)||process.exit(0)},250);for(i.unref();!s&&U(e);){let n=!1;try{a=!1;let r=await b(e);if(n=!0,a=!0,s||!U(e)){await v(e),n=!1,a=!1;break}let i=new WebSocket(r.wsUrl);o=i,i.binaryType="arraybuffer";try{await B(i,"Bridge"),i.addEventListener("message",r=>{(async()=>{let s=await E(r);await $(i,s,e,t)})().catch(e=>{console.error(e instanceof Error?e.message:String(e))})}),await k(i)}finally{o=null,a=!1,t.forEach(e=>L(e,1012,"bridge disconnected")),t.clear(),n&&(await v(e),n=!1)}}catch(r){if(o=null,a=!1,n&&(await v(e),n=!1),s||!U(e))break;console.error(r instanceof Error?r.message:String(r))}if(s||!U(e))break;await r(1e3)}clearInterval(i)}(async function(e,r){let t=function(e,r){let t=e[0];if(t!==f&&t!==g)return null;let w=r[l]?.trim(),h=r[c]?.trim(),b=r[a]?.trim();if(!w||!h||!b)throw Error("Metro companion worker is missing required environment configuration.");let v=r[u]?.trim(),S=r[m]?.trim(),E=r[p]?.trim();if(!v||!S||!E)throw Error("Metro companion worker is missing required bridge scope configuration.");return{serverBaseUrl:w,bearerToken:h,localBaseUrl:b,bridgeScope:{tenantId:v,runId:S,leaseId:E},launchUrl:r[d]?.trim()||void 0,statePath:r[o]?.trim()||void 0,registerPath:r[s]?.trim()||void 0,unregisterPath:r[i]?.trim()||void 0,devicePort:function(e){if(!e?.trim())return;let r=Number.parseInt(e,10);if(!Number.isInteger(r)||r<1||r>65535)throw Error("Companion worker received invalid device port configuration.");return r}(r[y]),session:r[n]?.trim()||void 0}}(e,r);return!!t&&(await P(t),!0)})(process.argv.slice(2),process.env).catch(e=>{if(e instanceof Error&&e.message.includes("missing required environment")){console.error(e.message),process.exitCode=1;return}console.error(e instanceof Error?e.stack??e.message:String(e)),process.exitCode=1});
|
package/dist/src/metro.d.ts
CHANGED
|
@@ -187,6 +187,27 @@ export declare type PrepareRemoteMetroResult = {
|
|
|
187
187
|
logPath: string;
|
|
188
188
|
};
|
|
189
189
|
|
|
190
|
+
declare type ReloadMetroOptions = {
|
|
191
|
+
metroHost?: string;
|
|
192
|
+
metroPort?: number | string;
|
|
193
|
+
bundleUrl?: string;
|
|
194
|
+
runtime?: MetroRuntimeHints;
|
|
195
|
+
timeoutMs?: number | string;
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
declare type ReloadMetroResult = {
|
|
199
|
+
reloaded: true;
|
|
200
|
+
reloadUrl: string;
|
|
201
|
+
status: number;
|
|
202
|
+
body: string;
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
export declare function reloadRemoteMetro(options?: ReloadRemoteMetroOptions): Promise<ReloadRemoteMetroResult>;
|
|
206
|
+
|
|
207
|
+
export declare type ReloadRemoteMetroOptions = ReloadMetroOptions;
|
|
208
|
+
|
|
209
|
+
export declare type ReloadRemoteMetroResult = ReloadMetroResult;
|
|
210
|
+
|
|
190
211
|
export declare function resolveRuntimeTransport(runtime: SessionRuntimeHints | undefined): {
|
|
191
212
|
host: string;
|
|
192
213
|
port: number;
|
package/dist/src/metro.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{buildMetroRuntimeHints as e,ensureMetroCompanion as t,
|
|
1
|
+
import{buildMetroRuntimeHints as e,ensureMetroCompanion as t,reloadMetro as r,stopMetroCompanion as o,prepareMetroRuntime as n}from"./1974.js";import{resolveRuntimeTransportHints as i}from"./8656.js";function s(e){return i(e)}async function a(e){let t=await n({projectRoot:e.projectRoot,kind:e.kind,publicBaseUrl:e.publicBaseUrl,proxyBaseUrl:e.proxyBaseUrl,proxyBearerToken:e.proxyBearerToken,bridgeScope:e.bridgeScope,launchUrl:e.launchUrl,companionProfileKey:e.profileKey,companionConsumerKey:e.consumerKey,metroPort:e.port,listenHost:e.listenHost,statusHost:e.statusHost,startupTimeoutMs:e.startupTimeoutMs,probeTimeoutMs:e.probeTimeoutMs,reuseExisting:e.reuseExisting,installDependenciesIfNeeded:e.installDependenciesIfNeeded,runtimeFilePath:e.runtimeFilePath,logPath:e.logPath,env:e.env});return{iosRuntime:t.iosRuntime,androidRuntime:t.androidRuntime,bridge:t.bridge,started:t.started,reused:t.reused,logPath:t.logPath}}async function u(e){let r=await t(e);return{pid:r.pid,started:r.spawned,logPath:r.logPath}}async function l(e){await o(e)}async function d(e={}){return await r(e)}function p(t){return e(t,"ios")}function m(t){return e(t,"android")}export{buildBundleUrl,normalizeBaseUrl}from"./320.js";export{m as buildAndroidRuntimeHints,p as buildIosRuntimeHints,u as ensureMetroTunnel,a as prepareRemoteMetro,d as reloadRemoteMetro,s as resolveRuntimeTransport,l as stopMetroTunnel};
|
package/package.json
CHANGED
|
@@ -16,6 +16,7 @@ Use this skill as a router with mandatory defaults. Read this file first. For no
|
|
|
16
16
|
- Prefer `diff snapshot` after a nearby mutation when you only need to know what changed.
|
|
17
17
|
- Avoid speculative mutations. You may take the smallest reversible UI action needed to unblock inspection or complete the requested task, such as dismissing a popup, closing an alert, or clearing an unintended surface.
|
|
18
18
|
- In React Native dev or debug builds, check early for visible warning or error overlays, tooltips, and toasts that can steal focus or intercept taps. If they are not part of the requested behavior, dismiss them and continue. If you saw them, report them in the final summary.
|
|
19
|
+
- In Metro-backed React Native dev loops, use `agent-device metro reload` for a JS app reload before falling back to `open <app> --relaunch`. It mirrors pressing `r` in the Metro terminal and preserves the native app process.
|
|
19
20
|
- Do not browse the web or use external sources unless the user explicitly asks.
|
|
20
21
|
- Re-snapshot after meaningful UI changes instead of reusing stale refs.
|
|
21
22
|
- Treat refs in default snapshot output as actionable-now, not durable identities. If a target appears only in an off-screen summary, use `scroll <direction>` and re-snapshot until the target is visible.
|
|
@@ -60,6 +61,7 @@ Use this skill as a router with mandatory defaults. Read this file first. For no
|
|
|
60
61
|
- If there is no simulator, no app install, or no open app session yet, switch to `bootstrap-install.md` instead of improvising setup steps.
|
|
61
62
|
- Use the smallest unblock action first when transient UI blocks inspection, but do not navigate, search, or enter new text just to make the UI reveal data unless the user asked for that interaction.
|
|
62
63
|
- In React Native dev or debug apps, treat visible warning or error overlays as transient blockers unless the user is explicitly asking you to diagnose them. Dismiss them when safe, then continue the requested flow.
|
|
64
|
+
- For React Native code changes where the app is already connected to Metro, prefer `agent-device metro reload`, then wait and re-snapshot. Use `open <app> --relaunch` only when Metro reload does not reconnect or native startup state must reset.
|
|
63
65
|
- Do not use external lookups to compensate for missing on-screen data unless the user asked for them.
|
|
64
66
|
- If the needed information is not exposed on screen, say that plainly instead of compensating with extra navigation, text entry, or web search.
|
|
65
67
|
- Prefer `@ref` or selector targeting over raw coordinates.
|
|
@@ -18,6 +18,7 @@ Use this exact order when you are not sure about the installed app identifier. O
|
|
|
18
18
|
|
|
19
19
|
- `install` or `reinstall`
|
|
20
20
|
- `install-from-source` when the artifact already exists at a URL the daemon can reach
|
|
21
|
+
- `install-from-source --github-actions-artifact` when a compatible remote daemon should resolve a GitHub Actions artifact
|
|
21
22
|
|
|
22
23
|
## Most common mistake to avoid
|
|
23
24
|
|
|
@@ -35,8 +36,6 @@ After setup is confirmed or completed, move to `exploration.md` before doing UI
|
|
|
35
36
|
- If `open <app>` fails, run `agent-device apps` and retry with a discovered app name before considering install steps.
|
|
36
37
|
- Do not install or reinstall on the first attempt unless the user explicitly asks for installation or provides a concrete artifact path or URL.
|
|
37
38
|
- When installation is required from a known location, prefer a checked-in shell script or other deterministic bootstrap command over ad hoc path guessing.
|
|
38
|
-
|
|
39
|
-
- If `open <app>` fails, or you are not sure which app name is available on the target, run `agent-device apps` first and choose from the discovered app list instead of guessing.
|
|
40
39
|
- Use `apps --platform <platform>` together with `--device`, `--udid`, or `--serial` when target selection matters.
|
|
41
40
|
- Once you have the correct app name, retry `open` with that exact discovered value.
|
|
42
41
|
|
|
@@ -64,8 +63,27 @@ agent-device install com.example.app ./build/MyApp.app --platform ios --device "
|
|
|
64
63
|
```bash
|
|
65
64
|
ARTIFACT_URL="<trusted-artifact-url>"
|
|
66
65
|
agent-device install-from-source "$ARTIFACT_URL" --platform android
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Daemon-resolved GitHub Actions artifacts:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
agent-device install-from-source \
|
|
72
|
+
--github-actions-artifact ORG/REPO:1234567890 \
|
|
73
|
+
--platform android
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Project config can provide an artifact name instead:
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"platform": "android",
|
|
81
|
+
"installSource": {
|
|
82
|
+
"type": "github-actions-artifact",
|
|
83
|
+
"repo": "ORG/REPO",
|
|
84
|
+
"artifact": "app-debug"
|
|
85
|
+
}
|
|
86
|
+
}
|
|
69
87
|
```
|
|
70
88
|
|
|
71
89
|
## Install guidance
|
|
@@ -73,6 +91,7 @@ agent-device install-from-source "$GITHUB_ARTIFACT_URL" --platform ios --header
|
|
|
73
91
|
- Use `install <app> <path>` when the app may already be installed and you do not need a fresh-state reset.
|
|
74
92
|
- Use `reinstall <app> <path>` when you explicitly need uninstall plus install as one deterministic step.
|
|
75
93
|
- Use `install-from-source <url>` only when an existing artifact URL is trusted, operator-approved, and reachable by the daemon.
|
|
94
|
+
- Use `--github-actions-artifact <org>/<repo>:<artifact>` when a compatible remote daemon should resolve a GitHub Actions artifact. Numeric artifacts are IDs; non-numeric artifacts are names.
|
|
76
95
|
- Local `.apk`, `.aab`, `.app`, and `.ipa` paths go through `install` or `reinstall`; existing reachable URLs go through `install-from-source`.
|
|
77
96
|
- Do not download, re-zip, publish temporary GitHub releases, or move CI artifacts elsewhere just to make an install command work.
|
|
78
97
|
- Keep install and open as separate phases. Do not turn them into one default command flow.
|
|
@@ -80,10 +99,10 @@ agent-device install-from-source "$GITHUB_ARTIFACT_URL" --platform ios --header
|
|
|
80
99
|
- Android: `.apk` and `.aab`
|
|
81
100
|
- iOS: `.app` and `.ipa`
|
|
82
101
|
- Android URL sources can be direct `.apk` or `.aab` files.
|
|
83
|
-
- Trusted artifact service URLs
|
|
102
|
+
- Trusted artifact service URLs may point at archive-backed downloads that contain one installable artifact. Prefer `--github-actions-artifact` for GitHub Actions artifacts that a compatible remote daemon can resolve with its own credentials.
|
|
84
103
|
- If a trusted artifact archive contains multiple installables, stop and ask for the intended artifact instead of guessing.
|
|
85
104
|
- `.aab` still requires `bundletool` in `PATH`, or `AGENT_DEVICE_BUNDLETOOL_JAR=<absolute-path-to-bundletool-all.jar>` with `java` in `PATH`, when the daemon installs the materialized artifact.
|
|
86
|
-
- For
|
|
105
|
+
- For `.ipa` archives with multiple app bundles, `<app>` is the bundle id or bundle name selection hint.
|
|
87
106
|
- After install or reinstall, later use `open <app>` with the exact discovered or known package/bundle identifier, not the artifact path.
|
|
88
107
|
|
|
89
108
|
## Choose the right starting point
|
|
@@ -115,6 +134,7 @@ agent-device --session auth snapshot -i
|
|
|
115
134
|
- Use semantic session names when you need multiple concurrent runs.
|
|
116
135
|
- Use `--save-script=<path>` on `close` when you want to keep a replay script.
|
|
117
136
|
- For dev loops where state can linger, prefer `open <app> --relaunch`.
|
|
137
|
+
- For Metro-backed React Native JS changes with the app already running, prefer `metro reload` instead of `open <app> --relaunch`; it asks Metro to reload connected apps without restarting the native process.
|
|
118
138
|
- In iOS sessions, use `open <app>` for the app itself. Use `open <url>` for deep links, and `open <app> <url>` when you need to launch the app and deep link in one step.
|
|
119
139
|
- On iOS, `appstate` is session-scoped and requires the matching active session on the target device.
|
|
120
140
|
|
|
@@ -21,6 +21,7 @@ Open this file when the app or screen is already running and you need to discove
|
|
|
21
21
|
- User asks for exact text from a known target: `get text`
|
|
22
22
|
- User asks you to tap, type, or choose an element: `snapshot -i`, then act
|
|
23
23
|
- User asks for the React Native component tree, props/state/hooks, or render profiling: use `agent-device react-devtools ...` and the `skills/react-devtools` workflow
|
|
24
|
+
- User asks to reload a Metro-backed React Native app after JS changes: `agent-device metro reload`, then wait briefly and re-run `snapshot` or `snapshot -i`
|
|
24
25
|
- React Native dev or debug build shows warning/error UI: capture enough evidence to identify it, dismiss it if it is not the requested behavior, then continue the flow and report it in the summary
|
|
25
26
|
- The on-screen keyboard is blocking the next step: `keyboard dismiss`; on iOS do this only while an app session is active, and use `keyboard status|get` only on Android
|
|
26
27
|
- UI does not expose the answer: say so plainly; do not browse or force the app into a new state unless asked
|
|
@@ -60,6 +61,16 @@ Open this file when the app or screen is already running and you need to discove
|
|
|
60
61
|
- Blocking or recurring: switch to [debugging.md](debugging.md) and collect evidence.
|
|
61
62
|
- Seen at any point: mention in the final summary even if dismissed.
|
|
62
63
|
|
|
64
|
+
**React Native Metro reload.** When a dev app is already running and connected to Metro, prefer a Metro reload over restarting the native app process:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
agent-device metro reload
|
|
68
|
+
agent-device wait 1000
|
|
69
|
+
agent-device snapshot -i
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Use `--metro-host`, `--metro-port`, or `--bundle-url` only when the active connection does not already carry the right runtime hints. Fall back to `open <app> --relaunch` when the app is not connected to Metro, Metro reload fails, or native startup state needs a clean process.
|
|
73
|
+
|
|
63
74
|
## Common example loops
|
|
64
75
|
|
|
65
76
|
These are examples, not required exact sequences. Adapt them to the app, state, and task at hand.
|
|
@@ -8,7 +8,9 @@ Open this file for remote daemon HTTP flows that let an agent running in a Linux
|
|
|
8
8
|
|
|
9
9
|
- `agent-device connect --remote-config <path>`
|
|
10
10
|
- `agent-device install-from-source <url> --remote-config <path> --platform android`
|
|
11
|
+
- `agent-device install-from-source --github-actions-artifact <org>/<repo>:<artifact> --remote-config <path> --platform android`
|
|
11
12
|
- `agent-device open <package> --remote-config <path> --relaunch`
|
|
13
|
+
- `agent-device metro reload --remote-config <path>`
|
|
12
14
|
- `agent-device snapshot --remote-config <path> -i`
|
|
13
15
|
- `agent-device disconnect --remote-config <path>`
|
|
14
16
|
- `agent-device connection status`
|
|
@@ -36,6 +38,7 @@ agent-device connect --remote-config ./remote-config.json
|
|
|
36
38
|
ARTIFACT_URL="<trusted-artifact-url>"
|
|
37
39
|
agent-device install-from-source "$ARTIFACT_URL" --platform android
|
|
38
40
|
agent-device open com.example.app --relaunch
|
|
41
|
+
agent-device metro reload
|
|
39
42
|
agent-device snapshot -i
|
|
40
43
|
agent-device fill @e3 "test@example.com"
|
|
41
44
|
agent-device disconnect
|
|
@@ -73,6 +76,7 @@ The first command that needs a lease or Metro runtime prepares and persists it.
|
|
|
73
76
|
- `connect` stores local non-secret connection state and defers tenant lease allocation plus Metro preparation until a later command needs them.
|
|
74
77
|
- Commands such as `install-from-source`, `open`, `snapshot`, and `apps` allocate or refresh the lease when needed.
|
|
75
78
|
- `open` prepares Metro runtime hints when the remote profile has Metro fields and no compatible runtime is already saved.
|
|
79
|
+
- `metro reload` reuses saved Metro runtime hints and asks Metro to reload connected React Native apps without restarting the native process.
|
|
76
80
|
- `batch` also prepares Metro when any step opens an app and that step does not provide its own runtime.
|
|
77
81
|
- `disconnect` closes the session when possible, stops the Metro companion owned by the connection, releases the lease when one was allocated, and removes local connection state.
|
|
78
82
|
|
|
@@ -82,12 +86,11 @@ Remote install examples:
|
|
|
82
86
|
agent-device install com.example.app ./app.apk
|
|
83
87
|
ARTIFACT_URL="<trusted-artifact-url>"
|
|
84
88
|
agent-device install-from-source "$ARTIFACT_URL" --platform android
|
|
85
|
-
GITHUB_ARTIFACT_URL="<trusted-github-actions-artifact-api-url>"
|
|
86
|
-
agent-device install-from-source "$GITHUB_ARTIFACT_URL" --platform ios --header "authorization: Bearer TOKEN"
|
|
87
89
|
```
|
|
88
90
|
|
|
89
91
|
- Use `install` or `reinstall` for local paths; remote daemons upload local artifacts automatically.
|
|
90
92
|
- Use `install-from-source` only for trusted, operator-approved artifact URLs the remote daemon can reach. Do not fetch arbitrary user-supplied URLs.
|
|
93
|
+
- Use `install-from-source --github-actions-artifact <org>/<repo>:<artifact>` when the remote daemon has repository credentials and supports daemon-resolved GitHub Actions artifacts.
|
|
91
94
|
- For local-path versus URL artifact rules, follow [bootstrap-install.md](bootstrap-install.md).
|
|
92
95
|
|
|
93
96
|
Use `agent-device connection status --session adc-android` to inspect the active connection without reading JSON state manually. Status output must not include auth tokens.
|
|
@@ -135,30 +138,47 @@ Optional overrides stay available for advanced cases:
|
|
|
135
138
|
- Start the daemon in HTTP mode with `AGENT_DEVICE_DAEMON_SERVER_MODE=http|dual` on the host.
|
|
136
139
|
- Point the profile or env at the remote host with `daemonBaseUrl` or `AGENT_DEVICE_DAEMON_BASE_URL=http(s)://host:port[/base-path]`.
|
|
137
140
|
- For non-loopback remote hosts, set `AGENT_DEVICE_DAEMON_AUTH_TOKEN` or `--daemon-auth-token`. The client rejects non-loopback remote daemon URLs without auth.
|
|
138
|
-
- Direct JSON-RPC callers can authenticate with request params, `Authorization: Bearer <token>`, or `x-agent-device-token`.
|
|
139
141
|
- Prefer an auth hook such as `AGENT_DEVICE_HTTP_AUTH_HOOK` when the host needs caller validation or tenant injection.
|
|
140
142
|
|
|
141
|
-
##
|
|
143
|
+
## Lease debug fallback
|
|
142
144
|
|
|
143
|
-
The main agent flow should use `connect
|
|
145
|
+
The main agent flow should use `connect` and `connection status`. For daemon-side auth, scope, or lease debugging, inspect host-side daemon logs and operator tooling instead of issuing raw daemon RPC from the agent shell.
|
|
146
|
+
|
|
147
|
+
## GitHub Actions artifact install
|
|
148
|
+
|
|
149
|
+
Use this when a compatible remote daemon resolves GitHub Actions artifacts server-side. Do not download CI artifacts locally or add a local `GITHUB_TOKEN` just to install CI output.
|
|
150
|
+
|
|
151
|
+
Artifact ID shape:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
agent-device install-from-source \
|
|
155
|
+
--github-actions-artifact OWNER/REPO:1234567890 \
|
|
156
|
+
--remote-config ./remote-config.json \
|
|
157
|
+
--platform android
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Artifact-name shape:
|
|
144
161
|
|
|
145
162
|
```bash
|
|
146
|
-
|
|
147
|
-
-
|
|
148
|
-
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
163
|
+
agent-device install-from-source \
|
|
164
|
+
--github-actions-artifact OWNER/REPO:app-debug \
|
|
165
|
+
--remote-config ./remote-config.json \
|
|
166
|
+
--platform ios
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Config shape:
|
|
170
|
+
|
|
171
|
+
```json
|
|
172
|
+
{
|
|
173
|
+
"installSource": {
|
|
174
|
+
"type": "github-actions-artifact",
|
|
175
|
+
"repo": "OWNER/REPO",
|
|
176
|
+
"artifact": "app-debug"
|
|
177
|
+
}
|
|
178
|
+
}
|
|
159
179
|
```
|
|
160
180
|
|
|
161
|
-
|
|
181
|
+
Numeric artifacts are passed as artifact IDs. Non-numeric artifacts are passed as artifact names.
|
|
162
182
|
|
|
163
183
|
## Failure semantics and trust notes
|
|
164
184
|
|
|
@@ -44,6 +44,8 @@ agent-device react-devtools profile rerenders --limit 5
|
|
|
44
44
|
- Labels like `@c5` reset when the app reloads or components remount. After reload, run `wait --connected` and inspect again.
|
|
45
45
|
- Profiling only captures renders between `profile start` and `profile stop`.
|
|
46
46
|
- On Android, set `adb reverse tcp:8097 tcp:8097` for React DevTools. If Metro is local, also set `adb reverse tcp:8081 tcp:8081`.
|
|
47
|
+
- For Android sessions connected through `agent-device connect --remote-config`, run `agent-device react-devtools ...` normally. The CLI registers a bridge companion tunnel to the local DevTools daemon on `127.0.0.1:8097` and unregisters it when the command exits.
|
|
48
|
+
- Remote Android React DevTools assumes the React Native-bundled DevTools behavior in React Native 0.83+. Do not assume older browser/Chromium DevTools workflows exist in remote sandboxes. For Expo apps, verify the SDK's bundled React Native version and runtime behavior first; no Expo SDK version is separately verified by this skill.
|
|
47
49
|
|
|
48
50
|
## References
|
|
49
51
|
|
package/dist/src/3883.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{runCmdSync as e}from"./9818.js";import{sleep as t}from"./4829.js";let r=[/(^|[/\s"'=])dist\/src\/daemon\.js($|[\s"'])/,/(^|[/\s"'=])src\/daemon\.ts($|[\s"'])/];function n(e){if(!Number.isInteger(e)||e<=0)return!1;try{return process.kill(e,0),!0}catch(e){return"EPERM"===e.code}}function i(t){if(!Number.isInteger(t)||t<=0)return null;try{let r=e("ps",["-p",String(t),"-o","lstart="],{allowFailure:!0,timeoutMs:1e3});if(0!==r.exitCode)return null;let n=r.stdout.trim();return n.length>0?n:null}catch{return null}}function o(t){if(!Number.isInteger(t)||t<=0)return null;try{let r=e("ps",["-p",String(t),"-o","command="],{allowFailure:!0,timeoutMs:1e3});if(0!==r.exitCode)return null;let n=r.stdout.trim();return n.length>0?n:null}catch{return null}}function l(e,t){let l;if(!n(e))return!1;if(t){let r=i(e);if(!r||r!==t)return!1}let s=o(e);return!!s&&!!(l=s.toLowerCase().replaceAll("\\","/")).includes("agent-device")&&r.some(e=>e.test(l))}function s(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,r){if(!n(e))return!0;let i=Date.now();for(;Date.now()-i<r;)if(await t(50),!n(e))return!0;return!n(e)}async function c(e,t){!l(e,t.expectedStartTime)||!s(e,"SIGTERM")||await u(e,t.termTimeoutMs)||s(e,"SIGKILL")&&await u(e,t.killTimeoutMs)}export{l as isAgentDeviceDaemonProcess,n as isProcessAlive,o as readProcessCommand,i as readProcessStartTime,c as stopProcessForTakeover,u as waitForProcessExit};
|
package/dist/src/8164.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{promises as t}from"node:fs";import o from"node:os";import e from"node:path";let r=["emulator","platform-tools",e.join("cmdline-tools","latest","bin"),e.join("cmdline-tools","tools","bin")];function n(t){let o=new Set,e=[];for(let r of t){let t=r.trim();!t||o.has(t)||(o.add(t),e.push(t))}return e}function i(t=process.env){let r=t.ANDROID_SDK_ROOT?.trim(),l=t.ANDROID_HOME?.trim(),s=t.HOME?.trim()||o.homedir();return n([r??"",l??"",s?e.join(s,"Android","Sdk"):""])}async function l(o){try{return await t.access(o,t.constants.X_OK),!0}catch{return!1}}async function s(t=process.env){let o,m=[];for(let n of i(t)){let t=[];for(let o of r){let r=e.join(n,o);await l(r)&&t.push(r)}0!==t.length&&(o||(o=n),m.push(...t))}if(o&&(t.ANDROID_SDK_ROOT=t.ANDROID_SDK_ROOT?.trim()||o,t.ANDROID_HOME=t.ANDROID_HOME?.trim()||o),0===m.length)return;let d=(t.PATH??"").split(e.delimiter).map(t=>t.trim()).filter(t=>t.length>0);t.PATH=n([...m,...d]).join(e.delimiter)}export{s as ensureAndroidSdkPathConfigured,i as resolveAndroidSdkRoots};
|
package/dist/src/9323.js
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import{URL as t}from"node:url";import{whichCmd as e,runCmd as n}from"./9818.js";import{asAppError as a,AppError as r}from"./9152.js";import{ensureAndroidSdkPathConfigured as i}from"./8164.js";import"./4829.js";function o(t,e){return["-s",t.id,...e]}async function s(){if(await i(),!await e("adb"))throw new r("TOOL_MISSING","adb not found in PATH")}function l(t,e){let n=`${t}
|
|
2
|
-
${e}`.toLowerCase();return n.includes("no shell command implementation")||n.includes("unknown command")}let u=/\.(?:apk|aab)$/i,d=/^[A-Za-z_][\w]*(\.[A-Za-z_][\w]*)+$/;function c(t){var e,n;let a=t.trim();return 0===a.length?"other":u.test(a)?a.includes("/")||a.includes("\\")||a.startsWith(".")||a.startsWith("~")||(e=a,!d.test(e))?"binary":"package":(n=a,d.test(n))?"package":"other"}function p(t){return`Android runtime hints require an installed package name, not "${t}". Install or reinstall the app first, then relaunch by package.`}let m=["AGENT_DEVICE_IOS_SIMULATOR_DEVICE_SET","IOS_SIMULATOR_DEVICE_SET"],f=["AGENT_DEVICE_ANDROID_DEVICE_ALLOWLIST","ANDROID_DEVICE_ALLOWLIST"];function h(t){return t?.trim()||void 0}function w(t,e){for(let n of t){let t=h(e[n]);if(t)return t}}function _(t,e=process.env){return h(t)??w(m,e)}function A(t){return new Set(t.split(/[\s,]+/).map(t=>t.trim()).filter(Boolean))}function g(t,e=process.env){let n=h(t)??w(f,e);if(n)return A(n)}function b(t,e={}){let n=_(e.simulatorSetPath);return n?["simctl","--set",n,...t]:["simctl",...t]}function S(t,e){return"ios"!==t.platform||"simulator"!==t.kind?["simctl",...e]:b(e,{simulatorSetPath:t.simulatorSetPath})}let I="shared_prefs/ReactNativeDevPrefs.xml",v="debug_http_host",C="dev_server_https",$="RCT_jsLocation",E="RCT_packager_scheme",R="React Native runtime hints require adb run-as access to the app sandbox. Verify the app is debuggable and the selected package/device are correct.",k='<?xml version="1.0" encoding="utf-8" standalone="yes" ?>\n<map>\n</map>\n';function y(t){return void 0!==D(t)}function D(e){if(!e)return;let n=K(e.metroHost),a=q(e.metroPort),i="http",o=K(e.bundleUrl);if(o){var s;let e;try{e=new t(o)}catch(t){throw new r("INVALID_ARGS",`Invalid runtime bundle URL: ${o}`,{},t)}("http:"===e.protocol||"https:"===e.protocol)&&(n??=K(e.hostname),a??=q(e.port.length>0?Number(e.port):"https:"===(s=e.protocol)?443:"http:"===s?80:void 0),i="https:"===e.protocol?"https":"http")}if(n&&a)return{host:n,port:a,scheme:i}}async function N(t){let{device:e,appId:n,runtime:a}=t;if(!n)return;let r=D(a);if(r){if("android"===e.platform)return void await L(e,n,r);"ios"===e.platform&&"simulator"===e.kind&&await P(e,n,r)}}async function x(t){let{device:e,appId:n}=t;if(n){if("android"===e.platform)return void await T(e,n);"ios"===e.platform&&"simulator"===e.kind&&await F(e,n)}}async function L(t,e,n){var a,r,i,o,s,l;let u,d;G(e);let c=(a=await O(t,e),r=v,i=`${n.host}:${n.port}`,u=` <string name="${W(r)}">${W(i)}</string>`,V(U(a,r),u));o=c,s=C,l="https"===n.scheme,d=` <boolean name="${W(s)}" value="${l?"true":"false"}" />`,c=V(U(o,s),d),await M(t,e,c)}async function T(t,e){G(e);let n=await O(t,e),a=U(n,v),r=U(a,C);r!==n&&await M(t,e,r)}async function O(t,e){let a=await n("adb",o(t,["shell","run-as",e,"cat",I]),{allowFailure:!0});return 0!==a.exitCode?k:H(a.stdout)}async function M(t,e,i){let s=o(t,["shell","run-as",e,"id"]),l=await n("adb",s,{allowFailure:!0});if(0!==l.exitCode){let t=z(l.stdout,l.stderr);throw new r("COMMAND_FAILED",t?`Failed to access Android app sandbox for ${e}`:`Failed to probe Android app sandbox for ${e}`,{package:e,cmd:"adb",args:s,stdout:l.stdout,stderr:l.stderr,exitCode:l.exitCode,hint:t?R:"adb shell run-as probe failed. Check adb connectivity and that the device is reachable. Inspect stderr/details for more information."})}try{await n("adb",o(t,["shell","run-as",e,"mkdir","-p","shared_prefs"])),await n("adb",o(t,["shell","run-as",e,"tee",I]),{stdin:i.trimEnd()})}catch(i){let t=a(i);if("TOOL_MISSING"===t.code)throw t;let n=z("string"==typeof t.details?.stdout?t.details.stdout:"","string"==typeof t.details?.stderr?t.details.stderr:"");throw new r("COMMAND_FAILED",n?`Failed to access Android app sandbox for ${e}`:`Failed to write Android runtime hints for ${e}`,{...t.details??{},package:e,cmd:"adb",phase:"write-runtime-hints",hint:n?R:"adb run-as succeeded, but writing ReactNativeDevPrefs.xml failed. Inspect stderr/details for the failing shell command."},t)}}async function P(t,e,a){await n("xcrun",S(t,["spawn",t.id,"defaults","write",e,$,"-string",`${a.host}:${a.port}`])),await n("xcrun",S(t,["spawn",t.id,"defaults","write",e,E,"-string",a.scheme]))}async function F(t,e){await n("xcrun",S(t,["spawn",t.id,"defaults","delete",e,$]),{allowFailure:!0}),await n("xcrun",S(t,["spawn",t.id,"defaults","delete",e,E]),{allowFailure:!0})}function H(t){let e=t.trim();return e.includes("<map")&&e.includes("</map>")?`${e}
|
|
3
|
-
`:k}function V(t,e){return H(t).replace("</map>",`${e}
|
|
4
|
-
</map>`)}function U(t,e){let n=e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return H(t).replace(RegExp(`^\\s*<string name="${n}">[\\s\\S]*?<\\/string>\\n?`,"m"),"").replace(RegExp(`^\\s*<boolean name="${n}" value="(?:true|false)"\\s*\\/?>\\n?`,"m"),"")}function K(t){let e=t?.trim();return e&&e.length>0?e:void 0}function G(t){if("binary"!==c(t))return;let e=p(t);throw new r("INVALID_ARGS",e,{package:t,hint:e})}function q(t){if(Number.isInteger(t)&&!(t<=0)&&!(t>65535))return t}function W(t){return t.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'")}function z(t,e){let n=`${t}
|
|
5
|
-
${e}`.toLowerCase();return["run-as: package not debuggable","run-as: permission denied","run-as: package is unknown","run-as: unknown package","is unknown","is not an application","could not set capabilities"].some(t=>n.includes(t))}export{o as adbArgs,N as applyRuntimeHintsToApp,b as buildSimctlArgs,S as buildSimctlArgsForDevice,c as classifyAndroidAppTarget,x as clearRuntimeHintsFromApp,s as ensureAdb,p as formatAndroidInstalledPackageRequiredMessage,y as hasRuntimeTransportHints,l as isClipboardShellUnsupported,A as parseSerialAllowlist,g as resolveAndroidSerialAllowlist,_ as resolveIosSimulatorDeviceSetPath,D as resolveRuntimeTransportHints,K as trimRuntimeValue};
|