@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,309 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Attachable Watcher - spawns a CLI process with PTY for attach/detach support
5
- * Runs detached from parent, provides Unix socket for attach clients.
6
- */
7
-
8
- import { appendFileSync, existsSync, mkdirSync } from 'fs';
9
- import { join } from 'path';
10
- import { homedir } from 'os';
11
- import { updateTask } from './store.js';
12
- import { detectStreamingModeError, recoverStructuredOutput } from './claude-recovery.js';
13
- import { createRequire } from 'module';
14
-
15
- // ═══════════════════════════════════════════════════════════════════════════
16
- // 🔴 CRITICAL: Global error handlers - MUST be installed BEFORE any async ops
17
- // Without these, uncaught errors cause SILENT process death (no logs, no status)
18
- // ═══════════════════════════════════════════════════════════════════════════
19
-
20
- const [, , taskIdArg, cwdArg, logFileArg, argsJsonArg, configJsonArg] = process.argv;
21
-
22
- function emergencyLog(msg) {
23
- if (logFileArg) {
24
- try {
25
- appendFileSync(logFileArg, msg);
26
- } catch {
27
- process.stderr.write(msg);
28
- }
29
- } else {
30
- process.stderr.write(msg);
31
- }
32
- }
33
-
34
- function crashWithError(error, source) {
35
- const timestamp = Date.now();
36
- const errorMsg = error instanceof Error ? error.stack || error.message : String(error);
37
-
38
- emergencyLog(`\n[${timestamp}][CRASH] ${source}: ${errorMsg}\n`);
39
- emergencyLog(`[${timestamp}][CRASH] Process terminating due to unhandled error\n`);
40
-
41
- if (taskIdArg) {
42
- try {
43
- updateTask(taskIdArg, {
44
- status: 'failed',
45
- error: `${source}: ${errorMsg}`,
46
- socketPath: null,
47
- });
48
- } catch (updateError) {
49
- emergencyLog(`[${timestamp}][CRASH] Failed to update task status: ${updateError.message}\n`);
50
- }
51
- }
52
-
53
- process.exit(1);
54
- }
55
-
56
- process.on('uncaughtException', (error) => {
57
- crashWithError(error, 'uncaughtException');
58
- });
59
-
60
- process.on('unhandledRejection', (reason) => {
61
- crashWithError(reason, 'unhandledRejection');
62
- });
63
-
64
- const require = createRequire(import.meta.url);
65
- const { AttachServer } = require('../src/attach');
66
- const { normalizeProviderName } = require('../lib/provider-names');
67
-
68
- const taskId = taskIdArg;
69
- const cwd = cwdArg;
70
- const logFile = logFileArg;
71
- const args = JSON.parse(argsJsonArg);
72
- const config = configJsonArg ? JSON.parse(configJsonArg) : {};
73
-
74
- const SOCKET_DIR = join(homedir(), '.zeroshot', 'sockets');
75
- const socketPath = join(SOCKET_DIR, `${taskId}.sock`);
76
-
77
- if (!existsSync(SOCKET_DIR)) {
78
- mkdirSync(SOCKET_DIR, { recursive: true });
79
- }
80
-
81
- function log(msg) {
82
- appendFileSync(logFile, msg);
83
- }
84
-
85
- const providerName = normalizeProviderName(config.provider || 'claude');
86
- const enableRecovery = providerName === 'claude';
87
-
88
- const env = { ...process.env, ...(config.env || {}) };
89
- const command = config.command || 'claude';
90
- const finalArgs = [...args];
91
-
92
- const silentJsonMode =
93
- config.outputFormat === 'json' && config.jsonSchema && config.silentJsonOutput && enableRecovery;
94
-
95
- let finalResultJson = null;
96
- let outputBuffer = '';
97
- let streamingModeError = null;
98
-
99
- function splitBufferLines(buffer, chunk) {
100
- const nextBuffer = buffer + chunk;
101
- const lines = nextBuffer.split('\n');
102
- const remaining = lines.pop() || '';
103
- return { lines, remaining };
104
- }
105
-
106
- function captureStreamingError(line, timestamp) {
107
- if (!enableRecovery) {
108
- return false;
109
- }
110
-
111
- const detectedError = detectStreamingModeError(line);
112
- if (!detectedError) {
113
- return false;
114
- }
115
-
116
- streamingModeError = { ...detectedError, timestamp };
117
- return true;
118
- }
119
-
120
- function maybeCaptureStructuredOutput(line) {
121
- try {
122
- const json = JSON.parse(line);
123
- if (json.structured_output) {
124
- finalResultJson = line;
125
- }
126
- } catch {
127
- // Not JSON, skip
128
- }
129
- }
130
-
131
- function handleSilentJsonLines(lines, timestamp) {
132
- for (const line of lines) {
133
- if (!line.trim()) continue;
134
- if (captureStreamingError(line, timestamp)) {
135
- continue;
136
- }
137
- maybeCaptureStructuredOutput(line);
138
- }
139
- }
140
-
141
- function handleStreamingLines(lines, timestamp) {
142
- for (const line of lines) {
143
- if (captureStreamingError(line, timestamp)) {
144
- continue;
145
- }
146
- log(`[${timestamp}]${line}\n`);
147
- }
148
- }
149
-
150
- function flushOutputBuffer(timestamp) {
151
- if (!outputBuffer.trim()) {
152
- return;
153
- }
154
-
155
- if (!enableRecovery) {
156
- if (!silentJsonMode) {
157
- log(`[${timestamp}]${outputBuffer}\n`);
158
- }
159
- return;
160
- }
161
-
162
- if (captureStreamingError(outputBuffer, timestamp)) {
163
- return;
164
- }
165
-
166
- if (silentJsonMode) {
167
- maybeCaptureStructuredOutput(outputBuffer);
168
- return;
169
- }
170
-
171
- log(`[${timestamp}]${outputBuffer}\n`);
172
- }
173
-
174
- function attemptRecovery(code, timestamp) {
175
- if (!(enableRecovery && code !== 0 && streamingModeError?.sessionId)) {
176
- return null;
177
- }
178
-
179
- const recovered = recoverStructuredOutput(streamingModeError.sessionId);
180
- if (recovered?.payload) {
181
- const recoveredLine = JSON.stringify(recovered.payload);
182
- if (silentJsonMode) {
183
- finalResultJson = recoveredLine;
184
- } else {
185
- log(`[${timestamp}]${recoveredLine}\n`);
186
- }
187
- } else if (streamingModeError.line) {
188
- if (silentJsonMode) {
189
- log(streamingModeError.line + '\n');
190
- } else {
191
- log(`[${streamingModeError.timestamp}]${streamingModeError.line}\n`);
192
- }
193
- }
194
-
195
- return recovered;
196
- }
197
-
198
- function writeCompletionFooter(code, signal) {
199
- if (config.outputFormat === 'json') {
200
- return;
201
- }
202
-
203
- log(`\n${'='.repeat(50)}\n`);
204
- log(`Finished: ${new Date().toISOString()}\n`);
205
- log(`Exit code: ${code}, Signal: ${signal}\n`);
206
- }
207
-
208
- const server = new AttachServer({
209
- id: taskId,
210
- socketPath,
211
- command,
212
- args: finalArgs,
213
- cwd,
214
- env,
215
- cols: 120,
216
- rows: 30,
217
- });
218
-
219
- server.on('output', (data) => {
220
- const chunk = data.toString();
221
- const timestamp = Date.now();
222
-
223
- const { lines, remaining } = splitBufferLines(outputBuffer, chunk);
224
- outputBuffer = remaining;
225
-
226
- if (silentJsonMode) {
227
- handleSilentJsonLines(lines, timestamp);
228
- } else {
229
- handleStreamingLines(lines, timestamp);
230
- }
231
- });
232
-
233
- server.on('exit', async ({ exitCode, signal }) => {
234
- const timestamp = Date.now();
235
- const code = exitCode;
236
-
237
- flushOutputBuffer(timestamp);
238
-
239
- const recovered = attemptRecovery(code, timestamp);
240
-
241
- if (silentJsonMode && finalResultJson) {
242
- log(finalResultJson + '\n');
243
- }
244
-
245
- writeCompletionFooter(code, signal);
246
-
247
- const resolvedCode = recovered?.payload ? 0 : code;
248
- const status = resolvedCode === 0 ? 'completed' : 'failed';
249
- try {
250
- await updateTask(taskId, {
251
- status,
252
- exitCode: resolvedCode,
253
- error: resolvedCode !== 0 && signal ? `Killed by ${signal}` : null,
254
- socketPath: null,
255
- });
256
- } catch (updateError) {
257
- log(`[${Date.now()}][ERROR] Failed to update task status: ${updateError.message}\n`);
258
- }
259
-
260
- setTimeout(() => {
261
- process.exit(0);
262
- }, 500);
263
- });
264
-
265
- server.on('error', async (err) => {
266
- log(`\nError: ${err.message}\n`);
267
- try {
268
- await updateTask(taskId, { status: 'failed', error: err.message });
269
- } catch (updateError) {
270
- log(`[${Date.now()}][ERROR] Failed to update task status: ${updateError.message}\n`);
271
- }
272
- process.exit(1);
273
- });
274
-
275
- server.on('clientAttach', ({ clientId }) => {
276
- log(`[${Date.now()}][ATTACH] Client attached: ${clientId.slice(0, 8)}...\n`);
277
- });
278
-
279
- server.on('clientDetach', ({ clientId }) => {
280
- log(`[${Date.now()}][DETACH] Client detached: ${clientId.slice(0, 8)}...\n`);
281
- });
282
-
283
- try {
284
- await server.start();
285
-
286
- updateTask(taskId, {
287
- pid: server.pid,
288
- socketPath,
289
- attachable: true,
290
- });
291
-
292
- log(`[${Date.now()}][SYSTEM] Started with PTY (attachable)\n`);
293
- log(`[${Date.now()}][SYSTEM] Socket: ${socketPath}\n`);
294
- log(`[${Date.now()}][SYSTEM] PID: ${server.pid}\n`);
295
- } catch (err) {
296
- log(`\nFailed to start: ${err.message}\n`);
297
- updateTask(taskId, { status: 'failed', error: err.message });
298
- process.exit(1);
299
- }
300
-
301
- process.on('SIGTERM', async () => {
302
- log(`[${Date.now()}][SYSTEM] Received SIGTERM, stopping...\n`);
303
- await server.stop('SIGTERM');
304
- });
305
-
306
- process.on('SIGINT', async () => {
307
- log(`[${Date.now()}][SYSTEM] Received SIGINT, stopping...\n`);
308
- await server.stop('SIGINT');
309
- });
@@ -1,156 +0,0 @@
1
- import { existsSync, readdirSync, readFileSync } from 'fs';
2
- import { join } from 'path';
3
- import { homedir } from 'os';
4
-
5
- export const STREAMING_MODE_ERROR = 'only prompt commands are supported in streaming mode';
6
-
7
- export function detectStreamingModeError(line) {
8
- const trimmed = typeof line === 'string' ? line.trim() : '';
9
- if (!trimmed.startsWith('{')) return null;
10
-
11
- try {
12
- const parsed = JSON.parse(trimmed);
13
- if (
14
- parsed &&
15
- parsed.type === 'result' &&
16
- parsed.is_error === true &&
17
- Array.isArray(parsed.errors) &&
18
- parsed.errors.includes(STREAMING_MODE_ERROR) &&
19
- typeof parsed.session_id === 'string'
20
- ) {
21
- return {
22
- sessionId: parsed.session_id,
23
- line: trimmed,
24
- };
25
- }
26
- } catch {
27
- // Ignore parse errors - not JSON
28
- }
29
-
30
- return null;
31
- }
32
-
33
- function findSessionJsonlPath(sessionId) {
34
- const claudeDir = process.env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude');
35
- const projectsDir = join(claudeDir, 'projects');
36
- if (!existsSync(projectsDir)) return null;
37
-
38
- const target = `${sessionId}.jsonl`;
39
- const queue = [projectsDir];
40
-
41
- while (queue.length > 0) {
42
- const dir = queue.pop();
43
- if (!dir) continue;
44
-
45
- let entries;
46
- try {
47
- entries = readdirSync(dir, { withFileTypes: true });
48
- } catch {
49
- continue;
50
- }
51
-
52
- for (const entry of entries) {
53
- if (entry.isFile() && entry.name === target) {
54
- return join(dir, entry.name);
55
- }
56
- if (entry.isDirectory()) {
57
- queue.push(join(dir, entry.name));
58
- }
59
- }
60
- }
61
-
62
- return null;
63
- }
64
-
65
- export function recoverStructuredOutput(sessionId) {
66
- const jsonlPath = findSessionJsonlPath(sessionId);
67
- if (!jsonlPath) return null;
68
-
69
- const fileContents = readJsonlFile(jsonlPath);
70
- if (!fileContents) return null;
71
-
72
- const { structuredOutput, usage } = findStructuredOutput(fileContents);
73
-
74
- if (!structuredOutput) return null;
75
-
76
- return {
77
- payload: buildStructuredOutputPayload(sessionId, structuredOutput, usage),
78
- sourcePath: jsonlPath,
79
- };
80
- }
81
-
82
- function readJsonlFile(jsonlPath) {
83
- try {
84
- return readFileSync(jsonlPath, 'utf8');
85
- } catch {
86
- return null;
87
- }
88
- }
89
-
90
- function findStructuredOutput(fileContents) {
91
- const lines = fileContents.split('\n');
92
- let structuredOutput = null;
93
- let usage = null;
94
-
95
- for (const line of lines) {
96
- const entry = parseJsonLine(line);
97
- if (!entry) {
98
- continue;
99
- }
100
-
101
- const extracted = extractStructuredOutputFromEntry(entry);
102
- if (extracted) {
103
- structuredOutput = extracted.structuredOutput;
104
- usage = extracted.usage;
105
- }
106
- }
107
-
108
- return { structuredOutput, usage };
109
- }
110
-
111
- function parseJsonLine(line) {
112
- if (!line.trim()) return null;
113
- try {
114
- return JSON.parse(line);
115
- } catch {
116
- // Skip invalid JSON lines
117
- return null;
118
- }
119
- }
120
-
121
- function extractStructuredOutputFromEntry(entry) {
122
- const message = entry?.message;
123
- const content = message?.content;
124
- if (!Array.isArray(content)) return null;
125
-
126
- let structuredOutput = null;
127
- let usage = null;
128
- for (const block of content) {
129
- if (block?.type === 'tool_use' && block?.name === 'StructuredOutput' && block?.input) {
130
- structuredOutput = block.input;
131
- usage = message?.usage && typeof message.usage === 'object' ? message.usage : null;
132
- }
133
- }
134
-
135
- if (!structuredOutput) {
136
- return null;
137
- }
138
-
139
- return { structuredOutput, usage };
140
- }
141
-
142
- function buildStructuredOutputPayload(sessionId, structuredOutput, usage) {
143
- const payload = {
144
- type: 'result',
145
- subtype: 'success',
146
- is_error: false,
147
- structured_output: structuredOutput,
148
- session_id: sessionId,
149
- };
150
-
151
- if (usage) {
152
- payload.usage = usage;
153
- }
154
-
155
- return payload;
156
- }
@@ -1,50 +0,0 @@
1
- import { unlinkSync, existsSync } from 'fs';
2
- import chalk from 'chalk';
3
- import { loadTasks, saveTasks } from '../store.js';
4
-
5
- export function cleanTasks(options = {}) {
6
- const tasks = loadTasks();
7
- const taskList = Object.values(tasks);
8
-
9
- if (taskList.length === 0) {
10
- console.log(chalk.dim('No tasks to clean.'));
11
- return;
12
- }
13
-
14
- const toRemove = [];
15
-
16
- for (const task of taskList) {
17
- const shouldRemove =
18
- options.all ||
19
- (options.completed && task.status === 'completed') ||
20
- (options.failed &&
21
- (task.status === 'failed' || task.status === 'stale' || task.status === 'killed'));
22
-
23
- if (shouldRemove) {
24
- toRemove.push(task);
25
- }
26
- }
27
-
28
- if (toRemove.length === 0) {
29
- console.log(chalk.dim('No tasks match the criteria.'));
30
- return;
31
- }
32
-
33
- console.log(chalk.dim(`Removing ${toRemove.length} task(s)...\n`));
34
-
35
- for (const task of toRemove) {
36
- // Delete log file
37
- if (task.logFile && existsSync(task.logFile)) {
38
- unlinkSync(task.logFile);
39
- }
40
-
41
- // Remove from tasks
42
- delete tasks[task.id];
43
-
44
- console.log(chalk.dim(` Removed: ${task.id} [${task.status}]`));
45
- }
46
-
47
- saveTasks(tasks);
48
-
49
- console.log(chalk.green(`\n✓ Cleaned ${toRemove.length} task(s)`));
50
- }
@@ -1,105 +0,0 @@
1
- import chalk from 'chalk';
2
- import { loadTasks } from '../store.js';
3
- import { isProcessRunning } from '../runner.js';
4
-
5
- export function listEpisodes(options = {}) {
6
- const tasks = loadTasks();
7
- const taskList = Object.values(tasks);
8
-
9
- if (taskList.length === 0) {
10
- console.log(chalk.dim('No episodes found.'));
11
- return;
12
- }
13
-
14
- // Sort by creation date, oldest first (chronological order)
15
- taskList.sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt));
16
-
17
- // Filter by status if specified
18
- let filtered = taskList;
19
- if (options.status) {
20
- filtered = taskList.filter((t) => t.status === options.status);
21
- }
22
-
23
- // Limit results
24
- const limit = options.limit || 20;
25
- filtered = filtered.slice(0, limit);
26
-
27
- // Table format (default) or verbose format
28
- if (options.verbose) {
29
- // Verbose format (old behavior)
30
- console.log(chalk.bold(`\nEpisodes (${filtered.length}/${taskList.length})\n`));
31
-
32
- for (const task of filtered) {
33
- // Verify running status
34
- let status = task.status;
35
- if (status === 'running' && !isProcessRunning(task.pid)) {
36
- status = 'stale';
37
- }
38
-
39
- const statusColor =
40
- {
41
- running: chalk.green,
42
- completed: chalk.green,
43
- failed: chalk.red,
44
- stale: chalk.yellow,
45
- }[status] || chalk.dim;
46
-
47
- const age = getAge(task.createdAt);
48
- const timestamp = new Date(task.createdAt).toLocaleString();
49
-
50
- console.log(
51
- `${statusColor('●')} ${chalk.cyan(task.id)} ${statusColor(`[${status}]`)} ${chalk.dim(age + ' • ' + timestamp)}`
52
- );
53
- console.log(` ${chalk.dim('CWD:')} ${task.cwd}`);
54
- console.log(` ${chalk.dim('Prompt:')} ${task.prompt}`);
55
- if (task.pid && status === 'running') {
56
- console.log(` ${chalk.dim('PID:')} ${task.pid}`);
57
- }
58
- if (task.error) {
59
- console.log(` ${chalk.red('Error:')} ${task.error}`);
60
- }
61
- console.log();
62
- }
63
- } else {
64
- // Table format (clean, default)
65
- console.log(chalk.bold(`\n=== Episodes (${filtered.length}/${taskList.length}) ===`));
66
- console.log(`${'ID'.padEnd(25)} ${'Status'.padEnd(12)} ${'Age'.padEnd(10)} CWD`);
67
- console.log('-'.repeat(100));
68
-
69
- for (const task of filtered) {
70
- // Verify running status
71
- let status = task.status;
72
- if (status === 'running' && !isProcessRunning(task.pid)) {
73
- status = 'stale';
74
- }
75
-
76
- const statusColor =
77
- {
78
- running: chalk.green,
79
- completed: chalk.green,
80
- failed: chalk.red,
81
- stale: chalk.yellow,
82
- }[status] || chalk.dim;
83
-
84
- const age = getAge(task.createdAt);
85
- const cwd = task.cwd.replace(process.env.HOME, '~');
86
-
87
- console.log(
88
- `${chalk.cyan(task.id.padEnd(25))} ${statusColor(status.padEnd(12))} ${chalk.dim(age.padEnd(10))} ${chalk.dim(cwd)}`
89
- );
90
- }
91
- console.log();
92
- }
93
- }
94
-
95
- function getAge(dateStr) {
96
- const diff = Date.now() - new Date(dateStr).getTime();
97
- const mins = Math.floor(diff / 60000);
98
- const hours = Math.floor(mins / 60);
99
- const days = Math.floor(hours / 24);
100
-
101
- if (days > 0) return `${days}d ago`;
102
- if (hours > 0) return `${hours}h ago`;
103
- if (mins > 0) return `${mins}m ago`;
104
- return 'just now';
105
- }
@@ -1,23 +0,0 @@
1
- import { getTask } from '../store.js';
2
-
3
- /**
4
- * Get log file path for a task (machine-readable output)
5
- * Used by cluster/agent-wrapper.js to follow task logs
6
- * @param {string} taskId - Task ID
7
- */
8
- export function getLogPath(taskId) {
9
- const task = getTask(taskId);
10
-
11
- if (!task) {
12
- console.error(`Task not found: ${taskId}`);
13
- process.exit(1);
14
- }
15
-
16
- if (!task.logFile) {
17
- console.error(`No log file for task: ${taskId}`);
18
- process.exit(1);
19
- }
20
-
21
- // Output just the path (machine-readable)
22
- console.log(task.logFile);
23
- }
@@ -1,32 +0,0 @@
1
- import chalk from 'chalk';
2
- import { getTask, updateTask } from '../store.js';
3
- import { killTask as killProcess, isProcessRunning } from '../runner.js';
4
-
5
- export function killTaskCommand(taskId) {
6
- const task = getTask(taskId);
7
-
8
- if (!task) {
9
- console.log(chalk.red(`Task not found: ${taskId}`));
10
- process.exit(1);
11
- }
12
-
13
- if (task.status !== 'running') {
14
- console.log(chalk.yellow(`Task is not running (status: ${task.status})`));
15
- return;
16
- }
17
-
18
- if (!isProcessRunning(task.pid)) {
19
- console.log(chalk.yellow('Process already dead, updating status...'));
20
- updateTask(taskId, { status: 'stale', error: 'Process died unexpectedly' });
21
- return;
22
- }
23
-
24
- const killed = killProcess(task.pid);
25
-
26
- if (killed) {
27
- console.log(chalk.green(`✓ Sent SIGTERM to task ${taskId} (PID: ${task.pid})`));
28
- updateTask(taskId, { status: 'killed', error: 'Killed by user' });
29
- } else {
30
- console.log(chalk.red(`Failed to kill task ${taskId}`));
31
- }
32
- }