@covibes/zeroshot 5.2.1 → 5.4.0
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.
- package/CHANGELOG.md +174 -189
- package/README.md +226 -195
- package/cli/commands/providers.js +149 -0
- package/cli/index.js +3145 -2366
- package/cli/lib/first-run.js +40 -3
- package/cli/message-formatters-normal.js +28 -6
- package/cluster-templates/base-templates/debug-workflow.json +24 -78
- package/cluster-templates/base-templates/full-workflow.json +99 -316
- package/cluster-templates/base-templates/single-worker.json +23 -15
- package/cluster-templates/base-templates/worker-validator.json +105 -36
- package/cluster-templates/conductor-bootstrap.json +9 -7
- package/lib/docker-config.js +14 -1
- package/lib/git-remote-utils.js +165 -0
- package/lib/id-detector.js +10 -7
- package/lib/provider-defaults.js +62 -0
- package/lib/provider-detection.js +59 -0
- package/lib/provider-names.js +57 -0
- package/lib/settings/claude-auth.js +78 -0
- package/lib/settings.js +298 -15
- package/lib/stream-json-parser.js +4 -238
- package/package.json +27 -6
- package/scripts/setup-merge-queue.sh +170 -0
- package/scripts/validate-templates.js +100 -0
- package/src/agent/agent-config.js +140 -63
- package/src/agent/agent-context-builder.js +336 -165
- package/src/agent/agent-hook-executor.js +337 -67
- package/src/agent/agent-lifecycle.js +386 -287
- package/src/agent/agent-stuck-detector.js +7 -7
- package/src/agent/agent-task-executor.js +944 -683
- package/src/agent/output-extraction.js +217 -0
- package/src/agent/output-reformatter.js +175 -0
- package/src/agent/schema-utils.js +146 -0
- package/src/agent-wrapper.js +112 -31
- package/src/agents/git-pusher-template.js +285 -0
- package/src/claude-task-runner.js +145 -44
- package/src/config-router.js +13 -13
- package/src/config-validator.js +1049 -563
- package/src/input-helpers.js +65 -0
- package/src/isolation-manager.js +499 -320
- package/src/issue-providers/README.md +305 -0
- package/src/issue-providers/azure-devops-provider.js +273 -0
- package/src/issue-providers/base-provider.js +232 -0
- package/src/issue-providers/github-provider.js +179 -0
- package/src/issue-providers/gitlab-provider.js +241 -0
- package/src/issue-providers/index.js +196 -0
- package/src/issue-providers/jira-provider.js +239 -0
- package/src/ledger.js +50 -11
- package/src/lib/safe-exec.js +88 -0
- package/src/orchestrator.js +1348 -757
- package/src/preflight.js +306 -149
- package/src/process-metrics.js +98 -56
- package/src/providers/anthropic/cli-builder.js +73 -0
- package/src/providers/anthropic/index.js +204 -0
- package/src/providers/anthropic/models.js +23 -0
- package/src/providers/anthropic/output-parser.js +177 -0
- package/src/providers/base-provider.js +251 -0
- package/src/providers/capabilities.js +60 -0
- package/src/providers/google/cli-builder.js +55 -0
- package/src/providers/google/index.js +116 -0
- package/src/providers/google/models.js +24 -0
- package/src/providers/google/output-parser.js +101 -0
- package/src/providers/index.js +91 -0
- package/src/providers/openai/cli-builder.js +133 -0
- package/src/providers/openai/index.js +136 -0
- package/src/providers/openai/models.js +21 -0
- package/src/providers/openai/output-parser.js +143 -0
- package/src/providers/opencode/cli-builder.js +42 -0
- package/src/providers/opencode/index.js +103 -0
- package/src/providers/opencode/models.js +55 -0
- package/src/providers/opencode/output-parser.js +122 -0
- package/src/schemas/sub-cluster.js +68 -39
- package/src/status-footer.js +94 -48
- package/src/sub-cluster-wrapper.js +92 -36
- package/src/task-runner.js +8 -6
- package/src/template-resolver.js +12 -9
- package/src/tui/data-poller.js +123 -99
- package/src/tui/formatters.js +4 -3
- package/src/tui/keybindings.js +259 -318
- package/src/tui/layout.js +20 -3
- package/src/tui/renderer.js +11 -21
- package/task-lib/attachable-watcher.js +150 -111
- package/task-lib/claude-recovery.js +156 -0
- package/task-lib/commands/episodes.js +105 -0
- package/task-lib/commands/list.js +3 -3
- package/task-lib/commands/logs.js +231 -189
- package/task-lib/commands/resume.js +3 -2
- package/task-lib/commands/run.js +12 -3
- package/task-lib/commands/schedules.js +111 -61
- package/task-lib/config.js +0 -2
- package/task-lib/runner.js +128 -50
- package/task-lib/scheduler.js +3 -3
- package/task-lib/store.js +464 -152
- package/task-lib/tui/formatters.js +94 -45
- package/task-lib/tui/renderer.js +13 -3
- package/task-lib/tui.js +73 -32
- package/task-lib/watcher.js +157 -100
- package/src/agents/git-pusher-agent.json +0 -20
- package/src/github.js +0 -103
package/src/preflight.js
CHANGED
|
@@ -2,17 +2,25 @@
|
|
|
2
2
|
* Preflight Validation - Check all dependencies before starting
|
|
3
3
|
*
|
|
4
4
|
* Validates:
|
|
5
|
-
* -
|
|
5
|
+
* - Selected provider CLI installed
|
|
6
6
|
* - gh CLI installed and authenticated (if using issue numbers)
|
|
7
7
|
* - Docker available (if using --docker)
|
|
8
8
|
*
|
|
9
9
|
* Provides CLEAR, ACTIONABLE error messages with recovery instructions.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
const { execSync } = require('
|
|
12
|
+
const { execSync } = require('./lib/safe-exec'); // Enforces timeouts
|
|
13
13
|
const path = require('path');
|
|
14
14
|
const fs = require('fs');
|
|
15
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');
|
|
16
24
|
|
|
17
25
|
/**
|
|
18
26
|
* Validation result
|
|
@@ -42,13 +50,15 @@ function formatError(title, detail, recovery) {
|
|
|
42
50
|
}
|
|
43
51
|
|
|
44
52
|
/**
|
|
45
|
-
* Check if a command exists
|
|
53
|
+
* Check if a command exists (cross-platform)
|
|
46
54
|
* @param {string} cmd - Command to check
|
|
47
55
|
* @returns {boolean}
|
|
48
56
|
*/
|
|
49
57
|
function commandExists(cmd) {
|
|
50
58
|
try {
|
|
51
|
-
|
|
59
|
+
// Windows uses 'where', Unix uses 'which'
|
|
60
|
+
const checkCmd = process.platform === 'win32' ? `where ${cmd}` : `which ${cmd}`;
|
|
61
|
+
execSync(checkCmd, { encoding: 'utf8', stdio: 'pipe' });
|
|
52
62
|
return true;
|
|
53
63
|
} catch {
|
|
54
64
|
return false;
|
|
@@ -120,29 +130,55 @@ function checkMacOsKeychain() {
|
|
|
120
130
|
*/
|
|
121
131
|
function checkClaudeAuth() {
|
|
122
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
|
+
|
|
123
172
|
const credentialsPath = path.join(configDir, '.credentials.json');
|
|
124
173
|
|
|
125
174
|
// Check if credentials file exists
|
|
126
175
|
if (!fs.existsSync(credentialsPath)) {
|
|
127
176
|
// No credentials file - check macOS Keychain as fallback
|
|
128
177
|
// Only use Keychain when using default config dir (not custom CLAUDE_CONFIG_DIR)
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const keychainResult = checkMacOsKeychain();
|
|
132
|
-
if (keychainResult.authenticated) {
|
|
133
|
-
return {
|
|
134
|
-
authenticated: true,
|
|
135
|
-
error: null,
|
|
136
|
-
configDir,
|
|
137
|
-
method: 'keychain',
|
|
138
|
-
};
|
|
139
|
-
}
|
|
178
|
+
if (!process.env.CLAUDE_CONFIG_DIR && checkMacOsKeychain().authenticated) {
|
|
179
|
+
return authResult(true, null, 'keychain');
|
|
140
180
|
}
|
|
141
|
-
return
|
|
142
|
-
authenticated: false,
|
|
143
|
-
error: 'No credentials file found',
|
|
144
|
-
configDir,
|
|
145
|
-
};
|
|
181
|
+
return authResult(false, 'No credentials file found');
|
|
146
182
|
}
|
|
147
183
|
|
|
148
184
|
// Check if credentials file has content
|
|
@@ -152,42 +188,21 @@ function checkClaudeAuth() {
|
|
|
152
188
|
|
|
153
189
|
// Check for OAuth token (primary auth method)
|
|
154
190
|
if (creds.claudeAiOauth?.accessToken) {
|
|
155
|
-
// Check if token is expired
|
|
156
191
|
const expiresAt = creds.claudeAiOauth.expiresAt;
|
|
157
192
|
if (expiresAt && new Date(expiresAt) < new Date()) {
|
|
158
|
-
return
|
|
159
|
-
authenticated: false,
|
|
160
|
-
error: 'OAuth token expired',
|
|
161
|
-
configDir,
|
|
162
|
-
};
|
|
193
|
+
return authResult(false, 'OAuth token expired');
|
|
163
194
|
}
|
|
164
|
-
return
|
|
165
|
-
authenticated: true,
|
|
166
|
-
error: null,
|
|
167
|
-
configDir,
|
|
168
|
-
};
|
|
195
|
+
return authResult(true);
|
|
169
196
|
}
|
|
170
197
|
|
|
171
198
|
// Check for API key auth
|
|
172
199
|
if (creds.apiKey) {
|
|
173
|
-
return
|
|
174
|
-
authenticated: true,
|
|
175
|
-
error: null,
|
|
176
|
-
configDir,
|
|
177
|
-
};
|
|
200
|
+
return authResult(true);
|
|
178
201
|
}
|
|
179
202
|
|
|
180
|
-
return
|
|
181
|
-
authenticated: false,
|
|
182
|
-
error: 'No valid authentication found in credentials',
|
|
183
|
-
configDir,
|
|
184
|
-
};
|
|
203
|
+
return authResult(false, 'No valid authentication found in credentials');
|
|
185
204
|
} catch (err) {
|
|
186
|
-
return {
|
|
187
|
-
authenticated: false,
|
|
188
|
-
error: `Failed to parse credentials: ${err.message}`,
|
|
189
|
-
configDir,
|
|
190
|
-
};
|
|
205
|
+
return authResult(false, `Failed to parse credentials: ${err.message}`);
|
|
191
206
|
}
|
|
192
207
|
}
|
|
193
208
|
|
|
@@ -272,26 +287,16 @@ function checkDocker() {
|
|
|
272
287
|
}
|
|
273
288
|
}
|
|
274
289
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
* @param {boolean} options.quiet - Suppress success messages
|
|
282
|
-
* @param {string} options.claudeCommand - Custom Claude command (from settings)
|
|
283
|
-
* @returns {ValidationResult}
|
|
284
|
-
*/
|
|
285
|
-
function runPreflight(options = {}) {
|
|
290
|
+
function buildClaudeCommand(options) {
|
|
291
|
+
const { command, args } = getClaudeCommand();
|
|
292
|
+
return options.claudeCommand || [command, ...args].join(' ');
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function validateClaudeProvider(options) {
|
|
286
296
|
const errors = [];
|
|
287
297
|
const warnings = [];
|
|
298
|
+
const claudeCommand = buildClaudeCommand(options);
|
|
288
299
|
|
|
289
|
-
// Get configured Claude command (supports custom commands like 'ccr code')
|
|
290
|
-
const { getClaudeCommand } = require('../lib/settings.js');
|
|
291
|
-
const { command, args } = getClaudeCommand();
|
|
292
|
-
const claudeCommand = options.claudeCommand || [command, ...args].join(' ');
|
|
293
|
-
|
|
294
|
-
// 1. Check Claude CLI installation
|
|
295
300
|
const claude = getClaudeVersion(claudeCommand);
|
|
296
301
|
if (!claude.installed) {
|
|
297
302
|
errors.push(
|
|
@@ -312,35 +317,15 @@ function runPreflight(options = {}) {
|
|
|
312
317
|
]
|
|
313
318
|
)
|
|
314
319
|
);
|
|
315
|
-
} else {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
formatError(
|
|
321
|
-
'Claude CLI not authenticated',
|
|
322
|
-
auth.error,
|
|
323
|
-
[
|
|
324
|
-
'Run: claude login',
|
|
325
|
-
'Follow the browser prompts to authenticate',
|
|
326
|
-
`Config directory: ${auth.configDir}`,
|
|
327
|
-
]
|
|
328
|
-
)
|
|
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.`
|
|
329
325
|
);
|
|
330
326
|
}
|
|
331
|
-
|
|
332
|
-
// Check version (warn if old)
|
|
333
|
-
if (claude.version) {
|
|
334
|
-
const [major, minor] = claude.version.split('.').map(Number);
|
|
335
|
-
if (major < 1 || (major === 1 && minor < 0)) {
|
|
336
|
-
warnings.push(
|
|
337
|
-
`⚠️ Claude CLI version ${claude.version} may be outdated. Consider upgrading.`
|
|
338
|
-
);
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
327
|
}
|
|
342
328
|
|
|
343
|
-
// 3. Check if running as root (blocks --dangerously-skip-permissions)
|
|
344
329
|
if (process.getuid && process.getuid() === 0) {
|
|
345
330
|
errors.push(
|
|
346
331
|
formatError(
|
|
@@ -356,78 +341,249 @@ function runPreflight(options = {}) {
|
|
|
356
341
|
);
|
|
357
342
|
}
|
|
358
343
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
'Required for fetching issues by number',
|
|
367
|
-
[
|
|
368
|
-
'Install: brew install gh (macOS) or apt install gh (Linux)',
|
|
369
|
-
'Or download from: https://cli.github.com/',
|
|
370
|
-
]
|
|
371
|
-
)
|
|
372
|
-
);
|
|
373
|
-
} else if (!gh.authenticated) {
|
|
374
|
-
errors.push(
|
|
375
|
-
formatError(
|
|
376
|
-
'GitHub CLI (gh) not authenticated',
|
|
377
|
-
gh.error,
|
|
378
|
-
[
|
|
379
|
-
'Run: gh auth login',
|
|
380
|
-
'Select GitHub.com, HTTPS, and authenticate via browser',
|
|
381
|
-
'Then verify: gh auth status',
|
|
382
|
-
]
|
|
383
|
-
)
|
|
384
|
-
);
|
|
385
|
-
}
|
|
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));
|
|
386
351
|
}
|
|
387
352
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
)
|
|
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
|
+
}
|
|
404
518
|
}
|
|
405
519
|
}
|
|
406
520
|
|
|
407
|
-
//
|
|
408
|
-
if (options.
|
|
409
|
-
|
|
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;
|
|
410
527
|
try {
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
}
|
|
416
|
-
|
|
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
|
|
417
534
|
errors.push(
|
|
418
|
-
formatError(
|
|
419
|
-
'
|
|
420
|
-
|
|
421
|
-
[
|
|
422
|
-
'Run from within a git repository',
|
|
423
|
-
'Or use --docker instead of --worktree for non-git directories',
|
|
424
|
-
'Initialize a repo with: git init',
|
|
425
|
-
]
|
|
426
|
-
)
|
|
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
|
+
])
|
|
427
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
|
+
}
|
|
428
569
|
}
|
|
429
570
|
}
|
|
430
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
|
+
|
|
431
587
|
return {
|
|
432
588
|
valid: errors.length === 0,
|
|
433
589
|
errors,
|
|
@@ -442,6 +598,7 @@ function runPreflight(options = {}) {
|
|
|
442
598
|
* @param {boolean} options.requireDocker - Whether Docker is required
|
|
443
599
|
* @param {boolean} options.requireGit - Whether git repo is required
|
|
444
600
|
* @param {boolean} options.quiet - Suppress success messages
|
|
601
|
+
* @param {string} options.provider - Provider override
|
|
445
602
|
*/
|
|
446
603
|
function requirePreflight(options = {}) {
|
|
447
604
|
const result = runPreflight(options);
|