Skip to content

feat(sdks): fold SDK monorepo in-tree under sdks/ - #819

Open
danielpaulus wants to merge 31 commits into
mainfrom
feat/sdks-in-tree
Open

feat(sdks): fold SDK monorepo in-tree under sdks/#819
danielpaulus wants to merge 31 commits into
mainfrom
feat/sdks-in-tree

Conversation

@danielpaulus

Copy link
Copy Markdown
Owner

What & why

The official go-ios client SDKs (Python, TypeScript, Java, C#) and the MCP server are generated from a single TypeSpec → OpenAPI 3.1 spec (80 operations). Until now they lived in a separate go-ios-sdks monorepo. This PR folds that monorepo in-tree under a top-level sdks/ directory so the spec, generated clients, MCP server, and their CI travel with go-ios itself.

Only committed files were brought over (via git archive) — no node_modules, dist, .venv, bin, obj, target, or tsp-output. The committed generated client code (src/generated, _generated, generated/, src/Generated) stays tracked; the nested sdks/.gitignore + per-package ignores (with their re-negations) handle this.

Layout

sdks/
  spec/                     TypeSpec sources + emitted OpenAPI 3.1 (openapi/openapi.yaml, 80 ops)
  packages/
    typescript/             @go-ios/sdk (src + test + committed src/generated)
    python/                 go-ios-sdk (src + tests + committed _generated)
    java/                   Maven package (src + committed generated/, scripts/)
    csharp/                 GoIos.Sdk solution (src + tests + committed src/Generated)
    mcp/                    MCP server
  docs/DESIGN.md
  scripts/
  README.md, PROJECT-STATUS.md, .gitignore

The root README.md gains a brief SDKs & MCP section pointing at sdks/.

CI — scoped to sdks/**

The SDK workflows must live at repo-root .github/workflows/ to run, but are scoped to sdks/** and renamed so they never collide with go-ios's existing workflows. No existing go-ios workflow is modified — only these are added:

File Trigger Notes
sdks.yml PR + push-to-main on sdks/** "SDKs CI": spec compile + TS/MCP/Python/C#/Java build+test
sdks-publish-typescript.yml workflow_dispatch only inert (no auto-publish)
sdks-publish-python.yml push/PR on sdks/packages/python/**, tag python-v*, dispatch publish gated on python-v* tag / opt-in dispatch
sdks-publish-csharp.yml push/PR on sdks/packages/csharp/**, tag csharp-v*, dispatch publish gated on csharp-v* tag; NuGet push skipped without secret
sdks-publish-java.yml push/PR on sdks/packages/java/** build+test only

All working-directory:, cache-dependency-path:, and paths: were repathed under sdks/. Publish workflows stay inert — gating unchanged. Publish tags are prefixed (csharp-v*, python-v*) so they do not collide with go-ios's own v* release tags.

Verification

  • go build ./... — passes (no Go sources under sdks/; go.work/go.mod untouched).
  • go test ./... — all packages pass.
  • git status — clean; confirmed no node_modules/dist/.venv/bin/obj/target/tsp-output staged (verified the ignores actively catch fabricated artifacts under sdks/).
  • sdks/spec/openapi/openapi.yaml present with 80 operations; each sdks/packages/* has source + tests + manifest; every workflow path points at a real file.

For the maintainer to decide

  • CI path-filtering: SDK-only PRs still trigger test.yml (Go unit tests) and its chained real-device e2e, because test.yml runs on all PRs. To skip those on sdks-only changes, add paths-ignore: ['sdks/**'] to test.ymlbut verify branch-protection required checks first, since skipped required checks can block merges. Left to maintainer (I did not modify test.yml).
  • Publish tag prefixes: kept as csharp-v* / python-v* to avoid colliding with go-ios v* release tags. Confirm these prefixes match your intended SDK release tagging.

🤖 Generated with Claude Code

https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk

Bring the standalone go-ios SDK monorepo into this repo under a top-level
sdks/ directory: the TypeSpec -> OpenAPI 3.1 spec, the Python/TypeScript/
Java/C# client SDKs, and the MCP server, plus their docs and scripts.

The SDK CI is relocated to repo-root .github/workflows/ (so it runs) but
scoped to sdks/** and renamed to avoid colliding with go-ios's existing
workflows:
- sdks.yml (validation: spec compile + per-package build/test)
- sdks-publish-{typescript,python,java,csharp}.yml (tag/dispatch-gated,
  inert; publish tags kept prefixed as csharp-v* / python-v* so they never
  collide with go-ios's v* release tags)

go build ./... and go test ./... are unaffected (no Go sources under sdks/,
go.work/go.mod untouched). Build artifacts under sdks/ are ignored via the
nested sdks/.gitignore and per-package ignores; the committed generated
client code stays tracked.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk
Comment thread .github/workflows/sdks-publish-csharp.yml Fixed
Comment thread .github/workflows/sdks-publish-csharp.yml Fixed
Comment thread .github/workflows/sdks-publish-java.yml Fixed
Comment thread .github/workflows/sdks-publish-python.yml Fixed
Comment thread .github/workflows/sdks-publish-python.yml Fixed
Comment on lines +45 to +62
name: TypeScript SDK build + test
runs-on: ubuntu-latest
defaults:
run:
working-directory: sdks/packages/typescript
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm
cache-dependency-path: sdks/packages/typescript/package-lock.json
- run: npm ci
- run: npx tsc --noEmit
- run: npm run build
- run: npm test

mcp:
Comment on lines +63 to +80
name: MCP server build + test
runs-on: ubuntu-latest
defaults:
run:
working-directory: sdks/packages/mcp
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm
cache-dependency-path: sdks/packages/mcp/package-lock.json
- run: npm ci
- run: npx tsc --noEmit
- run: npm run build
- run: npm test

python:
Comment on lines +81 to +103
name: Python SDK test (3.9-3.13) + types
runs-on: ubuntu-latest
defaults:
run:
working-directory: sdks/packages/python
strategy:
matrix:
python-version: ["3.9", "3.13"]
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v5
- name: Set up Python ${{ matrix.python-version }}
run: uv python install ${{ matrix.python-version }}
- name: Sync deps
run: uv sync --all-extras --dev
- name: Test
run: uv run pytest
- name: Type check (facade)
run: uv run mypy src/go_ios_sdk --exclude '_generated'
continue-on-error: false

csharp:
Comment on lines +104 to +117
name: C# SDK build + test
runs-on: ubuntu-latest
defaults:
run:
working-directory: sdks/packages/csharp
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: "8.0.x"
- run: dotnet build -c Release
- run: dotnet test -c Release --no-build

java:
Comment on lines +118 to +130
name: Java SDK compile + test
runs-on: ubuntu-latest
defaults:
run:
working-directory: sdks/packages/java
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"
- name: Compile + test (javac + JUnit console)
run: bash scripts/verify.sh
Replace the four inert per-package publish workflows with ONE
dispatch-only release pipeline for all five SDKs, generated from one
OpenAPI spec and versioned in lockstep.

- sdks/scripts/set-version.sh stamps one version into every manifest
  (ts/mcp package.json, python pyproject.toml, java pom.xml, csharp
  csproj) and fails loudly if any manifest is not updated.
- release-sdks.yml: workflow_dispatch only (version + dry_run, default
  true). Build+test all five before any publish. Each ecosystem publish
  is gated twice: dry_run does a real dry-run (npm --dry-run, twine
  check, mvn verify, dotnet pack) with no upload; a registry-armed guard
  makes even a real run self-skip until the registry is configured.
  npm via OIDC (no token), PyPI via trusted publishing, Maven Central via
  central-publishing-maven-plugin + GPG, NuGet via dotnet nuget push.
  git tag sdk-v<version> + GitHub release only after all publishes
  succeed and not dry_run.
- Delete sdks-publish-{typescript,python,csharp,java}.yml; keep sdks.yml
  CI as-is (comment updated).
- sdks/docs/RELEASING.md: how to cut a release (dry_run first),
  lockstep model, and the registry prerequisites; until armed, real
  publishes self-skip.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk
@danielpaulus

Copy link
Copy Markdown
Owner Author

Added: consolidated dispatch-only SDK release pipeline

Replaced the four inert per-package publish workflows with one dispatch-only pipeline, .github/workflows/release-sdks.yml. The five SDKs are generated from one OpenAPI spec, so they release in lockstep — a single version input is stamped into every manifest and shipped to every registry together. Kept entirely separate from the CLI's release.yml.

release-sdks.yml design

  • Trigger: workflow_dispatch only, inputs version (required) and dry_run (default true). No tag/push trigger — nothing runs off a merge.
  • prepare: sdks/scripts/set-version.sh stamps version into typescript/mcp package.json, python pyproject.toml, java pom.xml, and the packable csharp .csproj; it fails loudly if any manifest isn't updated. The stamped tree is passed to the build jobs as an artifact.
  • Build+test all five (mirrors sdks.yml: node for ts+mcp, uv for python, dotnet for csharp, scripts/verify.sh for java). No publish job starts until all five pass.
  • Publish jobs (one per ecosystem), gated twice:
    1. if: ${{ !inputs.dry_run }} — a dry run does a real dry-run instead: npm publish --dry-run, uv build + twine check, mvn verify, dotnet pack — no upload.
    2. Registry-armed guard — even a real (non-dry) run self-skips the upload with a ::warning:: when the registry isn't configured yet, so the first real run is safe before any registry exists.
  • Auth: npm via OIDC (id-token: write, provenance, no token / no .npmrc line); PyPI via trusted publishing (pypa/gh-action-pypi-publish); Maven Central via central-publishing-maven-plugin + GPG; NuGet via dotnet nuget push.
  • Final job tag-and-release: creates sdk-v<version> tag + GitHub release, only after every publish succeeds and not dry_run. All mutating/outbound steps are in the publish + final stages, so an earlier failure ships nothing.

packages/mcp is private: true; it's versioned and built/tested for parity but not published to npm.

Removed

  • .github/workflows/sdks-publish-typescript.yml
  • .github/workflows/sdks-publish-python.yml
  • .github/workflows/sdks-publish-csharp.yml
  • .github/workflows/sdks-publish-java.yml

sdks.yml (CI) is unchanged except a one-line comment update pointing at the new workflow.

Registry prerequisites (maintainer, before real publishing)

Until each is set up, that ecosystem's real publish self-skips (run still succeeds):

  • npm — create @go-ios org + @go-ios/sdk, register OIDC trusted publisher for this repo/workflow. No token.
  • PyPI — create project go-ios-sdk + trusted publisher; set repo secret PYPI_TRUSTED_PUBLISHER_CONFIGURED (arm flag).
  • Maven Central — register namespace (com.github.danielpaulus or io.github.*) + GPG key; set MAVEN_GPG_PRIVATE_KEY, MAVEN_GPG_PASSPHRASE, CENTRAL_TOKEN_USERNAME, CENTRAL_TOKEN_PASSWORD.
  • NuGet — reserve GoIos.Sdk id; set NUGET_API_KEY.

Full instructions in sdks/docs/RELEASING.md.

Verified

  • release-sdks.yml passes actionlint clean; all workflow YAML parses.
  • go build ./... and go test ./... still pass (no Go changes).
  • Dispatch-only, dry_run default true — nothing can publish until the registries are armed. Workflow not run.

danielpaulus and others added 11 commits August 10, 2026 22:39
Extend the in-tree TypeSpec spec from the spec-v2 parity surface (80 ops /
65 paths) to the feature-complete daemon surface on feature/restapi-complete:
125 operations across 107 paths under /api/v1, a 1:1 match to the daemon's
registered routes (excluding /healthz, /readyz, /swagger).

Adds the 45 new operations from Waves 1-4 as new route files:
- routes-diagnostics.tsp (w1a): diskspace, ip, rsd, battery/registry; and a
  `?domain=` query on the existing /lockdown op.
- routes-accessibility.tsp (w1b): GET/PUT voiceover, GET/PUT zoom, POST ax/audit,
  GET ax, PUT setlocation/gpx (multipart).
- routes-fsync.tsp (w1c): fsync ls/tree/pull(binary)/push(raw|multipart)/rm/mkdir
  (?path=,?bundleID=), cloudconfig, and host-scoped /prepare/skip-options.
- routes-webinspector.tsp (w1d): pages, launch, eval (424 when disabled).
- routes-ui.tsp (w2): tap/swipe/longpress/type/button/api, app launch/terminate/
  foreground, screenshot(PNG)/source/size/orientation/status, PUT orientation;
  proxied to a forwarded WDA/DeviceKit backend (backend/wdaUrl/timeout params).
- routes-streams.tsp (w3): ui/stream, screenshot/stream, pcap modeled as BINARY
  byte streams (x-stream: binary, x-content-type), NOT text/event-stream SSE.
- routes-sign.tsp (w4): host-scoped /sign/{certificate,provision,app} and
  /prepare/create-cert, plus device-scoped /device/{udid}/prepare (multipart).

All request/response models added to models.tsp mirroring the Go structs; adds
413/424/501 error models. Re-emits canonical OpenAPI 3.1 + 3.2 via regen.sh and
documents the new groups and binary-vs-SSE distinction in docs/DESIGN.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk
…ublishable

Extend the curated MCP tool set (31 -> 44) for the feature-complete daemon and
flip the package to publishable.

Publishable:
- remove `private: true`; add repo/homepage/bugs/keywords metadata and
  `publishConfig` { access: public, provenance: true } (matches @go-ios/typescript).

New tools:
- UI automation (drive the device via a forwarded WDA/DeviceKit backend):
  ui_tap, ui_swipe, ui_type, ui_press_button, ui_source (view hierarchy, bounded),
  ui_screenshot (single frame via UI backend), ui_app_launch, ui_app_terminate.
  All take backend/wdaUrl/timeout; descriptions document the run_wda + forward
  prerequisite.
- Diagnostics: device_diskspace, device_ip.
- Files (read-only over AFC): list_device_files, read_file (bounded 512 KiB).
- Location: set_location_gpx (multipart upload).
- Web debugging: list_webinspector_pages, webinspector_eval (JS eval).

Client: getBytes now takes query params; getTextBounded takes an accept override;
friendly errors for 424/501/502 (UI/WebInspector prerequisites).

Deliberately omitted: sign/*, prepare (host-local/secret-handling), erase
(destructive), raw pcap/screenshot/ui video streams (too large; single-frame or
bounded-capture instead), fsync/files writes+deletes. reboot/shutdown stay
DISRUPTIVE-flagged.

Tests: registration covers new tools via CURATED_TOOL_NAMES + udid invariant;
added UI-prerequisite assertions and handler tests (ui_tap/ui_type/ui_source/
ui_screenshot, device_diskspace, list_device_files, read_file, webinspector_eval,
424 surfacing). 37 tests green; build + tsc --noEmit clean. README updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk
Regenerate the hey-api client from the 125-op openapi.yaml and extend the
ergonomic facade with the new groups, keeping the existing shape and
cross-language-consistent naming:

- device: diskSpace/ip/rsd/batteryRegistry/cloudConfig; lockdown(domain?);
  ax/axAudit; voiceOver/setVoiceOver; zoom/setZoom; setLocationGpx; prepare
- device.fsync: ls/tree/pull/push/rm/mkdir (bundleId-scoped)
- device.webinspector: pages/launch/eval
- device.ui: tap/swipe/longPress/type/button/screenshot/source/size/
  orientation/setOrientation/status/api + app.launch|terminate|foreground
- binary streams (raw bytes, not SSE): device.ui.stream, device.screenshotStream,
  device.pcap via a new reusable BinaryStream helper over Response.body with
  AbortSignal support
- client.sign: certificate/provision/app; client.prepare: createCert/skipOptions

Add vitest coverage for the new groups plus binary-stream tests (mock chunked
body iteration, mid-stream + pre-aborted cancellation, error mapping); existing
SSE tests still pass. Update the README. build/test/tsc all green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk
Regenerate the vendored openapi-python-client (0.26.1) from the 125-operation
spec and extend both the sync and async facades with the new v3 groups:

- device diagnostics/network: disk_space, ip, rsd, battery_registry, and a
  domain-scoped lockdown(domain=...)
- accessibility/location: voice_over/set_voice_over, zoom/set_zoom, ax,
  ax_audit, set_location_gpx (multipart)
- AFC fsync: ls/tree/pull/push/rm/mkdir (bundle_id -> bundleID) and cloud_config
- webinspector: pages/launch/eval
- ui automation: tap/swipe/long_press/type/button/screenshot/source/size/
  orientation/set_orientation/status/api and ui.app.launch|terminate|foreground,
  all with backend/wda_url/timeout kwargs
- binary streams (raw bytes, NOT SSE): ui.stream, screenshot_stream, pcap via a
  reusable byte-chunk stream helper (sync generator + async generator over httpx
  streaming, cancelable)
- device-scoped prepare (multipart) and host-scoped sign.certificate/provision/app
  + prepare.create_cert/skip_options

Adds facade tests for representative new groups and binary streams (including a
chunked-transport boundary/cancel test and async cancellation), plus README
docs. pytest (69) green; mypy and ruff clean on the facade.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk
Regenerate the low-level client from the 125-op OpenAPI spec and add an
ergonomic hand-written facade over java.net.http covering the complete
go-ios REST surface, matching the TS/Python/C# SDK naming.

New facade groups on top of the base ~80 ops:
- Device: diskSpace/ip/rsd/batteryRegistry, lockdown()/lockdown(domain),
  voiceOver/setVoiceOver, zoom/setZoom, ax/axAudit, setLocationGpx,
  cloudConfig, prepare (multipart).
- Device.fsync(): ls/tree/pull(->byte[])/push/rm/mkdir (path + optional bundleId).
- Device.webinspector(): pages/launch/eval.
- Device.ui(): tap/swipe/longPress/type/button/screenshot(->byte[])/source/
  size/orientation/setOrientation/status/app launch|terminate|foreground/api,
  with backend/wdaUrl/timeout via Ui.Options.
- Binary streams (x-stream: binary, not SSE): Device.ui().stream(),
  screenshotStream(), pcap() return a closeable BinaryStream (InputStream).
- Host-scoped: client.sign().app/certificate(->byte[])/provision(),
  client.prepare().createCert()/skipOptions().

Streaming: typed SseReader (syslog/notifications/ostrace/listen/sysmontap/
job logs) plus a separate raw BinaryStream seam. Binary/multipart/octet-stream
endpoints go through the RawHttp helper for byte-exact wire formats.

Tests: add V3SurfaceHttpTest (new groups + a chunked binary-stream test);
existing facade + SSE suites still pass. 41 tests green via scripts/verify.sh
(javac --release 17 + JUnit console; no Maven). Update README to the 125-op
surface.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk
Regenerate the low-level csharp client from the 125-op spec (regen.sh;
anyOf-Heartbeat dangling-comma post-gen fix still applies clean) and extend
the hand-written facade with the new groups, matching the shared SDK API shape:

- DeviceClient: DiskSpace/Ip/Rsd/BatteryRegistry, VoiceOver/Zoom (+setters),
  AxAudit/Ax, SetLocationGpx, CloudConfig, Prepare; LockdownAsync(domain).
- DeviceClient.Fsync: Ls/Tree/Pull(->byte[])/Push/Rm/Mkdir (path + bundleId).
- DeviceClient.WebInspector: Pages/Launch/Eval.
- DeviceClient.Ui: Tap/Swipe/LongPress/Type/Button/Screenshot(->byte[])/Source/
  Size/Orientation(+set)/Status/App launch|terminate|foreground/Api, with
  backend/wdaUrl/timeout Options.
- Raw binary streams (BinaryStream : Stream via ResponseHeadersRead, token-aware,
  disposable): Ui.Stream, ScreenshotStream, Pcap. Separate from the SseReader.
- Host: client.Sign.Certificate(->byte[])/Provision/App(->byte[]);
  client.Prepare.CreateCert/SkipOptions.

xUnit tests cover the new groups plus binary-stream chunked-read and
cancellation; SSE tests unchanged. README updated. dotnet build/test -c Release
green (50/50).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk
…tignore

The MCP package dropped private:true, so wire it into release-sdks.yml's
publish-npm job: build + npm publish (OIDC, --access public, provenance) for
sdks/packages/mcp, dry-run/OIDC-guarded exactly like the TypeScript publish.
Removed the stale "mcp is private" note. The job already depends on build-mcp.

Also harden sdks/packages/java/.gitignore: the repo-root bare `main` pattern
(for the compiled go-ios binary) also matched this Maven package's src/main/,
which silently untracked the hand-written Java facade sources. Add !src/**
negations so the SDK sources are always tracked.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk
@danielpaulus

Copy link
Copy Markdown
Owner Author

v3 integration: full 125-endpoint SDK surface

Merged the v3 spec + all five package regens onto this branch and verified everything green.

Coverage

Fixes in this batch

  • .gitignore hardening (Java src/main): the repo-root bare main pattern (for the compiled go-ios binary) also matched this Maven package's src/main/, silently untracking the hand-written Java facade sources. Added !src/** negations in sdks/packages/java/.gitignore; git check-ignore on the facade sources now returns nothing and all 31 src/main files are tracked.
  • release-sdks.yml: wired @go-ios/mcp into the publish-npm job (build + npm publish via OIDC / --access public / provenance, working-directory: sdks/packages/mcp), gated on the mcp build job and dry-run/OIDC-guarded exactly like the TS publish. Removed the stale "mcp is private" note.

Verification (all green, local toolchains)

  • Spec: tsp compile clean, 125 ops, reproduces committed output.
  • TypeScript: build + 72 tests pass.
  • MCP: build + 37 tests pass (44 registered tools).
  • Python: 69 pytest pass; ruff clean; mypy clean.
  • Java: scripts/verify.sh — 41 tests pass.
  • C#: dotnet test -c Release (.NET 8) — 50 tests pass.
  • go build ./... clean (Go tree unaffected).
  • All workflow YAMLs valid (actionlint clean).

danielpaulus and others added 10 commits August 11, 2026 11:19
Add examples/GoIos.Examples: a heavily-commented console project that
references the SDK facade and doubles as documentation and a pre-release
smoke test. Env-configured (GO_IOS_BASE_URL / GO_IOS_API_KEY / GO_IOS_UDID /
RUN_UI). Covers list-devices, device-info, list-apps, screenshot,
stream-syslog, and an optional (RUN_UI=1) ui-automation example that skips
gracefully when WDA is unreachable.

Program.cs dispatches by arg; `run-all` runs examples 1-5 in sequence,
returns non-zero on any exception, and prints SKIP (not FAIL) for no-device /
no-WDA steps. Adds examples/run.sh, examples/README.md, and an Examples
section in the package README.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk
Add sdks/packages/mcp/examples/: paste-ready + annotated MCP client
configs (Claude Desktop, generic), a stdio `list-tools` script that
spawns the built server and introspects its curated tool set (no
device/daemon needed), an optional `call-tool` that exercises
list_devices against a running daemon (SKIPs gracefully if unreachable),
and a `run-all` runner wired to `npm run examples` that asserts the exact
44-tool set is present and runs call-tool only when GO_IOS_API_KEY is set
and the daemon is reachable. README documents it and the package README
links to it. Adds tsx as a devDependency; the examples dir is not
published (package `files` is dist + README only).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk
…nner

Add sdks/packages/python/examples/: standalone, heavily-commented scripts that
double as docs and as a pre-release smoke test against a live go-ios daemon.

- 01_list_devices, 02_device_info, 03_list_apps, 04_screenshot,
  05_stream_syslog (SSE, bounded), 06_async_stream (AsyncIosClient sysmontap,
  bounded), 07_ui_automation (optional; skips if WDA backend unreachable).
- run_all.py runs 01-06 in sequence (07 only when RUN_UI=1); exits non-zero on
  any unexpected exception, prints SKIP (exit 0) for no-device / unreachable-UI.
- Env-configured: GO_IOS_BASE_URL (default http://localhost:8080), GO_IOS_API_KEY
  (required; helpful non-zero exit when unset), GO_IOS_UDID (optional).
- examples/README.md documents daemon setup + how to run; linked from package
  README via a new Examples section.

Verified: py_compile clean, ruff/mypy/pytest green; runner exit behavior and
no-device SKIP path checked against a mock daemon.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk
Add a heavily-commented examples suite under sdks/packages/java/examples/
that doubles as documentation and a pre-release smoke test. Each class is a
standalone main configured via GO_IOS_BASE_URL / GO_IOS_API_KEY / GO_IOS_UDID:

- ListDevicesExample, DeviceInfoExample, ListAppsExample, ScreenshotExample,
  StreamSyslogExample (SSE via SseReader), and an optional UiAutomationExample
  (WDA tap+type, gated on RUN_UI=1, skips gracefully when unreachable).
- RunAllExamples runs 1-5 in sequence and exits non-zero on any exception;
  device-dependent steps print SKIP when no device is attached so the suite
  passes on a device-less daemon. Missing GO_IOS_API_KEY -> helpful message + exit 1.
- run.sh compiles the SDK + examples via javac (mirroring scripts/verify.sh's
  classpath) and runs the driver; --compile-only builds without a daemon.
- examples/README.md documents daemon startup, env, and compile/run; linked
  from the package README's new "Examples" section.

Verified: examples compile via javac (JDK 17+); scripts/verify.sh still green;
go build ./... clean. Build output (examples/target/, .tools/) is already
ignored by the package .gitignore, so no .gitignore changes were needed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk
Add sdks/packages/typescript/examples/: standalone, heavily-commented
.ts scripts that double as docs and a pre-release smoke test. Cover the
core public API (list devices, device info, list apps, screenshot, SSE
syslog stream, opt-in UI automation), all configured via env vars
(GO_IOS_BASE_URL/GO_IOS_API_KEY/GO_IOS_UDID).

run-all.ts runs 01-05 (06 only when RUN_UI=1) sharing one client and
exits non-zero if any example genuinely fails; device-dependent steps
that can't run (no device / UI backend not forwarded) SKIP without
failing. Missing GO_IOS_API_KEY prints help and exits non-zero.

Add tsx devDep, `examples` + `examples:build` scripts, examples/tsconfig
for typechecking, examples/README.md, and an Examples section in the
package README. Ignore the screenshot.png artifact.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk
Integrate the five per-language examples suites (typescript, python, java,
csharp, mcp) and tie them together:

- sdks/docs/EXAMPLES.md: top-level overview (docs + pre-release smoke test),
  per-language index, shared GO_IOS_BASE_URL/GO_IOS_API_KEY/GO_IOS_UDID/RUN_UI
  convention, how to run them all. Linked from sdks/README.md. Documents the
  canonical daemon port (8080, matching the daemon's --addr default) and the
  SDK-library-vs-daemon default discrepancy (libraries default to :60105 from
  the spec servers URL) for a follow-up reconcile.
- sdks.yml: add the device-free MCP list-tools smoke check (npm run examples)
  to the mcp job so a broken MCP server fails CI.
- verify-sdk-examples.yml: new dispatch-only, farm-gated workflow that runs
  every SDK's example runner against a live go-ios REST daemon + device. Inert
  until the full REST daemon lands on main (#817/#821); the daemon-start step
  is a documented placeholder.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@danielpaulus

Copy link
Copy Markdown
Owner Author

Integrated the five example suites (typescript, python, java, csharp, mcp) into this PR — each merged cleanly (every branch touched only its own sdks/packages/<lang>/examples/ dir plus that package's README/package.json). On top of the merges:

  • Examples overview: added sdks/docs/EXAMPLES.md (docs + pre-release smoke test, per-language index linking each examples/README.md, shared GO_IOS_BASE_URL / GO_IOS_API_KEY / GO_IOS_UDID / RUN_UI env convention, how to run all). Linked from sdks/README.md.
  • Base-URL default reconciled: confirmed the real go-ios REST daemon defaults to :8080 (--addr default in restapi/api/server.go on feature/restapi-parity). All examples already target http://localhost:8080, so no example changes were needed. Noted in the doc the SDK-library-vs-daemon discrepancy — the client libraries default baseUrl to http://localhost:60105 (from the OpenAPI servers URL), which does not match the daemon's :8080. Library defaults left unchanged (out of scope); flagged for a follow-up reconcile.
  • CI gate (device-free): the mcp job in sdks.yml now runs npm run examples, the list-tools smoke check that asserts the exact curated 44-tool set. A broken MCP server now fails CI. (call-tool auto-skips without a daemon.)
  • Device verification (farm-gated): added .github/workflows/verify-sdk-examples.ymlworkflow_dispatch-only — that runs every SDK's example runner against a live daemon + device on the self-hosted farm. It is inert today: the full REST daemon is not on main yet (lands with restapi: full CLI parity + production readiness #817/feat(restapi): full CLI-parity endpoints (Waves 1-4: diagnostics, a11y, fsync, webinspector, UI automation, streams, codesigning) + uidriver #821), so the daemon-start step is a documented placeholder and a guard makes the job fail loudly if dispatched early. It activates once the daemon is deployable on office01/ganjalf.

Local verification: MCP npm run examples prints the 44-tool PASS and exits 0; TS build+test+examples:build green; spec tsp compile clean; Python pytest 69 passed; Java verify.sh + examples compile-only green; go build ./... clean; both workflow YAMLs pass actionlint. C# dotnet test was not run locally (no .NET SDK currently installed on this box) but the merge is confined to examples/ and CI's csharp job covers it.

Comment on lines +42 to +111
name: Run every SDK example runner against a live daemon
# Runs on the self-hosted device farm (office01 / ganjalf), reusing the same
# runner labels as real-device.yml. Until #817/#821 land and the daemon is
# deployable there, do not dispatch this.
runs-on: [self-hosted, macOS]
env:
GO_IOS_BASE_URL: ${{ vars.GO_IOS_BASE_URL || 'http://localhost:8080' }}
GO_IOS_API_KEY: ${{ secrets.GO_IOS_API_KEY }}
RUN_UI: ${{ inputs.run_ui && '1' || '' }}
steps:
- uses: actions/checkout@v4

# ─────────────────────────────────────────────────────────────────────
# PLACEHOLDER — activate once the full REST daemon is on main (#817/#821).
#
# The daemon does not exist on `main` yet, so we cannot build/start it
# here. When it lands, replace this step with something like:
#
# - name: Build & start go-ios REST daemon
# run: |
# go build -o /tmp/ios ./
# /tmp/ios api --api-key "$GO_IOS_API_KEY" --addr :8080 &
# # wait until GET $GO_IOS_BASE_URL/health responds
# for i in $(seq 1 30); do
# curl -fsS -H "Authorization: Bearer $GO_IOS_API_KEY" \
# "$GO_IOS_BASE_URL/api/v1/health" && break || sleep 1
# done
#
# (Default bind address is :8080 — see restapi/api/server.go and
# sdks/docs/EXAMPLES.md.)
# ─────────────────────────────────────────────────────────────────────
- name: Start go-ios REST daemon (PLACEHOLDER — see #817/#821)
run: |
echo "::error::verify-sdk-examples is inert until the full go-ios REST daemon lands on main (#817/#821)."
echo "Fill in the daemon-start step above and remove this guard once the daemon is deployable on the farm."
exit 1

# --- Language toolchains (kept ready for when the gate above is removed) ---
- uses: actions/setup-node@v4
with:
node-version: "22"
- uses: astral-sh/setup-uv@v5
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"
- uses: actions/setup-dotnet@v4
with:
dotnet-version: "8.0.x"

# --- Run each SDK's example runner; any non-zero exit fails the job ---
- name: TypeScript examples
working-directory: sdks/packages/typescript
run: npm ci && npm run examples

- name: Python examples
working-directory: sdks/packages/python
run: uv sync --all-extras && uv run python examples/run_all.py

- name: Java examples
working-directory: sdks/packages/java
run: bash examples/run.sh

- name: C# examples
working-directory: sdks/packages/csharp
run: dotnet run --project examples/GoIos.Examples -- run-all

- name: MCP examples (list-tools + call-tool against the live daemon)
working-directory: sdks/packages/mcp
run: npm ci && npm run build && npm run examples
danielpaulus and others added 2 commits August 11, 2026 11:57
Make IosClient's baseUrl optional. Resolution order: explicit baseUrl
option (verbatim) > GO_IOS_BASE_URL env > discovery of the local daemon
(<GO_IOS_HOME | ~/.go-ios>/rest-api.json baseUrl) > clear throw.

Adds src/discovery.ts (home resolution + sync read/parse of rest-api.json)
and discovery unit tests; updates the README to document discovery.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk
Make baseUrl optional on IosClient.Builder. When it is not set explicitly,
resolve the daemon endpoint in order: explicit .baseUrl() > GO_IOS_BASE_URL
env > discovery of <home>/rest-api.json (home = GO_IOS_HOME or ~/.go-ios) >
IosDiscoveryException naming the expected path.

Add a Discovery class (with an injectable env/system-property seam for tests)
and an IosDiscoveryException. apiKey handling is unchanged and never read from
the discovery file. Adds DiscoveryTest and updates the README.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk
danielpaulus and others added 6 commits August 11, 2026 11:57
…ort)

Make IosClientOptions.BaseUrl optional and resolve the daemon address in
order: explicit Options.BaseUrl > GO_IOS_BASE_URL env > discovery of
<home>/rest-api.json > DaemonNotFoundException. Add a Discovery class
(home = GO_IOS_HOME or ~/.go-ios; reads baseUrl via System.Text.Json) and a
parameterless IosClient() ctor. ApiKey unchanged.

Adds xUnit discovery tests (temp GO_IOS_HOME + rest-api.json) and updates the
README with a "Connecting (daemon discovery)" section.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk
Make base_url optional on IosClient/AsyncIosClient. Resolution order:
explicit base_url arg > GO_IOS_BASE_URL env > discovery file
(<GO_IOS_HOME or ~/.go-ios>/rest-api.json baseUrl) > clear DiscoveryError.
api_key now also falls back to GO_IOS_API_KEY.

Adds discovery.py (home resolution + read/parse of rest-api.json, with a
stale-pid hint) and discovery tests. Examples default to discovery when
GO_IOS_BASE_URL is unset. README/examples docs updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk
…daemon

The Python examples already default to discovery. Do the same for the
TypeScript, Java, and C# examples: when GO_IOS_BASE_URL is unset, construct the
client with no baseUrl so the SDK auto-discovers the local daemon via
~/.go-ios/rest-api.json — no more hardcoded http://localhost:8080 fallback.

Update each examples/README and sdks/docs/EXAMPLES.md: the daemon now uses an
ephemeral loopback port and the SDKs auto-discover it (resolution order:
explicit baseUrl > GO_IOS_BASE_URL > discovery file > error). Documents that the
earlier SDK-default (:60105) vs daemon (:8080) mismatch is resolved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@danielpaulus

Copy link
Copy Markdown
Owner Author

Integrated the four port-discovery branches (discovery/typescript, discovery/python, discovery/java, discovery/csharp) into this PR — all merged cleanly (each touched only its own sdks/packages/<lang>/).

All 4 SDKs now auto-discover the local daemon via ~/.go-ios/rest-api.json (resolution order: explicit baseUrl > GO_IOS_BASE_URL env > discovery file > clear error). There is no hardcoded port default anymore — this resolves the earlier SDK-default (:60105) vs daemon (:8080) mismatch; the OpenAPI servers URL is now a doc placeholder only.

Examples harmonized: TS (_shared.ts), Java (Env.java), and C# (ExampleContext.cs) now match the Python examples — when GO_IOS_BASE_URL is unset they construct the client with no baseUrl so discovery kicks in (no http://localhost:8080 fallback). Each examples/README and sdks/docs/EXAMPLES.md updated: the daemon uses an ephemeral loopback port by default and the SDKs auto-discover it (pin with --addr :8080 / GO_IOS_BASE_URL).

Pairs with daemon PR #825 (ephemeral port + discovery-file writer).

Verification (local): go build ./... clean; TS tsc --noEmit + build + test (80) + examples:build green; MCP build + test (37) green; Python pytest (86) + mypy + ruff + py_compile examples/*.py green; Java verify.sh (54) + examples run.sh --compile-only green; C# dotnet build -c Release + dotnet test -c Release (58) + examples project build green.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants