@synergenius/flow-weaver 0.23.4 → 0.24.0

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 (38) hide show
  1. package/dist/api/generate-in-place.d.ts +0 -9
  2. package/dist/api/generate-in-place.js +6 -54
  3. package/dist/api/generate.d.ts +4 -5
  4. package/dist/api/generate.js +6 -26
  5. package/dist/cli/commands/compile.d.ts +0 -5
  6. package/dist/cli/commands/compile.js +36 -8
  7. package/dist/cli/commands/context.js +4 -6
  8. package/dist/cli/commands/create.js +6 -14
  9. package/dist/cli/commands/describe.js +6 -10
  10. package/dist/cli/commands/diagram.js +18 -25
  11. package/dist/cli/commands/diff.js +7 -14
  12. package/dist/cli/commands/docs.js +3 -6
  13. package/dist/cli/commands/doctor.js +1 -1
  14. package/dist/cli/commands/export.js +1 -1
  15. package/dist/cli/commands/grammar.js +3 -4
  16. package/dist/cli/commands/implement.js +8 -13
  17. package/dist/cli/commands/market.js +4 -8
  18. package/dist/cli/commands/migrate.js +2 -1
  19. package/dist/cli/commands/modify.js +2 -1
  20. package/dist/cli/commands/openapi.js +2 -1
  21. package/dist/cli/commands/pattern.js +3 -2
  22. package/dist/cli/commands/strip.js +3 -6
  23. package/dist/cli/commands/validate.js +6 -1
  24. package/dist/cli/flow-weaver.mjs +789 -797
  25. package/dist/cli/index.js +10 -12
  26. package/dist/cli/postinstall.d.ts +16 -0
  27. package/dist/cli/postinstall.js +119 -0
  28. package/dist/cli/utils/parse-int-strict.d.ts +7 -0
  29. package/dist/cli/utils/parse-int-strict.js +17 -0
  30. package/dist/cli/utils/safe-write.d.ts +18 -0
  31. package/dist/cli/utils/safe-write.js +54 -0
  32. package/dist/generated-version.d.ts +1 -1
  33. package/dist/generated-version.js +1 -1
  34. package/dist/generator/unified.js +8 -6
  35. package/docs/reference/cli-reference.md +0 -1
  36. package/docs/reference/compilation.md +2 -10
  37. package/package.json +4 -2
  38. package/scripts/postinstall.cjs +86 -0
package/dist/cli/index.js CHANGED
@@ -11,6 +11,7 @@ import * as path from 'node:path';
11
11
  // Load built-in extensions (CI/CD, etc.) before any commands run
12
12
  import '../extensions/index.js';
13
13
  import { Command, Option } from 'commander';
14
+ import { parseIntStrict } from './utils/parse-int-strict.js';
14
15
  import { logger } from './utils/logger.js';
15
16
  import { getErrorMessage } from '../utils/error-utils.js';
16
17
  const version = typeof __CLI_VERSION__ !== 'undefined' ? __CLI_VERSION__ : '0.0.0-dev';
@@ -71,14 +72,13 @@ program
71
72
  .option('-w, --workflow <name>', 'Specific workflow name to compile')
72
73
  .addOption(new Option('-f, --format <format>', 'Module format').choices(['esm', 'cjs', 'auto']).default('auto'))
73
74
  .option('--strict', 'Treat type coercion warnings as errors', false)
74
- .option('--inline-runtime', 'Force inline runtime even when @synergenius/flow-weaver package is installed', false)
75
75
  .option('--clean', 'Omit redundant @param/@returns annotations from compiled output', false)
76
76
  .option('--target <target>', 'Compilation target: typescript (default) or a registered extension target')
77
77
  .option('--cron <schedule>', 'Set cron trigger schedule')
78
78
  .option('--serve', 'Generate serve() handler for HTTP event reception')
79
79
  .option('--framework <name>', 'Framework adapter for serve handler (next, express, hono, fastify, remix)')
80
80
  .option('--typed-events', 'Generate Zod event schemas from workflow @param annotations')
