@covibes/zeroshot 1.0.1 → 1.1.3

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 (42) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +2 -0
  3. package/cli/index.js +151 -208
  4. package/cli/message-formatter-utils.js +75 -0
  5. package/cli/message-formatters-normal.js +214 -0
  6. package/cli/message-formatters-watch.js +181 -0
  7. package/cluster-templates/base-templates/full-workflow.json +10 -5
  8. package/docker/zeroshot-cluster/Dockerfile +6 -0
  9. package/package.json +5 -2
  10. package/src/agent/agent-task-executor.js +237 -112
  11. package/src/isolation-manager.js +94 -51
  12. package/src/orchestrator.js +45 -10
  13. package/src/preflight.js +383 -0
  14. package/src/process-metrics.js +546 -0
  15. package/src/status-footer.js +543 -0
  16. package/task-lib/attachable-watcher.js +202 -0
  17. package/task-lib/commands/clean.js +50 -0
  18. package/task-lib/commands/get-log-path.js +23 -0
  19. package/task-lib/commands/kill.js +32 -0
  20. package/task-lib/commands/list.js +105 -0
  21. package/task-lib/commands/logs.js +411 -0
  22. package/task-lib/commands/resume.js +41 -0
  23. package/task-lib/commands/run.js +48 -0
  24. package/task-lib/commands/schedule.js +105 -0
  25. package/task-lib/commands/scheduler-cmd.js +96 -0
  26. package/task-lib/commands/schedules.js +98 -0
  27. package/task-lib/commands/status.js +44 -0
  28. package/task-lib/commands/unschedule.js +16 -0
  29. package/task-lib/completion.js +9 -0
  30. package/task-lib/config.js +10 -0
  31. package/task-lib/name-generator.js +230 -0
  32. package/task-lib/package.json +3 -0
  33. package/task-lib/runner.js +123 -0
  34. package/task-lib/scheduler.js +252 -0
  35. package/task-lib/store.js +217 -0
  36. package/task-lib/tui/formatters.js +166 -0
  37. package/task-lib/tui/index.js +197 -0
  38. package/task-lib/tui/layout.js +111 -0
  39. package/task-lib/tui/renderer.js +119 -0
  40. package/task-lib/tui.js +384 -0
  41. package/task-lib/watcher.js +162 -0
  42. package/cluster-templates/conductor-junior-bootstrap.json +0 -69
