runline 0.11.6 → 0.13.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 (256) hide show
  1. package/dist/commands/actions.js +3 -1
  2. package/dist/core/engine.js +3 -0
  3. package/dist/index.d.ts +1 -1
  4. package/dist/plugin/api.d.ts +2 -1
  5. package/dist/plugin/types.d.ts +2 -0
  6. package/dist/plugins/_shared/googleSchemas.js +23 -0
  7. package/dist/plugins/actionNetwork/src/index.js +23 -0
  8. package/dist/plugins/activeCampaign/src/index.js +48 -0
  9. package/dist/plugins/adalo/src/index.js +5 -0
  10. package/dist/plugins/affinity/src/index.js +16 -0
  11. package/dist/plugins/agileCrm/src/index.js +15 -0
  12. package/dist/plugins/airtable/src/index.js +10 -0
  13. package/dist/plugins/airtop/src/index.js +24 -0
  14. package/dist/plugins/apiTemplateIo/src/index.js +4 -0
  15. package/dist/plugins/asana/src/index.js +22 -0
  16. package/dist/plugins/autopilot/src/index.js +11 -0
  17. package/dist/plugins/bambooHr/src/index.js +11 -0
  18. package/dist/plugins/bannerbear/src/index.js +4 -0
  19. package/dist/plugins/baserow/src/index.js +8 -0
  20. package/dist/plugins/beeminder/src/index.js +18 -0
  21. package/dist/plugins/bitly/src/index.js +3 -0
  22. package/dist/plugins/bitwarden/src/index.js +19 -0
  23. package/dist/plugins/box/src/index.js +11 -0
  24. package/dist/plugins/brandfetch/src/index.js +5 -0
  25. package/dist/plugins/brevo/src/index.js +15 -0
  26. package/dist/plugins/bubble/src/index.js +5 -0
  27. package/dist/plugins/chargebee/src/index.js +5 -0
  28. package/dist/plugins/circleci/src/index.js +3 -0
  29. package/dist/plugins/ciscoWebex/src/index.js +10 -0
  30. package/dist/plugins/clearbit/src/index.js +3 -0
  31. package/dist/plugins/clickup/src/index.js +61 -0
  32. package/dist/plugins/clockify/src/index.js +25 -0
  33. package/dist/plugins/cloudflare/src/index.js +4 -0
  34. package/dist/plugins/cockpit/src/index.js +5 -0
  35. package/dist/plugins/coda/src/index.js +18 -0
  36. package/dist/plugins/coingecko/src/index.js +10 -0
  37. package/dist/plugins/contentful/src/index.js +7 -0
  38. package/dist/plugins/convertkit/src/index.js +16 -0
  39. package/dist/plugins/copper/src/index.js +7 -0
  40. package/dist/plugins/cortex/src/index.js +4 -0
  41. package/dist/plugins/currents/src/index.js +22 -0
  42. package/dist/plugins/customerIo/src/index.js +9 -0
  43. package/dist/plugins/databricks/src/index.js +34 -0
  44. package/dist/plugins/deepl/src/index.js +2 -0
  45. package/dist/plugins/demio/src/index.js +4 -0
  46. package/dist/plugins/dhl/src/index.js +1 -0
  47. package/dist/plugins/discord/src/index.js +13 -0
  48. package/dist/plugins/discourse/src/index.js +16 -0
  49. package/dist/plugins/disqus/src/index.js +4 -0
  50. package/dist/plugins/docker/src/index.js +5 -0
  51. package/dist/plugins/drift/src/index.js +5 -0
  52. package/dist/plugins/dropbox/src/index.js +9 -0
  53. package/dist/plugins/dropcontact/src/index.js +2 -0
  54. package/dist/plugins/egoi/src/index.js +4 -0
  55. package/dist/plugins/elasticsearch/src/index.js +9 -0
  56. package/dist/plugins/emelia/src/index.js +9 -0
  57. package/dist/plugins/erpnext/src/index.js +5 -0
  58. package/dist/plugins/facebookGraph/src/index.js +1 -0
  59. package/dist/plugins/freshdesk/src/index.js +10 -0
  60. package/dist/plugins/freshservice/src/index.js +7 -0
  61. package/dist/plugins/freshworksCrm/src/index.js +7 -0
  62. package/dist/plugins/getresponse/src/index.js +5 -0
  63. package/dist/plugins/ghost/src/index.js +5 -0
  64. package/dist/plugins/github/src/index.js +37 -0
  65. package/dist/plugins/gitlab/src/index.js +17 -0
  66. package/dist/plugins/gmail/src/index.js +200 -184
  67. package/dist/plugins/gong/src/index.js +4 -0
  68. package/dist/plugins/googleAppsScript/src/index.js +98 -43
  69. package/dist/plugins/googleCalendar/src/index.js +329 -250
  70. package/dist/plugins/googleContacts/src/index.js +144 -145
  71. package/dist/plugins/googleDocs/src/documents.js +31 -38
  72. package/dist/plugins/googleDocs/src/formatting.js +51 -47
  73. package/dist/plugins/googleDocs/src/images.js +23 -26
  74. package/dist/plugins/googleDocs/src/shared.js +55 -0
  75. package/dist/plugins/googleDocs/src/structure.js +111 -123
  76. package/dist/plugins/googleDocs/src/tables.js +103 -154
  77. package/dist/plugins/googleDocs/src/tabs.js +27 -23
  78. package/dist/plugins/googleDocs/src/text.js +106 -136
  79. package/dist/plugins/googleDrive/src/index.js +461 -408
  80. package/dist/plugins/googleImage/src/index.js +14 -16
  81. package/dist/plugins/googleSheets/src/index.js +527 -339
  82. package/dist/plugins/googleSlides/src/index.js +92 -72
  83. package/dist/plugins/googleTasks/src/index.js +94 -92
  84. package/dist/plugins/gotify/src/index.js +3 -0
  85. package/dist/plugins/gotowebinar/src/index.js +20 -0
  86. package/dist/plugins/grafana/src/index.js +17 -0
  87. package/dist/plugins/graphql/src/index.js +2 -0
  88. package/dist/plugins/grist/src/index.js +4 -0
  89. package/dist/plugins/hackernews/src/index.js +6 -0
  90. package/dist/plugins/halopsa/src/index.js +5 -0
  91. package/dist/plugins/harvest/src/index.js +14 -0
  92. package/dist/plugins/helpscout/src/index.js +13 -0
  93. package/dist/plugins/highlevel/src/index.js +17 -0
  94. package/dist/plugins/homeAssistant/src/index.js +13 -0
  95. package/dist/plugins/hubspot/src/index.js +14 -0
  96. package/dist/plugins/humanticAi/src/index.js +3 -0
  97. package/dist/plugins/hunter/src/index.js +3 -0
  98. package/dist/plugins/intercom/src/index.js +10 -0
  99. package/dist/plugins/iterable/src/index.js +6 -0
  100. package/dist/plugins/jenkins/src/index.js +11 -0
  101. package/dist/plugins/jira/src/index.js +16 -0
  102. package/dist/plugins/keap/src/index.js +28 -0
  103. package/dist/plugins/kobotoolbox/src/index.js +17 -0
  104. package/dist/plugins/lemlist/src/index.js +15 -0
  105. package/dist/plugins/linear/src/attachments.js +6 -0
  106. package/dist/plugins/linear/src/comments.js +5 -0
  107. package/dist/plugins/linear/src/cycles.js +2 -0
  108. package/dist/plugins/linear/src/initiatives.js +5 -0
  109. package/dist/plugins/linear/src/issues.js +14 -0
  110. package/dist/plugins/linear/src/labels.js +5 -0
  111. package/dist/plugins/linear/src/organization.js +1 -0
  112. package/dist/plugins/linear/src/projects.js +11 -0
  113. package/dist/plugins/linear/src/shared.js +2 -0
  114. package/dist/plugins/linear/src/states.js +2 -0
  115. package/dist/plugins/linear/src/teams.js +3 -0
  116. package/dist/plugins/linear/src/users.js +2 -0
  117. package/dist/plugins/linear/src/views.js +4 -0
  118. package/dist/plugins/linear/src/webhooks.js +4 -0
  119. package/dist/plugins/lingvanex/src/index.js +1 -0
  120. package/dist/plugins/linkedin/src/index.js +1 -0
  121. package/dist/plugins/lonescale/src/index.js +4 -0
  122. package/dist/plugins/magento/src/index.js +15 -0
  123. package/dist/plugins/mailcheck/src/index.js +1 -0
  124. package/dist/plugins/mailchimp/src/index.js +14 -0
  125. package/dist/plugins/mailerlite/src/index.js +4 -0
  126. package/dist/plugins/mailgun/src/index.js +1 -0
  127. package/dist/plugins/mailjet/src/index.js +3 -0
  128. package/dist/plugins/mandrill/src/index.js +2 -0
  129. package/dist/plugins/marketstack/src/index.js +3 -0
  130. package/dist/plugins/matrix/src/index.js +10 -0
  131. package/dist/plugins/mattermost/src/index.js +19 -0
  132. package/dist/plugins/mautic/src/index.js +20 -0
  133. package/dist/plugins/medium/src/index.js +3 -0
  134. package/dist/plugins/messagebird/src/index.js +2 -0
  135. package/dist/plugins/metabase/src/index.js +10 -0
  136. package/dist/plugins/microsoftCalendar/src/index.js +2 -0
  137. package/dist/plugins/microsoftFiles/src/index.js +5 -0
  138. package/dist/plugins/microsoftMail/src/index.js +4 -0
  139. package/dist/plugins/misp/src/index.js +44 -0
  140. package/dist/plugins/mocean/src/index.js +2 -0
  141. package/dist/plugins/monday/src/index.js +18 -0
  142. package/dist/plugins/monicaCrm/src/index.js +5 -0
  143. package/dist/plugins/msg91/src/index.js +1 -0
  144. package/dist/plugins/nasa/src/index.js +7 -0
  145. package/dist/plugins/netlify/src/index.js +7 -0
  146. package/dist/plugins/netscalerAdc/src/index.js +3 -0
  147. package/dist/plugins/nextcloud/src/index.js +13 -0
  148. package/dist/plugins/nocodb/src/index.js +5 -0
  149. package/dist/plugins/node/src/index.js +38 -0
  150. package/dist/plugins/notion/src/index.js +14 -0
  151. package/dist/plugins/npm/src/index.js +5 -0
  152. package/dist/plugins/odoo/src/index.js +6 -0
  153. package/dist/plugins/okta/src/index.js +5 -0
  154. package/dist/plugins/oneSimpleApi/src/index.js +10 -0
  155. package/dist/plugins/onfleet/src/index.js +24 -0
  156. package/dist/plugins/openThesaurus/src/index.js +1 -0
  157. package/dist/plugins/openai/src/index.js +1 -0
  158. package/dist/plugins/openweathermap/src/index.js +2 -0
  159. package/dist/plugins/oura/src/index.js +2 -0
  160. package/dist/plugins/paddle/src/index.js +9 -0
  161. package/dist/plugins/pagerduty/src/index.js +9 -0
  162. package/dist/plugins/parallel/src/index.js +1 -0
  163. package/dist/plugins/paypal/src/index.js +4 -0
  164. package/dist/plugins/peekalink/src/index.js +2 -0
  165. package/dist/plugins/phantombuster/src/index.js +5 -0
  166. package/dist/plugins/philipsHue/src/index.js +4 -0
  167. package/dist/plugins/pipedrive/src/index.js +14 -0
  168. package/dist/plugins/plivo/src/index.js +3 -0
  169. package/dist/plugins/postbin/src/index.js +6 -0
  170. package/dist/plugins/posthog/src/index.js +5 -0
  171. package/dist/plugins/profitwell/src/index.js +2 -0
  172. package/dist/plugins/pushbullet/src/index.js +4 -0
  173. package/dist/plugins/pushcut/src/index.js +1 -0
  174. package/dist/plugins/pushover/src/index.js +1 -0
  175. package/dist/plugins/quickbase/src/index.js +8 -0
  176. package/dist/plugins/quickbooks/src/index.js +5 -0
  177. package/dist/plugins/quickchart/src/index.js +1 -0
  178. package/dist/plugins/raindrop/src/index.js +13 -0
  179. package/dist/plugins/recraft/src/index.js +1 -0
  180. package/dist/plugins/reddit/src/index.js +10 -0
  181. package/dist/plugins/replicate/src/index.js +1 -0
  182. package/dist/plugins/rocketchat/src/index.js +1 -0
  183. package/dist/plugins/rundeck/src/index.js +2 -0
  184. package/dist/plugins/salesforce/src/metadata.js +4 -0
  185. package/dist/plugins/salesforce/src/query.js +5 -0
  186. package/dist/plugins/salesforce/src/sobjects.js +12 -0
  187. package/dist/plugins/salesmate/src/index.js +5 -0
  188. package/dist/plugins/securityScorecard/src/index.js +14 -0
  189. package/dist/plugins/segment/src/index.js +4 -0
  190. package/dist/plugins/sendgrid/src/index.js +10 -0
  191. package/dist/plugins/sendy/src/index.js +6 -0
  192. package/dist/plugins/sentry/src/index.js +24 -0
  193. package/dist/plugins/servicenow/src/index.js +10 -0
  194. package/dist/plugins/shiftLabs/src/index.js +6 -0
  195. package/dist/plugins/shiftLabs/src/issues.js +4 -0
  196. package/dist/plugins/shiftLabs/src/pages.js +10 -0
  197. package/dist/plugins/shiftLabs/src/shared.js +2 -0
  198. package/dist/plugins/shiftLabs/src/transcription.js +203 -0
  199. package/dist/plugins/shopify/src/index.js +10 -0
  200. package/dist/plugins/signl4/src/index.js +2 -0
  201. package/dist/plugins/slack/src/index.js +38 -0
  202. package/dist/plugins/sms77/src/index.js +2 -0
  203. package/dist/plugins/splunk/src/index.js +16 -0
  204. package/dist/plugins/spotify/src/index.js +30 -0
  205. package/dist/plugins/stackby/src/index.js +4 -0
  206. package/dist/plugins/steel/src/browser.js +7 -0
  207. package/dist/plugins/steel/src/captchas.js +3 -0
  208. package/dist/plugins/steel/src/credentials.js +4 -0
  209. package/dist/plugins/steel/src/extensions.js +5 -0
  210. package/dist/plugins/steel/src/files.js +10 -0
  211. package/dist/plugins/steel/src/profiles.js +4 -0
  212. package/dist/plugins/steel/src/sessions.js +11 -0
  213. package/dist/plugins/storyblok/src/index.js +7 -0
  214. package/dist/plugins/strapi/src/index.js +5 -0
  215. package/dist/plugins/strava/src/index.js +6 -0
  216. package/dist/plugins/stripe/src/index.js +20 -0
  217. package/dist/plugins/supabase/src/index.js +5 -0
  218. package/dist/plugins/syncromsp/src/index.js +20 -0
  219. package/dist/plugins/tapfiliate/src/index.js +11 -0
  220. package/dist/plugins/telegram/src/index.js +21 -0
  221. package/dist/plugins/thehive/src/index.js +23 -0
  222. package/dist/plugins/thehiveProject/src/index.js +31 -0
  223. package/dist/plugins/todoist/src/index.js +31 -0
  224. package/dist/plugins/together/src/index.js +1 -0
  225. package/dist/plugins/travisci/src/index.js +5 -0
  226. package/dist/plugins/trello/src/index.js +37 -0
  227. package/dist/plugins/twake/src/index.js +1 -0
  228. package/dist/plugins/twilio/src/index.js +2 -0
  229. package/dist/plugins/twist/src/index.js +22 -0
  230. package/dist/plugins/twitter/src/index.js +8 -0
  231. package/dist/plugins/unleashedSoftware/src/index.js +3 -0
  232. package/dist/plugins/uplead/src/index.js +2 -0
  233. package/dist/plugins/uproc/src/index.js +1 -0
  234. package/dist/plugins/uptimerobot/src/index.js +22 -0
  235. package/dist/plugins/urlscanio/src/index.js +3 -0
  236. package/dist/plugins/vercel/src/account.js +1 -0
  237. package/dist/plugins/vercel/src/deployments.js +5 -0
  238. package/dist/plugins/vercel/src/env.js +4 -0
  239. package/dist/plugins/vercel/src/projects.js +2 -0
  240. package/dist/plugins/vercel/src/shared.js +1 -0
  241. package/dist/plugins/vero/src/index.js +6 -0
  242. package/dist/plugins/vonage/src/index.js +1 -0
  243. package/dist/plugins/wekan/src/index.js +24 -0
  244. package/dist/plugins/woocommerce/src/index.js +5 -0
  245. package/dist/plugins/wordpress/src/index.js +10 -0
  246. package/dist/plugins/xai/src/index.js +1 -0
  247. package/dist/plugins/xero/src/index.js +8 -0
  248. package/dist/plugins/yourls/src/index.js +3 -0
  249. package/dist/plugins/zammad/src/index.js +12 -0
  250. package/dist/plugins/zendesk/src/index.js +18 -0
  251. package/dist/plugins/zoho/src/index.js +6 -0
  252. package/dist/plugins/zoom/src/index.js +5 -0
  253. package/dist/plugins/zulip/src/index.js +15 -0
  254. package/dist/sdk.d.ts +2 -1
  255. package/dist/sdk.js +1 -0
  256. package/package.json +1 -1
