@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
package/dist/main.js ADDED
@@ -0,0 +1,715 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
3
+ import { Command } from "commander";
4
+ import { aliases } from "./commands/aliases.js";
5
+ import { baseCreate, baseQuery, bases, baseViews } from "./commands/bases.js";
6
+ import { bookmark, bookmarks } from "./commands/bookmarks.js";
7
+ import { canvasAddEdge, canvasAddNode, canvasCreate, canvases, canvasNodes, canvasRead, canvasRemoveNode, } from "./commands/canvas.js";
8
+ import { configGet, configSet, configShow } from "./commands/config.js";
9
+ import { append, create, del, move, prepend, read, rename, } from "./commands/crud.js";
10
+ import { daily, dailyAppend, dailyPath, dailyPrepend, dailyRead, } from "./commands/daily.js";
11
+ import { file, files, folder, folders } from "./commands/files.js";
12
+ import { graph } from "./commands/graph.js";
13
+ import { init, initTemplates } from "./commands/init.js";
14
+ import { backlinks, deadends, links, orphans, unresolvedLinks, } from "./commands/links.js";
15
+ import { outline } from "./commands/outline.js";
16
+ import { overview } from "./commands/overview.js";
17
+ import { properties, propertyRead, propertyRemove, propertySet, } from "./commands/properties.js";
18
+ import { search } from "./commands/search.js";
19
+ import { tag, tags } from "./commands/tags.js";
20
+ import { task, tasks } from "./commands/tasks.js";
21
+ import { templateInsert, templateRead, templates, } from "./commands/templates.js";
22
+ import { vault } from "./commands/vault.js";
23
+ import { wordcount } from "./commands/wordcount.js";
24
+ const require = createRequire(import.meta.url);
25
+ const { version } = require("../package.json");
26
+ const program = new Command();
27
+ program
28
+ .name("napkin")
29
+ .description("🧻 Knowledge system for AI agents. Local-first, file-based, progressively disclosed.")
30
+ .version(`napkin ${version}`, "-v, --version")
31
+ .option("--json", "Output as JSON")
32
+ .option("-q, --quiet", "Suppress output")
33
+ .option("--vault <path>", "Vault path (default: auto-detect from cwd)")
34
+ .option("--copy", "Copy output to clipboard")
35
+ .showSuggestionAfterError(true)
36
+ .helpCommand(false)
37
+ .addHelpText("before", () => {
38
+ showHelp();
39
+ process.exit(0);
40
+ });
41
+ function showHelp() {
42
+ console.log(`Usage: napkin [options] [command]
43
+
44
+ 🧻 Knowledge system for AI agents. Local-first, file-based, progressively disclosed.
45
+
46
+ Examples:
47
+ $ napkin init --template coding Create a vault with coding structure
48
+ $ napkin overview See what's in the vault
49
+ $ napkin search "auth" Find content about auth
50
+ $ napkin read "Architecture" Read a specific file
51
+
52
+ Workflow: init → overview → search → read
53
+
54
+ Getting started:
55
+ init Initialize a new vault (--template, --list)
56
+ overview Vault map with TF-IDF keywords per folder
57
+ vault Show vault info (path, file count, size)
58
+ config Vault configuration (show, get, set)
59
+ graph Interactive vault graph visualization
60
+
61
+ Reading:
62
+ read <file> Read a file
63
+ search <query> Search vault (BM25 + backlinks + recency)
64
+
65
+ Writing:
66
+ create <name> Create a new file (--template, --content)
67
+ append <file> Append content to a file
68
+ prepend <file> Prepend content after frontmatter
69
+ move <file> <to> Move a file
70
+ rename <file> <name> Rename a file
71
+ delete <file> Delete a file
72
+
73
+ Subcommands:
74
+ file Files, folders, outline, wordcount
75
+ daily Daily notes (today, read, append, prepend)
76
+ tag Tags and aliases
77
+ property Frontmatter properties (list, set, remove, read)
78
+ task Tasks and checklists (list, show, toggle)
79
+ link Links and graph (out, back, orphans, deadends)
80
+ template Note templates (list, read, insert)
81
+ base Database views over vault files
82
+ canvas JSON Canvas operations
83
+ bookmark Bookmarks
84
+
85
+ Options:
86
+ --json Output as JSON
87
+ -q, --quiet Suppress output
88
+ --vault <path> Vault path (default: auto-detect from cwd)
89
+ --copy Copy output to clipboard
90
+ -v, --version Show version
91
+ -h, --help Show this help
92
+
93
+ All commands support --json for structured output.
94
+ More help: napkin <command> --help
95
+ Docs: https://github.com/Michaelliv/napkin`);
96
+ }
97
+ // ── Getting started ─────────────────────────────────────────────────
98
+ program
99
+ .command("init")
100
+ .description("Initialize a new vault")
101
+ .option("--path <path>", "Directory to initialize (default: cwd)")
102
+ .option("--template <name>", "Scaffold with template (see init --list)")
103
+ .option("--list", "List available vault templates")
104
+ .action(async (opts, cmd) => {
105
+ const root = { ...cmd.optsWithGlobals(), ...opts };
106
+ if (root.list) {
107
+ await initTemplates(root);
108
+ }
109
+ else {
110
+ await init(root);
111
+ }
112
+ });
113
+ program
114
+ .command("overview")
115
+ .description("Vault map with keywords (Level 1 progressive disclosure)")
116
+ .option("--depth <n>", "Max folder depth")
117
+ .option("--keywords <n>", "Max keywords per folder")
118
+ .action(async (opts, cmd) => {
119
+ const root = { ...cmd.optsWithGlobals(), ...opts };
120
+ await overview(root);
121
+ });
122
+ program
123
+ .command("graph")
124
+ .description("Interactive vault graph visualization")
125
+ .action(async (_opts, cmd) => {
126
+ const root = cmd.optsWithGlobals();
127
+ await graph({}, root);
128
+ });
129
+ program
130
+ .command("vault")
131
+ .description("Show vault info (path, file count, size)")
132
+ .action(async (_opts, cmd) => {
133
+ const root = cmd.optsWithGlobals();
134
+ await vault(root);
135
+ });
136
+ // ── Reading ─────────────────────────────────────────────────────────
137
+ program
138
+ .command("read <file>")
139
+ .description("Read a file")
140
+ .action(async (fileRef, _opts, cmd) => {
141
+ const root = cmd.optsWithGlobals();
142
+ await read(fileRef, root);
143
+ });
144
+ program
145
+ .command("search [query...]")
146
+ .description("Search vault (ranked by BM25 + backlinks + recency)")
147
+ .option("--query <text>", "Search query")
148
+ .option("--path <folder>", "Limit to folder")
149
+ .option("--limit <n>", "Max results (default: 30)")
150
+ .option("--total", "Return match count")
151
+ .option("--snippet-lines <n>", "Context lines around matches (default: 0)")
152
+ .option("--no-snippets", "Return files only, no snippets")
153
+ .option("--score", "Include relevance score in output")
154
+ .action(async (queryWords, opts, cmd) => {
155
+ const root = { ...cmd.optsWithGlobals(), ...opts };
156
+ if (queryWords.length && !root.query) {
157
+ root.query = queryWords.join(" ");
158
+ }
159
+ await search(root);
160
+ });
161
+ // ── Writing ─────────────────────────────────────────────────────────
162
+ program
163
+ .command("create [name] [content]")
164
+ .description("Create a new file")
165
+ .option("--name <name>", "File name (alternative to positional)")
166
+ .option("--path <path>", "File path from vault root")
167
+ .option("--content <text>", "Initial content (alternative to positional)")
168
+ .option("--template <name>", "Template to use")
169
+ .option("--overwrite", "Overwrite if file exists")
170
+ .action(async (name, content, opts, cmd) => {
171
+ const root = { ...cmd.optsWithGlobals(), ...opts };
172
+ root.name = root.name || name;
173
+ root.content = root.content || content;
174
+ await create(root);
175
+ });
176
+ program
177
+ .command("append [file] [content]")
178
+ .description("Append content to a file")
179
+ .option("--file <name>", "Target file (alternative to positional)")
180
+ .option("--content <text>", "Content to append (alternative to positional)")
181
+ .option("--inline", "Append without newline")
182
+ .action(async (file, content, opts, cmd) => {
183
+ const root = { ...cmd.optsWithGlobals(), ...opts };
184
+ root.file = root.file || file;
185
+ root.content = root.content || content;
186
+ await append(root);
187
+ });
188
+ program
189
+ .command("prepend [file] [content]")
190
+ .description("Prepend content after frontmatter")
191
+ .option("--file <name>", "Target file (alternative to positional)")
192
+ .option("--content <text>", "Content to prepend (alternative to positional)")
193
+ .option("--inline", "Prepend without newline")
194
+ .action(async (file, content, opts, cmd) => {
195
+ const root = { ...cmd.optsWithGlobals(), ...opts };
196
+ root.file = root.file || file;
197
+ root.content = root.content || content;
198
+ await prepend(root);
199
+ });
200
+ program
201
+ .command("move [file] [to]")
202
+ .description("Move a file")
203
+ .option("--file <name>", "File to move (alternative to positional)")
204
+ .option("--to <path>", "Destination folder or path (alternative to positional)")
205
+ .action(async (file, to, opts, cmd) => {
206
+ const root = { ...cmd.optsWithGlobals(), ...opts };
207
+ root.file = root.file || file;
208
+ root.to = root.to || to;
209
+ await move(root);
210
+ });
211
+ program
212
+ .command("rename [file] [name]")
213
+ .description("Rename a file")
214
+ .option("--file <name>", "File to rename (alternative to positional)")
215
+ .option("--name <name>", "New file name (alternative to positional)")
216
+ .action(async (file, name, opts, cmd) => {
217
+ const root = { ...cmd.optsWithGlobals(), ...opts };
218
+ root.file = root.file || file;
219
+ root.name = root.name || name;
220
+ await rename(root);
221
+ });
222
+ program
223
+ .command("delete [file]")
224
+ .description("Delete a file")
225
+ .option("--file <name>", "File to delete (alternative to positional)")
226
+ .option("--permanent", "Skip trash, delete permanently")
227
+ .action(async (file, opts, cmd) => {
228
+ const root = { ...cmd.optsWithGlobals(), ...opts };
229
+ root.file = root.file || file;
230
+ await del(root);
231
+ });
232
+ // ── file ────────────────────────────────────────────────────────────
233
+ const fileCmd = program
234
+ .command("file")
235
+ .description("Files, folders, and metadata");
236
+ fileCmd
237
+ .command("info [name]")
238
+ .description("Show file info")
239
+ .action(async (name, _opts, cmd) => {
240
+ const root = cmd.optsWithGlobals();
241
+ await file(name, root);
242
+ });
243
+ fileCmd
244
+ .command("list")
245
+ .description("List files in vault")
246
+ .option("--folder <path>", "Filter by folder")
247
+ .option("--ext <extension>", "Filter by extension")
248
+ .option("--total", "Return file count")
249
+ .action(async (opts, cmd) => {
250
+ const root = { ...cmd.optsWithGlobals(), ...opts };
251
+ await files(root);
252
+ });
253
+ fileCmd
254
+ .command("folder <path>")
255
+ .description("Show folder info")
256
+ .option("--info <type>", "Return specific info: files, folders, or size")
257
+ .action(async (folderPath, opts, cmd) => {
258
+ const root = { ...cmd.optsWithGlobals(), ...opts };
259
+ await folder(folderPath, root);
260
+ });
261
+ fileCmd
262
+ .command("folders")
263
+ .description("List folders")
264
+ .option("--folder <path>", "Filter by parent folder")
265
+ .option("--total", "Return folder count")
266
+ .action(async (opts, cmd) => {
267
+ const root = { ...cmd.optsWithGlobals(), ...opts };
268
+ await folders(root);
269
+ });
270
+ fileCmd
271
+ .command("outline [file]")
272
+ .description("Show headings for a file")
273
+ .option("--file <name>", "File name (alternative to positional)")
274
+ .option("--format <type>", "Output format: tree, md, json")
275
+ .option("--total", "Return heading count")
276
+ .action(async (file, opts, cmd) => {
277
+ const root = { ...cmd.optsWithGlobals(), ...opts };
278
+ root.file = root.file || file;
279
+ await outline(root);
280
+ });
281
+ fileCmd
282
+ .command("wordcount [file]")
283
+ .description("Count words and characters")
284
+ .option("--file <name>", "File name (alternative to positional)")
285
+ .option("--words", "Return word count only")
286
+ .option("--characters", "Return character count only")
287
+ .action(async (file, opts, cmd) => {
288
+ const root = { ...cmd.optsWithGlobals(), ...opts };
289
+ root.file = root.file || file;
290
+ await wordcount(root);
291
+ });
292
+ // ── daily ───────────────────────────────────────────────────────────
293
+ const dailyCmd = program.command("daily").description("Daily notes");
294
+ dailyCmd
295
+ .command("today")
296
+ .description("Create today's daily note")
297
+ .action(async (_opts, cmd) => {
298
+ const root = cmd.optsWithGlobals();
299
+ await daily(root);
300
+ });
301
+ dailyCmd
302
+ .command("path")
303
+ .description("Get daily note path")
304
+ .action(async (_opts, cmd) => {
305
+ const root = cmd.optsWithGlobals();
306
+ await dailyPath(root);
307
+ });
308
+ dailyCmd
309
+ .command("read")
310
+ .description("Read daily note contents")
311
+ .action(async (_opts, cmd) => {
312
+ const root = cmd.optsWithGlobals();
313
+ await dailyRead(root);
314
+ });
315
+ dailyCmd
316
+ .command("append [content]")
317
+ .description("Append to daily note")
318
+ .option("--content <text>", "Content to append (alternative to positional)")
319
+ .option("--inline", "Append without newline")
320
+ .action(async (content, opts, cmd) => {
321
+ const root = { ...cmd.optsWithGlobals(), ...opts };
322
+ root.content = root.content || content;
323
+ await dailyAppend(root);
324
+ });
325
+ dailyCmd
326
+ .command("prepend [content]")
327
+ .description("Prepend to daily note")
328
+ .option("--content <text>", "Content to prepend (alternative to positional)")
329
+ .option("--inline", "Prepend without newline")
330
+ .action(async (content, opts, cmd) => {
331
+ const root = { ...cmd.optsWithGlobals(), ...opts };
332
+ root.content = root.content || content;
333
+ await dailyPrepend(root);
334
+ });
335
+ // ── tag ─────────────────────────────────────────────────────────────
336
+ const tagCmd = program.command("tag").description("Tags and aliases");
337
+ tagCmd
338
+ .command("list")
339
+ .description("List tags in vault")
340
+ .option("--file <name>", "Filter by file")
341
+ .option("--counts", "Include tag counts")
342
+ .option("--total", "Return tag count")
343
+ .option("--sort <by>", "Sort by: name (default) or count")
344
+ .action(async (opts, cmd) => {
345
+ const root = { ...cmd.optsWithGlobals(), ...opts };
346
+ await tags(root);
347
+ });
348
+ tagCmd
349
+ .command("info")
350
+ .description("Get tag info")
351
+ .option("--name <tag>", "Tag name")
352
+ .option("--verbose", "Include file list")
353
+ .action(async (opts, cmd) => {
354
+ const root = { ...cmd.optsWithGlobals(), ...opts };
355
+ await tag(root);
356
+ });
357
+ tagCmd
358
+ .command("aliases")
359
+ .description("List aliases in vault")
360
+ .option("--file <name>", "Filter by file")
361
+ .option("--total", "Return alias count")
362
+ .option("--verbose", "Include file paths")
363
+ .action(async (opts, cmd) => {
364
+ const root = { ...cmd.optsWithGlobals(), ...opts };
365
+ await aliases(root);
366
+ });
367
+ // ── property ────────────────────────────────────────────────────────
368
+ const propCmd = program
369
+ .command("property")
370
+ .description("Frontmatter properties");
371
+ propCmd
372
+ .command("list")
373
+ .description("List properties in vault")
374
+ .option("--file <name>", "Filter by file")
375
+ .option("--counts", "Include counts")
376
+ .option("--total", "Return property count")
377
+ .option("--sort <by>", "Sort by: name (default) or count")
378
+ .action(async (opts, cmd) => {
379
+ const root = { ...cmd.optsWithGlobals(), ...opts };
380
+ await properties(root);
381
+ });
382
+ propCmd
383
+ .command("set")
384
+ .description("Set a property on a file")
385
+ .option("--name <name>", "Property name")
386
+ .option("--value <value>", "Property value")
387
+ .option("--file <name>", "Target file")
388
+ .action(async (opts, cmd) => {
389
+ const root = { ...cmd.optsWithGlobals(), ...opts };
390
+ await propertySet(root);
391
+ });
392
+ propCmd
393
+ .command("remove")
394
+ .description("Remove a property from a file")
395
+ .option("--name <name>", "Property name")
396
+ .option("--file <name>", "Target file")
397
+ .action(async (opts, cmd) => {
398
+ const root = { ...cmd.optsWithGlobals(), ...opts };
399
+ await propertyRemove(root);
400
+ });
401
+ propCmd
402
+ .command("read")
403
+ .description("Read a property value")
404
+ .option("--name <name>", "Property name")
405
+ .option("--file <name>", "Target file")
406
+ .action(async (opts, cmd) => {
407
+ const root = { ...cmd.optsWithGlobals(), ...opts };
408
+ await propertyRead(root);
409
+ });
410
+ // ── task ────────────────────────────────────────────────────────────
411
+ const taskCmd = program.command("task").description("Tasks and checklists");
412
+ taskCmd
413
+ .command("list")
414
+ .description("List tasks in vault")
415
+ .option("--file <name>", "Filter by file")
416
+ .option("--done", "Show completed tasks")
417
+ .option("--todo", "Show incomplete tasks")
418
+ .option("--total", "Return task count")
419
+ .option("--verbose", "Group by file with line numbers")
420
+ .option("--daily", "Show tasks from daily note")
421
+ .option("--status <char>", "Filter by status character")
422
+ .action(async (opts, cmd) => {
423
+ const root = { ...cmd.optsWithGlobals(), ...opts };
424
+ await tasks(root);
425
+ });
426
+ taskCmd
427
+ .command("show")
428
+ .description("Show or update a task")
429
+ .option("--file <name>", "File name")
430
+ .option("--line <n>", "Line number")
431
+ .option("--ref <path:line>", "Task reference")
432
+ .option("--toggle", "Toggle task status")
433
+ .option("--done", "Mark as done")
434
+ .option("--todo", "Mark as todo")
435
+ .option("--status <char>", "Set status character")
436
+ .option("--daily", "Daily note")
437
+ .action(async (opts, cmd) => {
438
+ const root = { ...cmd.optsWithGlobals(), ...opts };
439
+ await task(root);
440
+ });
441
+ // ── link ────────────────────────────────────────────────────────────
442
+ const linkCmd = program.command("link").description("Links and graph analysis");
443
+ linkCmd
444
+ .command("out")
445
+ .description("List outgoing links from a file")
446
+ .option("--file <name>", "Source file")
447
+ .option("--total", "Return link count")
448
+ .action(async (opts, cmd) => {
449
+ const root = { ...cmd.optsWithGlobals(), ...opts };
450
+ await links(root);
451
+ });
452
+ linkCmd
453
+ .command("back")
454
+ .description("List backlinks to a file")
455
+ .option("--file <name>", "Target file")
456
+ .option("--counts", "Include link counts")
457
+ .option("--total", "Return backlink count")
458
+ .action(async (opts, cmd) => {
459
+ const root = { ...cmd.optsWithGlobals(), ...opts };
460
+ await backlinks(root);
461
+ });
462
+ linkCmd
463
+ .command("unresolved")
464
+ .description("List unresolved links in vault")
465
+ .option("--total", "Return count")
466
+ .option("--counts", "Include link counts")
467
+ .option("--verbose", "Include source files")
468
+ .action(async (opts, cmd) => {
469
+ const root = { ...cmd.optsWithGlobals(), ...opts };
470
+ await unresolvedLinks(root);
471
+ });
472
+ linkCmd
473
+ .command("orphans")
474
+ .description("Files with no incoming links")
475
+ .option("--total", "Return count")
476
+ .action(async (opts, cmd) => {
477
+ const root = { ...cmd.optsWithGlobals(), ...opts };
478
+ await orphans(root);
479
+ });
480
+ linkCmd
481
+ .command("deadends")
482
+ .description("Files with no outgoing links")
483
+ .option("--total", "Return count")
484
+ .action(async (opts, cmd) => {
485
+ const root = { ...cmd.optsWithGlobals(), ...opts };
486
+ await deadends(root);
487
+ });
488
+ // ── base ────────────────────────────────────────────────────────────
489
+ const baseCmd = program
490
+ .command("base")
491
+ .description("Bases (database views over vault files)");
492
+ baseCmd
493
+ .command("list")
494
+ .description("List base files in vault")
495
+ .action(async (_opts, cmd) => {
496
+ const root = cmd.optsWithGlobals();
497
+ await bases(root);
498
+ });
499
+ baseCmd
500
+ .command("views")
501
+ .description("List views in a base")
502
+ .option("--file <name>", "Base file name")
503
+ .option("--path <path>", "Base file path")
504
+ .action(async (opts, cmd) => {
505
+ const root = { ...cmd.optsWithGlobals(), ...opts };
506
+ await baseViews(root);
507
+ });
508
+ baseCmd
509
+ .command("query")
510
+ .description("Query a base and return results")
511
+ .option("--file <name>", "Base file name")
512
+ .option("--path <path>", "Base file path")
513
+ .option("--view <name>", "View name to query")
514
+ .option("--format <type>", "Output format: json, csv, tsv, md, paths")
515
+ .action(async (opts, cmd) => {
516
+ const root = { ...cmd.optsWithGlobals(), ...opts };
517
+ await baseQuery(root);
518
+ });
519
+ baseCmd
520
+ .command("create")
521
+ .description("Create a new item in a base")
522
+ .option("--file <name>", "Base file name")
523
+ .option("--path <path>", "Base file path")
524
+ .option("--name <name>", "New file name")
525
+ .option("--content <text>", "Initial content")
526
+ .action(async (opts, cmd) => {
527
+ const root = { ...cmd.optsWithGlobals(), ...opts };
528
+ await baseCreate(root);
529
+ });
530
+ // ── canvas ──────────────────────────────────────────────────────────
531
+ const canvasCmd = program
532
+ .command("canvas")
533
+ .description("JSON Canvas operations");
534
+ canvasCmd
535
+ .command("list")
536
+ .description("List canvas files in vault")
537
+ .option("--total", "Return count")
538
+ .action(async (opts, cmd) => {
539
+ const root = { ...cmd.optsWithGlobals(), ...opts };
540
+ await canvases(root);
541
+ });
542
+ canvasCmd
543
+ .command("read")
544
+ .description("Read a canvas file")
545
+ .option("--file <name>", "Canvas file name")
546
+ .action(async (opts, cmd) => {
547
+ const root = { ...cmd.optsWithGlobals(), ...opts };
548
+ await canvasRead(root);
549
+ });
550
+ canvasCmd
551
+ .command("nodes")
552
+ .description("List nodes in a canvas")
553
+ .option("--file <name>", "Canvas file name")
554
+ .option("--type <type>", "Filter by type: text, file, link, group")
555
+ .action(async (opts, cmd) => {
556
+ const root = { ...cmd.optsWithGlobals(), ...opts };
557
+ await canvasNodes(root);
558
+ });
559
+ canvasCmd
560
+ .command("create")
561
+ .description("Create an empty canvas")
562
+ .option("--file <name>", "Canvas file name")
563
+ .option("--path <path>", "Folder path")
564
+ .action(async (opts, cmd) => {
565
+ const root = { ...cmd.optsWithGlobals(), ...opts };
566
+ await canvasCreate(root);
567
+ });
568
+ canvasCmd
569
+ .command("add-node")
570
+ .description("Add a node to a canvas")
571
+ .option("--file <name>", "Canvas file name")
572
+ .option("--type <type>", "Node type: text, file, link, group")
573
+ .option("--text <text>", "Text content (for text nodes)")
574
+ .option("--note-file <path>", "File path (for file nodes)")
575
+ .option("--subpath <subpath>", "Subpath (for file nodes)")
576
+ .option("--url <url>", "URL (for link nodes)")
577
+ .option("--label <label>", "Label (for group nodes)")
578
+ .option("--x <n>", "X position")
579
+ .option("--y <n>", "Y position")
580
+ .option("--width <n>", "Width")
581
+ .option("--height <n>", "Height")
582
+ .option("--color <color>", "Node color (1-6 or hex)")
583
+ .action(async (opts, cmd) => {
584
+ const root = { ...cmd.optsWithGlobals(), ...opts };
585
+ await canvasAddNode(root);
586
+ });
587
+ canvasCmd
588
+ .command("add-edge")
589
+ .description("Add an edge between nodes")
590
+ .option("--file <name>", "Canvas file name")
591
+ .option("--from <id>", "Source node ID (or prefix)")
592
+ .option("--to <id>", "Target node ID (or prefix)")
593
+ .option("--from-side <side>", "Source side: top, right, bottom, left")
594
+ .option("--to-side <side>", "Target side: top, right, bottom, left")
595
+ .option("--label <text>", "Edge label")
596
+ .option("--color <color>", "Edge color")
597
+ .action(async (opts, cmd) => {
598
+ const root = { ...cmd.optsWithGlobals(), ...opts };
599
+ await canvasAddEdge(root);
600
+ });
601
+ canvasCmd
602
+ .command("remove-node")
603
+ .description("Remove a node and its edges")
604
+ .option("--file <name>", "Canvas file name")
605
+ .option("--id <id>", "Node ID (or prefix)")
606
+ .action(async (opts, cmd) => {
607
+ const root = { ...cmd.optsWithGlobals(), ...opts };
608
+ await canvasRemoveNode(root);
609
+ });
610
+ // ── template ────────────────────────────────────────────────────────
611
+ const tmplCmd = program.command("template").description("Note templates");
612
+ tmplCmd
613
+ .command("list")
614
+ .description("List note templates")
615
+ .option("--total", "Return template count")
616
+ .action(async (opts, cmd) => {
617
+ const root = { ...cmd.optsWithGlobals(), ...opts };
618
+ await templates(root);
619
+ });
620
+ tmplCmd
621
+ .command("read")
622
+ .description("Read template content")
623
+ .option("--name <name>", "Template name")
624
+ .option("--resolve", "Resolve template variables")
625
+ .option("--title <title>", "Title for variable resolution")
626
+ .action(async (opts, cmd) => {
627
+ const root = { ...cmd.optsWithGlobals(), ...opts };
628
+ await templateRead(root);
629
+ });
630
+ tmplCmd
631
+ .command("insert")
632
+ .description("Insert template into a file")
633
+ .option("--name <name>", "Template name")
634
+ .option("--file <name>", "Target file")
635
+ .action(async (opts, cmd) => {
636
+ const root = { ...cmd.optsWithGlobals(), ...opts };
637
+ await templateInsert(root);
638
+ });
639
+ // ── bookmark ────────────────────────────────────────────────────────
640
+ const bookmarkCmd = program.command("bookmark").description("Bookmarks");
641
+ bookmarkCmd
642
+ .command("list")
643
+ .description("List bookmarks")
644
+ .option("--total", "Return bookmark count")
645
+ .option("--verbose", "Include bookmark types")
646
+ .action(async (opts, cmd) => {
647
+ const root = { ...cmd.optsWithGlobals(), ...opts };
648
+ await bookmarks(root);
649
+ });
650
+ bookmarkCmd
651
+ .command("add")
652
+ .description("Add a bookmark")
653
+ .option("--file <path>", "File to bookmark")
654
+ .option("--subpath <subpath>", "Subpath within file")
655
+ .option("--folder <path>", "Folder to bookmark")
656
+ .option("--search <query>", "Search query to bookmark")
657
+ .option("--url <url>", "URL to bookmark")
658
+ .option("--title <title>", "Bookmark title")
659
+ .action(async (opts, cmd) => {
660
+ const root = { ...cmd.optsWithGlobals(), ...opts };
661
+ await bookmark(root);
662
+ });
663
+ // ── config ──────────────────────────────────────────────────────────
664
+ const configCmd = program.command("config").description("Vault configuration");
665
+ configCmd
666
+ .command("show")
667
+ .description("Show current config")
668
+ .action(async (_opts, cmd) => {
669
+ const root = cmd.optsWithGlobals();
670
+ await configShow(root);
671
+ });
672
+ configCmd
673
+ .command("get")
674
+ .description("Get a config value")
675
+ .option("--key <path>", "Config key (dot notation, e.g. search.limit)")
676
+ .action(async (opts, cmd) => {
677
+ const root = { ...cmd.optsWithGlobals(), ...opts };
678
+ await configGet(root);
679
+ });
680
+ configCmd
681
+ .command("set")
682
+ .description("Set a config value")
683
+ .option("--key <path>", "Config key (dot notation, e.g. search.limit)")
684
+ .option("--value <value>", "Value to set (JSON or string)")
685
+ .action(async (opts, cmd) => {
686
+ const root = { ...cmd.optsWithGlobals(), ...opts };
687
+ await configSet(root);
688
+ });
689
+ // ── copy support ────────────────────────────────────────────────────
690
+ const origWrite = process.stdout.write.bind(process.stdout);
691
+ let capturedOutput = "";
692
+ program.hook("preAction", () => {
693
+ const opts = program.opts();
694
+ if (opts.copy) {
695
+ process.stdout.write = (chunk, ...args) => {
696
+ capturedOutput += chunk.toString();
697
+ return origWrite(chunk, ...args);
698
+ };
699
+ }
700
+ });
701
+ program
702
+ .parseAsync(process.argv)
703
+ .then(async () => {
704
+ const opts = program.opts();
705
+ if (opts.copy && capturedOutput.trim()) {
706
+ const { exec } = await import("node:child_process");
707
+ const proc = exec("pbcopy");
708
+ proc.stdin?.write(capturedOutput);
709
+ proc.stdin?.end();
710
+ }
711
+ })
712
+ .catch((err) => {
713
+ console.error("Fatal error:", err.message);
714
+ process.exit(1);
715
+ });