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 CHANGED
@@ -1,21 +1,24 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Vynthen
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ This software is proprietary and owned exclusively by Tibebe Solomon and Vynthen
2
+
3
+ NO PART OF THIS SOFTWARE may be copied, modified, distributed, sublicensed, sold, or used in any commercial or non-commercial product without explicit written permission from Tibebe Solomon.
4
+
5
+ REVERSE ENGINEERING PROHIBITED:
6
+ Reverse engineering, decompilation, and disassembly are strictly prohibited. You agree not to reverse engineer, decompile, or disassemble any part of this software.
7
+
8
+ UNAUTHORIZED USE:
9
+ Any unauthorized use will result in immediate legal action under international copyright law and the Digital Millennium Copyright Act (DMCA).
10
+
11
+ PERSONAL USE ONLY:
12
+ This software is provided for personal use only by licensed end users. Commercial use requires a separate written agreement with Tibebe Solomon.
13
+
14
+ PENALTIES:
15
+ Violators are subject to civil and criminal penalties including damages up to $150,000 per violation under 17 U.S.C. § 504 (Statutory Damages for Copyright Infringement).
16
+
17
+ ALL RIGHTS RESERVED WORLDWIDE:
18
+ © 2026 Tibebe Solomon / Vynthen. All rights reserved.
19
+
20
+ DISCLAIMER:
21
+ THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. THE AUTHOR DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
22
+
23
+ LIMITATION OF LIABILITY:
24
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/bin/vcode.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * V Code CLI — Vynthen Local Bridge
5
- * Entry point for all CLI commands.
4
+ * V Code CLI 2.0 Terminal AI Coding Agent
5
+ * Powered by Vynthen
6
6
  */
7
7
 
8
8
  import { Command } from 'commander';
@@ -18,48 +18,20 @@ const program = new Command();
18
18
 
19
19
  program
20
20
  .name('vcode')
21
- .description('Connect your local machine to Vynthen V Code')
21
+ .description('V Code Terminal AI Coding Agent powered by Vynthen')
22
22
  .version(pkg.version, '-v, --version');
23
23
 
24
24
  program
25
25
  .command('login')
26
- .description('Authenticate with your Vynthen account')
26
+ .description('Authenticate with Vynthen email and password')
27
27
  .action(async () => {
28
28
  const { login } = await import('../lib/commands/login.js');
29
29
  await login();
30
30
  });
31
31
 
32
- program
33
- .command('start')
34
- .description('Start the V Code bridge and connect to Vynthen')
35
- .option('--approve-all', 'Auto-approve all operations for this session')
36
- .option('--server', 'Auto-start the WebSocket server (default: true)', true)
37
- .option('--local', 'Use local WebSocket server (default, use --no-local for remote)', true)
38
- .action(async (opts) => {
39
- if (!opts.server) {
40
- process.env.VCODE_WS_URL = 'ws://localhost:3001';
41
- }
42
- const { start } = await import('../lib/commands/start.js');
43
- await start(opts);
44
- });
45
-
46
- program
47
- .command('serve')
48
- .description('Start only the WebSocket server (no Vynthen connection)')
49
- .option('--port <number>', 'Port to listen on', '3001')
50
- .action(async (opts) => {
51
- const { spawn } = await import('child_process');
52
- const serverPath = new URL('../lib/server.js', import.meta.url);
53
- const proc = spawn('node', [serverPath.pathname], {
54
- stdio: 'inherit',
55
- env: { ...process.env, VCODE_WS_PORT: opts.port }
56
- });
57
- proc.on('close', (code) => process.exit(code || 0));
58
- });
59
-
60
32
  program
61
33
  .command('logout')
