offcourse 0.0.1 → 1.0.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 (284) hide show
  1. package/.github/workflows/ci.yml +50 -0
  2. package/.husky/commit-msg +2 -0
  3. package/.husky/pre-commit +1 -0
  4. package/.husky/pre-push +3 -0
  5. package/.prettierrc +8 -0
  6. package/.release-it.json +23 -0
  7. package/ARCHITECTURE.md +233 -0
  8. package/CHANGELOG.md +78 -0
  9. package/README.md +256 -16
  10. package/commitlint.config.js +4 -0
  11. package/dist/ai/openRouter.d.ts +47 -0
  12. package/dist/ai/openRouter.d.ts.map +1 -0
  13. package/dist/ai/openRouter.js +116 -0
  14. package/dist/ai/openRouter.js.map +1 -0
  15. package/dist/ai/transcriptPolisher.d.ts +24 -0
  16. package/dist/ai/transcriptPolisher.d.ts.map +1 -0
  17. package/dist/ai/transcriptPolisher.js +89 -0
  18. package/dist/ai/transcriptPolisher.js.map +1 -0
  19. package/dist/cli/commands/config.d.ts +13 -0
  20. package/dist/cli/commands/config.d.ts.map +1 -0
  21. package/dist/cli/commands/config.js +66 -0
  22. package/dist/cli/commands/config.js.map +1 -0
  23. package/dist/cli/commands/enrich.d.ts +14 -0
  24. package/dist/cli/commands/enrich.d.ts.map +1 -0
  25. package/dist/cli/commands/enrich.js +271 -0
  26. package/dist/cli/commands/enrich.js.map +1 -0
  27. package/dist/cli/commands/inspect.d.ts +11 -0
  28. package/dist/cli/commands/inspect.d.ts.map +1 -0
  29. package/dist/cli/commands/inspect.js +365 -0
  30. package/dist/cli/commands/inspect.js.map +1 -0
  31. package/dist/cli/commands/login.d.ts +12 -0
  32. package/dist/cli/commands/login.d.ts.map +1 -0
  33. package/dist/cli/commands/login.js +55 -0
  34. package/dist/cli/commands/login.js.map +1 -0
  35. package/dist/cli/commands/status.d.ts +15 -0
  36. package/dist/cli/commands/status.d.ts.map +1 -0
  37. package/dist/cli/commands/status.js +118 -0
  38. package/dist/cli/commands/status.js.map +1 -0
  39. package/dist/cli/commands/sync.d.ts +16 -0
  40. package/dist/cli/commands/sync.d.ts.map +1 -0
  41. package/dist/cli/commands/sync.js +922 -0
  42. package/dist/cli/commands/sync.js.map +1 -0
  43. package/dist/cli/commands/syncGhl.d.ts +20 -0
  44. package/dist/cli/commands/syncGhl.d.ts.map +1 -0
  45. package/dist/cli/commands/syncGhl.js +483 -0
  46. package/dist/cli/commands/syncGhl.js.map +1 -0
  47. package/dist/cli/commands/syncHighLevel.d.ts +24 -0
  48. package/dist/cli/commands/syncHighLevel.d.ts.map +1 -0
  49. package/dist/cli/commands/syncHighLevel.js +483 -0
  50. package/dist/cli/commands/syncHighLevel.js.map +1 -0
  51. package/dist/cli/commands/syncHighLevel.test.d.ts +2 -0
  52. package/dist/cli/commands/syncHighLevel.test.d.ts.map +1 -0
  53. package/dist/cli/commands/syncHighLevel.test.js +102 -0
  54. package/dist/cli/commands/syncHighLevel.test.js.map +1 -0
  55. package/dist/cli/index.d.ts +3 -0
  56. package/dist/cli/index.d.ts.map +1 -0
  57. package/dist/cli/index.js +106 -0
  58. package/dist/cli/index.js.map +1 -0
  59. package/dist/config/configManager.d.ts +31 -0
  60. package/dist/config/configManager.d.ts.map +1 -0
  61. package/dist/config/configManager.js +64 -0
  62. package/dist/config/configManager.js.map +1 -0
  63. package/dist/config/paths.d.ts +21 -0
  64. package/dist/config/paths.d.ts.map +1 -0
  65. package/dist/config/paths.js +33 -0
  66. package/dist/config/paths.js.map +1 -0
  67. package/dist/config/paths.test.d.ts +2 -0
  68. package/dist/config/paths.test.d.ts.map +1 -0
  69. package/dist/config/paths.test.js +70 -0
  70. package/dist/config/paths.test.js.map +1 -0
  71. package/dist/config/schema.d.ts +60 -0
  72. package/dist/config/schema.d.ts.map +1 -0
  73. package/dist/config/schema.js +50 -0
  74. package/dist/config/schema.js.map +1 -0
  75. package/dist/config/schema.test.d.ts +2 -0
  76. package/dist/config/schema.test.d.ts.map +1 -0
  77. package/dist/config/schema.test.js +151 -0
  78. package/dist/config/schema.test.js.map +1 -0
  79. package/dist/downloader/hlsDownloader.d.ts +58 -0
  80. package/dist/downloader/hlsDownloader.d.ts.map +1 -0
  81. package/dist/downloader/hlsDownloader.js +254 -0
  82. package/dist/downloader/hlsDownloader.js.map +1 -0
  83. package/dist/downloader/hlsDownloader.test.d.ts +2 -0
  84. package/dist/downloader/hlsDownloader.test.d.ts.map +1 -0
  85. package/dist/downloader/hlsDownloader.test.js +116 -0
  86. package/dist/downloader/hlsDownloader.test.js.map +1 -0
  87. package/dist/downloader/hlsValidator.d.ts +35 -0
  88. package/dist/downloader/hlsValidator.d.ts.map +1 -0
  89. package/dist/downloader/hlsValidator.js +148 -0
  90. package/dist/downloader/hlsValidator.js.map +1 -0
  91. package/dist/downloader/index.d.ts +26 -0
  92. package/dist/downloader/index.d.ts.map +1 -0
  93. package/dist/downloader/index.js +52 -0
  94. package/dist/downloader/index.js.map +1 -0
  95. package/dist/downloader/loomDownloader.d.ts +56 -0
  96. package/dist/downloader/loomDownloader.d.ts.map +1 -0
  97. package/dist/downloader/loomDownloader.js +559 -0
  98. package/dist/downloader/loomDownloader.js.map +1 -0
  99. package/dist/downloader/loomDownloader.test.d.ts +2 -0
  100. package/dist/downloader/loomDownloader.test.d.ts.map +1 -0
  101. package/dist/downloader/loomDownloader.test.js +36 -0
  102. package/dist/downloader/loomDownloader.test.js.map +1 -0
  103. package/dist/downloader/queue.d.ts +56 -0
  104. package/dist/downloader/queue.d.ts.map +1 -0
  105. package/dist/downloader/queue.js +88 -0
  106. package/dist/downloader/queue.js.map +1 -0
  107. package/dist/downloader/queue.test.d.ts +2 -0
  108. package/dist/downloader/queue.test.d.ts.map +1 -0
  109. package/dist/downloader/queue.test.js +158 -0
  110. package/dist/downloader/queue.test.js.map +1 -0
  111. package/dist/downloader/videoDownloader.d.ts +32 -0
  112. package/dist/downloader/videoDownloader.d.ts.map +1 -0
  113. package/dist/downloader/videoDownloader.js +173 -0
  114. package/dist/downloader/videoDownloader.js.map +1 -0
  115. package/dist/downloader/vimeoDownloader.d.ts +52 -0
  116. package/dist/downloader/vimeoDownloader.d.ts.map +1 -0
  117. package/dist/downloader/vimeoDownloader.js +565 -0
  118. package/dist/downloader/vimeoDownloader.js.map +1 -0
  119. package/dist/downloader/vimeoDownloader.test.d.ts +2 -0
  120. package/dist/downloader/vimeoDownloader.test.d.ts.map +1 -0
  121. package/dist/downloader/vimeoDownloader.test.js +51 -0
  122. package/dist/downloader/vimeoDownloader.test.js.map +1 -0
  123. package/dist/scraper/auth.d.ts +29 -0
  124. package/dist/scraper/auth.d.ts.map +1 -0
  125. package/dist/scraper/auth.js +115 -0
  126. package/dist/scraper/auth.js.map +1 -0
  127. package/dist/scraper/extractor.d.ts +49 -0
  128. package/dist/scraper/extractor.d.ts.map +1 -0
  129. package/dist/scraper/extractor.js +627 -0
  130. package/dist/scraper/extractor.js.map +1 -0
  131. package/dist/scraper/extractor.test.d.ts +2 -0
  132. package/dist/scraper/extractor.test.d.ts.map +1 -0
  133. package/dist/scraper/extractor.test.js +65 -0
  134. package/dist/scraper/extractor.test.js.map +1 -0
  135. package/dist/scraper/ghl/auth.d.ts +25 -0
  136. package/dist/scraper/ghl/auth.d.ts.map +1 -0
  137. package/dist/scraper/ghl/auth.js +187 -0
  138. package/dist/scraper/ghl/auth.js.map +1 -0
  139. package/dist/scraper/ghl/extractor.d.ts +96 -0
  140. package/dist/scraper/ghl/extractor.d.ts.map +1 -0
  141. package/dist/scraper/ghl/extractor.js +345 -0
  142. package/dist/scraper/ghl/extractor.js.map +1 -0
  143. package/dist/scraper/ghl/index.d.ts +4 -0
  144. package/dist/scraper/ghl/index.d.ts.map +1 -0
  145. package/dist/scraper/ghl/index.js +4 -0
  146. package/dist/scraper/ghl/index.js.map +1 -0
  147. package/dist/scraper/ghl/navigator.d.ts +93 -0
  148. package/dist/scraper/ghl/navigator.d.ts.map +1 -0
  149. package/dist/scraper/ghl/navigator.js +447 -0
  150. package/dist/scraper/ghl/navigator.js.map +1 -0
  151. package/dist/scraper/highlevel/auth.d.ts +25 -0
  152. package/dist/scraper/highlevel/auth.d.ts.map +1 -0
  153. package/dist/scraper/highlevel/auth.js +189 -0
  154. package/dist/scraper/highlevel/auth.js.map +1 -0
  155. package/dist/scraper/highlevel/extractor.d.ts +97 -0
  156. package/dist/scraper/highlevel/extractor.d.ts.map +1 -0
  157. package/dist/scraper/highlevel/extractor.js +386 -0
  158. package/dist/scraper/highlevel/extractor.js.map +1 -0
  159. package/dist/scraper/highlevel/extractor.test.d.ts +2 -0
  160. package/dist/scraper/highlevel/extractor.test.d.ts.map +1 -0
  161. package/dist/scraper/highlevel/extractor.test.js +101 -0
  162. package/dist/scraper/highlevel/extractor.test.js.map +1 -0
  163. package/dist/scraper/highlevel/index.d.ts +3 -0
  164. package/dist/scraper/highlevel/index.d.ts.map +1 -0
  165. package/dist/scraper/highlevel/index.js +3 -0
  166. package/dist/scraper/highlevel/index.js.map +1 -0
  167. package/dist/scraper/highlevel/navigator.d.ts +93 -0
  168. package/dist/scraper/highlevel/navigator.d.ts.map +1 -0
  169. package/dist/scraper/highlevel/navigator.js +492 -0
  170. package/dist/scraper/highlevel/navigator.js.map +1 -0
  171. package/dist/scraper/highlevel/navigator.test.d.ts +2 -0
  172. package/dist/scraper/highlevel/navigator.test.d.ts.map +1 -0
  173. package/dist/scraper/highlevel/navigator.test.js +78 -0
  174. package/dist/scraper/highlevel/navigator.test.js.map +1 -0
  175. package/dist/scraper/navigator.d.ts +65 -0
  176. package/dist/scraper/navigator.d.ts.map +1 -0
  177. package/dist/scraper/navigator.js +300 -0
  178. package/dist/scraper/navigator.js.map +1 -0
  179. package/dist/scraper/navigator.test.d.ts +2 -0
  180. package/dist/scraper/navigator.test.d.ts.map +1 -0
  181. package/dist/scraper/navigator.test.js +63 -0
  182. package/dist/scraper/navigator.test.js.map +1 -0
  183. package/dist/scraper/skoolApi.d.ts +17 -0
  184. package/dist/scraper/skoolApi.d.ts.map +1 -0
  185. package/dist/scraper/skoolApi.js +72 -0
  186. package/dist/scraper/skoolApi.js.map +1 -0
  187. package/dist/scraper/videoInterceptor.d.ts +19 -0
  188. package/dist/scraper/videoInterceptor.d.ts.map +1 -0
  189. package/dist/scraper/videoInterceptor.js +315 -0
  190. package/dist/scraper/videoInterceptor.js.map +1 -0
  191. package/dist/shared/auth.d.ts +58 -0
  192. package/dist/shared/auth.d.ts.map +1 -0
  193. package/dist/shared/auth.js +211 -0
  194. package/dist/shared/auth.js.map +1 -0
  195. package/dist/shared/fs.d.ts +31 -0
  196. package/dist/shared/fs.d.ts.map +1 -0
  197. package/dist/shared/fs.js +73 -0
  198. package/dist/shared/fs.js.map +1 -0
  199. package/dist/shared/http.d.ts +15 -0
  200. package/dist/shared/http.d.ts.map +1 -0
  201. package/dist/shared/http.js +31 -0
  202. package/dist/shared/http.js.map +1 -0
  203. package/dist/shared/index.d.ts +4 -0
  204. package/dist/shared/index.d.ts.map +1 -0
  205. package/dist/shared/index.js +4 -0
  206. package/dist/shared/index.js.map +1 -0
  207. package/dist/state/database.d.ts +245 -0
  208. package/dist/state/database.d.ts.map +1 -0
  209. package/dist/state/database.js +676 -0
  210. package/dist/state/database.js.map +1 -0
  211. package/dist/state/database.test.d.ts +2 -0
  212. package/dist/state/database.test.d.ts.map +1 -0
  213. package/dist/state/database.test.js +34 -0
  214. package/dist/state/database.test.js.map +1 -0
  215. package/dist/state/index.d.ts +2 -0
  216. package/dist/state/index.d.ts.map +1 -0
  217. package/dist/state/index.js +2 -0
  218. package/dist/state/index.js.map +1 -0
  219. package/dist/storage/fileSystem.d.ts +56 -0
  220. package/dist/storage/fileSystem.d.ts.map +1 -0
  221. package/dist/storage/fileSystem.js +121 -0
  222. package/dist/storage/fileSystem.js.map +1 -0
  223. package/dist/transcription/whisperService.d.ts +27 -0
  224. package/dist/transcription/whisperService.d.ts.map +1 -0
  225. package/dist/transcription/whisperService.js +102 -0
  226. package/dist/transcription/whisperService.js.map +1 -0
  227. package/eslint.config.js +55 -0
  228. package/package.json +68 -11
  229. package/src/__fixtures__/highlevel-post-response.json +68 -0
  230. package/src/__fixtures__/hls-master-playlist.m3u8 +24 -0
  231. package/src/cli/commands/__snapshots__/syncHighLevel.test.ts.snap +38 -0
  232. package/src/cli/commands/config.ts +74 -0
  233. package/src/cli/commands/inspect.ts +441 -0
  234. package/src/cli/commands/login.ts +68 -0
  235. package/src/cli/commands/status.ts +147 -0
  236. package/src/cli/commands/sync.ts +1235 -0
  237. package/src/cli/commands/syncHighLevel.test.ts +144 -0
  238. package/src/cli/commands/syncHighLevel.ts +639 -0
  239. package/src/cli/index.ts +121 -0
  240. package/src/config/configManager.ts +75 -0
  241. package/src/config/paths.test.ts +83 -0
  242. package/src/config/paths.ts +36 -0
  243. package/src/config/schema.test.ts +173 -0
  244. package/src/config/schema.ts +65 -0
  245. package/src/downloader/hlsDownloader.test.ts +148 -0
  246. package/src/downloader/hlsDownloader.ts +327 -0
  247. package/src/downloader/hlsValidator.ts +196 -0
  248. package/src/downloader/index.ts +122 -0
  249. package/src/downloader/loomDownloader.test.ts +43 -0
  250. package/src/downloader/loomDownloader.ts +742 -0
  251. package/src/downloader/queue.test.ts +199 -0
  252. package/src/downloader/queue.ts +118 -0
  253. package/src/downloader/vimeoDownloader.test.ts +62 -0
  254. package/src/downloader/vimeoDownloader.ts +722 -0
  255. package/src/scraper/extractor.test.ts +124 -0
  256. package/src/scraper/extractor.ts +757 -0
  257. package/src/scraper/highlevel/__snapshots__/extractor.test.ts.snap +41 -0
  258. package/src/scraper/highlevel/extractor.test.ts +134 -0
  259. package/src/scraper/highlevel/extractor.ts +537 -0
  260. package/src/scraper/highlevel/index.ts +2 -0
  261. package/src/scraper/highlevel/navigator.test.ts +110 -0
  262. package/src/scraper/highlevel/navigator.ts +668 -0
  263. package/src/scraper/highlevel/schemas.ts +183 -0
  264. package/src/scraper/navigator.test.ts +122 -0
  265. package/src/scraper/navigator.ts +355 -0
  266. package/src/scraper/schemas.ts +177 -0
  267. package/src/scraper/videoInterceptor.ts +435 -0
  268. package/src/shared/auth.test.ts +58 -0
  269. package/src/shared/auth.ts +251 -0
  270. package/src/shared/firebase.ts +151 -0
  271. package/src/shared/fs.ts +80 -0
  272. package/src/shared/http.ts +34 -0
  273. package/src/shared/index.ts +6 -0
  274. package/src/shared/slug.ts +26 -0
  275. package/src/shared/url.test.ts +122 -0
  276. package/src/shared/url.ts +57 -0
  277. package/src/state/database.test.ts +49 -0
  278. package/src/state/database.ts +919 -0
  279. package/src/state/index.ts +14 -0
  280. package/src/storage/fileSystem.test.ts +64 -0
  281. package/src/storage/fileSystem.ts +175 -0
  282. package/tsconfig.json +28 -0
  283. package/vitest.config.ts +29 -0
  284. package/cli.js +0 -45
