@rubytech/create-maxy-code 0.1.112 → 0.1.114

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 (56) hide show
  1. package/dist/__tests__/brew-install.test.js +10 -0
  2. package/dist/__tests__/brew-resolve.test.js +35 -0
  3. package/dist/__tests__/launchd-plist.test.js +46 -0
  4. package/dist/__tests__/macos-darwin-branch.test.js +85 -0
  5. package/dist/brew-install.js +5 -0
  6. package/dist/index.js +26 -6
  7. package/package.json +1 -1
  8. package/payload/platform/neo4j/schema.cypher +7 -2
  9. package/payload/platform/plugins/admin/skills/public-agent-manager/SKILL.md +14 -7
  10. package/payload/platform/plugins/cloudflare/references/manual-setup.md +105 -0
  11. package/payload/platform/plugins/docs/references/deployment.md +22 -4
  12. package/payload/platform/plugins/docs/references/internals.md +10 -26
  13. package/payload/platform/plugins/memory/PLUGIN.md +2 -1
  14. package/payload/platform/plugins/memory/mcp/dist/index.js +35 -0
  15. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  16. package/payload/platform/plugins/memory/mcp/dist/tools/image-fetch.d.ts +15 -0
  17. package/payload/platform/plugins/memory/mcp/dist/tools/image-fetch.d.ts.map +1 -0
  18. package/payload/platform/plugins/memory/mcp/dist/tools/image-fetch.js +64 -0
  19. package/payload/platform/plugins/memory/mcp/dist/tools/image-fetch.js.map +1 -0
  20. package/payload/platform/plugins/memory/references/schema-estate-agent.md +25 -0
  21. package/payload/platform/plugins/venture-studio/PLUGIN.md +7 -3
  22. package/payload/platform/plugins/venture-studio/bin/scaffold.sh +14 -2
  23. package/payload/platform/plugins/venture-studio/skills/investor-data-room/references/data-room-structure.md +10 -0
  24. package/payload/platform/plugins/venture-studio/skills/prototype-host/SKILL.md +179 -0
  25. package/payload/platform/plugins/venture-studio/skills/prototype-host/references/cloudflared-ingress-edit.md +81 -0
  26. package/payload/platform/plugins/venture-studio/skills/prototype-host/references/scaffold-frameworks.md +60 -0
  27. package/payload/platform/plugins/venture-studio/skills/prototype-host/references/systemd-user-service.md +104 -0
  28. package/payload/platform/plugins/whatsapp/.claude-plugin/plugin.json +9 -0
  29. package/payload/platform/plugins/whatsapp/PLUGIN.md +1 -1
  30. package/payload/platform/services/claude-session-manager/dist/brand-foreign-filter.d.ts +30 -0
  31. package/payload/platform/services/claude-session-manager/dist/brand-foreign-filter.d.ts.map +1 -0
  32. package/payload/platform/services/claude-session-manager/dist/brand-foreign-filter.js +86 -0
  33. package/payload/platform/services/claude-session-manager/dist/brand-foreign-filter.js.map +1 -0
  34. package/payload/platform/services/claude-session-manager/dist/index.js +51 -9
  35. package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
  36. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
  37. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +35 -4
  38. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
  39. package/payload/platform/services/claude-session-manager/dist/system-prompt.d.ts +7 -0
  40. package/payload/platform/services/claude-session-manager/dist/system-prompt.d.ts.map +1 -1
  41. package/payload/platform/services/claude-session-manager/dist/system-prompt.js +7 -0
  42. package/payload/platform/services/claude-session-manager/dist/system-prompt.js.map +1 -1
  43. package/payload/platform/templates/agents/public/IDENTITY.md +11 -1
  44. package/payload/premium-plugins/real-agent/BUNDLE.md +1 -1
  45. package/payload/premium-plugins/real-agent/agents/listing-curator.md +21 -2
  46. package/payload/premium-plugins/real-agent/plugins/buyers/.claude-plugin/plugin.json +1 -1
  47. package/payload/premium-plugins/real-agent/plugins/buyers/PLUGIN.md +3 -2
  48. package/payload/premium-plugins/real-agent/plugins/buyers/skills/property-recommender/SKILL.md +116 -0
  49. package/payload/premium-plugins/venture-studio/PLUGIN.md +7 -3
  50. package/payload/premium-plugins/venture-studio/bin/scaffold.sh +14 -2
  51. package/payload/premium-plugins/venture-studio/skills/investor-data-room/references/data-room-structure.md +10 -0
  52. package/payload/premium-plugins/venture-studio/skills/prototype-host/SKILL.md +179 -0
  53. package/payload/premium-plugins/venture-studio/skills/prototype-host/references/cloudflared-ingress-edit.md +81 -0
  54. package/payload/premium-plugins/venture-studio/skills/prototype-host/references/scaffold-frameworks.md +60 -0
  55. package/payload/premium-plugins/venture-studio/skills/prototype-host/references/systemd-user-service.md +104 -0
  56. package/payload/server/server.js +92 -41
