@lesgo/memory-mcp 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.
package/README.md ADDED
@@ -0,0 +1,128 @@
1
+ # memory-mcp
2
+
3
+ Personal External Brain MCP Server - A stateless MCP server for syncing templates, standards, and knowledge via GitHub.
4
+
5
+ ## Features
6
+
7
+ - **Zero-latency reads**: In-memory cache for instant access to templates and standards
8
+ - **Non-blocking writes**: Async GitHub sync with local queue for resilience
9
+ - **Auto-categorization**: Files organized by category (templates, standards, knowledge, etc.)
10
+ - **Full-text search**: Search across file paths and content
11
+
12
+ ## Installation
13
+
14
+ ### VS Code / Copilot
15
+
16
+ Add to your `~/.vscode/mcp.json` or VS Code settings:
17
+
18
+ ```json
19
+ {
20
+ "servers": {
21
+ "memory-mcp": {
22
+ "type": "stdio",
23
+ "command": "npx",
24
+ "args": ["-y", "memory-mcp"],
25
+ "env": {
26
+ "MEMORY_MCP_REPO": "your-username/memory-mcp-storage",
27
+ "MEMORY_MCP_TOKEN": "ghp_your_token_here"
28
+ }
29
+ }
30
+ }
31
+ }
32
+ ```
33
+
34
+ ### Claude Desktop
35
+
36
+ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
37
+
38
+ ```json
39
+ {
40
+ "mcpServers": {
41
+ "memory-mcp": {
42
+ "command": "npx",
43
+ "args": ["-y", "memory-mcp"],
44
+ "env": {
45
+ "MEMORY_MCP_REPO": "your-username/memory-mcp-storage",
46
+ "MEMORY_MCP_TOKEN": "ghp_your_token_here"
47
+ }
48
+ }
49
+ }
50
+ }
51
+ ```
52
+
53
+ ## Configuration
54
+
55
+ | Variable | Required | Description |
56
+ |----------|----------|-------------|
57
+ | `MEMORY_MCP_REPO` | Yes | GitHub repository in `owner/repo` format |
58
+ | `MEMORY_MCP_TOKEN` | Yes | GitHub Personal Access Token with `repo` scope |
59
+
60
+ ## Tools
61
+
62
+ ### `list_knowledge`
63
+ List all available memory files organized by category.
64
+
65
+ ### `read_memory`
66
+ Read the content of a specific memory file.
67
+ ```json
68
+ { "path": "templates/user-story.md" }
69
+ ```
70
+
71
+ ### `save_memory`
72
+ Create or update a memory file. Category folders are auto-created.
73
+ ```json
74
+ { "category": "templates", "name": "user-story.md", "content": "..." }
75
+ ```
76
+
77
+ ### `delete_memory`
78
+ Delete a memory file.
79
+ ```json
80
+ { "path": "templates/old-template.md" }
81
+ ```
82
+
83
+ ### `search_memory`
84
+ Search across all memory files by keyword.
85
+ ```json
86
+ { "query": "graphql error" }
87
+ ```
88
+
89
+ ## Storage Repository Structure
90
+
91
+ Create a private GitHub repository (e.g., `memory-mcp-storage`) with this structure:
92
+
93
+ ```
94
+ /
95
+ ├── index.json # Auto-generated manifest
96
+ ├── templates/ # Markdown templates
97
+ │ ├── user-story.md
98
+ │ └── pull-request.md
99
+ ├── standards/ # Technical rules (YAML/JSON)
100
+ │ ├── typescript.yaml
101
+ │ └── graphql.yaml
102
+ └── knowledge/ # Long-form context
103
+ └── architecture.md
104
+ ```
105
+
106
+ ## How It Works
107
+
108
+ 1. **Startup**: Fetches all files from GitHub, populates in-memory cache
109
+ 2. **Reads**: Cache-first (instant), falls back to GitHub API on miss
110
+ 3. **Writes**: Updates cache immediately, returns to agent, syncs GitHub async
111
+ 4. **Failures**: Failed writes queue to `~/.memory-mcp-queue.json` and retry on next operation
112
+
113
+ ## Development
114
+
115
+ ```bash
116
+ # Install dependencies
117
+ npm install
118
+
119
+ # Build
120
+ npm run build
121
+
122
+ # Watch mode
123
+ npm run dev
124
+ ```
125
+
126
+ ## License
127
+
128
+ MIT
@@ -0,0 +1,62 @@
1
+ export interface CacheEntry {
2
+ content: string;
3
+ sha: string;
4
+ }
5
+ export interface SearchResult {
6
+ path: string;
7
+ snippet: string;
8
+ }
9
+ /**
10
+ * In-memory cache for fast reads
11
+ * Cache is populated on startup and updated optimistically on writes
12
+ */
13
+ export declare class MemoryCache {
14
+ private cache;
15
+ /**
16
+ * Get a cached entry
17
+ */
18
+ get(path: string): CacheEntry | undefined;
19
+ /**
20
+ * Set a cache entry
21
+ */
22
+ set(path: string, content: string, sha: string): void;
23
+ /**
24
+ * Delete a cache entry
25
+ */
26
+ delete(path: string): boolean;
27
+ /**
28
+ * Check if path exists in cache
29
+ */
30
+ has(path: string): boolean;
31
+ /**
32
+ * Get all cached paths
33
+ */
34
+ keys(): string[];
35
+ /**
36
+ * Get all entries
37
+ */
38
+ entries(): Array<[string, CacheEntry]>;
39
+ /**
40
+ * Get unique categories (top-level folders)
41
+ */
42
+ categories(): string[];
43
+ /**
44
+ * Search across paths and content
45
+ * Returns matching paths with content snippets
46
+ */
47
+ search(query: string): SearchResult[];
48
+ /**
49
+ * Extract a snippet around the match
50
+ */
51
+ private extractSnippet;
52
+ /**
53
+ * Clear the entire cache
54
+ */
55
+ clear(): void;
56
+ /**
57
+ * Get cache size
58
+ */
59
+ get size(): number;
60
+ }
61
+ export declare const cache: MemoryCache;
62
+ //# sourceMappingURL=cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,KAAK,CAAsC;IAEnD;;OAEG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IAUzC;;OAEG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAKrD;;OAEG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAQ7B;;OAEG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI1B;;OAEG;IACH,IAAI,IAAI,MAAM,EAAE;IAIhB;;OAEG;IACH,OAAO,IAAI,KAAK,CAAC,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAItC;;OAEG;IACH,UAAU,IAAI,MAAM,EAAE;IAWtB;;;OAGG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,EAAE;IA6BrC;;OAEG;IACH,OAAO,CAAC,cAAc;IAgCtB;;OAEG;IACH,KAAK,IAAI,IAAI;IAKb;;OAEG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF;AAGD,eAAO,MAAM,KAAK,aAAoB,CAAC"}
package/dist/cache.js ADDED
@@ -0,0 +1,141 @@
1
+ import { log } from './logger.js';
2
+ /**
3
+ * In-memory cache for fast reads
4
+ * Cache is populated on startup and updated optimistically on writes
5
+ */
6
+ export class MemoryCache {
7
+ cache = new Map();
8
+ /**
9
+ * Get a cached entry
10
+ */
11
+ get(path) {
12
+ const entry = this.cache.get(path);
13
+ if (entry) {
14
+ log('cache', `HIT: ${path}`);
15
+ }
16
+ else {
17
+ log('cache', `MISS: ${path}`);
18
+ }
19
+ return entry;
20
+ }
21
+ /**
22
+ * Set a cache entry
23
+ */
24
+ set(path, content, sha) {
25
+ this.cache.set(path, { content, sha });
26
+ log('cache', `SET: ${path}`);
27
+ }
28
+ /**
29
+ * Delete a cache entry
30
+ */
31
+ delete(path) {
32
+ const deleted = this.cache.delete(path);
33
+ if (deleted) {
34
+ log('cache', `DELETE: ${path}`);
35
+ }
36
+ return deleted;
37
+ }
38
+ /**
39
+ * Check if path exists in cache
40
+ */
41
+ has(path) {
42
+ return this.cache.has(path);
43
+ }
44
+ /**
45
+ * Get all cached paths
46
+ */
47
+ keys() {
48
+ return Array.from(this.cache.keys());
49
+ }
50
+ /**
51
+ * Get all entries
52
+ */
53
+ entries() {
54
+ return Array.from(this.cache.entries());
55
+ }
56
+ /**
57
+ * Get unique categories (top-level folders)
58
+ */
59
+ categories() {
60
+ const categories = new Set();
61
+ for (const path of this.cache.keys()) {
62
+ const parts = path.split('/');
63
+ if (parts.length > 1) {
64
+ categories.add(parts[0]);
65
+ }
66
+ }
67
+ return Array.from(categories).sort();
68
+ }
69
+ /**
70
+ * Search across paths and content
71
+ * Returns matching paths with content snippets
72
+ */
73
+ search(query) {
74
+ const results = [];
75
+ const queryLower = query.toLowerCase();
76
+ for (const [path, entry] of this.cache.entries()) {
77
+ // Check path match
78
+ if (path.toLowerCase().includes(queryLower)) {
79
+ results.push({
80
+ path,
81
+ snippet: this.extractSnippet(entry.content, query),
82
+ });
83
+ continue;
84
+ }
85
+ // Check content match
86
+ const contentLower = entry.content.toLowerCase();
87
+ const matchIndex = contentLower.indexOf(queryLower);
88
+ if (matchIndex !== -1) {
89
+ results.push({
90
+ path,
91
+ snippet: this.extractSnippet(entry.content, query, matchIndex),
92
+ });
93
+ }
94
+ }
95
+ log('cache', `SEARCH: "${query}" found ${results.length} results`);
96
+ return results;
97
+ }
98
+ /**
99
+ * Extract a snippet around the match
100
+ */
101
+ extractSnippet(content, query, matchIndex) {
102
+ const maxLength = 150;
103
+ if (content.length <= maxLength) {
104
+ return content;
105
+ }
106
+ if (matchIndex === undefined) {
107
+ const contentLower = content.toLowerCase();
108
+ matchIndex = contentLower.indexOf(query.toLowerCase());
109
+ }
110
+ if (matchIndex === -1) {
111
+ return content.slice(0, maxLength) + '...';
112
+ }
113
+ // Calculate start and end positions centered around the match
114
+ const snippetStart = Math.max(0, matchIndex - 50);
115
+ const snippetEnd = Math.min(content.length, matchIndex + query.length + 100);
116
+ let snippet = content.slice(snippetStart, snippetEnd);
117
+ if (snippetStart > 0) {
118
+ snippet = '...' + snippet;
119
+ }
120
+ if (snippetEnd < content.length) {
121
+ snippet = snippet + '...';
122
+ }
123
+ return snippet;
124
+ }
125
+ /**
126
+ * Clear the entire cache
127
+ */
128
+ clear() {
129
+ this.cache.clear();
130
+ log('cache', 'CLEARED');
131
+ }
132
+ /**
133
+ * Get cache size
134
+ */
135
+ get size() {
136
+ return this.cache.size;
137
+ }
138
+ }
139
+ // Global cache instance
140
+ export const cache = new MemoryCache();
141
+ //# sourceMappingURL=cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ export interface GitHubFile {
2
+ path: string;
3
+ content: string;
4
+ sha: string;
5
+ }
6
+ export interface GitHubConfig {
7
+ token: string;
8
+ repo: string;
9
+ }
10
+ export declare class GitHubClient {
11
+ private octokit;
12
+ private owner;
13
+ private repo;
14
+ constructor(config: GitHubConfig);
15
+ /**
16
+ * List all files in the repository
17
+ */
18
+ listTree(): Promise<Array<{
19
+ path: string;
20
+ sha: string;
21
+ }>>;
22
+ /**
23
+ * Get file content by path
24
+ */
25
+ getFile(path: string): Promise<GitHubFile | null>;
26
+ /**
27
+ * Create or update a file
28
+ */
29
+ putFile(path: string, content: string, sha?: string): Promise<string>;
30
+ /**
31
+ * Delete a file
32
+ */
33
+ deleteFile(path: string, sha: string): Promise<void>;
34
+ /**
35
+ * Check if repository exists and is accessible
36
+ */
37
+ validateAccess(): Promise<boolean>;
38
+ }
39
+ //# sourceMappingURL=github.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github.d.ts","sourceRoot":"","sources":["../src/github.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,IAAI,CAAS;gBAET,MAAM,EAAE,YAAY;IAUhC;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAgC/D;;OAEG;IACG,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IA4BvD;;OAEG;IACG,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAiB3E;;OAEG;IACG,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAY1D;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC;CAWzC"}
package/dist/github.js ADDED
@@ -0,0 +1,120 @@
1
+ import { Octokit } from '@octokit/rest';
2
+ import { log } from './logger.js';
3
+ export class GitHubClient {
4
+ octokit;
5
+ owner;
6
+ repo;
7
+ constructor(config) {
8
+ const [owner, repo] = config.repo.split('/');
9
+ if (!owner || !repo) {
10
+ throw new Error(`Invalid repo format: "${config.repo}". Expected "owner/repo".`);
11
+ }
12
+ this.owner = owner;
13
+ this.repo = repo;
14
+ this.octokit = new Octokit({ auth: config.token });
15
+ }
16
+ /**
17
+ * List all files in the repository
18
+ */
19
+ async listTree() {
20
+ log('github', `Fetching tree for ${this.owner}/${this.repo}`);
21
+ try {
22
+ // Get the default branch
23
+ const { data: repoData } = await this.octokit.repos.get({
24
+ owner: this.owner,
25
+ repo: this.repo,
26
+ });
27
+ const defaultBranch = repoData.default_branch;
28
+ // Get the tree recursively
29
+ const { data: treeData } = await this.octokit.git.getTree({
30
+ owner: this.owner,
31
+ repo: this.repo,
32
+ tree_sha: defaultBranch,
33
+ recursive: 'true',
34
+ });
35
+ return treeData.tree
36
+ .filter((item) => item.type === 'blob' && item.path && item.sha)
37
+ .map((item) => ({ path: item.path, sha: item.sha }));
38
+ }
39
+ catch (error) {
40
+ if (error instanceof Error && 'status' in error && (error.status === 404 || error.status === 409)) {
41
+ log('github', 'Repository is empty or not found, returning empty tree');
42
+ return [];
43
+ }
44
+ throw error;
45
+ }
46
+ }
47
+ /**
48
+ * Get file content by path
49
+ */
50
+ async getFile(path) {
51
+ log('github', `Fetching file: ${path}`);
52
+ try {
53
+ const { data } = await this.octokit.repos.getContent({
54
+ owner: this.owner,
55
+ repo: this.repo,
56
+ path,
57
+ });
58
+ if (Array.isArray(data) || data.type !== 'file') {
59
+ return null;
60
+ }
61
+ const content = Buffer.from(data.content, 'base64').toString('utf-8');
62
+ return {
63
+ path,
64
+ content,
65
+ sha: data.sha,
66
+ };
67
+ }
68
+ catch (error) {
69
+ if (error instanceof Error && 'status' in error && error.status === 404) {
70
+ return null;
71
+ }
72
+ throw error;
73
+ }
74
+ }
75
+ /**
76
+ * Create or update a file
77
+ */
78
+ async putFile(path, content, sha) {
79
+ log('github', `${sha ? 'Updating' : 'Creating'} file: ${path}`);
80
+ const message = sha ? `Update ${path}` : `Create ${path}`;
81
+ const { data } = await this.octokit.repos.createOrUpdateFileContents({
82
+ owner: this.owner,
83
+ repo: this.repo,
84
+ path,
85
+ message,
86
+ content: Buffer.from(content).toString('base64'),
87
+ sha,
88
+ });
89
+ return data.content?.sha ?? '';
90
+ }
91
+ /**
92
+ * Delete a file
93
+ */
94
+ async deleteFile(path, sha) {
95
+ log('github', `Deleting file: ${path}`);
96
+ await this.octokit.repos.deleteFile({
97
+ owner: this.owner,
98
+ repo: this.repo,
99
+ path,
100
+ message: `Delete ${path}`,
101
+ sha,
102
+ });
103
+ }
104
+ /**
105
+ * Check if repository exists and is accessible
106
+ */
107
+ async validateAccess() {
108
+ try {
109
+ await this.octokit.repos.get({
110
+ owner: this.owner,
111
+ repo: this.repo,
112
+ });
113
+ return true;
114
+ }
115
+ catch {
116
+ return false;
117
+ }
118
+ }
119
+ }
120
+ //# sourceMappingURL=github.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github.js","sourceRoot":"","sources":["../src/github.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import { GitHubClient } from './github.js';
2
+ import { MemoryCache } from './cache.js';
3
+ export interface IndexFile {
4
+ categories: string[];
5
+ files: Array<{
6
+ path: string;
7
+ category: string;
8
+ }>;
9
+ lastUpdated: string;
10
+ }
11
+ /**
12
+ * Generate and update index.json based on cache contents
13
+ */
14
+ export declare function updateIndex(github: GitHubClient, cache: MemoryCache): Promise<void>;
15
+ /**
16
+ * Parse index.json content
17
+ */
18
+ export declare function parseIndex(content: string): IndexFile;
19
+ //# sourceMappingURL=index-gen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-gen.d.ts","sourceRoot":"","sources":["../src/index-gen.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAGzC,MAAM,WAAW,SAAS;IACxB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjD,WAAW,EAAE,MAAM,CAAC;CACrB;AAID;;GAEG;AACH,wBAAsB,WAAW,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAiCzF;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAErD"}
@@ -0,0 +1,41 @@
1
+ import { log } from './logger.js';
2
+ const INDEX_PATH = 'index.json';
3
+ /**
4
+ * Generate and update index.json based on cache contents
5
+ */
6
+ export async function updateIndex(github, cache) {
7
+ log('writer', 'Regenerating index.json');
8
+ const files = cache.keys()
9
+ .filter((path) => path !== INDEX_PATH)
10
+ .map((path) => {
11
+ const parts = path.split('/');
12
+ const category = parts.length > 1 ? parts[0] : 'root';
13
+ return { path, category };
14
+ })
15
+ .sort((a, b) => a.path.localeCompare(b.path));
16
+ const categories = [...new Set(files.map((f) => f.category))].sort();
17
+ const index = {
18
+ categories,
19
+ files,
20
+ lastUpdated: new Date().toISOString(),
21
+ };
22
+ const content = JSON.stringify(index, null, 2);
23
+ // Get existing SHA for index.json
24
+ const existingIndex = cache.get(INDEX_PATH);
25
+ try {
26
+ const newSha = await github.putFile(INDEX_PATH, content, existingIndex?.sha);
27
+ cache.set(INDEX_PATH, content, newSha);
28
+ log('writer', `Index updated with ${files.length} files in ${categories.length} categories`);
29
+ }
30
+ catch (error) {
31
+ log('writer', `Failed to update index: ${error}`);
32
+ throw error;
33
+ }
34
+ }
35
+ /**
36
+ * Parse index.json content
37
+ */
38
+ export function parseIndex(content) {
39
+ return JSON.parse(content);
40
+ }
41
+ //# sourceMappingURL=index-gen.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-gen.js","sourceRoot":"","sources":["../src/index-gen.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAQlC,MAAM,UAAU,GAAG,YAAY,CAAC;AAEhC;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,MAAoB,EAAE,KAAkB;IACxE,GAAG,CAAC,QAAQ,EAAE,yBAAyB,CAAC,CAAC;IAEzC,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,EAAE;SACvB,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,UAAU,CAAC;SACrC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACZ,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC9B,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;QACtD,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IAC5B,CAAC,CAAC;SACD,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAEhD,MAAM,UAAU,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAErE,MAAM,KAAK,GAAc;QACvB,UAAU;QACV,KAAK;QACL,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACtC,CAAC;IAEF,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAE/C,kCAAkC;IAClC,MAAM,aAAa,GAAG,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IAE5C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,UAAU,EAAE,OAAO,EAAE,aAAa,EAAE,GAAG,CAAC,CAAC;QAC7E,KAAK,CAAC,GAAG,CAAC,UAAU,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;QACvC,GAAG,CAAC,QAAQ,EAAE,sBAAsB,KAAK,CAAC,MAAM,aAAa,UAAU,CAAC,MAAM,aAAa,CAAC,CAAC;IAC/F,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,GAAG,CAAC,QAAQ,EAAE,2BAA2B,KAAK,EAAE,CAAC,CAAC;QAClD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,OAAe;IACxC,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAc,CAAC;AAC1C,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}