@mmmbuto/nexuscrew 0.7.6 → 0.8.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.
Files changed (59) hide show
  1. package/README.md +173 -18
  2. package/frontend/dist/assets/index-BBOgTCoO.css +32 -0
  3. package/frontend/dist/assets/index-DQrDBogT.js +83 -0
  4. package/frontend/dist/index.html +2 -2
  5. package/frontend/dist/sw.js +29 -0
  6. package/frontend/dist/version.json +1 -0
  7. package/lib/auth/middleware.js +7 -1
  8. package/lib/auth/token.js +31 -1
  9. package/lib/cli/commands.js +462 -31
  10. package/lib/cli/doctor.js +160 -0
  11. package/lib/cli/fleet-service.js +319 -0
  12. package/lib/cli/init.js +107 -7
  13. package/lib/cli/path.js +24 -0
  14. package/lib/cli/service.js +14 -3
  15. package/lib/cli/url.js +63 -0
  16. package/lib/config.js +5 -0
  17. package/lib/decks/routes.js +81 -0
  18. package/lib/decks/store.js +117 -0
  19. package/lib/files/routes.js +59 -2
  20. package/lib/files/store.js +16 -1
  21. package/lib/fleet/boot.js +62 -0
  22. package/lib/fleet/builtin.js +594 -0
  23. package/lib/fleet/definitions.js +410 -0
  24. package/lib/fleet/index.js +49 -10
  25. package/lib/fleet/managed.js +211 -0
  26. package/lib/fleet/provider.js +102 -0
  27. package/lib/fleet/routes.js +78 -8
  28. package/lib/fs/routes.js +56 -0
  29. package/lib/mcp/server.js +362 -0
  30. package/lib/nodes/commands.js +396 -0
  31. package/lib/nodes/store.js +358 -0
  32. package/lib/nodes/tunnel-supervisor.js +102 -0
  33. package/lib/nodes/tunnel.js +300 -0
  34. package/lib/notify/asks.js +150 -0
  35. package/lib/notify/events.js +59 -0
  36. package/lib/notify/notifier.js +37 -0
  37. package/lib/notify/persist.js +73 -0
  38. package/lib/notify/push.js +289 -0
  39. package/lib/notify/routes.js +260 -0
  40. package/lib/proxy/node-proxy.js +292 -0
  41. package/lib/pty/attach.js +34 -9
  42. package/lib/pty/provider.js +21 -6
  43. package/lib/server.js +214 -15
  44. package/lib/settings/routes.js +473 -0
  45. package/lib/ws/bridge.js +10 -1
  46. package/package.json +8 -2
  47. package/skills/nexuscrew-agent/SKILL.md +83 -0
  48. package/skills/nexuscrew-agent/bin/nc-deliver +23 -0
  49. package/skills/nexuscrew-agent/bin/nc-send +48 -0
  50. package/frontend/dist/assets/index-BWhSqR3J.css +0 -32
  51. package/frontend/dist/assets/index-Bx8vdLZY.css +0 -32
  52. package/frontend/dist/assets/index-CLb2xmyu.js +0 -81
  53. package/frontend/dist/assets/index-CgxfkmRo.js +0 -81
  54. package/frontend/dist/assets/index-DE67kR0M.js +0 -81
  55. package/frontend/dist/assets/index-DQMx2p4x.js +0 -81
  56. package/frontend/dist/assets/index-DWhfVwKW.css +0 -32
  57. package/frontend/dist/assets/index-DoPZ_3-h.js +0 -81
  58. package/frontend/dist/assets/index-o9l7pPAf.css +0 -32
  59. package/frontend/dist/assets/index-q9PUrGO0.js +0 -81
@@ -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-DE67kR0M.js"></script>
15
- <link rel="stylesheet" crossorigin href="/assets/index-DWhfVwKW.css">
14
+ <script type="module" crossorigin src="/assets/index-DQrDBogT.js"></script>
15
+ <link rel="stylesheet" crossorigin href="/assets/index-BBOgTCoO.css">
16
16
  </head>
17
17
  <body>
18
18
  <div id="root"></div>
@@ -1,3 +1,32 @@
1
1
  self.addEventListener('install', () => self.skipWaiting());
2
2
  self.addEventListener('activate', () => self.clients.claim());
3
3
  self.addEventListener('fetch', (e) => e.respondWith(fetch(e.request)));
4
+
5
+ // Web Push del MCP bridge: payload JSON {title, body?, url?} dal server.
6
+ // tag fisso 'nexuscrew': le notifiche si sostituiscono invece di accumularsi.
7
+ self.addEventListener('push', (e) => {
8
+ let data = {};
9
+ try { data = e.data ? e.data.json() : {}; } catch (_) { /* payload non JSON: ignora */ }
10
+ const title = typeof data.title === 'string' && data.title ? data.title : 'NexusCrew';
11
+ e.waitUntil(self.registration.showNotification(title, {
12
+ body: typeof data.body === 'string' ? data.body : '',
13
+ tag: 'nexuscrew',
14
+ data: { url: typeof data.url === 'string' ? data.url : '/' },
15
+ }));
16
+ });
17
+
18
+ // Click sulla notifica: focus di una finestra gia' aperta (deep-link via
19
+ // navigate) oppure apertura di una nuova su data.url (es. /#ask=<id>).
20
+ self.addEventListener('notificationclick', (e) => {
21
+ e.notification.close();
22
+ const url = (e.notification.data && e.notification.data.url) || '/';
23
+ e.waitUntil(self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then((wins) => {
24
+ for (const w of wins) {
25
+ if ('focus' in w) {
26
+ if ('navigate' in w) w.navigate(url).catch(() => {});
27
+ return w.focus();
28
+ }
29
+ }
30
+ return self.clients.openWindow ? self.clients.openWindow(url) : undefined;
31
+ }));
32
+ });
@@ -0,0 +1 @@
1
+ {"version":"0.8.0"}
@@ -7,9 +7,15 @@ function bearerFrom(req) {
7
7
  }
8
8
 
9
9
  // 401 uniforme: nessun dettaglio su cosa è andato storto.
10
+ // `token` puo' essere una stringa (storico) o un holder live {get}: l'holder si
11
+ // rilegge ad OGNI richiesta, cosi' una rotazione (audit F7 / §4b(3)) invalida il
12
+ // vecchio token senza restart. backward-compat: la stringa resta accettata.
10
13
  function requireToken(token) {
14
+ const read = (token && typeof token === 'object' && typeof token.get === 'function')
15
+ ? token.get
16
+ : () => token;
11
17
  return (req, res, next) => {
12
- if (verify(token, bearerFrom(req))) return next();
18
+ if (verify(read(), bearerFrom(req))) return next();
13
19
  res.status(401).json({ error: 'unauthorized' });
14
20
  };
15
21
  }
package/lib/auth/token.js CHANGED
@@ -53,6 +53,36 @@ function loadOrCreateToken(tokenPath) {
53
53
  return tok;
54
54
  }
55
55
 
56
+ // Rotazione token: genera un NUOVO segreto e sostituisce il file in modo ATOMICO
57
+ // (tmp stessa dir -> chmod 0600 -> rename), no-follow symlink. [A2 §4b(3)]
58
+ // A differenza di loadOrCreateToken NON preserva l'esistente: overwrite voluto.
59
+ // L'invalidazione reale delle sessioni attive la fa il chiamante (restart service:
60
+ // il server carica il token solo allo startup). Ritorna il nuovo token (il chiamante
61
+ // NON deve stamparlo: si usa `nexuscrew url`).
62
+ function rotateToken(tokenPath) {
63
+ // reject symlink target preesistente (no-follow), ENOENT ok (nuovo file)
64
+ try {
65
+ const st = fs.lstatSync(tokenPath);
66
+ if (st.isSymbolicLink()) {
67
+ throw new Error(`refusing symlink token path: ${tokenPath}`);
68
+ }
69
+ } catch (e) {
70
+ if (e.code !== 'ENOENT') throw e;
71
+ }
72
+ fs.mkdirSync(path.dirname(tokenPath), { recursive: true });
73
+ const tok = crypto.randomBytes(24).toString('base64url');
74
+ const tmp = tokenPath + '.tmp.' + process.pid;
75
+ try {
76
+ fs.writeFileSync(tmp, tok + '\n', { flag: 'wx', mode: 0o600 }); // exclusive temp
77
+ fs.chmodSync(tmp, 0o600);
78
+ fs.renameSync(tmp, tokenPath); // atomic replace
79
+ } catch (e) {
80
+ try { fs.unlinkSync(tmp); } catch (_) {} // cleanup temp su failure
81
+ throw e;
82
+ }
83
+ return tok;
84
+ }
85
+
56
86
  function verify(expected, given) {
57
87
  if (typeof expected !== 'string' || typeof given !== 'string') return false;
58
88
  const a = Buffer.from(expected);
@@ -61,4 +91,4 @@ function verify(expected, given) {
61
91
  return crypto.timingSafeEqual(a, b);
62
92
  }
63
93
 
64
- module.exports = { loadOrCreateToken, readTokenSafe, verify };
94
+ module.exports = { loadOrCreateToken, readTokenSafe, rotateToken, verify };