@mmmbuto/nexuscrew 0.8.58 → 0.9.1
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/CHANGELOG.md +197 -2
- package/README.md +23 -4
- package/frontend/dist/assets/index-0vuhL1YP.css +32 -0
- package/frontend/dist/assets/index-Bu_-2-Uu.js +93 -0
- package/frontend/dist/index.html +2 -2
- package/frontend/dist/version.json +1 -1
- package/lib/audio/adapters.js +50 -6
- package/lib/cli/commands.js +54 -2
- package/lib/cli/doctor.js +95 -12
- package/lib/cli/init.js +25 -3
- package/lib/cli/path.js +43 -10
- package/lib/cli/pidfile.js +23 -2
- package/lib/config.js +24 -0
- package/lib/fleet/builtin.js +161 -19
- package/lib/fleet/catalogs/opencode-go.json +50 -4
- package/lib/fleet/cell-exec.js +87 -9
- package/lib/fleet/cell-lease-server.js +719 -0
- package/lib/fleet/cell-lease.js +112 -0
- package/lib/fleet/definitions.js +101 -7
- package/lib/fleet/launch-broker.js +115 -3
- package/lib/fleet/lease-client.js +191 -0
- package/lib/fleet/lease-routes.js +92 -0
- package/lib/fleet/lease-verifier.js +230 -0
- package/lib/fleet/managed.js +366 -48
- package/lib/fleet/prompt-delivery.js +50 -2
- package/lib/fleet/provider.js +1 -1
- package/lib/fleet/runtime.js +53 -6
- package/lib/live-host/bridge.js +369 -0
- package/lib/live-host/routes.js +190 -0
- package/lib/live-host/store.js +96 -0
- package/lib/mcp/tools.js +51 -0
- package/lib/nodes/commands.js +21 -3
- package/lib/nodes/store.js +57 -0
- package/lib/nodes/tunnel.js +23 -1
- package/lib/proxy/federation.js +130 -9
- package/lib/proxy/node-proxy.js +33 -0
- package/lib/proxy/panel-auth.js +337 -0
- package/lib/proxy/panel-proxy.js +336 -0
- package/lib/server.js +252 -7
- package/lib/settings/pairing-coordinator.js +32 -0
- package/lib/settings/public-peering-routes.js +13 -1
- package/package.json +1 -1
- package/skills/aidesktop/SKILL.md +201 -0
- package/skills/aidesktop/docker/Dockerfile +21 -0
- package/skills/aidesktop/docker/custom-cont-init.d/10-cdp-relay.sh +20 -0
- package/skills/aidesktop/docker/docker-compose.example.yml +75 -0
- package/skills/crew/SKILL.md +15 -0
- package/skills/live/SKILL.md +90 -0
- package/skills/mail-assistant/SKILL.md +15 -0
- package/skills/memory/SKILL.md +15 -0
- package/skills/nexuscrew/SKILL.md +113 -0
- package/skills/nexuscrew-agent/SKILL.md +18 -0
- package/skills/vl-msa/SKILL.md +15 -0
- package/frontend/dist/assets/index-BEGNtmx2.js +0 -93
- package/frontend/dist/assets/index-CYi_lhCg.css +0 -32
- package/skills/alibaba-token-media/SKILL.md +0 -133
- package/skills/alibaba-token-media/agents/openai.yaml +0 -4
- package/skills/alibaba-token-media/references/api-contract.md +0 -97
- package/skills/alibaba-token-media/scripts/alibaba_token_media.py +0 -550
- package/skills/fill-forms/SKILL.md +0 -154
- package/skills/fill-forms/agents/openai.yaml +0 -4
- package/skills/fill-forms/references/overlay-technique.md +0 -99
- package/skills/fill-forms/requirements.txt +0 -4
- package/skills/fill-forms/scripts/dump_docx.py +0 -70
- package/skills/fill-forms/scripts/fill_docx.py +0 -207
- package/skills/fill-forms/scripts/fill_pdf.py +0 -424
- package/skills/fill-forms/scripts/inspect_pdf.py +0 -188
- package/skills/fill-forms/scripts/prepare_signature.py +0 -171
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: aidesktop
|
|
3
|
+
description: Use when a user wants to give a cell a web panel via panelUrl, asks why a panel URL or panel request was rejected, wants to build the AI Desktop container recipe for an isolated browser and desktop, or needs to connect a Playwright MCP client to that desktop's Chromium through its CDP relay.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# AI Desktop
|
|
7
|
+
|
|
8
|
+
A desktop you can watch and a browser you can drive are two faces of the same
|
|
9
|
+
container: watching it is the **panel**, driving it is the **CDP relay** a
|
|
10
|
+
Playwright MCP client attaches to. This skill covers both together, plus the
|
|
11
|
+
recipe that builds the container itself.
|
|
12
|
+
|
|
13
|
+
## The panel
|
|
14
|
+
|
|
15
|
+
`panelUrl` is a **property of the cell** (or its engine, as a default for
|
|
16
|
+
every cell using it) — a sibling of `engine` and `cwd`, not a command and not
|
|
17
|
+
something the cell runs. The app's cell/engine editor has a `panelUrl` field
|
|
18
|
+
for this; do not write the URL into the command field, which makes the cell
|
|
19
|
+
try to execute it as a program and fails to start, while no panel button
|
|
20
|
+
appears either, since `panelUrl` was never actually set.
|
|
21
|
+
|
|
22
|
+
The value must be an `http:`/`https:` URL pointing at **loopback** —
|
|
23
|
+
`127.0.0.1`, `[::1]`, or `localhost`. Anything else is rejected when the
|
|
24
|
+
definition is read, and the cell keeps working, just without a panel button.
|
|
25
|
+
**A container's own address is not loopback** — publish its port to
|
|
26
|
+
`127.0.0.1` on the host that runs the node, and point `panelUrl` there. This
|
|
27
|
+
is not a nitpick: the forwarder resolves *which cell*, never *where to
|
|
28
|
+
connect*, so allowing a non-loopback destination would turn it into a way to
|
|
29
|
+
reach any address the node can reach. See
|
|
30
|
+
[The cell panel](../../docs/CELL_PANEL.md) for the full explanation and the
|
|
31
|
+
worked example.
|
|
32
|
+
|
|
33
|
+
### Origin separation (0.9.1)
|
|
34
|
+
|
|
35
|
+
The panel used to be served from the same origin as the control plane, which
|
|
36
|
+
meant a compromised or hostile page mounted in the panel iframe could, in
|
|
37
|
+
principle, read the operator's token straight out of the app's own
|
|
38
|
+
`localStorage`. As of 0.9.1, the panel is served from a **second loopback
|
|
39
|
+
port** — the browser's Same-Origin Policy treats a different port as a
|
|
40
|
+
different origin, so a document in that frame has nothing of the control
|
|
41
|
+
plane's to read or write. The app also sends
|
|
42
|
+
`Content-Security-Policy: frame-ancestors` on its own page, which is a
|
|
43
|
+
separate, orthogonal protection: it stops the app itself from being embedded
|
|
44
|
+
as someone else's iframe.
|
|
45
|
+
|
|
46
|
+
This covers **remote** cells too, not just local ones. Two paired nodes
|
|
47
|
+
negotiate a second port pair the same way they already negotiate the control
|
|
48
|
+
plane's own tunnel: when the hub side has a panel server running, it
|
|
49
|
+
announces its panel port at join time, the joining side reserves a local port
|
|
50
|
+
for it, and the supervisor forwards both destinations over the same SSH
|
|
51
|
+
connection — one `-L` for control, one for the panel, never sharing a port.
|
|
52
|
+
`GET /api/config` exposes the result as `nodePanelPorts` (node name → the
|
|
53
|
+
locally forwarded port), and the app resolves the right one per cell from its
|
|
54
|
+
route before ever opening the frame.
|
|
55
|
+
|
|
56
|
+
A node with no port pair on record — paired **before** this existed, or
|
|
57
|
+
paired since but with the local port reservation having failed at the time
|
|
58
|
+
(pairing still succeeds; the port pair is treated as an extension, never a
|
|
59
|
+
reason to fail the bond) — is not treated as an error either way: its cells
|
|
60
|
+
fall back to the old federated path (same origin as the control plane)
|
|
61
|
+
exactly as before, and a cell never borrows another node's port to paper over
|
|
62
|
+
the gap — the absence stays visible as the old behavior, not a silent, wrong
|
|
63
|
+
origin. The pair is only established at pairing time, not by reconnecting an
|
|
64
|
+
existing tunnel; re-pair the node to pick one up.
|
|
65
|
+
|
|
66
|
+
None of this changes how a request actually gets into the frame: an
|
|
67
|
+
authenticated call asks for a one-use ticket bound to one cell, the frame's
|
|
68
|
+
first request spends it and receives a viewing cookie scoped to that cell's
|
|
69
|
+
panel path, and the app's own token never reaches the frame. See
|
|
70
|
+
[How the browser gets in](../../docs/CELL_PANEL.md#how-the-browser-gets-in)
|
|
71
|
+
for the full mechanism — it is unchanged by the port move, only relocated.
|
|
72
|
+
|
|
73
|
+
### Panels on another node
|
|
74
|
+
|
|
75
|
+
Opening a panel that lives on a paired node needs that node's permission,
|
|
76
|
+
**granted there, never by the requester**:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
nexuscrew nodes panel <node> on
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Denied by default. Without it, the rejection names itself —
|
|
83
|
+
`panel-not-granted` — instead of leaving a blank rectangle to debug.
|
|
84
|
+
|
|
85
|
+
## The Docker recipe
|
|
86
|
+
|
|
87
|
+
**This repository ships the recipe, not an image.** The base,
|
|
88
|
+
`lscr.io/linuxserver/webtop`, is GPL-3.0; a `FROM` line pointing at its public
|
|
89
|
+
source is a recipe, not a distributed derivative, and the only thing this
|
|
90
|
+
project actually owns is the two added lines below and their reasons. It also
|
|
91
|
+
means no registry to host, nothing to keep patched on your behalf, and a
|
|
92
|
+
recipe you can read before you run it.
|
|
93
|
+
|
|
94
|
+
Build it yourself from [`docker/`](docker/):
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
cd <skill-dir>/docker
|
|
98
|
+
cp docker-compose.example.yml docker-compose.yml
|
|
99
|
+
# create ./.gui_password yourself — see the comments in the compose file
|
|
100
|
+
docker compose up -d --build
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
First start creates an **empty** browser profile: no logins, no history,
|
|
104
|
+
nothing carried over. The example compose ships no branding and no
|
|
105
|
+
pre-populated `/config` — you get a bare webtop desktop plus the two
|
|
106
|
+
additions below.
|
|
107
|
+
|
|
108
|
+
### The two additions, and why each exists
|
|
109
|
+
|
|
110
|
+
- **`socat`** — modern Chrome ignores `--remote-debugging-address` and binds
|
|
111
|
+
its DevTools/CDP port to `127.0.0.1` *inside* the container, so a Docker
|
|
112
|
+
port mapping alone never reaches it. `socat` relays
|
|
113
|
+
`0.0.0.0:9223 -> 127.0.0.1:9222` inside the container's own network
|
|
114
|
+
namespace; the published host port still only opens on `127.0.0.1`, so the
|
|
115
|
+
loopback-only rule is unchanged — nothing new is exposed, only bridged.
|
|
116
|
+
- **the init script** also clears `Singleton{Lock,Cookie,Socket}` files an
|
|
117
|
+
unclean shutdown leaves behind, which otherwise stop Chromium from starting
|
|
118
|
+
again with no visible error.
|
|
119
|
+
|
|
120
|
+
Notably absent: anything that tries to sandbox the browser. That is the next
|
|
121
|
+
section, and it is the part worth reading before you adapt this recipe.
|
|
122
|
+
|
|
123
|
+
### Where the boundary is
|
|
124
|
+
|
|
125
|
+
**The browser in this container runs with `--no-sandbox`, and this recipe
|
|
126
|
+
keeps it that way.** That is a trade, made deliberately, and you should
|
|
127
|
+
understand it before deciding whether it fits your situation.
|
|
128
|
+
|
|
129
|
+
The base image launches Chromium through `/usr/local/bin/wrapped-chromium`,
|
|
130
|
+
which hardcodes the flag:
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
${BIN} --password-store=basic --no-sandbox --test-type "$@"
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Installing Chromium's setuid helper does not change this, and overriding the
|
|
137
|
+
wrapper to drop the flag does not produce a sandbox either. Measured inside a
|
|
138
|
+
running container, launching the browser without it aborts:
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
The setuid sandbox is not running as root. Common causes:
|
|
142
|
+
* A parent process set prctl(PR_SET_NO_NEW_PRIVS, ...)
|
|
143
|
+
Failed to move to new namespace: PID namespaces supported,
|
|
144
|
+
Network namespace supported, but failed: errno = Operation not permitted
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Both routes are closed, and for opposite reasons. The **setuid** sandbox needs
|
|
148
|
+
its helper to elevate, which `no-new-privileges` exists to prevent. The
|
|
149
|
+
**namespace** sandbox needs unprivileged user namespaces, which the container
|
|
150
|
+
denies. Opening either one means handing the container `CAP_SYS_ADMIN` or
|
|
151
|
+
`seccomp=unconfined`.
|
|
152
|
+
|
|
153
|
+
That is the trade: **you would weaken the boundary that is actually holding in
|
|
154
|
+
order to build one inside it.** For a desktop reachable only over loopback,
|
|
155
|
+
the container is the stronger of the two — so the recipe keeps the container
|
|
156
|
+
hard and accepts the browser soft. `no-new-privileges` stays on.
|
|
157
|
+
|
|
158
|
+
**What this costs you, stated plainly:** a renderer exploit lands in the
|
|
159
|
+
container. Everything the browser can reach is in the blast radius — its
|
|
160
|
+
profile, its logged-in sessions, and anything you mount. Mount as little as
|
|
161
|
+
possible, keep the published ports on loopback, and do not treat this desktop
|
|
162
|
+
as isolation between *sites*: it is isolation between the desktop and the
|
|
163
|
+
host.
|
|
164
|
+
|
|
165
|
+
**If your situation differs** — an untrusted desktop, or a host where the
|
|
166
|
+
container boundary matters less than the browser one — the inverse trade is
|
|
167
|
+
legitimate: grant `seccomp=unconfined`, override the wrapper, and verify you
|
|
168
|
+
actually got a sandbox rather than assuming it. Check the running process, not
|
|
169
|
+
the launch log:
|
|
170
|
+
|
|
171
|
+
- open `chrome://sandbox` inside the desktop and read what it reports, or
|
|
172
|
+
- confirm the zygote no longer carries `--no-sandbox` (`ps -eo args | grep zygote`).
|
|
173
|
+
|
|
174
|
+
Until one of those confirms it, assume the browser is unsandboxed — which,
|
|
175
|
+
with this recipe as shipped, it is.
|
|
176
|
+
### Commanding the browser
|
|
177
|
+
|
|
178
|
+
The relay's published port, `127.0.0.1:9222` on the host, is the CDP
|
|
179
|
+
endpoint a Playwright MCP client attaches to — for example:
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
playwright-mcp --cdp-endpoint http://127.0.0.1:9222
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Register that command as its own MCP server in the AI client's configuration;
|
|
186
|
+
NexusCrew does not bundle browser automation itself, it only gives the
|
|
187
|
+
container a loopback CDP port to attach one to. It is the same Chromium the
|
|
188
|
+
panel shows over the desktop, so it carries the same logins — treat driving
|
|
189
|
+
it with the same care as watching it.
|
|
190
|
+
|
|
191
|
+
## Dependencies
|
|
192
|
+
|
|
193
|
+
**Bundled:** the `docker/` recipe (`Dockerfile`, `docker-compose.example.yml`,
|
|
194
|
+
`custom-cont-init.d/10-cdp-relay.sh`) ships with this skill.
|
|
195
|
+
|
|
196
|
+
**External (you must provide):**
|
|
197
|
+
|
|
198
|
+
| Need | Install | Probe / failure mode |
|
|
199
|
+
|---|---|---|
|
|
200
|
+
| Docker with Compose v2 | your platform's Docker install | `docker compose version` fails → nothing in `docker/` builds or runs |
|
|
201
|
+
| A Playwright-capable MCP client, to drive the browser | install separately, point it at the CDP port | if no such client is registered, no `browser_*`-style tools exist in that session — the desktop and its panel still work without it |
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# AI Desktop = a webtop base plus the one thing the base is missing: a way to
|
|
2
|
+
# reach Chrome DevTools Protocol from outside the container.
|
|
3
|
+
#
|
|
4
|
+
# WHAT THIS IMAGE DELIBERATELY DOES NOT DO
|
|
5
|
+
# It does not try to give the browser its own sandbox. That is a decision, not
|
|
6
|
+
# an omission, and it was made after measuring — see ../SKILL.md, "Where the
|
|
7
|
+
# boundary is". In short: inside this container the browser's own sandbox
|
|
8
|
+
# cannot start (setuid helper blocked by no-new-privileges, unprivileged user
|
|
9
|
+
# namespaces denied), and the changes that would let it start are exactly the
|
|
10
|
+
# ones that weaken the container — the boundary that actually holds here.
|
|
11
|
+
FROM lscr.io/linuxserver/webtop:debian-xfce
|
|
12
|
+
|
|
13
|
+
RUN apt-get update \
|
|
14
|
+
&& apt-get install -y --no-install-recommends socat \
|
|
15
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
16
|
+
|
|
17
|
+
# socat: modern Chrome IGNORES --remote-debugging-address and binds CDP to
|
|
18
|
+
# 127.0.0.1 inside the container, so a Docker port mapping alone never
|
|
19
|
+
# reaches it. The relay 0.0.0.0:9223 -> 127.0.0.1:9222 is the only bridge,
|
|
20
|
+
# and it still only leaves the container through a port you publish to the
|
|
21
|
+
# host's own loopback (see docker-compose.example.yml).
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Runs at every container start, as root, BEFORE the desktop session starts.
|
|
3
|
+
#
|
|
4
|
+
# 1) Orphaned profile locks: if the container dies while Chromium is still
|
|
5
|
+
# running, Singleton{Lock,Cookie,Socket} files are left behind pointing at
|
|
6
|
+
# a dead pid, and Chromium then REFUSES to start on the next boot.
|
|
7
|
+
# Observed symptom: autostart present, zero processes, no visible error.
|
|
8
|
+
# It is safe to remove them here: the desktop session has not started yet,
|
|
9
|
+
# so no browser can possibly be alive.
|
|
10
|
+
rm -f /config/browser-profile/Singleton* 2>/dev/null || true
|
|
11
|
+
|
|
12
|
+
# 2) CDP relay: modern Chrome ignores --remote-debugging-address and binds
|
|
13
|
+
# DevTools to 127.0.0.1 inside the container, unreachable through a Docker
|
|
14
|
+
# port mapping. socat bridges it. What leaves the container through this
|
|
15
|
+
# port still only reaches the host's own loopback — see the port mapping
|
|
16
|
+
# in docker-compose.example.yml.
|
|
17
|
+
pkill -f "TCP-LISTEN:9223" 2>/dev/null || true
|
|
18
|
+
nohup socat TCP-LISTEN:9223,fork,reuseaddr TCP:127.0.0.1:9222 >/dev/null 2>&1 &
|
|
19
|
+
|
|
20
|
+
echo "[cdp-relay] stale profile locks cleared; socat 9223 -> 9222 started"
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Example compose for the AI Desktop recipe. Copy this next to the Dockerfile
|
|
2
|
+
# and custom-cont-init.d/, adjust the marked values, then:
|
|
3
|
+
#
|
|
4
|
+
# docker compose up -d --build
|
|
5
|
+
#
|
|
6
|
+
# First start creates an EMPTY browser profile under ./config — no logins,
|
|
7
|
+
# no history, nothing carried over from anywhere else. Expect a self-signed
|
|
8
|
+
# certificate warning on the HTTPS port on first connect: normal for a
|
|
9
|
+
# loopback-only service, and safe to trust locally.
|
|
10
|
+
services:
|
|
11
|
+
ai-desktop:
|
|
12
|
+
build: .
|
|
13
|
+
image: ai-desktop:local
|
|
14
|
+
container_name: ai-desktop
|
|
15
|
+
restart: unless-stopped
|
|
16
|
+
|
|
17
|
+
environment:
|
|
18
|
+
- PUID=1000
|
|
19
|
+
- PGID=1000
|
|
20
|
+
- TZ=Etc/UTC # set your own timezone
|
|
21
|
+
- TITLE=AI Desktop
|
|
22
|
+
# GUI login: without a password, anyone who reaches the port gets a
|
|
23
|
+
# terminal with passwordless sudo INSIDE the container. Create your own
|
|
24
|
+
# ./.gui_password file (mode 600, one line, no trailing newline needed)
|
|
25
|
+
# before the first start — do not put the password in this file.
|
|
26
|
+
- CUSTOM_USER=admin # set your own login name
|
|
27
|
+
- FILE__PASSWORD=/run/gui_password
|
|
28
|
+
|
|
29
|
+
volumes:
|
|
30
|
+
- ./config:/config
|
|
31
|
+
- ./.gui_password:/run/gui_password:ro
|
|
32
|
+
- ./custom-cont-init.d:/custom-cont-init.d:ro
|
|
33
|
+
|
|
34
|
+
# Loopback only, like everything else in NexusCrew: reach this from
|
|
35
|
+
# elsewhere through an SSH tunnel or VPN you control, never by publishing
|
|
36
|
+
# these ports beyond 127.0.0.1.
|
|
37
|
+
# 6900 = http, 6901 = https. The https port is the one worth using day to
|
|
38
|
+
# day — full desktop features (clipboard, audio/video codecs) need it.
|
|
39
|
+
ports:
|
|
40
|
+
- "127.0.0.1:6900:3000"
|
|
41
|
+
- "127.0.0.1:6901:3001"
|
|
42
|
+
# CDP of the Chromium running on the desktop: the channel a Playwright
|
|
43
|
+
# MCP client attaches to (see SKILL.md). Same browser you see and use
|
|
44
|
+
# over the desktop, so same logins.
|
|
45
|
+
- "127.0.0.1:9222:9223"
|
|
46
|
+
|
|
47
|
+
# Chromium crashes on tab open without adequate /dev/shm.
|
|
48
|
+
shm_size: "1gb"
|
|
49
|
+
mem_limit: 4g # tune to your machine
|
|
50
|
+
mem_reservation: 1g
|
|
51
|
+
cpus: 2.0
|
|
52
|
+
|
|
53
|
+
# KEEP THIS. The container is the boundary that holds here, and this line
|
|
54
|
+
# is part of it. The browser inside runs with --no-sandbox — that is not
|
|
55
|
+
# an accident, it is the trade this image makes: giving Chromium its own
|
|
56
|
+
# sandbox would require handing the container CAP_SYS_ADMIN or
|
|
57
|
+
# seccomp=unconfined, weakening the one boundary that is actually doing
|
|
58
|
+
# the work in order to add one inside it.
|
|
59
|
+
#
|
|
60
|
+
# Measured, not assumed: with this line set, launching the browser without
|
|
61
|
+
# --no-sandbox aborts — the setuid helper cannot elevate (PR_SET_NO_NEW_PRIVS)
|
|
62
|
+
# and the namespace sandbox is denied ("failed to move to new namespace:
|
|
63
|
+
# Operation not permitted"). See ../SKILL.md, "Where the boundary is".
|
|
64
|
+
#
|
|
65
|
+
# The consequence is real and you should plan for it: a renderer exploit
|
|
66
|
+
# lands in this container. Treat what the browser can reach — its profile,
|
|
67
|
+
# its sessions, anything you mount — as being inside the blast radius.
|
|
68
|
+
security_opt:
|
|
69
|
+
- no-new-privileges:true
|
|
70
|
+
|
|
71
|
+
logging:
|
|
72
|
+
driver: json-file
|
|
73
|
+
options:
|
|
74
|
+
max-size: "10m"
|
|
75
|
+
max-file: "3"
|
package/skills/crew/SKILL.md
CHANGED
|
@@ -87,3 +87,18 @@ report.
|
|
|
87
87
|
|
|
88
88
|
Transport receipts do not prove task acceptance or completion. Report only
|
|
89
89
|
results that have been verified.
|
|
90
|
+
|
|
91
|
+
## Dependencies
|
|
92
|
+
|
|
93
|
+
**Bundled:** nothing — this skill is documentation only.
|
|
94
|
+
|
|
95
|
+
**External (you must provide):**
|
|
96
|
+
|
|
97
|
+
| Need | Install | Probe / failure mode |
|
|
98
|
+
|---|---|---|
|
|
99
|
+
| A Crew MCP fabric (companion) | optional companion `mcp-crewd-rs`: <https://github.com/DioNanos/mcp-crewd-rs> — the repository's Install section is authoritative and may change; this skill deliberately does not duplicate its commands (**not verified here**) | if the companion is not registered, the client exposes no `cell_*` tools; ask the client for its tool list |
|
|
100
|
+
| Client MCP registration | register the companion's stdio command in the AI client's MCP config | same failure mode: absent tools, not a runtime error |
|
|
101
|
+
| A `crew` token issued by the fabric | `crew token issue` on the host that runs the fabric (see the companion's docs) | spawns are rejected as unauthorized — the rejection names the missing authorization |
|
|
102
|
+
|
|
103
|
+
Without the companion there is no worker delegation in that session: say so
|
|
104
|
+
and suggest the companion once instead of emulating cells with raw tmux.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: live
|
|
3
|
+
description: Use when a user wants to know or change which cell is the Live host on a node, asks why a Live host designation was rejected, or needs to grant or revoke another node's permission to designate a Live host cell on this one (liveHostAccess, via nexuscrew nodes live-host).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Live host cell
|
|
7
|
+
|
|
8
|
+
Live is the phone-facing quick-open flow: a single **host cell**, designated
|
|
9
|
+
per node, is what a Live session lands on. See
|
|
10
|
+
[Fleet and terminals](../../docs/FLEET.md) for what Live is; this skill covers
|
|
11
|
+
designating it, clearing it, and permitting it across nodes.
|
|
12
|
+
|
|
13
|
+
## One host cell per node, guarded by a revision
|
|
14
|
+
|
|
15
|
+
Each node keeps exactly one `hostCell` (or none). Changing it is a
|
|
16
|
+
compare-and-swap on a `revision` number, never an unconditional write:
|
|
17
|
+
|
|
18
|
+
1. `GET .../live-host` returns `{ hostCell, revision, eligible, host: { lease } }`.
|
|
19
|
+
2. `POST .../live-host/designate { cellId, expectedRevision }` — `expectedRevision`
|
|
20
|
+
must be the value just read. A stale revision (someone else changed the
|
|
21
|
+
designation meanwhile) is rejected with 409, not silently overwritten.
|
|
22
|
+
3. `POST .../live-host/clear { expectedRevision }` — same rule, removes the
|
|
23
|
+
designation.
|
|
24
|
+
|
|
25
|
+
Always re-read the revision immediately before writing; never reuse one from
|
|
26
|
+
an earlier render. This is what keeps two people (or two tabs) racing to star
|
|
27
|
+
a cell from leaving the store in a mixed state — one write wins, the other
|
|
28
|
+
gets a 409 and re-reads.
|
|
29
|
+
|
|
30
|
+
The designation survives the cell going inactive; it is never dropped just
|
|
31
|
+
because tmux is not attached right now. `eligible` is computed fresh on every
|
|
32
|
+
read from the roster and the designated cell's lease state
|
|
33
|
+
(`live`/`grace`/`expired`/`none`/`unavailable`) — a designated-but-not-eligible
|
|
34
|
+
host is a distinct, readable state, not an error to explain away.
|
|
35
|
+
|
|
36
|
+
## Command the node that owns the cell, not the one serving the page
|
|
37
|
+
|
|
38
|
+
This is the point of the feature: **the request must reach the node whose
|
|
39
|
+
roster contains the cell**, not whichever node happens to be rendering the
|
|
40
|
+
current page. Route it exactly like a federated deck — an empty route for a
|
|
41
|
+
local cell, that node's route array for a remote one:
|
|
42
|
+
|
|
43
|
+
```js
|
|
44
|
+
getLiveHost(token, route) // route: [] or [...hops]
|
|
45
|
+
designateHostCell(token, cellId, expectedRevision, route)
|
|
46
|
+
clearHostCell(token, expectedRevision, route)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Before 0.9.1 these calls took no `route`, so starring a cell shown from a
|
|
50
|
+
remote node either did nothing or silently changed the wrong node's
|
|
51
|
+
designation. That mismatch — not a missing capability — was the defect: a
|
|
52
|
+
parameter nobody passed does not exist.
|
|
53
|
+
|
|
54
|
+
## Federated permission: liveHostAccess
|
|
55
|
+
|
|
56
|
+
A peer may designate or read another node's Live host only if that node has
|
|
57
|
+
granted it. The permission is per peer, **denied by default**, and granted by
|
|
58
|
+
the node that **owns** the cell — never by the one asking:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
nexuscrew nodes live-host <node> on
|
|
62
|
+
nexuscrew nodes live-host <node> off
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Without it, the request gets a **named** rejection, not a timeout or a star
|
|
66
|
+
that quietly does nothing: HTTP 403 with `reason: "live-host-not-granted"`.
|
|
67
|
+
Surface this distinctly from a generic failure — the fix is a grant on the
|
|
68
|
+
owning node, not a retry from the requester.
|
|
69
|
+
|
|
70
|
+
## Common mistakes
|
|
71
|
+
|
|
72
|
+
- **Reusing a stale `revision`.** `GET` immediately before every
|
|
73
|
+
`designate`/`clear`; a revision read earlier in the session is stale by
|
|
74
|
+
definition once anything else has changed the designation.
|
|
75
|
+
- **Designating a cell that belongs to a different node than the route
|
|
76
|
+
targets.** The route selects which node's roster is searched; a `cellId`
|
|
77
|
+
absent from that node's local roster is rejected with 404, never forwarded
|
|
78
|
+
further to guess where it might live.
|
|
79
|
+
- **Reading a `live-host-not-granted` rejection as a bug.** It means exactly
|
|
80
|
+
what it says: run `nexuscrew nodes live-host <node> on` on the node that
|
|
81
|
+
owns the cell, not on the node making the request.
|
|
82
|
+
- **Assuming Live host state is global.** It is one value **per node**; a node
|
|
83
|
+
with nothing designated still answers `GET` with `hostCell: null` — a valid
|
|
84
|
+
state, distinct from "not permitted to ask".
|
|
85
|
+
|
|
86
|
+
## Dependencies
|
|
87
|
+
|
|
88
|
+
**Bundled:** this is a NexusCrew core feature. No external MCP companion or
|
|
89
|
+
separate service is required — only a running NexusCrew node on each side of
|
|
90
|
+
the designation.
|
|
@@ -69,3 +69,18 @@ client-native scheduler or loop mechanism instead of manual polling, avoid
|
|
|
69
69
|
duplicate jobs, respect quiet hours, and remain silent on unchanged ticks when
|
|
70
70
|
the host workflow supports silent monitoring. A recurring authorization does
|
|
71
71
|
not automatically authorize sending or permanent deletion.
|
|
72
|
+
|
|
73
|
+
## Dependencies
|
|
74
|
+
|
|
75
|
+
**Bundled:** nothing — this skill is documentation only.
|
|
76
|
+
|
|
77
|
+
**External (you must provide):**
|
|
78
|
+
|
|
79
|
+
| Need | Install | Probe / failure mode |
|
|
80
|
+
|---|---|---|
|
|
81
|
+
| A mail MCP connector already exposed by the current client (any Gmail/IMAP-capable MCP server; e.g. the optional companion `mcp-email-rs` <https://github.com/DioNanos/mcp-email-rs> — its Install section is authoritative, **not verified here**) | register the connector in the AI client's MCP config, following that connector's own instructions | if no mail tools are exposed, mailbox discovery returns nothing to call — ask the client for its tool list; do not assume provider folders or account layout |
|
|
82
|
+
| Mailbox credentials | provided by the connector's own auth flow | auth failures surface from the connector, not from this skill |
|
|
83
|
+
|
|
84
|
+
This skill never sends or deletes mail on its own initiative; a missing
|
|
85
|
+
connector means the task cannot start, which must be reported, not worked
|
|
86
|
+
around with direct IMAP scripting.
|
package/skills/memory/SKILL.md
CHANGED
|
@@ -79,3 +79,18 @@ document store, not in a bounded log.
|
|
|
79
79
|
If no Memory MCP tool is available and this skill is packaged with NexusCrew,
|
|
80
80
|
the optional companion is documented in `../../MCP_COMPANIONS.md`. Explain the
|
|
81
81
|
missing capability and ask before installing or configuring anything.
|
|
82
|
+
|
|
83
|
+
## Dependencies
|
|
84
|
+
|
|
85
|
+
**Bundled:** nothing — this skill is documentation only.
|
|
86
|
+
|
|
87
|
+
**External (you must provide):**
|
|
88
|
+
|
|
89
|
+
| Need | Install | Probe / failure mode |
|
|
90
|
+
|---|---|---|
|
|
91
|
+
| A Memory MCP server (companion) | optional companion `mcp-memory-rs`: <https://github.com/DioNanos/mcp-memory-rs> — the repository's Install section is authoritative and may change; this skill deliberately does not duplicate its commands (**not verified here**) | if the companion is not registered, the client exposes no `memory_*` tools; ask the client for its tool list rather than guessing |
|
|
92
|
+
| Client MCP registration | register the companion's stdio command in the AI client's MCP config | same failure mode as above: absent tools, not a runtime error |
|
|
93
|
+
|
|
94
|
+
Without the companion this skill is not usable in that session: say so and
|
|
95
|
+
suggest the companion once (see the packaged `mcp-companions.json` catalog)
|
|
96
|
+
instead of approximating with file reads.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nexuscrew
|
|
3
|
+
description: Read this first when working with NexusCrew for any reason — understanding what it is, what an AI agent can do through it, how cells, nodes, decks, engines, panels and Live fit together, which companion skills cover which capability, and where the trust boundaries are. This is the entry point: every other NexusCrew skill assumes what is written here.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# NexusCrew — what it is and what you can do through it
|
|
7
|
+
|
|
8
|
+
Read this before the other NexusCrew skills. They describe individual
|
|
9
|
+
capabilities; this one describes the machine they belong to, and the rules that
|
|
10
|
+
hold across all of them.
|
|
11
|
+
|
|
12
|
+
If the running build and this document disagree, **the build wins**. Check with
|
|
13
|
+
`nexuscrew status`, `nexuscrew doctor`, and the MCP tool list actually exposed
|
|
14
|
+
in your session.
|
|
15
|
+
|
|
16
|
+
## The shape of the system
|
|
17
|
+
|
|
18
|
+
NexusCrew turns live tmux sessions, AI CLI workers and connected machines into
|
|
19
|
+
one local-first control plane, reachable from a browser.
|
|
20
|
+
|
|
21
|
+
Five nouns carry almost everything:
|
|
22
|
+
|
|
23
|
+
- **Node** — one installation on one machine. Its identity is an opaque
|
|
24
|
+
`instanceId`; the human-readable name is a **label**, and two nodes may
|
|
25
|
+
legitimately carry the same one. **Address things by id, never by name.**
|
|
26
|
+
- **Cell** — one stable working identity (`Dev`, `Research`, …) bound to one
|
|
27
|
+
tmux session and one engine. A cell is not a process: it survives restarts of
|
|
28
|
+
the service, and stopping it does not end the work it was doing.
|
|
29
|
+
- **Engine** — what a cell runs: an AI CLI, a plain shell, a command in a
|
|
30
|
+
container. Some are *managed* (the service knows how to describe and
|
|
31
|
+
configure them), some are not.
|
|
32
|
+
- **Deck** — a relationship between cells that work together. Not a duplicate
|
|
33
|
+
cell, not a group chat: an arrangement.
|
|
34
|
+
- **Panel** — an optional web interface a cell can carry, served next to its
|
|
35
|
+
terminal. A remote desktop, a notebook, a dashboard.
|
|
36
|
+
|
|
37
|
+
Two machines that have paired are **peers**. Federation is the normal case, not
|
|
38
|
+
the exception: assume the human is driving from one node toward another.
|
|
39
|
+
|
|
40
|
+
## What an AI agent can actually do
|
|
41
|
+
|
|
42
|
+
Through the MCP bridge, when the tools are exposed in your session:
|
|
43
|
+
|
|
44
|
+
| You want to | Use | Covered by |
|
|
45
|
+
|---|---|---|
|
|
46
|
+
| tell the human something, or ask | `nc_notify`, `nc_ask` | `nexuscrew-agent` |
|
|
47
|
+
| read runtime state, identity, decks | `nc_status`, `nc_identity`, `nc_deck` | `nexuscrew-agent` |
|
|
48
|
+
| find and message another cell | `nc_cells` then `nc_send_cell` | `nexuscrew-agent` |
|
|
49
|
+
| speak on a node or audio group | `nc_speak`, `nc_speak_group` | `nexuscrew-agent` |
|
|
50
|
+
| hand a file to the human | `nc_send_file`, `nc_inbox` | `nexuscrew-agent` |
|
|
51
|
+
| find out why a cell will not start | `nc_cell_diagnostics` | `nexuscrew-agent` |
|
|
52
|
+
| keep state across sessions | Memory MCP | `memory` |
|
|
53
|
+
| index and retrieve documents | MSA MCP | `vl-msa` |
|
|
54
|
+
| delegate bounded work to workers | Crew MCP | `crew` |
|
|
55
|
+
| read, search and triage mail | Mail MCP | `mail-assistant` |
|
|
56
|
+
|
|
57
|
+
The last four are **optional companions**, listed in
|
|
58
|
+
[`mcp-companions.json`](../../mcp-companions.json). They are separate servers:
|
|
59
|
+
absent unless installed, and never installed silently.
|
|
60
|
+
|
|
61
|
+
## Four rules that will save you a wasted hour
|
|
62
|
+
|
|
63
|
+
**A receipt is not an outcome.** `submitted` from `nc_send_cell` means the text
|
|
64
|
+
was pasted and Enter was pressed. It does not mean the message was understood,
|
|
65
|
+
accepted, or acted on. If completion matters, ask for an explicit answer.
|
|
66
|
+
|
|
67
|
+
**There is no offline queue.** A cell that is not active cannot receive. A
|
|
68
|
+
`canReceive: false` peer is not "queued", it is unreachable.
|
|
69
|
+
|
|
70
|
+
**Discovery is not authorization.** Seeing a cell or node in a listing does not
|
|
71
|
+
mean you may act on it. Panels in particular are denied by default and granted
|
|
72
|
+
per peer, by the node that owns the cell.
|
|
73
|
+
|
|
74
|
+
**A cell that looks idle may not be.** A TUI queues incoming messages while it
|
|
75
|
+
works, which is healthy. Do not conclude a cell is stuck from a quiet pane.
|
|
76
|
+
|
|
77
|
+
## Trust boundaries
|
|
78
|
+
|
|
79
|
+
- Everything binds to **loopback** by default. Reaching a node from elsewhere
|
|
80
|
+
means a tunnel or a pairing, deliberately.
|
|
81
|
+
- A **panel** must point at loopback on the machine that runs the node. This is
|
|
82
|
+
not a port-forward: the caller picks *which cell*, never *where to connect*.
|
|
83
|
+
Without that rule, opening "a panel" would be a way to reach anything the
|
|
84
|
+
node can reach.
|
|
85
|
+
- Never put tokens, keys, cookies or pairing material into MCP payloads,
|
|
86
|
+
messages to cells, or documents. Do not read credential files to work around
|
|
87
|
+
a missing tool.
|
|
88
|
+
- Mutations are honoured or refused, never faked: `NEXUSCREW_READONLY=1` and
|
|
89
|
+
routed-peer inspect-only limits are real, and a refusal is information.
|
|
90
|
+
|
|
91
|
+
## Where to look next
|
|
92
|
+
|
|
93
|
+
| Topic | Document |
|
|
94
|
+
|---|---|
|
|
95
|
+
| Cells, engines, decks | [Fleet](../../docs/FLEET.md) |
|
|
96
|
+
| Peers, pairing, visibility | [Nodes](../../docs/NODES.md) |
|
|
97
|
+
| The MCP bridge and client setup | [MCP](../../docs/MCP.md) |
|
|
98
|
+
| A cell's web panel | [Cell panel](../../docs/CELL_PANEL.md) |
|
|
99
|
+
| Files, environment, settings | [Configuration](../../docs/CONFIGURATION.md) |
|
|
100
|
+
| CLI, boot, backup, diagnostics | [Operations](../../docs/OPERATIONS.md) |
|
|
101
|
+
| Trust boundaries in depth | [Security](../../docs/SECURITY.md) |
|
|
102
|
+
|
|
103
|
+
## When something does not work
|
|
104
|
+
|
|
105
|
+
In this order, because each step rules out the one before:
|
|
106
|
+
|
|
107
|
+
1. `nexuscrew status` — is the service running, on which port, in which roles?
|
|
108
|
+
2. `nexuscrew doctor` — local diagnostics.
|
|
109
|
+
3. Is the tool you need **actually exposed** in this session? Do not emulate a
|
|
110
|
+
missing tool by reading state files; say it is missing and degrade openly.
|
|
111
|
+
4. For a cell that will not start: `nc_cell_diagnostics` before anything else —
|
|
112
|
+
it returns the redacted command and the last startup failure.
|
|
113
|
+
5. Only then look at files, and say that you are doing so and why.
|
|
@@ -169,3 +169,21 @@ work. The setting is also applied to windows created later in that session.
|
|
|
169
169
|
- **Assuming a node listens on the port you know** → NexusCrew selects a free port per installation, and a peer's remote port is not the port that node listens on locally. Read it from `nexuscrew status` on that node; a health check aimed at the wrong port reports a dead service that is perfectly alive.
|
|
170
170
|
- **Writing a reply into a local inbox directory** → the inbox is per-installation and is not synchronised between nodes. Answering a remote caller by dropping a file in your own inbox reaches nobody; reply through the tool that addressed you.
|
|
171
171
|
- **Treating a dead scroll gesture as a web-terminal bug** → the pane is in the alternate buffer. Check whether it predates the NexusCrew setting or opted out with `alternateScreen:true`; never send raw page keys to a TUI to work around it.
|
|
172
|
+
|
|
173
|
+
## Dependencies
|
|
174
|
+
|
|
175
|
+
**Bundled (installed with the package):** the `nexuscrew` CLI, `lib/`, these
|
|
176
|
+
skills, and the `bin/nc-send` / `bin/nc-deliver` helpers arrive with
|
|
177
|
+
`npm install -g @mmmbuto/nexuscrew` (Node.js >= 18 required by `engines`).
|
|
178
|
+
|
|
179
|
+
**External (you must provide):**
|
|
180
|
+
|
|
181
|
+
| Need | Install | Probe / failure mode |
|
|
182
|
+
|---|---|---|
|
|
183
|
+
| Node.js >= 18 | Debian/Ubuntu `apt install nodejs` (nodesource for 18+), Fedora `dnf install nodejs`, macOS `brew install node`, Termux `pkg install nodejs-lts` | `node -v` prints >= 18; below that `npm install` refuses per `engines` |
|
|
184
|
+
| tmux | Debian/Ubuntu `apt install tmux`, Fedora `dnf install tmux`, macOS `brew install tmux`, Termux `pkg install tmux` | `nexuscrew doctor` reports tmux missing by name; `nc-send` exits 127 with `nc-send: tmux not found on PATH (set TMUX_BIN)` — the failure names itself |
|
|
185
|
+
| An AI client that can register the MCP server | register the stdio command `nexuscrew mcp` in the client's MCP config | if the `nc_*` tools are not exposed, no `nc_` tool exists in the session — see "MCP bridge" above |
|
|
186
|
+
| A running NexusCrew service (for most tools) | `nexuscrew serve` (foreground) or your platform service manager | tools fail to reach the bridge; Termux has no systemd — run `nexuscrew serve` inside a tmux session or your own keep-alive |
|
|
187
|
+
|
|
188
|
+
If tmux is missing, MCP inspection (`nc_status`, `nc_identity`) still works;
|
|
189
|
+
anything that targets a session (including the `nc-send` fallback) does not.
|
package/skills/vl-msa/SKILL.md
CHANGED
|
@@ -66,3 +66,18 @@ round. Avoid a single unbounded search.
|
|
|
66
66
|
If no VL-MSA tool is available and this skill is packaged with NexusCrew, the
|
|
67
67
|
optional companion is documented in `../../MCP_COMPANIONS.md`. Explain the
|
|
68
68
|
missing capability and ask before installing or configuring anything.
|
|
69
|
+
|
|
70
|
+
## Dependencies
|
|
71
|
+
|
|
72
|
+
**Bundled:** nothing — this skill is documentation only.
|
|
73
|
+
|
|
74
|
+
**External (you must provide):**
|
|
75
|
+
|
|
76
|
+
| Need | Install | Probe / failure mode |
|
|
77
|
+
|---|---|---|
|
|
78
|
+
| A VL-MSA MCP server (companion) | optional companion `mcp-vl-msa-rs`: <https://github.com/DioNanos/mcp-vl-msa-rs> — the repository's Install section is authoritative and may change; this skill deliberately does not duplicate its commands (**not verified here**) | if the companion is not registered, the client exposes no `msa_*` tools; ask the client for its tool list |
|
|
79
|
+
| Client MCP registration | register the companion's stdio command in the AI client's MCP config | same failure mode: absent tools, not a runtime error |
|
|
80
|
+
|
|
81
|
+
Without the companion, durable retrieval is simply unavailable in that
|
|
82
|
+
session: say so and suggest the companion once instead of reading the
|
|
83
|
+
server's on-disk collections directly (which this skill forbids).
|