lens-content-processor 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/dist/bundler/article.d.ts +41 -0
  2. package/dist/bundler/article.js +240 -0
  3. package/dist/bundler/article.js.map +1 -0
  4. package/dist/bundler/article.test.d.ts +1 -0
  5. package/dist/bundler/article.test.js +212 -0
  6. package/dist/bundler/article.test.js.map +1 -0
  7. package/dist/bundler/video.d.ts +50 -0
  8. package/dist/bundler/video.js +213 -0
  9. package/dist/bundler/video.js.map +1 -0
  10. package/dist/bundler/video.test.d.ts +1 -0
  11. package/dist/bundler/video.test.js +95 -0
  12. package/dist/bundler/video.test.js.map +1 -0
  13. package/dist/cli.d.ts +10 -0
  14. package/dist/cli.js +80 -0
  15. package/dist/cli.js.map +1 -0
  16. package/dist/cli.test.d.ts +1 -0
  17. package/dist/cli.test.js +45 -0
  18. package/dist/cli.test.js.map +1 -0
  19. package/dist/flattener/index.d.ts +20 -0
  20. package/dist/flattener/index.js +707 -0
  21. package/dist/flattener/index.js.map +1 -0
  22. package/dist/flattener/index.test.d.ts +1 -0
  23. package/dist/flattener/index.test.js +547 -0
  24. package/dist/flattener/index.test.js.map +1 -0
  25. package/dist/fs/read-vault.d.ts +4 -0
  26. package/dist/fs/read-vault.js +28 -0
  27. package/dist/fs/read-vault.js.map +1 -0
  28. package/dist/fs/read-vault.test.d.ts +1 -0
  29. package/dist/fs/read-vault.test.js +37 -0
  30. package/dist/fs/read-vault.test.js.map +1 -0
  31. package/dist/index.d.ts +75 -0
  32. package/dist/index.js +137 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/parser/course.d.ts +17 -0
  35. package/dist/parser/course.js +145 -0
  36. package/dist/parser/course.js.map +1 -0
  37. package/dist/parser/course.test.d.ts +1 -0
  38. package/dist/parser/course.test.js +117 -0
  39. package/dist/parser/course.test.js.map +1 -0
  40. package/dist/parser/frontmatter.d.ts +8 -0
  41. package/dist/parser/frontmatter.js +58 -0
  42. package/dist/parser/frontmatter.js.map +1 -0
  43. package/dist/parser/frontmatter.test.d.ts +1 -0
  44. package/dist/parser/frontmatter.test.js +54 -0
  45. package/dist/parser/frontmatter.test.js.map +1 -0
  46. package/dist/parser/learning-outcome.d.ts +21 -0
  47. package/dist/parser/learning-outcome.js +116 -0
  48. package/dist/parser/learning-outcome.js.map +1 -0
  49. package/dist/parser/learning-outcome.test.d.ts +1 -0
  50. package/dist/parser/learning-outcome.test.js +75 -0
  51. package/dist/parser/learning-outcome.test.js.map +1 -0
  52. package/dist/parser/lens.d.ts +51 -0
  53. package/dist/parser/lens.js +328 -0
  54. package/dist/parser/lens.js.map +1 -0
  55. package/dist/parser/lens.test.d.ts +1 -0
  56. package/dist/parser/lens.test.js +453 -0
  57. package/dist/parser/lens.test.js.map +1 -0
  58. package/dist/parser/module.d.ts +21 -0
  59. package/dist/parser/module.js +128 -0
  60. package/dist/parser/module.js.map +1 -0
  61. package/dist/parser/module.test.d.ts +1 -0
  62. package/dist/parser/module.test.js +99 -0
  63. package/dist/parser/module.test.js.map +1 -0
  64. package/dist/parser/sections.d.ts +18 -0
  65. package/dist/parser/sections.js +128 -0
  66. package/dist/parser/sections.js.map +1 -0
  67. package/dist/parser/sections.test.d.ts +1 -0
  68. package/dist/parser/sections.test.js +297 -0
  69. package/dist/parser/sections.test.js.map +1 -0
  70. package/dist/parser/wikilink.d.ts +12 -0
  71. package/dist/parser/wikilink.js +35 -0
  72. package/dist/parser/wikilink.js.map +1 -0
  73. package/dist/parser/wikilink.test.d.ts +1 -0
  74. package/dist/parser/wikilink.test.js +76 -0
  75. package/dist/parser/wikilink.test.js.map +1 -0
  76. package/dist/validator/field-typos.d.ts +10 -0
  77. package/dist/validator/field-typos.js +96 -0
  78. package/dist/validator/field-typos.js.map +1 -0
  79. package/dist/validator/field-typos.test.d.ts +1 -0
  80. package/dist/validator/field-typos.test.js +41 -0
  81. package/dist/validator/field-typos.test.js.map +1 -0
  82. package/dist/validator/field-values.d.ts +11 -0
  83. package/dist/validator/field-values.js +37 -0
  84. package/dist/validator/field-values.js.map +1 -0
  85. package/dist/validator/field-values.test.d.ts +1 -0
  86. package/dist/validator/field-values.test.js +45 -0
  87. package/dist/validator/field-values.test.js.map +1 -0
  88. package/dist/validator/segment-fields.d.ts +12 -0
  89. package/dist/validator/segment-fields.js +55 -0
  90. package/dist/validator/segment-fields.js.map +1 -0
  91. package/dist/validator/segment-fields.test.d.ts +1 -0
  92. package/dist/validator/segment-fields.test.js +73 -0
  93. package/dist/validator/segment-fields.test.js.map +1 -0
  94. package/dist/validator/standalone.test.d.ts +1 -0
  95. package/dist/validator/standalone.test.js +190 -0
  96. package/dist/validator/standalone.test.js.map +1 -0
  97. package/dist/validator/uuid.d.ts +17 -0
  98. package/dist/validator/uuid.js +46 -0
  99. package/dist/validator/uuid.js.map +1 -0
  100. package/dist/validator/uuid.test.d.ts +1 -0
  101. package/dist/validator/uuid.test.js +229 -0
  102. package/dist/validator/uuid.test.js.map +1 -0
  103. package/package.json +45 -0
