runline 0.34.0 → 0.35.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.
Files changed (54) hide show
  1. package/dist/core/engine.js +12 -8
  2. package/dist/credentials/registry.js +5 -0
  3. package/dist/credentials/types.d.ts +10 -0
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.js +1 -0
  6. package/dist/plugin/schema.d.ts +16 -4
  7. package/dist/plugin/schema.js +28 -5
  8. package/dist/plugin/types.d.ts +8 -0
  9. package/dist/plugins/_shared/base64.js +30 -0
  10. package/dist/plugins/_shared/credentialAdapter.js +23 -3
  11. package/dist/plugins/_shared/credentials.js +29 -2
  12. package/dist/plugins/_shared/googleAuth.js +62 -1
  13. package/dist/plugins/_shared/googleCredentials.js +40 -7
  14. package/dist/plugins/_shared/localFile.js +40 -0
  15. package/dist/plugins/_shared/mediaFile.js +12 -12
  16. package/dist/plugins/_shared/microsoftAuth.js +19 -4
  17. package/dist/plugins/_shared/microsoftCredentials.js +12 -7
  18. package/dist/plugins/_shared/shiftCloud.js +14 -18
  19. package/dist/plugins/_shared/shiftUpload.js +14 -10
  20. package/dist/plugins/agentMail/src/credentials.js +35 -0
  21. package/dist/plugins/agentMail/src/drafts.js +137 -0
  22. package/dist/plugins/agentMail/src/inboxes.js +95 -0
  23. package/dist/plugins/agentMail/src/index.js +35 -0
  24. package/dist/plugins/agentMail/src/messages.js +154 -0
  25. package/dist/plugins/agentMail/src/schemas.js +156 -0
  26. package/dist/plugins/agentMail/src/shared.js +203 -0
  27. package/dist/plugins/agentMail/src/threads.js +75 -0
  28. package/dist/plugins/elevenlabs/src/shared.js +29 -43
  29. package/dist/plugins/fal/src/index.js +1 -1
  30. package/dist/plugins/gmail/src/index.js +22 -85
  31. package/dist/plugins/googleAppsScript/src/index.js +2 -1
  32. package/dist/plugins/googleCalendar/src/index.js +6 -39
  33. package/dist/plugins/googleContacts/src/index.js +2 -32
  34. package/dist/plugins/googleDocs/src/index.js +2 -33
  35. package/dist/plugins/googleDrive/src/index.js +19 -45
  36. package/dist/plugins/googleImage/src/index.js +1 -1
  37. package/dist/plugins/googleSheets/src/index.js +2 -34
  38. package/dist/plugins/googleSlides/src/index.js +2 -32
  39. package/dist/plugins/googleTasks/src/index.js +2 -32
  40. package/dist/plugins/highlevel/src/index.js +8 -2
  41. package/dist/plugins/microsoftCalendar/src/index.js +2 -7
  42. package/dist/plugins/microsoftFiles/src/index.js +7 -8
  43. package/dist/plugins/microsoftMail/src/index.js +2 -7
  44. package/dist/plugins/openai/src/index.js +1 -1
  45. package/dist/plugins/recraft/src/index.js +1 -1
  46. package/dist/plugins/replicate/src/index.js +1 -1
  47. package/dist/plugins/shiftObjects/src/objects.js +1 -1
  48. package/dist/plugins/shiftOcr/src/index.js +8 -3
  49. package/dist/plugins/shiftTranscription/src/transcription.js +1 -1
  50. package/dist/plugins/together/src/index.js +1 -1
  51. package/dist/plugins/xai/src/index.js +1 -1
  52. package/package.json +2 -1
  53. package/dist/tests/helpers/engine-harness.d.ts +0 -1
  54. package/dist/tests/helpers/engine-harness.js +0 -236
@@ -1,14 +1,11 @@
1
1
  /**
2
2
  * Gmail plugin for runline.
3
3
  *
4
- * Authentication: OAuth2 user flow, seeded once via
5
- * `runline auth gmail`. The connection stores `clientId`,
6
- * `clientSecret`, `refreshToken`, plus cached `accessToken` +
7
- * `accessTokenExpiresAt`. When the cached token is missing or
8
- * within 60 s of expiry, the plugin refreshes against
9
- * `https://oauth2.googleapis.com/token` and persists the new
10
- * token via `ctx.updateConnection`. File-level locking inside
11
- * runline core keeps concurrent refreshes safe.
4
+ * Authentication: the Google grant's OAuth2 user flow, seeded once
5
+ * via `runline auth gmail`, or a service account. The connection
6
+ * stores `clientId`, `clientSecret` and `refreshToken`; the access
7
+ * token is refreshed lazily (60 s skew) and cached on the connection
8
+ * as the grant's, so every Google plugin sharing it signs with it.
12
9
  *
13
10
  * This plugin deliberately doesn't depend on nodemailer or
14
11
  * mailparser. MIME building is a minimal hand-rolled encoder
@@ -21,9 +18,10 @@
21
18
  * or parsed `payload` tree for format=full).
22
19
  */
