@tobilu/qmd 2.0.0 → 2.0.1

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/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [2.0.1] - 2026-03-10
6
+
7
+ ### Changes
8
+
9
+ - `qmd skill install` copies the packaged QMD skill into
10
+ `~/.claude/commands/` for one-command setup. #355 (thanks @nibzard)
11
+
12
+ ### Fixes
13
+
14
+ - Fix Qwen3-Embedding GGUF filename case — HuggingFace filenames are
15
+ case-sensitive, the lowercase variant returned 404. #349 (thanks @byheaven)
16
+ - Resolve symlinked global launcher path so `qmd` works correctly when
17
+ installed via `npm i -g`. #352 (thanks @nibzard)
18
+
5
19
  ## [2.0.0] - 2026-03-10
6
20
 
7
21
  QMD 2.0 declares a stable library API. The SDK is now the primary interface —
package/README.md CHANGED
@@ -500,7 +500,7 @@ This is useful for multilingual corpora (e.g. Chinese, Japanese, Korean) where
500
500
 
501
501
  ```sh
502
502
  # Use Qwen3-Embedding-0.6B for better multilingual (CJK) support
503
- export QMD_EMBED_MODEL="hf:Qwen/Qwen3-Embedding-0.6B-GGUF/qwen3-embedding-0.6b-q8_0.gguf"
503
+ export QMD_EMBED_MODEL="hf:Qwen/Qwen3-Embedding-0.6B-GGUF/Qwen3-Embedding-0.6B-Q8_0.gguf"
504
504
 
505
505
  # After changing the model, re-embed all collections:
506
506
  qmd embed -f
package/bin/qmd CHANGED
@@ -1,8 +1,19 @@
1
1
  #!/bin/sh
2
+ # Resolve symlinks so global installs (npm link / npm install -g) can find the
3
+ # actual package directory instead of the global bin directory.
4
+ SOURCE="$0"
5
+ while [ -L "$SOURCE" ]; do
6
+ SOURCE_DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
7
+ TARGET="$(readlink "$SOURCE")"
8
+ case "$TARGET" in
9
+ /*) SOURCE="$TARGET" ;;
10
+ *) SOURCE="$SOURCE_DIR/$TARGET" ;;
11
+ esac
12
+ done
13
+
2
14
  # Detect the runtime used to install this package and use the matching one
3
15
  # to avoid native module ABI mismatches (e.g., better-sqlite3 compiled for bun vs node)
4
-
5
- DIR="$(cd "$(dirname "$0")/.." && pwd)"
16
+ DIR="$(cd -P "$(dirname "$SOURCE")/.." && pwd)"
6
17
 
7
18
  # Check if we were installed with bun (look for bun.lock or bun-lockb)
8
19
  if [ -f "$DIR/bun.lock" ] || [ -f "$DIR/bun.lockb" ] || [ -n "$BUN_INSTALL" ]; then
package/dist/cli/qmd.js CHANGED
@@ -3,13 +3,15 @@ import { openDatabase } from "../db.js";
3
3
  import fastGlob from "fast-glob";
4
4
  import { execSync, spawn as nodeSpawn } from "child_process";
5
5
  import { fileURLToPath } from "url";
6
- import { dirname, join as pathJoin } from "path";
6
+ import { dirname, join as pathJoin, relative as relativePath } from "path";
7
7
  import { parseArgs } from "util";
8
- import { readFileSync, realpathSync, statSync, existsSync, unlinkSync, writeFileSync, openSync, closeSync, mkdirSync } from "fs";
8
+ import { readFileSync, realpathSync, statSync, existsSync, unlinkSync, writeFileSync, openSync, closeSync, mkdirSync, lstatSync, rmSync, symlinkSync, readlinkSync } from "fs";
9
+ import { createInterface } from "readline/promises";
9
10
  import { getPwd, getRealPath, homedir, resolve, enableProductionMode, searchFTS, extractSnippet, getContextForFile, getContextForPath, listCollections, removeCollection, renameCollection, findSimilarFiles, findDocumentByDocid, isDocid, matchFilesByGlob, getHashesNeedingEmbedding, getHashesForEmbedding, clearAllEmbeddings, insertEmbedding, getStatus, hashContent, extractTitle, formatDocForEmbedding, chunkDocumentByTokens, clearCache, getCacheKey, getCachedResult, setCachedResult, getIndexHealth, parseVirtualPath, buildVirtualPath, isVirtualPath, resolveVirtualPath, toVirtualPath, insertContent, insertDocument, findActiveDocument, updateDocumentTitle, updateDocument, deactivateDocument, getActiveDocumentPaths, cleanupOrphanedContent, deleteLLMCache, deleteInactiveDocuments, cleanupOrphanedVectors, vacuumDatabase, getCollectionsWithoutContext, getTopLevelPathsWithoutContext, handelize, hybridQuery, vectorSearchQuery, structuredSearch, addLineNumbers, DEFAULT_EMBED_MODEL, DEFAULT_RERANK_MODEL, DEFAULT_GLOB, DEFAULT_MULTI_GET_MAX_BYTES, createStore, getDefaultDbPath, reindexCollection, generateEmbeddings, syncConfigToDb, } from "../store.js";
10
11
  import { disposeDefaultLlamaCpp, getDefaultLlamaCpp, withLLMSession, pullModels, DEFAULT_EMBED_MODEL_URI, DEFAULT_GENERATE_MODEL_URI, DEFAULT_RERANK_MODEL_URI, DEFAULT_MODEL_CACHE_DIR } from "../llm.js";
11
12
  import { formatSearchResults, formatDocuments, escapeXml, escapeCSV, } from "./formatter.js";
12
13
  import { getCollection as getCollectionFromYaml, listCollections as yamlListCollections, getDefaultCollectionNames, addContext as yamlAddContext, removeContext as yamlRemoveContext, removeCollection as yamlRemoveCollectionFn, renameCollection as yamlRenameCollectionFn, setGlobalContext, listAllContexts, setConfigIndexName, loadConfig, } from "../collections.js";
14
+ import { getEmbeddedQmdSkillContent, getEmbeddedQmdSkillFiles } from "../embedded-skills.js";
13
15
  // Enable production mode - allows using default database path
14
16
  // Tests must set INDEX_PATH or use createStore() with explicit path
15
17
  enableProductionMode();
@@ -1969,6 +1971,8 @@ function parseCLI() {
1969
1971
  help: { type: "boolean", short: "h" },
1970
1972
  version: { type: "boolean", short: "v" },
1971
1973
  skill: { type: "boolean" },
1974
+ global: { type: "boolean" },
1975
+ yes: { type: "boolean" },
1972
1976
  // Search options
1973
1977
  n: { type: "string" },
1974
1978
  "min-score": { type: "string" },
@@ -2047,19 +2051,116 @@ function parseCLI() {
2047
2051
  values,
2048
2052
  };
2049
2053
  }
2054
+ function getSkillInstallDir(globalInstall) {
2055
+ return globalInstall
2056
+ ? resolve(homedir(), ".agents", "skills", "qmd")
2057
+ : resolve(getPwd(), ".agents", "skills", "qmd");
2058
+ }
2059
+ function getClaudeSkillLinkPath(globalInstall) {
2060
+ return globalInstall
2061
+ ? resolve(homedir(), ".claude", "skills", "qmd")
2062
+ : resolve(getPwd(), ".claude", "skills", "qmd");
2063
+ }
2064
+ function pathExists(path) {
2065
+ try {
2066
+ lstatSync(path);
2067
+ return true;
2068
+ }
2069
+ catch {
2070
+ return false;
2071
+ }
2072
+ }
2073
+ function removePath(path) {
2074
+ const stat = lstatSync(path);
2075
+ if (stat.isDirectory() && !stat.isSymbolicLink()) {
2076
+ rmSync(path, { recursive: true, force: true });
2077
+ }
2078
+ else {
2079
+ unlinkSync(path);
2080
+ }
2081
+ }
2050
2082
  function showSkill() {
2051
- const scriptDir = dirname(fileURLToPath(import.meta.url));
2052
- const relativePath = pathJoin("skills", "qmd", "SKILL.md");
2053
- const skillPath = pathJoin(scriptDir, "..", "..", relativePath);
2054
- console.log(`QMD Skill (${relativePath})`);
2055
- console.log(`Location: ${skillPath}`);
2083
+ console.log("QMD Skill (embedded)");
2056
2084
  console.log("");
2057
- if (!existsSync(skillPath)) {
2058
- console.error("SKILL.md not found. If you built from source, ensure skills/qmd/SKILL.md exists.");
2085
+ const content = getEmbeddedQmdSkillContent();
2086
+ process.stdout.write(content.endsWith("\n") ? content : content + "\n");
2087
+ }
2088
+ function writeEmbeddedSkill(targetDir, force) {
2089
+ if (pathExists(targetDir)) {
2090
+ if (!force) {
2091
+ throw new Error(`Skill already exists: ${targetDir} (use --force to replace it)`);
2092
+ }
2093
+ removePath(targetDir);
2094
+ }
2095
+ mkdirSync(targetDir, { recursive: true });
2096
+ for (const file of getEmbeddedQmdSkillFiles()) {
2097
+ const destination = resolve(targetDir, file.relativePath);
2098
+ mkdirSync(dirname(destination), { recursive: true });
2099
+ writeFileSync(destination, file.content, "utf-8");
2100
+ }
2101
+ }
2102
+ function ensureClaudeSymlink(linkPath, targetDir, force) {
2103
+ const parentDir = dirname(linkPath);
2104
+ if (pathExists(parentDir)) {
2105
+ const resolvedTargetDir = realpathSync(dirname(targetDir));
2106
+ const resolvedLinkParent = realpathSync(parentDir);
2107
+ // If .claude/skills already resolves to the same directory as .agents/skills,
2108
+ // the skill is already visible to Claude and creating qmd -> qmd would loop.
2109
+ if (resolvedTargetDir === resolvedLinkParent) {
2110
+ return false;
2111
+ }
2112
+ }
2113
+ const linkTarget = relativePath(parentDir, targetDir) || ".";
2114
+ mkdirSync(parentDir, { recursive: true });
2115
+ if (pathExists(linkPath)) {
2116
+ const stat = lstatSync(linkPath);
2117
+ if (stat.isSymbolicLink() && readlinkSync(linkPath) === linkTarget) {
2118
+ return true;
2119
+ }
2120
+ if (!force) {
2121
+ throw new Error(`Claude skill path already exists: ${linkPath} (use --force to replace it)`);
2122
+ }
2123
+ removePath(linkPath);
2124
+ }
2125
+ symlinkSync(linkTarget, linkPath, "dir");
2126
+ return true;
2127
+ }
2128
+ async function shouldCreateClaudeSymlink(linkPath, autoYes) {
2129
+ if (autoYes) {
2130
+ return true;
2131
+ }
2132
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
2133
+ console.log(`Tip: create a Claude symlink manually at ${linkPath}`);
2134
+ return false;
2135
+ }
2136
+ const rl = createInterface({
2137
+ input: process.stdin,
2138
+ output: process.stdout,
2139
+ });
2140
+ try {
2141
+ const answer = await rl.question(`Create a symlink in ${linkPath}? [y/N] `);
2142
+ const normalized = answer.trim().toLowerCase();
2143
+ return normalized === "y" || normalized === "yes";
2144
+ }
2145
+ finally {
2146
+ rl.close();
2147
+ }
2148
+ }
2149
+ async function installSkill(globalInstall, force, autoYes) {
2150
+ const installDir = getSkillInstallDir(globalInstall);
2151
+ writeEmbeddedSkill(installDir, force);
2152
+ console.log(`✓ Installed QMD skill to ${installDir}`);
2153
+ const claudeLinkPath = getClaudeSkillLinkPath(globalInstall);
2154
+ if (!(await shouldCreateClaudeSymlink(claudeLinkPath, autoYes))) {
2059
2155
  return;
2060
2156
  }
2061
- const content = readFileSync(skillPath, "utf-8");
2062
- process.stdout.write(content.endsWith("\n") ? content : content + "\n");
2157
+ const linked = ensureClaudeSymlink(claudeLinkPath, installDir, force);
2158
+ if (linked) {
2159
+ console.log(`✓ Linked Claude skill at ${claudeLinkPath}`);
2160
+ }
2161
+ else {
2162
+ console.log(`✓ Claude already sees the skill via ${dirname(claudeLinkPath)}`);
2163
+ }
2063
2164
  }
2064
2165
  function showHelp() {
2065
2166
  console.log("qmd — Quick Markdown Search");
@@ -2074,6 +2175,7 @@ function showHelp() {
2074
2175
  console.log(" qmd vsearch <query> - Vector similarity only");
2075
2176
  console.log(" qmd get <file>[:line] [-l N] - Show a single document, optional line slice");
2076
2177
  console.log(" qmd multi-get <pattern> - Batch fetch via glob or comma-separated list");
2178
+ console.log(" qmd skill show/install - Show or install the packaged QMD skill");
2077
2179
  console.log(" qmd mcp - Start the MCP server (stdio transport for AI agents)");
2078
2180
  console.log("");
2079
2181
  console.log("Collections & context:");
@@ -2123,7 +2225,9 @@ function showHelp() {
2123
2225
  console.log("");
2124
2226
  console.log("AI agents & integrations:");
2125
2227
  console.log(" - Run `qmd mcp` to expose the MCP server (stdio) to agents/IDEs.");
2126
- console.log(" - `qmd --skill` prints the packaged skills/qmd/SKILL.md (path + contents).");
2228
+ console.log(" - `qmd skill install` installs the QMD skill into ./.agents/skills/qmd.");
2229
+ console.log(" - Use `qmd skill install --global` for ~/.agents/skills/qmd.");
2230
+ console.log(" - `qmd --skill` is kept as an alias for `qmd skill show`.");
2127
2231
  console.log(" - Advanced: `qmd mcp --http ...` and `qmd mcp --http --daemon` are optional for custom transports.");
2128
2232
  console.log("");
2129
2233
  console.log("Global options:");
@@ -2178,6 +2282,19 @@ if (isMain) {
2178
2282
  showSkill();
2179
2283
  process.exit(0);
2180
2284
  }
2285
+ if (cli.values.help && cli.command === "skill") {
2286
+ console.log("Usage: qmd skill <show|install> [options]");
2287
+ console.log("");
2288
+ console.log("Commands:");
2289
+ console.log(" show Print the packaged QMD skill");
2290
+ console.log(" install Install into ./.agents/skills/qmd");
2291
+ console.log("");
2292
+ console.log("Options:");
2293
+ console.log(" --global Install into ~/.agents/skills/qmd");
2294
+ console.log(" --yes Also create the .claude/skills/qmd symlink");
2295
+ console.log(" -f, --force Replace existing install or symlink");
2296
+ process.exit(0);
2297
+ }
2181
2298
  if (!cli.command || cli.values.help) {
2182
2299
  showHelp();
2183
2300
  process.exit(cli.values.help ? 0 : 1);
@@ -2546,6 +2663,44 @@ if (isMain) {
2546
2663
  }
2547
2664
  break;
2548
2665
  }
2666
+ case "skill": {
2667
+ const subcommand = cli.args[0];
2668
+ switch (subcommand) {
2669
+ case "show": {
2670
+ showSkill();
2671
+ break;
2672
+ }
2673
+ case "install": {
2674
+ try {
2675
+ await installSkill(Boolean(cli.values.global), Boolean(cli.values.force), Boolean(cli.values.yes));
2676
+ }
2677
+ catch (error) {
2678
+ console.error(error instanceof Error ? error.message : String(error));
2679
+ process.exit(1);
2680
+ }
2681
+ break;
2682
+ }
2683
+ case "help":
2684
+ case undefined: {
2685
+ console.log("Usage: qmd skill <show|install> [options]");
2686
+ console.log("");
2687
+ console.log("Commands:");
2688
+ console.log(" show Print the packaged QMD skill");
2689
+ console.log(" install Install into ./.agents/skills/qmd");
2690
+ console.log("");
2691
+ console.log("Options:");
2692
+ console.log(" --global Install into ~/.agents/skills/qmd");
2693
+ console.log(" --yes Also create the .claude/skills/qmd symlink");
2694
+ console.log(" -f, --force Replace existing install or symlink");
2695
+ process.exit(0);
2696
+ }
2697
+ default:
2698
+ console.error(`Unknown subcommand: ${subcommand}`);
2699
+ console.error("Run 'qmd skill help' for usage");
2700
+ process.exit(1);
2701
+ }
2702
+ break;
2703
+ }
2549
2704
  case "cleanup": {
2550
2705
  const db = getDb();
2551
2706
  // 1. Clear llm_cache
@@ -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
+ }
package/dist/llm.js CHANGED
@@ -47,7 +47,7 @@ export function formatDocForEmbedding(text, title, modelUri) {
47
47
  // =============================================================================
48
48
  // HuggingFace model URIs for node-llama-cpp
49
49
  // Format: hf:<user>/<repo>/<file>
50
- // Override via QMD_EMBED_MODEL env var (e.g. hf:Qwen/Qwen3-Embedding-0.6B-GGUF/qwen3-embedding-0.6b-q8_0.gguf)
50
+ // Override via QMD_EMBED_MODEL env var (e.g. hf:Qwen/Qwen3-Embedding-0.6B-GGUF/Qwen3-Embedding-0.6B-Q8_0.gguf)
51
51
  const DEFAULT_EMBED_MODEL = process.env.QMD_EMBED_MODEL ?? "hf:ggml-org/embeddinggemma-300M-GGUF/embeddinggemma-300M-Q8_0.gguf";
52
52
  const DEFAULT_RERANK_MODEL = "hf:ggml-org/Qwen3-Reranker-0.6B-Q8_0-GGUF/qwen3-reranker-0.6b-q8_0.gguf";
53
53
  // const DEFAULT_GENERATE_MODEL = "hf:ggml-org/Qwen3-0.6B-GGUF/Qwen3-0.6B-Q8_0.gguf";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tobilu/qmd",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "Query Markup Documents - On-device hybrid search for markdown files with BM25, vector search, and LLM reranking",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",