@@ -0,0 +1,81 @@
1
+ # Cloudflared ingress edit — append a hostname rule before the catch-all
2
+
3
+ This is the canonical pattern for adding a new hostname → local port mapping to an existing brand cloudflared `config.yml`. Sourced from the multi-hostname ingress block documented at [`platform/plugins/cloudflare/references/manual-setup.md`](../../../../../platform/plugins/cloudflare/references/manual-setup.md) under the "Multi-ingress: adding hostnames for prototype services" section.
4
+
5
+ ## The config.yml shape
6
+
7
+ A working brand `config.yml` always looks like:
8
+
9
+ ```yaml
10
+ tunnel: <TUNNEL_ID>
11
+ credentials-file: <CFG_DIR>/<TUNNEL_ID>.json
12
+ ingress:
13
+ - hostname: admin.<BRAND_HOSTNAME>
14
+ service: http://localhost:<PORT>
15
+ - hostname: public.<BRAND_HOSTNAME>
16
+ service: http://localhost:<PORT>
17
+ - service: http_status:404
18
+ ```
19
+
20
+ A new prototype rule must land **before** the `http_status:404` catch-all; otherwise cloudflared matches the catch-all first and the prototype hostname returns 404.
21
+
22
+ ## The append pattern
23
+
24
+ `sed` cannot safely insert before the last block of a multi-line YAML rule (`- service: http_status:404` is one logical entry but the line above it might be a comment or blank). `awk` is the right primitive: scan for the literal `- service: http_status:404` line and insert two lines before it.
25
+
26
+ ```bash
27
+ SURFACE="<surface>" # e.g. landing
28
+ PORT="<port>" # from <surface-dir>/.port
29
+ HOSTNAME="${SURFACE}.${BRAND_HOSTNAME}"
30
+
31
+ # Idempotency guard — skip if the hostname already appears as an ingress rule.
32
+ if grep -qE "^[[:space:]]*-[[:space:]]+hostname:[[:space:]]+${HOSTNAME}\b" "${CFG_DIR}/config.yml"; then
33
+ echo "[prototype-host] ingress-row-exists hostname=${HOSTNAME} — skipping append"
34
+ else
35
+ awk -v host="${HOSTNAME}" -v port="${PORT}" '
36
+ /^[[:space:]]*-[[:space:]]+service:[[:space:]]+http_status:404[[:space:]]*$/ {
37
+ print " - hostname: " host
38
+ print " service: http://localhost:" port
39
+ }
40
+ { print }
41
+ ' "${CFG_DIR}/config.yml" > "${CFG_DIR}/config.yml.new"
42
+ mv "${CFG_DIR}/config.yml.new" "${CFG_DIR}/config.yml"
43
+ fi
44
+ ```
45
+
46
+ ## Validate, route, reload
47
+
48
+ ```bash
49
+ # 1. Validate the YAML and ingress semantics.
50
+ cloudflared --origincert "${CFG_DIR}/cert.pem" \
51
+ --config "${CFG_DIR}/config.yml" \
52
+ tunnel ingress validate
53
+
54
+ # 2. Create the DNS row (CNAME from <hostname> to <TUNNEL_ID>.cfargotunnel.com).
55
+ TUNNEL_ID=$(jq -r '.id // .tunnelId' "${CFG_DIR}/tunnel.state")
56
+ cloudflared --origincert "${CFG_DIR}/cert.pem" \
57
+ tunnel route dns --overwrite-dns "${TUNNEL_ID}" "${HOSTNAME}"
58
+
59
+ # 3. Reload the connector so it picks up the new ingress rule.
60
+ systemctl --user restart "${BRAND}-cloudflared.service"
61
+ ```
62
+
63
+ Each step is its own command, runs sequentially, exits non-zero on failure. The skill propagates the failing command's stderr verbatim — no parsing, no narration ([[feedback_no_stdout_parsing_for_control_flow]]).
64
+
65
+ ## What `--overwrite-dns` does
66
+
67
+ If a CNAME for `<hostname>` already exists in the account's DNS (e.g. from a prior install), `tunnel route dns` errors with `record already exists`. `--overwrite-dns` replaces the existing record with one pointing at this tunnel. Use it unconditionally — the alternative (probe-then-create) doubles the API surface and races with concurrent DNS edits.
68
+
69
+ ## Why restart, not reload
70
+
71
+ `cloudflared` watches `config.yml` only when launched with `--config-watch` (off by default in the brand service unit). `systemctl --user restart` is the deterministic way to pick up the new rule. Brief connection blip (~1s) is acceptable because the brand's admin + public hostnames already tolerate the same blip on every cloudflared upgrade.
72
+
73
+ ## Failure points
74
+
75
+ | Symptom | Root cause | Fix |
76
+ |---|---|---|
77
+ | `validate: ingress rule N has no service` | `awk` inserted only the hostname line, not the service line | Re-run; the snippet emits both lines atomically per match |
78
+ | `validate: failed to parse YAML` | A previous `awk` run left a partial line | `cat ${CFG_DIR}/config.yml` and hand-fix; the skill cannot recover from corrupt YAML |
79
+ | `route dns: 1004 invalid hostname` | `<surface>` violates DNS label rules (uppercase, underscore, leading dash) | Re-run with a valid `<surface>`; the skill validates `^[a-z0-9-]+$` before phase 4 |
80
+ | `route dns: 9103 unauthorized` | The cert.pem in `${CFG_DIR}` doesn't grant DNS-edit on the zone | Re-run Step 1 of the cloudflare runbook to refresh `cert.pem` |
81
+ | Restart succeeds but `curl -I` returns 502 | The dev server isn't actually listening on `<port>` | Phase 5 (systemd) failed earlier; the skill should have caught it. Read `journalctl --user -u ${BRAND}-prototype-<surface>.service -n 50` |
@@ -0,0 +1,60 @@
1
+ # Framework scaffolders, dev-server commands, and port overrides
2
+
3
+ Five frameworks supported. Each row gives the exact command set the prototype-host skill runs in phases 2 and 5.
4
+
5
+ ## Selection defaults
6
+
7
+ | Surface intent | Default framework | Reason |
8
+ |---|---|---|
9
+ | `landing` | `static` | One HTML file rendered from `01-narrative/LANDING.md` is faster to ship and faster to load than a Vite bundle. Lighthouse-clean by construction. |
10
+ | `prototype` | `vite` | Vue/React/Svelte all supported; HMR is the founder's iteration loop. |
11
+
12
+ If the operator names a framework explicitly, use that. Otherwise these defaults stand.
13
+
14
+ ## `static`
15
+
16
+ No scaffolder. The skill writes a single `index.html` directly into `<surface-dir>/` from `01-narrative/LANDING.md`, with brand tokens inlined into `<style>` in the `<head>`.
17
+
18
+ - **Build:** none (raw HTML); for a `dist/` build (if the operator adds Tailwind etc later) the skill respects `<surface-dir>/dist/` if present.
19
+ - **Serve:** `cloudflared file-server --port <port> <surface-dir>` (or `<surface-dir>/dist` if a build directory exists).
20
+ - **Port override:** `--port <port>` on `cloudflared file-server`.
21
+
22
+ ## `vite`
23
+
24
+ - **Scaffold:** `cd <surface-dir-parent> && npm create vite@latest <surface-name> -- --template vanilla-ts -- --yes`. The agent prompts the operator before this if they want a non-vanilla template (`react-ts`, `svelte-ts`, `vue-ts`); otherwise `vanilla-ts` is the default — the founder can rewrite `index.html` + `src/main.ts` to anything from there.
25
+ - **Install:** `cd <surface-dir> && npm install`
26
+ - **Build:** `npm run build` (outputs to `<surface-dir>/dist/`)
27
+ - **Dev server:** `npm run dev -- --port <port> --host 127.0.0.1`
28
+ - **Port override:** `--port <port>` (Vite respects the flag; `PORT=` is ignored).
29
+
30
+ ## `next`
31
+
32
+ - **Scaffold:** `cd <surface-dir-parent> && npx -y create-next-app@latest <surface-name> --typescript --app --tailwind --eslint --src-dir --import-alias "@/*" --use-npm --yes`
33
+ - **Install:** scaffolder runs `npm install` automatically.
34
+ - **Build:** `npm run build`
35
+ - **Dev server:** `npm run dev -- -p <port> -H 127.0.0.1`
36
+ - **Port override:** `-p <port>` (Next.js dev/start respect the flag; `PORT=` also works but `-p` is explicit).
37
+
38
+ ## `sveltekit`
39
+
40
+ - **Scaffold:** `cd <surface-dir-parent> && npx -y sv create <surface-name> --template minimal --types ts --no-add-ons --install npm`
41
+ - **Install:** scaffolder runs `npm install` automatically when `--install npm` is passed.
42
+ - **Build:** `npm run build`
43
+ - **Dev server:** `npm run dev -- --port <port> --host 127.0.0.1`
44
+ - **Port override:** `--port <port>`.
45
+
46
+ ## `astro`
47
+
48
+ - **Scaffold:** `cd <surface-dir-parent> && npm create astro@latest <surface-name> -- --template minimal --typescript strict --install --no-git --yes`
49
+ - **Install:** scaffolder runs `npm install` when `--install` is passed.
50
+ - **Build:** `npm run build` (outputs to `<surface-dir>/dist/`)
51
+ - **Dev server:** `npm run dev -- --port <port> --host 127.0.0.1`
52
+ - **Port override:** `--port <port>`.
53
+
54
+ ## Why `--host 127.0.0.1`
55
+
56
+ Every dev server above binds to `127.0.0.1`, not `0.0.0.0`. Cloudflared connects to the loopback address; binding to `0.0.0.0` would expose the dev server on the install device's LAN address, which is unwanted (LAN clients should reach the prototype through the tunnel, not directly).
57
+
58
+ ## Why no `--yes` on `create-next-app` interactive prompts in older versions
59
+
60
+ `create-next-app@latest` (≥14.2) treats all listed flags as the answer set and runs non-interactively. If a flag is omitted it falls back to a TTY prompt — fatal under the agent's non-TTY Bash invocation. Always pass every flag the table lists.
@@ -0,0 +1,104 @@
1
+ # Systemd user unit template for a hosted prototype surface
2
+
3
+ One unit per hosted surface: `${HOME}/.config/systemd/user/${BRAND}-prototype-<surface>.service`.
4
+
5
+ Two flavours: dev-server mode (frameworks with hot-reload) and static file-server mode (raw HTML or built `dist/`).
6
+
7
+ ## Dev-server mode (vite, next, sveltekit, astro)
8
+
9
+ ```ini
10
+ [Unit]
11
+ Description=${BRAND} prototype surface <surface> — <framework> dev server
12
+ After=network.target
13
+
14
+ [Service]
15
+ Type=simple
16
+ WorkingDirectory=<surface-dir>
17
+ Environment="PORT=<port>"
18
+ Environment="NODE_ENV=development"
19
+ ExecStart=/usr/bin/env npm run dev -- <framework-port-flag> <port> --host 127.0.0.1
20
+ Restart=on-failure
21
+ RestartSec=3
22
+ StandardOutput=journal
23
+ StandardError=journal
24
+
25
+ [Install]
26
+ WantedBy=default.target
27
+ ```
28
+
29
+ `<framework-port-flag>` is the per-framework override from [`scaffold-frameworks.md`](scaffold-frameworks.md): `--port` for Vite/SvelteKit/Astro, `-p` for Next.
30
+
31
+ ## Static file-server mode
32
+
33
+ For the `static` framework (raw HTML rendered from `LANDING.md`):
34
+
35
+ ```ini
36
+ [Unit]
37
+ Description=${BRAND} prototype surface <surface> — static file server
38
+ After=network.target
39
+
40
+ [Service]
41
+ Type=simple
42
+ WorkingDirectory=<surface-dir>
43
+ ExecStart=/usr/local/bin/cloudflared file-server --port <port> --address 127.0.0.1 <surface-dir>
44
+ Restart=on-failure
45
+ RestartSec=3
46
+ StandardOutput=journal
47
+ StandardError=journal
48
+
49
+ [Install]
50
+ WantedBy=default.target
51
+ ```
52
+
53
+ If `<surface-dir>/dist/` exists (the founder added a build step later), point `ExecStart`'s last arg at `<surface-dir>/dist` instead of `<surface-dir>`. The skill detects this automatically with `[ -d <surface-dir>/dist ] && SERVE_DIR=<surface-dir>/dist || SERVE_DIR=<surface-dir>`.
54
+
55
+ ## Why a user unit, not a root unit
56
+
57
+ The brand cloudflared connector already runs as a `systemctl --user` unit ([`platform/plugins/cloudflare/references/reset-guide.md`](../../../../../platform/plugins/cloudflare/references/reset-guide.md)). Keeping prototype units in the same scope means:
58
+
59
+ - Same `journalctl --user` namespace — the operator reads one log namespace, not two.
60
+ - No `sudo` required to install, enable, or restart.
61
+ - `loginctl enable-linger <user>` (already set by the installer) survives reboot — units come back without an SSH session.
62
+
63
+ Root units would also work, but split the operator's mental model and require `sudo` for every restart. Not worth the tradeoff for a per-prototype surface.
64
+
65
+ ## Enabling and starting
66
+
67
+ ```bash
68
+ systemctl --user daemon-reload
69
+ systemctl --user enable --now "${BRAND}-prototype-<surface>.service"
70
+
71
+ # Verify it stayed up.
72
+ systemctl --user is-active "${BRAND}-prototype-<surface>.service"
73
+
74
+ # If not active, dump the journal and surface verbatim.
75
+ if [ "$(systemctl --user is-active "${BRAND}-prototype-<surface>.service")" != "active" ]; then
76
+ journalctl --user -u "${BRAND}-prototype-<surface>.service" -n 50 --no-pager >&2
77
+ exit 1
78
+ fi
79
+ ```
80
+
81
+ ## Re-run after edit
82
+
83
+ If the skill regenerates the unit file (e.g. the port changed, or the static surface gained a `dist/` directory), the sequence is:
84
+
85
+ ```bash
86
+ systemctl --user daemon-reload
87
+ systemctl --user restart "${BRAND}-prototype-<surface>.service"
88
+ ```
89
+
90
+ `daemon-reload` is required after any unit-file edit; `restart` picks up the new `ExecStart`.
91
+
92
+ ## Why `Restart=on-failure`, not `always`
93
+
94
+ Dev servers exit 0 in legitimate cases (the operator killed `npm run dev` from another shell to apply a config edit). `on-failure` respects those exits; `always` would fight them.
95
+
96
+ ## Logging
97
+
98
+ All output (stdout + stderr from the dev server / file server) goes to the user journal. Read with:
99
+
100
+ ```bash
101
+ journalctl --user -u "${BRAND}-prototype-<surface>.service" -f
102
+ ```
103
+
104
+ No file-based log rotation needed — systemd handles journal rotation.
@@ -12134,6 +12134,55 @@ app34.get("/:rel{.*}", (c) => {
12134
12134
  });
