synabun 2026.4.20-beta.3 → 2026.4.20-beta.4
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.
|
@@ -10,13 +10,6 @@ import { emit } from './state.js';
|
|
|
10
10
|
|
|
11
11
|
const $ = (id) => document.getElementById(id);
|
|
12
12
|
|
|
13
|
-
const UPDATE_COMMANDS = {
|
|
14
|
-
'claude-code': 'npm install -g @anthropic-ai/claude-code@latest',
|
|
15
|
-
'codex': 'npm install -g @openai/codex@latest',
|
|
16
|
-
'gemini': 'npm install -g @google/gemini-cli@latest',
|
|
17
|
-
'opencode': 'opencode upgrade',
|
|
18
|
-
};
|
|
19
|
-
|
|
20
13
|
const TOOL_ELEMENTS = {
|
|
21
14
|
'claude-code': { toolbar: 'claude-update-badge', menu: 'claude-menu-update-badge' },
|
|
22
15
|
'codex': { toolbar: 'codex-update-badge', menu: 'codex-menu-update-badge' },
|
|
@@ -32,10 +25,34 @@ const PARENT_ELEMENTS = {
|
|
|
32
25
|
'gemini': { toolbar: null, menu: 'menu-terminal-gemini' },
|
|
33
26
|
};
|
|
34
27
|
|
|
28
|
+
const TOOL_LABELS = {
|
|
29
|
+
'claude-code': 'Claude Code',
|
|
30
|
+
'codex': 'Codex',
|
|
31
|
+
'opencode': 'OpenCode',
|
|
32
|
+
'gemini': 'Gemini CLI',
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Cached server response so click handlers can read per-tool updateCommand
|
|
36
|
+
// without re-fetching. Refreshed on every applyBadges() call.
|
|
37
|
+
let _latestData = null;
|
|
38
|
+
|
|
35
39
|
function runUpdate(key, badge) {
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
const info = _latestData?.tools?.[key];
|
|
41
|
+
const cmd = info?.updateCommand;
|
|
42
|
+
|
|
43
|
+
if (!cmd) {
|
|
44
|
+
// Tool installed via a source we can't safely auto-update from
|
|
45
|
+
// (winget, native installer, etc.) and has no built-in self-updater.
|
|
46
|
+
const label = TOOL_LABELS[key] || key;
|
|
47
|
+
const src = info?.installSource;
|
|
48
|
+
const hint = src && src !== 'unknown' && src !== 'other'
|
|
49
|
+
? `via your installer (${src})`
|
|
50
|
+
: 'using your original installer';
|
|
51
|
+
alert(`${label} update available (v${info?.installed} → v${info?.latest}).\n\nPlease update ${hint}.`);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
emit('terminal:run-command', { command: cmd, label: `Update ${TOOL_LABELS[key] || key}` });
|
|
39
56
|
// Optimistically clear the badge and restore parent tooltip
|
|
40
57
|
if (badge) {
|
|
41
58
|
badge.textContent = '';
|
|
@@ -70,11 +87,17 @@ function wireParentIntercept(parentId, badgeId, key) {
|
|
|
70
87
|
|
|
71
88
|
function applyBadges(data) {
|
|
72
89
|
if (!data.tools) return;
|
|
90
|
+
_latestData = data;
|
|
73
91
|
|
|
74
92
|
for (const [key, ids] of Object.entries(TOOL_ELEMENTS)) {
|
|
75
93
|
const info = data.tools[key];
|
|
76
94
|
const has = info && info.updateAvailable;
|
|
77
|
-
const
|
|
95
|
+
const canUpdate = has && info?.canUpdate;
|
|
96
|
+
const tip = has
|
|
97
|
+
? (canUpdate
|
|
98
|
+
? `v${info.installed} → v${info.latest} — click to update`
|
|
99
|
+
: `v${info.installed} → v${info.latest} — update via ${info.installSource || 'your installer'}`)
|
|
100
|
+
: '';
|
|
78
101
|
const parents = PARENT_ELEMENTS[key];
|
|
79
102
|
|
|
80
103
|
if (ids.toolbar) {
|
|
@@ -18,7 +18,7 @@ import express from 'express';
|
|
|
18
18
|
import { fileURLToPath, pathToFileURL } from 'url';
|
|
19
19
|
import { basename, delimiter, dirname, extname, join, resolve, sep } from 'path';
|
|
20
20
|
import { config as dotenvConfig } from 'dotenv';
|
|
21
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, unlinkSync, rmSync, statSync, fstatSync, renameSync, cpSync, copyFileSync, appendFileSync, openSync, readSync, closeSync, chmodSync, watch as fsWatch, createWriteStream } from 'fs';
|
|
21
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, unlinkSync, rmSync, statSync, fstatSync, renameSync, cpSync, copyFileSync, appendFileSync, openSync, readSync, closeSync, chmodSync, watch as fsWatch, createWriteStream, realpathSync } from 'fs';
|
|
22
22
|
import { randomBytes, randomUUID, createHash, pbkdf2Sync, createDecipheriv } from 'crypto';
|
|
23
23
|
import { exec, execSync, spawn, spawnSync } from 'child_process';
|
|
24
24
|
import { promisify } from 'util';
|
|
@@ -9758,6 +9758,66 @@ function semverNewer(a, b) {
|
|
|
9758
9758
|
|| (pb[0] === pa[0] && pb[1] === pa[1] && pb[2] > pa[2]);
|
|
9759
9759
|
}
|
|
9760
9760
|
|
|
9761
|
+
function resolveBinPath(cmd, env) {
|
|
9762
|
+
try {
|
|
9763
|
+
const isWin = process.platform === 'win32';
|
|
9764
|
+
const finder = isWin ? `where ${cmd}` : `command -v ${cmd}`;
|
|
9765
|
+
const opts = { encoding: 'utf-8', timeout: 2000, stdio: ['pipe', 'pipe', 'pipe'], env };
|
|
9766
|
+
if (!isWin) opts.shell = '/bin/sh';
|
|
9767
|
+
const raw = execSync(finder, opts).trim();
|
|
9768
|
+
const first = raw.split(/\r?\n/)[0] || null;
|
|
9769
|
+
if (!first) return null;
|
|
9770
|
+
// Follow symlinks so install-source detection sees the real install dir
|
|
9771
|
+
// (e.g. /usr/local/bin/claude → /usr/local/lib/node_modules/.../bin/claude).
|
|
9772
|
+
try { return realpathSync(first); } catch { return first; }
|
|
9773
|
+
} catch { return null; }
|
|
9774
|
+
}
|
|
9775
|
+
|
|
9776
|
+
function detectInstallSource(binPath) {
|
|
9777
|
+
if (!binPath) return 'unknown';
|
|
9778
|
+
const p = binPath.replace(/\\/g, '/').toLowerCase();
|
|
9779
|
+
if (/\/node_modules\//.test(p) || /\/npm\/node_modules\//.test(p)) return 'npm';
|
|
9780
|
+
if (p.includes('/opt/homebrew/') || p.includes('/usr/local/cellar/') || /\/homebrew\//.test(p)) return 'brew';
|
|
9781
|
+
if (p.includes('/.claude/local/') || p.includes('/anthropicclaude/')) return 'native';
|
|
9782
|
+
if (/\/program files( \(x86\))?\//.test(p) || /\/localappdata\/.*anthropicclaude/.test(p)) return 'native';
|
|
9783
|
+
if (p.includes('/winget/') || p.includes('/microsoft/winget/')) return 'winget';
|
|
9784
|
+
if (/\/usr\/local\/lib\/node_modules\//.test(p)) return 'npm';
|
|
9785
|
+
return 'other';
|
|
9786
|
+
}
|
|
9787
|
+
|
|
9788
|
+
const NPM_PKGS = {
|
|
9789
|
+
'claude-code': '@anthropic-ai/claude-code',
|
|
9790
|
+
'codex': '@openai/codex',
|
|
9791
|
+
'gemini': '@google/gemini-cli',
|
|
9792
|
+
'opencode': 'opencode-ai',
|
|
9793
|
+
};
|
|
9794
|
+
|
|
9795
|
+
const BREW_FORMULAE = {
|
|
9796
|
+
'claude-code': 'claude-code',
|
|
9797
|
+
'codex': 'codex',
|
|
9798
|
+
'gemini': 'gemini-cli',
|
|
9799
|
+
'opencode': 'opencode',
|
|
9800
|
+
};
|
|
9801
|
+
|
|
9802
|
+
// Pick the smoothest update command for each tool given its install source.
|
|
9803
|
+
// Tools with native self-updaters (claude, opencode) handle install-source
|
|
9804
|
+
// detection internally — prefer those everywhere. Other tools fall back to
|
|
9805
|
+
// install-source-specific package managers (npm/brew). Returns null when no
|
|
9806
|
+
// safe automated update path exists (e.g., winget/native installer with no
|
|
9807
|
+
// self-updater) — caller should show a "use your installer" hint instead.
|
|
9808
|
+
function buildUpdateCommand(toolKey, installSource) {
|
|
9809
|
+
if (toolKey === 'claude-code') return 'claude update';
|
|
9810
|
+
if (toolKey === 'opencode') return 'opencode upgrade';
|
|
9811
|
+
|
|
9812
|
+
const npmPkg = NPM_PKGS[toolKey];
|
|
9813
|
+
if (!npmPkg) return null;
|
|
9814
|
+
|
|
9815
|
+
if (installSource === 'npm') return `npm install -g ${npmPkg}@latest`;
|
|
9816
|
+
if (installSource === 'brew') return `brew upgrade ${BREW_FORMULAE[toolKey] || toolKey}`;
|
|
9817
|
+
if (installSource === 'native' || installSource === 'winget') return null;
|
|
9818
|
+
return `npm install -g ${npmPkg}@latest`;
|
|
9819
|
+
}
|
|
9820
|
+
|
|
9761
9821
|
async function checkToolVersions() {
|
|
9762
9822
|
// Strip node_modules/.bin entries so the version check reflects the user's
|
|
9763
9823
|
// global install rather than any copy bundled with SynaBun itself.
|
|
@@ -9765,7 +9825,8 @@ async function checkToolVersions() {
|
|
|
9765
9825
|
.split(delimiter)
|
|
9766
9826
|
.filter(p => !/[\\/]node_modules[\\/]\.bin[\\/]?$/i.test(p))
|
|
9767
9827
|
.join(delimiter);
|
|
9768
|
-
const
|
|
9828
|
+
const cleanEnv = { ...process.env, PATH: augPath };
|
|
9829
|
+
const execOpts = { encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'], env: cleanEnv };
|
|
9769
9830
|
|
|
9770
9831
|
const results = {};
|
|
9771
9832
|
|
|
@@ -9791,10 +9852,18 @@ async function checkToolVersions() {
|
|
|
9791
9852
|
}
|
|
9792
9853
|
} catch { /* network error */ }
|
|
9793
9854
|
|
|
9855
|
+
const binPath = installed ? resolveBinPath(tool.cmd, cleanEnv) : null;
|
|
9856
|
+
const installSource = detectInstallSource(binPath);
|
|
9857
|
+
const updateCommand = installed ? buildUpdateCommand(tool.key, installSource) : null;
|
|
9858
|
+
|
|
9794
9859
|
results[tool.key] = {
|
|
9795
9860
|
installed,
|
|
9796
9861
|
latest,
|
|
9797
9862
|
updateAvailable: !!(installed && latest && semverNewer(installed, latest)),
|
|
9863
|
+
installSource,
|
|
9864
|
+
binPath,
|
|
9865
|
+
updateCommand,
|
|
9866
|
+
canUpdate: !!updateCommand,
|
|
9798
9867
|
};
|
|
9799
9868
|
}
|
|
9800
9869
|
|