@joshualelon/clawdbot-skill-flow 0.3.1 → 0.5.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/README.md +54 -14
- package/package.json +17 -4
- package/src/config.ts +20 -1
- package/src/engine/hooks-loader.ts +18 -7
- package/src/hooks/README.md +437 -0
- package/src/hooks/common.ts +225 -0
- package/src/hooks/dynamic-buttons.ts +269 -0
- package/src/hooks/google-sheets.ts +326 -0
- package/src/hooks/index.ts +45 -0
- package/src/hooks/scheduling.ts +259 -0
- package/src/hooks/types.ts +76 -0
- package/src/state/flow-store.ts +7 -0
- package/src/state/history-store.ts +16 -2
- package/src/types.ts +2 -0
- package/src/validation.ts +2 -1
package/README.md
CHANGED
|
@@ -14,6 +14,7 @@ Build deterministic, button-driven conversation flows without AI inference overh
|
|
|
14
14
|
- **Session Management** - Automatic timeout handling (30 minutes) with in-memory state
|
|
15
15
|
- **History Tracking** - JSONL append-only log for completed flows
|
|
16
16
|
- **Cron Integration** - Schedule flows to run automatically via Clawdbot's cron system
|
|
17
|
+
- **Hooks Utility Library** - Pre-built integrations for Google Sheets, dynamic buttons, scheduling, and more
|
|
17
18
|
|
|
18
19
|
## Requirements
|
|
19
20
|
|
|
@@ -224,7 +225,52 @@ Customize flow behavior at key points without forking the plugin:
|
|
|
224
225
|
- `onFlowComplete(session)` - Called when flow completes (e.g., schedule next session)
|
|
225
226
|
- `onFlowAbandoned(session, reason)` - Called on timeout/cancellation (e.g., track completion rates)
|
|
226
227
|
|
|
227
|
-
|
|
228
|
+
#### Hooks Utility Library
|
|
229
|
+
|
|
230
|
+
The plugin includes an optional hooks utility library with pre-built integrations:
|
|
231
|
+
|
|
232
|
+
- **Google Sheets** - Log flow data and query history
|
|
233
|
+
- **Dynamic Buttons** - Generate buttons based on historical data
|
|
234
|
+
- **Scheduling** - Schedule recurring workflow sessions
|
|
235
|
+
- **Common Utilities** - Compose hooks, retry logic, validation
|
|
236
|
+
|
|
237
|
+
**Quick Example:**
|
|
238
|
+
|
|
239
|
+
```javascript
|
|
240
|
+
// ~/.clawdbot/flows/pushups/hooks.js
|
|
241
|
+
import { createSheetsLogger } from '@joshualelon/clawdbot-skill-flow/hooks/google-sheets';
|
|
242
|
+
import { createDynamicButtons } from '@joshualelon/clawdbot-skill-flow/hooks/dynamic-buttons';
|
|
243
|
+
import { createScheduler } from '@joshualelon/clawdbot-skill-flow/hooks/scheduling';
|
|
244
|
+
|
|
245
|
+
export default {
|
|
246
|
+
// Generate buttons based on history
|
|
247
|
+
onStepRender: createDynamicButtons({
|
|
248
|
+
spreadsheetId: '1ABC...xyz',
|
|
249
|
+
variable: 'reps',
|
|
250
|
+
strategy: 'centered',
|
|
251
|
+
buttonCount: 5,
|
|
252
|
+
step: 5
|
|
253
|
+
}),
|
|
254
|
+
|
|
255
|
+
// Log to Google Sheets
|
|
256
|
+
onCapture: createSheetsLogger({
|
|
257
|
+
spreadsheetId: '1ABC...xyz',
|
|
258
|
+
worksheetName: 'Workouts',
|
|
259
|
+
includeMetadata: true
|
|
260
|
+
}),
|
|
261
|
+
|
|
262
|
+
// Schedule next session
|
|
263
|
+
onFlowComplete: createScheduler({
|
|
264
|
+
days: ['mon', 'wed', 'fri'],
|
|
265
|
+
time: '08:00',
|
|
266
|
+
timezone: 'America/Chicago'
|
|
267
|
+
})
|
|
268
|
+
};
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
**Custom Hooks:**
|
|
272
|
+
|
|
273
|
+
You can also write custom hooks from scratch:
|
|
228
274
|
|
|
229
275
|
```javascript
|
|
230
276
|
export default {
|
|
@@ -241,26 +287,20 @@ export default {
|
|
|
241
287
|
},
|
|
242
288
|
|
|
243
289
|
async onCapture(variable, value, session) {
|
|
244
|
-
//
|
|
245
|
-
|
|
246
|
-
spreadsheetId: 'YOUR_SHEET_ID',
|
|
247
|
-
values: [[new Date(), session.senderId, variable, value]],
|
|
248
|
-
});
|
|
290
|
+
// Custom logging logic
|
|
291
|
+
console.log(`Captured ${variable}=${value}`);
|
|
249
292
|
},
|
|
250
293
|
|
|
251
294
|
async onFlowComplete(session) {
|
|
252
|
-
//
|
|
253
|
-
|
|
254
|
-
await cron.create({
|
|
255
|
-
schedule: nextDate,
|
|
256
|
-
message: '/flow-start pushups',
|
|
257
|
-
userId: session.senderId,
|
|
258
|
-
});
|
|
295
|
+
// Custom completion logic
|
|
296
|
+
console.log('Flow completed:', session);
|
|
259
297
|
},
|
|
260
298
|
};
|
|
261
299
|
```
|
|
262
300
|
|
|
263
|
-
|
|
301
|
+
**Documentation:**
|
|
302
|
+
- [Hooks Utility Library Reference](./src/hooks/README.md) - Complete API documentation
|
|
303
|
+
- `src/examples/pushups-hooks.example.js` - Custom hooks example
|
|
264
304
|
|
|
265
305
|
### Custom Storage Backends
|
|
266
306
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@joshualelon/clawdbot-skill-flow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Multi-step workflow orchestration plugin for Clawdbot",
|
|
6
6
|
"keywords": [
|
|
@@ -30,6 +30,12 @@
|
|
|
30
30
|
"LICENSE",
|
|
31
31
|
"clawdbot.plugin.json"
|
|
32
32
|
],
|
|
33
|
+
"exports": {
|
|
34
|
+
".": "./index.ts",
|
|
35
|
+
"./hooks": "./src/hooks/index.ts",
|
|
36
|
+
"./hooks/*": "./src/hooks/*.ts",
|
|
37
|
+
"./types": "./src/types.ts"
|
|
38
|
+
},
|
|
33
39
|
"clawdbot": {
|
|
34
40
|
"extensions": [
|
|
35
41
|
"./index.ts"
|
|
@@ -39,7 +45,17 @@
|
|
|
39
45
|
"lockfile": "^1.0.4",
|
|
40
46
|
"zod": "^3.22.4"
|
|
41
47
|
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"clawdbot": ">=2026.1.0",
|
|
50
|
+
"googleapis": ">=140.0.0"
|
|
51
|
+
},
|
|
52
|
+
"peerDependenciesMeta": {
|
|
53
|
+
"googleapis": {
|
|
54
|
+
"optional": true
|
|
55
|
+
}
|
|
56
|
+
},
|
|
42
57
|
"devDependencies": {
|
|
58
|
+
"googleapis": "^144.0.0",
|
|
43
59
|
"@types/node": "^22.0.0",
|
|
44
60
|
"@vitest/coverage-v8": "^1.0.0",
|
|
45
61
|
"dependency-cruiser": "^17.0.0",
|
|
@@ -51,9 +67,6 @@
|
|
|
51
67
|
"typescript": "^5.3.0",
|
|
52
68
|
"vitest": "^1.0.0"
|
|
53
69
|
},
|
|
54
|
-
"peerDependencies": {
|
|
55
|
-
"clawdbot": ">=2026.1.0"
|
|
56
|
-
},
|
|
57
70
|
"scripts": {
|
|
58
71
|
"test": "vitest run",
|
|
59
72
|
"test:watch": "vitest",
|
package/src/config.ts
CHANGED
|
@@ -2,6 +2,11 @@ import { z } from "zod";
|
|
|
2
2
|
|
|
3
3
|
export const SkillFlowConfigSchema = z
|
|
4
4
|
.object({
|
|
5
|
+
flowsDir: z
|
|
6
|
+
.string()
|
|
7
|
+
.optional()
|
|
8
|
+
.describe("Custom flows directory (default: ~/.clawdbot/flows)"),
|
|
9
|
+
|
|
5
10
|
sessionTimeoutMinutes: z
|
|
6
11
|
.number()
|
|
7
12
|
.int()
|
|
@@ -35,11 +40,25 @@ export const SkillFlowConfigSchema = z
|
|
|
35
40
|
|
|
36
41
|
export type SkillFlowConfig = z.infer<typeof SkillFlowConfigSchema>;
|
|
37
42
|
|
|
43
|
+
// Store parsed config for access by other modules
|
|
44
|
+
let pluginConfig: SkillFlowConfig | null = null;
|
|
45
|
+
|
|
38
46
|
/**
|
|
39
47
|
* Parse and validate plugin config with defaults
|
|
40
48
|
*/
|
|
41
49
|
export function parseSkillFlowConfig(
|
|
42
50
|
raw: unknown
|
|
43
51
|
): SkillFlowConfig {
|
|
44
|
-
|
|
52
|
+
pluginConfig = SkillFlowConfigSchema.parse(raw ?? {});
|
|
53
|
+
return pluginConfig;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get the current plugin config (must call parseSkillFlowConfig first)
|
|
58
|
+
*/
|
|
59
|
+
export function getPluginConfig(): SkillFlowConfig {
|
|
60
|
+
if (!pluginConfig) {
|
|
61
|
+
throw new Error("Plugin config not initialized. Call parseSkillFlowConfig first.");
|
|
62
|
+
}
|
|
63
|
+
return pluginConfig;
|
|
45
64
|
}
|
|
@@ -10,6 +10,20 @@ import {
|
|
|
10
10
|
resolvePathSafely,
|
|
11
11
|
validatePathWithinBase,
|
|
12
12
|
} from "../security/path-validation.js";
|
|
13
|
+
import { getPluginConfig } from "../config.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Get the flows directory path (same logic as flow-store.ts)
|
|
17
|
+
*/
|
|
18
|
+
function getFlowsDir(api: ClawdbotPluginApi): string {
|
|
19
|
+
const config = getPluginConfig();
|
|
20
|
+
if (config.flowsDir) {
|
|
21
|
+
const expandedPath = config.flowsDir.replace(/^~/, process.env.HOME || "~");
|
|
22
|
+
return path.resolve(expandedPath);
|
|
23
|
+
}
|
|
24
|
+
const stateDir = api.runtime.state.resolveStateDir();
|
|
25
|
+
return path.join(stateDir, "flows");
|
|
26
|
+
}
|
|
13
27
|
|
|
14
28
|
/**
|
|
15
29
|
* Load hooks from a file path
|
|
@@ -23,7 +37,7 @@ export async function loadHooks(
|
|
|
23
37
|
): Promise<FlowHooks | null> {
|
|
24
38
|
try {
|
|
25
39
|
// Validate path is within flows directory
|
|
26
|
-
const flowsDir =
|
|
40
|
+
const flowsDir = getFlowsDir(api);
|
|
27
41
|
validatePathWithinBase(hooksPath, flowsDir, "hooks file");
|
|
28
42
|
|
|
29
43
|
// Check if file exists
|
|
@@ -55,7 +69,7 @@ export async function loadStorageBackend(
|
|
|
55
69
|
): Promise<StorageBackend | null> {
|
|
56
70
|
try {
|
|
57
71
|
// Validate path is within flows directory
|
|
58
|
-
const flowsDir =
|
|
72
|
+
const flowsDir = getFlowsDir(api);
|
|
59
73
|
validatePathWithinBase(backendPath, flowsDir, "storage backend");
|
|
60
74
|
|
|
61
75
|
// Check if file exists
|
|
@@ -87,11 +101,8 @@ export function resolveFlowPath(
|
|
|
87
101
|
flowName: string,
|
|
88
102
|
relativePath: string
|
|
89
103
|
): string {
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
"flows",
|
|
93
|
-
flowName
|
|
94
|
-
);
|
|
104
|
+
const flowsDir = getFlowsDir(api);
|
|
105
|
+
const flowDir = path.join(flowsDir, flowName);
|
|
95
106
|
|
|
96
107
|
// Validate that relativePath doesn't escape flowDir
|
|
97
108
|
return resolvePathSafely(flowDir, relativePath, "flow path");
|
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
# Hooks Utility Library
|
|
2
|
+
|
|
3
|
+
Optional utilities for common workflow patterns in Skill Flow.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The hooks library provides pre-built integrations and helpers for:
|
|
8
|
+
|
|
9
|
+
- **Google Sheets**: Log flow data and query history
|
|
10
|
+
- **Dynamic Buttons**: Generate button options based on historical data
|
|
11
|
+
- **Scheduling**: Schedule recurring workflow sessions
|
|
12
|
+
- **Common Utilities**: Compose hooks, retry logic, validation
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
The hooks library is included with the skill-flow plugin:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @joshualelon/clawdbot-skill-flow
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Google Sheets Integration:** If you plan to use Google Sheets utilities, install googleapis separately (145MB):
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install googleapis
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
This is an optional peer dependency - only install if needed.
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
Import utilities from the hooks submodule:
|
|
33
|
+
|
|
34
|
+
```javascript
|
|
35
|
+
// Import specific utilities
|
|
36
|
+
import { createSheetsLogger } from '@joshualelon/clawdbot-skill-flow/hooks/google-sheets';
|
|
37
|
+
import { createDynamicButtons } from '@joshualelon/clawdbot-skill-flow/hooks/dynamic-buttons';
|
|
38
|
+
|
|
39
|
+
// Or import from main hooks export
|
|
40
|
+
import { composeHooks, withRetry } from '@joshualelon/clawdbot-skill-flow/hooks';
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Google Sheets Integration
|
|
44
|
+
|
|
45
|
+
Log flow data to Google Sheets for analysis and reporting.
|
|
46
|
+
|
|
47
|
+
### Setup
|
|
48
|
+
|
|
49
|
+
1. Create a Google Cloud service account
|
|
50
|
+
2. Download the service account JSON key
|
|
51
|
+
3. Share your spreadsheet with the service account email
|
|
52
|
+
4. Set the `GOOGLE_APPLICATION_CREDENTIALS` environment variable
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account-key.json
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Basic Usage
|
|
59
|
+
|
|
60
|
+
```javascript
|
|
61
|
+
// ~/.clawdbot/flows/pushups/hooks.js
|
|
62
|
+
import { createSheetsLogger } from '@joshualelon/clawdbot-skill-flow/hooks/google-sheets';
|
|
63
|
+
|
|
64
|
+
export default {
|
|
65
|
+
onCapture: createSheetsLogger({
|
|
66
|
+
spreadsheetId: '1ABC...xyz',
|
|
67
|
+
worksheetName: 'Workouts',
|
|
68
|
+
columns: ['set1', 'set2', 'set3', 'set4'],
|
|
69
|
+
includeMetadata: true
|
|
70
|
+
})
|
|
71
|
+
};
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### API Reference
|
|
75
|
+
|
|
76
|
+
#### `createSheetsLogger(options)`
|
|
77
|
+
|
|
78
|
+
Create a hook that logs captured variables to Google Sheets.
|
|
79
|
+
|
|
80
|
+
**Options:**
|
|
81
|
+
- `spreadsheetId` (string, required): The Google Sheets ID
|
|
82
|
+
- `worksheetName` (string, optional): Name of the worksheet (default: "Sheet1")
|
|
83
|
+
- `columns` (string[], optional): Variable names to log (default: all variables)
|
|
84
|
+
- `includeMetadata` (boolean, optional): Add timestamp, userId, flowName (default: true)
|
|
85
|
+
- `credentials` (object, optional): Service account credentials (default: from environment)
|
|
86
|
+
|
|
87
|
+
#### `appendToSheet(spreadsheetId, worksheetName, rows, credentials?)`
|
|
88
|
+
|
|
89
|
+
Low-level utility to append rows to a Google Sheet.
|
|
90
|
+
|
|
91
|
+
```javascript
|
|
92
|
+
await appendToSheet('1ABC...xyz', 'Workouts', [
|
|
93
|
+
{ date: '2026-01-25', reps: 20, weight: 45 },
|
|
94
|
+
{ date: '2026-01-26', reps: 22, weight: 45 }
|
|
95
|
+
]);
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
#### `querySheetHistory(spreadsheetId, worksheetName, filters?, credentials?)`
|
|
99
|
+
|
|
100
|
+
Query historical data from a Google Sheet.
|
|
101
|
+
|
|
102
|
+
```javascript
|
|
103
|
+
const history = await querySheetHistory('1ABC...xyz', 'Workouts', {
|
|
104
|
+
flowName: 'pushups',
|
|
105
|
+
userId: 'user123',
|
|
106
|
+
dateRange: [new Date('2026-01-01'), new Date()]
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Dynamic Buttons
|
|
111
|
+
|
|
112
|
+
Generate button options based on historical data.
|
|
113
|
+
|
|
114
|
+
### Basic Usage
|
|
115
|
+
|
|
116
|
+
```javascript
|
|
117
|
+
// ~/.clawdbot/flows/pushups/hooks.js
|
|
118
|
+
import { createDynamicButtons } from '@joshualelon/clawdbot-skill-flow/hooks/dynamic-buttons';
|
|
119
|
+
|
|
120
|
+
export default {
|
|
121
|
+
onStepRender: createDynamicButtons({
|
|
122
|
+
spreadsheetId: '1ABC...xyz',
|
|
123
|
+
variable: 'reps',
|
|
124
|
+
strategy: 'centered',
|
|
125
|
+
buttonCount: 5,
|
|
126
|
+
step: 5
|
|
127
|
+
})
|
|
128
|
+
};
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Strategies
|
|
132
|
+
|
|
133
|
+
#### Centered
|
|
134
|
+
|
|
135
|
+
Buttons centered around the historical average.
|
|
136
|
+
|
|
137
|
+
```javascript
|
|
138
|
+
// If average is 20:
|
|
139
|
+
// Buttons: [10, 15, 20, 25, 30]
|
|
140
|
+
strategy: 'centered'
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
#### Progressive
|
|
144
|
+
|
|
145
|
+
Increasing difficulty from the average.
|
|
146
|
+
|
|
147
|
+
```javascript
|
|
148
|
+
// If average is 20:
|
|
149
|
+
// Buttons: [20, 25, 30, 35, 40]
|
|
150
|
+
strategy: 'progressive'
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
#### Range
|
|
154
|
+
|
|
155
|
+
Evenly-spaced range around the average.
|
|
156
|
+
|
|
157
|
+
```javascript
|
|
158
|
+
// If average is 25:
|
|
159
|
+
// Buttons: [15, 20, 25, 30, 35]
|
|
160
|
+
strategy: 'range'
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### API Reference
|
|
164
|
+
|
|
165
|
+
#### `createDynamicButtons(config)`
|
|
166
|
+
|
|
167
|
+
Create a hook that generates dynamic buttons based on historical data.
|
|
168
|
+
|
|
169
|
+
**Config:**
|
|
170
|
+
- `spreadsheetId` (string, optional): Load history from Google Sheets
|
|
171
|
+
- `historyFile` (string, optional): Or load from local .jsonl file
|
|
172
|
+
- `variable` (string, required): Which variable to generate buttons for
|
|
173
|
+
- `strategy` (string, required): "centered" | "progressive" | "range"
|
|
174
|
+
- `buttonCount` (number, optional): How many buttons (default: 5)
|
|
175
|
+
- `step` (number, optional): Increment between buttons (default: 5)
|
|
176
|
+
|
|
177
|
+
#### `getRecentAverage(variable, history, count?)`
|
|
178
|
+
|
|
179
|
+
Calculate the recent average value for a variable.
|
|
180
|
+
|
|
181
|
+
```javascript
|
|
182
|
+
const avgReps = await getRecentAverage('reps', history, 10);
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
#### `generateButtonRange(center, count, step, strategy)`
|
|
186
|
+
|
|
187
|
+
Generate a range of button values.
|
|
188
|
+
|
|
189
|
+
```javascript
|
|
190
|
+
const buttons = generateButtonRange(20, 5, 5, 'centered');
|
|
191
|
+
// Returns: [10, 15, 20, 25, 30]
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Scheduling
|
|
195
|
+
|
|
196
|
+
Schedule recurring workflow sessions.
|
|
197
|
+
|
|
198
|
+
### Basic Usage
|
|
199
|
+
|
|
200
|
+
```javascript
|
|
201
|
+
// ~/.clawdbot/flows/pushups/hooks.js
|
|
202
|
+
import { createScheduler } from '@joshualelon/clawdbot-skill-flow/hooks/scheduling';
|
|
203
|
+
|
|
204
|
+
export default {
|
|
205
|
+
onFlowComplete: createScheduler({
|
|
206
|
+
days: ['mon', 'wed', 'fri'],
|
|
207
|
+
time: '08:00',
|
|
208
|
+
timezone: 'America/Chicago',
|
|
209
|
+
calendarCheck: true
|
|
210
|
+
})
|
|
211
|
+
};
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### API Reference
|
|
215
|
+
|
|
216
|
+
#### `createScheduler(config)`
|
|
217
|
+
|
|
218
|
+
Create a hook that schedules the next workflow session after completion.
|
|
219
|
+
|
|
220
|
+
**Config:**
|
|
221
|
+
- `days` (string[], optional): Days of week (default: ['mon', 'wed', 'fri'])
|
|
222
|
+
- `time` (string, optional): Time in HH:MM format (default: '08:00')
|
|
223
|
+
- `timezone` (string, optional): IANA timezone (default: 'UTC')
|
|
224
|
+
- `calendarCheck` (boolean, optional): Check for conflicts (default: false)
|
|
225
|
+
- `rescheduleOnConflict` (boolean, optional): Find alternative slot (default: false)
|
|
226
|
+
|
|
227
|
+
**Note:** The scheduling utilities are placeholders that log schedules. Integrate with your scheduling system (cron, job queue, etc.) by wrapping the `scheduleNextSession` function.
|
|
228
|
+
|
|
229
|
+
#### `scheduleNextSession(flowName, userId, nextDate)`
|
|
230
|
+
|
|
231
|
+
Schedule the next workflow session.
|
|
232
|
+
|
|
233
|
+
```javascript
|
|
234
|
+
await scheduleNextSession('pushups', 'user123', new Date('2026-01-27T08:00:00Z'));
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
#### `checkCalendarConflicts(dateTime, duration?)`
|
|
238
|
+
|
|
239
|
+
Check if there are calendar conflicts at the given time.
|
|
240
|
+
|
|
241
|
+
```javascript
|
|
242
|
+
const hasConflict = await checkCalendarConflicts(new Date('2026-01-27T08:00:00Z'), 60);
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
#### `findNextAvailableSlot(preferredDates, duration)`
|
|
246
|
+
|
|
247
|
+
Find the next available time slot.
|
|
248
|
+
|
|
249
|
+
```javascript
|
|
250
|
+
const nextSlot = await findNextAvailableSlot([new Date('2026-01-27T08:00:00Z')], 60);
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Common Utilities
|
|
254
|
+
|
|
255
|
+
General-purpose utilities for hook composition and error handling.
|
|
256
|
+
|
|
257
|
+
### Compose Hooks
|
|
258
|
+
|
|
259
|
+
Combine multiple hooks into a single hook.
|
|
260
|
+
|
|
261
|
+
```javascript
|
|
262
|
+
import { composeHooks } from '@joshualelon/clawdbot-skill-flow/hooks';
|
|
263
|
+
import { createSheetsLogger } from '@joshualelon/clawdbot-skill-flow/hooks/google-sheets';
|
|
264
|
+
|
|
265
|
+
const notifySlack = async (variable, value, session) => {
|
|
266
|
+
// Custom notification logic
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
export default {
|
|
270
|
+
onCapture: composeHooks(
|
|
271
|
+
createSheetsLogger({ spreadsheetId: '...' }),
|
|
272
|
+
notifySlack
|
|
273
|
+
)
|
|
274
|
+
};
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Retry with Backoff
|
|
278
|
+
|
|
279
|
+
Retry operations with exponential backoff.
|
|
280
|
+
|
|
281
|
+
```javascript
|
|
282
|
+
import { withRetry } from '@joshualelon/clawdbot-skill-flow/hooks';
|
|
283
|
+
|
|
284
|
+
const data = await withRetry(
|
|
285
|
+
() => fetch('https://api.example.com/data'),
|
|
286
|
+
{ maxAttempts: 3, delayMs: 1000, backoff: true }
|
|
287
|
+
);
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Validation Helpers
|
|
291
|
+
|
|
292
|
+
```javascript
|
|
293
|
+
import { validateEmail, validateNumber, validatePhone } from '@joshualelon/clawdbot-skill-flow/hooks';
|
|
294
|
+
|
|
295
|
+
// Email validation
|
|
296
|
+
if (!validateEmail(value)) {
|
|
297
|
+
throw new Error('Invalid email');
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Number validation with bounds
|
|
301
|
+
if (!validateNumber(value, 0, 100)) {
|
|
302
|
+
throw new Error('Number must be between 0 and 100');
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Phone validation
|
|
306
|
+
if (!validatePhone(value)) {
|
|
307
|
+
throw new Error('Invalid phone number');
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Conditional Hooks
|
|
312
|
+
|
|
313
|
+
Only run a hook if a condition is met.
|
|
314
|
+
|
|
315
|
+
```javascript
|
|
316
|
+
import { whenCondition } from '@joshualelon/clawdbot-skill-flow/hooks';
|
|
317
|
+
import { createSheetsLogger } from '@joshualelon/clawdbot-skill-flow/hooks/google-sheets';
|
|
318
|
+
|
|
319
|
+
export default {
|
|
320
|
+
onCapture: whenCondition(
|
|
321
|
+
(session) => session.variables.score > 100,
|
|
322
|
+
createSheetsLogger({ spreadsheetId: '...' })
|
|
323
|
+
)
|
|
324
|
+
};
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Rate Limiting
|
|
328
|
+
|
|
329
|
+
Debounce or throttle hook execution.
|
|
330
|
+
|
|
331
|
+
```javascript
|
|
332
|
+
import { debounceHook, throttleHook } from '@joshualelon/clawdbot-skill-flow/hooks';
|
|
333
|
+
|
|
334
|
+
// Debounce: wait for quiet period before executing
|
|
335
|
+
const debouncedHook = debounceHook(myHook, 1000);
|
|
336
|
+
|
|
337
|
+
// Throttle: ensure minimum time between executions
|
|
338
|
+
const throttledHook = throttleHook(myHook, 5000);
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
## API Reference
|
|
342
|
+
|
|
343
|
+
### Common Functions
|
|
344
|
+
|
|
345
|
+
- `composeHooks<T>(...hooks): HookFunction<T>` - Combine multiple hooks
|
|
346
|
+
- `withRetry<T>(fn, options): Promise<T>` - Retry with exponential backoff
|
|
347
|
+
- `validateEmail(value): boolean` - Validate email format
|
|
348
|
+
- `validateNumber(value, min?, max?): boolean` - Validate number with bounds
|
|
349
|
+
- `validatePhone(value): boolean` - Validate phone format
|
|
350
|
+
- `whenCondition<T>(condition, hook): HookFunction<T>` - Conditional execution
|
|
351
|
+
- `debounceHook<T>(hook, delayMs): HookFunction<T>` - Debounce hook
|
|
352
|
+
- `throttleHook<T>(hook, intervalMs): HookFunction<T>` - Throttle hook
|
|
353
|
+
|
|
354
|
+
## Examples
|
|
355
|
+
|
|
356
|
+
### Complete Workout Tracker
|
|
357
|
+
|
|
358
|
+
```javascript
|
|
359
|
+
// ~/.clawdbot/flows/pushups/hooks.js
|
|
360
|
+
import {
|
|
361
|
+
composeHooks,
|
|
362
|
+
createSheetsLogger,
|
|
363
|
+
createDynamicButtons,
|
|
364
|
+
createScheduler
|
|
365
|
+
} from '@joshualelon/clawdbot-skill-flow/hooks';
|
|
366
|
+
|
|
367
|
+
export default {
|
|
368
|
+
// Generate buttons based on history
|
|
369
|
+
onStepRender: createDynamicButtons({
|
|
370
|
+
spreadsheetId: '1ABC...xyz',
|
|
371
|
+
variable: 'reps',
|
|
372
|
+
strategy: 'centered',
|
|
373
|
+
buttonCount: 5,
|
|
374
|
+
step: 5
|
|
375
|
+
}),
|
|
376
|
+
|
|
377
|
+
// Log to Google Sheets
|
|
378
|
+
onCapture: createSheetsLogger({
|
|
379
|
+
spreadsheetId: '1ABC...xyz',
|
|
380
|
+
worksheetName: 'Workouts',
|
|
381
|
+
includeMetadata: true
|
|
382
|
+
}),
|
|
383
|
+
|
|
384
|
+
// Schedule next session
|
|
385
|
+
onFlowComplete: createScheduler({
|
|
386
|
+
days: ['mon', 'wed', 'fri'],
|
|
387
|
+
time: '08:00',
|
|
388
|
+
timezone: 'America/Chicago'
|
|
389
|
+
})
|
|
390
|
+
};
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
### Custom Integration
|
|
394
|
+
|
|
395
|
+
```javascript
|
|
396
|
+
// ~/.clawdbot/flows/survey/hooks.js
|
|
397
|
+
import { composeHooks, withRetry } from '@joshualelon/clawdbot-skill-flow/hooks';
|
|
398
|
+
import { createSheetsLogger } from '@joshualelon/clawdbot-skill-flow/hooks/google-sheets';
|
|
399
|
+
|
|
400
|
+
// Custom webhook notification
|
|
401
|
+
const notifyWebhook = async (variable, value, session) => {
|
|
402
|
+
await withRetry(
|
|
403
|
+
() => fetch('https://hooks.example.com/survey', {
|
|
404
|
+
method: 'POST',
|
|
405
|
+
headers: { 'Content-Type': 'application/json' },
|
|
406
|
+
body: JSON.stringify({ variable, value, session })
|
|
407
|
+
}),
|
|
408
|
+
{ maxAttempts: 3, backoff: true }
|
|
409
|
+
);
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
export default {
|
|
413
|
+
onCapture: composeHooks(
|
|
414
|
+
createSheetsLogger({ spreadsheetId: '...' }),
|
|
415
|
+
notifyWebhook
|
|
416
|
+
)
|
|
417
|
+
};
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
## TypeScript Support
|
|
421
|
+
|
|
422
|
+
Full TypeScript types are exported:
|
|
423
|
+
|
|
424
|
+
```typescript
|
|
425
|
+
import type {
|
|
426
|
+
FlowHooks,
|
|
427
|
+
SheetsLogOptions,
|
|
428
|
+
DynamicButtonsConfig,
|
|
429
|
+
ScheduleConfig,
|
|
430
|
+
HookFunction,
|
|
431
|
+
RetryOptions
|
|
432
|
+
} from '@joshualelon/clawdbot-skill-flow/hooks';
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
## License
|
|
436
|
+
|
|
437
|
+
MIT
|