@mmmbuto/nexuscrew 0.9.43 → 0.9.46

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.
@@ -11,8 +11,8 @@
11
11
  <meta name="apple-mobile-web-app-title" content="NexusCrew" />
12
12
  <link rel="manifest" href="/manifest.json" />
13
13
  <title>NexusCrew</title>
14
- <script type="module" crossorigin src="/assets/index-CeN87Vib.js"></script>
15
- <link rel="stylesheet" crossorigin href="/assets/index-DDw517fP.css">
14
+ <script type="module" crossorigin src="/assets/index-DN3krh50.js"></script>
15
+ <link rel="stylesheet" crossorigin href="/assets/index-BaqhMxzK.css">
16
16
  </head>
17
17
  <body>
18
18
  <div id="root"></div>
@@ -1 +1 @@
1
- {"version":"0.9.43"}
1
+ {"version":"0.9.46"}
@@ -46,6 +46,15 @@ Usage:
46
46
  nexuscrew help show this help
47
47
  nexuscrew version show the installed version
48
48
 
49
+ Runtime commands for service managers, MCP clients and the boot integration
50
+ (not configuration surfaces):
51
+
52
+ nexuscrew serve run the runtime in the foreground (--pidfile available)
53
+ nexuscrew mcp serve the MCP bridge on stdio for AI sessions
54
+ nexuscrew fleet-boot start the cells marked boot:true
55
+ nexuscrew identity provision [--force] [--no-write-config] [--dir <path>]
56
+ write the node identity authority credentials
57
+
49
58
  Configuration, engines, models and advanced lifecycle also live in the PWA.
