@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,411 +0,0 @@
1
- import { existsSync, statSync, readFileSync, openSync, readSync, closeSync } from 'fs';
2
- import chalk from 'chalk';
3
- import { getTask } from '../store.js';
4
- import { isProcessRunning } from '../runner.js';
5
- import { createRequire } from 'module';
6
-
7
- // Import cluster's stream parser (shared between task and cluster)
8
- const require = createRequire(import.meta.url);
9
- const { parseChunk } = require('../../lib/stream-json-parser');
10
-
11
- // Tool icons for different tool types
12
- const TOOL_ICONS = {
13
- Read: '📖',
14
- Write: '📝',
15
- Edit: '✏️',
16
- Bash: '💻',
17
- Glob: '🔍',
18
- Grep: '🔎',
19
- WebFetch: '🌐',
20
- WebSearch: '🔎',
21
- Task: '🤖',
22
- TodoWrite: '📋',
23
- AskUserQuestion: '❓',
24
- BashOutput: '📤',
25
- KillShell: '🔪',
26
- };
27
-
28
- function getToolIcon(toolName) {
29
- return TOOL_ICONS[toolName] || '🔧';
30
- }
31
-
32
- // Format tool call input for display
33
- function formatToolCall(toolName, input) {
34
- if (!input) return '';
35
-
36
- switch (toolName) {
37
- case 'Bash':
38
- return input.command ? `$ ${input.command}` : '';
39
- case 'Read':
40
- return input.file_path ? input.file_path.split('/').slice(-2).join('/') : '';
41
- case 'Write':
42
- return input.file_path ? `→ ${input.file_path.split('/').slice(-2).join('/')}` : '';
43
- case 'Edit':
44
- return input.file_path ? input.file_path.split('/').slice(-2).join('/') : '';
45
- case 'Glob':
46
- return input.pattern || '';
47
- case 'Grep':
48
- return input.pattern ? `/${input.pattern}/` : '';
49
- case 'WebFetch':
50
- return input.url ? input.url.substring(0, 50) : '';
51
- case 'WebSearch':
52
- return input.query ? `"${input.query}"` : '';
53
- case 'Task':
54
- return input.description || '';
55
- case 'TodoWrite':
56
- if (input.todos && Array.isArray(input.todos)) {
57
- const statusCounts = {};
58
- input.todos.forEach((todo) => {
59
- statusCounts[todo.status] = (statusCounts[todo.status] || 0) + 1;
60
- });
61
- const parts = Object.entries(statusCounts).map(
62
- ([status, count]) => `${count} ${status.replace('_', ' ')}`
63
- );
64
- return `${input.todos.length} todo${input.todos.length === 1 ? '' : 's'} (${parts.join(', ')})`;
65
- }
66
- return '';
67
- case 'AskUserQuestion':
68
- if (input.questions && Array.isArray(input.questions)) {
69
- const q = input.questions[0];
70
- const preview = q.question.substring(0, 50);
71
- return input.questions.length > 1
72
- ? `${input.questions.length} questions: "${preview}..."`
73
- : `"${preview}${q.question.length > 50 ? '...' : ''}"`;
74
- }
75
- return '';
76
- default:
77
- // For unknown tools, show first key-value pair
78
- const keys = Object.keys(input);
79
- if (keys.length > 0) {
80
- const val = String(input[keys[0]]).substring(0, 40);
81
- return val.length < String(input[keys[0]]).length ? val + '...' : val;
82
- }
83
- return '';
84
- }
85
- }
86
-
87
- // Format tool result for display
88
- function formatToolResult(content, isError, toolName, toolInput) {
89
- if (!content) return isError ? 'error' : 'done';
90
-
91
- // For errors, show full message
92
- if (isError) {
93
- const firstLine = content.split('\n')[0].substring(0, 80);
94
- return chalk.red(firstLine);
95
- }
96
-
97
- // For TodoWrite, show the actual todo items
98
- if (toolName === 'TodoWrite' && toolInput?.todos && Array.isArray(toolInput.todos)) {
99
- const todos = toolInput.todos;
100
- if (todos.length === 0) return chalk.dim('no todos');
101
- if (todos.length === 1) {
102
- const status =
103
- todos[0].status === 'completed' ? '✓' : todos[0].status === 'in_progress' ? '⧗' : '○';
104
- return chalk.dim(
105
- `${status} ${todos[0].content.substring(0, 50)}${todos[0].content.length > 50 ? '...' : ''}`
106
- );
107
- }
108
- // Multiple todos - show first one as preview
109
- const status =
110
- todos[0].status === 'completed' ? '✓' : todos[0].status === 'in_progress' ? '⧗' : '○';
111
- return chalk.dim(
112
- `${status} ${todos[0].content.substring(0, 40)}... (+${todos.length - 1} more)`
113
- );
114
- }
115
-
116
- // For success, show summary
117
- const lines = content.split('\n').filter((l) => l.trim());
118
- if (lines.length === 0) return 'done';
119
- if (lines.length === 1) {
120
- const line = lines[0].substring(0, 60);
121
- return chalk.dim(line.length < lines[0].length ? line + '...' : line);
122
- }
123
- // Multiple lines - show count
124
- return chalk.dim(`${lines.length} lines`);
125
- }
126
-
127
- // Line buffer for accumulating text that streams without newlines
128
- let lineBuffer = '';
129
- let currentToolCall = null;
130
-
131
- function resetState() {
132
- lineBuffer = '';
133
- currentToolCall = null;
134
- }
135
-
136
- // Flush pending text in buffer
137
- function flushLineBuffer() {
138
- if (lineBuffer.trim()) {
139
- process.stdout.write(lineBuffer);
140
- if (!lineBuffer.endsWith('\n')) {
141
- process.stdout.write('\n');
142
- }
143
- }
144
- lineBuffer = '';
145
- }
146
-
147
- // Accumulate text and print complete lines
148
- function accumulateText(text) {
149
- lineBuffer += text;
150
-
151
- // Print complete lines, keep incomplete in buffer
152
- const lines = lineBuffer.split('\n');
153
- if (lines.length > 1) {
154
- // Print all complete lines
155
- for (let i = 0; i < lines.length - 1; i++) {
156
- console.log(lines[i]);
157
- }
158
- // Keep incomplete line in buffer
159
- lineBuffer = lines[lines.length - 1];
160
- }
161
- }
162
-
163
- // Process parsed events and output formatted content
164
- function processEvent(event) {
165
- switch (event.type) {
166
- case 'text':
167
- accumulateText(event.text);
168
- break;
169
-
170
- case 'thinking':
171
- case 'thinking_start':
172
- if (event.text) {
173
- console.log(chalk.dim.italic(event.text));
174
- } else if (event.type === 'thinking_start') {
175
- console.log(chalk.dim.italic('💭 thinking...'));
176
- }
177
- break;
178
-
179
- case 'tool_start':
180
- flushLineBuffer();
181
- break;
182
-
183
- case 'tool_call':
184
- flushLineBuffer();
185
- const icon = getToolIcon(event.toolName);
186
- const toolDesc = formatToolCall(event.toolName, event.input);
187
- console.log(`${icon} ${chalk.cyan(event.toolName)} ${chalk.dim(toolDesc)}`);
188
- currentToolCall = { toolName: event.toolName, input: event.input };
189
- break;
190
-
191
- case 'tool_input':
192
- // Streaming tool input JSON - skip (shown in tool_call)
193
- break;
194
-
195
- case 'tool_result':
196
- const status = event.isError ? chalk.red('✗') : chalk.green('✓');
197
- const resultDesc = formatToolResult(
198
- event.content,
199
- event.isError,
200
- currentToolCall?.toolName,
201
- currentToolCall?.input
202
- );
203
- console.log(` ${status} ${resultDesc}`);
204
- break;
205
-
206
- case 'result':
207
- flushLineBuffer();
208
- if (event.error) {
209
- console.log(chalk.red(`\n✗ ERROR: ${event.error}`));
210
- } else {
211
- console.log(chalk.green(`\n✓ Completed`));
212
- if (event.cost) {
213
- console.log(chalk.dim(` Cost: $${event.cost.toFixed(4)}`));
214
- }
215
- if (event.duration) {
216
- const mins = Math.floor(event.duration / 60000);
217
- const secs = Math.floor((event.duration % 60000) / 1000);
218
- console.log(chalk.dim(` Duration: ${mins}m ${secs}s`));
219
- }
220
- }
221
- break;
222
-
223
- case 'block_end':
224
- // Content block ended
225
- break;
226
-
227
- case 'multi':
228
- // Multiple events
229
- if (event.events) {
230
- for (const e of event.events) {
231
- processEvent(e);
232
- }
233
- }
234
- break;
235
- }
236
- }
237
-
238
- // Parse a raw log line (may have timestamp prefix)
239
- function parseLogLine(line) {
240
- let trimmed = line.trim();
241
- if (!trimmed) return [];
242
-
243
- // Strip timestamp prefix if present: [1234567890]{...} -> {...}
244
- const timestampMatch = trimmed.match(/^\[\d+\](.*)$/);
245
- if (timestampMatch) {
246
- trimmed = timestampMatch[1];
247
- }
248
-
249
- // Non-JSON lines output as-is
250
- if (!trimmed.startsWith('{')) {
251
- return [{ type: 'text', text: trimmed + '\n' }];
252
- }
253
-
254
- // Parse JSON using cluster's parser
255
- return parseChunk(trimmed);
256
- }
257
-
258
- export async function showLogs(taskId, options = {}) {
259
- const task = getTask(taskId);
260
-
261
- if (!task) {
262
- console.log(chalk.red(`Task not found: ${taskId}`));
263
- process.exit(1);
264
- }
265
-
266
- if (!existsSync(task.logFile)) {
267
- console.log(chalk.yellow('Log file not found (task may still be starting).'));
268
- return;
269
- }
270
-
271
- const lines = options.lines || 50;
272
- const follow = options.follow;
273
- const watch = options.watch;
274
-
275
- // IMPORTANT: -f should stream logs (like tail -f), -w launches interactive TUI
276
- // This prevents confusion when users expect tail-like behavior
277
- if (watch) {
278
- // Use TUI for watch mode (interactive interface)
279
- const { default: TaskLogsTUI } = await import('../tui/index.js');
280
- const tui = new TaskLogsTUI({
281
- taskId: task.id,
282
- logFile: task.logFile,
283
- taskInfo: {
284
- status: task.status,
285
- createdAt: task.createdAt,
286
- prompt: task.prompt,
287
- },
288
- pid: task.pid,
289
- });
290
- await tui.start();
291
- } else if (follow) {
292
- // Stream logs continuously (like tail -f) - show last N lines first
293
- await tailFollow(task.logFile, task.pid, lines);
294
- } else {
295
- await tailLines(task.logFile, lines);
296
- }
297
- }
298
-
299
- function tailLines(file, n) {
300
- resetState();
301
- const rawContent = readFileSync(file, 'utf-8');
302
- const rawLines = rawContent.split('\n');
303
-
304
- // Parse and process all events
305
- const allEvents = [];
306
- for (const line of rawLines) {
307
- const events = parseLogLine(line);
308
- allEvents.push(...events);
309
- }
310
-
311
- // Tail to last n events
312
- const tailedEvents = allEvents.slice(-n);
313
- for (const event of tailedEvents) {
314
- processEvent(event);
315
- }
316
- flushLineBuffer();
317
- }
318
-
319
- async function tailFollow(file, pid, lines = 50) {
320
- resetState();
321
- // First, output last N events (like tail -f behavior)
322
- const rawContent = readFileSync(file, 'utf-8');
323
- const rawLines = rawContent.split('\n');
324
-
325
- // Parse all events first
326
- const allEvents = [];
327
- for (const line of rawLines) {
328
- const events = parseLogLine(line);
329
- allEvents.push(...events);
330
- }
331
-
332
- // Output only the last N events
333
- const tailedEvents = allEvents.slice(-lines);
334
- for (const event of tailedEvents) {
335
- processEvent(event);
336
- }
337
- flushLineBuffer();
338
-
339
- // Poll for changes (more reliable than fs.watch)
340
- let lastSize = statSync(file).size;
341
- let noChangeCount = 0;
342
-
343
- const interval = setInterval(() => {
344
- try {
345
- const currentSize = statSync(file).size;
346
-
347
- if (currentSize > lastSize) {
348
- // Read new content
349
- const buffer = Buffer.alloc(currentSize - lastSize);
350
- const fd = openSync(file, 'r');
351
- readSync(fd, buffer, 0, buffer.length, lastSize);
352
- closeSync(fd);
353
-
354
- // Parse and output new lines
355
- const newLines = buffer.toString().split('\n');
356
- for (const line of newLines) {
357
- const events = parseLogLine(line);
358
- for (const event of events) {
359
- processEvent(event);
360
- }
361
- }
362
- flushLineBuffer();
363
-
364
- lastSize = currentSize;
365
- noChangeCount = 0;
366
- } else {
367
- noChangeCount++;
368
-
369
- // Check if process is still running after 5 seconds of no output
370
- if (noChangeCount >= 10 && pid && !isProcessRunning(pid)) {
371
- // Read any final content
372
- const finalSize = statSync(file).size;
373
- if (finalSize > lastSize) {
374
- const buffer = Buffer.alloc(finalSize - lastSize);
375
- const fd = openSync(file, 'r');
376
- readSync(fd, buffer, 0, buffer.length, lastSize);
377
- closeSync(fd);
378
-
379
- const finalLines = buffer.toString().split('\n');
380
- for (const line of finalLines) {
381
- const events = parseLogLine(line);
382
- for (const event of events) {
383
- processEvent(event);
384
- }
385
- }
386
- flushLineBuffer();
387
- }
388
-
389
- console.log(chalk.dim('\n--- Task completed ---'));
390
- clearInterval(interval);
391
- process.exit(0);
392
- }
393
- }
394
- } catch (err) {
395
- // File might have been deleted
396
- console.log(chalk.red(`\nError reading log: ${err.message}`));
397
- clearInterval(interval);
398
- process.exit(1);
399
- }
400
- }, 500); // Poll every 500ms
401
-
402
- // Keep alive until Ctrl+C
403
- process.on('SIGINT', () => {
404
- clearInterval(interval);
405
- console.log(chalk.dim('\nStopped following.'));
406
- process.exit(0);
407
- });
408
-
409
- // Keep process running
410
- await new Promise(() => {});
411
- }
@@ -1,42 +0,0 @@
1
- import chalk from 'chalk';
2
- import { getTask } from '../store.js';
3
- import { spawnTask } from '../runner.js';
4
-
5
- export async function resumeTask(taskId, newPrompt) {
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(
15
- chalk.yellow(`Task is still running. Use 'zeroshot logs -f ${taskId}' to follow output.`)
16
- );
17
- return;
18
- }
19
-
20
- const prompt = newPrompt || 'Continue from where you left off. Complete the task.';
21
-
22
- console.log(chalk.dim(`Resuming task ${taskId}...`));
23
- console.log(chalk.dim(`Original prompt: ${task.prompt}`));
24
- console.log(chalk.dim(`Resume prompt: ${prompt}`));
25
-
26
- const newTask = await spawnTask(prompt, {
27
- cwd: task.cwd,
28
- continue: true, // Use --continue to load most recent session in that directory
29
- sessionId: task.sessionId,
30
- provider: task.provider,
31
- });
32
-
33
- console.log(chalk.green(`\n✓ Resumed as new task: ${chalk.cyan(newTask.id)}`));
34
- console.log(chalk.dim(` PID: ${newTask.pid}`));
35
- console.log(chalk.dim(` Log: ${newTask.logFile}`));
36
-
37
- console.log(chalk.dim('\nCommands:'));
38
- console.log(chalk.dim(` zeroshot logs -f ${newTask.id} # Follow output`));
39
- console.log();
40
-
41
- return newTask;
42
- }
@@ -1,57 +0,0 @@
1
- import chalk from 'chalk';
2
- import { spawnTask } from '../runner.js';
3
-
4
- export async function runTask(prompt, options = {}) {
5
- if (!prompt || prompt.trim().length === 0) {
6
- console.log(chalk.red('Error: Prompt is required'));
7
- process.exit(1);
8
- }
9
-
10
- const outputFormat = options.outputFormat || 'stream-json';
11
- const jsonSchema = options.jsonSchema;
12
- const silentJsonOutput = options.silentJsonOutput || false;
13
-
14
- console.log(chalk.dim('Spawning task...'));
15
- if (options.provider) {
16
- console.log(chalk.dim(` Provider: ${options.provider}`));
17
- }
18
- if (options.model) {
19
- console.log(chalk.dim(` Model: ${options.model}`));
20
- }
21
- if (options.modelLevel) {
22
- console.log(chalk.dim(` Level: ${options.modelLevel}`));
23
- }
24
- if (jsonSchema && outputFormat === 'json') {
25
- console.log(chalk.dim(` JSON Schema: enforced`));
26
- if (silentJsonOutput) {
27
- console.log(chalk.dim(` Silent mode: log contains ONLY final JSON`));
28
- }
29
- }
30
-
31
- const task = await spawnTask(prompt, {
32
- cwd: options.cwd || process.cwd(),
33
- model: options.model,
34
- modelLevel: options.modelLevel,
35
- reasoningEffort: options.reasoningEffort,
36
- provider: options.provider,
37
- resume: options.resume,
38
- continue: options.continue,
39
- outputFormat,
40
- jsonSchema,
41
- silentJsonOutput,
42
- });
43
-
44
- console.log(chalk.green(`\n✓ Task spawned: ${chalk.cyan(task.id)}`));
45
- console.log(chalk.dim(` Log: ${task.logFile}`));
46
- console.log(chalk.dim(` CWD: ${task.cwd}`));
47
-
48
- console.log(chalk.dim('\nCommands:'));
49
- console.log(chalk.dim(` zeroshot attach ${task.id} # Attach to task (Ctrl+B d to detach)`));
50
- console.log(chalk.dim(` zeroshot logs ${task.id} # View output`));
51
- console.log(chalk.dim(` zeroshot logs -f ${task.id} # Follow output`));
52
- console.log(chalk.dim(` zeroshot status ${task.id} # Check status`));
53
- console.log(chalk.dim(` zeroshot kill ${task.id} # Stop task`));
54
- console.log();
55
-
56
- return task;
57
- }
@@ -1,105 +0,0 @@
1
- import chalk from 'chalk';
2
- import { addSchedule, generateScheduleId, ensureDirs } from '../store.js';
3
- import { parseInterval, calculateNextRun, getDaemonStatus } from '../scheduler.js';
4
- import { fork } from 'child_process';
5
- import { join, dirname } from 'path';
6
- import { fileURLToPath } from 'url';
7
-
8
- const __dirname = dirname(fileURLToPath(import.meta.url));
9
-
10
- export function createSchedule(prompt, options = {}) {
11
- if (!prompt || prompt.trim().length === 0) {
12
- console.log(chalk.red('Error: Prompt is required'));
13
- process.exit(1);
14
- }
15
-
16
- if (!options.every && !options.cron) {
17
- console.log(chalk.red('Error: Either --every or --cron is required'));
18
- console.log(chalk.dim(' Examples:'));
19
- console.log(chalk.dim(' zeroshot schedule "backup" --every 1d'));
20
- console.log(chalk.dim(' zeroshot schedule "cleanup" --cron "0 2 * * *"'));
21
- process.exit(1);
22
- }
23
-
24
- ensureDirs();
25
-
26
- let interval = null;
27
- let cron = null;
28
-
29
- if (options.every) {
30
- interval = parseInterval(options.every);
31
- if (!interval) {
32
- console.log(chalk.red(`Error: Invalid interval format "${options.every}"`));
33
- console.log(chalk.dim(' Valid formats: 30s, 5m, 2h, 1d, 1w'));
34
- process.exit(1);
35
- }
36
- }
37
-
38
- if (options.cron) {
39
- cron = options.cron;
40
- // Basic validation
41
- if (cron.trim().split(/\s+/).length !== 5) {
42
- console.log(chalk.red(`Error: Invalid cron format "${options.cron}"`));
43
- console.log(chalk.dim(' Format: minute hour day month weekday'));
44
- console.log(chalk.dim(' Example: "0 2 * * *" (daily at 2am)'));
45
- process.exit(1);
46
- }
47
- }
48
-
49
- const schedule = {
50
- id: generateScheduleId(),
51
- prompt: prompt.slice(0, 200) + (prompt.length > 200 ? '...' : ''),
52
- fullPrompt: prompt,
53
- cwd: options.cwd || process.cwd(),
54
- interval,
55
- cron,
56
- nextRunAt: null,
57
- lastRunAt: null,
58
- lastTaskId: null,
59
- enabled: true,
60
- createdAt: new Date().toISOString(),
61
- updatedAt: new Date().toISOString(),
62
- };
63
-
64
- // Calculate first run time
65
- const nextRun = calculateNextRun(schedule);
66
- schedule.nextRunAt = nextRun ? nextRun.toISOString() : null;
67
-
68
- addSchedule(schedule);
69
-
70
- console.log(chalk.green(`\n✓ Schedule created: ${chalk.cyan(schedule.id)}`));
71
- console.log(chalk.dim(` Prompt: ${schedule.prompt}`));
72
- console.log(chalk.dim(` CWD: ${schedule.cwd}`));
73
-
74
- if (interval) {
75
- const humanInterval = options.every;
76
- console.log(chalk.dim(` Interval: every ${humanInterval}`));
77
- }
78
- if (cron) {
79
- console.log(chalk.dim(` Cron: ${cron}`));
80
- }
81
- console.log(chalk.dim(` Next run: ${nextRun ? nextRun.toISOString() : 'N/A'}`));
82
-
83
- // Check if scheduler is running, start if not
84
- const status = getDaemonStatus();
85
- if (!status.running) {
86
- console.log(chalk.yellow('\n⚠ Scheduler daemon is not running'));
87
- console.log(chalk.dim(' Starting scheduler daemon...'));
88
-
89
- // Fork scheduler as detached process
90
- const scheduler = fork(join(__dirname, '..', 'scheduler.js'), [], {
91
- detached: true,
92
- stdio: 'ignore',
93
- });
94
- scheduler.unref();
95
-
96
- console.log(chalk.green(' ✓ Scheduler daemon started'));
97
- }
98
-
99
- console.log(chalk.dim('\nCommands:'));
100
- console.log(chalk.dim(` zeroshot schedules # List all schedules`));
101
- console.log(chalk.dim(` zeroshot unschedule ${schedule.id} # Remove this schedule`));
102
- console.log();
103
-
104
- return schedule;
105
- }