@usestatemachines/sdk 0.6.1 → 0.7.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.
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # @usestatemachines/sdk
2
2
 
3
3
  TypeScript SDK for [State Machines](https://usestatemachines.com): create disposable environments
4
- of replica apps, connect to them, take snapshots of their data, pause and resume them, and delete
4
+ of replica apps, connect to them, take snapshots of their data, and delete
5
5
  them.
6
6
 
7
7
  Documentation: [docs.usestatemachines.com](https://docs.usestatemachines.com)
@@ -49,13 +49,12 @@ const sm = new StateMachines({
49
49
  | `sm` | `me()` |
50
50
  | `sm.apps` | `list(query?)`, `versions(appId, query?)` |
51
51
  | `sm.workspaces` | `list(query?)`, `get(id)` |
52
- | `sm.environments` | `create(input, options?)`, `list(query?)`, `get(id)`, `update(id, { name?, labels? })`, `pause(id)`, `resume(id, options?)`, `delete(id)`, `wait(id, { status })`, `connect(id, app, { actorId? }?)` |
52
+ | `sm.environments` | `create(input, options?)`, `list(query?)`, `get(id)`, `update(id, { name?, labels? })`, `delete(id)`, `wait(id, { status })`, `connect(id, app, { actorId? }?)` |
53
53
  | `sm.snapshots` | `create({ environmentId, name? }, options?)`, `list(query?)`, `get(id)`, `update(id, { name })`, `delete(id)`, `wait(id, { status })` |
54
54
  | `sm.requests` | `list(query)`, `get(id)`, `stats(query)`, `body(id, 'request' \| 'response')` |
55
55
  | `sm.auditEvents` | `list(query?)` |
56
56
 
57
- Every environment the SDK returns carries bound helpers: `env.connect(app)`, `env.pause()`,
58
- `env.resume()`, `env.delete()`, `env.refresh()` and `env.snapshots.create({ name? })`. They are
57
+ Every environment the SDK returns carries bound helpers: `env.connect(app)`, `env.delete()`, `env.refresh()` and `env.snapshots.create({ name? })`. They are
59
58
  not enumerable, so `JSON.stringify(env)` holds only the API's fields.
60
59
 
61
60
  ## Apps
@@ -93,7 +92,7 @@ A new environment runs each app's default version.
93
92
  `env.connect(app, { actorId? })` returns the API's `Credentials`: `url`, `headers`, `token`,
94
93
  `expiresAt` and `login`, plus `fetch(path, init?)`.
95
94
 
96
- - `fetch` joins `path` to `url`; a leading `/` means the root of the app. It adds `headers`
95
+ - `fetch` joins `path` to `url`. A leading `/` means the root of the app. It adds `headers`
97
96
  (`StateMachines-Environment-Token`), and puts `token` in the header of the API whose path
98
97
  matches: `Authorization: Bearer` for Salesforce REST and Bulk 2.0.
99
98
  - Salesforce SOAP takes the token in the envelope: put `credentials.token` in
@@ -110,13 +109,6 @@ A new environment runs each app's default version.
110
109
  Environments created with `tokenInUrl: true` carry the environment token in `url` instead, and
111
110
  `headers` is empty.
112
111
 
113
- ## Pausing and resuming
114
-
115
- `env.pause()` takes effect when the API answers: the gateway refuses calls, the compute is
116
- released and the data stays. The time limit stops counting. `env.resume()` boots the apps again
117
- on the same data and waits until `running`. A paused or failed environment is deleted after one
118
- day.
119
-
120
112
  ## Snapshots
121
113
 
122
114
  ```ts
@@ -125,7 +117,7 @@ const copy = await sm.environments.create({ snapshotId: seeded.id });
125
117
  ```
126
118
 
127
119
  A snapshot holds every app of a running environment at one moment, and waits until `ready`. A
128
- pause or delete during the snapshot fails it with `failure.code: 'interrupted'`. One snapshot is
120
+ delete during the snapshot fails it with `failure.code: 'interrupted'`. One snapshot is
129
121
  `saving` per environment at a time. `sm.snapshots.delete(id)` deletes a `ready` or `failed`
130
122
  snapshot.
131
123
 
@@ -133,14 +125,13 @@ snapshot.
133
125
 
134
126
  | Method | Waits for | Throws |
135
127
  | --- | --- | --- |
136
- | `environments.create` | `running` | `EnvironmentFailedError` when the environment fails; `UnexpectedStatusError` on `paused` or `deleted` for another reason; `WaitTimeoutError` |
137
- | `environments.resume` | `running` | `EnvironmentFailedError` when the environment fails; `UnexpectedStatusError` on `paused` or `deleted` for another reason; `WaitTimeoutError` |
138
- | `snapshots.create` | `ready` | `SnapshotFailedError` on `failed`; `UnexpectedStatusError` on `deleted` |
128
+ | `environments.create` | `running` | `EnvironmentFailedError` when the environment fails, `UnexpectedStatusError` on `deleted` for another reason, and `WaitTimeoutError` |
129
+ | `snapshots.create` | `ready` | `SnapshotFailedError` on `failed` and `UnexpectedStatusError` on `deleted` |
139
130
  | `environments.wait(id, { status })`, `snapshots.wait(id, { status })` | `status` | the same rule: a status that cannot reach `status` without another call |
140
131
 
141
- Pass `{ wait: false }` to `create`, `resume` or `snapshots.create` to get the API's answer at once.
132
+ Pass `{ wait: false }` to `create` or `snapshots.create` to get the API's answer at once.
142
133
  `timeoutMs` bounds the wait, and `signal` aborts it. The option types document each default.
143
- `pause` and `delete` do not wait: the status changes when the API answers.
134
+ `delete` returns when the API confirms the status change.
144
135
 
145
136
  ## YAML
146
137
 
@@ -171,7 +162,7 @@ response body.
171
162
 
172
163
  | Code | Status | Meaning |
173
164
  | --- | --- | --- |
174
- | `invalid_request` | 400 | A wrong field; see `fields`. |
165
+ | `invalid_request` | 400 | A wrong field. See `fields`. |
175
166
  | `unauthenticated` | 401 | A missing, expired or revoked key. |
176
167
  | `forbidden` | 403 | The key lacks the permission. |
177
168
  | `not_found` | 404 | No such resource in this workspace. |
@@ -192,17 +183,16 @@ error envelope, and never retries them. These codes come from State Machines, in
192
183
 
193
184
  | Code | Status | Meaning |
194
185
  | --- | --- | --- |
195
- | `environment_not_running` | 409 | The environment is `paused`, `failed` or `deleted`. The body's `error.status` names the status. |
186
+ | `environment_not_running` | 409 | The environment is `failed` or `deleted`. The body's `error.status` names the status. |
196
187
  | `environment_starting` | 503 | The environment is still `starting`. The response carries `Retry-After`. |
197
188
  | `environment_unavailable` | 503 | The app is restarting or did not answer. The response carries `Retry-After`. |
198
189
  | `response_too_large` | 502 | The app's response is over the size limit of State Machines. |
199
- | `capacity_exceeded` | 429 | The app has too many requests in flight. |
200
190
  | `unsupported_protocol` | 501 | The app does not serve this API version or protocol. |
201
191
 
202
192
  ## Retries and timeouts
203
193
 
204
194
  The SDK retries `502`, `503`, `504` and connection errors with backoff, until a deadline.
205
- `create` and `snapshots.create` send one `Idempotency-Key` for every retry; pass your own
195
+ `create` and `snapshots.create` send one `Idempotency-Key` for every retry. Pass your own
206
196
  `idempotencyKey` to replay a call whose outcome you don't know. Each HTTP attempt times out after
207
197
  `attemptTimeoutMs`.
208
198
 
@@ -239,11 +229,9 @@ against it.
239
229
  | Default host `https://state-api.usestatemachines.workers.dev` | `https://api.usestatemachines.com` |
240
230
  | `sm.environments.start(input)` | `sm.environments.create(input)`, which waits until `running` |
241
231
  | `env.stop()`, `sm.environments.stop(id)` | `env.delete()`, `sm.environments.delete(id)` |
242
- | `env.suspend()`, `sm.environments.suspend(id)` | `env.pause()`, `sm.environments.pause(id)` |
243
- | `env.resume()` then `env.waitUntilReady()` | `env.resume()`, which waits until `running` |
244
232
  | `waitUntilReady`, `waitUntilStopped`, `waitUntilSuspended` | `sm.environments.wait(id, { status })` |
245
233
  | Apps as `{ key, appId }` | Apps as `{ app, name }`, or the app name as a string |
246
- | `env.connect(app, { apiId, actorId })` returns a `Connection` | `env.connect(app, { actorId })` returns `Credentials`; `fetch` picks the API from the path |
234
+ | `env.connect(app, { apiId, actorId })` returns a `Connection` | `env.connect(app, { actorId })` returns `Credentials`, and `fetch` picks the API from the path |
247
235
  | `connection.baseUrl`, `connection.headers()` | `credentials.url`, `credentials.headers`, `credentials.token` |
248
236
  | `X-State-Environment-Token` | `StateMachines-Environment-Token` |
249
237
  | `env.snapshot(options)`, `sm.snapshots.capture(environmentId, options)` | `env.snapshots.create(options)`, `sm.snapshots.create({ environmentId, ...options })` |
@@ -253,7 +241,7 @@ against it.
253
241
  | The catalog | `sm.apps.list()`, `sm.apps.versions(appId)` |
254
242
  | `TimeoutError` | `WaitTimeoutError` |
255
243
  | `error.retryAfter` | `error.retryAfterMs` |
256
- | Error codes such as `environment-limit` and `capture-in-progress` | Codes such as `environment_limit_reached` and `snapshot_in_progress`; see [Errors](#errors) |
244
+ | Error codes such as `environment-limit` and `capture-in-progress` | Codes such as `environment_limit_reached` and `snapshot_in_progress`. See [Errors](#errors) |
257
245
 
258
246
  ## License
259
247
 
package/agents/SKILL.md CHANGED
@@ -1,14 +1,14 @@
1
1
  ---
2
2
  name: state-machines
3
- description: Use when an agent or test needs a disposable replica of a SaaS app such as Salesforce, to run integration tests or try API calls against a disposable copy instead of a real org. Covers the @usestatemachines/sdk TypeScript SDK - creating environments, credentials, choosing an actor, snapshots, pausing and resuming, cleanup, and errors such as environment_limit_reached and version_unavailable. Also use when the user mentions State Machines, STATEMACHINES_API_KEY or @usestatemachines/sdk.
3
+ description: Use when an agent or test needs a disposable replica of a SaaS app such as Salesforce, to run integration tests or try API calls against a disposable copy instead of a real org. Covers the @usestatemachines/sdk TypeScript SDK - creating environments, credentials, choosing an actor, snapshots, cleanup, and errors such as environment_limit_reached and version_unavailable. Also use when the user mentions State Machines, STATEMACHINES_API_KEY or @usestatemachines/sdk.
4
4
  ---
5
5
 
6
- Read `node_modules/@usestatemachines/sdk/README.md` before relying on memory; it matches the installed version. If the package is missing, run `npm install @usestatemachines/sdk`.
6
+ Read `node_modules/@usestatemachines/sdk/README.md` before relying on memory. It matches the installed version. If the package is missing, run `npm install @usestatemachines/sdk`.
7
7
 
8
8
  ## Workflow
9
9
 
10
- 1. Check that `STATEMACHINES_API_KEY` is set without printing it (`test -n "$STATEMACHINES_API_KEY"`). If it isn't, ask the user to set it; the SDK doesn't read `.env` files.
11
- 2. Create the environment with only the apps the task needs: `const env = await sm.environments.create({ apps: ['salesforce'] })`. Log `await sm.apps.list()` for each app's ID; don't guess IDs. `create` waits until the environment is `running`. If the start fails, the server deletes the environment and `create` throws `EnvironmentFailedError` with `err.environment.failure`.
10
+ 1. Check that `STATEMACHINES_API_KEY` is set without printing it (`test -n "$STATEMACHINES_API_KEY"`). If it isn't, ask the user to set it. The SDK doesn't read `.env` files.
11
+ 2. Create the environment with only the apps the task needs: `const env = await sm.environments.create({ apps: ['salesforce'] })`. Log `await sm.apps.list()` for each app's ID. Don't guess IDs. `create` waits until the environment is `running`. If the start fails, the server deletes the environment and `create` throws `EnvironmentFailedError` with `err.environment.failure`.
12
12
  3. Open a `try` right after `create` returns, and call `await env.delete()` in its `finally`. It deletes the environment and its data.
13
13
  4. Get credentials for one app: `const crm = await env.connect('salesforce')`. This acts as the app's default actor, the Salesforce administrator. To act with another user's permissions, pass an `actorId` from the `actors` of the environment's version: the entry of `(await sm.apps.versions('salesforce')).data` whose `id` is `env.apps[0].versionId`. Salesforce actor IDs are its user IDs, such as `005…`, never a role name.
14
14
  5. Call the app with `crm.fetch(path)`. A leading `/` means the root of the app's `url`.
@@ -33,10 +33,9 @@ try {
33
33
  ## Rules
34
34
 
35
35
  - Always delete in `finally`.
36
- - `env.pause()` releases the compute and keeps the data; it is not cleanup. `env.resume()` waits until `running` again. A paused or failed environment is deleted after one day, and still needs `env.delete()` once you are done.
37
36
  - To create without waiting, pass `{ wait: false }`, then `sm.environments.wait(env.id, { status: 'running' })`.
38
37
  - The time limit counts `starting` and `running`. `(await sm.me()).limits.timeLimitMinutes` gives its `default` and `maximum`. Set `timeLimitMinutes` in `create` for longer runs.
39
- - Running environments are capped per organization: `limits.concurrentEnvironments` from `sm.me()`. Delete the ones you no longer need. A paused or failed environment doesn't count until it resumes.
38
+ - Running environments are capped per organization: `limits.concurrentEnvironments` from `sm.me()`. Delete the ones you no longer need.
40
39
  - Never print, log or commit `STATEMACHINES_API_KEY`, `credentials.token` or `credentials.headers`.
41
40
  - Salesforce's SOAP API takes the actor token in the envelope, not a header: put `crm.token` in `SessionHeader/sessionId`. `crm.fetch` then sends only the environment token header.
42
41
  - `crm.fetch` never sends your API key, never follows redirects and only reaches URLs under the app's `url`.
@@ -55,17 +54,17 @@ The SDK already retries `502`, `503`, `504` and connection errors with one idemp
55
54
  | Symptom | Cause | Fix |
56
55
  | --- | --- | --- |
57
56
  | `version_unavailable`, 422 | The app has no default version. | Check the app ID against `sm.apps.list()`. |
58
- | `environment_limit_reached`, 429 | The organization is at its cap of running environments. | List them with `sm.environments.list({ status: ['starting', 'running'] })`, delete or pause one, then create again. |
59
- | `invalid_status`, 409 | The status does not allow the call, for example pausing an environment that is `starting`. | Read `sm.environments.get(id)` and act on its `status`. |
57
+ | `environment_limit_reached`, 429 | The organization is at its cap of running environments. | List them with `sm.environments.list({ status: ['starting', 'running'] })`, delete one, then create again. |
58
+ | `invalid_status`, 409 | The status does not allow the call, for example taking a snapshot of an environment that is `starting`. | Read `sm.environments.get(id)` and act on its `status`. |
60
59
  | `invalid_request`, 400 | A wrong field. | Read `err.fields`, keyed by dotted path. |
61
- | `EnvironmentFailedError` | The environment failed to start or resume. | Read `err.environment.failure`: `{ app, code, message }`. `invalid_settings` means the app refused its settings. |
62
- | `UnexpectedStatusError` | The environment was paused or deleted while the SDK waited. | Read `err.status` and `err.resource`. |
60
+ | `EnvironmentFailedError` | The environment failed to start. | Read `err.environment.failure`: `{ app, code, message }`. `invalid_settings` means the app refused its settings. |
61
+ | `UnexpectedStatusError` | The environment was deleted while the SDK waited. | Read `err.status` and `err.resource`. |
63
62
  | `WaitTimeoutError` | The status was not reached within `timeoutMs`. | Wait again with `sm.environments.wait(id, { status })`, or delete the environment. |
64
63
  | `snapshot_in_progress`, 409 | The environment already has a snapshot in `saving`. | `const [running] = (await sm.snapshots.list({ environmentId: env.id, status: ['saving'] })).data`, then `await sm.snapshots.wait(running.id, { status: 'ready' })`, then create another. |
65
- | `SnapshotFailedError` | The snapshot failed. `interrupted` means the environment was paused or deleted first. | Create another snapshot while the environment is `running`. |
66
- | `environment_not_running`, 409, from `crm.fetch` | The environment is paused, failed or deleted. `crm.fetch` returns it as a response and never retries. | Read `(await res.json()).error.status`; `env.resume()` a paused or failed environment. |
64
+ | `SnapshotFailedError` | The snapshot failed. `interrupted` means the environment was deleted first. | Create another snapshot while the environment is `running`. |
65
+ | `environment_not_running`, 409, from `crm.fetch` | The environment is failed or deleted. `crm.fetch` returns it as a response and never retries. | Read `(await res.json()).error.status`. Create a new environment if you need another run. |
67
66
  | `environment_starting` or `environment_unavailable`, 503, from `crm.fetch` | The app is starting or restarting. | Wait the `Retry-After` seconds and call again. |
68
67
  | A vendor client such as jsforce is refused by the gateway | The gateway needs `StateMachines-Environment-Token` on every request. | Prefer `crm.fetch`. If you pass `crm.headers` to another client, send them only to `crm.url` and disable redirects. |
69
- | `TypeError: Pass apiKey or set STATEMACHINES_API_KEY.` | No key in the environment. | Ask the user to set `STATEMACHINES_API_KEY`; in Workers, pass `apiKey`. |
68
+ | `TypeError: Pass apiKey or set STATEMACHINES_API_KEY.` | No key in the environment. | Ask the user to set `STATEMACHINES_API_KEY`. In Workers, pass `apiKey`. |
70
69
 
71
70
  More: https://docs.usestatemachines.com, https://app.usestatemachines.com/llms.txt and https://www.npmjs.com/package/@usestatemachines/sdk
package/agents/index.js CHANGED
@@ -1,22 +1,22 @@
1
1
  // Generated by scripts/build-agents.mjs from templates/ and examples/quickstart.ts.
2
2
 
3
3
  export const prompt =
4
- "Set up State Machines so this project can run code against a disposable replica of a SaaS app, such as Salesforce. Follow these steps in order.\n\n1. Install the SDK: `npm install @usestatemachines/sdk`. It is ESM only and runs on Node.js, at the versions in its `engines` field, and on Cloudflare Workers, not in browsers.\n2. Check that `STATEMACHINES_API_KEY` is set without printing it: `test -n \"$STATEMACHINES_API_KEY\"`. If it is missing, stop and ask me to set it; the SDK doesn't read `.env` files. In Cloudflare Workers, pass the key as `new StateMachines({ apiKey })`.\n3. Save this quickstart unchanged as `state-quickstart.mts` and run `npx tsx state-quickstart.mts` to confirm the setup. It prints `200` and the Salesforce org's limits:\n\n```ts\nimport { StateMachines } from '@usestatemachines/sdk';\n\nconst sm = new StateMachines(); // reads STATEMACHINES_API_KEY\nconst env = await sm.environments.create({ apps: ['salesforce'] });\n\ntry {\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\n4. Adapt it to my task. Log `await sm.apps.list()` to find each app's ID, and `await sm.apps.versions(appId)` for the actors and APIs of its versions; don't guess IDs. Create only the apps the task needs; a string such as `'salesforce'` names the app and also becomes its name in the environment. `connect()` acts as the app's default actor, the Salesforce administrator; to act as another user, pass an `actorId` from the version's `actors`. To reuse seeded data, create a snapshot once with `await env.snapshots.create()`, then `sm.environments.create({ snapshotId })`.\n5. Always call `env.delete()` in a `finally`; it deletes the environment and its data. If the start fails, the server deletes the environment itself and `create` throws `EnvironmentFailedError`. Pass your own `idempotencyKey` to `create`; if it throws with no response, call it again with the same input and key.\n6. Never print, log or commit keys or tokens, including `credentials.token` and `credentials.headers`.\n\nRead more: https://docs.usestatemachines.com, `node_modules/@usestatemachines/sdk/README.md`, https://app.usestatemachines.com/llms.txt and https://www.npmjs.com/package/@usestatemachines/sdk\n";
4
+ "Set up State Machines so this project can run code against a disposable replica of a SaaS app, such as Salesforce. Follow these steps in order.\n\n1. Install the SDK: `npm install @usestatemachines/sdk`. It is ESM only and runs on Node.js, at the versions in its `engines` field, and on Cloudflare Workers, not in browsers.\n2. Check that `STATEMACHINES_API_KEY` is set without printing it: `test -n \"$STATEMACHINES_API_KEY\"`. If it is missing, stop and ask me to set it. The SDK doesn't read `.env` files. In Cloudflare Workers, pass the key as `new StateMachines({ apiKey })`.\n3. Save this quickstart unchanged as `state-quickstart.mts` and run `npx tsx state-quickstart.mts` to confirm the setup. It prints `200` and the Salesforce org's limits:\n\n```ts\nimport { StateMachines } from '@usestatemachines/sdk';\n\nconst sm = new StateMachines(); // reads STATEMACHINES_API_KEY\nconst env = await sm.environments.create({ apps: ['salesforce'] });\n\ntry {\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\n4. Adapt it to my task. Log `await sm.apps.list()` to find each app's ID, and `await sm.apps.versions(appId)` for the actors and APIs of its versions. Don't guess IDs. Create only the apps the task needs. A string such as `'salesforce'` names the app and also becomes its name in the environment. `connect()` acts as the app's default actor, the Salesforce administrator. To act as another user, pass an `actorId` from the version's `actors`. To reuse seeded data, create a snapshot once with `await env.snapshots.create()`, then `sm.environments.create({ snapshotId })`.\n5. Always call `env.delete()` in a `finally`. It deletes the environment and its data. If the start fails, the server deletes the environment itself and `create` throws `EnvironmentFailedError`. Pass your own `idempotencyKey` to `create`. If it throws with no response, call it again with the same input and key.\n6. Never print, log or commit keys or tokens, including `credentials.token` and `credentials.headers`.\n\nRead more: https://docs.usestatemachines.com, `node_modules/@usestatemachines/sdk/README.md`, https://app.usestatemachines.com/llms.txt and https://www.npmjs.com/package/@usestatemachines/sdk\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, pause, resume and delete them with the TypeScript SDK `@usestatemachines/sdk`.\n\nInstall with `npm install @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.\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`. Pass `{ wait: false }` to get it back at once in `starting`. Always call `env.delete()` in a `finally`.\n- Apps: a string names the app, and the app's name in the environment is the same string. Use `{ app: 'salesforce', name: 'crm', settings }` for your own name, for two copies of one app, or for settings. Later calls take the name.\n- Pause and resume: `env.pause()` releases the compute and keeps the data; the time limit stops counting. `env.resume()` boots the apps again on the same data and waits until `running`. A paused environment is deleted after one day.\n- Credentials: `const crm = await env.connect('salesforce')` returns `url`, `headers`, `token` and `fetch`. `crm.fetch(path)` joins `path` to `url`, adds `headers`, and puts `token` in the header of the API whose path matches: `Authorization: Bearer` for Salesforce REST and Bulk 2.0. For Salesforce SOAP, put `crm.token` in the envelope's `SessionHeader/sessionId` yourself. `fetch` never sends your API key, never follows redirects and never retries. `token` and `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…`. Read them from the `actors` of the environment's version: the entry of `(await sm.apps.versions('salesforce')).data` whose `id` is `env.apps[0].versionId`.\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 pause or 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`, `paused`, `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. A `failed` environment keeps its data for `resume()` and has `failure`: `{ app, code, message }` with `start_failed`, `resume_failed`, `restart_failed`, `version_unavailable` or `invalid_settings`.\n- Snapshot `status`: `saving`, `ready`, `failed`, `deleted`. A failed snapshot has `failure.code` `interrupted` or `internal_error`.\n- `create` and `resume` throw `EnvironmentFailedError` when the environment fails, `UnexpectedStatusError` when it becomes `paused` or `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\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: pause needs `running`; resume needs `paused` or `failed`. |\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 | Pause or delete an environment, then create or resume again. |\n| `unavailable` | 503 | Retried automatically. If `create` still fails, call it again with the same input and your own `idempotencyKey`. |\n\n## Errors from the app URL\n\n`crm.fetch` returns these as responses with the same JSON envelope, and never retries them.\n\n| Code | Status | What to do |\n| --- | --- | --- |\n| `environment_not_running` | 409 | The environment is paused, failed or deleted; `error.status` names it. Resume a paused or failed environment. |\n| `environment_starting` | 503 | Wait `Retry-After` and call again. |\n| `environment_unavailable` | 503 | The app is restarting. Wait `Retry-After` and call again. |\n| `response_too_large` | 502 | The response is over the size limit. Ask for less, for example with a smaller page. |\n| `capacity_exceeded` | 429 | Too many requests in flight to the app. Send fewer at once. |\n| `unsupported_protocol` | 501 | The app does not serve this API version or protocol. |\n\n## Quickstart\n\nSave it unchanged as `state-quickstart.mts` and 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({ apps: ['salesforce'] });\n\ntry {\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://app.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\nInstall with `npm install @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.\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`. Pass `{ wait: false }` to get it back at once in `starting`. Always call `env.delete()` in a `finally`.\n- Apps: a string names the app, and the app's name in the environment is the same string. Use `{ app: 'salesforce', name: 'crm', settings }` for your own name, for two copies of one app, or for settings. Later calls take the name.\n- Credentials: `const crm = await env.connect('salesforce')` returns `url`, `headers`, `token` and `fetch`. `crm.fetch(path)` joins `path` to `url`, adds `headers`, and puts `token` in the header of the API whose path matches: `Authorization: Bearer` for Salesforce REST and Bulk 2.0. For Salesforce SOAP, put `crm.token` in the envelope's `SessionHeader/sessionId` yourself. `fetch` never sends your API key, never follows redirects and never retries. `token` and `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…`. Read them from the `actors` of the environment's version: the entry of `(await sm.apps.versions('salesforce')).data` whose `id` is `env.apps[0].versionId`.\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. A `failed` environment has `failure`: `{ app, code, message }` with `start_failed`, `restart_failed`, `version_unavailable` or `invalid_settings`.\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\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 an environment, then create again. |\n| `unavailable` | 503 | Retried automatically. If `create` still fails, call it again with the same input and your own `idempotencyKey`. |\n\n## Errors from the app URL\n\n`crm.fetch` returns these as responses with the same JSON envelope, and never retries them.\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 | Wait `Retry-After` and call again. |\n| `environment_unavailable` | 503 | The app is restarting. Wait `Retry-After` and call again. |\n| `response_too_large` | 502 | The response is over the size limit. Ask for less, for example with a smaller page. |\n| `unsupported_protocol` | 501 | The app does not serve this API version or protocol. |\n\n## Quickstart\n\nSave it unchanged as `state-quickstart.mts` and 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({ apps: ['salesforce'] });\n\ntry {\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://app.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 an agent or test needs a disposable replica of a SaaS app such as Salesforce, to run integration tests or try API calls against a disposable copy instead of a real org. Covers the @usestatemachines/sdk TypeScript SDK - creating environments, credentials, choosing an actor, snapshots, pausing and resuming, cleanup, and errors such as environment_limit_reached and version_unavailable. Also use when the user mentions State Machines, STATEMACHINES_API_KEY or @usestatemachines/sdk.\n---\n\nRead `node_modules/@usestatemachines/sdk/README.md` before relying on memory; it matches the installed version. If the package is missing, run `npm install @usestatemachines/sdk`.\n\n## Workflow\n\n1. Check that `STATEMACHINES_API_KEY` is set without printing it (`test -n \"$STATEMACHINES_API_KEY\"`). If it isn't, ask the user to set it; the SDK doesn't read `.env` files.\n2. Create the environment with only the apps the task needs: `const env = await sm.environments.create({ apps: ['salesforce'] })`. Log `await sm.apps.list()` for each app's ID; don't guess IDs. `create` waits until the environment is `running`. If the start fails, the server deletes the environment and `create` throws `EnvironmentFailedError` with `err.environment.failure`.\n3. Open a `try` right after `create` returns, and call `await env.delete()` in its `finally`. It deletes the environment and its data.\n4. Get credentials for one app: `const crm = await env.connect('salesforce')`. This acts as the app's default actor, the Salesforce administrator. To act with another user's permissions, pass an `actorId` from the `actors` of the environment's version: the entry of `(await sm.apps.versions('salesforce')).data` whose `id` is `env.apps[0].versionId`. Salesforce actor IDs are its user IDs, such as `005…`, never a role name.\n5. Call the app with `crm.fetch(path)`. A leading `/` means the root of the app's `url`.\n\nThe whole flow, as the package ships it. It is ESM with top-level `await`: save it as `state-quickstart.mts` and 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({ apps: ['salesforce'] });\n\ntry {\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## Rules\n\n- Always delete in `finally`.\n- `env.pause()` releases the compute and keeps the data; it is not cleanup. `env.resume()` waits until `running` again. A paused or failed environment is deleted after one day, and still needs `env.delete()` once you are done.\n- To create without waiting, pass `{ wait: false }`, then `sm.environments.wait(env.id, { status: 'running' })`.\n- The time limit counts `starting` and `running`. `(await sm.me()).limits.timeLimitMinutes` gives its `default` and `maximum`. Set `timeLimitMinutes` in `create` for longer runs.\n- Running environments are capped per organization: `limits.concurrentEnvironments` from `sm.me()`. Delete the ones you no longer need. A paused or failed environment doesn't count until it resumes.\n- Never print, log or commit `STATEMACHINES_API_KEY`, `credentials.token` or `credentials.headers`.\n- Salesforce's SOAP API takes the actor token in the envelope, not a header: put `crm.token` in `SessionHeader/sessionId`. `crm.fetch` then sends only the environment token header.\n- `crm.fetch` never sends your API key, never follows redirects and only reaches URLs under the app's `url`.\n- Runtimes: Node.js, at the versions in the package's `engines` field, and Cloudflare Workers. ESM only. TypeScript needs `lib: [\"DOM\"]` or `@types/node`. Never ship an API key to a browser.\n- In Cloudflare Workers, pass the key explicitly: `new StateMachines({ apiKey: env.STATEMACHINES_API_KEY })`. A start takes longer than one request may last, so create with `{ wait: false }` and read `sm.environments.get(id)` in later requests until it is `running`, or pass a `signal` no shorter than the expected start.\n- Every `list` returns one page, `{ data, nextCursor }`. Pass `nextCursor` back as `cursor` until it is `null`.\n- To reuse seeded data, `await env.snapshots.create({ name })` once, then `sm.environments.create({ snapshotId })` in place of `apps`.\n- Check errors with `instanceof` (`StateMachinesError`, `EnvironmentFailedError`, `SnapshotFailedError`, `UnexpectedStatusError`, `WaitTimeoutError`), not by `name`.\n\n## Recovering a failed create\n\nThe SDK already retries `502`, `503`, `504` and connection errors with one idempotency key. If you need to replay a create whose outcome is unknown, pass your own key: `sm.environments.create(input, { idempotencyKey })`, and call it again with the same input and key. It returns the environment the first call created, or creates it if that call never arrived. A different input fails with `idempotency_key_reused`.\n\n## Troubleshooting\n\n| Symptom | Cause | Fix |\n| --- | --- | --- |\n| `version_unavailable`, 422 | The app has no default version. | Check the app ID against `sm.apps.list()`. |\n| `environment_limit_reached`, 429 | The organization is at its cap of running environments. | List them with `sm.environments.list({ status: ['starting', 'running'] })`, delete or pause one, then create again. |\n| `invalid_status`, 409 | The status does not allow the call, for example pausing an environment that is `starting`. | Read `sm.environments.get(id)` and act on its `status`. |\n| `invalid_request`, 400 | A wrong field. | Read `err.fields`, keyed by dotted path. |\n| `EnvironmentFailedError` | The environment failed to start or resume. | Read `err.environment.failure`: `{ app, code, message }`. `invalid_settings` means the app refused its settings. |\n| `UnexpectedStatusError` | The environment was paused or deleted while the SDK waited. | Read `err.status` and `err.resource`. |\n| `WaitTimeoutError` | The status was not reached within `timeoutMs`. | Wait again with `sm.environments.wait(id, { status })`, or delete the environment. |\n| `snapshot_in_progress`, 409 | The environment already has a snapshot in `saving`. | `const [running] = (await sm.snapshots.list({ environmentId: env.id, status: ['saving'] })).data`, then `await sm.snapshots.wait(running.id, { status: 'ready' })`, then create another. |\n| `SnapshotFailedError` | The snapshot failed. `interrupted` means the environment was paused or deleted first. | Create another snapshot while the environment is `running`. |\n| `environment_not_running`, 409, from `crm.fetch` | The environment is paused, failed or deleted. `crm.fetch` returns it as a response and never retries. | Read `(await res.json()).error.status`; `env.resume()` a paused or failed environment. |\n| `environment_starting` or `environment_unavailable`, 503, from `crm.fetch` | The app is starting or restarting. | Wait the `Retry-After` seconds and call again. |\n| A vendor client such as jsforce is refused by the gateway | The gateway needs `StateMachines-Environment-Token` on every request. | Prefer `crm.fetch`. If you pass `crm.headers` to another client, send them only to `crm.url` and disable redirects. |\n| `TypeError: Pass apiKey or set STATEMACHINES_API_KEY.` | No key in the environment. | Ask the user to set `STATEMACHINES_API_KEY`; in Workers, pass `apiKey`. |\n\nMore: https://docs.usestatemachines.com, https://app.usestatemachines.com/llms.txt and https://www.npmjs.com/package/@usestatemachines/sdk\n";
10
+ "---\nname: state-machines\ndescription: Use when an agent or test needs a disposable replica of a SaaS app such as Salesforce, to run integration tests or try API calls against a disposable copy instead of a real org. Covers the @usestatemachines/sdk TypeScript SDK - creating environments, credentials, choosing an actor, snapshots, cleanup, and errors such as environment_limit_reached and version_unavailable. Also use when the user mentions State Machines, STATEMACHINES_API_KEY or @usestatemachines/sdk.\n---\n\nRead `node_modules/@usestatemachines/sdk/README.md` before relying on memory. It matches the installed version. If the package is missing, run `npm install @usestatemachines/sdk`.\n\n## Workflow\n\n1. Check that `STATEMACHINES_API_KEY` is set without printing it (`test -n \"$STATEMACHINES_API_KEY\"`). If it isn't, ask the user to set it. The SDK doesn't read `.env` files.\n2. Create the environment with only the apps the task needs: `const env = await sm.environments.create({ apps: ['salesforce'] })`. Log `await sm.apps.list()` for each app's ID. Don't guess IDs. `create` waits until the environment is `running`. If the start fails, the server deletes the environment and `create` throws `EnvironmentFailedError` with `err.environment.failure`.\n3. Open a `try` right after `create` returns, and call `await env.delete()` in its `finally`. It deletes the environment and its data.\n4. Get credentials for one app: `const crm = await env.connect('salesforce')`. This acts as the app's default actor, the Salesforce administrator. To act with another user's permissions, pass an `actorId` from the `actors` of the environment's version: the entry of `(await sm.apps.versions('salesforce')).data` whose `id` is `env.apps[0].versionId`. Salesforce actor IDs are its user IDs, such as `005…`, never a role name.\n5. Call the app with `crm.fetch(path)`. A leading `/` means the root of the app's `url`.\n\nThe whole flow, as the package ships it. It is ESM with top-level `await`: save it as `state-quickstart.mts` and 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({ apps: ['salesforce'] });\n\ntry {\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## Rules\n\n- Always delete in `finally`.\n- To create without waiting, pass `{ wait: false }`, then `sm.environments.wait(env.id, { status: 'running' })`.\n- The time limit counts `starting` and `running`. `(await sm.me()).limits.timeLimitMinutes` gives its `default` and `maximum`. Set `timeLimitMinutes` in `create` for longer runs.\n- Running environments are capped per organization: `limits.concurrentEnvironments` from `sm.me()`. Delete the ones you no longer need.\n- Never print, log or commit `STATEMACHINES_API_KEY`, `credentials.token` or `credentials.headers`.\n- Salesforce's SOAP API takes the actor token in the envelope, not a header: put `crm.token` in `SessionHeader/sessionId`. `crm.fetch` then sends only the environment token header.\n- `crm.fetch` never sends your API key, never follows redirects and only reaches URLs under the app's `url`.\n- Runtimes: Node.js, at the versions in the package's `engines` field, and Cloudflare Workers. ESM only. TypeScript needs `lib: [\"DOM\"]` or `@types/node`. Never ship an API key to a browser.\n- In Cloudflare Workers, pass the key explicitly: `new StateMachines({ apiKey: env.STATEMACHINES_API_KEY })`. A start takes longer than one request may last, so create with `{ wait: false }` and read `sm.environments.get(id)` in later requests until it is `running`, or pass a `signal` no shorter than the expected start.\n- Every `list` returns one page, `{ data, nextCursor }`. Pass `nextCursor` back as `cursor` until it is `null`.\n- To reuse seeded data, `await env.snapshots.create({ name })` once, then `sm.environments.create({ snapshotId })` in place of `apps`.\n- Check errors with `instanceof` (`StateMachinesError`, `EnvironmentFailedError`, `SnapshotFailedError`, `UnexpectedStatusError`, `WaitTimeoutError`), not by `name`.\n\n## Recovering a failed create\n\nThe SDK already retries `502`, `503`, `504` and connection errors with one idempotency key. If you need to replay a create whose outcome is unknown, pass your own key: `sm.environments.create(input, { idempotencyKey })`, and call it again with the same input and key. It returns the environment the first call created, or creates it if that call never arrived. A different input fails with `idempotency_key_reused`.\n\n## Troubleshooting\n\n| Symptom | Cause | Fix |\n| --- | --- | --- |\n| `version_unavailable`, 422 | The app has no default version. | Check the app ID against `sm.apps.list()`. |\n| `environment_limit_reached`, 429 | The organization is at its cap of running environments. | List them with `sm.environments.list({ status: ['starting', 'running'] })`, delete one, then create again. |\n| `invalid_status`, 409 | The status does not allow the call, for example taking a snapshot of an environment that is `starting`. | Read `sm.environments.get(id)` and act on its `status`. |\n| `invalid_request`, 400 | A wrong field. | Read `err.fields`, keyed by dotted path. |\n| `EnvironmentFailedError` | The environment failed to start. | Read `err.environment.failure`: `{ app, code, message }`. `invalid_settings` means the app refused its settings. |\n| `UnexpectedStatusError` | The environment was deleted while the SDK waited. | Read `err.status` and `err.resource`. |\n| `WaitTimeoutError` | The status was not reached within `timeoutMs`. | Wait again with `sm.environments.wait(id, { status })`, or delete the environment. |\n| `snapshot_in_progress`, 409 | The environment already has a snapshot in `saving`. | `const [running] = (await sm.snapshots.list({ environmentId: env.id, status: ['saving'] })).data`, then `await sm.snapshots.wait(running.id, { status: 'ready' })`, then create another. |\n| `SnapshotFailedError` | The snapshot failed. `interrupted` means the environment was deleted first. | Create another snapshot while the environment is `running`. |\n| `environment_not_running`, 409, from `crm.fetch` | The environment is failed or deleted. `crm.fetch` returns it as a response and never retries. | Read `(await res.json()).error.status`. Create a new environment if you need another run. |\n| `environment_starting` or `environment_unavailable`, 503, from `crm.fetch` | The app is starting or restarting. | Wait the `Retry-After` seconds and call again. |\n| A vendor client such as jsforce is refused by the gateway | The gateway needs `StateMachines-Environment-Token` on every request. | Prefer `crm.fetch`. If you pass `crm.headers` to another client, send them only to `crm.url` and disable redirects. |\n| `TypeError: Pass apiKey or set STATEMACHINES_API_KEY.` | No key in the environment. | Ask the user to set `STATEMACHINES_API_KEY`. In Workers, pass `apiKey`. |\n\nMore: https://docs.usestatemachines.com, https://app.usestatemachines.com/llms.txt and https://www.npmjs.com/package/@usestatemachines/sdk\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({ apps: ['salesforce'] });\n\ntry {\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
15
  export const templates = Object.freeze({
16
16
  prompt:
17
- "Set up State Machines so this project can run code against a disposable replica of a SaaS app, such as Salesforce. Follow these steps in order.\n\n1. Install the SDK: `npm install @usestatemachines/sdk`. It is ESM only and runs on Node.js, at the versions in its `engines` field, and on Cloudflare Workers, not in browsers.\n2. Check that `STATEMACHINES_API_KEY` is set without printing it: `test -n \"$STATEMACHINES_API_KEY\"`. If it is missing, stop and ask me to set it; the SDK doesn't read `.env` files. In Cloudflare Workers, pass the key as `new StateMachines({ apiKey })`.\n3. Save this quickstart unchanged as `state-quickstart.mts` and run `npx tsx state-quickstart.mts` to confirm the setup. It prints `200` and the Salesforce org's limits:\n\n```ts\nimport { StateMachines } from '@usestatemachines/sdk';\n\nconst sm = new StateMachines(); // reads STATEMACHINES_API_KEY\nconst env = await sm.environments.create({ apps: ['salesforce'] });\n\ntry {\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\n4. Adapt it to my task. Log `await sm.apps.list()` to find each app's ID, and `await sm.apps.versions(appId)` for the actors and APIs of its versions; don't guess IDs. Create only the apps the task needs; a string such as `'salesforce'` names the app and also becomes its name in the environment. `connect()` acts as the app's default actor, the Salesforce administrator; to act as another user, pass an `actorId` from the version's `actors`. To reuse seeded data, create a snapshot once with `await env.snapshots.create()`, then `sm.environments.create({ snapshotId })`.\n5. Always call `env.delete()` in a `finally`; it deletes the environment and its data. If the start fails, the server deletes the environment itself and `create` throws `EnvironmentFailedError`. Pass your own `idempotencyKey` to `create`; if it throws with no response, call it again with the same input and key.\n6. Never print, log or commit keys or tokens, including `credentials.token` and `credentials.headers`.\n\nRead more: https://docs.usestatemachines.com, `node_modules/@usestatemachines/sdk/README.md`, {{origin}}/llms.txt and https://www.npmjs.com/package/@usestatemachines/sdk\n",
17
+ "Set up State Machines so this project can run code against a disposable replica of a SaaS app, such as Salesforce. Follow these steps in order.\n\n1. Install the SDK: `npm install @usestatemachines/sdk`. It is ESM only and runs on Node.js, at the versions in its `engines` field, and on Cloudflare Workers, not in browsers.\n2. Check that `STATEMACHINES_API_KEY` is set without printing it: `test -n \"$STATEMACHINES_API_KEY\"`. If it is missing, stop and ask me to set it. The SDK doesn't read `.env` files. In Cloudflare Workers, pass the key as `new StateMachines({ apiKey })`.\n3. Save this quickstart unchanged as `state-quickstart.mts` and run `npx tsx state-quickstart.mts` to confirm the setup. It prints `200` and the Salesforce org's limits:\n\n```ts\nimport { StateMachines } from '@usestatemachines/sdk';\n\nconst sm = new StateMachines(); // reads STATEMACHINES_API_KEY\nconst env = await sm.environments.create({ apps: ['salesforce'] });\n\ntry {\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\n4. Adapt it to my task. Log `await sm.apps.list()` to find each app's ID, and `await sm.apps.versions(appId)` for the actors and APIs of its versions. Don't guess IDs. Create only the apps the task needs. A string such as `'salesforce'` names the app and also becomes its name in the environment. `connect()` acts as the app's default actor, the Salesforce administrator. To act as another user, pass an `actorId` from the version's `actors`. To reuse seeded data, create a snapshot once with `await env.snapshots.create()`, then `sm.environments.create({ snapshotId })`.\n5. Always call `env.delete()` in a `finally`. It deletes the environment and its data. If the start fails, the server deletes the environment itself and `create` throws `EnvironmentFailedError`. Pass your own `idempotencyKey` to `create`. If it throws with no response, call it again with the same input and key.\n6. Never print, log or commit keys or tokens, including `credentials.token` and `credentials.headers`.\n\nRead more: https://docs.usestatemachines.com, `node_modules/@usestatemachines/sdk/README.md`, {{origin}}/llms.txt and https://www.npmjs.com/package/@usestatemachines/sdk\n",
18
18
  llmsTxt:
19
- "# State Machines\n\n> State Machines runs disposable environments of replica SaaS apps, such as Salesforce, for tests and coding agents. Create, connect to, pause, resume and delete them with the TypeScript SDK `@usestatemachines/sdk`.\n\nInstall with `npm install @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.\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`. Pass `{ wait: false }` to get it back at once in `starting`. Always call `env.delete()` in a `finally`.\n- Apps: a string names the app, and the app's name in the environment is the same string. Use `{ app: 'salesforce', name: 'crm', settings }` for your own name, for two copies of one app, or for settings. Later calls take the name.\n- Pause and resume: `env.pause()` releases the compute and keeps the data; the time limit stops counting. `env.resume()` boots the apps again on the same data and waits until `running`. A paused environment is deleted after one day.\n- Credentials: `const crm = await env.connect('salesforce')` returns `url`, `headers`, `token` and `fetch`. `crm.fetch(path)` joins `path` to `url`, adds `headers`, and puts `token` in the header of the API whose path matches: `Authorization: Bearer` for Salesforce REST and Bulk 2.0. For Salesforce SOAP, put `crm.token` in the envelope's `SessionHeader/sessionId` yourself. `fetch` never sends your API key, never follows redirects and never retries. `token` and `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…`. Read them from the `actors` of the environment's version: the entry of `(await sm.apps.versions('salesforce')).data` whose `id` is `env.apps[0].versionId`.\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 pause or 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`, `paused`, `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. A `failed` environment keeps its data for `resume()` and has `failure`: `{ app, code, message }` with `start_failed`, `resume_failed`, `restart_failed`, `version_unavailable` or `invalid_settings`.\n- Snapshot `status`: `saving`, `ready`, `failed`, `deleted`. A failed snapshot has `failure.code` `interrupted` or `internal_error`.\n- `create` and `resume` throw `EnvironmentFailedError` when the environment fails, `UnexpectedStatusError` when it becomes `paused` or `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\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: pause needs `running`; resume needs `paused` or `failed`. |\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 | Pause or delete an environment, then create or resume again. |\n| `unavailable` | 503 | Retried automatically. If `create` still fails, call it again with the same input and your own `idempotencyKey`. |\n\n## Errors from the app URL\n\n`crm.fetch` returns these as responses with the same JSON envelope, and never retries them.\n\n| Code | Status | What to do |\n| --- | --- | --- |\n| `environment_not_running` | 409 | The environment is paused, failed or deleted; `error.status` names it. Resume a paused or failed environment. |\n| `environment_starting` | 503 | Wait `Retry-After` and call again. |\n| `environment_unavailable` | 503 | The app is restarting. Wait `Retry-After` and call again. |\n| `response_too_large` | 502 | The response is over the size limit. Ask for less, for example with a smaller page. |\n| `capacity_exceeded` | 429 | Too many requests in flight to the app. Send fewer at once. |\n| `unsupported_protocol` | 501 | The app does not serve this API version or protocol. |\n\n## Quickstart\n\nSave it unchanged as `state-quickstart.mts` and 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({ apps: ['salesforce'] });\n\ntry {\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]({{origin}}/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",
19
+ "# 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\nInstall with `npm install @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.\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`. Pass `{ wait: false }` to get it back at once in `starting`. Always call `env.delete()` in a `finally`.\n- Apps: a string names the app, and the app's name in the environment is the same string. Use `{ app: 'salesforce', name: 'crm', settings }` for your own name, for two copies of one app, or for settings. Later calls take the name.\n- Credentials: `const crm = await env.connect('salesforce')` returns `url`, `headers`, `token` and `fetch`. `crm.fetch(path)` joins `path` to `url`, adds `headers`, and puts `token` in the header of the API whose path matches: `Authorization: Bearer` for Salesforce REST and Bulk 2.0. For Salesforce SOAP, put `crm.token` in the envelope's `SessionHeader/sessionId` yourself. `fetch` never sends your API key, never follows redirects and never retries. `token` and `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…`. Read them from the `actors` of the environment's version: the entry of `(await sm.apps.versions('salesforce')).data` whose `id` is `env.apps[0].versionId`.\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. A `failed` environment has `failure`: `{ app, code, message }` with `start_failed`, `restart_failed`, `version_unavailable` or `invalid_settings`.\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\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 an environment, then create again. |\n| `unavailable` | 503 | Retried automatically. If `create` still fails, call it again with the same input and your own `idempotencyKey`. |\n\n## Errors from the app URL\n\n`crm.fetch` returns these as responses with the same JSON envelope, and never retries them.\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 | Wait `Retry-After` and call again. |\n| `environment_unavailable` | 503 | The app is restarting. Wait `Retry-After` and call again. |\n| `response_too_large` | 502 | The response is over the size limit. Ask for less, for example with a smaller page. |\n| `unsupported_protocol` | 501 | The app does not serve this API version or protocol. |\n\n## Quickstart\n\nSave it unchanged as `state-quickstart.mts` and 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({ apps: ['salesforce'] });\n\ntry {\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]({{origin}}/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",
20
20
  skill:
21
- "---\nname: state-machines\ndescription: Use when an agent or test needs a disposable replica of a SaaS app such as Salesforce, to run integration tests or try API calls against a disposable copy instead of a real org. Covers the @usestatemachines/sdk TypeScript SDK - creating environments, credentials, choosing an actor, snapshots, pausing and resuming, cleanup, and errors such as environment_limit_reached and version_unavailable. Also use when the user mentions State Machines, STATEMACHINES_API_KEY or @usestatemachines/sdk.\n---\n\nRead `node_modules/@usestatemachines/sdk/README.md` before relying on memory; it matches the installed version. If the package is missing, run `npm install @usestatemachines/sdk`.\n\n## Workflow\n\n1. Check that `STATEMACHINES_API_KEY` is set without printing it (`test -n \"$STATEMACHINES_API_KEY\"`). If it isn't, ask the user to set it; the SDK doesn't read `.env` files.\n2. Create the environment with only the apps the task needs: `const env = await sm.environments.create({ apps: ['salesforce'] })`. Log `await sm.apps.list()` for each app's ID; don't guess IDs. `create` waits until the environment is `running`. If the start fails, the server deletes the environment and `create` throws `EnvironmentFailedError` with `err.environment.failure`.\n3. Open a `try` right after `create` returns, and call `await env.delete()` in its `finally`. It deletes the environment and its data.\n4. Get credentials for one app: `const crm = await env.connect('salesforce')`. This acts as the app's default actor, the Salesforce administrator. To act with another user's permissions, pass an `actorId` from the `actors` of the environment's version: the entry of `(await sm.apps.versions('salesforce')).data` whose `id` is `env.apps[0].versionId`. Salesforce actor IDs are its user IDs, such as `005…`, never a role name.\n5. Call the app with `crm.fetch(path)`. A leading `/` means the root of the app's `url`.\n\nThe whole flow, as the package ships it. It is ESM with top-level `await`: save it as `state-quickstart.mts` and 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({ apps: ['salesforce'] });\n\ntry {\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## Rules\n\n- Always delete in `finally`.\n- `env.pause()` releases the compute and keeps the data; it is not cleanup. `env.resume()` waits until `running` again. A paused or failed environment is deleted after one day, and still needs `env.delete()` once you are done.\n- To create without waiting, pass `{ wait: false }`, then `sm.environments.wait(env.id, { status: 'running' })`.\n- The time limit counts `starting` and `running`. `(await sm.me()).limits.timeLimitMinutes` gives its `default` and `maximum`. Set `timeLimitMinutes` in `create` for longer runs.\n- Running environments are capped per organization: `limits.concurrentEnvironments` from `sm.me()`. Delete the ones you no longer need. A paused or failed environment doesn't count until it resumes.\n- Never print, log or commit `STATEMACHINES_API_KEY`, `credentials.token` or `credentials.headers`.\n- Salesforce's SOAP API takes the actor token in the envelope, not a header: put `crm.token` in `SessionHeader/sessionId`. `crm.fetch` then sends only the environment token header.\n- `crm.fetch` never sends your API key, never follows redirects and only reaches URLs under the app's `url`.\n- Runtimes: Node.js, at the versions in the package's `engines` field, and Cloudflare Workers. ESM only. TypeScript needs `lib: [\"DOM\"]` or `@types/node`. Never ship an API key to a browser.\n- In Cloudflare Workers, pass the key explicitly: `new StateMachines({ apiKey: env.STATEMACHINES_API_KEY })`. A start takes longer than one request may last, so create with `{ wait: false }` and read `sm.environments.get(id)` in later requests until it is `running`, or pass a `signal` no shorter than the expected start.\n- Every `list` returns one page, `{ data, nextCursor }`. Pass `nextCursor` back as `cursor` until it is `null`.\n- To reuse seeded data, `await env.snapshots.create({ name })` once, then `sm.environments.create({ snapshotId })` in place of `apps`.\n- Check errors with `instanceof` (`StateMachinesError`, `EnvironmentFailedError`, `SnapshotFailedError`, `UnexpectedStatusError`, `WaitTimeoutError`), not by `name`.\n\n## Recovering a failed create\n\nThe SDK already retries `502`, `503`, `504` and connection errors with one idempotency key. If you need to replay a create whose outcome is unknown, pass your own key: `sm.environments.create(input, { idempotencyKey })`, and call it again with the same input and key. It returns the environment the first call created, or creates it if that call never arrived. A different input fails with `idempotency_key_reused`.\n\n## Troubleshooting\n\n| Symptom | Cause | Fix |\n| --- | --- | --- |\n| `version_unavailable`, 422 | The app has no default version. | Check the app ID against `sm.apps.list()`. |\n| `environment_limit_reached`, 429 | The organization is at its cap of running environments. | List them with `sm.environments.list({ status: ['starting', 'running'] })`, delete or pause one, then create again. |\n| `invalid_status`, 409 | The status does not allow the call, for example pausing an environment that is `starting`. | Read `sm.environments.get(id)` and act on its `status`. |\n| `invalid_request`, 400 | A wrong field. | Read `err.fields`, keyed by dotted path. |\n| `EnvironmentFailedError` | The environment failed to start or resume. | Read `err.environment.failure`: `{ app, code, message }`. `invalid_settings` means the app refused its settings. |\n| `UnexpectedStatusError` | The environment was paused or deleted while the SDK waited. | Read `err.status` and `err.resource`. |\n| `WaitTimeoutError` | The status was not reached within `timeoutMs`. | Wait again with `sm.environments.wait(id, { status })`, or delete the environment. |\n| `snapshot_in_progress`, 409 | The environment already has a snapshot in `saving`. | `const [running] = (await sm.snapshots.list({ environmentId: env.id, status: ['saving'] })).data`, then `await sm.snapshots.wait(running.id, { status: 'ready' })`, then create another. |\n| `SnapshotFailedError` | The snapshot failed. `interrupted` means the environment was paused or deleted first. | Create another snapshot while the environment is `running`. |\n| `environment_not_running`, 409, from `crm.fetch` | The environment is paused, failed or deleted. `crm.fetch` returns it as a response and never retries. | Read `(await res.json()).error.status`; `env.resume()` a paused or failed environment. |\n| `environment_starting` or `environment_unavailable`, 503, from `crm.fetch` | The app is starting or restarting. | Wait the `Retry-After` seconds and call again. |\n| A vendor client such as jsforce is refused by the gateway | The gateway needs `StateMachines-Environment-Token` on every request. | Prefer `crm.fetch`. If you pass `crm.headers` to another client, send them only to `crm.url` and disable redirects. |\n| `TypeError: Pass apiKey or set STATEMACHINES_API_KEY.` | No key in the environment. | Ask the user to set `STATEMACHINES_API_KEY`; in Workers, pass `apiKey`. |\n\nMore: https://docs.usestatemachines.com, {{origin}}/llms.txt and https://www.npmjs.com/package/@usestatemachines/sdk\n",
21
+ "---\nname: state-machines\ndescription: Use when an agent or test needs a disposable replica of a SaaS app such as Salesforce, to run integration tests or try API calls against a disposable copy instead of a real org. Covers the @usestatemachines/sdk TypeScript SDK - creating environments, credentials, choosing an actor, snapshots, cleanup, and errors such as environment_limit_reached and version_unavailable. Also use when the user mentions State Machines, STATEMACHINES_API_KEY or @usestatemachines/sdk.\n---\n\nRead `node_modules/@usestatemachines/sdk/README.md` before relying on memory. It matches the installed version. If the package is missing, run `npm install @usestatemachines/sdk`.\n\n## Workflow\n\n1. Check that `STATEMACHINES_API_KEY` is set without printing it (`test -n \"$STATEMACHINES_API_KEY\"`). If it isn't, ask the user to set it. The SDK doesn't read `.env` files.\n2. Create the environment with only the apps the task needs: `const env = await sm.environments.create({ apps: ['salesforce'] })`. Log `await sm.apps.list()` for each app's ID. Don't guess IDs. `create` waits until the environment is `running`. If the start fails, the server deletes the environment and `create` throws `EnvironmentFailedError` with `err.environment.failure`.\n3. Open a `try` right after `create` returns, and call `await env.delete()` in its `finally`. It deletes the environment and its data.\n4. Get credentials for one app: `const crm = await env.connect('salesforce')`. This acts as the app's default actor, the Salesforce administrator. To act with another user's permissions, pass an `actorId` from the `actors` of the environment's version: the entry of `(await sm.apps.versions('salesforce')).data` whose `id` is `env.apps[0].versionId`. Salesforce actor IDs are its user IDs, such as `005…`, never a role name.\n5. Call the app with `crm.fetch(path)`. A leading `/` means the root of the app's `url`.\n\nThe whole flow, as the package ships it. It is ESM with top-level `await`: save it as `state-quickstart.mts` and 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({ apps: ['salesforce'] });\n\ntry {\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## Rules\n\n- Always delete in `finally`.\n- To create without waiting, pass `{ wait: false }`, then `sm.environments.wait(env.id, { status: 'running' })`.\n- The time limit counts `starting` and `running`. `(await sm.me()).limits.timeLimitMinutes` gives its `default` and `maximum`. Set `timeLimitMinutes` in `create` for longer runs.\n- Running environments are capped per organization: `limits.concurrentEnvironments` from `sm.me()`. Delete the ones you no longer need.\n- Never print, log or commit `STATEMACHINES_API_KEY`, `credentials.token` or `credentials.headers`.\n- Salesforce's SOAP API takes the actor token in the envelope, not a header: put `crm.token` in `SessionHeader/sessionId`. `crm.fetch` then sends only the environment token header.\n- `crm.fetch` never sends your API key, never follows redirects and only reaches URLs under the app's `url`.\n- Runtimes: Node.js, at the versions in the package's `engines` field, and Cloudflare Workers. ESM only. TypeScript needs `lib: [\"DOM\"]` or `@types/node`. Never ship an API key to a browser.\n- In Cloudflare Workers, pass the key explicitly: `new StateMachines({ apiKey: env.STATEMACHINES_API_KEY })`. A start takes longer than one request may last, so create with `{ wait: false }` and read `sm.environments.get(id)` in later requests until it is `running`, or pass a `signal` no shorter than the expected start.\n- Every `list` returns one page, `{ data, nextCursor }`. Pass `nextCursor` back as `cursor` until it is `null`.\n- To reuse seeded data, `await env.snapshots.create({ name })` once, then `sm.environments.create({ snapshotId })` in place of `apps`.\n- Check errors with `instanceof` (`StateMachinesError`, `EnvironmentFailedError`, `SnapshotFailedError`, `UnexpectedStatusError`, `WaitTimeoutError`), not by `name`.\n\n## Recovering a failed create\n\nThe SDK already retries `502`, `503`, `504` and connection errors with one idempotency key. If you need to replay a create whose outcome is unknown, pass your own key: `sm.environments.create(input, { idempotencyKey })`, and call it again with the same input and key. It returns the environment the first call created, or creates it if that call never arrived. A different input fails with `idempotency_key_reused`.\n\n## Troubleshooting\n\n| Symptom | Cause | Fix |\n| --- | --- | --- |\n| `version_unavailable`, 422 | The app has no default version. | Check the app ID against `sm.apps.list()`. |\n| `environment_limit_reached`, 429 | The organization is at its cap of running environments. | List them with `sm.environments.list({ status: ['starting', 'running'] })`, delete one, then create again. |\n| `invalid_status`, 409 | The status does not allow the call, for example taking a snapshot of an environment that is `starting`. | Read `sm.environments.get(id)` and act on its `status`. |\n| `invalid_request`, 400 | A wrong field. | Read `err.fields`, keyed by dotted path. |\n| `EnvironmentFailedError` | The environment failed to start. | Read `err.environment.failure`: `{ app, code, message }`. `invalid_settings` means the app refused its settings. |\n| `UnexpectedStatusError` | The environment was deleted while the SDK waited. | Read `err.status` and `err.resource`. |\n| `WaitTimeoutError` | The status was not reached within `timeoutMs`. | Wait again with `sm.environments.wait(id, { status })`, or delete the environment. |\n| `snapshot_in_progress`, 409 | The environment already has a snapshot in `saving`. | `const [running] = (await sm.snapshots.list({ environmentId: env.id, status: ['saving'] })).data`, then `await sm.snapshots.wait(running.id, { status: 'ready' })`, then create another. |\n| `SnapshotFailedError` | The snapshot failed. `interrupted` means the environment was deleted first. | Create another snapshot while the environment is `running`. |\n| `environment_not_running`, 409, from `crm.fetch` | The environment is failed or deleted. `crm.fetch` returns it as a response and never retries. | Read `(await res.json()).error.status`. Create a new environment if you need another run. |\n| `environment_starting` or `environment_unavailable`, 503, from `crm.fetch` | The app is starting or restarting. | Wait the `Retry-After` seconds and call again. |\n| A vendor client such as jsforce is refused by the gateway | The gateway needs `StateMachines-Environment-Token` on every request. | Prefer `crm.fetch`. If you pass `crm.headers` to another client, send them only to `crm.url` and disable redirects. |\n| `TypeError: Pass apiKey or set STATEMACHINES_API_KEY.` | No key in the environment. | Ask the user to set `STATEMACHINES_API_KEY`. In Workers, pass `apiKey`. |\n\nMore: https://docs.usestatemachines.com, {{origin}}/llms.txt and https://www.npmjs.com/package/@usestatemachines/sdk\n",
22
22
  });
package/agents/llms.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  # State Machines
2
2
 
3
- > State Machines runs disposable environments of replica SaaS apps, such as Salesforce, for tests and coding agents. Create, connect to, pause, resume and delete them with the TypeScript SDK `@usestatemachines/sdk`.
3
+ > 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`.
4
4
 
5
5
  Install with `npm install @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.
6
6
 
@@ -8,10 +8,9 @@ Install with `npm install @usestatemachines/sdk`. It is ESM only and runs on Nod
8
8
 
9
9
  - 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`. Pass `{ wait: false }` to get it back at once in `starting`. Always call `env.delete()` in a `finally`.
10
10
  - Apps: a string names the app, and the app's name in the environment is the same string. Use `{ app: 'salesforce', name: 'crm', settings }` for your own name, for two copies of one app, or for settings. Later calls take the name.
11
- - Pause and resume: `env.pause()` releases the compute and keeps the data; the time limit stops counting. `env.resume()` boots the apps again on the same data and waits until `running`. A paused environment is deleted after one day.
12
11
  - Credentials: `const crm = await env.connect('salesforce')` returns `url`, `headers`, `token` and `fetch`. `crm.fetch(path)` joins `path` to `url`, adds `headers`, and puts `token` in the header of the API whose path matches: `Authorization: Bearer` for Salesforce REST and Bulk 2.0. For Salesforce SOAP, put `crm.token` in the envelope's `SessionHeader/sessionId` yourself. `fetch` never sends your API key, never follows redirects and never retries. `token` and `headers` are secrets.
13
12
  - 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…`. Read them from the `actors` of the environment's version: the entry of `(await sm.apps.versions('salesforce')).data` whose `id` is `env.apps[0].versionId`.
14
- - 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 pause or delete during the snapshot fails it with `interrupted`.
13
+ - 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`.
15
14
  - 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.
16
15
  - YAML: `parseEnvironmentYaml(text)` turns a YAML document with the fields of `create` into its input.
17
16
  - Pagination: every `list` returns one page, `{ data, nextCursor }`. Pass `nextCursor` back as `cursor` until it is `null`.
@@ -20,9 +19,9 @@ Install with `npm install @usestatemachines/sdk`. It is ESM only and runs on Nod
20
19
 
21
20
  Enums are open: handle values you don't recognise.
22
21
 
23
- - Environment `status`: `starting`, `running`, `paused`, `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. A `failed` environment keeps its data for `resume()` and has `failure`: `{ app, code, message }` with `start_failed`, `resume_failed`, `restart_failed`, `version_unavailable` or `invalid_settings`.
22
+ - 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. A `failed` environment has `failure`: `{ app, code, message }` with `start_failed`, `restart_failed`, `version_unavailable` or `invalid_settings`.
24
23
  - Snapshot `status`: `saving`, `ready`, `failed`, `deleted`. A failed snapshot has `failure.code` `interrupted` or `internal_error`.
25
- - `create` and `resume` throw `EnvironmentFailedError` when the environment fails, `UnexpectedStatusError` when it becomes `paused` or `deleted` for another reason, and `WaitTimeoutError` when `timeoutMs` passes.
24
+ - `create` throws `EnvironmentFailedError` when the environment fails, `UnexpectedStatusError` when it becomes `deleted` for another reason, and `WaitTimeoutError` when `timeoutMs` passes.
26
25
  - `snapshots.create` throws `SnapshotFailedError` when the snapshot fails and `UnexpectedStatusError` when it was deleted.
27
26
  - `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.
28
27
 
@@ -40,13 +39,13 @@ A failed API call throws `StateMachinesError` with `status`, `code`, `message`,
40
39
  | `invalid_request` | 400 | Read `fields`, keyed by the dotted path of each wrong field. |
41
40
  | `forbidden` | 403 | The key lacks the permission. |
42
41
  | `not_found` | 404 | Check the ID and the workspace. |
43
- | `invalid_status` | 409 | The status does not allow the call: pause needs `running`; resume needs `paused` or `failed`. |
42
+ | `invalid_status` | 409 | The status does not allow the call. Snapshots need a running environment. |
44
43
  | `snapshot_in_progress` | 409 | Wait for the running snapshot, then create another. |
45
44
  | `idempotency_key_reused` | 409 | The key was used with a different input. Replay with the exact same input. |
46
45
  | `workspace_archived` | 409 | The workspace is archived. |
47
46
  | `body_too_large` | 413 | The request body is over the API's size limit. Send less. |
48
47
  | `version_unavailable` | 422 | The app has no default version. |
49
- | `environment_limit_reached` | 429 | Pause or delete an environment, then create or resume again. |
48
+ | `environment_limit_reached` | 429 | Delete an environment, then create again. |
50
49
  | `unavailable` | 503 | Retried automatically. If `create` still fails, call it again with the same input and your own `idempotencyKey`. |
51
50
 
52
51
  ## Errors from the app URL
@@ -55,11 +54,10 @@ A failed API call throws `StateMachinesError` with `status`, `code`, `message`,
55
54
 
56
55
  | Code | Status | What to do |
57
56
  | --- | --- | --- |
58
- | `environment_not_running` | 409 | The environment is paused, failed or deleted; `error.status` names it. Resume a paused or failed environment. |
57
+ | `environment_not_running` | 409 | The environment is failed or deleted; `error.status` names it. Create another environment. |
59
58
  | `environment_starting` | 503 | Wait `Retry-After` and call again. |
60
59
  | `environment_unavailable` | 503 | The app is restarting. Wait `Retry-After` and call again. |
61
60
  | `response_too_large` | 502 | The response is over the size limit. Ask for less, for example with a smaller page. |
62
- | `capacity_exceeded` | 429 | Too many requests in flight to the app. Send fewer at once. |
63
61
  | `unsupported_protocol` | 501 | The app does not serve this API version or protocol. |
64
62
 
65
63
  ## Quickstart
package/agents/prompt.md CHANGED
@@ -1,7 +1,7 @@
1
1
  Set up State Machines so this project can run code against a disposable replica of a SaaS app, such as Salesforce. Follow these steps in order.
2
2
 
3
3
  1. Install the SDK: `npm install @usestatemachines/sdk`. It is ESM only and runs on Node.js, at the versions in its `engines` field, and on Cloudflare Workers, not in browsers.
4
- 2. Check that `STATEMACHINES_API_KEY` is set without printing it: `test -n "$STATEMACHINES_API_KEY"`. If it is missing, stop and ask me to set it; the SDK doesn't read `.env` files. In Cloudflare Workers, pass the key as `new StateMachines({ apiKey })`.
4
+ 2. Check that `STATEMACHINES_API_KEY` is set without printing it: `test -n "$STATEMACHINES_API_KEY"`. If it is missing, stop and ask me to set it. The SDK doesn't read `.env` files. In Cloudflare Workers, pass the key as `new StateMachines({ apiKey })`.
5
5
  3. Save this quickstart unchanged as `state-quickstart.mts` and run `npx tsx state-quickstart.mts` to confirm the setup. It prints `200` and the Salesforce org's limits:
6
6
 
7
7
  ```ts
@@ -19,8 +19,8 @@ try {
19
19
  }
20
20
  ```
21
21
 
22
- 4. Adapt it to my task. Log `await sm.apps.list()` to find each app's ID, and `await sm.apps.versions(appId)` for the actors and APIs of its versions; don't guess IDs. Create only the apps the task needs; a string such as `'salesforce'` names the app and also becomes its name in the environment. `connect()` acts as the app's default actor, the Salesforce administrator; to act as another user, pass an `actorId` from the version's `actors`. To reuse seeded data, create a snapshot once with `await env.snapshots.create()`, then `sm.environments.create({ snapshotId })`.
23
- 5. Always call `env.delete()` in a `finally`; it deletes the environment and its data. If the start fails, the server deletes the environment itself and `create` throws `EnvironmentFailedError`. Pass your own `idempotencyKey` to `create`; if it throws with no response, call it again with the same input and key.
22
+ 4. Adapt it to my task. Log `await sm.apps.list()` to find each app's ID, and `await sm.apps.versions(appId)` for the actors and APIs of its versions. Don't guess IDs. Create only the apps the task needs. A string such as `'salesforce'` names the app and also becomes its name in the environment. `connect()` acts as the app's default actor, the Salesforce administrator. To act as another user, pass an `actorId` from the version's `actors`. To reuse seeded data, create a snapshot once with `await env.snapshots.create()`, then `sm.environments.create({ snapshotId })`.
23
+ 5. Always call `env.delete()` in a `finally`. It deletes the environment and its data. If the start fails, the server deletes the environment itself and `create` throws `EnvironmentFailedError`. Pass your own `idempotencyKey` to `create`. If it throws with no response, call it again with the same input and key.
24
24
  6. Never print, log or commit keys or tokens, including `credentials.token` and `credentials.headers`.
25
25
 
26
26
  Read more: https://docs.usestatemachines.com, `node_modules/@usestatemachines/sdk/README.md`, https://app.usestatemachines.com/llms.txt and https://www.npmjs.com/package/@usestatemachines/sdk
@@ -40,12 +40,6 @@ export interface CreateEnvironmentOptions extends SignalOptions {
40
40
  /** A replay with the same key and input returns the same environment. Defaults to a new UUID. */
41
41
  idempotencyKey?: string;
42
42
  }
43
- export interface ResumeOptions extends SignalOptions {
44
- /** Wait until the environment is `running`. Defaults to true. */
45
- wait?: boolean;
46
- /** Deadline for the wait. Defaults to 10 minutes. */
47
- timeoutMs?: number;
48
- }
49
43
  export type CreateSnapshotOptions = CreateEnvironmentOptions;
50
44
  export interface ConnectOptions {
51
45
  /** Defaults to the app's default actor (for Salesforce, the administrator). */
@@ -87,10 +81,6 @@ export declare class StateMachines {
87
81
  get: (environmentId: string) => Promise<Environment>;
88
82
  /** `labels` replaces the whole map. */
89
83
  update: (environmentId: string, body: UpdateEnvironmentRequestBody) => Promise<Environment>;
90
- /** Takes effect when the API answers: the gateway refuses calls from then on. */
91
- pause: (environmentId: string, options?: SignalOptions) => Promise<Environment>;
92
- /** Resumes a paused or failed environment and, unless `wait` is false, waits until it is `running`. */
93
- resume: (environmentId: string, options?: ResumeOptions) => Promise<Environment>;
94
84
  /** Deletes the environment and its data. Safe to repeat. */
95
85
  delete: (environmentId: string, options?: SignalOptions) => Promise<Environment>;
96
86
  /**
@@ -72,18 +72,6 @@ export class StateMachines {
72
72
  get: async (environmentId) => this.#bind(await this.#call(undefined, (call) => this.#sdk.environments.get({ environmentId }, call))),
73
73
  /** `labels` replaces the whole map. */
74
74
  update: async (environmentId, body) => this.#bind(await this.#call(undefined, (call) => this.#sdk.environments.update({ environmentId, body }, call))),
75
- /** Takes effect when the API answers: the gateway refuses calls from then on. */
76
- pause: async (environmentId, options = {}) => this.#bind(await this.#call(options.signal, (call) => this.#sdk.environments.pause({ environmentId }, call))),
77
- /** Resumes a paused or failed environment and, unless `wait` is false, waits until it is `running`. */
78
- resume: async (environmentId, options = {}) => {
79
- const resumed = await this.#call(options.signal, (call) => this.#sdk.environments.resume({ environmentId }, call));
80
- return options.wait === false
81
- ? this.#bind(resumed)
82
- : this.environments.wait(environmentId, {
83
- status: 'running',
84
- ...waitOptions(options),
85
- });
86
- },
87
75
  /** Deletes the environment and its data. Safe to repeat. */
88
76
  delete: async (environmentId, options = {}) => this.#bind(await this.#call(options.signal, (call) => this.#sdk.environments.delete({ environmentId }, call))),
89
77
  /**
@@ -186,8 +174,6 @@ export class StateMachines {
186
174
  const { id } = data;
187
175
  const helpers = {
188
176
  connect: (app, options = {}) => this.#connect(id, data, app, options),
189
- pause: (options) => this.environments.pause(id, options),
190
- resume: (options) => this.environments.resume(id, options),
191
177
  delete: (options) => this.environments.delete(id, options),
192
178
  refresh: () => this.environments.get(id),
193
179
  snapshots: {
@@ -1,6 +1,6 @@
1
1
  import type { Environment as EnvironmentData } from '../models/environment.js';
2
2
  import type { Snapshot } from '../models/snapshot.js';
3
- import type { ConnectOptions, CreateSnapshotOptions, ResumeOptions, SignalOptions } from './client.js';
3
+ import type { ConnectOptions, CreateSnapshotOptions, SignalOptions } from './client.js';
4
4
  import type { Credentials } from './credentials.js';
5
5
  /**
6
6
  * An environment as the API returned it, with helpers bound to it. The helpers are not
@@ -10,9 +10,6 @@ export type Environment = EnvironmentData & EnvironmentHelpers;
10
10
  export interface EnvironmentHelpers {
11
11
  /** Credentials for one app; `actorId` defaults to the app's default actor. */
12
12
  connect(app: string, options?: ConnectOptions): Promise<Credentials>;
13
- pause(options?: SignalOptions): Promise<Environment>;
14
- /** Waits until `running` unless `wait` is false. */
15
- resume(options?: ResumeOptions): Promise<Environment>;
16
13
  delete(options?: SignalOptions): Promise<Environment>;
17
14
  /** Reads the environment again. */
18
15
  refresh(): Promise<Environment>;
@@ -1,5 +1,5 @@
1
1
  export type { Actor, Api, App, AuditEvent, BodyPreview, CreateEnvironment, CreateEnvironmentApp, EnvironmentApp, EnvironmentFailure, EnvironmentFailureCode, EnvironmentStatus, ErrorCode, Me, Permission, Principal, RequestDetail, RequestStats, RequestT as Request, Snapshot, SnapshotStatus, Version, Workspace, } from '../models/index.js';
2
- export type { ConnectOptions, CreateEnvironmentOptions, CreateSnapshotOptions, ListAuditEventsQuery, ListEnvironmentsQuery, ListRequestsQuery, ListSnapshotsQuery, ListWorkspacesQuery, Page, PageQuery, RequestStatsQuery, ResumeOptions, SignalOptions, StateMachinesOptions, } from './client.js';
2
+ export type { ConnectOptions, CreateEnvironmentOptions, CreateSnapshotOptions, ListAuditEventsQuery, ListEnvironmentsQuery, ListRequestsQuery, ListSnapshotsQuery, ListWorkspacesQuery, Page, PageQuery, RequestStatsQuery, SignalOptions, StateMachinesOptions, } from './client.js';
3
3
  export { StateMachines } from './client.js';
4
4
  export type { Credentials } from './credentials.js';
5
5
  export type { Environment, EnvironmentHelpers } from './environment.js';
@@ -2,14 +2,10 @@ import { EnvironmentFailedError, isTransient, SnapshotFailedError, toStateMachin
2
2
  import { linkSignal } from './signal.js';
3
3
  export const defaultTimeoutMs = 600000;
4
4
  const readTimeoutMs = 30000;
5
- /**
6
- * The statuses each status reaches with no call from the customer: a boot ends, a restart
7
- * fails, the time limit passes, a paused or failed environment expires after a day.
8
- */
5
+ /** Statuses reachable without another customer request. */
9
6
  const environmentReaches = {
10
7
  starting: ['running', 'failed', 'deleted'],
11
8
  running: ['failed', 'deleted'],
12
- paused: ['deleted'],
13
9
  failed: ['deleted'],
14
10
  deleted: [],
15
11
  };
@@ -31,7 +31,7 @@ export declare function serverURLFromOptions(options: SDKOptions): URL | null;
31
31
  export declare const SDK_METADATA: {
32
32
  readonly language: "typescript";
33
33
  readonly openapiDocVersion: "1.0.0";
34
- readonly sdkVersion: "0.6.1";
34
+ readonly sdkVersion: "0.7.0";
35
35
  readonly genVersion: "2.941.0";
36
- readonly userAgent: "speakeasy-sdk/typescript 0.6.1 2.941.0 1.0.0 @usestatemachines/sdk";
36
+ readonly userAgent: "speakeasy-sdk/typescript 0.7.0 2.941.0 1.0.0 @usestatemachines/sdk";
37
37
  };
package/esm/lib/config.js CHANGED
@@ -24,7 +24,7 @@ export function serverURLFromOptions(options) {
24
24
  export const SDK_METADATA = {
25
25
  language: "typescript",
26
26
  openapiDocVersion: "1.0.0",
27
- sdkVersion: "0.6.1",
27
+ sdkVersion: "0.7.0",
28
28
  genVersion: "2.941.0",
29
- userAgent: "speakeasy-sdk/typescript 0.6.1 2.941.0 1.0.0 @usestatemachines/sdk",
29
+ userAgent: "speakeasy-sdk/typescript 0.7.0 2.941.0 1.0.0 @usestatemachines/sdk",
30
30
  };