runline 0.11.2 → 0.11.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/plugin/loader.js +5 -0
  2. package/dist/plugins/googleDocs/src/documents.js +91 -0
  3. package/dist/plugins/googleDocs/src/formatting.js +126 -0
  4. package/dist/plugins/googleDocs/src/images.js +66 -0
  5. package/dist/plugins/googleDocs/src/index.js +45 -1008
  6. package/dist/plugins/googleDocs/src/shared.js +110 -0
  7. package/dist/plugins/googleDocs/src/structure.js +392 -0
  8. package/dist/plugins/googleDocs/src/tables.js +390 -0
  9. package/dist/plugins/googleDocs/src/tabs.js +77 -0
  10. package/dist/plugins/googleDocs/src/text.js +313 -0
  11. package/dist/plugins/linear/src/attachments.js +36 -12
  12. package/dist/plugins/linear/src/comments.js +20 -8
  13. package/dist/plugins/linear/src/cycles.js +22 -8
  14. package/dist/plugins/linear/src/initiatives.js +59 -19
  15. package/dist/plugins/linear/src/issues.js +123 -41
  16. package/dist/plugins/linear/src/labels.js +31 -11
  17. package/dist/plugins/linear/src/organization.js +1 -1
  18. package/dist/plugins/linear/src/projects.js +171 -57
  19. package/dist/plugins/linear/src/shared.js +16 -5
  20. package/dist/plugins/linear/src/states.js +14 -6
  21. package/dist/plugins/linear/src/teams.js +35 -12
  22. package/dist/plugins/linear/src/users.js +7 -3
  23. package/dist/plugins/linear/src/views.js +29 -10
  24. package/dist/plugins/linear/src/webhooks.js +39 -13
  25. package/dist/plugins/salesforce/src/index.js +33 -219
  26. package/dist/plugins/salesforce/src/metadata.js +50 -0
  27. package/dist/plugins/salesforce/src/query.js +52 -0
  28. package/dist/plugins/salesforce/src/queryResult.js +4 -0
  29. package/dist/plugins/salesforce/src/shared.js +122 -0
  30. package/dist/plugins/salesforce/src/sobjects.js +152 -0
  31. package/package.json +1 -1
@@ -6,145 +6,15 @@
6
6
  * because `document.create` goes through Drive's files endpoint
7
7
  * — the Docs API itself only creates blank documents without a
8
8
  * target folder.
9
- *
10
- * Surface area:
11
- *
12
- * document.create
13
- * document.get (optional `simple=true` returns flat text)
14
- * document.batchUpdate (raw request list)
15
- *
16
- * Plus convenience helpers that wrap the most common batchUpdate
17
- * shapes as first-class actions, addressable without constructing
18
- * the nested request objects by hand:
19
- *
20
- * document.insertText
21
- * document.replaceAllText
22
- * document.deleteContentRange
23
- * document.insertTable
24
- * document.insertPageBreak
25
- * document.createParagraphBullets
26
- * document.deleteParagraphBullets
27
- * document.createNamedRange
28
- * document.deleteNamedRange
29
- * document.createHeader / document.deleteHeader
30
- * document.createFooter / document.deleteFooter
31
- * document.deletePositionedObject
32
- * document.insertTableRow / document.deleteTableRow
33
- * document.insertTableColumn / document.deleteTableColumn
34
- *
35
- * Every helper ultimately hits `POST /v1/documents/{id}:batchUpdate`
36
- * with a single request; callers who need to chain multiple edits
37
- * atomically can compose them via `document.batchUpdate`.
38
- */
39
- import { googleAccessToken } from "../../_shared/googleAuth.js";
40
- // ─── Auth ────────────────────────────────────────────────────────
41
- async function accessToken(ctx) {
42
- return googleAccessToken(ctx, "googleDocs", SCOPES);
43
- }
44
- // ─── Request ─────────────────────────────────────────────────────
45
- const DOCS_BASE = "https://docs.googleapis.com/v1";
46
- const DRIVE_BASE = "https://www.googleapis.com/drive/v3";
47
- async function docsRequest(ctx, method, path, body, qs, baseOverride) {
48
- const token = await accessToken(ctx);
49
- const url = new URL(`${baseOverride ?? DOCS_BASE}${path}`);
50
- if (qs) {
51
- for (const [k, v] of Object.entries(qs)) {
52
- if (v === undefined || v === null)
53
- continue;
54
- url.searchParams.set(k, String(v));
55
- }
56
- }
57
- const init = {
58
- method,
59
- headers: { Authorization: `Bearer ${token}`, Accept: "application/json" },
60
- };
61
- if (body && Object.keys(body).length > 0) {
62
- init.headers["Content-Type"] = "application/json";
63
- init.body = JSON.stringify(body);
64
- }
65
- const res = await fetch(url.toString(), init);
66
- if (res.status === 204)
67
- return { success: true };
68
- const text = await res.text();
69
- if (!res.ok) {
70
- throw new Error(`googleDocs: ${method} ${path} → ${res.status} ${text}`);
71
- }
72
- return text ? JSON.parse(text) : { success: true };
73
- }
74
- // ─── Helpers ────────────────────────────────────────────────────
75
- const DOC_URL_REGEX = /https:\/\/docs\.google\.com\/document\/d\/([a-zA-Z0-9-_]+)/;
76
- /**
77
- * Accept a bare document ID or a full docs.google.com URL and return
78
- * the ID. Falls through to the input unchanged if no URL is detected.
79
- */
80
- function extractDocumentId(input) {
81
- if (!input)
82
- throw new Error("googleDocs: documentId or URL is required");
83
- const m = input.match(DOC_URL_REGEX);
84
- return m ? m[1] : input;
85
- }
86
- /**
87
- * Build a `Location` object for Docs insert requests. When
88
- * `segmentId` is "body" or missing, send an empty segmentId — Docs
89
- * treats that as "the document body". `index` is required for
90
- * `location`; `endOfSegmentLocation` doesn't take one.
91
- */
92
- function buildLocation(kind, segmentId, index) {
93
- const seg = segmentId && segmentId !== "body" ? segmentId : "";
94
- if (kind === "endOfSegmentLocation") {
95
- return { endOfSegmentLocation: { segmentId: seg } };
96
- }
97
- if (index === undefined || index === null) {
98
- throw new Error("googleDocs: `index` is required when location kind is 'location'");
99
- }
100
- return { location: { segmentId: seg, index } };
101
- }
102
- async function runBatchUpdate(ctx, documentId, request, writeControl) {
103
- const body = { requests: [request] };
104
- if (writeControl)
105
- body.writeControl = writeControl;
106
- const res = (await docsRequest(ctx, "POST", `/documents/${documentId}:batchUpdate`, body));
107
- // Flatten single-request replies so callers don't have to drill in.
108
- const reply = res.replies?.[0] ?? {};
109
- const key = Object.keys(reply)[0];
110
- return { documentId, ...(key ? { [key]: reply[key] } : {}) };
111
- }
112
- /**
113
- * Walk a `document.body.content` tree and concatenate every
114
- * `textRun.content` we find — the `simple=true` output on
115
- * `document.get`. Intentionally ignores tables, headers, footers,
116
- * and inline objects.
117
9
  */
