diff --git a/package.json b/package.json index 955ae014db..c0a5ad7e4f 100644 --- a/package.json +++ b/package.json @@ -196,8 +196,6 @@ "!android/ime-helper/dist/*.idsig", "!android/ime-helper/README.md", "linux/atspi-dump.py", - "skills/agent-device", - "skills/dogfood", "server.json", "smithery.yaml", "README.md", diff --git a/scripts/__tests__/agent-setup-startup-contract.test.ts b/scripts/__tests__/agent-setup-startup-contract.test.ts new file mode 100644 index 0000000000..fb7c5817c0 --- /dev/null +++ b/scripts/__tests__/agent-setup-startup-contract.test.ts @@ -0,0 +1,48 @@ +import assert from 'node:assert/strict'; +import { readFile } from 'node:fs/promises'; +import { join } from 'node:path'; +import { test } from 'vitest'; + +const ROOT = join(import.meta.dirname, '..', '..'); +const AGENT_SETUP = join(ROOT, 'website', 'docs', 'docs', 'agent-setup.md'); +const OPEN_FIRST = 'For a normal app-driving task, start immediately.'; +const MANDATORY_STARTUP_PROBES = [ + { + pattern: /Before planning commands, run `agent-device --version`/, + example: 'Before planning commands, run `agent-device --version`', + }, + { + pattern: /Before planning device work, run `agent-device --version`/, + example: 'Before planning device work, run `agent-device --version`', + }, + { + pattern: /run `agent-device help workflow` before planning/, + example: 'run `agent-device help workflow` before planning', + }, +] as const; + +function assertOpenFirstSetup(content: string): void { + const openFirstRules = content.split(OPEN_FIRST).length - 1; + assert.equal(openFirstRules, 3, 'recommended, Cursor, and Claude rules must start with open'); + for (const probe of MANDATORY_STARTUP_PROBES) { + assert.doesNotMatch( + content, + probe.pattern, + `agent setup contains mandatory startup probe: ${probe.pattern}`, + ); + } +} + +test('agent setup rules start normal work with open and avoid mandatory probes', async () => { + assertOpenFirstSetup(await readFile(AGENT_SETUP, 'utf8')); +}); + +for (const probe of MANDATORY_STARTUP_PROBES) { + test(`agent setup contract rejects ${probe.pattern}`, async () => { + const content = await readFile(AGENT_SETUP, 'utf8'); + assert.throws( + () => assertOpenFirstSetup(`${content}\n${probe.example}\n`), + /agent setup contains mandatory startup probe/, + ); + }); +} diff --git a/scripts/__tests__/npm-skills-exclusion.test.ts b/scripts/__tests__/npm-skills-exclusion.test.ts new file mode 100644 index 0000000000..98f40b0b27 --- /dev/null +++ b/scripts/__tests__/npm-skills-exclusion.test.ts @@ -0,0 +1,21 @@ +import assert from 'node:assert/strict'; +import { readFile } from 'node:fs/promises'; +import { join } from 'node:path'; +import { test } from 'vitest'; + +const ROOT = join(import.meta.dirname, '..', '..'); + +test('the npm package excludes repository skills', async () => { + const manifest = JSON.parse(await readFile(join(ROOT, 'package.json'), 'utf8')) as { + files?: string[]; + }; + const publishedSkills = (manifest.files ?? []).filter( + (entry) => entry === 'skills' || entry.startsWith('skills/'), + ); + + assert.deepEqual( + publishedSkills, + [], + 'skills are installed from the repository and must not ship in the npm CLI package', + ); +}); diff --git a/scripts/__tests__/simulator-skills-contract.test.ts b/scripts/__tests__/simulator-skills-contract.test.ts index 7d0bdc0dbf..d55cc761a7 100644 --- a/scripts/__tests__/simulator-skills-contract.test.ts +++ b/scripts/__tests__/simulator-skills-contract.test.ts @@ -10,50 +10,65 @@ type Contract = { requiredText: string; }; +type Prohibition = { + id: string; + forbiddenText: string; +}; + const SKILLS = [ { name: 'iOS Simulator', path: join(ROOT, 'skills', 'ios-simulator', 'SKILL.md'), contracts: [ { - id: 'foreground platform open', - requiredText: 'agent-device open --platform ios --foreground', + id: 'human-owned CLI installation', + requiredText: 'npm install -g agent-device@latest', + }, + { + id: 'immediate app-driving start', + requiredText: 'For a normal app-driving task, start immediately.', }, + { id: 'validation help routing', requiredText: 'agent-device help validate' }, { - id: 'initial interactive snapshot', - requiredText: '`open` returns the initial interactive snapshot.', + id: 'autonomous mutable install refusal', + requiredText: 'Do not run that command autonomously', }, - { id: 'current ref or selector', requiredText: 'Use its current refs or a selector.' }, - { id: 'settled planned actions', requiredText: 'agent-device press @eN --settle' }, - { id: 'type settle exception', requiredText: '`type` never takes `--settle`;' }, { - id: 'end-state verification', - requiredText: 'Verify the end state with a selector or exact text, then close:', + id: 'foreground platform open', + requiredText: 'agent-device open --platform ios --foreground', }, - { id: 'session close', requiredText: 'agent-device close' }, ] satisfies Contract[], + prohibitions: [ + { id: 'version startup probe', forbiddenText: 'agent-device --version' }, + { id: 'routine help startup probe', forbiddenText: 'agent-device help manual-qa' }, + ] satisfies Prohibition[], }, { name: 'Android Emulator', path: join(ROOT, 'skills', 'android-emulator', 'SKILL.md'), contracts: [ { - id: 'foreground platform open', - requiredText: 'agent-device open --platform android --foreground', + id: 'human-owned CLI installation', + requiredText: 'npm install -g agent-device@latest', }, { - id: 'initial interactive snapshot', - requiredText: '`open` returns the initial interactive snapshot.', + id: 'immediate app-driving start', + requiredText: 'For a normal app-driving task, start immediately.', }, - { id: 'current ref or selector', requiredText: 'Use its current refs or a selector.' }, - { id: 'settled planned actions', requiredText: 'agent-device press @eN --settle' }, - { id: 'type settle exception', requiredText: '`type` never takes `--settle`;' }, + { id: 'validation help routing', requiredText: 'agent-device help validate' }, { - id: 'end-state verification', - requiredText: 'Verify the end state with a selector or exact text, then close:', + id: 'autonomous mutable install refusal', + requiredText: 'Do not run that command autonomously', + }, + { + id: 'foreground platform open', + requiredText: 'agent-device open --platform android --foreground', }, - { id: 'session close', requiredText: 'agent-device close' }, ] satisfies Contract[], + prohibitions: [ + { id: 'version startup probe', forbiddenText: 'agent-device --version' }, + { id: 'routine help startup probe', forbiddenText: 'agent-device help manual-qa' }, + ] satisfies Prohibition[], }, ] as const; @@ -61,11 +76,18 @@ function assertSkillContract(content: string, contract: Contract): void { assert.ok(content.includes(contract.requiredText), `missing ${contract.id} guidance`); } +function assertSkillProhibition(content: string, prohibition: Prohibition): void { + assert.ok(!content.includes(prohibition.forbiddenText), `contains ${prohibition.id} guidance`); +} + describe('simulator skill contracts', () => { for (const skill of SKILLS) { test(`${skill.name} keeps its required workflow guidance`, async () => { const content = await readFile(skill.path, 'utf8'); for (const contract of skill.contracts) assertSkillContract(content, contract); + for (const prohibition of skill.prohibitions) { + assertSkillProhibition(content, prohibition); + } }); for (const contract of skill.contracts) { @@ -79,5 +101,16 @@ describe('simulator skill contracts', () => { ); }); } + + for (const prohibition of skill.prohibitions) { + test(`${skill.name} rejects ${prohibition.id} guidance`, async () => { + const content = await readFile(skill.path, 'utf8'); + const broken = `${content}\n${prohibition.forbiddenText}\n`; + assert.throws( + () => assertSkillProhibition(broken, prohibition), + new RegExp(`contains ${prohibition.id} guidance`), + ); + }); + } } }); diff --git a/skills/android-emulator/SKILL.md b/skills/android-emulator/SKILL.md index 22860545d4..59d4b54f32 100644 --- a/skills/android-emulator/SKILL.md +++ b/skills/android-emulator/SKILL.md @@ -5,40 +5,28 @@ description: Verify and debug native, React Native, Expo, or Flutter apps on an # Android Emulator -Use `agent-device` on an Android Emulator to verify a running app. Work from the live UI, act on current refs or selectors, and verify the result before closing the session. - -For an app or package id, open it in the foreground: +Require the `agent-device` CLI to be installed separately before driving an emulator: ```bash -agent-device open --platform android --foreground +npm install -g agent-device@latest ``` -`open` returns the initial interactive snapshot. Use its current refs or a selector. For a planned action, use `--settle`. If the settled diff shows the next target, continue from it: - -```bash -agent-device press @eN --settle -agent-device fill @eN "text" --settle -``` +Treat installation and upgrades as user-owned setup steps. Do not run that command autonomously or substitute a mutable `npx -y agent-device@latest` invocation. -Run `agent-device snapshot -i` only when the settled diff does not show the next target. `type` never takes `--settle`; verify it with a snapshot or named `wait`. Keep state-changing commands serial. Verify the end state with a selector or exact text, then close: +For a normal app-driving task, start immediately. Do not probe first with `--help`, `--version`, `devices`, `appstate`, `snapshot`, or `screenshot`. Target Android explicitly when opening an app or package id: ```bash -agent-device close +agent-device open --platform android --foreground ``` -For non-routine work, use the version-matched CLI help: +Follow the initial interactive snapshot and corrective error hints. If the shell reports that `agent-device` is unavailable, stop and ask the user to install it or expose their existing installation on `PATH`. -```bash -agent-device help validate -``` - -Read only the relevant follow-up topic for specialized work: +Only when the task is specialized or a command shape is unclear, read the relevant version-matched help topic: ```bash +agent-device help validate # engineering validation and build freshness agent-device help debugging # screenshots, logs, traces, video, and failures agent-device help react-native # React Native and Expo runtime guidance agent-device help react-devtools # component tree, props/state/hooks, and renders agent-device help scripting # durable replay and CI workflows ``` - -Use `adb shell` only for platform operations. Use this workflow to verify the app and keep diagnostic output when it fails. diff --git a/skills/ios-simulator/SKILL.md b/skills/ios-simulator/SKILL.md index d6e2e87447..2a77984154 100644 --- a/skills/ios-simulator/SKILL.md +++ b/skills/ios-simulator/SKILL.md @@ -5,40 +5,28 @@ description: Verify and debug native, React Native, Expo, or Flutter apps on an # iOS Simulator -Use `agent-device` on an iOS Simulator to verify a running app. Work from the live UI, act on current refs or selectors, and verify the result before closing the session. - -For an app or bundle id, open it in the foreground: +Require the `agent-device` CLI to be installed separately before driving a simulator: ```bash -agent-device open --platform ios --foreground +npm install -g agent-device@latest ``` -`open` returns the initial interactive snapshot. Use its current refs or a selector. For a planned action, use `--settle`. If the settled diff shows the next target, continue from it: - -```bash -agent-device press @eN --settle -agent-device fill @eN "text" --settle -``` +Treat installation and upgrades as user-owned setup steps. Do not run that command autonomously or substitute a mutable `npx -y agent-device@latest` invocation. -Run `agent-device snapshot -i` only when the settled diff does not show the next target. `type` never takes `--settle`; verify it with a snapshot or named `wait`. Keep state-changing commands serial. Verify the end state with a selector or exact text, then close: +For a normal app-driving task, start immediately. Do not probe first with `--help`, `--version`, `devices`, `appstate`, `snapshot`, or `screenshot`. Target iOS explicitly when opening an app or bundle id: ```bash -agent-device close +agent-device open --platform ios --foreground ``` -For non-routine work, use the version-matched CLI help: +Follow the initial interactive snapshot and corrective error hints. If the shell reports that `agent-device` is unavailable, stop and ask the user to install it or expose their existing installation on `PATH`. -```bash -agent-device help validate -``` - -Read only the relevant follow-up topic for specialized work: +Only when the task is specialized or a command shape is unclear, read the relevant version-matched help topic: ```bash +agent-device help validate # engineering validation and build freshness agent-device help debugging # screenshots, logs, traces, video, and failures agent-device help react-native # React Native and Expo runtime guidance agent-device help react-devtools # component tree, props/state/hooks, and renders agent-device help scripting # durable replay and CI workflows ``` - -If platform help says a capability is unavailable, follow it. Keep diagnostic output when verification fails. diff --git a/src/utils/update-check.ts b/src/utils/update-check.ts index 61868d4094..e4b828f092 100644 --- a/src/utils/update-check.ts +++ b/src/utils/update-check.ts @@ -41,7 +41,7 @@ export function maybeRunUpgradeNotifier(options: UpgradeNotifierOptions): void { if (shouldShowUpgradeNotice(cache, options.currentVersion)) { process.stderr.write( `Update available: ${PACKAGE_NAME} ${options.currentVersion} -> ${cache.latestVersion}. ` + - `Run \`npm install -g ${PACKAGE_NAME}@latest\` to upgrade the CLI and bundled skills.\n`, + `Run \`npm install -g ${PACKAGE_NAME}@latest\` to upgrade the CLI.\n`, ); writeUpdateCheckCache(cachePath, { ...cache, diff --git a/vitest.config.ts b/vitest.config.ts index 60a76fa161..db3cc2667f 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -70,6 +70,8 @@ export default defineConfig({ 'scripts/__tests__/help-conformance-error-recovery-coverage.test.ts', 'scripts/__tests__/help-conformance-sample-outputs.test.ts', 'scripts/__tests__/help-conformance-topic-coverage.test.ts', + 'scripts/__tests__/agent-setup-startup-contract.test.ts', + 'scripts/__tests__/npm-skills-exclusion.test.ts', 'scripts/__tests__/simulator-skills-contract.test.ts', // The publishing gate's closure audit against fixture packages: parse-only, and the // only place the gate's failure direction is exercised at all (the gate itself needs a diff --git a/website/docs/docs/agent-setup.md b/website/docs/docs/agent-setup.md index b770fb3543..02fe9ddc81 100644 --- a/website/docs/docs/agent-setup.md +++ b/website/docs/docs/agent-setup.md @@ -9,7 +9,7 @@ description: Configure Cursor, Codex, Claude Code, Windsurf, Cline, Goose, iOS S Use this page to wire Cursor, Codex, Claude Code, Windsurf, Cline, Goose, or another coding agent into mobile, TV, desktop, and web app verification. It covers skills, project rules, and MCP setup for React Native QA, Expo app verification, iOS Simulator automation, Android Emulator automation, tvOS checks, Android TV checks, Vega OS VVD control, web browser sessions, debugging, profiling, and exploratory QA. -The short version: install the CLI, make the agent read version-matched help, and let the agent use either MCP tools or CLI commands. MCP tools use command contracts backed by the same `AgentDeviceClient` execution path as the CLI adapters. +The short version: install the CLI, let the agent start normal work with the requested app, and use version-matched help only for specialized work or an unclear command shape. MCP tools use command contracts backed by the same `AgentDeviceClient` execution path as the CLI adapters. ## Prerequisite: install the CLI @@ -26,28 +26,28 @@ npx agent-device --version npx agent-device help workflow ``` -Global install is better for normal agent workflows because repeated commands, skills, and terminal sessions resolve to one stable version. Project-local installs are also good when you want a lockfile-pinned agent-device version. +Global install is better for normal agent workflows because repeated commands and terminal sessions resolve to one stable version. Project-local installs are also good when you want a lockfile-pinned agent-device version. Avoid telling agents to choose an npm version or run `npx -y agent-device@latest` autonomously: it fetches and executes a mutable npm package without a human prompt. For unattended agent use, prefer a trusted installed binary, a project-local install, or a version supplied by the user or project config. For Node, Xcode, Android SDK, macOS, and iOS device prerequisites, see [Installation](/docs/installation). -## Install the skill +## Install the skills -Install the skill when your agent runtime supports skills: +Install the CLI first, then install the repository skills when your agent runtime supports them: ```bash npx skills add callstack/agent-device ``` -The bundled [agent-device skill](https://github.com/callstack/agent-device/blob/main/skills/agent-device/SKILL.md) is the canonical router for skill-aware clients. For focused simulator work, use the [iOS Simulator skill](https://github.com/callstack/agent-device/blob/main/skills/ios-simulator/SKILL.md) or [Android Emulator skill](https://github.com/callstack/agent-device/blob/main/skills/android-emulator/SKILL.md). They intentionally point agents back to installed CLI help instead of duplicating the command manual. +Skills are distributed from the GitHub repository rather than the npm package. The [agent-device skill](https://github.com/callstack/agent-device/blob/main/skills/agent-device/SKILL.md) is the canonical router for skill-aware clients. For focused simulator work, use the [iOS Simulator skill](https://github.com/callstack/agent-device/blob/main/skills/ios-simulator/SKILL.md) or [Android Emulator skill](https://github.com/callstack/agent-device/blob/main/skills/android-emulator/SKILL.md). They start normal work directly and route agents to the separately installed, version-matched CLI help only when the task is specialized or a command shape is unclear. ## Recommended agent rule Add this as a project rule, custom instruction, or skill equivalent when your agent client supports it: ```text -Use agent-device only for app/device automation tasks. Before planning commands, run `agent-device --version` and read `agent-device help workflow`. For TV, Fire TV, or Vega OS tasks, read `agent-device help tv`. For exploratory QA, read `agent-device help dogfood`. For logs, network, audio, traces, or runtime failures, read `agent-device help debugging`. For React Native component trees, props/state/hooks, slow renders, or rerenders, read `agent-device help react-devtools`. For React Native JavaScript heap growth, heap snapshots, or retained-object leaks, read `agent-device help cdp`. For React Native apps, overlays, Metro/Fast Refresh blockers, and routing to React DevTools or debugging evidence, read `agent-device help react-native`. +Use agent-device only for app/device automation tasks. For a normal app-driving task, start immediately. Do not probe first with `--help`, `--version`, `devices`, `appstate`, `snapshot`, or `screenshot`; open the requested app in the foreground and continue from its initial interactive snapshot. For TV, Fire TV, or Vega OS tasks, read `agent-device help tv`. For exploratory QA, read `agent-device help dogfood`. For logs, network, audio, traces, or runtime failures, read `agent-device help debugging`. For React Native component trees, props/state/hooks, slow renders, or rerenders, read `agent-device help react-devtools`. For React Native JavaScript heap growth, heap snapshots, allocation hotspots, or retained-object leaks, read `agent-device help cdp`. For React Native apps, overlays, Metro/Fast Refresh blockers, and routing to React DevTools or debugging evidence, read `agent-device help react-native`. Use MCP tools or the CLI in the integrated terminal. If `agent-device` is not on PATH but the user installed it globally in another shell, resolve the command the same way the user would from a normal terminal session and run that absolute path instead. This may require inspecting shell startup behavior or package-manager/global bin locations; do not assume the agent process `PATH` is the user's `PATH`. Do not silently fall back to `npx -y agent-device@latest`; ask or use an exact version. MCP exposes structured tools backed by the agent-device client; it does not expose generic shell execution. Prefer `open -> snapshot -i -> act -> re-snapshot -> verify -> close` where the target supports capture and selectors; otherwise follow target-specific help. Use current refs such as `@e3` for exploration and selectors for durable replay. Keep mutating commands against one session serial. Capture screenshots, logs, network, audio, perf, traces, recordings, and `.ad` replay scripts only when they add evidence. ``` @@ -60,7 +60,7 @@ For web automation, MCP tools can target `platform: "web"` after the managed bac Tool execution failures are returned as MCP tool results with `isError: true`; clients and agents should inspect the tool result, not only the successful JSON-RPC envelope. -MCP clients must not use this server as a generic shell runner. If the CLI is missing, agents should ask a human before installing or updating packages, then verify with `agent-device --version` and start with `agent-device help workflow`. +MCP clients must not use this server as a generic shell runner. If the CLI is missing, agents should ask a human before installing or updating packages, reconnect the server after setup, and retry the intended app-driving command without adding version/help probes. Global install configuration: @@ -107,7 +107,7 @@ alwaysApply: true --- Use agent-device only for app/device automation tasks. -Before planning device work, run `agent-device --version` and read `agent-device help workflow`. +For a normal app-driving task, start immediately. Do not probe first with `--help`, `--version`, `devices`, `appstate`, `snapshot`, or `screenshot`; open the requested app in the foreground and continue from its initial interactive snapshot. For TV, Fire TV, or Vega OS tasks, read `agent-device help tv`. For exploratory QA, read `agent-device help dogfood`. For logs, network, audio, traces, or runtime failures, read `agent-device help debugging`. @@ -125,11 +125,7 @@ EOF Then ask Cursor Agent to run: ```bash -agent-device --version -agent-device help workflow -agent-device apps --platform ios -agent-device open --platform ios -agent-device snapshot -i +agent-device open --platform ios --foreground ``` ### Cursor path B: MCP tools @@ -174,13 +170,10 @@ If the MCP server fails because Cursor cannot find the global binary, use the ab Put the recommended rule in `AGENTS.md` or the project instructions. Let Codex run `agent-device` in the terminal: ```bash -agent-device help workflow -agent-device boot --platform ios -agent-device open --platform ios -agent-device snapshot -i +agent-device open --platform ios --foreground ``` -Some agent clients run commands in an environment that differs from the user's normal install shell. If the user installed `agent-device` globally but the agent cannot find it, resolve the command the same way the user would from a normal terminal session, then use the absolute binary path for `--version`, `help workflow`, and subsequent commands. This may require inspecting shell startup behavior or package-manager/global bin locations; do not assume the agent process `PATH` is the user's `PATH`. +Some agent clients run commands in an environment that differs from the user's normal install shell. If the user installed `agent-device` globally but the agent cannot find it, resolve the command the same way the user would from a normal terminal session, then use the absolute binary path for the intended `open` and subsequent commands. This may require inspecting shell startup behavior or package-manager/global bin locations; do not assume the agent process `PATH` is the user's `PATH`. For reviews or planning-only tasks, tell the agent not to run devices unless explicitly requested. @@ -197,7 +190,7 @@ cat > CLAUDE.md <<'EOF' # agent-device Use agent-device only for app/device automation tasks. -Before planning device work, run `agent-device --version` and read `agent-device help workflow`. +For a normal app-driving task, start immediately. Do not probe first with `--help`, `--version`, `devices`, `appstate`, `snapshot`, or `screenshot`; open the requested app in the foreground and continue from its initial interactive snapshot. For TV, Fire TV, or Vega OS tasks, read `agent-device help tv`. For exploratory QA, read `agent-device help dogfood`. For logs, network, audio, traces, or runtime failures, read `agent-device help debugging`. @@ -215,10 +208,7 @@ EOF Then ask Claude Code to run: ```bash -agent-device --version -agent-device help workflow -agent-device help dogfood -agent-device help react-native +agent-device open --platform android --foreground ``` ### Claude path B: MCP tools @@ -261,7 +251,7 @@ The same CLI commands remain available in the integrated terminal for long-runni Use the [MCP server](#mcp-server) configuration when the client supports `mcpServers`, then tell the agent to use MCP tools or terminal CLI commands for device workflows. -If the client has project rules or custom instructions, add the recommended agent rule above. If it does not, start the conversation by asking the agent to run `agent-device help workflow` before planning. +If the client has project rules or custom instructions, add the recommended agent rule above. If it does not, ask the agent to open the requested app and continue from the initial interactive snapshot; introduce a help topic only when the task is specialized or a command shape is unclear. ## Why this setup works