@marktoflow/cli 2.0.3 → 2.0.4

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.
Files changed (66) hide show
  1. package/README.md +48 -5
  2. package/dist/commands/agent.d.ts +6 -0
  3. package/dist/commands/agent.d.ts.map +1 -0
  4. package/dist/commands/agent.js +56 -0
  5. package/dist/commands/agent.js.map +1 -0
  6. package/dist/commands/bundle.d.ts +11 -0
  7. package/dist/commands/bundle.d.ts.map +1 -0
  8. package/dist/commands/bundle.js +94 -0
  9. package/dist/commands/bundle.js.map +1 -0
  10. package/dist/commands/connect.d.ts +11 -0
  11. package/dist/commands/connect.d.ts.map +1 -0
  12. package/dist/commands/connect.js +203 -0
  13. package/dist/commands/connect.js.map +1 -0
  14. package/dist/commands/credentials.d.ts +16 -0
  15. package/dist/commands/credentials.d.ts.map +1 -0
  16. package/dist/commands/credentials.js +94 -0
  17. package/dist/commands/credentials.js.map +1 -0
  18. package/dist/commands/doctor.d.ts +5 -0
  19. package/dist/commands/doctor.d.ts.map +1 -0
  20. package/dist/commands/doctor.js +60 -0
  21. package/dist/commands/doctor.js.map +1 -0
  22. package/dist/commands/gui.d.ts +11 -0
  23. package/dist/commands/gui.d.ts.map +1 -0
  24. package/dist/commands/gui.js +55 -0
  25. package/dist/commands/gui.js.map +1 -0
  26. package/dist/commands/init.d.ts +8 -0
  27. package/dist/commands/init.d.ts.map +1 -0
  28. package/dist/commands/init.js +79 -0
  29. package/dist/commands/init.js.map +1 -0
  30. package/dist/commands/run.d.ts +13 -0
  31. package/dist/commands/run.d.ts.map +1 -0
  32. package/dist/commands/run.js +311 -0
  33. package/dist/commands/run.js.map +1 -0
  34. package/dist/commands/schedule.d.ts +5 -0
  35. package/dist/commands/schedule.d.ts.map +1 -0
  36. package/dist/commands/schedule.js +19 -0
  37. package/dist/commands/schedule.js.map +1 -0
  38. package/dist/commands/template.d.ts +5 -0
  39. package/dist/commands/template.d.ts.map +1 -0
  40. package/dist/commands/template.js +18 -0
  41. package/dist/commands/template.js.map +1 -0
  42. package/dist/commands/tools.d.ts +5 -0
  43. package/dist/commands/tools.d.ts.map +1 -0
  44. package/dist/commands/tools.js +28 -0
  45. package/dist/commands/tools.js.map +1 -0
  46. package/dist/commands/workflow.d.ts +5 -0
  47. package/dist/commands/workflow.d.ts.map +1 -0
  48. package/dist/commands/workflow.js +31 -0
  49. package/dist/commands/workflow.js.map +1 -0
  50. package/dist/i18n.d.ts +17 -0
  51. package/dist/i18n.d.ts.map +1 -0
  52. package/dist/i18n.js +70 -0
  53. package/dist/i18n.js.map +1 -0
  54. package/dist/index.d.ts +1 -0
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +49 -1040
  57. package/dist/index.js.map +1 -1
  58. package/dist/utils/agent-config.d.ts +12 -0
  59. package/dist/utils/agent-config.d.ts.map +1 -0
  60. package/dist/utils/agent-config.js +52 -0
  61. package/dist/utils/agent-config.js.map +1 -0
  62. package/dist/utils/index.d.ts +1 -0
  63. package/dist/utils/index.d.ts.map +1 -1
  64. package/dist/utils/index.js +1 -0
  65. package/dist/utils/index.js.map +1 -1
  66. package/package.json +33 -8
package/dist/index.js CHANGED
@@ -3,91 +3,49 @@
3
3
  * marktoflow CLI
4
4
  *
5
5
  * Agent automation framework with native MCP support.
6
+ * This file is the thin routing layer — all command logic lives in ./commands/.
6
7
  */
7
8
  import { Command } from 'commander';
8
- import chalk from 'chalk';
9
- import ora from 'ora';
10
- import { existsSync, mkdirSync, writeFileSync, readdirSync, statSync, readFileSync } from 'node:fs';
11
- import { join } from 'node:path';
12
- import { parseFile, WorkflowEngine, SDKRegistry, createSDKStepExecutor, StepStatus, WorkflowStatus, loadEnv, ToolRegistry, WorkflowBundle, Scheduler, TemplateRegistry, loadConfig, createCredentialManager, getAvailableBackends, EncryptionBackend, StateStore, } from '@marktoflow/core';
13
- import { registerIntegrations } from '@marktoflow/integrations';
9
+ import { join, dirname } from 'node:path';
10
+ import { readFileSync } from 'node:fs';
11
+ import { fileURLToPath } from 'node:url';
12
+ import { loadEnv } from '@marktoflow/core';
14
13
  import { workerCommand } from './worker.js';
15
14
  import { triggerCommand } from './trigger.js';
16
15
  import { serveCommand } from './serve.js';
16
+ // Commands
17
+ import { executeInit } from './commands/init.js';
17
18
  import { runWorkflowWizard, listTemplates } from './commands/new.js';
18
19
  import { runUpdateWizard, listAgents } from './commands/update.js';
19
- import { parse as parseYaml } from 'yaml';
20
- import { executeDryRun, displayDryRunSummary } from './commands/dry-run.js';
20
+ import { executeRun } from './commands/run.js';
21
+ import { executeWorkflowList } from './commands/workflow.js';
22
+ import { executeAgentList, executeAgentInfo } from './commands/agent.js';
23
+ import { executeToolsList } from './commands/tools.js';
24
+ import { executeCredentialsList, executeCredentialsVerify } from './commands/credentials.js';
25
+ import { executeScheduleList } from './commands/schedule.js';
26
+ import { executeBundleList, executeBundleInfo, executeBundleValidate, executeBundleRun } from './commands/bundle.js';
27
+ import { executeTemplateList } from './commands/template.js';
28
+ import { executeConnect } from './commands/connect.js';
29
+ import { executeDoctor } from './commands/doctor.js';
30
+ import { executeGui } from './commands/gui.js';
21
31
  import { WorkflowDebugger, parseBreakpoints } from './commands/debug.js';
22
32
  import { executeTestConnection } from './commands/test-connection.js';
23
33
  import { executeHistory, executeHistoryDetail, executeReplay } from './commands/history.js';
24
- import { parseInputPairs, debugLogInputs, validateAndApplyDefaults, printMissingInputsError, overrideAgentInWorkflow, debugLogAgentOverride, overrideModelInWorkflow, detectAgents, detectAgent, getKnownAgentIds, } from './utils/index.js';
25
- const VERSION = '2.0.2';
34
+ // Utils used by debug command
35
+ import { parseInputPairs, validateAndApplyDefaults, printMissingInputsError, overrideAgentInWorkflow, overrideModelInWorkflow, getAgentSDKName, getAgentAuthConfig, } from './utils/index.js';
36
+ import { parseFile, SDKRegistry, createSDKStepExecutor, loadConfig, } from '@marktoflow/core';
37
+ import { registerIntegrations } from '@marktoflow/integrations';
38
+ import { existsSync } from 'node:fs';
39
+ import chalk from 'chalk';
40
+ import ora from 'ora';
41
+ // Read version from package.json
42
+ const __filename = fileURLToPath(import.meta.url);
43
+ const __dirname = dirname(__filename);
44
+ const packageJsonPath = join(__dirname, '../package.json');
45
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
46
+ const VERSION = packageJson.version;
26
47
  // Load environment variables from .env files on CLI startup
27
48
  loadEnv();