50
59
  NexusCrew binds only to 127.0.0.1 and automatically selects a free port.`;
51
60
 
@@ -1557,8 +1566,9 @@ function dispatch(argv, opts = {}) {
1557
1566
  waitDelayMs: opts.waitDelayMs === undefined ? 250 : opts.waitDelayMs,
1558
1567
  }).then(attesa);
1559
1568
  }
1560
- // Internal runtime commands used by service managers and MCP clients. They
1561
- // are intentionally omitted from HELP and are not configuration surfaces.
1569
+ // Internal runtime commands used by service managers, MCP clients and the
1570
+ // boot integration. They are not configuration surfaces: the help lists
1571
+ // them under their own heading, apart from the public commands.
1562
1572
  if (cmd === 'serve') {
1563
1573
  serve({ ...opts, pidfile: flags.pidfile, serverStart: opts.serverStart });
1564
1574
  return { code: 0, keepAlive: true }; // server.listen tiene il processo vivo; non exit
@@ -1,8 +1,9 @@
1
1
  'use strict';
2
2
  // Telemetria per-cella: contesto LIBERO e tier 5h/7d USATI, letti dal file che
3
- // la statusline di Claude Code scrive in <root>/<sessione>/telemetry.json
4
- // (snippet documentato in docs/STATUSLINE_TELEMETRY.md, NON applicato: la
5
- // statusline e' dell'operatore). Il verso dei due dati e' OPPOSTO e i nomi del file lo
3
+ // la statusline di Claude Code scrive in <root>/<sessione>/telemetry.json.
4
+ // Quel file e' dell'operatore: NexusCrew non installa ne' modifica la sua
5
+ // statusline, e legge solo cio' che lei ha gia' scritto. Il verso dei due dati
6
+ // e' OPPOSTO e i nomi del file lo
6
7
  // portano scritto dentro: `contextFreePct` e' quanto RESTA, `tier*UsedPct` e'
7
8
  // quanto e' STATO CONSUMATO. Confondere i versi produce una riga che dice il
8
9
  // contrario del vero e fa prendere la decisione opposta a quella giusta.
@@ -369,10 +369,10 @@ function createLeaseManager(cfg = {}, seams = {}) {
369
369
  return true;
370
370
  }
371
371
 
372
- // il canale verify espone l'enum COMPLETO del contratto v1
373
- // (docs/identity/verify-channel-v1.md): a differenza del relay
374
- // challenge-proof (che collassa i motivi sensibili), qui la diagnostica
375
- // puntuale e' parte del contratto — il daemon decide il fail-closed.
372
+ // Il canale verify espone l'enum COMPLETO del contratto v1: a differenza
373
+ // del relay challenge-proof (che collassa i motivi sensibili), qui la
374
+ // diagnostica puntuale e' parte del contratto — il daemon decide il
375
+ // fail-closed.
376
376
  function identityVerifyReason(reason) {
377
377
  const known = new Set([
378
378
  'malformed', 'expired', 'bad-proof', 'replay', 'challenge-replay',
@@ -150,7 +150,7 @@ function notFound(res) { res.status(404).json({ error: 'not found' }); }
150
150
  // passata (requireToken davanti). Ordine interno: name strict -> no-transitive ->
151
151
  // resolve -> readonly -> proxy.
152
152
  function createNodeProxy(deps) {
153
- const { resolveNode, readonly = () => false, httpRequest = http.request } = deps;
153
+ const { resolveNode, readonly = () => false, httpRequest = http.request, proxyTimeoutMs = PROXY_TIMEOUT_MS } = deps;
154
154
  return function nodeProxy(req, res) {
155
155
  const parsed = splitNodePath(req.url);
156
156
  if (!parsed) return notFound(res); // no name
@@ -164,11 +164,11 @@ function createNodeProxy(deps) {
164
164
  if (readonly() && MUTATING.has(req.method)) {
165
165
  return res.status(403).json({ error: 'READONLY: mutazione verso nodo bloccata' });
166
166
  }
167
- proxyHttp(req, res, node, parsed.rest, parsed.search, httpRequest);
167
+ proxyHttp(req, res, node, parsed.rest, parsed.search, httpRequest, proxyTimeoutMs);
168
168
  };
169
169
  }
170
170
 
171
- function proxyHttp(req, res, node, rest, search, httpRequest) {
171
+ function proxyHttp(req, res, node, rest, search, httpRequest, timeoutMs) {
172
172
  const options = {
173
173
  host: '127.0.0.1', // upstream loopback ONLY, from config
174
174
  port: node.localPort,
@@ -193,10 +193,18 @@ function proxyHttp(req, res, node, rest, search, httpRequest) {
193
193
  if (!res.headersSent) res.status(502).json({ error: 'node non raggiungibile' });
194
194
  return;
195
195
  }
196
- upstream.setTimeout(PROXY_TIMEOUT_MS, () => upstream.destroy(new Error('upstream timeout')));
197
- upstream.on('error', () => {
198
- if (!res.headersSent) res.status(502).json({ error: 'node non raggiungibile' });
199
- else res.destroy();
196
+ upstream.setTimeout(timeoutMs || PROXY_TIMEOUT_MS, () => upstream.destroy(new Error("upstream timeout")));
197
+ upstream.on('error', (e) => {
198
+ // Il timeout di attesa (PROXY_TIMEOUT_MS) NON è un nodo morto: il nodo
199
+ // remoto può essere ancora al lavoro sulla richiesta (es. un avvio cella
200
+ // oltre i 30 s). Il `cause` distinto permette al client di trattarlo
201
+ // come attesa in corso e non come errore del nodo.
202
+ if (!res.headersSent) {
203
+ const waiting = e && e.message === 'upstream timeout';
204
+ res.status(502).json(waiting
205
+ ? { error: 'node non raggiungibile', cause: 'upstream-timeout' }
206
+ : { error: 'node non raggiungibile' });
207
+ } else res.destroy();
200
208
  });
201
209
  req.on('aborted', () => upstream.destroy());
202
210
  req.pipe(upstream);
@@ -48,6 +48,9 @@
48
48
  "msa_index_batch",
49
49
  "msa_search",
50
50
  "msa_fetch_doc",
51
+ "msa_list_collections",
52
+ "msa_stats",
53
+ "msa_manifest",
51
54
  "msa_remember",
52
55
  "msa_forget",
53
56
  "msa_interleave_round"
@@ -72,7 +75,9 @@
72
75
  "cell_result",
73
76
  "cell_list",
74
77
  "cell_send",
75
- "cell_ask"
78
+ "cell_ask",
79
+ "cell_await",
80
+ "cell_inbox"
76
81
  ]
77
82
  },
78
83
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmmbuto/nexuscrew",
3
- "version": "0.9.43",
3
+ "version": "0.9.46",
4
4
  "description": "Faithful browser tmux client — attach to live sessions over a real PTY, localhost-only, mobile-easy",
5
5
  "main": "lib/server.js",
6
6
  "bin": {
@@ -91,6 +91,12 @@ project actually owns is the two added lines below and their reasons. It also
91
91
  means no registry to host, nothing to keep patched on your behalf, and a
92
92
  recipe you can read before you run it.
93
93
 
94
+ The recipe needs a host that runs Docker with Compose v2 — see the
95
+ dependencies at the end. On a platform without Docker, Android/Termux
96
+ included, the container cannot build there: the `panelUrl` mechanics earlier
97
+ in this skill still apply to any loopback web UI that node can reach, and the
98
+ recipe simply runs on a different host.
99
+
94
100
  Build it yourself from [`docker/`](docker/):
95
101
 
96
102
  ```bash
