@cad0p/napkin 0.8.1-20260601.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.
Files changed (94) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +346 -0
  3. package/bin/napkin.js +19 -0
  4. package/package.json +77 -0
  5. package/src/commands/aliases.test.ts +64 -0
  6. package/src/commands/aliases.ts +31 -0
  7. package/src/commands/bases.test.ts +88 -0
  8. package/src/commands/bases.ts +174 -0
  9. package/src/commands/bookmarks.test.ts +103 -0
  10. package/src/commands/bookmarks.ts +78 -0
  11. package/src/commands/canvas.test.ts +257 -0
  12. package/src/commands/canvas.ts +255 -0
  13. package/src/commands/cli-positional.test.ts +208 -0
  14. package/src/commands/config.ts +71 -0
  15. package/src/commands/crud.test.ts +185 -0
  16. package/src/commands/crud.ts +251 -0
  17. package/src/commands/daily.test.ts +105 -0
  18. package/src/commands/daily.ts +74 -0
  19. package/src/commands/exit-codes.test.ts +235 -0
  20. package/src/commands/files.test.ts +78 -0
  21. package/src/commands/files.ts +167 -0
  22. package/src/commands/graph.ts +500 -0
  23. package/src/commands/init.test.ts +299 -0
  24. package/src/commands/init.ts +70 -0
  25. package/src/commands/links.test.ts +98 -0
  26. package/src/commands/links.ts +142 -0
  27. package/src/commands/outline.test.ts +48 -0
  28. package/src/commands/outline.ts +61 -0
  29. package/src/commands/overview.test.ts +201 -0
  30. package/src/commands/overview.ts +63 -0
  31. package/src/commands/properties.test.ts +108 -0
  32. package/src/commands/properties.ts +146 -0
  33. package/src/commands/search.test.ts +273 -0
  34. package/src/commands/search.ts +71 -0
  35. package/src/commands/tags.test.ts +76 -0
  36. package/src/commands/tags.ts +70 -0
  37. package/src/commands/tasks.test.ts +137 -0
  38. package/src/commands/tasks.ts +136 -0
  39. package/src/commands/templates.test.ts +99 -0
  40. package/src/commands/templates.ts +94 -0
  41. package/src/commands/vault.test.ts +63 -0
  42. package/src/commands/vault.ts +19 -0
  43. package/src/commands/wordcount.test.ts +53 -0
  44. package/src/commands/wordcount.ts +55 -0
  45. package/src/core/aliases.ts +34 -0
  46. package/src/core/bases.ts +93 -0
  47. package/src/core/bookmarks.ts +49 -0
  48. package/src/core/canvas.ts +226 -0
  49. package/src/core/config.ts +42 -0
  50. package/src/core/core.test.ts +326 -0
  51. package/src/core/crud.ts +198 -0
  52. package/src/core/daily.ts +135 -0
  53. package/src/core/files.ts +64 -0
  54. package/src/core/init.ts +205 -0
  55. package/src/core/links.ts +78 -0
  56. package/src/core/outline.ts +16 -0
  57. package/src/core/overview.ts +524 -0
  58. package/src/core/properties.ts +93 -0
  59. package/src/core/search.ts +208 -0
  60. package/src/core/tags.ts +56 -0
  61. package/src/core/tasks.ts +156 -0
  62. package/src/core/templates.ts +91 -0
  63. package/src/core/vault.ts +46 -0
  64. package/src/core/wordcount.ts +24 -0
  65. package/src/index.ts +28 -0
  66. package/src/main.ts +852 -0
  67. package/src/sdk.test.ts +392 -0
  68. package/src/sdk.ts +464 -0
  69. package/src/templates/coding.ts +106 -0
  70. package/src/templates/company.ts +123 -0
  71. package/src/templates/index.ts +21 -0
  72. package/src/templates/personal.ts +93 -0
  73. package/src/templates/product.ts +125 -0
  74. package/src/templates/research.ts +116 -0
  75. package/src/templates/types.ts +7 -0
  76. package/src/types/glimpseui.d.ts +18 -0
  77. package/src/types.d.ts +25 -0
  78. package/src/utils/bases.test.ts +646 -0
  79. package/src/utils/bases.ts +792 -0
  80. package/src/utils/config.ts +173 -0
  81. package/src/utils/exit-codes.ts +5 -0
  82. package/src/utils/files.test.ts +384 -0
  83. package/src/utils/files.ts +355 -0
  84. package/src/utils/formula.ts +511 -0
  85. package/src/utils/frontmatter.test.ts +70 -0
  86. package/src/utils/frontmatter.ts +47 -0
  87. package/src/utils/markdown.test.ts +95 -0
  88. package/src/utils/markdown.ts +115 -0
  89. package/src/utils/output.ts +65 -0
  90. package/src/utils/search-cache.test.ts +117 -0
  91. package/src/utils/search-cache.ts +71 -0
  92. package/src/utils/test-helpers.ts +49 -0
  93. package/src/utils/vault.test.ts +236 -0
  94. package/src/utils/vault.ts +186 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Michael Liv
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,346 @@
1
+ # napkin
2
+
3
+ 🧻 Knowledge system for agents. Local-first, file-based, progressively disclosed.
4
+
5
+ Every great idea started on a napkin.
6
+
7
+ ```bash
8
+ # pnpm
9
+ pnpm add -g @cad0p/napkin
10
+
11
+ # npm
12
+ npm install -g @cad0p/napkin
13
+ ```
14
+
15
+ ---
16
+
17
+ ## Quick Start
18
+
19
+ ```bash
20
+ # Initialize a vault
21
+ napkin init --template coding
22
+
23
+ # See what's in it
24
+ napkin overview
25
+
26
+ # Search for something
27
+ napkin search "authentication"
28
+
29
+ # Read a file
30
+ napkin read "Architecture"
31
+
32
+ # Write
33
+ napkin create "Decision" --template Decision
34
+ napkin append "Decision" "We chose Postgres."
35
+ napkin daily append "- [ ] Review PR"
36
+ ```
37
+
38
+ ---
39
+
40
+ ## SDK
41
+
42
+ napkin is also a library. No CLI, no stdout - just data:
43
+
44
+ ```typescript
45
+ import { Napkin } from "napkin-ai";
46
+
47
+ // Always works - creates bare vault if needed
48
+ const n = new Napkin("/path/to/project");
49
+
50
+ // Progressive disclosure
51
+ const overview = n.overview();
52
+ const results = n.search("authentication");
53
+ const file = n.read("Architecture");
54
+
55
+ // Write
56
+ n.create({ name: "New Note", content: "# Hello" });
57
+ n.append("New Note", "\nMore content");
58
+
59
+ // Daily notes
60
+ n.dailyEnsure();
61
+ n.dailyAppend("- Met with team");
62
+
63
+ // Everything else
64
+ n.tags();
65
+ n.tasks({ todo: true });
66
+ n.linksBack("Architecture");
67
+ n.outline("Architecture");
68
+ n.properties();
69
+ n.bookmarks();
70
+ n.config();
71
+ ```
72
+
73
+ Scaffold with a template:
74
+
75
+ ```typescript
76
+ Napkin.scaffold("/path/to/project", { template: "coding" });
77
+ Napkin.vaultTemplates(); // list available templates
78
+ ```
79
+
80
+ All SDK methods return typed data and throw errors on failure. No `console.log`, no `process.exit`.
81
+
82
+ ---
83
+
84
+ ## Progressive Disclosure
85
+
86
+ napkin is designed as a memory system for agents. Instead of dumping the full vault into context, it reveals information gradually:
87
+
88
+ | Level | Command | Tokens | What it does |
89
+ |-------|---------|--------|-------------|
90
+ | 0 | `NAPKIN.md` | ~200 | Project context note |
91
+ | 1 | `napkin overview` | ~1-2k | L0 + vault map with TF-IDF keywords |
92
+ | 2 | `napkin search <query>` | ~2-5k | Ranked results with snippets |
93
+ | 3 | `napkin read <file>` | ~5-20k | Full file content |
94
+
95
+ ## Benchmarks
96
+
97
+ napkin includes agentic retrieval benchmarks in `bench/`. The headline result is [LongMemEval](https://arxiv.org/abs/2410.10813) (ICLR 2025), which tests long-term conversational memory across 500 questions.
98
+
99
+ | Dataset | Sessions | pi + napkin | Best prior system | GPT-4o full context |
100
+ |---------|----------|-------------|-------------------|---------------------|
101
+ | Oracle | 1-6 | **92.0%** | 92.4% | 92.4% |
102
+ | S | ~40 | **91.0%** | 86% | 64% |
103
+ | M | ~500 | **83.0%** | 72% | n/a |
104
+
105
+ Zero preprocessing. No embeddings, no graphs, no summaries. Just BM25 search on markdown files.
106
+
107
+ See [`bench/README.md`](bench/README.md) for details and usage.
108
+
109
+ ---
110
+
111
+ ## Vault Structure
112
+
113
+ `.napkin/` holds config. Content lives in the project directory alongside `.obsidian/`:
114
+
115
+ ```
116
+ my-project/
117
+ .napkin/ # napkin config
118
+ config.json # Unified config (syncs to .obsidian/)
119
+ .obsidian/ # Obsidian config (auto-generated)
120
+ NAPKIN.md # Context note (Level 0)
121
+ decisions/ # Template-defined directories
122
+ architecture/
123
+ Templates/ # Note templates
124
+ src/ # Your project (not in vault)
125
+ ```
126
+
127
+ ## Templates
128
+
129
+ Scaffold a vault with a domain-specific structure:
130
+
131
+ ```bash
132
+ napkin init --template coding # decisions/, architecture/, guides/, changelog/
133
+ napkin init --template company # people/, projects/, runbooks/, infrastructure/
134
+ napkin init --template product # features/, roadmap/, research/, specs/, releases/
135
+ napkin init --template personal # people/, projects/, areas/, references/
136
+ napkin init --template research # papers/, concepts/, questions/, experiments/
137
+ ```
138
+
139
+ Each template includes directory structure, `_about.md` files, Obsidian note templates, and a `NAPKIN.md` skeleton.
140
+
141
+ ---
142
+
143
+ ## For Agents
144
+
145
+ Every command supports `--json` for structured output and `-q` for raw output:
146
+
147
+ ```bash
148
+ napkin overview --json # Structured vault map
149
+ napkin search "auth" --json # Ranked results as JSON
150
+ napkin read "Note" -q # Raw markdown, nothing else
151
+ ```
152
+
153
+ ---
154
+
155
+ ## CLI Reference
156
+
157
+ ### Global Flags
158
+
159
+ | Flag | Description |
160
+ |---|---|
161
+ | `--json` | Output as JSON |
162
+ | `-q, --quiet` | Suppress output |
163
+ | `--vault <path>` | Vault path (default: auto-detect from cwd) |
164
+ | `--copy` | Copy output to clipboard |
165
+
166
+ ### Core
167
+
168
+ ```bash
169
+ napkin vault # Vault info
170
+ napkin overview # Vault map with keywords
171
+ napkin read <file> # Read file contents
172
+ napkin create "Note" $'# Hello\nFirst note.' # Create with content
173
+ napkin append "Note" $'\n## Update\nNew info.' # Append to file
174
+ napkin prepend "Note" $'---\ntags: [new]\n---' # Prepend frontmatter
175
+ napkin move "Note" Archive # Move to folder
176
+ napkin rename "Note" "Renamed" # Rename file
177
+ napkin delete "Note" # Move to .trash
178
+ napkin search "meeting" # Ranked search with snippets
179
+ napkin search "TODO" --no-snippets # Files only
180
+ echo "piped content" | napkin append "Note" # Stdin support
181
+ ```
182
+
183
+ ### Files & Folders - `napkin file`
184
+
185
+ ```bash
186
+ napkin file info <name> # File info (path, size, dates)
187
+ napkin file list # List all files
188
+ napkin file list --ext md # Filter by extension
189
+ napkin file list --folder Projects # Filter by folder
190
+ napkin file folder <path> # Folder info
191
+ napkin file folders # List all folders
192
+ napkin file outline "note" # Heading tree
193
+ napkin file wordcount "note" # Word + character count
194
+ ```
195
+
196
+ ### Daily Notes - `napkin daily`
197
+
198
+ ```bash
199
+ napkin daily today # Create today's daily note
200
+ napkin daily path # Print daily note path
201
+ napkin daily read # Print daily note contents
202
+ napkin daily append "- [ ] Buy groceries"
203
+ napkin daily prepend "## Morning"
204
+ ```
205
+
206
+ ### Tags - `napkin tag`
207
+
208
+ ```bash
209
+ napkin tag list # List all tags
210
+ napkin tag list --counts # With occurrence counts
211
+ napkin tag list --sort count # Sort by frequency
212
+ napkin tag info --name "project" # Tag info
213
+ napkin tag aliases # List all aliases
214
+ ```
215
+
216
+ ### Properties - `napkin property`
217
+
218
+ ```bash
219
+ napkin property list # List all properties
220
+ napkin property list --file "note" # Properties for a file
221
+ napkin property read --file "note" --name title
222
+ napkin property set --file "note" --name status --value done
223
+ napkin property remove --file "note" --name status
224
+ ```
225
+
226
+ ### Tasks - `napkin task`
227
+
228
+ ```bash
229
+ napkin task list # List all tasks
230
+ napkin task list --todo # Incomplete only
231
+ napkin task list --done # Completed only
232
+ napkin task list --daily # Today's daily note tasks
233
+ napkin task show --file "note" --line 3 --toggle
234
+ ```
235
+
236
+ ### Links - `napkin link`
237
+
238
+ ```bash
239
+ napkin link out --file "note" # Outgoing links
240
+ napkin link back --file "note" # Backlinks
241
+ napkin link unresolved # Broken links
242
+ napkin link orphans # No incoming links
243
+ napkin link deadends # No outgoing links
244
+ ```
245
+
246
+ ### Bases - `napkin base`
247
+
248
+ ```bash
249
+ napkin base list # List .base files
250
+ napkin base views --file "projects" # List views
251
+ napkin base query --file "projects" # Query default view
252
+ napkin base query --file "projects" --view "Active" --format csv
253
+ napkin base create --file "projects" --name "New Item"
254
+ ```
255
+
256
+ ### Canvas - `napkin canvas`
257
+
258
+ ```bash
259
+ napkin canvas list # List .canvas files
260
+ napkin canvas read --file "Board" # Dump canvas
261
+ napkin canvas nodes --file "Board" # List nodes
262
+ napkin canvas create --file "Board" # Create empty canvas
263
+ napkin canvas add-node --file "Board" --type text --text "# Hello"
264
+ napkin canvas add-edge --file "Board" --from abc1 --to def2
265
+ napkin canvas remove-node --file "Board" --id abc1
266
+ ```
267
+
268
+ ### Templates - `napkin template`
269
+
270
+ ```bash
271
+ napkin template list # List note templates
272
+ napkin template read --name "Daily Note"
273
+ napkin template insert --file "note" --name "Template"
274
+ ```
275
+
276
+ ### Bookmarks - `napkin bookmark`
277
+
278
+ ```bash
279
+ napkin bookmark list # List bookmarks
280
+ napkin bookmark add --file "note" # Bookmark a file
281
+ ```
282
+
283
+ ### Config - `napkin config`
284
+
285
+ ```bash
286
+ napkin config show # Show full config
287
+ napkin config get --key search.limit # Get a value
288
+ napkin config set --key search.limit --value 50
289
+ ```
290
+
291
+ ### Graph - `napkin graph`
292
+
293
+ ```bash
294
+ napkin graph # Interactive vault graph
295
+ ```
296
+
297
+ Force-directed graph of vault notes and wikilinks. Click nodes to read content in a sidebar.
298
+
299
+ ---
300
+
301
+ ## File Resolution
302
+
303
+ Files can be referenced two ways:
304
+ - **By name** (wikilink-style): `"Active Projects"` - searches all `.md` files by basename
305
+ - **By path**: `"Projects/Active Projects.md"` - exact path from vault root
306
+
307
+ ---
308
+
309
+ ## Architecture
310
+
311
+ ```
312
+ src/
313
+ index.ts # SDK exports: Napkin class + all types
314
+ sdk.ts # Napkin class wrapping core modules
315
+ main.ts # CLI entry (Commander) - thin wrapper
316
+ core/ # Pure logic, returns data, no stdout
317
+ commands/ # CLI wrappers: parse args → sdk → format + print
318
+ utils/ # Shared utilities (files, frontmatter, markdown, etc.)
319
+ ```
320
+
321
+ Core modules never call `console.log`, `process.exit`, or import output utilities. They return typed data and throw errors. The CLI commands are thin wrappers that instantiate the SDK, call methods, and format the output.
322
+
323
+ ---
324
+
325
+ ## Pi Integration
326
+
327
+ For [pi](https://github.com/badlogic/pi) users, install [pi-napkin](https://github.com/cad0p/pi-napkin) — vault context injection, `kb_search`/`kb_read` tools, and automatic distillation.
328
+
329
+ ```bash
330
+ pi install git:github.com/cad0p/pi-napkin
331
+ ```
332
+
333
+ ---
334
+
335
+ ## Development
336
+
337
+ ```bash
338
+ bun install
339
+ bun run dev -- vault --json
340
+ bun test
341
+ bun run check
342
+ ```
343
+
344
+ ## License
345
+
346
+ MIT
package/bin/napkin.js ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+ // Launcher for the napkin CLI.
3
+ //
4
+ // napkin ships its TypeScript sources directly (no committed `dist/`). Node's
5
+ // built-in type stripping refuses to run `.ts` files under `node_modules`
6
+ // (ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING), so this tiny `.js` stub —
7
+ // which IS allowed under node_modules — uses jiti to load the real `.ts`
8
+ // entry point. jiti strips types AND resolves the package's `.js` import
9
+ // specifiers to their `.ts` sources, all in pure JS (no native toolchain).
10
+ //
11
+ // jiti is resolved relative to THIS file (not the caller's cwd) so the bin
12
+ // works no matter where it is invoked from.
13
+ import { createRequire } from "node:module";
14
+
15
+ const require = createRequire(import.meta.url);
16
+ const { createJiti } = require("jiti");
17
+
18
+ const jiti = createJiti(import.meta.url, { moduleCache: false });
19
+ await jiti.import("../src/main.ts");
package/package.json ADDED
@@ -0,0 +1,77 @@
1
+ {
2
+ "name": "@cad0p/napkin",
3
+ "version": "0.8.1-20260601.0",
4
+ "publishConfig": {
5
+ "access": "public",
6
+ "registry": "https://registry.npmjs.org"
7
+ },
8
+ "description": "🧻 Knowledge system for agents. Local-first, file-based, progressively disclosed.",
9
+ "type": "module",
10
+ "main": "src/index.ts",
11
+ "exports": {
12
+ ".": "./src/index.ts"
13
+ },
14
+ "bin": {
15
+ "napkin": "./bin/napkin.js"
16
+ },
17
+ "files": [
18
+ "src",
19
+ "bin"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsc",
23
+ "typecheck": "tsc --noEmit",
24
+ "dev": "bun run src/main.ts",
25
+ "build:bun": "bun build --compile src/main.ts --outfile napkin",
26
+ "build:bun-linux": "bun build --compile --target=bun-linux-x64 src/main.ts --outfile napkin-linux-x64",
27
+ "build:bun-mac-arm": "bun build --compile --target=bun-darwin-arm64 src/main.ts --outfile napkin-darwin-arm64",
28
+ "build:bun-mac-x64": "bun build --compile --target=bun-darwin-x64 src/main.ts --outfile napkin-darwin-x64",
29
+ "test": "bun test",
30
+ "format": "bunx biome format --write src/",
31
+ "lint": "bunx biome lint src/",
32
+ "check": "bunx biome check src/"
33
+ },
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/cad0p/napkin.git"
37
+ },
38
+ "homepage": "https://github.com/cad0p/napkin#readme",
39
+ "bugs": {
40
+ "url": "https://github.com/cad0p/napkin/issues"
41
+ },
42
+ "keywords": [
43
+ "obsidian",
44
+ "cli",
45
+ "markdown",
46
+ "vault",
47
+ "notes",
48
+ "tasks",
49
+ "tags",
50
+ "search",
51
+ "local-first"
52
+ ],
53
+ "license": "MIT",
54
+ "devDependencies": {
55
+ "@biomejs/biome": "^2.3.14",
56
+ "@types/bun": "latest",
57
+ "@types/js-yaml": "^4.0.9",
58
+ "@types/node": "^25.6.0",
59
+ "typescript": "^5.8.0"
60
+ },
61
+ "dependencies": {
62
+ "jiti": "^2.6.0",
63
+ "chalk": "^5.6.2",
64
+ "commander": "^14.0.3",
65
+ "gray-matter": "^4.0.3",
66
+ "jexl": "^2.3.0",
67
+ "js-yaml": "^4.1.0",
68
+ "minisearch": "^7.2.0",
69
+ "sql.js": "^1.14.0"
70
+ },
71
+ "optionalDependencies": {
72
+ "glimpseui": "^0.3.7"
73
+ },
74
+ "trustedDependencies": [
75
+ "glimpseui"
76
+ ]
77
+ }
@@ -0,0 +1,64 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
+ import { createTempVault } from "../utils/test-helpers.js";
3
+ import { aliases } from "./aliases.js";
4
+
5
+ let v: { path: string; vaultPath: string; cleanup: () => void };
6
+
7
+ async function captureJson(
8
+ fn: () => Promise<void>,
9
+ ): Promise<Record<string, unknown>> {
10
+ const orig = console.log;
11
+ const logs: string[] = [];
12
+ console.log = (...args: unknown[]) => logs.push(args.map(String).join(" "));
13
+ await fn();
14
+ console.log = orig;
15
+ return JSON.parse(logs.join(""));
16
+ }
17
+
18
+ beforeEach(() => {
19
+ v = createTempVault({
20
+ "note1.md": "---\naliases:\n - Alpha\n - A1\n---\nBody",
21
+ "note2.md": "---\naliases: Beta\n---\nBody",
22
+ "note3.md": "No aliases here",
23
+ });
24
+ });
25
+
26
+ afterEach(() => {
27
+ v.cleanup();
28
+ });
29
+
30
+ describe("aliases", () => {
31
+ test("lists all aliases", async () => {
32
+ const data = await captureJson(() =>
33
+ aliases({ json: true, vault: v.path }),
34
+ );
35
+ const a = data.aliases as string[];
36
+ expect(a).toContain("Alpha");
37
+ expect(a).toContain("A1");
38
+ expect(a).toContain("Beta");
39
+ expect(a.length).toBe(3);
40
+ });
41
+
42
+ test("returns total", async () => {
43
+ const data = await captureJson(() =>
44
+ aliases({ json: true, vault: v.path, total: true }),
45
+ );
46
+ expect(data.total).toBe(3);
47
+ });
48
+
49
+ test("filters by file", async () => {
50
+ const data = await captureJson(() =>
51
+ aliases({ json: true, vault: v.path, file: "note1" }),
52
+ );
53
+ const a = data.aliases as string[];
54
+ expect(a).toEqual(["Alpha", "A1"]);
55
+ });
56
+
57
+ test("verbose includes file paths", async () => {
58
+ const data = await captureJson(() =>
59
+ aliases({ json: true, vault: v.path, verbose: true }),
60
+ );
61
+ const a = data.aliases as { alias: string; file: string }[];
62
+ expect(a[0].file).toBeTruthy();
63
+ });
64
+ });
@@ -0,0 +1,31 @@
1
+ import { Napkin } from "../sdk.js";
2
+ import { dim, type OutputOptions, output } from "../utils/output.js";
3
+
4
+ export async function aliases(
5
+ opts: OutputOptions & {
6
+ vault?: string;
7
+ file?: string;
8
+ total?: boolean;
9
+ verbose?: boolean;
10
+ },
11
+ ) {
12
+ const n = new Napkin(opts.vault || process.cwd());
13
+ const result = n.aliases(opts.file);
14
+
15
+ output(opts, {
16
+ json: () => {
17
+ if (opts.total) return { total: result.length };
18
+ if (opts.verbose) return { aliases: result };
19
+ return { aliases: result.map((r) => r.alias) };
20
+ },
21
+ human: () => {
22
+ if (opts.total) {
23
+ console.log(result.length);
24
+ } else {
25
+ for (const r of result) {
26
+ console.log(opts.verbose ? `${r.alias}\t${dim(r.file)}` : r.alias);
27
+ }
28
+ }
29
+ },
30
+ });
31
+ }
@@ -0,0 +1,88 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
+ import { createTempVault } from "../utils/test-helpers.js";
3
+ import { baseQuery, bases, baseViews } from "./bases.js";
4
+
5
+ let v: { path: string; vaultPath: string; cleanup: () => void };
6
+
7
+ async function captureJson(
8
+ fn: () => Promise<void>,
9
+ ): Promise<Record<string, unknown>> {
10
+ const orig = console.log;
11
+ const logs: string[] = [];
12
+ console.log = (...args: unknown[]) => logs.push(args.map(String).join(" "));
13
+ await fn();
14
+ console.log = orig;
15
+ return JSON.parse(logs.join(""));
16
+ }
17
+
18
+ beforeEach(() => {
19
+ v = createTempVault({
20
+ "Projects/alpha.md": "---\ntitle: Alpha\nstatus: active\n---\n# Alpha",
21
+ "Projects/beta.md": "---\ntitle: Beta\nstatus: done\n---\n# Beta",
22
+ "Notes/random.md": "---\ntitle: Random\n---\n# Random",
23
+ "projects.base":
24
+ 'filters:\n file.inFolder("Projects")\nviews:\n - type: table\n name: "All Projects"\n - type: table\n name: "Active"\n filters:\n \'status == "active"\'',
25
+ });
26
+ });
27
+
28
+ afterEach(() => {
29
+ v.cleanup();
30
+ });
31
+
32
+ describe("bases", () => {
33
+ test("lists .base files", async () => {
34
+ const data = await captureJson(() => bases({ json: true, vault: v.path }));
35
+ const b = data.bases as string[];
36
+ expect(b).toContain("projects.base");
37
+ });
38
+ });
39
+
40
+ describe("baseViews", () => {
41
+ test("lists views in a base", async () => {
42
+ const data = await captureJson(() =>
43
+ baseViews({ json: true, vault: v.path, file: "projects" }),
44
+ );
45
+ const views = data.views as { name: string; type: string }[];
46
+ expect(views.length).toBe(2);
47
+ expect(views[0].name).toBe("All Projects");
48
+ expect(views[1].name).toBe("Active");
49
+ });
50
+ });
51
+
52
+ describe("baseQuery", () => {
53
+ test("queries default view", async () => {
54
+ const data = await captureJson(() =>
55
+ baseQuery({ json: true, vault: v.path, file: "projects" }),
56
+ );
57
+ const rows = data.rows as Record<string, unknown>[];
58
+ expect(rows.length).toBe(2); // Alpha + Beta in Projects folder
59
+ });
60
+
61
+ test("queries named view with filters", async () => {
62
+ const data = await captureJson(() =>
63
+ baseQuery({
64
+ json: true,
65
+ vault: v.path,
66
+ file: "projects",
67
+ view: "Active",
68
+ }),
69
+ );
70
+ const rows = data.rows as Record<string, unknown>[];
71
+ expect(rows.length).toBe(1);
72
+ expect(rows[0].title).toBe("Alpha");
73
+ });
74
+
75
+ test("outputs as paths", async () => {
76
+ const data = await captureJson(() =>
77
+ baseQuery({
78
+ json: true,
79
+ vault: v.path,
80
+ file: "projects",
81
+ format: "paths",
82
+ }),
83
+ );
84
+ const paths = data.paths as string[];
85
+ expect(paths).toContain("Projects/alpha.md");
86
+ expect(paths).toContain("Projects/beta.md");
87
+ });
88
+ });