seekstone 0.20.3 → 0.22.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 +4 -2
- package/dist/index.js +654 -125
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -116,7 +116,7 @@ __export(fetch_model_exports, {
|
|
|
116
116
|
});
|
|
117
117
|
import { createHash as createHash4 } from "crypto";
|
|
118
118
|
import { existsSync as existsSync2 } from "fs";
|
|
119
|
-
import { mkdir as mkdir10, readFile as
|
|
119
|
+
import { mkdir as mkdir10, readFile as readFile21, writeFile as writeFile5 } from "fs/promises";
|
|
120
120
|
import { join as join13 } from "path";
|
|
121
121
|
function selectManifest(argv, env) {
|
|
122
122
|
let requested;
|
|
@@ -154,7 +154,7 @@ async function runFetchModel(deps) {
|
|
|
154
154
|
let skipped = 0;
|
|
155
155
|
for (const file of manifest.files) {
|
|
156
156
|
const dest = join13(destDir, file.name);
|
|
157
|
-
if (existsSync2(dest) && sha2562(await
|
|
157
|
+
if (existsSync2(dest) && sha2562(await readFile21(dest)) === file.sha256) {
|
|
158
158
|
skipped++;
|
|
159
159
|
continue;
|
|
160
160
|
}
|
|
@@ -533,6 +533,57 @@ function parseFrontmatter(text) {
|
|
|
533
533
|
};
|
|
534
534
|
}
|
|
535
535
|
|
|
536
|
+
// ../core/src/tasks.ts
|
|
537
|
+
var TASK_RE = /^([ \t]*)([-*+]) \[(.)\](?: (.*))?$/;
|
|
538
|
+
var HEADING_RE = /^(#{1,6})\s+(.+)/;
|
|
539
|
+
var FENCE_RE = /^[ \t]*(`{3,}|~{3,})[ \t]*([^`]*)$/;
|
|
540
|
+
function extractTasks(raw) {
|
|
541
|
+
const fm = parseFrontmatter(raw);
|
|
542
|
+
const tasks = [];
|
|
543
|
+
let charOffset = 0;
|
|
544
|
+
let heading;
|
|
545
|
+
let fence = null;
|
|
546
|
+
for (const [i, rawLine] of raw.split("\n").entries()) {
|
|
547
|
+
const lineCharOffset = charOffset;
|
|
548
|
+
charOffset += rawLine.length + 1;
|
|
549
|
+
if (lineCharOffset < fm.bodyStart) continue;
|
|
550
|
+
const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
|
|
551
|
+
if (fence) {
|
|
552
|
+
const close = FENCE_RE.exec(line);
|
|
553
|
+
const closeMarker = close?.[1] ?? "";
|
|
554
|
+
const closeTrailer = close?.[2] ?? "";
|
|
555
|
+
if (closeMarker.startsWith(fence.char) && closeMarker.length >= fence.len && closeTrailer === "") {
|
|
556
|
+
fence = null;
|
|
557
|
+
}
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
560
|
+
const open2 = /^[ \t]*(`{3,}|~{3,})/.exec(line);
|
|
561
|
+
if (open2) {
|
|
562
|
+
const marker = open2[1] ?? "";
|
|
563
|
+
fence = { char: marker[0] ?? "`", len: marker.length };
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
const hm = HEADING_RE.exec(line);
|
|
567
|
+
if (hm) {
|
|
568
|
+
heading = (hm[2] ?? "").trim();
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
const tm = TASK_RE.exec(line);
|
|
572
|
+
if (tm) {
|
|
573
|
+
const indent = tm[1] ?? "";
|
|
574
|
+
tasks.push({
|
|
575
|
+
line: i + 1,
|
|
576
|
+
state: tm[3] ?? "",
|
|
577
|
+
text: (tm[4] ?? "").trim(),
|
|
578
|
+
...heading !== void 0 ? { heading } : {},
|
|
579
|
+
col: indent.length + 3
|
|
580
|
+
// bullet + space + '[' precede the marker
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
return tasks;
|
|
585
|
+
}
|
|
586
|
+
|
|
536
587
|
// src/tools/append_note.ts
|
|
537
588
|
import { z } from "zod";
|
|
538
589
|
|
|
@@ -896,6 +947,7 @@ async function appendNote(ctx2, input) {
|
|
|
896
947
|
if (cached) {
|
|
897
948
|
cached.body = newBody;
|
|
898
949
|
cached.raw = newContent;
|
|
950
|
+
cached.tasks = extractTasks(newContent);
|
|
899
951
|
}
|
|
900
952
|
return {
|
|
901
953
|
path: input.path,
|
|
@@ -983,6 +1035,13 @@ function frontmatterTags(fm) {
|
|
|
983
1035
|
}
|
|
984
1036
|
return [];
|
|
985
1037
|
}
|
|
1038
|
+
function frontmatterAliases(fm) {
|
|
1039
|
+
if (!fm) return [];
|
|
1040
|
+
const raw = fm.aliases ?? fm.alias;
|
|
1041
|
+
if (raw == null) return [];
|
|
1042
|
+
const values = Array.isArray(raw) ? raw : typeof raw === "string" ? raw.split(",") : [];
|
|
1043
|
+
return values.filter((a) => typeof a === "string").map((a) => a.trim()).filter(Boolean);
|
|
1044
|
+
}
|
|
986
1045
|
|
|
987
1046
|
// src/tools/context_pack.ts
|
|
988
1047
|
import { z as z3 } from "zod";
|
|
@@ -1015,15 +1074,26 @@ function truncate(s, max) {
|
|
|
1015
1074
|
function buildResolveMaps(notes2) {
|
|
1016
1075
|
const byBasename = /* @__PURE__ */ new Map();
|
|
1017
1076
|
const byPathNoExt = /* @__PURE__ */ new Map();
|
|
1018
|
-
|
|
1077
|
+
const byAlias = /* @__PURE__ */ new Map();
|
|
1078
|
+
for (const [path2, note] of notes2) {
|
|
1019
1079
|
const pathNoExt = path2.replace(/\.md$/i, "").toLowerCase();
|
|
1020
1080
|
const base = pathNoExt.split("/").pop() ?? pathNoExt;
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
const
|
|
1024
|
-
|
|
1081
|
+
push(byBasename, base, path2);
|
|
1082
|
+
push(byPathNoExt, pathNoExt, path2);
|
|
1083
|
+
const fm = note?.fm ?? null;
|
|
1084
|
+
for (const alias of frontmatterAliases(fm)) {
|
|
1085
|
+
push(byAlias, alias.toLowerCase(), { path: path2, alias });
|
|
1086
|
+
}
|
|
1025
1087
|
}
|
|
1026
|
-
|
|
1088
|
+
for (const list of byBasename.values()) list.sort();
|
|
1089
|
+
for (const list of byPathNoExt.values()) list.sort();
|
|
1090
|
+
for (const list of byAlias.values()) list.sort((a, b) => a.path < b.path ? -1 : 1);
|
|
1091
|
+
return { byBasename, byPathNoExt, byAlias };
|
|
1092
|
+
}
|
|
1093
|
+
function push(map, key, value) {
|
|
1094
|
+
const list = map.get(key);
|
|
1095
|
+
if (list) list.push(value);
|
|
1096
|
+
else map.set(key, [value]);
|
|
1027
1097
|
}
|
|
1028
1098
|
function resolveLink(target, notes2, maps) {
|
|
1029
1099
|
if (notes2.has(target)) return target;
|
|
@@ -1032,7 +1102,27 @@ function resolveLink(target, notes2, maps) {
|
|
|
1032
1102
|
const m = maps ?? buildResolveMaps(notes2);
|
|
1033
1103
|
const targetLower = target.toLowerCase();
|
|
1034
1104
|
const targetBase = targetLower.split("/").pop() ?? targetLower;
|
|
1035
|
-
return m.byBasename.get(targetBase) ?? m.byPathNoExt.get(targetLower);
|
|
1105
|
+
return m.byBasename.get(targetBase)?.[0] ?? m.byPathNoExt.get(targetLower)?.[0] ?? m.byAlias.get(targetLower)?.[0]?.path;
|
|
1106
|
+
}
|
|
1107
|
+
function resolveCandidates(target, notes2, maps) {
|
|
1108
|
+
const out = [];
|
|
1109
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1110
|
+
const add = (candidate) => {
|
|
1111
|
+
if (seen.has(candidate.path)) return;
|
|
1112
|
+
seen.add(candidate.path);
|
|
1113
|
+
out.push(candidate);
|
|
1114
|
+
};
|
|
1115
|
+
if (notes2.has(target)) add({ path: target, via: "path" });
|
|
1116
|
+
const withMd = `${target}.md`;
|
|
1117
|
+
if (notes2.has(withMd)) add({ path: withMd, via: "path" });
|
|
1118
|
+
const m = maps ?? buildResolveMaps(notes2);
|
|
1119
|
+
const targetLower = target.toLowerCase();
|
|
1120
|
+
const targetBase = targetLower.split("/").pop() ?? targetLower;
|
|
1121
|
+
for (const path2 of m.byBasename.get(targetBase) ?? []) add({ path: path2, via: "basename" });
|
|
1122
|
+
for (const path2 of m.byPathNoExt.get(targetLower) ?? []) add({ path: path2, via: "relpath" });
|
|
1123
|
+
for (const { path: path2, alias } of m.byAlias.get(targetLower) ?? [])
|
|
1124
|
+
add({ path: path2, via: "alias", alias });
|
|
1125
|
+
return out;
|
|
1036
1126
|
}
|
|
1037
1127
|
|
|
1038
1128
|
// src/tools/search.ts
|
|
@@ -1478,6 +1568,7 @@ function buildDoc(relPath, raw) {
|
|
|
1478
1568
|
fmKeys: fm.keys.join(" "),
|
|
1479
1569
|
fm: fm.data,
|
|
1480
1570
|
raw,
|
|
1571
|
+
tasks: extractTasks(raw),
|
|
1481
1572
|
sizeBytes: Buffer.byteLength(raw, "utf8"),
|
|
1482
1573
|
mtimeMs: Date.now()
|
|
1483
1574
|
};
|
|
@@ -1772,11 +1863,77 @@ function listTags(ctx2, input) {
|
|
|
1772
1863
|
return { tags, total: tags.length };
|
|
1773
1864
|
}
|
|
1774
1865
|
|
|
1775
|
-
// src/tools/
|
|
1866
|
+
// src/tools/list_tasks.ts
|
|
1776
1867
|
import { z as z10 } from "zod";
|
|
1777
|
-
var
|
|
1778
|
-
|
|
1779
|
-
path: z10.string().optional().describe("
|
|
1868
|
+
var ISO_DATE = z10.string().refine((s) => !Number.isNaN(Date.parse(s)), { message: "not a parseable date" });
|
|
1869
|
+
var ListTasksInput = z10.object({
|
|
1870
|
+
path: z10.string().optional().describe("Vault-relative path to a single note. Omit to search the whole vault."),
|
|
1871
|
+
folder: z10.string().optional().describe("Restrict to notes under this vault-relative folder prefix."),
|
|
1872
|
+
tag: z10.string().optional().describe(
|
|
1873
|
+
'Restrict to notes with this tag. # optional, case-insensitive, and nested child tags match (e.g. "project" matches #project/alpha).'
|
|
1874
|
+
),
|
|
1875
|
+
state: z10.union([z10.enum(["open", "done", "any"]), z10.string().length(1)]).default("open").describe(
|
|
1876
|
+
'Which tasks to return: "open" (`- [ ]`, the default), "done" (`- [x]` or `- [X]`), "any", or a single custom marker character such as "/" or "-".'
|
|
1877
|
+
),
|
|
1878
|
+
modifiedAfter: ISO_DATE.optional().describe(
|
|
1879
|
+
"Only tasks in notes modified at or after this ISO 8601 date/time."
|
|
1880
|
+
),
|
|
1881
|
+
limit: z10.number().int().min(1).max(500).default(100).describe("Maximum number of tasks.")
|
|
1882
|
+
});
|
|
1883
|
+
function stateMatches(marker, wanted) {
|
|
1884
|
+
if (wanted === "any") return true;
|
|
1885
|
+
if (wanted === "open") return marker === " ";
|
|
1886
|
+
if (wanted === "done") return marker === "x" || marker === "X";
|
|
1887
|
+
return marker === wanted;
|
|
1888
|
+
}
|
|
1889
|
+
function listTasks(ctx2, input) {
|
|
1890
|
+
const tag = input.tag === void 0 ? void 0 : normalizeTag(input.tag);
|
|
1891
|
+
const after = input.modifiedAfter ? Date.parse(input.modifiedAfter) : void 0;
|
|
1892
|
+
let sources;
|
|
1893
|
+
if (input.path !== void 0) {
|
|
1894
|
+
const note = ctx2.notes.get(input.path);
|
|
1895
|
+
if (!note) {
|
|
1896
|
+
throw new Error(
|
|
1897
|
+
JSON.stringify({
|
|
1898
|
+
error: "note_not_found",
|
|
1899
|
+
path: input.path,
|
|
1900
|
+
hint: "No indexed note at this path. Use resolve_note or list_notes to find the right path."
|
|
1901
|
+
})
|
|
1902
|
+
);
|
|
1903
|
+
}
|
|
1904
|
+
sources = [[input.path, note]];
|
|
1905
|
+
} else {
|
|
1906
|
+
sources = ctx2.notes;
|
|
1907
|
+
}
|
|
1908
|
+
const rows = [];
|
|
1909
|
+
for (const [path2, note] of sources) {
|
|
1910
|
+
if (input.folder && !path2.startsWith(input.folder)) continue;
|
|
1911
|
+
if (tag && !noteHasTag(note.tags, tag)) continue;
|
|
1912
|
+
if (after !== void 0 && note.mtimeMs < after) continue;
|
|
1913
|
+
if (note.tasks.length === 0) continue;
|
|
1914
|
+
for (const task of note.tasks) {
|
|
1915
|
+
if (!stateMatches(task.state, input.state)) continue;
|
|
1916
|
+
rows.push({
|
|
1917
|
+
path: path2,
|
|
1918
|
+
line: task.line,
|
|
1919
|
+
state: task.state,
|
|
1920
|
+
text: task.text,
|
|
1921
|
+
...task.heading !== void 0 ? { heading: task.heading } : {}
|
|
1922
|
+
});
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
if (rows.length === 0 && input.tag) assertTagExists(ctx2.notes, input.tag);
|
|
1926
|
+
rows.sort((a, b) => a.path.localeCompare(b.path) || a.line - b.line);
|
|
1927
|
+
const total = rows.length;
|
|
1928
|
+
const tasks = rows.slice(0, input.limit);
|
|
1929
|
+
return { tasks, total, truncated: total > input.limit };
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
// src/tools/list_writes.ts
|
|
1933
|
+
import { z as z11 } from "zod";
|
|
1934
|
+
var ListWritesInput = z11.object({
|
|
1935
|
+
limit: z11.number().int().min(1).max(200).default(20).describe("Max entries to return, newest first (1\u2013200, default 20)."),
|
|
1936
|
+
path: z11.string().optional().describe("Only entries that touched this vault-relative path.")
|
|
1780
1937
|
});
|
|
1781
1938
|
function listWrites(ctx2, rawInput) {
|
|
1782
1939
|
const input = ListWritesInput.parse(rawInput);
|
|
@@ -1789,17 +1946,17 @@ function listWrites(ctx2, rawInput) {
|
|
|
1789
1946
|
// src/tools/move_note.ts
|
|
1790
1947
|
import { mkdir as mkdir4, readFile as readFile5, rename as rename4 } from "fs/promises";
|
|
1791
1948
|
import { dirname as dirname2, join as join3 } from "path";
|
|
1792
|
-
import { z as
|
|
1949
|
+
import { z as z12 } from "zod";
|
|
1793
1950
|
|
|
1794
1951
|
// src/tools/rewrite_links.ts
|
|
1795
1952
|
import { posix } from "path";
|
|
1796
1953
|
var WIKI_RE = /(!?)\[\[([^\]|#\n]{1,512})(#[^\]|\n]{1,512})?(\|[^\]\n]{1,512})?\]\]/g;
|
|
1797
1954
|
var MD_RE = /(!?)\[([^\]\n]*)\]\(([^)\n]+)\)/g;
|
|
1798
|
-
var
|
|
1955
|
+
var FENCE_RE2 = /^\s*(```|~~~)/;
|
|
1799
1956
|
function mapUnfencedLines(raw, fn) {
|
|
1800
1957
|
let inFence = false;
|
|
1801
1958
|
return raw.split("\n").map((line) => {
|
|
1802
|
-
if (
|
|
1959
|
+
if (FENCE_RE2.test(line)) {
|
|
1803
1960
|
inFence = !inFence;
|
|
1804
1961
|
return line;
|
|
1805
1962
|
}
|
|
@@ -1838,8 +1995,9 @@ function rewriteNoteLinks(raw, sourcePath, oldPath, newPath, preNotes, postNotes
|
|
|
1838
1995
|
let count = 0;
|
|
1839
1996
|
const rewriteWikiLine = (line) => line.replace(WIKI_RE, (full, bang, target, frag, alias) => {
|
|
1840
1997
|
const t = target.trim();
|
|
1841
|
-
if (
|
|
1842
|
-
|
|
1998
|
+
if (resolveCandidates(t, postNotes)[0]?.path === newPath) return full;
|
|
1999
|
+
const pre = resolveCandidates(t, preNotes)[0];
|
|
2000
|
+
if (pre?.path !== oldPath || pre.via === "alias") return full;
|
|
1843
2001
|
count++;
|
|
1844
2002
|
const next = wikiTargetFor(newPath, postNotes);
|
|
1845
2003
|
return `${bang}[[${next}${frag ?? ""}${alias ?? ""}]]`;
|
|
@@ -1884,14 +2042,14 @@ function hasMarkdownLinkTo(raw, sourcePath, targetPath) {
|
|
|
1884
2042
|
}
|
|
1885
2043
|
|
|
1886
2044
|
// src/tools/move_note.ts
|
|
1887
|
-
var MoveNoteInput =
|
|
1888
|
-
from:
|
|
1889
|
-
to:
|
|
1890
|
-
overwrite:
|
|
1891
|
-
rewriteLinks:
|
|
2045
|
+
var MoveNoteInput = z12.object({
|
|
2046
|
+
from: z12.string().describe("Vault-relative path of the note to move."),
|
|
2047
|
+
to: z12.string().describe("Vault-relative destination path."),
|
|
2048
|
+
overwrite: z12.boolean().default(false).describe("Overwrite the destination if it exists. Defaults to false."),
|
|
2049
|
+
rewriteLinks: z12.boolean().default(true).describe(
|
|
1892
2050
|
"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."
|
|
1893
2051
|
),
|
|
1894
|
-
prevHash:
|
|
2052
|
+
prevHash: z12.string().optional().describe(
|
|
1895
2053
|
"Optional compare-and-swap guard on the source note: the contentHash from a prior read. Fails with hash_conflict if the note changed since."
|
|
1896
2054
|
)
|
|
1897
2055
|
});
|
|
@@ -1997,7 +2155,7 @@ async function moveNote(ctx2, rawInput) {
|
|
|
1997
2155
|
import { readFile as readFile6 } from "fs/promises";
|
|
1998
2156
|
|
|
1999
2157
|
// ../core/src/outline.ts
|
|
2000
|
-
var
|
|
2158
|
+
var HEADING_RE2 = /^(#{1,6})\s+(.+)/;
|
|
2001
2159
|
var BLOCK_REF_RE = /\^([a-zA-Z0-9-]+)$/;
|
|
2002
2160
|
function buildOutline(raw, options = {}) {
|
|
2003
2161
|
const { includeBlocks = true, includeSizes = false } = options;
|
|
@@ -2010,7 +2168,7 @@ function buildOutline(raw, options = {}) {
|
|
|
2010
2168
|
const lineCharOffset = charOffset;
|
|
2011
2169
|
const lineNum = i + 1;
|
|
2012
2170
|
if (charOffset >= fm.bodyStart) {
|
|
2013
|
-
const hm =
|
|
2171
|
+
const hm = HEADING_RE2.exec(line);
|
|
2014
2172
|
if (hm) {
|
|
2015
2173
|
headings.push({
|
|
2016
2174
|
text: (hm[2] ?? "").trim(),
|
|
@@ -2044,11 +2202,11 @@ function buildOutline(raw, options = {}) {
|
|
|
2044
2202
|
}
|
|
2045
2203
|
|
|
2046
2204
|
// src/tools/outline_note.ts
|
|
2047
|
-
import { z as
|
|
2048
|
-
var OutlineNoteInput =
|
|
2049
|
-
path:
|
|
2050
|
-
includeBlocks:
|
|
2051
|
-
includeSizes:
|
|
2205
|
+
import { z as z13 } from "zod";
|
|
2206
|
+
var OutlineNoteInput = z13.object({
|
|
2207
|
+
path: z13.string().describe("Vault-relative path to the note."),
|
|
2208
|
+
includeBlocks: z13.boolean().default(true).describe("Include block-reference anchors (^id). Default true."),
|
|
2209
|
+
includeSizes: z13.boolean().default(false).describe("Include per-section character length in each heading entry. Default false.")
|
|
2052
2210
|
});
|
|
2053
2211
|
async function outlineNote(ctx2, input) {
|
|
2054
2212
|
const absPath = resolveVaultPath(ctx2.vaultRoot, input.path);
|
|
@@ -2062,13 +2220,13 @@ async function outlineNote(ctx2, input) {
|
|
|
2062
2220
|
// src/tools/patch_frontmatter.ts
|
|
2063
2221
|
import { readFile as readFile7 } from "fs/promises";
|
|
2064
2222
|
import { parseDocument as parseDocument2 } from "yaml";
|
|
2065
|
-
import { z as
|
|
2066
|
-
var PatchFrontmatterInput =
|
|
2067
|
-
path:
|
|
2068
|
-
patch:
|
|
2223
|
+
import { z as z14 } from "zod";
|
|
2224
|
+
var PatchFrontmatterInput = z14.object({
|
|
2225
|
+
path: z14.string().describe("Vault-relative path to the note."),
|
|
2226
|
+
patch: z14.record(z14.string(), z14.unknown()).describe(
|
|
2069
2227
|
"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."
|
|
2070
2228
|
),
|
|
2071
|
-
prevHash:
|
|
2229
|
+
prevHash: z14.string().optional().describe(
|
|
2072
2230
|
"Optional compare-and-swap guard: the contentHash from a prior read. Fails with hash_conflict if the note changed since."
|
|
2073
2231
|
)
|
|
2074
2232
|
});
|
|
@@ -2125,6 +2283,7 @@ ${original}`;
|
|
|
2125
2283
|
const updated = parseFrontmatter(newContent);
|
|
2126
2284
|
cached.raw = newContent;
|
|
2127
2285
|
cached.fmKeys = updated.keys.join(" ");
|
|
2286
|
+
cached.tasks = extractTasks(newContent);
|
|
2128
2287
|
}
|
|
2129
2288
|
return {
|
|
2130
2289
|
path: input.path,
|
|
@@ -2137,21 +2296,21 @@ ${original}`;
|
|
|
2137
2296
|
|
|
2138
2297
|
// src/tools/patch_note.ts
|
|
2139
2298
|
import { readFile as readFile8 } from "fs/promises";
|
|
2140
|
-
import { z as
|
|
2141
|
-
var PatchNoteInput =
|
|
2142
|
-
path:
|
|
2143
|
-
target:
|
|
2144
|
-
|
|
2145
|
-
|
|
2299
|
+
import { z as z15 } from "zod";
|
|
2300
|
+
var PatchNoteInput = z15.object({
|
|
2301
|
+
path: z15.string().describe("Vault-relative path to the note."),
|
|
2302
|
+
target: z15.union([
|
|
2303
|
+
z15.object({ heading: z15.string().describe("Heading text (without # markers).") }),
|
|
2304
|
+
z15.object({ block: z15.string().describe("Block reference ID (without the ^ prefix).") })
|
|
2146
2305
|
]).describe("Target section. Provide exactly one of heading or block."),
|
|
2147
|
-
operation:
|
|
2306
|
+
operation: z15.enum(["append", "prepend", "replace"]).describe(
|
|
2148
2307
|
"append: insert after section content. prepend: insert after heading line. replace: swap section content."
|
|
2149
2308
|
),
|
|
2150
|
-
content:
|
|
2151
|
-
createIfMissing:
|
|
2309
|
+
content: z15.string().min(1).describe("Content to insert or replace with."),
|
|
2310
|
+
createIfMissing: z15.boolean().default(false).describe(
|
|
2152
2311
|
"If the heading target is not found, append a new heading (level 2) + content. Only valid for heading targets."
|
|
2153
2312
|
),
|
|
2154
|
-
prevHash:
|
|
2313
|
+
prevHash: z15.string().optional().describe(
|
|
2155
2314
|
"Optional compare-and-swap guard: the contentHash from a prior read. Fails with hash_conflict if the note changed since."
|
|
2156
2315
|
)
|
|
2157
2316
|
});
|
|
@@ -2279,6 +2438,7 @@ function updateCache(ctx2, path2, newContent, bodyStart) {
|
|
|
2279
2438
|
if (cached) {
|
|
2280
2439
|
cached.body = newContent.slice(bodyStart);
|
|
2281
2440
|
cached.raw = newContent;
|
|
2441
|
+
cached.tasks = extractTasks(newContent);
|
|
2282
2442
|
cached.sizeBytes = Buffer.byteLength(newContent, "utf8");
|
|
2283
2443
|
}
|
|
2284
2444
|
}
|
|
@@ -2286,8 +2446,8 @@ function updateCache(ctx2, path2, newContent, bodyStart) {
|
|
|
2286
2446
|
// src/tools/periodic_note.ts
|
|
2287
2447
|
import { access as access3, mkdir as mkdir5, readFile as readFile9 } from "fs/promises";
|
|
2288
2448
|
import { dirname as dirname3, join as join4 } from "path";
|
|
2289
|
-
import { z as
|
|
2290
|
-
var PERIOD =
|
|
2449
|
+
import { z as z16 } from "zod";
|
|
2450
|
+
var PERIOD = z16.enum(["daily", "weekly", "monthly", "quarterly", "yearly"]);
|
|
2291
2451
|
var DEFAULTS = {
|
|
2292
2452
|
daily: { folder: "", format: "YYYY-MM-DD", template: "" },
|
|
2293
2453
|
weekly: { folder: "", format: "gggg-[W]ww", template: "" },
|
|
@@ -2352,12 +2512,12 @@ function resolveNotePath(cfg, date) {
|
|
|
2352
2512
|
const filename = `${formatMomentDate(date, cfg.format)}.md`;
|
|
2353
2513
|
return cfg.folder ? `${cfg.folder}/${filename}` : filename;
|
|
2354
2514
|
}
|
|
2355
|
-
var GetPeriodicNoteInput =
|
|
2515
|
+
var GetPeriodicNoteInput = z16.object({
|
|
2356
2516
|
period: PERIOD.default("daily").describe(
|
|
2357
2517
|
"Period type: daily (default), weekly, monthly, quarterly, or yearly."
|
|
2358
2518
|
),
|
|
2359
|
-
date:
|
|
2360
|
-
createIfMissing:
|
|
2519
|
+
date: z16.string().optional().describe("ISO date string (YYYY-MM-DD). Defaults to today when omitted."),
|
|
2520
|
+
createIfMissing: z16.boolean().default(false).describe(
|
|
2361
2521
|
"Create the note from the configured template if it does not exist. Default false \u2014 reports missing without creating."
|
|
2362
2522
|
)
|
|
2363
2523
|
});
|
|
@@ -2393,12 +2553,12 @@ async function getPeriodicNote(ctx2, rawInput) {
|
|
|
2393
2553
|
upsertDoc(ctx2, buildDoc(path2, body));
|
|
2394
2554
|
return { path: path2, existed: false, created: true, contentHash: contentHash(body) };
|
|
2395
2555
|
}
|
|
2396
|
-
var AppendPeriodicNoteInput =
|
|
2556
|
+
var AppendPeriodicNoteInput = z16.object({
|
|
2397
2557
|
period: PERIOD.default("daily").describe("Period type. Default: daily."),
|
|
2398
|
-
date:
|
|
2399
|
-
content:
|
|
2400
|
-
createIfMissing:
|
|
2401
|
-
prevHash:
|
|
2558
|
+
date: z16.string().optional().describe("ISO date string (YYYY-MM-DD). Defaults to today."),
|
|
2559
|
+
content: z16.string().min(1).describe("Text to append to the note body. Separated from existing content by a blank line."),
|
|
2560
|
+
createIfMissing: z16.boolean().default(true).describe("Create the note if it does not exist before appending. Default true."),
|
|
2561
|
+
prevHash: z16.string().optional().describe(
|
|
2402
2562
|
"Optional compare-and-swap guard: the contentHash from a prior read of the resolved periodic note. Fails with hash_conflict if it changed since."
|
|
2403
2563
|
)
|
|
2404
2564
|
});
|
|
@@ -2450,6 +2610,7 @@ async function appendPeriodicNote(ctx2, rawInput) {
|
|
|
2450
2610
|
if (cached) {
|
|
2451
2611
|
cached.body = newBody;
|
|
2452
2612
|
cached.raw = newRaw;
|
|
2613
|
+
cached.tasks = extractTasks(newRaw);
|
|
2453
2614
|
}
|
|
2454
2615
|
return {
|
|
2455
2616
|
path: path2,
|
|
@@ -2459,38 +2620,38 @@ async function appendPeriodicNote(ctx2, rawInput) {
|
|
|
2459
2620
|
}
|
|
2460
2621
|
|
|
2461
2622
|
// src/tools/query_notes.ts
|
|
2462
|
-
import { z as
|
|
2623
|
+
import { z as z17 } from "zod";
|
|
2463
2624
|
var OPS_WITHOUT_VALUE = /* @__PURE__ */ new Set(["exists", "missing"]);
|
|
2464
|
-
var Predicate =
|
|
2465
|
-
key:
|
|
2466
|
-
op:
|
|
2625
|
+
var Predicate = z17.object({
|
|
2626
|
+
key: z17.string().min(1).describe("Frontmatter key to test (top-level)."),
|
|
2627
|
+
op: z17.enum(["eq", "ne", "contains", "exists", "missing", "gt", "gte", "lt", "lte"]).describe(
|
|
2467
2628
|
"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)."
|
|
2468
2629
|
),
|
|
2469
|
-
value:
|
|
2630
|
+
value: z17.union([z17.string(), z17.number(), z17.boolean()]).optional().describe("Comparison value. Required for every op except exists/missing.")
|
|
2470
2631
|
}).refine((p) => OPS_WITHOUT_VALUE.has(p.op) || p.value !== void 0, {
|
|
2471
2632
|
message: "value is required for this op"
|
|
2472
2633
|
});
|
|
2473
|
-
var
|
|
2474
|
-
var QueryNotesInput =
|
|
2475
|
-
where:
|
|
2476
|
-
folder:
|
|
2477
|
-
tag:
|
|
2634
|
+
var ISO_DATE2 = z17.string().refine((s) => !Number.isNaN(Date.parse(s)), { message: "not a parseable date" });
|
|
2635
|
+
var QueryNotesInput = z17.object({
|
|
2636
|
+
where: z17.array(Predicate).max(20).default([]).describe("Frontmatter predicates. All must match (AND)."),
|
|
2637
|
+
folder: z17.string().optional().describe("Restrict to notes under this vault-relative folder prefix."),
|
|
2638
|
+
tag: z17.string().optional().describe(
|
|
2478
2639
|
'Restrict to notes with this tag. # optional, case-insensitive, and nested child tags match (e.g. "project" matches #project/alpha).'
|
|
2479
2640
|
),
|
|
2480
|
-
modifiedAfter:
|
|
2641
|
+
modifiedAfter: ISO_DATE2.optional().describe(
|
|
2481
2642
|
"Only notes modified at or after this ISO 8601 date/time."
|
|
2482
2643
|
),
|
|
2483
|
-
modifiedBefore:
|
|
2644
|
+
modifiedBefore: ISO_DATE2.optional().describe(
|
|
2484
2645
|
"Only notes modified before this ISO 8601 date/time."
|
|
2485
2646
|
),
|
|
2486
|
-
minSizeBytes:
|
|
2487
|
-
maxSizeBytes:
|
|
2488
|
-
select:
|
|
2647
|
+
minSizeBytes: z17.number().int().min(0).optional().describe("Only notes at least this many bytes."),
|
|
2648
|
+
maxSizeBytes: z17.number().int().min(0).optional().describe("Only notes at most this many bytes."),
|
|
2649
|
+
select: z17.array(z17.string()).max(20).default([]).describe(
|
|
2489
2650
|
'Extra fields to return per hit: frontmatter keys, or the specials "mtime", "size", "tags". Default returns only path + title, keeping the payload minimal.'
|
|
2490
2651
|
),
|
|
2491
|
-
sort:
|
|
2492
|
-
order:
|
|
2493
|
-
limit:
|
|
2652
|
+
sort: z17.enum(["path", "title", "mtime", "size"]).default("path").describe("Sort field."),
|
|
2653
|
+
order: z17.enum(["asc", "desc"]).default("asc").describe("Sort order."),
|
|
2654
|
+
limit: z17.number().int().min(1).max(500).default(100).describe("Maximum number of results.")
|
|
2494
2655
|
});
|
|
2495
2656
|
function scalarEq(a, b) {
|
|
2496
2657
|
if (a === b) return true;
|
|
@@ -2590,16 +2751,16 @@ function queryNotes(ctx2, input) {
|
|
|
2590
2751
|
|
|
2591
2752
|
// src/tools/read_note.ts
|
|
2592
2753
|
import { readFile as readFile10 } from "fs/promises";
|
|
2593
|
-
import { z as
|
|
2594
|
-
var ReadNoteInput =
|
|
2595
|
-
path:
|
|
2596
|
-
section:
|
|
2597
|
-
block:
|
|
2598
|
-
lines:
|
|
2599
|
-
from:
|
|
2600
|
-
to:
|
|
2754
|
+
import { z as z18 } from "zod";
|
|
2755
|
+
var ReadNoteInput = z18.object({
|
|
2756
|
+
path: z18.string().describe('Vault-relative path to the note, e.g. "Daily Notes/2026-05-29.md".'),
|
|
2757
|
+
section: z18.string().optional().describe("Return only this heading section (heading text, # prefix optional)."),
|
|
2758
|
+
block: z18.string().optional().describe("Return only the block anchored by this id (^ prefix optional)."),
|
|
2759
|
+
lines: z18.object({
|
|
2760
|
+
from: z18.number().int().min(1).describe("First line (1-indexed, inclusive)."),
|
|
2761
|
+
to: z18.number().int().min(1).optional().describe("Last line (1-indexed, inclusive). Defaults to end of file.")
|
|
2601
2762
|
}).optional().describe("Return only this line range (1-indexed, inclusive)."),
|
|
2602
|
-
includeFrontmatter:
|
|
2763
|
+
includeFrontmatter: z18.boolean().optional().describe(
|
|
2603
2764
|
"Include frontmatter in span results. Default false for section/block spans. No effect on lines or whole-note reads."
|
|
2604
2765
|
)
|
|
2605
2766
|
}).refine((v) => [v.section, v.block, v.lines].filter((x) => x !== void 0).length <= 1, {
|
|
@@ -2694,19 +2855,77 @@ async function readNote(ctx2, input) {
|
|
|
2694
2855
|
};
|
|
2695
2856
|
}
|
|
2696
2857
|
|
|
2858
|
+
// src/tools/read_notes.ts
|
|
2859
|
+
import { z as z19 } from "zod";
|
|
2860
|
+
var DEFAULT_BUDGET_BYTES = 16384;
|
|
2861
|
+
var ReadNotesInput = z19.object({
|
|
2862
|
+
items: z19.array(ReadNoteInput).min(1).max(20).describe(
|
|
2863
|
+
"Notes to read, 1\u201320, each with the same options as read_note (path plus at most one of section, block, or lines)."
|
|
2864
|
+
),
|
|
2865
|
+
budgetBytes: z19.number().int().min(256).max(65536).default(DEFAULT_BUDGET_BYTES).describe(
|
|
2866
|
+
"Cap on the total note text returned across all items (UTF-8 bytes). When exceeded, each item is truncated to a fair share."
|
|
2867
|
+
)
|
|
2868
|
+
});
|
|
2869
|
+
async function readNotes(ctx2, input) {
|
|
2870
|
+
const budget = input.budgetBytes;
|
|
2871
|
+
const results = await Promise.all(
|
|
2872
|
+
input.items.map((item) => readNote(ctx2, item).catch((err) => toItemError(item.path, err)))
|
|
2873
|
+
);
|
|
2874
|
+
const entries = results.filter((r) => !("error" in r)).map((r) => ({ r, size: r.bytesReturned, alloc: 0 }));
|
|
2875
|
+
allocate(entries, budget);
|
|
2876
|
+
const cut = new Map(
|
|
2877
|
+
entries.map((e) => [e.r, truncateItem(e.r, e.alloc)])
|
|
2878
|
+
);
|
|
2879
|
+
const notes2 = results.map((r) => cut.get(r) ?? r);
|
|
2880
|
+
const bytesUsed = [...cut.values()].reduce((sum, r) => sum + r.bytesReturned, 0);
|
|
2881
|
+
return { notes: notes2, budgetBytes: budget, bytesUsed };
|
|
2882
|
+
}
|
|
2883
|
+
function toItemError(path2, err) {
|
|
2884
|
+
const { message, code } = err;
|
|
2885
|
+
if (message.startsWith("{")) return { path: path2, ...JSON.parse(message) };
|
|
2886
|
+
if (message.startsWith("Path outside vault")) return { path: path2, error: "outside_vault" };
|
|
2887
|
+
if (code === "ENOENT") return { path: path2, error: "not_found" };
|
|
2888
|
+
return { path: path2, error: "read_failed", message };
|
|
2889
|
+
}
|
|
2890
|
+
function allocate(entries, budget) {
|
|
2891
|
+
let pending = entries.filter((e) => e.size > 0);
|
|
2892
|
+
let remaining = budget;
|
|
2893
|
+
for (const e of entries) e.alloc = e.size;
|
|
2894
|
+
if (entries.reduce((sum, e) => sum + e.size, 0) <= budget) return;
|
|
2895
|
+
while (pending.length > 0) {
|
|
2896
|
+
const share = Math.floor(remaining / pending.length);
|
|
2897
|
+
const fits = pending.filter((e) => e.size <= share);
|
|
2898
|
+
if (fits.length === 0) {
|
|
2899
|
+
const extra = remaining - share * pending.length;
|
|
2900
|
+
for (const [k, e] of pending.entries()) e.alloc = share + (k < extra ? 1 : 0);
|
|
2901
|
+
return;
|
|
2902
|
+
}
|
|
2903
|
+
for (const e of fits) remaining -= e.size;
|
|
2904
|
+
pending = pending.filter((e) => e.size > share);
|
|
2905
|
+
}
|
|
2906
|
+
}
|
|
2907
|
+
function truncateItem(r, maxBytes) {
|
|
2908
|
+
if (r.bytesReturned <= maxBytes) return r;
|
|
2909
|
+
const buf = Buffer.from(r.content, "utf8");
|
|
2910
|
+
let end = maxBytes;
|
|
2911
|
+
while (end > 0 && (buf.readUInt8(end) & 192) === 128) end--;
|
|
2912
|
+
const content = buf.toString("utf8", 0, end);
|
|
2913
|
+
return { ...r, content, bytesReturned: end, truncated: true };
|
|
2914
|
+
}
|
|
2915
|
+
|
|
2697
2916
|
// src/tools/rename_heading.ts
|
|
2698
2917
|
import { readFile as readFile11 } from "fs/promises";
|
|
2699
2918
|
import { join as join5 } from "path";
|
|
2700
|
-
import { z as
|
|
2701
|
-
var RenameHeadingInput =
|
|
2702
|
-
path:
|
|
2703
|
-
oldHeading:
|
|
2919
|
+
import { z as z20 } from "zod";
|
|
2920
|
+
var RenameHeadingInput = z20.object({
|
|
2921
|
+
path: z20.string().describe("Vault-relative path of the note containing the heading."),
|
|
2922
|
+
oldHeading: z20.string().min(1).describe(
|
|
2704
2923
|
"Current heading text, without # markers. Matched case-insensitively; if the note has duplicate headings the first one wins, mirroring Obsidian link resolution."
|
|
2705
2924
|
),
|
|
2706
|
-
newHeading:
|
|
2925
|
+
newHeading: z20.string().min(1).refine((s) => !/[\n[\]|#]/.test(s), {
|
|
2707
2926
|
message: 'newHeading must not contain newlines, "[", "]", "|", or "#" \u2014 those break [[note#heading]] link syntax.'
|
|
2708
2927
|
}).describe("New heading text, without # markers."),
|
|
2709
|
-
prevHash:
|
|
2928
|
+
prevHash: z20.string().optional().describe(
|
|
2710
2929
|
"Optional compare-and-swap guard: the contentHash from a prior read of this note. Fails with hash_conflict if the note changed since."
|
|
2711
2930
|
)
|
|
2712
2931
|
});
|
|
@@ -2829,17 +3048,17 @@ async function renameHeading(ctx2, rawInput) {
|
|
|
2829
3048
|
|
|
2830
3049
|
// src/tools/replace_in_note.ts
|
|
2831
3050
|
import { readFile as readFile12 } from "fs/promises";
|
|
2832
|
-
import { z as
|
|
2833
|
-
var ReplaceInNoteInput =
|
|
2834
|
-
path:
|
|
2835
|
-
find:
|
|
2836
|
-
replace:
|
|
2837
|
-
regex:
|
|
2838
|
-
caseSensitive:
|
|
2839
|
-
wholeWord:
|
|
2840
|
-
limit:
|
|
2841
|
-
dryRun:
|
|
2842
|
-
prevHash:
|
|
3051
|
+
import { z as z21 } from "zod";
|
|
3052
|
+
var ReplaceInNoteInput = z21.object({
|
|
3053
|
+
path: z21.string().describe("Vault-relative path to the note."),
|
|
3054
|
+
find: z21.string().min(1).max(1e3).describe("Text or pattern to find (max 1000 chars; bounds regex-mode pattern size)."),
|
|
3055
|
+
replace: z21.string().describe("Replacement text. Supports $1, $2, \u2026 backreferences in regex mode."),
|
|
3056
|
+
regex: z21.boolean().default(false).describe("Treat find as a regular expression."),
|
|
3057
|
+
caseSensitive: z21.boolean().default(false).describe("Case-sensitive matching."),
|
|
3058
|
+
wholeWord: z21.boolean().default(false).describe("Match whole words only (\\b word-boundary)."),
|
|
3059
|
+
limit: z21.number().int().min(1).optional().describe("Maximum number of replacements. Omit to replace all."),
|
|
3060
|
+
dryRun: z21.boolean().default(false).describe("If true, report matches without writing anything."),
|
|
3061
|
+
prevHash: z21.string().optional().describe(
|
|
2843
3062
|
"Optional compare-and-swap guard: the contentHash from a prior read. Fails with hash_conflict if the note changed since."
|
|
2844
3063
|
)
|
|
2845
3064
|
});
|
|
@@ -2909,6 +3128,7 @@ async function replaceInNote(ctx2, input) {
|
|
|
2909
3128
|
if (cached) {
|
|
2910
3129
|
cached.body = newBody;
|
|
2911
3130
|
cached.raw = newContent;
|
|
3131
|
+
cached.tasks = extractTasks(newContent);
|
|
2912
3132
|
cached.sizeBytes = Buffer.byteLength(newContent, "utf8");
|
|
2913
3133
|
}
|
|
2914
3134
|
return {
|
|
@@ -2921,15 +3141,56 @@ async function replaceInNote(ctx2, input) {
|
|
|
2921
3141
|
};
|
|
2922
3142
|
}
|
|
2923
3143
|
|
|
3144
|
+
// src/tools/resolve_note.ts
|
|
3145
|
+
import { z as z22 } from "zod";
|
|
3146
|
+
var ResolveNoteInput = z22.object({
|
|
3147
|
+
reference: z22.string().min(1).describe(
|
|
3148
|
+
'Reference to resolve: a vault-relative path ("Projects/Core.md"), a path without extension, a bare note name ("Core"), or a frontmatter alias.'
|
|
3149
|
+
)
|
|
3150
|
+
});
|
|
3151
|
+
var MAX_SUGGESTIONS2 = 3;
|
|
3152
|
+
function suggestReferences(ctx2, reference) {
|
|
3153
|
+
const wanted = reference.toLowerCase();
|
|
3154
|
+
const counts = /* @__PURE__ */ new Map();
|
|
3155
|
+
for (const [path2, note] of ctx2.notes) {
|
|
3156
|
+
const base = path2.replace(/\.md$/i, "").split("/").pop();
|
|
3157
|
+
const names = base ? [base, ...frontmatterAliases(note.fm)] : frontmatterAliases(note.fm);
|
|
3158
|
+
for (const name of new Set(names)) {
|
|
3159
|
+
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
3160
|
+
}
|
|
3161
|
+
}
|
|
3162
|
+
const budget = Math.max(2, Math.ceil(wanted.length / 3));
|
|
3163
|
+
const scored = [];
|
|
3164
|
+
for (const [name, count] of counts) {
|
|
3165
|
+
const dist = editDistance(wanted, name.toLowerCase());
|
|
3166
|
+
if (dist <= budget) scored.push({ name, dist, count });
|
|
3167
|
+
}
|
|
3168
|
+
scored.sort((a, b) => a.dist - b.dist || b.count - a.count || a.name.localeCompare(b.name));
|
|
3169
|
+
return scored.slice(0, MAX_SUGGESTIONS2).map((s) => s.name);
|
|
3170
|
+
}
|
|
3171
|
+
function resolveNote(ctx2, input) {
|
|
3172
|
+
const matches2 = resolveCandidates(input.reference, ctx2.notes);
|
|
3173
|
+
const result = {
|
|
3174
|
+
reference: input.reference,
|
|
3175
|
+
matches: matches2,
|
|
3176
|
+
ambiguous: matches2.length > 1
|
|
3177
|
+
};
|
|
3178
|
+
if (matches2.length === 0) {
|
|
3179
|
+
const didYouMean = suggestReferences(ctx2, input.reference);
|
|
3180
|
+
if (didYouMean.length > 0) result.didYouMean = didYouMean;
|
|
3181
|
+
}
|
|
3182
|
+
return result;
|
|
3183
|
+
}
|
|
3184
|
+
|
|
2924
3185
|
// src/tools/undo_write.ts
|
|
2925
3186
|
import { mkdir as mkdir6, readFile as readFile13, rm as rm3 } from "fs/promises";
|
|
2926
3187
|
import { dirname as dirname4 } from "path";
|
|
2927
|
-
import { z as
|
|
2928
|
-
var UndoWriteInput =
|
|
2929
|
-
seq:
|
|
3188
|
+
import { z as z23 } from "zod";
|
|
3189
|
+
var UndoWriteInput = z23.object({
|
|
3190
|
+
seq: z23.number().int().min(1).optional().describe(
|
|
2930
3191
|
"Journal entry to revert (from list_writes). Defaults to the most recent undoable write."
|
|
2931
3192
|
),
|
|
2932
|
-
force:
|
|
3193
|
+
force: z23.boolean().default(false).describe(
|
|
2933
3194
|
"Restore even if a file changed after the journaled write. The clobbered state is journaled first, so nothing is lost. Defaults to false \u2014 conflicts are refused."
|
|
2934
3195
|
)
|
|
2935
3196
|
});
|
|
@@ -3038,6 +3299,104 @@ async function postBytes(journal, hash) {
|
|
|
3038
3299
|
}
|
|
3039
3300
|
}
|
|
3040
3301
|
|
|
3302
|
+
// src/tools/update_task.ts
|
|
3303
|
+
import { readFile as readFile14 } from "fs/promises";
|
|
3304
|
+
import { z as z24 } from "zod";
|
|
3305
|
+
var UpdateTaskInput = z24.object({
|
|
3306
|
+
path: z24.string().describe("Vault-relative path to the note."),
|
|
3307
|
+
line: z24.number().int().min(1).describe("1-based line number of the task, as returned by list_tasks."),
|
|
3308
|
+
expectText: z24.string().optional().describe(
|
|
3309
|
+
"Optional guard: the task text list_tasks returned. Fails with task_text_mismatch if the line\u2019s text changed since."
|
|
3310
|
+
),
|
|
3311
|
+
state: z24.string().length(1).refine((s) => !/[\]\r\n]/.test(s), {
|
|
3312
|
+
message: 'state must be a single character other than "]" or a line break'
|
|
3313
|
+
}).describe('New marker character: "x" (done), " " (open), or a custom marker like "/".'),
|
|
3314
|
+
prevHash: z24.string().optional().describe(
|
|
3315
|
+
"Optional compare-and-swap guard: the contentHash from a prior read. Fails with hash_conflict if the note changed since."
|
|
3316
|
+
)
|
|
3317
|
+
});
|
|
3318
|
+
function lineStartOffset(raw, line) {
|
|
3319
|
+
let offset = 0;
|
|
3320
|
+
for (let n = 1; n < line; n++) {
|
|
3321
|
+
offset = raw.indexOf("\n", offset) + 1;
|
|
3322
|
+
}
|
|
3323
|
+
return offset;
|
|
3324
|
+
}
|
|
3325
|
+
async function updateTask(ctx2, input) {
|
|
3326
|
+
const absPath = resolveVaultPath(ctx2.vaultRoot, input.path);
|
|
3327
|
+
assertWritable(ctx2.policy, input.path);
|
|
3328
|
+
const original = await readFile14(absPath, "utf8");
|
|
3329
|
+
if (input.prevHash !== void 0) assertHashMatch(original, input.prevHash, input.path);
|
|
3330
|
+
const lineCount = original.split("\n").length;
|
|
3331
|
+
if (input.line > lineCount) {
|
|
3332
|
+
throw new Error(
|
|
3333
|
+
JSON.stringify({
|
|
3334
|
+
error: "line_out_of_range",
|
|
3335
|
+
path: input.path,
|
|
3336
|
+
line: input.line,
|
|
3337
|
+
lineCount,
|
|
3338
|
+
hint: "The note has fewer lines than the requested task line. Re-run list_tasks."
|
|
3339
|
+
})
|
|
3340
|
+
);
|
|
3341
|
+
}
|
|
3342
|
+
const task = extractTasks(original).find((t) => t.line === input.line);
|
|
3343
|
+
if (!task) {
|
|
3344
|
+
throw new Error(
|
|
3345
|
+
JSON.stringify({
|
|
3346
|
+
error: "not_a_task",
|
|
3347
|
+
path: input.path,
|
|
3348
|
+
line: input.line,
|
|
3349
|
+
hint: "Line is not a Markdown checkbox (or is inside frontmatter or a code fence). Re-run list_tasks."
|
|
3350
|
+
})
|
|
3351
|
+
);
|
|
3352
|
+
}
|
|
3353
|
+
if (input.expectText !== void 0 && task.text !== input.expectText) {
|
|
3354
|
+
throw new Error(
|
|
3355
|
+
JSON.stringify({
|
|
3356
|
+
error: "task_text_mismatch",
|
|
3357
|
+
path: input.path,
|
|
3358
|
+
line: input.line,
|
|
3359
|
+
expected: input.expectText,
|
|
3360
|
+
actual: task.text,
|
|
3361
|
+
hint: "The task text changed since it was listed. Re-run list_tasks and retry."
|
|
3362
|
+
})
|
|
3363
|
+
);
|
|
3364
|
+
}
|
|
3365
|
+
if (task.state === input.state) {
|
|
3366
|
+
return {
|
|
3367
|
+
path: input.path,
|
|
3368
|
+
line: input.line,
|
|
3369
|
+
oldState: task.state,
|
|
3370
|
+
newState: task.state,
|
|
3371
|
+
contentHash: contentHash(original)
|
|
3372
|
+
};
|
|
3373
|
+
}
|
|
3374
|
+
const markerOffset = lineStartOffset(original, input.line) + task.col;
|
|
3375
|
+
const newContent = `${original.slice(0, markerOffset)}${input.state}${original.slice(markerOffset + 1)}`;
|
|
3376
|
+
const fm = parseFrontmatter(original);
|
|
3377
|
+
const originalFmRegion = original.slice(0, fm.bodyStart);
|
|
3378
|
+
await journalWrite(ctx2, "update_task", input.path, original, newContent);
|
|
3379
|
+
await atomicWrite(absPath, newContent);
|
|
3380
|
+
const written = await readFile14(absPath, "utf8");
|
|
3381
|
+
if (written.slice(0, originalFmRegion.length) !== originalFmRegion) {
|
|
3382
|
+
throw new Error("Write-safety violation: frontmatter region changed unexpectedly");
|
|
3383
|
+
}
|
|
3384
|
+
const cached = ctx2.notes.get(input.path);
|
|
3385
|
+
if (cached) {
|
|
3386
|
+
cached.body = newContent.slice(fm.bodyStart);
|
|
3387
|
+
cached.raw = newContent;
|
|
3388
|
+
cached.tasks = extractTasks(newContent);
|
|
3389
|
+
cached.sizeBytes = Buffer.byteLength(newContent, "utf8");
|
|
3390
|
+
}
|
|
3391
|
+
return {
|
|
3392
|
+
path: input.path,
|
|
3393
|
+
line: input.line,
|
|
3394
|
+
oldState: task.state,
|
|
3395
|
+
newState: input.state,
|
|
3396
|
+
contentHash: contentHash(newContent)
|
|
3397
|
+
};
|
|
3398
|
+
}
|
|
3399
|
+
|
|
3041
3400
|
// src/dispatch.ts
|
|
3042
3401
|
var WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
3043
3402
|
"create_note",
|
|
@@ -3049,6 +3408,7 @@ var WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
|
3049
3408
|
"patch_note",
|
|
3050
3409
|
"replace_in_note",
|
|
3051
3410
|
"append_periodic_note",
|
|
3411
|
+
"update_task",
|
|
3052
3412
|
"undo_write"
|
|
3053
3413
|
]);
|
|
3054
3414
|
var META_KEYS = [
|
|
@@ -3072,6 +3432,8 @@ var META_KEYS = [
|
|
|
3072
3432
|
"period",
|
|
3073
3433
|
"date",
|
|
3074
3434
|
"permanent",
|
|
3435
|
+
"line",
|
|
3436
|
+
"state",
|
|
3075
3437
|
"prevHash",
|
|
3076
3438
|
"budgetBytes",
|
|
3077
3439
|
"seq",
|
|
@@ -3086,6 +3448,7 @@ function safeMeta(args) {
|
|
|
3086
3448
|
}
|
|
3087
3449
|
if (typeof a.query === "string") out.queryLen = a.query.length;
|
|
3088
3450
|
if (Array.isArray(a.where)) out.whereCount = a.where.length;
|
|
3451
|
+
if (Array.isArray(a.items)) out.itemsCount = a.items.length;
|
|
3089
3452
|
return out;
|
|
3090
3453
|
}
|
|
3091
3454
|
function resultBytes(r) {
|
|
@@ -3235,6 +3598,11 @@ async function run(ctx2, name, args) {
|
|
|
3235
3598
|
const result = await readNote(ctx2, input);
|
|
3236
3599
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
3237
3600
|
}
|
|
3601
|
+
case "read_notes": {
|
|
3602
|
+
const input = ReadNotesInput.parse(args);
|
|
3603
|
+
const result = await readNotes(ctx2, input);
|
|
3604
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
3605
|
+
}
|
|
3238
3606
|
case "list_notes": {
|
|
3239
3607
|
const input = ListNotesInput.parse(args);
|
|
3240
3608
|
const entries = listNotes(ctx2, input);
|
|
@@ -3245,6 +3613,11 @@ async function run(ctx2, name, args) {
|
|
|
3245
3613
|
const result = listTags(ctx2, input);
|
|
3246
3614
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
3247
3615
|
}
|
|
3616
|
+
case "list_tasks": {
|
|
3617
|
+
const input = ListTasksInput.parse(args);
|
|
3618
|
+
const result = listTasks(ctx2, input);
|
|
3619
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
3620
|
+
}
|
|
3248
3621
|
case "create_note": {
|
|
3249
3622
|
const input = CreateNoteInput.parse(args);
|
|
3250
3623
|
const result = await createNote(ctx2, input);
|
|
@@ -3374,6 +3747,11 @@ async function run(ctx2, name, args) {
|
|
|
3374
3747
|
const result = getLinks(ctx2, input);
|
|
3375
3748
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
3376
3749
|
}
|
|
3750
|
+
case "resolve_note": {
|
|
3751
|
+
const input = ResolveNoteInput.parse(args);
|
|
3752
|
+
const result = resolveNote(ctx2, input);
|
|
3753
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
3754
|
+
}
|
|
3377
3755
|
case "replace_in_note": {
|
|
3378
3756
|
const input = ReplaceInNoteInput.parse(args);
|
|
3379
3757
|
const result = await replaceInNote(ctx2, input);
|
|
@@ -3415,6 +3793,20 @@ async function run(ctx2, name, args) {
|
|
|
3415
3793
|
}
|
|
3416
3794
|
};
|
|
3417
3795
|
}
|
|
3796
|
+
case "update_task": {
|
|
3797
|
+
const input = UpdateTaskInput.parse(args);
|
|
3798
|
+
const result = await updateTask(ctx2, input);
|
|
3799
|
+
return {
|
|
3800
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
3801
|
+
audit: {
|
|
3802
|
+
path: result.path,
|
|
3803
|
+
line: result.line,
|
|
3804
|
+
oldState: result.oldState,
|
|
3805
|
+
newState: result.newState,
|
|
3806
|
+
contentHash: result.contentHash
|
|
3807
|
+
}
|
|
3808
|
+
};
|
|
3809
|
+
}
|
|
3418
3810
|
case "list_writes": {
|
|
3419
3811
|
const input = ListWritesInput.parse(args);
|
|
3420
3812
|
const result = listWrites(ctx2, input);
|
|
@@ -3438,7 +3830,7 @@ async function run(ctx2, name, args) {
|
|
|
3438
3830
|
}
|
|
3439
3831
|
|
|
3440
3832
|
// src/index/build.ts
|
|
3441
|
-
import { readFile as
|
|
3833
|
+
import { readFile as readFile15 } from "fs/promises";
|
|
3442
3834
|
import { basename as basename3, extname as extname4 } from "path";
|
|
3443
3835
|
|
|
3444
3836
|
// ../core/src/walk.ts
|
|
@@ -3521,7 +3913,7 @@ async function buildIndex(vaultRoot2) {
|
|
|
3521
3913
|
await Promise.all(
|
|
3522
3914
|
noteEntries.map(async (entry) => {
|
|
3523
3915
|
try {
|
|
3524
|
-
const raw = await
|
|
3916
|
+
const raw = await readFile15(entry.absPath, "utf8");
|
|
3525
3917
|
const fm = parseFrontmatter(raw);
|
|
3526
3918
|
const inlineTags = extractInlineTags(fm.body);
|
|
3527
3919
|
const fmTags = frontmatterTags(fm.data);
|
|
@@ -3535,6 +3927,7 @@ async function buildIndex(vaultRoot2) {
|
|
|
3535
3927
|
fmKeys: fm.keys.join(" "),
|
|
3536
3928
|
fm: fm.data,
|
|
3537
3929
|
raw,
|
|
3930
|
+
tasks: extractTasks(raw),
|
|
3538
3931
|
sizeBytes: entry.sizeBytes,
|
|
3539
3932
|
mtimeMs: entry.mtimeMs
|
|
3540
3933
|
};
|
|
@@ -3575,7 +3968,7 @@ function buildBacklinks(notes2) {
|
|
|
3575
3968
|
|
|
3576
3969
|
// src/init.ts
|
|
3577
3970
|
import { execFileSync } from "child_process";
|
|
3578
|
-
import { access as access4, copyFile, mkdir as mkdir7, readdir as readdir2, readFile as
|
|
3971
|
+
import { access as access4, copyFile, mkdir as mkdir7, readdir as readdir2, readFile as readFile16, stat as stat3, writeFile as writeFile2 } from "fs/promises";
|
|
3579
3972
|
import path, { dirname as dirname5, join as join7 } from "path";
|
|
3580
3973
|
function parseInitArgs(argv) {
|
|
3581
3974
|
const opts = { write: false, client: "desktop" };
|
|
@@ -3719,7 +4112,7 @@ async function runInit(opts, deps) {
|
|
|
3719
4112
|
]
|
|
3720
4113
|
};
|
|
3721
4114
|
}
|
|
3722
|
-
const rf = deps.readFile ??
|
|
4115
|
+
const rf = deps.readFile ?? readFile16;
|
|
3723
4116
|
let vaultPath = opts.vault ?? deps.env.SEEKSTONE_VAULT;
|
|
3724
4117
|
if (!vaultPath) {
|
|
3725
4118
|
const registryPath = obsidianRegistryPath(deps.platform, {
|
|
@@ -3923,7 +4316,7 @@ function installProcessGuards(log2) {
|
|
|
3923
4316
|
init_model_manifest();
|
|
3924
4317
|
import { createHash as createHash2 } from "crypto";
|
|
3925
4318
|
import { existsSync } from "fs";
|
|
3926
|
-
import { mkdir as mkdir8, readdir as readdir3, readFile as
|
|
4319
|
+
import { mkdir as mkdir8, readdir as readdir3, readFile as readFile17, rename as rename5, writeFile as writeFile3 } from "fs/promises";
|
|
3927
4320
|
import { join as join8 } from "path";
|
|
3928
4321
|
async function materializeBundledModel(cfg, bundledDir, deps = {}) {
|
|
3929
4322
|
if (!bundledDir) return;
|
|
@@ -3933,7 +4326,7 @@ async function materializeBundledModel(cfg, bundledDir, deps = {}) {
|
|
|
3933
4326
|
}
|
|
3934
4327
|
let bundledId;
|
|
3935
4328
|
try {
|
|
3936
|
-
const raw = await
|
|
4329
|
+
const raw = await readFile17(join8(bundledDir, "manifest.json"), "utf8");
|
|
3937
4330
|
bundledId = String(JSON.parse(raw).id);
|
|
3938
4331
|
} catch (err) {
|
|
3939
4332
|
throw new Error(
|
|
@@ -3953,7 +4346,7 @@ async function materializeBundledModel(cfg, bundledDir, deps = {}) {
|
|
|
3953
4346
|
let written = 0;
|
|
3954
4347
|
for (const file of manifest.files) {
|
|
3955
4348
|
const dest = join8(cfg.modelDir, file.name);
|
|
3956
|
-
if (existsSync(dest) && sha256(await
|
|
4349
|
+
if (existsSync(dest) && sha256(await readFile17(dest)) === file.sha256) continue;
|
|
3957
4350
|
const buf = await concatParts(bundledDir, file.name);
|
|
3958
4351
|
const got = sha256(buf);
|
|
3959
4352
|
if (got !== file.sha256) {
|
|
@@ -3988,7 +4381,7 @@ async function concatParts(bundledDir, name) {
|
|
|
3988
4381
|
);
|
|
3989
4382
|
}
|
|
3990
4383
|
const buffers = [];
|
|
3991
|
-
for (const part of parts) buffers.push(await
|
|
4384
|
+
for (const part of parts) buffers.push(await readFile17(join8(bundledDir, part)));
|
|
3992
4385
|
return Buffer.concat(buffers);
|
|
3993
4386
|
}
|
|
3994
4387
|
function sha256(buf) {
|
|
@@ -4033,7 +4426,7 @@ ${window.map((p) => p.text).join("\n\n")}`,
|
|
|
4033
4426
|
}
|
|
4034
4427
|
function splitParagraphs(normalized) {
|
|
4035
4428
|
const out = [];
|
|
4036
|
-
const
|
|
4429
|
+
const push2 = (rawStart, raw) => {
|
|
4037
4430
|
const text = raw.trim();
|
|
4038
4431
|
if (text.length === 0) return;
|
|
4039
4432
|
const start = rawStart + (raw.length - raw.trimStart().length);
|
|
@@ -4042,10 +4435,10 @@ function splitParagraphs(normalized) {
|
|
|
4042
4435
|
const sep4 = /\n[ \t]*\n+/g;
|
|
4043
4436
|
let prev = 0;
|
|
4044
4437
|
for (const m of normalized.matchAll(sep4)) {
|
|
4045
|
-
|
|
4438
|
+
push2(prev, normalized.slice(prev, m.index));
|
|
4046
4439
|
prev = m.index + m[0].length;
|
|
4047
4440
|
}
|
|
4048
|
-
|
|
4441
|
+
push2(prev, normalized.slice(prev));
|
|
4049
4442
|
return out;
|
|
4050
4443
|
}
|
|
4051
4444
|
function splitOversized(para) {
|
|
@@ -4146,7 +4539,7 @@ function candidateSetIdf(queryIds, docs) {
|
|
|
4146
4539
|
}
|
|
4147
4540
|
|
|
4148
4541
|
// ../core/src/embed/model2vec.ts
|
|
4149
|
-
import { readFile as
|
|
4542
|
+
import { readFile as readFile18 } from "fs/promises";
|
|
4150
4543
|
import { basename as basename4, join as join10 } from "path";
|
|
4151
4544
|
|
|
4152
4545
|
// ../core/src/embed/safetensors.ts
|
|
@@ -4328,9 +4721,9 @@ function isCjk(cp) {
|
|
|
4328
4721
|
var TENSOR_NAME = "embeddings";
|
|
4329
4722
|
async function loadModel2Vec(modelDir) {
|
|
4330
4723
|
const [configRaw, tokenizerRaw, weights] = await Promise.all([
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4724
|
+
readFile18(join10(modelDir, "config.json"), "utf8"),
|
|
4725
|
+
readFile18(join10(modelDir, "tokenizer.json"), "utf8"),
|
|
4726
|
+
readFile18(join10(modelDir, "model.safetensors"))
|
|
4334
4727
|
]);
|
|
4335
4728
|
const config = JSON.parse(configRaw);
|
|
4336
4729
|
const tokenizer = loadWordPieceTokenizer(JSON.parse(tokenizerRaw));
|
|
@@ -4489,7 +4882,7 @@ function isTokenEmbedder(e) {
|
|
|
4489
4882
|
|
|
4490
4883
|
// src/semantic/cache.ts
|
|
4491
4884
|
import { createHash as createHash3 } from "crypto";
|
|
4492
|
-
import { mkdir as mkdir9, readFile as
|
|
4885
|
+
import { mkdir as mkdir9, readFile as readFile19, rename as rename6, writeFile as writeFile4 } from "fs/promises";
|
|
4493
4886
|
import { join as join11 } from "path";
|
|
4494
4887
|
var CACHE_VERSION = 3;
|
|
4495
4888
|
function cachePathsFor(cacheDir, vaultRoot2, modelId) {
|
|
@@ -4499,11 +4892,11 @@ function cachePathsFor(cacheDir, vaultRoot2, modelId) {
|
|
|
4499
4892
|
}
|
|
4500
4893
|
async function loadCache(paths, modelId, dim) {
|
|
4501
4894
|
try {
|
|
4502
|
-
const manifest = JSON.parse(await
|
|
4895
|
+
const manifest = JSON.parse(await readFile19(paths.manifest, "utf8"));
|
|
4503
4896
|
if (manifest.version !== CACHE_VERSION || manifest.modelId !== modelId || manifest.dim !== dim) {
|
|
4504
4897
|
return void 0;
|
|
4505
4898
|
}
|
|
4506
|
-
const buf = await
|
|
4899
|
+
const buf = await readFile19(paths.bin);
|
|
4507
4900
|
const totalChunks = manifest.entries.reduce((a, e) => a + e.chunks, 0);
|
|
4508
4901
|
const vectorBytes = totalChunks * dim * 4;
|
|
4509
4902
|
const spanBytes = totalChunks * 2 * 4;
|
|
@@ -5121,6 +5514,59 @@ var ALL_TOOLS = [
|
|
|
5121
5514
|
required: ["path"]
|
|
5122
5515
|
}
|
|
5123
5516
|
},
|
|
5517
|
+
{
|
|
5518
|
+
name: "read_notes",
|
|
5519
|
+
annotations: READ_ONLY_ANNOTATIONS,
|
|
5520
|
+
description: "Read up to 20 notes (or spans of them) in one call instead of repeated read_note calls, e.g. the top hits from search, query_notes, list_notes, or get_backlinks. Each item takes the same options as read_note and returns the same fields, in request order. budgetBytes (default 16384) caps the total note text: when the notes exceed it, each is cut to a fair share, marked truncated: true, and keeps noteBytes, so read the rest with read_note. A failing item (missing path, unknown section) returns its own error without failing the batch. contentHash is always the whole file's hash, usable as prevHash.",
|
|
5521
|
+
inputSchema: {
|
|
5522
|
+
type: "object",
|
|
5523
|
+
properties: {
|
|
5524
|
+
items: {
|
|
5525
|
+
type: "array",
|
|
5526
|
+
description: "Notes to read, 1\u201320. Same options as read_note.",
|
|
5527
|
+
items: {
|
|
5528
|
+
type: "object",
|
|
5529
|
+
properties: {
|
|
5530
|
+
path: {
|
|
5531
|
+
type: "string",
|
|
5532
|
+
description: 'Vault-relative path, e.g. "Daily Notes/2026-05-29.md".'
|
|
5533
|
+
},
|
|
5534
|
+
section: {
|
|
5535
|
+
type: "string",
|
|
5536
|
+
description: "Return only this heading section (heading text, # prefix optional)."
|
|
5537
|
+
},
|
|
5538
|
+
block: {
|
|
5539
|
+
type: "string",
|
|
5540
|
+
description: "Return only the line anchored by this block id (^ prefix optional)."
|
|
5541
|
+
},
|
|
5542
|
+
lines: {
|
|
5543
|
+
type: "object",
|
|
5544
|
+
description: "Return only this line range (1-indexed, inclusive).",
|
|
5545
|
+
properties: {
|
|
5546
|
+
from: { type: "number", description: "First line (1-indexed)." },
|
|
5547
|
+
to: {
|
|
5548
|
+
type: "number",
|
|
5549
|
+
description: "Last line (1-indexed, inclusive). Defaults to EOF."
|
|
5550
|
+
}
|
|
5551
|
+
},
|
|
5552
|
+
required: ["from"]
|
|
5553
|
+
},
|
|
5554
|
+
includeFrontmatter: {
|
|
5555
|
+
type: "boolean",
|
|
5556
|
+
description: "Prepend frontmatter to section/block span results. Default false for spans."
|
|
5557
|
+
}
|
|
5558
|
+
},
|
|
5559
|
+
required: ["path"]
|
|
5560
|
+
}
|
|
5561
|
+
},
|
|
5562
|
+
budgetBytes: {
|
|
5563
|
+
type: "number",
|
|
5564
|
+
description: "Cap on total note text returned across all items, in UTF-8 bytes (256\u201365536, default 16384)."
|
|
5565
|
+
}
|
|
5566
|
+
},
|
|
5567
|
+
required: ["items"]
|
|
5568
|
+
}
|
|
5569
|
+
},
|
|
5124
5570
|
{
|
|
5125
5571
|
name: "list_notes",
|
|
5126
5572
|
annotations: READ_ONLY_ANNOTATIONS,
|
|
@@ -5162,6 +5608,45 @@ var ALL_TOOLS = [
|
|
|
5162
5608
|
required: []
|
|
5163
5609
|
}
|
|
5164
5610
|
},
|
|
5611
|
+
{
|
|
5612
|
+
name: "list_tasks",
|
|
5613
|
+
annotations: READ_ONLY_ANNOTATIONS,
|
|
5614
|
+
description: 'List Markdown checkbox tasks as compact structured rows (path, line, state, text, nearest heading) \u2014 vault-wide or scoped by note, folder, tag, or modified time. Defaults to open tasks. Custom markers like "/" are single-char states. Rows feed straight into update_task; use this instead of searching for "- [ ]" or reading whole notes.',
|
|
5615
|
+
inputSchema: {
|
|
5616
|
+
type: "object",
|
|
5617
|
+
properties: {
|
|
5618
|
+
path: {
|
|
5619
|
+
type: "string",
|
|
5620
|
+
description: "Vault-relative path to a single note. Omit to search the whole vault."
|
|
5621
|
+
},
|
|
5622
|
+
folder: { type: "string", description: "Restrict to a vault-relative folder prefix." },
|
|
5623
|
+
tag: {
|
|
5624
|
+
type: "string",
|
|
5625
|
+
description: 'Restrict to notes with this tag. # optional, case-insensitive; nested child tags match ("project" matches project/alpha).'
|
|
5626
|
+
},
|
|
5627
|
+
state: {
|
|
5628
|
+
anyOf: [
|
|
5629
|
+
{
|
|
5630
|
+
type: "string",
|
|
5631
|
+
enum: ["open", "done", "any"],
|
|
5632
|
+
description: 'open = "- [ ]" (default); done = "- [x]" or "- [X]"; any = every task.'
|
|
5633
|
+
},
|
|
5634
|
+
{
|
|
5635
|
+
type: "string",
|
|
5636
|
+
description: 'A single custom marker character, e.g. "/" or "-".'
|
|
5637
|
+
}
|
|
5638
|
+
],
|
|
5639
|
+
description: "Which tasks to return. Default: open."
|
|
5640
|
+
},
|
|
5641
|
+
modifiedAfter: {
|
|
5642
|
+
type: "string",
|
|
5643
|
+
description: "Only tasks in notes modified at or after this ISO 8601 date/time."
|
|
5644
|
+
},
|
|
5645
|
+
limit: { type: "number", description: "Max tasks (1\u2013500, default 100)." }
|
|
5646
|
+
},
|
|
5647
|
+
required: []
|
|
5648
|
+
}
|
|
5649
|
+
},
|
|
5165
5650
|
{
|
|
5166
5651
|
name: "create_note",
|
|
5167
5652
|
annotations: DESTRUCTIVE_WRITE_ANNOTATIONS,
|
|
@@ -5401,6 +5886,22 @@ var ALL_TOOLS = [
|
|
|
5401
5886
|
required: ["path"]
|
|
5402
5887
|
}
|
|
5403
5888
|
},
|
|
5889
|
+
{
|
|
5890
|
+
name: "resolve_note",
|
|
5891
|
+
annotations: READ_ONLY_ANNOTATIONS,
|
|
5892
|
+
description: "Resolve a note reference \u2014 a wikilink target, bare note name, path without extension, or frontmatter alias \u2014 to canonical vault-relative path(s). Each match carries provenance (path | basename | relpath | alias); matches[0] is exactly what a wikilink with that target resolves to, and ambiguous: true means more than one note matched. Empty matches means the reference resolves to nothing (didYouMean then lists close names). Returns paths only \u2014 resolve first, then read_notes. Pure index lookup.",
|
|
5893
|
+
inputSchema: {
|
|
5894
|
+
type: "object",
|
|
5895
|
+
properties: {
|
|
5896
|
+
reference: {
|
|
5897
|
+
type: "string",
|
|
5898
|
+
minLength: 1,
|
|
5899
|
+
description: 'Reference to resolve: a vault-relative path ("Projects/Core.md"), a path without extension, a bare note name ("Core"), or a frontmatter alias.'
|
|
5900
|
+
}
|
|
5901
|
+
},
|
|
5902
|
+
required: ["reference"]
|
|
5903
|
+
}
|
|
5904
|
+
},
|
|
5404
5905
|
{
|
|
5405
5906
|
name: "replace_in_note",
|
|
5406
5907
|
annotations: DESTRUCTIVE_WRITE_ANNOTATIONS,
|
|
@@ -5498,6 +5999,34 @@ var ALL_TOOLS = [
|
|
|
5498
5999
|
required: ["content"]
|
|
5499
6000
|
}
|
|
5500
6001
|
},
|
|
6002
|
+
{
|
|
6003
|
+
name: "update_task",
|
|
6004
|
+
annotations: IDEMPOTENT_DESTRUCTIVE_WRITE_ANNOTATIONS,
|
|
6005
|
+
description: "Set the state of one Markdown checkbox identified by path + line (from list_tasks), rewriting exactly one character \u2014 the rest of the file, frontmatter included, stays byte-identical. Pass expectText to guard against the line having changed, and prevHash for full compare-and-swap. Setting the current state again is a success no-op.",
|
|
6006
|
+
inputSchema: {
|
|
6007
|
+
type: "object",
|
|
6008
|
+
properties: {
|
|
6009
|
+
path: { type: "string", description: "Vault-relative path to the note." },
|
|
6010
|
+
line: {
|
|
6011
|
+
type: "number",
|
|
6012
|
+
description: "1-based line number of the task, as returned by list_tasks."
|
|
6013
|
+
},
|
|
6014
|
+
expectText: {
|
|
6015
|
+
type: "string",
|
|
6016
|
+
description: "Optional guard: the task text list_tasks returned. Fails with task_text_mismatch if the line\u2019s text changed since."
|
|
6017
|
+
},
|
|
6018
|
+
state: {
|
|
6019
|
+
type: "string",
|
|
6020
|
+
description: 'New marker character: "x" (done), " " (open), or a custom marker like "/".'
|
|
6021
|
+
},
|
|
6022
|
+
prevHash: {
|
|
6023
|
+
type: "string",
|
|
6024
|
+
description: "Optional compare-and-swap guard: the contentHash from a prior read. Fails with hash_conflict if the note changed since."
|
|
6025
|
+
}
|
|
6026
|
+
},
|
|
6027
|
+
required: ["path", "line", "state"]
|
|
6028
|
+
}
|
|
6029
|
+
},
|
|
5501
6030
|
{
|
|
5502
6031
|
name: "list_writes",
|
|
5503
6032
|
annotations: READ_ONLY_ANNOTATIONS,
|
|
@@ -5542,7 +6071,7 @@ function visibleTools(policy2) {
|
|
|
5542
6071
|
|
|
5543
6072
|
// src/watcher.ts
|
|
5544
6073
|
import { realpathSync } from "fs";
|
|
5545
|
-
import { readFile as
|
|
6074
|
+
import { readFile as readFile20 } from "fs/promises";
|
|
5546
6075
|
import { join as join12, relative as relative2, sep as sep3 } from "path";
|
|
5547
6076
|
import chokidar from "chokidar";
|
|
5548
6077
|
var DEFAULT_POLL_INTERVAL_MS = 1e4;
|
|
@@ -5574,7 +6103,7 @@ function startWatcher(ctx2, log2, opts) {
|
|
|
5574
6103
|
const toRel = (abs) => relative2(vaultRoot2, abs).split(sep3).join("/");
|
|
5575
6104
|
async function upsert(relPath, op) {
|
|
5576
6105
|
try {
|
|
5577
|
-
const raw = await
|
|
6106
|
+
const raw = await readFile20(join12(vaultRoot2, relPath), "utf8");
|
|
5578
6107
|
removeNoteBacklinks(ctx2, relPath);
|
|
5579
6108
|
upsertDoc(ctx2, buildDoc(relPath, raw));
|
|
5580
6109
|
addNoteBacklinks(ctx2, relPath, raw);
|
|
@@ -5628,7 +6157,7 @@ function startWatcher(ctx2, log2, opts) {
|
|
|
5628
6157
|
}
|
|
5629
6158
|
|
|
5630
6159
|
// src/index.ts
|
|
5631
|
-
var VERSION = true ? "0.
|
|
6160
|
+
var VERSION = true ? "0.22.0" : "0.0.0-dev";
|
|
5632
6161
|
var intent = parseCliIntent(process.argv.slice(2));
|
|
5633
6162
|
if (intent === "version") {
|
|
5634
6163
|
process.stdout.write(`${VERSION}
|