claude-remote-cli 2.8.0 → 2.9.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.
@@ -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-CUkDx_1l.js"></script>
15
- <link rel="stylesheet" crossorigin href="/assets/index-Not5cXLa.css">
14
+ <script type="module" crossorigin src="/assets/index-Dkj00kFR.js"></script>
15
+ <link rel="stylesheet" crossorigin href="/assets/index-B3mDW63X.css">
16
16
  </head>
17
17
  <body>
18
18
  <div id="app"></div>
@@ -8,6 +8,7 @@ export const DEFAULTS = {
8
8
  repos: [],
9
9
  claudeCommand: 'claude',
10
10
  claudeArgs: [],
11
+ defaultAgent: 'claude',
11
12
  };
12
13
  export function loadConfig(configPath) {
13
14
  if (!fs.existsSync(configPath)) {
@@ -11,6 +11,7 @@ import cookieParser from 'cookie-parser';
11
11
  import { loadConfig, saveConfig, DEFAULTS, readMeta, writeMeta, deleteMeta, ensureMetaDir } from './config.js';
12
12
  import * as auth from './auth.js';
13
13
  import * as sessions from './sessions.js';
14
+ import { AGENT_CONTINUE_ARGS } from './sessions.js';
14
15
  import { setupWebSocket } from './ws.js';
15
16
  import { WorktreeWatcher, WORKTREE_DIRS, isValidWorktreePath, parseWorktreeListPorcelain, parseAllWorktrees } from './watcher.js';
16
17
  import { isInstalled as serviceIsInstalled } from './service.js';
@@ -500,6 +501,21 @@ async function main() {
500
501
  broadcastEvent('worktrees-changed');
501
502
  res.json(config.rootDirs);
502
503
  });
504
+ // GET /config/defaultAgent — get default coding agent
505
+ app.get('/config/defaultAgent', requireAuth, (_req, res) => {
506
+ res.json({ defaultAgent: config.defaultAgent || 'claude' });
507
+ });
508
+ // PATCH /config/defaultAgent — set default coding agent
509
+ app.patch('/config/defaultAgent', requireAuth, (req, res) => {
510
+ const { defaultAgent } = req.body;
511
+ if (!defaultAgent || (defaultAgent !== 'claude' && defaultAgent !== 'codex')) {
512
+ res.status(400).json({ error: 'defaultAgent must be "claude" or "codex"' });
513
+ return;
514
+ }
515
+ config.defaultAgent = defaultAgent;
516
+ saveConfig(CONFIG_PATH, config);
517
+ res.json({ defaultAgent: config.defaultAgent });
518
+ });
503
519
  // DELETE /worktrees — remove a worktree, prune, and delete its branch
504
520
  app.delete('/worktrees', requireAuth, async (req, res) => {
505
521
  const { worktreePath, repoPath } = req.body;
@@ -574,11 +590,12 @@ async function main() {
574
590
  });
575
591
  // POST /sessions
576
592
  app.post('/sessions', requireAuth, async (req, res) => {
577
- const { repoPath, repoName, worktreePath, branchName, claudeArgs } = req.body;
593
+ const { repoPath, repoName, worktreePath, branchName, claudeArgs, agent } = req.body;
578
594
  if (!repoPath) {
579
595
  res.status(400).json({ error: 'repoPath is required' });
580
596
  return;
581
597
  }
598
+ const resolvedAgent = agent || config.defaultAgent || 'claude';
582
599
  const name = repoName || repoPath.split('/').filter(Boolean).pop() || 'session';
583
600
  const baseArgs = [...(config.claudeArgs || []), ...(claudeArgs || [])];
584
601
  // Compute root by matching repoPath against configured rootDirs
@@ -591,7 +608,7 @@ async function main() {
591
608
  let resolvedBranch = '';
592
609
  if (worktreePath) {
593
610
  // Resume existing worktree
594
- args = ['--continue', ...baseArgs];
611
+ args = [...AGENT_CONTINUE_ARGS[resolvedAgent], ...baseArgs];
595
612
  cwd = worktreePath;
596
613
  sessionRepoPath = worktreePath;
597
614
  worktreeName = worktreePath.split('/').pop() || '';
@@ -648,12 +665,12 @@ async function main() {
648
665
  }
649
666
  const repoSession = sessions.create({
650
667
  type: 'repo',
668
+ agent: resolvedAgent,
651
669
  repoName: name,
652
670
  repoPath,
653
671
  cwd: repoPath,
654
672
  root,
655
673
  displayName: name,
656
- command: config.claudeCommand,
657
674
  args: baseArgs,
658
675
  });
659
676
  res.status(201).json(repoSession);
@@ -664,10 +681,11 @@ async function main() {
664
681
  cwd = existingWt.path;
665
682
  sessionRepoPath = existingWt.path;
666
683
  worktreeName = existingWt.path.split('/').pop() || '';
667
- args = ['--continue', ...baseArgs];
684
+ args = [...AGENT_CONTINUE_ARGS[resolvedAgent], ...baseArgs];
668
685
  const displayNameVal = branchName || worktreeName;
669
686
  const session = sessions.create({
670
687
  type: 'worktree',
688
+ agent: resolvedAgent,
671
689
  repoName: name,
672
690
  repoPath: sessionRepoPath,
673
691
  cwd,
@@ -675,7 +693,6 @@ async function main() {
675
693
  worktreeName,
676
694
  branchName: branchName || worktreeName,
677
695
  displayName: displayNameVal,
678
- command: config.claudeCommand,
679
696
  args,
680
697
  configPath: CONFIG_PATH,
681
698
  });
@@ -710,6 +727,7 @@ async function main() {
710
727
  const displayName = branchName || worktreeName;
711
728
  const session = sessions.create({
712
729
  type: 'worktree',
730
+ agent: resolvedAgent,
713
731
  repoName: name,
714
732
  repoPath: sessionRepoPath,
715
733
  cwd,
@@ -717,7 +735,6 @@ async function main() {
717
735
  worktreeName,
718
736
  branchName: branchName || worktreeName,
719
737
  displayName,
720
- command: config.claudeCommand,
721
738
  args,
722
739
  configPath: CONFIG_PATH,
723
740
  });
@@ -733,11 +750,12 @@ async function main() {
733
750
  });
734
751
  // POST /sessions/repo — start a session in the repo root (no worktree)
735
752
  app.post('/sessions/repo', requireAuth, (req, res) => {
736
- const { repoPath, repoName, continue: continueSession, claudeArgs } = req.body;
753
+ const { repoPath, repoName, continue: continueSession, claudeArgs, agent } = req.body;
737
754
  if (!repoPath) {
738
755
  res.status(400).json({ error: 'repoPath is required' });
739
756
  return;
740
757
  }
758
+ const resolvedAgent = agent || config.defaultAgent || 'claude';
741
759
  // One repo session at a time
742
760
  const existing = sessions.findRepoSession(repoPath);
743
761
  if (existing) {
@@ -746,17 +764,17 @@ async function main() {
746
764
  }
747
765
  const name = repoName || repoPath.split('/').filter(Boolean).pop() || 'session';
748
766
  const baseArgs = [...(config.claudeArgs || []), ...(claudeArgs || [])];
749
- const args = continueSession ? ['--continue', ...baseArgs] : [...baseArgs];
767
+ const args = continueSession ? [...AGENT_CONTINUE_ARGS[resolvedAgent], ...baseArgs] : [...baseArgs];
750
768
  const roots = config.rootDirs || [];
751
769
  const root = roots.find(function (r) { return repoPath.startsWith(r); }) || '';
752
770
  const session = sessions.create({
753
771
  type: 'repo',
772
+ agent: resolvedAgent,
754
773
  repoName: name,
755
774
  repoPath,
756
775
  cwd: repoPath,
757
776
  root,
758
777
  displayName: name,
759
- command: config.claudeCommand,
760
778
  args,
761
779
  });
762
780
  res.status(201).json(session);
@@ -4,6 +4,18 @@ import fs from 'node:fs';
4
4
  import os from 'node:os';
5
5
  import path from 'node:path';
6
6
  import { readMeta, writeMeta } from './config.js';
7
+ const AGENT_COMMANDS = {
8
+ claude: 'claude',
9
+ codex: 'codex',
10
+ };
11
+ const AGENT_YOLO_ARGS = {
12
+ claude: ['--dangerously-skip-permissions'],
13
+ codex: ['--full-auto'],
14
+ };
15
+ const AGENT_CONTINUE_ARGS = {
16
+ claude: ['--continue'],
17
+ codex: ['resume', '--last'],
18
+ };
7
19
  // In-memory registry: id -> Session
8
20
  const sessions = new Map();
9
21
  const IDLE_TIMEOUT_MS = 5000;
@@ -11,13 +23,14 @@ let idleChangeCallback = null;
11
23
  function onIdleChange(cb) {
12
24
  idleChangeCallback = cb;
13
25
  }
14
- function create({ type, repoName, repoPath, cwd, root, worktreeName, branchName, displayName, command, args = [], cols = 80, rows = 24, configPath }) {
26
+ function create({ type, agent = 'claude', repoName, repoPath, cwd, root, worktreeName, branchName, displayName, command, args = [], cols = 80, rows = 24, configPath }) {
15
27
  const id = crypto.randomBytes(8).toString('hex');
16
28
  const createdAt = new Date().toISOString();
29
+ const resolvedCommand = command || AGENT_COMMANDS[agent];
17
30
  // Strip CLAUDECODE env var to allow spawning claude inside a claude-managed server
18
31
  const env = Object.assign({}, process.env);
19
32
  delete env.CLAUDECODE;
20
- const ptyProcess = pty.spawn(command, args, {
33
+ const ptyProcess = pty.spawn(resolvedCommand, args, {
21
34
  name: 'xterm-256color',
22
35
  cols,
23
36
  rows,
@@ -31,6 +44,7 @@ function create({ type, repoName, repoPath, cwd, root, worktreeName, branchName,
31
44
  const session = {
32
45
  id,
33
46
  type: type || 'worktree',
47
+ agent,
34
48
  root: root || '',
35
49
  repoName: repoName || '',
36
50
  repoPath,
@@ -70,6 +84,7 @@ function create({ type, repoName, repoPath, cwd, root, worktreeName, branchName,
70
84
  }
71
85
  }, IDLE_TIMEOUT_MS);
72
86
  }
87
+ const continueArgs = AGENT_CONTINUE_ARGS[agent];
73
88
  function attachHandlers(proc, canRetry) {
74
89
  const spawnTime = Date.now();
75
90
  proc.onData((data) => {
@@ -89,12 +104,12 @@ function create({ type, repoName, repoPath, cwd, root, worktreeName, branchName,
89
104
  }
90
105
  });
91
106
  proc.onExit(({ exitCode }) => {
92
- // If --continue failed quickly, retry without it
107
+ // If continue args failed quickly, retry without them
93
108
  if (canRetry && (Date.now() - spawnTime) < 3000 && exitCode !== 0) {
94
- const retryArgs = args.filter(a => a !== '--continue');
109
+ const retryArgs = args.filter(a => !continueArgs.includes(a));
95
110
  scrollback.length = 0;
96
111
  scrollbackBytes = 0;
97
- const retryPty = pty.spawn(command, retryArgs, {
112
+ const retryPty = pty.spawn(resolvedCommand, retryArgs, {
98
113
  name: 'xterm-256color',
99
114
  cols,
100
115
  rows,
@@ -117,17 +132,18 @@ function create({ type, repoName, repoPath, cwd, root, worktreeName, branchName,
117
132
  fs.rm(tmpDir, { recursive: true, force: true }, () => { });
118
133
  });
119
134
  }
120
- attachHandlers(ptyProcess, args.includes('--continue'));
121
- return { id, type: session.type, root: session.root, repoName: session.repoName, repoPath, worktreeName: session.worktreeName, branchName: session.branchName, displayName: session.displayName, pid: ptyProcess.pid, createdAt, lastActivity: createdAt, idle: false };
135
+ attachHandlers(ptyProcess, continueArgs.some(a => args.includes(a)));
136
+ return { id, type: session.type, agent: session.agent, root: session.root, repoName: session.repoName, repoPath, worktreeName: session.worktreeName, branchName: session.branchName, displayName: session.displayName, pid: ptyProcess.pid, createdAt, lastActivity: createdAt, idle: false };
122
137
  }
123
138
  function get(id) {
124
139
  return sessions.get(id);
125
140
  }
126
141
  function list() {
127
142
  return Array.from(sessions.values())
128
- .map(({ id, type, root, repoName, repoPath, worktreeName, branchName, displayName, createdAt, lastActivity, idle }) => ({
143
+ .map(({ id, type, agent, root, repoName, repoPath, worktreeName, branchName, displayName, createdAt, lastActivity, idle }) => ({
129
144
  id,
130
145
  type,
146
+ agent,
131
147
  root,
132
148
  repoName,
133
149
  repoPath,
@@ -172,4 +188,4 @@ function write(id, data) {
172
188
  function findRepoSession(repoPath) {
173
189
  return list().find((s) => s.type === 'repo' && s.repoPath === repoPath);
174
190
  }
175
- export { create, get, list, kill, resize, updateDisplayName, write, onIdleChange, findRepoSession };
191
+ export { create, get, list, kill, resize, updateDisplayName, write, onIdleChange, findRepoSession, AGENT_COMMANDS, AGENT_YOLO_ARGS, AGENT_CONTINUE_ARGS };
@@ -40,6 +40,7 @@ test('loadConfig merges with defaults for missing fields', () => {
40
40
  assert.deepEqual(config.repos, DEFAULTS.repos);
41
41
  assert.equal(config.claudeCommand, DEFAULTS.claudeCommand);
42
42
  assert.deepEqual(config.claudeArgs, DEFAULTS.claudeArgs);
43
+ assert.equal(config.defaultAgent, DEFAULTS.defaultAgent);
43
44
  });
44
45
  test('loadConfig throws if config file not found', () => {
45
46
  const configPath = path.join(tmpDir, 'nonexistent.json');
@@ -59,6 +60,7 @@ test('DEFAULTS has expected keys and values', () => {
59
60
  assert.deepEqual(DEFAULTS.repos, []);
60
61
  assert.equal(DEFAULTS.claudeCommand, 'claude');
61
62
  assert.deepEqual(DEFAULTS.claudeArgs, []);
63
+ assert.equal(DEFAULTS.defaultAgent, 'claude');
62
64
  });
63
65
  test('ensureMetaDir creates worktree-meta directory', () => {
64
66
  const configPath = path.join(tmpDir, 'config.json');
@@ -264,4 +264,39 @@ describe('sessions', () => {
264
264
  createdIds.push(result.id);
265
265
  assert.strictEqual(result.branchName, '');
266
266
  });
267
+ it('agent defaults to claude when not specified', () => {
268
+ const result = sessions.create({
269
+ repoName: 'test-repo',
270
+ repoPath: '/tmp',
271
+ command: '/bin/echo',
272
+ args: ['hello'],
273
+ });
274
+ createdIds.push(result.id);
275
+ assert.strictEqual(result.agent, 'claude');
276
+ });
277
+ it('agent is set when specified', () => {
278
+ const result = sessions.create({
279
+ repoName: 'test-repo',
280
+ repoPath: '/tmp',
281
+ agent: 'codex',
282
+ command: '/bin/echo',
283
+ args: ['hello'],
284
+ });
285
+ createdIds.push(result.id);
286
+ assert.strictEqual(result.agent, 'codex');
287
+ });
288
+ it('list includes agent field', () => {
289
+ const result = sessions.create({
290
+ repoName: 'test-repo',
291
+ repoPath: '/tmp',
292
+ agent: 'codex',
293
+ command: '/bin/echo',
294
+ args: ['hello'],
295
+ });
296
+ createdIds.push(result.id);
297
+ const list = sessions.list();
298
+ const session = list.find(s => s.id === result.id);
299
+ assert.ok(session);
300
+ assert.strictEqual(session.agent, 'codex');
301
+ });
267
302
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-remote-cli",
3
- "version": "2.8.0",
3
+ "version": "2.9.0",
4
4
  "description": "Remote web interface for Claude Code CLI sessions",
5
5
  "type": "module",
6
6
  "main": "dist/server/index.js",