@covibes/zeroshot 5.3.0 → 5.4.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 +94 -14
- package/cli/commands/providers.js +8 -9
- package/cli/index.js +3032 -2409
- package/cli/message-formatters-normal.js +28 -6
- package/cluster-templates/base-templates/debug-workflow.json +1 -1
- package/cluster-templates/base-templates/full-workflow.json +72 -188
- package/cluster-templates/base-templates/worker-validator.json +59 -3
- package/cluster-templates/conductor-bootstrap.json +4 -4
- package/lib/docker-config.js +8 -0
- package/lib/git-remote-utils.js +165 -0
- package/lib/id-detector.js +10 -7
- package/lib/provider-defaults.js +62 -0
- package/lib/provider-names.js +2 -1
- package/lib/settings/claude-auth.js +78 -0
- package/lib/settings.js +161 -63
- package/package.json +7 -2
- package/scripts/setup-merge-queue.sh +170 -0
- package/src/agent/agent-config.js +135 -82
- package/src/agent/agent-context-builder.js +297 -188
- package/src/agent/agent-hook-executor.js +310 -113
- package/src/agent/agent-lifecycle.js +385 -325
- package/src/agent/agent-stuck-detector.js +7 -7
- package/src/agent/agent-task-executor.js +824 -565
- package/src/agent/output-extraction.js +41 -24
- package/src/agent/output-reformatter.js +1 -1
- package/src/agent/schema-utils.js +108 -73
- package/src/agent-wrapper.js +10 -1
- package/src/agents/git-pusher-template.js +285 -0
- package/src/claude-task-runner.js +85 -34
- package/src/config-validator.js +922 -657
- package/src/input-helpers.js +65 -0
- package/src/isolation-manager.js +289 -199
- package/src/issue-providers/README.md +305 -0
- package/src/issue-providers/azure-devops-provider.js +273 -0
- package/src/issue-providers/base-provider.js +232 -0
- package/src/issue-providers/github-provider.js +179 -0
- package/src/issue-providers/gitlab-provider.js +241 -0
- package/src/issue-providers/index.js +196 -0
- package/src/issue-providers/jira-provider.js +239 -0
- package/src/ledger.js +22 -5
- package/src/lib/safe-exec.js +88 -0
- package/src/orchestrator.js +1107 -811
- package/src/preflight.js +313 -159
- package/src/process-metrics.js +98 -56
- package/src/providers/anthropic/cli-builder.js +53 -25
- package/src/providers/anthropic/index.js +72 -2
- package/src/providers/anthropic/output-parser.js +32 -14
- package/src/providers/base-provider.js +70 -0
- package/src/providers/capabilities.js +9 -0
- package/src/providers/google/output-parser.js +47 -38
- package/src/providers/index.js +19 -3
- package/src/providers/openai/cli-builder.js +14 -3
- package/src/providers/openai/index.js +1 -0
- package/src/providers/openai/output-parser.js +44 -30
- package/src/providers/opencode/cli-builder.js +42 -0
- package/src/providers/opencode/index.js +103 -0
- package/src/providers/opencode/models.js +55 -0
- package/src/providers/opencode/output-parser.js +122 -0
- package/src/schemas/sub-cluster.js +68 -39
- package/src/status-footer.js +94 -48
- package/src/sub-cluster-wrapper.js +76 -35
- package/src/template-resolver.js +12 -9
- package/src/tui/data-poller.js +123 -99
- package/src/tui/formatters.js +4 -3
- package/src/tui/keybindings.js +259 -318
- package/src/tui/renderer.js +11 -21
- package/task-lib/attachable-watcher.js +118 -81
- package/task-lib/claude-recovery.js +61 -24
- package/task-lib/commands/episodes.js +105 -0
- package/task-lib/commands/list.js +2 -2
- package/task-lib/commands/logs.js +231 -189
- package/task-lib/commands/schedules.js +111 -61
- package/task-lib/config.js +0 -2
- package/task-lib/runner.js +84 -27
- package/task-lib/scheduler.js +1 -1
- package/task-lib/store.js +467 -168
- package/task-lib/tui/formatters.js +94 -45
- package/task-lib/tui/renderer.js +13 -3
- package/task-lib/tui.js +73 -32
- package/task-lib/watcher.js +128 -90
- package/src/agents/git-pusher-agent.json +0 -20
- package/src/github.js +0 -139
package/src/tui/keybindings.js
CHANGED
|
@@ -8,350 +8,278 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
const blessed = require('blessed');
|
|
11
|
+
const fs = require('fs');
|
|
11
12
|
const { spawn } = require('child_process');
|
|
13
|
+
const { execSync } = require('../lib/safe-exec'); // Enforces timeouts
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
});
|
|
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';
|
|
38
19
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
}
|
|
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,
|
|
59
33
|
});
|
|
34
|
+
tui.renderer.renderLogs(tui.messages.slice(-20));
|
|
35
|
+
}
|
|
60
36
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
+
}
|
|
66
59
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
+
}
|
|
72
77
|
|
|
73
|
-
|
|
74
|
-
|
|
78
|
+
function moveSelection(screen, tui, orchestrator, delta) {
|
|
79
|
+
if (tui.clusters.length === 0) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
75
82
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
83
|
+
tui.selectedIndex = Math.min(tui.clusters.length - 1, Math.max(0, tui.selectedIndex + delta));
|
|
84
|
+
tui.renderer.renderClustersTable(tui.clusters, tui.selectedIndex);
|
|
79
85
|
|
|
80
|
-
|
|
81
|
-
|
|
86
|
+
const selectedCluster = tui.clusters[tui.selectedIndex];
|
|
87
|
+
if (selectedCluster) {
|
|
88
|
+
tui.renderer.setSelectedCluster(selectedCluster.id);
|
|
89
|
+
tui.messages = [];
|
|
82
90
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
tui.selectedIndex = Math.min(tui.clusters.length - 1, tui.selectedIndex + 1);
|
|
87
|
-
tui.renderer.renderClustersTable(tui.clusters, tui.selectedIndex);
|
|
91
|
+
const status = orchestrator.getStatus(selectedCluster.id);
|
|
92
|
+
tui.renderer.renderAgentTable(status.agents, tui.resourceStats);
|
|
93
|
+
}
|
|
88
94
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
+
}
|
|
94
116
|
|
|
95
|
-
|
|
96
|
-
|
|
117
|
+
function confirmClusterAction(options) {
|
|
118
|
+
const { screen, tui, selectedCluster, label, color, prompt, action, successText, failureText } =
|
|
119
|
+
options;
|
|
120
|
+
const question = createConfirmationDialog(screen, label, color);
|
|
97
121
|
|
|
98
|
-
|
|
99
|
-
|
|
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');
|
|
100
132
|
}
|
|
101
133
|
|
|
102
134
|
screen.render();
|
|
103
135
|
});
|
|
136
|
+
}
|
|
104
137
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
);
|
|
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}`,
|
|
150
154
|
});
|
|
155
|
+
}
|
|
151
156
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
);
|
|
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}`,
|
|
197
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
|
+
}
|
|
198
193
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
if (tui.clusters.length === 0) return;
|
|
202
|
-
const selectedCluster = tui.clusters[tui.selectedIndex];
|
|
203
|
-
if (!selectedCluster) return;
|
|
194
|
+
function findTerminalCommand() {
|
|
195
|
+
const terminals = ['gnome-terminal', 'konsole', 'xterm', 'urxvt', 'alacritty', 'kitty'];
|
|
204
196
|
|
|
197
|
+
for (const terminal of terminals) {
|
|
205
198
|
try {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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();
|
|
199
|
+
execSync(`which ${terminal}`, { stdio: 'ignore' });
|
|
200
|
+
return terminal;
|
|
201
|
+
} catch {
|
|
202
|
+
// Ignore missing terminal
|
|
226
203
|
}
|
|
227
|
-
}
|
|
204
|
+
}
|
|
228
205
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
if (tui.clusters.length === 0) return;
|
|
232
|
-
const selectedCluster = tui.clusters[tui.selectedIndex];
|
|
233
|
-
if (!selectedCluster) return;
|
|
206
|
+
return 'xterm';
|
|
207
|
+
}
|
|
234
208
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
-
});
|
|
209
|
+
function buildLogCommand(clusterId) {
|
|
210
|
+
const term =
|
|
211
|
+
process.env.TERM_PROGRAM || (process.env.COLORTERM ? 'gnome-terminal' : null) || 'xterm';
|
|
299
212
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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
|
+
}
|
|
309
255
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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;
|
|
313
262
|
}
|
|
314
|
-
});
|
|
315
263
|
|
|
316
|
-
|
|
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
|
-
});
|
|
264
|
+
tui.exit();
|
|
337
265
|
});
|
|
266
|
+
}
|
|
338
267
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
content: `
|
|
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: `
|
|
355
283
|
{bold}Navigation:{/bold}
|
|
356
284
|
↑/k Move selection up
|
|
357
285
|
↓/j Move selection down
|
|
@@ -368,16 +296,29 @@ function setupKeybindings(screen, widgets, tui, orchestrator) {
|
|
|
368
296
|
q/Ctrl-C Exit TUI
|
|
369
297
|
|
|
370
298
|
Press any key to close...
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
helpBox.key(['escape', 'q', 'enter', 'space'], () => {
|
|
375
|
-
helpBox.destroy();
|
|
376
|
-
screen.render();
|
|
377
|
-
});
|
|
299
|
+
`.trim(),
|
|
300
|
+
});
|
|
378
301
|
|
|
302
|
+
helpBox.key(['escape', 'q', 'enter', 'space'], () => {
|
|
303
|
+
helpBox.destroy();
|
|
379
304
|
screen.render();
|
|
380
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));
|
|
381
322
|
}
|
|
382
323
|
|
|
383
324
|
module.exports = { setupKeybindings };
|