@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.
- package/dist/__tests__/brew-install.test.js +10 -0
- package/dist/__tests__/brew-resolve.test.js +35 -0
- package/dist/__tests__/launchd-plist.test.js +46 -0
- package/dist/__tests__/macos-darwin-branch.test.js +85 -0
- package/dist/brew-install.js +5 -0
- package/dist/index.js +26 -6
- package/package.json +1 -1
- package/payload/platform/neo4j/schema.cypher +7 -2
- package/payload/platform/plugins/admin/skills/public-agent-manager/SKILL.md +14 -7
- package/payload/platform/plugins/cloudflare/references/manual-setup.md +105 -0
- package/payload/platform/plugins/docs/references/deployment.md +22 -4
- package/payload/platform/plugins/docs/references/internals.md +10 -26
- package/payload/platform/plugins/memory/PLUGIN.md +2 -1
- package/payload/platform/plugins/memory/mcp/dist/index.js +35 -0
- package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/image-fetch.d.ts +15 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/image-fetch.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/image-fetch.js +64 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/image-fetch.js.map +1 -0
- package/payload/platform/plugins/memory/references/schema-estate-agent.md +25 -0
- package/payload/platform/plugins/venture-studio/PLUGIN.md +7 -3
- package/payload/platform/plugins/venture-studio/bin/scaffold.sh +14 -2
- package/payload/platform/plugins/venture-studio/skills/investor-data-room/references/data-room-structure.md +10 -0
- package/payload/platform/plugins/venture-studio/skills/prototype-host/SKILL.md +179 -0
- package/payload/platform/plugins/venture-studio/skills/prototype-host/references/cloudflared-ingress-edit.md +81 -0
- package/payload/platform/plugins/venture-studio/skills/prototype-host/references/scaffold-frameworks.md +60 -0
- package/payload/platform/plugins/venture-studio/skills/prototype-host/references/systemd-user-service.md +104 -0
- package/payload/platform/plugins/whatsapp/.claude-plugin/plugin.json +9 -0
- package/payload/platform/plugins/whatsapp/PLUGIN.md +1 -1
- package/payload/platform/services/claude-session-manager/dist/brand-foreign-filter.d.ts +30 -0
- package/payload/platform/services/claude-session-manager/dist/brand-foreign-filter.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/brand-foreign-filter.js +86 -0
- package/payload/platform/services/claude-session-manager/dist/brand-foreign-filter.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/index.js +51 -9
- package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +35 -4
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/system-prompt.d.ts +7 -0
- package/payload/platform/services/claude-session-manager/dist/system-prompt.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/system-prompt.js +7 -0
- package/payload/platform/services/claude-session-manager/dist/system-prompt.js.map +1 -1
- package/payload/platform/templates/agents/public/IDENTITY.md +11 -1
- package/payload/premium-plugins/real-agent/BUNDLE.md +1 -1
- package/payload/premium-plugins/real-agent/agents/listing-curator.md +21 -2
- package/payload/premium-plugins/real-agent/plugins/buyers/.claude-plugin/plugin.json +1 -1
- package/payload/premium-plugins/real-agent/plugins/buyers/PLUGIN.md +3 -2
- package/payload/premium-plugins/real-agent/plugins/buyers/skills/property-recommender/SKILL.md +116 -0
- package/payload/premium-plugins/venture-studio/PLUGIN.md +7 -3
- package/payload/premium-plugins/venture-studio/bin/scaffold.sh +14 -2
- package/payload/premium-plugins/venture-studio/skills/investor-data-room/references/data-room-structure.md +10 -0
- package/payload/premium-plugins/venture-studio/skills/prototype-host/SKILL.md +179 -0
- package/payload/premium-plugins/venture-studio/skills/prototype-host/references/cloudflared-ingress-edit.md +81 -0
- package/payload/premium-plugins/venture-studio/skills/prototype-host/references/scaffold-frameworks.md +60 -0
- package/payload/premium-plugins/venture-studio/skills/prototype-host/references/systemd-user-service.md +104 -0
- 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.
|
package/payload/server/server.js
CHANGED
|
@@ -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
|
|
12571
|
-
|
|
12572
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
12857
|
+
app36.get("/api/remote-auth/status", (c) => {
|
|
12809
12858
|
return c.json({ configured: isRemoteAuthConfigured() });
|
|
12810
12859
|
});
|
|
12811
|
-
|
|
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
|
-
|
|
12891
|
+
app36.route("/api/_client-error", client_error_default);
|
|
12843
12892
|
console.log("[client-error-route] mounted");
|
|
12844
|
-
|
|
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
|
-
|
|
12878
|
-
|
|
12879
|
-
|
|
12880
|
-
|
|
12881
|
-
|
|
12882
|
-
var
|
|
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
|
-
|
|
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 (!
|
|
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
|
-
|
|
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
|
-
|
|
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, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
13094
13144
|
}
|
|
13095
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
13128
|
-
|
|
13129
|
-
|
|
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
|
-
|
|
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
|
-
|
|
13213
|
+
app36.get("/g/:slug", (c) => {
|
|
13164
13214
|
return c.html(brandedPublicHtml());
|
|
13165
13215
|
});
|
|
13166
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
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 });
|