@conversionpros/aiva 1.0.1 → 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.
Files changed (149) hide show
  1. package/bin/aiva.js +26 -14
  2. package/lib/bluebubbles.js +145 -0
  3. package/lib/config-gen.js +253 -0
  4. package/lib/constants.js +72 -0
  5. package/lib/launch-agent.js +112 -0
  6. package/lib/prerequisites.js +236 -0
  7. package/lib/process.js +59 -145
  8. package/lib/setup.js +224 -194
  9. package/lib/validate.js +194 -0
  10. package/package.json +7 -32
  11. package/auto-deploy.js +0 -190
  12. package/cli-sync.js +0 -126
  13. package/d2a-prompt-template.txt +0 -106
  14. package/diagnostics-api.js +0 -304
  15. package/docs/ara-dedup-fix-scope.md +0 -112
  16. package/docs/ara-fix-round2-scope.md +0 -61
  17. package/docs/ara-greeting-fix-scope.md +0 -70
  18. package/docs/calendar-date-fix-scope.md +0 -28
  19. package/docs/getting-started.md +0 -115
  20. package/docs/network-architecture-rollout-scope.md +0 -43
  21. package/docs/scope-google-oauth-integration.md +0 -351
  22. package/docs/settings-page-scope.md +0 -50
  23. package/docs/xai-imagine-scope.md +0 -116
  24. package/docs/xai-voice-integration-scope.md +0 -115
  25. package/docs/xai-voice-tools-scope.md +0 -165
  26. package/email-router.js +0 -512
  27. package/follow-up-handler.js +0 -606
  28. package/gateway-monitor.js +0 -158
  29. package/google-email.js +0 -379
  30. package/google-oauth.js +0 -310
  31. package/grok-imagine.js +0 -97
  32. package/health-reporter.js +0 -287
  33. package/invisible-prefix-base.txt +0 -206
  34. package/invisible-prefix-owner.txt +0 -26
  35. package/invisible-prefix-slim.txt +0 -10
  36. package/invisible-prefix.txt +0 -43
  37. package/knowledge-base.js +0 -472
  38. package/lib/cli.js +0 -19
  39. package/lib/server.js +0 -42
  40. package/meta-capi.js +0 -206
  41. package/meta-leads.js +0 -411
  42. package/notion-oauth.js +0 -323
  43. package/public/agent-config.html +0 -241
  44. package/public/aiva-avatar-anime.png +0 -0
  45. package/public/css/docs.css.bak +0 -688
  46. package/public/css/onboarding.css +0 -543
  47. package/public/diagrams/claude-subscription-pool.html +0 -329
  48. package/public/diagrams/claude-subscription-pool.png +0 -0
  49. package/public/docs-icon.png +0 -0
  50. package/public/escalation.html +0 -237
  51. package/public/group-config.html +0 -300
  52. package/public/icon-192.png +0 -0
  53. package/public/icon-512.png +0 -0
  54. package/public/icons/agents.svg +0 -1
  55. package/public/icons/attach.svg +0 -1
  56. package/public/icons/characters.svg +0 -1
  57. package/public/icons/chat.svg +0 -1
  58. package/public/icons/docs.svg +0 -1
  59. package/public/icons/heartbeat.svg +0 -1
  60. package/public/icons/messages.svg +0 -1
  61. package/public/icons/mic.svg +0 -1
  62. package/public/icons/notes.svg +0 -1
  63. package/public/icons/settings.svg +0 -1
  64. package/public/icons/tasks.svg +0 -1
  65. package/public/images/onboarding/p0-communication-layer.png +0 -0
  66. package/public/images/onboarding/p0-infinite-surface.png +0 -0
  67. package/public/images/onboarding/p0-learning-model.png +0 -0
  68. package/public/images/onboarding/p0-meet-aiva.png +0 -0
  69. package/public/images/onboarding/p4-contact-intelligence.png +0 -0
  70. package/public/images/onboarding/p4-context-compounds.png +0 -0
  71. package/public/images/onboarding/p4-message-router.png +0 -0
  72. package/public/images/onboarding/p4-per-contact-rules.png +0 -0
  73. package/public/images/onboarding/p4-send-messages.png +0 -0
  74. package/public/images/onboarding/p6-be-precise.png +0 -0
  75. package/public/images/onboarding/p6-review-escalations.png +0 -0
  76. package/public/images/onboarding/p6-voice-input.png +0 -0
  77. package/public/images/onboarding/p7-completion.png +0 -0
  78. package/public/index.html +0 -11594
  79. package/public/js/onboarding.js +0 -699
  80. package/public/manifest.json +0 -24
  81. package/public/messages-v2.html +0 -2824
  82. package/public/permission-approve.html.bak +0 -107
  83. package/public/permissions.html +0 -150
  84. package/public/styles/design-system.css +0 -68
  85. package/router-db.js +0 -604
  86. package/router-utils.js +0 -28
  87. package/router-v2/adapters/imessage.js +0 -191
  88. package/router-v2/adapters/quo.js +0 -82
  89. package/router-v2/adapters/whatsapp.js +0 -192
  90. package/router-v2/contact-manager.js +0 -234
  91. package/router-v2/conversation-engine.js +0 -498
  92. package/router-v2/data/knowledge-base.json +0 -176
  93. package/router-v2/data/router-v2.db +0 -0
  94. package/router-v2/data/router-v2.db-shm +0 -0
  95. package/router-v2/data/router-v2.db-wal +0 -0
  96. package/router-v2/data/router.db +0 -0
  97. package/router-v2/db.js +0 -457
  98. package/router-v2/escalation-bridge.js +0 -540
  99. package/router-v2/follow-up-engine.js +0 -347
  100. package/router-v2/index.js +0 -441
  101. package/router-v2/ingestion.js +0 -213
  102. package/router-v2/knowledge-base.js +0 -231
  103. package/router-v2/lead-qualifier.js +0 -152
  104. package/router-v2/learning-loop.js +0 -202
  105. package/router-v2/outbound-sender.js +0 -160
  106. package/router-v2/package.json +0 -13
  107. package/router-v2/permission-gate.js +0 -86
  108. package/router-v2/playbook.js +0 -177
  109. package/router-v2/prompts/base.js +0 -52
  110. package/router-v2/prompts/first-contact.js +0 -38
  111. package/router-v2/prompts/lead-qualification.js +0 -37
  112. package/router-v2/prompts/scheduling.js +0 -72
  113. package/router-v2/prompts/style-overrides.js +0 -22
  114. package/router-v2/scheduler.js +0 -301
  115. package/router-v2/scripts/migrate-v1-to-v2.js +0 -215
  116. package/router-v2/scripts/seed-faq.js +0 -67
  117. package/router-v2/seed-knowledge-base.js +0 -39
  118. package/router-v2/utils/ai.js +0 -129
  119. package/router-v2/utils/phone.js +0 -52
  120. package/router-v2/utils/response-validator.js +0 -98
  121. package/router-v2/utils/sanitize.js +0 -222
  122. package/router.js +0 -5005
  123. package/routes/google-calendar.js +0 -186
  124. package/scripts/deploy.sh +0 -62
  125. package/scripts/macos-calendar.sh +0 -232
  126. package/scripts/onboard-device.sh +0 -466
  127. package/server.js +0 -5131
  128. package/start.sh +0 -24
  129. package/templates/AGENTS.md +0 -548
  130. package/templates/IDENTITY.md +0 -15
  131. package/templates/docs-agents.html +0 -132
  132. package/templates/docs-app.html +0 -130
  133. package/templates/docs-home.html +0 -83
  134. package/templates/docs-imessage.html +0 -121
  135. package/templates/docs-tasks.html +0 -123
  136. package/templates/docs-tips.html +0 -175
  137. package/templates/getting-started.html +0 -809
  138. package/templates/invisible-prefix-base.txt +0 -171
  139. package/templates/invisible-prefix-owner.txt +0 -282
  140. package/templates/invisible-prefix.txt +0 -338
  141. package/templates/manifest.json +0 -61
  142. package/templates/memory-org/clients.md +0 -7
  143. package/templates/memory-org/credentials.md +0 -9
  144. package/templates/memory-org/devices.md +0 -7
  145. package/templates/updates.html +0 -464
  146. package/tts-proxy.js +0 -96
  147. package/voice-call-local.js +0 -731
  148. package/voice-call.js +0 -732
  149. package/wa-listener.js +0 -354
