@covibes/zeroshot 5.3.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 (119) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +8 -385
  3. package/cli/index.js +108 -4615
  4. package/package.json +16 -125
  5. package/CHANGELOG.md +0 -537
  6. package/cli/commands/providers.js +0 -150
  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 -253
  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 -697
  14. package/cluster-templates/base-templates/single-worker.json +0 -63
  15. package/cluster-templates/base-templates/worker-validator.json +0 -152
  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 -212
  21. package/lib/id-detector.js +0 -53
  22. package/lib/provider-detection.js +0 -59
  23. package/lib/provider-names.js +0 -56
  24. package/lib/settings.js +0 -405
  25. package/lib/stream-json-parser.js +0 -10
  26. package/scripts/fix-node-pty-permissions.js +0 -75
  27. package/scripts/record-demo.sh +0 -279
  28. package/scripts/test-install.sh +0 -40
  29. package/scripts/validate-templates.js +0 -100
  30. package/src/agent/agent-config.js +0 -193
  31. package/src/agent/agent-context-builder.js +0 -335
  32. package/src/agent/agent-hook-executor.js +0 -402
  33. package/src/agent/agent-lifecycle.js +0 -683
  34. package/src/agent/agent-stuck-detector.js +0 -256
  35. package/src/agent/agent-task-executor.js +0 -1431
  36. package/src/agent/agent-trigger-evaluator.js +0 -67
  37. package/src/agent/output-extraction.js +0 -200
  38. package/src/agent/output-reformatter.js +0 -175
  39. package/src/agent/schema-utils.js +0 -111
  40. package/src/agent-wrapper.js +0 -570
  41. package/src/agents/git-pusher-agent.json +0 -20
  42. package/src/attach/attach-client.js +0 -438
  43. package/src/attach/attach-server.js +0 -543
  44. package/src/attach/index.js +0 -35
  45. package/src/attach/protocol.js +0 -220
  46. package/src/attach/ring-buffer.js +0 -121
  47. package/src/attach/socket-discovery.js +0 -267
  48. package/src/claude-task-runner.js +0 -523
  49. package/src/config-router.js +0 -80
  50. package/src/config-validator.js +0 -1660
  51. package/src/copy-worker.js +0 -43
  52. package/src/github.js +0 -139
  53. package/src/isolation-manager.js +0 -1381
  54. package/src/ledger.js +0 -614
  55. package/src/logic-engine.js +0 -223
  56. package/src/message-bus-bridge.js +0 -139
  57. package/src/message-bus.js +0 -249
  58. package/src/name-generator.js +0 -232
  59. package/src/orchestrator.js +0 -2519
  60. package/src/preflight.js +0 -485
  61. package/src/process-metrics.js +0 -566
  62. package/src/providers/anthropic/cli-builder.js +0 -45
  63. package/src/providers/anthropic/index.js +0 -134
  64. package/src/providers/anthropic/models.js +0 -23
  65. package/src/providers/anthropic/output-parser.js +0 -159
  66. package/src/providers/base-provider.js +0 -181
  67. package/src/providers/capabilities.js +0 -51
  68. package/src/providers/google/cli-builder.js +0 -55
  69. package/src/providers/google/index.js +0 -116
  70. package/src/providers/google/models.js +0 -24
  71. package/src/providers/google/output-parser.js +0 -92
  72. package/src/providers/index.js +0 -75
  73. package/src/providers/openai/cli-builder.js +0 -122
  74. package/src/providers/openai/index.js +0 -135
  75. package/src/providers/openai/models.js +0 -21
  76. package/src/providers/openai/output-parser.js +0 -129
  77. package/src/schemas/sub-cluster.js +0 -156
  78. package/src/status-footer.js +0 -980
  79. package/src/sub-cluster-wrapper.js +0 -560
  80. package/src/task-runner.js +0 -30
  81. package/src/template-resolver.js +0 -369
  82. package/src/tui/CHANGES.txt +0 -133
  83. package/src/tui/LAYOUT.md +0 -261
  84. package/src/tui/README.txt +0 -192
  85. package/src/tui/TWO-LEVEL-NAVIGATION.md +0 -186
  86. package/src/tui/data-poller.js +0 -325
  87. package/src/tui/demo.js +0 -208
  88. package/src/tui/formatters.js +0 -128
  89. package/src/tui/index.js +0 -193
  90. package/src/tui/keybindings.js +0 -383
  91. package/src/tui/layout.js +0 -334
  92. package/src/tui/renderer.js +0 -194
  93. package/task-lib/attachable-watcher.js +0 -272
  94. package/task-lib/claude-recovery.js +0 -119
  95. package/task-lib/commands/clean.js +0 -50
  96. package/task-lib/commands/get-log-path.js +0 -23
  97. package/task-lib/commands/kill.js +0 -32
  98. package/task-lib/commands/list.js +0 -105
  99. package/task-lib/commands/logs.js +0 -411
  100. package/task-lib/commands/resume.js +0 -42
  101. package/task-lib/commands/run.js +0 -57
  102. package/task-lib/commands/schedule.js +0 -105
  103. package/task-lib/commands/scheduler-cmd.js +0 -96
  104. package/task-lib/commands/schedules.js +0 -98
  105. package/task-lib/commands/status.js +0 -44
  106. package/task-lib/commands/unschedule.js +0 -16
  107. package/task-lib/completion.js +0 -9
  108. package/task-lib/config.js +0 -10
  109. package/task-lib/name-generator.js +0 -230
  110. package/task-lib/package.json +0 -3
  111. package/task-lib/runner.js +0 -144
  112. package/task-lib/scheduler.js +0 -252
  113. package/task-lib/store.js +0 -230
  114. package/task-lib/tui/formatters.js +0 -166
  115. package/task-lib/tui/index.js +0 -197
  116. package/task-lib/tui/layout.js +0 -111
  117. package/task-lib/tui/renderer.js +0 -119
  118. package/task-lib/tui.js +0 -384
  119. package/task-lib/watcher.js +0 -188
