claude-remote-cli 3.5.2 → 3.5.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.
- package/dist/frontend/assets/{index-D8zGa-Fd.css → index-CiwYPknn.css} +2 -2
- package/dist/frontend/assets/{index-DnrS1YFL.js → index-QZrLSCSL.js} +21 -21
- package/dist/frontend/index.html +2 -2
- package/dist/server/config.js +22 -0
- package/dist/server/index.js +16 -14
- package/dist/server/workspaces.js +38 -8
- package/dist/test/config.test.js +114 -1
- package/package.json +1 -1
package/dist/frontend/index.html
CHANGED
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
|
12
12
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
|
13
13
|
<meta name="theme-color" content="#1a1a1a" />
|
|
14
|
-
<script type="module" crossorigin src="/assets/index-
|
|
15
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
14
|
+
<script type="module" crossorigin src="/assets/index-QZrLSCSL.js"></script>
|
|
15
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CiwYPknn.css">
|
|
16
16
|
</head>
|
|
17
17
|
<body>
|
|
18
18
|
<div id="app"></div>
|
package/dist/server/config.js
CHANGED
|
@@ -74,6 +74,28 @@ export function getWorkspaceSettings(config, workspacePath) {
|
|
|
74
74
|
// Per-workspace settings override global — only for defined keys
|
|
75
75
|
return { ...globalDefaults, ...perWorkspace };
|
|
76
76
|
}
|
|
77
|
+
export function resolveSessionSettings(config, repoPath, overrides) {
|
|
78
|
+
const ws = getWorkspaceSettings(config, repoPath);
|
|
79
|
+
return {
|
|
80
|
+
agent: overrides.agent ?? ws.defaultAgent ?? 'claude',
|
|
81
|
+
yolo: overrides.yolo ?? ws.defaultYolo ?? false,
|
|
82
|
+
continue: overrides.continue ?? ws.defaultContinue ?? true,
|
|
83
|
+
useTmux: overrides.useTmux ?? ws.launchInTmux ?? false,
|
|
84
|
+
claudeArgs: overrides.claudeArgs ?? ws.claudeArgs ?? [],
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
export function deleteWorkspaceSettingKeys(configPath, config, workspacePath, keys) {
|
|
88
|
+
if (!config.workspaceSettings?.[workspacePath])
|
|
89
|
+
return;
|
|
90
|
+
for (const key of keys) {
|
|
91
|
+
delete config.workspaceSettings[workspacePath][key];
|
|
92
|
+
}
|
|
93
|
+
// Clean up empty workspace entries
|
|
94
|
+
if (Object.keys(config.workspaceSettings[workspacePath]).length === 0) {
|
|
95
|
+
delete config.workspaceSettings[workspacePath];
|
|
96
|
+
}
|
|
97
|
+
saveConfig(configPath, config);
|
|
98
|
+
}
|
|
77
99
|
export function setWorkspaceSettings(configPath, config, workspacePath, settings) {
|
|
78
100
|
if (!config.workspaceSettings)
|
|
79
101
|
config.workspaceSettings = {};
|
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, readMeta, writeMeta, deleteMeta, ensureMetaDir } from './config.js';
|
|
11
|
+
import { loadConfig, saveConfig, DEFAULTS, readMeta, writeMeta, deleteMeta, ensureMetaDir, resolveSessionSettings } from './config.js';
|
|
12
12
|
import * as auth from './auth.js';
|
|
13
13
|
import * as sessions from './sessions.js';
|
|
14
14
|
import { AGENT_CONTINUE_ARGS, AGENT_YOLO_ARGS, serializeAll, restoreFromDisk, activeTmuxSessionNames, populateMetaCache } from './sessions.js';
|
|
@@ -542,12 +542,12 @@ async function main() {
|
|
|
542
542
|
// Sanitize optional terminal dimensions
|
|
543
543
|
const safeCols = typeof cols === 'number' && Number.isFinite(cols) && cols >= 1 && cols <= 500 ? Math.round(cols) : undefined;
|
|
544
544
|
const safeRows = typeof rows === 'number' && Number.isFinite(rows) && rows >= 1 && rows <= 200 ? Math.round(rows) : undefined;
|
|
545
|
-
const
|
|
545
|
+
const resolved = resolveSessionSettings(config, repoPath, { agent, yolo, useTmux, claudeArgs });
|
|
546
|
+
const resolvedAgent = resolved.agent;
|
|
546
547
|
const name = repoName || repoPath.split('/').filter(Boolean).pop() || 'session';
|
|
547
548
|
const baseArgs = [
|
|
548
|
-
...(
|
|
549
|
-
...(yolo ? AGENT_YOLO_ARGS[resolvedAgent] : []),
|
|
550
|
-
...(claudeArgs || []),
|
|
549
|
+
...(resolved.claudeArgs),
|
|
550
|
+
...(resolved.yolo ? AGENT_YOLO_ARGS[resolvedAgent] : []),
|
|
551
551
|
];
|
|
552
552
|
// Compute root by matching repoPath against configured rootDirs
|
|
553
553
|
const roots = config.rootDirs || [];
|
|
@@ -653,7 +653,7 @@ async function main() {
|
|
|
653
653
|
root,
|
|
654
654
|
displayName: name,
|
|
655
655
|
args: baseArgs,
|
|
656
|
-
useTmux: useTmux
|
|
656
|
+
useTmux: resolved.useTmux,
|
|
657
657
|
...(safeCols != null && { cols: safeCols }),
|
|
658
658
|
...(safeRows != null && { rows: safeRows }),
|
|
659
659
|
});
|
|
@@ -679,7 +679,7 @@ async function main() {
|
|
|
679
679
|
displayName: displayNameVal,
|
|
680
680
|
args,
|
|
681
681
|
configPath: CONFIG_PATH,
|
|
682
|
-
useTmux: useTmux
|
|
682
|
+
useTmux: resolved.useTmux,
|
|
683
683
|
...(safeCols != null && { cols: safeCols }),
|
|
684
684
|
...(safeRows != null && { rows: safeRows }),
|
|
685
685
|
});
|
|
@@ -724,7 +724,7 @@ async function main() {
|
|
|
724
724
|
displayName,
|
|
725
725
|
args,
|
|
726
726
|
configPath: CONFIG_PATH,
|
|
727
|
-
useTmux: useTmux
|
|
727
|
+
useTmux: resolved.useTmux,
|
|
728
728
|
...(safeCols != null && { cols: safeCols }),
|
|
729
729
|
...(safeRows != null && { rows: safeRows }),
|
|
730
730
|
needsBranchRename: isMountainName || (needsBranchRename ?? false),
|
|
@@ -747,18 +747,20 @@ async function main() {
|
|
|
747
747
|
res.status(400).json({ error: 'repoPath is required' });
|
|
748
748
|
return;
|
|
749
749
|
}
|
|
750
|
-
const
|
|
750
|
+
const resolved = resolveSessionSettings(config, repoPath, {
|
|
751
|
+
agent, yolo, continue: continueSession, useTmux, claudeArgs,
|
|
752
|
+
});
|
|
753
|
+
const resolvedAgent = resolved.agent;
|
|
751
754
|
// Sanitize optional terminal dimensions
|
|
752
755
|
const safeCols = typeof cols === 'number' && Number.isFinite(cols) && cols >= 1 && cols <= 500 ? Math.round(cols) : undefined;
|
|
753
756
|
const safeRows = typeof rows === 'number' && Number.isFinite(rows) && rows >= 1 && rows <= 200 ? Math.round(rows) : undefined;
|
|
754
757
|
// Multiple sessions per repo allowed (multi-tab support)
|
|
755
758
|
const name = repoName || repoPath.split('/').filter(Boolean).pop() || 'session';
|
|
756
759
|
const baseArgs = [
|
|
757
|
-
...(
|
|
758
|
-
...(yolo ? AGENT_YOLO_ARGS[resolvedAgent] : []),
|
|
759
|
-
...(claudeArgs || []),
|
|
760
|
+
...(resolved.claudeArgs),
|
|
761
|
+
...(resolved.yolo ? AGENT_YOLO_ARGS[resolvedAgent] : []),
|
|
760
762
|
];
|
|
761
|
-
const args =
|
|
763
|
+
const args = resolved.continue ? [...AGENT_CONTINUE_ARGS[resolvedAgent], ...baseArgs] : [...baseArgs];
|
|
762
764
|
const roots = config.rootDirs || [];
|
|
763
765
|
const root = roots.find(function (r) { return repoPath.startsWith(r); }) || '';
|
|
764
766
|
let branchName = '';
|
|
@@ -777,7 +779,7 @@ async function main() {
|
|
|
777
779
|
displayName: name,
|
|
778
780
|
args,
|
|
779
781
|
branchName,
|
|
780
|
-
useTmux: useTmux
|
|
782
|
+
useTmux: resolved.useTmux,
|
|
781
783
|
...(safeCols != null && { cols: safeCols }),
|
|
782
784
|
...(safeRows != null && { rows: safeRows }),
|
|
783
785
|
});
|
|
@@ -4,7 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import { execFile } from 'node:child_process';
|
|
5
5
|
import { promisify } from 'node:util';
|
|
6
6
|
import { Router } from 'express';
|
|
7
|
-
import { loadConfig, saveConfig, getWorkspaceSettings, setWorkspaceSettings } from './config.js';
|
|
7
|
+
import { loadConfig, saveConfig, getWorkspaceSettings, setWorkspaceSettings, deleteWorkspaceSettingKeys } from './config.js';
|
|
8
8
|
import { listBranches, getActivityFeed, getCiStatus, getPrForBranch, getUnresolvedCommentCount, switchBranch, getCurrentBranch } from './git.js';
|
|
9
9
|
import { MOUNTAIN_NAMES } from './types.js';
|
|
10
10
|
const execFileAsync = promisify(execFile);
|
|
@@ -341,14 +341,27 @@ export function createWorkspaceRouter(deps) {
|
|
|
341
341
|
// -------------------------------------------------------------------------
|
|
342
342
|
router.get('/settings', async (req, res) => {
|
|
343
343
|
const workspacePath = typeof req.query.path === 'string' ? req.query.path : undefined;
|
|
344
|
+
const merged = req.query.merged === 'true';
|
|
344
345
|
if (!workspacePath) {
|
|
345
346
|
res.status(400).json({ error: 'path query parameter is required' });
|
|
346
347
|
return;
|
|
347
348
|
}
|
|
348
349
|
const config = getConfig();
|
|
349
350
|
const resolved = path.resolve(workspacePath);
|
|
350
|
-
|
|
351
|
-
|
|
351
|
+
if (merged) {
|
|
352
|
+
const wsOverrides = config.workspaceSettings?.[resolved] ?? {};
|
|
353
|
+
const effective = getWorkspaceSettings(config, resolved);
|
|
354
|
+
const overridden = [];
|
|
355
|
+
for (const key of ['defaultAgent', 'defaultContinue', 'defaultYolo', 'launchInTmux']) {
|
|
356
|
+
if (wsOverrides[key] !== undefined)
|
|
357
|
+
overridden.push(key);
|
|
358
|
+
}
|
|
359
|
+
res.json({ settings: effective, overridden });
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
const settings = config.workspaceSettings?.[resolved] ?? {};
|
|
363
|
+
res.json(settings);
|
|
364
|
+
}
|
|
352
365
|
});
|
|
353
366
|
// -------------------------------------------------------------------------
|
|
354
367
|
// PATCH /workspaces/settings — update per-workspace settings
|
|
@@ -362,11 +375,28 @@ export function createWorkspaceRouter(deps) {
|
|
|
362
375
|
const resolved = path.resolve(workspacePath);
|
|
363
376
|
const updates = req.body;
|
|
364
377
|
const config = getConfig();
|
|
365
|
-
|
|
366
|
-
const
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
378
|
+
// Separate null values (deletions) from actual updates
|
|
379
|
+
const keysToDelete = [];
|
|
380
|
+
const keysToUpdate = {};
|
|
381
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
382
|
+
if (value === null) {
|
|
383
|
+
keysToDelete.push(key);
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
keysToUpdate[key] = value;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
// Apply deletions first
|
|
390
|
+
if (keysToDelete.length > 0) {
|
|
391
|
+
deleteWorkspaceSettingKeys(configPath, config, resolved, keysToDelete);
|
|
392
|
+
}
|
|
393
|
+
// Apply updates
|
|
394
|
+
if (Object.keys(keysToUpdate).length > 0) {
|
|
395
|
+
setWorkspaceSettings(configPath, config, resolved, keysToUpdate);
|
|
396
|
+
}
|
|
397
|
+
// Return the current raw workspace settings
|
|
398
|
+
const final = config.workspaceSettings?.[resolved] ?? {};
|
|
399
|
+
res.json(final);
|
|
370
400
|
});
|
|
371
401
|
// -------------------------------------------------------------------------
|
|
372
402
|
// GET /workspaces/pr — PR info for a specific branch
|
package/dist/test/config.test.js
CHANGED
|
@@ -3,7 +3,7 @@ import assert from 'node:assert/strict';
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import os from 'node:os';
|
|
6
|
-
import { DEFAULTS, loadConfig, saveConfig, ensureMetaDir, readMeta, writeMeta, deleteMeta } from '../server/config.js';
|
|
6
|
+
import { DEFAULTS, loadConfig, saveConfig, ensureMetaDir, readMeta, writeMeta, deleteMeta, resolveSessionSettings, deleteWorkspaceSettingKeys } from '../server/config.js';
|
|
7
7
|
let tmpDir;
|
|
8
8
|
before(() => {
|
|
9
9
|
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'claude-remote-cli-config-test-'));
|
|
@@ -110,3 +110,116 @@ test('deleteMeta is a no-op for non-existent metadata', () => {
|
|
|
110
110
|
const configPath = path.join(tmpDir, 'config.json');
|
|
111
111
|
assert.doesNotThrow(() => deleteMeta(configPath, '/no/such/path'));
|
|
112
112
|
});
|
|
113
|
+
test('resolveSessionSettings returns global defaults when no workspace or overrides', () => {
|
|
114
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
115
|
+
fs.writeFileSync(configPath, JSON.stringify({
|
|
116
|
+
defaultAgent: 'claude',
|
|
117
|
+
defaultContinue: true,
|
|
118
|
+
defaultYolo: false,
|
|
119
|
+
launchInTmux: false,
|
|
120
|
+
claudeArgs: [],
|
|
121
|
+
}), 'utf8');
|
|
122
|
+
const config = loadConfig(configPath);
|
|
123
|
+
const result = resolveSessionSettings(config, '/some/repo', {});
|
|
124
|
+
assert.equal(result.agent, 'claude');
|
|
125
|
+
assert.equal(result.yolo, false);
|
|
126
|
+
assert.equal(result.continue, true);
|
|
127
|
+
assert.equal(result.useTmux, false);
|
|
128
|
+
assert.deepEqual(result.claudeArgs, []);
|
|
129
|
+
});
|
|
130
|
+
test('resolveSessionSettings applies workspace overrides over globals', () => {
|
|
131
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
132
|
+
fs.writeFileSync(configPath, JSON.stringify({
|
|
133
|
+
defaultAgent: 'claude',
|
|
134
|
+
defaultYolo: false,
|
|
135
|
+
defaultContinue: true,
|
|
136
|
+
launchInTmux: false,
|
|
137
|
+
claudeArgs: [],
|
|
138
|
+
workspaceSettings: {
|
|
139
|
+
'/my/repo': { defaultYolo: true, defaultAgent: 'codex' },
|
|
140
|
+
},
|
|
141
|
+
}), 'utf8');
|
|
142
|
+
const config = loadConfig(configPath);
|
|
143
|
+
const result = resolveSessionSettings(config, '/my/repo', {});
|
|
144
|
+
assert.equal(result.agent, 'codex');
|
|
145
|
+
assert.equal(result.yolo, true);
|
|
146
|
+
assert.equal(result.continue, true);
|
|
147
|
+
});
|
|
148
|
+
test('resolveSessionSettings explicit overrides beat workspace settings', () => {
|
|
149
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
150
|
+
fs.writeFileSync(configPath, JSON.stringify({
|
|
151
|
+
defaultAgent: 'claude',
|
|
152
|
+
defaultYolo: true,
|
|
153
|
+
defaultContinue: true,
|
|
154
|
+
launchInTmux: false,
|
|
155
|
+
claudeArgs: [],
|
|
156
|
+
workspaceSettings: {
|
|
157
|
+
'/my/repo': { defaultYolo: true },
|
|
158
|
+
},
|
|
159
|
+
}), 'utf8');
|
|
160
|
+
const config = loadConfig(configPath);
|
|
161
|
+
const result = resolveSessionSettings(config, '/my/repo', { yolo: false });
|
|
162
|
+
assert.equal(result.yolo, false);
|
|
163
|
+
});
|
|
164
|
+
test('resolveSessionSettings uses override claudeArgs, not global', () => {
|
|
165
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
166
|
+
fs.writeFileSync(configPath, JSON.stringify({
|
|
167
|
+
defaultAgent: 'claude',
|
|
168
|
+
defaultYolo: false,
|
|
169
|
+
defaultContinue: true,
|
|
170
|
+
launchInTmux: false,
|
|
171
|
+
claudeArgs: ['--global-arg'],
|
|
172
|
+
}), 'utf8');
|
|
173
|
+
const config = loadConfig(configPath);
|
|
174
|
+
const result = resolveSessionSettings(config, '/some/repo', { claudeArgs: ['--custom'] });
|
|
175
|
+
assert.deepEqual(result.claudeArgs, ['--custom']);
|
|
176
|
+
});
|
|
177
|
+
test('resolveSessionSettings falls through to globals when no workspace exists', () => {
|
|
178
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
179
|
+
fs.writeFileSync(configPath, JSON.stringify({
|
|
180
|
+
defaultAgent: 'codex',
|
|
181
|
+
defaultYolo: true,
|
|
182
|
+
defaultContinue: false,
|
|
183
|
+
launchInTmux: true,
|
|
184
|
+
claudeArgs: ['--verbose'],
|
|
185
|
+
}), 'utf8');
|
|
186
|
+
const config = loadConfig(configPath);
|
|
187
|
+
const result = resolveSessionSettings(config, '/nonexistent/repo', {});
|
|
188
|
+
assert.equal(result.agent, 'codex');
|
|
189
|
+
assert.equal(result.yolo, true);
|
|
190
|
+
assert.equal(result.continue, false);
|
|
191
|
+
assert.equal(result.useTmux, true);
|
|
192
|
+
assert.deepEqual(result.claudeArgs, ['--verbose']);
|
|
193
|
+
});
|
|
194
|
+
test('deleteWorkspaceSettingKeys removes specified keys', () => {
|
|
195
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
196
|
+
const config = {
|
|
197
|
+
...DEFAULTS,
|
|
198
|
+
workspaceSettings: {
|
|
199
|
+
'/my/repo': { defaultYolo: true, defaultAgent: 'codex', branchPrefix: 'dy/' },
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
fs.writeFileSync(configPath, JSON.stringify(config), 'utf8');
|
|
203
|
+
deleteWorkspaceSettingKeys(configPath, config, '/my/repo', ['defaultYolo', 'defaultAgent']);
|
|
204
|
+
assert.equal(config.workspaceSettings['/my/repo'].defaultYolo, undefined);
|
|
205
|
+
assert.equal(config.workspaceSettings['/my/repo'].defaultAgent, undefined);
|
|
206
|
+
assert.equal(config.workspaceSettings['/my/repo'].branchPrefix, 'dy/');
|
|
207
|
+
});
|
|
208
|
+
test('deleteWorkspaceSettingKeys removes entire workspace entry when empty', () => {
|
|
209
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
210
|
+
const config = {
|
|
211
|
+
...DEFAULTS,
|
|
212
|
+
workspaceSettings: {
|
|
213
|
+
'/my/repo': { defaultYolo: true },
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
fs.writeFileSync(configPath, JSON.stringify(config), 'utf8');
|
|
217
|
+
deleteWorkspaceSettingKeys(configPath, config, '/my/repo', ['defaultYolo']);
|
|
218
|
+
assert.equal(config.workspaceSettings['/my/repo'], undefined);
|
|
219
|
+
});
|
|
220
|
+
test('deleteWorkspaceSettingKeys is no-op for nonexistent workspace', () => {
|
|
221
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
222
|
+
const config = { ...DEFAULTS };
|
|
223
|
+
fs.writeFileSync(configPath, JSON.stringify(config), 'utf8');
|
|
224
|
+
assert.doesNotThrow(() => deleteWorkspaceSettingKeys(configPath, config, '/no/such/repo', ['defaultYolo']));
|
|
225
|
+
});
|