whatbroke-cli 0.2.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.
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.js ADDED
@@ -0,0 +1,186 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ diffTraces,
4
+ diffTracesSampled,
5
+ hasSamples,
6
+ loadTrace,
7
+ renderMarkdown,
8
+ renderTerminal,
9
+ startProxy
10
+ } from "./chunk-PXR57ZGT.js";
11
+
12
+ // src/cli.ts
13
+ import pc from "picocolors";
14
+ var HELP = `whatbroke - diff your AI agent's behavior between two runs
15
+
16
+ usage:
17
+ whatbroke diff <before.jsonl> <after.jsonl> [options]
18
+ whatbroke record --out <trace.jsonl> [options]
19
+
20
+ diff options:
21
+ --json machine-readable output
22
+ --md markdown output (drop it in a PR comment)
23
+ --fail-on <level> exit 1 on: breaking (default), warning, never
24
+ --latency <ratio> flag latency regressions above this ratio (default 1.5)
25
+ --cost <ratio> flag cost increases above this ratio (default 1.25)
26
+ --no-outputs skip comparing final outputs
27
+
28
+ record options:
29
+ --out <file> trace file to write (required)
30
+ --port <n> port to listen on (default 4141)
31
+ --run <name> run id when no x-whatbroke-run header is sent
32
+ --target <url> forward everything to this origin instead
33
+
34
+ record starts a local proxy. point your agent at it and run it unchanged:
35
+ OPENAI_BASE_URL=http://127.0.0.1:4141/v1 (openai sdk)
36
+ ANTHROPIC_BASE_URL=http://127.0.0.1:4141 (anthropic sdk)
37
+ stop it with ctrl-c, then diff the trace against a baseline.
38
+
39
+ nondeterministic agent? record each scenario a few times as name#1, name#2, ...
40
+ and diff will report a flap rate per finding instead of noise.
41
+
42
+ trace format: one JSON event per line
43
+ {"type":"run_start","run":"refund-flow","meta":{"model":"gpt-4o"}}
44
+ {"type":"llm_call","run":"refund-flow","model":"gpt-4o","latency_ms":900,"tokens":{"input":512,"output":128}}
45
+ {"type":"tool_call","run":"refund-flow","name":"lookup_order","args":{"order_id":"A-1042"}}
46
+ {"type":"output","run":"refund-flow","content":"Refund issued."}
47
+ {"type":"run_end","run":"refund-flow","status":"ok"}
48
+
49
+ you can also record with the SDK (import { Recorder } from "whatbroke") or write
50
+ the JSONL yourself from any language. Full docs: https://github.com/arthi-arumugam-git/whatbroke
51
+ `;
52
+ function fail(message) {
53
+ console.error(pc.red(`whatbroke: ${message}`));
54
+ console.error(pc.dim(`try: whatbroke --help`));
55
+ process.exit(2);
56
+ }
57
+ function main() {
58
+ const argv = process.argv.slice(2);
59
+ if (!argv.length || argv.includes("-h") || argv.includes("--help")) {
60
+ console.log(HELP);
61
+ process.exit(0);
62
+ }
63
+ const command = argv[0];
64
+ if (command === "record") {
65
+ runRecord(argv.slice(1));
66
+ return;
67
+ }
68
+ if (command !== "diff") fail(`unknown command: ${command}`);
69
+ const positional = [];
70
+ let format = "terminal";
71
+ let failOn = "breaking";
72
+ let latencyThreshold = 1.5;
73
+ let costThreshold = 1.25;
74
+ let compareOutputs = true;
75
+ for (let i = 1; i < argv.length; i++) {
76
+ const arg = argv[i];
77
+ switch (arg) {
78
+ case "--json":
79
+ format = "json";
80
+ break;
81
+ case "--md":
82
+ format = "md";
83
+ break;
84
+ case "--no-outputs":
85
+ compareOutputs = false;
86
+ break;
87
+ case "--fail-on": {
88
+ const value = argv[++i];
89
+ if (value !== "breaking" && value !== "warning" && value !== "never") {
90
+ fail(`--fail-on must be breaking, warning, or never`);
91
+ }
92
+ failOn = value;
93
+ break;
94
+ }
95
+ case "--latency": {
96
+ const value = Number(argv[++i]);
97
+ if (!Number.isFinite(value) || value <= 0) fail(`--latency needs a positive number`);
98
+ latencyThreshold = value;
99
+ break;
100
+ }
101
+ case "--cost": {
102
+ const value = Number(argv[++i]);
103
+ if (!Number.isFinite(value) || value <= 0) fail(`--cost needs a positive number`);
104
+ costThreshold = value;
105
+ break;
106
+ }
107
+ default:
108
+ if (arg.startsWith("-")) fail(`unknown option: ${arg}`);
109
+ positional.push(arg);
110
+ }
111
+ }
112
+ if (positional.length !== 2) {
113
+ fail("diff needs exactly two trace files: whatbroke diff before.jsonl after.jsonl");
114
+ }
115
+ let result;
116
+ try {
117
+ const before = loadTrace(positional[0]);
118
+ const after = loadTrace(positional[1]);
119
+ const options = { latencyThreshold, costThreshold, compareOutputs };
120
+ result = hasSamples(before) || hasSamples(after) ? diffTracesSampled(before, after, options) : diffTraces(before, after, options);
121
+ } catch (err) {
122
+ fail(err instanceof Error ? err.message : String(err));
123
+ }
124
+ if (format === "json") {
125
+ console.log(JSON.stringify(result, null, 2));
126
+ } else if (format === "md") {
127
+ console.log(renderMarkdown(result));
128
+ } else {
129
+ console.log(renderTerminal(result));
130
+ }
131
+ const shouldFail = failOn === "breaking" && result.breaking > 0 || failOn === "warning" && (result.breaking > 0 || result.warnings > 0);
132
+ process.exit(shouldFail ? 1 : 0);
133
+ }
134
+ function runRecord(argv) {
135
+ let out = "";
136
+ let port = 4141;
137
+ let run;
138
+ let target;
139
+ for (let i = 0; i < argv.length; i++) {
140
+ const arg = argv[i];
141
+ switch (arg) {
142
+ case "--out":
143
+ out = argv[++i] ?? "";
144
+ break;
145
+ case "--port": {
146
+ const value = Number(argv[++i]);
147
+ if (!Number.isInteger(value) || value < 0 || value > 65535) {
148
+ fail(`--port needs a number between 0 and 65535`);
149
+ }
150
+ port = value;
151
+ break;
152
+ }
153
+ case "--run":
154
+ run = argv[++i];
155
+ break;
156
+ case "--target":
157
+ target = argv[++i];
158
+ break;
159
+ default:
160
+ fail(`unknown option: ${arg}`);
161
+ }
162
+ }
163
+ if (!out) fail("record needs --out <trace.jsonl>");
164
+ startProxy({
165
+ file: out,
166
+ port,
167
+ run,
168
+ target,
169
+ onRecord: (runId, model, toolNames) => {
170
+ const tools = toolNames.length ? ` -> ${toolNames.join(", ")}` : "";
171
+ console.log(pc.dim(` [${runId}] ${model}${tools}`));
172
+ }
173
+ }).then((proxy) => {
174
+ console.log(`recording to ${out}`);
175
+ console.log(pc.dim(` OPENAI_BASE_URL=${proxy.url}/v1`));
176
+ console.log(pc.dim(` ANTHROPIC_BASE_URL=${proxy.url}`));
177
+ console.log(pc.dim(" ctrl-c to stop"));
178
+ console.log("");
179
+ process.on("SIGINT", () => {
180
+ proxy.close().finally(() => process.exit(0));
181
+ });
182
+ }).catch((err) => {
183
+ fail(err instanceof Error ? err.message : String(err));
184
+ });
185
+ }
186
+ main();
@@ -0,0 +1,187 @@
1
+ interface RunStartEvent {
2
+ type: "run_start";
3
+ run: string;
4
+ ts?: number;
5
+ meta?: Record<string, unknown>;
6
+ }
7
+ interface LlmCallEvent {
8
+ type: "llm_call";
9
+ run: string;
10
+ ts?: number;
11
+ model: string;
12
+ latency_ms?: number;
13
+ tokens?: {
14
+ input?: number;
15
+ output?: number;
16
+ };
17
+ cost_usd?: number;
18
+ stop_reason?: string;
19
+ error?: string;
20
+ }
21
+ interface ToolCallEvent {
22
+ type: "tool_call";
23
+ run: string;
24
+ ts?: number;
25
+ name: string;
26
+ args?: Record<string, unknown>;
27
+ latency_ms?: number;
28
+ error?: string;
29
+ }
30
+ interface OutputEvent {
31
+ type: "output";
32
+ run: string;
33
+ ts?: number;
34
+ content: string;
35
+ }
36
+ interface RunEndEvent {
37
+ type: "run_end";
38
+ run: string;
39
+ ts?: number;
40
+ status: "ok" | "error";
41
+ error?: string;
42
+ }
43
+ type TraceEvent = RunStartEvent | LlmCallEvent | ToolCallEvent | OutputEvent | RunEndEvent;
44
+ interface Run {
45
+ id: string;
46
+ meta: Record<string, unknown>;
47
+ llmCalls: LlmCallEvent[];
48
+ toolCalls: ToolCallEvent[];
49
+ outputs: OutputEvent[];
50
+ status: "ok" | "error" | "unknown";
51
+ error?: string;
52
+ }
53
+ type Severity = "breaking" | "warning" | "info";
54
+ interface Finding {
55
+ severity: Severity;
56
+ /** stable identity for the thing that changed (tool name, "output", ...), used to correlate findings across sampled runs */
57
+ subject?: string;
58
+ /** how often this finding appeared across sampled run pairs, e.g. "4/9" */
59
+ rate?: string;
60
+ /** true when the same finding also shows up between baseline samples, i.e. the agent was already flaky here */
61
+ flaky?: boolean;
62
+ kind: "run_missing" | "run_added" | "status_changed" | "tool_removed" | "tool_added" | "tool_args_changed" | "tool_reordered" | "tool_error" | "output_changed" | "output_missing" | "latency_regression" | "cost_increase" | "token_change" | "model_changed";
63
+ run: string;
64
+ message: string;
65
+ detail?: Record<string, unknown>;
66
+ }
67
+ interface RunStats {
68
+ llmCalls: number;
69
+ toolCalls: number;
70
+ inputTokens: number;
71
+ outputTokens: number;
72
+ costUsd: number;
73
+ latencyMs: number;
74
+ models: string[];
75
+ }
76
+ interface RunDiff {
77
+ run: string;
78
+ findings: Finding[];
79
+ before?: RunStats;
80
+ after?: RunStats;
81
+ }
82
+ interface DiffResult {
83
+ runs: RunDiff[];
84
+ findings: Finding[];
85
+ breaking: number;
86
+ warnings: number;
87
+ info: number;
88
+ }
89
+ interface DiffOptions {
90
+ /** flag latency regressions above this ratio, e.g. 1.5 = 50% slower (default 1.5) */
91
+ latencyThreshold?: number;
92
+ /** flag cost increases above this ratio (default 1.25) */
93
+ costThreshold?: number;
94
+ /** compare final outputs (default true) */
95
+ compareOutputs?: boolean;
96
+ }
97
+
98
+ declare function diffTraces(before: Map<string, Run>, after: Map<string, Run>, options?: DiffOptions): DiffResult;
99
+
100
+ declare function hasSamples(runs: Map<string, Run>): boolean;
101
+ /**
102
+ * Like diffTraces, but runs named `name#1`, `name#2`, ... are treated as
103
+ * repeated samples of the same scenario. Every before sample is compared to
104
+ * every after sample, findings carry an occurrence rate, and findings that
105
+ * also appear between baseline samples are demoted to flaky info, because the
106
+ * agent already behaved that way before the change.
107
+ */
108
+ declare function diffTracesSampled(before: Map<string, Run>, after: Map<string, Run>, options?: DiffOptions): DiffResult;
109
+
110
+ declare function parseTrace(text: string): Map<string, Run>;
111
+ declare function loadTrace(path: string): Map<string, Run>;
112
+
113
+ interface ProxyOptions {
114
+ /** trace file path, e.g. traces/current.jsonl */
115
+ file: string;
116
+ /** port to listen on, 0 picks a free one (default 4141) */
117
+ port?: number;
118
+ /** upstream origin override, e.g. http://localhost:8080 for a mock */
119
+ target?: string;
120
+ /** run id when the client sends no x-whatbroke-run header */
121
+ run?: string;
122
+ /** called once per recorded llm call, used by the CLI for progress output */
123
+ onRecord?: (run: string, model: string, toolNames: string[]) => void;
124
+ }
125
+ interface ProxyHandle {
126
+ port: number;
127
+ url: string;
128
+ close: () => Promise<void>;
129
+ }
130
+ /**
131
+ * Starts a local HTTP proxy that forwards requests to the OpenAI or
132
+ * Anthropic API and records every LLM call, tool call, and final text
133
+ * output to a JSONL trace. Point OPENAI_BASE_URL or ANTHROPIC_BASE_URL at
134
+ * it and run your agent unchanged. Requests to /v1/messages go to
135
+ * api.anthropic.com, everything else to api.openai.com, unless `target`
136
+ * says otherwise. Group runs with an x-whatbroke-run request header.
137
+ */
138
+ declare function startProxy(options: ProxyOptions): Promise<ProxyHandle>;
139
+
140
+ interface RecorderOptions {
141
+ /** trace file path, e.g. traces/before.jsonl */
142
+ file: string;
143
+ /** run id for this scenario, defaults to "default" */
144
+ run?: string;
145
+ meta?: Record<string, unknown>;
146
+ }
147
+ /**
148
+ * Writes trace events to a JSONL file. Use one recorder per scenario run,
149
+ * or pass a run id per call if you drive many scenarios from one place.
150
+ */
151
+ declare class Recorder {
152
+ private file;
153
+ private run;
154
+ constructor(options: RecorderOptions);
155
+ private write;
156
+ llmCall(data: {
157
+ model: string;
158
+ latencyMs?: number;
159
+ inputTokens?: number;
160
+ outputTokens?: number;
161
+ costUsd?: number;
162
+ stopReason?: string;
163
+ error?: string;
164
+ }): void;
165
+ toolCall(name: string, args?: Record<string, unknown>, data?: {
166
+ latencyMs?: number;
167
+ error?: string;
168
+ }): void;
169
+ output(content: string): void;
170
+ end(status?: "ok" | "error", error?: string): void;
171
+ /**
172
+ * Wraps an OpenAI client (openai npm package) so every
173
+ * chat.completions.create call is recorded, including tool calls the
174
+ * model requested. Returns the same client.
175
+ */
176
+ wrapOpenAI<T extends object>(client: T): T;
177
+ /**
178
+ * Wraps an Anthropic client (@anthropic-ai/sdk) so every messages.create
179
+ * call is recorded, including tool_use blocks. Returns the same client.
180
+ */
181
+ wrapAnthropic<T extends object>(client: T): T;
182
+ }
183
+
184
+ declare function renderTerminal(result: DiffResult): string;
185
+ declare function renderMarkdown(result: DiffResult): string;
186
+
187
+ export { type DiffOptions, type DiffResult, type Finding, Recorder, type Run, type RunDiff, type RunStats, type Severity, type TraceEvent, diffTraces, diffTracesSampled, hasSamples, loadTrace, parseTrace, renderMarkdown, renderTerminal, startProxy };
package/dist/index.js ADDED
@@ -0,0 +1,22 @@
1
+ import {
2
+ Recorder,
3
+ diffTraces,
4
+ diffTracesSampled,
5
+ hasSamples,
6
+ loadTrace,
7
+ parseTrace,
8
+ renderMarkdown,
9
+ renderTerminal,
10
+ startProxy
11
+ } from "./chunk-PXR57ZGT.js";
12
+ export {
13
+ Recorder,
14
+ diffTraces,
15
+ diffTracesSampled,
16
+ hasSamples,
17
+ loadTrace,
18
+ parseTrace,
19
+ renderMarkdown,
20
+ renderTerminal,
21
+ startProxy
22
+ };
@@ -0,0 +1,28 @@
1
+ {"type":"run_start","run":"refund-flow","meta":{"model":"gpt-4o","scenario":"customer asks for a refund on order A-1042"}}
2
+ {"type":"llm_call","run":"refund-flow","model":"gpt-4o","latency_ms":840,"tokens":{"input":1240,"output":86},"cost_usd":0.0044}
3
+ {"type":"tool_call","run":"refund-flow","name":"lookup_order","args":{"order_id":"A-1042"},"latency_ms":120}
4
+ {"type":"llm_call","run":"refund-flow","model":"gpt-4o","latency_ms":920,"tokens":{"input":1410,"output":64},"cost_usd":0.0048}
5
+ {"type":"tool_call","run":"refund-flow","name":"issue_refund","args":{"order_id":"A-1042","amount":42.5,"reason":"damaged item"},"latency_ms":340}
6
+ {"type":"llm_call","run":"refund-flow","model":"gpt-4o","latency_ms":610,"tokens":{"input":1520,"output":58},"cost_usd":0.0049}
7
+ {"type":"output","run":"refund-flow","content":"Your refund of $42.50 for order A-1042 has been issued. It should appear in 3-5 business days."}
8
+ {"type":"run_end","run":"refund-flow","status":"ok"}
9
+ {"type":"run_start","run":"cancel-subscription","meta":{"model":"gpt-4o","scenario":"customer wants to cancel their subscription"}}
10
+ {"type":"llm_call","run":"cancel-subscription","model":"gpt-4o","latency_ms":780,"tokens":{"input":1180,"output":92},"cost_usd":0.0042}
11
+ {"type":"tool_call","run":"cancel-subscription","name":"get_subscription","args":{"customer_id":"C-889"},"latency_ms":95}
12
+ {"type":"llm_call","run":"cancel-subscription","model":"gpt-4o","latency_ms":850,"tokens":{"input":1350,"output":71},"cost_usd":0.0046}
13
+ {"type":"tool_call","run":"cancel-subscription","name":"cancel_subscription","args":{"customer_id":"C-889","effective":"end_of_period"},"latency_ms":210}
14
+ {"type":"llm_call","run":"cancel-subscription","model":"gpt-4o","latency_ms":590,"tokens":{"input":1460,"output":55},"cost_usd":0.0047}
15
+ {"type":"output","run":"cancel-subscription","content":"Your subscription is cancelled effective at the end of the current billing period. You keep access until then."}
16
+ {"type":"run_end","run":"cancel-subscription","status":"ok"}
17
+ {"type":"run_start","run":"order-status","meta":{"model":"gpt-4o","scenario":"customer asks where their order is"}}
18
+ {"type":"llm_call","run":"order-status","model":"gpt-4o","latency_ms":720,"tokens":{"input":1100,"output":78},"cost_usd":0.0039}
19
+ {"type":"tool_call","run":"order-status","name":"lookup_order","args":{"order_id":"B-2210"},"latency_ms":110}
20
+ {"type":"llm_call","run":"order-status","model":"gpt-4o","latency_ms":640,"tokens":{"input":1290,"output":66},"cost_usd":0.0043}
21
+ {"type":"output","run":"order-status","content":"Order B-2210 shipped yesterday and is expected to arrive Friday."}
22
+ {"type":"run_end","run":"order-status","status":"ok"}
23
+ {"type":"run_start","run":"angry-escalation","meta":{"model":"gpt-4o","scenario":"furious customer demands a manager"}}
24
+ {"type":"llm_call","run":"angry-escalation","model":"gpt-4o","latency_ms":880,"tokens":{"input":1330,"output":104},"cost_usd":0.0047}
25
+ {"type":"tool_call","run":"angry-escalation","name":"create_ticket","args":{"priority":"high","team":"support-escalations"},"latency_ms":150}
26
+ {"type":"llm_call","run":"angry-escalation","model":"gpt-4o","latency_ms":700,"tokens":{"input":1480,"output":89},"cost_usd":0.0049}
27
+ {"type":"output","run":"angry-escalation","content":"I understand this has been frustrating. I've escalated your case to our support team with high priority. Ticket #4471, someone will reach out within 2 hours."}
28
+ {"type":"run_end","run":"angry-escalation","status":"ok"}
@@ -0,0 +1,26 @@
1
+ {"type":"run_start","run":"refund-flow","meta":{"model":"gpt-5-mini","scenario":"customer asks for a refund on order A-1042"}}
2
+ {"type":"llm_call","run":"refund-flow","model":"gpt-5-mini","latency_ms":610,"tokens":{"input":1240,"output":71},"cost_usd":0.0011}
3
+ {"type":"tool_call","run":"refund-flow","name":"lookup_order","args":{"order_id":"A-1042"},"latency_ms":120}
4
+ {"type":"llm_call","run":"refund-flow","model":"gpt-5-mini","latency_ms":680,"tokens":{"input":1410,"output":52},"cost_usd":0.0012}
5
+ {"type":"tool_call","run":"refund-flow","name":"issue_refund","args":{"order_id":"A-1042","amount":425,"reason":"damaged item"},"latency_ms":340}
6
+ {"type":"llm_call","run":"refund-flow","model":"gpt-5-mini","latency_ms":450,"tokens":{"input":1520,"output":49},"cost_usd":0.0012}
7
+ {"type":"output","run":"refund-flow","content":"Your refund of $425.00 for order A-1042 has been issued. It should appear in 3-5 business days."}
8
+ {"type":"run_end","run":"refund-flow","status":"ok"}
9
+ {"type":"run_start","run":"cancel-subscription","meta":{"model":"gpt-5-mini","scenario":"customer wants to cancel their subscription"}}
10
+ {"type":"llm_call","run":"cancel-subscription","model":"gpt-5-mini","latency_ms":590,"tokens":{"input":1180,"output":88},"cost_usd":0.001}
11
+ {"type":"tool_call","run":"cancel-subscription","name":"get_subscription","args":{"customer_id":"C-889"},"latency_ms":95}
12
+ {"type":"llm_call","run":"cancel-subscription","model":"gpt-5-mini","latency_ms":620,"tokens":{"input":1350,"output":112},"cost_usd":0.0011}
13
+ {"type":"output","run":"cancel-subscription","content":"I can help with that. To confirm the cancellation, please reply CONFIRM and I will process it right away."}
14
+ {"type":"run_end","run":"cancel-subscription","status":"ok"}
15
+ {"type":"run_start","run":"order-status","meta":{"model":"gpt-5-mini","scenario":"customer asks where their order is"}}
16
+ {"type":"llm_call","run":"order-status","model":"gpt-5-mini","latency_ms":540,"tokens":{"input":1100,"output":74},"cost_usd":0.001}
17
+ {"type":"tool_call","run":"order-status","name":"lookup_order","args":{"order_id":"B-2210"},"latency_ms":110}
18
+ {"type":"llm_call","run":"order-status","model":"gpt-5-mini","latency_ms":480,"tokens":{"input":1290,"output":66},"cost_usd":0.001}
19
+ {"type":"output","run":"order-status","content":"Order B-2210 shipped yesterday and is expected to arrive Friday."}
20
+ {"type":"run_end","run":"order-status","status":"ok"}
21
+ {"type":"run_start","run":"angry-escalation","meta":{"model":"gpt-5-mini","scenario":"furious customer demands a manager"}}
22
+ {"type":"llm_call","run":"angry-escalation","model":"gpt-5-mini","latency_ms":650,"tokens":{"input":1330,"output":96},"cost_usd":0.001}
23
+ {"type":"tool_call","run":"angry-escalation","name":"create_ticket","args":{"priority":"normal","team":"support-general"},"latency_ms":150}
24
+ {"type":"llm_call","run":"angry-escalation","model":"gpt-5-mini","latency_ms":510,"tokens":{"input":1480,"output":81},"cost_usd":0.001}
25
+ {"type":"output","run":"angry-escalation","content":"I'm sorry to hear that. I've created a ticket for your issue and our team will get back to you soon."}
26
+ {"type":"run_end","run":"angry-escalation","status":"ok"}
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "whatbroke-cli",
3
+ "version": "0.2.0",
4
+ "description": "Diff your AI agent's behavior between two runs. Swap a model, change a prompt, run whatbroke, see exactly what changed.",
5
+ "keywords": [
6
+ "ai-agents",
7
+ "llm",
8
+ "evals",
9
+ "regression-testing",
10
+ "diff",
11
+ "tracing",
12
+ "openai",
13
+ "anthropic"
14
+ ],
15
+ "license": "MIT",
16
+ "author": "Arthi Arumugam",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/arthi-arumugam-git/whatbroke.git"
20
+ },
21
+ "homepage": "https://github.com/arthi-arumugam-git/whatbroke#readme",
22
+ "bugs": {
23
+ "url": "https://github.com/arthi-arumugam-git/whatbroke/issues"
24
+ },
25
+ "sideEffects": false,
26
+ "type": "module",
27
+ "bin": {
28
+ "whatbroke": "dist/cli.js"
29
+ },
30
+ "main": "./dist/index.js",
31
+ "types": "./dist/index.d.ts",
32
+ "exports": {
33
+ ".": {
34
+ "types": "./dist/index.d.ts",
35
+ "import": "./dist/index.js"
36
+ }
37
+ },
38
+ "files": [
39
+ "dist",
40
+ "examples"
41
+ ],
42
+ "scripts": {
43
+ "build": "tsup src/index.ts src/cli.ts --format esm --dts --clean",
44
+ "test": "vitest run",
45
+ "dev": "tsx src/cli.ts",
46
+ "prepublishOnly": "npm run build && npm test"
47
+ },
48
+ "devDependencies": {
49
+ "@types/node": "^22.10.0",
50
+ "tsup": "^8.3.0",
51
+ "tsx": "^4.19.0",
52
+ "typescript": "^5.7.0",
53
+ "vitest": "^2.1.0"
54
+ },
55
+ "dependencies": {
56
+ "picocolors": "^1.1.0"
57
+ },
58
+ "engines": {
59
+ "node": ">=18"
60
+ }
61
+ }