@joshualelon/clawdbot-skill-flow 2.2.1 → 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 +14 -0
- package/package.json +1 -1
- package/src/hooks/google-sheets.ts +149 -0
- package/src/hooks/index.ts +1 -1
package/README.md
CHANGED
|
@@ -427,6 +427,20 @@ export async function myAfterCapture(variable: string, value: string | number, s
|
|
|
427
427
|
const { appendToSheet } = api.hooks;
|
|
428
428
|
await appendToSheet('spreadsheet-id', { [variable]: value });
|
|
429
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
|
+
}
|
|
430
444
|
```
|
|
431
445
|
|
|
432
446
|
**Complete Example:**
|
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";
|