@conversionpros/aiva 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) 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/config.js +1 -1
  5. package/lib/constants.js +72 -0
  6. package/lib/launch-agent.js +112 -0
  7. package/lib/prerequisites.js +236 -0
  8. package/lib/process.js +59 -145
  9. package/lib/setup.js +224 -194
  10. package/lib/validate.js +194 -0
  11. package/package.json +7 -32
  12. package/auto-deploy.js +0 -190
  13. package/cli-sync.js +0 -126
  14. package/d2a-prompt-template.txt +0 -106
  15. package/diagnostics-api.js +0 -304
  16. package/docs/ara-dedup-fix-scope.md +0 -112
  17. package/docs/ara-fix-round2-scope.md +0 -61
  18. package/docs/ara-greeting-fix-scope.md +0 -70
  19. package/docs/calendar-date-fix-scope.md +0 -28
  20. package/docs/getting-started.md +0 -115
  21. package/docs/network-architecture-rollout-scope.md +0 -43
  22. package/docs/scope-google-oauth-integration.md +0 -351
  23. package/docs/settings-page-scope.md +0 -50
  24. package/docs/xai-imagine-scope.md +0 -116
  25. package/docs/xai-voice-integration-scope.md +0 -115
  26. package/docs/xai-voice-tools-scope.md +0 -165
  27. package/email-router.js +0 -512
  28. package/follow-up-handler.js +0 -606
  29. package/gateway-monitor.js +0 -158
  30. package/google-email.js +0 -379
  31. package/google-oauth.js +0 -310
  32. package/grok-imagine.js +0 -97
  33. package/health-reporter.js +0 -287
  34. package/invisible-prefix-base.txt +0 -206
  35. package/invisible-prefix-owner.txt +0 -26
  36. package/invisible-prefix-slim.txt +0 -10
  37. package/invisible-prefix.txt +0 -43
  38. package/knowledge-base.js +0 -472
  39. package/lib/cli.js +0 -19
  40. package/lib/server.js +0 -42
  41. package/meta-capi.js +0 -206
  42. package/meta-leads.js +0 -411
  43. package/notion-oauth.js +0 -323
  44. package/public/agent-config.html +0 -241
  45. package/public/aiva-avatar-anime.png +0 -0
  46. package/public/css/docs.css.bak +0 -688
  47. package/public/css/onboarding.css +0 -543
  48. package/public/diagrams/claude-subscription-pool.html +0 -329
  49. package/public/diagrams/claude-subscription-pool.png +0 -0
  50. package/public/docs-icon.png +0 -0
  51. package/public/escalation.html +0 -237
  52. package/public/group-config.html +0 -300
  53. package/public/icon-192.png +0 -0
  54. package/public/icon-512.png +0 -0
  55. package/public/icons/agents.svg +0 -1
  56. package/public/icons/attach.svg +0 -1
  57. package/public/icons/characters.svg +0 -1
  58. package/public/icons/chat.svg +0 -1
  59. package/public/icons/docs.svg +0 -1
  60. package/public/icons/heartbeat.svg +0 -1
  61. package/public/icons/messages.svg +0 -1
  62. package/public/icons/mic.svg +0 -1
  63. package/public/icons/notes.svg +0 -1
  64. package/public/icons/settings.svg +0 -1
  65. package/public/icons/tasks.svg +0 -1
  66. package/public/images/onboarding/p0-communication-layer.png +0 -0
  67. package/public/images/onboarding/p0-infinite-surface.png +0 -0
  68. package/public/images/onboarding/p0-learning-model.png +0 -0
  69. package/public/images/onboarding/p0-meet-aiva.png +0 -0
  70. package/public/images/onboarding/p4-contact-intelligence.png +0 -0
  71. package/public/images/onboarding/p4-context-compounds.png +0 -0
  72. package/public/images/onboarding/p4-message-router.png +0 -0
  73. package/public/images/onboarding/p4-per-contact-rules.png +0 -0
  74. package/public/images/onboarding/p4-send-messages.png +0 -0
  75. package/public/images/onboarding/p6-be-precise.png +0 -0
  76. package/public/images/onboarding/p6-review-escalations.png +0 -0
  77. package/public/images/onboarding/p6-voice-input.png +0 -0
  78. package/public/images/onboarding/p7-completion.png +0 -0
  79. package/public/index.html +0 -11594
  80. package/public/js/onboarding.js +0 -699
  81. package/public/manifest.json +0 -24
  82. package/public/messages-v2.html +0 -2824
  83. package/public/permission-approve.html.bak +0 -107
  84. package/public/permissions.html +0 -150
  85. package/public/styles/design-system.css +0 -68
  86. package/router-db.js +0 -604
  87. package/router-utils.js +0 -28
  88. package/router-v2/adapters/imessage.js +0 -191
  89. package/router-v2/adapters/quo.js +0 -82
  90. package/router-v2/adapters/whatsapp.js +0 -192
  91. package/router-v2/contact-manager.js +0 -234
  92. package/router-v2/conversation-engine.js +0 -498
  93. package/router-v2/data/knowledge-base.json +0 -176
  94. package/router-v2/data/router-v2.db +0 -0
  95. package/router-v2/data/router-v2.db-shm +0 -0
  96. package/router-v2/data/router-v2.db-wal +0 -0
  97. package/router-v2/data/router.db +0 -0
  98. package/router-v2/db.js +0 -457
  99. package/router-v2/escalation-bridge.js +0 -540
  100. package/router-v2/follow-up-engine.js +0 -347
  101. package/router-v2/index.js +0 -441
  102. package/router-v2/ingestion.js +0 -213
  103. package/router-v2/knowledge-base.js +0 -231
  104. package/router-v2/lead-qualifier.js +0 -152
  105. package/router-v2/learning-loop.js +0 -202
  106. package/router-v2/outbound-sender.js +0 -160
  107. package/router-v2/package.json +0 -13
  108. package/router-v2/permission-gate.js +0 -86
  109. package/router-v2/playbook.js +0 -177
  110. package/router-v2/prompts/base.js +0 -52
  111. package/router-v2/prompts/first-contact.js +0 -38
  112. package/router-v2/prompts/lead-qualification.js +0 -37
  113. package/router-v2/prompts/scheduling.js +0 -72
  114. package/router-v2/prompts/style-overrides.js +0 -22
  115. package/router-v2/scheduler.js +0 -301
  116. package/router-v2/scripts/migrate-v1-to-v2.js +0 -215
  117. package/router-v2/scripts/seed-faq.js +0 -67
  118. package/router-v2/seed-knowledge-base.js +0 -39
  119. package/router-v2/utils/ai.js +0 -129
  120. package/router-v2/utils/phone.js +0 -52
  121. package/router-v2/utils/response-validator.js +0 -98
  122. package/router-v2/utils/sanitize.js +0 -222
  123. package/router.js +0 -5005
  124. package/routes/google-calendar.js +0 -186
  125. package/scripts/deploy.sh +0 -62
  126. package/scripts/macos-calendar.sh +0 -232
  127. package/scripts/onboard-device.sh +0 -466
  128. package/server.js +0 -5131
  129. package/start.sh +0 -24
  130. package/templates/AGENTS.md +0 -548
  131. package/templates/IDENTITY.md +0 -15
  132. package/templates/docs-agents.html +0 -132
  133. package/templates/docs-app.html +0 -130
  134. package/templates/docs-home.html +0 -83
  135. package/templates/docs-imessage.html +0 -121
  136. package/templates/docs-tasks.html +0 -123
  137. package/templates/docs-tips.html +0 -175
  138. package/templates/getting-started.html +0 -809
  139. package/templates/invisible-prefix-base.txt +0 -171
  140. package/templates/invisible-prefix-owner.txt +0 -282
  141. package/templates/invisible-prefix.txt +0 -338
  142. package/templates/manifest.json +0 -61
  143. package/templates/memory-org/clients.md +0 -7
  144. package/templates/memory-org/credentials.md +0 -9
  145. package/templates/memory-org/devices.md +0 -7
  146. package/templates/updates.html +0 -464
  147. package/tts-proxy.js +0 -96
  148. package/voice-call-local.js +0 -731
  149. package/voice-call.js +0 -732
  150. package/wa-listener.js +0 -354
