vcode-cli 1.0.5 → 2.0.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/LICENSE +24 -21
- package/bin/vcode.js +63 -41
- package/lib/agent/ai.js +131 -0
- package/lib/agent/context.js +111 -0
- package/lib/agent/loop.js +237 -0
- package/lib/auth/index.js +120 -0
- package/lib/byte/index.js +207 -0
- package/lib/commands/add.js +9 -0
- package/lib/commands/clear.js +8 -0
- package/lib/commands/config.js +37 -0
- package/lib/commands/help.js +27 -0
- package/lib/commands/history.js +29 -0
- package/lib/commands/start.js +17 -138
- package/lib/commands/undo.js +23 -0
- package/lib/logo.js +64 -127
- package/lib/tools/filesystem.js +315 -0
- package/lib/tools/permissions.js +87 -0
- package/lib/tools/shell.js +150 -0
- package/lib/ui/byte.js +178 -0
- package/lib/ui/logo.js +53 -0
- package/package.json +13 -16
package/lib/logo.js
CHANGED
|
@@ -1,144 +1,81 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* V Code — Retro ASCII Logo with animated blinking eyes.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
1
|
import chalk from 'chalk';
|
|
2
|
+
import { Byte, type ByteMood } from './byte/index.js';
|
|
6
3
|
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
║ ╚████╔╝ ╚██████╗╚██████╔╝██████╔╝███████╗ ║
|
|
16
|
-
║ ╚═══╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ ║
|
|
17
|
-
║ ║
|
|
18
|
-
║ ┌──────────────────────┐ ║
|
|
19
|
-
║ │ ◉ ◉ │ ║
|
|
20
|
-
║ │ ─── │ ║
|
|
21
|
-
║ └──────────────────────┘ ║
|
|
22
|
-
║ ║
|
|
23
|
-
╚═══════════════════════════════════════════════════════╝`,
|
|
24
|
-
];
|
|
25
|
-
|
|
26
|
-
const FRAMES_BLINK = [
|
|
27
|
-
`
|
|
28
|
-
╔═══════════════════════════════════════════════════════╗
|
|
29
|
-
║ ║
|
|
30
|
-
║ ██╗ ██╗ ██████╗ ██████╗ ██████╗ ███████╗ ║
|
|
31
|
-
║ ██║ ██║ ██╔════╝██╔═══██╗██╔══██╗██╔════╝ ║
|
|
32
|
-
║ ██║ ██║ ██║ ██║ ██║██║ ██║█████╗ ║
|
|
33
|
-
║ ╚██╗ ██╔╝ ██║ ██║ ██║██║ ██║██╔══╝ ║
|
|
34
|
-
║ ╚████╔╝ ╚██████╗╚██████╔╝██████╔╝███████╗ ║
|
|
35
|
-
║ ╚═══╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ ║
|
|
36
|
-
║ ║
|
|
37
|
-
║ ┌──────────────────────┐ ║
|
|
38
|
-
║ │ ━ ━ │ ║
|
|
39
|
-
║ │ ─── │ ║
|
|
40
|
-
║ └──────────────────────┘ ║
|
|
41
|
-
║ ║
|
|
42
|
-
╚═══════════════════════════════════════════════════════╝`,
|
|
43
|
-
];
|
|
4
|
+
const V_CODE_LOGO = `
|
|
5
|
+
██████╗ ██████╗ ███████╗███╗ ███╗███████╗███╗ ██╗
|
|
6
|
+
██╔════╝██╔═══██╗██╔════╝████╗ ████║██╔════╝████╗ ██║
|
|
7
|
+
██║ ██║ ██║█████╗ ██╔████╔██║█████╗ ██╔██╗ ██║
|
|
8
|
+
██║ ██║ ██║██╔══╝ ██║╚██╔╝██║██╔══╝ ██║╚██╗██║
|
|
9
|
+
╚██████╗╚██████╔╝███████╗██║ ╚═╝ ██║███████╗██║ ╚████║
|
|
10
|
+
╚═════╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═══╝
|
|
11
|
+
`;
|
|
44
12
|
|
|
45
|
-
const
|
|
46
|
-
`
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
║ ╚██╗ ██╔╝ ██║ ██║ ██║██║ ██║██╔══╝ ║
|
|
53
|
-
║ ╚████╔╝ ╚██████╗╚██████╔╝██████╔╝███████╗ ║
|
|
54
|
-
║ ╚═══╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ ║
|
|
55
|
-
║ ║
|
|
56
|
-
║ ┌──────────────────────┐ ║
|
|
57
|
-
║ │ ◉ ◉ │ ║
|
|
58
|
-
║ │ ─── │ ║
|
|
59
|
-
║ └──────────────────────┘ ║
|
|
60
|
-
║ ║
|
|
61
|
-
╚═══════════════════════════════════════════════════════╝`,
|
|
13
|
+
const EYE_FRAMES = [
|
|
14
|
+
` ◉ ◉`,
|
|
15
|
+
` ● ●`,
|
|
16
|
+
` ● ●`,
|
|
17
|
+
` ░ ░`,
|
|
18
|
+
` ░ ░`,
|
|
19
|
+
` ◉ ◉`,
|
|
62
20
|
];
|
|
63
21
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
║ ║
|
|
68
|
-
║ ██╗ ██╗ ██████╗ ██████╗ ██████╗ ███████╗ ║
|
|
69
|
-
║ ██║ ██║ ██╔════╝██╔═══██╗██╔══██╗██╔════╝ ║
|
|
70
|
-
║ ██║ ██║ ██║ ██║ ██║██║ ██║█████╗ ║
|
|
71
|
-
║ ╚██╗ ██╔╝ ██║ ██║ ██║██║ ██║██╔══╝ ║
|
|
72
|
-
║ ╚████╔╝ ╚██████╗╚██████╔╝██████╔╝███████╗ ║
|
|
73
|
-
║ ╚═══╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ ║
|
|
74
|
-
║ ║
|
|
75
|
-
║ ┌──────────────────────┐ ║
|
|
76
|
-
║ │ ◉ ◉ │ ║
|
|
77
|
-
║ │ ─── │ ║
|
|
78
|
-
║ └──────────────────────┘ ║
|
|
79
|
-
║ ║
|
|
80
|
-
╚═══════════════════════════════════════════════════════╝`,
|
|
81
|
-
];
|
|
22
|
+
function clearScreen() {
|
|
23
|
+
process.stdout.write('\x1Bc');
|
|
24
|
+
}
|
|
82
25
|
|
|
83
|
-
|
|
26
|
+
async function animateEyes(duration: number = 3000) {
|
|
27
|
+
return new Promise<void>(resolve => {
|
|
28
|
+
let frameIndex = 0;
|
|
29
|
+
const interval = setInterval(() => {
|
|
30
|
+
frameIndex = (frameIndex + 1) % EYE_FRAMES.length;
|
|
31
|
+
}, 150);
|
|
84
32
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
.replace(/[┌┐└┘│─┘┐]/g, (m) => chalk.hex('#334155')(m))
|
|
91
|
-
.replace(/───/g, chalk.hex('#f97316')('───'));
|
|
33
|
+
setTimeout(() => {
|
|
34
|
+
clearInterval(interval);
|
|
35
|
+
resolve();
|
|
36
|
+
}, duration);
|
|
37
|
+
});
|
|
92
38
|
}
|
|
93
39
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
console.log(chalk.gray(' ─────────────────────────────────────────────────────\n'));
|
|
40
|
+
export async function showLogo(mini: boolean = false, showAnimation: boolean = true) {
|
|
41
|
+
clearScreen();
|
|
42
|
+
|
|
43
|
+
if (showAnimation) {
|
|
44
|
+
console.log(chalk.hex('#a855f7').bold(mini ? '' : V_CODE_LOGO));
|
|
45
|
+
await animateEyes(2000);
|
|
46
|
+
} else {
|
|
47
|
+
console.log(chalk.hex('#a855f7').bold(mini ? '' : V_CODE_LOGO));
|
|
48
|
+
}
|
|
104
49
|
}
|
|
105
50
|
|
|
106
|
-
/**
|
|
107
|
-
* Animate logo with blinking eyes. Returns a stop function.
|
|
108
|
-
*/
|
|
109
51
|
export function startLogoAnimation() {
|
|
110
52
|
let running = true;
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
{ frames: FRAMES_LOOK_LEFT, duration: 800 },
|
|
118
|
-
{ frames: FRAMES_OPEN, duration: 1500 },
|
|
119
|
-
{ frames: FRAMES_LOOK_RIGHT, duration: 800 },
|
|
120
|
-
{ frames: FRAMES_OPEN, duration: 2000 },
|
|
121
|
-
];
|
|
122
|
-
|
|
123
|
-
let idx = 0;
|
|
124
|
-
|
|
125
|
-
const render = () => {
|
|
126
|
-
if (!running) return;
|
|
127
|
-
const step = sequence[idx % sequence.length];
|
|
128
|
-
// Move cursor to top and re-draw logo portion
|
|
129
|
-
process.stdout.write('\x1b[1;1H');
|
|
130
|
-
process.stdout.write(colorize(step.frames[0]));
|
|
131
|
-
idx++;
|
|
132
|
-
setTimeout(render, step.duration);
|
|
53
|
+
|
|
54
|
+
const render = async () => {
|
|
55
|
+
clearScreen();
|
|
56
|
+
console.log(chalk.hex('#a855f7').bold(V_CODE_LOGO));
|
|
57
|
+
await animateEyes(1500);
|
|
58
|
+
if (running) render();
|
|
133
59
|
};
|
|
134
|
-
|
|
135
|
-
console.clear();
|
|
60
|
+
|
|
136
61
|
render();
|
|
137
|
-
|
|
138
|
-
process.stdout.write('\n');
|
|
139
|
-
process.stdout.write(chalk.gray(' ─────────────────────────────────────────────────────\n'));
|
|
140
|
-
process.stdout.write(chalk.hex('#a855f7').bold(' Vynthen V Code — Local Bridge\n'));
|
|
141
|
-
process.stdout.write(chalk.gray(' ─────────────────────────────────────────────────────\n'));
|
|
142
|
-
|
|
62
|
+
|
|
143
63
|
return () => { running = false; };
|
|
144
64
|
}
|
|
65
|
+
|
|
66
|
+
export function showVersion() {
|
|
67
|
+
const version = 'v2.0.0';
|
|
68
|
+
const columns = process.stdout.columns || 80;
|
|
69
|
+
const padding = Math.max(0, columns - version.length - 1);
|
|
70
|
+
console.log(chalk.gray(' '.repeat(padding) + version));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function showStartup(user: string, plan: string, project: string) {
|
|
74
|
+
console.log('');
|
|
75
|
+
console.log(chalk.gray(' User: ') + chalk.cyan(user));
|
|
76
|
+
console.log(chalk.gray(' Plan: ') + (plan === 'pro' ? chalk.green(plan) : chalk.yellow(plan)));
|
|
77
|
+
console.log(chalk.gray(' Project: ') + chalk.white(project));
|
|
78
|
+
console.log('');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export { Byte, type ByteMood };
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { diffLines } from 'diff';
|
|
5
|
+
|
|
6
|
+
export interface FileOperation {
|
|
7
|
+
type: 'read' | 'write' | 'delete' | 'create_dir' | 'rename' | 'move';
|
|
8
|
+
path: string;
|
|
9
|
+
content?: string;
|
|
10
|
+
newPath?: string;
|
|
11
|
+
timestamp: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const operationHistory: FileOperation[] = [];
|
|
15
|
+
const MAX_HISTORY = 50;
|
|
16
|
+
|
|
17
|
+
export function addOperation(op: FileOperation): void {
|
|
18
|
+
operationHistory.unshift(op);
|
|
19
|
+
if (operationHistory.length > MAX_HISTORY) {
|
|
20
|
+
operationHistory.pop();
|
|
21
|
+
}
|
|
22
|
+
logOperation(op);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function getOperationHistory(): FileOperation[] {
|
|
26
|
+
return [...operationHistory];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function getLastOperation(): FileOperation | null {
|
|
30
|
+
return operationHistory[0] || null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function clearHistory(): void {
|
|
34
|
+
operationHistory.length = 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function logOperation(op: FileOperation): void {
|
|
38
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || '';
|
|
39
|
+
const logDir = path.join(homeDir, '.vcode');
|
|
40
|
+
const logFile = path.join(logDir, 'audit.log');
|
|
41
|
+
|
|
42
|
+
// Ensure directory exists
|
|
43
|
+
if (!fs.existsSync(logDir)) {
|
|
44
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const logEntry = `[${new Date().toISOString()}] ${op.type}: ${op.path}${op.newPath ? ` -> ${op.newPath}` : ''}\n`;
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
fs.appendFileSync(logFile, logEntry);
|
|
51
|
+
} catch {
|
|
52
|
+
// Silently fail if we can't write to log
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// File operations
|
|
57
|
+
export async function readFile(filePath: string): Promise<string> {
|
|
58
|
+
const fullPath = path.resolve(filePath);
|
|
59
|
+
|
|
60
|
+
if (!fs.existsSync(fullPath)) {
|
|
61
|
+
throw new Error(`File not found: ${filePath}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const stats = fs.statSync(fullPath);
|
|
65
|
+
if (stats.isDirectory()) {
|
|
66
|
+
throw new Error(`Path is a directory: ${filePath}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const content = fs.readFileSync(fullPath, 'utf8');
|
|
70
|
+
|
|
71
|
+
addOperation({
|
|
72
|
+
type: 'read',
|
|
73
|
+
path: fullPath,
|
|
74
|
+
timestamp: Date.now()
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return content;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function writeFile(filePath: string, content: string, createDiff = true): Promise<{ success: boolean; diff?: string }> {
|
|
81
|
+
const fullPath = path.resolve(filePath);
|
|
82
|
+
const dir = path.dirname(fullPath);
|
|
83
|
+
|
|
84
|
+
// Ensure directory exists
|
|
85
|
+
if (!fs.existsSync(dir)) {
|
|
86
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let diffOutput = '';
|
|
90
|
+
if (createDiff && fs.existsSync(fullPath)) {
|
|
91
|
+
const oldContent = fs.readFileSync(fullPath, 'utf8');
|
|
92
|
+
diffOutput = generateDiff(fullPath, oldContent, content);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
fs.writeFileSync(fullPath, content, 'utf8');
|
|
96
|
+
|
|
97
|
+
addOperation({
|
|
98
|
+
type: 'write',
|
|
99
|
+
path: fullPath,
|
|
100
|
+
content,
|
|
101
|
+
timestamp: Date.now()
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
return { success: true, diff: diffOutput };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export async function deleteFile(filePath: string): Promise<boolean> {
|
|
108
|
+
const fullPath = path.resolve(filePath);
|
|
109
|
+
|
|
110
|
+
if (!fs.existsSync(fullPath)) {
|
|
111
|
+
throw new Error(`Path does not exist: ${filePath}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const stats = fs.statSync(fullPath);
|
|
115
|
+
|
|
116
|
+
if (stats.isDirectory()) {
|
|
117
|
+
fs.rmSync(fullPath, { recursive: true });
|
|
118
|
+
} else {
|
|
119
|
+
fs.unlinkSync(fullPath);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
addOperation({
|
|
123
|
+
type: 'delete',
|
|
124
|
+
path: fullPath,
|
|
125
|
+
timestamp: Date.now()
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export async function createDirectory(dirPath: string): Promise<boolean> {
|
|
132
|
+
const fullPath = path.resolve(dirPath);
|
|
133
|
+
|
|
134
|
+
if (fs.existsSync(fullPath)) {
|
|
135
|
+
throw new Error(`Directory already exists: ${dirPath}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
fs.mkdirSync(fullPath, { recursive: true });
|
|
139
|
+
|
|
140
|
+
addOperation({
|
|
141
|
+
type: 'create_dir',
|
|
142
|
+
path: fullPath,
|
|
143
|
+
timestamp: Date.now()
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export async function renameFile(oldPath: string, newPath: string): Promise<boolean> {
|
|
150
|
+
const fullOldPath = path.resolve(oldPath);
|
|
151
|
+
const fullNewPath = path.resolve(newPath);
|
|
152
|
+
|
|
153
|
+
if (!fs.existsSync(fullOldPath)) {
|
|
154
|
+
throw new Error(`Path does not exist: ${oldPath}`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
fs.renameSync(fullOldPath, fullNewPath);
|
|
158
|
+
|
|
159
|
+
addOperation({
|
|
160
|
+
type: 'rename',
|
|
161
|
+
path: fullOldPath,
|
|
162
|
+
newPath: fullNewPath,
|
|
163
|
+
timestamp: Date.now()
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export async function listDirectory(dirPath: string): Promise<Array<{ name: string; isDirectory: boolean; path: string }>> {
|
|
170
|
+
const fullPath = path.resolve(dirPath);
|
|
171
|
+
|
|
172
|
+
if (!fs.existsSync(fullPath)) {
|
|
173
|
+
throw new Error(`Directory not found: ${dirPath}`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const stats = fs.statSync(fullPath);
|
|
177
|
+
if (!stats.isDirectory()) {
|
|
178
|
+
throw new Error(`Path is not a directory: ${dirPath}`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const items = fs.readdirSync(fullPath, { withFileTypes: true });
|
|
182
|
+
|
|
183
|
+
return items.map(item => ({
|
|
184
|
+
name: item.name,
|
|
185
|
+
isDirectory: item.isDirectory(),
|
|
186
|
+
path: path.join(fullPath, item.name)
|
|
187
|
+
}));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export async function getFileTree(dirPath: string, maxDepth = 3, currentDepth = 0): Promise<Array<{ name: string; type: 'file' | 'directory'; path: string; children?: any[] }>> {
|
|
191
|
+
if (currentDepth >= maxDepth) return [];
|
|
192
|
+
|
|
193
|
+
const fullPath = path.resolve(dirPath);
|
|
194
|
+
|
|
195
|
+
if (!fs.existsSync(fullPath)) {
|
|
196
|
+
throw new Error(`Directory not found: ${dirPath}`);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const items = fs.readdirSync(fullPath, { withFileTypes: true });
|
|
200
|
+
const result = [];
|
|
201
|
+
|
|
202
|
+
for (const item of items) {
|
|
203
|
+
// Skip hidden files and common ignored directories
|
|
204
|
+
if (item.name.startsWith('.') || ['node_modules', 'dist', 'build', '__pycache__', '.git'].includes(item.name)) {
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const itemPath = path.join(fullPath, item.name);
|
|
209
|
+
|
|
210
|
+
if (item.isDirectory()) {
|
|
211
|
+
const children = await getFileTree(itemPath, maxDepth, currentDepth + 1);
|
|
212
|
+
result.push({
|
|
213
|
+
name: item.name,
|
|
214
|
+
type: 'directory',
|
|
215
|
+
path: itemPath,
|
|
216
|
+
children
|
|
217
|
+
});
|
|
218
|
+
} else {
|
|
219
|
+
result.push({
|
|
220
|
+
name: item.name,
|
|
221
|
+
type: 'file',
|
|
222
|
+
path: itemPath
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return result.sort((a, b) => {
|
|
228
|
+
if (a.type === b.type) return a.name.localeCompare(b.name);
|
|
229
|
+
return a.type === 'directory' ? -1 : 1;
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function generateDiff(filePath: string, oldContent: string, newContent: string): string {
|
|
234
|
+
const changes = diffLines(oldContent, newContent);
|
|
235
|
+
let output = chalk.bold.cyan(`\n--- ${filePath}\n`);
|
|
236
|
+
output += chalk.bold.cyan(`+++ ${filePath}\n`);
|
|
237
|
+
|
|
238
|
+
let lineNum = 1;
|
|
239
|
+
for (const change of changes) {
|
|
240
|
+
const lines = change.value.split('\n').filter(l => l !== '');
|
|
241
|
+
for (const line of lines) {
|
|
242
|
+
if (change.added) {
|
|
243
|
+
output += chalk.green(`+${lineNum}: ${line}\n`);
|
|
244
|
+
} else if (change.removed) {
|
|
245
|
+
output += chalk.red(`-${lineNum}: ${line}\n`);
|
|
246
|
+
} else {
|
|
247
|
+
output += chalk.gray(` ${lineNum}: ${line}\n`);
|
|
248
|
+
}
|
|
249
|
+
lineNum++;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return output;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export function formatFileTree(tree: any[], prefix = '', isLast = true): string {
|
|
257
|
+
let output = '';
|
|
258
|
+
|
|
259
|
+
for (let i = 0; i < tree.length; i++) {
|
|
260
|
+
const item = tree[i];
|
|
261
|
+
const isLastItem = i === tree.length - 1;
|
|
262
|
+
const connector = isLastItem ? '└── ' : '├── ';
|
|
263
|
+
const typeIcon = item.type === 'directory' ? '📁 ' : '📄 ';
|
|
264
|
+
|
|
265
|
+
output += `${prefix}${connector}${typeIcon}${item.name}\n`;
|
|
266
|
+
|
|
267
|
+
if (item.children) {
|
|
268
|
+
const newPrefix = prefix + (isLastItem ? ' ' : '│ ');
|
|
269
|
+
output += formatFileTree(item.children, newPrefix, isLastItem);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return output;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export async function searchFiles(dirPath: string, pattern: string, isRegex = false): Promise<Array<{ file: string; line: number; content: string }>> {
|
|
277
|
+
const results: Array<{ file: string; line: number; content: string }> = [];
|
|
278
|
+
const regex = isRegex ? new RegExp(pattern) : new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i');
|
|
279
|
+
|
|
280
|
+
async function searchInDir(dir: string) {
|
|
281
|
+
const items = fs.readdirSync(dir, { withFileTypes: true });
|
|
282
|
+
|
|
283
|
+
for (const item of items) {
|
|
284
|
+
const itemPath = path.join(dir, item.name);
|
|
285
|
+
|
|
286
|
+
if (item.name.startsWith('.') || ['node_modules', 'dist', 'build', '__pycache__', '.git'].includes(item.name)) {
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (item.isDirectory()) {
|
|
291
|
+
await searchInDir(itemPath);
|
|
292
|
+
} else {
|
|
293
|
+
try {
|
|
294
|
+
const content = fs.readFileSync(itemPath, 'utf8');
|
|
295
|
+
const lines = content.split('\n');
|
|
296
|
+
|
|
297
|
+
for (let i = 0; i < lines.length; i++) {
|
|
298
|
+
if (regex.test(lines[i])) {
|
|
299
|
+
results.push({
|
|
300
|
+
file: itemPath,
|
|
301
|
+
line: i + 1,
|
|
302
|
+
content: lines[i].trim()
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
} catch {
|
|
307
|
+
// Skip files that can't be read
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
await searchInDir(path.resolve(dirPath));
|
|
314
|
+
return results;
|
|
315
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
|
|
4
|
+
export type PermissionDecision = 'approve' | 'deny' | 'approve_all' | 'deny_all' | 'skip' | 'skip_all';
|
|
5
|
+
|
|
6
|
+
let sessionApproveAll = false;
|
|
7
|
+
let sessionDenyAll = false;
|
|
8
|
+
|
|
9
|
+
export function setSessionApproveAll(enabled: boolean): void {
|
|
10
|
+
sessionApproveAll = enabled;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function setSessionDenyAll(enabled: boolean): void {
|
|
14
|
+
sessionDenyAll = enabled;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function resetSessionPermissions(): void {
|
|
18
|
+
sessionApproveAll = false;
|
|
19
|
+
sessionDenyAll = false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function askPermission(message: string, type: 'write' | 'delete' | 'command'): Promise<PermissionDecision> {
|
|
23
|
+
if (sessionApproveAll) return 'approve';
|
|
24
|
+
if (sessionDenyAll) return 'deny';
|
|
25
|
+
|
|
26
|
+
const choices = [
|
|
27
|
+
{ name: 'y', value: 'approve', short: 'y - yes' },
|
|
28
|
+
{ name: 'n', value: 'deny', short: 'n - no' },
|
|
29
|
+
{ name: 'a', value: 'approve_all', short: 'a - approve all' },
|
|
30
|
+
{ name: 's', value: 'skip_all', short: 's - skip all' }
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
const typeIndicator = {
|
|
34
|
+
write: chalk.green('📝'),
|
|
35
|
+
delete: chalk.red('🗑️'),
|
|
36
|
+
command: chalk.yellow('⚡')
|
|
37
|
+
}[type];
|
|
38
|
+
|
|
39
|
+
const { decision } = await inquirer.prompt([
|
|
40
|
+
{
|
|
41
|
+
type: 'list',
|
|
42
|
+
name: 'decision',
|
|
43
|
+
message: `${typeIndicator} ${message}`,
|
|
44
|
+
choices,
|
|
45
|
+
default: 'deny'
|
|
46
|
+
}
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
if (decision === 'approve_all') {
|
|
50
|
+
sessionApproveAll = true;
|
|
51
|
+
} else if (decision === 'skip_all') {
|
|
52
|
+
sessionDenyAll = true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return decision;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function confirmDelete(path: string): Promise<boolean> {
|
|
59
|
+
console.log(chalk.red(`\n ⚠️ WARNING: You are about to DELETE:`));
|
|
60
|
+
console.log(chalk.red(` ${path}`));
|
|
61
|
+
console.log(chalk.red(` This cannot be undone!\n`));
|
|
62
|
+
|
|
63
|
+
const { confirm } = await inquirer.prompt([
|
|
64
|
+
{
|
|
65
|
+
type: 'confirm',
|
|
66
|
+
name: 'confirm',
|
|
67
|
+
message: chalk.red(' Are you absolutely sure? Type "yes" to confirm:'),
|
|
68
|
+
default: false
|
|
69
|
+
}
|
|
70
|
+
]);
|
|
71
|
+
|
|
72
|
+
return confirm === true;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function showDiffAndConfirm(path: string, diff: string): Promise<PermissionDecision> {
|
|
76
|
+
console.log(chalk.bold.cyan(`\n 📝 File changes for: ${path}`));
|
|
77
|
+
console.log(diff);
|
|
78
|
+
|
|
79
|
+
return askPermission(`Apply these changes?`, 'write');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function showCommandAndConfirm(command: string): Promise<PermissionDecision> {
|
|
83
|
+
console.log(chalk.bold.yellow(`\n ⚡ Command to execute:`));
|
|
84
|
+
console.log(chalk.bgYellow.black(` ${command} `));
|
|
85
|
+
|
|
86
|
+
return askPermission(`Run this command?`, 'command');
|
|
87
|
+
}
|