seekstone 0.12.1 → 0.12.2
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 -3
- package/dist/index.js +63 -95
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -110,7 +110,7 @@ Other MCP clients (Windsurf, Cline, …) take the Option 3 JSON block in their o
|
|
|
110
110
|
|
|
111
111
|
| Tool | Description |
|
|
112
112
|
|---|---|
|
|
113
|
-
| `search` | Full-text search. Returns ranked excerpts (default ~120 chars, tunable via `excerptLength`), not full notes. Fuzzy
|
|
113
|
+
| `search` | Full-text search. Returns ranked excerpts (default ~120 chars, tunable via `excerptLength`), not full notes. Fuzzy and prefix matching. |
|
|
114
114
|
| `query_notes` | Structured metadata query. Filter by frontmatter key/value predicates (`eq`, `ne`, `contains`, `exists`, `missing`, `gt`/`gte`/`lt`/`lte`), tag, folder, modified time, and size; sort and select the fields you need. Returns compact rows, not note content. |
|
|
115
115
|
| `context_pack` | Answer-ready context for a natural-language question in one call, hard-capped at a byte budget (default 2 KB): ranked excerpts, linked neighbor notes with one-line summaries, and follow-up source paths — replaces a search → read → get_backlinks round-trip loop. |
|
|
116
116
|
| `read_note` | Read the full content of a note by vault-relative path. Supports returning a single section, block, or line range. |
|
|
@@ -131,10 +131,10 @@ Other MCP clients (Windsurf, Cline, …) take the Option 3 JSON block in their o
|
|
|
131
131
|
| `append_note` | Append to a note body without touching frontmatter. |
|
|
132
132
|
| `patch_frontmatter` | Set/update/delete frontmatter keys without reordering existing keys or changing quote style. |
|
|
133
133
|
| `patch_note` | Insert text immediately after a heading without touching frontmatter. |
|
|
134
|
-
| `replace_in_note` |
|
|
134
|
+
| `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. |
|
|
135
135
|
| `append_periodic_note` | Append to today's periodic note, creating it from a template if it doesn't yet exist. |
|
|
136
136
|
|
|
137
|
-
|
|
137
|
+
The content-editing tools (`append_note`, `patch_note`, `patch_frontmatter`, `replace_in_note`, `append_periodic_note`, and `create_note` with `overwrite: true`) support optional **compare-and-swap**: pass the `contentHash` you got from `read_note` as `prevHash` and the write fails cleanly if the note changed underneath you, instead of silently discarding the concurrent edit.
|
|
138
138
|
|
|
139
139
|
---
|
|
140
140
|
|
|
@@ -146,6 +146,7 @@ Every edit tool supports optional **compare-and-swap**: pass the `contentHash` y
|
|
|
146
146
|
| `SEEKSTONE_LOG_LEVEL` | no | `error` \| `warn` \| `info` (default) \| `debug`. |
|
|
147
147
|
| `SEEKSTONE_LOG_FILE` | no | Absolute path; when set, JSON-line logs are appended here (size-rotated). |
|
|
148
148
|
| `SEEKSTONE_WATCH_POLL` | no | Set to `1` to stat-poll for changes instead of native OS events — reliable on network drives, WSL, containers. |
|
|
149
|
+
| `SEEKSTONE_LOG_MAX_SIZE` | no | Log-rotation threshold for `SEEKSTONE_LOG_FILE` (e.g. `10mb`; default 5 MB). |
|
|
149
150
|
| `SEEKSTONE_READ_ONLY` | no | Set to `1` to run read-only: the 8 write tools are unregistered entirely (and rejected if called anyway), so the session provably cannot modify your vault. |
|
|
150
151
|
| `SEEKSTONE_WRITE_PATHS` | no | Comma-separated vault-relative globs (e.g. `journal/**,inbox/*.md`). Writes are permitted only under matching paths; the rest of the vault stays read-only. |
|
|
151
152
|
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
+
import { resolve as resolve2 } from "path";
|
|
4
5
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
5
6
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
7
|
import {
|
|
@@ -75,7 +76,6 @@ Docs: https://github.com/shaqmughal/seekstone`;
|
|
|
75
76
|
|
|
76
77
|
// src/tools/append_note.ts
|
|
77
78
|
import { readFile } from "fs/promises";
|
|
78
|
-
import { join } from "path";
|
|
79
79
|
|
|
80
80
|
// ../core/src/frontmatter.ts
|
|
81
81
|
import { parse as parseYaml } from "yaml";
|
|
@@ -200,6 +200,16 @@ function assertWritable(policy2, relPath) {
|
|
|
200
200
|
}
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
+
// src/vault-path.ts
|
|
204
|
+
import { resolve, sep } from "path";
|
|
205
|
+
function resolveVaultPath(vaultRoot2, inputPath) {
|
|
206
|
+
const abs = resolve(vaultRoot2, inputPath);
|
|
207
|
+
if (abs !== vaultRoot2 && !abs.startsWith(vaultRoot2 + sep)) {
|
|
208
|
+
throw new Error(`Path outside vault: ${inputPath}`);
|
|
209
|
+
}
|
|
210
|
+
return abs;
|
|
211
|
+
}
|
|
212
|
+
|
|
203
213
|
// src/tools/append_note.ts
|
|
204
214
|
var AppendNoteInput = z.object({
|
|
205
215
|
path: z.string().describe("Vault-relative path to the note."),
|
|
@@ -211,10 +221,7 @@ var AppendNoteInput = z.object({
|
|
|
211
221
|
)
|
|
212
222
|
});
|
|
213
223
|
async function appendNote(ctx2, input) {
|
|
214
|
-
const absPath =
|
|
215
|
-
if (!absPath.startsWith(ctx2.vaultRoot)) {
|
|
216
|
-
throw new Error(`Path outside vault: ${input.path}`);
|
|
217
|
-
}
|
|
224
|
+
const absPath = resolveVaultPath(ctx2.vaultRoot, input.path);
|
|
218
225
|
assertWritable(ctx2.policy, input.path);
|
|
219
226
|
const original = await readFile(absPath, "utf8");
|
|
220
227
|
if (input.prevHash !== void 0) assertHashMatch(original, input.prevHash, input.path);
|
|
@@ -565,7 +572,7 @@ function contextPack(ctx2, input) {
|
|
|
565
572
|
|
|
566
573
|
// src/tools/create_note.ts
|
|
567
574
|
import { access, mkdir, readFile as readFile2 } from "fs/promises";
|
|
568
|
-
import { dirname
|
|
575
|
+
import { dirname } from "path";
|
|
569
576
|
import { stringify as stringifyYaml } from "yaml";
|
|
570
577
|
import { z as z4 } from "zod";
|
|
571
578
|
|
|
@@ -611,10 +618,7 @@ var CreateNoteInput = z4.object({
|
|
|
611
618
|
});
|
|
612
619
|
async function createNote(ctx2, rawInput) {
|
|
613
620
|
const input = CreateNoteInput.parse(rawInput);
|
|
614
|
-
const absPath =
|
|
615
|
-
if (!absPath.startsWith(ctx2.vaultRoot)) {
|
|
616
|
-
throw new Error(`Path outside vault: ${input.path}`);
|
|
617
|
-
}
|
|
621
|
+
const absPath = resolveVaultPath(ctx2.vaultRoot, input.path);
|
|
618
622
|
assertWritable(ctx2.policy, input.path);
|
|
619
623
|
if (!input.overwrite) {
|
|
620
624
|
try {
|
|
@@ -659,7 +663,7 @@ ${stringifyYaml(input.frontmatter, { lineWidth: 0 })}---
|
|
|
659
663
|
|
|
660
664
|
// src/tools/delete_note.ts
|
|
661
665
|
import { access as access2, mkdir as mkdir2, rename as rename2, rm } from "fs/promises";
|
|
662
|
-
import { basename as basename2, extname as extname2, join
|
|
666
|
+
import { basename as basename2, extname as extname2, join } from "path";
|
|
663
667
|
import { z as z5 } from "zod";
|
|
664
668
|
|
|
665
669
|
// src/index/backlinks.ts
|
|
@@ -707,7 +711,7 @@ async function freeTrashPath(vaultRoot2, noteBase) {
|
|
|
707
711
|
let candidate = `.trash/${noteBase}`;
|
|
708
712
|
for (; ; ) {
|
|
709
713
|
try {
|
|
710
|
-
await access2(
|
|
714
|
+
await access2(join(vaultRoot2, candidate));
|
|
711
715
|
} catch {
|
|
712
716
|
return candidate;
|
|
713
717
|
}
|
|
@@ -716,19 +720,16 @@ async function freeTrashPath(vaultRoot2, noteBase) {
|
|
|
716
720
|
}
|
|
717
721
|
async function deleteNote(ctx2, rawInput) {
|
|
718
722
|
const input = DeleteNoteInput.parse(rawInput);
|
|
719
|
-
const absPath =
|
|
720
|
-
if (!absPath.startsWith(ctx2.vaultRoot)) {
|
|
721
|
-
throw new Error(`Path outside vault: ${input.path}`);
|
|
722
|
-
}
|
|
723
|
+
const absPath = resolveVaultPath(ctx2.vaultRoot, input.path);
|
|
723
724
|
assertWritable(ctx2.policy, input.path);
|
|
724
725
|
let trashedTo;
|
|
725
726
|
if (input.permanent) {
|
|
726
727
|
await rm(absPath);
|
|
727
728
|
} else {
|
|
728
729
|
await access2(absPath);
|
|
729
|
-
await mkdir2(
|
|
730
|
+
await mkdir2(join(ctx2.vaultRoot, ".trash"), { recursive: true });
|
|
730
731
|
trashedTo = await freeTrashPath(ctx2.vaultRoot, basename2(input.path));
|
|
731
|
-
await rename2(absPath,
|
|
732
|
+
await rename2(absPath, join(ctx2.vaultRoot, trashedTo));
|
|
732
733
|
}
|
|
733
734
|
removeNoteBacklinks(ctx2, input.path);
|
|
734
735
|
if (ctx2.notes.has(input.path)) {
|
|
@@ -741,7 +742,6 @@ async function deleteNote(ctx2, rawInput) {
|
|
|
741
742
|
}
|
|
742
743
|
|
|
743
744
|
// src/tools/get_backlinks.ts
|
|
744
|
-
import { join as join4 } from "path";
|
|
745
745
|
import { z as z6 } from "zod";
|
|
746
746
|
var GetBacklinksInput = z6.object({
|
|
747
747
|
path: z6.string().describe("Vault-relative path to the target note."),
|
|
@@ -750,10 +750,7 @@ var GetBacklinksInput = z6.object({
|
|
|
750
750
|
});
|
|
751
751
|
var EXCERPT_MAX = 200;
|
|
752
752
|
function getBacklinks(ctx2, input) {
|
|
753
|
-
|
|
754
|
-
if (!absPath.startsWith(ctx2.vaultRoot)) {
|
|
755
|
-
throw new Error(`Path outside vault: ${input.path}`);
|
|
756
|
-
}
|
|
753
|
+
resolveVaultPath(ctx2.vaultRoot, input.path);
|
|
757
754
|
const includeContext = input.includeContext ?? true;
|
|
758
755
|
const limit = input.limit ?? 50;
|
|
759
756
|
const refs = ctx2.backlinks.get(input.path) ?? [];
|
|
@@ -778,16 +775,12 @@ function getBacklinks(ctx2, input) {
|
|
|
778
775
|
}
|
|
779
776
|
|
|
780
777
|
// src/tools/get_links.ts
|
|
781
|
-
import { join as join5 } from "path";
|
|
782
778
|
import { z as z7 } from "zod";
|
|
783
779
|
var GetLinksInput = z7.object({
|
|
784
780
|
path: z7.string().describe("Vault-relative path to the note.")
|
|
785
781
|
});
|
|
786
782
|
function getLinks(ctx2, input) {
|
|
787
|
-
|
|
788
|
-
if (!absPath.startsWith(ctx2.vaultRoot)) {
|
|
789
|
-
throw new Error(`Path outside vault: ${input.path}`);
|
|
790
|
-
}
|
|
783
|
+
resolveVaultPath(ctx2.vaultRoot, input.path);
|
|
791
784
|
const note = ctx2.notes.get(input.path);
|
|
792
785
|
if (note === void 0) throw new Error(`Note not found: ${input.path}`);
|
|
793
786
|
const rawLinks = extractLinksWithLines(note.raw);
|
|
@@ -872,7 +865,7 @@ function listTags(ctx2, input) {
|
|
|
872
865
|
|
|
873
866
|
// src/tools/move_note.ts
|
|
874
867
|
import { access as access3, mkdir as mkdir3, rename as rename3 } from "fs/promises";
|
|
875
|
-
import { dirname as dirname2, join as
|
|
868
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
876
869
|
import { z as z10 } from "zod";
|
|
877
870
|
|
|
878
871
|
// src/tools/rewrite_links.ts
|
|
@@ -976,10 +969,8 @@ var MoveNoteInput = z10.object({
|
|
|
976
969
|
});
|
|
977
970
|
async function moveNote(ctx2, rawInput) {
|
|
978
971
|
const input = MoveNoteInput.parse(rawInput);
|
|
979
|
-
const absFrom =
|
|
980
|
-
const absTo =
|
|
981
|
-
if (!absFrom.startsWith(ctx2.vaultRoot)) throw new Error(`Path outside vault: ${input.from}`);
|
|
982
|
-
if (!absTo.startsWith(ctx2.vaultRoot)) throw new Error(`Path outside vault: ${input.to}`);
|
|
972
|
+
const absFrom = resolveVaultPath(ctx2.vaultRoot, input.from);
|
|
973
|
+
const absTo = resolveVaultPath(ctx2.vaultRoot, input.to);
|
|
983
974
|
assertWritable(ctx2.policy, input.from);
|
|
984
975
|
assertWritable(ctx2.policy, input.to);
|
|
985
976
|
try {
|
|
@@ -1037,7 +1028,7 @@ async function moveNote(ctx2, rawInput) {
|
|
|
1037
1028
|
);
|
|
1038
1029
|
removeNoteBacklinks(ctx2, path2);
|
|
1039
1030
|
if (count > 0) {
|
|
1040
|
-
await atomicWrite(
|
|
1031
|
+
await atomicWrite(join2(ctx2.vaultRoot, path2), content);
|
|
1041
1032
|
upsertDoc(ctx2, buildDoc(path2, content));
|
|
1042
1033
|
notesRewritten++;
|
|
1043
1034
|
linksRewritten += count;
|
|
@@ -1054,7 +1045,6 @@ async function moveNote(ctx2, rawInput) {
|
|
|
1054
1045
|
|
|
1055
1046
|
// src/tools/outline_note.ts
|
|
1056
1047
|
import { readFile as readFile3 } from "fs/promises";
|
|
1057
|
-
import { join as join7 } from "path";
|
|
1058
1048
|
|
|
1059
1049
|
// ../core/src/outline.ts
|
|
1060
1050
|
var HEADING_RE = /^(#{1,6})\s+(.+)/;
|
|
@@ -1111,10 +1101,7 @@ var OutlineNoteInput = z11.object({
|
|
|
1111
1101
|
includeSizes: z11.boolean().default(false).describe("Include per-section character length in each heading entry. Default false.")
|
|
1112
1102
|
});
|
|
1113
1103
|
async function outlineNote(ctx2, input) {
|
|
1114
|
-
const absPath =
|
|
1115
|
-
if (!absPath.startsWith(ctx2.vaultRoot)) {
|
|
1116
|
-
throw new Error(`Path outside vault: ${input.path}`);
|
|
1117
|
-
}
|
|
1104
|
+
const absPath = resolveVaultPath(ctx2.vaultRoot, input.path);
|
|
1118
1105
|
const raw = await readFile3(absPath, "utf8");
|
|
1119
1106
|
return buildOutline(raw, {
|
|
1120
1107
|
includeBlocks: input.includeBlocks,
|
|
@@ -1124,7 +1111,6 @@ async function outlineNote(ctx2, input) {
|
|
|
1124
1111
|
|
|
1125
1112
|
// src/tools/patch_frontmatter.ts
|
|
1126
1113
|
import { readFile as readFile4 } from "fs/promises";
|
|
1127
|
-
import { join as join8 } from "path";
|
|
1128
1114
|
import { parseDocument } from "yaml";
|
|
1129
1115
|
import { z as z12 } from "zod";
|
|
1130
1116
|
var PatchFrontmatterInput = z12.object({
|
|
@@ -1137,10 +1123,7 @@ var PatchFrontmatterInput = z12.object({
|
|
|
1137
1123
|
)
|
|
1138
1124
|
});
|
|
1139
1125
|
async function patchFrontmatter(ctx2, input) {
|
|
1140
|
-
const absPath =
|
|
1141
|
-
if (!absPath.startsWith(ctx2.vaultRoot)) {
|
|
1142
|
-
throw new Error(`Path outside vault: ${input.path}`);
|
|
1143
|
-
}
|
|
1126
|
+
const absPath = resolveVaultPath(ctx2.vaultRoot, input.path);
|
|
1144
1127
|
assertWritable(ctx2.policy, input.path);
|
|
1145
1128
|
const original = await readFile4(absPath, "utf8");
|
|
1146
1129
|
if (input.prevHash !== void 0) assertHashMatch(original, input.prevHash, input.path);
|
|
@@ -1203,7 +1186,6 @@ ${original}`;
|
|
|
1203
1186
|
|
|
1204
1187
|
// src/tools/patch_note.ts
|
|
1205
1188
|
import { readFile as readFile5 } from "fs/promises";
|
|
1206
|
-
import { join as join9 } from "path";
|
|
1207
1189
|
import { z as z13 } from "zod";
|
|
1208
1190
|
var PatchNoteInput = z13.object({
|
|
1209
1191
|
path: z13.string().describe("Vault-relative path to the note."),
|
|
@@ -1232,10 +1214,7 @@ function lineStart(raw, offset) {
|
|
|
1232
1214
|
return prev === -1 ? 0 : prev + 1;
|
|
1233
1215
|
}
|
|
1234
1216
|
async function patchNote(ctx2, input) {
|
|
1235
|
-
const absPath =
|
|
1236
|
-
if (!absPath.startsWith(ctx2.vaultRoot)) {
|
|
1237
|
-
throw new Error(`Path outside vault: ${input.path}`);
|
|
1238
|
-
}
|
|
1217
|
+
const absPath = resolveVaultPath(ctx2.vaultRoot, input.path);
|
|
1239
1218
|
assertWritable(ctx2.policy, input.path);
|
|
1240
1219
|
const raw = await readFile5(absPath, "utf8");
|
|
1241
1220
|
if (input.prevHash !== void 0) assertHashMatch(raw, input.prevHash, input.path);
|
|
@@ -1250,15 +1229,15 @@ async function patchNote(ctx2, input) {
|
|
|
1250
1229
|
const heading = outline.headings.find((h) => h.text.toLowerCase() === targetText.toLowerCase());
|
|
1251
1230
|
if (!heading) {
|
|
1252
1231
|
if (input.createIfMissing) {
|
|
1253
|
-
const
|
|
1232
|
+
const sep4 = raw.endsWith("\n\n") ? "" : raw.endsWith("\n") ? "\n" : "\n\n";
|
|
1254
1233
|
const contentNl = input.content.endsWith("\n") ? "" : "\n";
|
|
1255
|
-
const newContent2 = `${raw}${
|
|
1234
|
+
const newContent2 = `${raw}${sep4}## ${targetText}
|
|
1256
1235
|
|
|
1257
1236
|
${input.content}${contentNl}`;
|
|
1258
1237
|
await atomicWrite(absPath, newContent2);
|
|
1259
1238
|
await verifyFrontmatter(absPath, originalFmRegion);
|
|
1260
1239
|
updateCache(ctx2, input.path, newContent2, fm.bodyStart);
|
|
1261
|
-
const headingOffset = raw.length +
|
|
1240
|
+
const headingOffset = raw.length + sep4.length;
|
|
1262
1241
|
return {
|
|
1263
1242
|
path: input.path,
|
|
1264
1243
|
bytesWritten: Buffer.byteLength(newContent2, "utf8"),
|
|
@@ -1307,8 +1286,8 @@ ${input.content}${contentNl}`;
|
|
|
1307
1286
|
let newContent;
|
|
1308
1287
|
switch (input.operation) {
|
|
1309
1288
|
case "prepend": {
|
|
1310
|
-
const
|
|
1311
|
-
newContent = `${before}${input.content}${
|
|
1289
|
+
const sep4 = input.content.endsWith("\n") ? "" : "\n";
|
|
1290
|
+
newContent = `${before}${input.content}${sep4}${section}${after}`;
|
|
1312
1291
|
break;
|
|
1313
1292
|
}
|
|
1314
1293
|
case "append": {
|
|
@@ -1353,7 +1332,7 @@ function updateCache(ctx2, path2, newContent, bodyStart) {
|
|
|
1353
1332
|
|
|
1354
1333
|
// src/tools/periodic_note.ts
|
|
1355
1334
|
import { access as access4, mkdir as mkdir4, readFile as readFile6 } from "fs/promises";
|
|
1356
|
-
import { dirname as dirname3, join as
|
|
1335
|
+
import { dirname as dirname3, join as join3 } from "path";
|
|
1357
1336
|
import { z as z14 } from "zod";
|
|
1358
1337
|
var PERIOD = z14.enum(["daily", "weekly", "monthly", "quarterly", "yearly"]);
|
|
1359
1338
|
var DEFAULTS = {
|
|
@@ -1373,7 +1352,7 @@ async function readJson(absPath) {
|
|
|
1373
1352
|
async function resolveConfig(vaultRoot2, period) {
|
|
1374
1353
|
const defaults = DEFAULTS[period];
|
|
1375
1354
|
if (period === "daily") {
|
|
1376
|
-
const cfg2 = await readJson(
|
|
1355
|
+
const cfg2 = await readJson(join3(vaultRoot2, ".obsidian", "daily-notes.json"));
|
|
1377
1356
|
return {
|
|
1378
1357
|
folder: cfg2?.folder ?? defaults.folder,
|
|
1379
1358
|
format: cfg2?.format ?? defaults.format,
|
|
@@ -1381,7 +1360,7 @@ async function resolveConfig(vaultRoot2, period) {
|
|
|
1381
1360
|
};
|
|
1382
1361
|
}
|
|
1383
1362
|
const cfg = await readJson(
|
|
1384
|
-
|
|
1363
|
+
join3(vaultRoot2, ".obsidian", "plugins", "periodic-notes", "data.json")
|
|
1385
1364
|
);
|
|
1386
1365
|
const entry = cfg?.[period];
|
|
1387
1366
|
return {
|
|
@@ -1434,8 +1413,7 @@ async function getPeriodicNote(ctx2, rawInput) {
|
|
|
1434
1413
|
const date = input.date ? new Date(input.date) : /* @__PURE__ */ new Date();
|
|
1435
1414
|
const cfg = await resolveConfig(ctx2.vaultRoot, input.period);
|
|
1436
1415
|
const path2 = resolveNotePath(cfg, date);
|
|
1437
|
-
const abs =
|
|
1438
|
-
if (!abs.startsWith(ctx2.vaultRoot)) throw new Error(`Path outside vault: ${path2}`);
|
|
1416
|
+
const abs = resolveVaultPath(ctx2.vaultRoot, path2);
|
|
1439
1417
|
let existed = false;
|
|
1440
1418
|
try {
|
|
1441
1419
|
await access4(abs);
|
|
@@ -1447,15 +1425,13 @@ async function getPeriodicNote(ctx2, rawInput) {
|
|
|
1447
1425
|
assertWritable(ctx2.policy, path2);
|
|
1448
1426
|
let body = "";
|
|
1449
1427
|
if (cfg.template) {
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
} catch {
|
|
1458
|
-
}
|
|
1428
|
+
try {
|
|
1429
|
+
const templateAbs = resolveVaultPath(
|
|
1430
|
+
ctx2.vaultRoot,
|
|
1431
|
+
cfg.template.endsWith(".md") ? cfg.template : `${cfg.template}.md`
|
|
1432
|
+
);
|
|
1433
|
+
body = await readFile6(templateAbs, "utf8");
|
|
1434
|
+
} catch {
|
|
1459
1435
|
}
|
|
1460
1436
|
}
|
|
1461
1437
|
await mkdir4(dirname3(abs), { recursive: true });
|
|
@@ -1477,8 +1453,7 @@ async function appendPeriodicNote(ctx2, rawInput) {
|
|
|
1477
1453
|
const date = input.date ? new Date(input.date) : /* @__PURE__ */ new Date();
|
|
1478
1454
|
const cfg = await resolveConfig(ctx2.vaultRoot, input.period);
|
|
1479
1455
|
const path2 = resolveNotePath(cfg, date);
|
|
1480
|
-
const abs =
|
|
1481
|
-
if (!abs.startsWith(ctx2.vaultRoot)) throw new Error(`Path outside vault: ${path2}`);
|
|
1456
|
+
const abs = resolveVaultPath(ctx2.vaultRoot, path2);
|
|
1482
1457
|
assertWritable(ctx2.policy, path2);
|
|
1483
1458
|
let original = "";
|
|
1484
1459
|
try {
|
|
@@ -1656,7 +1631,6 @@ function queryNotes(ctx2, input) {
|
|
|
1656
1631
|
|
|
1657
1632
|
// src/tools/read_note.ts
|
|
1658
1633
|
import { readFile as readFile7 } from "fs/promises";
|
|
1659
|
-
import { join as join11 } from "path";
|
|
1660
1634
|
import { z as z16 } from "zod";
|
|
1661
1635
|
var ReadNoteInput = z16.object({
|
|
1662
1636
|
path: z16.string().describe('Vault-relative path to the note, e.g. "Daily Notes/2026-05-29.md".'),
|
|
@@ -1673,10 +1647,7 @@ var ReadNoteInput = z16.object({
|
|
|
1673
1647
|
message: "At most one of section, block, or lines may be specified."
|
|
1674
1648
|
});
|
|
1675
1649
|
async function readNote(ctx2, input) {
|
|
1676
|
-
const absPath =
|
|
1677
|
-
if (!absPath.startsWith(ctx2.vaultRoot)) {
|
|
1678
|
-
throw new Error(`Path outside vault: ${input.path}`);
|
|
1679
|
-
}
|
|
1650
|
+
const absPath = resolveVaultPath(ctx2.vaultRoot, input.path);
|
|
1680
1651
|
const raw = await readFile7(absPath, "utf8");
|
|
1681
1652
|
const noteBytes = Buffer.byteLength(raw, "utf8");
|
|
1682
1653
|
const hash = contentHash(raw);
|
|
@@ -1766,7 +1737,6 @@ async function readNote(ctx2, input) {
|
|
|
1766
1737
|
|
|
1767
1738
|
// src/tools/replace_in_note.ts
|
|
1768
1739
|
import { readFile as readFile8 } from "fs/promises";
|
|
1769
|
-
import { join as join12 } from "path";
|
|
1770
1740
|
import { z as z17 } from "zod";
|
|
1771
1741
|
var ReplaceInNoteInput = z17.object({
|
|
1772
1742
|
path: z17.string().describe("Vault-relative path to the note."),
|
|
@@ -1791,10 +1761,7 @@ function buildRegex(find, opts) {
|
|
|
1791
1761
|
return new RegExp(pattern, flags);
|
|
1792
1762
|
}
|
|
1793
1763
|
async function replaceInNote(ctx2, input) {
|
|
1794
|
-
const absPath =
|
|
1795
|
-
if (!absPath.startsWith(ctx2.vaultRoot)) {
|
|
1796
|
-
throw new Error(`Path outside vault: ${input.path}`);
|
|
1797
|
-
}
|
|
1764
|
+
const absPath = resolveVaultPath(ctx2.vaultRoot, input.path);
|
|
1798
1765
|
assertWritable(ctx2.policy, input.path);
|
|
1799
1766
|
let re;
|
|
1800
1767
|
try {
|
|
@@ -2076,7 +2043,7 @@ import { basename as basename3, extname as extname4 } from "path";
|
|
|
2076
2043
|
|
|
2077
2044
|
// ../core/src/walk.ts
|
|
2078
2045
|
import { stat } from "fs/promises";
|
|
2079
|
-
import { extname as extname3, join as
|
|
2046
|
+
import { extname as extname3, join as join4, relative, sep as sep2 } from "path";
|
|
2080
2047
|
import fg from "fast-glob";
|
|
2081
2048
|
var IMAGE_EXTS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg", ".bmp", ".heic"]);
|
|
2082
2049
|
var VIDEO_EXTS = /* @__PURE__ */ new Set([".mp4", ".mov", ".webm", ".mkv", ".avi"]);
|
|
@@ -2118,7 +2085,7 @@ async function walkVault(vaultRoot2) {
|
|
|
2118
2085
|
const stats = typeof m === "string" ? await stat(absPath) : m.stats;
|
|
2119
2086
|
if (!stats) continue;
|
|
2120
2087
|
const relPath = relative(vaultRoot2, absPath);
|
|
2121
|
-
const topDir = relPath.split(
|
|
2088
|
+
const topDir = relPath.split(sep2)[0] ?? "";
|
|
2122
2089
|
entries.push({
|
|
2123
2090
|
absPath,
|
|
2124
2091
|
relPath,
|
|
@@ -2207,7 +2174,7 @@ function buildBacklinks(notes2) {
|
|
|
2207
2174
|
// src/init.ts
|
|
2208
2175
|
import { execFileSync } from "child_process";
|
|
2209
2176
|
import { access as access5, copyFile, mkdir as mkdir5, readdir, readFile as readFile10, stat as stat2, writeFile as writeFile2 } from "fs/promises";
|
|
2210
|
-
import path, { dirname as dirname4, join as
|
|
2177
|
+
import path, { dirname as dirname4, join as join5 } from "path";
|
|
2211
2178
|
function parseInitArgs(argv) {
|
|
2212
2179
|
const opts = { write: false, client: "desktop" };
|
|
2213
2180
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -2250,7 +2217,7 @@ function cursorConfigPath(platform, env) {
|
|
|
2250
2217
|
return j(env.home ?? "", ".cursor", "mcp.json");
|
|
2251
2218
|
}
|
|
2252
2219
|
function vscodeMcpConfigPath(cwd) {
|
|
2253
|
-
return
|
|
2220
|
+
return join5(cwd, ".vscode", "mcp.json");
|
|
2254
2221
|
}
|
|
2255
2222
|
function obsidianRegistryPath(platform, env) {
|
|
2256
2223
|
if (platform === "win32") {
|
|
@@ -2312,7 +2279,7 @@ async function validateVault(vaultPath) {
|
|
|
2312
2279
|
return { ok: false, error: `Path does not exist: ${vaultPath}` };
|
|
2313
2280
|
}
|
|
2314
2281
|
try {
|
|
2315
|
-
await access5(
|
|
2282
|
+
await access5(join5(vaultPath, ".obsidian"));
|
|
2316
2283
|
} catch {
|
|
2317
2284
|
return {
|
|
2318
2285
|
ok: false,
|
|
@@ -2332,7 +2299,7 @@ async function countMarkdown(root) {
|
|
|
2332
2299
|
}
|
|
2333
2300
|
for (const e of entries) {
|
|
2334
2301
|
if (e.name.startsWith(".")) continue;
|
|
2335
|
-
const full =
|
|
2302
|
+
const full = join5(dir, e.name);
|
|
2336
2303
|
if (e.isDirectory()) await walk(full);
|
|
2337
2304
|
else if (e.name.endsWith(".md")) count++;
|
|
2338
2305
|
}
|
|
@@ -3052,7 +3019,7 @@ function visibleTools(policy2) {
|
|
|
3052
3019
|
// src/watcher.ts
|
|
3053
3020
|
import { realpathSync } from "fs";
|
|
3054
3021
|
import { readFile as readFile11 } from "fs/promises";
|
|
3055
|
-
import { join as
|
|
3022
|
+
import { join as join6, relative as relative2, sep as sep3 } from "path";
|
|
3056
3023
|
import chokidar from "chokidar";
|
|
3057
3024
|
function startWatcher(ctx2, log2, opts) {
|
|
3058
3025
|
const usePolling = opts?.usePolling ?? process.env.SEEKSTONE_WATCH_POLL === "1";
|
|
@@ -3061,10 +3028,10 @@ function startWatcher(ctx2, log2, opts) {
|
|
|
3061
3028
|
vaultRoot2 = realpathSync(ctx2.vaultRoot);
|
|
3062
3029
|
} catch {
|
|
3063
3030
|
}
|
|
3064
|
-
const toRel = (abs) => relative2(vaultRoot2, abs).split(
|
|
3031
|
+
const toRel = (abs) => relative2(vaultRoot2, abs).split(sep3).join("/");
|
|
3065
3032
|
async function upsert(relPath, op) {
|
|
3066
3033
|
try {
|
|
3067
|
-
const raw = await readFile11(
|
|
3034
|
+
const raw = await readFile11(join6(vaultRoot2, relPath), "utf8");
|
|
3068
3035
|
removeNoteBacklinks(ctx2, relPath);
|
|
3069
3036
|
upsertDoc(ctx2, buildDoc(relPath, raw));
|
|
3070
3037
|
addNoteBacklinks(ctx2, relPath, raw);
|
|
@@ -3096,7 +3063,7 @@ function startWatcher(ctx2, log2, opts) {
|
|
|
3096
3063
|
ignored: (p) => {
|
|
3097
3064
|
const rel = relative2(vaultRoot2, p);
|
|
3098
3065
|
if (!rel || rel === ".") return false;
|
|
3099
|
-
return rel.split(
|
|
3066
|
+
return rel.split(sep3).filter(Boolean).some((seg) => seg.startsWith(".") && seg !== "..");
|
|
3100
3067
|
}
|
|
3101
3068
|
});
|
|
3102
3069
|
watcher2.on("add", onUpsert("add")).on("change", onUpsert("change")).on("unlink", (abs) => {
|
|
@@ -3104,9 +3071,9 @@ function startWatcher(ctx2, log2, opts) {
|
|
|
3104
3071
|
opts?.onRawEvent?.("unlink", abs, relPath);
|
|
3105
3072
|
if (relPath.endsWith(".md")) remove(relPath);
|
|
3106
3073
|
});
|
|
3107
|
-
const ready = new Promise((
|
|
3074
|
+
const ready = new Promise((resolve3) => {
|
|
3108
3075
|
watcher2.once("ready", () => {
|
|
3109
|
-
|
|
3076
|
+
resolve3();
|
|
3110
3077
|
});
|
|
3111
3078
|
});
|
|
3112
3079
|
return {
|
|
@@ -3116,7 +3083,7 @@ function startWatcher(ctx2, log2, opts) {
|
|
|
3116
3083
|
}
|
|
3117
3084
|
|
|
3118
3085
|
// src/index.ts
|
|
3119
|
-
var VERSION = true ? "0.12.
|
|
3086
|
+
var VERSION = true ? "0.12.2" : "0.0.0-dev";
|
|
3120
3087
|
var intent = parseCliIntent(process.argv.slice(2));
|
|
3121
3088
|
if (intent === "version") {
|
|
3122
3089
|
process.stdout.write(`${VERSION}
|
|
@@ -3146,11 +3113,12 @@ if (intent === "init") {
|
|
|
3146
3113
|
}
|
|
3147
3114
|
var log = createLogger();
|
|
3148
3115
|
installProcessGuards(log);
|
|
3149
|
-
var
|
|
3150
|
-
if (!
|
|
3116
|
+
var rawVaultRoot = process.env.SEEKSTONE_VAULT;
|
|
3117
|
+
if (!rawVaultRoot) {
|
|
3151
3118
|
log.error("SEEKSTONE_VAULT env var is required");
|
|
3152
3119
|
process.exit(1);
|
|
3153
3120
|
}
|
|
3121
|
+
var vaultRoot = resolve2(rawVaultRoot);
|
|
3154
3122
|
var policy = parseWritePolicy(process.env);
|
|
3155
3123
|
if (policy.readOnly) log.info("read-only mode", { env: "SEEKSTONE_READ_ONLY" });
|
|
3156
3124
|
if (policy.writeGlobs) log.info("write paths scoped", { globs: policy.writeGlobs });
|