@covibes/zeroshot 5.4.0 → 5.4.1

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 (136) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +8 -465
  3. package/cli/index.js +101 -5231
  4. package/package.json +16 -130
  5. package/CHANGELOG.md +0 -537
  6. package/cli/commands/providers.js +0 -149
  7. package/cli/lib/first-run.js +0 -211
  8. package/cli/lib/update-checker.js +0 -281
  9. package/cli/message-formatter-utils.js +0 -75
  10. package/cli/message-formatters-normal.js +0 -275
  11. package/cli/message-formatters-watch.js +0 -181
  12. package/cluster-templates/base-templates/debug-workflow.json +0 -375
  13. package/cluster-templates/base-templates/full-workflow.json +0 -581
  14. package/cluster-templates/base-templates/single-worker.json +0 -63
  15. package/cluster-templates/base-templates/worker-validator.json +0 -208
  16. package/cluster-templates/conductor-bootstrap.json +0 -124
  17. package/docker/zeroshot-cluster/Dockerfile +0 -152
  18. package/docker/zeroshot-cluster/pre-baked-deps.json +0 -28
  19. package/lib/completion.js +0 -174
  20. package/lib/docker-config.js +0 -220
  21. package/lib/git-remote-utils.js +0 -165
  22. package/lib/id-detector.js +0 -56
  23. package/lib/provider-defaults.js +0 -62
  24. package/lib/provider-detection.js +0 -59
  25. package/lib/provider-names.js +0 -57
  26. package/lib/settings/claude-auth.js +0 -78
  27. package/lib/settings.js +0 -503
  28. package/lib/stream-json-parser.js +0 -10
  29. package/scripts/fix-node-pty-permissions.js +0 -75
  30. package/scripts/record-demo.sh +0 -279
  31. package/scripts/setup-merge-queue.sh +0 -170
  32. package/scripts/test-install.sh +0 -40
  33. package/scripts/validate-templates.js +0 -100
  34. package/src/agent/agent-config.js +0 -246
  35. package/src/agent/agent-context-builder.js +0 -444
  36. package/src/agent/agent-hook-executor.js +0 -599
  37. package/src/agent/agent-lifecycle.js +0 -743
  38. package/src/agent/agent-stuck-detector.js +0 -256
  39. package/src/agent/agent-task-executor.js +0 -1690
  40. package/src/agent/agent-trigger-evaluator.js +0 -67
  41. package/src/agent/output-extraction.js +0 -217
  42. package/src/agent/output-reformatter.js +0 -175
  43. package/src/agent/schema-utils.js +0 -146
  44. package/src/agent-wrapper.js +0 -579
  45. package/src/agents/git-pusher-template.js +0 -285
  46. package/src/attach/attach-client.js +0 -438
  47. package/src/attach/attach-server.js +0 -543
  48. package/src/attach/index.js +0 -35
  49. package/src/attach/protocol.js +0 -220
  50. package/src/attach/ring-buffer.js +0 -121
  51. package/src/attach/socket-discovery.js +0 -267
  52. package/src/claude-task-runner.js +0 -574
  53. package/src/config-router.js +0 -80
  54. package/src/config-validator.js +0 -1925
  55. package/src/copy-worker.js +0 -43
  56. package/src/input-helpers.js +0 -65
  57. package/src/isolation-manager.js +0 -1471
  58. package/src/issue-providers/README.md +0 -305
  59. package/src/issue-providers/azure-devops-provider.js +0 -273
  60. package/src/issue-providers/base-provider.js +0 -232
  61. package/src/issue-providers/github-provider.js +0 -179
  62. package/src/issue-providers/gitlab-provider.js +0 -241
  63. package/src/issue-providers/index.js +0 -196
  64. package/src/issue-providers/jira-provider.js +0 -239
  65. package/src/ledger.js +0 -631
  66. package/src/lib/safe-exec.js +0 -88
  67. package/src/logic-engine.js +0 -223
  68. package/src/message-bus-bridge.js +0 -139
  69. package/src/message-bus.js +0 -249
  70. package/src/name-generator.js +0 -232
  71. package/src/orchestrator.js +0 -2815
  72. package/src/preflight.js +0 -639
  73. package/src/process-metrics.js +0 -608
  74. package/src/providers/anthropic/cli-builder.js +0 -73
  75. package/src/providers/anthropic/index.js +0 -204
  76. package/src/providers/anthropic/models.js +0 -23
  77. package/src/providers/anthropic/output-parser.js +0 -177
  78. package/src/providers/base-provider.js +0 -251
  79. package/src/providers/capabilities.js +0 -60
  80. package/src/providers/google/cli-builder.js +0 -55
  81. package/src/providers/google/index.js +0 -116
  82. package/src/providers/google/models.js +0 -24
  83. package/src/providers/google/output-parser.js +0 -101
  84. package/src/providers/index.js +0 -91
  85. package/src/providers/openai/cli-builder.js +0 -133
  86. package/src/providers/openai/index.js +0 -136
  87. package/src/providers/openai/models.js +0 -21
  88. package/src/providers/openai/output-parser.js +0 -143
  89. package/src/providers/opencode/cli-builder.js +0 -42
  90. package/src/providers/opencode/index.js +0 -103
  91. package/src/providers/opencode/models.js +0 -55
  92. package/src/providers/opencode/output-parser.js +0 -122
  93. package/src/schemas/sub-cluster.js +0 -185
  94. package/src/status-footer.js +0 -1026
  95. package/src/sub-cluster-wrapper.js +0 -601
  96. package/src/task-runner.js +0 -30
  97. package/src/template-resolver.js +0 -372
  98. package/src/tui/CHANGES.txt +0 -133
  99. package/src/tui/LAYOUT.md +0 -261
  100. package/src/tui/README.txt +0 -192
  101. package/src/tui/TWO-LEVEL-NAVIGATION.md +0 -186
  102. package/src/tui/data-poller.js +0 -349
  103. package/src/tui/demo.js +0 -208
  104. package/src/tui/formatters.js +0 -129
  105. package/src/tui/index.js +0 -193
  106. package/src/tui/keybindings.js +0 -324
  107. package/src/tui/layout.js +0 -334
  108. package/src/tui/renderer.js +0 -184
  109. package/task-lib/attachable-watcher.js +0 -309
  110. package/task-lib/claude-recovery.js +0 -156
  111. package/task-lib/commands/clean.js +0 -50
  112. package/task-lib/commands/episodes.js +0 -105
  113. package/task-lib/commands/get-log-path.js +0 -23
  114. package/task-lib/commands/kill.js +0 -32
  115. package/task-lib/commands/list.js +0 -105
  116. package/task-lib/commands/logs.js +0 -453
  117. package/task-lib/commands/resume.js +0 -42
  118. package/task-lib/commands/run.js +0 -57
  119. package/task-lib/commands/schedule.js +0 -105
  120. package/task-lib/commands/scheduler-cmd.js +0 -96
  121. package/task-lib/commands/schedules.js +0 -148
  122. package/task-lib/commands/status.js +0 -44
  123. package/task-lib/commands/unschedule.js +0 -16
  124. package/task-lib/completion.js +0 -9
  125. package/task-lib/config.js +0 -8
  126. package/task-lib/name-generator.js +0 -230
  127. package/task-lib/package.json +0 -3
  128. package/task-lib/runner.js +0 -201
  129. package/task-lib/scheduler.js +0 -252
  130. package/task-lib/store.js +0 -529
  131. package/task-lib/tui/formatters.js +0 -215
  132. package/task-lib/tui/index.js +0 -197
  133. package/task-lib/tui/layout.js +0 -111
  134. package/task-lib/tui/renderer.js +0 -129
  135. package/task-lib/tui.js +0 -425
  136. package/task-lib/watcher.js +0 -226