12135
12135
  var sites_default = app34;
12136
12136
 
12137
+ // server/routes/listings.ts
12138
+ var SAFE_SLUG_RE = /^[a-z0-9](?:[a-z0-9-]{0,118}[a-z0-9])?$/;
12139
+ var CHAT_SESSION_KEY_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{7,127}$/;
12140
+ var app35 = new Hono();
12141
+ app35.get("/:slug/click", async (c) => {
12142
+ const slug = c.req.param("slug") ?? "";
12143
+ const rawSession = c.req.query("session") ?? "";
12144
+ const sessionKey = CHAT_SESSION_KEY_RE.test(rawSession) ? rawSession : "invalid";
12145
+ if (!SAFE_SLUG_RE.test(slug)) {
12146
+ console.error(`[property-card-click] reject reason=bad-slug-shape slug=${JSON.stringify(slug)} status=400`);
12147
+ return c.text("Bad Request", 400);
12148
+ }
12149
+ const account = resolveAccount();
12150
+ if (!account) {
12151
+ console.error(`[property-card-click] reject reason=no-account slug=${slug} status=404`);
12152
+ return c.text("Not Found", 404);
12153
+ }
12154
+ let pageUrl = null;
12155
+ const session = getSession();
12156
+ try {
12157
+ const result = await session.run(
12158
+ `MATCH (l:Listing {accountId: $accountId, slug: $slug})
12159
+ WHERE l.scope = 'public'
12160
+ RETURN l.pageUrl AS pageUrl
12161
+ LIMIT 1`,
12162
+ { accountId: account.accountId, slug }
12163
+ );
12164
+ const record = result.records[0];
12165
+ const raw = record?.get("pageUrl");
12166
+ pageUrl = typeof raw === "string" && raw.length > 0 ? raw : null;
12167
+ } catch (err) {
12168
+ const message = err instanceof Error ? err.message : String(err);
12169
+ console.error(`[property-card-click] reject reason=neo4j-error slug=${slug} sessionKey=${sessionKey} err=${JSON.stringify(message)} status=503`);
12170
+ await session.close().catch(() => {
12171
+ });
12172
+ return c.text("Service Unavailable", 503);
12173
+ } finally {
12174
+ await session.close().catch(() => {
12175
+ });
12176
+ }
12177
+ if (!pageUrl) {
12178
+ console.error(`[property-card-click] reject reason=listing-not-found slug=${slug} sessionKey=${sessionKey} accountId=${account.accountId} status=404`);
12179
+ return c.text("Not Found", 404);
12180
+ }
12181
+ console.log(`[property-card-click] sessionKey=${sessionKey} listingSlug=${slug} ts=${(/* @__PURE__ */ new Date()).toISOString()}`);
12182
+ return c.redirect(pageUrl, 302);
12183
+ });
12184
+ var listings_default = app35;
12185
+
12137
12186
  // app/lib/graph-health.ts
