@cad0p/napkin 0.8.1

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.
Files changed (123) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +342 -0
  3. package/dist/commands/aliases.d.ts +7 -0
  4. package/dist/commands/aliases.js +25 -0
  5. package/dist/commands/bases.d.ts +23 -0
  6. package/dist/commands/bases.js +139 -0
  7. package/dist/commands/bookmarks.d.ts +15 -0
  8. package/dist/commands/bookmarks.js +51 -0
  9. package/dist/commands/canvas.d.ts +49 -0
  10. package/dist/commands/canvas.js +186 -0
  11. package/dist/commands/config.d.ts +13 -0
  12. package/dist/commands/config.js +48 -0
  13. package/dist/commands/crud.d.ts +40 -0
  14. package/dist/commands/crud.js +195 -0
  15. package/dist/commands/daily.d.ts +20 -0
  16. package/dist/commands/daily.js +58 -0
  17. package/dist/commands/files.d.ts +23 -0
  18. package/dist/commands/files.js +132 -0
  19. package/dist/commands/graph.d.ts +4 -0
  20. package/dist/commands/graph.js +461 -0
  21. package/dist/commands/init.d.ts +7 -0
  22. package/dist/commands/init.js +52 -0
  23. package/dist/commands/links.d.ts +26 -0
  24. package/dist/commands/links.js +119 -0
  25. package/dist/commands/outline.d.ts +7 -0
  26. package/dist/commands/outline.js +48 -0
  27. package/dist/commands/overview.d.ts +6 -0
  28. package/dist/commands/overview.js +40 -0
  29. package/dist/commands/properties.d.ts +24 -0
  30. package/dist/commands/properties.js +115 -0
  31. package/dist/commands/search.d.ts +13 -0
  32. package/dist/commands/search.js +48 -0
  33. package/dist/commands/tags.d.ts +13 -0
  34. package/dist/commands/tags.js +51 -0
  35. package/dist/commands/tasks.d.ts +22 -0
  36. package/dist/commands/tasks.js +106 -0
  37. package/dist/commands/templates.d.ts +16 -0
  38. package/dist/commands/templates.js +70 -0
  39. package/dist/commands/vault.d.ts +4 -0
  40. package/dist/commands/vault.js +17 -0
  41. package/dist/commands/wordcount.d.ts +7 -0
  42. package/dist/commands/wordcount.js +43 -0
  43. package/dist/core/aliases.d.ts +5 -0
  44. package/dist/core/aliases.js +26 -0
  45. package/dist/core/bases.d.ts +29 -0
  46. package/dist/core/bases.js +67 -0
  47. package/dist/core/bookmarks.d.ts +14 -0
  48. package/dist/core/bookmarks.js +34 -0
  49. package/dist/core/canvas.d.ts +74 -0
  50. package/dist/core/canvas.js +125 -0
  51. package/dist/core/config.d.ts +7 -0
  52. package/dist/core/config.js +35 -0
  53. package/dist/core/crud.d.ts +32 -0
  54. package/dist/core/crud.js +119 -0
  55. package/dist/core/daily.d.ts +12 -0
  56. package/dist/core/daily.js +102 -0
  57. package/dist/core/files.d.ts +15 -0
  58. package/dist/core/files.js +30 -0
  59. package/dist/core/init.d.ts +31 -0
  60. package/dist/core/init.js +119 -0
  61. package/dist/core/links.d.ts +11 -0
  62. package/dist/core/links.js +66 -0
  63. package/dist/core/outline.d.ts +3 -0
  64. package/dist/core/outline.js +12 -0
  65. package/dist/core/overview.d.ts +15 -0
  66. package/dist/core/overview.js +384 -0
  67. package/dist/core/properties.d.ts +14 -0
  68. package/dist/core/properties.js +60 -0
  69. package/dist/core/search.d.ts +17 -0
  70. package/dist/core/search.js +153 -0
  71. package/dist/core/tags.d.ts +11 -0
  72. package/dist/core/tags.js +40 -0
  73. package/dist/core/tasks.d.ts +35 -0
  74. package/dist/core/tasks.js +97 -0
  75. package/dist/core/templates.d.ts +14 -0
  76. package/dist/core/templates.js +55 -0
  77. package/dist/core/vault.d.ts +10 -0
  78. package/dist/core/vault.js +37 -0
  79. package/dist/core/wordcount.d.ts +5 -0
  80. package/dist/core/wordcount.js +16 -0
  81. package/dist/index.d.ts +17 -0
  82. package/dist/index.js +1 -0
  83. package/dist/main.d.ts +2 -0
  84. package/dist/main.js +715 -0
  85. package/dist/sdk.d.ts +179 -0
  86. package/dist/sdk.js +232 -0
  87. package/dist/templates/coding.d.ts +2 -0
  88. package/dist/templates/coding.js +104 -0
  89. package/dist/templates/company.d.ts +2 -0
  90. package/dist/templates/company.js +121 -0
  91. package/dist/templates/index.d.ts +4 -0
  92. package/dist/templates/index.js +15 -0
  93. package/dist/templates/personal.d.ts +2 -0
  94. package/dist/templates/personal.js +91 -0
  95. package/dist/templates/product.d.ts +2 -0
  96. package/dist/templates/product.js +123 -0
  97. package/dist/templates/research.d.ts +2 -0
  98. package/dist/templates/research.js +114 -0
  99. package/dist/templates/types.d.ts +7 -0
  100. package/dist/templates/types.js +1 -0
  101. package/dist/utils/bases.d.ts +61 -0
  102. package/dist/utils/bases.js +661 -0
  103. package/dist/utils/config.d.ts +42 -0
  104. package/dist/utils/config.js +112 -0
  105. package/dist/utils/exit-codes.d.ts +5 -0
  106. package/dist/utils/exit-codes.js +5 -0
  107. package/dist/utils/files.d.ts +135 -0
  108. package/dist/utils/files.js +299 -0
  109. package/dist/utils/formula.d.ts +28 -0
  110. package/dist/utils/formula.js +462 -0
  111. package/dist/utils/frontmatter.d.ts +17 -0
  112. package/dist/utils/frontmatter.js +34 -0
  113. package/dist/utils/markdown.d.ts +31 -0
  114. package/dist/utils/markdown.js +80 -0
  115. package/dist/utils/output.d.ts +28 -0
  116. package/dist/utils/output.js +48 -0
  117. package/dist/utils/search-cache.d.ts +29 -0
  118. package/dist/utils/search-cache.js +41 -0
  119. package/dist/utils/test-helpers.d.ts +13 -0
  120. package/dist/utils/test-helpers.js +40 -0
  121. package/dist/utils/vault.d.ts +21 -0
  122. package/dist/utils/vault.js +144 -0
  123. package/package.json +76 -0
