@joshualelon/clawdbot-skill-flow 2.2.0 → 2.3.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 +41 -1
- package/package.json +1 -1
- package/src/hooks/google-sheets.ts +149 -0
- package/src/hooks/index.ts +1 -1
package/README.md
CHANGED
|
@@ -403,7 +403,47 @@ Each step can declare what actions to execute:
|
|
|
403
403
|
|
|
404
404
|
#### Hooks File Structure
|
|
405
405
|
|
|
406
|
-
Action functions are exported as **named exports** from your hooks file.
|
|
406
|
+
Action functions are exported as **named exports** from your hooks file. All actions receive an `api` parameter as their final argument - plugin utilities are available via `api.hooks`.
|
|
407
|
+
|
|
408
|
+
> **Note:** No imports needed! Just destructure what you need from `api.hooks`.
|
|
409
|
+
|
|
410
|
+
**Action Signatures:**
|
|
411
|
+
|
|
412
|
+
```typescript
|
|
413
|
+
// Fetch actions - retrieve data before rendering
|
|
414
|
+
export async function myFetch(session: FlowSession, api: EnhancedPluginApi) {
|
|
415
|
+
const { querySheetHistory } = api.hooks;
|
|
416
|
+
return { variableName: value };
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// BeforeRender actions - modify step before display
|
|
420
|
+
export async function myBeforeRender(step: FlowStep, session: FlowSession, api: EnhancedPluginApi) {
|
|
421
|
+
const { generateButtonRange } = api.hooks;
|
|
422
|
+
return { ...step, buttons: [...] };
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// AfterCapture actions - side effects after capturing input
|
|
426
|
+
export async function myAfterCapture(variable: string, value: string | number, session: FlowSession, api: EnhancedPluginApi) {
|
|
427
|
+
const { appendToSheet } = api.hooks;
|
|
428
|
+
await appendToSheet('spreadsheet-id', { [variable]: value });
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Create a new spreadsheet (typically in a fetch action)
|
|
432
|
+
export async function createWorkoutLog(session, api) {
|
|
433
|
+
const { createSpreadsheet } = api.hooks;
|
|
434
|
+
|
|
435
|
+
const { spreadsheetId, spreadsheetUrl } = await createSpreadsheet({
|
|
436
|
+
title: `${session.flowName} Log - ${new Date().getFullYear()}`,
|
|
437
|
+
worksheetName: 'Sessions',
|
|
438
|
+
headers: ['timestamp', 'userId', 'set1', 'set2', 'set3', 'set4', 'total'],
|
|
439
|
+
folderId: process.env.GOOGLE_DRIVE_FOLDER_ID // Optional: move to specific folder
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
return { spreadsheetId, spreadsheetUrl };
|
|
443
|
+
}
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
**Complete Example:**
|
|
407
447
|
|
|
408
448
|
```javascript
|
|
409
449
|
// ~/.clawdbot/flows/pushups/hooks.js
|
package/package.json
CHANGED
|
@@ -284,6 +284,155 @@ export async function querySheetHistory(
|
|
|
284
284
|
return data;
|
|
285
285
|
}
|
|
286
286
|
|
|
287
|
+
/**
|
|
288
|
+
* Create a new Google Spreadsheet with optional initial data
|
|
289
|
+
*
|
|
290
|
+
* @param options - Configuration for spreadsheet creation
|
|
291
|
+
* @returns Object containing spreadsheetId and spreadsheetUrl
|
|
292
|
+
*
|
|
293
|
+
* @example
|
|
294
|
+
* ```ts
|
|
295
|
+
* // Create a simple spreadsheet
|
|
296
|
+
* const { spreadsheetId, spreadsheetUrl } = await createSpreadsheet({
|
|
297
|
+
* title: 'Pushups Log 2026',
|
|
298
|
+
* worksheetName: 'Sessions'
|
|
299
|
+
* });
|
|
300
|
+
*
|
|
301
|
+
* // Create with initial headers and move to folder
|
|
302
|
+
* const result = await createSpreadsheet({
|
|
303
|
+
* title: 'Pushups Log 2026',
|
|
304
|
+
* worksheetName: 'Sessions',
|
|
305
|
+
* headers: ['timestamp', 'userId', 'set1', 'set2', 'set3', 'set4', 'total'],
|
|
306
|
+
* folderId: '1ABC...xyz'
|
|
307
|
+
* });
|
|
308
|
+
* ```
|
|
309
|
+
*/
|
|
310
|
+
export async function createSpreadsheet(options: {
|
|
311
|
+
title: string;
|
|
312
|
+
worksheetName?: string;
|
|
313
|
+
headers?: string[];
|
|
314
|
+
folderId?: string;
|
|
315
|
+
credentials?: GoogleServiceAccountCredentials;
|
|
316
|
+
}): Promise<{ spreadsheetId: string; spreadsheetUrl: string }> {
|
|
317
|
+
const {
|
|
318
|
+
title,
|
|
319
|
+
worksheetName = 'Sheet1',
|
|
320
|
+
headers,
|
|
321
|
+
folderId,
|
|
322
|
+
credentials,
|
|
323
|
+
} = options;
|
|
324
|
+
|
|
325
|
+
const sheets = await createSheetsClient(credentials);
|
|
326
|
+
|
|
327
|
+
// Create the spreadsheet
|
|
328
|
+
const createResponse = await sheets.spreadsheets.create({
|
|
329
|
+
requestBody: {
|
|
330
|
+
properties: {
|
|
331
|
+
title,
|
|
332
|
+
locale: 'en_US',
|
|
333
|
+
timeZone: 'America/Chicago', // Default to CT, users can change
|
|
334
|
+
},
|
|
335
|
+
sheets: [
|
|
336
|
+
{
|
|
337
|
+
properties: {
|
|
338
|
+
title: worksheetName,
|
|
339
|
+
gridProperties: {
|
|
340
|
+
rowCount: 1000,
|
|
341
|
+
columnCount: 26,
|
|
342
|
+
frozenRowCount: headers ? 1 : 0, // Freeze header row if headers provided
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
],
|
|
347
|
+
},
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
const spreadsheetId = createResponse.data.spreadsheetId;
|
|
351
|
+
const spreadsheetUrl = createResponse.data.spreadsheetUrl;
|
|
352
|
+
|
|
353
|
+
if (!spreadsheetId || !spreadsheetUrl) {
|
|
354
|
+
throw new Error('Failed to create spreadsheet: missing ID or URL');
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Add headers if provided
|
|
358
|
+
if (headers && headers.length > 0) {
|
|
359
|
+
await sheets.spreadsheets.values.update({
|
|
360
|
+
spreadsheetId,
|
|
361
|
+
range: `${worksheetName}!A1`,
|
|
362
|
+
valueInputOption: 'RAW',
|
|
363
|
+
requestBody: {
|
|
364
|
+
values: [headers],
|
|
365
|
+
},
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
// Bold the header row
|
|
369
|
+
await sheets.spreadsheets.batchUpdate({
|
|
370
|
+
spreadsheetId,
|
|
371
|
+
requestBody: {
|
|
372
|
+
requests: [
|
|
373
|
+
{
|
|
374
|
+
repeatCell: {
|
|
375
|
+
range: {
|
|
376
|
+
sheetId: 0,
|
|
377
|
+
startRowIndex: 0,
|
|
378
|
+
endRowIndex: 1,
|
|
379
|
+
},
|
|
380
|
+
cell: {
|
|
381
|
+
userEnteredFormat: {
|
|
382
|
+
textFormat: {
|
|
383
|
+
bold: true,
|
|
384
|
+
},
|
|
385
|
+
},
|
|
386
|
+
},
|
|
387
|
+
fields: 'userEnteredFormat.textFormat.bold',
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
],
|
|
391
|
+
},
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Move to folder if specified
|
|
396
|
+
if (folderId) {
|
|
397
|
+
// Create Drive API client with same auth
|
|
398
|
+
let driveAuth;
|
|
399
|
+
if (credentials) {
|
|
400
|
+
driveAuth = new google.auth.GoogleAuth({
|
|
401
|
+
credentials: {
|
|
402
|
+
client_email: credentials.clientEmail,
|
|
403
|
+
private_key: credentials.privateKey,
|
|
404
|
+
},
|
|
405
|
+
scopes: ['https://www.googleapis.com/auth/drive.file'],
|
|
406
|
+
});
|
|
407
|
+
} else {
|
|
408
|
+
driveAuth = new google.auth.GoogleAuth({
|
|
409
|
+
scopes: ['https://www.googleapis.com/auth/drive.file'],
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
414
|
+
const drive = google.drive({ version: 'v3', auth: driveAuth as any });
|
|
415
|
+
|
|
416
|
+
// Get current parents (usually root)
|
|
417
|
+
const file = await drive.files.get({
|
|
418
|
+
fileId: spreadsheetId,
|
|
419
|
+
fields: 'parents',
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
const previousParents = file.data.parents?.join(',');
|
|
423
|
+
|
|
424
|
+
// Move to new folder
|
|
425
|
+
await drive.files.update({
|
|
426
|
+
fileId: spreadsheetId,
|
|
427
|
+
addParents: folderId,
|
|
428
|
+
removeParents: previousParents,
|
|
429
|
+
fields: 'id, parents',
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
return { spreadsheetId, spreadsheetUrl };
|
|
434
|
+
}
|
|
435
|
+
|
|
287
436
|
/**
|
|
288
437
|
* Ensure a worksheet exists, create it if not
|
|
289
438
|
*/
|
package/src/hooks/index.ts
CHANGED
|
@@ -31,7 +31,7 @@ export {
|
|
|
31
31
|
} from "./common.js";
|
|
32
32
|
|
|
33
33
|
// Re-export Google Sheets utilities
|
|
34
|
-
export { createSheetsLogger, appendToSheet, querySheetHistory } from "./google-sheets.js";
|
|
34
|
+
export { createSheetsLogger, appendToSheet, querySheetHistory, createSpreadsheet } from "./google-sheets.js";
|
|
35
35
|
|
|
36
36
|
// Re-export dynamic buttons utilities
|
|
37
37
|
export { createDynamicButtons, getRecentAverage, generateButtonRange } from "./dynamic-buttons.js";
|