You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Migrate to @executor-js/emulate 0.7.0's typed control-plane client
0.7.0 ships EmulatorClient + connectEmulator: one typed handle over the
/_emulate control plane (credentials.mint, ledger.list/clear, seed,
reset, manifest, ...) wherever the emulator runs — locally spawned or
hosted. Every hand-rolled fetch + response cast goes away:
- connect-handoff: mints via credentials.mint and asserts against
LedgerEntry[] (match on request.body) instead of substring-searching
the raw ledger text.
- targets/cloud setAccessTokenTtl: client.seed() instead of a raw POST.
- CLI ledger command: typed entries, JSON output.
- emulate skill: the client table replaces the raw-endpoint table as
the primary interface; raw /_emulate stays documented for curl use.
Also folds the CLI onto src/ports.ts claimPorts (from the worktree-env
PR this branch now stacks on) — the CLI's ad-hoc probe-and-walk is
gone; a held block lock marks the instance and releases on down.
Verified: connect-handoff green on selfhost + cloud (the ledger
assertion exercises the hosted resend emulator's typed client), the
token-expiry mcp-execute scenario green on cloud (exercises seed).
|`client.openapiUrl`| The spec URL — feed it straight to Executor's addSpec to register the emulator as an integration |
53
+
|`client.credentials.mint({type:"api-key"})`|`IssuedCredential` in the service's real shape: API keys, bearer tokens, OAuth/OIDC clients, client-credentials apps |
|`client.seed({...})`| Add state via the service's seed schema (e.g. WorkOS `{oauth:{default_access_token_ttl_seconds:60}}` to compress token expiry) |
56
+
|`client.reset()`| Reset state + logs, replay seed — works remotely, unlike the old local-only reset |
57
+
|`client.manifest()` / `.quickstart()` / `.specs()` / `.coverage()`| What the service is, which operations are real vs partial |
|`GET /_emulate/openapi`| A real OpenAPI document for the service — feed it straight to Executor's addSpec to register the emulator as an integration |
53
-
|`POST /_emulate/credentials`| Mint a credential in the service's real shape: `{"type":"api-key"}`, bearer tokens, OAuth/OIDC clients, client-credentials apps |
|`POST /_emulate/seed`| Add state via the service's seed schema (e.g. WorkOS `{"oauth":{"default_access_token_ttl_seconds":60}}` to compress token expiry) |
56
-
|`POST /_emulate/reset`| Reset state + logs, replay seed |
57
-
|`GET /_emulate/state`| Current store snapshot |
58
-
|`GET /_emulate/coverage`| Which operations are implemented vs partial |
59
-
|`GET /_emulate/connections`| Copyable SDK/env/curl snippets resolved against this instance |
60
+
The same routes exist as raw HTTP under `/_emulate/*` (start at
61
+
`GET /_emulate/quickstart`, written for agents) for curl/browser use — but in
62
+
TypeScript, reach for the client; the types are the point.
60
63
61
64
## Recipes
62
65
63
66
**Test an integration end-to-end for real** (the `connect-handoff` pattern):
64
-
mint an API key via `/_emulate/credentials` → register the emulator's
65
-
`/_emulate/openapi` spec with the product → invoke a tool through the
66
-
product → assert the call landed by reading `/_emulate/ledger`. The ledger
67
-
is the proof — "the product made this exact upstream call with this auth" —
68
-
which beats asserting on the product's own response.
67
+
`client.credentials.mint(...)` → register `client.openapiUrl` with the
68
+
product → invoke a tool through the product → find the call in
69
+
`client.ledger.list()` (match on `entry.request.body` / `operationId`). The
70
+
ledger is the proof — "the product made this exact upstream call with this
71
+
auth" — which beats asserting on the product's own response.
69
72
70
73
**Real OAuth/OIDC flows**: google/okta/microsoft/apple/clerk/workos mint
71
74
OAuth clients and run real authorize/token endpoints. The WorkOS emulator
@@ -75,9 +78,10 @@ clients, and Vault KV. Real SDK + `WORKOS_API_URL` override = the product's
75
78
untouched auth code against it. Set `EMULATE_WORKOS_AUDIENCE=<client_id>`
76
79
before `createEmulator` so minted MCP access tokens carry the right audience.
77
80
78
-
**A live, human-pokeable cloud instance with zero .env**: see
79
-
`e2e/scripts/cloud-demo.ts` and `e2e/setup/cloud.globalsetup.ts` — WorkOS +
80
-
Autumn emulators + the app's real dev stack.
81
+
**A live, human-pokeable cloud instance with zero .env**:
82
+
`cd e2e && bun run cli up cloud --share` — WorkOS + Autumn emulators + the
83
+
app's real dev stack (recipe in `e2e/setup/cloud.boot.ts`), fronted with
0 commit comments