12138
12187
  var HOUR_MS = 60 * 60 * 1e3;
12139
12188
  var timer = null;
@@ -12567,9 +12616,9 @@ watchFile(ALIAS_DOMAINS_PATH, { interval: 2e3 }, () => {
12567
12616
  function isPublicHost(host) {
12568
12617
  return host.startsWith("public.") || aliasDomains.has(host);
12569
12618
  }
12570
- var app35 = new Hono();
12571
- app35.use("*", clientIpMiddleware);
12572
- app35.use("*", async (c, next) => {
12619
+ var app36 = new Hono();
12620
+ app36.use("*", clientIpMiddleware);
12621
+ app36.use("*", async (c, next) => {
12573
12622
  await next();
12574
12623
  c.header("X-Content-Type-Options", "nosniff");
12575
12624
  c.header("Referrer-Policy", "strict-origin-when-cross-origin");
@@ -12579,7 +12628,7 @@ app35.use("*", async (c, next) => {
12579
12628
  );
12580
12629
  });
12581
12630
  var HTTP_LOG_PATHS = /* @__PURE__ */ new Set(["/vnc-viewer.html", "/vnc-popout.html"]);
12582
- app35.use("*", async (c, next) => {
12631
+ app36.use("*", async (c, next) => {
12583
12632
  if (!HTTP_LOG_PATHS.has(c.req.path)) {
12584
12633
  await next();
12585
12634
  return;
@@ -12612,7 +12661,7 @@ var PUBLIC_ALLOWED_PREFIXES = [
12612
12661
  "/sites/"
12613
12662
  ];
12614
12663
  var PUBLIC_ALLOWED_EXACT = ["/favicon.ico"];
12615
- app35.use("*", async (c, next) => {
12664
+ app36.use("*", async (c, next) => {
12616
12665
  const host = (c.req.header("host") ?? "").split(":")[0];
12617
12666
  if (!isPublicHost(host)) {
12618
12667
  await next();
@@ -12652,7 +12701,7 @@ function resolveRemoteAuthOpts() {
12652
12701
  return brandLoginOpts;
12653
12702
  }
12654
12703
  var MAX_LOGIN_BODY = 8 * 1024;
12655
- app35.post("/__remote-auth/login", async (c) => {
12704
+ app36.post("/__remote-auth/login", async (c) => {
12656
12705
  const client = clientFrom(c);
12657
12706
  const clientIp = client.ip || "unknown";
12658
12707
  if (!requestIsTlsTerminated(c)) {
@@ -12697,7 +12746,7 @@ app35.post("/__remote-auth/login", async (c) => {
12697
12746
  }
12698
12747
  });
12699
12748
  });
12700
- app35.get("/__remote-auth/logout", (c) => {
12749
+ app36.get("/__remote-auth/logout", (c) => {
12701
12750
  const client = clientFrom(c);
12702
12751
  const clientIp = client.ip || "unknown";
12703
12752
  console.error(`[remote-auth] logout ip=${clientIp}`);
@@ -12710,7 +12759,7 @@ app35.get("/__remote-auth/logout", (c) => {
12710
12759
  }
12711
12760
  });
12712
12761
  });
12713
- app35.post("/__remote-auth/change-password", async (c) => {
12762
+ app36.post("/__remote-auth/change-password", async (c) => {
12714
12763
  const client = clientFrom(c);
12715
12764
  const clientIp = client.ip || "unknown";
12716
12765
  const rateLimited = checkRateLimit(client);
@@ -12761,13 +12810,13 @@ app35.post("/__remote-auth/change-password", async (c) => {
12761
12810
  return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(), mode: "change", changeError: "Failed to save password", redirect }), 200);
12762
12811
  }
12763
12812
  });
12764
- app35.get("/__remote-auth/setup", (c) => {
12813
+ app36.get("/__remote-auth/setup", (c) => {
12765
12814
  if (isRemoteAuthConfigured()) {
12766
12815
  return c.redirect("/");
12767
12816
  }
12768
12817
  return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(), mode: "setup" }), 200);
12769
12818
  });
12770
- app35.post("/__remote-auth/set-initial-password", async (c) => {
12819
+ app36.post("/__remote-auth/set-initial-password", async (c) => {
12771
12820
  if (isRemoteAuthConfigured()) {
12772
12821
  return c.redirect("/");
12773
12822
  }
@@ -12805,10 +12854,10 @@ app35.post("/__remote-auth/set-initial-password", async (c) => {
12805
12854
  return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(), mode: "setup", setupError: "Failed to save password. Please try again." }), 200);
12806
12855
  }
12807
12856
  });
12808
- app35.get("/api/remote-auth/status", (c) => {
12857
+ app36.get("/api/remote-auth/status", (c) => {
12809
12858
  return c.json({ configured: isRemoteAuthConfigured() });
12810
12859
  });
12811
- app35.post("/api/remote-auth/set-password", async (c) => {
12860
+ app36.post("/api/remote-auth/set-password", async (c) => {
12812
12861
  let body;
12813
12862
  try {
12814
12863
  body = await c.req.json();
@@ -12839,9 +12888,9 @@ app35.post("/api/remote-auth/set-password", async (c) => {
12839
12888
  return c.json({ error: "Failed to save password" }, 500);
12840
12889
  }
12841
12890
  });
12842
- app35.route("/api/_client-error", client_error_default);
12891
+ app36.route("/api/_client-error", client_error_default);
12843
12892
  console.log("[client-error-route] mounted");
12844
- app35.use("*", async (c, next) => {
12893
+ app36.use("*", async (c, next) => {
12845
12894
  const host = (c.req.header("host") ?? "").split(":")[0];
12846
12895
  const path2 = c.req.path;
12847
12896
  if (path2 === "/favicon.ico" || path2.startsWith("/assets/") || path2.startsWith("/brand/")) {
@@ -12874,12 +12923,12 @@ app35.use("*", async (c, next) => {
12874
12923
  console.error(`[remote-auth] login required ip=${clientIp} path=${path2} ${disambig}`);
12875
12924
  return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(), redirect: path2 }), 200);
12876
12925
  });
12877
- app35.route("/api/health", health_default);
12878
- app35.route("/api/chat", chat_default);
12879
- app35.route("/api/whatsapp", whatsapp_default);
12880
- app35.route("/api/onboarding", onboarding_default);
12881
- app35.route("/api/admin", admin_default);
12882
- var SAFE_SLUG_RE = /^[a-z][a-z0-9-]{2,49}$/;
12926
+ app36.route("/api/health", health_default);
12927
+ app36.route("/api/chat", chat_default);
12928
+ app36.route("/api/whatsapp", whatsapp_default);
12929
+ app36.route("/api/onboarding", onboarding_default);
12930
+ app36.route("/api/admin", admin_default);
12931
+ var SAFE_SLUG_RE2 = /^[a-z][a-z0-9-]{2,49}$/;
12883
12932
  var SAFE_FILENAME_RE = /^[a-z0-9_][a-z0-9_.-]{0,99}$/i;
12884
12933
  var IMAGE_MIME = {
12885
12934
  ".png": "image/png",
@@ -12890,10 +12939,10 @@ var IMAGE_MIME = {
12890
12939
  ".svg": "image/svg+xml",
12891
12940
  ".ico": "image/x-icon"
12892
12941
  };
12893
- app35.get("/agent-assets/:slug/:filename", (c) => {
12942
+ app36.get("/agent-assets/:slug/:filename", (c) => {
12894
12943
  const slug = c.req.param("slug");
12895
12944
  const filename = c.req.param("filename");
12896
- if (!SAFE_SLUG_RE.test(slug)) {
12945
+ if (!SAFE_SLUG_RE2.test(slug)) {
12897
12946
  console.error(`[agent-assets] invalid-slug slug=${slug}`);
12898
12947
  return c.text("Not found", 404);
12899
12948
  }
@@ -12925,7 +12974,7 @@ app35.get("/agent-assets/:slug/:filename", (c) => {
12925
12974
  "Cache-Control": "public, max-age=3600"
12926
12975
  });
12927
12976
  });
12928
- app35.get("/generated/:filename", (c) => {
12977
+ app36.get("/generated/:filename", (c) => {
12929
12978
  const filename = c.req.param("filename");
12930
12979
  if (!SAFE_FILENAME_RE.test(filename) || filename.includes("..")) {
12931
12980
  console.error(`[generated] serve file=${filename} status=403`);
@@ -12955,7 +13004,8 @@ app35.get("/generated/:filename", (c) => {
12955
13004
  "Cache-Control": "public, max-age=86400"
12956
13005
  });
12957
13006
  });
12958
- app35.route("/sites", sites_default);
13007
+ app36.route("/sites", sites_default);
13008
+ app36.route("/listings", listings_default);
12959
13009
  var htmlCache = /* @__PURE__ */ new Map();
12960
13010
  var brandLogoPath = "/brand/maxy-monochrome.png";
12961
13011
  var brandIconPath = "/brand/maxy-monochrome.png";
@@ -13092,7 +13142,7 @@ function brandedPublicHtml(agentSlug) {
13092
13142
  function escapeHtml(s) {
13093
13143
  return s.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
13094
13144
  }
13095
- app35.get("/", (c) => {
13145
+ app36.get("/", (c) => {
13096
13146
  const host = (c.req.header("host") ?? "").split(":")[0];
13097
13147
  if (isPublicHost(host)) {
13098
13148
  const defaultSlug = resolveDefaultSlug();
@@ -13100,12 +13150,12 @@ app35.get("/", (c) => {
13100
13150
  }
13101
13151
  return c.html(cachedHtml("index.html"));
13102
13152
  });
13103
- app35.get("/public", (c) => {
13153
+ app36.get("/public", (c) => {
13104
13154
  const host = (c.req.header("host") ?? "").split(":")[0];
13105
13155
  if (isPublicHost(host)) return c.text("Not found", 404);
13106
13156
  return c.html(cachedHtml("public.html"));
13107
13157
  });
13108
- app35.get("/chat", (c) => {
13158
+ app36.get("/chat", (c) => {
13109
13159
  const host = (c.req.header("host") ?? "").split(":")[0];
13110
13160
  if (isPublicHost(host)) return c.text("Not found", 404);
13111
13161
  return c.html(cachedHtml("public.html"));
@@ -13124,9 +13174,9 @@ async function logViewerFetch(c, next) {
13124
13174
  duration_ms: Date.now() - start
13125
13175
  });
13126
13176
  }
13127
- app35.use("/vnc-viewer.html", logViewerFetch);
13128
- app35.use("/vnc-popout.html", logViewerFetch);
13129
- app35.get("/vnc-popout.html", (c) => {
13177
+ app36.use("/vnc-viewer.html", logViewerFetch);
13178
+ app36.use("/vnc-popout.html", logViewerFetch);
13179
+ app36.get("/vnc-popout.html", (c) => {
13130
13180
  let html = htmlCache.get("vnc-popout.html");
13131
13181
  if (!html) {
13132
13182
  html = readFileSync19(resolve21(process.cwd(), "public", "vnc-popout.html"), "utf-8");
@@ -13139,7 +13189,7 @@ app35.get("/vnc-popout.html", (c) => {
13139
13189
  }
13140
13190
  return c.html(html);
13141
13191
  });
13142
- app35.post("/api/vnc/client-event", async (c) => {
13192
+ app36.post("/api/vnc/client-event", async (c) => {
13143
13193
  let body;
13144
13194
  try {
13145
13195
  body = await c.req.json();
@@ -13160,25 +13210,25 @@ app35.post("/api/vnc/client-event", async (c) => {
13160
13210
  });
13161
13211
  return c.json({ ok: true });
13162
13212
  });
13163
- app35.get("/g/:slug", (c) => {
13213
+ app36.get("/g/:slug", (c) => {
13164
13214
  return c.html(brandedPublicHtml());
13165
13215
  });
13166
- app35.get("/graph", (c) => {
13216
+ app36.get("/graph", (c) => {
13167
13217
  const host = (c.req.header("host") ?? "").split(":")[0];
13168
13218
  if (isPublicHost(host)) return c.text("Not found", 404);
13169
13219
  return c.html(cachedHtml("graph.html"));
13170
13220
  });
13171
- app35.get("/sessions", (c) => {
13221
+ app36.get("/sessions", (c) => {
13172
13222
  const host = (c.req.header("host") ?? "").split(":")[0];
13173
13223
  if (isPublicHost(host)) return c.text("Not found", 404);
13174
13224
  return c.html(cachedHtml("sessions.html"));
13175
13225
  });
13176
- app35.get("/data", (c) => {
13226
+ app36.get("/data", (c) => {
13177
13227
  const host = (c.req.header("host") ?? "").split(":")[0];
13178
13228
  if (isPublicHost(host)) return c.text("Not found", 404);
13179
13229
  return c.html(cachedHtml("data.html"));
13180
13230
  });
13181
- app35.get("/:slug", async (c, next) => {
13231
+ app36.get("/:slug", async (c, next) => {
13182
13232
  const slug = c.req.param("slug");
13183
13233
  if (AGENT_SLUG_PATTERN.test(`/${slug}`)) {
13184
13234
  const branding = loadBrandingCache(slug);
@@ -13188,15 +13238,15 @@ app35.get("/:slug", async (c, next) => {
13188
13238
  await next();
13189
13239
  });
13190
13240
  if (brandFaviconPath !== "/favicon.ico") {
13191
- app35.get("/favicon.ico", (c) => {
13241
+ app36.get("/favicon.ico", (c) => {
13192
13242
  c.header("Cache-Control", "public, max-age=300");
13193
13243
  return c.redirect(brandFaviconPath, 302);
13194
13244
  });
13195
13245
  }
13196
- app35.use("/*", serveStatic({ root: "./public" }));
13246
+ app36.use("/*", serveStatic({ root: "./public" }));
13197
13247
  var port = requirePortEnv("MAXY_UI_INTERNAL_PORT", { tag: "ui-server" });
13198
13248
  var hostname = process.env.HOSTNAME ?? "127.0.0.1";
13199
- var httpServer = serve({ fetch: app35.fetch, port, hostname });
13249
+ var httpServer = serve({ fetch: app36.fetch, port, hostname });
13200
13250
  console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
13201
13251
  {
13202
13252
  const loopHist = monitorEventLoopDelay({ resolution: 50 });
@@ -13220,7 +13270,8 @@ var SUBAPP_MANIFEST = [
13220
13270
  { prefix: "/api/whatsapp", file: "server/routes/whatsapp.ts", subapp: whatsapp_default },
13221
13271
  { prefix: "/api/onboarding", file: "server/routes/onboarding.ts", subapp: onboarding_default },
13222
13272
  { prefix: "/api/admin", file: "server/routes/admin/index.ts", subapp: admin_default },
13223
- { prefix: "/api/_client-error", file: "server/routes/client-error.ts", subapp: client_error_default }
13273
+ { prefix: "/api/_client-error", file: "server/routes/client-error.ts", subapp: client_error_default },
13274
+ { prefix: "/listings", file: "server/routes/listings.ts", subapp: listings_default }
13224
13275
  ];
13225
13276
  for (const m of SUBAPP_MANIFEST) {
13226
13277
  const routeCount = Array.isArray(m.subapp.routes) ? m.subapp.routes.length : 0;
@@ -13228,7 +13279,7 @@ for (const m of SUBAPP_MANIFEST) {
13228
13279
  }
13229
13280
  try {
13230
13281
  const registered = [];
13231
- for (const r of app35.routes ?? []) {
13282
+ for (const r of app36.routes ?? []) {
13232
13283
  if (typeof r.path !== "string" || r.path.includes(":") || r.path.includes("*")) continue;
13233
13284
  if (AGENT_SLUG_PATTERN.test(r.path)) {
13234
13285
  registered.push({ method: (r.method ?? "ALL").toUpperCase(), path: r.path });