seekstone 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +275 -53
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -746,30 +746,181 @@ function updateCache(ctx2, path2, newContent, bodyStart) {
|
|
|
746
746
|
}
|
|
747
747
|
}
|
|
748
748
|
|
|
749
|
-
// src/tools/
|
|
750
|
-
import { readFile as readFile5 } from "fs/promises";
|
|
751
|
-
import { join as join10 } from "path";
|
|
749
|
+
// src/tools/periodic_note.ts
|
|
750
|
+
import { access as access3, mkdir as mkdir3, readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
|
|
751
|
+
import { dirname as dirname3, join as join10 } from "path";
|
|
752
752
|
import { z as z12 } from "zod";
|
|
753
|
-
var
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
753
|
+
var PERIOD = z12.enum(["daily", "weekly", "monthly", "quarterly", "yearly"]);
|
|
754
|
+
var DEFAULTS = {
|
|
755
|
+
daily: { folder: "", format: "YYYY-MM-DD", template: "" },
|
|
756
|
+
weekly: { folder: "", format: "gggg-[W]ww", template: "" },
|
|
757
|
+
monthly: { folder: "", format: "YYYY-MM", template: "" },
|
|
758
|
+
quarterly: { folder: "", format: "YYYY-[Q]Q", template: "" },
|
|
759
|
+
yearly: { folder: "", format: "YYYY", template: "" }
|
|
760
|
+
};
|
|
761
|
+
async function readJson(absPath) {
|
|
762
|
+
try {
|
|
763
|
+
return JSON.parse(await readFile5(absPath, "utf8"));
|
|
764
|
+
} catch {
|
|
765
|
+
return null;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
async function resolveConfig(vaultRoot2, period) {
|
|
769
|
+
const defaults = DEFAULTS[period];
|
|
770
|
+
if (period === "daily") {
|
|
771
|
+
const cfg2 = await readJson(join10(vaultRoot2, ".obsidian", "daily-notes.json"));
|
|
772
|
+
return {
|
|
773
|
+
folder: cfg2?.folder ?? defaults.folder,
|
|
774
|
+
format: cfg2?.format ?? defaults.format,
|
|
775
|
+
template: cfg2?.template ?? defaults.template
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
const cfg = await readJson(
|
|
779
|
+
join10(vaultRoot2, ".obsidian", "plugins", "periodic-notes", "data.json")
|
|
780
|
+
);
|
|
781
|
+
const entry = cfg?.[period];
|
|
782
|
+
return {
|
|
783
|
+
folder: entry?.folder ?? defaults.folder,
|
|
784
|
+
format: entry?.format ?? defaults.format,
|
|
785
|
+
template: entry?.template ?? defaults.template
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
function isoWeekAndYear(date) {
|
|
789
|
+
const d = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
|
|
790
|
+
const day = d.getUTCDay() || 7;
|
|
791
|
+
d.setUTCDate(d.getUTCDate() + 4 - day);
|
|
792
|
+
const jan1 = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
|
|
793
|
+
const week = Math.ceil(((d.getTime() - jan1.getTime()) / 864e5 + 1) / 7);
|
|
794
|
+
return { week, year: d.getUTCFullYear() };
|
|
795
|
+
}
|
|
796
|
+
function formatMomentDate(date, format) {
|
|
797
|
+
const year = date.getUTCFullYear();
|
|
798
|
+
const month = date.getUTCMonth() + 1;
|
|
799
|
+
const day = date.getUTCDate();
|
|
800
|
+
const quarter = Math.ceil(month / 3);
|
|
801
|
+
const { week: isoWeek, year: isoYear } = isoWeekAndYear(date);
|
|
802
|
+
const PLACEHOLDER = "\0";
|
|
803
|
+
const literals = [];
|
|
804
|
+
let s = format.replace(/\[([^\]]*)\]/g, (_, lit) => {
|
|
805
|
+
literals.push(lit);
|
|
806
|
+
return `${PLACEHOLDER}${literals.length - 1}${PLACEHOLDER}`;
|
|
807
|
+
});
|
|
808
|
+
s = s.replace(/gggg/g, String(isoYear).padStart(4, "0")).replace(/YYYY/g, String(year).padStart(4, "0")).replace(/YY/g, String(year).slice(-2)).replace(/MM/g, String(month).padStart(2, "0")).replace(/M/g, String(month)).replace(/DD/g, String(day).padStart(2, "0")).replace(/D/g, String(day)).replace(/ww/g, String(isoWeek).padStart(2, "0")).replace(/W/g, String(isoWeek)).replace(/Q/g, String(quarter));
|
|
809
|
+
return s.replace(
|
|
810
|
+
new RegExp(`${PLACEHOLDER}(\\d+)${PLACEHOLDER}`, "g"),
|
|
811
|
+
(_, i) => literals[+i] ?? ""
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
function resolveNotePath(cfg, date) {
|
|
815
|
+
const filename = `${formatMomentDate(date, cfg.format)}.md`;
|
|
816
|
+
return cfg.folder ? `${cfg.folder}/${filename}` : filename;
|
|
817
|
+
}
|
|
818
|
+
var GetPeriodicNoteInput = z12.object({
|
|
819
|
+
period: PERIOD.default("daily").describe(
|
|
820
|
+
"Period type: daily (default), weekly, monthly, quarterly, or yearly."
|
|
821
|
+
),
|
|
822
|
+
date: z12.string().optional().describe("ISO date string (YYYY-MM-DD). Defaults to today when omitted."),
|
|
823
|
+
createIfMissing: z12.boolean().default(false).describe(
|
|
824
|
+
"Create the note from the configured template if it does not exist. Default false \u2014 reports missing without creating."
|
|
825
|
+
)
|
|
826
|
+
});
|
|
827
|
+
async function getPeriodicNote(ctx2, rawInput) {
|
|
828
|
+
const input = GetPeriodicNoteInput.parse(rawInput);
|
|
829
|
+
const date = input.date ? new Date(input.date) : /* @__PURE__ */ new Date();
|
|
830
|
+
const cfg = await resolveConfig(ctx2.vaultRoot, input.period);
|
|
831
|
+
const path2 = resolveNotePath(cfg, date);
|
|
832
|
+
const abs = join10(ctx2.vaultRoot, path2);
|
|
833
|
+
if (!abs.startsWith(ctx2.vaultRoot)) throw new Error(`Path outside vault: ${path2}`);
|
|
834
|
+
let existed = false;
|
|
835
|
+
try {
|
|
836
|
+
await access3(abs);
|
|
837
|
+
existed = true;
|
|
838
|
+
} catch {
|
|
839
|
+
}
|
|
840
|
+
if (existed) return { path: path2, existed: true, created: false };
|
|
841
|
+
if (!input.createIfMissing) return { path: path2, existed: false, created: false };
|
|
842
|
+
let body = "";
|
|
843
|
+
if (cfg.template) {
|
|
844
|
+
const templateAbs = join10(
|
|
845
|
+
ctx2.vaultRoot,
|
|
846
|
+
cfg.template.endsWith(".md") ? cfg.template : `${cfg.template}.md`
|
|
847
|
+
);
|
|
848
|
+
if (templateAbs.startsWith(ctx2.vaultRoot)) {
|
|
849
|
+
try {
|
|
850
|
+
body = await readFile5(templateAbs, "utf8");
|
|
851
|
+
} catch {
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
await mkdir3(dirname3(abs), { recursive: true });
|
|
856
|
+
await writeFile5(abs, body, "utf8");
|
|
857
|
+
upsertDoc(ctx2, buildDoc(path2, body));
|
|
858
|
+
return { path: path2, existed: false, created: true };
|
|
859
|
+
}
|
|
860
|
+
var AppendPeriodicNoteInput = z12.object({
|
|
861
|
+
period: PERIOD.default("daily").describe("Period type. Default: daily."),
|
|
862
|
+
date: z12.string().optional().describe("ISO date string (YYYY-MM-DD). Defaults to today."),
|
|
863
|
+
content: z12.string().min(1).describe("Text to append to the note body. Separated from existing content by a blank line."),
|
|
864
|
+
createIfMissing: z12.boolean().default(true).describe("Create the note if it does not exist before appending. Default true.")
|
|
865
|
+
});
|
|
866
|
+
async function appendPeriodicNote(ctx2, rawInput) {
|
|
867
|
+
const input = AppendPeriodicNoteInput.parse(rawInput);
|
|
868
|
+
const date = input.date ? new Date(input.date) : /* @__PURE__ */ new Date();
|
|
869
|
+
const cfg = await resolveConfig(ctx2.vaultRoot, input.period);
|
|
870
|
+
const path2 = resolveNotePath(cfg, date);
|
|
871
|
+
const abs = join10(ctx2.vaultRoot, path2);
|
|
872
|
+
if (!abs.startsWith(ctx2.vaultRoot)) throw new Error(`Path outside vault: ${path2}`);
|
|
873
|
+
let original = "";
|
|
874
|
+
try {
|
|
875
|
+
original = await readFile5(abs, "utf8");
|
|
876
|
+
} catch (err) {
|
|
877
|
+
if (err.code !== "ENOENT") throw err;
|
|
878
|
+
if (!input.createIfMissing) throw new Error(`Periodic note not found: ${path2}`);
|
|
879
|
+
await mkdir3(dirname3(abs), { recursive: true });
|
|
880
|
+
await writeFile5(abs, input.content, "utf8");
|
|
881
|
+
upsertDoc(ctx2, buildDoc(path2, input.content));
|
|
882
|
+
return { path: path2, bytesWritten: Buffer.byteLength(input.content, "utf8") };
|
|
883
|
+
}
|
|
884
|
+
const fm = parseFrontmatter(original);
|
|
885
|
+
const body = fm.body.endsWith("\n") ? fm.body : `${fm.body}
|
|
886
|
+
`;
|
|
887
|
+
const separator = body.endsWith("\n\n") ? "" : "\n";
|
|
888
|
+
const newBody = `${body}${separator}${input.content}`;
|
|
889
|
+
const header = original.slice(0, fm.bodyStart);
|
|
890
|
+
const newRaw = `${header}${newBody}`;
|
|
891
|
+
await writeFile5(abs, newRaw, "utf8");
|
|
892
|
+
const cached = ctx2.notes.get(path2);
|
|
893
|
+
if (cached) {
|
|
894
|
+
cached.body = newBody;
|
|
895
|
+
cached.raw = newRaw;
|
|
896
|
+
}
|
|
897
|
+
return { path: path2, bytesWritten: Buffer.byteLength(newRaw, "utf8") };
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
// src/tools/read_note.ts
|
|
901
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
902
|
+
import { join as join11 } from "path";
|
|
903
|
+
import { z as z13 } from "zod";
|
|
904
|
+
var ReadNoteInput = z13.object({
|
|
905
|
+
path: z13.string().describe('Vault-relative path to the note, e.g. "Daily Notes/2026-05-29.md".'),
|
|
906
|
+
section: z13.string().optional().describe("Return only this heading section (heading text, # prefix optional)."),
|
|
907
|
+
block: z13.string().optional().describe("Return only the block anchored by this id (^ prefix optional)."),
|
|
908
|
+
lines: z13.object({
|
|
909
|
+
from: z13.number().int().min(1).describe("First line (1-indexed, inclusive)."),
|
|
910
|
+
to: z13.number().int().min(1).optional().describe("Last line (1-indexed, inclusive). Defaults to end of file.")
|
|
760
911
|
}).optional().describe("Return only this line range (1-indexed, inclusive)."),
|
|
761
|
-
includeFrontmatter:
|
|
912
|
+
includeFrontmatter: z13.boolean().optional().describe(
|
|
762
913
|
"Include frontmatter in span results. Default false for section/block spans. No effect on lines or whole-note reads."
|
|
763
914
|
)
|
|
764
915
|
}).refine((v) => [v.section, v.block, v.lines].filter((x) => x !== void 0).length <= 1, {
|
|
765
916
|
message: "At most one of section, block, or lines may be specified."
|
|
766
917
|
});
|
|
767
918
|
async function readNote(ctx2, input) {
|
|
768
|
-
const absPath =
|
|
919
|
+
const absPath = join11(ctx2.vaultRoot, input.path);
|
|
769
920
|
if (!absPath.startsWith(ctx2.vaultRoot)) {
|
|
770
921
|
throw new Error(`Path outside vault: ${input.path}`);
|
|
771
922
|
}
|
|
772
|
-
const raw = await
|
|
923
|
+
const raw = await readFile6(absPath, "utf8");
|
|
773
924
|
const noteBytes = Buffer.byteLength(raw, "utf8");
|
|
774
925
|
if (input.section === void 0 && input.block === void 0 && input.lines === void 0) {
|
|
775
926
|
return { path: input.path, content: raw, bytesReturned: noteBytes, noteBytes };
|
|
@@ -849,18 +1000,18 @@ async function readNote(ctx2, input) {
|
|
|
849
1000
|
}
|
|
850
1001
|
|
|
851
1002
|
// src/tools/replace_in_note.ts
|
|
852
|
-
import { readFile as
|
|
853
|
-
import { join as
|
|
854
|
-
import { z as
|
|
855
|
-
var ReplaceInNoteInput =
|
|
856
|
-
path:
|
|
857
|
-
find:
|
|
858
|
-
replace:
|
|
859
|
-
regex:
|
|
860
|
-
caseSensitive:
|
|
861
|
-
wholeWord:
|
|
862
|
-
limit:
|
|
863
|
-
dryRun:
|
|
1003
|
+
import { readFile as readFile7, rename as rename3, writeFile as writeFile6 } from "fs/promises";
|
|
1004
|
+
import { join as join12 } from "path";
|
|
1005
|
+
import { z as z14 } from "zod";
|
|
1006
|
+
var ReplaceInNoteInput = z14.object({
|
|
1007
|
+
path: z14.string().describe("Vault-relative path to the note."),
|
|
1008
|
+
find: z14.string().min(1).describe("Text or pattern to find."),
|
|
1009
|
+
replace: z14.string().describe("Replacement text. Supports $1, $2, \u2026 backreferences in regex mode."),
|
|
1010
|
+
regex: z14.boolean().default(false).describe("Treat find as a regular expression."),
|
|
1011
|
+
caseSensitive: z14.boolean().default(false).describe("Case-sensitive matching."),
|
|
1012
|
+
wholeWord: z14.boolean().default(false).describe("Match whole words only (\\b word-boundary)."),
|
|
1013
|
+
limit: z14.number().int().min(1).optional().describe("Maximum number of replacements. Omit to replace all."),
|
|
1014
|
+
dryRun: z14.boolean().default(false).describe("If true, report matches without writing anything.")
|
|
864
1015
|
});
|
|
865
1016
|
function escapeRegex(s) {
|
|
866
1017
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -873,11 +1024,11 @@ function buildRegex(find, opts) {
|
|
|
873
1024
|
}
|
|
874
1025
|
async function atomicWrite2(absPath, content) {
|
|
875
1026
|
const tmpPath = `${absPath}.seekstone-replace-tmp`;
|
|
876
|
-
await
|
|
1027
|
+
await writeFile6(tmpPath, content, "utf8");
|
|
877
1028
|
await rename3(tmpPath, absPath);
|
|
878
1029
|
}
|
|
879
1030
|
async function replaceInNote(ctx2, input) {
|
|
880
|
-
const absPath =
|
|
1031
|
+
const absPath = join12(ctx2.vaultRoot, input.path);
|
|
881
1032
|
if (!absPath.startsWith(ctx2.vaultRoot)) {
|
|
882
1033
|
throw new Error(`Path outside vault: ${input.path}`);
|
|
883
1034
|
}
|
|
@@ -891,7 +1042,7 @@ async function replaceInNote(ctx2, input) {
|
|
|
891
1042
|
} catch (err) {
|
|
892
1043
|
throw new Error(`Invalid regex: ${err instanceof Error ? err.message : String(err)}`);
|
|
893
1044
|
}
|
|
894
|
-
const raw = await
|
|
1045
|
+
const raw = await readFile7(absPath, "utf8");
|
|
895
1046
|
const fm = parseFrontmatter(raw);
|
|
896
1047
|
const originalFmRegion = raw.slice(0, fm.bodyStart);
|
|
897
1048
|
const body = raw.slice(fm.bodyStart);
|
|
@@ -922,7 +1073,7 @@ async function replaceInNote(ctx2, input) {
|
|
|
922
1073
|
newBody += body.slice(lastIndex);
|
|
923
1074
|
const newContent = `${originalFmRegion}${newBody}`;
|
|
924
1075
|
await atomicWrite2(absPath, newContent);
|
|
925
|
-
const written = await
|
|
1076
|
+
const written = await readFile7(absPath, "utf8");
|
|
926
1077
|
if (written.slice(0, originalFmRegion.length) !== originalFmRegion) {
|
|
927
1078
|
throw new Error("Write-safety violation: frontmatter region changed unexpectedly");
|
|
928
1079
|
}
|
|
@@ -942,7 +1093,7 @@ async function replaceInNote(ctx2, input) {
|
|
|
942
1093
|
}
|
|
943
1094
|
|
|
944
1095
|
// src/tools/search.ts
|
|
945
|
-
import { z as
|
|
1096
|
+
import { z as z15 } from "zod";
|
|
946
1097
|
|
|
947
1098
|
// src/index/excerpt.ts
|
|
948
1099
|
function extractExcerpt(text, terms, maxLen = 200) {
|
|
@@ -969,11 +1120,11 @@ function truncate(s, max) {
|
|
|
969
1120
|
}
|
|
970
1121
|
|
|
971
1122
|
// src/tools/search.ts
|
|
972
|
-
var SearchInput =
|
|
973
|
-
query:
|
|
974
|
-
limit:
|
|
975
|
-
folder:
|
|
976
|
-
tag:
|
|
1123
|
+
var SearchInput = z15.object({
|
|
1124
|
+
query: z15.string().min(1).describe("Search query. Supports fuzzy matching and prefix search."),
|
|
1125
|
+
limit: z15.number().int().min(1).max(50).default(10).describe("Maximum number of results to return."),
|
|
1126
|
+
folder: z15.string().optional().describe("Restrict results to notes under this vault-relative folder prefix."),
|
|
1127
|
+
tag: z15.string().optional().describe("Restrict results to notes containing this tag.")
|
|
977
1128
|
});
|
|
978
1129
|
function search(ctx2, input) {
|
|
979
1130
|
const results = ctx2.index.search(input.query, {
|
|
@@ -1017,7 +1168,9 @@ var META_KEYS = [
|
|
|
1017
1168
|
"overwrite",
|
|
1018
1169
|
"pattern",
|
|
1019
1170
|
"minCount",
|
|
1020
|
-
"sort"
|
|
1171
|
+
"sort",
|
|
1172
|
+
"period",
|
|
1173
|
+
"date"
|
|
1021
1174
|
];
|
|
1022
1175
|
function safeMeta(args) {
|
|
1023
1176
|
if (typeof args !== "object" || args === null) return {};
|
|
@@ -1134,18 +1287,35 @@ async function run(ctx2, name, args) {
|
|
|
1134
1287
|
const result = await replaceInNote(ctx2, input);
|
|
1135
1288
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
1136
1289
|
}
|
|
1290
|
+
case "get_periodic_note": {
|
|
1291
|
+
const input = GetPeriodicNoteInput.parse(args);
|
|
1292
|
+
const result = await getPeriodicNote(ctx2, input);
|
|
1293
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
1294
|
+
}
|
|
1295
|
+
case "append_periodic_note": {
|
|
1296
|
+
const input = AppendPeriodicNoteInput.parse(args);
|
|
1297
|
+
const result = await appendPeriodicNote(ctx2, input);
|
|
1298
|
+
return {
|
|
1299
|
+
content: [
|
|
1300
|
+
{
|
|
1301
|
+
type: "text",
|
|
1302
|
+
text: `Appended ${result.bytesWritten} bytes to ${result.path}.`
|
|
1303
|
+
}
|
|
1304
|
+
]
|
|
1305
|
+
};
|
|
1306
|
+
}
|
|
1137
1307
|
default:
|
|
1138
1308
|
throw new Error(`Unknown tool: ${name}`);
|
|
1139
1309
|
}
|
|
1140
1310
|
}
|
|
1141
1311
|
|
|
1142
1312
|
// src/index/build.ts
|
|
1143
|
-
import { readFile as
|
|
1313
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
1144
1314
|
import { basename as basename2, extname as extname3 } from "path";
|
|
1145
1315
|
|
|
1146
1316
|
// ../core/src/walk.ts
|
|
1147
1317
|
import { stat } from "fs/promises";
|
|
1148
|
-
import { extname as extname2, join as
|
|
1318
|
+
import { extname as extname2, join as join13, relative, sep } from "path";
|
|
1149
1319
|
import fg from "fast-glob";
|
|
1150
1320
|
var IMAGE_EXTS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg", ".bmp", ".heic"]);
|
|
1151
1321
|
var VIDEO_EXTS = /* @__PURE__ */ new Set([".mp4", ".mov", ".webm", ".mkv", ".avi"]);
|
|
@@ -1222,7 +1392,7 @@ async function buildIndex(vaultRoot2) {
|
|
|
1222
1392
|
await Promise.all(
|
|
1223
1393
|
noteEntries.map(async (entry) => {
|
|
1224
1394
|
try {
|
|
1225
|
-
const raw = await
|
|
1395
|
+
const raw = await readFile8(entry.absPath, "utf8");
|
|
1226
1396
|
const fm = parseFrontmatter(raw);
|
|
1227
1397
|
const inlineTags = extractInlineTags(fm.body);
|
|
1228
1398
|
const fmTags = frontmatterTags(fm.data);
|
|
@@ -1274,8 +1444,8 @@ function buildBacklinks(notes2) {
|
|
|
1274
1444
|
|
|
1275
1445
|
// src/init.ts
|
|
1276
1446
|
import { execFileSync } from "child_process";
|
|
1277
|
-
import { access as
|
|
1278
|
-
import path, { dirname as
|
|
1447
|
+
import { access as access4, copyFile, mkdir as mkdir4, readFile as readFile9, readdir, stat as stat2, writeFile as writeFile7 } from "fs/promises";
|
|
1448
|
+
import path, { dirname as dirname4, join as join14 } from "path";
|
|
1279
1449
|
function parseInitArgs(argv) {
|
|
1280
1450
|
const opts = { write: false, client: "desktop" };
|
|
1281
1451
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -1363,7 +1533,7 @@ async function validateVault(vaultPath) {
|
|
|
1363
1533
|
return { ok: false, error: `Path does not exist: ${vaultPath}` };
|
|
1364
1534
|
}
|
|
1365
1535
|
try {
|
|
1366
|
-
await
|
|
1536
|
+
await access4(join14(vaultPath, ".obsidian"));
|
|
1367
1537
|
} catch {
|
|
1368
1538
|
return {
|
|
1369
1539
|
ok: false,
|
|
@@ -1383,7 +1553,7 @@ async function countMarkdown(root) {
|
|
|
1383
1553
|
}
|
|
1384
1554
|
for (const e of entries) {
|
|
1385
1555
|
if (e.name.startsWith(".")) continue;
|
|
1386
|
-
const full =
|
|
1556
|
+
const full = join14(dir, e.name);
|
|
1387
1557
|
if (e.isDirectory()) await walk(full);
|
|
1388
1558
|
else if (e.name.endsWith(".md")) count++;
|
|
1389
1559
|
}
|
|
@@ -1392,7 +1562,7 @@ async function countMarkdown(root) {
|
|
|
1392
1562
|
return count;
|
|
1393
1563
|
}
|
|
1394
1564
|
async function runInit(opts, deps) {
|
|
1395
|
-
const rf = deps.readFile ??
|
|
1565
|
+
const rf = deps.readFile ?? readFile9;
|
|
1396
1566
|
let vaultPath = opts.vault ?? deps.env.SEEKSTONE_VAULT;
|
|
1397
1567
|
if (!vaultPath) {
|
|
1398
1568
|
const registryPath = obsidianRegistryPath(deps.platform, {
|
|
@@ -1521,12 +1691,12 @@ async function runInit(opts, deps) {
|
|
|
1521
1691
|
}
|
|
1522
1692
|
const merged = mergeSeekstoneConfig(existing, vaultPath);
|
|
1523
1693
|
let backupPath;
|
|
1524
|
-
await
|
|
1694
|
+
await mkdir4(dirname4(configPath), { recursive: true });
|
|
1525
1695
|
if (existingRaw !== null) {
|
|
1526
1696
|
backupPath = `${configPath}.backup-${deps.timestamp}`;
|
|
1527
1697
|
await copyFile(configPath, backupPath);
|
|
1528
1698
|
}
|
|
1529
|
-
await
|
|
1699
|
+
await writeFile7(configPath, `${JSON.stringify(merged, null, 2)}
|
|
1530
1700
|
`, "utf8");
|
|
1531
1701
|
out.push(
|
|
1532
1702
|
`\u2713 Patched ${configPath}`,
|
|
@@ -1598,7 +1768,7 @@ function createLogger(opts = {}) {
|
|
|
1598
1768
|
);
|
|
1599
1769
|
}
|
|
1600
1770
|
}
|
|
1601
|
-
function
|
|
1771
|
+
function writeFile8(record) {
|
|
1602
1772
|
if (!fileOk || filePath === void 0) return;
|
|
1603
1773
|
const line = `${JSON.stringify(record)}
|
|
1604
1774
|
`;
|
|
@@ -1620,7 +1790,7 @@ function createLogger(opts = {}) {
|
|
|
1620
1790
|
const f = fields ?? {};
|
|
1621
1791
|
stderr(`${lvl.toUpperCase().padEnd(5)} ${ts} ${msg}${fmtFields(f)}
|
|
1622
1792
|
`);
|
|
1623
|
-
|
|
1793
|
+
writeFile8({ t: ts, level: lvl, msg, ...f });
|
|
1624
1794
|
}
|
|
1625
1795
|
return {
|
|
1626
1796
|
level,
|
|
@@ -1632,8 +1802,8 @@ function createLogger(opts = {}) {
|
|
|
1632
1802
|
}
|
|
1633
1803
|
|
|
1634
1804
|
// src/watcher.ts
|
|
1635
|
-
import { readFile as
|
|
1636
|
-
import { join as
|
|
1805
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
1806
|
+
import { join as join15, relative as relative2, sep as sep2 } from "path";
|
|
1637
1807
|
import chokidar from "chokidar";
|
|
1638
1808
|
function removeNoteBacklinks(ctx2, relPath) {
|
|
1639
1809
|
const oldDoc = ctx2.notes.get(relPath);
|
|
@@ -1670,7 +1840,7 @@ function startWatcher(ctx2, log2, opts) {
|
|
|
1670
1840
|
const toRel = (abs) => relative2(ctx2.vaultRoot, abs).split(sep2).join("/");
|
|
1671
1841
|
async function upsert(relPath, op) {
|
|
1672
1842
|
try {
|
|
1673
|
-
const raw = await
|
|
1843
|
+
const raw = await readFile10(join15(ctx2.vaultRoot, relPath), "utf8");
|
|
1674
1844
|
removeNoteBacklinks(ctx2, relPath);
|
|
1675
1845
|
upsertDoc(ctx2, buildDoc(relPath, raw));
|
|
1676
1846
|
addNoteBacklinks(ctx2, relPath, raw);
|
|
@@ -1688,6 +1858,7 @@ function startWatcher(ctx2, log2, opts) {
|
|
|
1688
1858
|
}
|
|
1689
1859
|
const onUpsert = (op) => (abs) => {
|
|
1690
1860
|
const relPath = toRel(abs);
|
|
1861
|
+
opts?.onRawEvent?.(op, abs, relPath);
|
|
1691
1862
|
if (relPath.endsWith(".md")) void upsert(relPath, op);
|
|
1692
1863
|
};
|
|
1693
1864
|
const watcher2 = chokidar.watch(ctx2.vaultRoot, {
|
|
@@ -1706,6 +1877,7 @@ function startWatcher(ctx2, log2, opts) {
|
|
|
1706
1877
|
});
|
|
1707
1878
|
watcher2.on("add", onUpsert("add")).on("change", onUpsert("change")).on("unlink", (abs) => {
|
|
1708
1879
|
const relPath = toRel(abs);
|
|
1880
|
+
opts?.onRawEvent?.("unlink", abs, relPath);
|
|
1709
1881
|
if (relPath.endsWith(".md")) remove(relPath);
|
|
1710
1882
|
});
|
|
1711
1883
|
const ready = new Promise((resolve) => {
|
|
@@ -1718,7 +1890,7 @@ function startWatcher(ctx2, log2, opts) {
|
|
|
1718
1890
|
}
|
|
1719
1891
|
|
|
1720
1892
|
// src/index.ts
|
|
1721
|
-
var VERSION = true ? "0.
|
|
1893
|
+
var VERSION = true ? "0.6.0" : "0.0.0-dev";
|
|
1722
1894
|
var intent = parseCliIntent(process.argv.slice(2));
|
|
1723
1895
|
if (intent === "version") {
|
|
1724
1896
|
process.stdout.write(`${VERSION}
|
|
@@ -2031,6 +2203,56 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
2031
2203
|
},
|
|
2032
2204
|
required: ["path", "find", "replace"]
|
|
2033
2205
|
}
|
|
2206
|
+
},
|
|
2207
|
+
{
|
|
2208
|
+
name: "get_periodic_note",
|
|
2209
|
+
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.",
|
|
2210
|
+
inputSchema: {
|
|
2211
|
+
type: "object",
|
|
2212
|
+
properties: {
|
|
2213
|
+
period: {
|
|
2214
|
+
type: "string",
|
|
2215
|
+
enum: ["daily", "weekly", "monthly", "quarterly", "yearly"],
|
|
2216
|
+
description: "Period type. Default: daily."
|
|
2217
|
+
},
|
|
2218
|
+
date: {
|
|
2219
|
+
type: "string",
|
|
2220
|
+
description: "ISO date string (YYYY-MM-DD). Defaults to today when omitted."
|
|
2221
|
+
},
|
|
2222
|
+
createIfMissing: {
|
|
2223
|
+
type: "boolean",
|
|
2224
|
+
description: "Create the note from the configured template if it does not exist. Default false."
|
|
2225
|
+
}
|
|
2226
|
+
},
|
|
2227
|
+
required: []
|
|
2228
|
+
}
|
|
2229
|
+
},
|
|
2230
|
+
{
|
|
2231
|
+
name: "append_periodic_note",
|
|
2232
|
+
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).",
|
|
2233
|
+
inputSchema: {
|
|
2234
|
+
type: "object",
|
|
2235
|
+
properties: {
|
|
2236
|
+
period: {
|
|
2237
|
+
type: "string",
|
|
2238
|
+
enum: ["daily", "weekly", "monthly", "quarterly", "yearly"],
|
|
2239
|
+
description: "Period type. Default: daily."
|
|
2240
|
+
},
|
|
2241
|
+
date: {
|
|
2242
|
+
type: "string",
|
|
2243
|
+
description: "ISO date string (YYYY-MM-DD). Defaults to today when omitted."
|
|
2244
|
+
},
|
|
2245
|
+
content: {
|
|
2246
|
+
type: "string",
|
|
2247
|
+
description: "Text to append to the note body."
|
|
2248
|
+
},
|
|
2249
|
+
createIfMissing: {
|
|
2250
|
+
type: "boolean",
|
|
2251
|
+
description: "Create the note if it does not exist before appending. Default true."
|
|
2252
|
+
}
|
|
2253
|
+
},
|
|
2254
|
+
required: ["content"]
|
|
2255
|
+
}
|
|
2034
2256
|
}
|
|
2035
2257
|
]
|
|
2036
2258
|
}));
|
|
@@ -2040,7 +2262,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
2040
2262
|
});
|
|
2041
2263
|
var transport = new StdioServerTransport();
|
|
2042
2264
|
await server.connect(transport);
|
|
2043
|
-
log.info("ready", { tools:
|
|
2265
|
+
log.info("ready", { tools: 16, transport: "stdio" });
|
|
2044
2266
|
process.stderr.write(
|
|
2045
2267
|
`seekstone: add to Claude Desktop:
|
|
2046
2268
|
${JSON.stringify(
|