seekstone 0.11.0 → 0.12.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/dist/index.js +414 -205
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -238,15 +238,6 @@ async function appendNote(ctx2, input) {
|
|
|
238
238
|
};
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
-
// src/tools/create_note.ts
|
|
242
|
-
import { access, mkdir, readFile as readFile2 } from "fs/promises";
|
|
243
|
-
import { dirname, join as join2 } from "path";
|
|
244
|
-
import { stringify as stringifyYaml } from "yaml";
|
|
245
|
-
import { z as z2 } from "zod";
|
|
246
|
-
|
|
247
|
-
// src/index/doc.ts
|
|
248
|
-
import { basename, extname } from "path";
|
|
249
|
-
|
|
250
241
|
// ../core/src/extract.ts
|
|
251
242
|
var LINK_WITH_LINES_RE = /(!?\[\[)([^\]|#\n]{1,512})(#[^\]|\n]{1,512})?(\|[^\]\n]{1,512})?(\]\])/g;
|
|
252
243
|
function extractLinksWithLines(raw) {
|
|
@@ -288,7 +279,298 @@ function frontmatterTags(fm) {
|
|
|
288
279
|
return [];
|
|
289
280
|
}
|
|
290
281
|
|
|
282
|
+
// src/tools/context_pack.ts
|
|
283
|
+
import { z as z3 } from "zod";
|
|
284
|
+
|
|
285
|
+
// src/index/excerpt.ts
|
|
286
|
+
function extractExcerpt(text, terms, maxLen = 200) {
|
|
287
|
+
const lower = text.toLowerCase();
|
|
288
|
+
let bestIdx = -1;
|
|
289
|
+
for (const term of terms) {
|
|
290
|
+
const idx = lower.indexOf(term.toLowerCase());
|
|
291
|
+
if (idx !== -1 && (bestIdx === -1 || idx < bestIdx)) {
|
|
292
|
+
bestIdx = idx;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (bestIdx === -1) {
|
|
296
|
+
return truncate(text.trimStart(), maxLen);
|
|
297
|
+
}
|
|
298
|
+
const half = Math.floor(maxLen / 2);
|
|
299
|
+
const start = Math.max(0, bestIdx - half);
|
|
300
|
+
const end = Math.min(text.length, bestIdx + half + (terms[0]?.length ?? 0));
|
|
301
|
+
const slice = text.slice(start, end).trim();
|
|
302
|
+
return `${start > 0 ? "\u2026" : ""}${slice}${end < text.length ? "\u2026" : ""}`;
|
|
303
|
+
}
|
|
304
|
+
function truncate(s, max) {
|
|
305
|
+
if (s.length <= max) return s;
|
|
306
|
+
return `${s.slice(0, max).trimEnd()}\u2026`;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// src/index/resolve.ts
|
|
310
|
+
function resolveLink(target, notes2) {
|
|
311
|
+
if (notes2.has(target)) return target;
|
|
312
|
+
const withMd = `${target}.md`;
|
|
313
|
+
if (notes2.has(withMd)) return withMd;
|
|
314
|
+
const targetLower = target.toLowerCase();
|
|
315
|
+
const targetBase = (target.split("/").pop() ?? target).toLowerCase();
|
|
316
|
+
for (const path2 of notes2.keys()) {
|
|
317
|
+
const pathNoExt = path2.replace(/\.md$/i, "");
|
|
318
|
+
if (pathNoExt.toLowerCase() === targetLower) return path2;
|
|
319
|
+
const pathBase = (pathNoExt.split("/").pop() ?? pathNoExt).toLowerCase();
|
|
320
|
+
if (pathBase === targetBase) return path2;
|
|
321
|
+
}
|
|
322
|
+
return void 0;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// src/tools/search.ts
|
|
326
|
+
import { z as z2 } from "zod";
|
|
327
|
+
var SearchInput = z2.object({
|
|
328
|
+
query: z2.string().min(1).describe("Search query. Supports fuzzy matching and prefix search."),
|
|
329
|
+
limit: z2.number().int().min(1).max(50).default(10).describe("Maximum number of results to return."),
|
|
330
|
+
folder: z2.string().optional().describe("Restrict results to notes under this vault-relative folder prefix."),
|
|
331
|
+
tag: z2.string().optional().describe("Restrict results to notes containing this tag."),
|
|
332
|
+
excerptLength: z2.number().int().min(20).max(2e3).optional().describe(
|
|
333
|
+
"Max characters of match context per hit (default 120). Lower trims payload; higher gives more context."
|
|
334
|
+
)
|
|
335
|
+
});
|
|
336
|
+
function basenameNoExt(path2) {
|
|
337
|
+
const base = path2.slice(path2.lastIndexOf("/") + 1);
|
|
338
|
+
const dot = base.lastIndexOf(".");
|
|
339
|
+
return dot > 0 ? base.slice(0, dot) : base;
|
|
340
|
+
}
|
|
341
|
+
function search(ctx2, input) {
|
|
342
|
+
const results = ctx2.index.search(input.query, {
|
|
343
|
+
boost: { title: 3, tags: 2, body: 1 },
|
|
344
|
+
fuzzy: 0.2,
|
|
345
|
+
prefix: true
|
|
346
|
+
});
|
|
347
|
+
const terms = input.query.toLowerCase().split(/\s+/).filter((t) => t.length > 1);
|
|
348
|
+
const hits = [];
|
|
349
|
+
for (const r of results) {
|
|
350
|
+
if (input.folder && !r.id.startsWith(input.folder)) continue;
|
|
351
|
+
if (input.tag) {
|
|
352
|
+
const note2 = ctx2.notes.get(r.id);
|
|
353
|
+
if (!note2) continue;
|
|
354
|
+
const noteTags2 = note2.tags.split(" ");
|
|
355
|
+
if (!noteTags2.some((t) => t === input.tag || t === input.tag?.replace(/^#/, ""))) continue;
|
|
356
|
+
}
|
|
357
|
+
const note = ctx2.notes.get(r.id);
|
|
358
|
+
const body = note?.body ?? "";
|
|
359
|
+
const noteTags = note?.tags ? note.tags.split(" ").filter(Boolean) : [];
|
|
360
|
+
const title = r.title;
|
|
361
|
+
const hit = {
|
|
362
|
+
path: r.id,
|
|
363
|
+
// Round the raw MiniSearch score: 2 decimals preserve relevance gaps without 17-digit float tax.
|
|
364
|
+
score: Math.round(r.score * 100) / 100,
|
|
365
|
+
excerpt: extractExcerpt(body, terms, input.excerptLength ?? 120)
|
|
366
|
+
};
|
|
367
|
+
if (title && title !== basenameNoExt(r.id)) hit.title = title;
|
|
368
|
+
if (noteTags.length > 0) hit.tags = noteTags;
|
|
369
|
+
hits.push(hit);
|
|
370
|
+
if (hits.length >= input.limit) break;
|
|
371
|
+
}
|
|
372
|
+
return hits;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// src/tools/context_pack.ts
|
|
376
|
+
var ContextPackInput = z3.object({
|
|
377
|
+
query: z3.string().min(1).describe("Natural-language question or topic to assemble context for."),
|
|
378
|
+
budgetBytes: z3.number().int().min(256).max(16384).default(2048).describe("Hard cap on the response JSON size in bytes. The pack never exceeds it.")
|
|
379
|
+
});
|
|
380
|
+
var MAX_CANDIDATES = 10;
|
|
381
|
+
var NEIGHBOR_HITS = 3;
|
|
382
|
+
var MAX_NEIGHBORS = 5;
|
|
383
|
+
var NEIGHBOR_SUMMARY_LEN = 100;
|
|
384
|
+
var MIN_EXCERPT_LEN = 60;
|
|
385
|
+
var FM_MAX_BYTES = 120;
|
|
386
|
+
var SOURCES_MAX = 8;
|
|
387
|
+
function entryCost(entry) {
|
|
388
|
+
return Buffer.byteLength(JSON.stringify(entry), "utf8") + 1;
|
|
389
|
+
}
|
|
390
|
+
function pickFm(fm) {
|
|
391
|
+
if (!fm) return void 0;
|
|
392
|
+
const out = {};
|
|
393
|
+
let picked = 0;
|
|
394
|
+
for (const key of Object.keys(fm)) {
|
|
395
|
+
const v = fm[key];
|
|
396
|
+
const t = typeof v;
|
|
397
|
+
if (t !== "string" && t !== "number" && t !== "boolean") continue;
|
|
398
|
+
const candidate = { ...out, [key]: v };
|
|
399
|
+
if (Buffer.byteLength(JSON.stringify(candidate), "utf8") > FM_MAX_BYTES) break;
|
|
400
|
+
out[key] = v;
|
|
401
|
+
picked += 1;
|
|
402
|
+
}
|
|
403
|
+
return picked > 0 ? out : void 0;
|
|
404
|
+
}
|
|
405
|
+
function clamp(n, lo, hi) {
|
|
406
|
+
return Math.min(hi, Math.max(lo, n));
|
|
407
|
+
}
|
|
408
|
+
function contextPack(ctx2, input) {
|
|
409
|
+
const results = ctx2.index.search(input.query, {
|
|
410
|
+
boost: { title: 3, tags: 2, body: 1 },
|
|
411
|
+
fuzzy: 0.2,
|
|
412
|
+
prefix: true
|
|
413
|
+
});
|
|
414
|
+
const totalMatches = results.length;
|
|
415
|
+
if (totalMatches === 0) {
|
|
416
|
+
return { excerpts: [], neighborhood: [], sources: [], totalMatches: 0, confidence: "none" };
|
|
417
|
+
}
|
|
418
|
+
const terms = input.query.toLowerCase().split(/\s+/).filter((t) => t.length > 1);
|
|
419
|
+
const scoreByPath = /* @__PURE__ */ new Map();
|
|
420
|
+
for (const r of results) scoreByPath.set(r.id, r.score);
|
|
421
|
+
const budget = input.budgetBytes;
|
|
422
|
+
let used = Buffer.byteLength(
|
|
423
|
+
JSON.stringify({
|
|
424
|
+
excerpts: [],
|
|
425
|
+
neighborhood: [],
|
|
426
|
+
sources: [],
|
|
427
|
+
totalMatches,
|
|
428
|
+
confidence: "high",
|
|
429
|
+
truncated: true
|
|
430
|
+
}),
|
|
431
|
+
"utf8"
|
|
432
|
+
);
|
|
433
|
+
const excerptLen = clamp(Math.floor(budget / 10), 80, 400);
|
|
434
|
+
const reserve = Math.min(Math.floor(budget / 4), 600);
|
|
435
|
+
const candidates = results.slice(0, MAX_CANDIDATES);
|
|
436
|
+
const excerpts = [];
|
|
437
|
+
const overflowHits = [];
|
|
438
|
+
let droppedForBudget = false;
|
|
439
|
+
const buildEntry = (r, len) => {
|
|
440
|
+
const note = ctx2.notes.get(r.id);
|
|
441
|
+
const body = note?.body ?? "";
|
|
442
|
+
const noteTags = note?.tags ? note.tags.split(" ").filter(Boolean) : [];
|
|
443
|
+
const entry = {
|
|
444
|
+
path: r.id,
|
|
445
|
+
score: Math.round(r.score * 100) / 100,
|
|
446
|
+
excerpt: extractExcerpt(body, terms, len)
|
|
447
|
+
};
|
|
448
|
+
const title = r.title;
|
|
449
|
+
if (title && title !== basenameNoExt(r.id)) entry.title = title;
|
|
450
|
+
if (noteTags.length > 0) entry.tags = noteTags;
|
|
451
|
+
const fm = pickFm(note?.fm ?? null);
|
|
452
|
+
if (fm) entry.fm = fm;
|
|
453
|
+
return entry;
|
|
454
|
+
};
|
|
455
|
+
const excerptCap = budget - reserve;
|
|
456
|
+
let phaseOpen = true;
|
|
457
|
+
for (const r of candidates) {
|
|
458
|
+
if (!phaseOpen) {
|
|
459
|
+
overflowHits.push({ path: r.id });
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
let placed = false;
|
|
463
|
+
const shrinkLens = [
|
|
464
|
+
.../* @__PURE__ */ new Set([
|
|
465
|
+
excerptLen,
|
|
466
|
+
Math.max(Math.floor(excerptLen / 2), MIN_EXCERPT_LEN),
|
|
467
|
+
MIN_EXCERPT_LEN
|
|
468
|
+
])
|
|
469
|
+
];
|
|
470
|
+
for (const len of shrinkLens) {
|
|
471
|
+
const entry = buildEntry(r, len);
|
|
472
|
+
const cost = entryCost(entry);
|
|
473
|
+
if (used + cost <= excerptCap) {
|
|
474
|
+
excerpts.push(entry);
|
|
475
|
+
used += cost;
|
|
476
|
+
placed = true;
|
|
477
|
+
if (len !== excerptLen) phaseOpen = false;
|
|
478
|
+
break;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
if (!placed) {
|
|
482
|
+
phaseOpen = false;
|
|
483
|
+
droppedForBudget = true;
|
|
484
|
+
overflowHits.push({ path: r.id });
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
const includedPaths = new Set(excerpts.map((e) => e.path));
|
|
488
|
+
const seeds = excerpts.slice(0, NEIGHBOR_HITS).map((e) => e.path);
|
|
489
|
+
const agg = /* @__PURE__ */ new Map();
|
|
490
|
+
const connect = (neighborPath, seed, rel, line) => {
|
|
491
|
+
if (neighborPath === seed || includedPaths.has(neighborPath)) return;
|
|
492
|
+
if (!ctx2.notes.has(neighborPath)) return;
|
|
493
|
+
let entry = agg.get(neighborPath);
|
|
494
|
+
if (!entry) {
|
|
495
|
+
entry = { rels: /* @__PURE__ */ new Set(), seedPaths: /* @__PURE__ */ new Set() };
|
|
496
|
+
agg.set(neighborPath, entry);
|
|
497
|
+
}
|
|
498
|
+
entry.rels.add(rel);
|
|
499
|
+
entry.seedPaths.add(seed);
|
|
500
|
+
if (rel === "backlink" && entry.line === void 0 && line !== void 0) entry.line = line;
|
|
501
|
+
};
|
|
502
|
+
for (const seed of seeds) {
|
|
503
|
+
for (const ref of ctx2.backlinks.get(seed) ?? []) {
|
|
504
|
+
connect(ref.path, seed, "backlink", ref.line);
|
|
505
|
+
}
|
|
506
|
+
const note = ctx2.notes.get(seed);
|
|
507
|
+
if (!note) continue;
|
|
508
|
+
for (const link of extractLinksWithLines(note.raw)) {
|
|
509
|
+
const target = resolveLink(link.target, ctx2.notes);
|
|
510
|
+
if (target !== void 0) connect(target, seed, "outlink");
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
const ranked = [...agg.entries()].sort((a, b) => {
|
|
514
|
+
const conn = b[1].seedPaths.size - a[1].seedPaths.size;
|
|
515
|
+
if (conn !== 0) return conn;
|
|
516
|
+
const score = (scoreByPath.get(b[0]) ?? 0) - (scoreByPath.get(a[0]) ?? 0);
|
|
517
|
+
if (score !== 0) return score;
|
|
518
|
+
return a[0] < b[0] ? -1 : 1;
|
|
519
|
+
});
|
|
520
|
+
const neighborhood = [];
|
|
521
|
+
const overflowNeighbors = [];
|
|
522
|
+
for (const [path2, info] of ranked) {
|
|
523
|
+
if (neighborhood.length >= MAX_NEIGHBORS) {
|
|
524
|
+
overflowNeighbors.push(info.line !== void 0 ? { path: path2, line: info.line } : { path: path2 });
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
const note = ctx2.notes.get(path2);
|
|
528
|
+
const entry = {
|
|
529
|
+
path: path2,
|
|
530
|
+
rel: info.rels.size === 2 ? "both" : info.rels.has("backlink") ? "backlink" : "outlink",
|
|
531
|
+
summary: extractExcerpt(note?.body ?? "", terms, NEIGHBOR_SUMMARY_LEN)
|
|
532
|
+
};
|
|
533
|
+
if (info.line !== void 0) entry.line = info.line;
|
|
534
|
+
const cost = entryCost(entry);
|
|
535
|
+
if (used + cost > budget) {
|
|
536
|
+
droppedForBudget = true;
|
|
537
|
+
overflowNeighbors.push(info.line !== void 0 ? { path: path2, line: info.line } : { path: path2 });
|
|
538
|
+
for (const [p, i] of ranked.slice(ranked.findIndex(([rp]) => rp === path2) + 1)) {
|
|
539
|
+
overflowNeighbors.push(i.line !== void 0 ? { path: p, line: i.line } : { path: p });
|
|
540
|
+
}
|
|
541
|
+
break;
|
|
542
|
+
}
|
|
543
|
+
neighborhood.push(entry);
|
|
544
|
+
used += cost;
|
|
545
|
+
}
|
|
546
|
+
const sources = [];
|
|
547
|
+
const sourcePaths = /* @__PURE__ */ new Set();
|
|
548
|
+
for (const ref of [...overflowHits, ...overflowNeighbors]) {
|
|
549
|
+
if (sources.length >= SOURCES_MAX) break;
|
|
550
|
+
if (includedPaths.has(ref.path) || sourcePaths.has(ref.path)) continue;
|
|
551
|
+
const cost = entryCost(ref);
|
|
552
|
+
if (used + cost > budget) break;
|
|
553
|
+
sources.push(ref);
|
|
554
|
+
sourcePaths.add(ref.path);
|
|
555
|
+
used += cost;
|
|
556
|
+
}
|
|
557
|
+
const hasTermInExcerpt = excerpts.some(
|
|
558
|
+
(e) => terms.some((t) => e.excerpt.toLowerCase().includes(t))
|
|
559
|
+
);
|
|
560
|
+
const confidence = excerpts.length === 0 || terms.length > 0 && !hasTermInExcerpt ? "low" : "high";
|
|
561
|
+
const result = { excerpts, neighborhood, sources, totalMatches, confidence };
|
|
562
|
+
if (droppedForBudget) result.truncated = true;
|
|
563
|
+
return result;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// src/tools/create_note.ts
|
|
567
|
+
import { access, mkdir, readFile as readFile2 } from "fs/promises";
|
|
568
|
+
import { dirname, join as join2 } from "path";
|
|
569
|
+
import { stringify as stringifyYaml } from "yaml";
|
|
570
|
+
import { z as z4 } from "zod";
|
|
571
|
+
|
|
291
572
|
// src/index/doc.ts
|
|
573
|
+
import { basename, extname } from "path";
|
|
292
574
|
function buildDoc(relPath, raw) {
|
|
293
575
|
const fm = parseFrontmatter(raw);
|
|
294
576
|
const allTags = [.../* @__PURE__ */ new Set([...extractInlineTags(fm.body), ...frontmatterTags(fm.data)])];
|
|
@@ -312,16 +594,16 @@ function upsertDoc(ctx2, doc) {
|
|
|
312
594
|
}
|
|
313
595
|
|
|
314
596
|
// src/tools/create_note.ts
|
|
315
|
-
var CreateNoteInput =
|
|
316
|
-
path:
|
|
597
|
+
var CreateNoteInput = z4.object({
|
|
598
|
+
path: z4.string().describe(
|
|
317
599
|
'Vault-relative path for the new note, e.g. "Daily Notes/2026-06-01.md". Parent directories are created automatically.'
|
|
318
600
|
),
|
|
319
|
-
content:
|
|
320
|
-
frontmatter:
|
|
321
|
-
overwrite:
|
|
601
|
+
content: z4.string().default("").describe("Body content for the note."),
|
|
602
|
+
frontmatter: z4.record(z4.string(), z4.unknown()).optional().describe("Frontmatter key-value pairs to write as a YAML block at the top of the note."),
|
|
603
|
+
overwrite: z4.boolean().default(false).describe(
|
|
322
604
|
"Overwrite an existing note. Defaults to false \u2014 throws if the note already exists."
|
|
323
605
|
),
|
|
324
|
-
prevHash:
|
|
606
|
+
prevHash: z4.string().optional().describe(
|
|
325
607
|
"Optional compare-and-swap guard for overwrite: true \u2014 the contentHash of the note being replaced. Fails with hash_conflict if it changed since it was read."
|
|
326
608
|
)
|
|
327
609
|
}).refine((v) => v.prevHash === void 0 || v.overwrite === true, {
|
|
@@ -378,23 +660,7 @@ ${stringifyYaml(input.frontmatter)}---
|
|
|
378
660
|
// src/tools/delete_note.ts
|
|
379
661
|
import { access as access2, mkdir as mkdir2, rename as rename2, rm } from "fs/promises";
|
|
380
662
|
import { basename as basename2, extname as extname2, join as join3 } from "path";
|
|
381
|
-
import { z as
|
|
382
|
-
|
|
383
|
-
// src/index/resolve.ts
|
|
384
|
-
function resolveLink(target, notes2) {
|
|
385
|
-
if (notes2.has(target)) return target;
|
|
386
|
-
const withMd = `${target}.md`;
|
|
387
|
-
if (notes2.has(withMd)) return withMd;
|
|
388
|
-
const targetLower = target.toLowerCase();
|
|
389
|
-
const targetBase = (target.split("/").pop() ?? target).toLowerCase();
|
|
390
|
-
for (const path2 of notes2.keys()) {
|
|
391
|
-
const pathNoExt = path2.replace(/\.md$/i, "");
|
|
392
|
-
if (pathNoExt.toLowerCase() === targetLower) return path2;
|
|
393
|
-
const pathBase = (pathNoExt.split("/").pop() ?? pathNoExt).toLowerCase();
|
|
394
|
-
if (pathBase === targetBase) return path2;
|
|
395
|
-
}
|
|
396
|
-
return void 0;
|
|
397
|
-
}
|
|
663
|
+
import { z as z5 } from "zod";
|
|
398
664
|
|
|
399
665
|
// src/index/backlinks.ts
|
|
400
666
|
function removeNoteBacklinks(ctx2, relPath) {
|
|
@@ -429,9 +695,9 @@ function addNoteBacklinks(ctx2, relPath, raw) {
|
|
|
429
695
|
}
|
|
430
696
|
|
|
431
697
|
// src/tools/delete_note.ts
|
|
432
|
-
var DeleteNoteInput =
|
|
433
|
-
path:
|
|
434
|
-
permanent:
|
|
698
|
+
var DeleteNoteInput = z5.object({
|
|
699
|
+
path: z5.string().describe("Vault-relative path of the note to delete."),
|
|
700
|
+
permanent: z5.boolean().default(false).describe(
|
|
435
701
|
"Permanently remove the note instead of moving it to the vault .trash/ folder. Defaults to false \u2014 deletes are recoverable."
|
|
436
702
|
)
|
|
437
703
|
});
|
|
@@ -476,11 +742,11 @@ async function deleteNote(ctx2, rawInput) {
|
|
|
476
742
|
|
|
477
743
|
// src/tools/get_backlinks.ts
|
|
478
744
|
import { join as join4 } from "path";
|
|
479
|
-
import { z as
|
|
480
|
-
var GetBacklinksInput =
|
|
481
|
-
path:
|
|
482
|
-
includeContext:
|
|
483
|
-
limit:
|
|
745
|
+
import { z as z6 } from "zod";
|
|
746
|
+
var GetBacklinksInput = z6.object({
|
|
747
|
+
path: z6.string().describe("Vault-relative path to the target note."),
|
|
748
|
+
includeContext: z6.boolean().optional().describe("Include a short excerpt (~200 chars) from the linking line. Default true."),
|
|
749
|
+
limit: z6.number().int().min(1).max(500).optional().describe("Max backlinks to return. Default 50.")
|
|
484
750
|
});
|
|
485
751
|
var EXCERPT_MAX = 200;
|
|
486
752
|
function getBacklinks(ctx2, input) {
|
|
@@ -513,9 +779,9 @@ function getBacklinks(ctx2, input) {
|
|
|
513
779
|
|
|
514
780
|
// src/tools/get_links.ts
|
|
515
781
|
import { join as join5 } from "path";
|
|
516
|
-
import { z as
|
|
517
|
-
var GetLinksInput =
|
|
518
|
-
path:
|
|
782
|
+
import { z as z7 } from "zod";
|
|
783
|
+
var GetLinksInput = z7.object({
|
|
784
|
+
path: z7.string().describe("Vault-relative path to the note.")
|
|
519
785
|
});
|
|
520
786
|
function getLinks(ctx2, input) {
|
|
521
787
|
const absPath = join5(ctx2.vaultRoot, input.path);
|
|
@@ -545,13 +811,13 @@ function getLinks(ctx2, input) {
|
|
|
545
811
|
}
|
|
546
812
|
|
|
547
813
|
// src/tools/list_notes.ts
|
|
548
|
-
import { z as
|
|
549
|
-
var ListNotesInput =
|
|
550
|
-
folder:
|
|
814
|
+
import { z as z8 } from "zod";
|
|
815
|
+
var ListNotesInput = z8.object({
|
|
816
|
+
folder: z8.string().optional().describe(
|
|
551
817
|
'Vault-relative folder prefix to list. Omit for all notes. Example: "Daily Notes/2026".'
|
|
552
818
|
),
|
|
553
|
-
tag:
|
|
554
|
-
limit:
|
|
819
|
+
tag: z8.string().optional().describe("Filter by tag. Include or omit the leading #."),
|
|
820
|
+
limit: z8.number().int().min(1).max(500).default(100).describe("Maximum number of results.")
|
|
555
821
|
});
|
|
556
822
|
function listNotes(ctx2, input) {
|
|
557
823
|
const tag = input.tag?.replace(/^#/, "");
|
|
@@ -574,11 +840,11 @@ function listNotes(ctx2, input) {
|
|
|
574
840
|
}
|
|
575
841
|
|
|
576
842
|
// src/tools/list_tags.ts
|
|
577
|
-
import { z as
|
|
578
|
-
var ListTagsInput =
|
|
579
|
-
pattern:
|
|
580
|
-
minCount:
|
|
581
|
-
sort:
|
|
843
|
+
import { z as z9 } from "zod";
|
|
844
|
+
var ListTagsInput = z9.object({
|
|
845
|
+
pattern: z9.string().optional().describe('Substring filter on tag name. Example: "work" matches "work" and "area/work".'),
|
|
846
|
+
minCount: z9.number().int().min(1).optional().describe("Only return tags used in at least this many notes."),
|
|
847
|
+
sort: z9.enum(["count", "name"]).default("count").describe("Sort by usage count descending (default) or alphabetically.")
|
|
582
848
|
});
|
|
583
849
|
function listTags(ctx2, input) {
|
|
584
850
|
const counts = /* @__PURE__ */ new Map();
|
|
@@ -607,7 +873,7 @@ function listTags(ctx2, input) {
|
|
|
607
873
|
// src/tools/move_note.ts
|
|
608
874
|
import { access as access3, mkdir as mkdir3, rename as rename3 } from "fs/promises";
|
|
609
875
|
import { dirname as dirname2, join as join6 } from "path";
|
|
610
|
-
import { z as
|
|
876
|
+
import { z as z10 } from "zod";
|
|
611
877
|
|
|
612
878
|
// src/tools/rewrite_links.ts
|
|
613
879
|
import { posix } from "path";
|
|
@@ -700,11 +966,11 @@ function hasMarkdownLinkTo(raw, sourcePath, targetPath) {
|
|
|
700
966
|
}
|
|
701
967
|
|
|
702
968
|
// src/tools/move_note.ts
|
|
703
|
-
var MoveNoteInput =
|
|
704
|
-
from:
|
|
705
|
-
to:
|
|
706
|
-
overwrite:
|
|
707
|
-
rewriteLinks:
|
|
969
|
+
var MoveNoteInput = z10.object({
|
|
970
|
+
from: z10.string().describe("Vault-relative path of the note to move."),
|
|
971
|
+
to: z10.string().describe("Vault-relative destination path."),
|
|
972
|
+
overwrite: z10.boolean().default(false).describe("Overwrite the destination if it exists. Defaults to false."),
|
|
973
|
+
rewriteLinks: z10.boolean().default(true).describe(
|
|
708
974
|
"Rewrite wikilinks and markdown links in other notes that point at the moved note, so nothing breaks. Defaults to true; pass false to move the file only."
|
|
709
975
|
)
|
|
710
976
|
});
|
|
@@ -838,11 +1104,11 @@ function buildOutline(raw, options = {}) {
|
|
|
838
1104
|
}
|
|
839
1105
|
|
|
840
1106
|
// src/tools/outline_note.ts
|
|
841
|
-
import { z as
|
|
842
|
-
var OutlineNoteInput =
|
|
843
|
-
path:
|
|
844
|
-
includeBlocks:
|
|
845
|
-
includeSizes:
|
|
1107
|
+
import { z as z11 } from "zod";
|
|
1108
|
+
var OutlineNoteInput = z11.object({
|
|
1109
|
+
path: z11.string().describe("Vault-relative path to the note."),
|
|
1110
|
+
includeBlocks: z11.boolean().default(true).describe("Include block-reference anchors (^id). Default true."),
|
|
1111
|
+
includeSizes: z11.boolean().default(false).describe("Include per-section character length in each heading entry. Default false.")
|
|
846
1112
|
});
|
|
847
1113
|
async function outlineNote(ctx2, input) {
|
|
848
1114
|
const absPath = join7(ctx2.vaultRoot, input.path);
|
|
@@ -860,13 +1126,13 @@ async function outlineNote(ctx2, input) {
|
|
|
860
1126
|
import { readFile as readFile4 } from "fs/promises";
|
|
861
1127
|
import { join as join8 } from "path";
|
|
862
1128
|
import { parseDocument } from "yaml";
|
|
863
|
-
import { z as
|
|
864
|
-
var PatchFrontmatterInput =
|
|
865
|
-
path:
|
|
866
|
-
patch:
|
|
1129
|
+
import { z as z12 } from "zod";
|
|
1130
|
+
var PatchFrontmatterInput = z12.object({
|
|
1131
|
+
path: z12.string().describe("Vault-relative path to the note."),
|
|
1132
|
+
patch: z12.record(z12.string(), z12.unknown()).describe(
|
|
867
1133
|
"Key-value pairs to set in the frontmatter. Existing keys not in the patch are unchanged. Pass null as a value to remove a key."
|
|
868
1134
|
),
|
|
869
|
-
prevHash:
|
|
1135
|
+
prevHash: z12.string().optional().describe(
|
|
870
1136
|
"Optional compare-and-swap guard: the contentHash from a prior read. Fails with hash_conflict if the note changed since."
|
|
871
1137
|
)
|
|
872
1138
|
});
|
|
@@ -938,21 +1204,21 @@ ${original}`;
|
|
|
938
1204
|
// src/tools/patch_note.ts
|
|
939
1205
|
import { readFile as readFile5 } from "fs/promises";
|
|
940
1206
|
import { join as join9 } from "path";
|
|
941
|
-
import { z as
|
|
942
|
-
var PatchNoteInput =
|
|
943
|
-
path:
|
|
944
|
-
target:
|
|
945
|
-
|
|
946
|
-
|
|
1207
|
+
import { z as z13 } from "zod";
|
|
1208
|
+
var PatchNoteInput = z13.object({
|
|
1209
|
+
path: z13.string().describe("Vault-relative path to the note."),
|
|
1210
|
+
target: z13.union([
|
|
1211
|
+
z13.object({ heading: z13.string().describe("Heading text (without # markers).") }),
|
|
1212
|
+
z13.object({ block: z13.string().describe("Block reference ID (without the ^ prefix).") })
|
|
947
1213
|
]).describe("Target section. Provide exactly one of heading or block."),
|
|
948
|
-
operation:
|
|
1214
|
+
operation: z13.enum(["append", "prepend", "replace"]).describe(
|
|
949
1215
|
"append: insert after section content. prepend: insert after heading line. replace: swap section content."
|
|
950
1216
|
),
|
|
951
|
-
content:
|
|
952
|
-
createIfMissing:
|
|
1217
|
+
content: z13.string().min(1).describe("Content to insert or replace with."),
|
|
1218
|
+
createIfMissing: z13.boolean().default(false).describe(
|
|
953
1219
|
"If the heading target is not found, append a new heading (level 2) + content. Only valid for heading targets."
|
|
954
1220
|
),
|
|
955
|
-
prevHash:
|
|
1221
|
+
prevHash: z13.string().optional().describe(
|
|
956
1222
|
"Optional compare-and-swap guard: the contentHash from a prior read. Fails with hash_conflict if the note changed since."
|
|
957
1223
|
)
|
|
958
1224
|
});
|
|
@@ -1088,8 +1354,8 @@ function updateCache(ctx2, path2, newContent, bodyStart) {
|
|
|
1088
1354
|
// src/tools/periodic_note.ts
|
|
1089
1355
|
import { access as access4, mkdir as mkdir4, readFile as readFile6 } from "fs/promises";
|
|
1090
1356
|
import { dirname as dirname3, join as join10 } from "path";
|
|
1091
|
-
import { z as
|
|
1092
|
-
var PERIOD =
|
|
1357
|
+
import { z as z14 } from "zod";
|
|
1358
|
+
var PERIOD = z14.enum(["daily", "weekly", "monthly", "quarterly", "yearly"]);
|
|
1093
1359
|
var DEFAULTS = {
|
|
1094
1360
|
daily: { folder: "", format: "YYYY-MM-DD", template: "" },
|
|
1095
1361
|
weekly: { folder: "", format: "gggg-[W]ww", template: "" },
|
|
@@ -1154,12 +1420,12 @@ function resolveNotePath(cfg, date) {
|
|
|
1154
1420
|
const filename = `${formatMomentDate(date, cfg.format)}.md`;
|
|
1155
1421
|
return cfg.folder ? `${cfg.folder}/${filename}` : filename;
|
|
1156
1422
|
}
|
|
1157
|
-
var GetPeriodicNoteInput =
|
|
1423
|
+
var GetPeriodicNoteInput = z14.object({
|
|
1158
1424
|
period: PERIOD.default("daily").describe(
|
|
1159
1425
|
"Period type: daily (default), weekly, monthly, quarterly, or yearly."
|
|
1160
1426
|
),
|
|
1161
|
-
date:
|
|
1162
|
-
createIfMissing:
|
|
1427
|
+
date: z14.string().optional().describe("ISO date string (YYYY-MM-DD). Defaults to today when omitted."),
|
|
1428
|
+
createIfMissing: z14.boolean().default(false).describe(
|
|
1163
1429
|
"Create the note from the configured template if it does not exist. Default false \u2014 reports missing without creating."
|
|
1164
1430
|
)
|
|
1165
1431
|
});
|
|
@@ -1197,12 +1463,12 @@ async function getPeriodicNote(ctx2, rawInput) {
|
|
|
1197
1463
|
upsertDoc(ctx2, buildDoc(path2, body));
|
|
1198
1464
|
return { path: path2, existed: false, created: true, contentHash: contentHash(body) };
|
|
1199
1465
|
}
|
|
1200
|
-
var AppendPeriodicNoteInput =
|
|
1466
|
+
var AppendPeriodicNoteInput = z14.object({
|
|
1201
1467
|
period: PERIOD.default("daily").describe("Period type. Default: daily."),
|
|
1202
|
-
date:
|
|
1203
|
-
content:
|
|
1204
|
-
createIfMissing:
|
|
1205
|
-
prevHash:
|
|
1468
|
+
date: z14.string().optional().describe("ISO date string (YYYY-MM-DD). Defaults to today."),
|
|
1469
|
+
content: z14.string().min(1).describe("Text to append to the note body. Separated from existing content by a blank line."),
|
|
1470
|
+
createIfMissing: z14.boolean().default(true).describe("Create the note if it does not exist before appending. Default true."),
|
|
1471
|
+
prevHash: z14.string().optional().describe(
|
|
1206
1472
|
"Optional compare-and-swap guard: the contentHash from a prior read of the resolved periodic note. Fails with hash_conflict if it changed since."
|
|
1207
1473
|
)
|
|
1208
1474
|
});
|
|
@@ -1262,114 +1528,36 @@ async function appendPeriodicNote(ctx2, rawInput) {
|
|
|
1262
1528
|
}
|
|
1263
1529
|
|
|
1264
1530
|
// src/tools/query_notes.ts
|
|
1265
|
-
import { z as
|
|
1266
|
-
|
|
1267
|
-
// src/tools/search.ts
|
|
1268
|
-
import { z as z13 } from "zod";
|
|
1269
|
-
|
|
1270
|
-
// src/index/excerpt.ts
|
|
1271
|
-
function extractExcerpt(text, terms, maxLen = 200) {
|
|
1272
|
-
const lower = text.toLowerCase();
|
|
1273
|
-
let bestIdx = -1;
|
|
1274
|
-
for (const term of terms) {
|
|
1275
|
-
const idx = lower.indexOf(term.toLowerCase());
|
|
1276
|
-
if (idx !== -1 && (bestIdx === -1 || idx < bestIdx)) {
|
|
1277
|
-
bestIdx = idx;
|
|
1278
|
-
}
|
|
1279
|
-
}
|
|
1280
|
-
if (bestIdx === -1) {
|
|
1281
|
-
return truncate(text.trimStart(), maxLen);
|
|
1282
|
-
}
|
|
1283
|
-
const half = Math.floor(maxLen / 2);
|
|
1284
|
-
const start = Math.max(0, bestIdx - half);
|
|
1285
|
-
const end = Math.min(text.length, bestIdx + half + (terms[0]?.length ?? 0));
|
|
1286
|
-
const slice = text.slice(start, end).trim();
|
|
1287
|
-
return `${start > 0 ? "\u2026" : ""}${slice}${end < text.length ? "\u2026" : ""}`;
|
|
1288
|
-
}
|
|
1289
|
-
function truncate(s, max) {
|
|
1290
|
-
if (s.length <= max) return s;
|
|
1291
|
-
return `${s.slice(0, max).trimEnd()}\u2026`;
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1294
|
-
// src/tools/search.ts
|
|
1295
|
-
var SearchInput = z13.object({
|
|
1296
|
-
query: z13.string().min(1).describe("Search query. Supports fuzzy matching and prefix search."),
|
|
1297
|
-
limit: z13.number().int().min(1).max(50).default(10).describe("Maximum number of results to return."),
|
|
1298
|
-
folder: z13.string().optional().describe("Restrict results to notes under this vault-relative folder prefix."),
|
|
1299
|
-
tag: z13.string().optional().describe("Restrict results to notes containing this tag."),
|
|
1300
|
-
excerptLength: z13.number().int().min(20).max(2e3).optional().describe(
|
|
1301
|
-
"Max characters of match context per hit (default 120). Lower trims payload; higher gives more context."
|
|
1302
|
-
)
|
|
1303
|
-
});
|
|
1304
|
-
function basenameNoExt(path2) {
|
|
1305
|
-
const base = path2.slice(path2.lastIndexOf("/") + 1);
|
|
1306
|
-
const dot = base.lastIndexOf(".");
|
|
1307
|
-
return dot > 0 ? base.slice(0, dot) : base;
|
|
1308
|
-
}
|
|
1309
|
-
function search(ctx2, input) {
|
|
1310
|
-
const results = ctx2.index.search(input.query, {
|
|
1311
|
-
boost: { title: 3, tags: 2, body: 1 },
|
|
1312
|
-
fuzzy: 0.2,
|
|
1313
|
-
prefix: true
|
|
1314
|
-
});
|
|
1315
|
-
const terms = input.query.toLowerCase().split(/\s+/).filter((t) => t.length > 1);
|
|
1316
|
-
const hits = [];
|
|
1317
|
-
for (const r of results) {
|
|
1318
|
-
if (input.folder && !r.id.startsWith(input.folder)) continue;
|
|
1319
|
-
if (input.tag) {
|
|
1320
|
-
const note2 = ctx2.notes.get(r.id);
|
|
1321
|
-
if (!note2) continue;
|
|
1322
|
-
const noteTags2 = note2.tags.split(" ");
|
|
1323
|
-
if (!noteTags2.some((t) => t === input.tag || t === input.tag?.replace(/^#/, ""))) continue;
|
|
1324
|
-
}
|
|
1325
|
-
const note = ctx2.notes.get(r.id);
|
|
1326
|
-
const body = note?.body ?? "";
|
|
1327
|
-
const noteTags = note?.tags ? note.tags.split(" ").filter(Boolean) : [];
|
|
1328
|
-
const title = r.title;
|
|
1329
|
-
const hit = {
|
|
1330
|
-
path: r.id,
|
|
1331
|
-
// Round the raw MiniSearch score: 2 decimals preserve relevance gaps without 17-digit float tax.
|
|
1332
|
-
score: Math.round(r.score * 100) / 100,
|
|
1333
|
-
excerpt: extractExcerpt(body, terms, input.excerptLength ?? 120)
|
|
1334
|
-
};
|
|
1335
|
-
if (title && title !== basenameNoExt(r.id)) hit.title = title;
|
|
1336
|
-
if (noteTags.length > 0) hit.tags = noteTags;
|
|
1337
|
-
hits.push(hit);
|
|
1338
|
-
if (hits.length >= input.limit) break;
|
|
1339
|
-
}
|
|
1340
|
-
return hits;
|
|
1341
|
-
}
|
|
1342
|
-
|
|
1343
|
-
// src/tools/query_notes.ts
|
|
1531
|
+
import { z as z15 } from "zod";
|
|
1344
1532
|
var OPS_WITHOUT_VALUE = /* @__PURE__ */ new Set(["exists", "missing"]);
|
|
1345
|
-
var Predicate =
|
|
1346
|
-
key:
|
|
1347
|
-
op:
|
|
1533
|
+
var Predicate = z15.object({
|
|
1534
|
+
key: z15.string().min(1).describe("Frontmatter key to test (top-level)."),
|
|
1535
|
+
op: z15.enum(["eq", "ne", "contains", "exists", "missing", "gt", "gte", "lt", "lte"]).describe(
|
|
1348
1536
|
"eq/ne compare scalars; contains matches array membership or substring; exists/missing test key presence; gt/gte/lt/lte compare numbers or strings (ISO dates sort correctly)."
|
|
1349
1537
|
),
|
|
1350
|
-
value:
|
|
1538
|
+
value: z15.union([z15.string(), z15.number(), z15.boolean()]).optional().describe("Comparison value. Required for every op except exists/missing.")
|
|
1351
1539
|
}).refine((p) => OPS_WITHOUT_VALUE.has(p.op) || p.value !== void 0, {
|
|
1352
1540
|
message: "value is required for this op"
|
|
1353
1541
|
});
|
|
1354
|
-
var ISO_DATE =
|
|
1355
|
-
var QueryNotesInput =
|
|
1356
|
-
where:
|
|
1357
|
-
folder:
|
|
1358
|
-
tag:
|
|
1542
|
+
var ISO_DATE = z15.string().refine((s) => !Number.isNaN(Date.parse(s)), { message: "not a parseable date" });
|
|
1543
|
+
var QueryNotesInput = z15.object({
|
|
1544
|
+
where: z15.array(Predicate).max(20).default([]).describe("Frontmatter predicates. All must match (AND)."),
|
|
1545
|
+
folder: z15.string().optional().describe("Restrict to notes under this vault-relative folder prefix."),
|
|
1546
|
+
tag: z15.string().optional().describe("Restrict to notes with this tag (# prefix optional)."),
|
|
1359
1547
|
modifiedAfter: ISO_DATE.optional().describe(
|
|
1360
1548
|
"Only notes modified at or after this ISO 8601 date/time."
|
|
1361
1549
|
),
|
|
1362
1550
|
modifiedBefore: ISO_DATE.optional().describe(
|
|
1363
1551
|
"Only notes modified before this ISO 8601 date/time."
|
|
1364
1552
|
),
|
|
1365
|
-
minSizeBytes:
|
|
1366
|
-
maxSizeBytes:
|
|
1367
|
-
select:
|
|
1553
|
+
minSizeBytes: z15.number().int().min(0).optional().describe("Only notes at least this many bytes."),
|
|
1554
|
+
maxSizeBytes: z15.number().int().min(0).optional().describe("Only notes at most this many bytes."),
|
|
1555
|
+
select: z15.array(z15.string()).max(20).default([]).describe(
|
|
1368
1556
|
'Extra fields to return per hit: frontmatter keys, or the specials "mtime", "size", "tags". Default returns only path + title, keeping the payload minimal.'
|
|
1369
1557
|
),
|
|
1370
|
-
sort:
|
|
1371
|
-
order:
|
|
1372
|
-
limit:
|
|
1558
|
+
sort: z15.enum(["path", "title", "mtime", "size"]).default("path").describe("Sort field."),
|
|
1559
|
+
order: z15.enum(["asc", "desc"]).default("asc").describe("Sort order."),
|
|
1560
|
+
limit: z15.number().int().min(1).max(500).default(100).describe("Maximum number of results.")
|
|
1373
1561
|
});
|
|
1374
1562
|
function scalarEq(a, b) {
|
|
1375
1563
|
if (a === b) return true;
|
|
@@ -1469,16 +1657,16 @@ function queryNotes(ctx2, input) {
|
|
|
1469
1657
|
// src/tools/read_note.ts
|
|
1470
1658
|
import { readFile as readFile7 } from "fs/promises";
|
|
1471
1659
|
import { join as join11 } from "path";
|
|
1472
|
-
import { z as
|
|
1473
|
-
var ReadNoteInput =
|
|
1474
|
-
path:
|
|
1475
|
-
section:
|
|
1476
|
-
block:
|
|
1477
|
-
lines:
|
|
1478
|
-
from:
|
|
1479
|
-
to:
|
|
1660
|
+
import { z as z16 } from "zod";
|
|
1661
|
+
var ReadNoteInput = z16.object({
|
|
1662
|
+
path: z16.string().describe('Vault-relative path to the note, e.g. "Daily Notes/2026-05-29.md".'),
|
|
1663
|
+
section: z16.string().optional().describe("Return only this heading section (heading text, # prefix optional)."),
|
|
1664
|
+
block: z16.string().optional().describe("Return only the block anchored by this id (^ prefix optional)."),
|
|
1665
|
+
lines: z16.object({
|
|
1666
|
+
from: z16.number().int().min(1).describe("First line (1-indexed, inclusive)."),
|
|
1667
|
+
to: z16.number().int().min(1).optional().describe("Last line (1-indexed, inclusive). Defaults to end of file.")
|
|
1480
1668
|
}).optional().describe("Return only this line range (1-indexed, inclusive)."),
|
|
1481
|
-
includeFrontmatter:
|
|
1669
|
+
includeFrontmatter: z16.boolean().optional().describe(
|
|
1482
1670
|
"Include frontmatter in span results. Default false for section/block spans. No effect on lines or whole-note reads."
|
|
1483
1671
|
)
|
|
1484
1672
|
}).refine((v) => [v.section, v.block, v.lines].filter((x) => x !== void 0).length <= 1, {
|
|
@@ -1579,17 +1767,17 @@ async function readNote(ctx2, input) {
|
|
|
1579
1767
|
// src/tools/replace_in_note.ts
|
|
1580
1768
|
import { readFile as readFile8 } from "fs/promises";
|
|
1581
1769
|
import { join as join12 } from "path";
|
|
1582
|
-
import { z as
|
|
1583
|
-
var ReplaceInNoteInput =
|
|
1584
|
-
path:
|
|
1585
|
-
find:
|
|
1586
|
-
replace:
|
|
1587
|
-
regex:
|
|
1588
|
-
caseSensitive:
|
|
1589
|
-
wholeWord:
|
|
1590
|
-
limit:
|
|
1591
|
-
dryRun:
|
|
1592
|
-
prevHash:
|
|
1770
|
+
import { z as z17 } from "zod";
|
|
1771
|
+
var ReplaceInNoteInput = z17.object({
|
|
1772
|
+
path: z17.string().describe("Vault-relative path to the note."),
|
|
1773
|
+
find: z17.string().min(1).max(1e3).describe("Text or pattern to find (max 1000 chars; bounds regex-mode pattern size)."),
|
|
1774
|
+
replace: z17.string().describe("Replacement text. Supports $1, $2, \u2026 backreferences in regex mode."),
|
|
1775
|
+
regex: z17.boolean().default(false).describe("Treat find as a regular expression."),
|
|
1776
|
+
caseSensitive: z17.boolean().default(false).describe("Case-sensitive matching."),
|
|
1777
|
+
wholeWord: z17.boolean().default(false).describe("Match whole words only (\\b word-boundary)."),
|
|
1778
|
+
limit: z17.number().int().min(1).optional().describe("Maximum number of replacements. Omit to replace all."),
|
|
1779
|
+
dryRun: z17.boolean().default(false).describe("If true, report matches without writing anything."),
|
|
1780
|
+
prevHash: z17.string().optional().describe(
|
|
1593
1781
|
"Optional compare-and-swap guard: the contentHash from a prior read. Fails with hash_conflict if the note changed since."
|
|
1594
1782
|
)
|
|
1595
1783
|
});
|
|
@@ -1701,7 +1889,8 @@ var META_KEYS = [
|
|
|
1701
1889
|
"period",
|
|
1702
1890
|
"date",
|
|
1703
1891
|
"permanent",
|
|
1704
|
-
"prevHash"
|
|
1892
|
+
"prevHash",
|
|
1893
|
+
"budgetBytes"
|
|
1705
1894
|
];
|
|
1706
1895
|
function safeMeta(args) {
|
|
1707
1896
|
if (typeof args !== "object" || args === null) return {};
|
|
@@ -1770,6 +1959,11 @@ async function run(ctx2, name, args) {
|
|
|
1770
1959
|
const hits = queryNotes(ctx2, input);
|
|
1771
1960
|
return { content: [{ type: "text", text: JSON.stringify(hits) }] };
|
|
1772
1961
|
}
|
|
1962
|
+
case "context_pack": {
|
|
1963
|
+
const input = ContextPackInput.parse(args);
|
|
1964
|
+
const pack = contextPack(ctx2, input);
|
|
1965
|
+
return { content: [{ type: "text", text: JSON.stringify(pack) }] };
|
|
1966
|
+
}
|
|
1773
1967
|
case "read_note": {
|
|
1774
1968
|
const input = ReadNoteInput.parse(args);
|
|
1775
1969
|
const result = await readNote(ctx2, input);
|
|
@@ -2489,6 +2683,21 @@ var ALL_TOOLS = [
|
|
|
2489
2683
|
required: []
|
|
2490
2684
|
}
|
|
2491
2685
|
},
|
|
2686
|
+
{
|
|
2687
|
+
name: "context_pack",
|
|
2688
|
+
description: 'Assemble everything needed to ANSWER a natural-language question in one call, under a strict byte budget (default 2048): ranked excerpts, linked neighbor notes (backlinks/outlinks) with one-line summaries, and follow-up source paths. Use search to locate notes and query_notes for metadata filters; use context_pack when you want answer-ready context without multiple round-trips. Empty excerpts with confidence "none" or "low" means the vault lacks coverage \u2014 do not infer content.',
|
|
2689
|
+
inputSchema: {
|
|
2690
|
+
type: "object",
|
|
2691
|
+
properties: {
|
|
2692
|
+
query: { type: "string", description: "Natural-language question or topic." },
|
|
2693
|
+
budgetBytes: {
|
|
2694
|
+
type: "number",
|
|
2695
|
+
description: "Hard cap on response JSON bytes (256\u201316384, default 2048)."
|
|
2696
|
+
}
|
|
2697
|
+
},
|
|
2698
|
+
required: ["query"]
|
|
2699
|
+
}
|
|
2700
|
+
},
|
|
2492
2701
|
{
|
|
2493
2702
|
name: "read_note",
|
|
2494
2703
|
description: "Read a note or a span of it \u2014 by heading section, block reference, or line range. Returns structured JSON with the content, bytes returned, total note size, and a contentHash to pass as prevHash to edit tools for compare-and-swap. Use search or outline_note first to find the right path and section names.",
|
|
@@ -2907,7 +3116,7 @@ function startWatcher(ctx2, log2, opts) {
|
|
|
2907
3116
|
}
|
|
2908
3117
|
|
|
2909
3118
|
// src/index.ts
|
|
2910
|
-
var VERSION = true ? "0.
|
|
3119
|
+
var VERSION = true ? "0.12.0" : "0.0.0-dev";
|
|
2911
3120
|
var intent = parseCliIntent(process.argv.slice(2));
|
|
2912
3121
|
if (intent === "version") {
|
|
2913
3122
|
process.stdout.write(`${VERSION}
|