@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.
- package/README.md +110 -11
- package/dist/{chunk-mn870nrv.js → chunk-xkxndz5g.js} +213 -154
- package/dist/sdk/components/workflow-picker-panel.d.ts +120 -0
- package/dist/sdk/define-workflow.d.ts +1 -1
- package/dist/sdk/index.js +1 -1
- package/dist/sdk/runtime/discovery.d.ts +57 -3
- package/dist/sdk/runtime/executor.d.ts +15 -2
- package/dist/sdk/runtime/tmux.d.ts +9 -0
- package/dist/sdk/types.d.ts +63 -4
- package/dist/sdk/workflows/builtin/deep-research-codebase/claude/index.d.ts +61 -0
- package/dist/sdk/workflows/builtin/deep-research-codebase/copilot/index.d.ts +48 -0
- package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/heuristic.d.ts +25 -0
- package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/prompts.d.ts +91 -0
- package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/scout.d.ts +56 -0
- package/dist/sdk/workflows/builtin/deep-research-codebase/opencode/index.d.ts +48 -0
- package/dist/sdk/workflows/builtin/ralph/claude/index.js +6 -5
- package/dist/sdk/workflows/builtin/ralph/copilot/index.js +6 -5
- package/dist/sdk/workflows/builtin/ralph/opencode/index.js +6 -5
- package/dist/sdk/workflows/index.d.ts +4 -4
- package/dist/sdk/workflows/index.js +7 -1
- package/package.json +1 -1
- package/src/cli.ts +25 -3
- package/src/commands/cli/chat/index.ts +5 -5
- package/src/commands/cli/init/index.ts +79 -77
- package/src/commands/cli/workflow-command.test.ts +757 -0
- package/src/commands/cli/workflow.test.ts +310 -0
- package/src/commands/cli/workflow.ts +445 -105
- package/src/sdk/components/workflow-picker-panel.tsx +1462 -0
- package/src/sdk/define-workflow.test.ts +101 -0
- package/src/sdk/define-workflow.ts +62 -2
- package/src/sdk/runtime/discovery.ts +111 -8
- package/src/sdk/runtime/executor.ts +89 -32
- package/src/sdk/runtime/tmux.conf +55 -0
- package/src/sdk/runtime/tmux.ts +34 -10
- package/src/sdk/types.ts +67 -4
- package/src/sdk/workflows/builtin/deep-research-codebase/claude/index.ts +294 -0
- package/src/sdk/workflows/builtin/deep-research-codebase/copilot/index.ts +276 -0
- package/src/sdk/workflows/builtin/deep-research-codebase/helpers/heuristic.ts +38 -0
- package/src/sdk/workflows/builtin/deep-research-codebase/helpers/prompts.ts +816 -0
- package/src/sdk/workflows/builtin/deep-research-codebase/helpers/scout.ts +334 -0
- package/src/sdk/workflows/builtin/deep-research-codebase/opencode/index.ts +284 -0
- package/src/sdk/workflows/builtin/ralph/claude/index.ts +8 -4
- package/src/sdk/workflows/builtin/ralph/copilot/index.ts +10 -4
- package/src/sdk/workflows/builtin/ralph/opencode/index.ts +8 -4
- package/src/sdk/workflows/index.ts +9 -1
- package/src/services/system/auto-sync.ts +1 -1
- package/src/services/system/install-ui.ts +109 -39
- 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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 {
|
|
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}
|
|
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
|
-
* ⠋
|
|
6
|
+
* ⠋ ■■■■■■■■■■■■■■■■■■・・・・・・・・・・・・ 50% tmux / psmux
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
* uses Catppuccin Mocha accent colors (
|
|
10
|
-
* success, Red for error) with
|
|
11
|
-
*
|
|
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 →
|
|
40
|
-
* success → Green
|
|
41
|
-
* error → Red
|
|
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
|
|
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
|
|
54
|
+
return "\x1b[38;2;243;139;168m"; // Catppuccin Red #f38ba8
|
|
56
55
|
case "progress":
|
|
57
56
|
default:
|
|
58
|
-
return "\x1b[38;2;
|
|
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;
|
|
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.
|
|
78
|
+
return COLORS.yellow;
|
|
80
79
|
}
|
|
81
80
|
}
|
|
82
81
|
|
|
83
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
151
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
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
|
}
|
package/src/theme/colors.ts
CHANGED
|
@@ -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
|
+
}
|