@marktoflow/cli 2.0.0-alpha.11 → 2.0.0-alpha.13
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 +53 -5
- package/dist/index.d.ts +1 -1
- package/dist/index.js +362 -30
- 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 +7 -4
package/README.md
CHANGED
|
@@ -2,11 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
> **Author:** Scott Glover <scottgl@gmail.com>
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**CLI-first agent automation framework** with native MCP support and direct SDK integration.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
**Version:** 2.0.0-alpha.13
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## What is marktoflow?
|
|
12
|
+
|
|
13
|
+
marktoflow is a **CLI-first automation framework** that lets you define workflows in Markdown + YAML and execute them across 30+ services. Write workflows as code, run them from the terminal, and optionally use the visual designer for editing.
|
|
14
|
+
|
|
15
|
+
**Key Differentiators:**
|
|
16
|
+
|
|
17
|
+
- 🖥️ **CLI-First** - Design and run workflows from your terminal
|
|
18
|
+
- 📝 **Workflows as Markdown** - Human-readable, version-controlled automation
|
|
19
|
+
- 🔌 **Native SDK Integration** - Direct method calls with full type safety
|
|
20
|
+
- 🤖 **AI Agent Support** - GitHub Copilot, Claude, Ollama - use existing subscriptions
|
|
21
|
+
- 🌐 **Universal REST Client** - Connect to any API without custom integrations
|
|
22
|
+
- 🎨 **Visual Designer (Optional)** - Web-based drag-and-drop editor
|
|
23
|
+
- 🏢 **Enterprise Ready** - RBAC, approvals, audit logging, cost tracking
|
|
10
24
|
|
|
11
25
|
## Features
|
|
12
26
|
|
|
@@ -38,7 +52,7 @@ npx @marktoflow/cli@alpha <command>
|
|
|
38
52
|
### Install from GitHub
|
|
39
53
|
|
|
40
54
|
```bash
|
|
41
|
-
npm install -g github:
|
|
55
|
+
npm install -g github:marktoflow/marktoflow#main
|
|
42
56
|
```
|
|
43
57
|
|
|
44
58
|
## Quick Start
|
|
@@ -280,6 +294,18 @@ marktoflow new code-review --output workflows/code-review.md
|
|
|
280
294
|
marktoflow new
|
|
281
295
|
```
|
|
282
296
|
|
|
297
|
+
**Available Templates:**
|
|
298
|
+
|
|
299
|
+
Built-in templates match the examples/ directory:
|
|
300
|
+
- `code-review` - Automated GitHub PR review
|
|
301
|
+
- `daily-standup` - Jira/Slack standup automation
|
|
302
|
+
- `incident-response` - Multi-service incident coordination
|
|
303
|
+
- `dependency-update` - Package update automation
|
|
304
|
+
- `sprint-planning` - AI-assisted sprint planning
|
|
305
|
+
- `web-automation` - Playwright browser automation
|
|
306
|
+
- `gmail-notification` - Email automation
|
|
307
|
+
- And more...
|
|
308
|
+
|
|
283
309
|
### Agent & Tool Management
|
|
284
310
|
|
|
285
311
|
#### `marktoflow agents list`
|
|
@@ -372,7 +398,29 @@ logging:
|
|
|
372
398
|
file: .marktoflow/logs/marktoflow.log
|
|
373
399
|
```
|
|
374
400
|
|
|
375
|
-
## Examples
|
|
401
|
+
## Production Examples
|
|
402
|
+
|
|
403
|
+
The [examples/](https://github.com/marktoflow/marktoflow/tree/main/examples) directory contains 14+ production-ready workflow templates you can use as starting points:
|
|
404
|
+
|
|
405
|
+
- **[code-review](https://github.com/marktoflow/marktoflow/tree/main/examples/code-review)** - Automated GitHub PR review with security, performance, and quality checks
|
|
406
|
+
- **[copilot-code-review](https://github.com/marktoflow/marktoflow/tree/main/examples/copilot-code-review)** - Advanced review using GitHub Copilot SDK
|
|
407
|
+
- **[daily-standup](https://github.com/marktoflow/marktoflow/tree/main/examples/daily-standup)** - Jira + Slack activity aggregation with AI summaries
|
|
408
|
+
- **[dependency-update](https://github.com/marktoflow/marktoflow/tree/main/examples/dependency-update)** - Automated package updates with AI changelogs
|
|
409
|
+
- **[incident-response](https://github.com/marktoflow/marktoflow/tree/main/examples/incident-response)** - PagerDuty + Slack + Jira coordination
|
|
410
|
+
- **[sprint-planning](https://github.com/marktoflow/marktoflow/tree/main/examples/sprint-planning)** - Velocity analysis + AI story selection
|
|
411
|
+
- **[doc-maintenance](https://github.com/marktoflow/marktoflow/tree/main/examples/doc-maintenance)** - Smart documentation updates using Ollama
|
|
412
|
+
- **[web-automation](https://github.com/marktoflow/marktoflow/tree/main/examples/web-automation)** - Playwright-based browser automation
|
|
413
|
+
- **[gmail-notification](https://github.com/marktoflow/marktoflow/tree/main/examples/gmail-notification)** - Email automation with Gmail API
|
|
414
|
+
- **[linear-sync](https://github.com/marktoflow/marktoflow/tree/main/examples/linear-sync)** - Linear issue tracking sync
|
|
415
|
+
- **[codebase-qa](https://github.com/marktoflow/marktoflow/tree/main/examples/codebase-qa)** - AI-powered codebase Q&A
|
|
416
|
+
|
|
417
|
+
Run any example:
|
|
418
|
+
|
|
419
|
+
```bash
|
|
420
|
+
marktoflow run examples/daily-standup/workflow.md
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
## CLI Examples
|
|
376
424
|
|
|
377
425
|
### Daily Standup Report
|
|
378
426
|
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
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';
|
|
@@ -13,12 +13,14 @@ import { parseFile, WorkflowEngine, SDKRegistry, createSDKStepExecutor, StepStat
|
|
|
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 { parseInputPairs, debugLogInputs, validateAndApplyDefaults, printMissingInputsError, overrideAgentInWorkflow, debugLogAgentOverride, overrideModelInWorkflow, } from './utils/index.js';
|
|
23
|
+
const VERSION = '2.0.0-alpha.13';
|
|
22
24
|
// Load environment variables from .env files on CLI startup
|
|
23
25
|
loadEnv();
|
|
24
26
|
function getConfig() {
|
|
@@ -36,16 +38,62 @@ function isBundle(path) {
|
|
|
36
38
|
return false;
|
|
37
39
|
}
|
|
38
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Map agent provider aliases to SDK names
|
|
43
|
+
*/
|
|
44
|
+
function getAgentSDKName(provider) {
|
|
45
|
+
const providerMap = {
|
|
46
|
+
'claude': 'claude-code',
|
|
47
|
+
'claude-code': 'claude-code',
|
|
48
|
+
'claude-agent': 'claude-agent',
|
|
49
|
+
'copilot': 'github-copilot',
|
|
50
|
+
'github-copilot': 'github-copilot',
|
|
51
|
+
'opencode': 'opencode',
|
|
52
|
+
'ollama': 'ollama',
|
|
53
|
+
'codex': 'codex',
|
|
54
|
+
};
|
|
55
|
+
const normalized = provider.toLowerCase();
|
|
56
|
+
const sdkName = providerMap[normalized];
|
|
57
|
+
if (!sdkName) {
|
|
58
|
+
throw new Error(`Unknown agent provider: ${provider}. Available: ${Object.keys(providerMap).join(', ')}`);
|
|
59
|
+
}
|
|
60
|
+
return sdkName;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Get default auth configuration for an agent provider
|
|
64
|
+
*/
|
|
65
|
+
function getAgentAuthConfig(sdkName) {
|
|
66
|
+
const authConfigs = {
|
|
67
|
+
'claude-code': {
|
|
68
|
+
api_key: '${ANTHROPIC_API_KEY}',
|
|
69
|
+
},
|
|
70
|
+
'claude-agent': {
|
|
71
|
+
api_key: '${ANTHROPIC_API_KEY}',
|
|
72
|
+
},
|
|
73
|
+
'github-copilot': {
|
|
74
|
+
token: '${GITHUB_TOKEN}',
|
|
75
|
+
},
|
|
76
|
+
'opencode': {},
|
|
77
|
+
'ollama': {
|
|
78
|
+
base_url: '${OLLAMA_BASE_URL:-http://localhost:11434}',
|
|
79
|
+
},
|
|
80
|
+
'codex': {
|
|
81
|
+
api_key: '${OPENAI_API_KEY}',
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
return authConfigs[sdkName] || {};
|
|
85
|
+
}
|
|
39
86
|
// ============================================================================
|
|
40
87
|
// CLI Setup
|
|
41
88
|
// ============================================================================
|
|
42
89
|
const program = new Command();
|
|
43
90
|
program
|
|
44
91
|
.name('marktoflow')
|
|
45
|
-
.description('
|
|
92
|
+
.description('Agent automation framework with native MCP support')
|
|
46
93
|
.version(VERSION);
|
|
47
94
|
program.addCommand(workerCommand);
|
|
48
95
|
program.addCommand(triggerCommand);
|
|
96
|
+
program.addCommand(serveCommand);
|
|
49
97
|
// ============================================================================
|
|
50
98
|
// Commands
|
|
51
99
|
// ============================================================================
|
|
@@ -84,7 +132,7 @@ workflow:
|
|
|
84
132
|
|
|
85
133
|
steps:
|
|
86
134
|
- id: greet
|
|
87
|
-
action:
|
|
135
|
+
action: core.log
|
|
88
136
|
inputs:
|
|
89
137
|
message: "Hello from marktoflow!"
|
|
90
138
|
---
|
|
@@ -154,6 +202,9 @@ program
|
|
|
154
202
|
.description('Run a workflow')
|
|
155
203
|
.option('-i, --input <key=value...>', 'Input parameters')
|
|
156
204
|
.option('-v, --verbose', 'Verbose output')
|
|
205
|
+
.option('-d, --debug', 'Debug mode with detailed output (includes stack traces)')
|
|
206
|
+
.option('-a, --agent <provider>', 'AI agent provider (claude-code, claude-agent, github-copilot, opencode, ollama, codex)')
|
|
207
|
+
.option('-m, --model <name>', 'Model name to use (e.g., claude-sonnet-4, gpt-4, etc.)')
|
|
157
208
|
.option('--dry-run', 'Parse workflow without executing')
|
|
158
209
|
.action(async (workflowPath, options) => {
|
|
159
210
|
const spinner = ora('Loading workflow...').start();
|
|
@@ -178,12 +229,56 @@ program
|
|
|
178
229
|
else {
|
|
179
230
|
spinner.succeed(`Loaded: ${workflow.metadata.name}`);
|
|
180
231
|
}
|
|
232
|
+
// Debug: Show workflow details
|
|
233
|
+
if (options.debug) {
|
|
234
|
+
console.log(chalk.gray('\n🐛 Debug: Workflow Details'));
|
|
235
|
+
console.log(chalk.gray(` ID: ${workflow.metadata.id}`));
|
|
236
|
+
console.log(chalk.gray(` Version: ${workflow.metadata.version}`));
|
|
237
|
+
console.log(chalk.gray(` Steps: ${workflow.steps.length}`));
|
|
238
|
+
console.log(chalk.gray(` Tools: ${Object.keys(workflow.tools).join(', ') || 'none'}`));
|
|
239
|
+
console.log(chalk.gray(` Inputs Required: ${Object.keys(workflow.inputs || {}).join(', ') || 'none'}`));
|
|
240
|
+
}
|
|
181
241
|
// Parse inputs
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
242
|
+
const parsedInputs = parseInputPairs(options.input);
|
|
243
|
+
// Debug: Show parsed inputs
|
|
244
|
+
if (options.debug) {
|
|
245
|
+
debugLogInputs(parsedInputs);
|
|
246
|
+
}
|
|
247
|
+
// Validate required inputs and apply defaults
|
|
248
|
+
const validation = validateAndApplyDefaults(workflow, parsedInputs, { debug: options.debug });
|
|
249
|
+
if (!validation.valid) {
|
|
250
|
+
spinner.fail('Missing required inputs');
|
|
251
|
+
printMissingInputsError(workflow, validation.missingInputs, 'run', workflowPath);
|
|
252
|
+
process.exit(1);
|
|
253
|
+
}
|
|
254
|
+
const inputs = validation.inputs;
|
|
255
|
+
// Override AI agent if specified
|
|
256
|
+
if (options.agent) {
|
|
257
|
+
const sdkName = getAgentSDKName(options.agent);
|
|
258
|
+
const authConfig = getAgentAuthConfig(sdkName);
|
|
259
|
+
const result = overrideAgentInWorkflow(workflow, sdkName, authConfig, {
|
|
260
|
+
verbose: options.verbose,
|
|
261
|
+
debug: options.debug,
|
|
262
|
+
});
|
|
263
|
+
if (options.debug) {
|
|
264
|
+
debugLogAgentOverride(options.agent, sdkName, result.replacedCount, authConfig);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
// Override model if specified
|
|
268
|
+
if (options.model) {
|
|
269
|
+
const result = overrideModelInWorkflow(workflow, options.model);
|
|
270
|
+
if (options.verbose || options.debug) {
|
|
271
|
+
if (result.overrideCount > 0) {
|
|
272
|
+
console.log(chalk.cyan(` Set model '${options.model}' for ${result.overrideCount} AI tool(s)`));
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (options.debug) {
|
|
276
|
+
console.log(chalk.gray('\n🐛 Debug: Model Override'));
|
|
277
|
+
console.log(chalk.gray(` Model: ${options.model}`));
|
|
278
|
+
console.log(chalk.gray(` Applied to ${result.overrideCount} AI tool(s)`));
|
|
279
|
+
}
|
|
280
|
+
if (result.overrideCount === 0 && (options.verbose || options.debug)) {
|
|
281
|
+
console.log(chalk.yellow(` Warning: --model specified but no AI tools found in workflow`));
|
|
187
282
|
}
|
|
188
283
|
}
|
|
189
284
|
// Handle dry-run mode
|
|
@@ -202,28 +297,166 @@ program
|
|
|
202
297
|
}
|
|
203
298
|
// Execute workflow
|
|
204
299
|
spinner.start('Executing workflow...');
|
|
300
|
+
// Debug: Show execution start
|
|
301
|
+
if (options.debug) {
|
|
302
|
+
console.log(chalk.gray('\n🐛 Debug: Starting Workflow Execution'));
|
|
303
|
+
console.log(chalk.gray(` Workflow: ${workflow.metadata.name}`));
|
|
304
|
+
console.log(chalk.gray(` Steps to execute: ${workflow.steps.length}`));
|
|
305
|
+
}
|
|
306
|
+
// Track which steps we've logged to avoid duplicate output on retries
|
|
307
|
+
const loggedSteps = new Set();
|
|
205
308
|
const engine = new WorkflowEngine({}, {
|
|
206
309
|
onStepStart: (step) => {
|
|
207
|
-
if (options.verbose) {
|
|
310
|
+
if (options.verbose || options.debug) {
|
|
208
311
|
spinner.text = `Executing: ${step.id}`;
|
|
209
312
|
}
|
|
313
|
+
// Only log step start once (not on retries)
|
|
314
|
+
if (options.debug && !loggedSteps.has(step.id)) {
|
|
315
|
+
console.log(chalk.gray(`\n🐛 Debug: Step Start - ${step.id}`));
|
|
316
|
+
console.log(chalk.gray(` Action: ${step.action || 'N/A'}`));
|
|
317
|
+
if (step.inputs) {
|
|
318
|
+
console.log(chalk.gray(` Inputs: ${JSON.stringify(step.inputs, null, 2).split('\n').join('\n ')}`));
|
|
319
|
+
}
|
|
320
|
+
loggedSteps.add(step.id);
|
|
321
|
+
}
|
|
210
322
|
},
|
|
211
323
|
onStepComplete: (step, result) => {
|
|
212
|
-
if (options.verbose) {
|
|
324
|
+
if (options.verbose || options.debug) {
|
|
213
325
|
const icon = result.status === StepStatus.COMPLETED ? '✓' : '✗';
|
|
214
326
|
console.log(` ${icon} ${step.id}: ${result.status}`);
|
|
215
327
|
}
|
|
328
|
+
if (options.debug) {
|
|
329
|
+
console.log(chalk.gray(`\n🐛 Debug: Step Complete - ${step.id}`));
|
|
330
|
+
console.log(chalk.gray(` Status: ${result.status}`));
|
|
331
|
+
console.log(chalk.gray(` Duration: ${result.duration}ms`));
|
|
332
|
+
// Show output variable name if set
|
|
333
|
+
if (step.outputVariable) {
|
|
334
|
+
console.log(chalk.gray(` Output Variable: ${step.outputVariable}`));
|
|
335
|
+
}
|
|
336
|
+
// Show full output in debug mode (not truncated)
|
|
337
|
+
if (result.output !== undefined) {
|
|
338
|
+
let outputStr;
|
|
339
|
+
if (typeof result.output === 'string') {
|
|
340
|
+
outputStr = result.output;
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
outputStr = JSON.stringify(result.output, null, 2);
|
|
344
|
+
}
|
|
345
|
+
// Split into lines and indent each line
|
|
346
|
+
const lines = outputStr.split('\n');
|
|
347
|
+
if (lines.length > 50) {
|
|
348
|
+
// If output is very large (>50 lines), show first 40 and last 5 lines
|
|
349
|
+
console.log(chalk.gray(` Output (${lines.length} lines):`));
|
|
350
|
+
lines.slice(0, 40).forEach(line => console.log(chalk.gray(` ${line}`)));
|
|
351
|
+
console.log(chalk.gray(` ... (${lines.length - 45} lines omitted) ...`));
|
|
352
|
+
lines.slice(-5).forEach(line => console.log(chalk.gray(` ${line}`)));
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
console.log(chalk.gray(` Output:`));
|
|
356
|
+
lines.forEach(line => console.log(chalk.gray(` ${line}`)));
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
if (result.error) {
|
|
360
|
+
console.log(chalk.red(` Error: ${result.error}`));
|
|
361
|
+
}
|
|
362
|
+
}
|
|
216
363
|
},
|
|
217
364
|
});
|
|
218
365
|
const registry = new SDKRegistry();
|
|
219
366
|
registerIntegrations(registry);
|
|
220
367
|
registry.registerTools(workflow.tools);
|
|
368
|
+
// Debug: Show registered tools
|
|
369
|
+
if (options.debug) {
|
|
370
|
+
console.log(chalk.gray('\n🐛 Debug: SDK Registry'));
|
|
371
|
+
console.log(chalk.gray(` Registered tools: ${Object.keys(workflow.tools).join(', ')}`));
|
|
372
|
+
}
|
|
221
373
|
const result = await engine.execute(workflow, inputs, registry, createSDKStepExecutor());
|
|
222
374
|
if (result.status === WorkflowStatus.COMPLETED) {
|
|
223
375
|
spinner.succeed(`Workflow completed in ${result.duration}ms`);
|
|
224
376
|
}
|
|
225
377
|
else {
|
|
226
378
|
spinner.fail(`Workflow failed: ${result.error}`);
|
|
379
|
+
// Debug: Show detailed error information
|
|
380
|
+
if (options.debug) {
|
|
381
|
+
console.log(chalk.red('\n🐛 Debug: Failure Details'));
|
|
382
|
+
console.log(chalk.red(` Error: ${result.error}`));
|
|
383
|
+
// Find the failed step
|
|
384
|
+
const failedStep = result.stepResults.find(s => s.status === StepStatus.FAILED);
|
|
385
|
+
if (failedStep) {
|
|
386
|
+
console.log(chalk.red(` Failed Step: ${failedStep.stepId}`));
|
|
387
|
+
console.log(chalk.red(` Step Duration: ${failedStep.duration}ms`));
|
|
388
|
+
if (failedStep.error) {
|
|
389
|
+
console.log(chalk.red(` Step Error: ${failedStep.error}`));
|
|
390
|
+
// Extract detailed error information
|
|
391
|
+
const errorObj = typeof failedStep.error === 'object' ? failedStep.error : null;
|
|
392
|
+
if (errorObj) {
|
|
393
|
+
// HTTP error details (Axios, fetch, etc.)
|
|
394
|
+
if (errorObj.response) {
|
|
395
|
+
console.log(chalk.red('\n HTTP Error Details:'));
|
|
396
|
+
console.log(chalk.red(` Status: ${errorObj.response.status} ${errorObj.response.statusText || ''}`));
|
|
397
|
+
if (errorObj.config?.url) {
|
|
398
|
+
console.log(chalk.red(` URL: ${errorObj.config.method?.toUpperCase() || 'GET'} ${errorObj.config.url}`));
|
|
399
|
+
}
|
|
400
|
+
if (errorObj.response.data) {
|
|
401
|
+
console.log(chalk.red(` Response Body:`));
|
|
402
|
+
try {
|
|
403
|
+
const responseStr = typeof errorObj.response.data === 'string'
|
|
404
|
+
? errorObj.response.data
|
|
405
|
+
: JSON.stringify(errorObj.response.data, null, 2);
|
|
406
|
+
const lines = responseStr.split('\n').slice(0, 20); // First 20 lines
|
|
407
|
+
lines.forEach((line) => console.log(chalk.red(` ${line}`)));
|
|
408
|
+
if (responseStr.split('\n').length > 20) {
|
|
409
|
+
console.log(chalk.red(` ... (truncated)`));
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
catch {
|
|
413
|
+
console.log(chalk.red(` [Unable to serialize response]`));
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
if (errorObj.response.headers) {
|
|
417
|
+
console.log(chalk.red(` Response Headers:`));
|
|
418
|
+
const headers = errorObj.response.headers;
|
|
419
|
+
Object.keys(headers).slice(0, 10).forEach((key) => {
|
|
420
|
+
console.log(chalk.red(` ${key}: ${headers[key]}`));
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
// Request details
|
|
425
|
+
if (errorObj.config && !errorObj.response) {
|
|
426
|
+
console.log(chalk.red('\n Request Details:'));
|
|
427
|
+
if (errorObj.config.url) {
|
|
428
|
+
console.log(chalk.red(` URL: ${errorObj.config.method?.toUpperCase() || 'GET'} ${errorObj.config.url}`));
|
|
429
|
+
}
|
|
430
|
+
if (errorObj.config.baseURL) {
|
|
431
|
+
console.log(chalk.red(` Base URL: ${errorObj.config.baseURL}`));
|
|
432
|
+
}
|
|
433
|
+
if (errorObj.code) {
|
|
434
|
+
console.log(chalk.red(` Error Code: ${errorObj.code}`));
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
// Stack trace
|
|
438
|
+
if (errorObj.stack) {
|
|
439
|
+
console.log(chalk.red('\n Stack Trace:'));
|
|
440
|
+
const stack = errorObj.stack.split('\n').slice(0, 15); // First 15 lines
|
|
441
|
+
stack.forEach((line) => console.log(chalk.red(` ${line}`)));
|
|
442
|
+
if (errorObj.stack.split('\n').length > 15) {
|
|
443
|
+
console.log(chalk.red(` ... (truncated)`));
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
if (failedStep.output) {
|
|
449
|
+
console.log(chalk.red(` Output: ${JSON.stringify(failedStep.output, null, 2)}`));
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
// Show context from previous steps
|
|
453
|
+
console.log(chalk.yellow('\n🐛 Debug: Execution Context'));
|
|
454
|
+
console.log(chalk.yellow(` Total steps executed: ${result.stepResults.length}`));
|
|
455
|
+
console.log(chalk.yellow(` Steps before failure:`));
|
|
456
|
+
result.stepResults.slice(0, -1).forEach(stepResult => {
|
|
457
|
+
console.log(chalk.yellow(` - ${stepResult.stepId}: ${stepResult.status}`));
|
|
458
|
+
});
|
|
459
|
+
}
|
|
227
460
|
process.exit(1);
|
|
228
461
|
}
|
|
229
462
|
// Show summary
|
|
@@ -235,9 +468,39 @@ program
|
|
|
235
468
|
const failed = result.stepResults.filter((s) => s.status === StepStatus.FAILED).length;
|
|
236
469
|
const skipped = result.stepResults.filter((s) => s.status === StepStatus.SKIPPED).length;
|
|
237
470
|
console.log(` Completed: ${completed}, Failed: ${failed}, Skipped: ${skipped}`);
|
|
471
|
+
// Exit successfully to avoid hanging due to open SDK connections
|
|
472
|
+
process.exit(0);
|
|
238
473
|
}
|
|
239
474
|
catch (error) {
|
|
240
475
|
spinner.fail(`Execution failed: ${error}`);
|
|
476
|
+
// Debug: Show full error details with stack trace
|
|
477
|
+
if (options.debug) {
|
|
478
|
+
console.log(chalk.red('\n🐛 Debug: Unhandled Error Details'));
|
|
479
|
+
console.log(chalk.red(` Error Type: ${error instanceof Error ? error.constructor.name : typeof error}`));
|
|
480
|
+
console.log(chalk.red(` Error Message: ${error instanceof Error ? error.message : String(error)}`));
|
|
481
|
+
if (error instanceof Error && error.stack) {
|
|
482
|
+
console.log(chalk.red('\n Stack Trace:'));
|
|
483
|
+
error.stack.split('\n').forEach(line => {
|
|
484
|
+
console.log(chalk.red(` ${line}`));
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
// Show error properties if available
|
|
488
|
+
if (error && typeof error === 'object') {
|
|
489
|
+
const errorObj = error;
|
|
490
|
+
const keys = Object.keys(errorObj).filter(k => k !== 'stack' && k !== 'message');
|
|
491
|
+
if (keys.length > 0) {
|
|
492
|
+
console.log(chalk.red('\n Additional Error Properties:'));
|
|
493
|
+
keys.forEach(key => {
|
|
494
|
+
try {
|
|
495
|
+
console.log(chalk.red(` ${key}: ${JSON.stringify(errorObj[key], null, 2)}`));
|
|
496
|
+
}
|
|
497
|
+
catch {
|
|
498
|
+
console.log(chalk.red(` ${key}: [Unable to serialize]`));
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
241
504
|
process.exit(1);
|
|
242
505
|
}
|
|
243
506
|
});
|
|
@@ -247,6 +510,8 @@ program
|
|
|
247
510
|
.description('Debug a workflow with step-by-step execution')
|
|
248
511
|
.option('-i, --input <key=value...>', 'Input parameters')
|
|
249
512
|
.option('-b, --breakpoint <stepId...>', 'Set breakpoints at step IDs')
|
|
513
|
+
.option('-a, --agent <provider>', 'AI agent provider (claude-code, claude-agent, github-copilot, opencode, ollama, codex)')
|
|
514
|
+
.option('-m, --model <name>', 'Model name to use (e.g., claude-sonnet-4, gpt-4, etc.)')
|
|
250
515
|
.option('--auto-start', 'Start without initial prompt')
|
|
251
516
|
.action(async (workflowPath, options) => {
|
|
252
517
|
const spinner = ora('Loading workflow for debugging...').start();
|
|
@@ -271,13 +536,24 @@ program
|
|
|
271
536
|
else {
|
|
272
537
|
spinner.succeed(`Loaded: ${workflow.metadata.name}`);
|
|
273
538
|
}
|
|
274
|
-
// Parse inputs
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
539
|
+
// Parse and validate inputs
|
|
540
|
+
const parsedInputs = parseInputPairs(options.input);
|
|
541
|
+
const validation = validateAndApplyDefaults(workflow, parsedInputs);
|
|
542
|
+
if (!validation.valid) {
|
|
543
|
+
spinner.fail('Missing required inputs');
|
|
544
|
+
printMissingInputsError(workflow, validation.missingInputs, 'debug', workflowPath);
|
|
545
|
+
process.exit(1);
|
|
546
|
+
}
|
|
547
|
+
const inputs = validation.inputs;
|
|
548
|
+
// Override AI agent if specified
|
|
549
|
+
if (options.agent) {
|
|
550
|
+
const sdkName = getAgentSDKName(options.agent);
|
|
551
|
+
const authConfig = getAgentAuthConfig(sdkName);
|
|
552
|
+
overrideAgentInWorkflow(workflow, sdkName, authConfig);
|
|
553
|
+
}
|
|
554
|
+
// Override model if specified
|
|
555
|
+
if (options.model) {
|
|
556
|
+
overrideModelInWorkflow(workflow, options.model);
|
|
281
557
|
}
|
|
282
558
|
// Parse breakpoints
|
|
283
559
|
const breakpoints = options.breakpoint ? parseBreakpoints(options.breakpoint) : [];
|
|
@@ -496,13 +772,14 @@ bundleCmd
|
|
|
496
772
|
}
|
|
497
773
|
const bundle = new WorkflowBundle(path);
|
|
498
774
|
const workflow = await bundle.loadWorkflowWithBundleTools();
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
775
|
+
// Parse and validate inputs
|
|
776
|
+
const parsedInputs = parseInputPairs(options.input);
|
|
777
|
+
const validation = validateAndApplyDefaults(workflow, parsedInputs);
|
|
778
|
+
if (!validation.valid) {
|
|
779
|
+
printMissingInputsError(workflow, validation.missingInputs, 'bundle run', path);
|
|
780
|
+
process.exit(1);
|
|
505
781
|
}
|
|
782
|
+
const inputs = validation.inputs;
|
|
506
783
|
const engine = new WorkflowEngine();
|
|
507
784
|
const registry = new SDKRegistry();
|
|
508
785
|
registerIntegrations(registry);
|
|
@@ -534,6 +811,7 @@ program
|
|
|
534
811
|
.option('--client-id <id>', 'OAuth client ID')
|
|
535
812
|
.option('--client-secret <secret>', 'OAuth client secret')
|
|
536
813
|
.option('--tenant-id <tenant>', 'Microsoft tenant ID (for Outlook)')
|
|
814
|
+
.option('--port <port>', 'Port for OAuth callback server (default: 8484)', '8484')
|
|
537
815
|
.action(async (service, options) => {
|
|
538
816
|
const serviceLower = service.toLowerCase();
|
|
539
817
|
console.log(chalk.bold(`Connecting ${service}...`));
|
|
@@ -541,6 +819,7 @@ program
|
|
|
541
819
|
if (serviceLower === 'gmail') {
|
|
542
820
|
const clientId = options.clientId ?? process.env.GOOGLE_CLIENT_ID;
|
|
543
821
|
const clientSecret = options.clientSecret ?? process.env.GOOGLE_CLIENT_SECRET;
|
|
822
|
+
const port = parseInt(options.port, 10);
|
|
544
823
|
if (!clientId || !clientSecret) {
|
|
545
824
|
console.log(chalk.yellow('\nGmail OAuth requires client credentials.'));
|
|
546
825
|
console.log('\nTo connect Gmail:');
|
|
@@ -554,7 +833,7 @@ program
|
|
|
554
833
|
}
|
|
555
834
|
try {
|
|
556
835
|
const { runGmailOAuth } = await import('./oauth.js');
|
|
557
|
-
const tokens = await runGmailOAuth({ clientId, clientSecret });
|
|
836
|
+
const tokens = await runGmailOAuth({ clientId, clientSecret, port });
|
|
558
837
|
console.log(chalk.green('\nGmail connected successfully!'));
|
|
559
838
|
console.log(chalk.dim(`Access token expires: ${tokens.expires_at ? new Date(tokens.expires_at).toISOString() : 'unknown'}`));
|
|
560
839
|
console.log('\nYou can now use Gmail in your workflows:');
|
|
@@ -564,25 +843,77 @@ program
|
|
|
564
843
|
auth:
|
|
565
844
|
client_id: "\${GOOGLE_CLIENT_ID}"
|
|
566
845
|
client_secret: "\${GOOGLE_CLIENT_SECRET}"
|
|
567
|
-
redirect_uri: "http://localhost
|
|
846
|
+
redirect_uri: "http://localhost:${port}/callback"
|
|
568
847
|
refresh_token: "\${GMAIL_REFRESH_TOKEN}"`));
|
|
848
|
+
process.exit(0);
|
|
849
|
+
}
|
|
850
|
+
catch (error) {
|
|
851
|
+
console.log(chalk.red(`\nOAuth failed: ${error}`));
|
|
852
|
+
process.exit(1);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
// Handle other Google services (Drive, Sheets, Calendar, Docs, Workspace)
|
|
856
|
+
if (serviceLower === 'google-drive' ||
|
|
857
|
+
serviceLower === 'drive' ||
|
|
858
|
+
serviceLower === 'google-sheets' ||
|
|
859
|
+
serviceLower === 'sheets' ||
|
|
860
|
+
serviceLower === 'google-calendar' ||
|
|
861
|
+
serviceLower === 'calendar' ||
|
|
862
|
+
serviceLower === 'google-docs' ||
|
|
863
|
+
serviceLower === 'docs' ||
|
|
864
|
+
serviceLower === 'google-workspace' ||
|
|
865
|
+
serviceLower === 'workspace') {
|
|
866
|
+
const clientId = options.clientId ?? process.env.GOOGLE_CLIENT_ID;
|
|
867
|
+
const clientSecret = options.clientSecret ?? process.env.GOOGLE_CLIENT_SECRET;
|
|
868
|
+
const port = parseInt(options.port, 10);
|
|
869
|
+
if (!clientId || !clientSecret) {
|
|
870
|
+
console.log(chalk.yellow('\nGoogle OAuth requires client credentials.'));
|
|
871
|
+
console.log('\nTo connect Google services:');
|
|
872
|
+
console.log(' 1. Go to https://console.cloud.google.com/');
|
|
873
|
+
console.log(' 2. Enable the API for your service (Drive, Sheets, etc.)');
|
|
874
|
+
console.log(' 3. Create OAuth 2.0 credentials (Desktop app type)');
|
|
875
|
+
console.log(` 4. Run: marktoflow connect ${service} --client-id YOUR_ID --client-secret YOUR_SECRET`);
|
|
876
|
+
console.log('\nOr set environment variables:');
|
|
877
|
+
console.log(' export GOOGLE_CLIENT_ID="your-client-id"');
|
|
878
|
+
console.log(' export GOOGLE_CLIENT_SECRET="your-client-secret"');
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
try {
|
|
882
|
+
const { runGoogleOAuth } = await import('./oauth.js');
|
|
883
|
+
const tokens = await runGoogleOAuth(serviceLower, { clientId, clientSecret, port });
|
|
884
|
+
console.log(chalk.dim(`Access token expires: ${tokens.expires_at ? new Date(tokens.expires_at).toISOString() : 'unknown'}`));
|
|
885
|
+
// Normalize service name for display
|
|
886
|
+
const normalizedService = serviceLower.startsWith('google-')
|
|
887
|
+
? serviceLower
|
|
888
|
+
: `google-${serviceLower}`;
|
|
889
|
+
console.log('\nYou can now use this service in your workflows:');
|
|
890
|
+
console.log(chalk.cyan(` tools:
|
|
891
|
+
${serviceLower.replace('google-', '')}:
|
|
892
|
+
sdk: "${normalizedService}"
|
|
893
|
+
auth:
|
|
894
|
+
client_id: "\${GOOGLE_CLIENT_ID}"
|
|
895
|
+
client_secret: "\${GOOGLE_CLIENT_SECRET}"
|
|
896
|
+
redirect_uri: "http://localhost:${port}/callback"
|
|
897
|
+
refresh_token: "\${GOOGLE_REFRESH_TOKEN}"
|
|
898
|
+
access_token: "\${GOOGLE_ACCESS_TOKEN}"`));
|
|
899
|
+
process.exit(0);
|
|
569
900
|
}
|
|
570
901
|
catch (error) {
|
|
571
902
|
console.log(chalk.red(`\nOAuth failed: ${error}`));
|
|
572
903
|
process.exit(1);
|
|
573
904
|
}
|
|
574
|
-
return;
|
|
575
905
|
}
|
|
576
906
|
if (serviceLower === 'outlook' || serviceLower === 'microsoft') {
|
|
577
907
|
const clientId = options.clientId ?? process.env.MICROSOFT_CLIENT_ID;
|
|
578
908
|
const clientSecret = options.clientSecret ?? process.env.MICROSOFT_CLIENT_SECRET;
|
|
579
909
|
const tenantId = options.tenantId ?? process.env.MICROSOFT_TENANT_ID;
|
|
910
|
+
const port = parseInt(options.port, 10);
|
|
580
911
|
if (!clientId) {
|
|
581
912
|
console.log(chalk.yellow('\nOutlook OAuth requires a client ID.'));
|
|
582
913
|
console.log('\nTo connect Outlook/Microsoft Graph:');
|
|
583
914
|
console.log(' 1. Go to https://portal.azure.com/');
|
|
584
915
|
console.log(' 2. Register an application in Azure AD');
|
|
585
|
-
console.log(
|
|
916
|
+
console.log(` 3. Add redirect URI: http://localhost:${port}/callback`);
|
|
586
917
|
console.log(' 4. Grant Mail.Read, Mail.Send, Calendars.ReadWrite permissions');
|
|
587
918
|
console.log(' 5. Run: marktoflow connect outlook --client-id YOUR_ID');
|
|
588
919
|
console.log('\nOr set environment variables:');
|
|
@@ -593,7 +924,7 @@ program
|
|
|
593
924
|
}
|
|
594
925
|
try {
|
|
595
926
|
const { runOutlookOAuth } = await import('./oauth.js');
|
|
596
|
-
const tokens = await runOutlookOAuth({ clientId, clientSecret, tenantId });
|
|
927
|
+
const tokens = await runOutlookOAuth({ clientId, clientSecret, tenantId, port });
|
|
597
928
|
console.log(chalk.green('\nOutlook connected successfully!'));
|
|
598
929
|
console.log(chalk.dim(`Access token expires: ${tokens.expires_at ? new Date(tokens.expires_at).toISOString() : 'unknown'}`));
|
|
599
930
|
console.log('\nYou can now use Outlook in your workflows:');
|
|
@@ -602,12 +933,12 @@ program
|
|
|
602
933
|
sdk: "@microsoft/microsoft-graph-client"
|
|
603
934
|
auth:
|
|
604
935
|
token: "\${OUTLOOK_ACCESS_TOKEN}"`));
|
|
936
|
+
process.exit(0);
|
|
605
937
|
}
|
|
606
938
|
catch (error) {
|
|
607
939
|
console.log(chalk.red(`\nOAuth failed: ${error}`));
|
|
608
940
|
process.exit(1);
|
|
609
941
|
}
|
|
610
|
-
return;
|
|
611
942
|
}
|
|
612
943
|
// Other services - show manual setup instructions
|
|
613
944
|
console.log('\nManual setup required. Set environment variables:');
|
|
@@ -663,6 +994,7 @@ program
|
|
|
663
994
|
console.log('\n' + chalk.bold('Available services:'));
|
|
664
995
|
console.log(' Communication: slack, discord');
|
|
665
996
|
console.log(' Email: gmail, outlook');
|
|
997
|
+
console.log(' Google Workspace: google-drive, google-sheets, google-calendar, google-docs, google-workspace');
|
|
666
998
|
console.log(' Project management: jira, linear');
|
|
667
999
|
console.log(' Documentation: notion, confluence');
|
|
668
1000
|
console.log(' Developer: github');
|