vcode-cli 1.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 +21 -0
- package/README.md +143 -0
- package/bin/vcode.js +64 -0
- package/lib/audit.js +48 -0
- package/lib/auth.js +43 -0
- package/lib/bridge.js +245 -0
- package/lib/commands/login.js +92 -0
- package/lib/commands/logout.js +34 -0
- package/lib/commands/start.js +100 -0
- package/lib/commands/status.js +84 -0
- package/lib/dispatcher.js +93 -0
- package/lib/keychain.js +74 -0
- package/lib/logo.js +144 -0
- package/lib/operations/browser.js +51 -0
- package/lib/operations/clipboard.js +52 -0
- package/lib/operations/filesystem.js +165 -0
- package/lib/operations/keyboard.js +88 -0
- package/lib/operations/mouse.js +114 -0
- package/lib/operations/process.js +66 -0
- package/lib/operations/screenshot.js +56 -0
- package/lib/operations/terminal.js +85 -0
- package/lib/permissions.js +113 -0
- package/package.json +55 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V Code — Start Command.
|
|
3
|
+
* Launches the bridge, shows animated logo, connects to Vynthen.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { showLogo, startLogoAnimation } from '../logo.js';
|
|
8
|
+
import { getToken, isTokenExpired, getUser } from '../keychain.js';
|
|
9
|
+
import { Bridge } from '../bridge.js';
|
|
10
|
+
import { setSessionApproveAll } from '../permissions.js';
|
|
11
|
+
|
|
12
|
+
export async function start(opts = {}) {
|
|
13
|
+
// ── Pre-flight checks ──
|
|
14
|
+
const token = await getToken();
|
|
15
|
+
|
|
16
|
+
if (!token) {
|
|
17
|
+
console.log(chalk.red('\n ✗ Not authenticated. Run `vcode login` first.\n'));
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (isTokenExpired(token)) {
|
|
22
|
+
console.log(chalk.red('\n ✗ Token expired. Run `vcode login` to re-authenticate.\n'));
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ── Session approve-all mode ──
|
|
27
|
+
if (opts.approveAll) {
|
|
28
|
+
setSessionApproveAll(true);
|
|
29
|
+
console.log(chalk.yellow('\n ⚠ Auto-approve mode enabled. All operations will be approved automatically.\n'));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ── Show animated logo ──
|
|
33
|
+
const stopAnimation = startLogoAnimation();
|
|
34
|
+
|
|
35
|
+
// Let animation play for 2.5 seconds then stop for clean UI
|
|
36
|
+
await new Promise(resolve => setTimeout(resolve, 2500));
|
|
37
|
+
stopAnimation();
|
|
38
|
+
|
|
39
|
+
// Show connection info
|
|
40
|
+
const user = await getUser();
|
|
41
|
+
console.log('');
|
|
42
|
+
console.log(chalk.gray(' ─────────────────────────────────────────────────────'));
|
|
43
|
+
console.log(chalk.hex('#a855f7').bold(' Vynthen V Code — Local Bridge'));
|
|
44
|
+
console.log(chalk.gray(' ─────────────────────────────────────────────────────'));
|
|
45
|
+
console.log('');
|
|
46
|
+
console.log(chalk.white(' User: ') + chalk.cyan(user || 'authenticated'));
|
|
47
|
+
console.log(chalk.white(' Mode: ') + chalk.gray(opts.approveAll ? 'Auto-approve' : 'Human-in-the-loop'));
|
|
48
|
+
console.log(chalk.white(' Endpoint: ') + chalk.gray('wss://vynthen.com/api/vcode-ws'));
|
|
49
|
+
console.log('');
|
|
50
|
+
|
|
51
|
+
// ── Launch bridge ──
|
|
52
|
+
const bridge = new Bridge({
|
|
53
|
+
onStatusChange: (status) => {
|
|
54
|
+
switch (status) {
|
|
55
|
+
case 'connected':
|
|
56
|
+
// Already handled inside bridge
|
|
57
|
+
break;
|
|
58
|
+
case 'reconnecting':
|
|
59
|
+
// Already handled inside bridge
|
|
60
|
+
break;
|
|
61
|
+
case 'auth_failed':
|
|
62
|
+
case 'token_expired':
|
|
63
|
+
process.exit(1);
|
|
64
|
+
break;
|
|
65
|
+
case 'disconnected':
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
onOperation: (opType, params) => {
|
|
70
|
+
// Logged inside bridge
|
|
71
|
+
},
|
|
72
|
+
onError: (err) => {
|
|
73
|
+
// Logged inside bridge
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// ── Graceful shutdown ──
|
|
78
|
+
const shutdown = () => {
|
|
79
|
+
console.log(chalk.gray('\n\n Shutting down V Code bridge...'));
|
|
80
|
+
bridge.disconnect();
|
|
81
|
+
console.log(chalk.gray(` Session duration: ${bridge.getUptime()}`));
|
|
82
|
+
console.log(chalk.gray(` Operations handled: ${bridge.operationCount}`));
|
|
83
|
+
console.log(chalk.green(' Goodbye! 👋\n'));
|
|
84
|
+
process.exit(0);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
process.on('SIGINT', shutdown);
|
|
88
|
+
process.on('SIGTERM', shutdown);
|
|
89
|
+
|
|
90
|
+
// ── Connect ──
|
|
91
|
+
try {
|
|
92
|
+
await bridge.connect();
|
|
93
|
+
} catch (err) {
|
|
94
|
+
console.log(chalk.red(` ✗ ${err.message}\n`));
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Keep process alive
|
|
99
|
+
await new Promise(() => {});
|
|
100
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V Code — Status Command.
|
|
3
|
+
* Shows connection status, logged-in user, and token info.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { getToken, getUser, decodeToken, isTokenExpired } from '../keychain.js';
|
|
8
|
+
import { getAuditPath, readAuditLog } from '../audit.js';
|
|
9
|
+
|
|
10
|
+
export async function status() {
|
|
11
|
+
console.log('');
|
|
12
|
+
console.log(chalk.hex('#a855f7').bold(' V Code — Status\n'));
|
|
13
|
+
|
|
14
|
+
// ── Auth Status ──
|
|
15
|
+
const token = await getToken();
|
|
16
|
+
const user = await getUser();
|
|
17
|
+
|
|
18
|
+
if (!token) {
|
|
19
|
+
console.log(chalk.white(' Auth: ') + chalk.red('Not logged in'));
|
|
20
|
+
console.log(chalk.gray(' Run `vcode login` to authenticate.\n'));
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const expired = isTokenExpired(token);
|
|
25
|
+
const payload = decodeToken(token);
|
|
26
|
+
|
|
27
|
+
console.log(chalk.white(' Auth: ') + (expired ? chalk.red('Token expired') : chalk.green('Authenticated')));
|
|
28
|
+
console.log(chalk.white(' User: ') + chalk.cyan(user || payload?.email || 'unknown'));
|
|
29
|
+
|
|
30
|
+
if (payload?.exp) {
|
|
31
|
+
const expDate = new Date(payload.exp * 1000);
|
|
32
|
+
const now = Date.now();
|
|
33
|
+
if (expired) {
|
|
34
|
+
console.log(chalk.white(' Expired: ') + chalk.red(expDate.toLocaleString()));
|
|
35
|
+
} else {
|
|
36
|
+
const remaining = payload.exp * 1000 - now;
|
|
37
|
+
const hours = Math.floor(remaining / 3600000);
|
|
38
|
+
const mins = Math.floor((remaining % 3600000) / 60000);
|
|
39
|
+
console.log(chalk.white(' Expires: ') + chalk.gray(`${expDate.toLocaleString()} (${hours}h ${mins}m remaining)`));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (payload?.iat) {
|
|
44
|
+
const iatDate = new Date(payload.iat * 1000);
|
|
45
|
+
console.log(chalk.white(' Issued: ') + chalk.gray(iatDate.toLocaleString()));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ── Token storage ──
|
|
49
|
+
console.log(chalk.white(' Storage: ') + chalk.gray('OS native keychain (encrypted)'));
|
|
50
|
+
|
|
51
|
+
// ── Audit log ──
|
|
52
|
+
const auditPath = getAuditPath();
|
|
53
|
+
console.log(chalk.white(' Audit: ') + chalk.gray(auditPath));
|
|
54
|
+
|
|
55
|
+
// ── Platform ──
|
|
56
|
+
console.log('');
|
|
57
|
+
console.log(chalk.white(' Platform: ') + chalk.gray(`${process.platform} ${process.arch}`));
|
|
58
|
+
console.log(chalk.white(' Node: ') + chalk.gray(process.version));
|
|
59
|
+
console.log(chalk.white(' CWD: ') + chalk.gray(process.cwd()));
|
|
60
|
+
|
|
61
|
+
// ── Recent audit entries ──
|
|
62
|
+
console.log('');
|
|
63
|
+
console.log(chalk.white(' Recent operations:'));
|
|
64
|
+
const log = readAuditLog(5);
|
|
65
|
+
if (log === '(no audit log yet)') {
|
|
66
|
+
console.log(chalk.gray(' No operations recorded yet.'));
|
|
67
|
+
} else {
|
|
68
|
+
log.split('\n').forEach(line => {
|
|
69
|
+
if (line.includes('APPROVED')) {
|
|
70
|
+
console.log(chalk.green(` ${line}`));
|
|
71
|
+
} else if (line.includes('DENIED')) {
|
|
72
|
+
console.log(chalk.red(` ${line}`));
|
|
73
|
+
} else {
|
|
74
|
+
console.log(chalk.gray(` ${line}`));
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
console.log('');
|
|
80
|
+
|
|
81
|
+
if (expired) {
|
|
82
|
+
console.log(chalk.yellow(' ⚠ Your token has expired. Run `vcode login` to re-authenticate.\n'));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V Code — Operation Dispatcher.
|
|
3
|
+
* Routes incoming operation requests to the correct handler.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readFileOp, readFileLinesOp, writeFileOp, deleteFileOp, renameFileOp, moveFileOp, createDirOp, deleteDirOp, listDirOp } from './operations/filesystem.js';
|
|
7
|
+
import { execCommandOp, spawnCommandOp } from './operations/terminal.js';
|
|
8
|
+
import { mouseMove, mouseClick, mouseDoubleClick, mouseDrag } from './operations/mouse.js';
|
|
9
|
+
import { keyType, keyPress } from './operations/keyboard.js';
|
|
10
|
+
import { screenshotOp, listDisplaysOp } from './operations/screenshot.js';
|
|
11
|
+
import { listProcessesOp, killProcessOp } from './operations/process.js';
|
|
12
|
+
import { clipboardReadOp, clipboardWriteOp } from './operations/clipboard.js';
|
|
13
|
+
import { openUrlOp, openFolderOp } from './operations/browser.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Dispatch an operation by type.
|
|
17
|
+
* @param {string} type — operation type identifier
|
|
18
|
+
* @param {object} params — operation parameters
|
|
19
|
+
* @returns {object} — operation result
|
|
20
|
+
*/
|
|
21
|
+
export async function dispatch(type, params = {}) {
|
|
22
|
+
switch (type) {
|
|
23
|
+
// ── File System ──
|
|
24
|
+
case 'READ_FILE':
|
|
25
|
+
return readFileOp(params.path);
|
|
26
|
+
case 'READ_FILE_LINES':
|
|
27
|
+
return readFileLinesOp(params.path, params.startLine, params.endLine);
|
|
28
|
+
case 'WRITE_FILE':
|
|
29
|
+
case 'CREATE_FILE':
|
|
30
|
+
return writeFileOp(params.path, params.content);
|
|
31
|
+
case 'DELETE_FILE':
|
|
32
|
+
return deleteFileOp(params.path);
|
|
33
|
+
case 'RENAME_FILE':
|
|
34
|
+
return renameFileOp(params.oldPath, params.newPath);
|
|
35
|
+
case 'MOVE_FILE':
|
|
36
|
+
return moveFileOp(params.oldPath, params.newPath);
|
|
37
|
+
case 'CREATE_DIR':
|
|
38
|
+
return createDirOp(params.path);
|
|
39
|
+
case 'DELETE_DIR':
|
|
40
|
+
return deleteDirOp(params.path);
|
|
41
|
+
case 'LIST_DIR':
|
|
42
|
+
return listDirOp(params.path);
|
|
43
|
+
|
|
44
|
+
// ── Terminal ──
|
|
45
|
+
case 'EXEC_COMMAND':
|
|
46
|
+
return execCommandOp(params.command, params.cwd, params.timeout);
|
|
47
|
+
case 'SPAWN_COMMAND':
|
|
48
|
+
return spawnCommandOp(params.command, params.args, params.cwd);
|
|
49
|
+
|
|
50
|
+
// ── Mouse ──
|
|
51
|
+
case 'MOUSE_MOVE':
|
|
52
|
+
return mouseMove(params.x, params.y);
|
|
53
|
+
case 'MOUSE_CLICK':
|
|
54
|
+
return mouseClick(params.button, params.x, params.y);
|
|
55
|
+
case 'MOUSE_DOUBLE_CLICK':
|
|
56
|
+
return mouseDoubleClick(params.x, params.y);
|
|
57
|
+
case 'MOUSE_DRAG':
|
|
58
|
+
return mouseDrag(params.fromX, params.fromY, params.toX, params.toY);
|
|
59
|
+
|
|
60
|
+
// ── Keyboard ──
|
|
61
|
+
case 'KEY_TYPE':
|
|
62
|
+
return keyType(params.text);
|
|
63
|
+
case 'KEY_PRESS':
|
|
64
|
+
return keyPress(params.keys);
|
|
65
|
+
|
|
66
|
+
// ── Screenshot ──
|
|
67
|
+
case 'SCREENSHOT':
|
|
68
|
+
return screenshotOp(params.display);
|
|
69
|
+
case 'LIST_DISPLAYS':
|
|
70
|
+
return listDisplaysOp();
|
|
71
|
+
|
|
72
|
+
// ── Process ──
|
|
73
|
+
case 'LIST_PROCESSES':
|
|
74
|
+
return listProcessesOp();
|
|
75
|
+
case 'KILL_PROCESS':
|
|
76
|
+
return killProcessOp(params.pid, params.signal);
|
|
77
|
+
|
|
78
|
+
// ── Clipboard ──
|
|
79
|
+
case 'CLIPBOARD_READ':
|
|
80
|
+
return clipboardReadOp();
|
|
81
|
+
case 'CLIPBOARD_WRITE':
|
|
82
|
+
return clipboardWriteOp(params.text);
|
|
83
|
+
|
|
84
|
+
// ── Browser / Files ──
|
|
85
|
+
case 'OPEN_URL':
|
|
86
|
+
return openUrlOp(params.url);
|
|
87
|
+
case 'OPEN_FOLDER':
|
|
88
|
+
return openFolderOp(params.path);
|
|
89
|
+
|
|
90
|
+
default:
|
|
91
|
+
return { success: false, error: `Unknown operation type: ${type}` };
|
|
92
|
+
}
|
|
93
|
+
}
|
package/lib/keychain.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V Code — OS Keychain storage via keytar.
|
|
3
|
+
* Stores JWT token securely in the native credential store.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import keytar from 'keytar';
|
|
7
|
+
|
|
8
|
+
const SERVICE = 'vcode-cli';
|
|
9
|
+
const ACCOUNT = 'vynthen-jwt';
|
|
10
|
+
const ACCOUNT_USER = 'vynthen-user';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Store JWT token in OS keychain.
|
|
14
|
+
*/
|
|
15
|
+
export async function storeToken(token) {
|
|
16
|
+
await keytar.setPassword(SERVICE, ACCOUNT, token);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Retrieve JWT token from OS keychain.
|
|
21
|
+
* Returns null if not found.
|
|
22
|
+
*/
|
|
23
|
+
export async function getToken() {
|
|
24
|
+
return await keytar.getPassword(SERVICE, ACCOUNT);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Delete JWT token from OS keychain.
|
|
29
|
+
*/
|
|
30
|
+
export async function deleteToken() {
|
|
31
|
+
await keytar.deletePassword(SERVICE, ACCOUNT);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Store user email in keychain.
|
|
36
|
+
*/
|
|
37
|
+
export async function storeUser(email) {
|
|
38
|
+
await keytar.setPassword(SERVICE, ACCOUNT_USER, email);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Retrieve stored user email.
|
|
43
|
+
*/
|
|
44
|
+
export async function getUser() {
|
|
45
|
+
return await keytar.getPassword(SERVICE, ACCOUNT_USER);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Delete stored user email.
|
|
50
|
+
*/
|
|
51
|
+
export async function deleteUser() {
|
|
52
|
+
await keytar.deletePassword(SERVICE, ACCOUNT_USER);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Decode a JWT and return its payload (without verification).
|
|
57
|
+
*/
|
|
58
|
+
export function decodeToken(token) {
|
|
59
|
+
try {
|
|
60
|
+
const payload = token.split('.')[1];
|
|
61
|
+
return JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'));
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Check if a JWT token is expired.
|
|
69
|
+
*/
|
|
70
|
+
export function isTokenExpired(token) {
|
|
71
|
+
const payload = decodeToken(token);
|
|
72
|
+
if (!payload || !payload.exp) return true;
|
|
73
|
+
return Date.now() >= payload.exp * 1000;
|
|
74
|
+
}
|
package/lib/logo.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V Code — Retro ASCII Logo with animated blinking eyes.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
|
|
7
|
+
const FRAMES_OPEN = [
|
|
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
|
+
];
|
|
44
|
+
|
|
45
|
+
const FRAMES_LOOK_LEFT = [
|
|
46
|
+
`
|
|
47
|
+
╔═══════════════════════════════════════════════════════╗
|
|
48
|
+
║ ║
|
|
49
|
+
║ ██╗ ██╗ ██████╗ ██████╗ ██████╗ ███████╗ ║
|
|
50
|
+
║ ██║ ██║ ██╔════╝██╔═══██╗██╔══██╗██╔════╝ ║
|
|
51
|
+
║ ██║ ██║ ██║ ██║ ██║██║ ██║█████╗ ║
|
|
52
|
+
║ ╚██╗ ██╔╝ ██║ ██║ ██║██║ ██║██╔══╝ ║
|
|
53
|
+
║ ╚████╔╝ ╚██████╗╚██████╔╝██████╔╝███████╗ ║
|
|
54
|
+
║ ╚═══╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ ║
|
|
55
|
+
║ ║
|
|
56
|
+
║ ┌──────────────────────┐ ║
|
|
57
|
+
║ │ ◉ ◉ │ ║
|
|
58
|
+
║ │ ─── │ ║
|
|
59
|
+
║ └──────────────────────┘ ║
|
|
60
|
+
║ ║
|
|
61
|
+
╚═══════════════════════════════════════════════════════╝`,
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
const FRAMES_LOOK_RIGHT = [
|
|
65
|
+
`
|
|
66
|
+
╔═══════════════════════════════════════════════════════╗
|
|
67
|
+
║ ║
|
|
68
|
+
║ ██╗ ██╗ ██████╗ ██████╗ ██████╗ ███████╗ ║
|
|
69
|
+
║ ██║ ██║ ██╔════╝██╔═══██╗██╔══██╗██╔════╝ ║
|
|
70
|
+
║ ██║ ██║ ██║ ██║ ██║██║ ██║█████╗ ║
|
|
71
|
+
║ ╚██╗ ██╔╝ ██║ ██║ ██║██║ ██║██╔══╝ ║
|
|
72
|
+
║ ╚████╔╝ ╚██████╗╚██████╔╝██████╔╝███████╗ ║
|
|
73
|
+
║ ╚═══╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ ║
|
|
74
|
+
║ ║
|
|
75
|
+
║ ┌──────────────────────┐ ║
|
|
76
|
+
║ │ ◉ ◉ │ ║
|
|
77
|
+
║ │ ─── │ ║
|
|
78
|
+
║ └──────────────────────┘ ║
|
|
79
|
+
║ ║
|
|
80
|
+
╚═══════════════════════════════════════════════════════╝`,
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
const allFrames = [FRAMES_OPEN, FRAMES_BLINK, FRAMES_LOOK_LEFT, FRAMES_LOOK_RIGHT];
|
|
84
|
+
|
|
85
|
+
function colorize(frame) {
|
|
86
|
+
return frame
|
|
87
|
+
.replace(/[██╗╔╚╝║═╗╝╬╦╩╠╣█▀▄]/g, (m) => chalk.hex('#a855f7')(m))
|
|
88
|
+
.replace(/[◉]/g, () => chalk.hex('#22d3ee')('◉'))
|
|
89
|
+
.replace(/[━]/g, () => chalk.hex('#64748b')('━'))
|
|
90
|
+
.replace(/[┌┐└┘│─┘┐]/g, (m) => chalk.hex('#334155')(m))
|
|
91
|
+
.replace(/───/g, chalk.hex('#f97316')('───'));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Show static logo.
|
|
96
|
+
*/
|
|
97
|
+
export async function showLogo(animate = false) {
|
|
98
|
+
console.clear();
|
|
99
|
+
const frame = colorize(FRAMES_OPEN[0]);
|
|
100
|
+
console.log(frame);
|
|
101
|
+
console.log(chalk.gray(' ─────────────────────────────────────────────────────'));
|
|
102
|
+
console.log(chalk.hex('#a855f7').bold(' Vynthen V Code — Local Bridge'));
|
|
103
|
+
console.log(chalk.gray(' ─────────────────────────────────────────────────────\n'));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Animate logo with blinking eyes. Returns a stop function.
|
|
108
|
+
*/
|
|
109
|
+
export function startLogoAnimation() {
|
|
110
|
+
let running = true;
|
|
111
|
+
const sequence = [
|
|
112
|
+
{ frames: FRAMES_OPEN, duration: 2500 },
|
|
113
|
+
{ frames: FRAMES_BLINK, duration: 150 },
|
|
114
|
+
{ frames: FRAMES_OPEN, duration: 300 },
|
|
115
|
+
{ frames: FRAMES_BLINK, duration: 150 },
|
|
116
|
+
{ frames: FRAMES_OPEN, duration: 3000 },
|
|
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);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
console.clear();
|
|
136
|
+
render();
|
|
137
|
+
// Print static footer below the logo area
|
|
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
|
+
|
|
143
|
+
return () => { running = false; };
|
|
144
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V Code — Browser & File Manager Operations.
|
|
3
|
+
* Open URLs in the default browser & folders in the file manager.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { requestPermission } from '../permissions.js';
|
|
7
|
+
|
|
8
|
+
let openModule;
|
|
9
|
+
try {
|
|
10
|
+
openModule = (await import('open')).default;
|
|
11
|
+
} catch {
|
|
12
|
+
// Not available
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Open a URL in the default browser.
|
|
17
|
+
*/
|
|
18
|
+
export async function openUrlOp(url) {
|
|
19
|
+
const allowed = await requestPermission('OPEN_URL', url);
|
|
20
|
+
if (!allowed) return { success: false, error: 'Permission denied' };
|
|
21
|
+
|
|
22
|
+
if (!openModule) {
|
|
23
|
+
return { success: false, error: 'open module not available' };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
await openModule(url);
|
|
28
|
+
return { success: true, url };
|
|
29
|
+
} catch (err) {
|
|
30
|
+
return { success: false, error: err.message };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Open a folder in the system file manager.
|
|
36
|
+
*/
|
|
37
|
+
export async function openFolderOp(folderPath) {
|
|
38
|
+
const allowed = await requestPermission('OPEN_FOLDER', folderPath);
|
|
39
|
+
if (!allowed) return { success: false, error: 'Permission denied' };
|
|
40
|
+
|
|
41
|
+
if (!openModule) {
|
|
42
|
+
return { success: false, error: 'open module not available' };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
await openModule(folderPath);
|
|
47
|
+
return { success: true, path: folderPath };
|
|
48
|
+
} catch (err) {
|
|
49
|
+
return { success: false, error: err.message };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V Code — Clipboard Operations.
|
|
3
|
+
* Read and write system clipboard.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { requestPermission } from '../permissions.js';
|
|
7
|
+
|
|
8
|
+
let clipboardy;
|
|
9
|
+
try {
|
|
10
|
+
clipboardy = await import('clipboardy');
|
|
11
|
+
} catch {
|
|
12
|
+
// Not available
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Read clipboard contents.
|
|
17
|
+
*/
|
|
18
|
+
export async function clipboardReadOp() {
|
|
19
|
+
const allowed = await requestPermission('CLIPBOARD_READ', 'Read clipboard contents');
|
|
20
|
+
if (!allowed) return { success: false, error: 'Permission denied' };
|
|
21
|
+
|
|
22
|
+
if (!clipboardy) {
|
|
23
|
+
return { success: false, error: 'clipboardy not available' };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const text = await clipboardy.default.read();
|
|
28
|
+
return { success: true, content: text };
|
|
29
|
+
} catch (err) {
|
|
30
|
+
return { success: false, error: err.message };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Write to clipboard.
|
|
36
|
+
*/
|
|
37
|
+
export async function clipboardWriteOp(text) {
|
|
38
|
+
const preview = text.length > 100 ? text.substring(0, 100) + '...' : text;
|
|
39
|
+
const allowed = await requestPermission('CLIPBOARD_WRITE', `Write to clipboard: "${preview}"`);
|
|
40
|
+
if (!allowed) return { success: false, error: 'Permission denied' };
|
|
41
|
+
|
|
42
|
+
if (!clipboardy) {
|
|
43
|
+
return { success: false, error: 'clipboardy not available' };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
await clipboardy.default.write(text);
|
|
48
|
+
return { success: true };
|
|
49
|
+
} catch (err) {
|
|
50
|
+
return { success: false, error: err.message };
|
|
51
|
+
}
|
|
52
|
+
}
|