package/bin/aiva.js CHANGED
@@ -3,19 +3,18 @@
3
3
  'use strict';
4
4
 
5
5
  const { Command } = require('commander');
6
- const path = require('path');
7
6
  const pkg = require('../package.json');
8
7
 
9
8
  const program = new Command();
10
9
 
11
10
  program
12
11
  .name('aiva')
13
- .description('AIVA - AI Virtual Assistant')
12
+ .description('AIVA CLI - Provisions a fresh Mac as an AIVA client device')
14
13
  .version(pkg.version, '-v, --version');
15
14
 
16
15
  program
17
16
  .command('setup')
18
- .description('Interactive setup wizard - configure and start AIVA')
17
+ .description('Full interactive setup wizard - provisions this Mac as an AIVA device')
19
18
  .option('--non-interactive', 'Skip interactive prompts, use defaults')
20
19
  .option('--port <port>', 'Server port', '3847')
21
20
  .option('--name <name>', 'Assistant name', 'AIVA')
@@ -23,12 +22,20 @@ program
23
22
  require('../lib/setup').run(opts);
24
23
  });
25
24
 
25
+ program
26
+ .command('validate')
27
+ .description('Run the validation checklist (SOP Step 11)')
28
+ .action(async () => {
29
+ const { runValidation } = require('../lib/validate');
30
+ const { passed, failed } = await runValidation();
31
+ process.exit(failed > 0 ? 1 : 0);
32
+ });
33
+
26
34
  program
