@the-open-engine/zeroshot 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.
Files changed (295) hide show
  1. package/CHANGELOG.md +539 -0
  2. package/LICENSE +21 -0
  3. package/README.md +508 -0
  4. package/cli/commands/inspect-render.js +135 -0
  5. package/cli/commands/inspect.js +294 -0
  6. package/cli/commands/providers.js +149 -0
  7. package/cli/index.js +5431 -0
  8. package/cli/lib/first-run.js +211 -0
  9. package/cli/lib/update-checker.js +281 -0
  10. package/cli/message-formatter-utils.js +75 -0
  11. package/cli/message-formatters-normal.js +275 -0
  12. package/cli/message-formatters-watch.js +185 -0
  13. package/cluster-templates/base-templates/debug-workflow.json +422 -0
  14. package/cluster-templates/base-templates/full-workflow.json +727 -0
  15. package/cluster-templates/base-templates/heavy-validation.json +272 -0
  16. package/cluster-templates/base-templates/quick-validation.json +285 -0
  17. package/cluster-templates/base-templates/single-worker.json +71 -0
  18. package/cluster-templates/base-templates/worker-validator.json +230 -0
  19. package/cluster-templates/conductor-bootstrap.json +130 -0
  20. package/docker/zeroshot-cluster/Dockerfile +153 -0
  21. package/docker/zeroshot-cluster/pre-baked-deps.json +28 -0
  22. package/lib/agent-cli-provider/adapters/claude-parser.d.ts +3 -0
  23. package/lib/agent-cli-provider/adapters/claude-parser.d.ts.map +1 -0
  24. package/lib/agent-cli-provider/adapters/claude-parser.js +122 -0
  25. package/lib/agent-cli-provider/adapters/claude-parser.js.map +1 -0
  26. package/lib/agent-cli-provider/adapters/claude-recovery.d.ts +15 -0
  27. package/lib/agent-cli-provider/adapters/claude-recovery.d.ts.map +1 -0
  28. package/lib/agent-cli-provider/adapters/claude-recovery.js +165 -0
  29. package/lib/agent-cli-provider/adapters/claude-recovery.js.map +1 -0
  30. package/lib/agent-cli-provider/adapters/claude.d.ts +3 -0
  31. package/lib/agent-cli-provider/adapters/claude.d.ts.map +1 -0
  32. package/lib/agent-cli-provider/adapters/claude.js +181 -0
  33. package/lib/agent-cli-provider/adapters/claude.js.map +1 -0
  34. package/lib/agent-cli-provider/adapters/codex-parser.d.ts +3 -0
  35. package/lib/agent-cli-provider/adapters/codex-parser.d.ts.map +1 -0
  36. package/lib/agent-cli-provider/adapters/codex-parser.js +192 -0
  37. package/lib/agent-cli-provider/adapters/codex-parser.js.map +1 -0
  38. package/lib/agent-cli-provider/adapters/codex.d.ts +3 -0
  39. package/lib/agent-cli-provider/adapters/codex.d.ts.map +1 -0
  40. package/lib/agent-cli-provider/adapters/codex.js +151 -0
  41. package/lib/agent-cli-provider/adapters/codex.js.map +1 -0
  42. package/lib/agent-cli-provider/adapters/common.d.ts +28 -0
  43. package/lib/agent-cli-provider/adapters/common.d.ts.map +1 -0
  44. package/lib/agent-cli-provider/adapters/common.js +71 -0
  45. package/lib/agent-cli-provider/adapters/common.js.map +1 -0
  46. package/lib/agent-cli-provider/adapters/gemini.d.ts +3 -0
  47. package/lib/agent-cli-provider/adapters/gemini.d.ts.map +1 -0
  48. package/lib/agent-cli-provider/adapters/gemini.js +176 -0
  49. package/lib/agent-cli-provider/adapters/gemini.js.map +1 -0
  50. package/lib/agent-cli-provider/adapters/index.d.ts +9 -0
  51. package/lib/agent-cli-provider/adapters/index.d.ts.map +1 -0
  52. package/lib/agent-cli-provider/adapters/index.js +95 -0
  53. package/lib/agent-cli-provider/adapters/index.js.map +1 -0
  54. package/lib/agent-cli-provider/adapters/opencode.d.ts +3 -0
  55. package/lib/agent-cli-provider/adapters/opencode.d.ts.map +1 -0
  56. package/lib/agent-cli-provider/adapters/opencode.js +228 -0
  57. package/lib/agent-cli-provider/adapters/opencode.js.map +1 -0
  58. package/lib/agent-cli-provider/contract-actions.d.ts +5 -0
  59. package/lib/agent-cli-provider/contract-actions.d.ts.map +1 -0
  60. package/lib/agent-cli-provider/contract-actions.js +140 -0
  61. package/lib/agent-cli-provider/contract-actions.js.map +1 -0
  62. package/lib/agent-cli-provider/contract-env.d.ts +11 -0
  63. package/lib/agent-cli-provider/contract-env.d.ts.map +1 -0
  64. package/lib/agent-cli-provider/contract-env.js +113 -0
  65. package/lib/agent-cli-provider/contract-env.js.map +1 -0
  66. package/lib/agent-cli-provider/contract-envelope.d.ts +49 -0
  67. package/lib/agent-cli-provider/contract-envelope.d.ts.map +1 -0
  68. package/lib/agent-cli-provider/contract-envelope.js +52 -0
  69. package/lib/agent-cli-provider/contract-envelope.js.map +1 -0
  70. package/lib/agent-cli-provider/contract-errors.d.ts +22 -0
  71. package/lib/agent-cli-provider/contract-errors.d.ts.map +1 -0
  72. package/lib/agent-cli-provider/contract-errors.js +71 -0
  73. package/lib/agent-cli-provider/contract-errors.js.map +1 -0
  74. package/lib/agent-cli-provider/contract-fallback.d.ts +7 -0
  75. package/lib/agent-cli-provider/contract-fallback.d.ts.map +1 -0
  76. package/lib/agent-cli-provider/contract-fallback.js +102 -0
  77. package/lib/agent-cli-provider/contract-fallback.js.map +1 -0
  78. package/lib/agent-cli-provider/contract-invoke.d.ts +5 -0
  79. package/lib/agent-cli-provider/contract-invoke.d.ts.map +1 -0
  80. package/lib/agent-cli-provider/contract-invoke.js +89 -0
  81. package/lib/agent-cli-provider/contract-invoke.js.map +1 -0
  82. package/lib/agent-cli-provider/contract-options.d.ts +3 -0
  83. package/lib/agent-cli-provider/contract-options.d.ts.map +1 -0
  84. package/lib/agent-cli-provider/contract-options.js +140 -0
  85. package/lib/agent-cli-provider/contract-options.js.map +1 -0
  86. package/lib/agent-cli-provider/contract-parse.d.ts +15 -0
  87. package/lib/agent-cli-provider/contract-parse.d.ts.map +1 -0
  88. package/lib/agent-cli-provider/contract-parse.js +81 -0
  89. package/lib/agent-cli-provider/contract-parse.js.map +1 -0
  90. package/lib/agent-cli-provider/contract-support.d.ts +18 -0
  91. package/lib/agent-cli-provider/contract-support.d.ts.map +1 -0
  92. package/lib/agent-cli-provider/contract-support.js +154 -0
  93. package/lib/agent-cli-provider/contract-support.js.map +1 -0
  94. package/lib/agent-cli-provider/contract.d.ts +13 -0
  95. package/lib/agent-cli-provider/contract.d.ts.map +1 -0
  96. package/lib/agent-cli-provider/contract.js +30 -0
  97. package/lib/agent-cli-provider/contract.js.map +1 -0
  98. package/lib/agent-cli-provider/env-safety.d.ts +11 -0
  99. package/lib/agent-cli-provider/env-safety.d.ts.map +1 -0
  100. package/lib/agent-cli-provider/env-safety.js +83 -0
  101. package/lib/agent-cli-provider/env-safety.js.map +1 -0
  102. package/lib/agent-cli-provider/errors.d.ts +5 -0
  103. package/lib/agent-cli-provider/errors.d.ts.map +1 -0
  104. package/lib/agent-cli-provider/errors.js +115 -0
  105. package/lib/agent-cli-provider/errors.js.map +1 -0
  106. package/lib/agent-cli-provider/executable.d.ts +3 -0
  107. package/lib/agent-cli-provider/executable.d.ts.map +1 -0
  108. package/lib/agent-cli-provider/executable.js +24 -0
  109. package/lib/agent-cli-provider/executable.js.map +1 -0
  110. package/lib/agent-cli-provider/index.d.ts +8 -0
  111. package/lib/agent-cli-provider/index.d.ts.map +1 -0
  112. package/lib/agent-cli-provider/index.js +31 -0
  113. package/lib/agent-cli-provider/index.js.map +1 -0
  114. package/lib/agent-cli-provider/invoke-evidence.d.ts +4 -0
  115. package/lib/agent-cli-provider/invoke-evidence.d.ts.map +1 -0
  116. package/lib/agent-cli-provider/invoke-evidence.js +17 -0
  117. package/lib/agent-cli-provider/invoke-evidence.js.map +1 -0
  118. package/lib/agent-cli-provider/json.d.ts +16 -0
  119. package/lib/agent-cli-provider/json.d.ts.map +1 -0
  120. package/lib/agent-cli-provider/json.js +110 -0
  121. package/lib/agent-cli-provider/json.js.map +1 -0
  122. package/lib/agent-cli-provider/log-prefix.d.ts +2 -0
  123. package/lib/agent-cli-provider/log-prefix.d.ts.map +1 -0
  124. package/lib/agent-cli-provider/log-prefix.js +22 -0
  125. package/lib/agent-cli-provider/log-prefix.js.map +1 -0
  126. package/lib/agent-cli-provider/process-runner.d.ts +17 -0
  127. package/lib/agent-cli-provider/process-runner.d.ts.map +1 -0
  128. package/lib/agent-cli-provider/process-runner.js +89 -0
  129. package/lib/agent-cli-provider/process-runner.js.map +1 -0
  130. package/lib/agent-cli-provider/redaction.d.ts +9 -0
  131. package/lib/agent-cli-provider/redaction.d.ts.map +1 -0
  132. package/lib/agent-cli-provider/redaction.js +227 -0
  133. package/lib/agent-cli-provider/redaction.js.map +1 -0
  134. package/lib/agent-cli-provider/schema.d.ts +5 -0
  135. package/lib/agent-cli-provider/schema.d.ts.map +1 -0
  136. package/lib/agent-cli-provider/schema.js +136 -0
  137. package/lib/agent-cli-provider/schema.js.map +1 -0
  138. package/lib/agent-cli-provider/single-agent-runtime.d.ts +15 -0
  139. package/lib/agent-cli-provider/single-agent-runtime.d.ts.map +1 -0
  140. package/lib/agent-cli-provider/single-agent-runtime.js +228 -0
  141. package/lib/agent-cli-provider/single-agent-runtime.js.map +1 -0
  142. package/lib/agent-cli-provider/types.d.ts +194 -0
  143. package/lib/agent-cli-provider/types.d.ts.map +1 -0
  144. package/lib/agent-cli-provider/types.js +12 -0
  145. package/lib/agent-cli-provider/types.js.map +1 -0
  146. package/lib/completion.js +174 -0
  147. package/lib/detached-startup.js +220 -0
  148. package/lib/docker-config.js +220 -0
  149. package/lib/git-remote-utils.js +165 -0
  150. package/lib/id-detector.js +55 -0
  151. package/lib/provider-defaults.js +62 -0
  152. package/lib/provider-detection.js +59 -0
  153. package/lib/provider-names.js +57 -0
  154. package/lib/repo-settings.js +69 -0
  155. package/lib/settings/claude-auth.js +78 -0
  156. package/lib/settings.js +542 -0
  157. package/lib/start-cluster.js +321 -0
  158. package/lib/stream-json-parser.js +67 -0
  159. package/package.json +162 -0
  160. package/scripts/fix-node-pty-permissions.js +75 -0
  161. package/scripts/record-demo.sh +279 -0
  162. package/scripts/setup-merge-queue.sh +170 -0
  163. package/scripts/test-install.sh +40 -0
  164. package/scripts/validate-templates.js +107 -0
  165. package/src/agent/agent-config.js +266 -0
  166. package/src/agent/agent-context-builder.js +189 -0
  167. package/src/agent/agent-context-sections.js +338 -0
  168. package/src/agent/agent-context-sources.js +147 -0
  169. package/src/agent/agent-hook-executor.js +721 -0
  170. package/src/agent/agent-input-injector.js +141 -0
  171. package/src/agent/agent-lifecycle.js +982 -0
  172. package/src/agent/agent-quality-gate-schema.js +93 -0
  173. package/src/agent/agent-quality-gates-context.js +51 -0
  174. package/src/agent/agent-stuck-detector.js +256 -0
  175. package/src/agent/agent-task-executor.js +2028 -0
  176. package/src/agent/agent-trigger-evaluator.js +67 -0
  177. package/src/agent/context-metrics.js +160 -0
  178. package/src/agent/context-pack-builder.js +367 -0
  179. package/src/agent/context-replay-policy.js +51 -0
  180. package/src/agent/guidance-queue.js +77 -0
  181. package/src/agent/output-extraction.js +367 -0
  182. package/src/agent/output-reformatter.js +175 -0
  183. package/src/agent/pr-verification.js +653 -0
  184. package/src/agent/rate-limit-backoff.js +82 -0
  185. package/src/agent/schema-utils.js +146 -0
  186. package/src/agent/validation-platform.js +35 -0
  187. package/src/agent-cli-provider/adapters/claude-parser.ts +133 -0
  188. package/src/agent-cli-provider/adapters/claude-recovery.ts +203 -0
  189. package/src/agent-cli-provider/adapters/claude.ts +247 -0
  190. package/src/agent-cli-provider/adapters/codex-parser.ts +211 -0
  191. package/src/agent-cli-provider/adapters/codex.ts +217 -0
  192. package/src/agent-cli-provider/adapters/common.ts +124 -0
  193. package/src/agent-cli-provider/adapters/gemini.ts +243 -0
  194. package/src/agent-cli-provider/adapters/index.ts +126 -0
  195. package/src/agent-cli-provider/adapters/opencode.ts +286 -0
  196. package/src/agent-cli-provider/contract-actions.ts +150 -0
  197. package/src/agent-cli-provider/contract-env.ts +111 -0
  198. package/src/agent-cli-provider/contract-envelope.ts +110 -0
  199. package/src/agent-cli-provider/contract-errors.ts +66 -0
  200. package/src/agent-cli-provider/contract-fallback.ts +121 -0
  201. package/src/agent-cli-provider/contract-invoke.ts +104 -0
  202. package/src/agent-cli-provider/contract-options.ts +173 -0
  203. package/src/agent-cli-provider/contract-parse.ts +94 -0
  204. package/src/agent-cli-provider/contract-support.ts +167 -0
  205. package/src/agent-cli-provider/contract.ts +56 -0
  206. package/src/agent-cli-provider/env-safety.ts +82 -0
  207. package/src/agent-cli-provider/errors.ts +122 -0
  208. package/src/agent-cli-provider/executable.ts +24 -0
  209. package/src/agent-cli-provider/index.ts +83 -0
  210. package/src/agent-cli-provider/invoke-evidence.ts +18 -0
  211. package/src/agent-cli-provider/json.ts +114 -0
  212. package/src/agent-cli-provider/log-prefix.ts +20 -0
  213. package/src/agent-cli-provider/process-runner.ts +145 -0
  214. package/src/agent-cli-provider/redaction.ts +282 -0
  215. package/src/agent-cli-provider/schema.ts +115 -0
  216. package/src/agent-cli-provider/single-agent-runtime.ts +311 -0
  217. package/src/agent-cli-provider/types.ts +237 -0
  218. package/src/agent-wrapper.js +615 -0
  219. package/src/agents/git-pusher-template.js +705 -0
  220. package/src/attach/attach-client.js +438 -0
  221. package/src/attach/attach-server.js +543 -0
  222. package/src/attach/index.js +37 -0
  223. package/src/attach/protocol.js +220 -0
  224. package/src/attach/ring-buffer.js +121 -0
  225. package/src/attach/send-input.js +88 -0
  226. package/src/attach/socket-discovery.js +267 -0
  227. package/src/claude-task-runner.js +661 -0
  228. package/src/config-router.js +89 -0
  229. package/src/config-validator.js +2202 -0
  230. package/src/copy-worker.js +43 -0
  231. package/src/guidance-topics.js +10 -0
  232. package/src/input-helpers.js +65 -0
  233. package/src/isolation-manager.js +1734 -0
  234. package/src/issue-providers/README.md +305 -0
  235. package/src/issue-providers/azure-devops-provider.js +307 -0
  236. package/src/issue-providers/base-provider.js +232 -0
  237. package/src/issue-providers/github-provider.js +210 -0
  238. package/src/issue-providers/gitlab-provider.js +262 -0
  239. package/src/issue-providers/index.js +196 -0
  240. package/src/issue-providers/jira-provider.js +260 -0
  241. package/src/ledger.js +692 -0
  242. package/src/lib/gc.js +232 -0
  243. package/src/lib/safe-exec.js +88 -0
  244. package/src/logic-engine.js +201 -0
  245. package/src/message-buffer.js +81 -0
  246. package/src/message-bus-bridge.js +144 -0
  247. package/src/message-bus.js +256 -0
  248. package/src/name-generator.js +232 -0
  249. package/src/orchestrator.js +3924 -0
  250. package/src/preflight.js +712 -0
  251. package/src/process-metrics.js +608 -0
  252. package/src/providers/anthropic/index.js +3 -0
  253. package/src/providers/base-provider.js +355 -0
  254. package/src/providers/capabilities.js +60 -0
  255. package/src/providers/google/index.js +3 -0
  256. package/src/providers/index.js +293 -0
  257. package/src/providers/openai/index.js +3 -0
  258. package/src/providers/opencode/index.js +3 -0
  259. package/src/quality-gates.js +143 -0
  260. package/src/schemas/sub-cluster.js +208 -0
  261. package/src/state-snapshot.js +398 -0
  262. package/src/state-snapshotter.js +142 -0
  263. package/src/status-footer.js +1026 -0
  264. package/src/sub-cluster-wrapper.js +693 -0
  265. package/src/task-runner.js +30 -0
  266. package/src/template-resolver.js +425 -0
  267. package/src/template-validation/index.js +338 -0
  268. package/src/template-validation/simulate-consensus-gates.js +324 -0
  269. package/src/template-validation/simulate-random-topology.js +541 -0
  270. package/src/template-validation/simulate-two-stage-validation.js +270 -0
  271. package/src/worktree-claude-config.js +135 -0
  272. package/src/worktree-tooling-env.js +150 -0
  273. package/task-lib/attachable-watcher.js +381 -0
  274. package/task-lib/commands/clean.js +50 -0
  275. package/task-lib/commands/episodes.js +105 -0
  276. package/task-lib/commands/get-log-path.js +23 -0
  277. package/task-lib/commands/kill.js +32 -0
  278. package/task-lib/commands/list.js +105 -0
  279. package/task-lib/commands/logs.js +439 -0
  280. package/task-lib/commands/resume.js +42 -0
  281. package/task-lib/commands/run.js +57 -0
  282. package/task-lib/commands/schedule.js +105 -0
  283. package/task-lib/commands/scheduler-cmd.js +96 -0
  284. package/task-lib/commands/schedules.js +148 -0
  285. package/task-lib/commands/status.js +44 -0
  286. package/task-lib/commands/unschedule.js +16 -0
  287. package/task-lib/completion.js +9 -0
  288. package/task-lib/config.js +11 -0
  289. package/task-lib/name-generator.js +230 -0
  290. package/task-lib/package.json +3 -0
  291. package/task-lib/provider-helper-runtime.js +29 -0
  292. package/task-lib/runner.js +190 -0
  293. package/task-lib/scheduler.js +252 -0
  294. package/task-lib/store.js +529 -0
  295. package/task-lib/watcher.js +305 -0