@@ -45,7 +45,7 @@ includes either is refused. Everything else is patchable.
45
45
  | `mcp` | no | **names only**, up to 64, each matching `[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}` |
46
46
  | `label`, `tmuxSession` | varies | display label ≤64 chars; session name ≤64 chars, immutable |
47
47
 
48
- Ceilings on the document as a whole: **32 cells**, **24 engines**. These are not
48
+ Ceilings on the document as a whole: **32 cells**, **100 engines**. These are not
49
49
  close to most installs, but a restore that silently dropped entries would be
50
50
  worse than one that fails, so a document over the cap is rejected rather than
51
51
  truncated.
@@ -66,10 +66,13 @@ operation or it does not, and the set is reported in `capabilities` from
66
66
  501 not supported by this fleet provider
67
67
  ```
68
68
 
69
- So the honest sequence is: read `capabilities`, and if the one you need
70
- (`define`, `edit`, `remove`, `restore`, `definitions`, `schema`) is missing,
71
- **say so to the user** rather than retrying. A `501` here is a statement about
72
- the provider, not about your payload — do not start rewriting the body.
69
+ So the honest sequence is: read `capabilities`, and if the one you need is
70
+ missing, **say so to the user** rather than retrying. The set varies by
71
+ provider; the built-in one reports `status`, `up`, `down`, `restart`, `engine`,
72
+ `boot`, `define`, `edit`, `remove`, `import`, `restore`, `schema`,
73
+ `definitions`, `credentials`, `model-test` and `edit-model`. A `501` here is a
74
+ statement about the provider, not about your payload — do not start rewriting
75
+ the body.
73
76
 
74
77
  Separately, a provider in read-only mode refuses writes with `403`. Two
75
78
  different refusals with two different meanings: `501` says "this provider can
@@ -46,9 +46,12 @@ Through the MCP bridge, when the tools are exposed in your session:
46
46
  | tell the human something, or ask | `nc_notify`, `nc_ask` | `nexuscrew-agent` |
47
47
  | read runtime state, identity, decks | `nc_status`, `nc_identity`, `nc_deck` | `nexuscrew-agent` |
48
48
  | find and message another cell | `nc_cells` then `nc_send_cell` | `nexuscrew-agent` |
49
+ | manage a paired VL micro-device | `nc_vl_nodes`, then `nc_vl_command` (plus `nc_vl_invite` / `nc_vl_revoke`) | `nexuscrew-agent` |
49
50
  | speak on a node or audio group | `nc_speak`, `nc_speak_group` | `nexuscrew-agent` |
51
+ | check or stop an utterance you started | `nc_speak_status`, `nc_speak_stop`, `nc_speak_group_status`, `nc_speak_group_stop` | `nexuscrew-agent` |
50
52
  | hand a file to the human | `nc_send_file`, `nc_inbox` | `nexuscrew-agent` |
51
53
  | find out why a cell will not start | `nc_cell_diagnostics` | `nexuscrew-agent` |
54
+ | register, renew or recover this cell's Live lease | `nc_lease_register`, `nc_lease_refresh`, `nc_lease_recovery` | `nexuscrew-agent` |
52
55
  | keep state across sessions | Memory MCP | `memory` |
53
56
  | index and retrieve documents | MSA MCP | `vl-msa` |
54
57
  | delegate bounded work to workers | Crew MCP | `crew` |
@@ -31,6 +31,7 @@ When the client exposes the NexusCrew MCP server, use these tools directly:
31
31
  | Check or stop a group utterance you started | `nc_speak_group_status`, `nc_speak_group_stop` |
32
32
  | Read who the caller is, without a session or token | `nc_identity` |
33
33
  | Read why a local Fleet cell failed to start | `nc_cell_diagnostics` |
34
+ | Register, renew or recover this cell's Live lease | `nc_lease_register`, `nc_lease_refresh`, `nc_lease_recovery` |
34
35
 
35
36
  Apply these rules:
36
37
 
@@ -50,6 +51,27 @@ Apply these rules:
50
51
  - `nc_cell_diagnostics` returns the redacted shell command and the last bounded spawn or start failure for one local Fleet cell. Use it when a cell will not come up, before reading state files.
51
52
  - Do not treat an MCP notification as a substitute for the final response required by the active client.
52
53
 
54
+ ### Live lease registration (`nc_lease_*`)
55
+
56
+ A cell can hold a **Live lease registration** on the node: it registers once,
57
+ then keeps the registration alive by presenting the proof it received. Three
58
+ tools, one job each — their return values do not overlap:
59
+
60
+ - `nc_lease_register {proof?}` — registers this cell under its own incarnation
61
+ and returns the first child proof. It can answer `{status:"pending"}` with a
62
+ `retryAfterMs` when the node is not tracking the cell yet: wait that long
63
+ and call it again rather than spinning.
64
+ - `nc_lease_refresh {proof}` — renews a live registration. Pass the last
65
+ received proof object back unchanged. Answers `{status:"live"}` with a new
66
+ proof; it is never pending, and `{status:"no-registration"}` means
67
+ `nc_lease_register` is the next call, not a retry.
68
+ - `nc_lease_recovery {proof}` — resumes the registration after a gap, keeping
69
+ the same incarnation; a proof that expired recently is still accepted. Every
70
+ attempt counts toward a cap — past it, register again.
71
+
72
+ Use them only when your runtime asks this cell to hold a Live lease. Treat
73
+ each status literally, and pass proofs through verbatim.
74
+
53
75
  The MCP server is the stdio command `nexuscrew mcp` and must be registered in the host AI client. If the `nc_*` tools are not exposed, report that the bridge is not configured in that session and use the fallback flows below where applicable.
54
76
 
55
77
  ## Optional capability companions
@@ -82,9 +104,14 @@ Per session, NexusCrew watches `<root>/<session>/{inbox,outbox}` (root = `$NEXUS
82
104
  When `nc_send_file` is unavailable, deliver with the helper (resolves the current tmux session, timestamps, never overwrites):
