@autifyhq/aximo-cli 0.4.0 → 0.6.0

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.
Files changed (96) hide show
  1. package/README.md +219 -38
  2. package/dist/cli.js +5 -0
  3. package/dist/client.d.ts +2 -0
  4. package/dist/client.js +18 -0
  5. package/dist/commands/device/device-config.d.ts +14 -0
  6. package/dist/commands/device/device-config.js +46 -0
  7. package/dist/commands/device/device-config.test.d.ts +1 -0
  8. package/dist/commands/device/device-config.test.js +56 -0
  9. package/dist/commands/device/doctor.d.ts +13 -0
  10. package/dist/commands/device/doctor.js +142 -25
  11. package/dist/commands/device/doctor.test.js +61 -1
  12. package/dist/commands/device/register.d.ts +2 -0
  13. package/dist/commands/device/register.js +50 -32
  14. package/dist/commands/device/register.test.d.ts +1 -0
  15. package/dist/commands/device/register.test.js +95 -0
  16. package/dist/commands/device/unregister.d.ts +5 -3
  17. package/dist/commands/device/unregister.js +32 -33
  18. package/dist/commands/device/unregister.test.d.ts +1 -0
  19. package/dist/commands/device/unregister.test.js +73 -0
  20. package/dist/commands/mobile/connect-core.d.ts +12 -0
  21. package/dist/commands/mobile/connect-core.js +88 -0
  22. package/dist/commands/mobile/connect.d.ts +9 -0
  23. package/dist/commands/mobile/connect.js +35 -0
  24. package/dist/commands/mobile/connect.test.d.ts +1 -0
  25. package/dist/commands/mobile/connect.test.js +87 -0
  26. package/dist/commands/mobile/doctor.d.ts +28 -0
  27. package/dist/commands/mobile/doctor.js +162 -0
  28. package/dist/commands/mobile/index.d.ts +2 -0
  29. package/dist/commands/mobile/index.js +16 -0
  30. package/dist/commands/mobile/install.d.ts +43 -0
  31. package/dist/commands/mobile/install.js +76 -0
  32. package/dist/commands/mobile/install.test.d.ts +1 -0
  33. package/dist/commands/mobile/install.test.js +104 -0
  34. package/dist/commands/mobile/list.d.ts +10 -0
  35. package/dist/commands/mobile/list.js +30 -0
  36. package/dist/commands/mobile/paths.d.ts +14 -0
  37. package/dist/commands/mobile/paths.js +71 -0
  38. package/dist/commands/mobile/register-core.d.ts +19 -0
  39. package/dist/commands/mobile/register-core.js +46 -0
  40. package/dist/commands/mobile/register.d.ts +37 -0
  41. package/dist/commands/mobile/register.js +57 -0
  42. package/dist/commands/mobile/register.test.d.ts +1 -0
  43. package/dist/commands/mobile/register.test.js +91 -0
  44. package/dist/commands/mobile/setup.d.ts +27 -0
  45. package/dist/commands/mobile/setup.js +100 -0
  46. package/dist/commands/mobile/setup.test.d.ts +1 -0
  47. package/dist/commands/mobile/setup.test.js +49 -0
  48. package/dist/commands/mobile/unregister.d.ts +14 -0
  49. package/dist/commands/mobile/unregister.js +37 -0
  50. package/dist/commands/schema.js +29 -0
  51. package/dist/commands/session/create.d.ts +5 -0
  52. package/dist/commands/session/create.js +6 -0
  53. package/dist/index.d.ts +3 -0
  54. package/dist/index.js +3 -0
  55. package/dist/mobile-control/app-install.d.ts +21 -0
  56. package/dist/mobile-control/app-install.js +28 -0
  57. package/dist/mobile-control/app-install.test.d.ts +1 -0
  58. package/dist/mobile-control/app-install.test.js +78 -0
  59. package/dist/mobile-control/appium-controller.d.ts +42 -0
  60. package/dist/mobile-control/appium-controller.js +372 -0
  61. package/dist/mobile-control/appium-controller.test.d.ts +1 -0
  62. package/dist/mobile-control/appium-controller.test.js +252 -0
  63. package/dist/mobile-control/appium-import.d.ts +7 -0
  64. package/dist/mobile-control/appium-import.js +6 -0
  65. package/dist/mobile-control/appium-import.test.d.ts +1 -0
  66. package/dist/mobile-control/appium-import.test.js +9 -0
  67. package/dist/mobile-control/appium-server.d.ts +30 -0
  68. package/dist/mobile-control/appium-server.js +110 -0
  69. package/dist/mobile-control/appium-server.test.d.ts +1 -0
  70. package/dist/mobile-control/appium-server.test.js +108 -0
  71. package/dist/mobile-control/controller.d.ts +22 -0
  72. package/dist/mobile-control/controller.js +1 -0
  73. package/dist/mobile-control/devices.d.ts +23 -0
  74. package/dist/mobile-control/devices.js +129 -0
  75. package/dist/mobile-control/devices.test.d.ts +1 -0
  76. package/dist/mobile-control/devices.test.js +75 -0
  77. package/dist/mobile-control/exec.d.ts +6 -0
  78. package/dist/mobile-control/exec.js +13 -0
  79. package/dist/mobile-control/ios-signing.d.ts +28 -0
  80. package/dist/mobile-control/ios-signing.js +38 -0
  81. package/dist/mobile-control/ios-signing.test.d.ts +1 -0
  82. package/dist/mobile-control/ios-signing.test.js +73 -0
  83. package/dist/mobile-control/tunnel.d.ts +15 -0
  84. package/dist/mobile-control/tunnel.js +165 -0
  85. package/dist/mobile-control/tunnel.test.d.ts +1 -0
  86. package/dist/mobile-control/tunnel.test.js +155 -0
  87. package/dist/mobile-control/webdriver.d.ts +31 -0
  88. package/dist/mobile-control/webdriver.js +137 -0
  89. package/dist/mobile-control/webdriver.test.d.ts +1 -0
  90. package/dist/mobile-control/webdriver.test.js +45 -0
  91. package/dist/proxy.d.ts +11 -0
  92. package/dist/proxy.js +85 -0
  93. package/dist/proxy.test.d.ts +1 -0
  94. package/dist/proxy.test.js +72 -0
  95. package/dist/types.d.ts +1 -0
  96. package/package.json +5 -2
