@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.
@@ -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
@@ -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 SVG or interactive HTML diagrams from workflow files.
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 SVG or interactive HTML diagrams from workflow files.
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 an SVG diagram of a workflow. Returns SVG string or writes to a file.', {
13
- filePath: z.string().describe('Path to the workflow .ts file'),
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 for the SVG. If omitted, returns SVG as text.'),
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) or html (interactive viewer)'),
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
- const resolvedPath = path.resolve(args.filePath);
37
- if (!fs.existsSync(resolvedPath)) {
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
- const isHtml = args.format === 'html';
46
- const result = isHtml
47
- ? fileToHTML(resolvedPath, diagramOptions)
48
- : fileToSVG(resolvedPath, diagramOptions);
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');
@@ -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 second parameter if it exists
1802
+ // Extract data ports from parameters beyond execute
1803
1803
  if (params.length > 1) {
1804
- const dataParam = params[1];
1805
- const dataParamType = dataParam.getType();
1806
- const properties = dataParamType.getProperties();
1807
- properties.forEach((prop) => {
1808
- const propName = prop.getName();
1809
- const propType = prop.getTypeAtLocation(dataParam);
1810
- const portType = this.inferPortType(propType);
1811
- const propTypeText = propType.getText();
1812
- // Extract schema for complex types (interfaces/objects)
1813
- const tsSchema = portType === 'OBJECT' ? this.extractTypeSchema(propType) : undefined;
1814
- // Check if JSDoc has explicit metadata override for this start port (from @param annotation)
1815
- // Always prefer ts-morph inferred type over JSDoc regex-based inference
1816
- // (JSDoc @param type inference uses regex which can fail for complex types)
1817
- const startPortConfig = config?.startPorts?.[propName];
1818
- ports[propName] = {
1819
- dataType: portType,
1820
- label: startPortConfig?.label || this.capitalize(propName),
1821
- ...(startPortConfig?.metadata && { metadata: startPortConfig.metadata }),
1822
- // Include original TS type for rich type display
1823
- ...(propTypeText && { tsType: propTypeText }),
1824
- // Include schema breakdown for complex types
1825
- ...(tsSchema && Object.keys(tsSchema).length > 0 && { tsSchema }),
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synergenius/flow-weaver",
3
- "version": "0.10.7",
3
+ "version": "0.10.9",
4
4
  "description": "Deterministic workflow compiler for AI agents. Compiles to standalone TypeScript, no runtime dependencies.",
5
5
  "private": false,
6
6
  "type": "module",