23
20
  import * as t from "typebox";
21
+ import { DownloadedFile, standardBase64 } from "../../_shared/base64.js";
24
22
  import { renderEmailJsx } from "../../_shared/emailJsx.js";
25
23
  import { pathSegment } from "../../_shared/credentials.js";
26
- import { googleCredential, googleJsonRequest } from "../../_shared/googleAuth.js";
24
+ import { googleCredential, googleJsonRequest, googleOAuth, } from "../../_shared/googleAuth.js";
27
25
  import { GoogleTimestamp, Id, NonEmptyString, stringEnum, } from "../../_shared/googleSchemas.js";
28
26
  // ─── Request ─────────────────────────────────────────────────────
29
27
  const API_BASE = "https://gmail.googleapis.com/gmail/v1/users/me";
@@ -87,18 +85,6 @@ function header(name, value) {
87
85
  function foldedBase64ByteLength(length) {
88
86
  return length === 0 ? 0 : length + Math.floor((length - 1) / 76) * CRLF.length;
89
87
  }
90
- function normalizeMimeBase64(value, index) {
91
- const compact = value.replace(/\s+/g, "");
92
- if (!/^[A-Za-z0-9+/=_-]*$/.test(compact)) {
93
- throw new Error(`gmail: attachment ${index} contentBase64 contains invalid base64 characters`);
94
- }
95
- const standard = compact.replace(/-/g, "+").replace(/_/g, "/");
96
- const remainder = standard.length % 4;
97
- if (remainder === 1) {
98
- throw new Error(`gmail: attachment ${index} contentBase64 has invalid base64 length`);
99
- }
100
- return remainder === 0 ? standard : `${standard}${"=".repeat(4 - remainder)}`;
101
- }
102
88
  function foldBase64(encoded) {
103
89
  let folded = "";
104
90
  for (let i = 0; i < encoded.length; i += 76) {
@@ -135,10 +121,20 @@ function normalizeAttachment(input, index) {
135
121
  if (typeof contentBase64 !== "string") {
136
122
  throw new Error(`gmail: attachment ${index} contentBase64 must be a base64 string`);
137
123
  }
124
+ const normalized = standardBase64(contentBase64);
125
+ if (normalized === undefined) {
126
+ throw new Error(`gmail: attachment ${index} contentBase64 is not valid base64`);
127
+ }
128
+ // A download result passed as contentBase64 names its own file and type.
129
+ const result = typeof content === "object" && content ? content : {};
130
+ const named = (value) => typeof value === "string" && value ? value : undefined;
138
131
  return {
139
- name: input.name ?? input.filename ?? `attachment-${index + 1}`,
140
- mimeType: input.mimeType ?? "application/octet-stream",
141
- contentBase64: normalizeMimeBase64(contentBase64, index),
132
+ name: input.name ??
133
+ input.filename ??
134
+ named(result.name) ??
135
+ `attachment-${index + 1}`,
136
+ mimeType: input.mimeType ?? named(result.mimeType) ?? "application/octet-stream",
137
+ contentBase64: normalized,
142
138
  };
143
139
  }
144
140
  function attachmentPart(att) {
@@ -432,9 +428,6 @@ function simplifyMessage(raw, labels) {
432
428
  * `References` headers plus the original `threadId`.
433
429
  */
434
430
  async function replyToMessage(ctx, messageId, p) {
435
- if (p.replyToSenderOnly && p.replyToRecipientsOnly) {
436
- throw new Error("gmail: replyToSenderOnly and replyToRecipientsOnly are mutually exclusive");
437
- }
438
431
  // Render before any network round-trip so malformed jsx fails
439
432
  // without spending Gmail API calls.
440
433
  const { text, html } = await resolveBody(p);
@@ -515,10 +508,7 @@ const MessageListVisibility = stringEnum(["show", "hide"]);
515
508
  const MetadataHeaders = t.Array(t.String());
516
509
  const LabelIds = t.Array(NonEmptyString, { minItems: 1 });
517
510
  const MaxResults = t.Integer({ minimum: 1, maximum: 500 });
518
- const AttachmentContent = t.Union([
519
- t.String(),
520
- t.Object({ contentBase64: t.String() }, strict),
521
- ]);
511
+ const AttachmentContent = t.Union([t.String(), DownloadedFile]);
522
512
  const Attachment = t.Object({
523
513
  name: t.Optional(t.String()),
524
514
  filename: t.Optional(t.String()),
@@ -582,60 +572,7 @@ export default function gmail(rl) {
582
572
  rl.setName("gmail");
583
573
  rl.setVersion("0.1.0");
584
574
  rl.setCredential(googleCredential("gmail", SCOPES));
585
- rl.setOAuth({
586
- authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
587
- tokenUrl: "https://oauth2.googleapis.com/token",
588
- scopes: SCOPES,
589
- // access_type=offline + prompt=consent are both required to
590
- // make Google return a refresh token. Without them, repeat
591
- // logins skip the refresh_token field in the response.
592
- authParams: { access_type: "offline", prompt: "consent" },
593
- setupHelp: [
594
- "You need a Google Cloud OAuth client. Takes ~5 minutes, one time.",
595
- "Do the steps in order — skipping step 4 causes a 403 when you log in.",
596
- "",
597
- "1. Create a Google Cloud project (or pick an existing one):",
598
- " https://console.cloud.google.com/projectcreate",
599
- " Make sure the new project is selected in the top navigation dropdown.",
600
- "",
601
- "2. Enable the Gmail API for that project:",
602
- " https://console.cloud.google.com/apis/library/gmail.googleapis.com",
603
- " Click 'Enable'.",
604
- "",
605
- "3. Configure the OAuth consent screen (first time only):",
606
- " https://console.cloud.google.com/apis/credentials/consent",
607
- " Click 'Get started' and fill in:",
608
- " • App name: runline (or whatever)",
609
- " • User support email: your email",
610
- " • Audience: External",
611
- " • Developer contact: your email",
612
- " You can skip the Scopes screen — runline declares scopes at auth time.",
613
- "",
614
- "4. Add yourself as a test user (required — External apps in Testing mode",
615
- " only allow authentication from addresses on this list):",
616
- " https://console.cloud.google.com/auth/audience",
617
- " Under 'Test users', click '+ Add users' and enter the Gmail address",
618
- " you'll use to log in. Consent and refresh tokens expire every 7 days",
619
- " while the app is in Testing mode.",
620
- "",
621
- "5. Create the OAuth client:",
622
- " https://console.cloud.google.com/apis/credentials",
623
- " • + Create credentials → OAuth client ID",
624
- " • Application type: Web application",
625
- " • Name: runline (or whatever)",
626
- " • Authorized redirect URIs → + Add URI:",
627
- " {{redirectUri}}",
628
- " • Click Create, then copy the Client ID and Client Secret from the",
629
- " dialog — the secret is only shown once.",
630
- "",
631
- "6. Paste them below (or re-run with --client-id / --client-secret,",
632
- " or export GMAIL_CLIENT_ID and GMAIL_CLIENT_SECRET).",
633
- "",
634
- "During login you'll see a 'Google hasn't verified this app' warning.",
635
- "Click 'Advanced → Go to runline (unsafe)' to continue — this is",
636
- "expected for apps in Testing mode.",
637
- ],
638
- });
575
+ rl.setOAuth(googleOAuth("gmail", SCOPES, "GMAIL"));
639
576
  rl.setConnectionSchema({
640
577
  authMethod: { type: "string", required: false, description: "delegated or serviceAccount (legacy configs infer the method)" },
641
578
  clientId: {
@@ -1,6 +1,6 @@
1
1
  import * as t from "typebox";
2
2
  import { pathSegment } from "../../_shared/credentials.js";
3
- import { googleCredential, googleJsonRequest, } from "../../_shared/googleAuth.js";
3
+ import { googleCredential, googleJsonRequest, googleOAuth, } from "../../_shared/googleAuth.js";
4
4
  import { Id, NonEmptyString, PositiveInteger, stringEnum, } from "../../_shared/googleSchemas.js";
5
5
  const SCRIPT_API = "https://script.googleapis.com/v1";
6
6
  const DRIVE_API = "https://www.googleapis.com/drive/v3";
@@ -42,6 +42,7 @@ export default function googleAppsScript(rl) {
42
42
  rl.setName("googleAppsScript");
43
43
  rl.setVersion("1.0.0");
44
44
  rl.setCredential(googleCredential("googleAppsScript", SCOPES));
45
+ rl.setOAuth(googleOAuth("googleAppsScript", SCOPES, "GOOGLE_APPS_SCRIPT"));
45
46
  rl.setConnectionSchema({
46
47
  authMethod: {
47
48
  type: "string",
@@ -1,13 +1,10 @@
1
1
  /**
2
2
  * Google Calendar plugin for runline.
3
3
  *
4
- * Authentication mirrors the gmail plugin: OAuth2 user flow, seeded
5
- * once via `runline auth googleCalendar`. The connection stores
6
- * `clientId`, `clientSecret`, `refreshToken`, plus cached
7
- * `accessToken` + `accessTokenExpiresAt`. Token refresh is lazy —
8
- * when the cached token is missing or within 60 s of expiry the
9
- * plugin hits `https://oauth2.googleapis.com/token` and persists
10
- * the new token via `ctx.updateConnection`.
4
+ * Authentication mirrors the gmail plugin: the Google grant's OAuth2
5
+ * user flow, seeded once via `runline auth googleCalendar`, with the
6
+ * access token refreshed lazily (60 s skew) and cached on the
7
+ * connection as the grant's.
11
8
  *
12
9
  * Surface area:
13
10
  *
@@ -30,7 +27,7 @@
30
27
  import rrulePkg from "rrule";
31
28
  import * as t from "typebox";
32
29
  import { pathSegment } from "../../_shared/credentials.js";
33
- import { googleCredential, googleJsonRequest } from "../../_shared/googleAuth.js";
30
+ import { googleCredential, googleJsonRequest, googleOAuth, } from "../../_shared/googleAuth.js";
34
31
  import { Id, NonEmptyString, NonEmptyStringArray, PositiveInteger, StringOrStringArray, stringEnum, } from "../../_shared/googleSchemas.js";
35
32
  // `rrule` ships as CJS; named imports fail under Node ESM.
36
33
  const { RRule } = rrulePkg;
@@ -503,37 +500,7 @@ export default function googleCalendar(rl) {
503
500
  rl.setName("googleCalendar");
504
501
  rl.setVersion("0.1.0");
505
502
  rl.setCredential(googleCredential("googleCalendar", SCOPES));
506
- rl.setOAuth({
507
- authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
508
- tokenUrl: "https://oauth2.googleapis.com/token",
509
- scopes: SCOPES,
510
- authParams: { access_type: "offline", prompt: "consent" },
511
- setupHelp: [
512
- "You need a Google Cloud OAuth client. Takes ~5 minutes, one time.",
513
- "",
514
- "1. Create or pick a Google Cloud project:",
515
- " https://console.cloud.google.com/projectcreate",
516
- "",
517
- "2. Enable the Google Calendar API:",
518
- " https://console.cloud.google.com/apis/library/calendar-json.googleapis.com",
519
- "",
520
- "3. Configure the OAuth consent screen (first time only):",
521
- " https://console.cloud.google.com/apis/credentials/consent",
522
- " • Audience: External",
523
- "",
524
- "4. Add yourself as a test user:",
525
- " https://console.cloud.google.com/auth/audience",
526
- "",
527
- "5. Create the OAuth client:",
528
- " https://console.cloud.google.com/apis/credentials",
529
- " • + Create credentials → OAuth client ID",
530
- " • Application type: Web application",
531
- " • Authorized redirect URIs → + Add URI: {{redirectUri}}",
532
- "",
533
- "6. Paste the Client ID and Client Secret below, or export",
534
- " GOOGLE_CALENDAR_CLIENT_ID and GOOGLE_CALENDAR_CLIENT_SECRET.",
535
- ],
536
- });
503
+ rl.setOAuth(googleOAuth("googleCalendar", SCOPES, "GOOGLE_CALENDAR"));
537
504
  rl.setConnectionSchema({
538
505
  authMethod: { type: "string", required: false, description: "delegated or serviceAccount (legacy configs infer the method)" },
539
506
  clientId: {
@@ -25,7 +25,7 @@
25
25
  */
26
26
  import * as t from "typebox";
27
27
  import { pathSegment } from "../../_shared/credentials.js";
28
- import { googleCredential, googleJsonRequest } from "../../_shared/googleAuth.js";
28
+ import { googleCredential, googleJsonRequest, googleOAuth, } from "../../_shared/googleAuth.js";
29
29
  import { Id, NonEmptyString, PositiveInteger, stringEnum, } from "../../_shared/googleSchemas.js";
30
30
  // ─── Fields ─────────────────────────────────────────────────────
31
31
  /**
@@ -375,37 +375,7 @@ export default function googleContacts(rl) {
375
375
  rl.setName("googleContacts");
376
376
  rl.setVersion("0.1.0");
377
377
  rl.setCredential(googleCredential("googleContacts", SCOPES));
378
- rl.setOAuth({
379
- authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
380
- tokenUrl: "https://oauth2.googleapis.com/token",
381
- scopes: SCOPES,
382
- authParams: { access_type: "offline", prompt: "consent" },
383
- setupHelp: [
384
- "You need a Google Cloud OAuth client. Takes ~5 minutes, one time.",
385
- "",
386
- "1. Create or pick a Google Cloud project:",
387
- " https://console.cloud.google.com/projectcreate",
388
- "",
389
- "2. Enable the People API:",
390
- " https://console.cloud.google.com/apis/library/people.googleapis.com",
391
- "",
392
- "3. Configure the OAuth consent screen:",
393
- " https://console.cloud.google.com/apis/credentials/consent",
394
- " • Audience: External",
395
- "",
396
- "4. Add yourself as a test user:",
397
- " https://console.cloud.google.com/auth/audience",
398
- "",
399
- "5. Create the OAuth client:",
400
- " https://console.cloud.google.com/apis/credentials",
401
- " • + Create credentials → OAuth client ID",
402
- " • Application type: Web application",
403
- " • Authorized redirect URIs → + Add URI: {{redirectUri}}",
404
- "",
405
- "6. Paste the Client ID and Client Secret below, or export",
406
- " GOOGLE_CONTACTS_CLIENT_ID and GOOGLE_CONTACTS_CLIENT_SECRET.",
407
- ],
408
- });
378
+ rl.setOAuth(googleOAuth("googleContacts", SCOPES, "GOOGLE_CONTACTS"));
409
379
  rl.setConnectionSchema({
410
380
  authMethod: { type: "string", required: false, description: "delegated or serviceAccount (legacy configs infer the method)" },
411
381
  clientId: { type: "string", required: false, env: "GOOGLE_CONTACTS_CLIENT_ID" },
@@ -7,7 +7,7 @@
7
7
  * — the Docs API itself only creates blank documents without a
8
8
  * target folder.
9
9
  */
10
- import { googleCredential } from "../../_shared/googleAuth.js";
10
+ import { googleCredential, googleOAuth } from "../../_shared/googleAuth.js";
11
11
  import { registerDocumentsActions } from "./documents.js";
12
12
  import { registerFormattingActions } from "./formatting.js";
13
13
  import { registerImagesActions } from "./images.js";
@@ -20,38 +20,7 @@ export default function googleDocs(rl) {
20
20
  rl.setName("googleDocs");
21
21
  rl.setVersion("0.1.0");
22
22
  rl.setCredential(googleCredential("googleDocs", SCOPES));
23
- rl.setOAuth({
24
- authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
25
- tokenUrl: "https://oauth2.googleapis.com/token",
26
- scopes: SCOPES,
27
- authParams: { access_type: "offline", prompt: "consent" },
28
- setupHelp: [
29
- "You need a Google Cloud OAuth client. Takes ~5 minutes, one time.",
30
- "",
31
- "1. Create or pick a Google Cloud project:",
32
- " https://console.cloud.google.com/projectcreate",
33
- "",
34
- "2. Enable the Google Docs API (and Drive API for document.create):",
35
- " https://console.cloud.google.com/apis/library/docs.googleapis.com",
36
- " https://console.cloud.google.com/apis/library/drive.googleapis.com",
37
- "",
38
- "3. Configure the OAuth consent screen:",
39
- " https://console.cloud.google.com/apis/credentials/consent",
40
- " • Audience: External",
41
- "",
42
- "4. Add yourself as a test user:",
43
- " https://console.cloud.google.com/auth/audience",
44
- "",
45
- "5. Create the OAuth client:",
46
- " https://console.cloud.google.com/apis/credentials",
47
- " • + Create credentials → OAuth client ID",
48
- " • Application type: Web application",
49
- " • Authorized redirect URIs → + Add URI: {{redirectUri}}",
50
- "",
51
- "6. Paste the Client ID and Client Secret below, or export",
52
- " GOOGLE_DOCS_CLIENT_ID and GOOGLE_DOCS_CLIENT_SECRET.",
53
- ],
54
- });
23
+ rl.setOAuth(googleOAuth("googleDocs", SCOPES, "GOOGLE_DOCS"));
55
24
  rl.setConnectionSchema({
56
25
  authMethod: {
57
26
  type: "string",
@@ -53,11 +53,13 @@
53
53
  * for larger files, driven off whether the caller supplies bytes
54
54
  * (Buffer) vs a path (streamed).
55
55
  */
56
- import { createReadStream, readFileSync, statSync, writeFileSync } from "node:fs";
56
+ import { createReadStream, statSync, writeFileSync } from "node:fs";
57
57
  import * as t from "typebox";
58
+ import { standardBase64 } from "../../_shared/base64.js";
58
59
  import { jsonAnswer, pathSegment } from "../../_shared/credentials.js";
59
- import { googleCredential, googleJsonRequest, googleResponse, } from "../../_shared/googleAuth.js";
60
+ import { googleCredential, googleJsonRequest, googleOAuth, googleResponse, } from "../../_shared/googleAuth.js";
60
61
  import { Id, NonEmptyString, RawGoogleObject, StringMap, StringOrStringArray, stringEnum, } from "../../_shared/googleSchemas.js";
62
+ import { readLocalFile } from "../../_shared/localFile.js";
61
63
  const PermissionRole = stringEnum([
62
64
  "owner",
63
65
  "organizer",
@@ -142,9 +144,12 @@ async function paginateAll(ctx, path, key, qs) {
142
144
  * mimeType. For resumable uploads we keep paths as paths so we can
143
145
  * stream instead of buffering 500 MB in memory.
144
146
  */
145
- function resolveContent(p) {
147
+ async function resolveContent(p) {
146
148
  if (typeof p.contentBase64 === "string") {
147
- const buf = Buffer.from(p.contentBase64, "base64");
149
+ const base64 = standardBase64(p.contentBase64);
150
+ if (base64 === undefined)
151
+ throw new Error("googleDrive: contentBase64 is not valid base64");
152
+ const buf = Buffer.from(base64, "base64");
148
153
  return {
149
154
  buffer: buf,
150
155
  size: buf.byteLength,
@@ -168,9 +173,14 @@ function resolveContent(p) {
168
173
  const BUFFER_THRESHOLD = 5 * 1024 * 1024;
169
174
  const fileName = p.name ?? p.contentPath.split("/").pop();
170
175
  if (stat.size <= BUFFER_THRESHOLD) {
176
+ const buffer = await readLocalFile(p.contentPath, {
177
+ subject: "googleDrive: contentPath",
178
+ maxBytes: BUFFER_THRESHOLD,
179
+ oversize: "googleDrive: contentPath grew while reading; retry the upload",
180
+ });
171
181
  return {
172
- buffer: readFileSync(p.contentPath),
173
- size: stat.size,
182
+ buffer,
183
+ size: buffer.byteLength,
174
184
  mimeType: p.mimeType,
175
185
  fileName,
176
186
  };
@@ -351,37 +361,7 @@ export default function googleDrive(rl) {
351
361
  rl.setName("googleDrive");
352
362
  rl.setVersion("0.1.0");
353
363
  rl.setCredential(googleCredential("googleDrive", SCOPES));
354
- rl.setOAuth({
355
- authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
356
- tokenUrl: "https://oauth2.googleapis.com/token",
357
- scopes: SCOPES,
358
- authParams: { access_type: "offline", prompt: "consent" },
359
- setupHelp: [
360
- "You need a Google Cloud OAuth client. Takes ~5 minutes, one time.",
361
- "",
362
- "1. Create or pick a Google Cloud project:",
363
- " https://console.cloud.google.com/projectcreate",
364
- "",
365
- "2. Enable the Google Drive API:",
366
- " https://console.cloud.google.com/apis/library/drive.googleapis.com",
367
- "",
368
- "3. Configure the OAuth consent screen:",
369
- " https://console.cloud.google.com/apis/credentials/consent",
370
- " • Audience: External",
371
- "",
372
- "4. Add yourself as a test user:",
373
- " https://console.cloud.google.com/auth/audience",
374
- "",
375
- "5. Create the OAuth client:",
376
- " https://console.cloud.google.com/apis/credentials",
377
- " • + Create credentials → OAuth client ID",
378
- " • Application type: Web application",
379
- " • Authorized redirect URIs → + Add URI: {{redirectUri}}",
380
- "",
381
- "6. Paste the Client ID and Client Secret below, or export",
382
- " GOOGLE_DRIVE_CLIENT_ID and GOOGLE_DRIVE_CLIENT_SECRET.",
383
- ],
384
- });
364
+ rl.setOAuth(googleOAuth("googleDrive", SCOPES, "GOOGLE_DRIVE"));
385
365
  rl.setConnectionSchema({
386
366
  authMethod: { type: "string", required: false, description: "delegated or serviceAccount (legacy configs infer the method)" },
387
367
  clientId: {
@@ -457,7 +437,7 @@ export default function googleDrive(rl) {
457
437
  }),
458
438
  async execute(input, ctx) {
459
439
  const p = (input ?? {});
460
- const content = resolveContent(p);
440
+ const content = await resolveContent(p);
461
441
  const metadata = {
462
442
  name: content.fileName ?? "Untitled",
463
443
  parents: [resolveParent(p.folderId, p.driveId)],
@@ -713,7 +693,7 @@ export default function googleDrive(rl) {
713
693
  // Step 1: upload new bytes if provided. Drive requires PATCH on
714
694
  // the upload endpoint for content replacement.
715
695
  if (hasBytes) {
716
- const c = resolveContent(p);
696
+ const c = await resolveContent(p);
717
697
  const mimeType = c.mimeType ?? p.mimeType ?? "application/octet-stream";
718
698
  if (c.buffer) {
719
699
  const url = new URL(`${API_BASE}/upload/drive/v3/files/${pathSegment(fileId)}`);
@@ -1494,9 +1474,6 @@ export default function googleDrive(rl) {
1494
1474
  if (p[k] !== undefined)
1495
1475
  body[k] = p[k];
1496
1476
  }
1497
- if (Object.keys(body).length === 0) {
1498
- throw new Error("googleDrive.revision.update: pass at least one of keepForever / published / publishAuto / publishedOutsideDomain.");
1499
- }
1500
1477
  return driveRequest(ctx, "PATCH", `/drive/v3/files/${pathSegment(p.fileId)}/revisions/${pathSegment(p.revisionId)}`, body, { fields: REVISION_FIELDS });
1501
1478
  },
1502
1479
  });
@@ -1682,9 +1659,6 @@ export default function googleDrive(rl) {
1682
1659
  qs.removeExpiration = true;
1683
1660
  if (p.useDomainAdminAccess)
1684
1661
  qs.useDomainAdminAccess = true;
1685
- if (Object.keys(body).length === 0 && !qs.removeExpiration) {
1686
- throw new Error("googleDrive.permission.update: pass at least one of role / expirationTime / removeExpiration.");
1687
- }
1688
1662
  return driveRequest(ctx, "PATCH", `/drive/v3/files/${pathSegment(p.fileId)}/permissions/${pathSegment(p.permissionId)}`, body, qs);
1689
1663
  },
1690
1664
  });
@@ -117,7 +117,7 @@ export default function googleImage(rl) {
117
117
  if (typeof p.prompt !== "string" || p.prompt.length === 0) {
118
118
  throw new Error("googleImage: prompt is required");
119
119
  }
120
- const img = readImageInput(p.imagePath, "googleImage");
120
+ const img = await readImageInput(p.imagePath, "googleImage");
121
121
  const model = p.model ?? "gemini-2.5-flash-image";
122
122
  const data = await generateContent(ctx, model, {
123
123
  contents: [
@@ -36,7 +36,7 @@
36
36
  */
37
37
  import * as t from "typebox";
38
38
  import { pathSegment } from "../../_shared/credentials.js";
39
- import { googleCredential, googleJsonRequest } from "../../_shared/googleAuth.js";
39
+ import { googleCredential, googleJsonRequest, googleOAuth, } from "../../_shared/googleAuth.js";
40
40
  import { Id, JsonValue, NonEmptyString, NonNegativeInteger, PositiveInteger, RawGoogleObject, StringArray, stringEnum, } from "../../_shared/googleSchemas.js";
41
41
  const ROW_NUMBER = "row_number";
42
42
  const sheetReferenceSchema = t.Union([NonEmptyString, NonNegativeInteger]);
@@ -338,39 +338,7 @@ export default function googleSheets(rl) {
338
338
  rl.setName("googleSheets");
339
339
  rl.setVersion("0.1.0");
340
340
  rl.setCredential(googleCredential("googleSheets", SCOPES));
341
- rl.setOAuth({
342
- authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
343
- tokenUrl: "https://oauth2.googleapis.com/token",
344
- scopes: SCOPES,
345
- authParams: { access_type: "offline", prompt: "consent" },
346
- setupHelp: [
347
- "You need a Google Cloud OAuth client. Takes ~5 minutes, one time.",
348
- "",
349
- "1. Create or pick a Google Cloud project:",
350
- " https://console.cloud.google.com/projectcreate",
351
- "",
352
- "2. Enable the Google Sheets API (and Drive API, required for",
353
- " spreadsheet.delete):",
354
- " https://console.cloud.google.com/apis/library/sheets.googleapis.com",
355
- " https://console.cloud.google.com/apis/library/drive.googleapis.com",
356
- "",
357
- "3. Configure the OAuth consent screen:",
358
- " https://console.cloud.google.com/apis/credentials/consent",
359
- " • Audience: External",
360
- "",
361
- "4. Add yourself as a test user:",
362
- " https://console.cloud.google.com/auth/audience",
363
- "",
364
- "5. Create the OAuth client:",
365
- " https://console.cloud.google.com/apis/credentials",
366
- " • + Create credentials → OAuth client ID",
367
- " • Application type: Web application",
368
- " • Authorized redirect URIs → + Add URI: {{redirectUri}}",
369
- "",
370
- "6. Paste the Client ID and Client Secret below, or export",
371
- " GOOGLE_SHEETS_CLIENT_ID and GOOGLE_SHEETS_CLIENT_SECRET.",
372
- ],
373
- });
341
+ rl.setOAuth(googleOAuth("googleSheets", SCOPES, "GOOGLE_SHEETS"));
374
342
  rl.setConnectionSchema({
375
343
  authMethod: { type: "string", required: false, description: "delegated or serviceAccount (legacy configs infer the method)" },
376
344
  clientId: {
@@ -22,7 +22,7 @@
22
22
  import { writeFileSync } from "node:fs";
23
23
  import * as t from "typebox";
24
24
  import { pathSegment } from "../../_shared/credentials.js";
25
- import { googleCredential, googleDownload, googleJsonRequest, } from "../../_shared/googleAuth.js";
25
+ import { googleCredential, googleDownload, googleJsonRequest, googleOAuth, } from "../../_shared/googleAuth.js";
26
26
  import { Id, NonEmptyString, PositiveInteger, RawGoogleObject, StringArray, stringEnum, } from "../../_shared/googleSchemas.js";
27
27
  // ─── Request ─────────────────────────────────────────────────────
28
28
  const API_BASE = "https://slides.googleapis.com/v1";
@@ -47,37 +47,7 @@ export default function googleSlides(rl) {
47
47
  rl.setName("googleSlides");
48
48
  rl.setVersion("0.1.0");
49
49
  rl.setCredential(googleCredential("googleSlides", SCOPES));
50
- rl.setOAuth({
51
- authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
52
- tokenUrl: "https://oauth2.googleapis.com/token",
53
- scopes: SCOPES,
54
- authParams: { access_type: "offline", prompt: "consent" },
55
- setupHelp: [
56
- "You need a Google Cloud OAuth client. Takes ~5 minutes, one time.",
57
- "",
58
- "1. Create or pick a Google Cloud project:",
59
- " https://console.cloud.google.com/projectcreate",
60
- "",
61
- "2. Enable the Google Slides API:",
62
- " https://console.cloud.google.com/apis/library/slides.googleapis.com",
63
- "",
64
- "3. Configure the OAuth consent screen:",
65
- " https://console.cloud.google.com/apis/credentials/consent",
66
- " • Audience: External",
67
- "",
68
- "4. Add yourself as a test user:",
69
- " https://console.cloud.google.com/auth/audience",
70
- "",
71
- "5. Create the OAuth client:",
72
- " https://console.cloud.google.com/apis/credentials",
73
- " • + Create credentials → OAuth client ID",
74
- " • Application type: Web application",
75
- " • Authorized redirect URIs → + Add URI: {{redirectUri}}",
76
- "",
77
- "6. Paste the Client ID and Client Secret below, or export",
78
- " GOOGLE_SLIDES_CLIENT_ID and GOOGLE_SLIDES_CLIENT_SECRET.",
79
- ],
80
- });
50
+ rl.setOAuth(googleOAuth("googleSlides", SCOPES, "GOOGLE_SLIDES"));
81
51
  rl.setConnectionSchema({
82
52
  authMethod: { type: "string", required: false, description: "delegated or serviceAccount (legacy configs infer the method)" },
83
53
  clientId: {
@@ -21,7 +21,7 @@
21
21
  */
22
22
  import * as t from "typebox";
23
23
  import { pathSegment } from "../../_shared/credentials.js";
24
- import { googleCredential, googleJsonRequest } from "../../_shared/googleAuth.js";
24
+ import { googleCredential, googleJsonRequest, googleOAuth, } from "../../_shared/googleAuth.js";
25
25
  import { GoogleTimestamp, Id, stringEnum, } from "../../_shared/googleSchemas.js";
26
26
  // ─── Request ─────────────────────────────────────────────────────
27
27
  const API_BASE = "https://tasks.googleapis.com/tasks/v1";
@@ -71,37 +71,7 @@ export default function googleTasks(rl) {
71
71
  rl.setName("googleTasks");
72
72
  rl.setVersion("0.1.0");
73
73
  rl.setCredential(googleCredential("googleTasks", SCOPES));
74
- rl.setOAuth({
75
- authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
76
- tokenUrl: "https://oauth2.googleapis.com/token",
77
- scopes: SCOPES,
78
- authParams: { access_type: "offline", prompt: "consent" },
79
- setupHelp: [
80
- "You need a Google Cloud OAuth client. Takes ~5 minutes, one time.",
81
- "",
82
- "1. Create or pick a Google Cloud project:",
83
- " https://console.cloud.google.com/projectcreate",
84
- "",
85
- "2. Enable the Google Tasks API:",
86
- " https://console.cloud.google.com/apis/library/tasks.googleapis.com",
87
- "",
88
- "3. Configure the OAuth consent screen:",
89
- " https://console.cloud.google.com/apis/credentials/consent",
90
- " • Audience: External",
91
- "",
92
- "4. Add yourself as a test user:",
93
- " https://console.cloud.google.com/auth/audience",
94
- "",
95
- "5. Create the OAuth client:",
96
- " https://console.cloud.google.com/apis/credentials",
97
- " • + Create credentials → OAuth client ID",
98
- " • Application type: Web application",
99
- " • Authorized redirect URIs → + Add URI: {{redirectUri}}",
100
- "",
101
- "6. Paste the Client ID and Client Secret below, or export",
102
- " GOOGLE_TASKS_CLIENT_ID and GOOGLE_TASKS_CLIENT_SECRET.",
103
- ],
104
- });
74
+ rl.setOAuth(googleOAuth("googleTasks", SCOPES, "GOOGLE_TASKS"));
105
75
  rl.setConnectionSchema({
106
76
  authMethod: { type: "string", required: false, description: "delegated or serviceAccount (legacy configs infer the method)" },
107
77
  clientId: { type: "string", required: false, env: "GOOGLE_TASKS_CLIENT_ID" },
@@ -62,7 +62,10 @@ export default function highlevel(rl) {
62
62
  },
63
63
  async execute(input, ctx) {
64
64
  const locationId = locationOf(ctx);
65
- const body = { ...input, locationId };
65
+ const body = {
66
+ ...input,
67
+ locationId,
68
+ };
66
69
  if (typeof body.tags === "string")
67
70
  body.tags = body.tags
68
71
  .split(",")
@@ -176,7 +179,10 @@ export default function highlevel(rl) {
176
179
  },
177
180
  async execute(input, ctx) {
178
181
  const locationId = locationOf(ctx);
179
- const body = { ...input, locationId };
182
+ const body = {
183
+ ...input,
184
+ locationId,
185
+ };
180
186
  if (body.stageId) {
181
187
  body.pipelineStageId = body.stageId;
182
188
  delete body.stageId;