@keystrokehq/skills 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +23 -15
  3. package/package.json +3 -10
  4. package/{keystroke-agent-authoring → src/keystroke-agent-authoring}/SKILL.md +4 -4
  5. package/{keystroke-agent-authoring → src/keystroke-agent-authoring}/references/messaging-gateways.md +7 -7
  6. package/{keystroke-agent-authoring → src/keystroke-agent-authoring}/references/patterns.md +5 -5
  7. package/{keystroke-agent-authoring → src/keystroke-agent-authoring}/references/prebuilt-integrations.md +78 -78
  8. package/{keystroke-agent-authoring → src/keystroke-agent-authoring}/references/sandbox-and-mcp.md +2 -2
  9. package/{keystroke-agent-authoring → src/keystroke-agent-authoring}/references/source-map.md +1 -1
  10. package/{keystroke-agent-authoring → src/keystroke-agent-authoring}/references/testing.md +4 -4
  11. package/{keystroke-cli-workspace → src/keystroke-cli-workspace}/SKILL.md +1 -1
  12. package/{keystroke-cli-workspace → src/keystroke-cli-workspace}/references/command-map.md +3 -3
  13. package/{keystroke-cli-workspace → src/keystroke-cli-workspace}/references/project-lifecycle.md +1 -1
  14. package/{keystroke-credential-binding → src/keystroke-credential-binding}/SKILL.md +43 -108
  15. package/{keystroke-credential-binding → src/keystroke-credential-binding}/references/patterns.md +38 -66
  16. package/{keystroke-credential-binding → src/keystroke-credential-binding}/references/source-map.md +9 -9
  17. package/{keystroke-trigger-authoring → src/keystroke-trigger-authoring}/references/patterns.md +8 -4
  18. package/{keystroke-trigger-authoring → src/keystroke-trigger-authoring}/references/source-map.md +2 -3
  19. package/{keystroke-trigger-authoring → src/keystroke-trigger-authoring}/references/testing.md +2 -2
  20. package/{keystroke-workflow-authoring → src/keystroke-workflow-authoring}/SKILL.md +2 -2
  21. package/{keystroke-workflow-authoring → src/keystroke-workflow-authoring}/references/prebuilt-integrations.md +65 -65
  22. package/{keystroke-workflow-authoring → src/keystroke-workflow-authoring}/references/runtime-helpers.md +3 -3
  23. package/{keystroke-workflow-authoring → src/keystroke-workflow-authoring}/references/source-map.md +1 -1
  24. package/{keystroke-workflow-authoring → src/keystroke-workflow-authoring}/references/testing.md +5 -4
  25. package/keystroke-agent-authoring/evals/evals.json +0 -29
  26. package/keystroke-cli-workspace/evals/evals.json +0 -23
  27. package/keystroke-credential-binding/evals/evals.json +0 -29
  28. package/keystroke-data-toolkit/evals/evals.json +0 -23
  29. package/keystroke-task-authoring/evals/evals.json +0 -23
  30. package/keystroke-trigger-authoring/evals/evals.json +0 -29
  31. package/keystroke-workflow-as-tool-debugging/evals/evals.json +0 -23
  32. package/keystroke-workflow-authoring/evals/evals.json +0 -29
  33. /package/{AGENTS-blurb.md → src/AGENTS.md} +0 -0
  34. /package/{keystroke-cli-workspace → src/keystroke-cli-workspace}/references/credentials-and-connect.md +0 -0
  35. /package/{keystroke-credential-binding → src/keystroke-credential-binding}/references/cli.md +0 -0
  36. /package/{keystroke-data-toolkit → src/keystroke-data-toolkit}/SKILL.md +0 -0
  37. /package/{keystroke-data-toolkit → src/keystroke-data-toolkit}/references/usage.md +0 -0
  38. /package/{keystroke-task-authoring → src/keystroke-task-authoring}/SKILL.md +0 -0
  39. /package/{keystroke-task-authoring → src/keystroke-task-authoring}/references/patterns.md +0 -0
  40. /package/{keystroke-task-authoring → src/keystroke-task-authoring}/references/source-map.md +0 -0
  41. /package/{keystroke-trigger-authoring → src/keystroke-trigger-authoring}/SKILL.md +0 -0
  42. /package/{keystroke-workflow-as-tool-debugging → src/keystroke-workflow-as-tool-debugging}/SKILL.md +0 -0
  43. /package/{keystroke-workflow-as-tool-debugging → src/keystroke-workflow-as-tool-debugging}/references/playbook.md +0 -0
  44. /package/{keystroke-workflow-authoring → src/keystroke-workflow-authoring}/references/patterns.md +0 -0
@@ -58,7 +58,7 @@ What these fields do:
58
58
  This example reuses `repoSandbox` from the previous snippet.
59
59
 
