@usestatemachines/sdk 0.11.0 → 0.12.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 +80 -15
- package/agents/SKILL.md +9 -4
- package/agents/index.js +4 -4
- package/agents/llms.txt +7 -5
- package/esm/extensions/client.d.ts +56 -8
- package/esm/extensions/client.js +67 -5
- package/esm/extensions/credentials.js +4 -0
- package/esm/extensions/environment.d.ts +11 -2
- package/esm/extensions/errors.d.ts +33 -0
- package/esm/extensions/errors.js +53 -0
- package/esm/extensions/import-data.d.ts +43 -0
- package/esm/extensions/import-data.js +88 -0
- package/esm/extensions/index.d.ts +4 -3
- package/esm/extensions/index.js +1 -1
- package/esm/extensions/packaging.d.ts +1 -0
- package/esm/extensions/packaging.js +1 -0
- package/esm/extensions/zip.d.ts +8 -0
- package/esm/extensions/zip.js +121 -0
- package/esm/funcs/environments-actors.d.ts +11 -0
- package/esm/funcs/environments-actors.js +102 -0
- package/esm/funcs/get-import-file.d.ts +12 -0
- package/esm/funcs/get-import-file.js +97 -0
- package/esm/funcs/list-import-files.d.ts +12 -0
- package/esm/funcs/list-import-files.js +98 -0
- package/esm/funcs/upload-import.d.ts +12 -0
- package/esm/funcs/upload-import.js +96 -0
- package/esm/lib/config.d.ts +2 -2
- package/esm/lib/config.js +2 -2
- package/esm/models/absent-record.d.ts +7 -3
- package/esm/models/absent-record.js +3 -2
- package/esm/models/actor.d.ts +3 -0
- package/esm/models/app-user.d.ts +17 -0
- package/esm/models/app-user.js +18 -0
- package/esm/models/credentials.d.ts +6 -1
- package/esm/models/credentials.js +2 -0
- package/esm/models/environment.d.ts +4 -4
- package/esm/models/environment.js +3 -4
- package/esm/models/error.d.ts +7 -0
- package/esm/models/error.js +4 -0
- package/esm/models/filled-field.d.ts +14 -0
- package/esm/models/filled-field.js +16 -0
- package/esm/models/import-actor.d.ts +16 -0
- package/esm/models/import-actor.js +15 -0
- package/esm/models/import-file.d.ts +56 -0
- package/esm/models/import-file.js +67 -0
- package/esm/models/import-folder.d.ts +56 -0
- package/esm/models/import-folder.js +75 -0
- package/esm/models/import-problem.d.ts +4 -4
- package/esm/models/import-problem.js +1 -1
- package/esm/models/index.d.ts +18 -1
- package/esm/models/index.js +18 -1
- package/esm/models/known-difference.d.ts +21 -0
- package/esm/models/known-difference.js +16 -0
- package/esm/models/launch-ui-session.d.ts +12 -2
- package/esm/models/launch-ui-session.js +2 -1
- package/esm/models/made-id.d.ts +22 -0
- package/esm/models/made-id.js +17 -0
- package/esm/models/matched-record.d.ts +22 -0
- package/esm/models/matched-record.js +17 -0
- package/esm/models/object-load.d.ts +21 -0
- package/esm/models/object-load.js +17 -0
- package/esm/models/operations/create-credentials.d.ts +10 -0
- package/esm/models/operations/create-credentials.js +1 -0
- package/esm/models/operations/get-import-file.d.ts +13 -0
- package/esm/models/operations/get-import-file.js +12 -0
- package/esm/models/operations/index.d.ts +4 -0
- package/esm/models/operations/index.js +4 -0
- package/esm/models/operations/list-actors.d.ts +32 -0
- package/esm/models/operations/list-actors.js +26 -0
- package/esm/models/operations/list-import-files.d.ts +15 -0
- package/esm/models/operations/list-import-files.js +13 -0
- package/esm/models/operations/list-snapshots.d.ts +2 -2
- package/esm/models/operations/list-snapshots.js +1 -1
- package/esm/models/operations/upload-import.d.ts +13 -0
- package/esm/models/operations/upload-import.js +17 -0
- package/esm/models/problem-counts.d.ts +17 -0
- package/esm/models/problem-counts.js +14 -0
- package/esm/models/received-import.d.ts +9 -0
- package/esm/models/received-import.js +13 -0
- package/esm/models/record-source.d.ts +13 -0
- package/esm/models/{absent-record-source.js → record-source.js} +3 -3
- package/esm/models/snapshot-app-full-load-counts.d.ts +57 -0
- package/esm/models/snapshot-app-full-load-counts.js +25 -0
- package/esm/models/snapshot-app-full-load.d.ts +101 -0
- package/esm/models/snapshot-app-full-load.js +42 -0
- package/esm/models/snapshot-app-limited-load-counts.d.ts +16 -0
- package/esm/models/snapshot-app-limited-load-counts.js +14 -0
- package/esm/models/snapshot-app-limited-load.d.ts +18 -0
- package/esm/models/snapshot-app-limited-load.js +16 -0
- package/esm/models/snapshot-app-load-counts.d.ts +13 -0
- package/esm/models/snapshot-app-load-counts.js +15 -0
- package/esm/models/snapshot-app-load.d.ts +5 -12
- package/esm/models/snapshot-app-load.js +6 -6
- package/esm/models/snapshot-list-item.d.ts +53 -0
- package/esm/models/snapshot-list-item.js +42 -0
- package/esm/models/snapshot.d.ts +1 -1
- package/esm/models/version.d.ts +3 -0
- package/esm/sdk/environments.d.ts +1 -0
- package/esm/sdk/environments.js +4 -0
- package/esm/sdk/sdk.d.ts +3 -0
- package/esm/sdk/sdk.js +12 -0
- package/openapi.json +1868 -510
- package/package.json +7 -3
- package/esm/models/absent-record-source.d.ts +0 -13
package/README.md
CHANGED
|
@@ -67,13 +67,14 @@ dates, and API key kind. Expiry dates are `Date` objects when present, or `null`
|
|
|
67
67
|
| `sm` | `me()` |
|
|
68
68
|
| `sm.apps` | `list(query?)`, `versions(appId, query?)` |
|
|
69
69
|
| `sm.workspaces` | `list(query?)`, `get(id)` |
|
|
70
|
-
| `sm.environments` | `create(input, options?)`, `list(query?)`, `get(id)`, `update(id, { name?, labels? })`, `delete(id)`, `wait(id, { status })`, `connect(id, appName, {
|
|
71
|
-
| `sm.snapshots` | `create({ environmentId, name? } \| { importId, name? }, options?)`, `list(query?)`, `get(id)`, `update(id, { name })`, `delete(id)`, `wait(id, { status })` |
|
|
70
|
+
| `sm.environments` | `create(input, options?)`, `list(query?)`, `get(id)`, `update(id, { name?, labels? })`, `delete(id)`, `wait(id, { status })`, `connect(id, appName, { as?, services? }?)`, `actors(id, appName, { search?, limit?, cursor? }?)` |
|
|
71
|
+
| `sm.snapshots` | `create({ environmentId, name? } \| { importId, name? } \| { import, name?, workspaceId? }, options?)`, `list(query?)`, `get(id)`, `update(id, { name })`, `delete(id)`, `wait(id, { status })` |
|
|
72
72
|
| `sm.imports` | `upload(zip, options?)`, `list(query?)`, `get(id)`, `delete(id)` |
|
|
73
73
|
| `sm.requests` | `list(query)`, `get(id)`, `stats(query)`, `body(id, 'request' \| 'response')` |
|
|
74
74
|
| `sm.auditEvents` | `list(query?)` |
|
|
75
75
|
|
|
76
|
-
Every environment the SDK returns carries bound helpers: `env.connect(appName)`, `env.
|
|
76
|
+
Every environment the SDK returns carries bound helpers: `env.connect(appName)`, `env.connectAll({ as })`,
|
|
77
|
+
`env.actors(appName)`, `env.delete()`, `env.refresh()` and `env.snapshots.create({ name? })`. They are
|
|
77
78
|
not enumerable, so `JSON.stringify(env)` holds only the API's fields.
|
|
78
79
|
|
|
79
80
|
## Apps
|
|
@@ -111,13 +112,14 @@ as `setup.preset`.
|
|
|
111
112
|
checks `services` again when the environment starts, and a value that it refuses fails the start
|
|
112
113
|
with `failure.code: 'invalid_services'`.
|
|
113
114
|
|
|
114
|
-
`sm.apps.list()` lists the apps. `sm.apps.versions(appId)` lists each version's services and
|
|
115
|
-
actors. A new environment runs each app's default
|
|
115
|
+
`sm.apps.list()` lists the apps. `sm.apps.versions(appId)` lists each version's services, and its
|
|
116
|
+
`actors`: the users of the app's seed, for display. A new environment runs each app's default
|
|
117
|
+
version.
|
|
116
118
|
|
|
117
119
|
## Credentials
|
|
118
120
|
|
|
119
|
-
`env.connect(appName, {
|
|
120
|
-
`services`, `expiresAt` and `login`, plus `fetch(path, init?)` and `service(id)`.
|
|
121
|
+
`env.connect(appName, { as?, services? })` returns the API's `Credentials`: `actorId`, `actor`,
|
|
122
|
+
`token`, `services`, `expiresAt` and `login`, plus `fetch(path, init?)` and `service(id)`.
|
|
121
123
|
|
|
122
124
|
- Each entry of `services` has an `id`, a `url` and the `headers` that every request to that URL
|
|
123
125
|
needs. A service's `url` is the app URL followed by the service's `path`. The `api` service is
|
|
@@ -137,8 +139,18 @@ actors. A new environment runs each app's default version.
|
|
|
137
139
|
`SessionHeader/sessionId` yourself.
|
|
138
140
|
- `fetch` never sends your API key, never follows redirects, never retries and only reaches paths
|
|
139
141
|
under the `api` service URL.
|
|
140
|
-
- `
|
|
141
|
-
|
|
142
|
+
- `as` names the user to act as, by email, username or ID, such as
|
|
143
|
+
`env.connect('salesforce', { as: 'alice.park@acme.example' })`. Any user who can sign in to the
|
|
144
|
+
running app can act, including a user that an import or a program created. Without `as`, the
|
|
145
|
+
credentials act as the app's default user. When the app's data came from an import whose
|
|
146
|
+
`actors` names users for the app, the default user is the first of them. Otherwise it is the
|
|
147
|
+
app's administrator. `actorId` is the old name of `as`.
|
|
148
|
+
- `actor` holds the `id`, `name`, `email`, `username`, `role` and `default` of the user that the
|
|
149
|
+
credentials act as. `actorId` holds the app's own ID of that user, whatever `as` named.
|
|
150
|
+
- A user that the app does not have throws `ActorNotFoundError`, whose `nearest` lists the users
|
|
151
|
+
who can act that are nearest to `as`, each with the fields of `actor`. To act as one of them,
|
|
152
|
+
send its `id` as `as`. A user that the app does not let sign in now, such as an inactive user,
|
|
153
|
+
throws `ActorCannotSignInError`.
|
|
142
154
|
- `login` holds what the vendor's own login call needs, when the version declares one for the
|
|
143
155
|
actor.
|
|
144
156
|
- `token` and each service's `headers` are secrets. To use another client, such as jsforce, send
|
|
@@ -147,6 +159,14 @@ actors. A new environment runs each app's default version.
|
|
|
147
159
|
Environments created with `tokenInUrl: true` carry the environment token in each service's `url`
|
|
148
160
|
instead of in its `headers`.
|
|
149
161
|
|
|
162
|
+
`env.actors(appName, { search?, limit?, cursor? })` returns one page of the users who can act in
|
|
163
|
+
the app, the default user first. `search` matches part of a name, an email or a username in any
|
|
164
|
+
case, or a whole ID.
|
|
165
|
+
|
|
166
|
+
`env.connectAll({ as })` connects to every app of the environment as the user that `as` names. It
|
|
167
|
+
returns `credentials`, by app name, for each app that lets the user act, and `errors`, with the
|
|
168
|
+
`ActorNotFoundError` or `ActorCannotSignInError` of each other app. Any other error throws.
|
|
169
|
+
|
|
150
170
|
## Browser UI sessions
|
|
151
171
|
|
|
152
172
|
`@usestatemachines/sdk/ui` is the only part of the SDK for a browser. It holds no client and no
|
|
@@ -182,11 +202,51 @@ const copy = await sm.environments.create({ snapshotId: built.id });
|
|
|
182
202
|
```
|
|
183
203
|
|
|
184
204
|
A snapshot from a `ready` import holds every app of the import, each on its default version,
|
|
185
|
-
with empty setup and services. Each app's `load`
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
205
|
+
with empty setup and services. Each app's `load` is the report of its load: the counts per
|
|
206
|
+
object, the IDs that the app made, the records that took over seed records, the filled values,
|
|
207
|
+
the actors, the seed users that the load deactivated, the records that did not load, the known
|
|
208
|
+
differences, and the problems. Each list holds its first items beside the count of all of them,
|
|
209
|
+
such as `absent` and `absentCount`. `actors` keeps the order of the import's `actors`, so the
|
|
210
|
+
first is the default user. A record that wrote a text ID, such as `acme`, holds it in `text`.
|
|
211
|
+
`sm.snapshots.list()` answers each app's `load` with every count and no list, so read one
|
|
212
|
+
snapshot with `sm.snapshots.get(id)` for the lists. An app that
|
|
213
|
+
refuses the import fails the snapshot with `failure.code: 'invalid_import'`, and
|
|
214
|
+
`SnapshotFailedError.problems` lists its problems. An environment from the snapshot does not
|
|
215
|
+
load the import, so it starts after the import is deleted.
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
const accounts = await sm.snapshots.create({
|
|
219
|
+
import: {
|
|
220
|
+
format: 'statemachines.import/v1',
|
|
221
|
+
name: 'q3-accounts',
|
|
222
|
+
apps: {
|
|
223
|
+
salesforce: {
|
|
224
|
+
records: {
|
|
225
|
+
Account: [{ Id: '001g5000003wfcFAAQ', Name: 'Brightwater Shipping' }],
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
`{ import }` takes an import as one object, the manifest with each app's records in it. The SDK
|
|
234
|
+
writes the object as a zip file, uploads it as `sm.imports.upload` does, and builds the snapshot
|
|
235
|
+
from the import, in one call. Each app holds its manifest settings, such as `loadMode`, and its
|
|
236
|
+
`records` by object. `content` holds the bytes of the files that the app's records name, by path
|
|
237
|
+
inside the app's `content/` folder, such as Box's `file_version/41500000004`.
|
|
238
|
+
|
|
239
|
+
The zip file holds the manifest as `statemachines.json`, without `records` and `content`. It
|
|
240
|
+
holds the records of each object as `<app>/records/<Object>.jsonl`, one record per line. So a
|
|
241
|
+
problem at line N of `salesforce/records/Account.jsonl` names
|
|
242
|
+
`apps.salesforce.records.Account[N - 1]`. A problem of `statemachines.json` points into the
|
|
243
|
+
manifest, such as `/apps/salesforce/loadMode` for `apps.salesforce.loadMode`.
|
|
244
|
+
|
|
245
|
+
Data that the SDK cannot write as a zip file, such as records that are not plain objects, throws
|
|
246
|
+
`StateMachinesError` with `code: 'invalid_request'` and `fields` before any request. An import
|
|
247
|
+
that its check leaves `invalid` throws `ImportInvalidError`, and the SDK creates no snapshot. The
|
|
248
|
+
import and the snapshot share one `idempotencyKey`, so a retry with the same key and the same
|
|
249
|
+
data finds both.
|
|
190
250
|
|
|
191
251
|
## Waiting
|
|
192
252
|
|
|
@@ -224,6 +284,9 @@ response body.
|
|
|
224
284
|
| `StateMachinesError` | any failed call | `code`, `status`, `requestId`, `fields`, `retryAfterMs` |
|
|
225
285
|
| `EnvironmentFailedError` (a `StateMachinesError`) | a waited environment failed | `environment` |
|
|
226
286
|
| `SnapshotFailedError` (a `StateMachinesError`) | a waited snapshot failed | `snapshot` and `problems`. `problems` is empty unless an app refused the import that the snapshot was built from. |
|
|
287
|
+
| `ImportInvalidError` (a `StateMachinesError`) | the check of the import that `snapshots.create({ import })` uploaded left it `invalid` | `import` and `problems` |
|
|
288
|
+
| `ActorNotFoundError` (a `StateMachinesError`) | the app has no user that `as` names | `appName` and `nearest` |
|
|
289
|
+
| `ActorCannotSignInError` (a `StateMachinesError`) | the app does not let the user that `as` names sign in now | `appName` |
|
|
227
290
|
| `UnexpectedStatusError` | a waited resource cannot reach the status without another call | `resource`, `status` |
|
|
228
291
|
| `WaitTimeoutError` | a wait passed `timeoutMs` | `resource` (the last read), `timeoutMs` |
|
|
229
292
|
|
|
@@ -233,12 +296,14 @@ response body.
|
|
|
233
296
|
| `unauthenticated` | 401 | A missing, expired or revoked key. |
|
|
234
297
|
| `forbidden` | 403 | The key lacks the permission. |
|
|
235
298
|
| `not_found` | 404 | No such resource in this workspace. |
|
|
236
|
-
| `invalid_status` | 409 | The status does not allow the call. |
|
|
299
|
+
| `invalid_status` | 409 | The status does not allow the call. Credentials need a `running` environment. |
|
|
237
300
|
| `snapshot_in_progress` | 409 | The environment has a snapshot in `saving`. |
|
|
238
301
|
| `idempotency_key_reused` | 409 | The same `idempotencyKey` with a different input. |
|
|
239
302
|
| `workspace_archived` | 409 | A write in an archived workspace. |
|
|
303
|
+
| `actor_cannot_sign_in` | 409 | The app does not let the user sign in now. `fields.as` gives the reason. |
|
|
240
304
|
| `body_too_large` | 413 | The request body is over the API's size limit, which the message names. |
|
|
241
305
|
| `version_unavailable` | 422 | The app has no default version. |
|
|
306
|
+
| `actor_not_found` | 422 | The app has no user with that email, username or ID. `nearest` lists the nearest users. |
|
|
242
307
|
| `environment_limit_reached` | 429 | Too many environments `starting` or `running`. |
|
|
243
308
|
| `internal_error` | 500 | An error in State Machines. |
|
|
244
309
|
| `unavailable` | 503 | Retried automatically. |
|
package/agents/SKILL.md
CHANGED
|
@@ -35,7 +35,7 @@ Never print, log or commit API keys, `credentials.token`, the `headers` of `cred
|
|
|
35
35
|
|
|
36
36
|
Apply the rest of this skill once the user has chosen a task. `sm.apps.list()` returns app IDs and each app's `defaultVersionId`. Every list returns `{ data, nextCursor }`. Follow cursors until you find the entry or `nextCursor` is null.
|
|
37
37
|
|
|
38
|
-
Read the app's default version from `sm.apps.versions(appId)` before you write code. It is the current guidance for that app. Each of its `services` has an `id`, a `visibility`, `limitations` and a `prompt`, and a service that serves MCP lists its `servers` with their `tools`. Its `actors` are the app's
|
|
38
|
+
Read the app's default version from `sm.apps.versions(appId)` before you write code. It is the current guidance for that app. Each of its `services` has an `id`, a `visibility`, `limitations` and a `prompt`, and a service that serves MCP lists its `servers` with their `tools`. Its `actors` are users of the app's seed, for display. They do not say who can act. Check the `limitations` against the behavior under test, as [known differences](https://app.usestatemachines.com/docs/apps/known-differences.md) does for Salesforce. A replica never calls the vendor and does not run arbitrary customer automation. An environment holds a customer's org data only when it starts from a snapshot created from an import of that data.
|
|
39
39
|
|
|
40
40
|
- Salesforce: the `api` service serves REST, Bulk API 2.0 and SOAP. Its `mcp` service is `preview`.
|
|
41
41
|
- Box: the `api` service serves the Box Platform API, uploads and OAuth. Its `mcp` service is `preview`.
|
|
@@ -45,18 +45,21 @@ Read the app's default version from `sm.apps.versions(appId)` before you write c
|
|
|
45
45
|
|
|
46
46
|
Create only the apps the task needs. A string such as `'linear'` is both the app ID and the app's name in the environment. Use `{ appId, name, setup, services }` to choose a name, run two copies of one app, pass `setup`, or configure a service. For example, `services` sets which MCP servers and tools the `mcp` service serves. Later calls take the name. Set `tokenInUrl: true` only for a client that cannot send headers.
|
|
47
47
|
|
|
48
|
-
`env.connect(name)` acts as the app's default
|
|
48
|
+
`env.connect(name)` acts as the app's default user and lists each `public` service. Pass `{ services: ['mcp'] }` to also list a `preview` service. To act as another user of the app, pass `as` with the user's email, username or ID, such as `env.connect('salesforce', { as: 'alice.park@acme.example' })`. Any user who can sign in to the running app can act, including a user that the test created. `credentials.actor` names the user that the credentials act as. Never pass a role label or a workspace member ID.
|
|
49
49
|
|
|
50
|
+
- `env.actors(name, { search })` pages through the users who can act, the default user first. Use it to find a user when the task does not name one by email, username or ID.
|
|
51
|
+
- `env.connectAll({ as })` connects to every app of the environment as one person. Its `credentials` hold each app that lets the user act, by app name. Its `errors` hold the `ActorNotFoundError` or `ActorCannotSignInError` of each other app. Any other error throws.
|
|
52
|
+
- A user that the app does not have throws `ActorNotFoundError`, and `err.nearest` lists the nearest users who can act. A user that the app does not let sign in now, such as an inactive user, throws `ActorCannotSignInError`.
|
|
50
53
|
- `credentials.fetch(path)` calls the `api` service with its headers and never follows redirects. It throws when the credentials list no `api` service, as for Linear.
|
|
51
54
|
- For MCP, `credentials.service('mcp').servers` lists the MCP servers, and `.server(name)` returns one server's `url` and `headers` to give the MCP client.
|
|
52
55
|
- An external client must send a service's `headers` only to that service's `url`, with redirects off. For Salesforce SOAP, put the actor token in `SessionHeader/sessionId`. See [credentials](https://app.usestatemachines.com/docs/environments/credentials.md).
|
|
53
|
-
- On a `tokenInUrl` environment,
|
|
56
|
+
- On a `tokenInUrl` environment, an MCP request without an `Authorization` header acts as the app's default user. To act as the user of the credentials, send the service's headers.
|
|
54
57
|
|
|
55
58
|
Point the existing integration's test transport at these URLs and credentials. If that needs a broader change, explain it before you expand the task.
|
|
56
59
|
|
|
57
60
|
## Seed your own data
|
|
58
61
|
|
|
59
|
-
Create the data a test reads before the test runs. Write it through the app's own API or MCP tools as the actor that owns it: Salesforce REST, composite or Bulk API 2.0, Linear MCP create tools, or Box uploads. A Linear workspace starts with one team and no issues. Use `setup` only for identity and configuration. To reuse seeded data, save it with `env.snapshots.create({ name })`, then create each test's environment from `{ snapshotId }`. See [isolated tests](https://app.usestatemachines.com/docs/sdk-api/testing.md).
|
|
62
|
+
Create the data a test reads before the test runs. Write it through the app's own API or MCP tools as the actor that owns it: Salesforce REST, composite or Bulk API 2.0, Linear MCP create tools, or Box uploads. A Linear workspace starts with one team and no issues. Use `setup` only for identity and configuration. To start from records, users and files that you write in one file, build a snapshot from an import, as [build a snapshot from an import](https://app.usestatemachines.com/docs/snapshots/import.md) shows. To reuse seeded data, save it with `env.snapshots.create({ name })`, then create each test's environment from `{ snapshotId }`. See [isolated tests](https://app.usestatemachines.com/docs/sdk-api/testing.md).
|
|
60
63
|
|
|
61
64
|
## Verify the result
|
|
62
65
|
|
|
@@ -104,6 +107,8 @@ Management failures throw SDK errors. App HTTP failures are returned as `Respons
|
|
|
104
107
|
| `unauthenticated` | The key is missing, expired or revoked. Fix the credential in the process that runs the task. |
|
|
105
108
|
| `invalid_request` | Read `err.fields` and the installed method signature. |
|
|
106
109
|
| `version_unavailable` | Discover the app ID and available versions. Stop if the required version is unavailable. |
|
|
110
|
+
| `ActorNotFoundError` | The app has no user with that email, username or ID. Check `err.nearest` or `env.actors(appName, { search })`. Act as another user only when the task allows it. |
|
|
111
|
+
| `ActorCannotSignInError` | The app does not let the user sign in now, for the reason in `err.fields.as`. Report it. Do not act as another user to make the test pass. |
|
|
107
112
|
| `environment_limit_reached` | Clean up only this task's known leftovers, or report the capacity blocker. |
|
|
108
113
|
| `EnvironmentFailedError` | Read `err.environment.failure.code`: `start_failed`, `pause_removed`, `version_unavailable`, `invalid_setup` or `invalid_services`. Clean up the owned environment. |
|
|
109
114
|
| `WaitTimeoutError` or abort | Use the kept environment ID. A non-null `WaitTimeoutError.resource` also carries it. If neither an ID nor a replay key is available, report unresolved cleanup. Do not guess from a list. |
|
package/agents/index.js
CHANGED
|
@@ -4,10 +4,10 @@ export const prompt =
|
|
|
4
4
|
"Set up State Machines in this existing repository so I can use disposable app environments with my coding agent.\n\n1. Read the repository instructions and inspect the existing State Machines workspace link and agent skill. Keep the repository's structure and conventions. Check that Node.js meets the CLI's runtime requirement. Use the pinned npx command below. A global CLI installation is not required.\n2. Run `npx @usestatemachines/cli@0.1.1 whoami --json`. If the project already has a `STATEMACHINES_API_KEY` loader, run the check through that loader without reading or displaying the secret. Use the same credential loader, CLI profile and API origin for every setup command below, including workspace discovery and init. Reuse valid access and confirm the returned account and organization match my task. If the existing API key or CLI profile works, skip sign-in and reuse it for setup. Do not create a replacement API key. Report an access denial or origin mismatch instead of changing accounts or permissions.\n3. Only when no project API key is configured and the access check reports a missing, expired or revoked CLI credential, run `npx @usestatemachines/cli@0.1.1 login --no-wait --json`. For an expired or revoked saved session, add `--yes` to replace that invalid session in the same profile. Do not replace a valid session for a different account. Show me the returned verification URL and code. Wait for my approval before resuming the same login with `npx @usestatemachines/cli@0.1.1 login --resume --json`, using the same profile. If a login is already pending, resume it after approval instead of starting another. If the request expires, offer a new login. Never ask me to paste a credential into chat, print it or commit it.\n If a pending login's approval URL or code is unavailable, ask before recovering it. Explain that recovery may finish an already-approved login. After confirmation, run `npx @usestatemachines/cli@0.1.1 login --resume --json --stream` with the same profile and API origin. Keep that process running and show its URL and code. Request approval only for a recognized login. Wait for `signed-in` before continuing setup. Do not start another login or resume process. If streaming output is unavailable, report the blocker without reading credential files.\n\n4. Run `npx @usestatemachines/cli@0.1.1 init --agent <this-agent> --yes`, choosing `claude` for Claude Code or `codex` for Codex. Reuse the repository's existing workspace link. If the CLI needs a workspace, run `npx @usestatemachines/cli@0.1.1 workspaces list --json`. Ask me which to use when more than one is available, then pass `--workspace <workspace-id>` to init. Do not replace a different workspace link or a customized skill without my approval. Read the installed skill and use its pinned npx commands for later work.\n5. Do not create a new project, install the SDK, write example code, change an integration or create remote resources during skill installation. Do not add `--sdk`, `--quickstart` or `--env` to init unless I ask for those changes.\n6. Report where the skill was installed, which workspace is linked, and whether the agent needs to reload it. Setup alone does not verify an integration. Ask: \"What would you like to run or test?\" Wait for my answer before making further changes.\n\nIf this agent is not Claude Code or Codex but can run commands and has `STATEMACHINES_API_KEY`, download https://app.usestatemachines.com/skill.md itself with `curl -fsS -H \"Authorization: Bearer $STATEMACHINES_API_KEY\"`. Never print the key. If this agent cannot fetch the file with that key, ask me to download https://app.usestatemachines.com/skill.md from my signed-in browser and provide the file. Do not request session cookies or credentials. Install that file in the agent's supported project-local skills directory, preserving any customized skill. If persistent skills are unsupported, read the file for this session and explain the limitation. Report setup as incomplete if the file is unavailable.\n";
|
|
5
5
|
|
|
6
6
|
export const llmsTxt =
|
|
7
|
-
"# State Machines\n\n> State Machines runs disposable environments of replica SaaS apps, such as Salesforce, Linear and Box, for tests and coding agents. Create, connect to, and delete them with the TypeScript SDK `@usestatemachines/sdk`.\n\nUse the project's package manager to install or reuse `@usestatemachines/sdk`. It is ESM only and runs on Node.js and Cloudflare Workers, not browsers. `engines` in its `package.json` gives the Node.js versions. TypeScript needs `lib: [\"DOM\"]` or `@types/node`. `new StateMachines()` reads the API key from `STATEMACHINES_API_KEY` and doesn't read `.env` files; in Cloudflare Workers, pass `apiKey` explicitly. Never print the key. The installed `node_modules/@usestatemachines/sdk/README.md` matches your version: read it before relying on memory. Read the bundled `agents/SKILL.md` for the task workflow. Reuse the project's secret loader and check the key in the process that runs the test, not just the current shell. Installing the skill does not require SDK installation, example files or remote resources. Ask what the user wants to run and wait. Install the SDK and verify access when their chosen task needs it. Run the connection check below only if the user asks to verify the connection. Do not choose a business workflow from the repository.\n\n## Get a credential\n\nIf the project's existing command loads `STATEMACHINES_API_KEY`, reuse that command and secret loader. Keep the same credential loader, CLI profile and API origin across setup commands. Otherwise, run `npx @usestatemachines/cli@0.1.1 whoami --json`. If it succeeds, confirm the account and organization match the task, then use the workspace commands below. If the CLI credential is missing, expired or revoked, start `npx @usestatemachines/cli@0.1.1 login --no-wait --json`. For an expired or revoked saved session, add `--yes` to replace that invalid session in the same profile. Do not replace a valid session for a different account. Show its `verificationUrl` and `userCode`, and wait for approval. A matching code does not identify the requesting computer. After that approval, run `npx @usestatemachines/cli@0.1.1 login --resume --json` with the same profile to finish that attempt. Resume an existing pending login after approval instead of creating another. Report access denial or an API origin mismatch without changing accounts or permissions.\n\nIf a pending login's approval URL or code is unavailable, ask before recovering it. Explain that recovery may finish an already-approved login. After confirmation, run `npx @usestatemachines/cli@0.1.1 login --resume --json --stream` with the same profile and API origin. Keep that process running and show its URL and code. Request approval only for a recognized login. Wait for `signed-in` before continuing setup. Do not start another login or resume process. If streaming output is unavailable, report the blocker without reading credential files.\n\nFor `cli.not_linked`, run `npx @usestatemachines/cli@0.1.1 workspaces list --json`. Ask which workspace to use when there is more than one, then run `npx @usestatemachines/cli@0.1.1 link --workspace <workspace-id> --json`. Run the user's existing code with `npx @usestatemachines/cli@0.1.1 run -- <command>`. Never print or commit a credential.\n\n## CLI commands\n\nPrefix these commands with `npx @usestatemachines/cli@0.1.1`. Use `help --json` for flags and result schemas:\n\n- `login`, `logout`, `whoami`, `profiles list`, and `profiles use`\n- `workspaces list`, `workspaces use`, `link`, `unlink`, and `init`\n- `apps list`, `environments create`, `environments list`, `environments get`, `environments wait`, and `environments delete`\n- `connect`, `snapshots create`, `snapshots list`, `snapshots get`, `snapshots delete`, `requests list`, and `requests get`\n- `run -- <command>`, `env pull`, `api`, `doctor`, `version`, `help`, and `completion`\n\n## Concepts\n\n- Environment: one disposable run of one or more apps. `const env = await sm.environments.create({ apps: ['salesforce'] })` creates it and waits until it is `running`. Use `{ wait: false }` to retain the ID before startup. Wait for `running` inside `try`, then delete the owned environment in `finally`, including after a wait failure.\n- Apps: a string names the app by its ID, and the app's name in the environment is the same string. Use `{ appId: 'salesforce', name: 'crm', setup }` for your own name, for two copies of one app, or for the app's setup. Later calls take the name.\n- App version: `sm.apps.versions(appId)` lists an app's versions. Read the default one before you use the app: each service has a `visibility`, `limitations`, a `prompt` and, for MCP, `servers` with their `tools`. `actors` lists the app's users with `id`, `name` and `summary`.\n- Credentials: `const crm = await env.connect('salesforce')` returns `token`, `services` and `fetch`. Each service has an `id`, a `url` and the `headers` its requests need. The credentials always list each `public` service of the app's version. Pass `{ services: [id] }` to `connect()` to also list a service by its ID. A `preview` service is listed only when named. `crm.service('api')` returns the `api` service, which is the app URL. `crm.fetch(path)` joins `path` to the `api` service URL and adds its headers, which carry `Authorization: Bearer` with `token`. It throws when the credentials list no `api` service, as for Linear, whose clients call its `mcp` service. `crm.service('mcp').server(name)` returns one MCP server's `url` and `headers` for an MCP client. On an environment created with `tokenInUrl: true`, a Salesforce or Box MCP request without `Authorization` acts as the default actor. Linear refuses an MCP request without `Authorization`, so a Linear MCP client always sends the `mcp` service's headers. For Salesforce SOAP, also put `crm.token` in the envelope's `SessionHeader/sessionId`. `fetch` never sends your API key, never follows redirects and never retries. `token` and each service's `headers` are secrets. On a `tokenInUrl` environment, each service and MCP server `url` carries the token and is a secret too.\n- Actor: one of the app's users; requests run with that user's permissions. `connect()` acts as the app's default actor (for Salesforce, the administrator) unless you pass `actorId`. Salesforce actor IDs are its user IDs, such as `005…`. Find the app by name in `env.apps`, then page through `sm.apps.versions(appId)` to find its exact `versionId`. Read actor IDs from that version's `actors`.\n- Snapshot: the saved data of every app of a running environment, taken at one moment, or of an import. `await env.snapshots.create({ name: 'seeded' })` waits until it is `ready`. `sm.snapshots.create({ importId })` builds one from a `ready` import, and each app's `load` counts the records that did not load and lists the first of them. `sm.environments.create({ snapshotId })` creates a new environment from it, in place of `apps`; each app keeps its name and version. A delete during the snapshot fails it with `interrupted`.\n- Recorded requests: `sm.requests.list({ environmentId })` pages the calls made to the environment's apps. `sm.requests.get(id)` adds headers and a preview of each body. `sm.requests.body(id, 'request')` or `'response'` returns the full body as bytes. Recording is best effort.\n- YAML: `parseEnvironmentYaml(text)` turns a YAML document with the fields of `create` into its input.\n- Pagination: every `list` returns one page, `{ data, nextCursor }`. Pass `nextCursor` back as `cursor` until it is `null`.\n\n## Statuses\n\nEnums are open: handle values you don't recognise.\n\n- Environment `status`: `starting`, `running`, `failed`, `deleted`. A `deleted` environment has a `reason` when State Machines deleted it: `expired`, `failed` (it never reached `running`) or `cancelled` (staff ended it). The `reason` is `null` when a caller deleted it. When the environment failed, `failure` is `{ appName, code, message }`, and `code` is `start_failed`, `pause_removed`, `version_unavailable`, `invalid_setup` or `invalid_services`. For `invalid_setup` and `invalid_services`, the app refused a value in `setup` or in `services`, and `message` starts with its path.\n- Snapshot `status`: `saving`, `ready`, `failed`, `deleted`. A failed snapshot has `failure.code` `interrupted`, `internal_error` or `invalid_import`. For `invalid_import`, an app refused the import that the snapshot was built from: `failure.appName` names the app, and `failure.problems` lists each problem.\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, with the problems of a refused import in `problems`, and `UnexpectedStatusError` when it was deleted.\n- `sm.environments.wait(id, { status })` and `sm.snapshots.wait(id, { status })` wait for any status, and throw once it cannot be reached without another call.\n\n## Limits\n\nWhen `sm.me().limits` is non-null:\n\n- Running environments are capped per organization: `(await sm.me()).limits.concurrentEnvironments` counts `starting` and `running`.\n- `timeLimitMinutes` has a default and a maximum: `(await sm.me()).limits.timeLimitMinutes`. It counts `starting` and `running` only; at the end the environment is deleted with `reason: 'expired'`.\n\n## Errors and retries\n\nA failed API call throws `StateMachinesError` with `status`, `code`, `message`, `requestId`, `fields` and `retryAfterMs`. The SDK retries `502`, `503`, `504` and connection errors itself, until a deadline; `create` retries reuse one idempotency key. It retries nothing else.\n\n| Code | Status | What to do |\n| --- | --- | --- |\n| `invalid_request` | 400 | Read `fields`, keyed by the dotted path of each wrong field. |\n| `unauthenticated` | 401 | The key is missing, expired or revoked. |\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. A snapshot needs a running environment or a `ready` import. |\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| `name_taken`, `last_owner`, `last_workspace`, `version_in_use`, `ui_disabled` | 409 | A workspace, member, version or UI session call conflicts with current state. Read `message`. |\n| `body_too_large` | 413 | The request body is over the API's size limit. Send less. |\n| `version_unavailable` | 422 | The app has no default version. |\n| `environment_limit_reached` | 429 | Delete only this task’s known leftovers, or ask the user to free capacity. |\n| `rate_limited` | 429 | Wait `retryAfterMs` when it is set, then retry. |\n| `internal_error` | 500 | Report `requestId`. |\n| `unavailable` | 503 | Retried automatically. If `create` still has an unknown outcome, replay the same input with the key retained before the original call. Do not generate a new key. |\n\n## Errors from the app URL\n\n`crm.fetch` returns these as responses with the same JSON envelope, and never retries them. Assert the expected status and result. For writes, read back the stored result. Never blindly retry a write whose outcome is unknown.\n\n| Code | Status | What to do |\n| --- | --- | --- |\n| `environment_not_running` | 409 | The environment is failed or deleted; `error.status` names it. Create another environment. |\n| `environment_starting` | 503 | For reads, wait `Retry-After` and retry. Check stored state before retrying an uncertain write. |\n| `environment_unavailable` | 503 | The app is restarting. For reads, wait `Retry-After`. Check stored state before retrying an uncertain write. |\n| `response_too_large` | 502 | The response is over the size limit. Ask for less, for example with a smaller page. |\n| `invalid_setup` | 503 | The app refused its `setup` after the environment started, such as in a newer version. `error.message` starts with the path of the value. Create another environment with a `setup` that the app accepts. |\n| `invalid_services` | 503 | The app refused a value in `services` after the environment started. `error.message` starts with the path of the value. Create another environment with `services` that the app accepts. |\n| `unsupported_protocol` | 501 | The app does not serve this path, method or content type, or the request body is compressed. |\n\n## Quickstart\n\nUse the project's TypeScript runner with its secret loader, or pass that runner through `npx @usestatemachines/cli@0.1.1 run -- <command>`. For an empty Node.js project, save as `state-quickstart.mts` and run `npx @usestatemachines/cli@0.1.1 run -- npx tsx state-quickstart.mts`. The check prints the response status and deletes the environment. Only after it prints `200`, report verified setup and the rerun command. Continue only with an explicitly supplied task. Otherwise, ask what to run next and wait with no setup environment left running.\n\n```ts\nimport { StateMachines } from '@usestatemachines/sdk';\n\nconst sm = new StateMachines(); // reads STATEMACHINES_API_KEY\nconst env = await sm.environments.create(\n { apps: ['salesforce'] },\n { wait: false },\n);\n\ntry {\n await sm.environments.wait(env.id, { status: 'running' });\n const salesforce = await env.connect('salesforce');\n const res = await salesforce.fetch('/services/data/v67.0/limits');\n console.log(res.status, await res.json());\n} finally {\n await env.delete();\n}\n```\n\n## Docs\n\n- [Documentation](https://app.usestatemachines.com/docs): guides, concepts and reference ([llms.txt](https://app.usestatemachines.com/docs/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\nFetch a docs page as Markdown with the API key in the `Authorization` header, for example `curl -fsS -H \"Authorization: Bearer $STATEMACHINES_API_KEY\" https://app.usestatemachines.com/docs/sdk-api/client.md`. Never print the key.\n";
|
|
7
|
+
"# State Machines\n\n> State Machines runs disposable environments of replica SaaS apps, such as Salesforce, Linear and Box, for tests and coding agents. Create, connect to, and delete them with the TypeScript SDK `@usestatemachines/sdk`.\n\nUse the project's package manager to install or reuse `@usestatemachines/sdk`. It is ESM only and runs on Node.js and Cloudflare Workers, not browsers. `engines` in its `package.json` gives the Node.js versions. TypeScript needs `lib: [\"DOM\"]` or `@types/node`. `new StateMachines()` reads the API key from `STATEMACHINES_API_KEY` and doesn't read `.env` files; in Cloudflare Workers, pass `apiKey` explicitly. Never print the key. The installed `node_modules/@usestatemachines/sdk/README.md` matches your version: read it before relying on memory. Read the bundled `agents/SKILL.md` for the task workflow. Reuse the project's secret loader and check the key in the process that runs the test, not just the current shell. Installing the skill does not require SDK installation, example files or remote resources. Ask what the user wants to run and wait. Install the SDK and verify access when their chosen task needs it. Run the connection check below only if the user asks to verify the connection. Do not choose a business workflow from the repository.\n\n## Get a credential\n\nIf the project's existing command loads `STATEMACHINES_API_KEY`, reuse that command and secret loader. Keep the same credential loader, CLI profile and API origin across setup commands. Otherwise, run `npx @usestatemachines/cli@0.1.1 whoami --json`. If it succeeds, confirm the account and organization match the task, then use the workspace commands below. If the CLI credential is missing, expired or revoked, start `npx @usestatemachines/cli@0.1.1 login --no-wait --json`. For an expired or revoked saved session, add `--yes` to replace that invalid session in the same profile. Do not replace a valid session for a different account. Show its `verificationUrl` and `userCode`, and wait for approval. A matching code does not identify the requesting computer. After that approval, run `npx @usestatemachines/cli@0.1.1 login --resume --json` with the same profile to finish that attempt. Resume an existing pending login after approval instead of creating another. Report access denial or an API origin mismatch without changing accounts or permissions.\n\nIf a pending login's approval URL or code is unavailable, ask before recovering it. Explain that recovery may finish an already-approved login. After confirmation, run `npx @usestatemachines/cli@0.1.1 login --resume --json --stream` with the same profile and API origin. Keep that process running and show its URL and code. Request approval only for a recognized login. Wait for `signed-in` before continuing setup. Do not start another login or resume process. If streaming output is unavailable, report the blocker without reading credential files.\n\nFor `cli.not_linked`, run `npx @usestatemachines/cli@0.1.1 workspaces list --json`. Ask which workspace to use when there is more than one, then run `npx @usestatemachines/cli@0.1.1 link --workspace <workspace-id> --json`. Run the user's existing code with `npx @usestatemachines/cli@0.1.1 run -- <command>`. Never print or commit a credential.\n\n## CLI commands\n\nPrefix these commands with `npx @usestatemachines/cli@0.1.1`. Use `help --json` for flags and result schemas:\n\n- `login`, `logout`, `whoami`, `profiles list`, and `profiles use`\n- `workspaces list`, `workspaces use`, `link`, `unlink`, and `init`\n- `apps list`, `environments create`, `environments list`, `environments get`, `environments wait`, and `environments delete`\n- `connect`, `snapshots create`, `snapshots list`, `snapshots get`, `snapshots delete`, `requests list`, and `requests get`\n- `run -- <command>`, `env pull`, `api`, `doctor`, `version`, `help`, and `completion`\n\n## Concepts\n\n- Environment: one disposable run of one or more apps. `const env = await sm.environments.create({ apps: ['salesforce'] })` creates it and waits until it is `running`. Use `{ wait: false }` to retain the ID before startup. Wait for `running` inside `try`, then delete the owned environment in `finally`, including after a wait failure.\n- Apps: a string names the app by its ID, and the app's name in the environment is the same string. Use `{ appId: 'salesforce', name: 'crm', setup }` for your own name, for two copies of one app, or for the app's setup. Later calls take the name.\n- App version: `sm.apps.versions(appId)` lists an app's versions. Read the default one before you use the app: each service has a `visibility`, `limitations`, a `prompt` and, for MCP, `servers` with their `tools`. `actors` lists users of the app's seed, for display. They do not say who can act.\n- Credentials: `const crm = await env.connect('salesforce')` returns `token`, `services` and `fetch`. Each service has an `id`, a `url` and the `headers` its requests need. The credentials always list each `public` service of the app's version. Pass `{ services: [id] }` to `connect()` to also list a service by its ID. A `preview` service is listed only when named. `crm.service('api')` returns the `api` service, which is the app URL. `crm.fetch(path)` joins `path` to the `api` service URL and adds its headers, which carry `Authorization: Bearer` with `token`. It throws when the credentials list no `api` service, as for Linear, whose clients call its `mcp` service. `crm.service('mcp').server(name)` returns one MCP server's `url` and `headers` for an MCP client. On an environment created with `tokenInUrl: true`, an MCP request without `Authorization` acts as the app's default user. For Salesforce SOAP, also put `crm.token` in the envelope's `SessionHeader/sessionId`. `fetch` never sends your API key, never follows redirects and never retries. `token` and each service's `headers` are secrets. On a `tokenInUrl` environment, each service and MCP server `url` carries the token and is a secret too.\n- Actor: the user of the app that a call acts as. Requests run with that user's permissions. `connect()` acts as the app's default user unless you pass `as`: the email, username or ID of any user who can sign in to the running app, such as `env.connect('salesforce', { as: 'alice.park@acme.example' })`. When the app's data came from an import whose `actors` names users for the app, the default user is the first of them. Otherwise it is the app's administrator. `credentials.actor` names that user, with the app's own `id`, such as a Salesforce `005…` ID. `env.actors(appName, { search })` pages through the users who can act, the default user first. `env.connectAll({ as })` connects to every app as one person: `credentials` holds each app that lets the user act, by app name, and `errors` holds the `ActorNotFoundError` or `ActorCannotSignInError` of each other app. Any other error throws. A user that the app does not have throws `ActorNotFoundError`, whose `nearest` lists the nearest users who can act. A user that the app does not let sign in throws `ActorCannotSignInError`.\n- Snapshot: the saved data of every app of a running environment, taken at one moment, or of an import. `await env.snapshots.create({ name: 'seeded' })` waits until it is `ready`. `sm.snapshots.create({ importId })` builds one from a `ready` import, and each app's `load` counts the records that did not load and lists the first of them. `sm.snapshots.create({ import: data })` takes the import as one object, the manifest with each app's `records` by object, then uploads it and builds the snapshot in one call. A problem at line N of `<app>/records/<Object>.jsonl` names `data.apps.<app>.records.<Object>[N - 1]`. `sm.environments.create({ snapshotId })` creates a new environment from it, in place of `apps`; each app keeps its name and version. A delete during the snapshot fails it with `interrupted`.\n- Recorded requests: `sm.requests.list({ environmentId })` pages the calls made to the environment's apps. `sm.requests.get(id)` adds headers and a preview of each body. `sm.requests.body(id, 'request')` or `'response'` returns the full body as bytes. Recording is best effort.\n- YAML: `parseEnvironmentYaml(text)` turns a YAML document with the fields of `create` into its input.\n- Pagination: every `list` returns one page, `{ data, nextCursor }`. Pass `nextCursor` back as `cursor` until it is `null`.\n\n## Statuses\n\nEnums are open: handle values you don't recognise.\n\n- Environment `status`: `starting`, `running`, `failed`, `deleted`. A `deleted` environment has a `reason` when State Machines deleted it: `expired`, `failed` (it never reached `running`) or `cancelled` (staff ended it). The `reason` is `null` when a caller deleted it. When the environment failed, `failure` is `{ appName, code, message }`, and `code` is `start_failed`, `pause_removed`, `version_unavailable`, `invalid_setup` or `invalid_services`. For `invalid_setup` and `invalid_services`, the app refused a value in `setup` or in `services`, and `message` starts with its path.\n- Snapshot `status`: `saving`, `ready`, `failed`, `deleted`. A failed snapshot has `failure.code` `interrupted`, `internal_error` or `invalid_import`. For `invalid_import`, an app refused the import that the snapshot was built from: `failure.appName` names the app, and `failure.problems` lists each problem.\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, with the problems of a refused import in `problems`, and `UnexpectedStatusError` when it was deleted. With `{ import }`, it throws `ImportInvalidError`, with the problems in `problems`, when the check leaves the uploaded import `invalid`, and it creates no snapshot.\n- `sm.environments.wait(id, { status })` and `sm.snapshots.wait(id, { status })` wait for any status, and throw once it cannot be reached without another call.\n\n## Limits\n\nWhen `sm.me().limits` is non-null:\n\n- Running environments are capped per organization: `(await sm.me()).limits.concurrentEnvironments` counts `starting` and `running`.\n- `timeLimitMinutes` has a default and a maximum: `(await sm.me()).limits.timeLimitMinutes`. It counts `starting` and `running` only; at the end the environment is deleted with `reason: 'expired'`.\n\n## Errors and retries\n\nA failed API call throws `StateMachinesError` with `status`, `code`, `message`, `requestId`, `fields` and `retryAfterMs`. The SDK retries `502`, `503`, `504` and connection errors itself, until a deadline; `create` retries reuse one idempotency key. It retries nothing else.\n\n| Code | Status | What to do |\n| --- | --- | --- |\n| `invalid_request` | 400 | Read `fields`, keyed by the dotted path of each wrong field. |\n| `unauthenticated` | 401 | The key is missing, expired or revoked. |\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. A snapshot needs a running environment or a `ready` import. |\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| `name_taken`, `last_owner`, `last_workspace`, `version_in_use`, `ui_disabled` | 409 | A workspace, member, version or UI session call conflicts with current state. Read `message`. |\n| `actor_cannot_sign_in` | 409 | The app does not let the user sign in now, such as an inactive user. `fields.as` gives the reason. Do not act as another user to make the test pass. |\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| `actor_not_found` | 422 | The app has no user with that email, username or ID. `err.nearest` lists the nearest users who can act. Check the name with `env.actors(appName, { search })`. |\n| `environment_limit_reached` | 429 | Delete only this task’s known leftovers, or ask the user to free capacity. |\n| `rate_limited` | 429 | Wait `retryAfterMs` when it is set, then retry. |\n| `internal_error` | 500 | Report `requestId`. |\n| `unavailable` | 503 | Retried automatically. If `create` still has an unknown outcome, replay the same input with the key retained before the original call. Do not generate a new key. |\n\n## Errors from the app URL\n\n`crm.fetch` returns these as responses with the same JSON envelope, and never retries them. Assert the expected status and result. For writes, read back the stored result. Never blindly retry a write whose outcome is unknown.\n\n| Code | Status | What to do |\n| --- | --- | --- |\n| `environment_not_running` | 409 | The environment is failed or deleted; `error.status` names it. Create another environment. |\n| `environment_starting` | 503 | For reads, wait `Retry-After` and retry. Check stored state before retrying an uncertain write. |\n| `environment_unavailable` | 503 | The app is restarting. For reads, wait `Retry-After`. Check stored state before retrying an uncertain write. |\n| `response_too_large` | 502 | The response is over the size limit. Ask for less, for example with a smaller page. |\n| `invalid_setup` | 503 | The app refused its `setup` after the environment started, such as in a newer version. `error.message` starts with the path of the value. Create another environment with a `setup` that the app accepts. |\n| `invalid_services` | 503 | The app refused a value in `services` after the environment started. `error.message` starts with the path of the value. Create another environment with `services` that the app accepts. |\n| `unsupported_protocol` | 501 | The app does not serve this path, method or content type, or the request body is compressed. |\n\n## Quickstart\n\nUse the project's TypeScript runner with its secret loader, or pass that runner through `npx @usestatemachines/cli@0.1.1 run -- <command>`. For an empty Node.js project, save as `state-quickstart.mts` and run `npx @usestatemachines/cli@0.1.1 run -- npx tsx state-quickstart.mts`. The check prints the response status and deletes the environment. Only after it prints `200`, report verified setup and the rerun command. Continue only with an explicitly supplied task. Otherwise, ask what to run next and wait with no setup environment left running.\n\n```ts\nimport { StateMachines } from '@usestatemachines/sdk';\n\nconst sm = new StateMachines(); // reads STATEMACHINES_API_KEY\nconst env = await sm.environments.create(\n { apps: ['salesforce'] },\n { wait: false },\n);\n\ntry {\n await sm.environments.wait(env.id, { status: 'running' });\n const salesforce = await env.connect('salesforce');\n const res = await salesforce.fetch('/services/data/v67.0/limits');\n console.log(res.status, await res.json());\n} finally {\n await env.delete();\n}\n```\n\n## Docs\n\n- [Documentation](https://app.usestatemachines.com/docs): guides, concepts and reference ([llms.txt](https://app.usestatemachines.com/docs/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\nFetch a docs page as Markdown with the API key in the `Authorization` header, for example `curl -fsS -H \"Authorization: Bearer $STATEMACHINES_API_KEY\" https://app.usestatemachines.com/docs/sdk-api/client.md`. Never print the key.\n";
|
|
8
8
|
|
|
9
9
|
export const skill =
|
|
10
|
-
"---\nname: state-machines\ndescription: Use when setting up State Machines or running code, integration tests, MCP clients, or API experiments against disposable replicas of SaaS apps such as Salesforce, Linear and Box. Applies to @usestatemachines/sdk and STATEMACHINES_API_KEY, including environment creation, actors, MCP servers, seeding, snapshots, cleanup and recovery.\n---\n\n## Start with the user's task\n\nDuring skill installation, read the repository instructions and preserve its existing structure. Do not create a new project, install project dependencies, write examples or create remote resources. Report the installed skill location and any reload requirement, then ask \"What would you like to run or test?\" and wait. Skill installation does not verify a connection.\n\nOnce the user chooses a task, inspect the project's package manager, runtime, runner and secret loader. Install or reuse a compatible `@usestatemachines/sdk` only when the task needs it. Read the installed README for API signatures and `package.json` for runtime requirements. The SDK is ESM and runs on Node.js and Cloudflare Workers, not in a browser. Follow the user's task and existing project conventions without requiring a separate onboarding example.\n\n## Get a credential\n\nThe SDK reads `STATEMACHINES_API_KEY` from the process environment. It does not load `.env` files. Use the project's existing test command or secret loader. An empty variable in your current shell does not prove that the test process lacks it. Verify presence without displaying the value. In Workers, pass the binding explicitly with `new StateMachines({ apiKey: env.STATEMACHINES_API_KEY })`.\n\nIf the project's existing command loads `STATEMACHINES_API_KEY`, keep using that command and secret loader. Keep the same credential loader, CLI profile and API origin across setup commands. Call `sm.me()` in that process to verify access and workspace scope. `me.limits` can be null.\n\nOtherwise, run `npx @usestatemachines/cli@0.1.1 whoami --json`. Confirm the returned account and organization match the task, then use the workspace commands below. If the CLI credential is missing, expired or revoked, complete this login:\n\n1. Run `npx @usestatemachines/cli@0.1.1 login --no-wait --json`. For an expired or revoked saved session, add `--yes` to replace that invalid session in the same profile. Do not replace a valid session for a different account.\n2. Show the returned `verificationUrl` and `userCode`. Ask the user to approve only the login you just started. A matching code does not identify the computer requesting access. Wait for the user to approve the login.\n3. After that approval, run `npx @usestatemachines/cli@0.1.1 login --resume --json` with the same profile used to start the login. Resume that attempt instead of starting another login. If it expired, offer a new login.\n\nIf a pending login's approval URL or code is unavailable, ask before recovering it. Explain that recovery may finish an already-approved login. After confirmation, run `npx @usestatemachines/cli@0.1.1 login --resume --json --stream` with the same profile and API origin. Keep that process running and show its URL and code. Request approval only for a recognized login. Wait for `signed-in` before continuing setup. Do not start another login or resume process. If streaming output is unavailable, report the blocker without reading credential files.\n\nIf a command returns `cli.not_linked`, run `npx @usestatemachines/cli@0.1.1 workspaces list --json`. Ask the user which workspace to use when there is more than one. Run `npx @usestatemachines/cli@0.1.1 link --workspace <workspace-id> --json` with the selected ID.\n\nRun the user's existing command with `npx @usestatemachines/cli@0.1.1 run -- <command>`. The child receives a workspace key. The CLI keeps the credential out of its output.\n\nIf access is denied, the API origin differs, or the workspace differs from the task, report the blocker. Do not invite or remove members, or change permissions.\n\nNever print, log or commit API keys, `credentials.token`, the `headers` of `credentials.services`, environment dumps, or secret-file contents. On a `tokenInUrl` environment, every service and MCP server `url` carries the token, so never print, log or commit those URLs either. Use synthetic test data.\n\n## Read the app before you use it\n\nApply the rest of this skill once the user has chosen a task. `sm.apps.list()` returns app IDs and each app's `defaultVersionId`. Every list returns `{ data, nextCursor }`. Follow cursors until you find the entry or `nextCursor` is null.\n\nRead the app's default version from `sm.apps.versions(appId)` before you write code. It is the current guidance for that app. Each of its `services` has an `id`, a `visibility`, `limitations` and a `prompt`, and a service that serves MCP lists its `servers` with their `tools`. Its `actors` are the app's users, each with an `id`, a `name` and a `summary`. Check the `limitations` against the behavior under test, as [known differences](https://app.usestatemachines.com/docs/apps/known-differences.md) does for Salesforce. A replica does not import a customer's data or run arbitrary customer automation.\n\n- Salesforce: the `api` service serves REST, Bulk API 2.0 and SOAP. Its `mcp` service is `preview`.\n- Box: the `api` service serves the Box Platform API, uploads and OAuth. Its `mcp` service is `preview`.\n- Linear: clients call the `mcp` service and its MCP server `linear`. Linear lists no `api` service. Its `ui` service is `preview`.\n\n## Create and connect\n\nCreate only the apps the task needs. A string such as `'linear'` is both the app ID and the app's name in the environment. Use `{ appId, name, setup, services }` to choose a name, run two copies of one app, pass `setup`, or configure a service. For example, `services` sets which MCP servers and tools the `mcp` service serves. Later calls take the name. Set `tokenInUrl: true` only for a client that cannot send headers.\n\n`env.connect(name)` acts as the app's default actor and lists each `public` service. Pass `{ services: ['mcp'] }` to also list a `preview` service. To act as another user, pass an `actorId` from the `actors` of the exact version that `env.apps` names for the app. Never use a role label, a workspace member ID, or the first version in a list.\n\n- `credentials.fetch(path)` calls the `api` service with its headers and never follows redirects. It throws when the credentials list no `api` service, as for Linear.\n- For MCP, `credentials.service('mcp').servers` lists the MCP servers, and `.server(name)` returns one server's `url` and `headers` to give the MCP client.\n- An external client must send a service's `headers` only to that service's `url`, with redirects off. For Salesforce SOAP, put the actor token in `SessionHeader/sessionId`. See [credentials](https://app.usestatemachines.com/docs/environments/credentials.md).\n- On a `tokenInUrl` environment, a Salesforce or Box MCP request without an `Authorization` header acts as the default actor. To act as `actorId`, send the service's headers. Linear refuses an MCP request without them, so a Linear MCP client always sends the `mcp` service's headers.\n\nPoint the existing integration's test transport at these URLs and credentials. If that needs a broader change, explain it before you expand the task.\n\n## Seed your own data\n\nCreate the data a test reads before the test runs. Write it through the app's own API or MCP tools as the actor that owns it: Salesforce REST, composite or Bulk API 2.0, Linear MCP create tools, or Box uploads. A Linear workspace starts with one team and no issues. Use `setup` only for identity and configuration. To reuse seeded data, save it with `env.snapshots.create({ name })`, then create each test's environment from `{ snapshotId }`. See [isolated tests](https://app.usestatemachines.com/docs/sdk-api/testing.md).\n\n## Verify the result\n\nAssert the expected status and result. For writes, read the stored data back and assert the relevant fields. For negative tests, assert the expected error. Do not weaken an assertion to make unsupported behavior pass. A successful standalone SDK request does not prove that the integration used the replica. Check its calls with `sm.requests.list({ environmentId })`.\n\n## Own the full lifecycle\n\nCreate with `{ wait: false }` to keep the accepted environment ID. Enter `try` immediately, then wait for `running`, connect and run the check inside it. Delete in `finally`, also after a startup failure, a timeout or a failed assertion. An aborted wait does not cancel the work that the API accepted, so clean up with a fresh, un-aborted signal. Keep both verification and cleanup errors, and report the environment ID if deletion fails.\n\nDelete only resources created for this task, never unrelated environments or a user-supplied snapshot. A snapshot in `saving` cannot be deleted yet. Keep its ID and wait for `ready` or `failed` first. In Cloudflare Workers, pass the key explicitly and keep the environment ID in the job's state. A request ending does not cancel startup.\n\nThe SDK retries management connection errors and `502`, `503` and `504` with one idempotency key per create call. To recover across attempts or a restart, keep the input and an explicit `idempotencyKey` before creation. Replay with the same input, key, workspace and caller. Do not create a replacement with a new key while the first outcome is unknown. See [operation recovery](https://app.usestatemachines.com/docs/sdk-api/recovery.md).\n\nWhen `sm.me().limits` is non-null, it gives the cap on concurrent environments and the default and maximum `timeLimitMinutes`. The time limit covers starting and running.\n\n## Optional connection check\n\nRun this only when the user asks to verify the connection. It proves the connection and cleanup, not an existing integration's behavior. Save it in the project's examples or test location. Use the existing TypeScript runner and secret loader, or pass that runner through `npx @usestatemachines/cli@0.1.1 run -- <command>`. For an empty Node.js project, save `state-quickstart.mts` and run `npx @usestatemachines/cli@0.1.1 run -- npx tsx state-quickstart.mts`.\n\n```ts\nimport { StateMachines } from '@usestatemachines/sdk';\n\nconst sm = new StateMachines(); // reads STATEMACHINES_API_KEY\nconst env = await sm.environments.create(\n { apps: ['salesforce'] },\n { wait: false },\n);\n\ntry {\n await sm.environments.wait(env.id, { status: 'running' });\n const salesforce = await env.connect('salesforce');\n const res = await salesforce.fetch('/services/data/v67.0/limits');\n console.log(res.status, await res.json());\n} finally {\n await env.delete();\n}\n```\n\n## Recover without changing the test's meaning\n\nManagement failures throw SDK errors. App HTTP failures are returned as `Response` objects. Use `instanceof` with the SDK error classes. Read the State Machines error code in `error.code` and tell it apart from an app's own error.\n\n| Failure | Next action |\n| --- | --- |\n| `unauthenticated` | The key is missing, expired or revoked. Fix the credential in the process that runs the task. |\n| `invalid_request` | Read `err.fields` and the installed method signature. |\n| `version_unavailable` | Discover the app ID and available versions. Stop if the required version is unavailable. |\n| `environment_limit_reached` | Clean up only this task's known leftovers, or report the capacity blocker. |\n| `EnvironmentFailedError` | Read `err.environment.failure.code`: `start_failed`, `pause_removed`, `version_unavailable`, `invalid_setup` or `invalid_services`. Clean up the owned environment. |\n| `WaitTimeoutError` or abort | Use the kept environment ID. A non-null `WaitTimeoutError.resource` also carries it. If neither an ID nor a replay key is available, report unresolved cleanup. Do not guess from a list. |\n| Unknown create outcome | Replay the kept input and idempotency key. Do not start another operation with a new key. |\n| `invalid_status` | Read the current resource status before you choose the next call. |\n| `snapshot_in_progress` | Wait for this environment's saving snapshot before you create another. |\n| `SnapshotFailedError` | Report the failure. Retry only if the task still needs a snapshot and the source environment is running. |\n| App `environment_not_running` | Inspect the environment. Do not silently switch the integration to another target. |\n| App `invalid_setup` or `invalid_services` | The app refused a value of `setup` or `services`. `error.message` starts with its path. Create another environment with an accepted value, and delete the owned one. |\n| App or gateway `503` | For a read, honor `Retry-After`. For a write with an uncertain outcome, inspect stored state or report the uncertainty before you retry. |\n| Cleanup failure | Keep the task result, report the owned resource ID and retry deletion with a fresh signal. Do not report cleanup as successful. |\n\n## Finish with evidence\n\nLeave the smallest rerunnable test or example. Report the behavior checked, command, assertion result, SDK and app versions, selected actor, resource IDs without secrets, and cleanup outcome. Say when execution was blocked or only typechecking ran. A passing replica check establishes the asserted behavior for that version and actor, not full production compatibility.\n\nFind other guides through https://app.usestatemachines.com/docs/llms.txt. Read focused pages before the full documentation bundle. This dashboard also serves an agent index at https://app.usestatemachines.com/llms.txt. Fetch a docs page as Markdown with the API key in the `Authorization` header, for example `curl -fsS -H \"Authorization: Bearer $STATEMACHINES_API_KEY\" https://app.usestatemachines.com/docs/sdk-api/client.md`. Never print the key.\n";
|
|
10
|
+
"---\nname: state-machines\ndescription: Use when setting up State Machines or running code, integration tests, MCP clients, or API experiments against disposable replicas of SaaS apps such as Salesforce, Linear and Box. Applies to @usestatemachines/sdk and STATEMACHINES_API_KEY, including environment creation, actors, MCP servers, seeding, snapshots, cleanup and recovery.\n---\n\n## Start with the user's task\n\nDuring skill installation, read the repository instructions and preserve its existing structure. Do not create a new project, install project dependencies, write examples or create remote resources. Report the installed skill location and any reload requirement, then ask \"What would you like to run or test?\" and wait. Skill installation does not verify a connection.\n\nOnce the user chooses a task, inspect the project's package manager, runtime, runner and secret loader. Install or reuse a compatible `@usestatemachines/sdk` only when the task needs it. Read the installed README for API signatures and `package.json` for runtime requirements. The SDK is ESM and runs on Node.js and Cloudflare Workers, not in a browser. Follow the user's task and existing project conventions without requiring a separate onboarding example.\n\n## Get a credential\n\nThe SDK reads `STATEMACHINES_API_KEY` from the process environment. It does not load `.env` files. Use the project's existing test command or secret loader. An empty variable in your current shell does not prove that the test process lacks it. Verify presence without displaying the value. In Workers, pass the binding explicitly with `new StateMachines({ apiKey: env.STATEMACHINES_API_KEY })`.\n\nIf the project's existing command loads `STATEMACHINES_API_KEY`, keep using that command and secret loader. Keep the same credential loader, CLI profile and API origin across setup commands. Call `sm.me()` in that process to verify access and workspace scope. `me.limits` can be null.\n\nOtherwise, run `npx @usestatemachines/cli@0.1.1 whoami --json`. Confirm the returned account and organization match the task, then use the workspace commands below. If the CLI credential is missing, expired or revoked, complete this login:\n\n1. Run `npx @usestatemachines/cli@0.1.1 login --no-wait --json`. For an expired or revoked saved session, add `--yes` to replace that invalid session in the same profile. Do not replace a valid session for a different account.\n2. Show the returned `verificationUrl` and `userCode`. Ask the user to approve only the login you just started. A matching code does not identify the computer requesting access. Wait for the user to approve the login.\n3. After that approval, run `npx @usestatemachines/cli@0.1.1 login --resume --json` with the same profile used to start the login. Resume that attempt instead of starting another login. If it expired, offer a new login.\n\nIf a pending login's approval URL or code is unavailable, ask before recovering it. Explain that recovery may finish an already-approved login. After confirmation, run `npx @usestatemachines/cli@0.1.1 login --resume --json --stream` with the same profile and API origin. Keep that process running and show its URL and code. Request approval only for a recognized login. Wait for `signed-in` before continuing setup. Do not start another login or resume process. If streaming output is unavailable, report the blocker without reading credential files.\n\nIf a command returns `cli.not_linked`, run `npx @usestatemachines/cli@0.1.1 workspaces list --json`. Ask the user which workspace to use when there is more than one. Run `npx @usestatemachines/cli@0.1.1 link --workspace <workspace-id> --json` with the selected ID.\n\nRun the user's existing command with `npx @usestatemachines/cli@0.1.1 run -- <command>`. The child receives a workspace key. The CLI keeps the credential out of its output.\n\nIf access is denied, the API origin differs, or the workspace differs from the task, report the blocker. Do not invite or remove members, or change permissions.\n\nNever print, log or commit API keys, `credentials.token`, the `headers` of `credentials.services`, environment dumps, or secret-file contents. On a `tokenInUrl` environment, every service and MCP server `url` carries the token, so never print, log or commit those URLs either. Use synthetic test data.\n\n## Read the app before you use it\n\nApply the rest of this skill once the user has chosen a task. `sm.apps.list()` returns app IDs and each app's `defaultVersionId`. Every list returns `{ data, nextCursor }`. Follow cursors until you find the entry or `nextCursor` is null.\n\nRead the app's default version from `sm.apps.versions(appId)` before you write code. It is the current guidance for that app. Each of its `services` has an `id`, a `visibility`, `limitations` and a `prompt`, and a service that serves MCP lists its `servers` with their `tools`. Its `actors` are users of the app's seed, for display. They do not say who can act. Check the `limitations` against the behavior under test, as [known differences](https://app.usestatemachines.com/docs/apps/known-differences.md) does for Salesforce. A replica never calls the vendor and does not run arbitrary customer automation. An environment holds a customer's org data only when it starts from a snapshot created from an import of that data.\n\n- Salesforce: the `api` service serves REST, Bulk API 2.0 and SOAP. Its `mcp` service is `preview`.\n- Box: the `api` service serves the Box Platform API, uploads and OAuth. Its `mcp` service is `preview`.\n- Linear: clients call the `mcp` service and its MCP server `linear`. Linear lists no `api` service. Its `ui` service is `preview`.\n\n## Create and connect\n\nCreate only the apps the task needs. A string such as `'linear'` is both the app ID and the app's name in the environment. Use `{ appId, name, setup, services }` to choose a name, run two copies of one app, pass `setup`, or configure a service. For example, `services` sets which MCP servers and tools the `mcp` service serves. Later calls take the name. Set `tokenInUrl: true` only for a client that cannot send headers.\n\n`env.connect(name)` acts as the app's default user and lists each `public` service. Pass `{ services: ['mcp'] }` to also list a `preview` service. To act as another user of the app, pass `as` with the user's email, username or ID, such as `env.connect('salesforce', { as: 'alice.park@acme.example' })`. Any user who can sign in to the running app can act, including a user that the test created. `credentials.actor` names the user that the credentials act as. Never pass a role label or a workspace member ID.\n\n- `env.actors(name, { search })` pages through the users who can act, the default user first. Use it to find a user when the task does not name one by email, username or ID.\n- `env.connectAll({ as })` connects to every app of the environment as one person. Its `credentials` hold each app that lets the user act, by app name. Its `errors` hold the `ActorNotFoundError` or `ActorCannotSignInError` of each other app. Any other error throws.\n- A user that the app does not have throws `ActorNotFoundError`, and `err.nearest` lists the nearest users who can act. A user that the app does not let sign in now, such as an inactive user, throws `ActorCannotSignInError`.\n- `credentials.fetch(path)` calls the `api` service with its headers and never follows redirects. It throws when the credentials list no `api` service, as for Linear.\n- For MCP, `credentials.service('mcp').servers` lists the MCP servers, and `.server(name)` returns one server's `url` and `headers` to give the MCP client.\n- An external client must send a service's `headers` only to that service's `url`, with redirects off. For Salesforce SOAP, put the actor token in `SessionHeader/sessionId`. See [credentials](https://app.usestatemachines.com/docs/environments/credentials.md).\n- On a `tokenInUrl` environment, an MCP request without an `Authorization` header acts as the app's default user. To act as the user of the credentials, send the service's headers.\n\nPoint the existing integration's test transport at these URLs and credentials. If that needs a broader change, explain it before you expand the task.\n\n## Seed your own data\n\nCreate the data a test reads before the test runs. Write it through the app's own API or MCP tools as the actor that owns it: Salesforce REST, composite or Bulk API 2.0, Linear MCP create tools, or Box uploads. A Linear workspace starts with one team and no issues. Use `setup` only for identity and configuration. To start from records, users and files that you write in one file, build a snapshot from an import, as [build a snapshot from an import](https://app.usestatemachines.com/docs/snapshots/import.md) shows. To reuse seeded data, save it with `env.snapshots.create({ name })`, then create each test's environment from `{ snapshotId }`. See [isolated tests](https://app.usestatemachines.com/docs/sdk-api/testing.md).\n\n## Verify the result\n\nAssert the expected status and result. For writes, read the stored data back and assert the relevant fields. For negative tests, assert the expected error. Do not weaken an assertion to make unsupported behavior pass. A successful standalone SDK request does not prove that the integration used the replica. Check its calls with `sm.requests.list({ environmentId })`.\n\n## Own the full lifecycle\n\nCreate with `{ wait: false }` to keep the accepted environment ID. Enter `try` immediately, then wait for `running`, connect and run the check inside it. Delete in `finally`, also after a startup failure, a timeout or a failed assertion. An aborted wait does not cancel the work that the API accepted, so clean up with a fresh, un-aborted signal. Keep both verification and cleanup errors, and report the environment ID if deletion fails.\n\nDelete only resources created for this task, never unrelated environments or a user-supplied snapshot. A snapshot in `saving` cannot be deleted yet. Keep its ID and wait for `ready` or `failed` first. In Cloudflare Workers, pass the key explicitly and keep the environment ID in the job's state. A request ending does not cancel startup.\n\nThe SDK retries management connection errors and `502`, `503` and `504` with one idempotency key per create call. To recover across attempts or a restart, keep the input and an explicit `idempotencyKey` before creation. Replay with the same input, key, workspace and caller. Do not create a replacement with a new key while the first outcome is unknown. See [operation recovery](https://app.usestatemachines.com/docs/sdk-api/recovery.md).\n\nWhen `sm.me().limits` is non-null, it gives the cap on concurrent environments and the default and maximum `timeLimitMinutes`. The time limit covers starting and running.\n\n## Optional connection check\n\nRun this only when the user asks to verify the connection. It proves the connection and cleanup, not an existing integration's behavior. Save it in the project's examples or test location. Use the existing TypeScript runner and secret loader, or pass that runner through `npx @usestatemachines/cli@0.1.1 run -- <command>`. For an empty Node.js project, save `state-quickstart.mts` and run `npx @usestatemachines/cli@0.1.1 run -- npx tsx state-quickstart.mts`.\n\n```ts\nimport { StateMachines } from '@usestatemachines/sdk';\n\nconst sm = new StateMachines(); // reads STATEMACHINES_API_KEY\nconst env = await sm.environments.create(\n { apps: ['salesforce'] },\n { wait: false },\n);\n\ntry {\n await sm.environments.wait(env.id, { status: 'running' });\n const salesforce = await env.connect('salesforce');\n const res = await salesforce.fetch('/services/data/v67.0/limits');\n console.log(res.status, await res.json());\n} finally {\n await env.delete();\n}\n```\n\n## Recover without changing the test's meaning\n\nManagement failures throw SDK errors. App HTTP failures are returned as `Response` objects. Use `instanceof` with the SDK error classes. Read the State Machines error code in `error.code` and tell it apart from an app's own error.\n\n| Failure | Next action |\n| --- | --- |\n| `unauthenticated` | The key is missing, expired or revoked. Fix the credential in the process that runs the task. |\n| `invalid_request` | Read `err.fields` and the installed method signature. |\n| `version_unavailable` | Discover the app ID and available versions. Stop if the required version is unavailable. |\n| `ActorNotFoundError` | The app has no user with that email, username or ID. Check `err.nearest` or `env.actors(appName, { search })`. Act as another user only when the task allows it. |\n| `ActorCannotSignInError` | The app does not let the user sign in now, for the reason in `err.fields.as`. Report it. Do not act as another user to make the test pass. |\n| `environment_limit_reached` | Clean up only this task's known leftovers, or report the capacity blocker. |\n| `EnvironmentFailedError` | Read `err.environment.failure.code`: `start_failed`, `pause_removed`, `version_unavailable`, `invalid_setup` or `invalid_services`. Clean up the owned environment. |\n| `WaitTimeoutError` or abort | Use the kept environment ID. A non-null `WaitTimeoutError.resource` also carries it. If neither an ID nor a replay key is available, report unresolved cleanup. Do not guess from a list. |\n| Unknown create outcome | Replay the kept input and idempotency key. Do not start another operation with a new key. |\n| `invalid_status` | Read the current resource status before you choose the next call. |\n| `snapshot_in_progress` | Wait for this environment's saving snapshot before you create another. |\n| `SnapshotFailedError` | Report the failure. Retry only if the task still needs a snapshot and the source environment is running. |\n| App `environment_not_running` | Inspect the environment. Do not silently switch the integration to another target. |\n| App `invalid_setup` or `invalid_services` | The app refused a value of `setup` or `services`. `error.message` starts with its path. Create another environment with an accepted value, and delete the owned one. |\n| App or gateway `503` | For a read, honor `Retry-After`. For a write with an uncertain outcome, inspect stored state or report the uncertainty before you retry. |\n| Cleanup failure | Keep the task result, report the owned resource ID and retry deletion with a fresh signal. Do not report cleanup as successful. |\n\n## Finish with evidence\n\nLeave the smallest rerunnable test or example. Report the behavior checked, command, assertion result, SDK and app versions, selected actor, resource IDs without secrets, and cleanup outcome. Say when execution was blocked or only typechecking ran. A passing replica check establishes the asserted behavior for that version and actor, not full production compatibility.\n\nFind other guides through https://app.usestatemachines.com/docs/llms.txt. Read focused pages before the full documentation bundle. This dashboard also serves an agent index at https://app.usestatemachines.com/llms.txt. Fetch a docs page as Markdown with the API key in the `Authorization` header, for example `curl -fsS -H \"Authorization: Bearer $STATEMACHINES_API_KEY\" https://app.usestatemachines.com/docs/sdk-api/client.md`. Never print the key.\n";
|
|
11
11
|
|
|
12
12
|
export const quickstart =
|
|
13
13
|
"import { StateMachines } from '@usestatemachines/sdk';\n\nconst sm = new StateMachines(); // reads STATEMACHINES_API_KEY\nconst env = await sm.environments.create(\n { apps: ['salesforce'] },\n { wait: false },\n);\n\ntry {\n await sm.environments.wait(env.id, { status: 'running' });\n const salesforce = await env.connect('salesforce');\n const res = await salesforce.fetch('/services/data/v67.0/limits');\n console.log(res.status, await res.json());\n} finally {\n await env.delete();\n}\n";
|
|
@@ -18,7 +18,7 @@ export const templates = Object.freeze({
|
|
|
18
18
|
prompt:
|
|
19
19
|
"Set up State Machines in this existing repository so I can use disposable app environments with my coding agent.\n\n1. Read the repository instructions and inspect the existing State Machines workspace link and agent skill. Keep the repository's structure and conventions. Check that Node.js meets the CLI's runtime requirement. Use the pinned npx command below. A global CLI installation is not required.\n2. Run `npx @usestatemachines/cli@0.1.1 whoami --json`. If the project already has a `STATEMACHINES_API_KEY` loader, run the check through that loader without reading or displaying the secret. Use the same credential loader, CLI profile and API origin for every setup command below, including workspace discovery and init. Reuse valid access and confirm the returned account and organization match my task. If the existing API key or CLI profile works, skip sign-in and reuse it for setup. Do not create a replacement API key. Report an access denial or origin mismatch instead of changing accounts or permissions.\n3. Only when no project API key is configured and the access check reports a missing, expired or revoked CLI credential, run `npx @usestatemachines/cli@0.1.1 login --no-wait --json`. For an expired or revoked saved session, add `--yes` to replace that invalid session in the same profile. Do not replace a valid session for a different account. Show me the returned verification URL and code. Wait for my approval before resuming the same login with `npx @usestatemachines/cli@0.1.1 login --resume --json`, using the same profile. If a login is already pending, resume it after approval instead of starting another. If the request expires, offer a new login. Never ask me to paste a credential into chat, print it or commit it.\n If a pending login's approval URL or code is unavailable, ask before recovering it. Explain that recovery may finish an already-approved login. After confirmation, run `npx @usestatemachines/cli@0.1.1 login --resume --json --stream` with the same profile and API origin. Keep that process running and show its URL and code. Request approval only for a recognized login. Wait for `signed-in` before continuing setup. Do not start another login or resume process. If streaming output is unavailable, report the blocker without reading credential files.\n\n4. Run `npx @usestatemachines/cli@0.1.1 init --agent <this-agent> --yes`, choosing `claude` for Claude Code or `codex` for Codex. Reuse the repository's existing workspace link. If the CLI needs a workspace, run `npx @usestatemachines/cli@0.1.1 workspaces list --json`. Ask me which to use when more than one is available, then pass `--workspace <workspace-id>` to init. Do not replace a different workspace link or a customized skill without my approval. Read the installed skill and use its pinned npx commands for later work.\n5. Do not create a new project, install the SDK, write example code, change an integration or create remote resources during skill installation. Do not add `--sdk`, `--quickstart` or `--env` to init unless I ask for those changes.\n6. Report where the skill was installed, which workspace is linked, and whether the agent needs to reload it. Setup alone does not verify an integration. Ask: \"What would you like to run or test?\" Wait for my answer before making further changes.\n\nIf this agent is not Claude Code or Codex but can run commands and has `STATEMACHINES_API_KEY`, download {{origin}}/skill.md itself with `curl -fsS -H \"Authorization: Bearer $STATEMACHINES_API_KEY\"`. Never print the key. If this agent cannot fetch the file with that key, ask me to download {{origin}}/skill.md from my signed-in browser and provide the file. Do not request session cookies or credentials. Install that file in the agent's supported project-local skills directory, preserving any customized skill. If persistent skills are unsupported, read the file for this session and explain the limitation. Report setup as incomplete if the file is unavailable.\n",
|
|
20
20
|
llmsTxt:
|
|
21
|
-
"# State Machines\n\n> State Machines runs disposable environments of replica SaaS apps, such as Salesforce, Linear and Box, for tests and coding agents. Create, connect to, and delete them with the TypeScript SDK `@usestatemachines/sdk`.\n\nUse the project's package manager to install or reuse `@usestatemachines/sdk`. It is ESM only and runs on Node.js and Cloudflare Workers, not browsers. `engines` in its `package.json` gives the Node.js versions. TypeScript needs `lib: [\"DOM\"]` or `@types/node`. `new StateMachines()` reads the API key from `STATEMACHINES_API_KEY` and doesn't read `.env` files; in Cloudflare Workers, pass `apiKey` explicitly. Never print the key. The installed `node_modules/@usestatemachines/sdk/README.md` matches your version: read it before relying on memory. Read the bundled `agents/SKILL.md` for the task workflow. Reuse the project's secret loader and check the key in the process that runs the test, not just the current shell. Installing the skill does not require SDK installation, example files or remote resources. Ask what the user wants to run and wait. Install the SDK and verify access when their chosen task needs it. Run the connection check below only if the user asks to verify the connection. Do not choose a business workflow from the repository.\n\n## Get a credential\n\nIf the project's existing command loads `STATEMACHINES_API_KEY`, reuse that command and secret loader. Keep the same credential loader, CLI profile and API origin across setup commands. Otherwise, run `npx @usestatemachines/cli@0.1.1 whoami --json`. If it succeeds, confirm the account and organization match the task, then use the workspace commands below. If the CLI credential is missing, expired or revoked, start `npx @usestatemachines/cli@0.1.1 login --no-wait --json`. For an expired or revoked saved session, add `--yes` to replace that invalid session in the same profile. Do not replace a valid session for a different account. Show its `verificationUrl` and `userCode`, and wait for approval. A matching code does not identify the requesting computer. After that approval, run `npx @usestatemachines/cli@0.1.1 login --resume --json` with the same profile to finish that attempt. Resume an existing pending login after approval instead of creating another. Report access denial or an API origin mismatch without changing accounts or permissions.\n\nIf a pending login's approval URL or code is unavailable, ask before recovering it. Explain that recovery may finish an already-approved login. After confirmation, run `npx @usestatemachines/cli@0.1.1 login --resume --json --stream` with the same profile and API origin. Keep that process running and show its URL and code. Request approval only for a recognized login. Wait for `signed-in` before continuing setup. Do not start another login or resume process. If streaming output is unavailable, report the blocker without reading credential files.\n\nFor `cli.not_linked`, run `npx @usestatemachines/cli@0.1.1 workspaces list --json`. Ask which workspace to use when there is more than one, then run `npx @usestatemachines/cli@0.1.1 link --workspace <workspace-id> --json`. Run the user's existing code with `npx @usestatemachines/cli@0.1.1 run -- <command>`. Never print or commit a credential.\n\n## CLI commands\n\nPrefix these commands with `npx @usestatemachines/cli@0.1.1`. Use `help --json` for flags and result schemas:\n\n- `login`, `logout`, `whoami`, `profiles list`, and `profiles use`\n- `workspaces list`, `workspaces use`, `link`, `unlink`, and `init`\n- `apps list`, `environments create`, `environments list`, `environments get`, `environments wait`, and `environments delete`\n- `connect`, `snapshots create`, `snapshots list`, `snapshots get`, `snapshots delete`, `requests list`, and `requests get`\n- `run -- <command>`, `env pull`, `api`, `doctor`, `version`, `help`, and `completion`\n\n## Concepts\n\n- Environment: one disposable run of one or more apps. `const env = await sm.environments.create({ apps: ['salesforce'] })` creates it and waits until it is `running`. Use `{ wait: false }` to retain the ID before startup. Wait for `running` inside `try`, then delete the owned environment in `finally`, including after a wait failure.\n- Apps: a string names the app by its ID, and the app's name in the environment is the same string. Use `{ appId: 'salesforce', name: 'crm', setup }` for your own name, for two copies of one app, or for the app's setup. Later calls take the name.\n- App version: `sm.apps.versions(appId)` lists an app's versions. Read the default one before you use the app: each service has a `visibility`, `limitations`, a `prompt` and, for MCP, `servers` with their `tools`. `actors` lists the app's users with `id`, `name` and `summary`.\n- Credentials: `const crm = await env.connect('salesforce')` returns `token`, `services` and `fetch`. Each service has an `id`, a `url` and the `headers` its requests need. The credentials always list each `public` service of the app's version. Pass `{ services: [id] }` to `connect()` to also list a service by its ID. A `preview` service is listed only when named. `crm.service('api')` returns the `api` service, which is the app URL. `crm.fetch(path)` joins `path` to the `api` service URL and adds its headers, which carry `Authorization: Bearer` with `token`. It throws when the credentials list no `api` service, as for Linear, whose clients call its `mcp` service. `crm.service('mcp').server(name)` returns one MCP server's `url` and `headers` for an MCP client. On an environment created with `tokenInUrl: true`, a Salesforce or Box MCP request without `Authorization` acts as the default actor. Linear refuses an MCP request without `Authorization`, so a Linear MCP client always sends the `mcp` service's headers. For Salesforce SOAP, also put `crm.token` in the envelope's `SessionHeader/sessionId`. `fetch` never sends your API key, never follows redirects and never retries. `token` and each service's `headers` are secrets. On a `tokenInUrl` environment, each service and MCP server `url` carries the token and is a secret too.\n- Actor: one of the app's users; requests run with that user's permissions. `connect()` acts as the app's default actor (for Salesforce, the administrator) unless you pass `actorId`. Salesforce actor IDs are its user IDs, such as `005…`. Find the app by name in `env.apps`, then page through `sm.apps.versions(appId)` to find its exact `versionId`. Read actor IDs from that version's `actors`.\n- Snapshot: the saved data of every app of a running environment, taken at one moment, or of an import. `await env.snapshots.create({ name: 'seeded' })` waits until it is `ready`. `sm.snapshots.create({ importId })` builds one from a `ready` import, and each app's `load` counts the records that did not load and lists the first of them. `sm.environments.create({ snapshotId })` creates a new environment from it, in place of `apps`; each app keeps its name and version. A delete during the snapshot fails it with `interrupted`.\n- Recorded requests: `sm.requests.list({ environmentId })` pages the calls made to the environment's apps. `sm.requests.get(id)` adds headers and a preview of each body. `sm.requests.body(id, 'request')` or `'response'` returns the full body as bytes. Recording is best effort.\n- YAML: `parseEnvironmentYaml(text)` turns a YAML document with the fields of `create` into its input.\n- Pagination: every `list` returns one page, `{ data, nextCursor }`. Pass `nextCursor` back as `cursor` until it is `null`.\n\n## Statuses\n\nEnums are open: handle values you don't recognise.\n\n- Environment `status`: `starting`, `running`, `failed`, `deleted`. A `deleted` environment has a `reason` when State Machines deleted it: `expired`, `failed` (it never reached `running`) or `cancelled` (staff ended it). The `reason` is `null` when a caller deleted it. When the environment failed, `failure` is `{ appName, code, message }`, and `code` is `start_failed`, `pause_removed`, `version_unavailable`, `invalid_setup` or `invalid_services`. For `invalid_setup` and `invalid_services`, the app refused a value in `setup` or in `services`, and `message` starts with its path.\n- Snapshot `status`: `saving`, `ready`, `failed`, `deleted`. A failed snapshot has `failure.code` `interrupted`, `internal_error` or `invalid_import`. For `invalid_import`, an app refused the import that the snapshot was built from: `failure.appName` names the app, and `failure.problems` lists each problem.\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, with the problems of a refused import in `problems`, and `UnexpectedStatusError` when it was deleted.\n- `sm.environments.wait(id, { status })` and `sm.snapshots.wait(id, { status })` wait for any status, and throw once it cannot be reached without another call.\n\n## Limits\n\nWhen `sm.me().limits` is non-null:\n\n- Running environments are capped per organization: `(await sm.me()).limits.concurrentEnvironments` counts `starting` and `running`.\n- `timeLimitMinutes` has a default and a maximum: `(await sm.me()).limits.timeLimitMinutes`. It counts `starting` and `running` only; at the end the environment is deleted with `reason: 'expired'`.\n\n## Errors and retries\n\nA failed API call throws `StateMachinesError` with `status`, `code`, `message`, `requestId`, `fields` and `retryAfterMs`. The SDK retries `502`, `503`, `504` and connection errors itself, until a deadline; `create` retries reuse one idempotency key. It retries nothing else.\n\n| Code | Status | What to do |\n| --- | --- | --- |\n| `invalid_request` | 400 | Read `fields`, keyed by the dotted path of each wrong field. |\n| `unauthenticated` | 401 | The key is missing, expired or revoked. |\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. A snapshot needs a running environment or a `ready` import. |\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| `name_taken`, `last_owner`, `last_workspace`, `version_in_use`, `ui_disabled` | 409 | A workspace, member, version or UI session call conflicts with current state. Read `message`. |\n| `body_too_large` | 413 | The request body is over the API's size limit. Send less. |\n| `version_unavailable` | 422 | The app has no default version. |\n| `environment_limit_reached` | 429 | Delete only this task’s known leftovers, or ask the user to free capacity. |\n| `rate_limited` | 429 | Wait `retryAfterMs` when it is set, then retry. |\n| `internal_error` | 500 | Report `requestId`. |\n| `unavailable` | 503 | Retried automatically. If `create` still has an unknown outcome, replay the same input with the key retained before the original call. Do not generate a new key. |\n\n## Errors from the app URL\n\n`crm.fetch` returns these as responses with the same JSON envelope, and never retries them. Assert the expected status and result. For writes, read back the stored result. Never blindly retry a write whose outcome is unknown.\n\n| Code | Status | What to do |\n| --- | --- | --- |\n| `environment_not_running` | 409 | The environment is failed or deleted; `error.status` names it. Create another environment. |\n| `environment_starting` | 503 | For reads, wait `Retry-After` and retry. Check stored state before retrying an uncertain write. |\n| `environment_unavailable` | 503 | The app is restarting. For reads, wait `Retry-After`. Check stored state before retrying an uncertain write. |\n| `response_too_large` | 502 | The response is over the size limit. Ask for less, for example with a smaller page. |\n| `invalid_setup` | 503 | The app refused its `setup` after the environment started, such as in a newer version. `error.message` starts with the path of the value. Create another environment with a `setup` that the app accepts. |\n| `invalid_services` | 503 | The app refused a value in `services` after the environment started. `error.message` starts with the path of the value. Create another environment with `services` that the app accepts. |\n| `unsupported_protocol` | 501 | The app does not serve this path, method or content type, or the request body is compressed. |\n\n## Quickstart\n\nUse the project's TypeScript runner with its secret loader, or pass that runner through `npx @usestatemachines/cli@0.1.1 run -- <command>`. For an empty Node.js project, save as `state-quickstart.mts` and run `npx @usestatemachines/cli@0.1.1 run -- npx tsx state-quickstart.mts`. The check prints the response status and deletes the environment. Only after it prints `200`, report verified setup and the rerun command. Continue only with an explicitly supplied task. Otherwise, ask what to run next and wait with no setup environment left running.\n\n```ts\nimport { StateMachines } from '@usestatemachines/sdk';\n\nconst sm = new StateMachines(); // reads STATEMACHINES_API_KEY\nconst env = await sm.environments.create(\n { apps: ['salesforce'] },\n { wait: false },\n);\n\ntry {\n await sm.environments.wait(env.id, { status: 'running' });\n const salesforce = await env.connect('salesforce');\n const res = await salesforce.fetch('/services/data/v67.0/limits');\n console.log(res.status, await res.json());\n} finally {\n await env.delete();\n}\n```\n\n## Docs\n\n- [Documentation]({{origin}}/docs): guides, concepts and reference ([llms.txt]({{origin}}/docs/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\nFetch a docs page as Markdown with the API key in the `Authorization` header, for example `curl -fsS -H \"Authorization: Bearer $STATEMACHINES_API_KEY\" {{origin}}/docs/sdk-api/client.md`. Never print the key.\n",
|
|
21
|
+
"# State Machines\n\n> State Machines runs disposable environments of replica SaaS apps, such as Salesforce, Linear and Box, for tests and coding agents. Create, connect to, and delete them with the TypeScript SDK `@usestatemachines/sdk`.\n\nUse the project's package manager to install or reuse `@usestatemachines/sdk`. It is ESM only and runs on Node.js and Cloudflare Workers, not browsers. `engines` in its `package.json` gives the Node.js versions. TypeScript needs `lib: [\"DOM\"]` or `@types/node`. `new StateMachines()` reads the API key from `STATEMACHINES_API_KEY` and doesn't read `.env` files; in Cloudflare Workers, pass `apiKey` explicitly. Never print the key. The installed `node_modules/@usestatemachines/sdk/README.md` matches your version: read it before relying on memory. Read the bundled `agents/SKILL.md` for the task workflow. Reuse the project's secret loader and check the key in the process that runs the test, not just the current shell. Installing the skill does not require SDK installation, example files or remote resources. Ask what the user wants to run and wait. Install the SDK and verify access when their chosen task needs it. Run the connection check below only if the user asks to verify the connection. Do not choose a business workflow from the repository.\n\n## Get a credential\n\nIf the project's existing command loads `STATEMACHINES_API_KEY`, reuse that command and secret loader. Keep the same credential loader, CLI profile and API origin across setup commands. Otherwise, run `npx @usestatemachines/cli@0.1.1 whoami --json`. If it succeeds, confirm the account and organization match the task, then use the workspace commands below. If the CLI credential is missing, expired or revoked, start `npx @usestatemachines/cli@0.1.1 login --no-wait --json`. For an expired or revoked saved session, add `--yes` to replace that invalid session in the same profile. Do not replace a valid session for a different account. Show its `verificationUrl` and `userCode`, and wait for approval. A matching code does not identify the requesting computer. After that approval, run `npx @usestatemachines/cli@0.1.1 login --resume --json` with the same profile to finish that attempt. Resume an existing pending login after approval instead of creating another. Report access denial or an API origin mismatch without changing accounts or permissions.\n\nIf a pending login's approval URL or code is unavailable, ask before recovering it. Explain that recovery may finish an already-approved login. After confirmation, run `npx @usestatemachines/cli@0.1.1 login --resume --json --stream` with the same profile and API origin. Keep that process running and show its URL and code. Request approval only for a recognized login. Wait for `signed-in` before continuing setup. Do not start another login or resume process. If streaming output is unavailable, report the blocker without reading credential files.\n\nFor `cli.not_linked`, run `npx @usestatemachines/cli@0.1.1 workspaces list --json`. Ask which workspace to use when there is more than one, then run `npx @usestatemachines/cli@0.1.1 link --workspace <workspace-id> --json`. Run the user's existing code with `npx @usestatemachines/cli@0.1.1 run -- <command>`. Never print or commit a credential.\n\n## CLI commands\n\nPrefix these commands with `npx @usestatemachines/cli@0.1.1`. Use `help --json` for flags and result schemas:\n\n- `login`, `logout`, `whoami`, `profiles list`, and `profiles use`\n- `workspaces list`, `workspaces use`, `link`, `unlink`, and `init`\n- `apps list`, `environments create`, `environments list`, `environments get`, `environments wait`, and `environments delete`\n- `connect`, `snapshots create`, `snapshots list`, `snapshots get`, `snapshots delete`, `requests list`, and `requests get`\n- `run -- <command>`, `env pull`, `api`, `doctor`, `version`, `help`, and `completion`\n\n## Concepts\n\n- Environment: one disposable run of one or more apps. `const env = await sm.environments.create({ apps: ['salesforce'] })` creates it and waits until it is `running`. Use `{ wait: false }` to retain the ID before startup. Wait for `running` inside `try`, then delete the owned environment in `finally`, including after a wait failure.\n- Apps: a string names the app by its ID, and the app's name in the environment is the same string. Use `{ appId: 'salesforce', name: 'crm', setup }` for your own name, for two copies of one app, or for the app's setup. Later calls take the name.\n- App version: `sm.apps.versions(appId)` lists an app's versions. Read the default one before you use the app: each service has a `visibility`, `limitations`, a `prompt` and, for MCP, `servers` with their `tools`. `actors` lists users of the app's seed, for display. They do not say who can act.\n- Credentials: `const crm = await env.connect('salesforce')` returns `token`, `services` and `fetch`. Each service has an `id`, a `url` and the `headers` its requests need. The credentials always list each `public` service of the app's version. Pass `{ services: [id] }` to `connect()` to also list a service by its ID. A `preview` service is listed only when named. `crm.service('api')` returns the `api` service, which is the app URL. `crm.fetch(path)` joins `path` to the `api` service URL and adds its headers, which carry `Authorization: Bearer` with `token`. It throws when the credentials list no `api` service, as for Linear, whose clients call its `mcp` service. `crm.service('mcp').server(name)` returns one MCP server's `url` and `headers` for an MCP client. On an environment created with `tokenInUrl: true`, an MCP request without `Authorization` acts as the app's default user. For Salesforce SOAP, also put `crm.token` in the envelope's `SessionHeader/sessionId`. `fetch` never sends your API key, never follows redirects and never retries. `token` and each service's `headers` are secrets. On a `tokenInUrl` environment, each service and MCP server `url` carries the token and is a secret too.\n- Actor: the user of the app that a call acts as. Requests run with that user's permissions. `connect()` acts as the app's default user unless you pass `as`: the email, username or ID of any user who can sign in to the running app, such as `env.connect('salesforce', { as: 'alice.park@acme.example' })`. When the app's data came from an import whose `actors` names users for the app, the default user is the first of them. Otherwise it is the app's administrator. `credentials.actor` names that user, with the app's own `id`, such as a Salesforce `005…` ID. `env.actors(appName, { search })` pages through the users who can act, the default user first. `env.connectAll({ as })` connects to every app as one person: `credentials` holds each app that lets the user act, by app name, and `errors` holds the `ActorNotFoundError` or `ActorCannotSignInError` of each other app. Any other error throws. A user that the app does not have throws `ActorNotFoundError`, whose `nearest` lists the nearest users who can act. A user that the app does not let sign in throws `ActorCannotSignInError`.\n- Snapshot: the saved data of every app of a running environment, taken at one moment, or of an import. `await env.snapshots.create({ name: 'seeded' })` waits until it is `ready`. `sm.snapshots.create({ importId })` builds one from a `ready` import, and each app's `load` counts the records that did not load and lists the first of them. `sm.snapshots.create({ import: data })` takes the import as one object, the manifest with each app's `records` by object, then uploads it and builds the snapshot in one call. A problem at line N of `<app>/records/<Object>.jsonl` names `data.apps.<app>.records.<Object>[N - 1]`. `sm.environments.create({ snapshotId })` creates a new environment from it, in place of `apps`; each app keeps its name and version. A delete during the snapshot fails it with `interrupted`.\n- Recorded requests: `sm.requests.list({ environmentId })` pages the calls made to the environment's apps. `sm.requests.get(id)` adds headers and a preview of each body. `sm.requests.body(id, 'request')` or `'response'` returns the full body as bytes. Recording is best effort.\n- YAML: `parseEnvironmentYaml(text)` turns a YAML document with the fields of `create` into its input.\n- Pagination: every `list` returns one page, `{ data, nextCursor }`. Pass `nextCursor` back as `cursor` until it is `null`.\n\n## Statuses\n\nEnums are open: handle values you don't recognise.\n\n- Environment `status`: `starting`, `running`, `failed`, `deleted`. A `deleted` environment has a `reason` when State Machines deleted it: `expired`, `failed` (it never reached `running`) or `cancelled` (staff ended it). The `reason` is `null` when a caller deleted it. When the environment failed, `failure` is `{ appName, code, message }`, and `code` is `start_failed`, `pause_removed`, `version_unavailable`, `invalid_setup` or `invalid_services`. For `invalid_setup` and `invalid_services`, the app refused a value in `setup` or in `services`, and `message` starts with its path.\n- Snapshot `status`: `saving`, `ready`, `failed`, `deleted`. A failed snapshot has `failure.code` `interrupted`, `internal_error` or `invalid_import`. For `invalid_import`, an app refused the import that the snapshot was built from: `failure.appName` names the app, and `failure.problems` lists each problem.\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, with the problems of a refused import in `problems`, and `UnexpectedStatusError` when it was deleted. With `{ import }`, it throws `ImportInvalidError`, with the problems in `problems`, when the check leaves the uploaded import `invalid`, and it creates no snapshot.\n- `sm.environments.wait(id, { status })` and `sm.snapshots.wait(id, { status })` wait for any status, and throw once it cannot be reached without another call.\n\n## Limits\n\nWhen `sm.me().limits` is non-null:\n\n- Running environments are capped per organization: `(await sm.me()).limits.concurrentEnvironments` counts `starting` and `running`.\n- `timeLimitMinutes` has a default and a maximum: `(await sm.me()).limits.timeLimitMinutes`. It counts `starting` and `running` only; at the end the environment is deleted with `reason: 'expired'`.\n\n## Errors and retries\n\nA failed API call throws `StateMachinesError` with `status`, `code`, `message`, `requestId`, `fields` and `retryAfterMs`. The SDK retries `502`, `503`, `504` and connection errors itself, until a deadline; `create` retries reuse one idempotency key. It retries nothing else.\n\n| Code | Status | What to do |\n| --- | --- | --- |\n| `invalid_request` | 400 | Read `fields`, keyed by the dotted path of each wrong field. |\n| `unauthenticated` | 401 | The key is missing, expired or revoked. |\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. A snapshot needs a running environment or a `ready` import. |\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| `name_taken`, `last_owner`, `last_workspace`, `version_in_use`, `ui_disabled` | 409 | A workspace, member, version or UI session call conflicts with current state. Read `message`. |\n| `actor_cannot_sign_in` | 409 | The app does not let the user sign in now, such as an inactive user. `fields.as` gives the reason. Do not act as another user to make the test pass. |\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| `actor_not_found` | 422 | The app has no user with that email, username or ID. `err.nearest` lists the nearest users who can act. Check the name with `env.actors(appName, { search })`. |\n| `environment_limit_reached` | 429 | Delete only this task’s known leftovers, or ask the user to free capacity. |\n| `rate_limited` | 429 | Wait `retryAfterMs` when it is set, then retry. |\n| `internal_error` | 500 | Report `requestId`. |\n| `unavailable` | 503 | Retried automatically. If `create` still has an unknown outcome, replay the same input with the key retained before the original call. Do not generate a new key. |\n\n## Errors from the app URL\n\n`crm.fetch` returns these as responses with the same JSON envelope, and never retries them. Assert the expected status and result. For writes, read back the stored result. Never blindly retry a write whose outcome is unknown.\n\n| Code | Status | What to do |\n| --- | --- | --- |\n| `environment_not_running` | 409 | The environment is failed or deleted; `error.status` names it. Create another environment. |\n| `environment_starting` | 503 | For reads, wait `Retry-After` and retry. Check stored state before retrying an uncertain write. |\n| `environment_unavailable` | 503 | The app is restarting. For reads, wait `Retry-After`. Check stored state before retrying an uncertain write. |\n| `response_too_large` | 502 | The response is over the size limit. Ask for less, for example with a smaller page. |\n| `invalid_setup` | 503 | The app refused its `setup` after the environment started, such as in a newer version. `error.message` starts with the path of the value. Create another environment with a `setup` that the app accepts. |\n| `invalid_services` | 503 | The app refused a value in `services` after the environment started. `error.message` starts with the path of the value. Create another environment with `services` that the app accepts. |\n| `unsupported_protocol` | 501 | The app does not serve this path, method or content type, or the request body is compressed. |\n\n## Quickstart\n\nUse the project's TypeScript runner with its secret loader, or pass that runner through `npx @usestatemachines/cli@0.1.1 run -- <command>`. For an empty Node.js project, save as `state-quickstart.mts` and run `npx @usestatemachines/cli@0.1.1 run -- npx tsx state-quickstart.mts`. The check prints the response status and deletes the environment. Only after it prints `200`, report verified setup and the rerun command. Continue only with an explicitly supplied task. Otherwise, ask what to run next and wait with no setup environment left running.\n\n```ts\nimport { StateMachines } from '@usestatemachines/sdk';\n\nconst sm = new StateMachines(); // reads STATEMACHINES_API_KEY\nconst env = await sm.environments.create(\n { apps: ['salesforce'] },\n { wait: false },\n);\n\ntry {\n await sm.environments.wait(env.id, { status: 'running' });\n const salesforce = await env.connect('salesforce');\n const res = await salesforce.fetch('/services/data/v67.0/limits');\n console.log(res.status, await res.json());\n} finally {\n await env.delete();\n}\n```\n\n## Docs\n\n- [Documentation]({{origin}}/docs): guides, concepts and reference ([llms.txt]({{origin}}/docs/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\nFetch a docs page as Markdown with the API key in the `Authorization` header, for example `curl -fsS -H \"Authorization: Bearer $STATEMACHINES_API_KEY\" {{origin}}/docs/sdk-api/client.md`. Never print the key.\n",
|
|
22
22
|
skill:
|
|
23
|
-
"---\nname: state-machines\ndescription: Use when setting up State Machines or running code, integration tests, MCP clients, or API experiments against disposable replicas of SaaS apps such as Salesforce, Linear and Box. Applies to @usestatemachines/sdk and STATEMACHINES_API_KEY, including environment creation, actors, MCP servers, seeding, snapshots, cleanup and recovery.\n---\n\n## Start with the user's task\n\nDuring skill installation, read the repository instructions and preserve its existing structure. Do not create a new project, install project dependencies, write examples or create remote resources. Report the installed skill location and any reload requirement, then ask \"What would you like to run or test?\" and wait. Skill installation does not verify a connection.\n\nOnce the user chooses a task, inspect the project's package manager, runtime, runner and secret loader. Install or reuse a compatible `@usestatemachines/sdk` only when the task needs it. Read the installed README for API signatures and `package.json` for runtime requirements. The SDK is ESM and runs on Node.js and Cloudflare Workers, not in a browser. Follow the user's task and existing project conventions without requiring a separate onboarding example.\n\n## Get a credential\n\nThe SDK reads `STATEMACHINES_API_KEY` from the process environment. It does not load `.env` files. Use the project's existing test command or secret loader. An empty variable in your current shell does not prove that the test process lacks it. Verify presence without displaying the value. In Workers, pass the binding explicitly with `new StateMachines({ apiKey: env.STATEMACHINES_API_KEY })`.\n\nIf the project's existing command loads `STATEMACHINES_API_KEY`, keep using that command and secret loader. Keep the same credential loader, CLI profile and API origin across setup commands. Call `sm.me()` in that process to verify access and workspace scope. `me.limits` can be null.\n\nOtherwise, run `npx @usestatemachines/cli@0.1.1 whoami --json`. Confirm the returned account and organization match the task, then use the workspace commands below. If the CLI credential is missing, expired or revoked, complete this login:\n\n1. Run `npx @usestatemachines/cli@0.1.1 login --no-wait --json`. For an expired or revoked saved session, add `--yes` to replace that invalid session in the same profile. Do not replace a valid session for a different account.\n2. Show the returned `verificationUrl` and `userCode`. Ask the user to approve only the login you just started. A matching code does not identify the computer requesting access. Wait for the user to approve the login.\n3. After that approval, run `npx @usestatemachines/cli@0.1.1 login --resume --json` with the same profile used to start the login. Resume that attempt instead of starting another login. If it expired, offer a new login.\n\nIf a pending login's approval URL or code is unavailable, ask before recovering it. Explain that recovery may finish an already-approved login. After confirmation, run `npx @usestatemachines/cli@0.1.1 login --resume --json --stream` with the same profile and API origin. Keep that process running and show its URL and code. Request approval only for a recognized login. Wait for `signed-in` before continuing setup. Do not start another login or resume process. If streaming output is unavailable, report the blocker without reading credential files.\n\nIf a command returns `cli.not_linked`, run `npx @usestatemachines/cli@0.1.1 workspaces list --json`. Ask the user which workspace to use when there is more than one. Run `npx @usestatemachines/cli@0.1.1 link --workspace <workspace-id> --json` with the selected ID.\n\nRun the user's existing command with `npx @usestatemachines/cli@0.1.1 run -- <command>`. The child receives a workspace key. The CLI keeps the credential out of its output.\n\nIf access is denied, the API origin differs, or the workspace differs from the task, report the blocker. Do not invite or remove members, or change permissions.\n\nNever print, log or commit API keys, `credentials.token`, the `headers` of `credentials.services`, environment dumps, or secret-file contents. On a `tokenInUrl` environment, every service and MCP server `url` carries the token, so never print, log or commit those URLs either. Use synthetic test data.\n\n## Read the app before you use it\n\nApply the rest of this skill once the user has chosen a task. `sm.apps.list()` returns app IDs and each app's `defaultVersionId`. Every list returns `{ data, nextCursor }`. Follow cursors until you find the entry or `nextCursor` is null.\n\nRead the app's default version from `sm.apps.versions(appId)` before you write code. It is the current guidance for that app. Each of its `services` has an `id`, a `visibility`, `limitations` and a `prompt`, and a service that serves MCP lists its `servers` with their `tools`. Its `actors` are the app's users, each with an `id`, a `name` and a `summary`. Check the `limitations` against the behavior under test, as [known differences]({{origin}}/docs/apps/known-differences.md) does for Salesforce. A replica does not import a customer's data or run arbitrary customer automation.\n\n- Salesforce: the `api` service serves REST, Bulk API 2.0 and SOAP. Its `mcp` service is `preview`.\n- Box: the `api` service serves the Box Platform API, uploads and OAuth. Its `mcp` service is `preview`.\n- Linear: clients call the `mcp` service and its MCP server `linear`. Linear lists no `api` service. Its `ui` service is `preview`.\n\n## Create and connect\n\nCreate only the apps the task needs. A string such as `'linear'` is both the app ID and the app's name in the environment. Use `{ appId, name, setup, services }` to choose a name, run two copies of one app, pass `setup`, or configure a service. For example, `services` sets which MCP servers and tools the `mcp` service serves. Later calls take the name. Set `tokenInUrl: true` only for a client that cannot send headers.\n\n`env.connect(name)` acts as the app's default actor and lists each `public` service. Pass `{ services: ['mcp'] }` to also list a `preview` service. To act as another user, pass an `actorId` from the `actors` of the exact version that `env.apps` names for the app. Never use a role label, a workspace member ID, or the first version in a list.\n\n- `credentials.fetch(path)` calls the `api` service with its headers and never follows redirects. It throws when the credentials list no `api` service, as for Linear.\n- For MCP, `credentials.service('mcp').servers` lists the MCP servers, and `.server(name)` returns one server's `url` and `headers` to give the MCP client.\n- An external client must send a service's `headers` only to that service's `url`, with redirects off. For Salesforce SOAP, put the actor token in `SessionHeader/sessionId`. See [credentials]({{origin}}/docs/environments/credentials.md).\n- On a `tokenInUrl` environment, a Salesforce or Box MCP request without an `Authorization` header acts as the default actor. To act as `actorId`, send the service's headers. Linear refuses an MCP request without them, so a Linear MCP client always sends the `mcp` service's headers.\n\nPoint the existing integration's test transport at these URLs and credentials. If that needs a broader change, explain it before you expand the task.\n\n## Seed your own data\n\nCreate the data a test reads before the test runs. Write it through the app's own API or MCP tools as the actor that owns it: Salesforce REST, composite or Bulk API 2.0, Linear MCP create tools, or Box uploads. A Linear workspace starts with one team and no issues. Use `setup` only for identity and configuration. To reuse seeded data, save it with `env.snapshots.create({ name })`, then create each test's environment from `{ snapshotId }`. See [isolated tests]({{origin}}/docs/sdk-api/testing.md).\n\n## Verify the result\n\nAssert the expected status and result. For writes, read the stored data back and assert the relevant fields. For negative tests, assert the expected error. Do not weaken an assertion to make unsupported behavior pass. A successful standalone SDK request does not prove that the integration used the replica. Check its calls with `sm.requests.list({ environmentId })`.\n\n## Own the full lifecycle\n\nCreate with `{ wait: false }` to keep the accepted environment ID. Enter `try` immediately, then wait for `running`, connect and run the check inside it. Delete in `finally`, also after a startup failure, a timeout or a failed assertion. An aborted wait does not cancel the work that the API accepted, so clean up with a fresh, un-aborted signal. Keep both verification and cleanup errors, and report the environment ID if deletion fails.\n\nDelete only resources created for this task, never unrelated environments or a user-supplied snapshot. A snapshot in `saving` cannot be deleted yet. Keep its ID and wait for `ready` or `failed` first. In Cloudflare Workers, pass the key explicitly and keep the environment ID in the job's state. A request ending does not cancel startup.\n\nThe SDK retries management connection errors and `502`, `503` and `504` with one idempotency key per create call. To recover across attempts or a restart, keep the input and an explicit `idempotencyKey` before creation. Replay with the same input, key, workspace and caller. Do not create a replacement with a new key while the first outcome is unknown. See [operation recovery]({{origin}}/docs/sdk-api/recovery.md).\n\nWhen `sm.me().limits` is non-null, it gives the cap on concurrent environments and the default and maximum `timeLimitMinutes`. The time limit covers starting and running.\n\n## Optional connection check\n\nRun this only when the user asks to verify the connection. It proves the connection and cleanup, not an existing integration's behavior. Save it in the project's examples or test location. Use the existing TypeScript runner and secret loader, or pass that runner through `npx @usestatemachines/cli@0.1.1 run -- <command>`. For an empty Node.js project, save `state-quickstart.mts` and run `npx @usestatemachines/cli@0.1.1 run -- npx tsx state-quickstart.mts`.\n\n```ts\nimport { StateMachines } from '@usestatemachines/sdk';\n\nconst sm = new StateMachines(); // reads STATEMACHINES_API_KEY\nconst env = await sm.environments.create(\n { apps: ['salesforce'] },\n { wait: false },\n);\n\ntry {\n await sm.environments.wait(env.id, { status: 'running' });\n const salesforce = await env.connect('salesforce');\n const res = await salesforce.fetch('/services/data/v67.0/limits');\n console.log(res.status, await res.json());\n} finally {\n await env.delete();\n}\n```\n\n## Recover without changing the test's meaning\n\nManagement failures throw SDK errors. App HTTP failures are returned as `Response` objects. Use `instanceof` with the SDK error classes. Read the State Machines error code in `error.code` and tell it apart from an app's own error.\n\n| Failure | Next action |\n| --- | --- |\n| `unauthenticated` | The key is missing, expired or revoked. Fix the credential in the process that runs the task. |\n| `invalid_request` | Read `err.fields` and the installed method signature. |\n| `version_unavailable` | Discover the app ID and available versions. Stop if the required version is unavailable. |\n| `environment_limit_reached` | Clean up only this task's known leftovers, or report the capacity blocker. |\n| `EnvironmentFailedError` | Read `err.environment.failure.code`: `start_failed`, `pause_removed`, `version_unavailable`, `invalid_setup` or `invalid_services`. Clean up the owned environment. |\n| `WaitTimeoutError` or abort | Use the kept environment ID. A non-null `WaitTimeoutError.resource` also carries it. If neither an ID nor a replay key is available, report unresolved cleanup. Do not guess from a list. |\n| Unknown create outcome | Replay the kept input and idempotency key. Do not start another operation with a new key. |\n| `invalid_status` | Read the current resource status before you choose the next call. |\n| `snapshot_in_progress` | Wait for this environment's saving snapshot before you create another. |\n| `SnapshotFailedError` | Report the failure. Retry only if the task still needs a snapshot and the source environment is running. |\n| App `environment_not_running` | Inspect the environment. Do not silently switch the integration to another target. |\n| App `invalid_setup` or `invalid_services` | The app refused a value of `setup` or `services`. `error.message` starts with its path. Create another environment with an accepted value, and delete the owned one. |\n| App or gateway `503` | For a read, honor `Retry-After`. For a write with an uncertain outcome, inspect stored state or report the uncertainty before you retry. |\n| Cleanup failure | Keep the task result, report the owned resource ID and retry deletion with a fresh signal. Do not report cleanup as successful. |\n\n## Finish with evidence\n\nLeave the smallest rerunnable test or example. Report the behavior checked, command, assertion result, SDK and app versions, selected actor, resource IDs without secrets, and cleanup outcome. Say when execution was blocked or only typechecking ran. A passing replica check establishes the asserted behavior for that version and actor, not full production compatibility.\n\nFind other guides through {{origin}}/docs/llms.txt. Read focused pages before the full documentation bundle. This dashboard also serves an agent index at {{origin}}/llms.txt. Fetch a docs page as Markdown with the API key in the `Authorization` header, for example `curl -fsS -H \"Authorization: Bearer $STATEMACHINES_API_KEY\" {{origin}}/docs/sdk-api/client.md`. Never print the key.\n",
|
|
23
|
+
"---\nname: state-machines\ndescription: Use when setting up State Machines or running code, integration tests, MCP clients, or API experiments against disposable replicas of SaaS apps such as Salesforce, Linear and Box. Applies to @usestatemachines/sdk and STATEMACHINES_API_KEY, including environment creation, actors, MCP servers, seeding, snapshots, cleanup and recovery.\n---\n\n## Start with the user's task\n\nDuring skill installation, read the repository instructions and preserve its existing structure. Do not create a new project, install project dependencies, write examples or create remote resources. Report the installed skill location and any reload requirement, then ask \"What would you like to run or test?\" and wait. Skill installation does not verify a connection.\n\nOnce the user chooses a task, inspect the project's package manager, runtime, runner and secret loader. Install or reuse a compatible `@usestatemachines/sdk` only when the task needs it. Read the installed README for API signatures and `package.json` for runtime requirements. The SDK is ESM and runs on Node.js and Cloudflare Workers, not in a browser. Follow the user's task and existing project conventions without requiring a separate onboarding example.\n\n## Get a credential\n\nThe SDK reads `STATEMACHINES_API_KEY` from the process environment. It does not load `.env` files. Use the project's existing test command or secret loader. An empty variable in your current shell does not prove that the test process lacks it. Verify presence without displaying the value. In Workers, pass the binding explicitly with `new StateMachines({ apiKey: env.STATEMACHINES_API_KEY })`.\n\nIf the project's existing command loads `STATEMACHINES_API_KEY`, keep using that command and secret loader. Keep the same credential loader, CLI profile and API origin across setup commands. Call `sm.me()` in that process to verify access and workspace scope. `me.limits` can be null.\n\nOtherwise, run `npx @usestatemachines/cli@0.1.1 whoami --json`. Confirm the returned account and organization match the task, then use the workspace commands below. If the CLI credential is missing, expired or revoked, complete this login:\n\n1. Run `npx @usestatemachines/cli@0.1.1 login --no-wait --json`. For an expired or revoked saved session, add `--yes` to replace that invalid session in the same profile. Do not replace a valid session for a different account.\n2. Show the returned `verificationUrl` and `userCode`. Ask the user to approve only the login you just started. A matching code does not identify the computer requesting access. Wait for the user to approve the login.\n3. After that approval, run `npx @usestatemachines/cli@0.1.1 login --resume --json` with the same profile used to start the login. Resume that attempt instead of starting another login. If it expired, offer a new login.\n\nIf a pending login's approval URL or code is unavailable, ask before recovering it. Explain that recovery may finish an already-approved login. After confirmation, run `npx @usestatemachines/cli@0.1.1 login --resume --json --stream` with the same profile and API origin. Keep that process running and show its URL and code. Request approval only for a recognized login. Wait for `signed-in` before continuing setup. Do not start another login or resume process. If streaming output is unavailable, report the blocker without reading credential files.\n\nIf a command returns `cli.not_linked`, run `npx @usestatemachines/cli@0.1.1 workspaces list --json`. Ask the user which workspace to use when there is more than one. Run `npx @usestatemachines/cli@0.1.1 link --workspace <workspace-id> --json` with the selected ID.\n\nRun the user's existing command with `npx @usestatemachines/cli@0.1.1 run -- <command>`. The child receives a workspace key. The CLI keeps the credential out of its output.\n\nIf access is denied, the API origin differs, or the workspace differs from the task, report the blocker. Do not invite or remove members, or change permissions.\n\nNever print, log or commit API keys, `credentials.token`, the `headers` of `credentials.services`, environment dumps, or secret-file contents. On a `tokenInUrl` environment, every service and MCP server `url` carries the token, so never print, log or commit those URLs either. Use synthetic test data.\n\n## Read the app before you use it\n\nApply the rest of this skill once the user has chosen a task. `sm.apps.list()` returns app IDs and each app's `defaultVersionId`. Every list returns `{ data, nextCursor }`. Follow cursors until you find the entry or `nextCursor` is null.\n\nRead the app's default version from `sm.apps.versions(appId)` before you write code. It is the current guidance for that app. Each of its `services` has an `id`, a `visibility`, `limitations` and a `prompt`, and a service that serves MCP lists its `servers` with their `tools`. Its `actors` are users of the app's seed, for display. They do not say who can act. Check the `limitations` against the behavior under test, as [known differences]({{origin}}/docs/apps/known-differences.md) does for Salesforce. A replica never calls the vendor and does not run arbitrary customer automation. An environment holds a customer's org data only when it starts from a snapshot created from an import of that data.\n\n- Salesforce: the `api` service serves REST, Bulk API 2.0 and SOAP. Its `mcp` service is `preview`.\n- Box: the `api` service serves the Box Platform API, uploads and OAuth. Its `mcp` service is `preview`.\n- Linear: clients call the `mcp` service and its MCP server `linear`. Linear lists no `api` service. Its `ui` service is `preview`.\n\n## Create and connect\n\nCreate only the apps the task needs. A string such as `'linear'` is both the app ID and the app's name in the environment. Use `{ appId, name, setup, services }` to choose a name, run two copies of one app, pass `setup`, or configure a service. For example, `services` sets which MCP servers and tools the `mcp` service serves. Later calls take the name. Set `tokenInUrl: true` only for a client that cannot send headers.\n\n`env.connect(name)` acts as the app's default user and lists each `public` service. Pass `{ services: ['mcp'] }` to also list a `preview` service. To act as another user of the app, pass `as` with the user's email, username or ID, such as `env.connect('salesforce', { as: 'alice.park@acme.example' })`. Any user who can sign in to the running app can act, including a user that the test created. `credentials.actor` names the user that the credentials act as. Never pass a role label or a workspace member ID.\n\n- `env.actors(name, { search })` pages through the users who can act, the default user first. Use it to find a user when the task does not name one by email, username or ID.\n- `env.connectAll({ as })` connects to every app of the environment as one person. Its `credentials` hold each app that lets the user act, by app name. Its `errors` hold the `ActorNotFoundError` or `ActorCannotSignInError` of each other app. Any other error throws.\n- A user that the app does not have throws `ActorNotFoundError`, and `err.nearest` lists the nearest users who can act. A user that the app does not let sign in now, such as an inactive user, throws `ActorCannotSignInError`.\n- `credentials.fetch(path)` calls the `api` service with its headers and never follows redirects. It throws when the credentials list no `api` service, as for Linear.\n- For MCP, `credentials.service('mcp').servers` lists the MCP servers, and `.server(name)` returns one server's `url` and `headers` to give the MCP client.\n- An external client must send a service's `headers` only to that service's `url`, with redirects off. For Salesforce SOAP, put the actor token in `SessionHeader/sessionId`. See [credentials]({{origin}}/docs/environments/credentials.md).\n- On a `tokenInUrl` environment, an MCP request without an `Authorization` header acts as the app's default user. To act as the user of the credentials, send the service's headers.\n\nPoint the existing integration's test transport at these URLs and credentials. If that needs a broader change, explain it before you expand the task.\n\n## Seed your own data\n\nCreate the data a test reads before the test runs. Write it through the app's own API or MCP tools as the actor that owns it: Salesforce REST, composite or Bulk API 2.0, Linear MCP create tools, or Box uploads. A Linear workspace starts with one team and no issues. Use `setup` only for identity and configuration. To start from records, users and files that you write in one file, build a snapshot from an import, as [build a snapshot from an import]({{origin}}/docs/snapshots/import.md) shows. To reuse seeded data, save it with `env.snapshots.create({ name })`, then create each test's environment from `{ snapshotId }`. See [isolated tests]({{origin}}/docs/sdk-api/testing.md).\n\n## Verify the result\n\nAssert the expected status and result. For writes, read the stored data back and assert the relevant fields. For negative tests, assert the expected error. Do not weaken an assertion to make unsupported behavior pass. A successful standalone SDK request does not prove that the integration used the replica. Check its calls with `sm.requests.list({ environmentId })`.\n\n## Own the full lifecycle\n\nCreate with `{ wait: false }` to keep the accepted environment ID. Enter `try` immediately, then wait for `running`, connect and run the check inside it. Delete in `finally`, also after a startup failure, a timeout or a failed assertion. An aborted wait does not cancel the work that the API accepted, so clean up with a fresh, un-aborted signal. Keep both verification and cleanup errors, and report the environment ID if deletion fails.\n\nDelete only resources created for this task, never unrelated environments or a user-supplied snapshot. A snapshot in `saving` cannot be deleted yet. Keep its ID and wait for `ready` or `failed` first. In Cloudflare Workers, pass the key explicitly and keep the environment ID in the job's state. A request ending does not cancel startup.\n\nThe SDK retries management connection errors and `502`, `503` and `504` with one idempotency key per create call. To recover across attempts or a restart, keep the input and an explicit `idempotencyKey` before creation. Replay with the same input, key, workspace and caller. Do not create a replacement with a new key while the first outcome is unknown. See [operation recovery]({{origin}}/docs/sdk-api/recovery.md).\n\nWhen `sm.me().limits` is non-null, it gives the cap on concurrent environments and the default and maximum `timeLimitMinutes`. The time limit covers starting and running.\n\n## Optional connection check\n\nRun this only when the user asks to verify the connection. It proves the connection and cleanup, not an existing integration's behavior. Save it in the project's examples or test location. Use the existing TypeScript runner and secret loader, or pass that runner through `npx @usestatemachines/cli@0.1.1 run -- <command>`. For an empty Node.js project, save `state-quickstart.mts` and run `npx @usestatemachines/cli@0.1.1 run -- npx tsx state-quickstart.mts`.\n\n```ts\nimport { StateMachines } from '@usestatemachines/sdk';\n\nconst sm = new StateMachines(); // reads STATEMACHINES_API_KEY\nconst env = await sm.environments.create(\n { apps: ['salesforce'] },\n { wait: false },\n);\n\ntry {\n await sm.environments.wait(env.id, { status: 'running' });\n const salesforce = await env.connect('salesforce');\n const res = await salesforce.fetch('/services/data/v67.0/limits');\n console.log(res.status, await res.json());\n} finally {\n await env.delete();\n}\n```\n\n## Recover without changing the test's meaning\n\nManagement failures throw SDK errors. App HTTP failures are returned as `Response` objects. Use `instanceof` with the SDK error classes. Read the State Machines error code in `error.code` and tell it apart from an app's own error.\n\n| Failure | Next action |\n| --- | --- |\n| `unauthenticated` | The key is missing, expired or revoked. Fix the credential in the process that runs the task. |\n| `invalid_request` | Read `err.fields` and the installed method signature. |\n| `version_unavailable` | Discover the app ID and available versions. Stop if the required version is unavailable. |\n| `ActorNotFoundError` | The app has no user with that email, username or ID. Check `err.nearest` or `env.actors(appName, { search })`. Act as another user only when the task allows it. |\n| `ActorCannotSignInError` | The app does not let the user sign in now, for the reason in `err.fields.as`. Report it. Do not act as another user to make the test pass. |\n| `environment_limit_reached` | Clean up only this task's known leftovers, or report the capacity blocker. |\n| `EnvironmentFailedError` | Read `err.environment.failure.code`: `start_failed`, `pause_removed`, `version_unavailable`, `invalid_setup` or `invalid_services`. Clean up the owned environment. |\n| `WaitTimeoutError` or abort | Use the kept environment ID. A non-null `WaitTimeoutError.resource` also carries it. If neither an ID nor a replay key is available, report unresolved cleanup. Do not guess from a list. |\n| Unknown create outcome | Replay the kept input and idempotency key. Do not start another operation with a new key. |\n| `invalid_status` | Read the current resource status before you choose the next call. |\n| `snapshot_in_progress` | Wait for this environment's saving snapshot before you create another. |\n| `SnapshotFailedError` | Report the failure. Retry only if the task still needs a snapshot and the source environment is running. |\n| App `environment_not_running` | Inspect the environment. Do not silently switch the integration to another target. |\n| App `invalid_setup` or `invalid_services` | The app refused a value of `setup` or `services`. `error.message` starts with its path. Create another environment with an accepted value, and delete the owned one. |\n| App or gateway `503` | For a read, honor `Retry-After`. For a write with an uncertain outcome, inspect stored state or report the uncertainty before you retry. |\n| Cleanup failure | Keep the task result, report the owned resource ID and retry deletion with a fresh signal. Do not report cleanup as successful. |\n\n## Finish with evidence\n\nLeave the smallest rerunnable test or example. Report the behavior checked, command, assertion result, SDK and app versions, selected actor, resource IDs without secrets, and cleanup outcome. Say when execution was blocked or only typechecking ran. A passing replica check establishes the asserted behavior for that version and actor, not full production compatibility.\n\nFind other guides through {{origin}}/docs/llms.txt. Read focused pages before the full documentation bundle. This dashboard also serves an agent index at {{origin}}/llms.txt. Fetch a docs page as Markdown with the API key in the `Authorization` header, for example `curl -fsS -H \"Authorization: Bearer $STATEMACHINES_API_KEY\" {{origin}}/docs/sdk-api/client.md`. Never print the key.\n",
|
|
24
24
|
});
|
package/agents/llms.txt
CHANGED
|
@@ -26,10 +26,10 @@ Prefix these commands with `npx @usestatemachines/cli@0.1.1`. Use `help --json`
|
|
|
26
26
|
|
|
27
27
|
- Environment: one disposable run of one or more apps. `const env = await sm.environments.create({ apps: ['salesforce'] })` creates it and waits until it is `running`. Use `{ wait: false }` to retain the ID before startup. Wait for `running` inside `try`, then delete the owned environment in `finally`, including after a wait failure.
|
|
28
28
|
- Apps: a string names the app by its ID, and the app's name in the environment is the same string. Use `{ appId: 'salesforce', name: 'crm', setup }` for your own name, for two copies of one app, or for the app's setup. Later calls take the name.
|
|
29
|
-
- App version: `sm.apps.versions(appId)` lists an app's versions. Read the default one before you use the app: each service has a `visibility`, `limitations`, a `prompt` and, for MCP, `servers` with their `tools`. `actors` lists the app's
|
|
30
|
-
- Credentials: `const crm = await env.connect('salesforce')` returns `token`, `services` and `fetch`. Each service has an `id`, a `url` and the `headers` its requests need. The credentials always list each `public` service of the app's version. Pass `{ services: [id] }` to `connect()` to also list a service by its ID. A `preview` service is listed only when named. `crm.service('api')` returns the `api` service, which is the app URL. `crm.fetch(path)` joins `path` to the `api` service URL and adds its headers, which carry `Authorization: Bearer` with `token`. It throws when the credentials list no `api` service, as for Linear, whose clients call its `mcp` service. `crm.service('mcp').server(name)` returns one MCP server's `url` and `headers` for an MCP client. On an environment created with `tokenInUrl: true`,
|
|
31
|
-
- Actor:
|
|
32
|
-
- Snapshot: the saved data of every app of a running environment, taken at one moment, or of an import. `await env.snapshots.create({ name: 'seeded' })` waits until it is `ready`. `sm.snapshots.create({ importId })` builds one from a `ready` import, and each app's `load` counts the records that did not load and lists the first of them. `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`.
|
|
29
|
+
- App version: `sm.apps.versions(appId)` lists an app's versions. Read the default one before you use the app: each service has a `visibility`, `limitations`, a `prompt` and, for MCP, `servers` with their `tools`. `actors` lists users of the app's seed, for display. They do not say who can act.
|
|
30
|
+
- Credentials: `const crm = await env.connect('salesforce')` returns `token`, `services` and `fetch`. Each service has an `id`, a `url` and the `headers` its requests need. The credentials always list each `public` service of the app's version. Pass `{ services: [id] }` to `connect()` to also list a service by its ID. A `preview` service is listed only when named. `crm.service('api')` returns the `api` service, which is the app URL. `crm.fetch(path)` joins `path` to the `api` service URL and adds its headers, which carry `Authorization: Bearer` with `token`. It throws when the credentials list no `api` service, as for Linear, whose clients call its `mcp` service. `crm.service('mcp').server(name)` returns one MCP server's `url` and `headers` for an MCP client. On an environment created with `tokenInUrl: true`, an MCP request without `Authorization` acts as the app's default user. For Salesforce SOAP, also put `crm.token` in the envelope's `SessionHeader/sessionId`. `fetch` never sends your API key, never follows redirects and never retries. `token` and each service's `headers` are secrets. On a `tokenInUrl` environment, each service and MCP server `url` carries the token and is a secret too.
|
|
31
|
+
- Actor: the user of the app that a call acts as. Requests run with that user's permissions. `connect()` acts as the app's default user unless you pass `as`: the email, username or ID of any user who can sign in to the running app, such as `env.connect('salesforce', { as: 'alice.park@acme.example' })`. When the app's data came from an import whose `actors` names users for the app, the default user is the first of them. Otherwise it is the app's administrator. `credentials.actor` names that user, with the app's own `id`, such as a Salesforce `005…` ID. `env.actors(appName, { search })` pages through the users who can act, the default user first. `env.connectAll({ as })` connects to every app as one person: `credentials` holds each app that lets the user act, by app name, and `errors` holds the `ActorNotFoundError` or `ActorCannotSignInError` of each other app. Any other error throws. A user that the app does not have throws `ActorNotFoundError`, whose `nearest` lists the nearest users who can act. A user that the app does not let sign in throws `ActorCannotSignInError`.
|
|
32
|
+
- Snapshot: the saved data of every app of a running environment, taken at one moment, or of an import. `await env.snapshots.create({ name: 'seeded' })` waits until it is `ready`. `sm.snapshots.create({ importId })` builds one from a `ready` import, and each app's `load` counts the records that did not load and lists the first of them. `sm.snapshots.create({ import: data })` takes the import as one object, the manifest with each app's `records` by object, then uploads it and builds the snapshot in one call. A problem at line N of `<app>/records/<Object>.jsonl` names `data.apps.<app>.records.<Object>[N - 1]`. `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`.
|
|
33
33
|
- 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.
|
|
34
34
|
- YAML: `parseEnvironmentYaml(text)` turns a YAML document with the fields of `create` into its input.
|
|
35
35
|
- Pagination: every `list` returns one page, `{ data, nextCursor }`. Pass `nextCursor` back as `cursor` until it is `null`.
|
|
@@ -41,7 +41,7 @@ Enums are open: handle values you don't recognise.
|
|
|
41
41
|
- Environment `status`: `starting`, `running`, `failed`, `deleted`. A `deleted` environment has a `reason` when State Machines deleted it: `expired`, `failed` (it never reached `running`) or `cancelled` (staff ended it). The `reason` is `null` when a caller deleted it. When the environment failed, `failure` is `{ appName, code, message }`, and `code` is `start_failed`, `pause_removed`, `version_unavailable`, `invalid_setup` or `invalid_services`. For `invalid_setup` and `invalid_services`, the app refused a value in `setup` or in `services`, and `message` starts with its path.
|
|
42
42
|
- Snapshot `status`: `saving`, `ready`, `failed`, `deleted`. A failed snapshot has `failure.code` `interrupted`, `internal_error` or `invalid_import`. For `invalid_import`, an app refused the import that the snapshot was built from: `failure.appName` names the app, and `failure.problems` lists each problem.
|
|
43
43
|
- `create` throws `EnvironmentFailedError` when the environment fails, `UnexpectedStatusError` when it becomes `deleted` for another reason, and `WaitTimeoutError` when `timeoutMs` passes.
|
|
44
|
-
- `snapshots.create` throws `SnapshotFailedError` when the snapshot fails, with the problems of a refused import in `problems`, and `UnexpectedStatusError` when it was deleted.
|
|
44
|
+
- `snapshots.create` throws `SnapshotFailedError` when the snapshot fails, with the problems of a refused import in `problems`, and `UnexpectedStatusError` when it was deleted. With `{ import }`, it throws `ImportInvalidError`, with the problems in `problems`, when the check leaves the uploaded import `invalid`, and it creates no snapshot.
|
|
45
45
|
- `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.
|
|
46
46
|
|
|
47
47
|
## Limits
|
|
@@ -66,8 +66,10 @@ A failed API call throws `StateMachinesError` with `status`, `code`, `message`,
|
|
|
66
66
|
| `idempotency_key_reused` | 409 | The key was used with a different input. Replay with the exact same input. |
|
|
67
67
|
| `workspace_archived` | 409 | The workspace is archived. |
|
|
68
68
|
| `name_taken`, `last_owner`, `last_workspace`, `version_in_use`, `ui_disabled` | 409 | A workspace, member, version or UI session call conflicts with current state. Read `message`. |
|
|
69
|
+
| `actor_cannot_sign_in` | 409 | The app does not let the user sign in now, such as an inactive user. `fields.as` gives the reason. Do not act as another user to make the test pass. |
|
|
69
70
|
| `body_too_large` | 413 | The request body is over the API's size limit. Send less. |
|
|
70
71
|
| `version_unavailable` | 422 | The app has no default version. |
|
|
72
|
+
| `actor_not_found` | 422 | The app has no user with that email, username or ID. `err.nearest` lists the nearest users who can act. Check the name with `env.actors(appName, { search })`. |
|
|
71
73
|
| `environment_limit_reached` | 429 | Delete only this task’s known leftovers, or ask the user to free capacity. |
|
|
72
74
|
| `rate_limited` | 429 | Wait `retryAfterMs` when it is set, then retry. |
|
|
73
75
|
| `internal_error` | 500 | Report `requestId`. |
|