@dotsetlabs/bellwether 2.1.2 → 2.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/README.md +2 -2
- package/dist/baseline/golden-output.d.ts +0 -4
- package/dist/baseline/golden-output.js +2 -47
- package/dist/cli/commands/baseline-accept.js +14 -45
- package/dist/cli/commands/baseline.js +23 -78
- package/dist/cli/commands/check-formatters.d.ts +10 -0
- package/dist/cli/commands/check-formatters.js +160 -0
- package/dist/cli/commands/check.js +33 -241
- package/dist/cli/commands/contract.js +1 -13
- package/dist/cli/commands/explore.js +19 -66
- package/dist/cli/commands/watch.js +2 -3
- package/dist/cli/output.d.ts +0 -42
- package/dist/cli/output.js +73 -110
- package/dist/cli/utils/config-loader.d.ts +6 -0
- package/dist/cli/utils/config-loader.js +19 -0
- package/dist/cli/utils/error-hints.d.ts +9 -0
- package/dist/cli/utils/error-hints.js +128 -0
- package/dist/cli/utils/headers.js +2 -25
- package/dist/cli/utils/path-resolution.d.ts +10 -0
- package/dist/cli/utils/path-resolution.js +27 -0
- package/dist/cli/utils/report-loader.d.ts +9 -0
- package/dist/cli/utils/report-loader.js +31 -0
- package/dist/cli/utils/server-runtime.d.ts +16 -0
- package/dist/cli/utils/server-runtime.js +31 -0
- package/dist/config/defaults.d.ts +0 -1
- package/dist/config/defaults.js +0 -1
- package/dist/constants/core.d.ts +0 -42
- package/dist/constants/core.js +0 -50
- package/dist/contract/validator.js +2 -47
- package/dist/interview/question-category.d.ts +5 -0
- package/dist/interview/question-category.js +2 -0
- package/dist/interview/question-types.d.ts +80 -0
- package/dist/interview/question-types.js +2 -0
- package/dist/interview/schema-test-generator.d.ts +3 -29
- package/dist/interview/schema-test-generator.js +11 -286
- package/dist/interview/test-fixtures.d.ts +19 -0
- package/dist/interview/test-fixtures.js +2 -0
- package/dist/interview/types.d.ts +5 -80
- package/dist/persona/types.d.ts +3 -5
- package/dist/scenarios/types.d.ts +1 -1
- package/dist/transport/auth-errors.d.ts +15 -0
- package/dist/transport/auth-errors.js +22 -0
- package/dist/transport/http-transport.js +7 -9
- package/dist/transport/mcp-client.d.ts +0 -4
- package/dist/transport/mcp-client.js +13 -37
- package/dist/transport/sse-transport.d.ts +0 -1
- package/dist/transport/sse-transport.js +13 -28
- package/dist/utils/content-type.d.ts +14 -0
- package/dist/utils/content-type.js +37 -0
- package/dist/utils/http-headers.d.ts +9 -0
- package/dist/utils/http-headers.js +34 -0
- package/dist/utils/smart-truncate.js +2 -23
- package/package.json +2 -2
package/dist/cli/output.js
CHANGED
|
@@ -55,64 +55,103 @@ export function resetOutput() {
|
|
|
55
55
|
export function isQuiet() {
|
|
56
56
|
return globalConfig.quiet ?? false;
|
|
57
57
|
}
|
|
58
|
+
function writeInfo(config, message) {
|
|
59
|
+
if (!config.quiet) {
|
|
60
|
+
console.log(message);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function writeWarn(message) {
|
|
64
|
+
console.warn(message);
|
|
65
|
+
}
|
|
66
|
+
function writeError(message) {
|
|
67
|
+
console.error(message);
|
|
68
|
+
}
|
|
69
|
+
function writeDebug(config, message, verbose) {
|
|
70
|
+
if (verbose && !config.quiet) {
|
|
71
|
+
console.log(message);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function writeNewline(config) {
|
|
75
|
+
if (!config.quiet) {
|
|
76
|
+
console.log('');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function writeLines(config, ...messages) {
|
|
80
|
+
if (!config.quiet) {
|
|
81
|
+
for (const msg of messages) {
|
|
82
|
+
console.log(msg);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function writeSection(config, title) {
|
|
87
|
+
if (!config.quiet) {
|
|
88
|
+
console.log(`\n--- ${title} ---`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function writeKeyValue(config, key, value) {
|
|
92
|
+
if (!config.quiet && value !== undefined) {
|
|
93
|
+
console.log(`${key}: ${value}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function writeListItem(config, item, indent) {
|
|
97
|
+
if (!config.quiet) {
|
|
98
|
+
const prefix = `${' '.repeat(indent)}- `;
|
|
99
|
+
console.log(`${prefix}${item}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function writeNumberedList(config, items, startIndex) {
|
|
103
|
+
if (!config.quiet) {
|
|
104
|
+
items.forEach((item, i) => {
|
|
105
|
+
console.log(` ${startIndex + i}) ${item}`);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
58
109
|
/**
|
|
59
110
|
* Standard information output.
|
|
60
111
|
* Use for progress messages, status updates, and general information.
|
|
61
112
|
*/
|
|
62
113
|
export function info(message) {
|
|
63
|
-
|
|
64
|
-
console.log(message);
|
|
65
|
-
}
|
|
114
|
+
writeInfo(globalConfig, message);
|
|
66
115
|
}
|
|
67
116
|
/**
|
|
68
117
|
* Success message output.
|
|
69
118
|
* Use for completion messages and positive confirmations.
|
|
70
119
|
*/
|
|
71
120
|
export function success(message) {
|
|
72
|
-
|
|
73
|
-
console.log(message);
|
|
74
|
-
}
|
|
121
|
+
writeInfo(globalConfig, message);
|
|
75
122
|
}
|
|
76
123
|
/**
|
|
77
124
|
* Warning message output.
|
|
78
125
|
* Always shown (not suppressed by quiet mode) as warnings are important.
|
|
79
126
|
*/
|
|
80
127
|
export function warn(message) {
|
|
81
|
-
|
|
128
|
+
writeWarn(message);
|
|
82
129
|
}
|
|
83
130
|
/**
|
|
84
131
|
* Error message output.
|
|
85
132
|
* Always shown (not suppressed by quiet mode) as errors are critical.
|
|
86
133
|
*/
|
|
87
134
|
export function error(message) {
|
|
88
|
-
|
|
135
|
+
writeError(message);
|
|
89
136
|
}
|
|
90
137
|
/**
|
|
91
138
|
* Debug output (only shown in verbose mode).
|
|
92
139
|
* For detailed information during development/troubleshooting.
|
|
93
140
|
*/
|
|
94
141
|
export function debug(message, verbose = false) {
|
|
95
|
-
|
|
96
|
-
console.log(message);
|
|
97
|
-
}
|
|
142
|
+
writeDebug(globalConfig, message, verbose);
|
|
98
143
|
}
|
|
99
144
|
/**
|
|
100
145
|
* Print a blank line for formatting.
|
|
101
146
|
*/
|
|
102
147
|
export function newline() {
|
|
103
|
-
|
|
104
|
-
console.log('');
|
|
105
|
-
}
|
|
148
|
+
writeNewline(globalConfig);
|
|
106
149
|
}
|
|
107
150
|
/**
|
|
108
151
|
* Print multiple lines.
|
|
109
152
|
*/
|
|
110
153
|
export function lines(...messages) {
|
|
111
|
-
|
|
112
|
-
for (const msg of messages) {
|
|
113
|
-
console.log(msg);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
154
|
+
writeLines(globalConfig, ...messages);
|
|
116
155
|
}
|
|
117
156
|
/**
|
|
118
157
|
* Print formatted JSON output.
|
|
@@ -125,36 +164,25 @@ export function json(data) {
|
|
|
125
164
|
* Print a section header.
|
|
126
165
|
*/
|
|
127
166
|
export function section(title) {
|
|
128
|
-
|
|
129
|
-
console.log(`\n--- ${title} ---`);
|
|
130
|
-
}
|
|
167
|
+
writeSection(globalConfig, title);
|
|
131
168
|
}
|
|
132
169
|
/**
|
|
133
170
|
* Print a key-value pair.
|
|
134
171
|
*/
|
|
135
172
|
export function keyValue(key, value) {
|
|
136
|
-
|
|
137
|
-
console.log(`${key}: ${value}`);
|
|
138
|
-
}
|
|
173
|
+
writeKeyValue(globalConfig, key, value);
|
|
139
174
|
}
|
|
140
175
|
/**
|
|
141
176
|
* Print a list item.
|
|
142
177
|
*/
|
|
143
178
|
export function listItem(item, indent = 0) {
|
|
144
|
-
|
|
145
|
-
const prefix = `${' '.repeat(indent)}- `;
|
|
146
|
-
console.log(`${prefix}${item}`);
|
|
147
|
-
}
|
|
179
|
+
writeListItem(globalConfig, item, indent);
|
|
148
180
|
}
|
|
149
181
|
/**
|
|
150
182
|
* Print numbered list items.
|
|
151
183
|
*/
|
|
152
184
|
export function numberedList(items, startIndex = 1) {
|
|
153
|
-
|
|
154
|
-
items.forEach((item, i) => {
|
|
155
|
-
console.log(` ${startIndex + i}) ${item}`);
|
|
156
|
-
});
|
|
157
|
-
}
|
|
185
|
+
writeNumberedList(globalConfig, items, startIndex);
|
|
158
186
|
}
|
|
159
187
|
/**
|
|
160
188
|
* Create a scoped output instance for a specific command.
|
|
@@ -172,63 +200,40 @@ export class Output {
|
|
|
172
200
|
this.config = config;
|
|
173
201
|
}
|
|
174
202
|
info(message) {
|
|
175
|
-
|
|
176
|
-
console.log(message);
|
|
177
|
-
}
|
|
203
|
+
writeInfo(this.config, message);
|
|
178
204
|
}
|
|
179
205
|
success(message) {
|
|
180
|
-
|
|
181
|
-
console.log(message);
|
|
182
|
-
}
|
|
206
|
+
writeInfo(this.config, message);
|
|
183
207
|
}
|
|
184
208
|
warn(message) {
|
|
185
|
-
|
|
209
|
+
writeWarn(message);
|
|
186
210
|
}
|
|
187
211
|
error(message) {
|
|
188
|
-
|
|
212
|
+
writeError(message);
|
|
189
213
|
}
|
|
190
214
|
debug(message, verbose = false) {
|
|
191
|
-
|
|
192
|
-
console.log(message);
|
|
193
|
-
}
|
|
215
|
+
writeDebug(this.config, message, verbose);
|
|
194
216
|
}
|
|
195
217
|
newline() {
|
|
196
|
-
|
|
197
|
-
console.log('');
|
|
198
|
-
}
|
|
218
|
+
writeNewline(this.config);
|
|
199
219
|
}
|
|
200
220
|
lines(...messages) {
|
|
201
|
-
|
|
202
|
-
for (const msg of messages) {
|
|
203
|
-
console.log(msg);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
221
|
+
writeLines(this.config, ...messages);
|
|
206
222
|
}
|
|
207
223
|
json(data) {
|
|
208
224
|
console.log(JSON.stringify(data, null, 2));
|
|
209
225
|
}
|
|
210
226
|
section(title) {
|
|
211
|
-
|
|
212
|
-
console.log(`\n--- ${title} ---`);
|
|
213
|
-
}
|
|
227
|
+
writeSection(this.config, title);
|
|
214
228
|
}
|
|
215
229
|
keyValue(key, value) {
|
|
216
|
-
|
|
217
|
-
console.log(`${key}: ${value}`);
|
|
218
|
-
}
|
|
230
|
+
writeKeyValue(this.config, key, value);
|
|
219
231
|
}
|
|
220
232
|
listItem(item, indent = 0) {
|
|
221
|
-
|
|
222
|
-
const prefix = `${' '.repeat(indent)}- `;
|
|
223
|
-
console.log(`${prefix}${item}`);
|
|
224
|
-
}
|
|
233
|
+
writeListItem(this.config, item, indent);
|
|
225
234
|
}
|
|
226
235
|
numberedList(items, startIndex = 1) {
|
|
227
|
-
|
|
228
|
-
items.forEach((item, i) => {
|
|
229
|
-
console.log(` ${startIndex + i}) ${item}`);
|
|
230
|
-
});
|
|
231
|
-
}
|
|
236
|
+
writeNumberedList(this.config, items, startIndex);
|
|
232
237
|
}
|
|
233
238
|
}
|
|
234
239
|
/**
|
|
@@ -415,46 +420,4 @@ export function createStreamingCallback(prefix) {
|
|
|
415
420
|
},
|
|
416
421
|
};
|
|
417
422
|
}
|
|
418
|
-
/**
|
|
419
|
-
* Label mapping for diff severity levels.
|
|
420
|
-
*/
|
|
421
|
-
const SEVERITY_LABELS = {
|
|
422
|
-
none: '[ok]',
|
|
423
|
-
info: '[info]',
|
|
424
|
-
warning: '[warn]',
|
|
425
|
-
breaking: '[break]',
|
|
426
|
-
};
|
|
427
|
-
/**
|
|
428
|
-
* Get the label for a severity level.
|
|
429
|
-
*/
|
|
430
|
-
export function getSeverityIcon(severity) {
|
|
431
|
-
return SEVERITY_LABELS[severity] ?? '?';
|
|
432
|
-
}
|
|
433
|
-
/**
|
|
434
|
-
* Default export for convenient importing.
|
|
435
|
-
*/
|
|
436
|
-
export default {
|
|
437
|
-
configureOutput,
|
|
438
|
-
getOutputConfig,
|
|
439
|
-
resetOutput,
|
|
440
|
-
isQuiet,
|
|
441
|
-
info,
|
|
442
|
-
success,
|
|
443
|
-
warn,
|
|
444
|
-
error,
|
|
445
|
-
debug,
|
|
446
|
-
newline,
|
|
447
|
-
lines,
|
|
448
|
-
json,
|
|
449
|
-
section,
|
|
450
|
-
keyValue,
|
|
451
|
-
listItem,
|
|
452
|
-
numberedList,
|
|
453
|
-
createOutput,
|
|
454
|
-
Output,
|
|
455
|
-
StreamingDisplay,
|
|
456
|
-
createStreamingDisplay,
|
|
457
|
-
createStreamingCallback,
|
|
458
|
-
getSeverityIcon,
|
|
459
|
-
};
|
|
460
423
|
//# sourceMappingURL=output.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type BellwetherConfig } from '../../config/loader.js';
|
|
2
|
+
/**
|
|
3
|
+
* Load configuration and exit with a user-friendly message when missing.
|
|
4
|
+
*/
|
|
5
|
+
export declare function loadConfigOrExit(configPath?: string): BellwetherConfig;
|
|
6
|
+
//# sourceMappingURL=config-loader.d.ts.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { loadConfig, ConfigNotFoundError } from '../../config/loader.js';
|
|
2
|
+
import { EXIT_CODES } from '../../constants.js';
|
|
3
|
+
import * as output from '../output.js';
|
|
4
|
+
/**
|
|
5
|
+
* Load configuration and exit with a user-friendly message when missing.
|
|
6
|
+
*/
|
|
7
|
+
export function loadConfigOrExit(configPath) {
|
|
8
|
+
try {
|
|
9
|
+
return loadConfig(configPath);
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
if (error instanceof ConfigNotFoundError) {
|
|
13
|
+
output.error(error.message);
|
|
14
|
+
process.exit(EXIT_CODES.ERROR);
|
|
15
|
+
}
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=config-loader.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared check-command transport/auth remediation hints.
|
|
3
|
+
*/
|
|
4
|
+
export declare function printCheckErrorHints(error: unknown, transport: string): void;
|
|
5
|
+
/**
|
|
6
|
+
* Shared explore-command transport/auth remediation hints.
|
|
7
|
+
*/
|
|
8
|
+
export declare function printExploreErrorHints(error: unknown, transport: string): void;
|
|
9
|
+
//# sourceMappingURL=error-hints.d.ts.map
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { ServerAuthError } from '../../errors/types.js';
|
|
2
|
+
import * as output from '../output.js';
|
|
3
|
+
function isAuthError(context) {
|
|
4
|
+
const { error, errorMessage } = context;
|
|
5
|
+
return (error instanceof ServerAuthError ||
|
|
6
|
+
errorMessage.includes('401') ||
|
|
7
|
+
errorMessage.includes('403') ||
|
|
8
|
+
errorMessage.includes('407') ||
|
|
9
|
+
/unauthorized|forbidden|authentication|authorization/i.test(errorMessage));
|
|
10
|
+
}
|
|
11
|
+
function isConnectionRefused(context) {
|
|
12
|
+
const { errorMessage } = context;
|
|
13
|
+
return errorMessage.includes('ECONNREFUSED') || errorMessage.includes('Connection refused');
|
|
14
|
+
}
|
|
15
|
+
function isRemoteNotFound(context) {
|
|
16
|
+
return context.isRemoteTransport && context.errorMessage.includes('HTTP 404');
|
|
17
|
+
}
|
|
18
|
+
function isTimeoutError(context) {
|
|
19
|
+
const { errorMessage } = context;
|
|
20
|
+
return errorMessage.includes('timeout') || errorMessage.includes('Timeout');
|
|
21
|
+
}
|
|
22
|
+
function isCommandNotFound(context) {
|
|
23
|
+
const { errorMessage, isRemoteTransport } = context;
|
|
24
|
+
return (!isRemoteTransport && (errorMessage.includes('ENOENT') || errorMessage.includes('not found')));
|
|
25
|
+
}
|
|
26
|
+
function isApiKeyError(context) {
|
|
27
|
+
const { errorMessage } = context;
|
|
28
|
+
return errorMessage.includes('API key') || errorMessage.includes('authentication');
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Shared check-command transport/auth remediation hints.
|
|
32
|
+
*/
|
|
33
|
+
export function printCheckErrorHints(error, transport) {
|
|
34
|
+
const context = {
|
|
35
|
+
error,
|
|
36
|
+
isRemoteTransport: transport !== 'stdio',
|
|
37
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
38
|
+
};
|
|
39
|
+
if (isAuthError(context)) {
|
|
40
|
+
output.error('\nAuthentication failed:');
|
|
41
|
+
output.error(' - Add server.headers.Authorization in bellwether.yaml');
|
|
42
|
+
output.error(' - Or pass --header "Authorization: Bearer $TOKEN"');
|
|
43
|
+
output.error(' - Verify credentials are valid and not expired');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (isConnectionRefused(context)) {
|
|
47
|
+
output.error('\nPossible causes:');
|
|
48
|
+
if (context.isRemoteTransport) {
|
|
49
|
+
output.error(' - The remote MCP server is not reachable');
|
|
50
|
+
output.error(' - The server URL/port is incorrect');
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
output.error(' - The MCP server is not running');
|
|
54
|
+
output.error(' - The server address/port is incorrect');
|
|
55
|
+
}
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (isRemoteNotFound(context)) {
|
|
59
|
+
output.error('\nPossible causes:');
|
|
60
|
+
output.error(' - The remote MCP URL is incorrect');
|
|
61
|
+
output.error(' - For SSE transport, verify the server exposes /sse');
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (isTimeoutError(context)) {
|
|
65
|
+
output.error('\nPossible causes:');
|
|
66
|
+
output.error(' - The MCP server is taking too long to respond');
|
|
67
|
+
output.error(' - Increase server.timeout in bellwether.yaml');
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (isCommandNotFound(context)) {
|
|
71
|
+
output.error('\nPossible causes:');
|
|
72
|
+
output.error(' - The server command was not found');
|
|
73
|
+
output.error(' - Check that the command is installed and in PATH');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Shared explore-command transport/auth remediation hints.
|
|
78
|
+
*/
|
|
79
|
+
export function printExploreErrorHints(error, transport) {
|
|
80
|
+
const context = {
|
|
81
|
+
error,
|
|
82
|
+
isRemoteTransport: transport !== 'stdio',
|
|
83
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
84
|
+
};
|
|
85
|
+
if (isAuthError(context)) {
|
|
86
|
+
output.error('\nPossible causes:');
|
|
87
|
+
output.error(' - Missing or invalid remote MCP authentication headers');
|
|
88
|
+
output.error(' - Add server.headers.Authorization or pass --header "Authorization: Bearer $TOKEN"');
|
|
89
|
+
output.error(' - Verify token scopes/permissions');
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (isConnectionRefused(context)) {
|
|
93
|
+
output.error('\nPossible causes:');
|
|
94
|
+
if (context.isRemoteTransport) {
|
|
95
|
+
output.error(' - The remote MCP server is not reachable');
|
|
96
|
+
output.error(' - The server URL/port is incorrect');
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
output.error(' - The MCP server is not running');
|
|
100
|
+
output.error(' - The server address/port is incorrect');
|
|
101
|
+
}
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (isRemoteNotFound(context)) {
|
|
105
|
+
output.error('\nPossible causes:');
|
|
106
|
+
output.error(' - The remote MCP URL is incorrect');
|
|
107
|
+
output.error(' - For SSE transport, verify the server exposes /sse');
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (isTimeoutError(context)) {
|
|
111
|
+
output.error('\nPossible causes:');
|
|
112
|
+
output.error(' - The MCP server is taking too long to respond');
|
|
113
|
+
output.error(' - Increase server.timeout in bellwether.yaml');
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (isCommandNotFound(context)) {
|
|
117
|
+
output.error('\nPossible causes:');
|
|
118
|
+
output.error(' - The server command was not found');
|
|
119
|
+
output.error(' - Check that the command is installed and in PATH');
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (isApiKeyError(context)) {
|
|
123
|
+
output.error('\nPossible causes:');
|
|
124
|
+
output.error(' - Missing or invalid API key');
|
|
125
|
+
output.error(' - Run "bellwether auth" to configure API keys');
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=error-hints.js.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Utilities for parsing and merging HTTP headers from CLI/config.
|
|
3
3
|
*/
|
|
4
|
+
import { mergeHeaderMaps, setHeaderCaseInsensitive } from '../../utils/http-headers.js';
|
|
4
5
|
const HEADER_NAME_PATTERN = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
|
|
5
6
|
/**
|
|
6
7
|
* Parse CLI --header values ("Name: value") into a validated header map.
|
|
@@ -34,30 +35,6 @@ export function parseCliHeaders(values) {
|
|
|
34
35
|
* Merge two header maps case-insensitively, with override precedence.
|
|
35
36
|
*/
|
|
36
37
|
export function mergeHeaders(base, override) {
|
|
37
|
-
|
|
38
|
-
return undefined;
|
|
39
|
-
}
|
|
40
|
-
const merged = {};
|
|
41
|
-
if (base) {
|
|
42
|
-
for (const [name, value] of Object.entries(base)) {
|
|
43
|
-
setHeaderCaseInsensitive(merged, name, value);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
if (override) {
|
|
47
|
-
for (const [name, value] of Object.entries(override)) {
|
|
48
|
-
setHeaderCaseInsensitive(merged, name, value);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return Object.keys(merged).length > 0 ? merged : undefined;
|
|
52
|
-
}
|
|
53
|
-
function setHeaderCaseInsensitive(headers, name, value) {
|
|
54
|
-
const normalized = name.toLowerCase();
|
|
55
|
-
for (const existing of Object.keys(headers)) {
|
|
56
|
-
if (existing.toLowerCase() === normalized) {
|
|
57
|
-
delete headers[existing];
|
|
58
|
-
break;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
headers[name] = value;
|
|
38
|
+
return mergeHeaderMaps(base, override);
|
|
62
39
|
}
|
|
63
40
|
//# sourceMappingURL=headers.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve a possibly relative path against the output directory.
|
|
3
|
+
*/
|
|
4
|
+
export declare function resolvePathFromOutputDir(path: string, outputDir: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* Resolve path using output dir first, then cwd fallback.
|
|
7
|
+
* Keeps existing baseline compare behavior for user-provided relative paths.
|
|
8
|
+
*/
|
|
9
|
+
export declare function resolvePathFromOutputDirOrCwd(path: string, outputDir: string): string;
|
|
10
|
+
//# sourceMappingURL=path-resolution.d.ts.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { existsSync } from 'fs';
|
|
2
|
+
import { isAbsolute, join } from 'path';
|
|
3
|
+
/**
|
|
4
|
+
* Resolve a possibly relative path against the output directory.
|
|
5
|
+
*/
|
|
6
|
+
export function resolvePathFromOutputDir(path, outputDir) {
|
|
7
|
+
return isAbsolute(path) ? path : join(outputDir, path);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Resolve path using output dir first, then cwd fallback.
|
|
11
|
+
* Keeps existing baseline compare behavior for user-provided relative paths.
|
|
12
|
+
*/
|
|
13
|
+
export function resolvePathFromOutputDirOrCwd(path, outputDir) {
|
|
14
|
+
if (isAbsolute(path)) {
|
|
15
|
+
return path;
|
|
16
|
+
}
|
|
17
|
+
const outputDirPath = join(outputDir, path);
|
|
18
|
+
if (existsSync(outputDirPath)) {
|
|
19
|
+
return outputDirPath;
|
|
20
|
+
}
|
|
21
|
+
const cwdPath = join(process.cwd(), path);
|
|
22
|
+
if (existsSync(cwdPath)) {
|
|
23
|
+
return cwdPath;
|
|
24
|
+
}
|
|
25
|
+
return outputDirPath;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=path-resolution.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { InterviewResult } from '../../interview/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Load a check-mode interview report from JSON.
|
|
4
|
+
*/
|
|
5
|
+
export declare function loadCheckInterviewResult(reportPath: string, options?: {
|
|
6
|
+
missingReportMessage?: string;
|
|
7
|
+
invalidModeMessage?: (model: string | undefined) => string;
|
|
8
|
+
}): InterviewResult;
|
|
9
|
+
//# sourceMappingURL=report-loader.d.ts.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'fs';
|
|
2
|
+
const DEFAULT_MISSING_REPORT_MESSAGE = 'Run `bellwether check` first with JSON output enabled.\n' +
|
|
3
|
+
'Configure in bellwether.yaml:\n' +
|
|
4
|
+
' output:\n' +
|
|
5
|
+
' format: json # or "both" for JSON + markdown';
|
|
6
|
+
/**
|
|
7
|
+
* Load a check-mode interview report from JSON.
|
|
8
|
+
*/
|
|
9
|
+
export function loadCheckInterviewResult(reportPath, options) {
|
|
10
|
+
if (!existsSync(reportPath)) {
|
|
11
|
+
throw new Error(`Test report not found: ${reportPath}\n\n${options?.missingReportMessage ?? DEFAULT_MISSING_REPORT_MESSAGE}`);
|
|
12
|
+
}
|
|
13
|
+
const content = readFileSync(reportPath, 'utf-8');
|
|
14
|
+
let result;
|
|
15
|
+
try {
|
|
16
|
+
result = JSON.parse(content);
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
throw new Error(`Invalid JSON in report file ${reportPath}: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
20
|
+
}
|
|
21
|
+
if (result.metadata.model && result.metadata.model !== 'check') {
|
|
22
|
+
if (options?.invalidModeMessage) {
|
|
23
|
+
throw new Error(options.invalidModeMessage(result.metadata.model));
|
|
24
|
+
}
|
|
25
|
+
throw new Error(`Baseline operations only work with check mode results.\n\n` +
|
|
26
|
+
`The report at ${reportPath} was created with explore mode (model: ${result.metadata.model}).\n` +
|
|
27
|
+
'Run `bellwether check` to generate a check mode report first.');
|
|
28
|
+
}
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=report-loader.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type BellwetherConfig } from '../../config/loader.js';
|
|
2
|
+
import type { TransportType } from '../../transport/base-transport.js';
|
|
3
|
+
export interface ResolvedServerRuntime {
|
|
4
|
+
serverCommand: string;
|
|
5
|
+
args: string[];
|
|
6
|
+
transport: TransportType;
|
|
7
|
+
remoteUrl?: string;
|
|
8
|
+
remoteSessionId?: string;
|
|
9
|
+
remoteHeaders?: Record<string, string>;
|
|
10
|
+
serverIdentifier: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Resolve server command, args, transport, and headers from config + CLI inputs.
|
|
14
|
+
*/
|
|
15
|
+
export declare function resolveServerRuntime(config: BellwetherConfig, serverCommandArg: string | undefined, serverArgs: string[], headerValues?: string[]): ResolvedServerRuntime;
|
|
16
|
+
//# sourceMappingURL=server-runtime.d.ts.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { parseCommandString, } from '../../config/loader.js';
|
|
2
|
+
import { mergeHeaders, parseCliHeaders } from './headers.js';
|
|
3
|
+
/**
|
|
4
|
+
* Resolve server command, args, transport, and headers from config + CLI inputs.
|
|
5
|
+
*/
|
|
6
|
+
export function resolveServerRuntime(config, serverCommandArg, serverArgs, headerValues) {
|
|
7
|
+
const serverConfig = config.server;
|
|
8
|
+
let serverCommand = serverCommandArg || serverConfig.command;
|
|
9
|
+
let args = serverArgs.length > 0 ? serverArgs : serverConfig.args;
|
|
10
|
+
if (!serverCommandArg && args.length === 0 && serverCommand.includes(' ')) {
|
|
11
|
+
const parsed = parseCommandString(serverCommand);
|
|
12
|
+
serverCommand = parsed.command;
|
|
13
|
+
args = parsed.args;
|
|
14
|
+
}
|
|
15
|
+
const transport = (serverConfig.transport ?? 'stdio');
|
|
16
|
+
const remoteUrl = serverConfig.url?.trim();
|
|
17
|
+
const remoteSessionId = serverConfig.sessionId?.trim();
|
|
18
|
+
const cliHeaders = parseCliHeaders(headerValues);
|
|
19
|
+
const remoteHeaders = mergeHeaders(serverConfig.headers, cliHeaders);
|
|
20
|
+
const serverIdentifier = transport === 'stdio' ? `${serverCommand} ${args.join(' ')}`.trim() : (remoteUrl ?? 'unknown');
|
|
21
|
+
return {
|
|
22
|
+
serverCommand,
|
|
23
|
+
args,
|
|
24
|
+
transport,
|
|
25
|
+
remoteUrl: remoteUrl || undefined,
|
|
26
|
+
remoteSessionId: remoteSessionId || undefined,
|
|
27
|
+
remoteHeaders,
|
|
28
|
+
serverIdentifier,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=server-runtime.js.map
|
|
@@ -101,7 +101,6 @@ export declare const CONFIG_DEFAULTS: {
|
|
|
101
101
|
};
|
|
102
102
|
readonly baseline: {
|
|
103
103
|
readonly path: "bellwether-baseline.json";
|
|
104
|
-
readonly savePath: ".bellwether/bellwether-baseline.json";
|
|
105
104
|
readonly failOnDrift: false;
|
|
106
105
|
readonly outputFormat: "text";
|
|
107
106
|
readonly severity: {
|
package/dist/config/defaults.js
CHANGED