@synergenius/flow-weaver 0.23.5 → 0.24.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.
Files changed (49) hide show
  1. package/dist/agent/index.d.ts +2 -1
  2. package/dist/agent/index.js +2 -0
  3. package/dist/agent/providers/anthropic.js +14 -1
  4. package/dist/agent/providers/claude-cli.d.ts +1 -1
  5. package/dist/agent/providers/claude-cli.js +4 -1
  6. package/dist/agent/providers/openai-compat.d.ts +1 -1
  7. package/dist/agent/providers/openai-compat.js +2 -1
  8. package/dist/agent/providers/platform.d.ts +1 -1
  9. package/dist/agent/providers/platform.js +3 -1
  10. package/dist/agent/types.d.ts +21 -2
  11. package/dist/agent/types.js +6 -1
  12. package/dist/api/generate-in-place.d.ts +0 -9
  13. package/dist/api/generate-in-place.js +6 -54
  14. package/dist/api/generate.d.ts +4 -5
  15. package/dist/api/generate.js +6 -26
  16. package/dist/cli/commands/compile.d.ts +0 -5
  17. package/dist/cli/commands/compile.js +36 -8
  18. package/dist/cli/commands/context.js +4 -6
  19. package/dist/cli/commands/create.js +6 -14
  20. package/dist/cli/commands/describe.js +6 -10
  21. package/dist/cli/commands/diagram.js +18 -25
  22. package/dist/cli/commands/diff.js +7 -14
  23. package/dist/cli/commands/docs.js +3 -6
  24. package/dist/cli/commands/doctor.js +1 -1
  25. package/dist/cli/commands/export.js +1 -1
  26. package/dist/cli/commands/grammar.js +3 -4
  27. package/dist/cli/commands/implement.js +8 -13
  28. package/dist/cli/commands/market.js +4 -8
  29. package/dist/cli/commands/migrate.js +2 -1
  30. package/dist/cli/commands/modify.js +2 -1
  31. package/dist/cli/commands/openapi.js +2 -1
  32. package/dist/cli/commands/pattern.js +3 -2
  33. package/dist/cli/commands/strip.js +3 -6
  34. package/dist/cli/commands/validate.js +6 -1
  35. package/dist/cli/flow-weaver.mjs +781 -791
  36. package/dist/cli/index.js +10 -12
  37. package/dist/cli/postinstall.d.ts +16 -0
  38. package/dist/cli/postinstall.js +119 -0
  39. package/dist/cli/utils/parse-int-strict.d.ts +7 -0
  40. package/dist/cli/utils/parse-int-strict.js +17 -0
  41. package/dist/cli/utils/safe-write.d.ts +18 -0
  42. package/dist/cli/utils/safe-write.js +54 -0
  43. package/dist/generated-version.d.ts +1 -1
  44. package/dist/generated-version.js +1 -1
  45. package/dist/mcp/tools-debug.js +2 -2
  46. package/docs/reference/cli-reference.md +0 -1
  47. package/docs/reference/compilation.md +2 -10
  48. package/package.json +4 -2
  49. package/scripts/postinstall.cjs +86 -0
@@ -6,38 +6,31 @@ import * as fs from 'fs';
6
6
  import * as path from 'path';
7
7
  import { fileToSVG, fileToHTML, fileToASCII } from '../../diagram/index.js';
8
8
  import { logger } from '../utils/logger.js';
9
- import { getErrorMessage } from '../../utils/error-utils.js';
9
+ import { safeWriteFile } from '../utils/safe-write.js';
10
10
  const ASCII_FORMATS = new Set(['ascii', 'ascii-compact', 'text']);
