@covibes/zeroshot 5.3.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 (119) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +8 -385
  3. package/cli/index.js +108 -4615
  4. package/package.json +16 -125
  5. package/CHANGELOG.md +0 -537
  6. package/cli/commands/providers.js +0 -150
  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 -253
  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 -697
  14. package/cluster-templates/base-templates/single-worker.json +0 -63
  15. package/cluster-templates/base-templates/worker-validator.json +0 -152
  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 -212
  21. package/lib/id-detector.js +0 -53
  22. package/lib/provider-detection.js +0 -59
  23. package/lib/provider-names.js +0 -56
  24. package/lib/settings.js +0 -405
  25. package/lib/stream-json-parser.js +0 -10
  26. package/scripts/fix-node-pty-permissions.js +0 -75
  27. package/scripts/record-demo.sh +0 -279
  28. package/scripts/test-install.sh +0 -40
  29. package/scripts/validate-templates.js +0 -100
  30. package/src/agent/agent-config.js +0 -193
  31. package/src/agent/agent-context-builder.js +0 -335
  32. package/src/agent/agent-hook-executor.js +0 -402
  33. package/src/agent/agent-lifecycle.js +0 -683
  34. package/src/agent/agent-stuck-detector.js +0 -256
  35. package/src/agent/agent-task-executor.js +0 -1431
  36. package/src/agent/agent-trigger-evaluator.js +0 -67
  37. package/src/agent/output-extraction.js +0 -200
  38. package/src/agent/output-reformatter.js +0 -175
  39. package/src/agent/schema-utils.js +0 -111
  40. package/src/agent-wrapper.js +0 -570
  41. package/src/agents/git-pusher-agent.json +0 -20
  42. package/src/attach/attach-client.js +0 -438
  43. package/src/attach/attach-server.js +0 -543
  44. package/src/attach/index.js +0 -35
  45. package/src/attach/protocol.js +0 -220
  46. package/src/attach/ring-buffer.js +0 -121
  47. package/src/attach/socket-discovery.js +0 -267
  48. package/src/claude-task-runner.js +0 -523
  49. package/src/config-router.js +0 -80
  50. package/src/config-validator.js +0 -1660
  51. package/src/copy-worker.js +0 -43
  52. package/src/github.js +0 -139
  53. package/src/isolation-manager.js +0 -1381
  54. package/src/ledger.js +0 -614
  55. package/src/logic-engine.js +0 -223
  56. package/src/message-bus-bridge.js +0 -139
  57. package/src/message-bus.js +0 -249
  58. package/src/name-generator.js +0 -232
  59. package/src/orchestrator.js +0 -2519
  60. package/src/preflight.js +0 -485
  61. package/src/process-metrics.js +0 -566
  62. package/src/providers/anthropic/cli-builder.js +0 -45
  63. package/src/providers/anthropic/index.js +0 -134
  64. package/src/providers/anthropic/models.js +0 -23
  65. package/src/providers/anthropic/output-parser.js +0 -159
  66. package/src/providers/base-provider.js +0 -181
  67. package/src/providers/capabilities.js +0 -51
  68. package/src/providers/google/cli-builder.js +0 -55
  69. package/src/providers/google/index.js +0 -116
  70. package/src/providers/google/models.js +0 -24
  71. package/src/providers/google/output-parser.js +0 -92
  72. package/src/providers/index.js +0 -75
  73. package/src/providers/openai/cli-builder.js +0 -122
  74. package/src/providers/openai/index.js +0 -135
  75. package/src/providers/openai/models.js +0 -21
  76. package/src/providers/openai/output-parser.js +0 -129
  77. package/src/schemas/sub-cluster.js +0 -156
  78. package/src/status-footer.js +0 -980
  79. package/src/sub-cluster-wrapper.js +0 -560
  80. package/src/task-runner.js +0 -30
  81. package/src/template-resolver.js +0 -369
  82. package/src/tui/CHANGES.txt +0 -133
  83. package/src/tui/LAYOUT.md +0 -261
  84. package/src/tui/README.txt +0 -192
  85. package/src/tui/TWO-LEVEL-NAVIGATION.md +0 -186
  86. package/src/tui/data-poller.js +0 -325
  87. package/src/tui/demo.js +0 -208
  88. package/src/tui/formatters.js +0 -128
  89. package/src/tui/index.js +0 -193
  90. package/src/tui/keybindings.js +0 -383
  91. package/src/tui/layout.js +0 -334
  92. package/src/tui/renderer.js +0 -194
  93. package/task-lib/attachable-watcher.js +0 -272
  94. package/task-lib/claude-recovery.js +0 -119
  95. package/task-lib/commands/clean.js +0 -50
  96. package/task-lib/commands/get-log-path.js +0 -23
  97. package/task-lib/commands/kill.js +0 -32
  98. package/task-lib/commands/list.js +0 -105
  99. package/task-lib/commands/logs.js +0 -411
  100. package/task-lib/commands/resume.js +0 -42
  101. package/task-lib/commands/run.js +0 -57
  102. package/task-lib/commands/schedule.js +0 -105
  103. package/task-lib/commands/scheduler-cmd.js +0 -96
  104. package/task-lib/commands/schedules.js +0 -98
  105. package/task-lib/commands/status.js +0 -44
  106. package/task-lib/commands/unschedule.js +0 -16
  107. package/task-lib/completion.js +0 -9
  108. package/task-lib/config.js +0 -10
  109. package/task-lib/name-generator.js +0 -230
  110. package/task-lib/package.json +0 -3
  111. package/task-lib/runner.js +0 -144
  112. package/task-lib/scheduler.js +0 -252
  113. package/task-lib/store.js +0 -230
  114. package/task-lib/tui/formatters.js +0 -166
  115. package/task-lib/tui/index.js +0 -197
  116. package/task-lib/tui/layout.js +0 -111
  117. package/task-lib/tui/renderer.js +0 -119
  118. package/task-lib/tui.js +0 -384
  119. package/task-lib/watcher.js +0 -188