@@ -0,0 +1,462 @@
1
+ import Jexl from "jexl";
2
+ // All known transforms (methods that Obsidian calls with dot syntax)
3
+ const TRANSFORMS = new Set([
4
+ // Any
5
+ "isTruthy",
6
+ "isType",
7
+ "toString",
8
+ // Number
9
+ "abs",
10
+ "ceil",
11
+ "floor",
12
+ "round",
13
+ "toFixed",
14
+ "isEmpty",
15
+ // String
16
+ "contains",
17
+ "containsAll",
18
+ "containsAny",
19
+ "startsWith",
20
+ "endsWith",
21
+ "lower",
22
+ "title",
23
+ "trim",
24
+ "replace",
25
+ "repeat",
26
+ "reverse",
27
+ "slice",
28
+ "split",
29
+ // Date
30
+ "format",
31
+ "date",
32
+ "time",
33
+ "relative",
34
+ // List
35
+ "filter",
36
+ "map",
37
+ "reduce",
38
+ "flat",
39
+ "join",
40
+ "sort",
41
+ "unique",
42
+ // File
43
+ "asLink",
44
+ "hasLink",
45
+ "hasTag",
46
+ "hasProperty",
47
+ "inFolder",
48
+ // Link
49
+ "asFile",
50
+ "linksTo",
51
+ // Object
52
+ "keys",
53
+ "values",
54
+ // Regex
55
+ "matches",
56
+ ]);
57
+ /**
58
+ * Transform Obsidian expression syntax to jexl syntax.
59
+ * Converts .method(args) to |method(args) for known transforms.
60
+ * Also remaps if() to _if() since if is reserved.
61
+ */
62
+ export function obsidianToJexl(expr) {
63
+ // Replace if( with _if( — but not inside strings
64
+ let result = expr;
65
+ // Handle if() function calls (not inside quotes)
66
+ result = result.replace(/\bif\s*\(/g, "_if(");
67
+ // Convert .method( to |method( for known transforms
68
+ // Must be careful not to convert property access like file.name
69
+ for (const t of TRANSFORMS) {
70
+ // Match .transform( but not when preceded by a quote (inside string)
71
+ const regex = new RegExp(`\\.${t}\\(`, "g");
72
+ result = result.replace(regex, `|${t}(`);
73
+ }
74
+ // Handle .length (property, not function call) — convert to |_length
75
+ result = result.replace(/\.length\b(?!\s*\()/g, "|_length");
76
+ // Handle .isEmpty() with no args — it's already converted above if it matches
77
+ // Handle .year, .month, .day, .hour, .minute, .second, .millisecond on dates
78
+ for (const field of [
79
+ "year",
80
+ "month",
81
+ "day",
82
+ "hour",
83
+ "minute",
84
+ "second",
85
+ "millisecond",
86
+ "days",
87
+ "hours",
88
+ "minutes",
89
+ "seconds",
90
+ "milliseconds",
91
+ ]) {
92
+ const regex = new RegExp(`\\.${field}\\b(?!\\s*\\()`, "g");
93
+ result = result.replace(regex, `|_${field}`);
94
+ }
95
+ return result;
96
+ }
97
+ /**
98
+ * Create a configured jexl instance with all Obsidian Bases functions.
99
+ */
100
+ export function createFormulaEngine() {
101
+ const jexl = new Jexl.Jexl();
102
+ // === Global functions ===
103
+ jexl.addFunction("_if", (cond, trueVal, falseVal) => cond ? trueVal : (falseVal ?? null));
104
+ jexl.addFunction("now", () => Date.now());
105
+ jexl.addFunction("today", () => {
106
+ const d = new Date();
107
+ d.setHours(0, 0, 0, 0);
108
+ return d.getTime();
109
+ });
110
+ jexl.addFunction("date", (s) => new Date(s).getTime());
111
+ jexl.addFunction("duration", (s) => parseDurationMs(s));
112
+ jexl.addFunction("min", (...args) => Math.min(...args));
113
+ jexl.addFunction("max", (...args) => Math.max(...args));
114
+ jexl.addFunction("number", (v) => {
115
+ if (typeof v === "boolean")
116
+ return v ? 1 : 0;
117
+ return Number(v);
118
+ });
119
+ jexl.addFunction("list", (v) => (Array.isArray(v) ? v : [v]));
120
+ jexl.addFunction("link", (path, display) => display || path);
121
+ jexl.addFunction("icon", (name) => `[${name}]`);
122
+ // === Number transforms ===
123
+ jexl.addTransform("abs", (v) => Math.abs(v));
124
+ jexl.addTransform("ceil", (v) => Math.ceil(v));
125
+ jexl.addTransform("floor", (v) => Math.floor(v));
126
+ jexl.addTransform("round", (v, digits) => {
127
+ const f = 10 ** (digits || 0);
128
+ return Math.round(v * f) / f;
129
+ });
130
+ jexl.addTransform("toFixed", (v, precision) => Number(v).toFixed(precision));
131
+ // === String transforms ===
132
+ jexl.addTransform("contains", (v, sub) => {
133
+ if (Array.isArray(v))
134
+ return v.includes(sub);
135
+ return String(v).includes(String(sub));
136
+ });
137
+ jexl.addTransform("containsAll", (v, ...subs) => {
138
+ if (Array.isArray(v))
139
+ return subs.every((s) => v.includes(s));
140
+ const s = String(v);
141
+ return subs.every((sub) => s.includes(String(sub)));
142
+ });
143
+ jexl.addTransform("containsAny", (v, ...subs) => {
144
+ if (Array.isArray(v))
145
+ return subs.some((s) => v.includes(s));
146
+ const s = String(v);
147
+ return subs.some((sub) => s.includes(String(sub)));
148
+ });
149
+ jexl.addTransform("startsWith", (v, q) => String(v).startsWith(q));
150
+ jexl.addTransform("endsWith", (v, q) => String(v).endsWith(q));
151
+ jexl.addTransform("lower", (v) => String(v).toLowerCase());
152
+ jexl.addTransform("title", (v) => String(v).replace(/\b\w/g, (c) => c.toUpperCase()));
153
+ jexl.addTransform("trim", (v) => String(v).trim());
154
+ jexl.addTransform("replace", (v, pat, rep) => String(v).replace(pat, rep));
155
+ jexl.addTransform("repeat", (v, n) => String(v).repeat(n));
156
+ jexl.addTransform("reverse", (v) => {
157
+ if (Array.isArray(v))
158
+ return [...v].reverse();
159
+ return String(v).split("").reverse().join("");
160
+ });
161
+ jexl.addTransform("slice", (v, start, end) => {
162
+ if (Array.isArray(v))
163
+ return v.slice(start, end);
164
+ return String(v).slice(start, end);
165
+ });
166
+ jexl.addTransform("split", (v, sep, n) => {
167
+ const parts = String(v).split(sep);
168
+ return n ? parts.slice(0, n) : parts;
169
+ });
170
+ jexl.addTransform("toString", (v) => String(v));
171
+ // === Date transforms ===
172
+ jexl.addTransform("format", (v, fmt) => {
173
+ const d = new Date(v);
174
+ return fmt
175
+ .replace("YYYY", String(d.getFullYear()))
176
+ .replace("MM", String(d.getMonth() + 1).padStart(2, "0"))
177
+ .replace("DD", String(d.getDate()).padStart(2, "0"))
178
+ .replace("HH", String(d.getHours()).padStart(2, "0"))
179
+ .replace("mm", String(d.getMinutes()).padStart(2, "0"))
180
+ .replace("ss", String(d.getSeconds()).padStart(2, "0"));
181
+ });
182
+ jexl.addTransform("date", (v) => {
183
+ const d = new Date(v);
184
+ d.setHours(0, 0, 0, 0);
185
+ return d.getTime();
186
+ });
187
+ jexl.addTransform("time", (v) => {
188
+ const d = new Date(v);
189
+ return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}:${String(d.getSeconds()).padStart(2, "0")}`;
190
+ });
191
+ jexl.addTransform("relative", (v) => {
192
+ const diff = Date.now() - v;
193
+ const abs = Math.abs(diff);
194
+ const ago = diff > 0;
195
+ if (abs < 60000)
196
+ return "just now";
197
+ if (abs < 3600000) {
198
+ const m = Math.floor(abs / 60000);
199
+ return ago
200
+ ? `${m} minute${m > 1 ? "s" : ""} ago`
201
+ : `in ${m} minute${m > 1 ? "s" : ""}`;
202
+ }
203
+ if (abs < 86400000) {
204
+ const h = Math.floor(abs / 3600000);
205
+ return ago
206
+ ? `${h} hour${h > 1 ? "s" : ""} ago`
207
+ : `in ${h} hour${h > 1 ? "s" : ""}`;
208
+ }
209
+ const d = Math.floor(abs / 86400000);
210
+ return ago
211
+ ? `${d} day${d > 1 ? "s" : ""} ago`
212
+ : `in ${d} day${d > 1 ? "s" : ""}`;
213
+ });
214
+ // === Date field transforms (act as property access) ===
215
+ jexl.addTransform("_year", (v) => new Date(v).getFullYear());
216
+ jexl.addTransform("_month", (v) => new Date(v).getMonth() + 1);
217
+ jexl.addTransform("_day", (v) => new Date(v).getDate());
218
+ jexl.addTransform("_hour", (v) => new Date(v).getHours());
219
+ jexl.addTransform("_minute", (v) => new Date(v).getMinutes());
220
+ jexl.addTransform("_second", (v) => new Date(v).getSeconds());
221
+ jexl.addTransform("_millisecond", (v) => new Date(v).getMilliseconds());
222
+ // === Duration field transforms ===
223
+ jexl.addTransform("_days", (v) => v / 86400000);
224
+ jexl.addTransform("_hours", (v) => v / 3600000);
225
+ jexl.addTransform("_minutes", (v) => v / 60000);
226
+ jexl.addTransform("_seconds", (v) => v / 1000);
227
+ jexl.addTransform("_milliseconds", (v) => v);
228
+ // === List transforms ===
229
+ jexl.addTransform("join", (v, sep) => Array.isArray(v) ? v.join(sep) : String(v));
230
+ jexl.addTransform("sort", (v) => Array.isArray(v) ? [...v].sort() : v);
231
+ jexl.addTransform("unique", (v) => Array.isArray(v) ? [...new Set(v)] : v);
232
+ jexl.addTransform("flat", (v) => Array.isArray(v) ? v.flat() : v);
233
+ // === Any transforms ===
234
+ jexl.addTransform("isEmpty", (v) => {
235
+ if (v === null || v === undefined || v === "")
236
+ return true;
237
+ if (Array.isArray(v))
238
+ return v.length === 0;
239
+ if (typeof v === "object")
240
+ return Object.keys(v).length === 0;
241
+ return false;
242
+ });
243
+ jexl.addTransform("isTruthy", (v) => !!v);
244
+ jexl.addTransform("isType", (v, type) => {
245
+ if (type === "string")
246
+ return typeof v === "string";
247
+ if (type === "number")
248
+ return typeof v === "number";
249
+ if (type === "boolean")
250
+ return typeof v === "boolean";
251
+ if (type === "list")
252
+ return Array.isArray(v);
253
+ return false;
254
+ });
255
+ jexl.addTransform("_length", (v) => {
256
+ if (typeof v === "string")
257
+ return v.length;
258
+ if (Array.isArray(v))
259
+ return v.length;
260
+ return 0;
261
+ });
262
+ // === Object transforms ===
263
+ jexl.addTransform("keys", (v) => v && typeof v === "object" && !Array.isArray(v) ? Object.keys(v) : []);
264
+ jexl.addTransform("values", (v) => v && typeof v === "object" && !Array.isArray(v) ? Object.values(v) : []);
265
+ // === File-like transforms (operate on context objects) ===
266
+ jexl.addTransform("hasTag", (file, ...tags) => {
267
+ if (!file?.tags)
268
+ return false;
269
+ return tags.some((t) => file.tags?.some((ft) => ft === t || ft.startsWith(`${t}/`)));
270
+ });
271
+ jexl.addTransform("hasLink", (file, target) => {
272
+ if (!file?.links)
273
+ return false;
274
+ return file.links.includes(target);
275
+ });
276
+ jexl.addTransform("hasProperty", (file, name) => {
277
+ if (!file?.properties)
278
+ return false;
279
+ return name in file.properties;
280
+ });
281
+ jexl.addTransform("inFolder", (file, folder) => {
282
+ if (!file?.folder)
283
+ return false;
284
+ return file.folder === folder || file.folder.startsWith(`${folder}/`);
285
+ });
286
+ jexl.addTransform("asLink", (file, display) => display || file?.name || "");
287
+ // === Regex ===
288
+ jexl.addTransform("matches", (pattern, target) => {
289
+ try {
290
+ return new RegExp(pattern).test(target);
291
+ }
292
+ catch {
293
+ return false;
294
+ }
295
+ });
296
+ return jexl;
297
+ }
298
+ /**
299
+ * Build a context object for formula evaluation from a database row.
300
+ */
301
+ export function buildFormulaContext(columns, row, formulaResults = {}, thisFile) {
302
+ const ctx = {};
303
+ const file = {};
304
+ const note = {};
305
+ for (let i = 0; i < columns.length; i++) {
306
+ const col = columns[i];
307
+ const val = row[i];
308
+ // File metadata columns
309
+ if ([
310
+ "path",
311
+ "name",
312
+ "basename",
313
+ "folder",
314
+ "ext",
315
+ "size",
316
+ "ctime",
317
+ "mtime",
318
+ ].includes(col)) {
319
+ file[col] = val;
320
+ }
321
+ else if (col === "tags") {
322
+ try {
323
+ file.tags = JSON.parse(val);
324
+ }
325
+ catch {
326
+ file.tags = [];
327
+ }
328
+ }
329
+ else if (col === "links") {
330
+ try {
331
+ file.links = JSON.parse(val);
332
+ }
333
+ catch {
334
+ file.links = [];
335
+ }
336
+ }
337
+ else if (col === "backlinks") {
338
+ try {
339
+ file.backlinks = JSON.parse(val);
340
+ }
341
+ catch {
342
+ file.backlinks = [];
343
+ }
344
+ }
345
+ else if (col === "embeds") {
346
+ try {
347
+ file.embeds = JSON.parse(val);
348
+ }
349
+ catch {
350
+ file.embeds = [];
351
+ }
352
+ }
353
+ else if (col === "file_properties") {
354
+ try {
355
+ file.properties = JSON.parse(val);
356
+ }
357
+ catch {
358
+ file.properties = {};
359
+ }
360
+ }
361
+ else {
362
+ // Frontmatter properties (already stripped of prop_ prefix by queryBase)
363
+ // Try to parse JSON values (lists, objects)
364
+ let parsed = val;
365
+ if (typeof val === "string") {
366
+ try {
367
+ const p = JSON.parse(val);
368
+ if (typeof p === "object")
369
+ parsed = p;
370
+ }
371
+ catch {
372
+ /* keep as string */
373
+ }
374
+ }
375
+ note[col] = parsed;
376
+ ctx[col] = parsed; // bare property access shorthand
377
+ }
378
+ }
379
+ // Add formula results
380
+ const formula = { ...formulaResults };
381
+ ctx.formula = formula;
382
+ ctx.file = file;
383
+ ctx.note = note;
384
+ if (thisFile)
385
+ ctx.this = { file: thisFile };
386
+ return ctx;
387
+ }
388
+ /**
389
+ * Evaluate all formulas for a single row.
390
+ * Handles formula dependencies (formula referencing another formula).
391
+ */
392
+ export async function evaluateFormulas(engine, formulas, columns, row, thisFile) {
393
+ const results = {};
394
+ const remaining = { ...formulas };
395
+ let iterations = 0;
396
+ const maxIterations = Object.keys(formulas).length + 1;
397
+ while (Object.keys(remaining).length > 0 && iterations < maxIterations) {
398
+ iterations++;
399
+ let resolved = false;
400
+ for (const [name, expr] of Object.entries(remaining)) {
401
+ // Check if this formula depends on unresolved formulas
402
+ const deps = Object.keys(remaining).filter((k) => k !== name && expr.includes(`formula.${k}`));
403
+ if (deps.length > 0)
404
+ continue;
405
+ const ctx = buildFormulaContext(columns, row, results, thisFile);
406
+ try {
407
+ const jexlExpr = obsidianToJexl(expr);
408
+ results[name] = await engine.eval(jexlExpr, ctx);
409
+ }
410
+ catch {
411
+ results[name] = null;
412
+ }
413
+ delete remaining[name];
414
+ resolved = true;
415
+ }
416
+ if (!resolved)
417
+ break; // Circular dependency, bail
418
+ }
419
+ // Any remaining (circular deps) get null
420
+ for (const name of Object.keys(remaining)) {
421
+ results[name] = null;
422
+ }
423
+ return results;
424
+ }
425
+ function parseDurationMs(dur) {
426
+ const match = dur.match(/^(\d+)\s*(y|year|years|M|month|months|d|day|days|w|week|weeks|h|hour|hours|m|minute|minutes|s|second|seconds)$/);
427
+ if (!match)
428
+ return 0;
429
+ const n = Number.parseInt(match[1], 10);
430
+ switch (match[2]) {
431
+ case "y":
432
+ case "year":
433
+ case "years":
434
+ return n * 365.25 * 24 * 60 * 60 * 1000;
435
+ case "M":
436
+ case "month":
437
+ case "months":
438
+ return n * 30.44 * 24 * 60 * 60 * 1000;
439
+ case "w":
440
+ case "week":
441
+ case "weeks":
442
+ return n * 7 * 24 * 60 * 60 * 1000;
443
+ case "d":
444
+ case "day":
445
+ case "days":
446
+ return n * 24 * 60 * 60 * 1000;
447
+ case "h":
448
+ case "hour":
449
+ case "hours":
450
+ return n * 60 * 60 * 1000;
451
+ case "m":
452
+ case "minute":
453
+ case "minutes":
454
+ return n * 60 * 1000;
455
+ case "s":
456
+ case "second":
457
+ case "seconds":
458
+ return n * 1000;
459
+ default:
460
+ return 0;
461
+ }
462
+ }
@@ -0,0 +1,17 @@
1
+ export interface ParsedFrontmatter {
2
+ properties: Record<string, unknown>;
3
+ body: string;
4
+ raw: string;
5
+ }
6
+ /**
7
+ * Parse YAML frontmatter from markdown content.
8
+ */
9
+ export declare function parseFrontmatter(content: string): ParsedFrontmatter;
10
+ /**
11
+ * Set a property in frontmatter, creating the --- block if needed.
12
+ */
13
+ export declare function setProperty(content: string, name: string, value: unknown): string;
14
+ /**
15
+ * Remove a property from frontmatter.
16
+ */
17
+ export declare function removeProperty(content: string, name: string): string;
@@ -0,0 +1,34 @@
1
+ import matter from "gray-matter";
2
+ /**
3
+ * Parse YAML frontmatter from markdown content.
4
+ */
5
+ export function parseFrontmatter(content) {
6
+ const result = matter(content);
7
+ return {
8
+ properties: result.data,
9
+ body: result.content,
10
+ raw: result.matter,
11
+ };
12
+ }
13
+ /**
14
+ * Set a property in frontmatter, creating the --- block if needed.
15
+ */
16
+ export function setProperty(content, name, value) {
17
+ const result = matter(content);
18
+ const data = { ...result.data, [name]: value };
19
+ return matter.stringify(result.content, data);
20
+ }
21
+ /**
22
+ * Remove a property from frontmatter.
23
+ */
24
+ export function removeProperty(content, name) {
25
+ const result = matter(content);
26
+ const data = { ...result.data };
27
+ delete data[name];
28
+ // If no properties left, return just the body
29
+ if (Object.keys(data).length === 0) {
30
+ const body = result.content;
31
+ return body.startsWith("\n") ? body.slice(1) : body;
32
+ }
33
+ return matter.stringify(result.content, data);
34
+ }
@@ -0,0 +1,31 @@
1
+ export interface Heading {
2
+ level: number;
3
+ text: string;
4
+ line: number;
5
+ }
6
+ export interface Task {
7
+ line: number;
8
+ status: string;
9
+ text: string;
10
+ done: boolean;
11
+ }
12
+ export interface LinkInfo {
13
+ outgoing: string[];
14
+ wikilinks: string[];
15
+ }
16
+ /**
17
+ * Extract headings from markdown content.
18
+ */
19
+ export declare function extractHeadings(content: string): Heading[];
20
+ /**
21
+ * Extract tasks (checkboxes) from markdown content.
22
+ */
23
+ export declare function extractTasks(content: string): Task[];
24
+ /**
25
+ * Extract tags from markdown content (both inline #tags and frontmatter tags).
26
+ */
27
+ export declare function extractTags(content: string): string[];
28
+ /**
29
+ * Extract links from markdown content.
30
+ */
31
+ export declare function extractLinks(content: string): LinkInfo;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Extract headings from markdown content.
3
+ */
4
+ export function extractHeadings(content) {
5
+ const headings = [];
6
+ const lines = content.split("\n");
7
+ for (let i = 0; i < lines.length; i++) {
8
+ const match = lines[i].match(/^(#{1,6})\s+(.+)$/);
9
+ if (match) {
10
+ headings.push({
11
+ level: match[1].length,
12
+ text: match[2].trim(),
13
+ line: i + 1,
14
+ });
15
+ }
16
+ }
17
+ return headings;
18
+ }
19
+ /**
20
+ * Extract tasks (checkboxes) from markdown content.
21
+ */
22
+ export function extractTasks(content) {
23
+ const tasks = [];
24
+ const lines = content.split("\n");
25
+ for (let i = 0; i < lines.length; i++) {
26
+ const match = lines[i].match(/^[\s]*[-*]\s+\[(.)\]\s+(.*)$/);
27
+ if (match) {
28
+ const status = match[1];
29
+ tasks.push({
30
+ line: i + 1,
31
+ status,
32
+ text: match[2].trim(),
33
+ done: status === "x" || status === "X",
34
+ });
35
+ }
36
+ }
37
+ return tasks;
38
+ }
39
+ /**
40
+ * Extract tags from markdown content (both inline #tags and frontmatter tags).
41
+ */
42
+ export function extractTags(content) {
43
+ const tags = new Set();
44
+ // Inline tags: #tag (not inside code blocks or links)
45
+ const tagRegex = /(?:^|\s)#([a-zA-Z][\w/-]*)/g;
46
+ for (let match = tagRegex.exec(content); match !== null; match = tagRegex.exec(content)) {
47
+ tags.add(match[1]);
48
+ }
49
+ return [...tags].sort();
50
+ }
51
+ /**
52
+ * Extract links from markdown content.
53
+ */
54
+ export function extractLinks(content) {
55
+ const wikilinks = [];
56
+ const outgoing = [];
57
+ // Wikilinks: [[target]] or [[target|alias]]
58
+ const wikiRegex = /\[\[([^\]|]+)(?:\|[^\]]+)?\]\]/g;
59
+ for (let match = wikiRegex.exec(content); match !== null; match = wikiRegex.exec(content)) {
60
+ const target = match[1].trim();
61
+ // Strip heading/block refs
62
+ const clean = target.split("#")[0].trim();
63
+ if (clean) {
64
+ wikilinks.push(clean);
65
+ outgoing.push(clean);
66
+ }
67
+ }
68
+ // Markdown links: [text](url)
69
+ const mdRegex = /\[([^\]]*)\]\(([^)]+)\)/g;
70
+ for (let match = mdRegex.exec(content); match !== null; match = mdRegex.exec(content)) {
71
+ const url = match[2].trim();
72
+ // Only internal links (not http/https/mailto)
73
+ if (!url.match(/^(https?|mailto|obsidian):\/\//)) {
74
+ const clean = decodeURIComponent(url.split("#")[0].trim());
75
+ if (clean)
76
+ outgoing.push(clean);
77
+ }
78
+ }
79
+ return { outgoing, wikilinks };
80
+ }
@@ -0,0 +1,28 @@
1
+ export interface OutputOptions {
2
+ json?: boolean;
3
+ quiet?: boolean;
4
+ }
5
+ export declare const success: (msg: string) => void;
6
+ export declare const info: (msg: string) => void;
7
+ export declare const warn: (msg: string) => void;
8
+ export declare const error: (msg: string) => void;
9
+ export declare const errorWithHint: (msg: string, hint: string) => void;
10
+ /**
11
+ * Standard "file not found" error with suggestions.
12
+ * Import suggestFile where needed and pass results here.
13
+ */
14
+ export declare function fileNotFound(ref: string, suggestions?: string[]): void;
15
+ export declare const bold: (s: string) => string;
16
+ export declare const dim: (s: string) => string;
17
+ export declare const cmd: (s: string) => string;
18
+ export declare const bullet: (msg: string) => void;
19
+ export declare const bulletDim: (msg: string) => void;
20
+ export declare const hint: (msg: string) => void;
21
+ export declare const nextStep: (command: string) => void;
22
+ export declare const header: (title: string) => void;
23
+ export declare function jsonOutput(data: object): void;
24
+ export declare function output(options: OutputOptions, handlers: {
25
+ json?: () => object;
26
+ quiet?: () => void;
27
+ human: () => void;
28
+ }): void;