package/README.md CHANGED
@@ -64,40 +64,47 @@ Credentials and device registrations are **per-environment** — to switch betwe
64
64
 
65
65
  Run `aximo <command> --help` for detailed usage. `aximo schema [command.path]` returns machine-readable argument introspection (e.g. `aximo schema session.create`).
66
66
 
67
- | Command | Description |
68
- | ---------------------------- | ---------------------------------------------------- |
69
- | `aximo configure` | Save credentials to `$AXIMO_HOME/config.json` |
70
- | `aximo health check` | Check API reachability |
71
- | `aximo project list` | List organization projects |
72
- | `aximo session create` | Create and queue a test session |
73
- | `aximo session get` | Get session status |
74
- | `aximo session save-as-case` | Save a finished session as a reusable test case |
75
- | `aximo case create` | Create a test case |
76
- | `aximo case delete` | Delete a test case |
77
- | `aximo plan create` | Create a test plan |
78
- | `aximo plan add-case` | Add a case to a plan |
79
- | `aximo plan remove-case` | Remove a case from a plan |
80
- | `aximo trigger case run` | Trigger a dashboard-created test case |
81
- | `aximo trigger case get` | Get triggered case status |
82
- | `aximo trigger plan run` | Trigger a test plan |
83
- | `aximo trigger plan get` | Get plan run status |
84
- | `aximo upload create` | Create an upload slot for an APK/IPA |
85
- | `aximo upload list` | List previously uploaded apps |
86
- | `aximo upload get` | Poll upload processing status |
87
- | `aximo upload put` | Upload binary file to the slot |
88
- | `aximo upload submit` | Finalize upload and trigger processing |
89
- | `aximo device list` | List mobile devices from Device Farm |
90
- | `aximo device setup` | First-time BYOD onboarding (API + Python + register) |
91
- | `aximo device register` | Register this machine as a BYOD device |
92
- | `aximo device connect` | Start the BYOD agent and connect to the relay |
93
- | `aximo device status` | List your organization's BYOD devices |
94
- | `aximo device ping` | Check your BYOD device's server-side status |
95
- | `aximo device doctor` | Diagnose BYOD setup (env, Python, relay, status) |
96
- | `aximo device logs` | Show the BYOD agent log |
97
- | `aximo device unregister` | Remove a BYOD device server-side + clear local creds |
98
- | `aximo device reset` | Remove BYOD local state (Python, deps, credentials) |
99
- | `aximo schema` | Show CLI surface as JSON |
100
- | `aximo mcp` | Run a stdio MCP server for agents (see below) |
67
+ | Command | Description |
68
+ | ---------------------------- | ------------------------------------------------------- |
69
+ | `aximo configure` | Save credentials to `$AXIMO_HOME/config.json` |
70
+ | `aximo health check` | Check API reachability |
71
+ | `aximo project list` | List organization projects |
72
+ | `aximo session create` | Create and queue a test session |
73
+ | `aximo session get` | Get session status |
74
+ | `aximo session save-as-case` | Save a finished session as a reusable test case |
75
+ | `aximo case create` | Create a test case |
76
+ | `aximo case delete` | Delete a test case |
77
+ | `aximo plan create` | Create a test plan |
78
+ | `aximo plan add-case` | Add a case to a plan |
79
+ | `aximo plan remove-case` | Remove a case from a plan |
80
+ | `aximo trigger case run` | Trigger a dashboard-created test case |
81
+ | `aximo trigger case get` | Get triggered case status |
82
+ | `aximo trigger plan run` | Trigger a test plan |
83
+ | `aximo trigger plan get` | Get plan run status |
84
+ | `aximo upload create` | Create an upload slot for an APK/IPA |
85
+ | `aximo upload list` | List previously uploaded apps |
86
+ | `aximo upload get` | Poll upload processing status |
87
+ | `aximo upload put` | Upload binary file to the slot |
88
+ | `aximo upload submit` | Finalize upload and trigger processing |
89
+ | `aximo device list` | List mobile devices from Device Farm |
90
+ | `aximo device setup` | First-time BYOD onboarding (API + Python + register) |
91
+ | `aximo device register` | Register this machine as a BYOD device |
92
+ | `aximo device connect` | Start the BYOD agent and connect to the relay |
93
+ | `aximo device status` | List your organization's BYOD devices |
94
+ | `aximo device ping` | Check your BYOD device's server-side status |
95
+ | `aximo device doctor` | Diagnose BYOD setup (env, Python, relay, status) |
96
+ | `aximo device logs` | Show the BYOD agent log |
97
+ | `aximo device unregister` | Remove a BYOD device server-side + clear local creds |
98
+ | `aximo device reset` | Remove BYOD local state (Python, deps, credentials) |
99
+ | `aximo mobile list` | List physically-connected phones (Android adb, iOS idb) |
100
+ | `aximo mobile setup` | Install the Appium drivers used to drive phones |
101
+ | `aximo mobile doctor` | Diagnose prerequisites (adb/idb, SDK/Xcode, driver) |
102
+ | `aximo mobile register` | Register a connected Android phone or iPhone |
103
+ | `aximo mobile install` | Install an app (.apk via adb, .ipa via idb) |
104
+ | `aximo mobile connect` | Start managed Appium + open the tunnel for the phone |
105
+ | `aximo mobile unregister` | Remove a local mobile device server-side + local creds |
106
+ | `aximo schema` | Show CLI surface as JSON |
107
+ | `aximo mcp` | Run a stdio MCP server for agents (see below) |
101
108
 
