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
package/README.md CHANGED
@@ -1,28 +1,142 @@
1
1
  # Offcourse
2
2
 
3
- Download online courses for offline access – of course! 📚
4
-
5
- > 🚧 **Coming Soon** – This package is currently in private development.
3
+ [![npm version](https://img.shields.io/npm/v/offcourse?color=3b82f6&label=npm)](https://www.npmjs.com/package/offcourse)
4
+ [![npm downloads](https://img.shields.io/npm/dm/offcourse?color=3b82f6)](https://www.npmjs.com/package/offcourse)
5
+ [![license](https://img.shields.io/npm/l/offcourse?color=3b82f6)](https://github.com/sebastian-software/offcourse/blob/main/LICENSE)
6
+ [![node](https://img.shields.io/badge/node-%3E%3D22-3b82f6)](https://nodejs.org)
7
+ [![codecov](https://codecov.io/gh/sebastian-software/offcourse/graph/badge.svg)](https://codecov.io/gh/sebastian-software/offcourse)
8
+ [![CI](https://github.com/sebastian-software/offcourse/actions/workflows/ci.yml/badge.svg)](https://github.com/sebastian-software/offcourse/actions/workflows/ci.yml)
6
9
 
7
- ## What is Offcourse?
10
+ Download online courses for offline access – of course! 📚
8
11
 
9
- Offcourse is a CLI tool that downloads online courses for offline access. It preserves the course structure, downloads videos, and converts lesson content to clean Markdown files.
12
+ Saves video content and lesson text as Markdown files, organized by module structure.
10
13
 
11
- ## Planned Features
14
+ ## Features
12
15
 
13
16
  - 🔐 **Browser-based authentication** – Log in once, sessions are cached
14
17
  - 📚 **Course structure preservation** – Maintains module/lesson hierarchy
15
- - 🎬 **Video downloads** – Supports Loom, native video (Vimeo, YouTube, Wistia planned)
18
+ - 🎬 **Video downloads** – Supports HLS streams, Loom and Vimeo
16
19
  - 📝 **Content extraction** – Converts lesson text to clean Markdown
17
20
  - ⏸️ **Resumable syncs** – Skips already downloaded content
18
21
  - ⚡ **Concurrent downloads** – Configurable parallelism
22
+ - 🔍 **Auto-detection** – Automatically detects platform from URL
19
23
 
20
24
  ## Supported Platforms
21
25
 
22
- | Platform | Status |
23
- |----------|--------|
24
- | [Skool.com](https://skool.com) | ✅ Ready |
25
- | [LearningSuite.io](https://learningsuite.io) | 🚧 Planned |
26
+ | Platform | Status | Notes |
27
+ |----------|--------|-------|
28
+ | [Skool.com](https://skool.com) | ✅ Supported | Community courses |
29
+ | [HighLevel (GoHighLevel)](https://gohighlevel.com) | Supported | Membership portals, ClientClub |
30
+ | [LearningSuite.io](https://learningsuite.io) | 🚧 Planned | |
31
+
32
+ ## Installation
33
+
34
+ ```bash
35
+ npm install -g offcourse
36
+ ```
37
+
38
+ Or run directly with npx:
39
+
40
+ ```bash
41
+ npx offcourse <command>
42
+ ```
43
+
44
+ Requires Node.js 22+.
45
+
46
+ For HLS video downloads (HighLevel native videos), [ffmpeg](https://ffmpeg.org/) must be installed:
47
+
48
+ ```bash
49
+ # macOS
50
+ brew install ffmpeg
51
+
52
+ # Ubuntu/Debian
53
+ sudo apt install ffmpeg
54
+
55
+ # Windows (via Chocolatey)
56
+ choco install ffmpeg
57
+ ```
58
+
59
+ ## Usage
60
+
61
+ ### Login
62
+
63
+ ```bash
64
+ # Opens browser for interactive login
65
+ offcourse login
66
+
67
+ # Force re-login
68
+ offcourse login --force
69
+ ```
70
+
71
+ ### Sync a Course
72
+
73
+ The `sync` command auto-detects the platform from the URL:
74
+
75
+ ```bash
76
+ # Auto-detect platform and download
77
+ offcourse sync <url>
78
+
79
+ # Skip video downloads
80
+ offcourse sync <url> --skip-videos
81
+
82
+ # Skip text content
83
+ offcourse sync <url> --skip-content
84
+
85
+ # Preview without downloading
86
+ offcourse sync <url> --dry-run
87
+
88
+ # Limit to first N lessons (for testing)
89
+ offcourse sync <url> --limit 5
90
+
91
+ # Override course name (useful when auto-detection fails)
92
+ offcourse sync <url> --course-name "My Course Name"
93
+
94
+ # Prefer specific video quality
95
+ offcourse sync <url> --quality 720p
96
+ ```
97
+
98
+ ### Platform-Specific Commands
99
+
100
+ ```bash
101
+ # Skool courses
102
+ offcourse sync-skool https://www.skool.com/your-community/classroom
103
+
104
+ # HighLevel/GoHighLevel membership portals
105
+ offcourse sync-highlevel https://member.example.com/courses/products/<id>
106
+ offcourse sync-highlevel <url> --course-name "Course Name"
107
+ ```
108
+
109
+ ### Configuration
110
+
111
+ ```bash
112
+ # Show current config
113
+ offcourse config show
114
+
115
+ # Set output directory
116
+ offcourse config set outputDir ~/Courses
117
+
118
+ # Set video quality (highest, lowest, 1080p, 720p, 480p)
119
+ offcourse config set videoQuality 720p
120
+
121
+ # Set download concurrency (1-5)
122
+ offcourse config set concurrency 3
123
+
124
+ # Run headless (no browser window)
125
+ offcourse config set headless true
126
+ ```
127
+
128
+ ### Inspect (Debugging)
129
+
130
+ ```bash
131
+ # Analyze page structure
132
+ offcourse inspect <url>
133
+
134
+ # Save analysis to files
135
+ offcourse inspect <url> --output ./analysis
136
+
137
+ # Include full HTML dump
138
+ offcourse inspect <url> --full
139
+ ```
26
140
 
27
141
  ## Output Structure
28
142
 
@@ -40,13 +154,139 @@ Offcourse is a CLI tool that downloads online courses for offline access. It pre
40
154
  └── ...
41
155
  ```
42
156
 
43
- ## Stay Updated
157
+ ## Platform Notes
44
158
 
45
- Star the repo to get notified when we release:
159
+ ### HighLevel (GoHighLevel)
46
160
 
47
- [github.com/sebastian-software/offcourse](https://github.com/sebastian-software/offcourse)
161
+ HighLevel is an all-in-one marketing platform with a "Memberships" feature for hosting courses. Offcourse supports:
48
162
 
49
- ## License
163
+ - **Authentication**: Firebase-based login via browser
164
+ - **Course structure**: Extracts products, categories, and posts via API
165
+ - **Video downloads**: Native HLS videos with quality selection (requires ffmpeg)
166
+ - **Embedded videos**: Vimeo, Loom, and other embedded players
167
+
168
+ Common HighLevel portal URLs:
169
+ - `https://member.yourdomain.com/courses/...`
170
+ - `https://portal.yourdomain.com/courses/...`
171
+ - `https://courses.yourdomain.com/...`
172
+
173
+ ## Development
174
+
175
+ ### Setup
176
+
177
+ ```bash
178
+ # Clone the repository
179
+ git clone https://github.com/sebastian-software/offcourse.git
180
+ cd offcourse
181
+
182
+ # Install dependencies
183
+ npm install
184
+
185
+ # Build
186
+ npm run build
187
+
188
+ # Link globally (optional)
189
+ npm link
190
+ ```
191
+
192
+ ### Commands
193
+
194
+ ```bash
195
+ # Watch mode
196
+ npm run dev
197
+
198
+ # Run directly (without build)
199
+ npx tsx src/cli/index.ts <command>
200
+
201
+ # Lint
202
+ npm run lint
203
+
204
+ # Format
205
+ npm run format
206
+
207
+ # Type check
208
+ npm run typecheck
209
+
210
+ # Test
211
+ npm test
212
+ ```
213
+
214
+ ### Git Hooks
215
+
216
+ This project uses [Husky](https://typicode.github.io/husky/) for Git hooks:
217
+
218
+ - **pre-commit**: Runs Prettier on staged files via lint-staged
219
+ - **pre-push**: Runs ESLint and TypeScript type checking
220
+ - **commit-msg**: Validates commit messages follow [Conventional Commits](https://www.conventionalcommits.org/)
221
+
222
+ ### Commit Convention
223
+
224
+ We follow [Conventional Commits](https://www.conventionalcommits.org/). Commit messages must follow this format:
50
225
 
51
- MIT © [Sebastian Software GmbH](https://sebastian-software.de)
226
+ ```
227
+ <type>[optional scope]: <description>
228
+
229
+ [optional body]
230
+
231
+ [optional footer(s)]
232
+ ```
233
+
234
+ **Types:**
235
+
236
+ - `feat`: New feature
237
+ - `fix`: Bug fix
238
+ - `docs`: Documentation changes
239
+ - `style`: Code style changes (formatting, semicolons, etc.)
240
+ - `refactor`: Code refactoring
241
+ - `perf`: Performance improvements
242
+ - `test`: Adding or updating tests
243
+ - `chore`: Maintenance tasks
244
+ - `ci`: CI/CD changes
245
+
246
+ **Examples:**
247
+
248
+ ```bash
249
+ git commit -m "feat: add support for Vimeo downloads"
250
+ git commit -m "fix: handle missing video URLs gracefully"
251
+ git commit -m "docs: update installation instructions"
252
+ ```
253
+
254
+ ### Releasing
255
+
256
+ Releases are managed with [release-it](https://github.com/release-it/release-it). The release process:
257
+
258
+ 1. Runs linting, type checking, and tests
259
+ 2. Bumps version based on conventional commits
260
+ 3. Generates/updates `CHANGELOG.md`
261
+ 4. Creates a Git tag and GitHub release
262
+ 5. Publishes to npm
263
+
264
+ ```bash
265
+ # Interactive release (will prompt for version bump)
266
+ npm run release
267
+
268
+ # Dry run (preview what would happen)
269
+ npm run release -- --dry-run
270
+
271
+ # Specific version bump
272
+ npm run release -- --minor
273
+ npm run release -- --major
274
+ ```
275
+
276
+ ## Acknowledgments
277
+
278
+ A huge thank you to [Sindre Sorhus](https://github.com/sindresorhus) 🙏 for creating and maintaining so many excellent packages that power this project:
279
+
280
+ - [`@sindresorhus/slugify`](https://github.com/sindresorhus/slugify) – Slugify a string
281
+ - [`conf`](https://github.com/sindresorhus/conf) – Simple config handling
282
+ - [`delay`](https://github.com/sindresorhus/delay) – Delay a promise
283
+ - [`execa`](https://github.com/sindresorhus/execa) – Process execution for humans
284
+ - [`ky`](https://github.com/sindresorhus/ky) – Tiny & elegant HTTP client
285
+ - [`p-queue`](https://github.com/sindresorhus/p-queue) – Promise queue with concurrency control
286
+ - [`p-retry`](https://github.com/sindresorhus/p-retry) – Retry a promise-returning function
287
+
288
+ His commitment to high-quality, well-documented, and beautifully designed open source software is truly inspiring. If you find his work useful, consider [sponsoring him](https://github.com/sponsors/sindresorhus).
289
+
290
+ ## License
52
291
 
292
+ MIT
@@ -0,0 +1,4 @@
1
+ export default {
2
+ extends: ["@commitlint/config-conventional"],
3
+ };
4
+
@@ -0,0 +1,47 @@
1
+ export interface OpenRouterOptions {
2
+ model?: string;
3
+ maxTokens?: number;
4
+ temperature?: number;
5
+ }
6
+ export interface ChatMessage {
7
+ role: "system" | "user" | "assistant";
8
+ content: string;
9
+ }
10
+ export interface UsageInfo {
11
+ promptTokens: number;
12
+ completionTokens: number;
13
+ totalTokens: number;
14
+ cost: number | undefined;
15
+ }
16
+ export interface ChatResult {
17
+ content: string;
18
+ usage: UsageInfo;
19
+ model: string;
20
+ }
21
+ /**
22
+ * Get cumulative usage stats for the current session.
23
+ */
24
+ export declare function getCumulativeUsage(): UsageInfo;
25
+ /**
26
+ * Reset cumulative usage stats.
27
+ */
28
+ export declare function resetCumulativeUsage(): void;
29
+ /**
30
+ * Check if OpenRouter is configured.
31
+ */
32
+ export declare function isConfigured(): boolean;
33
+ /**
34
+ * Send a chat completion request to OpenRouter.
35
+ * Returns full result with usage info.
36
+ */
37
+ export declare function chatWithUsage(messages: ChatMessage[], options?: OpenRouterOptions): Promise<ChatResult>;
38
+ /**
39
+ * Send a chat completion request to OpenRouter.
40
+ * Simple version that just returns the content string.
41
+ */
42
+ export declare function chat(messages: ChatMessage[], options?: OpenRouterOptions): Promise<string>;
43
+ /**
44
+ * Simple prompt helper.
45
+ */
46
+ export declare function prompt(userPrompt: string, systemPrompt?: string, options?: OpenRouterOptions): Promise<string>;
47
+ //# sourceMappingURL=openRouter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openRouter.d.ts","sourceRoot":"","sources":["../../src/ai/openRouter.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,SAAS;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1B;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,SAAS,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAUD;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,SAAS,CAE9C;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAO3C;AAsBD;;GAEG;AACH,wBAAgB,YAAY,IAAI,OAAO,CAEtC;AAcD;;;GAGG;AACH,wBAAsB,aAAa,CACjC,QAAQ,EAAE,WAAW,EAAE,EACvB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,UAAU,CAAC,CA+CrB;AAED;;;GAGG;AACH,wBAAsB,IAAI,CACxB,QAAQ,EAAE,WAAW,EAAE,EACvB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,MAAM,CAAC,CAGjB;AAED;;GAEG;AACH,wBAAsB,MAAM,CAC1B,UAAU,EAAE,MAAM,EAClB,YAAY,CAAC,EAAE,MAAM,EACrB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,MAAM,CAAC,CAUjB"}
@@ -0,0 +1,116 @@
1
+ import { config } from "dotenv";
2
+ // Load .env file
3
+ config();
4
+ const OPENROUTER_API_URL = "https://openrouter.ai/api/v1/chat/completions";
5
+ // Track cumulative usage across calls
6
+ let cumulativeUsage = {
7
+ promptTokens: 0,
8
+ completionTokens: 0,
9
+ totalTokens: 0,
10
+ cost: undefined,
11
+ };
12
+ /**
13
+ * Get cumulative usage stats for the current session.
14
+ */
15
+ export function getCumulativeUsage() {
16
+ return { ...cumulativeUsage };
17
+ }
18
+ /**
19
+ * Reset cumulative usage stats.
20
+ */
21
+ export function resetCumulativeUsage() {
22
+ cumulativeUsage = {
23
+ promptTokens: 0,
24
+ completionTokens: 0,
25
+ totalTokens: 0,
26
+ cost: undefined,
27
+ };
28
+ }
29
+ /**
30
+ * Get the OpenRouter API key from environment.
31
+ */
32
+ function getApiKey() {
33
+ const key = process.env.OPENROUTER_API_KEY;
34
+ if (!key) {
35
+ throw new Error("OPENROUTER_API_KEY not set. Add it to .env file or set as environment variable.");
36
+ }
37
+ return key;
38
+ }
39
+ /**
40
+ * Get the default model from environment or use fallback.
41
+ */
42
+ function getDefaultModel() {
43
+ return process.env.OPENROUTER_MODEL ?? "anthropic/claude-3.5-sonnet";
44
+ }
45
+ /**
46
+ * Check if OpenRouter is configured.
47
+ */
48
+ export function isConfigured() {
49
+ return !!process.env.OPENROUTER_API_KEY;
50
+ }
51
+ /**
52
+ * Send a chat completion request to OpenRouter.
53
+ * Returns full result with usage info.
54
+ */
55
+ export async function chatWithUsage(messages, options = {}) {
56
+ const apiKey = getApiKey();
57
+ const model = options.model ?? getDefaultModel();
58
+ const response = await fetch(OPENROUTER_API_URL, {
59
+ method: "POST",
60
+ headers: {
61
+ Authorization: `Bearer ${apiKey}`,
62
+ "Content-Type": "application/json",
63
+ "HTTP-Referer": "https://github.com/course-grab",
64
+ "X-Title": "course-grab",
65
+ },
66
+ body: JSON.stringify({
67
+ model,
68
+ messages,
69
+ max_tokens: options.maxTokens ?? 4096,
70
+ temperature: options.temperature ?? 0.3,
71
+ }),
72
+ });
73
+ if (!response.ok) {
74
+ const error = await response.text();
75
+ throw new Error(`OpenRouter API error: ${response.status} - ${error}`);
76
+ }
77
+ const data = (await response.json());
78
+ const usage = {
79
+ promptTokens: data.usage?.prompt_tokens ?? 0,
80
+ completionTokens: data.usage?.completion_tokens ?? 0,
81
+ totalTokens: data.usage?.total_tokens ?? 0,
82
+ cost: data.total_cost,
83
+ };
84
+ // Accumulate usage
85
+ cumulativeUsage.promptTokens += usage.promptTokens;
86
+ cumulativeUsage.completionTokens += usage.completionTokens;
87
+ cumulativeUsage.totalTokens += usage.totalTokens;
88
+ if (usage.cost !== undefined) {
89
+ cumulativeUsage.cost = (cumulativeUsage.cost ?? 0) + usage.cost;
90
+ }
91
+ return {
92
+ content: data.choices[0]?.message?.content ?? "",
93
+ usage,
94
+ model: data.model ?? model,
95
+ };
96
+ }
97
+ /**
98
+ * Send a chat completion request to OpenRouter.
99
+ * Simple version that just returns the content string.
100
+ */
101
+ export async function chat(messages, options = {}) {
102
+ const result = await chatWithUsage(messages, options);
103
+ return result.content;
104
+ }
105
+ /**
106
+ * Simple prompt helper.
107
+ */
108
+ export async function prompt(userPrompt, systemPrompt, options) {
109
+ const messages = [];
110
+ if (systemPrompt) {
111
+ messages.push({ role: "system", content: systemPrompt });
112
+ }
113
+ messages.push({ role: "user", content: userPrompt });
114
+ return chat(messages, options);
115
+ }
116
+ //# sourceMappingURL=openRouter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openRouter.js","sourceRoot":"","sources":["../../src/ai/openRouter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ export interface PolishedTranscript {
2
+ summary: string;
3
+ transcript: string;
4
+ }
5
+ /**
6
+ * Convert folder name to readable title.
7
+ * "01-1-herzlich-willkommen" → "Herzlich Willkommen"
8
+ * "02-onboarding-social-leads-academy" → "Onboarding Social Leads Academy"
9
+ */
10
+ export declare function folderNameToTitle(folderName: string): string;
11
+ /**
12
+ * Polish a transcript using an LLM.
13
+ * Returns separate summary and transcript.
14
+ */
15
+ export declare function polishTranscript(rawTranscript: string): Promise<PolishedTranscript>;
16
+ /**
17
+ * Generate a module summary from multiple lesson summaries.
18
+ */
19
+ export declare function generateModuleSummary(moduleName: string, lessonSummaries: Array<{
20
+ name: string;
21
+ title: string;
22
+ summary: string;
23
+ }>): Promise<string>;
24
+ //# sourceMappingURL=transcriptPolisher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transcriptPolisher.d.ts","sourceRoot":"","sources":["../../src/ai/transcriptPolisher.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAW5D;AAkCD;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA0BzF;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CACzC,UAAU,EAAE,MAAM,EAClB,eAAe,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,GACvE,OAAO,CAAC,MAAM,CAAC,CAkBjB"}
@@ -0,0 +1,89 @@
1
+ import { prompt, isConfigured } from "./openRouter.js";
2
+ /**
3
+ * Convert folder name to readable title.
4
+ * "01-1-herzlich-willkommen" → "Herzlich Willkommen"
5
+ * "02-onboarding-social-leads-academy" → "Onboarding Social Leads Academy"
6
+ */
7
+ export function folderNameToTitle(folderName) {
8
+ return folderName
9
+ // Remove leading numbers and separators (e.g., "01-1-", "02-")
10
+ .replace(/^[\d]+-[\d]*-?/, "")
11
+ // Replace remaining dashes with spaces
12
+ .replace(/-/g, " ")
13
+ // Capitalize each word
14
+ .split(" ")
15
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
16
+ .join(" ")
17
+ .trim();
18
+ }
19
+ const SYSTEM_PROMPT = `Du bist ein Experte für die Aufbereitung von Video-Transkripten.
20
+
21
+ Deine Aufgabe:
22
+ 1. Erstelle eine kurze TLDR-Zusammenfassung (2-3 Sätze)
23
+ 2. Strukturiere den Text in sinnvolle Absätze
24
+ 3. Füge Markdown-Formatierung hinzu:
25
+ - **Fettschrift** für wichtige Begriffe und Kernaussagen
26
+ - *Kursiv* für Betonungen
27
+ - Überschriften (## oder ###) für klare Themenwechsel
28
+ - Aufzählungen wo es Sinn macht
29
+
30
+ Regeln:
31
+ - Behalte den Originaltext bei, ändere keine Wörter
32
+ - Korrigiere nur offensichtliche Transkriptionsfehler
33
+ - Füge Absätze an natürlichen Sprechpausen ein
34
+ - Halte alles auf Deutsch`;
35
+ const USER_PROMPT_TEMPLATE = `Hier ist ein Video-Transkript das aufbereitet werden soll:
36
+
37
+ ---
38
+ {transcript}
39
+ ---
40
+
41
+ Antworte in folgendem Format:
42
+
43
+ ## TLDR
44
+ [2-3 Sätze Zusammenfassung]
45
+
46
+ ## Transkript
47
+
48
+ [Aufbereiteter Text mit Markdown-Formatierung]`;
49
+ /**
50
+ * Polish a transcript using an LLM.
51
+ * Returns separate summary and transcript.
52
+ */
53
+ export async function polishTranscript(rawTranscript) {
54
+ if (!isConfigured()) {
55
+ return {
56
+ summary: "",
57
+ transcript: rawTranscript,
58
+ };
59
+ }
60
+ const userPrompt = USER_PROMPT_TEMPLATE.replace("{transcript}", rawTranscript);
61
+ const result = await prompt(userPrompt, SYSTEM_PROMPT, {
62
+ maxTokens: 8192,
63
+ temperature: 0.2,
64
+ });
65
+ // Parse the response
66
+ const tldrMatch = result.match(/## TLDR\s*\n+([\s\S]*?)(?=\n## Transkript|$)/i);
67
+ const transcriptMatch = result.match(/## Transkript\s*\n+([\s\S]*?)$/i);
68
+ const summary = tldrMatch?.[1]?.trim() ?? "";
69
+ const transcript = transcriptMatch?.[1]?.trim() ?? result;
70
+ return {
71
+ summary,
72
+ transcript,
73
+ };
74
+ }
75
+ /**
76
+ * Generate a module summary from multiple lesson summaries.
77
+ */
78
+ export async function generateModuleSummary(moduleName, lessonSummaries) {
79
+ if (!isConfigured() || lessonSummaries.length === 0) {
80
+ return "";
81
+ }
82
+ const moduleTitle = folderNameToTitle(moduleName);
83
+ const summariesText = lessonSummaries
84
+ .map((l, i) => `### ${i + 1}. ${l.title}\n${l.summary}`)
85
+ .join("\n\n");
86
+ const result = await prompt(`Hier sind die Zusammenfassungen aller Lektionen des Moduls "${moduleTitle}":\n\n${summariesText}\n\nErstelle eine übergreifende Zusammenfassung des gesamten Moduls (5-8 Sätze). Fasse die wichtigsten Lernziele und Kernkonzepte zusammen.`, "Du bist ein Experte für Kurszusammenfassungen. Antworte auf Deutsch in klarem, präzisem Stil.", { maxTokens: 512, temperature: 0.3 });
87
+ return `# Zusammenfassung: ${moduleTitle}\n\n${result.trim()}\n\n---\n\n## Lektionen\n\n${summariesText}`;
88
+ }
89
+ //# sourceMappingURL=transcriptPolisher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transcriptPolisher.js","sourceRoot":"","sources":["../../src/ai/transcriptPolisher.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAOvD;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,UAAkB;IAClD,OAAO,UAAU;QACf,+DAA+D;SAC9D,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC;QAC9B,uCAAuC;SACtC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC;QACnB,uBAAuB;SACtB,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;SAC3D,IAAI,CAAC,GAAG,CAAC;SACT,IAAI,EAAE,CAAC;AACZ,CAAC;AAED,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;0BAeI,CAAC;AAE3B,MAAM,oBAAoB,GAAG;;;;;;;;;;;;;+CAakB,CAAC;AAEhD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,aAAqB;IAC1D,IAAI,CAAC,YAAY,EAAE,EAAE,CAAC;QACpB,OAAO;YACL,OAAO,EAAE,EAAE;YACX,UAAU,EAAE,aAAa;SAC1B,CAAC;IACJ,CAAC;IAED,MAAM,UAAU,GAAG,oBAAoB,CAAC,OAAO,CAAC,cAAc,EAAE,aAAa,CAAC,CAAC;IAE/E,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,UAAU,EAAE,aAAa,EAAE;QACrD,SAAS,EAAE,IAAI;QACf,WAAW,EAAE,GAAG;KACjB,CAAC,CAAC;IAEH,qBAAqB;IACrB,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,+CAA+C,CAAC,CAAC;IAChF,MAAM,eAAe,GAAG,MAAM,CAAC,KAAK,CAAC,iCAAiC,CAAC,CAAC;IAExE,MAAM,OAAO,GAAG,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAC7C,MAAM,UAAU,GAAG,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,MAAM,CAAC;IAE1D,OAAO;QACL,OAAO;QACP,UAAU;KACX,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,UAAkB,EAClB,eAAwE;IAExE,IAAI,CAAC,YAAY,EAAE,IAAI,eAAe,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpD,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,WAAW,GAAG,iBAAiB,CAAC,UAAU,CAAC,CAAC;IAElD,MAAM,aAAa,GAAG,eAAe;SAClC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;SACvD,IAAI,CAAC,MAAM,CAAC,CAAC;IAEhB,MAAM,MAAM,GAAG,MAAM,MAAM,CACzB,+DAA+D,WAAW,SAAS,aAAa,6IAA6I,EAC7O,+FAA+F,EAC/F,EAAE,SAAS,EAAE,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,CACrC,CAAC;IAEF,OAAO,sBAAsB,WAAW,OAAO,MAAM,CAAC,IAAI,EAAE,8BAA8B,aAAa,EAAE,CAAC;AAC5G,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Shows all current configuration values.
3
+ */
4
+ export declare function configShowCommand(): void;
5
+ /**
6
+ * Sets a configuration value.
7
+ */
8
+ export declare function configSetCommand(key: string, value: string): void;
9
+ /**
10
+ * Gets a specific configuration value.
11
+ */
12
+ export declare function configGetCommand(key: string): void;
13
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/config.ts"],"names":[],"mappings":"AAMA;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAUxC;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAiCjE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAWlD"}