package/src/tui/index.js DELETED
@@ -1,193 +0,0 @@
1
- /**
2
- * TUI - Main interactive dashboard
3
- *
4
- * Coordinates:
5
- * - Screen and layout
6
- * - Data polling
7
- * - Rendering
8
- * - Keybindings
9
- * - State management
10
- */
11
-
12
- const blessed = require('blessed');
13
- const { createLayout } = require('./layout');
14
- const DataPoller = require('./data-poller');
15
- const Renderer = require('./renderer');
16
- const { setupKeybindings } = require('./keybindings');
17
-
18
- class TUI {
19
- constructor(options) {
20
- this.orchestrator = options.orchestrator;
21
- this.filter = options.filter || 'running';
22
- this.refreshRate = options.refreshRate || 1000;
23
-
24
- // State
25
- this.clusters = [];
26
- this.resourceStats = new Map();
27
- this.messages = [];
28
- this.selectedIndex = 0;
29
- this.poller = null;
30
- this.renderer = null;
31
- this.widgets = null;
32
- this.screen = null;
33
-
34
- // View mode: 'overview' or 'detail'
35
- this.viewMode = 'overview';
36
- this.detailClusterId = null;
37
- }
38
-
39
- start() {
40
- // Create screen
41
- this.screen = blessed.screen({
42
- smartCSR: true,
43
- title: 'Vibe Cluster Watch',
44
- dockBorders: true,
45
- fullUnicode: true,
46
- });
47
-
48
- // Create layout
49
- this.widgets = createLayout(this.screen);
50
-
51
- // Show immediate loading message
52
- this.widgets.statsBox.setContent('{center}{bold}Loading...{/bold}{/center}');
53
- this.screen.render();
54
-
55
- // Create renderer
56
- this.renderer = new Renderer(this.widgets, this.screen);
57
-
58
- // Setup keybindings (pass TUI instance for state management)
59
- setupKeybindings(this.screen, this.widgets, this, this.orchestrator);
60
-
61
- // Create data poller
62
- this.poller = new DataPoller(this.orchestrator, {
63
- refreshRate: this.refreshRate,
64
- onUpdate: (update) => this._handleUpdate(update),
65
- });
66
-
67
- // Initial message
68
- this.messages.push({
69
- timestamp: new Date().toISOString(),
70
- text: 'TUI started. Press ? for help.',
71
- level: 'info',
72
- });
73
- this.renderer.renderLogs(this.messages.slice(-20));
74
-
75
- // Start polling
76
- this.poller.start();
77
-
78
- // Initial render
79
- this.screen.render();
80
- }
81
-
82
- _handleUpdate(update) {
83
- // Update state based on update.type
84
- switch (update.type) {
85
- case 'cluster_state':
86
- // Update cluster list
87
- this.clusters = update.clusters;
88
-
89
- // Apply filter
90
- let filteredClusters = this.clusters;
91
- if (this.filter === 'running') {
92
- // For "running" filter, only show truly active (running) clusters
93
- // Exclude initializing, stopped, failed, etc.
94
- filteredClusters = this.clusters.filter((c) => c.state === 'running');
95
- } else if (this.filter !== 'all') {
96
- // For other specific filters, match exact state
97
- filteredClusters = this.clusters.filter((c) => c.state === this.filter);
98
- }
99
-
100
- // Ensure selectedIndex is valid
101
- if (this.selectedIndex >= filteredClusters.length) {
102
- this.selectedIndex = Math.max(0, filteredClusters.length - 1);
103
- }
104
-
105
- // Render clusters table
106
- this.renderer.renderClustersTable(filteredClusters, this.selectedIndex);
107
-
108
- // Render system stats
109
- this.renderer.renderSystemStats(this.clusters, this.resourceStats);
110
-
111
- // Update agent table for selected cluster (ONLY in detail view)
112
- if (this.viewMode === 'detail' && this.detailClusterId) {
113
- // In detail view, show agents for the detail cluster
114
- try {
115
- const status = this.orchestrator.getStatus(this.detailClusterId);
116
- this.renderer.renderAgentTable(status.agents, this.resourceStats);
117
- } catch {
118
- // Cluster might have been stopped/killed
119
- this.renderer.renderAgentTable([], this.resourceStats);
120
- }
121
- } else if (this.viewMode === 'overview') {
122
- // In overview view, don't show agents (or show empty)
123
- this.renderer.renderAgentTable([], this.resourceStats);
124
- }
125
- break;
126
-
127
- case 'resource_stats':
128
- // Update resource stats
129
- this.resourceStats = update.stats;
130
-
131
- // Re-render system stats
132
- this.renderer.renderSystemStats(this.clusters, this.resourceStats);
133
-
134
- // Update agent table with new resource stats (ONLY in detail view)
135
- if (this.viewMode === 'detail' && this.detailClusterId) {
136
- try {
137
- const status = this.orchestrator.getStatus(this.detailClusterId);
138
- this.renderer.renderAgentTable(status.agents, this.resourceStats);
139
- } catch {
140
- this.renderer.renderAgentTable([], this.resourceStats);
141
- }
142
- }
143
- break;
144
-
145
- case 'new_message':
146
- // Only add messages from the selected cluster
147
- const selectedClusterId = this.renderer.selectedClusterId;
148
- if (selectedClusterId && update.clusterId === selectedClusterId) {
149
- // Add new message to log
150
- this.messages.push(update.message);
151
-
152
- // Keep only last 100 messages in memory
153
- if (this.messages.length > 100) {
154
- this.messages = this.messages.slice(-100);
155
- }
156
-
157
- // Render last 20 messages
158
- this.renderer.renderLogs(this.messages.slice(-20));
159
- }
160
- break;
161
-
162
- case 'error':
163
- // Add error to log
164
- this.messages.push({
165
- timestamp: new Date().toISOString(),
166
- text: `✗ ${update.error}`,
167
- level: 'error',
168
- });
169
-
170
- if (this.messages.length > 100) {
171
- this.messages = this.messages.slice(-100);
172
- }
173
-
174
- this.renderer.renderLogs(this.messages.slice(-20));
175
- break;
176
- }
177
-
178
- // Render screen
179
- this.screen.render();
180
- }
181
-
182
- exit() {
183
- if (this.poller) {
184
- this.poller.stop();
185
- }
186
- if (this.screen) {
187
- this.screen.destroy();
188
- }
189
- process.exit(0);
190
- }
191
- }
192
-
193
- module.exports = TUI;
@@ -1,383 +0,0 @@
1
- /**
2
- * Keybindings for TUI
3
- *
4
- * Handles:
5
- * - Navigation (up/down, j/k)
6
- * - Actions (kill, stop, export, logs)
7
- * - Confirmations for destructive actions
8
- */
9
-
10
- const blessed = require('blessed');
11
- const { spawn } = require('child_process');
12
-
13
- function setupKeybindings(screen, widgets, tui, orchestrator) {
14
- // Enter - drill into cluster detail view
15
- screen.key(['enter'], () => {
16
- if (tui.viewMode === 'overview' && tui.clusters.length > 0) {
17
- const selectedCluster = tui.clusters[tui.selectedIndex];
18
- if (selectedCluster) {
19
- tui.viewMode = 'detail';
20
- tui.detailClusterId = selectedCluster.id;
21
- tui.renderer.setSelectedCluster(selectedCluster.id);
22
- tui.messages = []; // Clear old messages
23
-
24
- // Update help text
25
- widgets.helpBar.setContent(
26
- '{cyan-fg}[Esc]{/} Back {cyan-fg}[k]{/} Kill {cyan-fg}[s]{/} Stop {cyan-fg}[e]{/} Export {cyan-fg}[l]{/} Logs {cyan-fg}[r]{/} Refresh {cyan-fg}[q]{/} Quit'
27
- );
28
-
29
- // Switch to detail layout: hide clusters/stats, show agents/logs
30
- widgets.clustersTable.hide();
31
- widgets.statsBox.hide();
32
- widgets.agentTable.show();
33
- widgets.logsBox.show();
34
- screen.render();
35
- }
36
- }
37
- });
38
-
39
- // Escape - back to overview
40
- screen.key(['escape'], () => {
41
- if (tui.viewMode === 'detail') {
42
- tui.viewMode = 'overview';
43
- tui.detailClusterId = null;
44
- tui.renderer.setSelectedCluster(null);
45
- tui.messages = []; // Clear messages
46
-
47
- // Update help text
48
- widgets.helpBar.setContent(
49
- '{cyan-fg}[Enter]{/} View {cyan-fg}[↑/↓]{/} Navigate {cyan-fg}[k]{/} Kill {cyan-fg}[s]{/} Stop {cyan-fg}[l]{/} Logs {cyan-fg}[r]{/} Refresh {cyan-fg}[q]{/} Quit'
50
- );
51
-
52
- // Switch to overview layout: show clusters/stats, hide agents/logs
53
- widgets.clustersTable.show();
54
- widgets.statsBox.show();
55
- widgets.agentTable.hide();
56
- widgets.logsBox.hide();
57
- screen.render();
58
- }
59
- });
60
-
61
- // Navigation - up
62
- screen.key(['up', 'k'], () => {
63
- if (tui.clusters.length === 0) return;
64
- tui.selectedIndex = Math.max(0, tui.selectedIndex - 1);
65
- tui.renderer.renderClustersTable(tui.clusters, tui.selectedIndex);
66
-
67
- // Update agent table and logs for newly selected cluster
68
- const selectedCluster = tui.clusters[tui.selectedIndex];
69
- if (selectedCluster) {
70
- // CRITICAL: Tell renderer which cluster is selected
71
- tui.renderer.setSelectedCluster(selectedCluster.id);
72
-
73
- // Clear old messages from previous cluster
74
- tui.messages = [];
75
-
76
- const status = orchestrator.getStatus(selectedCluster.id);
77
- tui.renderer.renderAgentTable(status.agents, tui.resourceStats);
78
- }
79
-
80
- screen.render();
81
- });
82
-
83
- // Navigation - down
84
- screen.key(['down', 'j'], () => {
85
- if (tui.clusters.length === 0) return;
86
- tui.selectedIndex = Math.min(tui.clusters.length - 1, tui.selectedIndex + 1);
87
- tui.renderer.renderClustersTable(tui.clusters, tui.selectedIndex);
88
-
89
- // Update agent table and logs for newly selected cluster
90
- const selectedCluster = tui.clusters[tui.selectedIndex];
91
- if (selectedCluster) {
92
- // CRITICAL: Tell renderer which cluster is selected
93
- tui.renderer.setSelectedCluster(selectedCluster.id);
94
-
95
- // Clear old messages from previous cluster
96
- tui.messages = [];
97
-
98
- const status = orchestrator.getStatus(selectedCluster.id);
99
- tui.renderer.renderAgentTable(status.agents, tui.resourceStats);
100
- }
101
-
102
- screen.render();
103
- });
104
-
105
- // Kill selected cluster (with confirmation)
106
- screen.key(['K'], () => {
107
- if (tui.clusters.length === 0) return;
108
- const selectedCluster = tui.clusters[tui.selectedIndex];
109
- if (!selectedCluster) return;
110
-
111
- // Create confirmation dialog
112
- const question = blessed.question({
113
- parent: screen,
114
- border: 'line',
115
- height: 'shrink',
116
- width: 'half',
117
- top: 'center',
118
- left: 'center',
119
- label: ' {bold}{red-fg}Confirm Kill{/red-fg}{/bold} ',
120
- tags: true,
121
- keys: true,
122
- vi: true,
123
- });
124
-
125
- question.ask(
126
- `Kill cluster ${selectedCluster.id}?\n\n(This will force-stop all agents)`,
127
- async (err, value) => {
128
- if (err) return;
129
- if (value) {
130
- try {
131
- await orchestrator.kill(selectedCluster.id);
132
- tui.messages.push({
133
- timestamp: new Date().toISOString(),
134
- text: `✓ Killed cluster ${selectedCluster.id}`,
135
- level: 'success',
136
- });
137
- tui.renderer.renderLogs(tui.messages.slice(-20));
138
- } catch (error) {
139
- tui.messages.push({
140
- timestamp: new Date().toISOString(),
141
- text: `✗ Failed to kill cluster: ${error.message}`,
142
- level: 'error',
143
- });
144
- tui.renderer.renderLogs(tui.messages.slice(-20));
145
- }
146
- screen.render();
147
- }
148
- }
149
- );
150
- });
151
-
152
- // Stop selected cluster (with confirmation)
153
- screen.key(['s'], () => {
154
- if (tui.clusters.length === 0) return;
155
- const selectedCluster = tui.clusters[tui.selectedIndex];
156
- if (!selectedCluster) return;
157
-
158
- // Create confirmation dialog
159
- const question = blessed.question({
160
- parent: screen,
161
- border: 'line',
162
- height: 'shrink',
163
- width: 'half',
164
- top: 'center',
165
- left: 'center',
166
- label: ' {bold}{yellow-fg}Confirm Stop{/yellow-fg}{/bold} ',
167
- tags: true,
168
- keys: true,
169
- vi: true,
170
- });
171
-
172
- question.ask(
173
- `Stop cluster ${selectedCluster.id}?\n\n(This will gracefully stop all agents)`,
174
- async (err, value) => {
175
- if (err) return;
176
- if (value) {
177
- try {
178
- await orchestrator.stop(selectedCluster.id);
179
- tui.messages.push({
180
- timestamp: new Date().toISOString(),
181
- text: `✓ Stopped cluster ${selectedCluster.id}`,
182
- level: 'success',
183
- });
184
- tui.renderer.renderLogs(tui.messages.slice(-20));
185
- } catch (error) {
186
- tui.messages.push({
187
- timestamp: new Date().toISOString(),
188
- text: `✗ Failed to stop cluster: ${error.message}`,
189
- level: 'error',
190
- });
191
- tui.renderer.renderLogs(tui.messages.slice(-20));
192
- }
193
- screen.render();
194
- }
195
- }
196
- );
197
- });
198
-
199
- // Export selected cluster
200
- screen.key(['e'], () => {
201
- if (tui.clusters.length === 0) return;
202
- const selectedCluster = tui.clusters[tui.selectedIndex];
203
- if (!selectedCluster) return;
204
-
205
- try {
206
- const markdown = orchestrator.export(selectedCluster.id, 'markdown');
207
- const fs = require('fs');
208
- const filename = `${selectedCluster.id}-export.md`;
209
- fs.writeFileSync(filename, markdown);
210
-
211
- tui.messages.push({
212
- timestamp: new Date().toISOString(),
213
- text: `✓ Exported cluster to ${filename}`,
214
- level: 'success',
215
- });
216
- tui.renderer.renderLogs(tui.messages.slice(-20));
217
- screen.render();
218
- } catch (error) {
219
- tui.messages.push({
220
- timestamp: new Date().toISOString(),
221
- text: `✗ Failed to export cluster: ${error.message}`,
222
- level: 'error',
223
- });
224
- tui.renderer.renderLogs(tui.messages.slice(-20));
225
- screen.render();
226
- }
227
- });
228
-
229
- // Show full logs (spawn zeroshot logs -f in new terminal)
230
- screen.key(['l'], () => {
231
- if (tui.clusters.length === 0) return;
232
- const selectedCluster = tui.clusters[tui.selectedIndex];
233
- if (!selectedCluster) return;
234
-
235
- try {
236
- // Detect terminal emulator
237
- const term =
238
- process.env.TERM_PROGRAM || (process.env.COLORTERM ? 'gnome-terminal' : null) || 'xterm';
239
-
240
- let cmd, args;
241
- if (term === 'iTerm.app' || term === 'Apple_Terminal') {
242
- // macOS
243
- cmd = 'osascript';
244
- args = [
245
- '-e',
246
- `tell application "Terminal" to do script "zeroshot logs ${selectedCluster.id} -f"`,
247
- ];
248
- } else {
249
- // Linux - try common terminal emulators
250
- const terminals = ['gnome-terminal', 'konsole', 'xterm', 'urxvt', 'alacritty', 'kitty'];
251
- cmd =
252
- terminals.find((t) => {
253
- try {
254
- require('child_process').execSync(`which ${t}`, {
255
- stdio: 'ignore',
256
- });
257
- return true;
258
- } catch {
259
- return false;
260
- }
261
- }) || 'xterm';
262
-
263
- if (cmd === 'gnome-terminal' || cmd === 'konsole') {
264
- args = [
265
- '--',
266
- 'bash',
267
- '-c',
268
- `zeroshot logs ${selectedCluster.id} -f; read -p "Press enter to close..."`,
269
- ];
270
- } else {
271
- args = [
272
- '-e',
273
- 'bash',
274
- '-c',
275
- `zeroshot logs ${selectedCluster.id} -f; read -p "Press enter to close..."`,
276
- ];
277
- }
278
- }
279
-
280
- spawn(cmd, args, { detached: true, stdio: 'ignore' });
281
-
282
- tui.messages.push({
283
- timestamp: new Date().toISOString(),
284
- text: `✓ Opened logs for ${selectedCluster.id} in new terminal`,
285
- level: 'success',
286
- });
287
- tui.renderer.renderLogs(tui.messages.slice(-20));
288
- screen.render();
289
- } catch (error) {
290
- tui.messages.push({
291
- timestamp: new Date().toISOString(),
292
- text: `✗ Failed to open logs: ${error.message}`,
293
- level: 'error',
294
- });
295
- tui.renderer.renderLogs(tui.messages.slice(-20));
296
- screen.render();
297
- }
298
- });
299
-
300
- // Force refresh
301
- screen.key(['r'], () => {
302
- tui.messages.push({
303
- timestamp: new Date().toISOString(),
304
- text: '↻ Refreshing...',
305
- level: 'info',
306
- });
307
- tui.renderer.renderLogs(tui.messages.slice(-20));
308
- screen.render();
309
-
310
- // Trigger immediate poll
311
- if (tui.poller) {
312
- tui.poller.poll();
313
- }
314
- });
315
-
316
- // Exit (with confirmation)
317
- screen.key(['q', 'C-c'], () => {
318
- const question = blessed.question({
319
- parent: screen,
320
- border: 'line',
321
- height: 'shrink',
322
- width: 'half',
323
- top: 'center',
324
- left: 'center',
325
- label: ' {bold}Confirm Exit{/bold} ',
326
- tags: true,
327
- keys: true,
328
- vi: true,
329
- });
330
-
331
- question.ask('Exit TUI?\n\n(Clusters will continue running)', (err, value) => {
332
- if (err) return;
333
- if (value) {
334
- tui.exit();
335
- }
336
- });
337
- });
338
-
339
- // Help
340
- screen.key(['?', 'h'], () => {
341
- const helpBox = blessed.box({
342
- parent: screen,
343
- border: 'line',
344
- height: '80%',
345
- width: '60%',
346
- top: 'center',
347
- left: 'center',
348
- label: ' {bold}Keybindings{/bold} ',
349
- tags: true,
350
- keys: true,
351
- vi: true,
352
- scrollable: true,
353
- alwaysScroll: true,
354
- content: `
355
- {bold}Navigation:{/bold}
356
- ↑/k Move selection up
357
- ↓/j Move selection down
358
-
359
- {bold}Actions:{/bold}
360
- K Kill selected cluster (force stop)
361
- s Stop selected cluster (graceful)
362
- e Export selected cluster to markdown
363
- l Show full logs in new terminal
364
- r Force refresh
365
-
366
- {bold}Other:{/bold}
367
- ?/h Show this help
368
- q/Ctrl-C Exit TUI
369
-
370
- Press any key to close...
371
- `.trim(),
372
- });
373
-
374
- helpBox.key(['escape', 'q', 'enter', 'space'], () => {
375
- helpBox.destroy();
376
- screen.render();
377
- });
378
-
379
- screen.render();
380
- });
381
- }
382
-
383
- module.exports = { setupKeybindings };