@@ -1,349 +0,0 @@
1
- /**
2
- * DataPoller - Aggregates cluster data for TUI display
3
- *
4
- * Polls all data sources at appropriate intervals:
5
- * - Cluster states (1s)
6
- * - Resource stats via pidusage (2s)
7
- * - New cluster detection (2s)
8
- * - Ledger message streaming (500ms per cluster)
9
- */
10
-
11
- const pidusage = require('pidusage');
12
- const Ledger = require('../ledger');
13
- const fs = require('fs');
14
- const path = require('path');
15
- const os = require('os');
16
-
17
- class DataPoller {
18
- constructor(orchestrator, options = {}) {
19
- this.orchestrator = orchestrator;
20
- this.intervals = [];
21
- this.ledgers = new Map(); // clusterId -> Ledger instance
22
- this.ledgerStopFns = new Map(); // clusterId -> stop function for pollForMessages
23
- this.onUpdate = options.onUpdate || (() => {}); // Callback for updates
24
- this.watchForNewClustersStopFn = null;
25
- }
26
-
27
- /**
28
- * Start all polling intervals
29
- */
30
- start() {
31
- // Poll cluster states (1s)
32
- const clusterStateInterval = setInterval(() => {
33
- this._pollClusterStates();
34
- }, 1000);
35
- this.intervals.push(clusterStateInterval);
36
-
37
- // Poll resource stats (2s)
38
- const resourceStatsInterval = setInterval(() => {
39
- this._pollResourceStats();
40
- }, 2000);
41
- this.intervals.push(resourceStatsInterval);
42
-
43
- // Watch for new clusters (2s)
44
- this._watchForNewClusters();
45
-
46
- // Defer initial polls to avoid blocking UI startup
47
- // Run in background after 50ms to let UI render first
48
- setTimeout(() => {
49
- this._pollClusterStates();
50
- }, 50);
51
-
52
- setTimeout(() => {
53
- this._pollResourceStats();
54
- }, 100);
55
- }
56
-
57
- /**
58
- * Stop all polling intervals and clean up resources
59
- */
60
- stop() {
61
- // Clear all intervals
62
- for (const intervalId of this.intervals) {
63
- clearInterval(intervalId);
64
- }
65
- this.intervals = [];
66
-
67
- // Stop watching for new clusters
68
- if (this.watchForNewClustersStopFn) {
69
- this.watchForNewClustersStopFn();
70
- this.watchForNewClustersStopFn = null;
71
- }
72
-
73
- // Stop all ledger polling
74
- for (const stopFn of this.ledgerStopFns.values()) {
75
- stopFn();
76
- }
77
- this.ledgerStopFns.clear();
78
-
79
- // Close all ledger connections
80
- for (const ledger of this.ledgers.values()) {
81
- try {
82
- ledger.close();
83
- } catch {
84
- // Ignore errors during cleanup
85
- }
86
- }
87
- this.ledgers.clear();
88
- }
89
-
90
- /**
91
- * Poll cluster states (1s interval)
92
- * Gets all clusters and their agent states from orchestrator
93
- * @private
94
- */
95
- _pollClusterStates() {
96
- try {
97
- const clusters = this.orchestrator.listClusters();
98
-
99
- // Get detailed status for each cluster
100
- const clustersWithStatus = clusters.map((cluster) => {
101
- try {
102
- const status = this.orchestrator.getStatus(cluster.id);
103
- // Add agentCount for stats calculation
104
- return {
105
- ...status,
106
- agentCount: status.agents ? status.agents.length : 0,
107
- };
108
- } catch (error) {
109
- console.error(
110
- `[DataPoller] Failed to get status for cluster ${cluster.id}:`,
111
- error.message
112
- );
113
- return {
114
- id: cluster.id,
115
- state: 'unknown',
116
- createdAt: cluster.createdAt,
117
- agents: [],
118
- agentCount: 0,
119
- messageCount: 0,
120
- };
121
- }
122
- });
123
-
124
- this.onUpdate({
125
- type: 'cluster_state',
126
- clusters: clustersWithStatus,
127
- });
128
- } catch (error) {
129
- console.error('[DataPoller] _pollClusterStates error:', error.message);
130
- }
131
- }
132
-
133
- /**
134
- * Poll resource stats (2s interval)
135
- * Uses pidusage to get CPU and memory for all agent processes
136
- * @private
137
- */
138
- async _pollResourceStats() {
139
- try {
140
- const clusters = this.orchestrator.listClusters();
141
- const stats = {};
142
- const pids = this._collectClusterPids(clusters);
143
-
144
- if (pids.length > 0) {
145
- const pidStats = await this._safePidUsage(pids);
146
- this._populatePidStats(stats, pids, pidStats);
147
- }
148
-
149
- this.onUpdate({
150
- type: 'resource_stats',
151
- stats,
152
- });
153
- } catch (error) {
154
- console.error('[DataPoller] _pollResourceStats error:', error.message);
155
- }
156
- }
157
-
158
- /**
159
- * Watch for new clusters (2s interval)
160
- * Uses orchestrator.watchForNewClusters to detect new clusters
161
- * and start streaming their ledger messages
162
- * @private
163
- */
164
- _watchForNewClusters() {
165
- this.watchForNewClustersStopFn = this.orchestrator.watchForNewClusters((cluster) => {
166
- // Lazy load ledger only when we need to stream messages
167
- // This avoids loading all ledgers on startup
168
- const ledger = this._loadLedgerForCluster(cluster, {
169
- label: 'cluster',
170
- requireExisting: true,
171
- });
172
-
173
- if (!ledger) {
174
- return;
175
- }
176
-
177
- // Start streaming messages
178
- this._streamLedgerMessages(cluster.id);
179
-
180
- // Emit update about new cluster
181
- this.onUpdate({
182
- type: 'new_cluster',
183
- cluster,
184
- });
185
- }, 2000);
186
-
187
- // Also load ledgers for all existing clusters
188
- const existingClusters = this.orchestrator.listClusters();
189
- for (const cluster of existingClusters) {
190
- const ledger = this._loadLedgerForCluster(cluster, { label: 'existing cluster' });
191
- if (!ledger) {
192
- continue;
193
- }
194
-
195
- this._streamLedgerMessages(cluster.id);
196
- }
197
- }
198
-
199
- /**
200
- * Stream ledger messages for a cluster (500ms interval)
201
- * Uses ledger.pollForMessages to get new messages
202
- * @param {string} clusterId - Cluster ID to stream messages from
203
- * @private
204
- */
205
- _streamLedgerMessages(clusterId) {
206
- const ledger = this.ledgers.get(clusterId);
207
- if (!ledger) {
208
- console.error(`[DataPoller] No ledger found for cluster ${clusterId}`);
209
- return;
210
- }
211
-
212
- // Stop existing polling if any
213
- const existingStopFn = this.ledgerStopFns.get(clusterId);
214
- if (existingStopFn) {
215
- existingStopFn();
216
- }
217
-
218
- // Start polling for messages
219
- const stopFn = ledger.pollForMessages(
220
- clusterId,
221
- (message) => {
222
- this.onUpdate({
223
- type: 'new_message',
224
- clusterId,
225
- message,
226
- });
227
- },
228
- 500, // Poll every 500ms
229
- 50 // Show last 50 messages initially
230
- );
231
-
232
- this.ledgerStopFns.set(clusterId, stopFn);
233
- }
234
-
235
- /**
236
- * Collect resource stats for all agent PIDs
237
- * @returns {Object} Map of pid -> { cpu, memory }
238
- * @private
239
- */
240
- async _collectResourceStats() {
241
- const stats = {};
242
- const clusters = this.orchestrator.listClusters();
243
-
244
- for (const cluster of clusters) {
245
- const pids = this._getClusterAgentPids(cluster);
246
- for (const pid of pids) {
247
- stats[pid] = await this._getSinglePidStat(pid);
248
- }
249
- }
250
-
251
- return stats;
252
- }
253
-
254
- _collectClusterPids(clusters) {
255
- const pids = [];
256
-
257
- for (const cluster of clusters) {
258
- pids.push(...this._getClusterAgentPids(cluster));
259
- }
260
-
261
- return pids;
262
- }
263
-
264
- _getClusterAgentPids(cluster) {
265
- try {
266
- const status = this.orchestrator.getStatus(cluster.id);
267
- const pids = [];
268
-
269
- for (const agent of status.agents || []) {
270
- if (agent.pid) {
271
- pids.push(agent.pid);
272
- }
273
- }
274
-
275
- return pids;
276
- } catch {
277
- // Skip clusters that error
278
- return [];
279
- }
280
- }
281
-
282
- async _getSinglePidStat(pid) {
283
- try {
284
- const pidStat = await pidusage(pid);
285
- return {
286
- cpu: pidStat.cpu || 0,
287
- memory: pidStat.memory || 0,
288
- };
289
- } catch {
290
- // Process died - set to zero
291
- return { cpu: 0, memory: 0 };
292
- }
293
- }
294
-
295
- async _safePidUsage(pids) {
296
- try {
297
- return await pidusage(pids);
298
- } catch {
299
- return null;
300
- }
301
- }
302
-
303
- _populatePidStats(stats, pids, pidStats) {
304
- for (const pid of pids) {
305
- const entry = pidStats?.[pid];
306
- stats[pid] = {
307
- cpu: entry?.cpu || 0,
308
- memory: entry?.memory || 0,
309
- };
310
- }
311
- }
312
-
313
- _loadLedgerForCluster(cluster, options) {
314
- const { label, requireExisting = false } = options;
315
-
316
- try {
317
- return this._ensureLedger(cluster.id, { requireExisting });
318
- } catch (error) {
319
- console.error(
320
- `[DataPoller] Failed to load ledger for ${label} ${cluster.id}:`,
321
- error.message
322
- );
323
- return null;
324
- }
325
- }
326
-
327
- _ensureLedger(clusterId, { requireExisting = false } = {}) {
328
- const existing = this.ledgers.get(clusterId);
329
- if (existing) {
330
- return existing;
331
- }
332
-
333
- const dbPath = this._getLedgerPath(clusterId);
334
- if (requireExisting && !fs.existsSync(dbPath)) {
335
- return null;
336
- }
337
-
338
- const ledger = new Ledger(dbPath);
339
- this.ledgers.set(clusterId, ledger);
340
- return ledger;
341
- }
342
-
343
- _getLedgerPath(clusterId) {
344
- const storageDir = this.orchestrator.storageDir || path.join(os.homedir(), '.zeroshot');
345
- return path.join(storageDir, `${clusterId}.db`);
346
- }
347
- }
348
-
349
- module.exports = DataPoller;
package/src/tui/demo.js DELETED
@@ -1,208 +0,0 @@
1
- /**
2
- * TUI Dashboard Demo
3
- * Simple demonstration of the dashboard layout with mock data
4
- *
5
- * Run: node src/tui/demo.js
6
- * Press: [q] to quit
7
- */
8
-
9
- const blessed = require('blessed');
10
- const {
11
- createLayout,
12
- updateClustersTable,
13
- updateAgentsTable,
14
- updateStatsBox,
15
- addLogEntry,
16
- } = require('./layout');
17
- const { formatTimestamp } = require('./formatters');
18
-
19
- // Create main screen
20
- const screen = blessed.screen({
21
- mouse: true,
22
- title: 'Cluster Dashboard - Demo',
23
- smartCSR: true,
24
- });
25
-
26
- // Create layout
27
- const layout = createLayout(screen);
28
-
29
- // Mock data generators
30
- const mockClusters = [
31
- {
32
- id: 'cluster-swift-falcon',
33
- status: 'running',
34
- agentCount: 5,
35
- config: 'default',
36
- uptime: formatTimestamp(2 * 60 * 60 * 1000 + 30 * 60 * 1000), // 2h 30m
37
- },
38
- {
39
- id: 'cluster-bold-panther',
40
- status: 'running',
41
- agentCount: 3,
42
- config: 'simple',
43
- uptime: formatTimestamp(45 * 60 * 1000), // 45m
44
- },
45
- {
46
- id: 'cluster-quick-eagle',
47
- status: 'stopped',
48
- agentCount: 0,
49
- config: 'default',
50
- uptime: '0s',
51
- },
52
- ];
53
-
54
- const mockAgents = [
55
- {
56
- clusterId: 'cluster-swift-falcon',
57
- id: 'worker-1',
58
- role: 'worker',
59
- status: 'running',
60
- iteration: 3,
61
- cpu: '12.5%',
62
- memory: '245 MB',
63
- },
64
- {
65
- clusterId: 'cluster-swift-falcon',
66
- id: 'validator-req',
67
- role: 'validator',
68
- status: 'idle',
69
- iteration: 0,
70
- cpu: '0.1%',
71
- memory: '128 MB',
72
- },
73
- {
74
- clusterId: 'cluster-swift-falcon',
75
- id: 'validator-sec',
76
- role: 'validator',
77
- status: 'idle',
78
- iteration: 0,
79
- cpu: '0.2%',
80
- memory: '135 MB',
81
- },
82
- {
83
- clusterId: 'cluster-bold-panther',
84
- id: 'worker-2',
85
- role: 'worker',
86
- status: 'running',
87
- iteration: 1,
88
- cpu: '8.3%',
89
- memory: '189 MB',
90
- },
91
- {
92
- clusterId: 'cluster-bold-panther',
93
- id: 'validator-qa',
94
- role: 'validator',
95
- status: 'running',
96
- iteration: 1,
97
- cpu: '5.1%',
98
- memory: '156 MB',
99
- },
100
- ];
101
-
102
- const mockStats = {
103
- activeClusters: 2,
104
- totalAgents: 5,
105
- usedMemory: '853 MB',
106
- totalMemory: '8 GB',
107
- totalCPU: '26.2%',
108
- };
109
-
110
- // Keyboard shortcuts
111
- screen.key(['q', 'C-c'], () => {
112
- return process.exit(0);
113
- });
114
-
115
- screen.key(['r'], () => {
116
- updateClustersTable(layout.clustersTable, mockClusters);
117
- updateAgentsTable(layout.agentTable, mockAgents);
118
- updateStatsBox(layout.statsBox, mockStats);
119
- addLogEntry(layout.logsBox, 'Dashboard refreshed', 'info');
120
- screen.render();
121
- });
122
-
123
- screen.key(['c'], () => {
124
- addLogEntry(layout.logsBox, 'Cluster started: cluster-wandering-wolf', 'info');
125
- screen.render();
126
- });
127
-
128
- screen.key(['k'], () => {
129
- addLogEntry(layout.logsBox, 'Cluster killed: cluster-quick-eagle', 'warn');
130
- screen.render();
131
- });
132
-
133
- screen.key(['s'], () => {
134
- addLogEntry(layout.logsBox, 'Warning: High memory usage on cluster-swift-falcon', 'warn');
135
- screen.render();
136
- });
137
-
138
- // Initialize with mock data
139
- updateClustersTable(layout.clustersTable, mockClusters);
140
- updateAgentsTable(layout.agentTable, mockAgents);
141
- updateStatsBox(layout.statsBox, mockStats);
142
-
143
- // Add initial log entries
144
- addLogEntry(layout.logsBox, 'Dashboard initialized', 'info');
145
- addLogEntry(layout.logsBox, 'Monitoring 2 active clusters', 'info');
146
- addLogEntry(layout.logsBox, 'System CPU: 26.2% | Memory: 853 MB / 8 GB', 'info');
147
-
148
- // Simulate live updates
149
- const updateInterval = setInterval(() => {
150
- // Update uptime for running clusters
151
- mockClusters.forEach((cluster) => {
152
- if (cluster.status === 'running') {
153
- const uptimeMs = Math.random() * 3 * 60 * 60 * 1000; // Random uptime
154
- cluster.uptime = formatTimestamp(uptimeMs);
155
- }
156
- });
157
-
158
- // Simulate CPU/Memory changes
159
- mockAgents.forEach((agent) => {
160
- if (agent.status === 'running') {
161
- agent.cpu = (Math.random() * 20).toFixed(1) + '%';
162
- agent.memory = Math.floor(Math.random() * 200 + 100) + ' MB';
163
- }
164
- });
165
-
166
- mockStats.totalCPU = (Math.random() * 50).toFixed(1) + '%';
167
-
168
- updateClustersTable(layout.clustersTable, mockClusters);
169
- updateAgentsTable(layout.agentTable, mockAgents);
170
- updateStatsBox(layout.statsBox, mockStats);
171
-
172
- screen.render();
173
- }, 3000);
174
-
175
- // Display help on startup
176
- setTimeout(() => {
177
- addLogEntry(
178
- layout.logsBox,
179
- 'Press [r] to refresh | [c] to add cluster | [k] to kill | [s] for warning | [q] to quit',
180
- 'info'
181
- );
182
- screen.render();
183
- }, 500);
184
-
185
- // Cleanup on exit
186
- process.on('exit', () => {
187
- clearInterval(updateInterval);
188
- });
189
-
190
- // Render initial screen
191
- screen.render();
192
-
193
- console.log(
194
- '\n' +
195
- '===============================================\n' +
196
- ' Cluster Dashboard - Demo Mode\n' +
197
- '===============================================\n' +
198
- 'Keyboard shortcuts:\n' +
199
- ' [↑/↓] Navigate between widgets\n' +
200
- ' [Tab] Next widget\n' +
201
- ' [Shift+Tab] Previous widget\n' +
202
- ' [r] Refresh data\n' +
203
- ' [c] Simulate cluster start\n' +
204
- ' [k] Simulate cluster kill\n' +
205
- ' [s] Simulate warning\n' +
206
- ' [q] Quit\n' +
207
- '===============================================\n\n'
208
- );
@@ -1,129 +0,0 @@
1
- /**
2
- * TUI Display Formatters
3
- * Converts raw values to human-readable formats for terminal display
4
- */
5
-
6
- /**
7
- * Convert milliseconds to human-readable uptime string
8
- * @param {number} ms - Milliseconds
9
- * @returns {string} Formatted uptime (e.g., "5m 23s", "2h 15m", "3d 4h")
10
- */
11
- const formatTimestamp = (ms) => {
12
- if (!ms || ms < 0) return '0s';
13
-
14
- const seconds = Math.floor(ms / 1000);
15
-
16
- if (seconds < 60) {
17
- return `${seconds}s`;
18
- }
19
-
20
- const minutes = Math.floor(seconds / 60);
21
- if (minutes < 60) {
22
- const remainingSeconds = seconds % 60;
23
- return remainingSeconds > 0 ? `${minutes}m ${remainingSeconds}s` : `${minutes}m`;
24
- }
25
-
26
- const hours = Math.floor(minutes / 60);
27
- if (hours < 24) {
28
- const remainingMinutes = minutes % 60;
29
- return remainingMinutes > 0 ? `${hours}h ${remainingMinutes}m` : `${hours}h`;
30
- }
31
-
32
- const days = Math.floor(hours / 24);
33
- const remainingHours = hours % 24;
34
- return remainingHours > 0 ? `${days}d ${remainingHours}h` : `${days}d`;
35
- };
36
-
37
- /**
38
- * Convert bytes to human-readable size string
39
- * @param {number} bytes - Number of bytes
40
- * @returns {string} Formatted size (e.g., "245 MB", "1.2 GB", "512 KB")
41
- */
42
- const formatBytes = (bytes) => {
43
- if (!bytes || bytes < 0) return '0 B';
44
-
45
- const units = ['B', 'KB', 'MB', 'GB', 'TB'];
46
- let size = bytes;
47
- let unitIndex = 0;
48
-
49
- while (size >= 1000 && unitIndex < units.length - 1) {
50
- size /= 1000;
51
- unitIndex++;
52
- }
53
-
54
- const formatted = size < 10 ? size.toFixed(1) : Math.round(size);
55
- return `${formatted} ${units[unitIndex]}`;
56
- };
57
-
58
- /**
59
- * Format CPU percentage with consistent precision
60
- * @param {number} percent - CPU percentage (0-100)
61
- * @returns {string} Formatted percentage (e.g., "12.3%", "0.1%")
62
- */
63
- const formatCPU = (percent) => {
64
- if (typeof percent !== 'number' || percent < 0) return '0.0%';
65
-
66
- // Assert normalized range (should never exceed 100% after per-core normalization)
67
- let normalizedPercent = percent;
68
- if (normalizedPercent > 100) {
69
- console.warn(`[formatCPU] CPU percent ${percent}% exceeds 100% - normalization bug?`);
70
- normalizedPercent = 100;
71
- }
72
-
73
- return `${normalizedPercent.toFixed(1)}%`;
74
- };
75
-
76
- /**
77
- * Map cluster state to unicode icon
78
- * @param {string} state - Cluster state (running, stopped, initializing, stopping, failed, killed)
79
- * @returns {string} Unicode icon representing state
80
- */
81
- const stateIcon = (state) => {
82
- const icons = {
83
- running: '●', // filled circle (green)
84
- stopped: '○', // hollow circle
85
- initializing: '◐', // half circle
86
- stopping: '◑', // half circle other way
87
- failed: '⚠', // warning
88
- killed: '⚠', // warning
89
- };
90
-
91
- return icons[state] || '?';
92
- };
93
-
94
- /**
95
- * Truncate string with ellipsis if exceeds max length
96
- * @param {string} str - String to truncate
97
- * @param {number} maxLen - Maximum length
98
- * @returns {string} Truncated string with "..." if needed
99
- */
100
- const truncate = (str, maxLen) => {
101
- if (!str || typeof str !== 'string') return '';
102
- if (str.length <= maxLen) return str;
103
-
104
- return str.substring(0, maxLen - 3) + '...';
105
- };
106
-
107
- /**
108
- * Format duration between two timestamps
109
- * @param {number} startMs - Start timestamp in milliseconds
110
- * @param {number} endMs - End timestamp in milliseconds (null = now)
111
- * @returns {string} Formatted duration (e.g., "5m 23s", "2h 15m")
112
- */
113
- const formatDuration = (startMs, endMs) => {
114
- if (!startMs || startMs < 0) return '0s';
115
-
116
- const end = endMs && endMs > 0 ? endMs : Date.now();
117
- const duration = Math.max(0, end - startMs);
118
-
119
- return formatTimestamp(duration);
120
- };
121
-
122
- module.exports = {
123
- formatTimestamp,
124
- formatBytes,
125
- formatCPU,
126
- stateIcon,
127
- truncate,
128
- formatDuration,
129
- };