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
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import keytar from 'keytar';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import inquirer from 'inquirer';
|
|
4
|
+
|
|
5
|
+
const SERVICE_NAME = 'vynthen-vcode';
|
|
6
|
+
const ACCOUNT_NAME = 'vynthen-credentials';
|
|
7
|
+
|
|
8
|
+
export interface VynthenCredentials {
|
|
9
|
+
email: string;
|
|
10
|
+
token: string;
|
|
11
|
+
plan: string;
|
|
12
|
+
expiresAt: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function login(): Promise<void> {
|
|
16
|
+
console.log(chalk.hex('#a855f7').bold('\n 🔐 V Code Login\n'));
|
|
17
|
+
console.log(chalk.gray(' Connect with your Vynthen account\n'));
|
|
18
|
+
|
|
19
|
+
const answers = await inquirer.prompt([
|
|
20
|
+
{
|
|
21
|
+
type: 'input',
|
|
22
|
+
name: 'email',
|
|
23
|
+
message: chalk.white(' Email:'),
|
|
24
|
+
validate: (input: string) => {
|
|
25
|
+
if (!input.includes('@')) return 'Please enter a valid email';
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
type: 'password',
|
|
31
|
+
name: 'password',
|
|
32
|
+
message: chalk.white(' Password:'),
|
|
33
|
+
mask: '*',
|
|
34
|
+
validate: (input: string) => {
|
|
35
|
+
if (input.length < 1) return 'Password required';
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
console.log(chalk.gray('\n Authenticating...'));
|
|
43
|
+
|
|
44
|
+
const response = await fetch('https://vynthen.com/api/auth/login', {
|
|
45
|
+
method: 'POST',
|
|
46
|
+
headers: { 'Content-Type': 'application/json' },
|
|
47
|
+
body: JSON.stringify({ email: answers.email, password: answers.password })
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
if (!response.ok) {
|
|
51
|
+
const err = await response.json();
|
|
52
|
+
console.log(chalk.red(`\n ✗ ${err.message || 'Authentication failed'}\n`));
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const data = await response.json();
|
|
57
|
+
|
|
58
|
+
// Store credentials in keychain
|
|
59
|
+
const credentials: VynthenCredentials = {
|
|
60
|
+
email: answers.email,
|
|
61
|
+
token: data.token,
|
|
62
|
+
plan: data.plan || 'free',
|
|
63
|
+
expiresAt: Date.now() + (8 * 60 * 60 * 1000) // 8 hours
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
await keytar.setPassword(SERVICE_NAME, ACCOUNT_NAME, JSON.stringify(credentials));
|
|
67
|
+
|
|
68
|
+
console.log(chalk.green('\n ✓ Logged in successfully!'));
|
|
69
|
+
console.log(chalk.gray(` Plan: ${chalk.cyan(credentials.plan)}`));
|
|
70
|
+
console.log(chalk.gray(` Token expires in 8 hours\n`));
|
|
71
|
+
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.log(chalk.red(`\n ✗ Connection error. Please try again.\n`));
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function logout(): Promise<void> {
|
|
79
|
+
const deleted = await keytar.deletePassword(SERVICE_NAME, ACCOUNT_NAME);
|
|
80
|
+
|
|
81
|
+
if (deleted) {
|
|
82
|
+
console.log(chalk.green('\n ✓ Logged out successfully\n'));
|
|
83
|
+
} else {
|
|
84
|
+
console.log(chalk.gray('\n Already logged out\n'));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export async function getCredentials(): Promise<VynthenCredentials | null> {
|
|
89
|
+
const stored = await keytar.getPassword(SERVICE_NAME, ACCOUNT_NAME);
|
|
90
|
+
if (!stored) return null;
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
const creds: VynthenCredentials = JSON.parse(stored);
|
|
94
|
+
|
|
95
|
+
// Check if token expired
|
|
96
|
+
if (Date.now() > creds.expiresAt) {
|
|
97
|
+
console.log(chalk.yellow('\n ⚠ Token expired. Please run vcode login\n'));
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return creds;
|
|
102
|
+
} catch {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export async function getToken(): Promise<string | null> {
|
|
108
|
+
const creds = await getCredentials();
|
|
109
|
+
return creds?.token || null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export async function getUser(): Promise<{ email: string; plan: string } | null> {
|
|
113
|
+
const creds = await getCredentials();
|
|
114
|
+
if (!creds) return null;
|
|
115
|
+
return { email: creds.email, plan: creds.plan };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function isAuthenticated(): boolean {
|
|
119
|
+
return getCredentials() !== null;
|
|
120
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
|
|
3
|
+
export type ByteMood = 'idle' | 'thinking' | 'working' | 'success' | 'error' | 'waiting' | 'celebrating';
|
|
4
|
+
|
|
5
|
+
const ASCII_ARTS: Record<ByteMood, string> = {
|
|
6
|
+
idle: `
|
|
7
|
+
/\\_____/\\
|
|
8
|
+
/ o o \\
|
|
9
|
+
( == ^ == )
|
|
10
|
+
) (
|
|
11
|
+
( )
|
|
12
|
+
( ( ) ( ) )
|
|
13
|
+
(__(__)__(__)_)
|
|
14
|
+
`,
|
|
15
|
+
thinking: `
|
|
16
|
+
/\\_____/\\
|
|
17
|
+
/ ? ? \\
|
|
18
|
+
( == o == )
|
|
19
|
+
) (
|
|
20
|
+
( o )
|
|
21
|
+
( ( ) ( ) )
|
|
22
|
+
(__(__)__(__)_)
|
|
23
|
+
meow?
|
|
24
|
+
`,
|
|
25
|
+
working: `
|
|
26
|
+
/\\_____/\\
|
|
27
|
+
/ O O \\
|
|
28
|
+
( == ^ == )
|
|
29
|
+
)____|____(
|
|
30
|
+
( typing )
|
|
31
|
+
( ( ) ( ) )
|
|
32
|
+
(__(__)__(__)_)
|
|
33
|
+
`,
|
|
34
|
+
success: `
|
|
35
|
+
/\\_____/\\
|
|
36
|
+
/ ^ ^ \\
|
|
37
|
+
( == v == )
|
|
38
|
+
) (
|
|
39
|
+
( victory! )
|
|
40
|
+
( ( ) ( ) )
|
|
41
|
+
(__(__)__(__)_)
|
|
42
|
+
purr!
|
|
43
|
+
`,
|
|
44
|
+
error: `
|
|
45
|
+
/\\_____/\\
|
|
46
|
+
/ X X \\
|
|
47
|
+
( == o == )
|
|
48
|
+
)_________(
|
|
49
|
+
( oops... )
|
|
50
|
+
( ( ) ( ) )
|
|
51
|
+
(__(__)__(__)_)
|
|
52
|
+
`,
|
|
53
|
+
waiting: `
|
|
54
|
+
/\\_____/\\
|
|
55
|
+
/ > < \\
|
|
56
|
+
( == ^ == )
|
|
57
|
+
)_____|____(
|
|
58
|
+
( you sure? )
|
|
59
|
+
( ( ) ( ) )
|
|
60
|
+
(__(__)__(__)_)
|
|
61
|
+
`,
|
|
62
|
+
celebrating: `
|
|
63
|
+
/\\_____/\\
|
|
64
|
+
/ ^ ^ \\
|
|
65
|
+
( == v == )
|
|
66
|
+
) (^o^) (
|
|
67
|
+
( purr!! )
|
|
68
|
+
( ( ) ( ) )
|
|
69
|
+
(__(__)__(__)_)
|
|
70
|
+
yay!
|
|
71
|
+
`,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const MESSAGES: Record<ByteMood, string[]> = {
|
|
75
|
+
idle: [
|
|
76
|
+
'ready when you are',
|
|
77
|
+
'waiting for your command~',
|
|
78
|
+
'*stretch* what shall we build?',
|
|
79
|
+
'*~*~',
|
|
80
|
+
],
|
|
81
|
+
thinking: [
|
|
82
|
+
'hmm let me think...',
|
|
83
|
+
'processing...',
|
|
84
|
+
'give me a moment~',
|
|
85
|
+
'thinking hard!',
|
|
86
|
+
],
|
|
87
|
+
working: [
|
|
88
|
+
'on it...',
|
|
89
|
+
'working my magic~',
|
|
90
|
+
'be right back...',
|
|
91
|
+
'typing away...',
|
|
92
|
+
],
|
|
93
|
+
success: [
|
|
94
|
+
'done! purr~',
|
|
95
|
+
'all set!',
|
|
96
|
+
'mission complete!',
|
|
97
|
+
'purrfect!',
|
|
98
|
+
],
|
|
99
|
+
error: [
|
|
100
|
+
'uh oh...',
|
|
101
|
+
'that didn\'t work...',
|
|
102
|
+
'oopsie~',
|
|
103
|
+
'need to try again...',
|
|
104
|
+
],
|
|
105
|
+
waiting: [
|
|
106
|
+
'you sure about this?',
|
|
107
|
+
'waiting for your okay~',
|
|
108
|
+
'what do you think?',
|
|
109
|
+
'approve?',
|
|
110
|
+
],
|
|
111
|
+
celebrating: [
|
|
112
|
+
'we did it!!',
|
|
113
|
+
'purrfect job!',
|
|
114
|
+
'amazing!',
|
|
115
|
+
'woohoo!',
|
|
116
|
+
],
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export class Byte {
|
|
120
|
+
private currentMood: ByteMood = 'idle';
|
|
121
|
+
private showCompanion: boolean = true;
|
|
122
|
+
private animationFrame: NodeJS.Timeout | null = null;
|
|
123
|
+
|
|
124
|
+
constructor(showCompanion: boolean = true) {
|
|
125
|
+
this.showCompanion = showCompanion;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
setCompanion(enabled: boolean) {
|
|
129
|
+
this.showCompanion = enabled;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
getMood(): ByteMood {
|
|
133
|
+
return this.currentMood;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
setMood(mood: ByteMood) {
|
|
137
|
+
this.currentMood = mood;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
getArt(): string {
|
|
141
|
+
return ASCII_ARTS[this.currentMood];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
getMessage(): string {
|
|
145
|
+
const messages = MESSAGES[this.currentMood];
|
|
146
|
+
return messages[Math.floor(Math.random() * messages.length)];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
show(mood?: ByteMood) {
|
|
150
|
+
if (!this.showCompanion) return;
|
|
151
|
+
|
|
152
|
+
if (mood) this.currentMood = mood;
|
|
153
|
+
|
|
154
|
+
const art = chalk.gray(ASCII_ARTS[this.currentMood]);
|
|
155
|
+
const message = chalk.cyan(this.getMessage());
|
|
156
|
+
|
|
157
|
+
console.log(art);
|
|
158
|
+
console.log(` ${message}`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
say(text: string, mood?: ByteMood) {
|
|
162
|
+
if (!this.showCompanion) {
|
|
163
|
+
console.log(chalk.cyan(` ${text}`));
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (mood) this.currentMood = mood;
|
|
168
|
+
|
|
169
|
+
const art = chalk.gray(ASCII_ARTS[this.currentMood]);
|
|
170
|
+
console.log(art);
|
|
171
|
+
console.log(` ${chalk.cyan(text)}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
animate(mood: ByteMood, duration: number = 2000) {
|
|
175
|
+
if (!this.showCompanion) return;
|
|
176
|
+
|
|
177
|
+
this.currentMood = mood;
|
|
178
|
+
|
|
179
|
+
const frames: ByteMood[] = [mood, 'idle'];
|
|
180
|
+
let frameIndex = 0;
|
|
181
|
+
|
|
182
|
+
this.animationFrame = setInterval(() => {
|
|
183
|
+
frameIndex = (frameIndex + 1) % frames.length;
|
|
184
|
+
this.currentMood = frames[frameIndex];
|
|
185
|
+
}, duration / 2);
|
|
186
|
+
|
|
187
|
+
setTimeout(() => {
|
|
188
|
+
this.stopAnimation();
|
|
189
|
+
this.currentMood = 'idle';
|
|
190
|
+
}, duration);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
stopAnimation() {
|
|
194
|
+
if (this.animationFrame) {
|
|
195
|
+
clearInterval(this.animationFrame);
|
|
196
|
+
this.animationFrame = null;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
clear() {
|
|
201
|
+
if (!this.showCompanion) return;
|
|
202
|
+
process.stdout.write('\x1b[9A'); // Move up lines
|
|
203
|
+
process.stdout.write('\x1b[J'); // Clear to end of screen
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export const byte = new Byte();
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { contextManager } from '../agent/context.js';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
|
|
5
|
+
export async function addFile(filePath: string) {
|
|
6
|
+
const fullPath = path.resolve(filePath);
|
|
7
|
+
await contextManager.addFile(fullPath);
|
|
8
|
+
console.log(chalk.green(`\n ✓ Added ${fullPath} to context\n`));
|
|
9
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { setCompanionEnabled } from '../ui/byte.js';
|
|
5
|
+
|
|
6
|
+
export async function config(opts: { companion?: boolean; noCompanion?: boolean }) {
|
|
7
|
+
const configDir = path.join(process.env.HOME || '', '.vcode');
|
|
8
|
+
const configPath = path.join(configDir, 'config.json');
|
|
9
|
+
|
|
10
|
+
// Ensure directory exists
|
|
11
|
+
if (!fs.existsSync(configDir)) {
|
|
12
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Load existing config
|
|
16
|
+
let config: Record<string, any> = {};
|
|
17
|
+
if (fs.existsSync(configPath)) {
|
|
18
|
+
try {
|
|
19
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
20
|
+
} catch {}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Update config
|
|
24
|
+
if (opts.companion !== undefined) {
|
|
25
|
+
config.companion = opts.companion;
|
|
26
|
+
setCompanionEnabled(opts.companion);
|
|
27
|
+
}
|
|
28
|
+
if (opts.noCompanion !== undefined) {
|
|
29
|
+
config.companion = !opts.noCompanion;
|
|
30
|
+
setCompanionEnabled(!opts.noCompanion);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
34
|
+
|
|
35
|
+
console.log(chalk.green('\n ✓ Settings saved\n'));
|
|
36
|
+
console.log(chalk.gray(` Config: ${configPath}\n`));
|
|
37
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
|
|
3
|
+
const COMMANDS = [
|
|
4
|
+
{ cmd: 'vcode login', desc: 'Authenticate with Vynthen email and password' },
|
|
5
|
+
{ cmd: 'vcode logout', desc: 'Clear stored credentials' },
|
|
6
|
+
{ cmd: 'vcode status', desc: 'Show user info and plan tier' },
|
|
7
|
+
{ cmd: 'vcode', desc: 'Start interactive agent in current folder' },
|
|
8
|
+
{ cmd: 'vcode history', desc: 'Show session history' },
|
|
9
|
+
{ cmd: 'vcode undo', desc: 'Undo last file operation' },
|
|
10
|
+
{ cmd: 'vcode clear', desc: 'Clear conversation context' },
|
|
11
|
+
{ cmd: 'vcode add <file>', desc: 'Add file to context' },
|
|
12
|
+
{ cmd: 'vcode config --no-companion', desc: 'Disable Byte companion' },
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
export async function showHelp() {
|
|
16
|
+
console.log(chalk.hex('#a855f7').bold('\n 📖 V Code Commands\n'));
|
|
17
|
+
|
|
18
|
+
for (const cmd of COMMANDS) {
|
|
19
|
+
console.log(chalk.white(` ${cmd.cmd.padEnd(25)} ${cmd.desc}`));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
console.log(chalk.gray('\n Examples:'));
|
|
23
|
+
console.log(chalk.gray(' vcode login'));
|
|
24
|
+
console.log(chalk.gray(' vcode add src/index.ts'));
|
|
25
|
+
console.log(chalk.gray(' vcode config --no-companion'));
|
|
26
|
+
console.log('');
|
|
27
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { getOperationHistory } from '../tools/filesystem.js';
|
|
3
|
+
|
|
4
|
+
export async function history() {
|
|
5
|
+
const ops = getOperationHistory();
|
|
6
|
+
|
|
7
|
+
if (ops.length === 0) {
|
|
8
|
+
console.log(chalk.yellow('\n No operations in history\n'));
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
console.log(chalk.hex('#a855f7').bold('\n 📜 Session History\n'));
|
|
13
|
+
|
|
14
|
+
for (const op of ops.slice(0, 20)) {
|
|
15
|
+
const time = new Date(op.timestamp).toLocaleTimeString();
|
|
16
|
+
const icon = {
|
|
17
|
+
read: '📄',
|
|
18
|
+
write: '✏️',
|
|
19
|
+
delete: '🗑️',
|
|
20
|
+
create_dir: '📁',
|
|
21
|
+
rename: '📝',
|
|
22
|
+
move: '➡️'
|
|
23
|
+
}[op.type] || '•';
|
|
24
|
+
|
|
25
|
+
console.log(chalk.gray(` ${time} ${icon} ${op.type}: ${op.path}`));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
console.log('');
|
|
29
|
+
}
|
package/lib/commands/start.js
CHANGED
|
@@ -1,145 +1,24 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* V Code — Start Command.
|
|
3
|
-
* Launches the bridge, shows animated logo, connects to Vynthen.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
1
|
import chalk from 'chalk';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { spawn } from 'child_process';
|
|
12
|
-
import * as fs from 'fs';
|
|
13
|
-
import * as path from 'path';
|
|
14
|
-
|
|
15
|
-
export async function start(opts = {}) {
|
|
16
|
-
const token = await getToken();
|
|
2
|
+
import { showLogoOnce } from '../ui/logo.js';
|
|
3
|
+
import { showByteIdle } from '../ui/byte.js';
|
|
4
|
+
import { getUser } from '../auth/index.js';
|
|
5
|
+
import { startAgent } from '../agent/loop.js';
|
|
17
6
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
console.log(chalk.red('\n ✗ Token expired. Run `vcode login` to re-authenticate.\n'));
|
|
7
|
+
export async function start(opts: { approveAll?: boolean } = {}) {
|
|
8
|
+
showLogoOnce();
|
|
9
|
+
|
|
10
|
+
const user = await getUser();
|
|
11
|
+
if (!user) {
|
|
12
|
+
console.log(chalk.red('\n ✗ Not authenticated. Run vcode login first.\n'));
|
|
25
13
|
process.exit(1);
|
|
26
14
|
}
|
|
27
|
-
|
|
28
|
-
if (opts.approveAll) {
|
|
29
|
-
setSessionApproveAll(true);
|
|
30
|
-
console.log(chalk.yellow('\n ⚠ Auto-approve mode enabled. All operations will be approved automatically.\n'));
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const stopAnimation = startLogoAnimation();
|
|
34
|
-
await new Promise(resolve => setTimeout(resolve, 2500));
|
|
35
|
-
stopAnimation();
|
|
36
|
-
|
|
37
|
-
const user = await getUser();
|
|
38
|
-
|
|
39
|
-
const serverUrl = process.env.VCODE_WS_URL ||
|
|
40
|
-
(process.env.VCODE_LOCAL === 'false' ? 'wss://vynthen.com/api/vcode-ws' : 'ws://localhost:3001');
|
|
41
|
-
const displayUrl = process.env.VCODE_LOCAL === 'false' ? 'vynthen.com' : 'localhost:3001';
|
|
42
|
-
const autoStartServer = opts.server !== false && opts.server !== undefined;
|
|
43
15
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const globalLibDir = path.join(path.dirname(process.execPath), '..', 'lib', 'node_modules', 'vcode-cli');
|
|
49
|
-
const globalServerScript = path.join(globalLibDir, 'lib', 'server.js');
|
|
50
|
-
const finalServerScript = serverExists ? serverScript : (fs.existsSync(globalServerScript) ? globalServerScript : null);
|
|
16
|
+
console.log(chalk.gray(` Project: ${process.cwd()}`));
|
|
17
|
+
console.log(chalk.gray(` User: ${user.email}`));
|
|
18
|
+
console.log(chalk.gray(` Plan: ${user.plan}`));
|
|
19
|
+
console.log('');
|
|
51
20
|
|
|
52
|
-
|
|
53
|
-
let serverProcess = null;
|
|
21
|
+
showByteIdle();
|
|
54
22
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
console.log(chalk.gray(' Starting V Code WebSocket server...'));
|
|
58
|
-
|
|
59
|
-
serverProcess = spawn('node', [finalServerScript], {
|
|
60
|
-
stdio: 'inherit',
|
|
61
|
-
detached: false
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
// Wait for server to start
|
|
65
|
-
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
66
|
-
|
|
67
|
-
if (serverProcess.killed || !serverProcess.pid) {
|
|
68
|
-
console.log(chalk.yellow('\n ⚠ Could not start WebSocket server. Make sure port 3001 is available.\n'));
|
|
69
|
-
} else {
|
|
70
|
-
console.log(chalk.green(' ✓ WebSocket server started on port 3001'));
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
console.log('');
|
|
75
|
-
console.log(chalk.gray(' ─────────────────────────────────────────────────────'));
|
|
76
|
-
console.log(chalk.hex('#a855f7').bold(' Vynthen V Code — Bridge'));
|
|
77
|
-
console.log(chalk.gray(' ─────────────────────────────────────────────────────'));
|
|
78
|
-
console.log('');
|
|
79
|
-
console.log(chalk.white(' User: ') + chalk.cyan(user || 'authenticated'));
|
|
80
|
-
console.log(chalk.white(' Mode: ') + chalk.gray(opts.approveAll ? 'Auto-approve' : 'Human-in-the-loop'));
|
|
81
|
-
console.log(chalk.white(' Endpoint: ') + chalk.gray(displayUrl));
|
|
82
|
-
console.log('');
|
|
83
|
-
|
|
84
|
-
let currentWorkingDir = process.cwd();
|
|
85
|
-
let terminalOutput = [];
|
|
86
|
-
|
|
87
|
-
const bridge = new Bridge({
|
|
88
|
-
onStatusChange: (status) => {
|
|
89
|
-
switch (status) {
|
|
90
|
-
case 'connected':
|
|
91
|
-
break;
|
|
92
|
-
case 'reconnecting':
|
|
93
|
-
break;
|
|
94
|
-
case 'auth_failed':
|
|
95
|
-
case 'token_expired':
|
|
96
|
-
process.exit(1);
|
|
97
|
-
break;
|
|
98
|
-
case 'disconnected':
|
|
99
|
-
break;
|
|
100
|
-
}
|
|
101
|
-
},
|
|
102
|
-
onOperation: (opType, params) => {
|
|
103
|
-
},
|
|
104
|
-
onError: (err) => {
|
|
105
|
-
},
|
|
106
|
-
onWorkingDirChange: (path) => {
|
|
107
|
-
currentWorkingDir = path;
|
|
108
|
-
console.log(chalk.gray(` 📁 Working directory: ${path}`));
|
|
109
|
-
},
|
|
110
|
-
onTerminalOutput: (output) => {
|
|
111
|
-
terminalOutput.push(output);
|
|
112
|
-
if (terminalOutput.length > 100) {
|
|
113
|
-
terminalOutput = terminalOutput.slice(-100);
|
|
114
|
-
}
|
|
115
|
-
process.stdout.write(output);
|
|
116
|
-
},
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
const shutdown = () => {
|
|
120
|
-
console.log(chalk.gray('\n\n Shutting down V Code bridge...'));
|
|
121
|
-
bridge.disconnect();
|
|
122
|
-
if (serverProcess) {
|
|
123
|
-
serverProcess.kill();
|
|
124
|
-
}
|
|
125
|
-
console.log(chalk.gray(` Session duration: ${bridge.getUptime()}`));
|
|
126
|
-
console.log(chalk.gray(` Operations handled: ${bridge.operationCount}`));
|
|
127
|
-
console.log(chalk.green(' Goodbye! 👋\n'));
|
|
128
|
-
process.exit(0);
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
process.on('SIGINT', shutdown);
|
|
132
|
-
process.on('SIGTERM', shutdown);
|
|
133
|
-
|
|
134
|
-
try {
|
|
135
|
-
await bridge.connect();
|
|
136
|
-
} catch (err) {
|
|
137
|
-
console.log(chalk.red(` ✗ ${err.message}\n`));
|
|
138
|
-
if (serverProcess) {
|
|
139
|
-
serverProcess.kill();
|
|
140
|
-
}
|
|
141
|
-
process.exit(1);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
await new Promise(() => {});
|
|
145
|
-
}
|
|
23
|
+
await startAgent(opts);
|
|
24
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { clearHistory, getLastOperation, readFile, writeFile } from '../tools/filesystem.js';
|
|
3
|
+
|
|
4
|
+
export async function undo() {
|
|
5
|
+
const lastOp = getLastOperation();
|
|
6
|
+
|
|
7
|
+
if (!lastOp) {
|
|
8
|
+
console.log(chalk.yellow('\n No operations to undo\n'));
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (lastOp.type === 'write' && lastOp.content) {
|
|
13
|
+
// Revert to original content
|
|
14
|
+
await writeFile(lastOp.path, lastOp.content, false);
|
|
15
|
+
console.log(chalk.green(`\n ✓ Reverted ${lastOp.path}\n`));
|
|
16
|
+
} else if (lastOp.type === 'delete') {
|
|
17
|
+
console.log(chalk.yellow('\n Cannot undo delete operation\n'));
|
|
18
|
+
} else {
|
|
19
|
+
console.log(chalk.yellow(`\n Cannot undo ${lastOp.type} operation\n`));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
clearHistory();
|
|
23
|
+
}
|