@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.
- package/LICENSE +1 -1
- package/README.md +8 -465
- package/cli/index.js +101 -5231
- package/package.json +16 -130
- package/CHANGELOG.md +0 -537
- package/cli/commands/providers.js +0 -149
- package/cli/lib/first-run.js +0 -211
- package/cli/lib/update-checker.js +0 -281
- package/cli/message-formatter-utils.js +0 -75
- package/cli/message-formatters-normal.js +0 -275
- package/cli/message-formatters-watch.js +0 -181
- package/cluster-templates/base-templates/debug-workflow.json +0 -375
- package/cluster-templates/base-templates/full-workflow.json +0 -581
- package/cluster-templates/base-templates/single-worker.json +0 -63
- package/cluster-templates/base-templates/worker-validator.json +0 -208
- package/cluster-templates/conductor-bootstrap.json +0 -124
- package/docker/zeroshot-cluster/Dockerfile +0 -152
- package/docker/zeroshot-cluster/pre-baked-deps.json +0 -28
- package/lib/completion.js +0 -174
- package/lib/docker-config.js +0 -220
- package/lib/git-remote-utils.js +0 -165
- package/lib/id-detector.js +0 -56
- package/lib/provider-defaults.js +0 -62
- package/lib/provider-detection.js +0 -59
- package/lib/provider-names.js +0 -57
- package/lib/settings/claude-auth.js +0 -78
- package/lib/settings.js +0 -503
- package/lib/stream-json-parser.js +0 -10
- package/scripts/fix-node-pty-permissions.js +0 -75
- package/scripts/record-demo.sh +0 -279
- package/scripts/setup-merge-queue.sh +0 -170
- package/scripts/test-install.sh +0 -40
- package/scripts/validate-templates.js +0 -100
- package/src/agent/agent-config.js +0 -246
- package/src/agent/agent-context-builder.js +0 -444
- package/src/agent/agent-hook-executor.js +0 -599
- package/src/agent/agent-lifecycle.js +0 -743
- package/src/agent/agent-stuck-detector.js +0 -256
- package/src/agent/agent-task-executor.js +0 -1690
- package/src/agent/agent-trigger-evaluator.js +0 -67
- package/src/agent/output-extraction.js +0 -217
- package/src/agent/output-reformatter.js +0 -175
- package/src/agent/schema-utils.js +0 -146
- package/src/agent-wrapper.js +0 -579
- package/src/agents/git-pusher-template.js +0 -285
- package/src/attach/attach-client.js +0 -438
- package/src/attach/attach-server.js +0 -543
- package/src/attach/index.js +0 -35
- package/src/attach/protocol.js +0 -220
- package/src/attach/ring-buffer.js +0 -121
- package/src/attach/socket-discovery.js +0 -267
- package/src/claude-task-runner.js +0 -574
- package/src/config-router.js +0 -80
- package/src/config-validator.js +0 -1925
- package/src/copy-worker.js +0 -43
- package/src/input-helpers.js +0 -65
- package/src/isolation-manager.js +0 -1471
- package/src/issue-providers/README.md +0 -305
- package/src/issue-providers/azure-devops-provider.js +0 -273
- package/src/issue-providers/base-provider.js +0 -232
- package/src/issue-providers/github-provider.js +0 -179
- package/src/issue-providers/gitlab-provider.js +0 -241
- package/src/issue-providers/index.js +0 -196
- package/src/issue-providers/jira-provider.js +0 -239
- package/src/ledger.js +0 -631
- package/src/lib/safe-exec.js +0 -88
- package/src/logic-engine.js +0 -223
- package/src/message-bus-bridge.js +0 -139
- package/src/message-bus.js +0 -249
- package/src/name-generator.js +0 -232
- package/src/orchestrator.js +0 -2815
- package/src/preflight.js +0 -639
- package/src/process-metrics.js +0 -608
- package/src/providers/anthropic/cli-builder.js +0 -73
- package/src/providers/anthropic/index.js +0 -204
- package/src/providers/anthropic/models.js +0 -23
- package/src/providers/anthropic/output-parser.js +0 -177
- package/src/providers/base-provider.js +0 -251
- package/src/providers/capabilities.js +0 -60
- package/src/providers/google/cli-builder.js +0 -55
- package/src/providers/google/index.js +0 -116
- package/src/providers/google/models.js +0 -24
- package/src/providers/google/output-parser.js +0 -101
- package/src/providers/index.js +0 -91
- package/src/providers/openai/cli-builder.js +0 -133
- package/src/providers/openai/index.js +0 -136
- package/src/providers/openai/models.js +0 -21
- package/src/providers/openai/output-parser.js +0 -143
- package/src/providers/opencode/cli-builder.js +0 -42
- package/src/providers/opencode/index.js +0 -103
- package/src/providers/opencode/models.js +0 -55
- package/src/providers/opencode/output-parser.js +0 -122
- package/src/schemas/sub-cluster.js +0 -185
- package/src/status-footer.js +0 -1026
- package/src/sub-cluster-wrapper.js +0 -601
- package/src/task-runner.js +0 -30
- package/src/template-resolver.js +0 -372
- package/src/tui/CHANGES.txt +0 -133
- package/src/tui/LAYOUT.md +0 -261
- package/src/tui/README.txt +0 -192
- package/src/tui/TWO-LEVEL-NAVIGATION.md +0 -186
- package/src/tui/data-poller.js +0 -349
- package/src/tui/demo.js +0 -208
- package/src/tui/formatters.js +0 -129
- package/src/tui/index.js +0 -193
- package/src/tui/keybindings.js +0 -324
- package/src/tui/layout.js +0 -334
- package/src/tui/renderer.js +0 -184
- package/task-lib/attachable-watcher.js +0 -309
- package/task-lib/claude-recovery.js +0 -156
- package/task-lib/commands/clean.js +0 -50
- package/task-lib/commands/episodes.js +0 -105
- package/task-lib/commands/get-log-path.js +0 -23
- package/task-lib/commands/kill.js +0 -32
- package/task-lib/commands/list.js +0 -105
- package/task-lib/commands/logs.js +0 -453
- package/task-lib/commands/resume.js +0 -42
- package/task-lib/commands/run.js +0 -57
- package/task-lib/commands/schedule.js +0 -105
- package/task-lib/commands/scheduler-cmd.js +0 -96
- package/task-lib/commands/schedules.js +0 -148
- package/task-lib/commands/status.js +0 -44
- package/task-lib/commands/unschedule.js +0 -16
- package/task-lib/completion.js +0 -9
- package/task-lib/config.js +0 -8
- package/task-lib/name-generator.js +0 -230
- package/task-lib/package.json +0 -3
- package/task-lib/runner.js +0 -201
- package/task-lib/scheduler.js +0 -252
- package/task-lib/store.js +0 -529
- package/task-lib/tui/formatters.js +0 -215
- package/task-lib/tui/index.js +0 -197
- package/task-lib/tui/layout.js +0 -111
- package/task-lib/tui/renderer.js +0 -129
- package/task-lib/tui.js +0 -425
- package/task-lib/watcher.js +0 -226
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;
|
package/src/tui/keybindings.js
DELETED
|
@@ -1,324 +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 fs = require('fs');
|
|
12
|
-
const { spawn } = require('child_process');
|
|
13
|
-
const { execSync } = require('../lib/safe-exec'); // Enforces timeouts
|
|
14
|
-
|
|
15
|
-
const HELP_TEXT_DETAIL =
|
|
16
|
-
'{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';
|
|
17
|
-
const HELP_TEXT_OVERVIEW =
|
|
18
|
-
'{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';
|
|
19
|
-
|
|
20
|
-
function getSelectedCluster(tui) {
|
|
21
|
-
if (tui.clusters.length === 0) {
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return tui.clusters[tui.selectedIndex] || null;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function pushLogMessage(tui, text, level) {
|
|
29
|
-
tui.messages.push({
|
|
30
|
-
timestamp: new Date().toISOString(),
|
|
31
|
-
text,
|
|
32
|
-
level,
|
|
33
|
-
});
|
|
34
|
-
tui.renderer.renderLogs(tui.messages.slice(-20));
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function enterDetailView(screen, widgets, tui) {
|
|
38
|
-
if (tui.viewMode !== 'overview') {
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const selectedCluster = getSelectedCluster(tui);
|
|
43
|
-
if (!selectedCluster) {
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
tui.viewMode = 'detail';
|
|
48
|
-
tui.detailClusterId = selectedCluster.id;
|
|
49
|
-
tui.renderer.setSelectedCluster(selectedCluster.id);
|
|
50
|
-
tui.messages = [];
|
|
51
|
-
|
|
52
|
-
widgets.helpBar.setContent(HELP_TEXT_DETAIL);
|
|
53
|
-
widgets.clustersTable.hide();
|
|
54
|
-
widgets.statsBox.hide();
|
|
55
|
-
widgets.agentTable.show();
|
|
56
|
-
widgets.logsBox.show();
|
|
57
|
-
screen.render();
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function exitDetailView(screen, widgets, tui) {
|
|
61
|
-
if (tui.viewMode !== 'detail') {
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
tui.viewMode = 'overview';
|
|
66
|
-
tui.detailClusterId = null;
|
|
67
|
-
tui.renderer.setSelectedCluster(null);
|
|
68
|
-
tui.messages = [];
|
|
69
|
-
|
|
70
|
-
widgets.helpBar.setContent(HELP_TEXT_OVERVIEW);
|
|
71
|
-
widgets.clustersTable.show();
|
|
72
|
-
widgets.statsBox.show();
|
|
73
|
-
widgets.agentTable.hide();
|
|
74
|
-
widgets.logsBox.hide();
|
|
75
|
-
screen.render();
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function moveSelection(screen, tui, orchestrator, delta) {
|
|
79
|
-
if (tui.clusters.length === 0) {
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
tui.selectedIndex = Math.min(tui.clusters.length - 1, Math.max(0, tui.selectedIndex + delta));
|
|
84
|
-
tui.renderer.renderClustersTable(tui.clusters, tui.selectedIndex);
|
|
85
|
-
|
|
86
|
-
const selectedCluster = tui.clusters[tui.selectedIndex];
|
|
87
|
-
if (selectedCluster) {
|
|
88
|
-
tui.renderer.setSelectedCluster(selectedCluster.id);
|
|
89
|
-
tui.messages = [];
|
|
90
|
-
|
|
91
|
-
const status = orchestrator.getStatus(selectedCluster.id);
|
|
92
|
-
tui.renderer.renderAgentTable(status.agents, tui.resourceStats);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
screen.render();
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function createConfirmationDialog(screen, label, color) {
|
|
99
|
-
const labelText = color
|
|
100
|
-
? ` {bold}{${color}-fg}${label}{/${color}-fg}{/bold} `
|
|
101
|
-
: ` {bold}${label}{/bold} `;
|
|
102
|
-
|
|
103
|
-
return blessed.question({
|
|
104
|
-
parent: screen,
|
|
105
|
-
border: 'line',
|
|
106
|
-
height: 'shrink',
|
|
107
|
-
width: 'half',
|
|
108
|
-
top: 'center',
|
|
109
|
-
left: 'center',
|
|
110
|
-
label: labelText,
|
|
111
|
-
tags: true,
|
|
112
|
-
keys: true,
|
|
113
|
-
vi: true,
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function confirmClusterAction(options) {
|
|
118
|
-
const { screen, tui, selectedCluster, label, color, prompt, action, successText, failureText } =
|
|
119
|
-
options;
|
|
120
|
-
const question = createConfirmationDialog(screen, label, color);
|
|
121
|
-
|
|
122
|
-
question.ask(prompt, async (err, value) => {
|
|
123
|
-
if (err || !value) {
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
try {
|
|
128
|
-
await action(selectedCluster);
|
|
129
|
-
pushLogMessage(tui, successText(selectedCluster), 'success');
|
|
130
|
-
} catch (error) {
|
|
131
|
-
pushLogMessage(tui, failureText(error), 'error');
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
screen.render();
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function handleKillCluster(screen, tui, orchestrator) {
|
|
139
|
-
const selectedCluster = getSelectedCluster(tui);
|
|
140
|
-
if (!selectedCluster) {
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
confirmClusterAction({
|
|
145
|
-
screen,
|
|
146
|
-
tui,
|
|
147
|
-
selectedCluster,
|
|
148
|
-
label: 'Confirm Kill',
|
|
149
|
-
color: 'red',
|
|
150
|
-
prompt: `Kill cluster ${selectedCluster.id}?\n\n(This will force-stop all agents)`,
|
|
151
|
-
action: (cluster) => orchestrator.kill(cluster.id),
|
|
152
|
-
successText: (cluster) => `✓ Killed cluster ${cluster.id}`,
|
|
153
|
-
failureText: (error) => `✗ Failed to kill cluster: ${error.message}`,
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function handleStopCluster(screen, tui, orchestrator) {
|
|
158
|
-
const selectedCluster = getSelectedCluster(tui);
|
|
159
|
-
if (!selectedCluster) {
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
confirmClusterAction({
|
|
164
|
-
screen,
|
|
165
|
-
tui,
|
|
166
|
-
selectedCluster,
|
|
167
|
-
label: 'Confirm Stop',
|
|
168
|
-
color: 'yellow',
|
|
169
|
-
prompt: `Stop cluster ${selectedCluster.id}?\n\n(This will gracefully stop all agents)`,
|
|
170
|
-
action: (cluster) => orchestrator.stop(cluster.id),
|
|
171
|
-
successText: (cluster) => `✓ Stopped cluster ${cluster.id}`,
|
|
172
|
-
failureText: (error) => `✗ Failed to stop cluster: ${error.message}`,
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function handleExportCluster(screen, tui, orchestrator) {
|
|
177
|
-
const selectedCluster = getSelectedCluster(tui);
|
|
178
|
-
if (!selectedCluster) {
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
try {
|
|
183
|
-
const markdown = orchestrator.export(selectedCluster.id, 'markdown');
|
|
184
|
-
const filename = `${selectedCluster.id}-export.md`;
|
|
185
|
-
fs.writeFileSync(filename, markdown);
|
|
186
|
-
pushLogMessage(tui, `✓ Exported cluster to ${filename}`, 'success');
|
|
187
|
-
} catch (error) {
|
|
188
|
-
pushLogMessage(tui, `✗ Failed to export cluster: ${error.message}`, 'error');
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
screen.render();
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function findTerminalCommand() {
|
|
195
|
-
const terminals = ['gnome-terminal', 'konsole', 'xterm', 'urxvt', 'alacritty', 'kitty'];
|
|
196
|
-
|
|
197
|
-
for (const terminal of terminals) {
|
|
198
|
-
try {
|
|
199
|
-
execSync(`which ${terminal}`, { stdio: 'ignore' });
|
|
200
|
-
return terminal;
|
|
201
|
-
} catch {
|
|
202
|
-
// Ignore missing terminal
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return 'xterm';
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function buildLogCommand(clusterId) {
|
|
210
|
-
const term =
|
|
211
|
-
process.env.TERM_PROGRAM || (process.env.COLORTERM ? 'gnome-terminal' : null) || 'xterm';
|
|
212
|
-
|
|
213
|
-
if (term === 'iTerm.app' || term === 'Apple_Terminal') {
|
|
214
|
-
return {
|
|
215
|
-
cmd: 'osascript',
|
|
216
|
-
args: ['-e', `tell application "Terminal" to do script "zeroshot logs ${clusterId} -f"`],
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
const cmd = findTerminalCommand();
|
|
221
|
-
const logCommand = `zeroshot logs ${clusterId} -f; read -p "Press enter to close..."`;
|
|
222
|
-
|
|
223
|
-
if (cmd === 'gnome-terminal' || cmd === 'konsole') {
|
|
224
|
-
return { cmd, args: ['--', 'bash', '-c', logCommand] };
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
return { cmd, args: ['-e', 'bash', '-c', logCommand] };
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
function handleOpenLogs(screen, tui) {
|
|
231
|
-
const selectedCluster = getSelectedCluster(tui);
|
|
232
|
-
if (!selectedCluster) {
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
try {
|
|
237
|
-
const { cmd, args } = buildLogCommand(selectedCluster.id);
|
|
238
|
-
spawn(cmd, args, { detached: true, stdio: 'ignore' });
|
|
239
|
-
pushLogMessage(tui, `✓ Opened logs for ${selectedCluster.id} in new terminal`, 'success');
|
|
240
|
-
} catch (error) {
|
|
241
|
-
pushLogMessage(tui, `✗ Failed to open logs: ${error.message}`, 'error');
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
screen.render();
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
function handleRefresh(screen, tui) {
|
|
248
|
-
pushLogMessage(tui, '↻ Refreshing...', 'info');
|
|
249
|
-
screen.render();
|
|
250
|
-
|
|
251
|
-
if (tui.poller) {
|
|
252
|
-
tui.poller.poll();
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
function handleExit(screen, tui) {
|
|
257
|
-
const question = createConfirmationDialog(screen, 'Confirm Exit');
|
|
258
|
-
|
|
259
|
-
question.ask('Exit TUI?\n\n(Clusters will continue running)', (err, value) => {
|
|
260
|
-
if (err || !value) {
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
tui.exit();
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
function handleHelp(screen) {
|
|
269
|
-
const helpBox = blessed.box({
|
|
270
|
-
parent: screen,
|
|
271
|
-
border: 'line',
|
|
272
|
-
height: '80%',
|
|
273
|
-
width: '60%',
|
|
274
|
-
top: 'center',
|
|
275
|
-
left: 'center',
|
|
276
|
-
label: ' {bold}Keybindings{/bold} ',
|
|
277
|
-
tags: true,
|
|
278
|
-
keys: true,
|
|
279
|
-
vi: true,
|
|
280
|
-
scrollable: true,
|
|
281
|
-
alwaysScroll: true,
|
|
282
|
-
content: `
|
|
283
|
-
{bold}Navigation:{/bold}
|
|
284
|
-
↑/k Move selection up
|
|
285
|
-
↓/j Move selection down
|
|
286
|
-
|
|
287
|
-
{bold}Actions:{/bold}
|
|
288
|
-
K Kill selected cluster (force stop)
|
|
289
|
-
s Stop selected cluster (graceful)
|
|
290
|
-
e Export selected cluster to markdown
|
|
291
|
-
l Show full logs in new terminal
|
|
292
|
-
r Force refresh
|
|
293
|
-
|
|
294
|
-
{bold}Other:{/bold}
|
|
295
|
-
?/h Show this help
|
|
296
|
-
q/Ctrl-C Exit TUI
|
|
297
|
-
|
|
298
|
-
Press any key to close...
|
|
299
|
-
`.trim(),
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
helpBox.key(['escape', 'q', 'enter', 'space'], () => {
|
|
303
|
-
helpBox.destroy();
|
|
304
|
-
screen.render();
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
screen.render();
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
function setupKeybindings(screen, widgets, tui, orchestrator) {
|
|
311
|
-
screen.key(['enter'], () => enterDetailView(screen, widgets, tui));
|
|
312
|
-
screen.key(['escape'], () => exitDetailView(screen, widgets, tui));
|
|
313
|
-
screen.key(['up', 'k'], () => moveSelection(screen, tui, orchestrator, -1));
|
|
314
|
-
screen.key(['down', 'j'], () => moveSelection(screen, tui, orchestrator, 1));
|
|
315
|
-
screen.key(['K'], () => handleKillCluster(screen, tui, orchestrator));
|
|
316
|
-
screen.key(['s'], () => handleStopCluster(screen, tui, orchestrator));
|
|
317
|
-
screen.key(['e'], () => handleExportCluster(screen, tui, orchestrator));
|
|
318
|
-
screen.key(['l'], () => handleOpenLogs(screen, tui));
|
|
319
|
-
screen.key(['r'], () => handleRefresh(screen, tui));
|
|
320
|
-
screen.key(['q', 'C-c'], () => handleExit(screen, tui));
|
|
321
|
-
screen.key(['?', 'h'], () => handleHelp(screen));
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
module.exports = { setupKeybindings };
|