@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.
- package/dist/index.js +428 -0
- package/package.json +31 -0
- package/payload/maxy/.env.example +12 -0
- package/payload/maxy/app/admin/components/ActivityTimeline.tsx +348 -0
- package/payload/maxy/app/admin/components/MarkdownMessage.tsx +40 -0
- package/payload/maxy/app/api/admin/chat/route.ts +72 -0
- package/payload/maxy/app/api/admin/logs/route.ts +40 -0
- package/payload/maxy/app/api/admin/session/route.ts +74 -0
- package/payload/maxy/app/api/chat/route.ts +72 -0
- package/payload/maxy/app/api/health/route.ts +26 -0
- package/payload/maxy/app/api/onboarding/claude-auth/route.ts +216 -0
- package/payload/maxy/app/api/onboarding/set-pin/route.ts +44 -0
- package/payload/maxy/app/api/session/route.ts +51 -0
- package/payload/maxy/app/api/telegram/webhook/route.ts +107 -0
- package/payload/maxy/app/apple-icon.png +0 -0
- package/payload/maxy/app/bot/page.tsx +373 -0
- package/payload/maxy/app/favicon.ico +0 -0
- package/payload/maxy/app/globals.css +1681 -0
- package/payload/maxy/app/layout.tsx +58 -0
- package/payload/maxy/app/lib/claude-agent.ts +503 -0
- package/payload/maxy/app/og/layout.tsx +15 -0
- package/payload/maxy/app/og/page.tsx +252 -0
- package/payload/maxy/app/page.tsx +594 -0
- package/payload/maxy/app/privacy/page.tsx +72 -0
- package/payload/maxy/app/public/page.tsx +266 -0
- package/payload/maxy/next.config.mjs +26 -0
- package/payload/maxy/package-lock.json +2198 -0
- package/payload/maxy/package.json +25 -0
- package/payload/maxy/proxy.ts +41 -0
- package/payload/maxy/public/brand/claude.png +0 -0
- package/payload/maxy/public/brand/maxy-black.png +0 -0
- package/payload/maxy/public/brand/maxy.png +0 -0
- package/payload/maxy/public/favicon.ico +0 -0
- package/payload/maxy/public/og-landscape.png +0 -0
- package/payload/maxy/public/og-portrait.png +0 -0
- package/payload/maxy/public/og-square.png +0 -0
- package/payload/maxy/public/pi-5.jpg +0 -0
- package/payload/maxy/public/robots.txt +5 -0
- package/payload/maxy/tsconfig.json +41 -0
- package/payload/maxy/tsconfig.tsbuildinfo +1 -0
- package/payload/maxy/ui.md +28 -0
- package/payload/platform/config/cloudflared.yml +17 -0
- package/payload/platform/knowledge/maxy.md +161 -0
- package/payload/platform/neo4j/schema.cypher +108 -0
- package/payload/platform/package-lock.json +1835 -0
- package/payload/platform/package.json +17 -0
- package/payload/platform/plugins/admin/PLUGIN.md +24 -0
- package/payload/platform/plugins/admin/hooks/pre-tool-use.sh +56 -0
- package/payload/platform/plugins/admin/hooks/session-start.sh +20 -0
- package/payload/platform/plugins/admin/mcp/dist/index.d.ts +2 -0
- package/payload/platform/plugins/admin/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/admin/mcp/dist/index.js +149 -0
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/admin/mcp/package.json +18 -0
- package/payload/platform/plugins/anthropic/PLUGIN.md +30 -0
- package/payload/platform/plugins/anthropic/references/setup-guide.md +146 -0
- package/payload/platform/plugins/business-assistant/PLUGIN.md +46 -0
- package/payload/platform/plugins/business-assistant/references/crm.md +112 -0
- package/payload/platform/plugins/business-assistant/references/document-management.md +96 -0
- package/payload/platform/plugins/business-assistant/references/escalation.md +126 -0
- package/payload/platform/plugins/business-assistant/references/invoicing.md +163 -0
- package/payload/platform/plugins/business-assistant/references/quoting.md +56 -0
- package/payload/platform/plugins/business-assistant/references/scheduling.md +127 -0
- package/payload/platform/plugins/cloudflare/PLUGIN.md +31 -0
- package/payload/platform/plugins/cloudflare/mcp/dist/index.d.ts +2 -0
- package/payload/platform/plugins/cloudflare/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/cloudflare/mcp/dist/index.js +174 -0
- package/payload/platform/plugins/cloudflare/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.d.ts +45 -0
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.d.ts.map +1 -0
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.js +256 -0
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.js.map +1 -0
- package/payload/platform/plugins/cloudflare/mcp/package.json +18 -0
- package/payload/platform/plugins/cloudflare/references/setup-guide.md +110 -0
- package/payload/platform/plugins/contacts/PLUGIN.md +18 -0
- package/payload/platform/plugins/contacts/mcp/dist/index.d.ts +2 -0
- package/payload/platform/plugins/contacts/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/index.js +182 -0
- package/payload/platform/plugins/contacts/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/lib/neo4j.d.ts +5 -0
- package/payload/platform/plugins/contacts/mcp/dist/lib/neo4j.d.ts.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/lib/neo4j.js +34 -0
- package/payload/platform/plugins/contacts/mcp/dist/lib/neo4j.js.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.d.ts +19 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.d.ts.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.js +68 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.js.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-list.d.ts +22 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-list.d.ts.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-list.js +46 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-list.js.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.d.ts +20 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.d.ts.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.js +56 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.js.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-update.d.ts +13 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-update.d.ts.map +1 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-update.js +54 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-update.js.map +1 -0
- package/payload/platform/plugins/contacts/mcp/package.json +19 -0
- package/payload/platform/plugins/documents/PLUGIN.md +12 -0
- package/payload/platform/plugins/documents/mcp/dist/index.d.ts +2 -0
- package/payload/platform/plugins/documents/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/documents/mcp/dist/index.js +82 -0
- package/payload/platform/plugins/documents/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/documents/mcp/package.json +20 -0
- package/payload/platform/plugins/memory/PLUGIN.md +17 -0
- package/payload/platform/plugins/memory/mcp/dist/index.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/index.js +164 -0
- package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/embeddings.d.ts +3 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/embeddings.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/embeddings.js +29 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/embeddings.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/neo4j.d.ts +5 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/neo4j.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/neo4j.js +34 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/neo4j.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.d.ts +8 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.js +71 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.d.ts +24 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.js +125 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts +18 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js +56 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/package.json +19 -0
- package/payload/platform/plugins/sales/PLUGIN.md +65 -0
- package/payload/platform/plugins/sales/references/close-tracking.md +76 -0
- package/payload/platform/plugins/sales/references/closing-framework.md +108 -0
- package/payload/platform/plugins/sales/references/comparisons.md +99 -0
- package/payload/platform/plugins/sales/references/competitive-positioning.md +51 -0
- package/payload/platform/plugins/sales/references/faq.md +62 -0
- package/payload/platform/plugins/sales/references/objection-handling.md +157 -0
- package/payload/platform/plugins/sales/references/pricing.md +71 -0
- package/payload/platform/plugins/sales/references/waitlist.md +23 -0
- package/payload/platform/plugins/scheduling/PLUGIN.md +12 -0
- package/payload/platform/plugins/scheduling/mcp/dist/index.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/index.js +13 -0
- package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/package.json +18 -0
- package/payload/platform/plugins/telegram/PLUGIN.md +31 -0
- package/payload/platform/plugins/telegram/mcp/dist/index.d.ts +2 -0
- package/payload/platform/plugins/telegram/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/index.js +101 -0
- package/payload/platform/plugins/telegram/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/lib/telegram.d.ts +27 -0
- package/payload/platform/plugins/telegram/mcp/dist/lib/telegram.d.ts.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/lib/telegram.js +41 -0
- package/payload/platform/plugins/telegram/mcp/dist/lib/telegram.js.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/tools/message-history.d.ts +16 -0
- package/payload/platform/plugins/telegram/mcp/dist/tools/message-history.d.ts.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/tools/message-history.js +62 -0
- package/payload/platform/plugins/telegram/mcp/dist/tools/message-history.js.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/tools/message.d.ts +20 -0
- package/payload/platform/plugins/telegram/mcp/dist/tools/message.d.ts.map +1 -0
- package/payload/platform/plugins/telegram/mcp/dist/tools/message.js +34 -0
- package/payload/platform/plugins/telegram/mcp/dist/tools/message.js.map +1 -0
- package/payload/platform/plugins/telegram/mcp/package.json +19 -0
- package/payload/platform/plugins/telegram/references/setup-guide.md +50 -0
- package/payload/platform/plugins/web/PLUGIN.md +12 -0
- package/payload/platform/plugins/web/mcp/dist/index.d.ts +2 -0
- package/payload/platform/plugins/web/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/web/mcp/dist/index.js +12 -0
- package/payload/platform/plugins/web/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/web/mcp/package.json +18 -0
- package/payload/platform/scripts/seed-neo4j.sh +73 -0
- package/payload/platform/scripts/setup.sh +177 -0
- package/payload/platform/scripts/start.sh +62 -0
- package/payload/platform/templates/account.json +4 -0
- package/payload/platform/templates/agents/admin/IDENTITY.md +28 -0
- package/payload/platform/templates/agents/admin/SOUL.md +1 -0
- package/payload/platform/templates/agents/public/IDENTITY.md +21 -0
- package/payload/platform/templates/agents/public/SOUL.md +1 -0
- 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
|