@usestatemachines/sdk 0.8.0 → 0.9.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 (48) hide show
  1. package/README.md +18 -1
  2. package/agents/SKILL.md +18 -4
  3. package/agents/index.js +7 -7
  4. package/agents/llms.txt +19 -1
  5. package/agents/prompt.md +11 -5
  6. package/esm/extensions/ui.d.ts +59 -0
  7. package/esm/extensions/ui.js +153 -0
  8. package/esm/funcs/launch-ui-session.d.ts +15 -0
  9. package/esm/funcs/launch-ui-session.js +101 -0
  10. package/esm/funcs/list-snapshot-creators.d.ts +11 -0
  11. package/esm/funcs/list-snapshot-creators.js +90 -0
  12. package/esm/funcs/revoke-ui-session.d.ts +15 -0
  13. package/esm/funcs/revoke-ui-session.js +104 -0
  14. package/esm/funcs/snapshots-list.js +3 -0
  15. package/esm/lib/config.d.ts +2 -2
  16. package/esm/lib/config.js +2 -2
  17. package/esm/models/browser-capability.d.ts +12 -0
  18. package/esm/models/browser-capability.js +16 -0
  19. package/esm/models/environment.d.ts +8 -0
  20. package/esm/models/environment.js +9 -0
  21. package/esm/models/error.d.ts +1 -0
  22. package/esm/models/error.js +1 -0
  23. package/esm/models/index.d.ts +5 -0
  24. package/esm/models/index.js +5 -0
  25. package/esm/models/launch-ui-session.d.ts +26 -0
  26. package/esm/models/launch-ui-session.js +16 -0
  27. package/esm/models/operations/index.d.ts +3 -0
  28. package/esm/models/operations/index.js +3 -0
  29. package/esm/models/operations/launch-ui-session.d.ts +16 -0
  30. package/esm/models/operations/launch-ui-session.js +14 -0
  31. package/esm/models/operations/list-snapshot-creators.d.ts +23 -0
  32. package/esm/models/operations/list-snapshot-creators.js +20 -0
  33. package/esm/models/operations/list-snapshots.d.ts +6 -0
  34. package/esm/models/operations/list-snapshots.js +3 -0
  35. package/esm/models/operations/revoke-ui-session.d.ts +15 -0
  36. package/esm/models/operations/revoke-ui-session.js +13 -0
  37. package/esm/models/service.d.ts +2 -0
  38. package/esm/models/service.js +2 -0
  39. package/esm/models/ui-session-launch.d.ts +21 -0
  40. package/esm/models/ui-session-launch.js +25 -0
  41. package/esm/models/ui-session-mode.d.ts +11 -0
  42. package/esm/models/ui-session-mode.js +12 -0
  43. package/esm/models/ui-session-revocation.d.ts +12 -0
  44. package/esm/models/ui-session-revocation.js +16 -0
  45. package/esm/sdk/sdk.d.ts +10 -0
  46. package/esm/sdk/sdk.js +18 -0
  47. package/openapi.json +738 -78
  48. package/package.json +6 -2
package/README.md CHANGED
@@ -146,6 +146,22 @@ actors. A new environment runs each app's default version.
146
146
  Environments created with `tokenInUrl: true` carry the environment token in each service's `url`
147
147
  instead of in its `headers`.
148
148
 
149
+ ## Browser UI sessions
150
+
151
+ `@usestatemachines/sdk/ui` is the only part of the SDK for a browser. It holds no client and no
152
+ credential. Your server launches a UI session with the API key, then gives the launch only to
153
+ the browser of the user it is for. In that browser, `submitLaunch(launch, target)` posts the
154
+ launch's one-use grant into `target`: an iframe for an `embedded` launch, or a window for a
155
+ `page` launch. Open the window in the click handler, such as with
156
+ `window.open('about:blank', name)`, so the browser does not block it.
157
+
158
+ `listenToSession(launch, iframe, onMessage, { signal })` calls `onMessage` with `ready`,
159
+ `failure` and `logout`. It passes only messages from the session's origin, from that iframe and
160
+ for that session. A browser can block the session's cookie in a frame, and a blocked frame looks
161
+ loaded to your page. So wait for `ready` with a timeout, and on `failure` or the timeout, offer
162
+ a `page` launch. The page that posts the grant must send its `Origin`, so do not serve it with
163
+ `Referrer-Policy: no-referrer`.
164
+
149
165
  ## Snapshots
150
166
 
151
167
  ```ts
@@ -251,7 +267,8 @@ to })` counts them. Recording is best effort.
251
267
 
252
268
  - **Node.js**, at the versions that `engines` in `package.json` allows. ESM only.
253
269
  - **Cloudflare Workers.** Pass `apiKey` explicitly.
254
- - **Not browsers.** Never ship an API key to a browser.
270
+ - **Not browsers.** Never ship an API key to a browser. Only `@usestatemachines/sdk/ui` runs
271
+ in a browser.
255
272
 
256
273
  TypeScript consumers need `lib: ["DOM"]` or `@types/node`, for the `fetch`, `Request` and
257
274
  `Response` types. While the version is `0.x`, a minor version may include breaking changes. Enums
package/agents/SKILL.md CHANGED
@@ -5,15 +5,29 @@ description: Use when setting up State Machines or running code, integration tes
5
5
 
6
6
  ## Start with the user's task
7
7
 
8
- During skill installation, read the repository instructions and preserve its existing structure. Do not create a new project, install dependencies, write examples or create remote resources. Report the installed skill location and any reload requirement, then ask "What would you like to run or test?" and wait. Skill installation does not verify a connection.
8
+ During skill installation, read the repository instructions and preserve its existing structure. Do not create a new project, install project dependencies, write examples or create remote resources. Report the installed skill location and any reload requirement, then ask "What would you like to run or test?" and wait. Skill installation does not verify a connection.
9
9
 
10
10
  Once the user chooses a task, inspect the project's package manager, runtime, runner and secret loader. Install or reuse a compatible `@usestatemachines/sdk` only when the task needs it. Read the installed README for API signatures and `package.json` for runtime requirements. The SDK is ESM and runs on Node.js and Cloudflare Workers, not in a browser. Follow the user's task and existing project conventions without requiring a separate onboarding example.
11
11
 
12
- ## Check access in the right process
12
+ ## Get a credential
13
13
 
14
14
  The SDK reads `STATEMACHINES_API_KEY` from the process environment. It does not load `.env` files. Use the project's existing test command or secret loader. An empty variable in your current shell does not prove that the test process lacks it. Verify presence without displaying the value. In Workers, pass the binding explicitly with `new StateMachines({ apiKey: env.STATEMACHINES_API_KEY })`.
15
15
 
16
- Call `sm.me()` to verify access and workspace scope before creating resources. `me.limits` can be null. If the key is missing, prepare the local test or example and tell the user the exact local file, binding, or launch command to configure and rerun, without asking for the key in chat. Do not call the API until access is available. If access is denied or the workspace differs from the task, report the blocker. Do not invite or remove members, or change permissions.
16
+ If the project's existing command loads `STATEMACHINES_API_KEY`, keep using that command and secret loader. Keep the same credential loader, CLI profile and API origin across setup commands. Call `sm.me()` in that process to verify access and workspace scope. `me.limits` can be null.
17
+
18
+ Otherwise, run `npx @usestatemachines/cli@0.1.1 whoami --json`. Confirm the returned account and organization match the task, then use the workspace commands below. If the CLI credential is missing, expired or revoked, complete this login:
19
+
20
+ 1. Run `npx @usestatemachines/cli@0.1.1 login --no-wait --json`. For an expired or revoked saved session, add `--yes` to replace that invalid session in the same profile. Do not replace a valid session for a different account.
21
+ 2. Show the returned `verificationUrl` and `userCode`. Ask the user to approve only the login you just started. A matching code does not identify the computer requesting access. Wait for the user to approve the login.
22
+ 3. After that approval, run `npx @usestatemachines/cli@0.1.1 login --resume --json` with the same profile used to start the login. Resume that attempt instead of starting another login. If it expired, offer a new login.
23
+
24
+ If a pending login's approval URL or code is unavailable, ask before recovering it. Explain that recovery may finish an already-approved login. After confirmation, run `npx @usestatemachines/cli@0.1.1 login --resume --json --stream` with the same profile and API origin. Keep that process running and show its URL and code. Request approval only for a recognized login. Wait for `signed-in` before continuing setup. Do not start another login or resume process. If streaming output is unavailable, report the blocker without reading credential files.
25
+
26
+ If a command returns `cli.not_linked`, run `npx @usestatemachines/cli@0.1.1 workspaces list --json`. Ask the user which workspace to use when there is more than one. Run `npx @usestatemachines/cli@0.1.1 link --workspace <workspace-id> --json` with the selected ID.
27
+
28
+ Run the user's existing command with `npx @usestatemachines/cli@0.1.1 run -- <command>`. The child receives a workspace key. The CLI keeps the credential out of its output.
29
+
30
+ If access is denied, the API origin differs, or the workspace differs from the task, report the blocker. Do not invite or remove members, or change permissions.
17
31
 
18
32
  Never print, log or commit API keys, `credentials.token`, the `headers` of `credentials.services`, environment dumps, or secret-file contents. Use synthetic test data.
19
33
 
@@ -38,7 +52,7 @@ The SDK retries management connection errors and `502`, `503`, `504` with one id
38
52
 
39
53
  ## Optional connection check
40
54
 
41
- Run this only when the user asks to verify the connection. It proves the connection and cleanup, not an existing integration's behavior. Save it in the project's examples or test location and run it with the existing TypeScript runner and secret loader. In a new Node.js project, `state-quickstart.mts` and `npx tsx state-quickstart.mts` are a fallback. Load the key into that process first.
55
+ Run this only when the user asks to verify the connection. It proves the connection and cleanup, not an existing integration's behavior. Save it in the project's examples or test location. Use the existing TypeScript runner and secret loader, or pass that runner through `npx @usestatemachines/cli@0.1.1 run -- <command>`. For an empty Node.js project, save `state-quickstart.mts` and run `npx @usestatemachines/cli@0.1.1 run -- npx tsx state-quickstart.mts`.
42
56
 
