@joshualelon/clawdbot-skill-flow 2.3.2 → 2.3.4
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/package.json +1 -1
- package/src/engine/action-registry.ts +5 -1
- package/src/engine/executor.ts +44 -17
- package/src/engine/renderer.ts +3 -3
- package/src/engine/transitions.ts +2 -2
- package/src/hooks/google-sheets.ts +128 -2
- package/src/hooks/types.ts +1 -0
- package/src/types.ts +2 -2
package/package.json
CHANGED
|
@@ -49,6 +49,9 @@ const sheetsAppendSchema = z.object({
|
|
|
49
49
|
privateKey: z.string(),
|
|
50
50
|
})
|
|
51
51
|
.optional(),
|
|
52
|
+
useGogOAuth: z.boolean().default(true).describe(
|
|
53
|
+
"Use gog CLI OAuth instead of service account (recommended to avoid quota issues)"
|
|
54
|
+
),
|
|
52
55
|
});
|
|
53
56
|
|
|
54
57
|
async function sheetsAppendExecute(
|
|
@@ -85,7 +88,8 @@ async function sheetsAppendExecute(
|
|
|
85
88
|
cfg.worksheetName,
|
|
86
89
|
[row],
|
|
87
90
|
cfg.credentials as GoogleServiceAccountCredentials | undefined,
|
|
88
|
-
cfg.headerMode
|
|
91
|
+
cfg.headerMode,
|
|
92
|
+
cfg.useGogOAuth
|
|
89
93
|
),
|
|
90
94
|
{ maxAttempts: 3, delayMs: 1000, backoff: true }
|
|
91
95
|
);
|
package/src/engine/executor.ts
CHANGED
|
@@ -96,14 +96,30 @@ async function executeStepActions(
|
|
|
96
96
|
);
|
|
97
97
|
|
|
98
98
|
// Inject result into session
|
|
99
|
-
if (result
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
if (result !== null && result !== undefined) {
|
|
100
|
+
if (typeof result === "object") {
|
|
101
|
+
// If result is an object, inject all its fields as separate variables
|
|
102
|
+
const resultObj = result as Record<string, unknown>;
|
|
103
|
+
for (const [key, value] of Object.entries(resultObj)) {
|
|
104
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
105
|
+
modifiedSession = {
|
|
106
|
+
...modifiedSession,
|
|
107
|
+
variables: {
|
|
108
|
+
...modifiedSession.variables,
|
|
109
|
+
[key]: value,
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// Update context for next actions
|
|
115
|
+
context.variables = modifiedSession.variables;
|
|
116
|
+
} else if (typeof result === "string" || typeof result === "number" || typeof result === "boolean") {
|
|
117
|
+
// If result is a primitive, inject it under varName
|
|
102
118
|
modifiedSession = {
|
|
103
119
|
...modifiedSession,
|
|
104
120
|
variables: {
|
|
105
121
|
...modifiedSession.variables,
|
|
106
|
-
[varName]:
|
|
122
|
+
[varName]: result,
|
|
107
123
|
},
|
|
108
124
|
};
|
|
109
125
|
// Update context for next actions
|
|
@@ -192,18 +208,29 @@ async function executeStepActions(
|
|
|
192
208
|
modifiedSession,
|
|
193
209
|
enhancedApi
|
|
194
210
|
);
|
|
195
|
-
if (result &&
|
|
196
|
-
if (
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
211
|
+
if (result !== null && result !== undefined) {
|
|
212
|
+
if (typeof result === "object") {
|
|
213
|
+
// If result is an object, inject all its fields as separate variables
|
|
214
|
+
for (const [key, value] of Object.entries(result)) {
|
|
215
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
216
|
+
modifiedSession = {
|
|
217
|
+
...modifiedSession,
|
|
218
|
+
variables: {
|
|
219
|
+
...modifiedSession.variables,
|
|
220
|
+
[key]: value,
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
}
|
|
206
224
|
}
|
|
225
|
+
} else if (typeof result === "string" || typeof result === "number" || typeof result === "boolean") {
|
|
226
|
+
// If result is a primitive, inject it under varName
|
|
227
|
+
modifiedSession = {
|
|
228
|
+
...modifiedSession,
|
|
229
|
+
variables: {
|
|
230
|
+
...modifiedSession.variables,
|
|
231
|
+
[varName]: result,
|
|
232
|
+
},
|
|
233
|
+
};
|
|
207
234
|
}
|
|
208
235
|
}
|
|
209
236
|
}
|
|
@@ -339,7 +366,7 @@ export async function processStep(
|
|
|
339
366
|
): Promise<{
|
|
340
367
|
reply: ReplyPayload;
|
|
341
368
|
complete: boolean;
|
|
342
|
-
updatedVariables: Record<string, string | number>;
|
|
369
|
+
updatedVariables: Record<string, string | number | boolean>;
|
|
343
370
|
}> {
|
|
344
371
|
// Load action registry for declarative actions
|
|
345
372
|
let actionRegistry: ActionRegistry | null = null;
|
|
@@ -446,7 +473,7 @@ export async function processStep(
|
|
|
446
473
|
*/
|
|
447
474
|
function generateCompletionMessage(
|
|
448
475
|
flow: FlowMetadata,
|
|
449
|
-
variables: Record<string, string | number>
|
|
476
|
+
variables: Record<string, string | number | boolean>
|
|
450
477
|
): string {
|
|
451
478
|
let message = `✅ Flow "${flow.name}" completed!\n\n`;
|
|
452
479
|
|
package/src/engine/renderer.ts
CHANGED
|
@@ -17,7 +17,7 @@ import { normalizeButton } from "../validation.js";
|
|
|
17
17
|
*/
|
|
18
18
|
function interpolateVariables(
|
|
19
19
|
text: string,
|
|
20
|
-
variables: Record<string, string | number>
|
|
20
|
+
variables: Record<string, string | number | boolean>
|
|
21
21
|
): string {
|
|
22
22
|
return text.replace(/\{\{(\w+)\}\}/g, (match, varName) => {
|
|
23
23
|
const value = variables[varName];
|
|
@@ -31,7 +31,7 @@ function interpolateVariables(
|
|
|
31
31
|
function renderTelegram(
|
|
32
32
|
flowName: string,
|
|
33
33
|
step: FlowStep,
|
|
34
|
-
variables: Record<string, string | number>
|
|
34
|
+
variables: Record<string, string | number | boolean>
|
|
35
35
|
): ReplyPayload {
|
|
36
36
|
const message = interpolateVariables(step.message, variables);
|
|
37
37
|
|
|
@@ -83,7 +83,7 @@ function renderTelegram(
|
|
|
83
83
|
*/
|
|
84
84
|
function renderFallback(
|
|
85
85
|
step: FlowStep,
|
|
86
|
-
variables: Record<string, string | number>
|
|
86
|
+
variables: Record<string, string | number | boolean>
|
|
87
87
|
): ReplyPayload {
|
|
88
88
|
const message = interpolateVariables(step.message, variables);
|
|
89
89
|
|
|
@@ -30,7 +30,7 @@ import { createInterpolationContext, interpolateConfig } from "./interpolation.j
|
|
|
30
30
|
*/
|
|
31
31
|
function evaluateCondition(
|
|
32
32
|
condition: FlowStep["condition"],
|
|
33
|
-
variables: Record<string, string | number>
|
|
33
|
+
variables: Record<string, string | number | boolean>
|
|
34
34
|
): boolean {
|
|
35
35
|
if (!condition) {
|
|
36
36
|
return false;
|
|
@@ -75,7 +75,7 @@ function evaluateCondition(
|
|
|
75
75
|
function findNextStep(
|
|
76
76
|
step: FlowStep,
|
|
77
77
|
value: string | number,
|
|
78
|
-
variables: Record<string, string | number>
|
|
78
|
+
variables: Record<string, string | number | boolean>
|
|
79
79
|
): string | undefined {
|
|
80
80
|
// 1. Check button-specific next
|
|
81
81
|
if (step.buttons && step.buttons.length > 0) {
|
|
@@ -68,6 +68,7 @@ export function createSheetsLogger(
|
|
|
68
68
|
includeMetadata = true,
|
|
69
69
|
credentials,
|
|
70
70
|
headerMode = 'append',
|
|
71
|
+
useGogOAuth,
|
|
71
72
|
} = options;
|
|
72
73
|
|
|
73
74
|
return async (variable: string, value: string | number, session: FlowSession) => {
|
|
@@ -99,7 +100,7 @@ export function createSheetsLogger(
|
|
|
99
100
|
|
|
100
101
|
// Append to sheet with retry
|
|
101
102
|
await withRetry(
|
|
102
|
-
() => appendToSheet(spreadsheetId, worksheetName, [row], credentials, headerMode),
|
|
103
|
+
() => appendToSheet(spreadsheetId, worksheetName, [row], credentials, headerMode, useGogOAuth),
|
|
103
104
|
{ maxAttempts: 3, delayMs: 1000, backoff: true }
|
|
104
105
|
);
|
|
105
106
|
} catch (error) {
|
|
@@ -114,6 +115,120 @@ export function createSheetsLogger(
|
|
|
114
115
|
};
|
|
115
116
|
}
|
|
116
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Append rows to Google Sheet using gog OAuth credentials
|
|
120
|
+
*/
|
|
121
|
+
async function appendToSheetWithGogOAuth(
|
|
122
|
+
spreadsheetId: string,
|
|
123
|
+
worksheetName: string,
|
|
124
|
+
rows: Array<Record<string, unknown>>,
|
|
125
|
+
headerMode: HeaderMode = 'append'
|
|
126
|
+
): Promise<void> {
|
|
127
|
+
if (rows.length === 0) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const accessToken = await getGogAccessToken();
|
|
132
|
+
const rowKeys = Object.keys(rows[0]!);
|
|
133
|
+
|
|
134
|
+
// Get existing data to check headers
|
|
135
|
+
const getUrl = `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}/values/${worksheetName}!A1:ZZ1`;
|
|
136
|
+
const getResponse = await fetch(getUrl, {
|
|
137
|
+
headers: { 'Authorization': `Bearer ${accessToken}` },
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
let existingHeaders: string[] = [];
|
|
141
|
+
if (getResponse.ok) {
|
|
142
|
+
const getData = await getResponse.json() as { values?: string[][] };
|
|
143
|
+
existingHeaders = getData.values?.[0] || [];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Handle headers based on mode
|
|
147
|
+
if (existingHeaders.length === 0) {
|
|
148
|
+
// Empty sheet - write headers
|
|
149
|
+
const updateUrl = `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}/values/${worksheetName}!A1?valueInputOption=RAW`;
|
|
150
|
+
await fetch(updateUrl, {
|
|
151
|
+
method: 'PUT',
|
|
152
|
+
headers: {
|
|
153
|
+
'Authorization': `Bearer ${accessToken}`,
|
|
154
|
+
'Content-Type': 'application/json',
|
|
155
|
+
},
|
|
156
|
+
body: JSON.stringify({
|
|
157
|
+
values: [rowKeys],
|
|
158
|
+
}),
|
|
159
|
+
});
|
|
160
|
+
} else if (!arraysEqual(existingHeaders, rowKeys)) {
|
|
161
|
+
// Headers don't match - apply mode
|
|
162
|
+
switch (headerMode) {
|
|
163
|
+
case 'strict':
|
|
164
|
+
throw new Error(
|
|
165
|
+
`Header mismatch in sheet "${worksheetName}". ` +
|
|
166
|
+
`Expected: ${existingHeaders.join(', ')}. ` +
|
|
167
|
+
`Got: ${rowKeys.join(', ')}. ` +
|
|
168
|
+
`Set headerMode to 'append' or 'overwrite' to handle this.`
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
case 'append': {
|
|
172
|
+
// Find new columns not in existing headers
|
|
173
|
+
const newColumns = rowKeys.filter(key => !existingHeaders.includes(key));
|
|
174
|
+
if (newColumns.length > 0) {
|
|
175
|
+
// Append new columns to the right
|
|
176
|
+
const updatedHeaders = [...existingHeaders, ...newColumns];
|
|
177
|
+
const updateUrl = `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}/values/${worksheetName}!A1?valueInputOption=RAW`;
|
|
178
|
+
await fetch(updateUrl, {
|
|
179
|
+
method: 'PUT',
|
|
180
|
+
headers: {
|
|
181
|
+
'Authorization': `Bearer ${accessToken}`,
|
|
182
|
+
'Content-Type': 'application/json',
|
|
183
|
+
},
|
|
184
|
+
body: JSON.stringify({
|
|
185
|
+
values: [updatedHeaders],
|
|
186
|
+
}),
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
case 'overwrite': {
|
|
193
|
+
// Replace headers completely
|
|
194
|
+
const updateUrl = `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}/values/${worksheetName}!A1?valueInputOption=RAW`;
|
|
195
|
+
await fetch(updateUrl, {
|
|
196
|
+
method: 'PUT',
|
|
197
|
+
headers: {
|
|
198
|
+
'Authorization': `Bearer ${accessToken}`,
|
|
199
|
+
'Content-Type': 'application/json',
|
|
200
|
+
},
|
|
201
|
+
body: JSON.stringify({
|
|
202
|
+
values: [rowKeys],
|
|
203
|
+
}),
|
|
204
|
+
});
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Convert rows to 2D array
|
|
211
|
+
const values = rows.map((row) => rowKeys.map((key) => row[key] ?? ""));
|
|
212
|
+
|
|
213
|
+
// Append data
|
|
214
|
+
const appendUrl = `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}/values/${worksheetName}!A:A:append?valueInputOption=RAW`;
|
|
215
|
+
const appendResponse = await fetch(appendUrl, {
|
|
216
|
+
method: 'POST',
|
|
217
|
+
headers: {
|
|
218
|
+
'Authorization': `Bearer ${accessToken}`,
|
|
219
|
+
'Content-Type': 'application/json',
|
|
220
|
+
},
|
|
221
|
+
body: JSON.stringify({
|
|
222
|
+
values,
|
|
223
|
+
}),
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
if (!appendResponse.ok) {
|
|
227
|
+
const errorText = await appendResponse.text();
|
|
228
|
+
throw new Error(`Failed to append to sheet: ${appendResponse.statusText} - ${errorText}`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
117
232
|
/**
|
|
118
233
|
* Low-level utility to append rows to a Google Sheet.
|
|
119
234
|
* Creates the worksheet if it doesn't exist, and adds headers on first write.
|
|
@@ -131,12 +246,23 @@ export async function appendToSheet(
|
|
|
131
246
|
worksheetName: string,
|
|
132
247
|
rows: Array<Record<string, unknown>>,
|
|
133
248
|
credentials?: GoogleServiceAccountCredentials,
|
|
134
|
-
headerMode: HeaderMode = 'append'
|
|
249
|
+
headerMode: HeaderMode = 'append',
|
|
250
|
+
useGogOAuth?: boolean
|
|
135
251
|
): Promise<void> {
|
|
136
252
|
if (rows.length === 0) {
|
|
137
253
|
return;
|
|
138
254
|
}
|
|
139
255
|
|
|
256
|
+
// Use gog OAuth if requested
|
|
257
|
+
if (useGogOAuth) {
|
|
258
|
+
return await appendToSheetWithGogOAuth(
|
|
259
|
+
spreadsheetId,
|
|
260
|
+
worksheetName,
|
|
261
|
+
rows,
|
|
262
|
+
headerMode
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
140
266
|
const sheets = await createSheetsClient(credentials);
|
|
141
267
|
|
|
142
268
|
// Ensure worksheet exists
|
package/src/hooks/types.ts
CHANGED
|
@@ -27,6 +27,7 @@ export interface SheetsLogOptions {
|
|
|
27
27
|
includeMetadata?: boolean; // Add timestamp, userId, flowName
|
|
28
28
|
credentials?: GoogleServiceAccountCredentials;
|
|
29
29
|
headerMode?: HeaderMode; // How to handle header mismatches (default: 'append')
|
|
30
|
+
useGogOAuth?: boolean; // Use gog CLI OAuth instead of service account (default: true)
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
/**
|
package/src/types.ts
CHANGED
|
@@ -106,14 +106,14 @@ export interface FlowSession {
|
|
|
106
106
|
currentStepId: string;
|
|
107
107
|
senderId: string;
|
|
108
108
|
channel: string;
|
|
109
|
-
variables: Record<string, string | number>;
|
|
109
|
+
variables: Record<string, string | number | boolean>;
|
|
110
110
|
startedAt: number;
|
|
111
111
|
lastActivityAt: number;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
export interface TransitionResult {
|
|
115
115
|
nextStepId?: string;
|
|
116
|
-
variables: Record<string, string | number>;
|
|
116
|
+
variables: Record<string, string | number | boolean>;
|
|
117
117
|
complete: boolean;
|
|
118
118
|
error?: string;
|
|
119
119
|
message?: string;
|