@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,58 @@
1
+ import type { Metadata } from 'next'
2
+ import { Cormorant, DM_Sans } from 'next/font/google'
3
+ import './globals.css'
4
+
5
+ const cormorant = Cormorant({
6
+ subsets: ['latin'],
7
+ weight: ['300', '400', '500'],
8
+ variable: '--font-display',
9
+ display: 'swap',
10
+ })
11
+
12
+ const dmSans = DM_Sans({
13
+ subsets: ['latin'],
14
+ weight: ['400', '500'],
15
+ variable: '--font-body',
16
+ display: 'swap',
17
+ })
18
+
19
+ export const metadata: Metadata = {
20
+ title: 'Maxy — AI for Productive People.',
21
+ description: 'A personal AI assistant that lives in your home. Your data never leaves.',
22
+ icons: {
23
+ icon: '/favicon.ico',
24
+ apple: '/apple-icon.png',
25
+ },
26
+ openGraph: {
27
+ title: 'Maxy — AI for Productive People.',
28
+ description: 'A personal AI assistant that lives in your home. Your data never leaves.',
29
+ siteName: 'Maxy',
30
+ type: 'website',
31
+ images: [
32
+ {
33
+ url: '/og-landscape.png',
34
+ width: 1200,
35
+ height: 628,
36
+ alt: 'Maxy — AI for Productive People.',
37
+ },
38
+ ],
39
+ },
40
+ twitter: {
41
+ card: 'summary_large_image',
42
+ title: 'Maxy — AI for Productive People.',
43
+ description: 'A personal AI assistant that lives in your home. Your data never leaves.',
44
+ images: ['/og-landscape.png'],
45
+ },
46
+ }
47
+
48
+ export default function RootLayout({
49
+ children,
50
+ }: {
51
+ children: React.ReactNode
52
+ }) {
53
+ return (
54
+ <html lang="en" className={`${cormorant.variable} ${dmSans.variable}`}>
55
+ <body>{children}</body>
56
+ </html>
57
+ )
58
+ }
@@ -0,0 +1,503 @@
1
+ import Anthropic from "@anthropic-ai/sdk";
2
+ import { spawn } from "node:child_process";
3
+ import { resolve } from "node:path";
4
+ import { readFileSync, readdirSync, existsSync, mkdirSync, createWriteStream } from "node:fs";
5
+ import { homedir } from "node:os";
6
+
7
+ const LOG_DIR = resolve(homedir(), ".maxy/logs");
8
+ function agentLogStream(name: string) {
9
+ mkdirSync(LOG_DIR, { recursive: true });
10
+ return createWriteStream(resolve(LOG_DIR, `${name}.log`), { flags: "a" });
11
+ }
12
+
13
+ // Load ANTHROPIC_API_KEY from persistent storage if not already in env.
14
+ // Written by the admin agent during onboarding.
15
+ if (!process.env.ANTHROPIC_API_KEY) {
16
+ const keyFile = resolve(homedir(), ".maxy/.anthropic-api-key");
17
+ if (existsSync(keyFile)) {
18
+ try {
19
+ process.env.ANTHROPIC_API_KEY = readFileSync(keyFile, "utf-8").trim();
20
+ } catch { /* ignore */ }
21
+ }
22
+ }
23
+
24
+ const PLATFORM_ROOT = resolve(process.cwd(), "../platform");
25
+ const ACCOUNTS_DIR = resolve(PLATFORM_ROOT, "config/accounts");
26
+
27
+ // ---------------------------------------------------------------------------
28
+ // Account resolution
29
+ // ---------------------------------------------------------------------------
30
+
31
+ interface AccountConfig {
32
+ accountId: string;
33
+ tier: string;
34
+ name: string;
35
+ description: string;
36
+ domains: { public: string; admin: string };
37
+ brand: Record<string, string>;
38
+ telegram: { publicBotToken: string };
39
+ }
40
+
41
+ /**
42
+ * Find the first account on this device.
43
+ * Phase 0: single account. Multi-account resolution is a Phase 1 concern.
44
+ */
45
+ function resolveAccount(): { accountId: string; accountDir: string; config: AccountConfig } | null {
46
+ if (!existsSync(ACCOUNTS_DIR)) return null;
47
+
48
+ const entries = readdirSync(ACCOUNTS_DIR, { withFileTypes: true });
49
+ for (const entry of entries) {
50
+ if (!entry.isDirectory()) continue;
51
+ const configPath = resolve(ACCOUNTS_DIR, entry.name, "account.json");
52
+ if (!existsSync(configPath)) continue;
53
+
54
+ const raw = readFileSync(configPath, "utf-8");
55
+ const config = JSON.parse(raw) as AccountConfig;
56
+ return {
57
+ accountId: config.accountId,
58
+ accountDir: resolve(ACCOUNTS_DIR, entry.name),
59
+ config,
60
+ };
61
+ }
62
+
63
+ return null;
64
+ }
65
+
66
+ /**
67
+ * Read the IDENTITY.md for a specific agent within an account.
68
+ */
69
+ function readIdentity(accountDir: string, agentName: string): string | null {
70
+ const identityPath = resolve(accountDir, "agents", agentName, "IDENTITY.md");
71
+ if (!existsSync(identityPath)) return null;
72
+ return readFileSync(identityPath, "utf-8");
73
+ }
74
+
75
+ // ---------------------------------------------------------------------------
76
+ // Session store
77
+ // ---------------------------------------------------------------------------
78
+
79
+ const sessionStore = new Map<
80
+ string,
81
+ { agentSessionId?: string; createdAt: number; agentType: "public" | "admin"; accountId: string }
82
+ >();
83
+
84
+ export function registerSession(
85
+ sessionKey: string,
86
+ agentType: "public" | "admin",
87
+ accountId: string
88
+ ): void {
89
+ sessionStore.set(sessionKey, { createdAt: Date.now(), agentType, accountId });
90
+ }
91
+
92
+ export function validateSession(
93
+ sessionKey: string,
94
+ agentType: "public" | "admin"
95
+ ): boolean {
96
+ const session = sessionStore.get(sessionKey);
97
+ if (!session) return false;
98
+ if (session.agentType !== agentType) return false;
99
+ if (Date.now() - session.createdAt > 24 * 60 * 60 * 1000) {
100
+ sessionStore.delete(sessionKey);
101
+ return false;
102
+ }
103
+ return true;
104
+ }
105
+
106
+ function storeAgentSessionId(sessionKey: string, agentSessionId: string): void {
107
+ const session = sessionStore.get(sessionKey);
108
+ if (session) {
109
+ session.agentSessionId = agentSessionId;
110
+ }
111
+ }
112
+
113
+ function getAgentSessionId(sessionKey: string): string | undefined {
114
+ return sessionStore.get(sessionKey)?.agentSessionId;
115
+ }
116
+
117
+ function getAccountIdForSession(sessionKey: string): string | undefined {
118
+ return sessionStore.get(sessionKey)?.accountId;
119
+ }
120
+
121
+ // ---------------------------------------------------------------------------
122
+ // MCP server configuration (admin agent only)
123
+ // ---------------------------------------------------------------------------
124
+
125
+ function getMcpServers(accountId: string) {
126
+ return {
127
+ "memory": {
128
+ command: "node",
129
+ args: [resolve(PLATFORM_ROOT, "plugins/memory/mcp/dist/index.js")],
130
+ env: { ACCOUNT_ID: accountId, PLATFORM_ROOT },
131
+ },
132
+ "contacts": {
133
+ command: "node",
134
+ args: [resolve(PLATFORM_ROOT, "plugins/contacts/mcp/dist/index.js")],
135
+ env: { ACCOUNT_ID: accountId, PLATFORM_ROOT },
136
+ },
137
+ "telegram": {
138
+ command: "node",
139
+ args: [resolve(PLATFORM_ROOT, "plugins/telegram/mcp/dist/index.js")],
140
+ env: { ACCOUNT_ID: accountId, PLATFORM_ROOT },
141
+ },
142
+ "admin": {
143
+ command: "node",
144
+ args: [resolve(PLATFORM_ROOT, "plugins/admin/mcp/dist/index.js")],
145
+ env: { ACCOUNT_ID: accountId, PLATFORM_ROOT },
146
+ },
147
+ "cloudflare": {
148
+ command: "node",
149
+ args: [resolve(PLATFORM_ROOT, "plugins/cloudflare/mcp/dist/index.js")],
150
+ env: { PLATFORM_ROOT },
151
+ },
152
+ "scheduling": {
153
+ command: "node",
154
+ args: [resolve(PLATFORM_ROOT, "plugins/scheduling/mcp/dist/index.js")],
155
+ env: { PLATFORM_ROOT },
156
+ },
157
+ "documents": {
158
+ command: "node",
159
+ args: [resolve(PLATFORM_ROOT, "plugins/documents/mcp/dist/index.js")],
160
+ env: { PLATFORM_ROOT },
161
+ },
162
+ "web": {
163
+ command: "node",
164
+ args: [resolve(PLATFORM_ROOT, "plugins/web/mcp/dist/index.js")],
165
+ env: { PLATFORM_ROOT },
166
+ },
167
+ };
168
+ }
169
+
170
+ const ADMIN_ALLOWED_TOOLS = [
171
+ "Read", "Write", "Edit", "Bash", "Glob", "Grep", "Agent",
172
+ "mcp__memory__memory-search",
173
+ "mcp__memory__memory-write",
174
+ "mcp__memory__memory-reindex",
175
+ "mcp__contacts__contact-create",
176
+ "mcp__contacts__contact-lookup",
177
+ "mcp__contacts__contact-update",
178
+ "mcp__contacts__contact-list",
179
+ "mcp__telegram__message",
180
+ "mcp__telegram__message-history",
181
+ "mcp__admin__system-status",
182
+ "mcp__admin__brand-settings",
183
+ "mcp__admin__account-manage",
184
+ "mcp__admin__logs-read",
185
+ "mcp__admin__plugin-read",
186
+ "mcp__cloudflare__tunnel-status",
187
+ "mcp__cloudflare__tunnel-install",
188
+ "mcp__cloudflare__tunnel-login",
189
+ "mcp__cloudflare__tunnel-create",
190
+ "mcp__cloudflare__tunnel-enable",
191
+ "mcp__cloudflare__tunnel-disable",
192
+ ];
193
+
194
+ // ---------------------------------------------------------------------------
195
+ // Memory pre-fetch for public agent
196
+ // ---------------------------------------------------------------------------
197
+
198
+ /**
199
+ * Call the memory MCP server via JSON-RPC to search for relevant context.
200
+ * Returns a formatted string of results, or null if the server is unavailable.
201
+ */
202
+ async function fetchMemoryContext(accountId: string, query: string): Promise<string | null> {
203
+ const serverPath = resolve(PLATFORM_ROOT, "plugins/memory/mcp/dist/index.js");
204
+ if (!existsSync(serverPath)) return null;
205
+
206
+ return new Promise((resolve) => {
207
+ const proc = spawn(process.execPath, [serverPath], {
208
+ env: { ...process.env, ACCOUNT_ID: accountId, READ_ONLY: "true" },
209
+ });
210
+
211
+ let buffer = "";
212
+ let settled = false;
213
+ const TOOL_CALL_ID = 2;
214
+
215
+ const settle = (value: string | null) => {
216
+ if (settled) return;
217
+ settled = true;
218
+ proc.kill();
219
+ resolve(value);
220
+ };
221
+
222
+ proc.stdout.on("data", (chunk: Buffer) => {
223
+ buffer += chunk.toString();
224
+ const lines = buffer.split("\n");
225
+ buffer = lines.pop() ?? "";
226
+
227
+ for (const line of lines) {
228
+ if (!line.trim()) continue;
229
+ let msg: Record<string, unknown>;
230
+ try { msg = JSON.parse(line); } catch { continue; }
231
+
232
+ if (msg.id === 1) {
233
+ // Server initialised — send initialized notification then tool call
234
+ proc.stdin.write(
235
+ JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" }) + "\n"
236
+ );
237
+ proc.stdin.write(
238
+ JSON.stringify({
239
+ jsonrpc: "2.0",
240
+ id: TOOL_CALL_ID,
241
+ method: "tools/call",
242
+ params: { name: "memory-search", arguments: { query, account_id: accountId } },
243
+ }) + "\n"
244
+ );
245
+ } else if (msg.id === TOOL_CALL_ID) {
246
+ const result = msg.result as Record<string, unknown> | undefined;
247
+ const content = result?.content as Array<{ type: string; text?: string }> | undefined;
248
+ const text = content?.find((c) => c.type === "text")?.text ?? null;
249
+ settle(text);
250
+ }
251
+ }
252
+ });
253
+
254
+ proc.on("error", () => settle(null));
255
+ proc.on("close", () => settle(null));
256
+
257
+ // Send MCP initialize
258
+ proc.stdin.write(
259
+ JSON.stringify({
260
+ jsonrpc: "2.0",
261
+ id: 1,
262
+ method: "initialize",
263
+ params: {
264
+ protocolVersion: "2024-11-05",
265
+ capabilities: {},
266
+ clientInfo: { name: "maxy-site", version: "1.0" },
267
+ },
268
+ }) + "\n"
269
+ );
270
+
271
+ // Timeout after 8 seconds
272
+ setTimeout(() => settle(null), 8000);
273
+ });
274
+ }
275
+
276
+ // ---------------------------------------------------------------------------
277
+ // Admin agent: Claude Code CLI subprocess
278
+ // ---------------------------------------------------------------------------
279
+
280
+ async function* invokeAdminAgent(
281
+ message: string,
282
+ systemPrompt: string,
283
+ accountDir: string,
284
+ accountId: string,
285
+ sessionKey?: string
286
+ ): AsyncGenerator<AgentStreamEvent> {
287
+ const resumeSessionId = sessionKey ? getAgentSessionId(sessionKey) : undefined;
288
+
289
+ const mcpConfig = JSON.stringify({ mcpServers: getMcpServers(accountId) });
290
+
291
+ const args = [
292
+ "--print",
293
+ "--output-format", "stream-json",
294
+ "--verbose",
295
+ "--system-prompt", systemPrompt,
296
+ "--mcp-config", mcpConfig,
297
+ "--allowedTools", ADMIN_ALLOWED_TOOLS.join(","),
298
+ "--permission-mode", "dontAsk",
299
+ "--model", "claude-opus-4-6",
300
+ "--max-turns", "20",
301
+ ];
302
+
303
+ if (resumeSessionId) {
304
+ args.push("--resume", resumeSessionId);
305
+ }
306
+
307
+ args.push(message);
308
+
309
+ const proc = spawn("claude", args, {
310
+ cwd: accountDir,
311
+ stdio: ["ignore", "pipe", "pipe"],
312
+ });
313
+ // Pipe stderr to a log file. Never discard it — MCP warnings, first-run
314
+ // setup messages, and real errors all arrive here. Without draining it the
315
+ // 64 KB pipe buffer fills, the process deadlocks, and stdout stops flowing.
316
+ const stderrLog = agentLogStream("claude-agent-stderr");
317
+ proc.stderr?.pipe(stderrLog);
318
+
319
+ let buffer = "";
320
+
321
+ const readLines = async function* () {
322
+ for await (const chunk of proc.stdout) {
323
+ buffer += (chunk as Buffer).toString();
324
+ const lines = buffer.split("\n");
325
+ buffer = lines.pop() ?? "";
326
+ for (const line of lines) {
327
+ if (line.trim()) yield line;
328
+ }
329
+ }
330
+ if (buffer.trim()) {
331
+ yield buffer;
332
+ buffer = "";
333
+ }
334
+ };
335
+
336
+ // Map tool call IDs to tool names so tool_result events carry the correct name
337
+ const toolIdToName = new Map<string, string>();
338
+
339
+ for await (const line of readLines()) {
340
+ let msg: Record<string, unknown>;
341
+ try { msg = JSON.parse(line); } catch { continue; }
342
+
343
+ if (msg.type === "system" && msg.subtype === "init") {
344
+ const agentSessionId = msg.session_id as string;
345
+ yield { type: "session_init", sessionId: agentSessionId };
346
+ if (sessionKey) storeAgentSessionId(sessionKey, agentSessionId);
347
+ continue;
348
+ }
349
+
350
+ if (msg.type === "assistant") {
351
+ const content = (msg.message as Record<string, unknown> | undefined)?.content as
352
+ Array<{ type: string; text?: string; thinking?: string; name?: string; input?: Record<string, unknown>; id?: string }> | undefined;
353
+
354
+ if (content) {
355
+ for (const block of content) {
356
+ if (block.type === "text" && block.text) {
357
+ yield { type: "text", content: block.text };
358
+ } else if (block.type === "thinking" && block.thinking) {
359
+ yield { type: "thinking", content: block.thinking };
360
+ } else if (block.type === "tool_use" && block.name) {
361
+ if (block.id) toolIdToName.set(block.id, block.name);
362
+ yield { type: "tool_use", name: block.name, input: block.input ?? {} };
363
+ }
364
+ }
365
+ }
366
+ continue;
367
+ }
368
+
369
+ if (msg.type === "user") {
370
+ const content = (msg as Record<string, unknown>).content as
371
+ Array<{ type: string; tool_use_id?: string; content?: string; is_error?: boolean }> | undefined;
372
+
373
+ if (Array.isArray(content)) {
374
+ for (const block of content) {
375
+ if (block.type === "tool_result") {
376
+ const name = (block.tool_use_id && toolIdToName.get(block.tool_use_id)) || block.tool_use_id || "unknown";
377
+ yield {
378
+ type: "tool_result",
379
+ name,
380
+ output: typeof block.content === "string" ? block.content : JSON.stringify(block.content),
381
+ error: block.is_error,
382
+ };
383
+ }
384
+ }
385
+ }
386
+ continue;
387
+ }
388
+
389
+ if (msg.type === "system") {
390
+ if (msg.subtype === "task_started") {
391
+ yield { type: "subagent_start", name: "subagent", task: String(msg.description ?? "") };
392
+ } else if (msg.subtype === "task_notification") {
393
+ yield { type: "subagent_end", name: "subagent", result: String(msg.message ?? "") };
394
+ }
395
+ continue;
396
+ }
397
+
398
+ if ("result" in msg) {
399
+ const usage = (msg as { usage?: { input_tokens?: number; output_tokens?: number } }).usage;
400
+ if (usage?.input_tokens !== undefined) {
401
+ yield { type: "usage", input_tokens: usage.input_tokens, output_tokens: usage.output_tokens ?? 0 };
402
+ }
403
+ yield { type: "done" };
404
+ }
405
+ }
406
+ }
407
+
408
+ // ---------------------------------------------------------------------------
409
+ // Public agent: direct Anthropic API (no Claude Code)
410
+ // ---------------------------------------------------------------------------
411
+
412
+ async function* invokePublicAgent(
413
+ message: string,
414
+ systemPrompt: string,
415
+ accountId: string
416
+ ): AsyncGenerator<AgentStreamEvent> {
417
+ const apiKey = process.env.ANTHROPIC_API_KEY;
418
+ if (!apiKey) {
419
+ yield { type: "text", content: "Public agent is not configured. ANTHROPIC_API_KEY is missing." };
420
+ yield { type: "done" };
421
+ return;
422
+ }
423
+
424
+ const memoryContext = await fetchMemoryContext(accountId, message);
425
+
426
+ const system = memoryContext
427
+ ? `${systemPrompt}\n\n<memory>\n${memoryContext}\n</memory>`
428
+ : systemPrompt;
429
+
430
+ const client = new Anthropic({ apiKey });
431
+
432
+ const stream = client.messages.stream({
433
+ model: "claude-haiku-4-5-20251001",
434
+ max_tokens: 1024,
435
+ system,
436
+ messages: [{ role: "user", content: message }],
437
+ });
438
+
439
+ for await (const event of stream) {
440
+ if (
441
+ event.type === "content_block_delta" &&
442
+ event.delta.type === "text_delta"
443
+ ) {
444
+ yield { type: "text", content: event.delta.text };
445
+ }
446
+ }
447
+
448
+ yield { type: "done" };
449
+ }
450
+
451
+ // ---------------------------------------------------------------------------
452
+ // Public entry point
453
+ // ---------------------------------------------------------------------------
454
+
455
+ interface AgentInvokeConfig {
456
+ type: "public" | "admin";
457
+ agentName?: string;
458
+ }
459
+
460
+ export async function* invokeAgent(
461
+ config: AgentInvokeConfig,
462
+ message: string,
463
+ sessionKey?: string
464
+ ): AsyncGenerator<AgentStreamEvent> {
465
+ const { type: agentType, agentName } = config;
466
+
467
+ const sessionAccountId = sessionKey ? getAccountIdForSession(sessionKey) : undefined;
468
+ const account = resolveAccount();
469
+
470
+ if (!account) {
471
+ yield { type: "text", content: "No account configured. Please complete onboarding first." };
472
+ yield { type: "done" };
473
+ return;
474
+ }
475
+
476
+ const accountId = sessionAccountId ?? account.accountId;
477
+ const resolvedAgentName = agentName ?? agentType;
478
+ const identity = readIdentity(account.accountDir, resolvedAgentName);
479
+
480
+ const defaultSystemPrompt = agentType === "public"
481
+ ? "You are a public assistant. Answer only from the memory context provided. Never use training data. British English. Be concise."
482
+ : "You are an admin agent. Full access. Professional, concise. British English. Be proactive — tell the user what needs doing and do it.";
483
+
484
+ const systemPrompt = identity ?? defaultSystemPrompt;
485
+
486
+ if (agentType === "public") {
487
+ yield* invokePublicAgent(message, systemPrompt, accountId);
488
+ } else {
489
+ yield* invokeAdminAgent(message, systemPrompt, account.accountDir, accountId, sessionKey);
490
+ }
491
+ }
492
+
493
+ export type AgentStreamEvent =
494
+ | { type: "session_init"; sessionId: string }
495
+ | { type: "text"; content: string }
496
+ | { type: "thinking"; content: string }
497
+ | { type: "tool_use"; name: string; input: Record<string, unknown> }
498
+ | { type: "tool_result"; name: string; output: string; error?: boolean }
499
+ | { type: "subagent_start"; name: string; task: string }
500
+ | { type: "subagent_end"; name: string; result: string }
501
+ | { type: "status"; message: string }
502
+ | { type: "usage"; input_tokens: number; output_tokens: number }
503
+ | { type: "done" };
@@ -0,0 +1,15 @@
1
+ export default function OGLayout({ children }: { children: React.ReactNode }) {
2
+ return (
3
+ <div
4
+ style={{
5
+ margin: 0,
6
+ padding: 0,
7
+ overflow: 'hidden',
8
+ backgroundColor: '#FAFAF8',
9
+ minHeight: '100vh',
10
+ }}
11
+ >
12
+ {children}
13
+ </div>
14
+ )
15
+ }