claude-remote-cli 1.2.0 → 1.3.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/server/index.js +59 -0
- package/package.json +1 -1
- package/public/app.js +72 -0
- package/public/index.html +11 -0
- package/public/style.css +78 -0
package/dist/server/index.js
CHANGED
|
@@ -12,12 +12,49 @@ import * as auth from './auth.js';
|
|
|
12
12
|
import * as sessions from './sessions.js';
|
|
13
13
|
import { setupWebSocket } from './ws.js';
|
|
14
14
|
import { WorktreeWatcher } from './watcher.js';
|
|
15
|
+
import { isInstalled as serviceIsInstalled } from './service.js';
|
|
15
16
|
const __filename = fileURLToPath(import.meta.url);
|
|
16
17
|
const __dirname = path.dirname(__filename);
|
|
17
18
|
const execFileAsync = promisify(execFile);
|
|
18
19
|
// When run via CLI bin, config lives in ~/.config/claude-remote-cli/
|
|
19
20
|
// When run directly (development), fall back to local config.json
|
|
20
21
|
const CONFIG_PATH = process.env.CLAUDE_REMOTE_CONFIG || path.join(__dirname, '..', '..', 'config.json');
|
|
22
|
+
const VERSION_CACHE_TTL = 5 * 60 * 1000;
|
|
23
|
+
let versionCache = null;
|
|
24
|
+
function getCurrentVersion() {
|
|
25
|
+
const pkgPath = path.join(__dirname, '..', '..', 'package.json');
|
|
26
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
27
|
+
return pkg.version;
|
|
28
|
+
}
|
|
29
|
+
function semverLessThan(a, b) {
|
|
30
|
+
const parse = (v) => v.split('.').map(Number);
|
|
31
|
+
const [aMaj = 0, aMin = 0, aPat = 0] = parse(a);
|
|
32
|
+
const [bMaj = 0, bMin = 0, bPat = 0] = parse(b);
|
|
33
|
+
if (aMaj !== bMaj)
|
|
34
|
+
return aMaj < bMaj;
|
|
35
|
+
if (aMin !== bMin)
|
|
36
|
+
return aMin < bMin;
|
|
37
|
+
return aPat < bPat;
|
|
38
|
+
}
|
|
39
|
+
async function getLatestVersion() {
|
|
40
|
+
const now = Date.now();
|
|
41
|
+
if (versionCache && now - versionCache.fetchedAt < VERSION_CACHE_TTL) {
|
|
42
|
+
return versionCache.latest;
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
const res = await fetch('https://registry.npmjs.org/claude-remote-cli/latest');
|
|
46
|
+
if (!res.ok)
|
|
47
|
+
return null;
|
|
48
|
+
const data = await res.json();
|
|
49
|
+
if (!data.version)
|
|
50
|
+
return null;
|
|
51
|
+
versionCache = { latest: data.version, fetchedAt: now };
|
|
52
|
+
return data.version;
|
|
53
|
+
}
|
|
54
|
+
catch (_) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
21
58
|
function parseTTL(ttl) {
|
|
22
59
|
if (typeof ttl !== 'string')
|
|
23
60
|
return 24 * 60 * 60 * 1000;
|
|
@@ -340,6 +377,28 @@ async function main() {
|
|
|
340
377
|
res.status(404).json({ error: 'Session not found' });
|
|
341
378
|
}
|
|
342
379
|
});
|
|
380
|
+
// GET /version — check current vs latest
|
|
381
|
+
app.get('/version', requireAuth, async (_req, res) => {
|
|
382
|
+
const current = getCurrentVersion();
|
|
383
|
+
const latest = await getLatestVersion();
|
|
384
|
+
const updateAvailable = latest !== null && semverLessThan(current, latest);
|
|
385
|
+
res.json({ current, latest, updateAvailable });
|
|
386
|
+
});
|
|
387
|
+
// POST /update — install latest version from npm
|
|
388
|
+
app.post('/update', requireAuth, async (_req, res) => {
|
|
389
|
+
try {
|
|
390
|
+
await execFileAsync('npm', ['install', '-g', 'claude-remote-cli@latest']);
|
|
391
|
+
const restarting = serviceIsInstalled();
|
|
392
|
+
res.json({ ok: true, restarting });
|
|
393
|
+
if (restarting) {
|
|
394
|
+
setTimeout(() => process.exit(0), 1000);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
catch (err) {
|
|
398
|
+
const message = err instanceof Error ? err.message : 'Update failed';
|
|
399
|
+
res.status(500).json({ ok: false, error: message });
|
|
400
|
+
}
|
|
401
|
+
});
|
|
343
402
|
server.listen(config.port, config.host, () => {
|
|
344
403
|
console.log(`claude-remote-cli listening on ${config.host}:${config.port}`);
|
|
345
404
|
});
|
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -40,6 +40,10 @@
|
|
|
40
40
|
var deleteWtName = document.getElementById('delete-wt-name');
|
|
41
41
|
var deleteWtCancel = document.getElementById('delete-wt-cancel');
|
|
42
42
|
var deleteWtConfirm = document.getElementById('delete-wt-confirm');
|
|
43
|
+
var updateToast = document.getElementById('update-toast');
|
|
44
|
+
var updateToastText = document.getElementById('update-toast-text');
|
|
45
|
+
var updateToastBtn = document.getElementById('update-toast-btn');
|
|
46
|
+
var updateToastDismiss = document.getElementById('update-toast-dismiss');
|
|
43
47
|
|
|
44
48
|
// Context menu state
|
|
45
49
|
var contextMenuTarget = null; // stores { worktreePath, repoPath, name }
|
|
@@ -117,6 +121,7 @@
|
|
|
117
121
|
loadRepos();
|
|
118
122
|
refreshAll();
|
|
119
123
|
connectEventSocket();
|
|
124
|
+
checkForUpdates();
|
|
120
125
|
}
|
|
121
126
|
|
|
122
127
|
// ── Terminal ────────────────────────────────────────────────────────────────
|
|
@@ -851,6 +856,73 @@
|
|
|
851
856
|
vv.addEventListener('scroll', onViewportResize);
|
|
852
857
|
})();
|
|
853
858
|
|
|
859
|
+
// ── Update Toast ─────────────────────────────────────────────────────────────
|
|
860
|
+
|
|
861
|
+
function checkForUpdates() {
|
|
862
|
+
fetch('/version')
|
|
863
|
+
.then(function (res) {
|
|
864
|
+
if (!res.ok) return;
|
|
865
|
+
return res.json();
|
|
866
|
+
})
|
|
867
|
+
.then(function (data) {
|
|
868
|
+
if (data && data.updateAvailable) {
|
|
869
|
+
showUpdateToast(data.current, data.latest);
|
|
870
|
+
}
|
|
871
|
+
})
|
|
872
|
+
.catch(function () {
|
|
873
|
+
// Silently ignore version check errors
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
function showUpdateToast(current, latest) {
|
|
878
|
+
updateToastText.textContent = 'Update available: v' + current + ' \u2192 v' + latest;
|
|
879
|
+
updateToast.hidden = false;
|
|
880
|
+
updateToastBtn.disabled = false;
|
|
881
|
+
updateToastBtn.textContent = 'Update Now';
|
|
882
|
+
|
|
883
|
+
updateToastBtn.onclick = function () {
|
|
884
|
+
triggerUpdate(latest);
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
function triggerUpdate(latest) {
|
|
889
|
+
updateToastBtn.disabled = true;
|
|
890
|
+
updateToastBtn.textContent = 'Updating\u2026';
|
|
891
|
+
|
|
892
|
+
fetch('/update', { method: 'POST' })
|
|
893
|
+
.then(function (res) {
|
|
894
|
+
return res.json().then(function (data) {
|
|
895
|
+
return { ok: res.ok, data: data };
|
|
896
|
+
});
|
|
897
|
+
})
|
|
898
|
+
.then(function (result) {
|
|
899
|
+
if (result.ok && result.data.restarting) {
|
|
900
|
+
updateToastText.textContent = 'Updated! Restarting server\u2026';
|
|
901
|
+
updateToastBtn.hidden = true;
|
|
902
|
+
updateToastDismiss.hidden = true;
|
|
903
|
+
setTimeout(function () {
|
|
904
|
+
location.reload();
|
|
905
|
+
}, 5000);
|
|
906
|
+
} else if (result.ok) {
|
|
907
|
+
updateToastText.textContent = 'Updated! Please restart the server manually.';
|
|
908
|
+
updateToastBtn.hidden = true;
|
|
909
|
+
} else {
|
|
910
|
+
updateToastText.textContent = 'Update failed: ' + (result.data.error || 'Unknown error');
|
|
911
|
+
updateToastBtn.disabled = false;
|
|
912
|
+
updateToastBtn.textContent = 'Retry';
|
|
913
|
+
}
|
|
914
|
+
})
|
|
915
|
+
.catch(function () {
|
|
916
|
+
updateToastText.textContent = 'Update failed. Please try again.';
|
|
917
|
+
updateToastBtn.disabled = false;
|
|
918
|
+
updateToastBtn.textContent = 'Retry';
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
updateToastDismiss.addEventListener('click', function () {
|
|
923
|
+
updateToast.hidden = true;
|
|
924
|
+
});
|
|
925
|
+
|
|
854
926
|
// ── Auto-auth Check ─────────────────────────────────────────────────────────
|
|
855
927
|
|
|
856
928
|
fetch('/sessions')
|
package/public/index.html
CHANGED
|
@@ -78,6 +78,17 @@
|
|
|
78
78
|
</div>
|
|
79
79
|
</div>
|
|
80
80
|
|
|
81
|
+
<!-- Update Toast -->
|
|
82
|
+
<div id="update-toast" hidden>
|
|
83
|
+
<div id="update-toast-content">
|
|
84
|
+
<span id="update-toast-text"></span>
|
|
85
|
+
<div id="update-toast-actions">
|
|
86
|
+
<button id="update-toast-btn" class="btn-accent">Update Now</button>
|
|
87
|
+
<button id="update-toast-dismiss" aria-label="Dismiss">×</button>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
81
92
|
</div>
|
|
82
93
|
|
|
83
94
|
<!-- New Session Dialog -->
|
package/public/style.css
CHANGED
|
@@ -818,6 +818,84 @@ dialog#delete-worktree-dialog h2 {
|
|
|
818
818
|
opacity: 0.85;
|
|
819
819
|
}
|
|
820
820
|
|
|
821
|
+
/* ===== Update Toast ===== */
|
|
822
|
+
|
|
823
|
+
#update-toast {
|
|
824
|
+
position: fixed;
|
|
825
|
+
bottom: 0;
|
|
826
|
+
left: 0;
|
|
827
|
+
right: 0;
|
|
828
|
+
z-index: 150;
|
|
829
|
+
display: flex;
|
|
830
|
+
justify-content: center;
|
|
831
|
+
padding: 12px 12px calc(12px + env(safe-area-inset-bottom));
|
|
832
|
+
pointer-events: none;
|
|
833
|
+
animation: toast-slide-up 0.25s ease-out;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
@keyframes toast-slide-up {
|
|
837
|
+
from {
|
|
838
|
+
transform: translateY(100%);
|
|
839
|
+
opacity: 0;
|
|
840
|
+
}
|
|
841
|
+
to {
|
|
842
|
+
transform: translateY(0);
|
|
843
|
+
opacity: 1;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
#update-toast-content {
|
|
848
|
+
display: flex;
|
|
849
|
+
flex-direction: row;
|
|
850
|
+
align-items: center;
|
|
851
|
+
gap: 12px;
|
|
852
|
+
background: var(--surface);
|
|
853
|
+
border: 1px solid var(--border);
|
|
854
|
+
border-radius: 10px;
|
|
855
|
+
padding: 12px 16px;
|
|
856
|
+
max-width: 500px;
|
|
857
|
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
|
|
858
|
+
pointer-events: auto;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
#update-toast-text {
|
|
862
|
+
flex: 1;
|
|
863
|
+
font-size: 0.85rem;
|
|
864
|
+
color: var(--text);
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
#update-toast-actions {
|
|
868
|
+
display: flex;
|
|
869
|
+
gap: 8px;
|
|
870
|
+
flex-shrink: 0;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
#update-toast-btn {
|
|
874
|
+
padding: 8px 14px;
|
|
875
|
+
border-radius: 6px;
|
|
876
|
+
font-size: 0.8rem;
|
|
877
|
+
border: none;
|
|
878
|
+
white-space: nowrap;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
#update-toast-btn:disabled {
|
|
882
|
+
opacity: 0.6;
|
|
883
|
+
cursor: not-allowed;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
#update-toast-dismiss {
|
|
887
|
+
background: none;
|
|
888
|
+
border: none;
|
|
889
|
+
color: var(--text-muted);
|
|
890
|
+
font-size: 1.2rem;
|
|
891
|
+
padding: 4px 6px;
|
|
892
|
+
cursor: pointer;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
#update-toast-dismiss:hover {
|
|
896
|
+
color: var(--text);
|
|
897
|
+
}
|
|
898
|
+
|
|
821
899
|
/* ===== Mobile Responsive ===== */
|
|
822
900
|
@media (max-width: 600px) {
|
|
823
901
|
#mobile-header {
|