@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.
Files changed (136) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +8 -465
  3. package/cli/index.js +101 -5231
  4. package/package.json +16 -130
  5. package/CHANGELOG.md +0 -537
  6. package/cli/commands/providers.js +0 -149
  7. package/cli/lib/first-run.js +0 -211
  8. package/cli/lib/update-checker.js +0 -281
  9. package/cli/message-formatter-utils.js +0 -75
  10. package/cli/message-formatters-normal.js +0 -275
  11. package/cli/message-formatters-watch.js +0 -181
  12. package/cluster-templates/base-templates/debug-workflow.json +0 -375
  13. package/cluster-templates/base-templates/full-workflow.json +0 -581
  14. package/cluster-templates/base-templates/single-worker.json +0 -63
  15. package/cluster-templates/base-templates/worker-validator.json +0 -208
  16. package/cluster-templates/conductor-bootstrap.json +0 -124
  17. package/docker/zeroshot-cluster/Dockerfile +0 -152
  18. package/docker/zeroshot-cluster/pre-baked-deps.json +0 -28
  19. package/lib/completion.js +0 -174
  20. package/lib/docker-config.js +0 -220
  21. package/lib/git-remote-utils.js +0 -165
  22. package/lib/id-detector.js +0 -56
  23. package/lib/provider-defaults.js +0 -62
  24. package/lib/provider-detection.js +0 -59
  25. package/lib/provider-names.js +0 -57
  26. package/lib/settings/claude-auth.js +0 -78
  27. package/lib/settings.js +0 -503
  28. package/lib/stream-json-parser.js +0 -10
  29. package/scripts/fix-node-pty-permissions.js +0 -75
  30. package/scripts/record-demo.sh +0 -279
  31. package/scripts/setup-merge-queue.sh +0 -170
  32. package/scripts/test-install.sh +0 -40
  33. package/scripts/validate-templates.js +0 -100
  34. package/src/agent/agent-config.js +0 -246
  35. package/src/agent/agent-context-builder.js +0 -444
  36. package/src/agent/agent-hook-executor.js +0 -599
  37. package/src/agent/agent-lifecycle.js +0 -743
  38. package/src/agent/agent-stuck-detector.js +0 -256
  39. package/src/agent/agent-task-executor.js +0 -1690
  40. package/src/agent/agent-trigger-evaluator.js +0 -67
  41. package/src/agent/output-extraction.js +0 -217
  42. package/src/agent/output-reformatter.js +0 -175
  43. package/src/agent/schema-utils.js +0 -146
  44. package/src/agent-wrapper.js +0 -579
  45. package/src/agents/git-pusher-template.js +0 -285
  46. package/src/attach/attach-client.js +0 -438
  47. package/src/attach/attach-server.js +0 -543
  48. package/src/attach/index.js +0 -35
  49. package/src/attach/protocol.js +0 -220
  50. package/src/attach/ring-buffer.js +0 -121
  51. package/src/attach/socket-discovery.js +0 -267
  52. package/src/claude-task-runner.js +0 -574
  53. package/src/config-router.js +0 -80
  54. package/src/config-validator.js +0 -1925
  55. package/src/copy-worker.js +0 -43
  56. package/src/input-helpers.js +0 -65
  57. package/src/isolation-manager.js +0 -1471
  58. package/src/issue-providers/README.md +0 -305
  59. package/src/issue-providers/azure-devops-provider.js +0 -273
  60. package/src/issue-providers/base-provider.js +0 -232
  61. package/src/issue-providers/github-provider.js +0 -179
  62. package/src/issue-providers/gitlab-provider.js +0 -241
  63. package/src/issue-providers/index.js +0 -196
  64. package/src/issue-providers/jira-provider.js +0 -239
  65. package/src/ledger.js +0 -631
  66. package/src/lib/safe-exec.js +0 -88
  67. package/src/logic-engine.js +0 -223
  68. package/src/message-bus-bridge.js +0 -139
  69. package/src/message-bus.js +0 -249
  70. package/src/name-generator.js +0 -232
  71. package/src/orchestrator.js +0 -2815
  72. package/src/preflight.js +0 -639
  73. package/src/process-metrics.js +0 -608
  74. package/src/providers/anthropic/cli-builder.js +0 -73
  75. package/src/providers/anthropic/index.js +0 -204
  76. package/src/providers/anthropic/models.js +0 -23
  77. package/src/providers/anthropic/output-parser.js +0 -177
  78. package/src/providers/base-provider.js +0 -251
  79. package/src/providers/capabilities.js +0 -60
  80. package/src/providers/google/cli-builder.js +0 -55
  81. package/src/providers/google/index.js +0 -116
  82. package/src/providers/google/models.js +0 -24
  83. package/src/providers/google/output-parser.js +0 -101
  84. package/src/providers/index.js +0 -91
  85. package/src/providers/openai/cli-builder.js +0 -133
  86. package/src/providers/openai/index.js +0 -136
  87. package/src/providers/openai/models.js +0 -21
  88. package/src/providers/openai/output-parser.js +0 -143
  89. package/src/providers/opencode/cli-builder.js +0 -42
  90. package/src/providers/opencode/index.js +0 -103
  91. package/src/providers/opencode/models.js +0 -55
  92. package/src/providers/opencode/output-parser.js +0 -122
  93. package/src/schemas/sub-cluster.js +0 -185
  94. package/src/status-footer.js +0 -1026
  95. package/src/sub-cluster-wrapper.js +0 -601
  96. package/src/task-runner.js +0 -30
  97. package/src/template-resolver.js +0 -372
  98. package/src/tui/CHANGES.txt +0 -133
  99. package/src/tui/LAYOUT.md +0 -261
  100. package/src/tui/README.txt +0 -192
  101. package/src/tui/TWO-LEVEL-NAVIGATION.md +0 -186
  102. package/src/tui/data-poller.js +0 -349
  103. package/src/tui/demo.js +0 -208
  104. package/src/tui/formatters.js +0 -129
  105. package/src/tui/index.js +0 -193
  106. package/src/tui/keybindings.js +0 -324
  107. package/src/tui/layout.js +0 -334
  108. package/src/tui/renderer.js +0 -184
  109. package/task-lib/attachable-watcher.js +0 -309
  110. package/task-lib/claude-recovery.js +0 -156
  111. package/task-lib/commands/clean.js +0 -50
  112. package/task-lib/commands/episodes.js +0 -105
  113. package/task-lib/commands/get-log-path.js +0 -23
  114. package/task-lib/commands/kill.js +0 -32
  115. package/task-lib/commands/list.js +0 -105
  116. package/task-lib/commands/logs.js +0 -453
  117. package/task-lib/commands/resume.js +0 -42
  118. package/task-lib/commands/run.js +0 -57
  119. package/task-lib/commands/schedule.js +0 -105
  120. package/task-lib/commands/scheduler-cmd.js +0 -96
  121. package/task-lib/commands/schedules.js +0 -148
  122. package/task-lib/commands/status.js +0 -44
  123. package/task-lib/commands/unschedule.js +0 -16
  124. package/task-lib/completion.js +0 -9
  125. package/task-lib/config.js +0 -8
  126. package/task-lib/name-generator.js +0 -230
  127. package/task-lib/package.json +0 -3
  128. package/task-lib/runner.js +0 -201
  129. package/task-lib/scheduler.js +0 -252
  130. package/task-lib/store.js +0 -529
  131. package/task-lib/tui/formatters.js +0 -215
  132. package/task-lib/tui/index.js +0 -197
  133. package/task-lib/tui/layout.js +0 -111
  134. package/task-lib/tui/renderer.js +0 -129
  135. package/task-lib/tui.js +0 -425
  136. package/task-lib/watcher.js +0 -226
