@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 CHANGED
@@ -2,11 +2,25 @@
2
2
 
3
3
  > **Author:** Scott Glover <scottgl@gmail.com>
4
4
 
5
- Agent automation framework with native MCP support - Command Line Interface.
5
+ **CLI-first agent automation framework** with native MCP support and direct SDK integration.
6
6
 
7
- ## Overview
7
+ **Version:** 2.0.0-alpha.13
8
8
 
9
- `@marktoflow/cli` is the main package for marktoflow, providing the command-line interface for creating, running, and managing automation workflows.
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:scottgl9/marktoflow#main
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
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * marktoflow CLI
4
4
  *
5
- * Universal automation framework with native MCP support.
5
+ * Agent automation framework with native MCP support.
6
6
  */
7
7
  export {};
8
8
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * marktoflow CLI
4
4
  *
5
- * Universal automation framework with native MCP support.
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
- const VERSION = '2.0.0-alpha.7';
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('Universal automation framework with native MCP support')
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: console.log
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 inputs = {};
183
- if (options.input) {
184
- for (const pair of options.input) {
185
- const [key, value] = pair.split('=');
186
- inputs[key] = value;
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 inputs = {};
276
- if (options.input) {
277
- for (const pair of options.input) {
278
- const [key, value] = pair.split('=');
279
- inputs[key] = value;
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
- const inputs = {};
500
- if (options.input) {
501
- for (const pair of options.input) {
502
- const [key, value] = pair.split('=');
503
- inputs[key] = value;
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:8484/callback"
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(' 3. Add redirect URI: http://localhost:8484/callback');
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');