@hyperdrive.bot/bmad-workflow 1.0.18 → 1.0.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/config/show.js +8 -2
- package/dist/commands/decompose.js +26 -5
- package/dist/commands/epics/create.d.ts +1 -0
- package/dist/commands/mcp/add.d.ts +16 -0
- package/dist/commands/mcp/add.js +77 -0
- package/dist/commands/mcp/credential/get.d.ts +14 -0
- package/dist/commands/mcp/credential/get.js +35 -0
- package/dist/commands/mcp/credential/list.d.ts +17 -0
- package/dist/commands/mcp/credential/list.js +67 -0
- package/dist/commands/mcp/credential/remove.d.ts +18 -0
- package/dist/commands/mcp/credential/remove.js +84 -0
- package/dist/commands/mcp/credential/set.d.ts +16 -0
- package/dist/commands/mcp/credential/set.js +41 -0
- package/dist/commands/mcp/credential/validate.d.ts +12 -0
- package/dist/commands/mcp/credential/validate.js +150 -0
- package/dist/commands/mcp/list.d.ts +17 -0
- package/dist/commands/mcp/list.js +80 -0
- package/dist/commands/mcp/logs.d.ts +15 -0
- package/dist/commands/mcp/logs.js +64 -0
- package/dist/commands/mcp/preset.d.ts +15 -0
- package/dist/commands/mcp/preset.js +84 -0
- package/dist/commands/mcp/remove.d.ts +14 -0
- package/dist/commands/mcp/remove.js +36 -0
- package/dist/commands/mcp/start.d.ts +12 -0
- package/dist/commands/mcp/start.js +80 -0
- package/dist/commands/mcp/status.d.ts +30 -0
- package/dist/commands/mcp/status.js +180 -0
- package/dist/commands/mcp/stop.d.ts +12 -0
- package/dist/commands/mcp/stop.js +47 -0
- package/dist/commands/stories/create.d.ts +1 -0
- package/dist/commands/stories/develop.d.ts +1 -0
- package/dist/commands/stories/qa.js +5 -2
- package/dist/commands/stories/review.d.ts +124 -0
- package/dist/commands/stories/review.js +516 -0
- package/dist/commands/workflow.d.ts +8 -0
- package/dist/commands/workflow.js +110 -2
- package/dist/mcp/types.d.ts +99 -0
- package/dist/mcp/types.js +7 -0
- package/dist/mcp/utils/docker-utils.d.ts +56 -0
- package/dist/mcp/utils/docker-utils.js +108 -0
- package/dist/mcp/utils/template-loader.d.ts +21 -0
- package/dist/mcp/utils/template-loader.js +60 -0
- package/dist/models/agent-options.d.ts +10 -1
- package/dist/models/workflow-config.d.ts +77 -0
- package/dist/models/workflow-result.d.ts +7 -0
- package/dist/services/agents/claude-agent-runner.js +19 -3
- package/dist/services/file-system/path-resolver.d.ts +10 -0
- package/dist/services/file-system/path-resolver.js +12 -0
- package/dist/services/mcp/mcp-config-manager.d.ts +54 -0
- package/dist/services/mcp/mcp-config-manager.js +146 -0
- package/dist/services/mcp/mcp-context-injector.d.ts +92 -0
- package/dist/services/mcp/mcp-context-injector.js +168 -0
- package/dist/services/mcp/mcp-credential-manager.d.ts +48 -0
- package/dist/services/mcp/mcp-credential-manager.js +124 -0
- package/dist/services/mcp/mcp-health-checker.d.ts +56 -0
- package/dist/services/mcp/mcp-health-checker.js +162 -0
- package/dist/services/mcp/types/health-types.d.ts +31 -0
- package/dist/services/mcp/types/health-types.js +7 -0
- package/dist/services/orchestration/dependency-graph-executor.js +1 -1
- package/dist/services/orchestration/task-decomposition-service.d.ts +2 -1
- package/dist/services/orchestration/task-decomposition-service.js +90 -36
- package/dist/services/orchestration/workflow-orchestrator.d.ts +54 -2
- package/dist/services/orchestration/workflow-orchestrator.js +303 -17
- package/dist/services/review/ai-review-scanner.d.ts +66 -0
- package/dist/services/review/ai-review-scanner.js +142 -0
- package/dist/services/review/coderabbit-scanner.d.ts +25 -0
- package/dist/services/review/coderabbit-scanner.js +31 -0
- package/dist/services/review/index.d.ts +20 -0
- package/dist/services/review/index.js +15 -0
- package/dist/services/review/lint-scanner.d.ts +46 -0
- package/dist/services/review/lint-scanner.js +172 -0
- package/dist/services/review/review-config.d.ts +62 -0
- package/dist/services/review/review-config.js +91 -0
- package/dist/services/review/review-phase-executor.d.ts +69 -0
- package/dist/services/review/review-phase-executor.js +152 -0
- package/dist/services/review/review-queue.d.ts +98 -0
- package/dist/services/review/review-queue.js +174 -0
- package/dist/services/review/review-reporter.d.ts +94 -0
- package/dist/services/review/review-reporter.js +386 -0
- package/dist/services/review/scanner-factory.d.ts +42 -0
- package/dist/services/review/scanner-factory.js +60 -0
- package/dist/services/review/self-heal-loop.d.ts +58 -0
- package/dist/services/review/self-heal-loop.js +132 -0
- package/dist/services/review/severity-classifier.d.ts +17 -0
- package/dist/services/review/severity-classifier.js +314 -0
- package/dist/services/review/tech-debt-tracker.d.ts +52 -0
- package/dist/services/review/tech-debt-tracker.js +245 -0
- package/dist/services/review/types.d.ts +93 -0
- package/dist/services/review/types.js +23 -0
- package/dist/services/validation/config-validator.d.ts +84 -0
- package/dist/services/validation/config-validator.js +78 -0
- package/dist/utils/credential-utils.d.ts +14 -0
- package/dist/utils/credential-utils.js +19 -0
- package/dist/utils/duration.d.ts +41 -0
- package/dist/utils/duration.js +89 -0
- package/dist/utils/shared-flags.d.ts +1 -0
- package/dist/utils/shared-flags.js +11 -2
- package/package.json +4 -2
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { Command, Flags } from '@oclif/core';
|
|
8
8
|
import { dump, load } from 'js-yaml';
|
|
9
9
|
import { FileManager } from '../../services/file-system/file-manager.js';
|
|
10
|
+
import { buildReviewConfig } from '../../services/review/review-config.js';
|
|
10
11
|
import * as colors from '../../utils/colors.js';
|
|
11
12
|
import { ConfigurationError, FileSystemError } from '../../utils/errors.js';
|
|
12
13
|
import { createLogger } from '../../utils/logger.js';
|
|
@@ -76,16 +77,21 @@ export default class Show extends Command {
|
|
|
76
77
|
this.error(colors.error(errorMsg), { exit: 1 });
|
|
77
78
|
return;
|
|
78
79
|
}
|
|
80
|
+
// Ensure review section is always present with defaults for display
|
|
81
|
+
const configObj = (config || {});
|
|
82
|
+
if (!configObj.review) {
|
|
83
|
+
configObj.review = buildReviewConfig();
|
|
84
|
+
}
|
|
79
85
|
// Display configuration in requested format
|
|
80
86
|
if (flags.json) {
|
|
81
87
|
// JSON output - machine-readable
|
|
82
|
-
this.log(JSON.stringify(
|
|
88
|
+
this.log(JSON.stringify(configObj, null, 2));
|
|
83
89
|
}
|
|
84
90
|
else {
|
|
85
91
|
// YAML output - human-readable with formatting
|
|
86
92
|
this.log(colors.bold('Configuration (.bmad-core/core-config.yaml):'));
|
|
87
93
|
this.log('');
|
|
88
|
-
this.log(dump(
|
|
94
|
+
this.log(dump(configObj, { indent: 2, lineWidth: 120 }));
|
|
89
95
|
}
|
|
90
96
|
logger.info('Configuration displayed successfully');
|
|
91
97
|
}
|
|
@@ -14,6 +14,7 @@ import { DependencyGraphExecutor } from '../services/orchestration/dependency-gr
|
|
|
14
14
|
import { TaskDecompositionService } from '../services/orchestration/task-decomposition-service.js';
|
|
15
15
|
import { DecomposeSessionScaffolder } from '../services/scaffolding/decompose-session-scaffolder.js';
|
|
16
16
|
import * as colors from '../utils/colors.js';
|
|
17
|
+
import { parseDuration } from '../utils/duration.js';
|
|
17
18
|
import { formatBox, formatTable } from '../utils/formatters.js';
|
|
18
19
|
import { createLogger } from '../utils/logger.js';
|
|
19
20
|
/**
|
|
@@ -100,9 +101,11 @@ export default class Decompose extends Command {
|
|
|
100
101
|
'story-prefix': Flags.string({
|
|
101
102
|
description: 'Project prefix for story IDs (e.g., "MIGRATE", "REFACTOR") - used with --story-format',
|
|
102
103
|
}),
|
|
103
|
-
'task-timeout': Flags.
|
|
104
|
+
'task-timeout': Flags.custom({
|
|
105
|
+
parse: async (input) => parseDuration(input),
|
|
106
|
+
})({
|
|
104
107
|
default: 1_800_000, // 30 minutes
|
|
105
|
-
description: 'Timeout per task
|
|
108
|
+
description: 'Timeout per task — accepts durations like 30s, 5m, 1h, 90m, or raw milliseconds (default: 30m)',
|
|
106
109
|
}),
|
|
107
110
|
verbose: Flags.boolean({
|
|
108
111
|
char: 'v',
|
|
@@ -119,6 +122,7 @@ export default class Decompose extends Command {
|
|
|
119
122
|
*/
|
|
120
123
|
async run() {
|
|
121
124
|
const { args, flags } = await this.parse(Decompose);
|
|
125
|
+
let sessionDir;
|
|
122
126
|
try {
|
|
123
127
|
// Validate provider
|
|
124
128
|
if (!isProviderSupported(flags.provider)) {
|
|
@@ -160,14 +164,17 @@ export default class Decompose extends Command {
|
|
|
160
164
|
}
|
|
161
165
|
// Create session directory
|
|
162
166
|
const timestamp = new Date().toISOString().replaceAll(/[:.]/g, '-').split('.')[0];
|
|
163
|
-
|
|
167
|
+
sessionDir = join(flags['output-dir'], `session-${timestamp}`);
|
|
164
168
|
await this.fileManager.createDirectory(sessionDir);
|
|
165
169
|
this.log(colors.info(`📁 Session directory: ${sessionDir}\n`));
|
|
166
170
|
// Phase 1: Decompose goal into task graph
|
|
167
|
-
this.log(colors.bold('⚙️ Phase 1: Decomposing goal into task graph
|
|
171
|
+
this.log(colors.bold('⚙️ Phase 1: Decomposing goal into task graph...'));
|
|
172
|
+
this.log(colors.dim(' (sending goal to AI agent — this typically takes 2-5 minutes)\n'));
|
|
173
|
+
const startPhase1 = Date.now();
|
|
168
174
|
const decompositionService = new TaskDecompositionService(createAgentRunner(flags.provider, this.logger), this.fileManager, new GlobMatcher(this.fileManager, this.logger), this.logger);
|
|
169
175
|
const taskGraph = await decompositionService.decomposeGoal(options, sessionDir);
|
|
170
|
-
|
|
176
|
+
const phase1Duration = ((Date.now() - startPhase1) / 1000).toFixed(1);
|
|
177
|
+
this.log(colors.success(`✓ Task graph generated in ${phase1Duration}s!\n`));
|
|
171
178
|
this.displayTaskGraphSummary(taskGraph);
|
|
172
179
|
// Phase 2: Scaffold session structure
|
|
173
180
|
this.log(colors.bold('\n⚙️ Phase 2: Creating session structure...\n'));
|
|
@@ -229,6 +236,20 @@ export default class Decompose extends Command {
|
|
|
229
236
|
this.logger.error({ error: error.message }, 'Decompose command failed');
|
|
230
237
|
this.log('\n' + colors.error('✗ Decompose failed:'));
|
|
231
238
|
this.log(colors.error(` ${error.message}`));
|
|
239
|
+
// Clean up empty session directories left by failed runs
|
|
240
|
+
if (sessionDir) {
|
|
241
|
+
try {
|
|
242
|
+
const { readdir, rmdir } = await import('node:fs/promises');
|
|
243
|
+
const contents = await readdir(sessionDir);
|
|
244
|
+
if (contents.length === 0) {
|
|
245
|
+
await rmdir(sessionDir);
|
|
246
|
+
this.logger.debug({ sessionDir }, 'Cleaned up empty session directory');
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
// Ignore cleanup errors
|
|
251
|
+
}
|
|
252
|
+
}
|
|
232
253
|
this.error('Execution failed', { exit: 1 });
|
|
233
254
|
}
|
|
234
255
|
}
|
|
@@ -41,6 +41,7 @@ export default class EpicsCreate extends Command {
|
|
|
41
41
|
provider: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
42
42
|
task: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
43
43
|
timeout: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
44
|
+
'review-timeout': import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
44
45
|
'max-retries': import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
45
46
|
'retry-backoff': import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
46
47
|
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Add Command
|
|
3
|
+
*
|
|
4
|
+
* Adds an MCP server to the active configuration by loading its template
|
|
5
|
+
* from server-templates/ and persisting it via McpConfigManager.
|
|
6
|
+
* Prompts for API key if required and not already configured.
|
|
7
|
+
*/
|
|
8
|
+
import { Command } from '@oclif/core';
|
|
9
|
+
export default class McpAdd extends Command {
|
|
10
|
+
static args: {
|
|
11
|
+
server: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
12
|
+
};
|
|
13
|
+
static description: string;
|
|
14
|
+
static examples: string[];
|
|
15
|
+
run(): Promise<void>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Add Command
|
|
3
|
+
*
|
|
4
|
+
* Adds an MCP server to the active configuration by loading its template
|
|
5
|
+
* from server-templates/ and persisting it via McpConfigManager.
|
|
6
|
+
* Prompts for API key if required and not already configured.
|
|
7
|
+
*/
|
|
8
|
+
import { Args, Command } from '@oclif/core';
|
|
9
|
+
import chalk from 'chalk';
|
|
10
|
+
import { listAvailableTemplates, loadServerTemplate } from '../../mcp/utils/template-loader.js';
|
|
11
|
+
import { McpConfigManager } from '../../services/mcp/mcp-config-manager.js';
|
|
12
|
+
import { McpCredentialManager } from '../../services/mcp/mcp-credential-manager.js';
|
|
13
|
+
import { createLogger } from '../../utils/logger.js';
|
|
14
|
+
const logger = createLogger({ namespace: 'commands:mcp:add' });
|
|
15
|
+
export default class McpAdd extends Command {
|
|
16
|
+
static args = {
|
|
17
|
+
server: Args.string({
|
|
18
|
+
description: 'Name of the MCP server template to add',
|
|
19
|
+
required: true,
|
|
20
|
+
}),
|
|
21
|
+
};
|
|
22
|
+
static description = 'Add an MCP server to the active gateway configuration';
|
|
23
|
+
static examples = [
|
|
24
|
+
'<%= config.bin %> mcp add context7',
|
|
25
|
+
'<%= config.bin %> mcp add exa',
|
|
26
|
+
'<%= config.bin %> mcp add playwright',
|
|
27
|
+
];
|
|
28
|
+
async run() {
|
|
29
|
+
const { args } = await this.parse(McpAdd);
|
|
30
|
+
const serverName = args.server;
|
|
31
|
+
logger.info('Adding MCP server: %s', serverName);
|
|
32
|
+
// 1. Load the server template
|
|
33
|
+
let template;
|
|
34
|
+
try {
|
|
35
|
+
template = loadServerTemplate(serverName);
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
const available = listAvailableTemplates();
|
|
39
|
+
this.error(`Server template '${serverName}' not found.\n\nAvailable templates: ${available.join(', ')}`, { exit: 1 });
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// 2. Add to config
|
|
43
|
+
const configManager = new McpConfigManager();
|
|
44
|
+
configManager.addServer(template);
|
|
45
|
+
this.log(chalk.green(`Server '${serverName}' added to configuration`));
|
|
46
|
+
// 3. Check credential requirements
|
|
47
|
+
if (template.api_key_required && template.env) {
|
|
48
|
+
const credentialManager = new McpCredentialManager();
|
|
49
|
+
const envVarNames = Object.keys(template.env);
|
|
50
|
+
for (const envVar of envVarNames) {
|
|
51
|
+
const existing = credentialManager.resolve(envVar);
|
|
52
|
+
if (existing) {
|
|
53
|
+
this.log(chalk.dim(` Credential ${envVar}: already configured`));
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
// Prompt for API key if running interactively
|
|
57
|
+
if (process.stdin.isTTY) {
|
|
58
|
+
const { password } = await import('@inquirer/prompts');
|
|
59
|
+
const value = await password({
|
|
60
|
+
mask: '*',
|
|
61
|
+
message: `Enter value for ${envVar}:`,
|
|
62
|
+
});
|
|
63
|
+
if (value) {
|
|
64
|
+
credentialManager.set(envVar, value);
|
|
65
|
+
this.log(chalk.green(` Credential set for ${envVar}`));
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
this.log(chalk.yellow(` Warning: ${envVar} not set. Server may not work without it.`));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
this.log(chalk.yellow(` Warning: ${envVar} is required but not set. Run in interactive mode or set the environment variable.`));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Credential Get Command
|
|
3
|
+
*
|
|
4
|
+
* Retrieves and displays a stored credential value in masked format.
|
|
5
|
+
*/
|
|
6
|
+
import { Command } from '@oclif/core';
|
|
7
|
+
export default class CredentialGet extends Command {
|
|
8
|
+
static args: {
|
|
9
|
+
key: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
10
|
+
};
|
|
11
|
+
static description: string;
|
|
12
|
+
static examples: string[];
|
|
13
|
+
run(): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Credential Get Command
|
|
3
|
+
*
|
|
4
|
+
* Retrieves and displays a stored credential value in masked format.
|
|
5
|
+
*/
|
|
6
|
+
import { Args, Command } from '@oclif/core';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import { McpCredentialManager } from '../../../services/mcp/mcp-credential-manager.js';
|
|
9
|
+
import { createLogger } from '../../../utils/logger.js';
|
|
10
|
+
import { maskValue } from '../../../utils/credential-utils.js';
|
|
11
|
+
const logger = createLogger({ namespace: 'commands:mcp:credential:get' });
|
|
12
|
+
export default class CredentialGet extends Command {
|
|
13
|
+
static args = {
|
|
14
|
+
key: Args.string({
|
|
15
|
+
description: 'Credential key name (e.g., EXA_API_KEY)',
|
|
16
|
+
required: true,
|
|
17
|
+
}),
|
|
18
|
+
};
|
|
19
|
+
static description = 'Get an MCP server credential (masked display)';
|
|
20
|
+
static examples = [
|
|
21
|
+
'<%= config.bin %> mcp credential get EXA_API_KEY',
|
|
22
|
+
];
|
|
23
|
+
async run() {
|
|
24
|
+
const { args } = await this.parse(CredentialGet);
|
|
25
|
+
const { key } = args;
|
|
26
|
+
logger.info('Getting credential: %s', key);
|
|
27
|
+
const credentialManager = new McpCredentialManager();
|
|
28
|
+
const value = credentialManager.resolve(key);
|
|
29
|
+
if (!value) {
|
|
30
|
+
this.error(chalk.red(`Credential '${key}' not found`), { exit: 1 });
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
this.log(`${chalk.bold(key)}: ${maskValue(value)}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Credential List Command
|
|
3
|
+
*
|
|
4
|
+
* Displays all stored credential keys with masked values, indicating
|
|
5
|
+
* which are required by currently enabled servers.
|
|
6
|
+
*/
|
|
7
|
+
import { Command } from '@oclif/core';
|
|
8
|
+
export default class CredentialList extends Command {
|
|
9
|
+
static description: string;
|
|
10
|
+
static examples: string[];
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
/**
|
|
13
|
+
* Map server name to known env var keys.
|
|
14
|
+
* This is a simple lookup based on known server templates.
|
|
15
|
+
*/
|
|
16
|
+
private getServerEnvKeys;
|
|
17
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Credential List Command
|
|
3
|
+
*
|
|
4
|
+
* Displays all stored credential keys with masked values, indicating
|
|
5
|
+
* which are required by currently enabled servers.
|
|
6
|
+
*/
|
|
7
|
+
import { Command } from '@oclif/core';
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
import { McpConfigManager } from '../../../services/mcp/mcp-config-manager.js';
|
|
10
|
+
import { McpCredentialManager } from '../../../services/mcp/mcp-credential-manager.js';
|
|
11
|
+
import { createLogger } from '../../../utils/logger.js';
|
|
12
|
+
import { maskValue } from '../../../utils/credential-utils.js';
|
|
13
|
+
const logger = createLogger({ namespace: 'commands:mcp:credential:list' });
|
|
14
|
+
export default class CredentialList extends Command {
|
|
15
|
+
static description = 'List all stored MCP server credentials';
|
|
16
|
+
static examples = [
|
|
17
|
+
'<%= config.bin %> mcp credential list',
|
|
18
|
+
];
|
|
19
|
+
async run() {
|
|
20
|
+
logger.info('Listing credentials');
|
|
21
|
+
const credentialManager = new McpCredentialManager();
|
|
22
|
+
const configManager = new McpConfigManager();
|
|
23
|
+
const entries = credentialManager.list();
|
|
24
|
+
if (entries.length === 0) {
|
|
25
|
+
this.log(chalk.dim('No credentials stored. Use "bmad-workflow mcp credential set <key> <value>" to add one.'));
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
// Determine which env var keys are required by enabled servers
|
|
29
|
+
const enabledServers = configManager.getEnabledServers();
|
|
30
|
+
const requiredKeys = new Set();
|
|
31
|
+
// We need to check the full server templates to get env var names
|
|
32
|
+
// For now, servers with apiKeyRequired=true imply their env keys are needed
|
|
33
|
+
for (const server of enabledServers) {
|
|
34
|
+
if (server.apiKeyRequired && server.enabled) {
|
|
35
|
+
// Map server name to known env keys via template
|
|
36
|
+
const envKeys = this.getServerEnvKeys(server.name);
|
|
37
|
+
for (const k of envKeys) {
|
|
38
|
+
requiredKeys.add(k);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
this.log(chalk.bold('\nMCP Credentials\n'));
|
|
43
|
+
this.log(`${chalk.dim('Key'.padEnd(25))} ${chalk.dim('Value'.padEnd(25))} ${chalk.dim('Status')}`);
|
|
44
|
+
this.log(chalk.dim('─'.repeat(70)));
|
|
45
|
+
for (const entry of entries) {
|
|
46
|
+
const value = credentialManager.resolve(entry.key);
|
|
47
|
+
const masked = value ? maskValue(value) : chalk.dim('not set');
|
|
48
|
+
const required = requiredKeys.has(entry.key);
|
|
49
|
+
const status = entry.configured
|
|
50
|
+
? (required ? chalk.green('configured (required)') : chalk.green('configured'))
|
|
51
|
+
: (required ? chalk.yellow('missing (required)') : chalk.dim('not set'));
|
|
52
|
+
this.log(`${entry.key.padEnd(25)} ${masked.padEnd(25)} ${status}`);
|
|
53
|
+
}
|
|
54
|
+
this.log('');
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Map server name to known env var keys.
|
|
58
|
+
* This is a simple lookup based on known server templates.
|
|
59
|
+
*/
|
|
60
|
+
getServerEnvKeys(serverName) {
|
|
61
|
+
const envMapping = {
|
|
62
|
+
apify: ['APIFY_TOKEN'],
|
|
63
|
+
exa: ['EXA_API_KEY'],
|
|
64
|
+
};
|
|
65
|
+
return envMapping[serverName] || [];
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Credential Remove Command
|
|
3
|
+
*
|
|
4
|
+
* Removes a credential entry from the stored credentials file.
|
|
5
|
+
* Warns if the key is required by an enabled server before proceeding.
|
|
6
|
+
*/
|
|
7
|
+
import { Command } from '@oclif/core';
|
|
8
|
+
export default class CredentialRemove extends Command {
|
|
9
|
+
static args: {
|
|
10
|
+
key: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
11
|
+
};
|
|
12
|
+
static description: string;
|
|
13
|
+
static examples: string[];
|
|
14
|
+
static flags: {
|
|
15
|
+
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
16
|
+
};
|
|
17
|
+
run(): Promise<void>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Credential Remove Command
|
|
3
|
+
*
|
|
4
|
+
* Removes a credential entry from the stored credentials file.
|
|
5
|
+
* Warns if the key is required by an enabled server before proceeding.
|
|
6
|
+
*/
|
|
7
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
import { McpConfigManager } from '../../../services/mcp/mcp-config-manager.js';
|
|
10
|
+
import { McpCredentialManager } from '../../../services/mcp/mcp-credential-manager.js';
|
|
11
|
+
import { createLogger } from '../../../utils/logger.js';
|
|
12
|
+
const logger = createLogger({ namespace: 'commands:mcp:credential:remove' });
|
|
13
|
+
/**
|
|
14
|
+
* Map server name to known env var keys.
|
|
15
|
+
*/
|
|
16
|
+
function getServerEnvKeys(serverName) {
|
|
17
|
+
const envMapping = {
|
|
18
|
+
apify: ['APIFY_TOKEN'],
|
|
19
|
+
exa: ['EXA_API_KEY'],
|
|
20
|
+
};
|
|
21
|
+
return envMapping[serverName] || [];
|
|
22
|
+
}
|
|
23
|
+
export default class CredentialRemove extends Command {
|
|
24
|
+
static args = {
|
|
25
|
+
key: Args.string({
|
|
26
|
+
description: 'Credential key name to remove',
|
|
27
|
+
required: true,
|
|
28
|
+
}),
|
|
29
|
+
};
|
|
30
|
+
static description = 'Remove an MCP server credential';
|
|
31
|
+
static examples = [
|
|
32
|
+
'<%= config.bin %> mcp credential remove EXA_API_KEY',
|
|
33
|
+
'<%= config.bin %> mcp credential remove APIFY_TOKEN --force',
|
|
34
|
+
];
|
|
35
|
+
static flags = {
|
|
36
|
+
force: Flags.boolean({
|
|
37
|
+
char: 'f',
|
|
38
|
+
default: false,
|
|
39
|
+
description: 'Skip confirmation when key is required by an enabled server',
|
|
40
|
+
}),
|
|
41
|
+
};
|
|
42
|
+
async run() {
|
|
43
|
+
const { args, flags } = await this.parse(CredentialRemove);
|
|
44
|
+
const { key } = args;
|
|
45
|
+
logger.info('Removing credential: %s', key);
|
|
46
|
+
const credentialManager = new McpCredentialManager();
|
|
47
|
+
const configManager = new McpConfigManager();
|
|
48
|
+
// Check if key is required by any enabled server
|
|
49
|
+
const enabledServers = configManager.getEnabledServers();
|
|
50
|
+
const dependentServers = [];
|
|
51
|
+
for (const server of enabledServers) {
|
|
52
|
+
if (server.apiKeyRequired && server.enabled) {
|
|
53
|
+
const envKeys = getServerEnvKeys(server.name);
|
|
54
|
+
if (envKeys.includes(key)) {
|
|
55
|
+
dependentServers.push(server.name);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (dependentServers.length > 0 && !flags.force) {
|
|
60
|
+
this.log(chalk.yellow(`Warning: '${key}' is required by enabled server(s): ${dependentServers.join(', ')}`));
|
|
61
|
+
this.log(chalk.yellow('Removing this credential may break those servers.'));
|
|
62
|
+
this.log(chalk.dim('Use --force to skip this warning.'));
|
|
63
|
+
if (process.stdin.isTTY) {
|
|
64
|
+
const { confirm } = await import('@inquirer/prompts');
|
|
65
|
+
const proceed = await confirm({ default: false, message: 'Proceed with removal?' });
|
|
66
|
+
if (!proceed) {
|
|
67
|
+
this.log(chalk.dim('Removal cancelled.'));
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
this.error(chalk.red(`Credential '${key}' is required by enabled servers. Use --force to remove.`), { exit: 1 });
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const removed = credentialManager.remove(key);
|
|
77
|
+
if (removed) {
|
|
78
|
+
this.log(chalk.green(`Credential '${key}' removed successfully`));
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
this.log(chalk.yellow(`Credential '${key}' not found in store`));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Credential Set Command
|
|
3
|
+
*
|
|
4
|
+
* Stores an API credential key-value pair to ~/.bmad/credentials/mcp-keys.yaml
|
|
5
|
+
* with file permissions 600. Rejects empty values.
|
|
6
|
+
*/
|
|
7
|
+
import { Command } from '@oclif/core';
|
|
8
|
+
export default class CredentialSet extends Command {
|
|
9
|
+
static args: {
|
|
10
|
+
key: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
11
|
+
value: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
12
|
+
};
|
|
13
|
+
static description: string;
|
|
14
|
+
static examples: string[];
|
|
15
|
+
run(): Promise<void>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Credential Set Command
|
|
3
|
+
*
|
|
4
|
+
* Stores an API credential key-value pair to ~/.bmad/credentials/mcp-keys.yaml
|
|
5
|
+
* with file permissions 600. Rejects empty values.
|
|
6
|
+
*/
|
|
7
|
+
import { Args, Command } from '@oclif/core';
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
import { McpCredentialManager } from '../../../services/mcp/mcp-credential-manager.js';
|
|
10
|
+
import { createLogger } from '../../../utils/logger.js';
|
|
11
|
+
import { maskValue } from '../../../utils/credential-utils.js';
|
|
12
|
+
const logger = createLogger({ namespace: 'commands:mcp:credential:set' });
|
|
13
|
+
export default class CredentialSet extends Command {
|
|
14
|
+
static args = {
|
|
15
|
+
key: Args.string({
|
|
16
|
+
description: 'Credential key name (e.g., EXA_API_KEY)',
|
|
17
|
+
required: true,
|
|
18
|
+
}),
|
|
19
|
+
value: Args.string({
|
|
20
|
+
description: 'Credential value',
|
|
21
|
+
required: true,
|
|
22
|
+
}),
|
|
23
|
+
};
|
|
24
|
+
static description = 'Set an MCP server credential';
|
|
25
|
+
static examples = [
|
|
26
|
+
'<%= config.bin %> mcp credential set EXA_API_KEY sk-exa-abc123',
|
|
27
|
+
'<%= config.bin %> mcp credential set APIFY_TOKEN apify_api_xyz',
|
|
28
|
+
];
|
|
29
|
+
async run() {
|
|
30
|
+
const { args } = await this.parse(CredentialSet);
|
|
31
|
+
const { key, value } = args;
|
|
32
|
+
logger.info('Setting credential: %s', key);
|
|
33
|
+
if (!value || value.trim().length === 0) {
|
|
34
|
+
this.error(chalk.red('Value cannot be empty'), { exit: 1 });
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const credentialManager = new McpCredentialManager();
|
|
38
|
+
credentialManager.set(key, value);
|
|
39
|
+
this.log(chalk.green(`Credential '${key}' set successfully (${maskValue(value)})`));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Credential Validate Command
|
|
3
|
+
*
|
|
4
|
+
* Validates all credentials required by enabled MCP servers by resolving them
|
|
5
|
+
* via the credential resolution chain and testing against live health checks.
|
|
6
|
+
*/
|
|
7
|
+
import { Command } from '@oclif/core';
|
|
8
|
+
export default class CredentialValidate extends Command {
|
|
9
|
+
static description: string;
|
|
10
|
+
static examples: string[];
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
}
|