project-graph-mcp 2.3.2 → 2.4.1
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.
- package/package.json +3 -2
- package/src/analysis/analysis-cache.ctx +9 -0
- package/src/analysis/analysis-cache.js +1 -1
- package/src/analysis/complexity.ctx +6 -0
- package/src/analysis/complexity.js +1 -1
- package/src/analysis/custom-rules.ctx +14 -0
- package/src/analysis/custom-rules.js +1 -1
- package/src/analysis/db-analysis.ctx +7 -0
- package/src/analysis/db-analysis.js +1 -1
- package/src/analysis/dead-code.ctx +6 -0
- package/src/analysis/dead-code.js +1 -1
- package/src/analysis/full-analysis.ctx +9 -0
- package/src/analysis/full-analysis.js +1 -1
- package/src/analysis/jsdoc-checker.ctx +10 -0
- package/src/analysis/jsdoc-checker.js +1 -1
- package/src/analysis/jsdoc-generator.ctx +9 -0
- package/src/analysis/jsdoc-generator.js +1 -1
- package/src/analysis/large-files.ctx +6 -0
- package/src/analysis/large-files.js +1 -1
- package/src/analysis/outdated-patterns.ctx +7 -0
- package/src/analysis/outdated-patterns.js +1 -1
- package/src/analysis/similar-functions.ctx +6 -0
- package/src/analysis/similar-functions.js +1 -1
- package/src/analysis/test-annotations.ctx +11 -0
- package/src/analysis/test-annotations.js +1 -1
- package/src/analysis/type-checker.ctx +6 -0
- package/src/analysis/type-checker.js +1 -1
- package/src/analysis/undocumented.ctx +8 -0
- package/src/analysis/undocumented.js +1 -1
- package/src/cli/cli-handlers.ctx +7 -0
- package/src/cli/cli-handlers.js +1 -1
- package/src/cli/cli.ctx +6 -0
- package/src/cli/cli.js +1 -1
- package/src/compact/ai-context.ctx +6 -0
- package/src/compact/ai-context.js +1 -1
- package/src/compact/compact-migrate.ctx +8 -0
- package/src/compact/compact-migrate.js +1 -1
- package/src/compact/compact.ctx +11 -0
- package/src/compact/compact.js +1 -1
- package/src/compact/compress.ctx +7 -0
- package/src/compact/compress.js +1 -1
- package/src/compact/ctx-resolver.ctx +2 -0
- package/src/compact/ctx-resolver.js +1 -1
- package/src/compact/ctx-to-jsdoc.ctx +11 -0
- package/src/compact/ctx-to-jsdoc.js +1 -1
- package/src/compact/doc-dialect.ctx +11 -0
- package/src/compact/doc-dialect.js +2 -2
- package/src/compact/expand.ctx +14 -0
- package/src/compact/expand.js +1 -1
- package/src/compact/framework-references.ctx +7 -0
- package/src/compact/framework-references.js +1 -1
- package/src/compact/instructions.ctx +6 -0
- package/src/compact/instructions.js +1 -1
- package/src/compact/jsdoc-builder.ctx +4 -0
- package/src/compact/jsdoc-builder.js +1 -1
- package/src/compact/mode-config.ctx +8 -0
- package/src/compact/mode-config.js +1 -1
- package/src/compact/split-declarations.ctx +6 -0
- package/src/compact/split-declarations.js +1 -1
- package/src/compact/validate-pipeline.ctx +12 -0
- package/src/compact/validate-pipeline.js +1 -1
- package/src/core/event-bus.ctx +9 -0
- package/src/core/event-bus.js +1 -1
- package/src/core/file-walker.ctx +1 -0
- package/src/core/file-walker.js +1 -1
- package/src/core/filters.ctx +12 -0
- package/src/core/filters.js +1 -1
- package/src/core/graph-builder.ctx +7 -0
- package/src/core/graph-builder.js +1 -1
- package/src/core/parser.ctx +12 -0
- package/src/core/parser.js +1 -1
- package/src/core/utils.ctx +1 -0
- package/src/core/utils.js +1 -1
- package/src/core/workspace.ctx +7 -0
- package/src/core/workspace.js +1 -1
- package/src/lang/lang-go.ctx +8 -0
- package/src/lang/lang-go.js +1 -1
- package/src/lang/lang-python.ctx +5 -0
- package/src/lang/lang-python.js +1 -1
- package/src/lang/lang-sql.ctx +10 -0
- package/src/lang/lang-sql.js +1 -1
- package/src/lang/lang-typescript.ctx +6 -0
- package/src/lang/lang-typescript.js +1 -1
- package/src/lang/lang-utils.ctx +5 -0
- package/src/lang/lang-utils.js +1 -1
- package/src/mcp/mcp-server.ctx +6 -0
- package/src/mcp/mcp-server.js +1 -1
- package/src/mcp/tool-defs.ctx +2 -0
- package/src/mcp/tool-defs.js +1 -1
- package/src/mcp/tools.ctx +13 -0
- package/src/mcp/tools.js +1 -1
- package/src/network/backend-lifecycle.ctx +10 -0
- package/src/network/backend-lifecycle.js +1 -1
- package/src/network/backend.ctx +5 -0
- package/src/network/backend.js +1 -1
- package/src/network/local-gateway.ctx +9 -0
- package/src/network/local-gateway.js +1 -1
- package/src/network/mdns.ctx +6 -0
- package/src/network/mdns.js +1 -1
- package/src/network/server.ctx +2 -0
- package/src/network/server.js +2 -2
- package/src/network/web-server.ctx +17 -0
- package/src/network/web-server.js +2 -2
- package/web/follow-controller.js +94 -25
- package/web/panels/dep-graph.js +207 -21
- package/project-graph-mcp-2.3.0.tgz +0 -0
- package/vendor/symbiote-node/CHANGELOG.md +0 -31
- package/vendor/symbiote-node/LICENSE +0 -21
- package/vendor/symbiote-node/README.md +0 -206
- package/vendor/symbiote-node/canvas/AutoLayout.js +0 -725
- package/vendor/symbiote-node/canvas/Breadcrumb/Breadcrumb.css.js +0 -73
- package/vendor/symbiote-node/canvas/Breadcrumb/Breadcrumb.js +0 -93
- package/vendor/symbiote-node/canvas/Breadcrumb/Breadcrumb.tpl.js +0 -9
- package/vendor/symbiote-node/canvas/CanvasConnectionRenderer.js +0 -962
- package/vendor/symbiote-node/canvas/ConnectionRenderer.js +0 -1468
- package/vendor/symbiote-node/canvas/FlowSimulator.js +0 -323
- package/vendor/symbiote-node/canvas/ForceLayout.js +0 -189
- package/vendor/symbiote-node/canvas/ForceWorker.js +0 -1325
- package/vendor/symbiote-node/canvas/GraphTabs/GraphTabs.css.js +0 -97
- package/vendor/symbiote-node/canvas/GraphTabs/GraphTabs.js +0 -176
- package/vendor/symbiote-node/canvas/GraphTabs/GraphTabs.tpl.js +0 -12
- package/vendor/symbiote-node/canvas/LODManager.js +0 -88
- package/vendor/symbiote-node/canvas/Minimap/Minimap.css.js +0 -71
- package/vendor/symbiote-node/canvas/Minimap/Minimap.js +0 -207
- package/vendor/symbiote-node/canvas/Minimap/Minimap.tpl.js +0 -9
- package/vendor/symbiote-node/canvas/NodeCanvas/NodeCanvas.css.js +0 -261
- package/vendor/symbiote-node/canvas/NodeCanvas/NodeCanvas.js +0 -1840
- package/vendor/symbiote-node/canvas/NodeCanvas/NodeCanvas.tpl.js +0 -22
- package/vendor/symbiote-node/canvas/NodeSearch/NodeSearch.css.js +0 -97
- package/vendor/symbiote-node/canvas/NodeSearch/NodeSearch.js +0 -132
- package/vendor/symbiote-node/canvas/NodeSearch/NodeSearch.tpl.js +0 -21
- package/vendor/symbiote-node/canvas/NodeViewManager.js +0 -584
- package/vendor/symbiote-node/canvas/PinExpansion.js +0 -131
- package/vendor/symbiote-node/canvas/PseudoConnection.js +0 -80
- package/vendor/symbiote-node/canvas/SubgraphManager.js +0 -201
- package/vendor/symbiote-node/canvas/SubgraphRouter.js +0 -443
- package/vendor/symbiote-node/canvas/ViewportActions.js +0 -446
- package/vendor/symbiote-node/core/Connection.js +0 -45
- package/vendor/symbiote-node/core/Editor.js +0 -451
- package/vendor/symbiote-node/core/Frame.js +0 -31
- package/vendor/symbiote-node/core/GraphMermaid.js +0 -348
- package/vendor/symbiote-node/core/GraphText.js +0 -210
- package/vendor/symbiote-node/core/Node.js +0 -143
- package/vendor/symbiote-node/core/Portal.js +0 -104
- package/vendor/symbiote-node/core/Socket.js +0 -185
- package/vendor/symbiote-node/core/SubgraphNode.js +0 -125
- package/vendor/symbiote-node/engine/AgentUICommands.js +0 -100
- package/vendor/symbiote-node/engine/Executor.js +0 -371
- package/vendor/symbiote-node/engine/Graph.js +0 -314
- package/vendor/symbiote-node/engine/GraphServer.js +0 -353
- package/vendor/symbiote-node/engine/HandlerLoader.js +0 -145
- package/vendor/symbiote-node/engine/History.js +0 -83
- package/vendor/symbiote-node/engine/Lifecycle.js +0 -118
- package/vendor/symbiote-node/engine/Persistence.js +0 -84
- package/vendor/symbiote-node/engine/Registry.js +0 -264
- package/vendor/symbiote-node/engine/SocketTypes.js +0 -79
- package/vendor/symbiote-node/engine/cli.js +0 -404
- package/vendor/symbiote-node/engine/index.js +0 -56
- package/vendor/symbiote-node/engine/nanoid.js +0 -28
- package/vendor/symbiote-node/engine/package.json +0 -26
- package/vendor/symbiote-node/engine/packs/ai/beat-detect.handler.js +0 -215
- package/vendor/symbiote-node/engine/packs/ai/content-adapt.handler.js +0 -238
- package/vendor/symbiote-node/engine/packs/ai/face-detect.handler.js +0 -287
- package/vendor/symbiote-node/engine/packs/ai/grok-generate.handler.js +0 -565
- package/vendor/symbiote-node/engine/packs/ai/kling-lipsync.handler.js +0 -414
- package/vendor/symbiote-node/engine/packs/ai/lesson-generate.handler.js +0 -343
- package/vendor/symbiote-node/engine/packs/ai/opencode.handler.js +0 -164
- package/vendor/symbiote-node/engine/packs/ai/replicate-lipsync.handler.js +0 -341
- package/vendor/symbiote-node/engine/packs/ai/tts.handler.js +0 -241
- package/vendor/symbiote-node/engine/packs/ai/whisper.handler.js +0 -191
- package/vendor/symbiote-node/engine/packs/data/db-query.handler.js +0 -67
- package/vendor/symbiote-node/engine/packs/data/news-accumulate.handler.js +0 -281
- package/vendor/symbiote-node/engine/packs/data/personas.handler.js +0 -160
- package/vendor/symbiote-node/engine/packs/data/prompt-loader.handler.js +0 -193
- package/vendor/symbiote-node/engine/packs/data/roles.handler.js +0 -216
- package/vendor/symbiote-node/engine/packs/data/rss-feed.handler.js +0 -244
- package/vendor/symbiote-node/engine/packs/debug/inject.handler.js +0 -52
- package/vendor/symbiote-node/engine/packs/flow/agent.handler.js +0 -73
- package/vendor/symbiote-node/engine/packs/flow/if.handler.js +0 -107
- package/vendor/symbiote-node/engine/packs/flow/loop.handler.js +0 -58
- package/vendor/symbiote-node/engine/packs/flow/merge.handler.js +0 -60
- package/vendor/symbiote-node/engine/packs/flow/retry.handler.js +0 -65
- package/vendor/symbiote-node/engine/packs/flow/switch.handler.js +0 -64
- package/vendor/symbiote-node/engine/packs/flow/wait-all.handler.js +0 -39
- package/vendor/symbiote-node/engine/packs/io/http-request.handler.js +0 -82
- package/vendor/symbiote-node/engine/packs/io/read-file.handler.js +0 -60
- package/vendor/symbiote-node/engine/packs/io/write-file.handler.js +0 -63
- package/vendor/symbiote-node/engine/packs/transform/anchor-match.handler.js +0 -494
- package/vendor/symbiote-node/engine/packs/transform/effects-skeleton.handler.js +0 -417
- package/vendor/symbiote-node/engine/packs/transform/json-parse.handler.js +0 -43
- package/vendor/symbiote-node/engine/packs/transform/lipsync-select.handler.js +0 -339
- package/vendor/symbiote-node/engine/packs/transform/riopla-adapt.handler.js +0 -432
- package/vendor/symbiote-node/engine/packs/transform/set.handler.js +0 -57
- package/vendor/symbiote-node/engine/packs/transform/template-builder.handler.js +0 -134
- package/vendor/symbiote-node/engine/packs/transform/template.handler.js +0 -79
- package/vendor/symbiote-node/engine/packs/transform/timeline-build.handler.js +0 -399
- package/vendor/symbiote-node/engine/packs/util/delay.handler.js +0 -39
- package/vendor/symbiote-node/engine/packs/util/log.handler.js +0 -44
- package/vendor/symbiote-node/engine/packs/video-pack.js +0 -323
- package/vendor/symbiote-node/index.js +0 -103
- package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.css.js +0 -361
- package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.js +0 -332
- package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.tpl.js +0 -96
- package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.css.js +0 -104
- package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.js +0 -133
- package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.tpl.js +0 -33
- package/vendor/symbiote-node/interactions/ConnectFlow.js +0 -307
- package/vendor/symbiote-node/interactions/Drag.js +0 -102
- package/vendor/symbiote-node/interactions/Selector.js +0 -132
- package/vendor/symbiote-node/interactions/SnapGrid.js +0 -65
- package/vendor/symbiote-node/interactions/Zoom.js +0 -140
- package/vendor/symbiote-node/layout/ActionZone/ActionZone.css.js +0 -88
- package/vendor/symbiote-node/layout/ActionZone/ActionZone.js +0 -254
- package/vendor/symbiote-node/layout/ActionZone/ActionZone.tpl.js +0 -11
- package/vendor/symbiote-node/layout/Layout/Layout.css.js +0 -88
- package/vendor/symbiote-node/layout/Layout/Layout.js +0 -622
- package/vendor/symbiote-node/layout/Layout/Layout.tpl.js +0 -25
- package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.css.js +0 -293
- package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.js +0 -467
- package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.tpl.js +0 -33
- package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.css.js +0 -46
- package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.js +0 -102
- package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.tpl.js +0 -6
- package/vendor/symbiote-node/layout/LayoutRouter/LayoutRouter.js +0 -156
- package/vendor/symbiote-node/layout/LayoutRouter/routerSync.js +0 -250
- package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.css.js +0 -379
- package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.js +0 -263
- package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.tpl.js +0 -20
- package/vendor/symbiote-node/layout/LayoutSidebar/SidebarSection.js +0 -183
- package/vendor/symbiote-node/layout/LayoutTree.js +0 -246
- package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.css.js +0 -43
- package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.js +0 -89
- package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.tpl.js +0 -14
- package/vendor/symbiote-node/layout/index.js +0 -16
- package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.css.js +0 -61
- package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.js +0 -79
- package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.tpl.js +0 -19
- package/vendor/symbiote-node/node/CtrlItem/CtrlItem.css.js +0 -41
- package/vendor/symbiote-node/node/CtrlItem/CtrlItem.js +0 -24
- package/vendor/symbiote-node/node/CtrlItem/CtrlItem.tpl.js +0 -16
- package/vendor/symbiote-node/node/GraphFrame/GraphFrame.css.js +0 -65
- package/vendor/symbiote-node/node/GraphFrame/GraphFrame.js +0 -29
- package/vendor/symbiote-node/node/GraphFrame/GraphFrame.tpl.js +0 -13
- package/vendor/symbiote-node/node/GraphNode/GraphNode.css.js +0 -683
- package/vendor/symbiote-node/node/GraphNode/GraphNode.js +0 -92
- package/vendor/symbiote-node/node/GraphNode/GraphNode.tpl.js +0 -17
- package/vendor/symbiote-node/node/NodeSocket/NodeSocket.js +0 -25
- package/vendor/symbiote-node/node/NodeSocket/NodeSocket.tpl.js +0 -7
- package/vendor/symbiote-node/node/PortItem/PortItem.css.js +0 -90
- package/vendor/symbiote-node/node/PortItem/PortItem.js +0 -87
- package/vendor/symbiote-node/node/PortItem/PortItem.tpl.js +0 -10
- package/vendor/symbiote-node/package.json +0 -59
- package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.css.js +0 -143
- package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.js +0 -131
- package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.tpl.js +0 -16
- package/vendor/symbiote-node/plugins/History.js +0 -384
- package/vendor/symbiote-node/plugins/Readonly.js +0 -59
- package/vendor/symbiote-node/shapes/CircleShape.js +0 -80
- package/vendor/symbiote-node/shapes/CommentShape.js +0 -35
- package/vendor/symbiote-node/shapes/DiamondShape.js +0 -115
- package/vendor/symbiote-node/shapes/NodeShape.js +0 -80
- package/vendor/symbiote-node/shapes/PillShape.js +0 -91
- package/vendor/symbiote-node/shapes/RectShape.js +0 -72
- package/vendor/symbiote-node/shapes/SVGShape.js +0 -494
- package/vendor/symbiote-node/shapes/index.js +0 -53
- package/vendor/symbiote-node/themes/Palette.js +0 -32
- package/vendor/symbiote-node/themes/Skin.js +0 -113
- package/vendor/symbiote-node/themes/Theme.js +0 -84
- package/vendor/symbiote-node/themes/carbon.js +0 -137
- package/vendor/symbiote-node/themes/dark.js +0 -137
- package/vendor/symbiote-node/themes/ebook.js +0 -138
- package/vendor/symbiote-node/themes/grey.js +0 -137
- package/vendor/symbiote-node/themes/light.js +0 -137
- package/vendor/symbiote-node/themes/neon.js +0 -138
- package/vendor/symbiote-node/themes/pcb.js +0 -273
- package/vendor/symbiote-node/themes/synthwave.js +0 -137
- package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.css.js +0 -86
- package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.js +0 -128
- package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.tpl.js +0 -29
|
@@ -1,343 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ai/lesson-generate — AI Lesson Generation
|
|
3
|
-
*
|
|
4
|
-
* Generates structured educational lessons from news content using AI.
|
|
5
|
-
* Supports lesson creation, vocabulary generation, daily digest compilation,
|
|
6
|
-
* and content style validation.
|
|
7
|
-
*
|
|
8
|
-
* Ported from Mr-Computer/automations/argentine-spanish-bot/src/services/learning-by-examples.js
|
|
9
|
-
*
|
|
10
|
-
* @module agi-graph/packs/ai/lesson-generate
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { readFile, readdir } from 'node:fs/promises';
|
|
14
|
-
import path from 'node:path';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Load educational materials for context
|
|
18
|
-
* @param {string} materialsDir
|
|
19
|
-
* @returns {Promise<Object>}
|
|
20
|
-
*/
|
|
21
|
-
async function loadMaterials(materialsDir) {
|
|
22
|
-
const materials = {};
|
|
23
|
-
try {
|
|
24
|
-
const entries = await readdir(materialsDir);
|
|
25
|
-
for (const entry of entries) {
|
|
26
|
-
if (!entry.endsWith('.md')) continue;
|
|
27
|
-
const content = await readFile(path.join(materialsDir, entry), 'utf-8');
|
|
28
|
-
materials[entry.replace('.md', '')] = content;
|
|
29
|
-
}
|
|
30
|
-
} catch { /* no materials dir */ }
|
|
31
|
-
return materials;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Build lesson generation prompt
|
|
36
|
-
* @param {Object} newsItems
|
|
37
|
-
* @param {Object} materials
|
|
38
|
-
* @param {string} focus
|
|
39
|
-
* @param {number} maxExamples
|
|
40
|
-
* @returns {string}
|
|
41
|
-
*/
|
|
42
|
-
function buildLessonPrompt(newsItems, materials, focus, maxExamples) {
|
|
43
|
-
let prompt = `You are an experienced Spanish teacher specializing in Rioplatense Argentine Spanish for A1 level learners.\n\n`;
|
|
44
|
-
|
|
45
|
-
prompt += `TASK: Generate a structured lesson based on the following news items.\n\n`;
|
|
46
|
-
|
|
47
|
-
// Add news items
|
|
48
|
-
prompt += `NEWS CONTEXT:\n`;
|
|
49
|
-
for (const item of newsItems.slice(0, 5)) {
|
|
50
|
-
prompt += `- ${item.title || 'Untitled'}: ${(item.description || item.content || '').slice(0, 200)}\n`;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Add focus if provided
|
|
54
|
-
if (focus) {
|
|
55
|
-
prompt += `\nLESSON FOCUS: ${focus}\n`;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Add educational materials for style reference
|
|
59
|
-
const materialKeys = Object.keys(materials);
|
|
60
|
-
if (materialKeys.length > 0) {
|
|
61
|
-
prompt += `\nSTYLE REFERENCE (learn from these examples):\n`;
|
|
62
|
-
for (const key of materialKeys.slice(0, 3)) {
|
|
63
|
-
prompt += `--- ${key} ---\n${materials[key].slice(0, 500)}\n\n`;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
prompt += `\nOUTPUT FORMAT: JSON object with these fields:
|
|
68
|
-
{
|
|
69
|
-
"title_es": "Lesson title in Spanish",
|
|
70
|
-
"title_ru": "Lesson title in Russian",
|
|
71
|
-
"focus": "Grammar/vocabulary focus",
|
|
72
|
-
"explanation_ru": "2-3 line explanation in Russian about the Spanish construction",
|
|
73
|
-
"examples": [{"es": "Spanish example", "ru": "Russian translation"}],
|
|
74
|
-
"note_pron_ru": "Optional pronunciation note in Russian",
|
|
75
|
-
"vocabulary": [{"es": "word/phrase", "ru": "translation"}],
|
|
76
|
-
"podcast_script": {
|
|
77
|
-
"speaker1": "Russian-speaking host lines",
|
|
78
|
-
"speaker2": "Spanish-speaking guest lines"
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
RULES:
|
|
83
|
-
- Maximum ${maxExamples} example pairs
|
|
84
|
-
- Use Rioplatense dialect (vos, Argentine vocabulary)
|
|
85
|
-
- Keep explanations simple for A1 level
|
|
86
|
-
- Examples should relate to the news context
|
|
87
|
-
- Vocabulary: 10 items, nouns with articles (el/la)
|
|
88
|
-
- No English, only Spanish (es-AR) and Russian
|
|
89
|
-
`;
|
|
90
|
-
|
|
91
|
-
return prompt;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Build digest prompt
|
|
96
|
-
* @param {Object} digestData
|
|
97
|
-
* @returns {string}
|
|
98
|
-
*/
|
|
99
|
-
function buildDigestPrompt(digestData) {
|
|
100
|
-
const { newsItems, categories } = digestData;
|
|
101
|
-
|
|
102
|
-
let prompt = `Create a daily educational news digest for A1 Spanish learners.\n\n`;
|
|
103
|
-
|
|
104
|
-
if (categories) {
|
|
105
|
-
prompt += `CATEGORIES:\n`;
|
|
106
|
-
for (const [cat, items] of Object.entries(categories)) {
|
|
107
|
-
prompt += `- ${cat}: ${items.length} items\n`;
|
|
108
|
-
}
|
|
109
|
-
prompt += '\n';
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
prompt += `TOP NEWS:\n`;
|
|
113
|
-
for (const item of (newsItems || []).slice(0, 8)) {
|
|
114
|
-
prompt += `- ${item.title}: ${(item.description || '').slice(0, 150)}\n`;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
prompt += `\nOUTPUT FORMAT: JSON with fields:
|
|
118
|
-
{
|
|
119
|
-
"title_es": "Digest title",
|
|
120
|
-
"title_ru": "Russian title",
|
|
121
|
-
"sections": [{"category": "Category name", "summary_es": "Spanish summary", "summary_ru": "Russian summary", "vocabulary": [{"es": "word", "ru": "translation"}]}],
|
|
122
|
-
"lesson": {"focus": "Grammar point", "examples": [{"es": "", "ru": ""}]}
|
|
123
|
-
}
|
|
124
|
-
`;
|
|
125
|
-
|
|
126
|
-
return prompt;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Parse flexible AI response
|
|
131
|
-
* @param {string} response
|
|
132
|
-
* @returns {Object}
|
|
133
|
-
*/
|
|
134
|
-
function parseResponse(response) {
|
|
135
|
-
const jsonMatch = response.match(/\{[\s\S]*\}/);
|
|
136
|
-
if (jsonMatch) {
|
|
137
|
-
try {
|
|
138
|
-
return JSON.parse(jsonMatch[0]);
|
|
139
|
-
} catch { /* fallback */ }
|
|
140
|
-
}
|
|
141
|
-
return { error: 'Failed to parse AI response', raw: response };
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Validate lesson segments
|
|
146
|
-
* @param {Object} lesson
|
|
147
|
-
* @param {Array} newsItems
|
|
148
|
-
* @returns {Array<string>}
|
|
149
|
-
*/
|
|
150
|
-
function validateLesson(lesson, newsItems) {
|
|
151
|
-
const violations = [];
|
|
152
|
-
|
|
153
|
-
if (!lesson.title_es) violations.push('Missing title_es');
|
|
154
|
-
if (!lesson.focus) violations.push('Missing focus');
|
|
155
|
-
if (!Array.isArray(lesson.examples) || lesson.examples.length === 0) {
|
|
156
|
-
violations.push('Missing or empty examples');
|
|
157
|
-
}
|
|
158
|
-
if (lesson.examples) {
|
|
159
|
-
for (let i = 0; i < lesson.examples.length; i++) {
|
|
160
|
-
const ex = lesson.examples[i];
|
|
161
|
-
if (!ex.es || !ex.ru) violations.push(`Example ${i} missing es or ru`);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return violations;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// ─── Handler Definition ────────────────────────────────────────────────
|
|
169
|
-
|
|
170
|
-
export default {
|
|
171
|
-
type: 'ai/lesson-generate',
|
|
172
|
-
category: 'ai',
|
|
173
|
-
icon: 'school',
|
|
174
|
-
|
|
175
|
-
driver: {
|
|
176
|
-
description: 'AI-powered lesson generation from news content with vocabulary and podcast scripts',
|
|
177
|
-
inputs: [
|
|
178
|
-
{ name: 'newsItems', type: 'any' },
|
|
179
|
-
],
|
|
180
|
-
outputs: [
|
|
181
|
-
{ name: 'result', type: 'any' },
|
|
182
|
-
{ name: 'error', type: 'string' },
|
|
183
|
-
],
|
|
184
|
-
params: {
|
|
185
|
-
operation: { type: 'string', default: 'lesson', description: 'Operation: lesson | vocabulary | daily-digest | validate-style' },
|
|
186
|
-
apiKey: { type: 'string', default: null, description: 'OpenRouter API key (or OPENROUTER_API_KEY env)' },
|
|
187
|
-
model: { type: 'string', default: 'anthropic/claude-sonnet-4', description: 'AI model to use' },
|
|
188
|
-
focus: { type: 'string', default: null, description: 'Lesson focus/topic' },
|
|
189
|
-
maxExamples: { type: 'int', default: 5, description: 'Maximum examples per lesson' },
|
|
190
|
-
materialsDir: { type: 'string', default: null, description: 'Path to educational materials for style reference' },
|
|
191
|
-
// daily-digest
|
|
192
|
-
categories: { type: 'any', default: null, description: 'News items grouped by category' },
|
|
193
|
-
// validate
|
|
194
|
-
content: { type: 'string', default: null, description: 'Content to validate' },
|
|
195
|
-
contentType: { type: 'string', default: 'news', description: 'Content type for validation' },
|
|
196
|
-
},
|
|
197
|
-
},
|
|
198
|
-
|
|
199
|
-
lifecycle: {
|
|
200
|
-
validate: (inputs, params) => {
|
|
201
|
-
if (params.operation === 'validate-style') {
|
|
202
|
-
return typeof params.content === 'string';
|
|
203
|
-
}
|
|
204
|
-
if (!Array.isArray(inputs.newsItems) || inputs.newsItems.length === 0) return false;
|
|
205
|
-
const apiKey = params.apiKey || process.env.OPENROUTER_API_KEY;
|
|
206
|
-
if (!apiKey) return false;
|
|
207
|
-
return true;
|
|
208
|
-
},
|
|
209
|
-
|
|
210
|
-
cacheKey: () => null, // AI output varies
|
|
211
|
-
|
|
212
|
-
execute: async (inputs, params) => {
|
|
213
|
-
const { newsItems } = inputs;
|
|
214
|
-
const { operation, model, focus, maxExamples } = params;
|
|
215
|
-
const apiKey = params.apiKey || process.env.OPENROUTER_API_KEY;
|
|
216
|
-
|
|
217
|
-
try {
|
|
218
|
-
switch (operation) {
|
|
219
|
-
case 'lesson': {
|
|
220
|
-
const materials = params.materialsDir
|
|
221
|
-
? await loadMaterials(params.materialsDir)
|
|
222
|
-
: {};
|
|
223
|
-
|
|
224
|
-
const prompt = buildLessonPrompt(newsItems, materials, focus, maxExamples);
|
|
225
|
-
|
|
226
|
-
const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {
|
|
227
|
-
method: 'POST',
|
|
228
|
-
headers: {
|
|
229
|
-
'Content-Type': 'application/json',
|
|
230
|
-
'Authorization': `Bearer ${apiKey}`,
|
|
231
|
-
},
|
|
232
|
-
body: JSON.stringify({
|
|
233
|
-
model,
|
|
234
|
-
messages: [{ role: 'user', content: prompt }],
|
|
235
|
-
temperature: 0.7,
|
|
236
|
-
}),
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
if (!response.ok) return { error: `API error: HTTP ${response.status}` };
|
|
240
|
-
|
|
241
|
-
const data = await response.json();
|
|
242
|
-
const aiResponse = data.choices?.[0]?.message?.content || '';
|
|
243
|
-
const lesson = parseResponse(aiResponse);
|
|
244
|
-
const violations = validateLesson(lesson, newsItems);
|
|
245
|
-
|
|
246
|
-
return {
|
|
247
|
-
result: {
|
|
248
|
-
lesson,
|
|
249
|
-
valid: violations.length === 0,
|
|
250
|
-
violations,
|
|
251
|
-
newsCount: newsItems.length,
|
|
252
|
-
model,
|
|
253
|
-
},
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
case 'vocabulary': {
|
|
258
|
-
const prompt = `Extract 10 key vocabulary items from these news headlines for A1 Spanish learners (Rioplatense dialect).\n\nNEWS:\n${newsItems.map(n => `- ${n.title}`).join('\n')}\n\nOUTPUT: JSON array of {"es": "word with article", "ru": "translation"}\nRules: nouns with el/la, no brands, no cognates, prefer regional vocabulary.`;
|
|
259
|
-
|
|
260
|
-
const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {
|
|
261
|
-
method: 'POST',
|
|
262
|
-
headers: {
|
|
263
|
-
'Content-Type': 'application/json',
|
|
264
|
-
'Authorization': `Bearer ${apiKey}`,
|
|
265
|
-
},
|
|
266
|
-
body: JSON.stringify({
|
|
267
|
-
model,
|
|
268
|
-
messages: [{ role: 'user', content: prompt }],
|
|
269
|
-
temperature: 0.5,
|
|
270
|
-
}),
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
if (!response.ok) return { error: `API error: HTTP ${response.status}` };
|
|
274
|
-
|
|
275
|
-
const data = await response.json();
|
|
276
|
-
const aiResponse = data.choices?.[0]?.message?.content || '';
|
|
277
|
-
const vocabulary = parseResponse(aiResponse);
|
|
278
|
-
|
|
279
|
-
return { result: { vocabulary, newsCount: newsItems.length, model } };
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
case 'daily-digest': {
|
|
283
|
-
const prompt = buildDigestPrompt({
|
|
284
|
-
newsItems,
|
|
285
|
-
categories: params.categories,
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {
|
|
289
|
-
method: 'POST',
|
|
290
|
-
headers: {
|
|
291
|
-
'Content-Type': 'application/json',
|
|
292
|
-
'Authorization': `Bearer ${apiKey}`,
|
|
293
|
-
},
|
|
294
|
-
body: JSON.stringify({
|
|
295
|
-
model,
|
|
296
|
-
messages: [{ role: 'user', content: prompt }],
|
|
297
|
-
temperature: 0.7,
|
|
298
|
-
}),
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
if (!response.ok) return { error: `API error: HTTP ${response.status}` };
|
|
302
|
-
|
|
303
|
-
const data = await response.json();
|
|
304
|
-
const aiResponse = data.choices?.[0]?.message?.content || '';
|
|
305
|
-
const digest = parseResponse(aiResponse);
|
|
306
|
-
|
|
307
|
-
return { result: { digest, newsCount: newsItems.length, model } };
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
case 'validate-style': {
|
|
311
|
-
const prompt = `Evaluate if the following ${params.contentType} content matches A1 Rioplatense Spanish learning material standards.\n\nCONTENT:\n${params.content}\n\nCheck:\n1. Vocabulary complexity (should be A1)\n2. Rioplatense dialect usage (vos, local terms)\n3. Bilingual coverage (es + ru)\n4. Educational value\n\nOUTPUT: JSON with {score: 0-100, issues: [string], suggestions: [string]}`;
|
|
312
|
-
|
|
313
|
-
const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {
|
|
314
|
-
method: 'POST',
|
|
315
|
-
headers: {
|
|
316
|
-
'Content-Type': 'application/json',
|
|
317
|
-
'Authorization': `Bearer ${apiKey}`,
|
|
318
|
-
},
|
|
319
|
-
body: JSON.stringify({
|
|
320
|
-
model,
|
|
321
|
-
messages: [{ role: 'user', content: prompt }],
|
|
322
|
-
temperature: 0.3,
|
|
323
|
-
}),
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
if (!response.ok) return { error: `API error: HTTP ${response.status}` };
|
|
327
|
-
|
|
328
|
-
const data = await response.json();
|
|
329
|
-
const aiResponse = data.choices?.[0]?.message?.content || '';
|
|
330
|
-
const validation = parseResponse(aiResponse);
|
|
331
|
-
|
|
332
|
-
return { result: { validation, model } };
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
default:
|
|
336
|
-
return { error: `Unknown operation: ${operation}` };
|
|
337
|
-
}
|
|
338
|
-
} catch (err) {
|
|
339
|
-
return { error: `lesson-generate ${operation} failed: ${err.message}` };
|
|
340
|
-
}
|
|
341
|
-
},
|
|
342
|
-
},
|
|
343
|
-
};
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ai/opencode — AI inference via OpenCode REST API + OpenRouter
|
|
3
|
-
*
|
|
4
|
-
* Connects to a running OpenCode (Crush) instance, creates a session,
|
|
5
|
-
* sends a prompt, and polls a file for the AI's JSON output.
|
|
6
|
-
* Model-agnostic: works with any model available through OpenRouter
|
|
7
|
-
* (DeepSeek, Claude, Gemini, etc.).
|
|
8
|
-
*
|
|
9
|
-
* Pattern from radio-conversation-service.js:
|
|
10
|
-
* POST /session → create session
|
|
11
|
-
* POST /session/:id/message → send prompt (fire & forget)
|
|
12
|
-
* Poll output file → wait for JSON result
|
|
13
|
-
*
|
|
14
|
-
* @module agi-graph/packs/ai/opencode
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import { promises as fs } from 'fs';
|
|
18
|
-
import path from 'path';
|
|
19
|
-
import os from 'os';
|
|
20
|
-
|
|
21
|
-
export default {
|
|
22
|
-
type: 'ai/opencode',
|
|
23
|
-
category: 'ai',
|
|
24
|
-
icon: 'psychology',
|
|
25
|
-
|
|
26
|
-
driver: {
|
|
27
|
-
description: 'AI inference via OpenCode + OpenRouter (DeepSeek, Claude, Gemini, etc.)',
|
|
28
|
-
inputs: [
|
|
29
|
-
{ name: 'prompt', type: 'string' },
|
|
30
|
-
{ name: 'context', type: 'any' },
|
|
31
|
-
],
|
|
32
|
-
outputs: [
|
|
33
|
-
{ name: 'result', type: 'any' },
|
|
34
|
-
{ name: 'error', type: 'string' },
|
|
35
|
-
],
|
|
36
|
-
params: {
|
|
37
|
-
model: { type: 'string', default: 'deepseek/deepseek-v3.2', description: 'OpenRouter model ID' },
|
|
38
|
-
provider: { type: 'string', default: 'openrouter', description: 'Model provider' },
|
|
39
|
-
opencodeUrl: { type: 'string', default: 'http://127.0.0.1:4096', description: 'OpenCode API URL' },
|
|
40
|
-
timeout: { type: 'int', default: 300000, description: 'Max wait time (ms)' },
|
|
41
|
-
outputDir: { type: 'string', default: '', description: 'Workspace dir for file exchange' },
|
|
42
|
-
},
|
|
43
|
-
},
|
|
44
|
-
|
|
45
|
-
lifecycle: {
|
|
46
|
-
validate: (inputs) => {
|
|
47
|
-
if (!inputs.prompt) return false;
|
|
48
|
-
return true;
|
|
49
|
-
},
|
|
50
|
-
|
|
51
|
-
cacheKey: (inputs, params) =>
|
|
52
|
-
`opencode:${params.model}:${inputs.prompt}:${JSON.stringify(inputs.context)}`,
|
|
53
|
-
|
|
54
|
-
execute: async (inputs, params) => {
|
|
55
|
-
const { prompt, context } = inputs;
|
|
56
|
-
const {
|
|
57
|
-
model,
|
|
58
|
-
provider,
|
|
59
|
-
opencodeUrl,
|
|
60
|
-
timeout,
|
|
61
|
-
outputDir,
|
|
62
|
-
} = params;
|
|
63
|
-
|
|
64
|
-
const baseUrl = opencodeUrl || process.env.OPENCODE_URL || 'http://127.0.0.1:4096';
|
|
65
|
-
const modelConfig = {
|
|
66
|
-
providerID: provider || process.env.OPENCODE_PROVIDER || 'openrouter',
|
|
67
|
-
modelID: model || process.env.OPENCODE_MODEL || 'deepseek/deepseek-v3.2',
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
// Workspace for file-based communication
|
|
71
|
-
const workspace = outputDir || process.env.OPENCODE_WORKSPACE ||
|
|
72
|
-
path.join(os.tmpdir(), 'agi-graph-opencode');
|
|
73
|
-
await fs.mkdir(workspace, { recursive: true });
|
|
74
|
-
|
|
75
|
-
const taskPath = path.join(workspace, 'task.json');
|
|
76
|
-
const outputPath = path.join(workspace, 'output.json');
|
|
77
|
-
|
|
78
|
-
// Write task file with context
|
|
79
|
-
await fs.writeFile(taskPath, JSON.stringify({
|
|
80
|
-
type: 'agi-graph-ai',
|
|
81
|
-
prompt,
|
|
82
|
-
context,
|
|
83
|
-
timestamp: Date.now(),
|
|
84
|
-
}, null, 2), 'utf8');
|
|
85
|
-
|
|
86
|
-
// Clean previous output
|
|
87
|
-
try { await fs.unlink(outputPath); } catch { /* ignore */ }
|
|
88
|
-
|
|
89
|
-
try {
|
|
90
|
-
// 1. Create session
|
|
91
|
-
const sessionRes = await fetch(`${baseUrl}/session`, {
|
|
92
|
-
method: 'POST',
|
|
93
|
-
headers: { 'Content-Type': 'application/json' },
|
|
94
|
-
body: JSON.stringify({ title: `agi-graph ${Date.now()}` }),
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
if (!sessionRes.ok) {
|
|
98
|
-
return { result: null, error: `Session creation failed: ${sessionRes.status}` };
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const session = await sessionRes.json();
|
|
102
|
-
|
|
103
|
-
// 2. Build full prompt with workspace instructions
|
|
104
|
-
const contextBlock = context
|
|
105
|
-
? `\n\n## Context\n\`\`\`json\n${JSON.stringify(context, null, 2)}\n\`\`\``
|
|
106
|
-
: '';
|
|
107
|
-
|
|
108
|
-
const fullPrompt = `${prompt}${contextBlock}
|
|
109
|
-
|
|
110
|
-
## Workspace: ${workspace}
|
|
111
|
-
|
|
112
|
-
### Instructions:
|
|
113
|
-
1. Read task from ${taskPath}
|
|
114
|
-
2. Process the request
|
|
115
|
-
3. Write result as JSON to ${outputPath}
|
|
116
|
-
|
|
117
|
-
Output format: { "result": <your_result> }`;
|
|
118
|
-
|
|
119
|
-
// 3. Send message (fire & forget)
|
|
120
|
-
const msgRes = await fetch(`${baseUrl}/session/${session.id}/message`, {
|
|
121
|
-
method: 'POST',
|
|
122
|
-
headers: { 'Content-Type': 'application/json' },
|
|
123
|
-
body: JSON.stringify({
|
|
124
|
-
model: modelConfig,
|
|
125
|
-
parts: [{ type: 'text', text: fullPrompt }],
|
|
126
|
-
}),
|
|
127
|
-
signal: AbortSignal.timeout(120000),
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
if (!msgRes.ok) {
|
|
131
|
-
return { result: null, error: `Message send failed: ${msgRes.status}` };
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// 4. Poll for output file
|
|
135
|
-
const startTime = Date.now();
|
|
136
|
-
const pollInterval = 3000;
|
|
137
|
-
|
|
138
|
-
while (Date.now() - startTime < timeout) {
|
|
139
|
-
try {
|
|
140
|
-
const content = await fs.readFile(outputPath, 'utf8');
|
|
141
|
-
const parsed = JSON.parse(content);
|
|
142
|
-
|
|
143
|
-
if (parsed.result !== undefined) {
|
|
144
|
-
return { result: parsed.result, error: null };
|
|
145
|
-
}
|
|
146
|
-
// If the file exists but has no result key, return entire content
|
|
147
|
-
if (Object.keys(parsed).length > 0) {
|
|
148
|
-
return { result: parsed, error: null };
|
|
149
|
-
}
|
|
150
|
-
} catch {
|
|
151
|
-
// File not ready yet
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
return { result: null, error: `Timeout after ${timeout}ms waiting for AI response` };
|
|
158
|
-
|
|
159
|
-
} catch (err) {
|
|
160
|
-
return { result: null, error: err.message };
|
|
161
|
-
}
|
|
162
|
-
},
|
|
163
|
-
},
|
|
164
|
-
};
|