@nclamvn/vibecode-cli 1.6.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.
- package/bin/vibecode.js +101 -1
- package/docs-site/README.md +41 -0
- package/docs-site/blog/2019-05-28-first-blog-post.md +12 -0
- package/docs-site/blog/2019-05-29-long-blog-post.md +44 -0
- package/docs-site/blog/2021-08-01-mdx-blog-post.mdx +24 -0
- package/docs-site/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg +0 -0
- package/docs-site/blog/2021-08-26-welcome/index.md +29 -0
- package/docs-site/blog/authors.yml +25 -0
- package/docs-site/blog/tags.yml +19 -0
- package/docs-site/docs/commands/agent.md +162 -0
- package/docs-site/docs/commands/assist.md +71 -0
- package/docs-site/docs/commands/build.md +53 -0
- package/docs-site/docs/commands/config.md +30 -0
- package/docs-site/docs/commands/debug.md +173 -0
- package/docs-site/docs/commands/doctor.md +34 -0
- package/docs-site/docs/commands/go.md +128 -0
- package/docs-site/docs/commands/index.md +79 -0
- package/docs-site/docs/commands/init.md +42 -0
- package/docs-site/docs/commands/learn.md +82 -0
- package/docs-site/docs/commands/lock.md +33 -0
- package/docs-site/docs/commands/plan.md +29 -0
- package/docs-site/docs/commands/review.md +31 -0
- package/docs-site/docs/commands/snapshot.md +34 -0
- package/docs-site/docs/commands/start.md +32 -0
- package/docs-site/docs/commands/status.md +37 -0
- package/docs-site/docs/commands/undo.md +83 -0
- package/docs-site/docs/configuration.md +72 -0
- package/docs-site/docs/faq.md +83 -0
- package/docs-site/docs/getting-started.md +119 -0
- package/docs-site/docs/guides/agent-mode.md +94 -0
- package/docs-site/docs/guides/debug-mode.md +83 -0
- package/docs-site/docs/guides/magic-mode.md +107 -0
- package/docs-site/docs/installation.md +98 -0
- package/docs-site/docs/intro.md +67 -0
- package/docs-site/docusaurus.config.ts +141 -0
- package/docs-site/package-lock.json +18039 -0
- package/docs-site/package.json +48 -0
- package/docs-site/sidebars.ts +70 -0
- package/docs-site/src/components/HomepageFeatures/index.tsx +72 -0
- package/docs-site/src/components/HomepageFeatures/styles.module.css +16 -0
- package/docs-site/src/css/custom.css +30 -0
- package/docs-site/src/pages/index.module.css +23 -0
- package/docs-site/src/pages/index.tsx +44 -0
- package/docs-site/src/pages/markdown-page.md +7 -0
- package/docs-site/src/theme/Footer/index.tsx +127 -0
- package/docs-site/src/theme/Footer/styles.module.css +285 -0
- package/docs-site/static/.nojekyll +0 -0
- package/docs-site/static/img/docusaurus-social-card.jpg +0 -0
- package/docs-site/static/img/docusaurus.png +0 -0
- package/docs-site/static/img/favicon.ico +0 -0
- package/docs-site/static/img/logo.svg +1 -0
- package/docs-site/static/img/undraw_docusaurus_mountain.svg +171 -0
- package/docs-site/static/img/undraw_docusaurus_react.svg +170 -0
- package/docs-site/static/img/undraw_docusaurus_tree.svg +40 -0
- package/docs-site/tsconfig.json +8 -0
- package/package.json +2 -1
- package/src/commands/ask.js +230 -0
- package/src/commands/debug.js +109 -1
- package/src/commands/docs.js +167 -0
- package/src/commands/git.js +1024 -0
- package/src/commands/migrate.js +341 -0
- package/src/commands/refactor.js +205 -0
- package/src/commands/review.js +126 -1
- package/src/commands/security.js +229 -0
- package/src/commands/shell.js +486 -0
- package/src/commands/test.js +194 -0
- package/src/commands/watch.js +556 -0
- package/src/debug/image-analyzer.js +304 -0
- package/src/index.js +27 -0
- package/src/utils/image.js +222 -0
|
@@ -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,486 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shell Mode for Vibecode CLI
|
|
3
|
+
* Interactive command shell with vibecode context and AI assistance
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import readline from 'readline';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import { exec, spawn } from 'child_process';
|
|
9
|
+
import { promisify } from 'util';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import fs from 'fs/promises';
|
|
12
|
+
|
|
13
|
+
const execAsync = promisify(exec);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Main shell command handler
|
|
17
|
+
*/
|
|
18
|
+
export async function shellCommand(options) {
|
|
19
|
+
const cwd = process.cwd();
|
|
20
|
+
const projectInfo = await getProjectInfo(cwd);
|
|
21
|
+
const history = [];
|
|
22
|
+
let historyIndex = -1;
|
|
23
|
+
|
|
24
|
+
// Render header
|
|
25
|
+
console.log(renderHeader(projectInfo));
|
|
26
|
+
|
|
27
|
+
// Setup readline with custom prompt
|
|
28
|
+
const rl = readline.createInterface({
|
|
29
|
+
input: process.stdin,
|
|
30
|
+
output: process.stdout,
|
|
31
|
+
prompt: chalk.green('vibe$ '),
|
|
32
|
+
historySize: 100,
|
|
33
|
+
terminal: true
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Custom completer
|
|
37
|
+
rl.on('line', async (line) => {
|
|
38
|
+
const input = line.trim();
|
|
39
|
+
|
|
40
|
+
if (!input) {
|
|
41
|
+
rl.prompt();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Add to history
|
|
46
|
+
history.push(input);
|
|
47
|
+
historyIndex = history.length;
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const shouldContinue = await processCommand(input, cwd, projectInfo, history, rl);
|
|
51
|
+
if (shouldContinue === false) {
|
|
52
|
+
return; // Exit was called
|
|
53
|
+
}
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.log(chalk.red(`\n Error: ${error.message}\n`));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
rl.prompt();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
rl.on('close', () => {
|
|
62
|
+
console.log(chalk.cyan('\n Shell closed.\n'));
|
|
63
|
+
process.exit(0);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Handle SIGINT (Ctrl+C)
|
|
67
|
+
rl.on('SIGINT', () => {
|
|
68
|
+
console.log(chalk.gray('\n (Use "exit" to quit)\n'));
|
|
69
|
+
rl.prompt();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
rl.prompt();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Get project information for context
|
|
77
|
+
*/
|
|
78
|
+
async function getProjectInfo(cwd) {
|
|
79
|
+
const info = {
|
|
80
|
+
name: path.basename(cwd),
|
|
81
|
+
type: 'Unknown',
|
|
82
|
+
framework: null,
|
|
83
|
+
hasGit: false,
|
|
84
|
+
branch: null,
|
|
85
|
+
hasVibecode: false
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
// Check package.json
|
|
90
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
91
|
+
const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf-8'));
|
|
92
|
+
|
|
93
|
+
info.name = pkg.name || info.name;
|
|
94
|
+
|
|
95
|
+
// Detect framework/type
|
|
96
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
97
|
+
|
|
98
|
+
if (deps.next) {
|
|
99
|
+
info.type = 'Next.js';
|
|
100
|
+
info.framework = 'next';
|
|
101
|
+
} else if (deps.react) {
|
|
102
|
+
info.type = 'React';
|
|
103
|
+
info.framework = 'react';
|
|
104
|
+
} else if (deps.vue) {
|
|
105
|
+
info.type = 'Vue';
|
|
106
|
+
info.framework = 'vue';
|
|
107
|
+
} else if (deps.express) {
|
|
108
|
+
info.type = 'Express';
|
|
109
|
+
info.framework = 'express';
|
|
110
|
+
} else if (deps.fastify) {
|
|
111
|
+
info.type = 'Fastify';
|
|
112
|
+
info.framework = 'fastify';
|
|
113
|
+
} else if (deps.typescript) {
|
|
114
|
+
info.type = 'TypeScript';
|
|
115
|
+
} else if (pkg.main || pkg.bin) {
|
|
116
|
+
info.type = 'Node.js';
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Add prisma indicator
|
|
120
|
+
if (deps['@prisma/client'] || deps.prisma) {
|
|
121
|
+
info.type += ' + Prisma';
|
|
122
|
+
}
|
|
123
|
+
} catch {
|
|
124
|
+
// No package.json
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
// Check git
|
|
129
|
+
await execAsync('git rev-parse --git-dir', { cwd });
|
|
130
|
+
info.hasGit = true;
|
|
131
|
+
|
|
132
|
+
const { stdout } = await execAsync('git branch --show-current', { cwd });
|
|
133
|
+
info.branch = stdout.trim() || 'HEAD';
|
|
134
|
+
} catch {
|
|
135
|
+
// Not a git repo
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
// Check vibecode
|
|
140
|
+
await fs.stat(path.join(cwd, '.vibecode'));
|
|
141
|
+
info.hasVibecode = true;
|
|
142
|
+
} catch {
|
|
143
|
+
// No vibecode
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return info;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Render the shell header
|
|
151
|
+
*/
|
|
152
|
+
function renderHeader(projectInfo) {
|
|
153
|
+
const gitInfo = projectInfo.hasGit
|
|
154
|
+
? chalk.gray(` [${projectInfo.branch}]`)
|
|
155
|
+
: '';
|
|
156
|
+
|
|
157
|
+
const vibeInfo = projectInfo.hasVibecode
|
|
158
|
+
? chalk.green(' [vibecode]')
|
|
159
|
+
: '';
|
|
160
|
+
|
|
161
|
+
return chalk.cyan(`
|
|
162
|
+
+----------------------------------------------------------------------+
|
|
163
|
+
| VIBECODE SHELL |
|
|
164
|
+
| |
|
|
165
|
+
| Project: ${chalk.white(projectInfo.name.padEnd(52))}|${gitInfo}${vibeInfo}
|
|
166
|
+
| Type: ${chalk.white(projectInfo.type.padEnd(55))}|
|
|
167
|
+
| |
|
|
168
|
+
| ${chalk.gray('Prefixes:')} |
|
|
169
|
+
| ${chalk.yellow('@')}${chalk.gray('<cmd>')} ${chalk.gray('Vibecode command (e.g., @status, @git)')} |
|
|
170
|
+
| ${chalk.yellow('?')}${chalk.gray('<query>')} ${chalk.gray('Ask AI (e.g., ?explain auth)')} |
|
|
171
|
+
| ${chalk.yellow('!')}${chalk.gray('<cmd>')} ${chalk.gray('Force raw execution')} |
|
|
172
|
+
| ${chalk.yellow('!!')} ${chalk.gray('Repeat last command')} |
|
|
173
|
+
| |
|
|
174
|
+
+----------------------------------------------------------------------+
|
|
175
|
+
`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Process a command input
|
|
180
|
+
*/
|
|
181
|
+
async function processCommand(input, cwd, projectInfo, history, rl) {
|
|
182
|
+
// Built-in commands
|
|
183
|
+
if (input === 'exit' || input === 'quit' || input === 'q') {
|
|
184
|
+
rl.close();
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (input === 'clear' || input === 'cls') {
|
|
189
|
+
console.clear();
|
|
190
|
+
console.log(renderHeader(projectInfo));
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (input === 'history' || input === 'hist') {
|
|
195
|
+
console.log(chalk.cyan('\n Command history:\n'));
|
|
196
|
+
const start = Math.max(0, history.length - 20);
|
|
197
|
+
history.slice(start, -1).forEach((cmd, i) => {
|
|
198
|
+
console.log(chalk.gray(` ${start + i + 1}. ${cmd}`));
|
|
199
|
+
});
|
|
200
|
+
console.log('');
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (input === 'help' || input === 'h') {
|
|
205
|
+
showHelp();
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (input === 'pwd') {
|
|
210
|
+
console.log(chalk.white(`\n ${cwd}\n`));
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (input === 'info') {
|
|
215
|
+
showProjectInfo(projectInfo, cwd);
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Repeat last command (!!)
|
|
220
|
+
if (input === '!!') {
|
|
221
|
+
if (history.length < 2) {
|
|
222
|
+
console.log(chalk.yellow('\n No previous command.\n'));
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
const lastCmd = history[history.length - 2];
|
|
226
|
+
console.log(chalk.gray(` Repeating: ${lastCmd}\n`));
|
|
227
|
+
return processCommand(lastCmd, cwd, projectInfo, history, rl);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// History expansion (!n)
|
|
231
|
+
const histMatch = input.match(/^!(\d+)$/);
|
|
232
|
+
if (histMatch) {
|
|
233
|
+
const index = parseInt(histMatch[1]) - 1;
|
|
234
|
+
if (index >= 0 && index < history.length - 1) {
|
|
235
|
+
const cmd = history[index];
|
|
236
|
+
console.log(chalk.gray(` Repeating: ${cmd}\n`));
|
|
237
|
+
return processCommand(cmd, cwd, projectInfo, history, rl);
|
|
238
|
+
}
|
|
239
|
+
console.log(chalk.yellow('\n Invalid history index.\n'));
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Vibecode commands (@)
|
|
244
|
+
if (input.startsWith('@')) {
|
|
245
|
+
await runVibecodeCommand(input.slice(1), cwd);
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// AI queries (?)
|
|
250
|
+
if (input.startsWith('?')) {
|
|
251
|
+
await runAIQuery(input.slice(1), cwd, projectInfo);
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Force raw execution (!)
|
|
256
|
+
if (input.startsWith('!') && !input.startsWith('!!')) {
|
|
257
|
+
await runRawCommand(input.slice(1), cwd);
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// cd command (special handling)
|
|
262
|
+
if (input.startsWith('cd ')) {
|
|
263
|
+
const dir = input.slice(3).trim();
|
|
264
|
+
try {
|
|
265
|
+
const newPath = path.resolve(cwd, dir);
|
|
266
|
+
await fs.stat(newPath);
|
|
267
|
+
process.chdir(newPath);
|
|
268
|
+
console.log(chalk.gray(`\n Changed to: ${newPath}\n`));
|
|
269
|
+
} catch {
|
|
270
|
+
console.log(chalk.red(`\n Directory not found: ${dir}\n`));
|
|
271
|
+
}
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Normal shell command
|
|
276
|
+
await runShellCommand(input, cwd);
|
|
277
|
+
return true;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Run a vibecode command
|
|
282
|
+
*/
|
|
283
|
+
async function runVibecodeCommand(cmd, cwd) {
|
|
284
|
+
const parts = cmd.trim().split(/\s+/);
|
|
285
|
+
const command = parts[0];
|
|
286
|
+
const args = parts.slice(1);
|
|
287
|
+
|
|
288
|
+
if (!command) {
|
|
289
|
+
console.log(chalk.yellow('\n Usage: @<command> [args]\n'));
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
console.log(chalk.cyan(`\n Running: vibecode ${cmd}\n`));
|
|
294
|
+
|
|
295
|
+
// Map aliases
|
|
296
|
+
const aliases = {
|
|
297
|
+
's': 'status',
|
|
298
|
+
'g': 'git',
|
|
299
|
+
'd': 'debug',
|
|
300
|
+
'b': 'build',
|
|
301
|
+
'w': 'watch',
|
|
302
|
+
'a': 'assist'
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const vibeCmd = aliases[command] || command;
|
|
306
|
+
|
|
307
|
+
return new Promise((resolve) => {
|
|
308
|
+
const child = spawn('vibecode', [vibeCmd, ...args], {
|
|
309
|
+
cwd,
|
|
310
|
+
stdio: 'inherit',
|
|
311
|
+
shell: true
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
child.on('close', (code) => {
|
|
315
|
+
console.log('');
|
|
316
|
+
resolve();
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
child.on('error', (error) => {
|
|
320
|
+
console.log(chalk.red(`\n Error: ${error.message}\n`));
|
|
321
|
+
resolve();
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Run an AI query using Claude
|
|
328
|
+
*/
|
|
329
|
+
async function runAIQuery(query, cwd, projectInfo) {
|
|
330
|
+
if (!query.trim()) {
|
|
331
|
+
console.log(chalk.yellow('\n Usage: ?<your question>\n'));
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
console.log(chalk.cyan(`\n AI Query: "${query}"\n`));
|
|
336
|
+
|
|
337
|
+
// Build context
|
|
338
|
+
const context = `Project: ${projectInfo.name} (${projectInfo.type})
|
|
339
|
+
Directory: ${cwd}
|
|
340
|
+
Question: ${query}
|
|
341
|
+
|
|
342
|
+
Please provide a helpful, concise answer.`;
|
|
343
|
+
|
|
344
|
+
return new Promise((resolve) => {
|
|
345
|
+
// Try using claude CLI directly
|
|
346
|
+
const child = spawn('claude', ['-p', context], {
|
|
347
|
+
cwd,
|
|
348
|
+
stdio: 'inherit',
|
|
349
|
+
shell: true
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
child.on('close', (code) => {
|
|
353
|
+
console.log('');
|
|
354
|
+
resolve();
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
child.on('error', () => {
|
|
358
|
+
// Fallback: suggest using vibecode assist
|
|
359
|
+
console.log(chalk.yellow(' Claude CLI not available.'));
|
|
360
|
+
console.log(chalk.gray(' Try: vibecode assist "' + query + '"\n'));
|
|
361
|
+
resolve();
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Run a raw shell command (force mode)
|
|
368
|
+
*/
|
|
369
|
+
async function runRawCommand(cmd, cwd) {
|
|
370
|
+
if (!cmd.trim()) {
|
|
371
|
+
console.log(chalk.yellow('\n Usage: !<command>\n'));
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return new Promise((resolve) => {
|
|
376
|
+
console.log('');
|
|
377
|
+
|
|
378
|
+
const child = spawn(cmd, [], {
|
|
379
|
+
cwd,
|
|
380
|
+
stdio: 'inherit',
|
|
381
|
+
shell: true
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
child.on('close', (code) => {
|
|
385
|
+
console.log('');
|
|
386
|
+
resolve();
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
child.on('error', (error) => {
|
|
390
|
+
console.log(chalk.red(`\n Error: ${error.message}\n`));
|
|
391
|
+
resolve();
|
|
392
|
+
});
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Run a normal shell command
|
|
398
|
+
*/
|
|
399
|
+
async function runShellCommand(cmd, cwd) {
|
|
400
|
+
return new Promise((resolve) => {
|
|
401
|
+
console.log('');
|
|
402
|
+
|
|
403
|
+
const child = spawn(cmd, [], {
|
|
404
|
+
cwd,
|
|
405
|
+
stdio: 'inherit',
|
|
406
|
+
shell: true
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
child.on('close', (code) => {
|
|
410
|
+
if (code !== 0 && code !== null) {
|
|
411
|
+
console.log(chalk.gray(` Exit code: ${code}`));
|
|
412
|
+
}
|
|
413
|
+
console.log('');
|
|
414
|
+
resolve();
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
child.on('error', (error) => {
|
|
418
|
+
// Command not found
|
|
419
|
+
const cmdName = cmd.split(' ')[0];
|
|
420
|
+
console.log(chalk.red(`\n Command not found: ${cmdName}`));
|
|
421
|
+
console.log(chalk.gray(` Did you mean: @${cmdName}?\n`));
|
|
422
|
+
resolve();
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Show help message
|
|
429
|
+
*/
|
|
430
|
+
function showHelp() {
|
|
431
|
+
console.log(chalk.white(`
|
|
432
|
+
VIBECODE SHELL HELP
|
|
433
|
+
|
|
434
|
+
${chalk.cyan('Built-in Commands:')}
|
|
435
|
+
exit, quit, q Exit shell
|
|
436
|
+
clear, cls Clear screen
|
|
437
|
+
history, hist Show command history
|
|
438
|
+
help, h Show this help
|
|
439
|
+
pwd Print working directory
|
|
440
|
+
info Show project info
|
|
441
|
+
cd <dir> Change directory
|
|
442
|
+
|
|
443
|
+
${chalk.cyan('Prefixes:')}
|
|
444
|
+
${chalk.yellow('@')}<command> Run vibecode command
|
|
445
|
+
e.g., @status, @git status, @debug
|
|
446
|
+
|
|
447
|
+
${chalk.yellow('?')}<query> Ask AI a question
|
|
448
|
+
e.g., ?explain this code, ?fix the error
|
|
449
|
+
|
|
450
|
+
${chalk.yellow('!')}<command> Force raw shell execution
|
|
451
|
+
e.g., !npm run dev
|
|
452
|
+
|
|
453
|
+
${chalk.yellow('!!')} Repeat last command
|
|
454
|
+
${chalk.yellow('!')}<n> Repeat command #n from history
|
|
455
|
+
|
|
456
|
+
${chalk.cyan('Examples:')}
|
|
457
|
+
vibe$ npm test Run npm test
|
|
458
|
+
vibe$ @git commit Git commit via vibecode
|
|
459
|
+
vibe$ @s Vibecode status (alias)
|
|
460
|
+
vibe$ ?what is useState Ask AI about useState
|
|
461
|
+
vibe$ !node server.js Run node directly
|
|
462
|
+
vibe$ !! Repeat last command
|
|
463
|
+
|
|
464
|
+
${chalk.cyan('Aliases:')}
|
|
465
|
+
@s -> @status @g -> @git
|
|
466
|
+
@d -> @debug @b -> @build
|
|
467
|
+
@w -> @watch @a -> @assist
|
|
468
|
+
`));
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Show project information
|
|
473
|
+
*/
|
|
474
|
+
function showProjectInfo(projectInfo, cwd) {
|
|
475
|
+
console.log(chalk.cyan(`
|
|
476
|
+
PROJECT INFO
|
|
477
|
+
|
|
478
|
+
Name: ${chalk.white(projectInfo.name)}
|
|
479
|
+
Type: ${chalk.white(projectInfo.type)}
|
|
480
|
+
Directory: ${chalk.white(cwd)}
|
|
481
|
+
Git: ${projectInfo.hasGit ? chalk.green('Yes') + chalk.gray(` (${projectInfo.branch})`) : chalk.gray('No')}
|
|
482
|
+
Vibecode: ${projectInfo.hasVibecode ? chalk.green('Yes') : chalk.gray('No')}
|
|
483
|
+
`));
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
export default shellCommand;
|