@cad0p/napkin 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +342 -0
  3. package/dist/commands/aliases.d.ts +7 -0
  4. package/dist/commands/aliases.js +25 -0
  5. package/dist/commands/bases.d.ts +23 -0
  6. package/dist/commands/bases.js +139 -0
  7. package/dist/commands/bookmarks.d.ts +15 -0
  8. package/dist/commands/bookmarks.js +51 -0
  9. package/dist/commands/canvas.d.ts +49 -0
  10. package/dist/commands/canvas.js +186 -0
  11. package/dist/commands/config.d.ts +13 -0
  12. package/dist/commands/config.js +48 -0
  13. package/dist/commands/crud.d.ts +40 -0
  14. package/dist/commands/crud.js +195 -0
  15. package/dist/commands/daily.d.ts +20 -0
  16. package/dist/commands/daily.js +58 -0
  17. package/dist/commands/files.d.ts +23 -0
  18. package/dist/commands/files.js +132 -0
  19. package/dist/commands/graph.d.ts +4 -0
  20. package/dist/commands/graph.js +461 -0
  21. package/dist/commands/init.d.ts +7 -0
  22. package/dist/commands/init.js +52 -0
  23. package/dist/commands/links.d.ts +26 -0
  24. package/dist/commands/links.js +119 -0
  25. package/dist/commands/outline.d.ts +7 -0
  26. package/dist/commands/outline.js +48 -0
  27. package/dist/commands/overview.d.ts +6 -0
  28. package/dist/commands/overview.js +40 -0
  29. package/dist/commands/properties.d.ts +24 -0
  30. package/dist/commands/properties.js +115 -0
  31. package/dist/commands/search.d.ts +13 -0
  32. package/dist/commands/search.js +48 -0
  33. package/dist/commands/tags.d.ts +13 -0
  34. package/dist/commands/tags.js +51 -0
  35. package/dist/commands/tasks.d.ts +22 -0
  36. package/dist/commands/tasks.js +106 -0
  37. package/dist/commands/templates.d.ts +16 -0
  38. package/dist/commands/templates.js +70 -0
  39. package/dist/commands/vault.d.ts +4 -0
  40. package/dist/commands/vault.js +17 -0
  41. package/dist/commands/wordcount.d.ts +7 -0
  42. package/dist/commands/wordcount.js +43 -0
  43. package/dist/core/aliases.d.ts +5 -0
  44. package/dist/core/aliases.js +26 -0
  45. package/dist/core/bases.d.ts +29 -0
  46. package/dist/core/bases.js +67 -0
  47. package/dist/core/bookmarks.d.ts +14 -0
  48. package/dist/core/bookmarks.js +34 -0
  49. package/dist/core/canvas.d.ts +74 -0
  50. package/dist/core/canvas.js +125 -0
  51. package/dist/core/config.d.ts +7 -0
  52. package/dist/core/config.js +35 -0
  53. package/dist/core/crud.d.ts +32 -0
  54. package/dist/core/crud.js +119 -0
  55. package/dist/core/daily.d.ts +12 -0
  56. package/dist/core/daily.js +102 -0
  57. package/dist/core/files.d.ts +15 -0
  58. package/dist/core/files.js +30 -0
  59. package/dist/core/init.d.ts +31 -0
  60. package/dist/core/init.js +119 -0
  61. package/dist/core/links.d.ts +11 -0
  62. package/dist/core/links.js +66 -0
  63. package/dist/core/outline.d.ts +3 -0
  64. package/dist/core/outline.js +12 -0
  65. package/dist/core/overview.d.ts +15 -0
  66. package/dist/core/overview.js +384 -0
  67. package/dist/core/properties.d.ts +14 -0
  68. package/dist/core/properties.js +60 -0
  69. package/dist/core/search.d.ts +17 -0
  70. package/dist/core/search.js +153 -0
  71. package/dist/core/tags.d.ts +11 -0
  72. package/dist/core/tags.js +40 -0
  73. package/dist/core/tasks.d.ts +35 -0
  74. package/dist/core/tasks.js +97 -0
  75. package/dist/core/templates.d.ts +14 -0
  76. package/dist/core/templates.js +55 -0
  77. package/dist/core/vault.d.ts +10 -0
  78. package/dist/core/vault.js +37 -0
  79. package/dist/core/wordcount.d.ts +5 -0
  80. package/dist/core/wordcount.js +16 -0
  81. package/dist/index.d.ts +17 -0
  82. package/dist/index.js +1 -0
  83. package/dist/main.d.ts +2 -0
  84. package/dist/main.js +715 -0
  85. package/dist/sdk.d.ts +179 -0
  86. package/dist/sdk.js +232 -0
  87. package/dist/templates/coding.d.ts +2 -0
  88. package/dist/templates/coding.js +104 -0
  89. package/dist/templates/company.d.ts +2 -0
  90. package/dist/templates/company.js +121 -0
  91. package/dist/templates/index.d.ts +4 -0
  92. package/dist/templates/index.js +15 -0
  93. package/dist/templates/personal.d.ts +2 -0
  94. package/dist/templates/personal.js +91 -0
  95. package/dist/templates/product.d.ts +2 -0
  96. package/dist/templates/product.js +123 -0
  97. package/dist/templates/research.d.ts +2 -0
  98. package/dist/templates/research.js +114 -0
  99. package/dist/templates/types.d.ts +7 -0
  100. package/dist/templates/types.js +1 -0
  101. package/dist/utils/bases.d.ts +61 -0
  102. package/dist/utils/bases.js +661 -0
  103. package/dist/utils/config.d.ts +42 -0
  104. package/dist/utils/config.js +112 -0
  105. package/dist/utils/exit-codes.d.ts +5 -0
  106. package/dist/utils/exit-codes.js +5 -0
  107. package/dist/utils/files.d.ts +135 -0
  108. package/dist/utils/files.js +299 -0
  109. package/dist/utils/formula.d.ts +28 -0
  110. package/dist/utils/formula.js +462 -0
  111. package/dist/utils/frontmatter.d.ts +17 -0
  112. package/dist/utils/frontmatter.js +34 -0
  113. package/dist/utils/markdown.d.ts +31 -0
  114. package/dist/utils/markdown.js +80 -0
  115. package/dist/utils/output.d.ts +28 -0
  116. package/dist/utils/output.js +48 -0
  117. package/dist/utils/search-cache.d.ts +29 -0
  118. package/dist/utils/search-cache.js +41 -0
  119. package/dist/utils/test-helpers.d.ts +13 -0
  120. package/dist/utils/test-helpers.js +40 -0
  121. package/dist/utils/vault.d.ts +21 -0
  122. package/dist/utils/vault.js +144 -0
  123. package/package.json +76 -0
