claude-remote-cli 3.4.0 → 3.4.1
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/git.js +38 -1
- package/dist/server/index.js +22 -2
- package/dist/server/sessions.js +1 -1
- package/dist/server/ws.js +9 -6
- package/dist/test/branch-rename.test.js +17 -0
- package/package.json +1 -1
package/dist/server/git.js
CHANGED
|
@@ -178,6 +178,10 @@ async function getPrForBranch(repoPath, branch, options = {}) {
|
|
|
178
178
|
return null;
|
|
179
179
|
try {
|
|
180
180
|
const data = JSON.parse(stdout);
|
|
181
|
+
// Only return OPEN PRs — gh pr view returns merged/closed PRs too
|
|
182
|
+
if (data.state !== 'OPEN') {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
181
185
|
return {
|
|
182
186
|
number: data.number,
|
|
183
187
|
title: data.title,
|
|
@@ -272,4 +276,37 @@ async function getWorkingTreeDiff(repoPath, exec = execFileAsync) {
|
|
|
272
276
|
return { additions: 0, deletions: 0 };
|
|
273
277
|
}
|
|
274
278
|
}
|
|
275
|
-
|
|
279
|
+
/**
|
|
280
|
+
* Convert a git branch name to a human-readable display name.
|
|
281
|
+
* "fix-mobile-scroll-bug" → "Fix mobile scroll bug"
|
|
282
|
+
* "feature/add-auth" → "Add auth"
|
|
283
|
+
*/
|
|
284
|
+
function branchToDisplayName(branch) {
|
|
285
|
+
const stripped = branch.replace(/^(feature|fix|chore|refactor|docs|test|ci|build)\//i, '');
|
|
286
|
+
const words = stripped.replace(/[-_]/g, ' ').trim();
|
|
287
|
+
if (!words)
|
|
288
|
+
return branch;
|
|
289
|
+
return words.charAt(0).toUpperCase() + words.slice(1);
|
|
290
|
+
}
|
|
291
|
+
async function isBranchStale(repoPath, branch, options = {}) {
|
|
292
|
+
const run = options.exec || execFileAsync;
|
|
293
|
+
try {
|
|
294
|
+
for (const base of ['main', 'master']) {
|
|
295
|
+
try {
|
|
296
|
+
const { stdout } = await run('git', ['rev-list', '--count', `${base}..${branch}`], { cwd: repoPath, timeout: 5000 });
|
|
297
|
+
const count = parseInt(stdout.trim(), 10);
|
|
298
|
+
if (count === 0)
|
|
299
|
+
return true;
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
catch {
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
export { listBranches, normalizeBranchNames, getActivityFeed, getCiStatus, getPrForBranch, getUnresolvedCommentCount, switchBranch, getCommitsAhead, getCurrentBranch, getWorkingTreeDiff, branchToDisplayName, isBranchStale, };
|
package/dist/server/index.js
CHANGED
|
@@ -16,7 +16,7 @@ import { setupWebSocket } from './ws.js';
|
|
|
16
16
|
import { WorktreeWatcher, WORKTREE_DIRS, isValidWorktreePath, parseWorktreeListPorcelain, parseAllWorktrees } from './watcher.js';
|
|
17
17
|
import { isInstalled as serviceIsInstalled } from './service.js';
|
|
18
18
|
import { extensionForMime, setClipboardImage } from './clipboard.js';
|
|
19
|
-
import { listBranches } from './git.js';
|
|
19
|
+
import { listBranches, isBranchStale } from './git.js';
|
|
20
20
|
import * as push from './push.js';
|
|
21
21
|
import { createWorkspaceRouter } from './workspaces.js';
|
|
22
22
|
import { MOUNTAIN_NAMES } from './types.js';
|
|
@@ -559,10 +559,30 @@ async function main() {
|
|
|
559
559
|
let resolvedBranch = '';
|
|
560
560
|
let isMountainName = false;
|
|
561
561
|
if (worktreePath) {
|
|
562
|
+
// Check if the worktree's branch is stale (merged/at base) and needs a fresh name
|
|
563
|
+
const currentBranchResult = await execFileAsync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: worktreePath }).catch(() => null);
|
|
564
|
+
const currentBranch = currentBranchResult?.stdout.trim();
|
|
565
|
+
if (currentBranch && !needsBranchRename) {
|
|
566
|
+
const stale = await isBranchStale(worktreePath, currentBranch);
|
|
567
|
+
if (stale) {
|
|
568
|
+
// Generate unique temp branch: <mountain>-<short-timestamp>
|
|
569
|
+
const mountainName = worktreePath.split('/').pop() || 'branch';
|
|
570
|
+
const suffix = Date.now().toString(36).slice(-4);
|
|
571
|
+
const tempBranch = `${mountainName}-${suffix}`;
|
|
572
|
+
try {
|
|
573
|
+
await execFileAsync('git', ['checkout', '-b', tempBranch], { cwd: worktreePath });
|
|
574
|
+
}
|
|
575
|
+
catch {
|
|
576
|
+
await execFileAsync('git', ['branch', '-m', tempBranch], { cwd: worktreePath }).catch(() => { });
|
|
577
|
+
}
|
|
578
|
+
isMountainName = true;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
562
581
|
// Only use --continue if:
|
|
563
582
|
// 1. Not a brand-new worktree (needsBranchRename flag)
|
|
564
583
|
// 2. A prior Claude session exists in this directory (.claude/ dir present)
|
|
565
|
-
|
|
584
|
+
// 3. Branch is not stale (isMountainName means we just created a fresh branch)
|
|
585
|
+
const hasPriorSession = !needsBranchRename && !isMountainName && fs.existsSync(path.join(worktreePath, '.claude'));
|
|
566
586
|
args = hasPriorSession ? [...AGENT_CONTINUE_ARGS[resolvedAgent], ...baseArgs] : [...baseArgs];
|
|
567
587
|
cwd = worktreePath;
|
|
568
588
|
sessionRepoPath = worktreePath;
|
package/dist/server/sessions.js
CHANGED
|
@@ -295,7 +295,7 @@ async function fetchMetaForSession(session) {
|
|
|
295
295
|
if (branch) {
|
|
296
296
|
try {
|
|
297
297
|
const pr = await getPrForBranch(repoPath, branch);
|
|
298
|
-
if (pr
|
|
298
|
+
if (pr) {
|
|
299
299
|
prNumber = pr.number;
|
|
300
300
|
additions = pr.additions;
|
|
301
301
|
deletions = pr.deletions;
|
package/dist/server/ws.js
CHANGED
|
@@ -3,6 +3,7 @@ import { execFile } from 'node:child_process';
|
|
|
3
3
|
import { promisify } from 'node:util';
|
|
4
4
|
import * as sessions from './sessions.js';
|
|
5
5
|
import { writeMeta } from './config.js';
|
|
6
|
+
import { branchToDisplayName } from './git.js';
|
|
6
7
|
const execFileAsync = promisify(execFile);
|
|
7
8
|
const BRANCH_POLL_INTERVAL_MS = 3000;
|
|
8
9
|
const BRANCH_POLL_MAX_ATTEMPTS = 10;
|
|
@@ -20,12 +21,13 @@ function startBranchWatcher(session, broadcastEvent, cfgPath) {
|
|
|
20
21
|
const currentBranch = stdout.trim();
|
|
21
22
|
if (currentBranch && currentBranch !== originalBranch) {
|
|
22
23
|
clearInterval(timer);
|
|
24
|
+
const displayName = branchToDisplayName(currentBranch);
|
|
23
25
|
session.branchName = currentBranch;
|
|
24
|
-
session.displayName =
|
|
25
|
-
broadcastEvent('session-renamed', { sessionId: session.id, branchName: currentBranch, displayName
|
|
26
|
+
session.displayName = displayName;
|
|
27
|
+
broadcastEvent('session-renamed', { sessionId: session.id, branchName: currentBranch, displayName });
|
|
26
28
|
writeMeta(cfgPath, {
|
|
27
29
|
worktreePath: session.repoPath,
|
|
28
|
-
displayName
|
|
30
|
+
displayName,
|
|
29
31
|
lastActivity: new Date().toISOString(),
|
|
30
32
|
branchName: currentBranch,
|
|
31
33
|
});
|
|
@@ -147,16 +149,17 @@ function setupWebSocket(server, authenticatedTokens, watcher, configPath) {
|
|
|
147
149
|
ptySession._renameBuffer = '';
|
|
148
150
|
const enterIndex = str.indexOf('\r');
|
|
149
151
|
if (enterIndex === -1) {
|
|
150
|
-
// Buffer without passthrough — don't echo to PTY during buffering.
|
|
151
|
-
// Previously we wrote to PTY here for echo and used Ctrl+U to undo on Enter,
|
|
152
|
-
// but Ctrl+U doesn't work reliably in Claude Code's Ink/React TUI.
|
|
153
152
|
ptySession._renameBuffer += str;
|
|
153
|
+
ptySession.pty.write(str); // Echo to terminal so user sees what they type
|
|
154
154
|
return;
|
|
155
155
|
}
|
|
156
156
|
// Enter detected — send rename prompt + full message to PTY in one shot
|
|
157
157
|
const buffered = ptySession._renameBuffer;
|
|
158
158
|
const beforeEnter = buffered + str.slice(0, enterIndex);
|
|
159
159
|
const afterEnter = str.slice(enterIndex); // includes the \r
|
|
160
|
+
// Clear the echoed input line before writing the full prompt+message
|
|
161
|
+
const clearLine = '\r' + ' '.repeat(Math.min(beforeEnter.length + 2, 200)) + '\r';
|
|
162
|
+
ptySession.pty.write(clearLine);
|
|
160
163
|
const renamePrompt = `Before doing anything else, rename the current git branch using \`git branch -m <new-name>\`. Choose a short, descriptive kebab-case branch name based on the task below.${ptySession.branchRenamePrompt ? ' User preferences: ' + ptySession.branchRenamePrompt : ''} Do not ask for confirmation — just rename and proceed.\n\n`;
|
|
161
164
|
ptySession.pty.write(renamePrompt + beforeEnter + afterEnter);
|
|
162
165
|
ptySession.needsBranchRename = false;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { test, describe } from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
3
|
import { MOUNTAIN_NAMES } from '../server/types.js';
|
|
4
|
+
import { branchToDisplayName } from '../server/git.js';
|
|
4
5
|
describe('MOUNTAIN_NAMES', () => {
|
|
5
6
|
test('contains 30 mountain names', () => {
|
|
6
7
|
assert.equal(MOUNTAIN_NAMES.length, 30);
|
|
@@ -26,3 +27,19 @@ describe('MOUNTAIN_NAMES', () => {
|
|
|
26
27
|
assert.equal(name3, 'everest'); // wraps back to start
|
|
27
28
|
});
|
|
28
29
|
});
|
|
30
|
+
describe('branchToDisplayName', () => {
|
|
31
|
+
test('converts kebab-case to sentence case', () => {
|
|
32
|
+
assert.equal(branchToDisplayName('fix-mobile-scroll-bug'), 'Fix mobile scroll bug');
|
|
33
|
+
});
|
|
34
|
+
test('strips common branch prefixes', () => {
|
|
35
|
+
assert.equal(branchToDisplayName('feature/add-auth'), 'Add auth');
|
|
36
|
+
assert.equal(branchToDisplayName('fix/api-timeout'), 'Api timeout');
|
|
37
|
+
assert.equal(branchToDisplayName('chore/update-deps'), 'Update deps');
|
|
38
|
+
});
|
|
39
|
+
test('handles simple names', () => {
|
|
40
|
+
assert.equal(branchToDisplayName('lhotse'), 'Lhotse');
|
|
41
|
+
});
|
|
42
|
+
test('handles underscores', () => {
|
|
43
|
+
assert.equal(branchToDisplayName('fix_the_thing'), 'Fix the thing');
|
|
44
|
+
});
|
|
45
|
+
});
|