@covibes/zeroshot 5.3.0 → 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/README.md +94 -14
- package/cli/commands/providers.js +8 -9
- package/cli/index.js +3032 -2409
- package/cli/message-formatters-normal.js +28 -6
- package/cluster-templates/base-templates/debug-workflow.json +1 -1
- package/cluster-templates/base-templates/full-workflow.json +72 -188
- package/cluster-templates/base-templates/worker-validator.json +59 -3
- package/cluster-templates/conductor-bootstrap.json +4 -4
- package/lib/docker-config.js +8 -0
- 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-names.js +2 -1
- package/lib/settings/claude-auth.js +78 -0
- package/lib/settings.js +161 -63
- package/package.json +7 -2
- package/scripts/setup-merge-queue.sh +170 -0
- package/src/agent/agent-config.js +135 -82
- package/src/agent/agent-context-builder.js +297 -188
- package/src/agent/agent-hook-executor.js +310 -113
- package/src/agent/agent-lifecycle.js +385 -325
- package/src/agent/agent-stuck-detector.js +7 -7
- package/src/agent/agent-task-executor.js +824 -565
- package/src/agent/output-extraction.js +41 -24
- package/src/agent/output-reformatter.js +1 -1
- package/src/agent/schema-utils.js +108 -73
- package/src/agent-wrapper.js +10 -1
- package/src/agents/git-pusher-template.js +285 -0
- package/src/claude-task-runner.js +85 -34
- package/src/config-validator.js +922 -657
- package/src/input-helpers.js +65 -0
- package/src/isolation-manager.js +289 -199
- 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 +22 -5
- package/src/lib/safe-exec.js +88 -0
- package/src/orchestrator.js +1107 -811
- package/src/preflight.js +313 -159
- package/src/process-metrics.js +98 -56
- package/src/providers/anthropic/cli-builder.js +53 -25
- package/src/providers/anthropic/index.js +72 -2
- package/src/providers/anthropic/output-parser.js +32 -14
- package/src/providers/base-provider.js +70 -0
- package/src/providers/capabilities.js +9 -0
- package/src/providers/google/output-parser.js +47 -38
- package/src/providers/index.js +19 -3
- package/src/providers/openai/cli-builder.js +14 -3
- package/src/providers/openai/index.js +1 -0
- package/src/providers/openai/output-parser.js +44 -30
- 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 +76 -35
- 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/renderer.js +11 -21
- package/task-lib/attachable-watcher.js +118 -81
- package/task-lib/claude-recovery.js +61 -24
- package/task-lib/commands/episodes.js +105 -0
- package/task-lib/commands/list.js +2 -2
- package/task-lib/commands/logs.js +231 -189
- package/task-lib/commands/schedules.js +111 -61
- package/task-lib/config.js +0 -2
- package/task-lib/runner.js +84 -27
- package/task-lib/scheduler.js +1 -1
- package/task-lib/store.js +467 -168
- 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 +128 -90
- package/src/agents/git-pusher-agent.json +0 -20
- package/src/github.js +0 -139
package/src/preflight.js
CHANGED
|
@@ -9,10 +9,18 @@
|
|
|
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
|
|
@@ -122,29 +130,55 @@ function checkMacOsKeychain() {
|
|
|
122
130
|
*/
|
|
123
131
|
function checkClaudeAuth() {
|
|
124
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
|
+
|
|
125
172
|
const credentialsPath = path.join(configDir, '.credentials.json');
|
|
126
173
|
|
|
127
174
|
// Check if credentials file exists
|
|
128
175
|
if (!fs.existsSync(credentialsPath)) {
|
|
129
176
|
// No credentials file - check macOS Keychain as fallback
|
|
130
177
|
// Only use Keychain when using default config dir (not custom CLAUDE_CONFIG_DIR)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const keychainResult = checkMacOsKeychain();
|
|
134
|
-
if (keychainResult.authenticated) {
|
|
135
|
-
return {
|
|
136
|
-
authenticated: true,
|
|
137
|
-
error: null,
|
|
138
|
-
configDir,
|
|
139
|
-
method: 'keychain',
|
|
140
|
-
};
|
|
141
|
-
}
|
|
178
|
+
if (!process.env.CLAUDE_CONFIG_DIR && checkMacOsKeychain().authenticated) {
|
|
179
|
+
return authResult(true, null, 'keychain');
|
|
142
180
|
}
|
|
143
|
-
return
|
|
144
|
-
authenticated: false,
|
|
145
|
-
error: 'No credentials file found',
|
|
146
|
-
configDir,
|
|
147
|
-
};
|
|
181
|
+
return authResult(false, 'No credentials file found');
|
|
148
182
|
}
|
|
149
183
|
|
|
150
184
|
// Check if credentials file has content
|
|
@@ -154,42 +188,21 @@ function checkClaudeAuth() {
|
|
|
154
188
|
|
|
155
189
|
// Check for OAuth token (primary auth method)
|
|
156
190
|
if (creds.claudeAiOauth?.accessToken) {
|
|
157
|
-
// Check if token is expired
|
|
158
191
|
const expiresAt = creds.claudeAiOauth.expiresAt;
|
|
159
192
|
if (expiresAt && new Date(expiresAt) < new Date()) {
|
|
160
|
-
return
|
|
161
|
-
authenticated: false,
|
|
162
|
-
error: 'OAuth token expired',
|
|
163
|
-
configDir,
|
|
164
|
-
};
|
|
193
|
+
return authResult(false, 'OAuth token expired');
|
|
165
194
|
}
|
|
166
|
-
return
|
|
167
|
-
authenticated: true,
|
|
168
|
-
error: null,
|
|
169
|
-
configDir,
|
|
170
|
-
};
|
|
195
|
+
return authResult(true);
|
|
171
196
|
}
|
|
172
197
|
|
|
173
198
|
// Check for API key auth
|
|
174
199
|
if (creds.apiKey) {
|
|
175
|
-
return
|
|
176
|
-
authenticated: true,
|
|
177
|
-
error: null,
|
|
178
|
-
configDir,
|
|
179
|
-
};
|
|
200
|
+
return authResult(true);
|
|
180
201
|
}
|
|
181
202
|
|
|
182
|
-
return
|
|
183
|
-
authenticated: false,
|
|
184
|
-
error: 'No valid authentication found in credentials',
|
|
185
|
-
configDir,
|
|
186
|
-
};
|
|
203
|
+
return authResult(false, 'No valid authentication found in credentials');
|
|
187
204
|
} catch (err) {
|
|
188
|
-
return {
|
|
189
|
-
authenticated: false,
|
|
190
|
-
error: `Failed to parse credentials: ${err.message}`,
|
|
191
|
-
configDir,
|
|
192
|
-
};
|
|
205
|
+
return authResult(false, `Failed to parse credentials: ${err.message}`);
|
|
193
206
|
}
|
|
194
207
|
}
|
|
195
208
|
|
|
@@ -274,6 +287,176 @@ function checkDocker() {
|
|
|
274
287
|
}
|
|
275
288
|
}
|
|
276
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
|
+
|
|
277
460
|
/**
|
|
278
461
|
* Run all preflight checks
|
|
279
462
|
* @param {Object} options - Preflight options
|
|
@@ -289,145 +472,116 @@ function runPreflight(options = {}) {
|
|
|
289
472
|
const errors = [];
|
|
290
473
|
const warnings = [];
|
|
291
474
|
|
|
292
|
-
const { loadSettings, getClaudeCommand } = require('../lib/settings.js');
|
|
293
|
-
const { normalizeProviderName } = require('../lib/provider-names');
|
|
294
475
|
const settings = loadSettings();
|
|
295
476
|
const providerName = normalizeProviderName(
|
|
296
477
|
options.provider || settings.defaultProvider || 'claude'
|
|
297
478
|
);
|
|
298
479
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
'Or install the missing command',
|
|
320
|
-
]
|
|
321
|
-
)
|
|
322
|
-
);
|
|
323
|
-
} else if (claude.version) {
|
|
324
|
-
const [major, minor] = claude.version.split('.').map(Number);
|
|
325
|
-
if (major < 1 || (major === 1 && minor < 0)) {
|
|
326
|
-
warnings.push(
|
|
327
|
-
`⚠️ Claude CLI version ${claude.version} may be outdated. Consider upgrading.`
|
|
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
|
+
)
|
|
328
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
|
+
}
|
|
329
517
|
}
|
|
330
518
|
}
|
|
519
|
+
}
|
|
331
520
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
);
|
|
346
|
-
}
|
|
347
|
-
} else if (providerName === 'codex') {
|
|
348
|
-
if (!commandExists('codex')) {
|
|
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
|
|
349
534
|
errors.push(
|
|
350
|
-
formatError('
|
|
351
|
-
'
|
|
352
|
-
'Then run: codex --version',
|
|
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',
|
|
353
537
|
])
|
|
354
538
|
);
|
|
539
|
+
// Skip CLI tool check if platform unknown
|
|
355
540
|
}
|
|
356
|
-
|
|
357
|
-
if (
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
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
|
+
}
|
|
364
569
|
}
|
|
365
|
-
} else {
|
|
366
|
-
errors.push(
|
|
367
|
-
formatError('Unknown provider', `Provider "${providerName}" is not supported`, [
|
|
368
|
-
'Use claude, codex, or gemini',
|
|
369
|
-
])
|
|
370
|
-
);
|
|
371
570
|
}
|
|
372
571
|
|
|
373
|
-
//
|
|
572
|
+
// Legacy gh check for backward compatibility
|
|
374
573
|
if (options.requireGh) {
|
|
375
|
-
|
|
376
|
-
if (!gh.installed) {
|
|
377
|
-
errors.push(
|
|
378
|
-
formatError('GitHub CLI (gh) not installed', 'Required for fetching issues by number', [
|
|
379
|
-
'Install: brew install gh (macOS) or apt install gh (Linux)',
|
|
380
|
-
'Or download from: https://cli.github.com/',
|
|
381
|
-
])
|
|
382
|
-
);
|
|
383
|
-
} else if (!gh.authenticated) {
|
|
384
|
-
errors.push(
|
|
385
|
-
formatError('GitHub CLI (gh) not authenticated', gh.error, [
|
|
386
|
-
'Run: gh auth login',
|
|
387
|
-
'Select GitHub.com, HTTPS, and authenticate via browser',
|
|
388
|
-
'Then verify: gh auth status',
|
|
389
|
-
])
|
|
390
|
-
);
|
|
391
|
-
}
|
|
574
|
+
errors.push(...validateGhRequirement());
|
|
392
575
|
}
|
|
393
576
|
|
|
394
|
-
//
|
|
577
|
+
// 6. Check Docker (if required)
|
|
395
578
|
if (options.requireDocker) {
|
|
396
|
-
|
|
397
|
-
if (!docker.available) {
|
|
398
|
-
errors.push(
|
|
399
|
-
formatError(
|
|
400
|
-
'Docker not available',
|
|
401
|
-
docker.error,
|
|
402
|
-
docker.error.includes('daemon')
|
|
403
|
-
? ['Start Docker Desktop', 'Or run: sudo systemctl start docker (Linux)']
|
|
404
|
-
: [
|
|
405
|
-
'Install Docker Desktop from: https://docker.com/products/docker-desktop',
|
|
406
|
-
'Then start Docker and verify: docker info',
|
|
407
|
-
]
|
|
408
|
-
)
|
|
409
|
-
);
|
|
410
|
-
}
|
|
579
|
+
errors.push(...validateDockerRequirement());
|
|
411
580
|
}
|
|
412
581
|
|
|
413
|
-
//
|
|
582
|
+
// 7. Check git repo (if required for worktree isolation)
|
|
414
583
|
if (options.requireGit) {
|
|
415
|
-
|
|
416
|
-
try {
|
|
417
|
-
execSync('git rev-parse --git-dir', { stdio: 'pipe' });
|
|
418
|
-
isGitRepo = true;
|
|
419
|
-
} catch {
|
|
420
|
-
// Not a git repo
|
|
421
|
-
}
|
|
422
|
-
if (!isGitRepo) {
|
|
423
|
-
errors.push(
|
|
424
|
-
formatError('Not in a git repository', 'Worktree isolation requires a git repository', [
|
|
425
|
-
'Run from within a git repository',
|
|
426
|
-
'Or use --docker instead of --worktree for non-git directories',
|
|
427
|
-
'Initialize a repo with: git init',
|
|
428
|
-
])
|
|
429
|
-
);
|
|
430
|
-
}
|
|
584
|
+
errors.push(...validateGitRequirement());
|
|
431
585
|
}
|
|
432
586
|
|
|
433
587
|
return {
|