@synergenius/flow-weaver 0.22.8 → 0.22.10

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.
@@ -2,13 +2,17 @@
2
2
  * @synergenius/flow-weaver/agent
3
3
  *
4
4
  * Provider-agnostic agent loop with MCP bridge for tool execution.
5
- * Two built-in providers: Anthropic API (raw fetch) and Claude CLI.
5
+ * Built-in providers: Anthropic API, Claude CLI, OpenAI-compatible (GPT-4o, Groq, Ollama, etc).
6
6
  */
7
7
  export type { StreamEvent, AgentMessage, AgentProvider, ToolDefinition, ToolExecutor, ToolEvent, McpBridge, AgentLoopOptions, AgentLoopResult, StreamOptions, SpawnFn, ClaudeCliProviderOptions, CliSessionOptions, Logger, } from './types.js';
8
8
  export { runAgentLoop } from './agent-loop.js';
9
9
  export { AnthropicProvider, createAnthropicProvider } from './providers/anthropic.js';
10
10
  export type { AnthropicProviderOptions } from './providers/anthropic.js';
11
11
  export { ClaudeCliProvider, createClaudeCliProvider } from './providers/claude-cli.js';
12
+ export { OpenAICompatProvider, createOpenAICompatProvider } from './providers/openai-compat.js';
13
+ export type { OpenAICompatProviderOptions } from './providers/openai-compat.js';
14
+ export { PlatformProvider, createPlatformProvider } from './providers/platform.js';
15
+ export type { PlatformProviderOptions } from './providers/platform.js';
12
16
  export { createMcpBridge } from './mcp-bridge.js';
13
17
  export { CliSession, getOrCreateCliSession, killCliSession, killAllCliSessions, } from './cli-session.js';
14
18
  export { buildSafeEnv, buildSafeSpawnOpts, MINIMAL_PATH, ENV_ALLOWLIST } from './env-allowlist.js';
@@ -2,13 +2,15 @@
2
2
  * @synergenius/flow-weaver/agent
3
3
  *
4
4
  * Provider-agnostic agent loop with MCP bridge for tool execution.
5
- * Two built-in providers: Anthropic API (raw fetch) and Claude CLI.
5
+ * Built-in providers: Anthropic API, Claude CLI, OpenAI-compatible (GPT-4o, Groq, Ollama, etc).
6
6
  */
7
7
  // Agent loop
8
8
  export { runAgentLoop } from './agent-loop.js';
9
9
  // Providers
10
10
  export { AnthropicProvider, createAnthropicProvider } from './providers/anthropic.js';
11
11
  export { ClaudeCliProvider, createClaudeCliProvider } from './providers/claude-cli.js';
12
+ export { OpenAICompatProvider, createOpenAICompatProvider } from './providers/openai-compat.js';
13
+ export { PlatformProvider, createPlatformProvider } from './providers/platform.js';
12
14
  // MCP bridge
13
15
  export { createMcpBridge } from './mcp-bridge.js';
14
16
  // CLI session (warm persistent sessions)
