@the-open-engine/zeroshot 6.2.0 → 6.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 (43) hide show
  1. package/README.md +141 -474
  2. package/cli/event-copy.js +12 -0
  3. package/cli/index.js +314 -96
  4. package/cli/message-formatters-normal.js +14 -2
  5. package/cli/message-formatters-watch.js +9 -2
  6. package/cluster-hooks/block-ask-user-question.py +53 -0
  7. package/cluster-hooks/block-dangerous-git.py +145 -0
  8. package/lib/clusters-registry.js +48 -0
  9. package/lib/compose-utils.js +101 -0
  10. package/lib/detached-startup.js +12 -1
  11. package/lib/id-detector.js +8 -9
  12. package/lib/path-check.js +63 -0
  13. package/lib/run-mode.js +34 -0
  14. package/lib/run-plan.js +32 -0
  15. package/lib/start-cluster.js +58 -28
  16. package/package.json +7 -6
  17. package/scripts/check-path.js +19 -0
  18. package/scripts/validate-templates.js +11 -29
  19. package/src/agent/agent-hook-executor.js +1 -1
  20. package/src/agent/agent-lifecycle.js +2 -0
  21. package/src/agent/agent-task-executor.js +4 -3
  22. package/src/agent/pr-verification.js +27 -1
  23. package/src/agents/git-pusher-template.js +121 -4
  24. package/src/attach/socket-discovery.js +2 -3
  25. package/src/claude-credentials.js +75 -0
  26. package/src/claude-task-runner.js +1 -0
  27. package/src/isolation-manager.js +12 -9
  28. package/src/issue-providers/README.md +45 -15
  29. package/src/issue-providers/index.js +2 -0
  30. package/src/issue-providers/jira-provider.js +8 -1
  31. package/src/issue-providers/linear-provider.js +405 -0
  32. package/src/ledger.js +45 -3
  33. package/src/lib/gc.js +2 -3
  34. package/src/message-bus.js +22 -2
  35. package/src/orchestrator.js +271 -144
  36. package/src/preflight.js +12 -16
  37. package/src/providers/base-provider.js +7 -6
  38. package/src/status-footer.js +13 -1
  39. package/src/template-validation/index.js +3 -0
  40. package/src/template-validation/report-formatter.js +55 -0
  41. package/src/worktree-claude-config.js +2 -13
  42. package/task-lib/runner.js +1 -0
  43. package/task-lib/watcher.js +1 -0
