@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
package/google-oauth.js DELETED
@@ -1,310 +0,0 @@
1
- /**
2
- * Google OAuth 2.0 Integration - Phase 1
3
- * Handles OAuth flow, token management, and account status.
4
- *
5
- * Endpoints:
6
- * GET /api/integrations/google/auth-url - Generate OAuth URL
7
- * GET /api/integrations/google/callback - Handle OAuth callback
8
- * GET /api/integrations/google/status - List connected accounts
9
- * DELETE /api/integrations/google/disconnect - Disconnect an account
10
- */
11
-
12
- const express = require('express');
13
- const crypto = require('crypto');
14
- const fs = require('fs');
15
- const path = require('path');
16
- const os = require('os');
17
- const { v4: uuidv4 } = require('uuid');
18
-
19
- const router = express.Router();
20
-
21
- // ─── Config ───────────────────────────────────────────────────────────────────
22
-
23
- const GOOGLE_CLIENT_ID = process.env.GOOGLE_CLIENT_ID;
24
- const GOOGLE_CLIENT_SECRET = process.env.GOOGLE_CLIENT_SECRET;
25
- const GOOGLE_REDIRECT_URI = process.env.GOOGLE_REDIRECT_URI || 'http://localhost:3847/api/integrations/google/callback';
26
- const TOKENS_FILE = path.join(__dirname, 'data', 'oauth-tokens.json');
27
-
28
- const SCOPES = [
29
- 'https://www.googleapis.com/auth/gmail.modify',
30
- 'https://www.googleapis.com/auth/calendar',
31
- 'https://www.googleapis.com/auth/contacts.readonly',
32
- 'https://www.googleapis.com/auth/userinfo.email',
33
- 'https://www.googleapis.com/auth/userinfo.profile',
34
- ];
35
-
36
- // In-memory CSRF state store (5 min TTL)
37
- const pendingStates = new Map();
38
-
39
- // ─── Encryption (matches existing tokens.json pattern) ────────────────────────
40
-
41
- function getEncryptionKey() {
42
- return crypto.createHash('sha256').update('aiva-tokens-' + os.hostname()).digest();
43
- }
44
-
45
- function encrypt(text) {
46
- const key = getEncryptionKey();
47
- const iv = crypto.randomBytes(12);
48
- const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
49
- let enc = cipher.update(text, 'utf8', 'hex');
50
- enc += cipher.final('hex');
51
- const tag = cipher.getAuthTag().toString('hex');
52
- return `${iv.toString('hex')}:${tag}:${enc}`;
53
- }
54
-
55
- function decrypt(stored) {
56
- const key = getEncryptionKey();
57
- const [ivHex, tagHex, enc] = stored.split(':');
58
- const decipher = crypto.createDecipheriv('aes-256-gcm', key, Buffer.from(ivHex, 'hex'));
59
- decipher.setAuthTag(Buffer.from(tagHex, 'hex'));
60
- let dec = decipher.update(enc, 'hex', 'utf8');
61
- dec += decipher.final('utf8');
62
- return dec;
63
- }
64
-
65
- // ─── Token Storage ────────────────────────────────────────────────────────────
66
-
67
- function loadTokens() {
68
- try {
69
- if (fs.existsSync(TOKENS_FILE)) {
70
- return JSON.parse(fs.readFileSync(TOKENS_FILE, 'utf8'));
71
- }
72
- } catch (e) {
73
- console.error('[google-oauth] Failed to load tokens:', e.message);
74
- }
75
- return { google: { accounts: [] } };
76
- }
77
-
78
- function saveTokens(data) {
79
- const dir = path.dirname(TOKENS_FILE);
80
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
81
- fs.writeFileSync(TOKENS_FILE, JSON.stringify(data, null, 2));
82
- }
83
-
84
- // ─── PKCE Helpers ─────────────────────────────────────────────────────────────
85
-
86
- function generateCodeVerifier() {
87
- return crypto.randomBytes(64).toString('base64url');
88
- }
89
-
90
- function generateCodeChallenge(verifier) {
91
- return crypto.createHash('sha256').update(verifier).digest('base64url');
92
- }
93
-
94
- // ─── Token Refresh ────────────────────────────────────────────────────────────
95
-
96
- async function refreshAccessToken(account) {
97
- const refreshToken = decrypt(account.refresh_token);
98
- const resp = await fetch('https://oauth2.googleapis.com/token', {
99
- method: 'POST',
100
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
101
- body: new URLSearchParams({
102
- client_id: GOOGLE_CLIENT_ID,
103
- client_secret: GOOGLE_CLIENT_SECRET,
104
- refresh_token: refreshToken,
105
- grant_type: 'refresh_token',
106
- }),
107
- });
108
-
109
- if (!resp.ok) {
110
- const err = await resp.text();
111
- console.error('[google-oauth] Refresh failed:', err);
112
- return null;
113
- }
114
-
115
- const data = await resp.json();
116
- account.access_token = encrypt(data.access_token);
117
- account.token_expiry = new Date(Date.now() + data.expires_in * 1000).toISOString();
118
- if (data.refresh_token) {
119
- account.refresh_token = encrypt(data.refresh_token);
120
- }
121
- return account;
122
- }
123
-
124
- /**
125
- * Get a valid access token for an account, refreshing if needed.
126
- * Exported so email/calendar modules can use it.
127
- */
128
- async function getValidAccessToken(accountId) {
129
- const store = loadTokens();
130
- const account = store.google.accounts.find(a => a.id === accountId);
131
- if (!account) throw new Error('Account not found');
132
-
133
- const expiry = new Date(account.token_expiry);
134
- const fiveMinFromNow = new Date(Date.now() + 5 * 60 * 1000);
135
-
136
- if (expiry > fiveMinFromNow) {
137
- return { token: decrypt(account.access_token), email: account.email };
138
- }
139
-
140
- // Refresh needed
141
- const refreshed = await refreshAccessToken(account);
142
- if (!refreshed) {
143
- account.status = 'expired';
144
- saveTokens(store);
145
- throw new Error('GOOGLE_AUTH_EXPIRED');
146
- }
147
- account.status = 'connected';
148
- saveTokens(store);
149
- return { token: decrypt(account.access_token), email: account.email };
150
- }
151
-
152
- // ─── Routes ───────────────────────────────────────────────────────────────────
153
-
154
- // Generate OAuth authorization URL
155
- router.get('/auth-url', (req, res) => {
156
- if (!GOOGLE_CLIENT_ID || !GOOGLE_CLIENT_SECRET) {
157
- return res.json({ success: false, error: 'Google OAuth not configured. Set GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET.' });
158
- }
159
-
160
- const state = crypto.randomBytes(32).toString('hex');
161
- const codeVerifier = generateCodeVerifier();
162
- const codeChallenge = generateCodeChallenge(codeVerifier);
163
-
164
- // Store state with 5 min TTL
165
- pendingStates.set(state, { codeVerifier, createdAt: Date.now() });
166
- setTimeout(() => pendingStates.delete(state), 5 * 60 * 1000);
167
-
168
- const params = new URLSearchParams({
169
- client_id: GOOGLE_CLIENT_ID,
170
- redirect_uri: GOOGLE_REDIRECT_URI,
171
- response_type: 'code',
172
- scope: SCOPES.join(' '),
173
- state,
174
- code_challenge: codeChallenge,
175
- code_challenge_method: 'S256',
176
- access_type: 'offline',
177
- prompt: 'consent',
178
- });
179
-
180
- const url = `https://accounts.google.com/o/oauth2/v2/auth?${params}`;
181
- res.json({ success: true, data: { url } });
182
- });
183
-
184
- // OAuth callback
185
- router.get('/callback', async (req, res) => {
186
- const { code, state, error } = req.query;
187
-
188
- if (error) {
189
- return res.redirect('/#settings?oauth=error&message=' + encodeURIComponent(error));
190
- }
191
-
192
- if (!state || !pendingStates.has(state)) {
193
- return res.redirect('/#settings?oauth=error&message=Invalid+state+token');
194
- }
195
-
196
- const { codeVerifier } = pendingStates.get(state);
197
- pendingStates.delete(state);
198
-
199
- try {
200
- // Exchange code for tokens
201
- const tokenResp = await fetch('https://oauth2.googleapis.com/token', {
202
- method: 'POST',
203
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
204
- body: new URLSearchParams({
205
- client_id: GOOGLE_CLIENT_ID,
206
- client_secret: GOOGLE_CLIENT_SECRET,
207
- code,
208
- code_verifier: codeVerifier,
209
- grant_type: 'authorization_code',
210
- redirect_uri: GOOGLE_REDIRECT_URI,
211
- }),
212
- });
213
-
214
- if (!tokenResp.ok) {
215
- const err = await tokenResp.text();
216
- console.error('[google-oauth] Token exchange failed:', err);
217
- return res.redirect('/#settings?oauth=error&message=Token+exchange+failed');
218
- }
219
-
220
- const tokens = await tokenResp.json();
221
-
222
- // Get user profile
223
- const profileResp = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {
224
- headers: { Authorization: `Bearer ${tokens.access_token}` },
225
- });
226
- const profile = await profileResp.json();
227
-
228
- // Store account
229
- const store = loadTokens();
230
-
231
- // Check if this email is already connected — update if so
232
- const existingIdx = store.google.accounts.findIndex(a => a.email === profile.email);
233
- const account = {
234
- id: existingIdx >= 0 ? store.google.accounts[existingIdx].id : uuidv4(),
235
- email: profile.email,
236
- name: profile.name || profile.email,
237
- picture: profile.picture || null,
238
- type: profile.hd ? 'workspace' : 'personal',
239
- access_token: encrypt(tokens.access_token),
240
- refresh_token: encrypt(tokens.refresh_token),
241
- token_expiry: new Date(Date.now() + tokens.expires_in * 1000).toISOString(),
242
- scopes: SCOPES,
243
- status: 'connected',
244
- connected_at: new Date().toISOString(),
245
- };
246
-
247
- if (existingIdx >= 0) {
248
- store.google.accounts[existingIdx] = account;
249
- } else {
250
- store.google.accounts.push(account);
251
- }
252
- saveTokens(store);
253
-
254
- console.log(`[google-oauth] Connected: ${profile.email} (${account.type})`);
255
- return res.redirect('/#settings?oauth=success&email=' + encodeURIComponent(profile.email));
256
-
257
- } catch (e) {
258
- console.error('[google-oauth] Callback error:', e);
259
- return res.redirect('/#settings?oauth=error&message=' + encodeURIComponent(e.message));
260
- }
261
- });
262
-
263
- // Account status
264
- router.get('/status', (req, res) => {
265
- const store = loadTokens();
266
- const accounts = store.google.accounts.map(a => ({
267
- id: a.id,
268
- email: a.email,
269
- name: a.name,
270
- picture: a.picture,
271
- type: a.type,
272
- status: a.status || 'connected',
273
- token_expiry: a.token_expiry,
274
- connected_at: a.connected_at,
275
- scopes: a.scopes,
276
- }));
277
- res.json({ success: true, data: { accounts, configured: !!(GOOGLE_CLIENT_ID && GOOGLE_CLIENT_SECRET) } });
278
- });
279
-
280
- // Disconnect account
281
- router.delete('/disconnect', async (req, res) => {
282
- const { accountId } = req.body || {};
283
- if (!accountId) return res.json({ success: false, error: 'accountId required' });
284
-
285
- const store = loadTokens();
286
- const idx = store.google.accounts.findIndex(a => a.id === accountId);
287
- if (idx < 0) return res.json({ success: false, error: 'Account not found' });
288
-
289
- const account = store.google.accounts[idx];
290
-
291
- // Attempt to revoke with Google
292
- try {
293
- const token = decrypt(account.refresh_token);
294
- await fetch(`https://oauth2.googleapis.com/revoke?token=${encodeURIComponent(token)}`, {
295
- method: 'POST',
296
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
297
- });
298
- } catch (e) {
299
- console.warn('[google-oauth] Revocation failed (deleting locally anyway):', e.message);
300
- }
301
-
302
- // Delete locally regardless
303
- store.google.accounts.splice(idx, 1);
304
- saveTokens(store);
305
-
306
- console.log(`[google-oauth] Disconnected: ${account.email}`);
307
- res.json({ success: true, data: { email: account.email } });
308
- });
309
-
310
- module.exports = { router, getValidAccessToken, loadTokens, decrypt, encrypt };
package/grok-imagine.js DELETED
@@ -1,97 +0,0 @@
1
- /**
2
- * Grok Imagine — Image & Video Generation via xAI API
3
- */
4
-
5
- const XAI_API_KEY = process.env.XAI_API_KEY || 'xai-Gn37fuJg5ty4gvWFG2rbth34AxNORUKH8r4vTXQDtjwMGUqKZ7nYy8u2YStosGUCVBEg7VMHSqQZcKS4';
6
- const IMAGE_API = 'https://api.x.ai/v1/images/generations';
7
- const VIDEO_API = 'https://api.x.ai/v1/videos/generations';
8
- const VIDEO_POLL_BASE = 'https://api.x.ai/v1/videos';
9
-
10
- const headers = () => ({
11
- 'Authorization': `Bearer ${XAI_API_KEY}`,
12
- 'Content-Type': 'application/json',
13
- });
14
-
15
- function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
16
-
17
- /**
18
- * Generate an image from a text prompt (or edit with imageUrl).
19
- */
20
- async function generateImage(prompt, options = {}) {
21
- const body = {
22
- model: 'grok-imagine-image',
23
- prompt,
24
- n: options.n || 1,
25
- response_format: 'url',
26
- };
27
- if (options.imageUrl) body.image_url = options.imageUrl;
28
-
29
- console.log('[grok-imagine] Generating image:', prompt);
30
- const res = await fetch(IMAGE_API, { method: 'POST', headers: headers(), body: JSON.stringify(body) });
31
- if (!res.ok) {
32
- const err = await res.text();
33
- throw new Error(`Image generation failed (${res.status}): ${err}`);
34
- }
35
- const data = await res.json();
36
- const url = data.data?.[0]?.url;
37
- if (!url) throw new Error('No image URL in response');
38
- console.log('[grok-imagine] Image generated:', url.substring(0, 80));
39
- return { url };
40
- }
41
-
42
- /**
43
- * Generate a video (async — polls until done).
44
- */
45
- async function generateVideo(prompt, options = {}) {
46
- const body = {
47
- model: 'grok-imagine-video',
48
- prompt,
49
- };
50
- if (options.duration) body.duration = options.duration;
51
- if (options.aspectRatio || options.aspect_ratio) body.aspect_ratio = options.aspectRatio || options.aspect_ratio;
52
- if (options.resolution) body.resolution = options.resolution;
53
- if (options.imageUrl) body.image_url = options.imageUrl;
54
- if (options.videoUrl) body.video_url = options.videoUrl;
55
-
56
- console.log('[grok-imagine] Starting video generation:', prompt);
57
- const res = await fetch(VIDEO_API, { method: 'POST', headers: headers(), body: JSON.stringify(body) });
58
- if (!res.ok) {
59
- const err = await res.text();
60
- throw new Error(`Video generation failed (${res.status}): ${err}`);
61
- }
62
- const { request_id } = await res.json();
63
- if (!request_id) throw new Error('No request_id in video response');
64
- console.log('[grok-imagine] Video request_id:', request_id);
65
-
66
- // Poll every 5 sec, timeout 10 min
67
- const timeout = Date.now() + 10 * 60 * 1000;
68
- while (Date.now() < timeout) {
69
- await sleep(5000);
70
- const poll = await fetch(`${VIDEO_POLL_BASE}/${request_id}`, { headers: headers() });
71
- if (!poll.ok) {
72
- console.warn('[grok-imagine] Poll error:', poll.status);
73
- continue;
74
- }
75
- const status = await poll.json();
76
- console.log('[grok-imagine] Video status:', status.status);
77
- if (status.status === 'done') {
78
- const url = status.video?.url;
79
- if (!url) throw new Error('Video done but no URL');
80
- return { url, duration: status.video?.duration };
81
- }
82
- if (status.status === 'expired') throw new Error('Video generation expired');
83
- }
84
- throw new Error('Video generation timed out (10 min)');
85
- }
86
-
87
- /** Convenience: edit an image */
88
- async function editImage(prompt, sourceImageUrl) {
89
- return generateImage(prompt, { imageUrl: sourceImageUrl });
90
- }
91
-
92
- /** Convenience: edit a video */
93
- async function editVideo(prompt, sourceVideoUrl) {
94
- return generateVideo(prompt, { videoUrl: sourceVideoUrl });
95
- }
96
-
97
- module.exports = { generateImage, generateVideo, editImage, editVideo };
@@ -1,287 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * AIVA Health Reporter
4
- * Runs on each device, collects health data every 5 minutes,
5
- * and POSTs it to the dashboard.
6
- */
7
-
8
- const http = require('http');
9
- const https = require('https');
10
- const { execSync } = require('child_process');
11
- const os = require('os');
12
- const fs = require('fs');
13
- const path = require('path');
14
-
15
- // Config from env
16
- const DASHBOARD_URL = process.env.DASHBOARD_URL || 'http://localhost:3456';
17
- const DEVICE_ID = process.env.DEVICE_ID || os.hostname();
18
- const HEARTBEAT_TOKEN = process.env.HEARTBEAT_TOKEN || '';
19
- const INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
20
- const LOCAL_PORT = 3853;
21
- const LOG_FILE = path.join(__dirname, 'data', 'health-reporter.log');
22
-
23
- // Ensure data dir exists
24
- try { fs.mkdirSync(path.join(__dirname, 'data'), { recursive: true }); } catch {}
25
-
26
- function log(msg) {
27
- const line = `[${new Date().toISOString()}] ${msg}`;
28
- console.log(line);
29
- try { fs.appendFileSync(LOG_FILE, line + '\n'); } catch {}
30
- }
31
-
32
- function run(cmd, fallback = null) {
33
- try { return execSync(cmd, { encoding: 'utf8', timeout: 10000 }).trim(); } catch { return fallback; }
34
- }
35
-
36
- function getGitHash() {
37
- return run(`git -C "${__dirname}" rev-parse HEAD`, 'unknown');
38
- }
39
-
40
- function getDiskSpace() {
41
- try {
42
- const raw = run('df -k / | tail -1');
43
- if (!raw) return null;
44
- const parts = raw.split(/\s+/);
45
- const total = parseInt(parts[1]) * 1024;
46
- const used = parseInt(parts[2]) * 1024;
47
- const available = parseInt(parts[3]) * 1024;
48
- return { total, used, available, percentUsed: Math.round((used / total) * 100) };
49
- } catch { return null; }
50
- }
51
-
52
- function getMemoryUsage() {
53
- const total = os.totalmem();
54
- const free = os.freemem();
55
- return { total, free, used: total - free, percentUsed: Math.round(((total - free) / total) * 100) };
56
- }
57
-
58
- function getProcessStatus() {
59
- // Check PM2 first
60
- const pm2List = run('pm2 jlist 2>/dev/null');
61
- if (pm2List) {
62
- try {
63
- const procs = JSON.parse(pm2List);
64
- const aivaProc = procs.find(p => p.name === 'aiva-app');
65
- if (aivaProc) {
66
- return {
67
- manager: 'pm2',
68
- running: aivaProc.pm2_env?.status === 'online',
69
- status: aivaProc.pm2_env?.status || 'unknown',
70
- uptime: aivaProc.pm2_env?.pm_uptime ? Date.now() - aivaProc.pm2_env.pm_uptime : null,
71
- restarts: aivaProc.pm2_env?.restart_time || 0,
72
- pid: aivaProc.pid
73
- };
74
- }
75
- } catch {}
76
- }
77
- // Check LaunchAgent
78
- const launchCheck = run('launchctl list 2>/dev/null | grep aiva');
79
- return {
80
- manager: launchCheck ? 'launchagent' : 'unknown',
81
- running: !!launchCheck,
82
- status: launchCheck ? 'running' : 'not found'
83
- };
84
- }
85
-
86
- function getAutoDeployStatus() {
87
- // Check if auto-deploy process is running
88
- const pm2List = run('pm2 jlist 2>/dev/null');
89
- let running = false;
90
- if (pm2List) {
91
- try {
92
- const procs = JSON.parse(pm2List);
93
- running = procs.some(p => p.name === 'auto-deploy' && p.pm2_env?.status === 'online');
94
- } catch {}
95
- }
96
- // Check auto-deploy log for last activity
97
- const logPath = path.join(__dirname, 'data', 'auto-deploy.log');
98
- let lastCheck = null, lastUpdate = null;
99
- try {
100
- const lines = fs.readFileSync(logPath, 'utf8').split('\n').filter(Boolean).slice(-50);
101
- for (const line of lines.reverse()) {
102
- if (!lastCheck && line.includes('Checking')) lastCheck = line.match(/\[(.*?)\]/)?.[1];
103
- if (!lastUpdate && (line.includes('Updated') || line.includes('Deployed'))) lastUpdate = line.match(/\[(.*?)\]/)?.[1];
104
- if (lastCheck && lastUpdate) break;
105
- }
106
- } catch {}
107
- return { running, lastCheck, lastUpdate };
108
- }
109
-
110
- function getErrorCount() {
111
- // Parse recent PM2 logs for errors in last hour
112
- const oneHourAgo = Date.now() - 3600000;
113
- let count = 0;
114
- try {
115
- const logDir = path.join(os.homedir(), '.pm2', 'logs');
116
- const files = fs.readdirSync(logDir).filter(f => f.includes('error') || f.includes('aiva'));
117
- for (const file of files) {
118
- const stat = fs.statSync(path.join(logDir, file));
119
- if (stat.mtimeMs < oneHourAgo) continue;
120
- const content = fs.readFileSync(path.join(logDir, file), 'utf8');
121
- const lines = content.split('\n');
122
- for (const line of lines) {
123
- if (line.toLowerCase().includes('error') || line.toLowerCase().includes('exception')) {
124
- // Try to extract timestamp
125
- const ts = line.match(/\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}/);
126
- if (ts && new Date(ts[0]).getTime() > oneHourAgo) count++;
127
- else if (!ts) count++; // Count if no timestamp
128
- }
129
- }
130
- }
131
- } catch {}
132
- return count;
133
- }
134
-
135
- function getMemorySearchStatus() {
136
- try {
137
- const raw = run('export PATH="/opt/homebrew/bin:$PATH"; openclaw memory status --deep 2>&1');
138
- if (!raw) return { status: 'unknown', error: 'no output' };
139
-
140
- const providerMatch = raw.match(/Provider:\s*(.+)/);
141
- const embeddingsMatch = raw.match(/Embeddings:\s*(.+)/);
142
- const indexedMatch = raw.match(/Indexed:\s*(.+)/);
143
- const chunksMatch = raw.match(/(\d+)\s*chunks/);
144
-
145
- const embeddingsReady = embeddingsMatch && embeddingsMatch[1].trim().toLowerCase() === 'ready';
146
-
147
- return {
148
- status: embeddingsReady ? 'healthy' : 'broken',
149
- provider: providerMatch ? providerMatch[1].trim() : 'unknown',
150
- embeddings: embeddingsMatch ? embeddingsMatch[1].trim() : 'unknown',
151
- indexed: indexedMatch ? indexedMatch[1].trim() : 'unknown',
152
- chunks: chunksMatch ? parseInt(chunksMatch[1]) : 0
153
- };
154
- } catch (e) {
155
- return { status: 'error', error: e.message };
156
- }
157
- }
158
-
159
- function getTokenStatus() {
160
- try {
161
- const tokensPath = path.join(__dirname, 'data', 'tokens.json');
162
- const tokens = JSON.parse(fs.readFileSync(tokensPath, 'utf8'));
163
- return Object.keys(tokens).map(name => ({
164
- name,
165
- configured: !!tokens[name] && tokens[name] !== ''
166
- }));
167
- } catch { return []; }
168
- }
169
-
170
- async function checkConnectivity(url) {
171
- return new Promise(resolve => {
172
- const mod = url.startsWith('https') ? https : http;
173
- const req = mod.get(url, { timeout: 5000 }, (res) => {
174
- resolve(true);
175
- res.resume();
176
- });
177
- req.on('error', () => resolve(false));
178
- req.on('timeout', () => { req.destroy(); resolve(false); });
179
- });
180
- }
181
-
182
- async function collectHealthData() {
183
- const [canReachGithub, canReachDashboard] = await Promise.all([
184
- checkConnectivity('https://github.com'),
185
- checkConnectivity(DASHBOARD_URL)
186
- ]);
187
-
188
- return {
189
- deviceId: DEVICE_ID,
190
- timestamp: new Date().toISOString(),
191
- gitHash: getGitHash(),
192
- uptime: {
193
- process: Math.floor(process.uptime()),
194
- system: Math.floor(os.uptime())
195
- },
196
- disk: getDiskSpace(),
197
- memory: getMemoryUsage(),
198
- appProcess: getProcessStatus(),
199
- autoDeploy: getAutoDeployStatus(),
200
- nodeVersion: process.version,
201
- macosVersion: run('sw_vers -productVersion', os.release()),
202
- hostname: os.hostname(),
203
- errorCount: getErrorCount(),
204
- tokens: getTokenStatus(),
205
- memorySearch: getMemorySearchStatus(),
206
- network: {
207
- github: canReachGithub,
208
- dashboard: canReachDashboard
209
- }
210
- };
211
- }
212
-
213
- function postHeartbeat(data) {
214
- return new Promise((resolve, reject) => {
215
- const url = new URL('/api/devices/heartbeat', DASHBOARD_URL);
216
- const payload = JSON.stringify(data);
217
- const mod = url.protocol === 'https:' ? https : http;
218
-
219
- const req = mod.request(url, {
220
- method: 'POST',
221
- headers: {
222
- 'Content-Type': 'application/json',
223
- 'Content-Length': Buffer.byteLength(payload),
224
- 'X-Device-Id': DEVICE_ID,
225
- 'X-Heartbeat-Token': HEARTBEAT_TOKEN
226
- },
227
- timeout: 10000
228
- }, (res) => {
229
- let body = '';
230
- res.on('data', c => body += c);
231
- res.on('end', () => resolve({ status: res.statusCode, body }));
232
- });
233
-
234
- req.on('error', reject);
235
- req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
236
- req.write(payload);
237
- req.end();
238
- });
239
- }
240
-
241
- let lastHealthData = null;
242
-
243
- async function heartbeatCycle() {
244
- try {
245
- log('Collecting health data...');
246
- const data = await collectHealthData();
247
- lastHealthData = data;
248
-
249
- log(`Sending heartbeat to ${DASHBOARD_URL}...`);
250
- const result = await postHeartbeat(data);
251
- log(`Heartbeat sent: ${result.status}`);
252
- } catch (err) {
253
- log(`Heartbeat error: ${err.message}`);
254
- }
255
- }
256
-
257
- // Local /health endpoint for diagnostics
258
- const server = http.createServer(async (req, res) => {
259
- if (req.url === '/health' && req.method === 'GET') {
260
- const data = lastHealthData || await collectHealthData();
261
- res.writeHead(200, { 'Content-Type': 'application/json' });
262
- res.end(JSON.stringify(data, null, 2));
263
- } else {
264
- res.writeHead(404);
265
- res.end('Not found');
266
- }
267
- });
268
-
269
- server.listen(LOCAL_PORT, () => {
270
- log(`Health reporter started. Device: ${DEVICE_ID}`);
271
- log(`Local diagnostics: http://localhost:${LOCAL_PORT}/health`);
272
- log(`Dashboard: ${DASHBOARD_URL}`);
273
-
274
- // Initial heartbeat
275
- heartbeatCycle();
276
-
277
- // Schedule every 5 minutes
278
- setInterval(heartbeatCycle, INTERVAL_MS);
279
- });
280
-
281
- server.on('error', (err) => {
282
- if (err.code === 'EADDRINUSE') {
283
- log(`Port ${LOCAL_PORT} in use, running without local endpoint`);
284
- heartbeatCycle();
285
- setInterval(heartbeatCycle, INTERVAL_MS);
286
- }
287
- });