@nclamvn/vibecode-cli 1.7.0 → 1.8.0

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.
@@ -0,0 +1,229 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════════
2
+ // VIBECODE CLI - Security Command
3
+ // Phase K5: AI Security Audit
4
+ // ═══════════════════════════════════════════════════════════════════════════════
5
+
6
+ import { spawn, exec } from 'child_process';
7
+ import { promisify } from 'util';
8
+ import fs from 'fs/promises';
9
+ import path from 'path';
10
+ import chalk from 'chalk';
11
+
12
+ const execAsync = promisify(exec);
13
+
14
+ export async function securityCommand(options = {}) {
15
+ const cwd = process.cwd();
16
+
17
+ console.log(chalk.cyan(`
18
+ ╭────────────────────────────────────────────────────────────────────╮
19
+ │ 🔒 SECURITY AUDIT │
20
+ │ │
21
+ │ Scanning for vulnerabilities... │
22
+ │ │
23
+ ╰────────────────────────────────────────────────────────────────────╯
24
+ `));
25
+
26
+ const results = {
27
+ npmAudit: null,
28
+ secretsScan: [],
29
+ timestamp: new Date().toISOString()
30
+ };
31
+
32
+ // 1. Run npm audit
33
+ console.log(chalk.gray('\n [1/3] Running npm audit...\n'));
34
+ try {
35
+ const { stdout } = await execAsync('npm audit --json', { cwd, timeout: 60000 });
36
+ results.npmAudit = JSON.parse(stdout);
37
+ console.log(chalk.green(' ✓ npm audit complete'));
38
+ } catch (error) {
39
+ try {
40
+ if (error.stdout) {
41
+ results.npmAudit = JSON.parse(error.stdout);
42
+ const vulns = results.npmAudit?.metadata?.vulnerabilities;
43
+ if (vulns) {
44
+ const total = vulns.critical + vulns.high + vulns.moderate + vulns.low;
45
+ if (total > 0) {
46
+ console.log(chalk.yellow(` ⚠ Found ${total} vulnerabilities`));
47
+ } else {
48
+ console.log(chalk.green(' ✓ No vulnerabilities found'));
49
+ }
50
+ }
51
+ }
52
+ } catch {
53
+ results.npmAudit = { error: 'npm audit failed or not applicable' };
54
+ console.log(chalk.gray(' - npm audit not applicable'));
55
+ }
56
+ }
57
+
58
+ // 2. Scan for secrets
59
+ console.log(chalk.gray('\n [2/3] Scanning for exposed secrets...\n'));
60
+ results.secretsScan = await scanForSecrets(cwd);
61
+ if (results.secretsScan.length > 0) {
62
+ console.log(chalk.red(` ⚠ Found ${results.secretsScan.length} potential secrets!`));
63
+ for (const secret of results.secretsScan.slice(0, 5)) {
64
+ console.log(chalk.gray(` - ${secret.file}: ${secret.type}`));
65
+ }
66
+ } else {
67
+ console.log(chalk.green(' ✓ No exposed secrets detected'));
68
+ }
69
+
70
+ // 3. AI security analysis
71
+ console.log(chalk.gray('\n [3/3] AI security analysis...\n'));
72
+
73
+ const vulnSummary = results.npmAudit?.metadata?.vulnerabilities;
74
+ const npmSummary = vulnSummary
75
+ ? `Critical: ${vulnSummary.critical}, High: ${vulnSummary.high}, Moderate: ${vulnSummary.moderate}, Low: ${vulnSummary.low}`
76
+ : 'npm audit not available';
77
+
78
+ const prompt = `
79
+ # Security Audit Request
80
+
81
+ ## Project: ${path.basename(cwd)}
82
+
83
+ ## NPM Audit Results:
84
+ ${npmSummary}
85
+
86
+ ## Potential Secrets Found:
87
+ ${results.secretsScan.map(s => `- ${s.file}: ${s.type}`).join('\n') || 'None detected'}
88
+
89
+ ## Security Analysis Required:
90
+ Analyze the codebase for security vulnerabilities:
91
+
92
+ 1. **Authentication Issues**
93
+ - Weak password handling
94
+ - Missing auth checks on protected routes
95
+ - Insecure session management
96
+ - JWT vulnerabilities
97
+
98
+ 2. **Input Validation**
99
+ - SQL injection risks
100
+ - XSS vulnerabilities
101
+ - Command injection
102
+ - Path traversal
103
+ - ReDoS (regex denial of service)
104
+
105
+ 3. **Data Exposure**
106
+ - Sensitive data in logs
107
+ - Exposed API keys in code
108
+ - Insecure data storage
109
+ - PII handling issues
110
+
111
+ 4. **Configuration Issues**
112
+ - CORS misconfiguration
113
+ - Missing security headers
114
+ - Debug mode in production
115
+ - Insecure defaults
116
+
117
+ 5. **Dependencies**
118
+ - Known vulnerable packages
119
+ - Outdated dependencies
120
+ - Unnecessary dependencies
121
+
122
+ ## Output Format:
123
+ For each vulnerability found:
124
+ - **Severity**: Critical / High / Medium / Low
125
+ - **Category**: Auth / Injection / Exposure / Config / Dependency
126
+ - **Location**: File and line number
127
+ - **Description**: What's the issue
128
+ - **Remediation**: How to fix it
129
+
130
+ End with a security score (A-F) and priority fixes.
131
+ `;
132
+
133
+ const promptFile = path.join(cwd, '.vibecode', 'security-prompt.md');
134
+ await fs.mkdir(path.dirname(promptFile), { recursive: true });
135
+ await fs.writeFile(promptFile, prompt);
136
+
137
+ await runClaudeCode(prompt, cwd);
138
+
139
+ // Save report
140
+ const reportPath = path.join(cwd, '.vibecode', 'reports', `security-${Date.now()}.json`);
141
+ await fs.mkdir(path.dirname(reportPath), { recursive: true });
142
+ await fs.writeFile(reportPath, JSON.stringify(results, null, 2));
143
+
144
+ console.log(chalk.green('\n✅ Security audit complete!'));
145
+ console.log(chalk.gray(` Report saved to: .vibecode/reports/\n`));
146
+
147
+ // Auto-fix option
148
+ if (options.fix) {
149
+ console.log(chalk.yellow('\n Attempting auto-fix...\n'));
150
+ try {
151
+ await execAsync('npm audit fix', { cwd });
152
+ console.log(chalk.green(' ✓ npm audit fix completed\n'));
153
+ } catch {
154
+ console.log(chalk.gray(' - npm audit fix not applicable\n'));
155
+ }
156
+ }
157
+ }
158
+
159
+ async function scanForSecrets(cwd) {
160
+ const secrets = [];
161
+ const patterns = [
162
+ { regex: /(['"])?(api[_-]?key|apikey)(['"])?\s*[:=]\s*(['"])[a-zA-Z0-9]{20,}(['"])/gi, type: 'API Key' },
163
+ { regex: /(['"])?(secret|password|passwd|pwd)(['"])?\s*[:=]\s*(['"])[^'"]{8,}(['"])/gi, type: 'Password/Secret' },
164
+ { regex: /(['"])?(aws[_-]?access[_-]?key[_-]?id)(['"])?\s*[:=]\s*(['"])[A-Z0-9]{20}(['"])/gi, type: 'AWS Access Key' },
165
+ { regex: /(['"])?(aws[_-]?secret)(['"])?\s*[:=]\s*(['"])[a-zA-Z0-9/+=]{40}(['"])/gi, type: 'AWS Secret Key' },
166
+ { regex: /(['"])?(private[_-]?key)(['"])?\s*[:=]\s*(['"])-----BEGIN/gi, type: 'Private Key' },
167
+ { regex: /(['"])?(auth[_-]?token|bearer)(['"])?\s*[:=]\s*(['"])[a-zA-Z0-9._-]{20,}(['"])/gi, type: 'Auth Token' },
168
+ { regex: /(['"])?(github[_-]?token)(['"])?\s*[:=]\s*(['"])gh[ps]_[a-zA-Z0-9]{36}(['"])/gi, type: 'GitHub Token' },
169
+ { regex: /(['"])?(stripe[_-]?key)(['"])?\s*[:=]\s*(['"])sk_[a-zA-Z0-9]{24,}(['"])/gi, type: 'Stripe Key' }
170
+ ];
171
+
172
+ const files = await getAllSourceFiles(cwd);
173
+
174
+ for (const file of files) {
175
+ try {
176
+ const content = await fs.readFile(path.join(cwd, file), 'utf-8');
177
+
178
+ for (const { regex, type } of patterns) {
179
+ regex.lastIndex = 0; // Reset regex state
180
+ if (regex.test(content)) {
181
+ secrets.push({ file, type });
182
+ break; // One type per file is enough
183
+ }
184
+ }
185
+ } catch {}
186
+ }
187
+
188
+ return secrets;
189
+ }
190
+
191
+ async function getAllSourceFiles(cwd) {
192
+ const files = [];
193
+ const extensions = ['.js', '.ts', '.jsx', '.tsx', '.json', '.env', '.yaml', '.yml', '.py', '.go'];
194
+ const ignoreDirs = ['node_modules', '.git', 'dist', 'build', '.next', 'coverage'];
195
+
196
+ async function scan(dir) {
197
+ try {
198
+ const entries = await fs.readdir(dir, { withFileTypes: true });
199
+
200
+ for (const entry of entries) {
201
+ if (entry.name.startsWith('.') && entry.name !== '.env') continue;
202
+ if (ignoreDirs.includes(entry.name)) continue;
203
+
204
+ const fullPath = path.join(dir, entry.name);
205
+
206
+ if (entry.isDirectory()) {
207
+ await scan(fullPath);
208
+ } else if (extensions.some(ext => entry.name.endsWith(ext))) {
209
+ files.push(path.relative(cwd, fullPath));
210
+ }
211
+ }
212
+ } catch {}
213
+ }
214
+
215
+ await scan(cwd);
216
+ return files.slice(0, 100);
217
+ }
218
+
219
+ async function runClaudeCode(prompt, cwd) {
220
+ return new Promise((resolve) => {
221
+ const child = spawn('claude', ['-p', prompt, '--dangerously-skip-permissions'], {
222
+ cwd,
223
+ stdio: 'inherit'
224
+ });
225
+
226
+ child.on('close', resolve);
227
+ child.on('error', () => resolve());
228
+ });
229
+ }
@@ -0,0 +1,194 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════════
2
+ // VIBECODE CLI - Test Command
3
+ // Phase K2: AI Test Generation
4
+ // ═══════════════════════════════════════════════════════════════════════════════
5
+
6
+ import { spawn } from 'child_process';
7
+ import fs from 'fs/promises';
8
+ import path from 'path';
9
+ import chalk from 'chalk';
10
+ import inquirer from 'inquirer';
11
+
12
+ export async function testCommand(targetPath, options = {}) {
13
+ const cwd = process.cwd();
14
+
15
+ // Generate tests
16
+ if (options.generate) {
17
+ return generateTests(cwd, targetPath, options);
18
+ }
19
+
20
+ // Run tests (pass through to npm test)
21
+ if (options.run) {
22
+ const { exec } = await import('child_process');
23
+ const child = exec('npm test', { cwd });
24
+ child.stdout?.pipe(process.stdout);
25
+ child.stderr?.pipe(process.stderr);
26
+ return new Promise(resolve => child.on('close', resolve));
27
+ }
28
+
29
+ // Coverage
30
+ if (options.coverage) {
31
+ const { exec } = await import('child_process');
32
+ const child = exec('npm run test:coverage || npm test -- --coverage', { cwd });
33
+ child.stdout?.pipe(process.stdout);
34
+ child.stderr?.pipe(process.stderr);
35
+ return new Promise(resolve => child.on('close', resolve));
36
+ }
37
+
38
+ // Default: show menu
39
+ const { action } = await inquirer.prompt([{
40
+ type: 'list',
41
+ name: 'action',
42
+ message: 'Test options:',
43
+ choices: [
44
+ { name: '🧪 Run tests (npm test)', value: 'run' },
45
+ { name: '✨ Generate tests for file/folder', value: 'generate' },
46
+ { name: '📊 Show coverage', value: 'coverage' },
47
+ { name: '👋 Exit', value: 'exit' }
48
+ ]
49
+ }]);
50
+
51
+ if (action === 'run') {
52
+ return testCommand(null, { run: true });
53
+ }
54
+ if (action === 'generate') {
55
+ const { target } = await inquirer.prompt([{
56
+ type: 'input',
57
+ name: 'target',
58
+ message: 'Path to generate tests for:',
59
+ default: 'src/'
60
+ }]);
61
+ return generateTests(cwd, target, options);
62
+ }
63
+ if (action === 'coverage') {
64
+ return testCommand(null, { coverage: true });
65
+ }
66
+ }
67
+
68
+ async function generateTests(cwd, targetPath, options) {
69
+ console.log(chalk.cyan(`
70
+ ╭────────────────────────────────────────────────────────────────────╮
71
+ │ 🧪 TEST GENERATION │
72
+ │ │
73
+ │ Target: ${(targetPath || 'src/').padEnd(52)}│
74
+ │ │
75
+ ╰────────────────────────────────────────────────────────────────────╯
76
+ `));
77
+
78
+ // Detect test framework
79
+ const framework = await detectTestFramework(cwd);
80
+ console.log(chalk.gray(` Test framework: ${framework}\n`));
81
+
82
+ // Get files to generate tests for
83
+ const files = await getFilesToTest(cwd, targetPath || 'src/');
84
+
85
+ if (files.length === 0) {
86
+ console.log(chalk.yellow(' No files found to generate tests for.\n'));
87
+ return;
88
+ }
89
+
90
+ console.log(chalk.gray(` Found ${files.length} files\n`));
91
+
92
+ const prompt = `
93
+ # Test Generation Request
94
+
95
+ ## Project: ${path.basename(cwd)}
96
+ ## Test Framework: ${framework}
97
+
98
+ ## Files to Generate Tests For:
99
+ ${files.map(f => `- ${f}`).join('\n')}
100
+
101
+ ## Instructions:
102
+ 1. Read each source file
103
+ 2. Generate comprehensive tests including:
104
+ - Unit tests for each function/method
105
+ - Edge cases (null, undefined, empty, boundary values)
106
+ - Error handling tests
107
+ - Mock external dependencies
108
+ - Integration tests where appropriate
109
+
110
+ 3. Use ${framework} syntax and conventions
111
+ 4. Follow AAA pattern (Arrange, Act, Assert)
112
+ 5. Add descriptive test names
113
+ 6. Include setup/teardown if needed
114
+
115
+ ## Output:
116
+ Create test files in __tests__/ or *.test.ts/js format.
117
+ For each source file, create corresponding test file.
118
+
119
+ Generate tests now.
120
+ `;
121
+
122
+ const promptFile = path.join(cwd, '.vibecode', 'test-gen-prompt.md');
123
+ await fs.mkdir(path.dirname(promptFile), { recursive: true });
124
+ await fs.writeFile(promptFile, prompt);
125
+
126
+ console.log(chalk.gray(' Generating tests with Claude Code...\n'));
127
+
128
+ await runClaudeCode(prompt, cwd);
129
+
130
+ console.log(chalk.green('\n✅ Tests generated!'));
131
+ console.log(chalk.gray(' Run: npm test\n'));
132
+ }
133
+
134
+ async function detectTestFramework(cwd) {
135
+ try {
136
+ const pkgPath = path.join(cwd, 'package.json');
137
+ const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf-8'));
138
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
139
+
140
+ if (deps.vitest) return 'vitest';
141
+ if (deps.jest) return 'jest';
142
+ if (deps.mocha) return 'mocha';
143
+ if (deps['@testing-library/react']) return 'jest + @testing-library/react';
144
+ if (deps.ava) return 'ava';
145
+ if (deps.tap) return 'tap';
146
+ if (deps.uvu) return 'uvu';
147
+
148
+ return 'jest'; // default
149
+ } catch {
150
+ return 'jest';
151
+ }
152
+ }
153
+
154
+ async function getFilesToTest(cwd, targetPath) {
155
+ const files = [];
156
+ const fullPath = path.join(cwd, targetPath);
157
+
158
+ async function scan(dir) {
159
+ try {
160
+ const entries = await fs.readdir(dir, { withFileTypes: true });
161
+
162
+ for (const entry of entries) {
163
+ if (entry.name.startsWith('.')) continue;
164
+ if (entry.name.includes('.test.') || entry.name.includes('.spec.')) continue;
165
+ if (entry.name === '__tests__') continue;
166
+ if (entry.name === 'node_modules') continue;
167
+ if (entry.name === 'dist' || entry.name === 'build') continue;
168
+
169
+ const entryPath = path.join(dir, entry.name);
170
+
171
+ if (entry.isDirectory()) {
172
+ await scan(entryPath);
173
+ } else if (/\.(js|ts|jsx|tsx)$/.test(entry.name)) {
174
+ files.push(path.relative(cwd, entryPath));
175
+ }
176
+ }
177
+ } catch {}
178
+ }
179
+
180
+ await scan(fullPath);
181
+ return files.slice(0, 20); // Limit
182
+ }
183
+
184
+ async function runClaudeCode(prompt, cwd) {
185
+ return new Promise((resolve) => {
186
+ const child = spawn('claude', ['-p', prompt, '--dangerously-skip-permissions'], {
187
+ cwd,
188
+ stdio: 'inherit'
189
+ });
190
+
191
+ child.on('close', resolve);
192
+ child.on('error', () => resolve());
193
+ });
194
+ }
package/src/index.js CHANGED
@@ -50,6 +50,14 @@ export { watchCommand } from './commands/watch.js';
50
50
  // Phase I3 Commands - Shell Mode
51
51
  export { shellCommand } from './commands/shell.js';
52
52
 
53
+ // Phase K Commands - Maximize Claude Code
54
+ export { testCommand } from './commands/test.js';
55
+ export { docsCommand } from './commands/docs.js';
56
+ export { refactorCommand } from './commands/refactor.js';
57
+ export { securityCommand } from './commands/security.js';
58
+ export { askCommand } from './commands/ask.js';
59
+ export { migrateCommand } from './commands/migrate.js';
60
+
53
61
  // UI exports (Phase H2: Dashboard)
54
62
  export {
55
63
  ProgressDashboard,