27
35
  .command('start')
28
- .description('Start the AIVA server')
29
- .option('-f, --foreground', 'Run in foreground instead of PM2')
30
- .action((opts) => {
31
- require('../lib/process').startServer(opts);
36
+ .description('Start the AIVA server via LaunchAgent')
37
+ .action(() => {
38
+ require('../lib/process').startServer();
32
39
  });
33
40
 
34
41
  program
@@ -60,13 +67,6 @@ program
60
67
  require('../lib/process').getLogs(parseInt(opts.lines, 10));
61
68
  });
62
69
 
63
- program
64
- .command('update')
65
- .description('Update AIVA to the latest version')
66
- .action(() => {
67
- require('../lib/cli').update();
68
- });
69
-
70
70
  program
71
71
  .command('config')
72
72
  .description('Show current configuration')
@@ -74,6 +74,18 @@ program
74
74
  require('../lib/config').printConfig();
75
75
  });
76
76
 
77
+ program
78
+ .command('encrypt-ts-key')
79
+ .description('Utility: encrypt a Tailscale auth key with a password')
80
+ .argument('<tskey>', 'Tailscale auth key')
81
+ .argument('<password>', 'Encryption password (the device activation code)')
82
+ .action((tskey, password) => {
83
+ const { encryptTailscaleKey } = require('../lib/constants');
84
+ const result = encryptTailscaleKey(tskey, password);
85
+ console.log(JSON.stringify(result, null, 2));
86
+ console.log('\nPaste this into lib/constants.js TAILSCALE_ENCRYPTED_KEY');
87
+ });
88
+
77
89
  program.parse(process.argv);
78
90
 
