claude-ps 0.2.4 → 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-JYWGOPVM.js +460 -0
- package/dist/chunk-KF2FBZRQ.js +150 -0
- package/dist/index.js +44 -171
- package/dist/{process-AUO5UVTV.js → process-XJGJPUAG.js} +1 -1
- package/dist/session-FTYMYOIH.js +19 -0
- package/package.json +2 -1
- package/dist/chunk-EZHIVMX4.js +0 -106
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/utils/session.ts
|
|
4
|
+
import { readFile as readFile2, readdir, stat as stat2 } from "fs/promises";
|
|
5
|
+
import { homedir } from "os";
|
|
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;
|
|
103
|
+
function cwdToProjectDir(cwd) {
|
|
104
|
+
return cwd.replace(/\//g, "-").replace(/^-/, "-");
|
|
105
|
+
}
|
|
106
|
+
async function getSessionPath(cwd, startTime) {
|
|
107
|
+
const projectDir = cwdToProjectDir(cwd);
|
|
108
|
+
const sessionsDir = join(homedir(), ".claude", "projects", projectDir);
|
|
109
|
+
try {
|
|
110
|
+
const files = await readdir(sessionsDir);
|
|
111
|
+
const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
|
|
112
|
+
if (process.env.DEBUG_SESSION) {
|
|
113
|
+
console.error(`[DEBUG] cwd: ${cwd}`);
|
|
114
|
+
console.error(`[DEBUG] projectDir: ${projectDir}`);
|
|
115
|
+
console.error(`[DEBUG] sessionsDir: ${sessionsDir}`);
|
|
116
|
+
console.error(`[DEBUG] found ${jsonlFiles.length} jsonl files`);
|
|
117
|
+
}
|
|
118
|
+
if (jsonlFiles.length === 0) return "";
|
|
119
|
+
const fileStats = await Promise.all(
|
|
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
|
+
)
|
|
132
|
+
);
|
|
133
|
+
if (process.env.DEBUG_SESSION) {
|
|
134
|
+
console.error(`[DEBUG] total files: ${fileStats.length}`);
|
|
135
|
+
for (const f of fileStats) {
|
|
136
|
+
console.error(
|
|
137
|
+
`[DEBUG] ${f.path.split("/").pop()}: ${f.size} bytes, birth: ${f.birthtime.toISOString()}, mtime: ${new Date(f.mtimeMs).toISOString()}`
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
if (startTime) {
|
|
141
|
+
console.error(`[DEBUG] startTime: ${startTime.toISOString()}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (startTime && fileStats.length > 0) {
|
|
145
|
+
const startMs = startTime.getTime();
|
|
146
|
+
const birthtimeThreshold = 3e5;
|
|
147
|
+
const birthtimeMatched = fileStats.filter(
|
|
148
|
+
(f) => Math.abs(f.birthtime.getTime() - startMs) < birthtimeThreshold
|
|
149
|
+
).sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
150
|
+
if (birthtimeMatched.length > 0) {
|
|
151
|
+
if (process.env.DEBUG_SESSION) {
|
|
152
|
+
console.error(
|
|
153
|
+
`[DEBUG] matched by birthtime: ${birthtimeMatched[0].path}`
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
return birthtimeMatched[0].path;
|
|
157
|
+
}
|
|
158
|
+
const mtimeThreshold = 6e5;
|
|
159
|
+
const mtimeMatched = fileStats.filter((f) => {
|
|
160
|
+
const mtimeDiff = f.mtimeMs - startMs;
|
|
161
|
+
return Math.abs(mtimeDiff) < mtimeThreshold;
|
|
162
|
+
}).sort(
|
|
163
|
+
(a, b) => Math.abs(a.mtimeMs - startMs) - Math.abs(b.mtimeMs - startMs)
|
|
164
|
+
);
|
|
165
|
+
if (mtimeMatched.length > 0) {
|
|
166
|
+
if (process.env.DEBUG_SESSION) {
|
|
167
|
+
console.error(`[DEBUG] matched by mtime: ${mtimeMatched[0].path}`);
|
|
168
|
+
}
|
|
169
|
+
return mtimeMatched[0].path;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
fileStats.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
173
|
+
if (process.env.DEBUG_SESSION && fileStats[0]) {
|
|
174
|
+
console.error(`[DEBUG] fallback to latest: ${fileStats[0].path}`);
|
|
175
|
+
}
|
|
176
|
+
return fileStats[0]?.path || "";
|
|
177
|
+
} catch {
|
|
178
|
+
return "";
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
async function getRecentMessages(sessionPath, limit = 5) {
|
|
182
|
+
if (!sessionPath) return [];
|
|
183
|
+
try {
|
|
184
|
+
const content = await readFile2(sessionPath, "utf-8");
|
|
185
|
+
const lines = content.trim().split("\n");
|
|
186
|
+
const messages = [];
|
|
187
|
+
for (const line of lines) {
|
|
188
|
+
try {
|
|
189
|
+
const entry = JSON.parse(line);
|
|
190
|
+
if (entry.type === "user" && entry.message?.content) {
|
|
191
|
+
const text = extractUserText(entry.message.content);
|
|
192
|
+
if (text && !isMetaMessage(text)) {
|
|
193
|
+
messages.push({
|
|
194
|
+
role: "user",
|
|
195
|
+
content: truncate(text, 100),
|
|
196
|
+
timestamp: entry.timestamp || ""
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
} else if (entry.type === "assistant" && entry.message?.content) {
|
|
200
|
+
const text = extractAssistantText(entry.message.content);
|
|
201
|
+
if (text) {
|
|
202
|
+
messages.push({
|
|
203
|
+
role: "assistant",
|
|
204
|
+
content: truncate(text, 100),
|
|
205
|
+
timestamp: entry.timestamp || ""
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
} catch {
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return messages.slice(-limit);
|
|
213
|
+
} catch {
|
|
214
|
+
return [];
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
async function getNewMessages(sessionPath, fromLine) {
|
|
218
|
+
if (!sessionPath) return { messages: [], totalLines: 0 };
|
|
219
|
+
try {
|
|
220
|
+
const content = await sessionContentCache.get(sessionPath);
|
|
221
|
+
const lines = content.trim().split("\n");
|
|
222
|
+
const totalLines = lines.length;
|
|
223
|
+
const newLines = lines.slice(fromLine);
|
|
224
|
+
const messages = [];
|
|
225
|
+
for (const line of newLines) {
|
|
226
|
+
try {
|
|
227
|
+
const entry = JSON.parse(line);
|
|
228
|
+
if (entry.type === "user" && entry.message?.content) {
|
|
229
|
+
const text = extractUserText(entry.message.content);
|
|
230
|
+
if (text && !isMetaMessage(text)) {
|
|
231
|
+
messages.push({
|
|
232
|
+
role: "user",
|
|
233
|
+
content: truncate(text, 100),
|
|
234
|
+
timestamp: entry.timestamp || ""
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
} else if (entry.type === "assistant" && entry.message?.content) {
|
|
238
|
+
const text = extractAssistantText(entry.message.content);
|
|
239
|
+
if (text) {
|
|
240
|
+
messages.push({
|
|
241
|
+
role: "assistant",
|
|
242
|
+
content: truncate(text, 100),
|
|
243
|
+
timestamp: entry.timestamp || ""
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
} catch {
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return { messages, totalLines };
|
|
251
|
+
} catch {
|
|
252
|
+
return { messages: [], totalLines: 0 };
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
async function getAllMessages(sessionPath) {
|
|
256
|
+
if (!sessionPath) return { messages: [], lineCount: 0 };
|
|
257
|
+
try {
|
|
258
|
+
const content = await sessionContentCache.get(sessionPath);
|
|
259
|
+
const lines = content.trim().split("\n");
|
|
260
|
+
const messages = [];
|
|
261
|
+
for (const line of lines) {
|
|
262
|
+
try {
|
|
263
|
+
const entry = JSON.parse(line);
|
|
264
|
+
if (entry.type === "user" && entry.message?.content) {
|
|
265
|
+
const text = extractUserText(entry.message.content);
|
|
266
|
+
if (text && !isMetaMessage(text)) {
|
|
267
|
+
messages.push({
|
|
268
|
+
role: "user",
|
|
269
|
+
content: truncate(text, 100),
|
|
270
|
+
timestamp: entry.timestamp || ""
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
} else if (entry.type === "assistant" && entry.message?.content) {
|
|
274
|
+
const text = extractAssistantText(entry.message.content);
|
|
275
|
+
if (text) {
|
|
276
|
+
messages.push({
|
|
277
|
+
role: "assistant",
|
|
278
|
+
content: truncate(text, 100),
|
|
279
|
+
timestamp: entry.timestamp || ""
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
} catch {
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
const limitedMessages = messages.length > MAX_MESSAGES ? messages.slice(-MAX_MESSAGES) : messages;
|
|
287
|
+
return { messages: limitedMessages, lineCount: lines.length };
|
|
288
|
+
} catch {
|
|
289
|
+
return { messages: [], lineCount: 0 };
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
function extractUserText(content) {
|
|
293
|
+
if (typeof content === "string") {
|
|
294
|
+
return content;
|
|
295
|
+
}
|
|
296
|
+
return "";
|
|
297
|
+
}
|
|
298
|
+
function extractAssistantText(content) {
|
|
299
|
+
if (!Array.isArray(content)) return "";
|
|
300
|
+
const textParts = content.filter((item) => item.type === "text" && item.text).map((item) => item.text);
|
|
301
|
+
return textParts.join("\n");
|
|
302
|
+
}
|
|
303
|
+
function isMetaMessage(text) {
|
|
304
|
+
return text.startsWith("<local-command") || text.startsWith("<command-name>") || text.startsWith("<command-message>");
|
|
305
|
+
}
|
|
306
|
+
function truncate(text, maxLen) {
|
|
307
|
+
const clean = text.replace(/\s+/g, " ").trim();
|
|
308
|
+
if (clean.length <= maxLen) return clean;
|
|
309
|
+
return `${clean.slice(0, maxLen - 3)}...`;
|
|
310
|
+
}
|
|
311
|
+
function formatSize(bytes) {
|
|
312
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
313
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
314
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
315
|
+
}
|
|
316
|
+
function formatTimeDiff(date, referenceDate) {
|
|
317
|
+
const diffMs = date.getTime() - referenceDate.getTime();
|
|
318
|
+
const diffSec = Math.abs(diffMs / 1e3);
|
|
319
|
+
if (diffSec < 60) {
|
|
320
|
+
return diffMs >= 0 ? `\u542F\u52A8\u540E ${diffSec.toFixed(0)} \u79D2` : `\u542F\u52A8\u524D ${diffSec.toFixed(0)} \u79D2`;
|
|
321
|
+
}
|
|
322
|
+
const diffMin = diffSec / 60;
|
|
323
|
+
return diffMs >= 0 ? `\u542F\u52A8\u540E ${diffMin.toFixed(1)} \u5206\u949F` : `\u542F\u52A8\u524D ${diffMin.toFixed(1)} \u5206\u949F`;
|
|
324
|
+
}
|
|
325
|
+
function clearSessionCache(sessionPath) {
|
|
326
|
+
sessionContentCache.clear(sessionPath);
|
|
327
|
+
}
|
|
328
|
+
function clearAllSessionCache() {
|
|
329
|
+
sessionContentCache.clearAll();
|
|
330
|
+
}
|
|
331
|
+
async function debugSessionMatching(processes) {
|
|
332
|
+
const output = [];
|
|
333
|
+
output.push("=== Claude Code \u4F1A\u8BDD\u8C03\u8BD5\u4FE1\u606F ===\n");
|
|
334
|
+
if (processes.length === 0) {
|
|
335
|
+
output.push("\u672A\u627E\u5230\u8FD0\u884C\u4E2D\u7684 Claude Code \u8FDB\u7A0B\n");
|
|
336
|
+
return output.join("\n");
|
|
337
|
+
}
|
|
338
|
+
let successCount = 0;
|
|
339
|
+
for (let i = 0; i < processes.length; i++) {
|
|
340
|
+
const proc = processes[i];
|
|
341
|
+
output.push(`\u8FDB\u7A0B #${i + 1} (PID: ${proc.pid})`);
|
|
342
|
+
output.push(` \u5DE5\u4F5C\u76EE\u5F55: ${proc.cwd}`);
|
|
343
|
+
output.push(` \u542F\u52A8\u65F6\u95F4: ${proc.startTime.toISOString()}`);
|
|
344
|
+
const projectDir = cwdToProjectDir(proc.cwd);
|
|
345
|
+
const sessionsDir = join(homedir(), ".claude", "projects", projectDir);
|
|
346
|
+
output.push(` \u9879\u76EE\u76EE\u5F55: ${projectDir}`);
|
|
347
|
+
output.push(` \u4F1A\u8BDD\u76EE\u5F55: ${sessionsDir}`);
|
|
348
|
+
output.push("");
|
|
349
|
+
try {
|
|
350
|
+
const files = await readdir(sessionsDir);
|
|
351
|
+
const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
|
|
352
|
+
if (jsonlFiles.length === 0) {
|
|
353
|
+
output.push(" \u26A0\uFE0F \u672A\u627E\u5230\u4F1A\u8BDD\u6587\u4EF6");
|
|
354
|
+
output.push("");
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
output.push(" \u627E\u5230\u7684\u4F1A\u8BDD\u6587\u4EF6:");
|
|
358
|
+
const fileStats = await Promise.all(
|
|
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
|
+
)
|
|
372
|
+
);
|
|
373
|
+
for (const file of fileStats) {
|
|
374
|
+
const ignored = file.size < 1024;
|
|
375
|
+
const prefix = ignored ? " \u2717" : " \u2713";
|
|
376
|
+
output.push(`${prefix} ${file.name}`);
|
|
377
|
+
output.push(` \u5927\u5C0F: ${formatSize(file.size)}`);
|
|
378
|
+
if (ignored) {
|
|
379
|
+
output.push(" (< 1KB, \u5DF2\u5FFD\u7565)");
|
|
380
|
+
}
|
|
381
|
+
output.push(
|
|
382
|
+
` \u521B\u5EFA: ${file.birthtime.toISOString()} (${formatTimeDiff(file.birthtime, proc.startTime)})`
|
|
383
|
+
);
|
|
384
|
+
output.push(
|
|
385
|
+
` \u4FEE\u6539: ${file.mtime.toISOString()} (${formatTimeDiff(file.mtime, proc.startTime)})`
|
|
386
|
+
);
|
|
387
|
+
output.push("");
|
|
388
|
+
}
|
|
389
|
+
const startMs = proc.startTime.getTime();
|
|
390
|
+
const validFiles = fileStats.filter((f) => f.size >= 1024);
|
|
391
|
+
let matchedFile = null;
|
|
392
|
+
let matchStrategy = "";
|
|
393
|
+
const birthtimeThreshold = 3e5;
|
|
394
|
+
const birthtimeMatched = validFiles.filter(
|
|
395
|
+
(f) => Math.abs(f.birthtime.getTime() - startMs) < birthtimeThreshold
|
|
396
|
+
).sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
397
|
+
if (birthtimeMatched.length > 0) {
|
|
398
|
+
matchedFile = birthtimeMatched[0];
|
|
399
|
+
matchStrategy = "\u521B\u5EFA\u65F6\u95F4\u5339\u914D \u2713";
|
|
400
|
+
} else {
|
|
401
|
+
const mtimeThreshold = 6e5;
|
|
402
|
+
const mtimeMatched = validFiles.filter((f) => {
|
|
403
|
+
const mtimeDiff = f.mtime.getTime() - startMs;
|
|
404
|
+
return Math.abs(mtimeDiff) < mtimeThreshold;
|
|
405
|
+
}).sort(
|
|
406
|
+
(a, b) => Math.abs(a.mtime.getTime() - startMs) - Math.abs(b.mtime.getTime() - startMs)
|
|
407
|
+
);
|
|
408
|
+
if (mtimeMatched.length > 0) {
|
|
409
|
+
matchedFile = mtimeMatched[0];
|
|
410
|
+
matchStrategy = "\u4FEE\u6539\u65F6\u95F4\u5339\u914D \u2713";
|
|
411
|
+
} else {
|
|
412
|
+
const sorted = [...validFiles].sort(
|
|
413
|
+
(a, b) => b.mtime.getTime() - a.mtime.getTime()
|
|
414
|
+
);
|
|
415
|
+
if (sorted.length > 0) {
|
|
416
|
+
matchedFile = sorted[0];
|
|
417
|
+
matchStrategy = "\u56DE\u9000\u5230\u6700\u65B0\u6587\u4EF6";
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
output.push(" \u5339\u914D\u7ED3\u679C:");
|
|
422
|
+
output.push(` \u7B56\u7565: ${matchStrategy}`);
|
|
423
|
+
if (matchedFile) {
|
|
424
|
+
output.push(` \u9009\u62E9: ${matchedFile.name}`);
|
|
425
|
+
try {
|
|
426
|
+
const { messages } = await getAllMessages(matchedFile.path);
|
|
427
|
+
output.push(` \u6D88\u606F\u6570: ${messages.length} \u6761`);
|
|
428
|
+
successCount++;
|
|
429
|
+
} catch {
|
|
430
|
+
output.push(" \u6D88\u606F\u6570: \u8BFB\u53D6\u5931\u8D25");
|
|
431
|
+
}
|
|
432
|
+
} else {
|
|
433
|
+
output.push(" \u9009\u62E9: \u65E0");
|
|
434
|
+
}
|
|
435
|
+
output.push("");
|
|
436
|
+
output.push("---");
|
|
437
|
+
output.push("");
|
|
438
|
+
} catch (error) {
|
|
439
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
440
|
+
output.push(` \u274C \u9519\u8BEF: ${errorMsg}`);
|
|
441
|
+
output.push("");
|
|
442
|
+
output.push("---");
|
|
443
|
+
output.push("");
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
output.push(
|
|
447
|
+
`\u603B\u8BA1: ${processes.length} \u4E2A\u8FDB\u7A0B, ${successCount} \u4E2A\u6210\u529F\u5339\u914D\u4F1A\u8BDD`
|
|
448
|
+
);
|
|
449
|
+
return output.join("\n");
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
export {
|
|
453
|
+
getSessionPath,
|
|
454
|
+
getRecentMessages,
|
|
455
|
+
getNewMessages,
|
|
456
|
+
getAllMessages,
|
|
457
|
+
clearSessionCache,
|
|
458
|
+
clearAllSessionCache,
|
|
459
|
+
debugSessionMatching
|
|
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,7 +2,13 @@
|
|
|
2
2
|
import {
|
|
3
3
|
getClaudeProcesses,
|
|
4
4
|
killProcess
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-KF2FBZRQ.js";
|
|
6
|
+
import {
|
|
7
|
+
clearSessionCache,
|
|
8
|
+
getAllMessages,
|
|
9
|
+
getNewMessages,
|
|
10
|
+
getSessionPath
|
|
11
|
+
} from "./chunk-JYWGOPVM.js";
|
|
6
12
|
|
|
7
13
|
// src/index.tsx
|
|
8
14
|
import { withFullScreen } from "fullscreen-ink";
|
|
@@ -193,7 +199,10 @@ function formatElapsed(elapsed) {
|
|
|
193
199
|
|
|
194
200
|
// src/components/ui/DetailPanel.tsx
|
|
195
201
|
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
196
|
-
function DetailPanel({
|
|
202
|
+
function DetailPanel({
|
|
203
|
+
process: proc,
|
|
204
|
+
isLive = true
|
|
205
|
+
}) {
|
|
197
206
|
if (!proc) {
|
|
198
207
|
return /* @__PURE__ */ jsx4(EmptyPrompt, { message: "\u9009\u62E9\u4E00\u4E2A\u8FDB\u7A0B\u67E5\u770B\u8BE6\u60C5" });
|
|
199
208
|
}
|
|
@@ -300,159 +309,12 @@ function ProcessList({
|
|
|
300
309
|
}
|
|
301
310
|
|
|
302
311
|
// src/hooks/useProcesses.ts
|
|
312
|
+
import pLimit from "p-limit";
|
|
303
313
|
import { useCallback, useEffect as useEffect2, useMemo, useRef as useRef2, useState } from "react";
|
|
304
314
|
|
|
305
|
-
// src/utils/session.ts
|
|
306
|
-
import { readFile, readdir } from "fs/promises";
|
|
307
|
-
import { homedir } from "os";
|
|
308
|
-
import { join } from "path";
|
|
309
|
-
function cwdToProjectDir(cwd) {
|
|
310
|
-
return cwd.replace(/\//g, "-").replace(/^-/, "-");
|
|
311
|
-
}
|
|
312
|
-
async function getSessionPath(cwd, startTime) {
|
|
313
|
-
const projectDir = cwdToProjectDir(cwd);
|
|
314
|
-
const sessionsDir = join(homedir(), ".claude", "projects", projectDir);
|
|
315
|
-
try {
|
|
316
|
-
const files = await readdir(sessionsDir);
|
|
317
|
-
const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
|
|
318
|
-
if (jsonlFiles.length === 0) return "";
|
|
319
|
-
const { stat } = await import("fs/promises");
|
|
320
|
-
const fileStats = await Promise.all(
|
|
321
|
-
jsonlFiles.map(async (f) => {
|
|
322
|
-
const path = join(sessionsDir, f);
|
|
323
|
-
const s = await stat(path);
|
|
324
|
-
return {
|
|
325
|
-
path,
|
|
326
|
-
birthtime: s.birthtime,
|
|
327
|
-
mtimeMs: s.mtimeMs,
|
|
328
|
-
size: s.size
|
|
329
|
-
};
|
|
330
|
-
})
|
|
331
|
-
);
|
|
332
|
-
const minSize = 1024;
|
|
333
|
-
const validFiles = fileStats.filter((f) => f.size >= minSize);
|
|
334
|
-
if (startTime && validFiles.length > 0) {
|
|
335
|
-
const startMs = startTime.getTime();
|
|
336
|
-
const birthtimeThreshold = 1e4;
|
|
337
|
-
const birthtimeMatched = validFiles.filter(
|
|
338
|
-
(f) => Math.abs(f.birthtime.getTime() - startMs) < birthtimeThreshold
|
|
339
|
-
).sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
340
|
-
if (birthtimeMatched.length > 0) {
|
|
341
|
-
return birthtimeMatched[0].path;
|
|
342
|
-
}
|
|
343
|
-
const mtimeThreshold = 6e4;
|
|
344
|
-
const mtimeMatched = validFiles.filter((f) => {
|
|
345
|
-
const mtimeDiff = f.mtimeMs - startMs;
|
|
346
|
-
return mtimeDiff >= 0 && mtimeDiff < mtimeThreshold;
|
|
347
|
-
}).sort((a, b) => a.mtimeMs - b.mtimeMs);
|
|
348
|
-
if (mtimeMatched.length > 0) {
|
|
349
|
-
return mtimeMatched[0].path;
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
const fallbackFiles = validFiles.length > 0 ? validFiles : fileStats;
|
|
353
|
-
fallbackFiles.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
354
|
-
return fallbackFiles[0]?.path || "";
|
|
355
|
-
} catch {
|
|
356
|
-
return "";
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
async function getNewMessages(sessionPath, fromLine) {
|
|
360
|
-
if (!sessionPath) return { messages: [], totalLines: 0 };
|
|
361
|
-
try {
|
|
362
|
-
const content = await readFile(sessionPath, "utf-8");
|
|
363
|
-
const lines = content.trim().split("\n");
|
|
364
|
-
const totalLines = lines.length;
|
|
365
|
-
const newLines = lines.slice(fromLine);
|
|
366
|
-
const messages = [];
|
|
367
|
-
for (const line of newLines) {
|
|
368
|
-
try {
|
|
369
|
-
const entry = JSON.parse(line);
|
|
370
|
-
if (entry.type === "user" && entry.message?.content) {
|
|
371
|
-
const text = extractUserText(entry.message.content);
|
|
372
|
-
if (text && !isMetaMessage(text)) {
|
|
373
|
-
messages.push({
|
|
374
|
-
role: "user",
|
|
375
|
-
content: truncate(text, 100),
|
|
376
|
-
timestamp: entry.timestamp || ""
|
|
377
|
-
});
|
|
378
|
-
}
|
|
379
|
-
} else if (entry.type === "assistant" && entry.message?.content) {
|
|
380
|
-
const text = extractAssistantText(entry.message.content);
|
|
381
|
-
if (text) {
|
|
382
|
-
messages.push({
|
|
383
|
-
role: "assistant",
|
|
384
|
-
content: truncate(text, 100),
|
|
385
|
-
timestamp: entry.timestamp || ""
|
|
386
|
-
});
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
} catch {
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
return { messages, totalLines };
|
|
393
|
-
} catch {
|
|
394
|
-
return { messages: [], totalLines: 0 };
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
async function getAllMessages(sessionPath) {
|
|
398
|
-
if (!sessionPath) return [];
|
|
399
|
-
try {
|
|
400
|
-
const content = await readFile(sessionPath, "utf-8");
|
|
401
|
-
const lines = content.trim().split("\n");
|
|
402
|
-
const messages = [];
|
|
403
|
-
for (const line of lines) {
|
|
404
|
-
try {
|
|
405
|
-
const entry = JSON.parse(line);
|
|
406
|
-
if (entry.type === "user" && entry.message?.content) {
|
|
407
|
-
const text = extractUserText(entry.message.content);
|
|
408
|
-
if (text && !isMetaMessage(text)) {
|
|
409
|
-
messages.push({
|
|
410
|
-
role: "user",
|
|
411
|
-
content: truncate(text, 100),
|
|
412
|
-
timestamp: entry.timestamp || ""
|
|
413
|
-
});
|
|
414
|
-
}
|
|
415
|
-
} else if (entry.type === "assistant" && entry.message?.content) {
|
|
416
|
-
const text = extractAssistantText(entry.message.content);
|
|
417
|
-
if (text) {
|
|
418
|
-
messages.push({
|
|
419
|
-
role: "assistant",
|
|
420
|
-
content: truncate(text, 100),
|
|
421
|
-
timestamp: entry.timestamp || ""
|
|
422
|
-
});
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
} catch {
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
return messages;
|
|
429
|
-
} catch {
|
|
430
|
-
return [];
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
function extractUserText(content) {
|
|
434
|
-
if (typeof content === "string") {
|
|
435
|
-
return content;
|
|
436
|
-
}
|
|
437
|
-
return "";
|
|
438
|
-
}
|
|
439
|
-
function extractAssistantText(content) {
|
|
440
|
-
if (!Array.isArray(content)) return "";
|
|
441
|
-
const textParts = content.filter((item) => item.type === "text" && item.text).map((item) => item.text);
|
|
442
|
-
return textParts.join("\n");
|
|
443
|
-
}
|
|
444
|
-
function isMetaMessage(text) {
|
|
445
|
-
return text.startsWith("<local-command") || text.startsWith("<command-name>") || text.startsWith("<command-message>");
|
|
446
|
-
}
|
|
447
|
-
function truncate(text, maxLen) {
|
|
448
|
-
const clean = text.replace(/\s+/g, " ").trim();
|
|
449
|
-
if (clean.length <= maxLen) return clean;
|
|
450
|
-
return `${clean.slice(0, maxLen - 3)}...`;
|
|
451
|
-
}
|
|
452
|
-
|
|
453
315
|
// src/hooks/useSessionWatcher.ts
|
|
454
|
-
import { useEffect, useRef } from "react";
|
|
455
316
|
import chokidar from "chokidar";
|
|
317
|
+
import { useEffect, useRef } from "react";
|
|
456
318
|
function useSessionWatcher(sessionPaths, onFileChange) {
|
|
457
319
|
const watcherRef = useRef(null);
|
|
458
320
|
const debounceTimersRef = useRef(/* @__PURE__ */ new Map());
|
|
@@ -470,6 +332,7 @@ function useSessionWatcher(sessionPaths, onFileChange) {
|
|
|
470
332
|
}
|
|
471
333
|
});
|
|
472
334
|
watcher.on("change", (path) => {
|
|
335
|
+
clearSessionCache(path);
|
|
473
336
|
const existingTimer = debounceTimersRef.current.get(path);
|
|
474
337
|
if (existingTimer) {
|
|
475
338
|
clearTimeout(existingTimer);
|
|
@@ -495,6 +358,7 @@ function useSessionWatcher(sessionPaths, onFileChange) {
|
|
|
495
358
|
}
|
|
496
359
|
|
|
497
360
|
// src/hooks/useProcesses.ts
|
|
361
|
+
var sessionLimit = pLimit(5);
|
|
498
362
|
function sortProcesses(processes, sortField) {
|
|
499
363
|
const sorted = [...processes];
|
|
500
364
|
switch (sortField) {
|
|
@@ -523,22 +387,20 @@ function useProcesses(interval2) {
|
|
|
523
387
|
try {
|
|
524
388
|
const procs = await getClaudeProcesses();
|
|
525
389
|
const enriched = await Promise.all(
|
|
526
|
-
procs.map(
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
};
|
|
541
|
-
})
|
|
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
|
+
)
|
|
542
404
|
);
|
|
543
405
|
setRawProcesses(enriched);
|
|
544
406
|
setError(null);
|
|
@@ -750,6 +612,7 @@ var cli = meow(
|
|
|
750
612
|
Options
|
|
751
613
|
-l, --list \u975E\u4EA4\u4E92\u6A21\u5F0F\uFF0C\u4EC5\u5217\u51FA\u8FDB\u7A0B
|
|
752
614
|
-j, --json JSON \u683C\u5F0F\u8F93\u51FA\uFF08\u914D\u5408 --list\uFF09
|
|
615
|
+
-d, --debug \u663E\u793A\u4F1A\u8BDD\u5339\u914D\u8C03\u8BD5\u4FE1\u606F
|
|
753
616
|
-i, --interval \u5237\u65B0\u95F4\u9694\u79D2\u6570\uFF08\u9ED8\u8BA4 2\uFF09
|
|
754
617
|
-v, --version \u663E\u793A\u7248\u672C
|
|
755
618
|
-h, --help \u663E\u793A\u5E2E\u52A9
|
|
@@ -758,6 +621,7 @@ var cli = meow(
|
|
|
758
621
|
$ claude-ps \u542F\u52A8 TUI
|
|
759
622
|
$ claude-ps --list \u5217\u51FA\u8FDB\u7A0B\u540E\u9000\u51FA
|
|
760
623
|
$ claude-ps --json JSON \u683C\u5F0F\u8F93\u51FA
|
|
624
|
+
$ claude-ps --debug \u663E\u793A\u4F1A\u8BDD\u5339\u914D\u8BE6\u60C5
|
|
761
625
|
$ claude-ps -i 5 \u8BBE\u7F6E\u5237\u65B0\u95F4\u9694\u4E3A 5 \u79D2
|
|
762
626
|
`,
|
|
763
627
|
{
|
|
@@ -778,6 +642,11 @@ var cli = meow(
|
|
|
778
642
|
shortFlag: "j",
|
|
779
643
|
default: false
|
|
780
644
|
},
|
|
645
|
+
debug: {
|
|
646
|
+
type: "boolean",
|
|
647
|
+
shortFlag: "d",
|
|
648
|
+
default: false
|
|
649
|
+
},
|
|
781
650
|
interval: {
|
|
782
651
|
type: "number",
|
|
783
652
|
shortFlag: "i",
|
|
@@ -791,11 +660,15 @@ var cli = meow(
|
|
|
791
660
|
}
|
|
792
661
|
}
|
|
793
662
|
);
|
|
794
|
-
var { list, json, interval } = cli.flags;
|
|
795
|
-
if (list || json) {
|
|
796
|
-
const { getClaudeProcesses: getClaudeProcesses2 } = await import("./process-
|
|
663
|
+
var { list, json, debug, interval } = cli.flags;
|
|
664
|
+
if (list || json || debug) {
|
|
665
|
+
const { getClaudeProcesses: getClaudeProcesses2 } = await import("./process-XJGJPUAG.js");
|
|
797
666
|
const processes = await getClaudeProcesses2();
|
|
798
|
-
if (
|
|
667
|
+
if (debug) {
|
|
668
|
+
const { debugSessionMatching } = await import("./session-FTYMYOIH.js");
|
|
669
|
+
const debugInfo = await debugSessionMatching(processes);
|
|
670
|
+
console.log(debugInfo);
|
|
671
|
+
} else if (json) {
|
|
799
672
|
console.log(JSON.stringify(processes, null, 2));
|
|
800
673
|
} else {
|
|
801
674
|
console.log("PID TTY CWD");
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
clearAllSessionCache,
|
|
4
|
+
clearSessionCache,
|
|
5
|
+
debugSessionMatching,
|
|
6
|
+
getAllMessages,
|
|
7
|
+
getNewMessages,
|
|
8
|
+
getRecentMessages,
|
|
9
|
+
getSessionPath
|
|
10
|
+
} from "./chunk-JYWGOPVM.js";
|
|
11
|
+
export {
|
|
12
|
+
clearAllSessionCache,
|
|
13
|
+
clearSessionCache,
|
|
14
|
+
debugSessionMatching,
|
|
15
|
+
getAllMessages,
|
|
16
|
+
getNewMessages,
|
|
17
|
+
getRecentMessages,
|
|
18
|
+
getSessionPath
|
|
19
|
+
};
|
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
|
-
};
|