@conversionpros/aiva 1.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 (152) hide show
  1. package/README.md +148 -0
  2. package/auto-deploy.js +190 -0
  3. package/bin/aiva.js +81 -0
  4. package/cli-sync.js +126 -0
  5. package/d2a-prompt-template.txt +106 -0
  6. package/diagnostics-api.js +304 -0
  7. package/docs/ara-dedup-fix-scope.md +112 -0
  8. package/docs/ara-fix-round2-scope.md +61 -0
  9. package/docs/ara-greeting-fix-scope.md +70 -0
  10. package/docs/calendar-date-fix-scope.md +28 -0
  11. package/docs/getting-started.md +115 -0
  12. package/docs/network-architecture-rollout-scope.md +43 -0
  13. package/docs/scope-google-oauth-integration.md +351 -0
  14. package/docs/settings-page-scope.md +50 -0
  15. package/docs/xai-imagine-scope.md +116 -0
  16. package/docs/xai-voice-integration-scope.md +115 -0
  17. package/docs/xai-voice-tools-scope.md +165 -0
  18. package/email-router.js +512 -0
  19. package/follow-up-handler.js +606 -0
  20. package/gateway-monitor.js +158 -0
  21. package/google-email.js +379 -0
  22. package/google-oauth.js +310 -0
  23. package/grok-imagine.js +97 -0
  24. package/health-reporter.js +287 -0
  25. package/invisible-prefix-base.txt +206 -0
  26. package/invisible-prefix-owner.txt +26 -0
  27. package/invisible-prefix-slim.txt +10 -0
  28. package/invisible-prefix.txt +43 -0
  29. package/knowledge-base.js +472 -0
  30. package/lib/cli.js +19 -0
  31. package/lib/config.js +124 -0
  32. package/lib/health.js +57 -0
  33. package/lib/process.js +207 -0
  34. package/lib/server.js +42 -0
  35. package/lib/setup.js +472 -0
  36. package/meta-capi.js +206 -0
  37. package/meta-leads.js +411 -0
  38. package/notion-oauth.js +323 -0
  39. package/package.json +61 -0
  40. package/public/agent-config.html +241 -0
  41. package/public/aiva-avatar-anime.png +0 -0
  42. package/public/css/docs.css.bak +688 -0
  43. package/public/css/onboarding.css +543 -0
  44. package/public/diagrams/claude-subscription-pool.html +329 -0
  45. package/public/diagrams/claude-subscription-pool.png +0 -0
  46. package/public/docs-icon.png +0 -0
  47. package/public/escalation.html +237 -0
  48. package/public/group-config.html +300 -0
  49. package/public/icon-192.png +0 -0
  50. package/public/icon-512.png +0 -0
  51. package/public/icons/agents.svg +1 -0
  52. package/public/icons/attach.svg +1 -0
  53. package/public/icons/characters.svg +1 -0
  54. package/public/icons/chat.svg +1 -0
  55. package/public/icons/docs.svg +1 -0
  56. package/public/icons/heartbeat.svg +1 -0
  57. package/public/icons/messages.svg +1 -0
  58. package/public/icons/mic.svg +1 -0
  59. package/public/icons/notes.svg +1 -0
  60. package/public/icons/settings.svg +1 -0
  61. package/public/icons/tasks.svg +1 -0
  62. package/public/images/onboarding/p0-communication-layer.png +0 -0
  63. package/public/images/onboarding/p0-infinite-surface.png +0 -0
  64. package/public/images/onboarding/p0-learning-model.png +0 -0
  65. package/public/images/onboarding/p0-meet-aiva.png +0 -0
  66. package/public/images/onboarding/p4-contact-intelligence.png +0 -0
  67. package/public/images/onboarding/p4-context-compounds.png +0 -0
  68. package/public/images/onboarding/p4-message-router.png +0 -0
  69. package/public/images/onboarding/p4-per-contact-rules.png +0 -0
  70. package/public/images/onboarding/p4-send-messages.png +0 -0
  71. package/public/images/onboarding/p6-be-precise.png +0 -0
  72. package/public/images/onboarding/p6-review-escalations.png +0 -0
  73. package/public/images/onboarding/p6-voice-input.png +0 -0
  74. package/public/images/onboarding/p7-completion.png +0 -0
  75. package/public/index.html +11594 -0
  76. package/public/js/onboarding.js +699 -0
  77. package/public/manifest.json +24 -0
  78. package/public/messages-v2.html +2824 -0
  79. package/public/permission-approve.html.bak +107 -0
  80. package/public/permissions.html +150 -0
  81. package/public/styles/design-system.css +68 -0
  82. package/router-db.js +604 -0
  83. package/router-utils.js +28 -0
  84. package/router-v2/adapters/imessage.js +191 -0
  85. package/router-v2/adapters/quo.js +82 -0
  86. package/router-v2/adapters/whatsapp.js +192 -0
  87. package/router-v2/contact-manager.js +234 -0
  88. package/router-v2/conversation-engine.js +498 -0
  89. package/router-v2/data/knowledge-base.json +176 -0
  90. package/router-v2/data/router-v2.db +0 -0
  91. package/router-v2/data/router-v2.db-shm +0 -0
  92. package/router-v2/data/router-v2.db-wal +0 -0
  93. package/router-v2/data/router.db +0 -0
  94. package/router-v2/db.js +457 -0
  95. package/router-v2/escalation-bridge.js +540 -0
  96. package/router-v2/follow-up-engine.js +347 -0
  97. package/router-v2/index.js +441 -0
  98. package/router-v2/ingestion.js +213 -0
  99. package/router-v2/knowledge-base.js +231 -0
  100. package/router-v2/lead-qualifier.js +152 -0
  101. package/router-v2/learning-loop.js +202 -0
  102. package/router-v2/outbound-sender.js +160 -0
  103. package/router-v2/package.json +13 -0
  104. package/router-v2/permission-gate.js +86 -0
  105. package/router-v2/playbook.js +177 -0
  106. package/router-v2/prompts/base.js +52 -0
  107. package/router-v2/prompts/first-contact.js +38 -0
  108. package/router-v2/prompts/lead-qualification.js +37 -0
  109. package/router-v2/prompts/scheduling.js +72 -0
  110. package/router-v2/prompts/style-overrides.js +22 -0
  111. package/router-v2/scheduler.js +301 -0
  112. package/router-v2/scripts/migrate-v1-to-v2.js +215 -0
  113. package/router-v2/scripts/seed-faq.js +67 -0
  114. package/router-v2/seed-knowledge-base.js +39 -0
  115. package/router-v2/utils/ai.js +129 -0
  116. package/router-v2/utils/phone.js +52 -0
  117. package/router-v2/utils/response-validator.js +98 -0
  118. package/router-v2/utils/sanitize.js +222 -0
  119. package/router.js +5005 -0
  120. package/routes/google-calendar.js +186 -0
  121. package/scripts/deploy.sh +62 -0
  122. package/scripts/macos-calendar.sh +232 -0
  123. package/scripts/onboard-device.sh +466 -0
  124. package/server.js +5131 -0
  125. package/start.sh +24 -0
  126. package/templates/AGENTS.md +548 -0
  127. package/templates/IDENTITY.md +15 -0
  128. package/templates/docs-agents.html +132 -0
  129. package/templates/docs-app.html +130 -0
  130. package/templates/docs-home.html +83 -0
  131. package/templates/docs-imessage.html +121 -0
  132. package/templates/docs-tasks.html +123 -0
  133. package/templates/docs-tips.html +175 -0
  134. package/templates/getting-started.html +809 -0
  135. package/templates/invisible-prefix-base.txt +171 -0
  136. package/templates/invisible-prefix-owner.txt +282 -0
  137. package/templates/invisible-prefix.txt +338 -0
  138. package/templates/manifest.json +61 -0
  139. package/templates/memory-org/clients.md +7 -0
  140. package/templates/memory-org/credentials.md +9 -0
  141. package/templates/memory-org/devices.md +7 -0
  142. package/templates/updates.html +464 -0
  143. package/templates/workspace/AGENTS.md.tmpl +161 -0
  144. package/templates/workspace/HEARTBEAT.md.tmpl +17 -0
  145. package/templates/workspace/IDENTITY.md.tmpl +15 -0
  146. package/templates/workspace/MEMORY.md.tmpl +16 -0
  147. package/templates/workspace/SOUL.md.tmpl +51 -0
  148. package/templates/workspace/USER.md.tmpl +25 -0
  149. package/tts-proxy.js +96 -0
  150. package/voice-call-local.js +731 -0
  151. package/voice-call.js +732 -0
  152. package/wa-listener.js +354 -0
