Skip to content

Commit b2dadde

Browse files
committed
first commit
0 parents  commit b2dadde

29 files changed

+16208
-0
lines changed

.github/workflows/ci.yml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
build-and-test:
11+
runs-on: ubuntu-latest
12+
13+
strategy:
14+
matrix:
15+
node-version: [18, 20, 22]
16+
17+
steps:
18+
- name: Checkout repository
19+
uses: actions/checkout@v4
20+
21+
- name: Setup Node.js ${{ matrix.node-version }}
22+
uses: actions/setup-node@v4
23+
with:
24+
node-version: ${{ matrix.node-version }}
25+
cache: 'npm'
26+
27+
- name: Install dependencies
28+
run: npm ci
29+
30+
- name: Type check
31+
run: npm run typecheck
32+
33+
- name: Run tests
34+
run: npm test
35+
36+
- name: Build
37+
run: npm run build
38+
39+
- name: Verify build output
40+
run: |
41+
test -f dist/index.js || (echo "Build output missing: dist/index.js" && exit 1)
42+
test -f dist/index.d.ts || (echo "Build output missing: dist/index.d.ts" && exit 1)

.gitignore

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Dependencies
2+
node_modules/
3+
npm-debug.log*
4+
yarn-debug.log*
5+
yarn-error.log*
6+
7+
# Build output
8+
dist/
9+
build/
10+
*.tsbuildinfo
11+
12+
# Environment variables
13+
.env
14+
.env.local
15+
.env.*.local
16+
17+
# IDE
18+
.vscode/
19+
.idea/
20+
*.swp
21+
*.swo
22+
*~
23+
.DS_Store
24+
25+
# Testing
26+
coverage/
27+
.nyc_output/
28+
29+
# Misc
30+
*.log
31+
.cache/

