claude-remote-cli 1.2.0 → 1.4.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 +60 -1
- package/package.json +1 -1
- package/public/app.js +91 -3
- package/public/index.html +20 -1
- package/public/style.css +112 -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;
|
|
@@ -280,7 +317,7 @@ async function main() {
|
|
|
280
317
|
return;
|
|
281
318
|
}
|
|
282
319
|
const name = repoName || repoPath.split('/').filter(Boolean).pop() || 'session';
|
|
283
|
-
const baseArgs = claudeArgs ||
|
|
320
|
+
const baseArgs = [...(config.claudeArgs || []), ...(claudeArgs || [])];
|
|
284
321
|
// Compute root by matching repoPath against configured rootDirs
|
|
285
322
|
const roots = config.rootDirs || [];
|
|
286
323
|
const root = roots.find(function (r) { return repoPath.startsWith(r); }) || '';
|
|
@@ -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
|
@@ -34,12 +34,18 @@
|
|
|
34
34
|
var sidebarRepoFilter = document.getElementById('sidebar-repo-filter');
|
|
35
35
|
var dialogRootSelect = document.getElementById('dialog-root-select');
|
|
36
36
|
var dialogRepoSelect = document.getElementById('dialog-repo-select');
|
|
37
|
+
var dialogYolo = document.getElementById('dialog-yolo');
|
|
37
38
|
var contextMenu = document.getElementById('context-menu');
|
|
39
|
+
var ctxResumeYolo = document.getElementById('ctx-resume-yolo');
|
|
38
40
|
var ctxDeleteWorktree = document.getElementById('ctx-delete-worktree');
|
|
39
41
|
var deleteWtDialog = document.getElementById('delete-worktree-dialog');
|
|
40
42
|
var deleteWtName = document.getElementById('delete-wt-name');
|
|
41
43
|
var deleteWtCancel = document.getElementById('delete-wt-cancel');
|
|
42
44
|
var deleteWtConfirm = document.getElementById('delete-wt-confirm');
|
|
45
|
+
var updateToast = document.getElementById('update-toast');
|
|
46
|
+
var updateToastText = document.getElementById('update-toast-text');
|
|
47
|
+
var updateToastBtn = document.getElementById('update-toast-btn');
|
|
48
|
+
var updateToastDismiss = document.getElementById('update-toast-dismiss');
|
|
43
49
|
|
|
44
50
|
// Context menu state
|
|
45
51
|
var contextMenuTarget = null; // stores { worktreePath, repoPath, name }
|
|
@@ -117,6 +123,7 @@
|
|
|
117
123
|
loadRepos();
|
|
118
124
|
refreshAll();
|
|
119
125
|
connectEventSocket();
|
|
126
|
+
checkForUpdates();
|
|
120
127
|
}
|
|
121
128
|
|
|
122
129
|
// ── Terminal ────────────────────────────────────────────────────────────────
|
|
@@ -545,7 +552,18 @@
|
|
|
545
552
|
.catch(function () {});
|
|
546
553
|
}
|
|
547
554
|
|
|
548
|
-
// ──
|
|
555
|
+
// ── Context Menu Actions ──────────────────────────────────────────────────
|
|
556
|
+
|
|
557
|
+
ctxResumeYolo.addEventListener('click', function (e) {
|
|
558
|
+
e.stopPropagation();
|
|
559
|
+
hideContextMenu();
|
|
560
|
+
if (!contextMenuTarget) return;
|
|
561
|
+
startSession(
|
|
562
|
+
contextMenuTarget.repoPath,
|
|
563
|
+
contextMenuTarget.worktreePath,
|
|
564
|
+
['--dangerously-skip-permissions']
|
|
565
|
+
);
|
|
566
|
+
});
|
|
549
567
|
|
|
550
568
|
ctxDeleteWorktree.addEventListener('click', function (e) {
|
|
551
569
|
e.stopPropagation();
|
|
@@ -653,12 +671,13 @@
|
|
|
653
671
|
dialogRepoSelect.disabled = false;
|
|
654
672
|
});
|
|
655
673
|
|
|
656
|
-
function startSession(repoPath, worktreePath) {
|
|
674
|
+
function startSession(repoPath, worktreePath, claudeArgs) {
|
|
657
675
|
var body = {
|
|
658
676
|
repoPath: repoPath,
|
|
659
677
|
repoName: repoPath.split('/').filter(Boolean).pop(),
|
|
660
678
|
};
|
|
661
679
|
if (worktreePath) body.worktreePath = worktreePath;
|
|
680
|
+
if (claudeArgs) body.claudeArgs = claudeArgs;
|
|
662
681
|
|
|
663
682
|
fetch('/sessions', {
|
|
664
683
|
method: 'POST',
|
|
@@ -678,6 +697,7 @@
|
|
|
678
697
|
|
|
679
698
|
newSessionBtn.addEventListener('click', function () {
|
|
680
699
|
customPath.value = '';
|
|
700
|
+
dialogYolo.checked = false;
|
|
681
701
|
populateDialogRootSelect();
|
|
682
702
|
|
|
683
703
|
var sidebarRoot = sidebarRootFilter.value;
|
|
@@ -704,7 +724,8 @@
|
|
|
704
724
|
dialogStart.addEventListener('click', function () {
|
|
705
725
|
var path = customPath.value.trim() || dialogRepoSelect.value;
|
|
706
726
|
if (!path) return;
|
|
707
|
-
|
|
727
|
+
var args = dialogYolo.checked ? ['--dangerously-skip-permissions'] : undefined;
|
|
728
|
+
startSession(path, undefined, args);
|
|
708
729
|
});
|
|
709
730
|
|
|
710
731
|
dialogCancel.addEventListener('click', function () {
|
|
@@ -851,6 +872,73 @@
|
|
|
851
872
|
vv.addEventListener('scroll', onViewportResize);
|
|
852
873
|
})();
|
|
853
874
|
|
|
875
|
+
// ── Update Toast ─────────────────────────────────────────────────────────────
|
|
876
|
+
|
|
877
|
+
function checkForUpdates() {
|
|
878
|
+
fetch('/version')
|
|
879
|
+
.then(function (res) {
|
|
880
|
+
if (!res.ok) return;
|
|
881
|
+
return res.json();
|
|
882
|
+
})
|
|
883
|
+
.then(function (data) {
|
|
884
|
+
if (data && data.updateAvailable) {
|
|
885
|
+
showUpdateToast(data.current, data.latest);
|
|
886
|
+
}
|
|
887
|
+
})
|
|
888
|
+
.catch(function () {
|
|
889
|
+
// Silently ignore version check errors
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
function showUpdateToast(current, latest) {
|
|
894
|
+
updateToastText.textContent = 'Update available: v' + current + ' \u2192 v' + latest;
|
|
895
|
+
updateToast.hidden = false;
|
|
896
|
+
updateToastBtn.disabled = false;
|
|
897
|
+
updateToastBtn.textContent = 'Update Now';
|
|
898
|
+
|
|
899
|
+
updateToastBtn.onclick = function () {
|
|
900
|
+
triggerUpdate(latest);
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
function triggerUpdate(latest) {
|
|
905
|
+
updateToastBtn.disabled = true;
|
|
906
|
+
updateToastBtn.textContent = 'Updating\u2026';
|
|
907
|
+
|
|
908
|
+
fetch('/update', { method: 'POST' })
|
|
909
|
+
.then(function (res) {
|
|
910
|
+
return res.json().then(function (data) {
|
|
911
|
+
return { ok: res.ok, data: data };
|
|
912
|
+
});
|
|
913
|
+
})
|
|
914
|
+
.then(function (result) {
|
|
915
|
+
if (result.ok && result.data.restarting) {
|
|
916
|
+
updateToastText.textContent = 'Updated! Restarting server\u2026';
|
|
917
|
+
updateToastBtn.hidden = true;
|
|
918
|
+
updateToastDismiss.hidden = true;
|
|
919
|
+
setTimeout(function () {
|
|
920
|
+
location.reload();
|
|
921
|
+
}, 5000);
|
|
922
|
+
} else if (result.ok) {
|
|
923
|
+
updateToastText.textContent = 'Updated! Please restart the server manually.';
|
|
924
|
+
updateToastBtn.hidden = true;
|
|
925
|
+
} else {
|
|
926
|
+
updateToastText.textContent = 'Update failed: ' + (result.data.error || 'Unknown error');
|
|
927
|
+
updateToastBtn.disabled = false;
|
|
928
|
+
updateToastBtn.textContent = 'Retry';
|
|
929
|
+
}
|
|
930
|
+
})
|
|
931
|
+
.catch(function () {
|
|
932
|
+
updateToastText.textContent = 'Update failed. Please try again.';
|
|
933
|
+
updateToastBtn.disabled = false;
|
|
934
|
+
updateToastBtn.textContent = 'Retry';
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
updateToastDismiss.addEventListener('click', function () {
|
|
939
|
+
updateToast.hidden = true;
|
|
940
|
+
});
|
|
941
|
+
|
|
854
942
|
// ── Auto-auth Check ─────────────────────────────────────────────────────────
|
|
855
943
|
|
|
856
944
|
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 -->
|
|
@@ -99,6 +110,13 @@
|
|
|
99
110
|
<label for="custom-path-input">Or enter a local path:</label>
|
|
100
111
|
<input type="text" id="custom-path-input" placeholder="/Users/you/code/my-repo" />
|
|
101
112
|
</div>
|
|
113
|
+
<div class="dialog-option">
|
|
114
|
+
<label>
|
|
115
|
+
<input type="checkbox" id="dialog-yolo" />
|
|
116
|
+
Yolo mode
|
|
117
|
+
</label>
|
|
118
|
+
<span class="dialog-option-hint">Skip all permission prompts</span>
|
|
119
|
+
</div>
|
|
102
120
|
<div class="dialog-actions">
|
|
103
121
|
<button id="dialog-cancel">Cancel</button>
|
|
104
122
|
<button id="dialog-start" class="btn-accent">New Worktree</button>
|
|
@@ -124,7 +142,8 @@
|
|
|
124
142
|
|
|
125
143
|
<!-- Context Menu -->
|
|
126
144
|
<div id="context-menu" class="context-menu" hidden>
|
|
127
|
-
<button id="ctx-
|
|
145
|
+
<button id="ctx-resume-yolo" class="context-menu-item">Resume in yolo mode</button>
|
|
146
|
+
<button id="ctx-delete-worktree" class="context-menu-item ctx-danger">Delete worktree</button>
|
|
128
147
|
</div>
|
|
129
148
|
|
|
130
149
|
<!-- Delete Worktree Confirmation Dialog -->
|
package/public/style.css
CHANGED
|
@@ -590,6 +590,35 @@ dialog#new-session-dialog h2 {
|
|
|
590
590
|
border-color: var(--accent);
|
|
591
591
|
}
|
|
592
592
|
|
|
593
|
+
.dialog-option {
|
|
594
|
+
display: flex;
|
|
595
|
+
flex-direction: column;
|
|
596
|
+
gap: 2px;
|
|
597
|
+
margin-bottom: 1rem;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
.dialog-option label {
|
|
601
|
+
display: flex;
|
|
602
|
+
align-items: center;
|
|
603
|
+
gap: 8px;
|
|
604
|
+
font-size: 0.875rem;
|
|
605
|
+
color: var(--text);
|
|
606
|
+
cursor: pointer;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
.dialog-option input[type="checkbox"] {
|
|
610
|
+
width: 16px;
|
|
611
|
+
height: 16px;
|
|
612
|
+
accent-color: var(--accent);
|
|
613
|
+
cursor: pointer;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
.dialog-option-hint {
|
|
617
|
+
font-size: 0.75rem;
|
|
618
|
+
color: var(--text-muted);
|
|
619
|
+
padding-left: 24px;
|
|
620
|
+
}
|
|
621
|
+
|
|
593
622
|
.dialog-actions {
|
|
594
623
|
display: flex;
|
|
595
624
|
gap: 8px;
|
|
@@ -769,6 +798,11 @@ dialog#settings-dialog h2 {
|
|
|
769
798
|
color: var(--accent);
|
|
770
799
|
}
|
|
771
800
|
|
|
801
|
+
.context-menu-item.ctx-danger:hover,
|
|
802
|
+
.context-menu-item.ctx-danger:active {
|
|
803
|
+
color: #c0392b;
|
|
804
|
+
}
|
|
805
|
+
|
|
772
806
|
/* ===== Delete Worktree Dialog ===== */
|
|
773
807
|
dialog#delete-worktree-dialog {
|
|
774
808
|
background: var(--surface);
|
|
@@ -818,6 +852,84 @@ dialog#delete-worktree-dialog h2 {
|
|
|
818
852
|
opacity: 0.85;
|
|
819
853
|
}
|
|
820
854
|
|
|
855
|
+
/* ===== Update Toast ===== */
|
|
856
|
+
|
|
857
|
+
#update-toast {
|
|
858
|
+
position: fixed;
|
|
859
|
+
bottom: 0;
|
|
860
|
+
left: 0;
|
|
861
|
+
right: 0;
|
|
862
|
+
z-index: 150;
|
|
863
|
+
display: flex;
|
|
864
|
+
justify-content: center;
|
|
865
|
+
padding: 12px 12px calc(12px + env(safe-area-inset-bottom));
|
|
866
|
+
pointer-events: none;
|
|
867
|
+
animation: toast-slide-up 0.25s ease-out;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
@keyframes toast-slide-up {
|
|
871
|
+
from {
|
|
872
|
+
transform: translateY(100%);
|
|
873
|
+
opacity: 0;
|
|
874
|
+
}
|
|
875
|
+
to {
|
|
876
|
+
transform: translateY(0);
|
|
877
|
+
opacity: 1;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
#update-toast-content {
|
|
882
|
+
display: flex;
|
|
883
|
+
flex-direction: row;
|
|
884
|
+
align-items: center;
|
|
885
|
+
gap: 12px;
|
|
886
|
+
background: var(--surface);
|
|
887
|
+
border: 1px solid var(--border);
|
|
888
|
+
border-radius: 10px;
|
|
889
|
+
padding: 12px 16px;
|
|
890
|
+
max-width: 500px;
|
|
891
|
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
|
|
892
|
+
pointer-events: auto;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
#update-toast-text {
|
|
896
|
+
flex: 1;
|
|
897
|
+
font-size: 0.85rem;
|
|
898
|
+
color: var(--text);
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
#update-toast-actions {
|
|
902
|
+
display: flex;
|
|
903
|
+
gap: 8px;
|
|
904
|
+
flex-shrink: 0;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
#update-toast-btn {
|
|
908
|
+
padding: 8px 14px;
|
|
909
|
+
border-radius: 6px;
|
|
910
|
+
font-size: 0.8rem;
|
|
911
|
+
border: none;
|
|
912
|
+
white-space: nowrap;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
#update-toast-btn:disabled {
|
|
916
|
+
opacity: 0.6;
|
|
917
|
+
cursor: not-allowed;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
#update-toast-dismiss {
|
|
921
|
+
background: none;
|
|
922
|
+
border: none;
|
|
923
|
+
color: var(--text-muted);
|
|
924
|
+
font-size: 1.2rem;
|
|
925
|
+
padding: 4px 6px;
|
|
926
|
+
cursor: pointer;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
#update-toast-dismiss:hover {
|
|
930
|
+
color: var(--text);
|
|
931
|
+
}
|
|
932
|
+
|
|
821
933
|
/* ===== Mobile Responsive ===== */
|
|
822
934
|
@media (max-width: 600px) {
|
|
823
935
|
#mobile-header {
|