@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
package/src/preflight.js DELETED
@@ -1,639 +0,0 @@
1
- /**
2
- * Preflight Validation - Check all dependencies before starting
3
- *
4
- * Validates:
5
- * - Selected provider CLI installed
6
- * - gh CLI installed and authenticated (if using issue numbers)
7
- * - Docker available (if using --docker)
8
- *
9
- * Provides CLEAR, ACTIONABLE error messages with recovery instructions.
10
- */
11
-
12
- const { execSync } = require('./lib/safe-exec'); // Enforces timeouts
13
- const path = require('path');
14
- const fs = require('fs');
15
- const os = require('os');
16
- const {
17
- isValidAnthropicKey,
18
- ANTHROPIC_KEY_PREFIX,
19
- resolveClaudeAuth,
20
- } = require('../lib/settings/claude-auth.js');
21
- const { loadSettings, getClaudeCommand } = require('../lib/settings.js');
22
- const { normalizeProviderName } = require('../lib/provider-names');
23
- const { detectGitContext } = require('../lib/git-remote-utils');
24
-
25
- /**
26
- * Validation result
27
- * @typedef {Object} ValidationResult
28
- * @property {boolean} valid - Whether validation passed
29
- * @property {string[]} errors - Fatal errors that block execution
30
- * @property {string[]} warnings - Non-fatal warnings
31
- */
32
-
33
- /**
34
- * Format error with recovery instructions
35
- * @param {string} title - Error title
36
- * @param {string} detail - Error details
37
- * @param {string[]} recovery - Recovery steps
38
- * @returns {string}
39
- */
40
- function formatError(title, detail, recovery) {
41
- let msg = `\n❌ ${title}\n`;
42
- msg += ` ${detail}\n`;
43
- if (recovery.length > 0) {
44
- msg += `\n To fix:\n`;
45
- recovery.forEach((step, i) => {
46
- msg += ` ${i + 1}. ${step}\n`;
47
- });
48
- }
49
- return msg;
50
- }
51
-
52
- /**
53
- * Check if a command exists (cross-platform)
54
- * @param {string} cmd - Command to check
55
- * @returns {boolean}
56
- */
57
- function commandExists(cmd) {
58
- try {
59
- // Windows uses 'where', Unix uses 'which'
60
- const checkCmd = process.platform === 'win32' ? `where ${cmd}` : `which ${cmd}`;
61
- execSync(checkCmd, { encoding: 'utf8', stdio: 'pipe' });
62
- return true;
63
- } catch {
64
- return false;
65
- }
66
- }
67
-
68
- /**
69
- * Get Claude CLI version
70
- * @param {string} claudeCommand - Optional custom Claude command (e.g., 'ccr code')
71
- * @returns {{ installed: boolean, version: string | null, error: string | null }}
72
- */
73
- function getClaudeVersion(claudeCommand = 'claude') {
74
- // Parse command parts
75
- const parts = claudeCommand.trim().split(/\s+/);
76
- const command = parts[0];
77
- const extraArgs = parts.slice(1);
78
-
79
- try {
80
- const versionArgs = [...extraArgs, '--version'];
81
- const versionCmd = [command, ...versionArgs].join(' ');
82
- const output = execSync(versionCmd, { encoding: 'utf8', stdio: 'pipe' });
83
- const match = output.match(/(\d+\.\d+\.\d+)/);
84
- return {
85
- installed: true,
86
- version: match ? match[1] : 'unknown',
87
- error: null,
88
- };
89
- } catch (err) {
90
- if (err.message.includes('command not found') || err.message.includes('not found')) {
91
- return {
92
- installed: false,
93
- version: null,
94
- error: `Command '${command}' not installed`,
95
- };
96
- }
97
- return {
98
- installed: false,
99
- version: null,
100
- error: err.message,
101
- };
102
- }
103
- }
104
-
105
- /**
106
- * Check macOS Keychain for Claude Code credentials
107
- * @returns {{ authenticated: boolean, error: string | null }}
108
- */
109
- function checkMacOsKeychain() {
110
- if (os.platform() !== 'darwin') {
111
- return { authenticated: false, error: 'Not macOS' };
112
- }
113
-
114
- try {
115
- // Check if Claude Code credentials exist in Keychain
116
- execSync('security find-generic-password -s "Claude Code-credentials"', {
117
- encoding: 'utf8',
118
- stdio: 'pipe',
119
- timeout: 2000,
120
- });
121
- return { authenticated: true, error: null };
122
- } catch {
123
- return { authenticated: false, error: 'No credentials in Keychain' };
124
- }
125
- }
126
-
127
- /**
128
- * Check Claude CLI authentication status
129
- * @returns {{ authenticated: boolean, error: string | null, configDir: string, method?: string }}
130
- */
131
- function checkClaudeAuth() {
132
- const configDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
133
-
134
- // Helper to create consistent result objects
135
- const authResult = (authenticated, error = null, method = null) => ({
136
- authenticated,
137
- error,
138
- configDir,
139
- ...(method && { method }),
140
- });
141
-
142
- // Check for Bedrock bearer token (highest priority)
143
- if (process.env.AWS_BEARER_TOKEN_BEDROCK) {
144
- if (!process.env.AWS_REGION) {
145
- return authResult(false, 'AWS_BEARER_TOKEN_BEDROCK set but AWS_REGION is missing');
146
- }
147
- return authResult(true, null, 'bedrock_api_key');
148
- }
149
-
150
- // Check for ANTHROPIC_API_KEY environment variable
151
- const apiKeyEnv = process.env.ANTHROPIC_API_KEY;
152
- if (apiKeyEnv) {
153
- if (!isValidAnthropicKey(apiKeyEnv)) {
154
- return authResult(false, `ANTHROPIC_API_KEY must start with ${ANTHROPIC_KEY_PREFIX}`);
155
- }
156
- return authResult(true, null, 'env_api_key');
157
- }
158
-
159
- // Check for settings-based auth (anthropicApiKey or bedrockApiKey in settings)
160
- const settings = loadSettings();
161
- const settingsAuth = resolveClaudeAuth(settings);
162
- if (settingsAuth.ANTHROPIC_API_KEY) {
163
- return authResult(true, null, 'settings_api_key');
164
- }
165
- if (settingsAuth.AWS_BEARER_TOKEN_BEDROCK) {
166
- if (!settingsAuth.AWS_REGION && !process.env.AWS_REGION) {
167
- return authResult(false, 'Bedrock configured in settings but AWS_REGION is missing');
168
- }
169
- return authResult(true, null, 'settings_bedrock');
170
- }
171
-
172
- const credentialsPath = path.join(configDir, '.credentials.json');
173
-
174
- // Check if credentials file exists
175
- if (!fs.existsSync(credentialsPath)) {
176
- // No credentials file - check macOS Keychain as fallback
177
- // Only use Keychain when using default config dir (not custom CLAUDE_CONFIG_DIR)
178
- if (!process.env.CLAUDE_CONFIG_DIR && checkMacOsKeychain().authenticated) {
179
- return authResult(true, null, 'keychain');
180
- }
181
- return authResult(false, 'No credentials file found');
182
- }
183
-
184
- // Check if credentials file has content
185
- try {
186
- const content = fs.readFileSync(credentialsPath, 'utf8');
187
- const creds = JSON.parse(content);
188
-
189
- // Check for OAuth token (primary auth method)
190
- if (creds.claudeAiOauth?.accessToken) {
191
- const expiresAt = creds.claudeAiOauth.expiresAt;
192
- if (expiresAt && new Date(expiresAt) < new Date()) {
193
- return authResult(false, 'OAuth token expired');
194
- }
195
- return authResult(true);
196
- }
197
-
198
- // Check for API key auth
199
- if (creds.apiKey) {
200
- return authResult(true);
201
- }
202
-
203
- return authResult(false, 'No valid authentication found in credentials');
204
- } catch (err) {
205
- return authResult(false, `Failed to parse credentials: ${err.message}`);
206
- }
207
- }
208
-
209
- /**
210
- * Check gh CLI authentication status
211
- * @returns {{ installed: boolean, authenticated: boolean, error: string | null }}
212
- */
213
- function checkGhAuth() {
214
- // Check if gh is installed
215
- if (!commandExists('gh')) {
216
- return {
217
- installed: false,
218
- authenticated: false,
219
- error: 'gh CLI not installed',
220
- };
221
- }
222
-
223
- // Check auth status
224
- try {
225
- execSync('gh auth status', { encoding: 'utf8', stdio: 'pipe' });
226
- return {
227
- installed: true,
228
- authenticated: true,
229
- error: null,
230
- };
231
- } catch (err) {
232
- // gh auth status returns non-zero if not authenticated
233
- const stderr = err.stderr || err.message || '';
234
-
235
- if (stderr.includes('not logged in')) {
236
- return {
237
- installed: true,
238
- authenticated: false,
239
- error: 'gh CLI not authenticated',
240
- };
241
- }
242
-
243
- return {
244
- installed: true,
245
- authenticated: false,
246
- error: stderr.trim() || 'Unknown gh auth error',
247
- };
248
- }
249
- }
250
-
251
- /**
252
- * Check Docker availability
253
- * @returns {{ available: boolean, error: string | null }}
254
- */
255
- function checkDocker() {
256
- try {
257
- execSync('docker --version', { encoding: 'utf8', stdio: 'pipe' });
258
-
259
- // Also check if Docker daemon is running
260
- execSync('docker info', { encoding: 'utf8', stdio: 'pipe' });
261
-
262
- return {
263
- available: true,
264
- error: null,
265
- };
266
- } catch (err) {
267
- const stderr = err.stderr || err.message || '';
268
-
269
- if (stderr.includes('command not found') || stderr.includes('not found')) {
270
- return {
271
- available: false,
272
- error: 'Docker not installed',
273
- };
274
- }
275
-
276
- if (stderr.includes('Cannot connect') || stderr.includes('Is the docker daemon running')) {
277
- return {
278
- available: false,
279
- error: 'Docker daemon not running',
280
- };
281
- }
282
-
283
- return {
284
- available: false,
285
- error: stderr.trim() || 'Unknown Docker error',
286
- };
287
- }
288
- }
289
-
290
- function buildClaudeCommand(options) {
291
- const { command, args } = getClaudeCommand();
292
- return options.claudeCommand || [command, ...args].join(' ');
293
- }
294
-
295
- function validateClaudeProvider(options) {
296
- const errors = [];
297
- const warnings = [];
298
- const claudeCommand = buildClaudeCommand(options);
299
-
300
- const claude = getClaudeVersion(claudeCommand);
301
- if (!claude.installed) {
302
- errors.push(
303
- formatError(
304
- 'Claude command not available',
305
- claude.error,
306
- claudeCommand === 'claude'
307
- ? [
308
- 'Install Claude CLI: npm install -g @anthropic-ai/claude-code',
309
- 'Or: brew install claude (macOS)',
310
- 'Then run: claude --version',
311
- ]
312
- : [
313
- `Command '${claudeCommand}' not found`,
314
- 'Check settings: zeroshot settings',
315
- 'Update claudeCommand: zeroshot settings set claudeCommand "your-command"',
316
- 'Or install the missing command',
317
- ]
318
- )
319
- );
320
- } else if (claude.version) {
321
- const [major, minor] = claude.version.split('.').map(Number);
322
- if (major < 1 || (major === 1 && minor < 0)) {
323
- warnings.push(
324
- `⚠️ Claude CLI version ${claude.version} may be outdated. Consider upgrading.`
325
- );
326
- }
327
- }
328
-
329
- if (process.getuid && process.getuid() === 0) {
330
- errors.push(
331
- formatError(
332
- 'Running as root',
333
- 'Claude CLI refuses --dangerously-skip-permissions flag when running as root (UID 0)',
334
- [
335
- 'Run as non-root user in Docker: docker run --user 1000:1000 ...',
336
- 'Or create non-root user: adduser testuser && su - testuser',
337
- 'Or use existing node user: docker run --user node ...',
338
- 'Security: Claude CLI blocks this flag as root to prevent privilege escalation',
339
- ]
340
- )
341
- );
342
- }
343
-
344
- return { errors, warnings };
345
- }
346
-
347
- function validateCliProvider(command, title, detail, recovery) {
348
- const errors = [];
349
- if (!commandExists(command)) {
350
- errors.push(formatError(title, detail, recovery));
351
- }
352
-
353
- return { errors, warnings: [] };
354
- }
355
-
356
- function validateProvider(providerName, options) {
357
- const validatorByProvider = {
358
- claude: () => validateClaudeProvider(options),
359
- codex: () =>
360
- validateCliProvider('codex', 'Codex CLI not available', 'Command "codex" not installed', [
361
- 'Install Codex CLI: npm install -g @openai/codex',
362
- 'Then run: codex --version',
363
- ]),
364
- gemini: () =>
365
- validateCliProvider('gemini', 'Gemini CLI not available', 'Command "gemini" not installed', [
366
- 'Install Gemini CLI: npm install -g @google/gemini-cli',
367
- 'Then run: gemini --version',
368
- ]),
369
- opencode: () =>
370
- validateCliProvider(
371
- 'opencode',
372
- 'Opencode CLI not available',
373
- 'Command "opencode" not installed',
374
- ['Install Opencode CLI: see https://opencode.ai', 'Then run: opencode --version']
375
- ),
376
- };
377
-
378
- const validator = validatorByProvider[providerName];
379
- if (!validator) {
380
- return {
381
- errors: [
382
- formatError('Unknown provider', `Provider "${providerName}" is not supported`, [
383
- 'Use claude, codex, gemini, or opencode',
384
- ]),
385
- ],
386
- warnings: [],
387
- };
388
- }
389
-
390
- return validator();
391
- }
392
-
393
- function validateGhRequirement() {
394
- const errors = [];
395
- const gh = checkGhAuth();
396
- if (!gh.installed) {
397
- errors.push(
398
- formatError('GitHub CLI (gh) not installed', 'Required for fetching issues by number', [
399
- 'Install: brew install gh (macOS) or apt install gh (Linux)',
400
- 'Or download from: https://cli.github.com/',
401
- ])
402
- );
403
- } else if (!gh.authenticated) {
404
- errors.push(
405
- formatError('GitHub CLI (gh) not authenticated', gh.error, [
406
- 'Run: gh auth login',
407
- 'Select GitHub.com, HTTPS, and authenticate via browser',
408
- 'Then verify: gh auth status',
409
- ])
410
- );
411
- }
412
-
413
- return errors;
414
- }
415
-
416
- function validateDockerRequirement() {
417
- const errors = [];
418
- const docker = checkDocker();
419
- if (!docker.available) {
420
- errors.push(
421
- formatError(
422
- 'Docker not available',
423
- docker.error,
424
- docker.error.includes('daemon')
425
- ? ['Start Docker Desktop', 'Or run: sudo systemctl start docker (Linux)']
426
- : [
427
- 'Install Docker Desktop from: https://docker.com/products/docker-desktop',
428
- 'Then start Docker and verify: docker info',
429
- ]
430
- )
431
- );
432
- }
433
-
434
- return errors;
435
- }
436
-
437
- function isGitRepository() {
438
- try {
439
- execSync('git rev-parse --git-dir', { stdio: 'pipe' });
440
- return true;
441
- } catch {
442
- return false;
443
- }
444
- }
445
-
446
- function validateGitRequirement() {
447
- if (isGitRepository()) {
448
- return [];
449
- }
450
-
451
- return [
452
- formatError('Not in a git repository', 'Worktree isolation requires a git repository', [
453
- 'Run from within a git repository',
454
- 'Or use --docker instead of --worktree for non-git directories',
455
- 'Initialize a repo with: git init',
456
- ]),
457
- ];
458
- }
459
-
460
- /**
461
- * Run all preflight checks
462
- * @param {Object} options - Preflight options
463
- * @param {boolean} options.requireGh - Whether gh CLI is required (true if using issue number)
464
- * @param {boolean} options.requireDocker - Whether Docker is required (true if using --docker)
465
- * @param {boolean} options.requireGit - Whether git repo is required (true if using --worktree)
466
- * @param {boolean} options.quiet - Suppress success messages
467
- * @param {string} options.claudeCommand - Custom Claude command (from settings)
468
- * @param {string} options.provider - Provider override
469
- * @returns {ValidationResult}
470
- */
471
- function runPreflight(options = {}) {
472
- const errors = [];
473
- const warnings = [];
474
-
475
- const settings = loadSettings();
476
- const providerName = normalizeProviderName(
477
- options.provider || settings.defaultProvider || 'claude'
478
- );
479
-
480
- const providerResult = validateProvider(providerName, options);
481
- errors.push(...providerResult.errors);
482
- warnings.push(...providerResult.warnings);
483
-
484
- // 4. Check issue provider CLI (if required)
485
- if (options.issueProvider) {
486
- const { getProvider } = require('./issue-providers');
487
- const ProviderClass = getProvider(options.issueProvider);
488
-
489
- if (ProviderClass) {
490
- const tool = ProviderClass.getRequiredTool();
491
-
492
- // Check if tool is installed
493
- if (!commandExists(tool.name)) {
494
- errors.push(
495
- formatError(
496
- `${ProviderClass.displayName} CLI (${tool.name}) not installed`,
497
- `Required for fetching ${ProviderClass.displayName} issues`,
498
- [tool.installHint, `Then verify: ${tool.checkCmd}`]
499
- )
500
- );
501
- } else {
502
- // Check provider authentication (abstracted per provider)
503
- // Use targetHost from URL input if provided, otherwise detect from git context
504
- // This ensures we check auth for the actual target, not the current repo
505
- const targetHost =
506
- options.targetHost || detectGitContext(options.cwd || process.cwd())?.host;
507
- const authResult = ProviderClass.checkAuth(targetHost);
508
- if (!authResult.authenticated) {
509
- errors.push(
510
- formatError(
511
- `${ProviderClass.displayName} CLI (${tool.name}) not authenticated`,
512
- authResult.error,
513
- authResult.recovery
514
- )
515
- );
516
- }
517
- }
518
- }
519
- }
520
-
521
- // 5. Check PR/MR CLI tools (if --pr or --ship mode is active)
522
- if (options.autoPr) {
523
- const { getPlatformForPR, getPRToolForPlatform, getProvider } = require('./issue-providers');
524
-
525
- let platform;
526
- let prGitContext;
527
- try {
528
- // Detect git platform (independent of issue provider)
529
- platform = getPlatformForPR(options.cwd || process.cwd());
530
- // Get git context for hostname (needed for multi-instance auth checks)
531
- prGitContext = detectGitContext(options.cwd || process.cwd());
532
- } catch (error) {
533
- // If platform detection fails, show clear error
534
- errors.push(
535
- formatError('--pr mode requires a git repository', error.message, [
536
- 'Ensure you are in a git repository with a remote URL from GitHub, GitLab, or Azure DevOps',
537
- ])
538
- );
539
- // Skip CLI tool check if platform unknown
540
- }
541
-
542
- if (platform) {
543
- // Get PR tool info from the provider (unified source of truth)
544
- const tool = getPRToolForPlatform(platform);
545
- const ProviderClass = getProvider(platform);
546
-
547
- if (tool && !commandExists(tool.name)) {
548
- errors.push(
549
- formatError(
550
- `${tool.displayName} CLI (${tool.name}) not installed`,
551
- `Required for --pr mode with ${tool.displayName} repositories`,
552
- [tool.installHint, `Then verify: ${tool.checkCmd}`]
553
- )
554
- );
555
- } else if (tool && ProviderClass) {
556
- // Check provider authentication (abstracted per provider)
557
- // Pass hostname for multi-instance providers (e.g., GitLab with self-hosted)
558
- const authResult = ProviderClass.checkAuth(prGitContext?.host);
559
- if (!authResult.authenticated) {
560
- errors.push(
561
- formatError(
562
- `${tool.displayName} CLI (${tool.name}) not authenticated`,
563
- authResult.error,
564
- authResult.recovery
565
- )
566
- );
567
- }
568
- }
569
- }
570
- }
571
-
572
- // Legacy gh check for backward compatibility
573
- if (options.requireGh) {
574
- errors.push(...validateGhRequirement());
575
- }
576
-
577
- // 6. Check Docker (if required)
578
- if (options.requireDocker) {
579
- errors.push(...validateDockerRequirement());
580
- }
581
-
582
- // 7. Check git repo (if required for worktree isolation)
583
- if (options.requireGit) {
584
- errors.push(...validateGitRequirement());
585
- }
586
-
587
- return {
588
- valid: errors.length === 0,
589
- errors,
590
- warnings,
591
- };
592
- }
593
-
594
- /**
595
- * Run preflight checks and exit if failed
596
- * @param {Object} options - Preflight options
597
- * @param {boolean} options.requireGh - Whether gh CLI is required
598
- * @param {boolean} options.requireDocker - Whether Docker is required
599
- * @param {boolean} options.requireGit - Whether git repo is required
600
- * @param {boolean} options.quiet - Suppress success messages
601
- * @param {string} options.provider - Provider override
602
- */
603
- function requirePreflight(options = {}) {
604
- const result = runPreflight(options);
605
-
606
- // Print warnings regardless of success
607
- if (result.warnings.length > 0) {
608
- for (const warning of result.warnings) {
609
- console.warn(warning);
610
- }
611
- }
612
-
613
- if (!result.valid) {
614
- console.error('\n' + '='.repeat(60));
615
- console.error('PREFLIGHT CHECK FAILED');
616
- console.error('='.repeat(60));
617
-
618
- for (const error of result.errors) {
619
- console.error(error);
620
- }
621
-
622
- console.error('='.repeat(60) + '\n');
623
- process.exit(1);
624
- }
625
-
626
- if (!options.quiet) {
627
- console.log('✓ Preflight checks passed');
628
- }
629
- }
630
-
631
- module.exports = {
632
- runPreflight,
633
- requirePreflight,
634
- getClaudeVersion,
635
- checkClaudeAuth,
636
- checkGhAuth,
637
- checkDocker,
638
- formatError,
639
- };