@marktoflow/cli 2.0.0-alpha.7 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -456
- package/dist/commands/dry-run.d.ts +1 -0
- package/dist/commands/dry-run.d.ts.map +1 -1
- package/dist/commands/dry-run.js +161 -25
- package/dist/commands/dry-run.js.map +1 -1
- package/dist/commands/history.d.ts +18 -0
- package/dist/commands/history.d.ts.map +1 -0
- package/dist/commands/history.js +258 -0
- package/dist/commands/history.js.map +1 -0
- package/dist/commands/test-connection.d.ts +26 -0
- package/dist/commands/test-connection.d.ts.map +1 -0
- package/dist/commands/test-connection.js +599 -0
- package/dist/commands/test-connection.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +575 -32
- package/dist/index.js.map +1 -1
- package/dist/oauth.d.ts +8 -2
- package/dist/oauth.d.ts.map +1 -1
- package/dist/oauth.js +191 -16
- package/dist/oauth.js.map +1 -1
- package/dist/serve.d.ts +3 -0
- package/dist/serve.d.ts.map +1 -0
- package/dist/serve.js +453 -0
- package/dist/serve.js.map +1 -0
- package/dist/utils/agent-override.d.ts +43 -0
- package/dist/utils/agent-override.d.ts.map +1 -0
- package/dist/utils/agent-override.js +90 -0
- package/dist/utils/agent-override.js.map +1 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +6 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/input-parser.d.ts +37 -0
- package/dist/utils/input-parser.d.ts.map +1 -0
- package/dist/utils/input-parser.js +80 -0
- package/dist/utils/input-parser.js.map +1 -0
- package/package.json +12 -8
package/dist/index.js
CHANGED
|
@@ -2,23 +2,27 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* marktoflow CLI
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* Agent automation framework with native MCP support.
|
|
6
6
|
*/
|
|
7
7
|
import { Command } from 'commander';
|
|
8
8
|
import chalk from 'chalk';
|
|
9
9
|
import ora from 'ora';
|
|
10
10
|
import { existsSync, mkdirSync, writeFileSync, readdirSync, statSync, readFileSync } from 'node:fs';
|
|
11
11
|
import { join } from 'node:path';
|
|
12
|
-
import { parseFile, WorkflowEngine, SDKRegistry, createSDKStepExecutor, StepStatus, WorkflowStatus, loadEnv, ToolRegistry, WorkflowBundle, Scheduler, TemplateRegistry, loadConfig, } from '@marktoflow/core';
|
|
12
|
+
import { parseFile, WorkflowEngine, SDKRegistry, createSDKStepExecutor, StepStatus, WorkflowStatus, loadEnv, ToolRegistry, WorkflowBundle, Scheduler, TemplateRegistry, loadConfig, createCredentialManager, getAvailableBackends, EncryptionBackend, StateStore, } from '@marktoflow/core';
|
|
13
13
|
import { registerIntegrations } from '@marktoflow/integrations';
|
|
14
14
|
import { workerCommand } from './worker.js';
|
|
15
15
|
import { triggerCommand } from './trigger.js';
|
|
16
|
+
import { serveCommand } from './serve.js';
|
|
16
17
|
import { runWorkflowWizard, listTemplates } from './commands/new.js';
|
|
17
18
|
import { runUpdateWizard, listAgents } from './commands/update.js';
|
|
18
19
|
import { parse as parseYaml } from 'yaml';
|
|
19
20
|
import { executeDryRun, displayDryRunSummary } from './commands/dry-run.js';
|
|
20
21
|
import { WorkflowDebugger, parseBreakpoints } from './commands/debug.js';
|
|
21
|
-
|
|
22
|
+
import { executeTestConnection } from './commands/test-connection.js';
|
|
23
|
+
import { executeHistory, executeHistoryDetail, executeReplay } from './commands/history.js';
|
|
24
|
+
import { parseInputPairs, debugLogInputs, validateAndApplyDefaults, printMissingInputsError, overrideAgentInWorkflow, debugLogAgentOverride, overrideModelInWorkflow, } from './utils/index.js';
|
|
25
|
+
const VERSION = '2.0.0-alpha.15';
|
|
22
26
|
// Load environment variables from .env files on CLI startup
|
|
23
27
|
loadEnv();
|
|
24
28
|
function getConfig() {
|
|
@@ -36,16 +40,62 @@ function isBundle(path) {
|
|
|
36
40
|
return false;
|
|
37
41
|
}
|
|
38
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Map agent provider aliases to SDK names
|
|
45
|
+
*/
|
|
46
|
+
function getAgentSDKName(provider) {
|
|
47
|
+
const providerMap = {
|
|
48
|
+
'claude': 'claude-code',
|
|
49
|
+
'claude-code': 'claude-code',
|
|
50
|
+
'claude-agent': 'claude-agent',
|
|
51
|
+
'copilot': 'github-copilot',
|
|
52
|
+
'github-copilot': 'github-copilot',
|
|
53
|
+
'opencode': 'opencode',
|
|
54
|
+
'ollama': 'ollama',
|
|
55
|
+
'codex': 'codex',
|
|
56
|
+
};
|
|
57
|
+
const normalized = provider.toLowerCase();
|
|
58
|
+
const sdkName = providerMap[normalized];
|
|
59
|
+
if (!sdkName) {
|
|
60
|
+
throw new Error(`Unknown agent provider: ${provider}. Available: ${Object.keys(providerMap).join(', ')}`);
|
|
61
|
+
}
|
|
62
|
+
return sdkName;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Get default auth configuration for an agent provider
|
|
66
|
+
*/
|
|
67
|
+
function getAgentAuthConfig(sdkName) {
|
|
68
|
+
const authConfigs = {
|
|
69
|
+
'claude-code': {
|
|
70
|
+
api_key: '${ANTHROPIC_API_KEY}',
|
|
71
|
+
},
|
|
72
|
+
'claude-agent': {
|
|
73
|
+
api_key: '${ANTHROPIC_API_KEY}',
|
|
74
|
+
},
|
|
75
|
+
'github-copilot': {
|
|
76
|
+
token: '${GITHUB_TOKEN}',
|
|
77
|
+
},
|
|
78
|
+
'opencode': {},
|
|
79
|
+
'ollama': {
|
|
80
|
+
base_url: '${OLLAMA_BASE_URL:-http://localhost:11434}',
|
|
81
|
+
},
|
|
82
|
+
'codex': {
|
|
83
|
+
api_key: '${OPENAI_API_KEY}',
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
return authConfigs[sdkName] || {};
|
|
87
|
+
}
|
|
39
88
|
// ============================================================================
|
|
40
89
|
// CLI Setup
|
|
41
90
|
// ============================================================================
|
|
42
91
|
const program = new Command();
|
|
43
92
|
program
|
|
44
93
|
.name('marktoflow')
|
|
45
|
-
.description('
|
|
94
|
+
.description('Agent automation framework with native MCP support')
|
|
46
95
|
.version(VERSION);
|
|
47
96
|
program.addCommand(workerCommand);
|
|
48
97
|
program.addCommand(triggerCommand);
|
|
98
|
+
program.addCommand(serveCommand);
|
|
49
99
|
// ============================================================================
|
|
50
100
|
// Commands
|
|
51
101
|
// ============================================================================
|
|
@@ -84,7 +134,7 @@ workflow:
|
|
|
84
134
|
|
|
85
135
|
steps:
|
|
86
136
|
- id: greet
|
|
87
|
-
action:
|
|
137
|
+
action: core.log
|
|
88
138
|
inputs:
|
|
89
139
|
message: "Hello from marktoflow!"
|
|
90
140
|
---
|
|
@@ -154,9 +204,13 @@ program
|
|
|
154
204
|
.description('Run a workflow')
|
|
155
205
|
.option('-i, --input <key=value...>', 'Input parameters')
|
|
156
206
|
.option('-v, --verbose', 'Verbose output')
|
|
207
|
+
.option('-d, --debug', 'Debug mode with detailed output (includes stack traces)')
|
|
208
|
+
.option('-a, --agent <provider>', 'AI agent provider (claude-code, claude-agent, github-copilot, opencode, ollama, codex)')
|
|
209
|
+
.option('-m, --model <name>', 'Model name to use (e.g., claude-sonnet-4, gpt-4, etc.)')
|
|
157
210
|
.option('--dry-run', 'Parse workflow without executing')
|
|
158
211
|
.action(async (workflowPath, options) => {
|
|
159
212
|
const spinner = ora('Loading workflow...').start();
|
|
213
|
+
let stateStore;
|
|
160
214
|
try {
|
|
161
215
|
const config = getConfig();
|
|
162
216
|
const workflowsDir = config.workflows?.path ?? '.marktoflow/workflows';
|
|
@@ -178,12 +232,56 @@ program
|
|
|
178
232
|
else {
|
|
179
233
|
spinner.succeed(`Loaded: ${workflow.metadata.name}`);
|
|
180
234
|
}
|
|
235
|
+
// Debug: Show workflow details
|
|
236
|
+
if (options.debug) {
|
|
237
|
+
console.log(chalk.gray('\n🐛 Debug: Workflow Details'));
|
|
238
|
+
console.log(chalk.gray(` ID: ${workflow.metadata.id}`));
|
|
239
|
+
console.log(chalk.gray(` Version: ${workflow.metadata.version}`));
|
|
240
|
+
console.log(chalk.gray(` Steps: ${workflow.steps.length}`));
|
|
241
|
+
console.log(chalk.gray(` Tools: ${Object.keys(workflow.tools).join(', ') || 'none'}`));
|
|
242
|
+
console.log(chalk.gray(` Inputs Required: ${Object.keys(workflow.inputs || {}).join(', ') || 'none'}`));
|
|
243
|
+
}
|
|
181
244
|
// Parse inputs
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
245
|
+
const parsedInputs = parseInputPairs(options.input);
|
|
246
|
+
// Debug: Show parsed inputs
|
|
247
|
+
if (options.debug) {
|
|
248
|
+
debugLogInputs(parsedInputs);
|
|
249
|
+
}
|
|
250
|
+
// Validate required inputs and apply defaults
|
|
251
|
+
const validation = validateAndApplyDefaults(workflow, parsedInputs, { debug: options.debug });
|
|
252
|
+
if (!validation.valid) {
|
|
253
|
+
spinner.fail('Missing required inputs');
|
|
254
|
+
printMissingInputsError(workflow, validation.missingInputs, 'run', workflowPath);
|
|
255
|
+
process.exit(1);
|
|
256
|
+
}
|
|
257
|
+
const inputs = validation.inputs;
|
|
258
|
+
// Override AI agent if specified
|
|
259
|
+
if (options.agent) {
|
|
260
|
+
const sdkName = getAgentSDKName(options.agent);
|
|
261
|
+
const authConfig = getAgentAuthConfig(sdkName);
|
|
262
|
+
const result = overrideAgentInWorkflow(workflow, sdkName, authConfig, {
|
|
263
|
+
verbose: options.verbose,
|
|
264
|
+
debug: options.debug,
|
|
265
|
+
});
|
|
266
|
+
if (options.debug) {
|
|
267
|
+
debugLogAgentOverride(options.agent, sdkName, result.replacedCount, authConfig);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
// Override model if specified
|
|
271
|
+
if (options.model) {
|
|
272
|
+
const result = overrideModelInWorkflow(workflow, options.model);
|
|
273
|
+
if (options.verbose || options.debug) {
|
|
274
|
+
if (result.overrideCount > 0) {
|
|
275
|
+
console.log(chalk.cyan(` Set model '${options.model}' for ${result.overrideCount} AI tool(s)`));
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (options.debug) {
|
|
279
|
+
console.log(chalk.gray('\n🐛 Debug: Model Override'));
|
|
280
|
+
console.log(chalk.gray(` Model: ${options.model}`));
|
|
281
|
+
console.log(chalk.gray(` Applied to ${result.overrideCount} AI tool(s)`));
|
|
282
|
+
}
|
|
283
|
+
if (result.overrideCount === 0 && (options.verbose || options.debug)) {
|
|
284
|
+
console.log(chalk.yellow(` Warning: --model specified but no AI tools found in workflow`));
|
|
187
285
|
}
|
|
188
286
|
}
|
|
189
287
|
// Handle dry-run mode
|
|
@@ -202,28 +300,172 @@ program
|
|
|
202
300
|
}
|
|
203
301
|
// Execute workflow
|
|
204
302
|
spinner.start('Executing workflow...');
|
|
303
|
+
// Debug: Show execution start
|
|
304
|
+
if (options.debug) {
|
|
305
|
+
console.log(chalk.gray('\n🐛 Debug: Starting Workflow Execution'));
|
|
306
|
+
console.log(chalk.gray(` Workflow: ${workflow.metadata.name}`));
|
|
307
|
+
console.log(chalk.gray(` Steps to execute: ${workflow.steps.length}`));
|
|
308
|
+
}
|
|
309
|
+
// Track which steps we've logged to avoid duplicate output on retries
|
|
310
|
+
const loggedSteps = new Set();
|
|
311
|
+
// Create StateStore for execution history
|
|
312
|
+
const stateDir = join(process.cwd(), '.marktoflow');
|
|
313
|
+
mkdirSync(stateDir, { recursive: true });
|
|
314
|
+
stateStore = new StateStore(join(stateDir, 'state.db'));
|
|
205
315
|
const engine = new WorkflowEngine({}, {
|
|
206
316
|
onStepStart: (step) => {
|
|
207
|
-
if (options.verbose) {
|
|
317
|
+
if (options.verbose || options.debug) {
|
|
208
318
|
spinner.text = `Executing: ${step.id}`;
|
|
209
319
|
}
|
|
320
|
+
// Only log step start once (not on retries)
|
|
321
|
+
if (options.debug && !loggedSteps.has(step.id)) {
|
|
322
|
+
console.log(chalk.gray(`\n🐛 Debug: Step Start - ${step.id}`));
|
|
323
|
+
console.log(chalk.gray(` Action: ${step.action || 'N/A'}`));
|
|
324
|
+
if (step.inputs) {
|
|
325
|
+
console.log(chalk.gray(` Inputs: ${JSON.stringify(step.inputs, null, 2).split('\n').join('\n ')}`));
|
|
326
|
+
}
|
|
327
|
+
loggedSteps.add(step.id);
|
|
328
|
+
}
|
|
210
329
|
},
|
|
211
330
|
onStepComplete: (step, result) => {
|
|
212
|
-
if (options.verbose) {
|
|
331
|
+
if (options.verbose || options.debug) {
|
|
213
332
|
const icon = result.status === StepStatus.COMPLETED ? '✓' : '✗';
|
|
214
333
|
console.log(` ${icon} ${step.id}: ${result.status}`);
|
|
215
334
|
}
|
|
335
|
+
if (options.debug) {
|
|
336
|
+
console.log(chalk.gray(`\n🐛 Debug: Step Complete - ${step.id}`));
|
|
337
|
+
console.log(chalk.gray(` Status: ${result.status}`));
|
|
338
|
+
console.log(chalk.gray(` Duration: ${result.duration}ms`));
|
|
339
|
+
// Show output variable name if set
|
|
340
|
+
if (step.outputVariable) {
|
|
341
|
+
console.log(chalk.gray(` Output Variable: ${step.outputVariable}`));
|
|
342
|
+
}
|
|
343
|
+
// Show full output in debug mode (not truncated)
|
|
344
|
+
if (result.output !== undefined) {
|
|
345
|
+
let outputStr;
|
|
346
|
+
if (typeof result.output === 'string') {
|
|
347
|
+
outputStr = result.output;
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
outputStr = JSON.stringify(result.output, null, 2);
|
|
351
|
+
}
|
|
352
|
+
// Split into lines and indent each line
|
|
353
|
+
const lines = outputStr.split('\n');
|
|
354
|
+
if (lines.length > 50) {
|
|
355
|
+
// If output is very large (>50 lines), show first 40 and last 5 lines
|
|
356
|
+
console.log(chalk.gray(` Output (${lines.length} lines):`));
|
|
357
|
+
lines.slice(0, 40).forEach(line => console.log(chalk.gray(` ${line}`)));
|
|
358
|
+
console.log(chalk.gray(` ... (${lines.length - 45} lines omitted) ...`));
|
|
359
|
+
lines.slice(-5).forEach(line => console.log(chalk.gray(` ${line}`)));
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
console.log(chalk.gray(` Output:`));
|
|
363
|
+
lines.forEach(line => console.log(chalk.gray(` ${line}`)));
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
if (result.error) {
|
|
367
|
+
console.log(chalk.red(` Error: ${result.error}`));
|
|
368
|
+
}
|
|
369
|
+
}
|
|
216
370
|
},
|
|
217
|
-
});
|
|
371
|
+
}, stateStore);
|
|
372
|
+
// Set workflow path for execution history
|
|
373
|
+
engine.workflowPath = resolvedPath;
|
|
218
374
|
const registry = new SDKRegistry();
|
|
219
375
|
registerIntegrations(registry);
|
|
220
376
|
registry.registerTools(workflow.tools);
|
|
377
|
+
// Debug: Show registered tools
|
|
378
|
+
if (options.debug) {
|
|
379
|
+
console.log(chalk.gray('\n🐛 Debug: SDK Registry'));
|
|
380
|
+
console.log(chalk.gray(` Registered tools: ${Object.keys(workflow.tools).join(', ')}`));
|
|
381
|
+
}
|
|
221
382
|
const result = await engine.execute(workflow, inputs, registry, createSDKStepExecutor());
|
|
222
383
|
if (result.status === WorkflowStatus.COMPLETED) {
|
|
223
384
|
spinner.succeed(`Workflow completed in ${result.duration}ms`);
|
|
224
385
|
}
|
|
225
386
|
else {
|
|
226
387
|
spinner.fail(`Workflow failed: ${result.error}`);
|
|
388
|
+
// Debug: Show detailed error information
|
|
389
|
+
if (options.debug) {
|
|
390
|
+
console.log(chalk.red('\n🐛 Debug: Failure Details'));
|
|
391
|
+
console.log(chalk.red(` Error: ${result.error}`));
|
|
392
|
+
// Find the failed step
|
|
393
|
+
const failedStep = result.stepResults.find(s => s.status === StepStatus.FAILED);
|
|
394
|
+
if (failedStep) {
|
|
395
|
+
console.log(chalk.red(` Failed Step: ${failedStep.stepId}`));
|
|
396
|
+
console.log(chalk.red(` Step Duration: ${failedStep.duration}ms`));
|
|
397
|
+
if (failedStep.error) {
|
|
398
|
+
console.log(chalk.red(` Step Error: ${failedStep.error}`));
|
|
399
|
+
// Extract detailed error information
|
|
400
|
+
const errorObj = typeof failedStep.error === 'object' ? failedStep.error : null;
|
|
401
|
+
if (errorObj) {
|
|
402
|
+
// HTTP error details (Axios, fetch, etc.)
|
|
403
|
+
if (errorObj.response) {
|
|
404
|
+
console.log(chalk.red('\n HTTP Error Details:'));
|
|
405
|
+
console.log(chalk.red(` Status: ${errorObj.response.status} ${errorObj.response.statusText || ''}`));
|
|
406
|
+
if (errorObj.config?.url) {
|
|
407
|
+
console.log(chalk.red(` URL: ${errorObj.config.method?.toUpperCase() || 'GET'} ${errorObj.config.url}`));
|
|
408
|
+
}
|
|
409
|
+
if (errorObj.response.data) {
|
|
410
|
+
console.log(chalk.red(` Response Body:`));
|
|
411
|
+
try {
|
|
412
|
+
const responseStr = typeof errorObj.response.data === 'string'
|
|
413
|
+
? errorObj.response.data
|
|
414
|
+
: JSON.stringify(errorObj.response.data, null, 2);
|
|
415
|
+
const lines = responseStr.split('\n').slice(0, 20); // First 20 lines
|
|
416
|
+
lines.forEach((line) => console.log(chalk.red(` ${line}`)));
|
|
417
|
+
if (responseStr.split('\n').length > 20) {
|
|
418
|
+
console.log(chalk.red(` ... (truncated)`));
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
catch {
|
|
422
|
+
console.log(chalk.red(` [Unable to serialize response]`));
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
if (errorObj.response.headers) {
|
|
426
|
+
console.log(chalk.red(` Response Headers:`));
|
|
427
|
+
const headers = errorObj.response.headers;
|
|
428
|
+
Object.keys(headers).slice(0, 10).forEach((key) => {
|
|
429
|
+
console.log(chalk.red(` ${key}: ${headers[key]}`));
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
// Request details
|
|
434
|
+
if (errorObj.config && !errorObj.response) {
|
|
435
|
+
console.log(chalk.red('\n Request Details:'));
|
|
436
|
+
if (errorObj.config.url) {
|
|
437
|
+
console.log(chalk.red(` URL: ${errorObj.config.method?.toUpperCase() || 'GET'} ${errorObj.config.url}`));
|
|
438
|
+
}
|
|
439
|
+
if (errorObj.config.baseURL) {
|
|
440
|
+
console.log(chalk.red(` Base URL: ${errorObj.config.baseURL}`));
|
|
441
|
+
}
|
|
442
|
+
if (errorObj.code) {
|
|
443
|
+
console.log(chalk.red(` Error Code: ${errorObj.code}`));
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
// Stack trace
|
|
447
|
+
if (errorObj.stack) {
|
|
448
|
+
console.log(chalk.red('\n Stack Trace:'));
|
|
449
|
+
const stack = errorObj.stack.split('\n').slice(0, 15); // First 15 lines
|
|
450
|
+
stack.forEach((line) => console.log(chalk.red(` ${line}`)));
|
|
451
|
+
if (errorObj.stack.split('\n').length > 15) {
|
|
452
|
+
console.log(chalk.red(` ... (truncated)`));
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
if (failedStep.output) {
|
|
458
|
+
console.log(chalk.red(` Output: ${JSON.stringify(failedStep.output, null, 2)}`));
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
// Show context from previous steps
|
|
462
|
+
console.log(chalk.yellow('\n🐛 Debug: Execution Context'));
|
|
463
|
+
console.log(chalk.yellow(` Total steps executed: ${result.stepResults.length}`));
|
|
464
|
+
console.log(chalk.yellow(` Steps before failure:`));
|
|
465
|
+
result.stepResults.slice(0, -1).forEach(stepResult => {
|
|
466
|
+
console.log(chalk.yellow(` - ${stepResult.stepId}: ${stepResult.status}`));
|
|
467
|
+
});
|
|
468
|
+
}
|
|
227
469
|
process.exit(1);
|
|
228
470
|
}
|
|
229
471
|
// Show summary
|
|
@@ -235,9 +477,42 @@ program
|
|
|
235
477
|
const failed = result.stepResults.filter((s) => s.status === StepStatus.FAILED).length;
|
|
236
478
|
const skipped = result.stepResults.filter((s) => s.status === StepStatus.SKIPPED).length;
|
|
237
479
|
console.log(` Completed: ${completed}, Failed: ${failed}, Skipped: ${skipped}`);
|
|
480
|
+
// Close StateStore and exit successfully to avoid hanging due to open SDK connections
|
|
481
|
+
stateStore.close();
|
|
482
|
+
process.exit(0);
|
|
238
483
|
}
|
|
239
484
|
catch (error) {
|
|
240
485
|
spinner.fail(`Execution failed: ${error}`);
|
|
486
|
+
// Debug: Show full error details with stack trace
|
|
487
|
+
if (options.debug) {
|
|
488
|
+
console.log(chalk.red('\n🐛 Debug: Unhandled Error Details'));
|
|
489
|
+
console.log(chalk.red(` Error Type: ${error instanceof Error ? error.constructor.name : typeof error}`));
|
|
490
|
+
console.log(chalk.red(` Error Message: ${error instanceof Error ? error.message : String(error)}`));
|
|
491
|
+
if (error instanceof Error && error.stack) {
|
|
492
|
+
console.log(chalk.red('\n Stack Trace:'));
|
|
493
|
+
error.stack.split('\n').forEach(line => {
|
|
494
|
+
console.log(chalk.red(` ${line}`));
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
// Show error properties if available
|
|
498
|
+
if (error && typeof error === 'object') {
|
|
499
|
+
const errorObj = error;
|
|
500
|
+
const keys = Object.keys(errorObj).filter(k => k !== 'stack' && k !== 'message');
|
|
501
|
+
if (keys.length > 0) {
|
|
502
|
+
console.log(chalk.red('\n Additional Error Properties:'));
|
|
503
|
+
keys.forEach(key => {
|
|
504
|
+
try {
|
|
505
|
+
console.log(chalk.red(` ${key}: ${JSON.stringify(errorObj[key], null, 2)}`));
|
|
506
|
+
}
|
|
507
|
+
catch {
|
|
508
|
+
console.log(chalk.red(` ${key}: [Unable to serialize]`));
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
// Close StateStore if it was created
|
|
515
|
+
stateStore?.close();
|
|
241
516
|
process.exit(1);
|
|
242
517
|
}
|
|
243
518
|
});
|
|
@@ -247,6 +522,8 @@ program
|
|
|
247
522
|
.description('Debug a workflow with step-by-step execution')
|
|
248
523
|
.option('-i, --input <key=value...>', 'Input parameters')
|
|
249
524
|
.option('-b, --breakpoint <stepId...>', 'Set breakpoints at step IDs')
|
|
525
|
+
.option('-a, --agent <provider>', 'AI agent provider (claude-code, claude-agent, github-copilot, opencode, ollama, codex)')
|
|
526
|
+
.option('-m, --model <name>', 'Model name to use (e.g., claude-sonnet-4, gpt-4, etc.)')
|
|
250
527
|
.option('--auto-start', 'Start without initial prompt')
|
|
251
528
|
.action(async (workflowPath, options) => {
|
|
252
529
|
const spinner = ora('Loading workflow for debugging...').start();
|
|
@@ -271,13 +548,24 @@ program
|
|
|
271
548
|
else {
|
|
272
549
|
spinner.succeed(`Loaded: ${workflow.metadata.name}`);
|
|
273
550
|
}
|
|
274
|
-
// Parse inputs
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
551
|
+
// Parse and validate inputs
|
|
552
|
+
const parsedInputs = parseInputPairs(options.input);
|
|
553
|
+
const validation = validateAndApplyDefaults(workflow, parsedInputs);
|
|
554
|
+
if (!validation.valid) {
|
|
555
|
+
spinner.fail('Missing required inputs');
|
|
556
|
+
printMissingInputsError(workflow, validation.missingInputs, 'debug', workflowPath);
|
|
557
|
+
process.exit(1);
|
|
558
|
+
}
|
|
559
|
+
const inputs = validation.inputs;
|
|
560
|
+
// Override AI agent if specified
|
|
561
|
+
if (options.agent) {
|
|
562
|
+
const sdkName = getAgentSDKName(options.agent);
|
|
563
|
+
const authConfig = getAgentAuthConfig(sdkName);
|
|
564
|
+
overrideAgentInWorkflow(workflow, sdkName, authConfig);
|
|
565
|
+
}
|
|
566
|
+
// Override model if specified
|
|
567
|
+
if (options.model) {
|
|
568
|
+
overrideModelInWorkflow(workflow, options.model);
|
|
281
569
|
}
|
|
282
570
|
// Parse breakpoints
|
|
283
571
|
const breakpoints = options.breakpoint ? parseBreakpoints(options.breakpoint) : [];
|
|
@@ -405,6 +693,107 @@ toolsCmd
|
|
|
405
693
|
console.log(` ${chalk.cyan(toolName)} ${types ? `(${types})` : ''}`);
|
|
406
694
|
}
|
|
407
695
|
});
|
|
696
|
+
// --- credentials ---
|
|
697
|
+
const credentialsCmd = program.command('credentials').description('Credential management');
|
|
698
|
+
credentialsCmd
|
|
699
|
+
.command('list')
|
|
700
|
+
.description('List stored credentials')
|
|
701
|
+
.option('--state-dir <path>', 'State directory', join('.marktoflow', 'credentials'))
|
|
702
|
+
.option('--backend <backend>', 'Encryption backend (aes-256-gcm, fernet, age, gpg)')
|
|
703
|
+
.option('--tag <tag>', 'Filter by tag')
|
|
704
|
+
.option('--show-expired', 'Include expired credentials')
|
|
705
|
+
.action((options) => {
|
|
706
|
+
try {
|
|
707
|
+
const stateDir = options.stateDir;
|
|
708
|
+
const backend = options.backend ?? undefined;
|
|
709
|
+
const manager = createCredentialManager({ stateDir, backend });
|
|
710
|
+
const credentials = manager.list(options.tag, options.showExpired);
|
|
711
|
+
if (credentials.length === 0) {
|
|
712
|
+
console.log(chalk.yellow('No credentials found.'));
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
console.log(chalk.bold(`Credentials (${credentials.length}):\n`));
|
|
716
|
+
for (const cred of credentials) {
|
|
717
|
+
const expired = cred.expiresAt && cred.expiresAt < new Date();
|
|
718
|
+
const status = expired ? chalk.red(' [EXPIRED]') : '';
|
|
719
|
+
console.log(` ${chalk.cyan(cred.name)}${status}`);
|
|
720
|
+
console.log(` Type: ${cred.credentialType}`);
|
|
721
|
+
if (cred.description) {
|
|
722
|
+
console.log(` Description: ${cred.description}`);
|
|
723
|
+
}
|
|
724
|
+
console.log(` Created: ${cred.createdAt.toISOString()}`);
|
|
725
|
+
console.log(` Updated: ${cred.updatedAt.toISOString()}`);
|
|
726
|
+
if (cred.expiresAt) {
|
|
727
|
+
console.log(` Expires: ${cred.expiresAt.toISOString()}`);
|
|
728
|
+
}
|
|
729
|
+
if (cred.tags.length > 0) {
|
|
730
|
+
console.log(` Tags: ${cred.tags.join(', ')}`);
|
|
731
|
+
}
|
|
732
|
+
console.log();
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
catch (error) {
|
|
736
|
+
console.log(chalk.red(`Failed to list credentials: ${error}`));
|
|
737
|
+
process.exit(1);
|
|
738
|
+
}
|
|
739
|
+
});
|
|
740
|
+
credentialsCmd
|
|
741
|
+
.command('verify')
|
|
742
|
+
.description('Verify credential encryption is working')
|
|
743
|
+
.option('--state-dir <path>', 'State directory', join('.marktoflow', 'credentials'))
|
|
744
|
+
.option('--backend <backend>', 'Encryption backend (aes-256-gcm, fernet, age, gpg)')
|
|
745
|
+
.action((options) => {
|
|
746
|
+
try {
|
|
747
|
+
const stateDir = options.stateDir;
|
|
748
|
+
const backend = options.backend ?? undefined;
|
|
749
|
+
console.log(chalk.bold('Credential Encryption Verification\n'));
|
|
750
|
+
// Show available backends
|
|
751
|
+
const backends = getAvailableBackends();
|
|
752
|
+
console.log(chalk.bold('Available backends:'));
|
|
753
|
+
for (const b of backends) {
|
|
754
|
+
const isDefault = b === EncryptionBackend.AES_256_GCM;
|
|
755
|
+
const marker = isDefault ? chalk.green(' (default)') : '';
|
|
756
|
+
const selected = (backend ?? EncryptionBackend.AES_256_GCM) === b ? chalk.cyan(' <-- selected') : '';
|
|
757
|
+
console.log(` ${chalk.cyan(b)}${marker}${selected}`);
|
|
758
|
+
}
|
|
759
|
+
console.log();
|
|
760
|
+
// Test encrypt/decrypt round-trip
|
|
761
|
+
const manager = createCredentialManager({ stateDir, backend });
|
|
762
|
+
const testValue = `verify-test-${Date.now()}`;
|
|
763
|
+
const testName = `__verify_test_${Date.now()}`;
|
|
764
|
+
console.log('Testing encrypt/decrypt round-trip...');
|
|
765
|
+
manager.set({ name: testName, value: testValue, tags: ['__test'] });
|
|
766
|
+
const decrypted = manager.get(testName);
|
|
767
|
+
if (decrypted === testValue) {
|
|
768
|
+
console.log(chalk.green(' Round-trip: PASS'));
|
|
769
|
+
}
|
|
770
|
+
else {
|
|
771
|
+
console.log(chalk.red(' Round-trip: FAIL'));
|
|
772
|
+
console.log(chalk.red(` Expected: ${testValue}`));
|
|
773
|
+
console.log(chalk.red(` Got: ${decrypted}`));
|
|
774
|
+
process.exit(1);
|
|
775
|
+
}
|
|
776
|
+
// Verify stored value is encrypted (not plain text)
|
|
777
|
+
const raw = manager.get(testName, false);
|
|
778
|
+
if (raw !== testValue) {
|
|
779
|
+
console.log(chalk.green(' Encryption: PASS (stored value is encrypted)'));
|
|
780
|
+
}
|
|
781
|
+
else {
|
|
782
|
+
console.log(chalk.red(' Encryption: FAIL (stored value is plain text)'));
|
|
783
|
+
process.exit(1);
|
|
784
|
+
}
|
|
785
|
+
// Cleanup test credential
|
|
786
|
+
manager.delete(testName);
|
|
787
|
+
console.log(chalk.green('\n All checks passed.'));
|
|
788
|
+
// Show credential count
|
|
789
|
+
const credentials = manager.list();
|
|
790
|
+
console.log(`\n Stored credentials: ${credentials.length}`);
|
|
791
|
+
}
|
|
792
|
+
catch (error) {
|
|
793
|
+
console.log(chalk.red(`Verification failed: ${error}`));
|
|
794
|
+
process.exit(1);
|
|
795
|
+
}
|
|
796
|
+
});
|
|
408
797
|
// --- schedule ---
|
|
409
798
|
const scheduleCmd = program.command('schedule').description('Scheduler management');
|
|
410
799
|
scheduleCmd
|
|
@@ -496,13 +885,14 @@ bundleCmd
|
|
|
496
885
|
}
|
|
497
886
|
const bundle = new WorkflowBundle(path);
|
|
498
887
|
const workflow = await bundle.loadWorkflowWithBundleTools();
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
888
|
+
// Parse and validate inputs
|
|
889
|
+
const parsedInputs = parseInputPairs(options.input);
|
|
890
|
+
const validation = validateAndApplyDefaults(workflow, parsedInputs);
|
|
891
|
+
if (!validation.valid) {
|
|
892
|
+
printMissingInputsError(workflow, validation.missingInputs, 'bundle run', path);
|
|
893
|
+
process.exit(1);
|
|
505
894
|
}
|
|
895
|
+
const inputs = validation.inputs;
|
|
506
896
|
const engine = new WorkflowEngine();
|
|
507
897
|
const registry = new SDKRegistry();
|
|
508
898
|
registerIntegrations(registry);
|
|
@@ -534,6 +924,7 @@ program
|
|
|
534
924
|
.option('--client-id <id>', 'OAuth client ID')
|
|
535
925
|
.option('--client-secret <secret>', 'OAuth client secret')
|
|
536
926
|
.option('--tenant-id <tenant>', 'Microsoft tenant ID (for Outlook)')
|
|
927
|
+
.option('--port <port>', 'Port for OAuth callback server (default: 8484)', '8484')
|
|
537
928
|
.action(async (service, options) => {
|
|
538
929
|
const serviceLower = service.toLowerCase();
|
|
539
930
|
console.log(chalk.bold(`Connecting ${service}...`));
|
|
@@ -541,6 +932,7 @@ program
|
|
|
541
932
|
if (serviceLower === 'gmail') {
|
|
542
933
|
const clientId = options.clientId ?? process.env.GOOGLE_CLIENT_ID;
|
|
543
934
|
const clientSecret = options.clientSecret ?? process.env.GOOGLE_CLIENT_SECRET;
|
|
935
|
+
const port = parseInt(options.port, 10);
|
|
544
936
|
if (!clientId || !clientSecret) {
|
|
545
937
|
console.log(chalk.yellow('\nGmail OAuth requires client credentials.'));
|
|
546
938
|
console.log('\nTo connect Gmail:');
|
|
@@ -554,7 +946,7 @@ program
|
|
|
554
946
|
}
|
|
555
947
|
try {
|
|
556
948
|
const { runGmailOAuth } = await import('./oauth.js');
|
|
557
|
-
const tokens = await runGmailOAuth({ clientId, clientSecret });
|
|
949
|
+
const tokens = await runGmailOAuth({ clientId, clientSecret, port });
|
|
558
950
|
console.log(chalk.green('\nGmail connected successfully!'));
|
|
559
951
|
console.log(chalk.dim(`Access token expires: ${tokens.expires_at ? new Date(tokens.expires_at).toISOString() : 'unknown'}`));
|
|
560
952
|
console.log('\nYou can now use Gmail in your workflows:');
|
|
@@ -564,25 +956,77 @@ program
|
|
|
564
956
|
auth:
|
|
565
957
|
client_id: "\${GOOGLE_CLIENT_ID}"
|
|
566
958
|
client_secret: "\${GOOGLE_CLIENT_SECRET}"
|
|
567
|
-
redirect_uri: "http://localhost
|
|
959
|
+
redirect_uri: "http://localhost:${port}/callback"
|
|
568
960
|
refresh_token: "\${GMAIL_REFRESH_TOKEN}"`));
|
|
961
|
+
process.exit(0);
|
|
962
|
+
}
|
|
963
|
+
catch (error) {
|
|
964
|
+
console.log(chalk.red(`\nOAuth failed: ${error}`));
|
|
965
|
+
process.exit(1);
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
// Handle other Google services (Drive, Sheets, Calendar, Docs, Workspace)
|
|
969
|
+
if (serviceLower === 'google-drive' ||
|
|
970
|
+
serviceLower === 'drive' ||
|
|
971
|
+
serviceLower === 'google-sheets' ||
|
|
972
|
+
serviceLower === 'sheets' ||
|
|
973
|
+
serviceLower === 'google-calendar' ||
|
|
974
|
+
serviceLower === 'calendar' ||
|
|
975
|
+
serviceLower === 'google-docs' ||
|
|
976
|
+
serviceLower === 'docs' ||
|
|
977
|
+
serviceLower === 'google-workspace' ||
|
|
978
|
+
serviceLower === 'workspace') {
|
|
979
|
+
const clientId = options.clientId ?? process.env.GOOGLE_CLIENT_ID;
|
|
980
|
+
const clientSecret = options.clientSecret ?? process.env.GOOGLE_CLIENT_SECRET;
|
|
981
|
+
const port = parseInt(options.port, 10);
|
|
982
|
+
if (!clientId || !clientSecret) {
|
|
983
|
+
console.log(chalk.yellow('\nGoogle OAuth requires client credentials.'));
|
|
984
|
+
console.log('\nTo connect Google services:');
|
|
985
|
+
console.log(' 1. Go to https://console.cloud.google.com/');
|
|
986
|
+
console.log(' 2. Enable the API for your service (Drive, Sheets, etc.)');
|
|
987
|
+
console.log(' 3. Create OAuth 2.0 credentials (Desktop app type)');
|
|
988
|
+
console.log(` 4. Run: marktoflow connect ${service} --client-id YOUR_ID --client-secret YOUR_SECRET`);
|
|
989
|
+
console.log('\nOr set environment variables:');
|
|
990
|
+
console.log(' export GOOGLE_CLIENT_ID="your-client-id"');
|
|
991
|
+
console.log(' export GOOGLE_CLIENT_SECRET="your-client-secret"');
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
try {
|
|
995
|
+
const { runGoogleOAuth } = await import('./oauth.js');
|
|
996
|
+
const tokens = await runGoogleOAuth(serviceLower, { clientId, clientSecret, port });
|
|
997
|
+
console.log(chalk.dim(`Access token expires: ${tokens.expires_at ? new Date(tokens.expires_at).toISOString() : 'unknown'}`));
|
|
998
|
+
// Normalize service name for display
|
|
999
|
+
const normalizedService = serviceLower.startsWith('google-')
|
|
1000
|
+
? serviceLower
|
|
1001
|
+
: `google-${serviceLower}`;
|
|
1002
|
+
console.log('\nYou can now use this service in your workflows:');
|
|
1003
|
+
console.log(chalk.cyan(` tools:
|
|
1004
|
+
${serviceLower.replace('google-', '')}:
|
|
1005
|
+
sdk: "${normalizedService}"
|
|
1006
|
+
auth:
|
|
1007
|
+
client_id: "\${GOOGLE_CLIENT_ID}"
|
|
1008
|
+
client_secret: "\${GOOGLE_CLIENT_SECRET}"
|
|
1009
|
+
redirect_uri: "http://localhost:${port}/callback"
|
|
1010
|
+
refresh_token: "\${GOOGLE_REFRESH_TOKEN}"
|
|
1011
|
+
access_token: "\${GOOGLE_ACCESS_TOKEN}"`));
|
|
1012
|
+
process.exit(0);
|
|
569
1013
|
}
|
|
570
1014
|
catch (error) {
|
|
571
1015
|
console.log(chalk.red(`\nOAuth failed: ${error}`));
|
|
572
1016
|
process.exit(1);
|
|
573
1017
|
}
|
|
574
|
-
return;
|
|
575
1018
|
}
|
|
576
1019
|
if (serviceLower === 'outlook' || serviceLower === 'microsoft') {
|
|
577
1020
|
const clientId = options.clientId ?? process.env.MICROSOFT_CLIENT_ID;
|
|
578
1021
|
const clientSecret = options.clientSecret ?? process.env.MICROSOFT_CLIENT_SECRET;
|
|
579
1022
|
const tenantId = options.tenantId ?? process.env.MICROSOFT_TENANT_ID;
|
|
1023
|
+
const port = parseInt(options.port, 10);
|
|
580
1024
|
if (!clientId) {
|
|
581
1025
|
console.log(chalk.yellow('\nOutlook OAuth requires a client ID.'));
|
|
582
1026
|
console.log('\nTo connect Outlook/Microsoft Graph:');
|
|
583
1027
|
console.log(' 1. Go to https://portal.azure.com/');
|
|
584
1028
|
console.log(' 2. Register an application in Azure AD');
|
|
585
|
-
console.log(
|
|
1029
|
+
console.log(` 3. Add redirect URI: http://localhost:${port}/callback`);
|
|
586
1030
|
console.log(' 4. Grant Mail.Read, Mail.Send, Calendars.ReadWrite permissions');
|
|
587
1031
|
console.log(' 5. Run: marktoflow connect outlook --client-id YOUR_ID');
|
|
588
1032
|
console.log('\nOr set environment variables:');
|
|
@@ -593,7 +1037,7 @@ program
|
|
|
593
1037
|
}
|
|
594
1038
|
try {
|
|
595
1039
|
const { runOutlookOAuth } = await import('./oauth.js');
|
|
596
|
-
const tokens = await runOutlookOAuth({ clientId, clientSecret, tenantId });
|
|
1040
|
+
const tokens = await runOutlookOAuth({ clientId, clientSecret, tenantId, port });
|
|
597
1041
|
console.log(chalk.green('\nOutlook connected successfully!'));
|
|
598
1042
|
console.log(chalk.dim(`Access token expires: ${tokens.expires_at ? new Date(tokens.expires_at).toISOString() : 'unknown'}`));
|
|
599
1043
|
console.log('\nYou can now use Outlook in your workflows:');
|
|
@@ -602,12 +1046,12 @@ program
|
|
|
602
1046
|
sdk: "@microsoft/microsoft-graph-client"
|
|
603
1047
|
auth:
|
|
604
1048
|
token: "\${OUTLOOK_ACCESS_TOKEN}"`));
|
|
1049
|
+
process.exit(0);
|
|
605
1050
|
}
|
|
606
1051
|
catch (error) {
|
|
607
1052
|
console.log(chalk.red(`\nOAuth failed: ${error}`));
|
|
608
1053
|
process.exit(1);
|
|
609
1054
|
}
|
|
610
|
-
return;
|
|
611
1055
|
}
|
|
612
1056
|
// Other services - show manual setup instructions
|
|
613
1057
|
console.log('\nManual setup required. Set environment variables:');
|
|
@@ -663,6 +1107,7 @@ program
|
|
|
663
1107
|
console.log('\n' + chalk.bold('Available services:'));
|
|
664
1108
|
console.log(' Communication: slack, discord');
|
|
665
1109
|
console.log(' Email: gmail, outlook');
|
|
1110
|
+
console.log(' Google Workspace: google-drive, google-sheets, google-calendar, google-docs, google-workspace');
|
|
666
1111
|
console.log(' Project management: jira, linear');
|
|
667
1112
|
console.log(' Documentation: notion, confluence');
|
|
668
1113
|
console.log(' Developer: github');
|
|
@@ -736,6 +1181,104 @@ program
|
|
|
736
1181
|
console.log(chalk.yellow('\n Run `marktoflow connect <service>` to set up integrations'));
|
|
737
1182
|
}
|
|
738
1183
|
});
|
|
1184
|
+
// --- test-connection ---
|
|
1185
|
+
program
|
|
1186
|
+
.command('test-connection [service]')
|
|
1187
|
+
.description('Test service connection(s)')
|
|
1188
|
+
.option('-a, --all', 'Test all configured services')
|
|
1189
|
+
.action(async (service, options) => {
|
|
1190
|
+
await executeTestConnection(service, options);
|
|
1191
|
+
});
|
|
1192
|
+
// --- history ---
|
|
1193
|
+
program
|
|
1194
|
+
.command('history [runId]')
|
|
1195
|
+
.description('View execution history')
|
|
1196
|
+
.option('-n, --limit <count>', 'Number of executions to show', '20')
|
|
1197
|
+
.option('-s, --status <status>', 'Filter by status (completed, failed, running)')
|
|
1198
|
+
.option('-w, --workflow <id>', 'Filter by workflow ID')
|
|
1199
|
+
.option('--step <stepId>', 'Show specific step details (requires runId)')
|
|
1200
|
+
.action((runId, options) => {
|
|
1201
|
+
if (runId) {
|
|
1202
|
+
executeHistoryDetail(runId, { step: options.step });
|
|
1203
|
+
}
|
|
1204
|
+
else {
|
|
1205
|
+
executeHistory({
|
|
1206
|
+
limit: options.limit ? parseInt(options.limit, 10) : 20,
|
|
1207
|
+
status: options.status,
|
|
1208
|
+
workflow: options.workflow,
|
|
1209
|
+
});
|
|
1210
|
+
}
|
|
1211
|
+
});
|
|
1212
|
+
// --- replay ---
|
|
1213
|
+
program
|
|
1214
|
+
.command('replay <runId>')
|
|
1215
|
+
.description('Replay a previous execution with the same inputs')
|
|
1216
|
+
.option('--from <stepId>', 'Resume from specific step')
|
|
1217
|
+
.option('--dry-run', 'Preview what would be executed')
|
|
1218
|
+
.action(async (runId, options) => {
|
|
1219
|
+
await executeReplay(runId, options);
|
|
1220
|
+
});
|
|
1221
|
+
// --- gui ---
|
|
1222
|
+
program
|
|
1223
|
+
.command('gui')
|
|
1224
|
+
.description('Launch visual workflow designer')
|
|
1225
|
+
.option('-p, --port <port>', 'Server port', '3001')
|
|
1226
|
+
.option('-o, --open', 'Open browser automatically')
|
|
1227
|
+
.option('-w, --workflow <path>', 'Open specific workflow')
|
|
1228
|
+
.option('-d, --dir <path>', 'Workflow directory', '.')
|
|
1229
|
+
.action(async (options) => {
|
|
1230
|
+
const spinner = ora('Starting GUI server...').start();
|
|
1231
|
+
try {
|
|
1232
|
+
// Check if @marktoflow/gui is available
|
|
1233
|
+
let guiModule;
|
|
1234
|
+
let guiPackagePath;
|
|
1235
|
+
try {
|
|
1236
|
+
guiModule = await import('@marktoflow/gui');
|
|
1237
|
+
// Find the GUI package location
|
|
1238
|
+
const { createRequire } = await import('node:module');
|
|
1239
|
+
const require = createRequire(import.meta.url);
|
|
1240
|
+
guiPackagePath = require.resolve('@marktoflow/gui');
|
|
1241
|
+
}
|
|
1242
|
+
catch {
|
|
1243
|
+
spinner.fail('@marktoflow/gui package not found');
|
|
1244
|
+
console.log(chalk.yellow('\nTo use the GUI, install the gui package:'));
|
|
1245
|
+
console.log(chalk.cyan(' npm install @marktoflow/gui@alpha'));
|
|
1246
|
+
console.log('\nOr run from the monorepo:');
|
|
1247
|
+
console.log(chalk.cyan(' pnpm --filter @marktoflow/gui dev'));
|
|
1248
|
+
process.exit(1);
|
|
1249
|
+
}
|
|
1250
|
+
spinner.succeed(`GUI server starting on http://localhost:${options.port}`);
|
|
1251
|
+
// Open browser if requested
|
|
1252
|
+
if (options.open) {
|
|
1253
|
+
const url = `http://localhost:${options.port}`;
|
|
1254
|
+
const { exec } = await import('node:child_process');
|
|
1255
|
+
const openCmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
1256
|
+
exec(`${openCmd} ${url}`);
|
|
1257
|
+
}
|
|
1258
|
+
console.log('\n' + chalk.bold('Marktoflow GUI'));
|
|
1259
|
+
console.log(` Server: ${chalk.cyan(`http://localhost:${options.port}`)}`);
|
|
1260
|
+
console.log(` Workflows: ${chalk.cyan(options.dir)}`);
|
|
1261
|
+
console.log('\n Press ' + chalk.bold('Ctrl+C') + ' to stop\n');
|
|
1262
|
+
// Find the static files directory
|
|
1263
|
+
// guiPackagePath is .../node_modules/@marktoflow/gui/dist/server/index.js
|
|
1264
|
+
// We need to go up to the package root: dist/server -> dist -> package root
|
|
1265
|
+
const { dirname, join } = await import('node:path');
|
|
1266
|
+
const guiPackageDir = dirname(dirname(dirname(guiPackagePath)));
|
|
1267
|
+
const staticDir = join(guiPackageDir, 'dist', 'client');
|
|
1268
|
+
// The GUI package will handle the server
|
|
1269
|
+
if (guiModule.startServer) {
|
|
1270
|
+
await guiModule.startServer({
|
|
1271
|
+
port: parseInt(options.port, 10),
|
|
1272
|
+
workflowDir: options.dir,
|
|
1273
|
+
staticDir: staticDir,
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
catch (error) {
|
|
1278
|
+
spinner.fail(`Failed to start GUI: ${error}`);
|
|
1279
|
+
process.exit(1);
|
|
1280
|
+
}
|
|
1281
|
+
});
|
|
739
1282
|
// --- version ---
|
|
740
1283
|
program
|
|
741
1284
|
.command('version')
|