@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.
- package/LICENSE +1 -1
- package/README.md +8 -465
- package/cli/index.js +101 -5231
- package/package.json +16 -130
- package/CHANGELOG.md +0 -537
- package/cli/commands/providers.js +0 -149
- package/cli/lib/first-run.js +0 -211
- package/cli/lib/update-checker.js +0 -281
- package/cli/message-formatter-utils.js +0 -75
- package/cli/message-formatters-normal.js +0 -275
- package/cli/message-formatters-watch.js +0 -181
- package/cluster-templates/base-templates/debug-workflow.json +0 -375
- package/cluster-templates/base-templates/full-workflow.json +0 -581
- package/cluster-templates/base-templates/single-worker.json +0 -63
- package/cluster-templates/base-templates/worker-validator.json +0 -208
- package/cluster-templates/conductor-bootstrap.json +0 -124
- package/docker/zeroshot-cluster/Dockerfile +0 -152
- package/docker/zeroshot-cluster/pre-baked-deps.json +0 -28
- package/lib/completion.js +0 -174
- package/lib/docker-config.js +0 -220
- package/lib/git-remote-utils.js +0 -165
- package/lib/id-detector.js +0 -56
- package/lib/provider-defaults.js +0 -62
- package/lib/provider-detection.js +0 -59
- package/lib/provider-names.js +0 -57
- package/lib/settings/claude-auth.js +0 -78
- package/lib/settings.js +0 -503
- package/lib/stream-json-parser.js +0 -10
- package/scripts/fix-node-pty-permissions.js +0 -75
- package/scripts/record-demo.sh +0 -279
- package/scripts/setup-merge-queue.sh +0 -170
- package/scripts/test-install.sh +0 -40
- package/scripts/validate-templates.js +0 -100
- package/src/agent/agent-config.js +0 -246
- package/src/agent/agent-context-builder.js +0 -444
- package/src/agent/agent-hook-executor.js +0 -599
- package/src/agent/agent-lifecycle.js +0 -743
- package/src/agent/agent-stuck-detector.js +0 -256
- package/src/agent/agent-task-executor.js +0 -1690
- package/src/agent/agent-trigger-evaluator.js +0 -67
- package/src/agent/output-extraction.js +0 -217
- package/src/agent/output-reformatter.js +0 -175
- package/src/agent/schema-utils.js +0 -146
- package/src/agent-wrapper.js +0 -579
- package/src/agents/git-pusher-template.js +0 -285
- package/src/attach/attach-client.js +0 -438
- package/src/attach/attach-server.js +0 -543
- package/src/attach/index.js +0 -35
- package/src/attach/protocol.js +0 -220
- package/src/attach/ring-buffer.js +0 -121
- package/src/attach/socket-discovery.js +0 -267
- package/src/claude-task-runner.js +0 -574
- package/src/config-router.js +0 -80
- package/src/config-validator.js +0 -1925
- package/src/copy-worker.js +0 -43
- package/src/input-helpers.js +0 -65
- package/src/isolation-manager.js +0 -1471
- package/src/issue-providers/README.md +0 -305
- package/src/issue-providers/azure-devops-provider.js +0 -273
- package/src/issue-providers/base-provider.js +0 -232
- package/src/issue-providers/github-provider.js +0 -179
- package/src/issue-providers/gitlab-provider.js +0 -241
- package/src/issue-providers/index.js +0 -196
- package/src/issue-providers/jira-provider.js +0 -239
- package/src/ledger.js +0 -631
- package/src/lib/safe-exec.js +0 -88
- package/src/logic-engine.js +0 -223
- package/src/message-bus-bridge.js +0 -139
- package/src/message-bus.js +0 -249
- package/src/name-generator.js +0 -232
- package/src/orchestrator.js +0 -2815
- package/src/preflight.js +0 -639
- package/src/process-metrics.js +0 -608
- package/src/providers/anthropic/cli-builder.js +0 -73
- package/src/providers/anthropic/index.js +0 -204
- package/src/providers/anthropic/models.js +0 -23
- package/src/providers/anthropic/output-parser.js +0 -177
- package/src/providers/base-provider.js +0 -251
- package/src/providers/capabilities.js +0 -60
- package/src/providers/google/cli-builder.js +0 -55
- package/src/providers/google/index.js +0 -116
- package/src/providers/google/models.js +0 -24
- package/src/providers/google/output-parser.js +0 -101
- package/src/providers/index.js +0 -91
- package/src/providers/openai/cli-builder.js +0 -133
- package/src/providers/openai/index.js +0 -136
- package/src/providers/openai/models.js +0 -21
- package/src/providers/openai/output-parser.js +0 -143
- package/src/providers/opencode/cli-builder.js +0 -42
- package/src/providers/opencode/index.js +0 -103
- package/src/providers/opencode/models.js +0 -55
- package/src/providers/opencode/output-parser.js +0 -122
- package/src/schemas/sub-cluster.js +0 -185
- package/src/status-footer.js +0 -1026
- package/src/sub-cluster-wrapper.js +0 -601
- package/src/task-runner.js +0 -30
- package/src/template-resolver.js +0 -372
- package/src/tui/CHANGES.txt +0 -133
- package/src/tui/LAYOUT.md +0 -261
- package/src/tui/README.txt +0 -192
- package/src/tui/TWO-LEVEL-NAVIGATION.md +0 -186
- package/src/tui/data-poller.js +0 -349
- package/src/tui/demo.js +0 -208
- package/src/tui/formatters.js +0 -129
- package/src/tui/index.js +0 -193
- package/src/tui/keybindings.js +0 -324
- package/src/tui/layout.js +0 -334
- package/src/tui/renderer.js +0 -184
- package/task-lib/attachable-watcher.js +0 -309
- package/task-lib/claude-recovery.js +0 -156
- package/task-lib/commands/clean.js +0 -50
- package/task-lib/commands/episodes.js +0 -105
- package/task-lib/commands/get-log-path.js +0 -23
- package/task-lib/commands/kill.js +0 -32
- package/task-lib/commands/list.js +0 -105
- package/task-lib/commands/logs.js +0 -453
- package/task-lib/commands/resume.js +0 -42
- package/task-lib/commands/run.js +0 -57
- package/task-lib/commands/schedule.js +0 -105
- package/task-lib/commands/scheduler-cmd.js +0 -96
- package/task-lib/commands/schedules.js +0 -148
- package/task-lib/commands/status.js +0 -44
- package/task-lib/commands/unschedule.js +0 -16
- package/task-lib/completion.js +0 -9
- package/task-lib/config.js +0 -8
- package/task-lib/name-generator.js +0 -230
- package/task-lib/package.json +0 -3
- package/task-lib/runner.js +0 -201
- package/task-lib/scheduler.js +0 -252
- package/task-lib/store.js +0 -529
- package/task-lib/tui/formatters.js +0 -215
- package/task-lib/tui/index.js +0 -197
- package/task-lib/tui/layout.js +0 -111
- package/task-lib/tui/renderer.js +0 -129
- package/task-lib/tui.js +0 -425
- 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
|
-
};
|