@@ -0,0 +1,119 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { loadConfig } from "../utils/config.js";
4
+ import { listFiles, resolveFile } from "../utils/files.js";
5
+ import { parseFrontmatter } from "../utils/frontmatter.js";
6
+ export function readFile(vaultPath, fileRef) {
7
+ const resolved = resolveFile(vaultPath, fileRef);
8
+ if (!resolved) {
9
+ throw new Error(`File not found: ${fileRef}`);
10
+ }
11
+ const content = fs.readFileSync(path.join(vaultPath, resolved), "utf-8");
12
+ return { path: resolved, content };
13
+ }
14
+ export function createFile(v, opts) {
15
+ let targetPath;
16
+ if (opts.path) {
17
+ targetPath = opts.path.endsWith(".md") ? opts.path : `${opts.path}.md`;
18
+ }
19
+ else {
20
+ const name = opts.name || "Untitled";
21
+ targetPath = `${name}.md`;
22
+ }
23
+ const fullPath = path.join(v.contentPath, targetPath);
24
+ if (fs.existsSync(fullPath) && !opts.overwrite) {
25
+ throw new Error(`File already exists: ${targetPath}. Use --overwrite to replace.`);
26
+ }
27
+ let content = opts.content || "";
28
+ if (opts.template) {
29
+ const config = loadConfig(v.configPath);
30
+ const templateRef = resolveFile(v.contentPath, opts.template) ||
31
+ resolveFile(v.contentPath, `${config.templates.folder}/${opts.template}`);
32
+ if (templateRef) {
33
+ content = fs.readFileSync(path.join(v.contentPath, templateRef), "utf-8");
34
+ }
35
+ else {
36
+ const tmplFiles = listFiles(v.contentPath, {
37
+ folder: config.templates.folder,
38
+ ext: "md",
39
+ }).map((f) => path.basename(f, ".md"));
40
+ throw new Error(`Template not found: ${opts.template}. Available: ${tmplFiles.slice(0, 3).join(", ")}`);
41
+ }
42
+ }
43
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
44
+ fs.writeFileSync(fullPath, content);
45
+ return { path: targetPath, created: true };
46
+ }
47
+ export function appendFile(vaultPath, fileRef, content, inline) {
48
+ const resolved = resolveFile(vaultPath, fileRef);
49
+ if (!resolved) {
50
+ throw new Error(`File not found: ${fileRef}`);
51
+ }
52
+ const fullPath = path.join(vaultPath, resolved);
53
+ const existing = fs.readFileSync(fullPath, "utf-8");
54
+ const separator = inline ? "" : "\n";
55
+ fs.writeFileSync(fullPath, existing + separator + content);
56
+ return resolved;
57
+ }
58
+ export function prependFile(vaultPath, fileRef, content, inline) {
59
+ const resolved = resolveFile(vaultPath, fileRef);
60
+ if (!resolved) {
61
+ throw new Error(`File not found: ${fileRef}`);
62
+ }
63
+ const fullPath = path.join(vaultPath, resolved);
64
+ const existing = fs.readFileSync(fullPath, "utf-8");
65
+ const separator = inline ? "" : "\n";
66
+ const { properties, body, raw } = parseFrontmatter(existing);
67
+ if (Object.keys(properties).length > 0) {
68
+ const frontmatter = `---\n${raw}\n---\n`;
69
+ fs.writeFileSync(fullPath, frontmatter + content + separator + body);
70
+ }
71
+ else {
72
+ fs.writeFileSync(fullPath, content + separator + existing);
73
+ }
74
+ return resolved;
75
+ }
76
+ export function moveFile(vaultPath, fileRef, destination) {
77
+ const resolved = resolveFile(vaultPath, fileRef);
78
+ if (!resolved) {
79
+ throw new Error(`File not found: ${fileRef}`);
80
+ }
81
+ let destPath = destination;
82
+ if (!destPath.endsWith(".md")) {
83
+ destPath = path.join(destPath, path.basename(resolved));
84
+ }
85
+ const srcFull = path.join(vaultPath, resolved);
86
+ const destFull = path.join(vaultPath, destPath);
87
+ fs.mkdirSync(path.dirname(destFull), { recursive: true });
88
+ fs.renameSync(srcFull, destFull);
89
+ return { from: resolved, to: destPath };
90
+ }
91
+ export function renameFile(vaultPath, fileRef, newName) {
92
+ const resolved = resolveFile(vaultPath, fileRef);
93
+ if (!resolved) {
94
+ throw new Error(`File not found: ${fileRef}`);
95
+ }
96
+ const name = newName.endsWith(".md") ? newName : `${newName}.md`;
97
+ const destPath = path.join(path.dirname(resolved), name);
98
+ const srcFull = path.join(vaultPath, resolved);
99
+ const destFull = path.join(vaultPath, destPath);
100
+ fs.renameSync(srcFull, destFull);
101
+ return { from: resolved, to: destPath };
102
+ }
103
+ export function deleteFile(vaultPath, fileRef, permanent) {
104
+ const resolved = resolveFile(vaultPath, fileRef);
105
+ if (!resolved) {
106
+ throw new Error(`File not found: ${fileRef}`);
107
+ }
108
+ const fullPath = path.join(vaultPath, resolved);
109
+ if (permanent) {
110
+ fs.unlinkSync(fullPath);
111
+ }
112
+ else {
113
+ const trashDir = path.join(vaultPath, ".trash");
114
+ fs.mkdirSync(trashDir, { recursive: true });
115
+ const trashPath = path.join(trashDir, path.basename(resolved));
116
+ fs.renameSync(fullPath, trashPath);
117
+ }
118
+ return { path: resolved, deleted: true, permanent: !!permanent };
119
+ }
@@ -0,0 +1,12 @@
1
+ import type { VaultInfo } from "../utils/vault.js";
2
+ export declare function getDailyPath(configPath: string, date?: Date): string;
3
+ export declare function ensureDaily(v: VaultInfo): {
4
+ path: string;
5
+ created: boolean;
6
+ };
7
+ export declare function readDaily(v: VaultInfo): {
8
+ path: string;
9
+ content: string;
10
+ };
11
+ export declare function appendDaily(v: VaultInfo, content: string, inline?: boolean): string;
12
+ export declare function prependDaily(v: VaultInfo, content: string, inline?: boolean): string;
@@ -0,0 +1,102 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { loadConfig } from "../utils/config.js";
4
+ import { parseFrontmatter } from "../utils/frontmatter.js";
5
+ function getDailyConfig(configPath) {
6
+ const config = loadConfig(configPath);
7
+ return {
8
+ folder: config.daily.folder,
9
+ format: config.daily.format,
10
+ template: `${config.templates.folder}/Daily Note`,
11
+ };
12
+ }
13
+ function formatDate(date, format) {
14
+ const days = [
15
+ "Sunday",
16
+ "Monday",
17
+ "Tuesday",
18
+ "Wednesday",
19
+ "Thursday",
20
+ "Friday",
21
+ "Saturday",
22
+ ];
23
+ const shortDays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
24
+ return format
25
+ .replace(/YYYY/g, String(date.getFullYear()))
26
+ .replace(/YY/g, String(date.getFullYear()).slice(-2))
27
+ .replace(/MM/g, String(date.getMonth() + 1).padStart(2, "0"))
28
+ .replace(/M/g, String(date.getMonth() + 1))
29
+ .replace(/DD/g, String(date.getDate()).padStart(2, "0"))
30
+ .replace(/D/g, String(date.getDate()))
31
+ .replace(/dddd/g, days[date.getDay()])
32
+ .replace(/ddd/g, shortDays[date.getDay()])
33
+ .replace(/HH/g, String(date.getHours()).padStart(2, "0"))
34
+ .replace(/H/g, String(date.getHours()))
35
+ .replace(/mm/g, String(date.getMinutes()).padStart(2, "0"))
36
+ .replace(/ss/g, String(date.getSeconds()).padStart(2, "0"));
37
+ }
38
+ export function getDailyPath(configPath, date) {
39
+ const config = getDailyConfig(configPath);
40
+ const d = date || new Date();
41
+ const filename = formatDate(d, config.format);
42
+ const folder = config.folder || "";
43
+ return folder ? `${folder}/${filename}.md` : `${filename}.md`;
44
+ }
45
+ export function ensureDaily(v) {
46
+ const dp = getDailyPath(v.configPath);
47
+ const fullPath = path.join(v.contentPath, dp);
48
+ if (!fs.existsSync(fullPath)) {
49
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
50
+ const config = getDailyConfig(v.configPath);
51
+ let content = "";
52
+ if (config.template) {
53
+ const templatePath = path.join(v.contentPath, `${config.template}.md`);
54
+ if (fs.existsSync(templatePath)) {
55
+ content = fs.readFileSync(templatePath, "utf-8");
56
+ }
57
+ }
58
+ fs.writeFileSync(fullPath, content);
59
+ return { path: dp, created: true };
60
+ }
61
+ return { path: dp, created: false };
62
+ }
63
+ export function readDaily(v) {
64
+ const dp = getDailyPath(v.configPath);
65
+ const fullPath = path.join(v.contentPath, dp);
66
+ if (!fs.existsSync(fullPath)) {
67
+ throw new Error(`Daily note not found: ${dp}`);
68
+ }
69
+ const content = fs.readFileSync(fullPath, "utf-8");
70
+ return { path: dp, content };
71
+ }
72
+ export function appendDaily(v, content, inline) {
73
+ const dp = getDailyPath(v.configPath);
74
+ const fullPath = path.join(v.contentPath, dp);
75
+ if (!fs.existsSync(fullPath)) {
76
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
77
+ fs.writeFileSync(fullPath, "");
78
+ }
79
+ const existing = fs.readFileSync(fullPath, "utf-8");
80
+ const separator = inline ? "" : "\n";
81
+ fs.writeFileSync(fullPath, existing + separator + content);
82
+ return dp;
83
+ }
84
+ export function prependDaily(v, content, inline) {
85
+ const dp = getDailyPath(v.configPath);
86
+ const fullPath = path.join(v.contentPath, dp);
87
+ if (!fs.existsSync(fullPath)) {
88
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
89
+ fs.writeFileSync(fullPath, "");
90
+ }
91
+ const existing = fs.readFileSync(fullPath, "utf-8");
92
+ const separator = inline ? "" : "\n";
93
+ const { properties, body, raw } = parseFrontmatter(existing);
94
+ if (Object.keys(properties).length > 0) {
95
+ const frontmatter = `---\n${raw}\n---\n`;
96
+ fs.writeFileSync(fullPath, frontmatter + content + separator + body);
97
+ }
98
+ else {
99
+ fs.writeFileSync(fullPath, content + separator + existing);
100
+ }
101
+ return dp;
102
+ }
@@ -0,0 +1,15 @@
1
+ import { type FileInfo } from "../utils/files.js";
2
+ export type { FileInfo };
3
+ export interface FolderInfo {
4
+ path: string;
5
+ files: number;
6
+ folders: number;
7
+ size: number;
8
+ }
9
+ export declare function getFileInfoResolved(vaultPath: string, fileRef: string): FileInfo;
10
+ export declare function getFileList(vaultPath: string, opts?: {
11
+ folder?: string;
12
+ ext?: string;
13
+ }): string[];
14
+ export declare function getFolderList(vaultPath: string, parentFolder?: string): string[];
15
+ export declare function getFolderInfo(vaultPath: string, folderPath: string): FolderInfo;
@@ -0,0 +1,30 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { getFileInfo, listFiles, listFolders, resolveFile, } from "../utils/files.js";
4
+ export function getFileInfoResolved(vaultPath, fileRef) {
5
+ const resolved = resolveFile(vaultPath, fileRef);
6
+ if (!resolved) {
7
+ throw new Error(`File not found: ${fileRef}`);
8
+ }
9
+ return getFileInfo(vaultPath, resolved);
10
+ }
11
+ export function getFileList(vaultPath, opts) {
12
+ return listFiles(vaultPath, opts);
13
+ }
14
+ export function getFolderList(vaultPath, parentFolder) {
15
+ return listFolders(vaultPath, parentFolder);
16
+ }
17
+ export function getFolderInfo(vaultPath, folderPath) {
18
+ const fullPath = path.join(vaultPath, folderPath);
19
+ if (!fs.existsSync(fullPath) || !fs.statSync(fullPath).isDirectory()) {
20
+ throw new Error(`Folder not found: ${folderPath}`);
21
+ }
22
+ const fileCount = listFiles(vaultPath, { folder: folderPath }).length;
23
+ const folderCount = listFolders(vaultPath, folderPath).length;
24
+ let size = 0;
25
+ const allFiles = listFiles(vaultPath, { folder: folderPath });
26
+ for (const f of allFiles) {
27
+ size += fs.statSync(path.join(vaultPath, f)).size;
28
+ }
29
+ return { path: folderPath, files: fileCount, folders: folderCount, size };
30
+ }
@@ -0,0 +1,31 @@
1
+ export interface InitResult {
2
+ status: string;
3
+ path: string;
4
+ napkin?: boolean;
5
+ configCreated?: boolean;
6
+ siblingLayout?: boolean;
7
+ template?: string | null;
8
+ files?: string[];
9
+ }
10
+ export interface TemplateInfo {
11
+ name: string;
12
+ description: string;
13
+ dirs: string[];
14
+ }
15
+ export declare function initVault(opts: {
16
+ path?: string;
17
+ template?: string;
18
+ }): InitResult;
19
+ export interface ScaffoldResult {
20
+ path: string;
21
+ created: boolean;
22
+ template: string;
23
+ files: string[];
24
+ }
25
+ export declare function scaffoldVault(targetPath: string, template?: string): ScaffoldResult;
26
+ export interface AddTemplateResult {
27
+ template: string;
28
+ files: string[];
29
+ }
30
+ export declare function addTemplate(targetPath: string, template: string): AddTemplateResult;
31
+ export declare function getInitTemplates(): TemplateInfo[];
@@ -0,0 +1,119 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { TEMPLATES } from "../templates/index.js";
4
+ import { DEFAULT_CONFIG, saveConfig, } from "../utils/config.js";
5
+ function scaffoldTemplate(targetDir, template) {
6
+ const created = [];
7
+ for (const dir of template.dirs) {
8
+ const dirPath = path.join(targetDir, dir);
9
+ if (!fs.existsSync(dirPath)) {
10
+ fs.mkdirSync(dirPath, { recursive: true });
11
+ created.push(`${dir}/`);
12
+ }
13
+ }
14
+ for (const [filePath, content] of Object.entries(template.files)) {
15
+ const fullPath = path.join(targetDir, filePath);
16
+ if (!fs.existsSync(fullPath)) {
17
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
18
+ fs.writeFileSync(fullPath, content);
19
+ created.push(filePath);
20
+ }
21
+ }
22
+ const napkinPath = path.join(targetDir, "NAPKIN.md");
23
+ if (!fs.existsSync(napkinPath)) {
24
+ fs.writeFileSync(napkinPath, template.napkinMd);
25
+ created.push("NAPKIN.md");
26
+ }
27
+ return created;
28
+ }
29
+ export function initVault(opts) {
30
+ if (!opts.path) {
31
+ throw new Error("No path specified for vault initialization");
32
+ }
33
+ const targetDir = path.resolve(opts.path);
34
+ const napkinDir = path.join(targetDir, ".napkin");
35
+ const obsidianDir = path.join(targetDir, ".obsidian");
36
+ const hasExistingObsidian = fs.existsSync(obsidianDir) && fs.statSync(obsidianDir).isDirectory();
37
+ const napkinExists = fs.existsSync(napkinDir);
38
+ const configExists = fs.existsSync(path.join(napkinDir, "config.json"));
39
+ if (napkinExists && configExists && !opts.template) {
40
+ return { status: "exists", path: napkinDir };
41
+ }
42
+ if (opts.template && !TEMPLATES[opts.template]) {
43
+ throw new Error(`Unknown template: ${opts.template}. Available: ${Object.keys(TEMPLATES).join(", ")}`);
44
+ }
45
+ if (!napkinExists) {
46
+ fs.mkdirSync(napkinDir, { recursive: true });
47
+ }
48
+ if (!fs.existsSync(path.join(napkinDir, "config.json"))) {
49
+ const config = {
50
+ ...DEFAULT_CONFIG,
51
+ vault: { root: "..", obsidian: "../.obsidian" },
52
+ };
53
+ saveConfig(napkinDir, config, hasExistingObsidian ? obsidianDir : undefined);
54
+ }
55
+ const contentRoot = targetDir;
56
+ let templateFiles = [];
57
+ if (opts.template) {
58
+ templateFiles = scaffoldTemplate(contentRoot, TEMPLATES[opts.template]);
59
+ }
60
+ return {
61
+ status: "created",
62
+ path: napkinDir,
63
+ napkin: !napkinExists,
64
+ configCreated: !configExists,
65
+ siblingLayout: hasExistingObsidian,
66
+ template: opts.template || null,
67
+ files: templateFiles,
68
+ };
69
+ }
70
+ export function scaffoldVault(targetPath, template) {
71
+ const resolved = path.resolve(targetPath);
72
+ const napkinDir = path.join(resolved, ".napkin");
73
+ const obsidianDir = path.join(resolved, ".obsidian");
74
+ const hasExistingObsidian = fs.existsSync(obsidianDir) && fs.statSync(obsidianDir).isDirectory();
75
+ const napkinExisted = fs.existsSync(napkinDir);
76
+ if (!napkinExisted) {
77
+ fs.mkdirSync(napkinDir, { recursive: true });
78
+ }
79
+ if (!fs.existsSync(path.join(napkinDir, "config.json"))) {
80
+ const config = {
81
+ ...DEFAULT_CONFIG,
82
+ vault: { root: "..", obsidian: "../.obsidian" },
83
+ };
84
+ saveConfig(napkinDir, config, hasExistingObsidian ? obsidianDir : undefined);
85
+ }
86
+ const contentRoot = resolved;
87
+ let files = [];
88
+ if (template) {
89
+ if (!TEMPLATES[template]) {
90
+ throw new Error(`Unknown template: ${template}. Available: ${Object.keys(TEMPLATES).join(", ")}`);
91
+ }
92
+ files = scaffoldTemplate(contentRoot, TEMPLATES[template]);
93
+ }
94
+ return {
95
+ path: napkinDir,
96
+ created: !napkinExisted,
97
+ template: template || "",
98
+ files,
99
+ };
100
+ }
101
+ export function addTemplate(targetPath, template) {
102
+ const resolved = path.resolve(targetPath);
103
+ const napkinDir = path.join(resolved, ".napkin");
104
+ if (!fs.existsSync(path.join(napkinDir, "config.json"))) {
105
+ throw new Error("Vault not initialized. Run scaffold first.");
106
+ }
107
+ if (!TEMPLATES[template]) {
108
+ throw new Error(`Unknown template: ${template}. Available: ${Object.keys(TEMPLATES).join(", ")}`);
109
+ }
110
+ const files = scaffoldTemplate(resolved, TEMPLATES[template]);
111
+ return { template, files };
112
+ }
113
+ export function getInitTemplates() {
114
+ return Object.values(TEMPLATES).map((t) => ({
115
+ name: t.name,
116
+ description: t.description,
117
+ dirs: t.dirs,
118
+ }));
119
+ }
@@ -0,0 +1,11 @@
1
+ export interface VaultLinks {
2
+ outgoing: Map<string, string[]>;
3
+ incoming: Map<string, string[]>;
4
+ unresolved: Map<string, string[]>;
5
+ }
6
+ export declare function buildLinkIndex(vaultPath: string): VaultLinks;
7
+ export declare function getBacklinks(vaultPath: string, fileRef: string): string[];
8
+ export declare function getOutgoingLinks(vaultPath: string, fileRef: string): string[];
9
+ export declare function getUnresolvedLinks(vaultPath: string): [string, string[]][];
10
+ export declare function getOrphans(vaultPath: string): string[];
11
+ export declare function getDeadends(vaultPath: string): string[];
@@ -0,0 +1,66 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { listFiles, resolveFile } from "../utils/files.js";
4
+ import { extractLinks } from "../utils/markdown.js";
5
+ export function buildLinkIndex(vaultPath) {
6
+ const files = listFiles(vaultPath, { ext: "md" });
7
+ const outgoing = new Map();
8
+ const incoming = new Map();
9
+ const unresolved = new Map();
10
+ for (const f of files)
11
+ incoming.set(f, []);
12
+ for (const file of files) {
13
+ const content = fs.readFileSync(path.join(vaultPath, file), "utf-8");
14
+ const links = extractLinks(content);
15
+ outgoing.set(file, links.outgoing);
16
+ for (const target of links.wikilinks) {
17
+ const resolved = resolveFile(vaultPath, target);
18
+ if (resolved) {
19
+ if (!incoming.has(resolved))
20
+ incoming.set(resolved, []);
21
+ incoming.get(resolved)?.push(file);
22
+ }
23
+ else {
24
+ if (!unresolved.has(target))
25
+ unresolved.set(target, []);
26
+ unresolved.get(target)?.push(file);
27
+ }
28
+ }
29
+ }
30
+ return { outgoing, incoming, unresolved };
31
+ }
32
+ export function getBacklinks(vaultPath, fileRef) {
33
+ const resolved = resolveFile(vaultPath, fileRef);
34
+ if (!resolved) {
35
+ throw new Error(`File not found: ${fileRef}`);
36
+ }
37
+ const { incoming } = buildLinkIndex(vaultPath);
38
+ return incoming.get(resolved) || [];
39
+ }
40
+ export function getOutgoingLinks(vaultPath, fileRef) {
41
+ const resolved = resolveFile(vaultPath, fileRef);
42
+ if (!resolved) {
43
+ throw new Error(`File not found: ${fileRef}`);
44
+ }
45
+ const content = fs.readFileSync(path.join(vaultPath, resolved), "utf-8");
46
+ const { outgoing } = extractLinks(content);
47
+ return outgoing;
48
+ }
49
+ export function getUnresolvedLinks(vaultPath) {
50
+ const { unresolved } = buildLinkIndex(vaultPath);
51
+ return [...unresolved.entries()].sort((a, b) => a[0].localeCompare(b[0]));
52
+ }
53
+ export function getOrphans(vaultPath) {
54
+ const { incoming } = buildLinkIndex(vaultPath);
55
+ return [...incoming.entries()]
56
+ .filter(([_, links]) => links.length === 0)
57
+ .map(([file]) => file)
58
+ .sort();
59
+ }
60
+ export function getDeadends(vaultPath) {
61
+ const { outgoing } = buildLinkIndex(vaultPath);
62
+ return [...outgoing.entries()]
63
+ .filter(([_, links]) => links.length === 0)
64
+ .map(([file]) => file)
65
+ .sort();
66
+ }
@@ -0,0 +1,3 @@
1
+ import { type Heading } from "../utils/markdown.js";
2
+ export type { Heading };
3
+ export declare function getOutline(vaultPath: string, fileRef: string): Heading[];
@@ -0,0 +1,12 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { resolveFile } from "../utils/files.js";
4
+ import { extractHeadings } from "../utils/markdown.js";
5
+ export function getOutline(vaultPath, fileRef) {
6
+ const resolved = resolveFile(vaultPath, fileRef);
7
+ if (!resolved) {
8
+ throw new Error(`File not found: ${fileRef}`);
9
+ }
10
+ const content = fs.readFileSync(path.join(vaultPath, resolved), "utf-8");
11
+ return extractHeadings(content);
12
+ }
@@ -0,0 +1,15 @@
1
+ export interface OverviewFolder {
2
+ path: string;
3
+ notes: number;
4
+ keywords: string[];
5
+ tags: string[];
6
+ }
7
+ export interface VaultOverview {
8
+ context?: string;
9
+ overview: OverviewFolder[];
10
+ warnings?: string[];
11
+ }
12
+ export declare function getOverview(contentPath: string, configPath: string, opts?: {
13
+ depth?: number;
14
+ keywords?: number;
15
+ }): VaultOverview;