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.
@@ -0,0 +1,253 @@
1
+ /**
2
+ * VORACODE Database — SQLite storage using bun:sqlite
3
+ *
4
+ * Stores sessions, messages, memory, stats, audit logs.
5
+ * All data is LOCAL by default — zero telemetry.
6
+ */
7
+
8
+ import Database from "better-sqlite3";
9
+ import { existsSync, mkdirSync } from "fs";
10
+ import { join } from "path";
11
+ import { homedir } from "os";
12
+
13
+ export class VoraDatabase {
14
+ private db: Database;
15
+ private dataDir: string;
16
+
17
+ constructor() {
18
+ this.dataDir = join(homedir(), ".local", "share", "voracode");
19
+ if (!existsSync(this.dataDir)) {
20
+ mkdirSync(this.dataDir, { recursive: true });
21
+ }
22
+
23
+ this.db = new Database(join(this.dataDir, "voracode.db"));
24
+ this.db.exec("PRAGMA journal_mode=WAL");
25
+ this.db.exec("PRAGMA foreign_keys=ON");
26
+ this.migrate();
27
+ }
28
+
29
+ private migrate(): void {
30
+ this.db.exec(`
31
+ CREATE TABLE IF NOT EXISTS sessions (
32
+ id TEXT PRIMARY KEY,
33
+ title TEXT,
34
+ project_path TEXT,
35
+ model_provider TEXT NOT NULL,
36
+ model_name TEXT NOT NULL,
37
+ total_tokens INTEGER DEFAULT 0,
38
+ total_turns INTEGER DEFAULT 0,
39
+ status TEXT DEFAULT 'active',
40
+ created_at TEXT DEFAULT (datetime('now')),
41
+ updated_at TEXT DEFAULT (datetime('now'))
42
+ )
43
+ `);
44
+
45
+ this.db.exec(`
46
+ CREATE TABLE IF NOT EXISTS messages (
47
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
48
+ session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
49
+ role TEXT NOT NULL,
50
+ content TEXT NOT NULL,
51
+ tokens INTEGER,
52
+ tool_calls TEXT,
53
+ parent_id INTEGER REFERENCES messages(id),
54
+ created_at TEXT DEFAULT (datetime('now'))
55
+ )
56
+ `);
57
+
58
+ this.db.exec(`
59
+ CREATE TABLE IF NOT EXISTS memory (
60
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
61
+ key TEXT UNIQUE,
62
+ content TEXT NOT NULL,
63
+ category TEXT,
64
+ priority INTEGER DEFAULT 1,
65
+ tags TEXT,
66
+ created_at TEXT DEFAULT (datetime('now')),
67
+ updated_at TEXT DEFAULT (datetime('now'))
68
+ )
69
+ `);
70
+
71
+ this.db.exec(`
72
+ CREATE VIRTUAL TABLE IF NOT EXISTS memory_fts USING fts5(
73
+ content, tags,
74
+ content=memory,
75
+ content_rowid=id
76
+ )
77
+ `);
78
+
79
+ this.db.exec(`
80
+ CREATE TABLE IF NOT EXISTS skills (
81
+ name TEXT PRIMARY KEY,
82
+ description TEXT NOT NULL,
83
+ source TEXT NOT NULL,
84
+ path TEXT NOT NULL,
85
+ trigger_count INTEGER DEFAULT 0,
86
+ success_count INTEGER DEFAULT 0,
87
+ last_used_at TEXT,
88
+ created_at TEXT DEFAULT (datetime('now'))
89
+ )
90
+ `);
91
+
92
+ this.db.exec(`
93
+ CREATE TABLE IF NOT EXISTS stats (
94
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
95
+ date TEXT NOT NULL UNIQUE,
96
+ api_calls INTEGER DEFAULT 0,
97
+ tokens_in INTEGER DEFAULT 0,
98
+ tokens_out INTEGER DEFAULT 0,
99
+ cache_hits INTEGER DEFAULT 0,
100
+ errors INTEGER DEFAULT 0,
101
+ tasks_completed INTEGER DEFAULT 0
102
+ )
103
+ `);
104
+
105
+ this.db.exec(`
106
+ CREATE TABLE IF NOT EXISTS audit_log (
107
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
108
+ timestamp TEXT DEFAULT (datetime('now')),
109
+ event_type TEXT NOT NULL,
110
+ details TEXT,
111
+ allowed INTEGER NOT NULL DEFAULT 1
112
+ )
113
+ `);
114
+
115
+ this.db.exec(`
116
+ CREATE TABLE IF NOT EXISTS checkpoints (
117
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
118
+ session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
119
+ message_id INTEGER NOT NULL REFERENCES messages(id),
120
+ description TEXT,
121
+ created_at TEXT DEFAULT (datetime('now'))
122
+ )
123
+ `);
124
+ }
125
+
126
+ // ── Session operations ──
127
+
128
+ createSession(provider: string, model: string, title = "Untitled"): string {
129
+ const id = crypto.randomUUID();
130
+ this.db.run(
131
+ "INSERT INTO sessions (id, title, model_provider, model_name) VALUES (?, ?, ?, ?)",
132
+ [id, title, provider, model],
133
+ );
134
+ return id;
135
+ }
136
+
137
+ getSession(id: string) {
138
+ return this.db.prepare("SELECT * FROM sessions WHERE id = ?").get(id);
139
+ }
140
+
141
+ listSessions(limit = 10) {
142
+ return this.db
143
+ .query("SELECT * FROM sessions ORDER BY created_at DESC LIMIT ?")
144
+ .all(limit);
145
+ }
146
+
147
+ updateSession(id: string, updates: Record<string, unknown>): void {
148
+ const keys = Object.keys(updates);
149
+ const setClause = keys.map((k) => `${k} = ?`).join(", ");
150
+ const values = keys.map((k) => updates[k]);
151
+ values.push(id);
152
+ this.db.run(`UPDATE sessions SET ${setClause}, updated_at = datetime('now') WHERE id = ?`, values);
153
+ }
154
+
155
+ deleteSession(id: string): void {
156
+ this.db.prepare("DELETE FROM sessions WHERE id = ?").run([id]);
157
+ }
158
+
159
+ // ── Message operations ──
160
+
161
+ addMessage(sessionId: string, role: string, content: string, tokens = 0, toolCalls?: string): number {
162
+ const result = this.db.run(
163
+ "INSERT INTO messages (session_id, role, content, tokens, tool_calls) VALUES (?, ?, ?, ?, ?)",
164
+ [sessionId, role, content, tokens, toolCalls || null],
165
+ );
166
+ return Number(result.lastInsertRowid);
167
+ }
168
+
169
+ getMessages(sessionId: string, limit = 100) {
170
+ return this.db
171
+ .query("SELECT * FROM messages WHERE session_id = ? ORDER BY created_at ASC LIMIT ?")
172
+ .all(sessionId, limit);
173
+ }
174
+
175
+ // ── Memory operations ──
176
+
177
+ addMemory(key: string, content: string, category = "general", tags?: string): void {
178
+ this.db.run(
179
+ "INSERT OR REPLACE INTO memory (key, content, category, tags, updated_at) VALUES (?, ?, ?, ?, datetime('now'))",
180
+ [key, content, category, tags || null],
181
+ );
182
+ }
183
+
184
+ searchMemory(query: string) {
185
+ return this.db
186
+ .query("SELECT m.* FROM memory_fts f JOIN memory m ON f.rowid = m.id WHERE memory_fts MATCH ? ORDER BY rank LIMIT 10")
187
+ .all(query);
188
+ }
189
+
190
+ // ── Stats operations ──
191
+
192
+ recordApiCall(tokensIn: number, tokensOut: number): void {
193
+ const today = new Date().toISOString().slice(0, 10);
194
+ this.db.run(`
195
+ INSERT INTO stats (date, api_calls, tokens_in, tokens_out) VALUES (?, 1, ?, ?)
196
+ ON CONFLICT(date) DO UPDATE SET api_calls = api_calls + 1, tokens_in = tokens_in + ?, tokens_out = tokens_out + ?
197
+ `, [today, tokensIn, tokensOut, tokensIn, tokensOut]);
198
+ }
199
+
200
+ getStats(days = 7) {
201
+ return this.db
202
+ .query("SELECT * FROM stats WHERE date >= date('now', ? || ' days') ORDER BY date")
203
+ .all(`-${days}`);
204
+ }
205
+
206
+ // ── Audit operations ──
207
+
208
+ logAudit(eventType: string, details: string, allowed = true): void {
209
+ this.db.run(
210
+ "INSERT INTO audit_log (event_type, details, allowed) VALUES (?, ?, ?)",
211
+ [eventType, details, allowed ? 1 : 0],
212
+ );
213
+ }
214
+
215
+ getAuditLog(limit = 50) {
216
+ return this.db
217
+ .query("SELECT * FROM audit_log ORDER BY timestamp DESC LIMIT ?")
218
+ .all(limit);
219
+ }
220
+
221
+ // ── Checkpoint operations ──
222
+
223
+ createCheckpoint(sessionId: string, messageId: number, description: string): void {
224
+ this.db.run(
225
+ "INSERT INTO checkpoints (session_id, message_id, description) VALUES (?, ?, ?)",
226
+ [sessionId, messageId, description],
227
+ );
228
+ }
229
+
230
+ // ── Utility ──
231
+
232
+ close(): void {
233
+ this.db.close();
234
+ }
235
+
236
+ getDbPath(): string {
237
+ return join(this.dataDir, "voracode.db");
238
+ }
239
+
240
+ // ── Generic database access (used by SelfImprovementEngine) ──
241
+
242
+ exec(sql: string): void {
243
+ this.db.exec(sql);
244
+ }
245
+
246
+ query(sql: string) {
247
+ return this.db.prepare(sql);
248
+ }
249
+
250
+ run(sql: string, params: unknown[] = []): void {
251
+ this.db.run(sql, params);
252
+ }
253
+ }
@@ -0,0 +1,170 @@
1
+ /**
2
+ * VORACODE MCP Config — MCP server configuration management
3
+ *
4
+ * Stores MCP server configs in JSON files.
5
+ * Supports stdio, HTTP, SSE, and WebSocket transports.
6
+ */
7
+
8
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, unlinkSync } from "fs";
9
+ import { join } from "path";
10
+ import { homedir } from "os";
11
+
12
+ export type TransportType = "stdio" | "http" | "sse" | "ws";
13
+
14
+ export interface McpServerConfig {
15
+ name: string;
16
+ transport: TransportType;
17
+ command?: string;
18
+ args?: string[];
19
+ env?: Record<string, string>;
20
+ url?: string;
21
+ headers?: Record<string, string>;
22
+ scope: "local" | "project";
23
+ enabled: boolean;
24
+ createdAt: string;
25
+ }
26
+
27
+ export class McpConfigManager {
28
+ private localDir: string;
29
+ private projectDir: string;
30
+
31
+ constructor() {
32
+ this.localDir = join(homedir(), ".config", "voracode", "mcp");
33
+ this.projectDir = join(process.cwd(), ".voracode", "mcp");
34
+ }
35
+
36
+ private ensureDirs(): void {
37
+ if (!existsSync(this.localDir)) mkdirSync(this.localDir, { recursive: true });
38
+ if (!existsSync(this.projectDir)) mkdirSync(this.projectDir, { recursive: true });
39
+ }
40
+
41
+ private getDir(scope: "local" | "project"): string {
42
+ return scope === "project" ? this.projectDir : this.localDir;
43
+ }
44
+
45
+ private getFilePath(name: string, scope: "local" | "project"): string {
46
+ return join(this.getDir(scope), `${name}.json`);
47
+ }
48
+
49
+ /**
50
+ * Add or update an MCP server config
51
+ */
52
+ add(config: McpServerConfig): void {
53
+ this.ensureDirs();
54
+ writeFileSync(
55
+ this.getFilePath(config.name, config.scope),
56
+ JSON.stringify(config, null, 2),
57
+ "utf-8",
58
+ );
59
+ }
60
+
61
+ /**
62
+ * Remove an MCP server config
63
+ */
64
+ remove(name: string): boolean {
65
+ // Try local first, then project
66
+ for (const scope of ["local", "project"] as const) {
67
+ const path = this.getFilePath(name, scope);
68
+ if (existsSync(path)) {
69
+ unlinkSync(path);
70
+ return true;
71
+ }
72
+ }
73
+ return false;
74
+ }
75
+
76
+ /**
77
+ * Get a single MCP server config
78
+ */
79
+ get(name: string): McpServerConfig | null {
80
+ for (const scope of ["local", "project"] as const) {
81
+ const path = this.getFilePath(name, scope);
82
+ if (existsSync(path)) {
83
+ try {
84
+ return JSON.parse(readFileSync(path, "utf-8")) as McpServerConfig;
85
+ } catch {
86
+ return null;
87
+ }
88
+ }
89
+ }
90
+ return null;
91
+ }
92
+
93
+ /**
94
+ * List all configured MCP servers
95
+ */
96
+ list(): McpServerConfig[] {
97
+ this.ensureDirs();
98
+ const servers: McpServerConfig[] = [];
99
+
100
+ for (const scope of ["local", "project"] as const) {
101
+ const dir = this.getDir(scope);
102
+ if (!existsSync(dir)) continue;
103
+
104
+ for (const file of readdirSync(dir)) {
105
+ if (!file.endsWith(".json")) continue;
106
+ try {
107
+ const config = JSON.parse(readFileSync(join(dir, file), "utf-8")) as McpServerConfig;
108
+ servers.push(config);
109
+ } catch {
110
+ // Skip invalid configs
111
+ }
112
+ }
113
+ }
114
+
115
+ return servers;
116
+ }
117
+
118
+ /**
119
+ * Test if a server is reachable
120
+ */
121
+ async test(name: string): Promise<{ success: boolean; message: string; tools?: string[] }> {
122
+ const config = this.get(name);
123
+ if (!config) {
124
+ return { success: false, message: `Server '${name}' not found` };
125
+ }
126
+
127
+ if (config.transport === "stdio") {
128
+ // Check if command exists
129
+ if (!config.command) {
130
+ return { success: false, message: "No command configured for stdio server" };
131
+ }
132
+ return {
133
+ success: true,
134
+ message: `Config OK — run with: ${config.command} ${(config.args || []).join(" ")}`,
135
+ };
136
+ }
137
+
138
+ if ((config.transport === "http" || config.transport === "sse" || config.transport === "ws") && config.url) {
139
+ try {
140
+ const headers: Record<string, string> = { "Content-Type": "application/json", ...config.headers };
141
+ const response = await fetch(config.url, {
142
+ method: "POST",
143
+ headers,
144
+ body: JSON.stringify({ jsonrpc: "2.0", id: "1", method: "initialize", params: { protocolVersion: "2024-11-05", capabilities: {}, clientInfo: { name: "voracode", version: "0.0.1" } } }),
145
+ signal: AbortSignal.timeout(5000),
146
+ });
147
+
148
+ if (response.ok) {
149
+ const data = await response.json().catch(() => null);
150
+ return {
151
+ success: true,
152
+ message: `Connected to ${config.name} (${config.transport})`,
153
+ tools: data?.result?.capabilities?.tools ? ["available"] : undefined,
154
+ };
155
+ }
156
+ return {
157
+ success: response.status === 401 || response.status === 403,
158
+ message: `Auth required (${response.status}) — use /mcp to authenticate`,
159
+ };
160
+ } catch (error) {
161
+ return {
162
+ success: false,
163
+ message: `Connection failed: ${error instanceof Error ? error.message : "unknown error"}`,
164
+ };
165
+ }
166
+ }
167
+
168
+ return { success: false, message: "Invalid server configuration" };
169
+ }
170
+ }