memtop 1.0.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/README.md ADDED
@@ -0,0 +1,26 @@
1
+ # memtop
2
+
3
+ 实时监控 macOS 系统内存使用情况的命令行工具。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ npm install -g memtop
9
+ ```
10
+
11
+ ## 使用
12
+
13
+ ```bash
14
+ memtop
15
+ ```
16
+
17
+ ## 功能特性
18
+
19
+ - 🚀 实时监控系统内存使用情况
20
+ - 📊 清晰的命令行界面展示
21
+ - 💻 专为 macOS 系统优化
22
+ - ⚡ 轻量级、高效
23
+
24
+ ## 许可证
25
+
26
+ MIT
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/index.js ADDED
@@ -0,0 +1,207 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { execSync } from "child_process";
5
+ function parseMemoryString(memStr) {
6
+ const match = memStr.match(/^([\d.]+)([KMG])?$/i);
7
+ if (!match) return 0;
8
+ const value = parseFloat(match[1]);
9
+ const unit = (match[2] || "K").toUpperCase();
10
+ switch (unit) {
11
+ case "G":
12
+ return value * 1024;
13
+ // GB -> MB
14
+ case "M":
15
+ return value;
16
+ // MB
17
+ case "K":
18
+ return value / 1024;
19
+ // KB -> MB
20
+ default:
21
+ return value / 1024;
22
+ }
23
+ }
24
+ var FRIENDLY_NAMES = {
25
+ // Safari WebKit 进程
26
+ "com.apple.WebKit.WebContent": "Safari (\u7F51\u9875\u5185\u5BB9)",
27
+ "com.apple.WebKit.Networking": "Safari (\u7F51\u7EDC)",
28
+ "com.apple.WebKit.GPU": "Safari (GPU)",
29
+ // 系统服务
30
+ WindowServer: "\u7A97\u53E3\u670D\u52A1\u5668",
31
+ WindowManager: "\u7A97\u53E3\u7BA1\u7406\u5668",
32
+ kernel_task: "\u7CFB\u7EDF\u5185\u6838",
33
+ launchd: "\u7CFB\u7EDF\u542F\u52A8\u670D\u52A1",
34
+ mds_stores: "Spotlight \u7D22\u5F15",
35
+ mds: "Spotlight",
36
+ mdworker: "Spotlight \u5DE5\u4F5C\u8FDB\u7A0B",
37
+ Finder: "\u8BBF\u8FBE",
38
+ Dock: "\u7A0B\u5E8F\u575E",
39
+ SystemUIServer: "\u7CFB\u7EDF UI \u670D\u52A1",
40
+ loginwindow: "\u767B\u5F55\u7A97\u53E3",
41
+ coreservicesd: "\u6838\u5FC3\u670D\u52A1",
42
+ cfprefsd: "\u504F\u597D\u8BBE\u7F6E\u670D\u52A1",
43
+ distnoted: "\u901A\u77E5\u670D\u52A1",
44
+ // 第三方应用别名
45
+ wechatwebdevtools: "\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177",
46
+ wechatdevtools: "\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177",
47
+ "wechatwebdevtools Helper": "\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177",
48
+ "wechatwebdevtools Helper (Renderer)": "\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177",
49
+ "wechatwebdevtools Helper (GPU)": "\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177"
50
+ };
51
+ function extractAppName(cmdPath) {
52
+ const appMatch = cmdPath.match(/\/([^/]+)\.app\//);
53
+ if (appMatch) {
54
+ const appName = appMatch[1];
55
+ if (FRIENDLY_NAMES[appName]) {
56
+ return FRIENDLY_NAMES[appName];
57
+ }
58
+ return appName;
59
+ }
60
+ const xpcMatch = cmdPath.match(/\/([^/]+)\.xpc\//);
61
+ if (xpcMatch) {
62
+ const xpcName = xpcMatch[1];
63
+ if (FRIENDLY_NAMES[xpcName]) {
64
+ return FRIENDLY_NAMES[xpcName];
65
+ }
66
+ return xpcName;
67
+ }
68
+ const parts = cmdPath.trim().split("/");
69
+ const lastPart = parts[parts.length - 1];
70
+ const processName = lastPart.split(" ")[0] || "unknown";
71
+ if (FRIENDLY_NAMES[processName]) {
72
+ return FRIENDLY_NAMES[processName];
73
+ }
74
+ return processName;
75
+ }
76
+ function getPidNameMap() {
77
+ const output = execSync("ps -eo pid=,comm=", { encoding: "utf-8" });
78
+ const lines = output.trim().split("\n");
79
+ const pidMap = /* @__PURE__ */ new Map();
80
+ for (const line of lines) {
81
+ const match = line.match(/^\s*(\d+)\s+(.+)$/);
82
+ if (match) {
83
+ const pid = parseInt(match[1], 10);
84
+ const comm = match[2].trim();
85
+ const name = extractAppName(comm);
86
+ pidMap.set(pid, name);
87
+ }
88
+ }
89
+ return pidMap;
90
+ }
91
+ function getProcessList() {
92
+ const pidNameMap = getPidNameMap();
93
+ const output = execSync("top -l 1 -o mem -n 200 -stats pid,mem", {
94
+ encoding: "utf-8"
95
+ });
96
+ const lines = output.trim().split("\n");
97
+ const processes = [];
98
+ let dataStartIndex = 0;
99
+ for (let i = 0; i < lines.length; i++) {
100
+ if (lines[i].trim().startsWith("PID")) {
101
+ dataStartIndex = i + 1;
102
+ break;
103
+ }
104
+ }
105
+ for (let i = dataStartIndex; i < lines.length; i++) {
106
+ const line = lines[i].trim();
107
+ if (!line) continue;
108
+ const match = line.match(/^(\d+)\s+([\d.]+[KMG]?)$/i);
109
+ if (match) {
110
+ const pid = parseInt(match[1], 10);
111
+ const memoryMB = parseMemoryString(match[2]);
112
+ const name = pidNameMap.get(pid) || `PID-${pid}`;
113
+ if (memoryMB >= 10) {
114
+ processes.push({ pid, name, memoryMB });
115
+ }
116
+ }
117
+ }
118
+ return processes;
119
+ }
120
+ function aggregateByApp(processes) {
121
+ const memoryMap = /* @__PURE__ */ new Map();
122
+ for (const proc of processes) {
123
+ const current = memoryMap.get(proc.name) || 0;
124
+ memoryMap.set(proc.name, current + proc.memoryMB);
125
+ }
126
+ const apps = [];
127
+ for (const [name, memoryMB] of memoryMap) {
128
+ apps.push({ name, memoryMB });
129
+ }
130
+ apps.sort((a, b) => b.memoryMB - a.memoryMB);
131
+ return apps.slice(0, 10);
132
+ }
133
+ function formatMemory(memoryMB) {
134
+ if (memoryMB >= 1024) {
135
+ return (memoryMB / 1024).toFixed(2) + " GB";
136
+ }
137
+ return memoryMB.toFixed(0) + " MB";
138
+ }
139
+ function getDisplayWidth(str) {
140
+ let width = 0;
141
+ for (const char of str) {
142
+ const code = char.codePointAt(0) || 0;
143
+ if (code >= 19968 && code <= 40959 || // CJK 基本
144
+ code >= 13312 && code <= 19903 || // CJK 扩展 A
145
+ code >= 65280 && code <= 65519 || // 全角字符
146
+ code >= 12288 && code <= 12351) {
147
+ width += 2;
148
+ } else {
149
+ width += 1;
150
+ }
151
+ }
152
+ return width;
153
+ }
154
+ function padEndByWidth(str, targetWidth) {
155
+ const currentWidth = getDisplayWidth(str);
156
+ if (currentWidth >= targetWidth) {
157
+ return str;
158
+ }
159
+ return str + " ".repeat(targetWidth - currentWidth);
160
+ }
161
+ function truncateByWidth(str, maxWidth) {
162
+ let width = 0;
163
+ let result = "";
164
+ for (const char of str) {
165
+ const code = char.codePointAt(0) || 0;
166
+ const charWidth = code >= 19968 && code <= 40959 || code >= 13312 && code <= 19903 || code >= 65280 && code <= 65519 || code >= 12288 && code <= 12351 ? 2 : 1;
167
+ if (width + charWidth > maxWidth - 2) {
168
+ return result + "..";
169
+ }
170
+ result += char;
171
+ width += charWidth;
172
+ }
173
+ return result;
174
+ }
175
+ function formatTable(apps) {
176
+ const maxNameWidth = Math.max(...apps.map((app) => getDisplayWidth(app.name)), 8);
177
+ const nameColWidth = Math.min(maxNameWidth, 40);
178
+ const memoryColWidth = 12;
179
+ const totalWidth = 6 + nameColWidth + memoryColWidth;
180
+ const header = "Rank " + padEndByWidth("App Name", nameColWidth) + "Memory".padStart(memoryColWidth);
181
+ const separator = "\u2500".repeat(totalWidth);
182
+ const rows = apps.map((app, index) => {
183
+ const rank = String(index + 1).padEnd(6);
184
+ const displayWidth = getDisplayWidth(app.name);
185
+ let name;
186
+ if (displayWidth > nameColWidth) {
187
+ name = truncateByWidth(app.name, nameColWidth);
188
+ name = padEndByWidth(name, nameColWidth);
189
+ } else {
190
+ name = padEndByWidth(app.name, nameColWidth);
191
+ }
192
+ const memory = formatMemory(app.memoryMB).padStart(memoryColWidth);
193
+ return `${rank}${name}${memory}`;
194
+ });
195
+ return [header, separator, ...rows].join("\n");
196
+ }
197
+ function main() {
198
+ try {
199
+ const processes = getProcessList();
200
+ const apps = aggregateByApp(processes);
201
+ console.log(formatTable(apps));
202
+ } catch (error) {
203
+ console.error("\u83B7\u53D6\u5185\u5B58\u4FE1\u606F\u5931\u8D25:", error.message);
204
+ process.exit(1);
205
+ }
206
+ }
207
+ main();
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "memtop",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "description": "实时监控 macOS 系统内存使用情况的命令行工具",
6
+ "main": "./dist/index.js",
7
+ "bin": {
8
+ "memtop": "./dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsup src/index.ts --format esm --dts",
12
+ "dev": "node --loader ts-node/esm src/index.ts",
13
+ "prepublishOnly": "npm run build"
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "keywords": [
19
+ "memory",
20
+ "monitor",
21
+ "macos",
22
+ "cli",
23
+ "memtop",
24
+ "system",
25
+ "performance"
26
+ ],
27
+ "author": "poxiao",
28
+ "license": "MIT",
29
+ "devDependencies": {
30
+ "@types/node": "^25.0.3",
31
+ "ts-node": "^10.9.2",
32
+ "tsup": "^8.5.1",
33
+ "typescript": "^5.9.3"
34
+ }
35
+ }