vynthen-vcode 1.0.0 → 1.0.1
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/package.json +1 -1
- package/vcode.js +222 -164
package/package.json
CHANGED
package/vcode.js
CHANGED
|
@@ -1,75 +1,136 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* V Code CLI — Local Bridge for Vynthen AI
|
|
5
|
-
*
|
|
6
|
-
* This CLI connects your local file system to V Code.
|
|
7
|
-
* When running, V Code can read, edit, and create files on your machine.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
3
|
const { WebSocket } = require('ws');
|
|
11
4
|
const fs = require('fs');
|
|
12
5
|
const path = require('path');
|
|
13
6
|
const os = require('os');
|
|
14
7
|
const { execSync } = require('child_process');
|
|
15
8
|
|
|
16
|
-
const VYNTHEN_BASE_URL = process.env.VYNTHEN_API_URL || '
|
|
17
|
-
const VYNTHEN_WS_URL = process.env.VYNTHEN_WS_URL || '
|
|
9
|
+
const VYNTHEN_BASE_URL = process.env.VYNTHEN_API_URL || 'https://vynthen.ai';
|
|
10
|
+
const VYNTHEN_WS_URL = process.env.VYNTHEN_WS_URL || 'wss://vynthen.ai/api/cli-bridge';
|
|
18
11
|
const TOKEN_PATH = path.join(os.homedir(), '.vcode_token');
|
|
19
12
|
|
|
13
|
+
// ── ANSI Colors ───────────────────────────────────────────────────────────
|
|
14
|
+
const C = {
|
|
15
|
+
reset: '\x1b[0m',
|
|
16
|
+
bold: '\x1b[1m',
|
|
17
|
+
dim: '\x1b[2m',
|
|
18
|
+
cyan: '\x1b[38;2;60;210;255m',
|
|
19
|
+
violet: '\x1b[38;2;167;139;250m',
|
|
20
|
+
orange: '\x1b[38;2;251;146;60m',
|
|
21
|
+
green: '\x1b[38;2;52;211;153m',
|
|
22
|
+
red: '\x1b[38;2;248;113;113m'
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const ANIMATION_FRAMES = [
|
|
26
|
+
[`╔══════════╗`, `║ / \\ ║`, `║ / ◠◠ \\ ║`, `║ / ● ● \\║`, `║ △──────△ ║`, `╚══════════╝`],
|
|
27
|
+
[`╔══════════╗`, `║ / \\ ║`, `║ / ◠◠ \\ ║`, `║ / ⊙ ⊙ \\║`, `║ △──────△ ║`, `╚══════════╝`],
|
|
28
|
+
[`╔══════════╗`, `║ / \\ ║`, `║ / ◠◠ \\ ║`, `║ / - - \\║`, `║ △──────△ ║`, `╚══════════╝`]
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
function drawAvatar(frameIdx, text = '') {
|
|
32
|
+
const f = ANIMATION_FRAMES[frameIdx % ANIMATION_FRAMES.length];
|
|
33
|
+
console.clear();
|
|
34
|
+
console.log(`\n${C.violet}${C.bold} V CODE COMPANION${C.reset}`);
|
|
35
|
+
console.log(`${C.cyan}${f[0]}`);
|
|
36
|
+
console.log(`${f[1]}`);
|
|
37
|
+
console.log(`${f[2]} ${C.orange}${text}${C.cyan}`);
|
|
38
|
+
console.log(`${f[3]}`);
|
|
39
|
+
console.log(`${f[4]}`);
|
|
40
|
+
console.log(`${f[5]}${C.reset}\n`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function playIntro(text) {
|
|
44
|
+
return new Promise(resolve => {
|
|
45
|
+
let tick = 0;
|
|
46
|
+
const interval = setInterval(() => {
|
|
47
|
+
drawAvatar(tick, text);
|
|
48
|
+
tick++;
|
|
49
|
+
if (tick > 5) {
|
|
50
|
+
clearInterval(interval);
|
|
51
|
+
resolve();
|
|
52
|
+
}
|
|
53
|
+
}, 200);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
20
57
|
// ── Login Command ────────────────────────────────────────────────────────
|
|
21
58
|
if (process.argv[2] === 'login') {
|
|
22
59
|
const readline = require('readline');
|
|
23
|
-
const rl = readline.createInterface({
|
|
24
|
-
|
|
25
|
-
|
|
60
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
61
|
+
|
|
62
|
+
const askEmail = () => new Promise(r => {
|
|
63
|
+
console.log(`${C.cyan}?${C.reset} ${C.bold}Email:${C.reset}`);
|
|
64
|
+
rl.question(' > ', ans => r(ans));
|
|
26
65
|
});
|
|
27
66
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
output: process.stdout
|
|
33
|
-
});
|
|
67
|
+
const askPass = () => new Promise(r => {
|
|
68
|
+
console.log(`\n${C.cyan}?${C.reset} ${C.bold}Password:${C.reset} ${C.dim}(Invisible securely)${C.reset}`);
|
|
69
|
+
process.stdout.write(' > ');
|
|
70
|
+
let password = '';
|
|
34
71
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
72
|
+
process.stdin.setRawMode(true);
|
|
73
|
+
process.stdin.resume();
|
|
74
|
+
process.stdin.setEncoding('utf8');
|
|
75
|
+
|
|
76
|
+
const listener = (chunk) => {
|
|
77
|
+
chunk = String(chunk);
|
|
78
|
+
switch (chunk) {
|
|
79
|
+
case '\n':
|
|
80
|
+
case '\r':
|
|
81
|
+
case '\u0004':
|
|
82
|
+
process.stdin.removeListener('data', listener);
|
|
83
|
+
process.stdin.setRawMode(false);
|
|
84
|
+
process.stdin.pause();
|
|
85
|
+
console.log('');
|
|
86
|
+
r(password);
|
|
87
|
+
break;
|
|
88
|
+
case '\u0003': // Ctrl+C
|
|
89
|
+
process.exit();
|
|
90
|
+
break;
|
|
91
|
+
case '\x7f':
|
|
92
|
+
case '\b':
|
|
93
|
+
password = password.slice(0, -1);
|
|
94
|
+
break;
|
|
95
|
+
default:
|
|
96
|
+
password += chunk;
|
|
97
|
+
break;
|
|
41
98
|
}
|
|
42
99
|
};
|
|
100
|
+
process.stdin.on('data', listener);
|
|
101
|
+
});
|
|
43
102
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
try {
|
|
50
|
-
const fetch = globalThis.fetch || require('node-fetch');
|
|
51
|
-
const res = await fetch(`${VYNTHEN_BASE_URL}/api/auth/cli-login`, {
|
|
52
|
-
method: 'POST',
|
|
53
|
-
headers: { 'Content-Type': 'application/json' },
|
|
54
|
-
body: JSON.stringify({ email: email.trim(), password })
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
if (!res.ok) {
|
|
58
|
-
const err = await res.json().catch(()=>({}));
|
|
59
|
-
console.error('\\x1b[31m%s\\x1b[0m', `Login failed: ${err.error || res.statusText}`);
|
|
60
|
-
process.exit(1);
|
|
61
|
-
}
|
|
103
|
+
(async () => {
|
|
104
|
+
await playIntro("Initializing Secure Link...");
|
|
105
|
+
const email = await askEmail();
|
|
106
|
+
const password = await askPass();
|
|
107
|
+
rl.close();
|
|
62
108
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
109
|
+
console.log(`${C.violet}\n⟳ Authenticating with Vynthen...${C.reset}`);
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
const fetch = globalThis.fetch || require('node-fetch');
|
|
113
|
+
const res = await fetch(`${VYNTHEN_BASE_URL}/api/auth/cli-login`, {
|
|
114
|
+
method: 'POST',
|
|
115
|
+
headers: { 'Content-Type': 'application/json' },
|
|
116
|
+
body: JSON.stringify({ email: email.trim(), password })
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
if (!res.ok) {
|
|
120
|
+
const err = await res.json().catch(()=>({}));
|
|
121
|
+
console.error(`\n${C.red}✖ Login failed:${C.reset} ${err.error || res.statusText}`);
|
|
69
122
|
process.exit(1);
|
|
70
123
|
}
|
|
71
|
-
|
|
72
|
-
|
|
124
|
+
|
|
125
|
+
const data = await res.json();
|
|
126
|
+
fs.writeFileSync(TOKEN_PATH, data.token, { mode: 0o600 });
|
|
127
|
+
console.log(`\n${C.green}✔ Successfully logged in. Secure Token saved.${C.reset}`);
|
|
128
|
+
process.exit(0);
|
|
129
|
+
} catch (err) {
|
|
130
|
+
console.error(`\n${C.red}✖ Connection error:${C.reset} ${err.message}`);
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
})();
|
|
73
134
|
return;
|
|
74
135
|
}
|
|
75
136
|
|
|
@@ -79,136 +140,133 @@ let token = process.env.VYNTHEN_CLI_TOKEN;
|
|
|
79
140
|
if (!token) {
|
|
80
141
|
try {
|
|
81
142
|
token = fs.readFileSync(TOKEN_PATH, 'utf8').trim();
|
|
82
|
-
} catch (err) {
|
|
83
|
-
// file not found or unreadable
|
|
84
|
-
}
|
|
143
|
+
} catch (err) {}
|
|
85
144
|
}
|
|
86
145
|
|
|
87
146
|
if (!token && !process.argv.includes('--anonymous')) {
|
|
88
|
-
console.error(
|
|
89
|
-
console.log(
|
|
147
|
+
console.error(`${C.red}✖ Error: Not logged in.${C.reset}`);
|
|
148
|
+
console.log(`Run \`${C.cyan}Vcode login${C.reset}\` or export VYNTHEN_CLI_TOKEN.`);
|
|
90
149
|
process.exit(1);
|
|
91
150
|
}
|
|
92
151
|
|
|
93
|
-
|
|
94
|
-
|
|
152
|
+
(async () => {
|
|
153
|
+
if (process.argv[2] !== '--silent') {
|
|
154
|
+
await playIntro("Awaiting AI Orders...");
|
|
155
|
+
}
|
|
156
|
+
console.log(`${C.violet}≈ Establishing secure bridge to Vynthen...${C.reset}`);
|
|
157
|
+
const ws = new WebSocket(`${VYNTHEN_WS_URL}?token=${token || 'anonymous'}`);
|
|
95
158
|
|
|
96
|
-
// ── Handlers
|
|
159
|
+
// ── Handlers ───────────────────────────────────────────────
|
|
160
|
+
const handleFileRead = (filepath) => {
|
|
161
|
+
try {
|
|
162
|
+
const absolutePath = path.resolve(process.cwd(), filepath);
|
|
163
|
+
const content = fs.readFileSync(absolutePath, 'utf8');
|
|
164
|
+
return { success: true, content };
|
|
165
|
+
} catch (err) {
|
|
166
|
+
return { success: false, error: err.message };
|
|
167
|
+
}
|
|
168
|
+
};
|
|
97
169
|
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
170
|
+
const handleFileWrite = (filepath, content) => {
|
|
171
|
+
try {
|
|
172
|
+
const absolutePath = path.resolve(process.cwd(), filepath);
|
|
173
|
+
fs.mkdirSync(path.dirname(absolutePath), { recursive: true });
|
|
174
|
+
fs.writeFileSync(absolutePath, content, 'utf8');
|
|
175
|
+
return { success: true };
|
|
176
|
+
} catch (err) {
|
|
177
|
+
return { success: false, error: err.message };
|
|
178
|
+
}
|
|
179
|
+
};
|
|
107
180
|
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
};
|
|
181
|
+
const handleCommand = (command) => {
|
|
182
|
+
try {
|
|
183
|
+
const output = execSync(command, { encoding: 'utf8', cwd: process.cwd(), timeout: 10000 });
|
|
184
|
+
return { success: true, output };
|
|
185
|
+
} catch (err) {
|
|
186
|
+
return { success: false, error: err.message, output: err.stdout ? err.stdout.toString() : '' };
|
|
187
|
+
}
|
|
188
|
+
};
|
|
118
189
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
190
|
+
ws.on('open', () => {
|
|
191
|
+
console.log(`${C.green}✔ Connected to V Code AI.${C.reset}`);
|
|
192
|
+
console.log(`${C.dim}Listening in: ${process.cwd()}${C.reset}`);
|
|
193
|
+
|
|
194
|
+
ws.send(JSON.stringify({
|
|
195
|
+
type: 'init',
|
|
196
|
+
hostname: os.hostname(),
|
|
197
|
+
cwd: process.cwd()
|
|
198
|
+
}));
|
|
199
|
+
});
|
|
127
200
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
hostname: require('os').hostname(),
|
|
137
|
-
cwd: process.cwd()
|
|
138
|
-
}));
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
const readline = require('readline');
|
|
142
|
-
const rl = readline.createInterface({
|
|
143
|
-
input: process.stdin,
|
|
144
|
-
output: process.stdout
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
const askApproval = (question) => {
|
|
148
|
-
return new Promise(resolve => {
|
|
149
|
-
rl.question(`\x1b[33m[Authorization Required]\x1b[0m ${question} (y/N): `, answer => {
|
|
150
|
-
resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
|
|
201
|
+
const readline = require('readline');
|
|
202
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
203
|
+
|
|
204
|
+
const askApproval = (question) => {
|
|
205
|
+
return new Promise(resolve => {
|
|
206
|
+
rl.question(`\n${C.orange}[Authorization]${C.reset} ${question} (y/N): `, answer => {
|
|
207
|
+
resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
|
|
208
|
+
});
|
|
151
209
|
});
|
|
152
|
-
}
|
|
153
|
-
};
|
|
210
|
+
};
|
|
154
211
|
|
|
155
|
-
ws.on('message', async (data) => {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
switch (req.type) {
|
|
160
|
-
case 'read_file':
|
|
161
|
-
console.log(`\x1b[34m[Agent]\x1b[0m Reading ${req.filepath}`);
|
|
162
|
-
response.result = handleFileRead(req.filepath);
|
|
163
|
-
break;
|
|
164
|
-
|
|
165
|
-
case 'write_file':
|
|
166
|
-
console.log(`\n\x1b[34m[Agent Request]\x1b[0m Write to \x1b[36m${req.filepath}\x1b[0m (${req.content.length} bytes)`);
|
|
167
|
-
const writeApproved = await askApproval(`Allow Agent to modify ${req.filepath}?`);
|
|
168
|
-
if (writeApproved) {
|
|
169
|
-
response.result = handleFileWrite(req.filepath, req.content);
|
|
170
|
-
console.log('\x1b[32m✓ Allowed\x1b[0m');
|
|
171
|
-
} else {
|
|
172
|
-
response.result = { success: false, error: 'User denied file write permission.' };
|
|
173
|
-
console.log('\x1b[31m✕ Denied\x1b[0m');
|
|
174
|
-
}
|
|
175
|
-
break;
|
|
176
|
-
|
|
177
|
-
case 'run_command':
|
|
178
|
-
console.log(`\n\x1b[34m[Agent Request]\x1b[0m Execute command: \x1b[36m${req.command}\x1b[0m`);
|
|
179
|
-
const cmdApproved = await askApproval(`Allow Agent to run this command?`);
|
|
180
|
-
if (cmdApproved) {
|
|
181
|
-
response.result = handleCommand(req.command);
|
|
182
|
-
console.log('\x1b[32m✓ Allowed\x1b[0m');
|
|
183
|
-
} else {
|
|
184
|
-
response.result = { success: false, error: 'User denied command execution permission.' };
|
|
185
|
-
console.log('\x1b[31m✕ Denied\x1b[0m');
|
|
186
|
-
}
|
|
187
|
-
break;
|
|
212
|
+
ws.on('message', async (data) => {
|
|
213
|
+
const req = JSON.parse(data);
|
|
214
|
+
let response = { id: req.id, type: req.type };
|
|
188
215
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
216
|
+
switch (req.type) {
|
|
217
|
+
case 'read_file':
|
|
218
|
+
console.log(`${C.cyan}[Agent]${C.reset} Reading ${req.filepath}`);
|
|
219
|
+
response.result = handleFileRead(req.filepath);
|
|
220
|
+
break;
|
|
192
221
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
222
|
+
case 'write_file':
|
|
223
|
+
console.log(`\n${C.violet}[Agent]${C.reset} Write to ${C.bold}${req.filepath}${C.reset} (${req.content.length} bytes)`);
|
|
224
|
+
const writeApproved = await askApproval(`Allow Agent to modify ${req.filepath}?`);
|
|
225
|
+
if (writeApproved) {
|
|
226
|
+
response.result = handleFileWrite(req.filepath, req.content);
|
|
227
|
+
console.log(`${C.green}✔ Allowed${C.reset}`);
|
|
228
|
+
} else {
|
|
229
|
+
response.result = { success: false, error: 'User denied permission.' };
|
|
230
|
+
console.log(`${C.red}✖ Denied${C.reset}`);
|
|
231
|
+
}
|
|
232
|
+
break;
|
|
233
|
+
|
|
234
|
+
case 'run_command':
|
|
235
|
+
console.log(`\n${C.violet}[Agent]${C.reset} Execute command: ${C.bold}${req.command}${C.reset}`);
|
|
236
|
+
const cmdApproved = await askApproval(`Allow Agent to run this command?`);
|
|
237
|
+
if (cmdApproved) {
|
|
238
|
+
response.result = handleCommand(req.command);
|
|
239
|
+
console.log(`${C.green}✔ Allowed${C.reset}`);
|
|
240
|
+
} else {
|
|
241
|
+
response.result = { success: false, error: 'User denied permission.' };
|
|
242
|
+
console.log(`${C.red}✖ Denied${C.reset}`);
|
|
243
|
+
}
|
|
244
|
+
break;
|
|
197
245
|
|
|
198
|
-
|
|
199
|
-
}
|
|
246
|
+
case 'ping':
|
|
247
|
+
response.result = { success: true };
|
|
248
|
+
break;
|
|
200
249
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}
|
|
250
|
+
default:
|
|
251
|
+
console.error(`${C.red}Unknown command type: ${req.type}${C.reset}`);
|
|
252
|
+
response.result = { success: false, error: 'Unknown command type' };
|
|
253
|
+
}
|
|
205
254
|
|
|
206
|
-
ws.
|
|
207
|
-
|
|
208
|
-
|
|
255
|
+
ws.send(JSON.stringify(response));
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
ws.on('close', () => {
|
|
259
|
+
console.log(`${C.orange}\nConnection closed.${C.reset} V Code exiting.`);
|
|
260
|
+
process.exit(0);
|
|
261
|
+
});
|
|
209
262
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
263
|
+
ws.on('error', (err) => {
|
|
264
|
+
console.error(`${C.red}WebSocket Error:${C.reset} ${err.message}`);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
process.on('SIGINT', () => {
|
|
268
|
+
console.log(`\n${C.dim}Gracefully shutting down...${C.reset}`);
|
|
269
|
+
ws.close();
|
|
270
|
+
process.exit(0);
|
|
271
|
+
});
|
|
272
|
+
})();
|