@rubytech/create-maxy 1.0.629 → 1.0.630

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.
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>Maxy</title>
7
7
  <link rel="icon" href="/favicon.ico">
8
- <script type="module" crossorigin src="/assets/admin-CGIu9HnV.js"></script>
8
+ <script type="module" crossorigin src="/assets/admin-DirN63aF.js"></script>
9
9
  <link rel="modulepreload" crossorigin href="/assets/chunk-Be6NvmcD.js">
10
10
  <link rel="modulepreload" crossorigin href="/assets/preload-helper-rov5CBGT.js">
11
11
  <link rel="modulepreload" crossorigin href="/assets/useVoiceRecorder-tbj4tUsl.js">
@@ -3610,8 +3610,6 @@ function handleUpgrade(req, clientSocket, head, opts) {
3610
3610
  const qsIndex = url2.indexOf("?");
3611
3611
  const pathname = qsIndex === -1 ? url2 : url2.slice(0, qsIndex);
3612
3612
  if (pathname !== WS_PATH) {
3613
- vncLog("ws-upgrade", { decision: "rejected", reason: "wrong-path", path: pathname });
3614
- clientSocket.destroy();
3615
3613
  return;
3616
3614
  }
3617
3615
  const corrId = newCorrId();
@@ -3828,9 +3826,203 @@ Content-Length: 0\r
3828
3826
  socket.destroy();
3829
3827
  }
3830
3828
 
