@stupify/cli 0.0.9 → 0.0.10

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/src/ui.ts ADDED
@@ -0,0 +1,187 @@
1
+ import {
2
+ confirm as clackConfirm,
3
+ intro as clackIntro,
4
+ isCancel,
5
+ log,
6
+ note,
7
+ outro as clackOutro,
8
+ progress,
9
+ spinner,
10
+ type SpinnerResult,
11
+ } from "@clack/prompts";
12
+ import { createReadStream, createWriteStream, type ReadStream, type WriteStream } from "node:fs";
13
+ import { platform } from "node:os";
14
+ import { stdin, stderr, stdout } from "node:process";
15
+ import type { Readable, Writable } from "node:stream";
16
+ import pc from "picocolors";
17
+
18
+ export type CliUi = ReturnType<typeof createCliUi>;
19
+
20
+ export type CliUiOptions = Readonly<{
21
+ quiet?: boolean;
22
+ }>;
23
+
24
+ type LogOptions = Readonly<{
25
+ force?: boolean;
26
+ }>;
27
+
28
+ type PromptIo = Readonly<{
29
+ input: Readable;
30
+ output: Writable;
31
+ close: () => void;
32
+ }>;
33
+
34
+ export function createCliUi(options: CliUiOptions = {}) {
35
+ const quiet = options.quiet ?? false;
36
+ const output = stderr;
37
+
38
+ function shouldWrite(logOptions?: LogOptions): boolean {
39
+ return logOptions?.force === true || !quiet;
40
+ }
41
+
42
+ function withPromptIo<T>(
43
+ run: (io: PromptIo) => Promise<T>,
44
+ ): Promise<T> {
45
+ const io = promptIo();
46
+ return run(io).finally(() => io.close());
47
+ }
48
+
49
+ return {
50
+ intro(title: string, logOptions?: LogOptions): void {
51
+ if (shouldWrite(logOptions)) clackIntro(title, { output });
52
+ },
53
+
54
+ outro(message: string, logOptions?: LogOptions): void {
55
+ if (shouldWrite(logOptions)) clackOutro(message, { output });
56
+ },
57
+
58
+ note(message: string, title?: string, logOptions?: LogOptions): void {
59
+ if (shouldWrite(logOptions)) note(message, title, { output });
60
+ },
61
+
62
+ info(message: string, logOptions?: LogOptions): void {
63
+ if (shouldWrite(logOptions)) log.info(message, { output });
64
+ },
65
+
66
+ step(message: string, logOptions?: LogOptions): void {
67
+ if (shouldWrite(logOptions)) log.step(message, { output });
68
+ },
69
+
70
+ success(message: string, logOptions?: LogOptions): void {
71
+ if (shouldWrite(logOptions)) log.success(message, { output });
72
+ },
73
+
74
+ warn(message: string, logOptions?: LogOptions): void {
75
+ if (shouldWrite(logOptions)) log.warn(message, { output });
76
+ },
77
+
78
+ error(message: string, logOptions?: LogOptions): void {
79
+ if (shouldWrite(logOptions)) log.error(message, { output });
80
+ },
81
+
82
+ debug(message: string): void {
83
+ if (!quiet) log.message(message, { output, symbol: pc.dim("trace") });
84
+ },
85
+
86
+ async confirm(message: string): Promise<boolean> {
87
+ return withPromptIo(async (io) => {
88
+ const result = await clackConfirm({
89
+ message,
90
+ active: "Yes",
91
+ inactive: "No",
92
+ initialValue: false,
93
+ input: io.input,
94
+ output: io.output,
95
+ });
96
+ if (isCancel(result)) return false;
97
+ return result;
98
+ });
99
+ },
100
+
101
+ spinner(message: string, logOptions?: LogOptions): SpinnerResult {
102
+ if (!shouldWrite(logOptions)) return silentSpinner();
103
+ const active = spinner({ output });
104
+ active.start(message);
105
+ return active;
106
+ },
107
+
108
+ progress(message: string, max: number, logOptions?: LogOptions) {
109
+ if (!shouldWrite(logOptions)) return silentProgress();
110
+ const active = progress({ output, max });
111
+ active.start(message);
112
+ return active;
113
+ },
114
+
115
+ writeStdout(text: string): void {
116
+ stdout.write(text.endsWith("\n") ? text : `${text}\n`);
117
+ },
118
+ };
119
+ }
120
+
121
+ export const format = {
122
+ heading: (value: string) => pc.bold(value),
123
+ label: (value: string) => pc.cyan(value),
124
+ muted: (value: string) => pc.dim(value),
125
+ success: (value: string) => pc.green(value),
126
+ warn: (value: string) => pc.yellow(value),
127
+ error: (value: string) => pc.red(value),
128
+ };
129
+
130
+ export function diagnostic(message: string): void {
131
+ log.message(message, {
132
+ output: stderr,
133
+ symbol: pc.dim("·"),
134
+ spacing: 0,
135
+ withGuide: false,
136
+ });
137
+ }
138
+
139
+ export function diagnosticError(message: string): void {
140
+ log.error(message, { output: stderr, spacing: 0, withGuide: false });
141
+ }
142
+
143
+ function promptIo(): PromptIo {
144
+ if (stdin.isTTY && stderr.isTTY) {
145
+ return { input: stdin, output: stderr, close: () => undefined };
146
+ }
147
+
148
+ if (platform() === "win32") {
149
+ throw new Error(
150
+ "No interactive terminal found. Run `stupify` once in an interactive terminal to set up the model.",
151
+ );
152
+ }
153
+
154
+ const input = createReadStream("/dev/tty");
155
+ const output = createWriteStream("/dev/tty");
156
+ return {
157
+ input,
158
+ output,
159
+ close: () => closePromptIo(input, output),
160
+ };
161
+ }
162
+
163
+ function closePromptIo(input: ReadStream, output: WriteStream): void {
164
+ input.destroy();
165
+ output.end();
166
+ }
167
+
168
+ function silentSpinner(): SpinnerResult {
169
+ return {
170
+ start: () => undefined,
171
+ stop: () => undefined,
172
+ cancel: () => undefined,
173
+ error: () => undefined,
174
+ message: () => undefined,
175
+ clear: () => undefined,
176
+ get isCancelled() {
177
+ return false;
178
+ },
179
+ };
180
+ }
181
+
182
+ function silentProgress() {
183
+ return {
184
+ ...silentSpinner(),
185
+ advance: () => undefined,
186
+ };
187
+ }