@rubytech/create-maxy 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 (181) hide show
  1. package/dist/index.js +428 -0
  2. package/package.json +31 -0
  3. package/payload/maxy/.env.example +12 -0
  4. package/payload/maxy/app/admin/components/ActivityTimeline.tsx +348 -0
  5. package/payload/maxy/app/admin/components/MarkdownMessage.tsx +40 -0
  6. package/payload/maxy/app/api/admin/chat/route.ts +72 -0
  7. package/payload/maxy/app/api/admin/logs/route.ts +40 -0
  8. package/payload/maxy/app/api/admin/session/route.ts +74 -0
  9. package/payload/maxy/app/api/chat/route.ts +72 -0
  10. package/payload/maxy/app/api/health/route.ts +26 -0
  11. package/payload/maxy/app/api/onboarding/claude-auth/route.ts +216 -0
  12. package/payload/maxy/app/api/onboarding/set-pin/route.ts +44 -0
  13. package/payload/maxy/app/api/session/route.ts +51 -0
  14. package/payload/maxy/app/api/telegram/webhook/route.ts +107 -0
  15. package/payload/maxy/app/apple-icon.png +0 -0
  16. package/payload/maxy/app/bot/page.tsx +373 -0
  17. package/payload/maxy/app/favicon.ico +0 -0
  18. package/payload/maxy/app/globals.css +1681 -0
  19. package/payload/maxy/app/layout.tsx +58 -0
  20. package/payload/maxy/app/lib/claude-agent.ts +503 -0
  21. package/payload/maxy/app/og/layout.tsx +15 -0
  22. package/payload/maxy/app/og/page.tsx +252 -0
  23. package/payload/maxy/app/page.tsx +594 -0
  24. package/payload/maxy/app/privacy/page.tsx +72 -0
  25. package/payload/maxy/app/public/page.tsx +266 -0
  26. package/payload/maxy/next.config.mjs +26 -0
  27. package/payload/maxy/package-lock.json +2198 -0
  28. package/payload/maxy/package.json +25 -0
  29. package/payload/maxy/proxy.ts +41 -0
  30. package/payload/maxy/public/brand/claude.png +0 -0
  31. package/payload/maxy/public/brand/maxy-black.png +0 -0
  32. package/payload/maxy/public/brand/maxy.png +0 -0
  33. package/payload/maxy/public/favicon.ico +0 -0
  34. package/payload/maxy/public/og-landscape.png +0 -0
  35. package/payload/maxy/public/og-portrait.png +0 -0
  36. package/payload/maxy/public/og-square.png +0 -0
  37. package/payload/maxy/public/pi-5.jpg +0 -0
  38. package/payload/maxy/public/robots.txt +5 -0
  39. package/payload/maxy/tsconfig.json +41 -0
  40. package/payload/maxy/tsconfig.tsbuildinfo +1 -0
  41. package/payload/maxy/ui.md +28 -0
  42. package/payload/platform/config/cloudflared.yml +17 -0
  43. package/payload/platform/knowledge/maxy.md +161 -0
  44. package/payload/platform/neo4j/schema.cypher +108 -0
  45. package/payload/platform/package-lock.json +1835 -0
  46. package/payload/platform/package.json +17 -0
  47. package/payload/platform/plugins/admin/PLUGIN.md +24 -0
  48. package/payload/platform/plugins/admin/hooks/pre-tool-use.sh +56 -0
  49. package/payload/platform/plugins/admin/hooks/session-start.sh +20 -0
  50. package/payload/platform/plugins/admin/mcp/dist/index.d.ts +2 -0
  51. package/payload/platform/plugins/admin/mcp/dist/index.d.ts.map +1 -0
  52. package/payload/platform/plugins/admin/mcp/dist/index.js +149 -0
  53. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -0
  54. package/payload/platform/plugins/admin/mcp/package.json +18 -0
  55. package/payload/platform/plugins/anthropic/PLUGIN.md +30 -0
  56. package/payload/platform/plugins/anthropic/references/setup-guide.md +146 -0
  57. package/payload/platform/plugins/business-assistant/PLUGIN.md +46 -0
  58. package/payload/platform/plugins/business-assistant/references/crm.md +112 -0
  59. package/payload/platform/plugins/business-assistant/references/document-management.md +96 -0
  60. package/payload/platform/plugins/business-assistant/references/escalation.md +126 -0
  61. package/payload/platform/plugins/business-assistant/references/invoicing.md +163 -0
  62. package/payload/platform/plugins/business-assistant/references/quoting.md +56 -0
  63. package/payload/platform/plugins/business-assistant/references/scheduling.md +127 -0
  64. package/payload/platform/plugins/cloudflare/PLUGIN.md +31 -0
  65. package/payload/platform/plugins/cloudflare/mcp/dist/index.d.ts +2 -0
  66. package/payload/platform/plugins/cloudflare/mcp/dist/index.d.ts.map +1 -0
  67. package/payload/platform/plugins/cloudflare/mcp/dist/index.js +174 -0
  68. package/payload/platform/plugins/cloudflare/mcp/dist/index.js.map +1 -0
  69. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.d.ts +45 -0
  70. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.d.ts.map +1 -0
  71. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.js +256 -0
  72. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.js.map +1 -0
  73. package/payload/platform/plugins/cloudflare/mcp/package.json +18 -0
  74. package/payload/platform/plugins/cloudflare/references/setup-guide.md +110 -0
  75. package/payload/platform/plugins/contacts/PLUGIN.md +18 -0
  76. package/payload/platform/plugins/contacts/mcp/dist/index.d.ts +2 -0
  77. package/payload/platform/plugins/contacts/mcp/dist/index.d.ts.map +1 -0
  78. package/payload/platform/plugins/contacts/mcp/dist/index.js +182 -0
  79. package/payload/platform/plugins/contacts/mcp/dist/index.js.map +1 -0
  80. package/payload/platform/plugins/contacts/mcp/dist/lib/neo4j.d.ts +5 -0
  81. package/payload/platform/plugins/contacts/mcp/dist/lib/neo4j.d.ts.map +1 -0
  82. package/payload/platform/plugins/contacts/mcp/dist/lib/neo4j.js +34 -0
  83. package/payload/platform/plugins/contacts/mcp/dist/lib/neo4j.js.map +1 -0
  84. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.d.ts +19 -0
  85. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.d.ts.map +1 -0
  86. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.js +68 -0
  87. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.js.map +1 -0
  88. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-list.d.ts +22 -0
  89. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-list.d.ts.map +1 -0
  90. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-list.js +46 -0
  91. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-list.js.map +1 -0
  92. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.d.ts +20 -0
  93. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.d.ts.map +1 -0
  94. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.js +56 -0
  95. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.js.map +1 -0
  96. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-update.d.ts +13 -0
  97. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-update.d.ts.map +1 -0
  98. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-update.js +54 -0
  99. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-update.js.map +1 -0
  100. package/payload/platform/plugins/contacts/mcp/package.json +19 -0
  101. package/payload/platform/plugins/documents/PLUGIN.md +12 -0
  102. package/payload/platform/plugins/documents/mcp/dist/index.d.ts +2 -0
  103. package/payload/platform/plugins/documents/mcp/dist/index.d.ts.map +1 -0
  104. package/payload/platform/plugins/documents/mcp/dist/index.js +82 -0
  105. package/payload/platform/plugins/documents/mcp/dist/index.js.map +1 -0
  106. package/payload/platform/plugins/documents/mcp/package.json +20 -0
  107. package/payload/platform/plugins/memory/PLUGIN.md +17 -0
  108. package/payload/platform/plugins/memory/mcp/dist/index.d.ts +2 -0
  109. package/payload/platform/plugins/memory/mcp/dist/index.d.ts.map +1 -0
  110. package/payload/platform/plugins/memory/mcp/dist/index.js +164 -0
  111. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -0
  112. package/payload/platform/plugins/memory/mcp/dist/lib/embeddings.d.ts +3 -0
  113. package/payload/platform/plugins/memory/mcp/dist/lib/embeddings.d.ts.map +1 -0
  114. package/payload/platform/plugins/memory/mcp/dist/lib/embeddings.js +29 -0
  115. package/payload/platform/plugins/memory/mcp/dist/lib/embeddings.js.map +1 -0
  116. package/payload/platform/plugins/memory/mcp/dist/lib/neo4j.d.ts +5 -0
  117. package/payload/platform/plugins/memory/mcp/dist/lib/neo4j.d.ts.map +1 -0
  118. package/payload/platform/plugins/memory/mcp/dist/lib/neo4j.js +34 -0
  119. package/payload/platform/plugins/memory/mcp/dist/lib/neo4j.js.map +1 -0
  120. package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.d.ts +8 -0
  121. package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.d.ts.map +1 -0
  122. package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.js +71 -0
  123. package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.js.map +1 -0
  124. package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.d.ts +24 -0
  125. package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.d.ts.map +1 -0
  126. package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.js +125 -0
  127. package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.js.map +1 -0
  128. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts +18 -0
  129. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts.map +1 -0
  130. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js +56 -0
  131. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js.map +1 -0
  132. package/payload/platform/plugins/memory/mcp/package.json +19 -0
  133. package/payload/platform/plugins/sales/PLUGIN.md +65 -0
  134. package/payload/platform/plugins/sales/references/close-tracking.md +76 -0
  135. package/payload/platform/plugins/sales/references/closing-framework.md +108 -0
  136. package/payload/platform/plugins/sales/references/comparisons.md +99 -0
  137. package/payload/platform/plugins/sales/references/competitive-positioning.md +51 -0
  138. package/payload/platform/plugins/sales/references/faq.md +62 -0
  139. package/payload/platform/plugins/sales/references/objection-handling.md +157 -0
  140. package/payload/platform/plugins/sales/references/pricing.md +71 -0
  141. package/payload/platform/plugins/sales/references/waitlist.md +23 -0
  142. package/payload/platform/plugins/scheduling/PLUGIN.md +12 -0
  143. package/payload/platform/plugins/scheduling/mcp/dist/index.d.ts +2 -0
  144. package/payload/platform/plugins/scheduling/mcp/dist/index.d.ts.map +1 -0
  145. package/payload/platform/plugins/scheduling/mcp/dist/index.js +13 -0
  146. package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -0
  147. package/payload/platform/plugins/scheduling/mcp/package.json +18 -0
  148. package/payload/platform/plugins/telegram/PLUGIN.md +31 -0
  149. package/payload/platform/plugins/telegram/mcp/dist/index.d.ts +2 -0
  150. package/payload/platform/plugins/telegram/mcp/dist/index.d.ts.map +1 -0
  151. package/payload/platform/plugins/telegram/mcp/dist/index.js +101 -0
  152. package/payload/platform/plugins/telegram/mcp/dist/index.js.map +1 -0
  153. package/payload/platform/plugins/telegram/mcp/dist/lib/telegram.d.ts +27 -0
  154. package/payload/platform/plugins/telegram/mcp/dist/lib/telegram.d.ts.map +1 -0
  155. package/payload/platform/plugins/telegram/mcp/dist/lib/telegram.js +41 -0
  156. package/payload/platform/plugins/telegram/mcp/dist/lib/telegram.js.map +1 -0
  157. package/payload/platform/plugins/telegram/mcp/dist/tools/message-history.d.ts +16 -0
  158. package/payload/platform/plugins/telegram/mcp/dist/tools/message-history.d.ts.map +1 -0
  159. package/payload/platform/plugins/telegram/mcp/dist/tools/message-history.js +62 -0
  160. package/payload/platform/plugins/telegram/mcp/dist/tools/message-history.js.map +1 -0
  161. package/payload/platform/plugins/telegram/mcp/dist/tools/message.d.ts +20 -0
  162. package/payload/platform/plugins/telegram/mcp/dist/tools/message.d.ts.map +1 -0
  163. package/payload/platform/plugins/telegram/mcp/dist/tools/message.js +34 -0
  164. package/payload/platform/plugins/telegram/mcp/dist/tools/message.js.map +1 -0
  165. package/payload/platform/plugins/telegram/mcp/package.json +19 -0
  166. package/payload/platform/plugins/telegram/references/setup-guide.md +50 -0
  167. package/payload/platform/plugins/web/PLUGIN.md +12 -0
  168. package/payload/platform/plugins/web/mcp/dist/index.d.ts +2 -0
  169. package/payload/platform/plugins/web/mcp/dist/index.d.ts.map +1 -0
  170. package/payload/platform/plugins/web/mcp/dist/index.js +12 -0
  171. package/payload/platform/plugins/web/mcp/dist/index.js.map +1 -0
  172. package/payload/platform/plugins/web/mcp/package.json +18 -0
  173. package/payload/platform/scripts/seed-neo4j.sh +73 -0
  174. package/payload/platform/scripts/setup.sh +177 -0
  175. package/payload/platform/scripts/start.sh +62 -0
  176. package/payload/platform/templates/account.json +4 -0
  177. package/payload/platform/templates/agents/admin/IDENTITY.md +28 -0
  178. package/payload/platform/templates/agents/admin/SOUL.md +1 -0
  179. package/payload/platform/templates/agents/public/IDENTITY.md +21 -0
  180. package/payload/platform/templates/agents/public/SOUL.md +1 -0
  181. package/payload/platform/tsconfig.base.json +18 -0