3829
+ // server/graph-proxy.ts
3830
+ import { createConnection as createConnection2 } from "net";
3831
+ var GRAPH_PREFIX = "/graph";
3832
+ var UPSTREAM_TIMEOUT_MS2 = 5e3;
3833
+ var HOP_BY_HOP2 = /* @__PURE__ */ new Set([
3834
+ "connection",
3835
+ "keep-alive",
3836
+ "proxy-authenticate",
3837
+ "proxy-authorization",
3838
+ "te",
3839
+ "trailer",
3840
+ "transfer-encoding",
3841
+ "upgrade"
3842
+ ]);
3843
+ function resolveUpstreamPort() {
3844
+ const uri = process.env.NEO4J_URI ?? "bolt://localhost:7687";
3845
+ const m = uri.match(/:(\d+)$/);
3846
+ const boltPort = m ? parseInt(m[1], 10) : 7687;
3847
+ return boltPort - 213;
3848
+ }
3849
+ var UPSTREAM_HOST = "127.0.0.1";
3850
+ var UPSTREAM_PORT = resolveUpstreamPort();
3851
+ function attachGraphHttpRoutes(app2) {
3852
+ const handler = async (c) => {
3853
+ const raw2 = c.req.raw;
3854
+ const url2 = new URL(raw2.url);
3855
+ const pathAfterPrefix = url2.pathname.slice(GRAPH_PREFIX.length) || "/";
3856
+ const upstreamUrl = `http://${UPSTREAM_HOST}:${UPSTREAM_PORT}${pathAfterPrefix}${url2.search}`;
3857
+ const upstreamHeaders = new Headers(raw2.headers);
3858
+ for (const h of HOP_BY_HOP2) upstreamHeaders.delete(h);
3859
+ upstreamHeaders.set("host", `${UPSTREAM_HOST}:${UPSTREAM_PORT}`);
3860
+ try {
3861
+ const upstream = await fetch(upstreamUrl, {
3862
+ method: raw2.method,
3863
+ headers: upstreamHeaders,
3864
+ body: raw2.body,
3865
+ // `duplex: 'half'` is required when forwarding a streaming body; TS
3866
+ // typings do not yet expose it but the undici runtime supports it.
3867
+ ...raw2.body ? { duplex: "half" } : {},
3868
+ redirect: "manual"
3869
+ });
3870
+ const resHeaders = new Headers(upstream.headers);
3871
+ for (const h of HOP_BY_HOP2) resHeaders.delete(h);
3872
+ return new Response(upstream.body, {
3873
+ status: upstream.status,
3874
+ statusText: upstream.statusText,
3875
+ headers: resHeaders
3876
+ });
3877
+ } catch (err) {
3878
+ const msg = err instanceof Error ? err.message : String(err);
3879
+ console.error(`[graph-proxy] upstream fetch failed for ${upstreamUrl}: ${msg}`);
3880
+ return c.text(`Graph proxy upstream unreachable: ${msg}`, 502);
3881
+ }
3882
+ };
3883
+ app2.all(GRAPH_PREFIX, handler);
3884
+ app2.all(`${GRAPH_PREFIX}/*`, handler);
3885
+ }
3886
+ function attachGraphWsProxy(server, opts) {
3887
+ server.on("upgrade", (req, clientSocket, head) => {
3888
+ try {
3889
+ const url2 = req.url ?? "";
3890
+ const qsIndex = url2.indexOf("?");
3891
+ const pathname = qsIndex === -1 ? url2 : url2.slice(0, qsIndex);
3892
+ const isGraphPath = pathname === GRAPH_PREFIX || pathname.startsWith(`${GRAPH_PREFIX}/`);
3893
+ if (!isGraphPath) {
3894
+ if (pathname !== "/websockify") {
3895
+ clientSocket.destroy();
3896
+ }
3897
+ return;
3898
+ }
3899
+ const hostHeader = (req.headers.host ?? "").split(":")[0];
3900
+ const remote = req.socket.remoteAddress;
3901
+ const xff = headerString2(req.headers["x-forwarded-for"]);
3902
+ const cookie = headerString2(req.headers.cookie);
3903
+ const decision = opts.canAccessAdmin({
3904
+ host: hostHeader,
3905
+ remoteAddress: remote,
3906
+ xForwardedFor: xff,
3907
+ cookieHeader: cookie,
3908
+ isPublicHost: opts.isPublicHost
3909
+ });
3910
+ if (!decision.allow) {
3911
+ const status = decision.reason === "public-host" ? 404 : 401;
3912
+ writeStatusAndDestroy2(clientSocket, status, decision.reason === "public-host" ? "Not Found" : "Unauthorized");
3913
+ return;
3914
+ }
3915
+ const originHeader = headerString2(req.headers.origin);
3916
+ const originHost = parseOriginHost2(originHeader);
3917
+ if (!originHost || originHost !== hostHeader || opts.isPublicHost(originHost)) {
3918
+ writeStatusAndDestroy2(clientSocket, 403, "Forbidden");
3919
+ return;
3920
+ }
3921
+ const rewrittenPath = pathname === GRAPH_PREFIX ? "/" : pathname.slice(GRAPH_PREFIX.length);
3922
+ const rewrittenUrl = qsIndex === -1 ? rewrittenPath : rewrittenPath + url2.slice(qsIndex);
3923
+ const upstream = createConnection2({ host: UPSTREAM_HOST, port: UPSTREAM_PORT });
3924
+ upstream.setTimeout(UPSTREAM_TIMEOUT_MS2);
3925
+ upstream.once("connect", () => {
3926
+ upstream.setTimeout(0);
3927
+ const lines = [];
3928
+ lines.push(`${req.method ?? "GET"} ${rewrittenUrl} HTTP/${req.httpVersion}`);
3929
+ lines.push(`host: ${UPSTREAM_HOST}:${UPSTREAM_PORT}`);
3930
+ for (const [name, value] of Object.entries(req.headers)) {
3931
+ if (name === "host") continue;
3932
+ if (HOP_BY_HOP2.has(name)) continue;
3933
+ if (value == null) continue;
3934
+ if (Array.isArray(value)) {
3935
+ for (const v of value) lines.push(`${name}: ${v}`);
3936
+ } else {
3937
+ lines.push(`${name}: ${value}`);
3938
+ }
3939
+ }
3940
+ const upgradeHeader = headerString2(req.headers.upgrade);
3941
+ const connectionHeader = headerString2(req.headers.connection);
3942
+ if (upgradeHeader) lines.push(`upgrade: ${upgradeHeader}`);
3943
+ if (connectionHeader) lines.push(`connection: ${connectionHeader}`);
3944
+ upstream.write(lines.join("\r\n") + "\r\n\r\n");
3945
+ if (head && head.length > 0) upstream.write(head);
3946
+ clientSocket.pipe(upstream);
3947
+ upstream.pipe(clientSocket);
3948
+ const teardown = () => {
3949
+ clientSocket.destroy();
3950
+ upstream.destroy();
3951
+ };
3952
+ clientSocket.once("close", teardown);
3953
+ upstream.once("close", teardown);
3954
+ clientSocket.once("error", teardown);
3955
+ upstream.once("error", teardown);
3956
+ });
3957
+ upstream.once("timeout", () => {
3958
+ writeStatusAndDestroy2(clientSocket, 504, "Gateway Timeout");
3959
+ upstream.destroy();
3960
+ });
3961
+ upstream.once("error", (err) => {
3962
+ console.error(`[graph-proxy] ws upstream error: ${err.message}`);
3963
+ writeStatusAndDestroy2(clientSocket, 502, "Bad Gateway");
3964
+ upstream.destroy();
3965
+ });
3966
+ } catch (err) {
3967
+ console.error(`[graph-proxy] upgrade handler exception: ${err.message}`);
3968
+ clientSocket.destroy();
3969
+ }
3970
+ });
3971
+ }
3972
+ function graphAuthMiddleware(opts) {
3973
+ return async (c, next) => {
3974
+ const url2 = new URL(c.req.raw.url);
3975
+ if (!url2.pathname.startsWith(GRAPH_PREFIX)) return next();
3976
+ const hostHeader = (c.req.header("host") ?? "").split(":")[0];
3977
+ const incoming = c.env?.incoming;
3978
+ const remote = incoming?.socket?.remoteAddress;
3979
+ const xff = c.req.header("x-forwarded-for");
3980
+ const cookie = c.req.header("cookie");
3981
+ const decision = opts.canAccessAdmin({
3982
+ host: hostHeader,
3983
+ remoteAddress: remote,
3984
+ xForwardedFor: xff,
3985
+ cookieHeader: cookie,
3986
+ isPublicHost: opts.isPublicHost
3987
+ });
3988
+ if (!decision.allow) {
3989
+ return c.text(
3990
+ decision.reason === "public-host" ? "Not Found" : "Unauthorized",
3991
+ decision.reason === "public-host" ? 404 : 401
3992
+ );
3993
+ }
3994
+ return next();
3995
+ };
3996
+ }
3997
+ function headerString2(value) {
3998
+ if (value == null) return void 0;
3999
+ return Array.isArray(value) ? value[0] : value;
4000
+ }
4001
+ function parseOriginHost2(origin) {
4002
+ if (!origin) return null;
4003
+ try {
4004
+ return new URL(origin).hostname;
4005
+ } catch {
4006
+ return null;
4007
+ }
4008
+ }
4009
+ function writeStatusAndDestroy2(socket, status, statusText) {
4010
+ try {
4011
+ socket.write(
4012
+ `HTTP/1.1 ${status} ${statusText}\r
4013
+ Connection: close\r
4014
+ Content-Length: 0\r
4015
+ \r
4016
+ `
4017
+ );
4018
+ } catch {
4019
+ }
4020
+ socket.destroy();
4021
+ }
4022
+
3831
4023
  // app/api/health/route.ts