@@ -1,59 +0,0 @@
1
- const { execSync, spawnSync } = require('child_process');
2
- const fs = require('fs');
3
- const path = require('path');
4
-
5
- function commandExists(command) {
6
- if (!command) return false;
7
- if (command.includes(path.sep)) {
8
- return fs.existsSync(command);
9
- }
10
- try {
11
- execSync(`command -v ${command}`, { stdio: 'pipe' });
12
- return true;
13
- } catch {
14
- return false;
15
- }
16
- }
17
-
18
- function getCommandPath(command) {
19
- if (!command) return null;
20
- if (command.includes(path.sep)) {
21
- return fs.existsSync(command) ? command : null;
22
- }
23
- try {
24
- const output = execSync(`command -v ${command}`, { encoding: 'utf8', stdio: 'pipe' });
25
- return output.trim() || null;
26
- } catch {
27
- return null;
28
- }
29
- }
30
-
31
- function getHelpOutput(command, args = []) {
32
- if (!commandExists(command)) return '';
33
-
34
- const attempt = (flag) => {
35
- const result = spawnSync(command, [...args, flag], { encoding: 'utf8' });
36
- const output = `${result.stdout || ''}${result.stderr || ''}`;
37
- return output.trim();
38
- };
39
-
40
- const help = attempt('--help');
41
- if (help) return help;
42
-
43
- const alt = attempt('-h');
44
- return alt || '';
45
- }
46
-
47
- function getVersionOutput(command, args = []) {
48
- if (!commandExists(command)) return '';
49
- const result = spawnSync(command, [...args, '--version'], { encoding: 'utf8' });
50
- const output = `${result.stdout || ''}${result.stderr || ''}`;
51
- return output.trim();
52
- }
53
-
54
- module.exports = {
55
- commandExists,
56
- getCommandPath,
57
- getHelpOutput,
58
- getVersionOutput,
59
- };
@@ -1,57 +0,0 @@
1
- const PROVIDER_ALIASES = {
2
- anthropic: 'claude',
3
- openai: 'codex',
4
- google: 'gemini',
5
- claude: 'claude',
6
- codex: 'codex',
7
- gemini: 'gemini',
8
- opencode: 'opencode',
9
- };
10
-
11
- const VALID_PROVIDERS = ['claude', 'codex', 'gemini', 'opencode'];
12
-
13
- function normalizeProviderName(name) {
14
- if (!name || typeof name !== 'string') return name;
15
- const normalized = PROVIDER_ALIASES[name.toLowerCase()];
16
- return normalized || name;
17
- }
18
-
19
- function normalizeProviderSettings(providerSettings) {
20
- if (
21
- !providerSettings ||
22
- typeof providerSettings !== 'object' ||
23
- Array.isArray(providerSettings)
24
- ) {
25
- return providerSettings;
26
- }
27
-
28
- const normalized = {};
29
- const entries = Object.entries(providerSettings);
30
- const aliasFirst = entries.sort(([left], [right]) => {
31
- const leftIsCanonical = normalizeProviderName(left) === left;
32
- const rightIsCanonical = normalizeProviderName(right) === right;
33
- if (leftIsCanonical === rightIsCanonical) return 0;
34
- return leftIsCanonical ? 1 : -1;
35
- });
36
-
37
- for (const [key, value] of aliasFirst) {
38
- const canonical = normalizeProviderName(key);
39
- if (!VALID_PROVIDERS.includes(canonical)) {
40
- normalized[key] = value;
41
- continue;
42
- }
43
- normalized[canonical] = {
44
- ...(normalized[canonical] || {}),
45
- ...(value || {}),
46
- };
47
- }
48
-
49
- return normalized;
50
- }
51
-
52
- module.exports = {
53
- PROVIDER_ALIASES,
54
- VALID_PROVIDERS,
55
- normalizeProviderName,
56
- normalizeProviderSettings,
57
- };
@@ -1,78 +0,0 @@
1
- /**
2
- * Claude authentication settings and helpers
3
- * Extracted from settings.js for provider-specific isolation
4
- */
5
-
6
- /**
7
- * Anthropic API key prefix for validation
8
- */
9
- const ANTHROPIC_KEY_PREFIX = 'sk-ant-';
10
-
11
- /**
12
- * Environment variables used for Claude authentication
13
- */
14
- const CLAUDE_AUTH_ENV_VARS = [
15
- 'ANTHROPIC_API_KEY',
16
- 'AWS_BEARER_TOKEN_BEDROCK',
17
- 'AWS_REGION',
18
- 'CLAUDE_CODE_USE_BEDROCK',
19
- ];
20
-
21
- /**
22
- * Validate an Anthropic API key format
23
- * @param {string|null|undefined} key - API key to validate
24
- * @returns {boolean} True if key is falsy OR starts with valid prefix
25
- */
26
- function isValidAnthropicKey(key) {
27
- return !key || key.startsWith(ANTHROPIC_KEY_PREFIX);
28
- }
29
-
30
- /**
31
- * Check if Bedrock mode is active based on env and overrides
32
- * @param {Object} [envOverrides={}] - Environment variable overrides to check
33
- * @returns {boolean} True if Bedrock mode is active
34
- */
35
- function isBedrockMode(envOverrides = {}) {
36
- return (
37
- envOverrides.CLAUDE_CODE_USE_BEDROCK === '1' || process.env.CLAUDE_CODE_USE_BEDROCK === '1'
38
- );
39
- }
40
-
41
- /**
42
- * Resolve Claude authentication environment variables from settings
43
- * Bedrock takes priority over direct Anthropic API key AND OAuth session
44
- * @param {Object} settings - Settings object from loadSettings()
45
- * @returns {Object} Environment variables to set for Claude auth
46
- */
47
- function resolveClaudeAuth(settings) {
48
- const claudeSettings = settings.providerSettings?.claude || {};
49
- const env = {};
50
-
51
- // Bedrock takes priority over everything (including OAuth)
52
- if (!process.env.AWS_BEARER_TOKEN_BEDROCK && claudeSettings.bedrockApiKey) {
53
- env.AWS_BEARER_TOKEN_BEDROCK = claudeSettings.bedrockApiKey;
54
- env.CLAUDE_CODE_USE_BEDROCK = '1';
55
- if (claudeSettings.bedrockRegion && !process.env.AWS_REGION) {
56
- env.AWS_REGION = claudeSettings.bedrockRegion;
57
- }
58
- } else if (process.env.AWS_BEARER_TOKEN_BEDROCK && !process.env.CLAUDE_CODE_USE_BEDROCK) {
59
- // Auto-set CLAUDE_CODE_USE_BEDROCK if token present in env
60
- env.CLAUDE_CODE_USE_BEDROCK = '1';
61
- }
62
-
63
- // Anthropic API key only if no Bedrock
64
- const hasBedrock = env.AWS_BEARER_TOKEN_BEDROCK || process.env.AWS_BEARER_TOKEN_BEDROCK;
65
- if (!process.env.ANTHROPIC_API_KEY && !hasBedrock && claudeSettings.anthropicApiKey) {
66
- env.ANTHROPIC_API_KEY = claudeSettings.anthropicApiKey;
67
- }
68
-
69
- return env;
70
- }
71
-
72
- module.exports = {
73
- ANTHROPIC_KEY_PREFIX,
74
- CLAUDE_AUTH_ENV_VARS,
75
- isValidAnthropicKey,
76
- isBedrockMode,
77
- resolveClaudeAuth,
78
- };
package/lib/settings.js DELETED
@@ -1,503 +0,0 @@
1
- /**
2
- * Settings management for zeroshot
3
- * Persistent user preferences stored in ~/.zeroshot/settings.json
4
- */
5
-
6
- const fs = require('fs');
7
- const path = require('path');
8
- const os = require('os');
9
- const { validateMountConfig, validateEnvPassthrough } = require('./docker-config');
10
- const {
11
- VALID_PROVIDERS,
12
- normalizeProviderName,
13
- normalizeProviderSettings,
14
- } = require('./provider-names');
15
-
16
- // Lazy-loaded to avoid circular dependency (issue-providers → settings → issue-providers)
17
- let _issueProviderFns = null;
18
- function getIssueProviderFns() {
19
- if (!_issueProviderFns) {
20
- _issueProviderFns = require('../src/issue-providers');
21
- }
22
- return _issueProviderFns;
23
- }
24
-
25
- /**
26
- * Get settings file path (dynamically reads env var for testing)
27
- * Using a getter ensures tests can override the path at runtime
28
- * @returns {string}
29
- */
30
- function getSettingsFile() {
31
- return (
32
- process.env.ZEROSHOT_SETTINGS_FILE || path.join(os.homedir(), '.zeroshot', 'settings.json')
33
- );
34
- }
35
-
36
- // Import provider defaults from separate module to avoid circular dependency
37
- const { getProviderDefaults, clearProviderDefaultsCache } = require('./provider-defaults');
38
-
39
- /**
40
- * Model hierarchy for cost ceiling validation
41
- * Higher number = more expensive/capable model
42
- */
43
- const MODEL_HIERARCHY = {
44
- opus: 3,
45
- sonnet: 2,
46
- haiku: 1,
47
- };
48
-
49
- const VALID_MODELS = Object.keys(MODEL_HIERARCHY);
50
- const LEVEL_RANKS = { level1: 1, level2: 2, level3: 3 };
51
-
52
- /**
53
- * Validate a requested model against the maxModel ceiling and minModel floor
54
- * @param {string} requestedModel - Model the agent wants to use
55
- * @param {string} maxModel - Maximum allowed model (cost ceiling)
56
- * @param {string|null} minModel - Minimum required model (cost floor)
57
- * @returns {string} The validated model
58
- * @throws {Error} If requested model exceeds ceiling or falls below floor
59
- */
60
- function validateModelAgainstMax(requestedModel, maxModel, minModel = null) {
61
- if (!requestedModel) return maxModel; // Default to ceiling if unspecified
62
-
63
- if (!VALID_MODELS.includes(requestedModel)) {
64
- throw new Error(`Invalid model "${requestedModel}". Valid: ${VALID_MODELS.join(', ')}`);
65
- }
66
- if (!VALID_MODELS.includes(maxModel)) {
67
- throw new Error(`Invalid maxModel "${maxModel}". Valid: ${VALID_MODELS.join(', ')}`);
68
- }
69
-
70
- if (MODEL_HIERARCHY[requestedModel] > MODEL_HIERARCHY[maxModel]) {
71
- throw new Error(
72
- `Agent requests "${requestedModel}" but maxModel is "${maxModel}". ` +
73
- `Either lower agent's model or raise maxModel.`
74
- );
75
- }
76
-
77
- if (minModel) {
78
- if (!VALID_MODELS.includes(minModel)) {
79
- throw new Error(`Invalid minModel "${minModel}". Valid: ${VALID_MODELS.join(', ')}`);
80
- }
81
- if (MODEL_HIERARCHY[minModel] > MODEL_HIERARCHY[maxModel]) {
82
- throw new Error(`minModel "${minModel}" cannot be higher than maxModel "${maxModel}".`);
83
- }
84
- if (MODEL_HIERARCHY[requestedModel] < MODEL_HIERARCHY[minModel]) {
85
- throw new Error(
86
- `Agent requests "${requestedModel}" but minModel is "${minModel}". ` +
87
- `Either raise agent's model or lower minModel.`
88
- );
89
- }
90
- }
91
-
92
- return requestedModel;
93
- }
94
-
95
- // Default settings (base - without issue provider settings which are added dynamically)
96
- const DEFAULT_SETTINGS_BASE = {
97
- maxModel: 'opus', // Cost ceiling - agents cannot use models above this
98
- minModel: null, // Cost floor - agents cannot use models below this (null = no minimum)
99
- defaultProvider: 'claude',
100
- get providerSettings() {
101
- // Dynamically build from providers on first access
102
- return getProviderDefaults();
103
- },
104
- defaultConfig: 'conductor-bootstrap',
105
- defaultDocker: false,
106
- strictSchema: true, // true = reliable json output (default), false = live streaming (may crash - see bold-meadow-11)
107
- logLevel: 'normal',
108
- // Auto-update settings
109
- autoCheckUpdates: true, // Check npm registry for newer versions
110
- lastUpdateCheckAt: null, // Unix timestamp of last check (null = never checked)
111
- lastSeenVersion: null, // Don't re-prompt for same version
112
- // Claude command - customize how to invoke Claude CLI (default: 'claude')
113
- // Example: 'ccr code' for claude-code-router integration
114
- claudeCommand: 'claude',
115
- // Docker isolation mounts - preset names or {host, container, readonly?} objects
116
- // Valid presets: gh, git, ssh, aws, azure, kube, terraform, gcloud, claude, codex, gemini, opencode
117
- dockerMounts: ['gh', 'git', 'ssh'],
118
- // Extra env vars to pass to Docker container (in addition to preset-implied ones)
119
- // Supports: VAR (if set), VAR_* (pattern), VAR=value (forced), VAR= (empty)
120
- dockerEnvPassthrough: [],
121
- // Container home directory - where $HOME resolves in container paths
122
- // Default: /home/node (matches zeroshot-cluster-base image)
123
- dockerContainerHome: '/home/node',
124
- // Issue provider settings - defaultIssueSource is here, others come from providers
125
- defaultIssueSource: 'github', // 'github' | 'gitlab' | 'jira' | 'azure-devops'
126
- };
127
-
128
- // Cache for merged defaults (base + issue provider settings)
129
- let _defaultSettingsCache = null;
130
-
131
- /**
132
- * Get DEFAULT_SETTINGS with issue provider settings merged in
133
- * Lazy-loaded to avoid circular dependency at module load time
134
- */
135
- function getDefaultSettings() {
136
- if (!_defaultSettingsCache) {
137
- const issueProviderDefaults = getIssueProviderFns().getIssueProviderSettingsDefaults();
138
- _defaultSettingsCache = {
139
- ...DEFAULT_SETTINGS_BASE,
140
- ...issueProviderDefaults,
141
- };
142
- }
143
- return _defaultSettingsCache;
144
- }
145
-
146
- // For backward compatibility, export DEFAULT_SETTINGS as a getter
147
- const DEFAULT_SETTINGS = new Proxy(DEFAULT_SETTINGS_BASE, {
148
- get(target, prop) {
149
- // For known base properties, return from base
150
- if (prop in target) {
151
- return target[prop];
152
- }
153
- // For issue provider settings, get from merged defaults
154
- const merged = getDefaultSettings();
155
- return merged[prop];
156
- },
157
- has(target, prop) {
158
- if (prop in target) return true;
159
- const merged = getDefaultSettings();
160
- return prop in merged;
161
- },
162
- ownKeys() {
163
- const merged = getDefaultSettings();
164
- return Object.keys(merged);
165
- },
166
- getOwnPropertyDescriptor(target, prop) {
167
- const merged = getDefaultSettings();
168
- if (prop in merged) {
169
- return { enumerable: true, configurable: true, value: merged[prop] };
170
- }
171
- return undefined;
172
- },
173
- });
174
-
175
- function mapLegacyModelToLevel(model) {
176
- switch (model) {
177
- case 'haiku':
178
- return 'level1';
179
- case 'sonnet':
180
- return 'level2';
181
- case 'opus':
182
- return 'level3';
183
- default:
184
- return null;
185
- }
186
- }
187
-
188
- function mergeProviderSettings(current, overrides) {
189
- // Ensure current has all providers with their defaults
190
- const providerDefaults = getProviderDefaults();
191
- const merged = {};
192
-
193
- for (const provider of VALID_PROVIDERS) {
194
- merged[provider] = {
195
- ...(providerDefaults[provider] || {}),
196
- ...(current[provider] || {}),
197
- ...(overrides?.[provider] || {}),
198
- };
199
- if (!merged[provider].levelOverrides) {
200
- merged[provider].levelOverrides = {};
201
- }
202
- }
203
- return merged;
204
- }
205
-
206
- function applyLegacyModelBounds(settings) {
207
- if (!settings.providerSettings) return settings;
208
- const claude = settings.providerSettings.claude || {};
209
- const legacyMaxLevel = mapLegacyModelToLevel(settings.maxModel);
210
- const legacyMinLevel = mapLegacyModelToLevel(settings.minModel);
211
-
212
- if (legacyMaxLevel) {
213
- claude.maxLevel = legacyMaxLevel;
214
- }
215
-
216
- if (legacyMinLevel) {
217
- claude.minLevel = legacyMinLevel;
218
- }
219
-
220
- const minRank = LEVEL_RANKS[claude.minLevel] || LEVEL_RANKS.level1;
221
- const maxRank = LEVEL_RANKS[claude.maxLevel] || LEVEL_RANKS.level3;
222
- const defaultRank = LEVEL_RANKS[claude.defaultLevel] || LEVEL_RANKS.level2;
223
-
224
- if (minRank > maxRank) {
225
- claude.minLevel = 'level1';
226
- claude.maxLevel = 'level3';
227
- } else if (defaultRank < minRank) {
228
- claude.defaultLevel = claude.minLevel;
229
- } else if (defaultRank > maxRank) {
230
- claude.defaultLevel = claude.maxLevel;
231
- }
232
-
233
- settings.providerSettings.claude = claude;
234
- return settings;
235
- }
236
-
237
- function normalizeLoadedSettings(parsed) {
238
- const normalized = { ...parsed };
239
- if (parsed.defaultProvider) {
240
- normalized.defaultProvider = normalizeProviderName(parsed.defaultProvider);
241
- }
242
- if (parsed.providerSettings) {
243
- normalized.providerSettings = normalizeProviderSettings(parsed.providerSettings);
244
- }
245
- return normalized;
246
- }
247
-
248
- /**
249
- * Load settings from disk, merging with defaults
250
- */
251
- function loadSettings() {
252
- const settingsFile = getSettingsFile();
253
- if (!fs.existsSync(settingsFile)) {
254
- // Return a copy with resolved providerSettings
255
- return {
256
- ...DEFAULT_SETTINGS,
257
- providerSettings: getProviderDefaults(),
258
- };
259
- }
260
- try {
261
- const data = fs.readFileSync(settingsFile, 'utf8');
262
- const parsed = normalizeLoadedSettings(JSON.parse(data));
263
- const merged = { ...DEFAULT_SETTINGS, ...parsed };
264
- merged.defaultProvider =
265
- normalizeProviderName(merged.defaultProvider) || DEFAULT_SETTINGS.defaultProvider;
266
- merged.providerSettings = mergeProviderSettings(getProviderDefaults(), parsed.providerSettings);
267
- return applyLegacyModelBounds(merged);
268
- } catch {
269
- console.error('Warning: Could not load settings, using defaults');
270
- return {
271
- ...DEFAULT_SETTINGS,
272
- providerSettings: getProviderDefaults(),
273
- };
274
- }
275
- }
276
-
277
- /**
278
- * Save settings to disk
279
- */
280
- function saveSettings(settings) {
281
- const settingsFile = getSettingsFile();
282
- const dir = path.dirname(settingsFile);
283
- if (!fs.existsSync(dir)) {
284
- fs.mkdirSync(dir, { recursive: true });
285
- }
286
- fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2), 'utf8');
287
- }
288
-
289
- /**
290
- * Validate claudeCommand setting
291
- * @returns {string|null} Error message if invalid, null if valid
292
- */
293
- function validateClaudeCommand(value) {
294
- if (typeof value !== 'string') {
295
- return 'claudeCommand must be a string';
296
- }
297
- if (value.trim().length === 0) {
298
- return 'claudeCommand cannot be empty';
299
- }
300
- return null;
301
- }
302
-
303
- /**
304
- * Validate providerSettings structure by delegating to provider implementations
305
- * @returns {string|null} Error message if invalid, null if valid
306
- */
307
- function validateProviderSettings(value) {
308
- const normalizedSettings = normalizeProviderSettings(value);
309
- if (typeof value !== 'object' || value === null || Array.isArray(value)) {
310
- return 'providerSettings must be an object';
311
- }
312
-
313
- // Lazy require to avoid circular dependency
314
- const { getProvider } = require('../src/providers');
315
-
316
- for (const [providerName, settings] of Object.entries(normalizedSettings || {})) {
317
- if (!VALID_PROVIDERS.includes(providerName)) {
318
- return `Unknown provider in providerSettings: ${providerName}`;
319
- }
320
-
321
- // Delegate validation to the provider
322
- try {
323
- const provider = getProvider(providerName);
324
- const error = provider.validateSettings(settings);
325
- if (error) return error;
326
- } catch (err) {
327
- return `Failed to validate ${providerName} settings: ${err.message}`;
328
- }
329
- }
330
-
331
- return null;
332
- }
333
-
334
- /**
335
- * Validate a setting value
336
- * @returns {string|null} Error message if invalid, null if valid
337
- */
338
- function validateSetting(key, value) {
339
- if (!(key in DEFAULT_SETTINGS)) {
340
- return `Unknown setting: ${key}`;
341
- }
342
-
343
- if (key === 'maxModel' && !VALID_MODELS.includes(value)) {
344
- return `Invalid model: ${value}. Valid models: ${VALID_MODELS.join(', ')}`;
345
- }
346
-
347
- if (key === 'minModel' && value !== null && !VALID_MODELS.includes(value)) {
348
- return `Invalid model: ${value}. Valid models: ${VALID_MODELS.join(', ')}, null`;
349
- }
350
-
351
- if (key === 'logLevel' && !['quiet', 'normal', 'verbose'].includes(value)) {
352
- return `Invalid log level: ${value}. Valid levels: quiet, normal, verbose`;
353
- }
354
-
355
- if (key === 'claudeCommand') {
356
- return validateClaudeCommand(value);
357
- }
358
-
359
- if (key === 'defaultProvider') {
360
- const normalized = normalizeProviderName(value);
361
- if (!VALID_PROVIDERS.includes(normalized)) {
362
- return `Invalid provider: ${value}. Valid providers: ${VALID_PROVIDERS.join(', ')}`;
363
- }
364
- }
365
-
366
- if (key === 'providerSettings') {
367
- return validateProviderSettings(value);
368
- }
369
-
370
- if (key === 'dockerMounts') {
371
- return validateMountConfig(value);
372
- }
373
-
374
- if (key === 'dockerEnvPassthrough') {
375
- return validateEnvPassthrough(value);
376
- }
377
-
378
- // Issue source settings validation (grouped for maintainability)
379
- const issueSourceError = validateIssueSourceSetting(key, value);
380
- if (issueSourceError !== undefined) {
381
- return issueSourceError;
382
- }
383
-
384
- return null;
385
- }
386
-
387
- /**
388
- * Validate issue source related settings
389
- * Delegates to issue providers for provider-specific settings
390
- * @param {string} key - Setting key
391
- * @param {any} value - Setting value
392
- * @returns {string|null|undefined} Error message, null if valid, undefined if not an issue source setting
393
- */
394
- function validateIssueSourceSetting(key, value) {
395
- // defaultIssueSource is handled here (not provider-specific)
396
- if (key === 'defaultIssueSource') {
397
- const { listProviders } = getIssueProviderFns();
398
- const validSources = listProviders();
399
- if (!validSources.includes(value)) {
400
- return `Invalid issue source: ${value}. Valid: ${validSources.join(', ')}`;
401
- }
402
- return null;
403
- }
404
-
405
- // Delegate to issue providers for provider-specific settings
406
- const { validateIssueProviderSetting } = getIssueProviderFns();
407
- return validateIssueProviderSetting(key, value);
408
- }
409
-
410
- /**
411
- * Coerce value to correct type based on default value type
412
- */
413
- function coerceValue(key, value) {
414
- const defaultValue = DEFAULT_SETTINGS[key];
415
-
416
- // Handle null values for minModel
417
- if (key === 'minModel' && (value === 'null' || value === null)) {
418
- return null;
419
- }
420
-
421
- if (typeof defaultValue === 'boolean') {
422
- return value === 'true' || value === '1' || value === 'yes' || value === true;
423
- }
424
-
425
- if (typeof defaultValue === 'number') {
426
- const parsed = parseInt(value);
427
- if (isNaN(parsed)) {
428
- throw new Error(`Invalid number: ${value}`);
429
- }
430
- return parsed;
431
- }
432
-
433
- // Handle array settings (dockerMounts, dockerEnvPassthrough)
434
- if (Array.isArray(defaultValue)) {
435
- if (typeof value === 'string') {
436
- try {
437
- const parsed = JSON.parse(value);
438
- if (!Array.isArray(parsed)) {
439
- throw new Error(`${key} must be an array`);
440
- }
441
- return parsed;
442
- } catch (e) {
443
- if (e instanceof SyntaxError) {
444
- throw new Error(`Invalid JSON for ${key}: ${value}`);
445
- }
446
- throw e;
447
- }
448
- }
449
- return value;
450
- }
451
-
452
- if (key === 'providerSettings') {
453
- if (typeof value === 'string') {
454
- try {
455
- return normalizeProviderSettings(JSON.parse(value));
456
- } catch {
457
- throw new Error(`Invalid JSON for providerSettings: ${value}`);
458
- }
459
- }
460
- return normalizeProviderSettings(value);
461
- }
462
-
463
- if (key === 'defaultProvider') {
464
- return normalizeProviderName(value);
465
- }
466
-
467
- return value;
468
- }
469
-
470
- /**
471
- * Get parsed Claude command from settings/env
472
- * Supports space-separated commands like 'ccr code'
473
- * @returns {{ command: string, args: string[] }}
474
- */
475
- function getClaudeCommand() {
476
- const settings = loadSettings();
477
- const raw = process.env.ZEROSHOT_CLAUDE_COMMAND || settings.claudeCommand || 'claude';
478
- const parts = raw.trim().split(/\s+/);
479
- return {
480
- command: parts[0],
481
- args: parts.slice(1),
482
- };
483
- }
484
-
485
- module.exports = {
486
- loadSettings,
487
- saveSettings,
488
- validateSetting,
489
- coerceValue,
490
- DEFAULT_SETTINGS,
491
- getSettingsFile,
492
- getClaudeCommand,
493
- // Model validation exports
494
- MODEL_HIERARCHY,
495
- VALID_MODELS,
496
- validateModelAgainstMax,
497
- // Provider defaults exports
498
- clearProviderDefaultsCache,
499
- // Backward compatibility: SETTINGS_FILE as getter (reads env var dynamically)
500
- get SETTINGS_FILE() {
501
- return getSettingsFile();
502
- },
503
- };
@@ -1,10 +0,0 @@
1
- /**
2
- * Compatibility wrapper for Claude stream-json parsing.
3
- * Prefer provider-specific parsers in src/providers.
4
- */
5
- const { parseEvent, parseChunk } = require('../src/providers/anthropic/output-parser');
6
-
7
- module.exports = {
8
- parseEvent,
9
- parseChunk,
10
- };