@@ -0,0 +1,315 @@
1
+ /**
2
+ * Captures Vimeo video URL by extracting it from the running player.
3
+ * The key insight: the video is ALREADY playing in the iframe - we just need to get the URL.
4
+ */
5
+ export async function captureVimeoConfig(page, _videoId, timeoutMs = 20000) {
6
+ try {
7
+ // Step 1: Make sure we have a Vimeo iframe or video wrapper
8
+ // Skool wraps videos in a VideoPlayerWrapper - click it to ensure video loads
9
+ const videoWrapper = await page.$('[class*="VideoPlayerWrapper"], [class*="video-wrapper"], [class*="VideoPlayer"]');
10
+ if (videoWrapper) {
11
+ await videoWrapper.click().catch(() => { });
12
+ await page.waitForTimeout(1000);
13
+ }
14
+ // Step 2: Wait for Vimeo iframe to appear
15
+ let vimeoFrame = null;
16
+ const startTime = Date.now();
17
+ while (!vimeoFrame && Date.now() - startTime < timeoutMs) {
18
+ // Try to find the iframe
19
+ const iframe = await page.$('iframe[src*="vimeo.com"], iframe[src*="player.vimeo"]');
20
+ if (iframe) {
21
+ vimeoFrame = await iframe.contentFrame();
22
+ if (vimeoFrame)
23
+ break;
24
+ }
25
+ await page.waitForTimeout(500);
26
+ }
27
+ if (!vimeoFrame) {
28
+ return { hlsUrl: null, progressiveUrl: null, error: "Vimeo iframe not found after waiting" };
29
+ }
30
+ // Step 3: Mute the video before playing (we don't want audio!)
31
+ await vimeoFrame.evaluate(() => {
32
+ const video = document.querySelector('video');
33
+ if (video) {
34
+ video.muted = true;
35
+ video.volume = 0;
36
+ }
37
+ }).catch(() => { });
38
+ // Step 4: Click play button in the iframe to start video
39
+ try {
40
+ // Multiple selectors for Vimeo's play button
41
+ await vimeoFrame.click('.vp-controls button, .play-icon, [aria-label="Play"], .vp-big-play-button, button', {
42
+ timeout: 2000
43
+ }).catch(() => { });
44
+ }
45
+ catch {
46
+ // Video might auto-play
47
+ }
48
+ // Ensure video stays muted
49
+ await vimeoFrame.evaluate(() => {
50
+ const video = document.querySelector('video');
51
+ if (video) {
52
+ video.muted = true;
53
+ video.volume = 0;
54
+ }
55
+ }).catch(() => { });
56
+ // Step 4: Wait for video to actually start playing and get the URL
57
+ let hlsUrl = null;
58
+ let progressiveUrl = null;
59
+ const extractionStart = Date.now();
60
+ while (!hlsUrl && !progressiveUrl && Date.now() - extractionStart < timeoutMs - 5000) {
61
+ const urls = await vimeoFrame.evaluate(() => {
62
+ const result = {
63
+ hlsUrl: null,
64
+ progressiveUrl: null,
65
+ debug: []
66
+ };
67
+ // Method 1: Get URL directly from video element
68
+ const video = document.querySelector('video');
69
+ if (video) {
70
+ result.debug.push(`Video element found, src length: ${video.src?.length ?? 0}`);
71
+ // Check currentSrc (what's actually playing)
72
+ if (video.currentSrc) {
73
+ result.debug.push(`currentSrc: ${video.currentSrc.substring(0, 80)}`);
74
+ if (video.currentSrc.includes('.m3u8')) {
75
+ result.hlsUrl = video.currentSrc;
76
+ }
77
+ else if (video.currentSrc.includes('.mp4')) {
78
+ result.progressiveUrl = video.currentSrc;
79
+ }
80
+ }
81
+ // Also check src attribute
82
+ if (!result.hlsUrl && !result.progressiveUrl && video.src) {
83
+ if (video.src.includes('.m3u8')) {
84
+ result.hlsUrl = video.src;
85
+ }
86
+ else if (video.src.includes('.mp4')) {
87
+ result.progressiveUrl = video.src;
88
+ }
89
+ }
90
+ }
91
+ // Method 2: Check source elements
92
+ if (!result.hlsUrl && !result.progressiveUrl) {
93
+ const sources = document.querySelectorAll('video source');
94
+ result.debug.push(`Found ${sources.length} source elements`);
95
+ for (const source of Array.from(sources)) {
96
+ const src = source.src;
97
+ if (src?.includes('.m3u8')) {
98
+ result.hlsUrl = src;
99
+ break;
100
+ }
101
+ else if (src?.includes('.mp4') && !result.progressiveUrl) {
102
+ result.progressiveUrl = src;
103
+ }
104
+ }
105
+ }
106
+ // Method 3: Extract from Vimeo's internal player state
107
+ if (!result.hlsUrl && !result.progressiveUrl) {
108
+ try {
109
+ const win = window;
110
+ // Try various Vimeo internal variables
111
+ const configPaths = [
112
+ win.playerConfig?.request?.files,
113
+ win.vimeo?.config?.request?.files,
114
+ win.__vimeo_player__?.config?.request?.files,
115
+ ];
116
+ for (const files of configPaths) {
117
+ if (!files)
118
+ continue;
119
+ // HLS
120
+ if (files.hls?.cdns) {
121
+ const cdns = files.hls.cdns;
122
+ for (const cdn of Object.keys(cdns)) {
123
+ if (cdns[cdn]?.url) {
124
+ result.hlsUrl = cdns[cdn].url;
125
+ result.debug.push(`Found HLS in playerConfig.${cdn}`);
126
+ break;
127
+ }
128
+ }
129
+ }
130
+ // Progressive MP4
131
+ if (!result.progressiveUrl && files.progressive?.length > 0) {
132
+ const sorted = [...files.progressive].sort((a, b) => (b.height ?? 0) - (a.height ?? 0));
133
+ result.progressiveUrl = sorted[0]?.url ?? null;
134
+ if (result.progressiveUrl) {
135
+ result.debug.push('Found progressive in playerConfig');
136
+ }
137
+ }
138
+ if (result.hlsUrl || result.progressiveUrl)
139
+ break;
140
+ }
141
+ }
142
+ catch (e) {
143
+ result.debug.push(`Config extraction error: ${e}`);
144
+ }
145
+ }
146
+ // Method 4: Network request URLs might be in DOM attributes
147
+ if (!result.hlsUrl && !result.progressiveUrl) {
148
+ const allElements = document.querySelectorAll('*');
149
+ for (const el of Array.from(allElements)) {
150
+ for (const attr of Array.from(el.attributes)) {
151
+ if (attr.value.includes('vimeocdn.com') && attr.value.includes('.m3u8')) {
152
+ result.hlsUrl = (/https:\/\/[^\s"']+\.m3u8[^\s"']*/.exec(attr.value))?.[0] ?? null;
153
+ if (result.hlsUrl) {
154
+ result.debug.push('Found HLS in element attribute');
155
+ break;
156
+ }
157
+ }
158
+ }
159
+ if (result.hlsUrl)
160
+ break;
161
+ }
162
+ }
163
+ return result;
164
+ });
165
+ hlsUrl = urls.hlsUrl;
166
+ progressiveUrl = urls.progressiveUrl;
167
+ if (!hlsUrl && !progressiveUrl) {
168
+ // Wait and try again
169
+ await new Promise(r => setTimeout(r, 1000));
170
+ }
171
+ }
172
+ if (hlsUrl || progressiveUrl) {
173
+ return { hlsUrl, progressiveUrl };
174
+ }
175
+ return {
176
+ hlsUrl: null,
177
+ progressiveUrl: null,
178
+ error: "Could not extract video URL from Vimeo player"
179
+ };
180
+ }
181
+ catch (error) {
182
+ return {
183
+ hlsUrl: null,
184
+ progressiveUrl: null,
185
+ error: `Vimeo extraction failed: ${error}`,
186
+ };
187
+ }
188
+ }
189
+ /**
190
+ * Captures Loom HLS URL by navigating directly to the embed page.
191
+ * This works better than CDP because we can intercept all requests on that page.
192
+ */
193
+ export async function captureLoomHls(page, videoId, timeoutMs = 15000) {
194
+ let capturedUrl = null;
195
+ const originalUrl = page.url();
196
+ try {
197
+ // Use CDP to intercept network responses
198
+ const client = await page.context().newCDPSession(page);
199
+ await client.send('Network.enable');
200
+ // Match HLS playlists from Loom's CDN
201
+ // Prefer master playlist (playlist.m3u8) over media playlists (mediaplaylist-*.m3u8)
202
+ const masterPattern = /luna\.loom\.com.*\/playlist\.m3u8/;
203
+ const anyHlsPattern = /luna\.loom\.com.*\.m3u8/;
204
+ // Set up listener before navigation
205
+ const responsePromise = new Promise((resolve) => {
206
+ const timeout = setTimeout(() => { resolve(); }, timeoutMs);
207
+ let hasMasterPlaylist = false;
208
+ client.on('Network.responseReceived', (event) => {
209
+ const url = event.response.url;
210
+ // Always prefer master playlist
211
+ if (masterPattern.test(url)) {
212
+ capturedUrl = url;
213
+ hasMasterPlaylist = true;
214
+ clearTimeout(timeout);
215
+ resolve();
216
+ }
217
+ else if (!hasMasterPlaylist && anyHlsPattern.test(url)) {
218
+ // Capture any HLS as fallback, but keep listening for master
219
+ capturedUrl = url;
220
+ }
221
+ });
222
+ });
223
+ // Navigate directly to Loom embed with autoplay (muted)
224
+ const embedUrl = `https://www.loom.com/embed/${videoId}?autoplay=1`;
225
+ await page.goto(embedUrl, { waitUntil: 'domcontentloaded', timeout: 10000 });
226
+ // Mute the video immediately
227
+ await page.evaluate(() => {
228
+ const video = document.querySelector('video');
229
+ if (video) {
230
+ video.muted = true;
231
+ video.volume = 0;
232
+ }
233
+ }).catch(() => { });
234
+ // Try to click play button if video doesn't autoplay
235
+ try {
236
+ await page.waitForTimeout(2000);
237
+ const playButton = await page.$('[data-testid="play-button"], .PlayButton, [aria-label="Play"], button[class*="play"]');
238
+ if (playButton) {
239
+ // Mute again before clicking play
240
+ await page.evaluate(() => {
241
+ const video = document.querySelector('video');
242
+ if (video) {
243
+ video.muted = true;
244
+ video.volume = 0;
245
+ }
246
+ }).catch(() => { });
247
+ await playButton.click();
248
+ }
249
+ }
250
+ catch {
251
+ // No play button or click failed
252
+ }
253
+ // Ensure video stays muted after play
254
+ await page.evaluate(() => {
255
+ const video = document.querySelector('video');
256
+ if (video) {
257
+ video.muted = true;
258
+ video.volume = 0;
259
+ }
260
+ }).catch(() => { });
261
+ // Wait for HLS to be captured
262
+ await responsePromise;
263
+ // Also try to extract from page JS if not found via network
264
+ if (!capturedUrl) {
265
+ const jsUrl = await page.evaluate(() => {
266
+ const win = window;
267
+ // Check __LOOM_SSR_STATE__
268
+ if (win.__LOOM_SSR_STATE__?.video?.asset_urls?.hls_url) {
269
+ return win.__LOOM_SSR_STATE__.video.asset_urls.hls_url;
270
+ }
271
+ // Check for Next.js data
272
+ const nextData = document.getElementById('__NEXT_DATA__');
273
+ if (nextData?.textContent) {
274
+ try {
275
+ const data = JSON.parse(nextData.textContent);
276
+ const hlsUrl = data?.props?.pageProps?.video?.asset_urls?.hls_url;
277
+ if (hlsUrl)
278
+ return hlsUrl;
279
+ // Try regex match in full data
280
+ const videoData = /hls_url['":\s]+['"]([^'"]+)['"]/.exec(JSON.stringify(data));
281
+ if (videoData?.[1])
282
+ return videoData[1];
283
+ }
284
+ catch { /* ignore parse errors */ }
285
+ }
286
+ // Scan scripts for HLS URL
287
+ const scripts = Array.from(document.querySelectorAll('script'));
288
+ for (const script of scripts) {
289
+ const match = /https:\/\/luna\.loom\.com[^"'\s]+\.m3u8[^"'\s]*/.exec(script.textContent);
290
+ if (match)
291
+ return match[0];
292
+ }
293
+ return null;
294
+ });
295
+ if (jsUrl) {
296
+ capturedUrl = jsUrl;
297
+ }
298
+ }
299
+ await client.detach();
300
+ }
301
+ catch {
302
+ // Error during capture
303
+ }
304
+ // Navigate back to original page
305
+ try {
306
+ await page.goto(originalUrl, { waitUntil: 'domcontentloaded', timeout: 10000 });
307
+ }
308
+ catch {
309
+ // Failed to navigate back
310
+ }
311
+ return capturedUrl
312
+ ? { hlsUrl: capturedUrl }
313
+ : { hlsUrl: null, error: "HLS URL not captured" };
314
+ }
315
+ //# sourceMappingURL=videoInterceptor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"videoInterceptor.js","sourceRoot":"","sources":["../../src/scraper/videoInterceptor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ import type { Browser, BrowserContext, Page } from "playwright";
2
+ export interface AuthSession {
3
+ context: BrowserContext;
4
+ page: Page;
5
+ }
6
+ export interface AuthConfig {
7
+ /** Domain to store session under */
8
+ domain: string;
9
+ /** URL to navigate to for login */
10
+ loginUrl: string;
11
+ /** Function to check if current URL is a login page */
12
+ isLoginPage: (url: string) => boolean;
13
+ /** Optional: Function to verify session is valid after navigation */
14
+ verifySession?: (page: Page) => Promise<boolean>;
15
+ /** Login timeout in ms (default: 5 minutes) */
16
+ loginTimeout?: number;
17
+ }
18
+ /**
19
+ * Creates a login page checker from patterns.
20
+ */
21
+ export declare function createLoginChecker(patterns?: RegExp[]): (url: string) => boolean;
22
+ /**
23
+ * Checks if a valid session exists for the given domain.
24
+ */
25
+ export declare function hasValidSession(domain: string): Promise<boolean>;
26
+ /**
27
+ * Performs interactive login by opening a browser window.
28
+ * The user logs in manually, and we capture the session.
29
+ */
30
+ export declare function performInteractiveLogin(config: AuthConfig): Promise<AuthSession>;
31
+ /**
32
+ * Gets an authenticated session, either from cache or via interactive login.
33
+ */
34
+ export declare function getAuthenticatedSession(config: AuthConfig, options?: {
35
+ forceLogin?: boolean;
36
+ headless?: boolean;
37
+ }): Promise<{
38
+ browser: Browser;
39
+ session: AuthSession;
40
+ }>;
41
+ /**
42
+ * Clears the session for a domain.
43
+ */
44
+ export declare function clearSession(domain: string): Promise<boolean>;
45
+ /**
46
+ * Checks if the page has a valid Firebase auth token.
47
+ * Used by HighLevel/GoHighLevel portals.
48
+ */
49
+ export declare function hasValidFirebaseToken(page: Page): Promise<boolean>;
50
+ /**
51
+ * Skool-specific login page checker.
52
+ */
53
+ export declare const isSkoolLoginPage: (url: string) => boolean;
54
+ /**
55
+ * HighLevel-specific login page checker.
56
+ */
57
+ export declare const isHighLevelLoginPage: (url: string) => boolean;
58
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/shared/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAKhE,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,cAAc,CAAC;IACxB,IAAI,EAAE,IAAI,CAAC;CACZ;AAED,MAAM,WAAW,UAAU;IACzB,oCAAoC;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,mCAAmC;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,uDAAuD;IACvD,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACtC,qEAAqE;IACrE,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACjD,+CAA+C;IAC/C,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAcD;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,GAAE,MAAM,EAA2B,GAC1C,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAE1B;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAGtE;AAyBD;;;GAGG;AACH,wBAAsB,uBAAuB,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAoDtF;AAED;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,UAAU,EAClB,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAO,GACzD,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,WAAW,CAAA;CAAE,CAAC,CAwErD;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAGnE;AAMD;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAkBxE;AAED;;GAEG;AACH,eAAO,MAAM,gBAAgB,QApNpB,MAAM,KAAK,OAoNoE,CAAC;AAEzF;;GAEG;AACH,eAAO,MAAM,oBAAoB,QAzNxB,MAAM,KAAK,OAgOlB,CAAC"}
@@ -0,0 +1,211 @@
1
+ import { chromium } from "playwright";
2
+ import { getSessionPath, SESSIONS_DIR } from "../config/paths.js";
3
+ import { ensureDir, outputJson, pathExists, readJson, removeFile } from "./fs.js";
4
+ /**
5
+ * Default login page detection patterns.
6
+ */
7
+ const DEFAULT_LOGIN_PATTERNS = [
8
+ /\/login/,
9
+ /\/signin/,
10
+ /\/auth/,
11
+ /accounts\.google\.com/,
12
+ /firebaseapp\.com/,
13
+ /sso\./,
14
+ ];
15
+ /**
16
+ * Creates a login page checker from patterns.
17
+ */
18
+ export function createLoginChecker(patterns = DEFAULT_LOGIN_PATTERNS) {
19
+ return (url) => patterns.some((p) => p.test(url));
20
+ }
21
+ /**
22
+ * Checks if a valid session exists for the given domain.
23
+ */
24
+ export async function hasValidSession(domain) {
25
+ const sessionPath = getSessionPath(domain);
26
+ return pathExists(sessionPath);
27
+ }
28
+ /**
29
+ * Loads an existing session from disk.
30
+ */
31
+ async function loadSession(browser, domain) {
32
+ const sessionPath = getSessionPath(domain);
33
+ const storageState = await readJson(sessionPath);
34
+ if (!storageState) {
35
+ throw new Error("Session file not found or invalid");
36
+ }
37
+ // Cast to any since Playwright's storageState type is complex
38
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
39
+ return browser.newContext({ storageState: storageState });
40
+ }
41
+ /**
42
+ * Saves the current session to disk.
43
+ */
44
+ async function saveSession(context, domain) {
45
+ const sessionPath = getSessionPath(domain);
46
+ const storageState = await context.storageState();
47
+ await outputJson(sessionPath, storageState);
48
+ }
49
+ /**
50
+ * Performs interactive login by opening a browser window.
51
+ * The user logs in manually, and we capture the session.
52
+ */
53
+ export async function performInteractiveLogin(config) {
54
+ await ensureDir(SESSIONS_DIR);
55
+ const browser = await chromium.launch({
56
+ headless: false, // Must be visible for user interaction
57
+ });
58
+ const context = await browser.newContext({
59
+ viewport: { width: 1280, height: 800 },
60
+ });
61
+ const page = await context.newPage();
62
+ await page.goto(config.loginUrl);
63
+ console.log("\n🔐 Browser opened. Please log in manually.");
64
+ console.log(" The window will close automatically after successful login.\n");
65
+ const timeout = config.loginTimeout ?? 300000; // 5 minutes default
66
+ const startTime = Date.now();
67
+ let loggedIn = false;
68
+ while (!loggedIn && Date.now() - startTime < timeout) {
69
+ await page.waitForTimeout(1000);
70
+ const currentUrl = page.url();
71
+ // Check if we're no longer on a login page
72
+ if (!config.isLoginPage(currentUrl)) {
73
+ // Optionally verify with custom function
74
+ if (config.verifySession) {
75
+ loggedIn = await config.verifySession(page);
76
+ }
77
+ else {
78
+ loggedIn = true;
79
+ }
80
+ }
81
+ }
82
+ if (!loggedIn) {
83
+ await browser.close();
84
+ throw new Error(`Login timed out after ${timeout / 1000} seconds`);
85
+ }
86
+ // Give the page a moment to fully load after login
87
+ await page.waitForLoadState("networkidle").catch(() => { });
88
+ await page.waitForTimeout(1000);
89
+ // Save the session
90
+ await saveSession(context, config.domain);
91
+ console.log("✅ Login successful! Session saved.\n");
92
+ return { context, page };
93
+ }
94
+ /**
95
+ * Gets an authenticated session, either from cache or via interactive login.
96
+ */
97
+ export async function getAuthenticatedSession(config, options = {}) {
98
+ const useHeadless = options.headless !== false;
99
+ const browser = await chromium.launch({
100
+ headless: useHeadless,
101
+ });
102
+ // Try to use existing session
103
+ if (!options.forceLogin && (await hasValidSession(config.domain))) {
104
+ try {
105
+ const context = await loadSession(browser, config.domain);
106
+ const page = await context.newPage();
107
+ // Navigate to verify session
108
+ await page.goto(config.loginUrl);
109
+ await page.waitForLoadState("domcontentloaded");
110
+ await page.waitForTimeout(2000);
111
+ const currentUrl = page.url();
112
+ // Check if we got redirected to login
113
+ if (config.isLoginPage(currentUrl)) {
114
+ console.log("⚠️ Session expired, need to re-login...");
115
+ await context.close();
116
+ await browser.close();
117
+ }
118
+ else {
119
+ // Optionally verify session
120
+ if (config.verifySession) {
121
+ const isValid = await config.verifySession(page);
122
+ if (!isValid) {
123
+ console.log("⚠️ Session invalid, need to re-login...");
124
+ await context.close();
125
+ await browser.close();
126
+ }
127
+ else {
128
+ console.log("✅ Using cached session");
129
+ return { browser, session: { context, page } };
130
+ }
131
+ }
132
+ else {
133
+ console.log("✅ Using cached session");
134
+ return { browser, session: { context, page } };
135
+ }
136
+ }
137
+ }
138
+ catch (error) {
139
+ console.log("⚠️ Failed to load session, need to re-login...", error);
140
+ await browser.close();
141
+ }
142
+ }
143
+ else {
144
+ await browser.close();
145
+ }
146
+ // Need fresh login - always visible for interactive login
147
+ const session = await performInteractiveLogin(config);
148
+ // Get the browser from the session context
149
+ const sessionBrowser = session.context.browser();
150
+ if (!sessionBrowser) {
151
+ throw new Error("Failed to get browser from session");
152
+ }
153
+ // After login, reopen with headless browser if needed
154
+ if (useHeadless) {
155
+ const newBrowser = await chromium.launch({ headless: true });
156
+ const context = await loadSession(newBrowser, config.domain);
157
+ const page = await context.newPage();
158
+ // Close the interactive session
159
+ await sessionBrowser.close();
160
+ return { browser: newBrowser, session: { context, page } };
161
+ }
162
+ return { browser: sessionBrowser, session };
163
+ }
164
+ /**
165
+ * Clears the session for a domain.
166
+ */
167
+ export async function clearSession(domain) {
168
+ const sessionPath = getSessionPath(domain);
169
+ return removeFile(sessionPath);
170
+ }
171
+ // ============================================
172
+ // Platform-specific helpers
173
+ // ============================================
174
+ /**
175
+ * Checks if the page has a valid Firebase auth token.
176
+ * Used by HighLevel/GoHighLevel portals.
177
+ */
178
+ export async function hasValidFirebaseToken(page) {
179
+ try {
180
+ return await page.evaluate(() => {
181
+ const tokenKey = Object.keys(localStorage).find((k) => k.includes("firebase:authUser"));
182
+ if (!tokenKey)
183
+ return false;
184
+ const tokenData = JSON.parse(localStorage.getItem(tokenKey) ?? "{}");
185
+ const expirationTime = tokenData?.stsTokenManager?.expirationTime;
186
+ if (expirationTime) {
187
+ return Date.now() < expirationTime;
188
+ }
189
+ return !!tokenData?.stsTokenManager?.accessToken;
190
+ });
191
+ }
192
+ catch {
193
+ return false;
194
+ }
195
+ }
196
+ /**
197
+ * Skool-specific login page checker.
198
+ */
199
+ export const isSkoolLoginPage = createLoginChecker([/\/login/, /accounts\.google\.com/]);
200
+ /**
201
+ * HighLevel-specific login page checker.
202
+ */
203
+ export const isHighLevelLoginPage = createLoginChecker([
204
+ /sso\.clientclub\.net/,
205
+ /\/login/,
206
+ /\/signin/,
207
+ /\/auth/,
208
+ /accounts\.google\.com/,
209
+ /firebaseapp\.com/,
210
+ ]);
211
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/shared/auth.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAoBlF;;GAEG;AACH,MAAM,sBAAsB,GAAG;IAC7B,SAAS;IACT,UAAU;IACV,QAAQ;IACR,uBAAuB;IACvB,kBAAkB;IAClB,OAAO;CACR,CAAC;AAEF;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAChC,WAAqB,sBAAsB;IAE3C,OAAO,CAAC,GAAW,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC5D,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,MAAc;IAClD,MAAM,WAAW,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IAC3C,OAAO,UAAU,CAAC,WAAW,CAAC,CAAC;AACjC,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,WAAW,CAAC,OAAgB,EAAE,MAAc;IACzD,MAAM,WAAW,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IAC3C,MAAM,YAAY,GAAG,MAAM,QAAQ,CAAC,WAAW,CAAC,CAAC;IACjD,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;IACvD,CAAC;IACD,8DAA8D;IAC9D,8DAA8D;IAC9D,OAAO,OAAO,CAAC,UAAU,CAAC,EAAE,YAAY,EAAE,YAAmB,EAAE,CAAC,CAAC;AACnE,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,WAAW,CAAC,OAAuB,EAAE,MAAc;IAChE,MAAM,WAAW,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IAC3C,MAAM,YAAY,GAAG,MAAM,OAAO,CAAC,YAAY,EAAE,CAAC;IAClD,MAAM,UAAU,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;AAC9C,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,MAAkB;IAC9D,MAAM,SAAS,CAAC,YAAY,CAAC,CAAC;IAE9B,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC;QACpC,QAAQ,EAAE,KAAK,EAAE,uCAAuC;KACzD,CAAC,CAAC;IAEH,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC;QACvC,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE;KACvC,CAAC,CAAC;IAEH,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;IACrC,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAEjC,OAAO,CAAC,GAAG,CAAC,8CAA8C,CAAC,CAAC;IAC5D,OAAO,CAAC,GAAG,CAAC,kEAAkE,CAAC,CAAC;IAEhF,MAAM,OAAO,GAAG,MAAM,CAAC,YAAY,IAAI,MAAM,CAAC,CAAC,oBAAoB;IACnE,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,IAAI,QAAQ,GAAG,KAAK,CAAC;IAErB,OAAO,CAAC,QAAQ,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,GAAG,OAAO,EAAE,CAAC;QACrD,MAAM,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;QAEhC,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAE9B,2CAA2C;QAC3C,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,UAAU,CAAC,EAAE,CAAC;YACpC,yCAAyC;YACzC,IAAI,MAAM,CAAC,aAAa,EAAE,CAAC;gBACzB,QAAQ,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;YAC9C,CAAC;iBAAM,CAAC;gBACN,QAAQ,GAAG,IAAI,CAAC;YAClB,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,yBAAyB,OAAO,GAAG,IAAI,UAAU,CAAC,CAAC;IACrE,CAAC;IAED,mDAAmD;IACnD,MAAM,IAAI,CAAC,gBAAgB,CAAC,aAAa,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAC3D,MAAM,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;IAEhC,mBAAmB;IACnB,MAAM,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IAE1C,OAAO,CAAC,GAAG,CAAC,sCAAsC,CAAC,CAAC;IAEpD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAC3B,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,MAAkB,EAClB,UAAwD,EAAE;IAE1D,MAAM,WAAW,GAAG,OAAO,CAAC,QAAQ,KAAK,KAAK,CAAC;IAE/C,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC;QACpC,QAAQ,EAAE,WAAW;KACtB,CAAC,CAAC;IAEH,8BAA8B;IAC9B,IAAI,CAAC,OAAO,CAAC,UAAU,IAAI,CAAC,MAAM,eAAe,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;QAClE,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;YAC1D,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;YAErC,6BAA6B;YAC7B,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YACjC,MAAM,IAAI,CAAC,gBAAgB,CAAC,kBAAkB,CAAC,CAAC;YAChD,MAAM,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;YAEhC,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAE9B,sCAAsC;YACtC,IAAI,MAAM,CAAC,WAAW,CAAC,UAAU,CAAC,EAAE,CAAC;gBACnC,OAAO,CAAC,GAAG,CAAC,0CAA0C,CAAC,CAAC;gBACxD,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;gBACtB,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;YACxB,CAAC;iBAAM,CAAC;gBACN,4BAA4B;gBAC5B,IAAI,MAAM,CAAC,aAAa,EAAE,CAAC;oBACzB,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;oBACjD,IAAI,CAAC,OAAO,EAAE,CAAC;wBACb,OAAO,CAAC,GAAG,CAAC,0CAA0C,CAAC,CAAC;wBACxD,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;wBACtB,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;oBACxB,CAAC;yBAAM,CAAC;wBACN,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;wBACtC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC;oBACjD,CAAC;gBACH,CAAC;qBAAM,CAAC;oBACN,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;oBACtC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC;gBACjD,CAAC;YACH,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,GAAG,CAAC,iDAAiD,EAAE,KAAK,CAAC,CAAC;YACtE,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;QACxB,CAAC;IACH,CAAC;SAAM,CAAC;QACN,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;IAED,0DAA0D;IAC1D,MAAM,OAAO,GAAG,MAAM,uBAAuB,CAAC,MAAM,CAAC,CAAC;IAEtD,2CAA2C;IAC3C,MAAM,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;IACjD,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;IACxD,CAAC;IAED,sDAAsD;IACtD,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,UAAU,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7D,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;QAC7D,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;QAErC,gCAAgC;QAChC,MAAM,cAAc,CAAC,KAAK,EAAE,CAAC;QAE7B,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC;IAC7D,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,CAAC;AAC9C,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,MAAc;IAC/C,MAAM,WAAW,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IAC3C,OAAO,UAAU,CAAC,WAAW,CAAC,CAAC;AACjC,CAAC;AAED,+CAA+C;AAC/C,4BAA4B;AAC5B,+CAA+C;AAE/C;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,IAAU;IACpD,IAAI,CAAC;QACH,OAAO,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE;YAC9B,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAC,CAAC;YACxF,IAAI,CAAC,QAAQ;gBAAE,OAAO,KAAK,CAAC;YAE5B,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,CAAC;YACrE,MAAM,cAAc,GAAG,SAAS,EAAE,eAAe,EAAE,cAAc,CAAC;YAElE,IAAI,cAAc,EAAE,CAAC;gBACnB,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,cAAc,CAAC;YACrC,CAAC;YAED,OAAO,CAAC,CAAC,SAAS,EAAE,eAAe,EAAE,WAAW,CAAC;QACnD,CAAC,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,kBAAkB,CAAC,CAAC,SAAS,EAAE,uBAAuB,CAAC,CAAC,CAAC;AAEzF;;GAEG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,kBAAkB,CAAC;IACrD,sBAAsB;IACtB,SAAS;IACT,UAAU;IACV,QAAQ;IACR,uBAAuB;IACvB,kBAAkB;CACnB,CAAC,CAAC"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Check if a file or directory exists.
3
+ */
4
+ export declare function pathExists(path: string): Promise<boolean>;
5
+ /**
6
+ * Ensure a directory exists, creating it recursively if needed.
7
+ */
8
+ export declare function ensureDir(dir: string): Promise<void>;
9
+ /**
10
+ * Write a file, creating parent directories if needed.
11
+ */
12
+ export declare function outputFile(path: string, data: string): Promise<void>;
13
+ /**
14
+ * Write JSON to a file, creating parent directories if needed.
15
+ */
16
+ export declare function outputJson(path: string, data: unknown): Promise<void>;
17
+ /**
18
+ * Read and parse a JSON file.
19
+ * Returns null if file doesn't exist or can't be parsed.
20
+ */
21
+ export declare function readJson<T = unknown>(path: string): Promise<T | null>;
22
+ /**
23
+ * Remove a file if it exists.
24
+ */
25
+ export declare function removeFile(path: string): Promise<boolean>;
26
+ /**
27
+ * Get file size in bytes, or null if file doesn't exist.
28
+ */
29
+ export declare function getFileSize(path: string): Promise<number | null>;
30
+ export { readFile, writeFile, mkdir, unlink, stat } from "node:fs/promises";
31
+ //# sourceMappingURL=fs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../../src/shared/fs.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAO/D;AAED;;GAEG;AACH,wBAAsB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE1D;AAED;;GAEG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAG1E;AAED;;GAEG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAE3E;AAED;;;GAGG;AACH,wBAAsB,QAAQ,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAO3E;AAED;;GAEG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAO/D;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAOtE;AAGD,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC"}