@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 CHANGED
@@ -50,7 +50,7 @@ function isArm64() {
50
50
  // ---------------------------------------------------------------------------
51
51
  // Installation steps
52
52
  // ---------------------------------------------------------------------------
53
- const TOTAL = "9";
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("3", TOTAL, "Neo4j already installed.");
169
- return; // Password check happens later, after deploy restores config
176
+ log("4", TOTAL, "Neo4j already installed.");
177
+ return;
170
178
  }
171
- log("3", TOTAL, "Installing Neo4j Community Edition 5...");
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("4", TOTAL, "Installing Ollama...");
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("4", TOTAL, "Ollama already installed.");
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("5", TOTAL, "Cloudflared already installed.");
213
+ log("6", TOTAL, "Cloudflared already installed.");
206
214
  return;
207
215
  }
208
- log("5", TOTAL, "Installing cloudflared...");
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("6", TOTAL, "Deploying Maxy...");
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("7", TOTAL, "Installing dependencies and building...");
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("8", TOTAL, "Setting up...");
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("9", TOTAL, "Starting Maxy...");
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/create-maxy",
3
- "version": "0.2.10",
3
+ "version": "0.3.1",
4
4
  "description": "Install Maxy — your personal AI assistant",
5
5
  "bin": {
6
6
  "create-maxy": "./dist/index.js"
@@ -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
- setPinError('Invalid PIN')
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('Connection error')
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 = messages.length + 1
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">Admin</h1>
148
- <p className="chat-intro">Enter your PIN to access the admin agent.</p>
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">Admin Agent</h1>
178
- <div className="chat-trust">
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="Ask the admin agent..."
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
  }
@@ -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
- // Block admin API access from non-admin subdomains
8
- if (pathname.startsWith('/api/admin/') && !host.includes('admin.maxy.bot')) {
9
- // In development (localhost), allow admin access
10
- if (!host.includes('localhost') && !host.includes('127.0.0.1')) {
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 (root page)
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, not a subdomain) → redirect to public.maxy.bot
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