claude-remote-cli 1.6.0 → 1.7.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/config.js +29 -0
- package/dist/server/index.js +14 -7
- package/dist/server/sessions.js +22 -1
- package/package.json +1 -1
- package/public/app.js +31 -2
- package/public/style.css +13 -0
package/dist/server/config.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import crypto from 'node:crypto';
|
|
2
4
|
export const DEFAULTS = {
|
|
3
5
|
host: '0.0.0.0',
|
|
4
6
|
port: 3456,
|
|
@@ -18,3 +20,30 @@ export function loadConfig(configPath) {
|
|
|
18
20
|
export function saveConfig(configPath, config) {
|
|
19
21
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
|
|
20
22
|
}
|
|
23
|
+
function metaDir(configPath) {
|
|
24
|
+
return path.join(path.dirname(configPath), 'worktree-meta');
|
|
25
|
+
}
|
|
26
|
+
function metaFilePath(configPath, worktreePath) {
|
|
27
|
+
const hash = crypto.createHash('sha256').update(worktreePath).digest('hex').slice(0, 16);
|
|
28
|
+
return path.join(metaDir(configPath), hash + '.json');
|
|
29
|
+
}
|
|
30
|
+
export function ensureMetaDir(configPath) {
|
|
31
|
+
const dir = metaDir(configPath);
|
|
32
|
+
if (!fs.existsSync(dir)) {
|
|
33
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export function readMeta(configPath, worktreePath) {
|
|
37
|
+
const fp = metaFilePath(configPath, worktreePath);
|
|
38
|
+
try {
|
|
39
|
+
return JSON.parse(fs.readFileSync(fp, 'utf8'));
|
|
40
|
+
}
|
|
41
|
+
catch (_) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export function writeMeta(configPath, meta) {
|
|
46
|
+
const fp = metaFilePath(configPath, meta.worktreePath);
|
|
47
|
+
ensureMetaDir(configPath);
|
|
48
|
+
fs.writeFileSync(fp, JSON.stringify(meta, null, 2), 'utf8');
|
|
49
|
+
}
|
package/dist/server/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import { execFile } from 'node:child_process';
|
|
|
8
8
|
import { promisify } from 'node:util';
|
|
9
9
|
import express from 'express';
|
|
10
10
|
import cookieParser from 'cookie-parser';
|
|
11
|
-
import { loadConfig, saveConfig, DEFAULTS } from './config.js';
|
|
11
|
+
import { loadConfig, saveConfig, DEFAULTS, readMeta, writeMeta, ensureMetaDir } from './config.js';
|
|
12
12
|
import * as auth from './auth.js';
|
|
13
13
|
import * as sessions from './sessions.js';
|
|
14
14
|
import { setupWebSocket } from './ws.js';
|
|
@@ -21,6 +21,8 @@ const execFileAsync = promisify(execFile);
|
|
|
21
21
|
// When run via CLI bin, config lives in ~/.config/claude-remote-cli/
|
|
22
22
|
// When run directly (development), fall back to local config.json
|
|
23
23
|
const CONFIG_PATH = process.env.CLAUDE_REMOTE_CONFIG || path.join(__dirname, '..', '..', 'config.json');
|
|
24
|
+
// Ensure worktree metadata directory exists alongside config
|
|
25
|
+
ensureMetaDir(CONFIG_PATH);
|
|
24
26
|
const VERSION_CACHE_TTL = 5 * 60 * 1000;
|
|
25
27
|
let versionCache = null;
|
|
26
28
|
function getCurrentVersion() {
|
|
@@ -216,12 +218,16 @@ async function main() {
|
|
|
216
218
|
for (const entry of entries) {
|
|
217
219
|
if (!entry.isDirectory())
|
|
218
220
|
continue;
|
|
221
|
+
const wtPath = path.join(worktreeDir, entry.name);
|
|
222
|
+
const meta = readMeta(CONFIG_PATH, wtPath);
|
|
219
223
|
worktrees.push({
|
|
220
224
|
name: entry.name,
|
|
221
|
-
path:
|
|
225
|
+
path: wtPath,
|
|
222
226
|
repoName: repo.name,
|
|
223
227
|
repoPath: repo.path,
|
|
224
228
|
root: repo.root,
|
|
229
|
+
displayName: meta ? meta.displayName : '',
|
|
230
|
+
lastActivity: meta ? meta.lastActivity : '',
|
|
225
231
|
});
|
|
226
232
|
}
|
|
227
233
|
}
|
|
@@ -327,8 +333,8 @@ async function main() {
|
|
|
327
333
|
let cwd;
|
|
328
334
|
let worktreeName;
|
|
329
335
|
if (worktreePath) {
|
|
330
|
-
// Resume existing worktree — run claude inside the worktree directory
|
|
331
|
-
args = [...baseArgs];
|
|
336
|
+
// Resume existing worktree — run claude --continue inside the worktree directory
|
|
337
|
+
args = ['--continue', ...baseArgs];
|
|
332
338
|
cwd = worktreePath;
|
|
333
339
|
worktreeName = worktreePath.split('/').pop() || '';
|
|
334
340
|
}
|
|
@@ -346,6 +352,7 @@ async function main() {
|
|
|
346
352
|
displayName: worktreeName,
|
|
347
353
|
command: config.claudeCommand,
|
|
348
354
|
args,
|
|
355
|
+
configPath: CONFIG_PATH,
|
|
349
356
|
});
|
|
350
357
|
res.status(201).json(session);
|
|
351
358
|
});
|
|
@@ -359,7 +366,7 @@ async function main() {
|
|
|
359
366
|
res.status(404).json({ error: 'Session not found' });
|
|
360
367
|
}
|
|
361
368
|
});
|
|
362
|
-
// PATCH /sessions/:id — update displayName and
|
|
369
|
+
// PATCH /sessions/:id — update displayName and persist to metadata
|
|
363
370
|
app.patch('/sessions/:id', requireAuth, (req, res) => {
|
|
364
371
|
const { displayName } = req.body;
|
|
365
372
|
if (!displayName) {
|
|
@@ -370,8 +377,8 @@ async function main() {
|
|
|
370
377
|
const id = req.params['id'];
|
|
371
378
|
const updated = sessions.updateDisplayName(id, displayName);
|
|
372
379
|
const session = sessions.get(id);
|
|
373
|
-
if (session
|
|
374
|
-
|
|
380
|
+
if (session) {
|
|
381
|
+
writeMeta(CONFIG_PATH, { worktreePath: session.repoPath, displayName, lastActivity: session.lastActivity });
|
|
375
382
|
}
|
|
376
383
|
res.json(updated);
|
|
377
384
|
}
|
package/dist/server/sessions.js
CHANGED
|
@@ -3,9 +3,10 @@ import crypto from 'node:crypto';
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import os from 'node:os';
|
|
5
5
|
import path from 'node:path';
|
|
6
|
+
import { readMeta, writeMeta } from './config.js';
|
|
6
7
|
// In-memory registry: id -> Session
|
|
7
8
|
const sessions = new Map();
|
|
8
|
-
function create({ repoName, repoPath, root, worktreeName, displayName, command, args = [], cols = 80, rows = 24 }) {
|
|
9
|
+
function create({ repoName, repoPath, root, worktreeName, displayName, command, args = [], cols = 80, rows = 24, configPath }) {
|
|
9
10
|
const id = crypto.randomBytes(8).toString('hex');
|
|
10
11
|
const createdAt = new Date().toISOString();
|
|
11
12
|
// Strip CLAUDECODE env var to allow spawning claude inside a claude-managed server
|
|
@@ -35,6 +36,15 @@ function create({ repoName, repoPath, root, worktreeName, displayName, command,
|
|
|
35
36
|
scrollback,
|
|
36
37
|
};
|
|
37
38
|
sessions.set(id, session);
|
|
39
|
+
// Load existing metadata to preserve a previously-set displayName
|
|
40
|
+
if (configPath && worktreeName) {
|
|
41
|
+
const existing = readMeta(configPath, repoPath);
|
|
42
|
+
if (existing && existing.displayName) {
|
|
43
|
+
session.displayName = existing.displayName;
|
|
44
|
+
}
|
|
45
|
+
writeMeta(configPath, { worktreePath: repoPath, displayName: session.displayName, lastActivity: createdAt });
|
|
46
|
+
}
|
|
47
|
+
let metaFlushTimer = null;
|
|
38
48
|
ptyProcess.onData((data) => {
|
|
39
49
|
session.lastActivity = new Date().toISOString();
|
|
40
50
|
scrollback.push(data);
|
|
@@ -43,8 +53,19 @@ function create({ repoName, repoPath, root, worktreeName, displayName, command,
|
|
|
43
53
|
while (scrollbackBytes > MAX_SCROLLBACK && scrollback.length > 1) {
|
|
44
54
|
scrollbackBytes -= scrollback.shift().length;
|
|
45
55
|
}
|
|
56
|
+
if (configPath && worktreeName && !metaFlushTimer) {
|
|
57
|
+
metaFlushTimer = setTimeout(() => {
|
|
58
|
+
metaFlushTimer = null;
|
|
59
|
+
writeMeta(configPath, { worktreePath: repoPath, displayName: session.displayName, lastActivity: session.lastActivity });
|
|
60
|
+
}, 5000);
|
|
61
|
+
}
|
|
46
62
|
});
|
|
47
63
|
ptyProcess.onExit(() => {
|
|
64
|
+
if (metaFlushTimer)
|
|
65
|
+
clearTimeout(metaFlushTimer);
|
|
66
|
+
if (configPath && worktreeName) {
|
|
67
|
+
writeMeta(configPath, { worktreePath: repoPath, displayName: session.displayName, lastActivity: session.lastActivity });
|
|
68
|
+
}
|
|
48
69
|
sessions.delete(id);
|
|
49
70
|
const tmpDir = path.join(os.tmpdir(), 'claude-remote-cli', id);
|
|
50
71
|
fs.rm(tmpDir, { recursive: true, force: true }, () => { });
|
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -320,6 +320,24 @@
|
|
|
320
320
|
return path.split('/').filter(Boolean).pop() || path;
|
|
321
321
|
}
|
|
322
322
|
|
|
323
|
+
function formatRelativeTime(isoString) {
|
|
324
|
+
if (!isoString) return '';
|
|
325
|
+
var now = Date.now();
|
|
326
|
+
var then = new Date(isoString).getTime();
|
|
327
|
+
var diffSec = Math.floor((now - then) / 1000);
|
|
328
|
+
if (diffSec < 60) return 'just now';
|
|
329
|
+
var diffMin = Math.floor(diffSec / 60);
|
|
330
|
+
if (diffMin < 60) return diffMin + ' min ago';
|
|
331
|
+
var diffHr = Math.floor(diffMin / 60);
|
|
332
|
+
if (diffHr < 24) return diffHr + (diffHr === 1 ? ' hour ago' : ' hours ago');
|
|
333
|
+
var diffDay = Math.floor(diffHr / 24);
|
|
334
|
+
if (diffDay === 1) return 'yesterday';
|
|
335
|
+
if (diffDay < 7) return diffDay + ' days ago';
|
|
336
|
+
var d = new Date(isoString);
|
|
337
|
+
var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
|
|
338
|
+
return months[d.getMonth()] + ' ' + d.getDate();
|
|
339
|
+
}
|
|
340
|
+
|
|
323
341
|
function renderUnifiedList() {
|
|
324
342
|
var rootFilter = sidebarRootFilter.value;
|
|
325
343
|
var repoFilter = sidebarRepoFilter.value;
|
|
@@ -396,6 +414,11 @@
|
|
|
396
414
|
infoDiv.appendChild(nameSpan);
|
|
397
415
|
infoDiv.appendChild(subSpan);
|
|
398
416
|
|
|
417
|
+
var timeSpan = document.createElement('span');
|
|
418
|
+
timeSpan.className = 'session-time';
|
|
419
|
+
timeSpan.textContent = formatRelativeTime(session.lastActivity);
|
|
420
|
+
infoDiv.appendChild(timeSpan);
|
|
421
|
+
|
|
399
422
|
var actionsDiv = document.createElement('div');
|
|
400
423
|
actionsDiv.className = 'session-actions';
|
|
401
424
|
|
|
@@ -439,8 +462,9 @@
|
|
|
439
462
|
|
|
440
463
|
var nameSpan = document.createElement('span');
|
|
441
464
|
nameSpan.className = 'session-name';
|
|
442
|
-
|
|
443
|
-
nameSpan.
|
|
465
|
+
var wtDisplayName = wt.displayName || wt.name;
|
|
466
|
+
nameSpan.textContent = wtDisplayName;
|
|
467
|
+
nameSpan.title = wtDisplayName;
|
|
444
468
|
|
|
445
469
|
var subSpan = document.createElement('span');
|
|
446
470
|
subSpan.className = 'session-sub';
|
|
@@ -449,6 +473,11 @@
|
|
|
449
473
|
infoDiv.appendChild(nameSpan);
|
|
450
474
|
infoDiv.appendChild(subSpan);
|
|
451
475
|
|
|
476
|
+
var timeSpan = document.createElement('span');
|
|
477
|
+
timeSpan.className = 'session-time';
|
|
478
|
+
timeSpan.textContent = formatRelativeTime(wt.lastActivity);
|
|
479
|
+
infoDiv.appendChild(timeSpan);
|
|
480
|
+
|
|
452
481
|
li.appendChild(infoDiv);
|
|
453
482
|
|
|
454
483
|
// Click to resume (but not if context menu just opened or long-press fired)
|
package/public/style.css
CHANGED
|
@@ -242,6 +242,10 @@ html, body {
|
|
|
242
242
|
color: rgba(255, 255, 255, 0.7);
|
|
243
243
|
}
|
|
244
244
|
|
|
245
|
+
#session-list li.active-session.active .session-time {
|
|
246
|
+
color: rgba(255, 255, 255, 0.5);
|
|
247
|
+
}
|
|
248
|
+
|
|
245
249
|
/* Inactive worktree (outline) */
|
|
246
250
|
#session-list li.inactive-worktree {
|
|
247
251
|
background: transparent;
|
|
@@ -280,6 +284,15 @@ html, body {
|
|
|
280
284
|
white-space: nowrap;
|
|
281
285
|
}
|
|
282
286
|
|
|
287
|
+
.session-time {
|
|
288
|
+
font-size: 0.65rem;
|
|
289
|
+
color: var(--text-muted);
|
|
290
|
+
opacity: 0.6;
|
|
291
|
+
overflow: hidden;
|
|
292
|
+
text-overflow: ellipsis;
|
|
293
|
+
white-space: nowrap;
|
|
294
|
+
}
|
|
295
|
+
|
|
283
296
|
.session-actions {
|
|
284
297
|
display: flex;
|
|
285
298
|
align-items: center;
|