81
- .option('--retries <n>', 'Number of retries per function', parseInt)
81
+ .option('--retries <n>', 'Number of retries per function', parseIntStrict)
82
82
  .option('--timeout <duration>', 'Function timeout (e.g. "30m", "1h")')
83
83
  .action(wrapAction(async (input, options) => {
84
84
  const { compileCommand } = await import('./commands/compile.js');
@@ -115,7 +115,7 @@ program
115
115
  program
116
116
  .command('diagram <input>')
117
117
  .description('Generate SVG or interactive HTML diagram of a workflow')
118
- .option('-t, --theme <theme>', 'Color theme: dark, light', 'dark')
118
+ .addOption(new Option('-t, --theme <theme>', 'Color theme').choices(['dark', 'light']).default('dark'))
119
119
  .option('--width <pixels>', 'SVG width in pixels')
120
120
  .option('-p, --padding <pixels>', 'Canvas padding in pixels')
121
121
  .option('--no-port-labels', 'Hide data type labels on ports')
@@ -190,7 +190,6 @@ program
190
190
  .option('--with-weaver', 'Install Weaver AI assistant')
191
191
  .option('--no-weaver', 'Skip Weaver installation')
192
192
  .option('--force', 'Overwrite existing files', false)
193
- .option('--json', 'Output results as JSON', false)
194
193
  .action(wrapAction(async (directory, options) => {
195
194
  const { initCommand } = await import('./commands/init.js');
196
195
  await initCommand(directory, options);
@@ -224,8 +223,6 @@ program
224
223
  .option('--once', 'Run once then exit', false)
225
224
  .option('--json', 'Output result as JSON', false)
226
225
  .option('--target <target>', 'Compilation target (default: typescript)')
227
- .option('--framework <framework>', 'Framework for serve handler', 'express')
228
- .option('--port <port>', 'Port for dev server', (v) => parseInt(v, 10), 3000)
229
226
  .action(wrapAction(async (input, options) => {
230
227
  const { devCommand } = await import('./commands/dev.js');
231
228
  await devCommand(input, options);
@@ -255,7 +252,7 @@ const createCmd = program.command('create').description('Create workflows or nod
255
252
  createCmd
256
253
  .command('workflow <template> <file>')
257
254
  .description('Create a workflow from a template')
258
- .option('-l, --line <number>', 'Insert at specific line number', parseInt)
255
+ .option('-l, --line <number>', 'Insert at specific line number', parseIntStrict)
259
256
  .option('-a, --async', 'Generate an async workflow', false)
260
257
  .option('-p, --preview', 'Preview generated code without writing', false)
261
258
  .option('--provider <provider>', 'LLM provider (openai, anthropic, ollama, mock)')
@@ -272,7 +269,7 @@ createCmd
272
269
  createCmd
273
270
  .command('node <name> <file>')
274
271
  .description('Create a node type from a template')
275
- .option('-l, --line <number>', 'Insert at specific line number', parseInt)
272
+ .option('-l, --line <number>', 'Insert at specific line number', parseIntStrict)
276
273
  .option('-t, --template <template>', 'Node template to use', 'transformer')
277
274
  .option('-p, --preview', 'Preview generated code without writing', false)
278
275
  .option('--strategy <strategy>', 'Template strategy (e.g. mock, callback, webhook)')
@@ -414,7 +411,7 @@ program
414
411
  .option('-t, --trace', 'Include execution trace events')
415
412
  .option('-s, --stream', 'Stream trace events in real-time')
416
413
  .option('--json', 'Output result as JSON', false)
417
- .option('--timeout <ms>', 'Execution timeout in milliseconds', parseInt)
414
+ .option('--timeout <ms>', 'Execution timeout in milliseconds', parseIntStrict)
418
415
  .option('--mocks <json>', 'Mock config for built-in nodes (events, invocations, agents, fast) as JSON')
419
416
  .option('--mocks-file <path>', 'Path to JSON file with mock config for built-in nodes')
420
417
  .option('-d, --debug', 'Start in step-through debug mode')
@@ -439,7 +436,7 @@ program
439
436
  .action(wrapAction(async (directory, options) => {
440
437
  const { serveCommand } = await import('./commands/serve.js');
441
438
  await serveCommand(directory, {
442
- port: parseInt(options.port, 10),
439
+ port: parseIntStrict(options.port),
443
440
  host: options.host,
444
441
  watch: options.watch,
445
442
  production: options.production,
@@ -455,7 +452,8 @@ program
455
452
  .requiredOption('-t, --target <target>', 'Target platform (install target packs via marketplace)')
456
453
  .requiredOption('-o, --output <path>', 'Output directory')
457
454
  .option('-w, --workflow <name>', 'Specific workflow name to export')
458
- .option('-p, --production', 'Production mode', true)
455
+ .option('-p, --production', 'Production mode (no debug events)', false)
456
+ .option('--bundle', 'Bundle node types into the output', false)
459
457
  .option('--dry-run', 'Preview without writing files', false)
460
458
  .option('--multi', 'Export all workflows in file as a single multi-workflow service', false)
461
459
  .option('--workflows <names>', 'Comma-separated list of workflows to export (used with --multi)')
@@ -625,7 +623,7 @@ marketCmd
625
623
  .option('--json', 'Output as JSON', false)
626
624
  .action(wrapAction(async (query, options) => {
627
625
  const { marketSearchCommand } = await import('./commands/market.js');
628
- await marketSearchCommand(query, { ...options, limit: parseInt(options.limit, 10) });
626
+ await marketSearchCommand(query, { ...options, limit: parseIntStrict(options.limit) });
629
627
  }));
630
628
  marketCmd
631
629
  .command('list')
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Postinstall welcome message.
3
+ *
4
+ * Context-aware, shown once after npm install.
5
+ * Silent in CI. No telemetry, no file modifications, no dark patterns.
6
+ */
7
+ export type InstallContext = 'ci' | 'global' | 'typescript' | 'existing' | 'project';
8
+ /**
9
+ * Detect the installation context based on cwd and environment variables.
10
+ */
11
+ export declare function detectContext(cwd: string, env: Record<string, string | undefined>): InstallContext;
12
+ /**
13
+ * Format the welcome message for the detected context.
14
+ */
15
+ export declare function formatMessage(context: InstallContext): string;
16
+ //# sourceMappingURL=postinstall.d.ts.map
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Postinstall welcome message.
3
+ *
4
+ * Context-aware, shown once after npm install.
5
+ * Silent in CI. No telemetry, no file modifications, no dark patterns.
6
+ */
7
+ import * as fs from 'fs';
8
+ import * as path from 'path';
9
+ const CI_ENV_VARS = ['CI', 'CONTINUOUS_INTEGRATION', 'BUILD_NUMBER', 'GITHUB_ACTIONS', 'GITLAB_CI', 'CIRCLECI', 'JENKINS_URL', 'CODEBUILD_BUILD_ID'];
10
+ /**
11
+ * Scan .ts files in a directory (non-recursive) for @flowWeaver annotations.
12
+ */
13
+ function dirHasFlowWeaver(dir) {
14
+ try {
15
+ const entries = fs.readdirSync(dir);
16
+ for (const entry of entries) {
17
+ if (!entry.endsWith('.ts') || entry.endsWith('.d.ts'))
18
+ continue;
19
+ try {
20
+ const content = fs.readFileSync(path.join(dir, entry), 'utf8');
21
+ if (content.includes('@flowWeaver'))
22
+ return true;
23
+ }
24
+ catch {
25
+ // Permission error or similar — skip
26
+ }
27
+ }
28
+ }
29
+ catch {
30
+ // Directory doesn't exist or can't be read
31
+ }
32
+ return false;
33
+ }
34
+ /**
35
+ * Scan .ts files in a directory (non-recursive) for existence.
36
+ */
37
+ function dirHasTsFiles(dir) {
38
+ try {
39
+ const entries = fs.readdirSync(dir);
40
+ return entries.some((e) => e.endsWith('.ts') && !e.endsWith('.d.ts'));
41
+ }
42
+ catch {
43
+ return false;
44
+ }
45
+ }
46
+ /**
47
+ * Detect the installation context based on cwd and environment variables.
48
+ */
49
+ export function detectContext(cwd, env) {
50
+ // CI — always silent
51
+ if (CI_ENV_VARS.some((v) => env[v])) {
52
+ return 'ci';
53
+ }
54
+ // No package.json — likely global install or outside a project
55
+ if (!fs.existsSync(path.join(cwd, 'package.json'))) {
56
+ return 'global';
57
+ }
58
+ // Check for TypeScript project (tsconfig.json as proxy)
59
+ const hasTsConfig = fs.existsSync(path.join(cwd, 'tsconfig.json'));
60
+ if (!hasTsConfig) {
61
+ return 'project';
62
+ }
63
+ // Check for existing @flowWeaver usage (top-level and src/ only)
64
+ if (dirHasFlowWeaver(cwd) || dirHasFlowWeaver(path.join(cwd, 'src'))) {
65
+ return 'existing';
66
+ }
67
+ // TypeScript project without @flowWeaver
68
+ if (dirHasTsFiles(cwd) || dirHasTsFiles(path.join(cwd, 'src'))) {
69
+ return 'typescript';
70
+ }
71
+ // Has tsconfig but no .ts files yet
72
+ return 'typescript';
73
+ }
74
+ /**
75
+ * Format the welcome message for the detected context.
76
+ */
77
+ export function formatMessage(context) {
78
+ switch (context) {
79
+ case 'ci':
80
+ return '';
81
+ case 'global':
82
+ return [
83
+ '',
84
+ ' flow-weaver installed \u2713',
85
+ '',
86
+ ' Create a project: fw init my-project',
87
+ ' Or try it now: fw create workflow hello-world',
88
+ '',
89
+ ].join('\n');
90
+ case 'typescript':
91
+ return [
92
+ '',
93
+ ' flow-weaver installed \u2713',
94
+ '',
95
+ ' Add above any function: /** @flowWeaver nodeType */',
96
+ ' Then compile: fw compile src/',
97
+ '',
98
+ ].join('\n');
99
+ case 'existing':
100
+ return [
101
+ '',
102
+ ' flow-weaver updated \u2713',
103
+ '',
104
+ ' Check health: fw doctor',
105
+ '',
106
+ ].join('\n');
107
+ case 'project':
108
+ return [
109
+ '',
110
+ ' flow-weaver installed \u2713',
111
+ '',
112
+ ' Get started: fw init',
113
+ '',
114
+ ].join('\n');
115
+ }
116
+ }
117
+ // Entry point logic lives in scripts/postinstall.cjs (standalone CJS, no build step).
118
+ // This module is the testable source of truth for the detection/formatting logic.
119
+ //# sourceMappingURL=postinstall.js.map
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Strict integer parser for CLI options.
3
+ * Unlike parseInt, rejects partial matches ("12abc") and non-numeric values.
4
+ * Throws a clear error instead of silently returning NaN.
5
+ */
6
+ export declare function parseIntStrict(value: string): number;
7
+ //# sourceMappingURL=parse-int-strict.d.ts.map
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Strict integer parser for CLI options.
3
+ * Unlike parseInt, rejects partial matches ("12abc") and non-numeric values.
4
+ * Throws a clear error instead of silently returning NaN.
5
+ */
6
+ export function parseIntStrict(value) {
7
+ const trimmed = value.trim();
8
+ if (trimmed === '') {
9
+ throw new Error(`"${value}" is not a valid number`);
10
+ }
11
+ const n = Number(trimmed);
12
+ if (!Number.isFinite(n) || !Number.isInteger(n)) {
13
+ throw new Error(`"${value}" is not a valid number`);
14
+ }
15
+ return n;
16
+ }
17
+ //# sourceMappingURL=parse-int-strict.js.map
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Cross-platform safe file writing utilities.
3
+ *
4
+ * - Creates parent directories automatically
5
+ * - Provides clear error messages for permission issues
6
+ * - Works on Windows, macOS, and Linux
7
+ */
8
+ /**
9
+ * Write content to a file, creating parent directories as needed.
10
+ * Throws a clear error on permission issues.
11
+ */
12
+ export declare function safeWriteFile(filePath: string, content: string, encoding?: BufferEncoding): void;
13
+ /**
14
+ * Append content to a file, creating the file and parent directories as needed.
15
+ * Throws a clear error on permission issues.
16
+ */
17
+ export declare function safeAppendFile(filePath: string, content: string, encoding?: BufferEncoding): void;
18
+ //# sourceMappingURL=safe-write.d.ts.map
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Cross-platform safe file writing utilities.
3
+ *
4
+ * - Creates parent directories automatically
5
+ * - Provides clear error messages for permission issues
6
+ * - Works on Windows, macOS, and Linux
7
+ */
8
+ import * as fs from 'fs';
9
+ import * as path from 'path';
10
+ function ensureDir(dirPath) {
11
+ if (!fs.existsSync(dirPath)) {
12
+ fs.mkdirSync(dirPath, { recursive: true });
13
+ }
14
+ }
15
+ function wrapIOError(filePath, error) {
16
+ if (error instanceof Error) {
17
+ const code = error.code;
18
+ if (code === 'EACCES' || code === 'EPERM') {
19
+ throw new Error(`Permission denied: cannot write to ${filePath}. Check file/directory permissions.`);
20
+ }
21
+ if (code === 'EROFS') {
22
+ throw new Error(`Read-only file system: cannot write to ${filePath}.`);
23
+ }
24
+ throw error;
25
+ }
26
+ throw error;
27
+ }
28
+ /**
29
+ * Write content to a file, creating parent directories as needed.
30
+ * Throws a clear error on permission issues.
31
+ */
32
+ export function safeWriteFile(filePath, content, encoding = 'utf8') {
33
+ try {
34
+ ensureDir(path.dirname(filePath));
35
+ fs.writeFileSync(filePath, content, encoding);
36
+ }
37
+ catch (error) {
38
+ wrapIOError(filePath, error);
39
+ }
40
+ }
41
+ /**
42
+ * Append content to a file, creating the file and parent directories as needed.
43
+ * Throws a clear error on permission issues.
44
+ */
45
+ export function safeAppendFile(filePath, content, encoding = 'utf8') {
46
+ try {
47
+ ensureDir(path.dirname(filePath));
48
+ fs.appendFileSync(filePath, content, encoding);
49
+ }
50
+ catch (error) {
51
+ wrapIOError(filePath, error);
52
+ }
53
+ }
54
+ //# sourceMappingURL=safe-write.js.map
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.23.4";
1
+ export declare const VERSION = "0.24.0";
2
2
  //# sourceMappingURL=generated-version.d.ts.map
@@ -1,3 +1,3 @@
1
1
  // Auto-generated by scripts/generate-version.ts — do not edit manually
2
- export const VERSION = '0.23.4';
2
+ export const VERSION = '0.24.0';
3
3
  //# sourceMappingURL=generated-version.js.map
@@ -456,7 +456,7 @@ export function generateControlFlowWithExecutionContext(workflow, nodeTypes, isA
456
456
  branchNeedsClose = true;
457
457
  }
458
458
  }
459
- generateBranchingNodeCode(instance, nodeType, workflow, nodeTypes, nodeRegion, availableVars, generatedNodes, lines, branchIndent, false, branchingNodes, branchRegions, isAsync, 'ctx', bundleMode, promotedPreDeclared, branchingNodesNeedingSuccessFlag.has(instanceId), production);
459
+ generateBranchingNodeCode(instance, nodeType, workflow, nodeTypes, nodeRegion, availableVars, generatedNodes, lines, branchIndent, false, branchingNodes, branchRegions, isAsync, 'ctx', bundleMode, promotedPreDeclared, branchingNodesNeedingSuccessFlag.has(instanceId) || topLevelSuccessFlags.has(toValidIdentifier(instanceId)), production);
460
460
  if (branchNeedsClose) {
461
461
  lines.push(` }`);
462
462
  }
@@ -885,8 +885,8 @@ function generateBranchingChainCode(chain, workflow, nodeTypes, branchingNodes,
885
885
  const preDeclaredFlags = new Set(alreadyDeclaredFlags);
886
886
  for (let i = 0; i < chain.length; i++) {
887
887
  const isLast = i === chain.length - 1;
888
- if (!isLast || forceTrackSuccessNodes.has(chain[i])) {
889
- const safeId = toValidIdentifier(chain[i]);
888
+ const safeId = toValidIdentifier(chain[i]);
889
+ if (!isLast || forceTrackSuccessNodes.has(chain[i]) || alreadyDeclaredFlags.has(safeId)) {
890
890
  if (!alreadyDeclaredFlags.has(safeId)) {
891
891
  lines.push(`${indent}let ${safeId}_success = false;`);
892
892
  }
@@ -924,7 +924,7 @@ function generateBranchingChainCode(chain, workflow, nodeTypes, branchingNodes,
924
924
  lines.push(`${indent}if (${guardCondition}) {`);
925
925
  }
926
926
  const nodeIndent = hasGuard ? indent + ' ' : indent;
927
- generateBranchingNodeCode(instance, nodeType, workflow, nodeTypes, effectiveRegion, availableVars, generatedNodes, lines, nodeIndent, false, branchingNodes, branchRegions, isAsync, ctxVar, bundleMode, preDeclaredFlags, !isLast || forceTrackSuccessNodes.has(chain[i]), // forceTrackSuccess for non-last chain nodes or nodes with promoted dependents
927
+ generateBranchingNodeCode(instance, nodeType, workflow, nodeTypes, effectiveRegion, availableVars, generatedNodes, lines, nodeIndent, false, branchingNodes, branchRegions, isAsync, ctxVar, bundleMode, preDeclaredFlags, !isLast || forceTrackSuccessNodes.has(chain[i]) || alreadyDeclaredFlags.has(safeId), // forceTrackSuccess for non-last chain nodes, nodes with promoted dependents, or nodes with pre-declared flags
928
928
  production);
929
929
  // Generate scoped children for this chain node
930
930
  generateScopedChildrenExecution(instance, nodeType, workflow, nodeTypes, generatedNodes, availableVars, lines, nodeIndent, branchingNodes, branchRegions, isAsync, bundleMode, production);
@@ -1225,7 +1225,8 @@ bundleMode = false, preDeclaredSuccessFlags = new Set(), forceTrackSuccess = fal
1225
1225
  lines.push(`${indent} let ${nestedSafeId}_success = false;`);
1226
1226
  nestedPreDeclared.add(nestedSafeId);
1227
1227
  }
1228
- generateBranchingNodeCode(inst, nodeType, workflow, allNodeTypes, nestedRegion, successVars, generatedNodes, lines, `${indent} `, false, branchingNodes, branchRegions, isAsync, ctxVar, bundleMode, nestedPreDeclared, false, production);
1228
+ generateBranchingNodeCode(inst, nodeType, workflow, allNodeTypes, nestedRegion, successVars, generatedNodes, lines, `${indent} `, false, branchingNodes, branchRegions, isAsync, ctxVar, bundleMode, nestedPreDeclared, nestedPreDeclared.has(nestedSafeId), // force tracking if flag was pre-declared at higher scope
1229
+ production);
1229
1230
  successExecutedNodes.push(instanceId);
1230
1231
  nestedRegion.successNodes.forEach((n) => successExecutedNodes.push(n));
1231
1232
  nestedRegion.failureNodes.forEach((n) => successExecutedNodes.push(n));
@@ -1275,7 +1276,8 @@ bundleMode = false, preDeclaredSuccessFlags = new Set(), forceTrackSuccess = fal
1275
1276
  lines.push(`${indent} let ${nestedSafeId}_success = false;`);
1276
1277
  nestedPreDeclared.add(nestedSafeId);
1277
1278
  }
1278
- generateBranchingNodeCode(inst, nodeType, workflow, allNodeTypes, nestedRegion, failureVars, generatedNodes, lines, `${indent} `, false, branchingNodes, branchRegions, isAsync, ctxVar, bundleMode, nestedPreDeclared, false, production);
1279
+ generateBranchingNodeCode(inst, nodeType, workflow, allNodeTypes, nestedRegion, failureVars, generatedNodes, lines, `${indent} `, false, branchingNodes, branchRegions, isAsync, ctxVar, bundleMode, nestedPreDeclared, nestedPreDeclared.has(nestedSafeId), // force tracking if flag was pre-declared at higher scope
1280
+ production);
1279
1281
  failureExecutedNodes.push(instanceId);
1280
1282
  nestedRegion.successNodes.forEach((n) => failureExecutedNodes.push(n));
1281
1283
  nestedRegion.failureNodes.forEach((n) => failureExecutedNodes.push(n));
@@ -67,7 +67,6 @@ fw compile <input> [options]
67
67
  | `-w, --workflow-name <name>` | Specific workflow name | all |
68
68
  | `-f, --format <format>` | Module format: `esm`, `cjs`, `auto` | `auto` |
69
69
  | `--strict` | Type coercion warnings become errors | `false` |
70
- | `--inline-runtime` | Force inline runtime | `false` |
71
70
  | `--clean` | Omit redundant @param/@returns | `false` |
72
71
  | `--target <target>` | `typescript` or `inngest` | `typescript` |
73
72
  | `--cron <schedule>` | Cron schedule (Inngest only) | — |
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: Compilation
3
3
  description: How compilation works, TypeScript and Inngest targets, compile options, and serve handler generation
4
- keywords: [compile, compilation, target, typescript, inngest, production, source-map, format, strict, inline-runtime, clean, serve, framework, step.run, durable, trigger, cancelOn, retries, timeout, throttle, cron, markers]
4
+ keywords: [compile, compilation, target, typescript, inngest, production, source-map, format, strict, clean, serve, framework, step.run, durable, trigger, cancelOn, retries, timeout, throttle, cron, markers]
5
5
  ---
6
6
 
7
7
  # Compilation
@@ -59,7 +59,7 @@ fw compile workflow.ts
59
59
  Generates code like:
60
60
  ```typescript
61
61
  // @flow-weaver-runtime — start
62
- import { ExecutionContext } from '@synergenius/flow-weaver/runtime';
62
+ // (inline runtime: GeneratedExecutionContext, CancellationError, types)
63
63
  // @flow-weaver-runtime — end
64
64
 
65
65
  export function myWorkflow(params: { data: string }) {
@@ -272,14 +272,6 @@ fw compile workflow.ts --strict
272
272
 
273
273
  Equivalent to adding `@strictTypes` to the workflow annotation.
274
274
 
275
- ### Inline Runtime (`--inline-runtime`)
276
-
277
- Force inline runtime code even when `@synergenius/flow-weaver` is installed as a dependency. Normally the compiler generates an import; this flag embeds the runtime directly.
278
-
279
- ```bash
280
- fw compile workflow.ts --inline-runtime
281
- ```
282
-
283
275
  ### Clean Output (`--clean`)
284
276
 
285
277
  Omit redundant `@param`/`@returns` annotations from the compiled output. Produces cleaner generated code.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synergenius/flow-weaver",
3
- "version": "0.23.4",
3
+ "version": "0.24.0",
4
4
  "description": "Deterministic workflow compiler for AI agents. Compiles to standalone TypeScript, no runtime dependencies.",
5
5
  "private": false,
6
6
  "type": "module",
@@ -108,6 +108,7 @@
108
108
  "dist",
109
109
  "!dist/**/*.map",
110
110
  "docs/reference",
111
+ "scripts/postinstall.cjs",
111
112
  "README.md",
112
113
  "LICENSE"
113
114
  ],
@@ -131,7 +132,8 @@
131
132
  "docs:serve": "npm run docs && npx http-server docs/api -c-1 -o",
132
133
  "prepare": "npm run build && husky || true",
133
134
  "cli": "tsx src/cli/index.ts",
134
- "diagram": "tsx scripts/generate-diagram.ts"
135
+ "diagram": "tsx scripts/generate-diagram.ts",
136
+ "postinstall": "node scripts/postinstall.cjs"
135
137
  },
136
138
  "keywords": [
137
139
  "flow-weaver",
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Postinstall welcome message — context-aware, shown after npm install.
4
+ *
5
+ * This is a standalone CJS script (no build step, no ESM, no dependencies).
6
+ * The logic is duplicated from src/cli/postinstall.ts which has full test coverage.
7
+ *
8
+ * Silent in CI. No telemetry. No file modifications. No dark patterns.
9
+ * Never fails the install.
10
+ */
11
+ 'use strict';
12
+
13
+ try {
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+
17
+ const CI_VARS = ['CI', 'CONTINUOUS_INTEGRATION', 'BUILD_NUMBER', 'GITHUB_ACTIONS', 'GITLAB_CI', 'CIRCLECI', 'JENKINS_URL', 'CODEBUILD_BUILD_ID'];
18
+ if (CI_VARS.some(v => process.env[v])) process.exit(0);
19
+
20
+ const cwd = process.env.INIT_CWD || process.cwd();
21
+
22
+ function hasPkg() { try { return fs.existsSync(path.join(cwd, 'package.json')); } catch { return false; } }
23
+ function hasTsConfig() { try { return fs.existsSync(path.join(cwd, 'tsconfig.json')); } catch { return false; } }
24
+
25
+ function dirHasTs(dir) {
26
+ try { return fs.readdirSync(dir).some(f => f.endsWith('.ts') && !f.endsWith('.d.ts')); }
27
+ catch { return false; }
28
+ }
29
+
30
+ function dirHasFlowWeaver(dir) {
31
+ try {
32
+ for (const f of fs.readdirSync(dir)) {
33
+ if (!f.endsWith('.ts') || f.endsWith('.d.ts')) continue;
34
+ try { if (fs.readFileSync(path.join(dir, f), 'utf8').includes('@flowWeaver')) return true; }
35
+ catch { /* skip */ }
36
+ }
37
+ } catch { /* skip */ }
38
+ return false;
39
+ }
40
+
41
+ let msg;
42
+
43
+ if (!hasPkg()) {
44
+ // Global install or outside a project
45
+ msg = [
46
+ '',
47
+ ' flow-weaver installed \u2713',
48
+ '',
49
+ ' Create a project: fw init my-project',
50
+ ' Or try it now: fw create workflow hello-world',
51
+ '',
52
+ ].join('\n');
53
+ } else if (!hasTsConfig()) {
54
+ // Project without TypeScript
55
+ msg = [
56
+ '',
57
+ ' flow-weaver installed \u2713',
58
+ '',
59
+ ' Get started: fw init',
60
+ '',
61
+ ].join('\n');
62
+ } else if (dirHasFlowWeaver(cwd) || dirHasFlowWeaver(path.join(cwd, 'src'))) {
63
+ // Existing flow-weaver project
64
+ msg = [
65
+ '',
66
+ ' flow-weaver updated \u2713',
67
+ '',
68
+ ' Check health: fw doctor',
69
+ '',
70
+ ].join('\n');
71
+ } else {
72
+ // TypeScript project, no @flowWeaver yet
73
+ msg = [
74
+ '',
75
+ ' flow-weaver installed \u2713',
76
+ '',
77
+ ' Add above any function: /** @flowWeaver nodeType */',
78
+ ' Then compile: fw compile src/',
79
+ '',
80
+ ].join('\n');
81
+ }
82
+
83
+ process.stderr.write(msg);
84
+ } catch {
85
+ // Never fail the install
86
+ }