ynab-mcp-deluxe 0.1.9
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/LICENSE +25 -0
- package/README.md +99 -0
- package/dist/backup.d.ts +28 -0
- package/dist/backup.d.ts.map +1 -0
- package/dist/backup.js +78 -0
- package/dist/drift-detection.d.ts +110 -0
- package/dist/drift-detection.d.ts.map +1 -0
- package/dist/drift-detection.js +366 -0
- package/dist/drift-snapshot.d.ts +74 -0
- package/dist/drift-snapshot.d.ts.map +1 -0
- package/dist/drift-snapshot.js +120 -0
- package/dist/helpers.d.ts +65 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +226 -0
- package/dist/local-budget.d.ts +86 -0
- package/dist/local-budget.d.ts.map +1 -0
- package/dist/local-budget.js +277 -0
- package/dist/logger.d.ts +50 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +136 -0
- package/dist/server.d.ts +9 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +1626 -0
- package/dist/sync-history.d.ts +95 -0
- package/dist/sync-history.d.ts.map +1 -0
- package/dist/sync-history.js +205 -0
- package/dist/sync-providers.d.ts +71 -0
- package/dist/sync-providers.d.ts.map +1 -0
- package/dist/sync-providers.js +105 -0
- package/dist/types.d.ts +535 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +4 -0
- package/dist/ynab-client.d.ts +257 -0
- package/dist/ynab-client.d.ts.map +1 -0
- package/dist/ynab-client.js +1248 -0
- package/package.json +102 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
=====================
|
|
3
|
+
|
|
4
|
+
Copyright © 2025 Frank Fiegel (frank@glama.ai)
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person
|
|
7
|
+
obtaining a copy of this software and associated documentation
|
|
8
|
+
files (the “Software”), to deal in the Software without
|
|
9
|
+
restriction, including without limitation the rights to use,
|
|
10
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
copies of the Software, and to permit persons to whom the
|
|
12
|
+
Software is furnished to do so, subject to the following
|
|
13
|
+
conditions:
|
|
14
|
+
|
|
15
|
+
The above copyright notice and this permission notice shall be
|
|
16
|
+
included in all copies or substantial portions of the Software.
|
|
17
|
+
|
|
18
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
|
|
19
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
20
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
21
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
22
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
23
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
24
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
25
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# YNAB MCP Deluxe
|
|
2
|
+
|
|
3
|
+
An MCP server for YNAB integration built with [FastMCP](https://github.com/punkpeye/fastmcp).
|
|
4
|
+
|
|
5
|
+
## Development
|
|
6
|
+
|
|
7
|
+
### Prerequisites
|
|
8
|
+
|
|
9
|
+
- [Bun](https://bun.sh/) - JavaScript runtime and package manager
|
|
10
|
+
|
|
11
|
+
### Getting Started
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
bun install
|
|
15
|
+
bun run dev
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### Start the server
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
bun run start
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Or use the dev script to interact with the server using CLI:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
bun run dev
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Testing
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
bun run test
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Linting and Formatting
|
|
37
|
+
|
|
38
|
+
Run the full signal check (typescript, eslint, prettier):
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
bun run signal
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Individual commands:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
bun run ts-check # TypeScript type checking
|
|
48
|
+
bun run lint # ESLint
|
|
49
|
+
bun run prettier-check # Prettier check
|
|
50
|
+
bun run prettier # Prettier format
|
|
51
|
+
bun run lint:fix # ESLint with auto-fix
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Security Considerations
|
|
55
|
+
|
|
56
|
+
### ⚠️ Sync History Contains Sensitive Financial Data
|
|
57
|
+
|
|
58
|
+
This server maintains a local sync history to enable efficient delta synchronization with YNAB. **This history contains complete snapshots of your budget data**, including:
|
|
59
|
+
|
|
60
|
+
- Account names and balances
|
|
61
|
+
- Transaction details (payees, amounts, dates, memos)
|
|
62
|
+
- Category names and budgeted amounts
|
|
63
|
+
- Payee information
|
|
64
|
+
|
|
65
|
+
**Location:** `~/.config/ynab-mcp-deluxe/sync-history/[budgetId]/`
|
|
66
|
+
|
|
67
|
+
**Important:**
|
|
68
|
+
|
|
69
|
+
- This data is stored unencrypted on your local filesystem
|
|
70
|
+
- Anyone with access to your user account can read this data
|
|
71
|
+
- Consider the security implications before using this on shared systems
|
|
72
|
+
- Back up this directory if you need to preserve sync state across reinstalls
|
|
73
|
+
|
|
74
|
+
### Clearing Sync History
|
|
75
|
+
|
|
76
|
+
To clear all sync history and force a fresh full sync on next access:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
rm -rf ~/.config/ynab-mcp-deluxe/sync-history/
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Or use the MCP tool (when available):
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
clear_sync_history
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Environment Variables
|
|
89
|
+
|
|
90
|
+
| Variable | Required | Default | Description |
|
|
91
|
+
| ----------------------------------- | -------- | ------- | ------------------------------------------------------------------- |
|
|
92
|
+
| `YNAB_ACCESS_TOKEN` | Yes | - | YNAB API personal access token |
|
|
93
|
+
| `YNAB_BUDGET_ID` | No | - | Hard constraint - only allow access to this budget (safety feature) |
|
|
94
|
+
| `YNAB_READ_ONLY` | No | `false` | Set to `true` or `1` to block all write operations |
|
|
95
|
+
| `YNAB_SYNC_INTERVAL_SECONDS` | No | `600` | How often to sync with YNAB (0 = always sync) |
|
|
96
|
+
| `YNAB_DRIFT_DETECTION` | No | `true` | Enable drift detection to validate merge logic |
|
|
97
|
+
| `YNAB_ALWAYS_FULL_SYNC` | No | `false` | Skip delta sync, always fetch full budget |
|
|
98
|
+
| `YNAB_DRIFT_CHECK_INTERVAL_SYNCS` | No | `1` | Check for drift every N syncs |
|
|
99
|
+
| `YNAB_DRIFT_CHECK_INTERVAL_MINUTES` | No | `0` | Check for drift every N minutes (0 = disabled) |
|
package/dist/backup.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Budget backup utilities
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Get the backup directory path
|
|
6
|
+
* ~/.config/ynab-mcp-deluxe/backups/
|
|
7
|
+
*/
|
|
8
|
+
export declare function getBackupDir(): string;
|
|
9
|
+
/**
|
|
10
|
+
* Generate backup filename with timestamp and budget ID
|
|
11
|
+
* Format: YYYY-MM-DD_HH-mm-ss_ynab-budget-[id]_backup.json
|
|
12
|
+
*/
|
|
13
|
+
export declare function generateBackupFilename(budgetId: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* Ensure the backup directory exists
|
|
16
|
+
*/
|
|
17
|
+
export declare function ensureBackupDir(): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Backup a single budget to disk
|
|
20
|
+
* @returns The full path to the backup file
|
|
21
|
+
*/
|
|
22
|
+
export declare function backupBudget(budgetId: string): Promise<string>;
|
|
23
|
+
/**
|
|
24
|
+
* Backup all budgets
|
|
25
|
+
* @returns Array of backup file paths
|
|
26
|
+
*/
|
|
27
|
+
export declare function backupAllBudgets(): Promise<string[]>;
|
|
28
|
+
//# sourceMappingURL=backup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backup.d.ts","sourceRoot":"","sources":["../src/backup.ts"],"names":[],"mappings":"AAAA;;GAEG;AAaH;;;GAGG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAU/D;AAED;;GAEG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAGrD;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA6BpE;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAU1D"}
|
package/dist/backup.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Budget backup utilities
|
|
3
|
+
*/
|
|
4
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
5
|
+
import { homedir } from 'node:os';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { ynabClient } from './ynab-client.js';
|
|
8
|
+
// Server version for backup metadata
|
|
9
|
+
const SERVER_VERSION = '1.0.0';
|
|
10
|
+
/**
|
|
11
|
+
* Get the backup directory path
|
|
12
|
+
* ~/.config/ynab-mcp-deluxe/backups/
|
|
13
|
+
*/
|
|
14
|
+
export function getBackupDir() {
|
|
15
|
+
return join(homedir(), '.config', 'ynab-mcp-deluxe', 'backups');
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Generate backup filename with timestamp and budget ID
|
|
19
|
+
* Format: YYYY-MM-DD_HH-mm-ss_ynab-budget-[id]_backup.json
|
|
20
|
+
*/
|
|
21
|
+
export function generateBackupFilename(budgetId) {
|
|
22
|
+
const now = new Date();
|
|
23
|
+
// Format: 2026-01-23_06-15-40
|
|
24
|
+
const timestamp = now
|
|
25
|
+
.toISOString()
|
|
26
|
+
.replace('T', '_')
|
|
27
|
+
.replace(/:/g, '-')
|
|
28
|
+
.replace(/\.\d+Z$/, '');
|
|
29
|
+
return `${timestamp}_ynab-budget-${budgetId}_backup.json`;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Ensure the backup directory exists
|
|
33
|
+
*/
|
|
34
|
+
export async function ensureBackupDir() {
|
|
35
|
+
const dir = getBackupDir();
|
|
36
|
+
await mkdir(dir, { recursive: true });
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Backup a single budget to disk
|
|
40
|
+
* @returns The full path to the backup file
|
|
41
|
+
*/
|
|
42
|
+
export async function backupBudget(budgetId) {
|
|
43
|
+
// Get budget info for metadata
|
|
44
|
+
const budgetInfo = await ynabClient.getBudgetInfo(budgetId);
|
|
45
|
+
// Get raw budget data
|
|
46
|
+
const { budget, server_knowledge } = await ynabClient.getBudgetByIdRaw(budgetId);
|
|
47
|
+
// Construct backup object
|
|
48
|
+
const backup = {
|
|
49
|
+
backup_metadata: {
|
|
50
|
+
backup_timestamp: new Date().toISOString(),
|
|
51
|
+
budget_id: budgetId,
|
|
52
|
+
budget_name: budgetInfo.name,
|
|
53
|
+
server_knowledge,
|
|
54
|
+
ynab_mcp_server_version: SERVER_VERSION,
|
|
55
|
+
},
|
|
56
|
+
budget,
|
|
57
|
+
};
|
|
58
|
+
// Ensure directory exists
|
|
59
|
+
await ensureBackupDir();
|
|
60
|
+
// Write backup file
|
|
61
|
+
const filename = generateBackupFilename(budgetId);
|
|
62
|
+
const filePath = join(getBackupDir(), filename);
|
|
63
|
+
await writeFile(filePath, JSON.stringify(backup, null, 2), 'utf-8');
|
|
64
|
+
return filePath;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Backup all budgets
|
|
68
|
+
* @returns Array of backup file paths
|
|
69
|
+
*/
|
|
70
|
+
export async function backupAllBudgets() {
|
|
71
|
+
const budgets = await ynabClient.getBudgets();
|
|
72
|
+
const paths = [];
|
|
73
|
+
for (const budget of budgets) {
|
|
74
|
+
const path = await backupBudget(budget.id);
|
|
75
|
+
paths.push(path);
|
|
76
|
+
}
|
|
77
|
+
return paths;
|
|
78
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drift detection for LocalBudget.
|
|
3
|
+
*
|
|
4
|
+
* This module compares a merged LocalBudget (base + deltas) against a fresh
|
|
5
|
+
* full budget fetch to detect any discrepancies in our merge logic.
|
|
6
|
+
*
|
|
7
|
+
* When drift is detected:
|
|
8
|
+
* 1. Log detailed warnings showing what differs
|
|
9
|
+
* 2. Self-heal by replacing local budget with the full fetch result
|
|
10
|
+
*/
|
|
11
|
+
import type { LocalBudget } from './types.js';
|
|
12
|
+
import type { Diff } from 'deep-diff';
|
|
13
|
+
/**
|
|
14
|
+
* Logger interface matching FastMCP's context log
|
|
15
|
+
*/
|
|
16
|
+
interface ContextLog {
|
|
17
|
+
debug: (message: string, data?: unknown) => void;
|
|
18
|
+
error: (message: string, data?: unknown) => void;
|
|
19
|
+
info: (message: string, data?: unknown) => void;
|
|
20
|
+
warn: (message: string, data?: unknown) => void;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Result of a drift check
|
|
24
|
+
*/
|
|
25
|
+
export interface DriftCheckResult {
|
|
26
|
+
/** Number of differences found */
|
|
27
|
+
differenceCount: number;
|
|
28
|
+
/** Summary of differences by category */
|
|
29
|
+
differenceSummary: Record<string, number>;
|
|
30
|
+
/** The raw differences (for debugging) */
|
|
31
|
+
differences: Diff<unknown>[];
|
|
32
|
+
/** Whether any drift was detected */
|
|
33
|
+
hasDrift: boolean;
|
|
34
|
+
/** Server knowledge from merged budget */
|
|
35
|
+
mergedServerKnowledge: number;
|
|
36
|
+
/** Whether the server knowledge values differ (external changes may have occurred) */
|
|
37
|
+
serverKnowledgeMismatch: boolean;
|
|
38
|
+
/** Server knowledge from truth budget */
|
|
39
|
+
truthServerKnowledge: number;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Check if drift detection is enabled.
|
|
43
|
+
*
|
|
44
|
+
* Default: true (enabled)
|
|
45
|
+
* Set YNAB_DRIFT_DETECTION=false to disable
|
|
46
|
+
*/
|
|
47
|
+
export declare function isDriftDetectionEnabled(): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Check if "always full sync" mode is enabled.
|
|
50
|
+
* When enabled, skip delta queries entirely and always fetch the full budget.
|
|
51
|
+
*
|
|
52
|
+
* Default: false (use delta sync for performance)
|
|
53
|
+
* Set YNAB_ALWAYS_FULL_SYNC=true to enable
|
|
54
|
+
*/
|
|
55
|
+
export declare function isAlwaysFullSyncEnabled(): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Get the drift check interval in number of syncs.
|
|
58
|
+
* In production, we don't need to check every sync - periodic checks are sufficient.
|
|
59
|
+
*
|
|
60
|
+
* Default: 1 (check every sync - good for development/alpha)
|
|
61
|
+
* Set YNAB_DRIFT_CHECK_INTERVAL_SYNCS to change
|
|
62
|
+
*/
|
|
63
|
+
export declare function getDriftCheckIntervalSyncs(): number;
|
|
64
|
+
/**
|
|
65
|
+
* Get the drift check interval in minutes.
|
|
66
|
+
* Alternative to sync-count-based checking.
|
|
67
|
+
*
|
|
68
|
+
* Default: 0 (disabled - use sync count instead)
|
|
69
|
+
* Set YNAB_DRIFT_CHECK_INTERVAL_MINUTES to enable time-based checking
|
|
70
|
+
*/
|
|
71
|
+
export declare function getDriftCheckIntervalMinutes(): number;
|
|
72
|
+
/**
|
|
73
|
+
* Determine if a drift check should be performed based on frequency settings.
|
|
74
|
+
* Call this before performing a drift check to respect rate limiting.
|
|
75
|
+
*
|
|
76
|
+
* @param budgetId - The budget ID to check drift state for
|
|
77
|
+
* @returns true if a drift check should be performed
|
|
78
|
+
*/
|
|
79
|
+
export declare function shouldPerformDriftCheck(budgetId: string): boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Record that a drift check was performed for a specific budget.
|
|
82
|
+
* Call this after completing a drift check.
|
|
83
|
+
*
|
|
84
|
+
* @param budgetId - The budget ID to record the drift check for
|
|
85
|
+
*/
|
|
86
|
+
export declare function recordDriftCheck(budgetId: string): void;
|
|
87
|
+
/**
|
|
88
|
+
* Reset drift check state (useful for testing).
|
|
89
|
+
*
|
|
90
|
+
* @param budgetId - Optional budget ID to reset. If not provided, resets all budgets.
|
|
91
|
+
*/
|
|
92
|
+
export declare function resetDriftCheckState(budgetId?: string): void;
|
|
93
|
+
/**
|
|
94
|
+
* Compare a merged LocalBudget against a "truth" budget from a full fetch.
|
|
95
|
+
*
|
|
96
|
+
* @param mergedBudget - The budget built from base + delta merges
|
|
97
|
+
* @param truthBudget - The budget from a fresh full fetch (source of truth)
|
|
98
|
+
* @returns Detailed comparison result
|
|
99
|
+
*/
|
|
100
|
+
export declare function checkForDrift(mergedBudget: LocalBudget, truthBudget: LocalBudget): DriftCheckResult;
|
|
101
|
+
/**
|
|
102
|
+
* Log drift check results appropriately based on outcome.
|
|
103
|
+
*
|
|
104
|
+
* @param result - The drift check result
|
|
105
|
+
* @param budgetId - The budget ID (for logging context)
|
|
106
|
+
* @param log - The logger to use
|
|
107
|
+
*/
|
|
108
|
+
export declare function logDriftCheckResult(result: DriftCheckResult, budgetId: string, log: ContextLog): void;
|
|
109
|
+
export {};
|
|
110
|
+
//# sourceMappingURL=drift-detection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drift-detection.d.ts","sourceRoot":"","sources":["../src/drift-detection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAC,IAAI,EAA4C,MAAM,WAAW,CAAC;AAwC/E;;GAEG;AACH,UAAU,UAAU;IAClB,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IACjD,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IACjD,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IAChD,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;CACjD;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,kCAAkC;IAClC,eAAe,EAAE,MAAM,CAAC;IACxB,yCAAyC;IACzC,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,0CAA0C;IAC1C,WAAW,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;IAC7B,qCAAqC;IACrC,QAAQ,EAAE,OAAO,CAAC;IAClB,0CAA0C;IAC1C,qBAAqB,EAAE,MAAM,CAAC;IAC9B,sFAAsF;IACtF,uBAAuB,EAAE,OAAO,CAAC;IACjC,yCAAyC;IACzC,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AA6CD;;;;;GAKG;AACH,wBAAgB,uBAAuB,IAAI,OAAO,CAQjD;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,IAAI,OAAO,CAGjD;AAED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,IAAI,MAAM,CAUnD;AAED;;;;;;GAMG;AACH,wBAAgB,4BAA4B,IAAI,MAAM,CAUrD;AAMD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAqCjE;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAGvD;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAQ5D;AAoFD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,YAAY,EAAE,WAAW,EACzB,WAAW,EAAE,WAAW,GACvB,gBAAgB,CAoBlB;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,gBAAgB,EACxB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,UAAU,GACd,IAAI,CAwEN"}
|