11
11
  export async function diagramCommand(input, options = {}) {
12
12
  const { output, format = 'svg', ...diagramOptions } = options;
13
13
  const filePath = path.resolve(input);
14
14
  if (!fs.existsSync(filePath)) {
15
- logger.error(`File not found: ${filePath}`);
16
- process.exit(1);
15
+ throw new Error(`File not found: ${filePath}`);
17
16
  }
18
- try {
19
- let result;
20
- if (ASCII_FORMATS.has(format)) {
21
- result = fileToASCII(filePath, { ...diagramOptions, format });
22
- }
23
- else if (format === 'html') {
24
- result = fileToHTML(filePath, diagramOptions);
25
- }
26
- else {
27
- result = fileToSVG(filePath, diagramOptions);
28
- }
29
- if (output) {
30
- const outputPath = path.resolve(output);
31
- fs.writeFileSync(outputPath, result, 'utf-8');
32
- logger.success(`Diagram written to ${outputPath}`);
33
- }
34
- else {
35
- process.stdout.write(result);
36
- }
17
+ let result;
18
+ if (ASCII_FORMATS.has(format)) {
19
+ result = fileToASCII(filePath, { ...diagramOptions, format });
37
20
  }
38
- catch (error) {
39
- logger.error(`Failed to generate diagram: ${getErrorMessage(error)}`);
40
- process.exit(1);
21
+ else if (format === 'html') {
22
+ result = fileToHTML(filePath, diagramOptions);
23
+ }
24
+ else {
25
+ result = fileToSVG(filePath, diagramOptions);
26
+ }
27
+ if (output) {
28
+ const outputPath = path.resolve(output);
29
+ safeWriteFile(outputPath, result);
30
+ logger.success(`Diagram written to ${outputPath}`);
31
+ }
32
+ else {
33
+ process.stdout.write(result);
41
34
  }
42
35
  }
43
36
  //# sourceMappingURL=diagram.js.map
@@ -13,12 +13,10 @@ export async function diffCommand(file1, file2, options = {}) {
13
13
  const filePath2 = path.resolve(file2);
14
14
  // Validate files exist
15
15
  if (!fs.existsSync(filePath1)) {
16
- logger.error(`File not found: ${filePath1}`);
17
- process.exit(1);
16
+ throw new Error(`File not found: ${filePath1}`);
18
17
  }
19
18
  if (!fs.existsSync(filePath2)) {
20
- logger.error(`File not found: ${filePath2}`);
21
- process.exit(1);
19
+ throw new Error(`File not found: ${filePath2}`);
22
20
  }
23
21
  try {
24
22
  // Parse both workflows
@@ -27,14 +25,10 @@ export async function diffCommand(file1, file2, options = {}) {
27
25
  parseWorkflow(filePath2, { workflowName }),
28
26
  ]);
29
27
  if (result1.errors.length > 0) {
30
- logger.error(`Parse errors in ${file1}:`);
31
- result1.errors.forEach((err) => logger.error(` ${err}`));
32
- process.exit(1);
28
+ throw new Error(`Parse errors in ${file1}:\n${result1.errors.map((err) => ` ${err}`).join('\n')}`);
33
29
  }
34
30
  if (result2.errors.length > 0) {
35
- logger.error(`Parse errors in ${file2}:`);
36
- result2.errors.forEach((err) => logger.error(` ${err}`));
37
- process.exit(1);
31
+ throw new Error(`Parse errors in ${file2}:\n${result2.errors.map((err) => ` ${err}`).join('\n')}`);
38
32
  }
39
33
  // Compare workflows
40
34
  const diff = WorkflowDiffer.compare(result1.ast, result2.ast);
@@ -50,15 +44,14 @@ export async function diffCommand(file1, file2, options = {}) {
50
44
  else {
51
45
  // eslint-disable-next-line no-console
52
46
  console.log(formatDiff(diff, format));
53
- // Exit with code 1 if there are differences (useful for CI)
47
+ // Throw if there are differences (useful for CI)
54
48
  if (!exitZero) {
55
- process.exit(1);
49
+ throw new Error('Workflows have differences');
56
50
  }
57
51
  }
58
52
  }
59
53
  catch (error) {
60
- logger.error(`Failed to diff workflows: ${getErrorMessage(error)}`);
61
- process.exit(1);
54
+ throw new Error(`Failed to diff workflows: ${getErrorMessage(error)}`);
62
55
  }
63
56
  }
64
57
  //# sourceMappingURL=diff.js.map
@@ -3,8 +3,7 @@ import { logger } from '../utils/logger.js';
3
3
  export async function docsListCommand(options) {
4
4
  const topics = listTopics();
5
5
  if (topics.length === 0) {
6
- logger.error('No documentation topics found.');
7
- process.exit(1);
6
+ throw new Error('No documentation topics found.');
8
7
  }
9
8
  if (options.json) {
10
9
  process.stdout.write(JSON.stringify({ topics }, null, 2) + '\n');
@@ -25,16 +24,14 @@ export async function docsReadCommand(topic, options) {
25
24
  if (options.json) {
26
25
  const structured = readTopicStructured(topic);
27
26
  if (!structured) {
28
- logger.error(`Unknown topic: "${topic}". Run "fw docs" to see available topics.`);
29
- process.exit(1);
27
+ throw new Error(`Unknown topic: "${topic}". Run "fw docs" to see available topics.`);
30
28
  }
31
29
  process.stdout.write(JSON.stringify(structured, null, 2) + '\n');
32
30
  return;
33
31
  }
34
32
  const doc = readTopic(topic, options.compact);
35
33
  if (!doc) {
36
- logger.error(`Unknown topic: "${topic}". Run "fw docs" to see available topics.`);
37
- process.exit(1);
34
+ throw new Error(`Unknown topic: "${topic}". Run "fw docs" to see available topics.`);
38
35
  }
39
36
  process.stdout.write(doc.content + '\n');
40
37
  }
@@ -707,7 +707,7 @@ export async function doctorCommand(options = {}) {
707
707
  }
708
708
  }
709
709
  if (!report.ok) {
710
- process.exit(1);
710
+ throw new Error('Doctor found issues that need to be fixed');
711
711
  }
712
712
  }
713
713
  //# sourceMappingURL=doctor.js.map
@@ -66,7 +66,7 @@ export async function exportCommand(input, options) {
66
66
  input,
67
67
  output: options.output,
68
68
  workflow: options.workflow,
69
- production: options.production ?? true,
69
+ production: options.production ?? false,
70
70
  bundle: options.bundle,
71
71
  dryRun: isDryRun,
72
72
  multi: isMulti,
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * Grammar command - output JSDoc grammar as HTML railroad diagrams or EBNF text
3
3
  */
4
- import * as fs from 'fs';
5
4
  import { generateGrammarDiagrams, getAllGrammars, serializedToEBNF, } from '../../chevrotain-parser/grammar-diagrams.js';
6
5
  import { logger } from '../utils/logger.js';
7
6
  import { getErrorMessage } from '../../utils/error-utils.js';
7
+ import { safeWriteFile } from '../utils/safe-write.js';
8
8
  export async function grammarCommand(options = {}) {
9
9
  // Default to ebnf on TTY (readable in terminal), html when writing to file
10
10
  const defaultFormat = options.output ? 'html' : (process.stdout.isTTY ? 'ebnf' : 'html');
@@ -26,7 +26,7 @@ export async function grammarCommand(options = {}) {
26
26
  content = generateGrammarDiagrams();
27
27
  }
28
28
  if (output) {
29
- fs.writeFileSync(output, content, 'utf-8');
29
+ safeWriteFile(output, content);
30
30
  logger.success(`Grammar written to ${output}`);
31
31
  }
32
32
  else {
@@ -34,8 +34,7 @@ export async function grammarCommand(options = {}) {
34
34
  }
35
35
  }
36
36
  catch (error) {
37
- logger.error(`Grammar generation failed: ${getErrorMessage(error)}`);
38
- process.exit(1);
37
+ throw new Error(`Grammar generation failed: ${getErrorMessage(error)}`);
39
38
  }
40
39
  }
41
40
  //# sourceMappingURL=grammar.js.map
@@ -7,6 +7,7 @@ import { parseWorkflow } from '../../api/index.js';
7
7
  import { generateFunctionSignature } from '../../annotation-generator.js';
8
8
  import { logger } from '../utils/logger.js';
9
9
  import { getErrorMessage } from '../../utils/error-utils.js';
10
+ import { safeWriteFile } from '../utils/safe-write.js';
10
11
  /**
11
12
  * Find a `declare function <name>(...): ...;` declaration in source text,
12
13
  * handling multiline signatures. Returns the full matched text and its
@@ -37,14 +38,11 @@ export async function implementCommand(input, nodeName, options = {}) {
37
38
  try {
38
39
  const filePath = path.resolve(input);
39
40
  if (!fs.existsSync(filePath)) {
40
- logger.error(`File not found: ${input}`);
41
- process.exit(1);
41
+ throw new Error(`File not found: ${input}`);
42
42
  }
43
43
  const parseResult = await parseWorkflow(filePath, { workflowName });
44
44
  if (parseResult.errors.length > 0) {
45
- logger.error('Parse errors:');
46
- parseResult.errors.forEach((e) => logger.error(` ${e}`));
47
- process.exit(1);
45
+ throw new Error(`Parse errors:\n${parseResult.errors.map((e) => ` ${e}`).join('\n')}`);
48
46
  }
49
47
  const ast = parseResult.ast;
50
48
  // Find the stub node type
@@ -59,18 +57,16 @@ export async function implementCommand(input, nodeName, options = {}) {
59
57
  .filter((nt) => nt.variant === 'STUB')
60
58
  .map((nt) => nt.functionName);
61
59
  if (available.length === 0) {
62
- logger.error('No stub nodes found in this workflow.');
60
+ throw new Error('No stub nodes found in this workflow.');
63
61
  }
64
62
  else {
65
- logger.error(`Stub node "${nodeName}" not found. Available stubs: ${available.join(', ')}`);
63
+ throw new Error(`Stub node "${nodeName}" not found. Available stubs: ${available.join(', ')}`);
66
64
  }
67
- process.exit(1);
68
65
  }
69
66
  const source = fs.readFileSync(filePath, 'utf8');
70
67
  const found = findDeclareFunction(source, stubNodeType.functionName);
71
68
  if (!found) {
72
- logger.error(`Could not find "declare function ${stubNodeType.functionName}" in source file.`);
73
- process.exit(1);
69
+ throw new Error(`Could not find "declare function ${stubNodeType.functionName}" in source file.`);
74
70
  }
75
71
  // Generate the real function signature
76
72
  const implementedType = { ...stubNodeType, variant: 'FUNCTION' };
@@ -84,13 +80,12 @@ export async function implementCommand(input, nodeName, options = {}) {
84
80
  }
85
81
  else {
86
82
  const updated = source.replace(found.match, replacement);
87
- fs.writeFileSync(filePath, updated, 'utf8');
83
+ safeWriteFile(filePath, updated);
88
84
  logger.success(`Implemented ${stubNodeType.functionName} in ${path.basename(filePath)}`);
89
85
  }
90
86
  }
91
87
  catch (error) {
92
- logger.error(`Implement failed: ${getErrorMessage(error)}`);
93
- process.exit(1);
88
+ throw new Error(`Implement failed: ${getErrorMessage(error)}`);
94
89
  }
95
90
  }
96
91
  //# sourceMappingURL=implement.js.map
@@ -24,13 +24,11 @@ export async function marketInitCommand(name, options = {}) {
24
24
  if (stat.isDirectory()) {
25
25
  const contents = fs.readdirSync(targetDir);
26
26
  if (contents.length > 0) {
27
- logger.error(`Directory "${name}" already exists and is not empty`);
28
- process.exit(1);
27
+ throw new Error(`Directory "${name}" already exists and is not empty`);
29
28
  }
30
29
  }
31
30
  else {
32
- logger.error(`"${name}" already exists and is not a directory`);
33
- process.exit(1);
31
+ throw new Error(`"${name}" already exists and is not a directory`);
34
32
  }
35
33
  }
36
34
  logger.section('Creating Marketplace Package');
@@ -211,8 +209,7 @@ export async function marketPackCommand(directory, options = {}) {
211
209
  }
212
210
  if (!validation.valid) {
213
211
  logger.newline();
214
- logger.error('Package validation failed. Fix errors above before publishing.');
215
- process.exit(1);
212
+ throw new Error('Package validation failed. Fix errors above before publishing.');
216
213
  }
217
214
  // 3. Write manifest
218
215
  const outPath = writeManifest(dir, manifest);
@@ -253,8 +250,7 @@ export async function marketPublishCommand(directory, options = {}) {
253
250
  }
254
251
  }
255
252
  catch (err) {
256
- logger.error(`npm publish failed: ${getErrorMessage(err)}`);
257
- process.exit(1);
253
+ throw new Error(`npm publish failed: ${getErrorMessage(err)}`);
258
254
  }
259
255
  }
260
256
  /**
@@ -13,6 +13,7 @@ import { generateInPlace } from '../../api/generate-in-place.js';
13
13
  import { WorkflowDiffer, formatDiff } from '../../diff/index.js';
14
14
  import { applyMigrations, getRegisteredMigrations } from '../../migration/registry.js';
15
15
  import { logger } from '../utils/logger.js';
16
+ import { safeWriteFile } from '../utils/safe-write.js';
16
17
  import { getErrorMessage } from '../../utils/error-utils.js';
17
18
  export async function migrateCommand(globPattern, options = {}) {
18
19
  const { dryRun = false, diff = false } = options;
@@ -69,7 +70,7 @@ export async function migrateCommand(globPattern, options = {}) {
69
70
  migratedCount++;
70
71
  continue;
71
72
  }
72
- fs.writeFileSync(filePath, genResult.code, 'utf8');
73
+ safeWriteFile(filePath, genResult.code);
73
74
  logger.success(` ${file}: migrated`);
74
75
  migratedCount++;
75
76
  }
@@ -4,6 +4,7 @@ import { parseWorkflow } from '../../api/index.js';
4
4
  import { generateInPlace } from '../../api/generate-in-place.js';
5
5
  import { applyModifyOperation, validateModifyParams } from '../../api/modify-operation.js';
6
6
  import { logger } from '../utils/logger.js';
7
+ import { safeWriteFile } from '../utils/safe-write.js';
7
8
  async function readParseModifyWrite(file, operation, params) {
8
9
  const validation = validateModifyParams(operation, params);
9
10
  if (!validation.success) {
@@ -17,7 +18,7 @@ async function readParseModifyWrite(file, operation, params) {
17
18
  }
18
19
  const { ast: modifiedAST, warnings } = applyModifyOperation(parseResult.ast, operation, params);
19
20
  const result = generateInPlace(source, modifiedAST);
20
- fs.writeFileSync(filePath, result.code, 'utf-8');
21
+ safeWriteFile(filePath, result.code);
21
22
  for (const w of warnings) {
22
23
  logger.warn(w);
23
24
  }
@@ -6,6 +6,7 @@ import * as fs from 'fs';
6
6
  import { WorkflowRegistry } from '../../server/workflow-registry.js';
7
7
  import { generateOpenAPIJson, generateOpenAPIYaml } from '../../deployment/openapi/generator.js';
8
8
  import { logger } from '../utils/logger.js';
9
+ import { safeWriteFile } from '../utils/safe-write.js';
9
10
  /**
10
11
  * Generate OpenAPI specification from workflows in a directory.
11
12
  *
@@ -59,7 +60,7 @@ export async function openapiCommand(dir, options) {
59
60
  // Output
60
61
  if (options.output) {
61
62
  const outputPath = path.resolve(options.output);
62
- fs.writeFileSync(outputPath, spec);
63
+ safeWriteFile(outputPath, spec);
63
64
  logger.success(`OpenAPI specification written to ${outputPath}`);
64
65
  }
65
66
  else {
@@ -10,6 +10,7 @@ import * as path from 'path';
10
10
  import { glob } from 'glob';
11
11
  import { AnnotationParser } from '../../parser.js';
12
12
  import { logger } from '../utils/logger.js';
13
+ import { safeWriteFile } from '../utils/safe-write.js';
13
14
  import { listPatterns, applyPattern, extractPattern } from '../../api/patterns.js';
14
15
  const parser = new AnnotationParser();
15
16
  /**
@@ -132,7 +133,7 @@ export async function patternApplyCommand(patternFile, targetFile, options) {
132
133
  return;
133
134
  }
134
135
  // Write modified content
135
- fs.writeFileSync(targetFile, result.modifiedContent);
136
+ safeWriteFile(targetFile, result.modifiedContent);
136
137
  logger.success(`Applied pattern "${pattern.name}" to ${targetFile}`);
137
138
  if (result.nodeTypesAdded.length > 0) {
138
139
  logger.info(`Added node types: ${result.nodeTypesAdded.join(', ')}`);
@@ -176,7 +177,7 @@ export async function patternExtractCommand(sourceFile, options) {
176
177
  return;
177
178
  }
178
179
  // Write to output file
179
- fs.writeFileSync(options.output, result.patternCode);
180
+ safeWriteFile(options.output, result.patternCode);
180
181
  logger.success(`Extracted pattern "${result.patternName}" to ${options.output}`);
181
182
  logger.info(`Included nodes: ${result.nodes.join(', ')}`);
182
183
  logger.info(`Input ports: ${result.inputPorts.join(', ') || 'none'}`);
@@ -3,6 +3,7 @@ import * as path from 'path';
3
3
  import { glob } from 'glob';
4
4
  import { hasInPlaceMarkers, stripGeneratedSections } from '../../api/generate-in-place.js';
5
5
  import { logger } from '../utils/logger.js';
6
+ import { safeWriteFile } from '../utils/safe-write.js';
6
7
  export async function stripCommand(input, options = {}) {
7
8
  const { output, dryRun = false, verbose = false } = options;
8
9
  // If input is a directory, expand to all .ts files recursively
@@ -25,8 +26,7 @@ export async function stripCommand(input, options = {}) {
25
26
  }
26
27
  });
27
28
  if (files.length === 0) {
28
- logger.error(`No files found matching pattern: ${input}`);
29
- process.exit(1);
29
+ throw new Error(`No files found matching pattern: ${input}`);
30
30
  }
31
31
  const t = logger.timer();
32
32
  let stripped = 0;
@@ -49,10 +49,7 @@ export async function stripCommand(input, options = {}) {
49
49
  const outPath = output
50
50
  ? path.join(path.resolve(output), path.basename(filePath))
51
51
  : filePath;
52
- if (output) {
53
- fs.mkdirSync(path.dirname(outPath), { recursive: true });
54
- }
55
- fs.writeFileSync(outPath, result);
52
+ safeWriteFile(outPath, result);
56
53
  stripped++;
57
54
  if (verbose) {
58
55
  logger.success(`Stripped: ${path.relative(process.cwd(), outPath)}`);
@@ -247,7 +247,12 @@ export async function validateCommand(input, options = {}) {
247
247
  }
248
248
  }
249
249
  if (totalErrors > 0) {
250
- process.exit(1);
250
+ if (json) {
251
+ // JSON output already emitted above — just signal failure via exit code
252
+ process.exitCode = 1;
253
+ return;
254
+ }
255
+ throw new Error(`Validation failed with ${totalErrors} error${totalErrors !== 1 ? 's' : ''}`);
251
256
  }
252
257
  }
253
258
  catch (error) {