@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.
- package/LICENSE +21 -0
- package/README.md +346 -0
- package/bin/napkin.js +19 -0
- package/package.json +77 -0
- package/src/commands/aliases.test.ts +64 -0
- package/src/commands/aliases.ts +31 -0
- package/src/commands/bases.test.ts +88 -0
- package/src/commands/bases.ts +174 -0
- package/src/commands/bookmarks.test.ts +103 -0
- package/src/commands/bookmarks.ts +78 -0
- package/src/commands/canvas.test.ts +257 -0
- package/src/commands/canvas.ts +255 -0
- package/src/commands/cli-positional.test.ts +208 -0
- package/src/commands/config.ts +71 -0
- package/src/commands/crud.test.ts +185 -0
- package/src/commands/crud.ts +251 -0
- package/src/commands/daily.test.ts +105 -0
- package/src/commands/daily.ts +74 -0
- package/src/commands/exit-codes.test.ts +235 -0
- package/src/commands/files.test.ts +78 -0
- package/src/commands/files.ts +167 -0
- package/src/commands/graph.ts +500 -0
- package/src/commands/init.test.ts +299 -0
- package/src/commands/init.ts +70 -0
- package/src/commands/links.test.ts +98 -0
- package/src/commands/links.ts +142 -0
- package/src/commands/outline.test.ts +48 -0
- package/src/commands/outline.ts +61 -0
- package/src/commands/overview.test.ts +201 -0
- package/src/commands/overview.ts +63 -0
- package/src/commands/properties.test.ts +108 -0
- package/src/commands/properties.ts +146 -0
- package/src/commands/search.test.ts +273 -0
- package/src/commands/search.ts +71 -0
- package/src/commands/tags.test.ts +76 -0
- package/src/commands/tags.ts +70 -0
- package/src/commands/tasks.test.ts +137 -0
- package/src/commands/tasks.ts +136 -0
- package/src/commands/templates.test.ts +99 -0
- package/src/commands/templates.ts +94 -0
- package/src/commands/vault.test.ts +63 -0
- package/src/commands/vault.ts +19 -0
- package/src/commands/wordcount.test.ts +53 -0
- package/src/commands/wordcount.ts +55 -0
- package/src/core/aliases.ts +34 -0
- package/src/core/bases.ts +93 -0
- package/src/core/bookmarks.ts +49 -0
- package/src/core/canvas.ts +226 -0
- package/src/core/config.ts +42 -0
- package/src/core/core.test.ts +326 -0
- package/src/core/crud.ts +198 -0
- package/src/core/daily.ts +135 -0
- package/src/core/files.ts +64 -0
- package/src/core/init.ts +205 -0
- package/src/core/links.ts +78 -0
- package/src/core/outline.ts +16 -0
- package/src/core/overview.ts +524 -0
- package/src/core/properties.ts +93 -0
- package/src/core/search.ts +208 -0
- package/src/core/tags.ts +56 -0
- package/src/core/tasks.ts +156 -0
- package/src/core/templates.ts +91 -0
- package/src/core/vault.ts +46 -0
- package/src/core/wordcount.ts +24 -0
- package/src/index.ts +28 -0
- package/src/main.ts +852 -0
- package/src/sdk.test.ts +392 -0
- package/src/sdk.ts +464 -0
- package/src/templates/coding.ts +106 -0
- package/src/templates/company.ts +123 -0
- package/src/templates/index.ts +21 -0
- package/src/templates/personal.ts +93 -0
- package/src/templates/product.ts +125 -0
- package/src/templates/research.ts +116 -0
- package/src/templates/types.ts +7 -0
- package/src/types/glimpseui.d.ts +18 -0
- package/src/types.d.ts +25 -0
- package/src/utils/bases.test.ts +646 -0
- package/src/utils/bases.ts +792 -0
- package/src/utils/config.ts +173 -0
- package/src/utils/exit-codes.ts +5 -0
- package/src/utils/files.test.ts +384 -0
- package/src/utils/files.ts +355 -0
- package/src/utils/formula.ts +511 -0
- package/src/utils/frontmatter.test.ts +70 -0
- package/src/utils/frontmatter.ts +47 -0
- package/src/utils/markdown.test.ts +95 -0
- package/src/utils/markdown.ts +115 -0
- package/src/utils/output.ts +65 -0
- package/src/utils/search-cache.test.ts +117 -0
- package/src/utils/search-cache.ts +71 -0
- package/src/utils/test-helpers.ts +49 -0
- package/src/utils/vault.test.ts +236 -0
- 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
|
+
});
|