@bastani/atomic 0.5.3-1 → 0.5.4-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.
Files changed (48) hide show
  1. package/README.md +110 -11
  2. package/dist/{chunk-mn870nrv.js → chunk-xkxndz5g.js} +213 -154
  3. package/dist/sdk/components/workflow-picker-panel.d.ts +120 -0
  4. package/dist/sdk/define-workflow.d.ts +1 -1
  5. package/dist/sdk/index.js +1 -1
  6. package/dist/sdk/runtime/discovery.d.ts +57 -3
  7. package/dist/sdk/runtime/executor.d.ts +15 -2
  8. package/dist/sdk/runtime/tmux.d.ts +9 -0
  9. package/dist/sdk/types.d.ts +63 -4
  10. package/dist/sdk/workflows/builtin/deep-research-codebase/claude/index.d.ts +61 -0
  11. package/dist/sdk/workflows/builtin/deep-research-codebase/copilot/index.d.ts +48 -0
  12. package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/heuristic.d.ts +25 -0
  13. package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/prompts.d.ts +91 -0
  14. package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/scout.d.ts +56 -0
  15. package/dist/sdk/workflows/builtin/deep-research-codebase/opencode/index.d.ts +48 -0
  16. package/dist/sdk/workflows/builtin/ralph/claude/index.js +6 -5
  17. package/dist/sdk/workflows/builtin/ralph/copilot/index.js +6 -5
  18. package/dist/sdk/workflows/builtin/ralph/opencode/index.js +6 -5
  19. package/dist/sdk/workflows/index.d.ts +4 -4
  20. package/dist/sdk/workflows/index.js +7 -1
  21. package/package.json +1 -1
  22. package/src/cli.ts +25 -3
  23. package/src/commands/cli/chat/index.ts +5 -5
  24. package/src/commands/cli/init/index.ts +79 -77
  25. package/src/commands/cli/workflow-command.test.ts +757 -0
  26. package/src/commands/cli/workflow.test.ts +310 -0
  27. package/src/commands/cli/workflow.ts +445 -105
  28. package/src/sdk/components/workflow-picker-panel.tsx +1462 -0
  29. package/src/sdk/define-workflow.test.ts +101 -0
  30. package/src/sdk/define-workflow.ts +62 -2
  31. package/src/sdk/runtime/discovery.ts +111 -8
  32. package/src/sdk/runtime/executor.ts +89 -32
  33. package/src/sdk/runtime/tmux.conf +55 -0
  34. package/src/sdk/runtime/tmux.ts +34 -10
  35. package/src/sdk/types.ts +67 -4
  36. package/src/sdk/workflows/builtin/deep-research-codebase/claude/index.ts +294 -0
  37. package/src/sdk/workflows/builtin/deep-research-codebase/copilot/index.ts +276 -0
  38. package/src/sdk/workflows/builtin/deep-research-codebase/helpers/heuristic.ts +38 -0
  39. package/src/sdk/workflows/builtin/deep-research-codebase/helpers/prompts.ts +816 -0
  40. package/src/sdk/workflows/builtin/deep-research-codebase/helpers/scout.ts +334 -0
  41. package/src/sdk/workflows/builtin/deep-research-codebase/opencode/index.ts +284 -0
  42. package/src/sdk/workflows/builtin/ralph/claude/index.ts +8 -4
  43. package/src/sdk/workflows/builtin/ralph/copilot/index.ts +10 -4
  44. package/src/sdk/workflows/builtin/ralph/opencode/index.ts +8 -4
  45. package/src/sdk/workflows/index.ts +9 -1
  46. package/src/services/system/auto-sync.ts +1 -1
  47. package/src/services/system/install-ui.ts +109 -39
  48. package/src/theme/colors.ts +65 -1
@@ -73,6 +73,12 @@ export default defineWorkflow<"copilot">({
73
73
  "Plan → orchestrate → review → debug loop with bounded iteration",
74
74
  })
