seekstone 0.13.0 → 0.14.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 +1 -1
- package/dist/index.js +104 -34
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -135,7 +135,7 @@ Other MCP clients (Windsurf, Cline, …) take the Option 3 JSON block in their o
|
|
|
135
135
|
| `replace_in_note` | Find and replace text in the note body — literal or regex, whole-word, case sensitivity, optional `limit` (replaces **all** occurrences by default), dry-run preview. |
|
|
136
136
|
| `append_periodic_note` | Append to today's periodic note, creating it from a template if it doesn't yet exist. |
|
|
137
137
|
|
|
138
|
-
|
|
138
|
+
Every write tool (`append_note`, `patch_note`, `patch_frontmatter`, `replace_in_note`, `rename_heading`, `move_note`, `delete_note`, `append_periodic_note`, and `create_note` with `overwrite: true`) supports optional **compare-and-swap**: pass the `contentHash` you got from `read_note` as `prevHash` and the call fails cleanly if the note changed underneath you — no silently discarded concurrent edit, no moving or deleting content you haven't seen. Every mutating result returns the new `contentHash`, so chained edits need no re-reads.
|
|
139
139
|
|
|
140
140
|
---
|
|
141
141
|
|
package/dist/index.js
CHANGED
|
@@ -701,7 +701,7 @@ ${stringifyYaml(input.frontmatter, { lineWidth: 0 })}---
|
|
|
701
701
|
}
|
|
702
702
|
|
|
703
703
|
// src/tools/delete_note.ts
|
|
704
|
-
import { access as access2, mkdir as mkdir2, rename as rename2, rm } from "fs/promises";
|
|
704
|
+
import { access as access2, mkdir as mkdir2, readFile as readFile3, rename as rename2, rm } from "fs/promises";
|
|
705
705
|
import { basename as basename2, extname as extname2, join } from "path";
|
|
706
706
|
import { z as z5 } from "zod";
|
|
707
707
|
|
|
@@ -742,6 +742,9 @@ var DeleteNoteInput = z5.object({
|
|
|
742
742
|
path: z5.string().describe("Vault-relative path of the note to delete."),
|
|
743
743
|
permanent: z5.boolean().default(false).describe(
|
|
744
744
|
"Permanently remove the note instead of moving it to the vault .trash/ folder. Defaults to false \u2014 deletes are recoverable."
|
|
745
|
+
),
|
|
746
|
+
prevHash: z5.string().optional().describe(
|
|
747
|
+
"Optional compare-and-swap guard: the contentHash from a prior read. Fails with hash_conflict if the note changed since \u2014 so you never delete content you have not seen."
|
|
745
748
|
)
|
|
746
749
|
});
|
|
747
750
|
async function freeTrashPath(vaultRoot2, noteBase) {
|
|
@@ -761,11 +764,12 @@ async function deleteNote(ctx2, rawInput) {
|
|
|
761
764
|
const input = DeleteNoteInput.parse(rawInput);
|
|
762
765
|
const absPath = resolveVaultPath(ctx2.vaultRoot, input.path);
|
|
763
766
|
assertWritable(ctx2.policy, input.path);
|
|
767
|
+
const raw = await readFile3(absPath, "utf8");
|
|
768
|
+
if (input.prevHash !== void 0) assertHashMatch(raw, input.prevHash, input.path);
|
|
764
769
|
let trashedTo;
|
|
765
770
|
if (input.permanent) {
|
|
766
771
|
await rm(absPath);
|
|
767
772
|
} else {
|
|
768
|
-
await access2(absPath);
|
|
769
773
|
await mkdir2(join(ctx2.vaultRoot, ".trash"), { recursive: true });
|
|
770
774
|
trashedTo = await freeTrashPath(ctx2.vaultRoot, basename2(input.path));
|
|
771
775
|
await rename2(absPath, join(ctx2.vaultRoot, trashedTo));
|
|
@@ -775,7 +779,11 @@ async function deleteNote(ctx2, rawInput) {
|
|
|
775
779
|
ctx2.index.discard(input.path);
|
|
776
780
|
ctx2.notes.delete(input.path);
|
|
777
781
|
}
|
|
778
|
-
const result = {
|
|
782
|
+
const result = {
|
|
783
|
+
path: input.path,
|
|
784
|
+
permanent: input.permanent,
|
|
785
|
+
contentHash: contentHash(raw)
|
|
786
|
+
};
|
|
779
787
|
if (trashedTo !== void 0) result.trashedTo = trashedTo;
|
|
780
788
|
return result;
|
|
781
789
|
}
|
|
@@ -903,7 +911,7 @@ function listTags(ctx2, input) {
|
|
|
903
911
|
}
|
|
904
912
|
|
|
905
913
|
// src/tools/move_note.ts
|
|
906
|
-
import { access as access3, mkdir as mkdir3, rename as rename3 } from "fs/promises";
|
|
914
|
+
import { access as access3, mkdir as mkdir3, readFile as readFile4, rename as rename3 } from "fs/promises";
|
|
907
915
|
import { dirname as dirname2, join as join2 } from "path";
|
|
908
916
|
import { z as z10 } from "zod";
|
|
909
917
|
|
|
@@ -1006,6 +1014,9 @@ var MoveNoteInput = z10.object({
|
|
|
1006
1014
|
overwrite: z10.boolean().default(false).describe("Overwrite the destination if it exists. Defaults to false."),
|
|
1007
1015
|
rewriteLinks: z10.boolean().default(true).describe(
|
|
1008
1016
|
"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."
|
|
1017
|
+
),
|
|
1018
|
+
prevHash: z10.string().optional().describe(
|
|
1019
|
+
"Optional compare-and-swap guard on the source note: the contentHash from a prior read. Fails with hash_conflict if the note changed since."
|
|
1009
1020
|
)
|
|
1010
1021
|
});
|
|
1011
1022
|
async function moveNote(ctx2, rawInput) {
|
|
@@ -1014,11 +1025,13 @@ async function moveNote(ctx2, rawInput) {
|
|
|
1014
1025
|
const absTo = resolveVaultPath(ctx2.vaultRoot, input.to);
|
|
1015
1026
|
assertWritable(ctx2.policy, input.from);
|
|
1016
1027
|
assertWritable(ctx2.policy, input.to);
|
|
1028
|
+
let raw;
|
|
1017
1029
|
try {
|
|
1018
|
-
await
|
|
1030
|
+
raw = await readFile4(absFrom, "utf8");
|
|
1019
1031
|
} catch {
|
|
1020
1032
|
throw new Error(`Note not found: ${input.from}`);
|
|
1021
1033
|
}
|
|
1034
|
+
if (input.prevHash !== void 0) assertHashMatch(raw, input.prevHash, input.from);
|
|
1022
1035
|
if (!input.overwrite) {
|
|
1023
1036
|
try {
|
|
1024
1037
|
await access3(absTo);
|
|
@@ -1041,7 +1054,6 @@ async function moveNote(ctx2, rawInput) {
|
|
|
1041
1054
|
}
|
|
1042
1055
|
await mkdir3(dirname2(absTo), { recursive: true });
|
|
1043
1056
|
await rename3(absFrom, absTo);
|
|
1044
|
-
const raw = ctx2.notes.get(input.from)?.raw ?? "";
|
|
1045
1057
|
removeNoteBacklinks(ctx2, input.from);
|
|
1046
1058
|
if (ctx2.notes.has(input.from)) {
|
|
1047
1059
|
ctx2.index.discard(input.from);
|
|
@@ -1079,13 +1091,19 @@ async function moveNote(ctx2, rawInput) {
|
|
|
1079
1091
|
}
|
|
1080
1092
|
}
|
|
1081
1093
|
ctx2.backlinks.delete(input.from);
|
|
1082
|
-
const result = {
|
|
1094
|
+
const result = {
|
|
1095
|
+
from: input.from,
|
|
1096
|
+
to: input.to,
|
|
1097
|
+
notesRewritten,
|
|
1098
|
+
linksRewritten,
|
|
1099
|
+
contentHash: contentHash(raw)
|
|
1100
|
+
};
|
|
1083
1101
|
if (skipped.length > 0) result.skipped = skipped;
|
|
1084
1102
|
return result;
|
|
1085
1103
|
}
|
|
1086
1104
|
|
|
1087
1105
|
// src/tools/outline_note.ts
|
|
1088
|
-
import { readFile as
|
|
1106
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
1089
1107
|
|
|
1090
1108
|
// ../core/src/outline.ts
|
|
1091
1109
|
var HEADING_RE = /^(#{1,6})\s+(.+)/;
|
|
@@ -1143,7 +1161,7 @@ var OutlineNoteInput = z11.object({
|
|
|
1143
1161
|
});
|
|
1144
1162
|
async function outlineNote(ctx2, input) {
|
|
1145
1163
|
const absPath = resolveVaultPath(ctx2.vaultRoot, input.path);
|
|
1146
|
-
const raw = await
|
|
1164
|
+
const raw = await readFile5(absPath, "utf8");
|
|
1147
1165
|
return buildOutline(raw, {
|
|
1148
1166
|
includeBlocks: input.includeBlocks,
|
|
1149
1167
|
includeSizes: input.includeSizes
|
|
@@ -1151,7 +1169,7 @@ async function outlineNote(ctx2, input) {
|
|
|
1151
1169
|
}
|
|
1152
1170
|
|
|
1153
1171
|
// src/tools/patch_frontmatter.ts
|
|
1154
|
-
import { readFile as
|
|
1172
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
1155
1173
|
import { parseDocument as parseDocument2 } from "yaml";
|
|
1156
1174
|
import { z as z12 } from "zod";
|
|
1157
1175
|
var PatchFrontmatterInput = z12.object({
|
|
@@ -1166,7 +1184,7 @@ var PatchFrontmatterInput = z12.object({
|
|
|
1166
1184
|
async function patchFrontmatter(ctx2, input) {
|
|
1167
1185
|
const absPath = resolveVaultPath(ctx2.vaultRoot, input.path);
|
|
1168
1186
|
assertWritable(ctx2.policy, input.path);
|
|
1169
|
-
const original = await
|
|
1187
|
+
const original = await readFile6(absPath, "utf8");
|
|
1170
1188
|
if (input.prevHash !== void 0) assertHashMatch(original, input.prevHash, input.path);
|
|
1171
1189
|
const fm = parseFrontmatter(original);
|
|
1172
1190
|
const keysChanged = [];
|
|
@@ -1226,7 +1244,7 @@ ${original}`;
|
|
|
1226
1244
|
}
|
|
1227
1245
|
|
|
1228
1246
|
// src/tools/patch_note.ts
|
|
1229
|
-
import { readFile as
|
|
1247
|
+
import { readFile as readFile7 } from "fs/promises";
|
|
1230
1248
|
import { z as z13 } from "zod";
|
|
1231
1249
|
var PatchNoteInput = z13.object({
|
|
1232
1250
|
path: z13.string().describe("Vault-relative path to the note."),
|
|
@@ -1257,7 +1275,7 @@ function lineStart(raw, offset) {
|
|
|
1257
1275
|
async function patchNote(ctx2, input) {
|
|
1258
1276
|
const absPath = resolveVaultPath(ctx2.vaultRoot, input.path);
|
|
1259
1277
|
assertWritable(ctx2.policy, input.path);
|
|
1260
|
-
const raw = await
|
|
1278
|
+
const raw = await readFile7(absPath, "utf8");
|
|
1261
1279
|
if (input.prevHash !== void 0) assertHashMatch(raw, input.prevHash, input.path);
|
|
1262
1280
|
const fm = parseFrontmatter(raw);
|
|
1263
1281
|
const originalFmRegion = raw.slice(0, fm.bodyStart);
|
|
@@ -1357,7 +1375,7 @@ ${input.content}${contentNl}`;
|
|
|
1357
1375
|
};
|
|
1358
1376
|
}
|
|
1359
1377
|
async function verifyFrontmatter(absPath, originalFmRegion) {
|
|
1360
|
-
const written = await
|
|
1378
|
+
const written = await readFile7(absPath, "utf8");
|
|
1361
1379
|
if (written.slice(0, originalFmRegion.length) !== originalFmRegion) {
|
|
1362
1380
|
throw new Error("Write-safety violation: frontmatter region changed unexpectedly");
|
|
1363
1381
|
}
|
|
@@ -1372,7 +1390,7 @@ function updateCache(ctx2, path2, newContent, bodyStart) {
|
|
|
1372
1390
|
}
|
|
1373
1391
|
|
|
1374
1392
|
// src/tools/periodic_note.ts
|
|
1375
|
-
import { access as access4, mkdir as mkdir4, readFile as
|
|
1393
|
+
import { access as access4, mkdir as mkdir4, readFile as readFile8 } from "fs/promises";
|
|
1376
1394
|
import { dirname as dirname3, join as join3 } from "path";
|
|
1377
1395
|
import { z as z14 } from "zod";
|
|
1378
1396
|
var PERIOD = z14.enum(["daily", "weekly", "monthly", "quarterly", "yearly"]);
|
|
@@ -1385,7 +1403,7 @@ var DEFAULTS = {
|
|
|
1385
1403
|
};
|
|
1386
1404
|
async function readJson(absPath) {
|
|
1387
1405
|
try {
|
|
1388
|
-
return JSON.parse(await
|
|
1406
|
+
return JSON.parse(await readFile8(absPath, "utf8"));
|
|
1389
1407
|
} catch {
|
|
1390
1408
|
return null;
|
|
1391
1409
|
}
|
|
@@ -1471,7 +1489,7 @@ async function getPeriodicNote(ctx2, rawInput) {
|
|
|
1471
1489
|
ctx2.vaultRoot,
|
|
1472
1490
|
cfg.template.endsWith(".md") ? cfg.template : `${cfg.template}.md`
|
|
1473
1491
|
);
|
|
1474
|
-
body = await
|
|
1492
|
+
body = await readFile8(templateAbs, "utf8");
|
|
1475
1493
|
} catch {
|
|
1476
1494
|
}
|
|
1477
1495
|
}
|
|
@@ -1498,7 +1516,7 @@ async function appendPeriodicNote(ctx2, rawInput) {
|
|
|
1498
1516
|
assertWritable(ctx2.policy, path2);
|
|
1499
1517
|
let original = "";
|
|
1500
1518
|
try {
|
|
1501
|
-
original = await
|
|
1519
|
+
original = await readFile8(abs, "utf8");
|
|
1502
1520
|
if (input.prevHash !== void 0) assertHashMatch(original, input.prevHash, path2);
|
|
1503
1521
|
} catch (err) {
|
|
1504
1522
|
if (err.code !== "ENOENT") throw err;
|
|
@@ -1671,7 +1689,7 @@ function queryNotes(ctx2, input) {
|
|
|
1671
1689
|
}
|
|
1672
1690
|
|
|
1673
1691
|
// src/tools/read_note.ts
|
|
1674
|
-
import { readFile as
|
|
1692
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
1675
1693
|
import { z as z16 } from "zod";
|
|
1676
1694
|
var ReadNoteInput = z16.object({
|
|
1677
1695
|
path: z16.string().describe('Vault-relative path to the note, e.g. "Daily Notes/2026-05-29.md".'),
|
|
@@ -1689,7 +1707,7 @@ var ReadNoteInput = z16.object({
|
|
|
1689
1707
|
});
|
|
1690
1708
|
async function readNote(ctx2, input) {
|
|
1691
1709
|
const absPath = resolveVaultPath(ctx2.vaultRoot, input.path);
|
|
1692
|
-
const raw = await
|
|
1710
|
+
const raw = await readFile9(absPath, "utf8");
|
|
1693
1711
|
const noteBytes = Buffer.byteLength(raw, "utf8");
|
|
1694
1712
|
const hash = contentHash(raw);
|
|
1695
1713
|
if (input.section === void 0 && input.block === void 0 && input.lines === void 0) {
|
|
@@ -1777,7 +1795,7 @@ async function readNote(ctx2, input) {
|
|
|
1777
1795
|
}
|
|
1778
1796
|
|
|
1779
1797
|
// src/tools/rename_heading.ts
|
|
1780
|
-
import { readFile as
|
|
1798
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
1781
1799
|
import { join as join4 } from "path";
|
|
1782
1800
|
import { z as z17 } from "zod";
|
|
1783
1801
|
var RenameHeadingInput = z17.object({
|
|
@@ -1818,7 +1836,7 @@ async function renameHeading(ctx2, rawInput) {
|
|
|
1818
1836
|
assertWritable(ctx2.policy, input.path);
|
|
1819
1837
|
let raw;
|
|
1820
1838
|
try {
|
|
1821
|
-
raw = await
|
|
1839
|
+
raw = await readFile10(absPath, "utf8");
|
|
1822
1840
|
} catch {
|
|
1823
1841
|
throw new Error(`Note not found: ${input.path}`);
|
|
1824
1842
|
}
|
|
@@ -1854,7 +1872,7 @@ async function renameHeading(ctx2, rawInput) {
|
|
|
1854
1872
|
newRaw = newRaw.slice(0, fm.bodyStart) + selfRewrite.content;
|
|
1855
1873
|
const originalFmRegion = raw.slice(0, fm.bodyStart);
|
|
1856
1874
|
await atomicWrite(absPath, newRaw);
|
|
1857
|
-
const written = await
|
|
1875
|
+
const written = await readFile10(absPath, "utf8");
|
|
1858
1876
|
if (written.slice(0, originalFmRegion.length) !== originalFmRegion) {
|
|
1859
1877
|
throw new Error("Write-safety violation: frontmatter region changed unexpectedly");
|
|
1860
1878
|
}
|
|
@@ -1900,7 +1918,7 @@ async function renameHeading(ctx2, rawInput) {
|
|
|
1900
1918
|
}
|
|
1901
1919
|
|
|
1902
1920
|
// src/tools/replace_in_note.ts
|
|
1903
|
-
import { readFile as
|
|
1921
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
1904
1922
|
import { z as z18 } from "zod";
|
|
1905
1923
|
var ReplaceInNoteInput = z18.object({
|
|
1906
1924
|
path: z18.string().describe("Vault-relative path to the note."),
|
|
@@ -1937,7 +1955,7 @@ async function replaceInNote(ctx2, input) {
|
|
|
1937
1955
|
} catch (err) {
|
|
1938
1956
|
throw new Error(`Invalid regex: ${err instanceof Error ? err.message : String(err)}`);
|
|
1939
1957
|
}
|
|
1940
|
-
const raw = await
|
|
1958
|
+
const raw = await readFile11(absPath, "utf8");
|
|
1941
1959
|
if (input.prevHash !== void 0) assertHashMatch(raw, input.prevHash, input.path);
|
|
1942
1960
|
const fm = parseFrontmatter(raw);
|
|
1943
1961
|
const originalFmRegion = raw.slice(0, fm.bodyStart);
|
|
@@ -1954,7 +1972,10 @@ async function replaceInNote(ctx2, input) {
|
|
|
1954
1972
|
path: input.path,
|
|
1955
1973
|
replacements: toReplace.length,
|
|
1956
1974
|
matches: matches2,
|
|
1957
|
-
frontmatterUnchanged: true
|
|
1975
|
+
frontmatterUnchanged: true,
|
|
1976
|
+
// Nothing was written — hash the content as read, so chained edits can
|
|
1977
|
+
// proceed from a dry run without a re-read.
|
|
1978
|
+
contentHash: contentHash(raw)
|
|
1958
1979
|
};
|
|
1959
1980
|
}
|
|
1960
1981
|
const singleRe = new RegExp(re.source, re.flags.replace("g", ""));
|
|
@@ -1969,7 +1990,7 @@ async function replaceInNote(ctx2, input) {
|
|
|
1969
1990
|
newBody += body.slice(lastIndex);
|
|
1970
1991
|
const newContent = `${originalFmRegion}${newBody}`;
|
|
1971
1992
|
await atomicWrite(absPath, newContent);
|
|
1972
|
-
const written = await
|
|
1993
|
+
const written = await readFile11(absPath, "utf8");
|
|
1973
1994
|
if (written.slice(0, originalFmRegion.length) !== originalFmRegion) {
|
|
1974
1995
|
throw new Error("Write-safety violation: frontmatter region changed unexpectedly");
|
|
1975
1996
|
}
|
|
@@ -2212,7 +2233,7 @@ async function run(ctx2, name, args) {
|
|
|
2212
2233
|
}
|
|
2213
2234
|
|
|
2214
2235
|
// src/index/build.ts
|
|
2215
|
-
import { readFile as
|
|
2236
|
+
import { readFile as readFile12 } from "fs/promises";
|
|
2216
2237
|
import { basename as basename3, extname as extname4 } from "path";
|
|
2217
2238
|
|
|
2218
2239
|
// ../core/src/walk.ts
|
|
@@ -2294,7 +2315,7 @@ async function buildIndex(vaultRoot2) {
|
|
|
2294
2315
|
await Promise.all(
|
|
2295
2316
|
noteEntries.map(async (entry) => {
|
|
2296
2317
|
try {
|
|
2297
|
-
const raw = await
|
|
2318
|
+
const raw = await readFile12(entry.absPath, "utf8");
|
|
2298
2319
|
const fm = parseFrontmatter(raw);
|
|
2299
2320
|
const inlineTags = extractInlineTags(fm.body);
|
|
2300
2321
|
const fmTags = frontmatterTags(fm.data);
|
|
@@ -2347,7 +2368,7 @@ function buildBacklinks(notes2) {
|
|
|
2347
2368
|
|
|
2348
2369
|
// src/init.ts
|
|
2349
2370
|
import { execFileSync } from "child_process";
|
|
2350
|
-
import { access as access5, copyFile, mkdir as mkdir5, readdir, readFile as
|
|
2371
|
+
import { access as access5, copyFile, mkdir as mkdir5, readdir, readFile as readFile13, stat as stat2, writeFile as writeFile2 } from "fs/promises";
|
|
2351
2372
|
import path, { dirname as dirname4, join as join6 } from "path";
|
|
2352
2373
|
function parseInitArgs(argv) {
|
|
2353
2374
|
const opts = { write: false, client: "desktop" };
|
|
@@ -2491,7 +2512,7 @@ async function runInit(opts, deps) {
|
|
|
2491
2512
|
]
|
|
2492
2513
|
};
|
|
2493
2514
|
}
|
|
2494
|
-
const rf = deps.readFile ??
|
|
2515
|
+
const rf = deps.readFile ?? readFile13;
|
|
2495
2516
|
let vaultPath = opts.vault ?? deps.env.SEEKSTONE_VAULT;
|
|
2496
2517
|
if (!vaultPath) {
|
|
2497
2518
|
const registryPath = obsidianRegistryPath(deps.platform, {
|
|
@@ -2752,9 +2773,32 @@ function installProcessGuards(log2) {
|
|
|
2752
2773
|
}
|
|
2753
2774
|
|
|
2754
2775
|
// src/tool-list.ts
|
|
2776
|
+
var READ_ONLY_ANNOTATIONS = {
|
|
2777
|
+
readOnlyHint: true,
|
|
2778
|
+
openWorldHint: false
|
|
2779
|
+
};
|
|
2780
|
+
var ADDITIVE_WRITE_ANNOTATIONS = {
|
|
2781
|
+
readOnlyHint: false,
|
|
2782
|
+
destructiveHint: false,
|
|
2783
|
+
idempotentHint: false,
|
|
2784
|
+
openWorldHint: false
|
|
2785
|
+
};
|
|
2786
|
+
var DESTRUCTIVE_WRITE_ANNOTATIONS = {
|
|
2787
|
+
readOnlyHint: false,
|
|
2788
|
+
destructiveHint: true,
|
|
2789
|
+
idempotentHint: false,
|
|
2790
|
+
openWorldHint: false
|
|
2791
|
+
};
|
|
2792
|
+
var IDEMPOTENT_DESTRUCTIVE_WRITE_ANNOTATIONS = {
|
|
2793
|
+
readOnlyHint: false,
|
|
2794
|
+
destructiveHint: true,
|
|
2795
|
+
idempotentHint: true,
|
|
2796
|
+
openWorldHint: false
|
|
2797
|
+
};
|
|
2755
2798
|
var ALL_TOOLS = [
|
|
2756
2799
|
{
|
|
2757
2800
|
name: "search",
|
|
2801
|
+
annotations: READ_ONLY_ANNOTATIONS,
|
|
2758
2802
|
description: "Full-text search across the vault. Returns ranked excerpts (~200 chars) \u2014 not full notes \u2014 to minimise context usage. Supports fuzzy matching and prefix search.",
|
|
2759
2803
|
inputSchema: {
|
|
2760
2804
|
type: "object",
|
|
@@ -2769,6 +2813,7 @@ var ALL_TOOLS = [
|
|
|
2769
2813
|
},
|
|
2770
2814
|
{
|
|
2771
2815
|
name: "query_notes",
|
|
2816
|
+
annotations: READ_ONLY_ANNOTATIONS,
|
|
2772
2817
|
description: "Structured metadata query \u2014 filter notes by frontmatter key/value predicates, tag, folder, modified time, and size. Returns compact rows (path + title by default; opt into more via select), not note content. Use this instead of search when filtering by properties rather than text.",
|
|
2773
2818
|
inputSchema: {
|
|
2774
2819
|
type: "object",
|
|
@@ -2826,6 +2871,7 @@ var ALL_TOOLS = [
|
|
|
2826
2871
|
},
|
|
2827
2872
|
{
|
|
2828
2873
|
name: "context_pack",
|
|
2874
|
+
annotations: READ_ONLY_ANNOTATIONS,
|
|
2829
2875
|
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.',
|
|
2830
2876
|
inputSchema: {
|
|
2831
2877
|
type: "object",
|
|
@@ -2841,6 +2887,7 @@ var ALL_TOOLS = [
|
|
|
2841
2887
|
},
|
|
2842
2888
|
{
|
|
2843
2889
|
name: "read_note",
|
|
2890
|
+
annotations: READ_ONLY_ANNOTATIONS,
|
|
2844
2891
|
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.",
|
|
2845
2892
|
inputSchema: {
|
|
2846
2893
|
type: "object",
|
|
@@ -2879,6 +2926,7 @@ var ALL_TOOLS = [
|
|
|
2879
2926
|
},
|
|
2880
2927
|
{
|
|
2881
2928
|
name: "list_notes",
|
|
2929
|
+
annotations: READ_ONLY_ANNOTATIONS,
|
|
2882
2930
|
description: "List notes, optionally filtered by folder prefix or tag.",
|
|
2883
2931
|
inputSchema: {
|
|
2884
2932
|
type: "object",
|
|
@@ -2892,6 +2940,7 @@ var ALL_TOOLS = [
|
|
|
2892
2940
|
},
|
|
2893
2941
|
{
|
|
2894
2942
|
name: "list_tags",
|
|
2943
|
+
annotations: READ_ONLY_ANNOTATIONS,
|
|
2895
2944
|
description: "List all tags in the vault with usage counts. Supports substring filtering, minimum count threshold, and sort order. Nested tags (e.g. area/work) include a parent field.",
|
|
2896
2945
|
inputSchema: {
|
|
2897
2946
|
type: "object",
|
|
@@ -2915,6 +2964,7 @@ var ALL_TOOLS = [
|
|
|
2915
2964
|
},
|
|
2916
2965
|
{
|
|
2917
2966
|
name: "create_note",
|
|
2967
|
+
annotations: DESTRUCTIVE_WRITE_ANNOTATIONS,
|
|
2918
2968
|
description: "Create a new note at a vault-relative path. Optionally sets frontmatter and body content. Parent directories are created automatically. Fails if the note already exists unless overwrite is true (prevHash may guard the overwrite).",
|
|
2919
2969
|
inputSchema: {
|
|
2920
2970
|
type: "object",
|
|
@@ -2943,6 +2993,7 @@ var ALL_TOOLS = [
|
|
|
2943
2993
|
},
|
|
2944
2994
|
{
|
|
2945
2995
|
name: "delete_note",
|
|
2996
|
+
annotations: DESTRUCTIVE_WRITE_ANNOTATIONS,
|
|
2946
2997
|
description: "Delete a note. By default it is moved to the vault .trash/ folder (recoverable by moving it back); pass permanent: true to remove it outright.",
|
|
2947
2998
|
inputSchema: {
|
|
2948
2999
|
type: "object",
|
|
@@ -2951,6 +3002,10 @@ var ALL_TOOLS = [
|
|
|
2951
3002
|
permanent: {
|
|
2952
3003
|
type: "boolean",
|
|
2953
3004
|
description: "Permanently remove instead of moving to .trash/. Defaults to false."
|
|
3005
|
+
},
|
|
3006
|
+
prevHash: {
|
|
3007
|
+
type: "string",
|
|
3008
|
+
description: "Optional compare-and-swap guard: the contentHash from a prior read. Fails with hash_conflict if the note changed since \u2014 so you never delete content you have not seen."
|
|
2954
3009
|
}
|
|
2955
3010
|
},
|
|
2956
3011
|
required: ["path"]
|
|
@@ -2958,6 +3013,7 @@ var ALL_TOOLS = [
|
|
|
2958
3013
|
},
|
|
2959
3014
|
{
|
|
2960
3015
|
name: "move_note",
|
|
3016
|
+
annotations: DESTRUCTIVE_WRITE_ANNOTATIONS,
|
|
2961
3017
|
description: "Move or rename a note to a new vault-relative path, rewriting wikilinks and markdown links in other notes that point at it so nothing breaks (links inside fenced code blocks are left alone). Parent directories at the destination are created automatically. Fails if the destination already exists unless overwrite is true.",
|
|
2962
3018
|
inputSchema: {
|
|
2963
3019
|
type: "object",
|
|
@@ -2971,6 +3027,10 @@ var ALL_TOOLS = [
|
|
|
2971
3027
|
rewriteLinks: {
|
|
2972
3028
|
type: "boolean",
|
|
2973
3029
|
description: "Rewrite references in other notes to follow the move. Defaults to true; pass false to move the file only."
|
|
3030
|
+
},
|
|
3031
|
+
prevHash: {
|
|
3032
|
+
type: "string",
|
|
3033
|
+
description: "Optional compare-and-swap guard on the source note: the contentHash from a prior read. Fails with hash_conflict if the note changed since."
|
|
2974
3034
|
}
|
|
2975
3035
|
},
|
|
2976
3036
|
required: ["from", "to"]
|
|
@@ -2978,6 +3038,7 @@ var ALL_TOOLS = [
|
|
|
2978
3038
|
},
|
|
2979
3039
|
{
|
|
2980
3040
|
name: "rename_heading",
|
|
3041
|
+
annotations: DESTRUCTIVE_WRITE_ANNOTATIONS,
|
|
2981
3042
|
description: "Rename a heading in a note and rewrite every [[note#heading]] wikilink and embed across the vault so references keep working \u2014 aliases preserved, fenced code blocks left alone. Served from the warm backlink index, no vault scan. Heading matching is case-insensitive; with duplicate headings the first match wins, mirroring Obsidian link resolution.",
|
|
2982
3043
|
inputSchema: {
|
|
2983
3044
|
type: "object",
|
|
@@ -3004,6 +3065,7 @@ var ALL_TOOLS = [
|
|
|
3004
3065
|
},
|
|
3005
3066
|
{
|
|
3006
3067
|
name: "append_note",
|
|
3068
|
+
annotations: ADDITIVE_WRITE_ANNOTATIONS,
|
|
3007
3069
|
description: "Append text to a note body without touching the frontmatter. Safe for meeting notes, daily logs, and append-only workflows.",
|
|
3008
3070
|
inputSchema: {
|
|
3009
3071
|
type: "object",
|
|
@@ -3020,6 +3082,7 @@ var ALL_TOOLS = [
|
|
|
3020
3082
|
},
|
|
3021
3083
|
{
|
|
3022
3084
|
name: "patch_frontmatter",
|
|
3085
|
+
annotations: IDEMPOTENT_DESTRUCTIVE_WRITE_ANNOTATIONS,
|
|
3023
3086
|
description: "Set, update, or delete frontmatter keys without reordering existing keys or changing quote style. Pass null as a value to delete a key.",
|
|
3024
3087
|
inputSchema: {
|
|
3025
3088
|
type: "object",
|
|
@@ -3040,6 +3103,7 @@ var ALL_TOOLS = [
|
|
|
3040
3103
|
},
|
|
3041
3104
|
{
|
|
3042
3105
|
name: "outline_note",
|
|
3106
|
+
annotations: READ_ONLY_ANNOTATIONS,
|
|
3043
3107
|
description: "Return a note's structure \u2014 heading tree with offsets, block-reference anchors, and frontmatter key list \u2014 without returning any prose. Use this before section reads or patches to discover what sections exist at a fraction of the cost of reading the full note.",
|
|
3044
3108
|
inputSchema: {
|
|
3045
3109
|
type: "object",
|
|
@@ -3059,6 +3123,7 @@ var ALL_TOOLS = [
|
|
|
3059
3123
|
},
|
|
3060
3124
|
{
|
|
3061
3125
|
name: "patch_note",
|
|
3126
|
+
annotations: DESTRUCTIVE_WRITE_ANNOTATIONS,
|
|
3062
3127
|
description: "Surgically edit a section of a note \u2014 targeted by heading or block reference \u2014 without rewriting the whole file. Operations: append (add after section), prepend (add after heading line), replace (swap section content). Frontmatter is never touched.",
|
|
3063
3128
|
inputSchema: {
|
|
3064
3129
|
type: "object",
|
|
@@ -3088,6 +3153,7 @@ var ALL_TOOLS = [
|
|
|
3088
3153
|
},
|
|
3089
3154
|
{
|
|
3090
3155
|
name: "get_backlinks",
|
|
3156
|
+
annotations: READ_ONLY_ANNOTATIONS,
|
|
3091
3157
|
description: "Return every note that links to the target note, with the source line and an optional excerpt. Results come from the pre-built reverse-link index so this is a fast, pure index lookup. Sort order: source path ascending.",
|
|
3092
3158
|
inputSchema: {
|
|
3093
3159
|
type: "object",
|
|
@@ -3107,6 +3173,7 @@ var ALL_TOOLS = [
|
|
|
3107
3173
|
},
|
|
3108
3174
|
{
|
|
3109
3175
|
name: "get_links",
|
|
3176
|
+
annotations: READ_ONLY_ANNOTATIONS,
|
|
3110
3177
|
description: "Return all outgoing wikilinks and embeds from a note. Each link is marked resolved (with target path) or unresolved. Duplicate targets are de-duplicated; results sorted by line number.",
|
|
3111
3178
|
inputSchema: {
|
|
3112
3179
|
type: "object",
|
|
@@ -3118,6 +3185,7 @@ var ALL_TOOLS = [
|
|
|
3118
3185
|
},
|
|
3119
3186
|
{
|
|
3120
3187
|
name: "replace_in_note",
|
|
3188
|
+
annotations: DESTRUCTIVE_WRITE_ANNOTATIONS,
|
|
3121
3189
|
description: "Find and replace text within a note body. Supports literal and regex search, case sensitivity, whole-word matching, and a replacement limit. Frontmatter is never touched. Use dryRun to preview matches before writing.",
|
|
3122
3190
|
inputSchema: {
|
|
3123
3191
|
type: "object",
|
|
@@ -3158,6 +3226,7 @@ var ALL_TOOLS = [
|
|
|
3158
3226
|
},
|
|
3159
3227
|
{
|
|
3160
3228
|
name: "get_periodic_note",
|
|
3229
|
+
annotations: ADDITIVE_WRITE_ANNOTATIONS,
|
|
3161
3230
|
description: "Get the path and existence status of a periodic note (daily, weekly, monthly, quarterly, or yearly) for a given date. Reads folder/format config from .obsidian/daily-notes.json (daily) or the periodic-notes plugin data.json. Optionally creates the note from the configured template if it is missing.",
|
|
3162
3231
|
inputSchema: {
|
|
3163
3232
|
type: "object",
|
|
@@ -3181,6 +3250,7 @@ var ALL_TOOLS = [
|
|
|
3181
3250
|
},
|
|
3182
3251
|
{
|
|
3183
3252
|
name: "append_periodic_note",
|
|
3253
|
+
annotations: ADDITIVE_WRITE_ANNOTATIONS,
|
|
3184
3254
|
description: "Append text to a periodic note (daily, weekly, monthly, quarterly, or yearly). Preserves existing frontmatter exactly. Creates the note first (from template if configured) when createIfMissing is true (default).",
|
|
3185
3255
|
inputSchema: {
|
|
3186
3256
|
type: "object",
|
|
@@ -3218,7 +3288,7 @@ function visibleTools(policy2) {
|
|
|
3218
3288
|
|
|
3219
3289
|
// src/watcher.ts
|
|
3220
3290
|
import { realpathSync } from "fs";
|
|
3221
|
-
import { readFile as
|
|
3291
|
+
import { readFile as readFile14 } from "fs/promises";
|
|
3222
3292
|
import { join as join7, relative as relative2, sep as sep3 } from "path";
|
|
3223
3293
|
import chokidar from "chokidar";
|
|
3224
3294
|
function startWatcher(ctx2, log2, opts) {
|
|
@@ -3231,7 +3301,7 @@ function startWatcher(ctx2, log2, opts) {
|
|
|
3231
3301
|
const toRel = (abs) => relative2(vaultRoot2, abs).split(sep3).join("/");
|
|
3232
3302
|
async function upsert(relPath, op) {
|
|
3233
3303
|
try {
|
|
3234
|
-
const raw = await
|
|
3304
|
+
const raw = await readFile14(join7(vaultRoot2, relPath), "utf8");
|
|
3235
3305
|
removeNoteBacklinks(ctx2, relPath);
|
|
3236
3306
|
upsertDoc(ctx2, buildDoc(relPath, raw));
|
|
3237
3307
|
addNoteBacklinks(ctx2, relPath, raw);
|
|
@@ -3283,7 +3353,7 @@ function startWatcher(ctx2, log2, opts) {
|
|
|
3283
3353
|
}
|
|
3284
3354
|
|
|
3285
3355
|
// src/index.ts
|
|
3286
|
-
var VERSION = true ? "0.
|
|
3356
|
+
var VERSION = true ? "0.14.0" : "0.0.0-dev";
|
|
3287
3357
|
var intent = parseCliIntent(process.argv.slice(2));
|
|
3288
3358
|
if (intent === "version") {
|
|
3289
3359
|
process.stdout.write(`${VERSION}
|