28
- function getConfig() {
29
- return loadConfig(process.cwd());
30
- }
31
- function isBundle(path) {
32
- try {
33
- const stat = existsSync(path) ? statSync(path) : null;
34
- if (!stat || !stat.isDirectory())
35
- return false;
36
- const entries = readdirSync(path);
37
- return entries.some((name) => name.endsWith('.md') && name !== 'README.md');
38
- }
39
- catch {
40
- return false;
41
- }
42
- }
43
- /**
44
- * Map agent provider aliases to SDK names
45
- */
46
- function getAgentSDKName(provider) {
47
- const providerMap = {
48
- 'claude': 'claude-agent',
49
- 'claude-agent': 'claude-agent',
50
- 'copilot': 'github-copilot',
51
- 'github-copilot': 'github-copilot',
52
- 'opencode': 'opencode',
53
- 'ollama': 'ollama',
54
- 'codex': 'codex',
55
- 'openai': 'openai',
56
- 'vllm': 'openai',
57
- 'openai-compatible': 'openai',
58
- };
59
- const normalized = provider.toLowerCase();
60
- const sdkName = providerMap[normalized];
61
- if (!sdkName) {
62
- throw new Error(`Unknown agent provider: ${provider}. Available: ${Object.keys(providerMap).join(', ')}`);
63
- }
64
- return sdkName;
65
- }
66
- /**
67
- * Get default auth configuration for an agent provider
68
- */
69
- function getAgentAuthConfig(sdkName) {
70
- const authConfigs = {
71
- 'claude-agent': {
72
- api_key: '${ANTHROPIC_API_KEY}',
73
- },
74
- 'github-copilot': {
75
- token: '${GITHUB_TOKEN}',
76
- },
77
- 'opencode': {},
78
- 'ollama': {
79
- base_url: '${OLLAMA_BASE_URL:-http://localhost:11434}',
80
- },
81
- 'codex': {
82
- api_key: '${OPENAI_API_KEY}',
83
- },
84
- 'openai': {
85
- api_key: '${OPENAI_API_KEY}',
86
- base_url: '${OPENAI_BASE_URL:-https://api.openai.com/v1}',
87
- },
88
- };
89
- return authConfigs[sdkName] || {};
90
- }
91
49
  // ============================================================================
92
50
  // CLI Setup
93
51
  // ============================================================================
@@ -107,76 +65,7 @@ program
107
65
  .command('init')
108
66
  .description('Initialize a new marktoflow project')
109
67
  .option('-f, --force', 'Overwrite existing configuration')
110
- .action(async (options) => {
111
- const spinner = ora('Initializing marktoflow project...').start();
112
- try {
113
- const configDir = '.marktoflow';
114
- const workflowsDir = join(configDir, 'workflows');
115
- const credentialsDir = join(configDir, 'credentials');
116
- if (existsSync(configDir) && !options.force) {
117
- spinner.fail('Project already initialized. Use --force to reinitialize.');
118
- return;
119
- }
120
- // Create directories
121
- mkdirSync(workflowsDir, { recursive: true });
122
- mkdirSync(credentialsDir, { recursive: true });
123
- // Create example workflow
124
- const exampleWorkflow = `---
125
- workflow:
126
- id: hello-world
127
- name: "Hello World"
128
- version: "1.0.0"
129
- description: "A simple example workflow"
130
-
131
- # Uncomment and configure to use Slack:
132
- # tools:
133
- # slack:
134
- # sdk: "@slack/web-api"
135
- # auth:
136
- # token: "\${SLACK_BOT_TOKEN}"
137
-
138
- steps:
139
- - id: greet
140
- action: core.log
141
- inputs:
142
- message: "Hello from marktoflow!"
143
- ---
144
-
145
- # Hello World Workflow
146
-
147
- This is a simple example workflow.
148
-
149
- ## Step 1: Greet
150
-
151
- Outputs a greeting message.
152
- `;
153
- writeFileSync(join(workflowsDir, 'hello-world.md'), exampleWorkflow);
154
- // Create .gitignore for credentials
155
- writeFileSync(join(credentialsDir, '.gitignore'), '# Ignore all credentials\n*\n!.gitignore\n');
156
- spinner.succeed('Project initialized successfully!');
157
- // Auto-detect agents
158
- const agents = detectAgents();
159
- const available = agents.filter((a) => a.available);
160
- if (available.length > 0) {
161
- console.log('\n' + chalk.bold('Detected agents:'));
162
- for (const agent of available) {
163
- const methodLabel = agent.method === 'cli' ? 'CLI' : agent.method === 'env' ? 'env' : 'server';
164
- console.log(` ${chalk.green('✓')} ${agent.name} (${methodLabel})`);
165
- }
166
- }
167
- console.log('\n' + chalk.bold('Next steps:'));
168
- console.log(` 1. Edit ${chalk.cyan('.marktoflow/workflows/hello-world.md')}`);
169
- console.log(` 2. Run ${chalk.cyan('marktoflow run hello-world.md')}`);
170
- console.log(` 3. Connect services: ${chalk.cyan('marktoflow connect slack')}`);
171
- if (available.length === 0) {
172
- console.log(` 4. Set up an AI agent: ${chalk.cyan('marktoflow agent list')}`);
173
- }
174
- }
175
- catch (error) {
176
- spinner.fail(`Initialization failed: ${error}`);
177
- process.exit(1);
178
- }
179
- });
68
+ .action(async (options) => executeInit(options));
180
69
  // --- new ---
181
70
  program
182
71
  .command('new')
@@ -224,314 +113,7 @@ program
224
113
  .option('-a, --agent <provider>', 'AI agent provider (claude-agent, openai, github-copilot, opencode, ollama, codex)')
225
114
  .option('-m, --model <name>', 'Model name to use (e.g., claude-sonnet-4, gpt-4, etc.)')
226
115
  .option('--dry-run', 'Parse workflow without executing')