102
109
  Mutating commands support `--json '<raw body>'` for direct OpenAPI payloads and `--dry-run` to preview requests without executing.
103
110
 
@@ -349,16 +356,22 @@ Normal for brief network blips — the agent retries with exponential backoff (1
349
356
  - Confirm outbound WebSocket on 443 isn't blocked: `aximo device doctor` runs a handshake probe.
350
357
 
351
358
  **Python download or pip install fails (corporate proxy).**
352
- The embedded Python is fetched from `github.com/astral-sh/python-build-standalone/releases`; packages come from PyPI. Behind a proxy:
359
+ The embedded Python is fetched from `github.com/astral-sh/python-build-standalone/releases` and packages come from PyPI, so `aximo device setup` needs outbound access to both. On a network that only reaches the internet through a proxy, set the standard proxy variables — the CLI, pip, and the desktop agent all honor them:
353
360
 
354
361
  ```bash
355
362
  # macOS / Linux
356
- ~/.aximo/python/bin/python3 -m pip config set global.proxy http://your-proxy:port
363
+ export HTTPS_PROXY=http://your-proxy:port
364
+ export HTTP_PROXY=http://your-proxy:port
357
365
 
358
- # Windows
359
- %USERPROFILE%\.aximo\python\python.exe -m pip config set global.proxy http://your-proxy:port
366
+ # Windows (PowerShell)
367
+ $env:HTTPS_PROXY = "http://your-proxy:port"
368
+ $env:HTTP_PROXY = "http://your-proxy:port"
360
369
  ```
361
370
 
371
+ Loopback and a private-range `AXIMO_BASE_URL` (e.g. an on-prem `172.30.x.x` server) are always reached directly, so you don't have to list them in `NO_PROXY`. If your proxy performs TLS interception, also point `NODE_EXTRA_CA_CERTS` at the proxy's CA so the download's certificate verifies.
372
+
373
+ If the proxy cannot reach GitHub at all, ask your network team to allow-list both `github.com` and its release-asset CDN `release-assets.githubusercontent.com` — the download redirects from the former to the latter, so allow-listing only `github.com` is not enough.
374
+
362
375
  To retry from a clean slate: `aximo device reset --python && aximo device setup`.
363
376
 
364
377
  **Device shows online, but tests fail on screenshot / click.**
@@ -388,6 +401,174 @@ Default `$AXIMO_HOME` is `~/.aximo` on macOS/Linux, `%USERPROFILE%\.aximo` on Wi
388
401
 
389
402
  > Devices are registered per-environment (see [Configuration](#configuration)) — a device registered against sandbox can't connect to production and vice versa.
390
403
 
404
+ ## Mobile (local Android phone or iPhone)
405
+
406
+ The `mobile` commands let you register a **physically-connected Android phone or iPhone** as an Aximo device and drive it locally, over the same gateway tunnel as desktop BYOD. Aximo's agent controls the phone through a CLI-managed [Appium](https://appium.io) server — UiAutomator2 for Android, XCUITest for iOS — useful for running mobile tests against your own handset instead of a Device Farm device.
407
+
408
+ > **iOS is macOS-only and needs Xcode + an Apple Developer account.** Driving a physical iPhone goes through Appium's XCUITest driver, which builds and code-signs a WebDriverAgent (WDA) runner onto the device. That requires macOS, Xcode, an Apple Developer **Team ID**, and a WDA bundle id backed by a valid provisioning profile — set via the `AXIMO_IOS_*` env vars below. Android has no such requirement, so iOS setup is heavier; everything after setup (`register` → `connect` → run) works the same on both platforms.
409
+
410
+ Terminology note: **`aximo mobile list`** shows phones plugged into _this_ machine; **`aximo device list`** (under [Device / BYOD](#device-byod--bring-your-own-device)) returns mobile devices from AWS Device Farm. Don't confuse the two.
411
+
412
+ ### Prerequisites
413
+
414
+ - **Node.js 20+**, plus `aximo-cli` installed globally (see [Install](#install)).
415
+ - **An Aximo API key**, configured via `aximo configure` or env vars.
416
+ - **A USB-connected phone**:
417
+ - **Android** — **USB debugging** enabled and authorized — see [Enable USB debugging on the phone](#enable-usb-debugging-on-the-phone).
418
+ - **iOS** — **Developer Mode** enabled (Settings → Privacy & Security) and the computer trusted.
419
+ - **On the machine that runs `aximo mobile connect`**:
420
+ - **Android** — `adb` and the Android SDK, with `ANDROID_HOME` set — see [Set up your computer (Android)](#set-up-your-computer-android).
421
+ - **iOS** — macOS with **Xcode** + command line tools, **`idb`**, and the `AXIMO_IOS_*` signing env vars — see [Set up your computer (iOS)](#set-up-your-computer-ios).
422
+
423
+ > The Appium server is bundled with the CLI; `aximo mobile setup` installs the UiAutomator2 (Android) and/or XCUITest (iOS) driver — you don't install or manage Appium yourself.
424
+
425
+ ### Set up your computer (Android)
426
+
427
+ You only need **`adb`** and the **Android SDK** on the machine running `aximo mobile connect`. Appium delegates to `adb`, and its UiAutomator2 driver needs the SDK (even though `adb` is already on `PATH`).
428
+
429
+ **macOS**
430
+
431
+ ```bash
432
+ # Option A — standalone platform-tools (just adb):
433
+ brew install --cask android-platform-tools
434
+ # Option B — full Android Studio (also installs the SDK):
435
+ brew install --cask android-studio
436
+
437
+ # Point ANDROID_HOME at the SDK (Android Studio's default location shown):
438
+ echo 'export ANDROID_HOME="$HOME/Library/Android/sdk"' >> ~/.zshrc
439
+ echo 'export PATH="$PATH:$ANDROID_HOME/platform-tools"' >> ~/.zshrc
440
+ source ~/.zshrc
441
+ ```
442
+
443
+ **Linux**
444
+
445
+ ```bash
446
+ # Debian/Ubuntu — adb from the distro (or download platform-tools from Google):
447
+ sudo apt-get install -y android-tools-adb
448
+ # Install the SDK via Android Studio or sdkmanager, then export its path:
449
+ echo 'export ANDROID_HOME="$HOME/Android/Sdk"' >> ~/.bashrc
450
+ echo 'export PATH="$PATH:$ANDROID_HOME/platform-tools"' >> ~/.bashrc
451
+ source ~/.bashrc
452
+ ```
453
+
454
+ **Windows**
455
+
456
+ - Install **Android Studio** (bundles the SDK + platform-tools), or download the standalone **SDK Platform-Tools** zip from Google.
457
+ - Set `ANDROID_HOME` to the SDK path (e.g. `%LOCALAPPDATA%\Android\Sdk`) and add `%ANDROID_HOME%\platform-tools` to `PATH` via _System Properties → Environment Variables_.
458
+
459
+ > `aximo mobile doctor` auto-detects the SDK at the standard locations above, so `ANDROID_HOME` is optional when you use the defaults — but setting it explicitly is the most reliable. Confirm `adb` works with `adb version`.
460
+
461
+ ### Set up your computer (iOS)
462
+
463
+ iOS local devices are **macOS-only** (Appium's XCUITest driver delegates to Xcode). You need Xcode, `idb`, and the signing env vars.
464
+
465
+ ```bash
466
+ # 1. Xcode + command line tools (XCUITest builds WebDriverAgent with them):
467
+ xcode-select --install # or install Xcode from the App Store, then: sudo xcodebuild -license accept
468
+
469
+ # 2. idb — discovers connected iPhones and installs .ipa builds:
470
+ brew tap facebook/fb && brew install idb-companion
471
+ pipx install fb-idb # or: pip install fb-idb
472
+
473
+ # 3. Signing config for WebDriverAgent (required for a physical iPhone):
474
+ export AXIMO_IOS_TEAM_ID="ABCDE12345" # your Apple Developer Team ID (developer.apple.com/account → Membership)
475
+ # Optional overrides (sensible defaults shown):
476
+ export AXIMO_IOS_SIGNING_ID="Apple Development"
477
+ export AXIMO_IOS_WDA_BUNDLE_ID="com.facebook.WebDriverAgentRunner"
478
+ # Optional: reuse a prebuilt WDA to skip per-session rebuilds:
479
+ # export AXIMO_IOS_DERIVED_DATA_PATH="$HOME/Library/Developer/Xcode/DerivedData/WebDriverAgent-xxxx"
480
+ ```
481
+
482
+ `AXIMO_IOS_TEAM_ID` is the load-bearing setting: the XCUITest driver uses it (with the signing id and WDA bundle id) to build and sign WebDriverAgent so it can run on the device. The WDA bundle id must be associated with a valid provisioning profile under that team. Run `aximo mobile doctor --platform ios` to confirm Xcode, `idb`, and the signing config are all present.
483
+
484
+ > Already set up Autify's MobileLink? The CLI also reads MobileLink's names as fallbacks — `XCODE_TEAM_ID`, `XCODE_SIGN_ID`, `WDA_BUNDLE_ID` — so an existing host needs no reconfiguration. `AXIMO_IOS_*` takes precedence when both are set.
485
+
486
+ > First connect to a physical iPhone is slow: Xcode builds and installs WebDriverAgent (a minute or two). If the build fails on signing, open `WebDriverAgent.xcodeproj` in Xcode once, pick your team, and let it auto-manage signing — or set `AXIMO_IOS_DERIVED_DATA_PATH` to a prebuilt WDA.
487
+
488
+ ### Enable USB debugging on the phone
489
+
490
+ Exact labels vary by manufacturer and Android version, but the flow is:
491
+
492
+ 1. **Unlock Developer options** — open **Settings → About phone** and tap **Build number** seven times until you see "You are now a developer".
493
+ 2. **Turn on USB debugging** — open **Settings → System → Developer options** and enable **USB debugging**.
494
+ 3. **Connect and authorize** — plug the phone into your computer with a **data-capable** USB cable, then tap **Allow** on the "Allow USB debugging?" prompt on the phone (tick "Always allow from this computer" to skip it next time).
495
+ 4. **Verify the computer sees it**:
496
+ ```bash
497
+ adb devices # or: aximo mobile list
498
+ ```
499
+ The phone should appear in state **`device`**. **`unauthorized`** → re-accept the prompt on the phone; **`offline`** → re-plug the cable and unlock the screen. Emulators are not supported — connect a physical phone.
500
+
501
+ > Keep the phone **unlocked** the first time you run a session: the initial command installs Appium's UiAutomator2 helper and creates the automation session on the device.
502
+
503
+ ### First-time setup
504
+
505
+ ```bash
506
+ aximo mobile setup # installs the Appium driver(s) for your platform(s)
507
+ aximo mobile doctor # checks tooling / drivers / bundled server
508
+ # Limit to one platform if you only test on one:
509
+ aximo mobile setup --platform ios # installs only the XCUITest driver
510
+ aximo mobile doctor --platform ios # checks Xcode / idb / signing config
511
+ ```
512
+
513
+ `setup` installs the Appium driver(s) — UiAutomator2 for Android, XCUITest for iOS — then prints what (if anything) is still missing. `doctor` is the first thing to run when anything's off: for Android it reports `adb` and the Android SDK; for iOS it reports `xcrun`/Xcode, `idb`, and your `AXIMO_IOS_TEAM_ID` signing config — alongside the bundled Appium server and driver — and exits non-zero if a required check fails.
514
+
515
+ ### Registering and connecting
516
+
517
+ ```bash
518
+ aximo mobile list # see connected phones (adb serials / iOS udids)
519
+
520
+ # Android:
521
+ aximo mobile register --name "My Pixel" --platform android # --platform defaults to android
522
+ # iOS:
523
+ aximo mobile register --name "My iPhone" --platform ios # targets the single connected iPhone
524
+
525
+ aximo mobile connect # starts managed Appium + opens the tunnel
526
+ # Ctrl+C to disconnect
527
+ ```
528
+
529
+ `register` writes credentials to `$AXIMO_HOME/mobile-devices/<deviceId>.json` (mode `0600`). It targets the single connected phone by default — pass `--serial` (Android) or `--udid` (iOS) to pick a specific one when several are attached. `connect` re-checks prerequisites, starts the managed Appium server, and opens the tunnel; it runs until Ctrl+C. If you have more than one registered phone, pass `--device-id` to choose which to connect.
530
+
531
+ Once connected, create a **mobile session in Aximo targeting this local device** — the agent routes its actions through the tunnel to the phone over Appium.
532
+
533
+ ### Testing your own app
534
+
535
+ Unlike Device Farm, a local phone runs the apps already installed on it. To test your own build, install it first:
536
+
537
+ ```bash
538
+ aximo mobile install --app ./app-debug.apk # Android: adb install -r
539
+ aximo mobile install --app ./MyApp.ipa # iOS: idb install (the .ipa must be signed for the device)
540
+ ```
541
+
542
+ Then create the session with the app's bundle id / package name as `--bundle-id` — the agent launches it on startup before the test begins:
543
+
544
+ ```bash
545
+ # Android:
546
+ aximo session create --platform ANDROID --user-device-id DEVICE_ID --bundle-id com.example.app \
547
+ --project-id PROJECT_ID --title "Smoke" --prompt "Sign in and open settings"
548
+ # iOS:
549
+ aximo session create --platform IOS --user-device-id DEVICE_ID --bundle-id com.example.MyApp \
550
+ --project-id PROJECT_ID --title "Smoke" --prompt "Sign in and open settings"
551
+ ```
552
+
553
+ ### Managing devices
554
+
555
+ ```bash
556
+ aximo mobile unregister # remove the locally registered device (server-side + local creds)
557
+ aximo mobile unregister --device-id DEVICE_ID # target a specific device when several are registered
558
+ ```
559
+
560
+ ### Flags reference
561
+
562
+ | Command | Flags |
563
+ | ------------------------- | ---------------------------------------------------------------------------------------------------- |
564
+ | `aximo mobile list` | _(none)_ |
565
+ | `aximo mobile setup` | `--platform android \| ios` (optional; installs both drivers by default) |
566
+ | `aximo mobile doctor` | `--platform android \| ios` (optional; checks both by default) |
567
+ | `aximo mobile register` | `--name <name>` (required), `--platform android \| ios` (default android), `--serial`/`--udid` |
568
+ | `aximo mobile install` | `--app <path-to-.apk\|.ipa>` (required), `--platform android \| ios` (inferred), `--serial`/`--udid` |
569
+ | `aximo mobile connect` | `--device-id <id>` (required only if multiple devices are registered) |
570
+ | `aximo mobile unregister` | `--device-id <id>` (defaults to the locally registered device) |
571
+
391
572
  ## MCP server
392
573
 
393
574
  `aximo mcp` runs a [Model Context Protocol](https://modelcontextprotocol.io) server over stdio, exposing your recorded sessions, screenshots, and test cases to MCP-capable agents (Claude Code, Claude Desktop, Cursor, Windsurf, VS Code, …). The motivating use: let an agent read a finished session and generate a Playwright spec without you piping JSON between commands.
package/dist/cli.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { defineCommand, runMain } from "citty";
3
3
  import pkg from "../package.json" with { type: "json" };
4
+ import { installProxySupport } from "./proxy.js";
4
5
  // citty's --help prints to stdout; route to stderr for `mcp` since its stdout
5
6
  // is JSON-RPC.
6
7
  const args = process.argv.slice(2);
@@ -27,10 +28,14 @@ const main = defineCommand({
27
28
  plan: () => import("./commands/plan/index.js").then((m) => m.default),
28
29
  trigger: () => import("./commands/trigger/index.js").then((m) => m.default),
29
30
  device: () => import("./commands/device/index.js").then((m) => m.default),
31
+ mobile: () => import("./commands/mobile/index.js").then((m) => m.default),
30
32
  upload: () => import("./commands/upload/index.js").then((m) => m.default),
31
33
  project: () => import("./commands/project/index.js").then((m) => m.default),
32
34
  schema: () => import("./commands/schema.js").then((m) => m.default),
33
35
  mcp: () => import("./commands/mcp/index.js").then((m) => m.default),
34
36
  },
35
37
  });
38
+ // Honor HTTP_PROXY / HTTPS_PROXY / NO_PROXY for every fetch the CLI makes (the
39
+ // embedded-Python download, API calls). No-op unless a proxy is configured.
40
+ await installProxySupport();
36
41
  runMain(main);
package/dist/client.d.ts CHANGED
@@ -16,3 +16,5 @@ export interface ClientDeps {
16
16
  }
17
17
  export declare function request<T>(path: string, options?: RequestOptions, deps?: ClientDeps): Promise<T>;
18
18
  export declare function requestBuffer(path: string, deps?: ClientDeps): Promise<Buffer>;
19
+ /** Authenticated GET that streams the response body to a file (no full-buffering). */
20
+ export declare function requestToFile(path: string, destPath: string, deps?: ClientDeps): Promise<void>;
package/dist/client.js CHANGED
@@ -88,3 +88,21 @@ export async function requestBuffer(path, deps = {}) {
88
88
  }
89
89
  return Buffer.from(await res.arrayBuffer());
90
90
  }
91
+ /** Authenticated GET that streams the response body to a file (no full-buffering). */
92
+ export async function requestToFile(path, destPath, deps = {}) {
93
+ const apiKey = deps.apiKey ?? getApiKey();
94
+ const baseUrl = deps.baseUrl ?? getBaseUrl();
95
+ const fetchImpl = deps.fetchImpl ?? fetch;
96
+ const url = buildUrl(baseUrl, path);
97
+ const res = await fetchImpl(url, { method: "GET", headers: { "x-api-key": apiKey } });
98
+ if (!res.ok) {
99
+ const retryAfter = res.headers.get("retry-after") ?? undefined;
100
+ throw httpError(res.status, await errorMessage(res), retryAfter);
101
+ }
102
+ if (!res.body)
103
+ throw new Error(`Empty response body from ${path}`);
104
+ const { Readable } = await import("node:stream");
105
+ const { pipeline } = await import("node:stream/promises");
106
+ const { createWriteStream } = await import("node:fs");
107
+ await pipeline(Readable.fromWeb(res.body), createWriteStream(destPath));
108
+ }
@@ -0,0 +1,14 @@
1
+ export interface DeviceConfig {
2
+ deviceId: string;
3
+ deviceToken: string;
4
+ name: string;
5
+ registeredAt: string;
6
+ baseUrl?: string;
7
+ }
8
+ /**
9
+ * Read and validate the local device registration (~/.aximo/device.json).
10
+ * Returns null when the file is missing, unreadable, or malformed.
11
+ */
12
+ export declare function readDeviceConfig(): DeviceConfig | null;
13
+ /** Persist the local device registration with owner-only permissions. */
14
+ export declare function writeDeviceConfig(config: DeviceConfig): void;
@@ -0,0 +1,46 @@
1
+ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { getAximoDir, getDeviceConfigPath } from "./paths.js";
3
+ /**
4
+ * Read and validate the local device registration (~/.aximo/device.json).
5
+ * Returns null when the file is missing, unreadable, or malformed.
6
+ */
7
+ export function readDeviceConfig() {
8
+ const path = getDeviceConfigPath();
9
+ if (!existsSync(path))
10
+ return null;
11
+ try {
12
+ const parsed = JSON.parse(readFileSync(path, "utf-8"));
13
+ if (!parsed || typeof parsed !== "object")
14
+ return null;
15
+ const raw = parsed;
16
+ if (typeof raw.deviceId !== "string" ||
17
+ typeof raw.deviceToken !== "string" ||
18
+ typeof raw.name !== "string") {
19
+ return null;
20
+ }
21
+ return {
22
+ deviceId: raw.deviceId,
23
+ deviceToken: raw.deviceToken,
24
+ name: raw.name,
25
+ registeredAt: typeof raw.registeredAt === "string" ? raw.registeredAt : "",
26
+ ...(typeof raw.baseUrl === "string" ? { baseUrl: raw.baseUrl } : {}),
27
+ };
28
+ }
29
+ catch {
30
+ return null;
31
+ }
32
+ }
33
+ /** Persist the local device registration with owner-only permissions. */
34
+ export function writeDeviceConfig(config) {
35
+ const dir = getAximoDir();
36
+ if (!existsSync(dir))
37
+ mkdirSync(dir, { recursive: true });
38
+ const path = getDeviceConfigPath();
39
+ writeFileSync(path, JSON.stringify(config, null, 2), { mode: 0o600 });
40
+ try {
41
+ chmodSync(path, 0o600);
42
+ }
43
+ catch {
44
+ // chmod may fail on Windows; file-level ACLs apply instead.
45
+ }
46
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,56 @@
1
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
5
+ import { readDeviceConfig, writeDeviceConfig } from "./device-config.js";
6
+ let tempHome;
7
+ beforeEach(() => {
8
+ tempHome = mkdtempSync(join(tmpdir(), "aximo-device-config-"));
9
+ process.env["AXIMO_HOME"] = tempHome;
10
+ });
11
+ afterEach(() => {
12
+ delete process.env["AXIMO_HOME"];
13
+ rmSync(tempHome, { recursive: true, force: true });
14
+ });
15
+ function devicePath() {
16
+ return join(tempHome, "device.json");
17
+ }
18
+ describe("device-config", () => {
19
+ it("returns null when the file is missing", () => {
20
+ expect(readDeviceConfig()).toBeNull();
21
+ });
22
+ it("round-trips a config including baseUrl", () => {
23
+ writeDeviceConfig({
24
+ deviceId: "d1",
25
+ deviceToken: "tok",
26
+ name: "mac",
27
+ registeredAt: "2026-06-29T00:00:00.000Z",
28
+ baseUrl: "https://srv:8443",
29
+ });
30
+ expect(readDeviceConfig()).toEqual({
31
+ deviceId: "d1",
32
+ deviceToken: "tok",
33
+ name: "mac",
34
+ registeredAt: "2026-06-29T00:00:00.000Z",
35
+ baseUrl: "https://srv:8443",
36
+ });
37
+ });
38
+ it("reads a pre-AT-577 file with no baseUrl (backward compatible)", () => {
39
+ writeFileSync(devicePath(), JSON.stringify({ deviceId: "d1", deviceToken: "tok", name: "mac", registeredAt: "t" }));
40
+ const cfg = readDeviceConfig();
41
+ expect(cfg).not.toBeNull();
42
+ expect(cfg?.deviceId).toBe("d1");
43
+ expect(cfg?.baseUrl).toBeUndefined();
44
+ });
45
+ it("returns null for malformed json or a missing deviceId", () => {
46
+ writeFileSync(devicePath(), "{ not json");
47
+ expect(readDeviceConfig()).toBeNull();
48
+ writeFileSync(devicePath(), JSON.stringify({ deviceToken: "tok", name: "mac" }));
49
+ expect(readDeviceConfig()).toBeNull();
50
+ });
51
+ it("writes the file with owner-only permissions", () => {
52
+ writeDeviceConfig({ deviceId: "d1", deviceToken: "tok", name: "mac", registeredAt: "t" });
53
+ // Sanity: file is valid JSON and readable back.
54
+ expect(JSON.parse(readFileSync(devicePath(), "utf-8"))).toMatchObject({ deviceId: "d1" });
55
+ });
56
+ });
@@ -17,6 +17,19 @@ export interface DoctorReport {
17
17
  };
18
18
  }
19
19
  export declare function relayProbeUrl(): string;
20
+ export type TlsProbeResult = {
21
+ ok: true;
22
+ } | {
23
+ ok: false;
24
+ kind: "cert" | "conn";
25
+ reason: string;
26
+ };
27
+ export interface AgentTlsDeps {
28
+ env?: NodeJS.ProcessEnv;
29
+ baseUrl?: string;
30
+ probe?: (host: string, port: number, ca: Buffer) => Promise<TlsProbeResult>;
31
+ }
32
+ export declare function checkAgentTlsTrust(deps?: AgentTlsDeps): Promise<Check>;
20
33
  export interface RunDoctorOptions {
21
34
  skipRelay?: boolean;
22
35
  skipApi?: boolean;