@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
package/dist/index.js ADDED
@@ -0,0 +1,428 @@
1
+ #!/usr/bin/env node
2
+ import { execFileSync, spawnSync } from "node:child_process";
3
+ import { existsSync, mkdirSync, writeFileSync, cpSync, readFileSync, rmSync } from "node:fs";
4
+ import { resolve, join } from "node:path";
5
+ import { randomBytes } from "node:crypto";
6
+ const INSTALL_DIR = resolve(process.env.HOME ?? "/root", "maxy");
7
+ const PAYLOAD_DIR = resolve(import.meta.dirname, "../payload");
8
+ // ---------------------------------------------------------------------------
9
+ // Helpers
10
+ // ---------------------------------------------------------------------------
11
+ function log(step, total, message) {
12
+ console.log(`[${step}/${total}] ${message}`);
13
+ }
14
+ function shell(command, args, options) {
15
+ const cmd = options?.sudo ? "sudo" : command;
16
+ const cmdArgs = options?.sudo ? [command, ...args] : args;
17
+ const result = spawnSync(cmd, cmdArgs, {
18
+ stdio: "inherit",
19
+ timeout: 300_000,
20
+ cwd: options?.cwd,
21
+ });
22
+ if (result.status !== 0) {
23
+ throw new Error(`Command failed (exit ${result.status}): ${cmd} ${cmdArgs.join(" ")}`);
24
+ }
25
+ }
26
+ function commandExists(cmd) {
27
+ try {
28
+ execFileSync("which", [cmd], { stdio: "pipe" });
29
+ return true;
30
+ }
31
+ catch {
32
+ return false;
33
+ }
34
+ }
35
+ function nodeVersion() {
36
+ try {
37
+ const v = execFileSync("node", ["-v"], { encoding: "utf-8" }).trim();
38
+ return parseInt(v.replace("v", "").split(".")[0], 10);
39
+ }
40
+ catch {
41
+ return 0;
42
+ }
43
+ }
44
+ function isLinux() {
45
+ return process.platform === "linux";
46
+ }
47
+ function isArm64() {
48
+ return process.arch === "arm64";
49
+ }
50
+ // ---------------------------------------------------------------------------
51
+ // Installation steps
52
+ // ---------------------------------------------------------------------------
53
+ const TOTAL = "10";
54
+ function installSystemDeps() {
55
+ log("1", TOTAL, "System dependencies and network...");
56
+ if (!isLinux()) {
57
+ console.log(" Skipping Linux-specific setup (not on Linux).");
58
+ return;
59
+ }
60
+ shell("apt-get", ["update", "-qq"], { sudo: true });
61
+ shell("apt-get", ["install", "-y", "-qq", "curl", "git", "unzip", "avahi-daemon", "avahi-utils"], { sudo: true });
62
+ shell("apt-get", ["install", "-y", "-qq", "tigervnc-standalone-server", "python3-websockify", "novnc", "xdg-utils"], { sudo: true });
63
+ // Reinstall chromium cleanly — resolves conflicts between chromium and chromium-browser packages
64
+ shell("apt-get", ["install", "--reinstall", "-y", "-qq", "chromium"], { sudo: true });
65
+ // Set hostname to 'maxy'
66
+ try {
67
+ const hostname = execFileSync("hostname", [], { encoding: "utf-8" }).trim();
68
+ if (hostname !== "maxy") {
69
+ console.log(` Setting hostname to 'maxy' (was '${hostname}')...`);
70
+ shell("hostnamectl", ["set-hostname", "maxy"], { sudo: true });
71
+ shell("sed", ["-i", `s/127\\.0\\.1\\.1.*$/127.0.1.1\\tmaxy/`, "/etc/hosts"], { sudo: true });
72
+ }
73
+ }
74
+ catch { /* not critical */ }
75
+ // Avahi service file for LAN discovery
76
+ const avahiService = `<?xml version="1.0" standalone='no'?>
77
+ <!DOCTYPE service-group SYSTEM "avahi-service.dtd">
78
+ <service-group>
79
+ <name replace-wildcards="yes">Maxy on %h</name>
80
+ <service>
81
+ <type>_http._tcp</type>
82
+ <port>19200</port>
83
+ <txt-record>role=maxy</txt-record>
84
+ </service>
85
+ </service-group>`;
86
+ try {
87
+ writeFileSync("/tmp/maxy-avahi.service", avahiService);
88
+ shell("cp", ["/tmp/maxy-avahi.service", "/etc/avahi/services/maxy.service"], { sudo: true });
89
+ shell("systemctl", ["enable", "avahi-daemon"], { sudo: true });
90
+ shell("systemctl", ["restart", "avahi-daemon"], { sudo: true });
91
+ }
92
+ catch { /* not critical */ }
93
+ console.log(" Device reachable at http://maxy.local:19200");
94
+ }
95
+ function installNodejs() {
96
+ if (commandExists("node") && nodeVersion() >= 20) {
97
+ log("2", TOTAL, `Node.js v${nodeVersion()} already installed.`);
98
+ return;
99
+ }
100
+ log("2", TOTAL, "Installing Node.js...");
101
+ if (!isLinux()) {
102
+ throw new Error("Automatic Node.js installation is only supported on Linux. Install Node.js 20+ manually.");
103
+ }
104
+ spawnSync("bash", ["-c", "curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -"], { stdio: "inherit" });
105
+ shell("apt-get", ["install", "-y", "-qq", "nodejs"], { sudo: true });
106
+ }
107
+ function installClaudeCode() {
108
+ log("3", TOTAL, "Installing Claude Code (latest)...");
109
+ shell("npm", ["install", "-g", "@anthropic-ai/claude-code@latest"], { sudo: true });
110
+ }
111
+ function resetNeo4jWithFreshPassword() {
112
+ const password = randomBytes(24).toString("base64url");
113
+ console.log(" Resetting Neo4j with fresh password...");
114
+ spawnSync("sudo", ["systemctl", "stop", "neo4j"], { stdio: "inherit" });
115
+ // Wipe data so set-initial-password works again
116
+ spawnSync("sudo", ["rm", "-rf",
117
+ "/var/lib/neo4j/data/databases",
118
+ "/var/lib/neo4j/data/transactions",
119
+ "/var/lib/neo4j/data/dbms",
120
+ ], { stdio: "inherit" });
121
+ shell("neo4j-admin", ["dbms", "set-initial-password", password], { sudo: true });
122
+ shell("systemctl", ["start", "neo4j"], { sudo: true });
123
+ // Wait for Neo4j to start accepting connections
124
+ console.log(" Waiting for Neo4j to start...");
125
+ for (let i = 0; i < 15; i++) {
126
+ const check = spawnSync("cypher-shell", [
127
+ "-u", "neo4j", "-p", password,
128
+ "-a", "bolt://localhost:7687",
129
+ "RETURN 1",
130
+ ], { stdio: "pipe", timeout: 5000 });
131
+ if (check.status === 0)
132
+ break;
133
+ spawnSync("sleep", ["2"]);
134
+ }
135
+ return password;
136
+ }
137
+ /** Check Neo4j has a working password. Called AFTER deploy so config is in place. */
138
+ function ensureNeo4jPassword() {
139
+ const passwordFile = join(INSTALL_DIR, "platform/config/.neo4j-password");
140
+ const configDir = resolve(INSTALL_DIR, "platform/config");
141
+ mkdirSync(configDir, { recursive: true });
142
+ // If password file exists, verify it works
143
+ if (existsSync(passwordFile)) {
144
+ const existingPassword = readFileSync(passwordFile, "utf-8").trim();
145
+ const check = spawnSync("cypher-shell", [
146
+ "-u", "neo4j", "-p", existingPassword,
147
+ "-a", "bolt://localhost:7687",
148
+ "RETURN 1",
149
+ ], { stdio: "pipe", timeout: 10000 });
150
+ if (check.status === 0) {
151
+ return; // Password works, data intact
152
+ }
153
+ console.log(" Stored password doesn't match Neo4j. Setting new password...");
154
+ }
155
+ else {
156
+ console.log(" No Neo4j password file found. Setting initial password...");
157
+ }
158
+ // Check if Neo4j has data (databases exist = not a fresh install)
159
+ const hasData = existsSync("/var/lib/neo4j/data/databases/neo4j");
160
+ if (hasData) {
161
+ // NEVER wipe an existing database. The user's data is more important
162
+ // than our ability to set a password. Fail with a clear message.
163
+ throw new Error("Neo4j has existing data but the password is unknown. " +
164
+ "Cannot reset without losing data. " +
165
+ "Set the password manually: cypher-shell -u neo4j -p <old-password> " +
166
+ "\"ALTER CURRENT USER SET PASSWORD FROM '<old>' TO '<new>'\" " +
167
+ "then write the new password to " + join(INSTALL_DIR, "platform/config/.neo4j-password"));
168
+ }
169
+ // Fresh install — safe to set initial password
170
+ const password = resetNeo4jWithFreshPassword();
171
+ writeFileSync(passwordFile, password, { mode: 0o600 });
172
+ // Also save to persistent location (~/.maxy/)
173
+ const persistDir = resolve(process.env.HOME ?? "/root", ".maxy");
174
+ mkdirSync(persistDir, { recursive: true });
175
+ writeFileSync(join(persistDir, ".neo4j-password"), password, { mode: 0o600 });
176
+ }
177
+ function installNeo4j() {
178
+ if (commandExists("neo4j")) {
179
+ log("4", TOTAL, "Neo4j already installed.");
180
+ return;
181
+ }
182
+ log("4", TOTAL, "Installing Neo4j Community Edition 5...");
183
+ if (!isLinux()) {
184
+ throw new Error("Automatic Neo4j installation is only supported on Linux. Install Neo4j 5.11+ manually.");
185
+ }
186
+ console.log(" Installing Java 17...");
187
+ shell("apt-get", ["install", "-y", "-qq", "openjdk-17-jre-headless"], { sudo: true });
188
+ spawnSync("bash", ["-c", "curl -fsSL https://debian.neo4j.com/neotechnology.gpg.key | sudo gpg --yes --dearmor -o /usr/share/keyrings/neo4j.gpg 2>/dev/null"], { stdio: "inherit" });
189
+ spawnSync("bash", ["-c", 'echo "deb [signed-by=/usr/share/keyrings/neo4j.gpg] https://debian.neo4j.com stable 5" | sudo tee /etc/apt/sources.list.d/neo4j.list'], { stdio: "inherit" });
190
+ shell("apt-get", ["update", "-qq"], { sudo: true });
191
+ shell("apt-get", ["install", "-y", "-qq", "neo4j"], { sudo: true });
192
+ shell("sed", ["-i", "s/#server.default_listen_address=0.0.0.0/server.default_listen_address=127.0.0.1/", "/etc/neo4j/neo4j.conf"], { sudo: true });
193
+ // Generate strong random password — stored in persistent location (~/.maxy/)
194
+ const password = randomBytes(24).toString("base64url");
195
+ const persistDir = resolve(process.env.HOME ?? "/root", ".maxy");
196
+ mkdirSync(persistDir, { recursive: true });
197
+ writeFileSync(join(persistDir, ".neo4j-password"), password, { mode: 0o600 });
198
+ // Also write to install dir (will be there when deploy step runs)
199
+ const configDir = resolve(INSTALL_DIR, "platform/config");
200
+ mkdirSync(configDir, { recursive: true });
201
+ writeFileSync(join(configDir, ".neo4j-password"), password, { mode: 0o600 });
202
+ shell("neo4j-admin", ["dbms", "set-initial-password", password], { sudo: true });
203
+ shell("systemctl", ["enable", "neo4j"], { sudo: true });
204
+ shell("systemctl", ["start", "neo4j"], { sudo: true });
205
+ console.log(" Neo4j started. Password stored securely.");
206
+ }
207
+ function installOllama() {
208
+ if (!commandExists("ollama")) {
209
+ log("5", TOTAL, "Installing Ollama...");
210
+ spawnSync("bash", ["-c", "curl -fsSL https://ollama.ai/install.sh | sh"], { stdio: "inherit" });
211
+ }
212
+ else {
213
+ log("5", TOTAL, "Ollama already installed.");
214
+ }
215
+ console.log(" Pulling nomic-embed-text model (~300MB)...");
216
+ shell("ollama", ["pull", "nomic-embed-text"]);
217
+ }
218
+ function installCloudflared() {
219
+ if (commandExists("cloudflared")) {
220
+ log("6", TOTAL, "Cloudflared already installed.");
221
+ return;
222
+ }
223
+ log("6", TOTAL, "Installing cloudflared...");
224
+ if (!isLinux()) {
225
+ console.log(" Skipping — install manually for your platform.");
226
+ return;
227
+ }
228
+ const arch = isArm64() ? "arm64" : "amd64";
229
+ const debPath = "/tmp/cloudflared.deb";
230
+ spawnSync("curl", ["-fsSL", `https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-${arch}.deb`, "-o", debPath], { stdio: "inherit" });
231
+ shell("dpkg", ["-i", debPath], { sudo: true });
232
+ spawnSync("rm", ["-f", debPath]);
233
+ }
234
+ function deployPayload() {
235
+ log("7", TOTAL, "Deploying Maxy...");
236
+ if (!existsSync(PAYLOAD_DIR)) {
237
+ throw new Error(`Payload not found at ${PAYLOAD_DIR}. Package may be corrupted.`);
238
+ }
239
+ // Persistent config lives at ~/.maxy/ — survives rm -rf ~/maxy
240
+ const persistentDir = resolve(process.env.HOME ?? "/root", ".maxy");
241
+ const persistentPasswordFile = join(persistentDir, ".neo4j-password");
242
+ const persistentAccountsDir = join(persistentDir, "accounts");
243
+ // Migrate: if password is in old location, move to persistent
244
+ const oldPasswordFile = join(INSTALL_DIR, "platform/config/.neo4j-password");
245
+ if (existsSync(oldPasswordFile) && !existsSync(persistentPasswordFile)) {
246
+ mkdirSync(persistentDir, { recursive: true });
247
+ cpSync(oldPasswordFile, persistentPasswordFile);
248
+ }
249
+ const oldAccountsDir = join(INSTALL_DIR, "platform/config/accounts");
250
+ if (existsSync(oldAccountsDir) && !existsSync(persistentAccountsDir)) {
251
+ mkdirSync(persistentDir, { recursive: true });
252
+ cpSync(oldAccountsDir, persistentAccountsDir, { recursive: true });
253
+ }
254
+ // Wipe old platform/maxy to prevent stale files
255
+ for (const dir of ["platform", "maxy", "docs", ".claude"]) {
256
+ const target = join(INSTALL_DIR, dir);
257
+ if (existsSync(target)) {
258
+ rmSync(target, { recursive: true });
259
+ }
260
+ }
261
+ mkdirSync(INSTALL_DIR, { recursive: true });
262
+ // Deploy payload
263
+ cpSync(PAYLOAD_DIR, INSTALL_DIR, {
264
+ recursive: true,
265
+ force: true,
266
+ });
267
+ // Link persistent config into install directory
268
+ const configDir = join(INSTALL_DIR, "platform/config");
269
+ mkdirSync(configDir, { recursive: true });
270
+ if (existsSync(persistentPasswordFile)) {
271
+ cpSync(persistentPasswordFile, join(configDir, ".neo4j-password"));
272
+ console.log(" Restored Neo4j password.");
273
+ }
274
+ if (existsSync(persistentAccountsDir)) {
275
+ cpSync(persistentAccountsDir, join(configDir, "accounts"), { recursive: true, force: true });
276
+ console.log(" Restored account data.");
277
+ }
278
+ console.log(` Deployed to ${INSTALL_DIR}`);
279
+ }
280
+ function buildPlatform() {
281
+ log("8", TOTAL, "Installing dependencies and building...");
282
+ // Stop the running service before rebuilding (upgrade path)
283
+ spawnSync("systemctl", ["--user", "stop", "maxy"], { stdio: "pipe" });
284
+ shell("npm", ["install", "--quiet"], { cwd: join(INSTALL_DIR, "platform") });
285
+ shell("npm", ["run", "build"], { cwd: join(INSTALL_DIR, "platform") });
286
+ shell("npm", ["install", "--quiet"], { cwd: join(INSTALL_DIR, "maxy") });
287
+ shell("npm", ["run", "build"], { cwd: join(INSTALL_DIR, "maxy") });
288
+ }
289
+ function setupVncViewer() {
290
+ if (!isLinux())
291
+ return;
292
+ const novncSrc = "/usr/share/novnc";
293
+ const novncDest = join(INSTALL_DIR, "maxy/public/novnc");
294
+ if (!existsSync(join(novncSrc, "core"))) {
295
+ console.log(" noVNC not found — skipping VNC viewer setup.");
296
+ return;
297
+ }
298
+ console.log(" Installing VNC viewer...");
299
+ // Copy core/ and vendor/ (pako compression library) — both required by rfb.js
300
+ cpSync(join(novncSrc, "core"), join(novncDest, "core"), { recursive: true, force: true });
301
+ const vendorSrc = join(novncSrc, "vendor");
302
+ if (existsSync(vendorSrc)) {
303
+ cpSync(vendorSrc, join(novncDest, "vendor"), { recursive: true, force: true });
304
+ }
305
+ // Custom viewer: no toolbar, scales to fit, auto-connects
306
+ const html = `<!DOCTYPE html>
307
+ <html>
308
+ <head>
309
+ <meta charset="utf-8">
310
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
311
+ <title>Connect Claude</title>
312
+ <style>
313
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
314
+ html, body { width: 100%; height: 100%; background: #111; overflow: hidden; }
315
+ #screen { position: relative; width: 100%; height: 100%; }
316
+ </style>
317
+ </head>
318
+ <body>
319
+ <div id="screen"></div>
320
+ <script type="module">
321
+ import RFB from '/novnc/core/rfb.js';
322
+ const p = new URLSearchParams(location.search);
323
+ const host = p.get('host') || location.hostname;
324
+ const port = p.get('port') || '6080';
325
+ const screen = document.getElementById('screen');
326
+ const rfb = new RFB(screen, 'ws://' + host + ':' + port + '/websockify');
327
+ rfb.scaleViewport = true;
328
+ rfb.clipViewport = true;
329
+ rfb.resizeSession = false;
330
+ new ResizeObserver(() => { rfb._updateSize(); }).observe(screen);
331
+ rfb.addEventListener('disconnect', () => { setTimeout(() => location.reload(), 2000); });
332
+ </script>
333
+ </body>
334
+ </html>`;
335
+ writeFileSync(join(INSTALL_DIR, "maxy/public/vnc-viewer.html"), html);
336
+ console.log(" VNC viewer ready at /vnc-viewer.html");
337
+ }
338
+ function setupAccount() {
339
+ log("9", TOTAL, "Setting up...");
340
+ const seedScript = join(INSTALL_DIR, "platform/scripts/seed-neo4j.sh");
341
+ if (existsSync(seedScript)) {
342
+ shell("bash", [seedScript], { cwd: INSTALL_DIR });
343
+ }
344
+ }
345
+ function installService() {
346
+ log("10", TOTAL, "Starting Maxy...");
347
+ if (!isLinux()) {
348
+ console.log(" Skipping systemd service (not Linux). Start manually with:");
349
+ console.log(` cd ${INSTALL_DIR}/maxy && npm start`);
350
+ return;
351
+ }
352
+ // Create systemd user service
353
+ const serviceDir = resolve(process.env.HOME ?? "/root", ".config/systemd/user");
354
+ mkdirSync(serviceDir, { recursive: true });
355
+ const persistDir = resolve(process.env.HOME ?? "/root", ".maxy");
356
+ const serviceFile = `[Unit]
357
+ Description=Maxy AI Assistant
358
+ After=neo4j.service
359
+
360
+ [Service]
361
+ Type=simple
362
+ WorkingDirectory=${INSTALL_DIR}/maxy
363
+ ExecStart=/usr/bin/npm start
364
+ Restart=on-failure
365
+ RestartSec=5
366
+ Environment=NODE_ENV=production
367
+ EnvironmentFile=-${persistDir}/.env
368
+
369
+ [Install]
370
+ WantedBy=default.target
371
+ `;
372
+ writeFileSync(join(serviceDir, "maxy.service"), serviceFile);
373
+ // Enable lingering so user services run without login
374
+ try {
375
+ const user = execFileSync("whoami", [], { encoding: "utf-8" }).trim();
376
+ spawnSync("sudo", ["loginctl", "enable-linger", user], { stdio: "inherit" });
377
+ }
378
+ catch { /* not critical */ }
379
+ // Reload and (re)start
380
+ spawnSync("systemctl", ["--user", "daemon-reload"], { stdio: "inherit" });
381
+ spawnSync("systemctl", ["--user", "enable", "maxy"], { stdio: "inherit" });
382
+ spawnSync("systemctl", ["--user", "restart", "maxy"], { stdio: "inherit" });
383
+ // Wait for the server to come up
384
+ console.log(" Waiting for web server...");
385
+ for (let i = 0; i < 20; i++) {
386
+ try {
387
+ execFileSync("curl", ["-sf", "http://localhost:19200", "-o", "/dev/null"], { timeout: 3000 });
388
+ return; // Server is up
389
+ }
390
+ catch {
391
+ spawnSync("sleep", ["2"]);
392
+ }
393
+ }
394
+ console.log(" Server may still be starting. Check http://maxy.local:19200 in a moment.");
395
+ }
396
+ // ---------------------------------------------------------------------------
397
+ // Main
398
+ // ---------------------------------------------------------------------------
399
+ const PKG_VERSION = JSON.parse(readFileSync(resolve(import.meta.dirname, "../package.json"), "utf-8")).version;
400
+ console.log("================================================================");
401
+ console.log(` Maxy — AI for Productive People. (create-maxy v${PKG_VERSION})`);
402
+ console.log("================================================================");
403
+ console.log("");
404
+ try {
405
+ installSystemDeps();
406
+ installNodejs();
407
+ installClaudeCode();
408
+ installNeo4j();
409
+ installOllama();
410
+ installCloudflared();
411
+ deployPayload(); // Must happen before ensureNeo4jPassword — restores config backup
412
+ ensureNeo4jPassword(); // Now config/.neo4j-password is available if it existed before
413
+ buildPlatform();
414
+ setupVncViewer();
415
+ setupAccount();
416
+ installService();
417
+ console.log("");
418
+ console.log("================================================================");
419
+ console.log("");
420
+ console.log(" Open in your browser: http://maxy.local:19200");
421
+ console.log("");
422
+ console.log("================================================================");
423
+ }
424
+ catch (err) {
425
+ console.error("");
426
+ console.error(`Setup failed: ${err instanceof Error ? err.message : String(err)}`);
427
+ process.exit(1);
428
+ }
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@rubytech/create-maxy",
3
+ "version": "1.0.0",
4
+ "description": "Install Maxy — your personal AI assistant",
5
+ "bin": {
6
+ "create-maxy": "./dist/index.js"
7
+ },
8
+ "type": "module",
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "bundle": "node scripts/bundle.js",
12
+ "prepublishOnly": "npm run build && npm run bundle"
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "payload"
17
+ ],
18
+ "keywords": [
19
+ "maxy",
20
+ "ai",
21
+ "assistant",
22
+ "raspberry-pi",
23
+ "claude"
24
+ ],
25
+ "author": "Rubytech LLC",
26
+ "license": "UNLICENSED",
27
+ "devDependencies": {
28
+ "typescript": "^5.7.0",
29
+ "@types/node": "^22.0.0"
30
+ }
31
+ }
@@ -0,0 +1,12 @@
1
+ # Admin access PIN (required — admin chat is disabled without this)
2
+ ADMIN_PIN=
3
+
4
+ # Telegram public bot (optional — get token from @BotFather)
5
+ TELEGRAM_PUBLIC_BOT_TOKEN=
6
+
7
+ # Neo4j and Ollama run locally with defaults set during installation.
8
+ # Override only if you've changed the defaults:
9
+ # NEO4J_URI=bolt://localhost:7687
10
+ # NEO4J_USER=neo4j
11
+ # NEO4J_PASSWORD is read from platform/config/.neo4j-password (generated during setup)
12
+ # OLLAMA_URL=http://localhost:11434