@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.
- package/dist/agent/index.d.ts +2 -1
- package/dist/agent/index.js +2 -0
- package/dist/agent/providers/anthropic.js +14 -1
- package/dist/agent/providers/claude-cli.d.ts +1 -1
- package/dist/agent/providers/claude-cli.js +4 -1
- package/dist/agent/providers/openai-compat.d.ts +1 -1
- package/dist/agent/providers/openai-compat.js +2 -1
- package/dist/agent/providers/platform.d.ts +1 -1
- package/dist/agent/providers/platform.js +3 -1
- package/dist/agent/types.d.ts +21 -2
- package/dist/agent/types.js +6 -1
- package/dist/api/generate-in-place.d.ts +0 -9
- package/dist/api/generate-in-place.js +6 -54
- package/dist/api/generate.d.ts +4 -5
- package/dist/api/generate.js +6 -26
- package/dist/cli/commands/compile.d.ts +0 -5
- package/dist/cli/commands/compile.js +36 -8
- package/dist/cli/commands/context.js +4 -6
- package/dist/cli/commands/create.js +6 -14
- package/dist/cli/commands/describe.js +6 -10
- package/dist/cli/commands/diagram.js +18 -25
- package/dist/cli/commands/diff.js +7 -14
- package/dist/cli/commands/docs.js +3 -6
- package/dist/cli/commands/doctor.js +1 -1
- package/dist/cli/commands/export.js +1 -1
- package/dist/cli/commands/grammar.js +3 -4
- package/dist/cli/commands/implement.js +8 -13
- package/dist/cli/commands/market.js +4 -8
- package/dist/cli/commands/migrate.js +2 -1
- package/dist/cli/commands/modify.js +2 -1
- package/dist/cli/commands/openapi.js +2 -1
- package/dist/cli/commands/pattern.js +3 -2
- package/dist/cli/commands/strip.js +3 -6
- package/dist/cli/commands/validate.js +6 -1
- package/dist/cli/flow-weaver.mjs +781 -791
- package/dist/cli/index.js +10 -12
- package/dist/cli/postinstall.d.ts +16 -0
- package/dist/cli/postinstall.js +119 -0
- package/dist/cli/utils/parse-int-strict.d.ts +7 -0
- package/dist/cli/utils/parse-int-strict.js +17 -0
- package/dist/cli/utils/safe-write.d.ts +18 -0
- package/dist/cli/utils/safe-write.js +54 -0
- package/dist/generated-version.d.ts +1 -1
- package/dist/generated-version.js +1 -1
- package/dist/mcp/tools-debug.js +2 -2
- package/docs/reference/cli-reference.md +0 -1
- package/docs/reference/compilation.md +2 -10
- package/package.json +4 -2
- package/scripts/postinstall.cjs +86 -0
package/dist/agent/index.d.ts
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* Provider-agnostic agent loop with MCP bridge for tool execution.
|
|
5
5
|
* Built-in providers: Anthropic API, Claude CLI, OpenAI-compatible (GPT-4o, Groq, Ollama, etc).
|
|
6
6
|
*/
|
|
7
|
-
export type { StreamEvent, AgentMessage, AgentProvider, ToolDefinition, ToolExecutor, ToolEvent, McpBridge, AgentLoopOptions, AgentLoopResult, StreamOptions, SpawnFn, ClaudeCliProviderOptions, CliSessionOptions, Logger, } from './types.js';
|
|
7
|
+
export type { SplitPrompt, StreamEvent, AgentMessage, AgentProvider, ToolDefinition, ToolExecutor, ToolEvent, McpBridge, AgentLoopOptions, AgentLoopResult, StreamOptions, SpawnFn, ClaudeCliProviderOptions, CliSessionOptions, Logger, } from './types.js';
|
|
8
|
+
export { joinSplitPrompt } from './types.js';
|
|
8
9
|
export { runAgentLoop } from './agent-loop.js';
|
|
9
10
|
export { AnthropicProvider, createAnthropicProvider } from './providers/anthropic.js';
|
|
10
11
|
export type { AnthropicProviderOptions } from './providers/anthropic.js';
|
package/dist/agent/index.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* Provider-agnostic agent loop with MCP bridge for tool execution.
|
|
5
5
|
* Built-in providers: Anthropic API, Claude CLI, OpenAI-compatible (GPT-4o, Groq, Ollama, etc).
|
|
6
6
|
*/
|
|
7
|
+
// Prompt utilities
|
|
8
|
+
export { joinSplitPrompt } from './types.js';
|
|
7
9
|
// Agent loop
|
|
8
10
|
export { runAgentLoop } from './agent-loop.js';
|
|
9
11
|
// Providers
|
|
@@ -4,6 +4,19 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Adapted from pack-weaver's streamAnthropicWithTools.
|
|
6
6
|
*/
|
|
7
|
+
/**
|
|
8
|
+
* Convert a SplitPrompt to Anthropic's system content block array.
|
|
9
|
+
* The prefix gets cache_control for prompt caching; the suffix does not.
|
|
10
|
+
*/
|
|
11
|
+
function buildSystemBlocks(prompt) {
|
|
12
|
+
const blocks = [
|
|
13
|
+
{ type: 'text', text: prompt.prefix, cache_control: { type: 'ephemeral' } },
|
|
14
|
+
];
|
|
15
|
+
if (prompt.suffix) {
|
|
16
|
+
blocks.push({ type: 'text', text: prompt.suffix });
|
|
17
|
+
}
|
|
18
|
+
return blocks;
|
|
19
|
+
}
|
|
7
20
|
export class AnthropicProvider {
|
|
8
21
|
apiKey;
|
|
9
22
|
model;
|
|
@@ -49,7 +62,7 @@ export class AnthropicProvider {
|
|
|
49
62
|
model,
|
|
50
63
|
max_tokens: maxTokens,
|
|
51
64
|
stream: true,
|
|
52
|
-
...(options?.systemPrompt ? { system: options.systemPrompt } : {}),
|
|
65
|
+
...(options?.systemPrompt ? { system: buildSystemBlocks(options.systemPrompt) } : {}),
|
|
53
66
|
messages: apiMessages,
|
|
54
67
|
...(apiTools.length > 0 ? { tools: apiTools } : {}),
|
|
55
68
|
});
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Adapted from platform's streamClaudeCliChat. Platform-specific dependencies
|
|
6
6
|
* (spawnSandboxed, getBinPath, config) are replaced with injectable options.
|
|
7
7
|
*/
|
|
8
|
-
import type
|
|
8
|
+
import { type AgentProvider, type AgentMessage, type ToolDefinition, type StreamEvent, type StreamOptions, type ClaudeCliProviderOptions } from '../types.js';
|
|
9
9
|
export declare class ClaudeCliProvider implements AgentProvider {
|
|
10
10
|
private binPath;
|
|
11
11
|
private cwd;
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* (spawnSandboxed, getBinPath, config) are replaced with injectable options.
|
|
7
7
|
*/
|
|
8
8
|
import { spawn as nodeSpawn } from 'node:child_process';
|
|
9
|
+
import { joinSplitPrompt, } from '../types.js';
|
|
9
10
|
import { StreamJsonParser } from '../streaming.js';
|
|
10
11
|
import { createMcpBridge } from '../mcp-bridge.js';
|
|
11
12
|
export class ClaudeCliProvider {
|
|
@@ -31,7 +32,9 @@ export class ClaudeCliProvider {
|
|
|
31
32
|
const model = options?.model ?? this.model;
|
|
32
33
|
// Format messages into a single prompt for -p mode
|
|
33
34
|
const prompt = formatPrompt(messages);
|
|
34
|
-
const systemPrompt = options?.systemPrompt
|
|
35
|
+
const systemPrompt = options?.systemPrompt
|
|
36
|
+
? joinSplitPrompt(options.systemPrompt)
|
|
37
|
+
: undefined;
|
|
35
38
|
// Set up MCP bridge for tool access if tools are provided and no config given
|
|
36
39
|
let bridge = null;
|
|
37
40
|
let mcpConfigPath = this.mcpConfigPath;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* No SDK dependency. Uses only Node.js native fetch + SSE parsing.
|
|
6
6
|
* Converts OpenAI's delta format to the canonical StreamEvent union.
|
|
7
7
|
*/
|
|
8
|
-
import type
|
|
8
|
+
import { type AgentProvider, type AgentMessage, type ToolDefinition, type StreamEvent, type StreamOptions } from '../types.js';
|
|
9
9
|
export interface OpenAICompatProviderOptions {
|
|
10
10
|
apiKey: string;
|
|
11
11
|
model?: string;
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* No SDK dependency. Uses only Node.js native fetch + SSE parsing.
|
|
6
6
|
* Converts OpenAI's delta format to the canonical StreamEvent union.
|
|
7
7
|
*/
|
|
8
|
+
import { joinSplitPrompt } from '../types.js';
|
|
8
9
|
export class OpenAICompatProvider {
|
|
9
10
|
apiKey;
|
|
10
11
|
model;
|
|
@@ -38,7 +39,7 @@ export class OpenAICompatProvider {
|
|
|
38
39
|
const body = {
|
|
39
40
|
model,
|
|
40
41
|
messages: [
|
|
41
|
-
...(options?.systemPrompt ? [{ role: 'system', content: options.systemPrompt }] : []),
|
|
42
|
+
...(options?.systemPrompt ? [{ role: 'system', content: joinSplitPrompt(options.systemPrompt) }] : []),
|
|
42
43
|
...apiMessages,
|
|
43
44
|
],
|
|
44
45
|
max_tokens: maxTokens,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Uses the platform's AI credits, no local API key needed.
|
|
4
4
|
* Connects to POST /ai-chat/stream and parses SSE events.
|
|
5
5
|
*/
|
|
6
|
-
import type
|
|
6
|
+
import { type AgentProvider, type AgentMessage, type ToolDefinition, type StreamEvent, type StreamOptions } from '../types.js';
|
|
7
7
|
export interface PlatformProviderOptions {
|
|
8
8
|
/** JWT token or API key for platform auth */
|
|
9
9
|
token: string;
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Uses the platform's AI credits, no local API key needed.
|
|
4
4
|
* Connects to POST /ai-chat/stream and parses SSE events.
|
|
5
5
|
*/
|
|
6
|
+
import { joinSplitPrompt } from '../types.js';
|
|
6
7
|
export class PlatformProvider {
|
|
7
8
|
token;
|
|
8
9
|
baseUrl;
|
|
@@ -37,7 +38,8 @@ export class PlatformProvider {
|
|
|
37
38
|
const body = { message };
|
|
38
39
|
if (options?.systemPrompt) {
|
|
39
40
|
// Platform doesn't accept system prompt directly via API — embed in message
|
|
40
|
-
|
|
41
|
+
const systemStr = joinSplitPrompt(options.systemPrompt);
|
|
42
|
+
body.message = `[System context: ${systemStr.slice(0, 2000)}]\n\n${message}`;
|
|
41
43
|
}
|
|
42
44
|
const response = await fetch(`${this.baseUrl}/ai-chat/stream`, {
|
|
43
45
|
method: 'POST',
|
package/dist/agent/types.d.ts
CHANGED
|
@@ -65,8 +65,25 @@ export interface ToolEvent {
|
|
|
65
65
|
result?: string;
|
|
66
66
|
isError?: boolean;
|
|
67
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Split system prompt for Anthropic API cache optimization.
|
|
70
|
+
*
|
|
71
|
+
* The prefix (stable FW knowledge) is cached across calls via cache_control.
|
|
72
|
+
* The suffix (per-task context) varies per call but rides on the cached prefix.
|
|
73
|
+
*
|
|
74
|
+
* Providers that support structured system blocks (Anthropic) use both parts.
|
|
75
|
+
* Providers that only accept strings (CLI, OpenAI, platform) concatenate them.
|
|
76
|
+
*/
|
|
77
|
+
export interface SplitPrompt {
|
|
78
|
+
/** Stable prefix — identical across calls. Cacheable. */
|
|
79
|
+
prefix: string;
|
|
80
|
+
/** Dynamic suffix — varies per task/call. Not cached. */
|
|
81
|
+
suffix: string;
|
|
82
|
+
}
|
|
83
|
+
/** Convert a SplitPrompt to a single string (for providers that don't support blocks). */
|
|
84
|
+
export declare function joinSplitPrompt(prompt: SplitPrompt): string;
|
|
68
85
|
export interface StreamOptions {
|
|
69
|
-
systemPrompt?:
|
|
86
|
+
systemPrompt?: SplitPrompt;
|
|
70
87
|
model?: string;
|
|
71
88
|
maxTokens?: number;
|
|
72
89
|
signal?: AbortSignal;
|
|
@@ -89,7 +106,7 @@ export interface McpBridge {
|
|
|
89
106
|
cleanup: () => void;
|
|
90
107
|
}
|
|
91
108
|
export interface AgentLoopOptions {
|
|
92
|
-
systemPrompt?:
|
|
109
|
+
systemPrompt?: SplitPrompt;
|
|
93
110
|
maxIterations?: number;
|
|
94
111
|
maxTokens?: number;
|
|
95
112
|
model?: string;
|
|
@@ -145,6 +162,8 @@ export interface CliSessionOptions {
|
|
|
145
162
|
model: string;
|
|
146
163
|
/** Pre-configured MCP config path. */
|
|
147
164
|
mcpConfigPath?: string;
|
|
165
|
+
/** Disable specific built-in tools (e.g. ['Read', 'Edit', 'Write', 'Bash'] to force MCP tools). */
|
|
166
|
+
disallowedTools?: string[];
|
|
148
167
|
/** Custom spawn function. Defaults to child_process.spawn. */
|
|
149
168
|
spawnFn?: SpawnFn;
|
|
150
169
|
/** Idle timeout in milliseconds. Defaults to 600000 (10 minutes). */
|
package/dist/agent/types.js
CHANGED
|
@@ -3,5 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* All types are pure — no runtime imports, no side effects.
|
|
5
5
|
*/
|
|
6
|
-
|
|
6
|
+
/** Convert a SplitPrompt to a single string (for providers that don't support blocks). */
|
|
7
|
+
export function joinSplitPrompt(prompt) {
|
|
8
|
+
if (!prompt.suffix)
|
|
9
|
+
return prompt.prefix;
|
|
10
|
+
return prompt.prefix + '\n\n' + prompt.suffix;
|
|
11
|
+
}
|
|
7
12
|
//# sourceMappingURL=types.js.map
|
|
@@ -29,17 +29,8 @@ export interface InPlaceGenerateOptions {
|
|
|
29
29
|
* @default 'esm'
|
|
30
30
|
*/
|
|
31
31
|
moduleFormat?: TModuleFormat;
|
|
32
|
-
/**
|
|
33
|
-
* Force inline runtime even when @synergenius/flow-weaver package is installed.
|
|
34
|
-
* When false/undefined, the compiler auto-detects the package and uses
|
|
35
|
-
* external imports when available (smaller generated code, shared runtime).
|
|
36
|
-
* @default false
|
|
37
|
-
*/
|
|
38
|
-
inlineRuntime?: boolean;
|
|
39
32
|
/**
|
|
40
33
|
* Absolute path to the source file being compiled.
|
|
41
|
-
* Used to detect if @synergenius/flow-weaver is installed relative to the file.
|
|
42
|
-
* If not provided, falls back to process.cwd().
|
|
43
34
|
*/
|
|
44
35
|
sourceFile?: string;
|
|
45
36
|
/**
|
|
@@ -15,7 +15,6 @@ import { shouldWorkflowBeAsync } from '../generator/async-detection.js';
|
|
|
15
15
|
import { detectSugarPatterns, filterStaleMacros } from '../sugar-optimizer.js';
|
|
16
16
|
import * as ts from 'typescript';
|
|
17
17
|
import * as path from 'path';
|
|
18
|
-
import * as fs from 'fs';
|
|
19
18
|
// Marker constants
|
|
20
19
|
export const MARKERS = {
|
|
21
20
|
RUNTIME_START: '// @flow-weaver-runtime-start',
|
|
@@ -23,30 +22,6 @@ export const MARKERS = {
|
|
|
23
22
|
BODY_START: '// @flow-weaver-body-start',
|
|
24
23
|
BODY_END: '// @flow-weaver-body-end',
|
|
25
24
|
};
|
|
26
|
-
/**
|
|
27
|
-
* Check if `@synergenius/flow-weaver` is available as an npm package
|
|
28
|
-
* by walking up from the given directory looking for node_modules.
|
|
29
|
-
*/
|
|
30
|
-
function isFlowWeaverPackageInstalled(startDir) {
|
|
31
|
-
let dir = startDir;
|
|
32
|
-
const root = path.parse(dir).root;
|
|
33
|
-
while (dir !== root) {
|
|
34
|
-
const candidate = path.join(dir, 'node_modules', '@synergenius', 'flow-weaver');
|
|
35
|
-
try {
|
|
36
|
-
if (fs.existsSync(candidate)) {
|
|
37
|
-
return true;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
catch {
|
|
41
|
-
// Permission error or similar — skip and keep walking
|
|
42
|
-
}
|
|
43
|
-
const parent = path.dirname(dir);
|
|
44
|
-
if (parent === dir)
|
|
45
|
-
break;
|
|
46
|
-
dir = parent;
|
|
47
|
-
}
|
|
48
|
-
return false;
|
|
49
|
-
}
|
|
50
25
|
/**
|
|
51
26
|
* Generate executable code in-place, preserving user code.
|
|
52
27
|
*
|
|
@@ -56,7 +31,7 @@ function isFlowWeaverPackageInstalled(startDir) {
|
|
|
56
31
|
* @returns The updated source code with generated sections
|
|
57
32
|
*/
|
|
58
33
|
export function generateInPlace(sourceCode, ast, options = {}) {
|
|
59
|
-
const { production = false, allWorkflows, moduleFormat = 'esm',
|
|
34
|
+
const { production = false, allWorkflows, moduleFormat = 'esm', sourceFile, skipParamReturns = false } = options;
|
|
60
35
|
let result = sourceCode;
|
|
61
36
|
let hasChanges = false;
|
|
62
37
|
// Step 1: Update JSDoc annotations for node type functions
|
|
@@ -90,15 +65,8 @@ export function generateInPlace(sourceCode, ast, options = {}) {
|
|
|
90
65
|
result = jsdocResult.code;
|
|
91
66
|
hasChanges = true;
|
|
92
67
|
}
|
|
93
|
-
// Step 3: Generate and insert/replace runtime section
|
|
94
|
-
|
|
95
|
-
let useExternalRuntime = false;
|
|
96
|
-
if (!inlineRuntime) {
|
|
97
|
-
const lookupDir = sourceFile ? path.dirname(sourceFile) : (ast.sourceFile ? path.dirname(ast.sourceFile) : process.cwd());
|
|
98
|
-
useExternalRuntime = isFlowWeaverPackageInstalled(lookupDir);
|
|
99
|
-
}
|
|
100
|
-
const externalRuntimePath = useExternalRuntime ? '@synergenius/flow-weaver/runtime' : undefined;
|
|
101
|
-
const runtimeCode = generateRuntimeSection(ast.functionName, production, moduleFormat, externalRuntimePath);
|
|
68
|
+
// Step 3: Generate and insert/replace runtime section (always inlined — zero runtime dependencies)
|
|
69
|
+
const runtimeCode = generateRuntimeSection(ast.functionName, production, moduleFormat);
|
|
102
70
|
const runtimeResult = replaceOrInsertSection(result, MARKERS.RUNTIME_START, MARKERS.RUNTIME_END, runtimeCode, 'top');
|
|
103
71
|
if (runtimeResult.changed) {
|
|
104
72
|
result = runtimeResult.code;
|
|
@@ -145,31 +113,15 @@ export function generateInPlace(sourceCode, ast, options = {}) {
|
|
|
145
113
|
}
|
|
146
114
|
/**
|
|
147
115
|
* Generate the runtime section with proper markers.
|
|
148
|
-
*
|
|
116
|
+
* Runtime is always inlined — zero runtime dependencies.
|
|
149
117
|
*/
|
|
150
|
-
function generateRuntimeSection(functionName, production, moduleFormat = 'esm'
|
|
118
|
+
function generateRuntimeSection(functionName, production, moduleFormat = 'esm') {
|
|
151
119
|
const lines = [];
|
|
152
120
|
lines.push('// ============================================================================');
|
|
153
121
|
lines.push('// DO NOT EDIT - This section is auto-generated by Flow Weaver');
|
|
154
122
|
lines.push('// ============================================================================');
|
|
155
123
|
lines.push('');
|
|
156
|
-
|
|
157
|
-
// External runtime: generate import statements instead of inline code
|
|
158
|
-
lines.push(`import { GeneratedExecutionContext, CancellationError } from '${externalRuntimePath}';`);
|
|
159
|
-
if (!production) {
|
|
160
|
-
lines.push(`import type { TDebugger, TDebugController } from '${externalRuntimePath}';`);
|
|
161
|
-
// Declare __flowWeaverDebugger__ so body code can reference it
|
|
162
|
-
lines.push('declare const __flowWeaverDebugger__: TDebugger | undefined;');
|
|
163
|
-
}
|
|
164
|
-
else {
|
|
165
|
-
// Production mode still needs TDebugController for the __ctrl__ variable
|
|
166
|
-
lines.push(`import type { TDebugController } from '${externalRuntimePath}';`);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
else {
|
|
170
|
-
// Inline runtime: embed all types and classes directly
|
|
171
|
-
lines.push(generateInlineRuntime(production));
|
|
172
|
-
}
|
|
124
|
+
lines.push(generateInlineRuntime(production));
|
|
173
125
|
return lines.join('\n');
|
|
174
126
|
}
|
|
175
127
|
/**
|
package/dist/api/generate.d.ts
CHANGED
|
@@ -18,12 +18,11 @@ export interface GenerateOptions extends Partial<ASTGenerateOptions> {
|
|
|
18
18
|
*/
|
|
19
19
|
moduleFormat?: TModuleFormat;
|
|
20
20
|
/**
|
|
21
|
-
*
|
|
22
|
-
* When
|
|
23
|
-
*
|
|
24
|
-
* @example '../runtime/types.js'
|
|
21
|
+
* Enable bundle mode for multi-workflow bundles.
|
|
22
|
+
* When true, imports node types from node-types/ directory and workflows from sibling files.
|
|
23
|
+
* Runtime is always inlined regardless of this setting.
|
|
25
24
|
*/
|
|
26
|
-
|
|
25
|
+
bundleMode?: boolean;
|
|
27
26
|
/**
|
|
28
27
|
* Constants from source file(s) to include at the top of the generated file.
|
|
29
28
|
* Used in bundle mode when local node functions are inlined and need their
|
package/dist/api/generate.js
CHANGED
|
@@ -38,7 +38,7 @@ export function generateModuleExports(functionNames) {
|
|
|
38
38
|
return `module.exports = { ${functionNames.join(', ')} };`;
|
|
39
39
|
}
|
|
40
40
|
export function generateCode(ast, options) {
|
|
41
|
-
const { production = false, sourceMap = false, allWorkflows = [], moduleFormat = 'esm',
|
|
41
|
+
const { production = false, sourceMap = false, allWorkflows = [], moduleFormat = 'esm', bundleMode = false, constants = [], externalNodeTypes = {}, generateStubs = false, outputFormat = 'typescript', } = options || {};
|
|
42
42
|
// Check for stub nodes — refuse to generate unless explicitly allowed
|
|
43
43
|
const stubNodeTypes = ast.nodeTypes.filter((nt) => nt.variant === 'STUB');
|
|
44
44
|
if (stubNodeTypes.length > 0 && !generateStubs) {
|
|
@@ -78,8 +78,6 @@ export function generateCode(ast, options) {
|
|
|
78
78
|
}
|
|
79
79
|
};
|
|
80
80
|
// Generate function body using existing body generator
|
|
81
|
-
// Bundle mode uses params object pattern for node wrapper calls
|
|
82
|
-
const bundleMode = !!externalRuntimePath;
|
|
83
81
|
const functionBody = bodyGenerator.generateWithExecutionContext(ast, ast.nodeTypes, shouldBeAsync, production, bundleMode);
|
|
84
82
|
// Build the complete module
|
|
85
83
|
const lines = [];
|
|
@@ -87,25 +85,8 @@ export function generateCode(ast, options) {
|
|
|
87
85
|
addLine();
|
|
88
86
|
lines.push('');
|
|
89
87
|
addLine();
|
|
90
|
-
// Include runtime (
|
|
91
|
-
|
|
92
|
-
// Import from external runtime module to avoid duplicate declarations in multi-file bundles
|
|
93
|
-
lines.push(`// Runtime imported from shared module`);
|
|
94
|
-
addLine();
|
|
95
|
-
lines.push(generateImportStatement(['GeneratedExecutionContext', 'CancellationError'], externalRuntimePath, moduleFormat));
|
|
96
|
-
addLine();
|
|
97
|
-
if (!production) {
|
|
98
|
-
// Import TDebugger type from external runtime
|
|
99
|
-
lines.push(moduleFormat === 'cjs'
|
|
100
|
-
? `const { TDebugger } = require('${externalRuntimePath}');`
|
|
101
|
-
: `import type { TDebugger } from '${externalRuntimePath}';`);
|
|
102
|
-
addLine();
|
|
103
|
-
}
|
|
104
|
-
lines.push('');
|
|
105
|
-
addLine();
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
// Include inline runtime (types + GeneratedExecutionContext)
|
|
88
|
+
// Include inline runtime (always inlined — zero runtime dependencies)
|
|
89
|
+
{
|
|
109
90
|
const inlineRuntime = generateInlineRuntime(production);
|
|
110
91
|
const runtimeLines = inlineRuntime.split('\n');
|
|
111
92
|
runtimeLines.forEach((line) => {
|
|
@@ -172,11 +153,10 @@ export function generateCode(ast, options) {
|
|
|
172
153
|
lines.push('');
|
|
173
154
|
addLine();
|
|
174
155
|
// Import regular node functions from source files
|
|
175
|
-
// In bundle mode
|
|
156
|
+
// In bundle mode, import from node-types directory
|
|
176
157
|
// Otherwise import from .generated files in the same directory
|
|
177
158
|
functionImportsByFile.forEach((nodes, sourceFile) => {
|
|
178
|
-
if (
|
|
179
|
-
// Bundle mode: import from node-types directory
|
|
159
|
+
if (bundleMode) {
|
|
180
160
|
// Bundle mode: import _impl (positional data args for expression nodes, execute + data args for regular)
|
|
181
161
|
// The wrapper is only for HTTP entry points, not internal workflow calls
|
|
182
162
|
nodes.forEach((node) => {
|
|
@@ -199,7 +179,7 @@ export function generateCode(ast, options) {
|
|
|
199
179
|
// Import workflows from their generated files
|
|
200
180
|
// In bundle mode, import from sibling workflow files
|
|
201
181
|
workflowImportsByFile.forEach((names, sourceFile) => {
|
|
202
|
-
if (
|
|
182
|
+
if (bundleMode) {
|
|
203
183
|
// Bundle mode: import each workflow from the workflows directory
|
|
204
184
|
names.forEach((name) => {
|
|
205
185
|
const relativePath = `./${name}.js`;
|
|
@@ -16,11 +16,6 @@ export interface CompileOptions {
|
|
|
16
16
|
* - 'auto': Auto-detect from project's package.json (default)
|
|
17
17
|
*/
|
|
18
18
|
format?: 'esm' | 'cjs' | 'auto';
|
|
19
|
-
/**
|
|
20
|
-
* Force inline runtime even when @synergenius/flow-weaver package is installed.
|
|
21
|
-
* By default, the compiler uses external runtime imports when the package is available.
|
|
22
|
-
*/
|
|
23
|
-
inlineRuntime?: boolean;
|
|
24
19
|
/**
|
|
25
20
|
* Omit redundant @param/@returns annotations from compiled output.
|
|
26
21
|
* Useful for vibe coders who don't use the visual editor.
|
|
@@ -14,6 +14,7 @@ import { getFriendlyError } from '../../friendly-errors.js';
|
|
|
14
14
|
import { detectProjectModuleFormat } from './doctor.js';
|
|
15
15
|
import { compileTargetRegistry } from '../../generator/compile-target-registry.js';
|
|
16
16
|
import { AnnotationParser } from '../../parser.js';
|
|
17
|
+
import { safeWriteFile, safeAppendFile } from '../utils/safe-write.js';
|
|
17
18
|
/** Show path relative to cwd for cleaner output */
|
|
18
19
|
function displayPath(filePath) {
|
|
19
20
|
const rel = path.relative(process.cwd(), filePath);
|
|
@@ -36,7 +37,7 @@ function resolveModuleFormat(format, cwd) {
|
|
|
36
37
|
return detection.format;
|
|
37
38
|
}
|
|
38
39
|
export async function compileCommand(input, options = {}) {
|
|
39
|
-
const { production = false, sourceMap = false, strict = false, verbose = false, workflowName, dryRun = false, format,
|
|
40
|
+
const { production = false, sourceMap = false, strict = false, verbose = false, workflowName, dryRun = false, format, clean = false, target, output } = options;
|
|
40
41
|
// Handle custom compile target
|
|
41
42
|
if (target && target !== 'typescript') {
|
|
42
43
|
return compileCustomTarget(target, input, { production, verbose, workflowName, dryRun, cron: options.cron, serve: options.serve, framework: options.framework, typedEvents: options.typedEvents, retries: options.retries, timeout: options.timeout });
|
|
@@ -67,6 +68,27 @@ export async function compileCommand(input, options = {}) {
|
|
|
67
68
|
if (files.length === 0) {
|
|
68
69
|
throw new Error(`No files found matching pattern: ${input}`);
|
|
69
70
|
}
|
|
71
|
+
// Resolve --output: determine if it's a file or directory target
|
|
72
|
+
let outputDir;
|
|
73
|
+
let outputFile;
|
|
74
|
+
if (output) {
|
|
75
|
+
const isOutputDir = output.endsWith('/') || output.endsWith(path.sep) ||
|
|
76
|
+
(fs.existsSync(output) && fs.statSync(output).isDirectory());
|
|
77
|
+
if (isOutputDir) {
|
|
78
|
+
outputDir = output.endsWith('/') || output.endsWith(path.sep) ? output.slice(0, -1) : output;
|
|
79
|
+
if (!fs.existsSync(outputDir)) {
|
|
80
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
else if (files.length > 1) {
|
|
84
|
+
// Multiple input files but output is a single file — ambiguous
|
|
85
|
+
throw new Error(`Cannot use --output with a file path when compiling multiple files. ` +
|
|
86
|
+
`Use a directory path instead (e.g. --output ${output}/)`);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
outputFile = path.resolve(output);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
70
92
|
const totalTimer = logger.timer();
|
|
71
93
|
logger.section('Compiling Workflows');
|
|
72
94
|
if (verbose) {
|
|
@@ -161,10 +183,16 @@ export async function compileCommand(input, options = {}) {
|
|
|
161
183
|
// Read original source
|
|
162
184
|
const sourceCode = fs.readFileSync(file, 'utf8');
|
|
163
185
|
// Generate code in-place (preserves types, interfaces, etc.)
|
|
164
|
-
const result = generateInPlace(sourceCode, parseResult.ast, { production, moduleFormat,
|
|
165
|
-
//
|
|
186
|
+
const result = generateInPlace(sourceCode, parseResult.ast, { production, moduleFormat, sourceFile: file, skipParamReturns: clean });
|
|
187
|
+
// Determine where to write the compiled output
|
|
188
|
+
const writePath = outputFile
|
|
189
|
+
? outputFile
|
|
190
|
+
: outputDir
|
|
191
|
+
? path.join(outputDir, path.basename(file))
|
|
192
|
+
: file; // in-place
|
|
193
|
+
// Write compiled output (skip in dry-run mode)
|
|
166
194
|
if (!dryRun) {
|
|
167
|
-
|
|
195
|
+
safeWriteFile(writePath, result.code);
|
|
168
196
|
// Generate source map if requested
|
|
169
197
|
if (sourceMap) {
|
|
170
198
|
const mapResult = generateCode(parseResult.ast, {
|
|
@@ -173,11 +201,11 @@ export async function compileCommand(input, options = {}) {
|
|
|
173
201
|
moduleFormat,
|
|
174
202
|
});
|
|
175
203
|
if (mapResult.sourceMap) {
|
|
176
|
-
const mapPath =
|
|
177
|
-
|
|
204
|
+
const mapPath = writePath + '.map';
|
|
205
|
+
safeWriteFile(mapPath, mapResult.sourceMap);
|
|
178
206
|
const sourceMappingComment = `\n//# sourceMappingURL=${path.basename(mapPath)}\n`;
|
|
179
207
|
if (!result.code.includes('//# sourceMappingURL=')) {
|
|
180
|
-
|
|
208
|
+
safeAppendFile(writePath, sourceMappingComment);
|
|
181
209
|
}
|
|
182
210
|
if (verbose) {
|
|
183
211
|
logger.info(` source map: ${displayPath(mapPath)}`);
|
|
@@ -291,7 +319,7 @@ export async function compileCustomTarget(target, input, options) {
|
|
|
291
319
|
}
|
|
292
320
|
}
|
|
293
321
|
else {
|
|
294
|
-
|
|
322
|
+
safeWriteFile(outputPath, code);
|
|
295
323
|
logger.success(`Compiled: ${displayPath(outputPath)}`);
|
|
296
324
|
}
|
|
297
325
|
logger.newline();
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Context command - generate LLM context bundles from documentation and grammar
|
|
3
3
|
*/
|
|
4
|
-
import * as fs from 'fs';
|
|
5
4
|
import { buildContext, PRESETS, PRESET_NAMES } from '../../context/index.js';
|
|
6
5
|
import { logger } from '../utils/logger.js';
|
|
6
|
+
import { safeWriteFile } from '../utils/safe-write.js';
|
|
7
7
|
export async function contextCommand(preset, options) {
|
|
8
8
|
// --list: show presets and exit
|
|
9
9
|
if (options.list) {
|
|
@@ -22,14 +22,12 @@ export async function contextCommand(preset, options) {
|
|
|
22
22
|
// Validate preset
|
|
23
23
|
const presetName = (preset ?? 'core');
|
|
24
24
|
if (!PRESET_NAMES.includes(presetName) && !options.topics) {
|
|
25
|
-
|
|
26
|
-
process.exit(1);
|
|
25
|
+
throw new Error(`Unknown preset "${preset}". Available: ${PRESET_NAMES.join(', ')}. Or use --topics to specify topics directly.`);
|
|
27
26
|
}
|
|
28
27
|
// Validate profile
|
|
29
28
|
const profile = options.profile ?? 'standalone';
|
|
30
29
|
if (profile !== 'standalone' && profile !== 'assistant') {
|
|
31
|
-
|
|
32
|
-
process.exit(1);
|
|
30
|
+
throw new Error(`Unknown profile "${profile}". Use "standalone" or "assistant".`);
|
|
33
31
|
}
|
|
34
32
|
const result = buildContext({
|
|
35
33
|
preset: PRESET_NAMES.includes(presetName) ? presetName : 'core',
|
|
@@ -40,7 +38,7 @@ export async function contextCommand(preset, options) {
|
|
|
40
38
|
});
|
|
41
39
|
// Write output
|
|
42
40
|
if (options.output) {
|
|
43
|
-
|
|
41
|
+
safeWriteFile(options.output, result.content);
|
|
44
42
|
logger.success(`Context written to ${options.output}`);
|
|
45
43
|
}
|
|
46
44
|
else {
|
|
@@ -41,9 +41,7 @@ export async function createWorkflowCommand(template, file, options = {}) {
|
|
|
41
41
|
const { line, async: isAsync = false, preview = false, provider, model, config: configJson, } = options;
|
|
42
42
|
const templateDef = getWorkflowTemplate(template);
|
|
43
43
|
if (!templateDef) {
|
|
44
|
-
|
|
45
|
-
logger.info("Run 'fw templates' to see available templates");
|
|
46
|
-
process.exit(1);
|
|
44
|
+
throw new Error(`Unknown workflow template: ${template}. Run 'fw templates' to see available templates`);
|
|
47
45
|
}
|
|
48
46
|
// Resolve to absolute path
|
|
49
47
|
const filePath = path.resolve(file);
|
|
@@ -67,8 +65,7 @@ export async function createWorkflowCommand(template, file, options = {}) {
|
|
|
67
65
|
Object.assign(config, JSON.parse(configJson));
|
|
68
66
|
}
|
|
69
67
|
catch {
|
|
70
|
-
|
|
71
|
-
process.exit(1);
|
|
68
|
+
throw new Error('Invalid --config JSON');
|
|
72
69
|
}
|
|
73
70
|
}
|
|
74
71
|
// Generate the template code
|
|
@@ -91,8 +88,7 @@ export async function createWorkflowCommand(template, file, options = {}) {
|
|
|
91
88
|
logger.info(` Workflow function: ${workflowName}`);
|
|
92
89
|
}
|
|
93
90
|
catch (error) {
|
|
94
|
-
|
|
95
|
-
process.exit(1);
|
|
91
|
+
throw new Error(`Failed to create workflow: ${getErrorMessage(error)}`);
|
|
96
92
|
}
|
|
97
93
|
}
|
|
98
94
|
/**
|
|
@@ -103,9 +99,7 @@ export async function createNodeCommand(name, file, options = {}) {
|
|
|
103
99
|
const { line, template = 'processor', preview = false, strategy, config: configJson } = options;
|
|
104
100
|
const templateDef = getNodeTemplate(template);
|
|
105
101
|
if (!templateDef) {
|
|
106
|
-
|
|
107
|
-
logger.info("Run 'fw templates' to see available templates");
|
|
108
|
-
process.exit(1);
|
|
102
|
+
throw new Error(`Unknown node template: ${template}. Run 'fw templates' to see available templates`);
|
|
109
103
|
}
|
|
110
104
|
// Resolve to absolute path
|
|
111
105
|
const filePath = path.resolve(file);
|
|
@@ -118,8 +112,7 @@ export async function createNodeCommand(name, file, options = {}) {
|
|
|
118
112
|
Object.assign(config, JSON.parse(configJson));
|
|
119
113
|
}
|
|
120
114
|
catch {
|
|
121
|
-
|
|
122
|
-
process.exit(1);
|
|
115
|
+
throw new Error('Invalid --config JSON');
|
|
123
116
|
}
|
|
124
117
|
}
|
|
125
118
|
// Generate the template code with provided name and optional config
|
|
@@ -140,8 +133,7 @@ export async function createNodeCommand(name, file, options = {}) {
|
|
|
140
133
|
logger.info(` Node function: ${nodeName}`);
|
|
141
134
|
}
|
|
142
135
|
catch (error) {
|
|
143
|
-
|
|
144
|
-
process.exit(1);
|
|
136
|
+
throw new Error(`Failed to create node: ${getErrorMessage(error)}`);
|
|
145
137
|
}
|
|
146
138
|
}
|
|
147
139
|
//# sourceMappingURL=create.js.map
|
|
@@ -9,6 +9,7 @@ import { validator } from '../../validator.js';
|
|
|
9
9
|
import { getNode, getIncomingConnections, getOutgoingConnections } from '../../api/query.js';
|
|
10
10
|
import { logger } from '../utils/logger.js';
|
|
11
11
|
import { getErrorMessage } from '../../utils/error-utils.js';
|
|
12
|
+
import { safeWriteFile } from '../utils/safe-write.js';
|
|
12
13
|
import { buildDiagramGraph } from '../../diagram/geometry.js';
|
|
13
14
|
import { renderASCII, renderASCIICompact } from '../../diagram/ascii-renderer.js';
|
|
14
15
|
export function buildNodeInfo(instance, nodeType) {
|
|
@@ -344,16 +345,13 @@ export async function describeCommand(input, options = {}) {
|
|
|
344
345
|
const { format = 'json', node: focusNodeId, workflowName, compile = false } = options;
|
|
345
346
|
const filePath = path.resolve(input);
|
|
346
347
|
if (!fs.existsSync(filePath)) {
|
|
347
|
-
|
|
348
|
-
process.exit(1);
|
|
348
|
+
throw new Error(`File not found: ${filePath}`);
|
|
349
349
|
}
|
|
350
350
|
try {
|
|
351
351
|
// Parse the workflow
|
|
352
352
|
const parseResult = await parseWorkflow(filePath, { workflowName });
|
|
353
353
|
if (parseResult.errors.length > 0) {
|
|
354
|
-
|
|
355
|
-
parseResult.errors.forEach((err) => logger.error(` ${err}`));
|
|
356
|
-
process.exit(1);
|
|
354
|
+
throw new Error(`Parse errors:\n${parseResult.errors.map((err) => ` ${err}`).join('\n')}`);
|
|
357
355
|
}
|
|
358
356
|
const ast = parseResult.ast;
|
|
359
357
|
// Only update runtime markers when explicitly requested via --compile
|
|
@@ -362,7 +360,7 @@ export async function describeCommand(input, options = {}) {
|
|
|
362
360
|
const sourceCode = fs.readFileSync(filePath, 'utf8');
|
|
363
361
|
const generated = generateInPlace(sourceCode, ast, { production: false });
|
|
364
362
|
if (generated.hasChanges) {
|
|
365
|
-
|
|
363
|
+
safeWriteFile(filePath, generated.code);
|
|
366
364
|
logger.info(`Updated runtime markers in ${path.basename(filePath)}`);
|
|
367
365
|
}
|
|
368
366
|
}
|
|
@@ -373,11 +371,9 @@ export async function describeCommand(input, options = {}) {
|
|
|
373
371
|
}
|
|
374
372
|
catch (error) {
|
|
375
373
|
if (error instanceof Error && error.message.startsWith('Node not found:')) {
|
|
376
|
-
|
|
377
|
-
process.exit(1);
|
|
374
|
+
throw error;
|
|
378
375
|
}
|
|
379
|
-
|
|
380
|
-
process.exit(1);
|
|
376
|
+
throw new Error(`Failed to describe workflow: ${getErrorMessage(error)}`);
|
|
381
377
|
}
|
|
382
378
|
}
|
|
383
379
|
//# sourceMappingURL=describe.js.map
|