@marktoflow/cli 2.0.0-alpha.3
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/dist/commands/debug.d.ts +75 -0
- package/dist/commands/debug.d.ts.map +1 -0
- package/dist/commands/debug.js +375 -0
- package/dist/commands/debug.js.map +1 -0
- package/dist/commands/dry-run.d.ts +40 -0
- package/dist/commands/dry-run.d.ts.map +1 -0
- package/dist/commands/dry-run.js +360 -0
- package/dist/commands/dry-run.js.map +1 -0
- package/dist/commands/new.d.ts +14 -0
- package/dist/commands/new.d.ts.map +1 -0
- package/dist/commands/new.js +483 -0
- package/dist/commands/new.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +726 -0
- package/dist/index.js.map +1 -0
- package/dist/oauth.d.ts +48 -0
- package/dist/oauth.d.ts.map +1 -0
- package/dist/oauth.js +314 -0
- package/dist/oauth.js.map +1 -0
- package/dist/trigger.d.ts +3 -0
- package/dist/trigger.d.ts.map +1 -0
- package/dist/trigger.js +58 -0
- package/dist/trigger.js.map +1 -0
- package/dist/worker.d.ts +3 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +69 -0
- package/dist/worker.js.map +1 -0
- package/package.json +57 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,726 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* marktoflow CLI
|
|
4
|
+
*
|
|
5
|
+
* Universal automation framework with native MCP support.
|
|
6
|
+
*/
|
|
7
|
+
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, } from '@marktoflow/core';
|
|
13
|
+
import { registerIntegrations } from '@marktoflow/integrations';
|
|
14
|
+
import { workerCommand } from './worker.js';
|
|
15
|
+
import { triggerCommand } from './trigger.js';
|
|
16
|
+
import { runWorkflowWizard, listTemplates } from './commands/new.js';
|
|
17
|
+
import { parse as parseYaml } from 'yaml';
|
|
18
|
+
import { executeDryRun, displayDryRunSummary } from './commands/dry-run.js';
|
|
19
|
+
import { WorkflowDebugger, parseBreakpoints } from './commands/debug.js';
|
|
20
|
+
const VERSION = '2.0.0-alpha.1';
|
|
21
|
+
// Load environment variables from .env files on CLI startup
|
|
22
|
+
loadEnv();
|
|
23
|
+
function getConfig() {
|
|
24
|
+
return loadConfig(process.cwd());
|
|
25
|
+
}
|
|
26
|
+
function isBundle(path) {
|
|
27
|
+
try {
|
|
28
|
+
const stat = existsSync(path) ? statSync(path) : null;
|
|
29
|
+
if (!stat || !stat.isDirectory())
|
|
30
|
+
return false;
|
|
31
|
+
const entries = readdirSync(path);
|
|
32
|
+
return entries.some((name) => name.endsWith('.md') && name !== 'README.md');
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// ============================================================================
|
|
39
|
+
// CLI Setup
|
|
40
|
+
// ============================================================================
|
|
41
|
+
const program = new Command();
|
|
42
|
+
program
|
|
43
|
+
.name('marktoflow')
|
|
44
|
+
.description('Universal automation framework with native MCP support')
|
|
45
|
+
.version(VERSION);
|
|
46
|
+
program.addCommand(workerCommand);
|
|
47
|
+
program.addCommand(triggerCommand);
|
|
48
|
+
// ============================================================================
|
|
49
|
+
// Commands
|
|
50
|
+
// ============================================================================
|
|
51
|
+
// --- init ---
|
|
52
|
+
program
|
|
53
|
+
.command('init')
|
|
54
|
+
.description('Initialize a new marktoflow project')
|
|
55
|
+
.option('-f, --force', 'Overwrite existing configuration')
|
|
56
|
+
.action(async (options) => {
|
|
57
|
+
const spinner = ora('Initializing marktoflow project...').start();
|
|
58
|
+
try {
|
|
59
|
+
const configDir = '.marktoflow';
|
|
60
|
+
const workflowsDir = join(configDir, 'workflows');
|
|
61
|
+
const credentialsDir = join(configDir, 'credentials');
|
|
62
|
+
if (existsSync(configDir) && !options.force) {
|
|
63
|
+
spinner.fail('Project already initialized. Use --force to reinitialize.');
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
// Create directories
|
|
67
|
+
mkdirSync(workflowsDir, { recursive: true });
|
|
68
|
+
mkdirSync(credentialsDir, { recursive: true });
|
|
69
|
+
// Create example workflow
|
|
70
|
+
const exampleWorkflow = `---
|
|
71
|
+
workflow:
|
|
72
|
+
id: hello-world
|
|
73
|
+
name: "Hello World"
|
|
74
|
+
version: "1.0.0"
|
|
75
|
+
description: "A simple example workflow"
|
|
76
|
+
|
|
77
|
+
# Uncomment and configure to use Slack:
|
|
78
|
+
# tools:
|
|
79
|
+
# slack:
|
|
80
|
+
# sdk: "@slack/web-api"
|
|
81
|
+
# auth:
|
|
82
|
+
# token: "\${SLACK_BOT_TOKEN}"
|
|
83
|
+
|
|
84
|
+
steps:
|
|
85
|
+
- id: greet
|
|
86
|
+
action: console.log
|
|
87
|
+
inputs:
|
|
88
|
+
message: "Hello from marktoflow!"
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
# Hello World Workflow
|
|
92
|
+
|
|
93
|
+
This is a simple example workflow.
|
|
94
|
+
|
|
95
|
+
## Step 1: Greet
|
|
96
|
+
|
|
97
|
+
Outputs a greeting message.
|
|
98
|
+
`;
|
|
99
|
+
writeFileSync(join(workflowsDir, 'hello-world.md'), exampleWorkflow);
|
|
100
|
+
// Create .gitignore for credentials
|
|
101
|
+
writeFileSync(join(credentialsDir, '.gitignore'), '# Ignore all credentials\n*\n!.gitignore\n');
|
|
102
|
+
spinner.succeed('Project initialized successfully!');
|
|
103
|
+
console.log('\n' + chalk.bold('Next steps:'));
|
|
104
|
+
console.log(` 1. Edit ${chalk.cyan('.marktoflow/workflows/hello-world.md')}`);
|
|
105
|
+
console.log(` 2. Run ${chalk.cyan('marktoflow run hello-world.md')}`);
|
|
106
|
+
console.log(` 3. Connect services: ${chalk.cyan('marktoflow connect slack')}`);
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
spinner.fail(`Initialization failed: ${error}`);
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
// --- new ---
|
|
114
|
+
program
|
|
115
|
+
.command('new')
|
|
116
|
+
.description('Create a new workflow from template')
|
|
117
|
+
.option('-o, --output <path>', 'Output file path')
|
|
118
|
+
.option('-t, --template <id>', 'Template ID to use')
|
|
119
|
+
.option('--list-templates', 'List available templates')
|
|
120
|
+
.action(async (options) => {
|
|
121
|
+
if (options.listTemplates) {
|
|
122
|
+
listTemplates();
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
await runWorkflowWizard(options);
|
|
126
|
+
});
|
|
127
|
+
// --- run ---
|
|
128
|
+
program
|
|
129
|
+
.command('run <workflow>')
|
|
130
|
+
.description('Run a workflow')
|
|
131
|
+
.option('-i, --input <key=value...>', 'Input parameters')
|
|
132
|
+
.option('-v, --verbose', 'Verbose output')
|
|
133
|
+
.option('--dry-run', 'Parse workflow without executing')
|
|
134
|
+
.action(async (workflowPath, options) => {
|
|
135
|
+
const spinner = ora('Loading workflow...').start();
|
|
136
|
+
try {
|
|
137
|
+
const config = getConfig();
|
|
138
|
+
const workflowsDir = config.workflows?.path ?? '.marktoflow/workflows';
|
|
139
|
+
// Resolve workflow path
|
|
140
|
+
let resolvedPath = workflowPath;
|
|
141
|
+
if (!existsSync(resolvedPath)) {
|
|
142
|
+
resolvedPath = join(workflowsDir, workflowPath);
|
|
143
|
+
}
|
|
144
|
+
if (!existsSync(resolvedPath)) {
|
|
145
|
+
spinner.fail(`Workflow not found: ${workflowPath}`);
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
// Parse workflow
|
|
149
|
+
const { workflow, warnings } = await parseFile(resolvedPath);
|
|
150
|
+
if (warnings.length > 0) {
|
|
151
|
+
spinner.warn('Workflow parsed with warnings:');
|
|
152
|
+
warnings.forEach((w) => console.log(chalk.yellow(` - ${w}`)));
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
spinner.succeed(`Loaded: ${workflow.metadata.name}`);
|
|
156
|
+
}
|
|
157
|
+
// Parse inputs
|
|
158
|
+
const inputs = {};
|
|
159
|
+
if (options.input) {
|
|
160
|
+
for (const pair of options.input) {
|
|
161
|
+
const [key, value] = pair.split('=');
|
|
162
|
+
inputs[key] = value;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// Handle dry-run mode
|
|
166
|
+
if (options.dryRun) {
|
|
167
|
+
const dryRunResult = await executeDryRun(workflow, inputs, {
|
|
168
|
+
verbose: options.verbose,
|
|
169
|
+
showMockData: true,
|
|
170
|
+
showVariables: true,
|
|
171
|
+
});
|
|
172
|
+
displayDryRunSummary(dryRunResult, {
|
|
173
|
+
verbose: options.verbose,
|
|
174
|
+
showMockData: true,
|
|
175
|
+
showVariables: true,
|
|
176
|
+
});
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
// Execute workflow
|
|
180
|
+
spinner.start('Executing workflow...');
|
|
181
|
+
const engine = new WorkflowEngine({}, {
|
|
182
|
+
onStepStart: (step) => {
|
|
183
|
+
if (options.verbose) {
|
|
184
|
+
spinner.text = `Executing: ${step.id}`;
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
onStepComplete: (step, result) => {
|
|
188
|
+
if (options.verbose) {
|
|
189
|
+
const icon = result.status === StepStatus.COMPLETED ? '✓' : '✗';
|
|
190
|
+
console.log(` ${icon} ${step.id}: ${result.status}`);
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
const registry = new SDKRegistry();
|
|
195
|
+
registerIntegrations(registry);
|
|
196
|
+
registry.registerTools(workflow.tools);
|
|
197
|
+
const result = await engine.execute(workflow, inputs, registry, createSDKStepExecutor());
|
|
198
|
+
if (result.status === WorkflowStatus.COMPLETED) {
|
|
199
|
+
spinner.succeed(`Workflow completed in ${result.duration}ms`);
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
spinner.fail(`Workflow failed: ${result.error}`);
|
|
203
|
+
process.exit(1);
|
|
204
|
+
}
|
|
205
|
+
// Show summary
|
|
206
|
+
console.log('\n' + chalk.bold('Summary:'));
|
|
207
|
+
console.log(` Status: ${result.status}`);
|
|
208
|
+
console.log(` Duration: ${result.duration}ms`);
|
|
209
|
+
console.log(` Steps: ${result.stepResults.length}`);
|
|
210
|
+
const completed = result.stepResults.filter((s) => s.status === StepStatus.COMPLETED).length;
|
|
211
|
+
const failed = result.stepResults.filter((s) => s.status === StepStatus.FAILED).length;
|
|
212
|
+
const skipped = result.stepResults.filter((s) => s.status === StepStatus.SKIPPED).length;
|
|
213
|
+
console.log(` Completed: ${completed}, Failed: ${failed}, Skipped: ${skipped}`);
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
spinner.fail(`Execution failed: ${error}`);
|
|
217
|
+
process.exit(1);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
// --- debug ---
|
|
221
|
+
program
|
|
222
|
+
.command('debug <workflow>')
|
|
223
|
+
.description('Debug a workflow with step-by-step execution')
|
|
224
|
+
.option('-i, --input <key=value...>', 'Input parameters')
|
|
225
|
+
.option('-b, --breakpoint <stepId...>', 'Set breakpoints at step IDs')
|
|
226
|
+
.option('--auto-start', 'Start without initial prompt')
|
|
227
|
+
.action(async (workflowPath, options) => {
|
|
228
|
+
const spinner = ora('Loading workflow for debugging...').start();
|
|
229
|
+
try {
|
|
230
|
+
const config = getConfig();
|
|
231
|
+
const workflowsDir = config.workflows?.path ?? '.marktoflow/workflows';
|
|
232
|
+
// Resolve workflow path
|
|
233
|
+
let resolvedPath = workflowPath;
|
|
234
|
+
if (!existsSync(resolvedPath)) {
|
|
235
|
+
resolvedPath = join(workflowsDir, workflowPath);
|
|
236
|
+
}
|
|
237
|
+
if (!existsSync(resolvedPath)) {
|
|
238
|
+
spinner.fail(`Workflow not found: ${workflowPath}`);
|
|
239
|
+
process.exit(1);
|
|
240
|
+
}
|
|
241
|
+
// Parse workflow
|
|
242
|
+
const { workflow, warnings } = await parseFile(resolvedPath);
|
|
243
|
+
if (warnings.length > 0) {
|
|
244
|
+
spinner.warn('Workflow parsed with warnings:');
|
|
245
|
+
warnings.forEach((w) => console.log(chalk.yellow(` - ${w}`)));
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
spinner.succeed(`Loaded: ${workflow.metadata.name}`);
|
|
249
|
+
}
|
|
250
|
+
// Parse inputs
|
|
251
|
+
const inputs = {};
|
|
252
|
+
if (options.input) {
|
|
253
|
+
for (const pair of options.input) {
|
|
254
|
+
const [key, value] = pair.split('=');
|
|
255
|
+
inputs[key] = value;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
// Parse breakpoints
|
|
259
|
+
const breakpoints = options.breakpoint ? parseBreakpoints(options.breakpoint) : [];
|
|
260
|
+
// Setup SDK registry and executor
|
|
261
|
+
const registry = new SDKRegistry();
|
|
262
|
+
registerIntegrations(registry);
|
|
263
|
+
registry.registerTools(workflow.tools);
|
|
264
|
+
// Create debugger
|
|
265
|
+
const workflowDebugger = new WorkflowDebugger(workflow, inputs, registry, createSDKStepExecutor(), {
|
|
266
|
+
breakpoints,
|
|
267
|
+
autoStart: options.autoStart,
|
|
268
|
+
});
|
|
269
|
+
// Start debugging session
|
|
270
|
+
await workflowDebugger.debug();
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
spinner.fail(`Debug session failed: ${error}`);
|
|
274
|
+
process.exit(1);
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
// --- workflow list ---
|
|
278
|
+
program
|
|
279
|
+
.command('workflow')
|
|
280
|
+
.description('Workflow management')
|
|
281
|
+
.command('list')
|
|
282
|
+
.description('List available workflows')
|
|
283
|
+
.action(async () => {
|
|
284
|
+
const workflowsDir = getConfig().workflows?.path ?? '.marktoflow/workflows';
|
|
285
|
+
if (!existsSync(workflowsDir)) {
|
|
286
|
+
console.log(chalk.yellow('No workflows found. Run `marktoflow init` first.'));
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
const { readdirSync } = await import('node:fs');
|
|
290
|
+
const files = readdirSync(workflowsDir).filter((f) => f.endsWith('.md'));
|
|
291
|
+
if (files.length === 0) {
|
|
292
|
+
console.log(chalk.yellow('No workflows found.'));
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
console.log(chalk.bold('Available Workflows:'));
|
|
296
|
+
for (const file of files) {
|
|
297
|
+
try {
|
|
298
|
+
const { workflow } = await parseFile(join(workflowsDir, file));
|
|
299
|
+
console.log(` ${chalk.cyan(file)}: ${workflow.metadata.name}`);
|
|
300
|
+
}
|
|
301
|
+
catch {
|
|
302
|
+
console.log(` ${chalk.red(file)}: (invalid)`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
// --- agent ---
|
|
307
|
+
const agentCmd = program.command('agent').description('Agent management');
|
|
308
|
+
agentCmd
|
|
309
|
+
.command('list')
|
|
310
|
+
.description('List available agents')
|
|
311
|
+
.action(() => {
|
|
312
|
+
const capabilitiesPath = join('.marktoflow', 'agents', 'capabilities.yaml');
|
|
313
|
+
const agentsFromFile = [];
|
|
314
|
+
if (existsSync(capabilitiesPath)) {
|
|
315
|
+
const content = readFileSync(capabilitiesPath, 'utf8');
|
|
316
|
+
const data = parseYaml(content);
|
|
317
|
+
agentsFromFile.push(...Object.keys(data?.agents ?? {}));
|
|
318
|
+
}
|
|
319
|
+
const knownAgents = ['claude-code', 'opencode', 'ollama', 'codex', 'gemini-cli'];
|
|
320
|
+
const allAgents = Array.from(new Set([...agentsFromFile, ...knownAgents]));
|
|
321
|
+
console.log(chalk.bold('Available Agents:'));
|
|
322
|
+
for (const agent of allAgents) {
|
|
323
|
+
const status = agentsFromFile.includes(agent)
|
|
324
|
+
? chalk.green('Registered')
|
|
325
|
+
: chalk.yellow('Not configured');
|
|
326
|
+
console.log(` ${chalk.cyan(agent)}: ${status}`);
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
agentCmd
|
|
330
|
+
.command('info <agent>')
|
|
331
|
+
.description('Show agent information')
|
|
332
|
+
.action((agent) => {
|
|
333
|
+
const capabilitiesPath = join('.marktoflow', 'agents', 'capabilities.yaml');
|
|
334
|
+
if (!existsSync(capabilitiesPath)) {
|
|
335
|
+
console.log(chalk.yellow('No capabilities file found. Run `marktoflow init` first.'));
|
|
336
|
+
process.exit(1);
|
|
337
|
+
}
|
|
338
|
+
const content = readFileSync(capabilitiesPath, 'utf8');
|
|
339
|
+
const data = parseYaml(content);
|
|
340
|
+
const info = data?.agents?.[agent];
|
|
341
|
+
if (!info) {
|
|
342
|
+
console.log(chalk.red(`Agent not found: ${agent}`));
|
|
343
|
+
process.exit(1);
|
|
344
|
+
}
|
|
345
|
+
console.log(chalk.bold(agent));
|
|
346
|
+
console.log(` Version: ${info.version ?? 'unknown'}`);
|
|
347
|
+
console.log(` Provider: ${info.provider ?? 'unknown'}`);
|
|
348
|
+
const capabilities = info.capabilities ?? {};
|
|
349
|
+
for (const [key, value] of Object.entries(capabilities)) {
|
|
350
|
+
if (typeof value === 'object' && value) {
|
|
351
|
+
for (const [subKey, subValue] of Object.entries(value)) {
|
|
352
|
+
console.log(` ${key}.${subKey}: ${String(subValue)}`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
console.log(` ${key}: ${String(value)}`);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
// --- tools ---
|
|
361
|
+
const toolsCmd = program.command('tools').description('Tool management');
|
|
362
|
+
toolsCmd
|
|
363
|
+
.command('list')
|
|
364
|
+
.description('List available tools')
|
|
365
|
+
.action(() => {
|
|
366
|
+
const registryPath = getConfig().tools?.registryPath ?? join('.marktoflow', 'tools', 'registry.yaml');
|
|
367
|
+
if (!existsSync(registryPath)) {
|
|
368
|
+
console.log(chalk.yellow("No tool registry found. Run 'marktoflow init' first."));
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
const registry = new ToolRegistry(registryPath);
|
|
372
|
+
const tools = registry.listTools();
|
|
373
|
+
if (tools.length === 0) {
|
|
374
|
+
console.log(chalk.yellow('No tools registered.'));
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
console.log(chalk.bold('Registered Tools:'));
|
|
378
|
+
for (const toolName of tools) {
|
|
379
|
+
const definition = registry.getDefinition(toolName);
|
|
380
|
+
const types = definition?.implementations.map((impl) => impl.type).join(', ') ?? '';
|
|
381
|
+
console.log(` ${chalk.cyan(toolName)} ${types ? `(${types})` : ''}`);
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
// --- schedule ---
|
|
385
|
+
const scheduleCmd = program.command('schedule').description('Scheduler management');
|
|
386
|
+
scheduleCmd
|
|
387
|
+
.command('list')
|
|
388
|
+
.description('List scheduled workflows')
|
|
389
|
+
.action(() => {
|
|
390
|
+
const scheduler = new Scheduler();
|
|
391
|
+
const jobs = scheduler.listJobs();
|
|
392
|
+
if (jobs.length === 0) {
|
|
393
|
+
console.log(chalk.yellow('No scheduled workflows found.'));
|
|
394
|
+
console.log('Add schedule triggers to your workflows to enable scheduling.');
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
console.log(chalk.bold('Scheduled Workflows:'));
|
|
398
|
+
for (const job of jobs) {
|
|
399
|
+
console.log(` ${chalk.cyan(job.id)} ${job.workflowPath} (${job.schedule})`);
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
// --- bundle ---
|
|
403
|
+
const bundleCmd = program.command('bundle').description('Workflow bundle commands');
|
|
404
|
+
bundleCmd
|
|
405
|
+
.command('list [path]')
|
|
406
|
+
.description('List workflow bundles in a directory')
|
|
407
|
+
.action((path = '.') => {
|
|
408
|
+
if (!existsSync(path)) {
|
|
409
|
+
console.log(chalk.red(`Path not found: ${path}`));
|
|
410
|
+
process.exit(1);
|
|
411
|
+
}
|
|
412
|
+
const entries = readdirSync(path, { withFileTypes: true });
|
|
413
|
+
const bundles = [];
|
|
414
|
+
for (const entry of entries) {
|
|
415
|
+
if (!entry.isDirectory())
|
|
416
|
+
continue;
|
|
417
|
+
const fullPath = join(path, entry.name);
|
|
418
|
+
if (isBundle(fullPath))
|
|
419
|
+
bundles.push(fullPath);
|
|
420
|
+
}
|
|
421
|
+
if (bundles.length === 0) {
|
|
422
|
+
console.log(chalk.yellow(`No bundles found in ${path}`));
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
console.log(chalk.bold('Bundles:'));
|
|
426
|
+
for (const bundlePath of bundles) {
|
|
427
|
+
console.log(` ${chalk.cyan(bundlePath)}`);
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
bundleCmd
|
|
431
|
+
.command('info <path>')
|
|
432
|
+
.description('Show information about a workflow bundle')
|
|
433
|
+
.action(async (path) => {
|
|
434
|
+
if (!isBundle(path)) {
|
|
435
|
+
console.log(chalk.red(`Not a valid bundle: ${path}`));
|
|
436
|
+
process.exit(1);
|
|
437
|
+
}
|
|
438
|
+
const bundle = new WorkflowBundle(path);
|
|
439
|
+
const workflow = await bundle.loadWorkflow();
|
|
440
|
+
const tools = bundle.loadTools().listTools();
|
|
441
|
+
console.log(chalk.bold(`Bundle: ${bundle.name}`));
|
|
442
|
+
console.log(` Workflow: ${workflow.metadata.name} (${workflow.metadata.id})`);
|
|
443
|
+
console.log(` Steps: ${workflow.steps.length}`);
|
|
444
|
+
console.log(` Tools: ${tools.length ? tools.join(', ') : 'none'}`);
|
|
445
|
+
});
|
|
446
|
+
bundleCmd
|
|
447
|
+
.command('validate <path>')
|
|
448
|
+
.description('Validate a workflow bundle')
|
|
449
|
+
.action(async (path) => {
|
|
450
|
+
if (!isBundle(path)) {
|
|
451
|
+
console.log(chalk.red(`Not a valid bundle: ${path}`));
|
|
452
|
+
process.exit(1);
|
|
453
|
+
}
|
|
454
|
+
try {
|
|
455
|
+
const bundle = new WorkflowBundle(path);
|
|
456
|
+
await bundle.loadWorkflow();
|
|
457
|
+
console.log(chalk.green(`Bundle '${bundle.name}' is valid.`));
|
|
458
|
+
}
|
|
459
|
+
catch (error) {
|
|
460
|
+
console.log(chalk.red(`Bundle validation failed: ${error}`));
|
|
461
|
+
process.exit(1);
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
bundleCmd
|
|
465
|
+
.command('run <path>')
|
|
466
|
+
.description('Run a workflow bundle')
|
|
467
|
+
.option('-i, --input <key=value...>', 'Input parameters')
|
|
468
|
+
.action(async (path, options) => {
|
|
469
|
+
if (!isBundle(path)) {
|
|
470
|
+
console.log(chalk.red(`Not a valid bundle: ${path}`));
|
|
471
|
+
process.exit(1);
|
|
472
|
+
}
|
|
473
|
+
const bundle = new WorkflowBundle(path);
|
|
474
|
+
const workflow = await bundle.loadWorkflowWithBundleTools();
|
|
475
|
+
const inputs = {};
|
|
476
|
+
if (options.input) {
|
|
477
|
+
for (const pair of options.input) {
|
|
478
|
+
const [key, value] = pair.split('=');
|
|
479
|
+
inputs[key] = value;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
const engine = new WorkflowEngine();
|
|
483
|
+
const registry = new SDKRegistry();
|
|
484
|
+
registerIntegrations(registry);
|
|
485
|
+
registry.registerTools(workflow.tools);
|
|
486
|
+
const result = await engine.execute(workflow, inputs, registry, createSDKStepExecutor());
|
|
487
|
+
console.log(chalk.bold(`Bundle completed: ${result.status}`));
|
|
488
|
+
});
|
|
489
|
+
// --- template ---
|
|
490
|
+
const templateCmd = program.command('template').description('Workflow template commands');
|
|
491
|
+
templateCmd
|
|
492
|
+
.command('list')
|
|
493
|
+
.description('List workflow templates')
|
|
494
|
+
.action(() => {
|
|
495
|
+
const registry = new TemplateRegistry();
|
|
496
|
+
const templates = registry.list();
|
|
497
|
+
if (!templates.length) {
|
|
498
|
+
console.log(chalk.yellow('No templates found.'));
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
console.log(chalk.bold('Templates:'));
|
|
502
|
+
for (const template of templates) {
|
|
503
|
+
console.log(` ${chalk.cyan(template.id)}: ${template.name}`);
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
// --- connect ---
|
|
507
|
+
program
|
|
508
|
+
.command('connect <service>')
|
|
509
|
+
.description('Connect a service (OAuth flow)')
|
|
510
|
+
.option('--client-id <id>', 'OAuth client ID')
|
|
511
|
+
.option('--client-secret <secret>', 'OAuth client secret')
|
|
512
|
+
.option('--tenant-id <tenant>', 'Microsoft tenant ID (for Outlook)')
|
|
513
|
+
.action(async (service, options) => {
|
|
514
|
+
const serviceLower = service.toLowerCase();
|
|
515
|
+
console.log(chalk.bold(`Connecting ${service}...`));
|
|
516
|
+
// Services that support OAuth flow
|
|
517
|
+
if (serviceLower === 'gmail') {
|
|
518
|
+
const clientId = options.clientId ?? process.env.GOOGLE_CLIENT_ID;
|
|
519
|
+
const clientSecret = options.clientSecret ?? process.env.GOOGLE_CLIENT_SECRET;
|
|
520
|
+
if (!clientId || !clientSecret) {
|
|
521
|
+
console.log(chalk.yellow('\nGmail OAuth requires client credentials.'));
|
|
522
|
+
console.log('\nTo connect Gmail:');
|
|
523
|
+
console.log(' 1. Go to https://console.cloud.google.com/');
|
|
524
|
+
console.log(' 2. Create OAuth 2.0 credentials (Desktop app type)');
|
|
525
|
+
console.log(' 3. Run: marktoflow connect gmail --client-id YOUR_ID --client-secret YOUR_SECRET');
|
|
526
|
+
console.log('\nOr set environment variables:');
|
|
527
|
+
console.log(' export GOOGLE_CLIENT_ID="your-client-id"');
|
|
528
|
+
console.log(' export GOOGLE_CLIENT_SECRET="your-client-secret"');
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
try {
|
|
532
|
+
const { runGmailOAuth } = await import('./oauth.js');
|
|
533
|
+
const tokens = await runGmailOAuth({ clientId, clientSecret });
|
|
534
|
+
console.log(chalk.green('\nGmail connected successfully!'));
|
|
535
|
+
console.log(chalk.dim(`Access token expires: ${tokens.expires_at ? new Date(tokens.expires_at).toISOString() : 'unknown'}`));
|
|
536
|
+
console.log('\nYou can now use Gmail in your workflows:');
|
|
537
|
+
console.log(chalk.cyan(` tools:
|
|
538
|
+
gmail:
|
|
539
|
+
sdk: "googleapis"
|
|
540
|
+
auth:
|
|
541
|
+
client_id: "\${GOOGLE_CLIENT_ID}"
|
|
542
|
+
client_secret: "\${GOOGLE_CLIENT_SECRET}"
|
|
543
|
+
redirect_uri: "http://localhost:8484/callback"
|
|
544
|
+
refresh_token: "\${GMAIL_REFRESH_TOKEN}"`));
|
|
545
|
+
}
|
|
546
|
+
catch (error) {
|
|
547
|
+
console.log(chalk.red(`\nOAuth failed: ${error}`));
|
|
548
|
+
process.exit(1);
|
|
549
|
+
}
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
if (serviceLower === 'outlook' || serviceLower === 'microsoft') {
|
|
553
|
+
const clientId = options.clientId ?? process.env.MICROSOFT_CLIENT_ID;
|
|
554
|
+
const clientSecret = options.clientSecret ?? process.env.MICROSOFT_CLIENT_SECRET;
|
|
555
|
+
const tenantId = options.tenantId ?? process.env.MICROSOFT_TENANT_ID;
|
|
556
|
+
if (!clientId) {
|
|
557
|
+
console.log(chalk.yellow('\nOutlook OAuth requires a client ID.'));
|
|
558
|
+
console.log('\nTo connect Outlook/Microsoft Graph:');
|
|
559
|
+
console.log(' 1. Go to https://portal.azure.com/');
|
|
560
|
+
console.log(' 2. Register an application in Azure AD');
|
|
561
|
+
console.log(' 3. Add redirect URI: http://localhost:8484/callback');
|
|
562
|
+
console.log(' 4. Grant Mail.Read, Mail.Send, Calendars.ReadWrite permissions');
|
|
563
|
+
console.log(' 5. Run: marktoflow connect outlook --client-id YOUR_ID');
|
|
564
|
+
console.log('\nOr set environment variables:');
|
|
565
|
+
console.log(' export MICROSOFT_CLIENT_ID="your-client-id"');
|
|
566
|
+
console.log(' export MICROSOFT_CLIENT_SECRET="your-client-secret" # optional');
|
|
567
|
+
console.log(' export MICROSOFT_TENANT_ID="common" # or your tenant ID');
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
try {
|
|
571
|
+
const { runOutlookOAuth } = await import('./oauth.js');
|
|
572
|
+
const tokens = await runOutlookOAuth({ clientId, clientSecret, tenantId });
|
|
573
|
+
console.log(chalk.green('\nOutlook connected successfully!'));
|
|
574
|
+
console.log(chalk.dim(`Access token expires: ${tokens.expires_at ? new Date(tokens.expires_at).toISOString() : 'unknown'}`));
|
|
575
|
+
console.log('\nYou can now use Outlook in your workflows:');
|
|
576
|
+
console.log(chalk.cyan(` tools:
|
|
577
|
+
outlook:
|
|
578
|
+
sdk: "@microsoft/microsoft-graph-client"
|
|
579
|
+
auth:
|
|
580
|
+
token: "\${OUTLOOK_ACCESS_TOKEN}"`));
|
|
581
|
+
}
|
|
582
|
+
catch (error) {
|
|
583
|
+
console.log(chalk.red(`\nOAuth failed: ${error}`));
|
|
584
|
+
process.exit(1);
|
|
585
|
+
}
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
// Other services - show manual setup instructions
|
|
589
|
+
console.log('\nManual setup required. Set environment variables:');
|
|
590
|
+
switch (serviceLower) {
|
|
591
|
+
case 'slack':
|
|
592
|
+
console.log(` export SLACK_BOT_TOKEN="xoxb-your-token"`);
|
|
593
|
+
console.log(` export SLACK_APP_TOKEN="xapp-your-token"`);
|
|
594
|
+
console.log(chalk.dim('\n Get tokens from https://api.slack.com/apps'));
|
|
595
|
+
break;
|
|
596
|
+
case 'github':
|
|
597
|
+
console.log(` export GITHUB_TOKEN="ghp_your-token"`);
|
|
598
|
+
console.log(chalk.dim('\n Create token at https://github.com/settings/tokens'));
|
|
599
|
+
break;
|
|
600
|
+
case 'jira':
|
|
601
|
+
console.log(` export JIRA_HOST="https://your-domain.atlassian.net"`);
|
|
602
|
+
console.log(` export JIRA_EMAIL="your-email@example.com"`);
|
|
603
|
+
console.log(` export JIRA_API_TOKEN="your-api-token"`);
|
|
604
|
+
console.log(chalk.dim('\n Create token at https://id.atlassian.com/manage-profile/security/api-tokens'));
|
|
605
|
+
break;
|
|
606
|
+
case 'confluence':
|
|
607
|
+
console.log(` export CONFLUENCE_HOST="https://your-domain.atlassian.net"`);
|
|
608
|
+
console.log(` export CONFLUENCE_EMAIL="your-email@example.com"`);
|
|
609
|
+
console.log(` export CONFLUENCE_API_TOKEN="your-api-token"`);
|
|
610
|
+
console.log(chalk.dim('\n Create token at https://id.atlassian.com/manage-profile/security/api-tokens'));
|
|
611
|
+
break;
|
|
612
|
+
case 'linear':
|
|
613
|
+
console.log(` export LINEAR_API_KEY="lin_api_your-key"`);
|
|
614
|
+
console.log(chalk.dim('\n Create key at https://linear.app/settings/api'));
|
|
615
|
+
break;
|
|
616
|
+
case 'notion':
|
|
617
|
+
console.log(` export NOTION_TOKEN="secret_your-token"`);
|
|
618
|
+
console.log(chalk.dim('\n Create integration at https://www.notion.so/my-integrations'));
|
|
619
|
+
break;
|
|
620
|
+
case 'discord':
|
|
621
|
+
console.log(` export DISCORD_BOT_TOKEN="your-bot-token"`);
|
|
622
|
+
console.log(chalk.dim('\n Create bot at https://discord.com/developers/applications'));
|
|
623
|
+
break;
|
|
624
|
+
case 'airtable':
|
|
625
|
+
console.log(` export AIRTABLE_TOKEN="pat_your-token"`);
|
|
626
|
+
console.log(` export AIRTABLE_BASE_ID="appXXXXX" # optional default base`);
|
|
627
|
+
console.log(chalk.dim('\n Create token at https://airtable.com/create/tokens'));
|
|
628
|
+
break;
|
|
629
|
+
case 'anthropic':
|
|
630
|
+
console.log(` export ANTHROPIC_API_KEY="sk-ant-your-key"`);
|
|
631
|
+
console.log(chalk.dim('\n Get key at https://console.anthropic.com/'));
|
|
632
|
+
break;
|
|
633
|
+
case 'openai':
|
|
634
|
+
console.log(` export OPENAI_API_KEY="sk-your-key"`);
|
|
635
|
+
console.log(chalk.dim('\n Get key at https://platform.openai.com/api-keys'));
|
|
636
|
+
break;
|
|
637
|
+
default:
|
|
638
|
+
console.log(` See documentation for ${service} configuration.`);
|
|
639
|
+
console.log('\n' + chalk.bold('Available services:'));
|
|
640
|
+
console.log(' Communication: slack, discord');
|
|
641
|
+
console.log(' Email: gmail, outlook');
|
|
642
|
+
console.log(' Project management: jira, linear');
|
|
643
|
+
console.log(' Documentation: notion, confluence');
|
|
644
|
+
console.log(' Developer: github');
|
|
645
|
+
console.log(' Data: airtable');
|
|
646
|
+
console.log(' AI: anthropic, openai');
|
|
647
|
+
}
|
|
648
|
+
});
|
|
649
|
+
// --- doctor ---
|
|
650
|
+
program
|
|
651
|
+
.command('doctor')
|
|
652
|
+
.description('Check environment and configuration')
|
|
653
|
+
.action(async () => {
|
|
654
|
+
console.log(chalk.bold('marktoflow Doctor\n'));
|
|
655
|
+
// Node version
|
|
656
|
+
const nodeVersion = process.version;
|
|
657
|
+
const nodeMajor = parseInt(nodeVersion.slice(1).split('.')[0]);
|
|
658
|
+
if (nodeMajor >= 20) {
|
|
659
|
+
console.log(chalk.green('✓') + ` Node.js ${nodeVersion}`);
|
|
660
|
+
}
|
|
661
|
+
else {
|
|
662
|
+
console.log(chalk.red('✗') + ` Node.js ${nodeVersion} (requires >=20)`);
|
|
663
|
+
}
|
|
664
|
+
// Project initialized
|
|
665
|
+
if (existsSync('.marktoflow')) {
|
|
666
|
+
console.log(chalk.green('✓') + ' Project initialized');
|
|
667
|
+
// Count workflows
|
|
668
|
+
const workflowsDir = '.marktoflow/workflows';
|
|
669
|
+
if (existsSync(workflowsDir)) {
|
|
670
|
+
const { readdirSync } = await import('node:fs');
|
|
671
|
+
const workflows = readdirSync(workflowsDir).filter((f) => f.endsWith('.md'));
|
|
672
|
+
console.log(chalk.green('✓') + ` ${workflows.length} workflow(s) found`);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
else {
|
|
676
|
+
console.log(chalk.yellow('○') + ' Project not initialized');
|
|
677
|
+
}
|
|
678
|
+
// Check for common environment variables
|
|
679
|
+
const envChecks = [
|
|
680
|
+
// Communication
|
|
681
|
+
['SLACK_BOT_TOKEN', 'Slack'],
|
|
682
|
+
['DISCORD_BOT_TOKEN', 'Discord'],
|
|
683
|
+
// Email
|
|
684
|
+
['GOOGLE_CLIENT_ID', 'Gmail'],
|
|
685
|
+
['MICROSOFT_CLIENT_ID', 'Outlook'],
|
|
686
|
+
// Project Management
|
|
687
|
+
['JIRA_API_TOKEN', 'Jira'],
|
|
688
|
+
['LINEAR_API_KEY', 'Linear'],
|
|
689
|
+
// Documentation
|
|
690
|
+
['NOTION_TOKEN', 'Notion'],
|
|
691
|
+
['CONFLUENCE_API_TOKEN', 'Confluence'],
|
|
692
|
+
// Developer
|
|
693
|
+
['GITHUB_TOKEN', 'GitHub'],
|
|
694
|
+
// Data
|
|
695
|
+
['AIRTABLE_TOKEN', 'Airtable'],
|
|
696
|
+
// AI
|
|
697
|
+
['ANTHROPIC_API_KEY', 'Anthropic'],
|
|
698
|
+
['OPENAI_API_KEY', 'OpenAI'],
|
|
699
|
+
];
|
|
700
|
+
console.log('\n' + chalk.bold('Services:'));
|
|
701
|
+
let configuredCount = 0;
|
|
702
|
+
for (const [envVar, name] of envChecks) {
|
|
703
|
+
if (process.env[envVar]) {
|
|
704
|
+
console.log(chalk.green('✓') + ` ${name} configured`);
|
|
705
|
+
configuredCount++;
|
|
706
|
+
}
|
|
707
|
+
else {
|
|
708
|
+
console.log(chalk.dim('○') + ` ${name} not configured`);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
if (configuredCount === 0) {
|
|
712
|
+
console.log(chalk.yellow('\n Run `marktoflow connect <service>` to set up integrations'));
|
|
713
|
+
}
|
|
714
|
+
});
|
|
715
|
+
// --- version ---
|
|
716
|
+
program
|
|
717
|
+
.command('version')
|
|
718
|
+
.description('Show version information')
|
|
719
|
+
.action(() => {
|
|
720
|
+
console.log(`marktoflow v${VERSION}`);
|
|
721
|
+
});
|
|
722
|
+
// ============================================================================
|
|
723
|
+
// Parse and Execute
|
|
724
|
+
// ============================================================================
|
|
725
|
+
program.parse();
|
|
726
|
+
//# sourceMappingURL=index.js.map
|