seekstone 0.14.0 → 0.15.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 CHANGED
@@ -110,7 +110,7 @@ Other MCP clients (Windsurf, Cline, …) take the Option 3 JSON block in their o
110
110
 
111
111
  | Tool | Description |
112
112
  |---|---|
113
- | `search` | Full-text search. Returns ranked excerpts (default ~120 chars, tunable via `excerptLength`), not full notes. Fuzzy and prefix matching. |
113
+ | `search` | Full-text search. Returns ranked excerpts (default ~120 chars, tunable via `excerptLength`), not full notes. Fuzzy and prefix matching; with `SEEKSTONE_SEMANTIC=1`, `mode: "semantic"`/`"hybrid"` searches by meaning via a local embedding model (nothing leaves your machine). |
114
114
  | `query_notes` | Structured metadata query. Filter by frontmatter key/value predicates (`eq`, `ne`, `contains`, `exists`, `missing`, `gt`/`gte`/`lt`/`lte`), tag, folder, modified time, and size; sort and select the fields you need. Returns compact rows, not note content. |
115
115
  | `context_pack` | Answer-ready context for a natural-language question in one call, hard-capped at a byte budget (default 2 KB): ranked excerpts, linked neighbor notes with one-line summaries, and follow-up source paths — replaces a search → read → get_backlinks round-trip loop. |
116
116
  | `read_note` | Read the full content of a note by vault-relative path. Supports returning a single section, block, or line range. |
