@keystrokehq/cli 0.1.38 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/{dist-DkLbeW8l.mjs → dist-BOhrc_Nv.mjs} +198 -561
  2. package/dist/dist-BOhrc_Nv.mjs.map +1 -0
  3. package/dist/{dist-B6z1wti6.mjs → dist-D-cLLjHv.mjs} +87 -2017
  4. package/dist/dist-D-cLLjHv.mjs.map +1 -0
  5. package/dist/{dist-GSI9JDuz.mjs → dist-DGKF3FGu.mjs} +31 -265
  6. package/dist/dist-DGKF3FGu.mjs.map +1 -0
  7. package/dist/dist-DMuIdus5.mjs +3 -0
  8. package/dist/{dist-gAvgHBlr.mjs → dist-Re6HHSqz.mjs} +2 -2
  9. package/dist/{dist-gAvgHBlr.mjs.map → dist-Re6HHSqz.mjs.map} +1 -1
  10. package/dist/index.mjs +177 -463
  11. package/dist/index.mjs.map +1 -1
  12. package/dist/{maybe-auto-update-Dv4MJvWb.mjs → maybe-auto-update-q5MthdI8.mjs} +2 -2
  13. package/dist/{maybe-auto-update-Dv4MJvWb.mjs.map → maybe-auto-update-q5MthdI8.mjs.map} +1 -1
  14. package/dist/skills-bundle/_AGENTS.mcp.md +5 -9
  15. package/dist/skills-bundle/_AGENTS.md +112 -243
  16. package/dist/skills-bundle/skills/keystroke-actions/SKILL.md +160 -0
  17. package/dist/skills-bundle/skills/keystroke-actions/references/catalog-and-imports.md +71 -0
  18. package/dist/skills-bundle/skills/keystroke-agents/SKILL.md +115 -0
  19. package/dist/skills-bundle/skills/keystroke-agents/references/models.md +23 -0
  20. package/dist/skills-bundle/skills/keystroke-agents/references/tools-mcp-codemode.md +73 -0
  21. package/dist/skills-bundle/skills/keystroke-agents/references/workflows-and-testing.md +26 -0
  22. package/dist/skills-bundle/skills/keystroke-apps/SKILL.md +151 -0
  23. package/dist/skills-bundle/skills/keystroke-apps/references/cli-and-catalog.md +104 -0
  24. package/dist/skills-bundle/skills/keystroke-channels/SKILL.md +66 -0
  25. package/dist/skills-bundle/skills/keystroke-channels/references/slack-setup.md +41 -0
  26. package/dist/skills-bundle/skills/keystroke-cli/SKILL.md +93 -0
  27. package/dist/skills-bundle/skills/keystroke-deploy/SKILL.md +93 -0
  28. package/dist/skills-bundle/skills/keystroke-deploy/references/build-and-full-deploy.md +30 -0
  29. package/dist/skills-bundle/skills/keystroke-deploy/references/filtered-deploy.md +50 -0
  30. package/dist/skills-bundle/skills/keystroke-deploy/references/wip-ignore.md +35 -0
  31. package/dist/skills-bundle/skills/keystroke-files/SKILL.md +43 -0
  32. package/dist/skills-bundle/skills/keystroke-skills/SKILL.md +42 -0
  33. package/dist/skills-bundle/skills/keystroke-triggers/SKILL.md +143 -0
  34. package/dist/skills-bundle/skills/keystroke-workflows/SKILL.md +78 -0
  35. package/dist/skills-bundle/skills/keystroke-workflows/references/authoring.md +168 -0
  36. package/dist/skills-bundle/skills/keystroke-workflows/references/testing.md +138 -0
  37. package/dist/templates/hello-world/.env.example +4 -0
  38. package/dist/templates/hello-world/README.md +3 -4
  39. package/dist/templates/hello-world/package.json +0 -1
  40. package/dist/templates/hello-world/src/actions/greet.ts +0 -1
  41. package/dist/templates/hello-world/src/agents/hello.ts +0 -2
  42. package/dist/templates/hello-world/src/workflows/greeting.ts +0 -1
  43. package/dist/{version-CiFlKPyE.mjs → version-DcR3O1UD.mjs} +3 -2
  44. package/dist/version-DcR3O1UD.mjs.map +1 -0
  45. package/package.json +5 -5
  46. package/dist/dist-B6z1wti6.mjs.map +0 -1
  47. package/dist/dist-CjWXZCN7.mjs +0 -3
  48. package/dist/dist-DkLbeW8l.mjs.map +0 -1
  49. package/dist/dist-GSI9JDuz.mjs.map +0 -1
  50. package/dist/version-CiFlKPyE.mjs.map +0 -1