@@ -22,14 +22,57 @@
22
22
  * sheet.deleteDimension — drop N rows or columns starting at an index
23
23
  * sheet.batchUpdate — raw passthrough to spreadsheets:batchUpdate
24
24
  *
25
+ * chart.add / chart.update / chart.delete
26
+ * namedRange.add / namedRange.delete
27
+ * protectedRange.add / protectedRange.delete
28
+ * conditionalFormat.add / conditionalFormat.delete
29
+ * dataValidation.set
30
+ *
25
31
  * Features: auto-column-add on update/upsert, multi-row
26
32
  * batchUpdate, RAW vs USER_ENTERED value input, appending via
27
33
  * values:append or PUT at lastRow+1, keepFirstRow on clear.
28
34
  * Filtering is intentionally the caller's job — `read` returns
29
35
  * raw rows / objects and callers post-filter.
30
36
  */
37
+ import * as t from "typebox";
31
38
  import { googleAccessToken } from "../../_shared/googleAuth.js";
39
+ import { Id, JsonValue, NonEmptyString, NonNegativeInteger, PositiveInteger, RawGoogleObject, StringArray, stringEnum, } from "../../_shared/googleSchemas.js";
32
40
  const ROW_NUMBER = "row_number";
41
+ const sheetReferenceSchema = t.Union([NonEmptyString, NonNegativeInteger]);
42
+ const cellValueSchema = t.Union([
43
+ t.String(),
44
+ t.Number(),
45
+ t.Boolean(),
46
+ t.Null(),
47
+ ]);
48
+ const objectRowSchema = t.Record(t.String(), JsonValue);
49
+ const rowsSchema = t.Union([
50
+ t.Array(t.Array(cellValueSchema)),
51
+ t.Array(objectRowSchema),
52
+ ]);
53
+ const valueInputOptionSchema = stringEnum(["RAW", "USER_ENTERED"]);
54
+ const handlingExtraDataSchema = stringEnum([
55
+ "ignore",
56
+ "error",
57
+ "insertInNewColumn",
58
+ ]);
59
+ const initialSheetSchema = t.Object({
60
+ title: NonEmptyString,
61
+ sheetId: t.Optional(NonNegativeInteger),
62
+ index: t.Optional(NonNegativeInteger),
63
+ sheetType: t.Optional(stringEnum(["GRID", "OBJECT", "DATA_SOURCE"])),
64
+ hidden: t.Optional(t.Boolean()),
65
+ rightToLeft: t.Optional(t.Boolean()),
66
+ gridProperties: t.Optional(t.Object({
67
+ rowCount: t.Optional(PositiveInteger),
68
+ columnCount: t.Optional(PositiveInteger),
69
+ frozenRowCount: t.Optional(NonNegativeInteger),
70
+ frozenColumnCount: t.Optional(NonNegativeInteger),
71
+ hideGridlines: t.Optional(t.Boolean()),
72
+ rowGroupControlAfter: t.Optional(t.Boolean()),
73
+ columnGroupControlAfter: t.Optional(t.Boolean()),
74
+ }, { additionalProperties: false })),
75
+ }, { additionalProperties: false });
33
76
  // ─── Auth ────────────────────────────────────────────────────────
