@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,150 +0,0 @@
1
- const readline = require('readline');
2
- const { loadSettings, saveSettings } = require('../../lib/settings');
3
- const { VALID_PROVIDERS, normalizeProviderName } = require('../../lib/provider-names');
4
- const { detectProviders, getProvider } = require('../../src/providers');
5
-
6
- function question(rl, prompt) {
7
- return new Promise((resolve) => {
8
- rl.question(prompt, (answer) => resolve(answer.trim()));
9
- });
10
- }
11
-
12
- async function providersCommand() {
13
- const detected = await detectProviders();
14
- const settings = loadSettings();
15
-
16
- console.log('\nProvider Status Default Level Model CLI Path');
17
- console.log('─'.repeat(70));
18
-
19
- for (const [name, status] of Object.entries(detected)) {
20
- const provider = getProvider(name);
21
- const statusIcon = status.available ? '✓ found' : '✗ not found';
22
- const providerSettings = settings.providerSettings?.[name] || {};
23
- const defaultLevel = providerSettings.defaultLevel || provider.getDefaultLevel();
24
- const levelOverrides = providerSettings.levelOverrides || {};
25
- const modelSpec = provider.resolveModelSpec(defaultLevel, levelOverrides);
26
- const modelLabel = modelSpec?.model || '-';
27
- const cliPath = status.available ? await provider.getCliPath() : '-';
28
- const isDefault = settings.defaultProvider === name ? ' (default)' : '';
29
-
30
- console.log(
31
- `${provider.displayName.padEnd(12)} ${statusIcon.padEnd(12)} ${defaultLevel.padEnd(
32
- 14
33
- )} ${modelLabel.padEnd(16)} ${cliPath}${isDefault}`
34
- );
35
- }
36
-
37
- console.log('\nCommands:');
38
- console.log(' zeroshot providers set-default <provider> Set default provider');
39
- console.log(' zeroshot providers setup <provider> Configure a provider');
40
- console.log(
41
- '\nNote: Authentication is managed by each CLI; zeroshot does not validate login or API keys.'
42
- );
43
- }
44
-
45
- function setDefaultCommand(args) {
46
- const provider = normalizeProviderName(args[0]);
47
- if (!VALID_PROVIDERS.includes(provider)) {
48
- console.error(`Invalid provider: ${args[0]}`);
49
- process.exit(1);
50
- }
51
-
52
- const settings = loadSettings();
53
- settings.defaultProvider = provider;
54
- saveSettings(settings);
55
-
56
- console.log(`Default provider set to: ${provider}`);
57
- }
58
-
59
- async function setupCommand(args) {
60
- const provider = normalizeProviderName(args[0]);
61
- if (!provider) {
62
- console.error('Provider is required (claude, codex, gemini)');
63
- process.exit(1);
64
- }
65
-
66
- const providerModule = getProvider(provider);
67
-
68
- console.log(`\n${providerModule.displayName} Setup\n`);
69
-
70
- const available = await providerModule.isAvailable();
71
- if (!available) {
72
- console.log(`✗ ${providerModule.cliCommand} CLI not found`);
73
- console.log('\nInstall with:');
74
- console.log(providerModule.getInstallInstructions());
75
- return;
76
- }
77
- console.log(`✓ ${providerModule.cliCommand} CLI found`);
78
-
79
- console.log('\nAuth is user-managed; run the CLI login flow if needed:');
80
- console.log(providerModule.getAuthInstructions());
81
-
82
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
83
-
84
- try {
85
- const levels = providerModule.getLevelMapping();
86
- const levelKeys = Object.keys(levels).sort((a, b) => levels[a].rank - levels[b].rank);
87
-
88
- console.log('\nAvailable levels (cheapest to most capable):');
89
- levelKeys.forEach((level, i) => {
90
- const spec = levels[level];
91
- const reasoning = spec.reasoningEffort ? ` (reasoning: ${spec.reasoningEffort})` : '';
92
- console.log(` ${i + 1}) ${level} -> ${spec.model}${reasoning}`);
93
- });
94
-
95
- const maxIdxRaw = await question(rl, `\nMax level (cost ceiling) [${levelKeys.length}]: `);
96
- const minIdxRaw = await question(rl, 'Min level (floor) [1]: ');
97
- const maxIdxNum = Math.min(
98
- levelKeys.length,
99
- Math.max(1, parseInt(maxIdxRaw) || levelKeys.length)
100
- );
101
- const minIdxNum = Math.min(levelKeys.length, Math.max(1, parseInt(minIdxRaw) || 1));
102
- const minIdx = Math.min(minIdxNum, maxIdxNum);
103
- const maxIdx = Math.max(minIdxNum, maxIdxNum);
104
-
105
- const defaultIdxRaw = await question(
106
- rl,
107
- `Default level (between ${minIdx}-${maxIdx}) [${maxIdx}]: `
108
- );
109
- const defaultIdxNum = Math.min(maxIdx, Math.max(minIdx, parseInt(defaultIdxRaw) || maxIdx));
110
-
111
- const levelOverrides = {};
112
- const wantsOverrides = await question(rl, 'Override models per level? [y/N]: ');
113
- if (wantsOverrides.toLowerCase() === 'y') {
114
- const catalog = Object.keys(providerModule.getModelCatalog());
115
- for (const level of levelKeys) {
116
- const modelChoice = await question(rl, `Model for ${level} (${catalog.join(', ')}): `);
117
- if (modelChoice) levelOverrides[level] = { model: modelChoice };
118
- if (provider === 'codex') {
119
- const reasoning = await question(rl, `Reasoning for ${level} (low|medium|high|xhigh): `);
120
- if (reasoning) {
121
- levelOverrides[level] = {
122
- ...(levelOverrides[level] || {}),
123
- reasoningEffort: reasoning,
124
- };
125
- }
126
- }
127
- }
128
- }
129
-
130
- const settings = loadSettings();
131
- settings.providerSettings = settings.providerSettings || {};
132
- settings.providerSettings[provider] = {
133
- maxLevel: levelKeys[maxIdx - 1],
134
- minLevel: levelKeys[minIdx - 1],
135
- defaultLevel: levelKeys[defaultIdxNum - 1],
136
- levelOverrides,
137
- };
138
- saveSettings(settings);
139
-
140
- console.log(`\n✓ ${providerModule.displayName} configured successfully`);
141
- } finally {
142
- rl.close();
143
- }
144
- }
145
-
146
- module.exports = {
147
- providersCommand,
148
- setDefaultCommand,
149
- setupCommand,
150
- };
@@ -1,211 +0,0 @@
1
- /**
2
- * First-Run Setup Wizard
3
- *
4
- * Interactive setup on first use:
5
- * - Welcome banner
6
- * - Max model ceiling selection (sonnet/opus/haiku)
7
- * - Auto-update preference
8
- * - Marks setup as complete
9
- */
10
-
11
- const readline = require('readline');
12
- const { loadSettings, saveSettings } = require('../../lib/settings');
13
- const { detectProviders } = require('../../src/providers');
14
-
15
- /**
16
- * Print welcome banner
17
- */
18
- function printWelcome() {
19
- console.log(`
20
- ╔═══════════════════════════════════════════════════════════════╗
21
- ║ ║
22
- ║ Welcome to Zeroshot! ║
23
- ║ Multi-agent orchestration engine ║
24
- ║ ║
25
- ║ Let's configure a few settings to get started. ║
26
- ║ ║
27
- ╚═══════════════════════════════════════════════════════════════╝
28
- `);
29
- }
30
-
31
- /**
32
- * Create readline interface
33
- * @returns {readline.Interface}
34
- */
35
- function createReadline() {
36
- return readline.createInterface({
37
- input: process.stdin,
38
- output: process.stdout,
39
- });
40
- }
41
-
42
- /**
43
- * Prompt for provider selection
44
- * @param {readline.Interface} rl
45
- * @param {object} detected
46
- * @returns {Promise<string>}
47
- */
48
- function promptProvider(rl, detected) {
49
- console.log('\nWhich AI provider would you like to use by default?\n');
50
-
51
- const available = Object.entries(detected).filter(([_, status]) => status.available);
52
-
53
- if (available.length === 0) {
54
- console.log('No AI CLI tools detected. Please install one of:');
55
- console.log(' - Claude Code: npm install -g @anthropic-ai/claude-code');
56
- console.log(' - Codex CLI: npm install -g @openai/codex');
57
- console.log(' - Gemini CLI: npm install -g @google/gemini-cli');
58
- process.exit(1);
59
- }
60
-
61
- available.forEach(([name], i) => {
62
- console.log(` ${i + 1}) ${name} (installed)`);
63
- });
64
-
65
- return new Promise((resolve) => {
66
- rl.question('\nChoice [1]: ', (answer) => {
67
- const idx = parseInt(answer) - 1 || 0;
68
- resolve(available[idx]?.[0] || available[0][0]);
69
- });
70
- });
71
- }
72
-
73
- /**
74
- * Prompt for model selection
75
- * @param {readline.Interface} rl
76
- * @returns {Promise<string>}
77
- */
78
- function promptModel(rl) {
79
- return new Promise((resolve) => {
80
- console.log('What is the maximum Claude model agents can use? (cost ceiling)\n');
81
- console.log(' 1) sonnet - Agents can use sonnet or haiku (recommended)');
82
- console.log(' 2) opus - Agents can use opus, sonnet, or haiku');
83
- console.log(' 3) haiku - Agents can only use haiku (lowest cost)\n');
84
-
85
- rl.question('Enter 1, 2, or 3 [2]: ', (answer) => {
86
- const choice = answer.trim() || '2';
87
- switch (choice) {
88
- case '2':
89
- resolve('opus');
90
- break;
91
- case '3':
92
- resolve('haiku');
93
- break;
94
- default:
95
- resolve('sonnet');
96
- }
97
- });
98
- });
99
- }
100
-
101
- /**
102
- * Prompt for auto-update preference
103
- * @param {readline.Interface} rl
104
- * @returns {Promise<boolean>}
105
- */
106
- function promptAutoUpdate(rl) {
107
- return new Promise((resolve) => {
108
- console.log('\nWould you like zeroshot to check for updates automatically?');
109
- console.log('(Checks npm registry every 24 hours)\n');
110
-
111
- rl.question('Enable auto-update checks? [Y/n]: ', (answer) => {
112
- const normalized = answer.trim().toLowerCase();
113
- // Default to yes if empty or starts with 'y'
114
- resolve(normalized === '' || normalized === 'y' || normalized === 'yes');
115
- });
116
- });
117
- }
118
-
119
- /**
120
- * Print completion message
121
- * @param {object} settings - Saved settings
122
- */
123
- function printComplete(settings) {
124
- console.log(`
125
- ╔═══════════════════════════════════════════════════════════════╗
126
- ║ Setup complete! ║
127
- ╚═══════════════════════════════════════════════════════════════╝
128
-
129
- Your settings:
130
- • Provider: ${settings.defaultProvider}
131
- • Max model: ${settings.maxModel} (Claude ceiling)
132
- • Auto-updates: ${settings.autoCheckUpdates ? 'enabled' : 'disabled'}
133
-
134
- Change anytime with: zeroshot settings set <key> <value>
135
-
136
- Get started:
137
- zeroshot run "Fix the bug in auth.js"
138
- zeroshot run 123 (GitHub issue number)
139
- zeroshot --help
140
-
141
- `);
142
- }
143
-
144
- /**
145
- * Check if first-run setup is needed
146
- * @param {object} settings - Current settings
147
- * @returns {boolean}
148
- */
149
- function detectFirstRun(settings) {
150
- return !settings.firstRunComplete;
151
- }
152
-
153
- /**
154
- * Main entry point - run first-time setup if needed
155
- * @param {object} options
156
- * @param {boolean} options.quiet - Skip interactive prompts
157
- * @returns {Promise<boolean>} True if setup was run
158
- */
159
- async function checkFirstRun(options = {}) {
160
- const settings = loadSettings();
161
-
162
- // Already completed setup
163
- if (!detectFirstRun(settings)) {
164
- return false;
165
- }
166
-
167
- // Quiet mode - use defaults, mark complete
168
- if (options.quiet) {
169
- settings.firstRunComplete = true;
170
- saveSettings(settings);
171
- return true;
172
- }
173
-
174
- // Interactive setup
175
- printWelcome();
176
-
177
- const rl = createReadline();
178
-
179
- try {
180
- const detected = await detectProviders();
181
- const provider = await promptProvider(rl, detected);
182
- settings.defaultProvider = provider;
183
-
184
- // Model ceiling selection
185
- const model = await promptModel(rl);
186
- settings.maxModel = model;
187
-
188
- // Auto-update preference
189
- const autoUpdate = await promptAutoUpdate(rl);
190
- settings.autoCheckUpdates = autoUpdate;
191
-
192
- // Mark complete
193
- settings.firstRunComplete = true;
194
- saveSettings(settings);
195
-
196
- // Print completion
197
- printComplete(settings);
198
-
199
- return true;
200
- } finally {
201
- rl.close();
202
- }
203
- }
204
-
205
- module.exports = {
206
- checkFirstRun,
207
- // Exported for testing
208
- detectFirstRun,
209
- printWelcome,
210
- printComplete,
211
- };
@@ -1,281 +0,0 @@
1
- /**
2
- * Update Checker - Checks npm registry for newer versions
3
- *
4
- * Features:
5
- * - 24-hour check interval (avoids registry spam)
6
- * - 5-second timeout (non-blocking if offline)
7
- * - Interactive prompt for manual update
8
- * - Respects quiet mode (no prompts in CI/scripts)
9
- */
10
-
11
- const https = require('https');
12
- const { spawn } = require('child_process');
13
- const readline = require('readline');
14
- const { loadSettings, saveSettings } = require('../../lib/settings');
15
-
16
- // 24 hours in milliseconds
17
- const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
18
-
19
- // Timeout for npm registry fetch (5 seconds)
20
- const FETCH_TIMEOUT_MS = 5000;
21
-
22
- // npm registry URL
23
- const REGISTRY_URL = 'https://registry.npmjs.org/@covibes/zeroshot/latest';
24
-
25
- /**
26
- * Get current package version
27
- * @returns {string}
28
- */
29
- function getCurrentVersion() {
30
- const pkg = require('../../package.json');
31
- return pkg.version;
32
- }
33
-
34
- /**
35
- * Compare semver versions
36
- * @param {string} current - Current version (e.g., "1.5.0")
37
- * @param {string} latest - Latest version (e.g., "1.6.0")
38
- * @returns {boolean} True if latest > current
39
- */
40
- function isNewerVersion(current, latest) {
41
- const currentParts = current.split('.').map(Number);
42
- const latestParts = latest.split('.').map(Number);
43
-
44
- for (let i = 0; i < 3; i++) {
45
- const c = currentParts[i] || 0;
46
- const l = latestParts[i] || 0;
47
- if (l > c) return true;
48
- if (l < c) return false;
49
- }
50
- return false;
51
- }
52
-
53
- /**
54
- * Fetch latest version from npm registry
55
- * @returns {Promise<string|null>} Latest version or null on failure
56
- */
57
- function fetchLatestVersion() {
58
- return new Promise((resolve) => {
59
- const req = https.get(REGISTRY_URL, { timeout: FETCH_TIMEOUT_MS }, (res) => {
60
- if (res.statusCode !== 200) {
61
- resolve(null);
62
- return;
63
- }
64
-
65
- let data = '';
66
- res.on('data', (chunk) => {
67
- data += chunk;
68
- });
69
-
70
- res.on('end', () => {
71
- try {
72
- const json = JSON.parse(data);
73
- resolve(json.version || null);
74
- } catch {
75
- resolve(null);
76
- }
77
- });
78
- });
79
-
80
- req.on('error', () => {
81
- resolve(null);
82
- });
83
-
84
- req.on('timeout', () => {
85
- req.destroy();
86
- resolve(null);
87
- });
88
-
89
- // Additional safety timeout
90
- setTimeout(() => {
91
- req.destroy();
92
- resolve(null);
93
- }, FETCH_TIMEOUT_MS + 1000);
94
- });
95
- }
96
-
97
- /**
98
- * Prompt user for update confirmation
99
- * @param {string} currentVersion
100
- * @param {string} latestVersion
101
- * @returns {Promise<boolean>} True if user wants to update
102
- */
103
- function promptForUpdate(currentVersion, latestVersion) {
104
- return new Promise((resolve) => {
105
- const rl = readline.createInterface({
106
- input: process.stdin,
107
- output: process.stdout,
108
- });
109
-
110
- console.log(`\n📦 Update available: ${currentVersion} → ${latestVersion}`);
111
- rl.question(' Install now? [y/N] ', (answer) => {
112
- rl.close();
113
- resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
114
- });
115
- });
116
- }
117
-
118
- /**
119
- * Check if we have write permission to npm global directory
120
- * @returns {boolean} True if we can write to npm global prefix
121
- */
122
- function canWriteToNpmGlobal() {
123
- const { execSync } = require('child_process');
124
- const fs = require('fs');
125
-
126
- try {
127
- // Get npm global prefix (e.g., /usr/lib or /home/user/.nvm/versions/node/...)
128
- const prefix = execSync('npm config get prefix', { encoding: 'utf8' }).trim();
129
- const globalModulesDir = require('path').join(prefix, 'lib', 'node_modules');
130
-
131
- // Check if directory exists and is writable
132
- fs.accessSync(globalModulesDir, fs.constants.W_OK);
133
- return true;
134
- } catch {
135
- return false;
136
- }
137
- }
138
-
139
- /**
140
- * Run npm install to update the package
141
- * @returns {Promise<boolean>} True if update succeeded
142
- */
143
- function runUpdate() {
144
- return new Promise((resolve) => {
145
- // Check permissions BEFORE attempting update
146
- if (!canWriteToNpmGlobal()) {
147
- console.log('\n⚠️ Cannot auto-update: no write permission to npm global directory.');
148
- console.log(' Run manually with sudo:');
149
- console.log(' sudo npm install -g @covibes/zeroshot@latest\n');
150
- resolve(false);
151
- return;
152
- }
153
-
154
- console.log('\n📥 Installing update...');
155
-
156
- const proc = spawn('npm', ['install', '-g', '@covibes/zeroshot@latest'], {
157
- stdio: 'inherit',
158
- shell: true,
159
- });
160
-
161
- proc.on('close', (code) => {
162
- if (code === 0) {
163
- console.log('✅ Update installed successfully!');
164
- console.log(' Restart zeroshot to use the new version.\n');
165
- resolve(true);
166
- } else {
167
- console.log('❌ Update failed. Try manually:');
168
- console.log(' sudo npm install -g @covibes/zeroshot@latest\n');
169
- resolve(false);
170
- }
171
- });
172
-
173
- proc.on('error', () => {
174
- console.log('❌ Update failed. Try manually:');
175
- console.log(' sudo npm install -g @covibes/zeroshot@latest\n');
176
- resolve(false);
177
- });
178
- });
179
- }
180
-
181
- /**
182
- * Check if update check should run
183
- * @param {object} settings - Current settings
184
- * @returns {boolean}
185
- */
186
- function shouldCheckForUpdates(settings) {
187
- // Disabled by user
188
- if (!settings.autoCheckUpdates) {
189
- return false;
190
- }
191
-
192
- // Never checked before
193
- if (!settings.lastUpdateCheckAt) {
194
- return true;
195
- }
196
-
197
- // Check if 24 hours have passed
198
- const elapsed = Date.now() - settings.lastUpdateCheckAt;
199
- return elapsed >= CHECK_INTERVAL_MS;
200
- }
201
-
202
- /**
203
- * Main entry point - check for updates
204
- * @param {object} options
205
- * @param {boolean} options.quiet - Skip interactive prompts
206
- * @returns {Promise<void>}
207
- */
208
- async function checkForUpdates(options = {}) {
209
- const settings = loadSettings();
210
-
211
- // Skip check if not due
212
- if (!shouldCheckForUpdates(settings)) {
213
- return;
214
- }
215
-
216
- const currentVersion = getCurrentVersion();
217
- const latestVersion = await fetchLatestVersion();
218
-
219
- // Update last check timestamp regardless of result
220
- settings.lastUpdateCheckAt = Date.now();
221
- saveSettings(settings);
222
-
223
- // Network failure - silently skip
224
- if (!latestVersion) {
225
- return;
226
- }
227
-
228
- // No update available
229
- if (!isNewerVersion(currentVersion, latestVersion)) {
230
- return;
231
- }
232
-
233
- // Already notified about this version
234
- if (settings.lastSeenVersion === latestVersion) {
235
- return;
236
- }
237
-
238
- // Update lastSeenVersion so we don't nag about the same version
239
- settings.lastSeenVersion = latestVersion;
240
- saveSettings(settings);
241
-
242
- // Check write permissions upfront
243
- const hasWriteAccess = canWriteToNpmGlobal();
244
-
245
- // Quiet mode - just inform, no prompt
246
- if (options.quiet) {
247
- console.log(`📦 Update available: ${currentVersion} → ${latestVersion}`);
248
- if (hasWriteAccess) {
249
- console.log(' Run: npm install -g @covibes/zeroshot@latest\n');
250
- } else {
251
- console.log(' Run: sudo npm install -g @covibes/zeroshot@latest\n');
252
- }
253
- return;
254
- }
255
-
256
- // No write permission - inform user but don't offer interactive prompt
257
- // (they'd say yes then get an error, which is frustrating UX)
258
- if (!hasWriteAccess) {
259
- console.log(`\n📦 Update available: ${currentVersion} → ${latestVersion}`);
260
- console.log(' Run: sudo npm install -g @covibes/zeroshot@latest\n');
261
- return;
262
- }
263
-
264
- // Interactive mode - prompt for update (only if we have write access)
265
- const wantsUpdate = await promptForUpdate(currentVersion, latestVersion);
266
- if (wantsUpdate) {
267
- await runUpdate();
268
- }
269
- }
270
-
271
- module.exports = {
272
- checkForUpdates,
273
- // Exported for testing and CLI update command
274
- getCurrentVersion,
275
- isNewerVersion,
276
- fetchLatestVersion,
277
- runUpdate,
278
- shouldCheckForUpdates,
279
- canWriteToNpmGlobal,
280
- CHECK_INTERVAL_MS,
281
- };