tuimon 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.
Files changed (50) hide show
  1. package/CLAUDE.md +80 -0
  2. package/README.md +153 -0
  3. package/client/tuimon-client.js +44 -0
  4. package/dist/browser.d.ts +7 -0
  5. package/dist/browser.d.ts.map +1 -0
  6. package/dist/browser.js +57 -0
  7. package/dist/browser.js.map +1 -0
  8. package/dist/cli.d.ts +3 -0
  9. package/dist/cli.d.ts.map +1 -0
  10. package/dist/cli.js +87 -0
  11. package/dist/cli.js.map +1 -0
  12. package/dist/detect.d.ts +8 -0
  13. package/dist/detect.d.ts.map +1 -0
  14. package/dist/detect.js +141 -0
  15. package/dist/detect.js.map +1 -0
  16. package/dist/encoder.d.ts +4 -0
  17. package/dist/encoder.d.ts.map +1 -0
  18. package/dist/encoder.js +115 -0
  19. package/dist/encoder.js.map +1 -0
  20. package/dist/fkeybar.d.ts +5 -0
  21. package/dist/fkeybar.d.ts.map +1 -0
  22. package/dist/fkeybar.js +89 -0
  23. package/dist/fkeybar.js.map +1 -0
  24. package/dist/index.d.ts +7 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.js +166 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/keyhandler.d.ts +5 -0
  29. package/dist/keyhandler.d.ts.map +1 -0
  30. package/dist/keyhandler.js +20 -0
  31. package/dist/keyhandler.js.map +1 -0
  32. package/dist/router.d.ts +10 -0
  33. package/dist/router.d.ts.map +1 -0
  34. package/dist/router.js +154 -0
  35. package/dist/router.js.map +1 -0
  36. package/dist/server.d.ts +5 -0
  37. package/dist/server.d.ts.map +1 -0
  38. package/dist/server.js +142 -0
  39. package/dist/server.js.map +1 -0
  40. package/dist/types.d.ts +108 -0
  41. package/dist/types.d.ts.map +1 -0
  42. package/dist/types.js +3 -0
  43. package/dist/types.js.map +1 -0
  44. package/package.json +44 -0
  45. package/templates/internal/confirm-quit.html +94 -0
  46. package/templates/starter/pages/cpu-detail.html +176 -0
  47. package/templates/starter/pages/memory-detail.html +166 -0
  48. package/templates/starter/pages/overview.html +186 -0
  49. package/templates/starter/tuimon.config.ts +87 -0
  50. package/tsconfig.json +23 -0
