@joshualelon/clawdbot-skill-flow 0.1.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.
- package/LICENSE +21 -0
- package/README.md +265 -0
- package/clawdbot.plugin.json +8 -0
- package/index.ts +65 -0
- package/package.json +76 -0
- package/src/commands/flow-create.ts +69 -0
- package/src/commands/flow-delete.ts +36 -0
- package/src/commands/flow-list.ts +33 -0
- package/src/commands/flow-start.ts +56 -0
- package/src/commands/flow-step.ts +101 -0
- package/src/engine/executor.ts +109 -0
- package/src/engine/renderer.ts +119 -0
- package/src/engine/transitions.ts +160 -0
- package/src/examples/onboarding.json +41 -0
- package/src/examples/pushups.json +49 -0
- package/src/examples/survey.json +41 -0
- package/src/state/flow-store.ts +158 -0
- package/src/state/history-store.ts +76 -0
- package/src/state/session-store.ts +155 -0
- package/src/types.ts +67 -0
- package/src/validation.ts +119 -0
- package/types.d.ts +36 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Clawdbot Team
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# Skill Flow
|
|
2
|
+
|
|
3
|
+
Multi-step workflow orchestration plugin for [Clawdbot](https://github.com/clawdbot/clawdbot).
|
|
4
|
+
|
|
5
|
+
Build deterministic, button-driven conversation flows without AI inference overhead. Perfect for scheduled workouts, surveys, onboarding wizards, approval workflows, and any wizard-style interactions.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Deterministic Execution** - Telegram button callbacks route directly to plugin commands, bypassing LLM entirely
|
|
10
|
+
- **Multi-Step Workflows** - Chain steps with conditional branching and variable capture
|
|
11
|
+
- **Channel Rendering** - Telegram inline keyboards with automatic fallback to text-based menus
|
|
12
|
+
- **Input Validation** - Built-in validators for numbers, emails, and phone numbers
|
|
13
|
+
- **Variable Interpolation** - Use `{{variableName}}` in messages to display captured data
|
|
14
|
+
- **Session Management** - Automatic timeout handling (30 minutes) with in-memory state
|
|
15
|
+
- **History Tracking** - JSONL append-only log for completed flows
|
|
16
|
+
- **Cron Integration** - Schedule flows to run automatically via Clawdbot's cron system
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
### 1. Install Plugin
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
clawdbot plugins install @joshualelon/clawdbot-skill-flow
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### 2. Create a Flow
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
clawdbot message send "/flow-create import $(cat <<'EOF'
|
|
30
|
+
{
|
|
31
|
+
"name": "daily-checkin",
|
|
32
|
+
"description": "Daily wellness check-in",
|
|
33
|
+
"version": "1.0.0",
|
|
34
|
+
"steps": [
|
|
35
|
+
{
|
|
36
|
+
"id": "mood",
|
|
37
|
+
"message": "How are you feeling today?",
|
|
38
|
+
"buttons": ["😊 Great", "😐 Okay", "😔 Not great"],
|
|
39
|
+
"capture": "mood",
|
|
40
|
+
"next": "sleep"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"id": "sleep",
|
|
44
|
+
"message": "How many hours did you sleep?",
|
|
45
|
+
"buttons": [4, 5, 6, 7, 8, 9, 10],
|
|
46
|
+
"capture": "sleep",
|
|
47
|
+
"validate": "number"
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
EOF
|
|
52
|
+
)"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 3. Run the Flow
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
/flow-start daily-checkin
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Commands
|
|
62
|
+
|
|
63
|
+
| Command | Description | Example |
|
|
64
|
+
|---------|-------------|---------|
|
|
65
|
+
| `/flow-start <name>` | Start a flow | `/flow-start pushups` |
|
|
66
|
+
| `/flow-list` | List all flows | `/flow-list` |
|
|
67
|
+
| `/flow-create import <json>` | Create flow from JSON | See Quick Start |
|
|
68
|
+
| `/flow-delete <name>` | Delete a flow | `/flow-delete pushups` |
|
|
69
|
+
| `/flow-step` | Internal command (callback handler) | N/A |
|
|
70
|
+
|
|
71
|
+
## Example Flows
|
|
72
|
+
|
|
73
|
+
### Pushups Workout
|
|
74
|
+
|
|
75
|
+
4-set pushup tracker with rep counting:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
clawdbot message send "/flow-create import $(cat src/examples/pushups.json)"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Customer Survey
|
|
82
|
+
|
|
83
|
+
Satisfaction survey with conditional branching (high vs low scores):
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
clawdbot message send "/flow-create import $(cat src/examples/survey.json)"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Onboarding Wizard
|
|
90
|
+
|
|
91
|
+
Multi-step setup with email validation and variable interpolation:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
clawdbot message send "/flow-create import $(cat src/examples/onboarding.json)"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Flow Schema
|
|
98
|
+
|
|
99
|
+
See [API Documentation](./docs/api.md) for complete schema reference.
|
|
100
|
+
|
|
101
|
+
### Minimal Example
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"name": "hello-world",
|
|
106
|
+
"description": "Simple greeting flow",
|
|
107
|
+
"version": "1.0.0",
|
|
108
|
+
"steps": [
|
|
109
|
+
{
|
|
110
|
+
"id": "greeting",
|
|
111
|
+
"message": "What's your name?",
|
|
112
|
+
"capture": "name",
|
|
113
|
+
"next": "farewell"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"id": "farewell",
|
|
117
|
+
"message": "Nice to meet you, {{name}}! 👋"
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Cron Integration
|
|
124
|
+
|
|
125
|
+
Schedule flows to run automatically:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
clawdbot cron add \
|
|
129
|
+
--name "daily-pushups" \
|
|
130
|
+
--schedule "45 13 * * *" \
|
|
131
|
+
--session-target isolated \
|
|
132
|
+
--message "/flow-start pushups" \
|
|
133
|
+
--channel telegram \
|
|
134
|
+
--to "+1234567890"
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Verify:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
clawdbot cron list
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Advanced Features
|
|
144
|
+
|
|
145
|
+
### Conditional Branching
|
|
146
|
+
|
|
147
|
+
Route users to different steps based on captured variables:
|
|
148
|
+
|
|
149
|
+
```json
|
|
150
|
+
{
|
|
151
|
+
"id": "nps",
|
|
152
|
+
"message": "Rate us 0-10",
|
|
153
|
+
"buttons": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
|
154
|
+
"capture": "nps",
|
|
155
|
+
"validate": "number",
|
|
156
|
+
"condition": {
|
|
157
|
+
"variable": "nps",
|
|
158
|
+
"greaterThan": 7,
|
|
159
|
+
"next": "positive-feedback"
|
|
160
|
+
},
|
|
161
|
+
"next": "negative-feedback"
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Input Validation
|
|
166
|
+
|
|
167
|
+
Enforce data types with built-in validators:
|
|
168
|
+
|
|
169
|
+
- `"validate": "number"` - Numeric input only
|
|
170
|
+
- `"validate": "email"` - Valid email format
|
|
171
|
+
- `"validate": "phone"` - Phone number format
|
|
172
|
+
|
|
173
|
+
### Variable Interpolation
|
|
174
|
+
|
|
175
|
+
Display captured data in subsequent messages:
|
|
176
|
+
|
|
177
|
+
```json
|
|
178
|
+
{
|
|
179
|
+
"id": "summary",
|
|
180
|
+
"message": "Thanks {{name}}! You scored {{nps}}/10."
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Button-Specific Routing
|
|
185
|
+
|
|
186
|
+
Override default `next` on a per-button basis:
|
|
187
|
+
|
|
188
|
+
```json
|
|
189
|
+
{
|
|
190
|
+
"id": "confirm",
|
|
191
|
+
"message": "Continue?",
|
|
192
|
+
"buttons": [
|
|
193
|
+
{ "text": "Yes", "value": "yes", "next": "step2" },
|
|
194
|
+
{ "text": "No", "value": "no", "next": "cancel" }
|
|
195
|
+
]
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## How It Works
|
|
200
|
+
|
|
201
|
+
### Telegram Button Callbacks
|
|
202
|
+
|
|
203
|
+
When you render a Telegram inline keyboard button:
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
{
|
|
207
|
+
text: "20",
|
|
208
|
+
callback_data: "/flow-step pushups set1:20"
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Telegram sends the `callback_data` string back to Clawdbot, which routes it directly to the `/flow-step` command—**no LLM inference required**.
|
|
213
|
+
|
|
214
|
+
This enables deterministic, instant responses for structured workflows.
|
|
215
|
+
|
|
216
|
+
### Session Management
|
|
217
|
+
|
|
218
|
+
- Sessions stored in-memory with 30-minute timeout
|
|
219
|
+
- Session key: `${senderId}-${flowName}`
|
|
220
|
+
- Automatic cleanup every 5 minutes
|
|
221
|
+
- History saved to `~/.clawdbot/flows/<name>/history.jsonl` on completion
|
|
222
|
+
|
|
223
|
+
### File Structure
|
|
224
|
+
|
|
225
|
+
```
|
|
226
|
+
~/.clawdbot/flows/
|
|
227
|
+
├── pushups/
|
|
228
|
+
│ ├── metadata.json
|
|
229
|
+
│ └── history.jsonl
|
|
230
|
+
└── survey/
|
|
231
|
+
├── metadata.json
|
|
232
|
+
└── history.jsonl
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Roadmap
|
|
236
|
+
|
|
237
|
+
### v0.2.0
|
|
238
|
+
- LLM-driven steps (e.g., "Ask user for feedback, then summarize")
|
|
239
|
+
- External service integrations (Google Sheets, webhooks)
|
|
240
|
+
- Flow analytics dashboard (completion rates, drop-off points)
|
|
241
|
+
|
|
242
|
+
### v0.3.0
|
|
243
|
+
- Visual flow builder (web UI)
|
|
244
|
+
- Loops and repeats
|
|
245
|
+
- Sub-flows (call another flow as a step)
|
|
246
|
+
|
|
247
|
+
### v1.0.0
|
|
248
|
+
- Production-ready comprehensive tests
|
|
249
|
+
- Performance optimizations
|
|
250
|
+
- Migration guides
|
|
251
|
+
|
|
252
|
+
## Contributing
|
|
253
|
+
|
|
254
|
+
Issues and PRs welcome! This plugin follows Clawdbot's coding conventions.
|
|
255
|
+
|
|
256
|
+
## License
|
|
257
|
+
|
|
258
|
+
MIT - See [LICENSE](./LICENSE) file
|
|
259
|
+
|
|
260
|
+
## Links
|
|
261
|
+
|
|
262
|
+
- [Clawdbot](https://github.com/clawdbot/clawdbot)
|
|
263
|
+
- [Plugin SDK Docs](https://docs.clawd.bot/plugins)
|
|
264
|
+
- [API Reference](./docs/api.md)
|
|
265
|
+
- [Tutorial](./docs/tutorial.md)
|
package/index.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill Flow Plugin - Multi-step workflow orchestration for Clawdbot
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ClawdbotPluginApi } from "clawdbot/plugin-sdk";
|
|
6
|
+
import { createFlowStartCommand } from "./src/commands/flow-start.js";
|
|
7
|
+
import { createFlowStepCommand } from "./src/commands/flow-step.js";
|
|
8
|
+
import { createFlowCreateCommand } from "./src/commands/flow-create.js";
|
|
9
|
+
import { createFlowListCommand } from "./src/commands/flow-list.js";
|
|
10
|
+
import { createFlowDeleteCommand } from "./src/commands/flow-delete.js";
|
|
11
|
+
|
|
12
|
+
const plugin = {
|
|
13
|
+
id: "skill-flow",
|
|
14
|
+
name: "Skill Flow",
|
|
15
|
+
description:
|
|
16
|
+
"Multi-step workflow orchestration for deterministic command execution",
|
|
17
|
+
version: "0.1.0",
|
|
18
|
+
|
|
19
|
+
register(api: ClawdbotPluginApi) {
|
|
20
|
+
// Register commands
|
|
21
|
+
api.registerCommand({
|
|
22
|
+
name: "flow-start",
|
|
23
|
+
description: "Start a workflow",
|
|
24
|
+
acceptsArgs: true,
|
|
25
|
+
requireAuth: true,
|
|
26
|
+
handler: createFlowStartCommand(api),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
api.registerCommand({
|
|
30
|
+
name: "flow-step",
|
|
31
|
+
description: "Handle flow step transition (internal)",
|
|
32
|
+
acceptsArgs: true,
|
|
33
|
+
requireAuth: true,
|
|
34
|
+
handler: createFlowStepCommand(api),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
api.registerCommand({
|
|
38
|
+
name: "flow-create",
|
|
39
|
+
description: "Create a new flow from JSON",
|
|
40
|
+
acceptsArgs: true,
|
|
41
|
+
requireAuth: true,
|
|
42
|
+
handler: createFlowCreateCommand(api),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
api.registerCommand({
|
|
46
|
+
name: "flow-list",
|
|
47
|
+
description: "List all available flows",
|
|
48
|
+
acceptsArgs: false,
|
|
49
|
+
requireAuth: true,
|
|
50
|
+
handler: createFlowListCommand(api),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
api.registerCommand({
|
|
54
|
+
name: "flow-delete",
|
|
55
|
+
description: "Delete a flow",
|
|
56
|
+
acceptsArgs: true,
|
|
57
|
+
requireAuth: true,
|
|
58
|
+
handler: createFlowDeleteCommand(api),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
api.logger.info("Skill Flow plugin registered successfully");
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export default plugin;
|
package/package.json
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@joshualelon/clawdbot-skill-flow",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Multi-step workflow orchestration plugin for Clawdbot",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"clawdbot",
|
|
8
|
+
"clawdbot-plugin",
|
|
9
|
+
"workflow",
|
|
10
|
+
"automation",
|
|
11
|
+
"skill",
|
|
12
|
+
"flow",
|
|
13
|
+
"telegram"
|
|
14
|
+
],
|
|
15
|
+
"author": "Joshua Le Long <joshualelon>",
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/joshualelon/clawdbot-skill-flow.git"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://github.com/joshualelon/clawdbot-skill-flow#readme",
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/joshualelon/clawdbot-skill-flow/issues"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"src/**/*",
|
|
27
|
+
"index.ts",
|
|
28
|
+
"types.d.ts",
|
|
29
|
+
"README.md",
|
|
30
|
+
"LICENSE",
|
|
31
|
+
"clawdbot.plugin.json"
|
|
32
|
+
],
|
|
33
|
+
"clawdbot": {
|
|
34
|
+
"extensions": [
|
|
35
|
+
"./index.ts"
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"lockfile": "^1.0.4",
|
|
40
|
+
"zod": "^3.22.4"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^22.0.0",
|
|
44
|
+
"@vitest/coverage-v8": "^1.0.0",
|
|
45
|
+
"dependency-cruiser": "^17.0.0",
|
|
46
|
+
"husky": "^9.0.0",
|
|
47
|
+
"jscpd": "^4.0.0",
|
|
48
|
+
"knip": "^5.0.0",
|
|
49
|
+
"lint-staged": "^15.0.0",
|
|
50
|
+
"oxlint": "^0.15.0",
|
|
51
|
+
"typescript": "^5.3.0",
|
|
52
|
+
"vitest": "^1.0.0"
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"clawdbot": ">=2026.1.0"
|
|
56
|
+
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"test": "vitest run",
|
|
59
|
+
"test:watch": "vitest",
|
|
60
|
+
"test:coverage": "vitest run --coverage",
|
|
61
|
+
"typecheck": "tsc --noEmit",
|
|
62
|
+
"lint": "oxlint src tests index.ts",
|
|
63
|
+
"lint:fix": "oxlint --fix src tests index.ts",
|
|
64
|
+
"check:deps": "depcruise src index.ts --config .dependency-cruiser.cjs",
|
|
65
|
+
"check:dead-code": "knip",
|
|
66
|
+
"check:duplicates": "jscpd src",
|
|
67
|
+
"check": "pnpm lint && pnpm typecheck && pnpm check:deps && pnpm check:dead-code && pnpm check:duplicates && pnpm test:coverage",
|
|
68
|
+
"prepare": "husky"
|
|
69
|
+
},
|
|
70
|
+
"lint-staged": {
|
|
71
|
+
"*.ts": [
|
|
72
|
+
"oxlint --fix",
|
|
73
|
+
"tsc --noEmit"
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /flow-create command - Create a new flow
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ClawdbotPluginApi } from "clawdbot/plugin-sdk";
|
|
6
|
+
import { saveFlow } from "../state/flow-store.js";
|
|
7
|
+
import { FlowMetadataSchema } from "../validation.js";
|
|
8
|
+
import type { FlowMetadata } from "../types.js";
|
|
9
|
+
|
|
10
|
+
export function createFlowCreateCommand(api: ClawdbotPluginApi) {
|
|
11
|
+
return async (args: { args?: string }) => {
|
|
12
|
+
const input = args.args?.trim();
|
|
13
|
+
|
|
14
|
+
if (!input) {
|
|
15
|
+
return {
|
|
16
|
+
text: "Usage: /flow-create import <json>\n\nExample:\n/flow-create import {...}",
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Check for 'import' subcommand
|
|
21
|
+
const importMatch = input.match(/^import\s+(.+)$/s);
|
|
22
|
+
|
|
23
|
+
if (!importMatch) {
|
|
24
|
+
return {
|
|
25
|
+
text: "Currently only 'import' mode is supported.\n\nUsage: /flow-create import <json>",
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const jsonStr = importMatch[1];
|
|
30
|
+
if (!jsonStr) {
|
|
31
|
+
return {
|
|
32
|
+
text: "Error: No JSON provided",
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
// Parse JSON
|
|
38
|
+
const data = JSON.parse(jsonStr) as unknown;
|
|
39
|
+
|
|
40
|
+
// Validate schema
|
|
41
|
+
const flow: FlowMetadata = FlowMetadataSchema.parse(data);
|
|
42
|
+
|
|
43
|
+
// Save flow
|
|
44
|
+
await saveFlow(api, flow);
|
|
45
|
+
|
|
46
|
+
api.logger.info(`Created flow "${flow.name}"`);
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
text: `✅ Flow "${flow.name}" created successfully!\n\nStart it with: /flow-start ${flow.name}`,
|
|
50
|
+
};
|
|
51
|
+
} catch (error) {
|
|
52
|
+
if (error instanceof SyntaxError) {
|
|
53
|
+
return {
|
|
54
|
+
text: `Error: Invalid JSON\n\n${error.message}`,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (error instanceof Error) {
|
|
59
|
+
return {
|
|
60
|
+
text: `Error: Invalid flow definition\n\n${error.message}`,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
text: "Error: Failed to create flow",
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /flow-delete command - Delete a flow
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ClawdbotPluginApi } from "clawdbot/plugin-sdk";
|
|
6
|
+
import { deleteFlow, loadFlow } from "../state/flow-store.js";
|
|
7
|
+
|
|
8
|
+
export function createFlowDeleteCommand(api: ClawdbotPluginApi) {
|
|
9
|
+
return async (args: { args?: string }) => {
|
|
10
|
+
const flowName = args.args?.trim();
|
|
11
|
+
|
|
12
|
+
if (!flowName) {
|
|
13
|
+
return {
|
|
14
|
+
text: "Usage: /flow-delete <flow-name>\n\nExample: /flow-delete pushups",
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Check if flow exists
|
|
19
|
+
const flow = await loadFlow(api, flowName);
|
|
20
|
+
|
|
21
|
+
if (!flow) {
|
|
22
|
+
return {
|
|
23
|
+
text: `Flow "${flowName}" not found.\n\nUse /flow-list to see available flows.`,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Delete flow
|
|
28
|
+
await deleteFlow(api, flowName);
|
|
29
|
+
|
|
30
|
+
api.logger.info(`Deleted flow "${flowName}"`);
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
text: `✅ Flow "${flowName}" deleted successfully.`,
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /flow-list command - List all available flows
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ClawdbotPluginApi } from "clawdbot/plugin-sdk";
|
|
6
|
+
import { listFlows } from "../state/flow-store.js";
|
|
7
|
+
|
|
8
|
+
export function createFlowListCommand(api: ClawdbotPluginApi) {
|
|
9
|
+
return async () => {
|
|
10
|
+
const flows = await listFlows(api);
|
|
11
|
+
|
|
12
|
+
if (flows.length === 0) {
|
|
13
|
+
return {
|
|
14
|
+
text: "No flows found.\n\nCreate one with: /flow-create import {...}",
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let message = `📋 Available Flows (${flows.length}):\n\n`;
|
|
19
|
+
|
|
20
|
+
for (const flow of flows) {
|
|
21
|
+
message += `• ${flow.name}\n`;
|
|
22
|
+
message += ` ${flow.description}\n`;
|
|
23
|
+
|
|
24
|
+
if (flow.triggers?.cron) {
|
|
25
|
+
message += ` 🕒 Cron: ${flow.triggers.cron}\n`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
message += ` Start: /flow-start ${flow.name}\n\n`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return { text: message };
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /flow-start command - Start a flow
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ClawdbotPluginApi } from "clawdbot/plugin-sdk";
|
|
6
|
+
import { loadFlow } from "../state/flow-store.js";
|
|
7
|
+
import { createSession, getSessionKey } from "../state/session-store.js";
|
|
8
|
+
import { startFlow } from "../engine/executor.js";
|
|
9
|
+
|
|
10
|
+
export function createFlowStartCommand(api: ClawdbotPluginApi) {
|
|
11
|
+
return async (args: {
|
|
12
|
+
args?: string;
|
|
13
|
+
senderId: string;
|
|
14
|
+
channel: string;
|
|
15
|
+
}) => {
|
|
16
|
+
const flowName = args.args?.trim();
|
|
17
|
+
|
|
18
|
+
// Validate input
|
|
19
|
+
if (!flowName) {
|
|
20
|
+
return {
|
|
21
|
+
text: "Usage: /flow-start <flow-name>\n\nExample: /flow-start pushups\n\nUse /flow-list to see available flows.",
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Load flow
|
|
26
|
+
const flow = await loadFlow(api, flowName);
|
|
27
|
+
|
|
28
|
+
if (!flow) {
|
|
29
|
+
return {
|
|
30
|
+
text: `Flow "${flowName}" not found.\n\nUse /flow-list to see available flows.`,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Verify flow has steps
|
|
35
|
+
if (!flow.steps || flow.steps.length === 0) {
|
|
36
|
+
return {
|
|
37
|
+
text: `Flow "${flowName}" has no steps.`,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Create session
|
|
42
|
+
const session = createSession({
|
|
43
|
+
flowName: flow.name,
|
|
44
|
+
currentStepId: flow.steps[0]!.id,
|
|
45
|
+
senderId: args.senderId,
|
|
46
|
+
channel: args.channel,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
api.logger.info(
|
|
50
|
+
`Started flow "${flowName}" for user ${args.senderId} (session: ${getSessionKey(args.senderId, flowName)})`
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
// Render first step
|
|
54
|
+
return startFlow(api, flow, session);
|
|
55
|
+
};
|
|
56
|
+
}
|