nexus-prime 7.5.0 → 7.6.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/dist/cli.js CHANGED
@@ -1905,6 +1905,75 @@ program
1905
1905
  timeoutMs: opts.timeout ? Number(opts.timeout) : undefined,
1906
1906
  });
1907
1907
  });
1908
+ program
1909
+ .command('fix-claude-hooks')
1910
+ .description('Strip stale Nexus hook entries from ~/.claude/settings.json (use after downgrading from a newer version)')
1911
+ .option('--dry-run', 'Show what would change without writing')
1912
+ .option('--workspace', 'Also clean ./.claude/settings.json in the current workspace')
1913
+ .action(async (opts) => {
1914
+ const fs = await import('fs');
1915
+ const path = await import('path');
1916
+ const os = await import('os');
1917
+ const { NEXUS_HOOK_COMMAND_MARKER } = await import('./install/claude-code-hooks.js');
1918
+ const targets = [path.join(os.homedir(), '.claude', 'settings.json')];
1919
+ if (opts.workspace)
1920
+ targets.push(path.join(process.cwd(), '.claude', 'settings.json'));
1921
+ let totalRemoved = 0;
1922
+ for (const target of targets) {
1923
+ if (!fs.existsSync(target)) {
1924
+ console.log(` skip: ${target} (not found)`);
1925
+ continue;
1926
+ }
1927
+ let parsed;
1928
+ try {
1929
+ parsed = JSON.parse(fs.readFileSync(target, 'utf8'));
1930
+ }
1931
+ catch (err) {
1932
+ console.error(` error: ${target} (invalid JSON: ${err instanceof Error ? err.message : String(err)})`);
1933
+ continue;
1934
+ }
1935
+ const hooks = parsed?.hooks;
1936
+ if (!hooks || typeof hooks !== 'object') {
1937
+ console.log(` ok: ${target} (no hooks block)`);
1938
+ continue;
1939
+ }
1940
+ let removedHere = 0;
1941
+ for (const event of Object.keys(hooks)) {
1942
+ if (!Array.isArray(hooks[event]))
1943
+ continue;
1944
+ const before = hooks[event];
1945
+ const after = before.filter((entry) => {
1946
+ const entryHooks = entry?.hooks;
1947
+ if (!Array.isArray(entryHooks))
1948
+ return true;
1949
+ const hasNexus = entryHooks.some((h) => typeof h?.command === 'string' && h.command.trimStart().startsWith(NEXUS_HOOK_COMMAND_MARKER));
1950
+ if (hasNexus)
1951
+ removedHere += 1;
1952
+ return !hasNexus;
1953
+ });
1954
+ if (after.length === 0) {
1955
+ delete hooks[event];
1956
+ }
1957
+ else {
1958
+ hooks[event] = after;
1959
+ }
1960
+ }
1961
+ if (Object.keys(hooks).length === 0)
1962
+ delete parsed.hooks;
1963
+ totalRemoved += removedHere;
1964
+ if (removedHere === 0) {
1965
+ console.log(` ok: ${target} (no Nexus entries)`);
1966
+ continue;
1967
+ }
1968
+ if (opts.dryRun) {
1969
+ console.log(` dry-run: ${target} would remove ${removedHere} Nexus hook entry/entries`);
1970
+ continue;
1971
+ }
1972
+ fs.writeFileSync(target, JSON.stringify(parsed, null, 2) + '\n', 'utf8');
1973
+ console.log(` fixed: ${target} (removed ${removedHere} entry/entries)`);
1974
+ }
1975
+ console.log(opts.dryRun ? `\nDry-run total: ${totalRemoved} entry/entries.` : `\nDone. Removed ${totalRemoved} stale Nexus hook entry/entries.`);
1976
+ });
1908
1977
  program
1909
1978
  .command('watch')
1910
1979
  .description('Tail live SSE events from the running daemon in a colored terminal feed')
@@ -1923,4 +1992,26 @@ program
1923
1992
  if (process.argv.length === 2) {
1924
1993
  process.argv.push('setup', 'auto');
1925
1994
  }