60
60
  ```ts
61
- import { anthropic } from '@keystroke/integration-ai';
61
+ import { anthropic } from '@keystrokehq/ai';
62
62
  import { Agent } from '@keystrokehq/core';
63
63
 
64
64
  export const codingAgent = new Agent({
@@ -160,7 +160,7 @@ export const securedServer = new McpServer({
160
160
  This example reuses `localDocsServer` and `securedServer` from the previous snippets.
161
161
 
162
162
  ```ts
163
- import { anthropic } from '@keystroke/integration-ai';
163
+ import { anthropic } from '@keystrokehq/ai';
164
164
  import { Agent } from '@keystrokehq/core';
165
165
 
166
166
  export const docsAgent = new Agent({
@@ -3,7 +3,7 @@
3
3
  Use only the public imports a user repo can rely on:
4
4
 
5
5
  ```ts
6
- import { anthropic } from '@keystroke/integration-ai';
6
+ import { anthropic } from '@keystrokehq/ai';
7
7
  import {
8
8
  Agent,
9
9
  CredentialSet,
@@ -13,11 +13,11 @@ Default testing targets:
13
13
 
14
14
  ## Vitest setup
15
15
 
16
- `keystrokeTestPlugin()` adds the core test setup file to Vitest, which is required for credential resolution and sandbox mocks.
16
+ `keystrokeTestPlugin()` adds the Keystroke testing setup file to Vitest, which is required for credential resolution and sandbox mocks.
17
17
 
18
18
  ```ts
19
19
  import { defineConfig } from 'vitest/config';
20
- import { keystrokeTestPlugin } from '@keystrokehq/core/vitest';
20
+ import { keystrokeTestPlugin } from '@keystrokehq/testing';
21
21
 
22
22
  export default defineConfig({
23
23
  plugins: [keystrokeTestPlugin()],
@@ -26,10 +26,10 @@ export default defineConfig({
26
26
 
27
27
  ## Test tools directly
28
28
 
29
- For testing tools in isolation, use the specialized `runTool` helper from `@keystrokehq/core/vitest` which automatically creates the appropriate tool context.
29
+ For testing tools in isolation, use the specialized `runTool` helper from `@keystrokehq/testing` which automatically creates the appropriate tool context.
30
30
 
31
31
  ```ts
32
- import { runTool } from '@keystrokehq/core/vitest';
32
+ import { runTool } from '@keystrokehq/testing';
33
33
 
34
34
  const result = await runTool(
35
35
  lookupCustomerTool,
@@ -87,7 +87,7 @@ Important distinctions:
87
87
  - Use `keystroke connect <integration>` for official integration connection flows.
88
88
  - Use `keystroke credentials ...` for credential inspection and upload flows.
89
89
  - Use `keystroke skills sync` to copy `@keystrokehq/skills` into local editor skill directories.
90
- - Use `keystroke workflows run <authoredWorkflowId>` for deployed-workflow manual invocation; use `try-deploy` only when testing a temporary build artifact.
90
+ - Use `keystroke workflows run <authoredWorkflowId>` for deployed-workflow manual invocation; use `keystroke workflows test <workflow>` when testing a temporary build artifact.
91
91
 
92
92
  ## References
93
93
 
@@ -36,8 +36,8 @@ Root help shows these directly. Subcommand help focuses on the local flags for t
36
36
  - `projects`: inspect cached project state
37
37
  - `connect`: connect official integrations through OAuth
38
38
  - `credentials`: list requirements and upload credentials
39
- - `workflows`: build, validate, inspect, diff, env, logs, paused, run, try-deploy
40
- - `test`: test workflows and agent-callable tools
39
+ - `workflows`: build, validate, inspect, diff, env, logs, paused, test, run
40
+ - `test`: test agent-callable tools
41
41
  - `deploy`: deploy workflows, agents, tasks, or focused targets via `--target <file>`
42
42
  - `sync`: local sync operations
43
43
  - `skills`: Keystroke skills sync flows
@@ -47,7 +47,7 @@ Root help shows these directly. Subcommand help focuses on the local flags for t
47
47
 
48
48
  - `keystroke workflows logs` is different from `keystroke logs`
49
49
  - `keystroke workflows run <authoredWorkflowId>` executes the current deployed workflow snapshot via the workflow execute API; it does not build or upload local code
50
- - `keystroke workflows try-deploy <workflow>` builds local code, uploads or references a test bundle, and runs that temporary artifact
50
+ - `keystroke workflows test <workflow>` builds local code, uploads or references a test bundle, and runs that temporary artifact
51
51
  - `keystroke deploy --target <file>` is the focused deploy path
52
52
  - many commands support `--json`
53
53
 
@@ -77,7 +77,7 @@ keystroke workflows run wf_onboard_user --input '{"email":"user@example.com"}'
77
77
 
78
78
  Notes:
79
79
  - `workflows run` executes the current deployed snapshot through `client.workflows.execute()` / `POST /api/v1/workflows/execute`
80
- - it does not build or upload local source; use `workflows try-deploy` for temporary local artifact testing
80
+ - it does not build or upload local source; use `workflows test` for temporary local artifact testing
81
81
  - pass `--project-id <uuid>` outside a project checkout, otherwise the CLI resolves `projectId` from `keystroke.config.ts`
82
82
  - pass `--workflow-globals` or `--workflow-globals-file` when the deployed manifest declares required workflow globals without defaults
83
83
  - use `--wait` to poll to a terminal status and `--follow` to poll logs/events while waiting
@@ -95,7 +95,7 @@ Teach these rules:
95
95
 
96
96
  Call out this distinction when it matters:
97
97
  - `CredentialSet.id` is the raw authored id used in runtime credential context keys
98
- - `resolvedCredentialSetId` is the namespaced or manifest-facing id used for bindings and storage
98
+ - `credentialDefinitionId` is the namespaced or manifest-facing id used for bindings and storage
99
99
  - runtime lookups still use the raw `id`
100
100
 
101
101
  This matters for integration authors and when explaining `createOperationFactory`.
@@ -118,14 +118,14 @@ This matters for integration authors and when explaining `createOperationFactory
118
118
  - If `resolve` is present, `stored` must also be present.
119
119
  - Use `CredentialSet` instead of env-based secret handling inside authored primitives.
120
120
  - Follow Zod v4 syntax in examples and authored code. See `../../../.agents/rules/zod-v4-requirements.md`.
121
- - The drawer-label rule is enforced at three layers today: compile-time on a single primitive (`AssertUniqueCredentialSetIds`), build-time across the project (`assertUniqueCredentialSetResolvedIds`), and deploy-time schema-drift detection (`detectSchemaDrifts`). Cluster 07 of the reconciled credential plan adds two more layers: construction-time identity registry and vault-row schema fingerprint. You do not need to do anything special in authored code beyond following the rule above.
121
+ - The drawer-label rule is enforced at three layers today: compile-time on a single primitive (`AssertUniqueCredentialSetIds`), build-time across the project (`assertUniqueCredentialDefinitionIds`), and deploy-time schema-drift detection (`detectSchemaDrifts`). Cluster 07 of the reconciled credential plan adds two more layers: construction-time identity registry and vault-row schema fingerprint. You do not need to do anything special in authored code beyond following the rule above.
122
122
 
123
123
  ### Vault-row schema mismatch
124
124
 
125
125
  Every vault row is stamped with the credential set's schema fingerprint at upload. When a workflow later resolves credentials against a credential set whose `auth` or `stored` shape has changed since the row was written, the credential runtime raises `CredentialSchemaMismatchError` with a copy-paste remediation command:
126
126
 
127
127
  ```
128
- The credentials stored for "keystroke:acme-crm" were uploaded against a different schema than the workflow currently expects.
128
+ The credentials stored for "acme-crm" were uploaded against a different schema than the workflow currently expects.
129
129
 
130
130
  Uploaded at: 2026-01-15T12:34:56.000Z
131
131
  Uploaded shape: abc123…
@@ -139,133 +139,68 @@ The CLI (`keystroke credentials upload`) and the web UI (`POST /api/v1/credentia
139
139
 
140
140
  You don't invoke the check yourself — it's wired into every resolver consumer. The only thing authored code does is what it already does: declare `auth` and `stored` honestly. When you change either, operators re-upload.
141
141
 
142
- ### Caching `resolve`
142
+ ### Dynamic credential resolution
143
143
 
144
- By default every step calls your `resolve` hook fresh. For shape-normalizing
145
- hooks (HubSpot, Notion) that is cheap and leaves authors on the plain-function
146
- form. Throw on missing input rather than falling back to an empty string — an
147
- empty token produces a confusing 401 at the provider instead of a clear
148
- "credentials not connected" error at the resolver boundary:
144
+ `CredentialSet` no longer accepts authored `resolve` hooks or separate
145
+ `stored` schemas. Runtime transforms that mint temporary credentials are modeled
146
+ as trusted dynamic connections:
149
147
 
150
148
  ```ts
151
- resolve: async (stored) => {
152
- const token = stored.ACCESS_TOKEN ?? stored.API_KEY;
153
- if (!token) {
154
- throw new Error(
155
- 'Unable to resolve HubSpot credentials: provide either ACCESS_TOKEN (via OAuth) or API_KEY (private app)'
156
- );
157
- }
158
- return { HUBSPOT_ACCESS_TOKEN: token };
159
- }
160
- ```
161
-
162
- Pair the hook with a `.refine()` on the `stored` schema so Zod rejects
163
- "neither field populated" at upload time, before the resolver runs.
164
-
165
- For hooks that do real work (login exchange, KMS-signed JWT, ephemeral session
166
- token), use the object form:
167
-
168
- ```ts
169
- resolve: async (stored) => mintToken(stored),
170
- resolveCacheMs: 10 * 60_000, // cache for 10 minutes within a single workflow run
171
- ```
172
-
173
- A workflow run has a single cache; steps 2..N hit the cache. When the run
174
- ends, the cache is discarded.
175
-
176
- Pick `cacheMs` conservatively — under 75% of the provider's actual token
177
- validity window is a safe default. If the cached token expires mid-run, pair
178
- with top-level `onCredentialRevoked: 'retry-once'` on the credential set to self-heal.
179
-
180
- ### Self-healing with `retry-once`
181
-
182
- When a step receives a 401 / 403 and the integration throws
183
- `CredentialRevokedError`, the default behavior fails the step and marks the
184
- connection broken. Sometimes the credential is actually fine — it just expired
185
- a moment ago, and re-running `resolve` would produce a working token.
186
-
187
- Opt in at the credential-set root (next to `connection`, not inside it):
188
-
189
- ```ts
190
- onCredentialRevoked: 'retry-once',
191
- connection: {
192
- kind: 'manual',
193
- }
149
+ connections: [
150
+ {
151
+ id: 'assume-role',
152
+ kind: 'dynamic',
153
+ resolver: { id: 'official.aws.assume-role', cacheMs: 10 * 60_000 },
154
+ },
155
+ ]
194
156
  ```
195
157
 
196
- Requires `resolve`. On `CredentialRevokedError`, the platform invalidates the
197
- run's cache for this credential set, re-runs `resolve`, and retries the step
198
- once. If the retry succeeds, the workflow continues and the connection is not
199
- marked broken. If the retry fails, the step fails normally and the connection
200
- status flips as today.
201
-
202
- The revoke-retry is orthogonal to the normal retry budget: a step with
203
- `retries: { attempts: 3 }` still gets its full three attempts after one
204
- revoke-retry fires.
205
-
206
- **When NOT to use.** For credential sets without a stateful `resolve` (Slack
207
- bot tokens, pasted API keys), re-running `resolve` on the same stored values
208
- produces the same output. `retry-once` would burn one extra attempt with the
209
- same result. Leave the default `'fail'`.
158
+ The build/runtime contracts call this `needsDynamicResolution` with an optional
159
+ `dynamicResolutionCacheMs` hint. User-authored integrations should prefer
160
+ manual, OAuth, credentials-exchange, exchange, or platform connections unless a
161
+ trusted registered resolver already exists.
210
162
 
211
163
  ### Workspace-authored OAuth integrations
212
164
 
213
- First-party (`namespace: 'keystroke'`) OAuth credential sets source
214
- `clientId` / `clientSecret` from the platform's shared provider apps.
215
- User-authored OAuth credential sets need to point at their own OAuth app,
216
- so they MUST declare `oauthClientSource` construction fails otherwise.
165
+ OAuth connections no longer declare where `clientId` / `clientSecret` come
166
+ from in authored code. Connection definitions are seeded or registered into
167
+ the database with an `oauth_client_policy`; trusted platform paths use that
168
+ policy to resolve the right provider app credential row.
217
169
 
218
- The pattern is two credential sets, both authored with the raw
219
- `CredentialSet` primitive:
170
+ The final authoring shape stays focused on the user-runtime credential:
220
171
 
221
172
  ```ts
222
- // file 1 — the internal client-app credential set
223
- export const acmeClientApp = new CredentialSet({
224
- id: 'acmeClientApp',
225
- namespace: 'keystroke',
226
- auth: z.object({ clientId: z.string(), clientSecret: z.string() }),
227
- platformMetadata: { kind: 'provider-app', visibility: 'internal' },
228
- // No `connection` — client-app credentials are provisioned out-of-band
229
- // (operator env, admin upload), not through a user-facing connect flow.
230
- });
231
-
232
- // file 2 — the user-connection OAuth credential set
233
173
  export const acmeCrm = new CredentialSet({
234
174
  id: 'acmeCrm',
235
- namespace: 'keystroke',
236
- platformMetadata: { kind: 'user-connection', visibility: 'user-visible' },
237
175
  auth: z.object({ ACME_ACCESS_TOKEN: z.string() }),
238
- connection: {
239
- kind: 'oauth',
240
- authUrl: 'https://auth.acme.example.com/oauth/authorize',
241
- tokenUrl: 'https://auth.acme.example.com/oauth/token',
242
- scopes: ['read:contacts'],
243
- tokenType: 'refreshable',
244
- vault: { accessToken: 'ACME_ACCESS_TOKEN' },
245
- oauthClientSource: {
246
- kind: 'workspace-provider-app',
247
- credentialSet: acmeClientApp,
176
+ connections: [
177
+ {
178
+ id: 'oauth',
179
+ kind: 'oauth',
180
+ authUrl: 'https://auth.acme.example.com/oauth/authorize',
181
+ tokenUrl: 'https://auth.acme.example.com/oauth/token',
182
+ scopes: ['read:contacts'],
183
+ tokenType: 'refreshable',
184
+ vault: { accessToken: 'ACME_ACCESS_TOKEN' },
248
185
  },
249
- },
186
+ ],
250
187
  });
251
188
  ```
252
189
 
253
190
  Key rules:
254
191
 
255
- - The `clientApp` MUST be marked `platformMetadata: { kind: 'provider-app', visibility: 'internal' }`
256
- so the sandbox refuses to inject `clientSecret` into user step code.
257
- Construction of the user-connection set throws if its
258
- `oauthClientSource.credentialSet` points at a non-internal set.
259
- - User-namespaced OAuth MUST declare `oauthClientSource`. Construction
260
- throws with a clear error if it's missing.
261
- - If the `clientApp` schema uses alternate key names (e.g. `appId` /
262
- `appSecret` instead of `clientId` / `clientSecret`), pass a `keyMap`:
263
- `oauthClientSource: { kind: 'workspace-provider-app', credentialSet: acmeClientApp, keyMap: { clientId: 'appId', clientSecret: 'appSecret' } }`.
192
+ - Provider app client material is represented by platform-only credential
193
+ definitions in DB seed/registration data, not by a public `CredentialSet`
194
+ field.
195
+ - `oauth_client_policy.kind = 'platform-provider-app'` selects Keystroke-owned
196
+ provider app credentials; `kind = 'workspace-provider-app'` selects
197
+ organization-scoped workspace provider app credentials.
198
+ - User-runtime consumers never receive platform-only provider app secrets.
264
199
 
265
200
  The platform's initiate, callback, and refresh flows route workspace
266
201
  OAuth through the same `resolveOAuthClient` helper first-party
267
202
  integrations use — the end-to-end behavior is identical once the
268
- workspace provider app is registered (`keystroke integrations register <id> --client-app <credentialSetId>`).
203
+ workspace provider app is registered.
269
204
 
270
205
  ## Validating a credential at upload time
271
206
 
@@ -330,7 +265,7 @@ timeout for OAuth callbacks. Your hook should finish quickly; an
330
265
  unresponsive provider endpoint surfaces to the operator as "Credential
331
266
  validation timed out."
332
267
 
333
- Canonical adopter: `packages/integrations/integration-stripe/src/integration.ts`.
268
+ Canonical adopter: the Stripe provider package in the integrations repo.
334
269
 
335
270
  ## `credentials-exchange`
336
271
 
@@ -346,7 +281,7 @@ not a third-party authorization server — exchange them for something else
346
281
  Input: `username` / `password`. Stored: session cookie + Browserbase
347
282
  context id. Rotate by probing the session and falling back to
348
283
  `'needs-reinput'` when the cookie has expired. Use
349
- `createBrowserLoginExchange` from `@keystroke/integration-browserbase`.
284
+ `createBrowserLoginExchange` from `@keystrokehq/browserbase`.
350
285
 
351
286
  3. **Service-account JWT.** User pastes a private-key PEM. Input:
352
287
  `clientEmail` + `privateKeyPem`. Stored: freshly-minted JWT. Rotate by
@@ -98,7 +98,7 @@ Use the declarative form when `tokenResult.accessToken` (optionally
98
98
 
99
99
  ```ts
100
100
  import { CredentialSet } from '@keystrokehq/core';
101
- import { createOAuth2Connection } from '@keystroke/credential-connection';
101
+ import { defineOAuthConnection } from '@keystroke/credential-connection';
102
102
  import { z } from 'zod';
103
103
 
104
104
  export const salesforceCredentials = new CredentialSet({
@@ -107,7 +107,7 @@ export const salesforceCredentials = new CredentialSet({
107
107
  SALESFORCE_ACCESS_TOKEN: z.string(),
108
108
  SALESFORCE_INSTANCE_URL: z.string(),
109
109
  }),
110
- connection: createOAuth2Connection({
110
+ connection: defineOAuthConnection({
111
111
  authUrl: 'https://login.salesforce.com/services/oauth2/authorize',
112
112
  tokenUrl: 'https://login.salesforce.com/services/oauth2/token',
113
113
  scopes: ['api', 'refresh_token', 'offline_access'],
@@ -137,7 +137,7 @@ Use the function form as the escape hatch when the vault layout depends on
137
137
  derived values or on post-exchange userinfo stashed on `tokenResult.raw`.
138
138
 
139
139
  ```ts
140
- connection: createOAuth2Connection({
140
+ connection: defineOAuthConnection({
141
141
  authUrl: 'https://account.docusign.com/oauth/auth',
142
142
  tokenUrl: 'https://account.docusign.com/oauth/token',
143
143
  scopes: ['signature', 'extended'],
@@ -206,55 +206,33 @@ connection: {
206
206
  },
207
207
  ```
208
208
 
209
- Canonical adopter: `packages/integrations/integration-stripe/src/integration.ts`.
209
+ Canonical adopter: the Stripe provider package in the integrations repo.
210
210
 
211
- ## Cached resolve
211
+ ## Dynamic credential resolution
212
212
 
213
- Use the object form of `resolve` to cache the auth value across steps in a
214
- single workflow run. A 20-step workflow against a credential set with
215
- `cacheMs: 60_000` invokes `resolve` exactly once; steps 2..N hit the cache.
213
+ Use a trusted dynamic connection when a provider requires minting temporary
214
+ runtime credentials from stored connection state. The resolver is registered by
215
+ descriptor, not authored as a credential-set method.
216
216
 
217
217
  ```ts
218
218
  import { CredentialSet } from '@keystrokehq/core';
219
- import { CredentialRevokedError } from '@keystrokehq/core/errors';
220
219
  import { z } from 'zod';
221
220
 
222
- export const legacyErp = new CredentialSet({
223
- id: 'legacyErp',
224
- auth: z.object({ sessionToken: z.string() }),
225
- stored: z.object({ username: z.string(), password: z.string() }),
226
- resolve: async ({ username, password }) => {
227
- const res = await fetch('https://erp.example.com/login', {
228
- method: 'POST',
229
- headers: { 'Content-Type': 'application/json' },
230
- body: JSON.stringify({ username, password }),
231
- });
232
- if (!res.ok) throw new CredentialRevokedError('legacyErp', 'Login rejected');
233
- const { token } = (await res.json()) as { token: string };
234
- return { sessionToken: token };
235
- },
236
- resolveCacheMs: 10 * 60_000, // tokens valid ~15min; refresh at 10min
237
- onCredentialRevoked: 'retry-once',
238
- connection: {
239
- kind: 'manual',
240
- instructions: 'Enter service-account credentials.',
241
- },
221
+ export const aws = new CredentialSet({
222
+ id: 'aws',
223
+ auth: z.object({ AWS_ACCESS_KEY_ID: z.string(), AWS_SECRET_ACCESS_KEY: z.string() }),
224
+ connections: [
225
+ {
226
+ id: 'assume-role',
227
+ kind: 'dynamic',
228
+ resolver: { id: 'official.aws.assume-role', cacheMs: 10 * 60_000 },
229
+ },
230
+ ],
242
231
  });
243
232
  ```
244
233
 
245
- ## Revoke-retry
246
-
247
- ```ts
248
- onCredentialRevoked: 'retry-once',
249
- connection: {
250
- kind: 'manual',
251
- instructions: 'Enter service-account credentials.',
252
- }
253
- ```
254
-
255
- Requires `resolve`. On `CredentialRevokedError`, the platform invalidates the
256
- run's cache for this credential set, re-runs `resolve`, and retries the failing
257
- step once. Orthogonal to the normal retry budget.
234
+ The generated contracts surface this as `needsDynamicResolution` and
235
+ `dynamicResolutionCacheMs`.
258
236
 
259
237
  ## `credentials-exchange` — API-login
260
238
 
@@ -314,7 +292,7 @@ export const acmeCrm = new CredentialSet({
314
292
  ## `credentials-exchange` — browser-login
315
293
 
316
294
  Provider has no API; login requires a real browser. Use
317
- `createBrowserLoginExchange` from `@keystroke/integration-browserbase` to
295
+ `createBrowserLoginExchange` from `@keystrokehq/browserbase` to
318
296
  drive Browserbase + Stagehand. The LLM never sees plaintext credentials —
319
297
  Stagehand substitutes `%fieldName%` at Chromium level.
320
298
 
@@ -322,7 +300,7 @@ Stagehand substitutes `%fieldName%` at Chromium level.
322
300
  import {
323
301
  createBrowserLoginExchange,
324
302
  type BrowserbaseCredentials,
325
- } from '@keystroke/integration-browserbase';
303
+ } from '@keystrokehq/browserbase';
326
304
  import { CredentialSet } from '@keystrokehq/core';
327
305
  import { z } from 'zod';
328
306
 
@@ -464,8 +442,6 @@ import { z } from 'zod';
464
442
 
465
443
  export const crmApi = new CredentialSet({
466
444
  id: 'crmApi',
467
- namespace: 'keystroke',
468
- platformMetadata: { kind: 'user-connection', visibility: 'user-visible' },
469
445
  auth: z.object({ apiKey: z.string() }),
470
446
  stored: z.object({ vaultPath: z.string() }),
471
447
  resolveAtPlatform: async (stored, ctx) => {
@@ -499,8 +475,6 @@ declare function assumeRole(params: {
499
475
 
500
476
  export const awsAssumed = new CredentialSet({
501
477
  id: 'awsAssumed',
502
- namespace: 'keystroke',
503
- platformMetadata: { kind: 'user-connection', visibility: 'user-visible' },
504
478
  auth: z.object({
505
479
  accessKeyId: z.string(),
506
480
  secretAccessKey: z.string(),
@@ -541,8 +515,6 @@ declare function kmsSignJwt(params: {
541
515
 
542
516
  export const serviceJwt = new CredentialSet({
543
517
  id: 'serviceJwt',
544
- namespace: 'keystroke',
545
- platformMetadata: { kind: 'user-connection', visibility: 'user-visible' },
546
518
  auth: z.object({ jwt: z.string() }),
547
519
  stored: z.object({ kmsKeyArn: z.string(), issuer: z.string(), audience: z.string() }),
548
520
  resolveAtPlatform: async (stored, ctx) => {
@@ -575,8 +547,7 @@ post-exchange identity fetches.
575
547
 
576
548
  ```ts
577
549
  import {
578
- buildDefaultAuthUrl,
579
- exchangeCodeDefault,
550
+ oauthDefaults,
580
551
  oauthPresets as p,
581
552
  pipe,
582
553
  } from '@keystroke/credential-connection';
@@ -584,8 +555,8 @@ import {
584
555
  connection: {
585
556
  kind: 'oauth',
586
557
  // ...urls, scopes, tokenType, vault...
587
- buildAuthUrl: pipe(buildDefaultAuthUrl, p.scopeDelimiter(',')),
588
- exchangeCode: pipe(exchangeCodeDefault, p.requireSuccessFlag('ok')),
558
+ buildAuthUrl: pipe(oauthDefaults.buildAuthUrl, p.scopeDelimiter(',')),
559
+ exchangeCode: pipe(oauthDefaults.exchangeCode, p.requireSuccessFlag('ok')),
589
560
  },
590
561
  ```
591
562
 
@@ -651,7 +622,7 @@ The same agent example could also be written with `new Operation({...})` if the
651
622
  ## Agent usage
652
623
 
653
624
  ```ts
654
- import { anthropic } from '@keystroke/integration-ai';
625
+ import { anthropic } from '@keystrokehq/ai';
655
626
  import { Agent } from '@keystrokehq/core';
656
627
 
657
628
  export const supportAgent = new Agent({
@@ -714,13 +685,14 @@ export const crmMcpServer = new McpServer({
714
685
 
715
686
  Use `credentialMapper(...)` to convert Keystroke credential values into transport-specific config such as headers, env vars, or query parameters.
716
687
 
717
- ## `needsResolve`
688
+ ## Dynamic resolution flags
718
689
 
719
690
  ```ts
720
- const shouldResolve = oauthCredentials.needsResolve;
691
+ const usesDynamicResolution = requirement.needsDynamicResolution;
721
692
  ```
722
693
 
723
- `needsResolve` is `true` only when the credential set was defined with both `stored` and `resolve`.
694
+ `needsDynamicResolution` is true only for requirements backed by a trusted
695
+ registered dynamic resolver.
724
696
 
725
697
  ## `describe()` and `toManifest()`
726
698
 
@@ -746,7 +718,7 @@ One `CredentialSet` declared in code is a **type**. The platform can store many
746
718
  ┌───────────────────────────────────────────────────────────────────┐
747
719
  │ Credential Set TYPE │
748
720
  │ Declared in code. One per integration. │
749
- │ Example: keystroke:gmail
721
+ │ Example: gmail
750
722
  │ schema = { access_token: string, refresh_token: string } │
751
723
  │ connection = OAuth 2.0 with Google │
752
724
  └───────────────────────────────────────────────────────────────────┘
@@ -767,7 +739,7 @@ One `CredentialSet` declared in code is a **type**. The platform can store many
767
739
  What authors often try first is the anti-pattern:
768
740
 
769
741
  ```ts
770
- import { gmail } from '@keystroke/integration-google';
742
+ import { gmail } from '@keystrokehq/google';
771
743
  import { Step } from '@keystrokehq/core';
772
744
 
773
745
  export const brokenStep = new Step({
@@ -783,7 +755,7 @@ The supported path is **multi-step decomposition**: one credential set type, mul
783
755
 
784
756
  ```ts
785
757
  // One credential set TYPE, declared exactly once (by the integration).
786
- import { gmail } from '@keystroke/integration-google';
758
+ import { gmail } from '@keystrokehq/google';
787
759
  import { Step, Workflow } from '@keystrokehq/core';
788
760
  import { z } from 'zod';
789
761
 
@@ -833,14 +805,14 @@ export const inboxDigest = new Workflow({
833
805
  });
834
806
  ```
835
807
 
836
- At run time, the operator connects both Gmail accounts, which creates two vault rows under `resolvedCredentialSetId: 'keystroke:gmail'`, then chooses which one backs each call site:
808
+ At run time, the operator connects both Gmail accounts, which creates two vault rows under `credentialDefinitionId: 'gmail'`, then chooses which one backs each call site:
837
809
 
838
810
  ```jsonc
839
811
  {
840
812
  "workflowId": "inbox-digest",
841
813
  "credentialBindings": {
842
- "readPersonal:keystroke:gmail": "cset_personal",
843
- "readWork:keystroke:gmail": "cset_work"
814
+ "readPersonal:gmail": "cset_personal",
815
+ "readWork:gmail": "cset_work"
844
816
  }
845
817
  }
846
818
  ```
@@ -856,14 +828,14 @@ One drawer per `id`, ever. A `CredentialSet` declaration is the label on the dra
856
828
  When a collision is accidental, the platform is increasingly strict about catching it early:
857
829
 
858
830
  - compile-time on a single primitive via `AssertUniqueCredentialSetIds`
859
- - build-time across the project via `assertUniqueCredentialSetResolvedIds`
831
+ - build-time across the project via `assertUniqueCredentialDefinitionIds`
860
832
  - deploy-time via schema-drift detection
861
833
  - later in the reconciled credential roadmap, construction-time identity registry and vault-row schema fingerprinting close the remaining gaps
862
834
 
863
835
  When "same id" is actually a multi-instance requirement, the answer is still **one declaration, many connected rows**.
864
836
 
865
837
  ```ts
866
- import { stripe } from '@keystroke/integration-stripe';
838
+ import { stripe } from '@keystrokehq/stripe';
867
839
 
868
840
  // One declaration in code.
869
841
  export const billingCredential = stripe;
@@ -10,25 +10,25 @@ import { CredentialSet } from '@keystrokehq/core';
10
10
 
11
11
  - `id`
12
12
  - `namespace`
13
- - `resolvedCredentialSetId`
13
+ - `credentialDefinitionId`
14
14
  - `name`
15
15
  - `description`
16
16
  - `auth`
17
- - `stored`
18
- - `resolve`
19
- - `needsResolve`
17
+ - `connections`
18
+ - `needsRawSecret`
19
+ - `onCredentialRevoked`
20
20
 
21
21
  ### What they are used for
22
22
 
23
23
  - `id`: stable credential set identifier
24
24
  - `namespace`: optional namespace used for manifest and storage identity
25
- - `resolvedCredentialSetId`: computed namespaced id used for manifest and binding infrastructure
25
+ - `credentialDefinitionId`: computed namespaced id used for manifest and binding infrastructure
26
26
  - `name`: human-readable name
27
27
  - `description`: short explanation of what the credentials are for
28
28
  - `auth`: runtime credential schema
29
- - `stored`: stored credential schema when the stored shape differs from the runtime shape
30
- - `resolve`: async conversion from stored values to runtime auth values
31
- - `needsResolve`: tells you whether the credential set uses the stored-plus-resolve flow
29
+ - `connections`: credential acquisition paths such as manual, OAuth, exchange, dynamic, or platform
30
+ - `needsRawSecret`: routes real secret values into the runtime when proxy substitution cannot work
31
+ - `onCredentialRevoked`: top-level policy for revoked credential errors
32
32
 
33
33
  ## `CredentialSet` instance methods
34
34
 
@@ -61,7 +61,7 @@ import { CredentialSet } from '@keystrokehq/core';
61
61
  - if `stored` is present, `resolve` must also be present
62
62
  - if `resolve` is present, `stored` must also be present
63
63
  - prefer typed access through runtime context rather than ad hoc env access inside primitives
64
- - runtime credential context keys use raw `id`, not `resolvedCredentialSetId`
64
+ - runtime credential context keys use raw `id`, not `credentialDefinitionId`
65
65
 
66
66
  ## Where to read next
67
67
 
@@ -202,12 +202,16 @@ triggers: [
202
202
 
203
203
  Call the trigger as a function with `{ transform }` when the trigger payload and workflow input are different shapes. This returns a `BoundTrigger`.
204
204
 
205
- ## Bound trigger with additional `filter`
205
+ ## Narrow a trigger for per-workflow filtering
206
206
 
207
207
  ```ts
208
+ const largePayments = paymentWebhook.narrow({
209
+ name: 'large-payments',
210
+ filter: z.object({ amount: z.number().gt(100) }),
211
+ });
212
+
208
213
  triggers: [
209
- paymentWebhook({
210
- filter: (payload) => payload.amount > 100,
214
+ largePayments({
211
215
  transform: (payload) => ({
212
216
  eventId: payload.id,
213
217
  amount: payload.amount,
@@ -216,7 +220,7 @@ triggers: [
216
220
  ]
217
221
  ```
218
222
 
219
- An additional filter on the binding composes with the trigger's own filter.
223
+ Filtering and idempotency live on the trigger or its `.narrow()` derivatives — never on the attachment. Bindings accept only `transform`. Use `.narrow()` to produce a filtered child trigger and attach that to the workflow.
220
224
 
221
225
  ## Task trigger note
222
226