project-graph-mcp 2.3.2 → 2.4.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 (279) hide show
  1. package/package.json +3 -2
  2. package/src/analysis/analysis-cache.ctx +9 -0
  3. package/src/analysis/analysis-cache.js +1 -1
  4. package/src/analysis/complexity.ctx +6 -0
  5. package/src/analysis/complexity.js +1 -1
  6. package/src/analysis/custom-rules.ctx +14 -0
  7. package/src/analysis/custom-rules.js +1 -1
  8. package/src/analysis/db-analysis.ctx +7 -0
  9. package/src/analysis/db-analysis.js +1 -1
  10. package/src/analysis/dead-code.ctx +6 -0
  11. package/src/analysis/dead-code.js +1 -1
  12. package/src/analysis/full-analysis.ctx +9 -0
  13. package/src/analysis/full-analysis.js +1 -1
  14. package/src/analysis/jsdoc-checker.ctx +10 -0
  15. package/src/analysis/jsdoc-checker.js +1 -1
  16. package/src/analysis/jsdoc-generator.ctx +9 -0
  17. package/src/analysis/jsdoc-generator.js +1 -1
  18. package/src/analysis/large-files.ctx +6 -0
  19. package/src/analysis/large-files.js +1 -1
  20. package/src/analysis/outdated-patterns.ctx +7 -0
  21. package/src/analysis/outdated-patterns.js +1 -1
  22. package/src/analysis/similar-functions.ctx +6 -0
  23. package/src/analysis/similar-functions.js +1 -1
  24. package/src/analysis/test-annotations.ctx +11 -0
  25. package/src/analysis/test-annotations.js +1 -1
  26. package/src/analysis/type-checker.ctx +6 -0
  27. package/src/analysis/type-checker.js +1 -1
  28. package/src/analysis/undocumented.ctx +8 -0
  29. package/src/analysis/undocumented.js +1 -1
  30. package/src/cli/cli-handlers.ctx +7 -0
  31. package/src/cli/cli-handlers.js +1 -1
  32. package/src/cli/cli.ctx +6 -0
  33. package/src/cli/cli.js +1 -1
  34. package/src/compact/ai-context.ctx +6 -0
  35. package/src/compact/ai-context.js +1 -1
  36. package/src/compact/compact-migrate.ctx +8 -0
  37. package/src/compact/compact-migrate.js +1 -1
  38. package/src/compact/compact.ctx +11 -0
  39. package/src/compact/compact.js +1 -1
  40. package/src/compact/compress.ctx +7 -0
  41. package/src/compact/compress.js +1 -1
  42. package/src/compact/ctx-resolver.ctx +2 -0
  43. package/src/compact/ctx-resolver.js +1 -1
  44. package/src/compact/ctx-to-jsdoc.ctx +11 -0
  45. package/src/compact/ctx-to-jsdoc.js +1 -1
  46. package/src/compact/doc-dialect.ctx +11 -0
  47. package/src/compact/doc-dialect.js +2 -2
  48. package/src/compact/expand.ctx +14 -0
  49. package/src/compact/expand.js +1 -1
  50. package/src/compact/framework-references.ctx +7 -0
  51. package/src/compact/framework-references.js +1 -1
  52. package/src/compact/instructions.ctx +6 -0
  53. package/src/compact/instructions.js +1 -1
  54. package/src/compact/jsdoc-builder.ctx +4 -0
  55. package/src/compact/jsdoc-builder.js +1 -1
  56. package/src/compact/mode-config.ctx +8 -0
  57. package/src/compact/mode-config.js +1 -1
  58. package/src/compact/split-declarations.ctx +6 -0
  59. package/src/compact/split-declarations.js +1 -1
  60. package/src/compact/validate-pipeline.ctx +12 -0
  61. package/src/compact/validate-pipeline.js +1 -1
  62. package/src/core/event-bus.ctx +9 -0
  63. package/src/core/event-bus.js +1 -1
  64. package/src/core/file-walker.ctx +1 -0
  65. package/src/core/file-walker.js +1 -1
  66. package/src/core/filters.ctx +12 -0
  67. package/src/core/filters.js +1 -1
  68. package/src/core/graph-builder.ctx +7 -0
  69. package/src/core/graph-builder.js +1 -1
  70. package/src/core/parser.ctx +12 -0
  71. package/src/core/parser.js +1 -1
  72. package/src/core/utils.ctx +1 -0
  73. package/src/core/utils.js +1 -1
  74. package/src/core/workspace.ctx +7 -0
  75. package/src/core/workspace.js +1 -1
  76. package/src/lang/lang-go.ctx +8 -0
  77. package/src/lang/lang-go.js +1 -1
  78. package/src/lang/lang-python.ctx +5 -0
  79. package/src/lang/lang-python.js +1 -1
  80. package/src/lang/lang-sql.ctx +10 -0
  81. package/src/lang/lang-sql.js +1 -1
  82. package/src/lang/lang-typescript.ctx +6 -0
  83. package/src/lang/lang-typescript.js +1 -1
  84. package/src/lang/lang-utils.ctx +5 -0
  85. package/src/lang/lang-utils.js +1 -1
  86. package/src/mcp/mcp-server.ctx +6 -0
  87. package/src/mcp/mcp-server.js +1 -1
  88. package/src/mcp/tool-defs.ctx +2 -0
  89. package/src/mcp/tool-defs.js +1 -1
  90. package/src/mcp/tools.ctx +13 -0
  91. package/src/mcp/tools.js +1 -1
  92. package/src/network/backend-lifecycle.ctx +10 -0
  93. package/src/network/backend-lifecycle.js +1 -1
  94. package/src/network/backend.ctx +5 -0
  95. package/src/network/backend.js +1 -1
  96. package/src/network/local-gateway.ctx +9 -0
  97. package/src/network/local-gateway.js +1 -1
  98. package/src/network/mdns.ctx +6 -0
  99. package/src/network/mdns.js +1 -1
  100. package/src/network/server.ctx +2 -0
  101. package/src/network/server.js +2 -2
  102. package/src/network/web-server.ctx +17 -0
  103. package/src/network/web-server.js +2 -2
  104. package/web/follow-controller.js +94 -25
  105. package/web/panels/dep-graph.js +207 -21
  106. package/project-graph-mcp-2.3.0.tgz +0 -0
  107. package/vendor/symbiote-node/CHANGELOG.md +0 -31
  108. package/vendor/symbiote-node/LICENSE +0 -21
  109. package/vendor/symbiote-node/README.md +0 -206
  110. package/vendor/symbiote-node/canvas/AutoLayout.js +0 -725
  111. package/vendor/symbiote-node/canvas/Breadcrumb/Breadcrumb.css.js +0 -73
  112. package/vendor/symbiote-node/canvas/Breadcrumb/Breadcrumb.js +0 -93
  113. package/vendor/symbiote-node/canvas/Breadcrumb/Breadcrumb.tpl.js +0 -9
  114. package/vendor/symbiote-node/canvas/CanvasConnectionRenderer.js +0 -962
  115. package/vendor/symbiote-node/canvas/ConnectionRenderer.js +0 -1468
  116. package/vendor/symbiote-node/canvas/FlowSimulator.js +0 -323
  117. package/vendor/symbiote-node/canvas/ForceLayout.js +0 -189
  118. package/vendor/symbiote-node/canvas/ForceWorker.js +0 -1325
  119. package/vendor/symbiote-node/canvas/GraphTabs/GraphTabs.css.js +0 -97
  120. package/vendor/symbiote-node/canvas/GraphTabs/GraphTabs.js +0 -176
  121. package/vendor/symbiote-node/canvas/GraphTabs/GraphTabs.tpl.js +0 -12
  122. package/vendor/symbiote-node/canvas/LODManager.js +0 -88
  123. package/vendor/symbiote-node/canvas/Minimap/Minimap.css.js +0 -71
  124. package/vendor/symbiote-node/canvas/Minimap/Minimap.js +0 -207
  125. package/vendor/symbiote-node/canvas/Minimap/Minimap.tpl.js +0 -9
  126. package/vendor/symbiote-node/canvas/NodeCanvas/NodeCanvas.css.js +0 -261
  127. package/vendor/symbiote-node/canvas/NodeCanvas/NodeCanvas.js +0 -1840
  128. package/vendor/symbiote-node/canvas/NodeCanvas/NodeCanvas.tpl.js +0 -22
  129. package/vendor/symbiote-node/canvas/NodeSearch/NodeSearch.css.js +0 -97
  130. package/vendor/symbiote-node/canvas/NodeSearch/NodeSearch.js +0 -132
  131. package/vendor/symbiote-node/canvas/NodeSearch/NodeSearch.tpl.js +0 -21
  132. package/vendor/symbiote-node/canvas/NodeViewManager.js +0 -584
  133. package/vendor/symbiote-node/canvas/PinExpansion.js +0 -131
  134. package/vendor/symbiote-node/canvas/PseudoConnection.js +0 -80
  135. package/vendor/symbiote-node/canvas/SubgraphManager.js +0 -201
  136. package/vendor/symbiote-node/canvas/SubgraphRouter.js +0 -443
  137. package/vendor/symbiote-node/canvas/ViewportActions.js +0 -446
  138. package/vendor/symbiote-node/core/Connection.js +0 -45
  139. package/vendor/symbiote-node/core/Editor.js +0 -451
  140. package/vendor/symbiote-node/core/Frame.js +0 -31
  141. package/vendor/symbiote-node/core/GraphMermaid.js +0 -348
  142. package/vendor/symbiote-node/core/GraphText.js +0 -210
  143. package/vendor/symbiote-node/core/Node.js +0 -143
  144. package/vendor/symbiote-node/core/Portal.js +0 -104
  145. package/vendor/symbiote-node/core/Socket.js +0 -185
  146. package/vendor/symbiote-node/core/SubgraphNode.js +0 -125
  147. package/vendor/symbiote-node/engine/AgentUICommands.js +0 -100
  148. package/vendor/symbiote-node/engine/Executor.js +0 -371
  149. package/vendor/symbiote-node/engine/Graph.js +0 -314
  150. package/vendor/symbiote-node/engine/GraphServer.js +0 -353
  151. package/vendor/symbiote-node/engine/HandlerLoader.js +0 -145
  152. package/vendor/symbiote-node/engine/History.js +0 -83
  153. package/vendor/symbiote-node/engine/Lifecycle.js +0 -118
  154. package/vendor/symbiote-node/engine/Persistence.js +0 -84
  155. package/vendor/symbiote-node/engine/Registry.js +0 -264
  156. package/vendor/symbiote-node/engine/SocketTypes.js +0 -79
  157. package/vendor/symbiote-node/engine/cli.js +0 -404
  158. package/vendor/symbiote-node/engine/index.js +0 -56
  159. package/vendor/symbiote-node/engine/nanoid.js +0 -28
  160. package/vendor/symbiote-node/engine/package.json +0 -26
  161. package/vendor/symbiote-node/engine/packs/ai/beat-detect.handler.js +0 -215
  162. package/vendor/symbiote-node/engine/packs/ai/content-adapt.handler.js +0 -238
  163. package/vendor/symbiote-node/engine/packs/ai/face-detect.handler.js +0 -287
  164. package/vendor/symbiote-node/engine/packs/ai/grok-generate.handler.js +0 -565
  165. package/vendor/symbiote-node/engine/packs/ai/kling-lipsync.handler.js +0 -414
  166. package/vendor/symbiote-node/engine/packs/ai/lesson-generate.handler.js +0 -343
  167. package/vendor/symbiote-node/engine/packs/ai/opencode.handler.js +0 -164
  168. package/vendor/symbiote-node/engine/packs/ai/replicate-lipsync.handler.js +0 -341
  169. package/vendor/symbiote-node/engine/packs/ai/tts.handler.js +0 -241
  170. package/vendor/symbiote-node/engine/packs/ai/whisper.handler.js +0 -191
  171. package/vendor/symbiote-node/engine/packs/data/db-query.handler.js +0 -67
  172. package/vendor/symbiote-node/engine/packs/data/news-accumulate.handler.js +0 -281
  173. package/vendor/symbiote-node/engine/packs/data/personas.handler.js +0 -160
  174. package/vendor/symbiote-node/engine/packs/data/prompt-loader.handler.js +0 -193
  175. package/vendor/symbiote-node/engine/packs/data/roles.handler.js +0 -216
  176. package/vendor/symbiote-node/engine/packs/data/rss-feed.handler.js +0 -244
  177. package/vendor/symbiote-node/engine/packs/debug/inject.handler.js +0 -52
  178. package/vendor/symbiote-node/engine/packs/flow/agent.handler.js +0 -73
  179. package/vendor/symbiote-node/engine/packs/flow/if.handler.js +0 -107
  180. package/vendor/symbiote-node/engine/packs/flow/loop.handler.js +0 -58
  181. package/vendor/symbiote-node/engine/packs/flow/merge.handler.js +0 -60
  182. package/vendor/symbiote-node/engine/packs/flow/retry.handler.js +0 -65
  183. package/vendor/symbiote-node/engine/packs/flow/switch.handler.js +0 -64
  184. package/vendor/symbiote-node/engine/packs/flow/wait-all.handler.js +0 -39
  185. package/vendor/symbiote-node/engine/packs/io/http-request.handler.js +0 -82
  186. package/vendor/symbiote-node/engine/packs/io/read-file.handler.js +0 -60
  187. package/vendor/symbiote-node/engine/packs/io/write-file.handler.js +0 -63
  188. package/vendor/symbiote-node/engine/packs/transform/anchor-match.handler.js +0 -494
  189. package/vendor/symbiote-node/engine/packs/transform/effects-skeleton.handler.js +0 -417
  190. package/vendor/symbiote-node/engine/packs/transform/json-parse.handler.js +0 -43
  191. package/vendor/symbiote-node/engine/packs/transform/lipsync-select.handler.js +0 -339
  192. package/vendor/symbiote-node/engine/packs/transform/riopla-adapt.handler.js +0 -432
  193. package/vendor/symbiote-node/engine/packs/transform/set.handler.js +0 -57
  194. package/vendor/symbiote-node/engine/packs/transform/template-builder.handler.js +0 -134
  195. package/vendor/symbiote-node/engine/packs/transform/template.handler.js +0 -79
  196. package/vendor/symbiote-node/engine/packs/transform/timeline-build.handler.js +0 -399
  197. package/vendor/symbiote-node/engine/packs/util/delay.handler.js +0 -39
  198. package/vendor/symbiote-node/engine/packs/util/log.handler.js +0 -44
  199. package/vendor/symbiote-node/engine/packs/video-pack.js +0 -323
  200. package/vendor/symbiote-node/index.js +0 -103
  201. package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.css.js +0 -361
  202. package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.js +0 -332
  203. package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.tpl.js +0 -96
  204. package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.css.js +0 -104
  205. package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.js +0 -133
  206. package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.tpl.js +0 -33
  207. package/vendor/symbiote-node/interactions/ConnectFlow.js +0 -307
  208. package/vendor/symbiote-node/interactions/Drag.js +0 -102
  209. package/vendor/symbiote-node/interactions/Selector.js +0 -132
  210. package/vendor/symbiote-node/interactions/SnapGrid.js +0 -65
  211. package/vendor/symbiote-node/interactions/Zoom.js +0 -140
  212. package/vendor/symbiote-node/layout/ActionZone/ActionZone.css.js +0 -88
  213. package/vendor/symbiote-node/layout/ActionZone/ActionZone.js +0 -254
  214. package/vendor/symbiote-node/layout/ActionZone/ActionZone.tpl.js +0 -11
  215. package/vendor/symbiote-node/layout/Layout/Layout.css.js +0 -88
  216. package/vendor/symbiote-node/layout/Layout/Layout.js +0 -622
  217. package/vendor/symbiote-node/layout/Layout/Layout.tpl.js +0 -25
  218. package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.css.js +0 -293
  219. package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.js +0 -467
  220. package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.tpl.js +0 -33
  221. package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.css.js +0 -46
  222. package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.js +0 -102
  223. package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.tpl.js +0 -6
  224. package/vendor/symbiote-node/layout/LayoutRouter/LayoutRouter.js +0 -156
  225. package/vendor/symbiote-node/layout/LayoutRouter/routerSync.js +0 -250
  226. package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.css.js +0 -379
  227. package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.js +0 -263
  228. package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.tpl.js +0 -20
  229. package/vendor/symbiote-node/layout/LayoutSidebar/SidebarSection.js +0 -183
  230. package/vendor/symbiote-node/layout/LayoutTree.js +0 -246
  231. package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.css.js +0 -43
  232. package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.js +0 -89
  233. package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.tpl.js +0 -14
  234. package/vendor/symbiote-node/layout/index.js +0 -16
  235. package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.css.js +0 -61
  236. package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.js +0 -79
  237. package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.tpl.js +0 -19
  238. package/vendor/symbiote-node/node/CtrlItem/CtrlItem.css.js +0 -41
  239. package/vendor/symbiote-node/node/CtrlItem/CtrlItem.js +0 -24
  240. package/vendor/symbiote-node/node/CtrlItem/CtrlItem.tpl.js +0 -16
  241. package/vendor/symbiote-node/node/GraphFrame/GraphFrame.css.js +0 -65
  242. package/vendor/symbiote-node/node/GraphFrame/GraphFrame.js +0 -29
  243. package/vendor/symbiote-node/node/GraphFrame/GraphFrame.tpl.js +0 -13
  244. package/vendor/symbiote-node/node/GraphNode/GraphNode.css.js +0 -683
  245. package/vendor/symbiote-node/node/GraphNode/GraphNode.js +0 -92
  246. package/vendor/symbiote-node/node/GraphNode/GraphNode.tpl.js +0 -17
  247. package/vendor/symbiote-node/node/NodeSocket/NodeSocket.js +0 -25
  248. package/vendor/symbiote-node/node/NodeSocket/NodeSocket.tpl.js +0 -7
  249. package/vendor/symbiote-node/node/PortItem/PortItem.css.js +0 -90
  250. package/vendor/symbiote-node/node/PortItem/PortItem.js +0 -87
  251. package/vendor/symbiote-node/node/PortItem/PortItem.tpl.js +0 -10
  252. package/vendor/symbiote-node/package.json +0 -59
  253. package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.css.js +0 -143
  254. package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.js +0 -131
  255. package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.tpl.js +0 -16
  256. package/vendor/symbiote-node/plugins/History.js +0 -384
  257. package/vendor/symbiote-node/plugins/Readonly.js +0 -59
  258. package/vendor/symbiote-node/shapes/CircleShape.js +0 -80
  259. package/vendor/symbiote-node/shapes/CommentShape.js +0 -35
  260. package/vendor/symbiote-node/shapes/DiamondShape.js +0 -115
  261. package/vendor/symbiote-node/shapes/NodeShape.js +0 -80
  262. package/vendor/symbiote-node/shapes/PillShape.js +0 -91
  263. package/vendor/symbiote-node/shapes/RectShape.js +0 -72
  264. package/vendor/symbiote-node/shapes/SVGShape.js +0 -494
  265. package/vendor/symbiote-node/shapes/index.js +0 -53
  266. package/vendor/symbiote-node/themes/Palette.js +0 -32
  267. package/vendor/symbiote-node/themes/Skin.js +0 -113
  268. package/vendor/symbiote-node/themes/Theme.js +0 -84
  269. package/vendor/symbiote-node/themes/carbon.js +0 -137
  270. package/vendor/symbiote-node/themes/dark.js +0 -137
  271. package/vendor/symbiote-node/themes/ebook.js +0 -138
  272. package/vendor/symbiote-node/themes/grey.js +0 -137
  273. package/vendor/symbiote-node/themes/light.js +0 -137
  274. package/vendor/symbiote-node/themes/neon.js +0 -138
  275. package/vendor/symbiote-node/themes/pcb.js +0 -273
  276. package/vendor/symbiote-node/themes/synthwave.js +0 -137
  277. package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.css.js +0 -86
  278. package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.js +0 -128
  279. package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.tpl.js +0 -29
