@tyvm/knowhow 0.0.90 → 0.0.92

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 (272) hide show
  1. package/.depcheckrc +30 -0
  2. package/bin/knowhow.js +1 -1
  3. package/package.json +8 -34
  4. package/src/agents/configurable/ConfigAgent.ts +2 -2
  5. package/src/agents/tools/executeScript/index.ts +5 -0
  6. package/src/agents/tools/googleSearch.ts +2 -2
  7. package/src/agents/tools/index.ts +0 -3
  8. package/src/agents/tools/list.ts +0 -147
  9. package/src/agents/tools/loadWebpage.ts +3 -113
  10. package/src/auth/browserLogin.ts +10 -13
  11. package/src/chat/modules/AgentModule.ts +0 -1
  12. package/src/chat/types.ts +1 -1
  13. package/src/cli.ts +63 -3
  14. package/src/clients/gemini.ts +96 -25
  15. package/src/clients/http.ts +7 -11
  16. package/src/clients/pricing/google.ts +122 -26
  17. package/src/conversion.ts +24 -54
  18. package/src/index.ts +15 -20
  19. package/src/login.ts +5 -6
  20. package/src/plugins/language.ts +0 -4
  21. package/src/plugins/plugins.ts +0 -14
  22. package/src/plugins/url.ts +31 -12
  23. package/src/services/EmbeddingsService.ts +70 -0
  24. package/src/services/KnowhowClient.ts +34 -34
  25. package/src/{plugins/downloader/downloader.ts → services/MediaProcessorService.ts} +109 -267
  26. package/src/services/S3.ts +19 -87
  27. package/src/services/index.ts +8 -8
  28. package/src/services/modules/index.ts +12 -3
  29. package/src/services/modules/types.ts +8 -2
  30. package/src/services/script-execution/ScriptExecutor.ts +29 -10
  31. package/src/services/script-execution/ScriptPolicy.ts +6 -2
  32. package/src/types.ts +1 -0
  33. package/src/utils/http.ts +127 -0
  34. package/src/workers/auth/PasskeySetup.ts +7 -11
  35. package/tests/clients/AIClient.test.ts +24 -21
  36. package/tests/manual/file-edits/figma.test.ts +3 -70
  37. package/tests/plugins/language/languagePlugin-content-triggers.test.ts +2 -0
  38. package/tests/plugins/language/languagePlugin.test.ts +2 -0
  39. package/tests/processors/ToolResponseCache.test.ts +2 -2
  40. package/tests/test.spec.ts +0 -14
  41. package/tests/unit/modules/moduleLoading.test.ts +12 -4
  42. package/tests/unit/plugins/pluginLoading.test.ts +6 -6
  43. package/ts_build/package.json +8 -34
  44. package/ts_build/src/agents/tools/ast/astAppendNode.d.ts +1 -1
  45. package/ts_build/src/agents/tools/ast/astAppendNode.js +2 -90
  46. package/ts_build/src/agents/tools/ast/astAppendNode.js.map +1 -1
  47. package/ts_build/src/agents/tools/ast/astDeleteNode.d.ts +1 -1
  48. package/ts_build/src/agents/tools/ast/astDeleteNode.js +2 -88
  49. package/ts_build/src/agents/tools/ast/astDeleteNode.js.map +1 -1
  50. package/ts_build/src/agents/tools/ast/astEditNode.d.ts +1 -1
  51. package/ts_build/src/agents/tools/ast/astEditNode.js +2 -90
  52. package/ts_build/src/agents/tools/ast/astEditNode.js.map +1 -1
  53. package/ts_build/src/agents/tools/ast/astGetPathForLine.d.ts +1 -1
  54. package/ts_build/src/agents/tools/ast/astGetPathForLine.js +2 -72
  55. package/ts_build/src/agents/tools/ast/astGetPathForLine.js.map +1 -1
  56. package/ts_build/src/agents/tools/ast/astListPaths.d.ts +1 -1
  57. package/ts_build/src/agents/tools/ast/astListPaths.js +2 -72
  58. package/ts_build/src/agents/tools/ast/astListPaths.js.map +1 -1
  59. package/ts_build/src/agents/tools/executeScript/index.d.ts +3 -2
  60. package/ts_build/src/agents/tools/executeScript/index.js +4 -1
  61. package/ts_build/src/agents/tools/executeScript/index.js.map +1 -1
  62. package/ts_build/src/agents/tools/googleSearch.js +2 -2
  63. package/ts_build/src/agents/tools/googleSearch.js.map +1 -1
  64. package/ts_build/src/agents/tools/index.d.ts +0 -3
  65. package/ts_build/src/agents/tools/index.js +0 -3
  66. package/ts_build/src/agents/tools/index.js.map +1 -1
  67. package/ts_build/src/agents/tools/list.js +0 -138
  68. package/ts_build/src/agents/tools/list.js.map +1 -1
  69. package/ts_build/src/agents/tools/loadWebpage.js +1 -89
  70. package/ts_build/src/agents/tools/loadWebpage.js.map +1 -1
  71. package/ts_build/src/agents/tools/textSearch.d.ts +1 -1
  72. package/ts_build/src/auth/browserLogin.js +7 -7
  73. package/ts_build/src/auth/browserLogin.js.map +1 -1
  74. package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
  75. package/ts_build/src/chat/types.d.ts +1 -1
  76. package/ts_build/src/cli.d.ts +1 -1
  77. package/ts_build/src/cli.js +47 -1
  78. package/ts_build/src/cli.js.map +1 -1
  79. package/ts_build/src/clients/gemini.d.ts +1 -73
  80. package/ts_build/src/clients/gemini.js +57 -19
  81. package/ts_build/src/clients/gemini.js.map +1 -1
  82. package/ts_build/src/clients/http.js +5 -9
  83. package/ts_build/src/clients/http.js.map +1 -1
  84. package/ts_build/src/clients/pricing/google.d.ts +17 -73
  85. package/ts_build/src/clients/pricing/google.js +47 -10
  86. package/ts_build/src/clients/pricing/google.js.map +1 -1
  87. package/ts_build/src/conversion.d.ts +1 -4
  88. package/ts_build/src/conversion.js +12 -27
  89. package/ts_build/src/conversion.js.map +1 -1
  90. package/ts_build/src/index.d.ts +4 -0
  91. package/ts_build/src/index.js +15 -14
  92. package/ts_build/src/index.js.map +1 -1
  93. package/ts_build/src/login.js +5 -4
  94. package/ts_build/src/login.js.map +1 -1
  95. package/ts_build/src/plugins/downloader/downloader.js +3 -3
  96. package/ts_build/src/plugins/downloader/downloader.js.map +1 -1
  97. package/ts_build/src/plugins/language.js.map +1 -1
  98. package/ts_build/src/plugins/plugins.js +0 -14
  99. package/ts_build/src/plugins/plugins.js.map +1 -1
  100. package/ts_build/src/plugins/tree-sitter/editor.d.ts +3 -32
  101. package/ts_build/src/plugins/tree-sitter/editor.js +6 -208
  102. package/ts_build/src/plugins/tree-sitter/editor.js.map +1 -1
  103. package/ts_build/src/plugins/tree-sitter/parser.d.ts +19 -54
  104. package/ts_build/src/plugins/tree-sitter/parser.js +19 -293
  105. package/ts_build/src/plugins/tree-sitter/parser.js.map +1 -1
  106. package/ts_build/src/plugins/tree-sitter/simple-paths.d.ts +2 -15
  107. package/ts_build/src/plugins/tree-sitter/simple-paths.js +2 -324
  108. package/ts_build/src/plugins/tree-sitter/simple-paths.js.map +1 -1
  109. package/ts_build/src/plugins/url.js +27 -8
  110. package/ts_build/src/plugins/url.js.map +1 -1
  111. package/ts_build/src/services/EmbeddingsService.d.ts +14 -0
  112. package/ts_build/src/services/EmbeddingsService.js +33 -0
  113. package/ts_build/src/services/EmbeddingsService.js.map +1 -0
  114. package/ts_build/src/services/GitHub.js +2 -2
  115. package/ts_build/src/services/GitHub.js.map +1 -1
  116. package/ts_build/src/services/KnowhowClient.d.ts +29 -29
  117. package/ts_build/src/services/KnowhowClient.js +33 -33
  118. package/ts_build/src/services/KnowhowClient.js.map +1 -1
  119. package/ts_build/src/services/MediaProcessorService.d.ts +22 -0
  120. package/ts_build/src/services/MediaProcessorService.js +215 -0
  121. package/ts_build/src/services/MediaProcessorService.js.map +1 -0
  122. package/ts_build/src/services/S3.d.ts +0 -4
  123. package/ts_build/src/services/S3.js +14 -60
  124. package/ts_build/src/services/S3.js.map +1 -1
  125. package/ts_build/src/services/index.d.ts +6 -5
  126. package/ts_build/src/services/index.js +6 -6
  127. package/ts_build/src/services/index.js.map +1 -1
  128. package/ts_build/src/services/modules/index.js +12 -3
  129. package/ts_build/src/services/modules/index.js.map +1 -1
  130. package/ts_build/src/services/modules/types.d.ts +8 -2
  131. package/ts_build/src/services/script-execution/ScriptExecutor.js +22 -7
  132. package/ts_build/src/services/script-execution/ScriptExecutor.js.map +1 -1
  133. package/ts_build/src/services/script-execution/ScriptPolicy.d.ts +1 -1
  134. package/ts_build/src/services/script-execution/ScriptPolicy.js +4 -2
  135. package/ts_build/src/services/script-execution/ScriptPolicy.js.map +1 -1
  136. package/ts_build/src/types.d.ts +1 -0
  137. package/ts_build/src/types.js +1 -0
  138. package/ts_build/src/types.js.map +1 -1
  139. package/ts_build/src/utils/http.d.ts +27 -0
  140. package/ts_build/src/utils/http.js +98 -0
  141. package/ts_build/src/utils/http.js.map +1 -0
  142. package/ts_build/src/workers/auth/PasskeySetup.js +6 -7
  143. package/ts_build/src/workers/auth/PasskeySetup.js.map +1 -1
  144. package/ts_build/tests/clients/AIClient.test.js +11 -14
  145. package/ts_build/tests/clients/AIClient.test.js.map +1 -1
  146. package/ts_build/tests/manual/file-edits/figma.test.d.ts +0 -1
  147. package/ts_build/tests/manual/file-edits/figma.test.js +1 -46
  148. package/ts_build/tests/manual/file-edits/figma.test.js.map +1 -1
  149. package/ts_build/tests/plugins/language/languagePlugin-content-triggers.test.js +2 -0
  150. package/ts_build/tests/plugins/language/languagePlugin-content-triggers.test.js.map +1 -1
  151. package/ts_build/tests/plugins/language/languagePlugin.test.js +2 -0
  152. package/ts_build/tests/plugins/language/languagePlugin.test.js.map +1 -1
  153. package/ts_build/tests/processors/ToolResponseCache.test.js +2 -2
  154. package/ts_build/tests/processors/ToolResponseCache.test.js.map +1 -1
  155. package/ts_build/tests/test.spec.js +0 -14
  156. package/ts_build/tests/test.spec.js.map +1 -1
  157. package/ts_build/tests/tree-sitter/tree-sitter.test.d.ts +0 -1
  158. package/ts_build/tests/tree-sitter/tree-sitter.test.js +2 -183
  159. package/ts_build/tests/tree-sitter/tree-sitter.test.js.map +1 -1
  160. package/ts_build/tests/unit/modules/moduleLoading.test.js +11 -4
  161. package/ts_build/tests/unit/modules/moduleLoading.test.js.map +1 -1
  162. package/ts_build/tests/unit/plugins/pluginLoading.test.js +4 -4
  163. package/ts_build/tests/unit/plugins/pluginLoading.test.js.map +1 -1
  164. package/benchmarks/.dockerignore +0 -7
  165. package/benchmarks/README.md +0 -166
  166. package/benchmarks/docker/Dockerfile +0 -68
  167. package/benchmarks/example-config.yml +0 -27
  168. package/benchmarks/jest.config.js +0 -13
  169. package/benchmarks/package-lock.json +0 -4297
  170. package/benchmarks/package.json +0 -39
  171. package/benchmarks/results/27b0a06/2025-09-27/xai/xai-grok-code-fast-1.json +0 -2909
  172. package/benchmarks/results/4057aed/2025-08-14/anthropic/anthropic-claude-sonnet-4-20250514.json +0 -1671
  173. package/benchmarks/results/4542435/2025-08-05/lms/lms-openai-gpt-oss-20b.json +0 -2814
  174. package/benchmarks/results/4542435/2025-08-05/lms/lms-qwen-qwen3-30b-a3b-2507.json +0 -2014
  175. package/benchmarks/results/4fb9125/2025-08-07/anthropic/anthropic-claude-sonnet-4-20250514.json +0 -3121
  176. package/benchmarks/results/5766aee/2025-08-02/lms-qwen/qwen3-coder-30b.json +0 -98
  177. package/benchmarks/results/6d73808/2025-08-07/openai/openai-gpt-5.json +0 -3256
  178. package/benchmarks/results/77bf0a6/2025-08-02/lms-qwen/qwen3-30b-a3b-2507.json +0 -4298
  179. package/benchmarks/results/8c0d445/2025-08-03/anthropic/anthropic-claude-sonnet-4-20250514.json +0 -3031
  180. package/benchmarks/results/8c0d445/2025-08-03/openai/openai-gpt-4.1-2025-04-14.json +0 -2990
  181. package/benchmarks/results/ac6b2ab/2025-08-03/anthropic/anthropic-claude-sonnet-4-20250514.json +0 -3256
  182. package/benchmarks/results/ac6b2ab/2025-08-03/lms/lms-qwen-qwen3-coder-30b.json +0 -3007
  183. package/benchmarks/results/ac6b2ab/2025-08-03/openai/openai-gpt-4.1-2025-04-14.json +0 -3256
  184. package/benchmarks/results/ac6b2ab/2025-08-03/openai/openai-gpt-4.1-mini-2025-04-14.json +0 -3036
  185. package/benchmarks/results/ac6b2ab/2025-08-03/openai/openai-gpt-4.1-nano-2025-04-14.json +0 -3280
  186. package/benchmarks/results/adff675/2025-08-04/lms/lms-qwen-qwen3-30b-a3b-2507.json +0 -1920
  187. package/benchmarks/results/adff675/2025-08-04/lms/lms-qwen-qwen3-coder-30b.json +0 -3281
  188. package/benchmarks/results/b502ed9/2025-08-03/lms-qwen/qwen3-coder-30b.json +0 -2896
  189. package/benchmarks/results/d1a8129/2025-08-03/lms/lms-qwen-qwen3-coder-30b.json +0 -3011
  190. package/benchmarks/results/e60471c/2025-08-03/lms/qwen3-30b-a3b-2507.json +0 -3003
  191. package/benchmarks/scripts/build-and-run.sh +0 -47
  192. package/benchmarks/scripts/clone-exercism.sh +0 -92
  193. package/benchmarks/scripts/validate.sh +0 -48
  194. package/benchmarks/src/__tests__/runner.test.ts +0 -27
  195. package/benchmarks/src/cli.ts +0 -90
  196. package/benchmarks/src/evaluators/EvaluatorRegistry.ts +0 -64
  197. package/benchmarks/src/evaluators/JavaScriptEvaluator.ts +0 -183
  198. package/benchmarks/src/evaluators/index.ts +0 -3
  199. package/benchmarks/src/evaluators/types.ts +0 -22
  200. package/benchmarks/src/index.ts +0 -3
  201. package/benchmarks/src/providers.ts +0 -13
  202. package/benchmarks/src/runner.ts +0 -824
  203. package/benchmarks/src/types.ts +0 -63
  204. package/benchmarks/tsconfig.json +0 -19
  205. package/leaderboard/README.md +0 -148
  206. package/leaderboard/app/api/benchmark-data/route.ts +0 -131
  207. package/leaderboard/app/api/benchmark-detail/route.ts +0 -172
  208. package/leaderboard/app/details/[model]/[provider]/[language]/page.tsx +0 -501
  209. package/leaderboard/app/exercise/[model]/[provider]/[language]/[exercise]/page.tsx +0 -375
  210. package/leaderboard/app/globals.css +0 -27
  211. package/leaderboard/app/layout.tsx +0 -21
  212. package/leaderboard/app/page.tsx +0 -170
  213. package/leaderboard/components/LeaderboardTable.tsx +0 -168
  214. package/leaderboard/components/PerformanceChart.tsx +0 -109
  215. package/leaderboard/next-env.d.ts +0 -5
  216. package/leaderboard/next.config.js +0 -4
  217. package/leaderboard/package-lock.json +0 -6363
  218. package/leaderboard/package.json +0 -28
  219. package/leaderboard/postcss.config.js +0 -6
  220. package/leaderboard/tailwind.config.js +0 -17
  221. package/leaderboard/tsconfig.json +0 -28
  222. package/leaderboard/types/benchmark.ts +0 -67
  223. package/leaderboard/utils/dataProcessor.ts +0 -33
  224. package/src/agents/tools/asana/definitions.ts +0 -199
  225. package/src/agents/tools/asana/index.ts +0 -108
  226. package/src/agents/tools/ast/astAppendNode.ts +0 -90
  227. package/src/agents/tools/ast/astDeleteNode.ts +0 -88
  228. package/src/agents/tools/ast/astEditNode.ts +0 -95
  229. package/src/agents/tools/ast/astGetPathForLine.ts +0 -73
  230. package/src/agents/tools/ast/astListPaths.ts +0 -66
  231. package/src/agents/tools/ast/index.ts +0 -7
  232. package/src/agents/tools/github/definitions.ts +0 -89
  233. package/src/agents/tools/github/index.ts +0 -67
  234. package/src/chat-old.ts +0 -446
  235. package/src/plugins/asana.ts +0 -146
  236. package/src/plugins/downloader/plugin.ts +0 -103
  237. package/src/plugins/downloader/types.ts +0 -92
  238. package/src/plugins/figma.ts +0 -158
  239. package/src/plugins/github.ts +0 -219
  240. package/src/plugins/jira.ts +0 -115
  241. package/src/plugins/linear.ts +0 -230
  242. package/src/plugins/notion.ts +0 -179
  243. package/src/plugins/tree-sitter/editor.ts +0 -369
  244. package/src/plugins/tree-sitter/lang-packs/index.ts +0 -23
  245. package/src/plugins/tree-sitter/lang-packs/java.ts +0 -59
  246. package/src/plugins/tree-sitter/lang-packs/javascript.ts +0 -57
  247. package/src/plugins/tree-sitter/lang-packs/python.ts +0 -45
  248. package/src/plugins/tree-sitter/lang-packs/types.ts +0 -79
  249. package/src/plugins/tree-sitter/lang-packs/typescript.ts +0 -49
  250. package/src/plugins/tree-sitter/parser.ts +0 -470
  251. package/src/plugins/tree-sitter/simple-paths.ts +0 -467
  252. package/src/services/GitHub.ts +0 -59
  253. package/tests/tree-sitter/editor.test.ts +0 -113
  254. package/tests/tree-sitter/invalid.test.ts +0 -299
  255. package/tests/tree-sitter/paths/common-edits.test.ts +0 -564
  256. package/tests/tree-sitter/paths/debug-exact-position.test.ts +0 -44
  257. package/tests/tree-sitter/paths/debug-line-indexing.test.ts +0 -49
  258. package/tests/tree-sitter/paths/debug-paths.test.ts +0 -90
  259. package/tests/tree-sitter/paths/paths.test.ts +0 -170
  260. package/tests/tree-sitter/paths/simple-paths.test.ts +0 -367
  261. package/tests/tree-sitter/sample-after.ts +0 -48
  262. package/tests/tree-sitter/sample-before.ts +0 -25
  263. package/tests/tree-sitter/test-files/completely-broken.ts +0 -7
  264. package/tests/tree-sitter/test-files/duplicate-braces.ts +0 -39
  265. package/tests/tree-sitter/test-files/invalid-nesting.ts +0 -39
  266. package/tests/tree-sitter/test-files/malformed-signature.ts +0 -39
  267. package/tests/tree-sitter/test-files/mismatched-parens.ts +0 -39
  268. package/tests/tree-sitter/test-files/missing-semicolon.ts +0 -39
  269. package/tests/tree-sitter/test-files/partially-broken.ts +0 -20
  270. package/tests/tree-sitter/test-files/specific-errors.ts +0 -14
  271. package/tests/tree-sitter/test-files/unclosed-string.ts +0 -39
  272. package/tests/tree-sitter/tree-sitter.test.ts +0 -251
