@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.
- package/dist/index.js +428 -0
- package/package.json +31 -0
- package/payload/maxy/.env.example +12 -0
- package/payload/maxy/app/admin/components/ActivityTimeline.tsx +348 -0
- package/payload/maxy/app/admin/components/MarkdownMessage.tsx +40 -0
- package/payload/maxy/app/api/admin/chat/route.ts +72 -0
- package/payload/maxy/app/api/admin/logs/route.ts +40 -0
- package/payload/maxy/app/api/admin/session/route.ts +74 -0
- package/payload/maxy/app/api/chat/route.ts +72 -0
- package/payload/maxy/app/api/health/route.ts +26 -0
- package/payload/maxy/app/api/onboarding/claude-auth/route.ts +216 -0
- package/payload/maxy/app/api/onboarding/set-pin/route.ts +44 -0
- package/payload/maxy/app/api/session/route.ts +51 -0
- package/payload/maxy/app/api/telegram/webhook/route.ts +107 -0
- package/payload/maxy/app/apple-icon.png +0 -0
- package/payload/maxy/app/bot/page.tsx +373 -0
- package/payload/maxy/app/favicon.ico +0 -0
- package/payload/maxy/app/globals.css +1681 -0
- package/payload/maxy/app/layout.tsx +58 -0
- package/payload/maxy/app/lib/claude-agent.ts +503 -0
- package/payload/maxy/app/og/layout.tsx +15 -0
- package/payload/maxy/app/og/page.tsx +252 -0
- package/payload/maxy/app/page.tsx +594 -0
- package/payload/maxy/app/privacy/page.tsx +72 -0
- package/payload/maxy/app/public/page.tsx +266 -0
- package/payload/maxy/next.config.mjs +26 -0
- package/payload/maxy/package-lock.json +2198 -0
- package/payload/maxy/package.json +25 -0
- package/payload/maxy/proxy.ts +41 -0
- package/payload/maxy/public/brand/claude.png +0 -0
- package/payload/maxy/public/brand/maxy-black.png +0 -0
- package/payload/maxy/public/brand/maxy.png +0 -0
- package/payload/maxy/public/favicon.ico +0 -0
- package/payload/maxy/public/og-landscape.png +0 -0
- package/payload/maxy/public/og-portrait.png +0 -0
- package/payload/maxy/public/og-square.png +0 -0
- package/payload/maxy/public/pi-5.jpg +0 -0
- package/payload/maxy/public/robots.txt +5 -0
- package/payload/maxy/tsconfig.json +41 -0
- package/payload/maxy/tsconfig.tsbuildinfo +1 -0
- package/payload/maxy/ui.md +28 -0
- package/payload/platform/config/cloudflared.yml +17 -0
- package/payload/platform/knowledge/maxy.md +161 -0
- package/payload/platform/neo4j/schema.cypher +108 -0
- package/payload/platform/package-lock.json +1835 -0
- package/payload/platform/package.json +17 -0
- package/payload/platform/plugins/admin/PLUGIN.md +24 -0
- package/payload/platform/plugins/admin/hooks/pre-tool-use.sh +56 -0
- package/payload/platform/plugins/admin/hooks/session-start.sh +20 -0
- package/payload/platform/plugins/admin/mcp/dist/index.d.ts +2 -0
- package/payload/platform/plugins/admin/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/admin/mcp/dist/index.js +149 -0
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/admin/mcp/package.json +18 -0
- package/payload/platform/plugins/anthropic/PLUGIN.md +30 -0
- package/payload/platform/plugins/anthropic/references/setup-guide.md +146 -0
- package/payload/platform/plugins/business-assistant/PLUGIN.md +46 -0
- package/payload/platform/plugins/business-assistant/references/crm.md +112 -0
- package/payload/platform/plugins/business-assistant/references/document-management.md +96 -0
- package/payload/platform/plugins/business-assistant/references/escalation.md +126 -0
- package/payload/platform/plugins/business-assistant/references/invoicing.md +163 -0
- package/payload/platform/plugins/business-assistant/references/quoting.md +56 -0
- package/payload/platform/plugins/business-assistant/references/scheduling.md +127 -0
- package/payload/platform/plugins/cloudflare/PLUGIN.md +31 -0
- package/payload/platform/plugins/cloudflare/mcp/dist/index.d.ts +2 -0
- package/payload/platform/plugins/cloudflare/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/cloudflare/mcp/dist/index.js +174 -0
- package/payload/platform/plugins/cloudflare/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.d.ts +45 -0
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.d.ts.map +1 -0
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.js +256 -0
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.js.map +1 -0
- package/payload/platform/plugins/cloudflare/mcp/package.json +18 -0
- package/payload/platform/plugins/cloudflare/references/setup-guide.md +110 -0
- package/payload/platform/plugins/contacts/PLUGIN.md +18 -0
- package/payload/platform/plugins/contacts/mcp/dist/index.d.ts +2 -0
- package/payload/platform/plugins/contacts/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/index.js +182 -0
- package/payload/platform/plugins/contacts/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/lib/neo4j.d.ts +5 -0
- package/payload/platform/plugins/contacts/mcp/dist/lib/neo4j.d.ts.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/lib/neo4j.js +34 -0
- package/payload/platform/plugins/contacts/mcp/dist/lib/neo4j.js.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.d.ts +19 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.d.ts.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.js +68 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.js.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-list.d.ts +22 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-list.d.ts.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-list.js +46 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-list.js.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.d.ts +20 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.d.ts.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.js +56 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.js.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-update.d.ts +13 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-update.d.ts.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-update.js +54 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-update.js.map +1 -0
- package/payload/platform/plugins/contacts/mcp/package.json +19 -0
- package/payload/platform/plugins/documents/PLUGIN.md +12 -0
- package/payload/platform/plugins/documents/mcp/dist/index.d.ts +2 -0
- package/payload/platform/plugins/documents/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/documents/mcp/dist/index.js +82 -0
- package/payload/platform/plugins/documents/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/documents/mcp/package.json +20 -0
- package/payload/platform/plugins/memory/PLUGIN.md +17 -0
- package/payload/platform/plugins/memory/mcp/dist/index.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/index.js +164 -0
- package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/embeddings.d.ts +3 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/embeddings.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/embeddings.js +29 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/embeddings.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/neo4j.d.ts +5 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/neo4j.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/neo4j.js +34 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/neo4j.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.d.ts +8 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.js +71 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.d.ts +24 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.js +125 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts +18 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js +56 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/package.json +19 -0
- package/payload/platform/plugins/sales/PLUGIN.md +65 -0
- package/payload/platform/plugins/sales/references/close-tracking.md +76 -0
- package/payload/platform/plugins/sales/references/closing-framework.md +108 -0
- package/payload/platform/plugins/sales/references/comparisons.md +99 -0
- package/payload/platform/plugins/sales/references/competitive-positioning.md +51 -0
- package/payload/platform/plugins/sales/references/faq.md +62 -0
- package/payload/platform/plugins/sales/references/objection-handling.md +157 -0
- package/payload/platform/plugins/sales/references/pricing.md +71 -0
- package/payload/platform/plugins/sales/references/waitlist.md +23 -0
- package/payload/platform/plugins/scheduling/PLUGIN.md +12 -0
- package/payload/platform/plugins/scheduling/mcp/dist/index.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/index.js +13 -0
- package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/package.json +18 -0
- package/payload/platform/plugins/telegram/PLUGIN.md +31 -0
- package/payload/platform/plugins/telegram/mcp/dist/index.d.ts +2 -0
- package/payload/platform/plugins/telegram/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/index.js +101 -0
- package/payload/platform/plugins/telegram/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/lib/telegram.d.ts +27 -0
- package/payload/platform/plugins/telegram/mcp/dist/lib/telegram.d.ts.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/lib/telegram.js +41 -0
- package/payload/platform/plugins/telegram/mcp/dist/lib/telegram.js.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/tools/message-history.d.ts +16 -0
- package/payload/platform/plugins/telegram/mcp/dist/tools/message-history.d.ts.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/tools/message-history.js +62 -0
- package/payload/platform/plugins/telegram/mcp/dist/tools/message-history.js.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/tools/message.d.ts +20 -0
- package/payload/platform/plugins/telegram/mcp/dist/tools/message.d.ts.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/tools/message.js +34 -0
- package/payload/platform/plugins/telegram/mcp/dist/tools/message.js.map +1 -0
- package/payload/platform/plugins/telegram/mcp/package.json +19 -0
- package/payload/platform/plugins/telegram/references/setup-guide.md +50 -0
- package/payload/platform/plugins/web/PLUGIN.md +12 -0
- package/payload/platform/plugins/web/mcp/dist/index.d.ts +2 -0
- package/payload/platform/plugins/web/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/web/mcp/dist/index.js +12 -0
- package/payload/platform/plugins/web/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/web/mcp/package.json +18 -0
- package/payload/platform/scripts/seed-neo4j.sh +73 -0
- package/payload/platform/scripts/setup.sh +177 -0
- package/payload/platform/scripts/start.sh +62 -0
- package/payload/platform/templates/account.json +4 -0
- package/payload/platform/templates/agents/admin/IDENTITY.md +28 -0
- package/payload/platform/templates/agents/admin/SOUL.md +1 -0
- package/payload/platform/templates/agents/public/IDENTITY.md +21 -0
- package/payload/platform/templates/agents/public/SOUL.md +1 -0
- 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
|