@@ -0,0 +1,712 @@
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
+ // Determine CLI presence without depending on `claude --version` working.
80
+ // Some environments can have Claude installed but a broken/unwritable config dir,
81
+ // which should NOT block preflight.
82
+ if (!commandExists(command)) {
83
+ return {
84
+ installed: false,
85
+ version: null,
86
+ error: `Command '${command}' not installed`,
87
+ };
88
+ }
89
+
90
+ try {
91
+ const versionArgs = [...extraArgs, '--version'];
92
+ const versionCmd = [command, ...versionArgs].join(' ');
93
+ // Claude Code may try to write debug output under CLAUDE_CONFIG_DIR even for `--version`.
94
+ // Preflight should detect installation, not fail due to an unwritable/invalid config dir.
95
+ const preflightConfigDir = path.join(os.tmpdir(), 'zeroshot-claude-preflight');
96
+ try {
97
+ fs.mkdirSync(preflightConfigDir, { recursive: true });
98
+ } catch {
99
+ // If we can't create it, fall back to current env.
100
+ }
101
+ const output = execSync(versionCmd, {
102
+ encoding: 'utf8',
103
+ stdio: 'pipe',
104
+ env: {
105
+ ...process.env,
106
+ CLAUDE_CONFIG_DIR: preflightConfigDir,
107
+ },
108
+ });
109
+ const match = output.match(/(\d+\.\d+\.\d+)/);
110
+ return {
111
+ installed: true,
112
+ version: match ? match[1] : 'unknown',
113
+ error: null,
114
+ };
115
+ } catch {
116
+ // The CLI exists, but the version command can still fail due to local environment
117
+ // (e.g. config-dir permissions). Treat this as installed with unknown version so
118
+ // preflight doesn't block on non-essential metadata.
119
+ return {
120
+ installed: true,
121
+ version: 'unknown',
122
+ error: null,
123
+ };
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Check macOS Keychain for Claude Code credentials
129
+ * @returns {{ authenticated: boolean, error: string | null }}
130
+ */
131
+ function checkMacOsKeychain() {
132
+ if (os.platform() !== 'darwin') {
133
+ return { authenticated: false, error: 'Not macOS' };
134
+ }
135
+
136
+ try {
137
+ // Check if Claude Code credentials exist in Keychain
138
+ execSync('security find-generic-password -s "Claude Code-credentials"', {
139
+ encoding: 'utf8',
140
+ stdio: 'pipe',
141
+ timeout: 2000,
142
+ });
143
+ return { authenticated: true, error: null };
144
+ } catch {
145
+ return { authenticated: false, error: 'No credentials in Keychain' };
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Check Claude CLI authentication status
151
+ * @returns {{ authenticated: boolean, error: string | null, configDir: string, method?: string }}
152
+ */
153
+ function checkClaudeAuth() {
154
+ const configDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
155
+
156
+ // Helper to create consistent result objects
157
+ const authResult = (authenticated, error = null, method = null) => ({
158
+ authenticated,
159
+ error,
160
+ configDir,
161
+ ...(method && { method }),
162
+ });
163
+
164
+ // Check for Bedrock bearer token (highest priority)
165
+ if (process.env.AWS_BEARER_TOKEN_BEDROCK) {
166
+ if (!process.env.AWS_REGION) {
167
+ return authResult(false, 'AWS_BEARER_TOKEN_BEDROCK set but AWS_REGION is missing');
168
+ }
169
+ return authResult(true, null, 'bedrock_api_key');
170
+ }
171
+
172
+ // Check for ANTHROPIC_API_KEY environment variable
173
+ const apiKeyEnv = process.env.ANTHROPIC_API_KEY;
174
+ if (apiKeyEnv) {
175
+ if (!isValidAnthropicKey(apiKeyEnv)) {
176
+ return authResult(false, `ANTHROPIC_API_KEY must start with ${ANTHROPIC_KEY_PREFIX}`);
177
+ }
178
+ return authResult(true, null, 'env_api_key');
179
+ }
180
+
181
+ // Check for settings-based auth (anthropicApiKey or bedrockApiKey in settings)
182
+ const settings = loadSettings();
183
+ const settingsAuth = resolveClaudeAuth(settings);
184
+ if (settingsAuth.ANTHROPIC_API_KEY) {
185
+ return authResult(true, null, 'settings_api_key');
186
+ }
187
+ if (settingsAuth.AWS_BEARER_TOKEN_BEDROCK) {
188
+ if (!settingsAuth.AWS_REGION && !process.env.AWS_REGION) {
189
+ return authResult(false, 'Bedrock configured in settings but AWS_REGION is missing');
190
+ }
191
+ return authResult(true, null, 'settings_bedrock');
192
+ }
193
+
194
+ const credentialsPath = path.join(configDir, '.credentials.json');
195
+
196
+ // Check if credentials file exists
197
+ if (!fs.existsSync(credentialsPath)) {
198
+ // No credentials file - check macOS Keychain as fallback
199
+ // Only use Keychain when using default config dir (not custom CLAUDE_CONFIG_DIR)
200
+ if (!process.env.CLAUDE_CONFIG_DIR && checkMacOsKeychain().authenticated) {
201
+ return authResult(true, null, 'keychain');
202
+ }
203
+ return authResult(false, 'No credentials file found');
204
+ }
205
+
206
+ // Check if credentials file has content
207
+ try {
208
+ const content = fs.readFileSync(credentialsPath, 'utf8');
209
+ const creds = JSON.parse(content);
210
+
211
+ // Check for OAuth token (primary auth method)
212
+ if (creds.claudeAiOauth?.accessToken) {
213
+ const expiresAt = creds.claudeAiOauth.expiresAt;
214
+ if (expiresAt && new Date(expiresAt) < new Date()) {
215
+ return authResult(false, 'OAuth token expired');
216
+ }
217
+ return authResult(true);
218
+ }
219
+
220
+ // Check for API key auth
221
+ if (creds.apiKey) {
222
+ return authResult(true);
223
+ }
224
+
225
+ return authResult(false, 'No valid authentication found in credentials');
226
+ } catch (err) {
227
+ return authResult(false, `Failed to parse credentials: ${err.message}`);
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Check gh CLI authentication status
233
+ * @returns {{ installed: boolean, authenticated: boolean, error: string | null }}
234
+ */
235
+ function checkGhAuth() {
236
+ // Check if gh is installed
237
+ if (!commandExists('gh')) {
238
+ return {
239
+ installed: false,
240
+ authenticated: false,
241
+ error: 'gh CLI not installed',
242
+ };
243
+ }
244
+
245
+ // Check auth status with timeout to prevent hangs
246
+ // gh auth status: exit 0 = authenticated, exit 1 = not authenticated
247
+ // Note: gh outputs to stderr even on success
248
+ const AUTH_CHECK_TIMEOUT_MS = 10000;
249
+ try {
250
+ execSync('gh auth status', {
251
+ encoding: 'utf8',
252
+ stdio: 'pipe',
253
+ timeout: AUTH_CHECK_TIMEOUT_MS,
254
+ });
255
+ // Exit code 0 = authenticated
256
+ return {
257
+ installed: true,
258
+ authenticated: true,
259
+ error: null,
260
+ };
261
+ } catch (err) {
262
+ const stderr = err.stderr || '';
263
+
264
+ // Check if killed due to timeout
265
+ if (err.killed || err.signal === 'SIGTERM') {
266
+ return {
267
+ installed: true,
268
+ authenticated: false,
269
+ error: 'gh auth status timed out - try running: gh auth login',
270
+ };
271
+ }
272
+
273
+ // gh auth status returns non-zero if not authenticated
274
+ // Check stderr for common "not logged in" patterns
275
+ if (
276
+ stderr.includes('not logged in') ||
277
+ stderr.includes('not logged into') ||
278
+ stderr.includes('You are not logged')
279
+ ) {
280
+ return {
281
+ installed: true,
282
+ authenticated: false,
283
+ error: 'gh CLI not authenticated',
284
+ };
285
+ }
286
+
287
+ // But if stderr contains "Logged in", trust that (some edge cases)
288
+ if (stderr.includes('Logged in')) {
289
+ return {
290
+ installed: true,
291
+ authenticated: true,
292
+ error: null,
293
+ };
294
+ }
295
+
296
+ // Provide more helpful error - include actual stderr for debugging
297
+ const details = stderr.trim() || `Exit code ${err.status || 'unknown'}`;
298
+ return {
299
+ installed: true,
300
+ authenticated: false,
301
+ error: `gh auth check failed: ${details}`,
302
+ };
303
+ }
304
+ }
305
+
306
+ /**
307
+ * Check Docker availability
308
+ * @returns {{ available: boolean, error: string | null }}
309
+ */
310
+ function checkDocker() {
311
+ try {
312
+ execSync('docker --version', { encoding: 'utf8', stdio: 'pipe' });
313
+
314
+ // Also check if Docker daemon is running
315
+ execSync('docker info', { encoding: 'utf8', stdio: 'pipe' });
316
+
317
+ return {
318
+ available: true,
319
+ error: null,
320
+ };
321
+ } catch (err) {
322
+ const stderr = err.stderr || err.message || '';
323
+
324
+ if (stderr.includes('command not found') || stderr.includes('not found')) {
325
+ return {
326
+ available: false,
327
+ error: 'Docker not installed',
328
+ };
329
+ }
330
+
331
+ if (stderr.includes('Cannot connect') || stderr.includes('Is the docker daemon running')) {
332
+ return {
333
+ available: false,
334
+ error: 'Docker daemon not running',
335
+ };
336
+ }
337
+
338
+ return {
339
+ available: false,
340
+ error: stderr.trim() || 'Unknown Docker error',
341
+ };
342
+ }
343
+ }
344
+
345
+ function buildClaudeCommand(options) {
346
+ const { command, args } = getClaudeCommand();
347
+ return options.claudeCommand || [command, ...args].join(' ');
348
+ }
349
+
350
+ function validateClaudeProvider(options) {
351
+ const errors = [];
352
+ const warnings = [];
353
+ const claudeCommand = buildClaudeCommand(options);
354
+
355
+ const claude = getClaudeVersion(claudeCommand);
356
+ if (!claude.installed) {
357
+ errors.push(
358
+ formatError(
359
+ 'Claude command not available',
360
+ claude.error,
361
+ claudeCommand === 'claude'
362
+ ? [
363
+ 'Install Claude CLI: npm install -g @anthropic-ai/claude-code',
364
+ 'Or: brew install claude (macOS)',
365
+ 'Then run: claude --version',
366
+ ]
367
+ : [
368
+ `Command '${claudeCommand}' not found`,
369
+ 'Check settings: zeroshot settings',
370
+ 'Update claudeCommand: zeroshot settings set claudeCommand "your-command"',
371
+ 'Or install the missing command',
372
+ ]
373
+ )
374
+ );
375
+ } else if (claude.version) {
376
+ const [major, minor] = claude.version.split('.').map(Number);
377
+ if (major < 1 || (major === 1 && minor < 0)) {
378
+ warnings.push(
379
+ `⚠️ Claude CLI version ${claude.version} may be outdated. Consider upgrading.`
380
+ );
381
+ }
382
+ }
383
+
384
+ if (process.getuid && process.getuid() === 0) {
385
+ errors.push(
386
+ formatError(
387
+ 'Running as root',
388
+ 'Claude CLI refuses --dangerously-skip-permissions flag when running as root (UID 0)',
389
+ [
390
+ 'Run as non-root user in Docker: docker run --user 1000:1000 ...',
391
+ 'Or create non-root user: adduser testuser && su - testuser',
392
+ 'Or use existing node user: docker run --user node ...',
393
+ 'Security: Claude CLI blocks this flag as root to prevent privilege escalation',
394
+ ]
395
+ )
396
+ );
397
+ }
398
+
399
+ return { errors, warnings };
400
+ }
401
+
402
+ function validateCliProvider(command, title, detail, recovery) {
403
+ const errors = [];
404
+ if (!commandExists(command)) {
405
+ errors.push(formatError(title, detail, recovery));
406
+ }
407
+
408
+ return { errors, warnings: [] };
409
+ }
410
+
411
+ function validateProvider(providerName, options) {
412
+ const validatorByProvider = {
413
+ claude: () => validateClaudeProvider(options),
414
+ codex: () =>
415
+ validateCliProvider('codex', 'Codex CLI not available', 'Command "codex" not installed', [
416
+ 'Install Codex CLI: npm install -g @openai/codex',
417
+ 'Then run: codex --version',
418
+ ]),
419
+ gemini: () =>
420
+ validateCliProvider('gemini', 'Gemini CLI not available', 'Command "gemini" not installed', [
421
+ 'Install Gemini CLI: npm install -g @google/gemini-cli',
422
+ 'Then run: gemini --version',
423
+ ]),
424
+ opencode: () =>
425
+ validateCliProvider(
426
+ 'opencode',
427
+ 'Opencode CLI not available',
428
+ 'Command "opencode" not installed',
429
+ ['Install Opencode CLI: see https://opencode.ai', 'Then run: opencode --version']
430
+ ),
431
+ };
432
+
433
+ const validator = validatorByProvider[providerName];
434
+ if (!validator) {
435
+ return {
436
+ errors: [
437
+ formatError('Unknown provider', `Provider "${providerName}" is not supported`, [
438
+ 'Use claude, codex, gemini, or opencode',
439
+ ]),
440
+ ],
441
+ warnings: [],
442
+ };
443
+ }
444
+
445
+ return validator();
446
+ }
447
+
448
+ function validateGhRequirement() {
449
+ const errors = [];
450
+ const gh = checkGhAuth();
451
+ if (!gh.installed) {
452
+ errors.push(
453
+ formatError('GitHub CLI (gh) not installed', 'Required for fetching issues by number', [
454
+ 'Install: brew install gh (macOS) or apt install gh (Linux)',
455
+ 'Or download from: https://cli.github.com/',
456
+ ])
457
+ );
458
+ } else if (!gh.authenticated) {
459
+ errors.push(
460
+ formatError('GitHub CLI (gh) not authenticated', gh.error, [
461
+ 'Run: gh auth login',
462
+ 'Select GitHub.com, HTTPS, and authenticate via browser',
463
+ 'Then verify: gh auth status',
464
+ ])
465
+ );
466
+ }
467
+
468
+ return errors;
469
+ }
470
+
471
+ function validateDockerRequirement() {
472
+ const errors = [];
473
+ const docker = checkDocker();
474
+ if (!docker.available) {
475
+ errors.push(
476
+ formatError(
477
+ 'Docker not available',
478
+ docker.error,
479
+ docker.error.includes('daemon')
480
+ ? ['Start Docker Desktop', 'Or run: sudo systemctl start docker (Linux)']
481
+ : [
482
+ 'Install Docker Desktop from: https://docker.com/products/docker-desktop',
483
+ 'Then start Docker and verify: docker info',
484
+ ]
485
+ )
486
+ );
487
+ }
488
+
489
+ return errors;
490
+ }
491
+
492
+ function isGitRepository() {
493
+ try {
494
+ execSync('git rev-parse --git-dir', { stdio: 'pipe' });
495
+ return true;
496
+ } catch {
497
+ return false;
498
+ }
499
+ }
500
+
501
+ function validateGitRequirement() {
502
+ if (isGitRepository()) {
503
+ return [];
504
+ }
505
+
506
+ return [
507
+ formatError('Not in a git repository', 'Worktree isolation requires a git repository', [
508
+ 'Run from within a git repository',
509
+ 'Or use --docker instead of --worktree for non-git directories',
510
+ 'Initialize a repo with: git init',
511
+ ]),
512
+ ];
513
+ }
514
+
515
+ /**
516
+ * Run all preflight checks
517
+ * @param {Object} options - Preflight options
518
+ * @param {boolean} options.requireGh - Whether gh CLI is required (true if using issue number)
519
+ * @param {boolean} options.requireDocker - Whether Docker is required (true if using --docker)
520
+ * @param {boolean} options.requireGit - Whether git repo is required (true if using --worktree)
521
+ * @param {boolean} options.quiet - Suppress success messages
522
+ * @param {string} options.claudeCommand - Custom Claude command (from settings)
523
+ * @param {string} options.provider - Provider override
524
+ * @returns {ValidationResult}
525
+ */
526
+ function runPreflight(options = {}) {
527
+ const errors = [];
528
+ const warnings = [];
529
+
530
+ const settings = loadSettings();
531
+
532
+ if (process.platform === 'win32') {
533
+ return {
534
+ valid: false,
535
+ errors: [
536
+ formatError(
537
+ 'Windows not supported',
538
+ 'Zeroshot currently supports Linux and macOS only; Windows (native or WSL) is deferred.',
539
+ [
540
+ 'Use Linux or macOS to run Zeroshot',
541
+ 'Or run inside a Linux VM/container on Windows',
542
+ 'Check README for supported platforms and updates',
543
+ ]
544
+ ),
545
+ ],
546
+ warnings: [],
547
+ };
548
+ }
549
+ const providerName = normalizeProviderName(
550
+ options.provider || settings.defaultProvider || 'claude'
551
+ );
552
+
553
+ const providerResult = validateProvider(providerName, options);
554
+ errors.push(...providerResult.errors);
555
+ warnings.push(...providerResult.warnings);
556
+
557
+ // 4. Check issue provider CLI (if required)
558
+ if (options.issueProvider) {
559
+ const { getProvider } = require('./issue-providers');
560
+ const ProviderClass = getProvider(options.issueProvider);
561
+
562
+ if (ProviderClass) {
563
+ const tool = ProviderClass.getRequiredTool();
564
+
565
+ // Check if tool is installed
566
+ if (!commandExists(tool.name)) {
567
+ errors.push(
568
+ formatError(
569
+ `${ProviderClass.displayName} CLI (${tool.name}) not installed`,
570
+ `Required for fetching ${ProviderClass.displayName} issues`,
571
+ [tool.installHint, `Then verify: ${tool.checkCmd}`]
572
+ )
573
+ );
574
+ } else {
575
+ // Check provider authentication (abstracted per provider)
576
+ // Use targetHost from URL input if provided, otherwise detect from git context
577
+ // This ensures we check auth for the actual target, not the current repo
578
+ const targetHost =
579
+ options.targetHost || detectGitContext(options.cwd || process.cwd())?.host;
580
+ const authResult = ProviderClass.checkAuth(targetHost);
581
+ if (!authResult.authenticated) {
582
+ errors.push(
583
+ formatError(
584
+ `${ProviderClass.displayName} CLI (${tool.name}) not authenticated`,
585
+ authResult.error,
586
+ authResult.recovery
587
+ )
588
+ );
589
+ }
590
+ }
591
+ }
592
+ }
593
+
594
+ // 5. Check PR/MR CLI tools (if --pr or --ship mode is active)
595
+ if (options.autoPr) {
596
+ const { getPlatformForPR, getPRToolForPlatform, getProvider } = require('./issue-providers');
597
+
598
+ let platform;
599
+ let prGitContext;
600
+ try {
601
+ // Detect git platform (independent of issue provider)
602
+ platform = getPlatformForPR(options.cwd || process.cwd());
603
+ // Get git context for hostname (needed for multi-instance auth checks)
604
+ prGitContext = detectGitContext(options.cwd || process.cwd());
605
+ } catch (error) {
606
+ // If platform detection fails, show clear error
607
+ errors.push(
608
+ formatError('--pr mode requires a git repository', error.message, [
609
+ 'Ensure you are in a git repository with a remote URL from GitHub, GitLab, or Azure DevOps',
610
+ ])
611
+ );
612
+ // Skip CLI tool check if platform unknown
613
+ }
614
+
615
+ if (platform) {
616
+ // Get PR tool info from the provider (unified source of truth)
617
+ const tool = getPRToolForPlatform(platform);
618
+ const ProviderClass = getProvider(platform);
619
+
620
+ if (tool && !commandExists(tool.name)) {
621
+ errors.push(
622
+ formatError(
623
+ `${tool.displayName} CLI (${tool.name}) not installed`,
624
+ `Required for --pr mode with ${tool.displayName} repositories`,
625
+ [tool.installHint, `Then verify: ${tool.checkCmd}`]
626
+ )
627
+ );
628
+ } else if (tool && ProviderClass) {
629
+ // Check provider authentication (abstracted per provider)
630
+ // Pass hostname for multi-instance providers (e.g., GitLab with self-hosted)
631
+ const authResult = ProviderClass.checkAuth(prGitContext?.host);
632
+ if (!authResult.authenticated) {
633
+ errors.push(
634
+ formatError(
635
+ `${tool.displayName} CLI (${tool.name}) not authenticated`,
636
+ authResult.error,
637
+ authResult.recovery
638
+ )
639
+ );
640
+ }
641
+ }
642
+ }
643
+ }
644
+
645
+ // Legacy gh check for backward compatibility
646
+ if (options.requireGh) {
647
+ errors.push(...validateGhRequirement());
648
+ }
649
+
650
+ // 6. Check Docker (if required)
651
+ if (options.requireDocker) {
652
+ errors.push(...validateDockerRequirement());
653
+ }
654
+
655
+ // 7. Check git repo (if required for worktree isolation)
656
+ if (options.requireGit) {
657
+ errors.push(...validateGitRequirement());
658
+ }
659
+
660
+ return {
661
+ valid: errors.length === 0,
662
+ errors,
663
+ warnings,
664
+ };
665
+ }
666
+
667
+ /**
668
+ * Run preflight checks and exit if failed
669
+ * @param {Object} options - Preflight options
670
+ * @param {boolean} options.requireGh - Whether gh CLI is required
671
+ * @param {boolean} options.requireDocker - Whether Docker is required
672
+ * @param {boolean} options.requireGit - Whether git repo is required
673
+ * @param {boolean} options.quiet - Suppress success messages
674
+ * @param {string} options.provider - Provider override
675
+ */
676
+ function requirePreflight(options = {}) {
677
+ const result = runPreflight(options);
678
+
679
+ // Print warnings regardless of success
680
+ if (result.warnings.length > 0) {
681
+ for (const warning of result.warnings) {
682
+ console.warn(warning);
683
+ }
684
+ }
685
+
686
+ if (!result.valid) {
687
+ console.error('\n' + '='.repeat(60));
688
+ console.error('PREFLIGHT CHECK FAILED');
689
+ console.error('='.repeat(60));
690
+
691
+ for (const error of result.errors) {
692
+ console.error(error);
693
+ }
694
+
695
+ console.error('='.repeat(60) + '\n');
696
+ process.exit(1);
697
+ }
698
+
699
+ if (!options.quiet) {
700
+ console.log('✓ Preflight checks passed');
701
+ }
702
+ }
703
+
704
+ module.exports = {
705
+ runPreflight,
706
+ requirePreflight,
707
+ getClaudeVersion,
708
+ checkClaudeAuth,
709
+ checkGhAuth,
710
+ checkDocker,
711
+ formatError,
712
+ };