3832
4024
  import { existsSync as existsSync11, readFileSync as readFileSync12 } from "fs";
3833
- import { createConnection as createConnection3 } from "net";
4025
+ import { createConnection as createConnection4 } from "net";
3834
4026
 
3835
4027
  // app/lib/claude-auth.ts
3836
4028
  import { readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
@@ -4187,7 +4379,7 @@ function contextWindow(model) {
4187
4379
 
4188
4380
  // app/lib/vnc.ts
4189
4381
  import { spawnSync, execFileSync } from "child_process";
4190
- import { createConnection as createConnection2 } from "net";
4382
+ import { createConnection as createConnection3 } from "net";
4191
4383
  import { mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
4192
4384
  import { resolve as resolve4 } from "path";
4193
4385
  var PLATFORM_ROOT2 = process.env.MAXY_PLATFORM_ROOT ?? resolve4(process.cwd(), "..");
@@ -4282,7 +4474,7 @@ async function waitForPort(port2, timeoutMs = 12e3) {
4282
4474
  const deadline = Date.now() + timeoutMs;
4283
4475
  while (Date.now() < deadline) {
4284
4476
  const ready = await new Promise((res) => {
4285
- const socket = createConnection2(port2, "127.0.0.1");
4477
+ const socket = createConnection3(port2, "127.0.0.1");
4286
4478
  socket.setTimeout(500);
4287
4479
  socket.once("connect", () => {
4288
4480
  socket.destroy();
@@ -7531,6 +7723,18 @@ function buildSpawnEnv(accountId, accountDir, conversationId) {
7531
7723
  STREAM_LOG_PATH: streamLogPath
7532
7724
  };
7533
7725
  }
7726
+ var cachedBrandHostname = null;
7727
+ function readBrandHostname() {
7728
+ if (cachedBrandHostname !== null) return cachedBrandHostname;
7729
+ try {
7730
+ const brandPath3 = resolve6(PLATFORM_ROOT4, "config", "brand.json");
7731
+ const parsed = JSON.parse(readFileSync7(brandPath3, "utf-8"));
7732
+ cachedBrandHostname = typeof parsed.hostname === "string" && parsed.hostname.length > 0 ? parsed.hostname : "maxy";
7733
+ } catch {
7734
+ cachedBrandHostname = "maxy";
7735
+ }
7736
+ return cachedBrandHostname;
7737
+ }
7534
7738
  function getMcpServers(accountId, conversationId, userId, enabledPlugins) {
7535
7739
  if (!conversationId) {
7536
7740
  throw new Error(`getMcpServers: conversationId is required (accountId=${accountId.slice(0, 8)})`);
@@ -7582,6 +7786,26 @@ function getMcpServers(accountId, conversationId, userId, enabledPlugins) {
7582
7786
  "plugin_playwright_playwright": {
7583
7787
  command: "npx",
7584
7788
  args: ["-y", "@playwright/mcp@latest", "--cdp-endpoint", "http://127.0.0.1:9222", "--caps", "pdf"]
7789
+ },
7790
+ // Graph MCP shim (Task 557) — spawns upstream `uvx mcp-neo4j-cypher` locked
7791
+ // into read-only mode and namespaced `maxy-graph`, wraps stderr with our
7792
+ // tee, and emits one `[graph-query]` line per tool call. Credentials flow
7793
+ // from the brand's own NEO4J_URI + config/.neo4j-password so a per-brand
7794
+ // admin session only ever sees its own Neo4j instance (isolation is
7795
+ // enforced upstream by the installer's process/port boundary per
7796
+ // MAXY-PRD.md:627, not in any application-layer filter).
7797
+ "graph": {
7798
+ command: "node",
7799
+ args: [resolve6(PLATFORM_ROOT4, "lib/graph-mcp/dist/index.js")],
7800
+ env: {
7801
+ ...baseEnv,
7802
+ BRAND: readBrandHostname(),
7803
+ NEO4J_URI: process.env.NEO4J_URI ?? "bolt://localhost:7687",
7804
+ NEO4J_USERNAME: process.env.NEO4J_USERNAME ?? process.env.NEO4J_USER ?? "neo4j",
7805
+ NEO4J_NAMESPACE: "maxy-graph",
7806
+ NEO4J_READ_ONLY: "true",
7807
+ NEO4J_RESPONSE_TOKEN_LIMIT: "20000"
7808
+ }
7585
7809
  }
7586
7810
  };
7587
7811
  const tgConfig = resolveAccount()?.config?.telegram;
@@ -7645,6 +7869,12 @@ var ADMIN_CORE_TOOLS = [
7645
7869
  "Glob",
7646
7870
  "Grep",
7647
7871
  "Agent",
7872
+ // Task 557 — upstream mcp-neo4j-cypher (namespaced maxy-graph, read-only).
7873
+ // maxy-graph_write_neo4j_cypher is intentionally absent: writes go through
7874
+ // the schema-aware memory-write tool, which validates labels, properties,
7875
+ // and embeddings. Admin-only by virtue of not being in the public allow list.
7876
+ "mcp__graph__maxy-graph_read_neo4j_cypher",
7877
+ "mcp__graph__maxy-graph_get_neo4j_schema",
7648
7878
  "mcp__memory__memory-search",
7649
7879
  "mcp__memory__memory-rank",
7650
7880
  "mcp__memory__memory-write",
@@ -10217,6 +10447,15 @@ ${body}`;
10217
10447
 
10218
10448
  ${manifest}`;
10219
10449
  }
10450
+ const graphRefPath = resolve6(PLATFORM_ROOT4, "plugins/memory/references/graph-primitives.md");
10451
+ try {
10452
+ const graphRef = readFileSync7(graphRefPath, "utf-8");
10453
+ baseSystemPrompt += `
10454
+
10455
+ ${graphRef}`;
10456
+ } catch (err) {
10457
+ console.error(`[graph-primitives] reference missing at ${graphRefPath} \u2014 admin session will have no Cypher cookbook: ${err instanceof Error ? err.message : String(err)}`);
10458
+ }
10220
10459
  }
10221
10460
  if (agentConfig?.budget) {
10222
10461
  const pluginTokens = embeddedPlugins.reduce((sum, p) => sum + estimateTokens(p.body), 0);
@@ -27923,7 +28162,7 @@ async function probeApiKey() {
27923
28162
  }
27924
28163
  function checkPort(port2, timeoutMs = 500) {
27925
28164
  return new Promise((resolve29) => {
27926
- const socket = createConnection3(port2, "127.0.0.1");
28165
+ const socket = createConnection4(port2, "127.0.0.1");
27927
28166
  socket.setTimeout(timeoutMs);
27928
28167
  socket.once("connect", () => {
27929
28168
  socket.destroy();
@@ -33202,6 +33441,9 @@ app.get("/:slug", async (c, next) => {
33202
33441
  }
33203
33442
  await next();
33204
33443
  });
33444
+ app.use("/graph/*", graphAuthMiddleware({ isPublicHost, canAccessAdmin }));
33445
+ app.use("/graph", graphAuthMiddleware({ isPublicHost, canAccessAdmin }));
33446
+ attachGraphHttpRoutes(app);
33205
33447
  app.use("/*", serveStatic({ root: "./public" }));
33206
33448
  var port = parseInt(process.env.PORT ?? "19200", 10);
33207
33449
  var hostname3 = process.env.HOSTNAME ?? "0.0.0.0";
@@ -33211,6 +33453,7 @@ attachVncWsProxy(httpServer, {
33211
33453
  upstreamHost: "127.0.0.1",
33212
33454
  upstreamPort: 6080
33213
33455
  });
33456
+ attachGraphWsProxy(httpServer, { isPublicHost, canAccessAdmin });
33214
33457
  console.log(`${BRAND.productName} listening on http://${hostname3}:${port}`);
33215
33458
  (async () => {
33216
33459
  try {