@nathanvale/chatline 0.0.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 (216) hide show
  1. package/CHANGELOG.md +1 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1535 -0
  4. package/dist/bin/index.js +5121 -0
  5. package/dist/cli/commands/clean.d.ts +17 -0
  6. package/dist/cli/commands/clean.d.ts.map +1 -0
  7. package/dist/cli/commands/clean.js +142 -0
  8. package/dist/cli/commands/clean.js.map +1 -0
  9. package/dist/cli/commands/doctor.d.ts +17 -0
  10. package/dist/cli/commands/doctor.d.ts.map +1 -0
  11. package/dist/cli/commands/doctor.js +202 -0
  12. package/dist/cli/commands/doctor.js.map +1 -0
  13. package/dist/cli/commands/enrich-ai.d.ts +17 -0
  14. package/dist/cli/commands/enrich-ai.d.ts.map +1 -0
  15. package/dist/cli/commands/enrich-ai.js +371 -0
  16. package/dist/cli/commands/enrich-ai.js.map +1 -0
  17. package/dist/cli/commands/index.d.ts +16 -0
  18. package/dist/cli/commands/index.d.ts.map +1 -0
  19. package/dist/cli/commands/index.js +16 -0
  20. package/dist/cli/commands/index.js.map +1 -0
  21. package/dist/cli/commands/ingest-csv.d.ts +17 -0
  22. package/dist/cli/commands/ingest-csv.d.ts.map +1 -0
  23. package/dist/cli/commands/ingest-csv.js +138 -0
  24. package/dist/cli/commands/ingest-csv.js.map +1 -0
  25. package/dist/cli/commands/ingest-db.d.ts +17 -0
  26. package/dist/cli/commands/ingest-db.d.ts.map +1 -0
  27. package/dist/cli/commands/ingest-db.js +159 -0
  28. package/dist/cli/commands/ingest-db.js.map +1 -0
  29. package/dist/cli/commands/init.d.ts +17 -0
  30. package/dist/cli/commands/init.d.ts.map +1 -0
  31. package/dist/cli/commands/init.js +110 -0
  32. package/dist/cli/commands/init.js.map +1 -0
  33. package/dist/cli/commands/normalize-link.d.ts +16 -0
  34. package/dist/cli/commands/normalize-link.d.ts.map +1 -0
  35. package/dist/cli/commands/normalize-link.js +144 -0
  36. package/dist/cli/commands/normalize-link.js.map +1 -0
  37. package/dist/cli/commands/render-markdown.d.ts +17 -0
  38. package/dist/cli/commands/render-markdown.d.ts.map +1 -0
  39. package/dist/cli/commands/render-markdown.js +218 -0
  40. package/dist/cli/commands/render-markdown.js.map +1 -0
  41. package/dist/cli/commands/stats.d.ts +17 -0
  42. package/dist/cli/commands/stats.d.ts.map +1 -0
  43. package/dist/cli/commands/stats.js +175 -0
  44. package/dist/cli/commands/stats.js.map +1 -0
  45. package/dist/cli/commands/validate.d.ts +17 -0
  46. package/dist/cli/commands/validate.d.ts.map +1 -0
  47. package/dist/cli/commands/validate.js +152 -0
  48. package/dist/cli/commands/validate.js.map +1 -0
  49. package/dist/cli/index.d.ts +13 -0
  50. package/dist/cli/index.d.ts.map +1 -0
  51. package/dist/cli/index.js +121 -0
  52. package/dist/cli/index.js.map +1 -0
  53. package/dist/cli/types.d.ts +93 -0
  54. package/dist/cli/types.d.ts.map +1 -0
  55. package/dist/cli/types.js +7 -0
  56. package/dist/cli/types.js.map +1 -0
  57. package/dist/cli/utils.d.ts +29 -0
  58. package/dist/cli/utils.d.ts.map +1 -0
  59. package/dist/cli/utils.js +53 -0
  60. package/dist/cli/utils.js.map +1 -0
  61. package/dist/cli.d.ts +9 -0
  62. package/dist/cli.d.ts.map +1 -0
  63. package/dist/cli.js +1805 -0
  64. package/dist/config/generator.d.ts +90 -0
  65. package/dist/config/generator.d.ts.map +1 -0
  66. package/dist/config/generator.js +320 -0
  67. package/dist/config/generator.js.map +1 -0
  68. package/dist/config/loader.d.ts +107 -0
  69. package/dist/config/loader.d.ts.map +1 -0
  70. package/dist/config/loader.js +251 -0
  71. package/dist/config/loader.js.map +1 -0
  72. package/dist/config/schema.d.ts +107 -0
  73. package/dist/config/schema.d.ts.map +1 -0
  74. package/dist/config/schema.js +169 -0
  75. package/dist/config/schema.js.map +1 -0
  76. package/dist/enrich/audio-transcription.d.ts +77 -0
  77. package/dist/enrich/audio-transcription.d.ts.map +1 -0
  78. package/dist/enrich/audio-transcription.js +370 -0
  79. package/dist/enrich/audio-transcription.js.map +1 -0
  80. package/dist/enrich/checkpoint.d.ts +137 -0
  81. package/dist/enrich/checkpoint.d.ts.map +1 -0
  82. package/dist/enrich/checkpoint.js +205 -0
  83. package/dist/enrich/checkpoint.js.map +1 -0
  84. package/dist/enrich/idempotency.d.ts +90 -0
  85. package/dist/enrich/idempotency.d.ts.map +1 -0
  86. package/dist/enrich/idempotency.js +188 -0
  87. package/dist/enrich/idempotency.js.map +1 -0
  88. package/dist/enrich/image-analysis.d.ts +62 -0
  89. package/dist/enrich/image-analysis.d.ts.map +1 -0
  90. package/dist/enrich/image-analysis.js +264 -0
  91. package/dist/enrich/image-analysis.js.map +1 -0
  92. package/dist/enrich/index.d.ts +60 -0
  93. package/dist/enrich/index.d.ts.map +1 -0
  94. package/dist/enrich/index.js +74 -0
  95. package/dist/enrich/index.js.map +1 -0
  96. package/dist/enrich/link-enrichment.d.ts +37 -0
  97. package/dist/enrich/link-enrichment.d.ts.map +1 -0
  98. package/dist/enrich/link-enrichment.js +202 -0
  99. package/dist/enrich/link-enrichment.js.map +1 -0
  100. package/dist/enrich/pdf-video-handling.d.ts +49 -0
  101. package/dist/enrich/pdf-video-handling.d.ts.map +1 -0
  102. package/dist/enrich/pdf-video-handling.js +325 -0
  103. package/dist/enrich/pdf-video-handling.js.map +1 -0
  104. package/dist/enrich/progress-tracker.d.ts +120 -0
  105. package/dist/enrich/progress-tracker.d.ts.map +1 -0
  106. package/dist/enrich/progress-tracker.js +220 -0
  107. package/dist/enrich/progress-tracker.js.map +1 -0
  108. package/dist/enrich/providers/firecrawl.d.ts +18 -0
  109. package/dist/enrich/providers/firecrawl.d.ts.map +1 -0
  110. package/dist/enrich/providers/firecrawl.js +48 -0
  111. package/dist/enrich/providers/firecrawl.js.map +1 -0
  112. package/dist/enrich/providers/generic.d.ts +16 -0
  113. package/dist/enrich/providers/generic.d.ts.map +1 -0
  114. package/dist/enrich/providers/generic.js +36 -0
  115. package/dist/enrich/providers/generic.js.map +1 -0
  116. package/dist/enrich/providers/index.d.ts +14 -0
  117. package/dist/enrich/providers/index.d.ts.map +1 -0
  118. package/dist/enrich/providers/index.js +13 -0
  119. package/dist/enrich/providers/index.js.map +1 -0
  120. package/dist/enrich/providers/instagram.d.ts +16 -0
  121. package/dist/enrich/providers/instagram.d.ts.map +1 -0
  122. package/dist/enrich/providers/instagram.js +43 -0
  123. package/dist/enrich/providers/instagram.js.map +1 -0
  124. package/dist/enrich/providers/spotify.d.ts +16 -0
  125. package/dist/enrich/providers/spotify.d.ts.map +1 -0
  126. package/dist/enrich/providers/spotify.js +45 -0
  127. package/dist/enrich/providers/spotify.js.map +1 -0
  128. package/dist/enrich/providers/twitter.d.ts +16 -0
  129. package/dist/enrich/providers/twitter.d.ts.map +1 -0
  130. package/dist/enrich/providers/twitter.js +43 -0
  131. package/dist/enrich/providers/twitter.js.map +1 -0
  132. package/dist/enrich/providers/types.d.ts +47 -0
  133. package/dist/enrich/providers/types.d.ts.map +1 -0
  134. package/dist/enrich/providers/types.js +15 -0
  135. package/dist/enrich/providers/types.js.map +1 -0
  136. package/dist/enrich/providers/youtube.d.ts +16 -0
  137. package/dist/enrich/providers/youtube.d.ts.map +1 -0
  138. package/dist/enrich/providers/youtube.js +43 -0
  139. package/dist/enrich/providers/youtube.js.map +1 -0
  140. package/dist/enrich/rate-limiting.d.ts +118 -0
  141. package/dist/enrich/rate-limiting.d.ts.map +1 -0
  142. package/dist/enrich/rate-limiting.js +258 -0
  143. package/dist/enrich/rate-limiting.js.map +1 -0
  144. package/dist/index.d.ts +688 -0
  145. package/dist/index.d.ts.map +1 -0
  146. package/dist/index.js +1729 -0
  147. package/dist/index.js.map +1 -0
  148. package/dist/ingest/dedup-merge.d.ts +82 -0
  149. package/dist/ingest/dedup-merge.d.ts.map +1 -0
  150. package/dist/ingest/dedup-merge.js +262 -0
  151. package/dist/ingest/dedup-merge.js.map +1 -0
  152. package/dist/ingest/ingest-csv.d.ts +62 -0
  153. package/dist/ingest/ingest-csv.d.ts.map +1 -0
  154. package/dist/ingest/ingest-csv.js +300 -0
  155. package/dist/ingest/ingest-csv.js.map +1 -0
  156. package/dist/ingest/ingest-db.d.ts +64 -0
  157. package/dist/ingest/ingest-db.d.ts.map +1 -0
  158. package/dist/ingest/ingest-db.js +172 -0
  159. package/dist/ingest/ingest-db.js.map +1 -0
  160. package/dist/ingest/link-replies-and-tapbacks.d.ts +53 -0
  161. package/dist/ingest/link-replies-and-tapbacks.d.ts.map +1 -0
  162. package/dist/ingest/link-replies-and-tapbacks.js +381 -0
  163. package/dist/ingest/link-replies-and-tapbacks.js.map +1 -0
  164. package/dist/normalize/date-converters.d.ts +45 -0
  165. package/dist/normalize/date-converters.d.ts.map +1 -0
  166. package/dist/normalize/date-converters.js +166 -0
  167. package/dist/normalize/date-converters.js.map +1 -0
  168. package/dist/normalize/path-validator.d.ts +65 -0
  169. package/dist/normalize/path-validator.d.ts.map +1 -0
  170. package/dist/normalize/path-validator.js +221 -0
  171. package/dist/normalize/path-validator.js.map +1 -0
  172. package/dist/normalize/validate-normalized.d.ts +45 -0
  173. package/dist/normalize/validate-normalized.d.ts.map +1 -0
  174. package/dist/normalize/validate-normalized.js +144 -0
  175. package/dist/normalize/validate-normalized.js.map +1 -0
  176. package/dist/render/embeds-blockquotes.d.ts +84 -0
  177. package/dist/render/embeds-blockquotes.d.ts.map +1 -0
  178. package/dist/render/embeds-blockquotes.js +204 -0
  179. package/dist/render/embeds-blockquotes.js.map +1 -0
  180. package/dist/render/grouping.d.ts +78 -0
  181. package/dist/render/grouping.d.ts.map +1 -0
  182. package/dist/render/grouping.js +134 -0
  183. package/dist/render/grouping.js.map +1 -0
  184. package/dist/render/index.d.ts +47 -0
  185. package/dist/render/index.d.ts.map +1 -0
  186. package/dist/render/index.js +245 -0
  187. package/dist/render/index.js.map +1 -0
  188. package/dist/render/reply-rendering.d.ts +88 -0
  189. package/dist/render/reply-rendering.d.ts.map +1 -0
  190. package/dist/render/reply-rendering.js +196 -0
  191. package/dist/render/reply-rendering.js.map +1 -0
  192. package/dist/schema/message.d.ts +125 -0
  193. package/dist/schema/message.d.ts.map +1 -0
  194. package/dist/schema/message.js +331 -0
  195. package/dist/schema/message.js.map +1 -0
  196. package/dist/utils/delta-detection.d.ts +107 -0
  197. package/dist/utils/delta-detection.d.ts.map +1 -0
  198. package/dist/utils/delta-detection.js +199 -0
  199. package/dist/utils/delta-detection.js.map +1 -0
  200. package/dist/utils/enrichment-merge.d.ts +135 -0
  201. package/dist/utils/enrichment-merge.d.ts.map +1 -0
  202. package/dist/utils/enrichment-merge.js +280 -0
  203. package/dist/utils/enrichment-merge.js.map +1 -0
  204. package/dist/utils/human.d.ts +15 -0
  205. package/dist/utils/human.d.ts.map +1 -0
  206. package/dist/utils/human.js +27 -0
  207. package/dist/utils/human.js.map +1 -0
  208. package/dist/utils/incremental-state.d.ts +133 -0
  209. package/dist/utils/incremental-state.d.ts.map +1 -0
  210. package/dist/utils/incremental-state.js +237 -0
  211. package/dist/utils/incremental-state.js.map +1 -0
  212. package/dist/utils/logger.d.ts +40 -0
  213. package/dist/utils/logger.d.ts.map +1 -0
  214. package/dist/utils/logger.js +176 -0
  215. package/dist/utils/logger.js.map +1 -0
  216. package/package.json +165 -0
