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.
@@ -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(async (f) => {
26
- const path = join(sessionsDir, f);
27
- const s = await stat(path);
28
- return {
29
- path,
30
- birthtime: s.birthtime,
31
- mtimeMs: s.mtimeMs,
32
- size: s.size
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((a, b) => Math.abs(a.mtimeMs - startMs) - Math.abs(b.mtimeMs - startMs));
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 readFile(sessionPath, "utf-8");
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 readFile(sessionPath, "utf-8");
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 readFile(sessionPath, "utf-8");
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
- return messages;
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(async (f) => {
255
- const path = join(sessionsDir, f);
256
- const s = await stat(path);
257
- return {
258
- name: f,
259
- path,
260
- birthtime: s.birthtime,
261
- mtime: new Date(s.mtimeMs),
262
- size: s.size
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-EZHIVMX4.js";
5
+ } from "./chunk-KF2FBZRQ.js";
6
6
  import {
7
+ clearSessionCache,
7
8
  getAllMessages,
8
9
  getNewMessages,
9
10
  getSessionPath
10
- } from "./chunk-RZG4BATR.js";
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(async (proc) => {
387
- const sessionPath = await getSessionPath(proc.cwd, proc.startTime);
388
- const messages = await getAllMessages(sessionPath);
389
- if (sessionPath && !sessionLineNumbers.current.has(sessionPath)) {
390
- const content = await import("fs/promises").then(
391
- (m) => m.readFile(sessionPath, "utf-8").catch(() => "")
392
- );
393
- const lines = content.trim().split("\n");
394
- sessionLineNumbers.current.set(sessionPath, lines.length);
395
- }
396
- return {
397
- ...proc,
398
- sessionPath,
399
- messages
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-AUO5UVTV.js");
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-TGVDWUTY.js");
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) {
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  getClaudeProcesses,
4
4
  killProcess
5
- } from "./chunk-EZHIVMX4.js";
5
+ } from "./chunk-KF2FBZRQ.js";
6
6
  export {
7
7
  getClaudeProcesses,
8
8
  killProcess
@@ -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-RZG4BATR.js";
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.5",
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": {
@@ -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
- };