voracode 0.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/LICENSE +21 -0
- package/README.md +146 -0
- package/dist/main.js +2842 -0
- package/package.json +57 -0
- package/src/cli/audit.ts +34 -0
- package/src/cli/config.ts +54 -0
- package/src/cli/doctor.ts +61 -0
- package/src/cli/init.ts +62 -0
- package/src/cli/key.ts +67 -0
- package/src/cli/lite.ts +65 -0
- package/src/cli/mcp.ts +221 -0
- package/src/cli/model.ts +103 -0
- package/src/cli/plugin.ts +49 -0
- package/src/cli/pro.ts +97 -0
- package/src/cli/run.ts +101 -0
- package/src/cli/session.ts +138 -0
- package/src/cli/skill.ts +156 -0
- package/src/cli/stats.ts +16 -0
- package/src/cli/update.ts +28 -0
- package/src/engine/agent.ts +256 -0
- package/src/engine/sub-agent.ts +122 -0
- package/src/main.ts +77 -0
- package/src/models/adapters/all-providers.ts +302 -0
- package/src/models/router.ts +426 -0
- package/src/security/owasp.ts +254 -0
- package/src/session/manager.ts +118 -0
- package/src/skills/self-improve/engine.ts +336 -0
- package/src/storage/config.ts +213 -0
- package/src/storage/database.ts +253 -0
- package/src/storage/mcp-config.ts +170 -0
- package/src/tools/executor.ts +362 -0
- package/src/ui/theme.ts +163 -0
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VORACODE Tool Executor — Execute tool calls from the AI agent
|
|
3
|
+
*
|
|
4
|
+
* Each tool is a function that takes JSON arguments and returns a result.
|
|
5
|
+
* Tools are sandboxed, logged, and audited.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { VoraDatabase } from "../storage/database";
|
|
9
|
+
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
10
|
+
import { execSync } from "child_process";
|
|
11
|
+
import { resolve } from "path";
|
|
12
|
+
import type { ToolDefinition as RouterToolDef } from "../models/router";
|
|
13
|
+
|
|
14
|
+
export interface ToolDefinition {
|
|
15
|
+
name: string;
|
|
16
|
+
description: string;
|
|
17
|
+
execute: (args: Record<string, unknown>) => Promise<unknown> | unknown;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Blocked command patterns — case-insensitive
|
|
21
|
+
const BLOCKED_COMMANDS = [
|
|
22
|
+
"rm -rf /", "rm -rf /*", "sudo ", "mkfs", "dd if=",
|
|
23
|
+
":(){", "chmod 777 /", "> /dev/", "format C:",
|
|
24
|
+
"del /f /s", "rd /s /q", "shutdown", "reboot",
|
|
25
|
+
"curl ", "wget ", // data exfiltration via download
|
|
26
|
+
"nc ", "ncat ", "netcat ", // network connections
|
|
27
|
+
"bash -c", "sh -c", "powershell -c", // encoded cmd execution
|
|
28
|
+
"chmod +x", // making files executable
|
|
29
|
+
">/dev/sda", ">/dev/sdb", // writing to raw disk
|
|
30
|
+
"| bash", "| sh", "| zsh", // pipe to shell
|
|
31
|
+
"`", "$(", // command substitution
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
// Project root for path validation
|
|
35
|
+
const PROJECT_ROOT = resolve(process.cwd());
|
|
36
|
+
|
|
37
|
+
export class ToolExecutor {
|
|
38
|
+
private db: VoraDatabase;
|
|
39
|
+
private tools: Map<string, ToolDefinition>;
|
|
40
|
+
|
|
41
|
+
constructor(db: VoraDatabase) {
|
|
42
|
+
this.db = db;
|
|
43
|
+
this.tools = new Map();
|
|
44
|
+
this.registerBuiltins();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private registerBuiltins(): void {
|
|
48
|
+
this.tools.set("file_read", {
|
|
49
|
+
name: "file_read",
|
|
50
|
+
description: "Read the contents of a file",
|
|
51
|
+
execute: (args) => this.fileRead(args.path as string),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
this.tools.set("file_write", {
|
|
55
|
+
name: "file_write",
|
|
56
|
+
description: "Write content to a file",
|
|
57
|
+
execute: (args) => this.fileWrite(args.path as string, args.content as string),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
this.tools.set("file_edit", {
|
|
61
|
+
name: "file_edit",
|
|
62
|
+
description: "Edit a file by replacing text",
|
|
63
|
+
execute: (args) => this.fileEdit(args.path as string, args.old_string as string, args.new_string as string),
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
this.tools.set("bash", {
|
|
67
|
+
name: "bash",
|
|
68
|
+
description: "Execute a shell command (sandboxed)",
|
|
69
|
+
execute: (args) => this.execBash(args.command as string),
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
this.tools.set("git_status", {
|
|
73
|
+
name: "git_status",
|
|
74
|
+
description: "Show git status",
|
|
75
|
+
execute: () => this.execGit(["status"]),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
this.tools.set("git_diff", {
|
|
79
|
+
name: "git_diff",
|
|
80
|
+
description: "Show git diff",
|
|
81
|
+
execute: () => this.execGit(["diff"]),
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
this.tools.set("git_commit", {
|
|
85
|
+
name: "git_commit",
|
|
86
|
+
description: "Commit changes with a message",
|
|
87
|
+
execute: (args) => this.execGit(["commit", "-m", args.message as string]),
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
this.tools.set("code_search", {
|
|
91
|
+
name: "code_search",
|
|
92
|
+
description: "Search codebase for a pattern",
|
|
93
|
+
execute: (args) => this.searchCode(args.pattern as string, args.path as string),
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
this.tools.set("web_fetch", {
|
|
97
|
+
name: "web_fetch",
|
|
98
|
+
description: "Fetch content from a URL",
|
|
99
|
+
execute: (args) => this.webFetch(args.url as string),
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
this.tools.set("think", {
|
|
103
|
+
name: "think",
|
|
104
|
+
description: "Reason through a problem step by step",
|
|
105
|
+
execute: (args) => `Reasoning: ${args.reasoning || "No reasoning provided."}`,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
this.tools.set("list_files", {
|
|
109
|
+
name: "list_files",
|
|
110
|
+
description: "List files in a directory",
|
|
111
|
+
execute: (args) => this.listDir(args.path as string),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Execute a tool by name with given arguments
|
|
117
|
+
*/
|
|
118
|
+
async execute(toolName: string, argsJson: string): Promise<unknown> {
|
|
119
|
+
const tool = this.tools.get(toolName);
|
|
120
|
+
if (!tool) {
|
|
121
|
+
return `Error: Unknown tool '${toolName}'. Available: ${Array.from(this.tools.keys()).join(", ")}`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let args: Record<string, unknown> = {};
|
|
125
|
+
try {
|
|
126
|
+
args = JSON.parse(argsJson);
|
|
127
|
+
} catch {
|
|
128
|
+
return `Error: Invalid JSON arguments for tool '${toolName}'`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Audit log
|
|
132
|
+
this.db.logAudit("tool_execute", JSON.stringify({ tool: toolName, args }));
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
const result = await Promise.resolve(tool.execute(args));
|
|
136
|
+
return result;
|
|
137
|
+
} catch (error) {
|
|
138
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
139
|
+
this.db.logAudit("tool_error", `${toolName}: ${errorMsg}`, false);
|
|
140
|
+
return `Error executing ${toolName}: ${errorMsg}`;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* List all registered tools
|
|
146
|
+
*/
|
|
147
|
+
listTools(): ToolDefinition[] {
|
|
148
|
+
return Array.from(this.tools.values());
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Register a custom tool (for plugins)
|
|
153
|
+
*/
|
|
154
|
+
registerTool(tool: ToolDefinition): void {
|
|
155
|
+
this.tools.set(tool.name, tool);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Get tool definitions in OpenAI function-calling format
|
|
160
|
+
*/
|
|
161
|
+
getToolSchemas(): RouterToolDef[] {
|
|
162
|
+
const schemas: RouterToolDef[] = [];
|
|
163
|
+
const paramSchemas: Record<string, { path: { type: string; description: string }; content: { type: string; description: string }; old_string: { type: string; description: string }; new_string: { type: string; description: string }; command: { type: string; description: string }; message: { type: string; description: string }; pattern: { type: string; description: string }; url: { type: string; description: string }; reasoning: { type: string; description: string } }> = {
|
|
164
|
+
file_read: { path: { type: "string", description: "File path" }, content: { type: "string", description: "" }, old_string: { type: "string", description: "" }, new_string: { type: "string", description: "" }, command: { type: "string", description: "" }, message: { type: "string", description: "" }, pattern: { type: "string", description: "" }, url: { type: "string", description: "" }, reasoning: { type: "string", description: "" } },
|
|
165
|
+
file_write: { path: { type: "string", description: "File path" }, content: { type: "string", description: "Content to write" }, old_string: { type: "string", description: "" }, new_string: { type: "string", description: "" }, command: { type: "string", description: "" }, message: { type: "string", description: "" }, pattern: { type: "string", description: "" }, url: { type: "string", description: "" }, reasoning: { type: "string", description: "" } },
|
|
166
|
+
file_edit: { path: { type: "string", description: "File path" }, content: { type: "string", description: "" }, old_string: { type: "string", description: "Text to replace" }, new_string: { type: "string", description: "Replacement text" }, command: { type: "string", description: "" }, message: { type: "string", description: "" }, pattern: { type: "string", description: "" }, url: { type: "string", description: "" }, reasoning: { type: "string", description: "" } },
|
|
167
|
+
bash: { path: { type: "string", description: "" }, content: { type: "string", description: "" }, old_string: { type: "string", description: "" }, new_string: { type: "string", description: "" }, command: { type: "string", description: "Shell command to execute" }, message: { type: "string", description: "" }, pattern: { type: "string", description: "" }, url: { type: "string", description: "" }, reasoning: { type: "string", description: "" } },
|
|
168
|
+
git_status: { path: { type: "string", description: "" }, content: { type: "string", description: "" }, old_string: { type: "string", description: "" }, new_string: { type: "string", description: "" }, command: { type: "string", description: "" }, message: { type: "string", description: "" }, pattern: { type: "string", description: "" }, url: { type: "string", description: "" }, reasoning: { type: "string", description: "" } },
|
|
169
|
+
git_diff: { path: { type: "string", description: "" }, content: { type: "string", description: "" }, old_string: { type: "string", description: "" }, new_string: { type: "string", description: "" }, command: { type: "string", description: "" }, message: { type: "string", description: "" }, pattern: { type: "string", description: "" }, url: { type: "string", description: "" }, reasoning: { type: "string", description: "" } },
|
|
170
|
+
git_commit: { path: { type: "string", description: "" }, content: { type: "string", description: "" }, old_string: { type: "string", description: "" }, new_string: { type: "string", description: "" }, command: { type: "string", description: "" }, message: { type: "string", description: "Commit message" }, pattern: { type: "string", description: "" }, url: { type: "string", description: "" }, reasoning: { type: "string", description: "" } },
|
|
171
|
+
code_search: { path: { type: "string", description: "Search path" }, content: { type: "string", description: "" }, old_string: { type: "string", description: "" }, new_string: { type: "string", description: "" }, command: { type: "string", description: "" }, message: { type: "string", description: "" }, pattern: { type: "string", description: "Search pattern (regex)" }, url: { type: "string", description: "" }, reasoning: { type: "string", description: "" } },
|
|
172
|
+
web_fetch: { path: { type: "string", description: "" }, content: { type: "string", description: "" }, old_string: { type: "string", description: "" }, new_string: { type: "string", description: "" }, command: { type: "string", description: "" }, message: { type: "string", description: "" }, pattern: { type: "string", description: "" }, url: { type: "string", description: "URL to fetch" }, reasoning: { type: "string", description: "" } },
|
|
173
|
+
think: { path: { type: "string", description: "" }, content: { type: "string", description: "" }, old_string: { type: "string", description: "" }, new_string: { type: "string", description: "" }, command: { type: "string", description: "" }, message: { type: "string", description: "" }, pattern: { type: "string", description: "" }, url: { type: "string", description: "" }, reasoning: { type: "string", description: "Step-by-step reasoning" } },
|
|
174
|
+
list_files: { path: { type: "string", description: "Directory path" }, content: { type: "string", description: "" }, old_string: { type: "string", description: "" }, new_string: { type: "string", description: "" }, command: { type: "string", description: "" }, message: { type: "string", description: "" }, pattern: { type: "string", description: "" }, url: { type: "string", description: "" }, reasoning: { type: "string", description: "" } },
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
for (const [, tool] of this.tools) {
|
|
178
|
+
const props: Record<string, { type: string; description: string }> = {};
|
|
179
|
+
const required: string[] = [];
|
|
180
|
+
const params = paramSchemas[tool.name];
|
|
181
|
+
|
|
182
|
+
if (params) {
|
|
183
|
+
for (const [key, val] of Object.entries(params)) {
|
|
184
|
+
if (val.description) {
|
|
185
|
+
props[key] = val;
|
|
186
|
+
required.push(key);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
schemas.push({
|
|
192
|
+
type: "function",
|
|
193
|
+
function: {
|
|
194
|
+
name: tool.name,
|
|
195
|
+
description: tool.description,
|
|
196
|
+
parameters: {
|
|
197
|
+
type: "object",
|
|
198
|
+
properties: props,
|
|
199
|
+
required: required.length > 0 ? required : undefined,
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return schemas;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// ── Tool implementations ──
|
|
209
|
+
|
|
210
|
+
private validatePath(resolved: string): void {
|
|
211
|
+
const normalized = resolve(resolved);
|
|
212
|
+
if (!normalized.startsWith(PROJECT_ROOT)) {
|
|
213
|
+
throw new Error(`Path traversal blocked: '${resolved}' is outside project`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
private fileRead(path: string): string {
|
|
218
|
+
if (!path) return "Error: path is required";
|
|
219
|
+
const resolved = resolve(PROJECT_ROOT, path);
|
|
220
|
+
try { this.validatePath(resolved); } catch (e) {
|
|
221
|
+
return `Error: ${e instanceof Error ? e.message : "invalid path"}`;
|
|
222
|
+
}
|
|
223
|
+
if (!existsSync(resolved)) return `Error: File not found: ${path}`;
|
|
224
|
+
return readFileSync(resolved, "utf-8");
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private fileWrite(path: string, content: string): string {
|
|
228
|
+
if (!path || content === undefined) return "Error: path and content are required";
|
|
229
|
+
const resolved = resolve(PROJECT_ROOT, path);
|
|
230
|
+
try { this.validatePath(resolved); } catch (e) {
|
|
231
|
+
return `Error: ${e instanceof Error ? e.message : "invalid path"}`;
|
|
232
|
+
}
|
|
233
|
+
writeFileSync(resolved, content, "utf-8");
|
|
234
|
+
return `Written ${content.length} bytes to ${path}`;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
private fileEdit(path: string, oldString: string, newString: string): string {
|
|
238
|
+
if (!path || !oldString || newString === undefined) return "Error: path, old_string, and new_string are required";
|
|
239
|
+
const resolved = resolve(PROJECT_ROOT, path);
|
|
240
|
+
try { this.validatePath(resolved); } catch (e) {
|
|
241
|
+
return `Error: ${e instanceof Error ? e.message : "invalid path"}`;
|
|
242
|
+
}
|
|
243
|
+
if (!existsSync(resolved)) return `Error: File not found: ${path}`;
|
|
244
|
+
|
|
245
|
+
let content = readFileSync(resolved, "utf-8");
|
|
246
|
+
if (!content.includes(oldString)) return `Error: old_string not found in ${path}`;
|
|
247
|
+
|
|
248
|
+
content = content.replace(oldString, newString);
|
|
249
|
+
writeFileSync(resolved, content, "utf-8");
|
|
250
|
+
return `Edited ${path}: replaced "${oldString}" with "${newString}"`;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
private execBash(command: string): string {
|
|
254
|
+
if (!command) return "Error: command is required";
|
|
255
|
+
|
|
256
|
+
// Security: block dangerous commands
|
|
257
|
+
for (const blocked of BLOCKED_COMMANDS) {
|
|
258
|
+
if (command.toLowerCase().includes(blocked.toLowerCase())) {
|
|
259
|
+
this.db.logAudit("blocked_command", command, false);
|
|
260
|
+
return `Error: Command blocked for security: matches pattern "${blocked}"`;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
const result = execSync(command, {
|
|
266
|
+
cwd: process.cwd(),
|
|
267
|
+
timeout: 30_000,
|
|
268
|
+
maxBuffer: 1_048_576,
|
|
269
|
+
encoding: "utf-8",
|
|
270
|
+
shell: process.platform === "win32" ? "cmd.exe" : "/bin/bash",
|
|
271
|
+
});
|
|
272
|
+
return result || "(command completed with no output)";
|
|
273
|
+
} catch (error) {
|
|
274
|
+
if (error instanceof Error) {
|
|
275
|
+
return `Error: ${error.message}`;
|
|
276
|
+
}
|
|
277
|
+
return "Error: Command execution failed";
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
private execGit(args: string[]): string {
|
|
282
|
+
try {
|
|
283
|
+
const result = execSync(`git ${args.join(" ")}`, {
|
|
284
|
+
cwd: process.cwd(),
|
|
285
|
+
timeout: 10_000,
|
|
286
|
+
maxBuffer: 1_048_576,
|
|
287
|
+
encoding: "utf-8",
|
|
288
|
+
});
|
|
289
|
+
return result || "(git command completed with no output)";
|
|
290
|
+
} catch (error) {
|
|
291
|
+
if (error instanceof Error) {
|
|
292
|
+
return `Git error: ${error.message}`;
|
|
293
|
+
}
|
|
294
|
+
return "Git error: Command failed";
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
private searchCode(pattern: string, path = "."): string {
|
|
299
|
+
if (!pattern) return "Error: pattern is required";
|
|
300
|
+
const isWin = process.platform === "win32";
|
|
301
|
+
const searchCmd = isWin
|
|
302
|
+
? `findstr /n /i /c:"${pattern}" "${resolve(PROJECT_ROOT, path)}\\*"`
|
|
303
|
+
: `rg -n "${pattern}" "${resolve(PROJECT_ROOT, path)}" --max-count 20`;
|
|
304
|
+
try {
|
|
305
|
+
const result = execSync(searchCmd, {
|
|
306
|
+
cwd: PROJECT_ROOT,
|
|
307
|
+
timeout: 10_000,
|
|
308
|
+
maxBuffer: 1_048_576,
|
|
309
|
+
encoding: "utf-8",
|
|
310
|
+
shell: isWin ? "cmd.exe" : "/bin/bash",
|
|
311
|
+
});
|
|
312
|
+
return result || "No matches found.";
|
|
313
|
+
} catch {
|
|
314
|
+
return "No matches found.";
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
private async webFetch(url: string): Promise<string> {
|
|
319
|
+
if (!url) return "Error: url is required";
|
|
320
|
+
|
|
321
|
+
// Security: block private IPs, localhost, etc.
|
|
322
|
+
try {
|
|
323
|
+
const parsed = new URL(url);
|
|
324
|
+
const blockedHosts = ["localhost", "127.0.0.1", "0.0.0.0", "::1"];
|
|
325
|
+
if (blockedHosts.includes(parsed.hostname)) {
|
|
326
|
+
return "Error: Cannot fetch from localhost for security reasons.";
|
|
327
|
+
}
|
|
328
|
+
} catch {
|
|
329
|
+
return "Error: Invalid URL";
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
try {
|
|
333
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(10_000) });
|
|
334
|
+
const text = await response.text();
|
|
335
|
+
return text.slice(0, 10_000); // Limit response size
|
|
336
|
+
} catch (error) {
|
|
337
|
+
return `Error fetching URL: ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
private listDir(path = "."): string {
|
|
342
|
+
const resolved = resolve(PROJECT_ROOT, path);
|
|
343
|
+
try { this.validatePath(resolved); } catch (e) {
|
|
344
|
+
return `Error: ${e instanceof Error ? e.message : "invalid path"}`;
|
|
345
|
+
}
|
|
346
|
+
if (!existsSync(resolved)) return `Error: Directory not found: ${path}`;
|
|
347
|
+
|
|
348
|
+
const isWin = process.platform === "win32";
|
|
349
|
+
const cmd = isWin ? `dir /b "${resolved}"` : `ls -la "${resolved}"`;
|
|
350
|
+
try {
|
|
351
|
+
const result = execSync(cmd, {
|
|
352
|
+
timeout: 5_000,
|
|
353
|
+
maxBuffer: 10_000,
|
|
354
|
+
encoding: "utf-8",
|
|
355
|
+
shell: isWin ? "cmd.exe" : "/bin/bash",
|
|
356
|
+
});
|
|
357
|
+
return result;
|
|
358
|
+
} catch {
|
|
359
|
+
return `Error: Could not list directory ${path}`;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
package/src/ui/theme.ts
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VORACODE Theme — Apple-inspired premium design system
|
|
3
|
+
*
|
|
4
|
+
* Consistent colors, typography, and spacing for all CLI output.
|
|
5
|
+
* Inspired by Apple's design language: minimal, clean, confident.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { version } from "../../package.json";
|
|
9
|
+
|
|
10
|
+
const VERSION = version || "0.0.1";
|
|
11
|
+
|
|
12
|
+
// ── Color Palette (ANSI 256-bit, cross-platform) ──
|
|
13
|
+
|
|
14
|
+
export const c = {
|
|
15
|
+
// Primary
|
|
16
|
+
brand: "\x1b[38;2;155;89;182m", // Purple (#9b59b6)
|
|
17
|
+
brandLight: "\x1b[38;2;186;135;205m", // Light purple
|
|
18
|
+
|
|
19
|
+
// Status
|
|
20
|
+
success: "\x1b[38;2;46;204;113m", // Mint green
|
|
21
|
+
error: "\x1b[38;2;231;76;60m", // Coral red
|
|
22
|
+
warning: "\x1b[38;2;241;196;15m", // Warm gold
|
|
23
|
+
info: "\x1b[38;2;52;152;219m", // Sky blue
|
|
24
|
+
|
|
25
|
+
// Text
|
|
26
|
+
white: "\x1b[38;2;255;255;255m",
|
|
27
|
+
dim: "\x1b[38;2;149;165;166m", // Slate
|
|
28
|
+
bright: "\x1b[38;2;189;195;199m", // Silver
|
|
29
|
+
muted: "\x1b[38;2;127;140;141m", // Gray
|
|
30
|
+
|
|
31
|
+
// Accent
|
|
32
|
+
cyan: "\x1b[38;2;0;184;212m",
|
|
33
|
+
orange: "\x1b[38;2;230;126;34m",
|
|
34
|
+
pink: "\x1b[38;2;236;100;175m",
|
|
35
|
+
teal: "\x1b[38;2;0;184;148m",
|
|
36
|
+
|
|
37
|
+
// Reset
|
|
38
|
+
reset: "\x1b[0m",
|
|
39
|
+
bold: "\x1b[1m",
|
|
40
|
+
dim2: "\x1b[2m",
|
|
41
|
+
italic: "\x1b[3m",
|
|
42
|
+
underline: "\x1b[4m",
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// ── Icons (Unicode) ──
|
|
46
|
+
|
|
47
|
+
export const icon = {
|
|
48
|
+
check: `${c.success}●${c.reset}`,
|
|
49
|
+
cross: `${c.error}●${c.reset}`,
|
|
50
|
+
warn: `${c.warning}●${c.reset}`,
|
|
51
|
+
info: `${c.info}●${c.reset}`,
|
|
52
|
+
arrow: `${c.brand}→${c.reset}`,
|
|
53
|
+
dot: `${c.dim}·${c.reset}`,
|
|
54
|
+
diamond: `${c.brand}◆${c.reset}`,
|
|
55
|
+
star: `${c.warning}★${c.reset}`,
|
|
56
|
+
bolt: `${c.cyan}⚡${c.reset}`,
|
|
57
|
+
shield: `${c.teal}◇${c.reset}`,
|
|
58
|
+
key: `${c.orange}◇${c.reset}`,
|
|
59
|
+
brain: `${c.purple || c.pink}◈${c.reset}`,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// ── Layout Helpers ──
|
|
63
|
+
|
|
64
|
+
export function divider(char = "─", width = 48): string {
|
|
65
|
+
return `${c.dim}${char.repeat(width)}${c.reset}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function indent(text: string, spaces = 4): string {
|
|
69
|
+
return text.split("\n").map((l) => " ".repeat(spaces) + l).join("\n");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ── Box Drawing (Premium) ──
|
|
73
|
+
|
|
74
|
+
export function box(lines: string[], width = 50): string {
|
|
75
|
+
const pad = (s: string, w: number) => s + " ".repeat(Math.max(0, w - s.length));
|
|
76
|
+
const top = ` ┌${"─".repeat(width)}┐`;
|
|
77
|
+
const bot = ` └${"─".repeat(width)}┘`;
|
|
78
|
+
const mid = lines.map((l) => ` │ ${pad(l, width - 1)}│`).join("\n");
|
|
79
|
+
return `${top}\n${mid}\n${bot}`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ── Branded Output ──
|
|
83
|
+
|
|
84
|
+
export function branded(text: string): string {
|
|
85
|
+
return `${c.brand}${text}${c.reset}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function success(text: string): string {
|
|
89
|
+
return `${icon.check} ${text}`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function error(text: string): string {
|
|
93
|
+
return `${icon.cross} ${text}`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function warning(text: string): string {
|
|
97
|
+
return `${icon.warn} ${text}`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function info(text: string): string {
|
|
101
|
+
return `${icon.info} ${text}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ── Banner ──
|
|
105
|
+
|
|
106
|
+
export function printBanner(): void {
|
|
107
|
+
console.log(`
|
|
108
|
+
${c.brand} ◆${c.reset} ${c.bold}V O R A C O D E${c.reset} ${c.brand}◆${c.reset}
|
|
109
|
+
|
|
110
|
+
${c.dim}Your AI engineering partner.${c.reset}
|
|
111
|
+
${c.dim}One agent, every surface.${c.reset}
|
|
112
|
+
|
|
113
|
+
${c.muted}v${VERSION}${c.reset} ${c.dim}·${c.reset} ${c.muted}${new Date().toLocaleDateString() + " " + new Date().toLocaleTimeString()}${c.reset}
|
|
114
|
+
`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ── Command Banner ──
|
|
118
|
+
|
|
119
|
+
export function commandBanner(title: string, subtitle?: string): void {
|
|
120
|
+
console.log(`
|
|
121
|
+
${c.brand}◆${c.reset} ${c.bold}${title}${c.reset}
|
|
122
|
+
${c.dim}${subtitle || ""}${c.reset}
|
|
123
|
+
`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ── Status Line ──
|
|
127
|
+
|
|
128
|
+
export function statusLine(label: string, value: string, type: "success" | "error" | "warning" | "info" | "dim" = "info"): string {
|
|
129
|
+
const colorMap = { success: c.success, error: c.error, warning: c.warning, info: c.info, dim: c.dim };
|
|
130
|
+
return ` ${c.dim}${label.padEnd(20)}${c.reset} ${colorMap[type]}${value}${c.reset}`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ── Table ──
|
|
134
|
+
|
|
135
|
+
export function table(headers: string[], rows: string[][]): string {
|
|
136
|
+
const widths = headers.map((h, i) => {
|
|
137
|
+
const maxData = Math.max(...rows.map((r) => (r[i] || "").length));
|
|
138
|
+
return Math.max(h.length, maxData);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const line = ` ┌${widths.map((w) => "─".repeat(w + 2)).join("┬")}┐`;
|
|
142
|
+
const head = ` │${headers.map((h, i) => ` ${c.bold}${h.padEnd(widths[i])}${c.reset} │`).join("")}`;
|
|
143
|
+
const sep = ` ├${widths.map((w) => "─".repeat(w + 2)).join("┼")}┤`;
|
|
144
|
+
const body = rows.map((r) =>
|
|
145
|
+
` │${r.map((cell, i) => ` ${cell.padEnd(widths[i])} │`).join("")}`
|
|
146
|
+
).join("\n");
|
|
147
|
+
|
|
148
|
+
return `${line}\n${head}\n${sep}\n${body}\n${line}`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ── Progress ──
|
|
152
|
+
|
|
153
|
+
export function spinner(text: string): string {
|
|
154
|
+
return ` ${c.brand}◆${c.reset} ${text}`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ── Footer ──
|
|
158
|
+
|
|
159
|
+
export function footer(): void {
|
|
160
|
+
console.log(`
|
|
161
|
+
${c.dim}${c.brand}◆${c.reset} ${c.muted}voracode${c.reset} ${c.dim}|${c.reset} ${c.muted}github.com/mysterious75/voracode${c.reset} ${c.dim}|${c.reset} ${c.muted}MIT${c.reset}
|
|
162
|
+
`);
|
|
163
|
+
}
|