118
- function flattenBodyText(body) {
119
- const parts = [];
120
- const content = body?.content ?? [];
121
- for (const item of content) {
122
- const para = item.paragraph;
123
- if (!para?.elements)
124
- continue;
125
- for (const el of para.elements) {
126
- const tr = el.textRun;
127
- if (tr?.content)
128
- parts.push(tr.content);
129
- }
130
- }
131
- return parts.join("");
132
- }
133
- // ─── Plugin ──────────────────────────────────────────────────────
134
- const SCOPES = [
135
- "https://www.googleapis.com/auth/documents",
136
- "https://www.googleapis.com/auth/drive.file",
137
- ];
138
- function hexToRgbF(hex) {
139
- const h = hex.replace(/^#/, "");
140
- const full = h.length === 3 ? h.split("").map((c) => c + c).join("") : h;
141
- const n = parseInt(full, 16);
142
- return {
143
- red: ((n >> 16) & 0xff) / 255,
144
- green: ((n >> 8) & 0xff) / 255,
145
- blue: (n & 0xff) / 255,
146
- };
147
- }
10
+ import { registerDocumentsActions } from "./documents.js";
11
+ import { registerFormattingActions } from "./formatting.js";
12
+ import { registerImagesActions } from "./images.js";
13
+ import { SCOPES } from "./shared.js";
14
+ import { registerStructureActions } from "./structure.js";
15
+ import { registerTablesActions } from "./tables.js";
16
+ import { registerTabActions } from "./tabs.js";
17
+ import { registerTextActions } from "./text.js";
148
18
  export default function googleDocs(rl) {
149
19
  rl.setName("googleDocs");
150
20
  rl.setVersion("0.1.0");
@@ -182,877 +52,44 @@ export default function googleDocs(rl) {
182
52
  });
183
53
  rl.setConnectionSchema({
184
54
  clientId: { type: "string", required: false, env: "GOOGLE_DOCS_CLIENT_ID" },
185
- clientSecret: { type: "string", required: false, env: "GOOGLE_DOCS_CLIENT_SECRET" },
186
- refreshToken: { type: "string", required: false, env: "GOOGLE_DOCS_REFRESH_TOKEN" },
187
- serviceAccountJson: { type: "string", required: false, env: "GOOGLE_DOCS_SERVICE_ACCOUNT_JSON" },
188
- serviceAccountEmail: { type: "string", required: false, env: "GOOGLE_DOCS_SERVICE_ACCOUNT_EMAIL" },
189
- serviceAccountPrivateKey: { type: "string", required: false, env: "GOOGLE_DOCS_SERVICE_ACCOUNT_PRIVATE_KEY" },
190
- serviceAccountSubject: { type: "string", required: false, env: "GOOGLE_DOCS_SERVICE_ACCOUNT_SUBJECT" },
55
+ clientSecret: {
56
+ type: "string",
57
+ required: false,
58
+ env: "GOOGLE_DOCS_CLIENT_SECRET",
59
+ },
60
+ refreshToken: {
61
+ type: "string",
62
+ required: false,
63
+ env: "GOOGLE_DOCS_REFRESH_TOKEN",
64
+ },
65
+ serviceAccountJson: {
66
+ type: "string",
67
+ required: false,
68
+ env: "GOOGLE_DOCS_SERVICE_ACCOUNT_JSON",
69
+ },
70
+ serviceAccountEmail: {
71
+ type: "string",
72
+ required: false,
73
+ env: "GOOGLE_DOCS_SERVICE_ACCOUNT_EMAIL",
74
+ },
75
+ serviceAccountPrivateKey: {
76
+ type: "string",
77
+ required: false,
78
+ env: "GOOGLE_DOCS_SERVICE_ACCOUNT_PRIVATE_KEY",
79
+ },
80
+ serviceAccountSubject: {
81
+ type: "string",
82
+ required: false,
83
+ env: "GOOGLE_DOCS_SERVICE_ACCOUNT_SUBJECT",
84
+ },
191
85
  accessToken: { type: "string", required: false },
192
86
  accessTokenExpiresAt: { type: "number", required: false },
193
87
  });
194
- // ── Document lifecycle ────────────────────────────────
195
- rl.registerAction("document.create", {
196
- description: "Create a new Google Doc, optionally in a specific Drive folder (goes through the Drive API; needs drive.file scope).",
197
- inputSchema: {
198
- title: { type: "string", required: true },
199
- folderId: {
200
- type: "string",
201
- required: false,
202
- description: "Parent folder in Drive. Omit to place in My Drive root.",
203
- },
204
- },
205
- async execute(input, ctx) {
206
- const p = (input ?? {});
207
- const body = {
208
- name: p.title,
209
- mimeType: "application/vnd.google-apps.document",
210
- };
211
- if (p.folderId) {
212
- body.parents = [p.folderId];
213
- }
214
- return docsRequest(ctx, "POST", "/files", body, undefined, DRIVE_BASE);
215
- },
216
- });
217
- rl.registerAction("document.get", {
218
- description: "Get a document. Accepts a bare ID or a docs.google.com URL. `simple=true` collapses the body to plain text.",
219
- inputSchema: {
220
- document: { type: "string", required: true, description: "Document ID or URL" },
221
- simple: { type: "boolean", required: false },
222
- suggestionsViewMode: {
223
- type: "string",
224
- required: false,
225
- description: "DEFAULT_FOR_CURRENT_ACCESS | SUGGESTIONS_INLINE | PREVIEW_SUGGESTIONS_ACCEPTED | PREVIEW_WITHOUT_SUGGESTIONS",
226
- },
227
- },
228
- async execute(input, ctx) {
229
- const p = (input ?? {});
230
- const documentId = extractDocumentId(p.document);
231
- const qs = {};
232
- if (p.suggestionsViewMode)
233
- qs.suggestionsViewMode = p.suggestionsViewMode;
234
- const res = (await docsRequest(ctx, "GET", `/documents/${documentId}`, undefined, qs));
235
- if (!p.simple)
236
- return res;
237
- return { documentId, content: flattenBodyText(res.body) };
238
- },
239
- });
240
- rl.registerAction("document.batchUpdate", {
241
- description: "Raw passthrough to documents.batchUpdate — pass a full `requests` array for atomic multi-edit operations.",
242
- inputSchema: {
243
- document: { type: "string", required: true },
244
- requests: { type: "array", required: true },
245
- writeControl: {
246
- type: "object",
247
- required: false,
248
- description: "{requiredRevisionId} | {targetRevisionId}",
249
- },
250
- },
251
- async execute(input, ctx) {
252
- const p = (input ?? {});
253
- const documentId = extractDocumentId(p.document);
254
- const body = {
255
- requests: p.requests,
256
- };
257
- if (p.writeControl)
258
- body.writeControl = p.writeControl;
259
- return docsRequest(ctx, "POST", `/documents/${documentId}:batchUpdate`, body);
260
- },
261
- });
262
- // ── Text edits ────────────────────────────────────────
263
- rl.registerAction("document.insertText", {
264
- description: "Insert text at a specific index, or at the end of a segment (body/header/footer/footnote).",
265
- inputSchema: {
266
- document: { type: "string", required: true },
267
- text: { type: "string", required: true },
268
- locationKind: {
269
- type: "string",
270
- required: false,
271
- description: "location (default; requires index) | endOfSegmentLocation",
272
- },
273
- index: { type: "number", required: false, description: "Required for locationKind=location" },
274
- segmentId: {
275
- type: "string",
276
- required: false,
277
- description: 'Segment ID, or "body" / empty for the main body',
278
- },
279
- },
280
- async execute(input, ctx) {
281
- const p = (input ?? {});
282
- const documentId = extractDocumentId(p.document);
283
- const kind = p.locationKind ?? "location";
284
- const locObj = buildLocation(kind, p.segmentId, p.index);
285
- return runBatchUpdate(ctx, documentId, {
286
- insertText: { text: p.text, ...locObj },
287
- });
288
- },
289
- });
290
- rl.registerAction("document.replaceAllText", {
291
- description: "Replace every occurrence of a text string throughout the document.",
292
- inputSchema: {
293
- document: { type: "string", required: true },
294
- findText: { type: "string", required: true },
295
- replaceText: { type: "string", required: true },
296
- matchCase: { type: "boolean", required: false },
297
- },
298
- async execute(input, ctx) {
299
- const p = (input ?? {});
300
- const documentId = extractDocumentId(p.document);
301
- return runBatchUpdate(ctx, documentId, {
302
- replaceAllText: {
303
- replaceText: p.replaceText,
304
- containsText: { text: p.findText, matchCase: p.matchCase === true },
305
- },
306
- });
307
- },
308
- });
309
- rl.registerAction("document.deleteContentRange", {
310
- description: "Delete text between two indices in a segment.",
311
- inputSchema: {
312
- document: { type: "string", required: true },
313
- startIndex: { type: "number", required: true },
314
- endIndex: { type: "number", required: true },
315
- segmentId: { type: "string", required: false },
316
- },
317
- async execute(input, ctx) {
318
- const p = (input ?? {});
319
- const documentId = extractDocumentId(p.document);
320
- const seg = p.segmentId && p.segmentId !== "body" ? p.segmentId : "";
321
- return runBatchUpdate(ctx, documentId, {
322
- deleteContentRange: {
323
- range: {
324
- segmentId: seg,
325
- startIndex: p.startIndex,
326
- endIndex: p.endIndex,
327
- },
328
- },
329
- });
330
- },
331
- });
332
- // ── Structural inserts ────────────────────────────────
333
- rl.registerAction("document.insertPageBreak", {
334
- description: "Insert a page break at an index or at the end of a segment.",
335
- inputSchema: {
336
- document: { type: "string", required: true },
337
- locationKind: { type: "string", required: false },
338
- index: { type: "number", required: false },
339
- segmentId: { type: "string", required: false },
340
- },
341
- async execute(input, ctx) {
342
- const p = (input ?? {});
343
- const documentId = extractDocumentId(p.document);
344
- const kind = p.locationKind ?? "location";
345
- return runBatchUpdate(ctx, documentId, {
346
- insertPageBreak: buildLocation(kind, p.segmentId, p.index),
347
- });
348
- },
349
- });
350
- rl.registerAction("document.insertTable", {
351
- description: "Insert an empty table with the given dimensions.",
352
- inputSchema: {
353
- document: { type: "string", required: true },
354
- rows: { type: "number", required: true },
355
- columns: { type: "number", required: true },
356
- locationKind: { type: "string", required: false },
357
- index: { type: "number", required: false },
358
- segmentId: { type: "string", required: false },
359
- },
360
- async execute(input, ctx) {
361
- const p = (input ?? {});
362
- const documentId = extractDocumentId(p.document);
363
- const kind = p.locationKind ?? "location";
364
- return runBatchUpdate(ctx, documentId, {
365
- insertTable: {
366
- rows: p.rows,
367
- columns: p.columns,
368
- ...buildLocation(kind, p.segmentId, p.index),
369
- },
370
- });
371
- },
372
- });
373
- rl.registerAction("document.insertTableRow", {
374
- description: "Insert a table row above or below a cell in an existing table.",
375
- inputSchema: {
376
- document: { type: "string", required: true },
377
- tableStartIndex: {
378
- type: "number",
379
- required: true,
380
- description: "Document index where the table begins",
381
- },
382
- rowIndex: { type: "number", required: true },
383
- columnIndex: { type: "number", required: true },
384
- insertBelow: { type: "boolean", required: false, description: "default: false (insert above)" },
385
- segmentId: { type: "string", required: false },
386
- },
387
- async execute(input, ctx) {
388
- const p = (input ?? {});
389
- const documentId = extractDocumentId(p.document);
390
- const seg = p.segmentId && p.segmentId !== "body" ? p.segmentId : "";
391
- return runBatchUpdate(ctx, documentId, {
392
- insertTableRow: {
393
- insertBelow: p.insertBelow === true,
394
- tableCellLocation: {
395
- rowIndex: p.rowIndex,
396
- columnIndex: p.columnIndex,
397
- tableStartLocation: { segmentId: seg, index: p.tableStartIndex },
398
- },
399
- },
400
- });
401
- },
402
- });
403
- rl.registerAction("document.deleteTableRow", {
404
- description: "Delete a specific row from a table.",
405
- inputSchema: {
406
- document: { type: "string", required: true },
407
- tableStartIndex: { type: "number", required: true },
408
- rowIndex: { type: "number", required: true },
409
- columnIndex: { type: "number", required: true },
410
- segmentId: { type: "string", required: false },
411
- },
412
- async execute(input, ctx) {
413
- const p = (input ?? {});
414
- const documentId = extractDocumentId(p.document);
415
- const seg = p.segmentId && p.segmentId !== "body" ? p.segmentId : "";
416
- return runBatchUpdate(ctx, documentId, {
417
- deleteTableRow: {
418
- tableCellLocation: {
419
- rowIndex: p.rowIndex,
420
- columnIndex: p.columnIndex,
421
- tableStartLocation: { segmentId: seg, index: p.tableStartIndex },
422
- },
423
- },
424
- });
425
- },
426
- });
427
- rl.registerAction("document.insertTableColumn", {
428
- description: "Insert a column left or right of a cell.",
429
- inputSchema: {
430
- document: { type: "string", required: true },
431
- tableStartIndex: { type: "number", required: true },
432
- rowIndex: { type: "number", required: true },
433
- columnIndex: { type: "number", required: true },
434
- insertRight: { type: "boolean", required: false, description: "default: false (insert left)" },
435
- segmentId: { type: "string", required: false },
436
- },
437
- async execute(input, ctx) {
438
- const p = (input ?? {});
439
- const documentId = extractDocumentId(p.document);
440
- const seg = p.segmentId && p.segmentId !== "body" ? p.segmentId : "";
441
- return runBatchUpdate(ctx, documentId, {
442
- insertTableColumn: {
443
- insertRight: p.insertRight === true,
444
- tableCellLocation: {
445
- rowIndex: p.rowIndex,
446
- columnIndex: p.columnIndex,
447
- tableStartLocation: { segmentId: seg, index: p.tableStartIndex },
448
- },
449
- },
450
- });
451
- },
452
- });
453
- rl.registerAction("document.deleteTableColumn", {
454
- description: "Delete a specific column from a table.",
455
- inputSchema: {
456
- document: { type: "string", required: true },
457
- tableStartIndex: { type: "number", required: true },
458
- rowIndex: { type: "number", required: true },
459
- columnIndex: { type: "number", required: true },
460
- segmentId: { type: "string", required: false },
461
- },
462
- async execute(input, ctx) {
463
- const p = (input ?? {});
464
- const documentId = extractDocumentId(p.document);
465
- const seg = p.segmentId && p.segmentId !== "body" ? p.segmentId : "";
466
- return runBatchUpdate(ctx, documentId, {
467
- deleteTableColumn: {
468
- tableCellLocation: {
469
- rowIndex: p.rowIndex,
470
- columnIndex: p.columnIndex,
471
- tableStartLocation: { segmentId: seg, index: p.tableStartIndex },
472
- },
473
- },
474
- });
475
- },
476
- });
477
- // ── Bullets ───────────────────────────────────────────
478
- rl.registerAction("document.createParagraphBullets", {
479
- description: "Apply a bullet preset to paragraphs spanning a range. Presets: BULLET_DISC_CIRCLE_SQUARE, BULLET_DIAMONDX_ARROW3D_SQUARE, BULLET_CHECKBOX, NUMBERED_DECIMAL_ALPHA_ROMAN, NUMBERED_DECIMAL_NESTED, etc.",
480
- inputSchema: {
481
- document: { type: "string", required: true },
482
- bulletPreset: { type: "string", required: true },
483
- startIndex: { type: "number", required: true },
484
- endIndex: { type: "number", required: true },
485
- segmentId: { type: "string", required: false },
486
- },
487
- async execute(input, ctx) {
488
- const p = (input ?? {});
489
- const documentId = extractDocumentId(p.document);
490
- const seg = p.segmentId && p.segmentId !== "body" ? p.segmentId : "";
491
- return runBatchUpdate(ctx, documentId, {
492
- createParagraphBullets: {
493
- bulletPreset: p.bulletPreset,
494
- range: { segmentId: seg, startIndex: p.startIndex, endIndex: p.endIndex },
495
- },
496
- });
497
- },
498
- });
499
- rl.registerAction("document.deleteParagraphBullets", {
500
- description: "Remove bullets from paragraphs in a range.",
501
- inputSchema: {
502
- document: { type: "string", required: true },
503
- startIndex: { type: "number", required: true },
504
- endIndex: { type: "number", required: true },
505
- segmentId: { type: "string", required: false },
506
- },
507
- async execute(input, ctx) {
508
- const p = (input ?? {});
509
- const documentId = extractDocumentId(p.document);
510
- const seg = p.segmentId && p.segmentId !== "body" ? p.segmentId : "";
511
- return runBatchUpdate(ctx, documentId, {
512
- deleteParagraphBullets: {
513
- range: { segmentId: seg, startIndex: p.startIndex, endIndex: p.endIndex },
514
- },
515
- });
516
- },
517
- });
518
- // ── Named ranges ──────────────────────────────────────
519
- rl.registerAction("document.createNamedRange", {
520
- description: "Create a named range over a span of text (useful for later programmatic edits).",
521
- inputSchema: {
522
- document: { type: "string", required: true },
523
- name: { type: "string", required: true },
524
- startIndex: { type: "number", required: true },
525
- endIndex: { type: "number", required: true },
526
- segmentId: { type: "string", required: false },
527
- },
528
- async execute(input, ctx) {
529
- const p = (input ?? {});
530
- const documentId = extractDocumentId(p.document);
531
- const seg = p.segmentId && p.segmentId !== "body" ? p.segmentId : "";
532
- return runBatchUpdate(ctx, documentId, {
533
- createNamedRange: {
534
- name: p.name,
535
- range: { segmentId: seg, startIndex: p.startIndex, endIndex: p.endIndex },
536
- },
537
- });
538
- },
539
- });
540
- rl.registerAction("document.deleteNamedRange", {
541
- description: "Delete named range(s). Pass one of `namedRangeId` or `name`; the latter deletes every range sharing that name.",
542
- inputSchema: {
543
- document: { type: "string", required: true },
544
- namedRangeId: { type: "string", required: false },
545
- name: { type: "string", required: false },
546
- },
547
- async execute(input, ctx) {
548
- const p = (input ?? {});
549
- const documentId = extractDocumentId(p.document);
550
- if (!p.namedRangeId && !p.name) {
551
- throw new Error("googleDocs: provide namedRangeId or name");
552
- }
553
- const req = p.namedRangeId
554
- ? { namedRangeId: p.namedRangeId }
555
- : { name: p.name };
556
- return runBatchUpdate(ctx, documentId, { deleteNamedRange: req });
557
- },
558
- });
559
- // ── Header / footer / positioned object ──────────────
560
- rl.registerAction("document.createHeader", {
561
- description: "Create a DEFAULT header attached to a SectionBreak.",
562
- inputSchema: {
563
- document: { type: "string", required: true },
564
- locationKind: { type: "string", required: false },
565
- index: { type: "number", required: false },
566
- segmentId: { type: "string", required: false },
567
- },
568
- async execute(input, ctx) {
569
- const p = (input ?? {});
570
- const documentId = extractDocumentId(p.document);
571
- const kind = p.locationKind ?? "location";
572
- const seg = p.segmentId && p.segmentId !== "body" ? p.segmentId : "";
573
- const sectionBreakLocation = { segmentId: seg };
574
- if (kind === "location") {
575
- if (p.index === undefined) {
576
- throw new Error("googleDocs: `index` is required when locationKind=location");
577
- }
578
- sectionBreakLocation.index = p.index;
579
- }
580
- return runBatchUpdate(ctx, documentId, {
581
- createHeader: { type: "DEFAULT", sectionBreakLocation },
582
- });
583
- },
584
- });
585
- rl.registerAction("document.deleteHeader", {
586
- description: "Delete a header by ID.",
587
- inputSchema: {
588
- document: { type: "string", required: true },
589
- headerId: { type: "string", required: true },
590
- },
591
- async execute(input, ctx) {
592
- const p = (input ?? {});
593
- const documentId = extractDocumentId(p.document);
594
- return runBatchUpdate(ctx, documentId, { deleteHeader: { headerId: p.headerId } });
595
- },
596
- });
597
- rl.registerAction("document.createFooter", {
598
- description: "Create a DEFAULT footer attached to a SectionBreak.",
599
- inputSchema: {
600
- document: { type: "string", required: true },
601
- locationKind: { type: "string", required: false },
602
- index: { type: "number", required: false },
603
- segmentId: { type: "string", required: false },
604
- },
605
- async execute(input, ctx) {
606
- const p = (input ?? {});
607
- const documentId = extractDocumentId(p.document);
608
- const kind = p.locationKind ?? "location";
609
- const seg = p.segmentId && p.segmentId !== "body" ? p.segmentId : "";
610
- const sectionBreakLocation = { segmentId: seg };
611
- if (kind === "location") {
612
- if (p.index === undefined) {
613
- throw new Error("googleDocs: `index` is required when locationKind=location");
614
- }
615
- sectionBreakLocation.index = p.index;
616
- }
617
- return runBatchUpdate(ctx, documentId, {
618
- createFooter: { type: "DEFAULT", sectionBreakLocation },
619
- });
620
- },
621
- });
622
- rl.registerAction("document.deleteFooter", {
623
- description: "Delete a footer by ID.",
624
- inputSchema: {
625
- document: { type: "string", required: true },
626
- footerId: { type: "string", required: true },
627
- },
628
- async execute(input, ctx) {
629
- const p = (input ?? {});
630
- const documentId = extractDocumentId(p.document);
631
- return runBatchUpdate(ctx, documentId, { deleteFooter: { footerId: p.footerId } });
632
- },
633
- });
634
- rl.registerAction("document.deletePositionedObject", {
635
- description: "Delete a positioned object (inline image, floating image, etc.) by its objectId.",
636
- inputSchema: {
637
- document: { type: "string", required: true },
638
- objectId: { type: "string", required: true },
639
- },
640
- async execute(input, ctx) {
641
- const p = (input ?? {});
642
- const documentId = extractDocumentId(p.document);
643
- return runBatchUpdate(ctx, documentId, {
644
- deletePositionedObject: { objectId: p.objectId },
645
- });
646
- },
647
- });
648
- // ─── Discrete text / paragraph / table formatting ────────────────
649
- //
650
- // These wrap individual batchUpdate sub-requests so the agent gets a
651
- // discoverable surface for the common formatting moves instead of having
652
- // to assemble a full batchUpdate payload.
653
- rl.registerAction("document.updateTextStyle", {
654
- description: "Apply text styling (bold, italic, underline, color, fontSize, fontFamily, link) to a range. Pass `fields` listing which TextStyle properties were set.",
655
- inputSchema: {
656
- document: { type: "string", required: true },
657
- startIndex: { type: "number", required: true },
658
- endIndex: { type: "number", required: true },
659
- bold: { type: "boolean", required: false },
660
- italic: { type: "boolean", required: false },
661
- underline: { type: "boolean", required: false },
662
- strikethrough: { type: "boolean", required: false },
663
- fontSizePt: { type: "number", required: false, description: "Font size in points." },
664
- fontFamily: { type: "string", required: false },
665
- foregroundColorHex: { type: "string", required: false, description: "Hex color, e.g. #1A73E8" },
666
- backgroundColorHex: { type: "string", required: false },
667
- link: { type: "string", required: false, description: "URL for the linked range." },
668
- segmentId: { type: "string", required: false, description: "Header/footer/footnote id; omit for the body." },
669
- },
670
- async execute(input, ctx) {
671
- const p = (input ?? {});
672
- const documentId = extractDocumentId(p.document);
673
- const ts = {};
674
- const fields = [];
675
- if (p.bold !== undefined) {
676
- ts.bold = p.bold;
677
- fields.push("bold");
678
- }
679
- if (p.italic !== undefined) {
680
- ts.italic = p.italic;
681
- fields.push("italic");
682
- }
683
- if (p.underline !== undefined) {
684
- ts.underline = p.underline;
685
- fields.push("underline");
686
- }
687
- if (p.strikethrough !== undefined) {
688
- ts.strikethrough = p.strikethrough;
689
- fields.push("strikethrough");
690
- }
691
- if (p.fontSizePt !== undefined) {
692
- ts.fontSize = { magnitude: p.fontSizePt, unit: "PT" };
693
- fields.push("fontSize");
694
- }
695
- if (p.fontFamily) {
696
- ts.weightedFontFamily = { fontFamily: p.fontFamily };
697
- fields.push("weightedFontFamily");
698
- }
699
- if (p.foregroundColorHex) {
700
- const c = hexToRgbF(p.foregroundColorHex);
701
- ts.foregroundColor = { color: { rgbColor: c } };
702
- fields.push("foregroundColor");
703
- }
704
- if (p.backgroundColorHex) {
705
- const c = hexToRgbF(p.backgroundColorHex);
706
- ts.backgroundColor = { color: { rgbColor: c } };
707
- fields.push("backgroundColor");
708
- }
709
- if (p.link) {
710
- ts.link = { url: p.link };
711
- fields.push("link");
712
- }
713
- if (fields.length === 0) {
714
- throw new Error("googleDocs.document.updateTextStyle: at least one styling property required");
715
- }
716
- return runBatchUpdate(ctx, documentId, [
717
- {
718
- updateTextStyle: {
719
- range: {
720
- startIndex: p.startIndex,
721
- endIndex: p.endIndex,
722
- segmentId: p.segmentId,
723
- },
724
- textStyle: ts,
725
- fields: fields.join(","),
726
- },
727
- },
728
- ]);
729
- },
730
- });
731
- rl.registerAction("document.updateParagraphStyle", {
732
- description: "Apply paragraph styling (alignment, named style, indents, spacing, direction) to the paragraphs intersecting the range.",
733
- inputSchema: {
734
- document: { type: "string", required: true },
735
- startIndex: { type: "number", required: true },
736
- endIndex: { type: "number", required: true },
737
- alignment: { type: "string", required: false, description: "START | CENTER | END | JUSTIFIED" },
738
- namedStyleType: { type: "string", required: false, description: "NORMAL_TEXT | TITLE | SUBTITLE | HEADING_1 .. HEADING_6" },
739
- direction: { type: "string", required: false, description: "LEFT_TO_RIGHT | RIGHT_TO_LEFT" },
740
- indentFirstLinePt: { type: "number", required: false },
741
- indentStartPt: { type: "number", required: false },
742
- indentEndPt: { type: "number", required: false },
743
- spaceAbovePt: { type: "number", required: false },
744
- spaceBelowPt: { type: "number", required: false },
745
- lineSpacing: { type: "number", required: false, description: "Percentage; 100 = single, 150 = 1.5x." },
746
- segmentId: { type: "string", required: false },
747
- },
748
- async execute(input, ctx) {
749
- const p = (input ?? {});
750
- const documentId = extractDocumentId(p.document);
751
- const ps = {};
752
- const fields = [];
753
- const pt = (n) => ({ magnitude: n, unit: "PT" });
754
- if (p.alignment) {
755
- ps.alignment = p.alignment;
756
- fields.push("alignment");
757
- }
758
- if (p.namedStyleType) {
759
- ps.namedStyleType = p.namedStyleType;
760
- fields.push("namedStyleType");
761
- }
762
- if (p.direction) {
763
- ps.direction = p.direction;
764
- fields.push("direction");
765
- }
766
- if (p.indentFirstLinePt !== undefined) {
767
- ps.indentFirstLine = pt(p.indentFirstLinePt);
768
- fields.push("indentFirstLine");
769
- }
770
- if (p.indentStartPt !== undefined) {
771
- ps.indentStart = pt(p.indentStartPt);
772
- fields.push("indentStart");
773
- }
774
- if (p.indentEndPt !== undefined) {
775
- ps.indentEnd = pt(p.indentEndPt);
776
- fields.push("indentEnd");
777
- }
778
- if (p.spaceAbovePt !== undefined) {
779
- ps.spaceAbove = pt(p.spaceAbovePt);
780
- fields.push("spaceAbove");
781
- }
782
- if (p.spaceBelowPt !== undefined) {
783
- ps.spaceBelow = pt(p.spaceBelowPt);
784
- fields.push("spaceBelow");
785
- }
786
- if (p.lineSpacing !== undefined) {
787
- ps.lineSpacing = p.lineSpacing;
788
- fields.push("lineSpacing");
789
- }
790
- if (fields.length === 0) {
791
- throw new Error("googleDocs.document.updateParagraphStyle: at least one property required");
792
- }
793
- return runBatchUpdate(ctx, documentId, [
794
- {
795
- updateParagraphStyle: {
796
- range: { startIndex: p.startIndex, endIndex: p.endIndex, segmentId: p.segmentId },
797
- paragraphStyle: ps,
798
- fields: fields.join(","),
799
- },
800
- },
801
- ]);
802
- },
803
- });
804
- rl.registerAction("document.updateTableCellStyle", {
805
- description: "Apply table-cell styling (background color, borders, padding) to a contiguous span of cells. Pass either a single cell via `tableStartLocation+rowIndex+columnIndex`, or a range via `tableStartLocation+rowSpan+columnSpan`.",
806
- inputSchema: {
807
- document: { type: "string", required: true },
808
- tableStartIndex: { type: "number", required: true, description: "The startIndex of the table element." },
809
- rowIndex: { type: "number", required: true },
810
- columnIndex: { type: "number", required: true },
811
- rowSpan: { type: "number", required: false, default: 1 },
812
- columnSpan: { type: "number", required: false, default: 1 },
813
- backgroundColorHex: { type: "string", required: false },
814
- paddingLeftPt: { type: "number", required: false },
815
- paddingRightPt: { type: "number", required: false },
816
- paddingTopPt: { type: "number", required: false },
817
- paddingBottomPt: { type: "number", required: false },
818
- contentAlignment: { type: "string", required: false, description: "TOP | MIDDLE | BOTTOM" },
819
- },
820
- async execute(input, ctx) {
821
- const p = (input ?? {});
822
- const documentId = extractDocumentId(p.document);
823
- const style = {};
824
- const fields = [];
825
- const pt = (n) => ({ magnitude: n, unit: "PT" });
826
- if (p.backgroundColorHex) {
827
- style.backgroundColor = { color: { rgbColor: hexToRgbF(p.backgroundColorHex) } };
828
- fields.push("backgroundColor");
829
- }
830
- if (p.paddingLeftPt !== undefined) {
831
- style.paddingLeft = pt(p.paddingLeftPt);
832
- fields.push("paddingLeft");
833
- }
834
- if (p.paddingRightPt !== undefined) {
835
- style.paddingRight = pt(p.paddingRightPt);
836
- fields.push("paddingRight");
837
- }
838
- if (p.paddingTopPt !== undefined) {
839
- style.paddingTop = pt(p.paddingTopPt);
840
- fields.push("paddingTop");
841
- }
842
- if (p.paddingBottomPt !== undefined) {
843
- style.paddingBottom = pt(p.paddingBottomPt);
844
- fields.push("paddingBottom");
845
- }
846
- if (p.contentAlignment) {
847
- style.contentAlignment = p.contentAlignment;
848
- fields.push("contentAlignment");
849
- }
850
- if (fields.length === 0) {
851
- throw new Error("googleDocs.document.updateTableCellStyle: at least one style property required");
852
- }
853
- return runBatchUpdate(ctx, documentId, [
854
- {
855
- updateTableCellStyle: {
856
- tableRange: {
857
- tableCellLocation: {
858
- tableStartLocation: { index: p.tableStartIndex },
859
- rowIndex: p.rowIndex,
860
- columnIndex: p.columnIndex,
861
- },
862
- rowSpan: p.rowSpan ?? 1,
863
- columnSpan: p.columnSpan ?? 1,
864
- },
865
- tableCellStyle: style,
866
- fields: fields.join(","),
867
- },
868
- },
869
- ]);
870
- },
871
- });
872
- rl.registerAction("document.mergeTableCells", {
873
- description: "Merge a contiguous block of cells in a table.",
874
- inputSchema: {
875
- document: { type: "string", required: true },
876
- tableStartIndex: { type: "number", required: true },
877
- rowIndex: { type: "number", required: true },
878
- columnIndex: { type: "number", required: true },
879
- rowSpan: { type: "number", required: true },
880
- columnSpan: { type: "number", required: true },
881
- },
882
- async execute(input, ctx) {
883
- const p = (input ?? {});
884
- const documentId = extractDocumentId(p.document);
885
- return runBatchUpdate(ctx, documentId, [
886
- {
887
- mergeTableCells: {
888
- tableRange: {
889
- tableCellLocation: {
890
- tableStartLocation: { index: p.tableStartIndex },
891
- rowIndex: p.rowIndex,
892
- columnIndex: p.columnIndex,
893
- },
894
- rowSpan: p.rowSpan,
895
- columnSpan: p.columnSpan,
896
- },
897
- },
898
- },
899
- ]);
900
- },
901
- });
902
- rl.registerAction("document.unmergeTableCells", {
903
- description: "Unmerge a previously merged block of cells.",
904
- inputSchema: {
905
- document: { type: "string", required: true },
906
- tableStartIndex: { type: "number", required: true },
907
- rowIndex: { type: "number", required: true },
908
- columnIndex: { type: "number", required: true },
909
- rowSpan: { type: "number", required: true },
910
- columnSpan: { type: "number", required: true },
911
- },
912
- async execute(input, ctx) {
913
- const p = (input ?? {});
914
- const documentId = extractDocumentId(p.document);
915
- return runBatchUpdate(ctx, documentId, [
916
- {
917
- unmergeTableCells: {
918
- tableRange: {
919
- tableCellLocation: {
920
- tableStartLocation: { index: p.tableStartIndex },
921
- rowIndex: p.rowIndex,
922
- columnIndex: p.columnIndex,
923
- },
924
- rowSpan: p.rowSpan,
925
- columnSpan: p.columnSpan,
926
- },
927
- },
928
- },
929
- ]);
930
- },
931
- });
932
- rl.registerAction("document.insertInlineImage", {
933
- description: "Insert an inline image at the given location. `uri` must point to a publicly fetchable image.",
934
- inputSchema: {
935
- document: { type: "string", required: true },
936
- index: { type: "number", required: true },
937
- uri: { type: "string", required: true },
938
- widthPt: { type: "number", required: false },
939
- heightPt: { type: "number", required: false },
940
- segmentId: { type: "string", required: false },
941
- },
942
- async execute(input, ctx) {
943
- const p = (input ?? {});
944
- const documentId = extractDocumentId(p.document);
945
- const pt = (n) => ({ magnitude: n, unit: "PT" });
946
- const req = {
947
- location: buildLocation(p.index, p.segmentId),
948
- uri: p.uri,
949
- };
950
- if (p.widthPt !== undefined || p.heightPt !== undefined) {
951
- req.objectSize = {};
952
- if (p.widthPt !== undefined)
953
- req.objectSize.width = pt(p.widthPt);
954
- if (p.heightPt !== undefined)
955
- req.objectSize.height = pt(p.heightPt);
956
- }
957
- return runBatchUpdate(ctx, documentId, [{ insertInlineImage: req }]);
958
- },
959
- });
960
- rl.registerAction("document.replaceImage", {
961
- description: "Replace an existing image (identified by its inline-object id) with a new image from a publicly fetchable URI.",
962
- inputSchema: {
963
- document: { type: "string", required: true },
964
- imageObjectId: { type: "string", required: true },
965
- uri: { type: "string", required: true },
966
- imageReplaceMethod: { type: "string", required: false, description: "CENTER_CROP (default) | (others as Docs API adds them)" },
967
- },
968
- async execute(input, ctx) {
969
- const p = (input ?? {});
970
- const documentId = extractDocumentId(p.document);
971
- return runBatchUpdate(ctx, documentId, [
972
- {
973
- replaceImage: {
974
- imageObjectId: p.imageObjectId,
975
- uri: p.uri,
976
- imageReplaceMethod: p.imageReplaceMethod ?? "CENTER_CROP",
977
- },
978
- },
979
- ]);
980
- },
981
- });
982
- rl.registerAction("document.insertSectionBreak", {
983
- description: "Insert a section break at the given location.",
984
- inputSchema: {
985
- document: { type: "string", required: true },
986
- index: { type: "number", required: true },
987
- sectionType: { type: "string", required: false, description: "CONTINUOUS | NEXT_PAGE. Default CONTINUOUS." },
988
- segmentId: { type: "string", required: false },
989
- },
990
- async execute(input, ctx) {
991
- const p = (input ?? {});
992
- const documentId = extractDocumentId(p.document);
993
- return runBatchUpdate(ctx, documentId, [
994
- {
995
- insertSectionBreak: {
996
- location: buildLocation(p.index, p.segmentId),
997
- sectionType: p.sectionType ?? "CONTINUOUS",
998
- },
999
- },
1000
- ]);
1001
- },
1002
- });
1003
- rl.registerAction("document.updateDocumentStyle", {
1004
- description: "Update document-level style (page size, margins, page numbers, default direction).",
1005
- inputSchema: {
1006
- document: { type: "string", required: true },
1007
- pageMarginTopPt: { type: "number", required: false },
1008
- pageMarginBottomPt: { type: "number", required: false },
1009
- pageMarginLeftPt: { type: "number", required: false },
1010
- pageMarginRightPt: { type: "number", required: false },
1011
- pageSizeWidthPt: { type: "number", required: false },
1012
- pageSizeHeightPt: { type: "number", required: false },
1013
- useCustomHeaderFooterMargins: { type: "boolean", required: false },
1014
- },
1015
- async execute(input, ctx) {
1016
- const p = (input ?? {});
1017
- const documentId = extractDocumentId(p.document);
1018
- const ds = {};
1019
- const fields = [];
1020
- const pt = (n) => ({ magnitude: n, unit: "PT" });
1021
- if (p.pageMarginTopPt !== undefined) {
1022
- ds.marginTop = pt(p.pageMarginTopPt);
1023
- fields.push("marginTop");
1024
- }
1025
- if (p.pageMarginBottomPt !== undefined) {
1026
- ds.marginBottom = pt(p.pageMarginBottomPt);
1027
- fields.push("marginBottom");
1028
- }
1029
- if (p.pageMarginLeftPt !== undefined) {
1030
- ds.marginLeft = pt(p.pageMarginLeftPt);
1031
- fields.push("marginLeft");
1032
- }
1033
- if (p.pageMarginRightPt !== undefined) {
1034
- ds.marginRight = pt(p.pageMarginRightPt);
1035
- fields.push("marginRight");
1036
- }
1037
- if (p.pageSizeWidthPt !== undefined || p.pageSizeHeightPt !== undefined) {
1038
- ds.pageSize = {};
1039
- if (p.pageSizeWidthPt !== undefined)
1040
- ds.pageSize.width = pt(p.pageSizeWidthPt);
1041
- if (p.pageSizeHeightPt !== undefined)
1042
- ds.pageSize.height = pt(p.pageSizeHeightPt);
1043
- fields.push("pageSize");
1044
- }
1045
- if (p.useCustomHeaderFooterMargins !== undefined) {
1046
- ds.useCustomHeaderFooterMargins = p.useCustomHeaderFooterMargins;
1047
- fields.push("useCustomHeaderFooterMargins");
1048
- }
1049
- if (fields.length === 0) {
1050
- throw new Error("googleDocs.document.updateDocumentStyle: pass at least one property");
1051
- }
1052
- return runBatchUpdate(ctx, documentId, [{ updateDocumentStyle: { documentStyle: ds, fields: fields.join(",") } }]);
1053
- },
1054
- });
1055
- // ─── Small helper for Docs additions ────────────────────────────
1056
- // (declared at the bottom so it doesn't conflict with the
1057
- // upstream module-scope `hexToRgb` if one is added later)
88
+ registerDocumentsActions(rl);
89
+ registerTextActions(rl);
90
+ registerTablesActions(rl);
91
+ registerTabActions(rl);
92
+ registerStructureActions(rl);
93
+ registerFormattingActions(rl);
94
+ registerImagesActions(rl);
1058
95
  }