napkin-ai 0.1.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 (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +194 -0
  3. package/dist/commands/aliases.d.ts +7 -0
  4. package/dist/commands/aliases.js +51 -0
  5. package/dist/commands/bases.d.ts +23 -0
  6. package/dist/commands/bases.js +192 -0
  7. package/dist/commands/bookmarks.d.ts +15 -0
  8. package/dist/commands/bookmarks.js +82 -0
  9. package/dist/commands/canvas.d.ts +49 -0
  10. package/dist/commands/canvas.js +264 -0
  11. package/dist/commands/crud.d.ts +40 -0
  12. package/dist/commands/crud.js +195 -0
  13. package/dist/commands/daily.d.ts +21 -0
  14. package/dist/commands/daily.js +142 -0
  15. package/dist/commands/files.d.ts +23 -0
  16. package/dist/commands/files.js +130 -0
  17. package/dist/commands/links.d.ts +26 -0
  18. package/dist/commands/links.js +149 -0
  19. package/dist/commands/onboard.d.ts +2 -0
  20. package/dist/commands/onboard.js +47 -0
  21. package/dist/commands/outline.d.ts +7 -0
  22. package/dist/commands/outline.js +46 -0
  23. package/dist/commands/properties.d.ts +24 -0
  24. package/dist/commands/properties.js +134 -0
  25. package/dist/commands/search.d.ts +11 -0
  26. package/dist/commands/search.js +85 -0
  27. package/dist/commands/tags.d.ts +13 -0
  28. package/dist/commands/tags.js +86 -0
  29. package/dist/commands/tasks.d.ts +22 -0
  30. package/dist/commands/tasks.js +164 -0
  31. package/dist/commands/templates.d.ts +16 -0
  32. package/dist/commands/templates.js +93 -0
  33. package/dist/commands/vault.d.ts +4 -0
  34. package/dist/commands/vault.js +51 -0
  35. package/dist/commands/wordcount.d.ts +7 -0
  36. package/dist/commands/wordcount.js +43 -0
  37. package/dist/main.d.ts +2 -0
  38. package/dist/main.js +571 -0
  39. package/dist/utils/bases.d.ts +61 -0
  40. package/dist/utils/bases.js +662 -0
  41. package/dist/utils/exit-codes.d.ts +5 -0
  42. package/dist/utils/exit-codes.js +5 -0
  43. package/dist/utils/files.d.ts +37 -0
  44. package/dist/utils/files.js +127 -0
  45. package/dist/utils/formula.d.ts +28 -0
  46. package/dist/utils/formula.js +462 -0
  47. package/dist/utils/frontmatter.d.ts +17 -0
  48. package/dist/utils/frontmatter.js +34 -0
  49. package/dist/utils/markdown.d.ts +31 -0
  50. package/dist/utils/markdown.js +80 -0
  51. package/dist/utils/output.d.ts +22 -0
  52. package/dist/utils/output.js +31 -0
  53. package/dist/utils/test-helpers.d.ts +7 -0
  54. package/dist/utils/test-helpers.js +30 -0
  55. package/dist/utils/vault.d.ts +14 -0
  56. package/dist/utils/vault.js +38 -0
  57. package/package.json +46 -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,194 @@
1
+ # napkin
2
+
3
+ 🧻 Obsidian-compatible CLI for agents.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g napkin
9
+ ```
10
+
11
+ Or build from source:
12
+
13
+ ```bash
14
+ git clone https://github.com/Michaelliv/napkin.git
15
+ cd napkin
16
+ bun install
17
+ bun run build:bun
18
+ ```
19
+
20
+ ## Usage
21
+
22
+ Run commands from inside an Obsidian vault (any directory containing `.obsidian/`):
23
+
24
+ ```bash
25
+ cd ~/my-vault
26
+ napkin vault
27
+ ```
28
+
29
+ Or specify the vault path:
30
+
31
+ ```bash
32
+ napkin --vault ~/my-vault vault
33
+ ```
34
+
35
+ ### Global Flags
36
+
37
+ | Flag | Description |
38
+ |---|---|
39
+ | `--json` | Output as JSON |
40
+ | `-q, --quiet` | Suppress output |
41
+ | `--vault <path>` | Vault path (default: auto-detect from cwd) |
42
+ | `--copy` | Copy output to clipboard |
43
+
44
+ ## Commands
45
+
46
+ ### Vault
47
+
48
+ ```bash
49
+ napkin vault # Show vault info (name, path, files, folders, size)
50
+ ```
51
+
52
+ ### Files & Folders
53
+
54
+ ```bash
55
+ napkin file <name> # Show file info
56
+ napkin files # List all files
57
+ napkin files --ext md # Filter by extension
58
+ napkin files --folder Projects # Filter by folder
59
+ napkin files --total # Count files
60
+ napkin folders # List folders
61
+ ```
62
+
63
+ ### File CRUD
64
+
65
+ ```bash
66
+ napkin read <file> # Read file contents
67
+ napkin create --name "Note" --content "Hello"
68
+ napkin create --path "Projects/new" --template "Meeting Note"
69
+ napkin append --file "Note" --content "More text"
70
+ napkin prepend --file "Note" --content "Top line"
71
+ napkin move --file "Note" --to Archive
72
+ napkin rename --file "Note" --name "Renamed"
73
+ napkin delete --file "Note" # Move to .trash
74
+ napkin delete --file "Note" --permanent
75
+ ```
76
+
77
+ ### Daily Notes
78
+
79
+ Reads config from `.obsidian/daily-notes.json` (folder, format, template).
80
+
81
+ ```bash
82
+ napkin daily # Create + open today's daily note
83
+ napkin daily:path # Print daily note path
84
+ napkin daily:read # Print daily note contents
85
+ napkin daily:append --content "- [ ] Buy groceries"
86
+ napkin daily:prepend --content "## Morning"
87
+ ```
88
+
89
+ ### Search
90
+
91
+ ```bash
92
+ napkin search --query "meeting" # Find files matching text
93
+ napkin search --query "TODO" --path Projects
94
+ napkin search --query "bug" --total # Count matches
95
+ napkin search:context --query "TODO" # Grep-style file:line:text output
96
+ ```
97
+
98
+ ### Tasks
99
+
100
+ ```bash
101
+ napkin tasks # List all tasks
102
+ napkin tasks --todo # Incomplete only
103
+ napkin tasks --done # Completed only
104
+ napkin tasks --daily # Today's daily note tasks
105
+ napkin tasks --file "Project A" # Tasks in specific file
106
+ napkin tasks --verbose # Group by file with line numbers
107
+ napkin task --file "note" --line 3 # Show task info
108
+ napkin task --file "note" --line 3 --toggle # Toggle ✓/○
109
+ napkin task --file "note" --line 3 --done # Mark done
110
+ napkin task --ref "note.md:3" --todo # Mark todo
111
+ ```
112
+
113
+ ### Tags
114
+
115
+ ```bash
116
+ napkin tags # List all tags
117
+ napkin tags --counts # With occurrence counts
118
+ napkin tags --sort count # Sort by frequency
119
+ napkin tag --name "project" # Tag info
120
+ napkin tag --name "project" --verbose # With file list
121
+ ```
122
+
123
+ ### Properties (Frontmatter)
124
+
125
+ ```bash
126
+ napkin properties # List all properties in vault
127
+ napkin properties --file "note" # Properties for a file
128
+ napkin properties --counts # With occurrence counts
129
+ napkin property:read --file "note" --name title
130
+ napkin property:set --file "note" --name status --value done
131
+ napkin property:remove --file "note" --name status
132
+ ```
133
+
134
+ ### Links
135
+
136
+ ```bash
137
+ napkin backlinks --file "note" # Files linking to this file
138
+ napkin links --file "note" # Outgoing links from file
139
+ napkin unresolved # Broken links in vault
140
+ napkin orphans # Files with no incoming links
141
+ napkin deadends # Files with no outgoing links
142
+ ```
143
+
144
+ ### Outline
145
+
146
+ ```bash
147
+ napkin outline --file "note" # Headings (tree format)
148
+ napkin outline --file "note" --format md
149
+ napkin outline --file "note" --format json
150
+ ```
151
+
152
+ ### Templates
153
+
154
+ ```bash
155
+ napkin templates # List templates
156
+ napkin template:read --name "Daily Note"
157
+ napkin template:read --name "Meeting" --resolve --title "Standup"
158
+ ```
159
+
160
+ ### Word Count
161
+
162
+ ```bash
163
+ napkin wordcount --file "note" # Words + characters
164
+ napkin wordcount --file "note" --words
165
+ ```
166
+
167
+ ### Agent Onboarding
168
+
169
+ ```bash
170
+ napkin onboard # Print agent instructions
171
+ ```
172
+
173
+ ## File Resolution
174
+
175
+ Files can be referenced two ways:
176
+ - **By name** (wikilink-style): `--file "Active Projects"` — searches all `.md` files by basename
177
+ - **By path**: `--file "Projects/Active Projects.md"` — exact path from vault root
178
+
179
+ ## For AI Agents
180
+
181
+ Every command supports `--json` for structured output. Run `napkin onboard` to get copy-paste instructions for your agent config.
182
+
183
+ ## Development
184
+
185
+ ```bash
186
+ bun install
187
+ bun run dev -- vault --json # Run in dev mode
188
+ bun test # Run tests
189
+ bun run check # Lint + format
190
+ ```
191
+
192
+ ## License
193
+
194
+ MIT
@@ -0,0 +1,7 @@
1
+ import { type OutputOptions } from "../utils/output.js";
2
+ export declare function aliases(opts: OutputOptions & {
3
+ vault?: string;
4
+ file?: string;
5
+ total?: boolean;
6
+ verbose?: boolean;
7
+ }): Promise<void>;
@@ -0,0 +1,51 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { listFiles, resolveFile } from "../utils/files.js";
4
+ import { parseFrontmatter } from "../utils/frontmatter.js";
5
+ import { dim, output } from "../utils/output.js";
6
+ import { findVault } from "../utils/vault.js";
7
+ function collectAliases(vaultPath, fileFilter) {
8
+ const files = fileFilter
9
+ ? (() => {
10
+ const r = resolveFile(vaultPath, fileFilter);
11
+ return r ? [r] : [];
12
+ })()
13
+ : listFiles(vaultPath, { ext: "md" });
14
+ const results = [];
15
+ for (const file of files) {
16
+ const content = fs.readFileSync(path.join(vaultPath, file), "utf-8");
17
+ const { properties } = parseFrontmatter(content);
18
+ const aliases = properties.aliases;
19
+ if (Array.isArray(aliases)) {
20
+ for (const a of aliases)
21
+ results.push({ alias: String(a), file });
22
+ }
23
+ else if (typeof aliases === "string" && aliases) {
24
+ results.push({ alias: aliases, file });
25
+ }
26
+ }
27
+ return results;
28
+ }
29
+ export async function aliases(opts) {
30
+ const v = findVault(opts.vault);
31
+ const result = collectAliases(v.path, opts.file);
32
+ output(opts, {
33
+ json: () => {
34
+ if (opts.total)
35
+ return { total: result.length };
36
+ if (opts.verbose)
37
+ return { aliases: result };
38
+ return { aliases: result.map((r) => r.alias) };
39
+ },
40
+ human: () => {
41
+ if (opts.total) {
42
+ console.log(result.length);
43
+ }
44
+ else {
45
+ for (const r of result) {
46
+ console.log(opts.verbose ? `${r.alias}\t${dim(r.file)}` : r.alias);
47
+ }
48
+ }
49
+ },
50
+ });
51
+ }
@@ -0,0 +1,23 @@
1
+ import { type OutputOptions } from "../utils/output.js";
2
+ export declare function bases(opts: OutputOptions & {
3
+ vault?: string;
4
+ }): Promise<void>;
5
+ export declare function baseViews(opts: OutputOptions & {
6
+ vault?: string;
7
+ file?: string;
8
+ path?: string;
9
+ }): Promise<void>;
10
+ export declare function baseQuery(opts: OutputOptions & {
11
+ vault?: string;
12
+ file?: string;
13
+ path?: string;
14
+ view?: string;
15
+ format?: string;
16
+ }): Promise<void>;
17
+ export declare function baseCreate(opts: OutputOptions & {
18
+ vault?: string;
19
+ file?: string;
20
+ path?: string;
21
+ name?: string;
22
+ content?: string;
23
+ }): Promise<void>;
@@ -0,0 +1,192 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { buildDatabase, parseBaseFile, queryBase } from "../utils/bases.js";
4
+ import { EXIT_USER_ERROR } from "../utils/exit-codes.js";
5
+ import { listFiles } from "../utils/files.js";
6
+ import { bold, dim, error, output, success, } from "../utils/output.js";
7
+ import { findVault } from "../utils/vault.js";
8
+ export async function bases(opts) {
9
+ const v = findVault(opts.vault);
10
+ const files = listFiles(v.path).filter((f) => f.endsWith(".base"));
11
+ output(opts, {
12
+ json: () => ({ bases: files }),
13
+ human: () => {
14
+ if (files.length === 0) {
15
+ console.log("No .base files found");
16
+ }
17
+ else {
18
+ for (const f of files)
19
+ console.log(f);
20
+ }
21
+ },
22
+ });
23
+ }
24
+ export async function baseViews(opts) {
25
+ const v = findVault(opts.vault);
26
+ const baseFile = resolveBaseFile(v.path, opts);
27
+ if (!baseFile) {
28
+ error("No base file specified. Use --file or --path");
29
+ process.exit(EXIT_USER_ERROR);
30
+ }
31
+ const content = fs.readFileSync(path.join(v.path, baseFile), "utf-8");
32
+ const config = parseBaseFile(content);
33
+ const views = (config.views || []).map((view) => ({
34
+ name: view.name || "(unnamed)",
35
+ type: view.type,
36
+ }));
37
+ output(opts, {
38
+ json: () => ({ views }),
39
+ human: () => {
40
+ for (const view of views) {
41
+ console.log(`${bold(view.name)} ${dim(view.type)}`);
42
+ }
43
+ },
44
+ });
45
+ }
46
+ export async function baseQuery(opts) {
47
+ const v = findVault(opts.vault);
48
+ const baseFile = resolveBaseFile(v.path, opts);
49
+ if (!baseFile) {
50
+ error("No base file specified. Use --file or --path");
51
+ process.exit(EXIT_USER_ERROR);
52
+ }
53
+ const content = fs.readFileSync(path.join(v.path, baseFile), "utf-8");
54
+ const config = parseBaseFile(content);
55
+ const db = await buildDatabase(v.path);
56
+ try {
57
+ // Derive thisFile from the base file path
58
+ const thisFile = baseFile
59
+ ? {
60
+ name: path.basename(baseFile),
61
+ path: baseFile,
62
+ folder: path.dirname(baseFile),
63
+ }
64
+ : undefined;
65
+ const result = await queryBase(db, config, opts.view, thisFile);
66
+ const fmt = opts.format || "json";
67
+ // Apply displayNames to columns for output
68
+ const displayCols = result.columns.map((c) => result.displayNames?.[c] || c);
69
+ output(opts, {
70
+ json: () => {
71
+ if (fmt === "paths") {
72
+ const pathIdx = result.columns.indexOf("path");
73
+ return { paths: result.rows.map((r) => r[pathIdx]) };
74
+ }
75
+ // Convert to array of objects
76
+ const rows = result.rows.map((row) => {
77
+ const obj = {};
78
+ for (let i = 0; i < result.columns.length; i++) {
79
+ obj[result.columns[i]] = row[i];
80
+ }
81
+ return obj;
82
+ });
83
+ const out = { columns: result.columns, rows };
84
+ if (result.displayNames &&
85
+ Object.keys(result.displayNames).length > 0) {
86
+ out.displayNames = result.displayNames;
87
+ }
88
+ if (result.groups) {
89
+ out.groups = result.groups.map((g) => ({
90
+ key: g.key,
91
+ rows: g.rows.map((row) => {
92
+ const obj = {};
93
+ for (let i = 0; i < result.columns.length; i++) {
94
+ obj[result.columns[i]] = row[i];
95
+ }
96
+ return obj;
97
+ }),
98
+ }));
99
+ }
100
+ if (result.summaries)
101
+ out.summaries = result.summaries;
102
+ return out;
103
+ },
104
+ human: () => {
105
+ if (result.rows.length === 0) {
106
+ console.log("No results");
107
+ return;
108
+ }
109
+ if (fmt === "paths") {
110
+ const pathIdx = result.columns.indexOf("path");
111
+ for (const row of result.rows)
112
+ console.log(row[pathIdx]);
113
+ return;
114
+ }
115
+ if (fmt === "csv" || fmt === "tsv") {
116
+ const sep = fmt === "csv" ? "," : "\t";
117
+ console.log(displayCols.join(sep));
118
+ for (const row of result.rows) {
119
+ console.log(row.map((v) => (v === null ? "" : String(v))).join(sep));
120
+ }
121
+ return;
122
+ }
123
+ if (fmt === "md") {
124
+ console.log(`| ${displayCols.join(" | ")} |`);
125
+ console.log(`| ${displayCols.map(() => "---").join(" | ")} |`);
126
+ for (const row of result.rows) {
127
+ console.log(`| ${row.map((v) => (v === null ? "" : String(v))).join(" | ")} |`);
128
+ }
129
+ return;
130
+ }
131
+ // Default: table-like
132
+ for (const row of result.rows) {
133
+ const obj = {};
134
+ for (let i = 0; i < result.columns.length; i++) {
135
+ if (row[i] !== null)
136
+ obj[result.columns[i]] = row[i];
137
+ }
138
+ console.log(JSON.stringify(obj));
139
+ }
140
+ },
141
+ });
142
+ }
143
+ finally {
144
+ db.close();
145
+ }
146
+ }
147
+ export async function baseCreate(opts) {
148
+ const v = findVault(opts.vault);
149
+ if (!opts.name) {
150
+ error("No name specified. Use --name <name>");
151
+ process.exit(EXIT_USER_ERROR);
152
+ }
153
+ // Create a new note (item in the base)
154
+ const targetPath = opts.path
155
+ ? opts.path.endsWith(".md")
156
+ ? opts.path
157
+ : `${opts.path}/${opts.name}.md`
158
+ : `${opts.name}.md`;
159
+ const fullPath = path.join(v.path, targetPath);
160
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
161
+ fs.writeFileSync(fullPath, opts.content || "");
162
+ output(opts, {
163
+ json: () => ({ path: targetPath, created: true }),
164
+ human: () => success(`Created ${targetPath}`),
165
+ });
166
+ }
167
+ function resolveBaseFile(vaultPath, opts) {
168
+ if (opts.path) {
169
+ const p = opts.path.endsWith(".base") ? opts.path : `${opts.path}.base`;
170
+ if (fs.existsSync(path.join(vaultPath, p)))
171
+ return p;
172
+ return null;
173
+ }
174
+ if (opts.file) {
175
+ // Search for .base file by name
176
+ const allFiles = listFiles(vaultPath).filter((f) => f.endsWith(".base"));
177
+ const target = opts.file.toLowerCase();
178
+ for (const f of allFiles) {
179
+ const basename = path.basename(f, ".base").toLowerCase();
180
+ if (basename === target)
181
+ return f;
182
+ }
183
+ // Try with .base extension
184
+ const withExt = opts.file.endsWith(".base")
185
+ ? opts.file
186
+ : `${opts.file}.base`;
187
+ if (fs.existsSync(path.join(vaultPath, withExt)))
188
+ return withExt;
189
+ return null;
190
+ }
191
+ return null;
192
+ }
@@ -0,0 +1,15 @@
1
+ import { type OutputOptions } from "../utils/output.js";
2
+ export declare function bookmarks(opts: OutputOptions & {
3
+ vault?: string;
4
+ total?: boolean;
5
+ verbose?: boolean;
6
+ }): Promise<void>;
7
+ export declare function bookmark(opts: OutputOptions & {
8
+ vault?: string;
9
+ file?: string;
10
+ subpath?: string;
11
+ folder?: string;
12
+ search?: string;
13
+ url?: string;
14
+ title?: string;
15
+ }): Promise<void>;
@@ -0,0 +1,82 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { EXIT_USER_ERROR } from "../utils/exit-codes.js";
4
+ import { dim, error, output, success, } from "../utils/output.js";
5
+ import { findVault } from "../utils/vault.js";
6
+ function readBookmarks(vaultPath) {
7
+ const configPath = path.join(vaultPath, ".obsidian", "bookmarks.json");
8
+ try {
9
+ const content = fs.readFileSync(configPath, "utf-8");
10
+ return JSON.parse(content);
11
+ }
12
+ catch {
13
+ return [];
14
+ }
15
+ }
16
+ function writeBookmarks(vaultPath, bookmarks) {
17
+ const configPath = path.join(vaultPath, ".obsidian", "bookmarks.json");
18
+ fs.writeFileSync(configPath, JSON.stringify(bookmarks, null, 2));
19
+ }
20
+ function flattenBookmarks(items) {
21
+ const result = [];
22
+ for (const item of items) {
23
+ if (item.type === "group" && item.items) {
24
+ result.push(...flattenBookmarks(item.items));
25
+ }
26
+ else {
27
+ result.push(item);
28
+ }
29
+ }
30
+ return result;
31
+ }
32
+ export async function bookmarks(opts) {
33
+ const v = findVault(opts.vault);
34
+ const items = readBookmarks(v.path);
35
+ const flat = flattenBookmarks(items);
36
+ output(opts, {
37
+ json: () => (opts.total ? { total: flat.length } : { bookmarks: flat }),
38
+ human: () => {
39
+ if (opts.total) {
40
+ console.log(flat.length);
41
+ }
42
+ else {
43
+ for (const b of flat) {
44
+ const label = b.title || b.path || b.query || b.url || "(untitled)";
45
+ console.log(opts.verbose ? `${label}\t${dim(b.type)}` : label);
46
+ }
47
+ }
48
+ },
49
+ });
50
+ }
51
+ export async function bookmark(opts) {
52
+ const v = findVault(opts.vault);
53
+ let entry;
54
+ if (opts.file) {
55
+ entry = {
56
+ type: "file",
57
+ path: opts.file,
58
+ title: opts.title,
59
+ subpath: opts.subpath,
60
+ };
61
+ }
62
+ else if (opts.folder) {
63
+ entry = { type: "folder", path: opts.folder, title: opts.title };
64
+ }
65
+ else if (opts.search) {
66
+ entry = { type: "search", query: opts.search, title: opts.title };
67
+ }
68
+ else if (opts.url) {
69
+ entry = { type: "url", url: opts.url, title: opts.title };
70
+ }
71
+ else {
72
+ error("Specify --file, --folder, --search, or --url to bookmark");
73
+ process.exit(EXIT_USER_ERROR);
74
+ }
75
+ const items = readBookmarks(v.path);
76
+ items.push(entry);
77
+ writeBookmarks(v.path, items);
78
+ output(opts, {
79
+ json: () => ({ added: entry }),
80
+ human: () => success(`Bookmarked ${entry.path || entry.query || entry.url}`),
81
+ });
82
+ }
@@ -0,0 +1,49 @@
1
+ import { type OutputOptions } from "../utils/output.js";
2
+ export declare function canvases(opts: OutputOptions & {
3
+ vault?: string;
4
+ total?: boolean;
5
+ }): Promise<void>;
6
+ export declare function canvasRead(opts: OutputOptions & {
7
+ vault?: string;
8
+ file?: string;
9
+ }): Promise<void>;
10
+ export declare function canvasNodes(opts: OutputOptions & {
11
+ vault?: string;
12
+ file?: string;
13
+ type?: string;
14
+ }): Promise<void>;
15
+ export declare function canvasCreate(opts: OutputOptions & {
16
+ vault?: string;
17
+ file?: string;
18
+ path?: string;
19
+ }): Promise<void>;
20
+ export declare function canvasAddNode(opts: OutputOptions & {
21
+ vault?: string;
22
+ file?: string;
23
+ type?: string;
24
+ text?: string;
25
+ noteFile?: string;
26
+ subpath?: string;
27
+ url?: string;
28
+ label?: string;
29
+ x?: string;
30
+ y?: string;
31
+ width?: string;
32
+ height?: string;
33
+ color?: string;
34
+ }): Promise<void>;
35
+ export declare function canvasAddEdge(opts: OutputOptions & {
36
+ vault?: string;
37
+ file?: string;
38
+ from?: string;
39
+ to?: string;
40
+ fromSide?: string;
41
+ toSide?: string;
42
+ label?: string;
43
+ color?: string;
44
+ }): Promise<void>;
45
+ export declare function canvasRemoveNode(opts: OutputOptions & {
46
+ vault?: string;
47
+ file?: string;
48
+ id?: string;
49
+ }): Promise<void>;