@@ -1,82 +1,54 @@
1
1
  import * as fs from "fs";
2
2
  import * as path from "path";
3
- import ytdl from "youtube-dl-exec";
4
- import Logger from "progress-estimator";
5
- import { DownloadInfo, KeyframeInfo, TranscriptChunk } from "./types";
6
- import { visionTool } from "../../agents/tools/visionTool";
7
- import { execAsync, fileExists, readFile, mkdir } from "../../utils";
8
- import { Clients } from "../../clients";
9
- import { Models } from "../../types";
10
-
11
- const logger = Logger();
12
-
13
- export class DownloaderService {
14
- constructor(private clients: typeof Clients) {}
15
-
16
- async askGptVision(
17
- imageUrl: string,
18
- question: string,
19
- provider = "openai",
20
- model = Models.openai.GPT_4o
21
- ) {
22
- const response = await this.clients.createCompletion(provider, {
23
- model,
24
- max_tokens: 2500,
25
- messages: [
26
- {
27
- role: "user",
28
- content: [
29
- { type: "text", text: question },
30
- {
31
- type: "image_url",
32
- image_url: {
33
- url: imageUrl,
34
- },
35
- },
36
- ],
37
- },
38
- ],
39
- });
3
+ import { exec } from "child_process";
4
+ import { promisify } from "util";
5
+ import { fileExists, readFile, mkdir } from "../utils";
6
+ import { AIClient } from "../clients";
40
7
 
41
- return response;
42
- }
43
-
44
- async download(url: string, outputDir: string) {
45
- const info = await this.info(url);
46
- const exists = await fileExists(`${outputDir}/${info.id}.${info.ext}`);
8
+ const execPromise = promisify(exec);
47
9
 
48
- if (exists) {
49
- console.log("File already exists, skipping download");
50
- return info;
51
- }
10
+ async function execAsync(command: string): Promise<string> {
11
+ const { stdout, stderr } = await execPromise(command);
12
+ return stdout + stderr;
13
+ }
52
14
 
53
- const scrape = ytdl(url, { output: `${outputDir}/%(id)s.%(ext)s` });
54
- const result = await logger(scrape, `Obtaining ${url}`);
55
- return info;
56
- }
15
+ export interface TranscriptChunk {
16
+ chunkPath: string;
17
+ text: string;
18
+ usd_cost: number;
19
+ }
57
20
 
58
- async info(url: string) {
59
- const info = await ytdl(url, {
60
- dumpSingleJson: true,
61
- noWarnings: true,
62
- });
63
- console.log(info);
64
- return info;
65
- }
21
+ export interface KeyframeInfo {
22
+ path: string;
23
+ description: string;
24
+ timestamp: number;
25
+ usd_cost?: number;
26
+ }
66
27
 
28
+ /**
29
+ * MediaProcessorService handles audio/video processing using:
30
+ * - ffmpeg (system tool) for chunking audio/video
31
+ * - OpenAI Whisper API for transcription
32
+ *
33
+ * This is part of the core services because microphone recording and
34
+ * audio-to-text transcription are base CLI features. The DownloaderService
35
+ * (in @tyvm/knowhow-module-video-downloader) uses this service for the
36
+ * audio/video processing steps after downloading with ytdl.
37
+ */
38
+ export class MediaProcessorService {
39
+ constructor(private clients: any) {}
40
+
41
+ /**
42
+ * Split an audio/video file into fixed-length mp3 chunks using ffmpeg.
43
+ */
67
44
  public async chunk(
68
45
  filePath: string,
69
46
  outputDir: string,
70
47
  CHUNK_LENGTH_SECONDS = 30,
71
48
  reuseExistingChunks = true
72
- ) {
49
+ ): Promise<string[]> {
73
50
  const parsed = path.parse(filePath);
74
51
  const fileName = parsed.name;
75
- const fileExt = parsed.ext;
76
- console.log({ fileName, fileExt });
77
- console.log("Chunking file", filePath);
78
-
79
- // create a temp directory
80
52
  const outputDirPath = path.join(outputDir, `${fileName}/chunks`);
81
53
  await fs.promises.mkdir(outputDirPath, { recursive: true });
82
54
  const doneFilePath = path.join(outputDirPath, ".chunking_done");
@@ -90,10 +62,9 @@ export class DownloaderService {
90
62
  if (existingChunkNames.length > 0 && doneFileExists) {
91
63
  if (reuseExistingChunks) {
92
64
  console.log("Chunks already exist, skipping");
93
- const names = existingChunkNames.map((chunkName) =>
65
+ return existingChunkNames.map((chunkName) =>
94
66
  path.join(outputDirPath, chunkName)
95
67
  );
96
- return names;
97
68
  } else {
98
69
  for (const file of existingFolderFiles) {
99
70
  fs.rmSync(path.join(outputDirPath, file), { recursive: true });
@@ -113,6 +84,9 @@ export class DownloaderService {
113
84
  return chunkNames.map((chunkName) => path.join(outputDirPath, chunkName));
114
85
  }
115
86
 
87
+ /**
88
+ * Stream transcription of audio chunks using Whisper.
89
+ */
116
90
  public async *streamTranscription(
117
91
  files: string[],
118
92
  outputPath: string,
@@ -123,13 +97,11 @@ export class DownloaderService {
123
97
  console.log("Transcription already exists, using cached data");
124
98
  const contents = await readFile(outputPath);
125
99
  const data = JSON.parse(contents.toString()) as TranscriptChunk[];
126
- for (const item of data) {
127
- yield item;
128
- }
100
+ for (const item of data) yield item;
129
101
  return;
130
102
  }
131
103
 
132
- const allTranscripts = [];
104
+ const allTranscripts: TranscriptChunk[] = [];
133
105
  for (const file of files) {
134
106
  const chunkName = path.parse(file).name;
135
107
  const chunkTranscriptPath = path.join(
@@ -139,17 +111,12 @@ export class DownloaderService {
139
111
  const chunkExists = await fileExists(chunkTranscriptPath);
140
112
 
141
113
  if (chunkExists && reusePreviousTranscript) {
142
- console.log(
143
- chunkTranscriptPath,
144
- " transcription already exists, using cached data"
145
- );
146
114
  const contents = await readFile(chunkTranscriptPath);
147
- const cached = {
115
+ const cached: TranscriptChunk = {
148
116
  chunkPath: chunkTranscriptPath,
149
117
  text: contents.toString(),
150
118
  usd_cost: 0,
151
119
  };
152
-
153
120
  yield cached;
154
121
  allTranscripts.push(cached);
155
122
  continue;
@@ -163,7 +130,7 @@ export class DownloaderService {
163
130
  fileName: path.basename(file),
164
131
  model: "whisper-1",
165
132
  })
166
- .catch((e) => {
133
+ .catch((e: any) => {
167
134
  console.error("Error transcribing", file, e);
168
135
  return { text: "" };
169
136
  });
@@ -171,11 +138,10 @@ export class DownloaderService {
171
138
  await mkdir(path.dirname(chunkTranscriptPath), { recursive: true });
172
139
  await fs.promises.writeFile(chunkTranscriptPath, transcript.text);
173
140
 
174
- // save chunk transcript to file
175
- const data = {
141
+ const data: TranscriptChunk = {
176
142
  chunkPath: chunkTranscriptPath,
177
143
  text: transcript.text,
178
- usd_cost: 30 * 0.0001, // assume 30 seconds,
144
+ usd_cost: 30 * 0.0001,
179
145
  };
180
146
  yield data;
181
147
  allTranscripts.push(data);
@@ -184,6 +150,9 @@ export class DownloaderService {
184
150
  fs.writeFileSync(outputPath, JSON.stringify(allTranscripts, null, 2));
185
151
  }
186
152
 
153
+ /**
154
+ * Transcribe all audio chunks and return the text strings.
155
+ */
187
156
  public async transcribeChunks(
188
157
  files: string[],
189
158
  outputPath: string,
@@ -191,18 +160,16 @@ export class DownloaderService {
191
160
  ): Promise<string[]> {
192
161
  const exists = await fileExists(outputPath);
193
162
  if (exists && reusePreviousTranscript) {
194
- console.log("Transcription already exists, using cached data");
195
163
  const contents = await readFile(outputPath);
196
164
  return JSON.parse(contents.toString()) as string[];
197
165
  }
198
166
 
199
- const fullText = [];
200
- for await (const { chunkPath, text } of this.streamTranscription(
167
+ const fullText: string[] = [];
168
+ for await (const { text } of this.streamTranscription(
201
169
  files,
202
170
  outputPath,
203
171
  reusePreviousTranscript
204
172
  )) {
205
- console.log("Chunk transcribed:", chunkPath);
206
173
  fullText.push(text);
207
174
  }
208
175
 
@@ -210,42 +177,65 @@ export class DownloaderService {
210
177
  return fullText;
211
178
  }
212
179
 
180
+ /**
181
+ * Process an audio/video file: chunk it with ffmpeg, then transcribe each chunk.
182
+ * Returns an array of transcript strings (one per chunk).
183
+ */
184
+ public async processAudio(
185
+ filePath: string,
186
+ reusePreviousTranscript = true,
187
+ chunkTime = 30
188
+ ): Promise<string[]> {
189
+ const parsed = path.parse(filePath);
190
+ const outputPath = `${parsed.dir}/${parsed.name}/transcript.json`;
191
+
192
+ const exists = await fileExists(outputPath);
193
+ if (exists && reusePreviousTranscript) {
194
+ const fileContent = (await readFile(outputPath, "utf8")) as string;
195
+ return outputPath.endsWith("txt")
196
+ ? fileContent.split("\n")
197
+ : JSON.parse(fileContent);
198
+ }
199
+
200
+ const chunks = await this.chunk(
201
+ filePath,
202
+ parsed.dir,
203
+ chunkTime,
204
+ reusePreviousTranscript
205
+ );
206
+ return this.transcribeChunks(chunks, outputPath, reusePreviousTranscript);
207
+ }
208
+
209
+ /**
210
+ * Extract keyframes from a video file using ffmpeg, then describe each with vision AI.
211
+ */
213
212
  public async *streamKeyFrameExtraction(
214
213
  filePath: string,
215
214
  videoJsonPath: string,
216
- reusePreviousKeyframes: boolean = true,
217
- interval: number = 10
215
+ reusePreviousKeyframes = true,
216
+ interval = 10
218
217
  ): AsyncGenerator<KeyframeInfo> {
219
218
  if (reusePreviousKeyframes && fs.existsSync(videoJsonPath)) {
220
- console.log("Keyframes already exist, using cached data");
221
219
  const contents = await readFile(videoJsonPath);
222
220
  const data = JSON.parse(contents.toString()) as KeyframeInfo[];
223
- for (const keyframe of data) {
224
- yield { ...keyframe, usd_cost: 0 };
225
- }
221
+ for (const keyframe of data) yield { ...keyframe, usd_cost: 0 };
226
222
  return;
227
223
  }
228
224
 
229
- const parsed = path.parse(filePath);
230
225
  const outputDir = path.dirname(videoJsonPath);
231
- const fileName = parsed.name;
232
- const keyframesDir = path.join(outputDir, `/keyframes`);
226
+ const keyframesDir = path.join(outputDir, "keyframes");
233
227
  await fs.promises.mkdir(keyframesDir, { recursive: true });
234
228
 
235
229
  const command = `ffmpeg -i "${filePath}" -vf "fps=1/${interval},scale=640:-1" "${keyframesDir}/frame%04d.jpg"`;
236
230
  await execAsync(command);
237
- console.log("Extracting keyframe:", command);
238
231
 
239
232
  const keyframes = await fs.promises.readdir(keyframesDir);
233
+ const allKeyframes: KeyframeInfo[] = [];
240
234
 
241
- const allKeyframes = [];
242
235
  for (const keyframe of keyframes) {
243
236
  const keyframePath = path.join(keyframesDir, keyframe);
244
237
  const keyframeName = path.parse(keyframe).name;
245
- const keyframeDescriptionPath = path.join(
246
- keyframesDir,
247
- `${keyframeName}.json`
248
- );
238
+ const keyframeDescriptionPath = path.join(keyframesDir, `${keyframeName}.json`);
249
239
  const descriptionExists = await fileExists(keyframeDescriptionPath);
250
240
 
251
241
  if (descriptionExists && reusePreviousKeyframes) {
@@ -257,10 +247,11 @@ export class DownloaderService {
257
247
  }
258
248
 
259
249
  const description = await this.describeKeyframe(keyframePath);
260
- const keyframeJson = {
250
+ const frameNumber = parseInt(keyframe.match(/\d+/)?.[0] ?? "0", 10);
251
+ const keyframeJson: KeyframeInfo = {
261
252
  path: keyframePath,
262
253
  description: description.choices[0].message.content,
263
- timestamp: this.extractTimestamp(keyframe, interval),
254
+ timestamp: frameNumber * interval,
264
255
  usd_cost: description.usd_cost,
265
256
  };
266
257
  await fs.promises.writeFile(
@@ -271,17 +262,14 @@ export class DownloaderService {
271
262
  allKeyframes.push(keyframeJson);
272
263
  }
273
264
 
274
- await fs.promises.writeFile(
275
- videoJsonPath,
276
- JSON.stringify(allKeyframes, null, 2)
277
- );
265
+ await fs.promises.writeFile(videoJsonPath, JSON.stringify(allKeyframes, null, 2));
278
266
  }
279
267
 
280
268
  public async extractKeyframes(
281
269
  filePath: string,
282
270
  outputPath: string,
283
- reusePreviousKeyframes: boolean = true,
284
- interval: number = 10
271
+ reusePreviousKeyframes = true,
272
+ interval = 10
285
273
  ): Promise<KeyframeInfo[]> {
286
274
  const keyframes: KeyframeInfo[] = [];
287
275
  for await (const keyframe of this.streamKeyFrameExtraction(
@@ -292,7 +280,6 @@ export class DownloaderService {
292
280
  )) {
293
281
  keyframes.push(keyframe);
294
282
  }
295
-
296
283
  await fs.promises.writeFile(outputPath, JSON.stringify(keyframes, null, 2));
297
284
  return keyframes;
298
285
  }
@@ -300,165 +287,20 @@ export class DownloaderService {
300
287
  private async describeKeyframe(keyframePath: string) {
301
288
  const question =
302
289
  "Describe this image in detail, focusing on the main elements and actions visible.";
303
- const base64 = await fs.promises.readFile(keyframePath, {
304
- encoding: "base64",
305
- });
290
+ const base64 = await fs.promises.readFile(keyframePath, { encoding: "base64" });
306
291
  const image = `data:image/jpeg;base64,${base64}`;
307
- console.log("Describing keyframe:", keyframePath);
308
- const response = await this.askGptVision(image, question);
309
- return response;
310
- }
311
-
312
- private extractTimestamp(keyframeName: string, interval: number): number {
313
- const frameNumber = parseInt(keyframeName.match(/\d+/)[0], 10);
314
- return frameNumber * interval;
315
- }
316
-
317
- async processAudio(
318
- filePath: string,
319
- reusePreviousTranscript = true,
320
- chunkTime = 30
321
- ): Promise<string[]> {
322
- const parsed = path.parse(filePath);
323
- const outputPath = `${parsed.dir}/${parsed.name}/transcript.json`;
324
-
325
- // Skip chunking if the full output exists
326
- const exists = await fileExists(outputPath);
327
- if (exists && reusePreviousTranscript) {
328
- console.log(
329
- `Transcription ${outputPath} already exists, using cached data`
330
- );
331
- const fileContent = await readFile(outputPath, "utf8");
332
- return outputPath.endsWith("txt")
333
- ? fileContent.split("\n")
334
- : JSON.parse(fileContent);
335
- }
336
-
337
- const chunks = await this.chunk(
338
- filePath,
339
- parsed.dir,
340
- chunkTime,
341
- reusePreviousTranscript
342
- );
343
- const transcription = await this.transcribeChunks(
344
- chunks,
345
- outputPath,
346
- reusePreviousTranscript
347
- );
348
-
349
- return transcription;
350
- }
351
-
352
- async *streamProcessAudio(
353
- filePath: string,
354
- reusePreviousTranscript = true,
355
- chunkTime = 30
356
- ): AsyncGenerator<TranscriptChunk> {
357
- const parsed = path.parse(filePath);
358
- const outputPath = `${parsed.dir}/${parsed.name}/transcript.json`;
359
-
360
- // Skip chunking if the full output exists
361
- const exists = await fileExists(outputPath);
362
- if (exists && reusePreviousTranscript) {
363
- console.log(
364
- `Transcription ${outputPath} already exists, using cached data`
365
- );
366
- const fileContent = await readFile(outputPath, "utf8");
367
- const lines = outputPath.endsWith("txt")
368
- ? fileContent.split("\n")
369
- : JSON.parse(fileContent);
370
-
371
- for (const line of lines) {
372
- if (typeof line === "string") {
373
- yield { chunkPath: "", text: line, usd_cost: 0 };
374
- } else {
375
- yield line as TranscriptChunk;
376
- }
377
- }
378
- return;
379
- }
380
-
381
- const chunks = await this.chunk(
382
- filePath,
383
- parsed.dir,
384
- chunkTime,
385
- reusePreviousTranscript
386
- );
387
-
388
- for await (const chunk of this.streamTranscription(
389
- chunks,
390
- outputPath,
391
- reusePreviousTranscript
392
- )) {
393
- yield chunk;
394
- }
395
- }
396
-
397
- async processVideo(
398
- filePath: string,
399
- reusePreviousTranscript = true,
400
- chunkTime = 30
401
- ) {
402
- const parsed = path.parse(filePath);
403
- const outputPath = `${parsed.dir}/${parsed.name}/video.json`;
404
-
405
- console.log("Processing audio...");
406
- const transcriptions = await this.processAudio(
407
- filePath,
408
- reusePreviousTranscript,
409
- chunkTime
410
- );
411
-
412
- console.log("Extracting keyframes...");
413
- const videoAnalysis = await this.extractKeyframes(
414
- filePath,
415
- outputPath,
416
- reusePreviousTranscript,
417
- chunkTime
418
- );
419
-
420
- return videoAnalysis.map((frame, index) => {
421
- return {
422
- frame,
423
- transcription: transcriptions[index],
424
- };
425
- });
426
- }
427
-
428
- async *streamProcessVideo(
429
- filePath: string,
430
- reusePreviousTranscript = true,
431
- chunkTime = 30
432
- ) {
433
- const parsed = path.parse(filePath);
434
- const videoJson = `${parsed.dir}/${parsed.name}/video.json`;
435
-
436
- console.log("Processing audio...");
437
- const transcriptions = this.streamProcessAudio(
438
- filePath,
439
- reusePreviousTranscript,
440
- chunkTime
441
- );
442
-
443
- console.log("Extracting keyframes...");
444
- const videoAnalysis = this.streamKeyFrameExtraction(
445
- filePath,
446
- videoJson,
447
- reusePreviousTranscript,
448
- chunkTime
449
- );
450
-
451
- for await (const frame of videoAnalysis) {
452
- const transcription = (await transcriptions.next())
453
- ?.value as TranscriptChunk;
454
- yield {
455
- frame,
456
- transcription: transcription || {
457
- chunkPath: "",
458
- text: "[missing transcript]",
459
- usd_cost: 0,
292
+ return this.clients.createCompletion("openai", {
293
+ model: "gpt-4o",
294
+ max_tokens: 2500,
295
+ messages: [
296
+ {
297
+ role: "user",
298
+ content: [
299
+ { type: "text", text: question },
300
+ { type: "image_url", image_url: { url: image } },
301
+ ],
460
302
  },
461
- };
462
- }
303
+ ],
304
+ });
463
305
  }
464
306
  }
@@ -1,101 +1,32 @@
1
- import {
2
- S3Client,
3
- HeadBucketCommand,
4
- CreateBucketCommand,
5
- GetObjectCommand,
6
- PutObjectCommand,
7
- GetObjectCommandInput,
8
- PutObjectCommandInput,
9
- } from "@aws-sdk/client-s3";
10
-
11
1
  import * as fs from "fs";
12
- const fsPromises = fs.promises;
13
- import { createWriteStream } from "fs";
14
- import * as path from "path";
15
- import { pipeline } from "stream";
2
+ import { createWriteStream, createReadStream } from "fs";
3
+ import { pipeline, Readable } from "stream";
16
4
  import * as util from "util";
17
- import axios from "axios";
18
5
 
19
- import { createReadStream } from "fs";
20
6
  const pipelineAsync = util.promisify(pipeline);
21
7
 
22
8
  export class S3Service {
23
- private s3: S3Client;
24
-
25
- constructor() {
26
- this.s3 = new S3Client();
27
- }
28
-
29
- async uploadFile(
30
- filePath: string,
31
- bucketName: string,
32
- key: string
33
- ): Promise<void> {
34
- const fileContent = await fsPromises.readFile(filePath);
35
-
36
- const params = {
37
- Bucket: bucketName,
38
- Key: key,
39
- Body: fileContent,
40
- };
41
-
42
- // create bucket if it doesn't exist
43
- try {
44
- await this.s3.send(new HeadBucketCommand({ Bucket: bucketName }));
45
- } catch (error) {
46
- const statusCode = error.$metadata.httpStatusCode;
47
- if (statusCode === 404) {
48
- await this.s3.send(new CreateBucketCommand({ Bucket: bucketName }));
49
- console.log(`Bucket ${bucketName} created successfully`);
50
- } else {
51
- throw error;
52
- }
53
- }
54
-
55
- await this.s3.send(new PutObjectCommand(params));
56
- console.log(`File uploaded successfully to ${bucketName}/${key}`);
57
- }
58
-
59
- async downloadFile(
60
- bucketName: string,
61
- key: string,
62
- destinationPath: string
63
- ): Promise<void> {
64
- const params = {
65
- Bucket: bucketName,
66
- Key: key,
67
- };
68
- const { Body } = await this.s3.send(new GetObjectCommand(params));
69
- const fileStream = createWriteStream(destinationPath);
70
-
71
- await pipelineAsync(Body as NodeJS.ReadableStream, fileStream);
72
-
73
- console.log(
74
- `File downloaded successfully from ${bucketName}/${key} to ${destinationPath}`
75
- );
76
- }
77
-
78
9
  async uploadToPresignedUrl(
79
10
  presignedUrl: string,
80
11
  filePath: string
81
12
  ): Promise<void> {
82
13
  try {
83
14
  const fileStream = createReadStream(filePath);
84
- const fileStats = await fsPromises.stat(filePath);
85
-
86
- const response = await axios.put(presignedUrl, fileStream, {
87
- headers: {
88
- "Content-Length": fileStats.size,
89
- },
90
- maxBodyLength: Infinity,
91
- maxContentLength: Infinity,
15
+ const fileStats = await fs.promises.stat(filePath);
16
+
17
+ const response = await fetch(presignedUrl, {
18
+ method: "PUT",
19
+ headers: { "Content-Length": String(fileStats.size) },
20
+ body: fileStream as any,
21
+ // @ts-ignore - Node 18+ supports ReadableStream body with duplex
22
+ duplex: "half",
92
23
  });
93
24
 
94
- if (response.status === 200) {
95
- console.log("File uploaded successfully to pre-signed URL");
96
- } else {
25
+ if (!response.ok) {
97
26
  throw new Error(`Upload failed with status code: ${response.status}`);
98
27
  }
28
+
29
+ console.log("File uploaded successfully to pre-signed URL");
99
30
  } catch (error) {
100
31
  console.error("Error uploading file to pre-signed URL:", error);
101
32
  throw error;
@@ -107,12 +38,14 @@ export class S3Service {
107
38
  destinationPath: string
108
39
  ): Promise<void> {
109
40
  try {
110
- const response = await axios.get(presignedUrl, {
111
- responseType: "stream",
112
- });
41
+ const response = await fetch(presignedUrl);
42
+
43
+ if (!response.ok) {
44
+ throw new Error(`Download failed with status code: ${response.status}`);
45
+ }
113
46
 
114
47
  const fileStream = createWriteStream(destinationPath);
115
- await pipelineAsync(response.data, fileStream);
48
+ await pipelineAsync(Readable.from(response.body as any), fileStream);
116
49
 
117
50
  console.log(
118
51
  `File downloaded successfully from pre-signed URL to ${destinationPath}`
@@ -123,4 +56,3 @@ export class S3Service {
123
56
  }
124
57
  }
125
58
  }
126
-