@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,90 @@
1
+ /**
2
+ * Config File Generator for iMessage Timeline
3
+ *
4
+ * Implements CONFIG--T03: Add Config Generation Command
5
+ * Generates starter configuration files with documentation comments
6
+ */
7
+ import type { ConfigFormat } from './schema.js';
8
+ /**
9
+ * CONFIG-T03-AC02: Generate config file content in specified format
10
+ *
11
+ * @param format - Output format ('json' or 'yaml')
12
+ * @returns Formatted config file content as string
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * const yamlContent = generateConfigContent('yaml')
17
+ * const jsonContent = generateConfigContent('json')
18
+ * ```
19
+ */
20
+ export declare function generateConfigContent(format: ConfigFormat): string;
21
+ /**
22
+ * CONFIG-T03-AC04: Check if config file exists at path
23
+ *
24
+ * @param filePath - Path to check
25
+ * @returns True if file exists and is readable
26
+ */
27
+ export declare function configFileExists(filePath: string): Promise<boolean>;
28
+ /**
29
+ * CONFIG-T03-AC05: Validate generated config content
30
+ *
31
+ * Parses the generated content and validates against schema
32
+ * to ensure the template produces valid config.
33
+ *
34
+ * @param content - Generated config content
35
+ * @param format - Format of content ('json' | 'yaml')
36
+ * @returns Validation result
37
+ */
38
+ export declare function validateGeneratedConfig(content: string, format: ConfigFormat): {
39
+ valid: boolean;
40
+ errors?: string[];
41
+ };
42
+ /**
43
+ * CONFIG-T03-AC01: Generate config file at specified path
44
+ *
45
+ * Implements the core config generation logic with:
46
+ * - Format selection (YAML/JSON)
47
+ * - Overwrite protection
48
+ * - Content validation
49
+ *
50
+ * @param options - Generation options
51
+ * @param options.filePath - Output file path
52
+ * @param options.format - File format ('json' | 'yaml')
53
+ * @param options.force - Overwrite existing file without prompt
54
+ * @returns Success/failure result
55
+ *
56
+ * @example
57
+ * ```typescript
58
+ * // Generate YAML config (default)
59
+ * await generateConfigFile({ filePath: './imessage-config.yaml' })
60
+ *
61
+ * // Generate JSON config
62
+ * await generateConfigFile({
63
+ * filePath: './imessage-config.json',
64
+ * format: 'json'
65
+ * })
66
+ *
67
+ * // Force overwrite
68
+ * await generateConfigFile({
69
+ * filePath: './imessage-config.yaml',
70
+ * force: true
71
+ * })
72
+ * ```
73
+ */
74
+ export declare function generateConfigFile(options: {
75
+ filePath: string;
76
+ format?: ConfigFormat;
77
+ force?: boolean;
78
+ }): Promise<{
79
+ success: boolean;
80
+ message: string;
81
+ filePath: string;
82
+ }>;
83
+ /**
84
+ * Get default config file path for a given format
85
+ *
86
+ * @param format - Desired format ('json' | 'yaml')
87
+ * @returns Default file path
88
+ */
89
+ export declare function getDefaultConfigPath(format: ConfigFormat): string;
90
+ //# sourceMappingURL=generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../src/config/generator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AA+K/C;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAOlE;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOzE;AAED;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CACtC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,YAAY,GAClB;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,CA4BvC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE;IACjD,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,YAAY,CAAA;IACrB,KAAK,CAAC,EAAE,OAAO,CAAA;CACf,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAkCnE;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAEjE"}
@@ -0,0 +1,320 @@
1
+ /**
2
+ * Config File Generator for iMessage Timeline
3
+ *
4
+ * Implements CONFIG--T03: Add Config Generation Command
5
+ * Generates starter configuration files with documentation comments
6
+ */
7
+ import { constants } from 'node:fs';
8
+ import { access, writeFile } from 'node:fs/promises';
9
+ import yaml from 'js-yaml';
10
+ import { validateConfig } from './schema.js';
11
+ /**
12
+ * CONFIG-T03-AC03: Config template with inline documentation
13
+ *
14
+ * This template includes comments explaining each configuration option.
15
+ * The comments are YAML-compatible and will be preserved in YAML output.
16
+ */
17
+ const CONFIG_TEMPLATE_WITH_DOCS = `# iMessage Timeline Configuration File
18
+ # Generated by: imessage-timeline init
19
+ #
20
+ # This file configures the iMessage message export and enrichment pipeline.
21
+ # Supports both YAML and JSON formats with environment variable substitution.
22
+
23
+ # Schema version (for future migrations)
24
+ version: "1.0"
25
+
26
+ # ============================================================================
27
+ # Attachment Resolution
28
+ # ============================================================================
29
+
30
+ # Directories to search for message attachments
31
+ # Multiple paths supported; first match wins
32
+ # Environment variable expansion supported: \${HOME}/Library/Messages
33
+ attachmentRoots:
34
+ - "~/Library/Messages/Attachments"
35
+ # Add additional search paths if needed:
36
+ # - "/path/to/imazing/backup/attachments"
37
+
38
+ # ============================================================================
39
+ # Gemini AI Configuration
40
+ # ============================================================================
41
+
42
+ gemini:
43
+ # Google Gemini API key (required for AI enrichment)
44
+ # Get your key from: https://makersuite.google.com/app/apikey
45
+ # Environment variable recommended for security: \${GEMINI_API_KEY}
46
+ apiKey: "\${GEMINI_API_KEY}"
47
+
48
+ # Gemini model to use for enrichment
49
+ # Options: gemini-1.5-pro, gemini-1.5-flash
50
+ # Default: gemini-1.5-pro (more accurate, slower)
51
+ model: "gemini-1.5-pro"
52
+
53
+ # Delay between API calls (milliseconds)
54
+ # Prevents rate limiting; adjust based on your quota
55
+ # Free tier: 60 requests/minute = 1000ms delay minimum
56
+ # Default: 1000 (1 second)
57
+ rateLimitDelay: 1000
58
+
59
+ # Maximum retries for failed API calls
60
+ # Applies exponential backoff with jitter
61
+ # Range: 0-10
62
+ # Default: 3
63
+ maxRetries: 3
64
+
65
+ # ============================================================================
66
+ # Firecrawl Configuration (Optional)
67
+ # ============================================================================
68
+
69
+ # firecrawl:
70
+ # # Firecrawl API key for enhanced link scraping (optional)
71
+ # # Get your key from: https://firecrawl.dev
72
+ # # Falls back to built-in scrapers if not provided
73
+ # apiKey: "\${FIRECRAWL_API_KEY}"
74
+ #
75
+ # # Enable/disable Firecrawl for link enrichment
76
+ # # Default: true (if apiKey provided)
77
+ # enabled: true
78
+
79
+ # ============================================================================
80
+ # Enrichment Pipeline Configuration
81
+ # ============================================================================
82
+
83
+ enrichment:
84
+ # Enable image analysis (captions, descriptions via Gemini Vision)
85
+ # HEIC/TIFF files converted to JPG for analysis
86
+ # Default: true
87
+ enableVisionAnalysis: true
88
+
89
+ # Enable audio transcription (voice memos, audio messages)
90
+ # Includes timestamps, speaker detection, and summaries
91
+ # Default: true
92
+ enableAudioTranscription: true
93
+
94
+ # Enable link context extraction (titles, summaries, metadata)
95
+ # Uses Firecrawl if configured, falls back to built-in scrapers
96
+ # Default: true
97
+ enableLinkEnrichment: true
98
+
99
+ # Cache directory for image previews (HEIC/TIFF → JPG)
100
+ # Prevents redundant conversions on re-runs
101
+ # Default: ./.cache/images
102
+ imageCacheDir: "./.cache/images"
103
+
104
+ # Checkpoint interval (messages per checkpoint)
105
+ # Lower = more frequent checkpoints = safer but slower
106
+ # Higher = faster but more work lost on interruption
107
+ # Range: 1-10000
108
+ # Default: 100
109
+ checkpointInterval: 100
110
+
111
+ # Force refresh of existing enrichments
112
+ # If true, re-enriches messages even if already enriched
113
+ # Default: false (skip already-enriched messages)
114
+ forceRefresh: false
115
+
116
+ # ============================================================================
117
+ # Markdown Rendering Configuration
118
+ # ============================================================================
119
+
120
+ render:
121
+ # Group messages by time-of-day (Morning, Afternoon, Evening)
122
+ # If false, renders as flat chronological list
123
+ # Default: true
124
+ groupByTimeOfDay: true
125
+
126
+ # Render replies as nested blockquotes under parent message
127
+ # If false, renders as flat list with reply indicators
128
+ # Default: true
129
+ renderRepliesAsNested: true
130
+
131
+ # Render tapbacks (reactions) as emoji (❤️, 😂, etc.)
132
+ # If false, renders as text descriptions
133
+ # Default: true
134
+ renderTapbacksAsEmoji: true
135
+
136
+ # Maximum nesting depth for nested replies
137
+ # Prevents infinite recursion in circular reply chains
138
+ # Range: 1-100
139
+ # Default: 10
140
+ maxNestingDepth: 10
141
+ `;
142
+ /**
143
+ * CONFIG-T03-AC03: Generate JSON template with comments
144
+ *
145
+ * JSON doesn't support inline comments, so we use a separate
146
+ * structure with documentation as values.
147
+ */
148
+ const CONFIG_TEMPLATE_JSON = {
149
+ _comment: 'iMessage Timeline Configuration File',
150
+ _generated: 'Generated by: imessage-timeline init',
151
+ _docs: 'Full documentation: https://github.com/your-repo/imessage-timeline',
152
+ version: '1.0',
153
+ attachmentRoots: ['~/Library/Messages/Attachments'],
154
+ gemini: {
155
+ _comment: 'Get API key from: https://makersuite.google.com/app/apikey',
156
+ apiKey: '${GEMINI_API_KEY}',
157
+ model: 'gemini-1.5-pro',
158
+ rateLimitDelay: 1000,
159
+ maxRetries: 3,
160
+ },
161
+ enrichment: {
162
+ enableVisionAnalysis: true,
163
+ enableAudioTranscription: true,
164
+ enableLinkEnrichment: true,
165
+ imageCacheDir: './.cache/images',
166
+ checkpointInterval: 100,
167
+ forceRefresh: false,
168
+ },
169
+ render: {
170
+ groupByTimeOfDay: true,
171
+ renderRepliesAsNested: true,
172
+ renderTapbacksAsEmoji: true,
173
+ maxNestingDepth: 10,
174
+ },
175
+ };
176
+ /**
177
+ * CONFIG-T03-AC02: Generate config file content in specified format
178
+ *
179
+ * @param format - Output format ('json' or 'yaml')
180
+ * @returns Formatted config file content as string
181
+ *
182
+ * @example
183
+ * ```typescript
184
+ * const yamlContent = generateConfigContent('yaml')
185
+ * const jsonContent = generateConfigContent('json')
186
+ * ```
187
+ */
188
+ export function generateConfigContent(format) {
189
+ if (format === 'yaml') {
190
+ // Return YAML template with comments preserved
191
+ return CONFIG_TEMPLATE_WITH_DOCS;
192
+ }
193
+ // Return formatted JSON with indentation
194
+ return `${JSON.stringify(CONFIG_TEMPLATE_JSON, null, 2)}\n`;
195
+ }
196
+ /**
197
+ * CONFIG-T03-AC04: Check if config file exists at path
198
+ *
199
+ * @param filePath - Path to check
200
+ * @returns True if file exists and is readable
201
+ */
202
+ export async function configFileExists(filePath) {
203
+ try {
204
+ await access(filePath, constants.R_OK);
205
+ return true;
206
+ }
207
+ catch {
208
+ return false;
209
+ }
210
+ }
211
+ /**
212
+ * CONFIG-T03-AC05: Validate generated config content
213
+ *
214
+ * Parses the generated content and validates against schema
215
+ * to ensure the template produces valid config.
216
+ *
217
+ * @param content - Generated config content
218
+ * @param format - Format of content ('json' | 'yaml')
219
+ * @returns Validation result
220
+ */
221
+ export function validateGeneratedConfig(content, format) {
222
+ try {
223
+ let parsed;
224
+ if (format === 'json') {
225
+ parsed = JSON.parse(content);
226
+ }
227
+ else {
228
+ parsed = yaml.load(content);
229
+ }
230
+ // Validate against schema (will throw if invalid)
231
+ // Note: This will fail validation because ${GEMINI_API_KEY} is a placeholder
232
+ // In production, we document this expected behavior
233
+ validateConfig(parsed);
234
+ return { valid: true };
235
+ }
236
+ catch (error) {
237
+ if (error instanceof Error) {
238
+ return {
239
+ valid: false,
240
+ errors: [error.message],
241
+ };
242
+ }
243
+ return {
244
+ valid: false,
245
+ errors: ['Unknown validation error'],
246
+ };
247
+ }
248
+ }
249
+ /**
250
+ * CONFIG-T03-AC01: Generate config file at specified path
251
+ *
252
+ * Implements the core config generation logic with:
253
+ * - Format selection (YAML/JSON)
254
+ * - Overwrite protection
255
+ * - Content validation
256
+ *
257
+ * @param options - Generation options
258
+ * @param options.filePath - Output file path
259
+ * @param options.format - File format ('json' | 'yaml')
260
+ * @param options.force - Overwrite existing file without prompt
261
+ * @returns Success/failure result
262
+ *
263
+ * @example
264
+ * ```typescript
265
+ * // Generate YAML config (default)
266
+ * await generateConfigFile({ filePath: './imessage-config.yaml' })
267
+ *
268
+ * // Generate JSON config
269
+ * await generateConfigFile({
270
+ * filePath: './imessage-config.json',
271
+ * format: 'json'
272
+ * })
273
+ *
274
+ * // Force overwrite
275
+ * await generateConfigFile({
276
+ * filePath: './imessage-config.yaml',
277
+ * force: true
278
+ * })
279
+ * ```
280
+ */
281
+ export async function generateConfigFile(options) {
282
+ const { filePath, format = 'yaml', force = false } = options;
283
+ // CONFIG-T03-AC04: Check for existing file
284
+ const exists = await configFileExists(filePath);
285
+ if (exists && !force) {
286
+ return {
287
+ success: false,
288
+ message: `Config file already exists at ${filePath}. Use --force to overwrite.`,
289
+ filePath,
290
+ };
291
+ }
292
+ // CONFIG-T03-AC02 & AC03: Generate content with docs
293
+ const content = generateConfigContent(format);
294
+ // Write file
295
+ try {
296
+ await writeFile(filePath, content, 'utf-8');
297
+ return {
298
+ success: true,
299
+ message: `✅ Generated ${format.toUpperCase()} config at ${filePath}`,
300
+ filePath,
301
+ };
302
+ }
303
+ catch (error) {
304
+ return {
305
+ success: false,
306
+ message: `Failed to write config file: ${error instanceof Error ? error.message : String(error)}`,
307
+ filePath,
308
+ };
309
+ }
310
+ }
311
+ /**
312
+ * Get default config file path for a given format
313
+ *
314
+ * @param format - Desired format ('json' | 'yaml')
315
+ * @returns Default file path
316
+ */
317
+ export function getDefaultConfigPath(format) {
318
+ return format === 'yaml' ? './imessage-config.yaml' : './imessage-config.json';
319
+ }
320
+ //# sourceMappingURL=generator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generator.js","sourceRoot":"","sources":["../../src/config/generator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Configuration Loader for iMessage Timeline
3
+ *
4
+ * Implements CONFIG--T02: Implement Config Loader
5
+ * Loads config from YAML/JSON files with env var substitution and precedence
6
+ */
7
+ import { type Config } from './schema.js';
8
+ /**
9
+ * CONFIG-T02-AC01: Discover config file in directory
10
+ *
11
+ * Checks files in order:
12
+ * 1. imessage-config.yaml
13
+ * 2. imessage-config.yml
14
+ * 3. imessage-config.json
15
+ *
16
+ * @param baseDir - Directory to search in (defaults to current directory)
17
+ * @returns Path to first existing config file, or null if none found
18
+ */
19
+ export declare function discoverConfigFile(baseDir?: string): Promise<string | null>;
20
+ /**
21
+ * CONFIG-T02-AC01: Load and parse config file
22
+ *
23
+ * Supports JSON and YAML formats with auto-detection
24
+ *
25
+ * @param filePath - Path to config file
26
+ * @returns Parsed config object (unvalidated)
27
+ * @throws Error if file cannot be read or parsed
28
+ */
29
+ export declare function loadConfigFile(filePath: string): Promise<unknown>;
30
+ /**
31
+ * CONFIG-T02-AC03: Substitute environment variables in config
32
+ *
33
+ * Recursively replaces ${VAR_NAME} patterns with environment variable values
34
+ *
35
+ * @param obj - Config object (or primitive)
36
+ * @returns Object with env vars substituted
37
+ *
38
+ * @example
39
+ * ```typescript
40
+ * // With process.env.GEMINI_API_KEY = 'secret123'
41
+ * substituteEnvVars({ apiKey: '${GEMINI_API_KEY}' })
42
+ * // => { apiKey: 'secret123' }
43
+ * ```
44
+ */
45
+ export declare function substituteEnvVars(obj: unknown): unknown;
46
+ /**
47
+ * CONFIG-T02-AC02: Merge configuration with precedence
48
+ *
49
+ * Precedence (highest to lowest):
50
+ * 1. CLI options
51
+ * 2. Config file
52
+ * 3. Defaults (applied by Zod schema)
53
+ *
54
+ * @param fileConfig - Config loaded from file
55
+ * @param cliOptions - Options from CLI flags
56
+ * @returns Merged config object
57
+ */
58
+ export declare function mergeConfig(fileConfig: Partial<Config>, cliOptions?: Partial<Config>): Partial<Config>;
59
+ /**
60
+ * CONFIG-T02: Main config loading function
61
+ *
62
+ * Loads configuration with the following precedence:
63
+ * 1. CLI options (highest priority)
64
+ * 2. Config file
65
+ * 3. Defaults from schema (lowest priority)
66
+ *
67
+ * @param options - Loading options
68
+ * @param options.configPath - Explicit config file path (optional)
69
+ * @param options.cliOptions - CLI options to merge (optional)
70
+ * @param options.skipCache - Force reload even if cached (optional)
71
+ * @returns Validated and merged configuration
72
+ * @throws Error if config is invalid or cannot be loaded
73
+ *
74
+ * @example
75
+ * ```typescript
76
+ * // Load with auto-discovery
77
+ * const config = await loadConfig()
78
+ *
79
+ * // Load specific file
80
+ * const config = await loadConfig({ configPath: './custom-config.yaml' })
81
+ *
82
+ * // Override with CLI options
83
+ * const config = await loadConfig({
84
+ * cliOptions: {
85
+ * gemini: { apiKey: 'override-key' }
86
+ * }
87
+ * })
88
+ * ```
89
+ */
90
+ export declare function loadConfig(options?: {
91
+ configPath?: string;
92
+ cliOptions?: Partial<Config>;
93
+ skipCache?: boolean;
94
+ }): Promise<Config>;
95
+ /**
96
+ * Clear the config cache
97
+ *
98
+ * Useful for testing or when config needs to be reloaded
99
+ */
100
+ export declare function clearConfigCache(): void;
101
+ /**
102
+ * Check if a config is currently cached
103
+ *
104
+ * @returns True if config is cached
105
+ */
106
+ export declare function isConfigCached(): boolean;
107
+ //# sourceMappingURL=loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,OAAO,EAAE,KAAK,MAAM,EAAsC,MAAM,aAAa,CAAA;AAS7E;;;;;;;;;;GAUG;AACH,wBAAsB,kBAAkB,CACvC,OAAO,GAAE,MAAsB,GAC7B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAqBxB;AAED;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CA4BvE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CA+BvD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAC1B,UAAU,EAAE,OAAO,CAAC,MAAM,CAAC,EAC3B,UAAU,GAAE,OAAO,CAAC,MAAM,CAAM,GAC9B,OAAO,CAAC,MAAM,CAAC,CAwCjB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAsB,UAAU,CAC/B,OAAO,GAAE;IACR,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,CAAA;IAC5B,SAAS,CAAC,EAAE,OAAO,CAAA;CACd,GACJ,OAAO,CAAC,MAAM,CAAC,CA+CjB;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAGvC;AAED;;;;GAIG;AACH,wBAAgB,cAAc,IAAI,OAAO,CAExC"}