@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
@@ -1,272 +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
- const server = new AttachServer({
100
- id: taskId,
101
- socketPath,
102
- command,
103
- args: finalArgs,
104
- cwd,
105
- env,
106
- cols: 120,
107
- rows: 30,
108
- });
109
-
110
- server.on('output', (data) => {
111
- const chunk = data.toString();
112
- const timestamp = Date.now();
113
-
114
- if (silentJsonMode) {
115
- outputBuffer += chunk;
116
- const lines = outputBuffer.split('\n');
117
- outputBuffer = lines.pop() || '';
118
-
119
- for (const line of lines) {
120
- if (!line.trim()) continue;
121
- if (enableRecovery) {
122
- const detectedError = detectStreamingModeError(line);
123
- if (detectedError) {
124
- streamingModeError = { ...detectedError, timestamp };
125
- continue;
126
- }
127
- }
128
- try {
129
- const json = JSON.parse(line);
130
- if (json.structured_output) {
131
- finalResultJson = line;
132
- }
133
- } catch {
134
- // Not JSON, skip
135
- }
136
- }
137
- } else {
138
- outputBuffer += chunk;
139
- const lines = outputBuffer.split('\n');
140
- outputBuffer = lines.pop() || '';
141
-
142
- for (const line of lines) {
143
- if (enableRecovery) {
144
- const detectedError = detectStreamingModeError(line);
145
- if (detectedError) {
146
- streamingModeError = { ...detectedError, timestamp };
147
- continue;
148
- }
149
- }
150
- log(`[${timestamp}]${line}\n`);
151
- }
152
- }
153
- });
154
-
155
- server.on('exit', async ({ exitCode, signal }) => {
156
- const timestamp = Date.now();
157
- const code = exitCode;
158
-
159
- if (outputBuffer.trim()) {
160
- if (enableRecovery) {
161
- const detectedError = detectStreamingModeError(outputBuffer);
162
- if (detectedError) {
163
- streamingModeError = { ...detectedError, timestamp };
164
- } else if (silentJsonMode) {
165
- try {
166
- const json = JSON.parse(outputBuffer);
167
- if (json.structured_output) {
168
- finalResultJson = outputBuffer;
169
- }
170
- } catch {
171
- // Not valid JSON
172
- }
173
- } else {
174
- log(`[${timestamp}]${outputBuffer}\n`);
175
- }
176
- } else if (!silentJsonMode) {
177
- log(`[${timestamp}]${outputBuffer}\n`);
178
- }
179
- }
180
-
181
- let recovered = null;
182
- if (enableRecovery && code !== 0 && streamingModeError?.sessionId) {
183
- recovered = recoverStructuredOutput(streamingModeError.sessionId);
184
- if (recovered?.payload) {
185
- const recoveredLine = JSON.stringify(recovered.payload);
186
- if (silentJsonMode) {
187
- finalResultJson = recoveredLine;
188
- } else {
189
- log(`[${timestamp}]${recoveredLine}\n`);
190
- }
191
- } else if (streamingModeError.line) {
192
- if (silentJsonMode) {
193
- log(streamingModeError.line + '\n');
194
- } else {
195
- log(`[${streamingModeError.timestamp}]${streamingModeError.line}\n`);
196
- }
197
- }
198
- }
199
-
200
- if (silentJsonMode && finalResultJson) {
201
- log(finalResultJson + '\n');
202
- }
203
-
204
- if (config.outputFormat !== 'json') {
205
- log(`\n${'='.repeat(50)}\n`);
206
- log(`Finished: ${new Date().toISOString()}\n`);
207
- log(`Exit code: ${code}, Signal: ${signal}\n`);
208
- }
209
-
210
- const resolvedCode = recovered?.payload ? 0 : code;
211
- const status = resolvedCode === 0 ? 'completed' : 'failed';
212
- try {
213
- await updateTask(taskId, {
214
- status,
215
- exitCode: resolvedCode,
216
- error: resolvedCode === 0 ? null : signal ? `Killed by ${signal}` : null,
217
- socketPath: null,
218
- });
219
- } catch (updateError) {
220
- log(`[${Date.now()}][ERROR] Failed to update task status: ${updateError.message}\n`);
221
- }
222
-
223
- setTimeout(() => {
224
- process.exit(0);
225
- }, 500);
226
- });
227
-
228
- server.on('error', async (err) => {
229
- log(`\nError: ${err.message}\n`);
230
- try {
231
- await updateTask(taskId, { status: 'failed', error: err.message });
232
- } catch (updateError) {
233
- log(`[${Date.now()}][ERROR] Failed to update task status: ${updateError.message}\n`);
234
- }
235
- process.exit(1);
236
- });
237
-
238
- server.on('clientAttach', ({ clientId }) => {
239
- log(`[${Date.now()}][ATTACH] Client attached: ${clientId.slice(0, 8)}...\n`);
240
- });
241
-
242
- server.on('clientDetach', ({ clientId }) => {
243
- log(`[${Date.now()}][DETACH] Client detached: ${clientId.slice(0, 8)}...\n`);
244
- });
245
-
246
- try {
247
- await server.start();
248
-
249
- updateTask(taskId, {
250
- pid: server.pid,
251
- socketPath,
252
- attachable: true,
253
- });
254
-
255
- log(`[${Date.now()}][SYSTEM] Started with PTY (attachable)\n`);
256
- log(`[${Date.now()}][SYSTEM] Socket: ${socketPath}\n`);
257
- log(`[${Date.now()}][SYSTEM] PID: ${server.pid}\n`);
258
- } catch (err) {
259
- log(`\nFailed to start: ${err.message}\n`);
260
- updateTask(taskId, { status: 'failed', error: err.message });
261
- process.exit(1);
262
- }
263
-
264
- process.on('SIGTERM', async () => {
265
- log(`[${Date.now()}][SYSTEM] Received SIGTERM, stopping...\n`);
266
- await server.stop('SIGTERM');
267
- });
268
-
269
- process.on('SIGINT', async () => {
270
- log(`[${Date.now()}][SYSTEM] Received SIGINT, stopping...\n`);
271
- await server.stop('SIGINT');
272
- });
@@ -1,119 +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
- let fileContents;
70
- try {
71
- fileContents = readFileSync(jsonlPath, 'utf8');
72
- } catch {
73
- return null;
74
- }
75
-
76
- const lines = fileContents.split('\n');
77
- let structuredOutput = null;
78
- let usage = null;
79
-
80
- for (const line of lines) {
81
- if (!line.trim()) continue;
82
- try {
83
- const entry = JSON.parse(line);
84
- const message = entry?.message;
85
- const content = message?.content;
86
- if (!Array.isArray(content)) continue;
87
-
88
- for (const block of content) {
89
- if (block?.type === 'tool_use' && block?.name === 'StructuredOutput' && block?.input) {
90
- structuredOutput = block.input;
91
- if (message?.usage && typeof message.usage === 'object') {
92
- usage = message.usage;
93
- }
94
- }
95
- }
96
- } catch {
97
- // Skip invalid JSON lines
98
- }
99
- }
100
-
101
- if (!structuredOutput) return null;
102
-
103
- const payload = {
104
- type: 'result',
105
- subtype: 'success',
106
- is_error: false,
107
- structured_output: structuredOutput,
108
- session_id: sessionId,
109
- };
110
-
111
- if (usage) {
112
- payload.usage = usage;
113
- }
114
-
115
- return {
116
- payload,
117
- sourcePath: jsonlPath,
118
- };
119
- }
@@ -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,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
- }
@@ -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 listTasks(options = {}) {
6
- const tasks = loadTasks();
7
- const taskList = Object.values(tasks);
8
-
9
- if (taskList.length === 0) {
10
- console.log(chalk.dim('No tasks found.'));
11
- return;
12
- }
13
-
14
- // Sort by creation date, newest first
15
- taskList.sort((a, b) => new Date(b.createdAt) - new Date(a.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(`\nTasks (${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=== Tasks (${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
- }