claude-ps 0.2.5 → 0.2.6
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/{chunk-RZG4BATR.js → chunk-JYWGOPVM.js} +141 -32
- package/dist/chunk-KF2FBZRQ.js +150 -0
- package/dist/index.js +22 -20
- package/dist/{process-AUO5UVTV.js → process-XJGJPUAG.js} +1 -1
- package/dist/{session-TGVDWUTY.js → session-FTYMYOIH.js} +5 -1
- package/package.json +2 -1
- package/dist/chunk-EZHIVMX4.js +0 -106
|
@@ -1,9 +1,105 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/utils/session.ts
|
|
4
|
-
import { readFile, readdir } from "fs/promises";
|
|
4
|
+
import { readFile as readFile2, readdir, stat as stat2 } from "fs/promises";
|
|
5
5
|
import { homedir } from "os";
|
|
6
6
|
import { join } from "path";
|
|
7
|
+
import pLimit from "p-limit";
|
|
8
|
+
|
|
9
|
+
// src/utils/cache.ts
|
|
10
|
+
import { readFile, stat } from "fs/promises";
|
|
11
|
+
var FileCache = class {
|
|
12
|
+
cache = /* @__PURE__ */ new Map();
|
|
13
|
+
maxSize;
|
|
14
|
+
ttl;
|
|
15
|
+
loader;
|
|
16
|
+
constructor(options) {
|
|
17
|
+
this.maxSize = options.maxSize ?? 50;
|
|
18
|
+
this.ttl = options.ttl ?? 5 * 60 * 1e3;
|
|
19
|
+
this.loader = options.loader;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* 获取文件内容(优先从缓存读取)
|
|
23
|
+
* @param path 文件路径
|
|
24
|
+
* @returns 文件内容
|
|
25
|
+
*/
|
|
26
|
+
async get(path) {
|
|
27
|
+
try {
|
|
28
|
+
const stats = await stat(path);
|
|
29
|
+
const currentMtime = stats.mtimeMs;
|
|
30
|
+
const cached = this.cache.get(path);
|
|
31
|
+
const now = Date.now();
|
|
32
|
+
if (cached && cached.mtimeMs === currentMtime && now - cached.cachedAt < this.ttl) {
|
|
33
|
+
this.cache.delete(path);
|
|
34
|
+
this.cache.set(path, cached);
|
|
35
|
+
return cached.data;
|
|
36
|
+
}
|
|
37
|
+
const data = await this.loader(path);
|
|
38
|
+
this.cache.set(path, {
|
|
39
|
+
data,
|
|
40
|
+
mtimeMs: currentMtime,
|
|
41
|
+
cachedAt: now
|
|
42
|
+
});
|
|
43
|
+
this.cleanup();
|
|
44
|
+
return data;
|
|
45
|
+
} catch {
|
|
46
|
+
return this.loader(path).catch(() => {
|
|
47
|
+
throw new Error(`Failed to load file: ${path}`);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* 清理过期和超出大小限制的缓存
|
|
53
|
+
*/
|
|
54
|
+
cleanup() {
|
|
55
|
+
const now = Date.now();
|
|
56
|
+
const entries = Array.from(this.cache.entries());
|
|
57
|
+
for (const [key, entry] of entries) {
|
|
58
|
+
if (now - entry.cachedAt >= this.ttl) {
|
|
59
|
+
this.cache.delete(key);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
while (this.cache.size > this.maxSize) {
|
|
63
|
+
const firstKey = this.cache.keys().next().value;
|
|
64
|
+
if (firstKey) {
|
|
65
|
+
this.cache.delete(firstKey);
|
|
66
|
+
} else {
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* 清除指定文件的缓存
|
|
73
|
+
* @param path 文件路径
|
|
74
|
+
*/
|
|
75
|
+
clear(path) {
|
|
76
|
+
this.cache.delete(path);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* 清除所有缓存
|
|
80
|
+
*/
|
|
81
|
+
clearAll() {
|
|
82
|
+
this.cache.clear();
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* 获取缓存大小
|
|
86
|
+
*/
|
|
87
|
+
get size() {
|
|
88
|
+
return this.cache.size;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// src/utils/session.ts
|
|
93
|
+
var statLimit = pLimit(10);
|
|
94
|
+
var sessionContentCache = new FileCache({
|
|
95
|
+
maxSize: 50,
|
|
96
|
+
ttl: 5 * 60 * 1e3,
|
|
97
|
+
// 5 分钟
|
|
98
|
+
loader: async (path) => {
|
|
99
|
+
return readFile2(path, "utf-8");
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
var MAX_MESSAGES = 100;
|
|
7
103
|
function cwdToProjectDir(cwd) {
|
|
8
104
|
return cwd.replace(/\//g, "-").replace(/^-/, "-");
|
|
9
105
|
}
|
|
@@ -20,18 +116,19 @@ async function getSessionPath(cwd, startTime) {
|
|
|
20
116
|
console.error(`[DEBUG] found ${jsonlFiles.length} jsonl files`);
|
|
21
117
|
}
|
|
22
118
|
if (jsonlFiles.length === 0) return "";
|
|
23
|
-
const { stat } = await import("fs/promises");
|
|
24
119
|
const fileStats = await Promise.all(
|
|
25
|
-
jsonlFiles.map(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
120
|
+
jsonlFiles.map(
|
|
121
|
+
(f) => statLimit(async () => {
|
|
122
|
+
const path = join(sessionsDir, f);
|
|
123
|
+
const s = await stat2(path);
|
|
124
|
+
return {
|
|
125
|
+
path,
|
|
126
|
+
birthtime: s.birthtime,
|
|
127
|
+
mtimeMs: s.mtimeMs,
|
|
128
|
+
size: s.size
|
|
129
|
+
};
|
|
130
|
+
})
|
|
131
|
+
)
|
|
35
132
|
);
|
|
36
133
|
if (process.env.DEBUG_SESSION) {
|
|
37
134
|
console.error(`[DEBUG] total files: ${fileStats.length}`);
|
|
@@ -62,7 +159,9 @@ async function getSessionPath(cwd, startTime) {
|
|
|
62
159
|
const mtimeMatched = fileStats.filter((f) => {
|
|
63
160
|
const mtimeDiff = f.mtimeMs - startMs;
|
|
64
161
|
return Math.abs(mtimeDiff) < mtimeThreshold;
|
|
65
|
-
}).sort(
|
|
162
|
+
}).sort(
|
|
163
|
+
(a, b) => Math.abs(a.mtimeMs - startMs) - Math.abs(b.mtimeMs - startMs)
|
|
164
|
+
);
|
|
66
165
|
if (mtimeMatched.length > 0) {
|
|
67
166
|
if (process.env.DEBUG_SESSION) {
|
|
68
167
|
console.error(`[DEBUG] matched by mtime: ${mtimeMatched[0].path}`);
|
|
@@ -82,7 +181,7 @@ async function getSessionPath(cwd, startTime) {
|
|
|
82
181
|
async function getRecentMessages(sessionPath, limit = 5) {
|
|
83
182
|
if (!sessionPath) return [];
|
|
84
183
|
try {
|
|
85
|
-
const content = await
|
|
184
|
+
const content = await readFile2(sessionPath, "utf-8");
|
|
86
185
|
const lines = content.trim().split("\n");
|
|
87
186
|
const messages = [];
|
|
88
187
|
for (const line of lines) {
|
|
@@ -118,7 +217,7 @@ async function getRecentMessages(sessionPath, limit = 5) {
|
|
|
118
217
|
async function getNewMessages(sessionPath, fromLine) {
|
|
119
218
|
if (!sessionPath) return { messages: [], totalLines: 0 };
|
|
120
219
|
try {
|
|
121
|
-
const content = await
|
|
220
|
+
const content = await sessionContentCache.get(sessionPath);
|
|
122
221
|
const lines = content.trim().split("\n");
|
|
123
222
|
const totalLines = lines.length;
|
|
124
223
|
const newLines = lines.slice(fromLine);
|
|
@@ -154,9 +253,9 @@ async function getNewMessages(sessionPath, fromLine) {
|
|
|
154
253
|
}
|
|
155
254
|
}
|
|
156
255
|
async function getAllMessages(sessionPath) {
|
|
157
|
-
if (!sessionPath) return [];
|
|
256
|
+
if (!sessionPath) return { messages: [], lineCount: 0 };
|
|
158
257
|
try {
|
|
159
|
-
const content = await
|
|
258
|
+
const content = await sessionContentCache.get(sessionPath);
|
|
160
259
|
const lines = content.trim().split("\n");
|
|
161
260
|
const messages = [];
|
|
162
261
|
for (const line of lines) {
|
|
@@ -184,9 +283,10 @@ async function getAllMessages(sessionPath) {
|
|
|
184
283
|
} catch {
|
|
185
284
|
}
|
|
186
285
|
}
|
|
187
|
-
|
|
286
|
+
const limitedMessages = messages.length > MAX_MESSAGES ? messages.slice(-MAX_MESSAGES) : messages;
|
|
287
|
+
return { messages: limitedMessages, lineCount: lines.length };
|
|
188
288
|
} catch {
|
|
189
|
-
return [];
|
|
289
|
+
return { messages: [], lineCount: 0 };
|
|
190
290
|
}
|
|
191
291
|
}
|
|
192
292
|
function extractUserText(content) {
|
|
@@ -222,6 +322,12 @@ function formatTimeDiff(date, referenceDate) {
|
|
|
222
322
|
const diffMin = diffSec / 60;
|
|
223
323
|
return diffMs >= 0 ? `\u542F\u52A8\u540E ${diffMin.toFixed(1)} \u5206\u949F` : `\u542F\u52A8\u524D ${diffMin.toFixed(1)} \u5206\u949F`;
|
|
224
324
|
}
|
|
325
|
+
function clearSessionCache(sessionPath) {
|
|
326
|
+
sessionContentCache.clear(sessionPath);
|
|
327
|
+
}
|
|
328
|
+
function clearAllSessionCache() {
|
|
329
|
+
sessionContentCache.clearAll();
|
|
330
|
+
}
|
|
225
331
|
async function debugSessionMatching(processes) {
|
|
226
332
|
const output = [];
|
|
227
333
|
output.push("=== Claude Code \u4F1A\u8BDD\u8C03\u8BD5\u4FE1\u606F ===\n");
|
|
@@ -249,19 +355,20 @@ async function debugSessionMatching(processes) {
|
|
|
249
355
|
continue;
|
|
250
356
|
}
|
|
251
357
|
output.push(" \u627E\u5230\u7684\u4F1A\u8BDD\u6587\u4EF6:");
|
|
252
|
-
const { stat } = await import("fs/promises");
|
|
253
358
|
const fileStats = await Promise.all(
|
|
254
|
-
jsonlFiles.map(
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
359
|
+
jsonlFiles.map(
|
|
360
|
+
(f) => statLimit(async () => {
|
|
361
|
+
const path = join(sessionsDir, f);
|
|
362
|
+
const s = await stat2(path);
|
|
363
|
+
return {
|
|
364
|
+
name: f,
|
|
365
|
+
path,
|
|
366
|
+
birthtime: s.birthtime,
|
|
367
|
+
mtime: new Date(s.mtimeMs),
|
|
368
|
+
size: s.size
|
|
369
|
+
};
|
|
370
|
+
})
|
|
371
|
+
)
|
|
265
372
|
);
|
|
266
373
|
for (const file of fileStats) {
|
|
267
374
|
const ignored = file.size < 1024;
|
|
@@ -316,7 +423,7 @@ async function debugSessionMatching(processes) {
|
|
|
316
423
|
if (matchedFile) {
|
|
317
424
|
output.push(` \u9009\u62E9: ${matchedFile.name}`);
|
|
318
425
|
try {
|
|
319
|
-
const messages = await getAllMessages(matchedFile.path);
|
|
426
|
+
const { messages } = await getAllMessages(matchedFile.path);
|
|
320
427
|
output.push(` \u6D88\u606F\u6570: ${messages.length} \u6761`);
|
|
321
428
|
successCount++;
|
|
322
429
|
} catch {
|
|
@@ -347,5 +454,7 @@ export {
|
|
|
347
454
|
getRecentMessages,
|
|
348
455
|
getNewMessages,
|
|
349
456
|
getAllMessages,
|
|
457
|
+
clearSessionCache,
|
|
458
|
+
clearAllSessionCache,
|
|
350
459
|
debugSessionMatching
|
|
351
460
|
};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/utils/process.ts
|
|
4
|
+
import { exec } from "child_process";
|
|
5
|
+
import { promisify } from "util";
|
|
6
|
+
import pLimit from "p-limit";
|
|
7
|
+
var execAsync = promisify(exec);
|
|
8
|
+
var processLimit = pLimit(8);
|
|
9
|
+
async function getCurrentTty() {
|
|
10
|
+
try {
|
|
11
|
+
const { stdout } = await execAsync("tty");
|
|
12
|
+
return stdout.trim().replace("/dev/", "");
|
|
13
|
+
} catch {
|
|
14
|
+
return "";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
async function batchGetProcessCwd(pids) {
|
|
18
|
+
if (pids.length === 0) return /* @__PURE__ */ new Map();
|
|
19
|
+
return processLimit(async () => {
|
|
20
|
+
try {
|
|
21
|
+
const pidList = pids.join(",");
|
|
22
|
+
const { stdout } = await execAsync(
|
|
23
|
+
`lsof -p ${pidList} 2>/dev/null | grep cwd`
|
|
24
|
+
);
|
|
25
|
+
const result = /* @__PURE__ */ new Map();
|
|
26
|
+
const lines = stdout.trim().split("\n");
|
|
27
|
+
for (const line of lines) {
|
|
28
|
+
const parts = line.trim().split(/\s+/);
|
|
29
|
+
if (parts.length >= 9) {
|
|
30
|
+
const pid = Number.parseInt(parts[1], 10);
|
|
31
|
+
const cwd = parts[8];
|
|
32
|
+
if (cwd.startsWith("/")) {
|
|
33
|
+
result.set(pid, cwd);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
} catch {
|
|
39
|
+
return /* @__PURE__ */ new Map();
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
async function batchGetProcessStats(pids) {
|
|
44
|
+
if (pids.length === 0) return /* @__PURE__ */ new Map();
|
|
45
|
+
return processLimit(async () => {
|
|
46
|
+
try {
|
|
47
|
+
const pidList = pids.join(",");
|
|
48
|
+
const { stdout } = await execAsync(
|
|
49
|
+
`ps -p ${pidList} -o pid,%cpu,%mem,etime 2>/dev/null`
|
|
50
|
+
);
|
|
51
|
+
const result = /* @__PURE__ */ new Map();
|
|
52
|
+
const lines = stdout.trim().split("\n");
|
|
53
|
+
for (let i = 1; i < lines.length; i++) {
|
|
54
|
+
const parts = lines[i].trim().split(/\s+/);
|
|
55
|
+
if (parts.length >= 4) {
|
|
56
|
+
const pid = Number.parseInt(parts[0], 10);
|
|
57
|
+
result.set(pid, {
|
|
58
|
+
cpu: Number.parseFloat(parts[1]) || 0,
|
|
59
|
+
memory: Number.parseFloat(parts[2]) || 0,
|
|
60
|
+
elapsed: parts[3] || ""
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return result;
|
|
65
|
+
} catch {
|
|
66
|
+
return /* @__PURE__ */ new Map();
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
function parseElapsedToDate(elapsed) {
|
|
71
|
+
const now = /* @__PURE__ */ new Date();
|
|
72
|
+
const parts = elapsed.split(/[-:]/);
|
|
73
|
+
let seconds = 0;
|
|
74
|
+
if (parts.length === 2) {
|
|
75
|
+
seconds = Number.parseInt(parts[0]) * 60 + Number.parseInt(parts[1]);
|
|
76
|
+
} else if (parts.length === 3) {
|
|
77
|
+
seconds = Number.parseInt(parts[0]) * 3600 + Number.parseInt(parts[1]) * 60 + Number.parseInt(parts[2]);
|
|
78
|
+
} else if (parts.length === 4) {
|
|
79
|
+
seconds = Number.parseInt(parts[0]) * 86400 + Number.parseInt(parts[1]) * 3600 + Number.parseInt(parts[2]) * 60 + Number.parseInt(parts[3]);
|
|
80
|
+
}
|
|
81
|
+
return new Date(now.getTime() - seconds * 1e3);
|
|
82
|
+
}
|
|
83
|
+
async function getClaudeProcesses() {
|
|
84
|
+
const currentTty = await getCurrentTty();
|
|
85
|
+
let stdout;
|
|
86
|
+
try {
|
|
87
|
+
const result = await execAsync(
|
|
88
|
+
`ps -eo pid,tty,command | grep -E '^\\s*[0-9]+\\s+\\S+\\s+claude(\\s|$)' | grep -v 'chrome-native-host' | grep -v grep`
|
|
89
|
+
);
|
|
90
|
+
stdout = result.stdout;
|
|
91
|
+
} catch {
|
|
92
|
+
return [];
|
|
93
|
+
}
|
|
94
|
+
const lines = stdout.trim().split("\n").filter(Boolean);
|
|
95
|
+
if (lines.length === 0) return [];
|
|
96
|
+
const basicInfo = [];
|
|
97
|
+
for (const line of lines) {
|
|
98
|
+
const match = line.trim().match(/^(\d+)\s+(\S+)\s+(.+)$/);
|
|
99
|
+
if (match) {
|
|
100
|
+
basicInfo.push({
|
|
101
|
+
pid: Number.parseInt(match[1]),
|
|
102
|
+
tty: match[2]
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (basicInfo.length === 0) return [];
|
|
107
|
+
const pids = basicInfo.map((p) => p.pid);
|
|
108
|
+
const [cwdMap, statsMap] = await Promise.all([
|
|
109
|
+
batchGetProcessCwd(pids),
|
|
110
|
+
batchGetProcessStats(pids)
|
|
111
|
+
]);
|
|
112
|
+
const processes = [];
|
|
113
|
+
for (const info of basicInfo) {
|
|
114
|
+
const cwd = cwdMap.get(info.pid) || "\u672A\u77E5";
|
|
115
|
+
const stats = statsMap.get(info.pid) || {
|
|
116
|
+
cpu: 0,
|
|
117
|
+
memory: 0,
|
|
118
|
+
elapsed: ""
|
|
119
|
+
};
|
|
120
|
+
const isOrphan = info.tty === "??" || info.tty === "?";
|
|
121
|
+
const isCurrent = currentTty !== "" && info.tty === currentTty;
|
|
122
|
+
processes.push({
|
|
123
|
+
pid: info.pid,
|
|
124
|
+
tty: info.tty,
|
|
125
|
+
cwd,
|
|
126
|
+
isCurrent,
|
|
127
|
+
isOrphan,
|
|
128
|
+
cpu: stats.cpu,
|
|
129
|
+
memory: stats.memory,
|
|
130
|
+
elapsed: stats.elapsed,
|
|
131
|
+
startTime: parseElapsedToDate(stats.elapsed),
|
|
132
|
+
sessionPath: ""
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
return processes;
|
|
136
|
+
}
|
|
137
|
+
async function killProcess(pid, force = false) {
|
|
138
|
+
try {
|
|
139
|
+
const signal = force ? "KILL" : "TERM";
|
|
140
|
+
await execAsync(`kill -${signal} ${pid}`);
|
|
141
|
+
return true;
|
|
142
|
+
} catch {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export {
|
|
148
|
+
getClaudeProcesses,
|
|
149
|
+
killProcess
|
|
150
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
import {
|
|
3
3
|
getClaudeProcesses,
|
|
4
4
|
killProcess
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-KF2FBZRQ.js";
|
|
6
6
|
import {
|
|
7
|
+
clearSessionCache,
|
|
7
8
|
getAllMessages,
|
|
8
9
|
getNewMessages,
|
|
9
10
|
getSessionPath
|
|
10
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-JYWGOPVM.js";
|
|
11
12
|
|
|
12
13
|
// src/index.tsx
|
|
13
14
|
import { withFullScreen } from "fullscreen-ink";
|
|
@@ -308,6 +309,7 @@ function ProcessList({
|
|
|
308
309
|
}
|
|
309
310
|
|
|
310
311
|
// src/hooks/useProcesses.ts
|
|
312
|
+
import pLimit from "p-limit";
|
|
311
313
|
import { useCallback, useEffect as useEffect2, useMemo, useRef as useRef2, useState } from "react";
|
|
312
314
|
|
|
313
315
|
// src/hooks/useSessionWatcher.ts
|
|
@@ -330,6 +332,7 @@ function useSessionWatcher(sessionPaths, onFileChange) {
|
|
|
330
332
|
}
|
|
331
333
|
});
|
|
332
334
|
watcher.on("change", (path) => {
|
|
335
|
+
clearSessionCache(path);
|
|
333
336
|
const existingTimer = debounceTimersRef.current.get(path);
|
|
334
337
|
if (existingTimer) {
|
|
335
338
|
clearTimeout(existingTimer);
|
|
@@ -355,6 +358,7 @@ function useSessionWatcher(sessionPaths, onFileChange) {
|
|
|
355
358
|
}
|
|
356
359
|
|
|
357
360
|
// src/hooks/useProcesses.ts
|
|
361
|
+
var sessionLimit = pLimit(5);
|
|
358
362
|
function sortProcesses(processes, sortField) {
|
|
359
363
|
const sorted = [...processes];
|
|
360
364
|
switch (sortField) {
|
|
@@ -383,22 +387,20 @@ function useProcesses(interval2) {
|
|
|
383
387
|
try {
|
|
384
388
|
const procs = await getClaudeProcesses();
|
|
385
389
|
const enriched = await Promise.all(
|
|
386
|
-
procs.map(
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
};
|
|
401
|
-
})
|
|
390
|
+
procs.map(
|
|
391
|
+
(proc) => sessionLimit(async () => {
|
|
392
|
+
const sessionPath = await getSessionPath(proc.cwd, proc.startTime);
|
|
393
|
+
const { messages, lineCount } = await getAllMessages(sessionPath);
|
|
394
|
+
if (sessionPath && !sessionLineNumbers.current.has(sessionPath)) {
|
|
395
|
+
sessionLineNumbers.current.set(sessionPath, lineCount);
|
|
396
|
+
}
|
|
397
|
+
return {
|
|
398
|
+
...proc,
|
|
399
|
+
sessionPath,
|
|
400
|
+
messages
|
|
401
|
+
};
|
|
402
|
+
})
|
|
403
|
+
)
|
|
402
404
|
);
|
|
403
405
|
setRawProcesses(enriched);
|
|
404
406
|
setError(null);
|
|
@@ -660,10 +662,10 @@ var cli = meow(
|
|
|
660
662
|
);
|
|
661
663
|
var { list, json, debug, interval } = cli.flags;
|
|
662
664
|
if (list || json || debug) {
|
|
663
|
-
const { getClaudeProcesses: getClaudeProcesses2 } = await import("./process-
|
|
665
|
+
const { getClaudeProcesses: getClaudeProcesses2 } = await import("./process-XJGJPUAG.js");
|
|
664
666
|
const processes = await getClaudeProcesses2();
|
|
665
667
|
if (debug) {
|
|
666
|
-
const { debugSessionMatching } = await import("./session-
|
|
668
|
+
const { debugSessionMatching } = await import("./session-FTYMYOIH.js");
|
|
667
669
|
const debugInfo = await debugSessionMatching(processes);
|
|
668
670
|
console.log(debugInfo);
|
|
669
671
|
} else if (json) {
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
clearAllSessionCache,
|
|
4
|
+
clearSessionCache,
|
|
3
5
|
debugSessionMatching,
|
|
4
6
|
getAllMessages,
|
|
5
7
|
getNewMessages,
|
|
6
8
|
getRecentMessages,
|
|
7
9
|
getSessionPath
|
|
8
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-JYWGOPVM.js";
|
|
9
11
|
export {
|
|
12
|
+
clearAllSessionCache,
|
|
13
|
+
clearSessionCache,
|
|
10
14
|
debugSessionMatching,
|
|
11
15
|
getAllMessages,
|
|
12
16
|
getNewMessages,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-ps",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"description": "TUI application for viewing and managing Claude Code processes",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"fullscreen-ink": "^0.1.0",
|
|
34
34
|
"ink": "^5.0.1",
|
|
35
35
|
"meow": "^13.2.0",
|
|
36
|
+
"p-limit": "^7.3.0",
|
|
36
37
|
"react": "^18.3.1"
|
|
37
38
|
},
|
|
38
39
|
"devDependencies": {
|
package/dist/chunk-EZHIVMX4.js
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/utils/process.ts
|
|
4
|
-
import { exec } from "child_process";
|
|
5
|
-
import { promisify } from "util";
|
|
6
|
-
var execAsync = promisify(exec);
|
|
7
|
-
async function getCurrentTty() {
|
|
8
|
-
try {
|
|
9
|
-
const { stdout } = await execAsync("tty");
|
|
10
|
-
return stdout.trim().replace("/dev/", "");
|
|
11
|
-
} catch {
|
|
12
|
-
return "";
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
async function getProcessCwd(pid) {
|
|
16
|
-
try {
|
|
17
|
-
const { stdout } = await execAsync(`lsof -p ${pid} 2>/dev/null | grep cwd`);
|
|
18
|
-
const match = stdout.trim().match(/\s(\/.+)$/);
|
|
19
|
-
return match ? match[1] : "";
|
|
20
|
-
} catch {
|
|
21
|
-
return "";
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
async function getProcessStats(pid) {
|
|
25
|
-
try {
|
|
26
|
-
const { stdout } = await execAsync(
|
|
27
|
-
`ps -p ${pid} -o %cpu,%mem,etime 2>/dev/null`
|
|
28
|
-
);
|
|
29
|
-
const lines = stdout.trim().split("\n");
|
|
30
|
-
if (lines.length < 2) return { cpu: 0, memory: 0, elapsed: "" };
|
|
31
|
-
const parts = lines[1].trim().split(/\s+/);
|
|
32
|
-
return {
|
|
33
|
-
cpu: Number.parseFloat(parts[0]) || 0,
|
|
34
|
-
memory: Number.parseFloat(parts[1]) || 0,
|
|
35
|
-
elapsed: parts[2] || ""
|
|
36
|
-
};
|
|
37
|
-
} catch {
|
|
38
|
-
return { cpu: 0, memory: 0, elapsed: "" };
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
function parseElapsedToDate(elapsed) {
|
|
42
|
-
const now = /* @__PURE__ */ new Date();
|
|
43
|
-
const parts = elapsed.split(/[-:]/);
|
|
44
|
-
let seconds = 0;
|
|
45
|
-
if (parts.length === 2) {
|
|
46
|
-
seconds = Number.parseInt(parts[0]) * 60 + Number.parseInt(parts[1]);
|
|
47
|
-
} else if (parts.length === 3) {
|
|
48
|
-
seconds = Number.parseInt(parts[0]) * 3600 + Number.parseInt(parts[1]) * 60 + Number.parseInt(parts[2]);
|
|
49
|
-
} else if (parts.length === 4) {
|
|
50
|
-
seconds = Number.parseInt(parts[0]) * 86400 + Number.parseInt(parts[1]) * 3600 + Number.parseInt(parts[2]) * 60 + Number.parseInt(parts[3]);
|
|
51
|
-
}
|
|
52
|
-
return new Date(now.getTime() - seconds * 1e3);
|
|
53
|
-
}
|
|
54
|
-
async function getClaudeProcesses() {
|
|
55
|
-
const currentTty = await getCurrentTty();
|
|
56
|
-
let stdout;
|
|
57
|
-
try {
|
|
58
|
-
const result = await execAsync(
|
|
59
|
-
`ps -eo pid,tty,command | grep -E '^\\s*[0-9]+\\s+\\S+\\s+claude(\\s|$)' | grep -v 'chrome-native-host' | grep -v grep`
|
|
60
|
-
);
|
|
61
|
-
stdout = result.stdout;
|
|
62
|
-
} catch {
|
|
63
|
-
return [];
|
|
64
|
-
}
|
|
65
|
-
const lines = stdout.trim().split("\n").filter(Boolean);
|
|
66
|
-
const processes = [];
|
|
67
|
-
for (const line of lines) {
|
|
68
|
-
const match = line.trim().match(/^(\d+)\s+(\S+)\s+(.+)$/);
|
|
69
|
-
if (!match) continue;
|
|
70
|
-
const pid = Number.parseInt(match[1]);
|
|
71
|
-
const tty = match[2];
|
|
72
|
-
const [cwd, stats] = await Promise.all([
|
|
73
|
-
getProcessCwd(pid),
|
|
74
|
-
getProcessStats(pid)
|
|
75
|
-
]);
|
|
76
|
-
const isOrphan = tty === "??" || tty === "?";
|
|
77
|
-
const isCurrent = currentTty !== "" && tty === currentTty;
|
|
78
|
-
processes.push({
|
|
79
|
-
pid,
|
|
80
|
-
tty,
|
|
81
|
-
cwd: cwd || "\u672A\u77E5",
|
|
82
|
-
isCurrent,
|
|
83
|
-
isOrphan,
|
|
84
|
-
cpu: stats.cpu,
|
|
85
|
-
memory: stats.memory,
|
|
86
|
-
elapsed: stats.elapsed,
|
|
87
|
-
startTime: parseElapsedToDate(stats.elapsed),
|
|
88
|
-
sessionPath: ""
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
return processes;
|
|
92
|
-
}
|
|
93
|
-
async function killProcess(pid, force = false) {
|
|
94
|
-
try {
|
|
95
|
-
const signal = force ? "KILL" : "TERM";
|
|
96
|
-
await execAsync(`kill -${signal} ${pid}`);
|
|
97
|
-
return true;
|
|
98
|
-
} catch {
|
|
99
|
-
return false;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export {
|
|
104
|
-
getClaudeProcesses,
|
|
105
|
-
killProcess
|
|
106
|
-
};
|