@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.
- package/README.md +48 -5
- package/dist/commands/agent.d.ts +6 -0
- package/dist/commands/agent.d.ts.map +1 -0
- package/dist/commands/agent.js +56 -0
- package/dist/commands/agent.js.map +1 -0
- package/dist/commands/bundle.d.ts +11 -0
- package/dist/commands/bundle.d.ts.map +1 -0
- package/dist/commands/bundle.js +94 -0
- package/dist/commands/bundle.js.map +1 -0
- package/dist/commands/connect.d.ts +11 -0
- package/dist/commands/connect.d.ts.map +1 -0
- package/dist/commands/connect.js +203 -0
- package/dist/commands/connect.js.map +1 -0
- package/dist/commands/credentials.d.ts +16 -0
- package/dist/commands/credentials.d.ts.map +1 -0
- package/dist/commands/credentials.js +94 -0
- package/dist/commands/credentials.js.map +1 -0
- package/dist/commands/doctor.d.ts +5 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +60 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/gui.d.ts +11 -0
- package/dist/commands/gui.d.ts.map +1 -0
- package/dist/commands/gui.js +55 -0
- package/dist/commands/gui.js.map +1 -0
- package/dist/commands/init.d.ts +8 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +79 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/run.d.ts +13 -0
- package/dist/commands/run.d.ts.map +1 -0
- package/dist/commands/run.js +311 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/commands/schedule.d.ts +5 -0
- package/dist/commands/schedule.d.ts.map +1 -0
- package/dist/commands/schedule.js +19 -0
- package/dist/commands/schedule.js.map +1 -0
- package/dist/commands/template.d.ts +5 -0
- package/dist/commands/template.d.ts.map +1 -0
- package/dist/commands/template.js +18 -0
- package/dist/commands/template.js.map +1 -0
- package/dist/commands/tools.d.ts +5 -0
- package/dist/commands/tools.d.ts.map +1 -0
- package/dist/commands/tools.js +28 -0
- package/dist/commands/tools.js.map +1 -0
- package/dist/commands/workflow.d.ts +5 -0
- package/dist/commands/workflow.d.ts.map +1 -0
- package/dist/commands/workflow.js +31 -0
- package/dist/commands/workflow.js.map +1 -0
- package/dist/i18n.d.ts +17 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/i18n.js +70 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +49 -1040
- package/dist/index.js.map +1 -1
- package/dist/utils/agent-config.d.ts +12 -0
- package/dist/utils/agent-config.d.ts.map +1 -0
- package/dist/utils/agent-config.js +52 -0
- package/dist/utils/agent-config.js.map +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- 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
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
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 {
|
|
20
|
-
import {
|
|
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
|
-
|
|
25
|
-
|
|
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 =
|
|
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')
|