@@ -0,0 +1,216 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { spawn, spawnSync, execFileSync } from 'node:child_process'
3
+ import { createConnection } from 'node:net'
4
+ import { mkdirSync, openSync, closeSync, writeFileSync, writeSync } from 'node:fs'
5
+ import { resolve } from 'node:path'
6
+ import { homedir } from 'node:os'
7
+
8
+ const LOG_DIR = resolve(homedir(), '.maxy/logs')
9
+
10
+ function ensureLogDir(): void {
11
+ mkdirSync(LOG_DIR, { recursive: true })
12
+ }
13
+
14
+ function logPath(name: string): string {
15
+ return resolve(LOG_DIR, `${name}.log`)
16
+ }
17
+
18
+ function checkAuthStatus(): boolean {
19
+ try {
20
+ execFileSync('claude', ['auth', 'status'], { encoding: 'utf-8', timeout: 5000 })
21
+ return true
22
+ } catch {
23
+ return false
24
+ }
25
+ }
26
+
27
+ function killVncStack(): void {
28
+ spawnSync('pkill', ['-f', 'Xtigervnc :99'], { stdio: 'pipe' })
29
+ spawnSync('pkill', ['-f', 'websockify.*6080'], { stdio: 'pipe' })
30
+ spawnSync('pkill', ['-f', 'chromium'], { stdio: 'pipe' })
31
+ spawnSync('rm', ['-f', '/tmp/.X99-lock', '/tmp/.X11-unix/X99'], { stdio: 'pipe' })
32
+ }
33
+
34
+ function killChromium(): void {
35
+ spawnSync('pkill', ['-f', 'chromium'], { stdio: 'pipe' })
36
+ }
37
+
38
+ function sleep(ms: number): Promise<void> {
39
+ return new Promise(r => setTimeout(r, ms))
40
+ }
41
+
42
+ async function waitForPort(port: number, timeoutMs = 12000): Promise<boolean> {
43
+ const deadline = Date.now() + timeoutMs
44
+ while (Date.now() < deadline) {
45
+ const ready = await new Promise<boolean>(resolve => {
46
+ const socket = createConnection(port, '127.0.0.1')
47
+ socket.setTimeout(500)
48
+ socket.once('connect', () => { socket.destroy(); resolve(true) })
49
+ socket.once('error', () => { socket.destroy(); resolve(false) })
50
+ socket.once('timeout', () => { socket.destroy(); resolve(false) })
51
+ })
52
+ if (ready) return true
53
+ await sleep(300)
54
+ }
55
+ return false
56
+ }
57
+
58
+ /**
59
+ * GET /api/onboarding/claude-auth
60
+ * Returns auth status.
61
+ */
62
+ export async function GET() {
63
+ return NextResponse.json({ authenticated: checkAuthStatus() })
64
+ }
65
+
66
+ /**
67
+ * POST /api/onboarding/claude-auth
68
+ * - {} — start VNC stack + `claude auth login`
69
+ * - { action: "wait" } — poll for completion, kills chromium when authenticated
70
+ * - { action: "stop" } — kill VNC stack (manual cancel or post-auth cleanup)
71
+ */
72
+ export async function POST(req: Request) {
73
+ let body: { action?: string } = {}
74
+ try { body = await req.json() } catch { /* empty = start */ }
75
+
76
+ if (body.action === 'stop') {
77
+ killVncStack()
78
+ return NextResponse.json({ stopped: true })
79
+ }
80
+
81
+ if (body.action === 'logout') {
82
+ try {
83
+ execFileSync('claude', ['auth', 'logout'], { encoding: 'utf-8', timeout: 8000 })
84
+ } catch { /* ignore — logout may fail if already logged out */ }
85
+ return NextResponse.json({ logged_out: true })
86
+ }
87
+
88
+ if (body.action === 'wait') {
89
+ const authenticated = checkAuthStatus()
90
+ if (authenticated) killChromium()
91
+ return NextResponse.json({ completed: authenticated, authenticated })
92
+ }
93
+
94
+ ensureLogDir()
95
+ killVncStack()
96
+ await sleep(400)
97
+
98
+ writeFileSync(logPath('tigervnc'), '')
99
+ writeFileSync(logPath('websockify'), '')
100
+ writeFileSync(logPath('claude-auth'), '')
101
+ writeFileSync(logPath('chromium'), '')
102
+
103
+ // 1. Xtigervnc — virtual X11 display :99 + VNC server on :5900, bypasses Wayland
104
+ const tigervncFd = openSync(logPath('tigervnc'), 'a')
105
+ spawn('Xtigervnc', [
106
+ ':99',
107
+ '-geometry', '1280x800',
108
+ '-depth', '24',
109
+ '-rfbport', '5900',
110
+ '-localhost',
111
+ '-SecurityTypes', 'None',
112
+ '-AlwaysShared',
113
+ ], {
114
+ detached: true,
115
+ stdio: ['ignore', tigervncFd, tigervncFd],
116
+ }).unref()
117
+
118
+ const vncReady = await waitForPort(5900)
119
+ if (!vncReady) {
120
+ return NextResponse.json({ error: 'VNC server failed to start' }, { status: 500 })
121
+ }
122
+
123
+ // 2. websockify — WebSocket bridge on :6080, serves noVNC static files
124
+ const websockifyFd = openSync(logPath('websockify'), 'a')
125
+ spawn('websockify', ['--web', '/usr/share/novnc', '6080', 'localhost:5900'], {
126
+ detached: true,
127
+ stdio: ['ignore', websockifyFd, websockifyFd],
128
+ }).unref()
129
+
130
+ const wsReady = await waitForPort(6080)
131
+ if (!wsReady) {
132
+ return NextResponse.json({ error: 'WebSocket proxy failed to start' }, { status: 500 })
133
+ }
134
+
135
+ // Chromium launcher: finds the right binary and injects flags that suppress
136
+ // the keyring prompt and force X11 display :99.
137
+ const binDir = resolve(homedir(), '.maxy/bin')
138
+ mkdirSync(binDir, { recursive: true })
139
+ const chromiumWrapper = resolve(binDir, 'chromium')
140
+ writeFileSync(chromiumWrapper, `#!/bin/bash
141
+ LOG="${homedir()}/.maxy/logs/chromium.log"
142
+ echo "==== [$(date)] chromium wrapper ====" >> "$LOG"
143
+ echo " DISPLAY=$DISPLAY WAYLAND=$WAYLAND_DISPLAY XDG_SESSION_TYPE=$XDG_SESSION_TYPE" >> "$LOG"
144
+ echo " args: $*" >> "$LOG"
145
+
146
+ # Check the virtual display is up
147
+ if DISPLAY=:99 xdpyinfo >/dev/null 2>&1; then
148
+ echo " xdpyinfo :99 OK" >> "$LOG"
149
+ else
150
+ echo " xdpyinfo :99 FAILED — display not ready" >> "$LOG"
151
+ fi
152
+
153
+ # Try the actual chromium binary first (/usr/bin/chromium on Pi Bookworm).
154
+ # /usr/bin/chromium-browser is a Pi shell script that injects flags which may
155
+ # override --ozone-platform=x11 and break rendering on a virtual X display.
156
+ BIN=""
157
+ for P in /usr/bin/chromium /usr/lib/chromium/chromium /usr/lib/chromium-browser/chromium-browser /usr/bin/chromium-browser /snap/bin/chromium; do
158
+ [ -x "$P" ] && BIN="$P" && break
159
+ done
160
+ if [ -z "$BIN" ]; then
161
+ echo " ERROR: no chromium binary found" >> "$LOG"
162
+ exit 1
163
+ fi
164
+ echo " launching $BIN" >> "$LOG"
165
+
166
+ # nohup detaches chromium from this wrapper's process group so it outlives
167
+ # claude auth login exiting (e.g. on timeout).
168
+ nohup "$BIN" \\
169
+ --no-sandbox \\
170
+ --test-type \\
171
+ --disable-dev-shm-usage \\
172
+ --disable-gpu \\
173
+ --password-store=basic \\
174
+ --use-mock-keychain \\
175
+ --ozone-platform=x11 \\
176
+ --no-first-run \\
177
+ --no-default-browser-check \\
178
+ --window-size=1280,800 \\
179
+ --window-position=0,0 \\
180
+ "$@" >> "$LOG" 2>&1 &
181
+ echo " chromium PID: $!" >> "$LOG"
182
+ `, { mode: 0o755 })
183
+
184
+ // Env: X11 display :99, Wayland suppressed. BROWSER=chromiumWrapper routes
185
+ // claude's browser-open call through our wrapper, which injects the keyring
186
+ // flags and finds the correct binary. This preserves the localhost:33051
187
+ // redirect URI that claude's local auth server needs to complete sign-in.
188
+ const x11Env = {
189
+ ...process.env,
190
+ DISPLAY: ':99',
191
+ WAYLAND_DISPLAY: '',
192
+ XDG_SESSION_TYPE: 'x11',
193
+ BROWSER: chromiumWrapper,
194
+ PATH: `${binDir}:${process.env.PATH ?? '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'}`,
195
+ }
196
+
197
+ // 3. claude auth login — pipe output to capture the auth URL in logs
198
+ const claudeAuthLogFd = openSync(logPath('claude-auth'), 'a')
199
+ const claudeProc = spawn('claude', ['auth', 'login'], {
200
+ env: x11Env,
201
+ stdio: ['ignore', 'pipe', 'pipe'],
202
+ })
203
+ claudeProc.unref()
204
+
205
+ const onClaudeOutput = (chunk: Buffer) => writeSync(claudeAuthLogFd, chunk)
206
+ claudeProc.stdout?.on('data', onClaudeOutput)
207
+ claudeProc.stderr?.on('data', onClaudeOutput)
208
+ claudeProc.once('close', () => closeSync(claudeAuthLogFd))
209
+
210
+ // Wait for claude to open the browser (via $BROWSER → chromiumWrapper).
211
+ // Chromium on Pi takes ~8-10s to render its first frame; give it time before
212
+ // the client connects VNC so the first connection sees actual content.
213
+ await sleep(8000)
214
+
215
+ return NextResponse.json({ started: true })
216
+ }
@@ -0,0 +1,44 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { existsSync, writeFileSync, mkdirSync, readFileSync } from 'node:fs'
3
+ import { resolve } from 'node:path'
4
+ import { createHash } from 'node:crypto'
5
+
6
+ const PERSISTENT_DIR = resolve(process.env.HOME ?? '/root', '.maxy')
7
+ const PIN_FILE = resolve(PERSISTENT_DIR, '.admin-pin')
8
+
9
+ function hashPin(pin: string): string {
10
+ return createHash('sha256').update(pin).digest('hex')
11
+ }
12
+
13
+ /**
14
+ * POST /api/onboarding/set-pin
15
+ * Sets the admin PIN. Only works when no PIN is configured.
16
+ * Body: { pin: string }
17
+ */
18
+ export async function POST(req: Request) {
19
+ if (existsSync(PIN_FILE)) {
20
+ return NextResponse.json(
21
+ { error: 'PIN is already configured.' },
22
+ { status: 409 },
23
+ )
24
+ }
25
+
26
+ let body: { pin: string }
27
+ try {
28
+ body = await req.json()
29
+ } catch {
30
+ return NextResponse.json({ error: 'Invalid request' }, { status: 400 })
31
+ }
32
+
33
+ if (!body.pin || body.pin.length < 4) {
34
+ return NextResponse.json(
35
+ { error: 'PIN must be at least 4 characters.' },
36
+ { status: 400 },
37
+ )
38
+ }
39
+
40
+ mkdirSync(PERSISTENT_DIR, { recursive: true })
41
+ writeFileSync(PIN_FILE, hashPin(body.pin), { mode: 0o600 })
42
+
43
+ return NextResponse.json({ ok: true })
44
+ }
@@ -0,0 +1,51 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { registerSession } from '../../lib/claude-agent'
3
+ import { readFileSync, readdirSync, existsSync } from 'node:fs'
4
+ import { resolve } from 'node:path'
5
+
6
+ const ACCOUNTS_DIR = resolve(process.cwd(), '../platform/config/accounts')
7
+
8
+ /** Find the first account's ID. Phase 0: single account. */
9
+ function getDefaultAccountId(): string | null {
10
+ if (!existsSync(ACCOUNTS_DIR)) return null
11
+ const entries = readdirSync(ACCOUNTS_DIR, { withFileTypes: true })
12
+ for (const entry of entries) {
13
+ if (!entry.isDirectory()) continue
14
+ const configPath = resolve(ACCOUNTS_DIR, entry.name, 'account.json')
15
+ if (!existsSync(configPath)) continue
16
+ const config = JSON.parse(readFileSync(configPath, 'utf-8'))
17
+ return config.accountId
18
+ }
19
+ return null
20
+ }
21
+
22
+ /**
23
+ * POST /api/session
24
+ * Creates a local session for the public chat agent.
25
+ * Returns a session_key for subsequent chat requests.
26
+ */
27
+ export async function POST(req: Request) {
28
+ let body: { session_id: string }
29
+ try {
30
+ body = await req.json()
31
+ } catch {
32
+ return NextResponse.json({ error: 'Invalid request' }, { status: 400 })
33
+ }
34
+
35
+ if (!body.session_id || typeof body.session_id !== 'string') {
36
+ return NextResponse.json({ error: 'session_id required' }, { status: 400 })
37
+ }
38
+
39
+ const accountId = getDefaultAccountId()
40
+ if (!accountId) {
41
+ return NextResponse.json({ error: 'No account configured' }, { status: 503 })
42
+ }
43
+
44
+ const sessionKey = crypto.randomUUID()
45
+ registerSession(sessionKey, 'public', accountId)
46
+
47
+ return NextResponse.json({
48
+ session_key: sessionKey,
49
+ agent_id: 'public',
50
+ })
51
+ }
@@ -0,0 +1,107 @@
1
+ import { NextRequest, NextResponse } from 'next/server'
2
+ import { invokeAgent } from '../../../lib/claude-agent'
3
+
4
+ const TELEGRAM_API = 'https://api.telegram.org'
5
+
6
+ interface TelegramUpdate {
7
+ message?: {
8
+ chat: { id: number }
9
+ from?: { first_name?: string; last_name?: string; username?: string }
10
+ text?: string
11
+ }
12
+ callback_query?: {
13
+ id: string
14
+ message?: { chat: { id: number } }
15
+ data?: string
16
+ }
17
+ }
18
+
19
+ /**
20
+ * POST /api/telegram/webhook
21
+ * Handles incoming Telegram bot updates.
22
+ * Invokes the public agent and sends the response back via Telegram.
23
+ */
24
+ export async function POST(req: NextRequest) {
25
+ const botToken = process.env.TELEGRAM_PUBLIC_BOT_TOKEN
26
+ if (!botToken) {
27
+ return NextResponse.json({ error: 'Bot token not configured' }, { status: 500 })
28
+ }
29
+
30
+ let update: TelegramUpdate
31
+ try {
32
+ update = await req.json()
33
+ } catch {
34
+ return NextResponse.json({ ok: true })
35
+ }
36
+
37
+ // Handle text messages
38
+ if (update.message?.text) {
39
+ const chatId = update.message.chat.id
40
+ const text = update.message.text
41
+
42
+ // Collect the full response from the agent
43
+ let responseText = ''
44
+ try {
45
+ for await (const event of invokeAgent({ type: 'public' }, text)) {
46
+ if (event.type === 'text') {
47
+ responseText += event.content
48
+ }
49
+ }
50
+ } catch (err) {
51
+ responseText = "I'm having trouble right now. Please try again in a moment."
52
+ }
53
+
54
+ if (responseText) {
55
+ await fetch(`${TELEGRAM_API}/bot${botToken}/sendMessage`, {
56
+ method: 'POST',
57
+ headers: { 'Content-Type': 'application/json' },
58
+ body: JSON.stringify({
59
+ chat_id: chatId,
60
+ text: responseText,
61
+ parse_mode: 'HTML',
62
+ }),
63
+ })
64
+ }
65
+ }
66
+
67
+ // Handle callback queries (inline keyboard buttons)
68
+ if (update.callback_query) {
69
+ const callbackId = update.callback_query.id
70
+ const chatId = update.callback_query.message?.chat.id
71
+ const data = update.callback_query.data
72
+
73
+ // Answer the callback to remove loading indicator
74
+ await fetch(`${TELEGRAM_API}/bot${botToken}/answerCallbackQuery`, {
75
+ method: 'POST',
76
+ headers: { 'Content-Type': 'application/json' },
77
+ body: JSON.stringify({ callback_query_id: callbackId }),
78
+ })
79
+
80
+ if (chatId && data) {
81
+ let responseText = ''
82
+ try {
83
+ for await (const event of invokeAgent({ type: 'public' }, data)) {
84
+ if (event.type === 'text') {
85
+ responseText += event.content
86
+ }
87
+ }
88
+ } catch {
89
+ responseText = "I'm having trouble right now. Please try again in a moment."
90
+ }
91
+
92
+ if (responseText) {
93
+ await fetch(`${TELEGRAM_API}/bot${botToken}/sendMessage`, {
94
+ method: 'POST',
95
+ headers: { 'Content-Type': 'application/json' },
96
+ body: JSON.stringify({
97
+ chat_id: chatId,
98
+ text: responseText,
99
+ parse_mode: 'HTML',
100
+ }),
101
+ })
102
+ }
103
+ }
104
+ }
105
+
106
+ return NextResponse.json({ ok: true })
107
+ }
Binary file