claude-code-watch 0.0.10 → 0.0.11
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/bin/claude-watch.js +3 -21
- package/package.json +2 -2
- package/public/favicon.svg +17 -0
- package/public/index.html +7 -7
- package/src/cli-helpers.js +26 -0
- package/src/parser/parser.js +9 -4
- package/src/server/server.js +29 -12
- package/src/watcher/watcher.js +38 -29
package/bin/claude-watch.js
CHANGED
|
@@ -7,6 +7,7 @@ const cp = require('child_process');
|
|
|
7
7
|
|
|
8
8
|
const { startServer } = require('../src/server/server');
|
|
9
9
|
const { listSessions, listActiveSessions } = require('../src/watcher/watcher');
|
|
10
|
+
const { compareVersions, parseDuration } = require('../src/cli-helpers');
|
|
10
11
|
|
|
11
12
|
const { version: VERSION } = require('../package.json');
|
|
12
13
|
|
|
@@ -41,14 +42,8 @@ ENVIRONMENT:
|
|
|
41
42
|
`);
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
function
|
|
45
|
-
|
|
46
|
-
const pb = b.split('.').map(Number);
|
|
47
|
-
for (let i = 0; i < 3; i++) {
|
|
48
|
-
if (pa[i] > pb[i]) return 1;
|
|
49
|
-
if (pa[i] < pb[i]) return -1;
|
|
50
|
-
}
|
|
51
|
-
return 0;
|
|
45
|
+
function printVersion() {
|
|
46
|
+
console.log(`claude-watch v${VERSION}`);
|
|
52
47
|
}
|
|
53
48
|
|
|
54
49
|
function fetchLatestVersion() {
|
|
@@ -128,19 +123,6 @@ async function runUpdate() {
|
|
|
128
123
|
}
|
|
129
124
|
}
|
|
130
125
|
|
|
131
|
-
function parseDuration(s) {
|
|
132
|
-
const match = s.match(/^(\d+)(ms|s|m|h)$/);
|
|
133
|
-
if (!match) throw new Error(`Invalid duration: ${s}`);
|
|
134
|
-
const val = parseInt(match[1], 10);
|
|
135
|
-
switch (match[2]) {
|
|
136
|
-
case 'ms': return val;
|
|
137
|
-
case 's': return val * 1000;
|
|
138
|
-
case 'm': return val * 60 * 1000;
|
|
139
|
-
case 'h': return val * 3600 * 1000;
|
|
140
|
-
default: throw new Error(`Invalid duration unit: ${match[2]}`);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
126
|
async function main() {
|
|
145
127
|
const args = process.argv.slice(2);
|
|
146
128
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-watch",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.11",
|
|
4
4
|
"description": "Web-based real-time monitor for Claude Code.",
|
|
5
5
|
"main": "./src/server/server.js",
|
|
6
6
|
"bin": {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"scripts": {
|
|
11
11
|
"start": "node bin/claude-watch.js",
|
|
12
12
|
"dev": "node --watch bin/claude-watch.js --no-open",
|
|
13
|
-
"test": "node --test tests/all.test.js tests/watcher.test.js tests/server.test.js"
|
|
13
|
+
"test": "node --test tests/all.test.js tests/watcher.test.js tests/server.test.js tests/cli.test.js"
|
|
14
14
|
},
|
|
15
15
|
"files": [
|
|
16
16
|
"bin",
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
|
|
4
|
+
<stop offset="0%" stop-color="#7c3aed"/>
|
|
5
|
+
<stop offset="100%" stop-color="#4c1d95"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
</defs>
|
|
8
|
+
<rect width="32" height="32" rx="6" fill="url(#bg)"/>
|
|
9
|
+
<!-- Eye outline - wide almond, flatter top/bottom -->
|
|
10
|
+
<path d="M4 16 C6 11 10 9 16 9 C22 9 26 11 28 16 C26 21 22 23 16 23 C10 23 6 21 4 16Z" fill="#1f2937" stroke="#c084fc" stroke-width="2"/>
|
|
11
|
+
<!-- Iris - horizontal ellipse -->
|
|
12
|
+
<ellipse cx="16" cy="16" rx="6" ry="4.5" fill="#a855f7" stroke="#e9d5ff" stroke-width="1.5"/>
|
|
13
|
+
<!-- Pupil -->
|
|
14
|
+
<ellipse cx="16" cy="16" rx="2.5" ry="2" fill="#0f0a1a"/>
|
|
15
|
+
<!-- Shine -->
|
|
16
|
+
<circle cx="14" cy="14.5" r="1.2" fill="#f9fafb"/>
|
|
17
|
+
</svg>
|
package/public/index.html
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>claude-watch</title>
|
|
7
|
+
<link rel="icon" type="image/svg+xml" href="favicon.svg">
|
|
7
8
|
<link rel="stylesheet" href="vendor/github-dark.min.css">
|
|
8
9
|
<style>
|
|
9
10
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
@@ -386,7 +387,6 @@ const MAX_ITEMS = 3000;
|
|
|
386
387
|
const MAX_LINES = 50;
|
|
387
388
|
let renderedItemCount = 0;
|
|
388
389
|
let needsFullRender = true;
|
|
389
|
-
visibleDirty = true;
|
|
390
390
|
|
|
391
391
|
// ══════════════════════════════════════════════════════════════════════════════
|
|
392
392
|
// Markdown renderer (marked + highlight.js)
|
|
@@ -588,8 +588,8 @@ function handleNewBgTask(payload) {
|
|
|
588
588
|
function handleSessionRemoved(payload) {
|
|
589
589
|
const idx = sessions.findIndex(s => s.id === payload.sessionID);
|
|
590
590
|
if (idx >= 0) {
|
|
591
|
-
|
|
592
|
-
|
|
591
|
+
sessions.splice(idx, 1);
|
|
592
|
+
sessionsMap.delete(payload.sessionID);
|
|
593
593
|
}
|
|
594
594
|
updateFilters();
|
|
595
595
|
rebuildNodes();
|
|
@@ -733,7 +733,7 @@ function getNodeHTML(node, idx) {
|
|
|
733
733
|
return `<div class="tree-row${selClass ? ' selected' : ''}">
|
|
734
734
|
<div class="tree-node" onclick="treeClick(${idx})" data-idx="${idx}">
|
|
735
735
|
<span class="tree-prefix">${treePrefix(node)}</span>${activeDot} ${node.collapsed ? '▸' : '▾'} ${esc(displayName)}
|
|
736
|
-
${node.collapsed && agentCount > 0 ? `(${agentCount})` : ''}
|
|
736
|
+
${node.collapsed && agentCount > 0 ? `(${esc(String(agentCount))})` : ''}
|
|
737
737
|
${subInfo}
|
|
738
738
|
</div>
|
|
739
739
|
<span class="tree-actions">
|
|
@@ -1112,11 +1112,11 @@ function removeSelectedSession() {
|
|
|
1112
1112
|
if (node.type === 'session') sid = node.id;
|
|
1113
1113
|
else sid = node.sessionID;
|
|
1114
1114
|
if (!sid) return;
|
|
1115
|
-
if (!confirm(`
|
|
1115
|
+
if (!confirm(`Remove session ${sid.slice(0, 12)}...?`)) return;
|
|
1116
1116
|
const idx = sessions.findIndex(s => s.id === sid);
|
|
1117
1117
|
if (idx >= 0) {
|
|
1118
|
-
|
|
1119
|
-
|
|
1118
|
+
sessions.splice(idx, 1);
|
|
1119
|
+
sessionsMap.delete(sid);
|
|
1120
1120
|
}
|
|
1121
1121
|
sendCmd('removeSession', { sessionID: sid });
|
|
1122
1122
|
updateFilters();
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function compareVersions(a, b) {
|
|
4
|
+
var pa = a.split('.').map(Number);
|
|
5
|
+
var pb = b.split('.').map(Number);
|
|
6
|
+
for (var i = 0; i < 3; i++) {
|
|
7
|
+
if ((pa[i] || 0) > (pb[i] || 0)) return 1;
|
|
8
|
+
if ((pa[i] || 0) < (pb[i] || 0)) return -1;
|
|
9
|
+
}
|
|
10
|
+
return 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function parseDuration(s) {
|
|
14
|
+
var match = s.match(/^(\d+)(ms|s|m|h)$/);
|
|
15
|
+
if (!match) throw new Error('Invalid duration: ' + s);
|
|
16
|
+
var val = parseInt(match[1], 10);
|
|
17
|
+
switch (match[2]) {
|
|
18
|
+
case 'ms': return val;
|
|
19
|
+
case 's': return val * 1000;
|
|
20
|
+
case 'm': return val * 60 * 1000;
|
|
21
|
+
case 'h': return val * 3600 * 1000;
|
|
22
|
+
default: throw new Error('Invalid duration unit: ' + match[2]);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports = { compareVersions, parseDuration };
|
package/src/parser/parser.js
CHANGED
|
@@ -43,7 +43,8 @@ function setDebugAll(val) {
|
|
|
43
43
|
|
|
44
44
|
function agentDisplayName(agentID) {
|
|
45
45
|
if (!agentID) return 'Main';
|
|
46
|
-
|
|
46
|
+
var id = String(agentID);
|
|
47
|
+
return 'Agent-' + id.slice(0, Math.min(AgentIDDisplayLength, id.length));
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
// ============================================================================
|
|
@@ -281,7 +282,7 @@ function diagnosticsBody(diagnostics) {
|
|
|
281
282
|
// ============================================================================
|
|
282
283
|
|
|
283
284
|
function parsePRLink(raw, timestamp) {
|
|
284
|
-
if (
|
|
285
|
+
if (raw.prNumber == null && !raw.prUrl) return [];
|
|
285
286
|
let content;
|
|
286
287
|
if (raw.prRepository && raw.prUrl) {
|
|
287
288
|
content = `PR #${raw.prNumber} ${raw.prRepository} \u2192 ${raw.prUrl}`;
|
|
@@ -441,8 +442,8 @@ function formatToolInput(toolName, input) {
|
|
|
441
442
|
if (inp.path) return `${inp.pattern} in ${inp.path}`;
|
|
442
443
|
return inp.pattern || '';
|
|
443
444
|
case 'Grep':
|
|
444
|
-
if (inp.path) return `/${inp.pattern}/ in ${inp.path}`;
|
|
445
|
-
return `/${inp.pattern}/`;
|
|
445
|
+
if (inp.path) return `/${inp.pattern || ''}/ in ${inp.path}`;
|
|
446
|
+
return `/${inp.pattern || ''}/`;
|
|
446
447
|
case 'WebFetch':
|
|
447
448
|
return inp.prompt || '';
|
|
448
449
|
case 'WebSearch':
|
|
@@ -493,4 +494,8 @@ module.exports = {
|
|
|
493
494
|
contextWindowFor,
|
|
494
495
|
formatTokenCount,
|
|
495
496
|
AgentIDDisplayLength,
|
|
497
|
+
formatToolInput,
|
|
498
|
+
prettyToolName,
|
|
499
|
+
agentDisplayName,
|
|
500
|
+
MAX_TOOL_INPUT_LENGTH,
|
|
496
501
|
};
|
package/src/server/server.js
CHANGED
|
@@ -93,11 +93,16 @@ class DashboardServer {
|
|
|
93
93
|
|
|
94
94
|
broadcast(type, payload) {
|
|
95
95
|
const msg = JSON.stringify({ type, payload });
|
|
96
|
+
const toRemove = [];
|
|
96
97
|
for (const ws of this.clients) {
|
|
97
98
|
if (ws.readyState === 1) {
|
|
98
|
-
try { ws.send(msg); } catch {
|
|
99
|
+
try { ws.send(msg); } catch { toRemove.push(ws); }
|
|
99
100
|
}
|
|
100
101
|
}
|
|
102
|
+
for (const ws of toRemove) {
|
|
103
|
+
this.clients.delete(ws);
|
|
104
|
+
try { ws.terminate(); } catch {}
|
|
105
|
+
}
|
|
101
106
|
}
|
|
102
107
|
|
|
103
108
|
sendJSON(res, data, status = 200) {
|
|
@@ -121,7 +126,7 @@ class DashboardServer {
|
|
|
121
126
|
}
|
|
122
127
|
|
|
123
128
|
async handleHTTP(req, res) {
|
|
124
|
-
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
129
|
+
const url = new URL(req.url, `http://${req.headers.host || 'localhost'}`);
|
|
125
130
|
const p = url.pathname;
|
|
126
131
|
|
|
127
132
|
if (p === '/' || p === '/index.html') {
|
|
@@ -183,21 +188,25 @@ class DashboardServer {
|
|
|
183
188
|
const filePath = params.get('path');
|
|
184
189
|
if (!filePath) { this.sendJSON(res, { error: 'Missing path param' }, 400); return; }
|
|
185
190
|
const resolved = path.resolve(filePath);
|
|
186
|
-
|
|
187
|
-
//
|
|
191
|
+
// Resolve both the user-provided path AND the allowed prefix through realpath
|
|
192
|
+
// to ensure consistent comparison even if homedir contains symlinks
|
|
193
|
+
let realPath;
|
|
194
|
+
let allowedPrefix;
|
|
188
195
|
try {
|
|
189
|
-
const
|
|
196
|
+
const homeReal = await fs.promises.realpath(os.homedir());
|
|
197
|
+
allowedPrefix = path.join(homeReal, '.claude', 'projects');
|
|
198
|
+
realPath = await fs.promises.realpath(resolved);
|
|
190
199
|
if (!realPath.startsWith(allowedPrefix)) {
|
|
191
200
|
this.sendJSON(res, { error: 'Access denied' }, 403);
|
|
192
201
|
return;
|
|
193
202
|
}
|
|
194
203
|
} catch {
|
|
195
|
-
// realpath fails for non-existent files — block them
|
|
204
|
+
// realpath fails for non-existent files or if homedir can't be resolved — block them
|
|
196
205
|
this.sendJSON(res, { error: 'Access denied' }, 403);
|
|
197
206
|
return;
|
|
198
207
|
}
|
|
199
208
|
try {
|
|
200
|
-
const content = await fs.promises.readFile(
|
|
209
|
+
const content = await fs.promises.readFile(realPath, 'utf-8');
|
|
201
210
|
this.sendJSON(res, { content });
|
|
202
211
|
} catch (err) {
|
|
203
212
|
this.sendJSON(res, { error: err.message }, 404);
|
|
@@ -239,11 +248,13 @@ class DashboardServer {
|
|
|
239
248
|
this.broadcast('autoDiscoveryChanged', { enabled: this.watcher.isAutoDiscoveryEnabled() });
|
|
240
249
|
break;
|
|
241
250
|
case 'removeSession':
|
|
242
|
-
|
|
243
|
-
|
|
251
|
+
if (typeof cmd.sessionID === 'string' && cmd.sessionID) {
|
|
252
|
+
this.watcher.removeSession(cmd.sessionID);
|
|
253
|
+
this.broadcast('sessionRemoved', { sessionID: cmd.sessionID });
|
|
254
|
+
}
|
|
244
255
|
break;
|
|
245
256
|
case 'setSkipHistory':
|
|
246
|
-
this.watcher.setSkipHistory(cmd.skip);
|
|
257
|
+
this.watcher.setSkipHistory(cmd.skip === true);
|
|
247
258
|
break;
|
|
248
259
|
case 'getContext':
|
|
249
260
|
this.sendContext(ws);
|
|
@@ -348,12 +359,14 @@ class DashboardServer {
|
|
|
348
359
|
const confirmed = await askYesNo(`Port ${port} is occupied by process(es) ${pids.join(', ')}. Kill them? [y/N] `);
|
|
349
360
|
if (!confirmed) {
|
|
350
361
|
console.error(`Port ${port} is in use. Exiting.`);
|
|
362
|
+
this.stop();
|
|
351
363
|
process.exit(1);
|
|
352
364
|
}
|
|
353
365
|
|
|
366
|
+
const myPid = process.pid;
|
|
354
367
|
for (const pid of pids) {
|
|
355
368
|
const parsedPid = parseInt(pid, 10);
|
|
356
|
-
if (Number.isInteger(parsedPid) && parsedPid >
|
|
369
|
+
if (Number.isInteger(parsedPid) && parsedPid > 1 && parsedPid !== myPid) {
|
|
357
370
|
try {
|
|
358
371
|
if (process.platform === 'win32') {
|
|
359
372
|
cp.execSync(`taskkill /PID ${parsedPid} /F`, { encoding: 'utf-8' });
|
|
@@ -369,7 +382,7 @@ class DashboardServer {
|
|
|
369
382
|
await new Promise(r => setTimeout(r, 3000));
|
|
370
383
|
for (const pid of pids) {
|
|
371
384
|
const parsedPid = parseInt(pid, 10);
|
|
372
|
-
if (Number.isInteger(parsedPid) && parsedPid >
|
|
385
|
+
if (Number.isInteger(parsedPid) && parsedPid > 1 && parsedPid !== myPid) {
|
|
373
386
|
try { process.kill(parsedPid, 0); process.kill(parsedPid, 'SIGKILL'); } catch {}
|
|
374
387
|
}
|
|
375
388
|
}
|
|
@@ -423,9 +436,11 @@ class DashboardServer {
|
|
|
423
436
|
this.server.on('error', (err) => {
|
|
424
437
|
if (err.code === 'EADDRINUSE') {
|
|
425
438
|
console.error(`Port ${this.port} is still in use after attempting to free it. Exiting.`);
|
|
439
|
+
this.stop();
|
|
426
440
|
process.exit(1);
|
|
427
441
|
} else {
|
|
428
442
|
console.error(`Server error: ${err.message}`);
|
|
443
|
+
this.stop();
|
|
429
444
|
process.exit(1);
|
|
430
445
|
}
|
|
431
446
|
});
|
|
@@ -438,6 +453,7 @@ class DashboardServer {
|
|
|
438
453
|
await w.start();
|
|
439
454
|
} catch (err) {
|
|
440
455
|
console.error('Watcher init error:', err.message);
|
|
456
|
+
this.stop();
|
|
441
457
|
process.exit(1);
|
|
442
458
|
}
|
|
443
459
|
|
|
@@ -490,6 +506,7 @@ async function startServer(options = {}) {
|
|
|
490
506
|
}
|
|
491
507
|
|
|
492
508
|
function askYesNo(prompt) {
|
|
509
|
+
if (!process.stdin.isTTY) return Promise.resolve(false);
|
|
493
510
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
494
511
|
return new Promise(resolve => {
|
|
495
512
|
rl.question(prompt, answer => {
|
package/src/watcher/watcher.js
CHANGED
|
@@ -772,32 +772,42 @@ class Watcher extends EventEmitter {
|
|
|
772
772
|
if (!line.includes('"tool_')) continue;
|
|
773
773
|
|
|
774
774
|
if (line.includes('"tool_use"')) {
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
775
|
+
try {
|
|
776
|
+
var raw = JSON.parse(line);
|
|
777
|
+
var content = raw.message && raw.message.content;
|
|
778
|
+
if (!Array.isArray(content)) continue;
|
|
779
|
+
for (var block of content) {
|
|
780
|
+
if (block.type !== 'tool_use' || !block.id) continue;
|
|
781
|
+
if (session.toolIndex.has(block.id)) continue;
|
|
782
|
+
session.toolIndex.set(block.id, {
|
|
783
|
+
toolName: block.name || '',
|
|
784
|
+
parentAgentID: agentID,
|
|
785
|
+
hasResult: false,
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
} catch { continue; }
|
|
785
789
|
}
|
|
786
790
|
|
|
787
791
|
if (line.includes('"tool_result"')) {
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
792
|
+
try {
|
|
793
|
+
var raw2 = JSON.parse(line);
|
|
794
|
+
var content2 = raw2.message && raw2.message.content;
|
|
795
|
+
if (!Array.isArray(content2)) continue;
|
|
796
|
+
for (var block2 of content2) {
|
|
797
|
+
if (block2.type !== 'tool_result' || !block2.tool_use_id) continue;
|
|
798
|
+
var tid = block2.tool_use_id;
|
|
799
|
+
var existing = session.toolIndex.get(tid);
|
|
800
|
+
if (existing) {
|
|
801
|
+
existing.hasResult = true;
|
|
802
|
+
} else {
|
|
803
|
+
session.toolIndex.set(tid, {
|
|
804
|
+
toolName: '',
|
|
805
|
+
parentAgentID: '',
|
|
806
|
+
hasResult: true,
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
} catch { continue; }
|
|
801
811
|
}
|
|
802
812
|
}
|
|
803
813
|
} catch (err) {
|
|
@@ -971,7 +981,7 @@ class Watcher extends EventEmitter {
|
|
|
971
981
|
const { bytesRead } = await handle.read(buf, 0, readLen, readFrom);
|
|
972
982
|
if (bytesRead === 0) break;
|
|
973
983
|
|
|
974
|
-
const chunk =
|
|
984
|
+
const chunk = buf.toString('utf-8', 0, bytesRead);
|
|
975
985
|
const combined = carryOver + chunk;
|
|
976
986
|
|
|
977
987
|
// Detect CRLF from first newline in the combined text
|
|
@@ -1217,7 +1227,7 @@ async function _listSessionsFiltered(limit, activeWithin) {
|
|
|
1217
1227
|
|
|
1218
1228
|
const candidates = [];
|
|
1219
1229
|
try {
|
|
1220
|
-
await
|
|
1230
|
+
await _walkDirAsync(claudeDir, (filePath, stats) => {
|
|
1221
1231
|
if (!isMainSessionFile(filePath, stats)) return;
|
|
1222
1232
|
if (activeWithin > 0 && (now - stats.mtimeMs) > activeWithin) return;
|
|
1223
1233
|
candidates.push({ filePath, stats });
|
|
@@ -1244,14 +1254,13 @@ async function _listSessionsFiltered(limit, activeWithin) {
|
|
|
1244
1254
|
return sessions;
|
|
1245
1255
|
}
|
|
1246
1256
|
|
|
1247
|
-
async function _walkDirStatic(dir, callback) {
|
|
1248
|
-
return _walkDirAsync(dir, callback);
|
|
1249
|
-
}
|
|
1250
|
-
|
|
1251
1257
|
module.exports = {
|
|
1252
1258
|
Watcher,
|
|
1253
1259
|
Session,
|
|
1254
1260
|
BackgroundTask,
|
|
1255
1261
|
listSessions,
|
|
1256
1262
|
listActiveSessions,
|
|
1263
|
+
resolveProjectPath,
|
|
1264
|
+
isMainSessionFile,
|
|
1265
|
+
readAgentType,
|
|
1257
1266
|
};
|