synabun 2026.4.25-beta.2 → 2026.4.26-beta
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 +22 -1
- package/mcp-server/package.json +8 -2
- package/neural-interface/package.json +5 -1
- package/neural-interface/public/shared/cdx/cdx-panel.js +56 -48
- package/neural-interface/public/shared/cdx/cdx-styles.js +58 -26
- package/neural-interface/public/shared/cdx/cdx-tabs.js +3 -0
- package/neural-interface/public/shared/ocp/ocp-panel.js +59 -48
- package/neural-interface/public/shared/ocp/ocp-styles.js +55 -25
- package/neural-interface/public/shared/ui-claude-panel.js +114 -73
- package/neural-interface/server.js +3 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Raw internal changelogs are archived in [changelog/](changelog/).
|
|
4
4
|
|
|
5
|
-
## 2026-04-
|
|
5
|
+
## 2026-04-26.beta-1
|
|
6
6
|
|
|
7
7
|
### Added — Dedicated Schedules Studio
|
|
8
8
|
- **Standalone schedule window** — Added `neural-interface/public/shared/ui-schedules-studio.js` and initialized it from both `neural-interface/public/variant/2d/main.js` and `neural-interface/public/variant/3d/main.js`. The new window opens from `automations:open-schedules` and keeps schedule management out of the Automation Studio detail pane.
|
|
@@ -57,6 +57,27 @@ Raw internal changelogs are archived in [changelog/](changelog/).
|
|
|
57
57
|
- **Official install URLs** — `claude-code` → `https://docs.claude.com/en/docs/claude-code/setup`, `codex` → `https://developers.openai.com/codex/cli/`, `opencode` → `https://opencode.ai/docs/`. (`gemini` constants defined for future use; no Gemini sidepanel currently exists.)
|
|
58
58
|
- **Banner styles** — Added `.cp-cli-missing*`, `.cxp-cli-missing*`, `.ocp-cli-missing*` rule sets to `ui-claude-panel.js` style block, `cdx/cdx-styles.js`, and `ocp/ocp-styles.js`. Includes `.cp-cli-blocked` / `.cxp-cli-blocked` / `.ocp-cli-blocked` panel classes for the dimmed input state.
|
|
59
59
|
|
|
60
|
+
### Fixed — CLI-Not-Installed Banner Hidden After Errors / Broken-Binary Cases (Sidepanels)
|
|
61
|
+
- **Banner relocated to top of messages container** — Was rendered inside `.cxp-empty` / `.cp-empty` / `.ocp-empty`. Once any system message appeared (e.g. `Codex session closed` + `Codex app-server exited before responding to request 1`), the empty-state collapsed and took the banner with it. Banner is now an independent `:first-child` of `#cxp-messages-container`, `#cp-messages-container`, and `.ocp-messages-container` via `container.insertBefore(banner, container.firstChild)`. Stays visible across transcript states. New `ensureCliBannerEl()` / `removeCliBannerEl()` / `refreshCliBanner()` helpers replace the old empty-state-scoped `renderCliMissingBanner` / `refreshCliBanners` in `neural-interface/public/shared/cdx/cdx-panel.js`, `neural-interface/public/shared/ui-claude-panel.js`, and `neural-interface/public/shared/ocp/ocp-panel.js`.
|
|
62
|
+
- **Spawn-failure pattern detection** — Banner only triggered when `/api/system/tool-versions` reported `installed: null`. A broken-but-present binary (e.g. `codex 0.125.0` spawning then exiting immediately) reports a real version, so the banner never showed. Added a per-panel `_cliInstallFailureForced` flag flipped on by WS error patterns:
|
|
63
|
+
- **Codex** — `applySocketError` in `cdx/cdx-tabs.js` matches `/Codex CLI not found|ENOENT|app-server exited before responding|app-server spawn/i` → dynamic-imports `cdx-panel.js` and calls exported `flagCdxCliInstallFailure()`.
|
|
64
|
+
- **Claude** — `case 'error'` in WS handler matches `/Claude CLI not found|ENOENT/i` → `flagClaudeCliInstallFailure()`.
|
|
65
|
+
- **OpenCode** — `onError` callback matches `/opencode.*(not.*found|ENOENT|spawn)|opencode CLI/i` → `flagOcpCliInstallFailure()`.
|
|
66
|
+
- All three flags clear automatically when the `cli-status` subscriber callback later receives an installed value.
|
|
67
|
+
- **Send-button gate now reads forced flag** — Claude `send()`, Codex `sendPrompt()` (via `.cxp-cli-blocked` panel class read in `syncInputEnabled`), and OpenCode `syncSendButton` / `handleSend` all check `_cliInstalled === null || _cliInstallFailureForced` so a broken-binary install also blocks send.
|
|
68
|
+
- **Banner restyled as sticky alert** — New `.cxp-cli-banner*`, `.cp-cli-banner*`, `.ocp-cli-banner*` rule sets in `cdx/cdx-styles.js`, `ui-claude-panel.js` style block, and `ocp/ocp-styles.js`. Three-column grid (icon, text, actions), orange/amber theme, `flex-shrink: 0`, sits at top of the scroll region with `z-index: 5` so it stays above transcript content.
|
|
69
|
+
|
|
70
|
+
### Fixed — High Severity npm audit Alerts in Install CLI (Neural Interface)
|
|
71
|
+
- **Playwright SSL cert verification CVE** — `neural-interface/package.json` pinned `playwright` at `1.54.2`, vulnerable to GHSA-7mvr-c777-76hp (browser downloader skipped SSL cert authenticity check). Bumped to `^1.59.1` (non-major) — fixed in `<1.55.1`. `setup.js` install flow no longer prints the high severity warning after `Installing MCP Server dependencies`.
|
|
72
|
+
- **lodash code injection + protobufjs RCE via transitives** — `lodash@4.17.23` (pulled by `archiver` → `archiver-utils@5.0.2`) had high severity GHSA-r5fr-rjxr-66jc (`_.template` import key code injection) plus moderate prototype pollution. `protobufjs@7.5.4` (pulled by `@huggingface/transformers` → `onnxruntime-web@1.22.0-dev.20250409-89f8206ba4`) had critical GHSA-xq3m-2v4x-88gg (arbitrary code execution). Added `overrides` block in `neural-interface/package.json` pinning `lodash: ^4.18.1` and `protobufjs: ^7.5.5` — both within the parents' implicit ranges, no breaking change.
|
|
73
|
+
|
|
74
|
+
### Fixed — Moderate/High npm audit Alerts in Install CLI (MCP Server)
|
|
75
|
+
- **uuid buffer bounds check CVE** — `mcp-server/package.json` had `uuid@^11.1.0`, vulnerable to GHSA-w5hq-g745-h8pq (missing buffer bounds check in `v3`/`v5`/`v6` when `buf` provided), fixed in `<14.0.0`. Bumped to `^14.0.0` (semver major) — `v4` named export unchanged, `src/tools/remember.ts` and `src/tui.ts` compile cleanly with no source modifications.
|
|
76
|
+
- **hono / @hono/node-server / postcss / vite / protobufjs transitives** — multiple moderate-to-high CVEs in vitest's transitive `vite@8.0.0-8.0.4` (path traversal, `server.fs.deny` bypass, websocket arbitrary file read), `hono@<4.12.14` (cookie validation, path traversal in `toSSG()`, ipRestriction IPv4-mapped IPv6), `@hono/node-server@<1.19.13` (serveStatic repeated-slash bypass), `postcss@<8.5.10` (XSS via unescaped `</style>`), and `protobufjs@<7.5.5`. Added `overrides` block in `mcp-server/package.json` pinning `protobufjs: ^7.5.5`, `hono: ^4.12.14`, `@hono/node-server: ^1.19.13`, `postcss: ^8.5.10`, `vite: ^8.0.10` — patches only, no breaking semver bumps to direct deps.
|
|
77
|
+
|
|
78
|
+
### Changed — Removed Deprecated `@types/uuid` devDependency
|
|
79
|
+
- **uuid v14 ships own type defs** — removed `@types/uuid: ^10.0.0` from `mcp-server/package.json` devDependencies. npm warned the stub package as deprecated (`This is a stub types definition. uuid provides its own type definitions, so you do not need this installed`). `tsc` build verified — no missing-types errors.
|
|
80
|
+
|
|
60
81
|
## 2026-04-24
|
|
61
82
|
|
|
62
83
|
### Added — "Continue planning" in OpenCode, Codex, and Claude plan UIs
|
package/mcp-server/package.json
CHANGED
|
@@ -24,15 +24,21 @@
|
|
|
24
24
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
25
25
|
"chalk": "^5.6.2",
|
|
26
26
|
"express": "^4.21.0",
|
|
27
|
-
"uuid": "^
|
|
27
|
+
"uuid": "^14.0.0",
|
|
28
28
|
"zod": "^3.24.2"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@types/express": "^5.0.0",
|
|
32
32
|
"@types/node": "^22.12.0",
|
|
33
|
-
"@types/uuid": "^10.0.0",
|
|
34
33
|
"tsx": "^4.19.0",
|
|
35
34
|
"typescript": "^5.9.3",
|
|
36
35
|
"vitest": "^4.1.0"
|
|
36
|
+
},
|
|
37
|
+
"overrides": {
|
|
38
|
+
"protobufjs": "^7.5.5",
|
|
39
|
+
"hono": "^4.12.14",
|
|
40
|
+
"@hono/node-server": "^1.19.13",
|
|
41
|
+
"postcss": "^8.5.10",
|
|
42
|
+
"vite": "^8.0.10"
|
|
37
43
|
}
|
|
38
44
|
}
|
|
@@ -21,8 +21,12 @@
|
|
|
21
21
|
"express": "^4.21.2",
|
|
22
22
|
"node-html-markdown": "^2.0.0",
|
|
23
23
|
"node-pty": "^1.1.0",
|
|
24
|
-
"playwright": "1.
|
|
24
|
+
"playwright": "^1.59.1",
|
|
25
25
|
"undici": "^8.1.0",
|
|
26
26
|
"ws": "^8.19.0"
|
|
27
|
+
},
|
|
28
|
+
"overrides": {
|
|
29
|
+
"lodash": "^4.18.1",
|
|
30
|
+
"protobufjs": "^7.5.5"
|
|
27
31
|
}
|
|
28
32
|
}
|
|
@@ -394,71 +394,79 @@ function startupEmptyText() {
|
|
|
394
394
|
}
|
|
395
395
|
|
|
396
396
|
// ── CLI installation status (banner + send-block) ──
|
|
397
|
-
|
|
398
|
-
|
|
397
|
+
let _cliInstallFailureForced = false;
|
|
398
|
+
|
|
399
|
+
function ensureCliBannerEl() {
|
|
400
|
+
if (!_panel) return null;
|
|
401
|
+
const container = panelEl('#cxp-messages-container');
|
|
402
|
+
if (!container) return null;
|
|
403
|
+
let banner = container.querySelector(':scope > .cxp-cli-banner');
|
|
404
|
+
if (banner) return banner;
|
|
399
405
|
const label = getCliLabel('codex');
|
|
400
406
|
const cmd = getCliInstallCommand('codex');
|
|
401
407
|
const url = getCliDocUrl('codex');
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
<
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
btn
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
btn.textContent = 'Re-check';
|
|
426
|
-
}
|
|
408
|
+
banner = document.createElement('div');
|
|
409
|
+
banner.className = 'cxp-cli-banner';
|
|
410
|
+
banner.innerHTML = `
|
|
411
|
+
<div class="cxp-cli-banner-icon">!</div>
|
|
412
|
+
<div class="cxp-cli-banner-text">
|
|
413
|
+
<div class="cxp-cli-banner-title">${label} CLI not installed</div>
|
|
414
|
+
<div class="cxp-cli-banner-body">Run <code>${cmd}</code> or follow the install guide.</div>
|
|
415
|
+
</div>
|
|
416
|
+
<div class="cxp-cli-banner-actions">
|
|
417
|
+
<a class="cxp-cli-banner-link" href="${url}" target="_blank" rel="noopener noreferrer">Install guide</a>
|
|
418
|
+
<button class="cxp-cli-banner-recheck" type="button">Re-check</button>
|
|
419
|
+
</div>
|
|
420
|
+
`;
|
|
421
|
+
container.insertBefore(banner, container.firstChild);
|
|
422
|
+
const btn = banner.querySelector('.cxp-cli-banner-recheck');
|
|
423
|
+
btn?.addEventListener('click', async () => {
|
|
424
|
+
btn.disabled = true;
|
|
425
|
+
btn.textContent = 'Checking…';
|
|
426
|
+
try { await recheckCliStatus('codex'); }
|
|
427
|
+
finally {
|
|
428
|
+
if (btn.isConnected) {
|
|
429
|
+
btn.disabled = false;
|
|
430
|
+
btn.textContent = 'Re-check';
|
|
427
431
|
}
|
|
428
|
-
}
|
|
429
|
-
}
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
return banner;
|
|
430
435
|
}
|
|
431
436
|
|
|
432
|
-
function
|
|
437
|
+
function removeCliBannerEl() {
|
|
433
438
|
if (!_panel) return;
|
|
434
|
-
const
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
// Re-evaluate send button state with new CLI flag
|
|
439
|
+
const container = panelEl('#cxp-messages-container');
|
|
440
|
+
const banner = container?.querySelector(':scope > .cxp-cli-banner');
|
|
441
|
+
banner?.remove();
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function refreshCliBanner() {
|
|
445
|
+
if (!_panel) return;
|
|
446
|
+
const missing = _cliInstalled === null || _cliInstallFailureForced;
|
|
447
|
+
_panel.classList.toggle('cxp-cli-blocked', missing);
|
|
448
|
+
if (missing) ensureCliBannerEl();
|
|
449
|
+
else removeCliBannerEl();
|
|
448
450
|
try { syncInputEnabled(); } catch {}
|
|
449
451
|
}
|
|
450
452
|
|
|
451
453
|
export function isCdxCliInstalled() {
|
|
452
|
-
return _cliInstalled !== null;
|
|
454
|
+
return _cliInstalled !== null && !_cliInstallFailureForced;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
export function flagCdxCliInstallFailure() {
|
|
458
|
+
_cliInstallFailureForced = true;
|
|
459
|
+
refreshCliBanner();
|
|
460
|
+
recheckCliStatus('codex').catch(() => {});
|
|
453
461
|
}
|
|
454
462
|
|
|
455
463
|
function ensureCliSubscription() {
|
|
456
464
|
if (_cliUnsub) return;
|
|
457
465
|
_cliUnsub = subscribeCliStatus('codex', (info) => {
|
|
458
466
|
_cliInstalled = info?.installed || null;
|
|
459
|
-
|
|
467
|
+
if (_cliInstalled) _cliInstallFailureForced = false;
|
|
468
|
+
refreshCliBanner();
|
|
460
469
|
});
|
|
461
|
-
document.addEventListener('cdx-empty-rebuilt', () => refreshCliBanners());
|
|
462
470
|
}
|
|
463
471
|
|
|
464
472
|
// ═══════════════════════════════════════════
|
|
@@ -497,7 +505,7 @@ function setVisible(nextVisible) {
|
|
|
497
505
|
loadCurrentProfile();
|
|
498
506
|
loadRecallProfile();
|
|
499
507
|
panelEl('#cxp-input')?.focus();
|
|
500
|
-
|
|
508
|
+
refreshCliBanner();
|
|
501
509
|
} else {
|
|
502
510
|
document.removeEventListener('keydown', _cdxDocEscHandler, { capture: true });
|
|
503
511
|
}
|
|
@@ -984,36 +984,68 @@ export function injectStyles() {
|
|
|
984
984
|
.cxp-empty-logo svg { width: 32px; height: 32px; opacity: 0.25; }
|
|
985
985
|
.cxp-empty-name { font-size: 13px; font-weight: 500; color: rgba(255,255,255,0.35); letter-spacing: 0.02em; }
|
|
986
986
|
.cxp-empty-status { font-size: 11px; color: rgba(255,255,255,0.28); line-height: 1.5; min-height: 1em; }
|
|
987
|
-
.cxp-cli-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
987
|
+
.cxp-cli-banner {
|
|
988
|
+
flex-shrink: 0;
|
|
989
|
+
display: grid;
|
|
990
|
+
grid-template-columns: auto 1fr auto;
|
|
991
|
+
gap: 10px;
|
|
992
|
+
align-items: center;
|
|
993
|
+
margin: 8px 12px 0;
|
|
994
|
+
padding: 10px 12px;
|
|
995
|
+
border: 1px solid rgba(255,180,80,0.45);
|
|
996
|
+
border-radius: 8px;
|
|
997
|
+
background: rgba(255,180,80,0.08);
|
|
998
|
+
color: rgba(255,235,200,0.92);
|
|
999
|
+
font-size: 11px;
|
|
1000
|
+
line-height: 1.4;
|
|
1001
|
+
position: relative;
|
|
1002
|
+
z-index: 5;
|
|
1003
|
+
}
|
|
1004
|
+
.cxp-cli-banner-icon {
|
|
1005
|
+
width: 22px; height: 22px;
|
|
1006
|
+
display: flex; align-items: center; justify-content: center;
|
|
1007
|
+
border-radius: 50%;
|
|
1008
|
+
background: rgba(255,180,80,0.25);
|
|
1009
|
+
color: rgba(255,210,140,1);
|
|
1010
|
+
font-weight: 700;
|
|
1011
|
+
}
|
|
1012
|
+
.cxp-cli-banner-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
|
|
1013
|
+
.cxp-cli-banner-title {
|
|
1014
|
+
font-size: 12px; font-weight: 600;
|
|
1015
|
+
color: rgba(255,210,140,0.95);
|
|
1016
|
+
}
|
|
1017
|
+
.cxp-cli-banner-body {
|
|
1018
|
+
font-size: 11px;
|
|
1019
|
+
color: rgba(255,255,255,0.7);
|
|
1020
|
+
word-break: break-word;
|
|
1021
|
+
}
|
|
1022
|
+
.cxp-cli-banner-body code {
|
|
1023
|
+
padding: 1px 5px;
|
|
1024
|
+
background: rgba(0,0,0,0.35);
|
|
1025
|
+
border-radius: 3px;
|
|
1026
|
+
font-size: 10.5px;
|
|
1027
|
+
color: rgba(255,255,255,0.85);
|
|
1004
1028
|
}
|
|
1005
|
-
.cxp-cli-
|
|
1006
|
-
.cxp-cli-
|
|
1029
|
+
.cxp-cli-banner-actions { display: flex; gap: 6px; }
|
|
1030
|
+
.cxp-cli-banner-link, .cxp-cli-banner-recheck {
|
|
1007
1031
|
display: inline-flex; align-items: center; gap: 4px;
|
|
1008
|
-
padding:
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1032
|
+
padding: 4px 9px;
|
|
1033
|
+
font-size: 11px;
|
|
1034
|
+
font-weight: 500;
|
|
1035
|
+
border-radius: 4px;
|
|
1036
|
+
border: 1px solid rgba(255,255,255,0.2);
|
|
1037
|
+
background: rgba(255,255,255,0.05);
|
|
1038
|
+
color: rgba(255,255,255,0.9);
|
|
1039
|
+
text-decoration: none;
|
|
1040
|
+
cursor: pointer;
|
|
1041
|
+
user-select: none;
|
|
1042
|
+
font-family: inherit;
|
|
1012
1043
|
}
|
|
1013
|
-
.cxp-cli-
|
|
1014
|
-
background: rgba(255,255,255,0.
|
|
1044
|
+
.cxp-cli-banner-link:hover, .cxp-cli-banner-recheck:hover {
|
|
1045
|
+
background: rgba(255,255,255,0.12);
|
|
1046
|
+
border-color: rgba(255,255,255,0.4);
|
|
1015
1047
|
}
|
|
1016
|
-
.cxp-cli-
|
|
1048
|
+
.cxp-cli-banner-recheck:disabled { opacity: 0.5; cursor: default; }
|
|
1017
1049
|
.codex-panel.cxp-cli-blocked .cxp-send { opacity: 0.4 !important; cursor: not-allowed !important; }
|
|
1018
1050
|
.codex-panel.cxp-cli-blocked .cxp-input { opacity: 0.7; }
|
|
1019
1051
|
.cxp-system {
|
|
@@ -552,6 +552,9 @@ function applySocketError(msg) {
|
|
|
552
552
|
if (_compacting) setCompactingUI(false);
|
|
553
553
|
appendSystem(text, 'error');
|
|
554
554
|
setStatus(text, 'error');
|
|
555
|
+
if (/Codex CLI not found|ENOENT|app-server exited before responding|app-server spawn/i.test(text)) {
|
|
556
|
+
import('./cdx-panel.js').then((m) => m.flagCdxCliInstallFailure?.()).catch(() => {});
|
|
557
|
+
}
|
|
555
558
|
if (msg.requestId) {
|
|
556
559
|
if (_sessionListRequest?.requestId === msg.requestId) {
|
|
557
560
|
_sessionListRequest.reject?.(new Error(text));
|
|
@@ -910,7 +910,7 @@ async function handleSend() {
|
|
|
910
910
|
if (!input) return;
|
|
911
911
|
const tab = activeTab();
|
|
912
912
|
if (!tab) return;
|
|
913
|
-
if (_cliInstalled === null) {
|
|
913
|
+
if (_cliInstalled === null || _cliInstallFailureForced) {
|
|
914
914
|
setStatus('offline', 'OpenCode CLI not installed. Install it first, then click Re-check.');
|
|
915
915
|
return;
|
|
916
916
|
}
|
|
@@ -1013,7 +1013,7 @@ function syncSendButton() {
|
|
|
1013
1013
|
const tab = activeTab();
|
|
1014
1014
|
if (!sendBtn) return;
|
|
1015
1015
|
|
|
1016
|
-
const cliBlocked = _cliInstalled === null;
|
|
1016
|
+
const cliBlocked = _cliInstalled === null || _cliInstallFailureForced;
|
|
1017
1017
|
|
|
1018
1018
|
if (cliBlocked) {
|
|
1019
1019
|
sendBtn.classList.remove('running');
|
|
@@ -1521,66 +1521,74 @@ function _ocpDocEscHandler(event) {
|
|
|
1521
1521
|
}
|
|
1522
1522
|
|
|
1523
1523
|
// ── CLI installation status (banner + send-block) ──
|
|
1524
|
-
|
|
1525
|
-
|
|
1524
|
+
let _cliInstallFailureForced = false;
|
|
1525
|
+
|
|
1526
|
+
function ensureCliBannerEl() {
|
|
1527
|
+
if (!_panel) return null;
|
|
1528
|
+
const container = _panel.querySelector('.ocp-messages-container');
|
|
1529
|
+
if (!container) return null;
|
|
1530
|
+
let banner = container.querySelector(':scope > .ocp-cli-banner');
|
|
1531
|
+
if (banner) return banner;
|
|
1526
1532
|
const label = getCliLabel('opencode');
|
|
1527
1533
|
const cmd = getCliInstallCommand('opencode');
|
|
1528
1534
|
const url = getCliDocUrl('opencode');
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
<
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
btn
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
btn.textContent = 'Re-check';
|
|
1553
|
-
}
|
|
1535
|
+
banner = document.createElement('div');
|
|
1536
|
+
banner.className = 'ocp-cli-banner';
|
|
1537
|
+
banner.innerHTML = `
|
|
1538
|
+
<div class="ocp-cli-banner-icon">!</div>
|
|
1539
|
+
<div class="ocp-cli-banner-text">
|
|
1540
|
+
<div class="ocp-cli-banner-title">${label} CLI not installed</div>
|
|
1541
|
+
<div class="ocp-cli-banner-body">Run <code>${cmd}</code> or follow the install guide.</div>
|
|
1542
|
+
</div>
|
|
1543
|
+
<div class="ocp-cli-banner-actions">
|
|
1544
|
+
<a class="ocp-cli-banner-link" href="${url}" target="_blank" rel="noopener noreferrer">Install guide</a>
|
|
1545
|
+
<button class="ocp-cli-banner-recheck" type="button">Re-check</button>
|
|
1546
|
+
</div>
|
|
1547
|
+
`;
|
|
1548
|
+
container.insertBefore(banner, container.firstChild);
|
|
1549
|
+
const btn = banner.querySelector('.ocp-cli-banner-recheck');
|
|
1550
|
+
btn?.addEventListener('click', async () => {
|
|
1551
|
+
btn.disabled = true;
|
|
1552
|
+
btn.textContent = 'Checking…';
|
|
1553
|
+
try { await recheckCliStatus('opencode'); }
|
|
1554
|
+
finally {
|
|
1555
|
+
if (btn.isConnected) {
|
|
1556
|
+
btn.disabled = false;
|
|
1557
|
+
btn.textContent = 'Re-check';
|
|
1554
1558
|
}
|
|
1555
|
-
}
|
|
1556
|
-
}
|
|
1559
|
+
}
|
|
1560
|
+
});
|
|
1561
|
+
return banner;
|
|
1557
1562
|
}
|
|
1558
1563
|
|
|
1559
|
-
function
|
|
1564
|
+
function removeCliBannerEl() {
|
|
1560
1565
|
if (!_panel) return;
|
|
1561
|
-
const
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
if (hintEl) hintEl.style.display = '';
|
|
1572
|
-
}
|
|
1573
|
-
}
|
|
1566
|
+
const banner = _panel.querySelector('.ocp-messages-container > .ocp-cli-banner');
|
|
1567
|
+
banner?.remove();
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
function refreshCliBanner() {
|
|
1571
|
+
if (!_panel) return;
|
|
1572
|
+
const missing = _cliInstalled === null || _cliInstallFailureForced;
|
|
1573
|
+
_panel.classList.toggle('ocp-cli-blocked', missing);
|
|
1574
|
+
if (missing) ensureCliBannerEl();
|
|
1575
|
+
else removeCliBannerEl();
|
|
1574
1576
|
syncSendButton();
|
|
1575
1577
|
}
|
|
1576
1578
|
|
|
1579
|
+
export function flagOcpCliInstallFailure() {
|
|
1580
|
+
_cliInstallFailureForced = true;
|
|
1581
|
+
refreshCliBanner();
|
|
1582
|
+
recheckCliStatus('opencode').catch(() => {});
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1577
1585
|
function ensureCliSubscription() {
|
|
1578
1586
|
if (_cliUnsub) return;
|
|
1579
1587
|
_cliUnsub = subscribeCliStatus('opencode', (info) => {
|
|
1580
1588
|
_cliInstalled = info?.installed || null;
|
|
1581
|
-
|
|
1589
|
+
if (_cliInstalled) _cliInstallFailureForced = false;
|
|
1590
|
+
refreshCliBanner();
|
|
1582
1591
|
});
|
|
1583
|
-
document.addEventListener('ocp-empty-rebuilt', () => refreshCliBanners());
|
|
1584
1592
|
}
|
|
1585
1593
|
|
|
1586
1594
|
function setVisible(nextVisible) {
|
|
@@ -1608,7 +1616,7 @@ function setVisible(nextVisible) {
|
|
|
1608
1616
|
if (_curProject) loadBranches(_curProject);
|
|
1609
1617
|
loadCurrentProfile();
|
|
1610
1618
|
loadRecallProfile();
|
|
1611
|
-
|
|
1619
|
+
refreshCliBanner();
|
|
1612
1620
|
} else {
|
|
1613
1621
|
document.removeEventListener('keydown', _ocpDocEscHandler, { capture: true });
|
|
1614
1622
|
}
|
|
@@ -1664,6 +1672,9 @@ function wsCallbacks() {
|
|
|
1664
1672
|
},
|
|
1665
1673
|
onError(msg) {
|
|
1666
1674
|
console.error('[ocp] Error:', msg.message);
|
|
1675
|
+
if (typeof msg.message === 'string' && /opencode.*(not.*found|ENOENT|spawn)|opencode CLI/i.test(msg.message)) {
|
|
1676
|
+
flagOcpCliInstallFailure();
|
|
1677
|
+
}
|
|
1667
1678
|
},
|
|
1668
1679
|
};
|
|
1669
1680
|
}
|
|
@@ -1521,38 +1521,68 @@ export function injectStyles() {
|
|
|
1521
1521
|
stroke: currentColor; fill: none;
|
|
1522
1522
|
stroke-width: 1; opacity: 0.3;
|
|
1523
1523
|
}
|
|
1524
|
-
.ocp-cli-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1524
|
+
.ocp-cli-banner {
|
|
1525
|
+
flex-shrink: 0;
|
|
1526
|
+
display: grid;
|
|
1527
|
+
grid-template-columns: auto 1fr auto;
|
|
1528
|
+
gap: 10px;
|
|
1529
|
+
align-items: center;
|
|
1530
|
+
margin: 8px 12px 0;
|
|
1531
|
+
padding: 10px 12px;
|
|
1532
|
+
border: 1px solid rgba(255,180,80,0.45);
|
|
1533
|
+
border-radius: 8px;
|
|
1534
|
+
background: rgba(255,180,80,0.08);
|
|
1535
|
+
color: rgba(255,235,200,0.92);
|
|
1536
|
+
font-size: 11px;
|
|
1537
|
+
line-height: 1.4;
|
|
1538
|
+
position: relative;
|
|
1539
|
+
z-index: 5;
|
|
1534
1540
|
}
|
|
1535
|
-
.ocp-cli-
|
|
1536
|
-
|
|
1541
|
+
.ocp-cli-banner-icon {
|
|
1542
|
+
width: 22px; height: 22px;
|
|
1543
|
+
display: flex; align-items: center; justify-content: center;
|
|
1544
|
+
border-radius: 50%;
|
|
1545
|
+
background: rgba(255,180,80,0.25);
|
|
1546
|
+
color: rgba(255,210,140,1);
|
|
1547
|
+
font-weight: 700;
|
|
1537
1548
|
}
|
|
1538
|
-
.ocp-cli-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
color: rgba(255,
|
|
1549
|
+
.ocp-cli-banner-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
|
|
1550
|
+
.ocp-cli-banner-title {
|
|
1551
|
+
font-size: 12px; font-weight: 600;
|
|
1552
|
+
color: rgba(255,210,140,0.95);
|
|
1542
1553
|
}
|
|
1543
|
-
.ocp-cli-
|
|
1544
|
-
|
|
1554
|
+
.ocp-cli-banner-body {
|
|
1555
|
+
font-size: 11px;
|
|
1556
|
+
color: rgba(255,255,255,0.7);
|
|
1557
|
+
word-break: break-word;
|
|
1558
|
+
}
|
|
1559
|
+
.ocp-cli-banner-body code {
|
|
1560
|
+
padding: 1px 5px;
|
|
1561
|
+
background: rgba(0,0,0,0.35);
|
|
1562
|
+
border-radius: 3px;
|
|
1563
|
+
font-size: 10.5px;
|
|
1564
|
+
color: rgba(255,255,255,0.85);
|
|
1565
|
+
}
|
|
1566
|
+
.ocp-cli-banner-actions { display: flex; gap: 6px; }
|
|
1567
|
+
.ocp-cli-banner-link, .ocp-cli-banner-recheck {
|
|
1545
1568
|
display: inline-flex; align-items: center; gap: 4px;
|
|
1546
|
-
padding:
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1569
|
+
padding: 4px 9px;
|
|
1570
|
+
font-size: 11px;
|
|
1571
|
+
font-weight: 500;
|
|
1572
|
+
border-radius: 4px;
|
|
1573
|
+
border: 1px solid rgba(255,255,255,0.2);
|
|
1574
|
+
background: rgba(255,255,255,0.05);
|
|
1575
|
+
color: rgba(255,255,255,0.9);
|
|
1576
|
+
text-decoration: none;
|
|
1577
|
+
cursor: pointer;
|
|
1578
|
+
user-select: none;
|
|
1550
1579
|
font-family: inherit;
|
|
1551
1580
|
}
|
|
1552
|
-
.ocp-cli-
|
|
1553
|
-
background: rgba(255,255,255,0.
|
|
1581
|
+
.ocp-cli-banner-link:hover, .ocp-cli-banner-recheck:hover {
|
|
1582
|
+
background: rgba(255,255,255,0.12);
|
|
1583
|
+
border-color: rgba(255,255,255,0.4);
|
|
1554
1584
|
}
|
|
1555
|
-
.ocp-cli-
|
|
1585
|
+
.ocp-cli-banner-recheck:disabled { opacity: 0.5; cursor: default; }
|
|
1556
1586
|
.ocp-panel.ocp-cli-blocked .ocp-input { opacity: 0.7; }
|
|
1557
1587
|
|
|
1558
1588
|
/* ── Thinking indicator ── */
|
|
@@ -432,38 +432,70 @@ function injectStyles() {
|
|
|
432
432
|
.cp-messages:has(.msg) .cp-empty,
|
|
433
433
|
.cp-messages:has(.msg-status) .cp-empty { display: none; }
|
|
434
434
|
|
|
435
|
-
/* ── CLI-not-installed banner (
|
|
436
|
-
.cp-cli-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
435
|
+
/* ── CLI-not-installed banner (sticky top of messages container) ── */
|
|
436
|
+
.cp-cli-banner {
|
|
437
|
+
flex-shrink: 0;
|
|
438
|
+
display: grid;
|
|
439
|
+
grid-template-columns: auto 1fr auto;
|
|
440
|
+
gap: 10px;
|
|
441
|
+
align-items: center;
|
|
442
|
+
margin: 8px 12px 0;
|
|
443
|
+
padding: 10px 12px;
|
|
444
|
+
border: 1px solid rgba(255,180,80,0.45);
|
|
445
|
+
border-radius: 8px;
|
|
446
|
+
background: rgba(255,180,80,0.08);
|
|
447
|
+
color: rgba(255,235,200,0.92);
|
|
448
|
+
font-size: 11px;
|
|
449
|
+
line-height: 1.4;
|
|
450
|
+
pointer-events: auto;
|
|
451
|
+
position: relative;
|
|
452
|
+
z-index: 5;
|
|
445
453
|
}
|
|
446
|
-
.cp-cli-
|
|
447
|
-
|
|
454
|
+
.cp-cli-banner-icon {
|
|
455
|
+
width: 22px; height: 22px;
|
|
456
|
+
display: flex; align-items: center; justify-content: center;
|
|
457
|
+
border-radius: 50%;
|
|
458
|
+
background: rgba(255,180,80,0.25);
|
|
459
|
+
color: rgba(255,210,140,1);
|
|
460
|
+
font-weight: 700;
|
|
448
461
|
}
|
|
449
|
-
.cp-cli-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
color: rgba(255,
|
|
462
|
+
.cp-cli-banner-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
|
|
463
|
+
.cp-cli-banner-title {
|
|
464
|
+
font-size: 12px; font-weight: 600;
|
|
465
|
+
color: rgba(255,210,140,0.95);
|
|
453
466
|
}
|
|
454
|
-
.cp-cli-
|
|
455
|
-
|
|
467
|
+
.cp-cli-banner-body {
|
|
468
|
+
font-size: 11px;
|
|
469
|
+
color: rgba(255,255,255,0.7);
|
|
470
|
+
word-break: break-word;
|
|
471
|
+
}
|
|
472
|
+
.cp-cli-banner-body code {
|
|
473
|
+
padding: 1px 5px;
|
|
474
|
+
background: rgba(0,0,0,0.35);
|
|
475
|
+
border-radius: 3px;
|
|
476
|
+
font-size: 10.5px;
|
|
477
|
+
color: rgba(255,255,255,0.85);
|
|
478
|
+
}
|
|
479
|
+
.cp-cli-banner-actions { display: flex; gap: 6px; }
|
|
480
|
+
.cp-cli-banner-link, .cp-cli-banner-recheck {
|
|
456
481
|
display: inline-flex; align-items: center; gap: 4px;
|
|
457
|
-
padding:
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
482
|
+
padding: 4px 9px;
|
|
483
|
+
font-size: 11px;
|
|
484
|
+
font-weight: 500;
|
|
485
|
+
border-radius: 4px;
|
|
486
|
+
border: 1px solid rgba(255,255,255,0.2);
|
|
487
|
+
background: rgba(255,255,255,0.05);
|
|
488
|
+
color: rgba(255,255,255,0.9);
|
|
489
|
+
text-decoration: none;
|
|
490
|
+
cursor: pointer;
|
|
491
|
+
user-select: none;
|
|
492
|
+
font-family: inherit;
|
|
461
493
|
}
|
|
462
|
-
.cp-cli-
|
|
463
|
-
background: rgba(255,255,255,0.
|
|
494
|
+
.cp-cli-banner-link:hover, .cp-cli-banner-recheck:hover {
|
|
495
|
+
background: rgba(255,255,255,0.12);
|
|
496
|
+
border-color: rgba(255,255,255,0.4);
|
|
464
497
|
}
|
|
465
|
-
.cp-cli-
|
|
466
|
-
/* Block send button visually + functionally when CLI missing */
|
|
498
|
+
.cp-cli-banner-recheck:disabled { opacity: 0.5; cursor: default; }
|
|
467
499
|
.claude-panel.cp-cli-blocked .cp-send { opacity: 0.4 !important; cursor: not-allowed !important; }
|
|
468
500
|
.claude-panel.cp-cli-blocked .cp-input { opacity: 0.7; }
|
|
469
501
|
|
|
@@ -2752,7 +2784,6 @@ function createTab(sessionId = null, label = 'New chat', { autoSwitch = true, id
|
|
|
2752
2784
|
msgDiv.innerHTML = `<div class="cp-empty"><div class="cp-empty-logo">${CLAUDE_ICON}</div><span class="cp-empty-name">Claude</span><span class="cp-empty-hint">Send a message to start</span></div>`;
|
|
2753
2785
|
container.appendChild(msgDiv);
|
|
2754
2786
|
tab.messagesEl = msgDiv;
|
|
2755
|
-
if (_cliInstalled === null) _renderCliMissingBanner(msgDiv.querySelector('.cp-empty'));
|
|
2756
2787
|
// Connect WebSocket
|
|
2757
2788
|
connectTab(tab);
|
|
2758
2789
|
// Create pill (rendered in shared tray, not inside panel)
|
|
@@ -3576,7 +3607,6 @@ async function selectSession(sid, label) {
|
|
|
3576
3607
|
const btn = _panel?.querySelector('.cp-session-label');
|
|
3577
3608
|
if (btn) btn.textContent = tab.label;
|
|
3578
3609
|
tab.messagesEl.innerHTML = `<div class="cp-empty"><div class="cp-empty-logo">${CLAUDE_ICON}</div><span class="cp-empty-name">Claude</span><span class="cp-empty-hint">Send a message to start</span></div>`;
|
|
3579
|
-
if (_cliInstalled === null) _renderCliMissingBanner(tab.messagesEl.querySelector('.cp-empty'));
|
|
3580
3610
|
if (sid) {
|
|
3581
3611
|
loadSessionHistory(sid, tab.messagesEl);
|
|
3582
3612
|
// Restore session cost from server
|
|
@@ -4371,6 +4401,9 @@ function _processTabMsg(tab, msg) {
|
|
|
4371
4401
|
if (tab.compacting) { tab.compacting = false; if (tab === activeTab()) _setCompactingUI(false); }
|
|
4372
4402
|
appendError(tab, msg.message);
|
|
4373
4403
|
notify('panel', NOTIF_TYPE.ERROR, tab.label || 'Claude Code', { tabId: tab.id });
|
|
4404
|
+
if (typeof msg.message === 'string' && /Claude CLI not found|ENOENT/i.test(msg.message)) {
|
|
4405
|
+
flagClaudeCliInstallFailure();
|
|
4406
|
+
}
|
|
4374
4407
|
break;
|
|
4375
4408
|
}
|
|
4376
4409
|
}
|
|
@@ -6586,7 +6619,7 @@ function runSlashCommand(tab, raw) {
|
|
|
6586
6619
|
}
|
|
6587
6620
|
|
|
6588
6621
|
function send({ shift = false } = {}) {
|
|
6589
|
-
if (_cliInstalled === null) {
|
|
6622
|
+
if (_cliInstalled === null || _cliInstallFailureForced) {
|
|
6590
6623
|
notify({ type: NOTIF_TYPE.WARN, title: 'Claude CLI not installed', body: 'Install the Claude Code CLI before sending a message.' });
|
|
6591
6624
|
return;
|
|
6592
6625
|
}
|
|
@@ -7293,65 +7326,73 @@ function syncReservedWidth() {
|
|
|
7293
7326
|
}
|
|
7294
7327
|
|
|
7295
7328
|
// ── CLI installation status (banner + send-block) ──
|
|
7296
|
-
|
|
7297
|
-
|
|
7329
|
+
let _cliInstallFailureForced = false;
|
|
7330
|
+
|
|
7331
|
+
function _ensureCliBannerEl() {
|
|
7332
|
+
if (!_panel) return null;
|
|
7333
|
+
const container = _panel.querySelector('#cp-messages-container');
|
|
7334
|
+
if (!container) return null;
|
|
7335
|
+
let banner = container.querySelector(':scope > .cp-cli-banner');
|
|
7336
|
+
if (banner) return banner;
|
|
7298
7337
|
const label = getCliLabel('claude-code');
|
|
7299
7338
|
const cmd = getCliInstallCommand('claude-code');
|
|
7300
7339
|
const url = getCliDocUrl('claude-code');
|
|
7301
|
-
|
|
7302
|
-
|
|
7303
|
-
|
|
7304
|
-
|
|
7305
|
-
|
|
7306
|
-
|
|
7307
|
-
|
|
7308
|
-
|
|
7309
|
-
|
|
7310
|
-
<
|
|
7311
|
-
|
|
7312
|
-
|
|
7313
|
-
|
|
7314
|
-
|
|
7315
|
-
|
|
7316
|
-
|
|
7317
|
-
btn
|
|
7318
|
-
|
|
7319
|
-
|
|
7320
|
-
|
|
7321
|
-
|
|
7322
|
-
|
|
7323
|
-
|
|
7324
|
-
btn.textContent = 'Re-check';
|
|
7325
|
-
}
|
|
7340
|
+
banner = document.createElement('div');
|
|
7341
|
+
banner.className = 'cp-cli-banner';
|
|
7342
|
+
banner.innerHTML = `
|
|
7343
|
+
<div class="cp-cli-banner-icon">!</div>
|
|
7344
|
+
<div class="cp-cli-banner-text">
|
|
7345
|
+
<div class="cp-cli-banner-title">${label} CLI not installed</div>
|
|
7346
|
+
<div class="cp-cli-banner-body">Run <code>${cmd}</code> or follow the install guide.</div>
|
|
7347
|
+
</div>
|
|
7348
|
+
<div class="cp-cli-banner-actions">
|
|
7349
|
+
<a class="cp-cli-banner-link" href="${url}" target="_blank" rel="noopener noreferrer">Install guide</a>
|
|
7350
|
+
<button class="cp-cli-banner-recheck" type="button">Re-check</button>
|
|
7351
|
+
</div>
|
|
7352
|
+
`;
|
|
7353
|
+
container.insertBefore(banner, container.firstChild);
|
|
7354
|
+
const btn = banner.querySelector('.cp-cli-banner-recheck');
|
|
7355
|
+
btn?.addEventListener('click', async () => {
|
|
7356
|
+
btn.disabled = true;
|
|
7357
|
+
btn.textContent = 'Checking…';
|
|
7358
|
+
try { await recheckCliStatus('claude-code'); }
|
|
7359
|
+
finally {
|
|
7360
|
+
if (btn.isConnected) {
|
|
7361
|
+
btn.disabled = false;
|
|
7362
|
+
btn.textContent = 'Re-check';
|
|
7326
7363
|
}
|
|
7327
|
-
}
|
|
7328
|
-
}
|
|
7364
|
+
}
|
|
7365
|
+
});
|
|
7366
|
+
return banner;
|
|
7367
|
+
}
|
|
7368
|
+
|
|
7369
|
+
function _removeCliBannerEl() {
|
|
7370
|
+
if (!_panel) return;
|
|
7371
|
+
const banner = _panel.querySelector('#cp-messages-container > .cp-cli-banner');
|
|
7372
|
+
banner?.remove();
|
|
7329
7373
|
}
|
|
7330
7374
|
|
|
7331
7375
|
function _refreshCliBanners() {
|
|
7332
7376
|
if (!_panel) return;
|
|
7333
|
-
const
|
|
7334
|
-
|
|
7335
|
-
|
|
7336
|
-
|
|
7337
|
-
} else {
|
|
7338
|
-
_panel.classList.remove('cp-cli-blocked');
|
|
7339
|
-
for (const el of empties) {
|
|
7340
|
-
const banner = el.querySelector('.cp-cli-missing');
|
|
7341
|
-
if (banner) banner.remove();
|
|
7342
|
-
const hint = el.querySelector('.cp-empty-hint');
|
|
7343
|
-
if (hint) hint.style.display = '';
|
|
7344
|
-
}
|
|
7345
|
-
}
|
|
7346
|
-
// Re-sync send button state — installed transition should re-enable input drafts
|
|
7377
|
+
const missing = _cliInstalled === null || _cliInstallFailureForced;
|
|
7378
|
+
_panel.classList.toggle('cp-cli-blocked', missing);
|
|
7379
|
+
if (missing) _ensureCliBannerEl();
|
|
7380
|
+
else _removeCliBannerEl();
|
|
7347
7381
|
const $send = _panel.querySelector('#cp-send');
|
|
7348
|
-
if ($send &&
|
|
7382
|
+
if ($send && missing) $send.disabled = true;
|
|
7383
|
+
}
|
|
7384
|
+
|
|
7385
|
+
export function flagClaudeCliInstallFailure() {
|
|
7386
|
+
_cliInstallFailureForced = true;
|
|
7387
|
+
_refreshCliBanners();
|
|
7388
|
+
recheckCliStatus('claude-code').catch(() => {});
|
|
7349
7389
|
}
|
|
7350
7390
|
|
|
7351
7391
|
function _ensureCliSubscription() {
|
|
7352
7392
|
if (_cliUnsub) return;
|
|
7353
7393
|
_cliUnsub = subscribeCliStatus('claude-code', (info) => {
|
|
7354
7394
|
_cliInstalled = info?.installed || null;
|
|
7395
|
+
if (_cliInstalled) _cliInstallFailureForced = false;
|
|
7355
7396
|
_refreshCliBanners();
|
|
7356
7397
|
});
|
|
7357
7398
|
}
|
|
@@ -10094,11 +10094,9 @@ async function checkNpmUpdate() {
|
|
|
10094
10094
|
const data = await res.json();
|
|
10095
10095
|
const latest = data.version;
|
|
10096
10096
|
|
|
10097
|
-
const
|
|
10098
|
-
const
|
|
10099
|
-
const updateAvailable =
|
|
10100
|
-
|| (lat[0] === cur[0] && lat[1] > cur[1])
|
|
10101
|
-
|| (lat[0] === cur[0] && lat[1] === cur[1] && lat[2] > cur[2]);
|
|
10097
|
+
const cleanCur = parseVersion(current) || current;
|
|
10098
|
+
const cleanLat = parseVersion(latest) || latest;
|
|
10099
|
+
const updateAvailable = semverNewer(cleanCur, cleanLat);
|
|
10102
10100
|
|
|
10103
10101
|
_npmUpdateCache = { current, latest, updateAvailable, checkedAt: new Date().toISOString() };
|
|
10104
10102
|
|