@@ -0,0 +1,93 @@
1
+ ---
2
+ name: keystroke-cli
3
+ description: Keystroke CLI — log in, manage projects, deploy your src/, and run/inspect what's deployed (workflows, agents, triggers, apps). Use when running keystroke commands for a project.
4
+ metadata:
5
+ keystroke-domain: cli
6
+ ---
7
+
8
+ # CLI
9
+
10
+ The CLI is the primary interface for keystroke projects. You build in `src/`, deploy to your project on the platform, then run and inspect what's deployed. The loop is **edit → deploy → run/inspect → repeat**; deploy often.
11
+
12
+ ## Log in
13
+
14
+ ```bash
15
+ keystroke auth login # once; token stored in the OS keychain and reused
16
+ keystroke auth login --org <org-slug> # headless org pick
17
+ keystroke config org # list org memberships
18
+ keystroke config use org <org-slug> # switch active org
19
+ ```
20
+
21
+ One login covers every command. `auth login` auto-picks your org if you have one and prompts if there are several.
22
+
23
+ ## Projects
24
+
25
+ A **project** is your deploy target on the platform. `keystroke init` scaffolds the local repo; the platform project is where it runs.
26
+
27
+ ```bash
28
+ keystroke project list
29
+ keystroke project create --name "My app" --description "Optional"
30
+ keystroke deploy --project <id> # build + upload src/; see deploy skill for --filter
31
+ ```
32
+
33
+ `--project` selects the deploy target. It's a global flag that works before or after the subcommand (`keystroke deploy --project <id>`).
34
+
35
+ New projects are **inactive** until the first deploy. After a deploy, `keystroke deploy` remembers the project (saved in `~/.keystroke`), so later commands target it without `--project`. To switch which project later commands use:
36
+
37
+ ```bash
38
+ keystroke config use project <id>
39
+ ```
40
+
41
+ To target a different project for a single command without changing the default:
42
+
43
+ ```bash
44
+ keystroke workflow runs list greeting --project <id>
45
+ ```
46
+
47
+ By default the CLI targets the cloud platform. To run commands against a locally running server (`keystroke start` / `keystroke dev`), use the global `--local` flag for one command, or `keystroke config use local` / `keystroke config use cloud` to switch the default target.
48
+
49
+ Full deploy, filtered module redeploy, and WIP ignore: [deploy skill](../keystroke-deploy/SKILL.md).
50
+
51
+ ## Pull an existing project
52
+
53
+ `keystroke pull` is the inverse of `deploy`: it downloads a project's **active deploy source** into a local directory, installs dependencies, and syncs bundled skills. Use it to start working on a project that was created or deployed elsewhere (e.g. on a fresh machine).
54
+
55
+ ```bash
56
+ keystroke pull --project <id> # into the current directory
57
+ keystroke pull --project <id> --dir ./my-app
58
+ keystroke pull --project <id> --force # overwrite an existing local tree
59
+ ```
60
+
61
+ It targets the active project when `--project` is omitted. A directory that already holds an unrelated project is refused unless you pass `--force`; a directory from a prior pull of the **same** project refreshes in place, and a no-op pull (already on the active artifact) exits without changes.
62
+
63
+ ## Run & inspect
64
+
65
+ After deploy, runtime commands operate on your project:
66
+
67
+ ```bash
68
+ keystroke workflow run greeting --input '{"name":"Ada"}'
69
+ keystroke workflow runs list greeting
70
+ keystroke workflow runs get greeting <run-id> --include steps,trace
71
+ keystroke agent prompt hello --message "Hi"
72
+ keystroke agent sessions get support <session-id> --include messages,trace
73
+ keystroke app list
74
+ keystroke trigger list
75
+ keystroke trigger list --endpoint stripe # all webhooks on a shared endpoint
76
+ keystroke trigger url stripe # shared route URL (or a trigger key)
77
+ ```
78
+
79
+ Always use the CLI to inspect runs and sessions — do not hand-craft HTTP requests.
80
+
81
+ ## Commands at a glance
82
+
83
+ | Command | What it does |
84
+ | -------------------------------------------------- | -------------------------------------------------------- |
85
+ | `auth login` | Authenticate; token reused for all commands |
86
+ | `project list`, `project create` | Manage deploy targets in the active org |
87
+ | `deploy` | Build `src/` and ship it; sets the active project |
88
+ | `pull` | Download a project's active deploy source into a local dir |
89
+ | `workflow`, `agent`, `trigger`, `app` | Run and inspect resources on your project |
90
+ | `connect <slug>` | Connect an integration (see apps skill) |
91
+ | `config use project <id>`, `config show` | Set/inspect the active project |
92
+
93
+ Related: [deploy](../keystroke-deploy/SKILL.md), [apps](../keystroke-apps/SKILL.md), [workflows](../keystroke-workflows/SKILL.md), [agents](../keystroke-agents/SKILL.md), [triggers](../keystroke-triggers/SKILL.md).
@@ -0,0 +1,93 @@
1
+ ---
2
+ name: keystroke-deploy
3
+ description: Build and deploy keystroke projects — full deploy, filtered module redeploy, @keystroke ignore for WIP modules. Use when shipping to the platform or keeping draft code out of production.
4
+ metadata:
5
+ keystroke-domain: deploy
6
+ ---
7
+
8
+ # Deploy
9
+
10
+ Deploy uploads a `dist/` tarball to the keystroke **platform** and promotes a new project-server runtime (blue/green). Use this skill when shipping your project to the platform.
11
+
12
+ ## Prerequisites
13
+
14
+ ```bash
15
+ keystroke auth login
16
+ keystroke project list # or: project create --name "My app"
17
+ keystroke deploy --project <slug> # first deploy must be full (no --filter)
18
+ ```
19
+
20
+ `--project` selects the deploy target; as a global flag it works before or after `deploy`. After a successful deploy the project becomes the active target (saved in `~/.keystroke`), so later commands (including subsequent deploys) can omit it.
21
+
22
+ Deploy always **builds before upload**. Full deploy wipes `dist/` (`clean: true`), rebuilds all modules, regenerates `route-manifest.json`, then packs and uploads.
23
+
24
+ ## Full deploy
25
+
26
+ ```bash
27
+ keystroke build # optional preview; deploy rebuilds anyway
28
+ keystroke deploy --project <slug>
29
+ keystroke deploy --project <slug> --dir ./my-app
30
+ ```
31
+
32
+ After success the project becomes the active target for later commands. Verify with `keystroke workflow run <key> --input '{}'`.
33
+
34
+ ## Filtered deploy (one module)
35
+
36
+ Redeploy a single agent/workflow/trigger without rebuilding everything. Requires an **active** artifact from a prior full deploy.
37
+
38
+ ```bash
39
+ keystroke deploy --project <slug> --filter workflows/morning-check
40
+ keystroke deploy --project <slug> --filter agents/support --filter workflows/signup-pipeline
41
+ ```
42
+
43
+ `--filter` matches exact build entry keys (`agents/foo`, `workflows/bar`, `triggers/baz`) — the paths under `src/` without extension. Repeat the flag for multiple modules. No globs.
44
+
45
+ **How it works:** isolated rebuild of each matched module → download active prod tarball → overlay rebuilt `.mjs` files → merge `route-manifest.json` by module → upload as a normal full deploy.
46
+
47
+ **Carried forward from prod:** `config.mjs`, bundled assets, skills metadata, and all untouched modules. Skill/asset drift is not validated in v1.
48
+
49
+ Detail: [filtered-deploy.md](references/filtered-deploy.md).
50
+
51
+ ## WIP modules (`@keystroke ignore`)
52
+
53
+ Keep draft agents/workflows/triggers in `src/` but out of the runtime:
54
+
55
+ ```ts
56
+ // @keystroke ignore // skip local build, dev, and deploy
57
+ // @keystroke ignore:deploy // local build + dev only; omitted from deploy
58
+ ```
59
+
60
+ - `ignore` — broken or not-ready; stays out of `dist/` everywhere.
61
+ - `ignore:deploy` — runs locally; excluded from deploy only.
62
+
63
+ Import guard: a shipped module that **imports** an `@keystroke ignore` file fails the build in **every** phase; importing an `@keystroke ignore:deploy` file builds fine locally and fails only at **deploy** time. Triggers attached to an ignored workflow need the same directive.
64
+
65
+ Detail: [wip-ignore.md](references/wip-ignore.md).
66
+
67
+ ## Build vs deploy
68
+
69
+ | Command | Phase | Output |
70
+ | ------------------ | ------- | ------------------------------------------- |
71
+ | `keystroke build` | `build` | `dist/` for inspection |
72
+ | `keystroke deploy` | `deploy`| `dist/` + upload; honors `ignore:deploy` |
73
+
74
+ Deploy always runs a fresh, clean build before upload.
75
+
76
+ Detail: [build-and-full-deploy.md](references/build-and-full-deploy.md).
77
+
78
+ ## Common mistakes
79
+
80
+ - **First deploy with `--filter`** — fails; run full deploy once.
81
+ - **Wrong `--dir`** — builds a different tree than you edited.
82
+ - **Expecting full refresh with `--filter`** — only matched modules rebuild; rest comes from active prod.
83
+ - **Connected apps aren't in `.env`** — deploy never uploads `.env`; connect apps for your project. See [apps skill](../keystroke-apps/SKILL.md).
84
+
85
+ ## Audit deployed runtime
86
+
87
+ ```bash
88
+ keystroke project deployments list --project <slug>
89
+ keystroke workflow run morning-check --input '{}'
90
+ keystroke trigger list
91
+ ```
92
+
93
+ Related: [cli skill](../keystroke-cli/SKILL.md), [apps skill](../keystroke-apps/SKILL.md).
@@ -0,0 +1,30 @@
1
+ # Build and full deploy
2
+
3
+ ## What deploy does
4
+
5
+ 1. `walkProject` with `phase: "deploy"` — applies `@keystroke ignore:deploy`, collects source snapshot for the dashboard.
6
+ 2. `buildApp` — tsdown with `clean: true`; writes `dist/` and `dist/.keystroke/route-manifest.json`.
7
+ 3. `packProjectArtifact` — gzip tarball of `dist/`.
8
+ 4. Platform `artifacts.create` → PUT tarball → `complete` → blue/green promote.
9
+
10
+ Source files upload in parallel (content-addressed); failure there warns but does not block deploy. The dashboard source snapshot is capped (per-file 256KB, total 8MB, 2000 files) and skips binaries, `.env`, and `.log` files — so `.env` is never uploaded.
11
+
12
+ After upload, the CLI polls the project status (every ~2s, up to a 120s timeout) until it reports `active` or `failed`; on failure it surfaces the platform's `lastError`. A hang usually means the platform promotion is still in progress.
13
+
14
+ ## Entry keys
15
+
16
+ Build entry keys mirror `dist/` paths without `.mjs`:
17
+
18
+ | Source file | Entry key |
19
+ | ------------------------------ | ---------------------- |
20
+ | `src/agents/support.ts` | `agents/support` |
21
+ | `src/workflows/morning-check.ts` | `workflows/morning-check` |
22
+ | `src/triggers/incoming-message.ts` | `triggers/incoming-message` |
23
+
24
+ Nested agent dirs use the same `agents/<id>` pattern from discovery.
25
+
26
+ ## Freshness
27
+
28
+ Full deploy always runs `buildApp` before pack. Default `clean: true` removes prior `dist/` output before rebuild. You do not need a separate `keystroke build` before deploy.
29
+
30
+ Orphan files from removed modules are unlikely to affect routing — `route-manifest.json` is regenerated and drives HTTP routes.
@@ -0,0 +1,50 @@
1
+ # Filtered deploy (`--filter`)
2
+
3
+ Ship changes to one or a few modules without a full rebuild of every agent, workflow, and trigger.
4
+
5
+ ## Command
6
+
7
+ ```bash
8
+ keystroke deploy --project <slug> --filter workflows/morning-check
9
+ keystroke deploy --project <slug> --filter agents/support --filter workflows/signup-pipeline
10
+ ```
11
+
12
+ (`--project` can go before or after `deploy`, and can be omitted after the first deploy.)
13
+
14
+ - Exact entry keys only — no globs, no negation, no slug aliases.
15
+ - First deploy for a project must be **full** (no `--filter`).
16
+ - Without an active artifact, deploy errors: run a full deploy first.
17
+
18
+ ## Pipeline
19
+
20
+ ```text
21
+ isolated single-entry build per --filter match (self-contained .mjs)
22
+ → download active prod tarball
23
+ → extract → overlay rebuilt files → merge route-manifest by moduleFile
24
+ → re-tar → normal artifacts.create / PUT / complete
25
+ ```
26
+
27
+ ## Isolated builds
28
+
29
+ Each filtered module is built alone in a scratch dir. Output is self-contained (no shared `../chunk-*.mjs` imports). The merge overlays only `dist/<kind>/<id>.mjs` (+ `.map`) onto the extracted prod tree.
30
+
31
+ ## Manifest merge
32
+
33
+ For each rebuilt module, base manifest entries with the same `moduleFile` are dropped; rebuilt entries are appended. Health, plugins, skills, integrations, and untouched modules stay from the active artifact.
34
+
35
+ ## What is not rebuilt
36
+
37
+ - `config.mjs` and built-in integration routes
38
+ - `dist/.keystroke/assets.mjs` and skill files on disk
39
+ - Any module not listed in `--filter`
40
+
41
+ Change `keystroke.config.ts` or project-wide assets → run a **full** deploy.
42
+
43
+ ## Verify
44
+
45
+ ```bash
46
+ keystroke project deployments list --project <slug> # new version active
47
+ keystroke workflow run <workflow-slug> --input '{}'
48
+ ```
49
+
50
+ Confirm untouched routes still work after a filtered deploy of a different module.
@@ -0,0 +1,35 @@
1
+ # WIP modules and `@keystroke ignore`
2
+
3
+ Place the directive in the **leading comment block** of a module file under `src/agents/`, `src/workflows/`, or `src/triggers/` (the scan stops at the first non-comment line). Line (`//`) and block (`/* … */`, `*`) comments both work.
4
+
5
+ ```ts
6
+ // @keystroke ignore
7
+ // @keystroke ignore:deploy
8
+ /** @keystroke ignore */
9
+ ```
10
+
11
+ Only `ignore` and `ignore:deploy` are valid. Any other scope (e.g. `// @keystroke ignore:build`) throws `Unknown @keystroke ignore target "…"` at build time.
12
+
13
+ ## Directives
14
+
15
+ | Directive | Local build (`build`, `dev`, `start`) | Deploy (`phase: deploy`) |
16
+ | ---------------------- | ------------------------------------- | ------------------------ |
17
+ | `// @keystroke ignore` | Skipped | Skipped |
18
+ | `// @keystroke ignore:deploy` | Included | Skipped |
19
+
20
+ ## When to use which
21
+
22
+ - **`ignore`** — module is broken, experimental, or not ready to run anywhere.
23
+ - **`ignore:deploy`** — safe to run locally; not ready for production yet.
24
+
25
+ ## Import guard
26
+
27
+ If a **shipped** module imports a file marked `@keystroke ignore`, the build **fails in every phase**. If it imports an `@keystroke ignore:deploy` file, the local build/dev succeeds and only the **deploy** build fails (because `ignore:deploy` files are excluded only in the deploy phase). Compose in workflows instead of importing ignored actions from production modules.
28
+
29
+ ## Triggers
30
+
31
+ A trigger attached to an ignored workflow should use the same ignore treatment, or the attachment will not behave as expected across build phases.
32
+
33
+ ## Filtered deploy note
34
+
35
+ `ignore:deploy` omits a module from the **new** build. It does **not** keep the last prod version — full deploy replaces the entire artifact. Use filtered deploy when you want to update one live module while carrying forward the rest of prod.
@@ -0,0 +1,43 @@
1
+ ---
2
+ name: keystroke-files
3
+ description: Add static files to agent workspaces from src/files/ — product docs, instructions, context. Use with defineSandbox on defineAgent.
4
+ metadata:
5
+ keystroke-domain: files
6
+ ---
7
+
8
+ # Files
9
+
10
+ Files under `src/files/` mount into the agent sandbox at `/workspace/agent/`.
11
+
12
+ ## Layout
13
+
14
+ ```
15
+ src/files/support/
16
+ product-guide.md
17
+ support-instructions.md
18
+ runbooks/
19
+ refunds.md
20
+ ```
21
+
22
+ Nested directories are preserved — `src/files/support/runbooks/refunds.md` mounts at `/workspace/agent/runbooks/refunds.md`.
23
+
24
+ ```ts
25
+ import { defineAgent } from "@keystrokehq/keystroke/agent";
26
+ import { defineSandbox } from "@keystrokehq/keystroke/sandbox";
27
+
28
+ export default defineAgent({
29
+ slug: "support",
30
+ systemPrompt: "Read /workspace/agent/product-guide.md before answering.",
31
+ sandbox: defineSandbox({ files: true }), // uses agent slug → src/files/support/
32
+ });
33
+ ```
34
+
35
+ Use `defineSandbox({ files: "shared" })` to mount `src/files/shared/` instead — the string value names the folder under `src/files/`.
36
+
37
+ If the named folder doesn't exist, the build fails with a clear error — create the folder (it must contain at least one file) before deploying.
38
+
39
+ Files are seeded **write-once** at sandbox startup: the agent can edit them at runtime, but those edits aren't persisted back to `src/files/` and reset on the next run. Treat `src/files/` as read-only seed content.
40
+
41
+ Point the agent at paths in `systemPrompt`. After deploy, verify behavior with `keystroke agent prompt support --message "…"`.
42
+
43
+ Related: [agents](.agents/skills/keystroke-agents/SKILL.md), [skills](.agents/skills/keystroke-skills/SKILL.md).
@@ -0,0 +1,42 @@
1
+ ---
2
+ name: keystroke-skills
3
+ description: Add Agent Skills playbooks in src/skills/ for defineAgent — SKILL.md format, optional references. Use when giving agents domain-specific instructions.
4
+ metadata:
5
+ keystroke-domain: skills
6
+ ---
7
+
8
+ # Project skills
9
+
10
+ Skills are playbooks the agent loads on demand. Author in `src/skills/`, attach by folder name on the agent.
11
+
12
+ ## Layout
13
+
14
+ ```
15
+ src/skills/support/
16
+ SKILL.md
17
+ references/refund-policy.md # optional
18
+ ```
19
+
20
+ ```ts
21
+ defineAgent({
22
+ slug: "support",
23
+ skills: ["support"] /* … */,
24
+ });
25
+ ```
26
+
27
+ At runtime → `/workspace/agent/skills/support/`.
28
+
29
+ ## SKILL.md
30
+
31
+ Follow [Agent Skills](https://agentskills.io/specification): YAML frontmatter with `name` (matches the folder) and `description` are the required fields; everything else (e.g. `metadata`) is optional. Keep the body short; put detail in `references/`.
32
+
33
+ ## Two kinds of skills (don't confuse them)
34
+
35
+ - **`src/skills/`** — *project* Agent Skills attached to your agents via `skills: [...]`. They deploy with your project; inspect deployed ones with `keystroke skill list`.
36
+ - **`.agents/skills/`** — the *bundled coding-agent* guides (like this one) that `keystroke init` scaffolds. Refresh them to the current CLI version with `keystroke skills sync` (note the plural `skills`).
37
+
38
+ ## External registries
39
+
40
+ Browse [skills.sh](https://skills.sh) — copy into `src/skills/{name}/` and fix `name` to match the folder.
41
+
42
+ Related: [agents](.agents/skills/keystroke-agents/SKILL.md), [files](.agents/skills/keystroke-files/SKILL.md).
@@ -0,0 +1,143 @@
1
+ ---
2
+ name: keystroke-triggers
3
+ description: Wire keystroke workflows to cron, webhook, and poll sources in src/triggers/. Use when adding automation or auditing trigger runs.
4
+ metadata:
5
+ keystroke-domain: triggers
6
+ ---
7
+
8
+ # Triggers
9
+
10
+ Triggers **attach** a source to a target (a workflow or an agent). No business logic here — only schedule, endpoint, validation, and filters.
11
+
12
+ Attachment id: `{sourceSlug}:{targetSlug}` (e.g. `signup:signup-pipeline`), where the suffix is the workflow's (or agent's) `slug`.
13
+
14
+ ## Cron
15
+
16
+ ```ts
17
+ import { defineCronSource } from "@keystrokehq/keystroke/trigger";
18
+ import workflow from "../workflows/morning-check";
19
+
20
+ export default defineCronSource({
21
+ slug: "morning-check",
22
+ schedule: "0 9 * * *",
23
+ }).attach({ workflow });
24
+ ```
25
+
26
+ ## Webhook
27
+
28
+ ```ts
29
+ import { defineWebhookSource } from "@keystrokehq/keystroke/trigger";
30
+ import { z } from "zod";
31
+ import workflow from "../workflows/signup-pipeline";
32
+
33
+ export default defineWebhookSource({
34
+ slug: "signup",
35
+ endpoint: "signup",
36
+ request: z.object({
37
+ name: z.string().trim().min(1),
38
+ email: z.string().email(),
39
+ company: z.string().trim().min(1),
40
+ }),
41
+ }).attach({ workflow });
42
+ ```
43
+
44
+ Use optional Zod `filter` for extra constraints beyond `request`:
45
+
46
+ ```ts
47
+ filter: z.object({ type: z.literal("invoice.paid") }),
48
+ ```
49
+
50
+ **Webhooks ack asynchronously.** The POST returns immediately — `202 { runId }` when a binding matches, or `{ ok: true, skipped: true }` when nothing does. The workflow runs in the background; its output is **not** returned in the HTTP response (there's no "respond to webhook"). To return data to the caller, make an outbound call from the workflow, or have the caller poll the run via the runs API / `keystroke workflow runs get`.
51
+
52
+ ### Shared endpoint (e.g. Stripe)
53
+
54
+ Multiple trigger files can use the same `endpoint` — one URL `POST /triggers/{endpoint}`, each with its own `slug`, `request`, `filter`, and `transform`. Unmatched payloads return `{ ok: true, skipped: true }`.
55
+
56
+ ```ts
57
+ // src/triggers/stripe-invoice-paid.ts
58
+ export default defineWebhookSource({
59
+ slug: "stripe-invoice-paid",
60
+ endpoint: "stripe",
61
+ request: z.object({ type: z.string(), data: z.object({ id: z.string() }) }),
62
+ filter: z.object({ type: z.literal("invoice.paid") }),
63
+ }).attach({ workflow: invoicePaidWorkflow, transform: (p) => ({ invoiceId: p.data.id }) });
64
+
65
+ // src/triggers/stripe-subscription-deleted.ts — same endpoint, different slug/schema/filter
66
+ ```
67
+
68
+ List or inspect all triggers on an endpoint:
69
+
70
+ ```bash
71
+ keystroke trigger list --endpoint stripe
72
+ keystroke trigger get stripe # same rows as list --endpoint (shared route)
73
+ keystroke trigger url stripe # one webhook URL for the route
74
+ ```
75
+
76
+ Use each trigger's `slug` for `trigger get` / run history (`keystroke trigger runs list stripe-invoice-paid:…`).
77
+
78
+ ## Poll
79
+
80
+ ```ts
81
+ import { definePollSource } from "@keystrokehq/keystroke/trigger";
82
+ import workflow from "../workflows/new-inbox";
83
+
84
+ export default definePollSource({
85
+ slug: "new-inbox",
86
+ schedule: "*/5 * * * *",
87
+ run: () => ({ emails: [] }),
88
+ }).attach({ workflow });
89
+ ```
90
+
91
+ Poll filtering uses a **function predicate** (not a Zod schema like webhooks) — either `.filter((result) => …)` chained on the source, or a `filter:` option. Returning falsy skips the tick. Group polls that should run together with `definePollSource({ id: "...", … })`.
92
+
93
+ ### Ephemeral poll (agents)
94
+
95
+ Agents can register a scheduled codemode script with `set_trigger`:
96
+
97
+ ```ts
98
+ set_trigger({
99
+ kind: "poll",
100
+ slug: "inbox",
101
+ schedule: "*/5 * * * *",
102
+ code: [
103
+ 'const emails = await tools["list-emails"]({ query: "is:unread" });',
104
+ "if (emails.items.length === 0) return;",
105
+ "console.log(JSON.stringify({ count: emails.items.length, items: emails.items }));",
106
+ ].join("\n"),
107
+ prompt: "You have {{payload.count}} unread emails.",
108
+ lifecycle: { maxExecutions: 10 }, // optional: cap runs (also `until`)
109
+ });
110
+ ```
111
+
112
+ - Write the script the same way you would for codemode (`bash` + `js-exec`).
113
+ - `console.log(JSON.stringify(result))` when there is work to do.
114
+ - Log nothing (or `null`) to skip — skipped ticks do not count toward `lifecycle.maxExecutions`.
115
+ - Prompt interpolation matches webhooks (`{{payload.path}}`).
116
+
117
+ ## Attachment patterns
118
+
119
+ - **Attach to an agent** instead of a workflow: `.attach({ agent, prompt })` — the source's payload drives the agent prompt (interpolated like webhooks).
120
+ - **Fan-out to multiple targets**: chain `.attach(...).attach(...)` (or export an array of attachments) to bind one source to several workflows/agents.
121
+ - **Shared source definitions** can live under `src/triggers/sources/` — that subfolder is excluded from attachment discovery, so it's a safe place for source defs you import elsewhere.
122
+ - Sources also accept optional `name` / `description` metadata.
123
+
124
+ ## Develop & audit
125
+
126
+ While building, invoke the workflow directly:
127
+
128
+ ```bash
129
+ keystroke workflow run signup-pipeline --input '{"name":"Ada","email":"a@acme.com","company":"Acme"}'
130
+ ```
131
+
132
+ Inspect trigger-driven runs:
133
+
134
+ ```bash
135
+ keystroke trigger runs list signup:signup-pipeline # --limit / --cursor / --trigger-type
136
+ keystroke trigger runs get signup:signup-pipeline <run-id> --include workflows,trace
137
+ keystroke trigger poll <poll-attachment-id> # on-demand poll (--group to run a poll group)
138
+ keystroke trigger attachment disable <trigger-slug> <attachment-id> # pause; `enable` to resume
139
+ ```
140
+
141
+ Check `src/triggers/` in your project for existing patterns before adding new ones.
142
+
143
+ Related: [workflows](.agents/skills/keystroke-workflows/SKILL.md), [channels](.agents/skills/keystroke-channels/SKILL.md).
@@ -0,0 +1,78 @@
1
+ ---
2
+ name: keystroke-workflows
3
+ description: Build keystroke workflows with defineWorkflow — chain actions, call agents, typed Zod IO. Use when authoring src/workflows/ or auditing workflow runs.
4
+ metadata:
5
+ keystroke-domain: workflows
6
+ ---
7
+
8
+ # Workflows
9
+
10
+ Workflows are **deterministic orchestration**: Zod input/output, a `run` function, and **actions as steps**. Triggers and the CLI invoke them; agents can participate inside actions.
11
+
12
+ ## Example: action chain
13
+
14
+ ```ts
15
+ import { defineWorkflow } from "@keystrokehq/keystroke/workflow";
16
+ import { slackSendMessage } from "@keystrokehq/slack/actions";
17
+ import { z } from "zod";
18
+ import { researchSignup } from "../actions/research-signup";
19
+ import { signupBriefMessage } from "../lib/signup";
20
+
21
+ export default defineWorkflow({
22
+ slug: "signup-pipeline",
23
+ input: z.object({ name: z.string(), email: z.string().email(), company: z.string() }),
24
+ output: z.object({ brief: z.string(), channel: z.string(), ts: z.string() }),
25
+ async run(input) {
26
+ const { brief } = await researchSignup.run(input);
27
+ const sent = await slackSendMessage.run({
28
+ channel: "#pipeline",
29
+ markdown_text: signupBriefMessage({ ...input, brief }),
30
+ });
31
+ return { brief, ...sent }; // sent provides { channel, ts }; merge in brief for the output schema
32
+ },
33
+ });
34
+ ```
35
+
36
+ `research-signup` calls an agent; `slackSendMessage` is an integration action used **directly as a step** here — never wrapped in a custom action. Note the `run` return is spread to satisfy the `output` schema (the Slack action only returns `channel`/`ts`). Keep orchestration in the workflow; an action is a single leaf step and never calls another action.
37
+
38
+ ## Run & audit
39
+
40
+ ```bash
41
+ keystroke workflow run signup-pipeline --input '{"name":"Ada","email":"ada@acme.com","company":"Acme"}'
42
+ keystroke workflow runs list signup-pipeline
43
+ keystroke workflow runs get signup-pipeline <run-id> --include steps,trace
44
+ ```
45
+
46
+ ## How workflows get invoked
47
+
48
+ | From | How |
49
+ | ---------- | --------------------------------------------------------------------- |
50
+ | CLI | `keystroke workflow run {slug} --input '{...}'` |
51
+ | HTTP | `POST /workflows/{slug}` |
52
+ | Trigger | cron / webhook / poll attachment in `src/triggers/` |
53
+ | Agent tool | `defineWorkflowTool(workflow)` from `@keystrokehq/runtime` on an agent |
54
+ | Sub-workflow step | import the workflow and `await otherWorkflow.run(input)` inside another workflow's `run` body |
55
+
56
+ To reuse a workflow inside another workflow, import it and call `await otherWorkflow.run(input)` — a first-class durable step, just like an action or agent (validates the sub-workflow's IO, checkpoints its output, gets its own trace span). The sub-workflow runs inline in the same run, so its inner steps and `ctx.sleep`/`ctx.hook` stay durable; it does **not** spawn a separate tracked run. Never thread `ctx` into it (`otherWorkflow.run(input, ctx)` is not a thing). To run a workflow as its *own* tracked run, expose it as an agent tool (`defineWorkflowTool`) or invoke it over HTTP.
57
+
58
+ The workflow `slug` is its identifier and route key. Slugs are unique per primitive kind, so a workflow can share a slug with an agent or trigger, but two workflows cannot share one.
59
+
60
+ ## Durability (the model you must design for)
61
+
62
+ Workflows are **durable**: each `await` of an action, agent `.prompt()`, or `promptLlm` is recorded as a `step_completed` event. If a later step fails, the run **replays** the log — completed steps return their recorded result instead of running again — and resumes at the first unfinished step. Two rules follow:
63
+
64
+ - **Side effects go inside steps.** Code in the `run` body that isn't a step (network calls, writes, `Date.now()`, random) re-executes on every replay. Wrap it in an action/agent/`promptLlm` call so it's recorded once.
65
+ - **Keep control flow deterministic, steps idempotent.** Branch on input and recorded step results, not on values that change between attempts. A step can be retried after a transient failure, so design actions to tolerate being called twice with the same input. Retries are automatic — you don't write retry loops; the runtime emits `step_retrying` / `step_failed`.
66
+
67
+ Step ids are correlation ids: `step:<slug>#<occurrence>` (`#0`, `#1`, …), or `step:<id>` when pinned with `.stepId()`. If an action's position can shift, pin a stable `.stepId()` so replays line up. Durable waits (`ctx.sleep`, `ctx.hook`) suspend the run without holding a process open. See [authoring.md](references/authoring.md).
68
+
69
+ ## Testing
70
+
71
+ Unit-test through `executeWorkflow` from `@keystrokehq/keystroke/workflow` — never call `workflow.run(...)` directly at the top level of a test (outside a run it has no durable context). Stub costly actions by seeding `step_completed` events in a `MemoryEventLog`. See [testing.md](references/testing.md).
72
+
73
+ ## Next references
74
+
75
+ - [authoring.md](references/authoring.md) — agents in actions, app connections, composition
76
+ - [testing.md](references/testing.md) — unit tests, stubbing actions, run/trace debugging
77
+
78
+ Related: [actions](.agents/skills/keystroke-actions/SKILL.md), [triggers](.agents/skills/keystroke-triggers/SKILL.md), [agents](.agents/skills/keystroke-agents/SKILL.md).