package/src/preflight.js CHANGED
@@ -21,6 +21,7 @@ const {
21
21
  const { loadSettings, getClaudeCommand } = require('../lib/settings.js');
22
22
  const { normalizeProviderName } = require('../lib/provider-names');
23
23
  const { detectGitContext } = require('../lib/git-remote-utils');
24
+ const { readKeychainCredentials } = require('./claude-credentials');
24
25
 
25
26
  /**
26
27
  * Validation result
@@ -133,17 +134,12 @@ function checkMacOsKeychain() {
133
134
  return { authenticated: false, error: 'Not macOS' };
134
135
  }
135
136
 
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
- });
137
+ // Reuses the same Keychain read that provisions isolated agent configs, so a
138
+ // green preflight here genuinely predicts runtime auth availability.
139
+ if (readKeychainCredentials()) {
143
140
  return { authenticated: true, error: null };
144
- } catch {
145
- return { authenticated: false, error: 'No credentials in Keychain' };
146
141
  }
142
+ return { authenticated: false, error: 'No credentials in Keychain' };
147
143
  }
148
144
 
149
145
  /**
@@ -523,7 +519,7 @@ function validateGitRequirement() {
523
519
  * @param {string} options.provider - Provider override
524
520
  * @returns {ValidationResult}
525
521
  */
526
- function runPreflight(options = {}) {
522
+ async function runPreflight(options = {}) {
527
523
  const errors = [];
528
524
  const warnings = [];
529
525
 
@@ -562,8 +558,8 @@ function runPreflight(options = {}) {
562
558
  if (ProviderClass) {
563
559
  const tool = ProviderClass.getRequiredTool();
564
560
 
565
- // Check if tool is installed
566
- if (!commandExists(tool.name)) {
561
+ // Check if tool is installed (providers with no CLI binary set tool.name to null)
562
+ if (tool.name && !commandExists(tool.name)) {
567
563
  errors.push(
568
564
  formatError(
569
565
  `${ProviderClass.displayName} CLI (${tool.name}) not installed`,
@@ -577,7 +573,7 @@ function runPreflight(options = {}) {
577
573
  // This ensures we check auth for the actual target, not the current repo
578
574
  const targetHost =
579
575
  options.targetHost || detectGitContext(options.cwd || process.cwd())?.host;
580
- const authResult = ProviderClass.checkAuth(targetHost);
576
+ const authResult = await ProviderClass.checkAuth(targetHost);
581
577
  if (!authResult.authenticated) {
582
578
  errors.push(
583
579
  formatError(
@@ -628,7 +624,7 @@ function runPreflight(options = {}) {
628
624
  } else if (tool && ProviderClass) {
629
625
  // Check provider authentication (abstracted per provider)
630
626
  // Pass hostname for multi-instance providers (e.g., GitLab with self-hosted)
631
- const authResult = ProviderClass.checkAuth(prGitContext?.host);
627
+ const authResult = await ProviderClass.checkAuth(prGitContext?.host);
632
628
  if (!authResult.authenticated) {
633
629
  errors.push(
634
630
  formatError(
@@ -673,8 +669,8 @@ function runPreflight(options = {}) {
673
669
  * @param {boolean} options.quiet - Suppress success messages
674
670
  * @param {string} options.provider - Provider override
675
671
  */
676
- function requirePreflight(options = {}) {
677
- const result = runPreflight(options);
672
+ async function requirePreflight(options = {}) {
673
+ const result = await runPreflight(options);
678
674
 
679
675
  // Print warnings regardless of success
680
676
  if (result.warnings.length > 0) {
@@ -248,16 +248,17 @@ class BaseProvider {
248
248
  );
249
249
  }
250
250
 
251
+ // minLevel/maxLevel are the user's floor/ceiling cost guardrails, not a hard
252
+ // constraint on template-pinned levels. Clamp an out-of-range level into range
253
+ // rather than throwing: rejecting made a single-level clamp (min === max)
254
+ // unusable with any template that pins a different level, and broke read-only
255
+ // commands like `zeroshot logs` that resolve model specs for display (#162).
251
256
  if (maxLevel && rank(level) > rank(maxLevel)) {
252
- throw new Error(
253
- `Level "${level}" exceeds maxLevel "${maxLevel}" for provider "${this.name}"`
254
- );
257
+ return maxLevel;
255
258
  }
256
259
 
257
260
  if (minLevel && rank(level) < rank(minLevel)) {
258
- throw new Error(
259
- `Level "${level}" is below minLevel "${minLevel}" for provider "${this.name}"`
260
- );
261
+ return minLevel;
261
262
  }
262
263
 
263
264
  return level;
@@ -118,6 +118,7 @@ class StatusFooter {
118
118
  this.scrollRegionSet = false;
119
119
  this.clusterId = null;
120
120
  this.clusterState = 'initializing';
121
+ this.runMode = null;
121
122
  this.startTime = Date.now();
122
123
  this.messageBus = null; // MessageBus for token usage tracking
123
124
 
@@ -392,6 +393,14 @@ class StatusFooter {
392
393
  this.clusterState = state;
393
394
  }
394
395
 
396
+ /**
397
+ * Set the armed run mode (e.g. 'ship', 'pr', 'worktree', 'docker')
398
+ * @param {string|null} mode
399
+ */
400
+ setRunMode(mode) {
401
+ this.runMode = mode;
402
+ }
403
+
395
404
  /**
396
405
  * Register an agent for monitoring
397
406
  * @param {AgentState} agentState
@@ -677,6 +686,10 @@ class StatusFooter {
677
686
  content += ` ${COLORS.cyan}${COLORS.bold}${shortId}${COLORS.reset} `;
678
687
  }
679
688
 
689
+ if (this.runMode) {
690
+ content += `${COLORS.dim}[${this.runMode}]${COLORS.reset} `;
691
+ }
692
+
680
693
  // Fill with border
681
694
  const contentLen = this.stripAnsi(content).length;
682
695
  const padding = Math.max(0, width - contentLen - 1);
@@ -879,7 +892,6 @@ class StatusFooter {
879
892
  * @returns {string}
880
893
  */
881
894
  stripAnsi(str) {
882
- // eslint-disable-next-line no-control-regex
883
895
  return str.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '');
884
896
  }
885
897
 
@@ -332,7 +332,10 @@ function ensureCompletionHandler(config, options = {}) {
332
332
  };
333
333
  }
334
334
 
335
+ const { formatValidationReport } = require('./report-formatter');
336
+
335
337
  module.exports = {
336
338
  validateTemplates,
337
339
  validateTemplateConfig,
340
+ formatValidationReport,
338
341
  };
@@ -0,0 +1,55 @@
1
+ const path = require('node:path');
2
+
3
+ function formatEntry({ relativePath, result, changedFiles, lines, suppressed }) {
4
+ if (!result.valid) {
5
+ lines.push(`\n❌ ${relativePath}`);
6
+ for (const error of result.errors) {
7
+ lines.push(` ERROR: ${error}`);
8
+ }
9
+ return true;
10
+ }
11
+
12
+ if (result.warnings.length > 0) {
13
+ if (changedFiles === null || changedFiles.has(relativePath)) {
14
+ lines.push(`\n⚠️ ${relativePath}`);
15
+ for (const warning of result.warnings) {
16
+ lines.push(` WARN: ${warning}`);
17
+ }
18
+ } else {
19
+ suppressed.count += result.warnings.length;
20
+ suppressed.fileCount += 1;
21
+ }
22
+ return false;
23
+ }
24
+
25
+ lines.push(`✓ ${relativePath}`);
26
+ return false;
27
+ }
28
+
29
+ function formatValidationReport(report, { changedFiles = null, cwd = process.cwd() } = {}) {
30
+ const lines = [];
31
+ const suppressed = { count: 0, fileCount: 0 };
32
+ let hasErrors = false;
33
+
34
+ for (const { filePath, result } of report.results) {
35
+ const relativePath = path.relative(cwd, filePath);
36
+ if (formatEntry({ relativePath, result, changedFiles, lines, suppressed })) {
37
+ hasErrors = true;
38
+ }
39
+ }
40
+
41
+ if (suppressed.count > 0) {
42
+ lines.push(
43
+ `\n${suppressed.count} pre-existing warning(s) across ${suppressed.fileCount} unrelated template(s) — run \`npm run validate:templates\` for detail`
44
+ );
45
+ }
46
+
47
+ lines.push(`\n${'='.repeat(60)}`);
48
+ lines.push(`Validated: ${report.validated} templates, Skipped: ${report.skipped} files`);
49
+
50
+ return { lines, hasErrors };
51
+ }
52
+
53
+ module.exports = {
54
+ formatValidationReport,
55
+ };
@@ -3,6 +3,7 @@ const os = require('os');
3
3
  const path = require('path');
4
4
 
5
5
  const { resolveWorktreeRoot } = require('./worktree-tooling-env');
6
+ const { provisionClaudeCredentials } = require('./claude-credentials');
6
7
 
7
8
  const CLAUDE_DIRNAME = '.claude';
8
9
  const SETTINGS_BASENAME = 'settings.json';
@@ -60,15 +61,6 @@ function ensureDir(dirPath) {
60
61
  fs.mkdirSync(dirPath, { recursive: true });
61
62
  }
62
63
 
63
- function copyIfExists(sourcePath, destPath) {
64
- if (!fs.existsSync(sourcePath)) {
65
- return;
66
- }
67
-
68
- ensureDir(path.dirname(destPath));
69
- fs.copyFileSync(sourcePath, destPath);
70
- }
71
-
72
64
  function resolveRepoClaudeConfig(worktreeRoot) {
73
65
  const configDir = path.join(worktreeRoot, CLAUDE_DIRNAME);
74
66
  const settingsPath = path.join(configDir, SETTINGS_BASENAME);
@@ -105,10 +97,7 @@ function prepareClaudeConfigDir(options = {}) {
105
97
  ensureDir(path.join(overlayDir, 'hooks'));
106
98
  ensureDir(path.join(overlayDir, 'projects'));
107
99
 
108
- copyIfExists(
109
- path.join(sourceDir, '.credentials.json'),
110
- path.join(overlayDir, '.credentials.json')
111
- );
100
+ provisionClaudeCredentials({ sourceDir, destDir: overlayDir });
112
101
 
113
102
  const sourceSettings = readJsonIfExists(path.join(sourceDir, SETTINGS_BASENAME)) || {};
114
103
  const repoSettings = readJsonIfExists(repoConfig.settingsPath) || {};
@@ -162,6 +162,7 @@ function spawnWatcher({ watcherScript, id, cwd, logFile, finalArgs, watcherConfi
162
162
  {
163
163
  detached: true,
164
164
  stdio: 'ignore',
165
+ windowsHide: true,
165
166
  }
166
167
  );
167
168
 
@@ -45,6 +45,7 @@ const child = spawn(command, finalArgs, {
45
45
  cwd: commandSpec.cwd || cwd,
46
46
  env,
47
47
  stdio: ['ignore', 'pipe', 'pipe'],
48
+ windowsHide: true,
48
49
  });
49
50
 
50
51
  updateTask(taskId, { pid: child.pid });