project-graph-mcp 2.3.1 → 2.3.2
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 +1 -1
- package/vendor/symbiote-node/engine/AgentUICommands.js +100 -0
- package/vendor/symbiote-node/engine/Executor.js +371 -0
- package/vendor/symbiote-node/engine/Graph.js +314 -0
- package/vendor/symbiote-node/engine/GraphServer.js +353 -0
- package/vendor/symbiote-node/engine/HandlerLoader.js +145 -0
- package/vendor/symbiote-node/engine/History.js +83 -0
- package/vendor/symbiote-node/engine/Lifecycle.js +118 -0
- package/vendor/symbiote-node/engine/Persistence.js +84 -0
- package/vendor/symbiote-node/engine/Registry.js +264 -0
- package/vendor/symbiote-node/engine/SocketTypes.js +79 -0
- package/vendor/symbiote-node/engine/cli.js +404 -0
- package/vendor/symbiote-node/engine/index.js +56 -0
- package/vendor/symbiote-node/engine/nanoid.js +28 -0
- package/vendor/symbiote-node/engine/package.json +26 -0
- package/vendor/symbiote-node/engine/packs/ai/beat-detect.handler.js +215 -0
- package/vendor/symbiote-node/engine/packs/ai/content-adapt.handler.js +238 -0
- package/vendor/symbiote-node/engine/packs/ai/face-detect.handler.js +287 -0
- package/vendor/symbiote-node/engine/packs/ai/grok-generate.handler.js +565 -0
- package/vendor/symbiote-node/engine/packs/ai/kling-lipsync.handler.js +414 -0
- package/vendor/symbiote-node/engine/packs/ai/lesson-generate.handler.js +343 -0
- package/vendor/symbiote-node/engine/packs/ai/opencode.handler.js +164 -0
- package/vendor/symbiote-node/engine/packs/ai/replicate-lipsync.handler.js +341 -0
- package/vendor/symbiote-node/engine/packs/ai/tts.handler.js +241 -0
- package/vendor/symbiote-node/engine/packs/ai/whisper.handler.js +191 -0
- package/vendor/symbiote-node/engine/packs/data/db-query.handler.js +67 -0
- package/vendor/symbiote-node/engine/packs/data/news-accumulate.handler.js +281 -0
- package/vendor/symbiote-node/engine/packs/data/personas.handler.js +160 -0
- package/vendor/symbiote-node/engine/packs/data/prompt-loader.handler.js +193 -0
- package/vendor/symbiote-node/engine/packs/data/roles.handler.js +216 -0
- package/vendor/symbiote-node/engine/packs/data/rss-feed.handler.js +244 -0
- package/vendor/symbiote-node/engine/packs/debug/inject.handler.js +52 -0
- package/vendor/symbiote-node/engine/packs/flow/agent.handler.js +73 -0
- package/vendor/symbiote-node/engine/packs/flow/if.handler.js +107 -0
- package/vendor/symbiote-node/engine/packs/flow/loop.handler.js +58 -0
- package/vendor/symbiote-node/engine/packs/flow/merge.handler.js +60 -0
- package/vendor/symbiote-node/engine/packs/flow/retry.handler.js +65 -0
- package/vendor/symbiote-node/engine/packs/flow/switch.handler.js +64 -0
- package/vendor/symbiote-node/engine/packs/flow/wait-all.handler.js +39 -0
- package/vendor/symbiote-node/engine/packs/io/http-request.handler.js +82 -0
- package/vendor/symbiote-node/engine/packs/io/read-file.handler.js +60 -0
- package/vendor/symbiote-node/engine/packs/io/write-file.handler.js +63 -0
- package/vendor/symbiote-node/engine/packs/transform/anchor-match.handler.js +494 -0
- package/vendor/symbiote-node/engine/packs/transform/effects-skeleton.handler.js +417 -0
- package/vendor/symbiote-node/engine/packs/transform/json-parse.handler.js +43 -0
- package/vendor/symbiote-node/engine/packs/transform/lipsync-select.handler.js +339 -0
- package/vendor/symbiote-node/engine/packs/transform/riopla-adapt.handler.js +432 -0
- package/vendor/symbiote-node/engine/packs/transform/set.handler.js +57 -0
- package/vendor/symbiote-node/engine/packs/transform/template-builder.handler.js +134 -0
- package/vendor/symbiote-node/engine/packs/transform/template.handler.js +79 -0
- package/vendor/symbiote-node/engine/packs/transform/timeline-build.handler.js +399 -0
- package/vendor/symbiote-node/engine/packs/util/delay.handler.js +39 -0
- package/vendor/symbiote-node/engine/packs/util/log.handler.js +44 -0
- package/vendor/symbiote-node/engine/packs/video-pack.js +323 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SocketTypes.js - Universal typed socket system
|
|
3
|
+
*
|
|
4
|
+
* Defines socket types for node connections with color coding
|
|
5
|
+
* and compatibility rules. Domain packs register additional types.
|
|
6
|
+
*
|
|
7
|
+
* @module agi-graph/SocketTypes
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {object} SocketTypeDef
|
|
12
|
+
* @property {string} color - Hex color for UI rendering
|
|
13
|
+
* @property {string} label - Human-readable label
|
|
14
|
+
* @property {string[]} compatible - Types this socket can connect to
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** @type {Map<string, SocketTypeDef>} */
|
|
18
|
+
const _types = new Map();
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Register a socket type
|
|
22
|
+
* @param {string} name
|
|
23
|
+
* @param {SocketTypeDef} def
|
|
24
|
+
*/
|
|
25
|
+
export function registerSocketType(name, def) {
|
|
26
|
+
_types.set(name, { ...def, compatible: def.compatible || [name] });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Register multiple socket types at once
|
|
31
|
+
* @param {Record<string, SocketTypeDef>} types
|
|
32
|
+
*/
|
|
33
|
+
export function registerSocketTypes(types) {
|
|
34
|
+
for (const [name, def] of Object.entries(types)) {
|
|
35
|
+
registerSocketType(name, def);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Get socket type definition
|
|
41
|
+
* @param {string} name
|
|
42
|
+
* @returns {SocketTypeDef|undefined}
|
|
43
|
+
*/
|
|
44
|
+
export function getSocketType(name) {
|
|
45
|
+
return _types.get(name);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Get all registered socket types
|
|
50
|
+
* @returns {Map<string, SocketTypeDef>}
|
|
51
|
+
*/
|
|
52
|
+
export function getAllSocketTypes() {
|
|
53
|
+
return new Map(_types);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Check if two socket types are compatible
|
|
58
|
+
* @param {string} from - Source socket type
|
|
59
|
+
* @param {string} to - Target socket type
|
|
60
|
+
* @returns {boolean}
|
|
61
|
+
*/
|
|
62
|
+
export function areSocketsCompatible(from, to) {
|
|
63
|
+
if (from === 'any' || to === 'any') return true;
|
|
64
|
+
if (from === to) return true;
|
|
65
|
+
const fromDef = _types.get(from);
|
|
66
|
+
if (!fromDef) return false;
|
|
67
|
+
return fromDef.compatible.includes(to);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Core socket types (always available)
|
|
71
|
+
registerSocketTypes({
|
|
72
|
+
any: { color: '#FFFFFF', label: 'Any', compatible: [] },
|
|
73
|
+
float: { color: '#A1A1A1', label: 'Float', compatible: ['float', 'int'] },
|
|
74
|
+
int: { color: '#598C5C', label: 'Integer', compatible: ['int', 'float'] },
|
|
75
|
+
string: { color: '#70B2FF', label: 'String', compatible: ['string'] },
|
|
76
|
+
boolean: { color: '#CCA6D6', label: 'Boolean', compatible: ['boolean'] },
|
|
77
|
+
object: { color: '#E09050', label: 'Object', compatible: ['object'] },
|
|
78
|
+
array: { color: '#50C878', label: 'Array', compatible: ['array'] },
|
|
79
|
+
});
|
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* cli.js - AGI-Graph command-line runner
|
|
5
|
+
*
|
|
6
|
+
* Execute, validate, and inspect workflow JSON files.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node symbiote-node/cli.js run <workflow.json> [--pack custom] [--secrets secrets.json] [--verbose]
|
|
10
|
+
* node symbiote-node/cli.js validate <workflow.json> [--pack custom]
|
|
11
|
+
* node symbiote-node/cli.js list [--pack custom]
|
|
12
|
+
* node symbiote-node/cli.js inspect <workflow.json>
|
|
13
|
+
*
|
|
14
|
+
* @module symbiote-node/cli */
|
|
15
|
+
|
|
16
|
+
import { readFile } from 'node:fs/promises';
|
|
17
|
+
import { resolve, dirname } from 'node:path';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
import { performance } from 'node:perf_hooks';
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
Graph,
|
|
23
|
+
Executor,
|
|
24
|
+
listDrivers,
|
|
25
|
+
getNodeType,
|
|
26
|
+
findCompatible,
|
|
27
|
+
getNodeMenu,
|
|
28
|
+
validateParams,
|
|
29
|
+
deserialize,
|
|
30
|
+
clearRegistry,
|
|
31
|
+
loadHandlers,
|
|
32
|
+
createServer,
|
|
33
|
+
} from './index.js';
|
|
34
|
+
|
|
35
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
36
|
+
|
|
37
|
+
// ─── Argument Parsing ────────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Parse CLI arguments into command and options
|
|
41
|
+
* @param {string[]} argv
|
|
42
|
+
* @returns {{command: string, target: string, options: Record<string, string|boolean>}}
|
|
43
|
+
*/
|
|
44
|
+
function parseArgs(argv) {
|
|
45
|
+
const args = argv.slice(2);
|
|
46
|
+
const command = args[0];
|
|
47
|
+
let target = '';
|
|
48
|
+
/** @type {Record<string, string|boolean>} */
|
|
49
|
+
const options = {};
|
|
50
|
+
|
|
51
|
+
for (let i = 1; i < args.length; i++) {
|
|
52
|
+
if (args[i].startsWith('--')) {
|
|
53
|
+
const key = args[i].slice(2);
|
|
54
|
+
const next = args[i + 1];
|
|
55
|
+
if (next && !next.startsWith('--')) {
|
|
56
|
+
options[key] = next;
|
|
57
|
+
i++;
|
|
58
|
+
} else {
|
|
59
|
+
options[key] = true;
|
|
60
|
+
}
|
|
61
|
+
} else if (!target) {
|
|
62
|
+
target = args[i];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return { command, target, options };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ─── Secrets Loader ──────────────────────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Load secrets from JSON file
|
|
73
|
+
* @param {string} [secretsPath]
|
|
74
|
+
* @returns {Promise<Record<string, string>>}
|
|
75
|
+
*/
|
|
76
|
+
async function loadSecrets(secretsPath) {
|
|
77
|
+
if (!secretsPath) {
|
|
78
|
+
// Try default location
|
|
79
|
+
const defaultPath = resolve(process.cwd(), 'secrets.json');
|
|
80
|
+
try {
|
|
81
|
+
const data = await readFile(defaultPath, 'utf-8');
|
|
82
|
+
return JSON.parse(data);
|
|
83
|
+
} catch {
|
|
84
|
+
return {};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
const data = await readFile(resolve(secretsPath), 'utf-8');
|
|
90
|
+
return JSON.parse(data);
|
|
91
|
+
} catch (err) {
|
|
92
|
+
console.error(`⚠ Could not load secrets from ${secretsPath}: ${err.message}`);
|
|
93
|
+
return {};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ─── Pack Loader ─────────────────────────────────────────────────────────────
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Load domain packs by name
|
|
101
|
+
* @param {string|string[]} packs
|
|
102
|
+
*/
|
|
103
|
+
async function loadPacks(packs) {
|
|
104
|
+
const packList = Array.isArray(packs) ? packs : packs.split(',');
|
|
105
|
+
for (const pack of packList) {
|
|
106
|
+
const packName = pack.trim();
|
|
107
|
+
try {
|
|
108
|
+
await import(`./packs/${packName}-pack.js`);
|
|
109
|
+
console.log(` ✔ Pack loaded: ${packName}`);
|
|
110
|
+
} catch (err) {
|
|
111
|
+
console.error(` ✖ Failed to load pack "${packName}": ${err.message}`);
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ─── Commands ────────────────────────────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Run a workflow JSON file
|
|
121
|
+
* @param {string} filePath
|
|
122
|
+
* @param {Record<string, string|boolean>} options
|
|
123
|
+
*/
|
|
124
|
+
async function cmdRun(filePath, options) {
|
|
125
|
+
const verbose = !!options.verbose;
|
|
126
|
+
console.log(`\n🚀 symbiote-node run: ${filePath}\n`);
|
|
127
|
+
// Load packs
|
|
128
|
+
if (options.pack) {
|
|
129
|
+
await loadPacks(/** @type {string} */(options.pack));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Load handler files
|
|
133
|
+
if (options.handlers) {
|
|
134
|
+
const dir = resolve(/** @type {string} */(options.handlers));
|
|
135
|
+
const types = await loadHandlers(dir);
|
|
136
|
+
if (verbose) console.log(` 🔧 Loaded ${types.length} handler(s) from ${options.handlers}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Load secrets
|
|
140
|
+
const secrets = await loadSecrets(/** @type {string|undefined} */(options.secrets));
|
|
141
|
+
if (Object.keys(secrets).length > 0 && verbose) {
|
|
142
|
+
console.log(` 🔑 Secrets loaded: ${Object.keys(secrets).join(', ')}`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Load workflow
|
|
146
|
+
const raw = await readFile(resolve(filePath), 'utf-8');
|
|
147
|
+
const workflowData = JSON.parse(raw);
|
|
148
|
+
|
|
149
|
+
console.log(` 📄 Workflow: ${workflowData.name || workflowData.id}`);
|
|
150
|
+
console.log(` 📊 Nodes: ${workflowData.nodes?.length || 0}`);
|
|
151
|
+
console.log(` 🔗 Connections: ${workflowData.connections?.length || 0}`);
|
|
152
|
+
console.log();
|
|
153
|
+
|
|
154
|
+
// Deserialize into Graph
|
|
155
|
+
const graph = deserialize(raw);
|
|
156
|
+
|
|
157
|
+
// Execute
|
|
158
|
+
const executor = new Executor();
|
|
159
|
+
const t0 = performance.now();
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
const result = await executor.run(graph, {
|
|
163
|
+
cache: workflowData.execution?.cache,
|
|
164
|
+
secrets,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const elapsed = (performance.now() - t0).toFixed(1);
|
|
168
|
+
console.log(` ✔ Execution complete in ${elapsed}ms`);
|
|
169
|
+
console.log(` 📋 Execution order: ${result.executionOrder.length} nodes`);
|
|
170
|
+
|
|
171
|
+
if (verbose) {
|
|
172
|
+
console.log('\n Execution log:');
|
|
173
|
+
for (const entry of result.log) {
|
|
174
|
+
const status = entry.skipped ? '⏭ skipped' : `✔ ${entry.time.toFixed(2)}ms`;
|
|
175
|
+
const nodeData = graph.getNode(entry.nodeId);
|
|
176
|
+
console.log(` ${nodeData?.name || entry.nodeId}: ${status}`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
console.log('\n Outputs:');
|
|
180
|
+
for (const [nodeId, output] of Object.entries(result.outputs)) {
|
|
181
|
+
const nodeData = graph.getNode(nodeId);
|
|
182
|
+
console.log(` ${nodeData?.name || nodeId}:`, JSON.stringify(output, null, 2).slice(0, 200));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Summary
|
|
187
|
+
const outputNodes = result.executionOrder.filter(id => {
|
|
188
|
+
const node = graph.getNode(id);
|
|
189
|
+
return node?.type?.startsWith('output/');
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
if (outputNodes.length > 0) {
|
|
193
|
+
console.log(`\n Output nodes:`);
|
|
194
|
+
for (const id of outputNodes) {
|
|
195
|
+
const node = graph.getNode(id);
|
|
196
|
+
console.log(` → ${node.name || node.type} (${id})`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
console.log(`\n✅ Done\n`);
|
|
201
|
+
|
|
202
|
+
} catch (err) {
|
|
203
|
+
const elapsed = (performance.now() - t0).toFixed(1);
|
|
204
|
+
console.error(`\n ✖ Execution failed after ${elapsed}ms: ${err.message}\n`);
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Validate a workflow JSON file without executing
|
|
211
|
+
* @param {string} filePath
|
|
212
|
+
* @param {Record<string, string|boolean>} options
|
|
213
|
+
*/
|
|
214
|
+
async function cmdValidate(filePath, options) {
|
|
215
|
+
console.log(`\n🔍 symbiote-node validate: ${filePath}\n`);
|
|
216
|
+
if (options.pack) {
|
|
217
|
+
await loadPacks(/** @type {string} */(options.pack));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (options.handlers) {
|
|
221
|
+
const dir = resolve(/** @type {string} */(options.handlers));
|
|
222
|
+
await loadHandlers(dir);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const raw = await readFile(resolve(filePath), 'utf-8');
|
|
226
|
+
const data = JSON.parse(raw);
|
|
227
|
+
|
|
228
|
+
let errors = 0;
|
|
229
|
+
let warnings = 0;
|
|
230
|
+
|
|
231
|
+
// Check all node types exist
|
|
232
|
+
for (const node of (data.nodes || [])) {
|
|
233
|
+
const typeDef = getNodeType(node.type);
|
|
234
|
+
if (!typeDef) {
|
|
235
|
+
console.error(` ✖ Unknown node type: ${node.type} (node: ${node.id})`);
|
|
236
|
+
errors++;
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Validate params
|
|
241
|
+
const validation = validateParams(node.type, node.params || {});
|
|
242
|
+
if (!validation.valid) {
|
|
243
|
+
for (const err of validation.errors) {
|
|
244
|
+
console.error(` ✖ ${node.id} (${node.type}): ${err}`);
|
|
245
|
+
errors++;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Check connections reference valid nodes
|
|
251
|
+
const nodeIds = new Set((data.nodes || []).map(n => n.id));
|
|
252
|
+
for (const conn of (data.connections || [])) {
|
|
253
|
+
if (!nodeIds.has(conn.from)) {
|
|
254
|
+
console.error(` ✖ Connection references unknown source node: ${conn.from}`);
|
|
255
|
+
errors++;
|
|
256
|
+
}
|
|
257
|
+
if (!nodeIds.has(conn.to)) {
|
|
258
|
+
console.error(` ✖ Connection references unknown target node: ${conn.to}`);
|
|
259
|
+
errors++;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Check for nodes with no connections (orphans)
|
|
264
|
+
const connectedNodes = new Set();
|
|
265
|
+
for (const conn of (data.connections || [])) {
|
|
266
|
+
connectedNodes.add(conn.from);
|
|
267
|
+
connectedNodes.add(conn.to);
|
|
268
|
+
}
|
|
269
|
+
for (const node of (data.nodes || [])) {
|
|
270
|
+
if (!connectedNodes.has(node.id)) {
|
|
271
|
+
console.warn(` ⚠ Orphan node: ${node.id} (${node.type})`);
|
|
272
|
+
warnings++;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
console.log();
|
|
277
|
+
if (errors === 0) {
|
|
278
|
+
console.log(` ✅ Valid (${warnings} warning${warnings !== 1 ? 's' : ''})\n`);
|
|
279
|
+
} else {
|
|
280
|
+
console.error(` ❌ ${errors} error${errors !== 1 ? 's' : ''}, ${warnings} warning${warnings !== 1 ? 's' : ''}\n`);
|
|
281
|
+
process.exit(1);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* List all registered node types
|
|
287
|
+
* @param {Record<string, string|boolean>} options
|
|
288
|
+
*/
|
|
289
|
+
async function cmdList(options) {
|
|
290
|
+
console.log(`\n📋 symbiote-node node types\n`);
|
|
291
|
+
if (options.pack) {
|
|
292
|
+
await loadPacks(/** @type {string} */(options.pack));
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (options.handlers) {
|
|
296
|
+
const dir = resolve(/** @type {string} */(options.handlers));
|
|
297
|
+
await loadHandlers(dir);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const menu = getNodeMenu();
|
|
301
|
+
for (const group of menu) {
|
|
302
|
+
console.log(` ═══ ${group.category.toUpperCase()} ═══`);
|
|
303
|
+
for (const node of group.nodes) {
|
|
304
|
+
const typeDef = getNodeType(node.type);
|
|
305
|
+
const ins = typeDef?.driver.inputs?.length || 0;
|
|
306
|
+
const outs = typeDef?.driver.outputs?.length || 0;
|
|
307
|
+
console.log(` ${node.type} [${ins}→${outs}] ${node.description || ''}`);
|
|
308
|
+
}
|
|
309
|
+
console.log();
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const total = listDrivers().length;
|
|
313
|
+
console.log(` Total: ${total} node types\n`);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Inspect a workflow — show structure without executing
|
|
318
|
+
* @param {string} filePath
|
|
319
|
+
*/
|
|
320
|
+
async function cmdInspect(filePath) {
|
|
321
|
+
console.log(`\n🔎 symbiote-node inspect: ${filePath}\n`);
|
|
322
|
+
const raw = await readFile(resolve(filePath), 'utf-8');
|
|
323
|
+
const data = JSON.parse(raw);
|
|
324
|
+
|
|
325
|
+
console.log(` Name: ${data.name || '(unnamed)'}`);
|
|
326
|
+
console.log(` ID: ${data.id || '(none)'}`);
|
|
327
|
+
console.log(` Version: ${data.version || '(none)'}`);
|
|
328
|
+
console.log();
|
|
329
|
+
|
|
330
|
+
// Nodes
|
|
331
|
+
console.log(` Nodes (${data.nodes?.length || 0}):`);
|
|
332
|
+
for (const node of (data.nodes || [])) {
|
|
333
|
+
const paramKeys = Object.keys(node.params || {});
|
|
334
|
+
const paramStr = paramKeys.length > 0 ? ` {${paramKeys.join(', ')}}` : '';
|
|
335
|
+
console.log(` ${node.id} [${node.type}] ${node.name || ''}${paramStr}`);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Connections
|
|
339
|
+
console.log(`\n Connections (${data.connections?.length || 0}):`);
|
|
340
|
+
for (const conn of (data.connections || [])) {
|
|
341
|
+
console.log(` ${conn.from}.${conn.out} → ${conn.to}.${conn.in}`);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Execution
|
|
345
|
+
if (data.execution) {
|
|
346
|
+
console.log(`\n Execution: mode=${data.execution.mode}, cache=${data.execution.cache}`);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
console.log();
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// ─── Main ────────────────────────────────────────────────────────────────────
|
|
353
|
+
|
|
354
|
+
const HELP = `
|
|
355
|
+
symbiote-node CLI — Universal node-based workflow runner
|
|
356
|
+
Commands:
|
|
357
|
+
run <file.workflow.json> Execute a workflow
|
|
358
|
+
validate <file.workflow.json> Validate without executing
|
|
359
|
+
list List all registered node types
|
|
360
|
+
inspect <file.workflow.json> Show workflow structure
|
|
361
|
+
serve <file.workflow.json> Start WebSocket + HTTP server
|
|
362
|
+
|
|
363
|
+
Options:
|
|
364
|
+
--pack <name> Load domain pack (e.g. "custom") --handlers <dir> Load handler files from directory
|
|
365
|
+
--secrets <path> Path to secrets.json
|
|
366
|
+
--port <number> Server port (default: 3100)
|
|
367
|
+
--verbose Show detailed execution log
|
|
368
|
+
`;
|
|
369
|
+
|
|
370
|
+
const { command, target, options } = parseArgs(process.argv);
|
|
371
|
+
|
|
372
|
+
switch (command) {
|
|
373
|
+
case 'run':
|
|
374
|
+
if (!target) { console.error('Usage: symbiote-node run <file.workflow.json>'); process.exit(1); } await cmdRun(target, options);
|
|
375
|
+
break;
|
|
376
|
+
|
|
377
|
+
case 'validate':
|
|
378
|
+
if (!target) { console.error('Usage: symbiote-node validate <file.workflow.json>'); process.exit(1); } await cmdValidate(target, options);
|
|
379
|
+
break;
|
|
380
|
+
|
|
381
|
+
case 'list':
|
|
382
|
+
await cmdList(options);
|
|
383
|
+
break;
|
|
384
|
+
|
|
385
|
+
case 'inspect':
|
|
386
|
+
if (!target) { console.error('Usage: symbiote-node inspect <file.workflow.json>'); process.exit(1); } await cmdInspect(target);
|
|
387
|
+
break;
|
|
388
|
+
|
|
389
|
+
case 'serve': {
|
|
390
|
+
const port = parseInt(options.port) || 3100;
|
|
391
|
+
await createServer({
|
|
392
|
+
port,
|
|
393
|
+
workflowFile: target,
|
|
394
|
+
handlersDir: options.handlers ? resolve(options.handlers) : undefined,
|
|
395
|
+
watchFiles: true,
|
|
396
|
+
verbose: !!options.verbose,
|
|
397
|
+
});
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
default:
|
|
402
|
+
console.log(HELP);
|
|
403
|
+
break;
|
|
404
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* symbiote-node - Universal node-based execution engine *
|
|
3
|
+
* AI-first, domain-agnostic graph runtime.
|
|
4
|
+
* Zero dependencies, pure ESM.
|
|
5
|
+
*
|
|
6
|
+
* @module symbiote-node */
|
|
7
|
+
|
|
8
|
+
// Core
|
|
9
|
+
export { Graph } from './Graph.js';
|
|
10
|
+
export { Executor } from './Executor.js';
|
|
11
|
+
export { History } from './History.js';
|
|
12
|
+
export { nanoid } from './nanoid.js';
|
|
13
|
+
|
|
14
|
+
// Registry (AI discovery)
|
|
15
|
+
export {
|
|
16
|
+
registerNodeType,
|
|
17
|
+
registerPack,
|
|
18
|
+
getNodeType,
|
|
19
|
+
listDrivers,
|
|
20
|
+
findCompatible,
|
|
21
|
+
findByCapability,
|
|
22
|
+
getNodeMenu,
|
|
23
|
+
registerCustomDrivers,
|
|
24
|
+
validateParams,
|
|
25
|
+
listPacks,
|
|
26
|
+
clearRegistry,
|
|
27
|
+
} from './Registry.js';
|
|
28
|
+
|
|
29
|
+
// Socket types
|
|
30
|
+
export {
|
|
31
|
+
registerSocketType,
|
|
32
|
+
registerSocketTypes,
|
|
33
|
+
getSocketType,
|
|
34
|
+
getAllSocketTypes,
|
|
35
|
+
areSocketsCompatible,
|
|
36
|
+
} from './SocketTypes.js';
|
|
37
|
+
|
|
38
|
+
// Persistence
|
|
39
|
+
export {
|
|
40
|
+
serialize,
|
|
41
|
+
deserialize,
|
|
42
|
+
saveToFile,
|
|
43
|
+
loadFromFile,
|
|
44
|
+
} from './Persistence.js';
|
|
45
|
+
|
|
46
|
+
// Lifecycle
|
|
47
|
+
export { runLifecycle } from './Lifecycle.js';
|
|
48
|
+
|
|
49
|
+
// Handler loader (Node.js only)
|
|
50
|
+
export { loadHandlers, watchHandlers } from './HandlerLoader.js';
|
|
51
|
+
|
|
52
|
+
// Agent UI commands
|
|
53
|
+
export * as AgentUI from './AgentUICommands.js';
|
|
54
|
+
|
|
55
|
+
// Graph server — import directly: import { createServer } from 'symbiote-node/engine/GraphServer.js'
|
|
56
|
+
// Not re-exported here because it requires 'ws' package (optional peer dependency)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* nanoid.js - Minimal ID generator
|
|
3
|
+
*
|
|
4
|
+
* Generates short unique IDs (8 chars) for nodes, workflows, etc.
|
|
5
|
+
* No dependencies. Crypto-based when available, Math.random fallback.
|
|
6
|
+
*
|
|
7
|
+
* @module agi-graph/nanoid
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz';
|
|
11
|
+
const ID_LENGTH = 8;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Generate a short unique ID
|
|
15
|
+
* @param {number} [length=8] - ID length
|
|
16
|
+
* @returns {string}
|
|
17
|
+
*/
|
|
18
|
+
export function nanoid(length = ID_LENGTH) {
|
|
19
|
+
let id = '';
|
|
20
|
+
const bytes = typeof globalThis.crypto?.getRandomValues === 'function'
|
|
21
|
+
? globalThis.crypto.getRandomValues(new Uint8Array(length))
|
|
22
|
+
: Array.from({ length }, () => Math.floor(Math.random() * 256));
|
|
23
|
+
|
|
24
|
+
for (let i = 0; i < length; i++) {
|
|
25
|
+
id += ALPHABET[bytes[i] % ALPHABET.length];
|
|
26
|
+
}
|
|
27
|
+
return id;
|
|
28
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "symbiote-node",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Universal node-based execution engine. Domain-agnostic graph runtime.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"symbiote-node": "./cli.js" },
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./index.js",
|
|
11
|
+
"./cli": "./cli.js",
|
|
12
|
+
"./packs/*": "./packs/*"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"*.js",
|
|
16
|
+
"packs/"
|
|
17
|
+
],
|
|
18
|
+
"keywords": [
|
|
19
|
+
"graph",
|
|
20
|
+
"nodes",
|
|
21
|
+
"workflow",
|
|
22
|
+
"dag",
|
|
23
|
+
"automation",
|
|
24
|
+
"execution-engine" ],
|
|
25
|
+
"license": "MIT"
|
|
26
|
+
}
|