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.
- package/README.md +33 -1
- package/config/roles/orchestrator/prompt.md +20 -5
- package/config/skills/agent/core/calendar-create/SKILL.md +62 -0
- package/config/skills/agent/core/calendar-create/execute.sh +91 -0
- package/config/skills/agent/core/calendar-create/execute.test.sh +80 -0
- package/config/skills/agent/core/calendar-list/SKILL.md +58 -0
- package/config/skills/agent/core/calendar-list/execute.sh +88 -0
- package/config/skills/agent/core/calendar-list/execute.test.sh +71 -0
- package/config/skills/agent/core/gmail-read/SKILL.md +61 -0
- package/config/skills/agent/core/gmail-read/execute.sh +62 -0
- package/config/skills/agent/core/gmail-read/execute.test.sh +68 -0
- package/config/skills/agent/core/gmail-search/SKILL.md +64 -0
- package/config/skills/agent/core/gmail-search/execute.sh +70 -0
- package/config/skills/agent/core/gmail-search/execute.test.sh +67 -0
- package/config/skills/agent/core/gmail-send/SKILL.md +69 -0
- package/config/skills/agent/core/gmail-send/execute.sh +114 -0
- package/config/skills/agent/core/gmail-send/execute.test.sh +83 -0
- package/config/skills/orchestrator/list-missions/SKILL.md +47 -0
- package/config/skills/orchestrator/list-missions/execute.sh +34 -0
- package/config/skills/orchestrator/list-missions/execute.test.sh +33 -0
- package/config/slack-master-app-manifest.json +64 -0
- package/dist/backend/backend/src/constants.d.ts +125 -0
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +125 -0
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/cloud/cloud.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/cloud/cloud.controller.js +3 -1
- package/dist/backend/backend/src/controllers/cloud/cloud.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/google/google.controller.d.ts +100 -0
- package/dist/backend/backend/src/controllers/google/google.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/google/google.controller.js +293 -0
- package/dist/backend/backend/src/controllers/google/google.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/google/google.routes.d.ts +26 -0
- package/dist/backend/backend/src/controllers/google/google.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/google/google.routes.js +38 -0
- package/dist/backend/backend/src/controllers/google/google.routes.js.map +1 -0
- package/dist/backend/backend/src/controllers/slack/slack.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/slack/slack.controller.js +209 -2
- package/dist/backend/backend/src/controllers/slack/slack.controller.js.map +1 -1
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +39 -8
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.js +4 -0
- package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-initializer.d.ts +8 -0
- package/dist/backend/backend/src/services/cloud/cloud-initializer.d.ts.map +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-initializer.js +25 -0
- package/dist/backend/backend/src/services/cloud/cloud-initializer.js.map +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-sync.types.d.ts +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-sync.types.d.ts.map +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-sync.types.js +1 -0
- package/dist/backend/backend/src/services/cloud/cloud-sync.types.js.map +1 -1
- package/dist/backend/backend/src/services/google/calendar.service.d.ts +98 -0
- package/dist/backend/backend/src/services/google/calendar.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/google/calendar.service.js +125 -0
- package/dist/backend/backend/src/services/google/calendar.service.js.map +1 -0
- package/dist/backend/backend/src/services/google/gmail.service.d.ts +165 -0
- package/dist/backend/backend/src/services/google/gmail.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/google/gmail.service.js +296 -0
- package/dist/backend/backend/src/services/google/gmail.service.js.map +1 -0
- package/dist/backend/backend/src/services/google/google-api.client.d.ts +44 -0
- package/dist/backend/backend/src/services/google/google-api.client.d.ts.map +1 -0
- package/dist/backend/backend/src/services/google/google-api.client.js +104 -0
- package/dist/backend/backend/src/services/google/google-api.client.js.map +1 -0
- package/dist/backend/backend/src/services/google/google-workspace-token.service.d.ts +159 -0
- package/dist/backend/backend/src/services/google/google-workspace-token.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/google/google-workspace-token.service.js +274 -0
- package/dist/backend/backend/src/services/google/google-workspace-token.service.js.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-agent-identity.service.d.ts +12 -1
- package/dist/backend/backend/src/services/slack/slack-agent-identity.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-agent-identity.service.js +30 -0
- package/dist/backend/backend/src/services/slack/slack-agent-identity.service.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-cloud-config.service.d.ts +186 -0
- package/dist/backend/backend/src/services/slack/slack-cloud-config.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-cloud-config.service.js +409 -0
- package/dist/backend/backend/src/services/slack/slack-cloud-config.service.js.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-initializer.d.ts +95 -1
- package/dist/backend/backend/src/services/slack/slack-initializer.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-initializer.js +284 -4
- package/dist/backend/backend/src/services/slack/slack-initializer.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-instance-registry.service.d.ts +178 -0
- package/dist/backend/backend/src/services/slack/slack-instance-registry.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-instance-registry.service.js +392 -0
- package/dist/backend/backend/src/services/slack/slack-instance-registry.service.js.map +1 -0
- package/dist/backend/backend/src/services/slack/slack.service.d.ts +76 -1
- package/dist/backend/backend/src/services/slack/slack.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack.service.js +234 -47
- package/dist/backend/backend/src/services/slack/slack.service.js.map +1 -1
- package/dist/backend/backend/src/services/v3/mission-reminder.service.d.ts +4 -0
- package/dist/backend/backend/src/services/v3/mission-reminder.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/v3/mission-reminder.service.js +17 -0
- package/dist/backend/backend/src/services/v3/mission-reminder.service.js.map +1 -1
- package/dist/backend/backend/src/services/v3/okr-owner-guidance.service.d.ts +116 -0
- package/dist/backend/backend/src/services/v3/okr-owner-guidance.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/v3/okr-owner-guidance.service.js +254 -0
- package/dist/backend/backend/src/services/v3/okr-owner-guidance.service.js.map +1 -0
- package/dist/backend/backend/src/types/slack.types.d.ts +141 -2
- package/dist/backend/backend/src/types/slack.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/slack.types.js.map +1 -1
- package/dist/backend/backend/src/types/v2/mission.types.d.ts +2 -0
- package/dist/backend/backend/src/types/v2/mission.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/v2/mission.types.js.map +1 -1
- package/dist/backend/build-info.json +2 -2
- package/dist/cli/backend/src/constants.d.ts +125 -0
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +125 -0
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/services/cloud/cloud-sync.types.d.ts +1 -1
- package/dist/cli/backend/src/services/cloud/cloud-sync.types.d.ts.map +1 -1
- package/dist/cli/backend/src/services/cloud/cloud-sync.types.js +1 -0
- package/dist/cli/backend/src/services/cloud/cloud-sync.types.js.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-agent-identity.service.d.ts +12 -1
- package/dist/cli/backend/src/services/slack/slack-agent-identity.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-agent-identity.service.js +30 -0
- package/dist/cli/backend/src/services/slack/slack-agent-identity.service.js.map +1 -1
- package/dist/cli/backend/src/services/slack/slack.service.d.ts +76 -1
- package/dist/cli/backend/src/services/slack/slack.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack.service.js +234 -47
- package/dist/cli/backend/src/services/slack/slack.service.js.map +1 -1
- package/dist/cli/backend/src/types/slack.types.d.ts +141 -2
- package/dist/cli/backend/src/types/slack.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/slack.types.js.map +1 -1
- package/dist/cli/backend/src/types/v2/mission.types.d.ts +2 -0
- package/dist/cli/backend/src/types/v2/mission.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/v2/mission.types.js.map +1 -1
- package/frontend/dist/assets/{index-019817e2.js → index-c87332bf.js} +273 -273
- package/frontend/dist/assets/{index-a56613e7.css → index-f6b4230e.css} +1 -1
- package/frontend/dist/index.html +2 -2
- 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(/ /gi, ' ')
|
|
103
|
+
.replace(/&/gi, '&')
|
|
104
|
+
.replace(/</gi, '<')
|
|
105
|
+
.replace(/>/gi, '>')
|
|
106
|
+
.replace(/"/gi, '"')
|
|
107
|
+
.replace(/'|'/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":"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"}
|
|
@@ -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"}
|