@synergenius/flow-weaver 0.10.7 → 0.10.9
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/LICENSE +101 -20
- package/dist/cli/commands/describe.d.ts +2 -2
- package/dist/cli/commands/describe.js +6 -0
- package/dist/cli/commands/diagram.d.ts +2 -2
- package/dist/cli/commands/diagram.js +13 -6
- package/dist/cli/flow-weaver.mjs +964 -361
- package/dist/cli/index.js +2 -2
- package/dist/diagram/ascii-renderer.d.ts +13 -0
- package/dist/diagram/ascii-renderer.js +528 -0
- package/dist/diagram/index.d.ts +4 -0
- package/dist/diagram/index.js +26 -0
- package/dist/diagram/types.d.ts +1 -1
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.js +1 -0
- package/dist/mcp/prompts.d.ts +3 -0
- package/dist/mcp/prompts.js +68 -0
- package/dist/mcp/server.js +2 -0
- package/dist/mcp/tools-diagram.d.ts +2 -1
- package/dist/mcp/tools-diagram.js +50 -14
- package/dist/mcp/tools-query.js +2 -2
- package/dist/parser.d.ts +6 -0
- package/dist/parser.js +59 -25
- package/package.json +1 -1
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const NOCODE_SYSTEM_PROMPT = `You are a workflow developer powered by Flow Weaver. You write real, production-quality TypeScript behind the scenes, but you present everything to the user as plain-language step descriptions, connections, and diagrams. The user never needs to see code unless they ask for it.
|
|
2
|
+
|
|
3
|
+
## How you work
|
|
4
|
+
|
|
5
|
+
When the user describes a workflow:
|
|
6
|
+
|
|
7
|
+
1. Break their description into discrete steps (nodes), each with typed inputs and outputs.
|
|
8
|
+
2. Call fw_create_model to generate the workflow skeleton with declare stubs.
|
|
9
|
+
3. Call fw_implement_node for each step, writing real TypeScript function bodies that do what the user described. Write actual working code, not placeholder comments.
|
|
10
|
+
4. Call fw_validate to check the result. If there are errors you can fix (missing connections, type mismatches), fix them silently with fw_modify. Only tell the user about problems you genuinely cannot resolve.
|
|
11
|
+
5. Show the result as:
|
|
12
|
+
- A numbered list of steps with one-sentence descriptions
|
|
13
|
+
- An ASCII diagram (call fw_diagram with format "ascii-compact")
|
|
14
|
+
Never show TypeScript code in this summary.
|
|
15
|
+
|
|
16
|
+
When the user asks to modify an existing workflow:
|
|
17
|
+
- Use fw_modify or fw_modify_batch for structural changes (add/remove nodes, connections).
|
|
18
|
+
- Use fw_implement_node to update a step's logic.
|
|
19
|
+
- Re-validate after every change.
|
|
20
|
+
|
|
21
|
+
## When to show code
|
|
22
|
+
|
|
23
|
+
Only reveal TypeScript when the user explicitly asks: "show me the code", "let me see the implementation", "what does step X look like", etc. When showing code, use fw_describe with format "text" or read the file directly.
|
|
24
|
+
|
|
25
|
+
## Tool usage patterns
|
|
26
|
+
|
|
27
|
+
- fw_create_model: Create new workflows from a structured description (steps, inputs/outputs, flow path).
|
|
28
|
+
- fw_implement_node: Replace a declare stub with a real function body.
|
|
29
|
+
- fw_modify / fw_modify_batch: Add/remove nodes and connections, rename nodes, reposition.
|
|
30
|
+
- fw_validate: Always run after changes. Fix what you can, report what you cannot.
|
|
31
|
+
- fw_describe: Inspect workflow structure. Use format "text" for human-readable, "json" for programmatic.
|
|
32
|
+
- fw_diagram: Generate visual representation. Prefer format "ascii-compact" for chat.
|
|
33
|
+
- fw_workflow_status: Check which steps are implemented vs still stubs.
|
|
34
|
+
- fw_scaffold / fw_list_templates: Bootstrap from templates when the user's request matches a known pattern.
|
|
35
|
+
- fw_docs: Look up Flow Weaver features (scoped ports, branching, iteration, agents) when you need specifics.
|
|
36
|
+
- fw_find_workflows: Locate existing workflow files in a directory.
|
|
37
|
+
- fw_compile: Generate executable JavaScript from the workflow source.
|
|
38
|
+
|
|
39
|
+
## Interaction style
|
|
40
|
+
|
|
41
|
+
- Ask 1-2 clarifying questions when the request is vague, not a long interrogation list.
|
|
42
|
+
- Default to sensible choices for things the user did not specify (file names, port types, node names).
|
|
43
|
+
- After creating or modifying a workflow, always show the step summary and ASCII diagram.
|
|
44
|
+
- Use fw_docs to look up advanced features before guessing at syntax.
|
|
45
|
+
|
|
46
|
+
## File conventions
|
|
47
|
+
|
|
48
|
+
- Workflow files use .ts extension.
|
|
49
|
+
- Default location is the current working directory.
|
|
50
|
+
- Node names use camelCase (e.g., validateEmail, sendNotification).
|
|
51
|
+
- Workflow names use PascalCase (e.g., EmailValidation, OrderProcessing).`;
|
|
52
|
+
export function registerPrompts(mcp) {
|
|
53
|
+
mcp.registerPrompt('flow-weaver-nocode', {
|
|
54
|
+
title: 'Flow Weaver No-Code Mode',
|
|
55
|
+
description: 'Describe workflows in plain language. The agent writes real TypeScript behind the scenes and presents results as step summaries and diagrams. Code is available on request.',
|
|
56
|
+
}, () => ({
|
|
57
|
+
messages: [
|
|
58
|
+
{
|
|
59
|
+
role: 'assistant',
|
|
60
|
+
content: {
|
|
61
|
+
type: 'text',
|
|
62
|
+
text: NOCODE_SYSTEM_PROMPT,
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=prompts.js.map
|
package/dist/mcp/server.js
CHANGED
|
@@ -14,6 +14,7 @@ import { registerDiagramTools } from './tools-diagram.js';
|
|
|
14
14
|
import { registerDocsTools } from './tools-docs.js';
|
|
15
15
|
import { registerModelTools } from './tools-model.js';
|
|
16
16
|
import { registerResources } from './resources.js';
|
|
17
|
+
import { registerPrompts } from './prompts.js';
|
|
17
18
|
function parseEventFilterFromEnv() {
|
|
18
19
|
const filter = {};
|
|
19
20
|
const include = process.env.FW_EVENT_INCLUDE;
|
|
@@ -75,6 +76,7 @@ export async function startMcpServer(options) {
|
|
|
75
76
|
registerDocsTools(mcp);
|
|
76
77
|
registerModelTools(mcp);
|
|
77
78
|
registerResources(mcp, connection, buffer);
|
|
79
|
+
registerPrompts(mcp);
|
|
78
80
|
// Connect transport (only in stdio MCP mode)
|
|
79
81
|
if (!options._testDeps && options.stdio) {
|
|
80
82
|
const transport = new StdioServerTransport();
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MCP Diagram Tool - fw_diagram
|
|
3
3
|
*
|
|
4
|
-
* Generates
|
|
4
|
+
* Generates diagrams from workflow files or inline source code.
|
|
5
|
+
* Supports SVG, interactive HTML, and ASCII text formats.
|
|
5
6
|
*/
|
|
6
7
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
7
8
|
export declare function registerDiagramTools(mcp: McpServer): void;
|
|
@@ -1,20 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MCP Diagram Tool - fw_diagram
|
|
3
3
|
*
|
|
4
|
-
* Generates
|
|
4
|
+
* Generates diagrams from workflow files or inline source code.
|
|
5
|
+
* Supports SVG, interactive HTML, and ASCII text formats.
|
|
5
6
|
*/
|
|
6
7
|
import { z } from 'zod';
|
|
7
8
|
import * as fs from 'fs';
|
|
8
9
|
import * as path from 'path';
|
|
9
|
-
import { fileToSVG, fileToHTML } from '../diagram/index.js';
|
|
10
|
+
import { fileToSVG, fileToHTML, fileToASCII, sourceToSVG, sourceToHTML, sourceToASCII } from '../diagram/index.js';
|
|
10
11
|
import { makeToolResult, makeErrorResult } from './response-utils.js';
|
|
12
|
+
const ASCII_FORMATS = new Set(['ascii', 'ascii-compact', 'text']);
|
|
11
13
|
export function registerDiagramTools(mcp) {
|
|
12
|
-
mcp.tool('fw_diagram', 'Generate
|
|
13
|
-
|
|
14
|
+
mcp.tool('fw_diagram', 'Generate a diagram of a workflow. Formats: svg/html produce visual markup, ascii/ascii-compact/text produce plain text readable in terminal. ' +
|
|
15
|
+
'Provide either filePath (workflow .ts file) or source (inline code).', {
|
|
16
|
+
filePath: z
|
|
17
|
+
.string()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe('Path to the workflow .ts file (required if source is not provided)'),
|
|
20
|
+
source: z
|
|
21
|
+
.string()
|
|
22
|
+
.optional()
|
|
23
|
+
.describe('Inline workflow source code (required if filePath is not provided)'),
|
|
14
24
|
outputPath: z
|
|
15
25
|
.string()
|
|
16
26
|
.optional()
|
|
17
|
-
.describe('Output file path
|
|
27
|
+
.describe('Output file path. If omitted, returns content as text.'),
|
|
18
28
|
workflowName: z
|
|
19
29
|
.string()
|
|
20
30
|
.optional()
|
|
@@ -28,24 +38,50 @@ export function registerDiagramTools(mcp) {
|
|
|
28
38
|
.optional()
|
|
29
39
|
.describe('Show port labels on diagram (default: true)'),
|
|
30
40
|
format: z
|
|
31
|
-
.enum(['svg', 'html'])
|
|
41
|
+
.enum(['svg', 'html', 'ascii', 'ascii-compact', 'text'])
|
|
32
42
|
.optional()
|
|
33
|
-
.describe('Output format: svg (default)
|
|
43
|
+
.describe('Output format: svg (default), html (interactive viewer), ascii (port-level detail), ascii-compact (compact boxes), text (structured list)'),
|
|
34
44
|
}, async (args) => {
|
|
35
45
|
try {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
return makeErrorResult('FILE_NOT_FOUND', `File not found: ${resolvedPath}`);
|
|
46
|
+
if (!args.filePath && !args.source) {
|
|
47
|
+
return makeErrorResult('MISSING_PARAM', 'Provide either filePath or source parameter');
|
|
39
48
|
}
|
|
49
|
+
const format = args.format ?? 'svg';
|
|
40
50
|
const diagramOptions = {
|
|
41
51
|
workflowName: args.workflowName,
|
|
42
52
|
theme: args.theme,
|
|
43
53
|
showPortLabels: args.showPortLabels,
|
|
54
|
+
format,
|
|
44
55
|
};
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
56
|
+
let result;
|
|
57
|
+
if (args.source) {
|
|
58
|
+
// Inline source code
|
|
59
|
+
if (ASCII_FORMATS.has(format)) {
|
|
60
|
+
result = sourceToASCII(args.source, diagramOptions);
|
|
61
|
+
}
|
|
62
|
+
else if (format === 'html') {
|
|
63
|
+
result = sourceToHTML(args.source, diagramOptions);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
result = sourceToSVG(args.source, diagramOptions);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
// File path
|
|
71
|
+
const resolvedPath = path.resolve(args.filePath);
|
|
72
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
73
|
+
return makeErrorResult('FILE_NOT_FOUND', `File not found: ${resolvedPath}`);
|
|
74
|
+
}
|
|
75
|
+
if (ASCII_FORMATS.has(format)) {
|
|
76
|
+
result = fileToASCII(resolvedPath, diagramOptions);
|
|
77
|
+
}
|
|
78
|
+
else if (format === 'html') {
|
|
79
|
+
result = fileToHTML(resolvedPath, diagramOptions);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
result = fileToSVG(resolvedPath, diagramOptions);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
49
85
|
if (args.outputPath) {
|
|
50
86
|
const outputResolved = path.resolve(args.outputPath);
|
|
51
87
|
fs.writeFileSync(outputResolved, result, 'utf-8');
|
package/dist/mcp/tools-query.js
CHANGED
|
@@ -21,9 +21,9 @@ export function registerQueryTools(mcp) {
|
|
|
21
21
|
mcp.tool('fw_describe', 'Describe a workflow in LLM-friendly format (nodes, connections, graph, validation).', {
|
|
22
22
|
filePath: z.string().describe('Path to the workflow .ts file'),
|
|
23
23
|
format: z
|
|
24
|
-
.enum(['json', 'text', 'mermaid', 'paths'])
|
|
24
|
+
.enum(['json', 'text', 'mermaid', 'paths', 'ascii', 'ascii-compact'])
|
|
25
25
|
.optional()
|
|
26
|
-
.describe('Output format (default: json)'),
|
|
26
|
+
.describe('Output format (default: json). ascii/ascii-compact produce terminal-readable diagrams.'),
|
|
27
27
|
node: z.string().optional().describe('Focus on a specific node ID'),
|
|
28
28
|
workflowName: z.string().optional().describe('Specific workflow if file has multiple'),
|
|
29
29
|
}, async (args) => {
|
package/dist/parser.d.ts
CHANGED
|
@@ -166,6 +166,12 @@ export declare class AnnotationParser {
|
|
|
166
166
|
* Returns a map of property names to their TypeScript type strings.
|
|
167
167
|
*/
|
|
168
168
|
private extractTypeSchema;
|
|
169
|
+
/**
|
|
170
|
+
* Check if a type should be expanded into individual ports via getProperties().
|
|
171
|
+
* Returns true for object literals and interfaces, false for primitives, arrays,
|
|
172
|
+
* and built-in types whose properties are prototype methods (string, number, etc.).
|
|
173
|
+
*/
|
|
174
|
+
private isExpandableObjectType;
|
|
169
175
|
private inferPortType;
|
|
170
176
|
/**
|
|
171
177
|
* Check if a function has a valid @flowWeaver annotation (nodeType, workflow, or pattern).
|
package/dist/parser.js
CHANGED
|
@@ -1799,32 +1799,52 @@ export class AnnotationParser {
|
|
|
1799
1799
|
else {
|
|
1800
1800
|
ports.execute = { dataType: 'STEP', tsType: 'boolean', label: 'Execute' };
|
|
1801
1801
|
}
|
|
1802
|
-
// Extract data ports from
|
|
1802
|
+
// Extract data ports from parameters beyond execute
|
|
1803
1803
|
if (params.length > 1) {
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
const
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
const
|
|
1812
|
-
|
|
1813
|
-
const
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1804
|
+
// Filter out __abortSignal__ which is injected by generateInPlace and
|
|
1805
|
+
// is not a user-visible port.
|
|
1806
|
+
const dataParams = params.slice(1).filter(p => p.getName() !== '__abortSignal__');
|
|
1807
|
+
// Multiple separate params: each becomes its own port
|
|
1808
|
+
// Single param with object type: expand its properties into ports
|
|
1809
|
+
const shouldExpandProperties = dataParams.length === 1 && this.isExpandableObjectType(dataParams[0].getType());
|
|
1810
|
+
if (shouldExpandProperties) {
|
|
1811
|
+
const dataParam = dataParams[0];
|
|
1812
|
+
const dataParamType = dataParam.getType();
|
|
1813
|
+
const properties = dataParamType.getProperties();
|
|
1814
|
+
properties.forEach((prop) => {
|
|
1815
|
+
const propName = prop.getName();
|
|
1816
|
+
const propType = prop.getTypeAtLocation(dataParam);
|
|
1817
|
+
const portType = this.inferPortType(propType);
|
|
1818
|
+
const propTypeText = propType.getText();
|
|
1819
|
+
const tsSchema = portType === 'OBJECT' ? this.extractTypeSchema(propType) : undefined;
|
|
1820
|
+
const startPortConfig = config?.startPorts?.[propName];
|
|
1821
|
+
ports[propName] = {
|
|
1822
|
+
dataType: portType,
|
|
1823
|
+
label: startPortConfig?.label || this.capitalize(propName),
|
|
1824
|
+
...(startPortConfig?.metadata && { metadata: startPortConfig.metadata }),
|
|
1825
|
+
...(propTypeText && { tsType: propTypeText }),
|
|
1826
|
+
...(tsSchema && Object.keys(tsSchema).length > 0 && { tsSchema }),
|
|
1827
|
+
};
|
|
1828
|
+
});
|
|
1829
|
+
}
|
|
1830
|
+
else {
|
|
1831
|
+
// Each parameter becomes its own port
|
|
1832
|
+
for (const param of dataParams) {
|
|
1833
|
+
const paramName = param.getName();
|
|
1834
|
+
const paramType = param.getType();
|
|
1835
|
+
const portType = this.inferPortType(paramType);
|
|
1836
|
+
const paramTypeText = paramType.getText(param);
|
|
1837
|
+
const tsSchema = portType === 'OBJECT' ? this.extractTypeSchema(paramType) : undefined;
|
|
1838
|
+
const startPortConfig = config?.startPorts?.[paramName];
|
|
1839
|
+
ports[paramName] = {
|
|
1840
|
+
dataType: portType,
|
|
1841
|
+
label: startPortConfig?.label || this.capitalize(paramName),
|
|
1842
|
+
...(startPortConfig?.metadata && { metadata: startPortConfig.metadata }),
|
|
1843
|
+
...(paramTypeText && { tsType: paramTypeText }),
|
|
1844
|
+
...(tsSchema && Object.keys(tsSchema).length > 0 && { tsSchema }),
|
|
1845
|
+
};
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1828
1848
|
}
|
|
1829
1849
|
}
|
|
1830
1850
|
else {
|
|
@@ -1923,6 +1943,20 @@ export class AnnotationParser {
|
|
|
1923
1943
|
}
|
|
1924
1944
|
return Object.keys(schema).length > 0 ? schema : undefined;
|
|
1925
1945
|
}
|
|
1946
|
+
/**
|
|
1947
|
+
* Check if a type should be expanded into individual ports via getProperties().
|
|
1948
|
+
* Returns true for object literals and interfaces, false for primitives, arrays,
|
|
1949
|
+
* and built-in types whose properties are prototype methods (string, number, etc.).
|
|
1950
|
+
*/
|
|
1951
|
+
isExpandableObjectType(tsType) {
|
|
1952
|
+
const typeText = tsType.getText();
|
|
1953
|
+
const primitiveTypes = new Set(['string', 'number', 'boolean', 'any', 'unknown', 'never', 'object', 'Object']);
|
|
1954
|
+
if (primitiveTypes.has(typeText))
|
|
1955
|
+
return false;
|
|
1956
|
+
if (typeText.endsWith('[]') || typeText.startsWith('Array<'))
|
|
1957
|
+
return false;
|
|
1958
|
+
return tsType.isObject() && tsType.getProperties().length > 0;
|
|
1959
|
+
}
|
|
1926
1960
|
inferPortType(tsType) {
|
|
1927
1961
|
const typeText = tsType.getText();
|
|
1928
1962
|
// Delegate to inferDataTypeFromTS for consistent type mapping
|
package/package.json
CHANGED