@synergenius/flow-weaver 0.29.1 → 0.30.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/README.md CHANGED
@@ -18,15 +18,27 @@ Flow Weaver is a TypeScript workflow compiler. Define workflows with JSDoc annot
18
18
 
19
19
  Build AI agent workflows, data pipelines, or automation scripts through natural language using MCP tools in Claude Code, Cursor, Windsurf, VS Code, JetBrains, Codex, or any MCP-compatible editor. Or write them by hand with annotations the same way you write JSDoc today.
20
20
 
21
- ## Project Status
21
+ ## About me & my vision
22
+
23
+ Hey, I am [Ricardo Morais](https://linkedin.com/in/moraispgsi), the project's founder and owner.
24
+
25
+ I have always wondered why Visual Programming never took off. After understanding why I tried to address most of the concerns from the development community:
26
+ - No lock-in: code is compiled and yours, not dependent on this library. You can remove the annotation after compiling and the file is standalone.
27
+ - The code is the source of truth, visual part runs on top, this allows you to create your workflows visually in the Studio, at the end of the day it is just plain Typescript code that you or any LLM can understand and iterate.
28
+ - It being just code opens a lot of doors for you, like using Git for version control, doing all sort of stuff you already do with code like testing, linting, automating, everything.
29
+ - In the era of LLMs and agents, being able to have that control is crucial, it is not a black box, you can see everything and so can your assistant.
22
30
 
23
- Flow Weaver is in **active alpha**. The compiler, validator, CLI, and MCP tools are stable and thoroughly tested. The test suite covers thousands of cases across parsing, compilation, validation, diffing, and deployment. CI runs on every commit.
31
+ As of now this is a solo project and I am building it in the open. There is a lot to do and rough edges to smooth out. If you run into issues or have questions, the [Discord](https://discord.gg/6Byh3ur2bk) is the best place to reach out. Every issue and question gets a response. I genuinely want to hear from you.
32
+
33
+ If this resonates with what you have been looking for, give it a try and let me know how it goes. Stars, feedback, and honest criticism all help equally.
34
+
35
+ ## Project Status
24
36
 
25
- This started as a curiosity by [Ricardo Morais](https://linkedin.com/in/moraispgsi) into why visual programming never took off and whether code-first and visual editing could actually coexist. It has grown into something real. It is actively maintained, with releases shipping regularly. If you run into issues or have questions, the [Discord](https://discord.gg/6Byh3ur2bk) is the best place to reach out. Every issue and question gets a response, usually within a day.
37
+ Flow Weaver is in **beta**. The compiler, validator, CLI, and MCP tools are stable and thoroughly tested. The test suite covers thousands of cases across parsing, compilation, validation, diffing, and deployment. CI runs on every commit.
26
38
 
27
39
  **Flow Weaver Studio**, the browser-based visual IDE, is the next major milestone and is being actively built. Core editing (canvas, terminal, diagnostics) is live today. The visual debugger, AI chat assistant, version history, and deployment dashboard are in progress. You can follow development on Discord or in the [GitHub releases](https://github.com/synergenius-fw/flow-weaver/releases).
28
40
 
29
- Breaking changes may occur between minor versions during alpha. Pin your version in `package.json` if stability matters for your project.
41
+ Breaking changes may still occur between minor versions during beta. Pin your version in `package.json` if stability matters for your project.
30
42
 
31
43
  ## Capabilities
32
44
 
@@ -224,11 +236,11 @@ export async function supportAgent(
224
236
 
225
237
  Run `fw --help` or `fw <command> --help` for full options.
226
238
 
227
- ## Flow Weaver Studio (Early Beta)
239
+ ## Flow Weaver Studio (Beta)
228
240
 
229
241
  A browser-based visual IDE for building workflows. Canvas editor, integrated terminal, real-time diagnostics, and the full CLI available in the cloud.
230
242
 
231
- Studio is in early beta. Core editing is live. The visual debugger, AI chat assistant, version history, and deployment dashboard are being actively developed and will ship incrementally. The cloud platform is not yet production-ready, and paid plans are not available yet. Use it to explore and experiment, but expect rough edges.
243
+ Studio is in beta. Core editing is live. The visual debugger, AI chat assistant, version history, and deployment dashboard are being actively developed and will ship incrementally. The cloud platform is not yet production-ready, and paid plans are not available yet. Use it to explore and experiment, but expect rough edges.
232
244
 
233
245
  [Open Studio](https://flowweaver.ai/studio) · [Learn more](https://flowweaver.ai/features)
234
246
 
@@ -137,48 +137,50 @@ export async function compileCommand(input, options = {}) {
137
137
  errorCount++;
138
138
  continue;
139
139
  }
140
- // Validate the AST (especially for --strict mode)
141
- if (strict) {
142
- const validation = validator.validate(parseResult.ast, { strictMode: true });
143
- if (validation.errors.length > 0) {
144
- logger.error(` ${fileName}`);
145
- validation.errors.forEach((err) => {
146
- const friendly = getFriendlyError(err);
147
- if (friendly) {
148
- const loc = err.location ? `[line ${err.location.line}] ` : '';
149
- logger.error(` ${loc}${friendly.title}: ${friendly.explanation}`);
150
- logger.warn(` How to fix: ${friendly.fix}`);
151
- if (err.docUrl) {
152
- logger.warn(` See: ${err.docUrl}`);
153
- }
140
+ // Validate the AST
141
+ const validation = validator.validate(parseResult.ast, { strictMode: strict });
142
+ // In strict mode, validation errors block compilation
143
+ if (strict && validation.errors.length > 0) {
144
+ logger.error(` ${fileName}`);
145
+ validation.errors.forEach((err) => {
146
+ const friendly = getFriendlyError(err);
147
+ if (friendly) {
148
+ const loc = err.location ? `[line ${err.location.line}] ` : '';
149
+ logger.error(` ${loc}${friendly.title}: ${friendly.explanation}`);
150
+ logger.warn(` How to fix: ${friendly.fix}`);
151
+ if (err.docUrl) {
152
+ logger.warn(` See: ${err.docUrl}`);
154
153
  }
155
- else {
156
- let msg = ` ${err.message}`;
157
- if (err.node) {
158
- msg += ` (node: ${err.node})`;
159
- }
160
- logger.error(msg);
161
- if (err.docUrl) {
162
- logger.warn(` See: ${err.docUrl}`);
163
- }
154
+ }
155
+ else {
156
+ let msg = ` ${err.message}`;
157
+ if (err.node) {
158
+ msg += ` (node: ${err.node})`;
164
159
  }
165
- });
166
- errorCount++;
167
- continue;
168
- }
169
- if (validation.warnings.length > 0 && verbose) {
170
- validation.warnings.forEach((warn) => {
171
- const friendly = getFriendlyError(warn);
172
- if (friendly) {
173
- const loc = warn.location ? `[line ${warn.location.line}] ` : '';
174
- logger.warn(` ${loc}${friendly.title}: ${friendly.explanation}`);
175
- logger.warn(` How to fix: ${friendly.fix}`);
160
+ logger.error(msg);
161
+ if (err.docUrl) {
162
+ logger.warn(` See: ${err.docUrl}`);
176
163
  }
177
- else {
178
- logger.warn(` ${warn.message}`);
164
+ }
165
+ });
166
+ errorCount++;
167
+ continue;
168
+ }
169
+ // Always show validation warnings (not just in verbose mode)
170
+ if (validation.warnings.length > 0) {
171
+ validation.warnings.forEach((warn) => {
172
+ const friendly = getFriendlyError(warn);
173
+ if (friendly) {
174
+ const loc = warn.location ? `[line ${warn.location.line}] ` : '';
175
+ logger.warn(` ${loc}${friendly.title}: ${friendly.explanation}`);
176
+ if (verbose) {
177
+ logger.warn(` How to fix: ${friendly.fix}`);
179
178
  }
180
- });
181
- }
179
+ }
180
+ else {
181
+ logger.warn(` ${warn.message}`);
182
+ }
183
+ });
182
184
  }
183
185
  // Read original source
184
186
  const sourceCode = fs.readFileSync(file, 'utf8');
@@ -349,7 +349,7 @@ export async function describeCommand(input, options = {}) {
349
349
  }
350
350
  try {
351
351
  // Parse the workflow
352
- const parseResult = await parseWorkflow(filePath, { workflowName });
352
+ const parseResult = await parseWorkflow(filePath, { workflowName, projectDir: path.dirname(filePath) });
353
353
  if (parseResult.errors.length > 0) {
354
354
  throw new Error(`Parse errors:\n${parseResult.errors.map((err) => ` ${err}`).join('\n')}`);
355
355
  }
@@ -13,6 +13,10 @@ export interface DevOptions extends DevModeOptions {
13
13
  clean?: boolean;
14
14
  /** Compilation target (default: typescript in-place) */
15
15
  target?: string;
16
+ /** Mock config for built-in nodes as JSON string */
17
+ mocks?: string;
18
+ /** Path to JSON file with mock config */
19
+ mocksFile?: string;
16
20
  }
17
21
  /**
18
22
  * Dev command: watch + compile + run in a single loop.
@@ -52,11 +52,38 @@ function parseParams(options) {
52
52
  }
53
53
  return {};
54
54
  }
55
+ /**
56
+ * Parse mock config from --mocks or --mocks-file.
57
+ */
58
+ function parseMocks(options) {
59
+ if (options.mocks) {
60
+ try {
61
+ return JSON.parse(options.mocks);
62
+ }
63
+ catch {
64
+ throw new Error(`Invalid JSON in --mocks: ${options.mocks}`);
65
+ }
66
+ }
67
+ if (options.mocksFile) {
68
+ const mocksFilePath = path.resolve(options.mocksFile);
69
+ if (!fs.existsSync(mocksFilePath)) {
70
+ throw new Error(`Mocks file not found: ${mocksFilePath}`);
71
+ }
72
+ try {
73
+ const content = fs.readFileSync(mocksFilePath, 'utf8');
74
+ return JSON.parse(content);
75
+ }
76
+ catch {
77
+ throw new Error(`Failed to parse mocks file: ${options.mocksFile}`);
78
+ }
79
+ }
80
+ return undefined;
81
+ }
55
82
  /**
56
83
  * Run a single compile + execute cycle.
57
84
  * Returns true if both compile and run succeeded.
58
85
  */
59
- async function compileAndRun(filePath, params, options) {
86
+ async function compileAndRun(filePath, params, mocks, options) {
60
87
  // Step 1: Compile
61
88
  const compileOpts = {
62
89
  format: options.format,
@@ -96,6 +123,7 @@ async function compileAndRun(filePath, params, options) {
96
123
  workflowName: options.workflow,
97
124
  production: options.production ?? false,
98
125
  includeTrace: !options.production,
126
+ mocks,
99
127
  });
100
128
  if (options.json) {
101
129
  process.stdout.write(JSON.stringify({
@@ -143,16 +171,20 @@ export async function devCommand(input, options = {}) {
143
171
  throw new Error(`Unknown dev target "${options.target}". ${available.length ? `Available: ${available.join(', ')}` : 'No dev mode providers registered. Install a pack that provides one.'}`);
144
172
  }
145
173
  const params = parseParams(options);
174
+ const mocks = parseMocks(options);
146
175
  if (!options.json) {
147
176
  logger.section('Dev Mode');
148
177
  logger.info(`File: ${path.basename(filePath)}`);
149
178
  if (Object.keys(params).length > 0) {
150
179
  logger.info(`Params: ${JSON.stringify(params)}`);
151
180
  }
181
+ if (mocks) {
182
+ logger.info(`Mocks: ${JSON.stringify(mocks)}`);
183
+ }
152
184
  logger.newline();
153
185
  }
154
186
  // Initial compile + run
155
- await compileAndRun(filePath, params, options);
187
+ await compileAndRun(filePath, params, mocks, options);
156
188
  // If --once, exit after first cycle
157
189
  if (options.once) {
158
190
  return;
@@ -173,7 +205,7 @@ export async function devCommand(input, options = {}) {
173
205
  if (!options.json) {
174
206
  cycleSeparator(file);
175
207
  }
176
- await compileAndRun(filePath, params, options);
208
+ await compileAndRun(filePath, params, mocks, options);
177
209
  });
178
210
  // Handle process termination
179
211
  const cleanup = () => {
@@ -5,6 +5,7 @@
5
5
  import * as fs from 'fs';
6
6
  import * as path from 'path';
7
7
  import { fileToSVG, fileToHTML, fileToASCII } from '../../diagram/index.js';
8
+ import { parser } from '../../parser.js';
8
9
  import { logger } from '../utils/logger.js';
9
10
  import { safeWriteFile } from '../utils/safe-write.js';
10
11
  const ASCII_FORMATS = new Set(['ascii', 'ascii-compact', 'text']);
@@ -14,6 +15,8 @@ export async function diagramCommand(input, options = {}) {
14
15
  if (!fs.existsSync(filePath)) {
15
16
  throw new Error(`File not found: ${filePath}`);
16
17
  }
18
+ // Load marketplace pack tag handlers before parsing
19
+ await parser.loadPackHandlers(path.dirname(filePath));
17
20
  let result;
18
21
  if (ASCII_FORMATS.has(format)) {
19
22
  result = fileToASCII(filePath, { ...diagramOptions, format });
@@ -21,8 +21,8 @@ export async function diffCommand(file1, file2, options = {}) {
21
21
  try {
22
22
  // Parse both workflows
23
23
  const [result1, result2] = await Promise.all([
24
- parseWorkflow(filePath1, { workflowName }),
25
- parseWorkflow(filePath2, { workflowName }),
24
+ parseWorkflow(filePath1, { workflowName, projectDir: path.dirname(filePath1) }),
25
+ parseWorkflow(filePath2, { workflowName, projectDir: path.dirname(filePath2) }),
26
26
  ]);
27
27
  if (result1.errors.length > 0) {
28
28
  throw new Error(`Parse errors in ${file1}:\n${result1.errors.map((err) => ` ${err}`).join('\n')}`);
@@ -184,7 +184,7 @@ async function runCommandInner(input, options) {
184
184
  let executionOrder = resumeExecutionOrder;
185
185
  if (!executionOrder) {
186
186
  const source = fs.readFileSync(filePath, 'utf8');
187
- const parsed = await parseWorkflow(source, { workflowName: options.workflow });
187
+ const parsed = await parseWorkflow(source, { workflowName: options.workflow, projectDir: path.dirname(filePath) });
188
188
  if (parsed.errors.length === 0) {
189
189
  executionOrder = getTopologicalOrder(parsed.ast);
190
190
  }
@@ -427,7 +427,7 @@ export async function validateMockConfig(mocks, filePath, workflowName) {
427
427
  }
428
428
  // Quick-parse the workflow to check which built-in node types are used
429
429
  try {
430
- const result = await parseWorkflow(filePath, { workflowName });
430
+ const result = await parseWorkflow(filePath, { workflowName, projectDir: path.dirname(filePath) });
431
431
  if (result.errors.length > 0 || !result.ast?.instances)
432
432
  return;
433
433
  const usedNodeTypes = new Set(result.ast.instances.map((i) => i.nodeType));
@@ -30,6 +30,17 @@ import { logger } from '../utils/logger.js';
30
30
  * ```
31
31
  */
32
32
  export async function serveCommand(dir, options) {
33
+ // Check fastify is installed before proceeding
34
+ try {
35
+ await import('fastify');
36
+ }
37
+ catch {
38
+ logger.error('The serve command requires fastify. Install it with:');
39
+ logger.newline();
40
+ logger.log(' npm install fastify');
41
+ logger.newline();
42
+ process.exit(1);
43
+ }
33
44
  const workflowDir = path.resolve(dir || '.');
34
45
  // Validate directory exists
35
46
  if (!fs.existsSync(workflowDir)) {
@@ -62,7 +62,7 @@ export async function validateCommand(input, options = {}) {
62
62
  }
63
63
  try {
64
64
  // Parse the workflow
65
- const parseResult = await parseWorkflow(file, { workflowName, projectDir: process.cwd() });
65
+ const parseResult = await parseWorkflow(file, { workflowName, projectDir: path.dirname(path.resolve(file)) });
66
66
  if (parseResult.warnings.length > 0) {
67
67
  if (!json && !quiet) {
68
68
  logger.warn(`Parse warnings in ${fileName}:`);