@covibes/zeroshot 5.4.0 → 5.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +8 -465
  3. package/cli/index.js +101 -5231
  4. package/package.json +16 -130
  5. package/CHANGELOG.md +0 -537
  6. package/cli/commands/providers.js +0 -149
  7. package/cli/lib/first-run.js +0 -211
  8. package/cli/lib/update-checker.js +0 -281
  9. package/cli/message-formatter-utils.js +0 -75
  10. package/cli/message-formatters-normal.js +0 -275
  11. package/cli/message-formatters-watch.js +0 -181
  12. package/cluster-templates/base-templates/debug-workflow.json +0 -375
  13. package/cluster-templates/base-templates/full-workflow.json +0 -581
  14. package/cluster-templates/base-templates/single-worker.json +0 -63
  15. package/cluster-templates/base-templates/worker-validator.json +0 -208
  16. package/cluster-templates/conductor-bootstrap.json +0 -124
  17. package/docker/zeroshot-cluster/Dockerfile +0 -152
  18. package/docker/zeroshot-cluster/pre-baked-deps.json +0 -28
  19. package/lib/completion.js +0 -174
  20. package/lib/docker-config.js +0 -220
  21. package/lib/git-remote-utils.js +0 -165
  22. package/lib/id-detector.js +0 -56
  23. package/lib/provider-defaults.js +0 -62
  24. package/lib/provider-detection.js +0 -59
  25. package/lib/provider-names.js +0 -57
  26. package/lib/settings/claude-auth.js +0 -78
  27. package/lib/settings.js +0 -503
  28. package/lib/stream-json-parser.js +0 -10
  29. package/scripts/fix-node-pty-permissions.js +0 -75
  30. package/scripts/record-demo.sh +0 -279
  31. package/scripts/setup-merge-queue.sh +0 -170
  32. package/scripts/test-install.sh +0 -40
  33. package/scripts/validate-templates.js +0 -100
  34. package/src/agent/agent-config.js +0 -246
  35. package/src/agent/agent-context-builder.js +0 -444
  36. package/src/agent/agent-hook-executor.js +0 -599
  37. package/src/agent/agent-lifecycle.js +0 -743
  38. package/src/agent/agent-stuck-detector.js +0 -256
  39. package/src/agent/agent-task-executor.js +0 -1690
  40. package/src/agent/agent-trigger-evaluator.js +0 -67
  41. package/src/agent/output-extraction.js +0 -217
  42. package/src/agent/output-reformatter.js +0 -175
  43. package/src/agent/schema-utils.js +0 -146
  44. package/src/agent-wrapper.js +0 -579
  45. package/src/agents/git-pusher-template.js +0 -285
  46. package/src/attach/attach-client.js +0 -438
  47. package/src/attach/attach-server.js +0 -543
  48. package/src/attach/index.js +0 -35
  49. package/src/attach/protocol.js +0 -220
  50. package/src/attach/ring-buffer.js +0 -121
  51. package/src/attach/socket-discovery.js +0 -267
  52. package/src/claude-task-runner.js +0 -574
  53. package/src/config-router.js +0 -80
  54. package/src/config-validator.js +0 -1925
  55. package/src/copy-worker.js +0 -43
  56. package/src/input-helpers.js +0 -65
  57. package/src/isolation-manager.js +0 -1471
  58. package/src/issue-providers/README.md +0 -305
  59. package/src/issue-providers/azure-devops-provider.js +0 -273
  60. package/src/issue-providers/base-provider.js +0 -232
  61. package/src/issue-providers/github-provider.js +0 -179
  62. package/src/issue-providers/gitlab-provider.js +0 -241
  63. package/src/issue-providers/index.js +0 -196
  64. package/src/issue-providers/jira-provider.js +0 -239
  65. package/src/ledger.js +0 -631
  66. package/src/lib/safe-exec.js +0 -88
  67. package/src/logic-engine.js +0 -223
  68. package/src/message-bus-bridge.js +0 -139
  69. package/src/message-bus.js +0 -249
  70. package/src/name-generator.js +0 -232
  71. package/src/orchestrator.js +0 -2815
  72. package/src/preflight.js +0 -639
  73. package/src/process-metrics.js +0 -608
  74. package/src/providers/anthropic/cli-builder.js +0 -73
  75. package/src/providers/anthropic/index.js +0 -204
  76. package/src/providers/anthropic/models.js +0 -23
  77. package/src/providers/anthropic/output-parser.js +0 -177
  78. package/src/providers/base-provider.js +0 -251
  79. package/src/providers/capabilities.js +0 -60
  80. package/src/providers/google/cli-builder.js +0 -55
  81. package/src/providers/google/index.js +0 -116
  82. package/src/providers/google/models.js +0 -24
  83. package/src/providers/google/output-parser.js +0 -101
  84. package/src/providers/index.js +0 -91
  85. package/src/providers/openai/cli-builder.js +0 -133
  86. package/src/providers/openai/index.js +0 -136
  87. package/src/providers/openai/models.js +0 -21
  88. package/src/providers/openai/output-parser.js +0 -143
  89. package/src/providers/opencode/cli-builder.js +0 -42
  90. package/src/providers/opencode/index.js +0 -103
  91. package/src/providers/opencode/models.js +0 -55
  92. package/src/providers/opencode/output-parser.js +0 -122
  93. package/src/schemas/sub-cluster.js +0 -185
  94. package/src/status-footer.js +0 -1026
  95. package/src/sub-cluster-wrapper.js +0 -601
  96. package/src/task-runner.js +0 -30
  97. package/src/template-resolver.js +0 -372
  98. package/src/tui/CHANGES.txt +0 -133
  99. package/src/tui/LAYOUT.md +0 -261
  100. package/src/tui/README.txt +0 -192
  101. package/src/tui/TWO-LEVEL-NAVIGATION.md +0 -186
  102. package/src/tui/data-poller.js +0 -349
  103. package/src/tui/demo.js +0 -208
  104. package/src/tui/formatters.js +0 -129
  105. package/src/tui/index.js +0 -193
  106. package/src/tui/keybindings.js +0 -324
  107. package/src/tui/layout.js +0 -334
  108. package/src/tui/renderer.js +0 -184
  109. package/task-lib/attachable-watcher.js +0 -309
  110. package/task-lib/claude-recovery.js +0 -156
  111. package/task-lib/commands/clean.js +0 -50
  112. package/task-lib/commands/episodes.js +0 -105
  113. package/task-lib/commands/get-log-path.js +0 -23
  114. package/task-lib/commands/kill.js +0 -32
  115. package/task-lib/commands/list.js +0 -105
  116. package/task-lib/commands/logs.js +0 -453
  117. package/task-lib/commands/resume.js +0 -42
  118. package/task-lib/commands/run.js +0 -57
  119. package/task-lib/commands/schedule.js +0 -105
  120. package/task-lib/commands/scheduler-cmd.js +0 -96
  121. package/task-lib/commands/schedules.js +0 -148
  122. package/task-lib/commands/status.js +0 -44
  123. package/task-lib/commands/unschedule.js +0 -16
  124. package/task-lib/completion.js +0 -9
  125. package/task-lib/config.js +0 -8
  126. package/task-lib/name-generator.js +0 -230
  127. package/task-lib/package.json +0 -3
  128. package/task-lib/runner.js +0 -201
  129. package/task-lib/scheduler.js +0 -252
  130. package/task-lib/store.js +0 -529
  131. package/task-lib/tui/formatters.js +0 -215
  132. package/task-lib/tui/index.js +0 -197
  133. package/task-lib/tui/layout.js +0 -111
  134. package/task-lib/tui/renderer.js +0 -129
  135. package/task-lib/tui.js +0 -425
  136. package/task-lib/watcher.js +0 -226
@@ -1,149 +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, opencode)');
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') continue;
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
- const settings = loadSettings();
130
- settings.providerSettings = settings.providerSettings || {};
131
- settings.providerSettings[provider] = {
132
- maxLevel: levelKeys[maxIdx - 1],
133
- minLevel: levelKeys[minIdx - 1],
134
- defaultLevel: levelKeys[defaultIdxNum - 1],
135
- levelOverrides,
136
- };
137
- saveSettings(settings);
138
-
139
- console.log(`\n✓ ${providerModule.displayName} configured successfully`);
140
- } finally {
141
- rl.close();
142
- }
143
- }
144
-
145
- module.exports = {
146
- providersCommand,
147
- setDefaultCommand,
148
- setupCommand,
149
- };
@@ -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
- };