@joshualelon/clawdbot-skill-flow 0.3.0 → 0.4.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 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
- **Example hooks file:**
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
- // Log to Google Sheets in real-time
245
- await sheets.append({
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
- // Schedule next workout
253
- const nextDate = calculateNextWorkout();
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
- See `src/examples/pushups-hooks.example.js` for a complete reference.
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/index.ts CHANGED
@@ -3,6 +3,9 @@
3
3
  */
4
4
 
5
5
  import type { ClawdbotPluginApi } from "clawdbot/plugin-sdk";
6
+ import { readFileSync } from "node:fs";
7
+ import { fileURLToPath } from "node:url";
8
+ import { dirname, join } from "node:path";
6
9
  import { parseSkillFlowConfig } from "./src/config.js";
7
10
  import { initSessionStore, createSession, getSessionKey } from "./src/state/session-store.js";
8
11
  import { createFlowStartCommand } from "./src/commands/flow-start.js";
@@ -15,12 +18,17 @@ import { startFlow } from "./src/engine/executor.js";
15
18
  import { FlowMetadataSchema } from "./src/validation.js";
16
19
  import type { FlowMetadata } from "./src/types.js";
17
20
 
21
+ // Read metadata from package.json
22
+ const __dirname = dirname(fileURLToPath(import.meta.url));
23
+ const packageJson = JSON.parse(
24
+ readFileSync(join(__dirname, "package.json"), "utf-8")
25
+ ) as { version: string; description: string };
26
+
18
27
  const plugin = {
19
28
  id: "skill-flow",
20
29
  name: "Skill Flow",
21
- description:
22
- "Multi-step workflow orchestration for deterministic command execution",
23
- version: "0.1.0",
30
+ description: packageJson.description,
31
+ version: packageJson.version,
24
32
 
25
33
  register(api: ClawdbotPluginApi) {
26
34
  // Parse and validate config
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@joshualelon/clawdbot-skill-flow",
3
- "version": "0.3.0",
3
+ "version": "0.4.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",
@@ -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