34
77
  async function accessToken(ctx) {
35
78
  return googleAccessToken(ctx, "googleSheets", SCOPES);
@@ -60,7 +103,8 @@ async function sheetsRequest(ctx, method, path, body, qs, baseOverride) {
60
103
  },
61
104
  };
62
105
  if (body && Object.keys(body).length > 0) {
63
- init.headers["Content-Type"] = "application/json";
106
+ init.headers["Content-Type"] =
107
+ "application/json";
64
108
  init.body = JSON.stringify(body);
65
109
  }
66
110
  const res = await fetch(url.toString(), init);
@@ -322,7 +366,13 @@ function a1RangeToGridRange(a1, sheetId) {
322
366
  const startRow = parseInt(m[2], 10) - 1;
323
367
  const endCol = columnLetterToNumber(m[3]);
324
368
  const endRow = parseInt(m[4], 10);
325
- return { sheetId, startRowIndex: startRow, endRowIndex: endRow, startColumnIndex: startCol, endColumnIndex: endCol };
369
+ return {
370
+ sheetId,
371
+ startRowIndex: startRow,
372
+ endRowIndex: endRow,
373
+ startColumnIndex: startCol,
374
+ endColumnIndex: endCol,
375
+ };
326
376
  }
327
377
  export default function googleSheets(rl) {
328
378
  rl.setName("googleSheets");
@@ -408,21 +458,14 @@ export default function googleSheets(rl) {
408
458
  });
409
459
  // ── Spreadsheet ───────────────────────────────────────
