@zibby/cli 0.4.14 → 0.4.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/zibby.js +2 -2
- package/dist/commands/init.js +64 -64
- package/dist/commands/workflows/generate.js +108 -108
- package/dist/commands/workflows/logs.js +16 -16
- package/dist/commands/workflows/run.js +7 -7
- package/dist/commands/workflows/schedule.js +10 -0
- package/dist/package.json +4 -2
- package/dist/templates/.claude/CLAUDE.md +425 -0
- package/dist/templates/.claude/commands/add-node.md +63 -0
- package/dist/templates/.claude/commands/add-skill.md +83 -0
- package/dist/templates/.claude/commands/new-workflow.md +61 -0
- package/dist/templates/.claude/commands/validate-workflow.md +67 -0
- package/dist/utils/session-uploader.js +1 -1
- package/package.json +4 -2
- package/templates/.claude/CLAUDE.md +425 -0
- package/templates/.claude/commands/add-node.md +63 -0
- package/templates/.claude/commands/add-skill.md +83 -0
- package/templates/.claude/commands/new-workflow.md +61 -0
- package/templates/.claude/commands/validate-workflow.md +67 -0
- package/templates/zibby-workflow-claude/agents-md-block.md +173 -0
- package/templates/zibby-workflow-claude/claude/agents/zibby-test-author.md +87 -0
- package/templates/zibby-workflow-claude/claude/agents/zibby-workflow-builder.md +101 -0
- package/templates/zibby-workflow-claude/claude/commands/zibby-add-node.md +75 -0
- package/templates/zibby-workflow-claude/claude/commands/zibby-debug.md +67 -0
- package/templates/zibby-workflow-claude/claude/commands/zibby-delete.md +37 -0
- package/templates/zibby-workflow-claude/claude/commands/zibby-deploy.md +87 -0
- package/templates/zibby-workflow-claude/claude/commands/zibby-list.md +30 -0
- package/templates/zibby-workflow-claude/claude/commands/zibby-memory-cost.md +39 -0
- package/templates/zibby-workflow-claude/claude/commands/zibby-memory-pull.md +47 -0
- package/templates/zibby-workflow-claude/claude/commands/zibby-memory-remote-use-hosted.md +61 -0
- package/templates/zibby-workflow-claude/claude/commands/zibby-memory-stats.md +38 -0
- package/templates/zibby-workflow-claude/claude/commands/zibby-static-ip.md +70 -0
- package/templates/zibby-workflow-claude/claude/commands/zibby-tail.md +53 -0
- package/templates/zibby-workflow-claude/claude/commands/zibby-test-debug.md +59 -0
- package/templates/zibby-workflow-claude/claude/commands/zibby-test-generate.md +39 -0
- package/templates/zibby-workflow-claude/claude/commands/zibby-test-run.md +49 -0
- package/templates/zibby-workflow-claude/claude/commands/zibby-test-write.md +46 -0
- package/templates/zibby-workflow-claude/claude/commands/zibby-trigger.md +56 -0
- package/templates/zibby-workflow-claude/claude/settings.json +10 -0
- package/templates/zibby-workflow-claude/cursor/rules/zibby-workflows.mdc +119 -0
- package/templates/zibby-workflow-claude/manifest.json +47 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readdirSync as $,statSync as m,createReadStream as
|
|
1
|
+
import{readdirSync as $,statSync as m,createReadStream as b,existsSync as k}from"node:fs";import{join as w,relative as T,sep as x,extname as j}from"node:path";var y=500*1024*1024,C=new Set([".DS_Store","Thumbs.db",".zibby-stop"]),v=new Set(["node_modules",".git","dist",".zibby","__tests__","__mocks__",".cache",".next",".turbo"]),z={".webm":"video/webm",".mp4":"video/mp4",".mov":"video/quicktime",".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".gif":"image/gif",".txt":"text/plain",".md":"text/markdown",".csv":"text/csv",".log":"text/plain",".json":"application/json",".yaml":"application/yaml",".yml":"application/yaml",".pdf":"application/pdf",".zip":"application/zip",".tar":"application/x-tar",".gz":"application/gzip"};function E(r){let n=j(r).toLowerCase();return z[n]||"application/octet-stream"}function S(r){let n=[],c;try{c=$(r)}catch{return n}for(let o of c){if(C.has(o)||o.startsWith(".")||v.has(o))continue;let t=w(r,o),l;try{l=m(t)}catch{continue}l.isDirectory()?n.push(...S(t)):l.isFile()&&n.push(t)}return n}async function O({apiUrl:r,apiKey:n,executionId:c,nodeName:o,filename:t,absolutePath:l,sizeBytes:p,contentType:u}){let i;try{let a=await fetch(`${r}/${c}/artifacts/upload-url`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${n}`},body:JSON.stringify({nodeName:o,filename:t,contentType:u,sizeBytes:p})});if(!a.ok){let e=await a.text();return console.warn(`[artifacts] upload-url failed for ${o}/${t}: ${a.status} ${e.slice(0,200)}`),null}i=await a.json()}catch(a){return console.warn(`[artifacts] upload-url request errored for ${o}/${t}: ${a.message}`),null}try{let a=b(l),e=i.requiredHeaders?{...i.requiredHeaders,"Content-Length":String(p)}:{"Content-Type":u,"Content-Length":String(p)},s=await fetch(i.url,{method:"PUT",headers:e,body:a,duplex:"half"});if(!s.ok)return console.warn(`[artifacts] S3 PUT failed for ${o}/${t}: ${s.status}`),null}catch(a){return console.warn(`[artifacts] S3 PUT errored for ${o}/${t}: ${a.message}`),null}return{nodeName:o,filename:t,s3Key:i.s3Key,contentType:u,sizeBytes:p}}async function F({sessionPath:r,executionId:n,apiUrl:c,apiKey:o}){let t={uploaded:[],skipped:[]};if(!r||!k(r))return t;if(!c||!o||!n)return console.warn("[artifacts] uploader missing required input \u2014 skipping"),t;let l;try{l=$(r)}catch(e){return console.warn(`[artifacts] could not read session folder ${r}: ${e.message}`),t}let p=[];for(let e of l){let s=w(r,e),g;try{g=m(s)}catch{continue}if(!g.isDirectory()||e.startsWith(".")||e.startsWith("_"))continue;let _=S(s);for(let h of _){let f=T(s,h).split(x).join("/"),d;try{d=m(h).size}catch{continue}if(d>y){t.skipped.push({nodeName:e,filename:f,reason:`size ${d} > ${y}`});continue}if(d===0){t.skipped.push({nodeName:e,filename:f,reason:"empty"});continue}p.push({apiUrl:c,apiKey:o,executionId:n,nodeName:e,filename:f,absolutePath:h,sizeBytes:d,contentType:E(f)})}}if(p.length===0)return t;let u=4,i=p.slice(),a=Array.from({length:Math.min(u,i.length)},async()=>{for(;i.length;){let e=i.shift(),s=await O(e);s?t.uploaded.push(s):t.skipped.push({nodeName:e.nodeName,filename:e.filename,reason:"upload failed"})}});if(await Promise.all(a),t.uploaded.length>0)try{let e=await fetch(`${c}/${n}/artifacts`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${o}`},body:JSON.stringify({files:t.uploaded})});if(!e.ok){let s=await e.text();console.warn(`[artifacts] record failed: ${e.status} ${s.slice(0,200)}`)}}catch(e){console.warn(`[artifacts] record errored: ${e.message}`)}return t}export{F as uploadSessionArtifacts};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zibby/cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.17",
|
|
4
4
|
"description": "Zibby CLI - Test automation generator and runner",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -35,12 +35,13 @@
|
|
|
35
35
|
"@aws-sdk/client-sqs": "^3.1038.0",
|
|
36
36
|
"@zibby/agent-workflow": "^0.3.2",
|
|
37
37
|
"@zibby/core": "^0.3.6",
|
|
38
|
-
"@zibby/ui-memory": "^1.0.0",
|
|
39
38
|
"@zibby/skills": "^0.1.11",
|
|
39
|
+
"@zibby/ui-memory": "^1.0.0",
|
|
40
40
|
"adm-zip": "^0.5.17",
|
|
41
41
|
"chalk": "^5.3.0",
|
|
42
42
|
"cli-highlight": "^2.1.11",
|
|
43
43
|
"commander": "^12.0.0",
|
|
44
|
+
"cronstrue": "^3.14.0",
|
|
44
45
|
"dotenv": "^17.4.1",
|
|
45
46
|
"express": "^4.18.2",
|
|
46
47
|
"glob": "^13.0.0",
|
|
@@ -54,6 +55,7 @@
|
|
|
54
55
|
},
|
|
55
56
|
"files": [
|
|
56
57
|
"dist/",
|
|
58
|
+
"templates/",
|
|
57
59
|
"README.md",
|
|
58
60
|
"LICENSE"
|
|
59
61
|
],
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
# Zibby project — how to write and ship workflows
|
|
2
|
+
|
|
3
|
+
This file is auto-loaded by Claude Code / Cursor / Codex when working in
|
|
4
|
+
this repo. It's the canonical reference for building Zibby workflows.
|
|
5
|
+
|
|
6
|
+
You are an AI agent. The user describes what they want; you write the
|
|
7
|
+
workflow code (graph + nodes + skills), test it locally, and deploy. The
|
|
8
|
+
user shouldn't need to read the code you produce — they just describe
|
|
9
|
+
the intent.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 0. The 30-second tour
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
.zibby/workflows/<name>/
|
|
17
|
+
├── workflow.json # name, description, version, default agent
|
|
18
|
+
├── graph.mjs # graph topology (nodes + edges)
|
|
19
|
+
├── nodes/ # one file per node — prompt + outputSchema + skills
|
|
20
|
+
│ ├── plan.mjs
|
|
21
|
+
│ ├── implement.mjs
|
|
22
|
+
│ └── verify.mjs
|
|
23
|
+
└── package.json # @zibby/agent-workflow + zod, nothing else needed
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Lifecycle:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
zibby workflow new <name> # scaffold (creates .zibby/workflows/<name>/)
|
|
30
|
+
zibby workflow run <name> -p key=val # run locally — one-shot, no server
|
|
31
|
+
zibby workflow start <name> # run locally with hot-reload (server)
|
|
32
|
+
zibby workflow validate <name> # static check (graph topology, schemas, skills)
|
|
33
|
+
zibby workflow deploy <name> # push to Zibby Cloud, returns UUID
|
|
34
|
+
zibby workflow trigger <uuid> -p ... # remote run
|
|
35
|
+
zibby workflow schedule <uuid> set … # recurring cron run (Unix 5-field)
|
|
36
|
+
zibby workflow logs -t # tail cloud logs
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Always `zibby workflow run` before `deploy`.** Local run is ~5s cold
|
|
40
|
+
start; cloud is ~60s. Iterating in cloud is 12× slower.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## 1. Anatomy of a workflow
|
|
45
|
+
|
|
46
|
+
### `workflow.json`
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"name": "code-review",
|
|
51
|
+
"description": "Review a git diff and return structured findings",
|
|
52
|
+
"version": "0.1.0",
|
|
53
|
+
"defaultAgent": "claude",
|
|
54
|
+
"stateSchema": {
|
|
55
|
+
"diff": "string",
|
|
56
|
+
"findings": "array"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`defaultAgent` is one of: `claude`, `cursor`, `codex`, `gemini`. Any node
|
|
62
|
+
can override with `agent: 'cursor'` in its config.
|
|
63
|
+
|
|
64
|
+
### `graph.mjs`
|
|
65
|
+
|
|
66
|
+
```js
|
|
67
|
+
import { WorkflowGraph } from '@zibby/agent-workflow';
|
|
68
|
+
import { z } from 'zod';
|
|
69
|
+
import { planNode } from './nodes/plan.mjs';
|
|
70
|
+
import { implementNode } from './nodes/implement.mjs';
|
|
71
|
+
import { verifyNode } from './nodes/verify.mjs';
|
|
72
|
+
|
|
73
|
+
export default function buildGraph() {
|
|
74
|
+
const graph = new WorkflowGraph();
|
|
75
|
+
|
|
76
|
+
graph.addNode('plan', planNode);
|
|
77
|
+
graph.addNode('implement', implementNode);
|
|
78
|
+
graph.addNode('verify', verifyNode);
|
|
79
|
+
|
|
80
|
+
graph.addEdge('plan', 'implement');
|
|
81
|
+
graph.addEdge('implement', 'verify');
|
|
82
|
+
graph.addEdge('verify', 'END'); // 'END' is the terminal sentinel
|
|
83
|
+
|
|
84
|
+
graph.setEntryPoint('plan');
|
|
85
|
+
|
|
86
|
+
return graph;
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### A node — `nodes/plan.mjs`
|
|
91
|
+
|
|
92
|
+
```js
|
|
93
|
+
import { z } from 'zod';
|
|
94
|
+
|
|
95
|
+
export const planNode = {
|
|
96
|
+
name: 'plan',
|
|
97
|
+
agent: 'claude', // optional — falls back to defaultAgent
|
|
98
|
+
outputSchema: z.object({
|
|
99
|
+
steps: z.array(z.string()),
|
|
100
|
+
risks: z.array(z.string()),
|
|
101
|
+
}),
|
|
102
|
+
prompt: (state) => `
|
|
103
|
+
You are planning a code change. The user wants:
|
|
104
|
+
${state.userRequest}
|
|
105
|
+
|
|
106
|
+
Return:
|
|
107
|
+
- steps: ordered list of actions to take
|
|
108
|
+
- risks: anything that might go wrong
|
|
109
|
+
`,
|
|
110
|
+
};
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Three required fields on every LLM node: `name`, `outputSchema` (a Zod
|
|
114
|
+
schema), `prompt` (string or function of state).
|
|
115
|
+
|
|
116
|
+
### A custom-code node (no LLM)
|
|
117
|
+
|
|
118
|
+
```js
|
|
119
|
+
export const fetchDiffNode = {
|
|
120
|
+
name: 'fetch_diff',
|
|
121
|
+
outputSchema: z.object({ diff: z.string(), filesChanged: z.array(z.string()) }),
|
|
122
|
+
execute: async (context) => {
|
|
123
|
+
const { execSync } = await import('node:child_process');
|
|
124
|
+
const diff = execSync('git diff --staged', { encoding: 'utf-8' });
|
|
125
|
+
const filesChanged = execSync('git diff --staged --name-only', { encoding: 'utf-8' })
|
|
126
|
+
.trim().split('\n').filter(Boolean);
|
|
127
|
+
return { diff, filesChanged };
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Custom-code nodes use `execute(context)` instead of `prompt`. They skip
|
|
133
|
+
the LLM entirely. Use them for deterministic work: git ops, file IO,
|
|
134
|
+
HTTP calls, parsing.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## 2. State — read and write
|
|
139
|
+
|
|
140
|
+
```js
|
|
141
|
+
// READ — state is passed to prompt fns + execute fns as the first arg
|
|
142
|
+
prompt: (state) => `Plan a change for: ${state.userRequest}`
|
|
143
|
+
|
|
144
|
+
execute: async (state) => {
|
|
145
|
+
const diff = state.diff; // read prior node output here
|
|
146
|
+
// ...
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**Each node's output is stored at `state[nodeName]`.** If `plan` returns
|
|
151
|
+
`{ steps: [...], risks: [...] }`, the next node reads them as
|
|
152
|
+
`state.plan.steps` and `state.plan.risks`.
|
|
153
|
+
|
|
154
|
+
Initial input goes at the TOP of state (not nested under `input`). When
|
|
155
|
+
the user runs `zibby workflow run code-review -p userRequest="fix login"`,
|
|
156
|
+
the first node sees `state.userRequest = "fix login"`.
|
|
157
|
+
|
|
158
|
+
**Don't use `state.set()` / `state.get()` inside `execute()`** — those
|
|
159
|
+
are internal to the graph runtime. Just `return` a plain object that
|
|
160
|
+
matches your `outputSchema`. The runtime puts it under `state[nodeName]`.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## 3. Conditional routing
|
|
165
|
+
|
|
166
|
+
```js
|
|
167
|
+
graph.addConditionalEdges('verify', (state) => {
|
|
168
|
+
if (state.verify.passed) return 'END';
|
|
169
|
+
if (state.verify.retries < 3) return 'implement';
|
|
170
|
+
return 'fail';
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
The router function receives the full state, returns the name of the
|
|
175
|
+
next node (or `'END'`). All possible target nodes must also be declared
|
|
176
|
+
elsewhere via `addNode`.
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## 4. Skills — pluggable MCP tool bundles
|
|
181
|
+
|
|
182
|
+
A **skill** is a named bundle of MCP tools a node can opt into. Built-in
|
|
183
|
+
skills: `browser`, `memory`. Custom skills: register them yourself.
|
|
184
|
+
|
|
185
|
+
### Using a skill in a node
|
|
186
|
+
|
|
187
|
+
```js
|
|
188
|
+
export const navigateNode = {
|
|
189
|
+
name: 'navigate',
|
|
190
|
+
skills: ['browser'], // ← opt in
|
|
191
|
+
outputSchema: z.object({ url: z.string(), title: z.string() }),
|
|
192
|
+
prompt: (state) => `Navigate to ${state.target} and report the title.`,
|
|
193
|
+
};
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
The agent (claude/cursor/codex/gemini) auto-discovers the skill's tools.
|
|
197
|
+
`browser` exposes `mcp__playwright__browser_navigate`, `_snapshot`,
|
|
198
|
+
`_click`, etc. The agent's `allowedTools` is set automatically — you
|
|
199
|
+
don't list each tool.
|
|
200
|
+
|
|
201
|
+
### Writing a custom skill
|
|
202
|
+
|
|
203
|
+
```js
|
|
204
|
+
// .zibby/workflows/<name>/skills/slack.mjs
|
|
205
|
+
import { registerSkill } from '@zibby/agent-workflow';
|
|
206
|
+
|
|
207
|
+
registerSkill({
|
|
208
|
+
id: 'slack', // referenced by `skills: ['slack']`
|
|
209
|
+
serverName: 'slack-mcp',
|
|
210
|
+
command: 'npx',
|
|
211
|
+
args: ['-y', '@modelcontextprotocol/server-slack'],
|
|
212
|
+
allowedTools: ['mcp__slack__*'],
|
|
213
|
+
envKeys: ['SLACK_BOT_TOKEN'], // required env to run
|
|
214
|
+
description: 'Read channels, post messages, search history',
|
|
215
|
+
});
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Then import it from your `graph.mjs` BEFORE building the graph:
|
|
219
|
+
|
|
220
|
+
```js
|
|
221
|
+
import './skills/slack.mjs'; // side-effect: registers the skill
|
|
222
|
+
import { WorkflowGraph } from '@zibby/agent-workflow';
|
|
223
|
+
// ...
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Skills register on `globalThis` so any node that runs in this process
|
|
227
|
+
can use them. In cloud, set required `envKeys` via
|
|
228
|
+
`zibby workflow env set slack ZIBBY_SECRET SLACK_BOT_TOKEN=xoxb-...`.
|
|
229
|
+
|
|
230
|
+
### Custom skill via a non-MCP function
|
|
231
|
+
|
|
232
|
+
If you don't have an MCP server, expose a Node function directly:
|
|
233
|
+
|
|
234
|
+
```js
|
|
235
|
+
registerSkill({
|
|
236
|
+
id: 'jira-fetch',
|
|
237
|
+
resolve: () => null, // no MCP — use middleware instead
|
|
238
|
+
middleware: async () => async (nodeName, next, state) => {
|
|
239
|
+
// attach a JS-only helper. Less common — prefer MCP when possible.
|
|
240
|
+
state.jiraFetch = async (issueId) => { /* ... */ };
|
|
241
|
+
return next();
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## 5. Agent strategies — claude / cursor / codex / gemini
|
|
249
|
+
|
|
250
|
+
The same node can run under any agent — they all read `prompt`, return
|
|
251
|
+
the same shaped `outputSchema`. Differences:
|
|
252
|
+
|
|
253
|
+
| Agent | Best for | Auth env |
|
|
254
|
+
|--------|-----------------------------------|-----------------------------------|
|
|
255
|
+
| claude | Reasoning, planning, structured output | `ANTHROPIC_API_KEY` or `CLAUDE_CODE_OAUTH_TOKEN` |
|
|
256
|
+
| cursor | Code editing in a repo | `CURSOR_API_KEY` |
|
|
257
|
+
| codex | Direct shell + code generation | `OPENAI_API_KEY` |
|
|
258
|
+
| gemini | Cheap volume tasks | `GEMINI_API_KEY` |
|
|
259
|
+
|
|
260
|
+
Pick per-node (override `defaultAgent`):
|
|
261
|
+
|
|
262
|
+
```js
|
|
263
|
+
graph.addNode('plan', { ...planNode, agent: 'claude' });
|
|
264
|
+
graph.addNode('implement', { ...implementNode, agent: 'cursor' });
|
|
265
|
+
graph.addNode('verify', { ...verifyNode, agent: 'codex' });
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Model overrides go in `.zibby.config.mjs`:
|
|
269
|
+
|
|
270
|
+
```js
|
|
271
|
+
export default {
|
|
272
|
+
models: {
|
|
273
|
+
plan: 'claude-opus-4-7',
|
|
274
|
+
implement: 'cursor-fast',
|
|
275
|
+
verify: 'gpt-5-codex',
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## 6. Running and shipping
|
|
283
|
+
|
|
284
|
+
### Local dry-run (FAST — do this every iteration)
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
zibby workflow run code-review -p userRequest="add rate limiting to /api"
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
One-shot run. ~5s cold, then ~2s per warm iteration when the agent has
|
|
291
|
+
cached prompts. Reads + writes to your local filesystem (no cloud
|
|
292
|
+
upload).
|
|
293
|
+
|
|
294
|
+
### Static validate (FASTER — does NOT run any agent)
|
|
295
|
+
|
|
296
|
+
```bash
|
|
297
|
+
zibby workflow validate code-review
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Checks: graph topology (no orphan nodes, entry point exists, edges
|
|
301
|
+
reach END), node shapes (every node has `outputSchema`), skill
|
|
302
|
+
references (every `skills: ['x']` is registered), schema validity. If
|
|
303
|
+
this fails, `run` will definitely fail too — fix it first.
|
|
304
|
+
|
|
305
|
+
### Deploy
|
|
306
|
+
|
|
307
|
+
```bash
|
|
308
|
+
zibby workflow deploy code-review
|
|
309
|
+
# → returns UUID, caches in .zibby-deploy.json
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
Bundles the workflow folder, uploads to Zibby Cloud. Cloud runs on
|
|
313
|
+
Fargate, picks up per-node API keys you set via `zibby workflow env`.
|
|
314
|
+
|
|
315
|
+
### Trigger remote run
|
|
316
|
+
|
|
317
|
+
```bash
|
|
318
|
+
zibby workflow trigger <uuid> -p userRequest="fix login bug" -t
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
`-t` tails logs in Heroku style. Cmd-C stops the tail (workflow keeps
|
|
322
|
+
running in cloud).
|
|
323
|
+
|
|
324
|
+
### Schedule recurring runs (cron)
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
# Set / update a schedule (standard Unix 5-field cron)
|
|
328
|
+
zibby workflow schedule <uuid> set "0 9 * * 1-5" # 9am weekdays UTC
|
|
329
|
+
zibby workflow schedule <uuid> set "*/15 * * * *" # every 15 min
|
|
330
|
+
zibby workflow schedule <uuid> set "0 9 * * *" --tz America/Los_Angeles # 9am LA time
|
|
331
|
+
zibby workflow schedule <uuid> set "0 0 * * *" -p kind=nightly # fixed input params
|
|
332
|
+
|
|
333
|
+
# Inspect / clear
|
|
334
|
+
zibby workflow schedule <uuid> # show current
|
|
335
|
+
zibby workflow schedule <uuid> clear # remove
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Backed by EventBridge Scheduler. Each scheduled fire goes through the
|
|
339
|
+
same `runWorkflow` path as manual + webhook triggers — same Fargate
|
|
340
|
+
container, same memory sync, same logs.
|
|
341
|
+
|
|
342
|
+
Cron format is **standard Unix 5-field** (`minute hour day-of-month
|
|
343
|
+
month day-of-week`). Use `crontab.guru` to debug expressions. The
|
|
344
|
+
backend converts to EventBridge's 6-field form internally; you never
|
|
345
|
+
see AWS's flavor.
|
|
346
|
+
|
|
347
|
+
**Minimum interval is 5 minutes.** `* * * * *`, `*/2 * * * *`, etc.
|
|
348
|
+
are rejected with 400. Use `*/5 * * * *` (every 5 min) or slower —
|
|
349
|
+
this matches GitHub Actions cron's rule. Each fire = a full Fargate
|
|
350
|
+
run = compute + LLM tokens + workflow-execution quota burn, so
|
|
351
|
+
sub-5-min runs aren't economical at our pricing. If you genuinely
|
|
352
|
+
need sub-minute reactivity, use the **webhook trigger** instead.
|
|
353
|
+
|
|
354
|
+
A workflow has at most ONE schedule. `set` is upsert (create if absent,
|
|
355
|
+
update if present). When the workflow is deleted, the schedule is
|
|
356
|
+
cleaned up too.
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## 7. Common pitfalls — read before debugging
|
|
361
|
+
|
|
362
|
+
| Symptom | Fix |
|
|
363
|
+
|--------------------------------------------------|------------------------------------------------|
|
|
364
|
+
| `Node 'X' must define outputSchema` | Add `outputSchema: z.object({...})` to the node config |
|
|
365
|
+
| `Skill 'foo' not registered` | Import the skill file in graph.mjs BEFORE `new WorkflowGraph()` |
|
|
366
|
+
| `state.previousNode is undefined` in a prompt | Wrong order — add `graph.addEdge('previousNode', 'thisNode')` |
|
|
367
|
+
| Custom-code node returns nothing | `return` an object matching `outputSchema`. `undefined` = failure |
|
|
368
|
+
| Agent ignores your skill's tools | Add `skills: ['name']` to the node config, not just register |
|
|
369
|
+
| Zod error: "Expected string, received undefined" | The previous node's outputSchema doesn't match its return — fix the producer, not the consumer |
|
|
370
|
+
| `workflow trigger` works but `run` doesn't | Local run reads env from `.env` / shell; cloud reads from `zibby workflow env`. Set both. |
|
|
371
|
+
| Hangs forever on a node | Add `retries: 0` to fail fast while debugging; check the prompt isn't asking the agent to wait |
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
375
|
+
## 8. The agent's job (this is YOU)
|
|
376
|
+
|
|
377
|
+
When the user says **"write me a workflow that does X"**:
|
|
378
|
+
|
|
379
|
+
1. **Sketch the graph first.** What are the 2-5 nodes? Which can be
|
|
380
|
+
custom-code (deterministic) and which need an LLM (judgement)?
|
|
381
|
+
2. **Run `zibby workflow new <name>`** to scaffold.
|
|
382
|
+
3. **Edit the files** — `graph.mjs`, `nodes/*.mjs`. Use Zod for every
|
|
383
|
+
schema. Default to `claude` unless the user has a preference.
|
|
384
|
+
4. **Run `zibby workflow validate <name>`.** Fix any reported issues.
|
|
385
|
+
5. **Run `zibby workflow run <name> -p ...`** with a realistic input.
|
|
386
|
+
Watch the timeline output. If a node fails, read its `raw` output
|
|
387
|
+
to understand what the LLM returned vs what the schema expected.
|
|
388
|
+
6. **Iterate on prompts** — the user shouldn't need to. If a node's
|
|
389
|
+
output doesn't match the schema, tighten the prompt or relax the
|
|
390
|
+
schema (in that order).
|
|
391
|
+
7. **Once it works locally**, ask the user if they want to deploy.
|
|
392
|
+
Don't deploy without asking — cloud has cost.
|
|
393
|
+
|
|
394
|
+
Read `.claude/commands/` for slash commands the user can invoke:
|
|
395
|
+
`/new-workflow`, `/add-node`, `/add-skill`, `/validate-workflow`.
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
## 9. Quick reference
|
|
400
|
+
|
|
401
|
+
```js
|
|
402
|
+
import { WorkflowGraph, registerSkill, registerStrategy, AgentStrategy } from '@zibby/agent-workflow';
|
|
403
|
+
import { z } from 'zod';
|
|
404
|
+
|
|
405
|
+
// Graph
|
|
406
|
+
const graph = new WorkflowGraph();
|
|
407
|
+
graph.addNode(name, { prompt, outputSchema, execute, skills, agent, retries });
|
|
408
|
+
graph.addEdge(from, to);
|
|
409
|
+
graph.addConditionalEdges(from, (state) => 'nextNodeName');
|
|
410
|
+
graph.setEntryPoint(name);
|
|
411
|
+
|
|
412
|
+
// Skill
|
|
413
|
+
registerSkill({ id, serverName, command, args, allowedTools, envKeys });
|
|
414
|
+
|
|
415
|
+
// Strategy (rare — only when wrapping a non-built-in LLM)
|
|
416
|
+
class MyAgent extends AgentStrategy { /* implement invoke() */ }
|
|
417
|
+
registerStrategy(new MyAgent());
|
|
418
|
+
|
|
419
|
+
// State (inside execute / prompt fns)
|
|
420
|
+
// READ: state.someKey or state.previousNode.field
|
|
421
|
+
// WRITE: return { ... } from execute() — runtime puts it at state[nodeName]
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
That's the whole API. Anything else is either a convenience or a
|
|
425
|
+
footgun — when in doubt, prefer the above.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Add a node to an existing Zibby workflow graph
|
|
3
|
+
argument-hint: <workflow-name> <node-purpose>
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /add-node
|
|
7
|
+
|
|
8
|
+
The user wants to extend an existing workflow with a new node.
|
|
9
|
+
|
|
10
|
+
**Arguments:** $ARGUMENTS
|
|
11
|
+
|
|
12
|
+
## Steps
|
|
13
|
+
|
|
14
|
+
1. **Find the workflow** — should be at `.zibby/workflows/<name>/`. If
|
|
15
|
+
the user didn't specify a name and there's only one workflow, use
|
|
16
|
+
that. If there are multiple, ask which.
|
|
17
|
+
|
|
18
|
+
2. **Read the existing `graph.mjs`** to understand:
|
|
19
|
+
- Current node sequence
|
|
20
|
+
- State shape (what each prior node returns)
|
|
21
|
+
- Where the new node should slot in (before / after / parallel)
|
|
22
|
+
|
|
23
|
+
3. **Decide LLM vs custom-code:**
|
|
24
|
+
- Custom-code (`execute`): the work is deterministic — git ops,
|
|
25
|
+
file IO, HTTP, parsing structured data, math
|
|
26
|
+
- LLM (`prompt`): the work needs judgement — summarization,
|
|
27
|
+
classification, generation, planning
|
|
28
|
+
|
|
29
|
+
4. **Create the node file** at `.zibby/workflows/<name>/nodes/<node>.mjs`:
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
import { z } from 'zod';
|
|
33
|
+
|
|
34
|
+
export const myNode = {
|
|
35
|
+
name: 'my_node',
|
|
36
|
+
outputSchema: z.object({ /* what this returns to state */ }),
|
|
37
|
+
prompt: (state) => `…use state.previousNodeName.field…`,
|
|
38
|
+
// OR for custom-code:
|
|
39
|
+
// execute: async (state) => ({ /* match outputSchema */ }),
|
|
40
|
+
};
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
5. **Wire it into `graph.mjs`:**
|
|
44
|
+
- `import { myNode } from './nodes/my-node.mjs';`
|
|
45
|
+
- `graph.addNode('my_node', myNode);`
|
|
46
|
+
- `graph.addEdge('prev_node', 'my_node');`
|
|
47
|
+
- `graph.addEdge('my_node', 'next_node');` (or 'END')
|
|
48
|
+
|
|
49
|
+
6. **Validate + run** to confirm it integrates:
|
|
50
|
+
```bash
|
|
51
|
+
zibby workflow validate <name>
|
|
52
|
+
zibby workflow run <name> -p ...
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
7. **Report**: what node you added, how state changed, the test result.
|
|
56
|
+
|
|
57
|
+
## Watch for
|
|
58
|
+
|
|
59
|
+
- The new node's `prompt` references `state.X.field` — make sure that
|
|
60
|
+
field exists in the previous node's `outputSchema`. If it doesn't,
|
|
61
|
+
fix the producer schema, not the consumer prompt.
|
|
62
|
+
- Don't change other nodes' schemas without telling the user — that's
|
|
63
|
+
a breaking change to downstream consumers.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Add a custom MCP skill to a Zibby workflow
|
|
3
|
+
argument-hint: <workflow-name> <skill-purpose-or-mcp-server-name>
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /add-skill
|
|
7
|
+
|
|
8
|
+
The user wants to add a custom skill (MCP tool bundle) to a workflow.
|
|
9
|
+
|
|
10
|
+
**Arguments:** $ARGUMENTS
|
|
11
|
+
|
|
12
|
+
## Steps
|
|
13
|
+
|
|
14
|
+
1. **Identify the MCP server** the skill wraps:
|
|
15
|
+
- If the user named one (e.g. "slack", "linear", "filesystem"), find
|
|
16
|
+
the official MCP server. Standard ones live at
|
|
17
|
+
`@modelcontextprotocol/server-<name>`.
|
|
18
|
+
- If unsure, ask: "Which MCP server should this skill wrap, or
|
|
19
|
+
should it be a JS-only middleware?"
|
|
20
|
+
|
|
21
|
+
2. **Find the workflow** at `.zibby/workflows/<name>/`. Create a
|
|
22
|
+
`skills/` subfolder if it doesn't exist.
|
|
23
|
+
|
|
24
|
+
3. **Write `skills/<id>.mjs`:**
|
|
25
|
+
|
|
26
|
+
```js
|
|
27
|
+
import { registerSkill } from '@zibby/agent-workflow';
|
|
28
|
+
|
|
29
|
+
registerSkill({
|
|
30
|
+
id: 'slack', // referenced by node `skills: ['slack']`
|
|
31
|
+
serverName: 'slack-mcp',
|
|
32
|
+
command: 'npx',
|
|
33
|
+
args: ['-y', '@modelcontextprotocol/server-slack'],
|
|
34
|
+
allowedTools: ['mcp__slack__*'], // pattern of tools the agent gets access to
|
|
35
|
+
envKeys: ['SLACK_BOT_TOKEN'],
|
|
36
|
+
description: 'Read channels, post messages, search history',
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
4. **Import the skill file from `graph.mjs`** at the TOP, before
|
|
41
|
+
`new WorkflowGraph()`:
|
|
42
|
+
|
|
43
|
+
```js
|
|
44
|
+
import './skills/slack.mjs'; // side-effect: registers the skill
|
|
45
|
+
import { WorkflowGraph } from '@zibby/agent-workflow';
|
|
46
|
+
// ...
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
5. **Opt nodes into the skill:**
|
|
50
|
+
|
|
51
|
+
```js
|
|
52
|
+
graph.addNode('post_summary', {
|
|
53
|
+
...postSummaryNode,
|
|
54
|
+
skills: ['slack'], // ← agent gets slack tools here
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
6. **Document the env requirement.** Add to the workflow's README or
|
|
59
|
+
tell the user which env var they need to set:
|
|
60
|
+
- Locally: `export SLACK_BOT_TOKEN=xoxb-...` or put in `.env`
|
|
61
|
+
- Cloud: `zibby workflow env set <workflow> SLACK_BOT_TOKEN=...`
|
|
62
|
+
|
|
63
|
+
7. **Validate + test:**
|
|
64
|
+
```bash
|
|
65
|
+
zibby workflow validate <name>
|
|
66
|
+
zibby workflow run <name> -p ...
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The agent should now have access to `mcp__slack__*` tools in the
|
|
70
|
+
nodes that opted in.
|
|
71
|
+
|
|
72
|
+
## When NOT to use a custom skill
|
|
73
|
+
|
|
74
|
+
- If the work can be done with plain Node.js (HTTP call, file write,
|
|
75
|
+
git command) — use a custom-code node with `execute()` instead. MCP
|
|
76
|
+
skills are for tool surfaces the agent decides to use, not for
|
|
77
|
+
deterministic glue.
|
|
78
|
+
|
|
79
|
+
## When to use `middleware` instead of MCP
|
|
80
|
+
|
|
81
|
+
If you don't have an MCP server but want to attach a JS helper that
|
|
82
|
+
nodes can use, see the "Custom skill via a non-MCP function" section
|
|
83
|
+
of `.claude/CLAUDE.md`. This is rare — prefer MCP when one exists.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Scaffold a new Zibby workflow from a natural-language description
|
|
3
|
+
argument-hint: <description of what the workflow should do>
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /new-workflow
|
|
7
|
+
|
|
8
|
+
You're about to create a new Zibby workflow. The user's request is:
|
|
9
|
+
|
|
10
|
+
**$ARGUMENTS**
|
|
11
|
+
|
|
12
|
+
## Steps
|
|
13
|
+
|
|
14
|
+
1. **Sketch the graph.** Based on the user's request, decide:
|
|
15
|
+
- How many nodes? (Typically 2-5. More than 7 is usually a sign of
|
|
16
|
+
overdesign — collapse adjacent nodes.)
|
|
17
|
+
- Which nodes need an LLM (judgement, generation) vs custom-code
|
|
18
|
+
(deterministic: git ops, HTTP, file IO)?
|
|
19
|
+
- What's the linear sequence vs conditional branching?
|
|
20
|
+
- What's the final output the user cares about?
|
|
21
|
+
|
|
22
|
+
2. **Pick a workflow name** — kebab-case, ≤24 chars, descriptive.
|
|
23
|
+
Examples: `code-review`, `pr-summary`, `nightly-changelog`.
|
|
24
|
+
|
|
25
|
+
3. **Run the scaffold:**
|
|
26
|
+
```bash
|
|
27
|
+
zibby workflow new <name>
|
|
28
|
+
```
|
|
29
|
+
This creates `.zibby/workflows/<name>/` with starter files.
|
|
30
|
+
|
|
31
|
+
4. **Edit the files** in this order (read CLAUDE.md §1 if you've forgotten the shapes):
|
|
32
|
+
- `workflow.json` — set `name`, `description`, `defaultAgent`
|
|
33
|
+
- `nodes/*.mjs` — one file per node, each with `name`, `outputSchema`
|
|
34
|
+
(Zod), and either `prompt` (LLM) or `execute` (custom-code)
|
|
35
|
+
- `graph.mjs` — wire them up with `addNode` + `addEdge` + `setEntryPoint`
|
|
36
|
+
|
|
37
|
+
5. **Validate** (this catches 80% of mistakes before running anything):
|
|
38
|
+
```bash
|
|
39
|
+
zibby workflow validate <name>
|
|
40
|
+
```
|
|
41
|
+
Fix any reported issues.
|
|
42
|
+
|
|
43
|
+
6. **Test locally** with a realistic input:
|
|
44
|
+
```bash
|
|
45
|
+
zibby workflow run <name> -p <key>=<value>
|
|
46
|
+
```
|
|
47
|
+
Watch the timeline. If a node fails, the `raw` field shows what the
|
|
48
|
+
agent actually returned vs what the schema expected.
|
|
49
|
+
|
|
50
|
+
7. **Report back to the user** with:
|
|
51
|
+
- The workflow path
|
|
52
|
+
- The local test result
|
|
53
|
+
- The exact `zibby workflow run` command they can use
|
|
54
|
+
- Ask if they want to deploy
|
|
55
|
+
|
|
56
|
+
## DO NOT
|
|
57
|
+
|
|
58
|
+
- Don't deploy without asking (`zibby workflow deploy` has cost)
|
|
59
|
+
- Don't use `state.set()` / `state.get()` inside `execute()` — just `return`
|
|
60
|
+
- Don't skip `zibby workflow validate` — it catches schema typos fast
|
|
61
|
+
- Don't add nodes the request didn't ask for
|