@@ -0,0 +1,213 @@
1
+ /**
2
+ * Parse a timestamp string into seconds.
3
+ *
4
+ * Supports formats:
5
+ * - MM:SS (e.g., "1:30" -> 90)
6
+ * - H:MM:SS (e.g., "1:30:00" -> 5400)
7
+ * - M:SS.ms (e.g., "0:00.40" -> 0.4) - used in .timestamps.json files
8
+ *
9
+ * @param str - Timestamp string
10
+ * @returns Number of seconds, or null if invalid format
11
+ */
12
+ export function parseTimestamp(str) {
13
+ // Handle M:SS.ms format (timestamps.json format)
14
+ const msMatch = str.match(/^(\d+):(\d+(?:\.\d+)?)$/);
15
+ if (msMatch) {
16
+ const minutes = parseInt(msMatch[1], 10);
17
+ const seconds = parseFloat(msMatch[2]);
18
+ return minutes * 60 + seconds;
19
+ }
20
+ // Match M:SS, MM:SS, H:MM:SS, HH:MM:SS patterns
21
+ const parts = str.split(':');
22
+ if (parts.length < 2 || parts.length > 3) {
23
+ return null;
24
+ }
25
+ // Validate all parts are numeric
26
+ for (const part of parts) {
27
+ if (!/^\d+$/.test(part)) {
28
+ return null;
29
+ }
30
+ }
31
+ if (parts.length === 2) {
32
+ // MM:SS format
33
+ const minutes = parseInt(parts[0], 10);
34
+ const seconds = parseInt(parts[1], 10);
35
+ return minutes * 60 + seconds;
36
+ }
37
+ else {
38
+ // H:MM:SS format
39
+ const hours = parseInt(parts[0], 10);
40
+ const minutes = parseInt(parts[1], 10);
41
+ const seconds = parseInt(parts[2], 10);
42
+ return hours * 3600 + minutes * 60 + seconds;
43
+ }
44
+ }
45
+ /**
46
+ * Parse a transcript line to extract its timestamp.
47
+ * Expected format: "M:SS - text" or "H:MM:SS - text"
48
+ *
49
+ * @param line - A line from the transcript
50
+ * @returns The timestamp in seconds, or null if no timestamp found
51
+ */
52
+ function parseTranscriptLineTimestamp(line) {
53
+ // Match timestamp at start of line: "0:00 - ", "1:30 - ", "1:30:00 - "
54
+ const match = line.match(/^(\d+:\d{2}(?::\d{2})?)\s*-/);
55
+ if (!match) {
56
+ return null;
57
+ }
58
+ return parseTimestamp(match[1]);
59
+ }
60
+ /**
61
+ * Extract words from timestamps data that fall within the requested time range.
62
+ *
63
+ * @param timestamps - Array of word-level timestamp entries
64
+ * @param fromSeconds - Start time in seconds
65
+ * @param toSeconds - End time in seconds
66
+ * @returns Words joined with spaces
67
+ */
68
+ export function extractFromTimestamps(timestamps, fromSeconds, toSeconds) {
69
+ const wordsInRange = [];
70
+ for (const entry of timestamps) {
71
+ const entryTime = parseTimestamp(entry.start);
72
+ if (entryTime === null)
73
+ continue;
74
+ // Include word if its start time falls within the range
75
+ if (entryTime >= fromSeconds && entryTime <= toSeconds) {
76
+ wordsInRange.push(entry.text);
77
+ }
78
+ }
79
+ return wordsInRange.join(' ');
80
+ }
81
+ /**
82
+ * Extract transcript content between two timestamps.
83
+ *
84
+ * If timestamps data (from .timestamps.json) is provided, uses word-level
85
+ * extraction. Otherwise, falls back to inline timestamp markers in the
86
+ * transcript markdown.
87
+ *
88
+ * @param transcript - The full transcript content (markdown)
89
+ * @param fromTime - Start timestamp string (e.g., "1:30")
90
+ * @param toTime - End timestamp string (e.g., "5:45")
91
+ * @param file - Source file path for error reporting
92
+ * @param timestamps - Optional word-level timestamps from .timestamps.json
93
+ * @returns Extracted transcript with from/to as seconds, or error
94
+ */
95
+ export function extractVideoExcerpt(transcript, fromTime, toTime, file, timestamps) {
96
+ // Parse the requested timestamps
97
+ const fromSeconds = parseTimestamp(fromTime);
98
+ if (fromSeconds === null) {
99
+ return {
100
+ error: {
101
+ file,
102
+ message: `Invalid start timestamp format: '${fromTime}'`,
103
+ suggestion: 'Use MM:SS (e.g., 1:30) or H:MM:SS (e.g., 1:30:00) format',
104
+ severity: 'error',
105
+ },
106
+ };
107
+ }
108
+ const toSeconds = parseTimestamp(toTime);
109
+ if (toSeconds === null) {
110
+ return {
111
+ error: {
112
+ file,
113
+ message: `Invalid end timestamp format: '${toTime}'`,
114
+ suggestion: 'Use MM:SS (e.g., 1:30) or H:MM:SS (e.g., 1:30:00) format',
115
+ severity: 'error',
116
+ },
117
+ };
118
+ }
119
+ // Validate that from is not after to
120
+ if (fromSeconds > toSeconds) {
121
+ return {
122
+ error: {
123
+ file,
124
+ message: `Start timestamp '${fromTime}' is after end timestamp '${toTime}'`,
125
+ suggestion: 'Ensure the from:: timestamp comes before the to:: timestamp',
126
+ severity: 'error',
127
+ },
128
+ };
129
+ }
130
+ // If timestamps data is provided, use word-level extraction
131
+ if (timestamps && timestamps.length > 0) {
132
+ const extractedText = extractFromTimestamps(timestamps, fromSeconds, toSeconds);
133
+ return {
134
+ from: fromSeconds,
135
+ to: toSeconds,
136
+ transcript: extractedText,
137
+ };
138
+ }
139
+ // Fall back to inline timestamp markers in the markdown
140
+ // Parse the transcript into lines with timestamps
141
+ const lines = transcript.split('\n');
142
+ const timestampedLines = [];
143
+ for (const line of lines) {
144
+ const ts = parseTranscriptLineTimestamp(line);
145
+ if (ts !== null) {
146
+ timestampedLines.push({ timestamp: ts, line });
147
+ }
148
+ }
149
+ // Find lines that fall within the requested range
150
+ // Handle plain prose transcripts (no timestamps at all)
151
+ if (timestampedLines.length === 0) {
152
+ // If starting from 0:00, include entire transcript
153
+ if (fromSeconds === 0) {
154
+ return {
155
+ from: fromSeconds,
156
+ to: toSeconds,
157
+ transcript: transcript.trim(),
158
+ };
159
+ }
160
+ // Can't extract from a non-zero timestamp in a plain prose transcript
161
+ return {
162
+ error: {
163
+ file,
164
+ message: `Start timestamp '${fromTime}' not found in transcript (transcript has no timestamp markers)`,
165
+ suggestion: 'This transcript has no timestamp markers. Use from:: 0:00 to include from the start, or add timestamps to the transcript.',
166
+ severity: 'error',
167
+ },
168
+ };
169
+ }
170
+ // A line is included if its timestamp >= fromSeconds and < toSeconds
171
+ // Special case: fromSeconds === 0 means "start of video" - no need to find exact 0:00 marker
172
+ let foundFrom = fromSeconds === 0;
173
+ let foundTo = false;
174
+ const excerptLines = [];
175
+ for (const { timestamp, line } of timestampedLines) {
176
+ if (timestamp === fromSeconds) {
177
+ foundFrom = true;
178
+ }
179
+ if (timestamp === toSeconds) {
180
+ foundTo = true;
181
+ }
182
+ // Include lines from fromSeconds up to (but not including) toSeconds
183
+ if (timestamp >= fromSeconds && timestamp < toSeconds) {
184
+ excerptLines.push(line);
185
+ }
186
+ }
187
+ if (!foundFrom) {
188
+ return {
189
+ error: {
190
+ file,
191
+ message: `Start timestamp '${fromTime}' not found in transcript`,
192
+ suggestion: 'Check that the timestamp exists as a line marker in the transcript',
193
+ severity: 'error',
194
+ },
195
+ };
196
+ }
197
+ if (!foundTo) {
198
+ return {
199
+ error: {
200
+ file,
201
+ message: `End timestamp '${toTime}' not found in transcript`,
202
+ suggestion: 'Check that the timestamp exists as a line marker in the transcript',
203
+ severity: 'error',
204
+ },
205
+ };
206
+ }
207
+ return {
208
+ from: fromSeconds,
209
+ to: toSeconds,
210
+ transcript: excerptLines.join('\n'),
211
+ };
212
+ }
213
+ //# sourceMappingURL=video.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"video.js","sourceRoot":"","sources":["../../src/bundler/video.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,95 @@
1
+ // src/bundler/video.test.ts
2
+ import { describe, it, expect } from 'vitest';
3
+ import { parseTimestamp, extractVideoExcerpt } from './video.js';
4
+ describe('parseTimestamp', () => {
5
+ it('converts MM:SS to seconds', () => {
6
+ expect(parseTimestamp('1:30')).toBe(90);
7
+ expect(parseTimestamp('5:45')).toBe(345);
8
+ expect(parseTimestamp('0:00')).toBe(0);
9
+ });
10
+ it('converts H:MM:SS to seconds', () => {
11
+ expect(parseTimestamp('1:30:00')).toBe(5400);
12
+ expect(parseTimestamp('2:15:30')).toBe(8130);
13
+ });
14
+ it('returns null for invalid format', () => {
15
+ expect(parseTimestamp('invalid')).toBeNull();
16
+ expect(parseTimestamp('abc:def')).toBeNull();
17
+ });
18
+ });
19
+ describe('extractVideoExcerpt', () => {
20
+ it('extracts transcript between timestamps and returns seconds', () => {
21
+ const transcript = `0:00 - Welcome to this video.
22
+ 0:30 - Today we'll discuss AI safety.
23
+ 1:30 - The first key point is alignment.
24
+ 2:00 - This means ensuring AI does what we want.
25
+ 5:45 - Moving on to the next topic.
26
+ 6:00 - Let's talk about interpretability.
27
+ `;
28
+ const result = extractVideoExcerpt(transcript, '1:30', // 90 seconds
29
+ '5:45', // 345 seconds
30
+ 'video_transcripts/test.md');
31
+ expect(result.from).toBe(90); // Seconds as number
32
+ expect(result.to).toBe(345); // Seconds as number
33
+ expect(result.transcript).toContain('alignment');
34
+ expect(result.transcript).toContain('what we want');
35
+ expect(result.transcript).not.toContain('Welcome');
36
+ expect(result.transcript).not.toContain('interpretability');
37
+ });
38
+ it('returns error for invalid timestamp format', () => {
39
+ const transcript = '0:00 - Content here.';
40
+ const result = extractVideoExcerpt(transcript, 'invalid', '1:00', 'video_transcripts/test.md');
41
+ expect(result.error).toBeDefined();
42
+ expect(result.error?.message).toContain('timestamp');
43
+ expect(result.error?.suggestion).toContain('MM:SS');
44
+ });
45
+ it('returns error when timestamp not found in transcript', () => {
46
+ const transcript = '0:00 - Short video.\n0:30 - End.';
47
+ const result = extractVideoExcerpt(transcript, '5:00', '10:00', 'video_transcripts/test.md');
48
+ expect(result.error).toBeDefined();
49
+ expect(result.error?.message).toContain('not found');
50
+ });
51
+ it('returns error when from timestamp is after to timestamp', () => {
52
+ const transcript = `0:00 - Start.
53
+ 3:00 - Middle.
54
+ 5:00 - End.`;
55
+ const result = extractVideoExcerpt(transcript, '5:00', '3:00', 'video.md');
56
+ expect(result.error).toBeDefined();
57
+ expect(result.error?.message).toContain('after');
58
+ });
59
+ describe('from 0:00 (start of video)', () => {
60
+ it('treats 0:00 as start of video even when no 0:00 marker exists', () => {
61
+ // Transcript starts at 0:30, not 0:00
62
+ const transcript = `0:30 - First line of dialogue.
63
+ 1:00 - Second line continues.
64
+ 1:30 - Third line wraps up.
65
+ 2:00 - End of section.`;
66
+ const result = extractVideoExcerpt(transcript, '0:00', '1:30', 'video.md');
67
+ // Should NOT error - 0:00 means "from the start"
68
+ expect(result.error).toBeUndefined();
69
+ expect(result.from).toBe(0);
70
+ expect(result.to).toBe(90);
71
+ // Should include lines from start up to (but not including) 1:30
72
+ expect(result.transcript).toContain('First line');
73
+ expect(result.transcript).toContain('Second line');
74
+ expect(result.transcript).not.toContain('Third line');
75
+ });
76
+ it('handles plain prose transcript with no timestamps at all', () => {
77
+ // This is common for transcripts that are just paragraphs of text
78
+ const transcript = `This is the first paragraph of the transcript. It contains some interesting content about AI safety.
79
+
80
+ This is the second paragraph. It continues discussing the topic.
81
+
82
+ This is the third paragraph. It concludes the main points.`;
83
+ const result = extractVideoExcerpt(transcript, '0:00', '5:00', 'video.md');
84
+ // Should NOT error - include entire content since no timestamps exist
85
+ expect(result.error).toBeUndefined();
86
+ expect(result.from).toBe(0);
87
+ expect(result.to).toBe(300);
88
+ // Should include entire transcript
89
+ expect(result.transcript).toContain('first paragraph');
90
+ expect(result.transcript).toContain('second paragraph');
91
+ expect(result.transcript).toContain('third paragraph');
92
+ });
93
+ });
94
+ });
95
+ //# sourceMappingURL=video.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"video.test.js","sourceRoot":"","sources":["../../src/bundler/video.test.ts"],"names":[],"mappings":"AAAA,4BAA4B;AAC5B,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEjE,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,EAAE,CAAC,2BAA2B,EAAE,GAAG,EAAE;QACnC,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACxC,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACzC,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;QACrC,MAAM,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7C,MAAM,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC7C,MAAM,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC/C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,EAAE,CAAC,4DAA4D,EAAE,GAAG,EAAE;QACpE,MAAM,UAAU,GAAG;;;;;;CAMtB,CAAC;QAEE,MAAM,MAAM,GAAG,mBAAmB,CAChC,UAAU,EACV,MAAM,EAAI,aAAa;QACvB,MAAM,EAAI,cAAc;QACxB,2BAA2B,CAC5B,CAAC;QAEF,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAW,oBAAoB;QAC5D,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAY,oBAAoB;QAC5D,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;QACjD,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;QACpD,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;QACnD,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,kBAAkB,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,UAAU,GAAG,sBAAsB,CAAC;QAE1C,MAAM,MAAM,GAAG,mBAAmB,CAChC,UAAU,EACV,SAAS,EACT,MAAM,EACN,2BAA2B,CAC5B,CAAC;QAEF,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;QACnC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;QACrD,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sDAAsD,EAAE,GAAG,EAAE;QAC9D,MAAM,UAAU,GAAG,kCAAkC,CAAC;QAEtD,MAAM,MAAM,GAAG,mBAAmB,CAChC,UAAU,EACV,MAAM,EACN,OAAO,EACP,2BAA2B,CAC5B,CAAC;QAEF,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;QACnC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;IACvD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yDAAyD,EAAE,GAAG,EAAE;QACjE,MAAM,UAAU,GAAG;;YAEX,CAAC;QAET,MAAM,MAAM,GAAG,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC;QAE3E,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;QACnC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,4BAA4B,EAAE,GAAG,EAAE;QAC1C,EAAE,CAAC,+DAA+D,EAAE,GAAG,EAAE;YACvE,sCAAsC;YACtC,MAAM,UAAU,GAAG;;;uBAGF,CAAC;YAElB,MAAM,MAAM,GAAG,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC;YAE3E,iDAAiD;YACjD,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,aAAa,EAAE,CAAC;YACrC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAC5B,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC3B,iEAAiE;YACjE,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;YAClD,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;YACnD,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;QACxD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;YAClE,kEAAkE;YAClE,MAAM,UAAU,GAAG;;;;2DAIkC,CAAC;YAEtD,MAAM,MAAM,GAAG,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC;YAE3E,sEAAsE;YACtE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,aAAa,EAAE,CAAC;YACrC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAC5B,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC5B,mCAAmC;YACnC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC;YACvD,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,kBAAkB,CAAC,CAAC;YACxD,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC;QACzD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ import { ProcessResult } from './index.js';
3
+ export interface CliOptions {
4
+ vaultPath: string | null;
5
+ outputPath: string | null;
6
+ includeWip: boolean;
7
+ stdin: boolean;
8
+ }
9
+ export declare function parseArgs(argv: string[]): CliOptions;
10
+ export declare function run(options: CliOptions): Promise<ProcessResult>;
package/dist/cli.js ADDED
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env node
2
+ // src/cli.ts
3
+ import { readVaultFiles } from './fs/read-vault.js';
4
+ import { processContent } from './index.js';
5
+ import { writeFile } from 'fs/promises';
6
+ export function parseArgs(argv) {
7
+ const args = argv.slice(2);
8
+ let vaultPath = null;
9
+ let outputPath = null;
10
+ let includeWip = false;
11
+ let stdin = false; // NEW
12
+ for (let i = 0; i < args.length; i++) {
13
+ if (args[i] === '--output' || args[i] === '-o') {
14
+ outputPath = args[i + 1] || null;
15
+ i++; // skip next arg
16
+ }
17
+ else if (args[i] === '--include-wip') {
18
+ includeWip = true;
19
+ }
20
+ else if (args[i] === '--stdin') { // NEW
21
+ stdin = true;
22
+ }
23
+ else if (!args[i].startsWith('-')) {
24
+ vaultPath = args[i];
25
+ }
26
+ }
27
+ return { vaultPath, outputPath, includeWip, stdin };
28
+ }
29
+ async function readStdin() {
30
+ const chunks = [];
31
+ for await (const chunk of process.stdin) {
32
+ chunks.push(chunk);
33
+ }
34
+ return Buffer.concat(chunks).toString('utf-8');
35
+ }
36
+ export async function run(options) {
37
+ let files;
38
+ if (options.stdin) {
39
+ // Read JSON from stdin: { "path": "content", ... }
40
+ const input = await readStdin();
41
+ const parsed = JSON.parse(input);
42
+ files = new Map(Object.entries(parsed));
43
+ }
44
+ else {
45
+ if (!options.vaultPath) {
46
+ throw new Error('Vault path is required (or use --stdin)');
47
+ }
48
+ files = await readVaultFiles(options.vaultPath, { includeWip: options.includeWip });
49
+ }
50
+ return processContent(files);
51
+ }
52
+ async function main() {
53
+ const options = parseArgs(process.argv);
54
+ if (!options.vaultPath && !options.stdin) {
55
+ console.error('Usage: npx tsx src/cli.ts <vault-path> [--output <file>] [--include-wip]');
56
+ console.error(' npx tsx src/cli.ts --stdin [--output <file>]');
57
+ process.exit(1);
58
+ }
59
+ try {
60
+ const result = await run(options);
61
+ const json = JSON.stringify(result, null, 2);
62
+ if (options.outputPath) {
63
+ await writeFile(options.outputPath, json, 'utf-8');
64
+ console.error(`Written to ${options.outputPath}`);
65
+ }
66
+ else {
67
+ console.log(json);
68
+ }
69
+ }
70
+ catch (error) {
71
+ console.error('Error:', error instanceof Error ? error.message : error);
72
+ process.exit(1);
73
+ }
74
+ }
75
+ // Run if executed directly (not imported as a module)
76
+ const isMainModule = process.argv[1]?.includes('cli.ts') || process.argv[1]?.includes('cli.js');
77
+ if (isMainModule) {
78
+ main();
79
+ }
80
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,45 @@
1
+ // src/cli.test.ts
2
+ import { describe, it, expect } from 'vitest';
3
+ import { spawn } from 'child_process';
4
+ import { readFile } from 'fs/promises';
5
+ import { join } from 'path';
6
+ describe('CLI --stdin flag', () => {
7
+ it('produces same output as file-based input', async () => {
8
+ const fixturePath = join(__dirname, '../fixtures/valid/minimal-module/input');
9
+ const expectedPath = join(__dirname, '../fixtures/valid/minimal-module/expected.json');
10
+ // Read fixture files into a Map-like object
11
+ const { readVaultFiles } = await import('./fs/read-vault.js');
12
+ const files = await readVaultFiles(fixturePath);
13
+ const filesObject = {};
14
+ for (const [path, content] of files.entries()) {
15
+ filesObject[path] = content;
16
+ }
17
+ // Run CLI with --stdin
18
+ const result = await new Promise((resolve, reject) => {
19
+ const child = spawn('npx', ['tsx', 'src/cli.ts', '--stdin'], {
20
+ cwd: join(__dirname, '..'),
21
+ stdio: ['pipe', 'pipe', 'pipe'],
22
+ });
23
+ let stdout = '';
24
+ let stderr = '';
25
+ child.stdout.on('data', (data) => { stdout += data; });
26
+ child.stderr.on('data', (data) => { stderr += data; });
27
+ child.on('close', (code) => {
28
+ if (code !== 0) {
29
+ reject(new Error(`CLI exited with code ${code}: ${stderr}`));
30
+ }
31
+ else {
32
+ resolve(stdout);
33
+ }
34
+ });
35
+ // Write JSON to stdin
36
+ child.stdin.write(JSON.stringify(filesObject));
37
+ child.stdin.end();
38
+ });
39
+ // Parse and compare (ignore whitespace differences)
40
+ const actual = JSON.parse(result);
41
+ const expected = JSON.parse(await readFile(expectedPath, 'utf-8'));
42
+ expect(actual).toEqual(expected);
43
+ }, 60000); // 60s timeout for subprocess (first run compiles)
44
+ });
45
+ //# sourceMappingURL=cli.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.test.js","sourceRoot":"","sources":["../src/cli.test.ts"],"names":[],"mappings":"AAAA,kBAAkB;AAClB,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACtC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,EAAE,CAAC,0CAA0C,EAAE,KAAK,IAAI,EAAE;QACxD,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,EAAE,wCAAwC,CAAC,CAAC;QAC9E,MAAM,YAAY,GAAG,IAAI,CAAC,SAAS,EAAE,gDAAgD,CAAC,CAAC;QAEvF,4CAA4C;QAC5C,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;QAC9D,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,WAAW,CAAC,CAAC;QAChD,MAAM,WAAW,GAA2B,EAAE,CAAC;QAC/C,KAAK,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC;YAC9C,WAAW,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC;QAC9B,CAAC;QAED,uBAAuB;QACvB,MAAM,MAAM,GAAG,MAAM,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC3D,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,YAAY,EAAE,SAAS,CAAC,EAAE;gBAC3D,GAAG,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC;gBAC1B,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;aAChC,CAAC,CAAC;YAEH,IAAI,MAAM,GAAG,EAAE,CAAC;YAChB,IAAI,MAAM,GAAG,EAAE,CAAC;YAEhB,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,GAAG,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;YACvD,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,GAAG,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;YAEvD,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;gBACzB,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;oBACf,MAAM,CAAC,IAAI,KAAK,CAAC,wBAAwB,IAAI,KAAK,MAAM,EAAE,CAAC,CAAC,CAAC;gBAC/D,CAAC;qBAAM,CAAC;oBACN,OAAO,CAAC,MAAM,CAAC,CAAC;gBAClB,CAAC;YACH,CAAC,CAAC,CAAC;YAEH,sBAAsB;YACtB,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC;YAC/C,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;QACpB,CAAC,CAAC,CAAC;QAEH,oDAAoD;QACpD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAClC,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,CAAC;QAEnE,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACnC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,kDAAkD;AAC/D,CAAC,CAAC,CAAC"}
@@ -0,0 +1,20 @@
1
+ import type { FlattenedModule, ContentError } from '../index.js';
2
+ export interface FlattenModuleResult {
3
+ module: FlattenedModule | null;
4
+ errors: ContentError[];
5
+ }
6
+ /**
7
+ * Flatten a module by resolving all references to Learning Outcomes, Lenses, and content.
8
+ *
9
+ * This function:
10
+ * 1. Parses the module file
11
+ * 2. For each Learning Outcome section, resolves the LO file
12
+ * 3. For each Lens in the LO, resolves the lens file
13
+ * 4. For each segment, extracts content from articles/videos as needed
14
+ *
15
+ * @param modulePath - Path to the module file within the files Map
16
+ * @param files - Map of all file paths to their content
17
+ * @param visitedPaths - Optional set of already-visited paths for cycle detection
18
+ * @returns Flattened module with resolved sections and segments, plus any errors
19
+ */
20
+ export declare function flattenModule(modulePath: string, files: Map<string, string>, visitedPaths?: Set<string>): FlattenModuleResult;