@@ -0,0 +1,166 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>TuiMon — Memory Detail</title>
7
+ <script src="https://cdn.jsdelivr.net/npm/chart.js@4"></script>
8
+ <style>
9
+ * { margin: 0; padding: 0; box-sizing: border-box; }
10
+ body {
11
+ background: #0d1117;
12
+ color: #c9d1d9;
13
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, monospace;
14
+ padding: 12px;
15
+ overflow: hidden;
16
+ height: 100vh;
17
+ display: grid;
18
+ grid-template-columns: 1fr 1fr;
19
+ grid-template-rows: auto 1fr auto;
20
+ gap: 12px;
21
+ }
22
+ .header {
23
+ grid-column: 1 / -1;
24
+ display: flex;
25
+ justify-content: space-between;
26
+ align-items: center;
27
+ padding-bottom: 8px;
28
+ border-bottom: 1px solid #21262d;
29
+ }
30
+ .header h1 { font-size: 16px; color: #a371f7; font-weight: 600; }
31
+ .header .back { font-size: 12px; color: #484f58; }
32
+ .panel {
33
+ background: #161b22;
34
+ border: 1px solid #21262d;
35
+ border-radius: 6px;
36
+ padding: 16px;
37
+ }
38
+ .panel h3 {
39
+ font-size: 12px;
40
+ color: #8b949e;
41
+ text-transform: uppercase;
42
+ letter-spacing: 1px;
43
+ margin-bottom: 12px;
44
+ }
45
+ .chart-wrap { height: calc(100% - 32px); }
46
+ .stats-bar {
47
+ grid-column: 1 / -1;
48
+ display: grid;
49
+ grid-template-columns: repeat(4, 1fr);
50
+ gap: 10px;
51
+ }
52
+ .mini-stat {
53
+ background: #161b22;
54
+ border: 1px solid #21262d;
55
+ border-radius: 6px;
56
+ padding: 10px 14px;
57
+ text-align: center;
58
+ }
59
+ .mini-stat .label { font-size: 10px; color: #8b949e; text-transform: uppercase; letter-spacing: 1px; }
60
+ .mini-stat .value { font-size: 22px; font-weight: 700; color: #a371f7; margin-top: 2px; }
61
+ .mini-stat .unit { font-size: 11px; color: #484f58; }
62
+ </style>
63
+ </head>
64
+ <body>
65
+ <div class="header">
66
+ <h1>Memory Detail</h1>
67
+ <span class="back">ESC to go back</span>
68
+ </div>
69
+
70
+ <div class="panel">
71
+ <h3>Memory Breakdown</h3>
72
+ <div class="chart-wrap"><canvas id="doughnut"></canvas></div>
73
+ </div>
74
+
75
+ <div class="panel">
76
+ <h3>Memory Timeline</h3>
77
+ <div class="chart-wrap"><canvas id="timeline"></canvas></div>
78
+ </div>
79
+
80
+ <div class="stats-bar">
81
+ <div class="mini-stat">
82
+ <div class="label">Used</div>
83
+ <div class="value" id="usedMb">--</div>
84
+ <div class="unit">MB</div>
85
+ </div>
86
+ <div class="mini-stat">
87
+ <div class="label">Total</div>
88
+ <div class="value" id="totalMb">--</div>
89
+ <div class="unit">MB</div>
90
+ </div>
91
+ <div class="mini-stat">
92
+ <div class="label">Usage</div>
93
+ <div class="value" id="memPct">--</div>
94
+ <div class="unit">%</div>
95
+ </div>
96
+ <div class="mini-stat">
97
+ <div class="label">Free</div>
98
+ <div class="value" id="freeMb">--</div>
99
+ <div class="unit">MB</div>
100
+ </div>
101
+ </div>
102
+
103
+ <script>
104
+ const memHistory = []
105
+ const MAX_POINTS = 300
106
+
107
+ const doughnut = new Chart(document.getElementById('doughnut'), {
108
+ type: 'doughnut',
109
+ data: {
110
+ labels: ['Used', 'Free'],
111
+ datasets: [{ data: [0, 100], backgroundColor: ['#a371f7', '#1c1e26'], borderWidth: 0 }],
112
+ },
113
+ options: {
114
+ responsive: true,
115
+ maintainAspectRatio: false,
116
+ animation: false,
117
+ cutout: '60%',
118
+ plugins: {
119
+ legend: { position: 'bottom', labels: { color: '#8b949e', padding: 16, font: { size: 12 } } },
120
+ },
121
+ },
122
+ })
123
+
124
+ const timeline = new Chart(document.getElementById('timeline'), {
125
+ type: 'line',
126
+ data: {
127
+ labels: [],
128
+ datasets: [{
129
+ data: memHistory,
130
+ borderColor: '#a371f7',
131
+ backgroundColor: 'rgba(163,113,247,0.15)',
132
+ fill: true,
133
+ tension: 0.2,
134
+ pointRadius: 0,
135
+ borderWidth: 2,
136
+ }],
137
+ },
138
+ options: {
139
+ responsive: true,
140
+ maintainAspectRatio: false,
141
+ animation: false,
142
+ scales: {
143
+ y: { min: 0, max: 100, grid: { color: '#21262d' }, ticks: { color: '#484f58', callback: v => v + '%' } },
144
+ x: { display: false },
145
+ },
146
+ plugins: { legend: { display: false } },
147
+ },
148
+ })
149
+
150
+ TuiMon.onUpdate(function (data) {
151
+ TuiMon.set('#memPct', data.memory)
152
+ TuiMon.set('#usedMb', data.memoryUsedMb)
153
+ TuiMon.set('#totalMb', data.memoryTotalMb)
154
+ TuiMon.set('#freeMb', data.memoryTotalMb - data.memoryUsedMb)
155
+
156
+ doughnut.data.datasets[0].data = [data.memory, 100 - data.memory]
157
+ doughnut.update()
158
+
159
+ memHistory.push(data.memory)
160
+ if (memHistory.length > MAX_POINTS) memHistory.shift()
161
+ timeline.data.labels = memHistory.map((_, i) => i)
162
+ timeline.update()
163
+ })
164
+ </script>
165
+ </body>
166
+ </html>
@@ -0,0 +1,186 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>TuiMon — Overview</title>
7
+ <script src="https://cdn.jsdelivr.net/npm/chart.js@4"></script>
8
+ <style>
9
+ * { margin: 0; padding: 0; box-sizing: border-box; }
10
+ body {
11
+ background: #0d1117;
12
+ color: #c9d1d9;
13
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, monospace;
14
+ padding: 12px;
15
+ overflow: hidden;
16
+ height: 100vh;
17
+ }
18
+ .header {
19
+ display: flex;
20
+ justify-content: space-between;
21
+ align-items: center;
22
+ margin-bottom: 12px;
23
+ padding-bottom: 8px;
24
+ border-bottom: 1px solid #21262d;
25
+ }
26
+ .header h1 { font-size: 16px; color: #58a6ff; font-weight: 600; }
27
+ .header .time { font-size: 12px; color: #484f58; }
28
+ .stats {
29
+ display: grid;
30
+ grid-template-columns: repeat(4, 1fr);
31
+ gap: 10px;
32
+ margin-bottom: 12px;
33
+ }
34
+ .stat-card {
35
+ background: #161b22;
36
+ border: 1px solid #21262d;
37
+ border-radius: 6px;
38
+ padding: 12px;
39
+ text-align: center;
40
+ }
41
+ .stat-card .label { font-size: 11px; color: #8b949e; text-transform: uppercase; letter-spacing: 1px; }
42
+ .stat-card .value { font-size: 28px; font-weight: 700; margin-top: 4px; }
43
+ .stat-card .unit { font-size: 12px; color: #8b949e; }
44
+ .stat-card.cpu .value { color: #58a6ff; }
45
+ .stat-card.mem .value { color: #a371f7; }
46
+ .stat-card.req .value { color: #3fb950; }
47
+ .stat-card.up .value { color: #d29922; }
48
+ .charts {
49
+ display: grid;
50
+ grid-template-columns: 2fr 1fr 1fr;
51
+ gap: 10px;
52
+ height: calc(100vh - 200px);
53
+ }
54
+ .tm-panel {
55
+ background: #161b22;
56
+ border: 1px solid #21262d;
57
+ border-radius: 6px;
58
+ padding: 12px;
59
+ position: relative;
60
+ overflow: hidden;
61
+ }
62
+ .tm-panel h3 {
63
+ font-size: 12px;
64
+ color: #8b949e;
65
+ text-transform: uppercase;
66
+ letter-spacing: 1px;
67
+ margin-bottom: 8px;
68
+ }
69
+ .chart-wrap { height: calc(100% - 28px); }
70
+ .log-panel {
71
+ grid-column: 1 / -1;
72
+ background: #161b22;
73
+ border: 1px solid #21262d;
74
+ border-radius: 6px;
75
+ padding: 10px 12px;
76
+ max-height: 100px;
77
+ overflow-y: auto;
78
+ font-family: monospace;
79
+ font-size: 11px;
80
+ }
81
+ .log-panel .log-entry { color: #8b949e; line-height: 1.6; }
82
+ .log-panel .log-entry.spike { color: #d29922; }
83
+ </style>
84
+ </head>
85
+ <body>
86
+ <div class="header">
87
+ <h1>TuiMon Dashboard</h1>
88
+ <span class="time" id="time"></span>
89
+ </div>
90
+
91
+ <div class="stats">
92
+ <div class="stat-card cpu">
93
+ <div class="label">CPU</div>
94
+ <div class="value" id="cpu">--</div>
95
+ <div class="unit">%</div>
96
+ </div>
97
+ <div class="stat-card mem">
98
+ <div class="label">Memory</div>
99
+ <div class="value" id="memory">--</div>
100
+ <div class="unit">%</div>
101
+ </div>
102
+ <div class="stat-card req">
103
+ <div class="label">Requests/s</div>
104
+ <div class="value" id="requests">--</div>
105
+ <div class="unit">req/s</div>
106
+ </div>
107
+ <div class="stat-card up">
108
+ <div class="label">Uptime</div>
109
+ <div class="value" id="uptime">--</div>
110
+ <div class="unit">seconds</div>
111
+ </div>
112
+ </div>
113
+
114
+ <div class="charts">
115
+ <div class="tm-panel" data-tm-key="g" data-tm-label="CPU Detail">
116
+ <h3>CPU History</h3>
117
+ <div class="chart-wrap"><canvas id="cpuChart"></canvas></div>
118
+ </div>
119
+ <div class="tm-panel" data-tm-key="m" data-tm-label="Memory Detail">
120
+ <h3>Memory Usage</h3>
121
+ <div class="chart-wrap"><canvas id="memChart"></canvas></div>
122
+ </div>
123
+ <div class="tm-panel">
124
+ <h3>Request Rate</h3>
125
+ <div class="chart-wrap"><canvas id="reqChart"></canvas></div>
126
+ </div>
127
+ </div>
128
+
129
+ <div class="log-panel" id="logs">
130
+ <div class="log-entry" style="color:#484f58">Waiting for data...</div>
131
+ </div>
132
+
133
+ <script>
134
+ const cpuHistory = []
135
+ const reqHistory = []
136
+ const MAX_POINTS = 60
137
+
138
+ const cpuChart = new Chart(document.getElementById('cpuChart'), {
139
+ type: 'line',
140
+ data: { labels: [], datasets: [{ data: cpuHistory, borderColor: '#58a6ff', backgroundColor: 'rgba(88,166,255,0.1)', fill: true, tension: 0.3, pointRadius: 0 }] },
141
+ options: { responsive: true, maintainAspectRatio: false, animation: false, scales: { y: { min: 0, max: 100, grid: { color: '#21262d' }, ticks: { color: '#484f58' } }, x: { display: false } }, plugins: { legend: { display: false } } },
142
+ })
143
+
144
+ const memChart = new Chart(document.getElementById('memChart'), {
145
+ type: 'doughnut',
146
+ data: { labels: ['Used', 'Free'], datasets: [{ data: [0, 100], backgroundColor: ['#a371f7', '#21262d'], borderWidth: 0 }] },
147
+ options: { responsive: true, maintainAspectRatio: false, animation: false, cutout: '65%', plugins: { legend: { display: false } } },
148
+ })
149
+
150
+ const reqChart = new Chart(document.getElementById('reqChart'), {
151
+ type: 'bar',
152
+ data: { labels: [], datasets: [{ data: reqHistory, backgroundColor: '#3fb950' }] },
153
+ options: { responsive: true, maintainAspectRatio: false, animation: false, scales: { y: { grid: { color: '#21262d' }, ticks: { color: '#484f58' } }, x: { display: false } }, plugins: { legend: { display: false } } },
154
+ })
155
+
156
+ TuiMon.onUpdate(function (data) {
157
+ TuiMon.set('#cpu', data.cpu)
158
+ TuiMon.set('#memory', data.memory)
159
+ TuiMon.set('#requests', data.requests)
160
+ TuiMon.set('#uptime', data.uptime)
161
+ TuiMon.set('#time', new Date(data.timestamp).toLocaleTimeString())
162
+
163
+ cpuHistory.push(data.cpu)
164
+ if (cpuHistory.length > MAX_POINTS) cpuHistory.shift()
165
+ cpuChart.data.labels = cpuHistory.map((_, i) => i)
166
+ cpuChart.update()
167
+
168
+ memChart.data.datasets[0].data = [data.memory, 100 - data.memory]
169
+ memChart.update()
170
+
171
+ reqHistory.push(data.requests)
172
+ if (reqHistory.length > MAX_POINTS) reqHistory.shift()
173
+ reqChart.data.labels = reqHistory.map((_, i) => i)
174
+ reqChart.update()
175
+
176
+ var logsEl = document.getElementById('logs')
177
+ if (data.logs && data.logs.length) {
178
+ logsEl.innerHTML = data.logs.map(function (l) {
179
+ var cls = l.includes('spike') ? 'log-entry spike' : 'log-entry'
180
+ return '<div class="' + cls + '">' + l + '</div>'
181
+ }).join('')
182
+ }
183
+ })
184
+ </script>
185
+ </body>
186
+ </html>
@@ -0,0 +1,87 @@
1
+ import tuimon from 'tuimon'
2
+ import { cpus, freemem, totalmem, loadavg } from 'node:os'
3
+
4
+ interface DashboardData {
5
+ cpu: number
6
+ memory: number
7
+ memoryUsedMb: number
8
+ memoryTotalMb: number
9
+ requests: number
10
+ errors: number
11
+ uptime: number
12
+ loadAvg: number[]
13
+ coreCount: number
14
+ timestamp: number
15
+ logs: string[]
16
+ }
17
+
18
+ const logs: string[] = []
19
+
20
+ function getCpuPercent(): number {
21
+ const list = cpus()
22
+ const total = list.reduce((a, c) => a + Object.values(c.times).reduce((x, y) => x + y, 0), 0)
23
+ const idle = list.reduce((a, c) => a + c.times.idle, 0)
24
+ return Math.round(100 - (idle / total) * 100)
25
+ }
26
+
27
+ async function getData(): Promise<DashboardData> {
28
+ const cpu = getCpuPercent()
29
+ const free = freemem()
30
+ const total = totalmem()
31
+ const requests = Math.round(100 + Math.random() * 400)
32
+
33
+ if (Math.random() > 0.85) {
34
+ logs.unshift(`[${new Date().toISOString()}] spike: ${requests} req/s`)
35
+ if (logs.length > 5) logs.pop()
36
+ }
37
+
38
+ return {
39
+ cpu,
40
+ memory: Math.round(((total - free) / total) * 100),
41
+ memoryUsedMb: Math.round((total - free) / 1024 / 1024),
42
+ memoryTotalMb: Math.round(total / 1024 / 1024),
43
+ requests,
44
+ errors: Math.round(Math.random() * 3),
45
+ uptime: Math.round(process.uptime()),
46
+ loadAvg: loadavg(),
47
+ coreCount: cpus().length,
48
+ timestamp: Date.now(),
49
+ logs,
50
+ }
51
+ }
52
+
53
+ const dash = await tuimon.start({
54
+ pages: {
55
+ overview: {
56
+ html: new URL('./pages/overview.html', import.meta.url).pathname,
57
+ default: true,
58
+ label: 'Overview',
59
+ keys: {
60
+ F5: { label: 'Refresh', action: async () => dash.render(await getData()) },
61
+ F10: { label: 'Quit', action: () => process.exit(0) },
62
+ },
63
+ },
64
+ cpu: {
65
+ html: new URL('./pages/cpu-detail.html', import.meta.url).pathname,
66
+ shortcut: 'g',
67
+ label: 'CPU Detail',
68
+ keys: {
69
+ F1: { label: 'Export CSV', action: () => console.error('export not implemented') },
70
+ F5: { label: 'Refresh', action: async () => dash.render(await getData()) },
71
+ F10: { label: 'Quit', action: () => process.exit(0) },
72
+ },
73
+ },
74
+ memory: {
75
+ html: new URL('./pages/memory-detail.html', import.meta.url).pathname,
76
+ shortcut: 'm',
77
+ label: 'Memory',
78
+ keys: {
79
+ F5: { label: 'Refresh', action: async () => dash.render(await getData()) },
80
+ F10: { label: 'Quit', action: () => process.exit(0) },
81
+ },
82
+ },
83
+ },
84
+
85
+ refresh: 1000,
86
+ data: getData,
87
+ })
package/tsconfig.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "lib": ["ES2022"],
7
+ "outDir": "./dist",
8
+ "rootDir": "./src",
9
+ "declaration": true,
10
+ "declarationMap": true,
11
+ "sourceMap": true,
12
+ "strict": true,
13
+ "noUncheckedIndexedAccess": true,
14
+ "noImplicitReturns": true,
15
+ "noFallthroughCasesInSwitch": true,
16
+ "exactOptionalPropertyTypes": true,
17
+ "forceConsistentCasingInFileNames": true,
18
+ "esModuleInterop": true,
19
+ "skipLibCheck": true
20
+ },
21
+ "include": ["src"],
22
+ "exclude": ["node_modules", "dist", "src/__tests__"]
23
+ }