@framers/agentos-ext-letterboxd 1.0.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.
@@ -0,0 +1,536 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Letterboxd Movie Lookup Tool — ITool implementation for Letterboxd scraping.
4
+ *
5
+ * Retrieves film metadata, aggregate ratings, director credits, genre tags,
6
+ * a synopsis, and a sample of recent reviews from Letterboxd. Uses a
7
+ * two-strategy approach:
8
+ *
9
+ * 1. **Recipe delegation** — if a `web_scrape_recipe` tool reference is
10
+ * provided at construction time, the tool delegates to the web-scraper
11
+ * extension's recipe engine for robust, tiered scraping.
12
+ * 2. **Built-in fallback** — a lightweight `fetch()`-based scraper that
13
+ * parses Letterboxd HTML via regex. No API key or headless browser
14
+ * required; works out of the box for most films.
15
+ *
16
+ * @module tools/letterboxdMovie
17
+ */
18
+ /* -------------------------------------------------------------------------- */
19
+ /* Constants */
20
+ /* -------------------------------------------------------------------------- */
21
+ /** Base URL for Letterboxd film search. */
22
+ const SEARCH_URL = 'https://letterboxd.com/search/films';
23
+ /** Base URL for individual film pages. */
24
+ const FILM_BASE_URL = 'https://letterboxd.com/film';
25
+ /** Maximum number of reviews to extract from a film page. */
26
+ const MAX_REVIEWS = 5;
27
+ /* -------------------------------------------------------------------------- */
28
+ /* User-Agent helper */
29
+ /* -------------------------------------------------------------------------- */
30
+ /**
31
+ * Return a realistic desktop Chrome User-Agent string.
32
+ *
33
+ * Rather than pulling in the full {@link UserAgentPool} from the web-scraper
34
+ * extension, this helper generates a single plausible UA on the fly.
35
+ *
36
+ * @returns A User-Agent header value string.
37
+ */
38
+ function realisticUserAgent() {
39
+ const chromeVersion = 128 + Math.floor(Math.random() * 6); // 128-133
40
+ const platforms = [
41
+ 'Windows NT 10.0; Win64; x64',
42
+ 'Macintosh; Intel Mac OS X 10_15_7',
43
+ 'Macintosh; Intel Mac OS X 14_5',
44
+ 'X11; Linux x86_64',
45
+ ];
46
+ const platform = platforms[Math.floor(Math.random() * platforms.length)];
47
+ return (`Mozilla/5.0 (${platform}) AppleWebKit/537.36 ` +
48
+ `(KHTML, like Gecko) Chrome/${chromeVersion}.0.0.0 Safari/537.36`);
49
+ }
50
+ /**
51
+ * Build a minimal set of browser-like request headers.
52
+ *
53
+ * @returns A header record suitable for `fetch()`.
54
+ */
55
+ function browserHeaders() {
56
+ return {
57
+ 'User-Agent': realisticUserAgent(),
58
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
59
+ 'Accept-Language': 'en-US,en;q=0.9',
60
+ 'Accept-Encoding': 'gzip, deflate, br',
61
+ 'Connection': 'keep-alive',
62
+ 'Upgrade-Insecure-Requests': '1',
63
+ };
64
+ }
65
+ /* -------------------------------------------------------------------------- */
66
+ /* HTML helpers */
67
+ /* -------------------------------------------------------------------------- */
68
+ /**
69
+ * Strip HTML tags from a string, collapse whitespace, and trim.
70
+ *
71
+ * @param html - Raw HTML fragment.
72
+ * @returns Plain text content.
73
+ */
74
+ function stripTags(html) {
75
+ return html
76
+ .replace(/<[^>]*>/g, ' ')
77
+ .replace(/&amp;/g, '&')
78
+ .replace(/&lt;/g, '<')
79
+ .replace(/&gt;/g, '>')
80
+ .replace(/&quot;/g, '"')
81
+ .replace(/&#039;/g, "'")
82
+ .replace(/&nbsp;/g, ' ')
83
+ .replace(/\s+/g, ' ')
84
+ .trim();
85
+ }
86
+ /* -------------------------------------------------------------------------- */
87
+ /* LetterboxdMovieTool */
88
+ /* -------------------------------------------------------------------------- */
89
+ /**
90
+ * ITool implementation that looks up movie information on Letterboxd.
91
+ *
92
+ * Supports two execution paths:
93
+ * - **Recipe delegation**: when a `web_scrape_recipe` compatible tool is
94
+ * provided via the constructor, the tool delegates scraping to the
95
+ * web-scraper extension for more robust, tiered fetching.
96
+ * - **Built-in fallback**: a lightweight `fetch()` + regex scraper that
97
+ * works without external dependencies or API keys.
98
+ *
99
+ * @example
100
+ * ```ts
101
+ * const tool = new LetterboxdMovieTool();
102
+ * const result = await tool.execute({ title: 'Arrival', year: 2016 }, ctx);
103
+ * if (result.success && result.output?.found) {
104
+ * console.log(result.output.title, result.output.rating);
105
+ * }
106
+ * ```
107
+ */
108
+ export class LetterboxdMovieTool {
109
+ id = 'letterboxd-movie-v1';
110
+ name = 'letterboxd_movie';
111
+ displayName = 'Letterboxd Movie Lookup';
112
+ description = 'Look up a movie on Letterboxd by title (and optional year). Returns ' +
113
+ 'the film\'s metadata — title, year, aggregate rating, directors, genres, ' +
114
+ 'synopsis, and a sample of recent user reviews. No API key required.';
115
+ category = 'media';
116
+ version = '1.0.0';
117
+ hasSideEffects = false;
118
+ inputSchema = {
119
+ type: 'object',
120
+ properties: {
121
+ title: {
122
+ type: 'string',
123
+ description: 'Film title to search for on Letterboxd.',
124
+ },
125
+ year: {
126
+ type: 'integer',
127
+ description: 'Optional release year to disambiguate titles with the same name.',
128
+ },
129
+ },
130
+ required: ['title'],
131
+ };
132
+ requiredCapabilities = ['capability:media_search'];
133
+ /**
134
+ * Optional reference to a recipe-capable scraping tool.
135
+ * When provided, the tool delegates to the web-scraper extension's
136
+ * recipe engine rather than using the built-in fallback scraper.
137
+ */
138
+ recipeTool;
139
+ /**
140
+ * Create a new LetterboxdMovieTool.
141
+ *
142
+ * @param recipeTool - Optional reference to a `web_scrape_recipe`-compatible
143
+ * tool. When provided, the tool attempts recipe-based
144
+ * scraping before falling back to the built-in scraper.
145
+ */
146
+ constructor(recipeTool) {
147
+ this.recipeTool = recipeTool;
148
+ }
149
+ /**
150
+ * Execute a Letterboxd movie lookup.
151
+ *
152
+ * @param args - The film title (and optional year) to search for.
153
+ * @param context - Tool execution context provided by the AgentOS runtime.
154
+ * @returns A {@link ToolExecutionResult} wrapping a {@link LetterboxdMovieOutput}.
155
+ */
156
+ async execute(args, context) {
157
+ const title = (args.title || '').trim();
158
+ if (!title) {
159
+ return { success: false, error: '`title` must be a non-empty string.' };
160
+ }
161
+ // Strategy 1: delegate to web_scrape_recipe if available
162
+ if (this.recipeTool) {
163
+ try {
164
+ const recipeResult = await this.executeViaRecipe(title, args.year, context);
165
+ if (recipeResult) {
166
+ return recipeResult;
167
+ }
168
+ // Recipe returned null — fall through to built-in scraper
169
+ }
170
+ catch {
171
+ // Recipe failed — fall through to built-in scraper
172
+ }
173
+ }
174
+ // Strategy 2: built-in fetch + regex scraper
175
+ try {
176
+ return await this.executeViaFallback(title, args.year);
177
+ }
178
+ catch (err) {
179
+ const msg = err instanceof Error ? err.message : String(err);
180
+ return { success: false, error: `Letterboxd lookup failed: ${msg}` };
181
+ }
182
+ }
183
+ /**
184
+ * Custom argument validation.
185
+ *
186
+ * @param args - Raw arguments from the LLM.
187
+ * @returns Validation result with error details if invalid.
188
+ */
189
+ validateArgs(args) {
190
+ const errors = [];
191
+ if (!args.title || typeof args.title !== 'string') {
192
+ errors.push('`title` must be a non-empty string.');
193
+ }
194
+ if (args.year !== undefined && (typeof args.year !== 'number' || !Number.isInteger(args.year))) {
195
+ errors.push('`year` must be an integer when provided.');
196
+ }
197
+ return { isValid: errors.length === 0, errors: errors.length > 0 ? errors : undefined };
198
+ }
199
+ /* -------------------------------------------------------------------------- */
200
+ /* Strategy 1: Recipe delegation */
201
+ /* -------------------------------------------------------------------------- */
202
+ /**
203
+ * Attempt to look up the film via the web-scraper recipe engine.
204
+ *
205
+ * Passes the title (and year if provided) to a `letterboxd-movie` recipe.
206
+ * Returns `null` if the recipe is not available or returns no useful data,
207
+ * signalling the caller to fall back to the built-in scraper.
208
+ *
209
+ * @param title - Film title to search for.
210
+ * @param year - Optional release year.
211
+ * @param context - Tool execution context for the delegated call.
212
+ * @returns A {@link ToolExecutionResult} if the recipe succeeded, or `null`
213
+ * to signal fallback.
214
+ */
215
+ async executeViaRecipe(title, year, context) {
216
+ if (!this.recipeTool)
217
+ return null;
218
+ const params = { query: title };
219
+ if (year !== undefined) {
220
+ params.year = String(year);
221
+ }
222
+ const result = await this.recipeTool.execute({ recipe: 'letterboxd-movie', params }, context);
223
+ if (!result.success || !result.output?.data) {
224
+ return null;
225
+ }
226
+ const data = result.output.data;
227
+ // The recipe is expected to return structured data with known keys
228
+ const output = {
229
+ found: true,
230
+ url: data.url,
231
+ title: data.title,
232
+ year: data.year,
233
+ rating: data.rating,
234
+ directors: data.directors,
235
+ genres: data.genres,
236
+ synopsis: data.synopsis,
237
+ reviews: Array.isArray(data.reviews) ? data.reviews : undefined,
238
+ };
239
+ return { success: true, output };
240
+ }
241
+ /* -------------------------------------------------------------------------- */
242
+ /* Strategy 2: Built-in fallback scraper */
243
+ /* -------------------------------------------------------------------------- */
244
+ /**
245
+ * Look up the film using the built-in `fetch()` + regex scraper.
246
+ *
247
+ * Performs up to three HTTP requests:
248
+ * 1. **Search** — `GET /search/films/{encodedTitle}/` to find the film slug.
249
+ * 2. **Direct URL** — if search fails (e.g. Cloudflare 403), tries
250
+ * `GET /film/{slug}/` where the slug is derived from the title.
251
+ * Film detail pages are typically not behind Cloudflare challenges.
252
+ * 3. **Film page** — `GET /film/{slug}/` to extract metadata and reviews.
253
+ *
254
+ * @param title - Film title to search for.
255
+ * @param year - Optional release year for disambiguation.
256
+ * @returns A {@link ToolExecutionResult} wrapping the extracted film data.
257
+ */
258
+ async executeViaFallback(title, year) {
259
+ // Step 1: Search for the film slug
260
+ let slug = await this.searchFilmSlug(title, year);
261
+ // Step 2: If search failed (e.g. Cloudflare 403), try the direct film
262
+ // URL pattern — detail pages are typically not behind Cloudflare.
263
+ if (!slug) {
264
+ const directSlug = this.titleToSlug(title);
265
+ const directUrl = `${FILM_BASE_URL}/${directSlug}/`;
266
+ const directHtml = await this.fetchPage(directUrl);
267
+ if (directHtml) {
268
+ const output = this.parseFilmPage(directHtml, directUrl);
269
+ return { success: true, output };
270
+ }
271
+ return {
272
+ success: true,
273
+ output: { found: false },
274
+ error: 'Search blocked by Cloudflare — tried direct URL lookup',
275
+ };
276
+ }
277
+ // Step 3: Fetch and parse the film page
278
+ const filmUrl = `${FILM_BASE_URL}/${slug}/`;
279
+ const filmHtml = await this.fetchPage(filmUrl);
280
+ if (!filmHtml) {
281
+ return {
282
+ success: true,
283
+ output: { found: false },
284
+ };
285
+ }
286
+ const output = this.parseFilmPage(filmHtml, filmUrl);
287
+ return { success: true, output };
288
+ }
289
+ /**
290
+ * Derive a URL slug from a film title.
291
+ *
292
+ * Lowercases the title, replaces non-alphanumeric runs with hyphens,
293
+ * and strips leading/trailing hyphens.
294
+ *
295
+ * @example
296
+ * ```
297
+ * titleToSlug('The Dark Knight') // => 'the-dark-knight'
298
+ * titleToSlug('Parasite') // => 'parasite'
299
+ * ```
300
+ *
301
+ * @param title - Film title to slugify.
302
+ * @returns A URL-safe slug string.
303
+ */
304
+ titleToSlug(title) {
305
+ return title
306
+ .toLowerCase()
307
+ .replace(/[^a-z0-9]+/g, '-')
308
+ .replace(/^-|-$/g, '');
309
+ }
310
+ /**
311
+ * Search Letterboxd for a film and return the slug of the best match.
312
+ *
313
+ * Parses the search results HTML for film links, optionally filtering
314
+ * by year when provided.
315
+ *
316
+ * @param title - Film title query string.
317
+ * @param year - Optional year to prefer in search results.
318
+ * @returns The film slug (e.g. "arrival-2016") or `null` if not found.
319
+ */
320
+ async searchFilmSlug(title, year) {
321
+ const encoded = encodeURIComponent(title);
322
+ const searchUrl = `${SEARCH_URL}/${encoded}/`;
323
+ const html = await this.fetchPage(searchUrl);
324
+ if (!html)
325
+ return null;
326
+ // Letterboxd search results contain links like /film/{slug}/
327
+ // within list items that also contain the year in a small tag
328
+ const resultPattern = /<a\s+[^>]*href="\/film\/([^/"]+)\/"[^>]*>/gi;
329
+ const slugs = [];
330
+ let match;
331
+ while ((match = resultPattern.exec(html)) !== null) {
332
+ slugs.push({ slug: match[1], index: match.index });
333
+ }
334
+ if (slugs.length === 0)
335
+ return null;
336
+ // If a year is provided, try to find a result whose surrounding
337
+ // HTML context mentions that year
338
+ if (year !== undefined) {
339
+ for (const { slug, index } of slugs) {
340
+ const contextWindow = html.slice(index, index + 500);
341
+ if (contextWindow.includes(String(year))) {
342
+ return slug;
343
+ }
344
+ }
345
+ }
346
+ // Default to first result
347
+ return slugs[0].slug;
348
+ }
349
+ /**
350
+ * Fetch a page from Letterboxd with realistic browser headers.
351
+ *
352
+ * Follows redirects and returns the response body as a string.
353
+ * Returns `null` on non-2xx status codes or network errors.
354
+ *
355
+ * @param url - Fully qualified URL to fetch.
356
+ * @returns HTML body string or `null` on failure.
357
+ */
358
+ async fetchPage(url) {
359
+ try {
360
+ const response = await fetch(url, {
361
+ headers: browserHeaders(),
362
+ redirect: 'follow',
363
+ });
364
+ if (!response.ok)
365
+ return null;
366
+ return await response.text();
367
+ }
368
+ catch {
369
+ return null;
370
+ }
371
+ }
372
+ /**
373
+ * Parse a Letterboxd film page HTML and extract structured metadata.
374
+ *
375
+ * Uses regex patterns to extract:
376
+ * - Title and year from the headline / head section
377
+ * - Aggregate rating from the rating histogram section
378
+ * - Director names from the crew section
379
+ * - Genre tags from the genre tab
380
+ * - Synopsis from the truncated description
381
+ * - A sample of recent user reviews
382
+ *
383
+ * @param html - Raw HTML of the film page.
384
+ * @param filmUrl - Canonical URL for inclusion in the output.
385
+ * @returns A {@link LetterboxdMovieOutput} with extracted data.
386
+ */
387
+ parseFilmPage(html, filmUrl) {
388
+ const output = {
389
+ found: true,
390
+ url: filmUrl,
391
+ };
392
+ // --- Title ---
393
+ // <meta property="og:title" content="Film Title (Year)">
394
+ const ogTitleMatch = html.match(/<meta\s+property="og:title"\s+content="([^"]+)"/i);
395
+ if (ogTitleMatch) {
396
+ const ogTitle = ogTitleMatch[1];
397
+ // Split "Film Title (2016)" or just use raw
398
+ const titleYearMatch = ogTitle.match(/^(.+?)\s*\((\d{4})\)\s*$/);
399
+ if (titleYearMatch) {
400
+ output.title = stripTags(titleYearMatch[1]);
401
+ output.year = titleYearMatch[2];
402
+ }
403
+ else {
404
+ output.title = stripTags(ogTitle);
405
+ }
406
+ }
407
+ // Fallback: <h1 class="headline-1 ...">Title</h1>
408
+ if (!output.title) {
409
+ const h1Match = html.match(/<h1[^>]*class="[^"]*headline[^"]*"[^>]*>([^<]+)<\/h1>/i);
410
+ if (h1Match) {
411
+ output.title = stripTags(h1Match[1]);
412
+ }
413
+ }
414
+ // --- Year (fallback) ---
415
+ // <small class="number"><a href="/films/year/2016/">2016</a></small>
416
+ if (!output.year) {
417
+ const yearMatch = html.match(/<a\s+href="\/films\/year\/(\d{4})\/"[^>]*>\d{4}<\/a>/i);
418
+ if (yearMatch) {
419
+ output.year = yearMatch[1];
420
+ }
421
+ }
422
+ // --- Aggregate rating ---
423
+ // <meta name="twitter:data2" content="X.XX out of 5">
424
+ const ratingMeta = html.match(/<meta\s+name="twitter:data2"\s+content="([\d.]+)\s+out\s+of\s+5"/i);
425
+ if (ratingMeta) {
426
+ output.rating = ratingMeta[1];
427
+ }
428
+ // Fallback: weighted average from histogram or schema.org
429
+ if (!output.rating) {
430
+ // <span class="average-rating">X.X</span>
431
+ const avgMatch = html.match(/<span[^>]*class="[^"]*average-rating[^"]*"[^>]*>([\d.]+)<\/span>/i);
432
+ if (avgMatch) {
433
+ output.rating = avgMatch[1];
434
+ }
435
+ }
436
+ // --- Directors ---
437
+ // <meta name="twitter:data1" content="Director Name, Director Name">
438
+ const directorMeta = html.match(/<meta\s+name="twitter:data1"\s+content="([^"]+)"/i);
439
+ if (directorMeta) {
440
+ output.directors = directorMeta[1];
441
+ }
442
+ // Fallback: <a href="/director/.../">Name</a> within crew block
443
+ if (!output.directors) {
444
+ const directorLinks = [];
445
+ const dirPattern = /<a\s+href="\/director\/[^/"]+\/"[^>]*>([^<]+)<\/a>/gi;
446
+ let dirMatch;
447
+ while ((dirMatch = dirPattern.exec(html)) !== null) {
448
+ const name = stripTags(dirMatch[1]);
449
+ if (name && !directorLinks.includes(name)) {
450
+ directorLinks.push(name);
451
+ }
452
+ }
453
+ if (directorLinks.length > 0) {
454
+ output.directors = directorLinks.join(', ');
455
+ }
456
+ }
457
+ // --- Genres ---
458
+ // <a class="text-slug" href="/films/genre/drama/">Drama</a>
459
+ const genreSet = [];
460
+ const genrePattern = /<a[^>]+href="\/films\/genre\/[^/"]+\/"[^>]*>([^<]+)<\/a>/gi;
461
+ let genreMatch;
462
+ while ((genreMatch = genrePattern.exec(html)) !== null) {
463
+ const genre = stripTags(genreMatch[1]);
464
+ if (genre && !genreSet.includes(genre)) {
465
+ genreSet.push(genre);
466
+ }
467
+ }
468
+ if (genreSet.length > 0) {
469
+ output.genres = genreSet.join(', ');
470
+ }
471
+ // --- Synopsis ---
472
+ // <meta property="og:description" content="...">
473
+ const ogDescMatch = html.match(/<meta\s+property="og:description"\s+content="([^"]+)"/i);
474
+ if (ogDescMatch) {
475
+ output.synopsis = stripTags(ogDescMatch[1]);
476
+ }
477
+ // Fallback: <div class="truncate">...<p>...</p>...</div>
478
+ if (!output.synopsis) {
479
+ const truncateMatch = html.match(/<div[^>]*class="[^"]*truncate[^"]*"[^>]*>([\s\S]*?)<\/div>/i);
480
+ if (truncateMatch) {
481
+ // Extract first <p> inside the truncate div
482
+ const pMatch = truncateMatch[1].match(/<p>([^<]+)<\/p>/i);
483
+ if (pMatch) {
484
+ output.synopsis = stripTags(pMatch[1]);
485
+ }
486
+ }
487
+ }
488
+ // --- Reviews ---
489
+ output.reviews = this.parseReviews(html);
490
+ return output;
491
+ }
492
+ /**
493
+ * Extract a sample of user reviews from a Letterboxd film page.
494
+ *
495
+ * Looks for review blocks in the "popular reviews" or "recent reviews"
496
+ * section and extracts username, star rating, and review text.
497
+ *
498
+ * @param html - Raw HTML of the film page.
499
+ * @returns An array of up to {@link MAX_REVIEWS} parsed reviews.
500
+ */
501
+ parseReviews(html) {
502
+ const reviews = [];
503
+ // Reviews are in <li> blocks within a section that contains
504
+ // class="film-detail-content" or similar review containers.
505
+ // Each review typically has:
506
+ // - <a class="context" href="/USERNAME/...">USERNAME</a>
507
+ // - <span class="rating ...">★★★★</span>
508
+ // - <div class="body-text ..."><p>Review text</p></div>
509
+ // Find review blocks — they contain "attribution" or "film-detail" class
510
+ const reviewBlockPattern = /<li[^>]*class="[^"]*film-detail[^"]*"[^>]*>([\s\S]*?)<\/li>/gi;
511
+ let blockMatch;
512
+ while ((blockMatch = reviewBlockPattern.exec(html)) !== null && reviews.length < MAX_REVIEWS) {
513
+ const block = blockMatch[1];
514
+ // Username
515
+ const userMatch = block.match(/<a[^>]*class="[^"]*context[^"]*"[^>]*href="\/([^/"]+)\/[^"]*"[^>]*>/i);
516
+ // Also try: <strong class="name">username</strong>
517
+ const userFallback = block.match(/<strong[^>]*class="[^"]*name[^"]*"[^>]*>([^<]+)<\/strong>/i);
518
+ const user = userMatch
519
+ ? userMatch[1]
520
+ : userFallback
521
+ ? stripTags(userFallback[1])
522
+ : 'anonymous';
523
+ // Star rating — Letterboxd uses ★ and ½ characters
524
+ const ratingMatch = block.match(/<span[^>]*class="[^"]*rating[^"]*"[^>]*>([★½]+)<\/span>/i);
525
+ const rating = ratingMatch ? ratingMatch[1] : '';
526
+ // Review text
527
+ const bodyMatch = block.match(/<div[^>]*class="[^"]*body-text[^"]*"[^>]*>([\s\S]*?)<\/div>/i);
528
+ const text = bodyMatch ? stripTags(bodyMatch[1]).slice(0, 500) : '';
529
+ if (user !== 'anonymous' || text) {
530
+ reviews.push({ user, rating, text });
531
+ }
532
+ }
533
+ return reviews;
534
+ }
535
+ }
536
+ //# sourceMappingURL=letterboxdMovie.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"letterboxdMovie.js","sourceRoot":"","sources":["../../src/tools/letterboxdMovie.ts"],"names":[],"mappings":"AAAA,cAAc;AACd;;;;;;;;;;;;;;;GAeG;AAyFH,gFAAgF;AAChF,gFAAgF;AAChF,gFAAgF;AAEhF,2CAA2C;AAC3C,MAAM,UAAU,GAAG,qCAAqC,CAAC;AAEzD,0CAA0C;AAC1C,MAAM,aAAa,GAAG,6BAA6B,CAAC;AAEpD,6DAA6D;AAC7D,MAAM,WAAW,GAAG,CAAC,CAAC;AAEtB,gFAAgF;AAChF,gFAAgF;AAChF,gFAAgF;AAEhF;;;;;;;GAOG;AACH,SAAS,kBAAkB;IACzB,MAAM,aAAa,GAAG,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU;IACrE,MAAM,SAAS,GAAG;QAChB,6BAA6B;QAC7B,mCAAmC;QACnC,gCAAgC;QAChC,mBAAmB;KACpB,CAAC;IACF,MAAM,QAAQ,GAAG,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;IACzE,OAAO,CACL,gBAAgB,QAAQ,uBAAuB;QAC/C,8BAA8B,aAAa,sBAAsB,CAClE,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,SAAS,cAAc;IACrB,OAAO;QACL,YAAY,EAAE,kBAAkB,EAAE;QAClC,QAAQ,EAAE,iEAAiE;QAC3E,iBAAiB,EAAE,gBAAgB;QACnC,iBAAiB,EAAE,mBAAmB;QACtC,YAAY,EAAE,YAAY;QAC1B,2BAA2B,EAAE,GAAG;KACjC,CAAC;AACJ,CAAC;AAED,gFAAgF;AAChF,gFAAgF;AAChF,gFAAgF;AAEhF;;;;;GAKG;AACH,SAAS,SAAS,CAAC,IAAY;IAC7B,OAAO,IAAI;SACR,OAAO,CAAC,UAAU,EAAE,GAAG,CAAC;SACxB,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC;SACtB,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC;SACrB,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC;SACrB,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC;SACvB,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC;SACvB,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC;SACvB,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;SACpB,IAAI,EAAE,CAAC;AACZ,CAAC;AAED,gFAAgF;AAChF,gFAAgF;AAChF,gFAAgF;AAEhF;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,OAAO,mBAAmB;IACrB,EAAE,GAAG,qBAAqB,CAAC;IAC3B,IAAI,GAAG,kBAAkB,CAAC;IAC1B,WAAW,GAAG,yBAAyB,CAAC;IACxC,WAAW,GAClB,uEAAuE;QACvE,2EAA2E;QAC3E,sEAAsE,CAAC;IAChE,QAAQ,GAAG,OAAO,CAAC;IACnB,OAAO,GAAG,OAAO,CAAC;IAClB,cAAc,GAAG,KAAK,CAAC;IAEvB,WAAW,GAAqB;QACvC,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE;YACV,KAAK,EAAE;gBACL,IAAI,EAAE,QAAQ;gBACd,WAAW,EAAE,yCAAyC;aACvD;YACD,IAAI,EAAE;gBACJ,IAAI,EAAE,SAAS;gBACf,WAAW,EAAE,kEAAkE;aAChF;SACF;QACD,QAAQ,EAAE,CAAC,OAAO,CAAC;KACpB,CAAC;IAEO,oBAAoB,GAAG,CAAC,yBAAyB,CAAC,CAAC;IAE5D;;;;OAIG;IACc,UAAU,CAA6B;IAExD;;;;;;OAMG;IACH,YAAY,UAA2B;QACrC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC/B,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,OAAO,CACX,IAA0B,EAC1B,OAA6B;QAE7B,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACxC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,qCAAqC,EAAE,CAAC;QAC1E,CAAC;QAED,yDAAyD;QACzD,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,IAAI,CAAC;gBACH,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;gBAC5E,IAAI,YAAY,EAAE,CAAC;oBACjB,OAAO,YAAY,CAAC;gBACtB,CAAC;gBACD,0DAA0D;YAC5D,CAAC;YAAC,MAAM,CAAC;gBACP,mDAAmD;YACrD,CAAC;QACH,CAAC;QAED,6CAA6C;QAC7C,IAAI,CAAC;YACH,OAAO,MAAM,IAAI,CAAC,kBAAkB,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QACzD,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACtB,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC7D,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,6BAA6B,GAAG,EAAE,EAAE,CAAC;QACvE,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACH,YAAY,CAAC,IAA6B;QACxC,MAAM,MAAM,GAAa,EAAE,CAAC;QAE5B,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;YAClD,MAAM,CAAC,IAAI,CAAC,qCAAqC,CAAC,CAAC;QACrD,CAAC;QAED,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;YAC/F,MAAM,CAAC,IAAI,CAAC,0CAA0C,CAAC,CAAC;QAC1D,CAAC;QAED,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC;IAC1F,CAAC;IAED,gFAAgF;IAChF,gFAAgF;IAChF,gFAAgF;IAEhF;;;;;;;;;;;;OAYG;IACK,KAAK,CAAC,gBAAgB,CAC5B,KAAa,EACb,IAAwB,EACxB,OAA6B;QAE7B,IAAI,CAAC,IAAI,CAAC,UAAU;YAAE,OAAO,IAAI,CAAC;QAElC,MAAM,MAAM,GAA2B,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;QACxD,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,MAAM,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;QAC7B,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,CAC1C,EAAE,MAAM,EAAE,kBAAkB,EAAE,MAAM,EAAE,EACtC,OAAO,CACR,CAAC;QAEF,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC;YAC5C,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,IAA2B,CAAC;QAEvD,mEAAmE;QACnE,MAAM,MAAM,GAA0B;YACpC,KAAK,EAAE,IAAI;YACX,GAAG,EAAE,IAAI,CAAC,GAAyB;YACnC,KAAK,EAAE,IAAI,CAAC,KAA2B;YACvC,IAAI,EAAE,IAAI,CAAC,IAA0B;YACrC,MAAM,EAAE,IAAI,CAAC,MAA4B;YACzC,SAAS,EAAE,IAAI,CAAC,SAA+B;YAC/C,MAAM,EAAE,IAAI,CAAC,MAA4B;YACzC,QAAQ,EAAE,IAAI,CAAC,QAA8B;YAC7C,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;SAChE,CAAC;QAEF,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IACnC,CAAC;IAED,gFAAgF;IAChF,gFAAgF;IAChF,gFAAgF;IAEhF;;;;;;;;;;;;;OAaG;IACK,KAAK,CAAC,kBAAkB,CAC9B,KAAa,EACb,IAAa;QAEb,mCAAmC;QACnC,IAAI,IAAI,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAElD,sEAAsE;QACtE,kEAAkE;QAClE,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;YAC3C,MAAM,SAAS,GAAG,GAAG,aAAa,IAAI,UAAU,GAAG,CAAC;YACpD,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;YACnD,IAAI,UAAU,EAAE,CAAC;gBACf,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;gBACzD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;YACnC,CAAC;YAED,OAAO;gBACL,OAAO,EAAE,IAAI;gBACb,MAAM,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE;gBACxB,KAAK,EAAE,wDAAwD;aAChE,CAAC;QACJ,CAAC;QAED,wCAAwC;QACxC,MAAM,OAAO,GAAG,GAAG,aAAa,IAAI,IAAI,GAAG,CAAC;QAC5C,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QAC/C,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,OAAO;gBACL,OAAO,EAAE,IAAI;gBACb,MAAM,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE;aACzB,CAAC;QACJ,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACrD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IACnC,CAAC;IAED;;;;;;;;;;;;;;OAcG;IACK,WAAW,CAAC,KAAa;QAC/B,OAAO,KAAK;aACT,WAAW,EAAE;aACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;aAC3B,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAC3B,CAAC;IAED;;;;;;;;;OASG;IACK,KAAK,CAAC,cAAc,CAAC,KAAa,EAAE,IAAa;QACvD,MAAM,OAAO,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;QAC1C,MAAM,SAAS,GAAG,GAAG,UAAU,IAAI,OAAO,GAAG,CAAC;QAC9C,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;QAC7C,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC;QAEvB,6DAA6D;QAC7D,8DAA8D;QAC9D,MAAM,aAAa,GAAG,6CAA6C,CAAC;QACpE,MAAM,KAAK,GAA2C,EAAE,CAAC;QACzD,IAAI,KAA6B,CAAC;QAElC,OAAO,CAAC,KAAK,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;YACnD,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAE,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;QACtD,CAAC;QAED,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAEpC,gEAAgE;QAChE,kCAAkC;QAClC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,KAAK,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,KAAK,EAAE,CAAC;gBACpC,MAAM,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,GAAG,CAAC,CAAC;gBACrD,IAAI,aAAa,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;oBACzC,OAAO,IAAI,CAAC;gBACd,CAAC;YACH,CAAC;QACH,CAAC;QAED,0BAA0B;QAC1B,OAAO,KAAK,CAAC,CAAC,CAAE,CAAC,IAAI,CAAC;IACxB,CAAC;IAED;;;;;;;;OAQG;IACK,KAAK,CAAC,SAAS,CAAC,GAAW;QACjC,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;gBAChC,OAAO,EAAE,cAAc,EAAE;gBACzB,QAAQ,EAAE,QAAQ;aACnB,CAAC,CAAC;YAEH,IAAI,CAAC,QAAQ,CAAC,EAAE;gBAAE,OAAO,IAAI,CAAC;YAC9B,OAAO,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QAC/B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED;;;;;;;;;;;;;;OAcG;IACK,aAAa,CAAC,IAAY,EAAE,OAAe;QACjD,MAAM,MAAM,GAA0B;YACpC,KAAK,EAAE,IAAI;YACX,GAAG,EAAE,OAAO;SACb,CAAC;QAEF,gBAAgB;QAChB,yDAAyD;QACzD,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,kDAAkD,CAAC,CAAC;QACpF,IAAI,YAAY,EAAE,CAAC;YACjB,MAAM,OAAO,GAAG,YAAY,CAAC,CAAC,CAAE,CAAC;YACjC,4CAA4C;YAC5C,MAAM,cAAc,GAAG,OAAO,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC;YACjE,IAAI,cAAc,EAAE,CAAC;gBACnB,MAAM,CAAC,KAAK,GAAG,SAAS,CAAC,cAAc,CAAC,CAAC,CAAE,CAAC,CAAC;gBAC7C,MAAM,CAAC,IAAI,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC;YAClC,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,KAAK,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;YACpC,CAAC;QACH,CAAC;QAED,kDAAkD;QAClD,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YAClB,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,wDAAwD,CAAC,CAAC;YACrF,IAAI,OAAO,EAAE,CAAC;gBACZ,MAAM,CAAC,KAAK,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC,CAAE,CAAC,CAAC;YACxC,CAAC;QACH,CAAC;QAED,0BAA0B;QAC1B,qEAAqE;QACrE,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YACjB,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,uDAAuD,CAAC,CAAC;YACtF,IAAI,SAAS,EAAE,CAAC;gBACd,MAAM,CAAC,IAAI,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;YAC7B,CAAC;QACH,CAAC;QAED,2BAA2B;QAC3B,sDAAsD;QACtD,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,mEAAmE,CAAC,CAAC;QACnG,IAAI,UAAU,EAAE,CAAC;YACf,MAAM,CAAC,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;QAChC,CAAC;QAED,0DAA0D;QAC1D,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YACnB,0CAA0C;YAC1C,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,mEAAmE,CAAC,CAAC;YACjG,IAAI,QAAQ,EAAE,CAAC;gBACb,MAAM,CAAC,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;YAC9B,CAAC;QACH,CAAC;QAED,oBAAoB;QACpB,qEAAqE;QACrE,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,mDAAmD,CAAC,CAAC;QACrF,IAAI,YAAY,EAAE,CAAC;YACjB,MAAM,CAAC,SAAS,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC;QACrC,CAAC;QAED,gEAAgE;QAChE,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;YACtB,MAAM,aAAa,GAAa,EAAE,CAAC;YACnC,MAAM,UAAU,GAAG,sDAAsD,CAAC;YAC1E,IAAI,QAAgC,CAAC;YACrC,OAAO,CAAC,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;gBACnD,MAAM,IAAI,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAE,CAAC,CAAC;gBACrC,IAAI,IAAI,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;oBAC1C,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAC3B,CAAC;YACH,CAAC;YACD,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC7B,MAAM,CAAC,SAAS,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC9C,CAAC;QACH,CAAC;QAED,iBAAiB;QACjB,4DAA4D;QAC5D,MAAM,QAAQ,GAAa,EAAE,CAAC;QAC9B,MAAM,YAAY,GAAG,4DAA4D,CAAC;QAClF,IAAI,UAAkC,CAAC;QACvC,OAAO,CAAC,UAAU,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;YACvD,MAAM,KAAK,GAAG,SAAS,CAAC,UAAU,CAAC,CAAC,CAAE,CAAC,CAAC;YACxC,IAAI,KAAK,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;gBACvC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACvB,CAAC;QACH,CAAC;QACD,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxB,MAAM,CAAC,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtC,CAAC;QAED,mBAAmB;QACnB,iDAAiD;QACjD,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,wDAAwD,CAAC,CAAC;QACzF,IAAI,WAAW,EAAE,CAAC;YAChB,MAAM,CAAC,QAAQ,GAAG,SAAS,CAAC,WAAW,CAAC,CAAC,CAAE,CAAC,CAAC;QAC/C,CAAC;QAED,yDAAyD;QACzD,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;YACrB,MAAM,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,6DAA6D,CAAC,CAAC;YAChG,IAAI,aAAa,EAAE,CAAC;gBAClB,4CAA4C;gBAC5C,MAAM,MAAM,GAAG,aAAa,CAAC,CAAC,CAAE,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC;gBAC3D,IAAI,MAAM,EAAE,CAAC;oBACX,MAAM,CAAC,QAAQ,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,CAAC;gBAC1C,CAAC;YACH,CAAC;QACH,CAAC;QAED,kBAAkB;QAClB,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;QAEzC,OAAO,MAAM,CAAC;IAChB,CAAC;IAED;;;;;;;;OAQG;IACK,YAAY,CAAC,IAAY;QAC/B,MAAM,OAAO,GAAuB,EAAE,CAAC;QAEvC,4DAA4D;QAC5D,4DAA4D;QAC5D,6BAA6B;QAC7B,2DAA2D;QAC3D,2CAA2C;QAC3C,0DAA0D;QAE1D,yEAAyE;QACzE,MAAM,kBAAkB,GACtB,+DAA+D,CAAC;QAElE,IAAI,UAAkC,CAAC;QACvC,OAAO,CAAC,UAAU,GAAG,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,IAAI,OAAO,CAAC,MAAM,GAAG,WAAW,EAAE,CAAC;YAC7F,MAAM,KAAK,GAAG,UAAU,CAAC,CAAC,CAAE,CAAC;YAE7B,WAAW;YACX,MAAM,SAAS,GAAG,KAAK,CAAC,KAAK,CAC3B,sEAAsE,CACvE,CAAC;YACF,mDAAmD;YACnD,MAAM,YAAY,GAAG,KAAK,CAAC,KAAK,CAAC,4DAA4D,CAAC,CAAC;YAC/F,MAAM,IAAI,GAAG,SAAS;gBACpB,CAAC,CAAC,SAAS,CAAC,CAAC,CAAE;gBACf,CAAC,CAAC,YAAY;oBACZ,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC,CAAE,CAAC;oBAC7B,CAAC,CAAC,WAAW,CAAC;YAElB,mDAAmD;YACnD,MAAM,WAAW,GAAG,KAAK,CAAC,KAAK,CAAC,0DAA0D,CAAC,CAAC;YAC5F,MAAM,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAElD,cAAc;YACd,MAAM,SAAS,GAAG,KAAK,CAAC,KAAK,CAC3B,8DAA8D,CAC/D,CAAC;YACF,MAAM,IAAI,GAAG,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAErE,IAAI,IAAI,KAAK,WAAW,IAAI,IAAI,EAAE,CAAC;gBACjC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;YACvC,CAAC;QACH,CAAC;QAED,OAAO,OAAO,CAAC;IACjB,CAAC;CACF"}
package/manifest.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "$schema": "https://agentos.sh/schemas/extension-manifest-v1.json",
3
+ "id": "com.framers.media.letterboxd",
4
+ "name": "Letterboxd Movie Lookup Extension",
5
+ "version": "1.0.0",
6
+ "description": "Look up movie metadata, ratings, and reviews from Letterboxd via scraping — no API key required",
7
+ "author": {
8
+ "name": "Frame.dev",
9
+ "email": "support@frame.dev",
10
+ "url": "https://github.com/framersai"
11
+ },
12
+ "license": "MIT",
13
+ "keywords": ["letterboxd", "movie", "film", "review", "media", "scraper"],
14
+ "agentosVersion": "^2.0.0",
15
+ "categories": ["media", "research"],
16
+ "extensions": [
17
+ {
18
+ "kind": "tool",
19
+ "id": "letterboxd_movie",
20
+ "displayName": "Letterboxd Movie Lookup",
21
+ "description": "Look up movie metadata, ratings, and reviews from Letterboxd",
22
+ "entry": "./dist/tools/letterboxdMovie.js"
23
+ }
24
+ ],
25
+ "configuration": {}
26
+ }
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@framers/agentos-ext-letterboxd",
3
+ "version": "1.0.0",
4
+ "description": "Letterboxd movie lookup tool for AgentOS — scrapes film metadata, ratings, and reviews",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "type": "module",
8
+ "sideEffects": false,
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ },
14
+ "./manifest.json": "./manifest.json",
15
+ "./package.json": "./package.json"
16
+ },
17
+ "keywords": [
18
+ "agentos",
19
+ "extension",
20
+ "letterboxd",
21
+ "movie",
22
+ "film",
23
+ "media",
24
+ "scraper"
25
+ ],
26
+ "author": {
27
+ "name": "Framers AI",
28
+ "email": "team@frame.dev",
29
+ "url": "https://frame.dev"
30
+ },
31
+ "contributors": [
32
+ {
33
+ "name": "Johnny Dunn",
34
+ "email": "johnnyfived@protonmail.com",
35
+ "url": "https://github.com/jddunn"
36
+ }
37
+ ],
38
+ "license": "MIT",
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "https://github.com/framersai/agentos-extensions.git",
42
+ "directory": "registry/curated/media/letterboxd"
43
+ },
44
+ "publishConfig": {
45
+ "access": "public"
46
+ },
47
+ "peerDependencies": {
48
+ "@framers/agentos": "^0.5.2"
49
+ },
50
+ "devDependencies": {
51
+ "@framers/agentos": "^0.6.6",
52
+ "@types/node": "^20.12.12",
53
+ "@vitest/coverage-v8": "^1.6.0",
54
+ "rimraf": "^5.0.7",
55
+ "typescript": "^5.4.5",
56
+ "vitest": "^1.6.0"
57
+ },
58
+ "scripts": {
59
+ "build": "tsc",
60
+ "test": "vitest run",
61
+ "test:watch": "vitest",
62
+ "test:coverage": "vitest run --coverage",
63
+ "lint": "eslint src --ext .ts",
64
+ "typecheck": "tsc --noEmit",
65
+ "clean": "rimraf dist"
66
+ }
67
+ }