@rubytech/create-maxy 0.4.0 → 0.4.2

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.

Potentially problematic release.


This version of @rubytech/create-maxy might be problematic. Click here for more details.

package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/create-maxy",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Install Maxy — your personal AI assistant",
5
5
  "bin": {
6
6
  "create-maxy": "./dist/index.js"
@@ -12,7 +12,7 @@ Everything an end user does must be achievable via the chat interface. No CLI. N
12
12
  - Conversational onboarding (IDENTITY.md placeholder population) is the configuration mechanism
13
13
  - Setup scripts install infrastructure only — product configuration happens via chat
14
14
  - If there is no conversational path to use a feature, the feature is incomplete
15
- - Exception: Phase 0 seed script (dog-food product knowledge). Customer devices onboard conversationally.
15
+ - Product knowledge is ingested as markdown via the admin agent there are no seed scripts.
16
16
 
17
17
  ---
18
18
 
@@ -48,6 +48,17 @@ Infrastructure endpoints (`localhost:7687`, `localhost:11434`) may have defaults
48
48
 
49
49
  ---
50
50
 
51
+ ## No Environment Variables for Credentials
52
+
53
+ Credentials are stored as files in `~/.maxy/` (persistent across upgrades):
54
+ - Neo4j password: `~/.maxy/.neo4j-password` (generated by installer)
55
+ - Admin PIN: `~/.maxy/.admin-pin` (set via web UI on first boot)
56
+ - Claude auth: OAuth flow in the web UI (managed by Claude Code)
57
+
58
+ There is no `.env.local`. MCP servers read the password file directly.
59
+
60
+ ---
61
+
51
62
  ## PRD Is Source of Truth
52
63
 
53
64
  `MAXY-PRD.md` is the single authority for all standards, data models, and architecture. Do not rely on patterns found in existing skill code — they may diverge from the PRD.
@@ -21,12 +21,17 @@ When complete, open `http://maxy.local:19200` from any device on the same networ
21
21
  7. Creates the initial account from templates
22
22
  8. Applies the Neo4j schema
23
23
 
24
- ## Post-install (all via conversation)
24
+ ## Onboarding (all via web UI)
25
25
 
26
- Open `http://maxy.local:19200` from any device on the same network and tell the admin agent what you need:
26
+ Open `http://maxy.local:19200` from any device on the same network. The onboarding flow is:
27
27
 
28
- - "Set my admin PIN" secures admin access
29
- - "Ingest the product knowledge" loads the knowledge base
28
+ 1. **Set PIN** — choose an admin PIN (stored at `~/.maxy/.admin-pin`)
29
+ 2. **Connect Claude** OAuth flow in the web UI (no API keys, no environment variables)
30
+ 3. **Chat** — the admin agent is ready
31
+
32
+ From there, tell the admin agent what you need:
33
+
34
+ - "Ingest the product knowledge" — loads the knowledge base (ingested as markdown, not cypher)
30
35
  - "Set up Telegram" — walks through BotFather bot creation
31
36
  - "Set up Cloudflare Tunnel" — configures remote access on your domain
32
37
  - "Check system status" — verifies all services are running
@@ -16,8 +16,9 @@ Fires at the beginning of each agent session. Loads the agent's IDENTITY.md from
16
16
 
17
17
  Fires before every tool call. Enforces the public agent lockdown.
18
18
 
19
- **Public agent:** Single tool allowed:
19
+ **Public agent:** Two tools allowed:
20
20
  - `memory-search` / `mcp__maxy-memory__memory-search`
21
+ - `skill-read` / `mcp__maxy-admin__skill-read`
21
22
 
22
23
  All other tools are blocked with exit code 2 and an error message. The public agent has zero write surface.
23
24
 
@@ -13,11 +13,11 @@ Graph-backed knowledge retrieval. The backbone of RAG (Retrieval-Augmented Gener
13
13
 
14
14
  **Dependencies:** Neo4j (bolt://localhost:7687), Ollama (http://localhost:11434)
15
15
 
16
- **Environment:**
17
- - `NEO4J_URI`, `NEO4J_USER`, `NEO4J_PASSWORD` Neo4j connection
18
- - `OLLAMA_URL` Ollama API endpoint
19
- - `ACCOUNT_ID` Account scope (default: "maxy")
20
- - `READ_ONLY` When "true", memory-write and memory-reindex are not registered
16
+ **Configuration:**
17
+ - Neo4j password read from `~/.maxy/.neo4j-password` (generated by installer)
18
+ - Ollama endpoint: `http://localhost:11434` (local default)
19
+ - Account ID passed by the runtime per session
20
+ - Read-only mode: when enabled, memory-write and memory-reindex are not registered (used for public agent)
21
21
 
22
22
  ## maxy-contacts
23
23
 
@@ -40,7 +40,8 @@ Multi-channel message delivery. Phase 0: Telegram only.
40
40
  - `message-history` — Query conversation log (Communication nodes in Neo4j).
41
41
 
42
42
  **Dependencies:** Telegram Bot API
43
- **Environment:** `TELEGRAM_PUBLIC_BOT_TOKEN`
43
+
44
+ **Configuration:** Bot token stored in the account config directory (`~/maxy/platform/config/accounts/{uuid}/`), set via conversational onboarding.
44
45
 
45
46
  ## maxy-admin
46
47
 
@@ -52,7 +53,7 @@ System status and configuration reading.
52
53
  - `account-manage` — Read account config (tier, domains, settings).
53
54
  - `logs-read` — Read system, session, or error logs (tail).
54
55
 
55
- **Environment:** `PLATFORM_ROOT` path to platform directory, `ACCOUNT_ID` account UUID
56
+ **Configuration:** Platform root and account ID passed by the runtime per session.
56
57
 
57
58
  ### Cloudflare Tunnel Management
58
59
 
@@ -35,14 +35,14 @@ Neo4j's built-in HNSW vector index is used for semantic search:
35
35
 
36
36
  The `memory-search` tool embeds the query via Ollama, runs vector similarity search against the index, then expands results via 1-hop graph traversal to include related nodes (e.g., Service → PriceSpecification).
37
37
 
38
- ## Seed Data
38
+ ## Credentials
39
39
 
40
- Product knowledge is seeded via Cypher scripts in `platform/neo4j/`:
41
- - `schema.cypher` — constraints, indexes, vector index creation
42
- - `seed-product-knowledge.cypher` — all product data (tiers, features, FAQs, comparisons)
40
+ The Neo4j password is generated by the installer and stored at `~/.maxy/.neo4j-password`. MCP servers read from this file. There are no environment variables for credentials.
43
41
 
44
- Run via `platform/scripts/seed-neo4j.sh`.
42
+ ## Product Knowledge
43
+
44
+ Product knowledge is ingested as markdown via the admin agent using `memory-write`. There are no seed scripts or pre-configured cypher files for product data. The schema (constraints, indexes, vector index) is applied by the installer via `platform/neo4j/schema.cypher`.
45
45
 
46
46
  ## Embedding Pipeline
47
47
 
48
- After seeding, run `memory-reindex` (via the admin agent or directly) to compute embeddings for all nodes. This calls Ollama's `/api/embed` endpoint with `nomic-embed-text` and stores the resulting 768-dimensional vectors on each node.
48
+ When the admin agent writes knowledge via `memory-write`, embeddings are computed automatically. The `memory-reindex` tool rebuilds embeddings for any nodes missing them (e.g., after a bulk import). This calls Ollama's `/api/embed` endpoint with `nomic-embed-text` and stores the resulting 768-dimensional vectors on each node.
@@ -36,9 +36,9 @@ platform/
36
36
 
37
37
  | Agent | Access | Model | Tools | Use |
38
38
  |-------|--------|-------|-------|-----|
39
- | Public | Read-only | claude-haiku-4-5 | memory-search | Sales, product enquiries |
39
+ | Public | Read-only | claude-haiku-4-5 | memory-search, skill-read | Sales, product enquiries |
40
40
  | Admin | Full | claude-opus-4-6 | All MCP tools + Claude Code native tools | Content management, monitoring |
41
41
 
42
42
  ## Security Model
43
43
 
44
- The public agent is locked down via the `pre-tool-use.sh` hook. It can only call `memory-search` — a single read-only tool. All other tool calls are blocked with exit code 2. The public agent has zero write surface; waitlist signups captured in conversation are extracted by the admin cron review job. The admin agent has no restrictions.
44
+ The public agent is locked down via the `pre-tool-use.sh` hook. It can only call `memory-search` and `skill-read` two read-only tools. All other tool calls are blocked with exit code 2. The public agent has zero write surface; waitlist signups captured in conversation are extracted by the admin cron review job. The admin agent has no restrictions.
@@ -10,7 +10,7 @@ Skills are domain-specific behaviour modules that teach agents how to handle spe
10
10
  | `business-assistant` | CRM, scheduling, quoting, invoicing, memory-first operations | Admin agent |
11
11
  | `telegram` | Telegram bot setup guide (BotFather, webhook, admin vs public) | Admin agent |
12
12
  | `cloudflare` | Cloudflare Tunnel setup guide (installation, DNS, domain) | Admin agent |
13
- | `anthropic` | Claude connection setup (OAuth, API key) | Admin agent |
13
+ | `anthropic` | Claude connection setup (OAuth flow via web UI) | Admin agent |
14
14
 
15
15
  ## Skill Format
16
16
 
@@ -2,16 +2,20 @@
2
2
 
3
3
  The web chat is a Next.js 16 application in `maxy/` that serves both the public sales agent and the admin agent.
4
4
 
5
- ## Domain Routing
5
+ ## Routing
6
6
 
7
- | Domain | Route | Page |
7
+ | Access | Route | Page |
8
8
  |--------|-------|------|
9
- | `public.maxy.bot` | `/` | Public chat (SSE streaming, text only) |
10
- | `admin.maxy.bot` | `/admin` | Admin chat (SSE streaming, full activity) |
9
+ | Local (`maxy.local:19200`) | `/` | Admin chat (PIN-gated, SSE streaming, full activity) |
10
+ | Local (`maxy.local:19200`) | `/public` | Public chat (SSE streaming, text only) |
11
+ | `public.maxy.bot` (Cloudflare Tunnel) | `/public` | Public chat |
12
+ | `admin.maxy.bot` (Cloudflare Tunnel) | `/` | Admin chat |
11
13
  | `getmaxy.com` | `/bot` | Marketing landing page |
12
14
  | `maxy.bot` | redirect | → `public.maxy.bot` |
13
15
  | `maxy.chat` | redirect | → `public.maxy.bot` |
14
16
 
17
+ The root page (`/`) is the admin interface. Local access on the same network = admin (with PIN). The public chat is at `/public`, served to the internet only via `public.maxy.bot` through Cloudflare Tunnel.
18
+
15
19
  Routing is handled by Next.js middleware (`maxy/middleware.ts`).
16
20
 
17
21
  ## Public Chat API
@@ -53,7 +57,7 @@ The admin UI (`maxy/app/admin/page.tsx`) renders these as a Claude Code-style ac
53
57
  Both endpoints use the shared `maxy/app/lib/claude-agent.ts` module which invokes `@anthropic-ai/claude-agent-sdk`'s `query()` function with:
54
58
  - Appropriate MCP server configurations
55
59
  - System prompts tailored to agent type
56
- - Tool allowlists (public: 3 tools, admin: all)
60
+ - Tool allowlists (public: 2 tools — memory-search + skill-read, admin: all)
57
61
  - Permission modes (public: dontAsk, admin: acceptEdits)
58
62
  - Model selection (public: haiku-4-5, admin: opus-4-6)
59
63
 
@@ -1,5 +1,8 @@
1
1
  import { NextResponse } from 'next/server'
2
- import { spawn, execFileSync } from 'node:child_process'
2
+ import { spawn, execFileSync, type ChildProcess } from 'node:child_process'
3
+
4
+ // Keep the login process alive between requests
5
+ let loginProcess: ChildProcess | null = null
3
6
 
4
7
  /**
5
8
  * GET /api/onboarding/claude-auth
@@ -10,7 +13,6 @@ export async function GET() {
10
13
  const output = execFileSync('claude', ['auth', 'status', '--json'], {
11
14
  encoding: 'utf-8',
12
15
  timeout: 10000,
13
- env: { ...process.env, BROWSER: 'echo' },
14
16
  })
15
17
  const status = JSON.parse(output)
16
18
  return NextResponse.json({
@@ -24,22 +26,60 @@ export async function GET() {
24
26
 
25
27
  /**
26
28
  * POST /api/onboarding/claude-auth
27
- * Start the Claude Code login flow. Returns the auth URL.
29
+ * Body: {} starts the login flow, returns auth URL
30
+ * Body: { code: "..." } — feeds the auth code to the running login process
28
31
  */
29
- export async function POST() {
32
+ export async function POST(req: Request) {
33
+ let body: { code?: string } = {}
34
+ try {
35
+ body = await req.json()
36
+ } catch { /* empty body = start flow */ }
37
+
38
+ // If code is provided, feed it to the running login process
39
+ if (body.code && loginProcess && loginProcess.stdin) {
40
+ loginProcess.stdin.write(body.code + '\n')
41
+ loginProcess.stdin.end()
42
+
43
+ // Wait a moment for the process to complete
44
+ await new Promise((r) => setTimeout(r, 3000))
45
+
46
+ // Check if auth succeeded
47
+ try {
48
+ const output = execFileSync('claude', ['auth', 'status', '--json'], {
49
+ encoding: 'utf-8',
50
+ timeout: 5000,
51
+ })
52
+ const status = JSON.parse(output)
53
+ loginProcess = null
54
+ return NextResponse.json({
55
+ authenticated: status.loggedIn === true,
56
+ email: status.email ?? null,
57
+ })
58
+ } catch {
59
+ loginProcess = null
60
+ return NextResponse.json({ authenticated: false, error: 'Authentication failed. Check the code and try again.' })
61
+ }
62
+ }
63
+
64
+ // Start a new login flow
65
+ if (loginProcess) {
66
+ loginProcess.kill()
67
+ loginProcess = null
68
+ }
69
+
30
70
  return new Promise<Response>((resolve) => {
31
71
  const child = spawn('claude', ['auth', 'login'], {
32
72
  stdio: ['pipe', 'pipe', 'pipe'],
33
73
  env: { ...process.env, BROWSER: 'echo' },
34
74
  })
35
75
 
76
+ loginProcess = child
77
+
36
78
  let output = ''
37
79
  let resolved = false
38
80
 
39
81
  function tryResolve() {
40
82
  if (resolved) return
41
-
42
- // Look for the auth URL in the output
43
83
  const match = output.match(/(https:\/\/claude\.ai\/oauth\/authorize[^\s]+)/)
44
84
  if (match) {
45
85
  resolved = true
@@ -57,26 +97,16 @@ export async function POST() {
57
97
  tryResolve()
58
98
  })
59
99
 
60
- // Timeout after 10 seconds
61
100
  setTimeout(() => {
62
101
  if (!resolved) {
63
102
  resolved = true
64
103
  child.kill()
104
+ loginProcess = null
65
105
  resolve(NextResponse.json(
66
106
  { error: 'Timed out waiting for auth URL.' },
67
107
  { status: 500 },
68
108
  ))
69
109
  }
70
110
  }, 10000)
71
-
72
- child.on('exit', () => {
73
- if (!resolved) {
74
- resolved = true
75
- resolve(NextResponse.json(
76
- { error: 'Login process exited without providing auth URL.' },
77
- { status: 500 },
78
- ))
79
- }
80
- })
81
111
  })
82
112
  }
@@ -1415,6 +1415,18 @@ a:hover {
1415
1415
  border-color: var(--sage);
1416
1416
  }
1417
1417
 
1418
+ .auth-retry-link {
1419
+ font-family: var(--font-body);
1420
+ font-size: 13px;
1421
+ color: var(--text-tertiary);
1422
+ text-decoration: none;
1423
+ margin-top: 12px;
1424
+ }
1425
+
1426
+ .auth-retry-link:hover {
1427
+ color: var(--sage);
1428
+ }
1429
+
1418
1430
  .admin-pin-error {
1419
1431
  color: #c44;
1420
1432
  font-size: 14px;
@@ -19,6 +19,7 @@ export default function AdminPage() {
19
19
  const [pinError, setPinError] = useState('')
20
20
  const [showPin, setShowPin] = useState(false)
21
21
  const [authUrl, setAuthUrl] = useState<string | null>(null)
22
+ const [authCode, setAuthCode] = useState('')
22
23
  const [authLoading, setAuthLoading] = useState(false)
23
24
  const [sessionKey, setSessionKey] = useState<string | null>(null)
24
25
  const [messages, setMessages] = useState<Message[]>([])
@@ -267,29 +268,49 @@ export default function AdminPage() {
267
268
  if (appState === 'connect-claude') {
268
269
  async function startAuth() {
269
270
  setAuthLoading(true)
271
+ setPinError('')
270
272
  try {
271
273
  const res = await fetch('/api/onboarding/claude-auth', { method: 'POST' })
272
274
  const data = await res.json()
273
275
  if (data.authUrl) {
274
276
  setAuthUrl(data.authUrl)
275
277
  window.open(data.authUrl, '_blank')
278
+ } else if (data.error) {
279
+ setPinError(data.error)
276
280
  }
277
- } catch { /* */ }
281
+ } catch {
282
+ setPinError('Could not start auth flow.')
283
+ }
278
284
  setAuthLoading(false)
279
285
  }
280
286
 
281
- async function checkAuth() {
282
- const res = await fetch('/api/onboarding/claude-auth')
283
- const data = await res.json()
284
- if (data.authenticated) {
285
- setAppState('enter-pin')
287
+ async function submitCode(e: FormEvent) {
288
+ e.preventDefault()
289
+ if (!authCode.trim()) return
290
+ setAuthLoading(true)
291
+ setPinError('')
292
+ try {
293
+ const res = await fetch('/api/onboarding/claude-auth', {
294
+ method: 'POST',
295
+ headers: { 'Content-Type': 'application/json' },
296
+ body: JSON.stringify({ code: authCode.trim() }),
297
+ })
298
+ const data = await res.json()
299
+ if (data.authenticated) {
300
+ setAppState('enter-pin')
301
+ } else {
302
+ setPinError(data.error || 'Authentication failed. Check the code and try again.')
303
+ }
304
+ } catch {
305
+ setPinError('Could not verify code.')
286
306
  }
307
+ setAuthLoading(false)
287
308
  }
288
309
 
289
310
  return (
290
311
  <div className="chat-page admin-page">
291
312
  <header className="chat-header">
292
- <img src="/brand/maxy-black.png" alt="Maxy" className="chat-logo" />
313
+ <img src="/brand/claude.png" alt="Claude" className="chat-logo" />
293
314
  <h1 className="chat-tagline">Connect Claude</h1>
294
315
  <p className="chat-intro">Sign in with your Anthropic account to power Maxy.</p>
295
316
  </header>
@@ -305,16 +326,32 @@ export default function AdminPage() {
305
326
  ) : (
306
327
  <>
307
328
  <p className="chat-intro" style={{ fontSize: '14px', marginTop: 0 }}>
308
- A browser window has opened. Sign in with Anthropic, then come back here.
329
+ Sign in on the Anthropic page, then paste the code here.
309
330
  </p>
310
- <a href={authUrl} target="_blank" rel="noopener noreferrer" className="btn-secondary">
311
- Open sign-in page again
331
+ <form onSubmit={submitCode}>
332
+ <div className="pin-input-row">
333
+ <input
334
+ type="text"
335
+ value={authCode}
336
+ onChange={e => setAuthCode(e.target.value)}
337
+ placeholder="Paste auth code"
338
+ className="chat-input"
339
+ autoFocus
340
+ />
341
+ <button type="submit" className="chat-send" disabled={!authCode.trim() || authLoading}>
342
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
343
+ <line x1="5" y1="12" x2="19" y2="12" />
344
+ <polyline points="12 5 19 12 12 19" />
345
+ </svg>
346
+ </button>
347
+ </div>
348
+ </form>
349
+ <a href={authUrl} target="_blank" rel="noopener noreferrer" className="auth-retry-link">
350
+ Try again
312
351
  </a>
313
- <button className="btn-primary" onClick={checkAuth} style={{ marginTop: '12px' }}>
314
- I{'\u2019'}ve signed in
315
- </button>
316
352
  </>
317
353
  )}
354
+ {pinError && <p className="admin-pin-error">{pinError}</p>}
318
355
  </div>
319
356
  </div>
320
357
  )