1926
- program.parse();
1995
+ // Graceful fallback for unknown `nexus-prime hook <subcommand>` invocations.
1996
+ // When users downgrade from a version that wrote hook entries to
1997
+ // ~/.claude/settings.json, the older binary may not have the referenced
1998
+ // subcommand. Exiting non-zero would fail every Claude Code session start.
1999
+ // Drain piped stdin (Claude pipes JSON into hooks) and exit 0 silently.
2000
+ const KNOWN_HOOK_SUBCOMMANDS = new Set([
2001
+ 'bootstrap', 'memory', 'mindkit', 'ghost-pass', 'session-dna',
2002
+ 'generate', 'deploy', 'undeploy', 'list', 'help', '--help', '-h',
2003
+ ]);
2004
+ if (process.argv[2] === 'hook' && process.argv[3] && !KNOWN_HOOK_SUBCOMMANDS.has(process.argv[3])) {
2005
+ if (!process.stdin.isTTY) {
2006
+ process.stdin.resume();
2007
+ process.stdin.on('data', () => { });
2008
+ process.stdin.on('end', () => process.exit(0));
2009
+ setTimeout(() => process.exit(0), 200).unref();
2010
+ }
2011
+ else {
2012
+ process.exit(0);
2013
+ }
2014
+ }
2015
+ else {
2016
+ program.parse();
2017
+ }
@@ -95,6 +95,12 @@ if(location.protocol==='file:'){
95
95
  <div id="board-pyramid"></div>
96
96
  </div>
97
97
 
98
+ <!-- Connected Ecosystem (per-client MCP status) -->
99
+ <div id="connected-ecosystem" class="card" style="margin-bottom:16px;display:none">
100
+ <div class="shd" style="margin-bottom:8px">Connected Ecosystem <span id="ecosystem-stamp" style="font-size:11px;opacity:.5;font-weight:400"></span></div>
101
+ <div id="ecosystem-list" style="display:flex;flex-wrap:wrap;gap:8px"></div>
102
+ </div>
103
+
98
104
  <!-- Hero KPI row -->
99
105
  <div id="hero-stats" role="region" aria-label="Key metrics">
100
106
  <div class="hero-stat"><div class="metric-val" id="m-tokens">—</div><div class="metric-lbl">Tokens Saved</div><canvas id="spark-tokens" class="sparkline" width="80" height="24" aria-hidden="true"></canvas></div>
@@ -116,6 +122,16 @@ if(location.protocol==='file:'){
116
122
  <!-- Orchestration pipeline (live decomposition + completion) -->
117
123
  <div id="orchestration-pipeline" class="card" style="margin-bottom:16px;display:none"></div>
118
124
 
125
+ <!-- Neural Stream HUD (live SSE event feed strip) -->
126
+ <div id="neural-stream-card" class="card" style="margin-bottom:16px;display:none">
127
+ <div class="shd" style="margin-bottom:8px;display:flex;align-items:center;gap:8px">
128
+ <span>Neural Stream</span>
129
+ <span id="neural-stream-count" style="font-size:11px;opacity:.5;font-weight:400"></span>
130
+ <span style="margin-left:auto;display:inline-flex;align-items:center;gap:4px;font-size:11px;opacity:.6;font-weight:400"><span style="display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--accent-good,#4ade80);animation:pulse 2s ease-in-out infinite"></span> live</span>
131
+ </div>
132
+ <div id="neural-stream-list" style="display:flex;flex-direction:column;gap:4px;max-height:200px;overflow-y:auto"></div>
133
+ </div>
134
+
119
135
  <!-- Tool health strip -->
120
136
  <div id="tool-health-card" class="card" style="margin-bottom:16px;display:none">
121
137
  <div class="shd" style="margin-bottom:8px">Tool Health <span id="tool-health-stamp" style="font-size:11px;opacity:.5;font-weight:400"></span></div>
@@ -45,6 +45,9 @@ setOnEvent(evt => {
45
45
  // Runtime tab ingests ALL events regardless of active tab (for live history)
46
46
  Runtime.ingestEvent(evt);
47
47
 
48
+ // Neural Stream HUD ring buffer ingests ALL events too (board card auto-renders)
49
+ Board.pushNeuralEvent?.(evt);
50
+
48
51
  const tab = S.tab;
49
52
  if (tab === 'board') {
50
53
  Board.render();
@@ -82,6 +82,9 @@ export const S = {
82
82
  lastDecomposition: null,
83
83
  lastCompletion: null,
84
84
 
85
+ // Neural Stream HUD ring buffer (latest SSE events, restored from v3.8.0)
86
+ neuralStream: [],
87
+
85
88
  // Assets sub-tabs
86
89
  assetsSubTab: 'skills',
87
90
 
@@ -93,6 +93,7 @@ export async function load() {
93
93
  }
94
94
 
95
95
  export function render() {
96
+ renderConnectedEcosystem();
96
97
  renderPyramidWidget();
97
98
  renderHero();
98
99
  renderFirstRunHero();
@@ -101,6 +102,126 @@ export function render() {
101
102
  renderEvents();
102
103
  renderPulse();
103
104
  renderToolHealth();
105
+ renderNeuralStream();
106
+ }
107
+
108
+ /* ── Connected Ecosystem (per-client MCP status, restored from v3.8.0) ── */
109
+ const _CLIENT_LABEL = {
110
+ 'claude-code': 'Claude Code',
111
+ claude: 'Claude',
112
+ 'claude-desktop': 'Claude Desktop',
113
+ codex: 'Codex',
114
+ cursor: 'Cursor',
115
+ opencode: 'Opencode',
116
+ windsurf: 'Windsurf',
117
+ antigravity: 'Antigravity',
118
+ aider: 'Aider',
119
+ continue: 'Continue',
120
+ cline: 'Cline',
121
+ openclaw: 'OpenClaw',
122
+ mcp: 'MCP',
123
+ };
124
+ function _clientStateColor(state) {
125
+ const s = String(state || '').toLowerCase();
126
+ if (s === 'primaryactive' || s === 'active') return 'var(--accent-good, #4ade80)';
127
+ if (s === 'idle' || s === 'detected') return 'var(--accent-warn, #fbbf24)';
128
+ if (s === 'installed') return 'var(--accent, #60a5fa)';
129
+ return 'var(--muted, #6b7280)';
130
+ }
131
+ function renderConnectedEcosystem() {
132
+ const host = document.getElementById('connected-ecosystem');
133
+ if (!host) return;
134
+ const list = document.getElementById('ecosystem-list');
135
+ const stamp = document.getElementById('ecosystem-stamp');
136
+ const op = S.operateSurface;
137
+ const clients = (op?.clients?.detected || op?.clients || []);
138
+ const arr = Array.isArray(clients) ? clients : [];
139
+ if (arr.length === 0) {
140
+ host.style.display = 'none';
141
+ return;
142
+ }
143
+ host.style.display = 'block';
144
+ const primaryId = op?.clients?.primary?.clientId || op?.primaryClient?.clientId || null;
145
+ const ordered = [...arr].sort((a, b) => {
146
+ const ap = a.clientId === primaryId ? 0 : 1;
147
+ const bp = b.clientId === primaryId ? 0 : 1;
148
+ if (ap !== bp) return ap - bp;
149
+ return String(a.clientId || '').localeCompare(String(b.clientId || ''));
150
+ });
151
+ const html = ordered.slice(0, 12).map((c) => {
152
+ const id = String(c.clientId || c.id || 'unknown');
153
+ const label = _CLIENT_LABEL[id] || id;
154
+ const isPrimary = id === primaryId;
155
+ const stateRaw = isPrimary ? 'primaryActive' : (c.state || c.status || 'idle');
156
+ const color = _clientStateColor(stateRaw);
157
+ const stateLabel = isPrimary ? 'PRIMARY·ACTIVE' : String(stateRaw).toUpperCase();
158
+ const lastSeen = c.lastHeartbeatAt || c.lastSeenAt;
159
+ const ago = lastSeen ? timeAgo(lastSeen) : '';
160
+ return `<div style="display:flex;align-items:center;gap:8px;padding:6px 10px;border-radius:6px;background:var(--surface2);font-size:12px;border-left:3px solid ${color}">
161
+ <span style="width:6px;height:6px;border-radius:50%;background:${color};flex-shrink:0"></span>
162
+ <span style="font-family:var(--font-mono);font-weight:600;color:var(--fg1)">${esc(label)}</span>
163
+ <span style="font-size:10px;color:${color};font-family:var(--font-mono);text-transform:uppercase;letter-spacing:0.5px">${esc(stateLabel)}</span>
164
+ ${ago ? `<span style="margin-left:auto;font-size:10px;color:var(--muted)">${esc(ago)}</span>` : ''}
165
+ </div>`;
166
+ }).join('');
167
+ list.innerHTML = html;
168
+ if (stamp) stamp.textContent = `${ordered.length} client${ordered.length === 1 ? '' : 's'}`;
169
+ }
170
+
171
+ /* ── Neural Stream HUD (live SSE event feed strip, restored from v3.8.0) ── */
172
+ const _NEURAL_MAX = 12;
173
+ function _neuralColor(category) {
174
+ switch (String(category || '').toLowerCase()) {
175
+ case 'memory': return 'oklch(68% 0.22 295)';
176
+ case 'token':
177
+ case 'tokens': return 'oklch(87% 0.19 152)';
178
+ case 'orchestration':
179
+ case 'planner': return 'oklch(83% 0.14 220)';
180
+ case 'synapse':
181
+ case 'operative':
182
+ case 'mission': return 'oklch(83% 0.17 55)';
183
+ case 'architects':
184
+ case 'dispatch':
185
+ case 'sentinel': return 'oklch(75% 0.20 30)';
186
+ case 'darwin':
187
+ case 'governance': return 'oklch(78% 0.18 340)';
188
+ case 'graph': return 'oklch(80% 0.15 180)';
189
+ default: return 'var(--muted, #6b7280)';
190
+ }
191
+ }
192
+ export function pushNeuralEvent(evt) {
193
+ if (!evt || typeof evt !== 'object') return;
194
+ if (!Array.isArray(S.neuralStream)) S.neuralStream = [];
195
+ S.neuralStream.unshift({
196
+ type: String(evt.type || ''),
197
+ category: String(evt.category || ''),
198
+ time: Number(evt.time || Date.now()),
199
+ summary: typeof evt.summary === 'string' ? evt.summary : '',
200
+ });
201
+ if (S.neuralStream.length > _NEURAL_MAX) S.neuralStream.length = _NEURAL_MAX;
202
+ renderNeuralStream();
203
+ }
204
+ function renderNeuralStream() {
205
+ const host = document.getElementById('neural-stream-card');
206
+ if (!host) return;
207
+ const list = document.getElementById('neural-stream-list');
208
+ const count = document.getElementById('neural-stream-count');
209
+ const items = Array.isArray(S.neuralStream) ? S.neuralStream : [];
210
+ if (items.length === 0) {
211
+ host.style.display = 'none';
212
+ return;
213
+ }
214
+ host.style.display = 'block';
215
+ if (count) count.textContent = `${items.length} signal${items.length === 1 ? '' : 's'}`;
216
+ list.innerHTML = items.map((e) => {
217
+ const color = _neuralColor(e.category);
218
+ const ago = timeAgo(e.time);
219
+ return `<div style="display:flex;align-items:baseline;gap:8px;padding:3px 6px;border-left:2px solid ${color};font-size:11px;font-family:var(--font-mono);line-height:1.4">
220
+ <span style="color:${color};font-weight:600;min-width:90px">${esc(e.type)}</span>
221
+ <span style="color:var(--fg1);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${esc(e.summary || e.category || '')}</span>
222
+ <span style="color:var(--muted);font-size:10px;flex-shrink:0">${esc(ago)}</span>
223
+ </div>`;
224
+ }).join('');
104
225
  }
105
226
 
106
227
  /* ── Memory pyramid (above kanban) ── */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexus-prime",
3
- "version": "7.5.0",
3
+ "version": "7.6.0",
4
4
  "description": "Local-first MCP control plane for coding agents with bootstrap-orchestrate execution, memory fabric, token budgeting, and worktree-backed swarms",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",