@aaronsb/google-workspace-mcp 2.0.0-alpha.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 (184) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +193 -0
  3. package/build/__helpers__/testSetup.d.ts +1 -0
  4. package/build/__helpers__/testSetup.js +6 -0
  5. package/build/__helpers__/testSetup.js.map +1 -0
  6. package/build/__tests__/accounts/credentials.test.d.ts +1 -0
  7. package/build/__tests__/accounts/credentials.test.js +129 -0
  8. package/build/__tests__/accounts/credentials.test.js.map +1 -0
  9. package/build/__tests__/accounts/registry.test.d.ts +1 -0
  10. package/build/__tests__/accounts/registry.test.js +74 -0
  11. package/build/__tests__/accounts/registry.test.js.map +1 -0
  12. package/build/__tests__/executor/errors.test.d.ts +1 -0
  13. package/build/__tests__/executor/errors.test.js +42 -0
  14. package/build/__tests__/executor/errors.test.js.map +1 -0
  15. package/build/__tests__/executor/gws.test.d.ts +1 -0
  16. package/build/__tests__/executor/gws.test.js +178 -0
  17. package/build/__tests__/executor/gws.test.js.map +1 -0
  18. package/build/__tests__/executor/paths.test.d.ts +1 -0
  19. package/build/__tests__/executor/paths.test.js +60 -0
  20. package/build/__tests__/executor/paths.test.js.map +1 -0
  21. package/build/__tests__/executor/workspace.test.d.ts +1 -0
  22. package/build/__tests__/executor/workspace.test.js +117 -0
  23. package/build/__tests__/executor/workspace.test.js.map +1 -0
  24. package/build/__tests__/factory/generator.test.d.ts +1 -0
  25. package/build/__tests__/factory/generator.test.js +178 -0
  26. package/build/__tests__/factory/generator.test.js.map +1 -0
  27. package/build/__tests__/factory/patch-coverage.test.d.ts +10 -0
  28. package/build/__tests__/factory/patch-coverage.test.js +148 -0
  29. package/build/__tests__/factory/patch-coverage.test.js.map +1 -0
  30. package/build/__tests__/factory/safety.test.d.ts +1 -0
  31. package/build/__tests__/factory/safety.test.js +107 -0
  32. package/build/__tests__/factory/safety.test.js.map +1 -0
  33. package/build/__tests__/integration/executor.test.d.ts +5 -0
  34. package/build/__tests__/integration/executor.test.js +46 -0
  35. package/build/__tests__/integration/executor.test.js.map +1 -0
  36. package/build/__tests__/integration/handlers.test.d.ts +6 -0
  37. package/build/__tests__/integration/handlers.test.js +95 -0
  38. package/build/__tests__/integration/handlers.test.js.map +1 -0
  39. package/build/__tests__/integration/setup.d.ts +19 -0
  40. package/build/__tests__/integration/setup.js +61 -0
  41. package/build/__tests__/integration/setup.js.map +1 -0
  42. package/build/__tests__/server/formatting/markdown.test.d.ts +1 -0
  43. package/build/__tests__/server/formatting/markdown.test.js +149 -0
  44. package/build/__tests__/server/formatting/markdown.test.js.map +1 -0
  45. package/build/__tests__/server/formatting/next-steps.test.d.ts +1 -0
  46. package/build/__tests__/server/formatting/next-steps.test.js +42 -0
  47. package/build/__tests__/server/formatting/next-steps.test.js.map +1 -0
  48. package/build/__tests__/server/handler.test.d.ts +1 -0
  49. package/build/__tests__/server/handler.test.js +97 -0
  50. package/build/__tests__/server/handler.test.js.map +1 -0
  51. package/build/__tests__/server/handlers/__mocks__/executor.d.ts +147 -0
  52. package/build/__tests__/server/handlers/__mocks__/executor.js +114 -0
  53. package/build/__tests__/server/handlers/__mocks__/executor.js.map +1 -0
  54. package/build/__tests__/server/handlers/accounts.test.d.ts +1 -0
  55. package/build/__tests__/server/handlers/accounts.test.js +127 -0
  56. package/build/__tests__/server/handlers/accounts.test.js.map +1 -0
  57. package/build/__tests__/server/handlers/calendar.test.d.ts +1 -0
  58. package/build/__tests__/server/handlers/calendar.test.js +95 -0
  59. package/build/__tests__/server/handlers/calendar.test.js.map +1 -0
  60. package/build/__tests__/server/handlers/drive.test.d.ts +1 -0
  61. package/build/__tests__/server/handlers/drive.test.js +81 -0
  62. package/build/__tests__/server/handlers/drive.test.js.map +1 -0
  63. package/build/__tests__/server/handlers/email.test.d.ts +1 -0
  64. package/build/__tests__/server/handlers/email.test.js +99 -0
  65. package/build/__tests__/server/handlers/email.test.js.map +1 -0
  66. package/build/__tests__/server/handlers/validate.test.d.ts +1 -0
  67. package/build/__tests__/server/handlers/validate.test.js +88 -0
  68. package/build/__tests__/server/handlers/validate.test.js.map +1 -0
  69. package/build/__tests__/server/queue.test.d.ts +1 -0
  70. package/build/__tests__/server/queue.test.js +194 -0
  71. package/build/__tests__/server/queue.test.js.map +1 -0
  72. package/build/__tests__/server/server.test.d.ts +7 -0
  73. package/build/__tests__/server/server.test.js +135 -0
  74. package/build/__tests__/server/server.test.js.map +1 -0
  75. package/build/__tests__/server/tools.test.d.ts +1 -0
  76. package/build/__tests__/server/tools.test.js +91 -0
  77. package/build/__tests__/server/tools.test.js.map +1 -0
  78. package/build/accounts/auth.d.ts +24 -0
  79. package/build/accounts/auth.js +118 -0
  80. package/build/accounts/auth.js.map +1 -0
  81. package/build/accounts/credentials.d.ts +11 -0
  82. package/build/accounts/credentials.js +52 -0
  83. package/build/accounts/credentials.js.map +1 -0
  84. package/build/accounts/index.d.ts +6 -0
  85. package/build/accounts/index.js +4 -0
  86. package/build/accounts/index.js.map +1 -0
  87. package/build/accounts/registry.d.ts +11 -0
  88. package/build/accounts/registry.js +62 -0
  89. package/build/accounts/registry.js.map +1 -0
  90. package/build/executor/errors.d.ts +15 -0
  91. package/build/executor/errors.js +37 -0
  92. package/build/executor/errors.js.map +1 -0
  93. package/build/executor/file-output.d.ts +23 -0
  94. package/build/executor/file-output.js +67 -0
  95. package/build/executor/file-output.js.map +1 -0
  96. package/build/executor/gws.d.ts +23 -0
  97. package/build/executor/gws.js +144 -0
  98. package/build/executor/gws.js.map +1 -0
  99. package/build/executor/index.d.ts +4 -0
  100. package/build/executor/index.js +4 -0
  101. package/build/executor/index.js.map +1 -0
  102. package/build/executor/paths.d.ts +6 -0
  103. package/build/executor/paths.js +25 -0
  104. package/build/executor/paths.js.map +1 -0
  105. package/build/executor/workspace.d.ts +38 -0
  106. package/build/executor/workspace.js +146 -0
  107. package/build/executor/workspace.js.map +1 -0
  108. package/build/factory/defaults.d.ts +8 -0
  109. package/build/factory/defaults.js +101 -0
  110. package/build/factory/defaults.js.map +1 -0
  111. package/build/factory/generator.d.ts +23 -0
  112. package/build/factory/generator.js +253 -0
  113. package/build/factory/generator.js.map +1 -0
  114. package/build/factory/manifest.yaml +852 -0
  115. package/build/factory/patches.d.ts +6 -0
  116. package/build/factory/patches.js +13 -0
  117. package/build/factory/patches.js.map +1 -0
  118. package/build/factory/registry.d.ts +12 -0
  119. package/build/factory/registry.js +18 -0
  120. package/build/factory/registry.js.map +1 -0
  121. package/build/factory/safety.d.ts +68 -0
  122. package/build/factory/safety.js +157 -0
  123. package/build/factory/safety.js.map +1 -0
  124. package/build/factory/types.d.ts +102 -0
  125. package/build/factory/types.js +6 -0
  126. package/build/factory/types.js.map +1 -0
  127. package/build/factory/yaml.d.ts +2 -0
  128. package/build/factory/yaml.js +3 -0
  129. package/build/factory/yaml.js.map +1 -0
  130. package/build/index.d.ts +2 -0
  131. package/build/index.js +14 -0
  132. package/build/index.js.map +1 -0
  133. package/build/server/formatting/markdown.d.ts +21 -0
  134. package/build/server/formatting/markdown.js +324 -0
  135. package/build/server/formatting/markdown.js.map +1 -0
  136. package/build/server/formatting/next-steps.d.ts +9 -0
  137. package/build/server/formatting/next-steps.js +123 -0
  138. package/build/server/formatting/next-steps.js.map +1 -0
  139. package/build/server/handler.d.ts +3 -0
  140. package/build/server/handler.js +24 -0
  141. package/build/server/handler.js.map +1 -0
  142. package/build/server/handlers/accounts.d.ts +2 -0
  143. package/build/server/handlers/accounts.js +181 -0
  144. package/build/server/handlers/accounts.js.map +1 -0
  145. package/build/server/handlers/calendar.d.ts +2 -0
  146. package/build/server/handlers/calendar.js +93 -0
  147. package/build/server/handlers/calendar.js.map +1 -0
  148. package/build/server/handlers/drive.d.ts +2 -0
  149. package/build/server/handlers/drive.js +74 -0
  150. package/build/server/handlers/drive.js.map +1 -0
  151. package/build/server/handlers/email.d.ts +2 -0
  152. package/build/server/handlers/email.js +115 -0
  153. package/build/server/handlers/email.js.map +1 -0
  154. package/build/server/handlers/validate.d.ts +3 -0
  155. package/build/server/handlers/validate.js +22 -0
  156. package/build/server/handlers/validate.js.map +1 -0
  157. package/build/server/handlers/workspace.d.ts +9 -0
  158. package/build/server/handlers/workspace.js +110 -0
  159. package/build/server/handlers/workspace.js.map +1 -0
  160. package/build/server/index.d.ts +4 -0
  161. package/build/server/index.js +4 -0
  162. package/build/server/index.js.map +1 -0
  163. package/build/server/queue.d.ts +11 -0
  164. package/build/server/queue.js +141 -0
  165. package/build/server/queue.js.map +1 -0
  166. package/build/server/server.d.ts +3 -0
  167. package/build/server/server.js +214 -0
  168. package/build/server/server.js.map +1 -0
  169. package/build/server/tools.d.ts +13 -0
  170. package/build/server/tools.js +99 -0
  171. package/build/server/tools.js.map +1 -0
  172. package/build/services/calendar/patch.d.ts +11 -0
  173. package/build/services/calendar/patch.js +116 -0
  174. package/build/services/calendar/patch.js.map +1 -0
  175. package/build/services/drive/patch.d.ts +10 -0
  176. package/build/services/drive/patch.js +131 -0
  177. package/build/services/drive/patch.js.map +1 -0
  178. package/build/services/gmail/attachments.d.ts +19 -0
  179. package/build/services/gmail/attachments.js +90 -0
  180. package/build/services/gmail/attachments.js.map +1 -0
  181. package/build/services/gmail/patch.d.ts +10 -0
  182. package/build/services/gmail/patch.js +226 -0
  183. package/build/services/gmail/patch.js.map +1 -0
  184. package/package.json +34 -0
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Calendar patch — domain-specific hooks for the calendar service.
3
+ *
4
+ * Key customizations:
5
+ * - List: default timeMin to today start
6
+ * - Agenda: raw text passthrough with event refs extraction
7
+ * - Create: custom response formatting with event details
8
+ * - Delete: custom confirmation message
9
+ */
10
+ import type { ServicePatch } from '../../factory/types.js';
11
+ export declare const calendarPatch: ServicePatch;
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Calendar patch — domain-specific hooks for the calendar service.
3
+ *
4
+ * Key customizations:
5
+ * - List: default timeMin to today start
6
+ * - Agenda: raw text passthrough with event refs extraction
7
+ * - Create: custom response formatting with event details
8
+ * - Delete: custom confirmation message
9
+ */
10
+ import { execute } from '../../executor/gws.js';
11
+ import { formatEventList, formatEventDetail } from '../../server/formatting/markdown.js';
12
+ import { nextSteps } from '../../server/formatting/next-steps.js';
13
+ import { requireString } from '../../server/handlers/validate.js';
14
+ /** Format calendar list — name, access role, primary flag. */
15
+ function formatCalendarList(data) {
16
+ const raw = data;
17
+ const items = (raw?.items ?? []);
18
+ if (items.length === 0) {
19
+ return { text: 'No calendars found.', refs: { count: 0 } };
20
+ }
21
+ const lines = items.map(cal => {
22
+ const id = String(cal.id ?? '');
23
+ const summary = String(cal.summary ?? '(unnamed)');
24
+ const role = String(cal.accessRole ?? '');
25
+ const primary = cal.primary ? ' ★' : '';
26
+ return `${summary}${primary} | ${role} | ${id}`;
27
+ });
28
+ return {
29
+ text: `## Calendars (${items.length})\n\n${lines.join('\n')}`,
30
+ refs: {
31
+ count: items.length,
32
+ calendarId: String(items[0]?.id ?? ''),
33
+ calendars: items.map(c => ({ id: c.id, summary: c.summary })),
34
+ },
35
+ };
36
+ }
37
+ export const calendarPatch = {
38
+ beforeExecute: {
39
+ list: async (args, ctx) => {
40
+ // Inject default timeMin (today start) if not provided
41
+ if (!ctx.params.timeMin) {
42
+ const now = new Date();
43
+ const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate()).toISOString();
44
+ // Patch the --params JSON to include timeMin
45
+ const paramsIdx = args.indexOf('--params');
46
+ if (paramsIdx !== -1) {
47
+ const gwsParams = JSON.parse(args[paramsIdx + 1]);
48
+ if (!gwsParams.timeMin) {
49
+ gwsParams.timeMin = todayStart;
50
+ }
51
+ args[paramsIdx + 1] = JSON.stringify(gwsParams);
52
+ }
53
+ }
54
+ return args;
55
+ },
56
+ },
57
+ formatList: (data, ctx) => {
58
+ switch (ctx.operation) {
59
+ case 'calendars':
60
+ return formatCalendarList(data);
61
+ default:
62
+ return formatEventList(data);
63
+ }
64
+ },
65
+ formatDetail: (data) => formatEventDetail(data),
66
+ customHandlers: {
67
+ agenda: async (params, account) => {
68
+ const result = await execute(['calendar', '+agenda'], { account });
69
+ const data = result.data;
70
+ const text = typeof result.data === 'string' ? result.data : JSON.stringify(result.data, null, 2);
71
+ const events = Array.isArray(data?.events) ? data.events : [];
72
+ return {
73
+ text: text + nextSteps('calendar', 'agenda', { email: account }),
74
+ refs: {
75
+ count: events.length,
76
+ eventId: events[0]?.id,
77
+ events: events.map((e) => e.id),
78
+ },
79
+ };
80
+ },
81
+ create: async (params, account) => {
82
+ const summary = requireString(params, 'summary');
83
+ const start = requireString(params, 'start');
84
+ const end = requireString(params, 'end');
85
+ const args = ['calendar', '+insert', '--summary', summary, '--start', start, '--end', end];
86
+ if (params.description)
87
+ args.push('--description', String(params.description));
88
+ if (params.location)
89
+ args.push('--location', String(params.location));
90
+ if (params.attendees)
91
+ args.push('--attendees', String(params.attendees));
92
+ const result = await execute(args, { account });
93
+ const data = result.data;
94
+ return {
95
+ text: `Event created: **${summary}**\n\n` +
96
+ `**When:** ${start} – ${end}\n` +
97
+ (params.location ? `**Where:** ${params.location}\n` : '') +
98
+ `**Event ID:** ${data.id ?? 'unknown'}` +
99
+ nextSteps('calendar', 'create', { email: account }),
100
+ refs: { id: data.id, eventId: data.id, summary, start, end },
101
+ };
102
+ },
103
+ delete: async (params, account) => {
104
+ const eventId = requireString(params, 'eventId');
105
+ await execute([
106
+ 'calendar', 'events', 'delete',
107
+ '--params', JSON.stringify({ calendarId: 'primary', eventId }),
108
+ ], { account });
109
+ return {
110
+ text: `Event deleted: ${eventId}` + nextSteps('calendar', 'delete', { email: account }),
111
+ refs: { eventId, status: 'deleted' },
112
+ };
113
+ },
114
+ },
115
+ };
116
+ //# sourceMappingURL=patch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patch.js","sourceRoot":"","sources":["../../../src/services/calendar/patch.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Drive patch — domain-specific hooks for the drive service.
3
+ *
4
+ * Key customizations:
5
+ * - Custom formatters for file lists and details
6
+ * - Upload: custom handler with positional file path arg
7
+ * - Download/Export: save to workspace via gws --output, return inline for text
8
+ */
9
+ import type { ServicePatch } from '../../factory/types.js';
10
+ export declare const drivePatch: ServicePatch;
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Drive patch — domain-specific hooks for the drive service.
3
+ *
4
+ * Key customizations:
5
+ * - Custom formatters for file lists and details
6
+ * - Upload: custom handler with positional file path arg
7
+ * - Download/Export: save to workspace via gws --output, return inline for text
8
+ */
9
+ import * as fs from 'node:fs/promises';
10
+ import { execute } from '../../executor/gws.js';
11
+ import { formatFileList, formatFileDetail } from '../../server/formatting/markdown.js';
12
+ import { nextSteps } from '../../server/formatting/next-steps.js';
13
+ import { requireString } from '../../server/handlers/validate.js';
14
+ import { ensureWorkspaceDir, resolveWorkspacePath, verifyPathSafety } from '../../executor/workspace.js';
15
+ import { isTextFile, formatFileOutput } from '../../executor/file-output.js';
16
+ /** Read a file from workspace and build the output result with optional inline content. */
17
+ async function readWorkspaceFile(filePath, filename, mimeType) {
18
+ const stat = await fs.stat(filePath);
19
+ const result = {
20
+ filename,
21
+ path: filePath,
22
+ size: stat.size,
23
+ };
24
+ if (isTextFile(filename, mimeType) && stat.size < 100_000) {
25
+ result.content = await fs.readFile(filePath, 'utf-8');
26
+ }
27
+ return result;
28
+ }
29
+ export const drivePatch = {
30
+ formatList: (data) => formatFileList(data),
31
+ formatDetail: (data) => formatFileDetail(data),
32
+ customHandlers: {
33
+ upload: async (params, account) => {
34
+ const filePath = requireString(params, 'filePath');
35
+ const args = ['drive', '+upload', filePath];
36
+ if (params.name)
37
+ args.push('--name', String(params.name));
38
+ if (params.parentFolderId)
39
+ args.push('--parent', String(params.parentFolderId));
40
+ const result = await execute(args, { account });
41
+ const data = result.data;
42
+ return {
43
+ text: `File uploaded: **${data.name ?? filePath}**\n\n**File ID:** ${data.id ?? 'unknown'}` +
44
+ nextSteps('drive', 'upload', { email: account }),
45
+ refs: { id: data.id, fileId: data.id, name: data.name },
46
+ };
47
+ },
48
+ download: async (params, account) => {
49
+ const fileId = requireString(params, 'fileId');
50
+ // Get file metadata for filename and mime type
51
+ const metaResult = await execute([
52
+ 'drive', 'files', 'get',
53
+ '--params', JSON.stringify({ fileId, fields: 'name,mimeType' }),
54
+ ], { account });
55
+ const meta = metaResult.data;
56
+ const filename = String(params.outputPath || meta.name || `file-${fileId}`);
57
+ const mimeType = String(meta.mimeType || '');
58
+ // Ensure workspace and resolve output path
59
+ const wsStatus = await ensureWorkspaceDir();
60
+ if (!wsStatus.valid)
61
+ throw new Error(`Workspace invalid: ${wsStatus.warning}`);
62
+ const outputPath = resolveWorkspacePath(filename);
63
+ await verifyPathSafety(outputPath);
64
+ // Download directly to disk via --output (preserves binary integrity)
65
+ await execute([
66
+ 'drive', 'files', 'get',
67
+ '--params', JSON.stringify({ fileId, alt: 'media' }),
68
+ '--output', outputPath,
69
+ ], { account });
70
+ const output = await readWorkspaceFile(outputPath, filename, mimeType);
71
+ return {
72
+ text: formatFileOutput(output) + nextSteps('drive', 'download', { email: account }),
73
+ refs: {
74
+ fileId,
75
+ filename: output.filename,
76
+ path: output.path,
77
+ size: output.size,
78
+ ...(output.content ? { content: output.content } : {}),
79
+ },
80
+ };
81
+ },
82
+ export: async (params, account) => {
83
+ const fileId = requireString(params, 'fileId');
84
+ const mimeType = requireString(params, 'mimeType');
85
+ // Map MIME type to file extension
86
+ const extMap = {
87
+ 'application/pdf': '.pdf',
88
+ 'text/csv': '.csv',
89
+ 'text/plain': '.txt',
90
+ 'text/html': '.html',
91
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': '.docx',
92
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': '.xlsx',
93
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation': '.pptx',
94
+ };
95
+ const ext = extMap[mimeType] || '';
96
+ // Get source file name
97
+ const metaResult = await execute([
98
+ 'drive', 'files', 'get',
99
+ '--params', JSON.stringify({ fileId, fields: 'name' }),
100
+ ], { account });
101
+ const meta = metaResult.data;
102
+ const baseName = String(meta.name || `export-${fileId}`).replace(/\.[^.]+$/, '');
103
+ const filename = String(params.outputPath || `${baseName}${ext}`);
104
+ // Ensure workspace and resolve output path
105
+ const wsStatus = await ensureWorkspaceDir();
106
+ if (!wsStatus.valid)
107
+ throw new Error(`Workspace invalid: ${wsStatus.warning}`);
108
+ const outputPath = resolveWorkspacePath(filename);
109
+ await verifyPathSafety(outputPath);
110
+ // Export directly to disk via --output (preserves binary integrity)
111
+ await execute([
112
+ 'drive', 'files', 'export',
113
+ '--params', JSON.stringify({ fileId, mimeType }),
114
+ '--output', outputPath,
115
+ ], { account });
116
+ const output = await readWorkspaceFile(outputPath, filename, mimeType);
117
+ return {
118
+ text: formatFileOutput(output) + nextSteps('drive', 'export', { email: account }),
119
+ refs: {
120
+ fileId,
121
+ filename: output.filename,
122
+ path: output.path,
123
+ size: output.size,
124
+ mimeType,
125
+ ...(output.content ? { content: output.content } : {}),
126
+ },
127
+ };
128
+ },
129
+ },
130
+ };
131
+ //# sourceMappingURL=patch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patch.js","sourceRoot":"","sources":["../../../src/services/drive/patch.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Gmail attachment handler — downloads email attachments to the workspace directory.
3
+ *
4
+ * The agent discovers attachments via the `read` operation, which lists
5
+ * filenames and sizes. Then calls `getAttachment` with just the messageId
6
+ * and filename — we resolve the attachment ID internally by re-reading
7
+ * the message payload. This keeps long Gmail attachment IDs out of the
8
+ * agent's context.
9
+ *
10
+ * Flow: read → see filenames → getAttachment(messageId, filename) → file in workspace
11
+ */
12
+ import type { HandlerResponse } from '../../server/formatting/markdown.js';
13
+ /**
14
+ * Download an email attachment by filename.
15
+ *
16
+ * Resolves the attachment ID internally by reading the message payload —
17
+ * the agent only needs to provide messageId and filename (from the read response).
18
+ */
19
+ export declare function handleGetAttachment(params: Record<string, unknown>, account: string): Promise<HandlerResponse>;
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Gmail attachment handler — downloads email attachments to the workspace directory.
3
+ *
4
+ * The agent discovers attachments via the `read` operation, which lists
5
+ * filenames and sizes. Then calls `getAttachment` with just the messageId
6
+ * and filename — we resolve the attachment ID internally by re-reading
7
+ * the message payload. This keeps long Gmail attachment IDs out of the
8
+ * agent's context.
9
+ *
10
+ * Flow: read → see filenames → getAttachment(messageId, filename) → file in workspace
11
+ */
12
+ import { execute } from '../../executor/gws.js';
13
+ import { requireString } from '../../server/handlers/validate.js';
14
+ import { saveToWorkspace, formatFileOutput } from '../../executor/file-output.js';
15
+ /** Walk message parts recursively to find attachments. */
16
+ function findAttachments(parts) {
17
+ const attachments = [];
18
+ for (const part of parts) {
19
+ const p = part;
20
+ const filename = p.filename;
21
+ const body = p.body;
22
+ const attachmentId = body?.attachmentId;
23
+ if (filename && attachmentId) {
24
+ attachments.push({
25
+ filename,
26
+ attachmentId,
27
+ mimeType: String(p.mimeType ?? ''),
28
+ size: Number(body?.size ?? 0),
29
+ });
30
+ }
31
+ if (Array.isArray(p.parts)) {
32
+ attachments.push(...findAttachments(p.parts));
33
+ }
34
+ }
35
+ return attachments;
36
+ }
37
+ /**
38
+ * Download an email attachment by filename.
39
+ *
40
+ * Resolves the attachment ID internally by reading the message payload —
41
+ * the agent only needs to provide messageId and filename (from the read response).
42
+ */
43
+ export async function handleGetAttachment(params, account) {
44
+ const messageId = requireString(params, 'messageId');
45
+ const filename = requireString(params, 'filename');
46
+ // Read the message to find the attachment ID for this filename
47
+ const msgResult = await execute([
48
+ 'gmail', 'users', 'messages', 'get',
49
+ '--params', JSON.stringify({ userId: 'me', id: messageId }),
50
+ ], { account });
51
+ const msg = msgResult.data;
52
+ const payload = msg.payload;
53
+ const allAttachments = payload?.parts ? findAttachments(payload.parts) : [];
54
+ const match = allAttachments.find(a => a.filename === filename);
55
+ if (!match) {
56
+ const available = allAttachments.map(a => a.filename).join(', ') || '(none)';
57
+ throw new Error(`Attachment '${filename}' not found in message ${messageId}. ` +
58
+ `Available attachments: ${available}`);
59
+ }
60
+ // Fetch the attachment data
61
+ const result = await execute([
62
+ 'gmail', 'users', 'messages', 'attachments', 'get',
63
+ '--params', JSON.stringify({
64
+ userId: 'me',
65
+ messageId,
66
+ id: match.attachmentId,
67
+ }),
68
+ ], { account });
69
+ const data = result.data;
70
+ const base64Data = String(data.data ?? '');
71
+ if (!base64Data) {
72
+ throw new Error('Attachment data is empty');
73
+ }
74
+ // Decode base64url to buffer
75
+ const base64Standard = base64Data.replace(/-/g, '+').replace(/_/g, '/');
76
+ const buffer = Buffer.from(base64Standard, 'base64');
77
+ // Save to workspace and return inline content for text files
78
+ const output = await saveToWorkspace(filename, buffer, match.mimeType);
79
+ return {
80
+ text: formatFileOutput(output),
81
+ refs: {
82
+ filename: output.filename,
83
+ path: output.path,
84
+ size: output.size,
85
+ messageId,
86
+ ...(output.content ? { content: output.content } : {}),
87
+ },
88
+ };
89
+ }
90
+ //# sourceMappingURL=attachments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attachments.js","sourceRoot":"","sources":["../../../src/services/gmail/attachments.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Gmail patch — domain-specific hooks for the email service.
3
+ *
4
+ * Key customizations:
5
+ * - Search hydration: messages.list only returns IDs, so we fetch metadata
6
+ * - Custom formatters: pipe-delimited list, header-extracted detail
7
+ * - Custom handlers: send/reply use specific response formatting
8
+ */
9
+ import type { ServicePatch } from '../../factory/types.js';
10
+ export declare const gmailPatch: ServicePatch;
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Gmail patch — domain-specific hooks for the email service.
3
+ *
4
+ * Key customizations:
5
+ * - Search hydration: messages.list only returns IDs, so we fetch metadata
6
+ * - Custom formatters: pipe-delimited list, header-extracted detail
7
+ * - Custom handlers: send/reply use specific response formatting
8
+ */
9
+ import { execute } from '../../executor/gws.js';
10
+ import { formatEmailList, formatEmailDetail } from '../../server/formatting/markdown.js';
11
+ import { nextSteps } from '../../server/formatting/next-steps.js';
12
+ import { requireString } from '../../server/handlers/validate.js';
13
+ import { handleGetAttachment } from './attachments.js';
14
+ /**
15
+ * Hydrate message IDs with metadata (From, Subject, Date, snippet).
16
+ * Reused from the original email handler.
17
+ */
18
+ async function hydrateMessages(messageIds, account) {
19
+ return Promise.all(messageIds.map(async (msg) => {
20
+ try {
21
+ const result = await execute([
22
+ 'gmail', 'users', 'messages', 'get',
23
+ '--params', JSON.stringify({
24
+ userId: 'me',
25
+ id: msg.id,
26
+ format: 'metadata',
27
+ metadataHeaders: ['From', 'Subject', 'Date'],
28
+ }),
29
+ ], { account });
30
+ const data = result.data;
31
+ const headers = (data.payload?.headers ?? []);
32
+ const getHeader = (name) => headers.find(h => h.name.toLowerCase() === name.toLowerCase())?.value;
33
+ return {
34
+ id: data.id,
35
+ threadId: data.threadId,
36
+ from: getHeader('from'),
37
+ subject: getHeader('subject'),
38
+ date: getHeader('date'),
39
+ snippet: data.snippet,
40
+ };
41
+ }
42
+ catch {
43
+ return { id: msg.id };
44
+ }
45
+ }));
46
+ }
47
+ /** Format labels list — name, type, unread count. */
48
+ function formatLabelList(data) {
49
+ const raw = data;
50
+ const labels = (raw?.labels ?? []);
51
+ if (labels.length === 0) {
52
+ return { text: 'No labels found.', refs: { count: 0 } };
53
+ }
54
+ // Separate system and user labels
55
+ const system = labels.filter(l => l.type === 'system');
56
+ const user = labels.filter(l => l.type === 'user');
57
+ const formatLabel = (l) => {
58
+ const id = String(l.id ?? '');
59
+ const name = String(l.name ?? '');
60
+ const unread = l.messagesUnread ? ` (${l.messagesUnread} unread)` : '';
61
+ return `${id} | ${name}${unread}`;
62
+ };
63
+ const parts = [];
64
+ if (user.length > 0) {
65
+ parts.push(`## User Labels (${user.length})\n`);
66
+ parts.push(...user.map(formatLabel));
67
+ }
68
+ if (system.length > 0) {
69
+ parts.push('', `## System Labels (${system.length})\n`);
70
+ parts.push(...system.map(formatLabel));
71
+ }
72
+ return {
73
+ text: parts.join('\n'),
74
+ refs: {
75
+ count: labels.length,
76
+ labels: labels.map(l => ({ id: l.id, name: l.name })),
77
+ },
78
+ };
79
+ }
80
+ /** Format threads list — thread ID, snippet, message count. */
81
+ function formatThreadList(data) {
82
+ const raw = data;
83
+ const threads = (raw?.threads ?? []);
84
+ if (threads.length === 0) {
85
+ return { text: 'No threads found.', refs: { count: 0 } };
86
+ }
87
+ const lines = threads.map(t => {
88
+ const id = String(t.id ?? '');
89
+ const snippet = String(t.snippet ?? '').slice(0, 80);
90
+ return `${id} | ${snippet}`;
91
+ });
92
+ return {
93
+ text: `## Threads (${threads.length})\n\n${lines.join('\n')}`,
94
+ refs: {
95
+ count: threads.length,
96
+ threadId: String(threads[0]?.id ?? ''),
97
+ threads: threads.map(t => String(t.id ?? '')),
98
+ },
99
+ };
100
+ }
101
+ /** Format thread detail — all messages in the thread. */
102
+ function formatThreadDetail(data) {
103
+ const raw = data;
104
+ const messages = (raw?.messages ?? []);
105
+ const threadId = String(raw?.id ?? '');
106
+ if (messages.length === 0) {
107
+ return { text: 'Empty thread.', refs: { threadId } };
108
+ }
109
+ const parts = [`## Thread (${messages.length} messages)\n`];
110
+ for (const msg of messages) {
111
+ const payload = msg.payload;
112
+ const headers = (payload?.headers ?? []);
113
+ const getHeader = (name) => headers.find(h => h.name.toLowerCase() === name.toLowerCase())?.value ?? '';
114
+ const from = getHeader('from');
115
+ const date = getHeader('date');
116
+ const subject = getHeader('subject');
117
+ const snippet = String(msg.snippet ?? '');
118
+ parts.push(`**${from}** — ${date}`);
119
+ if (subject)
120
+ parts.push(`Subject: ${subject}`);
121
+ parts.push(snippet, '');
122
+ }
123
+ return {
124
+ text: parts.join('\n'),
125
+ refs: {
126
+ threadId,
127
+ messageCount: messages.length,
128
+ messageId: String(messages[messages.length - 1]?.id ?? ''),
129
+ messages: messages.map(m => String(m.id ?? '')),
130
+ },
131
+ };
132
+ }
133
+ export const gmailPatch = {
134
+ afterExecute: {
135
+ search: async (result, ctx) => {
136
+ // messages.list returns bare IDs — hydrate with metadata
137
+ const raw = result;
138
+ const ids = (raw?.messages ?? []);
139
+ if (ids.length === 0)
140
+ return { messages: [] };
141
+ const messages = await hydrateMessages(ids, ctx.account);
142
+ return { messages };
143
+ },
144
+ },
145
+ formatList: (data, ctx) => {
146
+ switch (ctx.operation) {
147
+ case 'labels':
148
+ return formatLabelList(data);
149
+ case 'threads':
150
+ return formatThreadList(data);
151
+ default:
152
+ return formatEmailList(data);
153
+ }
154
+ },
155
+ formatDetail: (data, ctx) => {
156
+ switch (ctx.operation) {
157
+ case 'getThread':
158
+ return formatThreadDetail(data);
159
+ default:
160
+ return formatEmailDetail(data);
161
+ }
162
+ },
163
+ customHandlers: {
164
+ send: async (params, account) => {
165
+ const to = requireString(params, 'to');
166
+ const subject = requireString(params, 'subject');
167
+ const body = requireString(params, 'body');
168
+ const args = ['gmail', '+send', '--to', to, '--subject', subject, '--body', body];
169
+ if (params.cc)
170
+ args.push('--cc', String(params.cc));
171
+ if (params.bcc)
172
+ args.push('--bcc', String(params.bcc));
173
+ const result = await execute(args, { account });
174
+ const data = result.data;
175
+ return {
176
+ text: `Email sent to ${to}.\n\n**Subject:** ${subject}\n**Message ID:** ${data.id ?? 'unknown'}` +
177
+ nextSteps('email', 'send', { email: account }),
178
+ refs: { id: data.id, threadId: data.threadId, to, subject },
179
+ };
180
+ },
181
+ modify: async (params, account) => {
182
+ const messageId = requireString(params, 'messageId');
183
+ const addLabelIds = params.addLabelIds
184
+ ? String(params.addLabelIds).split(',').map(s => s.trim())
185
+ : [];
186
+ const removeLabelIds = params.removeLabelIds
187
+ ? String(params.removeLabelIds).split(',').map(s => s.trim())
188
+ : [];
189
+ if (addLabelIds.length === 0 && removeLabelIds.length === 0) {
190
+ throw new Error('At least one of addLabelIds or removeLabelIds is required');
191
+ }
192
+ const body = {};
193
+ if (addLabelIds.length > 0)
194
+ body.addLabelIds = addLabelIds;
195
+ if (removeLabelIds.length > 0)
196
+ body.removeLabelIds = removeLabelIds;
197
+ const result = await execute([
198
+ 'gmail', 'users', 'messages', 'modify',
199
+ '--params', JSON.stringify({ userId: 'me', id: messageId }),
200
+ '--json', JSON.stringify(body),
201
+ ], { account });
202
+ const data = result.data;
203
+ const labels = (data.labelIds ?? []);
204
+ return {
205
+ text: `Labels updated on ${messageId}.\n\n**Current labels:** ${labels.join(', ') || '(none)'}` +
206
+ nextSteps('email', 'modify', { email: account }),
207
+ refs: { messageId, labelIds: labels },
208
+ };
209
+ },
210
+ getAttachment: handleGetAttachment,
211
+ reply: async (params, account) => {
212
+ const messageId = requireString(params, 'messageId');
213
+ const body = requireString(params, 'body');
214
+ const result = await execute([
215
+ 'gmail', '+reply', '--message-id', messageId, '--body', body,
216
+ ], { account });
217
+ const data = result.data;
218
+ return {
219
+ text: `Reply sent.\n\n**Message ID:** ${data.id ?? 'unknown'}` +
220
+ nextSteps('email', 'reply', { email: account }),
221
+ refs: { id: data.id, threadId: data.threadId, messageId },
222
+ };
223
+ },
224
+ },
225
+ };
226
+ //# sourceMappingURL=patch.js.map