43
57
  ```ts
44
58
  import { StateMachines } from '@usestatemachines/sdk';
package/agents/index.js CHANGED
@@ -1,24 +1,24 @@
1
1
  // Generated by scripts/build-agents.mjs from templates/ and examples/quickstart.ts.
2
2
 
3
3
  export const prompt =
4
- "Install the State Machines skill in this existing repository so I can use disposable app environments with my coding agent.\n\n1. Read the repository instructions. Download the skill from https://docs.usestatemachines.com/skill.md and install it as `state-machines/SKILL.md` in this agent's supported project-local skills directory. Reuse an identical installed copy. Preserve customizations and ask before replacing a different copy. If persistent skills are unsupported, read it for this session and explain the limitation. If the download fails, report the blocker.\n2. Read the skill. Do not create a new project, install the SDK, write example code, change an integration or create remote resources during skill installation. Keep the repository's existing conventions.\n3. The API key belongs in the project's existing secret configuration as `STATEMACHINES_API_KEY`. Never ask me to paste it into chat, print it or commit it. If I need help configuring it, identify the existing secret loader and explain where to save it without reading or displaying secret values.\n4. Report where the skill was installed and whether the agent needs to reload it. Skill installation alone does not verify an API connection. Ask: \"What would you like to run or test?\" Wait for my answer before making further changes.\n";
4
+ "Set up State Machines in this existing repository so I can use disposable app environments with my coding agent.\n\n1. Read the repository instructions and inspect the existing State Machines workspace link and agent skill. Keep the repository's structure and conventions. Check that Node.js meets the CLI's runtime requirement. Use the pinned npx command below. A global CLI installation is not required.\n2. Run `npx @usestatemachines/cli@0.1.1 whoami --json`. If the project already has a `STATEMACHINES_API_KEY` loader, run the check through that loader without reading or displaying the secret. Use the same credential loader, CLI profile and API origin for every setup command below, including workspace discovery and init. Reuse valid access and confirm the returned account and organization match my task. If the existing API key or CLI profile works, skip sign-in and reuse it for setup. Do not create a replacement API key. Report an access denial or origin mismatch instead of changing accounts or permissions.\n3. Only when no project API key is configured and the access check reports a missing, expired or revoked CLI credential, run `npx @usestatemachines/cli@0.1.1 login --no-wait --json`. For an expired or revoked saved session, add `--yes` to replace that invalid session in the same profile. Do not replace a valid session for a different account. Show me the returned verification URL and code. Wait for my approval before resuming the same login with `npx @usestatemachines/cli@0.1.1 login --resume --json`, using the same profile. If a login is already pending, resume it after approval instead of starting another. If the request expires, offer a new login. Never ask me to paste a credential into chat, print it or commit it.\n If a pending login's approval URL or code is unavailable, ask before recovering it. Explain that recovery may finish an already-approved login. After confirmation, run `npx @usestatemachines/cli@0.1.1 login --resume --json --stream` with the same profile and API origin. Keep that process running and show its URL and code. Request approval only for a recognized login. Wait for `signed-in` before continuing setup. Do not start another login or resume process. If streaming output is unavailable, report the blocker without reading credential files.\n\n4. Run `npx @usestatemachines/cli@0.1.1 init --agent <this-agent> --yes`, choosing `claude` for Claude Code or `codex` for Codex. Reuse the repository's existing workspace link. If the CLI needs a workspace, run `npx @usestatemachines/cli@0.1.1 workspaces list --json`. Ask me which to use when more than one is available, then pass `--workspace <workspace-id>` to init. Do not replace a different workspace link or a customized skill without my approval. Read the installed skill and use its pinned npx commands for later work.\n5. Do not create a new project, install the SDK, write example code, change an integration or create remote resources during skill installation. Do not add `--sdk`, `--quickstart` or `--env` to init unless I ask for those changes.\n6. Report where the skill was installed, which workspace is linked, and whether the agent needs to reload it. Setup alone does not verify an integration. Ask: \"What would you like to run or test?\" Wait for my answer before making further changes.\n\nIf this agent cannot run commands or is not Claude Code or Codex, ask me to download https://app.usestatemachines.com/skill.md from my signed-in browser and provide the file. Do not request session cookies or credentials. Install that file in the agent's supported project-local skills directory, preserving any customized skill. If persistent skills are unsupported, read the file for this session and explain the limitation. Report setup as incomplete if the file is unavailable.\n";
5
5
 
6
6
  export const llmsTxt =
7
- "# State Machines\n\n> State Machines runs disposable environments of replica SaaS apps, such as Salesforce, for tests and coding agents. Create, connect to, and delete them with the TypeScript SDK `@usestatemachines/sdk`.\n\nUse the project's package manager to install or reuse `@usestatemachines/sdk`. It is ESM only and runs on Node.js and Cloudflare Workers, not browsers. `engines` in its `package.json` gives the Node.js versions. TypeScript needs `lib: [\"DOM\"]` or `@types/node`. `new StateMachines()` reads the API key from `STATEMACHINES_API_KEY` and doesn't read `.env` files; in Cloudflare Workers, pass `apiKey` explicitly. Never print the key. The installed `node_modules/@usestatemachines/sdk/README.md` matches your version: read it before relying on memory. Read the bundled `agents/SKILL.md` for the task workflow. Reuse the project's secret loader and check the key in the process that runs the test, not just the current shell. Installing the skill does not require SDK installation, example files or remote resources. Ask what the user wants to run and wait. Install the SDK and verify access when their chosen task needs it. Run the connection check below only if the user asks to verify the connection. Do not choose a business workflow from the repository.\n\n## Concepts\n\n- Environment: one disposable run of one or more apps. `const env = await sm.environments.create({ apps: ['salesforce'] })` creates it and waits until it is `running`. Use `{ wait: false }` to retain the ID before startup. Wait for `running` inside `try`, then delete the owned environment in `finally`, including after a wait failure.\n- Apps: a string names the app by its ID, and the app's name in the environment is the same string. Use `{ appId: 'salesforce', name: 'crm', setup }` for your own name, for two copies of one app, or for the app's setup. Later calls take the name.\n- Credentials: `const crm = await env.connect('salesforce')` returns `token`, `services` and `fetch`. Each service has an `id`, a `url` and the `headers` its requests need. The credentials always list each `public` service of the app's version. Pass `{ services: [id] }` to `connect()` to also list a service by its ID. A `preview` service is listed only when named. `crm.service('api')` returns the `api` service, which is the app URL. `crm.fetch(path)` joins `path` to the `api` service URL and adds its headers, which carry `Authorization: Bearer` with `token`. For Salesforce SOAP, also put `crm.token` in the envelope's `SessionHeader/sessionId`. `fetch` never sends your API key, never follows redirects and never retries. `token` and each service's `headers` are secrets.\n- Actor: one of the app's users; requests run with that user's permissions. `connect()` acts as the app's default actor (for Salesforce, the administrator) unless you pass `actorId`. Salesforce actor IDs are its user IDs, such as `005…`. Find the app by name in `env.apps`, then page through `sm.apps.versions(appId)` to find its exact `versionId`. Read actor IDs from that version's `actors`.\n- Snapshot: the saved data of every app of a running environment, taken at one moment. `await env.snapshots.create({ name: 'seeded' })` waits until it is `ready`. `sm.environments.create({ snapshotId })` creates a new environment from it, in place of `apps`; each app keeps its name and version. A delete during the snapshot fails it with `interrupted`.\n- Recorded requests: `sm.requests.list({ environmentId })` pages the calls made to the environment's apps. `sm.requests.get(id)` adds headers and a preview of each body. `sm.requests.body(id, 'request')` or `'response'` returns the full body as bytes. Recording is best effort.\n- YAML: `parseEnvironmentYaml(text)` turns a YAML document with the fields of `create` into its input.\n- Pagination: every `list` returns one page, `{ data, nextCursor }`. Pass `nextCursor` back as `cursor` until it is `null`.\n\n## Statuses\n\nEnums are open: handle values you don't recognise.\n\n- Environment `status`: `starting`, `running`, `failed`, `deleted`. A `deleted` environment has a `reason` when State Machines deleted it: `expired`, `failed` (it never reached `running`) or `cancelled` (staff ended it). The `reason` is `null` when a caller deleted it. When the environment failed, `failure` is `{ appName, code, message }`, and `code` is `start_failed`, `pause_removed`, `version_unavailable`, `invalid_setup` or `invalid_services`. For `invalid_setup` and `invalid_services`, the app refused a value in `setup` or in `services`, and `message` starts with its path.\n- Snapshot `status`: `saving`, `ready`, `failed`, `deleted`. A failed snapshot has `failure.code` `interrupted` or `internal_error`.\n- `create` throws `EnvironmentFailedError` when the environment fails, `UnexpectedStatusError` when it becomes `deleted` for another reason, and `WaitTimeoutError` when `timeoutMs` passes.\n- `snapshots.create` throws `SnapshotFailedError` when the snapshot fails and `UnexpectedStatusError` when it was deleted.\n- `sm.environments.wait(id, { status })` and `sm.snapshots.wait(id, { status })` wait for any status, and throw once it cannot be reached without another call.\n\n## Limits\n\nWhen `sm.me().limits` is non-null:\n\n- Running environments are capped per organization: `(await sm.me()).limits.concurrentEnvironments` counts `starting` and `running`.\n- `timeLimitMinutes` has a default and a maximum: `(await sm.me()).limits.timeLimitMinutes`. It counts `starting` and `running` only; at the end the environment is deleted with `reason: 'expired'`.\n\n## Errors and retries\n\nA failed API call throws `StateMachinesError` with `status`, `code`, `message`, `requestId`, `fields` and `retryAfterMs`. The SDK retries `502`, `503`, `504` and connection errors itself, until a deadline; `create` retries reuse one idempotency key. It retries nothing else.\n\n| Code | Status | What to do |\n| --- | --- | --- |\n| `invalid_request` | 400 | Read `fields`, keyed by the dotted path of each wrong field. |\n| `forbidden` | 403 | The key lacks the permission. |\n| `not_found` | 404 | Check the ID and the workspace. |\n| `invalid_status` | 409 | The status does not allow the call. Snapshots need a running environment. |\n| `snapshot_in_progress` | 409 | Wait for the running snapshot, then create another. |\n| `idempotency_key_reused` | 409 | The key was used with a different input. Replay with the exact same input. |\n| `workspace_archived` | 409 | The workspace is archived. |\n| `body_too_large` | 413 | The request body is over the API's size limit. Send less. |\n| `version_unavailable` | 422 | The app has no default version. |\n| `environment_limit_reached` | 429 | Delete only this task’s known leftovers, or ask the user to free capacity. |\n| `unavailable` | 503 | Retried automatically. If `create` still has an unknown outcome, replay the same input with the key retained before the original call. Do not generate a new key. |\n\n## Errors from the app URL\n\n`crm.fetch` returns these as responses with the same JSON envelope, and never retries them. Assert the expected status and result. For writes, read back the stored result. Never blindly retry a write whose outcome is unknown.\n\n| Code | Status | What to do |\n| --- | --- | --- |\n| `environment_not_running` | 409 | The environment is failed or deleted; `error.status` names it. Create another environment. |\n| `environment_starting` | 503 | For reads, wait `Retry-After` and retry. Check stored state before retrying an uncertain write. |\n| `environment_unavailable` | 503 | The app is restarting. For reads, wait `Retry-After`. Check stored state before retrying an uncertain write. |\n| `response_too_large` | 502 | The response is over the size limit. Ask for less, for example with a smaller page. |\n| `invalid_setup` | 503 | The app refused its `setup` after the environment started, such as in a newer version. `error.message` starts with the path of the value. Create another environment with a `setup` that the app accepts. |\n| `invalid_services` | 503 | The app refused a value in `services` after the environment started. `error.message` starts with the path of the value. Create another environment with `services` that the app accepts. |\n| `unsupported_protocol` | 501 | The app does not serve this path, method or content type, or the request body is compressed. |\n\n## Quickstart\n\nUse the project's TypeScript runner and secret loader. For an empty Node.js project, save as `state-quickstart.mts` and run `npx tsx state-quickstart.mts` with the key available. The check prints the response status and deletes the environment. Only after it prints `200`, report verified setup and the rerun command. Continue only with an explicitly supplied task; otherwise ask what to run next and wait with no setup environment left running.\n\n```ts\nimport { StateMachines } from '@usestatemachines/sdk';\n\nconst sm = new StateMachines(); // reads STATEMACHINES_API_KEY\nconst env = await sm.environments.create(\n { apps: ['salesforce'] },\n { wait: false },\n);\n\ntry {\n await sm.environments.wait(env.id, { status: 'running' });\n const salesforce = await env.connect('salesforce');\n const res = await salesforce.fetch('/services/data/v67.0/limits');\n console.log(res.status, await res.json());\n} finally {\n await env.delete();\n}\n```\n\n## Docs\n\n- [Documentation](https://docs.usestatemachines.com): guides, concepts and reference ([llms.txt](https://docs.usestatemachines.com/llms.txt))\n- [npm package](https://www.npmjs.com/package/@usestatemachines/sdk): install, README and versions\n- [Agent skill](https://docs.usestatemachines.com/skill.md): workflow, rules and troubleshooting for coding agents\n- [OpenAPI document](https://cdn.jsdelivr.net/npm/@usestatemachines/sdk/openapi.json): the HTTP API the SDK calls\n- [State Machines](https://usestatemachines.com): website\n";
7
+ "# State Machines\n\n> State Machines runs disposable environments of replica SaaS apps, such as Salesforce, for tests and coding agents. Create, connect to, and delete them with the TypeScript SDK `@usestatemachines/sdk`.\n\nUse the project's package manager to install or reuse `@usestatemachines/sdk`. It is ESM only and runs on Node.js and Cloudflare Workers, not browsers. `engines` in its `package.json` gives the Node.js versions. TypeScript needs `lib: [\"DOM\"]` or `@types/node`. `new StateMachines()` reads the API key from `STATEMACHINES_API_KEY` and doesn't read `.env` files; in Cloudflare Workers, pass `apiKey` explicitly. Never print the key. The installed `node_modules/@usestatemachines/sdk/README.md` matches your version: read it before relying on memory. Read the bundled `agents/SKILL.md` for the task workflow. Reuse the project's secret loader and check the key in the process that runs the test, not just the current shell. Installing the skill does not require SDK installation, example files or remote resources. Ask what the user wants to run and wait. Install the SDK and verify access when their chosen task needs it. Run the connection check below only if the user asks to verify the connection. Do not choose a business workflow from the repository.\n\n## Get a credential\n\nIf the project's existing command loads `STATEMACHINES_API_KEY`, reuse that command and secret loader. Keep the same credential loader, CLI profile and API origin across setup commands. Otherwise, run `npx @usestatemachines/cli@0.1.1 whoami --json`. If it succeeds, confirm the account and organization match the task, then use the workspace commands below. If the CLI credential is missing, expired or revoked, start `npx @usestatemachines/cli@0.1.1 login --no-wait --json`. For an expired or revoked saved session, add `--yes` to replace that invalid session in the same profile. Do not replace a valid session for a different account. Show its `verificationUrl` and `userCode`, and wait for approval. A matching code does not identify the requesting computer. After that approval, run `npx @usestatemachines/cli@0.1.1 login --resume --json` with the same profile to finish that attempt. Resume an existing pending login after approval instead of creating another. Report access denial or an API origin mismatch without changing accounts or permissions.\n\nIf a pending login's approval URL or code is unavailable, ask before recovering it. Explain that recovery may finish an already-approved login. After confirmation, run `npx @usestatemachines/cli@0.1.1 login --resume --json --stream` with the same profile and API origin. Keep that process running and show its URL and code. Request approval only for a recognized login. Wait for `signed-in` before continuing setup. Do not start another login or resume process. If streaming output is unavailable, report the blocker without reading credential files.\n\nFor `cli.not_linked`, run `npx @usestatemachines/cli@0.1.1 workspaces list --json`. Ask which workspace to use when there is more than one, then run `npx @usestatemachines/cli@0.1.1 link --workspace <workspace-id> --json`. Run the user's existing code with `npx @usestatemachines/cli@0.1.1 run -- <command>`. Never print or commit a credential.\n\n## CLI commands\n\nPrefix these commands with `npx @usestatemachines/cli@0.1.1`. Use `help --json` for flags and result schemas:\n\n- `login`, `logout`, `whoami`, `profiles list`, and `profiles use`\n- `workspaces list`, `workspaces use`, `link`, `unlink`, and `init`\n- `apps list`, `environments create`, `environments list`, `environments get`, `environments wait`, and `environments delete`\n- `connect`, `snapshots create`, `snapshots list`, `snapshots get`, `snapshots delete`, `requests list`, and `requests get`\n- `run -- <command>`, `env pull`, `api`, `doctor`, `version`, `help`, and `completion`\n\n## Concepts\n\n- Environment: one disposable run of one or more apps. `const env = await sm.environments.create({ apps: ['salesforce'] })` creates it and waits until it is `running`. Use `{ wait: false }` to retain the ID before startup. Wait for `running` inside `try`, then delete the owned environment in `finally`, including after a wait failure.\n- Apps: a string names the app by its ID, and the app's name in the environment is the same string. Use `{ appId: 'salesforce', name: 'crm', setup }` for your own name, for two copies of one app, or for the app's setup. Later calls take the name.\n- Credentials: `const crm = await env.connect('salesforce')` returns `token`, `services` and `fetch`. Each service has an `id`, a `url` and the `headers` its requests need. The credentials always list each `public` service of the app's version. Pass `{ services: [id] }` to `connect()` to also list a service by its ID. A `preview` service is listed only when named. `crm.service('api')` returns the `api` service, which is the app URL. `crm.fetch(path)` joins `path` to the `api` service URL and adds its headers, which carry `Authorization: Bearer` with `token`. For Salesforce SOAP, also put `crm.token` in the envelope's `SessionHeader/sessionId`. `fetch` never sends your API key, never follows redirects and never retries. `token` and each service's `headers` are secrets.\n- Actor: one of the app's users; requests run with that user's permissions. `connect()` acts as the app's default actor (for Salesforce, the administrator) unless you pass `actorId`. Salesforce actor IDs are its user IDs, such as `005…`. Find the app by name in `env.apps`, then page through `sm.apps.versions(appId)` to find its exact `versionId`. Read actor IDs from that version's `actors`.\n- Snapshot: the saved data of every app of a running environment, taken at one moment. `await env.snapshots.create({ name: 'seeded' })` waits until it is `ready`. `sm.environments.create({ snapshotId })` creates a new environment from it, in place of `apps`; each app keeps its name and version. A delete during the snapshot fails it with `interrupted`.\n- Recorded requests: `sm.requests.list({ environmentId })` pages the calls made to the environment's apps. `sm.requests.get(id)` adds headers and a preview of each body. `sm.requests.body(id, 'request')` or `'response'` returns the full body as bytes. Recording is best effort.\n- YAML: `parseEnvironmentYaml(text)` turns a YAML document with the fields of `create` into its input.\n- Pagination: every `list` returns one page, `{ data, nextCursor }`. Pass `nextCursor` back as `cursor` until it is `null`.\n\n## Statuses\n\nEnums are open: handle values you don't recognise.\n\n- Environment `status`: `starting`, `running`, `failed`, `deleted`. A `deleted` environment has a `reason` when State Machines deleted it: `expired`, `failed` (it never reached `running`) or `cancelled` (staff ended it). The `reason` is `null` when a caller deleted it. When the environment failed, `failure` is `{ appName, code, message }`, and `code` is `start_failed`, `pause_removed`, `version_unavailable`, `invalid_setup` or `invalid_services`. For `invalid_setup` and `invalid_services`, the app refused a value in `setup` or in `services`, and `message` starts with its path.\n- Snapshot `status`: `saving`, `ready`, `failed`, `deleted`. A failed snapshot has `failure.code` `interrupted` or `internal_error`.\n- `create` throws `EnvironmentFailedError` when the environment fails, `UnexpectedStatusError` when it becomes `deleted` for another reason, and `WaitTimeoutError` when `timeoutMs` passes.\n- `snapshots.create` throws `SnapshotFailedError` when the snapshot fails and `UnexpectedStatusError` when it was deleted.\n- `sm.environments.wait(id, { status })` and `sm.snapshots.wait(id, { status })` wait for any status, and throw once it cannot be reached without another call.\n\n## Limits\n\nWhen `sm.me().limits` is non-null:\n\n- Running environments are capped per organization: `(await sm.me()).limits.concurrentEnvironments` counts `starting` and `running`.\n- `timeLimitMinutes` has a default and a maximum: `(await sm.me()).limits.timeLimitMinutes`. It counts `starting` and `running` only; at the end the environment is deleted with `reason: 'expired'`.\n\n## Errors and retries\n\nA failed API call throws `StateMachinesError` with `status`, `code`, `message`, `requestId`, `fields` and `retryAfterMs`. The SDK retries `502`, `503`, `504` and connection errors itself, until a deadline; `create` retries reuse one idempotency key. It retries nothing else.\n\n| Code | Status | What to do |\n| --- | --- | --- |\n| `invalid_request` | 400 | Read `fields`, keyed by the dotted path of each wrong field. |\n| `forbidden` | 403 | The key lacks the permission. |\n| `not_found` | 404 | Check the ID and the workspace. |\n| `invalid_status` | 409 | The status does not allow the call. Snapshots need a running environment. |\n| `snapshot_in_progress` | 409 | Wait for the running snapshot, then create another. |\n| `idempotency_key_reused` | 409 | The key was used with a different input. Replay with the exact same input. |\n| `workspace_archived` | 409 | The workspace is archived. |\n| `body_too_large` | 413 | The request body is over the API's size limit. Send less. |\n| `version_unavailable` | 422 | The app has no default version. |\n| `environment_limit_reached` | 429 | Delete only this task’s known leftovers, or ask the user to free capacity. |\n| `unavailable` | 503 | Retried automatically. If `create` still has an unknown outcome, replay the same input with the key retained before the original call. Do not generate a new key. |\n\n## Errors from the app URL\n\n`crm.fetch` returns these as responses with the same JSON envelope, and never retries them. Assert the expected status and result. For writes, read back the stored result. Never blindly retry a write whose outcome is unknown.\n\n| Code | Status | What to do |\n| --- | --- | --- |\n| `environment_not_running` | 409 | The environment is failed or deleted; `error.status` names it. Create another environment. |\n| `environment_starting` | 503 | For reads, wait `Retry-After` and retry. Check stored state before retrying an uncertain write. |\n| `environment_unavailable` | 503 | The app is restarting. For reads, wait `Retry-After`. Check stored state before retrying an uncertain write. |\n| `response_too_large` | 502 | The response is over the size limit. Ask for less, for example with a smaller page. |\n| `invalid_setup` | 503 | The app refused its `setup` after the environment started, such as in a newer version. `error.message` starts with the path of the value. Create another environment with a `setup` that the app accepts. |\n| `invalid_services` | 503 | The app refused a value in `services` after the environment started. `error.message` starts with the path of the value. Create another environment with `services` that the app accepts. |\n| `unsupported_protocol` | 501 | The app does not serve this path, method or content type, or the request body is compressed. |\n\n## Quickstart\n\nUse the project's TypeScript runner with its secret loader, or pass that runner through `npx @usestatemachines/cli@0.1.1 run -- <command>`. For an empty Node.js project, save as `state-quickstart.mts` and run `npx @usestatemachines/cli@0.1.1 run -- npx tsx state-quickstart.mts`. The check prints the response status and deletes the environment. Only after it prints `200`, report verified setup and the rerun command. Continue only with an explicitly supplied task. Otherwise, ask what to run next and wait with no setup environment left running.\n\n```ts\nimport { StateMachines } from '@usestatemachines/sdk';\n\nconst sm = new StateMachines(); // reads STATEMACHINES_API_KEY\nconst env = await sm.environments.create(\n { apps: ['salesforce'] },\n { wait: false },\n);\n\ntry {\n await sm.environments.wait(env.id, { status: 'running' });\n const salesforce = await env.connect('salesforce');\n const res = await salesforce.fetch('/services/data/v67.0/limits');\n console.log(res.status, await res.json());\n} finally {\n await env.delete();\n}\n```\n\n## Docs\n\n- [Documentation](https://docs.usestatemachines.com): guides, concepts and reference ([llms.txt](https://docs.usestatemachines.com/llms.txt))\n- [npm package](https://www.npmjs.com/package/@usestatemachines/sdk): install, README and versions\n- [Agent skill](https://docs.usestatemachines.com/skill.md): workflow, rules and troubleshooting for coding agents\n- [OpenAPI document](https://cdn.jsdelivr.net/npm/@usestatemachines/sdk/openapi.json): the HTTP API the SDK calls\n- [State Machines](https://usestatemachines.com): website\n";
8
8
 
9
9
  export const skill =
10
- "---\nname: state-machines\ndescription: Use when setting up State Machines or running code, integration tests, or API experiments against disposable SaaS replicas such as Salesforce. Applies to @usestatemachines/sdk and STATEMACHINES_API_KEY, including environment creation, actors, snapshots, cleanup and recovery.\n---\n\n## Start with the user's task\n\nDuring skill installation, read the repository instructions and preserve its existing structure. Do not create a new project, install dependencies, write examples or create remote resources. Report the installed skill location and any reload requirement, then ask \"What would you like to run or test?\" and wait. Skill installation does not verify a connection.\n\nOnce the user chooses a task, inspect the project's package manager, runtime, runner and secret loader. Install or reuse a compatible `@usestatemachines/sdk` only when the task needs it. Read the installed README for API signatures and `package.json` for runtime requirements. The SDK is ESM and runs on Node.js and Cloudflare Workers, not in a browser. Follow the user's task and existing project conventions without requiring a separate onboarding example.\n\n## Check access in the right process\n\nThe SDK reads `STATEMACHINES_API_KEY` from the process environment. It does not load `.env` files. Use the project's existing test command or secret loader. An empty variable in your current shell does not prove that the test process lacks it. Verify presence without displaying the value. In Workers, pass the binding explicitly with `new StateMachines({ apiKey: env.STATEMACHINES_API_KEY })`.\n\nCall `sm.me()` to verify access and workspace scope before creating resources. `me.limits` can be null. If the key is missing, prepare the local test or example and tell the user the exact local file, binding, or launch command to configure and rerun, without asking for the key in chat. Do not call the API until access is available. If access is denied or the workspace differs from the task, report the blocker. Do not invite or remove members, or change permissions.\n\nNever print, log or commit API keys, `credentials.token`, the `headers` of `credentials.services`, environment dumps, or secret-file contents. Use synthetic test data.\n\n## Run the user’s chosen task\n\nApply this section after setup, once the user has chosen a task.\n\n- `sm.apps.list()` returns app IDs. Every list returns `{ data, nextCursor }`. Follow cursors until the required entry is found or `nextCursor` is null.\n- Create only the apps the task needs. A string such as `'salesforce'` is both its app ID and its name in the environment. For named apps, later `connect()` calls use the name.\n- `connect(appName)` uses the app's default actor, the Salesforce administrator. For another actor, find the matching app by name in `environment.apps`, read its `versionId`, then find that exact version through `sm.apps.versions(appId)`. Use an actor ID from its `actors`, never a role label, workspace member ID, or the first version in a list.\n- Check the app's services and [known differences](https://docs.usestatemachines.com/apps/known-differences.md) for the behavior under test. A replica does not import a customer's org or run arbitrary customer automation.\n- Configure the existing integration's test transport with the returned app URL and credentials. Verify its requests reach that URL. A successful standalone SDK request does not prove the integration uses the replica. If transport configuration needs a broader change, explain it before expanding the task.\n- Check the expected HTTP status and assert the result. For writes, read the stored data back and assert the relevant fields. For negative tests, assert the expected error. Do not weaken an assertion to make unsupported behavior pass.\n\n## Own the full lifecycle\n\nCreate with `{ wait: false }` to retain the accepted environment ID before waiting. Enter `try` immediately, then wait for `running`, connect and run the check inside it. Delete in `finally`, including after a startup failure, timeout or failed assertion. An aborted wait does not cancel the work that the API accepted. Cleanup must use a fresh, un-aborted signal. Preserve both verification and cleanup errors, and report the environment ID if deletion fails.\n\nDelete only environments created for this task. If the organization is at its limit, clean up a known leftover from this task or ask the user to free capacity. Do not delete unrelated environments or a user-supplied reusable snapshot.\n\nThe SDK retries management connection errors and `502`, `503`, `504` with one idempotency key per create call. To recover across separate attempts or a restart, retain the input and an explicit `idempotencyKey` before creation. Replay using the same input, key, workspace and caller. Do not create a replacement with a new key while the first outcome is unknown. See [operation recovery](https://docs.usestatemachines.com/sdk-api/recovery.md).\n\n## Optional connection check\n\nRun this only when the user asks to verify the connection. It proves the connection and cleanup, not an existing integration's behavior. Save it in the project's examples or test location and run it with the existing TypeScript runner and secret loader. In a new Node.js project, `state-quickstart.mts` and `npx tsx state-quickstart.mts` are a fallback. Load the key into that process first.\n\n```ts\nimport { StateMachines } from '@usestatemachines/sdk';\n\nconst sm = new StateMachines(); // reads STATEMACHINES_API_KEY\nconst env = await sm.environments.create(\n { apps: ['salesforce'] },\n { wait: false },\n);\n\ntry {\n await sm.environments.wait(env.id, { status: 'running' });\n const salesforce = await env.connect('salesforce');\n const res = await salesforce.fetch('/services/data/v67.0/limits');\n console.log(res.status, await res.json());\n} finally {\n await env.delete();\n}\n```\n\n## Load details when needed\n\n- **Repeated fixtures or parallel tests:** Read [isolated tests](https://docs.usestatemachines.com/sdk-api/testing.md). Save seeded data with `env.snapshots.create({ name })`, then create each test's environment with `{ snapshotId }`. A snapshot in `saving` cannot be deleted yet. Retain its ID and wait for `ready` or `failed` before cleanup. Delete a reusable snapshot only when the task calls for it.\n- **Another actor, vendor client or SOAP:** Read [credentials](https://docs.usestatemachines.com/environments/credentials.md). `credentials.fetch()` confines requests to the app URL, adds its authentication headers, and never follows redirects. External clients must send a service's `headers` only to its `url` and disable redirects. For Salesforce SOAP, put the actor token in `SessionHeader/sessionId` in the envelope.\n- **Cloudflare Workers:** Pass the key explicitly. Use `{ wait: false }` and keep the environment ID across requests or in the job's state. A request ending does not cancel startup. Follow the worker's background-job lifecycle for polling and cleanup.\n- **Long runs:** Read `sm.me().limits` when non-null. The environment time limit covers starting and running. Use a supported `timeLimitMinutes` when the task needs it.\n\n## Recover without changing the test's meaning\n\nManagement failures throw SDK errors. App HTTP failures are returned as `Response` objects. Use `instanceof` with the SDK error classes. Native `credentials.fetch()` never retries. Read the State Machines error code in `error.code` and distinguish it from an app's own error.\n\n| Failure | Next action |\n| --- | --- |\n| `invalid_request` | Read `err.fields` and the installed method signature. |\n| `version_unavailable` | Discover the app ID and available versions. Stop if the required version is unavailable. |\n| `environment_limit_reached` | Clean up only this task's known leftovers, or report the capacity blocker. |\n| `EnvironmentFailedError` | Read `err.environment.failure` and clean up the owned environment. |\n| `WaitTimeoutError` or abort | Use the retained environment ID. If older code waited inside `create`, a non-null `WaitTimeoutError.resource` carries its ID. If neither an ID nor a replay key is recoverable, report unresolved cleanup. Do not guess from a list. Wait again only if the task should continue. Otherwise delete the owned environment. |\n| Unknown create outcome | Replay the retained input and idempotency key. Do not start another operation with a new key. |\n| `invalid_status` | Read the current resource status before choosing the next call. |\n| `snapshot_in_progress` | Find the saving snapshot for this environment and wait for it before creating another. |\n| `SnapshotFailedError` | Report the failure. Retry only if the task still needs a snapshot and the source environment is running. |\n| App `environment_not_running` | Inspect the environment. Do not silently switch the integration to another target. |\n| App `invalid_setup` or `invalid_services` | The app refused a value of `setup` or `services` after the start. `error.message` starts with its path. Create another environment with a value that the app accepts, and delete the owned one. |\n| App or gateway `503` | For a retryable read, honor `Retry-After`. For a write with an uncertain outcome, inspect stored state or report uncertainty before retrying. |\n| Cleanup failure | Preserve the task result, report the owned resource ID and retry deletion with valid access and a fresh signal. Do not report cleanup as successful. |\n\n## Finish with evidence\n\nLeave the smallest rerunnable test or example. Report the behavior checked, command, assertion result, SDK and app versions, selected actor, resource IDs without secrets, and cleanup outcome. Say when execution was blocked or only typechecking ran. A passing replica check establishes the asserted behavior for that version and actor, not full production compatibility.\n\nFind other guides through https://docs.usestatemachines.com/llms.txt. Read focused pages before the full documentation bundle. This dashboard also serves an agent index at https://app.usestatemachines.com/llms.txt.\n";
10
+ "---\nname: state-machines\ndescription: Use when setting up State Machines or running code, integration tests, or API experiments against disposable SaaS replicas such as Salesforce. Applies to @usestatemachines/sdk and STATEMACHINES_API_KEY, including environment creation, actors, snapshots, cleanup and recovery.\n---\n\n## Start with the user's task\n\nDuring skill installation, read the repository instructions and preserve its existing structure. Do not create a new project, install project dependencies, write examples or create remote resources. Report the installed skill location and any reload requirement, then ask \"What would you like to run or test?\" and wait. Skill installation does not verify a connection.\n\nOnce the user chooses a task, inspect the project's package manager, runtime, runner and secret loader. Install or reuse a compatible `@usestatemachines/sdk` only when the task needs it. Read the installed README for API signatures and `package.json` for runtime requirements. The SDK is ESM and runs on Node.js and Cloudflare Workers, not in a browser. Follow the user's task and existing project conventions without requiring a separate onboarding example.\n\n## Get a credential\n\nThe SDK reads `STATEMACHINES_API_KEY` from the process environment. It does not load `.env` files. Use the project's existing test command or secret loader. An empty variable in your current shell does not prove that the test process lacks it. Verify presence without displaying the value. In Workers, pass the binding explicitly with `new StateMachines({ apiKey: env.STATEMACHINES_API_KEY })`.\n\nIf the project's existing command loads `STATEMACHINES_API_KEY`, keep using that command and secret loader. Keep the same credential loader, CLI profile and API origin across setup commands. Call `sm.me()` in that process to verify access and workspace scope. `me.limits` can be null.\n\nOtherwise, run `npx @usestatemachines/cli@0.1.1 whoami --json`. Confirm the returned account and organization match the task, then use the workspace commands below. If the CLI credential is missing, expired or revoked, complete this login:\n\n1. Run `npx @usestatemachines/cli@0.1.1 login --no-wait --json`. For an expired or revoked saved session, add `--yes` to replace that invalid session in the same profile. Do not replace a valid session for a different account.\n2. Show the returned `verificationUrl` and `userCode`. Ask the user to approve only the login you just started. A matching code does not identify the computer requesting access. Wait for the user to approve the login.\n3. After that approval, run `npx @usestatemachines/cli@0.1.1 login --resume --json` with the same profile used to start the login. Resume that attempt instead of starting another login. If it expired, offer a new login.\n\nIf a pending login's approval URL or code is unavailable, ask before recovering it. Explain that recovery may finish an already-approved login. After confirmation, run `npx @usestatemachines/cli@0.1.1 login --resume --json --stream` with the same profile and API origin. Keep that process running and show its URL and code. Request approval only for a recognized login. Wait for `signed-in` before continuing setup. Do not start another login or resume process. If streaming output is unavailable, report the blocker without reading credential files.\n\nIf a command returns `cli.not_linked`, run `npx @usestatemachines/cli@0.1.1 workspaces list --json`. Ask the user which workspace to use when there is more than one. Run `npx @usestatemachines/cli@0.1.1 link --workspace <workspace-id> --json` with the selected ID.\n\nRun the user's existing command with `npx @usestatemachines/cli@0.1.1 run -- <command>`. The child receives a workspace key. The CLI keeps the credential out of its output.\n\nIf access is denied, the API origin differs, or the workspace differs from the task, report the blocker. Do not invite or remove members, or change permissions.\n\nNever print, log or commit API keys, `credentials.token`, the `headers` of `credentials.services`, environment dumps, or secret-file contents. Use synthetic test data.\n\n## Run the user’s chosen task\n\nApply this section after setup, once the user has chosen a task.\n\n- `sm.apps.list()` returns app IDs. Every list returns `{ data, nextCursor }`. Follow cursors until the required entry is found or `nextCursor` is null.\n- Create only the apps the task needs. A string such as `'salesforce'` is both its app ID and its name in the environment. For named apps, later `connect()` calls use the name.\n- `connect(appName)` uses the app's default actor, the Salesforce administrator. For another actor, find the matching app by name in `environment.apps`, read its `versionId`, then find that exact version through `sm.apps.versions(appId)`. Use an actor ID from its `actors`, never a role label, workspace member ID, or the first version in a list.\n- Check the app's services and [known differences](https://docs.usestatemachines.com/apps/known-differences.md) for the behavior under test. A replica does not import a customer's org or run arbitrary customer automation.\n- Configure the existing integration's test transport with the returned app URL and credentials. Verify its requests reach that URL. A successful standalone SDK request does not prove the integration uses the replica. If transport configuration needs a broader change, explain it before expanding the task.\n- Check the expected HTTP status and assert the result. For writes, read the stored data back and assert the relevant fields. For negative tests, assert the expected error. Do not weaken an assertion to make unsupported behavior pass.\n\n## Own the full lifecycle\n\nCreate with `{ wait: false }` to retain the accepted environment ID before waiting. Enter `try` immediately, then wait for `running`, connect and run the check inside it. Delete in `finally`, including after a startup failure, timeout or failed assertion. An aborted wait does not cancel the work that the API accepted. Cleanup must use a fresh, un-aborted signal. Preserve both verification and cleanup errors, and report the environment ID if deletion fails.\n\nDelete only environments created for this task. If the organization is at its limit, clean up a known leftover from this task or ask the user to free capacity. Do not delete unrelated environments or a user-supplied reusable snapshot.\n\nThe SDK retries management connection errors and `502`, `503`, `504` with one idempotency key per create call. To recover across separate attempts or a restart, retain the input and an explicit `idempotencyKey` before creation. Replay using the same input, key, workspace and caller. Do not create a replacement with a new key while the first outcome is unknown. See [operation recovery](https://docs.usestatemachines.com/sdk-api/recovery.md).\n\n## Optional connection check\n\nRun this only when the user asks to verify the connection. It proves the connection and cleanup, not an existing integration's behavior. Save it in the project's examples or test location. Use the existing TypeScript runner and secret loader, or pass that runner through `npx @usestatemachines/cli@0.1.1 run -- <command>`. For an empty Node.js project, save `state-quickstart.mts` and run `npx @usestatemachines/cli@0.1.1 run -- npx tsx state-quickstart.mts`.\n\n```ts\nimport { StateMachines } from '@usestatemachines/sdk';\n\nconst sm = new StateMachines(); // reads STATEMACHINES_API_KEY\nconst env = await sm.environments.create(\n { apps: ['salesforce'] },\n { wait: false },\n);\n\ntry {\n await sm.environments.wait(env.id, { status: 'running' });\n const salesforce = await env.connect('salesforce');\n const res = await salesforce.fetch('/services/data/v67.0/limits');\n console.log(res.status, await res.json());\n} finally {\n await env.delete();\n}\n```\n\n## Load details when needed\n\n- **Repeated fixtures or parallel tests:** Read [isolated tests](https://docs.usestatemachines.com/sdk-api/testing.md). Save seeded data with `env.snapshots.create({ name })`, then create each test's environment with `{ snapshotId }`. A snapshot in `saving` cannot be deleted yet. Retain its ID and wait for `ready` or `failed` before cleanup. Delete a reusable snapshot only when the task calls for it.\n- **Another actor, vendor client or SOAP:** Read [credentials](https://docs.usestatemachines.com/environments/credentials.md). `credentials.fetch()` confines requests to the app URL, adds its authentication headers, and never follows redirects. External clients must send a service's `headers` only to its `url` and disable redirects. For Salesforce SOAP, put the actor token in `SessionHeader/sessionId` in the envelope.\n- **Cloudflare Workers:** Pass the key explicitly. Use `{ wait: false }` and keep the environment ID across requests or in the job's state. A request ending does not cancel startup. Follow the worker's background-job lifecycle for polling and cleanup.\n- **Long runs:** Read `sm.me().limits` when non-null. The environment time limit covers starting and running. Use a supported `timeLimitMinutes` when the task needs it.\n\n## Recover without changing the test's meaning\n\nManagement failures throw SDK errors. App HTTP failures are returned as `Response` objects. Use `instanceof` with the SDK error classes. Native `credentials.fetch()` never retries. Read the State Machines error code in `error.code` and distinguish it from an app's own error.\n\n| Failure | Next action |\n| --- | --- |\n| `invalid_request` | Read `err.fields` and the installed method signature. |\n| `version_unavailable` | Discover the app ID and available versions. Stop if the required version is unavailable. |\n| `environment_limit_reached` | Clean up only this task's known leftovers, or report the capacity blocker. |\n| `EnvironmentFailedError` | Read `err.environment.failure` and clean up the owned environment. |\n| `WaitTimeoutError` or abort | Use the retained environment ID. If older code waited inside `create`, a non-null `WaitTimeoutError.resource` carries its ID. If neither an ID nor a replay key is recoverable, report unresolved cleanup. Do not guess from a list. Wait again only if the task should continue. Otherwise delete the owned environment. |\n| Unknown create outcome | Replay the retained input and idempotency key. Do not start another operation with a new key. |\n| `invalid_status` | Read the current resource status before choosing the next call. |\n| `snapshot_in_progress` | Find the saving snapshot for this environment and wait for it before creating another. |\n| `SnapshotFailedError` | Report the failure. Retry only if the task still needs a snapshot and the source environment is running. |\n| App `environment_not_running` | Inspect the environment. Do not silently switch the integration to another target. |\n| App `invalid_setup` or `invalid_services` | The app refused a value of `setup` or `services` after the start. `error.message` starts with its path. Create another environment with a value that the app accepts, and delete the owned one. |\n| App or gateway `503` | For a retryable read, honor `Retry-After`. For a write with an uncertain outcome, inspect stored state or report uncertainty before retrying. |\n| Cleanup failure | Preserve the task result, report the owned resource ID and retry deletion with valid access and a fresh signal. Do not report cleanup as successful. |\n\n## Finish with evidence\n\nLeave the smallest rerunnable test or example. Report the behavior checked, command, assertion result, SDK and app versions, selected actor, resource IDs without secrets, and cleanup outcome. Say when execution was blocked or only typechecking ran. A passing replica check establishes the asserted behavior for that version and actor, not full production compatibility.\n\nFind other guides through https://docs.usestatemachines.com/llms.txt. Read focused pages before the full documentation bundle. This dashboard also serves an agent index at https://app.usestatemachines.com/llms.txt.\n";
11
11
 
12
12
  export const quickstart =
13
13
  "import { StateMachines } from '@usestatemachines/sdk';\n\nconst sm = new StateMachines(); // reads STATEMACHINES_API_KEY\nconst env = await sm.environments.create(\n { apps: ['salesforce'] },\n { wait: false },\n);\n\ntry {\n await sm.environments.wait(env.id, { status: 'running' });\n const salesforce = await env.connect('salesforce');\n const res = await salesforce.fetch('/services/data/v67.0/limits');\n console.log(res.status, await res.json());\n} finally {\n await env.delete();\n}\n";
14
14
 
15
- export const cliVersion = null;
15
+ export const cliVersion = '0.1.1';
16
16
 
17
17
  export const templates = Object.freeze({
18
18
  prompt:
19
- "Install the State Machines skill in this existing repository so I can use disposable app environments with my coding agent.\n\n1. Read the repository instructions. Download the skill from https://docs.usestatemachines.com/skill.md and install it as `state-machines/SKILL.md` in this agent's supported project-local skills directory. Reuse an identical installed copy. Preserve customizations and ask before replacing a different copy. If persistent skills are unsupported, read it for this session and explain the limitation. If the download fails, report the blocker.\n2. Read the skill. Do not create a new project, install the SDK, write example code, change an integration or create remote resources during skill installation. Keep the repository's existing conventions.\n3. The API key belongs in the project's existing secret configuration as `STATEMACHINES_API_KEY`. Never ask me to paste it into chat, print it or commit it. If I need help configuring it, identify the existing secret loader and explain where to save it without reading or displaying secret values.\n4. Report where the skill was installed and whether the agent needs to reload it. Skill installation alone does not verify an API connection. Ask: \"What would you like to run or test?\" Wait for my answer before making further changes.\n",
19
+ "Set up State Machines in this existing repository so I can use disposable app environments with my coding agent.\n\n1. Read the repository instructions and inspect the existing State Machines workspace link and agent skill. Keep the repository's structure and conventions. Check that Node.js meets the CLI's runtime requirement. Use the pinned npx command below. A global CLI installation is not required.\n2. Run `npx @usestatemachines/cli@0.1.1 whoami --json`. If the project already has a `STATEMACHINES_API_KEY` loader, run the check through that loader without reading or displaying the secret. Use the same credential loader, CLI profile and API origin for every setup command below, including workspace discovery and init. Reuse valid access and confirm the returned account and organization match my task. If the existing API key or CLI profile works, skip sign-in and reuse it for setup. Do not create a replacement API key. Report an access denial or origin mismatch instead of changing accounts or permissions.\n3. Only when no project API key is configured and the access check reports a missing, expired or revoked CLI credential, run `npx @usestatemachines/cli@0.1.1 login --no-wait --json`. For an expired or revoked saved session, add `--yes` to replace that invalid session in the same profile. Do not replace a valid session for a different account. Show me the returned verification URL and code. Wait for my approval before resuming the same login with `npx @usestatemachines/cli@0.1.1 login --resume --json`, using the same profile. If a login is already pending, resume it after approval instead of starting another. If the request expires, offer a new login. Never ask me to paste a credential into chat, print it or commit it.\n If a pending login's approval URL or code is unavailable, ask before recovering it. Explain that recovery may finish an already-approved login. After confirmation, run `npx @usestatemachines/cli@0.1.1 login --resume --json --stream` with the same profile and API origin. Keep that process running and show its URL and code. Request approval only for a recognized login. Wait for `signed-in` before continuing setup. Do not start another login or resume process. If streaming output is unavailable, report the blocker without reading credential files.\n\n4. Run `npx @usestatemachines/cli@0.1.1 init --agent <this-agent> --yes`, choosing `claude` for Claude Code or `codex` for Codex. Reuse the repository's existing workspace link. If the CLI needs a workspace, run `npx @usestatemachines/cli@0.1.1 workspaces list --json`. Ask me which to use when more than one is available, then pass `--workspace <workspace-id>` to init. Do not replace a different workspace link or a customized skill without my approval. Read the installed skill and use its pinned npx commands for later work.\n5. Do not create a new project, install the SDK, write example code, change an integration or create remote resources during skill installation. Do not add `--sdk`, `--quickstart` or `--env` to init unless I ask for those changes.\n6. Report where the skill was installed, which workspace is linked, and whether the agent needs to reload it. Setup alone does not verify an integration. Ask: \"What would you like to run or test?\" Wait for my answer before making further changes.\n\nIf this agent cannot run commands or is not Claude Code or Codex, ask me to download {{origin}}/skill.md from my signed-in browser and provide the file. Do not request session cookies or credentials. Install that file in the agent's supported project-local skills directory, preserving any customized skill. If persistent skills are unsupported, read the file for this session and explain the limitation. Report setup as incomplete if the file is unavailable.\n",
20
20
  llmsTxt:
21
- "# State Machines\n\n> State Machines runs disposable environments of replica SaaS apps, such as Salesforce, for tests and coding agents. Create, connect to, and delete them with the TypeScript SDK `@usestatemachines/sdk`.\n\nUse the project's package manager to install or reuse `@usestatemachines/sdk`. It is ESM only and runs on Node.js and Cloudflare Workers, not browsers. `engines` in its `package.json` gives the Node.js versions. TypeScript needs `lib: [\"DOM\"]` or `@types/node`. `new StateMachines()` reads the API key from `STATEMACHINES_API_KEY` and doesn't read `.env` files; in Cloudflare Workers, pass `apiKey` explicitly. Never print the key. The installed `node_modules/@usestatemachines/sdk/README.md` matches your version: read it before relying on memory. Read the bundled `agents/SKILL.md` for the task workflow. Reuse the project's secret loader and check the key in the process that runs the test, not just the current shell. Installing the skill does not require SDK installation, example files or remote resources. Ask what the user wants to run and wait. Install the SDK and verify access when their chosen task needs it. Run the connection check below only if the user asks to verify the connection. Do not choose a business workflow from the repository.\n\n## Concepts\n\n- Environment: one disposable run of one or more apps. `const env = await sm.environments.create({ apps: ['salesforce'] })` creates it and waits until it is `running`. Use `{ wait: false }` to retain the ID before startup. Wait for `running` inside `try`, then delete the owned environment in `finally`, including after a wait failure.\n- Apps: a string names the app by its ID, and the app's name in the environment is the same string. Use `{ appId: 'salesforce', name: 'crm', setup }` for your own name, for two copies of one app, or for the app's setup. Later calls take the name.\n- Credentials: `const crm = await env.connect('salesforce')` returns `token`, `services` and `fetch`. Each service has an `id`, a `url` and the `headers` its requests need. The credentials always list each `public` service of the app's version. Pass `{ services: [id] }` to `connect()` to also list a service by its ID. A `preview` service is listed only when named. `crm.service('api')` returns the `api` service, which is the app URL. `crm.fetch(path)` joins `path` to the `api` service URL and adds its headers, which carry `Authorization: Bearer` with `token`. For Salesforce SOAP, also put `crm.token` in the envelope's `SessionHeader/sessionId`. `fetch` never sends your API key, never follows redirects and never retries. `token` and each service's `headers` are secrets.\n- Actor: one of the app's users; requests run with that user's permissions. `connect()` acts as the app's default actor (for Salesforce, the administrator) unless you pass `actorId`. Salesforce actor IDs are its user IDs, such as `005…`. Find the app by name in `env.apps`, then page through `sm.apps.versions(appId)` to find its exact `versionId`. Read actor IDs from that version's `actors`.\n- Snapshot: the saved data of every app of a running environment, taken at one moment. `await env.snapshots.create({ name: 'seeded' })` waits until it is `ready`. `sm.environments.create({ snapshotId })` creates a new environment from it, in place of `apps`; each app keeps its name and version. A delete during the snapshot fails it with `interrupted`.\n- Recorded requests: `sm.requests.list({ environmentId })` pages the calls made to the environment's apps. `sm.requests.get(id)` adds headers and a preview of each body. `sm.requests.body(id, 'request')` or `'response'` returns the full body as bytes. Recording is best effort.\n- YAML: `parseEnvironmentYaml(text)` turns a YAML document with the fields of `create` into its input.\n- Pagination: every `list` returns one page, `{ data, nextCursor }`. Pass `nextCursor` back as `cursor` until it is `null`.\n\n## Statuses\n\nEnums are open: handle values you don't recognise.\n\n- Environment `status`: `starting`, `running`, `failed`, `deleted`. A `deleted` environment has a `reason` when State Machines deleted it: `expired`, `failed` (it never reached `running`) or `cancelled` (staff ended it). The `reason` is `null` when a caller deleted it. When the environment failed, `failure` is `{ appName, code, message }`, and `code` is `start_failed`, `pause_removed`, `version_unavailable`, `invalid_setup` or `invalid_services`. For `invalid_setup` and `invalid_services`, the app refused a value in `setup` or in `services`, and `message` starts with its path.\n- Snapshot `status`: `saving`, `ready`, `failed`, `deleted`. A failed snapshot has `failure.code` `interrupted` or `internal_error`.\n- `create` throws `EnvironmentFailedError` when the environment fails, `UnexpectedStatusError` when it becomes `deleted` for another reason, and `WaitTimeoutError` when `timeoutMs` passes.\n- `snapshots.create` throws `SnapshotFailedError` when the snapshot fails and `UnexpectedStatusError` when it was deleted.\n- `sm.environments.wait(id, { status })` and `sm.snapshots.wait(id, { status })` wait for any status, and throw once it cannot be reached without another call.\n\n## Limits\n\nWhen `sm.me().limits` is non-null:\n\n- Running environments are capped per organization: `(await sm.me()).limits.concurrentEnvironments` counts `starting` and `running`.\n- `timeLimitMinutes` has a default and a maximum: `(await sm.me()).limits.timeLimitMinutes`. It counts `starting` and `running` only; at the end the environment is deleted with `reason: 'expired'`.\n\n## Errors and retries\n\nA failed API call throws `StateMachinesError` with `status`, `code`, `message`, `requestId`, `fields` and `retryAfterMs`. The SDK retries `502`, `503`, `504` and connection errors itself, until a deadline; `create` retries reuse one idempotency key. It retries nothing else.\n\n| Code | Status | What to do |\n| --- | --- | --- |\n| `invalid_request` | 400 | Read `fields`, keyed by the dotted path of each wrong field. |\n| `forbidden` | 403 | The key lacks the permission. |\n| `not_found` | 404 | Check the ID and the workspace. |\n| `invalid_status` | 409 | The status does not allow the call. Snapshots need a running environment. |\n| `snapshot_in_progress` | 409 | Wait for the running snapshot, then create another. |\n| `idempotency_key_reused` | 409 | The key was used with a different input. Replay with the exact same input. |\n| `workspace_archived` | 409 | The workspace is archived. |\n| `body_too_large` | 413 | The request body is over the API's size limit. Send less. |\n| `version_unavailable` | 422 | The app has no default version. |\n| `environment_limit_reached` | 429 | Delete only this task’s known leftovers, or ask the user to free capacity. |\n| `unavailable` | 503 | Retried automatically. If `create` still has an unknown outcome, replay the same input with the key retained before the original call. Do not generate a new key. |\n\n## Errors from the app URL\n\n`crm.fetch` returns these as responses with the same JSON envelope, and never retries them. Assert the expected status and result. For writes, read back the stored result. Never blindly retry a write whose outcome is unknown.\n\n| Code | Status | What to do |\n| --- | --- | --- |\n| `environment_not_running` | 409 | The environment is failed or deleted; `error.status` names it. Create another environment. |\n| `environment_starting` | 503 | For reads, wait `Retry-After` and retry. Check stored state before retrying an uncertain write. |\n| `environment_unavailable` | 503 | The app is restarting. For reads, wait `Retry-After`. Check stored state before retrying an uncertain write. |\n| `response_too_large` | 502 | The response is over the size limit. Ask for less, for example with a smaller page. |\n| `invalid_setup` | 503 | The app refused its `setup` after the environment started, such as in a newer version. `error.message` starts with the path of the value. Create another environment with a `setup` that the app accepts. |\n| `invalid_services` | 503 | The app refused a value in `services` after the environment started. `error.message` starts with the path of the value. Create another environment with `services` that the app accepts. |\n| `unsupported_protocol` | 501 | The app does not serve this path, method or content type, or the request body is compressed. |\n\n## Quickstart\n\nUse the project's TypeScript runner and secret loader. For an empty Node.js project, save as `state-quickstart.mts` and run `npx tsx state-quickstart.mts` with the key available. The check prints the response status and deletes the environment. Only after it prints `200`, report verified setup and the rerun command. Continue only with an explicitly supplied task; otherwise ask what to run next and wait with no setup environment left running.\n\n```ts\nimport { StateMachines } from '@usestatemachines/sdk';\n\nconst sm = new StateMachines(); // reads STATEMACHINES_API_KEY\nconst env = await sm.environments.create(\n { apps: ['salesforce'] },\n { wait: false },\n);\n\ntry {\n await sm.environments.wait(env.id, { status: 'running' });\n const salesforce = await env.connect('salesforce');\n const res = await salesforce.fetch('/services/data/v67.0/limits');\n console.log(res.status, await res.json());\n} finally {\n await env.delete();\n}\n```\n\n## Docs\n\n- [Documentation](https://docs.usestatemachines.com): guides, concepts and reference ([llms.txt](https://docs.usestatemachines.com/llms.txt))\n- [npm package](https://www.npmjs.com/package/@usestatemachines/sdk): install, README and versions\n- [Agent skill](https://docs.usestatemachines.com/skill.md): workflow, rules and troubleshooting for coding agents\n- [OpenAPI document](https://cdn.jsdelivr.net/npm/@usestatemachines/sdk/openapi.json): the HTTP API the SDK calls\n- [State Machines](https://usestatemachines.com): website\n",
21
+ "# State Machines\n\n> State Machines runs disposable environments of replica SaaS apps, such as Salesforce, for tests and coding agents. Create, connect to, and delete them with the TypeScript SDK `@usestatemachines/sdk`.\n\nUse the project's package manager to install or reuse `@usestatemachines/sdk`. It is ESM only and runs on Node.js and Cloudflare Workers, not browsers. `engines` in its `package.json` gives the Node.js versions. TypeScript needs `lib: [\"DOM\"]` or `@types/node`. `new StateMachines()` reads the API key from `STATEMACHINES_API_KEY` and doesn't read `.env` files; in Cloudflare Workers, pass `apiKey` explicitly. Never print the key. The installed `node_modules/@usestatemachines/sdk/README.md` matches your version: read it before relying on memory. Read the bundled `agents/SKILL.md` for the task workflow. Reuse the project's secret loader and check the key in the process that runs the test, not just the current shell. Installing the skill does not require SDK installation, example files or remote resources. Ask what the user wants to run and wait. Install the SDK and verify access when their chosen task needs it. Run the connection check below only if the user asks to verify the connection. Do not choose a business workflow from the repository.\n\n## Get a credential\n\nIf the project's existing command loads `STATEMACHINES_API_KEY`, reuse that command and secret loader. Keep the same credential loader, CLI profile and API origin across setup commands. Otherwise, run `npx @usestatemachines/cli@0.1.1 whoami --json`. If it succeeds, confirm the account and organization match the task, then use the workspace commands below. If the CLI credential is missing, expired or revoked, start `npx @usestatemachines/cli@0.1.1 login --no-wait --json`. For an expired or revoked saved session, add `--yes` to replace that invalid session in the same profile. Do not replace a valid session for a different account. Show its `verificationUrl` and `userCode`, and wait for approval. A matching code does not identify the requesting computer. After that approval, run `npx @usestatemachines/cli@0.1.1 login --resume --json` with the same profile to finish that attempt. Resume an existing pending login after approval instead of creating another. Report access denial or an API origin mismatch without changing accounts or permissions.\n\nIf a pending login's approval URL or code is unavailable, ask before recovering it. Explain that recovery may finish an already-approved login. After confirmation, run `npx @usestatemachines/cli@0.1.1 login --resume --json --stream` with the same profile and API origin. Keep that process running and show its URL and code. Request approval only for a recognized login. Wait for `signed-in` before continuing setup. Do not start another login or resume process. If streaming output is unavailable, report the blocker without reading credential files.\n\nFor `cli.not_linked`, run `npx @usestatemachines/cli@0.1.1 workspaces list --json`. Ask which workspace to use when there is more than one, then run `npx @usestatemachines/cli@0.1.1 link --workspace <workspace-id> --json`. Run the user's existing code with `npx @usestatemachines/cli@0.1.1 run -- <command>`. Never print or commit a credential.\n\n## CLI commands\n\nPrefix these commands with `npx @usestatemachines/cli@0.1.1`. Use `help --json` for flags and result schemas:\n\n- `login`, `logout`, `whoami`, `profiles list`, and `profiles use`\n- `workspaces list`, `workspaces use`, `link`, `unlink`, and `init`\n- `apps list`, `environments create`, `environments list`, `environments get`, `environments wait`, and `environments delete`\n- `connect`, `snapshots create`, `snapshots list`, `snapshots get`, `snapshots delete`, `requests list`, and `requests get`\n- `run -- <command>`, `env pull`, `api`, `doctor`, `version`, `help`, and `completion`\n\n## Concepts\n\n- Environment: one disposable run of one or more apps. `const env = await sm.environments.create({ apps: ['salesforce'] })` creates it and waits until it is `running`. Use `{ wait: false }` to retain the ID before startup. Wait for `running` inside `try`, then delete the owned environment in `finally`, including after a wait failure.\n- Apps: a string names the app by its ID, and the app's name in the environment is the same string. Use `{ appId: 'salesforce', name: 'crm', setup }` for your own name, for two copies of one app, or for the app's setup. Later calls take the name.\n- Credentials: `const crm = await env.connect('salesforce')` returns `token`, `services` and `fetch`. Each service has an `id`, a `url` and the `headers` its requests need. The credentials always list each `public` service of the app's version. Pass `{ services: [id] }` to `connect()` to also list a service by its ID. A `preview` service is listed only when named. `crm.service('api')` returns the `api` service, which is the app URL. `crm.fetch(path)` joins `path` to the `api` service URL and adds its headers, which carry `Authorization: Bearer` with `token`. For Salesforce SOAP, also put `crm.token` in the envelope's `SessionHeader/sessionId`. `fetch` never sends your API key, never follows redirects and never retries. `token` and each service's `headers` are secrets.\n- Actor: one of the app's users; requests run with that user's permissions. `connect()` acts as the app's default actor (for Salesforce, the administrator) unless you pass `actorId`. Salesforce actor IDs are its user IDs, such as `005…`. Find the app by name in `env.apps`, then page through `sm.apps.versions(appId)` to find its exact `versionId`. Read actor IDs from that version's `actors`.\n- Snapshot: the saved data of every app of a running environment, taken at one moment. `await env.snapshots.create({ name: 'seeded' })` waits until it is `ready`. `sm.environments.create({ snapshotId })` creates a new environment from it, in place of `apps`; each app keeps its name and version. A delete during the snapshot fails it with `interrupted`.\n- Recorded requests: `sm.requests.list({ environmentId })` pages the calls made to the environment's apps. `sm.requests.get(id)` adds headers and a preview of each body. `sm.requests.body(id, 'request')` or `'response'` returns the full body as bytes. Recording is best effort.\n- YAML: `parseEnvironmentYaml(text)` turns a YAML document with the fields of `create` into its input.\n- Pagination: every `list` returns one page, `{ data, nextCursor }`. Pass `nextCursor` back as `cursor` until it is `null`.\n\n## Statuses\n\nEnums are open: handle values you don't recognise.\n\n- Environment `status`: `starting`, `running`, `failed`, `deleted`. A `deleted` environment has a `reason` when State Machines deleted it: `expired`, `failed` (it never reached `running`) or `cancelled` (staff ended it). The `reason` is `null` when a caller deleted it. When the environment failed, `failure` is `{ appName, code, message }`, and `code` is `start_failed`, `pause_removed`, `version_unavailable`, `invalid_setup` or `invalid_services`. For `invalid_setup` and `invalid_services`, the app refused a value in `setup` or in `services`, and `message` starts with its path.\n- Snapshot `status`: `saving`, `ready`, `failed`, `deleted`. A failed snapshot has `failure.code` `interrupted` or `internal_error`.\n- `create` throws `EnvironmentFailedError` when the environment fails, `UnexpectedStatusError` when it becomes `deleted` for another reason, and `WaitTimeoutError` when `timeoutMs` passes.\n- `snapshots.create` throws `SnapshotFailedError` when the snapshot fails and `UnexpectedStatusError` when it was deleted.\n- `sm.environments.wait(id, { status })` and `sm.snapshots.wait(id, { status })` wait for any status, and throw once it cannot be reached without another call.\n\n## Limits\n\nWhen `sm.me().limits` is non-null:\n\n- Running environments are capped per organization: `(await sm.me()).limits.concurrentEnvironments` counts `starting` and `running`.\n- `timeLimitMinutes` has a default and a maximum: `(await sm.me()).limits.timeLimitMinutes`. It counts `starting` and `running` only; at the end the environment is deleted with `reason: 'expired'`.\n\n## Errors and retries\n\nA failed API call throws `StateMachinesError` with `status`, `code`, `message`, `requestId`, `fields` and `retryAfterMs`. The SDK retries `502`, `503`, `504` and connection errors itself, until a deadline; `create` retries reuse one idempotency key. It retries nothing else.\n\n| Code | Status | What to do |\n| --- | --- | --- |\n| `invalid_request` | 400 | Read `fields`, keyed by the dotted path of each wrong field. |\n| `forbidden` | 403 | The key lacks the permission. |\n| `not_found` | 404 | Check the ID and the workspace. |\n| `invalid_status` | 409 | The status does not allow the call. Snapshots need a running environment. |\n| `snapshot_in_progress` | 409 | Wait for the running snapshot, then create another. |\n| `idempotency_key_reused` | 409 | The key was used with a different input. Replay with the exact same input. |\n| `workspace_archived` | 409 | The workspace is archived. |\n| `body_too_large` | 413 | The request body is over the API's size limit. Send less. |\n| `version_unavailable` | 422 | The app has no default version. |\n| `environment_limit_reached` | 429 | Delete only this task’s known leftovers, or ask the user to free capacity. |\n| `unavailable` | 503 | Retried automatically. If `create` still has an unknown outcome, replay the same input with the key retained before the original call. Do not generate a new key. |\n\n## Errors from the app URL\n\n`crm.fetch` returns these as responses with the same JSON envelope, and never retries them. Assert the expected status and result. For writes, read back the stored result. Never blindly retry a write whose outcome is unknown.\n\n| Code | Status | What to do |\n| --- | --- | --- |\n| `environment_not_running` | 409 | The environment is failed or deleted; `error.status` names it. Create another environment. |\n| `environment_starting` | 503 | For reads, wait `Retry-After` and retry. Check stored state before retrying an uncertain write. |\n| `environment_unavailable` | 503 | The app is restarting. For reads, wait `Retry-After`. Check stored state before retrying an uncertain write. |\n| `response_too_large` | 502 | The response is over the size limit. Ask for less, for example with a smaller page. |\n| `invalid_setup` | 503 | The app refused its `setup` after the environment started, such as in a newer version. `error.message` starts with the path of the value. Create another environment with a `setup` that the app accepts. |\n| `invalid_services` | 503 | The app refused a value in `services` after the environment started. `error.message` starts with the path of the value. Create another environment with `services` that the app accepts. |\n| `unsupported_protocol` | 501 | The app does not serve this path, method or content type, or the request body is compressed. |\n\n## Quickstart\n\nUse the project's TypeScript runner with its secret loader, or pass that runner through `npx @usestatemachines/cli@0.1.1 run -- <command>`. For an empty Node.js project, save as `state-quickstart.mts` and run `npx @usestatemachines/cli@0.1.1 run -- npx tsx state-quickstart.mts`. The check prints the response status and deletes the environment. Only after it prints `200`, report verified setup and the rerun command. Continue only with an explicitly supplied task. Otherwise, ask what to run next and wait with no setup environment left running.\n\n```ts\nimport { StateMachines } from '@usestatemachines/sdk';\n\nconst sm = new StateMachines(); // reads STATEMACHINES_API_KEY\nconst env = await sm.environments.create(\n { apps: ['salesforce'] },\n { wait: false },\n);\n\ntry {\n await sm.environments.wait(env.id, { status: 'running' });\n const salesforce = await env.connect('salesforce');\n const res = await salesforce.fetch('/services/data/v67.0/limits');\n console.log(res.status, await res.json());\n} finally {\n await env.delete();\n}\n```\n\n## Docs\n\n- [Documentation](https://docs.usestatemachines.com): guides, concepts and reference ([llms.txt](https://docs.usestatemachines.com/llms.txt))\n- [npm package](https://www.npmjs.com/package/@usestatemachines/sdk): install, README and versions\n- [Agent skill](https://docs.usestatemachines.com/skill.md): workflow, rules and troubleshooting for coding agents\n- [OpenAPI document](https://cdn.jsdelivr.net/npm/@usestatemachines/sdk/openapi.json): the HTTP API the SDK calls\n- [State Machines](https://usestatemachines.com): website\n",
22
22
  skill:
23
- "---\nname: state-machines\ndescription: Use when setting up State Machines or running code, integration tests, or API experiments against disposable SaaS replicas such as Salesforce. Applies to @usestatemachines/sdk and STATEMACHINES_API_KEY, including environment creation, actors, snapshots, cleanup and recovery.\n---\n\n## Start with the user's task\n\nDuring skill installation, read the repository instructions and preserve its existing structure. Do not create a new project, install dependencies, write examples or create remote resources. Report the installed skill location and any reload requirement, then ask \"What would you like to run or test?\" and wait. Skill installation does not verify a connection.\n\nOnce the user chooses a task, inspect the project's package manager, runtime, runner and secret loader. Install or reuse a compatible `@usestatemachines/sdk` only when the task needs it. Read the installed README for API signatures and `package.json` for runtime requirements. The SDK is ESM and runs on Node.js and Cloudflare Workers, not in a browser. Follow the user's task and existing project conventions without requiring a separate onboarding example.\n\n## Check access in the right process\n\nThe SDK reads `STATEMACHINES_API_KEY` from the process environment. It does not load `.env` files. Use the project's existing test command or secret loader. An empty variable in your current shell does not prove that the test process lacks it. Verify presence without displaying the value. In Workers, pass the binding explicitly with `new StateMachines({ apiKey: env.STATEMACHINES_API_KEY })`.\n\nCall `sm.me()` to verify access and workspace scope before creating resources. `me.limits` can be null. If the key is missing, prepare the local test or example and tell the user the exact local file, binding, or launch command to configure and rerun, without asking for the key in chat. Do not call the API until access is available. If access is denied or the workspace differs from the task, report the blocker. Do not invite or remove members, or change permissions.\n\nNever print, log or commit API keys, `credentials.token`, the `headers` of `credentials.services`, environment dumps, or secret-file contents. Use synthetic test data.\n\n## Run the user’s chosen task\n\nApply this section after setup, once the user has chosen a task.\n\n- `sm.apps.list()` returns app IDs. Every list returns `{ data, nextCursor }`. Follow cursors until the required entry is found or `nextCursor` is null.\n- Create only the apps the task needs. A string such as `'salesforce'` is both its app ID and its name in the environment. For named apps, later `connect()` calls use the name.\n- `connect(appName)` uses the app's default actor, the Salesforce administrator. For another actor, find the matching app by name in `environment.apps`, read its `versionId`, then find that exact version through `sm.apps.versions(appId)`. Use an actor ID from its `actors`, never a role label, workspace member ID, or the first version in a list.\n- Check the app's services and [known differences](https://docs.usestatemachines.com/apps/known-differences.md) for the behavior under test. A replica does not import a customer's org or run arbitrary customer automation.\n- Configure the existing integration's test transport with the returned app URL and credentials. Verify its requests reach that URL. A successful standalone SDK request does not prove the integration uses the replica. If transport configuration needs a broader change, explain it before expanding the task.\n- Check the expected HTTP status and assert the result. For writes, read the stored data back and assert the relevant fields. For negative tests, assert the expected error. Do not weaken an assertion to make unsupported behavior pass.\n\n## Own the full lifecycle\n\nCreate with `{ wait: false }` to retain the accepted environment ID before waiting. Enter `try` immediately, then wait for `running`, connect and run the check inside it. Delete in `finally`, including after a startup failure, timeout or failed assertion. An aborted wait does not cancel the work that the API accepted. Cleanup must use a fresh, un-aborted signal. Preserve both verification and cleanup errors, and report the environment ID if deletion fails.\n\nDelete only environments created for this task. If the organization is at its limit, clean up a known leftover from this task or ask the user to free capacity. Do not delete unrelated environments or a user-supplied reusable snapshot.\n\nThe SDK retries management connection errors and `502`, `503`, `504` with one idempotency key per create call. To recover across separate attempts or a restart, retain the input and an explicit `idempotencyKey` before creation. Replay using the same input, key, workspace and caller. Do not create a replacement with a new key while the first outcome is unknown. See [operation recovery](https://docs.usestatemachines.com/sdk-api/recovery.md).\n\n## Optional connection check\n\nRun this only when the user asks to verify the connection. It proves the connection and cleanup, not an existing integration's behavior. Save it in the project's examples or test location and run it with the existing TypeScript runner and secret loader. In a new Node.js project, `state-quickstart.mts` and `npx tsx state-quickstart.mts` are a fallback. Load the key into that process first.\n\n```ts\nimport { StateMachines } from '@usestatemachines/sdk';\n\nconst sm = new StateMachines(); // reads STATEMACHINES_API_KEY\nconst env = await sm.environments.create(\n { apps: ['salesforce'] },\n { wait: false },\n);\n\ntry {\n await sm.environments.wait(env.id, { status: 'running' });\n const salesforce = await env.connect('salesforce');\n const res = await salesforce.fetch('/services/data/v67.0/limits');\n console.log(res.status, await res.json());\n} finally {\n await env.delete();\n}\n```\n\n## Load details when needed\n\n- **Repeated fixtures or parallel tests:** Read [isolated tests](https://docs.usestatemachines.com/sdk-api/testing.md). Save seeded data with `env.snapshots.create({ name })`, then create each test's environment with `{ snapshotId }`. A snapshot in `saving` cannot be deleted yet. Retain its ID and wait for `ready` or `failed` before cleanup. Delete a reusable snapshot only when the task calls for it.\n- **Another actor, vendor client or SOAP:** Read [credentials](https://docs.usestatemachines.com/environments/credentials.md). `credentials.fetch()` confines requests to the app URL, adds its authentication headers, and never follows redirects. External clients must send a service's `headers` only to its `url` and disable redirects. For Salesforce SOAP, put the actor token in `SessionHeader/sessionId` in the envelope.\n- **Cloudflare Workers:** Pass the key explicitly. Use `{ wait: false }` and keep the environment ID across requests or in the job's state. A request ending does not cancel startup. Follow the worker's background-job lifecycle for polling and cleanup.\n- **Long runs:** Read `sm.me().limits` when non-null. The environment time limit covers starting and running. Use a supported `timeLimitMinutes` when the task needs it.\n\n## Recover without changing the test's meaning\n\nManagement failures throw SDK errors. App HTTP failures are returned as `Response` objects. Use `instanceof` with the SDK error classes. Native `credentials.fetch()` never retries. Read the State Machines error code in `error.code` and distinguish it from an app's own error.\n\n| Failure | Next action |\n| --- | --- |\n| `invalid_request` | Read `err.fields` and the installed method signature. |\n| `version_unavailable` | Discover the app ID and available versions. Stop if the required version is unavailable. |\n| `environment_limit_reached` | Clean up only this task's known leftovers, or report the capacity blocker. |\n| `EnvironmentFailedError` | Read `err.environment.failure` and clean up the owned environment. |\n| `WaitTimeoutError` or abort | Use the retained environment ID. If older code waited inside `create`, a non-null `WaitTimeoutError.resource` carries its ID. If neither an ID nor a replay key is recoverable, report unresolved cleanup. Do not guess from a list. Wait again only if the task should continue. Otherwise delete the owned environment. |\n| Unknown create outcome | Replay the retained input and idempotency key. Do not start another operation with a new key. |\n| `invalid_status` | Read the current resource status before choosing the next call. |\n| `snapshot_in_progress` | Find the saving snapshot for this environment and wait for it before creating another. |\n| `SnapshotFailedError` | Report the failure. Retry only if the task still needs a snapshot and the source environment is running. |\n| App `environment_not_running` | Inspect the environment. Do not silently switch the integration to another target. |\n| App `invalid_setup` or `invalid_services` | The app refused a value of `setup` or `services` after the start. `error.message` starts with its path. Create another environment with a value that the app accepts, and delete the owned one. |\n| App or gateway `503` | For a retryable read, honor `Retry-After`. For a write with an uncertain outcome, inspect stored state or report uncertainty before retrying. |\n| Cleanup failure | Preserve the task result, report the owned resource ID and retry deletion with valid access and a fresh signal. Do not report cleanup as successful. |\n\n## Finish with evidence\n\nLeave the smallest rerunnable test or example. Report the behavior checked, command, assertion result, SDK and app versions, selected actor, resource IDs without secrets, and cleanup outcome. Say when execution was blocked or only typechecking ran. A passing replica check establishes the asserted behavior for that version and actor, not full production compatibility.\n\nFind other guides through https://docs.usestatemachines.com/llms.txt. Read focused pages before the full documentation bundle. This dashboard also serves an agent index at {{origin}}/llms.txt.\n",
23
+ "---\nname: state-machines\ndescription: Use when setting up State Machines or running code, integration tests, or API experiments against disposable SaaS replicas such as Salesforce. Applies to @usestatemachines/sdk and STATEMACHINES_API_KEY, including environment creation, actors, snapshots, cleanup and recovery.\n---\n\n## Start with the user's task\n\nDuring skill installation, read the repository instructions and preserve its existing structure. Do not create a new project, install project dependencies, write examples or create remote resources. Report the installed skill location and any reload requirement, then ask \"What would you like to run or test?\" and wait. Skill installation does not verify a connection.\n\nOnce the user chooses a task, inspect the project's package manager, runtime, runner and secret loader. Install or reuse a compatible `@usestatemachines/sdk` only when the task needs it. Read the installed README for API signatures and `package.json` for runtime requirements. The SDK is ESM and runs on Node.js and Cloudflare Workers, not in a browser. Follow the user's task and existing project conventions without requiring a separate onboarding example.\n\n## Get a credential\n\nThe SDK reads `STATEMACHINES_API_KEY` from the process environment. It does not load `.env` files. Use the project's existing test command or secret loader. An empty variable in your current shell does not prove that the test process lacks it. Verify presence without displaying the value. In Workers, pass the binding explicitly with `new StateMachines({ apiKey: env.STATEMACHINES_API_KEY })`.\n\nIf the project's existing command loads `STATEMACHINES_API_KEY`, keep using that command and secret loader. Keep the same credential loader, CLI profile and API origin across setup commands. Call `sm.me()` in that process to verify access and workspace scope. `me.limits` can be null.\n\nOtherwise, run `npx @usestatemachines/cli@0.1.1 whoami --json`. Confirm the returned account and organization match the task, then use the workspace commands below. If the CLI credential is missing, expired or revoked, complete this login:\n\n1. Run `npx @usestatemachines/cli@0.1.1 login --no-wait --json`. For an expired or revoked saved session, add `--yes` to replace that invalid session in the same profile. Do not replace a valid session for a different account.\n2. Show the returned `verificationUrl` and `userCode`. Ask the user to approve only the login you just started. A matching code does not identify the computer requesting access. Wait for the user to approve the login.\n3. After that approval, run `npx @usestatemachines/cli@0.1.1 login --resume --json` with the same profile used to start the login. Resume that attempt instead of starting another login. If it expired, offer a new login.\n\nIf a pending login's approval URL or code is unavailable, ask before recovering it. Explain that recovery may finish an already-approved login. After confirmation, run `npx @usestatemachines/cli@0.1.1 login --resume --json --stream` with the same profile and API origin. Keep that process running and show its URL and code. Request approval only for a recognized login. Wait for `signed-in` before continuing setup. Do not start another login or resume process. If streaming output is unavailable, report the blocker without reading credential files.\n\nIf a command returns `cli.not_linked`, run `npx @usestatemachines/cli@0.1.1 workspaces list --json`. Ask the user which workspace to use when there is more than one. Run `npx @usestatemachines/cli@0.1.1 link --workspace <workspace-id> --json` with the selected ID.\n\nRun the user's existing command with `npx @usestatemachines/cli@0.1.1 run -- <command>`. The child receives a workspace key. The CLI keeps the credential out of its output.\n\nIf access is denied, the API origin differs, or the workspace differs from the task, report the blocker. Do not invite or remove members, or change permissions.\n\nNever print, log or commit API keys, `credentials.token`, the `headers` of `credentials.services`, environment dumps, or secret-file contents. Use synthetic test data.\n\n## Run the user’s chosen task\n\nApply this section after setup, once the user has chosen a task.\n\n- `sm.apps.list()` returns app IDs. Every list returns `{ data, nextCursor }`. Follow cursors until the required entry is found or `nextCursor` is null.\n- Create only the apps the task needs. A string such as `'salesforce'` is both its app ID and its name in the environment. For named apps, later `connect()` calls use the name.\n- `connect(appName)` uses the app's default actor, the Salesforce administrator. For another actor, find the matching app by name in `environment.apps`, read its `versionId`, then find that exact version through `sm.apps.versions(appId)`. Use an actor ID from its `actors`, never a role label, workspace member ID, or the first version in a list.\n- Check the app's services and [known differences](https://docs.usestatemachines.com/apps/known-differences.md) for the behavior under test. A replica does not import a customer's org or run arbitrary customer automation.\n- Configure the existing integration's test transport with the returned app URL and credentials. Verify its requests reach that URL. A successful standalone SDK request does not prove the integration uses the replica. If transport configuration needs a broader change, explain it before expanding the task.\n- Check the expected HTTP status and assert the result. For writes, read the stored data back and assert the relevant fields. For negative tests, assert the expected error. Do not weaken an assertion to make unsupported behavior pass.\n\n## Own the full lifecycle\n\nCreate with `{ wait: false }` to retain the accepted environment ID before waiting. Enter `try` immediately, then wait for `running`, connect and run the check inside it. Delete in `finally`, including after a startup failure, timeout or failed assertion. An aborted wait does not cancel the work that the API accepted. Cleanup must use a fresh, un-aborted signal. Preserve both verification and cleanup errors, and report the environment ID if deletion fails.\n\nDelete only environments created for this task. If the organization is at its limit, clean up a known leftover from this task or ask the user to free capacity. Do not delete unrelated environments or a user-supplied reusable snapshot.\n\nThe SDK retries management connection errors and `502`, `503`, `504` with one idempotency key per create call. To recover across separate attempts or a restart, retain the input and an explicit `idempotencyKey` before creation. Replay using the same input, key, workspace and caller. Do not create a replacement with a new key while the first outcome is unknown. See [operation recovery](https://docs.usestatemachines.com/sdk-api/recovery.md).\n\n## Optional connection check\n\nRun this only when the user asks to verify the connection. It proves the connection and cleanup, not an existing integration's behavior. Save it in the project's examples or test location. Use the existing TypeScript runner and secret loader, or pass that runner through `npx @usestatemachines/cli@0.1.1 run -- <command>`. For an empty Node.js project, save `state-quickstart.mts` and run `npx @usestatemachines/cli@0.1.1 run -- npx tsx state-quickstart.mts`.\n\n```ts\nimport { StateMachines } from '@usestatemachines/sdk';\n\nconst sm = new StateMachines(); // reads STATEMACHINES_API_KEY\nconst env = await sm.environments.create(\n { apps: ['salesforce'] },\n { wait: false },\n);\n\ntry {\n await sm.environments.wait(env.id, { status: 'running' });\n const salesforce = await env.connect('salesforce');\n const res = await salesforce.fetch('/services/data/v67.0/limits');\n console.log(res.status, await res.json());\n} finally {\n await env.delete();\n}\n```\n\n## Load details when needed\n\n- **Repeated fixtures or parallel tests:** Read [isolated tests](https://docs.usestatemachines.com/sdk-api/testing.md). Save seeded data with `env.snapshots.create({ name })`, then create each test's environment with `{ snapshotId }`. A snapshot in `saving` cannot be deleted yet. Retain its ID and wait for `ready` or `failed` before cleanup. Delete a reusable snapshot only when the task calls for it.\n- **Another actor, vendor client or SOAP:** Read [credentials](https://docs.usestatemachines.com/environments/credentials.md). `credentials.fetch()` confines requests to the app URL, adds its authentication headers, and never follows redirects. External clients must send a service's `headers` only to its `url` and disable redirects. For Salesforce SOAP, put the actor token in `SessionHeader/sessionId` in the envelope.\n- **Cloudflare Workers:** Pass the key explicitly. Use `{ wait: false }` and keep the environment ID across requests or in the job's state. A request ending does not cancel startup. Follow the worker's background-job lifecycle for polling and cleanup.\n- **Long runs:** Read `sm.me().limits` when non-null. The environment time limit covers starting and running. Use a supported `timeLimitMinutes` when the task needs it.\n\n## Recover without changing the test's meaning\n\nManagement failures throw SDK errors. App HTTP failures are returned as `Response` objects. Use `instanceof` with the SDK error classes. Native `credentials.fetch()` never retries. Read the State Machines error code in `error.code` and distinguish it from an app's own error.\n\n| Failure | Next action |\n| --- | --- |\n| `invalid_request` | Read `err.fields` and the installed method signature. |\n| `version_unavailable` | Discover the app ID and available versions. Stop if the required version is unavailable. |\n| `environment_limit_reached` | Clean up only this task's known leftovers, or report the capacity blocker. |\n| `EnvironmentFailedError` | Read `err.environment.failure` and clean up the owned environment. |\n| `WaitTimeoutError` or abort | Use the retained environment ID. If older code waited inside `create`, a non-null `WaitTimeoutError.resource` carries its ID. If neither an ID nor a replay key is recoverable, report unresolved cleanup. Do not guess from a list. Wait again only if the task should continue. Otherwise delete the owned environment. |\n| Unknown create outcome | Replay the retained input and idempotency key. Do not start another operation with a new key. |\n| `invalid_status` | Read the current resource status before choosing the next call. |\n| `snapshot_in_progress` | Find the saving snapshot for this environment and wait for it before creating another. |\n| `SnapshotFailedError` | Report the failure. Retry only if the task still needs a snapshot and the source environment is running. |\n| App `environment_not_running` | Inspect the environment. Do not silently switch the integration to another target. |\n| App `invalid_setup` or `invalid_services` | The app refused a value of `setup` or `services` after the start. `error.message` starts with its path. Create another environment with a value that the app accepts, and delete the owned one. |\n| App or gateway `503` | For a retryable read, honor `Retry-After`. For a write with an uncertain outcome, inspect stored state or report uncertainty before retrying. |\n| Cleanup failure | Preserve the task result, report the owned resource ID and retry deletion with valid access and a fresh signal. Do not report cleanup as successful. |\n\n## Finish with evidence\n\nLeave the smallest rerunnable test or example. Report the behavior checked, command, assertion result, SDK and app versions, selected actor, resource IDs without secrets, and cleanup outcome. Say when execution was blocked or only typechecking ran. A passing replica check establishes the asserted behavior for that version and actor, not full production compatibility.\n\nFind other guides through https://docs.usestatemachines.com/llms.txt. Read focused pages before the full documentation bundle. This dashboard also serves an agent index at {{origin}}/llms.txt.\n",
24
24
  });
package/agents/llms.txt CHANGED
@@ -4,6 +4,24 @@
4
4
 
5
5
  Use the project's package manager to install or reuse `@usestatemachines/sdk`. It is ESM only and runs on Node.js and Cloudflare Workers, not browsers. `engines` in its `package.json` gives the Node.js versions. TypeScript needs `lib: ["DOM"]` or `@types/node`. `new StateMachines()` reads the API key from `STATEMACHINES_API_KEY` and doesn't read `.env` files; in Cloudflare Workers, pass `apiKey` explicitly. Never print the key. The installed `node_modules/@usestatemachines/sdk/README.md` matches your version: read it before relying on memory. Read the bundled `agents/SKILL.md` for the task workflow. Reuse the project's secret loader and check the key in the process that runs the test, not just the current shell. Installing the skill does not require SDK installation, example files or remote resources. Ask what the user wants to run and wait. Install the SDK and verify access when their chosen task needs it. Run the connection check below only if the user asks to verify the connection. Do not choose a business workflow from the repository.
6
6
 
7
+ ## Get a credential
8
+
9
+ If the project's existing command loads `STATEMACHINES_API_KEY`, reuse that command and secret loader. Keep the same credential loader, CLI profile and API origin across setup commands. Otherwise, run `npx @usestatemachines/cli@0.1.1 whoami --json`. If it succeeds, confirm the account and organization match the task, then use the workspace commands below. If the CLI credential is missing, expired or revoked, start `npx @usestatemachines/cli@0.1.1 login --no-wait --json`. For an expired or revoked saved session, add `--yes` to replace that invalid session in the same profile. Do not replace a valid session for a different account. Show its `verificationUrl` and `userCode`, and wait for approval. A matching code does not identify the requesting computer. After that approval, run `npx @usestatemachines/cli@0.1.1 login --resume --json` with the same profile to finish that attempt. Resume an existing pending login after approval instead of creating another. Report access denial or an API origin mismatch without changing accounts or permissions.
10
+
11
+ If a pending login's approval URL or code is unavailable, ask before recovering it. Explain that recovery may finish an already-approved login. After confirmation, run `npx @usestatemachines/cli@0.1.1 login --resume --json --stream` with the same profile and API origin. Keep that process running and show its URL and code. Request approval only for a recognized login. Wait for `signed-in` before continuing setup. Do not start another login or resume process. If streaming output is unavailable, report the blocker without reading credential files.
12
+
13
+ For `cli.not_linked`, run `npx @usestatemachines/cli@0.1.1 workspaces list --json`. Ask which workspace to use when there is more than one, then run `npx @usestatemachines/cli@0.1.1 link --workspace <workspace-id> --json`. Run the user's existing code with `npx @usestatemachines/cli@0.1.1 run -- <command>`. Never print or commit a credential.
14
+
15
+ ## CLI commands
16
+
17
+ Prefix these commands with `npx @usestatemachines/cli@0.1.1`. Use `help --json` for flags and result schemas:
18
+
19
+ - `login`, `logout`, `whoami`, `profiles list`, and `profiles use`
20
+ - `workspaces list`, `workspaces use`, `link`, `unlink`, and `init`
21
+ - `apps list`, `environments create`, `environments list`, `environments get`, `environments wait`, and `environments delete`
22
+ - `connect`, `snapshots create`, `snapshots list`, `snapshots get`, `snapshots delete`, `requests list`, and `requests get`
23
+ - `run -- <command>`, `env pull`, `api`, `doctor`, `version`, `help`, and `completion`
24
+
7
25
  ## Concepts
8
26
 
9
27
  - Environment: one disposable run of one or more apps. `const env = await sm.environments.create({ apps: ['salesforce'] })` creates it and waits until it is `running`. Use `{ wait: false }` to retain the ID before startup. Wait for `running` inside `try`, then delete the owned environment in `finally`, including after a wait failure.
@@ -66,7 +84,7 @@ A failed API call throws `StateMachinesError` with `status`, `code`, `message`,
66
84
 
67
85
  ## Quickstart
68
86
 
69
- Use the project's TypeScript runner and secret loader. For an empty Node.js project, save as `state-quickstart.mts` and run `npx tsx state-quickstart.mts` with the key available. The check prints the response status and deletes the environment. Only after it prints `200`, report verified setup and the rerun command. Continue only with an explicitly supplied task; otherwise ask what to run next and wait with no setup environment left running.
87
+ Use the project's TypeScript runner with its secret loader, or pass that runner through `npx @usestatemachines/cli@0.1.1 run -- <command>`. For an empty Node.js project, save as `state-quickstart.mts` and run `npx @usestatemachines/cli@0.1.1 run -- npx tsx state-quickstart.mts`. The check prints the response status and deletes the environment. Only after it prints `200`, report verified setup and the rerun command. Continue only with an explicitly supplied task. Otherwise, ask what to run next and wait with no setup environment left running.
70
88
 
71
89
  ```ts
72
90
  import { StateMachines } from '@usestatemachines/sdk';
package/agents/prompt.md CHANGED
@@ -1,6 +1,12 @@
1
- Install the State Machines skill in this existing repository so I can use disposable app environments with my coding agent.
1
+ Set up State Machines in this existing repository so I can use disposable app environments with my coding agent.
2
2
 
3
- 1. Read the repository instructions. Download the skill from https://docs.usestatemachines.com/skill.md and install it as `state-machines/SKILL.md` in this agent's supported project-local skills directory. Reuse an identical installed copy. Preserve customizations and ask before replacing a different copy. If persistent skills are unsupported, read it for this session and explain the limitation. If the download fails, report the blocker.
4
- 2. Read the skill. Do not create a new project, install the SDK, write example code, change an integration or create remote resources during skill installation. Keep the repository's existing conventions.
5
- 3. The API key belongs in the project's existing secret configuration as `STATEMACHINES_API_KEY`. Never ask me to paste it into chat, print it or commit it. If I need help configuring it, identify the existing secret loader and explain where to save it without reading or displaying secret values.
6
- 4. Report where the skill was installed and whether the agent needs to reload it. Skill installation alone does not verify an API connection. Ask: "What would you like to run or test?" Wait for my answer before making further changes.
3
+ 1. Read the repository instructions and inspect the existing State Machines workspace link and agent skill. Keep the repository's structure and conventions. Check that Node.js meets the CLI's runtime requirement. Use the pinned npx command below. A global CLI installation is not required.
4
+ 2. Run `npx @usestatemachines/cli@0.1.1 whoami --json`. If the project already has a `STATEMACHINES_API_KEY` loader, run the check through that loader without reading or displaying the secret. Use the same credential loader, CLI profile and API origin for every setup command below, including workspace discovery and init. Reuse valid access and confirm the returned account and organization match my task. If the existing API key or CLI profile works, skip sign-in and reuse it for setup. Do not create a replacement API key. Report an access denial or origin mismatch instead of changing accounts or permissions.
5
+ 3. Only when no project API key is configured and the access check reports a missing, expired or revoked CLI credential, run `npx @usestatemachines/cli@0.1.1 login --no-wait --json`. For an expired or revoked saved session, add `--yes` to replace that invalid session in the same profile. Do not replace a valid session for a different account. Show me the returned verification URL and code. Wait for my approval before resuming the same login with `npx @usestatemachines/cli@0.1.1 login --resume --json`, using the same profile. If a login is already pending, resume it after approval instead of starting another. If the request expires, offer a new login. Never ask me to paste a credential into chat, print it or commit it.
6
+ If a pending login's approval URL or code is unavailable, ask before recovering it. Explain that recovery may finish an already-approved login. After confirmation, run `npx @usestatemachines/cli@0.1.1 login --resume --json --stream` with the same profile and API origin. Keep that process running and show its URL and code. Request approval only for a recognized login. Wait for `signed-in` before continuing setup. Do not start another login or resume process. If streaming output is unavailable, report the blocker without reading credential files.
7
+
8
+ 4. Run `npx @usestatemachines/cli@0.1.1 init --agent <this-agent> --yes`, choosing `claude` for Claude Code or `codex` for Codex. Reuse the repository's existing workspace link. If the CLI needs a workspace, run `npx @usestatemachines/cli@0.1.1 workspaces list --json`. Ask me which to use when more than one is available, then pass `--workspace <workspace-id>` to init. Do not replace a different workspace link or a customized skill without my approval. Read the installed skill and use its pinned npx commands for later work.
9
+ 5. Do not create a new project, install the SDK, write example code, change an integration or create remote resources during skill installation. Do not add `--sdk`, `--quickstart` or `--env` to init unless I ask for those changes.
10
+ 6. Report where the skill was installed, which workspace is linked, and whether the agent needs to reload it. Setup alone does not verify an integration. Ask: "What would you like to run or test?" Wait for my answer before making further changes.
11
+
12
+ If this agent cannot run commands or is not Claude Code or Codex, ask me to download https://app.usestatemachines.com/skill.md from my signed-in browser and provide the file. Do not request session cookies or credentials. Install that file in the agent's supported project-local skills directory, preserving any customized skill. If persistent skills are unsupported, read the file for this session and explain the limitation. Report setup as incomplete if the file is unavailable.
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Browser helpers for a UI session that a trusted server launched. This
3
+ * module imports nothing: it holds no management client, API key or token,
4
+ * so a browser bundle that imports it holds none either.
5
+ */
6
+ /** The fields of a launch that a browser needs. A launch from the API has them all. */
7
+ export interface UiLaunch {
8
+ readonly sessionId: string;
9
+ /** `embedded` posts into an iframe; `page` posts into a window of its own. */
10
+ readonly mode: 'embedded' | 'page';
11
+ /** The session's own origin. */
12
+ readonly origin: string;
13
+ /** Where the browser posts the grant. It is on `origin`. */
14
+ readonly exchangeUrl: string;
15
+ /** One use. Post it once, from the origin the launch named. */
16
+ readonly grant: string;
17
+ }
18
+ /** A message that an embedded session posts to the page that frames it. */
19
+ export type UiSessionMessage = {
20
+ readonly event: 'ready';
21
+ readonly sessionId: string;
22
+ } | {
23
+ readonly event: 'logout';
24
+ readonly sessionId: string;
25
+ } | {
26
+ readonly event: 'failure';
27
+ /** Null when the session failed before it knew its ID, such as when the browser blocked its cookie. */
28
+ readonly sessionId: string | null;
29
+ /** A short code, such as `ui_access_required`, or null. */
30
+ readonly reason: string | null;
31
+ };
32
+ export interface ListenOptions {
33
+ /** Removes the listener when it aborts. */
34
+ readonly signal?: AbortSignal;
35
+ }
36
+ /**
37
+ * Posts the launch's grant to its exchange URL, targeted at `target`: an
38
+ * iframe for an `embedded` launch, or a window for a `page` launch. Open the
39
+ * window in the click handler, such as with `window.open('about:blank',
40
+ * name)`, so the browser does not block it. The session then loads in
41
+ * `target`, and a window loses its `opener`. A launch posts once; a second
42
+ * post of the same grant is refused.
43
+ *
44
+ * The posting page must send its `Origin`, so its `Referrer-Policy` must not
45
+ * be `no-referrer`.
46
+ */
47
+ export declare function submitLaunch(launch: UiLaunch, target: HTMLIFrameElement | Window): void;
48
+ /**
49
+ * The session message in `data`, or null when `data` is not exactly one of
50
+ * the messages for `sessionId`. A failure may carry a null session ID.
51
+ */
52
+ export declare function parseSessionMessage(data: unknown, sessionId: string): UiSessionMessage | null;
53
+ /**
54
+ * Calls `onMessage` with each message that the session in `source` posts:
55
+ * only from the launch's exact origin, only from `source`'s window, and only
56
+ * for the launch's session. Every other message is ignored. Answers the
57
+ * function that removes the listener.
58
+ */
59
+ export declare function listenToSession(launch: Pick<UiLaunch, 'sessionId' | 'origin'>, source: HTMLIFrameElement | Window, onMessage: (message: UiSessionMessage) => void, options?: ListenOptions): () => void;