@rubytech/create-realagent 1.0.434 → 1.0.436

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 (28) hide show
  1. package/dist/index.js +22 -1
  2. package/dist/uninstall.js +44 -26
  3. package/package.json +1 -1
  4. package/payload/maxy/server.js +204 -76
  5. package/payload/platform/plugins/admin/mcp/dist/index.js +379 -10
  6. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  7. package/payload/platform/plugins/admin/skills/onboarding/skill.md +1 -1
  8. package/payload/platform/plugins/admin/skills/plugin-management/skill.md +16 -58
  9. package/payload/platform/plugins/admin/skills/public-agent-manager/skill.md +20 -3
  10. package/payload/platform/plugins/anthropic/PLUGIN.md +2 -2
  11. package/payload/platform/plugins/cloudflare/mcp/dist/index.js +22 -12
  12. package/payload/platform/plugins/cloudflare/mcp/dist/index.js.map +1 -1
  13. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.d.ts +9 -5
  14. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.d.ts.map +1 -1
  15. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.js +82 -31
  16. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.js.map +1 -1
  17. package/payload/platform/plugins/email/mcp/dist/scripts/email-auto-respond.d.ts +1 -1
  18. package/payload/platform/plugins/email/mcp/dist/scripts/email-auto-respond.js +30 -4
  19. package/payload/platform/plugins/email/mcp/dist/scripts/email-auto-respond.js.map +1 -1
  20. package/payload/platform/plugins/email/mcp/dist/scripts/email-fetch.js +31 -3
  21. package/payload/platform/plugins/email/mcp/dist/scripts/email-fetch.js.map +1 -1
  22. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.d.ts +1 -1
  23. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +30 -2
  24. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
  25. package/payload/platform/scripts/resume-tunnel.sh +14 -1
  26. package/payload/platform/scripts/vnc.sh +14 -1
  27. package/payload/platform/templates/agents/admin/IDENTITY.md +1 -1
  28. package/payload/premium-plugins/real-agency/plugins/real-agency-teaching/PLUGIN.md +1 -2