@@ -0,0 +1,98 @@
1
+ import chalk from 'chalk';
2
+ import { loadSchedules } from '../store.js';
3
+ import { getDaemonStatus } from '../scheduler.js';
4
+
5
+ export function listSchedules(_options = {}) {
6
+ const schedules = loadSchedules();
7
+ const scheduleList = Object.values(schedules);
8
+
9
+ // Check daemon status
10
+ const daemonStatus = getDaemonStatus();
11
+
12
+ if (scheduleList.length === 0) {
13
+ console.log(chalk.dim('No schedules found.'));
14
+ console.log(chalk.dim('\nCreate a schedule:'));
15
+ console.log(chalk.dim(' zeroshot schedule "your prompt" --every 1h'));
16
+ console.log(chalk.dim(' zeroshot schedule "your prompt" --cron "0 * * * *"'));
17
+ return;
18
+ }
19
+
20
+ // Show daemon status
21
+ if (daemonStatus.running) {
22
+ console.log(chalk.green(`Scheduler: running (PID: ${daemonStatus.pid})`));
23
+ } else if (daemonStatus.stale) {
24
+ console.log(chalk.yellow('Scheduler: not running (stale PID file)'));
25
+ } else {
26
+ console.log(chalk.red('Scheduler: not running'));
27
+ console.log(chalk.dim(' Run: zeroshot scheduler start'));
28
+ }
29
+ console.log();
30
+
31
+ // Sort by next run time
32
+ scheduleList.sort((a, b) => {
33
+ if (!a.nextRunAt) return 1;
34
+ if (!b.nextRunAt) return -1;
35
+ return new Date(a.nextRunAt) - new Date(b.nextRunAt);
36
+ });
37
+
38
+ console.log(chalk.bold('Scheduled Tasks:'));
39
+ console.log();
40
+
41
+ for (const schedule of scheduleList) {
42
+ const statusColor = schedule.enabled ? chalk.green : chalk.red;
43
+ const statusText = schedule.enabled ? '●' : '○';
44
+
45
+ console.log(`${statusColor(statusText)} ${chalk.cyan(schedule.id)}`);
46
+ console.log(chalk.dim(` Prompt: ${schedule.prompt}`));
47
+
48
+ if (schedule.interval) {
49
+ const ms = schedule.interval;
50
+ let human;
51
+ if (ms >= 7 * 24 * 60 * 60 * 1000) human = `${ms / (7 * 24 * 60 * 60 * 1000)}w`;
52
+ else if (ms >= 24 * 60 * 60 * 1000) human = `${ms / (24 * 60 * 60 * 1000)}d`;
53
+ else if (ms >= 60 * 60 * 1000) human = `${ms / (60 * 60 * 1000)}h`;
54
+ else if (ms >= 60 * 1000) human = `${ms / (60 * 1000)}m`;
55
+ else human = `${ms / 1000}s`;
56
+ console.log(chalk.dim(` Interval: every ${human}`));
57
+ }
58
+ if (schedule.cron) {
59
+ console.log(chalk.dim(` Cron: ${schedule.cron}`));
60
+ }
61
+
62
+ if (schedule.nextRunAt) {
63
+ const nextRun = new Date(schedule.nextRunAt);
64
+ const now = new Date();
65
+ const diff = nextRun - now;
66
+
67
+ let timeUntil;
68
+ if (diff < 0) {
69
+ timeUntil = 'overdue';
70
+ } else if (diff < 60000) {
71
+ timeUntil = 'in < 1m';
72
+ } else if (diff < 3600000) {
73
+ timeUntil = `in ${Math.round(diff / 60000)}m`;
74
+ } else if (diff < 86400000) {
75
+ timeUntil = `in ${Math.round(diff / 3600000)}h`;
76
+ } else {
77
+ timeUntil = `in ${Math.round(diff / 86400000)}d`;
78
+ }
79
+
80
+ console.log(chalk.dim(` Next run: ${nextRun.toISOString()} (${timeUntil})`));
81
+ }
82
+
83
+ if (schedule.lastRunAt) {
84
+ console.log(chalk.dim(` Last run: ${schedule.lastRunAt}`));
85
+ if (schedule.lastTaskId) {
86
+ console.log(chalk.dim(` Last task: ${schedule.lastTaskId}`));
87
+ }
88
+ }
89
+
90
+ if (schedule.verify) {
91
+ console.log(chalk.dim(` Verify: enabled`));
92
+ }
93
+
94
+ console.log();
95
+ }
96
+
97
+ console.log(chalk.dim(`Total: ${scheduleList.length} schedule(s)`));
98
+ }
@@ -0,0 +1,44 @@
1
+ import chalk from 'chalk';
2
+ import { getTask } from '../store.js';
3
+ import { isProcessRunning } from '../runner.js';
4
+
5
+ export function showStatus(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
+ // Verify running status
14
+ let status = task.status;
15
+ if (status === 'running' && !isProcessRunning(task.pid)) {
16
+ status = 'stale (process died)';
17
+ }
18
+
19
+ const statusColor =
20
+ {
21
+ running: chalk.blue,
22
+ completed: chalk.green,
23
+ failed: chalk.red,
24
+ }[task.status] || chalk.yellow;
25
+
26
+ console.log(chalk.bold(`\nTask: ${task.id}\n`));
27
+ console.log(`${chalk.dim('Status:')} ${statusColor(status)}`);
28
+ console.log(`${chalk.dim('Created:')} ${task.createdAt}`);
29
+ console.log(`${chalk.dim('Updated:')} ${task.updatedAt}`);
30
+ console.log(`${chalk.dim('CWD:')} ${task.cwd}`);
31
+ console.log(`${chalk.dim('PID:')} ${task.pid || 'N/A'}`);
32
+ console.log(`${chalk.dim('Exit Code:')} ${task.exitCode ?? 'N/A'}`);
33
+ console.log(`${chalk.dim('Session:')} ${task.sessionId || 'N/A'}`);
34
+ console.log(`${chalk.dim('Log File:')} ${task.logFile}`);
35
+
36
+ console.log(`\n${chalk.dim('Prompt:')}`);
37
+ console.log(task.fullPrompt || task.prompt);
38
+
39
+ if (task.error) {
40
+ console.log(`\n${chalk.red('Error:')} ${task.error}`);
41
+ }
42
+
43
+ console.log();
44
+ }
@@ -0,0 +1,16 @@
1
+ import chalk from 'chalk';
2
+ import { getSchedule, removeSchedule } from '../store.js';
3
+
4
+ export function deleteSchedule(scheduleId) {
5
+ const schedule = getSchedule(scheduleId);
6
+
7
+ if (!schedule) {
8
+ console.log(chalk.red(`Schedule not found: ${scheduleId}`));
9
+ process.exit(1);
10
+ }
11
+
12
+ removeSchedule(scheduleId);
13
+
14
+ console.log(chalk.green(`✓ Schedule removed: ${chalk.cyan(scheduleId)}`));
15
+ console.log(chalk.dim(` Prompt: ${schedule.prompt}`));
16
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Shell completion setup for ct
3
+ * Placeholder - completion not yet implemented
4
+ */
5
+
6
+ export function setupCompletion() {
7
+ // TODO: Implement shell completion
8
+ // For now, this is a no-op
9
+ }
@@ -0,0 +1,10 @@
1
+ import { homedir } from 'os';
2
+ import { join } from 'path';
3
+
4
+ export const TASKS_DIR = join(homedir(), '.claude-zeroshot');
5
+ export const TASKS_FILE = join(TASKS_DIR, 'tasks.json');
6
+ export const LOGS_DIR = join(TASKS_DIR, 'logs');
7
+ export const SCHEDULES_FILE = join(TASKS_DIR, 'schedules.json');
8
+ export const SCHEDULER_PID_FILE = join(TASKS_DIR, 'scheduler.pid');
9
+ export const SCHEDULER_LOG = join(TASKS_DIR, 'scheduler.log');
10
+ export const DEFAULT_MODEL = 'sonnet';
@@ -0,0 +1,230 @@
1
+ /**
2
+ * Human-readable name generator (like Weights & Biases)
3
+ * Generates names like "wandering-forest-42" or "bright-star-17"
4
+ *
5
+ * No prefix - short forms used everywhere for simplicity
6
+ */
7
+
8
+ const ADJECTIVES = [
9
+ // Colors
10
+ 'amber',
11
+ 'azure',
12
+ 'crimson',
13
+ 'emerald',
14
+ 'golden',
15
+ 'indigo',
16
+ 'jade',
17
+ 'ruby',
18
+ 'sapphire',
19
+ 'silver',
20
+ 'violet',
21
+ 'bronze',
22
+ 'coral',
23
+ 'ivory',
24
+ 'pearl',
25
+ 'platinum',
26
+ 'scarlet',
27
+ 'cobalt',
28
+ 'copper',
29
+ 'obsidian',
30
+ 'onyx',
31
+ 'opal',
32
+ 'topaz',
33
+ 'turquoise',
34
+ // Nature
35
+ 'wandering',
36
+ 'bright',
37
+ 'silent',
38
+ 'ancient',
39
+ 'swift',
40
+ 'noble',
41
+ 'bold',
42
+ 'wild',
43
+ 'gentle',
44
+ 'hidden',
45
+ 'fierce',
46
+ 'calm',
47
+ 'frozen',
48
+ 'misty',
49
+ 'stormy',
50
+ 'sunny',
51
+ // Cosmic
52
+ 'cosmic',
53
+ 'crystal',
54
+ 'electric',
55
+ 'lunar',
56
+ 'solar',
57
+ 'stellar',
58
+ 'astral',
59
+ 'orbital',
60
+ 'mystic',
61
+ 'quantum',
62
+ 'radiant',
63
+ 'twilight',
64
+ 'vivid',
65
+ 'zen',
66
+ 'infinite',
67
+ 'eternal',
68
+ // Tech/Abstract
69
+ 'clever',
70
+ 'rapid',
71
+ 'steady',
72
+ 'agile',
73
+ 'nimble',
74
+ 'keen',
75
+ 'sharp',
76
+ 'quick',
77
+ 'prime',
78
+ 'binary',
79
+ 'neural',
80
+ 'atomic',
81
+ 'sonic',
82
+ 'hyper',
83
+ 'mega',
84
+ 'ultra',
85
+ // Descriptive
86
+ 'blazing',
87
+ 'gleaming',
88
+ 'glowing',
89
+ 'shining',
90
+ 'burning',
91
+ 'flaming',
92
+ 'sparkling',
93
+ 'dazzling',
94
+ 'roaring',
95
+ 'rushing',
96
+ 'soaring',
97
+ 'flying',
98
+ 'rising',
99
+ 'falling',
100
+ 'spinning',
101
+ 'dancing',
102
+ ];
103
+
104
+ const NOUNS = [
105
+ // Nature
106
+ 'forest',
107
+ 'river',
108
+ 'mountain',
109
+ 'ocean',
110
+ 'thunder',
111
+ 'canyon',
112
+ 'summit',
113
+ 'valley',
114
+ 'cascade',
115
+ 'glacier',
116
+ 'volcano',
117
+ 'desert',
118
+ 'meadow',
119
+ 'tundra',
120
+ 'jungle',
121
+ 'reef',
122
+ // Space
123
+ 'star',
124
+ 'comet',
125
+ 'nebula',
126
+ 'galaxy',
127
+ 'pulsar',
128
+ 'quasar',
129
+ 'aurora',
130
+ 'eclipse',
131
+ 'meteor',
132
+ 'nova',
133
+ 'cosmos',
134
+ 'orbit',
135
+ 'void',
136
+ 'horizon',
137
+ 'zenith',
138
+ 'equinox',
139
+ // Mythical
140
+ 'phoenix',
141
+ 'dragon',
142
+ 'griffin',
143
+ 'sphinx',
144
+ 'hydra',
145
+ 'kraken',
146
+ 'titan',
147
+ 'atlas',
148
+ 'oracle',
149
+ 'rune',
150
+ 'sigil',
151
+ 'glyph',
152
+ 'totem',
153
+ 'aegis',
154
+ 'aether',
155
+ 'flux',
156
+ // Animals
157
+ 'falcon',
158
+ 'eagle',
159
+ 'wolf',
160
+ 'bear',
161
+ 'tiger',
162
+ 'hawk',
163
+ 'lion',
164
+ 'panther',
165
+ 'raven',
166
+ 'serpent',
167
+ 'shark',
168
+ 'owl',
169
+ 'fox',
170
+ 'lynx',
171
+ 'viper',
172
+ 'condor',
173
+ // Architecture
174
+ 'citadel',
175
+ 'temple',
176
+ 'spire',
177
+ 'tower',
178
+ 'fortress',
179
+ 'bastion',
180
+ 'vault',
181
+ 'sanctum',
182
+ 'beacon',
183
+ 'arch',
184
+ 'bridge',
185
+ 'gate',
186
+ 'hall',
187
+ 'keep',
188
+ 'dome',
189
+ 'obelisk',
190
+ // Abstract
191
+ 'cipher',
192
+ 'echo',
193
+ 'nexus',
194
+ 'prism',
195
+ 'relic',
196
+ 'vertex',
197
+ 'vortex',
198
+ 'pulse',
199
+ 'surge',
200
+ 'spark',
201
+ 'flame',
202
+ 'storm',
203
+ 'wave',
204
+ 'drift',
205
+ 'shift',
206
+ 'core',
207
+ ];
208
+
209
+ // 96 adjectives × 96 nouns × 100 numbers = 921,600 combinations
210
+
211
+ /**
212
+ * Generate a human-readable name
213
+ * @param {string} _prefix - DEPRECATED: Ignored for backwards compat, short form always used
214
+ * @returns {string} Human-readable name (e.g., 'wandering-forest-42')
215
+ */
216
+ export function generateName(_prefix = '') {
217
+ const adjective = ADJECTIVES[Math.floor(Math.random() * ADJECTIVES.length)];
218
+ const noun = NOUNS[Math.floor(Math.random() * NOUNS.length)];
219
+ const number = Math.floor(Math.random() * 100);
220
+
221
+ return `${adjective}-${noun}-${number}`;
222
+ }
223
+
224
+ /**
225
+ * Generate a short unique suffix (for collision prevention)
226
+ * @returns {string} Short random suffix (e.g., 'a3f9')
227
+ */
228
+ export function generateSuffix() {
229
+ return Math.random().toString(36).slice(2, 6);
230
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }
@@ -0,0 +1,123 @@
1
+ import { fork } from 'child_process';
2
+ import { join, dirname } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import { LOGS_DIR, DEFAULT_MODEL } from './config.js';
5
+ import { addTask, generateId, ensureDirs } from './store.js';
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+
9
+ export function spawnTask(prompt, options = {}) {
10
+ ensureDirs();
11
+
12
+ const id = generateId();
13
+ const logFile = join(LOGS_DIR, `${id}.log`);
14
+ const cwd = options.cwd || process.cwd();
15
+ const model = options.model || process.env.ANTHROPIC_MODEL || DEFAULT_MODEL;
16
+
17
+ // Build claude command args
18
+ // --print: non-interactive mode
19
+ // --dangerously-skip-permissions: background tasks can't prompt for approval (CRITICAL)
20
+ // --output-format: stream-json (default) for real-time, text for clean output, json for structured
21
+ const outputFormat = options.outputFormat || 'stream-json';
22
+ const args = ['--print', '--dangerously-skip-permissions', '--output-format', outputFormat];
23
+
24
+ // Only add streaming options for stream-json format
25
+ if (outputFormat === 'stream-json') {
26
+ args.push('--verbose');
27
+ // Include partial messages to get streaming updates before completion (required for stream-json format)
28
+ args.push('--include-partial-messages');
29
+ }
30
+
31
+ // Add JSON schema if provided (only works with --output-format json)
32
+ if (options.jsonSchema) {
33
+ if (outputFormat !== 'json') {
34
+ console.warn('Warning: --json-schema requires --output-format json, ignoring schema');
35
+ } else {
36
+ // CRITICAL: Must stringify schema object before passing to CLI (like zeroshot does)
37
+ const schemaString =
38
+ typeof options.jsonSchema === 'string'
39
+ ? options.jsonSchema
40
+ : JSON.stringify(options.jsonSchema);
41
+ args.push('--json-schema', schemaString);
42
+ }
43
+ }
44
+
45
+ if (options.resume) {
46
+ args.push('--resume', options.resume);
47
+ } else if (options.continue) {
48
+ args.push('--continue');
49
+ }
50
+
51
+ args.push(prompt);
52
+
53
+ const task = {
54
+ id,
55
+ prompt: prompt.slice(0, 200) + (prompt.length > 200 ? '...' : ''),
56
+ fullPrompt: prompt,
57
+ cwd,
58
+ status: 'running',
59
+ pid: null,
60
+ sessionId: options.resume || options.sessionId || null,
61
+ logFile,
62
+ createdAt: new Date().toISOString(),
63
+ updatedAt: new Date().toISOString(),
64
+ exitCode: null,
65
+ error: null,
66
+ // Schedule reference (if spawned by scheduler)
67
+ scheduleId: options.scheduleId || null,
68
+ // Attach support
69
+ socketPath: null,
70
+ attachable: false,
71
+ };
72
+
73
+ addTask(task);
74
+
75
+ // Fork a watcher process that will manage the claude process
76
+ const watcherConfig = {
77
+ outputFormat,
78
+ jsonSchema: options.jsonSchema || null,
79
+ silentJsonOutput: options.silentJsonOutput || false,
80
+ model,
81
+ };
82
+
83
+ // Use attachable watcher by default (unless explicitly disabled)
84
+ // Attachable watcher uses node-pty and creates a Unix socket for attach/detach
85
+ const useAttachable = options.attachable !== false;
86
+ const watcherScript = useAttachable
87
+ ? join(__dirname, 'attachable-watcher.js')
88
+ : join(__dirname, 'watcher.js');
89
+
90
+ const watcher = fork(
91
+ watcherScript,
92
+ [id, cwd, logFile, JSON.stringify(args), JSON.stringify(watcherConfig)],
93
+ {
94
+ detached: true,
95
+ stdio: 'ignore',
96
+ }
97
+ );
98
+
99
+ watcher.unref();
100
+
101
+ // Return task immediately - watcher will update PID async
102
+ return task;
103
+ }
104
+
105
+ export function isProcessRunning(pid) {
106
+ if (!pid) return false;
107
+ try {
108
+ process.kill(pid, 0);
109
+ return true;
110
+ } catch {
111
+ return false;
112
+ }
113
+ }
114
+
115
+ export function killTask(pid) {
116
+ if (!pid) return false;
117
+ try {
118
+ process.kill(pid, 'SIGTERM');
119
+ return true;
120
+ } catch {
121
+ return false;
122
+ }
123
+ }