package/knowledge-base.js DELETED
@@ -1,472 +0,0 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
-
4
- const WORKSPACE = path.join(process.env.HOME || '/Users/brandonburgan', '.openclaw', 'workspace');
5
- const DB_PATH = path.join(__dirname, 'data', 'knowledge.db');
6
- const EMBEDDING_DIM = 1536;
7
-
8
- // Try to find an embedding endpoint - gateway chat completions doesn't support embeddings,
9
- // so we fall back to BM25 text search which works great for this use case
10
- let EMBEDDING_URL = null;
11
- let EMBEDDING_TOKEN = null;
12
- let EMBEDDING_MODEL = 'openai/text-embedding-3-small';
13
-
14
- const WATCHED_PATHS = [
15
- path.join(WORKSPACE, 'memory'),
16
- path.join(WORKSPACE, 'MEMORY.md'),
17
- path.join(WORKSPACE, 'TOOLS.md'),
18
- path.join(WORKSPACE, 'AGENTS.md'),
19
- path.join(WORKSPACE, 'SOUL.md'),
20
- path.join(WORKSPACE, 'USER.md'),
21
- ];
22
-
23
- let db = null;
24
- let lastIndexedAt = null;
25
- let watchers = [];
26
- let debounceTimers = {};
27
-
28
- function initKnowledgeBase() {
29
- const Database = require('better-sqlite3');
30
- const dataDir = path.join(__dirname, 'data');
31
- if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true });
32
-
33
- db = new Database(DB_PATH);
34
- db.pragma('journal_mode = WAL');
35
-
36
- db.exec(`
37
- CREATE TABLE IF NOT EXISTS chunks (
38
- id INTEGER PRIMARY KEY AUTOINCREMENT,
39
- file_path TEXT NOT NULL,
40
- file_mtime REAL NOT NULL,
41
- chunk_index INTEGER NOT NULL,
42
- start_line INTEGER NOT NULL,
43
- end_line INTEGER NOT NULL,
44
- text TEXT NOT NULL,
45
- embedding BLOB,
46
- created_at TEXT DEFAULT CURRENT_TIMESTAMP
47
- );
48
- CREATE INDEX IF NOT EXISTS idx_file_path ON chunks(file_path);
49
- `);
50
-
51
- // Try to detect embedding API availability
52
- tryDetectEmbeddingAPI();
53
-
54
- console.log('[knowledge-base] Initialized SQLite at', DB_PATH);
55
- return db;
56
- }
57
-
58
- async function tryDetectEmbeddingAPI() {
59
- // TIER 1: Platform infrastructure — uses CMP's key from .env, NOT client token. This is intentional.
60
- if (process.env.OPENAI_API_KEY) {
61
- EMBEDDING_URL = 'https://api.openai.com/v1/embeddings';
62
- EMBEDDING_TOKEN = process.env.OPENAI_API_KEY;
63
- EMBEDDING_MODEL = 'text-embedding-3-small';
64
- console.log('[knowledge-base] Using OpenAI embeddings API');
65
- return;
66
- }
67
- // Check OpenClaw gateway (may support embeddings in future)
68
- try {
69
- const resp = await fetch('http://127.0.0.1:18789/v1/embeddings', {
70
- method: 'POST',
71
- headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${process.env.OPENCLAW_GATEWAY_TOKEN || 'Ttsrgr812!'}` },
72
- body: JSON.stringify({ model: 'openai/text-embedding-3-small', input: 'test' })
73
- });
74
- if (resp.ok) {
75
- EMBEDDING_URL = 'http://127.0.0.1:18789/v1/embeddings';
76
- EMBEDDING_TOKEN = process.env.OPENCLAW_GATEWAY_TOKEN || 'Ttsrgr812!';
77
- console.log('[knowledge-base] Using OpenClaw gateway embeddings');
78
- return;
79
- }
80
- } catch (e) {}
81
- console.log('[knowledge-base] No embedding API available — using BM25 text search');
82
- }
83
-
84
- // ========== BM25 SEARCH ==========
85
- // Simple but effective text search when no embedding API is available
86
-
87
- function tokenize(text) {
88
- return text.toLowerCase()
89
- .replace(/[^a-z0-9\s-]/g, ' ')
90
- .split(/\s+/)
91
- .filter(t => t.length > 1);
92
- }
93
-
94
- function bm25Search(query, chunks, limit = 5) {
95
- const queryTokens = tokenize(query);
96
- if (queryTokens.length === 0) return [];
97
-
98
- const N = chunks.length;
99
- const avgDl = chunks.reduce((s, c) => s + tokenize(c.text).length, 0) / N;
100
- const k1 = 1.5, b = 0.75;
101
-
102
- // Compute IDF for query terms
103
- const df = {};
104
- for (const token of queryTokens) df[token] = 0;
105
- for (const chunk of chunks) {
106
- const tokens = new Set(tokenize(chunk.text));
107
- for (const qt of queryTokens) {
108
- if (tokens.has(qt)) df[qt]++;
109
- }
110
- }
111
-
112
- const idf = {};
113
- for (const [term, freq] of Object.entries(df)) {
114
- idf[term] = Math.log((N - freq + 0.5) / (freq + 0.5) + 1);
115
- }
116
-
117
- // Score each chunk
118
- const scored = chunks.map(chunk => {
119
- const tokens = tokenize(chunk.text);
120
- const dl = tokens.length;
121
- const tf = {};
122
- for (const t of tokens) {
123
- tf[t] = (tf[t] || 0) + 1;
124
- }
125
-
126
- let score = 0;
127
- for (const qt of queryTokens) {
128
- const termTf = tf[qt] || 0;
129
- if (termTf === 0) continue;
130
- const tfNorm = (termTf * (k1 + 1)) / (termTf + k1 * (1 - b + b * (dl / avgDl)));
131
- score += (idf[qt] || 0) * tfNorm;
132
- }
133
-
134
- return {
135
- filePath: chunk.file_path,
136
- startLine: chunk.start_line,
137
- endLine: chunk.end_line,
138
- text: chunk.text,
139
- score
140
- };
141
- });
142
-
143
- scored.sort((a, b) => b.score - a.score);
144
- return scored.slice(0, limit).filter(r => r.score > 0);
145
- }
146
-
147
- // ========== EMBEDDING SEARCH ==========
148
-
149
- async function getEmbedding(text) {
150
- if (!EMBEDDING_URL) return null;
151
- const resp = await fetch(EMBEDDING_URL, {
152
- method: 'POST',
153
- headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${EMBEDDING_TOKEN}` },
154
- body: JSON.stringify({ model: EMBEDDING_MODEL, input: text })
155
- });
156
- if (!resp.ok) throw new Error(`Embedding API error: ${resp.status}`);
157
- const data = await resp.json();
158
- return new Float32Array(data.data[0].embedding);
159
- }
160
-
161
- async function getEmbeddingsBatch(texts) {
162
- if (!EMBEDDING_URL) return texts.map(() => null);
163
- const results = [];
164
- for (let i = 0; i < texts.length; i += 20) {
165
- const batch = texts.slice(i, i + 20);
166
- const resp = await fetch(EMBEDDING_URL, {
167
- method: 'POST',
168
- headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${EMBEDDING_TOKEN}` },
169
- body: JSON.stringify({ model: EMBEDDING_MODEL, input: batch })
170
- });
171
- if (!resp.ok) throw new Error(`Embedding API error: ${resp.status}`);
172
- const data = await resp.json();
173
- for (const item of data.data) results.push(new Float32Array(item.embedding));
174
- }
175
- return results;
176
- }
177
-
178
- function cosineSimilarity(a, b) {
179
- let dot = 0;
180
- for (let i = 0; i < a.length; i++) dot += a[i] * b[i];
181
- return dot;
182
- }
183
-
184
- // ========== CHUNKING ==========
185
-
186
- function chunkMarkdown(text) {
187
- const lines = text.split('\n');
188
- const chunks = [];
189
- const MAX_CHARS = 2000;
190
-
191
- // Build paragraphs
192
- let paragraphs = [];
193
- let currentPara = [];
194
- let paraStart = 0;
195
-
196
- for (let i = 0; i < lines.length; i++) {
197
- if (lines[i].trim() === '' && currentPara.length > 0) {
198
- paragraphs.push({ text: currentPara.join('\n'), startLine: paraStart + 1, endLine: i });
199
- currentPara = [];
200
- paraStart = i + 1;
201
- } else {
202
- if (currentPara.length === 0) paraStart = i;
203
- currentPara.push(lines[i]);
204
- }
205
- }
206
- if (currentPara.length > 0) {
207
- paragraphs.push({ text: currentPara.join('\n'), startLine: paraStart + 1, endLine: lines.length });
208
- }
209
-
210
- // Group into chunks
211
- let chunk = { texts: [], startLine: 0, endLine: 0, charCount: 0 };
212
-
213
- for (const para of paragraphs) {
214
- if (para.text.length > MAX_CHARS) {
215
- if (chunk.texts.length > 0) {
216
- chunks.push({ text: chunk.texts.join('\n\n'), startLine: chunk.startLine, endLine: chunk.endLine });
217
- chunk = { texts: [], startLine: 0, endLine: 0, charCount: 0 };
218
- }
219
- const sentences = para.text.match(/[^.!?\n]+[.!?\n]+|[^.!?\n]+$/g) || [para.text];
220
- let sentChunk = [], sentCharCount = 0;
221
- for (const sent of sentences) {
222
- if (sentCharCount + sent.length > MAX_CHARS && sentChunk.length > 0) {
223
- chunks.push({ text: sentChunk.join(' '), startLine: para.startLine, endLine: para.endLine });
224
- sentChunk = sentChunk.slice(-2);
225
- sentCharCount = sentChunk.join(' ').length;
226
- }
227
- sentChunk.push(sent.trim());
228
- sentCharCount += sent.length;
229
- }
230
- if (sentChunk.length > 0) {
231
- chunks.push({ text: sentChunk.join(' '), startLine: para.startLine, endLine: para.endLine });
232
- }
233
- } else {
234
- if (chunk.charCount + para.text.length > MAX_CHARS && chunk.texts.length > 0) {
235
- chunks.push({ text: chunk.texts.join('\n\n'), startLine: chunk.startLine, endLine: chunk.endLine });
236
- const lastText = chunk.texts[chunk.texts.length - 1];
237
- chunk = { texts: [lastText], startLine: chunk.startLine, endLine: chunk.endLine, charCount: lastText.length };
238
- }
239
- if (chunk.texts.length === 0) chunk.startLine = para.startLine;
240
- chunk.texts.push(para.text);
241
- chunk.endLine = para.endLine;
242
- chunk.charCount += para.text.length;
243
- }
244
- }
245
- if (chunk.texts.length > 0) {
246
- chunks.push({ text: chunk.texts.join('\n\n'), startLine: chunk.startLine, endLine: chunk.endLine });
247
- }
248
-
249
- return chunks;
250
- }
251
-
252
- // ========== INDEXING ==========
253
-
254
- async function indexFile(filePath) {
255
- if (!db) return 0;
256
- try {
257
- if (!fs.existsSync(filePath)) return 0;
258
- const stat = fs.statSync(filePath);
259
- const mtime = stat.mtimeMs;
260
-
261
- const existing = db.prepare('SELECT file_mtime FROM chunks WHERE file_path = ? LIMIT 1').get(filePath);
262
- if (existing && existing.file_mtime === mtime) return 0;
263
-
264
- const text = fs.readFileSync(filePath, 'utf8');
265
- if (!text.trim()) return 0;
266
-
267
- const chunks = chunkMarkdown(text);
268
- if (chunks.length === 0) return 0;
269
-
270
- // Try embeddings, fall back to null
271
- let embeddings;
272
- try {
273
- embeddings = await getEmbeddingsBatch(chunks.map(c => c.text));
274
- } catch (e) {
275
- embeddings = chunks.map(() => null);
276
- }
277
-
278
- db.prepare('DELETE FROM chunks WHERE file_path = ?').run(filePath);
279
-
280
- const insert = db.prepare('INSERT INTO chunks (file_path, file_mtime, chunk_index, start_line, end_line, text, embedding) VALUES (?, ?, ?, ?, ?, ?, ?)');
281
- const insertMany = db.transaction((items) => { for (const item of items) insert.run(...item); });
282
-
283
- insertMany(chunks.map((c, i) => [
284
- filePath, mtime, i, c.startLine, c.endLine, c.text,
285
- embeddings[i] ? Buffer.from(embeddings[i].buffer) : null
286
- ]));
287
-
288
- return chunks.length;
289
- } catch (e) {
290
- console.error(`[knowledge-base] Error indexing ${filePath}:`, e.message);
291
- return 0;
292
- }
293
- }
294
-
295
- function findMdFiles(dir) {
296
- const files = [];
297
- if (!fs.existsSync(dir)) return files;
298
- const stat = fs.statSync(dir);
299
- if (stat.isFile() && dir.endsWith('.md')) return [dir];
300
- if (!stat.isDirectory()) return files;
301
- for (const entry of fs.readdirSync(dir)) {
302
- if (entry.startsWith('.') || entry === 'node_modules') continue;
303
- const full = path.join(dir, entry);
304
- const s = fs.statSync(full);
305
- if (s.isDirectory()) files.push(...findMdFiles(full));
306
- else if (entry.endsWith('.md')) files.push(full);
307
- }
308
- return files;
309
- }
310
-
311
- async function indexAllFiles() {
312
- if (!db) return { filesIndexed: 0, chunksCreated: 0 };
313
- let filesIndexed = 0, chunksCreated = 0;
314
- const allFiles = [];
315
- for (const wp of WATCHED_PATHS) allFiles.push(...findMdFiles(wp));
316
- for (const f of allFiles) {
317
- const n = await indexFile(f);
318
- if (n > 0) { filesIndexed++; chunksCreated += n; }
319
- }
320
- lastIndexedAt = new Date().toISOString();
321
- console.log(`[knowledge-base] Indexed ${filesIndexed} files, ${chunksCreated} chunks`);
322
- return { filesIndexed, chunksCreated };
323
- }
324
-
325
- // ========== SEARCH ==========
326
-
327
- async function search(query, limit = 5) {
328
- if (!db) return [];
329
- const allChunks = db.prepare('SELECT id, file_path, start_line, end_line, text, embedding FROM chunks').all();
330
- if (allChunks.length === 0) return [];
331
-
332
- // Check if we have embeddings and an embedding API
333
- const hasEmbeddings = allChunks.some(c => c.embedding != null);
334
-
335
- if (hasEmbeddings && EMBEDDING_URL) {
336
- try {
337
- const queryEmb = await getEmbedding(query);
338
- if (queryEmb) {
339
- const scored = allChunks.filter(r => r.embedding).map(row => {
340
- const emb = new Float32Array(new Uint8Array(row.embedding).buffer);
341
- return { filePath: row.file_path, startLine: row.start_line, endLine: row.end_line, text: row.text, score: cosineSimilarity(queryEmb, emb) };
342
- });
343
- scored.sort((a, b) => b.score - a.score);
344
- return scored.slice(0, limit);
345
- }
346
- } catch (e) {
347
- console.error('[knowledge-base] Embedding search failed, falling back to BM25:', e.message);
348
- }
349
- }
350
-
351
- // BM25 fallback
352
- return bm25Search(query, allChunks, limit);
353
- }
354
-
355
- // ========== STATUS ==========
356
-
357
- function getStatus() {
358
- if (!db) return { totalFiles: 0, totalChunks: 0, lastIndexedAt: null, watchedPaths: WATCHED_PATHS, searchMode: 'offline' };
359
- const totalChunks = db.prepare('SELECT COUNT(*) as c FROM chunks').get().c;
360
- const totalFiles = db.prepare('SELECT COUNT(DISTINCT file_path) as c FROM chunks').get().c;
361
- const hasEmbeddings = db.prepare('SELECT COUNT(*) as c FROM chunks WHERE embedding IS NOT NULL').get().c > 0;
362
- return {
363
- totalFiles, totalChunks, lastIndexedAt, watchedPaths: WATCHED_PATHS,
364
- searchMode: hasEmbeddings && EMBEDDING_URL ? 'vector' : 'bm25'
365
- };
366
- }
367
-
368
- // ========== FILE WATCHER ==========
369
-
370
- function startWatcher() {
371
- for (const wp of WATCHED_PATHS) {
372
- if (!fs.existsSync(wp)) continue;
373
- try {
374
- const isDir = fs.statSync(wp).isDirectory();
375
- const watcher = fs.watch(wp, { recursive: isDir }, (eventType, filename) => {
376
- if (!filename) return;
377
- if (!filename.endsWith('.md')) return;
378
- const fullPath = isDir ? path.join(wp, filename) : wp;
379
-
380
- if (debounceTimers[fullPath]) clearTimeout(debounceTimers[fullPath]);
381
- debounceTimers[fullPath] = setTimeout(async () => {
382
- delete debounceTimers[fullPath];
383
- if (fs.existsSync(fullPath)) {
384
- const n = await indexFile(fullPath);
385
- if (n > 0) console.log(`[knowledge-base] Re-indexed ${path.basename(fullPath)} (${n} chunks)`);
386
- }
387
- }, 2000);
388
- });
389
- watchers.push(watcher);
390
- } catch (e) {
391
- console.error(`[knowledge-base] Could not watch ${wp}:`, e.message);
392
- }
393
- }
394
- console.log(`[knowledge-base] Watching ${watchers.length} paths`);
395
- }
396
-
397
- // ========== ROUTES ==========
398
-
399
- function setupRoutes(app) {
400
- app.get('/api/knowledge/search', async (req, res) => {
401
- try {
402
- const q = req.query.q;
403
- if (!q) return res.status(400).json({ error: 'Missing q parameter' });
404
- const limit = parseInt(req.query.limit) || 5;
405
- const results = await search(q, limit);
406
- res.json({ results });
407
- } catch (e) {
408
- res.status(500).json({ error: e.message });
409
- }
410
- });
411
-
412
- app.post('/api/knowledge/reindex', async (req, res) => {
413
- try {
414
- db.prepare('DELETE FROM chunks').run();
415
- const result = await indexAllFiles();
416
- res.json({ success: true, ...result });
417
- } catch (e) {
418
- res.status(500).json({ error: e.message });
419
- }
420
- });
421
-
422
- app.get('/api/knowledge/status', (req, res) => {
423
- res.json(getStatus());
424
- });
425
- }
426
-
427
- // Index arbitrary text (e.g., voice note transcripts) into the knowledge base
428
- function indexText(id, text, metadata = {}) {
429
- if (!db) initKnowledgeBase();
430
- const filePath = id; // e.g. "voice-note:uuid"
431
- const mtime = Date.now();
432
-
433
- // Delete existing chunks for this id
434
- db.prepare('DELETE FROM chunks WHERE file_path = ?').run(filePath);
435
-
436
- // Split text into chunks
437
- const lines = text.split('\n');
438
- const chunkSize = 500; // chars per chunk
439
- let chunks = [];
440
- let current = '';
441
- let startLine = 1;
442
- let lineNum = 1;
443
-
444
- for (const line of lines) {
445
- if (current.length + line.length > chunkSize && current.length > 0) {
446
- chunks.push({ text: current.trim(), startLine, endLine: lineNum - 1 });
447
- current = '';
448
- startLine = lineNum;
449
- }
450
- current += line + '\n';
451
- lineNum++;
452
- }
453
- if (current.trim()) {
454
- chunks.push({ text: current.trim(), startLine, endLine: lineNum - 1 });
455
- }
456
-
457
- // Prepend metadata to first chunk for context
458
- if (chunks.length > 0 && metadata.title) {
459
- chunks[0].text = `[Voice Note: ${metadata.title}] ${metadata.tags?.length ? 'Tags: ' + metadata.tags.join(', ') + '. ' : ''}${chunks[0].text}`;
460
- }
461
-
462
- const insert = db.prepare('INSERT INTO chunks (file_path, file_mtime, chunk_index, start_line, end_line, text, embedding) VALUES (?, ?, ?, ?, ?, ?, ?)');
463
- const emptyEmbedding = Buffer.alloc(0);
464
- chunks.forEach((chunk, i) => {
465
- insert.run(filePath, mtime, i, chunk.startLine, chunk.endLine, chunk.text, emptyEmbedding);
466
- });
467
-
468
- console.log(`[knowledge-base] Indexed text "${id}": ${chunks.length} chunks`);
469
- return { chunks: chunks.length };
470
- }
471
-
472
- module.exports = { initKnowledgeBase, indexAllFiles, search, getStatus, startWatcher, setupRoutes, indexText };
package/lib/cli.js DELETED
@@ -1,19 +0,0 @@
1
- 'use strict';
2
-
3
- const { execSync } = require('child_process');
4
- const pc = require('picocolors');
5
-
6
- function update() {
7
- console.log(pc.cyan('Checking for updates...'));
8
- try {
9
- execSync('npm update -g @conversionpros/aiva', { stdio: 'inherit' });
10
- console.log(pc.green('\nUpdate complete. Restarting AIVA...'));
11
- const { restartServer } = require('./process');
12
- restartServer();
13
- } catch (e) {
14
- console.error(pc.red('Update failed:'), e.message);
15
- process.exit(1);
16
- }
17
- }
18
-
19
- module.exports = { update };
package/lib/server.js DELETED
@@ -1,42 +0,0 @@
1
- 'use strict';
2
-
3
- // AIVA Server - CLI adapter
4
- // This wraps the main server.js, setting up environment variables
5
- // so that it resolves paths from ~/.aiva/ instead of hardcoded locations.
6
-
7
- const path = require('path');
8
- const fs = require('fs');
9
-
10
- // Determine AIVA home - either from env or default ~/.aiva/
11
- const AIVA_HOME = process.env.AIVA_HOME || path.join(require('os').homedir(), '.aiva');
12
- const configPath = process.env.AIVA_CONFIG_PATH || path.join(AIVA_HOME, 'config.json');
13
-
14
- // Load config
15
- let config = {};
16
- try {
17
- config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
18
- } catch (e) {
19
- console.warn('[aiva] No config found at', configPath, '- using defaults');
20
- }
21
-
22
- // Set environment variables that the server reads
23
- process.env.PORT = process.env.PORT || String(config.port || 3847);
24
- process.env.AIVA_HOME = AIVA_HOME;
25
- process.env.AIVA_DATA_DIR = process.env.AIVA_DATA_DIR || path.join(AIVA_HOME, 'data');
26
- process.env.AIVA_UPLOADS_DIR = process.env.AIVA_UPLOADS_DIR || path.join(AIVA_HOME, 'uploads');
27
- process.env.AIVA_LOGS_DIR = process.env.AIVA_LOGS_DIR || path.join(AIVA_HOME, 'logs');
28
-
29
- // Ensure data directories exist
30
- [process.env.AIVA_DATA_DIR, process.env.AIVA_UPLOADS_DIR, process.env.AIVA_LOGS_DIR].forEach(dir => {
31
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
32
- });
33
-
34
- // Load .env from AIVA_HOME if it exists (user-specific environment)
35
- const envPath = path.join(AIVA_HOME, '.env');
36
- if (fs.existsSync(envPath)) {
37
- require('dotenv').config({ path: envPath });
38
- }
39
-
40
- // Now load the actual server - it lives alongside this file in the package
41
- const serverPath = path.join(__dirname, '..', 'server.js');
42
- require(serverPath);