@dura-run/cli 0.1.5 → 0.2.1

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.
@@ -0,0 +1,379 @@
1
+ # dura.run — Development Guide
2
+
3
+ > This skill teaches an AI agent how to scaffold projects, write automation handlers, use SDK globals, and run locally with `dura dev`.
4
+
5
+ ## Scaffolding a New Project
6
+
7
+ ### `dura new <name>` — Create a New Project
8
+
9
+ Creates a new directory with the full project skeleton:
10
+
11
+ ```bash
12
+ # Create a project called "my-api"
13
+ dura new my-api
14
+
15
+ # Create in a specific parent directory
16
+ dura new my-api --dir ~/projects
17
+
18
+ # Create from a template
19
+ dura new my-api --template rest-api --org org_abc123
20
+ ```
21
+
22
+ **What gets created:**
23
+
24
+ ```
25
+ my-api/
26
+ ├── dura.json # Manifest with a "hello" automation
27
+ ├── routes/
28
+ │ └── hello.ts # Starter HTTP handler
29
+ ├── jobs/ # Empty directory for cron/event handlers
30
+ └── .gitignore # Ignores .env.local, node_modules, .dura, dist
31
+ ```
32
+
33
+ **Project name rules:** Must start with a letter, can contain letters, numbers, hyphens, and underscores.
34
+
35
+ ### `dura init` — Initialize an Existing Directory
36
+
37
+ Use `dura init` when you already have a directory and want to turn it into a dura project:
38
+
39
+ ```bash
40
+ cd my-existing-project
41
+ dura init
42
+ ```
43
+
44
+ This creates `dura.json`, starter files, and installs AI agent skills.
45
+
46
+ ## Writing Automation Handlers
47
+
48
+ ### Handler Signature
49
+
50
+ Every automation handler is a default-exported async function:
51
+
52
+ ```typescript
53
+ import type { DuraRequest, DuraResponse } from "@dura/sdk";
54
+
55
+ export default async function handler(req: DuraRequest): Promise<DuraResponse> {
56
+ // Your logic here
57
+ return {
58
+ status: 200,
59
+ headers: { "content-type": "application/json" },
60
+ body: { result: "success" },
61
+ };
62
+ }
63
+ ```
64
+
65
+ ### DuraRequest Shape
66
+
67
+ The `req` object contains:
68
+
69
+ ```typescript
70
+ interface DuraRequest {
71
+ method: string; // HTTP method (GET, POST, etc.)
72
+ path: string; // Request path
73
+ headers: Record<string, string>;
74
+ query: Record<string, string>;
75
+ body: unknown; // Parsed JSON body (or raw string)
76
+ }
77
+ ```
78
+
79
+ ### DuraResponse Shape
80
+
81
+ Return an object with:
82
+
83
+ ```typescript
84
+ interface DuraResponse {
85
+ status: number; // HTTP status code
86
+ headers?: Record<string, string>; // Response headers
87
+ body?: unknown; // JSON-serializable response body
88
+ }
89
+ ```
90
+
91
+ ### Example: REST API Handler
92
+
93
+ ```typescript
94
+ import type { DuraRequest, DuraResponse } from "@dura/sdk";
95
+
96
+ export default async function handler(req: DuraRequest): Promise<DuraResponse> {
97
+ if (req.method === "GET") {
98
+ const users = await dura.kv.get("users") as string[] ?? [];
99
+ return {
100
+ status: 200,
101
+ headers: { "content-type": "application/json" },
102
+ body: { users },
103
+ };
104
+ }
105
+
106
+ if (req.method === "POST") {
107
+ const { name, email } = req.body as { name: string; email: string };
108
+ if (!name || !email) {
109
+ return { status: 400, body: { error: "name and email are required" } };
110
+ }
111
+
112
+ const users = await dura.kv.get("users") as string[] ?? [];
113
+ users.push(name);
114
+ await dura.kv.set("users", users);
115
+
116
+ dura.log.info("User created", { name, email });
117
+ return { status: 201, body: { created: true, name } };
118
+ }
119
+
120
+ return { status: 405, body: { error: "Method not allowed" } };
121
+ }
122
+ ```
123
+
124
+ ### Example: Cron Job Handler
125
+
126
+ ```typescript
127
+ import type { DuraRequest, DuraResponse } from "@dura/sdk";
128
+
129
+ export default async function handler(req: DuraRequest): Promise<DuraResponse> {
130
+ dura.log.info("Starting daily cleanup");
131
+
132
+ const response = await dura.fetch("https://api.example.com/stale-records", {
133
+ method: "DELETE",
134
+ headers: { Authorization: `Bearer ${dura.env.get("API_TOKEN")}` },
135
+ });
136
+
137
+ const result = await response.json();
138
+ dura.log.info("Cleanup complete", { deleted: result.count });
139
+
140
+ return { status: 200, body: { deleted: result.count } };
141
+ }
142
+ ```
143
+
144
+ ### Example: Chained Automation
145
+
146
+ ```typescript
147
+ // routes/process-order.ts
148
+ import type { DuraRequest, DuraResponse } from "@dura/sdk";
149
+
150
+ export default async function handler(req: DuraRequest): Promise<DuraResponse> {
151
+ const order = req.body as { orderId: string; items: string[] };
152
+
153
+ // Process the order...
154
+ dura.log.info("Order processed", { orderId: order.orderId });
155
+
156
+ // Trigger the notification automation (fire-and-forget)
157
+ await dura.trigger("send-notification", {
158
+ type: "order_complete",
159
+ orderId: order.orderId,
160
+ });
161
+
162
+ // Or wait for the result
163
+ const result = await dura.triggerAndWait("calculate-shipping", {
164
+ items: order.items,
165
+ });
166
+
167
+ return { status: 200, body: { orderId: order.orderId, shipping: result?.body } };
168
+ }
169
+ ```
170
+
171
+ ## SDK Globals Reference
172
+
173
+ Inside automation handlers, the `dura` global object is always available. You do not need to import it — it is injected by the runtime.
174
+
175
+ ### `dura.log` — Structured Logging
176
+
177
+ ```typescript
178
+ dura.log.debug("Debug message");
179
+ dura.log.info("Processing request", { userId: "123" });
180
+ dura.log.warn("Rate limit approaching", { current: 95, max: 100 });
181
+ dura.log.error("Payment failed", { error: err.message, orderId });
182
+ ```
183
+
184
+ All log calls accept `(message: string, data?: unknown)`. The `data` argument is included as structured metadata in log entries, searchable via `dura logs --filter`.
185
+
186
+ ### `dura.fetch` — HTTP Client
187
+
188
+ ```typescript
189
+ // Works exactly like the standard fetch API
190
+ const res = await dura.fetch("https://api.example.com/data", {
191
+ method: "POST",
192
+ headers: { "Content-Type": "application/json" },
193
+ body: JSON.stringify({ key: "value" }),
194
+ });
195
+ const data = await res.json();
196
+ ```
197
+
198
+ In production, `dura.fetch` routes through the executor's network sandbox. In local dev (`dura dev`), it uses the standard `fetch` directly.
199
+
200
+ ### `dura.env` — Environment Variables
201
+
202
+ ```typescript
203
+ const dbUrl = dura.env.get("DATABASE_URL");
204
+ const apiKey = dura.env.get("STRIPE_SECRET_KEY");
205
+
206
+ if (!dbUrl) {
207
+ dura.log.error("DATABASE_URL is not set");
208
+ return { status: 500, body: { error: "Configuration error" } };
209
+ }
210
+ ```
211
+
212
+ In local dev, env vars are loaded from `.env.local`. In production, they come from the environment's secret store (managed via `dura secrets`).
213
+
214
+ ### `dura.kv` — Key-Value Store
215
+
216
+ ```typescript
217
+ // Set a value
218
+ await dura.kv.set("user:123", { name: "Alice", role: "admin" });
219
+
220
+ // Set with TTL (seconds)
221
+ await dura.kv.set("session:abc", { token: "xyz" }, { ttl: 3600 });
222
+
223
+ // Get a value
224
+ const user = await dura.kv.get("user:123");
225
+
226
+ // Check existence
227
+ const exists = await dura.kv.has("user:123");
228
+
229
+ // Delete
230
+ await dura.kv.delete("user:123");
231
+
232
+ // List keys by prefix
233
+ const userKeys = await dura.kv.list("user:");
234
+
235
+ // Atomic increment
236
+ await dura.kv.incr("page-views", 1);
237
+ ```
238
+
239
+ In local dev, KV is backed by an in-memory Map (resets when the dev server restarts). In production, it uses Redis.
240
+
241
+ ### `dura.trigger` / `dura.triggerAndWait` — Chain Automations
242
+
243
+ ```typescript
244
+ // Fire-and-forget — trigger another automation without waiting
245
+ await dura.trigger("send-email", { to: "user@example.com", subject: "Hello" });
246
+
247
+ // Trigger and wait for the result
248
+ const result = await dura.triggerAndWait("validate-address", {
249
+ street: "123 Main St",
250
+ city: "Springfield",
251
+ });
252
+ // result = { status: 200, body: { valid: true } }
253
+ ```
254
+
255
+ ## Local Development with `dura dev`
256
+
257
+ ### Starting the Dev Server
258
+
259
+ ```bash
260
+ # Start in the current directory on port 3000 (default)
261
+ dura dev
262
+
263
+ # Specify a different directory or port
264
+ dura dev --dir ./my-project --port 8080
265
+ ```
266
+
267
+ ### What `dura dev` Does
268
+
269
+ 1. **Reads `dura.json`** — discovers all automations and their triggers
270
+ 2. **Loads `.env.local`** — injects secrets into the dev environment
271
+ 3. **Starts HTTP server** — maps HTTP triggers to handler files
272
+ 4. **Starts cron scheduler** — schedules cron triggers locally
273
+ 5. **Starts file watcher** — watches `routes/`, `jobs/`, `lib/` for hot-reload
274
+ 6. **Installs SDK globals** — sets up `dura.log`, `dura.fetch`, `dura.env`, `dura.kv`, `dura.trigger`
275
+
276
+ ### Testing Locally
277
+
278
+ Once the dev server is running:
279
+
280
+ ```bash
281
+ # Test an HTTP handler
282
+ curl http://localhost:3000/hello
283
+
284
+ # Test a POST handler
285
+ curl -X POST http://localhost:3000/webhook \
286
+ -H "Content-Type: application/json" \
287
+ -d '{"event": "user.created"}'
288
+
289
+ # Manually trigger any automation
290
+ dura run hello --project local
291
+ ```
292
+
293
+ ### Local Environment File
294
+
295
+ Create `.env.local` in the project root for local secrets:
296
+
297
+ ```
298
+ DATABASE_URL=postgres://localhost:5432/mydb
299
+ API_TOKEN=sk_test_abc123
300
+ SLACK_WEBHOOK_URL=https://hooks.slack.com/...
301
+ ```
302
+
303
+ This file is gitignored by default. These values are accessible via `dura.env.get("DATABASE_URL")` in your handlers.
304
+
305
+ ## Registering Automations in `dura.json`
306
+
307
+ ### Adding an HTTP Automation
308
+
309
+ ```json
310
+ {
311
+ "name": "get-users",
312
+ "entrypoint": "routes/users.ts",
313
+ "triggers": [
314
+ { "type": "http", "method": "GET", "path": "/users" }
315
+ ]
316
+ }
317
+ ```
318
+
319
+ ### Adding a Cron Job
320
+
321
+ ```json
322
+ {
323
+ "name": "daily-report",
324
+ "entrypoint": "jobs/daily-report.ts",
325
+ "triggers": [
326
+ { "type": "cron", "schedule": "0 9 * * *" }
327
+ ],
328
+ "config": {
329
+ "timeout": 60000,
330
+ "retries": 2
331
+ }
332
+ }
333
+ ```
334
+
335
+ ### Adding a Chained Automation
336
+
337
+ ```json
338
+ {
339
+ "name": "send-notification",
340
+ "entrypoint": "routes/notify.ts",
341
+ "triggers": [
342
+ { "type": "chain", "from": "process-order" }
343
+ ]
344
+ }
345
+ ```
346
+
347
+ ### Adding a Manual-Only Automation
348
+
349
+ ```json
350
+ {
351
+ "name": "migrate-data",
352
+ "entrypoint": "jobs/migrate.ts",
353
+ "triggers": [
354
+ { "type": "manual" }
355
+ ],
356
+ "config": {
357
+ "timeout": 120000,
358
+ "memory": 512
359
+ }
360
+ }
361
+ ```
362
+
363
+ ### Project-Level Config
364
+
365
+ Set defaults that apply to all automations:
366
+
367
+ ```json
368
+ {
369
+ "name": "my-project",
370
+ "automations": [...],
371
+ "config": {
372
+ "timeout": 15000,
373
+ "memory": 128,
374
+ "concurrency": 10
375
+ }
376
+ }
377
+ ```
378
+
379
+ Per-automation `config` overrides project-level `config`.
@@ -0,0 +1,196 @@
1
+ # dura.run — Platform Overview
2
+
3
+ > This skill teaches an AI agent the core concepts, project structure, and deployment lifecycle of the dura.run automation platform.
4
+
5
+ ## What Is dura.run?
6
+
7
+ dura.run is a managed automation platform where developers build, deploy, and debug JavaScript/TypeScript automations. Automations run in secure V8 isolates on the dura cloud — you write handler functions, define triggers, and dura handles execution, scaling, logging, and monitoring.
8
+
9
+ Think of it as "serverless functions with built-in observability and an automation-first developer experience."
10
+
11
+ ## Core Concepts
12
+
13
+ ### Automations
14
+
15
+ An automation is a single unit of work — a TypeScript function that receives a request and returns a response. Each automation has:
16
+
17
+ - **Name** — unique identifier within a project (e.g., `sync-users`, `process-payment`)
18
+ - **Entrypoint** — path to the handler file (e.g., `routes/sync-users.ts`)
19
+ - **Triggers** — how the automation gets invoked (HTTP, cron, event, manual, chain)
20
+ - **Config** — optional overrides for timeout, memory, concurrency, and retries
21
+
22
+ ### Triggers
23
+
24
+ Automations can be triggered in five ways:
25
+
26
+ | Type | Description | Example |
27
+ |------|-------------|---------|
28
+ | `http` | Incoming HTTP request at a specific path and method | `GET /api/users` |
29
+ | `cron` | Scheduled execution on a cron expression | `0 */6 * * *` (every 6 hours) |
30
+ | `event` | Fired when an external event source emits a matching event | Webhook relay events |
31
+ | `manual` | Triggered explicitly via CLI (`dura run`) or API | One-off tasks, debugging |
32
+ | `chain` | Triggered by another automation completing | Multi-step pipelines |
33
+
34
+ ### Projects
35
+
36
+ A project is a collection of related automations deployed together. Every project has:
37
+
38
+ - An organization owner
39
+ - A `dura.json` manifest defining all automations and their triggers
40
+ - One or more environments (production, staging, preview, custom)
41
+ - A deployment history with rollback capability
42
+
43
+ ### Organizations
44
+
45
+ Organizations own projects and manage team access. Each user gets a personal workspace (organization) on signup. Organizations have:
46
+
47
+ - Members with roles: `owner`, `admin`, `member`
48
+ - API keys scoped to the org
49
+ - Usage quotas and billing (free, pro, enterprise plans)
50
+
51
+ ### Environments
52
+
53
+ Environments isolate deployments. A project can have multiple environments:
54
+
55
+ - **production** — live traffic, default deployment target
56
+ - **staging** — pre-production testing
57
+ - **preview** — ephemeral environments for branch previews
58
+ - **custom** — user-defined environments
59
+
60
+ Each environment has its own secrets, deployment state, and URL.
61
+
62
+ ## Project Structure
63
+
64
+ A dura project is a directory with this layout:
65
+
66
+ ```
67
+ my-project/
68
+ ├── dura.json # Project manifest — defines automations and triggers
69
+ ├── routes/ # HTTP-triggered automation handlers
70
+ │ ├── hello.ts
71
+ │ └── users.ts
72
+ ├── jobs/ # Cron and event-triggered handlers
73
+ │ └── cleanup.ts
74
+ ├── lib/ # Shared utilities (imported by handlers)
75
+ │ └── db.ts
76
+ ├── .env.local # Local secrets (never committed)
77
+ ├── .gitignore
78
+ └── node_modules/
79
+ ```
80
+
81
+ ### `dura.json` — The Project Manifest
82
+
83
+ The manifest is the single source of truth for what a project contains. Example:
84
+
85
+ ```json
86
+ {
87
+ "name": "my-project",
88
+ "automations": [
89
+ {
90
+ "name": "hello",
91
+ "entrypoint": "routes/hello.ts",
92
+ "triggers": [
93
+ { "type": "http", "method": "GET", "path": "/hello" }
94
+ ]
95
+ },
96
+ {
97
+ "name": "sync-users",
98
+ "entrypoint": "jobs/sync-users.ts",
99
+ "triggers": [
100
+ { "type": "cron", "schedule": "0 */6 * * *" }
101
+ ],
102
+ "config": {
103
+ "timeout": 30000,
104
+ "retries": 3
105
+ }
106
+ },
107
+ {
108
+ "name": "process-webhook",
109
+ "entrypoint": "routes/webhook.ts",
110
+ "triggers": [
111
+ { "type": "http", "method": "POST", "path": "/webhook" }
112
+ ]
113
+ }
114
+ ],
115
+ "env": [
116
+ { "name": "DATABASE_URL", "required": true, "description": "Postgres connection string" },
117
+ { "name": "SLACK_TOKEN", "required": false }
118
+ ],
119
+ "config": {
120
+ "timeout": 10000,
121
+ "memory": 128
122
+ }
123
+ }
124
+ ```
125
+
126
+ **Manifest fields:**
127
+
128
+ | Field | Required | Description |
129
+ |-------|----------|-------------|
130
+ | `name` | Yes | Project name — letters, numbers, hyphens, underscores |
131
+ | `automations` | Yes | Array of automation definitions (at least one) |
132
+ | `automations[].name` | Yes | Automation name — must start with a letter |
133
+ | `automations[].entrypoint` | Yes | Path to the handler file (relative to project root) |
134
+ | `automations[].triggers` | No | Array of trigger configurations |
135
+ | `automations[].config` | No | Per-automation overrides (timeout, memory, concurrency, retries) |
136
+ | `env` | No | Declared environment variables the project uses |
137
+ | `config` | No | Project-level defaults for timeout, memory, concurrency |
138
+
139
+ ### Handler File Format
140
+
141
+ Every handler file must export a default async function:
142
+
143
+ ```typescript
144
+ import type { DuraRequest, DuraResponse } from "@dura/sdk";
145
+
146
+ export default async function handler(req: DuraRequest): Promise<DuraResponse> {
147
+ return {
148
+ status: 200,
149
+ headers: { "content-type": "application/json" },
150
+ body: { message: "Hello from Dura!" },
151
+ };
152
+ }
153
+ ```
154
+
155
+ ## Deployment Lifecycle
156
+
157
+ 1. **Develop** — Write handlers locally, test with `dura dev`
158
+ 2. **Bundle** — `dura deploy` bundles all automations into a deployable archive
159
+ 3. **Upload** — Bundle is uploaded to Dura's object storage via a presigned URL
160
+ 4. **Validate** — The control plane validates the manifest and bundle integrity
161
+ 5. **Activate** — If validation passes, the deployment becomes active
162
+ 6. **Execute** — Incoming triggers invoke automation handlers in V8 isolates
163
+ 7. **Monitor** — Execution logs, metrics, and diagnostics are available in real time
164
+
165
+ ### Deployment States
166
+
167
+ | Status | Meaning |
168
+ |--------|---------|
169
+ | `pending` | Deployment created, awaiting bundle upload |
170
+ | `building` | Bundle uploaded, being processed |
171
+ | `deploying` | Validation passed, activating |
172
+ | `active` | Live — serving traffic |
173
+ | `failed` | Validation or activation failed |
174
+ | `rolled_back` | Was active, replaced by a rollback |
175
+ | `superseded` | Was active, replaced by a newer deployment |
176
+
177
+ ## CLI Command Reference
178
+
179
+ | Command | Purpose |
180
+ |---------|---------|
181
+ | `dura login` | Authenticate with the dura control plane |
182
+ | `dura init` | Initialize a dura project in the current directory |
183
+ | `dura new <name>` | Scaffold a new dura project in a new directory |
184
+ | `dura dev` | Start local development server with hot-reload |
185
+ | `dura deploy` | Bundle and deploy to an environment |
186
+ | `dura run <name>` | Manually trigger an automation |
187
+ | `dura logs` | View execution logs |
188
+ | `dura diagnose` | AI-powered root cause analysis of failed executions |
189
+ | `dura replay <id>` | Replay a past execution |
190
+ | `dura test` | Run the project test suite |
191
+ | `dura rollback` | Revert to a previous deployment |
192
+ | `dura env` | Manage project environments |
193
+ | `dura secrets` | Manage project secrets |
194
+ | `dura deployments` | View deployment history |
195
+ | `dura status` | Show project and automation status |
196
+ | `dura config` | Manage CLI configuration |