227
- .action(async (workflowPath, options) => {
228
- const spinner = ora('Loading workflow...').start();
229
- let stateStore;
230
- try {
231
- const config = getConfig();
232
- const workflowsDir = config.workflows?.path ?? '.marktoflow/workflows';
233
- // Resolve workflow path
234
- let resolvedPath = workflowPath;
235
- if (!existsSync(resolvedPath)) {
236
- resolvedPath = join(workflowsDir, workflowPath);
237
- }
238
- if (!existsSync(resolvedPath)) {
239
- spinner.fail(`Workflow not found: ${workflowPath}`);
240
- process.exit(1);
241
- }
242
- // Parse workflow
243
- const { workflow, warnings } = await parseFile(resolvedPath);
244
- if (warnings.length > 0) {
245
- spinner.warn('Workflow parsed with warnings:');
246
- warnings.forEach((w) => console.log(chalk.yellow(` - ${w}`)));
247
- }
248
- else {
249
- spinner.succeed(`Loaded: ${workflow.metadata.name}`);
250
- }
251
- // Debug: Show workflow details
252
- if (options.debug) {
253
- console.log(chalk.gray('\n🐛 Debug: Workflow Details'));
254
- console.log(chalk.gray(` ID: ${workflow.metadata.id}`));
255
- console.log(chalk.gray(` Version: ${workflow.metadata.version}`));
256
- console.log(chalk.gray(` Steps: ${workflow.steps.length}`));
257
- console.log(chalk.gray(` Tools: ${Object.keys(workflow.tools).join(', ') || 'none'}`));
258
- console.log(chalk.gray(` Inputs Required: ${Object.keys(workflow.inputs || {}).join(', ') || 'none'}`));
259
- }
260
- // Parse inputs
261
- const parsedInputs = parseInputPairs(options.input);
262
- // Debug: Show parsed inputs
263
- if (options.debug) {
264
- debugLogInputs(parsedInputs);
265
- }
266
- // Validate required inputs and apply defaults
267
- const validation = validateAndApplyDefaults(workflow, parsedInputs, { debug: options.debug });
268
- if (!validation.valid) {
269
- spinner.fail('Missing required inputs');
270
- printMissingInputsError(workflow, validation.missingInputs, 'run', workflowPath);
271
- process.exit(1);
272
- }
273
- const inputs = validation.inputs;
274
- // Override AI agent if specified
275
- if (options.agent) {
276
- const sdkName = getAgentSDKName(options.agent);
277
- const authConfig = getAgentAuthConfig(sdkName);
278
- const result = overrideAgentInWorkflow(workflow, sdkName, authConfig, {
279
- verbose: options.verbose,
280
- debug: options.debug,
281
- });
282
- if (options.debug) {
283
- debugLogAgentOverride(options.agent, sdkName, result.replacedCount, authConfig);
284
- }
285
- }
286
- // Override model if specified
287
- if (options.model) {
288
- const result = overrideModelInWorkflow(workflow, options.model);
289
- if (options.verbose || options.debug) {
290
- if (result.overrideCount > 0) {
291
- console.log(chalk.cyan(` Set model '${options.model}' for ${result.overrideCount} AI tool(s)`));
292
- }
293
- }
294
- if (options.debug) {
295
- console.log(chalk.gray('\n🐛 Debug: Model Override'));
296
- console.log(chalk.gray(` Model: ${options.model}`));
297
- console.log(chalk.gray(` Applied to ${result.overrideCount} AI tool(s)`));
298
- }
299
- if (result.overrideCount === 0 && (options.verbose || options.debug)) {
300
- console.log(chalk.yellow(` Warning: --model specified but no AI tools found in workflow`));
301
- }
302
- }
303
- // Handle dry-run mode
304
- if (options.dryRun) {
305
- const dryRunResult = await executeDryRun(workflow, inputs, {
306
- verbose: options.verbose,
307
- showMockData: true,
308
- showVariables: true,
309
- });
310
- displayDryRunSummary(dryRunResult, {
311
- verbose: options.verbose,
312
- showMockData: true,
313
- showVariables: true,
314
- });
315
- return;
316
- }
317
- // Execute workflow
318
- spinner.start('Executing workflow...');
319
- // Debug: Show execution start
320
- if (options.debug) {
321
- console.log(chalk.gray('\n🐛 Debug: Starting Workflow Execution'));
322
- console.log(chalk.gray(` Workflow: ${workflow.metadata.name}`));
323
- console.log(chalk.gray(` Steps to execute: ${workflow.steps.length}`));
324
- }
325
- // Track which steps we've logged to avoid duplicate output on retries
326
- const loggedSteps = new Set();
327
- // Create StateStore for execution history
328
- const stateDir = join(process.cwd(), '.marktoflow');
329
- mkdirSync(stateDir, { recursive: true });
330
- stateStore = new StateStore(join(stateDir, 'state.db'));
331
- const engine = new WorkflowEngine({}, {
332
- onStepStart: (step) => {
333
- if (options.verbose || options.debug) {
334
- spinner.text = `Executing: ${step.id}`;
335
- }
336
- // Only log step start once (not on retries)
337
- if (options.debug && !loggedSteps.has(step.id)) {
338
- console.log(chalk.gray(`\n🐛 Debug: Step Start - ${step.id}`));
339
- console.log(chalk.gray(` Action: ${step.action || 'N/A'}`));
340
- if (step.inputs) {
341
- console.log(chalk.gray(` Inputs: ${JSON.stringify(step.inputs, null, 2).split('\n').join('\n ')}`));
342
- }
343
- loggedSteps.add(step.id);
344
- }
345
- },
346
- onStepComplete: (step, result) => {
347
- if (options.verbose || options.debug) {
348
- const icon = result.status === StepStatus.COMPLETED ? '✓' : '✗';
349
- console.log(` ${icon} ${step.id}: ${result.status}`);
350
- }
351
- if (options.debug) {
352
- console.log(chalk.gray(`\n🐛 Debug: Step Complete - ${step.id}`));
353
- console.log(chalk.gray(` Status: ${result.status}`));
354
- console.log(chalk.gray(` Duration: ${result.duration}ms`));
355
- // Show output variable name if set
356
- if (step.outputVariable) {
357
- console.log(chalk.gray(` Output Variable: ${step.outputVariable}`));
358
- }
359
- // Show full output in debug mode (not truncated)
360
- if (result.output !== undefined) {
361
- let outputStr;
362
- if (typeof result.output === 'string') {
363
- outputStr = result.output;
364
- }
365
- else {
366
- outputStr = JSON.stringify(result.output, null, 2);
367
- }
368
- // Split into lines and indent each line
369
- const lines = outputStr.split('\n');
370
- if (lines.length > 50) {
371
- // If output is very large (>50 lines), show first 40 and last 5 lines
372
- console.log(chalk.gray(` Output (${lines.length} lines):`));
373
- lines.slice(0, 40).forEach(line => console.log(chalk.gray(` ${line}`)));
374
- console.log(chalk.gray(` ... (${lines.length - 45} lines omitted) ...`));
375
- lines.slice(-5).forEach(line => console.log(chalk.gray(` ${line}`)));
376
- }
377
- else {
378
- console.log(chalk.gray(` Output:`));
379
- lines.forEach(line => console.log(chalk.gray(` ${line}`)));
380
- }
381
- }
382
- if (result.error) {
383
- console.log(chalk.red(` Error: ${result.error}`));
384
- }
385
- }
386
- },
387
- }, stateStore);
388
- // Set workflow path for execution history
389
- engine.workflowPath = resolvedPath;
390
- const registry = new SDKRegistry();
391
- registerIntegrations(registry);
392
- registry.registerTools(workflow.tools);
393
- // Debug: Show registered tools
394
- if (options.debug) {
395
- console.log(chalk.gray('\n🐛 Debug: SDK Registry'));
396
- console.log(chalk.gray(` Registered tools: ${Object.keys(workflow.tools).join(', ')}`));
397
- }
398
- const result = await engine.execute(workflow, inputs, registry, createSDKStepExecutor());
399
- if (result.status === WorkflowStatus.COMPLETED) {
400
- spinner.succeed(`Workflow completed in ${result.duration}ms`);
401
- }
402
- else {
403
- spinner.fail(`Workflow failed: ${result.error}`);
404
- // Debug: Show detailed error information
405
- if (options.debug) {
406
- console.log(chalk.red('\n🐛 Debug: Failure Details'));
407
- console.log(chalk.red(` Error: ${result.error}`));
408
- // Find the failed step
409
- const failedStep = result.stepResults.find(s => s.status === StepStatus.FAILED);
410
- if (failedStep) {
411
- console.log(chalk.red(` Failed Step: ${failedStep.stepId}`));
412
- console.log(chalk.red(` Step Duration: ${failedStep.duration}ms`));
413
- if (failedStep.error) {
414
- console.log(chalk.red(` Step Error: ${failedStep.error}`));
415
- // Extract detailed error information
416
- const errorObj = typeof failedStep.error === 'object' ? failedStep.error : null;
417
- if (errorObj) {
418
- // HTTP error details (Axios, fetch, etc.)
419
- if (errorObj.response) {
420
- console.log(chalk.red('\n HTTP Error Details:'));
421
- console.log(chalk.red(` Status: ${errorObj.response.status} ${errorObj.response.statusText || ''}`));
422
- if (errorObj.config?.url) {
423
- console.log(chalk.red(` URL: ${errorObj.config.method?.toUpperCase() || 'GET'} ${errorObj.config.url}`));
424
- }
425
- if (errorObj.response.data) {
426
- console.log(chalk.red(` Response Body:`));
427
- try {
428
- const responseStr = typeof errorObj.response.data === 'string'
429
- ? errorObj.response.data
430
- : JSON.stringify(errorObj.response.data, null, 2);
431
- const lines = responseStr.split('\n').slice(0, 20); // First 20 lines
432
- lines.forEach((line) => console.log(chalk.red(` ${line}`)));
433
- if (responseStr.split('\n').length > 20) {
434
- console.log(chalk.red(` ... (truncated)`));
435
- }
436
- }
437
- catch {
438
- console.log(chalk.red(` [Unable to serialize response]`));
439
- }
440
- }
441
- if (errorObj.response.headers) {
442
- console.log(chalk.red(` Response Headers:`));
443
- const headers = errorObj.response.headers;
444
- Object.keys(headers).slice(0, 10).forEach((key) => {
445
- console.log(chalk.red(` ${key}: ${headers[key]}`));
446
- });
447
- }
448
- }
449
- // Request details
450
- if (errorObj.config && !errorObj.response) {
451
- console.log(chalk.red('\n Request Details:'));
452
- if (errorObj.config.url) {
453
- console.log(chalk.red(` URL: ${errorObj.config.method?.toUpperCase() || 'GET'} ${errorObj.config.url}`));
454
- }
455
- if (errorObj.config.baseURL) {
456
- console.log(chalk.red(` Base URL: ${errorObj.config.baseURL}`));
457
- }
458
- if (errorObj.code) {
459
- console.log(chalk.red(` Error Code: ${errorObj.code}`));
460
- }
461
- }
462
- // Stack trace
463
- if (errorObj.stack) {
464
- console.log(chalk.red('\n Stack Trace:'));
465
- const stack = errorObj.stack.split('\n').slice(0, 15); // First 15 lines
466
- stack.forEach((line) => console.log(chalk.red(` ${line}`)));
467
- if (errorObj.stack.split('\n').length > 15) {
468
- console.log(chalk.red(` ... (truncated)`));
469
- }
470
- }
471
- }
472
- }
473
- if (failedStep.output) {
474
- console.log(chalk.red(` Output: ${JSON.stringify(failedStep.output, null, 2)}`));
475
- }
476
- }
477
- // Show context from previous steps
478
- console.log(chalk.yellow('\n🐛 Debug: Execution Context'));
479
- console.log(chalk.yellow(` Total steps executed: ${result.stepResults.length}`));
480
- console.log(chalk.yellow(` Steps before failure:`));
481
- result.stepResults.slice(0, -1).forEach(stepResult => {
482
- console.log(chalk.yellow(` - ${stepResult.stepId}: ${stepResult.status}`));
483
- });
484
- }
485
- process.exit(1);
486
- }
487
- // Show summary
488
- console.log('\n' + chalk.bold('Summary:'));
489
- console.log(` Status: ${result.status}`);
490
- console.log(` Duration: ${result.duration}ms`);
491
- console.log(` Steps: ${result.stepResults.length}`);
492
- const completed = result.stepResults.filter((s) => s.status === StepStatus.COMPLETED).length;
493
- const failed = result.stepResults.filter((s) => s.status === StepStatus.FAILED).length;
494
- const skipped = result.stepResults.filter((s) => s.status === StepStatus.SKIPPED).length;
495
- console.log(` Completed: ${completed}, Failed: ${failed}, Skipped: ${skipped}`);
496
- // Close StateStore and exit successfully to avoid hanging due to open SDK connections
497
- stateStore.close();
498
- process.exit(0);
499
- }
500
- catch (error) {
501
- spinner.fail(`Execution failed: ${error}`);
502
- // Debug: Show full error details with stack trace
503
- if (options.debug) {
504
- console.log(chalk.red('\n🐛 Debug: Unhandled Error Details'));
505
- console.log(chalk.red(` Error Type: ${error instanceof Error ? error.constructor.name : typeof error}`));
506
- console.log(chalk.red(` Error Message: ${error instanceof Error ? error.message : String(error)}`));
507
- if (error instanceof Error && error.stack) {
508
- console.log(chalk.red('\n Stack Trace:'));
509
- error.stack.split('\n').forEach(line => {
510
- console.log(chalk.red(` ${line}`));
511
- });
512
- }
513
- // Show error properties if available
514
- if (error && typeof error === 'object') {
515
- const errorObj = error;
516
- const keys = Object.keys(errorObj).filter(k => k !== 'stack' && k !== 'message');
517
- if (keys.length > 0) {
518
- console.log(chalk.red('\n Additional Error Properties:'));
519
- keys.forEach(key => {
520
- try {
521
- console.log(chalk.red(` ${key}: ${JSON.stringify(errorObj[key], null, 2)}`));
522
- }
523
- catch {
524
- console.log(chalk.red(` ${key}: [Unable to serialize]`));
525
- }
526
- });
527
- }
528
- }
529
- }
530
- // Close StateStore if it was created
531
- stateStore?.close();
532
- process.exit(1);
533
- }
534
- });
116
+ .action(async (workflowPath, options) => executeRun(workflowPath, options));
535
117
  // --- debug ---