@@ -0,0 +1,25 @@
1
+ /**
2
+ * OpenAI-compatible API provider — works with any service that speaks
3
+ * the OpenAI chat completions API: OpenAI, Groq, Together, Ollama, etc.
4
+ *
5
+ * No SDK dependency. Uses only Node.js native fetch + SSE parsing.
6
+ * Converts OpenAI's delta format to the canonical StreamEvent union.
7
+ */
8
+ import type { AgentProvider, AgentMessage, ToolDefinition, StreamEvent, StreamOptions } from '../types.js';
9
+ export interface OpenAICompatProviderOptions {
10
+ apiKey: string;
11
+ model?: string;
12
+ maxTokens?: number;
13
+ /** Base URL for the API (default: https://api.openai.com). Include /v1 if needed. */
14
+ baseUrl?: string;
15
+ }
16
+ export declare class OpenAICompatProvider implements AgentProvider {
17
+ private apiKey;
18
+ private model;
19
+ private maxTokens;
20
+ private baseUrl;
21
+ constructor(options: OpenAICompatProviderOptions);
22
+ stream(messages: AgentMessage[], tools: ToolDefinition[], options?: StreamOptions): AsyncGenerator<StreamEvent>;
23
+ }
24
+ export declare function createOpenAICompatProvider(options: OpenAICompatProviderOptions): OpenAICompatProvider;
25
+ //# sourceMappingURL=openai-compat.d.ts.map
@@ -0,0 +1,204 @@
1
+ /**
2
+ * OpenAI-compatible API provider — works with any service that speaks
3
+ * the OpenAI chat completions API: OpenAI, Groq, Together, Ollama, etc.
4
+ *
5
+ * No SDK dependency. Uses only Node.js native fetch + SSE parsing.
6
+ * Converts OpenAI's delta format to the canonical StreamEvent union.
7
+ */
8
+ export class OpenAICompatProvider {
9
+ apiKey;
10
+ model;
11
+ maxTokens;
12
+ baseUrl;
13
+ constructor(options) {
14
+ if (!options.apiKey) {
15
+ throw new Error('OpenAICompatProvider requires an API key');
16
+ }
17
+ this.apiKey = options.apiKey;
18
+ this.model = options.model ?? 'gpt-4o';
19
+ this.maxTokens = options.maxTokens ?? 4096;
20
+ this.baseUrl = (options.baseUrl ?? 'https://api.openai.com').replace(/\/+$/, '');
21
+ }
22
+ async *stream(messages, tools, options) {
23
+ const model = options?.model ?? this.model;
24
+ const maxTokens = options?.maxTokens ?? this.maxTokens;
25
+ // Build OpenAI-format messages
26
+ const apiMessages = messages.map((m) => formatMessage(m));
27
+ // Build OpenAI-format tools
28
+ const apiTools = tools.length > 0
29
+ ? tools.map((t) => ({
30
+ type: 'function',
31
+ function: {
32
+ name: t.name,
33
+ description: t.description,
34
+ parameters: t.inputSchema,
35
+ },
36
+ }))
37
+ : undefined;
38
+ const body = {
39
+ model,
40
+ messages: [
41
+ ...(options?.systemPrompt ? [{ role: 'system', content: options.systemPrompt }] : []),
42
+ ...apiMessages,
43
+ ],
44
+ max_tokens: maxTokens,
45
+ stream: true,
46
+ };
47
+ if (apiTools && apiTools.length > 0) {
48
+ body.tools = apiTools;
49
+ }
50
+ // Determine the completions endpoint
51
+ const url = this.baseUrl.includes('/v1/')
52
+ ? `${this.baseUrl}chat/completions`
53
+ : `${this.baseUrl}/v1/chat/completions`;
54
+ const response = await fetch(url, {
55
+ method: 'POST',
56
+ headers: {
57
+ 'Content-Type': 'application/json',
58
+ Authorization: `Bearer ${this.apiKey}`,
59
+ },
60
+ body: JSON.stringify(body),
61
+ signal: options?.signal,
62
+ });
63
+ if (!response.ok) {
64
+ const errText = await response.text().catch(() => '');
65
+ yield { type: 'text_delta', text: `OpenAI API error ${response.status}: ${errText.slice(0, 300)}` };
66
+ yield { type: 'message_stop', finishReason: 'error' };
67
+ return;
68
+ }
69
+ if (!response.body) {
70
+ yield { type: 'message_stop', finishReason: 'error' };
71
+ return;
72
+ }
73
+ // Parse SSE stream
74
+ const reader = response.body.getReader();
75
+ const decoder = new TextDecoder();
76
+ let buffer = '';
77
+ let hasToolCalls = false;
78
+ // Track active tool calls (OpenAI sends incremental deltas by index)
79
+ const activeToolCalls = new Map();
80
+ try {
81
+ while (true) {
82
+ const { done, value } = await reader.read();
83
+ if (done)
84
+ break;
85
+ buffer += decoder.decode(value, { stream: true });
86
+ const lines = buffer.split('\n');
87
+ buffer = lines.pop() || '';
88
+ for (const line of lines) {
89
+ if (!line.startsWith('data: '))
90
+ continue;
91
+ const data = line.slice(6).trim();
92
+ if (data === '[DONE]') {
93
+ // Flush any pending tool calls
94
+ for (const [, tc] of activeToolCalls) {
95
+ let args = {};
96
+ try {
97
+ args = JSON.parse(tc.argsJson);
98
+ }
99
+ catch { /* malformed */ }
100
+ yield { type: 'tool_use_end', id: tc.id, arguments: args };
101
+ }
102
+ yield { type: 'message_stop', finishReason: hasToolCalls ? 'tool_calls' : 'stop' };
103
+ return;
104
+ }
105
+ let parsed;
106
+ try {
107
+ parsed = JSON.parse(data);
108
+ }
109
+ catch {
110
+ continue;
111
+ }
112
+ // Extract usage if present
113
+ if (parsed.usage) {
114
+ const usage = parsed.usage;
115
+ yield {
116
+ type: 'usage',
117
+ promptTokens: usage.prompt_tokens ?? 0,
118
+ completionTokens: usage.completion_tokens ?? 0,
119
+ };
120
+ }
121
+ const choices = parsed.choices;
122
+ if (!choices || choices.length === 0)
123
+ continue;
124
+ const choice = choices[0];
125
+ const delta = choice.delta;
126
+ if (!delta)
127
+ continue;
128
+ // Text content
129
+ if (delta.content && typeof delta.content === 'string') {
130
+ yield { type: 'text_delta', text: delta.content };
131
+ }
132
+ // Tool calls
133
+ const toolCalls = delta.tool_calls;
134
+ if (toolCalls) {
135
+ for (const tc of toolCalls) {
136
+ const index = tc.index ?? 0;
137
+ const fn = tc.function;
138
+ if (tc.id) {
139
+ // New tool call
140
+ hasToolCalls = true;
141
+ const name = fn?.name ? String(fn.name) : 'unknown';
142
+ activeToolCalls.set(index, { id: String(tc.id), name, argsJson: '' });
143
+ yield { type: 'tool_use_start', id: String(tc.id), name };
144
+ }
145
+ // Accumulate function arguments
146
+ if (fn?.arguments && typeof fn.arguments === 'string') {
147
+ const existing = activeToolCalls.get(index);
148
+ if (existing) {
149
+ existing.argsJson += fn.arguments;
150
+ yield { type: 'tool_use_delta', id: existing.id, partialJson: fn.arguments };
151
+ }
152
+ }
153
+ }
154
+ }
155
+ // Finish reason
156
+ if (choice.finish_reason === 'tool_calls') {
157
+ hasToolCalls = true;
158
+ }
159
+ }
160
+ }
161
+ }
162
+ finally {
163
+ reader.releaseLock();
164
+ }
165
+ // If we got here without [DONE], flush
166
+ for (const [, tc] of activeToolCalls) {
167
+ let args = {};
168
+ try {
169
+ args = JSON.parse(tc.argsJson);
170
+ }
171
+ catch { /* malformed */ }
172
+ yield { type: 'tool_use_end', id: tc.id, arguments: args };
173
+ }
174
+ yield { type: 'message_stop', finishReason: hasToolCalls ? 'tool_calls' : 'stop' };
175
+ }
176
+ }
177
+ function formatMessage(m) {
178
+ if (m.role === 'tool') {
179
+ return {
180
+ role: 'tool',
181
+ tool_call_id: m.toolCallId,
182
+ content: typeof m.content === 'string' ? m.content : JSON.stringify(m.content),
183
+ };
184
+ }
185
+ if (m.role === 'assistant' && m.toolCalls?.length) {
186
+ return {
187
+ role: 'assistant',
188
+ content: typeof m.content === 'string' && m.content ? m.content : null,
189
+ tool_calls: m.toolCalls.map((tc) => ({
190
+ id: tc.id,
191
+ type: 'function',
192
+ function: { name: tc.name, arguments: JSON.stringify(tc.arguments) },
193
+ })),
194
+ };
195
+ }
196
+ return {
197
+ role: m.role,
198
+ content: typeof m.content === 'string' ? m.content : JSON.stringify(m.content),
199
+ };
200
+ }
201
+ export function createOpenAICompatProvider(options) {
202
+ return new OpenAICompatProvider(options);
203
+ }
204
+ //# sourceMappingURL=openai-compat.js.map
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Platform AI provider — routes AI calls through the Flow Weaver platform.
3
+ * Uses the platform's AI credits, no local API key needed.
4
+ * Connects to POST /ai-chat/stream and parses SSE events.
5
+ */
6
+ import type { AgentProvider, AgentMessage, ToolDefinition, StreamEvent, StreamOptions } from '../types.js';
7
+ export interface PlatformProviderOptions {
8
+ /** JWT token or API key for platform auth */
9
+ token: string;
10
+ /** Platform base URL */
11
+ platformUrl: string;
12
+ /** Model override (optional — platform selects default) */
13
+ model?: string;
14
+ }
15
+ export declare class PlatformProvider implements AgentProvider {
16
+ private token;
17
+ private baseUrl;
18
+ private model;
19
+ constructor(options: PlatformProviderOptions);
20
+ stream(messages: AgentMessage[], _tools: ToolDefinition[], options?: StreamOptions): AsyncGenerator<StreamEvent>;
21
+ }
22
+ export declare function createPlatformProvider(options: PlatformProviderOptions): PlatformProvider;
23
+ //# sourceMappingURL=platform.d.ts.map
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Platform AI provider — routes AI calls through the Flow Weaver platform.
3
+ * Uses the platform's AI credits, no local API key needed.
4
+ * Connects to POST /ai-chat/stream and parses SSE events.
5
+ */
6
+ export class PlatformProvider {
7
+ token;
8
+ baseUrl;
9
+ model;
10
+ constructor(options) {
11
+ if (!options.token)
12
+ throw new Error('PlatformProvider requires a token');
13
+ if (!options.platformUrl)
14
+ throw new Error('PlatformProvider requires a platformUrl');
15
+ this.token = options.token;
16
+ this.baseUrl = options.platformUrl.replace(/\/+$/, '');
17
+ this.model = options.model;
18
+ }
19
+ async *stream(messages, _tools, options) {
20
+ // Format the last user message as the prompt
21
+ // The platform's agent loop handles tools internally
22
+ const lastUserMsg = [...messages].reverse().find(m => m.role === 'user');
23
+ const message = lastUserMsg
24
+ ? (typeof lastUserMsg.content === 'string' ? lastUserMsg.content : JSON.stringify(lastUserMsg.content))
25
+ : '';
26
+ if (!message) {
27
+ yield { type: 'message_stop', finishReason: 'stop' };
28
+ return;
29
+ }
30
+ const isApiKey = this.token.startsWith('fw_');
31
+ const headers = {
32
+ 'Content-Type': 'application/json',
33
+ ...(isApiKey
34
+ ? { 'X-API-Key': this.token }
35
+ : { Authorization: `Bearer ${this.token}` }),
36
+ };
37
+ const body = { message };
38
+ if (options?.systemPrompt) {
39
+ // Platform doesn't accept system prompt directly via API — embed in message
40
+ body.message = `[System context: ${options.systemPrompt.slice(0, 2000)}]\n\n${message}`;
41
+ }
42
+ const response = await fetch(`${this.baseUrl}/ai-chat/stream`, {
43
+ method: 'POST',
44
+ headers,
45
+ body: JSON.stringify(body),
46
+ signal: options?.signal,
47
+ });
48
+ if (!response.ok) {
49
+ const errText = await response.text().catch(() => '');
50
+ yield { type: 'text_delta', text: `Platform error ${response.status}: ${errText.slice(0, 300)}` };
51
+ yield { type: 'message_stop', finishReason: 'error' };
52
+ return;
53
+ }
54
+ if (!response.body) {
55
+ yield { type: 'message_stop', finishReason: 'error' };
56
+ return;
57
+ }
58
+ // Parse SSE stream from platform
59
+ const reader = response.body.getReader();
60
+ const decoder = new TextDecoder();
61
+ let buffer = '';
62
+ try {
63
+ while (true) {
64
+ const { done, value } = await reader.read();
65
+ if (done)
66
+ break;
67
+ buffer += decoder.decode(value, { stream: true });
68
+ const lines = buffer.split('\n');
69
+ buffer = lines.pop() || '';
70
+ for (const line of lines) {
71
+ if (!line.startsWith('data: '))
72
+ continue;
73
+ let event;
74
+ try {
75
+ event = JSON.parse(line.slice(6));
76
+ }
77
+ catch {
78
+ continue;
79
+ }
80
+ // Map platform SSE events to canonical StreamEvent
81
+ switch (event.type) {
82
+ case 'text_delta':
83
+ yield { type: 'text_delta', text: String(event.content ?? '') };
84
+ break;
85
+ case 'thinking_delta':
86
+ yield { type: 'thinking_delta', text: String(event.content ?? '') };
87
+ break;
88
+ case 'tool_call_start':
89
+ yield {
90
+ type: 'tool_use_start',
91
+ id: String(event.toolCallId ?? ''),
92
+ name: String(event.name ?? ''),
93
+ };
94
+ break;
95
+ case 'tool_call_result':
96
+ yield {
97
+ type: 'tool_result',
98
+ id: String(event.toolCallId ?? ''),
99
+ result: String(event.result ?? ''),
100
+ isError: !!event.isError,
101
+ };
102
+ break;
103
+ case 'usage':
104
+ yield {
105
+ type: 'usage',
106
+ promptTokens: event.promptTokens ?? 0,
107
+ completionTokens: event.completionTokens ?? 0,
108
+ };
109
+ break;
110
+ case 'done':
111
+ yield { type: 'message_stop', finishReason: 'stop' };
112
+ return;
113
+ case 'error':
114
+ yield { type: 'text_delta', text: `Error: ${event.message ?? 'Unknown error'}` };
115
+ yield { type: 'message_stop', finishReason: 'error' };
116
+ return;
117
+ }
118
+ }
119
+ }
120
+ }
121
+ finally {
122
+ reader.releaseLock();
123
+ }
124
+ yield { type: 'message_stop', finishReason: 'stop' };
125
+ }
126
+ }
127
+ export function createPlatformProvider(options) {
128
+ return new PlatformProvider(options);
129
+ }
130
+ //# sourceMappingURL=platform.js.map
@@ -0,0 +1,8 @@
1
+ export declare function loginCommand(options: {
2
+ email?: string;
3
+ apiKey?: string;
4
+ platformUrl?: string;
5
+ }): Promise<void>;
6
+ export declare function logoutCommand(): Promise<void>;
7
+ export declare function authStatusCommand(): Promise<void>;
8
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1,137 @@
1
+ import * as readline from 'node:readline';
2
+ import { loadCredentials, saveCredentials, clearCredentials, getPlatformUrl } from '../config/credentials.js';
3
+ import { PlatformClient } from '../config/platform-client.js';
4
+ export async function loginCommand(options) {
5
+ const platformUrl = options.platformUrl ?? getPlatformUrl();
6
+ console.log('');
7
+ console.log(' \x1b[1mFlow Weaver Login\x1b[0m');
8
+ console.log(` \x1b[2mPlatform: ${platformUrl}\x1b[0m`);
9
+ console.log('');
10
+ // Validate platform reachable
11
+ try {
12
+ const resp = await fetch(`${platformUrl}/ready`);
13
+ if (!resp.ok)
14
+ throw new Error();
15
+ }
16
+ catch {
17
+ console.error(' \x1b[31m✗\x1b[0m Cannot connect to platform');
18
+ console.error(` Check: ${platformUrl}`);
19
+ process.exit(1);
20
+ }
21
+ let token;
22
+ let email;
23
+ let plan;
24
+ let userId;
25
+ if (options.apiKey) {
26
+ // API key auth
27
+ token = options.apiKey;
28
+ const client = new PlatformClient({ token, email: '', plan: 'free', platformUrl, expiresAt: Infinity });
29
+ try {
30
+ const user = await client.getUser();
31
+ email = user.email;
32
+ plan = user.plan;
33
+ userId = user.id;
34
+ }
35
+ catch {
36
+ console.error(' \x1b[31m✗\x1b[0m Invalid API key');
37
+ process.exit(1);
38
+ return;
39
+ }
40
+ }
41
+ else {
42
+ // Email/password auth
43
+ email = options.email ?? await prompt(' Email: ');
44
+ const password = await prompt(' Password: ', true);
45
+ try {
46
+ const resp = await fetch(`${platformUrl}/auth/login`, {
47
+ method: 'POST',
48
+ headers: { 'Content-Type': 'application/json' },
49
+ body: JSON.stringify({ email, password }),
50
+ });
51
+ if (!resp.ok) {
52
+ const err = await resp.json().catch(() => ({ error: 'Login failed' }));
53
+ console.error(`\n \x1b[31m✗\x1b[0m ${err.error}`);
54
+ process.exit(1);
55
+ return;
56
+ }
57
+ const data = await resp.json();
58
+ token = data.token;
59
+ email = data.user.email;
60
+ plan = data.user.plan;
61
+ userId = data.user.id;
62
+ }
63
+ catch (err) {
64
+ console.error(`\n \x1b[31m✗\x1b[0m ${err instanceof Error ? err.message : 'Login failed'}`);
65
+ process.exit(1);
66
+ return;
67
+ }
68
+ }
69
+ const expiresAt = options.apiKey
70
+ ? Date.now() + 365 * 24 * 60 * 60 * 1000 // 1 year for API keys
71
+ : Date.now() + 7 * 24 * 60 * 60 * 1000; // 7 days for JWT
72
+ saveCredentials({ token, email, plan: plan, platformUrl, expiresAt, userId });
73
+ console.log('');
74
+ console.log(` \x1b[32m✓\x1b[0m Logged in as \x1b[1m${email}\x1b[0m (${plan} plan)`);
75
+ console.log('');
76
+ console.log(' What\'s unlocked:');
77
+ console.log(' \x1b[36mfw deploy src/workflow.ts\x1b[0m \x1b[2m# deploy to cloud\x1b[0m');
78
+ console.log(' \x1b[36mfw status\x1b[0m \x1b[2m# see deployments + usage\x1b[0m');
79
+ console.log(' \x1b[36mweaver assistant\x1b[0m \x1b[2m# AI with platform credits\x1b[0m');
80
+ console.log('');
81
+ }
82
+ export async function logoutCommand() {
83
+ clearCredentials();
84
+ console.log(' \x1b[32m✓\x1b[0m Logged out');
85
+ }
86
+ export async function authStatusCommand() {
87
+ const creds = loadCredentials();
88
+ if (!creds) {
89
+ console.log('');
90
+ console.log(' Not logged in.');
91
+ console.log(' Run: \x1b[36mfw login\x1b[0m');
92
+ console.log('');
93
+ return;
94
+ }
95
+ const expiresIn = Math.floor((creds.expiresAt - Date.now()) / 1000 / 60 / 60);
96
+ console.log('');
97
+ console.log(` \x1b[32m✓\x1b[0m Logged in as \x1b[1m${creds.email}\x1b[0m`);
98
+ console.log(` Plan: ${creds.plan}`);
99
+ console.log(` Platform: ${creds.platformUrl}`);
100
+ console.log(` Token expires in: ${expiresIn}h`);
101
+ console.log('');
102
+ }
103
+ function prompt(message, hidden = false) {
104
+ return new Promise((resolve) => {
105
+ const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
106
+ if (hidden && process.stdin.isTTY) {
107
+ // Hide password input
108
+ process.stderr.write(message);
109
+ process.stdin.setRawMode(true);
110
+ let input = '';
111
+ const handler = (key) => {
112
+ const ch = key.toString();
113
+ if (ch === '\r' || ch === '\n') {
114
+ process.stdin.setRawMode(false);
115
+ process.stdin.removeListener('data', handler);
116
+ process.stderr.write('\n');
117
+ rl.close();
118
+ resolve(input);
119
+ }
120
+ else if (ch === '\x03') { // Ctrl+C
121
+ process.exit(1);
122
+ }
123
+ else if (ch === '\x7f') { // Backspace
124
+ input = input.slice(0, -1);
125
+ }
126
+ else {
127
+ input += ch;
128
+ }
129
+ };
130
+ process.stdin.on('data', handler);
131
+ }
132
+ else {
133
+ rl.question(message, (answer) => { rl.close(); resolve(answer); });
134
+ }
135
+ });
136
+ }
137
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1,6 @@
1
+ export declare function deployCommand(filePath: string, options?: {
2
+ name?: string;
3
+ }): Promise<void>;
4
+ export declare function undeployCommand(slug: string): Promise<void>;
5
+ export declare function cloudStatusCommand(): Promise<void>;
6
+ //# sourceMappingURL=deploy.d.ts.map
@@ -0,0 +1,97 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import { loadCredentials } from '../config/credentials.js';
4
+ import { PlatformClient } from '../config/platform-client.js';
5
+ export async function deployCommand(filePath, options = {}) {
6
+ const creds = loadCredentials();
7
+ if (!creds) {
8
+ console.error(' \x1b[31m✗\x1b[0m Not logged in. Run: fw login');
9
+ process.exit(1);
10
+ return;
11
+ }
12
+ const absPath = path.resolve(filePath);
13
+ if (!fs.existsSync(absPath)) {
14
+ console.error(` \x1b[31m✗\x1b[0m File not found: ${filePath}`);
15
+ process.exit(1);
16
+ return;
17
+ }
18
+ const source = fs.readFileSync(absPath, 'utf-8');
19
+ const name = options.name ?? path.basename(filePath, path.extname(filePath));
20
+ const client = new PlatformClient(creds);
21
+ console.log('');
22
+ console.log(` \x1b[2mPushing ${name}...\x1b[0m`);
23
+ try {
24
+ const workflow = await client.pushWorkflow(name, source);
25
+ console.log(` \x1b[32m✓\x1b[0m Pushed (v${workflow.version})`);
26
+ console.log(` \x1b[2mDeploying...\x1b[0m`);
27
+ const deployment = await client.deploy(workflow.slug);
28
+ console.log(` \x1b[32m✓\x1b[0m Deployed: ${deployment.slug}`);
29
+ console.log('');
30
+ console.log(` Endpoint: ${creds.platformUrl}/run/${deployment.slug}`);
31
+ console.log('');
32
+ console.log(' Test it:');
33
+ console.log(` curl -X POST ${creds.platformUrl}/run/${deployment.slug} \\`);
34
+ console.log(` -H "X-API-Key: <your-api-key>" \\`);
35
+ console.log(` -H "Content-Type: application/json" \\`);
36
+ console.log(` -d '{"input": "hello"}'`);
37
+ console.log('');
38
+ }
39
+ catch (err) {
40
+ console.error(` \x1b[31m✗\x1b[0m ${err instanceof Error ? err.message : 'Deploy failed'}`);
41
+ process.exit(1);
42
+ }
43
+ }
44
+ export async function undeployCommand(slug) {
45
+ const creds = loadCredentials();
46
+ if (!creds) {
47
+ console.error(' \x1b[31m✗\x1b[0m Not logged in.');
48
+ process.exit(1);
49
+ return;
50
+ }
51
+ const client = new PlatformClient(creds);
52
+ try {
53
+ await client.undeploy(slug);
54
+ console.log(` \x1b[32m✓\x1b[0m Undeployed: ${slug}`);
55
+ }
56
+ catch (err) {
57
+ console.error(` \x1b[31m✗\x1b[0m ${err instanceof Error ? err.message : 'Undeploy failed'}`);
58
+ }
59
+ }
60
+ export async function cloudStatusCommand() {
61
+ const creds = loadCredentials();
62
+ if (!creds) {
63
+ console.log('');
64
+ console.log(' Not logged in. Run: \x1b[36mfw login\x1b[0m');
65
+ console.log('');
66
+ return;
67
+ }
68
+ const client = new PlatformClient(creds);
69
+ console.log('');
70
+ console.log(` \x1b[1m${creds.email}\x1b[0m \x1b[2m(${creds.plan} plan)\x1b[0m`);
71
+ console.log('');
72
+ try {
73
+ const deployments = await client.listDeployments();
74
+ if (deployments.length === 0) {
75
+ console.log(' No deployments.');
76
+ }
77
+ else {
78
+ console.log(' Deployments:');
79
+ for (const d of deployments) {
80
+ const icon = d.status === 'active' ? '\x1b[32m●\x1b[0m' : '\x1b[33m○\x1b[0m';
81
+ console.log(` ${icon} ${d.slug.padEnd(25)} ${d.status}`);
82
+ }
83
+ }
84
+ }
85
+ catch {
86
+ console.log(' \x1b[33m⚠\x1b[0m Could not fetch deployments');
87
+ }
88
+ try {
89
+ const usage = await client.getUsage();
90
+ console.log('');
91
+ console.log(` AI Credits: ${usage.aiCalls} calls this month`);
92
+ console.log(` Executions: ${usage.executions} this month`);
93
+ }
94
+ catch { /* usage not available */ }
95
+ console.log('');
96
+ }
97
+ //# sourceMappingURL=deploy.js.map