75
75
  .run(async (ctx) => {
76
+ // Free-form workflows receive their positional prompt under
77
+ // `inputs.prompt`; destructure once so every stage below can close
78
+ // over a bare `userPromptText` without re-reaching into ctx.inputs.
79
+ // (Named `userPromptText` rather than `prompt` to avoid confusion
80
+ // with the `prompt:` object key used in the Copilot send call.)
81
+ const userPromptText = ctx.inputs.prompt ?? "";
76
82
  let consecutiveClean = 0;
77
83
  let debuggerReport = "";
78
84
 
@@ -86,7 +92,7 @@ export default defineWorkflow<"copilot">({
86
92
  async (s) => {
87
93
  await s.session.sendAndWait(
88
94
  {
89
- prompt: buildPlannerPrompt(s.userPrompt, {
95
+ prompt: buildPlannerPrompt(userPromptText, {
90
96
  iteration,
91
97
  debuggerReport: debuggerReport || undefined,
92
98
  }),
@@ -109,7 +115,7 @@ export default defineWorkflow<"copilot">({
109
115
  async (s) => {
110
116
  await s.session.sendAndWait(
111
117
  {
112
- prompt: buildOrchestratorPrompt(s.userPrompt, {
118
+ prompt: buildOrchestratorPrompt(userPromptText, {
113
119
  plannerNotes: planner.result,
114
120
  }),
115
121
  },
@@ -130,7 +136,7 @@ export default defineWorkflow<"copilot">({
130
136
  async (s) => {
131
137
  await s.session.sendAndWait(
132
138
  {
133
- prompt: buildReviewPrompt(s.userPrompt, {
139
+ prompt: buildReviewPrompt(userPromptText, {
134
140
  gitStatus,
135
141
  iteration,
136
142
  }),
@@ -161,7 +167,7 @@ export default defineWorkflow<"copilot">({
161
167
  async (s) => {
162
168
  await s.session.sendAndWait(
163
169
  {
164
- prompt: buildReviewPrompt(s.userPrompt, {
170
+ prompt: buildReviewPrompt(userPromptText, {
165
171
  gitStatus,
166
172
  iteration,
167
173
  isConfirmationPass: true,
@@ -42,6 +42,10 @@ export default defineWorkflow<"opencode">({
42
42
  "Plan → orchestrate → review → debug loop with bounded iteration",
43
43
  })
44
44
  .run(async (ctx) => {
45
+ // Free-form workflows receive their positional prompt under
46
+ // `inputs.prompt`; destructure once so every stage below can close
47
+ // over a bare `prompt` string without re-reaching into ctx.inputs.
48
+ const prompt = ctx.inputs.prompt ?? "";
45
49
  let consecutiveClean = 0;
46
50
  let debuggerReport = "";
47
51
 
@@ -58,7 +62,7 @@ export default defineWorkflow<"opencode">({
58
62
  parts: [
59
63
  {
60
64
  type: "text",
61
- text: buildPlannerPrompt(s.userPrompt, {
65
+ text: buildPlannerPrompt(prompt, {
62
66
  iteration,
63
67
  debuggerReport: debuggerReport || undefined,
64
68
  }),
@@ -84,7 +88,7 @@ export default defineWorkflow<"opencode">({
84
88
  parts: [
85
89
  {
86
90
  type: "text",
87
- text: buildOrchestratorPrompt(s.userPrompt, {
91
+ text: buildOrchestratorPrompt(prompt, {
88
92
  plannerNotes: planner.result,
89
93
  }),
90
94
  },
@@ -109,7 +113,7 @@ export default defineWorkflow<"opencode">({
109
113
  parts: [
110
114
  {
111
115
  type: "text",
112
- text: buildReviewPrompt(s.userPrompt, {
116
+ text: buildReviewPrompt(prompt, {
113
117
  gitStatus,
114
118
  iteration,
115
119
  }),
@@ -143,7 +147,7 @@ export default defineWorkflow<"opencode">({
143
147
  parts: [
144
148
  {
145
149
  type: "text",
146
- text: buildReviewPrompt(s.userPrompt, {
150
+ text: buildReviewPrompt(prompt, {
147
151
  gitStatus,
148
152
  iteration,
149
153
  isConfirmationPass: true,
@@ -20,6 +20,8 @@ export type {
20
20
  WorkflowContext,
21
21
  WorkflowOptions,
22
22
  WorkflowDefinition,
23
+ WorkflowInput,
24
+ WorkflowInputType,
23
25
  StageClientOptions,
24
26
  StageSessionOptions,
25
27
  ProviderClient,
@@ -52,6 +54,7 @@ export type { OpenCodeValidationWarning } from "../providers/opencode.ts";
52
54
 
53
55
  // Runtime — tmux utilities
54
56
  export {
57
+ SOCKET_NAME,
55
58
  isTmuxInstalled,
56
59
  getMuxBinary,
57
60
  resetMuxBinaryCache,
@@ -69,6 +72,7 @@ export {
69
72
  killWindow,
70
73
  sessionExists,
71
74
  attachSession,
75
+ spawnMuxAttach,
72
76
  switchClient,
73
77
  getCurrentSession,
74
78
  attachOrSwitch,
@@ -89,9 +93,13 @@ export {
89
93
  AGENTS,
90
94
  discoverWorkflows,
91
95
  findWorkflow,
96
+ loadWorkflowsMetadata,
92
97
  WORKFLOWS_GITIGNORE,
93
98
  } from "../runtime/discovery.ts";
94
- export type { DiscoveredWorkflow } from "../runtime/discovery.ts";
99
+ export type {
100
+ DiscoveredWorkflow,
101
+ WorkflowWithMetadata,
102
+ } from "../runtime/discovery.ts";
95
103
 
96
104
  // Runtime — workflow loader pipeline
97
105
  export { WorkflowLoader } from "../runtime/loader.ts";
@@ -90,7 +90,7 @@ export async function autoSyncIfStale(): Promise<void> {
90
90
  if (stored === VERSION) return;
91
91
 
92
92
  console.log(
93
- `\n ${COLORS.dim}Setting up atomic ${COLORS.reset}${COLORS.bold}v${VERSION}${COLORS.reset}${COLORS.dim}…${COLORS.reset}\n`,
93
+ `\n ${COLORS.dim}Setting up atomic ${COLORS.reset}${COLORS.bold}v${VERSION}${COLORS.reset}${COLORS.dim}…${COLORS.reset}`,
94
94
  );
95
95
 
96
96
  // Steps are split into two parallel phases:
@@ -3,12 +3,12 @@
3
3
  *
4
4
  * Renders an OpenCode-inspired single-line progress bar:
5
5
  *
6
- * ⠋ ■■■■■■■■■■■■■■■■■■・・・・・・・・・・・・ 50% tmux / psmux
6
+ * ⠋ ■■■■■■■■■■■■■■■■■■・・・・・・・・・・・・ 50% tmux / psmux
7
7
  *
8
- * where the braille spinner is provided by `@clack/prompts` and the bar
9
- * uses Catppuccin Mocha accent colors (Blue for progress, Green for
10
- * success, Red for error) with true-color → 256-color → basic ANSI
11
- * fallback.
8
+ * The braille spinner animates in-place via a setInterval loop and the
9
+ * bar uses Catppuccin Mocha accent colors (Yellow for progress, Green
10
+ * for success, Red for error) with a per-character true-color gradient
11
+ * that falls back gracefully through 256-color → basic ANSI.
12
12
  *
13
13
  * Steps are grouped into **phases**. Steps within a phase run in parallel
14
14
  * (via `Promise.all`); phases themselves run sequentially so later phases
@@ -23,7 +23,6 @@
23
23
  * library, just what auto-sync needs to stop being visually noisy.
24
24
  */
25
25
 
26
- import { spinner } from "@clack/prompts";
27
26
  import { COLORS } from "@/theme/colors.ts";
28
27
  import {
29
28
  supportsTrueColor,
@@ -36,9 +35,9 @@ const BAR_EMPTY = "・";
36
35
 
37
36
  /**
38
37
  * Semantic bar states mapped to Catppuccin Mocha colors:
39
- * progress → Blue #89b4fa (accent; "in flight")
40
- * success → Green #a6e3a1 (universal "completed")
41
- * error → Red #f38ba8 (universal "failed")
38
+ * progress → Yellow #f9e2af (warm accent; "in flight")
39
+ * success → Green #a6e3a1 (universal "completed")
40
+ * error → Red #f38ba8 (universal "failed")
42
41
  *
43
42
  * The empty track stays dim regardless — only the filled portion carries
44
43
  * the status signal, which keeps the bar legible while still telegraphing
@@ -50,12 +49,12 @@ function fillColor(state: BarState): string {
50
49
  if (supportsTrueColor()) {
51
50
  switch (state) {
52
51
  case "success":
53
- return "\x1b[38;2;166;227;161m"; // Catppuccin Green #a6e3a1
52
+ return "\x1b[38;2;166;227;161m"; // Catppuccin Green #a6e3a1
54
53
  case "error":
55
- return "\x1b[38;2;243;139;168m"; // Catppuccin Red #f38ba8
54
+ return "\x1b[38;2;243;139;168m"; // Catppuccin Red #f38ba8
56
55
  case "progress":
57
56
  default:
58
- return "\x1b[38;2;137;180;250m"; // Catppuccin Blue #89b4fa
57
+ return "\x1b[38;2;249;226;175m"; // Catppuccin Yellow #f9e2af
59
58
  }
60
59
  }
61
60
  if (supports256Color()) {
@@ -66,7 +65,7 @@ function fillColor(state: BarState): string {
66
65
  return "\x1b[38;5;211m";
67
66
  case "progress":
68
67
  default:
69
- return "\x1b[38;5;111m";
68
+ return "\x1b[38;5;222m";
70
69
  }
71
70
  }
72
71
  switch (state) {
@@ -76,11 +75,36 @@ function fillColor(state: BarState): string {
76
75
  return COLORS.red;
77
76
  case "progress":
78
77
  default:
79
- return COLORS.blue;
78
+ return COLORS.yellow;
80
79
  }
81
80
  }
82
81
 
83
- /** Render a progress bar: colored filled ■ + dim empty ・ */
82
+ type RGB = [number, number, number];
83
+
84
+ /**
85
+ * Gradient endpoints for the filled bar segment. Each state interpolates
86
+ * from a slightly deeper/warmer tone (left) to the full Catppuccin
87
+ * accent (right), producing a smooth continuous color gradient.
88
+ */
89
+ function gradientEndpoints(state: BarState): { start: RGB; end: RGB } {
90
+ switch (state) {
91
+ case "success":
92
+ return { start: [126, 201, 138], end: [166, 227, 161] };
93
+ case "error":
94
+ return { start: [224, 108, 136], end: [243, 139, 168] };
95
+ case "progress":
96
+ default:
97
+ return { start: [242, 196, 120], end: [249, 226, 175] };
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Render a progress bar: gradient-filled ■ + dim empty ・
103
+ *
104
+ * In true-color mode each filled character gets its own interpolated RGB
105
+ * value, producing a smooth continuous gradient. Falls back to a single
106
+ * solid color on 256-color or basic ANSI terminals.
107
+ */
84
108
  function renderBar(
85
109
  completed: number,
86
110
  total: number,
@@ -90,14 +114,25 @@ function renderBar(
90
114
  const ratio = Math.max(0, Math.min(1, completed / safeTotal));
91
115
  const filled = Math.round(ratio * BAR_WIDTH);
92
116
  const empty = BAR_WIDTH - filled;
93
- return (
94
- fillColor(state) +
95
- BAR_FILLED.repeat(filled) +
96
- COLORS.reset +
97
- COLORS.dim +
98
- BAR_EMPTY.repeat(empty) +
99
- COLORS.reset
100
- );
117
+
118
+ let filledStr = "";
119
+ if (filled > 0) {
120
+ if (supportsTrueColor()) {
121
+ const { start, end } = gradientEndpoints(state);
122
+ for (let i = 0; i < filled; i++) {
123
+ const t = filled > 1 ? i / (filled - 1) : 1;
124
+ const r = Math.round(start[0] + (end[0] - start[0]) * t);
125
+ const g = Math.round(start[1] + (end[1] - start[1]) * t);
126
+ const b = Math.round(start[2] + (end[2] - start[2]) * t);
127
+ filledStr += `\x1b[38;2;${r};${g};${b}m${BAR_FILLED}`;
128
+ }
129
+ filledStr += COLORS.reset;
130
+ } else {
131
+ filledStr = fillColor(state) + BAR_FILLED.repeat(filled) + COLORS.reset;
132
+ }
133
+ }
134
+
135
+ return filledStr + COLORS.dim + BAR_EMPTY.repeat(empty) + COLORS.reset;
101
136
  }
102
137
 
103
138
  function formatLine(
@@ -130,6 +165,8 @@ export interface Step {
130
165
  /** A phase is a group of steps that run in parallel. */
131
166
  export type Phase = Step[];
132
167
 
168
+ const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
169
+
133
170
  /**
134
171
  * Runs phases of async steps with a single persistent spinner line
135
172
  * showing stepped progress. Steps within each phase run in parallel;
@@ -144,18 +181,39 @@ export type Phase = Step[];
144
181
  export async function runSteps(phases: Phase[]): Promise<StepResult[]> {
145
182
  const total = phases.reduce((n, phase) => n + phase.length, 0);
146
183
  const results: StepResult[] = [];
147
- const s = spinner();
148
184
  let completed = 0;
185
+ let frameIdx = 0;
186
+ let currentLabel = phases[0]?.[0]?.label ?? "";
187
+ let animating = true;
188
+
189
+ const isTTY = process.stdout.isTTY ?? false;
149
190
 
150
- // Start with 0/total so the user sees the bar immediately.
151
- s.start(formatLine(0, total, phases[0]?.[0]?.label ?? ""));
191
+ if (isTTY) process.stdout.write("\x1b[?25l"); // hide cursor
192
+
193
+ // Restore cursor on unexpected exit so the terminal isn't left broken.
194
+ const restoreCursor = () => {
195
+ if (isTTY) process.stdout.write("\x1b[?25h");
196
+ };
197
+ process.once("SIGINT", restoreCursor);
198
+ process.once("SIGTERM", restoreCursor);
199
+
200
+ // Animate the braille spinner + progress bar in-place (80 ms/frame).
201
+ const interval = isTTY
202
+ ? setInterval(() => {
203
+ if (!animating) return;
204
+ const frame = SPINNER_FRAMES[frameIdx % SPINNER_FRAMES.length];
205
+ const line = formatLine(completed, total, currentLabel);
206
+ process.stdout.write(
207
+ `\r\x1b[2K ${COLORS.blue}${frame}${COLORS.reset} ${line}`,
208
+ );
209
+ frameIdx++;
210
+ }, 80)
211
+ : null;
152
212
 
153
213
  for (const phase of phases) {
154
- // Show all in-flight labels for this phase.
155
214
  const inFlight = new Set(phase.map((step) => step.label));
156
- s.message(formatLine(completed, total, [...inFlight].join(", ")));
215
+ currentLabel = [...inFlight].join(", ");
157
216
 
158
- // Run every step in this phase concurrently.
159
217
  const phaseResults = await Promise.all(
160
218
  phase.map(async (step): Promise<StepResult> => {
161
219
  try {
@@ -163,9 +221,7 @@ export async function runSteps(phases: Phase[]): Promise<StepResult[]> {
163
221
  completed++;
164
222
  inFlight.delete(step.label);
165
223
  if (inFlight.size > 0) {
166
- s.message(
167
- formatLine(completed, total, [...inFlight].join(", ")),
168
- );
224
+ currentLabel = [...inFlight].join(", ");
169
225
  }
170
226
  return { label: step.label, ok: true };
171
227
  } catch (error) {
@@ -174,9 +230,7 @@ export async function runSteps(phases: Phase[]): Promise<StepResult[]> {
174
230
  completed++;
175
231
  inFlight.delete(step.label);
176
232
  if (inFlight.size > 0) {
177
- s.message(
178
- formatLine(completed, total, [...inFlight].join(", ")),
179
- );
233
+ currentLabel = [...inFlight].join(", ");
180
234
  }
181
235
  return { label: step.label, ok: false, error: message };
182
236
  }
@@ -186,16 +240,32 @@ export async function runSteps(phases: Phase[]): Promise<StepResult[]> {
186
240
  results.push(...phaseResults);
187
241
  }
188
242
 
189
- // Stop with a filled bar + final label. Bar color flips to the
190
- // universal status colors: green when every step succeeded, red when
191
- // any step failed.
243
+ // Stop animation and render the final line.
244
+ animating = false;
245
+ if (interval) clearInterval(interval);
246
+ process.removeListener("SIGINT", restoreCursor);
247
+ process.removeListener("SIGTERM", restoreCursor);
248
+
192
249
  const okCount = results.filter((r) => r.ok).length;
193
250
  const allOk = okCount === total;
194
251
  const finalState: BarState = allOk ? "success" : "error";
252
+ const glyph = allOk
253
+ ? `${fillColor("success")}✓${COLORS.reset}`
254
+ : `${fillColor("error")}✗${COLORS.reset}`;
195
255
  const finalLabel = allOk
196
256
  ? `${fillColor("success")}Setup complete${COLORS.reset}`
197
257
  : `${fillColor("error")}Setup finished with errors${COLORS.reset}`;
198
- s.stop(formatLine(total, total, finalLabel, finalState));
258
+
259
+ if (isTTY) {
260
+ process.stdout.write(
261
+ `\r\x1b[2K ${glyph} ${formatLine(total, total, finalLabel, finalState)}\n`,
262
+ );
263
+ process.stdout.write("\x1b[?25h"); // show cursor
264
+ } else {
265
+ console.log(
266
+ ` ${glyph} ${formatLine(total, total, finalLabel, finalState)}`,
267
+ );
268
+ }
199
269
 
200
270
  return results;
201
271
  }
@@ -1,4 +1,4 @@
1
- import { supportsColor } from "@/services/system/detect.ts";
1
+ import { supportsColor, supportsTrueColor } from "@/services/system/detect.ts";
2
2
 
3
3
  /**
4
4
  * ANSI color and formatting codes for CLI output
@@ -25,3 +25,67 @@ const NO_COLORS = {
25
25
  } as const;
26
26
 
27
27
  export const COLORS = supportsColor() ? ANSI_CODES : NO_COLORS;
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // Catppuccin Mocha palette — shared across all CLI commands
31
+ //
32
+ // Truecolor terminals get the full palette via 24-bit ANSI SGR; legacy
33
+ // terminals degrade to basic ANSI; NO_COLOR emits plain text.
34
+ // Hex values mirror .impeccable.md and src/sdk/runtime/theme.ts.
35
+ // ---------------------------------------------------------------------------
36
+
37
+ export type PaletteKey = "text" | "dim" | "accent" | "success" | "error" | "warning" | "mauve" | "info";
38
+
39
+ export const PALETTE: Record<PaletteKey, readonly [number, number, number]> = {
40
+ text: [205, 214, 244], // #cdd6f4
41
+ dim: [127, 132, 156], // #7f849c (Overlay1)
42
+ accent: [137, 180, 250], // #89b4fa (Blue)
43
+ success: [166, 227, 161], // #a6e3a1 (Green)
44
+ error: [243, 139, 168], // #f38ba8 (Red)
45
+ warning: [249, 226, 175], // #f9e2af (Yellow)
46
+ mauve: [203, 166, 247], // #cba6f7 (Mauve)
47
+ info: [137, 220, 235], // #89dceb (Sky)
48
+ };
49
+
50
+ export interface PaintOptions {
51
+ bold?: boolean;
52
+ }
53
+
54
+ export type Paint = (key: PaletteKey, text: string, opts?: PaintOptions) => string;
55
+
56
+ /**
57
+ * Build a colour-aware painter for the current terminal.
58
+ *
59
+ * Truecolor terminals get the full Catppuccin palette; legacy terminals
60
+ * degrade to basic ANSI; NO_COLOR emits plain text. The optional `bold`
61
+ * flag adds weight contrast — essential for typographic hierarchy in a
62
+ * monospace medium where size and family are fixed.
63
+ */
64
+ export function createPainter(): Paint {
65
+ if (supportsTrueColor()) {
66
+ return (key, text, opts) => {
67
+ const [r, g, b] = PALETTE[key];
68
+ const sgr = opts?.bold
69
+ ? `\x1b[1;38;2;${r};${g};${b}m`
70
+ : `\x1b[38;2;${r};${g};${b}m`;
71
+ return `${sgr}${text}\x1b[0m`;
72
+ };
73
+ }
74
+ if (supportsColor()) {
75
+ const ANSI: Record<PaletteKey, string> = {
76
+ text: "",
77
+ dim: "\x1b[2m",
78
+ accent: "\x1b[34m",
79
+ success: "\x1b[32m",
80
+ error: "\x1b[31m",
81
+ warning: "\x1b[33m",
82
+ mauve: "\x1b[35m",
83
+ info: "\x1b[36m",
84
+ };
85
+ return (key, text, opts) => {
86
+ const weight = opts?.bold ? "\x1b[1m" : "";
87
+ return `${weight}${ANSI[key]}${text}\x1b[0m`;
88
+ };
89
+ }
90
+ return (_key, text) => text;
91
+ }