@conversionpros/aiva 1.0.1 → 2.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.
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 +9 -34
  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
@@ -1,301 +0,0 @@
1
- // ── Scheduler - Calendar Booking / Reschedule / Cancel ───
2
- // Invoked as a tool call from the Conversation Engine.
3
- 'use strict';
4
-
5
- const { getStmts, getSetting } = require('./db');
6
-
7
- const CALENDAR_BASE = 'http://localhost:3847/api/integrations/google/calendars';
8
-
9
- function log(msg, data) {
10
- const ts = new Date().toISOString();
11
- if (data) console.log(`[${ts}] [SCHEDULER] ${msg}`, JSON.stringify(data));
12
- else console.log(`[${ts}] [SCHEDULER] ${msg}`);
13
- }
14
-
15
- /**
16
- * Get scheduling rule for a contact category.
17
- * @param {string} category
18
- * @returns {Object}
19
- */
20
- function getSchedulingRule(category) {
21
- const rule = getStmts().getSchedulingRule.get(category);
22
- return rule || { category, rule_preset: 'flexible', custom_instructions: '', structured_overrides: '{}' };
23
- }
24
-
25
- /**
26
- * Check calendar availability for a given time range.
27
- * @param {string} timeMin - ISO datetime start
28
- * @param {string} timeMax - ISO datetime end
29
- * @param {string} [calendarId='primary'] - Calendar ID
30
- * @param {string} [accountId] - Google account ID
31
- * @returns {Promise<Array>} List of existing events (conflicts)
32
- */
33
- async function checkAvailability(timeMin, timeMax, calendarId = 'primary', accountId = null) {
34
- try {
35
- let url = `${CALENDAR_BASE}/${encodeURIComponent(calendarId)}/events?timeMin=${encodeURIComponent(timeMin)}&timeMax=${encodeURIComponent(timeMax)}`;
36
- if (accountId) url += `&accountId=${accountId}`;
37
-
38
- const resp = await fetch(url, { signal: AbortSignal.timeout(15000) });
39
- if (!resp.ok) {
40
- log('Calendar API error', { status: resp.status });
41
- return { error: `calendar_api_${resp.status}`, events: null };
42
- }
43
-
44
- const data = await resp.json();
45
- return { error: null, events: data.events || data.items || data || [] };
46
- } catch (err) {
47
- log('Calendar check failed', { error: err.message });
48
- return { error: err.message, events: null };
49
- }
50
- }
51
-
52
- /**
53
- * Book an appointment on the calendar.
54
- * @param {Object} event
55
- * @param {string} event.summary - Event title
56
- * @param {string} event.startTime - ISO datetime
57
- * @param {string} event.endTime - ISO datetime
58
- * @param {string} [event.description] - Event description
59
- * @param {string} [event.attendeeEmail] - Attendee email
60
- * @param {string} [event.calendarId='primary']
61
- * @param {string} [event.accountId]
62
- * @returns {Promise<{success: boolean, event?: Object, error?: string}>}
63
- */
64
- async function bookAppointment(event) {
65
- const calendarId = event.calendarId || 'primary';
66
- const accountId = event.accountId || null;
67
-
68
- // Check for conflicts first
69
- const availCheck = await checkAvailability(event.startTime, event.endTime, calendarId, accountId);
70
- if (availCheck.error) {
71
- log('Cannot verify availability', { error: availCheck.error });
72
- return { success: false, error: `Cannot verify calendar availability: ${availCheck.error}. Please try again.` };
73
- }
74
- if (availCheck.events.length > 0) {
75
- const conflictNames = availCheck.events.map(c => c.summary || 'Busy').join(', ');
76
- log('Booking conflict', { time: event.startTime, conflicts: conflictNames });
77
- return {
78
- success: false,
79
- error: `Time slot has conflicts: ${conflictNames}`,
80
- conflicts: availCheck.events,
81
- };
82
- }
83
-
84
- try {
85
- let url = `${CALENDAR_BASE}/${encodeURIComponent(calendarId)}/events`;
86
- if (accountId) url += `?accountId=${accountId}`;
87
-
88
- const body = {
89
- summary: event.summary,
90
- start: { dateTime: event.startTime },
91
- end: { dateTime: event.endTime },
92
- };
93
- if (event.description) body.description = event.description;
94
- if (event.attendeeEmail) body.attendees = [{ email: event.attendeeEmail }];
95
-
96
- const resp = await fetch(url, {
97
- method: 'POST',
98
- headers: { 'Content-Type': 'application/json' },
99
- body: JSON.stringify(body),
100
- signal: AbortSignal.timeout(15000),
101
- });
102
-
103
- if (!resp.ok) {
104
- const errText = await resp.text().catch(() => 'unknown');
105
- log('Booking failed', { status: resp.status, error: errText.substring(0, 200) });
106
- return { success: false, error: `Calendar API error: ${resp.status}` };
107
- }
108
-
109
- const created = await resp.json();
110
- log('Booking success', { summary: event.summary, start: event.startTime });
111
- return { success: true, event: created };
112
- } catch (err) {
113
- log('Booking error', { error: err.message });
114
- return { success: false, error: err.message };
115
- }
116
- }
117
-
118
- /**
119
- * Find available slots in a date range.
120
- * @param {string} dateStr - Date string (e.g. "2026-02-27")
121
- * @param {number} [durationMin=30] - Meeting duration in minutes
122
- * @param {string} [calendarId='primary']
123
- * @param {string} [accountId]
124
- * @returns {Promise<Array<{start: string, end: string}>>}
125
- */
126
- async function findAvailableSlots(dateStr, durationMin = 30, calendarId = 'primary', accountId = null) {
127
- // Use Intl to get the correct PST/PDT offset for the given date
128
- const formatter = new Intl.DateTimeFormat('en-US', { timeZone: 'America/Los_Angeles', timeZoneName: 'shortOffset' });
129
- const refDate = new Date(dateStr + 'T12:00:00Z');
130
- const parts = formatter.formatToParts(refDate);
131
- const tzPart = parts.find(p => p.type === 'timeZoneName')?.value || 'GMT-8';
132
- const offsetMatch = tzPart.match(/GMT([+-]\d+)/);
133
- const offsetHours = offsetMatch ? parseInt(offsetMatch[1]) : -8;
134
- const offsetStr = `${offsetHours >= 0 ? '+' : ''}${String(Math.abs(offsetHours)).padStart(2, '0')}:00`;
135
-
136
- const dayStart = new Date(`${dateStr}T09:00:00${offsetStr}`);
137
- const dayEnd = new Date(`${dateStr}T17:00:00${offsetStr}`);
138
-
139
- const availCheck = await checkAvailability(dayStart.toISOString(), dayEnd.toISOString(), calendarId, accountId);
140
- if (availCheck.error) return [];
141
- const events = availCheck.events;
142
-
143
- // Build busy blocks
144
- const busy = events.map(e => ({
145
- start: new Date(e.start?.dateTime || e.start).getTime(),
146
- end: new Date(e.end?.dateTime || e.end).getTime(),
147
- })).sort((a, b) => a.start - b.start);
148
-
149
- // Find gaps
150
- const slots = [];
151
- const durationMs = durationMin * 60000;
152
- let cursor = dayStart.getTime();
153
-
154
- for (const block of busy) {
155
- if (block.start - cursor >= durationMs) {
156
- slots.push({
157
- start: new Date(cursor).toISOString(),
158
- end: new Date(cursor + durationMs).toISOString(),
159
- });
160
- }
161
- cursor = Math.max(cursor, block.end);
162
- }
163
-
164
- // Check remaining time after last event
165
- if (dayEnd.getTime() - cursor >= durationMs) {
166
- slots.push({
167
- start: new Date(cursor).toISOString(),
168
- end: new Date(cursor + durationMs).toISOString(),
169
- });
170
- }
171
-
172
- return slots;
173
- }
174
-
175
- /**
176
- * Reschedule an existing event.
177
- * @param {string} eventId - Event ID to reschedule
178
- * @param {string} newStart - New ISO start time
179
- * @param {string} newEnd - New ISO end time
180
- * @param {string} [calendarId='primary']
181
- * @param {string} [accountId]
182
- * @returns {Promise<{success: boolean, error?: string}>}
183
- */
184
- async function rescheduleAppointment(eventId, newStart, newEnd, calendarId = 'primary', accountId = null) {
185
- // Check for conflicts at new time
186
- const availCheck = await checkAvailability(newStart, newEnd, calendarId, accountId);
187
- if (availCheck.error) {
188
- return { success: false, error: `Cannot verify availability: ${availCheck.error}` };
189
- }
190
- if (availCheck.events.length > 0) {
191
- return { success: false, error: 'New time slot has conflicts', conflicts: availCheck.events };
192
- }
193
-
194
- try {
195
- let url = `${CALENDAR_BASE}/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(eventId)}`;
196
- if (accountId) url += `?accountId=${accountId}`;
197
-
198
- const resp = await fetch(url, {
199
- method: 'PUT',
200
- headers: { 'Content-Type': 'application/json' },
201
- body: JSON.stringify({
202
- start: { dateTime: newStart },
203
- end: { dateTime: newEnd },
204
- }),
205
- signal: AbortSignal.timeout(15000),
206
- });
207
-
208
- if (!resp.ok) return { success: false, error: `Calendar API error: ${resp.status}` };
209
- log('Rescheduled', { eventId, newStart });
210
- return { success: true };
211
- } catch (err) {
212
- return { success: false, error: err.message };
213
- }
214
- }
215
-
216
- /**
217
- * Cancel an existing event.
218
- * @param {string} eventId
219
- * @param {string} [calendarId='primary']
220
- * @param {string} [accountId]
221
- * @returns {Promise<{success: boolean, error?: string}>}
222
- */
223
- async function cancelAppointment(eventId, calendarId = 'primary', accountId = null) {
224
- try {
225
- let url = `${CALENDAR_BASE}/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(eventId)}`;
226
- if (accountId) url += `?accountId=${accountId}`;
227
-
228
- const resp = await fetch(url, {
229
- method: 'DELETE',
230
- signal: AbortSignal.timeout(15000),
231
- });
232
-
233
- if (!resp.ok) return { success: false, error: `Calendar API error: ${resp.status}` };
234
- log('Cancelled', { eventId });
235
- return { success: true };
236
- } catch (err) {
237
- return { success: false, error: err.message };
238
- }
239
- }
240
-
241
- /**
242
- * Handle a schedule_appointment tool call from the Conversation Engine.
243
- * @param {Object} args - Tool call arguments from Sonnet
244
- * @param {Object} contact - Contact record
245
- * @returns {Promise<Object>} Result to feed back into conversation
246
- */
247
- async function handleToolCall(args, contact) {
248
- const action = args.action || 'book';
249
- const accountId = args.accountId || getSetting('calendarAccountId') || null;
250
- const calendarId = args.calendarId || 'primary';
251
-
252
- switch (action) {
253
- case 'check_availability': {
254
- const slots = await findAvailableSlots(args.date, args.duration || 30, calendarId, accountId);
255
- if (slots.length === 0) {
256
- return { success: false, message: `No available ${args.duration || 30}-minute slots on ${args.date}.` };
257
- }
258
- return {
259
- success: true,
260
- message: `Found ${slots.length} available slot(s) on ${args.date}.`,
261
- slots: slots.slice(0, 3),
262
- };
263
- }
264
-
265
- case 'book': {
266
- const result = await bookAppointment({
267
- summary: args.summary || `Meeting with ${contact.name}`,
268
- startTime: args.startTime,
269
- endTime: args.endTime,
270
- description: args.description || `Booked by AIVA for ${contact.name} (${contact.phone})`,
271
- attendeeEmail: args.attendeeEmail || null,
272
- calendarId,
273
- accountId,
274
- });
275
- return result;
276
- }
277
-
278
- case 'reschedule': {
279
- const result = await rescheduleAppointment(args.eventId, args.newStart, args.newEnd, calendarId, accountId);
280
- return result;
281
- }
282
-
283
- case 'cancel': {
284
- const result = await cancelAppointment(args.eventId, calendarId, accountId);
285
- return result;
286
- }
287
-
288
- default:
289
- return { success: false, error: `Unknown scheduling action: ${action}` };
290
- }
291
- }
292
-
293
- module.exports = {
294
- getSchedulingRule,
295
- checkAvailability,
296
- bookAppointment,
297
- findAvailableSlots,
298
- rescheduleAppointment,
299
- cancelAppointment,
300
- handleToolCall,
301
- };
@@ -1,215 +0,0 @@
1
- #!/usr/bin/env node
2
- // ── Migrate v1 Database to v2 ────────────────────────────
3
- // Reads from v1 (data/aiva.db or message-router/router.db), writes to v2.
4
- // Safe to run multiple times (idempotent). Does NOT modify v1 database.
5
- 'use strict';
6
-
7
- const path = require('path');
8
- const fs = require('fs');
9
- const Database = require('better-sqlite3');
10
-
11
- const V1_PATHS = [
12
- path.join(process.env.HOME, '.openclaw', 'workspace', 'message-router', 'router.db'),
13
- path.join(__dirname, '..', '..', 'data', 'aiva.db'),
14
- ];
15
-
16
- const V2_DIR = path.join(__dirname, '..', 'data');
17
- const V2_PATH = path.join(V2_DIR, 'router-v2.db');
18
-
19
- function log(msg) {
20
- console.log(`[MIGRATE] ${msg}`);
21
- }
22
-
23
- function findV1Db() {
24
- for (const p of V1_PATHS) {
25
- if (fs.existsSync(p)) {
26
- log(`Found v1 DB at: ${p}`);
27
- return p;
28
- }
29
- }
30
- return null;
31
- }
32
-
33
- function run() {
34
- const v1Path = findV1Db();
35
- if (!v1Path) {
36
- log('No v1 database found. Nothing to migrate.');
37
- return;
38
- }
39
-
40
- // Initialize v2 DB (this creates tables)
41
- const { initDatabase } = require('../db');
42
- const { db: v2 } = initDatabase();
43
-
44
- const v1 = new Database(v1Path, { readonly: true });
45
- v1.pragma('journal_mode = WAL');
46
-
47
- let migrated = { contacts: 0, context: 0, scopes: 0, messages: 0, settings: 0, followUps: 0, schedulingRules: 0 };
48
-
49
- // ── Migrate contacts (contact_rules -> contacts) ──
50
- try {
51
- const contacts = v1.prepare('SELECT * FROM contact_rules').all();
52
- const upsert = v2.prepare(`
53
- INSERT INTO contacts (phone, name, category, response_mode, style, instructions, source, introduced, qualification_score, pipeline_stage, created_at, updated_at)
54
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, 'none', ?, ?)
55
- ON CONFLICT(phone) DO NOTHING
56
- `);
57
-
58
- for (const c of contacts) {
59
- if (!c.phone) continue;
60
- upsert.run(
61
- c.phone, c.name || 'Unknown', c.category || 'unknown',
62
- c.response_mode || 'auto', c.style || 'casual',
63
- c.instructions || '', c.source || 'unknown',
64
- c.aiva_introduced || 0,
65
- c.created_at || new Date().toISOString(),
66
- c.updated_at || new Date().toISOString(),
67
- );
68
- migrated.contacts++;
69
- }
70
- log(`Migrated ${migrated.contacts} contacts`);
71
- } catch (e) {
72
- log(`Contacts migration error: ${e.message}`);
73
- }
74
-
75
- // ── Migrate contact_context ──
76
- try {
77
- const contexts = v1.prepare('SELECT * FROM contact_context').all();
78
- const upsert = v2.prepare(`
79
- INSERT INTO contact_context (phone, relationship, last_topic, pending_items, conversation_summary, preferences, last_interaction)
80
- VALUES (?, ?, ?, ?, ?, ?, ?)
81
- ON CONFLICT(phone) DO NOTHING
82
- `);
83
-
84
- for (const ctx of contexts) {
85
- if (!ctx.phone) continue;
86
- upsert.run(
87
- ctx.phone, ctx.relationship || '', ctx.last_topic || '',
88
- ctx.pending_items || '[]', ctx.conversation_summary || '',
89
- ctx.preferences_learned || '{}', ctx.last_interaction || null,
90
- );
91
- migrated.context++;
92
- }
93
- log(`Migrated ${migrated.context} contact contexts`);
94
- } catch (e) {
95
- log(`Context migration error: ${e.message}`);
96
- }
97
-
98
- // ── Migrate contact_scopes ──
99
- try {
100
- const scopes = v1.prepare('SELECT * FROM contact_scopes').all();
101
- const upsert = v2.prepare(`
102
- INSERT INTO contact_scopes (phone, scope, granted, granted_by, granted_at)
103
- VALUES (?, ?, ?, ?, ?)
104
- ON CONFLICT(phone, scope) DO NOTHING
105
- `);
106
-
107
- for (const s of scopes) {
108
- upsert.run(s.phone, s.scope, s.granted || 0, s.granted_by || 'migrated', s.granted_at || null);
109
- migrated.scopes++;
110
- }
111
- log(`Migrated ${migrated.scopes} scopes`);
112
- } catch (e) {
113
- log(`Scopes migration error: ${e.message}`);
114
- }
115
-
116
- // ── Migrate message_log ──
117
- try {
118
- const messages = v1.prepare('SELECT * FROM message_log ORDER BY id DESC LIMIT 5000').all();
119
- const insert = v2.prepare(`
120
- INSERT INTO message_log (phone, channel, direction, text, attachments, sent_by, state_at_time, created_at)
121
- VALUES (?, ?, ?, ?, '[]', ?, ?, ?)
122
- `);
123
-
124
- // Check if we already have messages to avoid re-importing
125
- const existingCount = v2.prepare('SELECT COUNT(*) as c FROM message_log').get().c;
126
- if (existingCount === 0) {
127
- for (const m of messages.reverse()) {
128
- const channel = (m.source || '').includes('whatsapp') ? 'whatsapp' : 'imessage';
129
- const sentBy = m.sent_by || (m.direction === 'outbound' ? 'aiva' : 'contact');
130
- insert.run(
131
- m.phone, channel, m.direction || 'inbound',
132
- m.message_preview || '', sentBy,
133
- JSON.stringify(m.rules_applied || '{}'),
134
- m.timestamp || new Date().toISOString(),
135
- );
136
- migrated.messages++;
137
- }
138
- log(`Migrated ${migrated.messages} messages`);
139
- } else {
140
- log(`Skipping message migration - v2 already has ${existingCount} messages`);
141
- }
142
- } catch (e) {
143
- log(`Message log migration error: ${e.message}`);
144
- }
145
-
146
- // ── Migrate settings ──
147
- try {
148
- const settings = v1.prepare('SELECT * FROM settings').all();
149
- const upsert = v2.prepare('INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)');
150
-
151
- for (const s of settings) {
152
- // Skip v1-only settings
153
- if (['scopes_migrated'].includes(s.key)) continue;
154
- upsert.run(s.key, s.value);
155
- migrated.settings++;
156
- }
157
- log(`Migrated ${migrated.settings} settings`);
158
- } catch (e) {
159
- log(`Settings migration error: ${e.message}`);
160
- }
161
-
162
- // ── Migrate follow_up_tracker ──
163
- try {
164
- const followUps = v1.prepare('SELECT * FROM follow_up_tracker').all();
165
- const upsert = v2.prepare(`
166
- INSERT INTO follow_up_tracker (phone, channel, contact_name, last_our_message, last_our_message_at, status, follow_up_count, max_follow_ups, next_follow_up_at, last_follow_up_at, last_follow_up_topic, opted_out, created_at, updated_at)
167
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
168
- ON CONFLICT(phone) DO NOTHING
169
- `);
170
-
171
- for (const fu of followUps) {
172
- upsert.run(
173
- fu.phone, fu.channel || 'imessage', fu.contact_name || 'Unknown',
174
- fu.last_our_message || '', fu.last_our_message_at,
175
- fu.status || 'active', fu.follow_up_count || 0,
176
- fu.max_follow_ups || 3, fu.next_follow_up_at,
177
- fu.last_follow_up_at, fu.last_follow_up_topic || '',
178
- fu.opted_out || 0, fu.created_at, fu.updated_at,
179
- );
180
- migrated.followUps++;
181
- }
182
- log(`Migrated ${migrated.followUps} follow-up trackers`);
183
- } catch (e) {
184
- log(`Follow-up migration error: ${e.message}`);
185
- }
186
-
187
- // ── Migrate scheduling_rules ──
188
- try {
189
- const rules = v1.prepare('SELECT * FROM scheduling_rules').all();
190
- const upsert = v2.prepare(`
191
- INSERT OR IGNORE INTO scheduling_rules (category, rule_preset, custom_instructions, structured_overrides)
192
- VALUES (?, ?, ?, ?)
193
- `);
194
-
195
- for (const r of rules) {
196
- upsert.run(r.category, r.rule_preset || 'flexible', r.custom_instructions || '', r.structured_overrides || '{}');
197
- migrated.schedulingRules++;
198
- }
199
- log(`Migrated ${migrated.schedulingRules} scheduling rules`);
200
- } catch (e) {
201
- log(`Scheduling rules migration error: ${e.message}`);
202
- }
203
-
204
- v1.close();
205
-
206
- log('Migration complete!');
207
- log(`Summary: ${JSON.stringify(migrated)}`);
208
- }
209
-
210
- // Run if called directly
211
- if (require.main === module) {
212
- run();
213
- }
214
-
215
- module.exports = { run };
@@ -1,67 +0,0 @@
1
- #!/usr/bin/env node
2
- // ── Seed Initial FAQ Entries ─────────────────────────────
3
- 'use strict';
4
-
5
- function run() {
6
- const { initDatabase } = require('../db');
7
- const { createFaq } = require('../knowledge-base');
8
-
9
- initDatabase();
10
-
11
- const entries = [
12
- {
13
- question: 'What services does Conversion Marketing Pros offer?',
14
- answer: 'We offer website design and development, paid advertising management (Google Ads, Meta Ads, LinkedIn), SEO, and marketing strategy consulting.',
15
- category: 'services',
16
- keywords: 'services, offer, do, help, work',
17
- },
18
- {
19
- question: 'What are your business hours?',
20
- answer: 'Our team is available Monday through Friday, 9 AM to 5 PM Pacific Time. We also respond to messages on Saturdays.',
21
- category: 'hours',
22
- keywords: 'hours, open, available, time, when',
23
- },
24
- {
25
- question: 'How much does a website cost?',
26
- answer: 'Website projects vary based on scope and complexity. We\'d love to understand your needs first - can we hop on a quick call to discuss?',
27
- category: 'pricing',
28
- keywords: 'cost, price, pricing, expensive, budget, website',
29
- },
30
- {
31
- question: 'How long does a website take to build?',
32
- answer: 'A typical website redesign takes 6-8 weeks from kickoff to launch. We can expedite for an additional fee if you have a tight deadline.',
33
- category: 'general',
34
- keywords: 'timeline, how long, weeks, time, build, launch',
35
- },
36
- {
37
- question: 'Where are you located?',
38
- answer: 'We\'re based in Spokane, Washington, but we work with clients across the US.',
39
- category: 'general',
40
- keywords: 'location, where, based, office, city',
41
- },
42
- {
43
- question: 'Do you offer free consultations?',
44
- answer: 'Yes! We offer a free 30-minute discovery call to learn about your goals and see if we\'re a good fit.',
45
- category: 'general',
46
- keywords: 'free, consultation, discovery, call, meeting',
47
- },
48
- ];
49
-
50
- let created = 0;
51
- for (const entry of entries) {
52
- try {
53
- createFaq(entry);
54
- created++;
55
- } catch (e) {
56
- console.log(`Skipped: ${entry.question.substring(0, 50)} - ${e.message}`);
57
- }
58
- }
59
-
60
- console.log(`[SEED-FAQ] Created ${created} FAQ entries`);
61
- }
62
-
63
- if (require.main === module) {
64
- run();
65
- }
66
-
67
- module.exports = { run };
@@ -1,39 +0,0 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- /**
5
- * Seed the FAQ entries from knowledge-base.json into the SQLite database.
6
- * Usage: node seed-knowledge-base.js [--clear]
7
- * --clear: Remove all existing FAQ entries before seeding
8
- */
9
-
10
- const path = require('path');
11
- const { listFaq, createFaq, deleteFaq } = require('./knowledge-base');
12
- const kbPath = path.join(__dirname, 'data', 'knowledge-base.json');
13
-
14
- // Force DB init
15
- const { initDatabase } = require('./db');
16
- initDatabase();
17
-
18
- const entries = require(kbPath);
19
- const doClear = process.argv.includes('--clear');
20
-
21
- if (doClear) {
22
- const existing = listFaq();
23
- console.log(`Clearing ${existing.length} existing FAQ entries...`);
24
- for (const e of existing) deleteFaq(e.id);
25
- }
26
-
27
- let created = 0;
28
- for (const entry of entries) {
29
- createFaq({
30
- device_id: null,
31
- question: entry.question,
32
- answer: entry.answer,
33
- category: entry.category,
34
- keywords: entry.keywords || '',
35
- });
36
- created++;
37
- }
38
-
39
- console.log(`Seeded ${created} FAQ entries from knowledge-base.json`);