org-qmd 0.1.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,146 @@
1
+ /**
2
+ * Collections configuration management
3
+ *
4
+ * This module manages the YAML-based collection configuration at ~/.config/qmd/index.yml.
5
+ * Collections define which directories to index and their associated contexts.
6
+ */
7
+ /**
8
+ * Context definitions for a collection
9
+ * Key is path prefix (e.g., "/", "/2024", "/Board of Directors")
10
+ * Value is the context description
11
+ */
12
+ export type ContextMap = Record<string, string>;
13
+ /**
14
+ * A single collection configuration
15
+ */
16
+ export interface Collection {
17
+ path: string;
18
+ pattern: string;
19
+ ignore?: string[];
20
+ context?: ContextMap;
21
+ update?: string;
22
+ includeByDefault?: boolean;
23
+ }
24
+ /**
25
+ * The complete configuration file structure
26
+ */
27
+ export interface CollectionConfig {
28
+ global_context?: string;
29
+ collections: Record<string, Collection>;
30
+ }
31
+ /**
32
+ * Collection with its name (for return values)
33
+ */
34
+ export interface NamedCollection extends Collection {
35
+ name: string;
36
+ }
37
+ /**
38
+ * Set the config source for SDK mode.
39
+ * - File path: load/save from a specific YAML file
40
+ * - Inline config: use an in-memory CollectionConfig (saveConfig updates in place, no file I/O)
41
+ * - undefined: reset to default file-based config
42
+ */
43
+ export declare function setConfigSource(source?: {
44
+ configPath?: string;
45
+ config?: CollectionConfig;
46
+ }): void;
47
+ /**
48
+ * Set the current index name for config file lookup
49
+ * Config file will be ~/.config/qmd/{indexName}.yml
50
+ */
51
+ export declare function setConfigIndexName(name: string): void;
52
+ /**
53
+ * Load configuration from the configured source.
54
+ * - Inline config: returns the in-memory object directly
55
+ * - File-based: reads from YAML file (default ~/.config/qmd/index.yml)
56
+ * Returns empty config if file doesn't exist
57
+ */
58
+ export declare function loadConfig(): CollectionConfig;
59
+ /**
60
+ * Save configuration to the configured source.
61
+ * - Inline config: updates the in-memory object (no file I/O)
62
+ * - File-based: writes to YAML file (default ~/.config/qmd/index.yml)
63
+ */
64
+ export declare function saveConfig(config: CollectionConfig): void;
65
+ /**
66
+ * Get a specific collection by name
67
+ * Returns null if not found
68
+ */
69
+ export declare function getCollection(name: string): NamedCollection | null;
70
+ /**
71
+ * List all collections
72
+ */
73
+ export declare function listCollections(): NamedCollection[];
74
+ /**
75
+ * Get collections that are included by default in queries
76
+ */
77
+ export declare function getDefaultCollections(): NamedCollection[];
78
+ /**
79
+ * Get collection names that are included by default
80
+ */
81
+ export declare function getDefaultCollectionNames(): string[];
82
+ /**
83
+ * Update a collection's settings
84
+ */
85
+ export declare function updateCollectionSettings(name: string, settings: {
86
+ update?: string | null;
87
+ includeByDefault?: boolean;
88
+ }): boolean;
89
+ /**
90
+ * Add or update a collection
91
+ */
92
+ export declare function addCollection(name: string, path: string, pattern?: string): void;
93
+ /**
94
+ * Remove a collection
95
+ */
96
+ export declare function removeCollection(name: string): boolean;
97
+ /**
98
+ * Rename a collection
99
+ */
100
+ export declare function renameCollection(oldName: string, newName: string): boolean;
101
+ /**
102
+ * Get global context
103
+ */
104
+ export declare function getGlobalContext(): string | undefined;
105
+ /**
106
+ * Set global context
107
+ */
108
+ export declare function setGlobalContext(context: string | undefined): void;
109
+ /**
110
+ * Get all contexts for a collection
111
+ */
112
+ export declare function getContexts(collectionName: string): ContextMap | undefined;
113
+ /**
114
+ * Add or update a context for a specific path in a collection
115
+ */
116
+ export declare function addContext(collectionName: string, pathPrefix: string, contextText: string): boolean;
117
+ /**
118
+ * Remove a context from a collection
119
+ */
120
+ export declare function removeContext(collectionName: string, pathPrefix: string): boolean;
121
+ /**
122
+ * List all contexts across all collections
123
+ */
124
+ export declare function listAllContexts(): Array<{
125
+ collection: string;
126
+ path: string;
127
+ context: string;
128
+ }>;
129
+ /**
130
+ * Find best matching context for a given collection and path
131
+ * Returns the most specific matching context (longest path prefix match)
132
+ */
133
+ export declare function findContextForPath(collectionName: string, filePath: string): string | undefined;
134
+ /**
135
+ * Get the config file path (useful for error messages)
136
+ */
137
+ export declare function getConfigPath(): string;
138
+ /**
139
+ * Check if config file exists
140
+ */
141
+ export declare function configExists(): boolean;
142
+ /**
143
+ * Validate a collection name
144
+ * Collection names must be valid and not contain special characters
145
+ */
146
+ export declare function isValidCollectionName(name: string): boolean;
@@ -0,0 +1,385 @@
1
+ /**
2
+ * Collections configuration management
3
+ *
4
+ * This module manages the YAML-based collection configuration at ~/.config/qmd/index.yml.
5
+ * Collections define which directories to index and their associated contexts.
6
+ */
7
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
8
+ import { join, dirname } from "path";
9
+ import { homedir } from "os";
10
+ import YAML from "yaml";
11
+ // ============================================================================
12
+ // Configuration paths
13
+ // ============================================================================
14
+ // Current index name (default: "index")
15
+ let currentIndexName = "index";
16
+ // SDK mode: optional in-memory config or custom config path
17
+ let configSource = { type: 'file' };
18
+ /**
19
+ * Set the config source for SDK mode.
20
+ * - File path: load/save from a specific YAML file
21
+ * - Inline config: use an in-memory CollectionConfig (saveConfig updates in place, no file I/O)
22
+ * - undefined: reset to default file-based config
23
+ */
24
+ export function setConfigSource(source) {
25
+ if (!source) {
26
+ configSource = { type: 'file' };
27
+ return;
28
+ }
29
+ if (source.config) {
30
+ // Ensure collections object exists
31
+ if (!source.config.collections) {
32
+ source.config.collections = {};
33
+ }
34
+ configSource = { type: 'inline', config: source.config };
35
+ }
36
+ else if (source.configPath) {
37
+ configSource = { type: 'file', path: source.configPath };
38
+ }
39
+ else {
40
+ configSource = { type: 'file' };
41
+ }
42
+ }
43
+ /**
44
+ * Set the current index name for config file lookup
45
+ * Config file will be ~/.config/qmd/{indexName}.yml
46
+ */
47
+ export function setConfigIndexName(name) {
48
+ // Resolve relative paths to absolute paths and sanitize for use as filename
49
+ if (name.includes('/')) {
50
+ const { resolve } = require('path');
51
+ const { cwd } = require('process');
52
+ const absolutePath = resolve(cwd(), name);
53
+ // Replace path separators with underscores to create a valid filename
54
+ currentIndexName = absolutePath.replace(/\//g, '_').replace(/^_/, '');
55
+ }
56
+ else {
57
+ currentIndexName = name;
58
+ }
59
+ }
60
+ function getConfigDir() {
61
+ // Allow override via QMD_CONFIG_DIR for testing
62
+ if (process.env.QMD_CONFIG_DIR) {
63
+ return process.env.QMD_CONFIG_DIR;
64
+ }
65
+ // Respect XDG Base Directory specification (consistent with store.ts)
66
+ if (process.env.XDG_CONFIG_HOME) {
67
+ return join(process.env.XDG_CONFIG_HOME, "qmd");
68
+ }
69
+ return join(homedir(), ".config", "qmd");
70
+ }
71
+ function getConfigFilePath() {
72
+ return join(getConfigDir(), `${currentIndexName}.yml`);
73
+ }
74
+ /**
75
+ * Ensure config directory exists
76
+ */
77
+ function ensureConfigDir() {
78
+ const configDir = getConfigDir();
79
+ if (!existsSync(configDir)) {
80
+ mkdirSync(configDir, { recursive: true });
81
+ }
82
+ }
83
+ // ============================================================================
84
+ // Core functions
85
+ // ============================================================================
86
+ /**
87
+ * Load configuration from the configured source.
88
+ * - Inline config: returns the in-memory object directly
89
+ * - File-based: reads from YAML file (default ~/.config/qmd/index.yml)
90
+ * Returns empty config if file doesn't exist
91
+ */
92
+ export function loadConfig() {
93
+ // SDK inline config mode
94
+ if (configSource.type === 'inline') {
95
+ return configSource.config;
96
+ }
97
+ // File-based config (SDK custom path or default)
98
+ const configPath = configSource.path || getConfigFilePath();
99
+ if (!existsSync(configPath)) {
100
+ return { collections: {} };
101
+ }
102
+ try {
103
+ const content = readFileSync(configPath, "utf-8");
104
+ const config = YAML.parse(content);
105
+ // Ensure collections object exists
106
+ if (!config.collections) {
107
+ config.collections = {};
108
+ }
109
+ return config;
110
+ }
111
+ catch (error) {
112
+ throw new Error(`Failed to parse ${configPath}: ${error}`);
113
+ }
114
+ }
115
+ /**
116
+ * Save configuration to the configured source.
117
+ * - Inline config: updates the in-memory object (no file I/O)
118
+ * - File-based: writes to YAML file (default ~/.config/qmd/index.yml)
119
+ */
120
+ export function saveConfig(config) {
121
+ // SDK inline config mode: update in place, no file I/O
122
+ if (configSource.type === 'inline') {
123
+ configSource.config = config;
124
+ return;
125
+ }
126
+ const configPath = configSource.path || getConfigFilePath();
127
+ const configDir = dirname(configPath);
128
+ if (!existsSync(configDir)) {
129
+ mkdirSync(configDir, { recursive: true });
130
+ }
131
+ try {
132
+ const yaml = YAML.stringify(config, {
133
+ indent: 2,
134
+ lineWidth: 0, // Don't wrap lines
135
+ });
136
+ writeFileSync(configPath, yaml, "utf-8");
137
+ }
138
+ catch (error) {
139
+ throw new Error(`Failed to write ${configPath}: ${error}`);
140
+ }
141
+ }
142
+ /**
143
+ * Get a specific collection by name
144
+ * Returns null if not found
145
+ */
146
+ export function getCollection(name) {
147
+ const config = loadConfig();
148
+ const collection = config.collections[name];
149
+ if (!collection) {
150
+ return null;
151
+ }
152
+ return { name, ...collection };
153
+ }
154
+ /**
155
+ * List all collections
156
+ */
157
+ export function listCollections() {
158
+ const config = loadConfig();
159
+ return Object.entries(config.collections).map(([name, collection]) => ({
160
+ name,
161
+ ...collection,
162
+ }));
163
+ }
164
+ /**
165
+ * Get collections that are included by default in queries
166
+ */
167
+ export function getDefaultCollections() {
168
+ return listCollections().filter(c => c.includeByDefault !== false);
169
+ }
170
+ /**
171
+ * Get collection names that are included by default
172
+ */
173
+ export function getDefaultCollectionNames() {
174
+ return getDefaultCollections().map(c => c.name);
175
+ }
176
+ /**
177
+ * Update a collection's settings
178
+ */
179
+ export function updateCollectionSettings(name, settings) {
180
+ const config = loadConfig();
181
+ const collection = config.collections[name];
182
+ if (!collection)
183
+ return false;
184
+ if (settings.update !== undefined) {
185
+ if (settings.update === null) {
186
+ delete collection.update;
187
+ }
188
+ else {
189
+ collection.update = settings.update;
190
+ }
191
+ }
192
+ if (settings.includeByDefault !== undefined) {
193
+ if (settings.includeByDefault === true) {
194
+ // true is default, remove the field
195
+ delete collection.includeByDefault;
196
+ }
197
+ else {
198
+ collection.includeByDefault = settings.includeByDefault;
199
+ }
200
+ }
201
+ saveConfig(config);
202
+ return true;
203
+ }
204
+ /**
205
+ * Add or update a collection
206
+ */
207
+ export function addCollection(name, path, pattern = "**/*.{md,org}") {
208
+ const config = loadConfig();
209
+ config.collections[name] = {
210
+ path,
211
+ pattern,
212
+ context: config.collections[name]?.context, // Preserve existing context
213
+ };
214
+ saveConfig(config);
215
+ }
216
+ /**
217
+ * Remove a collection
218
+ */
219
+ export function removeCollection(name) {
220
+ const config = loadConfig();
221
+ if (!config.collections[name]) {
222
+ return false;
223
+ }
224
+ delete config.collections[name];
225
+ saveConfig(config);
226
+ return true;
227
+ }
228
+ /**
229
+ * Rename a collection
230
+ */
231
+ export function renameCollection(oldName, newName) {
232
+ const config = loadConfig();
233
+ if (!config.collections[oldName]) {
234
+ return false;
235
+ }
236
+ if (config.collections[newName]) {
237
+ throw new Error(`Collection '${newName}' already exists`);
238
+ }
239
+ config.collections[newName] = config.collections[oldName];
240
+ delete config.collections[oldName];
241
+ saveConfig(config);
242
+ return true;
243
+ }
244
+ // ============================================================================
245
+ // Context management
246
+ // ============================================================================
247
+ /**
248
+ * Get global context
249
+ */
250
+ export function getGlobalContext() {
251
+ const config = loadConfig();
252
+ return config.global_context;
253
+ }
254
+ /**
255
+ * Set global context
256
+ */
257
+ export function setGlobalContext(context) {
258
+ const config = loadConfig();
259
+ config.global_context = context;
260
+ saveConfig(config);
261
+ }
262
+ /**
263
+ * Get all contexts for a collection
264
+ */
265
+ export function getContexts(collectionName) {
266
+ const collection = getCollection(collectionName);
267
+ return collection?.context;
268
+ }
269
+ /**
270
+ * Add or update a context for a specific path in a collection
271
+ */
272
+ export function addContext(collectionName, pathPrefix, contextText) {
273
+ const config = loadConfig();
274
+ const collection = config.collections[collectionName];
275
+ if (!collection) {
276
+ return false;
277
+ }
278
+ if (!collection.context) {
279
+ collection.context = {};
280
+ }
281
+ collection.context[pathPrefix] = contextText;
282
+ saveConfig(config);
283
+ return true;
284
+ }
285
+ /**
286
+ * Remove a context from a collection
287
+ */
288
+ export function removeContext(collectionName, pathPrefix) {
289
+ const config = loadConfig();
290
+ const collection = config.collections[collectionName];
291
+ if (!collection?.context?.[pathPrefix]) {
292
+ return false;
293
+ }
294
+ delete collection.context[pathPrefix];
295
+ // Remove empty context object
296
+ if (Object.keys(collection.context).length === 0) {
297
+ delete collection.context;
298
+ }
299
+ saveConfig(config);
300
+ return true;
301
+ }
302
+ /**
303
+ * List all contexts across all collections
304
+ */
305
+ export function listAllContexts() {
306
+ const config = loadConfig();
307
+ const results = [];
308
+ // Add global context if present
309
+ if (config.global_context) {
310
+ results.push({
311
+ collection: "*",
312
+ path: "/",
313
+ context: config.global_context,
314
+ });
315
+ }
316
+ // Add collection contexts
317
+ for (const [name, collection] of Object.entries(config.collections)) {
318
+ if (collection.context) {
319
+ for (const [path, context] of Object.entries(collection.context)) {
320
+ results.push({
321
+ collection: name,
322
+ path,
323
+ context,
324
+ });
325
+ }
326
+ }
327
+ }
328
+ return results;
329
+ }
330
+ /**
331
+ * Find best matching context for a given collection and path
332
+ * Returns the most specific matching context (longest path prefix match)
333
+ */
334
+ export function findContextForPath(collectionName, filePath) {
335
+ const config = loadConfig();
336
+ const collection = config.collections[collectionName];
337
+ if (!collection?.context) {
338
+ return config.global_context;
339
+ }
340
+ // Find all matching prefixes
341
+ const matches = [];
342
+ for (const [prefix, context] of Object.entries(collection.context)) {
343
+ // Normalize paths for comparison
344
+ const normalizedPath = filePath.startsWith("/") ? filePath : `/${filePath}`;
345
+ const normalizedPrefix = prefix.startsWith("/") ? prefix : `/${prefix}`;
346
+ if (normalizedPath.startsWith(normalizedPrefix)) {
347
+ matches.push({ prefix: normalizedPrefix, context });
348
+ }
349
+ }
350
+ // Return most specific match (longest prefix)
351
+ if (matches.length > 0) {
352
+ matches.sort((a, b) => b.prefix.length - a.prefix.length);
353
+ return matches[0].context;
354
+ }
355
+ // Fallback to global context
356
+ return config.global_context;
357
+ }
358
+ // ============================================================================
359
+ // Utility functions
360
+ // ============================================================================
361
+ /**
362
+ * Get the config file path (useful for error messages)
363
+ */
364
+ export function getConfigPath() {
365
+ if (configSource.type === 'inline')
366
+ return '<inline>';
367
+ return configSource.path || getConfigFilePath();
368
+ }
369
+ /**
370
+ * Check if config file exists
371
+ */
372
+ export function configExists() {
373
+ if (configSource.type === 'inline')
374
+ return true;
375
+ const path = configSource.path || getConfigFilePath();
376
+ return existsSync(path);
377
+ }
378
+ /**
379
+ * Validate a collection name
380
+ * Collection names must be valid and not contain special characters
381
+ */
382
+ export function isValidCollectionName(name) {
383
+ // Allow alphanumeric, hyphens, underscores
384
+ return /^[a-zA-Z0-9_-]+$/.test(name);
385
+ }
package/dist/db.d.ts ADDED
@@ -0,0 +1,41 @@
1
+ /**
2
+ * db.ts - Cross-runtime SQLite compatibility layer
3
+ *
4
+ * Provides a unified Database export that works under both Bun (bun:sqlite)
5
+ * and Node.js (better-sqlite3). The APIs are nearly identical — the main
6
+ * difference is the import path.
7
+ *
8
+ * On macOS, Apple's system SQLite is compiled with SQLITE_OMIT_LOAD_EXTENSION,
9
+ * which prevents loading native extensions like sqlite-vec. When running under
10
+ * Bun we call Database.setCustomSQLite() to swap in Homebrew's full-featured
11
+ * SQLite build before creating any database instances.
12
+ */
13
+ export declare const isBun: boolean;
14
+ /**
15
+ * Open a SQLite database. Works with both bun:sqlite and better-sqlite3.
16
+ */
17
+ export declare function openDatabase(path: string): Database;
18
+ /**
19
+ * Common subset of the Database interface used throughout QMD.
20
+ */
21
+ export interface Database {
22
+ exec(sql: string): void;
23
+ prepare(sql: string): Statement;
24
+ loadExtension(path: string): void;
25
+ close(): void;
26
+ }
27
+ export interface Statement {
28
+ run(...params: any[]): {
29
+ changes: number;
30
+ lastInsertRowid: number | bigint;
31
+ };
32
+ get(...params: any[]): any;
33
+ all(...params: any[]): any[];
34
+ }
35
+ /**
36
+ * Load the sqlite-vec extension into a database.
37
+ *
38
+ * Throws with platform-specific fix instructions when the extension is
39
+ * unavailable.
40
+ */
41
+ export declare function loadSqliteVec(db: Database): void;
package/dist/db.js ADDED
@@ -0,0 +1,75 @@
1
+ /**
2
+ * db.ts - Cross-runtime SQLite compatibility layer
3
+ *
4
+ * Provides a unified Database export that works under both Bun (bun:sqlite)
5
+ * and Node.js (better-sqlite3). The APIs are nearly identical — the main
6
+ * difference is the import path.
7
+ *
8
+ * On macOS, Apple's system SQLite is compiled with SQLITE_OMIT_LOAD_EXTENSION,
9
+ * which prevents loading native extensions like sqlite-vec. When running under
10
+ * Bun we call Database.setCustomSQLite() to swap in Homebrew's full-featured
11
+ * SQLite build before creating any database instances.
12
+ */
13
+ export const isBun = typeof globalThis.Bun !== "undefined";
14
+ let _Database;
15
+ let _sqliteVecLoad;
16
+ if (isBun) {
17
+ // Dynamic string prevents tsc from resolving bun:sqlite on Node.js builds
18
+ const bunSqlite = "bun:" + "sqlite";
19
+ const BunDatabase = (await import(/* @vite-ignore */ bunSqlite)).Database;
20
+ // See: https://bun.com/docs/runtime/sqlite#setcustomsqlite
21
+ if (process.platform === "darwin") {
22
+ const homebrewPaths = [
23
+ "/opt/homebrew/opt/sqlite/lib/libsqlite3.dylib", // Apple Silicon
24
+ "/usr/local/opt/sqlite/lib/libsqlite3.dylib", // Intel
25
+ ];
26
+ for (const p of homebrewPaths) {
27
+ try {
28
+ BunDatabase.setCustomSQLite(p);
29
+ break;
30
+ }
31
+ catch { }
32
+ }
33
+ }
34
+ _Database = BunDatabase;
35
+ // setCustomSQLite may have silently failed — test that extensions actually work.
36
+ try {
37
+ const { getLoadablePath } = await import("sqlite-vec");
38
+ const vecPath = getLoadablePath();
39
+ const testDb = new BunDatabase(":memory:");
40
+ testDb.loadExtension(vecPath);
41
+ testDb.close();
42
+ _sqliteVecLoad = (db) => db.loadExtension(vecPath);
43
+ }
44
+ catch {
45
+ // Vector search won't work, but BM25 and other operations are unaffected.
46
+ _sqliteVecLoad = null;
47
+ }
48
+ }
49
+ else {
50
+ _Database = (await import("better-sqlite3")).default;
51
+ const sqliteVec = await import("sqlite-vec");
52
+ _sqliteVecLoad = (db) => sqliteVec.load(db);
53
+ }
54
+ /**
55
+ * Open a SQLite database. Works with both bun:sqlite and better-sqlite3.
56
+ */
57
+ export function openDatabase(path) {
58
+ return new _Database(path);
59
+ }
60
+ /**
61
+ * Load the sqlite-vec extension into a database.
62
+ *
63
+ * Throws with platform-specific fix instructions when the extension is
64
+ * unavailable.
65
+ */
66
+ export function loadSqliteVec(db) {
67
+ if (!_sqliteVecLoad) {
68
+ const hint = isBun && process.platform === "darwin"
69
+ ? "On macOS with Bun, install Homebrew SQLite: brew install sqlite\n" +
70
+ "Or install qmd with npm instead: npm install -g @tobilu/qmd"
71
+ : "Ensure the sqlite-vec native module is installed correctly.";
72
+ throw new Error(`sqlite-vec extension is unavailable. ${hint}`);
73
+ }
74
+ _sqliteVecLoad(db);
75
+ }
@@ -0,0 +1,6 @@
1
+ export type EmbeddedSkillFile = {
2
+ relativePath: string;
3
+ content: string;
4
+ };
5
+ export declare function getEmbeddedQmdSkillFiles(): EmbeddedSkillFile[];
6
+ export declare function getEmbeddedQmdSkillContent(): string;
@@ -0,0 +1,14 @@
1
+ // Generated from skills/qmd source files. Keep this in sync when updating the packaged skill.
2
+ const EMBEDDED_QMD_SKILL_BASE64 = {
3
+ "SKILL.md": "LS0tCm5hbWU6IHFtZApkZXNjcmlwdGlvbjogU2VhcmNoIG1hcmtkb3duIGtub3dsZWRnZSBiYXNlcywgbm90ZXMsIGFuZCBkb2N1bWVudGF0aW9uIHVzaW5nIFFNRC4gVXNlIHdoZW4gdXNlcnMgYXNrIHRvIHNlYXJjaCBub3RlcywgZmluZCBkb2N1bWVudHMsIG9yIGxvb2sgdXAgaW5mb3JtYXRpb24uCmxpY2Vuc2U6IE1JVApjb21wYXRpYmlsaXR5OiBSZXF1aXJlcyBxbWQgQ0xJIG9yIE1DUCBzZXJ2ZXIuIEluc3RhbGwgdmlhIGBucG0gaW5zdGFsbCAtZyBAdG9iaWx1L3FtZGAuCm1ldGFkYXRhOgogIGF1dGhvcjogdG9iaQogIHZlcnNpb246ICIyLjAuMCIKYWxsb3dlZC10b29sczogQmFzaChxbWQ6KiksIG1jcF9fcW1kX18qCi0tLQoKIyBRTUQgLSBRdWljayBNYXJrZG93biBTZWFyY2gKCkxvY2FsIHNlYXJjaCBlbmdpbmUgZm9yIG1hcmtkb3duIGNvbnRlbnQuCgojIyBTdGF0dXMKCiFgcW1kIHN0YXR1cyAyPi9kZXYvbnVsbCB8fCBlY2hvICJOb3QgaW5zdGFsbGVkOiBucG0gaW5zdGFsbCAtZyBAdG9iaWx1L3FtZCJgCgojIyBNQ1A6IGBxdWVyeWAKCmBgYGpzb24KewogICJzZWFyY2hlcyI6IFsKICAgIHsgInR5cGUiOiAibGV4IiwgInF1ZXJ5IjogIkNBUCB0aGVvcmVtIGNvbnNpc3RlbmN5IiB9LAogICAgeyAidHlwZSI6ICJ2ZWMiLCAicXVlcnkiOiAidHJhZGVvZmYgYmV0d2VlbiBjb25zaXN0ZW5jeSBhbmQgYXZhaWxhYmlsaXR5IiB9CiAgXSwKICAiY29sbGVjdGlvbnMiOiBbImRvY3MiXSwKICAibGltaXQiOiAxMAp9CmBgYAoKIyMjIFF1ZXJ5IFR5cGVzCgp8IFR5cGUgfCBNZXRob2QgfCBJbnB1dCB8CnwtLS0tLS18LS0tLS0tLS18LS0tLS0tLXwKfCBgbGV4YCB8IEJNMjUgfCBLZXl3b3JkcyDigJQgZXhhY3QgdGVybXMsIG5hbWVzLCBjb2RlIHwKfCBgdmVjYCB8IFZlY3RvciB8IFF1ZXN0aW9uIOKAlCBuYXR1cmFsIGxhbmd1YWdlIHwKfCBgaHlkZWAgfCBWZWN0b3IgfCBBbnN3ZXIg4oCUIGh5cG90aGV0aWNhbCByZXN1bHQgKDUwLTEwMCB3b3JkcykgfAoKIyMjIFdyaXRpbmcgR29vZCBRdWVyaWVzCgoqKmxleCAoa2V5d29yZCkqKgotIDItNSB0ZXJtcywgbm8gZmlsbGVyIHdvcmRzCi0gRXhhY3QgcGhyYXNlOiBgImNvbm5lY3Rpb24gcG9vbCJgIChxdW90ZWQpCi0gRXhjbHVkZSB0ZXJtczogYHBlcmZvcm1hbmNlIC1zcG9ydHNgIChtaW51cyBwcmVmaXgpCi0gQ29kZSBpZGVudGlmaWVycyB3b3JrOiBgaGFuZGxlRXJyb3IgYXN5bmNgCgoqKnZlYyAoc2VtYW50aWMpKioKLSBGdWxsIG5hdHVyYWwgbGFuZ3VhZ2UgcXVlc3Rpb24KLSBCZSBzcGVjaWZpYzogYCJob3cgZG9lcyB0aGUgcmF0ZSBsaW1pdGVyIGhhbmRsZSBidXJzdCB0cmFmZmljImAKLSBJbmNsdWRlIGNvbnRleHQ6IGAiaW4gdGhlIHBheW1lbnQgc2VydmljZSwgaG93IGFyZSByZWZ1bmRzIHByb2Nlc3NlZCJgCgoqKmh5ZGUgKGh5cG90aGV0aWNhbCBkb2N1bWVudCkqKgotIFdyaXRlIDUwLTEwMCB3b3JkcyBvZiB3aGF0IHRoZSAqYW5zd2VyKiBsb29rcyBsaWtlCi0gVXNlIHRoZSB2b2NhYnVsYXJ5IHlvdSBleHBlY3QgaW4gdGhlIHJlc3VsdAoKKipleHBhbmQgKGF1dG8tZXhwYW5kKSoqCi0gVXNlIGEgc2luZ2xlLWxpbmUgcXVlcnkgKGltcGxpY2l0KSBvciBgZXhwYW5kOiBxdWVzdGlvbmAgb24gaXRzIG93biBsaW5lCi0gTGV0cyB0aGUgbG9jYWwgTExNIGdlbmVyYXRlIGxleC92ZWMvaHlkZSB2YXJpYXRpb25zCi0gRG8gbm90IG1peCBgZXhwYW5kOmAgd2l0aCBvdGhlciB0eXBlZCBsaW5lcyDigJQgaXQncyBlaXRoZXIgYSBzdGFuZGFsb25lIGV4cGFuZCBxdWVyeSBvciBhIGZ1bGwgcXVlcnkgZG9jdW1lbnQKCiMjIyBJbnRlbnQgKERpc2FtYmlndWF0aW9uKQoKV2hlbiBhIHF1ZXJ5IHRlcm0gaXMgYW1iaWd1b3VzLCBhZGQgYGludGVudGAgdG8gc3RlZXIgcmVzdWx0czoKCmBgYGpzb24KewogICJzZWFyY2hlcyI6IFsKICAgIHsgInR5cGUiOiAibGV4IiwgInF1ZXJ5IjogInBlcmZvcm1hbmNlIiB9CiAgXSwKICAiaW50ZW50IjogIndlYiBwYWdlIGxvYWQgdGltZXMgYW5kIENvcmUgV2ViIFZpdGFscyIKfQpgYGAKCkludGVudCBhZmZlY3RzIGV4cGFuc2lvbiwgcmVyYW5raW5nLCBjaHVuayBzZWxlY3Rpb24sIGFuZCBzbmlwcGV0IGV4dHJhY3Rpb24uIEl0IGRvZXMgbm90IHNlYXJjaCBvbiBpdHMgb3duIOKAlCBpdCdzIGEgc3RlZXJpbmcgc2lnbmFsIHRoYXQgZGlzYW1iaWd1YXRlcyBxdWVyaWVzIGxpa2UgInBlcmZvcm1hbmNlIiAod2ViLXBlcmYgdnMgdGVhbSBoZWFsdGggdnMgZml0bmVzcykuCgojIyMgQ29tYmluaW5nIFR5cGVzCgp8IEdvYWwgfCBBcHByb2FjaCB8CnwtLS0tLS18LS0tLS0tLS0tLXwKfCBLbm93IGV4YWN0IHRlcm1zIHwgYGxleGAgb25seSB8CnwgRG9uJ3Qga25vdyB2b2NhYnVsYXJ5IHwgVXNlIGEgc2luZ2xlLWxpbmUgcXVlcnkgKGltcGxpY2l0IGBleHBhbmQ6YCkgb3IgYHZlY2AgfAp8IEJlc3QgcmVjYWxsIHwgYGxleGAgKyBgdmVjYCB8CnwgQ29tcGxleCB0b3BpYyB8IGBsZXhgICsgYHZlY2AgKyBgaHlkZWAgfAp8IEFtYmlndW91cyBxdWVyeSB8IEFkZCBgaW50ZW50YCB0byBhbnkgY29tYmluYXRpb24gYWJvdmUgfAoKRmlyc3QgcXVlcnkgZ2V0cyAyeCB3ZWlnaHQgaW4gZnVzaW9uIOKAlCBwdXQgeW91ciBiZXN0IGd1ZXNzIGZpcnN0LgoKIyMjIExleCBRdWVyeSBTeW50YXgKCnwgU3ludGF4IHwgTWVhbmluZyB8IEV4YW1wbGUgfAp8LS0tLS0tLS18LS0tLS0tLS0tfC0tLS0tLS0tLXwKfCBgdGVybWAgfCBQcmVmaXggbWF0Y2ggfCBgcGVyZmAgbWF0Y2hlcyAicGVyZm9ybWFuY2UiIHwKfCBgInBocmFzZSJgIHwgRXhhY3QgcGhyYXNlIHwgYCJyYXRlIGxpbWl0ZXIiYCB8CnwgYC10ZXJtYCB8IEV4Y2x1ZGUgfCBgcGVyZm9ybWFuY2UgLXNwb3J0c2AgfAoKTm90ZTogYC10ZXJtYCBvbmx5IHdvcmtzIGluIGxleCBxdWVyaWVzLCBub3QgdmVjL2h5ZGUuCgojIyMgQ29sbGVjdGlvbiBGaWx0ZXJpbmcKCmBgYGpzb24KeyAiY29sbGVjdGlvbnMiOiBbImRvY3MiXSB9ICAgICAgICAgICAgICAvLyBTaW5nbGUKeyAiY29sbGVjdGlvbnMiOiBbImRvY3MiLCAibm90ZXMiXSB9ICAgICAvLyBNdWx0aXBsZSAoT1IpCmBgYAoKT21pdCB0byBzZWFyY2ggYWxsIGNvbGxlY3Rpb25zLgoKIyMgT3RoZXIgTUNQIFRvb2xzCgp8IFRvb2wgfCBVc2UgfAp8LS0tLS0tfC0tLS0tfAp8IGBnZXRgIHwgUmV0cmlldmUgZG9jIGJ5IHBhdGggb3IgYCNkb2NpZGAgfAp8IGBtdWx0aV9nZXRgIHwgUmV0cmlldmUgbXVsdGlwbGUgYnkgZ2xvYi9saXN0IHwKfCBgc3RhdHVzYCB8IENvbGxlY3Rpb25zIGFuZCBoZWFsdGggfAoKIyMgQ0xJCgpgYGBiYXNoCnFtZCBxdWVyeSAicXVlc3Rpb24iICAgICAgICAgICAgICAjIEF1dG8tZXhwYW5kICsgcmVyYW5rCnFtZCBxdWVyeSAkJ2xleDogWFxudmVjOiBZJyAgICAgICAjIFN0cnVjdHVyZWQKcW1kIHF1ZXJ5ICQnZXhwYW5kOiBxdWVzdGlvbicgICAgICMgRXhwbGljaXQgZXhwYW5kCnFtZCBxdWVyeSAtLWpzb24gLS1leHBsYWluICJxIiAgICAjIFNob3cgc2NvcmUgdHJhY2VzIChSUkYgKyByZXJhbmsgYmxlbmQpCnFtZCBzZWFyY2ggImtleXdvcmRzIiAgICAgICAgICAgICAjIEJNMjUgb25seSAobm8gTExNKQpxbWQgZ2V0ICIjYWJjMTIzIiAgICAgICAgICAgICAgICAgIyBCeSBkb2NpZApxbWQgbXVsdGktZ2V0ICJqb3VybmFscy8yMDI2LSoubWQiIC1sIDQwICAjIEJhdGNoIHB1bGwgc25pcHBldHMgYnkgZ2xvYgpxbWQgbXVsdGktZ2V0IG5vdGVzL2Zvby5tZCxub3Rlcy9iYXIubWQgICAjIENvbW1hLXNlcGFyYXRlZCBsaXN0LCBwcmVzZXJ2ZXMgb3JkZXIKYGBgCgojIyBIVFRQIEFQSQoKYGBgYmFzaApjdXJsIC1YIFBPU1QgaHR0cDovL2xvY2FsaG9zdDo4MTgxL3F1ZXJ5IFwKICAtSCAiQ29udGVudC1UeXBlOiBhcHBsaWNhdGlvbi9qc29uIiBcCiAgLWQgJ3sic2VhcmNoZXMiOiBbeyJ0eXBlIjogImxleCIsICJxdWVyeSI6ICJ0ZXN0In1dfScKYGBgCgojIyBTZXR1cAoKYGBgYmFzaApucG0gaW5zdGFsbCAtZyBAdG9iaWx1L3FtZApxbWQgY29sbGVjdGlvbiBhZGQgfi9ub3RlcyAtLW5hbWUgbm90ZXMKcW1kIGVtYmVkCmBgYAo=",
4
+ "references/mcp-setup.md": "IyBRTUQgTUNQIFNlcnZlciBTZXR1cAoKIyMgSW5zdGFsbAoKYGBgYmFzaApucG0gaW5zdGFsbCAtZyBAdG9iaWx1L3FtZApxbWQgY29sbGVjdGlvbiBhZGQgfi9wYXRoL3RvL21hcmtkb3duIC0tbmFtZSBteWtub3dsZWRnZQpxbWQgZW1iZWQKYGBgCgojIyBDb25maWd1cmUgTUNQIENsaWVudAoKKipDbGF1ZGUgQ29kZSoqIChgfi8uY2xhdWRlL3NldHRpbmdzLmpzb25gKToKYGBganNvbgp7CiAgIm1jcFNlcnZlcnMiOiB7CiAgICAicW1kIjogeyAiY29tbWFuZCI6ICJxbWQiLCAiYXJncyI6IFsibWNwIl0gfQogIH0KfQpgYGAKCioqQ2xhdWRlIERlc2t0b3AqKiAoYH4vTGlicmFyeS9BcHBsaWNhdGlvbiBTdXBwb3J0L0NsYXVkZS9jbGF1ZGVfZGVza3RvcF9jb25maWcuanNvbmApOgpgYGBqc29uCnsKICAibWNwU2VydmVycyI6IHsKICAgICJxbWQiOiB7ICJjb21tYW5kIjogInFtZCIsICJhcmdzIjogWyJtY3AiXSB9CiAgfQp9CmBgYAoKKipPcGVuQ2xhdyoqIChgfi8ub3BlbmNsYXcvb3BlbmNsYXcuanNvbmApOgpgYGBqc29uCnsKICAibWNwIjogewogICAgInNlcnZlcnMiOiB7CiAgICAgICJxbWQiOiB7ICJjb21tYW5kIjogInFtZCIsICJhcmdzIjogWyJtY3AiXSB9CiAgICB9CiAgfQp9CmBgYAoKIyMgSFRUUCBNb2RlCgpgYGBiYXNoCnFtZCBtY3AgLS1odHRwICAgICAgICAgICAgICAjIFBvcnQgODE4MQpxbWQgbWNwIC0taHR0cCAtLWRhZW1vbiAgICAgIyBCYWNrZ3JvdW5kCnFtZCBtY3Agc3RvcCAgICAgICAgICAgICAgICAjIFN0b3AgZGFlbW9uCmBgYAoKIyMgVG9vbHMKCiMjIyBzdHJ1Y3R1cmVkX3NlYXJjaAoKU2VhcmNoIHdpdGggcHJlLWV4cGFuZGVkIHF1ZXJpZXMuCgpgYGBqc29uCnsKICAic2VhcmNoZXMiOiBbCiAgICB7ICJ0eXBlIjogImxleCIsICJxdWVyeSI6ICJrZXl3b3JkIHBocmFzZXMiIH0sCiAgICB7ICJ0eXBlIjogInZlYyIsICJxdWVyeSI6ICJuYXR1cmFsIGxhbmd1YWdlIHF1ZXN0aW9uIiB9LAogICAgeyAidHlwZSI6ICJoeWRlIiwgInF1ZXJ5IjogImh5cG90aGV0aWNhbCBhbnN3ZXIgcGFzc2FnZS4uLiIgfQogIF0sCiAgImxpbWl0IjogMTAsCiAgImNvbGxlY3Rpb24iOiAib3B0aW9uYWwiLAogICJtaW5TY29yZSI6IDAuMAp9CmBgYAoKfCBUeXBlIHwgTWV0aG9kIHwgSW5wdXQgfAp8LS0tLS0tfC0tLS0tLS0tfC0tLS0tLS18CnwgYGxleGAgfCBCTTI1IHwgS2V5d29yZHMgKDItNSB0ZXJtcykgfAp8IGB2ZWNgIHwgVmVjdG9yIHwgUXVlc3Rpb24gfAp8IGBoeWRlYCB8IFZlY3RvciB8IEFuc3dlciBwYXNzYWdlICg1MC0xMDAgd29yZHMpIHwKCiMjIyBnZXQKClJldHJpZXZlIGRvY3VtZW50IGJ5IHBhdGggb3IgYCNkb2NpZGAuCgp8IFBhcmFtIHwgVHlwZSB8IERlc2NyaXB0aW9uIHwKfC0tLS0tLS18LS0tLS0tfC0tLS0tLS0tLS0tLS18CnwgYHBhdGhgIHwgc3RyaW5nIHwgRmlsZSBwYXRoIG9yIGAjZG9jaWRgIHwKfCBgZnVsbGAgfCBib29sPyB8IFJldHVybiBmdWxsIGNvbnRlbnQgfAp8IGBsaW5lTnVtYmVyc2AgfCBib29sPyB8IEFkZCBsaW5lIG51bWJlcnMgfAoKIyMjIG11bHRpX2dldAoKUmV0cmlldmUgbXVsdGlwbGUgZG9jdW1lbnRzLgoKfCBQYXJhbSB8IFR5cGUgfCBEZXNjcmlwdGlvbiB8CnwtLS0tLS0tfC0tLS0tLXwtLS0tLS0tLS0tLS0tfAp8IGBwYXR0ZXJuYCB8IHN0cmluZyB8IEdsb2Igb3IgY29tbWEtc2VwYXJhdGVkIGxpc3QgfAp8IGBtYXhCeXRlc2AgfCBudW1iZXI/IHwgU2tpcCBsYXJnZSBmaWxlcyAoZGVmYXVsdCAxMEtCKSB8CgojIyMgc3RhdHVzCgpJbmRleCBoZWFsdGggYW5kIGNvbGxlY3Rpb25zLiBObyBwYXJhbXMuCgojIyBUcm91Ymxlc2hvb3RpbmcKCi0gKipOb3Qgc3RhcnRpbmcqKjogYHdoaWNoIHFtZGAsIGBxbWQgbWNwYCBtYW51YWxseQotICoqTm8gcmVzdWx0cyoqOiBgcW1kIGNvbGxlY3Rpb24gbGlzdGAsIGBxbWQgZW1iZWRgCi0gKipTbG93IGZpcnN0IHNlYXJjaCoqOiBOb3JtYWwsIG1vZGVscyBsb2FkaW5nICh+M0dCKQo="
5
+ };
6
+ export function getEmbeddedQmdSkillFiles() {
7
+ return Object.entries(EMBEDDED_QMD_SKILL_BASE64).map(([relativePath, encoded]) => ({
8
+ relativePath,
9
+ content: Buffer.from(encoded, 'base64').toString('utf8'),
10
+ }));
11
+ }
12
+ export function getEmbeddedQmdSkillContent() {
13
+ return Buffer.from(EMBEDDED_QMD_SKILL_BASE64["SKILL.md"], "base64").toString("utf8");
14
+ }