crewly 1.16.0 → 1.17.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 (130) hide show
  1. package/README.md +33 -1
  2. package/config/roles/orchestrator/prompt.md +20 -5
  3. package/config/skills/agent/core/calendar-create/SKILL.md +62 -0
  4. package/config/skills/agent/core/calendar-create/execute.sh +91 -0
  5. package/config/skills/agent/core/calendar-create/execute.test.sh +80 -0
  6. package/config/skills/agent/core/calendar-list/SKILL.md +58 -0
  7. package/config/skills/agent/core/calendar-list/execute.sh +88 -0
  8. package/config/skills/agent/core/calendar-list/execute.test.sh +71 -0
  9. package/config/skills/agent/core/gmail-read/SKILL.md +61 -0
  10. package/config/skills/agent/core/gmail-read/execute.sh +62 -0
  11. package/config/skills/agent/core/gmail-read/execute.test.sh +68 -0
  12. package/config/skills/agent/core/gmail-search/SKILL.md +64 -0
  13. package/config/skills/agent/core/gmail-search/execute.sh +70 -0
  14. package/config/skills/agent/core/gmail-search/execute.test.sh +67 -0
  15. package/config/skills/agent/core/gmail-send/SKILL.md +69 -0
  16. package/config/skills/agent/core/gmail-send/execute.sh +114 -0
  17. package/config/skills/agent/core/gmail-send/execute.test.sh +83 -0
  18. package/config/skills/orchestrator/list-missions/SKILL.md +47 -0
  19. package/config/skills/orchestrator/list-missions/execute.sh +34 -0
  20. package/config/skills/orchestrator/list-missions/execute.test.sh +33 -0
  21. package/config/slack-master-app-manifest.json +64 -0
  22. package/dist/backend/backend/src/constants.d.ts +125 -0
  23. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  24. package/dist/backend/backend/src/constants.js +125 -0
  25. package/dist/backend/backend/src/constants.js.map +1 -1
  26. package/dist/backend/backend/src/controllers/cloud/cloud.controller.d.ts.map +1 -1
  27. package/dist/backend/backend/src/controllers/cloud/cloud.controller.js +3 -1
  28. package/dist/backend/backend/src/controllers/cloud/cloud.controller.js.map +1 -1
  29. package/dist/backend/backend/src/controllers/google/google.controller.d.ts +100 -0
  30. package/dist/backend/backend/src/controllers/google/google.controller.d.ts.map +1 -0
  31. package/dist/backend/backend/src/controllers/google/google.controller.js +293 -0
  32. package/dist/backend/backend/src/controllers/google/google.controller.js.map +1 -0
  33. package/dist/backend/backend/src/controllers/google/google.routes.d.ts +26 -0
  34. package/dist/backend/backend/src/controllers/google/google.routes.d.ts.map +1 -0
  35. package/dist/backend/backend/src/controllers/google/google.routes.js +38 -0
  36. package/dist/backend/backend/src/controllers/google/google.routes.js.map +1 -0
  37. package/dist/backend/backend/src/controllers/slack/slack.controller.d.ts.map +1 -1
  38. package/dist/backend/backend/src/controllers/slack/slack.controller.js +209 -2
  39. package/dist/backend/backend/src/controllers/slack/slack.controller.js.map +1 -1
  40. package/dist/backend/backend/src/index.d.ts.map +1 -1
  41. package/dist/backend/backend/src/index.js +39 -8
  42. package/dist/backend/backend/src/index.js.map +1 -1
  43. package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
  44. package/dist/backend/backend/src/routes/api.routes.js +4 -0
  45. package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
  46. package/dist/backend/backend/src/services/cloud/cloud-initializer.d.ts +8 -0
  47. package/dist/backend/backend/src/services/cloud/cloud-initializer.d.ts.map +1 -1
  48. package/dist/backend/backend/src/services/cloud/cloud-initializer.js +25 -0
  49. package/dist/backend/backend/src/services/cloud/cloud-initializer.js.map +1 -1
  50. package/dist/backend/backend/src/services/cloud/cloud-sync.types.d.ts +1 -1
  51. package/dist/backend/backend/src/services/cloud/cloud-sync.types.d.ts.map +1 -1
  52. package/dist/backend/backend/src/services/cloud/cloud-sync.types.js +1 -0
  53. package/dist/backend/backend/src/services/cloud/cloud-sync.types.js.map +1 -1
  54. package/dist/backend/backend/src/services/google/calendar.service.d.ts +98 -0
  55. package/dist/backend/backend/src/services/google/calendar.service.d.ts.map +1 -0
  56. package/dist/backend/backend/src/services/google/calendar.service.js +125 -0
  57. package/dist/backend/backend/src/services/google/calendar.service.js.map +1 -0
  58. package/dist/backend/backend/src/services/google/gmail.service.d.ts +165 -0
  59. package/dist/backend/backend/src/services/google/gmail.service.d.ts.map +1 -0
  60. package/dist/backend/backend/src/services/google/gmail.service.js +296 -0
  61. package/dist/backend/backend/src/services/google/gmail.service.js.map +1 -0
  62. package/dist/backend/backend/src/services/google/google-api.client.d.ts +44 -0
  63. package/dist/backend/backend/src/services/google/google-api.client.d.ts.map +1 -0
  64. package/dist/backend/backend/src/services/google/google-api.client.js +104 -0
  65. package/dist/backend/backend/src/services/google/google-api.client.js.map +1 -0
  66. package/dist/backend/backend/src/services/google/google-workspace-token.service.d.ts +159 -0
  67. package/dist/backend/backend/src/services/google/google-workspace-token.service.d.ts.map +1 -0
  68. package/dist/backend/backend/src/services/google/google-workspace-token.service.js +274 -0
  69. package/dist/backend/backend/src/services/google/google-workspace-token.service.js.map +1 -0
  70. package/dist/backend/backend/src/services/slack/slack-agent-identity.service.d.ts +12 -1
  71. package/dist/backend/backend/src/services/slack/slack-agent-identity.service.d.ts.map +1 -1
  72. package/dist/backend/backend/src/services/slack/slack-agent-identity.service.js +30 -0
  73. package/dist/backend/backend/src/services/slack/slack-agent-identity.service.js.map +1 -1
  74. package/dist/backend/backend/src/services/slack/slack-cloud-config.service.d.ts +186 -0
  75. package/dist/backend/backend/src/services/slack/slack-cloud-config.service.d.ts.map +1 -0
  76. package/dist/backend/backend/src/services/slack/slack-cloud-config.service.js +409 -0
  77. package/dist/backend/backend/src/services/slack/slack-cloud-config.service.js.map +1 -0
  78. package/dist/backend/backend/src/services/slack/slack-initializer.d.ts +95 -1
  79. package/dist/backend/backend/src/services/slack/slack-initializer.d.ts.map +1 -1
  80. package/dist/backend/backend/src/services/slack/slack-initializer.js +284 -4
  81. package/dist/backend/backend/src/services/slack/slack-initializer.js.map +1 -1
  82. package/dist/backend/backend/src/services/slack/slack-instance-registry.service.d.ts +178 -0
  83. package/dist/backend/backend/src/services/slack/slack-instance-registry.service.d.ts.map +1 -0
  84. package/dist/backend/backend/src/services/slack/slack-instance-registry.service.js +392 -0
  85. package/dist/backend/backend/src/services/slack/slack-instance-registry.service.js.map +1 -0
  86. package/dist/backend/backend/src/services/slack/slack.service.d.ts +76 -1
  87. package/dist/backend/backend/src/services/slack/slack.service.d.ts.map +1 -1
  88. package/dist/backend/backend/src/services/slack/slack.service.js +234 -47
  89. package/dist/backend/backend/src/services/slack/slack.service.js.map +1 -1
  90. package/dist/backend/backend/src/services/v3/mission-reminder.service.d.ts +4 -0
  91. package/dist/backend/backend/src/services/v3/mission-reminder.service.d.ts.map +1 -1
  92. package/dist/backend/backend/src/services/v3/mission-reminder.service.js +17 -0
  93. package/dist/backend/backend/src/services/v3/mission-reminder.service.js.map +1 -1
  94. package/dist/backend/backend/src/services/v3/okr-owner-guidance.service.d.ts +116 -0
  95. package/dist/backend/backend/src/services/v3/okr-owner-guidance.service.d.ts.map +1 -0
  96. package/dist/backend/backend/src/services/v3/okr-owner-guidance.service.js +254 -0
  97. package/dist/backend/backend/src/services/v3/okr-owner-guidance.service.js.map +1 -0
  98. package/dist/backend/backend/src/types/slack.types.d.ts +141 -2
  99. package/dist/backend/backend/src/types/slack.types.d.ts.map +1 -1
  100. package/dist/backend/backend/src/types/slack.types.js.map +1 -1
  101. package/dist/backend/backend/src/types/v2/mission.types.d.ts +2 -0
  102. package/dist/backend/backend/src/types/v2/mission.types.d.ts.map +1 -1
  103. package/dist/backend/backend/src/types/v2/mission.types.js.map +1 -1
  104. package/dist/backend/build-info.json +2 -2
  105. package/dist/cli/backend/src/constants.d.ts +125 -0
  106. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  107. package/dist/cli/backend/src/constants.js +125 -0
  108. package/dist/cli/backend/src/constants.js.map +1 -1
  109. package/dist/cli/backend/src/services/cloud/cloud-sync.types.d.ts +1 -1
  110. package/dist/cli/backend/src/services/cloud/cloud-sync.types.d.ts.map +1 -1
  111. package/dist/cli/backend/src/services/cloud/cloud-sync.types.js +1 -0
  112. package/dist/cli/backend/src/services/cloud/cloud-sync.types.js.map +1 -1
  113. package/dist/cli/backend/src/services/slack/slack-agent-identity.service.d.ts +12 -1
  114. package/dist/cli/backend/src/services/slack/slack-agent-identity.service.d.ts.map +1 -1
  115. package/dist/cli/backend/src/services/slack/slack-agent-identity.service.js +30 -0
  116. package/dist/cli/backend/src/services/slack/slack-agent-identity.service.js.map +1 -1
  117. package/dist/cli/backend/src/services/slack/slack.service.d.ts +76 -1
  118. package/dist/cli/backend/src/services/slack/slack.service.d.ts.map +1 -1
  119. package/dist/cli/backend/src/services/slack/slack.service.js +234 -47
  120. package/dist/cli/backend/src/services/slack/slack.service.js.map +1 -1
  121. package/dist/cli/backend/src/types/slack.types.d.ts +141 -2
  122. package/dist/cli/backend/src/types/slack.types.d.ts.map +1 -1
  123. package/dist/cli/backend/src/types/slack.types.js.map +1 -1
  124. package/dist/cli/backend/src/types/v2/mission.types.d.ts +2 -0
  125. package/dist/cli/backend/src/types/v2/mission.types.d.ts.map +1 -1
  126. package/dist/cli/backend/src/types/v2/mission.types.js.map +1 -1
  127. package/frontend/dist/assets/{index-019817e2.js → index-c87332bf.js} +273 -273
  128. package/frontend/dist/assets/{index-a56613e7.css → index-f6b4230e.css} +1 -1
  129. package/frontend/dist/index.html +2 -2
  130. package/package.json +1 -1