62
- .description('Clear stored authentication token')
34
+ .description('Clear stored credentials')
63
35
  .action(async () => {
64
36
  const { logout } = await import('../lib/commands/logout.js');
65
37
  await logout();
@@ -67,17 +39,67 @@ program
67
39
 
68
40
  program
69
41
  .command('status')
70
- .description('Show connection status and logged-in user')
42
+ .description('Show user info and plan tier')
71
43
  .action(async () => {
72
44
  const { status } = await import('../lib/commands/status.js');
73
45
  await status();
74
46
  });
75
47
 
76
- program.parse(process.argv);
48
+ program
49
+ .command('history')
50
+ .description('Show session history')
51
+ .action(async () => {
52
+ const { history } = await import('../lib/commands/history.js');
53
+ await history();
54
+ });
55
+
56
+ program
57
+ .command('undo')
58
+ .description('Undo last file operation')
59
+ .action(async () => {
60
+ const { undo } = await import('../lib/commands/undo.js');
61
+ await undo();
62
+ });
77
63
 
78
- // Show help if no command provided
79
- if (!process.argv.slice(2).length) {
80
- const { showLogo } = await import('../lib/logo.js');
81
- await showLogo(false);
82
- program.outputHelp();
83
- }
64
+ program
65
+ .command('clear')
66
+ .description('Clear conversation context')
67
+ .action(async () => {
68
+ const { clearContext } = await import('../lib/commands/clear.js');
69
+ await clearContext();
70
+ });
71
+
72
+ program
73
+ .command('config')
74
+ .description('Configure V Code settings')
75
+ .option('--no-companion', 'Disable Byte companion')
76
+ .option('--companion', 'Enable Byte companion')
77
+ .action(async (opts) => {
78
+ const { config } = await import('../lib/commands/config.js');
79
+ await config(opts);
80
+ });
81
+
82
+ program
83
+ .command('add')
84
+ .description('Add file to context')
85
+ .argument('<file>', 'File path to add')
86
+ .action(async (file) => {
87
+ const { addFile } = await import('../lib/commands/add.js');
88
+ await addFile(file);
89
+ });
90
+
91
+ program
92
+ .command('help')
93
+ .description('Show all commands')
94
+ .action(async () => {
95
+ const { showHelp } = await import('../lib/commands/help.js');
96
+ await showHelp();
97
+ });
98
+
99
+ // Default: start interactive mode
100
+ program.action(async () => {
101
+ const { start } = await import('../lib/commands/start.js');
102
+ await start({});
103
+ });
104
+
105
+ program.parse(process.argv);
@@ -0,0 +1,131 @@
1
+ import { getToken } from '../auth/index.js';
2
+ import chalk from 'chalk';
3
+
4
+ const API_BASE = 'https://vynthen.com/api/vcode-proxy';
5
+ const DEFAULT_MODEL = 'qwen/qwen3.6-plus:free';
6
+
7
+ export interface AIRequest {
8
+ message: string;
9
+ context?: {
10
+ files?: string[];
11
+ projectTree?: string;
12
+ workingDir?: string;
13
+ };
14
+ history?: Array<{ role: 'user' | 'assistant'; content: string }>;
15
+ }
16
+
17
+ export interface AIResponse {
18
+ content: string;
19
+ reasoning?: string;
20
+ toolCalls?: Array<{
21
+ name: string;
22
+ params: Record<string, unknown>;
23
+ }>;
24
+ }
25
+
26
+ export class VCodeAI {
27
+ private abortController: AbortController | null = null;
28
+
29
+ async chat(request: AIRequest, onChunk?: (chunk: string) => void): Promise<AIResponse> {
30
+ const token = await getToken();
31
+
32
+ if (!token) {
33
+ throw new Error('Not authenticated. Run vcode login first.');
34
+ }
35
+
36
+ this.abortController = new AbortController();
37
+
38
+ try {
39
+ const response = await fetch(API_BASE, {
40
+ method: 'POST',
41
+ headers: {
42
+ 'Content-Type': 'application/json',
43
+ 'Authorization': `Bearer ${token}`
44
+ },
45
+ body: JSON.stringify({
46
+ message: request.message,
47
+ context: request.context,
48
+ history: request.history,
49
+ model: DEFAULT_MODEL
50
+ }),
51
+ signal: this.abortController.signal
52
+ });
53
+
54
+ if (!response.ok) {
55
+ if (response.status === 401) {
56
+ throw new Error('Authentication failed. Please run vcode login again.');
57
+ }
58
+ if (response.status === 429) {
59
+ throw new Error('Rate limited. Please wait 15 minutes and try again.');
60
+ }
61
+ throw new Error(`API error: ${response.statusText}`);
62
+ }
63
+
64
+ // Handle streaming response
65
+ if (response.body) {
66
+ const reader = response.body.getReader();
67
+ const decoder = new TextDecoder();
68
+ let content = '';
69
+ let reasoning = '';
70
+
71
+ while (true) {
72
+ const { done, value } = await reader.read();
73
+ if (done) break;
74
+
75
+ const chunk = decoder.decode(value);
76
+ const lines = chunk.split('\n');
77
+
78
+ for (const line of lines) {
79
+ if (line.startsWith('data: ')) {
80
+ const data = line.slice(6);
81
+
82
+ if (data === '[DONE]') continue;
83
+
84
+ try {
85
+ const parsed = JSON.parse(data);
86
+
87
+ if (parsed.type === 'thinking') {
88
+ reasoning += parsed.content;
89
+ if (onChunk) onChunk(chalk.italic(chalk.gray(parsed.content)));
90
+ } else if (parsed.type === 'content') {
91
+ content += parsed.content;
92
+ if (onChunk) onChunk(parsed.content);
93
+ } else if (parsed.type === 'tool_call') {
94
+ // Handle tool call
95
+ }
96
+ } catch {
97
+ // Not JSON, treat as content
98
+ content += data;
99
+ if (onChunk) onChunk(data);
100
+ }
101
+ }
102
+ }
103
+ }
104
+
105
+ return { content, reasoning };
106
+ }
107
+
108
+ // Non-streaming fallback
109
+ const data = await response.json();
110
+ return {
111
+ content: data.content || '',
112
+ reasoning: data.reasoning,
113
+ toolCalls: data.toolCalls
114
+ };
115
+
116
+ } catch (error) {
117
+ if (error instanceof Error && error.name === 'AbortError') {
118
+ throw new Error('Request cancelled');
119
+ }
120
+ throw error;
121
+ }
122
+ }
123
+
124
+ abort(): void {
125
+ if (this.abortController) {
126
+ this.abortController.abort();
127
+ }
128
+ }
129
+ }
130
+
131
+ export const ai = new VCodeAI();
@@ -0,0 +1,111 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { getFileTree, formatFileTree, readFile } from './filesystem.js';
4
+
5
+ export interface ContextFile {
6
+ path: string;
7
+ content?: string;
8
+ summary?: string;
9
+ }
10
+
11
+ export interface ConversationMessage {
12
+ role: 'user' | 'assistant';
13
+ content: string;
14
+ timestamp: number;
15
+ }
16
+
17
+ class ContextManager {
18
+ private conversation: ConversationMessage[] = [];
19
+ private contextFiles: ContextFile[] = [];
20
+ private projectTree: string = '';
21
+ private workingDir: string = '';
22
+
23
+ async initContext(workingDirectory: string): Promise<void> {
24
+ this.workingDir = workingDirectory;
25
+ this.conversation = [];
26
+ this.contextFiles = [];
27
+
28
+ console.log(' Building project context...');
29
+
30
+ // Build file tree
31
+ const tree = await getFileTree(workingDirectory, 4);
32
+ this.projectTree = formatFileTree(tree);
33
+
34
+ // Auto-add key files
35
+ const keyFiles = ['package.json', 'README.md', 'tsconfig.json', 'jsconfig.json', '.env.example'];
36
+
37
+ for (const file of keyFiles) {
38
+ const filePath = path.join(workingDirectory, file);
39
+ if (fs.existsSync(filePath)) {
40
+ await this.addFile(filePath);
41
+ }
42
+ }
43
+ }
44
+
45
+ async addFile(filePath: string): Promise<void> {
46
+ try {
47
+ const content = await readFile(filePath);
48
+ this.contextFiles.push({
49
+ path: filePath,
50
+ content
51
+ });
52
+ } catch (error) {
53
+ console.log(` Could not read ${filePath}: ${error}`);
54
+ }
55
+ }
56
+
57
+ removeFile(filePath: string): void {
58
+ this.contextFiles = this.contextFiles.filter(f => f.path !== filePath);
59
+ }
60
+
61
+ clearFiles(): void {
62
+ this.contextFiles = [];
63
+ }
64
+
65
+ addMessage(role: 'user' | 'assistant', content: string): void {
66
+ this.conversation.push({
67
+ role,
68
+ content,
69
+ timestamp: Date.now()
70
+ });
71
+
72
+ // Keep last 50 messages
73
+ if (this.conversation.length > 50) {
74
+ this.conversation.shift();
75
+ }
76
+ }
77
+
78
+ clearConversation(): void {
79
+ this.conversation = [];
80
+ }
81
+
82
+ getConversation(): ConversationMessage[] {
83
+ return [...this.conversation];
84
+ }
85
+
86
+ getContextFiles(): ContextFile[] {
87
+ return [...this.contextFiles];
88
+ }
89
+
90
+ getProjectTree(): string {
91
+ return this.projectTree;
92
+ }
93
+
94
+ getWorkingDir(): string {
95
+ return this.workingDir;
96
+ }
97
+
98
+ getContextForAI(): {
99
+ files: Array<{ path: string; content: string }>;
100
+ projectTree: string;
101
+ history: ConversationMessage[];
102
+ } {
103
+ return {
104
+ files: this.contextFiles.map(f => ({ path: f.path, content: f.content || '' })),
105
+ projectTree: this.projectTree,
106
+ history: this.conversation.slice(-10)
107
+ };
108
+ }
109
+ }
110
+
111
+ export const contextManager = new ContextManager();
@@ -0,0 +1,237 @@
1
+ import readline from 'readline';
2
+ import chalk from 'chalk';
3
+ import { getUser, getToken } from '../auth/index.js';
4
+ import { ai } from './ai.js';
5
+ import { contextManager } from './context.js';
6
+ import { readFile, writeFile, deleteFile, createDirectory, renameFile, listDirectory, searchFiles, generateDiff } from '../tools/filesystem.js';
7
+ import { runCommand, gitStatus, gitDiff, gitCommit, gitPush, gitPull } from '../tools/shell.js';
8
+ import { askPermission, showDiffAndConfirm, showCommandAndConfirm, confirmDelete, setSessionApproveAll, resetSessionPermissions } from '../tools/permissions.js';
9
+ import { showByteThinking, showByteWorking, showByteSuccess, showByteError, showByteWaiting, showByteCelebrating, setCompanionEnabled } from '../ui/byte.js';
10
+ import * as fs from 'fs';
11
+ import * as path from 'path';
12
+
13
+ export interface AgentOptions {
14
+ approveAll?: boolean;
15
+ }
16
+
17
+ const TOOL_DESCRIPTIONS: Record<string, string> = {
18
+ read_file: 'Read a file',
19
+ write_file: 'Write to a file',
20
+ delete_file: 'Delete a file',
21
+ create_dir: 'Create a directory',
22
+ rename_file: 'Rename a file',
23
+ list_dir: 'List directory contents',
24
+ run_command: 'Run a terminal command',
25
+ git_status: 'Check git status',
26
+ git_diff: 'Show git diff',
27
+ git_commit: 'Git commit changes',
28
+ git_push: 'Push to remote',
29
+ git_pull: 'Pull from remote',
30
+ search: 'Search files for pattern'
31
+ };
32
+
33
+ export async function startAgent(opts: AgentOptions = {}): Promise<void> {
34
+ if (opts.approveAll) {
35
+ setSessionApproveAll(true);
36
+ console.log(chalk.yellow(' ⚠ Auto-approve mode enabled\n'));
37
+ }
38
+
39
+ const user = await getUser();
40
+ if (!user) {
41
+ console.log(chalk.red(' ✗ Not authenticated. Run vcode login first.\n'));
42
+ process.exit(1);
43
+ }
44
+
45
+ // Load companion preference
46
+ try {
47
+ const configPath = path.join(process.env.HOME || '', '.vcode', 'config.json');
48
+ if (fs.existsSync(configPath)) {
49
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
50
+ if (config.companion === false) {
51
+ setCompanionEnabled(false);
52
+ }
53
+ }
54
+ } catch {}
55
+
56
+ const workingDir = process.cwd();
57
+ console.log(chalk.gray(` Project: ${workingDir}`));
58
+ console.log(chalk.gray(` User: ${user.email}`));
59
+ console.log(chalk.gray(` Plan: ${user.plan}\n`));
60
+
61
+ // Initialize context
62
+ await contextManager.initContext(workingDir);
63
+ console.log(chalk.green(' ✓ Context initialized\n'));
64
+
65
+ // Show interactive prompt
66
+ const rl = readline.createInterface({
67
+ input: process.stdin,
68
+ output: process.stdout
69
+ });
70
+
71
+ const prompt = () => {
72
+ rl.question(chalk.hex('#a855f7')('\nvcode > '), async (input) => {
73
+ const command = input.trim();
74
+
75
+ if (!command) {
76
+ prompt();
77
+ return;
78
+ }
79
+
80
+ if (command === 'exit' || command === 'quit') {
81
+ console.log(chalk.gray('\n Goodbye! 👋\n'));
82
+ rl.close();
83
+ return;
84
+ }
85
+
86
+ if (command === 'clear') {
87
+ console.clear();
88
+ prompt();
89
+ return;
90
+ }
91
+
92
+ if (command === 'undo') {
93
+ // Undo implementation
94
+ console.log(chalk.yellow(' Feature coming soon\n'));
95
+ prompt();
96
+ return;
97
+ }
98
+
99
+ // Process through AI
100
+ await processUserMessage(command);
101
+ prompt();
102
+ });
103
+ };
104
+
105
+ prompt();
106
+ }
107
+
108
+ async function processUserMessage(message: string): Promise<void> {
109
+ showByteThinking();
110
+
111
+ try {
112
+ contextManager.addMessage('user', message);
113
+
114
+ const context = contextManager.getContextForAI();
115
+
116
+ const response = await ai.chat({
117
+ message,
118
+ context: {
119
+ files: context.files,
120
+ projectTree: context.projectTree,
121
+ workingDir: contextManager.getWorkingDir()
122
+ },
123
+ history: context.history
124
+ }, (chunk) => {
125
+ process.stdout.write(chunk);
126
+ });
127
+
128
+ console.log('\n');
129
+ showByteWorking();
130
+
131
+ // Process tool calls from response
132
+ if (response.toolCalls) {
133
+ for (const toolCall of response.toolCalls) {
134
+ await executeToolCall(toolCall.name, toolCall.params);
135
+ }
136
+ }
137
+
138
+ contextManager.addMessage('assistant', response.content);
139
+ showByteSuccess();
140
+
141
+ } catch (error) {
142
+ showByteError();
143
+ console.log(chalk.red(` ✗ ${error}\n`));
144
+ }
145
+ }
146
+
147
+ async function executeToolCall(toolName: string, params: Record<string, unknown>): Promise<void> {
148
+ const p = params as Record<string, string>;
149
+
150
+ switch (toolName) {
151
+ case 'read_file': {
152
+ const content = await readFile(p.path);
153
+ console.log(chalk.bold.cyan(`\n 📄 ${p.path}\n`));
154
+ console.log(content);
155
+ break;
156
+ }
157
+
158
+ case 'write_file': {
159
+ const diff = generateDiff(p.path, '', p.content);
160
+ const decision = await showDiffAndConfirm(p.path, diff);
161
+
162
+ if (decision === 'approve' || decision === 'approve_all') {
163
+ const result = await writeFile(p.path, p.content, false);
164
+ if (result.success) {
165
+ console.log(chalk.green(` ✓ Wrote ${p.path}`));
166
+ }
167
+ } else {
168
+ console.log(chalk.gray(' Skipped'));
169
+ }
170
+ break;
171
+ }
172
+
173
+ case 'delete_file': {
174
+ const confirm = await confirmDelete(p.path);
175
+ if (confirm) {
176
+ await deleteFile(p.path);
177
+ console.log(chalk.green(` ✓ Deleted ${p.path}`));
178
+ }
179
+ break;
180
+ }
181
+
182
+ case 'create_dir': {
183
+ await createDirectory(p.path);
184
+ console.log(chalk.green(` ✓ Created directory ${p.path}`));
185
+ break;
186
+ }
187
+
188
+ case 'rename_file': {
189
+ const decision = await askPermission(`Rename ${p.oldPath} to ${p.newPath}?`, 'write');
190
+ if (decision === 'approve' || decision === 'approve_all') {
191
+ await renameFile(p.oldPath, p.newPath);
192
+ console.log(chalk.green(` ✓ Renamed ${p.oldPath} to ${p.newPath}`));
193
+ }
194
+ break;
195
+ }
196
+
197
+ case 'list_dir': {
198
+ const items = await listDirectory(p.path);
199
+ console.log(chalk.bold.cyan(`\n 📁 ${p.path}\n`));
200
+ for (const item of items) {
201
+ const icon = item.isDirectory ? '📁' : '📄';
202
+ console.log(` ${icon} ${item.name}`);
203
+ }
204
+ break;
205
+ }
206
+
207
+ case 'run_command': {
208
+ const decision = await showCommandAndConfirm(p.command);
209
+ if (decision === 'approve' || decision === 'approve_all') {
210
+ showByteWaiting();
211
+ const result = await runCommand(p.command);
212
+ if (result.stdout) console.log(result.stdout);
213
+ if (result.stderr) console.log(chalk.red(result.stderr));
214
+ if (result.success) {
215
+ console.log(chalk.green(` ✓ Command completed`));
216
+ }
217
+ }
218
+ break;
219
+ }
220
+
221
+ case 'search': {
222
+ const results = await searchFiles(p.path, p.pattern, p.isRegex);
223
+ console.log(chalk.bold.cyan(`\n 🔍 Results for "${p.pattern}" in ${p.path}\n`));
224
+ for (const r of results.slice(0, 20)) {
225
+ console.log(chalk.gray(` ${r.file}:${r.line}`));
226
+ console.log(chalk.white(` ${r.content}\n`));
227
+ }
228
+ if (results.length > 20) {
229
+ console.log(chalk.gray(` ... and ${results.length - 20} more\n`));
230
+ }
231
+ break;
232
+ }
233
+
234
+ default:
235
+ console.log(chalk.yellow(` Unknown tool: ${toolName}`));
236
+ }
237
+ }