@securityreviewai/securityreview-kit 0.1.11
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 +90 -0
- package/bin/securityreview-kit.js +5 -0
- package/package.json +44 -0
- package/src/cli.js +54 -0
- package/src/commands/init.js +332 -0
- package/src/commands/status.js +86 -0
- package/src/commands/switch-project.js +205 -0
- package/src/generators/mcp/claude.js +27 -0
- package/src/generators/mcp/codex.js +44 -0
- package/src/generators/mcp/cursor.js +27 -0
- package/src/generators/mcp/gemini.js +28 -0
- package/src/generators/mcp/vscode.js +29 -0
- package/src/generators/mcp/windsurf.js +27 -0
- package/src/generators/rules/antigravity.js +22 -0
- package/src/generators/rules/claude.js +13 -0
- package/src/generators/rules/codex.js +13 -0
- package/src/generators/rules/content.js +72 -0
- package/src/generators/rules/content.md +93 -0
- package/src/generators/rules/create-ide-workflow.md +34 -0
- package/src/generators/rules/ctm_sync.md +33 -0
- package/src/generators/rules/ctm_sync_rule.md +78 -0
- package/src/generators/rules/cursor.js +84 -0
- package/src/generators/rules/gemini.js +13 -0
- package/src/generators/rules/hooks.json +11 -0
- package/src/generators/rules/skill.md +161 -0
- package/src/generators/rules/srai-profile.md +32 -0
- package/src/generators/rules/vscode.js +13 -0
- package/src/generators/rules/windsurf.js +13 -0
- package/src/utils/constants.js +63 -0
- package/src/utils/detect.js +27 -0
- package/src/utils/fs-helpers.js +82 -0
- package/src/utils/srai.js +183 -0
package/README.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# securityreview-kit
|
|
2
|
+
|
|
3
|
+
> Bootstrap [security-review-mcp](https://www.npmjs.com/package/security-review-mcp) for AI IDEs and CLI tools in one command.
|
|
4
|
+
|
|
5
|
+
**securityreview-kit** configures the SRAI security review MCP server and installs workspace rules so your AI assistant consults security threat models and countermeasures *before* generating code.
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# Interactive mode (recommended)
|
|
11
|
+
npx securityreview-kit init
|
|
12
|
+
|
|
13
|
+
# Or specify targets directly
|
|
14
|
+
npx securityreview-kit init --target cursor --api-url https://api.example.com --api-key YOUR_TOKEN
|
|
15
|
+
|
|
16
|
+
# Install for multiple targets
|
|
17
|
+
npx securityreview-kit init --target cursor claude vscode
|
|
18
|
+
|
|
19
|
+
# Install for all supported targets
|
|
20
|
+
npx securityreview-kit init --all --api-url https://api.example.com --api-key YOUR_TOKEN
|
|
21
|
+
|
|
22
|
+
# Re-open project selection menu and update installed rules
|
|
23
|
+
npx securityreview-kit init --switch-project
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Supported Targets
|
|
27
|
+
|
|
28
|
+
| Target | Flag | MCP Config | Workspace Rule |
|
|
29
|
+
|---|---|---|---|
|
|
30
|
+
| Cursor | `cursor` | `.cursor/mcp.json` | `.cursor/rules/srai-security-review.mdc`, `.cursor/rules/ctm_sync_rule.mdc`, `.cursor/commands/ctm_sync.md`, `.cursor/agents/ctm_sync.md`, `.cursor/commands/create-ide-workflow.md`, `.cursor/commands/srai-profile.md`, `.cursor/skills/threat-modelling/SKILL.md` |
|
|
31
|
+
| Claude Code | `claude` | `.claude/settings.json` | `CLAUDE.md` |
|
|
32
|
+
| VS Code Copilot | `vscode` | `.vscode/mcp.json` | `.github/copilot-instructions.md` |
|
|
33
|
+
| Windsurf | `windsurf` | `.windsurf/mcp_config.json` | `.windsurf/rules/srai-security-review.md` |
|
|
34
|
+
| Codex | `codex` | `.codex/config.toml` | `AGENTS.md` |
|
|
35
|
+
| Gemini CLI | `gemini` | `.gemini/settings.json` | `GEMINI.md` |
|
|
36
|
+
| Antigravity | `antigravity` | `.gemini/settings.json` | `.agents/rules/srai-security-review.md` |
|
|
37
|
+
|
|
38
|
+
## Commands
|
|
39
|
+
|
|
40
|
+
### `securityreview-kit init`
|
|
41
|
+
|
|
42
|
+
Configure security-review-mcp for your IDE/CLI. Runs interactively when no flags are provided.
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
Options:
|
|
46
|
+
-t, --target <name...> Target IDE/CLI (cursor, claude, vscode, windsurf, codex, gemini, antigravity)
|
|
47
|
+
-a, --all Install for all supported targets
|
|
48
|
+
--project-name <name> (Optional) Preselect project name from fetched API project list
|
|
49
|
+
--api-url <url> SRAI API URL (or set SECURITY_REVIEW_API_URL env var)
|
|
50
|
+
--api-key <token> SRAI API Token (or set SECURITY_REVIEW_API_TOKEN env var)
|
|
51
|
+
--switch-project Fetch projects and only update mapped workspace rules
|
|
52
|
+
--skip-mcp Skip MCP server config installation
|
|
53
|
+
--skip-rules Skip workspace rule installation
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### `securityreview-kit init --switch-project`
|
|
57
|
+
|
|
58
|
+
Fetches projects from `https://<api-url>/api/projects/` using `Authorization: Bearer <api-key>`, shows a single-select menu, and updates installed workspace rules with the selected project.
|
|
59
|
+
|
|
60
|
+
### `securityreview-kit status`
|
|
61
|
+
|
|
62
|
+
Show current configuration status for all supported targets in the workspace.
|
|
63
|
+
|
|
64
|
+
## Environment Variables
|
|
65
|
+
|
|
66
|
+
| Variable | Description |
|
|
67
|
+
|---|---|
|
|
68
|
+
| `SECURITY_REVIEW_PROJECT_NAME` | Optional default project name to preselect in the project menu |
|
|
69
|
+
| `SECURITY_REVIEW_API_URL` | SRAI platform API endpoint |
|
|
70
|
+
| `SECURITY_REVIEW_API_TOKEN` | Your SRAI API token |
|
|
71
|
+
|
|
72
|
+
These can be provided via CLI flags, environment variables, or interactive prompts.
|
|
73
|
+
|
|
74
|
+
## What Gets Installed
|
|
75
|
+
|
|
76
|
+
**MCP Server Config** — tells your IDE how to launch the `security-review-mcp` server via `npx`.
|
|
77
|
+
|
|
78
|
+
**Workspace Rules** — instructs the AI assistant to consult SRAI threat models and countermeasures before generating security-relevant code. If configured, the selected SRAI project name is injected into the MCP workflow instructions in the installed rule content.
|
|
79
|
+
|
|
80
|
+
## How It Works
|
|
81
|
+
|
|
82
|
+
1. Run `securityreview-kit init`
|
|
83
|
+
2. Select your IDE/CLI target(s)
|
|
84
|
+
3. Choose whether to install workspace rules and MCP config
|
|
85
|
+
4. If MCP is selected, enter your SRAI credentials (API URL, token)
|
|
86
|
+
5. The tool fetches `/api/projects/` and you select exactly one SRAI project from the menu
|
|
87
|
+
6. The tool creates/merges MCP config and workspace rule files
|
|
88
|
+
7. Your AI assistant now has access to SRAI security reviews
|
|
89
|
+
|
|
90
|
+
The tool is **idempotent** — running it multiple times safely updates existing configs without duplicating content.
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@securityreviewai/securityreview-kit",
|
|
3
|
+
"version": "0.1.11",
|
|
4
|
+
"description": "Bootstrap security-review-mcp for AI IDEs and CLI tools",
|
|
5
|
+
"author": "Debarshi Das <debarshi.das@we45.com>",
|
|
6
|
+
"license": "UNLICENSED",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"bin": {
|
|
9
|
+
"securityreview-kit": "./bin/securityreview-kit.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"bin/",
|
|
13
|
+
"src/",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=18"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"test": "node --test src/**/*.test.js",
|
|
21
|
+
"start": "node bin/securityreview-kit.js"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"security",
|
|
25
|
+
"mcp",
|
|
26
|
+
"security-review",
|
|
27
|
+
"srai",
|
|
28
|
+
"ai-ide",
|
|
29
|
+
"cursor",
|
|
30
|
+
"claude",
|
|
31
|
+
"codex",
|
|
32
|
+
"gemini",
|
|
33
|
+
"windsurf",
|
|
34
|
+
"vscode"
|
|
35
|
+
],
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"chalk": "^5.4.0",
|
|
38
|
+
"commander": "^13.0.0",
|
|
39
|
+
"inquirer": "^12.0.0"
|
|
40
|
+
},
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public"
|
|
43
|
+
}
|
|
44
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { initCommand } from './commands/init.js';
|
|
3
|
+
import { statusCommand } from './commands/status.js';
|
|
4
|
+
import { switchProjectCommand } from './commands/switch-project.js';
|
|
5
|
+
import { TARGET_NAMES } from './utils/constants.js';
|
|
6
|
+
|
|
7
|
+
export function run() {
|
|
8
|
+
const program = new Command();
|
|
9
|
+
|
|
10
|
+
program
|
|
11
|
+
.name('securityreview-kit')
|
|
12
|
+
.description('Bootstrap security-review-mcp for AI IDEs and CLI tools')
|
|
13
|
+
.version('0.1.0');
|
|
14
|
+
|
|
15
|
+
program
|
|
16
|
+
.command('init')
|
|
17
|
+
.description('Configure security-review-mcp for your IDE / CLI tool')
|
|
18
|
+
.option(
|
|
19
|
+
'-t, --target <name...>',
|
|
20
|
+
`Target IDE/CLI (${TARGET_NAMES.join(', ')}). Omit for interactive mode.`,
|
|
21
|
+
)
|
|
22
|
+
.option('-a, --all', 'Install for all supported targets')
|
|
23
|
+
.option('--project-name <name>', 'Default SRAI project name to preselect in project menu')
|
|
24
|
+
.option('--api-url <url>', 'SRAI API URL (or set SECURITY_REVIEW_API_URL env var)')
|
|
25
|
+
.option('--api-key <token>', 'SRAI API Token (or set SECURITY_REVIEW_API_TOKEN env var)')
|
|
26
|
+
.option('--switch-project', 'Fetch projects and only update mapped workspace rules')
|
|
27
|
+
.option('--skip-mcp', 'Skip MCP server config installation')
|
|
28
|
+
.option('--skip-rules', 'Skip workspace rule installation')
|
|
29
|
+
.action(async (options) => {
|
|
30
|
+
try {
|
|
31
|
+
if (options.switchProject) {
|
|
32
|
+
await switchProjectCommand(options);
|
|
33
|
+
} else {
|
|
34
|
+
await initCommand(options);
|
|
35
|
+
}
|
|
36
|
+
} catch (err) {
|
|
37
|
+
if (err.name === 'ExitPromptError') {
|
|
38
|
+
// User cancelled interactive prompt
|
|
39
|
+
console.log('\n Cancelled.\n');
|
|
40
|
+
process.exit(0);
|
|
41
|
+
}
|
|
42
|
+
throw err;
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
program
|
|
47
|
+
.command('status')
|
|
48
|
+
.description('Show current security-review-mcp configuration status')
|
|
49
|
+
.action(async () => {
|
|
50
|
+
await statusCommand();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
program.parse();
|
|
54
|
+
}
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { input, checkbox, confirm, select } from '@inquirer/prompts';
|
|
3
|
+
import { TARGETS, TARGET_NAMES } from '../utils/constants.js';
|
|
4
|
+
import { detectTargets } from '../utils/detect.js';
|
|
5
|
+
import { fetchProjectNames, getStoredCredentials, normalizeApiUrl } from '../utils/srai.js';
|
|
6
|
+
|
|
7
|
+
// Dynamic imports for generators (avoids loading all at startup)
|
|
8
|
+
const mcpGenerators = {
|
|
9
|
+
cursor: () => import('../generators/mcp/cursor.js'),
|
|
10
|
+
claude: () => import('../generators/mcp/claude.js'),
|
|
11
|
+
vscode: () => import('../generators/mcp/vscode.js'),
|
|
12
|
+
windsurf: () => import('../generators/mcp/windsurf.js'),
|
|
13
|
+
codex: () => import('../generators/mcp/codex.js'),
|
|
14
|
+
gemini: () => import('../generators/mcp/gemini.js'),
|
|
15
|
+
antigravity: () => import('../generators/mcp/gemini.js'),
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const ruleGenerators = {
|
|
19
|
+
cursor: () => import('../generators/rules/cursor.js'),
|
|
20
|
+
claude: () => import('../generators/rules/claude.js'),
|
|
21
|
+
vscode: () => import('../generators/rules/vscode.js'),
|
|
22
|
+
windsurf: () => import('../generators/rules/windsurf.js'),
|
|
23
|
+
codex: () => import('../generators/rules/codex.js'),
|
|
24
|
+
gemini: () => import('../generators/rules/gemini.js'),
|
|
25
|
+
antigravity: () => import('../generators/rules/antigravity.js'),
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function normalizeRuleResults(rawResult) {
|
|
29
|
+
const entries = Array.isArray(rawResult) ? rawResult : [rawResult];
|
|
30
|
+
|
|
31
|
+
return entries.map((entry) => {
|
|
32
|
+
if (typeof entry === 'string') {
|
|
33
|
+
return { filePath: entry, action: 'created', kind: 'rule' };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (entry && typeof entry.filePath === 'string') {
|
|
37
|
+
const allowedKinds = new Set(['rule', 'command', 'agent', 'skill']);
|
|
38
|
+
const kind = allowedKinds.has(entry.kind) ? entry.kind : 'rule';
|
|
39
|
+
return { filePath: entry.filePath, action: entry.action || 'created', kind };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
throw new Error('Rule generator returned an invalid result.');
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Resolve environment variables from flags, env, or interactive prompt.
|
|
48
|
+
*/
|
|
49
|
+
async function resolveEnvVars(options, interactive, cwd) {
|
|
50
|
+
let apiUrl = normalizeApiUrl(options.apiUrl || process.env.SECURITY_REVIEW_API_URL || '');
|
|
51
|
+
let apiToken = options.apiKey || process.env.SECURITY_REVIEW_API_TOKEN || '';
|
|
52
|
+
|
|
53
|
+
if (!apiUrl || !apiToken) {
|
|
54
|
+
const stored = getStoredCredentials(cwd);
|
|
55
|
+
if (!apiUrl && stored.apiUrl) {
|
|
56
|
+
apiUrl = stored.apiUrl;
|
|
57
|
+
}
|
|
58
|
+
if (!apiToken && stored.apiToken) {
|
|
59
|
+
apiToken = stored.apiToken;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (interactive) {
|
|
64
|
+
if (!apiUrl) {
|
|
65
|
+
apiUrl = await input({
|
|
66
|
+
message: '🔗 SRAI API URL:',
|
|
67
|
+
default: 'app.demo.securityreview.ai',
|
|
68
|
+
validate: (v) => {
|
|
69
|
+
const normalized = normalizeApiUrl(v);
|
|
70
|
+
if (!normalized) return 'Must be a valid URL';
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
new URL(normalized);
|
|
74
|
+
return true;
|
|
75
|
+
} catch {
|
|
76
|
+
return 'Must be a valid URL';
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
apiUrl = normalizeApiUrl(apiUrl);
|
|
81
|
+
} else {
|
|
82
|
+
console.log(chalk.dim(` API URL: ${apiUrl} (from saved config/env/flags)`));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!apiToken) {
|
|
86
|
+
apiToken = await input({
|
|
87
|
+
message: '🔑 SRAI API Token:',
|
|
88
|
+
validate: (v) => (v.length > 0 ? true : 'Token is required'),
|
|
89
|
+
});
|
|
90
|
+
} else {
|
|
91
|
+
console.log(chalk.dim(` API Token: ${'•'.repeat(8)} (from saved config/env/flags)`));
|
|
92
|
+
}
|
|
93
|
+
} else {
|
|
94
|
+
if (!apiUrl || !apiToken) {
|
|
95
|
+
console.log(
|
|
96
|
+
chalk.yellow(
|
|
97
|
+
'⚠ Missing credentials. Set SECURITY_REVIEW_API_URL and SECURITY_REVIEW_API_TOKEN\n' +
|
|
98
|
+
' environment variables, pass --api-url and --api-key flags, or run in interactive mode.',
|
|
99
|
+
),
|
|
100
|
+
);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return { apiUrl, apiToken };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Resolve project mapping from SRAI API.
|
|
110
|
+
*/
|
|
111
|
+
async function resolveProjectName(options, interactive, apiUrl, apiToken) {
|
|
112
|
+
const pinnedProject = (options.projectName || process.env.SECURITY_REVIEW_PROJECT_NAME || '').trim();
|
|
113
|
+
|
|
114
|
+
if (!interactive && pinnedProject) {
|
|
115
|
+
return pinnedProject;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const projectNames = await fetchProjectNames(apiUrl, apiToken);
|
|
119
|
+
|
|
120
|
+
if (!interactive) {
|
|
121
|
+
if (projectNames.length === 1) {
|
|
122
|
+
return projectNames[0];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
console.log(
|
|
126
|
+
chalk.yellow(
|
|
127
|
+
'⚠ Multiple projects available. Pass --project-name or run interactive mode to choose one.',
|
|
128
|
+
),
|
|
129
|
+
);
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const selected = await select({
|
|
134
|
+
message: '🧩 Select SRAI project to connect:',
|
|
135
|
+
choices: projectNames.map((name) => ({
|
|
136
|
+
name,
|
|
137
|
+
value: name,
|
|
138
|
+
})),
|
|
139
|
+
default: projectNames.includes(pinnedProject) ? pinnedProject : undefined,
|
|
140
|
+
pageSize: 12,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
return selected;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Resolve targets from flags or interactive prompt.
|
|
148
|
+
*/
|
|
149
|
+
async function resolveTargets(options, interactive, cwd) {
|
|
150
|
+
// Flag mode: explicit target(s)
|
|
151
|
+
if (options.target) {
|
|
152
|
+
const targets = Array.isArray(options.target) ? options.target : [options.target];
|
|
153
|
+
for (const t of targets) {
|
|
154
|
+
if (!TARGET_NAMES.includes(t)) {
|
|
155
|
+
console.log(chalk.red(`✗ Unknown target: ${t}`));
|
|
156
|
+
console.log(chalk.dim(` Valid targets: ${TARGET_NAMES.join(', ')}`));
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return targets;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (options.all) {
|
|
164
|
+
return [...TARGET_NAMES];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (!interactive) {
|
|
168
|
+
console.log(chalk.red('✗ No target specified. Use --target <name> or --all.'));
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Interactive: detect and offer choices
|
|
173
|
+
const detected = detectTargets(cwd);
|
|
174
|
+
|
|
175
|
+
console.log('');
|
|
176
|
+
if (detected.length > 0) {
|
|
177
|
+
console.log(
|
|
178
|
+
chalk.dim(` Detected IDEs in workspace: ${detected.map((d) => TARGETS[d].name).join(', ')}`),
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const selected = await checkbox({
|
|
183
|
+
message: '🎯 Select target IDE(s) / CLI(s) to configure:',
|
|
184
|
+
choices: TARGET_NAMES.map((key) => ({
|
|
185
|
+
name: `${TARGETS[key].name}`,
|
|
186
|
+
value: key,
|
|
187
|
+
checked: detected.includes(key),
|
|
188
|
+
})),
|
|
189
|
+
validate: (v) => (v.length > 0 ? true : 'Select at least one target'),
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
return selected;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Main init command handler.
|
|
197
|
+
*/
|
|
198
|
+
export async function initCommand(options) {
|
|
199
|
+
const cwd = process.cwd();
|
|
200
|
+
const interactive = !options.target && !options.all;
|
|
201
|
+
|
|
202
|
+
// Banner
|
|
203
|
+
console.log('');
|
|
204
|
+
console.log(chalk.bold.cyan(' ╔══════════════════════════════════════╗'));
|
|
205
|
+
console.log(chalk.bold.cyan(' ║') + chalk.bold(' 🛡️ Security Review Kit — Init ') + chalk.bold.cyan(' ║'));
|
|
206
|
+
console.log(chalk.bold.cyan(' ╚══════════════════════════════════════╝'));
|
|
207
|
+
console.log('');
|
|
208
|
+
|
|
209
|
+
if (interactive) {
|
|
210
|
+
console.log(chalk.dim(' Interactive setup — follow the prompts below.\n'));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Step 1: Resolve targets
|
|
214
|
+
console.log(chalk.bold.white(' Step 1 of 4: Select Targets'));
|
|
215
|
+
console.log(chalk.dim(' ─────────────────────────────────'));
|
|
216
|
+
const targets = await resolveTargets(options, interactive, cwd);
|
|
217
|
+
console.log(chalk.green(` ✓ Targets: ${targets.map((t) => TARGETS[t].name).join(', ')}`));
|
|
218
|
+
console.log('');
|
|
219
|
+
|
|
220
|
+
// Step 2: What to install (rules question first, then MCP)
|
|
221
|
+
let installMcp = !options.skipMcp;
|
|
222
|
+
let installRules = !options.skipRules;
|
|
223
|
+
|
|
224
|
+
if (interactive) {
|
|
225
|
+
console.log(chalk.bold.white(' Step 2 of 4: Installation Options'));
|
|
226
|
+
console.log(chalk.dim(' ─────────────────────────────────'));
|
|
227
|
+
installRules = await confirm({
|
|
228
|
+
message: '📋 Install workspace security rules?',
|
|
229
|
+
default: true,
|
|
230
|
+
});
|
|
231
|
+
installMcp = await confirm({
|
|
232
|
+
message: '📡 Install MCP server configuration?',
|
|
233
|
+
default: true,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (!installMcp && !installRules) {
|
|
238
|
+
console.log(chalk.yellow(' ⚠ Nothing to install. Exiting.'));
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
console.log('');
|
|
242
|
+
|
|
243
|
+
// Step 3: Resolve credentials (only needed when MCP is being installed)
|
|
244
|
+
let envVars = { apiUrl: '', apiToken: '' };
|
|
245
|
+
if (installMcp) {
|
|
246
|
+
console.log(chalk.bold.white(' Step 3 of 4: SRAI Credentials'));
|
|
247
|
+
console.log(chalk.dim(' ─────────────────────────────────'));
|
|
248
|
+
envVars = await resolveEnvVars(options, interactive, cwd);
|
|
249
|
+
console.log(chalk.green(' ✓ Credentials configured'));
|
|
250
|
+
} else {
|
|
251
|
+
console.log(chalk.dim(' Step 3 of 4: Skipping SRAI credentials (MCP not selected).'));
|
|
252
|
+
}
|
|
253
|
+
console.log('');
|
|
254
|
+
|
|
255
|
+
// Step 4: Resolve project name (only needed when MCP is being installed)
|
|
256
|
+
let projectName = options.projectName || process.env.SECURITY_REVIEW_PROJECT_NAME || '';
|
|
257
|
+
if (installMcp) {
|
|
258
|
+
console.log(chalk.bold.white(' Step 4 of 4: SRAI Project Mapping'));
|
|
259
|
+
console.log(chalk.dim(' ─────────────────────────────────'));
|
|
260
|
+
projectName = await resolveProjectName(options, interactive, envVars.apiUrl, envVars.apiToken);
|
|
261
|
+
if (projectName) {
|
|
262
|
+
console.log(chalk.green(` ✓ Project mapped: ${projectName}`));
|
|
263
|
+
} else {
|
|
264
|
+
console.log(chalk.dim(' No project name provided. Rules will use a generic project lookup instruction.'));
|
|
265
|
+
}
|
|
266
|
+
} else {
|
|
267
|
+
console.log(chalk.dim(' Step 4 of 4: Skipping SRAI project mapping (MCP not selected).'));
|
|
268
|
+
}
|
|
269
|
+
console.log('');
|
|
270
|
+
|
|
271
|
+
console.log(chalk.bold.white(' Installing...'));
|
|
272
|
+
console.log(chalk.dim(' ─────────────────────────────────'));
|
|
273
|
+
|
|
274
|
+
const results = [];
|
|
275
|
+
|
|
276
|
+
for (const target of targets) {
|
|
277
|
+
const targetInfo = TARGETS[target];
|
|
278
|
+
console.log('');
|
|
279
|
+
console.log(chalk.bold(` ${targetInfo.name}`));
|
|
280
|
+
|
|
281
|
+
if (installMcp) {
|
|
282
|
+
try {
|
|
283
|
+
const gen = await mcpGenerators[target]();
|
|
284
|
+
const mcpPath = gen.generate(cwd, envVars);
|
|
285
|
+
console.log(chalk.green(` ✓ MCP config → ${typeof mcpPath === 'string' ? mcpPath : mcpPath}`));
|
|
286
|
+
results.push({ target, type: 'mcp', status: 'ok', path: mcpPath });
|
|
287
|
+
} catch (err) {
|
|
288
|
+
console.log(chalk.red(` ✗ MCP config failed: ${err.message}`));
|
|
289
|
+
results.push({ target, type: 'mcp', status: 'error', error: err.message });
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (installRules) {
|
|
294
|
+
try {
|
|
295
|
+
const gen = await ruleGenerators[target]();
|
|
296
|
+
const generatedRules = normalizeRuleResults(gen.generate(cwd, { projectName }));
|
|
297
|
+
|
|
298
|
+
for (const rule of generatedRules) {
|
|
299
|
+
const labelByKind = {
|
|
300
|
+
rule: 'Workspace rule',
|
|
301
|
+
command: 'Workspace command',
|
|
302
|
+
agent: 'Workspace agent',
|
|
303
|
+
skill: 'Workspace skill',
|
|
304
|
+
};
|
|
305
|
+
const label = labelByKind[rule.kind] || 'Workspace rule';
|
|
306
|
+
console.log(chalk.green(` ✓ ${label} → ${rule.filePath} (${rule.action})`));
|
|
307
|
+
results.push({ target, type: rule.kind, status: 'ok', path: rule.filePath, action: rule.action });
|
|
308
|
+
}
|
|
309
|
+
} catch (err) {
|
|
310
|
+
console.log(chalk.red(` ✗ Workspace rule failed: ${err.message}`));
|
|
311
|
+
results.push({ target, type: 'rule', status: 'error', error: err.message });
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Summary
|
|
317
|
+
const ok = results.filter((r) => r.status === 'ok').length;
|
|
318
|
+
const errors = results.filter((r) => r.status === 'error').length;
|
|
319
|
+
|
|
320
|
+
console.log('');
|
|
321
|
+
console.log(chalk.dim(' ─────────────────────────────────'));
|
|
322
|
+
if (errors === 0) {
|
|
323
|
+
console.log(chalk.bold.green(` ✅ Done! ${ok} configuration(s) installed successfully.`));
|
|
324
|
+
} else {
|
|
325
|
+
console.log(
|
|
326
|
+
chalk.bold.yellow(` ⚠ Done with ${errors} error(s). ${ok} configuration(s) installed.`),
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
console.log('');
|
|
330
|
+
console.log(chalk.dim(' Run `securityreview-kit status` to verify your setup.'));
|
|
331
|
+
console.log('');
|
|
332
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { TARGETS, TARGET_NAMES, SENTINEL_START } from '../utils/constants.js';
|
|
5
|
+
import { readText, readJson } from '../utils/fs-helpers.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Status command — show what's configured in the current workspace.
|
|
9
|
+
*/
|
|
10
|
+
export async function statusCommand() {
|
|
11
|
+
const cwd = process.cwd();
|
|
12
|
+
|
|
13
|
+
console.log('');
|
|
14
|
+
console.log(chalk.bold.cyan(' ╔══════════════════════════════════════╗'));
|
|
15
|
+
console.log(chalk.bold.cyan(' ║') + chalk.bold(' 🛡️ Security Review Kit — Status ') + chalk.bold.cyan(' ║'));
|
|
16
|
+
console.log(chalk.bold.cyan(' ╚══════════════════════════════════════╝'));
|
|
17
|
+
console.log('');
|
|
18
|
+
console.log(chalk.dim(` Workspace: ${cwd}`));
|
|
19
|
+
console.log('');
|
|
20
|
+
|
|
21
|
+
let anyFound = false;
|
|
22
|
+
|
|
23
|
+
for (const key of TARGET_NAMES) {
|
|
24
|
+
const target = TARGETS[key];
|
|
25
|
+
const mcpPath = join(cwd, target.mcpConfigPath);
|
|
26
|
+
const rulePath = join(cwd, target.rulePath);
|
|
27
|
+
|
|
28
|
+
const mcpExists = existsSync(mcpPath);
|
|
29
|
+
const ruleExists = existsSync(rulePath);
|
|
30
|
+
|
|
31
|
+
// Check if the MCP config actually has our server
|
|
32
|
+
let mcpHasServer = false;
|
|
33
|
+
if (mcpExists) {
|
|
34
|
+
if (target.mcpConfigPath.endsWith('.toml')) {
|
|
35
|
+
const content = readText(mcpPath);
|
|
36
|
+
mcpHasServer = content.includes('[mcp_servers.security-review-mcp]');
|
|
37
|
+
} else {
|
|
38
|
+
const json = readJson(mcpPath);
|
|
39
|
+
const servers = json?.mcpServers || json?.servers || {};
|
|
40
|
+
mcpHasServer = 'security-review-mcp' in servers;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Check if rule file has our sentinel
|
|
45
|
+
let ruleHasSrai = false;
|
|
46
|
+
if (ruleExists) {
|
|
47
|
+
if (target.ruleMode === 'append') {
|
|
48
|
+
const content = readText(rulePath);
|
|
49
|
+
ruleHasSrai = content.includes(SENTINEL_START) || content.includes('SRAI Security Review');
|
|
50
|
+
} else {
|
|
51
|
+
// Standalone rule file — existence is enough
|
|
52
|
+
ruleHasSrai = true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!mcpHasServer && !ruleHasSrai) continue;
|
|
57
|
+
|
|
58
|
+
anyFound = true;
|
|
59
|
+
console.log(chalk.bold(` ${target.name}`));
|
|
60
|
+
console.log(
|
|
61
|
+
` MCP Config: ${mcpHasServer ? chalk.green('✓ Configured') : chalk.dim('✗ Not found')} ${chalk.dim(target.mcpConfigPath)}`,
|
|
62
|
+
);
|
|
63
|
+
console.log(
|
|
64
|
+
` Workspace Rule: ${ruleHasSrai ? chalk.green('✓ Installed') : chalk.dim('✗ Not found')} ${chalk.dim(target.rulePath)}`,
|
|
65
|
+
);
|
|
66
|
+
console.log('');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!anyFound) {
|
|
70
|
+
console.log(chalk.yellow(' No security-review-mcp configurations found in this workspace.'));
|
|
71
|
+
console.log(chalk.dim(' Run `securityreview-kit init` to set up.'));
|
|
72
|
+
console.log('');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Check env vars
|
|
76
|
+
console.log(chalk.bold(' Environment'));
|
|
77
|
+
const apiUrl = process.env.SECURITY_REVIEW_API_URL;
|
|
78
|
+
const apiToken = process.env.SECURITY_REVIEW_API_TOKEN;
|
|
79
|
+
console.log(
|
|
80
|
+
` SECURITY_REVIEW_API_URL: ${apiUrl ? chalk.green('✓ Set') : chalk.yellow('✗ Not set')}`,
|
|
81
|
+
);
|
|
82
|
+
console.log(
|
|
83
|
+
` SECURITY_REVIEW_API_TOKEN: ${apiToken ? chalk.green('✓ Set') : chalk.yellow('✗ Not set')}`,
|
|
84
|
+
);
|
|
85
|
+
console.log('');
|
|
86
|
+
}
|