@breadc/tui 1.0.0-beta.3 → 1.0.0-beta.5
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/dist/index.d.mts +135 -5
- package/dist/index.mjs +350 -4
- package/package.json +2 -2
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +1,137 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { Writable } from "node:stream";
|
|
2
|
+
|
|
3
|
+
//#region src/chat/types.d.ts
|
|
4
|
+
type AnyState = Record<string, unknown>;
|
|
5
|
+
type LogLevel = 'log' | 'info' | 'warn' | 'error';
|
|
6
|
+
interface LogEntry {
|
|
7
|
+
level: LogLevel;
|
|
8
|
+
tag?: string;
|
|
9
|
+
message: string;
|
|
10
|
+
createdAt: Date;
|
|
11
|
+
}
|
|
12
|
+
interface OutputStream {
|
|
13
|
+
write(chunk: string): boolean;
|
|
14
|
+
isTTY?: boolean;
|
|
15
|
+
}
|
|
3
16
|
//#endregion
|
|
4
|
-
//#region src/
|
|
5
|
-
|
|
17
|
+
//#region src/chat/widget.d.ts
|
|
18
|
+
interface RenderContext<S extends AnyState = AnyState> {
|
|
19
|
+
tick: number;
|
|
20
|
+
state: S;
|
|
21
|
+
fields: AnyState;
|
|
22
|
+
}
|
|
23
|
+
type WidgetTemplate<S extends AnyState = AnyState> = string | string[] | ((ctx: RenderContext<S>) => string | string[]);
|
|
24
|
+
type WidgetFieldResolver<S extends AnyState = AnyState> = (ctx: RenderContext<S>) => unknown;
|
|
25
|
+
type WidgetFields<S extends AnyState = AnyState> = Record<string, WidgetFieldResolver<S>>;
|
|
26
|
+
interface WidgetSpec<S extends AnyState = AnyState> {
|
|
27
|
+
state: S;
|
|
28
|
+
template: WidgetTemplate<S>;
|
|
29
|
+
fields?: WidgetFields<S>;
|
|
30
|
+
}
|
|
31
|
+
interface WidgetHandle<S extends AnyState = AnyState> {
|
|
32
|
+
readonly id: string;
|
|
33
|
+
setState(next: Partial<S> | ((previous: S) => Partial<S> | S)): WidgetHandle<S>;
|
|
34
|
+
setTemplate(template: WidgetTemplate<S>): WidgetHandle<S>;
|
|
35
|
+
setFields(fields: WidgetFields<S>): WidgetHandle<S>;
|
|
36
|
+
remove(): void;
|
|
37
|
+
}
|
|
38
|
+
interface SpinnerWidgetState {
|
|
39
|
+
message: string;
|
|
40
|
+
}
|
|
41
|
+
interface SpinnerWidgetOptions<S extends AnyState = AnyState> {
|
|
42
|
+
frames?: string[];
|
|
43
|
+
template?: WidgetTemplate<SpinnerWidgetState & S>;
|
|
44
|
+
state?: S;
|
|
45
|
+
fields?: WidgetFields<SpinnerWidgetState & S>;
|
|
46
|
+
}
|
|
47
|
+
interface ProgressWidgetState {
|
|
48
|
+
message: string;
|
|
49
|
+
value: number;
|
|
50
|
+
total: number;
|
|
51
|
+
}
|
|
52
|
+
interface ProgressWidgetOptions<S extends AnyState = AnyState> {
|
|
53
|
+
value?: number;
|
|
54
|
+
total?: number;
|
|
55
|
+
width?: number;
|
|
56
|
+
complete?: string;
|
|
57
|
+
incomplete?: string;
|
|
58
|
+
template?: WidgetTemplate<ProgressWidgetState & S>;
|
|
59
|
+
state?: S;
|
|
60
|
+
fields?: WidgetFields<ProgressWidgetState & S>;
|
|
61
|
+
}
|
|
6
62
|
//#endregion
|
|
7
|
-
|
|
63
|
+
//#region src/chat/renderer.d.ts
|
|
64
|
+
interface RendererOptions {
|
|
65
|
+
stream: OutputStream;
|
|
66
|
+
isTTY: boolean;
|
|
67
|
+
tickInterval: number;
|
|
68
|
+
nonTTYInterval: number;
|
|
69
|
+
}
|
|
70
|
+
declare class Renderer {
|
|
71
|
+
private readonly stream;
|
|
72
|
+
private readonly isTTY;
|
|
73
|
+
private readonly tickInterval;
|
|
74
|
+
private readonly nonTTYInterval;
|
|
75
|
+
private readonly widgets;
|
|
76
|
+
private tick;
|
|
77
|
+
private disposed;
|
|
78
|
+
private idCounter;
|
|
79
|
+
private prevBottomCount;
|
|
80
|
+
private scheduled;
|
|
81
|
+
private queuedForce;
|
|
82
|
+
private ticker;
|
|
83
|
+
private lastNonTTYRender;
|
|
84
|
+
constructor(options: RendererOptions);
|
|
85
|
+
writeAboveBottom(line: string): void;
|
|
86
|
+
createWidget<S extends AnyState>(spec: WidgetSpec<S>): WidgetHandle<S>;
|
|
87
|
+
createSpinnerWidget<S extends AnyState = AnyState>(message: string, options?: SpinnerWidgetOptions<S>): WidgetHandle<SpinnerWidgetState & S>;
|
|
88
|
+
createProgressWidget<S extends AnyState = AnyState>(message: string, options?: ProgressWidgetOptions<S>): WidgetHandle<ProgressWidgetState & S>;
|
|
89
|
+
render(force?: boolean): void;
|
|
90
|
+
clearBottom(): void;
|
|
91
|
+
dispose(): void;
|
|
92
|
+
private createId;
|
|
93
|
+
private stopTicker;
|
|
94
|
+
private ensureTicker;
|
|
95
|
+
private clearBottomTTY;
|
|
96
|
+
private renderWidgets;
|
|
97
|
+
private drawBottomTTY;
|
|
98
|
+
private drawBottomNonTTY;
|
|
99
|
+
private scheduleRender;
|
|
100
|
+
}
|
|
101
|
+
//#endregion
|
|
102
|
+
//#region src/chat/log.d.ts
|
|
103
|
+
interface LogFormatterOptions {
|
|
104
|
+
tag?: string;
|
|
105
|
+
columns: number;
|
|
106
|
+
isTTY: boolean;
|
|
107
|
+
}
|
|
108
|
+
//#endregion
|
|
109
|
+
//#region src/chat/chat.d.ts
|
|
110
|
+
interface ChatOptions {
|
|
111
|
+
renderer?: Renderer;
|
|
112
|
+
stream?: Writable & {
|
|
113
|
+
isTTY?: boolean;
|
|
114
|
+
columns?: number;
|
|
115
|
+
};
|
|
116
|
+
tickInterval?: number;
|
|
117
|
+
nonTTYInterval?: number;
|
|
118
|
+
log?: {
|
|
119
|
+
tag?: string;
|
|
120
|
+
format?: (entry: LogEntry, options: LogFormatterOptions) => string;
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
interface Chat {
|
|
124
|
+
log(...args: unknown[]): void;
|
|
125
|
+
info(...args: unknown[]): void;
|
|
126
|
+
warn(...args: unknown[]): void;
|
|
127
|
+
error(...args: unknown[]): void;
|
|
128
|
+
widget<S extends AnyState>(spec: WidgetSpec<S>): WidgetHandle<S>;
|
|
129
|
+
spinner<S extends AnyState = AnyState>(message: string, options?: SpinnerWidgetOptions<S>): WidgetHandle<SpinnerWidgetState & S>;
|
|
130
|
+
progress<S extends AnyState = AnyState>(message: string, options?: ProgressWidgetOptions<S>): WidgetHandle<ProgressWidgetState & S>;
|
|
131
|
+
render(force?: boolean): void;
|
|
132
|
+
clearBottom(): void;
|
|
133
|
+
dispose(): void;
|
|
134
|
+
}
|
|
135
|
+
declare function chat(options?: ChatOptions): Chat;
|
|
136
|
+
//#endregion
|
|
137
|
+
export { AnyState, LogEntry, LogLevel, OutputStream, chat };
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,353 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { format } from "node:util";
|
|
2
|
+
import readline from "node:readline";
|
|
3
|
+
import { cyan, gray, red, yellow } from "@breadc/color";
|
|
4
|
+
|
|
5
|
+
//#region src/chat/helpers.ts
|
|
6
|
+
function renderProgressBar(value, total, barOptions) {
|
|
7
|
+
const width = Math.max(1, barOptions.width);
|
|
8
|
+
const complete = barOptions.complete ?? "=";
|
|
9
|
+
const incomplete = barOptions.incomplete ?? "-";
|
|
10
|
+
const ratio = total > 0 ? clamp(value / total, 0, 1) : 0;
|
|
11
|
+
const completeCount = Math.round(ratio * width);
|
|
12
|
+
return complete.repeat(completeCount) + incomplete.repeat(width - completeCount);
|
|
13
|
+
}
|
|
14
|
+
function renderPercent(value, total) {
|
|
15
|
+
if (total <= 0) return 0;
|
|
16
|
+
return Math.floor(clamp(value / total * 100, 0, 100));
|
|
17
|
+
}
|
|
18
|
+
function renderTemplateLines(template, context, resolvedValues) {
|
|
19
|
+
const rawTemplate = typeof template === "function" ? template(context) : template;
|
|
20
|
+
const templateLines = Array.isArray(rawTemplate) ? rawTemplate : [rawTemplate];
|
|
21
|
+
const lines = [];
|
|
22
|
+
for (const line of templateLines) {
|
|
23
|
+
const text = line.replace(/\{([a-zA-Z0-9_]+)\}/g, (_, key) => {
|
|
24
|
+
const value = resolvedValues[key];
|
|
25
|
+
return stringifyValue(value);
|
|
26
|
+
});
|
|
27
|
+
for (const chunk of text.split(/\r?\n/g)) lines.push(chunk);
|
|
28
|
+
}
|
|
29
|
+
return lines;
|
|
30
|
+
}
|
|
31
|
+
function stringifyValue(value) {
|
|
32
|
+
if (value === void 0 || value === null) return "";
|
|
33
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return String(value);
|
|
34
|
+
try {
|
|
35
|
+
return JSON.stringify(value);
|
|
36
|
+
} catch {
|
|
37
|
+
return String(value);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function numericOrDefault(value, fallback) {
|
|
41
|
+
const parsed = Number(value);
|
|
42
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
43
|
+
}
|
|
44
|
+
function normalizeProgressValue(value, total) {
|
|
45
|
+
if (total <= 0) return 0;
|
|
46
|
+
return clamp(value, 0, total);
|
|
47
|
+
}
|
|
48
|
+
function clamp(value, min, max) {
|
|
49
|
+
return Math.min(Math.max(value, min), max);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/chat/renderer.ts
|
|
54
|
+
const DEFAULT_SPINNER_FRAMES = [
|
|
55
|
+
"-",
|
|
56
|
+
"\\",
|
|
57
|
+
"|",
|
|
58
|
+
"/"
|
|
59
|
+
];
|
|
60
|
+
const DEFAULT_PROGRESS_WIDTH = 24;
|
|
61
|
+
var Renderer = class {
|
|
62
|
+
constructor(options) {
|
|
63
|
+
this.widgets = [];
|
|
64
|
+
this.tick = 0;
|
|
65
|
+
this.disposed = false;
|
|
66
|
+
this.idCounter = 0;
|
|
67
|
+
this.prevBottomCount = 0;
|
|
68
|
+
this.scheduled = false;
|
|
69
|
+
this.queuedForce = false;
|
|
70
|
+
this.lastNonTTYRender = 0;
|
|
71
|
+
this.stream = options.stream;
|
|
72
|
+
this.isTTY = options.isTTY;
|
|
73
|
+
this.tickInterval = options.tickInterval;
|
|
74
|
+
this.nonTTYInterval = options.nonTTYInterval;
|
|
75
|
+
}
|
|
76
|
+
writeAboveBottom(line) {
|
|
77
|
+
if (this.disposed) return;
|
|
78
|
+
if (this.isTTY) this.clearBottomTTY();
|
|
79
|
+
this.stream.write(`${line}\n`);
|
|
80
|
+
if (this.isTTY) this.drawBottomTTY();
|
|
81
|
+
}
|
|
82
|
+
createWidget(spec) {
|
|
83
|
+
const widget = {
|
|
84
|
+
id: this.createId(),
|
|
85
|
+
state: { ...spec.state },
|
|
86
|
+
template: spec.template,
|
|
87
|
+
fields: { ...spec.fields ?? {} }
|
|
88
|
+
};
|
|
89
|
+
this.widgets.push(widget);
|
|
90
|
+
this.ensureTicker();
|
|
91
|
+
this.scheduleRender(true);
|
|
92
|
+
const handle = {
|
|
93
|
+
id: widget.id,
|
|
94
|
+
setState: (next) => {
|
|
95
|
+
if (this.disposed || !this.widgets.includes(widget)) return handle;
|
|
96
|
+
const patch = typeof next === "function" ? next({ ...widget.state }) : next;
|
|
97
|
+
widget.state = {
|
|
98
|
+
...widget.state,
|
|
99
|
+
...patch
|
|
100
|
+
};
|
|
101
|
+
this.scheduleRender(false);
|
|
102
|
+
return handle;
|
|
103
|
+
},
|
|
104
|
+
setTemplate: (template) => {
|
|
105
|
+
if (this.disposed || !this.widgets.includes(widget)) return handle;
|
|
106
|
+
widget.template = template;
|
|
107
|
+
this.scheduleRender(false);
|
|
108
|
+
return handle;
|
|
109
|
+
},
|
|
110
|
+
setFields: (fields) => {
|
|
111
|
+
if (this.disposed || !this.widgets.includes(widget)) return handle;
|
|
112
|
+
widget.fields = {
|
|
113
|
+
...widget.fields,
|
|
114
|
+
...fields
|
|
115
|
+
};
|
|
116
|
+
this.scheduleRender(false);
|
|
117
|
+
return handle;
|
|
118
|
+
},
|
|
119
|
+
remove: () => {
|
|
120
|
+
if (this.disposed) return;
|
|
121
|
+
const index = this.widgets.indexOf(widget);
|
|
122
|
+
if (index === -1) return;
|
|
123
|
+
this.widgets.splice(index, 1);
|
|
124
|
+
if (this.widgets.length === 0) this.stopTicker();
|
|
125
|
+
this.scheduleRender(true);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
return handle;
|
|
129
|
+
}
|
|
130
|
+
createSpinnerWidget(message, options = {}) {
|
|
131
|
+
const frames = options.frames?.length ? options.frames : DEFAULT_SPINNER_FRAMES;
|
|
132
|
+
const template = options.template ?? "{frame} {message}";
|
|
133
|
+
const fields = {
|
|
134
|
+
frame: (ctx) => {
|
|
135
|
+
if (frames.length === 0) return "";
|
|
136
|
+
return frames[ctx.tick % frames.length];
|
|
137
|
+
},
|
|
138
|
+
...options.fields ?? {}
|
|
139
|
+
};
|
|
140
|
+
return this.createWidget({
|
|
141
|
+
state: {
|
|
142
|
+
message,
|
|
143
|
+
...options.state ?? {}
|
|
144
|
+
},
|
|
145
|
+
template,
|
|
146
|
+
fields
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
createProgressWidget(message, options = {}) {
|
|
150
|
+
const width = Math.max(1, options.width || DEFAULT_PROGRESS_WIDTH);
|
|
151
|
+
const complete = options.complete ?? "█";
|
|
152
|
+
const incomplete = options.incomplete ?? "░";
|
|
153
|
+
const template = options.template ?? "{message} [{bar}] {percent}% {value}/{total}";
|
|
154
|
+
const fields = {
|
|
155
|
+
bar: (ctx) => {
|
|
156
|
+
const total = numericOrDefault(ctx.state.total, 0);
|
|
157
|
+
return renderProgressBar(normalizeProgressValue(numericOrDefault(ctx.state.value, 0), total), total, {
|
|
158
|
+
width,
|
|
159
|
+
complete,
|
|
160
|
+
incomplete
|
|
161
|
+
});
|
|
162
|
+
},
|
|
163
|
+
percent: (ctx) => {
|
|
164
|
+
const total = numericOrDefault(ctx.state.total, 0);
|
|
165
|
+
return renderPercent(normalizeProgressValue(numericOrDefault(ctx.state.value, 0), total), total);
|
|
166
|
+
},
|
|
167
|
+
...options.fields ?? {}
|
|
168
|
+
};
|
|
169
|
+
return this.createWidget({
|
|
170
|
+
state: {
|
|
171
|
+
message,
|
|
172
|
+
value: options.value ?? 0,
|
|
173
|
+
total: options.total ?? 100,
|
|
174
|
+
...options.state ?? {}
|
|
175
|
+
},
|
|
176
|
+
template,
|
|
177
|
+
fields
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
render(force = false) {
|
|
181
|
+
if (this.disposed) return;
|
|
182
|
+
if (this.isTTY) {
|
|
183
|
+
this.clearBottomTTY();
|
|
184
|
+
this.drawBottomTTY();
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
this.drawBottomNonTTY(force);
|
|
188
|
+
}
|
|
189
|
+
clearBottom() {
|
|
190
|
+
if (this.disposed || !this.isTTY) return;
|
|
191
|
+
this.clearBottomTTY();
|
|
192
|
+
}
|
|
193
|
+
dispose() {
|
|
194
|
+
if (this.disposed) return;
|
|
195
|
+
this.disposed = true;
|
|
196
|
+
this.stopTicker();
|
|
197
|
+
this.widgets.length = 0;
|
|
198
|
+
if (this.isTTY) this.clearBottomTTY();
|
|
199
|
+
}
|
|
200
|
+
createId() {
|
|
201
|
+
this.idCounter += 1;
|
|
202
|
+
return `widget-${this.idCounter}`;
|
|
203
|
+
}
|
|
204
|
+
stopTicker() {
|
|
205
|
+
if (!this.ticker) return;
|
|
206
|
+
clearInterval(this.ticker);
|
|
207
|
+
this.ticker = void 0;
|
|
208
|
+
}
|
|
209
|
+
ensureTicker() {
|
|
210
|
+
if (this.ticker || this.disposed || this.widgets.length === 0) return;
|
|
211
|
+
this.ticker = setInterval(() => {
|
|
212
|
+
if (this.disposed || this.widgets.length === 0) {
|
|
213
|
+
this.stopTicker();
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
this.tick += 1;
|
|
217
|
+
this.scheduleRender(false);
|
|
218
|
+
}, this.tickInterval);
|
|
219
|
+
}
|
|
220
|
+
clearBottomTTY() {
|
|
221
|
+
if (!this.isTTY || this.prevBottomCount === 0) return;
|
|
222
|
+
const output = this.stream;
|
|
223
|
+
if (this.prevBottomCount > 1) readline.moveCursor(output, 0, -(this.prevBottomCount - 1));
|
|
224
|
+
readline.cursorTo(output, 0);
|
|
225
|
+
readline.clearScreenDown(output);
|
|
226
|
+
this.prevBottomCount = 0;
|
|
227
|
+
}
|
|
228
|
+
renderWidgets() {
|
|
229
|
+
const lines = [];
|
|
230
|
+
for (const widget of this.widgets) {
|
|
231
|
+
const context = {
|
|
232
|
+
tick: this.tick,
|
|
233
|
+
state: widget.state,
|
|
234
|
+
fields: {}
|
|
235
|
+
};
|
|
236
|
+
const resolvedValues = {
|
|
237
|
+
...widget.state,
|
|
238
|
+
tick: this.tick
|
|
239
|
+
};
|
|
240
|
+
for (const [key, resolver] of Object.entries(widget.fields)) resolvedValues[key] = resolver(context);
|
|
241
|
+
lines.push(...renderTemplateLines(widget.template, context, resolvedValues));
|
|
242
|
+
}
|
|
243
|
+
return lines;
|
|
244
|
+
}
|
|
245
|
+
drawBottomTTY() {
|
|
246
|
+
const lines = this.renderWidgets();
|
|
247
|
+
if (lines.length === 0) {
|
|
248
|
+
this.prevBottomCount = 0;
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
252
|
+
this.stream.write(lines[i]);
|
|
253
|
+
if (i < lines.length - 1) this.stream.write("\n");
|
|
254
|
+
}
|
|
255
|
+
this.prevBottomCount = lines.length;
|
|
256
|
+
}
|
|
257
|
+
drawBottomNonTTY(force) {
|
|
258
|
+
const now = Date.now();
|
|
259
|
+
if (!force && now - this.lastNonTTYRender < this.nonTTYInterval) return;
|
|
260
|
+
const lines = this.renderWidgets();
|
|
261
|
+
if (lines.length === 0) return;
|
|
262
|
+
for (const line of lines) this.stream.write(`${line}\n`);
|
|
263
|
+
this.lastNonTTYRender = now;
|
|
264
|
+
}
|
|
265
|
+
scheduleRender(force = false) {
|
|
266
|
+
if (this.disposed) return;
|
|
267
|
+
this.queuedForce = this.queuedForce || force;
|
|
268
|
+
if (this.scheduled) return;
|
|
269
|
+
this.scheduled = true;
|
|
270
|
+
queueMicrotask(() => {
|
|
271
|
+
this.scheduled = false;
|
|
272
|
+
const shouldForce = this.queuedForce;
|
|
273
|
+
this.queuedForce = false;
|
|
274
|
+
this.render(shouldForce);
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
//#endregion
|
|
280
|
+
//#region src/chat/log.ts
|
|
281
|
+
const LOG_LEVEL_PREFIX = {
|
|
282
|
+
log: "LOG",
|
|
283
|
+
info: "INFO",
|
|
284
|
+
warn: "WARN",
|
|
285
|
+
error: "ERROR"
|
|
286
|
+
};
|
|
287
|
+
const LOG_LEVEL_COLOR = {
|
|
288
|
+
log: gray,
|
|
289
|
+
info: cyan,
|
|
290
|
+
warn: yellow,
|
|
291
|
+
error: red
|
|
292
|
+
};
|
|
293
|
+
function defaultLogFormatter(entry, options) {
|
|
294
|
+
if (options.isTTY) {
|
|
295
|
+
if (entry.level === "log") return entry.message;
|
|
296
|
+
const color = LOG_LEVEL_COLOR[entry.level];
|
|
297
|
+
return `${color(`[${LOG_LEVEL_PREFIX[entry.level]}]`)} ${entry.message}`;
|
|
298
|
+
} else return `[${LOG_LEVEL_PREFIX[entry.level]}] ${entry.message}`;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
//#endregion
|
|
302
|
+
//#region src/chat/chat.ts
|
|
303
|
+
const DEFAULT_TICK_INTERVAL = 80;
|
|
304
|
+
const DEFAULT_NON_TTY_INTERVAL = 1e3;
|
|
305
|
+
function chat(options = {}) {
|
|
306
|
+
const stream = options.stream ?? process.stderr;
|
|
307
|
+
const isTTY = !!stream.isTTY;
|
|
308
|
+
const tickInterval = Math.max(1, options.tickInterval ?? DEFAULT_TICK_INTERVAL);
|
|
309
|
+
const nonTTYInterval = Math.max(0, options.nonTTYInterval ?? DEFAULT_NON_TTY_INTERVAL);
|
|
310
|
+
const renderer = options.renderer ?? new Renderer({
|
|
311
|
+
stream,
|
|
312
|
+
isTTY,
|
|
313
|
+
tickInterval,
|
|
314
|
+
nonTTYInterval
|
|
315
|
+
});
|
|
316
|
+
const writeLog = (level, args) => {
|
|
317
|
+
const entry = {
|
|
318
|
+
level,
|
|
319
|
+
message: format(...args),
|
|
320
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
321
|
+
};
|
|
322
|
+
const opt = {
|
|
323
|
+
tag: options.log?.tag,
|
|
324
|
+
columns: stream.columns || 80,
|
|
325
|
+
isTTY: stream.isTTY || false
|
|
326
|
+
};
|
|
327
|
+
const line = options.log?.format ? options.log.format(entry, opt) : defaultLogFormatter(entry, opt);
|
|
328
|
+
renderer.writeAboveBottom(line);
|
|
329
|
+
};
|
|
330
|
+
return {
|
|
331
|
+
log(...args) {
|
|
332
|
+
writeLog("log", args);
|
|
333
|
+
},
|
|
334
|
+
info(...args) {
|
|
335
|
+
writeLog("info", args);
|
|
336
|
+
},
|
|
337
|
+
warn(...args) {
|
|
338
|
+
writeLog("warn", args);
|
|
339
|
+
},
|
|
340
|
+
error(...args) {
|
|
341
|
+
writeLog("error", args);
|
|
342
|
+
},
|
|
343
|
+
widget: (spec) => renderer.createWidget(spec),
|
|
344
|
+
spinner: (message, spinnerOptions) => renderer.createSpinnerWidget(message, spinnerOptions),
|
|
345
|
+
progress: (message, progressOptions) => renderer.createProgressWidget(message, progressOptions),
|
|
346
|
+
render: (force) => renderer.render(force),
|
|
347
|
+
clearBottom: () => renderer.clearBottom(),
|
|
348
|
+
dispose: () => renderer.dispose()
|
|
349
|
+
};
|
|
4
350
|
}
|
|
5
351
|
|
|
6
352
|
//#endregion
|
|
7
|
-
export {
|
|
353
|
+
export { chat };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@breadc/tui",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.5",
|
|
4
4
|
"description": "TUI for Breadc",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"breadc",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"std-env": "^3.10.0",
|
|
36
36
|
"string-width": "^8.1.1",
|
|
37
|
-
"@breadc/color": "1.0.0-beta.
|
|
37
|
+
"@breadc/color": "1.0.0-beta.5"
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
|
40
40
|
"build": "tsdown",
|