83
105
 
84
106
  ```bash
85
- bin/nc-deliver report.pdf chart.png # → ~/NexusFiles/<session>/outbox/
107
+ "$(npm root -g)/@mmmbuto/nexuscrew/skills/nexuscrew-agent/bin/nc-deliver" report.pdf chart.png
108
+ # → ~/NexusFiles/<session>/outbox/
86
109
  ```
87
110
 
111
+ The helpers ship inside the npm package and are **not linked on `PATH`** — the
112
+ only global command is `nexuscrew` — so address them under the global package
113
+ root; `npm root -g` prints that root for the Node installation in use.
114
+
88
115
  Don't hand-craft the path from a guessed session name — use `nc-deliver`, or derive the session with `tmux display-message -p '#S'`.
89
116
 
90
117
  ## Sending text to a tmux session
@@ -96,9 +123,10 @@ non-Fleet sessions; it must not bypass federation visibility or routing ACLs.
96
123
  `tmux send-keys 'msg' Enter` is **not** reliable: a TUI's paste-burst detector swallows the Enter and the message just sits in the composer, while exit code is still 0. Use the helper:
97
124
 
98
125
  ```bash
99
- bin/nc-send <session> "text" # paste + submit
100
- bin/nc-send <session> --file prompt.txt # from a file
101
- bin/nc-send <session> --no-submit "text" # leave in composer, no Enter
126
+ NC_SEND="$(npm root -g)/@mmmbuto/nexuscrew/skills/nexuscrew-agent/bin/nc-send"
127
+ "$NC_SEND" <session> "text" # paste + submit
128
+ "$NC_SEND" <session> --file prompt.txt # from a file
129
+ "$NC_SEND" <session> --no-submit "text" # leave in composer, no Enter
102
130
  ```
103
131
 
104
132
  It does: `load-buffer` → `paste-buffer -p` (bracketed paste) → burst-flush (`C-e`) → `Enter`. **Verify it landed** — never trust the exit code:
@@ -173,8 +201,11 @@ work. The setting is also applied to windows created later in that session.
173
201
  ## Dependencies
174
202
 
175
203
  **Bundled (installed with the package):** the `nexuscrew` CLI, `lib/`, these
176
- skills, and the `bin/nc-send` / `bin/nc-deliver` helpers arrive with
204
+ skills, and the `nc-send` / `nc-deliver` helpers arrive with
177
205
  `npm install -g @mmmbuto/nexuscrew` (Node.js >= 18 required by `engines`).
206
+ Only `nexuscrew` is linked on `PATH`; the helpers live inside the package at
207
+ `skills/nexuscrew-agent/bin/` — reach them through `npm root -g` as shown in
208
+ File exchange above.
178
209
 
179
210
  **External (you must provide):**
180
211