claude-remote-cli 3.0.3 → 3.0.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.
@@ -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-CKQHbnTN.js"></script>
15
- <link rel="stylesheet" crossorigin href="/assets/index-BgOmCV-k.css">
14
+ <script type="module" crossorigin src="/assets/index-C9kPfx3H.js"></script>
15
+ <link rel="stylesheet" crossorigin href="/assets/index-Bw4iKQrv.css">
16
16
  </head>
17
17
  <body>
18
18
  <div id="app"></div>
@@ -13,6 +13,7 @@ export const DEFAULTS = {
13
13
  defaultYolo: false,
14
14
  launchInTmux: false,
15
15
  defaultNotifications: true,
16
+ workspaces: [],
16
17
  };
17
18
  export function loadConfig(configPath) {
18
19
  if (!fs.existsSync(configPath)) {
@@ -61,3 +62,24 @@ export function deleteMeta(configPath, worktreePath) {
61
62
  // File may not exist; ignore
62
63
  }
63
64
  }
65
+ export function getWorkspaceSettings(config, workspacePath) {
66
+ const globalDefaults = {
67
+ defaultAgent: config.defaultAgent,
68
+ defaultContinue: config.defaultContinue,
69
+ defaultYolo: config.defaultYolo,
70
+ launchInTmux: config.launchInTmux,
71
+ claudeArgs: config.claudeArgs,
72
+ };
73
+ const perWorkspace = config.workspaceSettings?.[workspacePath] || {};
74
+ // Per-workspace settings override global — only for defined keys
75
+ return { ...globalDefaults, ...perWorkspace };
76
+ }
77
+ export function setWorkspaceSettings(configPath, config, workspacePath, settings) {
78
+ if (!config.workspaceSettings)
79
+ config.workspaceSettings = {};
80
+ config.workspaceSettings[workspacePath] = {
81
+ ...config.workspaceSettings[workspacePath],
82
+ ...settings,
83
+ };
84
+ saveConfig(configPath, config);
85
+ }
@@ -27,4 +27,196 @@ async function listBranches(repoPath, options = {}) {
27
27
  return [];
28
28
  }
29
29
  }