536
118
  program
537
119
  .command('debug <workflow>')
@@ -544,9 +126,8 @@ program
544
126
  .action(async (workflowPath, options) => {
545
127
  const spinner = ora('Loading workflow for debugging...').start();
546
128
  try {
547
- const config = getConfig();
129
+ const config = loadConfig(process.cwd());
548
130
  const workflowsDir = config.workflows?.path ?? '.marktoflow/workflows';
549
- // Resolve workflow path
550
131
  let resolvedPath = workflowPath;
551
132
  if (!existsSync(resolvedPath)) {
552
133
  resolvedPath = join(workflowsDir, workflowPath);
@@ -555,7 +136,6 @@ program
555
136
  spinner.fail(`Workflow not found: ${workflowPath}`);
556
137
  process.exit(1);
557
138
  }
558
- // Parse workflow
559
139
  const { workflow, warnings } = await parseFile(resolvedPath);
560
140
  if (warnings.length > 0) {
561
141
  spinner.warn('Workflow parsed with warnings:');
@@ -564,7 +144,6 @@ program
564
144
  else {
565
145
  spinner.succeed(`Loaded: ${workflow.metadata.name}`);
566
146
  }
567
- // Parse and validate inputs
568
147
  const parsedInputs = parseInputPairs(options.input);
569
148
  const validation = validateAndApplyDefaults(workflow, parsedInputs);
570
149
  if (!validation.valid) {
@@ -573,28 +152,22 @@ program
573
152
  process.exit(1);
574
153
  }
575
154
  const inputs = validation.inputs;
576
- // Override AI agent if specified
577
155
  if (options.agent) {
578
156
  const sdkName = getAgentSDKName(options.agent);
579
157
  const authConfig = getAgentAuthConfig(sdkName);
580
158
  overrideAgentInWorkflow(workflow, sdkName, authConfig);
581
159
  }
582
- // Override model if specified
583
160
  if (options.model) {
584
161
  overrideModelInWorkflow(workflow, options.model);
585
162
  }
586
- // Parse breakpoints
587
163
  const breakpoints = options.breakpoint ? parseBreakpoints(options.breakpoint) : [];
588
- // Setup SDK registry and executor
589
164
  const registry = new SDKRegistry();
590
165
  registerIntegrations(registry);
591
166
  registry.registerTools(workflow.tools);
592
- // Create debugger
593
167
  const workflowDebugger = new WorkflowDebugger(workflow, inputs, registry, createSDKStepExecutor(), {
594
168
  breakpoints,
595
169
  autoStart: options.autoStart,
596
170
  });
597
- // Start debugging session
598
171
  await workflowDebugger.debug();
599
172
  }
600
173
  catch (error) {
@@ -608,108 +181,23 @@ program
608
181
  .description('Workflow management')
609
182
  .command('list')
610
183
  .description('List available workflows')
611
- .action(async () => {
612
- const workflowsDir = getConfig().workflows?.path ?? '.marktoflow/workflows';
613
- if (!existsSync(workflowsDir)) {
614
- console.log(chalk.yellow('No workflows found. Run `marktoflow init` first.'));
615
- return;
616
- }
617
- const { readdirSync } = await import('node:fs');
618
- const files = readdirSync(workflowsDir).filter((f) => f.endsWith('.md'));
619
- if (files.length === 0) {
620
- console.log(chalk.yellow('No workflows found.'));
621
- return;
622
- }
623
- console.log(chalk.bold('Available Workflows:'));
624
- for (const file of files) {
625
- try {
626
- const { workflow } = await parseFile(join(workflowsDir, file));
627
- console.log(` ${chalk.cyan(file)}: ${workflow.metadata.name}`);
628
- }
629
- catch {
630
- console.log(` ${chalk.red(file)}: (invalid)`);
631
- }
632
- }
633
- });
184
+ .action(async () => executeWorkflowList());
634
185
  // --- agent ---
635
186
  const agentCmd = program.command('agent').description('Agent management');
636
187
  agentCmd
637
188
  .command('list')
638
189
  .description('List available agents')
639
- .action(() => {
640
- const agents = detectAgents();
641
- console.log(chalk.bold('Available Agents:\n'));
642
- for (const agent of agents) {
643
- if (agent.available) {
644
- const methodLabel = agent.method === 'cli' ? 'CLI found' : agent.method === 'env' ? 'env var set' : 'server running';
645
- console.log(` ${chalk.cyan(agent.id)} ${chalk.green('Available')} (${methodLabel})`);
646
- }
647
- else {
648
- console.log(` ${chalk.cyan(agent.id)} ${chalk.yellow('Not configured')} — ${agent.configHint}`);
649
- }
650
- }
651
- console.log(`\n Config: ${chalk.dim('.marktoflow/agents/capabilities.yaml')}`);
652
- });
190
+ .action(() => executeAgentList());
653
191
  agentCmd
654
192
  .command('info <agent>')
655
193
  .description('Show agent information')
656
- .action((agentId) => {
657
- const detected = detectAgent(agentId);
658
- if (!detected) {
659
- const known = getKnownAgentIds();
660
- console.log(chalk.red(`Unknown agent: ${agentId}`));
661
- console.log(`Known agents: ${known.join(', ')}`);
662
- process.exit(1);
663
- }
664
- console.log(chalk.bold(detected.name) + ` (${detected.id})\n`);
665
- if (detected.available) {
666
- const methodLabel = detected.method === 'cli' ? 'CLI found on PATH' : detected.method === 'env' ? 'Environment variable set' : 'Server running';
667
- console.log(` Status: ${chalk.green('Available')}`);
668
- console.log(` Method: ${methodLabel}`);
669
- }
670
- else {
671
- console.log(` Status: ${chalk.yellow('Not configured')}`);
672
- console.log(` Setup: ${detected.configHint}`);
673
- }
674
- // Show config file info if it exists
675
- const capabilitiesPath = join('.marktoflow', 'agents', 'capabilities.yaml');
676
- if (existsSync(capabilitiesPath)) {
677
- const content = readFileSync(capabilitiesPath, 'utf8');
678
- const data = parseYaml(content);
679
- const fileInfo = data?.agents?.[agentId];
680
- if (fileInfo) {
681
- console.log(`\n ${chalk.dim('From capabilities.yaml:')}`);
682
- if (fileInfo.version)
683
- console.log(` Version: ${fileInfo.version}`);
684
- if (fileInfo.provider)
685
- console.log(` Provider: ${fileInfo.provider}`);
686
- }
687
- }
688
- });
194
+ .action((agentId) => executeAgentInfo(agentId));
689
195
  // --- tools ---
690
196
  const toolsCmd = program.command('tools').description('Tool management');
691
197
  toolsCmd
692
198
  .command('list')
693
199
  .description('List available tools')
694
- .action(() => {
695
- const registryPath = getConfig().tools?.registryPath ?? join('.marktoflow', 'tools', 'registry.yaml');
696
- if (!existsSync(registryPath)) {
697
- console.log(chalk.yellow("No tool registry found. Run 'marktoflow init' first."));
698
- return;
699
- }
700
- const registry = new ToolRegistry(registryPath);
701
- const tools = registry.listTools();
702
- if (tools.length === 0) {
703
- console.log(chalk.yellow('No tools registered.'));
704
- return;
705
- }
706
- console.log(chalk.bold('Registered Tools:'));
707
- for (const toolName of tools) {
708
- const definition = registry.getDefinition(toolName);
709
- const types = definition?.implementations.map((impl) => impl.type).join(', ') ?? '';
710
- console.log(` ${chalk.cyan(toolName)} ${types ? `(${types})` : ''}`);
711
- }
712
- });
200
+ .action(() => executeToolsList());
713
201
  // --- credentials ---
714
202
  const credentialsCmd = program.command('credentials').description('Credential management');
715
203
  credentialsCmd
@@ -719,221 +207,44 @@ credentialsCmd
719
207
  .option('--backend <backend>', 'Encryption backend (aes-256-gcm, fernet, age, gpg)')
720
208
  .option('--tag <tag>', 'Filter by tag')
721
209
  .option('--show-expired', 'Include expired credentials')
722
- .action((options) => {
723
- try {
724
- const stateDir = options.stateDir;
725
- const backend = options.backend ?? undefined;
726
- const manager = createCredentialManager({ stateDir, backend });
727
- const credentials = manager.list(options.tag, options.showExpired);
728
- if (credentials.length === 0) {
729
- console.log(chalk.yellow('No credentials found.'));
730
- return;
731
- }
732
- console.log(chalk.bold(`Credentials (${credentials.length}):\n`));
733
- for (const cred of credentials) {
734
- const expired = cred.expiresAt && cred.expiresAt < new Date();
735
- const status = expired ? chalk.red(' [EXPIRED]') : '';
736
- console.log(` ${chalk.cyan(cred.name)}${status}`);
737
- console.log(` Type: ${cred.credentialType}`);
738
- if (cred.description) {
739
- console.log(` Description: ${cred.description}`);
740
- }
741
- console.log(` Created: ${cred.createdAt.toISOString()}`);
742
- console.log(` Updated: ${cred.updatedAt.toISOString()}`);
743
- if (cred.expiresAt) {
744
- console.log(` Expires: ${cred.expiresAt.toISOString()}`);
745
- }
746
- if (cred.tags.length > 0) {
747
- console.log(` Tags: ${cred.tags.join(', ')}`);
748
- }
749
- console.log();
750
- }
751
- }
752
- catch (error) {
753
- console.log(chalk.red(`Failed to list credentials: ${error}`));
754
- process.exit(1);
755
- }
756
- });
210
+ .action((options) => executeCredentialsList(options));
757
211
  credentialsCmd
758
212
  .command('verify')
759
213
  .description('Verify credential encryption is working')
760
214
  .option('--state-dir <path>', 'State directory', join('.marktoflow', 'credentials'))
761
215
  .option('--backend <backend>', 'Encryption backend (aes-256-gcm, fernet, age, gpg)')
762
- .action((options) => {
763
- try {
764
- const stateDir = options.stateDir;
765
- const backend = options.backend ?? undefined;
766
- console.log(chalk.bold('Credential Encryption Verification\n'));
767
- // Show available backends
768
- const backends = getAvailableBackends();
769
- console.log(chalk.bold('Available backends:'));
770
- for (const b of backends) {
771
- const isDefault = b === EncryptionBackend.AES_256_GCM;
772
- const marker = isDefault ? chalk.green(' (default)') : '';
773
- const selected = (backend ?? EncryptionBackend.AES_256_GCM) === b ? chalk.cyan(' <-- selected') : '';
774
- console.log(` ${chalk.cyan(b)}${marker}${selected}`);
775
- }
776
- console.log();
777
- // Test encrypt/decrypt round-trip
778
- const manager = createCredentialManager({ stateDir, backend });
779
- const testValue = `verify-test-${Date.now()}`;
780
- const testName = `__verify_test_${Date.now()}`;
781
- console.log('Testing encrypt/decrypt round-trip...');
782
- manager.set({ name: testName, value: testValue, tags: ['__test'] });
783
- const decrypted = manager.get(testName);
784
- if (decrypted === testValue) {
785
- console.log(chalk.green(' Round-trip: PASS'));
786
- }
787
- else {
788
- console.log(chalk.red(' Round-trip: FAIL'));
789
- console.log(chalk.red(` Expected: ${testValue}`));
790
- console.log(chalk.red(` Got: ${decrypted}`));
791
- process.exit(1);
792
- }
793
- // Verify stored value is encrypted (not plain text)
794
- const raw = manager.get(testName, false);
795
- if (raw !== testValue) {
796
- console.log(chalk.green(' Encryption: PASS (stored value is encrypted)'));
797
- }
798
- else {
799
- console.log(chalk.red(' Encryption: FAIL (stored value is plain text)'));
800
- process.exit(1);
801
- }
802
- // Cleanup test credential
803
- manager.delete(testName);
804
- console.log(chalk.green('\n All checks passed.'));
805
- // Show credential count
806
- const credentials = manager.list();
807
- console.log(`\n Stored credentials: ${credentials.length}`);
808
- }
809
- catch (error) {
810
- console.log(chalk.red(`Verification failed: ${error}`));
811
- process.exit(1);
812
- }
813
- });
216
+ .action((options) => executeCredentialsVerify(options));
814
217
  // --- schedule ---
815
218
  const scheduleCmd = program.command('schedule').description('Scheduler management');
816
219
  scheduleCmd
817
220
  .command('list')
818
221
  .description('List scheduled workflows')
819
- .action(() => {
820
- const scheduler = new Scheduler();
821
- const jobs = scheduler.listJobs();
822
- if (jobs.length === 0) {
823
- console.log(chalk.yellow('No scheduled workflows found.'));
824
- console.log('Add schedule triggers to your workflows to enable scheduling.');
825
- return;
826
- }
827
- console.log(chalk.bold('Scheduled Workflows:'));
828
- for (const job of jobs) {
829
- console.log(` ${chalk.cyan(job.id)} ${job.workflowPath} (${job.schedule})`);
830
- }
831
- });
222
+ .action(() => executeScheduleList());
832
223
  // --- bundle ---
833
224
  const bundleCmd = program.command('bundle').description('Workflow bundle commands');
834
225
  bundleCmd
835
226
  .command('list [path]')
836
227
  .description('List workflow bundles in a directory')
837
- .action((path = '.') => {
838
- if (!existsSync(path)) {
839
- console.log(chalk.red(`Path not found: ${path}`));
840
- process.exit(1);
841
- }
842
- const entries = readdirSync(path, { withFileTypes: true });
843
- const bundles = [];
844
- for (const entry of entries) {
845
- if (!entry.isDirectory())
846
- continue;
847
- const fullPath = join(path, entry.name);
848
- if (isBundle(fullPath))
849
- bundles.push(fullPath);
850
- }
851
- if (bundles.length === 0) {
852
- console.log(chalk.yellow(`No bundles found in ${path}`));
853
- return;
854
- }
855
- console.log(chalk.bold('Bundles:'));
856
- for (const bundlePath of bundles) {
857
- console.log(` ${chalk.cyan(bundlePath)}`);
858
- }
859
- });
228
+ .action((path) => executeBundleList(path));
860
229
  bundleCmd
861
230
  .command('info <path>')
862
231
  .description('Show information about a workflow bundle')
863
- .action(async (path) => {
864
- if (!isBundle(path)) {
865
- console.log(chalk.red(`Not a valid bundle: ${path}`));
866
- process.exit(1);
867
- }
868
- const bundle = new WorkflowBundle(path);
869
- const workflow = await bundle.loadWorkflow();
870
- const tools = bundle.loadTools().listTools();
871
- console.log(chalk.bold(`Bundle: ${bundle.name}`));
872
- console.log(` Workflow: ${workflow.metadata.name} (${workflow.metadata.id})`);
873
- console.log(` Steps: ${workflow.steps.length}`);
874
- console.log(` Tools: ${tools.length ? tools.join(', ') : 'none'}`);
875
- });
232
+ .action(async (path) => executeBundleInfo(path));
876
233
  bundleCmd
877
234
  .command('validate <path>')
878
235
  .description('Validate a workflow bundle')
879
- .action(async (path) => {
880
- if (!isBundle(path)) {
881
- console.log(chalk.red(`Not a valid bundle: ${path}`));
882
- process.exit(1);
883
- }
884
- try {
885
- const bundle = new WorkflowBundle(path);
886
- await bundle.loadWorkflow();
887
- console.log(chalk.green(`Bundle '${bundle.name}' is valid.`));
888
- }
889
- catch (error) {
890
- console.log(chalk.red(`Bundle validation failed: ${error}`));
891
- process.exit(1);
892
- }
893
- });
236
+ .action(async (path) => executeBundleValidate(path));
894
237
  bundleCmd
895
238
  .command('run <path>')
896
239
  .description('Run a workflow bundle')
897
240
  .option('-i, --input <key=value...>', 'Input parameters')
898
- .action(async (path, options) => {
899
- if (!isBundle(path)) {
900
- console.log(chalk.red(`Not a valid bundle: ${path}`));
901
- process.exit(1);
902
- }
903
- const bundle = new WorkflowBundle(path);
904
- const workflow = await bundle.loadWorkflowWithBundleTools();
905
- // Parse and validate inputs
906
- const parsedInputs = parseInputPairs(options.input);
907
- const validation = validateAndApplyDefaults(workflow, parsedInputs);
908
- if (!validation.valid) {
909
- printMissingInputsError(workflow, validation.missingInputs, 'bundle run', path);
910
- process.exit(1);
911
- }
912
- const inputs = validation.inputs;
913
- const engine = new WorkflowEngine();
914
- const registry = new SDKRegistry();
915
- registerIntegrations(registry);
916
- registry.registerTools(workflow.tools);
917
- const result = await engine.execute(workflow, inputs, registry, createSDKStepExecutor());
918
- console.log(chalk.bold(`Bundle completed: ${result.status}`));
919
- });
241
+ .action(async (path, options) => executeBundleRun(path, options));
920
242
  // --- template ---
921
243
  const templateCmd = program.command('template').description('Workflow template commands');
922
244
  templateCmd
923
245
  .command('list')
924
246
  .description('List workflow templates')
925
- .action(() => {
926
- const registry = new TemplateRegistry();
927
- const templates = registry.list();
928
- if (!templates.length) {
929
- console.log(chalk.yellow('No templates found.'));
930
- return;
931
- }
932
- console.log(chalk.bold('Templates:'));
933
- for (const template of templates) {
934
- console.log(` ${chalk.cyan(template.id)}: ${template.name}`);
935
- }
936
- });
247
+ .action(() => executeTemplateList());
937
248
  // --- connect ---
938
249
  program
939
250
  .command('connect <service>')
@@ -942,262 +253,12 @@ program
942
253
  .option('--client-secret <secret>', 'OAuth client secret')
943
254
  .option('--tenant-id <tenant>', 'Microsoft tenant ID (for Outlook)')
944
255
  .option('--port <port>', 'Port for OAuth callback server (default: 8484)', '8484')
945
- .action(async (service, options) => {
946
- const serviceLower = service.toLowerCase();
947
- console.log(chalk.bold(`Connecting ${service}...`));
948
- // Services that support OAuth flow
949
- if (serviceLower === 'gmail') {
950
- const clientId = options.clientId ?? process.env.GOOGLE_CLIENT_ID;
951
- const clientSecret = options.clientSecret ?? process.env.GOOGLE_CLIENT_SECRET;
952
- const port = parseInt(options.port, 10);
953
- if (!clientId || !clientSecret) {
954
- console.log(chalk.yellow('\nGmail OAuth requires client credentials.'));
955
- console.log('\nTo connect Gmail:');
956
- console.log(' 1. Go to https://console.cloud.google.com/');
957
- console.log(' 2. Create OAuth 2.0 credentials (Desktop app type)');
958
- console.log(' 3. Run: marktoflow connect gmail --client-id YOUR_ID --client-secret YOUR_SECRET');
959
- console.log('\nOr set environment variables:');
960
- console.log(' export GOOGLE_CLIENT_ID="your-client-id"');
961
- console.log(' export GOOGLE_CLIENT_SECRET="your-client-secret"');
962
- return;
963
- }
964
- try {
965
- const { runGmailOAuth } = await import('./oauth.js');
966
- const tokens = await runGmailOAuth({ clientId, clientSecret, port });
967
- console.log(chalk.green('\nGmail connected successfully!'));
968
- console.log(chalk.dim(`Access token expires: ${tokens.expires_at ? new Date(tokens.expires_at).toISOString() : 'unknown'}`));
969
- console.log('\nYou can now use Gmail in your workflows:');
970
- console.log(chalk.cyan(` tools:
971
- gmail:
972
- sdk: "googleapis"
973
- auth:
974
- client_id: "\${GOOGLE_CLIENT_ID}"
975
- client_secret: "\${GOOGLE_CLIENT_SECRET}"
976
- redirect_uri: "http://localhost:${port}/callback"
977
- refresh_token: "\${GMAIL_REFRESH_TOKEN}"`));
978
- process.exit(0);
979
- }
980
- catch (error) {
981
- console.log(chalk.red(`\nOAuth failed: ${error}`));
982
- process.exit(1);
983
- }
984
- }
985
- // Handle other Google services (Drive, Sheets, Calendar, Docs, Workspace)
986
- if (serviceLower === 'google-drive' ||
987
- serviceLower === 'drive' ||
988
- serviceLower === 'google-sheets' ||
989
- serviceLower === 'sheets' ||
990
- serviceLower === 'google-calendar' ||
991
- serviceLower === 'calendar' ||
992
- serviceLower === 'google-docs' ||
993
- serviceLower === 'docs' ||
994
- serviceLower === 'google-workspace' ||
995
- serviceLower === 'workspace') {
996
- const clientId = options.clientId ?? process.env.GOOGLE_CLIENT_ID;
997
- const clientSecret = options.clientSecret ?? process.env.GOOGLE_CLIENT_SECRET;
998
- const port = parseInt(options.port, 10);
999
- if (!clientId || !clientSecret) {
1000
- console.log(chalk.yellow('\nGoogle OAuth requires client credentials.'));
1001
- console.log('\nTo connect Google services:');
1002
- console.log(' 1. Go to https://console.cloud.google.com/');
1003
- console.log(' 2. Enable the API for your service (Drive, Sheets, etc.)');
1004
- console.log(' 3. Create OAuth 2.0 credentials (Desktop app type)');
1005
- console.log(` 4. Run: marktoflow connect ${service} --client-id YOUR_ID --client-secret YOUR_SECRET`);
1006
- console.log('\nOr set environment variables:');
1007
- console.log(' export GOOGLE_CLIENT_ID="your-client-id"');
1008
- console.log(' export GOOGLE_CLIENT_SECRET="your-client-secret"');
1009
- return;
1010
- }
1011
- try {
1012
- const { runGoogleOAuth } = await import('./oauth.js');
1013
- const tokens = await runGoogleOAuth(serviceLower, { clientId, clientSecret, port });
1014
- console.log(chalk.dim(`Access token expires: ${tokens.expires_at ? new Date(tokens.expires_at).toISOString() : 'unknown'}`));
1015
- // Normalize service name for display
1016
- const normalizedService = serviceLower.startsWith('google-')
1017
- ? serviceLower
1018
- : `google-${serviceLower}`;
1019
- console.log('\nYou can now use this service in your workflows:');
1020
- console.log(chalk.cyan(` tools:
1021
- ${serviceLower.replace('google-', '')}:
1022
- sdk: "${normalizedService}"
1023
- auth:
1024
- client_id: "\${GOOGLE_CLIENT_ID}"
1025
- client_secret: "\${GOOGLE_CLIENT_SECRET}"
1026
- redirect_uri: "http://localhost:${port}/callback"
1027
- refresh_token: "\${GOOGLE_REFRESH_TOKEN}"
1028
- access_token: "\${GOOGLE_ACCESS_TOKEN}"`));
1029
- process.exit(0);
1030
- }
1031
- catch (error) {
1032
- console.log(chalk.red(`\nOAuth failed: ${error}`));
1033
- process.exit(1);
1034
- }
1035
- }
1036
- if (serviceLower === 'outlook' || serviceLower === 'microsoft') {
1037
- const clientId = options.clientId ?? process.env.MICROSOFT_CLIENT_ID;
1038
- const clientSecret = options.clientSecret ?? process.env.MICROSOFT_CLIENT_SECRET;
1039
- const tenantId = options.tenantId ?? process.env.MICROSOFT_TENANT_ID;
1040
- const port = parseInt(options.port, 10);
1041
- if (!clientId) {
1042
- console.log(chalk.yellow('\nOutlook OAuth requires a client ID.'));
1043
- console.log('\nTo connect Outlook/Microsoft Graph:');
1044
- console.log(' 1. Go to https://portal.azure.com/');
1045
- console.log(' 2. Register an application in Azure AD');
1046
- console.log(` 3. Add redirect URI: http://localhost:${port}/callback`);
1047
- console.log(' 4. Grant Mail.Read, Mail.Send, Calendars.ReadWrite permissions');
1048
- console.log(' 5. Run: marktoflow connect outlook --client-id YOUR_ID');
1049
- console.log('\nOr set environment variables:');
1050
- console.log(' export MICROSOFT_CLIENT_ID="your-client-id"');
1051
- console.log(' export MICROSOFT_CLIENT_SECRET="your-client-secret" # optional');
1052
- console.log(' export MICROSOFT_TENANT_ID="common" # or your tenant ID');
1053
- return;
1054
- }
1055
- try {
1056
- const { runOutlookOAuth } = await import('./oauth.js');
1057
- const tokens = await runOutlookOAuth({ clientId, clientSecret, tenantId, port });
1058
- console.log(chalk.green('\nOutlook connected successfully!'));
1059
- console.log(chalk.dim(`Access token expires: ${tokens.expires_at ? new Date(tokens.expires_at).toISOString() : 'unknown'}`));
1060
- console.log('\nYou can now use Outlook in your workflows:');
1061
- console.log(chalk.cyan(` tools:
1062
- outlook:
1063
- sdk: "@microsoft/microsoft-graph-client"
1064
- auth:
1065
- token: "\${OUTLOOK_ACCESS_TOKEN}"`));
1066
- process.exit(0);
1067
- }
1068
- catch (error) {
1069
- console.log(chalk.red(`\nOAuth failed: ${error}`));
1070
- process.exit(1);
1071
- }
1072
- }
1073
- // Other services - show manual setup instructions
1074
- console.log('\nManual setup required. Set environment variables:');
1075
- switch (serviceLower) {
1076
- case 'slack':
1077
- console.log(` export SLACK_BOT_TOKEN="xoxb-your-token"`);
1078
- console.log(` export SLACK_APP_TOKEN="xapp-your-token"`);
1079
- console.log(chalk.dim('\n Get tokens from https://api.slack.com/apps'));
1080
- break;
1081
- case 'github':
1082
- console.log(` export GITHUB_TOKEN="ghp_your-token"`);
1083
- console.log(chalk.dim('\n Create token at https://github.com/settings/tokens'));
1084
- break;
1085
- case 'jira':
1086
- console.log(` export JIRA_HOST="https://your-domain.atlassian.net"`);
1087
- console.log(` export JIRA_EMAIL="your-email@example.com"`);
1088
- console.log(` export JIRA_API_TOKEN="your-api-token"`);
1089
- console.log(chalk.dim('\n Create token at https://id.atlassian.com/manage-profile/security/api-tokens'));
1090
- break;
1091
- case 'confluence':
1092
- console.log(` export CONFLUENCE_HOST="https://your-domain.atlassian.net"`);
1093
- console.log(` export CONFLUENCE_EMAIL="your-email@example.com"`);
1094
- console.log(` export CONFLUENCE_API_TOKEN="your-api-token"`);
1095
- console.log(chalk.dim('\n Create token at https://id.atlassian.com/manage-profile/security/api-tokens'));
1096
- break;
1097
- case 'linear':
1098
- console.log(` export LINEAR_API_KEY="lin_api_your-key"`);
1099
- console.log(chalk.dim('\n Create key at https://linear.app/settings/api'));
1100
- break;
1101
- case 'notion':
1102
- console.log(` export NOTION_TOKEN="secret_your-token"`);
1103
- console.log(chalk.dim('\n Create integration at https://www.notion.so/my-integrations'));
1104
- break;
1105
- case 'discord':
1106
- console.log(` export DISCORD_BOT_TOKEN="your-bot-token"`);
1107
- console.log(chalk.dim('\n Create bot at https://discord.com/developers/applications'));
1108
- break;
1109
- case 'airtable':
1110
- console.log(` export AIRTABLE_TOKEN="pat_your-token"`);
1111
- console.log(` export AIRTABLE_BASE_ID="appXXXXX" # optional default base`);
1112
- console.log(chalk.dim('\n Create token at https://airtable.com/create/tokens'));
1113
- break;
1114
- case 'anthropic':
1115
- console.log(` export ANTHROPIC_API_KEY="sk-ant-your-key"`);
1116
- console.log(chalk.dim('\n Get key at https://console.anthropic.com/'));
1117
- break;
1118
- case 'openai':
1119
- console.log(` export OPENAI_API_KEY="sk-your-key"`);
1120
- console.log(chalk.dim('\n Get key at https://platform.openai.com/api-keys'));
1121
- break;
1122
- default:
1123
- console.log(` See documentation for ${service} configuration.`);
1124
- console.log('\n' + chalk.bold('Available services:'));
1125
- console.log(' Communication: slack, discord');
1126
- console.log(' Email: gmail, outlook');
1127
- console.log(' Google Workspace: google-drive, google-sheets, google-calendar, google-docs, google-workspace');
1128
- console.log(' Project management: jira, linear');
1129
- console.log(' Documentation: notion, confluence');
1130
- console.log(' Developer: github');
1131
- console.log(' Data: airtable');
1132
- console.log(' AI: anthropic, openai');
1133
- }
1134
- });
256
+ .action(async (service, options) => executeConnect(service, options));
1135
257
  // --- doctor ---
1136
258
  program
1137
259
  .command('doctor')
1138
260
  .description('Check environment and configuration')
1139
- .action(async () => {
1140
- console.log(chalk.bold('marktoflow Doctor\n'));
1141
- // Node version
1142
- const nodeVersion = process.version;
1143
- const nodeMajor = parseInt(nodeVersion.slice(1).split('.')[0]);
1144
- if (nodeMajor >= 20) {
1145
- console.log(chalk.green('✓') + ` Node.js ${nodeVersion}`);
1146
- }
1147
- else {
1148
- console.log(chalk.red('✗') + ` Node.js ${nodeVersion} (requires >=20)`);
1149
- }
1150
- // Project initialized
1151
- if (existsSync('.marktoflow')) {
1152
- console.log(chalk.green('✓') + ' Project initialized');
1153
- // Count workflows
1154
- const workflowsDir = '.marktoflow/workflows';
1155
- if (existsSync(workflowsDir)) {
1156
- const { readdirSync } = await import('node:fs');
1157
- const workflows = readdirSync(workflowsDir).filter((f) => f.endsWith('.md'));
1158
- console.log(chalk.green('✓') + ` ${workflows.length} workflow(s) found`);
1159
- }
1160
- }
1161
- else {
1162
- console.log(chalk.yellow('○') + ' Project not initialized');
1163
- }
1164
- // Check for common environment variables
1165
- const envChecks = [
1166
- // Communication
1167
- ['SLACK_BOT_TOKEN', 'Slack'],
1168
- ['DISCORD_BOT_TOKEN', 'Discord'],
1169
- // Email
1170
- ['GOOGLE_CLIENT_ID', 'Gmail'],
1171
- ['MICROSOFT_CLIENT_ID', 'Outlook'],
1172
- // Project Management
1173
- ['JIRA_API_TOKEN', 'Jira'],
1174
- ['LINEAR_API_KEY', 'Linear'],
1175
- // Documentation
1176
- ['NOTION_TOKEN', 'Notion'],
1177
- ['CONFLUENCE_API_TOKEN', 'Confluence'],
1178
- // Developer
1179
- ['GITHUB_TOKEN', 'GitHub'],
1180
- // Data
1181
- ['AIRTABLE_TOKEN', 'Airtable'],
1182
- // AI
1183
- ['ANTHROPIC_API_KEY', 'Anthropic'],
1184
- ['OPENAI_API_KEY', 'OpenAI'],
1185
- ];
1186
- console.log('\n' + chalk.bold('Services:'));
1187
- let configuredCount = 0;
1188
- for (const [envVar, name] of envChecks) {
1189
- if (process.env[envVar]) {
1190
- console.log(chalk.green('✓') + ` ${name} configured`);
1191
- configuredCount++;
1192
- }
1193
- else {
1194
- console.log(chalk.dim('○') + ` ${name} not configured`);
1195
- }
1196
- }
1197
- if (configuredCount === 0) {
1198
- console.log(chalk.yellow('\n Run `marktoflow connect <service>` to set up integrations'));
1199
- }
1200
- });
261
+ .action(async () => executeDoctor());
1201
262
  // --- test-connection ---
1202
263
  program
1203
264
  .command('test-connection [service]')
@@ -1243,59 +304,7 @@ program
1243
304
  .option('-o, --open', 'Open browser automatically')
1244
305
  .option('-w, --workflow <path>', 'Open specific workflow')
1245
306
  .option('-d, --dir <path>', 'Workflow directory', '.')
1246
- .action(async (options) => {
1247
- const spinner = ora('Starting GUI server...').start();
1248
- try {
1249
- // Check if @marktoflow/gui is available
1250
- let guiModule;
1251
- let guiPackagePath;
1252
- try {
1253
- guiModule = await import('@marktoflow/gui');
1254
- // Find the GUI package location
1255
- const { createRequire } = await import('node:module');
1256
- const require = createRequire(import.meta.url);
1257
- guiPackagePath = require.resolve('@marktoflow/gui');
1258
- }
1259
- catch {
1260
- spinner.fail('@marktoflow/gui package not found');
1261
- console.log(chalk.yellow('\nTo use the GUI, install the gui package:'));
1262
- console.log(chalk.cyan(' npm install @marktoflow/gui@alpha'));
1263
- console.log('\nOr run from the monorepo:');
1264
- console.log(chalk.cyan(' pnpm --filter @marktoflow/gui dev'));
1265
- process.exit(1);
1266
- }
1267
- spinner.succeed(`GUI server starting on http://localhost:${options.port}`);
1268
- // Open browser if requested
1269
- if (options.open) {
1270
- const url = `http://localhost:${options.port}`;
1271
- const { exec } = await import('node:child_process');
1272
- const openCmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
1273
- exec(`${openCmd} ${url}`);
1274
- }
1275
- console.log('\n' + chalk.bold('Marktoflow GUI'));
1276
- console.log(` Server: ${chalk.cyan(`http://localhost:${options.port}`)}`);
1277
- console.log(` Workflows: ${chalk.cyan(options.dir)}`);
1278
- console.log('\n Press ' + chalk.bold('Ctrl+C') + ' to stop\n');
1279
- // Find the static files directory
1280
- // guiPackagePath is .../node_modules/@marktoflow/gui/dist/server/index.js
1281
- // We need to go up to the package root: dist/server -> dist -> package root
1282
- const { dirname, join } = await import('node:path');
1283
- const guiPackageDir = dirname(dirname(dirname(guiPackagePath)));
1284
- const staticDir = join(guiPackageDir, 'dist', 'client');
1285
- // The GUI package will handle the server
1286
- if (guiModule.startServer) {
1287
- await guiModule.startServer({
1288
- port: parseInt(options.port, 10),
1289
- workflowDir: options.dir,
1290
- staticDir: staticDir,
1291
- });
1292
- }
1293
- }
1294
- catch (error) {
1295
- spinner.fail(`Failed to start GUI: ${error}`);
1296
- process.exit(1);
1297
- }
1298
- });
307
+ .action(async (options) => executeGui(options));
1299
308
  // --- version ---
1300
309
  program
1301
310
  .command('version')