@rubytech/create-maxy 0.2.10 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +21 -12
- package/package.json +1 -1
- package/payload/maxy/app/admin/page.tsx +15 -20
- package/payload/maxy/proxy.ts +20 -7
- package/payload/maxy/public/maxy-black.png +0 -0
- package/payload/maxy/public/maxy-family.png +0 -0
- package/payload/maxy/public/maxy-girl.png +0 -0
- package/payload/maxy/public/maxy-pi.png +0 -0
- package/payload/maxy/public/maxy-trio.png +0 -0
- package/payload/maxy/public/maxy-woman.png +0 -0
- package/payload/maxy/public/maxy.png +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/dist/index.js
CHANGED
|
@@ -50,7 +50,7 @@ function isArm64() {
|
|
|
50
50
|
// ---------------------------------------------------------------------------
|
|
51
51
|
// Installation steps
|
|
52
52
|
// ---------------------------------------------------------------------------
|
|
53
|
-
const TOTAL = "
|
|
53
|
+
const TOTAL = "10";
|
|
54
54
|
function installSystemDeps() {
|
|
55
55
|
log("1", TOTAL, "System dependencies and network...");
|
|
56
56
|
if (!isLinux()) {
|
|
@@ -101,6 +101,14 @@ function installNodejs() {
|
|
|
101
101
|
spawnSync("bash", ["-c", "curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -"], { stdio: "inherit" });
|
|
102
102
|
shell("apt-get", ["install", "-y", "-qq", "nodejs"], { sudo: true });
|
|
103
103
|
}
|
|
104
|
+
function installClaudeCode() {
|
|
105
|
+
if (commandExists("claude")) {
|
|
106
|
+
log("3", TOTAL, "Claude Code already installed.");
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
log("3", TOTAL, "Installing Claude Code...");
|
|
110
|
+
shell("npm", ["install", "-g", "@anthropic-ai/claude-code"]);
|
|
111
|
+
}
|
|
104
112
|
function resetNeo4jWithFreshPassword() {
|
|
105
113
|
const password = randomBytes(24).toString("base64url");
|
|
106
114
|
console.log(" Resetting Neo4j with fresh password...");
|
|
@@ -165,10 +173,10 @@ function ensureNeo4jPassword() {
|
|
|
165
173
|
}
|
|
166
174
|
function installNeo4j() {
|
|
167
175
|
if (commandExists("neo4j")) {
|
|
168
|
-
log("
|
|
169
|
-
return;
|
|
176
|
+
log("4", TOTAL, "Neo4j already installed.");
|
|
177
|
+
return;
|
|
170
178
|
}
|
|
171
|
-
log("
|
|
179
|
+
log("4", TOTAL, "Installing Neo4j Community Edition 5...");
|
|
172
180
|
if (!isLinux()) {
|
|
173
181
|
throw new Error("Automatic Neo4j installation is only supported on Linux. Install Neo4j 5.11+ manually.");
|
|
174
182
|
}
|
|
@@ -191,21 +199,21 @@ function installNeo4j() {
|
|
|
191
199
|
}
|
|
192
200
|
function installOllama() {
|
|
193
201
|
if (!commandExists("ollama")) {
|
|
194
|
-
log("
|
|
202
|
+
log("5", TOTAL, "Installing Ollama...");
|
|
195
203
|
spawnSync("bash", ["-c", "curl -fsSL https://ollama.ai/install.sh | sh"], { stdio: "inherit" });
|
|
196
204
|
}
|
|
197
205
|
else {
|
|
198
|
-
log("
|
|
206
|
+
log("5", TOTAL, "Ollama already installed.");
|
|
199
207
|
}
|
|
200
208
|
console.log(" Pulling nomic-embed-text model (~300MB)...");
|
|
201
209
|
shell("ollama", ["pull", "nomic-embed-text"]);
|
|
202
210
|
}
|
|
203
211
|
function installCloudflared() {
|
|
204
212
|
if (commandExists("cloudflared")) {
|
|
205
|
-
log("
|
|
213
|
+
log("6", TOTAL, "Cloudflared already installed.");
|
|
206
214
|
return;
|
|
207
215
|
}
|
|
208
|
-
log("
|
|
216
|
+
log("6", TOTAL, "Installing cloudflared...");
|
|
209
217
|
if (!isLinux()) {
|
|
210
218
|
console.log(" Skipping — install manually for your platform.");
|
|
211
219
|
return;
|
|
@@ -217,7 +225,7 @@ function installCloudflared() {
|
|
|
217
225
|
spawnSync("rm", ["-f", debPath]);
|
|
218
226
|
}
|
|
219
227
|
function deployPayload() {
|
|
220
|
-
log("
|
|
228
|
+
log("7", TOTAL, "Deploying Maxy...");
|
|
221
229
|
if (!existsSync(PAYLOAD_DIR)) {
|
|
222
230
|
throw new Error(`Payload not found at ${PAYLOAD_DIR}. Package may be corrupted.`);
|
|
223
231
|
}
|
|
@@ -256,21 +264,21 @@ function deployPayload() {
|
|
|
256
264
|
console.log(` Deployed to ${INSTALL_DIR}`);
|
|
257
265
|
}
|
|
258
266
|
function buildPlatform() {
|
|
259
|
-
log("
|
|
267
|
+
log("8", TOTAL, "Installing dependencies and building...");
|
|
260
268
|
shell("npm", ["install", "--quiet"], { cwd: join(INSTALL_DIR, "platform") });
|
|
261
269
|
shell("npm", ["run", "build"], { cwd: join(INSTALL_DIR, "platform") });
|
|
262
270
|
shell("npm", ["install", "--quiet"], { cwd: join(INSTALL_DIR, "maxy") });
|
|
263
271
|
shell("npm", ["run", "build"], { cwd: join(INSTALL_DIR, "maxy") });
|
|
264
272
|
}
|
|
265
273
|
function setupAccount() {
|
|
266
|
-
log("
|
|
274
|
+
log("9", TOTAL, "Setting up...");
|
|
267
275
|
const seedScript = join(INSTALL_DIR, "platform/scripts/seed-neo4j.sh");
|
|
268
276
|
if (existsSync(seedScript)) {
|
|
269
277
|
shell("bash", [seedScript], { cwd: INSTALL_DIR });
|
|
270
278
|
}
|
|
271
279
|
}
|
|
272
280
|
function installService() {
|
|
273
|
-
log("
|
|
281
|
+
log("10", TOTAL, "Starting Maxy...");
|
|
274
282
|
if (!isLinux()) {
|
|
275
283
|
console.log(" Skipping systemd service (not Linux). Start manually with:");
|
|
276
284
|
console.log(` cd ${INSTALL_DIR}/maxy && npm start`);
|
|
@@ -328,6 +336,7 @@ console.log("");
|
|
|
328
336
|
try {
|
|
329
337
|
installSystemDeps();
|
|
330
338
|
installNodejs();
|
|
339
|
+
installClaudeCode();
|
|
331
340
|
installNeo4j();
|
|
332
341
|
installOllama();
|
|
333
342
|
installCloudflared();
|
package/package.json
CHANGED
|
@@ -19,6 +19,7 @@ export default function AdminPage() {
|
|
|
19
19
|
const [isStreaming, setIsStreaming] = useState(false)
|
|
20
20
|
const messagesEndRef = useRef<HTMLDivElement>(null)
|
|
21
21
|
const inputRef = useRef<HTMLInputElement>(null)
|
|
22
|
+
const pinInputRef = useRef<HTMLInputElement>(null)
|
|
22
23
|
|
|
23
24
|
useEffect(() => {
|
|
24
25
|
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
|
|
@@ -26,6 +27,7 @@ export default function AdminPage() {
|
|
|
26
27
|
|
|
27
28
|
useEffect(() => {
|
|
28
29
|
if (authenticated) inputRef.current?.focus()
|
|
30
|
+
else pinInputRef.current?.focus()
|
|
29
31
|
}, [authenticated])
|
|
30
32
|
|
|
31
33
|
async function handlePinSubmit(e: FormEvent) {
|
|
@@ -40,7 +42,8 @@ export default function AdminPage() {
|
|
|
40
42
|
})
|
|
41
43
|
|
|
42
44
|
if (!res.ok) {
|
|
43
|
-
|
|
45
|
+
const data = await res.json().catch(() => ({}))
|
|
46
|
+
setPinError(data.error || 'Invalid PIN')
|
|
44
47
|
return
|
|
45
48
|
}
|
|
46
49
|
|
|
@@ -48,7 +51,7 @@ export default function AdminPage() {
|
|
|
48
51
|
setSessionKey(data.session_key)
|
|
49
52
|
setAuthenticated(true)
|
|
50
53
|
} catch {
|
|
51
|
-
setPinError('
|
|
54
|
+
setPinError('Could not connect.')
|
|
52
55
|
}
|
|
53
56
|
}
|
|
54
57
|
|
|
@@ -56,13 +59,14 @@ export default function AdminPage() {
|
|
|
56
59
|
if (!text || isStreaming || !sessionKey) return
|
|
57
60
|
|
|
58
61
|
const adminMessage: Message = { role: 'admin', content: text }
|
|
62
|
+
const currentLength = messages.length
|
|
59
63
|
setMessages(prev => [...prev, adminMessage])
|
|
60
64
|
setInput('')
|
|
61
65
|
setIsStreaming(true)
|
|
62
66
|
|
|
63
67
|
const maxyMessage: Message = { role: 'maxy', events: [] }
|
|
64
68
|
setMessages(prev => [...prev, maxyMessage])
|
|
65
|
-
const messageIndex =
|
|
69
|
+
const messageIndex = currentLength + 1
|
|
66
70
|
|
|
67
71
|
try {
|
|
68
72
|
const res = await fetch('/api/admin/chat', {
|
|
@@ -71,9 +75,7 @@ export default function AdminPage() {
|
|
|
71
75
|
body: JSON.stringify({ message: text, session_key: sessionKey }),
|
|
72
76
|
})
|
|
73
77
|
|
|
74
|
-
if (!res.ok)
|
|
75
|
-
throw new Error('Chat request failed')
|
|
76
|
-
}
|
|
78
|
+
if (!res.ok) throw new Error('Chat request failed')
|
|
77
79
|
|
|
78
80
|
const reader = res.body?.getReader()
|
|
79
81
|
if (!reader) throw new Error('No response stream')
|
|
@@ -141,15 +143,16 @@ export default function AdminPage() {
|
|
|
141
143
|
|
|
142
144
|
if (!authenticated) {
|
|
143
145
|
return (
|
|
144
|
-
<div className="chat-page">
|
|
146
|
+
<div className="chat-page admin-page">
|
|
145
147
|
<header className="chat-header">
|
|
146
148
|
<img src="/maxy-black.png" alt="Maxy" className="chat-logo" />
|
|
147
|
-
<h1 className="chat-tagline">
|
|
148
|
-
<p className="chat-intro">
|
|
149
|
+
<h1 className="chat-tagline">Maxy</h1>
|
|
150
|
+
<p className="chat-intro">Convenience as standard.</p>
|
|
149
151
|
</header>
|
|
150
152
|
<div className="admin-pin-form">
|
|
151
153
|
<form onSubmit={handlePinSubmit}>
|
|
152
154
|
<input
|
|
155
|
+
ref={pinInputRef}
|
|
153
156
|
type="password"
|
|
154
157
|
value={pin}
|
|
155
158
|
onChange={e => setPin(e.target.value)}
|
|
@@ -174,12 +177,8 @@ export default function AdminPage() {
|
|
|
174
177
|
<div className="chat-page admin-page">
|
|
175
178
|
<header className="chat-header">
|
|
176
179
|
<img src="/maxy-black.png" alt="Maxy" className="chat-logo" />
|
|
177
|
-
<h1 className="chat-tagline">
|
|
178
|
-
<
|
|
179
|
-
<span className="chat-trust-item">Authenticated</span>
|
|
180
|
-
<span className="chat-trust-sep">{'\u00B7'}</span>
|
|
181
|
-
<span className="chat-trust-item">Full Access</span>
|
|
182
|
-
</div>
|
|
180
|
+
<h1 className="chat-tagline">Maxy</h1>
|
|
181
|
+
<p className="chat-intro">Convenience as standard.</p>
|
|
183
182
|
</header>
|
|
184
183
|
|
|
185
184
|
<div className="chat-messages">
|
|
@@ -214,7 +213,7 @@ export default function AdminPage() {
|
|
|
214
213
|
type="text"
|
|
215
214
|
value={input}
|
|
216
215
|
onChange={e => setInput(e.target.value)}
|
|
217
|
-
placeholder="
|
|
216
|
+
placeholder="Tell Maxy what you need..."
|
|
218
217
|
disabled={isStreaming}
|
|
219
218
|
aria-label="Type your message"
|
|
220
219
|
/>
|
|
@@ -231,10 +230,6 @@ export default function AdminPage() {
|
|
|
231
230
|
</button>
|
|
232
231
|
</form>
|
|
233
232
|
</div>
|
|
234
|
-
|
|
235
|
-
<footer className="chat-footer">
|
|
236
|
-
<a href="https://getmaxy.com">getmaxy.com {'\u2197'}</a>
|
|
237
|
-
</footer>
|
|
238
233
|
</div>
|
|
239
234
|
)
|
|
240
235
|
}
|
package/payload/maxy/proxy.ts
CHANGED
|
@@ -1,13 +1,27 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server'
|
|
2
2
|
|
|
3
|
+
function isLocalAccess(host: string): boolean {
|
|
4
|
+
return host.includes('.local') ||
|
|
5
|
+
host.includes('localhost') ||
|
|
6
|
+
host.includes('127.0.0.1') ||
|
|
7
|
+
host.includes('192.168.') ||
|
|
8
|
+
host.includes('10.') ||
|
|
9
|
+
host.startsWith('172.')
|
|
10
|
+
}
|
|
11
|
+
|
|
3
12
|
export function proxy(request: NextRequest) {
|
|
4
13
|
const host = request.headers.get('host') || ''
|
|
5
14
|
const { pathname } = request.nextUrl
|
|
6
15
|
|
|
7
|
-
//
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
16
|
+
// Local network access (maxy.local:19200, 192.168.x.x, etc.) → admin interface
|
|
17
|
+
// On the local network, the user IS the admin. No public chat on local.
|
|
18
|
+
if (isLocalAccess(host) && pathname === '/') {
|
|
19
|
+
return NextResponse.rewrite(new URL('/admin', request.url))
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Allow admin API from local network and admin.maxy.bot
|
|
23
|
+
if (pathname.startsWith('/api/admin/')) {
|
|
24
|
+
if (!isLocalAccess(host) && !host.includes('admin.maxy.bot')) {
|
|
11
25
|
return new NextResponse(JSON.stringify({ error: 'Admin API not available on this domain' }), {
|
|
12
26
|
status: 403,
|
|
13
27
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -15,7 +29,7 @@ export function proxy(request: NextRequest) {
|
|
|
15
29
|
}
|
|
16
30
|
}
|
|
17
31
|
|
|
18
|
-
// public.maxy.bot → serve public chat
|
|
32
|
+
// public.maxy.bot → serve public chat
|
|
19
33
|
if (host.includes('public.maxy.bot')) {
|
|
20
34
|
return NextResponse.next()
|
|
21
35
|
}
|
|
@@ -30,7 +44,7 @@ export function proxy(request: NextRequest) {
|
|
|
30
44
|
return NextResponse.rewrite(new URL('/bot', request.url))
|
|
31
45
|
}
|
|
32
46
|
|
|
33
|
-
// maxy.bot (bare
|
|
47
|
+
// maxy.bot (bare) → redirect to public.maxy.bot
|
|
34
48
|
if ((host === 'maxy.bot' || host === 'www.maxy.bot') && !host.includes('public.') && !host.includes('admin.')) {
|
|
35
49
|
return NextResponse.redirect(`https://public.maxy.bot${pathname}`, 301)
|
|
36
50
|
}
|
|
@@ -44,6 +58,5 @@ export function proxy(request: NextRequest) {
|
|
|
44
58
|
}
|
|
45
59
|
|
|
46
60
|
export const config = {
|
|
47
|
-
// Match all paths except Next.js internals and static files
|
|
48
61
|
matcher: ['/((?!_next/static|_next/image|favicon.ico|.*\\.(?:png|jpg|jpeg|gif|svg|ico|webp|woff|woff2|ttf)$).*)'],
|
|
49
62
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|