vcode-cli 1.0.4 → 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 -137
- 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/LICENSE
CHANGED
|
@@ -1,21 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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 —
|
|
5
|
-
*
|
|
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('
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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);
|
package/lib/agent/ai.js
ADDED
|
@@ -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
|
+
}
|