79
91
  if (!process.argv.slice(2).length) {
@@ -0,0 +1,145 @@
1
+ 'use strict';
2
+
3
+ const prompts = require('prompts');
4
+ const pc = require('picocolors');
5
+ const http = require('http');
6
+ const { section, ok, warn, info } = require('./prerequisites');
7
+ const { BLUEBUBBLES_PORT } = require('./constants');
8
+
9
+ /**
10
+ * Interactive BlueBubbles/iMessage setup walkthrough
11
+ */
12
+ async function setupBlueBubbles() {
13
+ section('BlueBubbles / iMessage Setup');
14
+ console.log();
15
+ info('BlueBubbles enables iMessage integration for AIVA.');
16
+ console.log();
17
+
18
+ // Step 1: Check if installed
19
+ const { installed } = await prompts({
20
+ type: 'confirm',
21
+ name: 'installed',
22
+ message: 'Do you have BlueBubbles installed?',
23
+ initial: true
24
+ });
25
+
26
+ if (!installed) {
27
+ console.log();
28
+ console.log(pc.cyan(' Download BlueBubbles from: https://bluebubbles.app'));
29
+ console.log(pc.cyan(' Install it, then come back here.'));
30
+ console.log();
31
+
32
+ const { ready } = await prompts({
33
+ type: 'confirm',
34
+ name: 'ready',
35
+ message: 'BlueBubbles installed and ready?',
36
+ initial: true
37
+ });
38
+
39
+ if (!ready) {
40
+ warn('Skipping BlueBubbles setup. You can configure it later.');
41
+ return null;
42
+ }
43
+ }
44
+
45
+ // Step 2: Sign in reminder
46
+ console.log();
47
+ info('Make sure BlueBubbles is open and signed in with the Apple ID for this device.');
48
+
49
+ const { signedIn } = await prompts({
50
+ type: 'confirm',
51
+ name: 'signedIn',
52
+ message: 'BlueBubbles is open and signed in?',
53
+ initial: true
54
+ });
55
+
56
+ if (!signedIn) {
57
+ warn('Please sign in to BlueBubbles first, then re-run setup.');
58
+ return null;
59
+ }
60
+
61
+ // Step 3: Get server password
62
+ const { bbPassword } = await prompts({
63
+ type: 'password',
64
+ name: 'bbPassword',
65
+ message: 'Enter your BlueBubbles server password:'
66
+ });
67
+
68
+ if (!bbPassword) {
69
+ warn('No password provided. Skipping webhook registration.');
70
+ return null;
71
+ }
72
+
73
+ // Step 4: Register webhook
74
+ console.log();
75
+ info('Registering AIVA webhook with BlueBubbles...');
76
+
77
+ try {
78
+ const webhookUrl = `http://127.0.0.1:3847/webhook/bluebubbles`;
79
+ const result = await registerWebhook(bbPassword, webhookUrl);
80
+ if (result) {
81
+ ok('Webhook registered successfully');
82
+ } else {
83
+ warn('Webhook registration may have failed. Check BlueBubbles settings.');
84
+ }
85
+ } catch (e) {
86
+ warn(`Webhook registration failed: ${e.message}`);
87
+ info('You can register manually in BlueBubbles settings.');
88
+ }
89
+
90
+ // Step 5: Test message
91
+ console.log();
92
+ console.log(pc.cyan(' Send a test iMessage from your phone to this device to verify.'));
93
+
94
+ const { tested } = await prompts({
95
+ type: 'confirm',
96
+ name: 'tested',
97
+ message: 'Test message received successfully?',
98
+ initial: true
99
+ });
100
+
101
+ if (tested) {
102
+ ok('BlueBubbles setup complete');
103
+ } else {
104
+ warn('BlueBubbles may need additional configuration');
105
+ info('Check BlueBubbles app for connection status');
106
+ }
107
+
108
+ return { password: bbPassword };
109
+ }
110
+
111
+ function registerWebhook(password, webhookUrl) {
112
+ return new Promise((resolve, reject) => {
113
+ const data = JSON.stringify({
114
+ url: webhookUrl,
115
+ events: ["new-message", "updated-message"]
116
+ });
117
+
118
+ const req = http.request({
119
+ hostname: '127.0.0.1',
120
+ port: BLUEBUBBLES_PORT,
121
+ path: `/api/v1/webhook?password=${encodeURIComponent(password)}`,
122
+ method: 'POST',
123
+ headers: {
124
+ 'Content-Type': 'application/json',
125
+ 'Content-Length': data.length
126
+ }
127
+ }, (res) => {
128
+ let body = '';
129
+ res.on('data', chunk => body += chunk);
130
+ res.on('end', () => {
131
+ resolve(res.statusCode < 400);
132
+ });
133
+ });
134
+
135
+ req.on('error', reject);
136
+ req.setTimeout(5000, () => {
137
+ req.destroy();
138
+ reject(new Error('Connection timeout'));
139
+ });
140
+ req.write(data);
141
+ req.end();
142
+ });
143
+ }
144
+
145
+ module.exports = { setupBlueBubbles };
@@ -0,0 +1,253 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+ const pc = require('picocolors');
7
+ const { GATEWAY_URL, DEFAULT_HOOKS_TOKEN, generateToken } = require('./constants');
8
+ const { ok, warn, info, section } = require('./prerequisites');
9
+
10
+ function getAppDir() {
11
+ return path.join(os.homedir(), '.openclaw', 'workspace', 'aiva-tasks');
12
+ }
13
+
14
+ function getOpenClawDir() {
15
+ return path.join(os.homedir(), '.openclaw');
16
+ }
17
+
18
+ /**
19
+ * Generate openclaw.json with ALL required fields from the SOP
20
+ */
21
+ function generateOpenClawConfig(clientData) {
22
+ const hooksToken = clientData.hooksToken || generateToken();
23
+ const homeDir = os.homedir();
24
+ const user = os.userInfo().username;
25
+
26
+ const config = {
27
+ hooks: {
28
+ enabled: true,
29
+ token: hooksToken
30
+ },
31
+ channels: {
32
+ aiva: {
33
+ enabled: true,
34
+ blockStreaming: true,
35
+ blockStreamingBreak: "message_end",
36
+ authToken: "",
37
+ routing: {
38
+ mainSessionUsers: [clientData.userId || "owner"]
39
+ },
40
+ dm: {
41
+ policy: "open"
42
+ }
43
+ }
44
+ },
45
+ gateway: {
46
+ http: {
47
+ endpoints: {
48
+ chatCompletions: {
49
+ enabled: true
50
+ }
51
+ }
52
+ },
53
+ auth: {
54
+ mode: "password",
55
+ password: clientData.gatewayPassword || require('crypto').randomBytes(16).toString('hex')
56
+ }
57
+ },
58
+ agents: {
59
+ defaults: {
60
+ blockStreamingDefault: "on",
61
+ blockStreamingBreak: "text_end",
62
+ blockStreamingCoalesce: {
63
+ minChars: 300,
64
+ idleMs: 1500
65
+ }
66
+ }
67
+ },
68
+ plugins: {
69
+ entries: {
70
+ aiva: { enabled: true }
71
+ },
72
+ load: {
73
+ paths: [`${homeDir}/.openclaw/extensions/aiva`]
74
+ },
75
+ installs: {
76
+ aiva: {
77
+ source: "path",
78
+ installPath: `${homeDir}/.openclaw/extensions/aiva`
79
+ }
80
+ }
81
+ },
82
+ tools: {
83
+ exec: {
84
+ notifyOnExit: false
85
+ }
86
+ }
87
+ };
88
+
89
+ return { config, hooksToken };
90
+ }
91
+
92
+ /**
93
+ * Generate auth-profiles.json in the CORRECT format (SOP Known Gotchas)
94
+ */
95
+ function generateAuthProfiles(anthropicKey) {
96
+ return {
97
+ profiles: {
98
+ "anthropic:key1": {
99
+ type: "token",
100
+ provider: "anthropic",
101
+ token: anthropicKey
102
+ }
103
+ }
104
+ };
105
+ }
106
+
107
+ /**
108
+ * Generate connection.json - MUST use localhost
109
+ */
110
+ function generateConnectionJson(hooksToken) {
111
+ return {
112
+ gatewayUrl: GATEWAY_URL,
113
+ gatewayToken: hooksToken
114
+ };
115
+ }
116
+
117
+ /**
118
+ * Generate users.json - owner + client user
119
+ */
120
+ function generateUsersJson(clientData) {
121
+ return {
122
+ users: [
123
+ { id: clientData.ownerId || "owner", name: clientData.ownerName || "Admin", role: "owner" },
124
+ {
125
+ id: clientData.userId,
126
+ name: clientData.clientName,
127
+ role: "user"
128
+ }
129
+ ]
130
+ };
131
+ }
132
+
133
+ /**
134
+ * Generate .env file for the AIVA app
135
+ */
136
+ function generateDotEnv(config) {
137
+ const lines = [
138
+ `PORT=${config.port || 3847}`,
139
+ `CODING_ENABLED=false`,
140
+ `AIVA_CHANNEL_PLUGIN=true`
141
+ ];
142
+ if (config.openaiKey) {
143
+ lines.push(`OPENAI_API_KEY=${config.openaiKey}`);
144
+ }
145
+ if (config.elevenLabsKey) {
146
+ lines.push(`ELEVENLABS_API_KEY=${config.elevenLabsKey}`);
147
+ }
148
+ return lines.join('\n') + '\n';
149
+ }
150
+
151
+ /**
152
+ * Write ALL config files to their correct locations
153
+ */
154
+ function writeAllConfigs(clientData, apiKeys) {
155
+ section('Configuration Files');
156
+
157
+ const appDir = getAppDir();
158
+ const openclawDir = getOpenClawDir();
159
+ const agentDir = path.join(openclawDir, 'agents', 'main', 'agent');
160
+
161
+ // Ensure directories
162
+ for (const dir of [appDir, agentDir, path.join(appDir, 'data')]) {
163
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
164
+ }
165
+
166
+ // 1. openclaw.json
167
+ const { config: oclConfig, hooksToken } = generateOpenClawConfig(clientData);
168
+ const openclawJsonPath = path.join(openclawDir, 'openclaw.json');
169
+ fs.writeFileSync(openclawJsonPath, JSON.stringify(oclConfig, null, 2) + '\n');
170
+ ok('openclaw.json');
171
+
172
+ // 2. auth-profiles.json (correct format!)
173
+ const authProfiles = generateAuthProfiles(apiKeys.anthropicKey);
174
+ const authProfilesPath = path.join(agentDir, 'auth-profiles.json');
175
+ fs.writeFileSync(authProfilesPath, JSON.stringify(authProfiles, null, 2) + '\n');
176
+ ok('auth-profiles.json (correct format)');
177
+
178
+ // 3. connection.json
179
+ const connectionJson = generateConnectionJson(hooksToken);
180
+ const connectionPath = path.join(appDir, 'data', 'connection.json');
181
+ fs.writeFileSync(connectionPath, JSON.stringify(connectionJson, null, 2) + '\n');
182
+ ok('connection.json (localhost)');
183
+
184
+ // 4. users.json (in app root, NOT just data/)
185
+ const usersJson = generateUsersJson(clientData);
186
+ const usersPath = path.join(appDir, 'users.json');
187
+ fs.writeFileSync(usersPath, JSON.stringify(usersJson, null, 2) + '\n');
188
+ ok('users.json (owner + client)');
189
+
190
+ // 5. .env
191
+ const dotEnv = generateDotEnv({
192
+ port: clientData.port || 3847,
193
+ openaiKey: apiKeys.openaiKey,
194
+ elevenLabsKey: apiKeys.elevenLabsKey
195
+ });
196
+ fs.writeFileSync(path.join(appDir, '.env'), dotEnv);
197
+ ok('.env');
198
+
199
+ return { hooksToken, appDir };
200
+ }
201
+
202
+ /**
203
+ * Set up channel plugin symlink
204
+ */
205
+ function setupChannelPlugin() {
206
+ section('Channel Plugin');
207
+
208
+ const homeDir = os.homedir();
209
+ const appDir = getAppDir();
210
+ const pluginSource = path.join(appDir, 'plugins', 'aiva-channel');
211
+ const extensionsDir = path.join(homeDir, '.openclaw', 'extensions');
212
+ const symlinkTarget = path.join(extensionsDir, 'aiva');
213
+
214
+ if (!fs.existsSync(extensionsDir)) {
215
+ fs.mkdirSync(extensionsDir, { recursive: true });
216
+ }
217
+
218
+ // Remove existing symlink if it exists
219
+ try {
220
+ if (fs.existsSync(symlinkTarget) || fs.lstatSync(symlinkTarget)) {
221
+ fs.unlinkSync(symlinkTarget);
222
+ }
223
+ } catch { /* doesn't exist, fine */ }
224
+
225
+ if (fs.existsSync(pluginSource)) {
226
+ fs.symlinkSync(pluginSource, symlinkTarget);
227
+ ok('Channel plugin symlinked');
228
+
229
+ // Verify the plugin manifest exists
230
+ const manifest = path.join(symlinkTarget, 'openclaw.plugin.json');
231
+ if (fs.existsSync(manifest)) {
232
+ ok('Plugin manifest found');
233
+ } else {
234
+ warn('Plugin manifest not found at expected location');
235
+ info('Check that plugins/aiva-channel/openclaw.plugin.json exists in the app');
236
+ }
237
+ } else {
238
+ warn(`Plugin source not found: ${pluginSource}`);
239
+ info('Channel plugin will need to be set up manually after app is cloned');
240
+ }
241
+ }
242
+
243
+ module.exports = {
244
+ generateOpenClawConfig,
245
+ generateAuthProfiles,
246
+ generateConnectionJson,
247
+ generateUsersJson,
248
+ generateDotEnv,
249
+ writeAllConfigs,
250
+ setupChannelPlugin,
251
+ getAppDir,
252
+ getOpenClawDir
253
+ };
package/lib/config.js CHANGED
@@ -11,7 +11,7 @@ const DEFAULT_CONFIG = {
11
11
  owner: '',
12
12
  port: 3847,
13
13
  gateway: {
14
- url: 'http://localhost:3033',
14
+ url: 'http://localhost:18789',
15
15
  password: ''
16
16
  },
17
17
  autoStart: true,
@@ -0,0 +1,72 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('crypto');
4
+
5
+ // GitHub deploy token (read-only, repo scope) for cloning the AIVA app
6
+ const GITHUB_DEPLOY_TOKEN = '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';
7
+ const GITHUB_CLONE_URL = `https://${GITHUB_DEPLOY_TOKEN}@github.com/mistermakeithappen/aiva-app.git`;
8
+
9
+ // Tailscale auth key - encrypted with AES-256-CBC
10
+ // The decryption password is given to each client as their "device activation code"
11
+ // TODO: Brandon to provide real encrypted key. This is a placeholder.
12
+ const TAILSCALE_ENCRYPTED_KEY = {
13
+ cipher: 'aes-256-cbc',
14
+ // Placeholder - replace with real encrypted Tailscale auth key
15
+ data: 'PLACEHOLDER_ENCRYPTED_TAILSCALE_KEY',
16
+ iv: 'PLACEHOLDER_IV'
17
+ };
18
+
19
+ // Default hooks token pattern
20
+ const DEFAULT_HOOKS_TOKEN = 'aiva-hook-secret-2026';
21
+
22
+ // App port
23
+ const APP_PORT = 3847;
24
+ const GATEWAY_PORT = 18789;
25
+ const GATEWAY_URL = `http://127.0.0.1:${GATEWAY_PORT}`;
26
+
27
+ // BlueBubbles default port
28
+ const BLUEBUBBLES_PORT = 1234;
29
+
30
+ function generateToken() {
31
+ return crypto.randomBytes(24).toString('hex');
32
+ }
33
+
34
+ function decryptTailscaleKey(password) {
35
+ if (TAILSCALE_ENCRYPTED_KEY.data === 'PLACEHOLDER_ENCRYPTED_TAILSCALE_KEY') {
36
+ return null; // Placeholder - not yet configured
37
+ }
38
+ try {
39
+ const key = crypto.scryptSync(password, 'aiva-tailscale-salt', 32);
40
+ const iv = Buffer.from(TAILSCALE_ENCRYPTED_KEY.iv, 'hex');
41
+ const decipher = crypto.createDecipheriv(TAILSCALE_ENCRYPTED_KEY.cipher, key, iv);
42
+ let decrypted = decipher.update(TAILSCALE_ENCRYPTED_KEY.data, 'hex', 'utf8');
43
+ decrypted += decipher.final('utf8');
44
+ return decrypted;
45
+ } catch {
46
+ return null;
47
+ }
48
+ }
49
+
50
+ // Encrypt a Tailscale key with a password (utility for Brandon to generate the encrypted blob)
51
+ function encryptTailscaleKey(tsKey, password) {
52
+ const key = crypto.scryptSync(password, 'aiva-tailscale-salt', 32);
53
+ const iv = crypto.randomBytes(16);
54
+ const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
55
+ let encrypted = cipher.update(tsKey, 'utf8', 'hex');
56
+ encrypted += cipher.final('hex');
57
+ return { cipher: 'aes-256-cbc', data: encrypted, iv: iv.toString('hex') };
58
+ }
59
+
60
+ module.exports = {
61
+ GITHUB_DEPLOY_TOKEN,
62
+ GITHUB_CLONE_URL,
63
+ TAILSCALE_ENCRYPTED_KEY,
64
+ DEFAULT_HOOKS_TOKEN,
65
+ APP_PORT,
66
+ GATEWAY_PORT,
67
+ GATEWAY_URL,
68
+ BLUEBUBBLES_PORT,
69
+ generateToken,
70
+ decryptTailscaleKey,
71
+ encryptTailscaleKey
72
+ };
@@ -0,0 +1,112 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+ const { execSync } = require('child_process');
7
+ const { ok, warn, info, section } = require('./prerequisites');
8
+
9
+ /**
10
+ * Generate and install the LaunchAgent plist for client devices.
11
+ * Uses com.aiva.tasks.plist pattern from SOP.
12
+ */
13
+ function setupLaunchAgent(appDir) {
14
+ section('LaunchAgent (Auto-Start)');
15
+
16
+ const homeDir = os.homedir();
17
+ const launchAgentsDir = path.join(homeDir, 'Library', 'LaunchAgents');
18
+ const plistPath = path.join(launchAgentsDir, 'com.aiva.tasks.plist');
19
+
20
+ if (!fs.existsSync(launchAgentsDir)) {
21
+ fs.mkdirSync(launchAgentsDir, { recursive: true });
22
+ }
23
+
24
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
25
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
26
+ <plist version="1.0">
27
+ <dict>
28
+ <key>Label</key>
29
+ <string>com.aiva.tasks</string>
30
+ <key>ProgramArguments</key>
31
+ <array>
32
+ <string>/opt/homebrew/bin/node</string>
33
+ <string>server.js</string>
34
+ </array>
35
+ <key>WorkingDirectory</key>
36
+ <string>${appDir}</string>
37
+ <key>RunAtLoad</key>
38
+ <true/>
39
+ <key>KeepAlive</key>
40
+ <true/>
41
+ <key>EnvironmentVariables</key>
42
+ <dict>
43
+ <key>CODING_ENABLED</key>
44
+ <string>false</string>
45
+ <key>AIVA_CHANNEL_PLUGIN</key>
46
+ <string>true</string>
47
+ <key>NODE_ENV</key>
48
+ <string>production</string>
49
+ <key>PATH</key>
50
+ <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
51
+ </dict>
52
+ <key>StandardOutPath</key>
53
+ <string>/tmp/aiva-app.log</string>
54
+ <key>StandardErrorPath</key>
55
+ <string>/tmp/aiva-app-err.log</string>
56
+ </dict>
57
+ </plist>`;
58
+
59
+ fs.writeFileSync(plistPath, plist);
60
+ ok(`LaunchAgent written: ${plistPath}`);
61
+
62
+ // Load the LaunchAgent (bootout first if already loaded)
63
+ const uid = process.getuid ? process.getuid() : 501;
64
+ try {
65
+ execSync(`launchctl bootout gui/${uid}/com.aiva.tasks 2>/dev/null || true`, { stdio: 'pipe' });
66
+ } catch { /* not loaded, fine */ }
67
+
68
+ try {
69
+ execSync(`launchctl bootstrap gui/${uid} "${plistPath}"`, { stdio: 'pipe' });
70
+ ok('LaunchAgent loaded');
71
+ } catch (e) {
72
+ warn(`LaunchAgent load failed: ${e.message}`);
73
+ info(`Load manually: launchctl bootstrap gui/${uid} "${plistPath}"`);
74
+ }
75
+
76
+ return plistPath;
77
+ }
78
+
79
+ /**
80
+ * Set up voice notes cron job on the device's own OpenClaw instance
81
+ */
82
+ function setupVoiceNotesCron() {
83
+ section('Voice Notes Cron');
84
+
85
+ try {
86
+ // Check if cron already exists
87
+ const existing = execSync('openclaw cron list 2>/dev/null || echo ""', { stdio: 'pipe' }).toString();
88
+ if (existing.includes('nightly-voice-notes')) {
89
+ ok('Voice notes cron already configured');
90
+ return;
91
+ }
92
+
93
+ execSync(`openclaw cron add \
94
+ --name nightly-voice-notes-processing \
95
+ --cron "0 23 * * *" \
96
+ --tz America/Los_Angeles \
97
+ --agent main \
98
+ --channel heartbeat \
99
+ --session isolated \
100
+ --no-deliver \
101
+ --timeout-seconds 300 \
102
+ --message "Process all pending voice notes from today. Fetch from localhost:3847/api/notes?status=pending. Classify each note (COMMAND/INFORMATION/TASK_REQUEST/JUNK). For COMMANDS, schedule as 8 AM systemEvents. For INFORMATION, file to memory. For TASK_REQUESTS, add to task board. Mark notes as processed. Send summary to device owner."`, {
103
+ stdio: 'pipe'
104
+ });
105
+ ok('Voice notes cron configured (11:00 PM PT)');
106
+ } catch (e) {
107
+ warn(`Voice notes cron setup failed: ${e.message}`);
108
+ info('Set up manually after OpenClaw is running');
109
+ }
110
+ }
111
+
112
+ module.exports = { setupLaunchAgent, setupVoiceNotesCron };