Skip to content

Commit f1c1423

Browse files
that-github-userunknownclaude
authored
Add --no-color flag and --output-format json for non-TTY output (#114)
--no-color sets NO_COLOR env so picocolors disables itself. --output-format json outputs raw EnsembleResult JSON to stdout instead of formatted display, useful for piping to other tools. Generated by thinktank Opus (5 agents, 3 pass, Copeland: #2 at +2). Closes #61 Co-authored-by: unknown <that-github-user@github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 3e75163 commit f1c1423

4 files changed

Lines changed: 61 additions & 3 deletions

File tree

src/cli.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ program
4545
String(cfg.threshold),
4646
)
4747
.option("--scoring <method>", "Scoring method: copeland (default) or weighted", "copeland")
48+
.option("--no-color", "Disable colored output")
49+
.option("--output-format <format>", "Output format: text (default) or json", "text")
4850
.option("--verbose", "Show detailed output from each agent")
4951
.action(async (promptArg: string | undefined, opts) => {
5052
const prompt = resolvePrompt(promptArg, opts.file);
@@ -79,6 +81,17 @@ program
7981
process.exit(1);
8082
}
8183

84+
// --no-color: commander parses --no-color as opts.color === false
85+
if (opts.color === false) {
86+
process.env.NO_COLOR = "1";
87+
}
88+
89+
const validFormats = ["text", "json"];
90+
if (!validFormats.includes(opts.outputFormat)) {
91+
console.error(`Error: --output-format must be one of: ${validFormats.join(", ")}`);
92+
process.exit(1);
93+
}
94+
8295
const knownModels = ["sonnet", "opus", "haiku"];
8396
if (!knownModels.includes(opts.model) && !opts.model.startsWith("claude-")) {
8497
console.warn(
@@ -97,6 +110,7 @@ program
97110
runner: opts.runner,
98111
scoring: opts.scoring,
99112
verbose: opts.verbose ?? false,
113+
outputFormat: opts.outputFormat,
100114
});
101115
});
102116

src/commands/run.test.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import assert from "node:assert/strict";
2-
import { describe, it } from "node:test";
2+
import { afterEach, describe, it } from "node:test";
33
import type { RunOptions } from "../types.js";
44
import { makeResultFilename, preflightValidation } from "./run.js";
55

@@ -13,6 +13,7 @@ function makeOpts(overrides: Partial<RunOptions> = {}): RunOptions {
1313
threshold: 0.3,
1414
verbose: false,
1515
scoring: "weighted",
16+
outputFormat: "text",
1617
...overrides,
1718
};
1819
}
@@ -78,3 +79,41 @@ describe("makeResultFilename", () => {
7879
assert.equal(filename, "run-2026-03-28T18-09-50-100Z.json");
7980
});
8081
});
82+
83+
describe("outputFormat option", () => {
84+
it("accepts text as default output format", () => {
85+
const opts = makeOpts({ outputFormat: "text" });
86+
assert.equal(opts.outputFormat, "text");
87+
});
88+
89+
it("accepts json output format", () => {
90+
const opts = makeOpts({ outputFormat: "json" });
91+
assert.equal(opts.outputFormat, "json");
92+
});
93+
});
94+
95+
describe("NO_COLOR environment variable", () => {
96+
const originalNoColor = process.env.NO_COLOR;
97+
98+
afterEach(() => {
99+
if (originalNoColor === undefined) {
100+
delete process.env.NO_COLOR;
101+
} else {
102+
process.env.NO_COLOR = originalNoColor;
103+
}
104+
});
105+
106+
it("can be set to disable colors", () => {
107+
process.env.NO_COLOR = "1";
108+
assert.equal(process.env.NO_COLOR, "1");
109+
});
110+
111+
it("picocolors respects NO_COLOR", async () => {
112+
process.env.NO_COLOR = "1";
113+
// picocolors checks NO_COLOR at import time, but its createColors
114+
// function can be used to verify the behavior
115+
const pc = await import("picocolors");
116+
const colors = pc.createColors(false);
117+
assert.equal(colors.bold("test"), "test");
118+
});
119+
});

src/commands/run.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,12 @@ export async function run(opts: RunOptions): Promise<void> {
153153
};
154154

155155
// Display results
156-
displayResults(result);
157-
displayApplyInstructions(result);
156+
if (opts.outputFormat === "json") {
157+
console.log(JSON.stringify(result));
158+
} else {
159+
displayResults(result);
160+
displayApplyInstructions(result);
161+
}
158162

159163
// Save result to .thinktank/
160164
await saveResult(result);

src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export interface RunOptions {
99
verbose: boolean;
1010
runner?: string;
1111
scoring: "weighted" | "copeland";
12+
outputFormat: "text" | "json";
1213
}
1314

1415
export interface AgentResult {

0 commit comments

Comments
 (0)