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