claude-ps 0.1.0
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/LICENSE +21 -0
- package/README.md +31 -0
- package/dist/chunk-TPK4XYR3.js +106 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +609 -0
- package/dist/process-2SZ7S64S.js +9 -0
- package/package.json +40 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ziheng
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# claude-ps
|
|
2
|
+
|
|
3
|
+
TUI application for viewing and managing Claude Code processes.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g claude-ps
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
claude-ps # 启动交互界面
|
|
15
|
+
claude-ps --list # 列出进程(非交互)
|
|
16
|
+
claude-ps --json # JSON 格式输出
|
|
17
|
+
claude-ps --interval 5 # 设置刷新间隔(秒)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Keybindings
|
|
21
|
+
|
|
22
|
+
- `↑/↓` or `j/k` - 移动选择
|
|
23
|
+
- `s` - 切换排序方式
|
|
24
|
+
- `r` - 刷新
|
|
25
|
+
- `d` - 终止进程 (SIGTERM)
|
|
26
|
+
- `D` - 强制终止 (SIGKILL)
|
|
27
|
+
- `q/ESC` - 退出
|
|
28
|
+
|
|
29
|
+
## License
|
|
30
|
+
|
|
31
|
+
MIT
|
|
@@ -0,0 +1,106 @@
|
|
|
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 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
|
+
};
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
getClaudeProcesses,
|
|
4
|
+
killProcess
|
|
5
|
+
} from "./chunk-TPK4XYR3.js";
|
|
6
|
+
|
|
7
|
+
// src/index.tsx
|
|
8
|
+
import { withFullScreen } from "fullscreen-ink";
|
|
9
|
+
import meow from "meow";
|
|
10
|
+
|
|
11
|
+
// src/App.tsx
|
|
12
|
+
import { Box as Box5, Text as Text5, useApp, useInput, useStdout } from "ink";
|
|
13
|
+
|
|
14
|
+
// src/components/DetailPanel.tsx
|
|
15
|
+
import { Box, Text } from "ink";
|
|
16
|
+
|
|
17
|
+
// src/constants/theme.ts
|
|
18
|
+
var USAGE_THRESHOLD = {
|
|
19
|
+
LOW: 30,
|
|
20
|
+
HIGH: 70
|
|
21
|
+
};
|
|
22
|
+
var COLORS = {
|
|
23
|
+
/** 使用率颜色 */
|
|
24
|
+
usage: {
|
|
25
|
+
low: "green",
|
|
26
|
+
medium: "yellow",
|
|
27
|
+
high: "red"
|
|
28
|
+
},
|
|
29
|
+
/** 标签颜色 */
|
|
30
|
+
label: "gray",
|
|
31
|
+
/** 数值颜色 */
|
|
32
|
+
value: "cyan",
|
|
33
|
+
/** 当前终端进程 */
|
|
34
|
+
current: "green",
|
|
35
|
+
/** 孤儿进程 */
|
|
36
|
+
orphan: "red",
|
|
37
|
+
/** 选中项 */
|
|
38
|
+
selected: "yellow",
|
|
39
|
+
/** 标题 */
|
|
40
|
+
title: "cyan"
|
|
41
|
+
};
|
|
42
|
+
var COLUMN_WIDTH = {
|
|
43
|
+
prefix: 2,
|
|
44
|
+
pid: 6,
|
|
45
|
+
cpu: 6,
|
|
46
|
+
memory: 6,
|
|
47
|
+
elapsed: 9
|
|
48
|
+
};
|
|
49
|
+
var ESTIMATED_TOTAL_MEMORY_MB = 16 * 1024;
|
|
50
|
+
function getUsageColor(percent) {
|
|
51
|
+
if (percent < USAGE_THRESHOLD.LOW) return COLORS.usage.low;
|
|
52
|
+
if (percent < USAGE_THRESHOLD.HIGH) return COLORS.usage.medium;
|
|
53
|
+
return COLORS.usage.high;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/utils/format.ts
|
|
57
|
+
function formatMemory(memPercent) {
|
|
58
|
+
const usedMB = memPercent / 100 * ESTIMATED_TOTAL_MEMORY_MB;
|
|
59
|
+
if (usedMB < 1024) return `${Math.round(usedMB)}MB`;
|
|
60
|
+
return `${(usedMB / 1024).toFixed(1)}GB`;
|
|
61
|
+
}
|
|
62
|
+
function formatElapsed(elapsed) {
|
|
63
|
+
if (!elapsed) return "\u672A\u77E5";
|
|
64
|
+
const parts = elapsed.split(/[-:]/);
|
|
65
|
+
if (parts.length === 2) {
|
|
66
|
+
return `${parts[0]}\u5206${parts[1]}\u79D2`;
|
|
67
|
+
}
|
|
68
|
+
if (parts.length === 3) {
|
|
69
|
+
return `${parts[0]}\u65F6${parts[1]}\u5206`;
|
|
70
|
+
}
|
|
71
|
+
if (parts.length === 4) {
|
|
72
|
+
return `${parts[0]}\u5929${parts[1]}\u65F6`;
|
|
73
|
+
}
|
|
74
|
+
return elapsed;
|
|
75
|
+
}
|
|
76
|
+
function shortenPath(inputPath, maxLen = 25) {
|
|
77
|
+
let path = inputPath;
|
|
78
|
+
const home = process.env.HOME || "";
|
|
79
|
+
if (home && path.startsWith(home)) {
|
|
80
|
+
path = `~${path.slice(home.length)}`;
|
|
81
|
+
}
|
|
82
|
+
if (path.length <= maxLen) return path;
|
|
83
|
+
const half = Math.floor((maxLen - 3) / 2);
|
|
84
|
+
return `${path.slice(0, half)}...${path.slice(-half)}`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/components/DetailPanel.tsx
|
|
88
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
89
|
+
function DetailPanel({ process: proc }) {
|
|
90
|
+
if (!proc) {
|
|
91
|
+
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", paddingLeft: 1, children: /* @__PURE__ */ jsx(Text, { color: COLORS.label, children: "\u9009\u62E9\u4E00\u4E2A\u8FDB\u7A0B\u67E5\u770B\u8BE6\u60C5" }) });
|
|
92
|
+
}
|
|
93
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingLeft: 1, children: [
|
|
94
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
95
|
+
/* @__PURE__ */ jsx(Text, { color: COLORS.title, children: "CPU: " }),
|
|
96
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
97
|
+
proc.cpu.toFixed(1),
|
|
98
|
+
"%"
|
|
99
|
+
] }),
|
|
100
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
101
|
+
/* @__PURE__ */ jsx(Text, { color: COLORS.title, children: "\u5185\u5B58: " }),
|
|
102
|
+
/* @__PURE__ */ jsx(Text, { children: formatMemory(proc.memory) }),
|
|
103
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
104
|
+
/* @__PURE__ */ jsx(Text, { color: COLORS.title, children: "\u65F6\u957F: " }),
|
|
105
|
+
/* @__PURE__ */ jsx(Text, { children: formatElapsed(proc.elapsed) })
|
|
106
|
+
] }),
|
|
107
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
108
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
109
|
+
/* @__PURE__ */ jsx(Text, { color: COLORS.title, children: "Session:" }),
|
|
110
|
+
/* @__PURE__ */ jsx(Text, { color: COLORS.label, wrap: "truncate", children: proc.sessionPath || "\u65E0\u4F1A\u8BDD\u6587\u4EF6" })
|
|
111
|
+
] }),
|
|
112
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
113
|
+
/* @__PURE__ */ jsx(Text, { color: COLORS.title, bold: true, children: "\u2500 \u6700\u8FD1\u5BF9\u8BDD \u2500" }),
|
|
114
|
+
proc.messages.length === 0 ? /* @__PURE__ */ jsx(Text, { color: COLORS.label, children: "\u65E0\u5BF9\u8BDD\u8BB0\u5F55" }) : proc.messages.map((msg) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
|
|
115
|
+
/* @__PURE__ */ jsxs(Text, { color: msg.role === "user" ? COLORS.current : "blue", children: [
|
|
116
|
+
"[",
|
|
117
|
+
msg.role === "user" ? "User" : "Claude",
|
|
118
|
+
"]",
|
|
119
|
+
" "
|
|
120
|
+
] }),
|
|
121
|
+
/* @__PURE__ */ jsx(Text, { wrap: "truncate", children: msg.content })
|
|
122
|
+
] }, msg.timestamp))
|
|
123
|
+
] });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// src/components/HelpBar.tsx
|
|
127
|
+
import { Box as Box2, Text as Text2 } from "ink";
|
|
128
|
+
import React from "react";
|
|
129
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
130
|
+
var hints = [
|
|
131
|
+
{ key: "\u2191/\u2193", desc: "\u79FB\u52A8" },
|
|
132
|
+
{ key: "d", desc: "\u7EC8\u6B62" },
|
|
133
|
+
{ key: "D", desc: "\u5F3A\u6740" },
|
|
134
|
+
{ key: "s", desc: "\u6392\u5E8F" },
|
|
135
|
+
{ key: "r", desc: "\u5237\u65B0" },
|
|
136
|
+
{ key: "q", desc: "\u9000\u51FA" }
|
|
137
|
+
];
|
|
138
|
+
var sortFieldLabels = {
|
|
139
|
+
cpu: "CPU",
|
|
140
|
+
memory: "\u5185\u5B58",
|
|
141
|
+
elapsed: "\u65F6\u957F",
|
|
142
|
+
default: "PID"
|
|
143
|
+
};
|
|
144
|
+
function HelpBar({ processCount, interval: interval2, sortField }) {
|
|
145
|
+
return /* @__PURE__ */ jsxs2(Box2, { justifyContent: "space-between", children: [
|
|
146
|
+
/* @__PURE__ */ jsx2(Box2, { children: hints.map((hint, i) => /* @__PURE__ */ jsxs2(React.Fragment, { children: [
|
|
147
|
+
/* @__PURE__ */ jsx2(Text2, { color: COLORS.selected, children: hint.key }),
|
|
148
|
+
/* @__PURE__ */ jsxs2(Text2, { color: COLORS.label, children: [
|
|
149
|
+
" ",
|
|
150
|
+
hint.desc
|
|
151
|
+
] }),
|
|
152
|
+
i < hints.length - 1 && /* @__PURE__ */ jsx2(Text2, { children: " " })
|
|
153
|
+
] }, hint.key)) }),
|
|
154
|
+
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
155
|
+
/* @__PURE__ */ jsxs2(Text2, { color: COLORS.value, children: [
|
|
156
|
+
processCount,
|
|
157
|
+
" \u8FDB\u7A0B"
|
|
158
|
+
] }),
|
|
159
|
+
/* @__PURE__ */ jsx2(Text2, { color: COLORS.label, children: " | " }),
|
|
160
|
+
/* @__PURE__ */ jsx2(Text2, { color: COLORS.label, children: "\u6392\u5E8F: " }),
|
|
161
|
+
/* @__PURE__ */ jsx2(Text2, { color: COLORS.value, children: sortFieldLabels[sortField] }),
|
|
162
|
+
/* @__PURE__ */ jsx2(Text2, { color: COLORS.label, children: " | " }),
|
|
163
|
+
/* @__PURE__ */ jsxs2(Text2, { color: COLORS.label, children: [
|
|
164
|
+
"\u5237\u65B0: ",
|
|
165
|
+
interval2,
|
|
166
|
+
"s"
|
|
167
|
+
] })
|
|
168
|
+
] })
|
|
169
|
+
] });
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// src/components/ProcessList.tsx
|
|
173
|
+
import { Box as Box3, Text as Text3 } from "ink";
|
|
174
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
175
|
+
function formatPercent(value, width) {
|
|
176
|
+
const str = `${value.toFixed(1)}%`;
|
|
177
|
+
return str.padStart(width);
|
|
178
|
+
}
|
|
179
|
+
function ProcessList({
|
|
180
|
+
processes,
|
|
181
|
+
selectedIndex,
|
|
182
|
+
loading
|
|
183
|
+
}) {
|
|
184
|
+
if (loading) {
|
|
185
|
+
return /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", children: /* @__PURE__ */ jsx3(Text3, { color: "gray", children: "\u52A0\u8F7D\u4E2D..." }) });
|
|
186
|
+
}
|
|
187
|
+
if (processes.length === 0) {
|
|
188
|
+
return /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", children: /* @__PURE__ */ jsx3(Text3, { color: "gray", children: "\u65E0\u8FD0\u884C\u4E2D\u7684 Claude \u8FDB\u7A0B" }) });
|
|
189
|
+
}
|
|
190
|
+
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
191
|
+
/* @__PURE__ */ jsxs3(Box3, { children: [
|
|
192
|
+
/* @__PURE__ */ jsx3(Text3, { bold: true, color: COLORS.title, children: " " }),
|
|
193
|
+
/* @__PURE__ */ jsx3(Box3, { width: COLUMN_WIDTH.pid, children: /* @__PURE__ */ jsx3(Text3, { bold: true, color: COLORS.title, children: "PID" }) }),
|
|
194
|
+
/* @__PURE__ */ jsx3(Box3, { width: COLUMN_WIDTH.cpu, children: /* @__PURE__ */ jsx3(Text3, { bold: true, color: COLORS.title, children: "CPU" }) }),
|
|
195
|
+
/* @__PURE__ */ jsx3(Box3, { width: COLUMN_WIDTH.memory, children: /* @__PURE__ */ jsx3(Text3, { bold: true, color: COLORS.title, children: "MEM" }) }),
|
|
196
|
+
/* @__PURE__ */ jsx3(Box3, { width: COLUMN_WIDTH.elapsed, children: /* @__PURE__ */ jsx3(Text3, { bold: true, color: COLORS.title, children: "\u65F6\u957F" }) }),
|
|
197
|
+
/* @__PURE__ */ jsx3(Text3, { bold: true, color: COLORS.title, children: "\u5DE5\u4F5C\u76EE\u5F55" })
|
|
198
|
+
] }),
|
|
199
|
+
/* @__PURE__ */ jsx3(Text3, { color: "gray", children: "\u2500".repeat(60) }),
|
|
200
|
+
processes.map((proc, index) => {
|
|
201
|
+
const isSelected = index === selectedIndex;
|
|
202
|
+
const prefix = isSelected ? "\u25B6 " : " ";
|
|
203
|
+
let cwdColor = void 0;
|
|
204
|
+
if (proc.isOrphan) cwdColor = COLORS.orphan;
|
|
205
|
+
else if (proc.isCurrent) cwdColor = COLORS.current;
|
|
206
|
+
return /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
207
|
+
/* @__PURE__ */ jsx3(Text3, { color: isSelected ? COLORS.selected : void 0, children: prefix }),
|
|
208
|
+
/* @__PURE__ */ jsx3(Box3, { width: COLUMN_WIDTH.pid, children: /* @__PURE__ */ jsx3(Text3, { color: isSelected ? COLORS.selected : void 0, children: proc.pid }) }),
|
|
209
|
+
/* @__PURE__ */ jsx3(Box3, { width: COLUMN_WIDTH.cpu, children: /* @__PURE__ */ jsx3(Text3, { color: getUsageColor(proc.cpu), children: formatPercent(proc.cpu, COLUMN_WIDTH.cpu - 1) }) }),
|
|
210
|
+
/* @__PURE__ */ jsx3(Box3, { width: COLUMN_WIDTH.memory, children: /* @__PURE__ */ jsx3(Text3, { color: getUsageColor(proc.memory), children: formatPercent(proc.memory, COLUMN_WIDTH.memory - 1) }) }),
|
|
211
|
+
/* @__PURE__ */ jsx3(Box3, { width: COLUMN_WIDTH.elapsed, children: /* @__PURE__ */ jsx3(Text3, { color: isSelected ? COLORS.selected : "gray", children: formatElapsed(proc.elapsed).slice(0, COLUMN_WIDTH.elapsed - 1) }) }),
|
|
212
|
+
/* @__PURE__ */ jsxs3(Text3, { color: cwdColor, children: [
|
|
213
|
+
shortenPath(proc.cwd, 30),
|
|
214
|
+
proc.isCurrent ? " \u25CF" : ""
|
|
215
|
+
] })
|
|
216
|
+
] }, proc.pid);
|
|
217
|
+
})
|
|
218
|
+
] });
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// src/components/StatusBar.tsx
|
|
222
|
+
import { Box as Box4, Text as Text4 } from "ink";
|
|
223
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
224
|
+
function calculateStats(processes) {
|
|
225
|
+
const totalCpu = processes.reduce((sum, p) => sum + p.cpu, 0);
|
|
226
|
+
const totalMemoryPercent = processes.reduce((sum, p) => sum + p.memory, 0);
|
|
227
|
+
const totalMemoryMB = totalMemoryPercent / 100 * ESTIMATED_TOTAL_MEMORY_MB;
|
|
228
|
+
const orphanCount = processes.filter((p) => p.isOrphan).length;
|
|
229
|
+
const currentCount = processes.filter((p) => p.isCurrent).length;
|
|
230
|
+
return {
|
|
231
|
+
count: processes.length,
|
|
232
|
+
totalCpu,
|
|
233
|
+
totalMemory: totalMemoryMB < 1024 ? `${Math.round(totalMemoryMB)}MB` : `${(totalMemoryMB / 1024).toFixed(1)}GB`,
|
|
234
|
+
orphanCount,
|
|
235
|
+
currentCount
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
function StatusBar({ processes }) {
|
|
239
|
+
const stats = calculateStats(processes);
|
|
240
|
+
return /* @__PURE__ */ jsxs4(Box4, { children: [
|
|
241
|
+
/* @__PURE__ */ jsx4(Text4, { color: COLORS.label, children: "\u8FDB\u7A0B: " }),
|
|
242
|
+
/* @__PURE__ */ jsx4(Text4, { color: COLORS.value, children: stats.count }),
|
|
243
|
+
/* @__PURE__ */ jsx4(Text4, { color: COLORS.label, children: " | CPU: " }),
|
|
244
|
+
/* @__PURE__ */ jsxs4(Text4, { color: COLORS.value, children: [
|
|
245
|
+
stats.totalCpu.toFixed(1),
|
|
246
|
+
"%"
|
|
247
|
+
] }),
|
|
248
|
+
/* @__PURE__ */ jsx4(Text4, { color: COLORS.label, children: " | \u5185\u5B58: " }),
|
|
249
|
+
/* @__PURE__ */ jsx4(Text4, { color: COLORS.value, children: stats.totalMemory }),
|
|
250
|
+
/* @__PURE__ */ jsx4(Text4, { color: COLORS.label, children: " | \u5B64\u513F: " }),
|
|
251
|
+
/* @__PURE__ */ jsx4(Text4, { color: stats.orphanCount > 0 ? COLORS.orphan : COLORS.value, children: stats.orphanCount }),
|
|
252
|
+
/* @__PURE__ */ jsx4(Text4, { color: COLORS.label, children: " | \u5F53\u524D: " }),
|
|
253
|
+
/* @__PURE__ */ jsx4(Text4, { color: stats.currentCount > 0 ? COLORS.current : COLORS.value, children: stats.currentCount })
|
|
254
|
+
] });
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// src/hooks/useProcesses.ts
|
|
258
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
259
|
+
|
|
260
|
+
// src/utils/session.ts
|
|
261
|
+
import { readFile, readdir } from "fs/promises";
|
|
262
|
+
import { homedir } from "os";
|
|
263
|
+
import { join } from "path";
|
|
264
|
+
function cwdToProjectDir(cwd) {
|
|
265
|
+
return cwd.replace(/\//g, "-").replace(/^-/, "-");
|
|
266
|
+
}
|
|
267
|
+
async function getSessionPath(cwd, startTime) {
|
|
268
|
+
const projectDir = cwdToProjectDir(cwd);
|
|
269
|
+
const sessionsDir = join(homedir(), ".claude", "projects", projectDir);
|
|
270
|
+
try {
|
|
271
|
+
const files = await readdir(sessionsDir);
|
|
272
|
+
const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
|
|
273
|
+
if (jsonlFiles.length === 0) return "";
|
|
274
|
+
const { stat } = await import("fs/promises");
|
|
275
|
+
const fileStats = await Promise.all(
|
|
276
|
+
jsonlFiles.map(async (f) => {
|
|
277
|
+
const path = join(sessionsDir, f);
|
|
278
|
+
const s = await stat(path);
|
|
279
|
+
return {
|
|
280
|
+
path,
|
|
281
|
+
birthtime: s.birthtime,
|
|
282
|
+
mtimeMs: s.mtimeMs,
|
|
283
|
+
size: s.size
|
|
284
|
+
};
|
|
285
|
+
})
|
|
286
|
+
);
|
|
287
|
+
const minSize = 1024;
|
|
288
|
+
const validFiles = fileStats.filter((f) => f.size >= minSize);
|
|
289
|
+
if (startTime && validFiles.length > 0) {
|
|
290
|
+
const startMs = startTime.getTime();
|
|
291
|
+
const birthtimeThreshold = 1e4;
|
|
292
|
+
const birthtimeMatched = validFiles.filter(
|
|
293
|
+
(f) => Math.abs(f.birthtime.getTime() - startMs) < birthtimeThreshold
|
|
294
|
+
).sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
295
|
+
if (birthtimeMatched.length > 0) {
|
|
296
|
+
return birthtimeMatched[0].path;
|
|
297
|
+
}
|
|
298
|
+
const mtimeThreshold = 6e4;
|
|
299
|
+
const mtimeMatched = validFiles.filter((f) => {
|
|
300
|
+
const mtimeDiff = f.mtimeMs - startMs;
|
|
301
|
+
return mtimeDiff >= 0 && mtimeDiff < mtimeThreshold;
|
|
302
|
+
}).sort((a, b) => a.mtimeMs - b.mtimeMs);
|
|
303
|
+
if (mtimeMatched.length > 0) {
|
|
304
|
+
return mtimeMatched[0].path;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
const fallbackFiles = validFiles.length > 0 ? validFiles : fileStats;
|
|
308
|
+
fallbackFiles.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
309
|
+
return fallbackFiles[0]?.path || "";
|
|
310
|
+
} catch {
|
|
311
|
+
return "";
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
async function getRecentMessages(sessionPath, limit = 5) {
|
|
315
|
+
if (!sessionPath) return [];
|
|
316
|
+
try {
|
|
317
|
+
const content = await readFile(sessionPath, "utf-8");
|
|
318
|
+
const lines = content.trim().split("\n");
|
|
319
|
+
const messages = [];
|
|
320
|
+
for (const line of lines) {
|
|
321
|
+
try {
|
|
322
|
+
const entry = JSON.parse(line);
|
|
323
|
+
if (entry.type === "user" && entry.message?.content) {
|
|
324
|
+
const text = extractUserText(entry.message.content);
|
|
325
|
+
if (text && !isMetaMessage(text)) {
|
|
326
|
+
messages.push({
|
|
327
|
+
role: "user",
|
|
328
|
+
content: truncate(text, 100),
|
|
329
|
+
timestamp: entry.timestamp || ""
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
} else if (entry.type === "assistant" && entry.message?.content) {
|
|
333
|
+
const text = extractAssistantText(entry.message.content);
|
|
334
|
+
if (text) {
|
|
335
|
+
messages.push({
|
|
336
|
+
role: "assistant",
|
|
337
|
+
content: truncate(text, 100),
|
|
338
|
+
timestamp: entry.timestamp || ""
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
} catch {
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return messages.slice(-limit);
|
|
346
|
+
} catch {
|
|
347
|
+
return [];
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
function extractUserText(content) {
|
|
351
|
+
if (typeof content === "string") {
|
|
352
|
+
return content;
|
|
353
|
+
}
|
|
354
|
+
return "";
|
|
355
|
+
}
|
|
356
|
+
function extractAssistantText(content) {
|
|
357
|
+
if (!Array.isArray(content)) return "";
|
|
358
|
+
const textParts = content.filter((item) => item.type === "text" && item.text).map((item) => item.text);
|
|
359
|
+
return textParts.join("\n");
|
|
360
|
+
}
|
|
361
|
+
function isMetaMessage(text) {
|
|
362
|
+
return text.startsWith("<local-command") || text.startsWith("<command-name>") || text.startsWith("<command-message>");
|
|
363
|
+
}
|
|
364
|
+
function truncate(text, maxLen) {
|
|
365
|
+
const clean = text.replace(/\s+/g, " ").trim();
|
|
366
|
+
if (clean.length <= maxLen) return clean;
|
|
367
|
+
return `${clean.slice(0, maxLen - 3)}...`;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// src/hooks/useProcesses.ts
|
|
371
|
+
function sortProcesses(processes, sortField) {
|
|
372
|
+
const sorted = [...processes];
|
|
373
|
+
switch (sortField) {
|
|
374
|
+
case "cpu":
|
|
375
|
+
sorted.sort((a, b) => b.cpu - a.cpu);
|
|
376
|
+
break;
|
|
377
|
+
case "memory":
|
|
378
|
+
sorted.sort((a, b) => b.memory - a.memory);
|
|
379
|
+
break;
|
|
380
|
+
case "elapsed":
|
|
381
|
+
sorted.sort((a, b) => a.startTime.getTime() - b.startTime.getTime());
|
|
382
|
+
break;
|
|
383
|
+
default:
|
|
384
|
+
sorted.sort((a, b) => a.pid - b.pid);
|
|
385
|
+
}
|
|
386
|
+
return sorted;
|
|
387
|
+
}
|
|
388
|
+
function useProcesses(interval2) {
|
|
389
|
+
const [rawProcesses, setRawProcesses] = useState([]);
|
|
390
|
+
const [loading, setLoading] = useState(true);
|
|
391
|
+
const [error, setError] = useState(null);
|
|
392
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
393
|
+
const [sortField, setSortField] = useState("default");
|
|
394
|
+
const refresh = useCallback(async () => {
|
|
395
|
+
try {
|
|
396
|
+
const procs = await getClaudeProcesses();
|
|
397
|
+
const enriched = await Promise.all(
|
|
398
|
+
procs.map(async (proc) => {
|
|
399
|
+
const sessionPath = await getSessionPath(proc.cwd, proc.startTime);
|
|
400
|
+
const messages = await getRecentMessages(sessionPath);
|
|
401
|
+
return {
|
|
402
|
+
...proc,
|
|
403
|
+
sessionPath,
|
|
404
|
+
messages
|
|
405
|
+
};
|
|
406
|
+
})
|
|
407
|
+
);
|
|
408
|
+
setRawProcesses(enriched);
|
|
409
|
+
setError(null);
|
|
410
|
+
setSelectedIndex(
|
|
411
|
+
(prev) => Math.min(prev, Math.max(0, enriched.length - 1))
|
|
412
|
+
);
|
|
413
|
+
} catch (e) {
|
|
414
|
+
setError(e instanceof Error ? e.message : "\u672A\u77E5\u9519\u8BEF");
|
|
415
|
+
} finally {
|
|
416
|
+
setLoading(false);
|
|
417
|
+
}
|
|
418
|
+
}, []);
|
|
419
|
+
useEffect(() => {
|
|
420
|
+
refresh();
|
|
421
|
+
const timer = setInterval(refresh, interval2 * 1e3);
|
|
422
|
+
return () => clearInterval(timer);
|
|
423
|
+
}, [refresh, interval2]);
|
|
424
|
+
const processes = useMemo(
|
|
425
|
+
() => sortProcesses(rawProcesses, sortField),
|
|
426
|
+
[rawProcesses, sortField]
|
|
427
|
+
);
|
|
428
|
+
const selectNext = useCallback(() => {
|
|
429
|
+
setSelectedIndex((prev) => Math.min(prev + 1, processes.length - 1));
|
|
430
|
+
}, [processes.length]);
|
|
431
|
+
const selectPrev = useCallback(() => {
|
|
432
|
+
setSelectedIndex((prev) => Math.max(prev - 1, 0));
|
|
433
|
+
}, []);
|
|
434
|
+
const cycleSortField = useCallback(() => {
|
|
435
|
+
setSortField((prev) => {
|
|
436
|
+
const order = ["cpu", "memory", "elapsed", "default"];
|
|
437
|
+
const idx = order.indexOf(prev);
|
|
438
|
+
return order[(idx + 1) % order.length];
|
|
439
|
+
});
|
|
440
|
+
}, []);
|
|
441
|
+
const killSelected = useCallback(
|
|
442
|
+
async (force = false) => {
|
|
443
|
+
const proc = processes[selectedIndex];
|
|
444
|
+
if (!proc) return false;
|
|
445
|
+
const success = await killProcess(proc.pid, force);
|
|
446
|
+
if (success) {
|
|
447
|
+
await refresh();
|
|
448
|
+
}
|
|
449
|
+
return success;
|
|
450
|
+
},
|
|
451
|
+
[processes, selectedIndex, refresh]
|
|
452
|
+
);
|
|
453
|
+
const selectedProcess = processes[selectedIndex] || null;
|
|
454
|
+
return {
|
|
455
|
+
processes,
|
|
456
|
+
loading,
|
|
457
|
+
error,
|
|
458
|
+
selectedIndex,
|
|
459
|
+
selectedProcess,
|
|
460
|
+
sortField,
|
|
461
|
+
refresh,
|
|
462
|
+
selectNext,
|
|
463
|
+
selectPrev,
|
|
464
|
+
cycleSortField,
|
|
465
|
+
killSelected
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// src/App.tsx
|
|
470
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
471
|
+
function App({ interval: interval2 }) {
|
|
472
|
+
const { exit } = useApp();
|
|
473
|
+
const { stdout } = useStdout();
|
|
474
|
+
const termWidth = stdout?.columns || 80;
|
|
475
|
+
const termHeight = stdout?.rows || 24;
|
|
476
|
+
const {
|
|
477
|
+
processes,
|
|
478
|
+
loading,
|
|
479
|
+
error,
|
|
480
|
+
selectedIndex,
|
|
481
|
+
selectedProcess,
|
|
482
|
+
sortField,
|
|
483
|
+
refresh,
|
|
484
|
+
selectNext,
|
|
485
|
+
selectPrev,
|
|
486
|
+
cycleSortField,
|
|
487
|
+
killSelected
|
|
488
|
+
} = useProcesses(interval2);
|
|
489
|
+
useInput((input, key) => {
|
|
490
|
+
if (input === "q" || key.escape) {
|
|
491
|
+
exit();
|
|
492
|
+
} else if (key.downArrow || input === "j") {
|
|
493
|
+
selectNext();
|
|
494
|
+
} else if (key.upArrow || input === "k") {
|
|
495
|
+
selectPrev();
|
|
496
|
+
} else if (input === "r") {
|
|
497
|
+
refresh();
|
|
498
|
+
} else if (input === "s") {
|
|
499
|
+
cycleSortField();
|
|
500
|
+
} else if (input === "d") {
|
|
501
|
+
killSelected(false);
|
|
502
|
+
} else if (input === "D") {
|
|
503
|
+
killSelected(true);
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
const leftWidth = Math.floor(termWidth * 0.45);
|
|
507
|
+
const rightWidth = termWidth - leftWidth - 1;
|
|
508
|
+
const contentHeight = termHeight - 4;
|
|
509
|
+
if (error) {
|
|
510
|
+
return /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", width: termWidth, height: termHeight, children: /* @__PURE__ */ jsxs5(Text5, { color: COLORS.orphan, children: [
|
|
511
|
+
"\u9519\u8BEF: ",
|
|
512
|
+
error
|
|
513
|
+
] }) });
|
|
514
|
+
}
|
|
515
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", width: termWidth, height: termHeight, children: [
|
|
516
|
+
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
517
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, color: COLORS.title, children: "claude-ps" }),
|
|
518
|
+
/* @__PURE__ */ jsx5(Text5, { color: COLORS.label, children: " - Claude Code \u8FDB\u7A0B\u7BA1\u7406\u5668" })
|
|
519
|
+
] }),
|
|
520
|
+
/* @__PURE__ */ jsx5(StatusBar, { processes }),
|
|
521
|
+
/* @__PURE__ */ jsxs5(Box5, { height: contentHeight, children: [
|
|
522
|
+
/* @__PURE__ */ jsx5(Box5, { width: leftWidth, flexDirection: "column", height: contentHeight, children: /* @__PURE__ */ jsx5(
|
|
523
|
+
ProcessList,
|
|
524
|
+
{
|
|
525
|
+
processes,
|
|
526
|
+
selectedIndex,
|
|
527
|
+
loading
|
|
528
|
+
}
|
|
529
|
+
) }),
|
|
530
|
+
/* @__PURE__ */ jsx5(Box5, { width: 1, flexDirection: "column", height: contentHeight, children: /* @__PURE__ */ jsx5(Text5, { color: COLORS.label, children: "\u2502\n".repeat(contentHeight).trim() }) }),
|
|
531
|
+
/* @__PURE__ */ jsx5(Box5, { width: rightWidth, flexDirection: "column", height: contentHeight, children: /* @__PURE__ */ jsx5(DetailPanel, { process: selectedProcess }) })
|
|
532
|
+
] }),
|
|
533
|
+
/* @__PURE__ */ jsx5(
|
|
534
|
+
Box5,
|
|
535
|
+
{
|
|
536
|
+
borderStyle: "single",
|
|
537
|
+
borderTop: true,
|
|
538
|
+
borderBottom: false,
|
|
539
|
+
borderLeft: false,
|
|
540
|
+
borderRight: false,
|
|
541
|
+
children: /* @__PURE__ */ jsx5(
|
|
542
|
+
HelpBar,
|
|
543
|
+
{
|
|
544
|
+
processCount: processes.length,
|
|
545
|
+
interval: interval2,
|
|
546
|
+
sortField
|
|
547
|
+
}
|
|
548
|
+
)
|
|
549
|
+
}
|
|
550
|
+
)
|
|
551
|
+
] });
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// src/index.tsx
|
|
555
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
556
|
+
var cli = meow(
|
|
557
|
+
`
|
|
558
|
+
Usage
|
|
559
|
+
$ claude-ps [options]
|
|
560
|
+
|
|
561
|
+
Options
|
|
562
|
+
-l, --list \u975E\u4EA4\u4E92\u6A21\u5F0F\uFF0C\u4EC5\u5217\u51FA\u8FDB\u7A0B
|
|
563
|
+
-j, --json JSON \u683C\u5F0F\u8F93\u51FA\uFF08\u914D\u5408 --list\uFF09
|
|
564
|
+
-i, --interval \u5237\u65B0\u95F4\u9694\u79D2\u6570\uFF08\u9ED8\u8BA4 2\uFF09
|
|
565
|
+
-v, --version \u663E\u793A\u7248\u672C
|
|
566
|
+
-h, --help \u663E\u793A\u5E2E\u52A9
|
|
567
|
+
|
|
568
|
+
Examples
|
|
569
|
+
$ claude-ps \u542F\u52A8 TUI
|
|
570
|
+
$ claude-ps --list \u5217\u51FA\u8FDB\u7A0B\u540E\u9000\u51FA
|
|
571
|
+
$ claude-ps --json JSON \u683C\u5F0F\u8F93\u51FA
|
|
572
|
+
$ claude-ps -i 5 \u8BBE\u7F6E\u5237\u65B0\u95F4\u9694\u4E3A 5 \u79D2
|
|
573
|
+
`,
|
|
574
|
+
{
|
|
575
|
+
importMeta: import.meta,
|
|
576
|
+
flags: {
|
|
577
|
+
list: {
|
|
578
|
+
type: "boolean",
|
|
579
|
+
shortFlag: "l",
|
|
580
|
+
default: false
|
|
581
|
+
},
|
|
582
|
+
json: {
|
|
583
|
+
type: "boolean",
|
|
584
|
+
shortFlag: "j",
|
|
585
|
+
default: false
|
|
586
|
+
},
|
|
587
|
+
interval: {
|
|
588
|
+
type: "number",
|
|
589
|
+
shortFlag: "i",
|
|
590
|
+
default: 2
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
);
|
|
595
|
+
var { list, json, interval } = cli.flags;
|
|
596
|
+
if (list || json) {
|
|
597
|
+
const { getClaudeProcesses: getClaudeProcesses2 } = await import("./process-2SZ7S64S.js");
|
|
598
|
+
const processes = await getClaudeProcesses2();
|
|
599
|
+
if (json) {
|
|
600
|
+
console.log(JSON.stringify(processes, null, 2));
|
|
601
|
+
} else {
|
|
602
|
+
console.log("PID TTY CWD");
|
|
603
|
+
for (const proc of processes) {
|
|
604
|
+
console.log(`${proc.pid} ${proc.tty} ${proc.cwd}`);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
process.exit(0);
|
|
608
|
+
}
|
|
609
|
+
withFullScreen(/* @__PURE__ */ jsx6(App, { interval })).start();
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-ps",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TUI application for viewing and managing Claude Code processes",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"claude-ps": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": ["dist"],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"dev": "tsx src/index.tsx",
|
|
13
|
+
"build": "tsup",
|
|
14
|
+
"lint": "biome check .",
|
|
15
|
+
"lint:fix": "biome check --write .",
|
|
16
|
+
"format": "biome format --write .",
|
|
17
|
+
"typecheck": "tsc --noEmit",
|
|
18
|
+
"all": "pnpm format && pnpm typecheck && pnpm lint"
|
|
19
|
+
},
|
|
20
|
+
"keywords": ["claude", "tui", "terminal", "process-manager"],
|
|
21
|
+
"author": "ziheng",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"chalk": "^5.3.0",
|
|
25
|
+
"fullscreen-ink": "^0.1.0",
|
|
26
|
+
"ink": "^5.0.1",
|
|
27
|
+
"meow": "^13.2.0",
|
|
28
|
+
"react": "^18.3.1"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@biomejs/biome": "^1.9.0",
|
|
32
|
+
"@types/react": "^18.3.0",
|
|
33
|
+
"tsup": "^8.3.0",
|
|
34
|
+
"tsx": "^4.19.0",
|
|
35
|
+
"typescript": "^5.6.0"
|
|
36
|
+
},
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=18"
|
|
39
|
+
}
|
|
40
|
+
}
|