claude-ps 0.2.2 → 0.2.3
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.
|
@@ -56,7 +56,7 @@ async function getClaudeProcesses() {
|
|
|
56
56
|
let stdout;
|
|
57
57
|
try {
|
|
58
58
|
const result = await execAsync(
|
|
59
|
-
`ps -eo pid,tty,command | grep -E '^\\s*[0-9]+\\s+\\S+\\s+claude\\s
|
|
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
60
|
);
|
|
61
61
|
stdout = result.stdout;
|
|
62
62
|
} catch {
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
getClaudeProcesses,
|
|
4
4
|
killProcess
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-EZHIVMX4.js";
|
|
6
6
|
|
|
7
7
|
// src/index.tsx
|
|
8
8
|
import { withFullScreen } from "fullscreen-ink";
|
|
@@ -193,7 +193,7 @@ function formatElapsed(elapsed) {
|
|
|
193
193
|
|
|
194
194
|
// src/components/ui/DetailPanel.tsx
|
|
195
195
|
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
196
|
-
function DetailPanel({ process: proc }) {
|
|
196
|
+
function DetailPanel({ process: proc, isLive = true }) {
|
|
197
197
|
if (!proc) {
|
|
198
198
|
return /* @__PURE__ */ jsx4(EmptyPrompt, { message: "\u9009\u62E9\u4E00\u4E2A\u8FDB\u7A0B\u67E5\u770B\u8BE6\u60C5" });
|
|
199
199
|
}
|
|
@@ -208,7 +208,10 @@ function DetailPanel({ process: proc }) {
|
|
|
208
208
|
/* @__PURE__ */ jsx4(Text4, { bold: true, color: "cyan", children: "Session:" }),
|
|
209
209
|
/* @__PURE__ */ jsx4(Text4, { color: COLORS.label, wrap: "truncate", children: proc.sessionPath || "\u65E0\u4F1A\u8BDD\u6587\u4EF6" })
|
|
210
210
|
] }),
|
|
211
|
-
/* @__PURE__ */
|
|
211
|
+
/* @__PURE__ */ jsxs4(Box4, { paddingBottom: 1, flexDirection: "row", gap: 1, children: [
|
|
212
|
+
/* @__PURE__ */ jsx4(Text4, { bold: true, color: "cyan", children: "\u6700\u8FD1\u5BF9\u8BDD" }),
|
|
213
|
+
isLive && proc.sessionPath && /* @__PURE__ */ jsx4(Text4, { color: "green", children: "\u25CF \u5B9E\u65F6" })
|
|
214
|
+
] }),
|
|
212
215
|
/* @__PURE__ */ jsx4(Box4, { flexDirection: "column", children: proc.messages.length === 0 ? /* @__PURE__ */ jsx4(Text4, { color: COLORS.label, children: "\u65E0\u5BF9\u8BDD\u8BB0\u5F55" }) : proc.messages.map((msg) => /* @__PURE__ */ jsxs4(Box4, { flexDirection: "row", children: [
|
|
213
216
|
/* @__PURE__ */ jsxs4(Text4, { color: msg.role === "user" ? COLORS.current : "blue", children: [
|
|
214
217
|
msg.role === "user" ? "[User]" : "[Claude]",
|
|
@@ -297,7 +300,7 @@ function ProcessList({
|
|
|
297
300
|
}
|
|
298
301
|
|
|
299
302
|
// src/hooks/useProcesses.ts
|
|
300
|
-
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
303
|
+
import { useCallback, useEffect as useEffect2, useMemo, useRef as useRef2, useState } from "react";
|
|
301
304
|
|
|
302
305
|
// src/utils/session.ts
|
|
303
306
|
import { readFile, readdir } from "fs/promises";
|
|
@@ -353,7 +356,45 @@ async function getSessionPath(cwd, startTime) {
|
|
|
353
356
|
return "";
|
|
354
357
|
}
|
|
355
358
|
}
|
|
356
|
-
async function
|
|
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) {
|
|
357
398
|
if (!sessionPath) return [];
|
|
358
399
|
try {
|
|
359
400
|
const content = await readFile(sessionPath, "utf-8");
|
|
@@ -384,7 +425,7 @@ async function getRecentMessages(sessionPath, limit = 5) {
|
|
|
384
425
|
} catch {
|
|
385
426
|
}
|
|
386
427
|
}
|
|
387
|
-
return messages
|
|
428
|
+
return messages;
|
|
388
429
|
} catch {
|
|
389
430
|
return [];
|
|
390
431
|
}
|
|
@@ -409,6 +450,50 @@ function truncate(text, maxLen) {
|
|
|
409
450
|
return `${clean.slice(0, maxLen - 3)}...`;
|
|
410
451
|
}
|
|
411
452
|
|
|
453
|
+
// src/hooks/useSessionWatcher.ts
|
|
454
|
+
import { useEffect, useRef } from "react";
|
|
455
|
+
import chokidar from "chokidar";
|
|
456
|
+
function useSessionWatcher(sessionPaths, onFileChange) {
|
|
457
|
+
const watcherRef = useRef(null);
|
|
458
|
+
const debounceTimersRef = useRef(/* @__PURE__ */ new Map());
|
|
459
|
+
useEffect(() => {
|
|
460
|
+
const validPaths = sessionPaths.filter((p) => p && p.length > 0);
|
|
461
|
+
if (validPaths.length === 0) return;
|
|
462
|
+
const watcher = chokidar.watch(validPaths, {
|
|
463
|
+
persistent: true,
|
|
464
|
+
ignoreInitial: true,
|
|
465
|
+
// 忽略初始添加事件
|
|
466
|
+
awaitWriteFinish: {
|
|
467
|
+
stabilityThreshold: 50,
|
|
468
|
+
// 文件稳定 50ms 后触发
|
|
469
|
+
pollInterval: 25
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
watcher.on("change", (path) => {
|
|
473
|
+
const existingTimer = debounceTimersRef.current.get(path);
|
|
474
|
+
if (existingTimer) {
|
|
475
|
+
clearTimeout(existingTimer);
|
|
476
|
+
}
|
|
477
|
+
const timer = setTimeout(() => {
|
|
478
|
+
onFileChange(path);
|
|
479
|
+
debounceTimersRef.current.delete(path);
|
|
480
|
+
}, 100);
|
|
481
|
+
debounceTimersRef.current.set(path, timer);
|
|
482
|
+
});
|
|
483
|
+
watcherRef.current = watcher;
|
|
484
|
+
return () => {
|
|
485
|
+
for (const timer of debounceTimersRef.current.values()) {
|
|
486
|
+
clearTimeout(timer);
|
|
487
|
+
}
|
|
488
|
+
debounceTimersRef.current.clear();
|
|
489
|
+
if (watcherRef.current) {
|
|
490
|
+
watcherRef.current.close();
|
|
491
|
+
watcherRef.current = null;
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
}, [sessionPaths, onFileChange]);
|
|
495
|
+
}
|
|
496
|
+
|
|
412
497
|
// src/hooks/useProcesses.ts
|
|
413
498
|
function sortProcesses(processes, sortField) {
|
|
414
499
|
const sorted = [...processes];
|
|
@@ -433,13 +518,21 @@ function useProcesses(interval2) {
|
|
|
433
518
|
const [error, setError] = useState(null);
|
|
434
519
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
435
520
|
const [sortField, setSortField] = useState("default");
|
|
521
|
+
const sessionLineNumbers = useRef2(/* @__PURE__ */ new Map());
|
|
436
522
|
const refresh = useCallback(async () => {
|
|
437
523
|
try {
|
|
438
524
|
const procs = await getClaudeProcesses();
|
|
439
525
|
const enriched = await Promise.all(
|
|
440
526
|
procs.map(async (proc) => {
|
|
441
527
|
const sessionPath = await getSessionPath(proc.cwd, proc.startTime);
|
|
442
|
-
const messages = await
|
|
528
|
+
const messages = await getAllMessages(sessionPath);
|
|
529
|
+
if (sessionPath && !sessionLineNumbers.current.has(sessionPath)) {
|
|
530
|
+
const content = await import("fs/promises").then(
|
|
531
|
+
(m) => m.readFile(sessionPath, "utf-8").catch(() => "")
|
|
532
|
+
);
|
|
533
|
+
const lines = content.trim().split("\n");
|
|
534
|
+
sessionLineNumbers.current.set(sessionPath, lines.length);
|
|
535
|
+
}
|
|
443
536
|
return {
|
|
444
537
|
...proc,
|
|
445
538
|
sessionPath,
|
|
@@ -458,7 +551,37 @@ function useProcesses(interval2) {
|
|
|
458
551
|
setLoading(false);
|
|
459
552
|
}
|
|
460
553
|
}, []);
|
|
461
|
-
|
|
554
|
+
const updateSessionMessages = useCallback(async (sessionPath) => {
|
|
555
|
+
if (!sessionPath) return;
|
|
556
|
+
try {
|
|
557
|
+
const fromLine = sessionLineNumbers.current.get(sessionPath) || 0;
|
|
558
|
+
const { messages: newMessages, totalLines } = await getNewMessages(
|
|
559
|
+
sessionPath,
|
|
560
|
+
fromLine
|
|
561
|
+
);
|
|
562
|
+
if (newMessages.length > 0) {
|
|
563
|
+
setRawProcesses(
|
|
564
|
+
(prev) => prev.map((proc) => {
|
|
565
|
+
if (proc.sessionPath === sessionPath) {
|
|
566
|
+
return {
|
|
567
|
+
...proc,
|
|
568
|
+
messages: [...proc.messages, ...newMessages]
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
return proc;
|
|
572
|
+
})
|
|
573
|
+
);
|
|
574
|
+
sessionLineNumbers.current.set(sessionPath, totalLines);
|
|
575
|
+
}
|
|
576
|
+
} catch {
|
|
577
|
+
}
|
|
578
|
+
}, []);
|
|
579
|
+
const sessionPaths = useMemo(
|
|
580
|
+
() => rawProcesses.map((p) => p.sessionPath).filter((p) => p),
|
|
581
|
+
[rawProcesses]
|
|
582
|
+
);
|
|
583
|
+
useSessionWatcher(sessionPaths, updateSessionMessages);
|
|
584
|
+
useEffect2(() => {
|
|
462
585
|
refresh();
|
|
463
586
|
const timer = setInterval(refresh, interval2 * 1e3);
|
|
464
587
|
return () => clearInterval(timer);
|
|
@@ -590,7 +713,7 @@ function App({ interval: interval2 }) {
|
|
|
590
713
|
height: contentHeight,
|
|
591
714
|
paddingX: 4,
|
|
592
715
|
paddingY: 2,
|
|
593
|
-
children: /* @__PURE__ */ jsx7(DetailPanel, { process: selectedProcess })
|
|
716
|
+
children: /* @__PURE__ */ jsx7(DetailPanel, { process: selectedProcess, isLive: true })
|
|
594
717
|
}
|
|
595
718
|
)
|
|
596
719
|
] }),
|
|
@@ -670,7 +793,7 @@ var cli = meow(
|
|
|
670
793
|
);
|
|
671
794
|
var { list, json, interval } = cli.flags;
|
|
672
795
|
if (list || json) {
|
|
673
|
-
const { getClaudeProcesses: getClaudeProcesses2 } = await import("./process-
|
|
796
|
+
const { getClaudeProcesses: getClaudeProcesses2 } = await import("./process-AUO5UVTV.js");
|
|
674
797
|
const processes = await getClaudeProcesses2();
|
|
675
798
|
if (json) {
|
|
676
799
|
console.log(JSON.stringify(processes, null, 2));
|
package/package.json
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-ps",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "TUI application for viewing and managing Claude Code processes",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
|
8
8
|
"claude-ps": "dist/index.js"
|
|
9
9
|
},
|
|
10
|
-
"files": [
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
11
13
|
"scripts": {
|
|
12
14
|
"dev": "tsx src/index.tsx",
|
|
13
15
|
"build": "tsup",
|
|
@@ -17,11 +19,17 @@
|
|
|
17
19
|
"typecheck": "tsc --noEmit",
|
|
18
20
|
"all": "pnpm format && pnpm typecheck && pnpm lint"
|
|
19
21
|
},
|
|
20
|
-
"keywords": [
|
|
22
|
+
"keywords": [
|
|
23
|
+
"claude",
|
|
24
|
+
"tui",
|
|
25
|
+
"terminal",
|
|
26
|
+
"process-manager"
|
|
27
|
+
],
|
|
21
28
|
"author": "ziheng",
|
|
22
29
|
"license": "MIT",
|
|
23
30
|
"dependencies": {
|
|
24
31
|
"chalk": "^5.3.0",
|
|
32
|
+
"chokidar": "^4.0.3",
|
|
25
33
|
"fullscreen-ink": "^0.1.0",
|
|
26
34
|
"ink": "^5.0.1",
|
|
27
35
|
"meow": "^13.2.0",
|