package/lib/setup.js ADDED
@@ -0,0 +1,472 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const pc = require('picocolors');
6
+ const prompts = require('prompts');
7
+ const { saveConfig, ensureDirectories, getAivaHome, getWorkspaceDir, loadConfig } = require('./config');
8
+ const { writeEcosystem, ensurePm2, startServer } = require('./process');
9
+ const { checkHealth } = require('./health');
10
+
11
+ const TEMPLATES_DIR = path.join(__dirname, '..', 'templates', 'workspace');
12
+
13
+ function renderTemplate(templatePath, vars) {
14
+ let content = fs.readFileSync(templatePath, 'utf8');
15
+ for (const [key, value] of Object.entries(vars)) {
16
+ const regex = new RegExp(`\\{\\{${key}\\}\\}`, 'g');
17
+ content = content.replace(regex, value || '');
18
+ }
19
+ return content;
20
+ }
21
+
22
+ function banner() {
23
+ console.log();
24
+ console.log(pc.cyan(pc.bold(' ╔═══════════════════════════════════╗')));
25
+ console.log(pc.cyan(pc.bold(' ║ AIVA Setup Wizard ║')));
26
+ console.log(pc.cyan(pc.bold(' ║ AI Virtual Assistant v1.0 ║')));
27
+ console.log(pc.cyan(pc.bold(' ╚═══════════════════════════════════╝')));
28
+ console.log();
29
+ }
30
+
31
+ function checkNodeVersion() {
32
+ const major = parseInt(process.versions.node.split('.')[0], 10);
33
+ if (major < 18) {
34
+ console.error(pc.red(`Node.js 18+ required. You have ${process.versions.node}`));
35
+ process.exit(1);
36
+ }
37
+ }
38
+
39
+ const STYLE_MAP = {
40
+ 'Professional & formal': 'Professional, formal, and polished. Every communication is precise and well-structured.',
41
+ 'Casual & friendly': 'Casual, friendly, and approachable. Conversational tone with genuine warmth.',
42
+ 'Direct & efficient': 'Direct, efficient, and no-nonsense. Lead with the answer, skip the fluff.',
43
+ 'Warm & supportive': 'Warm, supportive, and encouraging. Empathetic while still getting things done.'
44
+ };
45
+
46
+ const TECH_LEVEL_CONFIG = {
47
+ power: {
48
+ communicationGuidance: 'Concise and direct. Lead with the answer, skip the explanation unless asked. Present options with brief trade-offs, let them decide fast. Never explain basic tech concepts. Assume competence.',
49
+ decisionStyle: 'Present the top recommendation with reasoning. Only elaborate if asked. Use interactive questionnaires for complex decisions with 3+ options only.',
50
+ autonomyLevel: 'Act first, report after. Only escalate truly ambiguous decisions or anything with financial, legal, or external impact. Routine operations never need permission.',
51
+ coachingNotes: 'Your human is technically sharp. Be a peer, not a teacher. When something goes wrong, lead with the fix, not the explanation.',
52
+ soulAddition: 'Your human is a power user. They want results, not narration. Respect their time and intelligence. Skip the hand-holding entirely.'
53
+ },
54
+ comfortable: {
55
+ communicationGuidance: 'Clear and efficient. Provide enough context to make informed decisions without overwhelming detail. Use plain language for technical concepts but do not over-explain.',
56
+ decisionStyle: 'Present 2-3 options with clear pros and cons. Make a recommendation but give them the choice. Use interactive questionnaires for any decision with 2+ options.',
57
+ autonomyLevel: 'Brief heads-up before significant actions. Report results with a one-line summary of what happened and why it matters.',
58
+ coachingNotes: 'Your human is capable but not deeply technical. Give the "why" alongside the "what" so they build understanding over time. When something goes wrong, explain what happened in plain terms, what you did about it, and whether they need to do anything.',
59
+ soulAddition: 'Your human is comfortable with tech but appreciates context. Build their understanding naturally over time without being condescending.'
60
+ },
61
+ guided: {
62
+ communicationGuidance: 'Patient and thorough. Explain what you are doing and why before doing it. Use analogies and plain language - never assume technical knowledge. Proactively explain implications of decisions.',
63
+ decisionStyle: 'Walk them through each decision step by step. Explain trade-offs in everyday terms. Make strong recommendations with clear reasoning. Use interactive questionnaires for every decision point, even binary ones.',
64
+ autonomyLevel: 'Always explain what you plan to do before doing it. After completing a task, explain what was done and what it means for them in plain language. Never act silently on anything that affects their business.',
65
+ coachingNotes: 'Your human needs you to be their tech translator. Every interaction is a chance to build their confidence. Never make them feel dumb for not knowing something. When something goes wrong, reassure first, then explain simply, then describe the fix. They need to feel safe that you have got it handled. Proactively teach them things that will help them use technology better over time. Be a coach, not just an executor.',
66
+ soulAddition: 'Your human trusts you to navigate technology for them. Be their guide. Celebrate their wins. Make complex things feel simple. Your patience is a feature, not a limitation.'
67
+ }
68
+ };
69
+
70
+ const EMOJI_MAP = {
71
+ 'lightning bolt': String.fromCodePoint(0x26A1),
72
+ 'star': String.fromCodePoint(0x2B50),
73
+ 'rocket': String.fromCodePoint(0x1F680),
74
+ 'sparkles': String.fromCodePoint(0x2728),
75
+ 'brain': String.fromCodePoint(0x1F9E0),
76
+ 'fire': String.fromCodePoint(0x1F525)
77
+ };
78
+
79
+ async function run(opts = {}) {
80
+ banner();
81
+ checkNodeVersion();
82
+
83
+ const detectedTz = Intl.DateTimeFormat().resolvedOptions().timeZone;
84
+
85
+ // ── Section 1: Server Configuration ──
86
+ console.log(pc.bold(' Server Configuration'));
87
+ console.log(pc.dim(' ─'.repeat(20)));
88
+
89
+ const serverAnswers = await prompts([
90
+ {
91
+ type: 'number',
92
+ name: 'port',
93
+ message: 'Which port should the server run on?',
94
+ initial: parseInt(opts.port, 10) || 3847
95
+ },
96
+ {
97
+ type: 'text',
98
+ name: 'gatewayUrl',
99
+ message: 'OpenClaw Gateway URL?',
100
+ initial: 'http://localhost:3033'
101
+ },
102
+ {
103
+ type: 'password',
104
+ name: 'gatewayPassword',
105
+ message: 'Gateway auth password?'
106
+ }
107
+ ], { onCancel: () => { console.log(pc.yellow('\nSetup cancelled.')); process.exit(0); } });
108
+
109
+ console.log();
110
+
111
+ // ── Section 2: Client Profile ──
112
+ console.log(pc.bold(' Client Profile'));
113
+ console.log(pc.dim(' ─'.repeat(20)));
114
+
115
+ const clientAnswers = await prompts([
116
+ {
117
+ type: 'text',
118
+ name: 'fullName',
119
+ message: "What's the client's full name?",
120
+ validate: v => v.trim() ? true : 'Name is required'
121
+ },
122
+ {
123
+ type: 'text',
124
+ name: 'nickname',
125
+ message: 'What should AIVA call them?',
126
+ initial: prev => prev.split(' ')[0]
127
+ },
128
+ {
129
+ type: 'select',
130
+ name: 'pronouns',
131
+ message: 'Their pronouns?',
132
+ choices: [
133
+ { title: 'he/him', value: 'he/him' },
134
+ { title: 'she/her', value: 'she/her' },
135
+ { title: 'they/them', value: 'they/them' },
136
+ { title: 'other', value: 'other' }
137
+ ]
138
+ },
139
+ {
140
+ type: prev => prev === 'other' ? 'text' : null,
141
+ name: 'customPronouns',
142
+ message: 'Enter pronouns:'
143
+ },
144
+ {
145
+ type: 'text',
146
+ name: 'timezone',
147
+ message: 'Their timezone?',
148
+ initial: detectedTz
149
+ },
150
+ {
151
+ type: 'text',
152
+ name: 'email',
153
+ message: 'Their email address?',
154
+ validate: v => v.includes('@') ? true : 'Enter a valid email'
155
+ },
156
+ {
157
+ type: 'text',
158
+ name: 'phone',
159
+ message: 'Their phone number? (optional)',
160
+ initial: ''
161
+ },
162
+ {
163
+ type: 'text',
164
+ name: 'business',
165
+ message: 'Business name? (optional)',
166
+ initial: ''
167
+ },
168
+ {
169
+ type: 'text',
170
+ name: 'businessDescription',
171
+ message: 'Brief description of what they do? (optional)',
172
+ initial: ''
173
+ }
174
+ ], { onCancel: () => { console.log(pc.yellow('\nSetup cancelled.')); process.exit(0); } });
175
+
176
+ console.log();
177
+
178
+ // ── Section 3: AIVA Personality ──
179
+ console.log(pc.bold(' AIVA Personality'));
180
+ console.log(pc.dim(' ─'.repeat(20)));
181
+
182
+ const personalityAnswers = await prompts([
183
+ {
184
+ type: 'text',
185
+ name: 'aivaName',
186
+ message: 'What should this AIVA be named?',
187
+ initial: opts.name || 'AIVA'
188
+ },
189
+ {
190
+ type: 'select',
191
+ name: 'style',
192
+ message: 'Communication style?',
193
+ choices: [
194
+ { title: 'Professional & formal', value: 'Professional & formal' },
195
+ { title: 'Casual & friendly', value: 'Casual & friendly' },
196
+ { title: 'Direct & efficient', value: 'Direct & efficient' },
197
+ { title: 'Warm & supportive', value: 'Warm & supportive' }
198
+ ]
199
+ },
200
+ {
201
+ type: 'select',
202
+ name: 'emoji',
203
+ message: "AIVA's signature emoji?",
204
+ choices: [
205
+ { title: `${EMOJI_MAP['lightning bolt']} Lightning bolt`, value: 'lightning bolt' },
206
+ { title: `${EMOJI_MAP['star']} Star`, value: 'star' },
207
+ { title: `${EMOJI_MAP['rocket']} Rocket`, value: 'rocket' },
208
+ { title: `${EMOJI_MAP['sparkles']} Sparkles`, value: 'sparkles' },
209
+ { title: `${EMOJI_MAP['brain']} Brain`, value: 'brain' },
210
+ { title: `${EMOJI_MAP['fire']} Fire`, value: 'fire' }
211
+ ]
212
+ }
213
+ ], { onCancel: () => { console.log(pc.yellow('\nSetup cancelled.')); process.exit(0); } });
214
+
215
+ // ── Section 4: Tech Sophistication ──
216
+ console.log(pc.bold(' Client Sophistication'));
217
+ console.log(pc.dim(' ─'.repeat(20)));
218
+
219
+ const techAnswers = await prompts([
220
+ {
221
+ type: 'select',
222
+ name: 'techLevel',
223
+ message: 'How tech-savvy is this person?',
224
+ choices: [
225
+ { title: 'Power User - Knows tech, wants concise updates, hates hand-holding', value: 'power' },
226
+ { title: 'Comfortable - Uses tech daily but not technical, needs context not tutorials', value: 'comfortable' },
227
+ { title: 'Guided - Needs step-by-step coaching, plain language, proactive guidance', value: 'guided' }
228
+ ]
229
+ }
230
+ ], { onCancel: () => { console.log(pc.yellow('\nSetup cancelled.')); process.exit(0); } });
231
+
232
+ console.log();
233
+
234
+ // ── Build config ──
235
+ const pronouns = clientAnswers.pronouns === 'other'
236
+ ? (clientAnswers.customPronouns || 'they/them')
237
+ : clientAnswers.pronouns;
238
+
239
+ const config = {
240
+ name: personalityAnswers.aivaName,
241
+ owner: clientAnswers.nickname,
242
+ port: serverAnswers.port,
243
+ gateway: {
244
+ url: serverAnswers.gatewayUrl,
245
+ password: serverAnswers.gatewayPassword
246
+ },
247
+ techLevel: techAnswers.techLevel,
248
+ autoStart: true,
249
+ installedAt: new Date().toISOString(),
250
+ client: {
251
+ fullName: clientAnswers.fullName,
252
+ nickname: clientAnswers.nickname,
253
+ pronouns,
254
+ timezone: clientAnswers.timezone,
255
+ email: clientAnswers.email,
256
+ phone: clientAnswers.phone || '',
257
+ business: clientAnswers.business || '',
258
+ businessDescription: clientAnswers.businessDescription || ''
259
+ },
260
+ personality: {
261
+ name: personalityAnswers.aivaName,
262
+ style: personalityAnswers.style,
263
+ emoji: personalityAnswers.emoji
264
+ }
265
+ };
266
+
267
+ // ── Create directories ──
268
+ console.log(pc.cyan(' Creating directories...'));
269
+ ensureDirectories();
270
+
271
+ // Create memory subdirectory
272
+ const memoryDir = path.join(getWorkspaceDir(), 'memory');
273
+ if (!fs.existsSync(memoryDir)) fs.mkdirSync(memoryDir, { recursive: true });
274
+
275
+ // ── Save config ──
276
+ console.log(pc.cyan(' Saving configuration...'));
277
+ saveConfig(config);
278
+
279
+ // ── Generate workspace files ──
280
+ console.log(pc.cyan(' Generating workspace files...'));
281
+ const templateVars = {
282
+ clientName: clientAnswers.fullName,
283
+ clientNickname: clientAnswers.nickname,
284
+ clientPronouns: pronouns,
285
+ clientTimezone: clientAnswers.timezone,
286
+ clientEmail: clientAnswers.email,
287
+ clientPhone: clientAnswers.phone || '(not provided)',
288
+ businessName: clientAnswers.business || '(not provided)',
289
+ businessDescription: clientAnswers.businessDescription || '(not provided)',
290
+ aivaName: personalityAnswers.aivaName,
291
+ aivaEmoji: EMOJI_MAP[personalityAnswers.emoji] || EMOJI_MAP['lightning bolt'],
292
+ communicationStyle: STYLE_MAP[personalityAnswers.style] || personalityAnswers.style,
293
+ techLevel: techAnswers.techLevel,
294
+ communicationGuidance: TECH_LEVEL_CONFIG[techAnswers.techLevel].communicationGuidance,
295
+ decisionStyle: TECH_LEVEL_CONFIG[techAnswers.techLevel].decisionStyle,
296
+ autonomyLevel: TECH_LEVEL_CONFIG[techAnswers.techLevel].autonomyLevel,
297
+ coachingNotes: TECH_LEVEL_CONFIG[techAnswers.techLevel].coachingNotes,
298
+ soulAddition: TECH_LEVEL_CONFIG[techAnswers.techLevel].soulAddition
299
+ };
300
+
301
+ const workspaceDir = getWorkspaceDir();
302
+ const templateFiles = [
303
+ { template: 'IDENTITY.md.tmpl', output: 'IDENTITY.md' },
304
+ { template: 'USER.md.tmpl', output: 'USER.md' },
305
+ { template: 'AGENTS.md.tmpl', output: 'AGENTS.md' },
306
+ { template: 'SOUL.md.tmpl', output: 'SOUL.md' },
307
+ { template: 'MEMORY.md.tmpl', output: 'MEMORY.md' },
308
+ { template: 'HEARTBEAT.md.tmpl', output: 'HEARTBEAT.md' }
309
+ ];
310
+
311
+ for (const { template, output } of templateFiles) {
312
+ const tmplPath = path.join(TEMPLATES_DIR, template);
313
+ if (fs.existsSync(tmplPath)) {
314
+ const rendered = renderTemplate(tmplPath, templateVars);
315
+ const outPath = path.join(workspaceDir, output);
316
+ // Don't overwrite existing files (preserve user edits on re-setup)
317
+ if (!fs.existsSync(outPath)) {
318
+ fs.writeFileSync(outPath, rendered);
319
+ console.log(pc.dim(` Created ${output}`));
320
+ } else {
321
+ console.log(pc.dim(` Skipped ${output} (already exists)`));
322
+ }
323
+ }
324
+ }
325
+
326
+ // ── Check/install PM2 ──
327
+ console.log(pc.cyan(' Checking PM2...'));
328
+ ensurePm2();
329
+
330
+ // ── Write PM2 ecosystem ──
331
+ console.log(pc.cyan(' Writing PM2 ecosystem config...'));
332
+ writeEcosystem();
333
+
334
+ // ── Auto-start setup ──
335
+ const platform = process.platform;
336
+ if (config.autoStart) {
337
+ if (platform === 'darwin') {
338
+ await setupLaunchd(config);
339
+ } else if (platform === 'linux') {
340
+ await setupSystemd(config);
341
+ }
342
+ }
343
+
344
+ // ── Start server ──
345
+ console.log(pc.cyan(' Starting AIVA...'));
346
+ await startServer();
347
+
348
+ // ── Health check ──
349
+ await new Promise(r => setTimeout(r, 3000));
350
+ const health = await checkHealth(config.port);
351
+
352
+ // ── Success banner ──
353
+ console.log();
354
+ console.log(pc.green(pc.bold(' ╔═══════════════════════════════════╗')));
355
+ console.log(pc.green(pc.bold(` ║ ${personalityAnswers.aivaName} is ready! ${EMOJI_MAP[personalityAnswers.emoji] || ''}`)));
356
+ console.log(pc.green(pc.bold(' ╚═══════════════════════════════════╝')));
357
+ console.log();
358
+ console.log(` ${pc.bold('URL:')} http://localhost:${config.port}`);
359
+ console.log(` ${pc.bold('Config:')} ${path.join(getAivaHome(), 'config.json')}`);
360
+ console.log(` ${pc.bold('Workspace:')} ${workspaceDir}`);
361
+ console.log(` ${pc.bold('Data:')} ${path.join(getAivaHome(), 'data')}`);
362
+ console.log(` ${pc.bold('Logs:')} ${path.join(getAivaHome(), 'logs')}`);
363
+ console.log(` ${pc.bold('Health:')} ${health.ok ? pc.green('Healthy') : pc.yellow('Starting up...')}`);
364
+ console.log();
365
+ console.log(pc.dim(' Commands:'));
366
+ console.log(pc.dim(' aiva status - Check server status'));
367
+ console.log(pc.dim(' aiva logs - View server logs'));
368
+ console.log(pc.dim(' aiva restart - Restart server'));
369
+ console.log(pc.dim(' aiva stop - Stop server'));
370
+ console.log();
371
+ }
372
+
373
+ async function setupLaunchd(config) {
374
+ const plistPath = path.join(
375
+ process.env.HOME || require('os').homedir(),
376
+ 'Library', 'LaunchAgents', 'com.conversionpros.aiva.plist'
377
+ );
378
+
379
+ // Find the aiva binary
380
+ let aivaBin;
381
+ try {
382
+ aivaBin = require('child_process').execSync('which aiva', { stdio: 'pipe' }).toString().trim();
383
+ } catch {
384
+ aivaBin = path.join(__dirname, '..', 'bin', 'aiva.js');
385
+ }
386
+
387
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
388
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
389
+ <plist version="1.0">
390
+ <dict>
391
+ <key>Label</key>
392
+ <string>com.conversionpros.aiva</string>
393
+ <key>ProgramArguments</key>
394
+ <array>
395
+ <string>${process.execPath}</string>
396
+ <string>${aivaBin}</string>
397
+ <string>start</string>
398
+ <string>--foreground</string>
399
+ </array>
400
+ <key>RunAtLoad</key>
401
+ <true/>
402
+ <key>KeepAlive</key>
403
+ <true/>
404
+ <key>StandardOutPath</key>
405
+ <string>${path.join(getAivaHome(), 'logs', 'aiva-launchd.log')}</string>
406
+ <key>StandardErrorPath</key>
407
+ <string>${path.join(getAivaHome(), 'logs', 'aiva-launchd-error.log')}</string>
408
+ <key>EnvironmentVariables</key>
409
+ <dict>
410
+ <key>AIVA_HOME</key>
411
+ <string>${getAivaHome()}</string>
412
+ <key>PORT</key>
413
+ <string>${config.port}</string>
414
+ </dict>
415
+ </dict>
416
+ </plist>`;
417
+
418
+ const { confirm } = await prompts({
419
+ type: 'confirm',
420
+ name: 'confirm',
421
+ message: 'Set up auto-start on login (launchd)?',
422
+ initial: true
423
+ });
424
+
425
+ if (confirm) {
426
+ const dir = path.dirname(plistPath);
427
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
428
+ fs.writeFileSync(plistPath, plist);
429
+ console.log(pc.dim(` Created ${plistPath}`));
430
+ }
431
+ }
432
+
433
+ async function setupSystemd(config) {
434
+ const { confirm } = await prompts({
435
+ type: 'confirm',
436
+ name: 'confirm',
437
+ message: 'Generate systemd service file for auto-start?',
438
+ initial: true
439
+ });
440
+
441
+ if (!confirm) return;
442
+
443
+ let aivaBin;
444
+ try {
445
+ aivaBin = require('child_process').execSync('which aiva', { stdio: 'pipe' }).toString().trim();
446
+ } catch {
447
+ aivaBin = path.join(__dirname, '..', 'bin', 'aiva.js');
448
+ }
449
+
450
+ const service = `[Unit]
451
+ Description=AIVA - AI Virtual Assistant
452
+ After=network.target
453
+
454
+ [Service]
455
+ Type=simple
456
+ ExecStart=${process.execPath} ${aivaBin} start --foreground
457
+ Restart=on-failure
458
+ RestartSec=5
459
+ Environment=AIVA_HOME=${getAivaHome()}
460
+ Environment=PORT=${config.port}
461
+
462
+ [Install]
463
+ WantedBy=default.target
464
+ `;
465
+
466
+ const servicePath = path.join(getAivaHome(), 'aiva.service');
467
+ fs.writeFileSync(servicePath, service);
468
+ console.log(pc.dim(` Wrote systemd service to ${servicePath}`));
469
+ console.log(pc.dim(' To install: systemctl --user enable --now ' + servicePath));
470
+ }
471
+
472
+ module.exports = { run };
package/meta-capi.js ADDED
@@ -0,0 +1,206 @@
1
+ /**
2
+ * Meta Conversions API (CAPI) Module
3
+ * Sends server-side events to Meta for optimization signals.
4
+ */
5
+ const crypto = require('crypto');
6
+ const https = require('https');
7
+
8
+ const PIXEL_ID = process.env.META_PIXEL_ID || '1257861836285895';
9
+ const ACCESS_TOKEN = process.env.META_ACCESS_TOKEN;
10
+ const API_VERSION = 'v21.0';
11
+
12
+ if (!ACCESS_TOKEN) {
13
+ console.warn('[meta-capi] META_ACCESS_TOKEN not set - CAPI events will fail');
14
+ }
15
+
16
+ function sha256(value) {
17
+ if (!value) return undefined;
18
+ return crypto.createHash('sha256').update(String(value).toLowerCase().trim()).digest('hex');
19
+ }
20
+
21
+ function generateEventId() {
22
+ return crypto.randomUUID();
23
+ }
24
+
25
+ /**
26
+ * Send an event to Meta Conversions API.
27
+ * @param {string} eventName - Lead, Schedule, Purchase, ViewContent, etc.
28
+ * @param {object} eventData - { sourceUrl, customData, actionSource }
29
+ * @param {object} userData - { email, phone, firstName, lastName, ... } (raw, will be hashed)
30
+ * @returns {Promise<object>}
31
+ */
32
+ async function sendEvent(eventName, eventData = {}, userData = {}) {
33
+ const eventId = generateEventId();
34
+ const eventTime = Math.floor(Date.now() / 1000);
35
+
36
+ const hashedUserData = {};
37
+ if (userData.email) hashedUserData.em = [sha256(userData.email)];
38
+ if (userData.phone) hashedUserData.ph = [sha256(userData.phone.replace(/\D/g, ''))];
39
+ if (userData.firstName) hashedUserData.fn = [sha256(userData.firstName)];
40
+ if (userData.lastName) hashedUserData.ln = [sha256(userData.lastName)];
41
+ if (userData.city) hashedUserData.ct = [sha256(userData.city)];
42
+ if (userData.state) hashedUserData.st = [sha256(userData.state)];
43
+ if (userData.zip) hashedUserData.zp = [sha256(userData.zip)];
44
+ if (userData.country) hashedUserData.country = [sha256(userData.country)];
45
+ if (userData.clientIpAddress) hashedUserData.client_ip_address = userData.clientIpAddress;
46
+ if (userData.clientUserAgent) hashedUserData.client_user_agent = userData.clientUserAgent;
47
+ if (userData.fbc) hashedUserData.fbc = userData.fbc;
48
+ if (userData.fbp) hashedUserData.fbp = userData.fbp;
49
+
50
+ const payload = {
51
+ data: [
52
+ {
53
+ event_name: eventName,
54
+ event_time: eventTime,
55
+ event_id: eventId,
56
+ action_source: eventData.actionSource || 'website',
57
+ event_source_url: eventData.sourceUrl || 'https://www.conversionmarketingpros.com',
58
+ user_data: hashedUserData,
59
+ ...(eventData.customData ? { custom_data: eventData.customData } : {}),
60
+ },
61
+ ],
62
+ ...(eventData.testEventCode ? { test_event_code: eventData.testEventCode } : {}),
63
+ };
64
+
65
+ const body = JSON.stringify(payload);
66
+ const token = ACCESS_TOKEN;
67
+
68
+ return new Promise((resolve, reject) => {
69
+ const req = https.request(
70
+ {
71
+ hostname: 'graph.facebook.com',
72
+ path: `/${API_VERSION}/${PIXEL_ID}/events?access_token=${encodeURIComponent(token)}`,
73
+ method: 'POST',
74
+ headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) },
75
+ },
76
+ (res) => {
77
+ let data = '';
78
+ res.on('data', (chunk) => (data += chunk));
79
+ res.on('end', () => {
80
+ try {
81
+ const parsed = JSON.parse(data);
82
+ console.log(`[meta-capi] ${eventName} sent | event_id=${eventId} | status=${res.statusCode} | events_received=${parsed.events_received ?? '?'}`);
83
+ resolve({ ok: res.statusCode === 200, eventId, response: parsed });
84
+ } catch (e) {
85
+ console.error(`[meta-capi] Parse error: ${data}`);
86
+ reject(e);
87
+ }
88
+ });
89
+ }
90
+ );
91
+ req.on('error', reject);
92
+ req.write(body);
93
+ req.end();
94
+ });
95
+ }
96
+
97
+ /**
98
+ * Express router factory for CAPI webhook endpoints.
99
+ */
100
+ function createRouter(express) {
101
+ const router = express.Router();
102
+
103
+ // Auth middleware - accept internal header or webhook secret
104
+ const WEBHOOK_SECRET = process.env.META_CAPI_WEBHOOK_SECRET || 'capi-webhook-2026';
105
+ router.use((req, res, next) => {
106
+ if (
107
+ req.headers['x-aiva-internal'] === 'true' ||
108
+ req.headers['x-webhook-secret'] === WEBHOOK_SECRET ||
109
+ req.method === 'GET' // allow test endpoint
110
+ ) {
111
+ return next();
112
+ }
113
+ return res.status(401).json({ error: 'Unauthorized' });
114
+ });
115
+
116
+ // Extract contact info from GHL webhook payload
117
+ function extractContact(body) {
118
+ // GHL sends contact data in various shapes
119
+ const contact = body.contact || body;
120
+ return {
121
+ email: contact.email || body.email,
122
+ phone: contact.phone || body.phone,
123
+ firstName: contact.firstName || contact.first_name || body.firstName || body.first_name,
124
+ lastName: contact.lastName || contact.last_name || body.lastName || body.last_name,
125
+ city: contact.city || body.city,
126
+ state: contact.state || body.state,
127
+ country: contact.country || body.country,
128
+ };
129
+ }
130
+
131
+ // POST /api/meta/capi/lead
132
+ router.post('/lead', async (req, res) => {
133
+ try {
134
+ const userData = extractContact(req.body);
135
+ const result = await sendEvent('Lead', {
136
+ sourceUrl: req.body.source_url || 'https://www.conversionmarketingpros.com',
137
+ actionSource: 'website',
138
+ }, userData);
139
+ console.log(`[meta-capi] Lead webhook processed | email=${userData.email || 'none'}`);
140
+ res.json({ ok: true, eventId: result.eventId });
141
+ } catch (err) {
142
+ console.error('[meta-capi] Lead error:', err.message);
143
+ res.status(500).json({ error: err.message });
144
+ }
145
+ });
146
+
147
+ // POST /api/meta/capi/schedule
148
+ router.post('/schedule', async (req, res) => {
149
+ try {
150
+ const userData = extractContact(req.body);
151
+ const result = await sendEvent('Schedule', {
152
+ sourceUrl: req.body.source_url || 'https://www.conversionmarketingpros.com',
153
+ actionSource: 'website',
154
+ }, userData);
155
+ console.log(`[meta-capi] Schedule webhook processed | email=${userData.email || 'none'}`);
156
+ res.json({ ok: true, eventId: result.eventId });
157
+ } catch (err) {
158
+ console.error('[meta-capi] Schedule error:', err.message);
159
+ res.status(500).json({ error: err.message });
160
+ }
161
+ });
162
+
163
+ // POST /api/meta/capi/purchase
164
+ router.post('/purchase', async (req, res) => {
165
+ try {
166
+ const userData = extractContact(req.body);
167
+ const result = await sendEvent('Purchase', {
168
+ sourceUrl: req.body.source_url || 'https://www.conversionmarketingpros.com',
169
+ actionSource: 'system_generated',
170
+ customData: {
171
+ currency: 'USD',
172
+ value: req.body.value || req.body.monetary_value || 0,
173
+ },
174
+ }, userData);
175
+ console.log(`[meta-capi] Purchase webhook processed | email=${userData.email || 'none'} | value=${req.body.value || 0}`);
176
+ res.json({ ok: true, eventId: result.eventId });
177
+ } catch (err) {
178
+ console.error('[meta-capi] Purchase error:', err.message);
179
+ res.status(500).json({ error: err.message });
180
+ }
181
+ });
182
+
183
+ // GET /api/meta/capi/test
184
+ router.get('/test', async (req, res) => {
185
+ try {
186
+ const result = await sendEvent('Lead', {
187
+ sourceUrl: 'https://www.conversionmarketingpros.com/test',
188
+ actionSource: 'website',
189
+ testEventCode: req.query.test_code || undefined,
190
+ }, {
191
+ email: 'test@example.com',
192
+ firstName: 'Test',
193
+ lastName: 'User',
194
+ phone: '+15551234567',
195
+ });
196
+ res.json({ ok: true, eventId: result.eventId, pixelId: PIXEL_ID, response: result.response });
197
+ } catch (err) {
198
+ console.error('[meta-capi] Test error:', err.message);
199
+ res.status(500).json({ error: err.message });
200
+ }
201
+ });
202
+
203
+ return router;
204
+ }
205
+
206
+ module.exports = { sendEvent, createRouter, sha256, PIXEL_ID: PIXEL_ID };