30
- export { listBranches, normalizeBranchNames };
30
+ async function getCurrentBranch(repoPath, options = {}) {
31
+ const run = options.exec || execFileAsync;
32
+ try {
33
+ const { stdout } = await run('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: repoPath });
34
+ return stdout.trim() || null;
35
+ }
36
+ catch {
37
+ return null;
38
+ }
39
+ }
40
+ async function getActivityFeed(repoPath, options = {}) {
41
+ const run = options.exec || execFileAsync;
42
+ try {
43
+ const { stdout } = await run('git', [
44
+ 'log',
45
+ '--all',
46
+ '--since=24h',
47
+ '--oneline',
48
+ '--max-count=50',
49
+ '--format=%H|%h|%s|%an|%ar|%D',
50
+ ], { cwd: repoPath, timeout: 5000 });
51
+ const lines = stdout.split('\n').filter((line) => line.trim());
52
+ const entries = [];
53
+ for (const line of lines) {
54
+ try {
55
+ // Split into exactly 6 parts by the first 5 pipe characters
56
+ const parts = [];
57
+ let remaining = line;
58
+ for (let i = 0; i < 5; i++) {
59
+ const idx = remaining.indexOf('|');
60
+ if (idx === -1)
61
+ break;
62
+ parts.push(remaining.slice(0, idx));
63
+ remaining = remaining.slice(idx + 1);
64
+ }
65
+ parts.push(remaining);
66
+ if (parts.length < 5)
67
+ continue;
68
+ const hash = parts[0] ?? '';
69
+ const shortHash = parts[1] ?? '';
70
+ const message = parts[2] ?? '';
71
+ const author = parts[3] ?? '';
72
+ const timeAgo = parts[4] ?? '';
73
+ const decorations = parts[5] ?? '';
74
+ if (!hash || !shortHash)
75
+ continue;
76
+ const branches = decorations
77
+ .split(',')
78
+ .map((d) => d.trim())
79
+ .filter((d) => d && !d.startsWith('tag:') && d !== 'HEAD')
80
+ .map((d) => d.replace(/^HEAD -> /, '').replace(/^origin\//, ''));
81
+ entries.push({
82
+ hash: hash.trim(),
83
+ shortHash: shortHash.trim(),
84
+ message: message.trim(),
85
+ author: author.trim(),
86
+ timeAgo: timeAgo.trim(),
87
+ branches: [...new Set(branches)],
88
+ });
89
+ }
90
+ catch {
91
+ // Skip malformed lines
92
+ continue;
93
+ }
94
+ }
95
+ return entries;
96
+ }
97
+ catch {
98
+ return [];
99
+ }
100
+ }
101
+ async function getCiStatus(repoPath, branch, options = {}) {
102
+ const run = options.exec || execFileAsync;
103
+ let stdout;
104
+ let stderr;
105
+ try {
106
+ ({ stdout, stderr } = await run('gh', ['pr', 'checks', branch, '--json', 'name,state,conclusion'], { cwd: repoPath, timeout: 5000 }));
107
+ }
108
+ catch (err) {
109
+ if (err && typeof err === 'object') {
110
+ const errObj = err;
111
+ const errorText = errObj.stderr ?? errObj.message ?? '';
112
+ // gh not installed
113
+ if (errObj.code === 'ENOENT')
114
+ return null;
115
+ // Not authenticated
116
+ if (typeof errorText === 'string' &&
117
+ (errorText.includes('not logged into') || errorText.includes('authentication'))) {
118
+ return { total: 0, passing: 0, failing: 0, pending: 0, authError: true };
119
+ }
120
+ // No PR for branch
121
+ if (typeof errorText === 'string' &&
122
+ (errorText.includes('no pull requests found') || errorText.includes('Could not find'))) {
123
+ return null;
124
+ }
125
+ }
126
+ return null;
127
+ }
128
+ // gh may exit 0 but write errors or auth prompts to stderr
129
+ if (stderr && (stderr.includes('not logged into') || stderr.includes('authentication'))) {
130
+ return { total: 0, passing: 0, failing: 0, pending: 0, authError: true };
131
+ }
132
+ if (!stdout.trim())
133
+ return null;
134
+ try {
135
+ const checks = JSON.parse(stdout);
136
+ let passing = 0;
137
+ let failing = 0;
138
+ let pending = 0;
139
+ for (const check of checks) {
140
+ const conclusion = (check.conclusion ?? '').toUpperCase();
141
+ const state = (check.state ?? '').toUpperCase();
142
+ if (conclusion === 'SUCCESS' || conclusion === 'SKIPPED' || conclusion === 'NEUTRAL') {
143
+ passing++;
144
+ }
145
+ else if (conclusion === 'FAILURE' || conclusion === 'CANCELLED' || conclusion === 'TIMED_OUT') {
146
+ failing++;
147
+ }
148
+ else if (state === 'IN_PROGRESS' || state === 'QUEUED' || state === 'PENDING' || conclusion === '') {
149
+ pending++;
150
+ }
151
+ else {
152
+ // Unknown conclusion — treat as pending rather than silently ignoring
153
+ pending++;
154
+ }
155
+ }
156
+ return { total: checks.length, passing, failing, pending };
157
+ }
158
+ catch {
159
+ return null;
160
+ }
161
+ }
162
+ async function getPrForBranch(repoPath, branch, options = {}) {
163
+ const run = options.exec || execFileAsync;
164
+ let stdout;
165
+ try {
166
+ ({ stdout } = await run('gh', [
167
+ 'pr',
168
+ 'view',
169
+ branch,
170
+ '--json',
171
+ 'number,title,url,state,headRefName,baseRefName,reviewDecision,isDraft',
172
+ ], { cwd: repoPath, timeout: 5000 }));
173
+ }
174
+ catch {
175
+ return null;
176
+ }
177
+ if (!stdout.trim())
178
+ return null;
179
+ try {
180
+ const data = JSON.parse(stdout);
181
+ return {
182
+ number: data.number,
183
+ title: data.title,
184
+ url: data.url,
185
+ state: data.state,
186
+ headRefName: data.headRefName,
187
+ baseRefName: data.baseRefName,
188
+ isDraft: data.isDraft,
189
+ reviewDecision: data.reviewDecision ?? null,
190
+ };
191
+ }
192
+ catch {
193
+ return null;
194
+ }
195
+ }
196
+ async function switchBranch(repoPath, branch, options = {}) {
197
+ const run = options.exec || execFileAsync;
198
+ try {
199
+ await run('git', ['checkout', branch], { cwd: repoPath, timeout: 5000 });
200
+ return { success: true };
201
+ }
202
+ catch (err) {
203
+ if (err && typeof err === 'object') {
204
+ const errObj = err;
205
+ const errorText = errObj.stderr ?? errObj.message ?? 'Unknown error';
206
+ return { success: false, error: errorText.trim() };
207
+ }
208
+ return { success: false, error: 'Unknown error' };
209
+ }
210
+ }
211
+ async function getCommitsAhead(repoPath, branch, baseBranch, options = {}) {
212
+ const run = options.exec || execFileAsync;
213
+ try {
214
+ const { stdout } = await run('git', ['rev-list', '--count', `${baseBranch}..${branch}`], { cwd: repoPath, timeout: 5000 });
215
+ const count = parseInt(stdout.trim(), 10);
216
+ return Number.isFinite(count) ? count : 0;
217
+ }
218
+ catch {
219
+ return 0;
220
+ }
221
+ }
222
+ export { listBranches, normalizeBranchNames, getActivityFeed, getCiStatus, getPrForBranch, switchBranch, getCommitsAhead, getCurrentBranch, };