@@ -3128,7 +3128,7 @@ async function GET() {
3128
3128
 
3129
3129
  // app/lib/claude-agent.ts
3130
3130
  import Anthropic from "@anthropic-ai/sdk";
3131
- import { spawn } from "child_process";
3131
+ import { spawn as spawn2 } from "child_process";
3132
3132
  import { resolve as resolve4 } from "path";
3133
3133
  import { readFileSync as readFileSync5, readdirSync, existsSync as existsSync5, mkdirSync as mkdirSync2, createWriteStream, statSync as statSync2, unlinkSync } from "fs";
3134
3134
 
@@ -3184,6 +3184,17 @@ async function ensureVnc() {
3184
3184
  }
3185
3185
  async function ensureCdp() {
3186
3186
  if (await waitForPort(9222, 1e3)) return true;
3187
+ const xAlive = await waitForPort(5900, 1e3);
3188
+ if (!xAlive) {
3189
+ console.error("[ensureCdp] X server down on :5900 \u2014 escalating to full VNC restart");
3190
+ const vncOk = await ensureVnc();
3191
+ if (!vncOk) {
3192
+ console.error("[ensureCdp] Full VNC restart failed \u2014 browser-specialist degraded");
3193
+ return false;
3194
+ }
3195
+ return waitForPort(9222, 12e3);
3196
+ }
3197
+ console.error("[ensureCdp] X alive on :5900, restarting Chrome only");
3187
3198
  try {
3188
3199
  execFileSync("bash", [VNC_SCRIPT, "start-chrome"], { timeout: 2e4 });
3189
3200
  } catch {
@@ -3256,6 +3267,7 @@ function buildX11Env(chromiumWrapperPath) {
3256
3267
  // app/lib/neo4j-store.ts
3257
3268
  import neo4j from "neo4j-driver";
3258
3269
  import { randomUUID } from "crypto";
3270
+ import { spawn } from "child_process";
3259
3271
  import { readFileSync as readFileSync4 } from "fs";
3260
3272
  import { resolve as resolve3 } from "path";
3261
3273
  var PLATFORM_ROOT2 = process.env.MAXY_PLATFORM_ROOT ?? resolve3(process.cwd(), "..");
@@ -3609,47 +3621,162 @@ async function deleteConversation(conversationId) {
3609
3621
  }
3610
3622
  }
3611
3623
  var GENERIC_MESSAGE = /^(h(i|ello|ey|owdy)|yo|sup|thanks|thank you|ok|okay|yes|no|good\s*(morning|afternoon|evening|night)|greetings|what'?s\s*up)[\s!?.,:;]*$/i;
3612
- function deriveLabel(message) {
3624
+ var SESSION_LABEL_MSG_CAP = 500;
3625
+ var SESSION_LABEL_TIMEOUT_MS = 15e3;
3626
+ var SESSION_LABEL_MODEL = "claude-haiku-4-5-20251001";
3627
+ var SESSION_LABEL_MAX_WORDS = 6;
3628
+ var SESSION_LABEL_MAX_ATTEMPTS = 3;
3629
+ var SESSION_LABEL_MAX_STDERR = 2048;
3630
+ function isMessageUseful(message) {
3613
3631
  const trimmed = message.trim();
3614
- if (trimmed.length < 4) return null;
3615
- if (GENERIC_MESSAGE.test(trimmed)) return null;
3616
- if (trimmed.startsWith("[New session.")) return null;
3617
- const firstSentence = trimmed.split(/[.!?\n]/)[0].trim();
3618
- if (firstSentence.length < 4) return null;
3619
- const words = firstSentence.split(/\s+/).slice(0, 8);
3620
- return words.join(" ");
3632
+ if (trimmed.length < 4) return false;
3633
+ if (trimmed.startsWith('{"')) return false;
3634
+ if (GENERIC_MESSAGE.test(trimmed)) return false;
3635
+ if (trimmed.startsWith("[New session.")) return false;
3636
+ return true;
3637
+ }
3638
+ var labelAccumulator = /* @__PURE__ */ new Map();
3639
+ var _spawnOverride = null;
3640
+ var SESSION_LABEL_SYSTEM = `You are a session labeler. Given the opening messages of a conversation with an AI assistant, produce a concise topic label.
3641
+
3642
+ Rules:
3643
+ - Exactly 3 to 6 words
3644
+ - Summarize the user's intent, do not copy verbatim
3645
+ - Capitalize the first word only (sentence case)
3646
+ - No punctuation, no quotes
3647
+ - If the messages are too vague or meaningless to summarize, respond with exactly: SKIP`;
3648
+ async function generateSessionLabel(messages) {
3649
+ const cappedMessages = messages.map((m) => m.slice(0, SESSION_LABEL_MSG_CAP));
3650
+ const userContent = cappedMessages.map((m, i) => `Message ${i + 1}: ${m}`).join("\n");
3651
+ const prompt = `${SESSION_LABEL_SYSTEM}
3652
+
3653
+ ${userContent}`;
3654
+ const args = [
3655
+ "--print",
3656
+ "--model",
3657
+ SESSION_LABEL_MODEL,
3658
+ "--max-turns",
3659
+ "1",
3660
+ "--permission-mode",
3661
+ "dontAsk",
3662
+ prompt
3663
+ ];
3664
+ return new Promise((resolve16) => {
3665
+ let stdout = "";
3666
+ let stderr = "";
3667
+ const spawnFn = _spawnOverride ?? spawn;
3668
+ const proc = spawnFn("claude", args, {
3669
+ stdio: ["ignore", "pipe", "pipe"]
3670
+ });
3671
+ proc.stdout?.on("data", (chunk) => {
3672
+ stdout += chunk.toString("utf-8");
3673
+ });
3674
+ proc.stderr?.on("data", (chunk) => {
3675
+ if (stderr.length < SESSION_LABEL_MAX_STDERR) {
3676
+ stderr += chunk.toString("utf-8").slice(0, SESSION_LABEL_MAX_STDERR - stderr.length);
3677
+ }
3678
+ });
3679
+ const timer = setTimeout(() => {
3680
+ proc.kill("SIGTERM");
3681
+ console.error("[persist] autoLabel: haiku subprocess timed out");
3682
+ resolve16(null);
3683
+ }, SESSION_LABEL_TIMEOUT_MS);
3684
+ proc.on("error", (err) => {
3685
+ clearTimeout(timer);
3686
+ console.error(`[persist] autoLabel: subprocess error \u2014 ${err.message}`);
3687
+ resolve16(null);
3688
+ });
3689
+ proc.on("close", (code) => {
3690
+ clearTimeout(timer);
3691
+ if (code !== 0) {
3692
+ console.error(`[persist] autoLabel: subprocess exited code=${code}${stderr ? ` stderr=${stderr.trim().slice(0, 200)}` : ""}`);
3693
+ resolve16(null);
3694
+ return;
3695
+ }
3696
+ const text = stdout.trim();
3697
+ if (!text) {
3698
+ console.error("[persist] autoLabel: haiku returned empty response");
3699
+ resolve16(null);
3700
+ return;
3701
+ }
3702
+ if (text === "SKIP") {
3703
+ console.error("[persist] autoLabel: haiku returned SKIP \u2014 messages too vague");
3704
+ resolve16(null);
3705
+ return;
3706
+ }
3707
+ const words = text.split(/\s+/).slice(0, SESSION_LABEL_MAX_WORDS);
3708
+ const label = words.join(" ");
3709
+ console.error(`[persist] autoLabel: haiku response="${label}"`);
3710
+ resolve16(label);
3711
+ });
3712
+ });
3621
3713
  }
3622
3714
  async function autoLabelSession(conversationId, userMessage) {
3623
- const label = deriveLabel(userMessage);
3624
- if (!label) return;
3625
- let embedding = null;
3626
- try {
3627
- embedding = await embed(label);
3628
- } catch (err) {
3629
- console.error(`[persist] Conversation embedding failed, labelling without: ${err instanceof Error ? err.message : String(err)}`);
3715
+ if (!conversationId) return;
3716
+ if (!isMessageUseful(userMessage)) {
3717
+ const reason = userMessage.trim().startsWith('{"') ? "JSON envelope" : userMessage.trim().length < 4 ? "too short" : GENERIC_MESSAGE.test(userMessage.trim()) ? "greeting" : "directive";
3718
+ console.error(`[persist] autoLabel: skipped ${conversationId.slice(0, 8)}\u2026 \u2014 ${reason}`);
3719
+ return;
3630
3720
  }
3631
- const session = getSession();
3721
+ let entry = labelAccumulator.get(conversationId);
3722
+ if (!entry) {
3723
+ entry = { messages: [], pending: false, attempts: 0 };
3724
+ labelAccumulator.set(conversationId, entry);
3725
+ }
3726
+ if (entry.attempts >= SESSION_LABEL_MAX_ATTEMPTS) {
3727
+ console.error(`[persist] autoLabel: evicted ${conversationId.slice(0, 8)}\u2026 after ${SESSION_LABEL_MAX_ATTEMPTS} attempts`);
3728
+ labelAccumulator.delete(conversationId);
3729
+ return;
3730
+ }
3731
+ entry.messages.push(userMessage.trim());
3732
+ if (entry.pending) {
3733
+ console.error(`[persist] autoLabel: accumulated for ${conversationId.slice(0, 8)}\u2026 (pending, ${entry.messages.length} msgs)`);
3734
+ return;
3735
+ }
3736
+ entry.pending = true;
3737
+ entry.attempts++;
3632
3738
  try {
3633
- const result = await session.run(
3634
- `MATCH (c:Conversation {conversationId: $conversationId})
3635
- WHERE c.name IS NULL
3636
- WITH c
3637
- OPTIONAL MATCH (m:Message)-[:PART_OF]->(c)
3638
- WHERE m.role = 'user'
3639
- WITH c, count(m) AS userCount
3640
- WHERE userCount <= 3
3641
- SET c.name = $label, c.updatedAt = datetime()
3642
- ${embedding ? ", c.embedding = $embedding" : ""}
3643
- RETURN c.name AS name`,
3644
- { conversationId, label, ...embedding ? { embedding } : {} }
3645
- );
3646
- if (result.records.length > 0) {
3647
- console.error(`[persist] Auto-labeled session ${conversationId.slice(0, 8)}\u2026: "${label}"${embedding ? " (embedded)" : ""}`);
3739
+ const label = await generateSessionLabel(entry.messages);
3740
+ if (!label) {
3741
+ entry.pending = false;
3742
+ return;
3743
+ }
3744
+ const fullLabel = `${label} \xB7 ${conversationId.slice(0, 8)}`;
3745
+ let embedding = null;
3746
+ try {
3747
+ embedding = await embed(fullLabel);
3748
+ } catch (err) {
3749
+ console.error(`[persist] Conversation embedding failed, labelling without: ${err instanceof Error ? err.message : String(err)}`);
3750
+ }
3751
+ const session = getSession();
3752
+ try {
3753
+ const result = await session.run(
3754
+ `MATCH (c:Conversation {conversationId: $conversationId})
3755
+ WHERE c.name IS NULL
3756
+ WITH c
3757
+ OPTIONAL MATCH (m:Message)-[:PART_OF]->(c)
3758
+ WHERE m.role = 'user'
3759
+ WITH c, count(m) AS userCount
3760
+ WHERE userCount <= 3
3761
+ SET c.name = $label, c.updatedAt = datetime()
3762
+ ${embedding ? ", c.embedding = $embedding" : ""}
3763
+ RETURN c.name AS name`,
3764
+ { conversationId, label: fullLabel, ...embedding ? { embedding } : {} }
3765
+ );
3766
+ if (result.records.length > 0) {
3767
+ console.error(`[persist] Auto-labeled session ${conversationId.slice(0, 8)}\u2026: "${fullLabel}"${embedding ? " (embedded)" : ""}`);
3768
+ labelAccumulator.delete(conversationId);
3769
+ }
3770
+ } catch (err) {
3771
+ console.error(`[persist] autoLabelSession failed: ${err instanceof Error ? err.message : String(err)}`);
3772
+ } finally {
3773
+ await session.close();
3648
3774
  }
3649
3775
  } catch (err) {
3650
- console.error(`[persist] autoLabelSession failed: ${err instanceof Error ? err.message : String(err)}`);
3776
+ console.error(`[persist] autoLabel: unexpected error \u2014 ${err instanceof Error ? err.message : String(err)}`);
3651
3777
  } finally {
3652
- await session.close();
3778
+ const currentEntry = labelAccumulator.get(conversationId);
3779
+ if (currentEntry) currentEntry.pending = false;
3653
3780
  }
3654
3781
  }
3655
3782
  var INITIAL_CONFIDENCE = 0.5;
@@ -4541,7 +4668,7 @@ function fetchMcpToolsList(pluginDir) {
4541
4668
  if (!existsSync5(serverPath)) return Promise.resolve([]);
4542
4669
  const startMs = Date.now();
4543
4670
  return new Promise((resolvePromise) => {
4544
- const proc = spawn(process.execPath, [serverPath], {
4671
+ const proc = spawn2(process.execPath, [serverPath], {
4545
4672
  env: {
4546
4673
  ...process.env,
4547
4674
  PLATFORM_ROOT: PLATFORM_ROOT3,
@@ -5179,7 +5306,7 @@ async function fetchMemoryContext(accountId, query, sessionKey, options) {
5179
5306
  }
5180
5307
  const startMs = Date.now();
5181
5308
  return new Promise((resolve16) => {
5182
- const proc = spawn(process.execPath, [serverPath], {
5309
+ const proc = spawn2(process.execPath, [serverPath], {
5183
5310
  env: {
5184
5311
  ...process.env,
5185
5312
  ACCOUNT_ID: accountId,
@@ -5274,7 +5401,7 @@ async function compactTrimmedMessages(accountId, trimmedMessages) {
5274
5401
  if (!existsSync5(serverPath)) return false;
5275
5402
  const briefing = trimmedMessages.map((m) => `[${m.role.toUpperCase()}] ${m.content}`).join("\n\n");
5276
5403
  return new Promise((resolvePromise) => {
5277
- const proc = spawn(process.execPath, [serverPath], {
5404
+ const proc = spawn2(process.execPath, [serverPath], {
5278
5405
  env: { ...process.env, ACCOUNT_ID: accountId }
5279
5406
  });
5280
5407
  let buffer = "";
@@ -5699,7 +5826,7 @@ async function* runCompactionTurn(accountDir, accountId, systemPrompt, resumeSes
5699
5826
  resumeSessionId,
5700
5827
  COMPACTION_PROMPT
5701
5828
  ];
5702
- const proc = spawn("claude", args, {
5829
+ const proc = spawn2("claude", args, {
5703
5830
  cwd: accountDir,
5704
5831
  stdio: ["ignore", "pipe", "pipe"],
5705
5832
  env: { ...process.env, PLATFORM_ROOT: PLATFORM_ROOT3, ACCOUNT_DIR: accountDir }
@@ -6271,7 +6398,13 @@ function buildAttachmentMetaText(attachments) {
6271
6398
  async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, adminModel, sessionKey, maxTurns = 20, attachments = [], retryCount = 0, enabledPlugins) {
6272
6399
  const userTimestamp = (/* @__PURE__ */ new Date()).toISOString();
6273
6400
  const resumeSessionId = sessionKey ? getAgentSessionId(sessionKey) : void 0;
6274
- await ensureCdp();
6401
+ const cdpOk = await ensureCdp();
6402
+ if (!cdpOk) {
6403
+ const cdpLog = agentLogStream("claude-agent-stream", accountDir);
6404
+ cdpLog.write(`[${isoTs()}] [warn] ensureCdp failed \u2014 browser-specialist degraded
6405
+ `);
6406
+ cdpLog.end();
6407
+ }
6275
6408
  const mcpConfig = JSON.stringify({ mcpServers: getMcpServers(accountId, enabledPlugins) });
6276
6409
  const specialistsDir = resolve4(accountDir, "specialists");
6277
6410
  if (!existsSync5(specialistsDir)) agentLogStream("claude-agent-stream", accountDir).write(`[${isoTs()}] [warn] specialists plugin dir missing: ${specialistsDir}
@@ -6301,7 +6434,7 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
6301
6434
  }
6302
6435
  const fullMessage = attachments.length > 0 ? message + buildAttachmentMetaText(attachments) : message;
6303
6436
  args.push(fullMessage);
6304
- const proc = spawn("claude", args, {
6437
+ const proc = spawn2("claude", args, {
6305
6438
  cwd: accountDir,
6306
6439
  stdio: ["ignore", "pipe", "pipe"],
6307
6440
  env: { ...process.env, PLATFORM_ROOT: PLATFORM_ROOT3, ACCOUNT_DIR: accountDir }
@@ -6521,7 +6654,9 @@ async function* invokeManagedAdminAgent(message, systemPrompt, accountDir, accou
6521
6654
  }
6522
6655
  const history = getMessageHistory(sessionKey);
6523
6656
  const augmentedSystemPrompt = buildManagedSystemPrompt(systemPrompt, history);
6524
- await ensureCdp();
6657
+ const cdpOk = await ensureCdp();
6658
+ if (!cdpOk) streamLog.write(`[${isoTs()}] [warn] ensureCdp failed \u2014 browser-specialist degraded
6659
+ `);
6525
6660
  const mcpConfig = JSON.stringify({ mcpServers: getMcpServers(accountId, enabledPlugins) });
6526
6661
  const specialistsDir = resolve4(accountDir, "specialists");
6527
6662
  if (!existsSync5(specialistsDir)) streamLog.write(`[${isoTs()}] [warn] specialists plugin dir missing: ${specialistsDir}
@@ -6548,7 +6683,7 @@ async function* invokeManagedAdminAgent(message, systemPrompt, accountDir, accou
6548
6683
  specialistsDir,
6549
6684
  fullMessage
6550
6685
  ];
6551
- const proc = spawn("claude", args, {
6686
+ const proc = spawn2("claude", args, {
6552
6687
  cwd: accountDir,
6553
6688
  stdio: ["ignore", "pipe", "pipe"],
6554
6689
  env: { ...process.env, PLATFORM_ROOT: PLATFORM_ROOT3, ACCOUNT_DIR: accountDir }
@@ -7807,10 +7942,9 @@ function renderLoginPage(opts) {
7807
7942
  const changeError = opts?.changeError ?? "";
7808
7943
  const success = opts?.success ?? "";
7809
7944
  const setupError = opts?.setupError ?? "";
7810
- const productName = opts?.productName ?? "Maxy";
7811
- const logoSrc = opts?.logoSrc ?? "/brand/maxy-monochrome.png";
7812
- const displayFont = opts?.displayFont ?? "Cormorant";
7813
- const bodyFont = opts?.bodyFont ?? "DM Sans";
7945
+ const primaryColor = opts?.primaryColor ?? "#7C8C72";
7946
+ const primaryHoverColor = opts?.primaryHoverColor ?? "#6A7A62";
7947
+ const primarySubtle = opts?.primarySubtle ?? "rgba(124,140,114,0.08)";
7814
7948
  const errorHtml = error ? `<p class="msg msg--error">${escapeHtml(error)}</p>` : "";
7815
7949
  const changeErrorHtml = changeError ? `<p class="msg msg--error">${escapeHtml(changeError)}</p>` : "";
7816
7950
  const successHtml = success ? `<p class="msg msg--success">${escapeHtml(success)}</p>` : "";
@@ -7834,14 +7968,14 @@ function renderLoginPage(opts) {
7834
7968
  <head>
7835
7969
  <meta charset="utf-8">
7836
7970
  <meta name="viewport" content="width=device-width, initial-scale=1">
7837
- <title>Sign in \u2014 ${escapeHtml(productName)}</title>
7971
+ <title>Sign in \u2014 Maxy</title>
7838
7972
  <link rel="preconnect" href="https://fonts.googleapis.com">
7839
7973
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
7840
- <link href="https://fonts.googleapis.com/css2?family=${encodeURIComponent(displayFont)}:wght@300;400&family=${encodeURIComponent(bodyFont)}:wght@400;500&display=swap" rel="stylesheet">
7974
+ <link href="https://fonts.googleapis.com/css2?family=Cormorant:wght@300;400&family=DM+Sans:wght@400;500&display=swap" rel="stylesheet">
7841
7975
  <style>
7842
7976
  * { margin: 0; padding: 0; box-sizing: border-box; }
7843
7977
  body {
7844
- font-family: '${bodyFont}', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
7978
+ font-family: 'DM Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
7845
7979
  background: #FAFAF8;
7846
7980
  color: #1A1A1A;
7847
7981
  min-height: 100vh;
@@ -7865,7 +7999,7 @@ function renderLoginPage(opts) {
7865
7999
  object-fit: contain;
7866
8000
  }
7867
8001
  .title {
7868
- font-family: '${displayFont}', Georgia, serif;
8002
+ font-family: 'Cormorant', Georgia, serif;
7869
8003
  font-weight: 300;
7870
8004
  font-size: 28px;
7871
8005
  color: #1A1A1A;
@@ -7903,8 +8037,8 @@ function renderLoginPage(opts) {
7903
8037
  transition: border-color 0.15s, box-shadow 0.15s;
7904
8038
  }
7905
8039
  .field-input:focus {
7906
- border-color: #7C8C72;
7907
- box-shadow: 0 0 0 3px rgba(124,140,114,0.08);
8040
+ border-color: ${primaryColor};
8041
+ box-shadow: 0 0 0 3px ${primarySubtle};
7908
8042
  }
7909
8043
  .options {
7910
8044
  display: flex;
@@ -7938,8 +8072,8 @@ function renderLoginPage(opts) {
7938
8072
  flex-shrink: 0;
7939
8073
  }
7940
8074
  .check input:checked + .check-box {
7941
- border-color: #7C8C72;
7942
- color: #7C8C72;
8075
+ border-color: ${primaryColor};
8076
+ color: ${primaryColor};
7943
8077
  }
7944
8078
  .check-label {
7945
8079
  font-size: 13px;
@@ -7964,7 +8098,7 @@ function renderLoginPage(opts) {
7964
8098
  width: 100%;
7965
8099
  padding: 12px;
7966
8100
  margin-top: 16px;
7967
- background: #7C8C72;
8101
+ background: ${primaryColor};
7968
8102
  color: #fff;
7969
8103
  border: none;
7970
8104
  border-radius: 10px;
@@ -7974,7 +8108,7 @@ function renderLoginPage(opts) {
7974
8108
  cursor: pointer;
7975
8109
  transition: background 0.15s;
7976
8110
  }
7977
- .btn:hover:not(:disabled) { background: #6A7A62; }
8111
+ .btn:hover:not(:disabled) { background: ${primaryHoverColor}; }
7978
8112
  .btn:disabled { opacity: 0.5; cursor: not-allowed; }
7979
8113
 
7980
8114
  /* Messages */
@@ -7984,7 +8118,7 @@ function renderLoginPage(opts) {
7984
8118
  margin-top: 8px;
7985
8119
  }
7986
8120
  .msg--error { color: #c44; }
7987
- .msg--success { color: #7C8C72; }
8121
+ .msg--success { color: ${primaryColor}; }
7988
8122
 
7989
8123
  /* Strength checklist (setup mode) */
7990
8124
  .strength-checklist { margin: 8px 0; }
@@ -8007,8 +8141,8 @@ function renderLoginPage(opts) {
8007
8141
  </head>
8008
8142
  <body>
8009
8143
  <div class="page">
8010
- <img src="${escapeHtml(logoSrc)}" alt="${escapeHtml(productName)}" class="logo-img">
8011
- <h1 class="title">${escapeHtml(productName)}</h1>
8144
+ <img src="/brand/maxy-monochrome.png" alt="Maxy" class="logo-img">
8145
+ <h1 class="title">Maxy</h1>
8012
8146
  <p class="subtitle">${escapeHtml(subtitleText)}</p>
8013
8147
 
8014
8148
  ${successHtml}
@@ -8100,7 +8234,7 @@ function renderLoginPage(opts) {
8100
8234
  <script>
8101
8235
  (function() {
8102
8236
  try {
8103
- var ch = new BroadcastChannel('platform-onboarding');
8237
+ var ch = new BroadcastChannel('maxy-onboarding');
8104
8238
  ch.postMessage({ type: 'remote-password-set' });
8105
8239
  ch.close();
8106
8240
  } catch(e) {}
@@ -8158,7 +8292,7 @@ function renderLoginPage(opts) {
8158
8292
  var met = r.test(p);
8159
8293
  if (!met) allMet = false;
8160
8294
  el.querySelector('.strength-icon').textContent = met ? '\\u2713' : '\\u25CB';
8161
- el.style.color = met ? '#7C8C72' : '#6B6B6B';
8295
+ el.style.color = met ? '${primaryColor}' : '#6B6B6B';
8162
8296
  });
8163
8297
  var match = p.length > 0 && p === confirm.value;
8164
8298
  btn.disabled = !(allMet && match);
@@ -9056,7 +9190,7 @@ async function POST8(req) {
9056
9190
  }
9057
9191
 
9058
9192
  // app/api/onboarding/claude-auth/route.ts
9059
- import { spawn as spawn2, execFileSync as execFileSync2 } from "child_process";
9193
+ import { spawn as spawn3, execFileSync as execFileSync2 } from "child_process";
9060
9194
  import { openSync, closeSync, writeFileSync as writeFileSync6, writeSync } from "fs";
9061
9195
  function checkAuthStatus() {
9062
9196
  try {
@@ -9123,7 +9257,7 @@ async function POST9(req) {
9123
9257
  const chromiumWrapper = writeChromiumWrapper();
9124
9258
  const x11Env = buildX11Env(chromiumWrapper);
9125
9259
  const claudeAuthLogFd = openSync(logPath("claude-auth"), "a");
9126
- const claudeProc = spawn2("claude", ["auth", "login"], {
9260
+ const claudeProc = spawn3("claude", ["auth", "login"], {
9127
9261
  env: x11Env,
9128
9262
  stdio: ["ignore", "pipe", "pipe"]
9129
9263
  });
@@ -9781,7 +9915,7 @@ async function GET7() {
9781
9915
  }
9782
9916
 
9783
9917
  // app/api/admin/version/upgrade/route.ts
9784
- import { spawn as spawn3 } from "child_process";
9918
+ import { spawn as spawn4 } from "child_process";
9785
9919
  import { existsSync as existsSync16, statSync as statSync4, writeFileSync as writeFileSync9, readFileSync as readFileSync16, openSync as openSync2, closeSync as closeSync2 } from "fs";
9786
9920
  import { resolve as resolve14, join as join4 } from "path";
9787
9921
  var PLATFORM_ROOT7 = process.env.MAXY_PLATFORM_ROOT ?? resolve14(process.cwd(), "..");
@@ -9833,7 +9967,7 @@ async function POST14(req) {
9833
9967
  invalidateVersionCache();
9834
9968
  try {
9835
9969
  const logFd = openSync2(LOG_FILE, "w");
9836
- const child = spawn3("npx", ["-y", `${upgradePkg}@latest`], {
9970
+ const child = spawn4("npx", ["-y", `${upgradePkg}@latest`], {
9837
9971
  detached: true,
9838
9972
  stdio: ["ignore", logFd, logFd],
9839
9973
  env: { ...process.env, npm_config_yes: "true" },
@@ -9968,6 +10102,11 @@ if (BRAND_JSON_PATH && existsSync17(BRAND_JSON_PATH)) {
9968
10102
  process.exit(1);
9969
10103
  }
9970
10104
  }
10105
+ var brandLoginOpts = {
10106
+ primaryColor: BRAND.defaultColors?.primary,
10107
+ primaryHoverColor: BRAND.defaultColors?.primaryHover,
10108
+ primarySubtle: BRAND.defaultColors?.primarySubtle
10109
+ };
9971
10110
  var ALIAS_DOMAINS_PATH = join5(homedir2(), BRAND.configDir, "alias-domains.json");
9972
10111
  function loadAliasDomains() {
9973
10112
  try {
@@ -10302,24 +10441,13 @@ app.get(
10302
10441
  );
10303
10442
  var htmlCache = /* @__PURE__ */ new Map();
10304
10443
  var brandLogoPath = "/brand/maxy-monochrome.png";
10305
- var brandDisplayFont = "Cormorant";
10306
- var brandBodyFont = "DM Sans";
10307
10444
  if (BRAND_JSON_PATH && existsSync17(BRAND_JSON_PATH)) {
10308
10445
  try {
10309
10446
  const fullBrand = JSON.parse(readFileSync17(BRAND_JSON_PATH, "utf-8"));
10310
10447
  if (fullBrand.assets?.logo) brandLogoPath = `/brand/${fullBrand.assets.logo}`;
10311
- const extractFontName = (stack) => stack.split(",")[0].replace(/'/g, "").trim();
10312
- if (fullBrand.defaultFonts?.display) brandDisplayFont = extractFontName(fullBrand.defaultFonts.display);
10313
- if (fullBrand.defaultFonts?.body) brandBodyFont = extractFontName(fullBrand.defaultFonts.body);
10314
10448
  } catch {
10315
10449
  }
10316
10450
  }
10317
- var brandLoginOpts = {
10318
- productName: BRAND.productName,
10319
- logoSrc: brandLogoPath,
10320
- displayFont: brandDisplayFont,
10321
- bodyFont: brandBodyFont
10322
- };
10323
10451
  var brandScript = `<script>window.__BRAND__=${JSON.stringify({
10324
10452
  productName: BRAND.productName,
10325
10453
  hostname: BRAND.hostname,