@trieungoctam/speckit 0.3.5 → 0.3.6
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/README.md +2 -0
- package/dist/cli.js +3 -1
- package/dist/commands/doctor.js +12 -9
- package/dist/commands/graph.js +4 -2
- package/dist/commands/init.js +4 -2
- package/dist/commands/ready.js +6 -4
- package/dist/commands/setup.js +24 -21
- package/dist/commands/sync.js +4 -2
- package/dist/commands/validate.js +4 -2
- package/dist/core/colors.d.ts +11 -0
- package/dist/core/colors.js +40 -0
- package/docs/project-changelog.md +12 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -30,6 +30,8 @@ npx @trieungoctam/speckit@latest session checkpoint --note "red complete"
|
|
|
30
30
|
npx @trieungoctam/speckit@latest review
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
+
Interactive CLI output is colorized when the terminal supports ANSI colors. Use `NO_COLOR=1` to disable colors, or `SPECKIT_COLOR=1` to force colors for demos and snapshots.
|
|
34
|
+
|
|
33
35
|
Or install globally:
|
|
34
36
|
|
|
35
37
|
```bash
|
package/dist/cli.js
CHANGED
|
@@ -19,6 +19,7 @@ import { runCommand } from "./commands/run.js";
|
|
|
19
19
|
import { readyCommand } from "./commands/ready.js";
|
|
20
20
|
import { reviewCommand } from "./commands/review.js";
|
|
21
21
|
import { closeCommand } from "./commands/close.js";
|
|
22
|
+
import { createColors } from "./core/colors.js";
|
|
22
23
|
export async function main(argv = process.argv.slice(2), root = process.cwd()) {
|
|
23
24
|
const parsed = parseArgs(argv);
|
|
24
25
|
try {
|
|
@@ -145,7 +146,8 @@ function isHelp(parsed) {
|
|
|
145
146
|
return parsed.command === "help" || parsed.command === "--help" || parsed.command === "-h" || has(parsed, "help");
|
|
146
147
|
}
|
|
147
148
|
function printHelp() {
|
|
148
|
-
|
|
149
|
+
const colors = createColors();
|
|
150
|
+
console.log(`${colors.bold(colors.cyan("Speckit"))} - Agile + TDD workflow compiler for agentic IDEs
|
|
149
151
|
|
|
150
152
|
Usage:
|
|
151
153
|
speckit setup
|
package/dist/commands/doctor.js
CHANGED
|
@@ -6,8 +6,10 @@ import { detectTestCommands } from "../core/test-detection.js";
|
|
|
6
6
|
import { coreFiles, enterpriseFiles } from "../core/scaffold.js";
|
|
7
7
|
import { agentFiles } from "../core/agent-scaffold.js";
|
|
8
8
|
import { validateWorkflowContract } from "../core/workflow-validator.js";
|
|
9
|
+
import { createColors } from "../core/colors.js";
|
|
9
10
|
export async function doctorCommand(options) {
|
|
10
11
|
const stdout = options.stdout ?? console;
|
|
12
|
+
const colors = createColors();
|
|
11
13
|
const tools = checkTools();
|
|
12
14
|
const tests = await detectTestCommands(options.root);
|
|
13
15
|
const deepChecks = options.deep ? await runDeepChecks(options.root) : [];
|
|
@@ -29,24 +31,25 @@ export async function doctorCommand(options) {
|
|
|
29
31
|
stdout.log(JSON.stringify(report, null, 2));
|
|
30
32
|
}
|
|
31
33
|
else {
|
|
32
|
-
stdout.log(
|
|
33
|
-
stdout.log(
|
|
34
|
-
stdout.log(`Tools: ${tools.map((tool) => `${tool.name}=${tool.available
|
|
34
|
+
stdout.log(`${colors.bold("Speckit doctor")}: ${colors.status(report.status === "ok", "ok", report.status)}`);
|
|
35
|
+
stdout.log(`${colors.bold("Node")}: ${report.node}`);
|
|
36
|
+
stdout.log(`Tools: ${tools.map((tool) => `${tool.name}=${colors.status(tool.available)}`).join(", ")}`);
|
|
35
37
|
for (const tool of tools.filter((tool) => !tool.available)) {
|
|
36
|
-
stdout.log(
|
|
38
|
+
stdout.log(`${colors.yellow("Tool hint")} ${tool.name}: ${tool.hint}`);
|
|
37
39
|
if (tool.install?.length) {
|
|
38
|
-
stdout.log(`Install ${tool.name}: ${tool.install.join(" OR ")}`);
|
|
40
|
+
stdout.log(`${colors.cyan(`Install ${tool.name}`)}: ${tool.install.join(" OR ")}`);
|
|
39
41
|
}
|
|
40
42
|
}
|
|
41
|
-
stdout.log(
|
|
43
|
+
stdout.log(`${colors.bold("Tests")}: ${tests[0]?.command ?? colors.yellow("not detected")}`);
|
|
42
44
|
for (const check of deepChecks) {
|
|
43
|
-
stdout.log(`Deep ${check.name}: ${check.ok
|
|
45
|
+
stdout.log(`Deep ${check.name}: ${colors.status(check.ok)}`);
|
|
44
46
|
}
|
|
45
47
|
for (const check of contractChecks) {
|
|
46
|
-
stdout.log(`Contract ${check.name}: ${check.ok ? "ok" : check.detail}`);
|
|
48
|
+
stdout.log(`Contract ${check.name}: ${check.ok ? colors.green("ok") : colors.red(check.detail)}`);
|
|
47
49
|
}
|
|
48
50
|
for (const adapter of adapters) {
|
|
49
|
-
|
|
51
|
+
const ok = adapter.present === adapter.total;
|
|
52
|
+
stdout.log(`Adapter ${adapter.name}: ${colors.status(ok, `${adapter.present}/${adapter.total}`, `${adapter.present}/${adapter.total}`)} files present`);
|
|
50
53
|
}
|
|
51
54
|
}
|
|
52
55
|
return report.status === "ok" ? 0 : 1;
|
package/dist/commands/graph.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
2
|
import { beadsViewerInstallGuide, checkTools, commandExists } from "../adapters/tool-checks.js";
|
|
3
3
|
import { prepareBeadsMirror } from "../core/beads-mirror.js";
|
|
4
|
+
import { createColors } from "../core/colors.js";
|
|
4
5
|
import { readSyncedStories, selectableStories } from "../core/synced-stories.js";
|
|
5
6
|
const robotActions = new Map([
|
|
6
7
|
["triage", "--robot-triage"],
|
|
@@ -37,6 +38,7 @@ export async function graphCommand(options) {
|
|
|
37
38
|
}
|
|
38
39
|
async function graphSetup(options) {
|
|
39
40
|
const stdout = options.stdout ?? console;
|
|
41
|
+
const colors = createColors();
|
|
40
42
|
const tools = checkTools().filter((tool) => ["br", "bd", "bv"].includes(tool.name));
|
|
41
43
|
const mirrorPath = await prepareBeadsMirror(options.root);
|
|
42
44
|
const report = {
|
|
@@ -51,9 +53,9 @@ async function graphSetup(options) {
|
|
|
51
53
|
else {
|
|
52
54
|
stdout.log(beadsViewerInstallGuide());
|
|
53
55
|
stdout.log("");
|
|
54
|
-
stdout.log(
|
|
56
|
+
stdout.log(`${colors.bold("Mirror")}: ${colors.cyan(mirrorPath)}`);
|
|
55
57
|
for (const tool of tools) {
|
|
56
|
-
stdout.log(`${tool.name}: ${tool.available
|
|
58
|
+
stdout.log(`${colors.bold(tool.name)}: ${colors.status(tool.available)} - ${colors.dim(tool.hint)}`);
|
|
57
59
|
}
|
|
58
60
|
}
|
|
59
61
|
return 0;
|
package/dist/commands/init.js
CHANGED
|
@@ -2,8 +2,10 @@ import { coreFiles, enterpriseFiles } from "../core/scaffold.js";
|
|
|
2
2
|
import { agentFiles } from "../core/agent-scaffold.js";
|
|
3
3
|
import { writeManagedFiles } from "../core/managed-files.js";
|
|
4
4
|
import { getAdapters } from "../config/adapter-registry.js";
|
|
5
|
+
import { createColors } from "../core/colors.js";
|
|
5
6
|
export async function initCommand(options) {
|
|
6
7
|
const stdout = options.stdout ?? console;
|
|
8
|
+
const colors = createColors();
|
|
7
9
|
const selectedIde = options.ide ?? "all";
|
|
8
10
|
const sharedFiles = [...coreFiles(), ...agentFiles()];
|
|
9
11
|
const baseFiles = options.enterprise ? [...sharedFiles, ...enterpriseFiles()] : sharedFiles;
|
|
@@ -13,9 +15,9 @@ export async function initCommand(options) {
|
|
|
13
15
|
const updated = results.filter((result) => result.status === "updated").length;
|
|
14
16
|
const skipped = results.filter((result) => result.status === "skipped");
|
|
15
17
|
const mode = options.enterprise ? "enterprise" : "standard";
|
|
16
|
-
stdout.log(
|
|
18
|
+
stdout.log(`${colors.bold("Speckit initialized")} (${colors.cyan(mode)}): ${colors.green(String(created))} created, ${colors.yellow(String(updated))} updated, ${colors.status(skipped.length === 0, String(skipped.length), String(skipped.length))} skipped.`);
|
|
17
19
|
for (const result of skipped) {
|
|
18
|
-
stdout.log(
|
|
20
|
+
stdout.log(`${colors.yellow("skipped")} ${result.path}: ${result.reason}`);
|
|
19
21
|
}
|
|
20
22
|
return skipped.length > 0 ? 2 : 0;
|
|
21
23
|
}
|
package/dist/commands/ready.js
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
import { evaluateReadiness } from "../core/readiness.js";
|
|
2
|
+
import { createColors } from "../core/colors.js";
|
|
2
3
|
export async function readyCommand(options) {
|
|
3
4
|
const stdout = options.stdout ?? console;
|
|
5
|
+
const colors = createColors();
|
|
4
6
|
const report = await evaluateReadiness(options.root, options.target);
|
|
5
7
|
if (options.json) {
|
|
6
8
|
stdout.log(JSON.stringify(report, null, 2));
|
|
7
9
|
return report.status === "ready" ? 0 : 1;
|
|
8
10
|
}
|
|
9
|
-
stdout.log(`# Speckit Readiness: ${report.status}`);
|
|
11
|
+
stdout.log(`# ${colors.bold("Speckit Readiness")}: ${colors.status(report.status === "ready", "ready", report.status)}`);
|
|
10
12
|
if (report.story) {
|
|
11
|
-
stdout.log(
|
|
13
|
+
stdout.log(`${colors.bold("Story")}: ${colors.cyan(report.story.path)}`);
|
|
12
14
|
}
|
|
13
15
|
for (const check of report.checks) {
|
|
14
|
-
stdout.log(`- ${check.ok
|
|
16
|
+
stdout.log(`- ${colors.status(check.ok, "ok", "blocked")} ${check.name}: ${check.detail}`);
|
|
15
17
|
}
|
|
16
18
|
if (report.status !== "ready") {
|
|
17
|
-
stdout.error("Run the suggested setup commands before `speckit run`.");
|
|
19
|
+
stdout.error(colors.yellow("Run the suggested setup commands before `speckit run`."));
|
|
18
20
|
}
|
|
19
21
|
return report.status === "ready" ? 0 : 1;
|
|
20
22
|
}
|
package/dist/commands/setup.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createInterface } from "node:readline/promises";
|
|
2
2
|
import { adapters } from "../config/adapter-registry.js";
|
|
3
|
+
import { createColors } from "../core/colors.js";
|
|
3
4
|
import { graphCommand } from "./graph.js";
|
|
4
5
|
import { initCommand } from "./init.js";
|
|
5
6
|
const modeChoices = [
|
|
@@ -17,6 +18,7 @@ export async function setupCommand(options) {
|
|
|
17
18
|
const stdin = options.stdin ?? process.stdin;
|
|
18
19
|
const output = options.output ?? process.stdout;
|
|
19
20
|
const stdout = options.stdout ?? console;
|
|
21
|
+
const colors = createColors(output);
|
|
20
22
|
if (options.answers) {
|
|
21
23
|
return setupFromAnswers(options, stdout, options.answers);
|
|
22
24
|
}
|
|
@@ -26,14 +28,14 @@ export async function setupCommand(options) {
|
|
|
26
28
|
}
|
|
27
29
|
const rl = createInterface({ input: stdin, output });
|
|
28
30
|
try {
|
|
29
|
-
stdout.log("Speckit setup");
|
|
31
|
+
stdout.log(colors.bold(colors.cyan("Speckit setup")));
|
|
30
32
|
stdout.log("");
|
|
31
|
-
const mode = await select(rl, stdout, modeChoices, "Choose setup mode", "1");
|
|
32
|
-
const ide = await select(rl, stdout, ideChoices, "Choose IDE adapter", "1");
|
|
33
|
-
const force = await confirm(rl, stdout, "Overwrite unmanaged files if needed?", false);
|
|
34
|
-
const configureGraph = await confirm(rl, stdout, "Run Beads Viewer setup after init?", true);
|
|
33
|
+
const mode = await select(rl, stdout, modeChoices, "Choose setup mode", "1", colors);
|
|
34
|
+
const ide = await select(rl, stdout, ideChoices, "Choose IDE adapter", "1", colors);
|
|
35
|
+
const force = await confirm(rl, stdout, "Overwrite unmanaged files if needed?", false, colors);
|
|
36
|
+
const configureGraph = await confirm(rl, stdout, "Run Beads Viewer setup after init?", true, colors);
|
|
35
37
|
stdout.log("");
|
|
36
|
-
stdout.log(`Selected: ${mode}, ide=${ide}, force=${force ? "yes" : "no"}, graph=${configureGraph ? "yes" : "no"}`);
|
|
38
|
+
stdout.log(colors.dim(`Selected: ${mode}, ide=${ide}, force=${force ? "yes" : "no"}, graph=${configureGraph ? "yes" : "no"}`));
|
|
37
39
|
stdout.log("");
|
|
38
40
|
const initExit = await initCommand({
|
|
39
41
|
root: options.root,
|
|
@@ -53,14 +55,15 @@ export async function setupCommand(options) {
|
|
|
53
55
|
}
|
|
54
56
|
}
|
|
55
57
|
async function setupFromAnswers(options, stdout, answers) {
|
|
56
|
-
|
|
58
|
+
const colors = createColors();
|
|
59
|
+
stdout.log(colors.bold(colors.cyan("Speckit setup")));
|
|
57
60
|
stdout.log("");
|
|
58
|
-
const mode = selectAnswer(stdout, modeChoices, "Choose setup mode", "1", answers.shift());
|
|
59
|
-
const ide = selectAnswer(stdout, ideChoices, "Choose IDE adapter", "1", answers.shift());
|
|
61
|
+
const mode = selectAnswer(stdout, modeChoices, "Choose setup mode", "1", answers.shift(), colors);
|
|
62
|
+
const ide = selectAnswer(stdout, ideChoices, "Choose IDE adapter", "1", answers.shift(), colors);
|
|
60
63
|
const force = confirmAnswer("Overwrite unmanaged files if needed?", false, answers.shift());
|
|
61
64
|
const configureGraph = confirmAnswer("Run Beads Viewer setup after init?", true, answers.shift());
|
|
62
65
|
stdout.log("");
|
|
63
|
-
stdout.log(`Selected: ${mode}, ide=${ide}, force=${force ? "yes" : "no"}, graph=${configureGraph ? "yes" : "no"}`);
|
|
66
|
+
stdout.log(colors.dim(`Selected: ${mode}, ide=${ide}, force=${force ? "yes" : "no"}, graph=${configureGraph ? "yes" : "no"}`));
|
|
64
67
|
stdout.log("");
|
|
65
68
|
const initExit = await initCommand({
|
|
66
69
|
root: options.root,
|
|
@@ -75,9 +78,9 @@ async function setupFromAnswers(options, stdout, answers) {
|
|
|
75
78
|
}
|
|
76
79
|
return initExit;
|
|
77
80
|
}
|
|
78
|
-
function selectAnswer(stdout, choices, prompt, defaultValue, answer) {
|
|
79
|
-
stdout.log(prompt);
|
|
80
|
-
choices.forEach((choice, index) => stdout.log(` ${index + 1}. ${choice.label}`));
|
|
81
|
+
function selectAnswer(stdout, choices, prompt, defaultValue, answer, colors = createColors()) {
|
|
82
|
+
stdout.log(colors.bold(prompt));
|
|
83
|
+
choices.forEach((choice, index) => stdout.log(` ${colors.cyan(String(index + 1))}. ${choice.label}`));
|
|
81
84
|
const selected = (answer?.trim() || defaultValue);
|
|
82
85
|
const index = Number.parseInt(selected, 10) - 1;
|
|
83
86
|
if (!Number.isInteger(index) || !choices[index]) {
|
|
@@ -96,33 +99,33 @@ function confirmAnswer(prompt, defaultValue, answer) {
|
|
|
96
99
|
return false;
|
|
97
100
|
throw new Error(`Invalid yes/no answer for "${prompt}": ${answer}`);
|
|
98
101
|
}
|
|
99
|
-
async function select(rl, stdout, choices, prompt, defaultValue) {
|
|
102
|
+
async function select(rl, stdout, choices, prompt, defaultValue, colors = createColors()) {
|
|
100
103
|
for (;;) {
|
|
101
|
-
stdout.log(prompt);
|
|
104
|
+
stdout.log(colors.bold(prompt));
|
|
102
105
|
choices.forEach((choice, index) => {
|
|
103
106
|
const number = String(index + 1);
|
|
104
|
-
stdout.log(` ${number}. ${choice.label}`);
|
|
107
|
+
stdout.log(` ${colors.cyan(number)}. ${choice.label}`);
|
|
105
108
|
});
|
|
106
|
-
const answer = (await rl.question(
|
|
109
|
+
const answer = (await rl.question(`${colors.dim("Select")} [${colors.cyan(defaultValue)}]: `)).trim() || defaultValue;
|
|
107
110
|
const index = Number.parseInt(answer, 10) - 1;
|
|
108
111
|
if (Number.isInteger(index) && choices[index]) {
|
|
109
112
|
stdout.log("");
|
|
110
113
|
return choices[index].value;
|
|
111
114
|
}
|
|
112
|
-
stdout.log(`Invalid selection: ${answer}`);
|
|
115
|
+
stdout.log(colors.red(`Invalid selection: ${answer}`));
|
|
113
116
|
stdout.log("");
|
|
114
117
|
}
|
|
115
118
|
}
|
|
116
|
-
async function confirm(rl, stdout, prompt, defaultValue) {
|
|
119
|
+
async function confirm(rl, stdout, prompt, defaultValue, colors = createColors()) {
|
|
117
120
|
const suffix = defaultValue ? "Y/n" : "y/N";
|
|
118
121
|
for (;;) {
|
|
119
|
-
const answer = (await rl.question(`${prompt} [${suffix}]: `)).trim().toLowerCase();
|
|
122
|
+
const answer = (await rl.question(`${prompt} [${colors.cyan(suffix)}]: `)).trim().toLowerCase();
|
|
120
123
|
if (!answer)
|
|
121
124
|
return defaultValue;
|
|
122
125
|
if (["y", "yes"].includes(answer))
|
|
123
126
|
return true;
|
|
124
127
|
if (["n", "no"].includes(answer))
|
|
125
128
|
return false;
|
|
126
|
-
stdout.log("Please answer y or n.");
|
|
129
|
+
stdout.log(colors.yellow("Please answer y or n."));
|
|
127
130
|
}
|
|
128
131
|
}
|
package/dist/commands/sync.js
CHANGED
|
@@ -3,8 +3,10 @@ import { join } from "node:path";
|
|
|
3
3
|
import { markdown, writeManagedFiles } from "../core/managed-files.js";
|
|
4
4
|
import { firstHeading, frontmatterValue } from "../core/story.js";
|
|
5
5
|
import { writeBeadsMirror } from "../core/beads-mirror.js";
|
|
6
|
+
import { createColors } from "../core/colors.js";
|
|
6
7
|
export async function syncCommand(options) {
|
|
7
8
|
const stdout = options.stdout ?? console;
|
|
9
|
+
const colors = createColors();
|
|
8
10
|
const storiesDir = join(options.root, ".speckit", "stories");
|
|
9
11
|
let entries;
|
|
10
12
|
try {
|
|
@@ -45,7 +47,7 @@ bv --robot-next --format json
|
|
|
45
47
|
},
|
|
46
48
|
], true);
|
|
47
49
|
const beadsPath = await writeBeadsMirror(options.root, stories);
|
|
48
|
-
stdout.log(`Synced ${stories.length} stories to .speckit/sync/beads-sync.jsonl`);
|
|
49
|
-
stdout.log(`Prepared Beads Viewer mirror at ${beadsPath}`);
|
|
50
|
+
stdout.log(`Synced ${colors.cyan(String(stories.length))} stories to ${colors.cyan(".speckit/sync/beads-sync.jsonl")}`);
|
|
51
|
+
stdout.log(`Prepared Beads Viewer mirror at ${colors.cyan(beadsPath)}`);
|
|
50
52
|
return 0;
|
|
51
53
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { validateWorkflowContract } from "../core/workflow-validator.js";
|
|
2
|
+
import { createColors } from "../core/colors.js";
|
|
2
3
|
export async function validateCommand(options) {
|
|
3
4
|
const stdout = options.stdout ?? console;
|
|
5
|
+
const colors = createColors();
|
|
4
6
|
const checks = await validateWorkflowContract(options.root);
|
|
5
7
|
const status = checks.every((check) => check.ok) ? "ok" : "needs-attention";
|
|
6
8
|
const report = { status, checks };
|
|
@@ -8,9 +10,9 @@ export async function validateCommand(options) {
|
|
|
8
10
|
stdout.log(JSON.stringify(report, null, 2));
|
|
9
11
|
}
|
|
10
12
|
else {
|
|
11
|
-
stdout.log(
|
|
13
|
+
stdout.log(`${colors.bold("Speckit workflow contract")}: ${colors.status(status === "ok", "ok", status)}`);
|
|
12
14
|
for (const check of checks) {
|
|
13
|
-
stdout.log(`${check.ok
|
|
15
|
+
stdout.log(`${colors.status(check.ok, "ok", "fail")} ${check.name}: ${check.detail}`);
|
|
14
16
|
}
|
|
15
17
|
}
|
|
16
18
|
return status === "ok" ? 0 : 1;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type ColorOutput = {
|
|
2
|
+
isTTY?: boolean;
|
|
3
|
+
};
|
|
4
|
+
type ColorName = "blue" | "cyan" | "dim" | "green" | "red" | "yellow" | "bold";
|
|
5
|
+
export type Colors = Record<ColorName, (text: string) => string> & {
|
|
6
|
+
enabled: boolean;
|
|
7
|
+
status(ok: boolean, okText?: string, failText?: string): string;
|
|
8
|
+
};
|
|
9
|
+
export declare function createColors(output?: ColorOutput): Colors;
|
|
10
|
+
export declare function colorsEnabled(output?: ColorOutput): boolean;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const ansi = {
|
|
2
|
+
blue: [34, 39],
|
|
3
|
+
cyan: [36, 39],
|
|
4
|
+
dim: [2, 22],
|
|
5
|
+
green: [32, 39],
|
|
6
|
+
red: [31, 39],
|
|
7
|
+
yellow: [33, 39],
|
|
8
|
+
bold: [1, 22],
|
|
9
|
+
};
|
|
10
|
+
export function createColors(output) {
|
|
11
|
+
const enabled = colorsEnabled(output);
|
|
12
|
+
const style = (name) => (text) => {
|
|
13
|
+
if (!enabled)
|
|
14
|
+
return text;
|
|
15
|
+
const [open, close] = ansi[name];
|
|
16
|
+
return `\u001B[${open}m${text}\u001B[${close}m`;
|
|
17
|
+
};
|
|
18
|
+
return {
|
|
19
|
+
enabled,
|
|
20
|
+
blue: style("blue"),
|
|
21
|
+
cyan: style("cyan"),
|
|
22
|
+
dim: style("dim"),
|
|
23
|
+
green: style("green"),
|
|
24
|
+
red: style("red"),
|
|
25
|
+
yellow: style("yellow"),
|
|
26
|
+
bold: style("bold"),
|
|
27
|
+
status(ok, okText = "ok", failText = "missing") {
|
|
28
|
+
return ok ? this.green(okText) : this.red(failText);
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export function colorsEnabled(output) {
|
|
33
|
+
if (process.env.NO_COLOR || process.env.SPECKIT_COLOR === "0")
|
|
34
|
+
return false;
|
|
35
|
+
if (process.env.FORCE_COLOR === "0")
|
|
36
|
+
return false;
|
|
37
|
+
if (process.env.FORCE_COLOR || process.env.SPECKIT_COLOR === "1")
|
|
38
|
+
return true;
|
|
39
|
+
return output?.isTTY ?? process.stdout.isTTY ?? false;
|
|
40
|
+
}
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Project Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.6 - 2026-05-11
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Added ANSI color rendering for interactive CLI and TUI output, including setup prompts, init summaries, doctor checks, graph setup, readiness, validation, sync, and help headers.
|
|
8
|
+
- Added `SPECKIT_COLOR=1` / `FORCE_COLOR` support for demos and snapshots, with `NO_COLOR` / `SPECKIT_COLOR=0` opt-out support.
|
|
9
|
+
|
|
10
|
+
### Quality
|
|
11
|
+
|
|
12
|
+
- Added color helper regression tests.
|
|
13
|
+
- JSON output remains uncolored for automation.
|
|
14
|
+
|
|
3
15
|
## 0.3.5 - 2026-05-11
|
|
4
16
|
|
|
5
17
|
### Added
|