@@ -0,0 +1,296 @@
1
+ /**
2
+ * Gmail Service
3
+ *
4
+ * Search, read and send on the owner's mailbox through the Gmail REST API
5
+ * with a Cloud-minted access token. Messages are built locally as RFC 822
6
+ * and sent base64url-encoded; bodies are decoded here; attachments are
7
+ * listed but never downloaded.
8
+ *
9
+ * @module services/google/gmail.service
10
+ */
11
+ import { GOOGLE_WORKSPACE_CONSTANTS } from '../../constants.js';
12
+ import { GoogleWorkspaceError } from './google-workspace-token.service.js';
13
+ import { buildGoogleUrl, googleRequest } from './google-api.client.js';
14
+ // ---------------------------------------------------------------------------
15
+ // Encoding helpers (exported for the tests and the controller's dry-run)
16
+ // ---------------------------------------------------------------------------
17
+ /**
18
+ * Standard base64url without padding — what `messages.send` expects in `raw`.
19
+ *
20
+ * @param input - Bytes or UTF-8 string
21
+ * @returns base64url string
22
+ */
23
+ export function base64UrlEncode(input) {
24
+ return Buffer.from(input).toString('base64url');
25
+ }
26
+ /**
27
+ * Decode Gmail's base64url body data (tolerates standard base64 too).
28
+ *
29
+ * @param data - base64url string
30
+ * @returns UTF-8 text
31
+ */
32
+ export function base64UrlDecode(data) {
33
+ return Buffer.from(data, 'base64url').toString('utf8');
34
+ }
35
+ /**
36
+ * RFC 2047 encoded-word for a header value that is not plain printable
37
+ * ASCII (a Chinese subject, an emoji); ASCII values are left untouched.
38
+ *
39
+ * @param value - Header value
40
+ * @returns The value as it should appear on the wire
41
+ */
42
+ export function encodeHeaderValue(value) {
43
+ // eslint-disable-next-line no-control-regex
44
+ if (/^[\x20-\x7e]*$/.test(value))
45
+ return value;
46
+ return `=?UTF-8?B?${Buffer.from(value, 'utf8').toString('base64')}?=`;
47
+ }
48
+ /**
49
+ * Wrap a base64 body at the RFC 2045 line width.
50
+ *
51
+ * @param base64 - Unwrapped base64
52
+ * @returns CRLF-wrapped base64
53
+ */
54
+ function wrapBase64(base64) {
55
+ const width = GOOGLE_WORKSPACE_CONSTANTS.MIME_LINE_WIDTH;
56
+ const lines = [];
57
+ for (let i = 0; i < base64.length; i += width)
58
+ lines.push(base64.slice(i, i + width));
59
+ return lines.join('\r\n');
60
+ }
61
+ /**
62
+ * Build the RFC 822 message for {@link GmailService.send}. The From header is
63
+ * left to Gmail (it fills in the authenticated user); the body is UTF-8
64
+ * text/plain, base64 transfer-encoded so any script is safe on the wire.
65
+ *
66
+ * @param input - Recipients, subject, body, optional reply threading
67
+ * @returns The raw message with CRLF line endings
68
+ * @throws GoogleWorkspaceError(400, validation) when `to` or `subject` is empty
69
+ */
70
+ export function buildRfc822(input) {
71
+ const CODES = GOOGLE_WORKSPACE_CONSTANTS.ERROR_CODES;
72
+ if (!input.to?.trim())
73
+ throw new GoogleWorkspaceError(400, CODES.VALIDATION, '"to" is required');
74
+ if (!input.subject?.trim())
75
+ throw new GoogleWorkspaceError(400, CODES.VALIDATION, '"subject" is required');
76
+ const headers = [`To: ${input.to.trim()}`];
77
+ if (input.cc?.trim())
78
+ headers.push(`Cc: ${input.cc.trim()}`);
79
+ headers.push(`Subject: ${encodeHeaderValue(input.subject.trim())}`);
80
+ if (input.inReplyTo?.trim()) {
81
+ const ref = input.inReplyTo.trim();
82
+ headers.push(`In-Reply-To: ${ref}`, `References: ${ref}`);
83
+ }
84
+ headers.push('MIME-Version: 1.0', 'Content-Type: text/plain; charset="UTF-8"', 'Content-Transfer-Encoding: base64');
85
+ const body = wrapBase64(Buffer.from(input.text ?? '', 'utf8').toString('base64'));
86
+ return `${headers.join('\r\n')}\r\n\r\n${body}`;
87
+ }
88
+ /**
89
+ * Very small HTML → text: drop scripts/styles, turn block boundaries into
90
+ * newlines, strip tags, unescape the common entities, collapse blank runs.
91
+ * Only used when a message has no text/plain part.
92
+ *
93
+ * @param html - HTML source
94
+ * @returns Plain text
95
+ */
96
+ export function stripHtml(html) {
97
+ return html
98
+ .replace(/<(script|style)[\s\S]*?<\/\1>/gi, '')
99
+ .replace(/<br\s*\/?>/gi, '\n')
100
+ .replace(/<\/(p|div|tr|li|h[1-6]|blockquote)>/gi, '\n')
101
+ .replace(/<[^>]+>/g, '')
102
+ .replace(/&nbsp;/gi, ' ')
103
+ .replace(/&amp;/gi, '&')
104
+ .replace(/&lt;/gi, '<')
105
+ .replace(/&gt;/gi, '>')
106
+ .replace(/&quot;/gi, '"')
107
+ .replace(/&#39;|&apos;/gi, "'")
108
+ .replace(/[ \t]+\n/g, '\n')
109
+ .replace(/\n{3,}/g, '\n\n')
110
+ .trim();
111
+ }
112
+ /**
113
+ * Case-insensitive header lookup.
114
+ *
115
+ * @param headers - Gmail header list
116
+ * @param name - Header name
117
+ * @returns Value or empty string
118
+ */
119
+ function header(headers, name) {
120
+ const lower = name.toLowerCase();
121
+ return headers?.find((h) => h.name.toLowerCase() === lower)?.value ?? '';
122
+ }
123
+ /**
124
+ * Walk a MIME tree depth-first.
125
+ *
126
+ * @param part - Root part
127
+ * @param visit - Callback per part
128
+ */
129
+ function walkParts(part, visit) {
130
+ if (!part)
131
+ return;
132
+ visit(part);
133
+ for (const child of part.parts ?? [])
134
+ walkParts(child, visit);
135
+ }
136
+ /**
137
+ * Pick the body: first text/plain part with inline data, else first text/html
138
+ * (stripped), else nothing.
139
+ *
140
+ * @param payload - Message payload
141
+ * @returns Body text and which kind it was
142
+ */
143
+ function extractBody(payload) {
144
+ let plain;
145
+ let html;
146
+ walkParts(payload, (p) => {
147
+ if (p.filename || !p.body?.data)
148
+ return;
149
+ const mime = (p.mimeType ?? '').toLowerCase();
150
+ if (mime === 'text/plain' && plain === undefined)
151
+ plain = base64UrlDecode(p.body.data);
152
+ else if (mime === 'text/html' && html === undefined)
153
+ html = base64UrlDecode(p.body.data);
154
+ });
155
+ if (plain !== undefined)
156
+ return { body: plain, bodyType: 'text' };
157
+ if (html !== undefined)
158
+ return { body: stripHtml(html), bodyType: 'html' };
159
+ return { body: '', bodyType: 'none' };
160
+ }
161
+ /**
162
+ * Every part that carries a filename and an attachmentId.
163
+ *
164
+ * @param payload - Message payload
165
+ * @returns Attachment references
166
+ */
167
+ function extractAttachments(payload) {
168
+ const out = [];
169
+ walkParts(payload, (p) => {
170
+ if (p.filename && p.body?.attachmentId) {
171
+ out.push({
172
+ filename: p.filename,
173
+ mimeType: p.mimeType ?? 'application/octet-stream',
174
+ size: p.body.size ?? 0,
175
+ attachmentId: p.body.attachmentId,
176
+ });
177
+ }
178
+ });
179
+ return out;
180
+ }
181
+ // ---------------------------------------------------------------------------
182
+ // Service
183
+ // ---------------------------------------------------------------------------
184
+ /**
185
+ * Gmail operations on the signed-in owner's mailbox.
186
+ *
187
+ * @example
188
+ * ```ts
189
+ * const gmail = new GmailService({ tokens: GoogleWorkspaceTokenService.getInstance() });
190
+ * const hits = await gmail.search({ query: 'is:unread newer_than:1d' });
191
+ * ```
192
+ */
193
+ export class GmailService {
194
+ deps;
195
+ base = GOOGLE_WORKSPACE_CONSTANTS.GMAIL_API_BASE;
196
+ /**
197
+ * @param deps - Token provider and optional fetch override
198
+ */
199
+ constructor(deps) {
200
+ this.deps = deps;
201
+ }
202
+ /**
203
+ * Gmail search (`messages.list`) followed by one metadata `messages.get`
204
+ * per hit for From / To / Subject / Date and the snippet.
205
+ *
206
+ * @param input - Gmail query string and result cap
207
+ * @returns Hits in Gmail's order (newest first)
208
+ * @throws GoogleWorkspaceError on auth / Google failures
209
+ */
210
+ async search(input) {
211
+ const query = (input.query ?? '').trim();
212
+ const max = clampMax(input.max, GOOGLE_WORKSPACE_CONSTANTS.GMAIL_DEFAULT_MAX_RESULTS, GOOGLE_WORKSPACE_CONSTANTS.GMAIL_MAX_RESULTS_CEILING);
213
+ const list = await googleRequest(this.deps, buildGoogleUrl(`${this.base}/messages`, { q: query, maxResults: max }));
214
+ const refs = list.messages ?? [];
215
+ if (refs.length === 0)
216
+ return [];
217
+ const messages = await Promise.all(refs.map((ref) => googleRequest(this.deps, buildGoogleUrl(`${this.base}/messages/${encodeURIComponent(ref.id)}`, {
218
+ format: 'metadata',
219
+ metadataHeaders: [...GOOGLE_WORKSPACE_CONSTANTS.GMAIL_SEARCH_HEADERS],
220
+ }))));
221
+ return messages.map((m) => {
222
+ const h = m.payload?.headers;
223
+ return {
224
+ id: m.id,
225
+ threadId: m.threadId,
226
+ from: header(h, 'From'),
227
+ to: header(h, 'To'),
228
+ subject: header(h, 'Subject'),
229
+ date: header(h, 'Date'),
230
+ snippet: m.snippet ?? '',
231
+ labelIds: m.labelIds ?? [],
232
+ };
233
+ });
234
+ }
235
+ /**
236
+ * Full `messages.get`; body decoded (text/plain preferred, stripped
237
+ * text/html as fallback), attachments listed only.
238
+ *
239
+ * @param id - Gmail message id
240
+ * @returns The message
241
+ * @throws GoogleWorkspaceError — 404 when Gmail has no such message
242
+ */
243
+ async read(id) {
244
+ if (!id?.trim()) {
245
+ throw new GoogleWorkspaceError(400, GOOGLE_WORKSPACE_CONSTANTS.ERROR_CODES.VALIDATION, 'message id is required');
246
+ }
247
+ const m = await googleRequest(this.deps, buildGoogleUrl(`${this.base}/messages/${encodeURIComponent(id.trim())}`, { format: 'full' }));
248
+ const h = m.payload?.headers;
249
+ const { body, bodyType } = extractBody(m.payload);
250
+ return {
251
+ id: m.id,
252
+ threadId: m.threadId,
253
+ from: header(h, 'From'),
254
+ to: header(h, 'To'),
255
+ cc: header(h, 'Cc'),
256
+ subject: header(h, 'Subject'),
257
+ date: header(h, 'Date'),
258
+ messageId: header(h, 'Message-ID'),
259
+ snippet: m.snippet ?? '',
260
+ body,
261
+ bodyType,
262
+ attachments: extractAttachments(m.payload),
263
+ labelIds: m.labelIds ?? [],
264
+ };
265
+ }
266
+ /**
267
+ * `messages.send` with a locally built RFC 822 message.
268
+ *
269
+ * @param input - Recipients, subject, text and optional reply threading
270
+ * @returns The sent message's id / threadId / labels
271
+ * @throws GoogleWorkspaceError(400, validation) for missing to/subject; auth / Google failures
272
+ */
273
+ async send(input) {
274
+ const raw = base64UrlEncode(buildRfc822(input));
275
+ const body = { raw };
276
+ if (input.threadId?.trim())
277
+ body.threadId = input.threadId.trim();
278
+ const sent = await googleRequest(this.deps, `${this.base}/messages/send`, { method: 'POST', body });
279
+ return { id: sent.id, threadId: sent.threadId, labelIds: sent.labelIds ?? [] };
280
+ }
281
+ }
282
+ /**
283
+ * Coerce a caller-supplied result cap into [1, ceiling], defaulting when
284
+ * absent or not a number.
285
+ *
286
+ * @param value - Requested cap
287
+ * @param fallback - Default
288
+ * @param ceiling - Hard maximum
289
+ * @returns The cap to send to Google
290
+ */
291
+ export function clampMax(value, fallback, ceiling) {
292
+ if (value === undefined || !Number.isFinite(value))
293
+ return fallback;
294
+ return Math.min(Math.max(Math.floor(value), 1), ceiling);
295
+ }
296
+ //# sourceMappingURL=gmail.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gmail.service.js","sourceRoot":"","sources":["../../../../../../backend/src/services/google/gmail.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Google API client — the one place Gmail and Calendar calls go through.
3
+ *
4
+ * Attaches the Cloud-minted access token, maps Google's HTTP failures to
5
+ * {@link GoogleWorkspaceError}, and drops the cached token on a 401 so the
6
+ * next call refreshes instead of failing the same way again.
7
+ *
8
+ * @module services/google/google-api.client
9
+ */
10
+ /** The slice of GoogleWorkspaceTokenService the Google services need. */
11
+ export interface GoogleTokenProvider {
12
+ getAccessToken(): Promise<string>;
13
+ clearCache(): void;
14
+ }
15
+ /** Shared constructor dependencies for Gmail / Calendar services. */
16
+ export interface GoogleApiDeps {
17
+ tokens: GoogleTokenProvider;
18
+ fetchImpl?: typeof fetch;
19
+ }
20
+ /**
21
+ * Build a URL with query parameters; `undefined`/empty values are skipped and
22
+ * array values are repeated (Gmail's `metadataHeaders=A&metadataHeaders=B`).
23
+ *
24
+ * @param base - Absolute URL without query
25
+ * @param params - Query parameters
26
+ * @returns The full URL string
27
+ */
28
+ export declare function buildGoogleUrl(base: string, params?: Record<string, string | number | string[] | undefined>): string;
29
+ /**
30
+ * Authenticated JSON call to a Google API.
31
+ *
32
+ * @param deps - Token provider + fetch
33
+ * @param url - Absolute URL (use {@link buildGoogleUrl})
34
+ * @param init - Method and optional JSON body
35
+ * @returns The parsed JSON response
36
+ * @throws GoogleWorkspaceError — token failures pass through; Google 401 →
37
+ * 401 google_error (cache dropped), 403/404/429 keep their status, other
38
+ * failures → 502 google_error, unreachable → 502 network
39
+ */
40
+ export declare function googleRequest<T>(deps: GoogleApiDeps, url: string, init?: {
41
+ method?: 'GET' | 'POST';
42
+ body?: unknown;
43
+ }): Promise<T>;
44
+ //# sourceMappingURL=google-api.client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"google-api.client.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/google/google-api.client.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,yEAAyE;AACzE,MAAM,WAAW,mBAAmB;IAClC,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAClC,UAAU,IAAI,IAAI,CAAC;CACpB;AAED,qEAAqE;AACrE,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,mBAAmB,CAAC;IAC5B,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAwBD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAM,GAAG,MAAM,CAWxH;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,aAAa,CAAC,CAAC,EACnC,IAAI,EAAE,aAAa,EACnB,GAAG,EAAE,MAAM,EACX,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAO,GACrD,OAAO,CAAC,CAAC,CAAC,CAsCZ"}
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Google API client — the one place Gmail and Calendar calls go through.
3
+ *
4
+ * Attaches the Cloud-minted access token, maps Google's HTTP failures to
5
+ * {@link GoogleWorkspaceError}, and drops the cached token on a 401 so the
6
+ * next call refreshes instead of failing the same way again.
7
+ *
8
+ * @module services/google/google-api.client
9
+ */
10
+ import { GOOGLE_WORKSPACE_CONSTANTS } from '../../constants.js';
11
+ import { GoogleWorkspaceError } from './google-workspace-token.service.js';
12
+ /**
13
+ * Extract the human message from a Google error body, tolerating both the
14
+ * object and the plain-string shapes Google uses.
15
+ *
16
+ * @param text - Raw response body
17
+ * @returns Message or undefined
18
+ */
19
+ function googleErrorMessage(text) {
20
+ try {
21
+ const parsed = JSON.parse(text);
22
+ if (typeof parsed.error === 'string')
23
+ return parsed.error;
24
+ return parsed.error?.message;
25
+ }
26
+ catch {
27
+ return text ? text.slice(0, 200) : undefined;
28
+ }
29
+ }
30
+ /**
31
+ * Build a URL with query parameters; `undefined`/empty values are skipped and
32
+ * array values are repeated (Gmail's `metadataHeaders=A&metadataHeaders=B`).
33
+ *
34
+ * @param base - Absolute URL without query
35
+ * @param params - Query parameters
36
+ * @returns The full URL string
37
+ */
38
+ export function buildGoogleUrl(base, params = {}) {
39
+ const url = new URL(base);
40
+ for (const [key, value] of Object.entries(params)) {
41
+ if (value === undefined || value === '')
42
+ continue;
43
+ if (Array.isArray(value)) {
44
+ for (const v of value)
45
+ url.searchParams.append(key, v);
46
+ }
47
+ else {
48
+ url.searchParams.set(key, String(value));
49
+ }
50
+ }
51
+ return url.toString();
52
+ }
53
+ /**
54
+ * Authenticated JSON call to a Google API.
55
+ *
56
+ * @param deps - Token provider + fetch
57
+ * @param url - Absolute URL (use {@link buildGoogleUrl})
58
+ * @param init - Method and optional JSON body
59
+ * @returns The parsed JSON response
60
+ * @throws GoogleWorkspaceError — token failures pass through; Google 401 →
61
+ * 401 google_error (cache dropped), 403/404/429 keep their status, other
62
+ * failures → 502 google_error, unreachable → 502 network
63
+ */
64
+ export async function googleRequest(deps, url, init = {}) {
65
+ const token = await deps.tokens.getAccessToken();
66
+ const fetchImpl = deps.fetchImpl ?? fetch;
67
+ const CODES = GOOGLE_WORKSPACE_CONSTANTS.ERROR_CODES;
68
+ let res;
69
+ try {
70
+ res = await fetchImpl(url, {
71
+ method: init.method ?? 'GET',
72
+ headers: {
73
+ Authorization: `Bearer ${token}`,
74
+ Accept: 'application/json',
75
+ ...(init.body !== undefined ? { 'Content-Type': 'application/json' } : {}),
76
+ },
77
+ body: init.body !== undefined ? JSON.stringify(init.body) : undefined,
78
+ signal: AbortSignal.timeout(GOOGLE_WORKSPACE_CONSTANTS.REQUEST_TIMEOUT_MS),
79
+ });
80
+ }
81
+ catch (err) {
82
+ const message = err instanceof Error ? err.message : String(err);
83
+ throw new GoogleWorkspaceError(502, CODES.NETWORK, `Google unreachable: ${message}`);
84
+ }
85
+ const text = await res.text();
86
+ if (!res.ok) {
87
+ const message = googleErrorMessage(text) ?? `Google request failed (${res.status})`;
88
+ if (res.status === 401) {
89
+ deps.tokens.clearCache();
90
+ throw new GoogleWorkspaceError(401, CODES.GOOGLE_ERROR, `Google rejected the access token: ${message}`);
91
+ }
92
+ const passthrough = res.status === 403 || res.status === 404 || res.status === 429;
93
+ throw new GoogleWorkspaceError(passthrough ? res.status : 502, CODES.GOOGLE_ERROR, message);
94
+ }
95
+ if (!text)
96
+ return {};
97
+ try {
98
+ return JSON.parse(text);
99
+ }
100
+ catch {
101
+ throw new GoogleWorkspaceError(502, CODES.GOOGLE_ERROR, 'Google returned a non-JSON response.');
102
+ }
103
+ }
104
+ //# sourceMappingURL=google-api.client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"google-api.client.js","sourceRoot":"","sources":["../../../../../../backend/src/services/google/google-api.client.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,oBAAoB,EAAE,MAAM,qCAAqC,CAAC;AAmB3E;;;;;;GAMG;AACH,SAAS,kBAAkB,CAAC,IAAY;IACtC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAoB,CAAC;QACnD,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ;YAAE,OAAO,MAAM,CAAC,KAAK,CAAC;QAC1D,OAAO,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC/C,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,SAAiE,EAAE;IAC9G,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC;IAC1B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAClD,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE;YAAE,SAAS;QAClD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACzB,KAAK,MAAM,CAAC,IAAI,KAAK;gBAAE,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACzD,CAAC;aAAM,CAAC;YACN,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QAC3C,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC;AACxB,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,IAAmB,EACnB,GAAW,EACX,OAAoD,EAAE;IAEtD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC;IACjD,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC;IAC1C,MAAM,KAAK,GAAG,0BAA0B,CAAC,WAAW,CAAC;IAErD,IAAI,GAAa,CAAC;IAClB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,SAAS,CAAC,GAAG,EAAE;YACzB,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,KAAK;YAC5B,OAAO,EAAE;gBACP,aAAa,EAAE,UAAU,KAAK,EAAE;gBAChC,MAAM,EAAE,kBAAkB;gBAC1B,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC3E;YACD,IAAI,EAAE,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;YACrE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,0BAA0B,CAAC,kBAAkB,CAAC;SAC3E,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,MAAM,IAAI,oBAAoB,CAAC,GAAG,EAAE,KAAK,CAAC,OAAO,EAAE,uBAAuB,OAAO,EAAE,CAAC,CAAC;IACvF,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IAC9B,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,OAAO,GAAG,kBAAkB,CAAC,IAAI,CAAC,IAAI,0BAA0B,GAAG,CAAC,MAAM,GAAG,CAAC;QACpF,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YACvB,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;YACzB,MAAM,IAAI,oBAAoB,CAAC,GAAG,EAAE,KAAK,CAAC,YAAY,EAAE,qCAAqC,OAAO,EAAE,CAAC,CAAC;QAC1G,CAAC;QACD,MAAM,WAAW,GAAG,GAAG,CAAC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,CAAC;QACnF,MAAM,IAAI,oBAAoB,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;IAC9F,CAAC;IACD,IAAI,CAAC,IAAI;QAAE,OAAO,EAAO,CAAC;IAC1B,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAM,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,oBAAoB,CAAC,GAAG,EAAE,KAAK,CAAC,YAAY,EAAE,sCAAsC,CAAC,CAAC;IAClG,CAAC;AACH,CAAC"}
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Google Workspace Token Service
3
+ *
4
+ * Crewly Cloud holds the owner's Google OAuth grant; this service asks Cloud
5
+ * (`/api/cloud/google/workspace/token`) for a short-lived access token and
6
+ * caches it until shortly before expiry, so Gmail / Calendar calls made by
7
+ * this instance never carry the refresh token and mail content never passes
8
+ * through Cloud.
9
+ *
10
+ * Cloud auth reuses the Crewly Cloud session already held by
11
+ * `CloudClientService` (its access JWT as Bearer) — no separate login.
12
+ *
13
+ * @module services/google/google-workspace-token.service
14
+ */
15
+ /** The slice of CloudClientService this service needs. */
16
+ export interface GoogleWorkspaceCloudClient {
17
+ isConnected(): boolean;
18
+ getToken(): string | null;
19
+ getCloudUrl(): string | null;
20
+ }
21
+ /** Constructor dependencies (all optional — defaults are the real singletons). */
22
+ export interface GoogleWorkspaceTokenServiceDeps {
23
+ cloud?: GoogleWorkspaceCloudClient;
24
+ fetchImpl?: typeof fetch;
25
+ now?: () => number;
26
+ }
27
+ /** Cloud's `/status` payload, plus whether this instance is signed in to Cloud at all. */
28
+ export interface GoogleWorkspaceStatus {
29
+ /** True when Cloud holds a live Workspace grant for the signed-in account */
30
+ connected: boolean;
31
+ /** True when this instance is signed in to Crewly Cloud */
32
+ cloudConnected: boolean;
33
+ email?: string;
34
+ scopes?: string[];
35
+ grantedAt?: string;
36
+ }
37
+ /**
38
+ * Error raised for every Cloud / Google failure. `status` is the HTTP status
39
+ * the OSS controller should answer with and `code` is one of
40
+ * {@link GOOGLE_WORKSPACE_CONSTANTS.ERROR_CODES} (or `http_<n>` for anything
41
+ * unmapped), so the controller and the skills can branch without parsing text.
42
+ */
43
+ export declare class GoogleWorkspaceError extends Error {
44
+ readonly status: number;
45
+ readonly code: string;
46
+ constructor(status: number, code: string, message: string);
47
+ }
48
+ /**
49
+ * Map a Cloud workspace-endpoint failure to the status/code this instance
50
+ * answers with. 404 (no grant) and 409 (grant revoked) both mean "not
51
+ * connected — go through the consent flow again"; 503 means Cloud itself has
52
+ * no Google client; 502 is Google refusing Cloud.
53
+ *
54
+ * @param httpStatus - Status Cloud answered with
55
+ * @param code - `code` (or `error`) field from Cloud's body, if any
56
+ * @param message - Human message from Cloud's body, if any
57
+ * @returns The error to throw
58
+ */
59
+ export declare function mapCloudFailure(httpStatus: number, code: string | undefined, message: string | undefined): GoogleWorkspaceError;
60
+ /**
61
+ * Fetches, caches and refreshes the Google access token that Cloud mints
62
+ * from the owner's stored grant.
63
+ *
64
+ * @example
65
+ * ```ts
66
+ * const token = await GoogleWorkspaceTokenService.getInstance().getAccessToken();
67
+ * ```
68
+ */
69
+ export declare class GoogleWorkspaceTokenService {
70
+ private static instance;
71
+ private readonly logger;
72
+ private readonly cloud;
73
+ private readonly fetchImpl;
74
+ private readonly nowFn;
75
+ /** Cached token + absolute expiry (ms since epoch), or null. */
76
+ private cached;
77
+ /** The one refresh currently on the wire, so concurrent callers share it. */
78
+ private inflight;
79
+ /**
80
+ * @param deps - Cloud client slice, fetch and clock overrides (tests)
81
+ */
82
+ constructor(deps?: GoogleWorkspaceTokenServiceDeps);
83
+ /**
84
+ * Process-wide instance bound to the real CloudClientService.
85
+ *
86
+ * @returns The singleton
87
+ */
88
+ static getInstance(): GoogleWorkspaceTokenService;
89
+ /**
90
+ * Drop the singleton (tests).
91
+ */
92
+ static resetInstance(): void;
93
+ /**
94
+ * Whether this instance is signed in to Crewly Cloud (prerequisite for
95
+ * everything else here).
96
+ *
97
+ * @returns True when a Cloud URL and access JWT are available
98
+ */
99
+ isCloudAvailable(): boolean;
100
+ /**
101
+ * A Google access token that is valid for at least the refresh margin.
102
+ * Served from cache when possible; otherwise one Cloud round-trip shared
103
+ * by every concurrent caller.
104
+ *
105
+ * @returns Bearer token for Gmail / Calendar
106
+ * @throws GoogleWorkspaceError — not_logged_in / not_connected / not_configured / google_error / network
107
+ */
108
+ getAccessToken(): Promise<string>;
109
+ /**
110
+ * Forget the cached token so the next call refreshes (used after Google
111
+ * answers 401, or when the grant is revoked).
112
+ */
113
+ clearCache(): void;
114
+ /**
115
+ * Whether Cloud holds a Workspace grant, and for which email.
116
+ * Never throws for the two "not there" cases (no Cloud session / no grant);
117
+ * those come back as `connected: false`.
118
+ *
119
+ * @returns Connection status
120
+ * @throws GoogleWorkspaceError for Cloud outages / misconfiguration
121
+ */
122
+ status(): Promise<GoogleWorkspaceStatus>;
123
+ /**
124
+ * Ask Cloud to revoke the grant and forget it; also drops the local cache.
125
+ *
126
+ * @returns Whether Cloud had a grant to remove
127
+ * @throws GoogleWorkspaceError when not signed in to Cloud or Cloud fails
128
+ */
129
+ disconnect(): Promise<{
130
+ removed: boolean;
131
+ }>;
132
+ /**
133
+ * The Cloud consent-start URL for the browser: carries the current Cloud
134
+ * access JWT (Cloud accepts `?token=` because a top-level navigation cannot
135
+ * set a Bearer header) and where to land afterwards.
136
+ *
137
+ * @param returnUrl - Absolute dashboard URL Cloud redirects back to
138
+ * @returns Absolute URL to open
139
+ * @throws GoogleWorkspaceError(401, not_logged_in) when not signed in to Cloud
140
+ */
141
+ buildConnectUrl(returnUrl: string): string;
142
+ /**
143
+ * One Cloud `/token` round-trip; updates the cache.
144
+ *
145
+ * @returns The fresh access token
146
+ */
147
+ private refresh;
148
+ /**
149
+ * Authenticated call to a Cloud workspace endpoint using the Cloud session
150
+ * JWT. Unwraps the auth service's `{ success, data }` envelope.
151
+ *
152
+ * @param method - HTTP verb
153
+ * @param suffix - Path under CLOUD_PATH
154
+ * @returns The `data` payload
155
+ * @throws GoogleWorkspaceError mapped via {@link mapCloudFailure}
156
+ */
157
+ private cloudRequest;
158
+ }
159
+ //# sourceMappingURL=google-workspace-token.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"google-workspace-token.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/google/google-workspace-token.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAMH,0DAA0D;AAC1D,MAAM,WAAW,0BAA0B;IACzC,WAAW,IAAI,OAAO,CAAC;IACvB,QAAQ,IAAI,MAAM,GAAG,IAAI,CAAC;IAC1B,WAAW,IAAI,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED,kFAAkF;AAClF,MAAM,WAAW,+BAA+B;IAC9C,KAAK,CAAC,EAAE,0BAA0B,CAAC;IACnC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,0FAA0F;AAC1F,MAAM,WAAW,qBAAqB;IACpC,6EAA6E;IAC7E,SAAS,EAAE,OAAO,CAAC;IACnB,2DAA2D;IAC3D,cAAc,EAAE,OAAO,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAkBD;;;;;GAKG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;aAE3B,MAAM,EAAE,MAAM;aACd,IAAI,EAAE,MAAM;gBADZ,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EAC5B,OAAO,EAAE,MAAM;CAKlB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,oBAAoB,CAe/H;AAED;;;;;;;;GAQG;AACH,qBAAa,2BAA2B;IACtC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAA4C;IAEnE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA6B;IACnD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;IACzC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;IAErC,gEAAgE;IAChE,OAAO,CAAC,MAAM,CAAgG;IAC9G,6EAA6E;IAC7E,OAAO,CAAC,QAAQ,CAAgC;IAEhD;;OAEG;gBACS,IAAI,GAAE,+BAAoC;IAOtD;;;;OAIG;IACH,MAAM,CAAC,WAAW,IAAI,2BAA2B;IAOjD;;OAEG;IACH,MAAM,CAAC,aAAa,IAAI,IAAI;IAI5B;;;;;OAKG;IACH,gBAAgB,IAAI,OAAO;IAI3B;;;;;;;OAOG;IACG,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC;IAavC;;;OAGG;IACH,UAAU,IAAI,IAAI;IAIlB;;;;;;;OAOG;IACG,MAAM,IAAI,OAAO,CAAC,qBAAqB,CAAC;IAuB9C;;;;;OAKG;IACG,UAAU,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IAMjD;;;;;;;;OAQG;IACH,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;IAc1C;;;;OAIG;YACW,OAAO;IAsBrB;;;;;;;;OAQG;YACW,YAAY;CA8B3B"}