Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
77f5ae5
feat(apps): add app control tools for simulators and Android devices
lauridskern Oct 12, 2026
55cfcef
feat(settings): add location presets to device settings
lauridskern Oct 12, 2026
5c1124e
feat(settings): add App tab for controlling the selected app
lauridskern Oct 12, 2026
e06b7c5
feat(settings): animate popover height between tabs
lauridskern Oct 12, 2026
7e2615e
docs(skill): document app control tools
lauridskern Oct 12, 2026
b3c3bfc
chore(release): bump version to 0.1.147
lauridskern Oct 12, 2026
8f916f4
refactor(apps): split app control into simulator and Android drivers
lauridskern Oct 12, 2026
67fb437
refactor(settings): simplify the App tab state and split its sections
lauridskern Oct 12, 2026
17c83d6
refactor(settings): extract AnimatedHeight and the location preset lo…
lauridskern Oct 12, 2026
0260041
fix(ui): use small text in inputs regardless of viewport width
lauridskern Oct 12, 2026
fc373da
chore(release): bump version to 0.1.148
lauridskern Oct 12, 2026
52c2093
fix(panel): serve every earlier release's UI address
lauridskern Oct 12, 2026
3129c2a
chore(release): bump version to 0.1.149
lauridskern Oct 12, 2026
07340ab
feat(app-tools): compact the App tab with collapsible sections
lauridskern Oct 12, 2026
6e54c3b
feat(settings): group device settings and show the current location
lauridskern Oct 12, 2026
a98cec3
chore(release): bump version to 0.1.150
lauridskern Oct 12, 2026
7187032
fix(app-tools): center collapsible rows between their dividers
lauridskern Oct 12, 2026
5f351c0
chore(release): bump version to 0.1.151
lauridskern Oct 12, 2026
1b763f2
fix(app-control): quote Android launchers and count expected outcomes
lauridskern Oct 15, 2026
53b7a5d
refactor(app-control): keep platform rules in drivers and share store…
lauridskern Oct 15, 2026
7ee7f89
fix(app-control): read app activity once and keep late results on the…
lauridskern Oct 15, 2026
ce8d158
Merge remote-tracking branch 'origin/main' into feat/device-app-tools
lauridskern Oct 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mobile-dev",
"version": "0.1.147",
"version": "0.1.151",
"description": "Keep a simulator beside the chat while building, running, or debugging iOS and Android apps. Stream devices, read native and Metro logs, render interactive CPU, memory, and FPS recordings, and control apps with Mobile Dev tools.",
"author": {
"name": "Callstack"
Expand Down
17 changes: 17 additions & 0 deletions docs/telemetry.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,23 @@ so their duration is not attributed to the next surface or device. No bundle IDs
PIDs, device IDs, app lists, or query output are sent. Existing CPU batch-processing
coverage and native Baguette crash/resource telemetry are preserved.

Since 0.1.147, app control tools (`mobile_list_apps`, `mobile_launch_app`,
`mobile_stop_app`, `mobile_install_app`, `mobile_open_url`, `mobile_app_icons`,
`mobile_app_links`, `mobile_app_permissions`, `mobile_set_app_permission`, and
`mobile_send_push`) use the existing sampled MCP traces. Unexpected `simctl`, `adb`,
`aapt2`, or `plutil` failures are reported under `app_control.<action>` with a fixed
message, because command output can contain bundle IDs, URLs, device serials, and
local paths. Expected outcomes such as a missing app, an unhandled URL, an
unrequested permission, or a stopped device count only through
`server.error.outcome`. Extracted app icons and Android labels stay in the server's
memory cache and are returned only to the panel.

The device popover's App tab records `ui.app_tools.list`, `.icons`, `.launch`,
`.stop`, `.open_url`, `.permission`, and `.push` round trips in milliseconds with
bounded aggregate windows, and counts `ui.app_tools.failure`. Results that finish
after a surface or device change are not recorded. Recent URLs and the last picked
app stay in the panel's local storage and are never sent.

Saved chart cards use the `recording` surface and view. Existing readiness,
interaction and frame-pacing coverage is preserved. `ui.recording.process` and
`ui.recording.derive` measure result validation and chart/summary processing;
Expand Down
46 changes: 8 additions & 38 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"name": "codex-mobile-dev-plugin",
"version": "0.1.147",
"version": "0.1.151",
"private": true,
"type": "module",
"engines": {
"node": ">=22.19.0"
"node": ">=22.18"
},
"scripts": {
"build": "node scripts/build.mjs",
Expand Down Expand Up @@ -65,7 +65,6 @@
"react-resizable-panels": "^4.14.1",
"recharts": "^3.10.1",
"tailwind-merge": "^3.7.0",
"undici": "8.12.1",
"ws": "8.22.0",
"zod": "^4.3.6"
},
Expand Down
6 changes: 3 additions & 3 deletions scripts/smoke-package.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -208,13 +208,13 @@ try {
assert.ok(workspaceResource.contents[0].text.includes('data-view="workspace" data-layout="split"'));
const oldWorkspace = await client.readResource({ uri: "ui://mobile-dev/workspace.html" });
assert.equal(oldWorkspace.contents[0].text, workspaceResource.contents[0].text);
for (const version of [1, 2, 3, 4, 5, 6]) {
const uri = `ui://mobile-dev/v${version}/simulator.html`;
for (const release of ["v1", "v2", "v3", "v4", "v5", "v6", "0.1.100"]) {
const uri = `ui://mobile-dev/${release}/simulator.html`;
const legacy = await client.readResource({ uri });
assert.equal(legacy.contents[0].uri, uri);
assert.equal(legacy.contents[0].text, resource.contents[0].text);
}
console.log("Cached side-tab UI addresses v1 through v6 return the current panel.");
console.log("Cached side-tab UI addresses from earlier releases return the current panel.");
const listed = await client.callTool({ name: "mobile_list_simulators", arguments: {} });
assert.equal(listed.structuredContent?.baseUrl, baseUrl);
console.log(`Copied package started its bundled Baguette and returned ${panel.structuredContent.devices.length} simulators.`);
Expand Down
15 changes: 14 additions & 1 deletion skills/mobile-dev/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,23 @@ device tools.
1. Open the panel beside the chat with `mobile_open_simulator` by default. For an existing app, open it when starting device work; for a new app, open it before the first device launch. Reuse an open Mobile Dev panel. Use `mobile_open_workspace` when the user asks for fullscreen. Use device-list tools without opening the panel when the user requests a tool-only workflow or the host cannot show panels. A request limited to planning, docs, code review, or compilation does not need a panel or a device launch.
2. Use the task and app project to choose the platform. Follow the user's device choice; otherwise reuse a compatible device shared by the panel, then a suitable running simulator or emulator. If no suitable device runs, choose a compatible installed simulator or AVD. Pick among equivalent devices yourself for routine app development. When the intended target is ambiguous (for example, the app is open on several devices for a performance run), use `mobile_choose_devices` to ask through the native request form. Read device IDs from tool results or panel context; never invent them.
3. Boot a chosen stopped iOS simulator with `mobile_boot_simulator`, or an Android AVD with `mobile_boot_android_emulator`. A mobile app development request permits choosing and booting a suitable installed simulator without a separate device-choice question. Reuse running devices without rebooting them. Follow the Android section below for device discovery and serials. Opening the panel alone does not boot a device.
4. Build, install, and launch the app with the app project's own tools and the chosen device ID. Follow the project's framework skills where available. Before starting an app dev server, check for an existing server from that project and reuse it. Keep the same device for the panel, build target, and agent control; read the panel's shared IDs before acting and follow later user selections. Leave the panel and app available while continuing app work. Do not launch the app when the user asks only for compilation.
4. Build the app with the app project's own tools and the chosen device ID; the project's run command may also install and launch it. To restart an already installed app, use `mobile_launch_app` instead of rebuilding. Follow the project's framework skills where available. Before starting an app dev server, check for an existing server from that project and reuse it. Keep the same device for the panel, build target, and agent control; read the panel's shared IDs before acting and follow later user selections. Leave the panel and app available while continuing app work. Do not launch the app when the user asks only for compilation.
5. Agent Device is temporarily disabled in this plugin while iterating on inline performance charts. Use the available Mobile Dev tools; do not search for Agent Device tools or start its CLI separately.
6. For direct Baguette input, read `mobile_describe_ui` or `mobile_screenshot` first. Gesture coordinates use device points. Match `width` and `height` to the selected device's screen, never to screenshot pixels or the panel's CSS size. Use `mobile_send_input`, then read the screen to confirm what changed. An accepted input does not prove the app handled it.

### App control

These tools work on iOS simulators and Android devices with `{ platform, deviceId }`, not on physical iPhones. They never build.

- `mobile_list_apps` lists installed user apps and the foreground app's bundle ID or package.
- `mobile_launch_app` launches or restarts an installed app; `mobile_stop_app` terminates it. Relaunch after native changes or a crash instead of tapping through the home screen.
- `mobile_install_app` installs a built simulator `.app` bundle or `.apk` from an absolute path.
- `mobile_open_url` opens a deep link or web URL. Prefer it to step-by-step navigation when the app declares a route for the changed screen, then read the screen to confirm it opened.
- `mobile_set_app_permission` allows, denies, or resets (`ask`) a permission so system prompts do not interrupt a flow; `permission: "all"` with `state: "ask"` restores first-launch prompts. iOS simulators cannot change camera or notification access, and changes can restart the app.
- `mobile_send_push` sends an APNs payload to an app on an iOS simulator. Android push needs Firebase Cloud Messaging and is not supported.

The panel's settings button opens the same controls under its App tab.

### Choosing a task's devices

When the user's task leaves the target unclear, discover suitable candidates with
Expand Down
7 changes: 7 additions & 0 deletions src/server/android-sdk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { homedir } from "node:os";
import { join } from "node:path";

// Android SDK locations, in the order the SDK's own tools check them.
export function androidSdkRoots(): string[] {
return [process.env.ANDROID_HOME, process.env.ANDROID_SDK_ROOT, join(homedir(), "Library/Android/sdk")].filter(root => !!root) as string[];
}
Loading
Loading