@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
@@ -0,0 +1,466 @@
1
+ #!/usr/bin/env bash
2
+ # ============================================================================
3
+ # AIVA Device Onboarding Script
4
+ # Fully provisions a fresh Mac Mini as an AIVA client device.
5
+ # Idempotent — safe to run multiple times.
6
+ #
7
+ # Usage:
8
+ # ./onboard-device.sh \
9
+ # --client-name "Kurt Burgan" \
10
+ # --device-id "kurt-mac-mini" \
11
+ # --dashboard-url "http://100.x.x.x:3851" \
12
+ # --tailscale-key "tskey-auth-xxx"
13
+ #
14
+ # Optional:
15
+ # --webhook-secret "xxx" GitHub webhook secret for auto-deploy
16
+ # --skip-tailscale Skip Tailscale join (already on tailnet)
17
+ # --app-port 3847 AIVA app port (default: 3847)
18
+ # --deploy-port 3849 Auto-deploy port (default: 3849)
19
+ # ============================================================================
20
+
21
+ set -euo pipefail
22
+
23
+ # ── Colors & helpers ────────────────────────────────────────────────────────
24
+ RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'
25
+ CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m'
26
+
27
+ step_num=0
28
+ step_results=()
29
+
30
+ log() { echo -e "${BLUE}[INFO]${NC} $*"; }
31
+ ok() { echo -e "${GREEN}[ OK]${NC} $*"; step_results+=("✅ $*"); }
32
+ warn() { echo -e "${YELLOW}[WARN]${NC} $*"; step_results+=("⚠️ $*"); }
33
+ fail() { echo -e "${RED}[FAIL]${NC} $*"; step_results+=("❌ $*"); }
34
+ die() { fail "$*"; print_summary; exit 1; }
35
+
36
+ step() {
37
+ step_num=$((step_num + 1))
38
+ echo ""
39
+ echo -e "${CYAN}${BOLD}━━━ Step ${step_num}: $* ━━━${NC}"
40
+ }
41
+
42
+ # ── Parse arguments ─────────────────────────────────────────────────────────
43
+ CLIENT_NAME=""
44
+ DEVICE_ID=""
45
+ DASHBOARD_URL=""
46
+ TAILSCALE_KEY=""
47
+ WEBHOOK_SECRET=""
48
+ SKIP_TAILSCALE=false
49
+ APP_PORT=3847
50
+ DEPLOY_PORT=3849
51
+
52
+ while [[ $# -gt 0 ]]; do
53
+ case "$1" in
54
+ --client-name) CLIENT_NAME="$2"; shift 2 ;;
55
+ --device-id) DEVICE_ID="$2"; shift 2 ;;
56
+ --dashboard-url) DASHBOARD_URL="$2"; shift 2 ;;
57
+ --tailscale-key) TAILSCALE_KEY="$2"; shift 2 ;;
58
+ --webhook-secret) WEBHOOK_SECRET="$2"; shift 2 ;;
59
+ --skip-tailscale) SKIP_TAILSCALE=true; shift ;;
60
+ --app-port) APP_PORT="$2"; shift 2 ;;
61
+ --deploy-port) DEPLOY_PORT="$2"; shift 2 ;;
62
+ -h|--help)
63
+ echo "Usage: $0 --client-name NAME --device-id ID --dashboard-url URL [--tailscale-key KEY] [--webhook-secret SECRET] [--skip-tailscale] [--app-port PORT] [--deploy-port PORT]"
64
+ exit 0 ;;
65
+ *) echo "Unknown option: $1"; exit 1 ;;
66
+ esac
67
+ done
68
+
69
+ # Validate required args
70
+ [[ -z "$CLIENT_NAME" ]] && die "Missing --client-name"
71
+ [[ -z "$DEVICE_ID" ]] && die "Missing --device-id"
72
+ [[ -z "$DASHBOARD_URL" ]] && die "Missing --dashboard-url"
73
+
74
+ REPO_URL="git@github.com:mistermakeithappen/aiva-app.git"
75
+ APP_DIR="$HOME/aiva-tasks"
76
+ CURRENT_USER=$(whoami)
77
+
78
+ echo ""
79
+ echo -e "${BOLD}${CYAN}╔══════════════════════════════════════════════╗${NC}"
80
+ echo -e "${BOLD}${CYAN}║ AIVA Device Onboarding ║${NC}"
81
+ echo -e "${BOLD}${CYAN}╚══════════════════════════════════════════════╝${NC}"
82
+ echo ""
83
+ echo -e " Client: ${BOLD}${CLIENT_NAME}${NC}"
84
+ echo -e " Device ID: ${BOLD}${DEVICE_ID}${NC}"
85
+ echo -e " Dashboard: ${BOLD}${DASHBOARD_URL}${NC}"
86
+ echo -e " User: ${BOLD}${CURRENT_USER}${NC}"
87
+ echo -e " App Dir: ${BOLD}${APP_DIR}${NC}"
88
+ echo ""
89
+
90
+ # ── Step 1: Validate prerequisites ──────────────────────────────────────────
91
+ step "Validate prerequisites"
92
+
93
+ # macOS check
94
+ if [[ "$(uname)" != "Darwin" ]]; then
95
+ die "This script only runs on macOS. Detected: $(uname)"
96
+ fi
97
+ MACOS_VERSION=$(sw_vers -productVersion)
98
+ log "macOS version: $MACOS_VERSION"
99
+ ok "macOS $MACOS_VERSION detected"
100
+
101
+ # Xcode CLI tools (needed for git)
102
+ if ! xcode-select -p &>/dev/null; then
103
+ log "Installing Xcode Command Line Tools..."
104
+ xcode-select --install 2>/dev/null || true
105
+ warn "Xcode CLI tools installing — you may need to accept the dialog and re-run this script"
106
+ fi
107
+
108
+ # Homebrew
109
+ if ! command -v brew &>/dev/null; then
110
+ log "Installing Homebrew..."
111
+ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
112
+ # Add to path for Apple Silicon
113
+ if [[ -f /opt/homebrew/bin/brew ]]; then
114
+ eval "$(/opt/homebrew/bin/brew shellenv)"
115
+ # Persist for future shells
116
+ echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> "$HOME/.zprofile" 2>/dev/null || true
117
+ fi
118
+ ok "Homebrew installed"
119
+ else
120
+ ok "Homebrew present ($(brew --version | head -1))"
121
+ fi
122
+
123
+ # Git
124
+ if ! command -v git &>/dev/null; then
125
+ log "Installing git via Homebrew..."
126
+ brew install git
127
+ ok "Git installed"
128
+ else
129
+ ok "Git present ($(git --version))"
130
+ fi
131
+
132
+ # Node.js
133
+ if ! command -v node &>/dev/null; then
134
+ log "Installing Node.js via Homebrew..."
135
+ brew install node
136
+ ok "Node.js installed ($(node --version))"
137
+ else
138
+ NODE_VERSION=$(node --version)
139
+ ok "Node.js present ($NODE_VERSION)"
140
+ fi
141
+
142
+ # npm
143
+ if ! command -v npm &>/dev/null; then
144
+ die "npm not found even though Node.js is installed"
145
+ fi
146
+ ok "npm present ($(npm --version))"
147
+
148
+ # ── Step 2: Join Tailscale ───────────────────────────────────────────────────
149
+ step "Join Tailscale network"
150
+
151
+ if [[ "$SKIP_TAILSCALE" == "true" ]]; then
152
+ log "Skipping Tailscale (--skip-tailscale flag)"
153
+ ok "Tailscale skipped by request"
154
+ elif [[ -z "$TAILSCALE_KEY" ]]; then
155
+ warn "No --tailscale-key provided — skipping Tailscale join. Device must already be on tailnet."
156
+ else
157
+ if ! command -v tailscale &>/dev/null; then
158
+ log "Tailscale CLI not found. Install Tailscale from https://tailscale.com/download/mac"
159
+ warn "Tailscale not installed — install manually and re-run"
160
+ else
161
+ # Check if already connected
162
+ TS_STATUS=$(tailscale status --json 2>/dev/null | grep -o '"BackendState":"[^"]*"' | cut -d'"' -f4 || echo "unknown")
163
+ if [[ "$TS_STATUS" == "Running" ]]; then
164
+ ok "Tailscale already connected"
165
+ else
166
+ log "Joining Tailscale with auth key..."
167
+ sudo tailscale up --authkey="$TAILSCALE_KEY" --hostname="$DEVICE_ID"
168
+ ok "Tailscale joined as $DEVICE_ID"
169
+ fi
170
+ fi
171
+ fi
172
+
173
+ # Get Tailscale IP
174
+ TAILSCALE_IP=""
175
+ if command -v tailscale &>/dev/null; then
176
+ TAILSCALE_IP=$(tailscale ip -4 2>/dev/null || echo "")
177
+ fi
178
+ if [[ -z "$TAILSCALE_IP" ]]; then
179
+ TAILSCALE_IP=$(ifconfig | grep "inet " | grep -v 127.0.0.1 | head -1 | awk '{print $2}')
180
+ warn "Could not get Tailscale IP — using local IP: $TAILSCALE_IP"
181
+ else
182
+ ok "Tailscale IP: $TAILSCALE_IP"
183
+ fi
184
+
185
+ # ── Step 3: Create directory structure ───────────────────────────────────────
186
+ step "Create directory structure"
187
+
188
+ mkdir -p "$APP_DIR"
189
+ mkdir -p "$APP_DIR/data"
190
+ ok "Directory structure ready: $APP_DIR"
191
+
192
+ # ── Step 4: Clone or update repo ────────────────────────────────────────────
193
+ step "Clone/update AIVA app repository"
194
+
195
+ if [[ -d "$APP_DIR/.git" ]]; then
196
+ log "Repository already exists — pulling latest..."
197
+ cd "$APP_DIR"
198
+ git fetch origin
199
+ git reset --hard origin/main
200
+ ok "Repository updated (git pull)"
201
+ else
202
+ # Check if directory has files (avoid clobbering)
203
+ if [[ -n "$(ls -A "$APP_DIR" 2>/dev/null)" ]]; then
204
+ log "Directory not empty but no git repo — backing up and cloning fresh"
205
+ mv "$APP_DIR" "${APP_DIR}.bak.$(date +%s)"
206
+ mkdir -p "$APP_DIR"
207
+ fi
208
+ log "Cloning repository..."
209
+ git clone "$REPO_URL" "$APP_DIR"
210
+ ok "Repository cloned"
211
+ fi
212
+
213
+ cd "$APP_DIR"
214
+
215
+ # ── Step 5: Install dependencies ────────────────────────────────────────────
216
+ step "Install Node.js dependencies"
217
+
218
+ cd "$APP_DIR"
219
+ npm install --production 2>&1 | tail -3
220
+ ok "Dependencies installed"
221
+
222
+ # ── Step 6: Configure environment ────────────────────────────────────────────
223
+ step "Configure environment (.env)"
224
+
225
+ ENV_FILE="$APP_DIR/.env"
226
+
227
+ # Only create/update if needed — preserve existing keys
228
+ if [[ -f "$ENV_FILE" ]]; then
229
+ log "Existing .env found — merging configuration"
230
+ # Helper: set a key in .env (update if exists, append if not)
231
+ set_env() {
232
+ local key="$1" val="$2"
233
+ if grep -q "^${key}=" "$ENV_FILE" 2>/dev/null; then
234
+ sed -i '' "s|^${key}=.*|${key}=${val}|" "$ENV_FILE"
235
+ else
236
+ echo "${key}=${val}" >> "$ENV_FILE"
237
+ fi
238
+ }
239
+ else
240
+ log "Creating new .env file"
241
+ touch "$ENV_FILE"
242
+ set_env() {
243
+ echo "$1=$2" >> "$ENV_FILE"
244
+ }
245
+ fi
246
+
247
+ set_env "DEVICE_ID" "$DEVICE_ID"
248
+ set_env "CLIENT_NAME" "$CLIENT_NAME"
249
+ set_env "DASHBOARD_URL" "$DASHBOARD_URL"
250
+ set_env "APP_PORT" "$APP_PORT"
251
+ set_env "AUTO_DEPLOY_PORT" "$DEPLOY_PORT"
252
+
253
+ if [[ -n "$WEBHOOK_SECRET" ]]; then
254
+ set_env "GITHUB_WEBHOOK_SECRET" "$WEBHOOK_SECRET"
255
+ fi
256
+
257
+ # Ensure CODING_ENABLED defaults to false for client devices
258
+ if ! grep -q "^CODING_ENABLED=" "$ENV_FILE" 2>/dev/null; then
259
+ set_env "CODING_ENABLED" "false"
260
+ fi
261
+
262
+ ok "Environment configured"
263
+
264
+ # ── Step 7: Start AIVA app ──────────────────────────────────────────────────
265
+ step "Start AIVA app service"
266
+
267
+ PLIST_DIR="$HOME/Library/LaunchAgents"
268
+ AIVA_PLIST="$PLIST_DIR/com.aiva.app.plist"
269
+ DEPLOY_PLIST="$PLIST_DIR/com.aiva.auto-deploy.plist"
270
+
271
+ # Prefer PM2 if available, otherwise LaunchAgent
272
+ if command -v pm2 &>/dev/null; then
273
+ log "PM2 detected — using PM2 for process management"
274
+
275
+ # Stop existing if running
276
+ pm2 delete aiva-app 2>/dev/null || true
277
+ pm2 delete aiva-auto-deploy 2>/dev/null || true
278
+
279
+ cd "$APP_DIR"
280
+ pm2 start server.js --name aiva-app --cwd "$APP_DIR"
281
+ pm2 start auto-deploy.js --name aiva-auto-deploy --cwd "$APP_DIR"
282
+ pm2 save
283
+
284
+ # Ensure PM2 starts on boot
285
+ pm2 startup 2>/dev/null || true
286
+
287
+ ok "AIVA app started via PM2"
288
+ else
289
+ log "PM2 not found — using macOS LaunchAgent"
290
+ mkdir -p "$PLIST_DIR"
291
+
292
+ # AIVA App LaunchAgent
293
+ cat > "$AIVA_PLIST" << PLIST
294
+ <?xml version="1.0" encoding="UTF-8"?>
295
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
296
+ <plist version="1.0">
297
+ <dict>
298
+ <key>Label</key>
299
+ <string>com.aiva.app</string>
300
+ <key>ProgramArguments</key>
301
+ <array>
302
+ <string>$(which node)</string>
303
+ <string>${APP_DIR}/server.js</string>
304
+ </array>
305
+ <key>WorkingDirectory</key>
306
+ <string>${APP_DIR}</string>
307
+ <key>EnvironmentVariables</key>
308
+ <dict>
309
+ <key>PATH</key>
310
+ <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
311
+ <key>HOME</key>
312
+ <string>${HOME}</string>
313
+ </dict>
314
+ <key>RunAtLoad</key>
315
+ <true/>
316
+ <key>KeepAlive</key>
317
+ <true/>
318
+ <key>StandardOutPath</key>
319
+ <string>${APP_DIR}/data/aiva-app.log</string>
320
+ <key>StandardErrorPath</key>
321
+ <string>${APP_DIR}/data/aiva-app-error.log</string>
322
+ </dict>
323
+ </plist>
324
+ PLIST
325
+
326
+ # Auto-Deploy LaunchAgent
327
+ cat > "$DEPLOY_PLIST" << PLIST
328
+ <?xml version="1.0" encoding="UTF-8"?>
329
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
330
+ <plist version="1.0">
331
+ <dict>
332
+ <key>Label</key>
333
+ <string>com.aiva.auto-deploy</string>
334
+ <key>ProgramArguments</key>
335
+ <array>
336
+ <string>$(which node)</string>
337
+ <string>${APP_DIR}/auto-deploy.js</string>
338
+ </array>
339
+ <key>WorkingDirectory</key>
340
+ <string>${APP_DIR}</string>
341
+ <key>EnvironmentVariables</key>
342
+ <dict>
343
+ <key>PATH</key>
344
+ <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
345
+ <key>HOME</key>
346
+ <string>${HOME}</string>
347
+ </dict>
348
+ <key>RunAtLoad</key>
349
+ <true/>
350
+ <key>KeepAlive</key>
351
+ <true/>
352
+ <key>StandardOutPath</key>
353
+ <string>${APP_DIR}/data/auto-deploy.log</string>
354
+ <key>StandardErrorPath</key>
355
+ <string>${APP_DIR}/data/auto-deploy-error.log</string>
356
+ </dict>
357
+ </plist>
358
+ PLIST
359
+
360
+ # Unload then load (idempotent)
361
+ launchctl unload "$AIVA_PLIST" 2>/dev/null || true
362
+ launchctl load "$AIVA_PLIST"
363
+ launchctl unload "$DEPLOY_PLIST" 2>/dev/null || true
364
+ launchctl load "$DEPLOY_PLIST"
365
+
366
+ ok "AIVA app started via LaunchAgent"
367
+ fi
368
+
369
+ # ── Step 8: Disable sleep ───────────────────────────────────────────────────
370
+ step "Configure power management (prevent sleep)"
371
+
372
+ sudo pmset -a sleep 0 displaysleep 0 disksleep 0 2>/dev/null && \
373
+ ok "Sleep disabled (pmset)" || \
374
+ warn "Could not set pmset — may need sudo password"
375
+
376
+ # ── Step 9: Register with dashboard ─────────────────────────────────────────
377
+ step "Register with dashboard"
378
+
379
+ # Wait a moment for the app to start
380
+ sleep 3
381
+
382
+ # Get macOS version and Node version for registration
383
+ OS_VERSION=$(sw_vers -productVersion)
384
+ NODE_VER=$(node --version)
385
+ MAC_ADDR=$(ifconfig en0 2>/dev/null | grep ether | awk '{print $2}' || echo "unknown")
386
+
387
+ REGISTER_BODY=$(cat << EOF
388
+ {
389
+ "deviceId": "${DEVICE_ID}",
390
+ "clientName": "${CLIENT_NAME}",
391
+ "tailscaleIp": "${TAILSCALE_IP}",
392
+ "macAddress": "${MAC_ADDR}",
393
+ "osVersion": "${OS_VERSION}",
394
+ "nodeVersion": "${NODE_VER}",
395
+ "appPath": "${APP_DIR}",
396
+ "sshUser": "${CURRENT_USER}"
397
+ }
398
+ EOF
399
+ )
400
+
401
+ REGISTER_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \
402
+ "${DASHBOARD_URL}/api/devices/register" \
403
+ -H "Content-Type: application/json" \
404
+ -d "$REGISTER_BODY" 2>/dev/null || echo -e "\n000")
405
+
406
+ HTTP_CODE=$(echo "$REGISTER_RESPONSE" | tail -1)
407
+ RESPONSE_BODY=$(echo "$REGISTER_RESPONSE" | sed '$d')
408
+
409
+ if [[ "$HTTP_CODE" == "200" || "$HTTP_CODE" == "201" ]]; then
410
+ ok "Registered with dashboard (HTTP $HTTP_CODE)"
411
+ log "Response: $RESPONSE_BODY"
412
+ else
413
+ warn "Dashboard registration returned HTTP $HTTP_CODE — device may need manual registration"
414
+ [[ -n "$RESPONSE_BODY" ]] && log "Response: $RESPONSE_BODY"
415
+ fi
416
+
417
+ # ── Step 10: Self-test ───────────────────────────────────────────────────────
418
+ step "Run self-test"
419
+
420
+ sleep 2
421
+
422
+ # Test AIVA app
423
+ APP_CHECK=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:${APP_PORT}" 2>/dev/null || echo "000")
424
+ if [[ "$APP_CHECK" == "200" ]]; then
425
+ ok "AIVA app responding on port $APP_PORT"
426
+ else
427
+ warn "AIVA app returned HTTP $APP_CHECK on port $APP_PORT (may still be starting)"
428
+ fi
429
+
430
+ # Test auto-deploy
431
+ DEPLOY_CHECK=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:${DEPLOY_PORT}" 2>/dev/null || echo "000")
432
+ if [[ "$DEPLOY_CHECK" == "200" || "$DEPLOY_CHECK" == "404" ]]; then
433
+ ok "Auto-deploy service responding on port $DEPLOY_PORT"
434
+ else
435
+ warn "Auto-deploy returned HTTP $DEPLOY_CHECK on port $DEPLOY_PORT"
436
+ fi
437
+
438
+ # Test git status
439
+ cd "$APP_DIR"
440
+ GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
441
+ GIT_HASH=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
442
+ ok "Git: branch=$GIT_BRANCH commit=$GIT_HASH"
443
+
444
+ # ── Summary ──────────────────────────────────────────────────────────────────
445
+ print_summary() {
446
+ echo ""
447
+ echo -e "${BOLD}${CYAN}╔══════════════════════════════════════════════╗${NC}"
448
+ echo -e "${BOLD}${CYAN}║ Onboarding Summary ║${NC}"
449
+ echo -e "${BOLD}${CYAN}╚══════════════════════════════════════════════╝${NC}"
450
+ echo ""
451
+ for result in "${step_results[@]}"; do
452
+ echo -e " $result"
453
+ done
454
+ echo ""
455
+ echo -e " ${BOLD}Device ID:${NC} $DEVICE_ID"
456
+ echo -e " ${BOLD}Client:${NC} $CLIENT_NAME"
457
+ echo -e " ${BOLD}Tailscale IP:${NC} $TAILSCALE_IP"
458
+ echo -e " ${BOLD}App URL:${NC} http://${TAILSCALE_IP}:${APP_PORT}"
459
+ echo -e " ${BOLD}App Dir:${NC} $APP_DIR"
460
+ echo -e " ${BOLD}Git:${NC} $GIT_BRANCH @ $GIT_HASH"
461
+ echo ""
462
+ echo -e " ${GREEN}${BOLD}Onboarding complete!${NC}"
463
+ echo ""
464
+ }
465
+
466
+ print_summary