410
460
  rl.registerAction("spreadsheet.create", {
461
+ access: "write",
411
462
  description: "Create a new spreadsheet",
412
- inputSchema: {
413
- title: { type: "string", required: true },
414
- sheets: {
415
- type: "array",
416
- required: false,
417
- description: "[{title, hidden?}] — initial sheet tabs",
418
- },
419
- locale: { type: "string", required: false, description: "e.g. en_US" },
420
- autoRecalc: {
421
- type: "string",
422
- required: false,
423
- description: "ON_CHANGE | MINUTE | HOUR",
424
- },
425
- },
463
+ inputSchema: t.Object({
464
+ title: NonEmptyString,
465
+ sheets: t.Optional(t.Array(initialSheetSchema)),
466
+ locale: t.Optional(NonEmptyString),
467
+ autoRecalc: t.Optional(stringEnum(["ON_CHANGE", "MINUTE", "HOUR"])),
468
+ }, { additionalProperties: false }),
426
469
  async execute(input, ctx) {
427
470
  const p = (input ?? {});
428
471
  const body = {
@@ -441,17 +484,14 @@ export default function googleSheets(rl) {
441
484
  },
442
485
  });
443
486
  rl.registerAction("spreadsheet.get", {
487
+ access: "read",
444
488
  description: "Get spreadsheet metadata (sheets, properties, optional grid data)",
445
- inputSchema: {
446
- spreadsheetId: { type: "string", required: true },
447
- includeGridData: { type: "boolean", required: false },
448
- ranges: {
449
- type: "array",
450
- required: false,
451
- description: "Limit the response to specific A1 ranges",
452
- },
453
- fields: { type: "string", required: false },
454
- },
489
+ inputSchema: t.Object({
490
+ spreadsheetId: Id,
491
+ includeGridData: t.Optional(t.Boolean()),
492
+ ranges: t.Optional(StringArray),
493
+ fields: t.Optional(NonEmptyString),
494
+ }, { additionalProperties: false }),
455
495
  async execute(input, ctx) {
456
496
  const p = (input ?? {});
457
497
  const qs = {};
@@ -465,8 +505,9 @@ export default function googleSheets(rl) {
465
505
  },
466
506
  });
467
507
  rl.registerAction("spreadsheet.delete", {
508
+ access: "write",
468
509
  description: "Delete a spreadsheet (via Drive API — requires drive.file scope)",
469
- inputSchema: { spreadsheetId: { type: "string", required: true } },
510
+ inputSchema: t.Object({ spreadsheetId: Id }, { additionalProperties: false }),
470
511
  async execute(input, ctx) {
471
512
  const p = (input ?? {});
472
513
  await sheetsRequest(ctx, "DELETE", `/drive/v3/files/${p.spreadsheetId}`, undefined, undefined, "https://www.googleapis.com");
@@ -475,24 +516,26 @@ export default function googleSheets(rl) {
475
516
  });
476
517
  // ── Sheet tabs ────────────────────────────────────────
477
518
  rl.registerAction("sheet.listTabs", {
519
+ access: "read",
478
520
  description: "List sheet tabs in a spreadsheet",
479
- inputSchema: { spreadsheetId: { type: "string", required: true } },
521
+ inputSchema: t.Object({ spreadsheetId: Id }, { additionalProperties: false }),
480
522
  async execute(input, ctx) {
481
523
  const p = (input ?? {});
482
524
  return getSheetProperties(ctx, p.spreadsheetId);
483
525
  },
484
526
  });
485
527
  rl.registerAction("sheet.addTab", {
528
+ access: "write",
486
529
  description: "Add a new sheet tab",
487
- inputSchema: {
488
- spreadsheetId: { type: "string", required: true },
489
- title: { type: "string", required: true },
490
- index: { type: "number", required: false, description: "Position within the spreadsheet" },
491
- sheetId: { type: "number", required: false, description: "Custom tab ID (non-negative)" },
492
- hidden: { type: "boolean", required: false },
493
- rightToLeft: { type: "boolean", required: false },
494
- tabColor: { type: "string", required: false, description: "Hex RGB (e.g. #0aa55c)" },
495
- },
530
+ inputSchema: t.Object({
531
+ spreadsheetId: Id,
532
+ title: NonEmptyString,
533
+ index: t.Optional(NonNegativeInteger),
534
+ sheetId: t.Optional(NonNegativeInteger),
535
+ hidden: t.Optional(t.Boolean()),
536
+ rightToLeft: t.Optional(t.Boolean()),
537
+ tabColor: t.Optional(t.String({ pattern: "^#?[0-9a-fA-F]{6}$" })),
538
+ }, { additionalProperties: false }),
496
539
  async execute(input, ctx) {
497
540
  const p = (input ?? {});
498
541
  const properties = { title: p.title };
@@ -514,15 +557,12 @@ export default function googleSheets(rl) {
514
557
  },
515
558
  });
516
559
  rl.registerAction("sheet.deleteTab", {
560
+ access: "write",
517
561
  description: "Delete a sheet tab",
518
- inputSchema: {
519
- spreadsheetId: { type: "string", required: true },
520
- sheet: {
521
- type: "string",
522
- required: true,
523
- description: "Tab title or numeric sheetId",
524
- },
525
- },
562
+ inputSchema: t.Object({
563
+ spreadsheetId: Id,
564
+ sheet: sheetReferenceSchema,
565
+ }, { additionalProperties: false }),
526
566
  async execute(input, ctx) {
527
567
  const p = (input ?? {});
528
568
  const sheetId = await resolveSheetId(ctx, p.spreadsheetId, p.sheet);
@@ -534,41 +574,22 @@ export default function googleSheets(rl) {
534
574
  });
535
575
  // ── Read ──────────────────────────────────────────────
536
576
  rl.registerAction("sheet.read", {
577
+ access: "read",
537
578
  description: "Read values from a range. Returns a 2D array by default; set `asObjects=true` to pair rows with a header row and return objects.",
538
- inputSchema: {
539
- spreadsheetId: { type: "string", required: true },
540
- range: {
541
- type: "string",
542
- required: true,
543
- description: "Sheet tab name, or full A1 range (e.g. 'Sheet1!A1:C10')",
544
- },
545
- asObjects: { type: "boolean", required: false },
546
- headerRow: {
547
- type: "number",
548
- required: false,
549
- description: "1-indexed header row (default: 1, used with asObjects)",
550
- },
551
- dataStartRow: {
552
- type: "number",
553
- required: false,
554
- description: "1-indexed first data row (default: headerRow + 1)",
555
- },
556
- valueRenderOption: {
557
- type: "string",
558
- required: false,
559
- description: "FORMATTED_VALUE (default) | UNFORMATTED_VALUE | FORMULA",
560
- },
561
- dateTimeRenderOption: {
562
- type: "string",
563
- required: false,
564
- description: "FORMATTED_STRING (default) | SERIAL_NUMBER",
565
- },
566
- includeRowNumber: {
567
- type: "boolean",
568
- required: false,
569
- description: "Attach `row_number` to each object (asObjects only)",
570
- },
571
- },
579
+ inputSchema: t.Object({
580
+ spreadsheetId: Id,
581
+ range: NonEmptyString,
582
+ asObjects: t.Optional(t.Boolean()),
583
+ headerRow: t.Optional(PositiveInteger),
584
+ dataStartRow: t.Optional(PositiveInteger),
585
+ valueRenderOption: t.Optional(stringEnum([
586
+ "FORMATTED_VALUE",
587
+ "UNFORMATTED_VALUE",
588
+ "FORMULA",
589
+ ])),
590
+ dateTimeRenderOption: t.Optional(stringEnum(["FORMATTED_STRING", "SERIAL_NUMBER"])),
591
+ includeRowNumber: t.Optional(t.Boolean()),
592
+ }, { additionalProperties: false }),
572
593
  async execute(input, ctx) {
573
594
  const p = (input ?? {});
574
595
  const rows = await getValues(ctx, p.spreadsheetId, p.range, p.valueRenderOption ?? "FORMATTED_VALUE", p.dateTimeRenderOption ?? "FORMATTED_STRING");
@@ -590,40 +611,17 @@ export default function googleSheets(rl) {
590
611
  });
591
612
  // ── Append ────────────────────────────────────────────
592
613
  rl.registerAction("sheet.append", {
614
+ access: "write",
593
615
  description: "Append rows to the bottom. Pass `rows` as arrays (matching columns) or objects (keyed by header names; extra keys become new columns by default).",
594
- inputSchema: {
595
- spreadsheetId: { type: "string", required: true },
596
- sheet: {
597
- type: "string",
598
- required: true,
599
- description: "Tab name or numeric sheetId",
600
- },
601
- rows: {
602
- type: "array",
603
- required: true,
604
- description: "Array of arrays, or array of objects",
605
- },
606
- headerRow: {
607
- type: "number",
608
- required: false,
609
- description: "1-indexed header row (default: 1, used when rows are objects)",
610
- },
611
- valueInputOption: {
612
- type: "string",
613
- required: false,
614
- description: "USER_ENTERED (default) | RAW",
615
- },
616
- handlingExtraData: {
617
- type: "string",
618
- required: false,
619
- description: "insertInNewColumn (default) | ignore | error",
620
- },
621
- useAppend: {
622
- type: "boolean",
623
- required: false,
624
- description: "Use values:append (safer w/ formulas but rewrites filters) instead of PUT at lastRow+1",
625
- },
626
- },
616
+ inputSchema: t.Object({
617
+ spreadsheetId: Id,
618
+ sheet: sheetReferenceSchema,
619
+ rows: rowsSchema,
620
+ headerRow: t.Optional(PositiveInteger),
621
+ valueInputOption: t.Optional(valueInputOptionSchema),
622
+ handlingExtraData: t.Optional(handlingExtraDataSchema),
623
+ useAppend: t.Optional(t.Boolean()),
624
+ }, { additionalProperties: false }),
627
625
  async execute(input, ctx) {
628
626
  const p = (input ?? {});
629
627
  const spreadsheetId = p.spreadsheetId;
@@ -684,39 +682,37 @@ export default function googleSheets(rl) {
684
682
  });
685
683
  // ── Update ────────────────────────────────────────────
686
684
  rl.registerAction("sheet.update", {
685
+ access: "write",
687
686
  description: "Update rows matched by a key column (or by the synthetic 'row_number' field). Rows are objects; undefined/null values are skipped.",
688
- inputSchema: {
689
- spreadsheetId: { type: "string", required: true },
690
- sheet: { type: "string", required: true },
691
- rows: { type: "array", required: true, description: "Array of objects" },
692
- matchKey: {
693
- type: "string",
694
- required: true,
695
- description: "Column name to match on, or 'row_number'",
696
- },
697
- headerRow: { type: "number", required: false, description: "1-indexed (default: 1)" },
698
- dataStartRow: { type: "number", required: false },
699
- valueInputOption: { type: "string", required: false },
700
- handlingExtraData: { type: "string", required: false },
701
- },
687
+ inputSchema: t.Object({
688
+ spreadsheetId: Id,
689
+ sheet: sheetReferenceSchema,
690
+ rows: t.Array(objectRowSchema),
691
+ matchKey: NonEmptyString,
692
+ headerRow: t.Optional(PositiveInteger),
693
+ dataStartRow: t.Optional(PositiveInteger),
694
+ valueInputOption: t.Optional(valueInputOptionSchema),
695
+ handlingExtraData: t.Optional(handlingExtraDataSchema),
696
+ }, { additionalProperties: false }),
702
697
  async execute(input, ctx) {
703
698
  const p = (input ?? {});
704
699
  return runUpdateOrUpsert(ctx, p, false);
705
700
  },
706
701
  });
707
702
  rl.registerAction("sheet.appendOrUpdate", {
703
+ access: "write",
708
704
  description: "Upsert rows. Rows whose match value is found are updated in place; rows with a missing or unknown match value are appended.",
709
- inputSchema: {
710
- spreadsheetId: { type: "string", required: true },
711
- sheet: { type: "string", required: true },
712
- rows: { type: "array", required: true, description: "Array of objects" },
713
- matchKey: { type: "string", required: true },
714
- headerRow: { type: "number", required: false },
715
- dataStartRow: { type: "number", required: false },
716
- valueInputOption: { type: "string", required: false },
717
- handlingExtraData: { type: "string", required: false },
718
- useAppend: { type: "boolean", required: false },
719
- },
705
+ inputSchema: t.Object({
706
+ spreadsheetId: Id,
707
+ sheet: sheetReferenceSchema,
708
+ rows: t.Array(objectRowSchema),
709
+ matchKey: NonEmptyString,
710
+ headerRow: t.Optional(PositiveInteger),
711
+ dataStartRow: t.Optional(PositiveInteger),
712
+ valueInputOption: t.Optional(valueInputOptionSchema),
713
+ handlingExtraData: t.Optional(handlingExtraDataSchema),
714
+ useAppend: t.Optional(t.Boolean()),
715
+ }, { additionalProperties: false }),
720
716
  async execute(input, ctx) {
721
717
  const p = (input ?? {});
722
718
  return runUpdateOrUpsert(ctx, p, true);
@@ -724,26 +720,72 @@ export default function googleSheets(rl) {
724
720
  });
725
721
  // ── Clear ─────────────────────────────────────────────
726
722
  rl.registerAction("sheet.clear", {
723
+ access: "write",
727
724
  description: "Clear values from a sheet. Modes: wholeSheet (optional keepFirstRow) | rows | columns | range.",
728
- inputSchema: {
729
- spreadsheetId: { type: "string", required: true },
730
- sheet: { type: "string", required: true },
731
- mode: {
732
- type: "string",
733
- required: false,
734
- description: "wholeSheet (default) | rows | columns | range",
735
- },
736
- startRow: { type: "number", required: false, description: "1-indexed, for mode=rows" },
737
- rowCount: { type: "number", required: false, description: "for mode=rows" },
738
- startColumn: { type: "string", required: false, description: "A1 letter, for mode=columns" },
739
- columnCount: { type: "number", required: false, description: "for mode=columns" },
740
- range: { type: "string", required: false, description: "A1 region, for mode=range" },
741
- keepFirstRow: {
742
- type: "boolean",
743
- required: false,
744
- description: "mode=wholeSheet only — preserve row 1",
745
- },
746
- },
725
+ inputSchema: t.Object({
726
+ spreadsheetId: Id,
727
+ sheet: sheetReferenceSchema,
728
+ mode: t.Optional(stringEnum(["wholeSheet", "rows", "columns", "range"])),
729
+ startRow: t.Optional(PositiveInteger),
730
+ rowCount: t.Optional(PositiveInteger),
731
+ startColumn: t.Optional(t.String({ pattern: "^[A-Za-z]+$" })),
732
+ columnCount: t.Optional(PositiveInteger),
733
+ range: t.Optional(NonEmptyString),
734
+ keepFirstRow: t.Optional(t.Boolean()),
735
+ }, {
736
+ additionalProperties: false,
737
+ oneOf: [
738
+ {
739
+ properties: { mode: { const: "wholeSheet" } },
740
+ not: {
741
+ anyOf: [
742
+ { required: ["startRow"] },
743
+ { required: ["rowCount"] },
744
+ { required: ["startColumn"] },
745
+ { required: ["columnCount"] },
746
+ { required: ["range"] },
747
+ ],
748
+ },
749
+ },
750
+ {
751
+ properties: { mode: { const: "rows" } },
752
+ required: ["mode"],
753
+ not: {
754
+ anyOf: [
755
+ { required: ["startColumn"] },
756
+ { required: ["columnCount"] },
757
+ { required: ["range"] },
758
+ { required: ["keepFirstRow"] },
759
+ ],
760
+ },
761
+ },
762
+ {
763
+ properties: { mode: { const: "columns" } },
764
+ required: ["mode"],
765
+ not: {
766
+ anyOf: [
767
+ { required: ["startRow"] },
768
+ { required: ["rowCount"] },
769
+ { required: ["range"] },
770
+ { required: ["keepFirstRow"] },
771
+ ],
772
+ },
773
+ },
774
+ {
775
+ properties: { mode: { const: "range" } },
776
+ required: ["mode", "range"],
777
+ not: {
778
+ anyOf: [
779
+ { required: ["startRow"] },
780
+ { required: ["rowCount"] },
781
+ { required: ["startColumn"] },
782
+ { required: ["columnCount"] },
783
+ { required: ["keepFirstRow"] },
784
+ ],
785
+ },
786
+ },
787
+ ],
788
+ }),
747
789
  async execute(input, ctx) {
748
790
  const p = (input ?? {});
749
791
  const sheetName = typeof p.sheet === "number"
@@ -768,7 +810,9 @@ export default function googleSheets(rl) {
768
810
  }
769
811
  else if (mode === "range") {
770
812
  const region = String(p.range ?? "");
771
- range = region.includes("!") ? `${sheetName}!${region.split("!")[1]}` : `${sheetName}!${region}`;
813
+ range = region.includes("!")
814
+ ? `${sheetName}!${region.split("!")[1]}`
815
+ : `${sheetName}!${region}`;
772
816
  }
773
817
  else {
774
818
  range = sheetName;
@@ -786,18 +830,15 @@ export default function googleSheets(rl) {
786
830
  });
787
831
  // ── Delete rows/columns ───────────────────────────────
788
832
  rl.registerAction("sheet.deleteDimension", {
833
+ access: "write",
789
834
  description: "Delete a range of rows or columns",
790
- inputSchema: {
791
- spreadsheetId: { type: "string", required: true },
792
- sheet: { type: "string", required: true },
793
- dimension: { type: "string", required: true, description: "ROWS | COLUMNS" },
794
- startIndex: {
795
- type: "number",
796
- required: true,
797
- description: "1-indexed (row number) or column letter position",
798
- },
799
- count: { type: "number", required: false, description: "How many to delete (default: 1)" },
800
- },
835
+ inputSchema: t.Object({
836
+ spreadsheetId: Id,
837
+ sheet: sheetReferenceSchema,
838
+ dimension: stringEnum(["ROWS", "COLUMNS"]),
839
+ startIndex: PositiveInteger,
840
+ count: t.Optional(PositiveInteger),
841
+ }, { additionalProperties: false }),
801
842
  async execute(input, ctx) {
802
843
  const p = (input ?? {});
803
844
  const sheetId = await resolveSheetId(ctx, p.spreadsheetId, p.sheet);
@@ -820,114 +861,155 @@ export default function googleSheets(rl) {
820
861
  });
821
862
  // ── Raw batchUpdate ───────────────────────────────────
822
863
  rl.registerAction("sheet.batchUpdate", {
864
+ access: "write",
823
865
  description: "Raw passthrough to spreadsheets:batchUpdate for anything this plugin doesn't expose directly (formatting, merges, conditional rules, …).",
824
- inputSchema: {
825
- spreadsheetId: { type: "string", required: true },
826
- requests: { type: "array", required: true },
827
- },
866
+ inputSchema: t.Object({
867
+ spreadsheetId: Id,
868
+ requests: t.Array(RawGoogleObject, { minItems: 1 }),
869
+ }, { additionalProperties: false }),
828
870
  async execute(input, ctx) {
829
871
  const p = (input ?? {});
830
872
  return spreadsheetBatchUpdate(ctx, p.spreadsheetId, p.requests);
831
873
  },
832
874
  });
833
- }
834
- /**
835
- * Shared implementation for `sheet.update` and `sheet.appendOrUpdate`.
836
- * Reads current headers + the key column, decides per row whether
837
- * it's an update or an append, issues one `values:batchUpdate` for
838
- * the updates, and (in upsert mode) falls through to the append
839
- * path for the rest.
840
- */
841
- async function runUpdateOrUpsert(ctx, p, upsert) {
842
- const spreadsheetId = p.spreadsheetId;
843
- const sheetIdOrName = p.sheet;
844
- const rows = p.rows;
845
- if (!Array.isArray(rows) || rows.length === 0) {
846
- return { updated: 0, appended: 0 };
847
- }
848
- const matchKey = p.matchKey;
849
- if (!matchKey)
850
- throw new Error("googleSheets: matchKey is required");
851
- const valueInputOption = p.valueInputOption ?? "USER_ENTERED";
852
- const allProps = await getSheetProperties(ctx, spreadsheetId);
853
- const prop = allProps.find((s) => s.title === sheetIdOrName || String(s.sheetId) === String(sheetIdOrName));
854
- if (!prop)
855
- throw new Error(`googleSheets: sheet "${sheetIdOrName}" not found`);
856
- const sheetName = prop.title;
857
- const sheetId = prop.sheetId;
858
- const headerRowIdx = Math.max(1, p.headerRow ?? 1) - 1;
859
- const dataStartRowIdx = Math.max(headerRowIdx + 1, (p.dataStartRow ?? headerRowIdx + 2) - 1);
860
- const handlingExtraData = p.handlingExtraData ?? "insertInNewColumn";
861
- // Headers come from FORMATTED_VALUE (cosmetic strings). The match
862
- // column needs UNFORMATTED_VALUE so "1,234" in input matches 1234
863
- // on the sheet (issue a second fetch with UNFORMATTED_VALUE).
864
- const sheetData = await getValues(ctx, spreadsheetId, sheetName, "FORMATTED_VALUE");
865
- let headers = sheetData[headerRowIdx] ?? [];
866
- let keyColumnValues = [];
867
- if (matchKey !== ROW_NUMBER) {
868
- const idx = headers.indexOf(matchKey);
869
- if (idx === -1) {
870
- if (!upsert)
871
- throw new Error(`googleSheets: match column "${matchKey}" not found`);
875
+ /**
876
+ * Shared implementation for `sheet.update` and `sheet.appendOrUpdate`.
877
+ * Reads current headers + the key column, decides per row whether
878
+ * it's an update or an append, issues one `values:batchUpdate` for
879
+ * the updates, and (in upsert mode) falls through to the append
880
+ * path for the rest.
881
+ */
882
+ async function runUpdateOrUpsert(ctx, p, upsert) {
883
+ const spreadsheetId = p.spreadsheetId;
884
+ const sheetIdOrName = p.sheet;
885
+ const rows = p.rows;
886
+ if (!Array.isArray(rows) || rows.length === 0) {
887
+ return { updated: 0, appended: 0 };
872
888
  }
873
- else {
874
- const unformatted = await getValues(ctx, spreadsheetId, sheetName, "UNFORMATTED_VALUE");
875
- keyColumnValues = unformatted.slice(dataStartRowIdx).map((r) => String(r[idx] ?? ""));
889
+ const matchKey = p.matchKey;
890
+ if (!matchKey)
891
+ throw new Error("googleSheets: matchKey is required");
892
+ const valueInputOption = p.valueInputOption ?? "USER_ENTERED";
893
+ const allProps = await getSheetProperties(ctx, spreadsheetId);
894
+ const prop = allProps.find((s) => s.title === sheetIdOrName ||
895
+ String(s.sheetId) === String(sheetIdOrName));
896
+ if (!prop)
897
+ throw new Error(`googleSheets: sheet "${sheetIdOrName}" not found`);
898
+ const sheetName = prop.title;
899
+ const sheetId = prop.sheetId;
900
+ const headerRowIdx = Math.max(1, p.headerRow ?? 1) - 1;
901
+ const dataStartRowIdx = Math.max(headerRowIdx + 1, (p.dataStartRow ?? headerRowIdx + 2) - 1);
902
+ const handlingExtraData = p.handlingExtraData ??
903
+ "insertInNewColumn";
904
+ // Headers come from FORMATTED_VALUE (cosmetic strings). The match
905
+ // column needs UNFORMATTED_VALUE so "1,234" in input matches 1234
906
+ // on the sheet (issue a second fetch with UNFORMATTED_VALUE).
907
+ const sheetData = await getValues(ctx, spreadsheetId, sheetName, "FORMATTED_VALUE");
908
+ let headers = sheetData[headerRowIdx] ?? [];
909
+ let keyColumnValues = [];
910
+ if (matchKey !== ROW_NUMBER) {
911
+ const idx = headers.indexOf(matchKey);
912
+ if (idx === -1) {
913
+ if (!upsert)
914
+ throw new Error(`googleSheets: match column "${matchKey}" not found`);
915
+ }
916
+ else {
917
+ const unformatted = await getValues(ctx, spreadsheetId, sheetName, "UNFORMATTED_VALUE");
918
+ keyColumnValues = unformatted
919
+ .slice(dataStartRowIdx)
920
+ .map((r) => String(r[idx] ?? ""));
921
+ }
876
922
  }
877
- }
878
- const prepared = prepareUpdateOrUpsert(rows, headers, keyColumnValues, dataStartRowIdx, matchKey, { upsert, handlingExtraData });
879
- // If we added new columns, extend the header row on the sheet first.
880
- if (prepared.newColumns.length > 0) {
881
- headers = [...headers, ...prepared.newColumns];
882
- await sheetsRequest(ctx, "PUT", `/v4/spreadsheets/${spreadsheetId}/values/${encodeA1(`${sheetName}!${headerRowIdx + 1}:${headerRowIdx + 1}`)}`, {
883
- range: `${sheetName}!${headerRowIdx + 1}:${headerRowIdx + 1}`,
884
- values: [headers],
885
- }, { valueInputOption });
886
- }
887
- // Qualify update ranges with the sheet name (prepareUpdateOrUpsert
888
- // returned bare A1 cells like "C5").
889
- const updateData = prepared.updateData.map((d) => ({
890
- range: `${sheetName}!${d.range}`,
891
- values: d.values,
892
- }));
893
- if (updateData.length > 0) {
894
- await batchValuesUpdate(ctx, spreadsheetId, updateData, valueInputOption);
895
- }
896
- if (upsert && prepared.appendData.length > 0) {
897
- const values = objectsToRows(prepared.appendData, headers);
898
- if (p.useAppend) {
899
- await sheetsRequest(ctx, "POST", `/v4/spreadsheets/${spreadsheetId}/values/${encodeA1(sheetName)}:append`, { range: sheetName, values }, { valueInputOption, insertDataOption: "INSERT_ROWS" });
923
+ const prepared = prepareUpdateOrUpsert(rows, headers, keyColumnValues, dataStartRowIdx, matchKey, { upsert, handlingExtraData });
924
+ // If we added new columns, extend the header row on the sheet first.
925
+ if (prepared.newColumns.length > 0) {
926
+ headers = [...headers, ...prepared.newColumns];
927
+ await sheetsRequest(ctx, "PUT", `/v4/spreadsheets/${spreadsheetId}/values/${encodeA1(`${sheetName}!${headerRowIdx + 1}:${headerRowIdx + 1}`)}`, {
928
+ range: `${sheetName}!${headerRowIdx + 1}:${headerRowIdx + 1}`,
929
+ values: [headers],
930
+ }, { valueInputOption });
900
931
  }
901
- else {
902
- const lastRow = sheetData.length;
903
- await appendEmptyRow(ctx, spreadsheetId, sheetId);
904
- const targetRange = `${sheetName}!${lastRow + 1}:${lastRow + values.length}`;
905
- await sheetsRequest(ctx, "PUT", `/v4/spreadsheets/${spreadsheetId}/values/${encodeA1(targetRange)}`, { range: targetRange, values }, { valueInputOption });
932
+ // Qualify update ranges with the sheet name (prepareUpdateOrUpsert
933
+ // returned bare A1 cells like "C5").
934
+ const updateData = prepared.updateData.map((d) => ({
935
+ range: `${sheetName}!${d.range}`,
936
+ values: d.values,
937
+ }));
938
+ if (updateData.length > 0) {
939
+ await batchValuesUpdate(ctx, spreadsheetId, updateData, valueInputOption);
906
940
  }
941
+ if (upsert && prepared.appendData.length > 0) {
942
+ const values = objectsToRows(prepared.appendData, headers);
943
+ if (p.useAppend) {
944
+ await sheetsRequest(ctx, "POST", `/v4/spreadsheets/${spreadsheetId}/values/${encodeA1(sheetName)}:append`, { range: sheetName, values }, { valueInputOption, insertDataOption: "INSERT_ROWS" });
945
+ }
946
+ else {
947
+ const lastRow = sheetData.length;
948
+ await appendEmptyRow(ctx, spreadsheetId, sheetId);
949
+ const targetRange = `${sheetName}!${lastRow + 1}:${lastRow + values.length}`;
950
+ await sheetsRequest(ctx, "PUT", `/v4/spreadsheets/${spreadsheetId}/values/${encodeA1(targetRange)}`, { range: targetRange, values }, { valueInputOption });
951
+ }
952
+ }
953
+ return {
954
+ updated: prepared.updateData.length,
955
+ appended: prepared.appendData.length,
956
+ newColumns: prepared.newColumns,
957
+ };
907
958
  }
908
- return {
909
- updated: prepared.updateData.length,
910
- appended: prepared.appendData.length,
911
- newColumns: prepared.newColumns,
912
- };
913
959
  // ─── Charts ─────────────────────────────────────────────────────
914
960
  rl.registerAction("chart.add", {
961
+ access: "write",
915
962
  description: "Embed a chart on a sheet. Minimal signature: type ('COLUMN'|'BAR'|'LINE'|'AREA'|'PIE'|'SCATTER'|'COMBO'), data source (sheet name + A1 range), and target anchor cell. For full control pass a raw `chartSpec` object instead.",
916
- inputSchema: {
917
- spreadsheetId: { type: "string", required: true },
918
- title: { type: "string", required: false },
919
- type: { type: "string", required: false, description: "Basic chart type. Default COLUMN. Ignored when `chartSpec` is provided." },
920
- sourceSheet: { type: "string", required: false, description: "Sheet name holding the source range. Ignored when `chartSpec` is provided." },
921
- sourceRange: { type: "string", required: false, description: "A1 range on `sourceSheet`, e.g. 'A1:D20'. Ignored when `chartSpec` is provided." },
922
- legendPosition: { type: "string", required: false, description: "BOTTOM_LEGEND | TOP_LEGEND | RIGHT_LEGEND | LEFT_LEGEND | NO_LEGEND. Default BOTTOM_LEGEND." },
923
- stackedType: { type: "string", required: false, description: "STACKED | PERCENT_STACKED — for COLUMN/BAR/AREA." },
924
- anchorSheet: { type: "string", required: true, description: "Sheet to place the chart on." },
925
- anchorRow: { type: "number", required: false, default: 0 },
926
- anchorColumn: { type: "number", required: false, default: 0 },
927
- widthPx: { type: "number", required: false, default: 600 },
928
- heightPx: { type: "number", required: false, default: 371 },
929
- chartSpec: { type: "object", required: false, description: "Raw ChartSpec object; bypasses the simple type+source builder." },
930
- },
963
+ inputSchema: t.Object({
964
+ spreadsheetId: Id,
965
+ title: t.Optional(t.String()),
966
+ type: t.Optional(stringEnum([
967
+ "COLUMN",
968
+ "BAR",
969
+ "LINE",
970
+ "AREA",
971
+ "PIE",
972
+ "SCATTER",
973
+ "COMBO",
974
+ ])),
975
+ sourceSheet: t.Optional(sheetReferenceSchema),
976
+ sourceRange: t.Optional(NonEmptyString),
977
+ legendPosition: t.Optional(stringEnum([
978
+ "BOTTOM_LEGEND",
979
+ "TOP_LEGEND",
980
+ "RIGHT_LEGEND",
981
+ "LEFT_LEGEND",
982
+ "NO_LEGEND",
983
+ ])),
984
+ stackedType: t.Optional(stringEnum(["STACKED", "PERCENT_STACKED"])),
985
+ anchorSheet: sheetReferenceSchema,
986
+ anchorRow: t.Optional(NonNegativeInteger),
987
+ anchorColumn: t.Optional(NonNegativeInteger),
988
+ widthPx: t.Optional(PositiveInteger),
989
+ heightPx: t.Optional(PositiveInteger),
990
+ chartSpec: t.Optional(RawGoogleObject),
991
+ }, {
992
+ additionalProperties: false,
993
+ oneOf: [
994
+ {
995
+ required: ["chartSpec"],
996
+ not: {
997
+ anyOf: [
998
+ { required: ["title"] },
999
+ { required: ["type"] },
1000
+ { required: ["sourceSheet"] },
1001
+ { required: ["sourceRange"] },
1002
+ { required: ["legendPosition"] },
1003
+ { required: ["stackedType"] },
1004
+ ],
1005
+ },
1006
+ },
1007
+ {
1008
+ required: ["sourceSheet", "sourceRange"],
1009
+ not: { required: ["chartSpec"] },
1010
+ },
1011
+ ],
1012
+ }),
931
1013
  async execute(input, ctx) {
932
1014
  const p = (input ?? {});
933
1015
  const spreadsheetId = p.spreadsheetId;
@@ -946,14 +1028,41 @@ async function runUpdateOrUpsert(ctx, p, upsert) {
946
1028
  legendPosition: p.legendPosition ?? "BOTTOM_LEGEND",
947
1029
  stackedType: p.stackedType,
948
1030
  headerCount: 1,
949
- domains: [{ domain: { sourceRange: { sources: [{
950
- sheetId: grid.sheetId, startRowIndex: grid.startRowIndex, endRowIndex: grid.endRowIndex,
951
- startColumnIndex: grid.startColumnIndex, endColumnIndex: grid.startColumnIndex + 1,
952
- }] } } }],
953
- series: [{ series: { sourceRange: { sources: [{
954
- sheetId: grid.sheetId, startRowIndex: grid.startRowIndex, endRowIndex: grid.endRowIndex,
955
- startColumnIndex: grid.startColumnIndex + 1, endColumnIndex: grid.endColumnIndex,
956
- }] } }, targetAxis: "LEFT_AXIS" }],
1031
+ domains: [
1032
+ {
1033
+ domain: {
1034
+ sourceRange: {
1035
+ sources: [
1036
+ {
1037
+ sheetId: grid.sheetId,
1038
+ startRowIndex: grid.startRowIndex,
1039
+ endRowIndex: grid.endRowIndex,
1040
+ startColumnIndex: grid.startColumnIndex,
1041
+ endColumnIndex: grid.startColumnIndex + 1,
1042
+ },
1043
+ ],
1044
+ },
1045
+ },
1046
+ },
1047
+ ],
1048
+ series: [
1049
+ {
1050
+ series: {
1051
+ sourceRange: {
1052
+ sources: [
1053
+ {
1054
+ sheetId: grid.sheetId,
1055
+ startRowIndex: grid.startRowIndex,
1056
+ endRowIndex: grid.endRowIndex,
1057
+ startColumnIndex: grid.startColumnIndex + 1,
1058
+ endColumnIndex: grid.endColumnIndex,
1059
+ },
1060
+ ],
1061
+ },
1062
+ },
1063
+ targetAxis: "LEFT_AXIS",
1064
+ },
1065
+ ],
957
1066
  },
958
1067
  };
959
1068
  }
@@ -963,7 +1072,11 @@ async function runUpdateOrUpsert(ctx, p, upsert) {
963
1072
  spec,
964
1073
  position: {
965
1074
  overlayPosition: {
966
- anchorCell: { sheetId: anchorSheetId, rowIndex: p.anchorRow ?? 0, columnIndex: p.anchorColumn ?? 0 },
1075
+ anchorCell: {
1076
+ sheetId: anchorSheetId,
1077
+ rowIndex: p.anchorRow ?? 0,
1078
+ columnIndex: p.anchorColumn ?? 0,
1079
+ },
967
1080
  widthPixels: p.widthPx ?? 600,
968
1081
  heightPixels: p.heightPx ?? 371,
969
1082
  },
@@ -975,13 +1088,14 @@ async function runUpdateOrUpsert(ctx, p, upsert) {
975
1088
  },
976
1089
  });
977
1090
  rl.registerAction("chart.update", {
1091
+ access: "write",
978
1092
  description: "Update an existing chart's spec by chartId.",
979
- inputSchema: {
980
- spreadsheetId: { type: "string", required: true },
981
- chartId: { type: "number", required: true },
982
- chartSpec: { type: "object", required: true },
983
- fields: { type: "string", required: false, default: "*" },
984
- },
1093
+ inputSchema: t.Object({
1094
+ spreadsheetId: Id,
1095
+ chartId: NonNegativeInteger,
1096
+ chartSpec: RawGoogleObject,
1097
+ fields: t.Optional(NonEmptyString),
1098
+ }, { additionalProperties: false }),
985
1099
  async execute(input, ctx) {
986
1100
  const p = (input ?? {});
987
1101
  return spreadsheetBatchUpdate(ctx, p.spreadsheetId, [
@@ -996,11 +1110,12 @@ async function runUpdateOrUpsert(ctx, p, upsert) {
996
1110
  },
997
1111
  });
998
1112
  rl.registerAction("chart.delete", {
1113
+ access: "write",
999
1114
  description: "Delete a chart by its embedded-object id.",
1000
- inputSchema: {
1001
- spreadsheetId: { type: "string", required: true },
1002
- chartId: { type: "number", required: true },
1003
- },
1115
+ inputSchema: t.Object({
1116
+ spreadsheetId: Id,
1117
+ chartId: NonNegativeInteger,
1118
+ }, { additionalProperties: false }),
1004
1119
  async execute(input, ctx) {
1005
1120
  const p = (input ?? {});
1006
1121
  return spreadsheetBatchUpdate(ctx, p.spreadsheetId, [
@@ -1010,13 +1125,14 @@ async function runUpdateOrUpsert(ctx, p, upsert) {
1010
1125
  });
1011
1126
  // ─── Named ranges ───────────────────────────────────────────────
1012
1127
  rl.registerAction("namedRange.add", {
1128
+ access: "write",
1013
1129
  description: "Create a named range on a sheet (so formulas can reference it by name).",
1014
- inputSchema: {
1015
- spreadsheetId: { type: "string", required: true },
1016
- name: { type: "string", required: true },
1017
- sheet: { type: "string", required: true },
1018
- range: { type: "string", required: true, description: "A1 range on `sheet`, e.g. 'A1:D20'." },
1019
- },
1130
+ inputSchema: t.Object({
1131
+ spreadsheetId: Id,
1132
+ name: NonEmptyString,
1133
+ sheet: sheetReferenceSchema,
1134
+ range: NonEmptyString,
1135
+ }, { additionalProperties: false }),
1020
1136
  async execute(input, ctx) {
1021
1137
  const p = (input ?? {});
1022
1138
  const sheetId = await resolveSheetId(ctx, p.spreadsheetId, p.sheet);
@@ -1027,11 +1143,12 @@ async function runUpdateOrUpsert(ctx, p, upsert) {
1027
1143
  },
1028
1144
  });
1029
1145
  rl.registerAction("namedRange.delete", {
1146
+ access: "write",
1030
1147
  description: "Delete a named range by its id.",
1031
- inputSchema: {
1032
- spreadsheetId: { type: "string", required: true },
1033
- namedRangeId: { type: "string", required: true },
1034
- },
1148
+ inputSchema: t.Object({
1149
+ spreadsheetId: Id,
1150
+ namedRangeId: Id,
1151
+ }, { additionalProperties: false }),
1035
1152
  async execute(input, ctx) {
1036
1153
  const p = (input ?? {});
1037
1154
  return spreadsheetBatchUpdate(ctx, p.spreadsheetId, [
@@ -1041,19 +1158,23 @@ async function runUpdateOrUpsert(ctx, p, upsert) {
1041
1158
  });
1042
1159
  // ─── Protected ranges ───────────────────────────────────────────
1043
1160
  rl.registerAction("protectedRange.add", {
1161
+ access: "write",
1044
1162
  description: "Protect a range from edits. Pass `editorEmails` to restrict who can still edit; without it, only the sheet owner can edit.",
1045
- inputSchema: {
1046
- spreadsheetId: { type: "string", required: true },
1047
- sheet: { type: "string", required: true },
1048
- range: { type: "string", required: false, description: "A1 range. Omit to protect the whole sheet." },
1049
- description: { type: "string", required: false },
1050
- warningOnly: { type: "boolean", required: false, description: "When true, edits are allowed but show a warning." },
1051
- editorEmails: { type: "array", required: false },
1052
- },
1163
+ inputSchema: t.Object({
1164
+ spreadsheetId: Id,
1165
+ sheet: sheetReferenceSchema,
1166
+ range: t.Optional(NonEmptyString),
1167
+ description: t.Optional(t.String()),
1168
+ warningOnly: t.Optional(t.Boolean()),
1169
+ editorEmails: t.Optional(t.Array(NonEmptyString)),
1170
+ }, { additionalProperties: false }),
1053
1171
  async execute(input, ctx) {
1054
1172
  const p = (input ?? {});
1055
1173
  const sheetId = await resolveSheetId(ctx, p.spreadsheetId, p.sheet);
1056
- const protectedRange = { description: p.description, warningOnly: p.warningOnly };
1174
+ const protectedRange = {
1175
+ description: p.description,
1176
+ warningOnly: p.warningOnly,
1177
+ };
1057
1178
  if (p.range)
1058
1179
  protectedRange.range = a1RangeToGridRange(p.range, sheetId);
1059
1180
  else
@@ -1067,11 +1188,12 @@ async function runUpdateOrUpsert(ctx, p, upsert) {
1067
1188
  },
1068
1189
  });
1069
1190
  rl.registerAction("protectedRange.delete", {
1191
+ access: "write",
1070
1192
  description: "Delete a protected range by its id.",
1071
- inputSchema: {
1072
- spreadsheetId: { type: "string", required: true },
1073
- protectedRangeId: { type: "number", required: true },
1074
- },
1193
+ inputSchema: t.Object({
1194
+ spreadsheetId: Id,
1195
+ protectedRangeId: NonNegativeInteger,
1196
+ }, { additionalProperties: false }),
1075
1197
  async execute(input, ctx) {
1076
1198
  const p = (input ?? {});
1077
1199
  return spreadsheetBatchUpdate(ctx, p.spreadsheetId, [
@@ -1081,22 +1203,39 @@ async function runUpdateOrUpsert(ctx, p, upsert) {
1081
1203
  });
1082
1204
  // ─── Conditional formatting ─────────────────────────────────────
1083
1205
  rl.registerAction("conditionalFormat.add", {
1206
+ access: "write",
1084
1207
  description: "Add a single-condition conditional-format rule. For complex rules pass a raw `rule` object instead of the simple builder.",
1085
- inputSchema: {
1086
- spreadsheetId: { type: "string", required: true },
1087
- sheet: { type: "string", required: true },
1088
- range: { type: "string", required: true },
1089
- condition: {
1090
- type: "object",
1091
- required: false,
1092
- description: "BooleanCondition, e.g. { type: 'NUMBER_GREATER', values: [{ userEnteredValue: '100' }] }.",
1093
- },
1094
- backgroundColorHex: { type: "string", required: false },
1095
- foregroundColorHex: { type: "string", required: false },
1096
- bold: { type: "boolean", required: false },
1097
- italic: { type: "boolean", required: false },
1098
- rule: { type: "object", required: false, description: "Raw ConditionalFormatRule; bypasses the simple builder." },
1099
- },
1208
+ inputSchema: t.Object({
1209
+ spreadsheetId: Id,
1210
+ sheet: sheetReferenceSchema,
1211
+ range: NonEmptyString,
1212
+ condition: t.Optional(RawGoogleObject),
1213
+ backgroundColorHex: t.Optional(t.String({ pattern: "^#?[0-9a-fA-F]{6}$" })),
1214
+ foregroundColorHex: t.Optional(t.String({ pattern: "^#?[0-9a-fA-F]{6}$" })),
1215
+ bold: t.Optional(t.Boolean()),
1216
+ italic: t.Optional(t.Boolean()),
1217
+ rule: t.Optional(RawGoogleObject),
1218
+ }, {
1219
+ additionalProperties: false,
1220
+ oneOf: [
1221
+ {
1222
+ required: ["rule"],
1223
+ not: {
1224
+ anyOf: [
1225
+ { required: ["condition"] },
1226
+ { required: ["backgroundColorHex"] },
1227
+ { required: ["foregroundColorHex"] },
1228
+ { required: ["bold"] },
1229
+ { required: ["italic"] },
1230
+ ],
1231
+ },
1232
+ },
1233
+ {
1234
+ required: ["condition"],
1235
+ not: { required: ["rule"] },
1236
+ },
1237
+ ],
1238
+ }),
1100
1239
  async execute(input, ctx) {
1101
1240
  const p = (input ?? {});
1102
1241
  const sheetId = await resolveSheetId(ctx, p.spreadsheetId, p.sheet);
@@ -1118,18 +1257,24 @@ async function runUpdateOrUpsert(ctx, p, upsert) {
1118
1257
  ts.italic = p.italic;
1119
1258
  fmt.textFormat = ts;
1120
1259
  }
1121
- rule = { ranges: [grid], booleanRule: { condition: p.condition, format: fmt } };
1260
+ rule = {
1261
+ ranges: [grid],
1262
+ booleanRule: { condition: p.condition, format: fmt },
1263
+ };
1122
1264
  }
1123
- return spreadsheetBatchUpdate(ctx, p.spreadsheetId, [{ addConditionalFormatRule: { rule, index: 0 } }]);
1265
+ return spreadsheetBatchUpdate(ctx, p.spreadsheetId, [
1266
+ { addConditionalFormatRule: { rule, index: 0 } },
1267
+ ]);
1124
1268
  },
1125
1269
  });
1126
1270
  rl.registerAction("conditionalFormat.delete", {
1271
+ access: "write",
1127
1272
  description: "Delete a conditional-format rule by its index within the sheet's rule list.",
1128
- inputSchema: {
1129
- spreadsheetId: { type: "string", required: true },
1130
- sheet: { type: "string", required: true },
1131
- index: { type: "number", required: true },
1132
- },
1273
+ inputSchema: t.Object({
1274
+ spreadsheetId: Id,
1275
+ sheet: sheetReferenceSchema,
1276
+ index: NonNegativeInteger,
1277
+ }, { additionalProperties: false }),
1133
1278
  async execute(input, ctx) {
1134
1279
  const p = (input ?? {});
1135
1280
  const sheetId = await resolveSheetId(ctx, p.spreadsheetId, p.sheet);
@@ -1140,18 +1285,54 @@ async function runUpdateOrUpsert(ctx, p, upsert) {
1140
1285
  });
1141
1286
  // ─── Data validation ────────────────────────────────────────────
1142
1287
  rl.registerAction("dataValidation.set", {
1288
+ access: "write",
1143
1289
  description: "Apply a data-validation rule to a range. Pass either `condition` (BooleanCondition) or `oneOfList` (array of values for ONE_OF_LIST). Use `clear: true` to remove validation.",
1144
- inputSchema: {
1145
- spreadsheetId: { type: "string", required: true },
1146
- sheet: { type: "string", required: true },
1147
- range: { type: "string", required: true },
1148
- condition: { type: "object", required: false },
1149
- oneOfList: { type: "array", required: false },
1150
- showCustomUi: { type: "boolean", required: false, default: true },
1151
- strict: { type: "boolean", required: false, default: true },
1152
- inputMessage: { type: "string", required: false },
1153
- clear: { type: "boolean", required: false },
1154
- },
1290
+ inputSchema: t.Object({
1291
+ spreadsheetId: Id,
1292
+ sheet: sheetReferenceSchema,
1293
+ range: NonEmptyString,
1294
+ condition: t.Optional(RawGoogleObject),
1295
+ oneOfList: t.Optional(t.Array(JsonValue, { minItems: 1 })),
1296
+ showCustomUi: t.Optional(t.Boolean()),
1297
+ strict: t.Optional(t.Boolean()),
1298
+ inputMessage: t.Optional(t.String()),
1299
+ clear: t.Optional(t.Boolean()),
1300
+ }, {
1301
+ additionalProperties: false,
1302
+ oneOf: [
1303
+ {
1304
+ required: ["condition"],
1305
+ not: {
1306
+ anyOf: [
1307
+ { required: ["oneOfList"] },
1308
+ { required: ["clear"] },
1309
+ ],
1310
+ },
1311
+ },
1312
+ {
1313
+ required: ["oneOfList"],
1314
+ not: {
1315
+ anyOf: [
1316
+ { required: ["condition"] },
1317
+ { required: ["clear"] },
1318
+ ],
1319
+ },
1320
+ },
1321
+ {
1322
+ properties: { clear: { const: true } },
1323
+ required: ["clear"],
1324
+ not: {
1325
+ anyOf: [
1326
+ { required: ["condition"] },
1327
+ { required: ["oneOfList"] },
1328
+ { required: ["showCustomUi"] },
1329
+ { required: ["strict"] },
1330
+ { required: ["inputMessage"] },
1331
+ ],
1332
+ },
1333
+ },
1334
+ ],
1335
+ }),
1155
1336
  async execute(input, ctx) {
1156
1337
  const p = (input ?? {});
1157
1338
  const sheetId = await resolveSheetId(ctx, p.spreadsheetId, p.sheet);
@@ -1162,12 +1343,19 @@ async function runUpdateOrUpsert(ctx, p, upsert) {
1162
1343
  if (!cond && Array.isArray(p.oneOfList)) {
1163
1344
  cond = {
1164
1345
  type: "ONE_OF_LIST",
1165
- values: p.oneOfList.map((v) => ({ userEnteredValue: String(v) })),
1346
+ values: p.oneOfList.map((v) => ({
1347
+ userEnteredValue: String(v),
1348
+ })),
1166
1349
  };
1167
1350
  }
1168
1351
  if (!cond)
1169
1352
  throw new Error("googleSheets.dataValidation.set: pass condition or oneOfList, or set clear=true");
1170
- rule = { condition: cond, strict: p.strict ?? true, showCustomUi: p.showCustomUi ?? true, inputMessage: p.inputMessage };
1353
+ rule = {
1354
+ condition: cond,
1355
+ strict: p.strict ?? true,
1356
+ showCustomUi: p.showCustomUi ?? true,
1357
+ inputMessage: p.inputMessage,
1358
+ };
1171
1359
  }
1172
1360
  return spreadsheetBatchUpdate(ctx, p.spreadsheetId, [
1173
1361
  { setDataValidation: { range: grid, rule: rule ?? undefined } },