README.md

Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
# opencode-workflows
2+
3+
Workflow automation plugin for OpenCode using the Mastra workflow engine. Define deterministic, multi-step processes that agents can trigger to perform complex tasks reliably.
4+
5+
## Features
6+
7+
- **Deterministic Automation**: Define rigid, multi-step processes (DAGs) in JSON
8+
- **Agentic Triggering**: Agents can call workflows as tools
9+
- **Hybrid Execution**: Mix shell commands, API calls, and LLM prompts
10+
- **Human-in-the-Loop**: Suspend workflows for human approval
11+
- **Parallel Execution**: Run independent steps concurrently
12+
13+
## Installation
14+
15+
```bash
16+
npm install opencode-workflows
17+
```
18+
19+
## Configuration
20+
21+
Add to your `opencode.json`:
22+
23+
```json
24+
{
25+
"plugin": ["opencode-workflows"]
26+
}
27+
```
28+
29+
### Plugin Options
30+
31+
```json
32+
{
33+
"plugin": [
34+
["opencode-workflows", {
35+
"workflowDirs": [".opencode/workflows"],
36+
"dbPath": ".opencode/data/workflows.db",
37+
"verbose": false
38+
}]
39+
]
40+
}
41+
```
42+
43+
| Option | Type | Default | Description |
44+
|--------|------|---------|-------------|
45+
| `workflowDirs` | `string[]` | `[".opencode/workflows"]` | Directories to scan for workflow JSON files |
46+
| `dbPath` | `string` | `".opencode/data/workflows.db"` | Path to SQLite database for persisting workflow runs |
47+
| `verbose` | `boolean` | `false` | Enable verbose logging |
48+
49+
### Persistence
50+
51+
Workflow runs are automatically persisted to a LibSQL (SQLite) database. This enables:
52+
53+
- **Crash Recovery**: Active runs are restored on plugin restart
54+
- **Run History**: Query past workflow executions via `/workflow runs`
55+
- **Suspend/Resume**: Suspended workflows survive session restarts
56+
57+
The database is created automatically at the configured `dbPath`.
58+
59+
## Workflow Definitions
60+
61+
Create workflow definitions in `.opencode/workflows/` as JSON files:
62+
63+
```json
64+
{
65+
"id": "deploy-prod",
66+
"description": "Deploys the application to production",
67+
"inputs": {
68+
"version": "string"
69+
},
70+
"steps": [
71+
{
72+
"id": "check-git",
73+
"type": "shell",
74+
"command": "git status --porcelain",
75+
"description": "Ensure git is clean"
76+
},
77+
{
78+
"id": "run-tests",
79+
"type": "shell",
80+
"command": "npm test",
81+
"after": ["check-git"]
82+
},
83+
{
84+
"id": "ask-approval",
85+
"type": "suspend",
86+
"description": "Wait for user to approve deployment",
87+
"after": ["run-tests"]
88+
},
89+
{
90+
"id": "deploy-script",
91+
"type": "shell",
92+
"command": "npm run deploy -- --tag {{inputs.version}}",
93+
"after": ["ask-approval"]
94+
}
95+
]
96+
}
97+
```
98+
99+
## Step Types
100+
101+
### Shell Step
102+
Execute shell commands:
103+
```json
104+
{
105+
"id": "build",
106+
"type": "shell",
107+
"command": "npm run build",
108+
"cwd": "./packages/app",
109+
"env": { "NODE_ENV": "production" },
110+
"failOnError": true
111+
}
112+
```
113+
114+
### Tool Step
115+
Invoke OpenCode tools:
116+
```json
117+
{
118+
"id": "send-notification",
119+
"type": "tool",
120+
"tool": "slack_send",
121+
"args": {
122+
"channel": "#releases",
123+
"text": "Deployed {{inputs.version}}"
124+
}
125+
}
126+
```
127+
128+
### Agent Step
129+
Prompt an LLM:
130+
```json
131+
{
132+
"id": "generate-changelog",
133+
"type": "agent",
134+
"prompt": "Generate a changelog for version {{inputs.version}}",
135+
"model": "gpt-4",
136+
"maxTokens": 1000
137+
}
138+
```
139+
140+
### Suspend Step
141+
Pause for human input:
142+
```json
143+
{
144+
"id": "approval",
145+
"type": "suspend",
146+
"message": "Ready to deploy. Resume to continue.",
147+
"description": "Wait for deployment approval"
148+
}
149+
```
150+
151+
### HTTP Step
152+
Make HTTP requests:
153+
```json
154+
{
155+
"id": "notify-slack",
156+
"type": "http",
157+
"method": "POST",
158+
"url": "https://hooks.slack.com/services/xxx",
159+
"headers": {
160+
"Content-Type": "application/json"
161+
},
162+
"body": {
163+
"text": "Deployed {{inputs.version}}"
164+
},
165+
"failOnError": true
166+
}
167+
```
168+
169+
HTTP step output includes:
170+
- `body` - Parsed JSON response, or `null` if response is not valid JSON
171+
- `text` - Raw response text (useful for non-JSON responses or debugging)
172+
- `status` - HTTP status code
173+
- `headers` - Response headers
174+
175+
### File Step
176+
Read, write, or delete files:
177+
```json
178+
{
179+
"id": "write-version",
180+
"type": "file",
181+
"action": "write",
182+
"path": "./version.txt",
183+
"content": "{{inputs.version}}"
184+
}
185+
```
186+
187+
```json
188+
{
189+
"id": "read-config",
190+
"type": "file",
191+
"action": "read",
192+
"path": "./config.json"
193+
}
194+
```
195+
196+
## Commands
197+
198+
Use the `/workflow` command:
199+
200+
- `/workflow list` - List available workflows
201+
- `/workflow show <id>` - Show workflow details
202+
- `/workflow run <id> [param=value ...]` - Run a workflow
203+
- `/workflow status <runId>` - Check run status
204+
- `/workflow resume <runId> [data]` - Resume a suspended workflow
205+
- `/workflow cancel <runId>` - Cancel a running workflow
206+
- `/workflow runs [workflowId]` - List recent runs
207+
208+
### Parameter Type Inference
209+
210+
When passing parameters via `/workflow run`, values are automatically converted to their appropriate types:
211+
212+
| Input | Parsed As |
213+
|-------|-----------|
214+
| `count=5` | `number` (5) |
215+
| `ratio=3.14` | `number` (3.14) |
216+
| `enabled=true` | `boolean` (true) |
217+
| `debug=false` | `boolean` (false) |
218+
| `name=hello` | `string` ("hello") |
219+
| `url=http://example.com?foo=bar` | `string` (preserved) |
220+
221+
This ensures workflow inputs match their expected schema types without manual conversion.
222+
223+
## Agent Tool
224+
225+
Agents can trigger workflows using the `workflow` tool:
226+
227+
```typescript
228+
// List workflows
229+
workflow({ mode: "list" })
230+
231+
// Run a workflow
232+
workflow({
233+
mode: "run",
234+
workflowId: "deploy-prod",
235+
params: { version: "1.2.0" }
236+
})
237+
238+
// Check status
239+
workflow({ mode: "status", runId: "abc-123" })
240+
241+
// Resume suspended workflow
242+
workflow({
243+
mode: "resume",
244+
runId: "abc-123",
245+
resumeData: { approved: true }
246+
})
247+
```
248+
249+
## Template Interpolation
250+
251+
Use `{{expression}}` syntax to reference:
252+
- `{{inputs.paramName}}` - Workflow input parameters
253+
- `{{steps.stepId.stdout}}` - Shell step stdout
254+
- `{{steps.stepId.response}}` - Agent step response
255+
- `{{steps.stepId.result}}` - Tool step result
256+
- `{{steps.stepId.body}}` - HTTP step response body (parsed JSON or null)
257+
- `{{steps.stepId.text}}` - HTTP step raw response text
258+
- `{{steps.stepId.content}}` - File step content (read action)
259+
- `{{env.VAR_NAME}}` - Environment variables
260+
- `{{run.id}}` - Current workflow run ID
261+
- `{{run.workflowId}}` - Workflow definition ID
262+
- `{{run.startedAt}}` - ISO timestamp when run started
263+
264+
### Type Preservation
265+
266+
When a template contains only a single variable reference (e.g., `"{{inputs.count}}"`), the original type is preserved. This means:
267+
- `"{{inputs.count}}"` with `count=42` returns the number `42`, not the string `"42"`
268+
- `"Count: {{inputs.count}}"` returns `"Count: 42"` (string interpolation)
269+
270+
### Conditional Execution
271+
272+
Steps can include a `condition` to control execution:
273+
```json
274+
{
275+
"id": "deploy-prod",
276+
"type": "shell",
277+
"command": "deploy.sh",
278+
"condition": "{{inputs.environment}}"
279+
}
280+
```
281+
The step is skipped if the condition evaluates to `"false"`, `"0"`, or `""`.
282+
283+
## Dependencies
284+
285+
Steps can declare dependencies using `after`:
286+
287+
```json
288+
{
289+
"id": "deploy",
290+
"type": "shell",
291+
"command": "deploy.sh",
292+
"after": ["build", "test"]
293+
}
294+
```
295+
296+
Steps at the same dependency level run in parallel.
297+
298+
## License
299+
300+
MIT

0 commit comments

Comments
 (0)