@@ -150,6 +150,9 @@ Every write tool (`append_note`, `patch_note`, `patch_frontmatter`, `replace_in_
150
150
  | `SEEKSTONE_LOG_MAX_SIZE` | no | Log-rotation threshold for `SEEKSTONE_LOG_FILE` (e.g. `10mb`; default 5 MB). |
151
151
  | `SEEKSTONE_READ_ONLY` | no | Set to `1` to run read-only: the 9 write tools are unregistered entirely (and rejected if called anyway), so the session provably cannot modify your vault. |
152
152
  | `SEEKSTONE_WRITE_PATHS` | no | Comma-separated vault-relative globs (e.g. `journal/**,inbox/*.md`). Writes are permitted only under matching paths; the rest of the vault stays read-only. |
153
+ | `SEEKSTONE_SEMANTIC` | no | Set to `1` to enable semantic search (`search` gains `mode: "semantic"` and `"hybrid"`). Download the local model once with `npx -y seekstone fetch-model`; the running server never touches the network. |
154
+ | `SEEKSTONE_MODEL_PATH` | no | Directory holding the Model2Vec embedding model (default: where `fetch-model` puts it). |
155
+ | `SEEKSTONE_CACHE_DIR` | no | Cache root for the model and per-vault embedding caches (default `~/.cache/seekstone`). |
153
156
 
154
157
  ---
155
158
 
@@ -0,0 +1,18 @@
1
+ // src/semantic/config.ts
2
+ import { join } from "path";
3
+ var DEFAULT_MODEL_ID = "potion-base-8M";
4
+ function defaultCacheDir(env, homedir) {
5
+ return env.SEEKSTONE_CACHE_DIR || join(homedir, ".cache", "seekstone");
6
+ }
7
+ function resolveSemanticConfig(env, homedir) {
8
+ if (env.SEEKSTONE_SEMANTIC !== "1") return void 0;
9
+ const cacheDir = defaultCacheDir(env, homedir);
10
+ const modelDir = env.SEEKSTONE_MODEL_PATH || join(cacheDir, "models", DEFAULT_MODEL_ID);
11
+ return { modelDir, cacheDir };
12
+ }
13
+
14
+ export {
15
+ defaultCacheDir,
16
+ resolveSemanticConfig
17
+ };
18
+ //# sourceMappingURL=chunk-RRLYVK2X.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/semantic/config.ts"],"sourcesContent":["import { join } from 'node:path';\n\n/**\n * Semantic-search configuration, resolved from env at boot.\n *\n * - `SEEKSTONE_SEMANTIC=1` enables the feature (off by default).\n * - `SEEKSTONE_MODEL_PATH` overrides where the Model2Vec model dir lives;\n * default is `<cache>/models/potion-base-8M`, where `seekstone fetch-model`\n * puts it.\n * - `SEEKSTONE_CACHE_DIR` overrides the cache root (default `~/.cache/seekstone`),\n * which holds both downloaded models and per-vault embedding caches.\n *\n * The server itself NEVER downloads anything — the model is fetched\n * out-of-band by the `fetch-model` subcommand, preserving the zero-network\n * guarantee (no-network.test.ts).\n */\n\nexport const DEFAULT_MODEL_ID = 'potion-base-8M';\n\nexport interface SemanticConfig {\n modelDir: string;\n cacheDir: string;\n}\n\nexport function defaultCacheDir(env: NodeJS.ProcessEnv, homedir: string): string {\n return env.SEEKSTONE_CACHE_DIR || join(homedir, '.cache', 'seekstone');\n}\n\nexport function resolveSemanticConfig(\n env: NodeJS.ProcessEnv,\n homedir: string,\n): SemanticConfig | undefined {\n if (env.SEEKSTONE_SEMANTIC !== '1') return undefined;\n const cacheDir = defaultCacheDir(env, homedir);\n const modelDir = env.SEEKSTONE_MODEL_PATH || join(cacheDir, 'models', DEFAULT_MODEL_ID);\n return { modelDir, cacheDir };\n}\n"],"mappings":";AAAA,SAAS,YAAY;AAiBd,IAAM,mBAAmB;AAOzB,SAAS,gBAAgB,KAAwB,SAAyB;AAC/E,SAAO,IAAI,uBAAuB,KAAK,SAAS,UAAU,WAAW;AACvE;AAEO,SAAS,sBACd,KACA,SAC4B;AAC5B,MAAI,IAAI,uBAAuB,IAAK,QAAO;AAC3C,QAAM,WAAW,gBAAgB,KAAK,OAAO;AAC7C,QAAM,WAAW,IAAI,wBAAwB,KAAK,UAAU,UAAU,gBAAgB;AACtF,SAAO,EAAE,UAAU,SAAS;AAC9B;","names":[]}
@@ -0,0 +1,81 @@
1
+ import {
2
+ defaultCacheDir
3
+ } from "./chunk-RRLYVK2X.js";
4
+
5
+ // src/semantic/fetch-model.ts
6
+ import { createHash } from "crypto";
7
+ import { existsSync } from "fs";
8
+ import { mkdir, readFile, writeFile } from "fs/promises";
9
+ import { join } from "path";
10
+
11
+ // src/semantic/model-manifest.ts
12
+ var DEFAULT_MODEL = {
13
+ id: "potion-base-8M",
14
+ license: "MIT (https://huggingface.co/minishlab/potion-base-8M)",
15
+ files: [
16
+ {
17
+ name: "model.safetensors",
18
+ url: "https://huggingface.co/minishlab/potion-base-8M/resolve/main/model.safetensors",
19
+ sha256: "f65d0f325faadc1e121c319e2faa41170d3fa07d8c89abd48ca5358d9a223de2",
20
+ bytes: 30236760
21
+ },
22
+ {
23
+ name: "tokenizer.json",
24
+ url: "https://huggingface.co/minishlab/potion-base-8M/resolve/main/tokenizer.json",
25
+ sha256: "e67e803f624fb4d67dea1c730d06e1067e1b14d830e2c2202569e3ef0f70bb50",
26
+ bytes: 683666
27
+ },
28
+ {
29
+ name: "config.json",
30
+ url: "https://huggingface.co/minishlab/potion-base-8M/resolve/main/config.json",
31
+ sha256: "2a6ac0e9aaa356a68a5688070db78fc3a464fefe85d2f06a1905ce3718687553",
32
+ bytes: 202
33
+ }
34
+ ]
35
+ };
36
+
37
+ // src/semantic/fetch-model.ts
38
+ async function runFetchModel(deps) {
39
+ const manifest = deps.manifest ?? DEFAULT_MODEL;
40
+ const fetchFn = deps.fetchFn ?? fetch;
41
+ const destDir = deps.env.SEEKSTONE_MODEL_PATH || join(defaultCacheDir(deps.env, deps.homedir), "models", manifest.id);
42
+ const output = [];
43
+ output.push(`fetch-model: ${manifest.id} \u2192 ${destDir}`);
44
+ try {
45
+ await mkdir(destDir, { recursive: true });
46
+ let fetched = 0;
47
+ let skipped = 0;
48
+ for (const file of manifest.files) {
49
+ const dest = join(destDir, file.name);
50
+ if (existsSync(dest) && sha256(await readFile(dest)) === file.sha256) {
51
+ skipped++;
52
+ continue;
53
+ }
54
+ output.push(`fetching ${file.name} (${file.bytes} bytes)\u2026`);
55
+ const res = await fetchFn(file.url);
56
+ if (!res.ok) throw new Error(`fetch ${file.url} failed: ${res.status}`);
57
+ const buf = Buffer.from(await res.arrayBuffer());
58
+ const got = sha256(buf);
59
+ if (got !== file.sha256) {
60
+ throw new Error(`checksum mismatch for ${file.name}: expected ${file.sha256}, got ${got}`);
61
+ }
62
+ await writeFile(dest, buf);
63
+ fetched++;
64
+ }
65
+ output.push(`\u2713 ${manifest.id}: ${fetched} fetched, ${skipped} already present (verified).`);
66
+ output.push(
67
+ "Enable semantic search with SEEKSTONE_SEMANTIC=1 in your MCP server config, then restart the session."
68
+ );
69
+ return { output, exitCode: 0 };
70
+ } catch (err) {
71
+ output.push(`\u2717 ${err instanceof Error ? err.message : String(err)}`);
72
+ return { output, exitCode: 1 };
73
+ }
74
+ }
75
+ function sha256(buf) {
76
+ return createHash("sha256").update(buf).digest("hex");
77
+ }
78
+ export {
79
+ runFetchModel
80
+ };
81
+ //# sourceMappingURL=fetch-model-7TGOJMZL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/semantic/fetch-model.ts","../src/semantic/model-manifest.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport { existsSync } from 'node:fs';\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { defaultCacheDir } from './config.js';\nimport { DEFAULT_MODEL, type ModelManifest } from './model-manifest.js';\n\n/**\n * `seekstone fetch-model` — the explicit, out-of-band download of the\n * embedding model that semantic search needs. This is a CLI subcommand that\n * exits before any MCP serving starts; the running server never fetches\n * (no-network.test.ts is the guarantee). Files are verified against pinned\n * SHA-256 hashes; already-present matching files are skipped.\n */\n\nexport interface FetchModelResult {\n output: string[];\n exitCode: number;\n}\n\nexport interface FetchModelDeps {\n env: NodeJS.ProcessEnv;\n homedir: string;\n manifest?: ModelManifest;\n fetchFn?: typeof fetch;\n}\n\nexport async function runFetchModel(deps: FetchModelDeps): Promise<FetchModelResult> {\n const manifest = deps.manifest ?? DEFAULT_MODEL;\n const fetchFn = deps.fetchFn ?? fetch;\n const destDir =\n deps.env.SEEKSTONE_MODEL_PATH ||\n join(defaultCacheDir(deps.env, deps.homedir), 'models', manifest.id);\n const output: string[] = [];\n output.push(`fetch-model: ${manifest.id} → ${destDir}`);\n try {\n await mkdir(destDir, { recursive: true });\n let fetched = 0;\n let skipped = 0;\n for (const file of manifest.files) {\n const dest = join(destDir, file.name);\n if (existsSync(dest) && sha256(await readFile(dest)) === file.sha256) {\n skipped++;\n continue;\n }\n output.push(`fetching ${file.name} (${file.bytes} bytes)…`);\n const res = await fetchFn(file.url);\n if (!res.ok) throw new Error(`fetch ${file.url} failed: ${res.status}`);\n const buf = Buffer.from(await res.arrayBuffer());\n const got = sha256(buf);\n if (got !== file.sha256) {\n throw new Error(`checksum mismatch for ${file.name}: expected ${file.sha256}, got ${got}`);\n }\n await writeFile(dest, buf);\n fetched++;\n }\n output.push(`✓ ${manifest.id}: ${fetched} fetched, ${skipped} already present (verified).`);\n output.push(\n 'Enable semantic search with SEEKSTONE_SEMANTIC=1 in your MCP server config, then restart the session.',\n );\n return { output, exitCode: 0 };\n } catch (err) {\n output.push(`✗ ${err instanceof Error ? err.message : String(err)}`);\n return { output, exitCode: 1 };\n }\n}\n\nfunction sha256(buf: Buffer): string {\n return createHash('sha256').update(buf).digest('hex');\n}\n","/**\n * Pinned download manifest for the default embedding model. Used ONLY by the\n * `seekstone fetch-model` subcommand — an explicit, out-of-band CLI action.\n * The MCP server itself never touches the network (no-network.test.ts).\n *\n * Hashes mirror packages/harness/fixtures/models/manifest.json (the harness\n * evals run against the same files).\n */\n\nexport interface ModelFile {\n name: string;\n url: string;\n sha256: string;\n bytes: number;\n}\n\nexport interface ModelManifest {\n id: string;\n license: string;\n files: ModelFile[];\n}\n\nexport const DEFAULT_MODEL: ModelManifest = {\n id: 'potion-base-8M',\n license: 'MIT (https://huggingface.co/minishlab/potion-base-8M)',\n files: [\n {\n name: 'model.safetensors',\n url: 'https://huggingface.co/minishlab/potion-base-8M/resolve/main/model.safetensors',\n sha256: 'f65d0f325faadc1e121c319e2faa41170d3fa07d8c89abd48ca5358d9a223de2',\n bytes: 30236760,\n },\n {\n name: 'tokenizer.json',\n url: 'https://huggingface.co/minishlab/potion-base-8M/resolve/main/tokenizer.json',\n sha256: 'e67e803f624fb4d67dea1c730d06e1067e1b14d830e2c2202569e3ef0f70bb50',\n bytes: 683666,\n },\n {\n name: 'config.json',\n url: 'https://huggingface.co/minishlab/potion-base-8M/resolve/main/config.json',\n sha256: '2a6ac0e9aaa356a68a5688070db78fc3a464fefe85d2f06a1905ce3718687553',\n bytes: 202,\n },\n ],\n};\n"],"mappings":";;;;;AAAA,SAAS,kBAAkB;AAC3B,SAAS,kBAAkB;AAC3B,SAAS,OAAO,UAAU,iBAAiB;AAC3C,SAAS,YAAY;;;ACmBd,IAAM,gBAA+B;AAAA,EAC1C,IAAI;AAAA,EACJ,SAAS;AAAA,EACT,OAAO;AAAA,IACL;AAAA,MACE,MAAM;AAAA,MACN,KAAK;AAAA,MACL,QAAQ;AAAA,MACR,OAAO;AAAA,IACT;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,KAAK;AAAA,MACL,QAAQ;AAAA,MACR,OAAO;AAAA,IACT;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,KAAK;AAAA,MACL,QAAQ;AAAA,MACR,OAAO;AAAA,IACT;AAAA,EACF;AACF;;;ADlBA,eAAsB,cAAc,MAAiD;AACnF,QAAM,WAAW,KAAK,YAAY;AAClC,QAAM,UAAU,KAAK,WAAW;AAChC,QAAM,UACJ,KAAK,IAAI,wBACT,KAAK,gBAAgB,KAAK,KAAK,KAAK,OAAO,GAAG,UAAU,SAAS,EAAE;AACrE,QAAM,SAAmB,CAAC;AAC1B,SAAO,KAAK,gBAAgB,SAAS,EAAE,WAAM,OAAO,EAAE;AACtD,MAAI;AACF,UAAM,MAAM,SAAS,EAAE,WAAW,KAAK,CAAC;AACxC,QAAI,UAAU;AACd,QAAI,UAAU;AACd,eAAW,QAAQ,SAAS,OAAO;AACjC,YAAM,OAAO,KAAK,SAAS,KAAK,IAAI;AACpC,UAAI,WAAW,IAAI,KAAK,OAAO,MAAM,SAAS,IAAI,CAAC,MAAM,KAAK,QAAQ;AACpE;AACA;AAAA,MACF;AACA,aAAO,KAAK,YAAY,KAAK,IAAI,KAAK,KAAK,KAAK,eAAU;AAC1D,YAAM,MAAM,MAAM,QAAQ,KAAK,GAAG;AAClC,UAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,SAAS,KAAK,GAAG,YAAY,IAAI,MAAM,EAAE;AACtE,YAAM,MAAM,OAAO,KAAK,MAAM,IAAI,YAAY,CAAC;AAC/C,YAAM,MAAM,OAAO,GAAG;AACtB,UAAI,QAAQ,KAAK,QAAQ;AACvB,cAAM,IAAI,MAAM,yBAAyB,KAAK,IAAI,cAAc,KAAK,MAAM,SAAS,GAAG,EAAE;AAAA,MAC3F;AACA,YAAM,UAAU,MAAM,GAAG;AACzB;AAAA,IACF;AACA,WAAO,KAAK,UAAK,SAAS,EAAE,KAAK,OAAO,aAAa,OAAO,8BAA8B;AAC1F,WAAO;AAAA,MACL;AAAA,IACF;AACA,WAAO,EAAE,QAAQ,UAAU,EAAE;AAAA,EAC/B,SAAS,KAAK;AACZ,WAAO,KAAK,UAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AACnE,WAAO,EAAE,QAAQ,UAAU,EAAE;AAAA,EAC/B;AACF;AAEA,SAAS,OAAO,KAAqB;AACnC,SAAO,WAAW,QAAQ,EAAE,OAAO,GAAG,EAAE,OAAO,KAAK;AACtD;","names":[]}