rewind-mcp-server 0.1.0 → 0.3.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 (54) hide show
  1. package/README.md +77 -90
  2. package/dist/__tests__/helpers.test.d.ts +1 -0
  3. package/dist/__tests__/helpers.test.js +131 -0
  4. package/dist/__tests__/helpers.test.js.map +1 -0
  5. package/dist/__tests__/image-block.live.d.ts +1 -0
  6. package/dist/__tests__/image-block.live.js +41 -0
  7. package/dist/__tests__/image-block.live.js.map +1 -0
  8. package/dist/__tests__/server.test.d.ts +1 -0
  9. package/dist/__tests__/server.test.js +1308 -0
  10. package/dist/__tests__/server.test.js.map +1 -0
  11. package/dist/__tests__/stdio-e2e.live.d.ts +1 -0
  12. package/dist/__tests__/stdio-e2e.live.js +60 -0
  13. package/dist/__tests__/stdio-e2e.live.js.map +1 -0
  14. package/dist/__tests__/ui-resource.live.d.ts +1 -0
  15. package/dist/__tests__/ui-resource.live.js +71 -0
  16. package/dist/__tests__/ui-resource.live.js.map +1 -0
  17. package/dist/client.d.ts +9 -0
  18. package/dist/client.js +15 -0
  19. package/dist/client.js.map +1 -1
  20. package/dist/index.js +0 -0
  21. package/dist/prompts.js +101 -0
  22. package/dist/prompts.js.map +1 -1
  23. package/dist/resources/ui.d.ts +33 -0
  24. package/dist/resources/ui.js +14 -0
  25. package/dist/resources/ui.js.map +1 -0
  26. package/dist/resources.js +128 -0
  27. package/dist/resources.js.map +1 -1
  28. package/dist/server.js +55 -1
  29. package/dist/server.js.map +1 -1
  30. package/dist/tools/collecting.js +116 -14
  31. package/dist/tools/collecting.js.map +1 -1
  32. package/dist/tools/cross-domain.js +74 -17
  33. package/dist/tools/cross-domain.js.map +1 -1
  34. package/dist/tools/debug.d.ts +6 -0
  35. package/dist/tools/debug.js +26 -0
  36. package/dist/tools/debug.js.map +1 -0
  37. package/dist/tools/helpers.d.ts +81 -5
  38. package/dist/tools/helpers.js +138 -4
  39. package/dist/tools/helpers.js.map +1 -1
  40. package/dist/tools/listening.js +223 -52
  41. package/dist/tools/listening.js.map +1 -1
  42. package/dist/tools/reading.js +74 -22
  43. package/dist/tools/reading.js.map +1 -1
  44. package/dist/tools/running.js +87 -43
  45. package/dist/tools/running.js.map +1 -1
  46. package/dist/tools/watching.js +199 -32
  47. package/dist/tools/watching.js.map +1 -1
  48. package/dist/ui-bundles.d.ts +3 -0
  49. package/dist/ui-bundles.js +8 -0
  50. package/dist/ui-bundles.js.map +1 -0
  51. package/dist/worker.d.ts +2 -1
  52. package/dist/worker.js +55 -30
  53. package/dist/worker.js.map +1 -1
  54. package/package.json +18 -3
package/README.md CHANGED
@@ -1,19 +1,17 @@
1
1
  # rewind-mcp-server
2
2
 