@@ -0,0 +1,65 @@
1
+ import type { MediaProvenance, Message } from '../schema/message';
2
+ export type PathValidationConfig = {
3
+ attachmentRoots: string[];
4
+ source: 'csv' | 'db' | 'merged';
5
+ };
6
+ export type PathValidationStats = {
7
+ total: number;
8
+ found: number;
9
+ missing: number;
10
+ notAbsolute: number;
11
+ errors: Array<{
12
+ guid: string;
13
+ filename: string;
14
+ error: string;
15
+ }>;
16
+ };
17
+ export type PathValidationResult = {
18
+ messages: Message[];
19
+ stats: PathValidationStats;
20
+ };
21
+ /**
22
+ * Validate that a path is absolute (starts with /)
23
+ * AC01: All media.path fields are absolute paths when files exist on disk
24
+ */
25
+ export declare function isAbsolutePath(filePath: string | null | undefined): boolean;
26
+ /**
27
+ * Check if a file exists on the filesystem
28
+ */
29
+ export declare function fileExists(filePath: string | null | undefined): boolean;
30
+ /**
31
+ * Expand tilde in paths to home directory
32
+ */
33
+ export declare function expandTildeInPath(filePath: string): string;
34
+ /**
35
+ * Search for an attachment file in configured root directories
36
+ * AC04: Support multiple attachment root directories from config
37
+ * Tries multiple strategies:
38
+ * 1. If filename is absolute and exists, return it
39
+ * 2. Try direct join of root + filename
40
+ * 3. Search for basename in root
41
+ */
42
+ export declare function searchAttachmentInRoots(filename: string, attachmentRoots: string[]): string | null;
43
+ /**
44
+ * Infer the source (csv/db) from message metadata or GUID pattern
45
+ */
46
+ export declare function inferSource(message: Message, defaultSource: 'csv' | 'db' | 'merged'): 'csv' | 'db';
47
+ /**
48
+ * Create provenance metadata for a media file
49
+ * AC02: Missing files retain original filename with provenance metadata
50
+ */
51
+ export declare function createProvenance(message: Message, source: 'csv' | 'db' | 'merged'): MediaProvenance;
52
+ /**
53
+ * Validate and enforce absolute paths for all media messages
54
+ *
55
+ * AC01: All media.path fields are absolute paths when files exist on disk
56
+ * AC02: Missing files retain original filename with provenance metadata
57
+ * AC03: Path validation errors reported with counters (found vs missing)
58
+ * AC04: Support multiple attachment root directories from config
59
+ */
60
+ export declare function validateAndEnforcePaths(messages: Message[], config: PathValidationConfig): PathValidationResult;
61
+ /**
62
+ * Format path validation statistics as human-readable string for logging
63
+ */
64
+ export declare function formatPathValidationStats(stats: PathValidationStats): string;
65
+ //# sourceMappingURL=path-validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path-validator.d.ts","sourceRoot":"","sources":["../../src/normalize/path-validator.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAIjE,MAAM,MAAM,oBAAoB,GAAG;IAClC,eAAe,EAAE,MAAM,EAAE,CAAA;IACzB,MAAM,EAAE,KAAK,GAAG,IAAI,GAAG,QAAQ,CAAA;CAC/B,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG;IACjC,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,KAAK,CAAC;QACb,IAAI,EAAE,MAAM,CAAA;QACZ,QAAQ,EAAE,MAAM,CAAA;QAChB,KAAK,EAAE,MAAM,CAAA;KACb,CAAC,CAAA;CACF,CAAA;AAED,MAAM,MAAM,oBAAoB,GAAG;IAClC,QAAQ,EAAE,OAAO,EAAE,CAAA;IACnB,KAAK,EAAE,mBAAmB,CAAA;CAC1B,CAAA;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAK3E;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CASvE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAK1D;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CACtC,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,MAAM,EAAE,GACvB,MAAM,GAAG,IAAI,CA4Bf;AAED;;GAEG;AACH,wBAAgB,WAAW,CAC1B,OAAO,EAAE,OAAO,EAChB,aAAa,EAAE,KAAK,GAAG,IAAI,GAAG,QAAQ,GACpC,KAAK,GAAG,IAAI,CAqBd;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC/B,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,KAAK,GAAG,IAAI,GAAG,QAAQ,GAC7B,eAAe,CAOjB;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CACtC,QAAQ,EAAE,OAAO,EAAE,EACnB,MAAM,EAAE,oBAAoB,GAC1B,oBAAoB,CA8FtB;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,mBAAmB,GAAG,MAAM,CAoB5E"}
@@ -0,0 +1,221 @@
1
+ import { existsSync } from 'node:fs';
2
+ import * as os from 'node:os';
3
+ import * as path from 'node:path';
4
+ /**
5
+ * Validate that a path is absolute (starts with /)
6
+ * AC01: All media.path fields are absolute paths when files exist on disk
7
+ */
8
+ export function isAbsolutePath(filePath) {
9
+ if (!filePath || typeof filePath !== 'string') {
10
+ return false;
11
+ }
12
+ return filePath.startsWith('/');
13
+ }
14
+ /**
15
+ * Check if a file exists on the filesystem
16
+ */
17
+ export function fileExists(filePath) {
18
+ if (!filePath || typeof filePath !== 'string') {
19
+ return false;
20
+ }
21
+ try {
22
+ return existsSync(filePath);
23
+ }
24
+ catch {
25
+ return false;
26
+ }
27
+ }
28
+ /**
29
+ * Expand tilde in paths to home directory
30
+ */
31
+ export function expandTildeInPath(filePath) {
32
+ if (filePath.startsWith('~')) {
33
+ return filePath.replace('~', os.homedir());
34
+ }
35
+ return filePath;
36
+ }
37
+ /**
38
+ * Search for an attachment file in configured root directories
39
+ * AC04: Support multiple attachment root directories from config
40
+ * Tries multiple strategies:
41
+ * 1. If filename is absolute and exists, return it
42
+ * 2. Try direct join of root + filename
43
+ * 3. Search for basename in root
44
+ */
45
+ export function searchAttachmentInRoots(filename, attachmentRoots) {
46
+ if (!filename || attachmentRoots.length === 0) {
47
+ return null;
48
+ }
49
+ for (const root of attachmentRoots) {
50
+ // Expand tilde in root directory
51
+ const expandedRoot = expandTildeInPath(root);
52
+ // Try direct file join
53
+ const directPath = path.join(expandedRoot, filename);
54
+ if (fileExists(directPath)) {
55
+ return directPath;
56
+ }
57
+ // Try basename matching (file in any subdirectory of root)
58
+ try {
59
+ const basename = path.basename(filename);
60
+ const basenameOnly = path.join(expandedRoot, basename);
61
+ if (fileExists(basenameOnly)) {
62
+ return basenameOnly;
63
+ }
64
+ }
65
+ catch {
66
+ // Continue to next root
67
+ }
68
+ }
69
+ return null;
70
+ }
71
+ /**
72
+ * Infer the source (csv/db) from message metadata or GUID pattern
73
+ */
74
+ export function inferSource(message, defaultSource) {
75
+ // Check guid pattern
76
+ if (message.guid?.startsWith('p:')) {
77
+ // Part GUID format: p:index/guid
78
+ return 'db';
79
+ }
80
+ if (message.guid?.startsWith('csv:')) {
81
+ return 'csv';
82
+ }
83
+ // Check if there's explicit metadata
84
+ const msgWithMeta = message;
85
+ if (msgWithMeta.exportMetadata?.source === 'csv') {
86
+ return 'csv';
87
+ }
88
+ if (msgWithMeta.exportMetadata?.source === 'db') {
89
+ return 'db';
90
+ }
91
+ // Default fallback
92
+ return defaultSource === 'csv' ? 'csv' : 'db';
93
+ }
94
+ /**
95
+ * Create provenance metadata for a media file
96
+ * AC02: Missing files retain original filename with provenance metadata
97
+ */
98
+ export function createProvenance(message, source) {
99
+ const now = new Date().toISOString();
100
+ return {
101
+ source: source,
102
+ lastSeen: message.date,
103
+ resolvedAt: now,
104
+ };
105
+ }
106
+ /**
107
+ * Validate and enforce absolute paths for all media messages
108
+ *
109
+ * AC01: All media.path fields are absolute paths when files exist on disk
110
+ * AC02: Missing files retain original filename with provenance metadata
111
+ * AC03: Path validation errors reported with counters (found vs missing)
112
+ * AC04: Support multiple attachment root directories from config
113
+ */
114
+ export function validateAndEnforcePaths(messages, config) {
115
+ const result = {
116
+ messages: [],
117
+ stats: {
118
+ total: 0,
119
+ found: 0,
120
+ missing: 0,
121
+ notAbsolute: 0,
122
+ errors: [],
123
+ },
124
+ };
125
+ for (const message of messages) {
126
+ // Only process media messages
127
+ if (message.messageKind !== 'media' || !message.media) {
128
+ result.messages.push(message);
129
+ continue;
130
+ }
131
+ const updatedMessage = { ...message };
132
+ const media = { ...message.media };
133
+ result.stats.total++;
134
+ let resolvedPath = media.path;
135
+ let pathAbsolute = isAbsolutePath(resolvedPath);
136
+ let pathExists = fileExists(resolvedPath);
137
+ // If path is not absolute, try to resolve it
138
+ if (!pathAbsolute && resolvedPath) {
139
+ // Try expanding tilde
140
+ const expanded = expandTildeInPath(resolvedPath);
141
+ if (fileExists(expanded)) {
142
+ resolvedPath = expanded;
143
+ pathAbsolute = true;
144
+ pathExists = true;
145
+ }
146
+ }
147
+ // If still not absolute, try searching in attachment roots
148
+ if (!pathAbsolute && media.filename) {
149
+ const found = searchAttachmentInRoots(media.filename, config.attachmentRoots);
150
+ if (found) {
151
+ resolvedPath = found;
152
+ pathAbsolute = true;
153
+ pathExists = true;
154
+ }
155
+ }
156
+ // Update statistics and path
157
+ if (pathAbsolute && pathExists) {
158
+ result.stats.found++;
159
+ media.path = resolvedPath;
160
+ }
161
+ else if (pathAbsolute && !pathExists) {
162
+ // Path is absolute but file not found
163
+ result.stats.missing++;
164
+ const error = `File not found at path: ${resolvedPath}`;
165
+ result.stats.errors.push({
166
+ guid: message.guid,
167
+ filename: media.filename,
168
+ error,
169
+ });
170
+ }
171
+ else if (!pathAbsolute) {
172
+ // Path is not absolute
173
+ result.stats.notAbsolute++;
174
+ const error = `Path is not absolute: ${resolvedPath || '(empty)'}`;
175
+ result.stats.errors.push({
176
+ guid: message.guid,
177
+ filename: media.filename,
178
+ error,
179
+ });
180
+ }
181
+ else {
182
+ // Unknown state
183
+ result.stats.missing++;
184
+ const error = 'Could not resolve path';
185
+ result.stats.errors.push({
186
+ guid: message.guid,
187
+ filename: media.filename,
188
+ error,
189
+ });
190
+ }
191
+ // AC02: Add provenance metadata
192
+ const _source = inferSource(message, config.source);
193
+ media.provenance = createProvenance(message, config.source);
194
+ updatedMessage.media = media;
195
+ result.messages.push(updatedMessage);
196
+ }
197
+ return result;
198
+ }
199
+ /**
200
+ * Format path validation statistics as human-readable string for logging
201
+ */
202
+ export function formatPathValidationStats(stats) {
203
+ const lines = [
204
+ '📁 Path Validation Report',
205
+ ` Total media files: ${stats.total}`,
206
+ ` ✓ Found (absolute & exists): ${stats.found}`,
207
+ ` ✗ Missing (absolute but no file): ${stats.missing}`,
208
+ ` ⚠ Not absolute: ${stats.notAbsolute}`,
209
+ ];
210
+ if (stats.errors.length > 0) {
211
+ lines.push(` Errors (${stats.errors.length}):`);
212
+ stats.errors.slice(0, 5).forEach((err) => {
213
+ lines.push(` - ${err.filename} (${err.guid}): ${err.error}`);
214
+ });
215
+ if (stats.errors.length > 5) {
216
+ lines.push(` ... and ${stats.errors.length - 5} more`);
217
+ }
218
+ }
219
+ return lines.join('\n');
220
+ }
221
+ //# sourceMappingURL=path-validator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path-validator.js","sourceRoot":"","sources":["../../src/normalize/path-validator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ import type { ZodIssue } from 'zod';
2
+ import type { Message } from '../schema/message';
3
+ export type ValidationError = {
4
+ index: number;
5
+ fieldPath: string;
6
+ message: string;
7
+ };
8
+ /**
9
+ * AC01: Validate all normalized messages against MessageSchema
10
+ * AC02: Include field paths in error messages
11
+ * AC03: Batch validation - collect all errors before throwing
12
+ * @param messages - Array of messages to validate
13
+ * @returns Array of validated messages if all pass
14
+ * @throws Error with formatted error messages if any validation fails
15
+ */
16
+ export declare function validateNormalizedMessages(messages: unknown[]): Message[];
17
+ /**
18
+ * AC02: Format Zod validation errors with field paths
19
+ * Converts Zod error array into readable messages with paths
20
+ * @param messageIndex - Index of the message in the array
21
+ * @param zodErrors - Zod validation errors
22
+ * @returns Formatted error message string
23
+ */
24
+ export declare function formatValidationErrors(_messageIndex: number, zodErrors: ZodIssue[]): string;
25
+ /**
26
+ * AC04: Detect if message has snake_case fields
27
+ * Recursively checks for snake_case field names
28
+ * @param obj - Object to check
29
+ * @returns true if any snake_case fields found
30
+ */
31
+ export declare function hasSnakeCaseFields(obj: unknown, depth?: number): boolean;
32
+ /**
33
+ * Get all snake_case fields in an object (for error reporting)
34
+ * @param obj - Object to check
35
+ * @returns Array of field names that use snake_case
36
+ */
37
+ export declare function getSnakeCaseFields(obj: unknown, prefix?: string, depth?: number): string[];
38
+ /**
39
+ * AC03: Collect validation errors from multiple messages
40
+ * Helper to aggregate errors with message index
41
+ * @param errorList - List of validation errors
42
+ * @returns Formatted error messages with indices
43
+ */
44
+ export declare function collectValidationErrors(errorList: ValidationError[]): string[];
45
+ //# sourceMappingURL=validate-normalized.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-normalized.d.ts","sourceRoot":"","sources":["../../src/normalize/validate-normalized.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,KAAK,CAAA;AACnC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAGhD,MAAM,MAAM,eAAe,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;CACf,CAAA;AAED;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CAuDzE;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACrC,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,QAAQ,EAAE,GACnB,MAAM,CAeR;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,SAAI,GAAG,OAAO,CAsBnE;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CACjC,GAAG,EAAE,OAAO,EACZ,MAAM,SAAK,EACX,KAAK,SAAI,GACP,MAAM,EAAE,CAuBV;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACtC,SAAS,EAAE,eAAe,EAAE,GAC1B,MAAM,EAAE,CAIV"}
@@ -0,0 +1,144 @@
1
+ // src/normalize/validate-normalized.ts
2
+ // Zod validation layer for normalized messages
3
+ // Spec §4.3, §9: Schema validation with comprehensive error reporting
4
+ import { MessageSchema } from '../schema/message';
5
+ /**
6
+ * AC01: Validate all normalized messages against MessageSchema
7
+ * AC02: Include field paths in error messages
8
+ * AC03: Batch validation - collect all errors before throwing
9
+ * @param messages - Array of messages to validate
10
+ * @returns Array of validated messages if all pass
11
+ * @throws Error with formatted error messages if any validation fails
12
+ */
13
+ export function validateNormalizedMessages(messages) {
14
+ if (!Array.isArray(messages)) {
15
+ throw new Error('Messages must be an array');
16
+ }
17
+ const validatedMessages = [];
18
+ const errors = [];
19
+ // Process each message and collect errors (AC03: no fail-fast)
20
+ for (let i = 0; i < messages.length; i++) {
21
+ const message = messages[i];
22
+ // AC04: Check for snake_case fields before schema validation
23
+ if (typeof message === 'object' &&
24
+ message !== null &&
25
+ hasSnakeCaseFields(message)) {
26
+ const snakeCaseFields = getSnakeCaseFields(message);
27
+ const fieldList = snakeCaseFields.join(', ');
28
+ errors.push({
29
+ index: i,
30
+ fieldPath: 'root',
31
+ message: `Message contains snake_case fields: ${fieldList}. Use camelCase instead.`,
32
+ });
33
+ continue;
34
+ }
35
+ // Validate against schema
36
+ const result = MessageSchema.safeParse(message);
37
+ if (!result.success) {
38
+ // AC02: Format Zod errors with field paths
39
+ const formattedErrors = formatValidationErrors(i, result.error.errors);
40
+ errors.push({
41
+ index: i,
42
+ fieldPath: 'multiple',
43
+ message: formattedErrors,
44
+ });
45
+ }
46
+ else {
47
+ validatedMessages.push(result.data);
48
+ }
49
+ }
50
+ // If any errors occurred, throw with all collected errors (AC03)
51
+ if (errors.length > 0) {
52
+ const errorSummary = errors
53
+ .map((e) => `messages.${e.index}: ${e.message}`)
54
+ .join('\n');
55
+ const error = new Error(`Validation failed:\n${errorSummary}`);
56
+ error.name = 'ValidationError';
57
+ throw error;
58
+ }
59
+ return validatedMessages;
60
+ }
61
+ /**
62
+ * AC02: Format Zod validation errors with field paths
63
+ * Converts Zod error array into readable messages with paths
64
+ * @param messageIndex - Index of the message in the array
65
+ * @param zodErrors - Zod validation errors
66
+ * @returns Formatted error message string
67
+ */
68
+ export function formatValidationErrors(_messageIndex, zodErrors) {
69
+ if (!Array.isArray(zodErrors) || zodErrors.length === 0) {
70
+ return 'Unknown validation error';
71
+ }
72
+ const formatted = zodErrors
73
+ .map((error) => {
74
+ // Build path: ["media", "enrichment", 0, "createdAt"] -> "media.enrichment.0.createdAt"
75
+ const path = (error.path || []).join('.');
76
+ const pathPrefix = path ? `${path}: ` : '';
77
+ return `${pathPrefix}${error.message}`;
78
+ })
79
+ .join('; ');
80
+ return formatted;
81
+ }
82
+ /**
83
+ * AC04: Detect if message has snake_case fields
84
+ * Recursively checks for snake_case field names
85
+ * @param obj - Object to check
86
+ * @returns true if any snake_case fields found
87
+ */
88
+ export function hasSnakeCaseFields(obj, depth = 0) {
89
+ if (depth > 10)
90
+ return false; // Prevent infinite recursion
91
+ if (typeof obj !== 'object' || obj === null)
92
+ return false;
93
+ const record = obj;
94
+ for (const key of Object.keys(record)) {
95
+ // Check if key contains underscore (snake_case indicator)
96
+ if (key.includes('_') && !key.startsWith('__')) {
97
+ return true;
98
+ }
99
+ // Recursively check nested objects
100
+ const value = record[key];
101
+ if (typeof value === 'object' && value !== null) {
102
+ if (hasSnakeCaseFields(value, depth + 1)) {
103
+ return true;
104
+ }
105
+ }
106
+ }
107
+ return false;
108
+ }
109
+ /**
110
+ * Get all snake_case fields in an object (for error reporting)
111
+ * @param obj - Object to check
112
+ * @returns Array of field names that use snake_case
113
+ */
114
+ export function getSnakeCaseFields(obj, prefix = '', depth = 0) {
115
+ if (depth > 10)
116
+ return []; // Prevent infinite recursion
117
+ if (typeof obj !== 'object' || obj === null)
118
+ return [];
119
+ const snakeCaseFields = [];
120
+ const record = obj;
121
+ for (const key of Object.keys(record)) {
122
+ const fullPath = prefix ? `${prefix}.${key}` : key;
123
+ // Check if key contains underscore (snake_case indicator)
124
+ if (key.includes('_') && !key.startsWith('__')) {
125
+ snakeCaseFields.push(fullPath);
126
+ }
127
+ // Recursively check nested objects
128
+ const value = record[key];
129
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
130
+ snakeCaseFields.push(...getSnakeCaseFields(value, fullPath, depth + 1));
131
+ }
132
+ }
133
+ return snakeCaseFields;
134
+ }
135
+ /**
136
+ * AC03: Collect validation errors from multiple messages
137
+ * Helper to aggregate errors with message index
138
+ * @param errorList - List of validation errors
139
+ * @returns Formatted error messages with indices
140
+ */
141
+ export function collectValidationErrors(errorList) {
142
+ return errorList.map((e) => `messages.${e.index}.${e.fieldPath}: ${e.message}`);
143
+ }
144
+ //# sourceMappingURL=validate-normalized.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-normalized.js","sourceRoot":"","sources":["../../src/normalize/validate-normalized.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Embeds and Blockquotes Rendering Module (RENDER--T03)
3
+ *
4
+ * Implements Obsidian-friendly embeds and blockquote rendering:
5
+ * - AC01: Image embeds with ![[path]] syntax
6
+ * - AC02: Preview images with links to originals (HEIC/TIFF)
7
+ * - AC03: Transcription blockquotes with speaker labels
8
+ * - AC04: Link context blockquotes with metadata
9
+ * - AC05: PDF summary blockquotes with formatting
10
+ */
11
+ import type { MediaEnrichment, Message } from '#schema/message';
12
+ /**
13
+ * AC01: Render image embed with Obsidian wikilink syntax
14
+ * Format: ![[path]]
15
+ */
16
+ export declare function renderImageEmbed(path: string): string;
17
+ /**
18
+ * AC02: Render preview image with link to original
19
+ * For HEIC/TIFF files, shows preview with link to original
20
+ */
21
+ export declare function renderPreviewImageWithLink(previewPath: string, originalPath: string): string;
22
+ /**
23
+ * AC03: Render transcription as blockquote
24
+ * Handles multiline transcriptions with speaker labels and timestamps
25
+ */
26
+ export declare function renderTranscriptionBlockquote(enrichment: MediaEnrichment): string;
27
+ /**
28
+ * AC04: Render link context as blockquote
29
+ * Shows title as markdown link and summary as blockquote
30
+ */
31
+ export declare function renderLinkContextBlockquote(enrichment: MediaEnrichment): string;
32
+ /**
33
+ * AC05: Render PDF summary as blockquote
34
+ * Preserves paragraph structure with blockquote formatting
35
+ */
36
+ export declare function renderPdfSummaryBlockquote(enrichment: MediaEnrichment): string;
37
+ /**
38
+ * Determine if a message should render an embed
39
+ * Only renders embeds for image media
40
+ */
41
+ export declare function shouldRenderEmbed(message: Message): boolean;
42
+ /**
43
+ * Get the path to embed for a media message
44
+ * Returns undefined if message cannot be embedded
45
+ */
46
+ export declare function getEmbedPath(message: Message): string | undefined;
47
+ /**
48
+ * Get preview path for an image file
49
+ * For HEIC/TIFF, returns the .jpg preview path
50
+ * For JPG/PNG, returns the original path
51
+ */
52
+ export declare function getPreviewPath(originalPath: string): string;
53
+ /**
54
+ * Get the original path from a preview path
55
+ * Handles HEIC.jpg -> HEIC and TIFF.jpg -> TIFF conversions
56
+ */
57
+ export declare function getOriginalPath(previewPath: string): string;
58
+ /**
59
+ * Extract enrichments of a specific kind from a media message
60
+ */
61
+ export declare function getEnrichmentsByKind(message: Message, kind: MediaEnrichment['kind']): MediaEnrichment[];
62
+ /**
63
+ * Get all transcription enrichments from a message
64
+ */
65
+ export declare function getTranscriptions(message: Message): MediaEnrichment[];
66
+ /**
67
+ * Get all link context enrichments from a message
68
+ */
69
+ export declare function getLinkContexts(message: Message): MediaEnrichment[];
70
+ /**
71
+ * Get all PDF summary enrichments from a message
72
+ */
73
+ export declare function getPdfSummaries(message: Message): MediaEnrichment[];
74
+ /**
75
+ * Render all blockquotes for a message's enrichments
76
+ */
77
+ export type RenderedEnrichments = {
78
+ embeds: string[];
79
+ transcriptions: string[];
80
+ linkContexts: string[];
81
+ pdfSummaries: string[];
82
+ };
83
+ export declare function renderAllEnrichments(message: Message): RenderedEnrichments;
84
+ //# sourceMappingURL=embeds-blockquotes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embeds-blockquotes.d.ts","sourceRoot":"","sources":["../../src/render/embeds-blockquotes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AAE/D;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAErD;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CACzC,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,GAClB,MAAM,CAGR;AAED;;;GAGG;AACH,wBAAgB,6BAA6B,CAC5C,UAAU,EAAE,eAAe,GACzB,MAAM,CAOR;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CAC1C,UAAU,EAAE,eAAe,GACzB,MAAM,CA2BR;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CACzC,UAAU,EAAE,eAAe,GACzB,MAAM,CAOR;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAO3D;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAMjE;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAa3D;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAkB3D;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CACnC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,GAC3B,eAAe,EAAE,CAMnB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,eAAe,EAAE,CAErE;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,eAAe,EAAE,CAEnE;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,eAAe,EAAE,CAEnE;AAED;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG;IACjC,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,YAAY,EAAE,MAAM,EAAE,CAAA;CACtB,CAAA;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,mBAAmB,CAwC1E"}