package/lib/completion.js DELETED
@@ -1,174 +0,0 @@
1
- const omelette = require('omelette');
2
- const fs = require('fs');
3
- const path = require('path');
4
-
5
- function setupCompletion() {
6
- const complete = omelette('zeroshot');
7
-
8
- complete.on('start', ({ reply }) => {
9
- reply(['--issue', '--text', '--config']);
10
- });
11
-
12
- complete.on('list', ({ reply }) => {
13
- reply([]);
14
- });
15
-
16
- complete.on('status', ({ reply }) => {
17
- // Complete with cluster IDs
18
- try {
19
- const clustersDir = path.join(process.env.HOME, '.zeroshot', 'clusters');
20
- if (fs.existsSync(clustersDir)) {
21
- const clusterIds = fs
22
- .readdirSync(clustersDir)
23
- .filter((f) => f.endsWith('.json'))
24
- .map((f) => f.replace('.json', ''));
25
- reply(clusterIds);
26
- } else {
27
- reply([]);
28
- }
29
- } catch {
30
- reply([]);
31
- }
32
- });
33
-
34
- complete.on('logs', ({ reply }) => {
35
- // Complete with cluster IDs or flags
36
- try {
37
- const clustersDir = path.join(process.env.HOME, '.zeroshot', 'clusters');
38
- const completions = ['--follow', '-f', '--limit', '-n'];
39
-
40
- if (fs.existsSync(clustersDir)) {
41
- const clusterIds = fs
42
- .readdirSync(clustersDir)
43
- .filter((f) => f.endsWith('.json'))
44
- .map((f) => f.replace('.json', ''));
45
- reply([...clusterIds, ...completions]);
46
- } else {
47
- reply(completions);
48
- }
49
- } catch {
50
- reply(['--follow', '-f', '--limit', '-n']);
51
- }
52
- });
53
-
54
- complete.on('stop', ({ reply }) => {
55
- // Complete with active cluster IDs
56
- try {
57
- const clustersDir = path.join(process.env.HOME, '.zeroshot', 'clusters');
58
- if (fs.existsSync(clustersDir)) {
59
- const clusterIds = fs
60
- .readdirSync(clustersDir)
61
- .filter((f) => f.endsWith('.json'))
62
- .map((f) => f.replace('.json', ''));
63
- reply(clusterIds);
64
- } else {
65
- reply([]);
66
- }
67
- } catch {
68
- reply([]);
69
- }
70
- });
71
-
72
- complete.on('kill', ({ reply }) => {
73
- // Complete with active cluster IDs
74
- try {
75
- const clustersDir = path.join(process.env.HOME, '.zeroshot', 'clusters');
76
- if (fs.existsSync(clustersDir)) {
77
- const clusterIds = fs
78
- .readdirSync(clustersDir)
79
- .filter((f) => f.endsWith('.json'))
80
- .map((f) => f.replace('.json', ''));
81
- reply(clusterIds);
82
- } else {
83
- reply([]);
84
- }
85
- } catch {
86
- reply([]);
87
- }
88
- });
89
-
90
- complete.on('export', ({ reply }) => {
91
- // Complete with cluster IDs
92
- try {
93
- const clustersDir = path.join(process.env.HOME, '.zeroshot', 'clusters');
94
- if (fs.existsSync(clustersDir)) {
95
- const clusterIds = fs
96
- .readdirSync(clustersDir)
97
- .filter((f) => f.endsWith('.json'))
98
- .map((f) => f.replace('.json', ''));
99
- reply(clusterIds);
100
- } else {
101
- reply([]);
102
- }
103
- } catch {
104
- reply([]);
105
- }
106
- });
107
-
108
- complete.on('finish', ({ reply }) => {
109
- // Complete with cluster IDs and flags
110
- try {
111
- const clustersDir = path.join(process.env.HOME, '.zeroshot', 'clusters');
112
- const completions = ['--merge', '--pr', '--push'];
113
-
114
- if (fs.existsSync(clustersDir)) {
115
- const clusterIds = fs
116
- .readdirSync(clustersDir)
117
- .filter((f) => f.endsWith('.json'))
118
- .map((f) => f.replace('.json', ''));
119
- reply([...clusterIds, ...completions]);
120
- } else {
121
- reply(completions);
122
- }
123
- } catch {
124
- reply(['--merge', '--pr', '--push']);
125
- }
126
- });
127
-
128
- complete.on('resume', ({ reply }) => {
129
- // Complete with cluster IDs
130
- try {
131
- const clustersDir = path.join(process.env.HOME, '.zeroshot', 'clusters');
132
- if (fs.existsSync(clustersDir)) {
133
- const clusterIds = fs
134
- .readdirSync(clustersDir)
135
- .filter((f) => f.endsWith('.json'))
136
- .map((f) => f.replace('.json', ''));
137
- reply(clusterIds);
138
- } else {
139
- reply([]);
140
- }
141
- } catch {
142
- reply([]);
143
- }
144
- });
145
-
146
- complete.on('ui', ({ reply }) => {
147
- reply(['--port']);
148
- });
149
-
150
- complete.on('watch', ({ reply }) => {
151
- reply(['--filter', '--refresh-rate', 'running', 'stopped', 'all']);
152
- });
153
-
154
- // Default completion - show commands
155
- complete.on('', ({ reply }) => {
156
- reply([
157
- 'start',
158
- 'list',
159
- 'status',
160
- 'logs',
161
- 'stop',
162
- 'kill',
163
- 'finish',
164
- 'resume',
165
- 'export',
166
- 'ui',
167
- 'watch',
168
- ]);
169
- });
170
-
171
- complete.init();
172
- }
173
-
174
- module.exports = { setupCompletion };
@@ -1,212 +0,0 @@
1
- /**
2
- * Docker mount configuration for isolation mode
3
- * Fully generic - no hardcoded paths, all configurable
4
- */
5
-
6
- /**
7
- * Built-in mount presets
8
- * Uses $HOME placeholder - resolved at runtime based on dockerContainerHome setting
9
- * Host paths use ~ (expanded to host user's home)
10
- * Container paths use $HOME (expanded to configured container home)
11
- */
12
- const MOUNT_PRESETS = {
13
- gh: { host: '~/.config/gh', container: '$HOME/.config/gh', readonly: false },
14
- git: { host: '~/.gitconfig', container: '$HOME/.gitconfig', readonly: true },
15
- ssh: { host: '~/.ssh', container: '$HOME/.ssh', readonly: true },
16
- aws: { host: '~/.aws', container: '$HOME/.aws', readonly: true },
17
- azure: { host: '~/.azure', container: '$HOME/.azure', readonly: true },
18
- kube: { host: '~/.kube', container: '$HOME/.kube', readonly: true },
19
- terraform: { host: '~/.terraform.d', container: '$HOME/.terraform.d', readonly: false },
20
- gcloud: { host: '~/.config/gcloud', container: '$HOME/.config/gcloud', readonly: true },
21
- claude: { host: '~/.claude', container: '$HOME/.claude', readonly: true },
22
- codex: { host: '~/.config/codex', container: '$HOME/.config/codex', readonly: true },
23
- gemini: { host: '~/.config/gemini', container: '$HOME/.config/gemini', readonly: true },
24
- };
25
-
26
- /**
27
- * Environment variables to auto-pass for each preset
28
- * Supports:
29
- * - Simple: 'VAR_NAME' (pass if set in host env)
30
- * - Pattern: 'VAR_*' (pass all matching vars)
31
- * - Forced: 'VAR=value' (always set to value)
32
- * - Empty: 'VAR=' (always set to empty string)
33
- */
34
- const ENV_PRESETS = {
35
- aws: ['AWS_REGION', 'AWS_DEFAULT_REGION', 'AWS_PROFILE', 'AWS_PAGER='],
36
- azure: ['AZURE_SUBSCRIPTION_ID', 'AZURE_TENANT_ID', 'AZURE_CLIENT_ID'],
37
- gcloud: ['CLOUDSDK_CORE_PROJECT', 'GOOGLE_CLOUD_PROJECT'],
38
- kube: ['KUBECONFIG'],
39
- terraform: ['TF_VAR_*'],
40
- };
41
-
42
- /**
43
- * Resolve mount config to actual mount specs
44
- * @param {Array<string|object>} config - Preset names or {host, container, readonly?} objects
45
- * @param {object} options - Resolution options
46
- * @param {string} [options.containerHome='/root'] - Container home directory for $HOME expansion
47
- * @returns {Array<{host: string, container: string, readonly: boolean}>}
48
- */
49
- function resolveMounts(config, options = {}) {
50
- if (!Array.isArray(config)) {
51
- throw new Error('dockerMounts must be an array');
52
- }
53
-
54
- const containerHome = options.containerHome || '/root';
55
-
56
- return config.flatMap((item) => {
57
- if (typeof item === 'string') {
58
- const preset = MOUNT_PRESETS[item];
59
- if (!preset) {
60
- throw new Error(
61
- `Unknown mount preset: "${item}". Valid presets: ${Object.keys(MOUNT_PRESETS).join(', ')}`
62
- );
63
- }
64
- return {
65
- host: preset.host,
66
- container: preset.container.replace(/\$HOME/g, containerHome),
67
- readonly: preset.readonly,
68
- };
69
- }
70
-
71
- if (typeof item === 'object' && item !== null) {
72
- if (!item.host || !item.container) {
73
- throw new Error('Custom mount must have "host" and "container" properties');
74
- }
75
- return {
76
- host: item.host,
77
- container: item.container.replace(/\$HOME/g, containerHome),
78
- readonly: item.readonly !== false,
79
- };
80
- }
81
-
82
- throw new Error(
83
- `Invalid mount config: ${JSON.stringify(item)}. Use preset name or {host, container, readonly?}`
84
- );
85
- });
86
- }
87
-
88
- /**
89
- * Resolve env vars to pass based on enabled presets + explicit additions
90
- * @param {Array<string|object>} mountConfig - The mount config (to detect presets)
91
- * @param {Array<string>} extraEnvs - Additional env vars to pass
92
- * @returns {Array<string>} - List of env var specs
93
- */
94
- function resolveEnvs(mountConfig, extraEnvs = []) {
95
- const envs = new Set(extraEnvs);
96
-
97
- for (const item of mountConfig) {
98
- if (typeof item === 'string' && ENV_PRESETS[item]) {
99
- for (const envVar of ENV_PRESETS[item]) {
100
- envs.add(envVar);
101
- }
102
- }
103
- }
104
-
105
- return [...envs];
106
- }
107
-
108
- /**
109
- * Expand env var patterns and resolve values
110
- * Supports:
111
- * - Simple: 'VAR_NAME' (use value from env if set)
112
- * - Pattern: 'VAR_*' (expand to all matching vars)
113
- * - Forced: 'VAR=value' (always set to value)
114
- * - Empty: 'VAR=' (always set to empty string)
115
- *
116
- * @param {Array<string>} envVars - List of env var specs
117
- * @param {object} env - Environment object (defaults to process.env)
118
- * @returns {Array<{name: string, value: string|null, forced: boolean}>}
119
- */
120
- function expandEnvPatterns(envVars, env = process.env) {
121
- const result = [];
122
-
123
- for (const envVar of envVars) {
124
- // Forced value: VAR=value or VAR=
125
- if (envVar.includes('=')) {
126
- const [name, ...valueParts] = envVar.split('=');
127
- const value = valueParts.join('=');
128
- result.push({ name, value, forced: true });
129
- }
130
- // Pattern: VAR_*
131
- else if (envVar.endsWith('*')) {
132
- const prefix = envVar.slice(0, -1);
133
- for (const key of Object.keys(env)) {
134
- if (key.startsWith(prefix)) {
135
- result.push({ name: key, value: null, forced: false });
136
- }
137
- }
138
- }
139
- // Simple: VAR_NAME
140
- else {
141
- result.push({ name: envVar, value: null, forced: false });
142
- }
143
- }
144
-
145
- return result;
146
- }
147
-
148
- /**
149
- * Validate mount config
150
- * @param {unknown} value - Value to validate
151
- * @returns {string|null} - Error message if invalid, null if valid
152
- */
153
- function validateMountConfig(value) {
154
- if (!Array.isArray(value)) {
155
- return 'dockerMounts must be an array';
156
- }
157
-
158
- for (const item of value) {
159
- if (typeof item === 'string') {
160
- if (!MOUNT_PRESETS[item]) {
161
- return `Unknown mount preset: "${item}". Valid: ${Object.keys(MOUNT_PRESETS).join(', ')}`;
162
- }
163
- } else if (typeof item === 'object' && item !== null) {
164
- if (!item.host) {
165
- return 'Custom mount missing "host" property';
166
- }
167
- if (!item.container) {
168
- return 'Custom mount missing "container" property';
169
- }
170
- if (item.readonly !== undefined && typeof item.readonly !== 'boolean') {
171
- return '"readonly" must be a boolean';
172
- }
173
- } else {
174
- return `Invalid mount: ${JSON.stringify(item)}. Use preset name or {host, container, readonly?}`;
175
- }
176
- }
177
-
178
- return null;
179
- }
180
-
181
- /**
182
- * Validate env passthrough config
183
- * @param {unknown} value - Value to validate
184
- * @returns {string|null} - Error message if invalid, null if valid
185
- */
186
- function validateEnvPassthrough(value) {
187
- if (!Array.isArray(value)) {
188
- return 'dockerEnvPassthrough must be an array';
189
- }
190
-
191
- for (const item of value) {
192
- if (typeof item !== 'string') {
193
- return `Invalid env var: ${JSON.stringify(item)}. Must be a string`;
194
- }
195
- // Allow: VAR, VAR_*, VAR=value, VAR=
196
- if (!/^[A-Z_][A-Z0-9_]*(\*|=.*)?$/i.test(item)) {
197
- return `Invalid env var spec: "${item}". Use VAR, VAR_*, or VAR=value`;
198
- }
199
- }
200
-
201
- return null;
202
- }
203
-
204
- module.exports = {
205
- MOUNT_PRESETS,
206
- ENV_PRESETS,
207
- resolveMounts,
208
- resolveEnvs,
209
- expandEnvPatterns,
210
- validateMountConfig,
211
- validateEnvPassthrough,
212
- };
@@ -1,53 +0,0 @@
1
- /**
2
- * ID Detector - Determines if an ID is a task or cluster
3
- *
4
- * Strategy:
5
- * 1. Check if ID exists in cluster storage
6
- * 2. If not, check if ID exists in task storage
7
- * 3. Return type: 'cluster', 'task', or null
8
- */
9
-
10
- const path = require('path');
11
- const fs = require('fs');
12
- const os = require('os');
13
-
14
- // Storage paths
15
- const CLUSTER_DIR = path.join(os.homedir(), '.zeroshot');
16
- const TASK_DIR = path.join(os.homedir(), '.claude-zeroshot');
17
-
18
- /**
19
- * Detect if ID is a cluster or task
20
- * @param {string} id - The ID to check
21
- * @returns {'cluster'|'task'|null} - Type of ID or null if not found
22
- */
23
- function detectIdType(id) {
24
- // Check clusters
25
- const clusterFile = path.join(CLUSTER_DIR, 'clusters.json');
26
- if (fs.existsSync(clusterFile)) {
27
- try {
28
- const clusters = JSON.parse(fs.readFileSync(clusterFile, 'utf8'));
29
- if (clusters[id]) {
30
- return 'cluster';
31
- }
32
- } catch {
33
- // Ignore parse errors
34
- }
35
- }
36
-
37
- // Check tasks
38
- const taskFile = path.join(TASK_DIR, 'tasks.json');
39
- if (fs.existsSync(taskFile)) {
40
- try {
41
- const tasks = JSON.parse(fs.readFileSync(taskFile, 'utf8'));
42
- if (tasks[id]) {
43
- return 'task';
44
- }
45
- } catch {
46
- // Ignore parse errors
47
- }
48
- }
49
-
50
- return null;
51
- }
52
-
53
- module.exports = { detectIdType };
@@ -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,56 +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
- };
9
-
10
- const VALID_PROVIDERS = ['claude', 'codex', 'gemini'];
11
-
12
- function normalizeProviderName(name) {
13
- if (!name || typeof name !== 'string') return name;
14
- const normalized = PROVIDER_ALIASES[name.toLowerCase()];
15
- return normalized || name;
16
- }
17
-
18
- function normalizeProviderSettings(providerSettings) {
19
- if (
20
- !providerSettings ||
21
- typeof providerSettings !== 'object' ||
22
- Array.isArray(providerSettings)
23
- ) {
24
- return providerSettings;
25
- }
26
-
27
- const normalized = {};
28
- const entries = Object.entries(providerSettings);
29
- const aliasFirst = entries.sort(([left], [right]) => {
30
- const leftIsCanonical = normalizeProviderName(left) === left;
31
- const rightIsCanonical = normalizeProviderName(right) === right;
32
- if (leftIsCanonical === rightIsCanonical) return 0;
33
- return leftIsCanonical ? 1 : -1;
34
- });
35
-
36
- for (const [key, value] of aliasFirst) {
37
- const canonical = normalizeProviderName(key);
38
- if (!VALID_PROVIDERS.includes(canonical)) {
39
- normalized[key] = value;
40
- continue;
41
- }
42
- normalized[canonical] = {
43
- ...(normalized[canonical] || {}),
44
- ...(value || {}),
45
- };
46
- }
47
-
48
- return normalized;
49
- }
50
-
51
- module.exports = {
52
- PROVIDER_ALIASES,
53
- VALID_PROVIDERS,
54
- normalizeProviderName,
55
- normalizeProviderSettings,
56
- };