@@ -1,341 +0,0 @@
1
- /**
2
- * ai/replicate-lipsync — Replicate API lip-sync via Kling model
3
- *
4
- * Alternative lipsync provider using Replicate API.
5
- * Model: kwaivgi/kling-lip-sync (~$0.014/second of output)
6
- * Simpler than direct Kling API — just video URL + audio URL → result.
7
- *
8
- * Supports:
9
- * - Single segment processing
10
- * - Batch processing with worker pool
11
- * - Tunnel validation (cloudflared/ngrok)
12
- *
13
- * Ported from Mr-Computer/modules/ai-music-video/src/services/replicate-lipsync.js
14
- *
15
- * @module agi-graph/packs/ai/replicate-lipsync
16
- */
17
-
18
- import { readFile, writeFile, mkdir } from 'fs/promises';
19
- import { existsSync } from 'fs';
20
- import { execSync } from 'child_process';
21
- import path from 'path';
22
-
23
- export default {
24
- type: 'ai/replicate-lipsync',
25
- category: 'ai',
26
- icon: 'mic',
27
-
28
- driver: {
29
- description: 'Replicate API lipsync via kwaivgi/kling-lip-sync model',
30
- inputs: [
31
- { name: 'videoUrl', type: 'string' },
32
- { name: 'audioPath', type: 'string' },
33
- ],
34
- outputs: [
35
- { name: 'result', type: 'any' },
36
- { name: 'error', type: 'string' },
37
- ],
38
- params: {
39
- operation: { type: 'string', default: 'process', description: 'Operation: process | batch | validate-tunnel' },
40
- replicateToken: { type: 'string', default: '', description: 'Replicate API token' },
41
- publicBaseUrl: { type: 'string', default: '', description: 'Public URL for file server' },
42
- outputDir: { type: 'string', default: '/tmp/replicate-lipsync', description: 'Output directory' },
43
- segmentId: { type: 'string', default: '', description: 'Segment identifier' },
44
- startTime: { type: 'number', default: 0, description: 'Audio start time (seconds)' },
45
- endTime: { type: 'number', default: 0, description: 'Audio end time (seconds)' },
46
- maxWaitMs: { type: 'int', default: 300000, description: 'Max poll wait time (ms)' },
47
- // Batch params
48
- segments: { type: 'any', default: null, description: 'Segments array' },
49
- videoMap: { type: 'any', default: null, description: 'Map of promptId → videoUrl' },
50
- concurrency: { type: 'int', default: 3, description: 'Max concurrent tasks' },
51
- },
52
- },
53
-
54
- lifecycle: {
55
- validate: (inputs, params) => {
56
- if (params.operation === 'validate-tunnel') return !!params.publicBaseUrl;
57
- if (!params.replicateToken) return false;
58
- if (params.operation === 'process' && (!inputs.videoUrl || !inputs.audioPath)) return false;
59
- if (params.operation === 'batch' && (!params.segments || !inputs.audioPath)) return false;
60
- return true;
61
- },
62
-
63
- cacheKey: (inputs, params) => {
64
- return `replicate:${params.operation}:${params.segmentId || ''}`;
65
- },
66
-
67
- execute: async (inputs, params) => {
68
- try {
69
- const op = params.operation;
70
-
71
- if (op === 'validate-tunnel') {
72
- const valid = await validateTunnel(params.publicBaseUrl);
73
- return { result: { valid }, error: null };
74
- }
75
-
76
- if (op === 'process') {
77
- const result = await processSegment(inputs, params);
78
- return { result, error: null };
79
- }
80
-
81
- if (op === 'batch') {
82
- const results = await processBatch(inputs, params);
83
- return { result: { processed: results.size, results: Object.fromEntries(results) }, error: null };
84
- }
85
-
86
- return { result: null, error: `Unknown operation: ${op}` };
87
- } catch (err) {
88
- return { result: null, error: err.message };
89
- }
90
- },
91
- },
92
- };
93
-
94
- // --- Replicate API ---
95
-
96
- /**
97
- * Create prediction on Replicate
98
- * @param {string} videoUrl
99
- * @param {string} audioUrl - Public URL or data URI
100
- * @param {string} token
101
- * @returns {Promise<Object>}
102
- */
103
- async function createPrediction(videoUrl, audioUrl, token) {
104
- const response = await fetch('https://api.replicate.com/v1/predictions', {
105
- method: 'POST',
106
- headers: {
107
- 'Authorization': `Bearer ${token}`,
108
- 'Content-Type': 'application/json',
109
- 'Prefer': 'wait',
110
- },
111
- body: JSON.stringify({
112
- version: 'kwaivgi/kling-lip-sync',
113
- input: {
114
- video: videoUrl,
115
- audio: audioUrl,
116
- },
117
- }),
118
- });
119
-
120
- if (!response.ok) {
121
- const error = await response.text();
122
- throw new Error(`Replicate API error ${response.status}: ${error}`);
123
- }
124
-
125
- return await response.json();
126
- }
127
-
128
- /**
129
- * Poll for prediction completion
130
- * @param {string} predictionId
131
- * @param {string} token
132
- * @param {number} maxWaitMs
133
- * @returns {Promise<Object>}
134
- */
135
- async function pollPrediction(predictionId, token, maxWaitMs = 300000) {
136
- const startTime = Date.now();
137
- const pollInterval = 5000;
138
-
139
- while (Date.now() - startTime < maxWaitMs) {
140
- const response = await fetch(`https://api.replicate.com/v1/predictions/${predictionId}`, {
141
- headers: { 'Authorization': `Bearer ${token}` },
142
- });
143
-
144
- if (!response.ok) {
145
- throw new Error(`Replicate poll error: ${response.status}`);
146
- }
147
-
148
- const prediction = await response.json();
149
-
150
- if (prediction.status === 'succeeded') {
151
- return prediction;
152
- }
153
- if (prediction.status === 'failed' || prediction.status === 'canceled') {
154
- throw new Error(`Replicate prediction ${prediction.status}: ${prediction.error || 'Unknown'}`);
155
- }
156
-
157
- await new Promise(resolve => setTimeout(resolve, pollInterval));
158
- }
159
-
160
- throw new Error(`Replicate prediction timed out after ${maxWaitMs / 1000}s`);
161
- }
162
-
163
- /**
164
- * Download result video
165
- * @param {string} videoUrl
166
- * @param {string} outputPath
167
- * @returns {Promise<string>}
168
- */
169
- async function downloadResult(videoUrl, outputPath) {
170
- const response = await fetch(videoUrl);
171
- if (!response.ok) {
172
- throw new Error(`Failed to download: ${response.status}`);
173
- }
174
-
175
- const buffer = await response.arrayBuffer();
176
- await writeFile(outputPath, Buffer.from(buffer));
177
- return outputPath;
178
- }
179
-
180
- // --- Utilities ---
181
-
182
- /**
183
- * Convert file to data URI
184
- * @param {string} filePath
185
- * @returns {Promise<string>}
186
- */
187
- async function fileToDataUri(filePath) {
188
- const buffer = await readFile(path.resolve(filePath));
189
- const ext = path.extname(filePath).toLowerCase();
190
- const mimeTypes = {
191
- '.mp3': 'audio/mpeg',
192
- '.wav': 'audio/wav',
193
- '.mp4': 'video/mp4',
194
- '.webm': 'video/webm',
195
- };
196
- const mime = mimeTypes[ext] || 'application/octet-stream';
197
- return `data:${mime};base64,${buffer.toString('base64')}`;
198
- }
199
-
200
- /**
201
- * Extract audio clip using FFmpeg
202
- * @param {string} audioPath
203
- * @param {number} startTime
204
- * @param {number} endTime
205
- * @param {string} outputPath
206
- * @returns {string}
207
- */
208
- function extractAudioClip(audioPath, startTime, endTime, outputPath) {
209
- if (existsSync(outputPath)) return outputPath;
210
-
211
- const duration = endTime - startTime;
212
- const cmd = `ffmpeg -y -i "${path.resolve(audioPath)}" -ss ${startTime.toFixed(3)} -t ${duration.toFixed(3)} ` +
213
- `-c:a libmp3lame -q:a 2 "${outputPath}" 2>/dev/null`;
214
-
215
- execSync(cmd, { stdio: 'pipe' });
216
- return outputPath;
217
- }
218
-
219
- /**
220
- * Get public URL for local file
221
- * @param {string} filePath
222
- * @param {string} publicBaseUrl
223
- * @returns {string}
224
- */
225
- function getPublicUrl(filePath, publicBaseUrl) {
226
- const absPath = path.resolve(filePath);
227
- return `${publicBaseUrl}/${encodeURIComponent(absPath)}`;
228
- }
229
-
230
- /**
231
- * Validate tunnel accessibility
232
- * @param {string} publicBaseUrl
233
- * @returns {Promise<boolean>}
234
- */
235
- async function validateTunnel(publicBaseUrl) {
236
- try {
237
- const controller = new AbortController();
238
- const timeout = setTimeout(() => controller.abort(), 10000);
239
- const response = await fetch(publicBaseUrl, { signal: controller.signal });
240
- clearTimeout(timeout);
241
- return response.status < 500;
242
- } catch {
243
- return false;
244
- }
245
- }
246
-
247
- // --- Processing pipeline ---
248
-
249
- /**
250
- * Process single segment
251
- * @param {Object} inputs
252
- * @param {Object} params
253
- * @returns {Promise<Object>}
254
- */
255
- async function processSegment(inputs, params) {
256
- const { segmentId, startTime, endTime, outputDir, replicateToken, publicBaseUrl } = params;
257
-
258
- const lipsyncDir = path.join(outputDir, 'lipsync-videos');
259
- const clipsDir = path.join(outputDir, 'audio-clips');
260
- await mkdir(lipsyncDir, { recursive: true });
261
- await mkdir(clipsDir, { recursive: true });
262
-
263
- const outputPath = path.join(lipsyncDir, `${segmentId}.mp4`);
264
- if (existsSync(outputPath)) {
265
- return { videoPath: outputPath, cached: true };
266
- }
267
-
268
- // Extract audio clip
269
- const clipPath = path.join(clipsDir, `${segmentId}.mp3`);
270
- extractAudioClip(inputs.audioPath, startTime, endTime, clipPath);
271
-
272
- // Get audio data URI or public URL
273
- let audioUrl;
274
- if (publicBaseUrl) {
275
- audioUrl = getPublicUrl(clipPath, publicBaseUrl);
276
- } else {
277
- audioUrl = await fileToDataUri(clipPath);
278
- }
279
-
280
- // Create prediction
281
- const prediction = await createPrediction(inputs.videoUrl, audioUrl, replicateToken);
282
-
283
- // Poll if not already complete
284
- let result = prediction;
285
- if (prediction.status !== 'succeeded') {
286
- result = await pollPrediction(prediction.id, replicateToken, params.maxWaitMs);
287
- }
288
-
289
- // Download result
290
- const resultUrl = result.output;
291
- if (!resultUrl) {
292
- throw new Error('No output URL in prediction result');
293
- }
294
-
295
- await downloadResult(resultUrl, outputPath);
296
- return { videoPath: outputPath, predictionId: prediction.id, cached: false };
297
- }
298
-
299
- /**
300
- * Process batch with worker pool
301
- * @param {Object} inputs
302
- * @param {Object} params
303
- * @returns {Promise<Map>}
304
- */
305
- async function processBatch(inputs, params) {
306
- const segments = params.segments;
307
- const videoMap = params.videoMap || {};
308
- const concurrency = params.concurrency;
309
- const results = new Map();
310
- const queue = [...segments];
311
-
312
- const workers = Array(Math.min(concurrency, queue.length)).fill(null).map(async () => {
313
- while (queue.length > 0) {
314
- const segment = queue.shift();
315
- if (!segment) break;
316
-
317
- const videoUrl = videoMap[segment.promptId];
318
- if (!videoUrl) continue;
319
-
320
- try {
321
- const segParams = {
322
- ...params,
323
- segmentId: segment.promptId,
324
- startTime: segment.start,
325
- endTime: segment.end,
326
- };
327
-
328
- const result = await processSegment(
329
- { videoUrl, audioPath: inputs.audioPath },
330
- segParams,
331
- );
332
- results.set(segment.promptId, result.videoPath);
333
- } catch (error) {
334
- console.error(`[Replicate] Failed: ${segment.promptId} - ${error.message}`);
335
- }
336
- }
337
- });
338
-
339
- await Promise.all(workers);
340
- return results;
341
- }
@@ -1,241 +0,0 @@
1
- /**
2
- * ai/tts — Text-to-Speech via Qwen3-TTS
3
- *
4
- * Two modes:
5
- * - SSH: batch script on remote server (mr-agent.rnd-pro.com)
6
- * - HTTP: POST to Qwen3 TTS HTTP endpoint
7
- *
8
- * Supports:
9
- * - Built-in speakers: ryan, vivian, aiden, dylan, eric, serena, sohee, chelsie, etc.
10
- * - Voice cloning via ref_audio (reference audio sample)
11
- * - Language: es (Spanish/Rioplatense), ru (Russian), en (English)
12
- *
13
- * Config from Mr-Computer/automations/argentine-spanish-bot:
14
- * TTS_SERVER_URL: http://localhost:5008
15
- * TTS_VENV_PATH: /home/mr-agent/automations/argentine-spanish-bot/venv
16
- * Batch script: utils/generate_qwen3tts_batch.py
17
- *
18
- * @module agi-graph/packs/ai/tts
19
- */
20
-
21
- import { execSync } from 'child_process';
22
- import { promises as fs } from 'fs';
23
- import path from 'path';
24
- import os from 'os';
25
-
26
- export default {
27
- type: 'ai/tts',
28
- category: 'ai',
29
- icon: 'record_voice_over',
30
-
31
- driver: {
32
- description: 'Text-to-Speech via Qwen3-TTS (SSH batch or HTTP)',
33
- inputs: [
34
- { name: 'text', type: 'string' },
35
- ],
36
- outputs: [
37
- { name: 'audioPath', type: 'string' },
38
- { name: 'error', type: 'string' },
39
- ],
40
- params: {
41
- mode: { type: 'string', default: 'http', description: 'ssh | http' },
42
- language: { type: 'string', default: 'es', description: 'Language: es, ru, en' },
43
- speaker: { type: 'string', default: 'vivian', description: 'Built-in Qwen3 speaker ID' },
44
- refAudio: { type: 'string', default: '', description: 'Path to voice reference audio (clone mode)' },
45
- outputDir: { type: 'string', default: '', description: 'Output directory for generated audio' },
46
- outputFormat: { type: 'string', default: 'wav', description: 'wav | mp3' },
47
- exaggeration: { type: 'number', default: 0, description: 'Voice exaggeration (0-1)' },
48
- cfg: { type: 'number', default: 0.1, description: 'Classifier-free guidance (0-1)' },
49
- // SSH params
50
- remoteHost: { type: 'string', default: 'mr-agent@mr-agent.rnd-pro.com', description: 'SSH host' },
51
- remotePath: { type: 'string', default: '/home/mr-agent/automations/argentine-spanish-bot', description: 'Remote project path' },
52
- remoteVenv: { type: 'string', default: '/home/mr-agent/automations/argentine-spanish-bot/venv', description: 'Remote Python venv path' },
53
- device: { type: 'string', default: 'cuda', description: 'cuda | cpu' },
54
- // HTTP params
55
- endpoint: { type: 'string', default: 'http://localhost:5008', description: 'TTS HTTP endpoint' },
56
- timeout: { type: 'int', default: 120000, description: 'Max wait time (ms)' },
57
- },
58
- },
59
-
60
- lifecycle: {
61
- validate: (inputs) => {
62
- if (!inputs.text) return false;
63
- return true;
64
- },
65
-
66
- cacheKey: (inputs, params) =>
67
- `tts:${params.mode}:${params.speaker}:${params.language}:${inputs.text}`,
68
-
69
- execute: async (inputs, params) => {
70
- const { text } = inputs;
71
- const mode = params.mode || 'http';
72
-
73
- if (mode === 'ssh') {
74
- return executeSSH(text, params);
75
- }
76
- return executeHTTP(text, params);
77
- },
78
- },
79
- };
80
-
81
- /**
82
- * Qwen3-TTS built-in speaker IDs
83
- * @type {Set<string>}
84
- */
85
- const SPEAKERS = new Set([
86
- 'aiden', 'dylan', 'eric', 'ono_anna', 'ryan',
87
- 'serena', 'sohee', 'uncle_fu', 'vivian', 'chelsie',
88
- ]);
89
-
90
- /**
91
- * SSH mode: write batch JSON → scp → remote python exec → scp result back
92
- * @param {string} text - Text to synthesize
93
- * @param {Object} params - Node params
94
- * @returns {Promise<Object>}
95
- */
96
- async function executeSSH(text, params) {
97
- const host = params.remoteHost || process.env.WHISPER_REMOTE_HOST || 'mr-agent@mr-agent.rnd-pro.com';
98
- const remotePath = params.remotePath || process.env.WHISPER_REMOTE_PATH || '/home/mr-agent/automations/argentine-spanish-bot';
99
- const venv = params.remoteVenv || process.env.TTS_VENV_PATH || `${remotePath}/venv`;
100
- const device = params.device || process.env.PODCAST_TTS_DEVICE || 'cuda';
101
-
102
- const outDir = params.outputDir || path.join(os.tmpdir(), 'agi-graph-tts');
103
- const taskId = `tts_${Date.now()}`;
104
- const localWav = path.join(outDir, `${taskId}.wav`);
105
- const remoteTmpDir = '/tmp/agi-graph-tts';
106
-
107
- try {
108
- await fs.mkdir(outDir, { recursive: true });
109
-
110
- // Build batch task
111
- const batchTask = [{
112
- id: taskId,
113
- text,
114
- lang: params.language || 'es',
115
- prompt: params.refAudio || null,
116
- out: `${remoteTmpDir}/${taskId}.wav`,
117
- exaggeration: params.exaggeration ?? 0,
118
- cfg: params.cfg ?? 0.1,
119
- }];
120
-
121
- // If using built-in speaker (no refAudio), add speaker param
122
- if (!params.refAudio && SPEAKERS.has(params.speaker)) {
123
- batchTask[0].speaker = params.speaker;
124
- }
125
-
126
- // Write local batch file
127
- const batchFile = path.join(outDir, `${taskId}_batch.json`);
128
- await fs.writeFile(batchFile, JSON.stringify(batchTask, null, 2));
129
-
130
- // Ensure remote dir + upload batch
131
- execSync(`ssh ${host} "mkdir -p ${remoteTmpDir}"`, {
132
- encoding: 'utf-8', stdio: 'pipe', timeout: 10000,
133
- });
134
-
135
- const remoteBatch = `${remoteTmpDir}/${taskId}_batch.json`;
136
- execSync(`scp "${batchFile}" "${host}:${remoteBatch}"`, {
137
- encoding: 'utf-8', stdio: 'pipe', timeout: 30000,
138
- });
139
-
140
- try {
141
- // Run batch script
142
- const pythonCmd = `${venv}/bin/python`;
143
- const scriptPath = `${remotePath}/utils/generate_qwen3tts_batch.py`;
144
- const cmd = `source "${venv}/bin/activate" && "${pythonCmd}" "${scriptPath}" --batch "${remoteBatch}" --device "${device}"`;
145
-
146
- execSync(`ssh ${host} '${cmd}'`, {
147
- encoding: 'utf-8',
148
- maxBuffer: 50 * 1024 * 1024,
149
- timeout: params.timeout || 120000,
150
- });
151
-
152
- // Download result
153
- const remoteOut = `${remoteTmpDir}/${taskId}.wav`;
154
- execSync(`scp "${host}:${remoteOut}" "${localWav}"`, {
155
- encoding: 'utf-8', stdio: 'pipe', timeout: 30000,
156
- });
157
-
158
- // Cleanup batch + remote output
159
- await fs.unlink(batchFile).catch(() => { });
160
- execSync(`ssh ${host} "rm -f ${remoteBatch} ${remoteOut}"`, {
161
- encoding: 'utf-8', stdio: 'pipe', timeout: 5000,
162
- }).toString();
163
-
164
- return { audioPath: localWav, error: null };
165
-
166
- } catch (err) {
167
- await fs.unlink(batchFile).catch(() => { });
168
- return { audioPath: null, error: err.message };
169
- }
170
-
171
- } catch (err) {
172
- return { audioPath: null, error: err.message };
173
- }
174
- }
175
-
176
- /**
177
- * HTTP mode: POST to Qwen3 TTS endpoint
178
- * @param {string} text - Text to synthesize
179
- * @param {Object} params - Node params
180
- * @returns {Promise<Object>}
181
- */
182
- async function executeHTTP(text, params) {
183
- const endpoint = params.endpoint || process.env.TTS_SERVER_URL || 'http://localhost:5008';
184
- const outDir = params.outputDir || path.join(os.tmpdir(), 'agi-graph-tts');
185
- const taskId = `tts_${Date.now()}`;
186
- const outputPath = path.join(outDir, `${taskId}.wav`);
187
-
188
- try {
189
- await fs.mkdir(outDir, { recursive: true });
190
-
191
- const body = {
192
- text,
193
- language: params.language || 'es',
194
- speaker: params.speaker || 'vivian',
195
- exaggeration: params.exaggeration ?? 0,
196
- cfg: params.cfg ?? 0.1,
197
- };
198
-
199
- // Add ref_audio for voice cloning
200
- if (params.refAudio) {
201
- const refBuffer = await fs.readFile(params.refAudio);
202
- body.ref_audio = refBuffer.toString('base64');
203
- }
204
-
205
- const response = await fetch(`${endpoint}/synthesize`, {
206
- method: 'POST',
207
- headers: { 'Content-Type': 'application/json' },
208
- body: JSON.stringify(body),
209
- signal: AbortSignal.timeout(params.timeout || 120000),
210
- });
211
-
212
- if (!response.ok) {
213
- return { audioPath: null, error: `TTS API error: ${response.status}` };
214
- }
215
-
216
- // Response is audio binary
217
- const contentType = response.headers.get('content-type') || '';
218
-
219
- if (contentType.includes('audio') || contentType.includes('octet-stream')) {
220
- const buffer = Buffer.from(await response.arrayBuffer());
221
- await fs.writeFile(outputPath, buffer);
222
- return { audioPath: outputPath, error: null };
223
- }
224
-
225
- // JSON response with file path
226
- const result = await response.json();
227
- if (result.audio_path) {
228
- return { audioPath: result.audio_path, error: null };
229
- }
230
- if (result.audio) {
231
- const buffer = Buffer.from(result.audio, 'base64');
232
- await fs.writeFile(outputPath, buffer);
233
- return { audioPath: outputPath, error: null };
234
- }
235
-
236
- return { audioPath: null, error: 'Unexpected TTS response format' };
237
-
238
- } catch (err) {
239
- return { audioPath: null, error: err.message };
240
- }
241
- }