3
- MCP (Model Context Protocol) server for the [Rewind](https://rewind.rest) personal data API. Gives Claude natural language access to your listening, running, watching, collecting, and reading data.
3
+ [![CI](https://github.com/pdugan20/rewind/actions/workflows/mcp-server.yml/badge.svg)](https://github.com/pdugan20/rewind/actions/workflows/mcp-server.yml)
4
+ [![npm version](https://img.shields.io/npm/v/rewind-mcp-server?logo=npm)](https://www.npmjs.com/package/rewind-mcp-server)
5
+ [![docs](https://img.shields.io/badge/docs-docs.rewind.rest-blue)](https://docs.rewind.rest/mcp-server)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow?logo=opensourceinitiative&logoColor=white)](https://opensource.org/licenses/MIT)
4
7
 
5
- ## Features
8
+ MCP server for the [Rewind](https://rewind.rest) personal data API. Gives AI assistants access to your listening, running, watching, collecting, and reading data.
6
9
 
7
- - **26 tools** across 5 domains (listening, running, watching, collecting, reading) + cross-domain search and feed
8
- - **4 resources** for sync status and year-in-review data
9
- - **3 prompt templates** for weekly summaries, year-in-review, and period comparisons
10
- - Works with Claude Desktop, Claude Code, Claude iOS, and claude.ai
10
+ ## Setup
11
11
 
12
- ## Installation
12
+ ### Desktop Apps
13
13
 
14
- ### Claude Desktop (Mac)
15
-
16
- Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
14
+ Add to your MCP client config (Claude Desktop, ChatGPT, Gemini, etc.):
17
15
 
18
16
  ```json
19
17
  {
@@ -29,112 +27,101 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
29
27
  }
30
28
  ```
31
29
 
32
- Restart Claude Desktop after saving.
30
+ ### Mobile & Web
31
+
32
+ Add as a remote integration in your client's settings:
33
+
34
+ - **URL**: `https://mcp.rewind.rest/mcp`
35
+ - **Authorization**: `Bearer rw_live_your_key_here`
33
36
 
34
- ### Claude Code
37
+ <details>
38
+ <summary>Claude Code</summary>
35
39
 
36
40
  ```bash
37
41
  claude mcp add rewind -- npx -y rewind-mcp-server
38
42
  ```
39
43
 
40
- Or add `.mcp.json` to your project root:
44
+ </details>
41
45
 
42
- ```json
43
- {
44
- "mcpServers": {
45
- "rewind": {
46
- "command": "npx",
47
- "args": ["-y", "rewind-mcp-server"],
48
- "env": {
49
- "REWIND_API_KEY": "rw_live_your_key_here"
50
- }
51
- }
52
- }
53
- }
54
- ```
46
+ Requires a [Rewind API key](https://docs.rewind.rest/authentication). `REWIND_API_URL` defaults to `https://api.rewind.rest`.
55
47
 
56
- ### Claude iOS / claude.ai (Web)
48
+ ## Tools
57
49
 
58
- These platforms connect to the hosted remote server. Go to Settings > Integrations and add:
50
+ | Domain | Source | Tools |
51
+ | ---------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
52
+ | **Listening** | Last.fm | Now playing, recent scrobbles, stats, top artists/albums/tracks, streaks, artist and album details, genre breakdown over time |
53
+ | **Running** | Strava | Stats, recent runs, personal records, streaks, activity details, per-mile splits, per-year summaries |
54
+ | **Watching** | Plex, Letterboxd | Recent watches, movie details, browse by genre/decade/director, stats, genre/decade/director breakdowns |
55
+ | **Collecting** | Discogs, Trakt | Vinyl collection, physical media (Blu-ray/4K UHD/HD DVD), collection and media stats |
56
+ | **Reading** | Instapaper | Recent articles, highlights, random highlight, stats |
57
+ | **Cross-domain** | All | Full-text search, unified feed, on-this-day |
59
58
 
60
- - **URL:** `https://mcp.rewind.rest/mcp`
61
- - **Authorization:** `Bearer rw_live_your_key_here`
59
+ ## Rich responses
62
60
 
63
- ## Environment Variables
61
+ Tool responses follow the MCP 2025-06-18 content model and include more than plain text:
64
62
 
65
- | Variable | Required | Default | Description |
66
- | ---------------- | -------- | ------------------------- | ----------------------------------- |
67
- | `REWIND_API_KEY` | Yes | -- | Your Rewind API key (`rw_live_...`) |
68
- | `REWIND_API_URL` | No | `https://api.rewind.rest` | API base URL |
63
+ - **Images.** Detail tools (`get_movie_details`, `get_album_details`, `get_artist_details`) and list tools (`get_recent_watches`, `get_recent_listens`, `get_top_albums`, `get_vinyl_collection`, etc.) return cover art / posters / artist imagery as `image` content blocks (top-N on lists, default N=5). Pass `include_images: false` to skip them when keeping responses compact matters.
64
+ - **Resource links.** External platform URLs come back as `resource_link` content blocks rather than being baked into prose -- Letterboxd reviews, Strava activities, Discogs releases, Apple Music pages, original article URLs, Last.fm pages.
65
+ - **Structured content.** Every tool also returns `structuredContent` with a JSON shape that mirrors the underlying API response, so the model can reason over exact numbers without re-parsing prose.
69
66
 
70
- ## Available Tools
67
+ ## Entity resources
71
68
 
72
- ### Listening (Last.fm)
69
+ The server exposes `@`-mentionable resources for fetching full detail on any entity:
73
70
 
74
- | Tool | Description |
75
- | ----------------------- | ---------------------------- |
76
- | `get_now_playing` | Currently playing track |
77
- | `get_recent_listens` | Recent scrobbles |
78
- | `get_listening_stats` | Overall listening statistics |
79
- | `get_top_artists` | Top artists by period |
80
- | `get_top_albums` | Top albums by period |
81
- | `get_top_tracks` | Top tracks by period |
82
- | `get_listening_streaks` | Current and longest streaks |
83
- | `get_artist_details` | Artist info by ID |
84
- | `get_album_details` | Album info by ID |
71
+ | Entity | URI | Source endpoint |
72
+ | -------------- | -------------------------------- | --------------------------------- |
73
+ | Movie | `rewind://movie/{id}` | `/v1/watching/movies/{id}` |
74
+ | Show | `rewind://show/{id}` | `/v1/watching/shows/{id}` |
75
+ | Album | `rewind://album/{id}` | `/v1/listening/albums/{id}` |
76
+ | Artist | `rewind://artist/{id}` | `/v1/listening/artists/{id}` |
77
+ | Vinyl | `rewind://vinyl/{id}` | `/v1/collecting/vinyl/{id}` |
78
+ | Physical media | `rewind://physical-media/{id}` | `/v1/collecting/media/{id}` |
79
+ | Article | `rewind://article/{id}` | `/v1/reading/articles/{id}` |
80
+ | Activity | `rewind://activity/{id}` | `/v1/running/activities/{id}` |
81
+ | Sync status | `rewind://sync/status` | `/v1/health/sync` |
82
+ | Year in review | `rewind://{domain}/year/{year}` | `/v1/{domain}/year/{year}` |
85
83
 
86
- ### Running (Strava)
84
+ `search` returns `resource_link`s pointing at these URIs so clients can drill from a match straight into the full record.
87
85
 
88
- | Tool | Description |
89
- | ---------------------- | --------------------------- |
90
- | `get_running_stats` | Overall running statistics |
91
- | `get_recent_runs` | Recent activities |
92
- | `get_personal_records` | PRs at standard distances |
93
- | `get_running_streaks` | Current and longest streaks |
94
- | `get_activity_details` | Single run details by ID |
86
+ ## Interactive UI (MCP Apps)
95
87
 
96
- ### Watching (Plex / Letterboxd)
88
+ `get_recent_watches` advertises a [MCP Apps](https://blog.modelcontextprotocol.io/posts/2026-01-26-mcp-apps/) UI resource. On clients that render MCP Apps (Claude Desktop, Claude web, VS Code GitHub Copilot, Goose) the tool returns an interactive poster grid inline in the conversation: thumbhash placeholders, theme-aware cards, clickable Letterboxd reviews, hover states. On clients that do not render MCP Apps the existing text + image + resource_link + structuredContent response is unchanged, so no caller regresses.
97
89
 
98
- | Tool | Description |
99
- | -------------------- | --------------------------- |
100
- | `get_recent_watches` | Recently watched movies/TV |
101
- | `get_movie_details` | Movie info by ID |
102
- | `get_watching_stats` | Overall watching statistics |
90
+ ## Example Queries
103
91
 
104
- ### Collecting (Discogs)
92
+ - "What albums have I been listening to the most recently?"
93
+ - "Compare my mile splits from this month vs last month"
94
+ - "When was the last time I watched a film by Wes Anderson?"
95
+ - "What Beastie Boys records are missing from my vinyl collection?"
96
+ - "How many articles did I read last year and stack-rank the top 10 sources"
97
+ - "Can you give me a quick summary of everything I did last week?"
105
98
 
106
- | Tool | Description |
107
- | ---------------------- | --------------------- |
108
- | `get_vinyl_collection` | Browse vinyl records |
109
- | `get_collecting_stats` | Collection statistics |
99
+ ## Prompts
110
100
 
111
- ### Reading (Instapaper)
101
+ The server exposes slash-command prompts that orchestrate multiple tools for common asks:
112
102
 
113
- | Tool | Description |
114
- | ------------------------ | ----------------------- |
115
- | `get_recent_reads` | Recently saved articles |
116
- | `get_reading_highlights` | Saved highlights |
117
- | `get_random_highlight` | Random highlight |
118
- | `get_reading_stats` | Reading statistics |
103
+ - `weekly-summary` -- rolls up activity across all domains for the past 7 days
104
+ - `year-in-review` -- comprehensive yearly recap
105
+ - `compare-periods` -- parametric comparison between two time windows in a single domain
106
+ - `letterboxd-review-draft` -- drafts a Letterboxd-style review for your most recent unrated film
107
+ - `training-report` -- coach-style running report for the last 7-14 days
108
+ - `film-diet` -- portrait of your film-watching taste (genre mix, decades, directors)
119
109
 
120
- ### Cross-Domain
110
+ ## Authentication
121
111
 
122
- | Tool | Description |
123
- | ----------------- | ----------------------------------- |
124
- | `search` | Full-text search across all domains |
125
- | `get_feed` | Unified activity feed |
126
- | `get_on_this_day` | Historical "on this day" items |
127
- | `get_health` | API and sync health status |
112
+ **Desktop apps** use the `REWIND_API_KEY` environment variable passed directly to the server process. The server authenticates with the Rewind API using this key as a Bearer token.
128
113
 
129
- ## Development
114
+ **Mobile and web clients** connect to the remote server at `mcp.rewind.rest`, which uses OAuth 2.1 with GitHub as the identity provider. The flow:
130
115
 
131
- ```bash
132
- cd mcp-server
133
- npm install
134
- npm run build
135
- REWIND_API_KEY=rw_live_... npm start
136
- ```
116
+ 1. Client discovers endpoints via `/.well-known/oauth-authorization-server`
117
+ 2. Client dynamically registers via `/register`
118
+ 3. User authenticates with GitHub and approves access
119
+ 4. Client receives scoped tokens (1h access, 90d refresh, PKCE S256)
120
+
121
+ The server has **read-only access** to your data. No write or admin operations are exposed.
137
122
 
138
- ## License
123
+ ## Privacy & Support
139
124
 
140
- MIT
125
+ - [Privacy Policy](https://rewind.rest/privacy)
126
+ - [Documentation](https://docs.rewind.rest/mcp-server)
127
+ - [Report an issue](https://github.com/pdugan20/rewind/issues)
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,131 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { text, resourceLink, imageBlock, withRichResponse, withErrorHandling, } from '../tools/helpers.js';
3
+ import { RewindClient } from '../client.js';
4
+ describe('helpers', () => {
5
+ describe('text()', () => {
6
+ it('wraps a string in a text content block', () => {
7
+ expect(text('hello')).toEqual({ type: 'text', text: 'hello' });
8
+ });
9
+ it('accepts an empty string', () => {
10
+ expect(text('')).toEqual({ type: 'text', text: '' });
11
+ });
12
+ });
13
+ describe('resourceLink()', () => {
14
+ it('builds a resource_link block for a valid URI', () => {
15
+ const block = resourceLink('https://letterboxd.com/u/film/', 'Letterboxd review');
16
+ expect(block).toEqual({
17
+ type: 'resource_link',
18
+ uri: 'https://letterboxd.com/u/film/',
19
+ name: 'Letterboxd review',
20
+ });
21
+ });
22
+ it('includes optional mimeType and description', () => {
23
+ const block = resourceLink('https://example.com', 'Example', {
24
+ mimeType: 'text/html',
25
+ description: 'An example site',
26
+ });
27
+ expect(block).toMatchObject({
28
+ type: 'resource_link',
29
+ uri: 'https://example.com',
30
+ name: 'Example',
31
+ mimeType: 'text/html',
32
+ description: 'An example site',
33
+ });
34
+ });
35
+ it('returns null when uri is null/undefined/empty', () => {
36
+ expect(resourceLink(null, 'x')).toBeNull();
37
+ expect(resourceLink(undefined, 'x')).toBeNull();
38
+ expect(resourceLink('', 'x')).toBeNull();
39
+ });
40
+ });
41
+ describe('imageBlock()', () => {
42
+ const bytes = new Uint8Array([0xff, 0xd8, 0xff, 0xe0]); // JPEG magic
43
+ it('base64-encodes bytes fetched from image.cdn_url', async () => {
44
+ const client = new RewindClient('https://api.test', 'rw_test');
45
+ const spy = vi.spyOn(client, 'getBinaryFromUrl').mockResolvedValue({
46
+ bytes,
47
+ mimeType: 'image/jpeg',
48
+ });
49
+ const block = await imageBlock(client, {
50
+ cdn_url: 'https://cdn.rewind.rest/watching/movies/1/original.jpg',
51
+ });
52
+ expect(block).not.toBeNull();
53
+ expect(block?.type).toBe('image');
54
+ expect(block?.mimeType).toBe('image/jpeg');
55
+ expect(block?.data).toBe('/9j/4A==');
56
+ expect(spy).toHaveBeenCalledWith('https://cdn.rewind.rest/watching/movies/1/original.jpg');
57
+ });
58
+ it('falls back to image.url when cdn_url is absent', async () => {
59
+ const client = new RewindClient('https://api.test', 'rw_test');
60
+ const spy = vi.spyOn(client, 'getBinaryFromUrl').mockResolvedValue({
61
+ bytes,
62
+ mimeType: 'image/jpeg',
63
+ });
64
+ await imageBlock(client, { url: 'https://cdn.example/x.jpg' });
65
+ expect(spy).toHaveBeenCalledWith('https://cdn.example/x.jpg');
66
+ });
67
+ it('returns null when image is null or undefined', async () => {
68
+ const client = new RewindClient('https://api.test', 'rw_test');
69
+ expect(await imageBlock(client, null)).toBeNull();
70
+ expect(await imageBlock(client, undefined)).toBeNull();
71
+ });
72
+ it('returns null when image has no cdn_url or url', async () => {
73
+ const client = new RewindClient('https://api.test', 'rw_test');
74
+ expect(await imageBlock(client, { thumbhash: 'x' })).toBeNull();
75
+ });
76
+ it('returns null on fetch failure instead of throwing', async () => {
77
+ const client = new RewindClient('https://api.test', 'rw_test');
78
+ vi.spyOn(client, 'getBinaryFromUrl').mockRejectedValue(new Error('network down'));
79
+ const block = await imageBlock(client, {
80
+ cdn_url: 'https://cdn.example/x.jpg',
81
+ });
82
+ expect(block).toBeNull();
83
+ });
84
+ });
85
+ describe('withRichResponse()', () => {
86
+ it('returns the handler result on success', async () => {
87
+ const result = await withRichResponse(async () => ({
88
+ content: [text('ok')],
89
+ structuredContent: { value: 42 },
90
+ }));
91
+ expect(result).toEqual({
92
+ content: [{ type: 'text', text: 'ok' }],
93
+ structuredContent: { value: 42 },
94
+ });
95
+ });
96
+ it('catches exceptions and returns isError=true', async () => {
97
+ const result = await withRichResponse(async () => {
98
+ throw new Error('boom');
99
+ });
100
+ expect(result.isError).toBe(true);
101
+ expect(result.content).toHaveLength(1);
102
+ expect(result.content[0]).toEqual({
103
+ type: 'text',
104
+ text: 'Error: boom',
105
+ });
106
+ });
107
+ it('handles non-Error throwables', async () => {
108
+ const result = await withRichResponse(async () => {
109
+ throw 'string error';
110
+ });
111
+ expect(result.isError).toBe(true);
112
+ expect(result.content[0].text).toBe('Error: string error');
113
+ });
114
+ });
115
+ describe('withErrorHandling() (legacy text-only wrapper)', () => {
116
+ it('still works for text-only tool handlers', async () => {
117
+ const result = await withErrorHandling(async () => 'plain text');
118
+ expect(result).toEqual({
119
+ content: [{ type: 'text', text: 'plain text' }],
120
+ });
121
+ });
122
+ it('catches and reports errors', async () => {
123
+ const result = await withErrorHandling(async () => {
124
+ throw new Error('nope');
125
+ });
126
+ expect(result.isError).toBe(true);
127
+ expect(result.content[0].text).toBe('Error: nope');
128
+ });
129
+ });
130
+ });
131
+ //# sourceMappingURL=helpers.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.test.js","sourceRoot":"","sources":["../../src/__tests__/helpers.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Live diagnostic: does imageBlock() actually produce a valid image
3
+ * content block when run against the real Rewind CDN? No mocks.
4
+ *
5
+ * Run: npx tsx src/__tests__/image-block.live.ts
6
+ */
7
+ import { RewindClient } from '../client.js';
8
+ import { imageBlock } from '../tools/helpers.js';
9
+ const FIXTURE = {
10
+ movieId: 710,
11
+ title: "I'm Chevy Chase and You're Not",
12
+ cdn_url: 'https://cdn.rewind.rest/watching/movies/710/original.jpg?width=300&height=300&fit=cover&format=auto&quality=85&v=1',
13
+ };
14
+ async function main() {
15
+ // Base URL + api key are irrelevant here -- imageBlock goes through
16
+ // getBinaryFromUrl which hits the full CDN URL without auth.
17
+ const client = new RewindClient('https://api.rewind.rest', 'rw_unused');
18
+ console.log(`Testing imageBlock against live CDN...`);
19
+ console.log(`Fixture: ${FIXTURE.title} (movie ${FIXTURE.movieId})`);
20
+ console.log(`URL: ${FIXTURE.cdn_url}\n`);
21
+ const start = Date.now();
22
+ const block = await imageBlock(client, { cdn_url: FIXTURE.cdn_url });
23
+ const ms = Date.now() - start;
24
+ if (!block) {
25
+ console.error(`FAIL: imageBlock returned null (fetched in ${ms}ms)`);
26
+ process.exit(1);
27
+ }
28
+ console.log(`SUCCESS (${ms}ms)`);
29
+ console.log(`- type: ${block.type}`);
30
+ console.log(`- mimeType: ${block.mimeType}`);
31
+ console.log(`- data size: ${block.data.length} base64 chars`);
32
+ console.log(`- sniff: ${block.data.slice(0, 24)}...`);
33
+ // JPEG base64 starts with /9j/
34
+ const looksLikeJpeg = block.data.startsWith('/9j/');
35
+ console.log(`- JPEG? ${looksLikeJpeg ? 'yes (starts with /9j/)' : 'NO -- unexpected'}`);
36
+ }
37
+ main().catch((err) => {
38
+ console.error('Unexpected error:', err);
39
+ process.exit(1);
40
+ });
41
+ //# sourceMappingURL=image-block.live.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-block.live.js","sourceRoot":"","sources":["../../src/__tests__/image-block.live.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEjD,MAAM,OAAO,GAAG;IACd,OAAO,EAAE,GAAG;IACZ,KAAK,EAAE,gCAAgC;IACvC,OAAO,EACL,oHAAoH;CACvH,CAAC;AAEF,KAAK,UAAU,IAAI;IACjB,oEAAoE;IACpE,6DAA6D;IAC7D,MAAM,MAAM,GAAG,IAAI,YAAY,CAAC,yBAAyB,EAAE,WAAW,CAAC,CAAC;IAExE,OAAO,CAAC,GAAG,CAAC,wCAAwC,CAAC,CAAC;IACtD,OAAO,CAAC,GAAG,CAAC,YAAY,OAAO,CAAC,KAAK,WAAW,OAAO,CAAC,OAAO,GAAG,CAAC,CAAC;IACpE,OAAO,CAAC,GAAG,CAAC,QAAQ,OAAO,CAAC,OAAO,IAAI,CAAC,CAAC;IAEzC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACzB,MAAM,KAAK,GAAG,MAAM,UAAU,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IACrE,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC;IAE9B,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,8CAA8C,EAAE,KAAK,CAAC,CAAC;QACrE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;IACjC,OAAO,CAAC,GAAG,CAAC,gBAAgB,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IAC1C,OAAO,CAAC,GAAG,CAAC,gBAAgB,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC9C,OAAO,CAAC,GAAG,CAAC,gBAAgB,KAAK,CAAC,IAAI,CAAC,MAAM,eAAe,CAAC,CAAC;IAC9D,OAAO,CAAC,GAAG,CAAC,gBAAgB,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC;IAC1D,+BAA+B;IAC/B,MAAM,aAAa,GAAG,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IACpD,OAAO,CAAC,GAAG,CACT,gBAAgB,aAAa,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC,CAAC,kBAAkB,EAAE,CAChF,CAAC;AACJ,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,mBAAmB,EAAE,GAAG,CAAC,CAAC;IACxC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ export {};