@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 +18 -6
- package/dist/cli/commands/compile.js +40 -38
- package/dist/cli/commands/describe.js +1 -1
- package/dist/cli/commands/dev.d.ts +4 -0
- package/dist/cli/commands/dev.js +35 -3
- package/dist/cli/commands/diagram.js +3 -0
- package/dist/cli/commands/diff.js +2 -2
- package/dist/cli/commands/run.js +2 -2
- package/dist/cli/commands/serve.js +11 -0
- package/dist/cli/commands/validate.js +1 -1
- package/dist/cli/flow-weaver.mjs +174 -265
- package/dist/cli/index.js +2 -11
- package/dist/doc-metadata/extractors/cli-commands.js +0 -10
- package/dist/generated-version.d.ts +1 -1
- package/dist/generated-version.js +1 -1
- package/docs/reference/cli-reference.md +1 -25
- package/package.json +1 -1
- package/dist/cli/commands/changelog.d.ts +0 -13
- package/dist/cli/commands/changelog.js +0 -135
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 (
|
|
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
|
|
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
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
167
|
-
|
|
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
|
-
|
|
178
|
-
|
|
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.
|
package/dist/cli/commands/dev.js
CHANGED
|
@@ -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')}`);
|
package/dist/cli/commands/run.js
CHANGED
|
@@ -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:
|
|
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}:`);
|