@mmmbuto/nexuscrew 0.8.58 → 0.9.0
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 +137 -2
- package/README.md +1 -0
- package/frontend/dist/assets/index-0vuhL1YP.css +32 -0
- package/frontend/dist/assets/index-zjL6kZ7J.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 +40 -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 +15 -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 +184 -0
- package/lib/live-host/store.js +96 -0
- package/lib/mcp/tools.js +51 -0
- package/lib/nodes/commands.js +9 -2
- package/lib/nodes/store.js +14 -0
- package/lib/nodes/tunnel.js +4 -1
- package/lib/proxy/federation.js +106 -9
- package/lib/proxy/node-proxy.js +33 -0
- package/lib/proxy/panel-auth.js +307 -0
- package/lib/proxy/panel-proxy.js +305 -0
- package/lib/server.js +127 -4
- package/package.json +1 -1
- package/skills/alibaba-token-media/SKILL.md +19 -0
- package/skills/crew/SKILL.md +15 -0
- package/skills/fill-forms/SKILL.md +23 -0
- package/skills/mail-assistant/SKILL.md +15 -0
- package/skills/memory/SKILL.md +15 -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/lib/server.js
CHANGED
|
@@ -23,7 +23,11 @@ const VERSION = require('../package.json').version;
|
|
|
23
23
|
const { transcribe } = require('./voice/transcribe.js');
|
|
24
24
|
const { selectProvider } = require('./fleet/provider.js');
|
|
25
25
|
const { fleetRoutes } = require('./fleet/routes.js');
|
|
26
|
+
const { leaseRoutes } = require('./fleet/lease-routes.js');
|
|
26
27
|
const { cellsRoutes } = require('./cells/routes.js');
|
|
28
|
+
const { liveHostRoutes } = require('./live-host/routes.js');
|
|
29
|
+
const { createLiveHostStore, liveHostPath } = require('./live-host/store.js');
|
|
30
|
+
const { createLiveBridge } = require('./live-host/bridge.js');
|
|
27
31
|
const { fsRoutes } = require('./fs/routes.js');
|
|
28
32
|
const nodesStore = require('./nodes/store.js');
|
|
29
33
|
const nodesTunnel = require('./nodes/tunnel.js');
|
|
@@ -33,6 +37,8 @@ const { createReverseSlotListeners } = require('./nodes/reverse-slot-listeners.j
|
|
|
33
37
|
const reverseRotation = require('./nodes/reverse-rotation.js');
|
|
34
38
|
const topologyCache = require('./nodes/topology-cache.js');
|
|
35
39
|
const { createNodeProxy, handleNodeUpgrade } = require('./proxy/node-proxy.js');
|
|
40
|
+
const { createPanelProxy, handlePanelUpgrade } = require('./proxy/panel-proxy.js');
|
|
41
|
+
const { createPanelAuth } = require('./proxy/panel-auth.js');
|
|
36
42
|
const federation = require('./proxy/federation.js');
|
|
37
43
|
const { audioRoutes } = require('./audio/routes.js');
|
|
38
44
|
const { isConsent: isAudioConsent } = require('./audio/consent.js');
|
|
@@ -549,9 +555,81 @@ function createServer(opts = {}) {
|
|
|
549
555
|
if (!reverseSlotListeners.respond(req, res)) res.status(404).json({ error: 'reverse slot non disponibile' });
|
|
550
556
|
});
|
|
551
557
|
|
|
552
|
-
//
|
|
558
|
+
// Pannello per-cella (D8): inoltra il traffico verso il `panelUrl` di UNA cella
|
|
559
|
+
// LOCALE. La destinazione non arriva mai dal chiamante — si risolve dallo stato
|
|
560
|
+
// della cella — e il token di NexusCrew non prosegue verso il pannello, che e'
|
|
561
|
+
// un servizio terzo e non un nodo. Local-only come /api/live-host: il pass-through
|
|
562
|
+
// /node/<name>/ NON deve portarci un peer qualsiasi (vedi LOCAL_ONLY_PREFIXES in
|
|
563
|
+
// proxy/node-proxy.js). L'attraversamento verso i nodi del proprietario passera'
|
|
564
|
+
// dalla via allowlistata della federazione, dietro il gate di proprieta'.
|
|
565
|
+
async function resolveCellPanel(cellId) {
|
|
566
|
+
const fleet = await fleetP;
|
|
567
|
+
if (!fleet || fleet.available !== true) return null; // fleet non interrogabile
|
|
568
|
+
const statusFn = typeof fleet.status === 'function' ? fleet.status : fleet.cellStatus;
|
|
569
|
+
if (typeof statusFn !== 'function') return null;
|
|
570
|
+
const st = await statusFn.call(fleet);
|
|
571
|
+
const cells = Array.isArray(st && st.cells) ? st.cells : [];
|
|
572
|
+
// La chiave e' `cell`, non `id`: e' cio' che cellStatus PRODUCE
|
|
573
|
+
// (fleet/runtime.js scrive `cell: c.id`, dove `id` e' il nome interno della
|
|
574
|
+
// definizione). Cercare `c.id` qui non trovava MAI nulla, quindi ogni cella
|
|
575
|
+
// con un pannello configurato rispondeva «pannello non disponibile» — e la
|
|
576
|
+
// UI mostrava una causa col nome sbagliato. Tutti gli altri consumatori di
|
|
577
|
+
// cellStatus usano gia' `c.cell` (cells/routes.js, live-host/bridge.js,
|
|
578
|
+
// live-host/routes.js): questo era l'unico fuori posto.
|
|
579
|
+
const cell = cells.find((c) => c && c.cell === cellId);
|
|
580
|
+
if (!cell) return undefined; // cella sconosciuta
|
|
581
|
+
return typeof cell.panelUrl === 'string' ? cell.panelUrl.trim() : '';
|
|
582
|
+
}
|
|
583
|
+
// Tutte le altre /api dietro Bearer: sul loopback il gate vero è il tunnel,
|
|
553
584
|
// ma il token chiude anche altri processi locali della stessa macchina.
|
|
554
585
|
const api = express.Router();
|
|
586
|
+
// L'INGRESSO al pannello sta PRIMA del requireToken generale: un <iframe> e'
|
|
587
|
+
// una navigazione del browser e non porta header, quindi l'autenticazione
|
|
588
|
+
// qui e' dedicata — Bearer per la PWA, ticket monouso + cookie di visione
|
|
589
|
+
// per l'iframe (proxy/panel-auth.js, misura 2026-08-15). Il cookie vale
|
|
590
|
+
// SOLO sul path della cella che lo ha emesso: non e' e non diventa
|
|
591
|
+
// un'autenticazione dell'origine.
|
|
592
|
+
const panelAuth = createPanelAuth({
|
|
593
|
+
verifyToken: (t) => verify(tokenHolder.value, t),
|
|
594
|
+
resolveCellPanel,
|
|
595
|
+
// Senza questo segreto il pannello non saprebbe distinguere la PWA
|
|
596
|
+
// dall'ultimo hop di una route federata — che entra qui col Bearer che il
|
|
597
|
+
// proxy si e' iniettato da se'. E' lo stesso segreto per-processo che
|
|
598
|
+
// firma gli hop di sotto: due valori diversi renderebbero ogni federata
|
|
599
|
+
// 'sospetta', cioe' chiuderebbero il pannello remoto invece di proteggerlo.
|
|
600
|
+
hopSecret: () => hopSecret,
|
|
601
|
+
log: (entry) => diagnostics.record('panel-auth', 'info', entry.outcome, {
|
|
602
|
+
cell: entry.cell, reason: entry.reason, state: entry.outcome,
|
|
603
|
+
}),
|
|
604
|
+
});
|
|
605
|
+
api.use('/panel', panelAuth.panelAuthMiddleware, createPanelProxy({
|
|
606
|
+
resolveCellPanel,
|
|
607
|
+
log: (entry) => diagnostics.record('panel-proxy', 'info', entry.outcome, {
|
|
608
|
+
cell: entry.cell, reason: entry.reason, state: entry.outcome,
|
|
609
|
+
}),
|
|
610
|
+
}));
|
|
611
|
+
// L'ingresso ai pannelli REMOTI ha la stessa malattia dell'iframe locale: la
|
|
612
|
+
// via federata /api/route/<nodi>/_/panel/... sta per finire sotto requireToken
|
|
613
|
+
// e una navigazione del browser non porta header. QUI il ticket non si può
|
|
614
|
+
// validare — è del nodo che lo ha emesso — quindi per le panel-resource
|
|
615
|
+
// federate il requireToken NON si applica: decide il nodo proprietario (gate
|
|
616
|
+
// panelAccess + panelAuth col ticket/cookie, e l'ultimo hop lo riconosce
|
|
617
|
+
// dalla prova di hop: di là il Bearer non apre più il pannello, altrimenti
|
|
618
|
+
// il contenuto uscirebbe per ogni peer con panelAccess senza che nessuno
|
|
619
|
+
// abbia mai preso un ticket). Resta della PWA autenticata l'EMISSIONE: il POST
|
|
620
|
+
// .../panel/<cella>/ticket transita di sotto, col Bearer, come ogni altra /api.
|
|
621
|
+
const panelFederatoRouter = federation.localRouter({
|
|
622
|
+
nodesPath, localPort: () => (server && server.address() ? server.address().port : cfg.port), localCredential: () => tokenHolder.value, readonly: proxyReadonly,
|
|
623
|
+
hopSecret: () => hopSecret,
|
|
624
|
+
});
|
|
625
|
+
api.use('/route', (req, res, next) => {
|
|
626
|
+
const i = req.url.indexOf('/_/');
|
|
627
|
+
if (i === -1) return next();
|
|
628
|
+
const resource = req.url.slice(i + 2);
|
|
629
|
+
if (!/^\/panel\/[A-Za-z0-9._-]{1,32}(?:\/.*)?$/.test(resource)) return next();
|
|
630
|
+
if (req.method === 'POST' && /^\/panel\/[A-Za-z0-9._-]{1,32}\/ticket\/?$/.test(resource)) return next();
|
|
631
|
+
panelFederatoRouter(req, res, next);
|
|
632
|
+
});
|
|
555
633
|
api.use(requireToken(tokenStore));
|
|
556
634
|
// Origine per lo scope celle: stessa prova di hop usata da audio e notify.
|
|
557
635
|
// requireCell:false perche' qui interessa QUALE NODO parla, non quale cella
|
|
@@ -710,6 +788,10 @@ function createServer(opts = {}) {
|
|
|
710
788
|
federatedRate: createSpeakRateLimiter(),
|
|
711
789
|
}));
|
|
712
790
|
api.use('/fleet', fleetRoutes(fleetP, { ...cfg, diagnostics }));
|
|
791
|
+
// Fetta 2b (D3): superficie child del lease Live via canale nativo del
|
|
792
|
+
// bridge (HTTP loopback + Bearer). La cella e' derivata dalla sessione; il
|
|
793
|
+
// proof firmato dal verifier per-installazione autorizza refresh/recovery.
|
|
794
|
+
api.use('/lease', leaseRoutes({ fleetP, readonly: proxyReadonly }));
|
|
713
795
|
// Audio Share. L'identita' del nodo NON e' un campo di cfg: si legge dal node
|
|
714
796
|
// store, la stessa fonte usata da /api/cells e /api/peers. Lo stato Fleet e'
|
|
715
797
|
// asincrono e va atteso: leggerlo come se fosse sincrono lascerebbe la
|
|
@@ -776,6 +858,27 @@ function createServer(opts = {}) {
|
|
|
776
858
|
})),
|
|
777
859
|
readonly: proxyReadonly,
|
|
778
860
|
}));
|
|
861
|
+
// Cella ospite Live (contratto rev6 §2): hostCell unico per nodo con CAS. Lo store
|
|
862
|
+
// vive accanto al token (stessa dir isolata nei test); le route sono local-only e
|
|
863
|
+
// il proxy nega /api/live-host via /node (federazione default-deny).
|
|
864
|
+
// Fetta 3: il ponte risolve il puntamento all'avvio di una Live (POST /bridge
|
|
865
|
+
// nello stesso gruppo, stessa auth, stesso local-only). Isolabile da cfg
|
|
866
|
+
// (rev5 MC0): spento non crea connessioni e la Live resta standard.
|
|
867
|
+
api.use('/live-host', liveHostRoutes({
|
|
868
|
+
fleetP,
|
|
869
|
+
store: createLiveHostStore({ filePath: liveHostPath(cfg) }),
|
|
870
|
+
readonly: proxyReadonly,
|
|
871
|
+
bridge: createLiveBridge({
|
|
872
|
+
cfg,
|
|
873
|
+
fleetP,
|
|
874
|
+
tokenGet: () => tokenHolder.value,
|
|
875
|
+
filesRoot: cfg.filesRoot,
|
|
876
|
+
log: opts.log || console.log,
|
|
877
|
+
}),
|
|
878
|
+
}));
|
|
879
|
+
// Pannello per-cella: montato con il suo ingresso dedicato (ticket+cookie)
|
|
880
|
+
// PRIMA del requireToken generale — vedi il blocco sopra, dove nascono
|
|
881
|
+
// `panelAuth` e `resolveCellPanel`.
|
|
779
882
|
api.use('/vl-nodes', vlNodeApiRoutes({
|
|
780
883
|
storePath: vlNodesPath, broker: vlNodeBroker, ownerId: vlOwnerId, readonly: proxyReadonly,
|
|
781
884
|
}));
|
|
@@ -954,7 +1057,14 @@ function createServer(opts = {}) {
|
|
|
954
1057
|
if (pathname.startsWith('/api/route/')) {
|
|
955
1058
|
let u; try { u = new URL(req.url, 'http://127.0.0.1'); } catch (_) { return socket.destroy(); }
|
|
956
1059
|
const given = bearerFrom(req) || u.searchParams.get('token') || '';
|
|
957
|
-
|
|
1060
|
+
// Le WebSocket di un pannello REMOTO partono dalla pagina nel frame e
|
|
1061
|
+
// portano solo il cookie di visione: il token qui non c'è e non deve
|
|
1062
|
+
// esserci. L'HUB non può validarle (il cookie è del nodo che lo ha
|
|
1063
|
+
// emesso): transita e decide il proprietario, gate panelAccess compreso
|
|
1064
|
+
// — stessa forma del bypass HTTP sulle panel-resource federate.
|
|
1065
|
+
const panelFederato = /^\/api\/route\/[^?#]*\/_\/panel\/[A-Za-z0-9._-]{1,32}(?:\/.*)?$/.test(pathname)
|
|
1066
|
+
&& /(?:^|;\s*)npanel=/.test(String(req.headers.cookie || ''));
|
|
1067
|
+
if (!panelFederato && !verify(tokenHolder.value, given)) return socket.destroy();
|
|
958
1068
|
federation.forwardUpgrade({ req, socket, head, nodesPath, localPort: runtimePort, localCredential: () => tokenHolder.value, ingress: null, readonly: proxyReadonly, activeSockets: proxySockets, hopSecret: () => hopSecret });
|
|
959
1069
|
return;
|
|
960
1070
|
}
|
|
@@ -964,6 +1074,19 @@ function createServer(opts = {}) {
|
|
|
964
1074
|
federation.forwardUpgrade({ req, socket, head, nodesPath, localPort: runtimePort, localCredential: () => tokenHolder.value, ingress, readonly: proxyReadonly, activeSockets: proxySockets, hopSecret: () => hopSecret });
|
|
965
1075
|
return;
|
|
966
1076
|
}
|
|
1077
|
+
if (pathname.startsWith('/api/panel/')) {
|
|
1078
|
+
handlePanelUpgrade({
|
|
1079
|
+
req, socket, head, resolveCellPanel,
|
|
1080
|
+
verifyToken: (t) => verify(tokenHolder.value, t),
|
|
1081
|
+
// Il cookie di visione apre le WS del pannello (la pagina nel frame
|
|
1082
|
+
// non puo' mettere header); Bearer e ?token= restano validi come prima.
|
|
1083
|
+
authorize: panelAuth.authorizeUpgrade,
|
|
1084
|
+
log: (entry) => diagnostics.record('panel-proxy', 'info', entry.outcome, {
|
|
1085
|
+
cell: entry.cell, reason: entry.reason, state: entry.outcome,
|
|
1086
|
+
}),
|
|
1087
|
+
});
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
967
1090
|
if (pathname === '/node' || pathname.startsWith('/node/')) {
|
|
968
1091
|
handleNodeUpgrade({
|
|
969
1092
|
req, socket, head, resolveNode,
|
|
@@ -991,7 +1114,7 @@ function createServer(opts = {}) {
|
|
|
991
1114
|
// async questo handler rischierebbe di perdere i frame che arrivano prima
|
|
992
1115
|
// di bindWs. `/ws` attacca per NOME DI SESSIONE e senza questo gate ogni
|
|
993
1116
|
// filtro sugli elenchi sarebbe decorativo — basterebbe indovinare
|
|
994
|
-
// `cloud-
|
|
1117
|
+
// `cloud-X`.
|
|
995
1118
|
const wsScope = wsCellScope(req);
|
|
996
1119
|
bindWs(ws, {
|
|
997
1120
|
openAttach,
|
|
@@ -1024,7 +1147,7 @@ function createServer(opts = {}) {
|
|
|
1024
1147
|
}
|
|
1025
1148
|
|
|
1026
1149
|
function start(opts = {}) {
|
|
1027
|
-
const { server, cfg } = createServer(opts);
|
|
1150
|
+
const { server, cfg } = createServer({ ...opts, cellLeaseEnabled: true });
|
|
1028
1151
|
const log = opts.log || console.log;
|
|
1029
1152
|
const requestedPort = cfg.port;
|
|
1030
1153
|
const nodesPath = opts.nodesPath || nodesStore.defaultNodesPath(cfg.home || os.homedir());
|
package/package.json
CHANGED
|
@@ -131,3 +131,22 @@ overwrite. An explicit `--output` must remain under the current user's home.
|
|
|
131
131
|
|
|
132
132
|
Read [references/api-contract.md](references/api-contract.md) before changing
|
|
133
133
|
payloads, limits, endpoints, or model handling.
|
|
134
|
+
|
|
135
|
+
## Dependencies
|
|
136
|
+
|
|
137
|
+
**Bundled (installed with the package):** the CLI
|
|
138
|
+
`scripts/alibaba_token_media.py` ships with this skill and is
|
|
139
|
+
dependency-free — Python 3 standard library only (verified against the
|
|
140
|
+
script's imports; it needs a Unix-like environment because it uses `fcntl`,
|
|
141
|
+
so Windows is out of scope).
|
|
142
|
+
|
|
143
|
+
**External (you must provide):**
|
|
144
|
+
|
|
145
|
+
| Need | Install | Probe / failure mode |
|
|
146
|
+
|---|---|---|
|
|
147
|
+
| Python 3 | Debian/Ubuntu `apt install python3`, Fedora `dnf install python3`, macOS `brew install python`, Termux `pkg install python` | `python3 --version` fails → nothing in this skill runs |
|
|
148
|
+
| An Alibaba Cloud **Token Plan Personal** subscription that includes the media models | activate on the Alibaba Cloud console — no local install | API calls fail with an authorization error from the service |
|
|
149
|
+
| API key in the environment | `export ALIBABA_CODE_API_KEY=<your key>` in the shell/service that runs the script | the script refuses with `ALIBABA_CODE_API_KEY is not set in this process environment` — the failure names the exact variable; no network call is made without it |
|
|
150
|
+
|
|
151
|
+
The key is read only from the environment: never paste it into a prompt, a
|
|
152
|
+
script argument, or a file.
|
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.
|
|
@@ -152,3 +152,26 @@ user deliberately chooses `--allow-unused`.
|
|
|
152
152
|
name.
|
|
153
153
|
5. Show the final preview or document to the user before calling it ready.
|
|
154
154
|
6. State clearly which fields remain empty, uncertain, unsigned or unsubmitted.
|
|
155
|
+
|
|
156
|
+
## Dependencies
|
|
157
|
+
|
|
158
|
+
**Bundled (installed with the package):** the five scripts
|
|
159
|
+
(`inspect_pdf.py`, `fill_pdf.py`, `fill_docx.py`, `dump_docx.py`,
|
|
160
|
+
`prepare_signature.py`) and `requirements.txt` ship inside this skill
|
|
161
|
+
directory.
|
|
162
|
+
|
|
163
|
+
**External (you must provide):**
|
|
164
|
+
|
|
165
|
+
| Need | Install | Probe / failure mode |
|
|
166
|
+
|---|---|---|
|
|
167
|
+
| Python 3 | Debian/Ubuntu `apt install python3 python3-pip`, Fedora `dnf install python3 pip`, macOS `brew install python`, Termux `pkg install python` | `python3 --version` fails → nothing in this skill runs |
|
|
168
|
+
| PyMuPDF, Pillow, numpy, python-docx (exact pins in the bundled `requirements.txt`) | add those four packages to the Python environment that runs the scripts, taking the versions from the bundled `requirements.txt`; this skill deliberately does not spell out the command, because it must ask before touching an environment | probe with `python3 -c "import fitz, PIL, numpy, docx"` — a missing module fails by naming itself (`ModuleNotFoundError: No module named 'fitz'`); add that package, not "dependencies" in the abstract |
|
|
169
|
+
|
|
170
|
+
Termux note: Termux has no systemd and no Docker assumptions here, but the
|
|
171
|
+
native wheels for PyMuPDF/numpy on Termux are **not verified**: if `pip`
|
|
172
|
+
cannot build them, this skill is not usable on that device — report that
|
|
173
|
+
plainly instead of switching to an unverified PDF writer.
|
|
174
|
+
|
|
175
|
+
Every script keeps the blank source untouched and writes a new output file;
|
|
176
|
+
if a dependency is missing mid-flow, the scripts refuse rather than emit a
|
|
177
|
+
half-filled form.
|
|
@@ -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.
|
|
@@ -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).
|