synabun 2026.4.26-1 → 2026.4.26-2
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/neural-interface/public/shared/cdx/cdx-panel.js +4 -4
- package/neural-interface/public/shared/cdx/cdx-styles.js +1 -1
- package/neural-interface/public/shared/ocp/ocp-panel.js +5 -4
- package/neural-interface/public/shared/ocp/ocp-styles.js +1 -1
- package/neural-interface/public/shared/styles.css +43 -7
- package/neural-interface/public/shared/ui-claude-panel.js +6 -5
- package/neural-interface/public/shared/ui-menubar.js +40 -0
- package/neural-interface/public/shared/ui-update.js +188 -13
- package/neural-interface/server.js +715 -11
- package/package.json +4 -1
- package/postinstall.js +20 -0
- package/preuninstall.js +140 -6
- package/updater.cmd +31 -0
- package/updater.mjs +245 -0
- package/updater.sh +29 -0
|
@@ -399,8 +399,8 @@ let _cliInstallFailureForced = false;
|
|
|
399
399
|
function ensureCliBannerEl() {
|
|
400
400
|
if (!_panel) return null;
|
|
401
401
|
const container = panelEl('#cxp-messages-container');
|
|
402
|
-
if (!container) return null;
|
|
403
|
-
let banner = container.querySelector(':scope > .cxp-cli-banner');
|
|
402
|
+
if (!container || !container.parentNode) return null;
|
|
403
|
+
let banner = container.parentNode.querySelector(':scope > .cxp-cli-banner');
|
|
404
404
|
if (banner) return banner;
|
|
405
405
|
const label = getCliLabel('codex');
|
|
406
406
|
const cmd = getCliInstallCommand('codex');
|
|
@@ -418,7 +418,7 @@ function ensureCliBannerEl() {
|
|
|
418
418
|
<button class="cxp-cli-banner-recheck" type="button">Re-check</button>
|
|
419
419
|
</div>
|
|
420
420
|
`;
|
|
421
|
-
container.insertBefore(banner, container
|
|
421
|
+
container.parentNode.insertBefore(banner, container);
|
|
422
422
|
const btn = banner.querySelector('.cxp-cli-banner-recheck');
|
|
423
423
|
btn?.addEventListener('click', async () => {
|
|
424
424
|
btn.disabled = true;
|
|
@@ -437,7 +437,7 @@ function ensureCliBannerEl() {
|
|
|
437
437
|
function removeCliBannerEl() {
|
|
438
438
|
if (!_panel) return;
|
|
439
439
|
const container = panelEl('#cxp-messages-container');
|
|
440
|
-
const banner = container?.querySelector(':scope > .cxp-cli-banner');
|
|
440
|
+
const banner = container?.parentNode?.querySelector(':scope > .cxp-cli-banner');
|
|
441
441
|
banner?.remove();
|
|
442
442
|
}
|
|
443
443
|
|
|
@@ -990,7 +990,7 @@ export function injectStyles() {
|
|
|
990
990
|
grid-template-columns: auto 1fr auto;
|
|
991
991
|
gap: 10px;
|
|
992
992
|
align-items: center;
|
|
993
|
-
margin: 8px
|
|
993
|
+
margin: 6px 8px 0;
|
|
994
994
|
padding: 10px 12px;
|
|
995
995
|
border: 1px solid rgba(255,180,80,0.45);
|
|
996
996
|
border-radius: 8px;
|
|
@@ -1526,8 +1526,8 @@ let _cliInstallFailureForced = false;
|
|
|
1526
1526
|
function ensureCliBannerEl() {
|
|
1527
1527
|
if (!_panel) return null;
|
|
1528
1528
|
const container = _panel.querySelector('.ocp-messages-container');
|
|
1529
|
-
if (!container) return null;
|
|
1530
|
-
let banner = container.querySelector(':scope > .ocp-cli-banner');
|
|
1529
|
+
if (!container || !container.parentNode) return null;
|
|
1530
|
+
let banner = container.parentNode.querySelector(':scope > .ocp-cli-banner');
|
|
1531
1531
|
if (banner) return banner;
|
|
1532
1532
|
const label = getCliLabel('opencode');
|
|
1533
1533
|
const cmd = getCliInstallCommand('opencode');
|
|
@@ -1545,7 +1545,7 @@ function ensureCliBannerEl() {
|
|
|
1545
1545
|
<button class="ocp-cli-banner-recheck" type="button">Re-check</button>
|
|
1546
1546
|
</div>
|
|
1547
1547
|
`;
|
|
1548
|
-
container.insertBefore(banner, container
|
|
1548
|
+
container.parentNode.insertBefore(banner, container);
|
|
1549
1549
|
const btn = banner.querySelector('.ocp-cli-banner-recheck');
|
|
1550
1550
|
btn?.addEventListener('click', async () => {
|
|
1551
1551
|
btn.disabled = true;
|
|
@@ -1563,7 +1563,8 @@ function ensureCliBannerEl() {
|
|
|
1563
1563
|
|
|
1564
1564
|
function removeCliBannerEl() {
|
|
1565
1565
|
if (!_panel) return;
|
|
1566
|
-
const
|
|
1566
|
+
const container = _panel.querySelector('.ocp-messages-container');
|
|
1567
|
+
const banner = container?.parentNode?.querySelector(':scope > .ocp-cli-banner');
|
|
1567
1568
|
banner?.remove();
|
|
1568
1569
|
}
|
|
1569
1570
|
|
|
@@ -1527,7 +1527,7 @@ export function injectStyles() {
|
|
|
1527
1527
|
grid-template-columns: auto 1fr auto;
|
|
1528
1528
|
gap: 10px;
|
|
1529
1529
|
align-items: center;
|
|
1530
|
-
margin: 8px
|
|
1530
|
+
margin: 6px 8px 0;
|
|
1531
1531
|
padding: 10px 12px;
|
|
1532
1532
|
border: 1px solid rgba(255,180,80,0.45);
|
|
1533
1533
|
border-radius: 8px;
|
|
@@ -5094,15 +5094,51 @@
|
|
|
5094
5094
|
}
|
|
5095
5095
|
.update-backup-btn:hover { background: rgba(52,199,89,0.16); }
|
|
5096
5096
|
.update-backup-btn:disabled { opacity: 0.5; pointer-events: none; }
|
|
5097
|
-
.update-backup-status {
|
|
5098
|
-
display: flex;
|
|
5099
|
-
align-items: center;
|
|
5100
|
-
gap: 8px;
|
|
5097
|
+
.update-backup-status {
|
|
5098
|
+
display: flex;
|
|
5099
|
+
align-items: center;
|
|
5100
|
+
gap: 8px;
|
|
5101
5101
|
font-size: 11px;
|
|
5102
5102
|
color: var(--t-muted);
|
|
5103
|
-
padding: 4px 0;
|
|
5104
|
-
}
|
|
5105
|
-
.update-
|
|
5103
|
+
padding: 4px 0;
|
|
5104
|
+
}
|
|
5105
|
+
.update-run-status {
|
|
5106
|
+
align-items: flex-start;
|
|
5107
|
+
gap: 10px;
|
|
5108
|
+
padding: 10px 12px;
|
|
5109
|
+
border-radius: var(--r-card);
|
|
5110
|
+
border: 1px solid rgba(255,255,255,0.08);
|
|
5111
|
+
background: rgba(255,255,255,0.045);
|
|
5112
|
+
color: var(--t-secondary);
|
|
5113
|
+
font-size: 12px;
|
|
5114
|
+
line-height: 1.4;
|
|
5115
|
+
}
|
|
5116
|
+
.update-run-status .update-backup-icon {
|
|
5117
|
+
flex: 0 0 auto;
|
|
5118
|
+
margin-top: 1px;
|
|
5119
|
+
}
|
|
5120
|
+
.update-run-status strong {
|
|
5121
|
+
display: block;
|
|
5122
|
+
color: var(--t-primary);
|
|
5123
|
+
font-size: 12px;
|
|
5124
|
+
font-weight: 600;
|
|
5125
|
+
margin-bottom: 2px;
|
|
5126
|
+
}
|
|
5127
|
+
.update-run-detail,
|
|
5128
|
+
.update-run-hint {
|
|
5129
|
+
display: block;
|
|
5130
|
+
color: var(--t-muted);
|
|
5131
|
+
}
|
|
5132
|
+
.update-run-hint {
|
|
5133
|
+
margin-top: 5px;
|
|
5134
|
+
opacity: .82;
|
|
5135
|
+
}
|
|
5136
|
+
.update-run-status code {
|
|
5137
|
+
font-family: var(--ff-mono);
|
|
5138
|
+
font-size: 11px;
|
|
5139
|
+
color: var(--t-secondary);
|
|
5140
|
+
}
|
|
5141
|
+
.update-backup-icon { font-size: 14px; font-weight: 700; }
|
|
5106
5142
|
.update-backup-icon.spin { animation: updateSpin 1s linear infinite; }
|
|
5107
5143
|
@keyframes updateSpin {
|
|
5108
5144
|
from { transform: rotate(0deg); }
|
|
@@ -439,7 +439,7 @@ function injectStyles() {
|
|
|
439
439
|
grid-template-columns: auto 1fr auto;
|
|
440
440
|
gap: 10px;
|
|
441
441
|
align-items: center;
|
|
442
|
-
margin: 8px
|
|
442
|
+
margin: 6px 8px 0;
|
|
443
443
|
padding: 10px 12px;
|
|
444
444
|
border: 1px solid rgba(255,180,80,0.45);
|
|
445
445
|
border-radius: 8px;
|
|
@@ -7331,8 +7331,8 @@ let _cliInstallFailureForced = false;
|
|
|
7331
7331
|
function _ensureCliBannerEl() {
|
|
7332
7332
|
if (!_panel) return null;
|
|
7333
7333
|
const container = _panel.querySelector('#cp-messages-container');
|
|
7334
|
-
if (!container) return null;
|
|
7335
|
-
let banner = container.querySelector(':scope > .cp-cli-banner');
|
|
7334
|
+
if (!container || !container.parentNode) return null;
|
|
7335
|
+
let banner = container.parentNode.querySelector(':scope > .cp-cli-banner');
|
|
7336
7336
|
if (banner) return banner;
|
|
7337
7337
|
const label = getCliLabel('claude-code');
|
|
7338
7338
|
const cmd = getCliInstallCommand('claude-code');
|
|
@@ -7350,7 +7350,7 @@ function _ensureCliBannerEl() {
|
|
|
7350
7350
|
<button class="cp-cli-banner-recheck" type="button">Re-check</button>
|
|
7351
7351
|
</div>
|
|
7352
7352
|
`;
|
|
7353
|
-
container.insertBefore(banner, container
|
|
7353
|
+
container.parentNode.insertBefore(banner, container);
|
|
7354
7354
|
const btn = banner.querySelector('.cp-cli-banner-recheck');
|
|
7355
7355
|
btn?.addEventListener('click', async () => {
|
|
7356
7356
|
btn.disabled = true;
|
|
@@ -7368,7 +7368,8 @@ function _ensureCliBannerEl() {
|
|
|
7368
7368
|
|
|
7369
7369
|
function _removeCliBannerEl() {
|
|
7370
7370
|
if (!_panel) return;
|
|
7371
|
-
const
|
|
7371
|
+
const container = _panel.querySelector('#cp-messages-container');
|
|
7372
|
+
const banner = container?.parentNode?.querySelector(':scope > .cp-cli-banner');
|
|
7372
7373
|
banner?.remove();
|
|
7373
7374
|
}
|
|
7374
7375
|
|
|
@@ -128,12 +128,21 @@ export function initMenubar() {
|
|
|
128
128
|
'menu-open-skills-studio': 'skills',
|
|
129
129
|
'menu-skills-new': 'skills',
|
|
130
130
|
'menu-skills-import': 'skills',
|
|
131
|
+
<<<<<<< HEAD
|
|
131
132
|
'menu-open-automation-studio': 'automations',
|
|
132
133
|
'menu-automations-schedule-new': 'automations',
|
|
133
134
|
'menu-automations-quick-timer': 'automations',
|
|
134
135
|
'menu-automations-schedule-groups': 'automations',
|
|
135
136
|
'menu-automations-new': 'automations',
|
|
136
137
|
'menu-automations-import': 'automations',
|
|
138
|
+
=======
|
|
139
|
+
'menu-open-automation-studio': 'automations',
|
|
140
|
+
'menu-automations-schedule-new': 'automations',
|
|
141
|
+
'menu-automations-quick-timer': 'automations',
|
|
142
|
+
'menu-automations-schedule-groups': 'automations',
|
|
143
|
+
'menu-automations-new': 'automations',
|
|
144
|
+
'menu-automations-import': 'automations',
|
|
145
|
+
>>>>>>> Clean
|
|
137
146
|
'menu-terminal-claude': 'terminal',
|
|
138
147
|
'menu-terminal-codex': 'terminal',
|
|
139
148
|
'menu-terminal-gemini': 'terminal',
|
|
@@ -317,6 +326,7 @@ function wireAutomationsMenu() {
|
|
|
317
326
|
emit('automations:open');
|
|
318
327
|
});
|
|
319
328
|
}
|
|
329
|
+
<<<<<<< HEAD
|
|
320
330
|
const schedulesItem = $('menu-automations-schedules');
|
|
321
331
|
if (schedulesItem) {
|
|
322
332
|
schedulesItem.addEventListener('click', () => {
|
|
@@ -345,6 +355,36 @@ function wireAutomationsMenu() {
|
|
|
345
355
|
emit('automations:open-schedules', { tab: 'groups' });
|
|
346
356
|
});
|
|
347
357
|
}
|
|
358
|
+
=======
|
|
359
|
+
const schedulesItem = $('menu-automations-schedules');
|
|
360
|
+
if (schedulesItem) {
|
|
361
|
+
schedulesItem.addEventListener('click', () => {
|
|
362
|
+
closeAll();
|
|
363
|
+
emit('automations:open-schedules');
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
const newScheduleItem = $('menu-automations-schedule-new');
|
|
367
|
+
if (newScheduleItem) {
|
|
368
|
+
newScheduleItem.addEventListener('click', () => {
|
|
369
|
+
closeAll();
|
|
370
|
+
emit('automations:open-schedules', { action: 'new-schedule' });
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
const quickTimerItem = $('menu-automations-quick-timer');
|
|
374
|
+
if (quickTimerItem) {
|
|
375
|
+
quickTimerItem.addEventListener('click', () => {
|
|
376
|
+
closeAll();
|
|
377
|
+
emit('automations:open-schedules', { action: 'quick-timer' });
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
const scheduleGroupsItem = $('menu-automations-schedule-groups');
|
|
381
|
+
if (scheduleGroupsItem) {
|
|
382
|
+
scheduleGroupsItem.addEventListener('click', () => {
|
|
383
|
+
closeAll();
|
|
384
|
+
emit('automations:open-schedules', { tab: 'groups' });
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
>>>>>>> Clean
|
|
348
388
|
const newItem = $('menu-automations-new');
|
|
349
389
|
if (newItem) {
|
|
350
390
|
newItem.addEventListener('click', () => {
|
|
@@ -4,10 +4,25 @@
|
|
|
4
4
|
// and provides a 3-step modal wizard with blocking backup gate.
|
|
5
5
|
// ═══════════════════════════════════════════
|
|
6
6
|
|
|
7
|
+
import { showUpdateToast } from './ui-tool-updates.js';
|
|
8
|
+
|
|
7
9
|
const $ = (id) => document.getElementById(id);
|
|
10
|
+
const SYNABUN_GITHUB_URL = 'https://github.com/danilokhury/Synabun';
|
|
8
11
|
|
|
9
12
|
let _versionData = null;
|
|
10
13
|
let _btnWired = false;
|
|
14
|
+
<<<<<<< HEAD
|
|
15
|
+
=======
|
|
16
|
+
let _pollWired = false;
|
|
17
|
+
|
|
18
|
+
function sourceLabel(data) {
|
|
19
|
+
const src = data?.source;
|
|
20
|
+
if (src === 'both') return 'npm + GitHub';
|
|
21
|
+
if (src === 'github') return 'GitHub';
|
|
22
|
+
if (src === 'npm') return 'npm';
|
|
23
|
+
return src || 'update';
|
|
24
|
+
}
|
|
25
|
+
>>>>>>> Clean
|
|
11
26
|
|
|
12
27
|
function applyTopRightButton(data) {
|
|
13
28
|
const btn = $('topright-update-btn');
|
|
@@ -17,7 +32,11 @@ function applyTopRightButton(data) {
|
|
|
17
32
|
btn.style.display = has ? '' : 'none';
|
|
18
33
|
if (badge) badge.textContent = has ? '!' : '';
|
|
19
34
|
if (has) {
|
|
35
|
+
<<<<<<< HEAD
|
|
20
36
|
const srcLabel = data.source === 'both' ? 'npm + GitHub' : data.source === 'github' ? 'GitHub' : 'npm';
|
|
37
|
+
=======
|
|
38
|
+
const srcLabel = sourceLabel(data);
|
|
39
|
+
>>>>>>> Clean
|
|
21
40
|
btn.dataset.tooltip = `Update: v${data.current} → v${data.latest} (${srcLabel})`;
|
|
22
41
|
}
|
|
23
42
|
if (!_btnWired) {
|
|
@@ -28,6 +47,26 @@ function applyTopRightButton(data) {
|
|
|
28
47
|
});
|
|
29
48
|
}
|
|
30
49
|
}
|
|
50
|
+
<<<<<<< HEAD
|
|
51
|
+
=======
|
|
52
|
+
|
|
53
|
+
function maybeShowUpdateAlert(data, { once = true } = {}) {
|
|
54
|
+
if (!data?.updateAvailable) return;
|
|
55
|
+
const key = `synabun-update-alert:${data.current}->${data.latest}:${sourceLabel(data)}`;
|
|
56
|
+
try {
|
|
57
|
+
if (once && sessionStorage.getItem(key) === '1') return;
|
|
58
|
+
sessionStorage.setItem(key, '1');
|
|
59
|
+
} catch {}
|
|
60
|
+
|
|
61
|
+
setTimeout(() => {
|
|
62
|
+
showUpdateToast({
|
|
63
|
+
updates: [{ label: `SynaBun v${data.current} → v${data.latest}`, source: sourceLabel(data) }],
|
|
64
|
+
errors: [],
|
|
65
|
+
onClick: () => openSynabunUpdateModal(),
|
|
66
|
+
});
|
|
67
|
+
}, 500);
|
|
68
|
+
}
|
|
69
|
+
>>>>>>> Clean
|
|
31
70
|
|
|
32
71
|
export async function initUpdate() {
|
|
33
72
|
try {
|
|
@@ -35,18 +74,37 @@ export async function initUpdate() {
|
|
|
35
74
|
if (!res.ok) return;
|
|
36
75
|
_versionData = await res.json();
|
|
37
76
|
applyTopRightButton(_versionData);
|
|
77
|
+
<<<<<<< HEAD
|
|
78
|
+
=======
|
|
79
|
+
maybeShowUpdateAlert(_versionData);
|
|
80
|
+
|
|
81
|
+
if (!_pollWired) {
|
|
82
|
+
_pollWired = true;
|
|
83
|
+
setInterval(() => {
|
|
84
|
+
forceCheckSynabunUpdate({ alert: true }).catch(() => {});
|
|
85
|
+
}, 30 * 60 * 1000);
|
|
86
|
+
}
|
|
87
|
+
>>>>>>> Clean
|
|
38
88
|
} catch { /* silent — no update UI if check fails */ }
|
|
39
89
|
}
|
|
40
90
|
|
|
41
91
|
// Force a fresh server-side check (bypasses 1h cache via ?force=1) and
|
|
42
92
|
// re-applies the top-right button + badge state. Returns the latest
|
|
43
93
|
// _synabunUpdateCache shape so callers can compose toasts.
|
|
94
|
+
<<<<<<< HEAD
|
|
44
95
|
export async function forceCheckSynabunUpdate() {
|
|
96
|
+
=======
|
|
97
|
+
export async function forceCheckSynabunUpdate({ alert = false } = {}) {
|
|
98
|
+
>>>>>>> Clean
|
|
45
99
|
try {
|
|
46
100
|
const res = await fetch('/api/system/version?force=1');
|
|
47
101
|
if (!res.ok) return null;
|
|
48
102
|
_versionData = await res.json();
|
|
49
103
|
applyTopRightButton(_versionData);
|
|
104
|
+
<<<<<<< HEAD
|
|
105
|
+
=======
|
|
106
|
+
if (alert) maybeShowUpdateAlert(_versionData);
|
|
107
|
+
>>>>>>> Clean
|
|
50
108
|
return _versionData;
|
|
51
109
|
} catch {
|
|
52
110
|
return null;
|
|
@@ -149,6 +207,7 @@ function openUpdateModal() {
|
|
|
149
207
|
}
|
|
150
208
|
|
|
151
209
|
function renderStep3() {
|
|
210
|
+
<<<<<<< HEAD
|
|
152
211
|
// Channel-aware install command. Beta users staying on beta keep the
|
|
153
212
|
// @beta tag; both targets exist on stable so we surface a single line.
|
|
154
213
|
const channel = _versionData.installedChannel || 'stable';
|
|
@@ -156,11 +215,41 @@ function openUpdateModal() {
|
|
|
156
215
|
const targetLooksBeta = parseSemverPre(target);
|
|
157
216
|
const installTag = (channel === 'prerelease' && targetLooksBeta) ? 'beta' : 'latest';
|
|
158
217
|
const installCmd = `npm install -g synabun@${installTag}`;
|
|
218
|
+
=======
|
|
219
|
+
const installPlan = _versionData.installPlan || {};
|
|
220
|
+
const canAutoUpdate = installPlan.canAutoUpdate === true;
|
|
221
|
+
const repoUrl = installPlan.openUrl || SYNABUN_GITHUB_URL;
|
|
222
|
+
const installCmd = canAutoUpdate
|
|
223
|
+
? (installPlan.displayCommand || 'npm i -g synabun@latest')
|
|
224
|
+
: repoUrl;
|
|
225
|
+
const installIntro = canAutoUpdate
|
|
226
|
+
? 'SynaBun will close and run this command in a new terminal window:'
|
|
227
|
+
: 'This SynaBun install was not detected as an npm install. Open the GitHub repository to update from your checkout or installer:';
|
|
228
|
+
const title = canAutoUpdate ? 'Run the Update' : 'Update from GitHub';
|
|
229
|
+
const installSource = canAutoUpdate
|
|
230
|
+
? 'Installing from npm.'
|
|
231
|
+
: (installPlan.manualHint || 'Open the SynaBun repository for the correct update path.');
|
|
232
|
+
const autoRestartControl = canAutoUpdate ? `
|
|
233
|
+
<p style="font-size:11px;color:var(--t-muted);opacity:.75;margin:10px 0 0">
|
|
234
|
+
SynaBun will relaunch automatically after the update finishes.
|
|
235
|
+
</p>` : '';
|
|
236
|
+
const manualButton = canAutoUpdate ? `
|
|
237
|
+
<button class="action-btn action-btn--ghost" id="update-manual" title="Run the command yourself in your own terminal">Manual</button>` : '';
|
|
238
|
+
const updateNowButton = canAutoUpdate ? `
|
|
239
|
+
<button class="action-btn action-btn--primary" id="update-run-now">
|
|
240
|
+
<svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:6px"><polyline points="8 17 12 21 16 17"/><line x1="12" y1="12" x2="12" y2="21"/><path d="M20.88 18.09A5 5 0 0 0 18 9h-1.26A8 8 0 1 0 3 16.29"/></svg>
|
|
241
|
+
Update Now
|
|
242
|
+
</button>` : `
|
|
243
|
+
<button class="action-btn action-btn--primary" id="update-run-now" data-url="${esc(repoUrl)}">
|
|
244
|
+
<svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:6px"><path d="M15 3h6v6"/><path d="M10 14 21 3"/><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/></svg>
|
|
245
|
+
Open GitHub
|
|
246
|
+
</button>`;
|
|
247
|
+
>>>>>>> Clean
|
|
159
248
|
|
|
160
249
|
return `
|
|
161
|
-
<h3 class="update-modal-title"
|
|
250
|
+
<h3 class="update-modal-title">${title}</h3>
|
|
162
251
|
<p style="font-size:12px;color:var(--t-muted);margin:4px 0 12px">
|
|
163
|
-
|
|
252
|
+
${esc(installIntro)}
|
|
164
253
|
</p>
|
|
165
254
|
<div class="update-code-block">
|
|
166
255
|
<code>${esc(installCmd)}</code>
|
|
@@ -168,19 +257,23 @@ function openUpdateModal() {
|
|
|
168
257
|
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
|
|
169
258
|
</button>
|
|
170
259
|
</div>
|
|
171
|
-
<p style="font-size:
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
<div class="update-
|
|
175
|
-
<
|
|
176
|
-
<
|
|
177
|
-
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
|
|
178
|
-
</button>
|
|
260
|
+
<p style="font-size:11px;color:var(--t-muted);opacity:.75;margin:8px 0 0">${esc(installSource)}</p>
|
|
261
|
+
${autoRestartControl}
|
|
262
|
+
|
|
263
|
+
<div class="update-run-status" id="update-run-status" style="display:none;margin:10px 0 0">
|
|
264
|
+
<span class="update-backup-icon" id="update-run-icon"></span>
|
|
265
|
+
<span id="update-run-text"></span>
|
|
179
266
|
</div>
|
|
180
|
-
|
|
267
|
+
|
|
268
|
+
<div class="tag-delete-modal-actions" style="margin-top:14px;flex-wrap:wrap;gap:8px">
|
|
181
269
|
<button class="action-btn action-btn--ghost" id="update-back">Back</button>
|
|
182
|
-
|
|
183
|
-
|
|
270
|
+
${manualButton}
|
|
271
|
+
${updateNowButton}
|
|
272
|
+
</div>
|
|
273
|
+
|
|
274
|
+
<p id="update-manual-note" style="display:none;font-size:11px;color:var(--t-muted);opacity:.75;margin:10px 0 0">
|
|
275
|
+
After running the command, restart SynaBun with <code>synabun</code>.
|
|
276
|
+
</p>`;
|
|
184
277
|
}
|
|
185
278
|
|
|
186
279
|
function bindEvents() {
|
|
@@ -221,6 +314,16 @@ function openUpdateModal() {
|
|
|
221
314
|
// Backup download
|
|
222
315
|
overlay.querySelector('#update-backup-download')?.addEventListener('click', handleBackupDownload);
|
|
223
316
|
|
|
317
|
+
// Update Now — kick off click-to-update flow
|
|
318
|
+
overlay.querySelector('#update-run-now')?.addEventListener('click', handleRunUpdate);
|
|
319
|
+
|
|
320
|
+
// Manual — surface the command + relaunch hint, hide auto-update controls
|
|
321
|
+
overlay.querySelector('#update-manual')?.addEventListener('click', () => {
|
|
322
|
+
overlay.querySelector('#update-manual-note').style.display = '';
|
|
323
|
+
const runNow = overlay.querySelector('#update-run-now');
|
|
324
|
+
if (runNow) runNow.style.display = 'none';
|
|
325
|
+
});
|
|
326
|
+
|
|
224
327
|
// Skip checkbox
|
|
225
328
|
const skipCb = overlay.querySelector('#update-skip-backup');
|
|
226
329
|
if (skipCb) {
|
|
@@ -232,6 +335,78 @@ function openUpdateModal() {
|
|
|
232
335
|
}
|
|
233
336
|
}
|
|
234
337
|
|
|
338
|
+
async function handleRunUpdate() {
|
|
339
|
+
const installPlan = _versionData?.installPlan || {};
|
|
340
|
+
const canAutoUpdate = installPlan.canAutoUpdate === true;
|
|
341
|
+
const repoUrl = installPlan.openUrl || SYNABUN_GITHUB_URL;
|
|
342
|
+
|
|
343
|
+
// Auto-restart is always on; server forces it true regardless of payload.
|
|
344
|
+
const autoRestart = true;
|
|
345
|
+
|
|
346
|
+
const runBtn = overlay.querySelector('#update-run-now');
|
|
347
|
+
const backBtn = overlay.querySelector('#update-back');
|
|
348
|
+
const manualBtn = overlay.querySelector('#update-manual');
|
|
349
|
+
const status = overlay.querySelector('#update-run-status');
|
|
350
|
+
const icon = overlay.querySelector('#update-run-icon');
|
|
351
|
+
const text = overlay.querySelector('#update-run-text');
|
|
352
|
+
|
|
353
|
+
if (!runBtn || !backBtn || !status || !icon || !text) return;
|
|
354
|
+
|
|
355
|
+
if (!canAutoUpdate) {
|
|
356
|
+
window.open(repoUrl, '_blank', 'noopener,noreferrer');
|
|
357
|
+
status.style.display = 'flex';
|
|
358
|
+
icon.className = 'update-backup-icon';
|
|
359
|
+
icon.textContent = '↗';
|
|
360
|
+
icon.style.color = '#4fc3f7';
|
|
361
|
+
text.innerHTML = `<strong>GitHub repository opened</strong><span class="update-run-detail">Use the repository instructions to update this non-npm installation.</span>`;
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
runBtn.disabled = true;
|
|
366
|
+
backBtn.disabled = true;
|
|
367
|
+
if (manualBtn) manualBtn.disabled = true;
|
|
368
|
+
runBtn.innerHTML = '<span class="update-spinner"></span> Launching updater...';
|
|
369
|
+
status.style.display = 'flex';
|
|
370
|
+
icon.className = 'update-backup-icon spin';
|
|
371
|
+
icon.textContent = '⟳';
|
|
372
|
+
icon.style.color = '';
|
|
373
|
+
text.innerHTML = '<strong>Launching updater</strong><span class="update-run-detail">Opening a terminal window for the npm update.</span>';
|
|
374
|
+
|
|
375
|
+
try {
|
|
376
|
+
const res = await fetch('/api/system/run-update', {
|
|
377
|
+
method: 'POST',
|
|
378
|
+
headers: { 'Content-Type': 'application/json' },
|
|
379
|
+
body: JSON.stringify({ autoRestart }),
|
|
380
|
+
});
|
|
381
|
+
if (!res.ok) {
|
|
382
|
+
let msg = `Updater failed (${res.status})`;
|
|
383
|
+
try { const b = await res.json(); msg = b.error || msg; } catch {}
|
|
384
|
+
throw new Error(msg);
|
|
385
|
+
}
|
|
386
|
+
await res.json();
|
|
387
|
+
|
|
388
|
+
icon.className = 'update-backup-icon';
|
|
389
|
+
icon.textContent = '✓';
|
|
390
|
+
icon.style.color = '#34c759';
|
|
391
|
+
text.innerHTML = `<strong>Updater terminal launched</strong><span class="update-run-detail">SynaBun is shutting down so npm can replace the package. Watch the terminal for install progress; SynaBun will relaunch automatically when it finishes.</span><span class="update-run-hint">If no terminal window appears, run <code>${esc(installPlan.displayCommand || 'npm i -g synabun@latest')}</code> manually.</span>`;
|
|
392
|
+
runBtn.innerHTML = 'Updater running…';
|
|
393
|
+
|
|
394
|
+
// Server is shutting down. Optionally redirect to a friendly offline
|
|
395
|
+
// notice — for now leave the page; user already understands from copy.
|
|
396
|
+
// Re-enable Back so they can close the modal.
|
|
397
|
+
setTimeout(() => { backBtn.disabled = false; }, 2500);
|
|
398
|
+
} catch (err) {
|
|
399
|
+
icon.className = 'update-backup-icon';
|
|
400
|
+
icon.textContent = '✗';
|
|
401
|
+
icon.style.color = 'var(--accent-red)';
|
|
402
|
+
text.textContent = 'Failed to launch updater: ' + err.message;
|
|
403
|
+
runBtn.disabled = false;
|
|
404
|
+
backBtn.disabled = false;
|
|
405
|
+
if (manualBtn) manualBtn.disabled = false;
|
|
406
|
+
runBtn.innerHTML = `<svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:6px"><polyline points="8 17 12 21 16 17"/><line x1="12" y1="12" x2="12" y2="21"/><path d="M20.88 18.09A5 5 0 0 0 18 9h-1.26A8 8 0 1 0 3 16.29"/></svg> Retry`;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
235
410
|
async function handleBackupDownload() {
|
|
236
411
|
const btn = overlay.querySelector('#update-backup-download');
|
|
237
412
|
const status = overlay.querySelector('#update-backup-status');
|