@remotedraw/cli 0.1.2 → 0.2.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/LICENSE +21 -0
- package/README.md +79 -2
- package/dist/cli.d.ts +15 -4
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1212 -759
- package/dist/cloud.d.ts +13 -0
- package/dist/cloud.d.ts.map +1 -1
- package/dist/cloud.js +61 -14
- package/dist/errors.d.ts +16 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +13 -0
- package/dist/generated/agent-skill.d.ts +2 -0
- package/dist/generated/agent-skill.d.ts.map +1 -0
- package/dist/generated/agent-skill.js +4 -0
- package/dist/i18n.d.ts +74 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/i18n.js +178 -0
- package/dist/index.js +35 -2
- package/dist/locales/catalog.d.ts +21 -0
- package/dist/locales/catalog.d.ts.map +1 -0
- package/dist/locales/catalog.js +1 -0
- package/dist/locales/en.d.ts +420 -0
- package/dist/locales/en.d.ts.map +1 -0
- package/dist/locales/en.js +446 -0
- package/dist/locales/index.d.ts +436 -0
- package/dist/locales/index.d.ts.map +1 -0
- package/dist/locales/index.js +27 -0
- package/dist/locales/nl.d.ts +10 -0
- package/dist/locales/nl.d.ts.map +1 -0
- package/dist/locales/nl.js +438 -0
- package/dist/setup-wizard.d.ts +10 -2
- package/dist/setup-wizard.d.ts.map +1 -1
- package/dist/setup-wizard.js +593 -102
- package/dist/telemetry.d.ts +73 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +145 -0
- package/dist/update.d.ts +53 -0
- package/dist/update.d.ts.map +1 -0
- package/dist/update.js +240 -0
- package/package.json +11 -4
package/dist/setup-wizard.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { emitKeypressEvents } from "node:readline";
|
|
3
|
+
import { t } from "./i18n.js";
|
|
3
4
|
export function initialWizardState(definition) {
|
|
4
5
|
return {
|
|
5
6
|
screen: "field",
|
|
@@ -118,6 +119,17 @@ export function transitionWizard(state, action, definition) {
|
|
|
118
119
|
}
|
|
119
120
|
return state;
|
|
120
121
|
}
|
|
122
|
+
function completedSteps(state, definition) {
|
|
123
|
+
return definition.fields
|
|
124
|
+
.slice(0, state.fieldIndex)
|
|
125
|
+
.map((field) => {
|
|
126
|
+
const value = state.values[field.key];
|
|
127
|
+
const label = field
|
|
128
|
+
.choices?.(state.values)
|
|
129
|
+
.find((option) => option.value === value)?.label;
|
|
130
|
+
return [field.label, label ?? value];
|
|
131
|
+
});
|
|
132
|
+
}
|
|
121
133
|
export function wizardSemanticView(state, definition) {
|
|
122
134
|
if (state.screen === "field") {
|
|
123
135
|
const field = activeField(state, definition);
|
|
@@ -125,7 +137,7 @@ export function wizardSemanticView(state, definition) {
|
|
|
125
137
|
const selectedChoice = choices?.find((choice) => choice.value === state.values[field.key]);
|
|
126
138
|
return {
|
|
127
139
|
screen: state.screen,
|
|
128
|
-
title: "
|
|
140
|
+
title: t("wizard.title.setup"),
|
|
129
141
|
progress: {
|
|
130
142
|
current: state.fieldIndex + 1,
|
|
131
143
|
total: definition.fields.length,
|
|
@@ -133,6 +145,8 @@ export function wizardSemanticView(state, definition) {
|
|
|
133
145
|
activeLabel: field.label,
|
|
134
146
|
value: state.values[field.key],
|
|
135
147
|
valueIsDefault: field.kind === "text" && !state.editedFields.includes(field.key),
|
|
148
|
+
valuePreview: field.kind === "text" ? field.preview?.(state.values) : undefined,
|
|
149
|
+
completed: completedSteps(state, definition),
|
|
136
150
|
choices,
|
|
137
151
|
selectedChoice: state.values[field.key],
|
|
138
152
|
selectedDetails: selectedChoice?.details,
|
|
@@ -140,54 +154,63 @@ export function wizardSemanticView(state, definition) {
|
|
|
140
154
|
controls: field.kind === "text"
|
|
141
155
|
? [
|
|
142
156
|
!state.editedFields.includes(field.key)
|
|
143
|
-
? "
|
|
144
|
-
: "
|
|
145
|
-
"
|
|
146
|
-
"
|
|
147
|
-
"
|
|
148
|
-
"
|
|
157
|
+
? t("wizard.hint.replaceDefault")
|
|
158
|
+
: t("wizard.hint.edit"),
|
|
159
|
+
t("wizard.control.steps"),
|
|
160
|
+
t("wizard.control.next"),
|
|
161
|
+
t("wizard.control.back"),
|
|
162
|
+
t("wizard.control.quitCtrlC"),
|
|
149
163
|
]
|
|
150
164
|
: [
|
|
151
|
-
"
|
|
152
|
-
...(selectedChoice?.details ? ["
|
|
153
|
-
"
|
|
154
|
-
"
|
|
155
|
-
"
|
|
156
|
-
"
|
|
165
|
+
t("wizard.control.choose"),
|
|
166
|
+
...(selectedChoice?.details ? [t("wizard.control.docs")] : []),
|
|
167
|
+
t("wizard.control.steps"),
|
|
168
|
+
t("wizard.control.next"),
|
|
169
|
+
t("wizard.control.back"),
|
|
170
|
+
t("wizard.control.quitQ"),
|
|
157
171
|
],
|
|
158
172
|
};
|
|
159
173
|
}
|
|
160
174
|
if (state.screen === "review") {
|
|
161
175
|
return {
|
|
162
176
|
screen: state.screen,
|
|
163
|
-
title: "
|
|
177
|
+
title: t("wizard.title.review"),
|
|
164
178
|
review: definition.review(state.values),
|
|
165
|
-
controls: [
|
|
179
|
+
controls: [
|
|
180
|
+
t("wizard.control.forward"),
|
|
181
|
+
t("wizard.control.backArrow"),
|
|
182
|
+
t("wizard.control.quitQ"),
|
|
183
|
+
],
|
|
166
184
|
};
|
|
167
185
|
}
|
|
168
186
|
if (state.screen === "confirm") {
|
|
169
187
|
return {
|
|
170
188
|
screen: state.screen,
|
|
171
|
-
title: "
|
|
189
|
+
title: t("wizard.title.confirm"),
|
|
190
|
+
review: definition.review(state.values),
|
|
172
191
|
choices: [
|
|
173
|
-
{ value: "no", label: "
|
|
174
|
-
{ value: "yes", label: "
|
|
192
|
+
{ value: "no", label: t("wizard.confirm.no") },
|
|
193
|
+
{ value: "yes", label: t("wizard.confirm.yes") },
|
|
175
194
|
],
|
|
176
195
|
selectedChoice: state.confirmCreate ? "yes" : "no",
|
|
177
|
-
controls: [
|
|
196
|
+
controls: [
|
|
197
|
+
t("wizard.control.choose"),
|
|
198
|
+
t("wizard.control.confirm"),
|
|
199
|
+
t("wizard.control.backArrow"),
|
|
200
|
+
],
|
|
178
201
|
};
|
|
179
202
|
}
|
|
180
203
|
return {
|
|
181
204
|
screen: state.screen,
|
|
182
205
|
title: state.screen === "executing"
|
|
183
|
-
? "
|
|
206
|
+
? t("wizard.title.executing")
|
|
184
207
|
: state.screen === "success"
|
|
185
|
-
? "
|
|
208
|
+
? t("wizard.title.success")
|
|
186
209
|
: state.screen === "error"
|
|
187
|
-
? "
|
|
210
|
+
? t("wizard.title.error")
|
|
188
211
|
: state.screen === "interrupted"
|
|
189
|
-
? "
|
|
190
|
-
: "
|
|
212
|
+
? t("wizard.title.interrupted")
|
|
213
|
+
: t("wizard.title.cancelled"),
|
|
191
214
|
controls: [],
|
|
192
215
|
createdPath: state.createdPath,
|
|
193
216
|
nextCommand: state.nextCommand,
|
|
@@ -196,39 +219,86 @@ export function wizardSemanticView(state, definition) {
|
|
|
196
219
|
}
|
|
197
220
|
export async function runSetupWizard(options) {
|
|
198
221
|
let state = initialWizardState(options.definition);
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
222
|
+
const animated = options.terminal.color;
|
|
223
|
+
// Monotonic so the wordmark, caret, and spinner never jump backwards.
|
|
224
|
+
let tick = IDLE_FRAME;
|
|
225
|
+
const paint = (frame) => {
|
|
226
|
+
options.terminal.write(renderWizard(state, options.definition, options.terminal, frame));
|
|
227
|
+
};
|
|
228
|
+
const play = async (frames, delay, from) => {
|
|
229
|
+
if (from != null)
|
|
230
|
+
tick = from;
|
|
231
|
+
for (let frame = 0; frame < frames; frame += 1) {
|
|
232
|
+
if (options.terminal.hasPendingKey?.() === true)
|
|
233
|
+
break;
|
|
234
|
+
paint(from != null ? tick + frame : (tick += 1));
|
|
235
|
+
if (frame < frames - 1)
|
|
236
|
+
await pause(delay);
|
|
237
|
+
}
|
|
238
|
+
if (from != null)
|
|
239
|
+
tick = from + frames - 1;
|
|
240
|
+
};
|
|
241
|
+
/** Waits for a key while keeping the frame alive, then goes quiet. */
|
|
242
|
+
const nextKey = async () => {
|
|
243
|
+
const readWithin = options.terminal.readKeyWithin;
|
|
244
|
+
if (animated && readWithin) {
|
|
245
|
+
for (let idle = 0; idle < IDLE_TICKS; idle += 1) {
|
|
246
|
+
const key = await readWithin(IDLE_TICK_MS);
|
|
247
|
+
if (key)
|
|
248
|
+
return key;
|
|
249
|
+
tick += 1;
|
|
250
|
+
paint(tick);
|
|
207
251
|
}
|
|
208
252
|
}
|
|
253
|
+
return await options.terminal.readKey();
|
|
254
|
+
};
|
|
255
|
+
try {
|
|
256
|
+
await options.terminal.enter();
|
|
257
|
+
if (animated)
|
|
258
|
+
await play(INTRO_FRAMES, INTRO_FRAME_MS, 0);
|
|
259
|
+
let previousStep = stepSignature(state);
|
|
209
260
|
while (true) {
|
|
210
|
-
|
|
261
|
+
const currentStep = stepSignature(state);
|
|
262
|
+
if (animated && currentStep !== previousStep) {
|
|
263
|
+
await play(STEP_FRAMES, STEP_FRAME_MS);
|
|
264
|
+
}
|
|
265
|
+
previousStep = currentStep;
|
|
266
|
+
paint(tick);
|
|
211
267
|
if (state.screen === "executing") {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
268
|
+
let running = true;
|
|
269
|
+
const execution = options
|
|
270
|
+
.execute(state.values)
|
|
271
|
+
.then((result) => ({ ok: true, result }), (error) => ({ ok: false, error }))
|
|
272
|
+
.finally(() => {
|
|
273
|
+
running = false;
|
|
274
|
+
});
|
|
275
|
+
if (animated) {
|
|
276
|
+
while (running) {
|
|
277
|
+
tick += 1;
|
|
278
|
+
paint(tick);
|
|
279
|
+
await pause(SPINNER_FRAME_MS);
|
|
280
|
+
}
|
|
215
281
|
}
|
|
216
|
-
|
|
217
|
-
|
|
282
|
+
const outcome = await execution;
|
|
283
|
+
state = outcome.ok
|
|
284
|
+
? transitionWizard(state, { type: "executionSucceeded", ...outcome.result }, options.definition)
|
|
285
|
+
: transitionWizard(state, {
|
|
218
286
|
type: "executionFailed",
|
|
219
|
-
message: `${error instanceof Error ? error.message : String(error)}\nControleer de doelmap en de toegangsrechten.`,
|
|
287
|
+
message: `${outcome.error instanceof Error ? outcome.error.message : String(outcome.error)}\nControleer de doelmap en de toegangsrechten.`,
|
|
220
288
|
}, options.definition);
|
|
221
|
-
}
|
|
222
289
|
continue;
|
|
223
290
|
}
|
|
224
291
|
if (state.screen === "success" || state.screen === "cancelled") {
|
|
292
|
+
if (animated && state.screen === "success") {
|
|
293
|
+
await play(SUCCESS_FRAMES, SUCCESS_FRAME_MS);
|
|
294
|
+
}
|
|
225
295
|
return { exitCode: 0, state };
|
|
226
296
|
}
|
|
227
297
|
if (state.screen === "error")
|
|
228
298
|
return { exitCode: 1, state };
|
|
229
299
|
if (state.screen === "interrupted")
|
|
230
300
|
return { exitCode: 130, state };
|
|
231
|
-
const key = await
|
|
301
|
+
const key = await nextKey();
|
|
232
302
|
const selectedDetails = wizardSemanticView(state, options.definition).selectedDetails;
|
|
233
303
|
if (key.name === "d" && selectedDetails) {
|
|
234
304
|
if (!options.terminal.openUrl) {
|
|
@@ -244,7 +314,7 @@ export async function runSetupWizard(options) {
|
|
|
244
314
|
catch {
|
|
245
315
|
state = {
|
|
246
316
|
...state,
|
|
247
|
-
error:
|
|
317
|
+
error: t("wizard.docs.openFailed", { url: selectedDetails.docsUrl }),
|
|
248
318
|
};
|
|
249
319
|
}
|
|
250
320
|
continue;
|
|
@@ -287,8 +357,29 @@ export function actionForKey(key, state) {
|
|
|
287
357
|
}
|
|
288
358
|
return { type: "character", value: "" };
|
|
289
359
|
}
|
|
290
|
-
|
|
291
|
-
|
|
360
|
+
const INK = {
|
|
361
|
+
accent: { rgb: [56, 189, 248], code: "36" },
|
|
362
|
+
glow: { rgb: [186, 230, 253], code: "96" },
|
|
363
|
+
brand: { rgb: [129, 140, 248], code: "94" },
|
|
364
|
+
muted: { rgb: [124, 134, 150], code: "2" },
|
|
365
|
+
success: { rgb: [52, 211, 153], code: "32" },
|
|
366
|
+
danger: { rgb: [248, 113, 113], code: "31" },
|
|
367
|
+
warn: { rgb: [251, 191, 36], code: "33" },
|
|
368
|
+
};
|
|
369
|
+
function ink(terminal, tone, value, heavy = false) {
|
|
370
|
+
if (!terminal.color || value.length === 0)
|
|
371
|
+
return value;
|
|
372
|
+
const entry = INK[tone];
|
|
373
|
+
const sequence = terminal.trueColor
|
|
374
|
+
? truecolor(entry.rgb)
|
|
375
|
+
: `\x1b[${entry.code}m`;
|
|
376
|
+
return `${heavy ? "\x1b[1m" : ""}${sequence}${value}\x1b[0m`;
|
|
377
|
+
}
|
|
378
|
+
function strong(terminal, value) {
|
|
379
|
+
return terminal.color && value.length > 0 ? `\x1b[1m${value}\x1b[0m` : value;
|
|
380
|
+
}
|
|
381
|
+
function truecolor(rgb) {
|
|
382
|
+
return `\x1b[38;2;${rgb[0]};${rgb[1]};${rgb[2]}m`;
|
|
292
383
|
}
|
|
293
384
|
function hyperlink(enabled, url, label) {
|
|
294
385
|
return enabled ? `\x1b]8;;${url}\x1b\\${label}\x1b]8;;\x1b\\` : label;
|
|
@@ -296,7 +387,7 @@ function hyperlink(enabled, url, label) {
|
|
|
296
387
|
async function openExternalUrl(url) {
|
|
297
388
|
const parsed = new URL(url);
|
|
298
389
|
if (parsed.protocol !== "https:") {
|
|
299
|
-
throw new Error("
|
|
390
|
+
throw new Error(t("error.httpsDocsOnly"));
|
|
300
391
|
}
|
|
301
392
|
const [command, args] = process.platform === "darwin"
|
|
302
393
|
? ["open", [url]]
|
|
@@ -346,102 +437,468 @@ function wrapText(value, width) {
|
|
|
346
437
|
lines.push(line);
|
|
347
438
|
return lines;
|
|
348
439
|
}
|
|
440
|
+
function cardLines(terminal, title, rows, width) {
|
|
441
|
+
const border = (value) => ink(terminal, "accent", value);
|
|
442
|
+
const header = `─ ${title} `;
|
|
443
|
+
const lines = [
|
|
444
|
+
`${border("╭")}${ink(terminal, "glow", header, true)}${border("─".repeat(Math.max(0, width + 2 - header.length)))}${border("╮")}`,
|
|
445
|
+
];
|
|
446
|
+
for (const row of rows) {
|
|
447
|
+
const fitted = row.text.length > width
|
|
448
|
+
? { text: `${row.text.slice(0, Math.max(1, width - 1))}…` }
|
|
449
|
+
: row;
|
|
450
|
+
lines.push(`${border("│")} ${fitted.rendered ?? fitted.text}${" ".repeat(Math.max(0, width - fitted.text.length))} ${border("│")}`);
|
|
451
|
+
}
|
|
452
|
+
lines.push(`${border("╰")}${border("─".repeat(width + 2))}${border("╯")}`);
|
|
453
|
+
return lines;
|
|
454
|
+
}
|
|
349
455
|
function renderChoiceDetails(details, terminal) {
|
|
350
456
|
const width = Math.max(20, Math.min(72, terminal.columns - 4));
|
|
351
|
-
const border = (value) => paint(terminal.color, "36", value);
|
|
352
|
-
const contentLine = (value = "", rendered = value) => `${border("│")} ${rendered}${" ".repeat(Math.max(0, width - value.length))} ${border("│")}`;
|
|
353
457
|
const button = `[ ${details.docsLabel} ↗ ]`;
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
458
|
+
const rows = [
|
|
459
|
+
...wrapText(details.summary, width).map((line) => ({
|
|
460
|
+
text: line,
|
|
461
|
+
rendered: strong(terminal, line),
|
|
462
|
+
})),
|
|
463
|
+
{ text: "" },
|
|
464
|
+
...wrapText(details.explanation, width).map((line) => ({ text: line })),
|
|
465
|
+
{ text: "" },
|
|
466
|
+
{
|
|
467
|
+
text: button,
|
|
468
|
+
rendered: hyperlink(terminal.hyperlinks === true, details.docsUrl, ink(terminal, "glow", button, true)),
|
|
469
|
+
},
|
|
362
470
|
];
|
|
363
471
|
if (terminal.hyperlinks !== true) {
|
|
364
|
-
|
|
472
|
+
rows.push(...wrapText(details.docsUrl, width).map((line) => ({
|
|
473
|
+
text: line,
|
|
474
|
+
rendered: ink(terminal, "muted", line),
|
|
475
|
+
})));
|
|
365
476
|
}
|
|
366
|
-
|
|
367
|
-
return lines;
|
|
477
|
+
return cardLines(terminal, t("wizard.card.help"), rows, width);
|
|
368
478
|
}
|
|
369
479
|
const BANNER_COLORS = ["36", "96", "94", "34"];
|
|
370
|
-
const
|
|
371
|
-
|
|
480
|
+
const BANNER_GRADIENT = [
|
|
481
|
+
[45, 212, 191],
|
|
482
|
+
[56, 189, 248],
|
|
483
|
+
[99, 130, 246],
|
|
484
|
+
[129, 140, 248],
|
|
485
|
+
];
|
|
486
|
+
const BANNER_GHOST = [42, 51, 66];
|
|
487
|
+
const BANNER_CREST = [236, 254, 255];
|
|
488
|
+
const SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
489
|
+
const SPARKLES = ["✦", "✧", "✶", "✧"];
|
|
490
|
+
const INTRO_FRAMES = 28;
|
|
491
|
+
const INTRO_FRAME_MS = 22;
|
|
492
|
+
const IDLE_FRAME = INTRO_FRAMES - 1;
|
|
493
|
+
const REVEAL_SPEED = 0.06;
|
|
494
|
+
const CREST_WIDTH = 0.22;
|
|
495
|
+
const SHIMMER_WIDTH = 0.2;
|
|
496
|
+
const SHIMMER_SPAN = 14;
|
|
497
|
+
const SHIMMER_PERIOD = 52;
|
|
498
|
+
const STEP_FRAMES = 4;
|
|
499
|
+
const STEP_FRAME_MS = 26;
|
|
500
|
+
const SPINNER_FRAME_MS = 80;
|
|
501
|
+
const SUCCESS_FRAMES = 6;
|
|
502
|
+
const SUCCESS_FRAME_MS = 55;
|
|
503
|
+
const IDLE_TICK_MS = 150;
|
|
504
|
+
const IDLE_TICKS = 60;
|
|
372
505
|
function pause(milliseconds) {
|
|
373
506
|
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
374
507
|
}
|
|
375
|
-
|
|
508
|
+
function stepSignature(state) {
|
|
509
|
+
return `${state.screen}:${state.fieldIndex}`;
|
|
510
|
+
}
|
|
511
|
+
/** Steady while the frame is still settling; blinks once the wizard idles. */
|
|
512
|
+
function caretGlyph(frame) {
|
|
513
|
+
const idleTicks = frame - IDLE_FRAME;
|
|
514
|
+
if (idleTicks <= 0)
|
|
515
|
+
return "▌";
|
|
516
|
+
return Math.floor(idleTicks / 3) % 2 === 0 ? "▌" : " ";
|
|
517
|
+
}
|
|
518
|
+
const WORDMARK = "RemoteDraw";
|
|
519
|
+
/** Pixel rows per glyph; rendered as solid blocks or half blocks. */
|
|
520
|
+
const BOLD_GLYPHS = {
|
|
521
|
+
R: ["######.", "##...##", "##...##", "######.", "##..##.", "##...##"],
|
|
522
|
+
D: ["######.", "##...##", "##...##", "##...##", "##...##", "######."],
|
|
523
|
+
e: ["......", ".####.", "##..##", "######", "##....", ".####."],
|
|
524
|
+
m: [
|
|
525
|
+
"..........",
|
|
526
|
+
"##########",
|
|
527
|
+
"##..##..##",
|
|
528
|
+
"##..##..##",
|
|
529
|
+
"##..##..##",
|
|
530
|
+
"##..##..##",
|
|
531
|
+
],
|
|
532
|
+
o: ["......", ".####.", "##..##", "##..##", "##..##", ".####."],
|
|
533
|
+
t: [".##..", ".##..", "#####", ".##..", ".##..", "..###"],
|
|
534
|
+
r: ["......", "##.###", "####..", "##....", "##....", "##...."],
|
|
535
|
+
a: ["......", ".####.", "....##", ".#####", "##..##", ".#####"],
|
|
536
|
+
w: ["........", "##....##", "##....##", "##.##.##", "##.##.##", ".##..##."],
|
|
537
|
+
};
|
|
538
|
+
const COMPACT_GLYPHS = {
|
|
539
|
+
R: ["####.", "#...#", "#...#", "####.", "#..#.", "#...#"],
|
|
540
|
+
D: ["####.", "#...#", "#...#", "#...#", "#...#", "####."],
|
|
541
|
+
e: [".....", ".###.", "#...#", "#####", "#....", ".###."],
|
|
542
|
+
m: [".....", "#####", "#.#.#", "#.#.#", "#.#.#", "#.#.#"],
|
|
543
|
+
o: [".....", ".###.", "#...#", "#...#", "#...#", ".###."],
|
|
544
|
+
t: [".#...", ".#...", "####.", ".#...", ".#...", "..##."],
|
|
545
|
+
r: [".....", "#.###", "##...", "#....", "#....", "#...."],
|
|
546
|
+
a: [".....", ".###.", "....#", ".####", "#...#", ".####"],
|
|
547
|
+
w: [".....", "#...#", "#...#", "#.#.#", "#.#.#", ".#.#."],
|
|
548
|
+
};
|
|
549
|
+
const BOLD_WIDTH = wordmarkWidth(BOLD_GLYPHS);
|
|
550
|
+
const COMPACT_WIDTH = wordmarkWidth(COMPACT_GLYPHS);
|
|
551
|
+
const BANNER_CACHE = new Map();
|
|
552
|
+
function wordmarkWidth(glyphs) {
|
|
553
|
+
return [...WORDMARK].reduce((total, character) => total + (glyphs[character]?.[0]?.length ?? 0) + 1, -1);
|
|
554
|
+
}
|
|
555
|
+
/** Full pixel rows: one text row per pixel row, solid blocks. */
|
|
556
|
+
function solidWordmark(glyphs) {
|
|
557
|
+
return Array.from({ length: 6 }, (_unused, row) => [...WORDMARK]
|
|
558
|
+
.map((character) => [...(glyphs[character]?.[row] ?? "")]
|
|
559
|
+
.map((pixel) => (pixel === "#" ? "█" : " "))
|
|
560
|
+
.join(""))
|
|
561
|
+
.join(" "));
|
|
562
|
+
}
|
|
563
|
+
/** Half-height: two pixel rows folded into one text row. */
|
|
564
|
+
function halfWordmark(glyphs) {
|
|
565
|
+
return Array.from({ length: 3 }, (_unused, row) => [...WORDMARK]
|
|
566
|
+
.map((character) => {
|
|
567
|
+
const top = glyphs[character]?.[row * 2] ?? "";
|
|
568
|
+
const bottom = glyphs[character]?.[row * 2 + 1] ?? "";
|
|
569
|
+
return [...top]
|
|
570
|
+
.map((pixel, column) => {
|
|
571
|
+
const upper = pixel === "#";
|
|
572
|
+
const lower = bottom[column] === "#";
|
|
573
|
+
return upper && lower ? "█" : upper ? "▀" : lower ? "▄" : " ";
|
|
574
|
+
})
|
|
575
|
+
.join("");
|
|
576
|
+
})
|
|
577
|
+
.join(" "));
|
|
578
|
+
}
|
|
579
|
+
function wordmarkRows(level, columns) {
|
|
580
|
+
const bold = columns >= BOLD_WIDTH + 2;
|
|
581
|
+
const key = `${level}:${bold}`;
|
|
582
|
+
const cached = BANNER_CACHE.get(key);
|
|
583
|
+
if (cached)
|
|
584
|
+
return cached;
|
|
585
|
+
const glyphs = bold ? BOLD_GLYPHS : COMPACT_GLYPHS;
|
|
586
|
+
const rows = level === 2 ? solidWordmark(glyphs) : halfWordmark(glyphs);
|
|
587
|
+
BANNER_CACHE.set(key, rows);
|
|
588
|
+
return rows;
|
|
589
|
+
}
|
|
590
|
+
function bannerLines(terminal, frame, level) {
|
|
591
|
+
if (level === 0)
|
|
592
|
+
return [ink(terminal, "glow", WORDMARK, true)];
|
|
593
|
+
const rows = wordmarkRows(level, terminal.columns);
|
|
594
|
+
return rows.map((row) => paintWordmark(terminal, row, frame));
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* The wordmark materialises: a crest sweeps left to right during the intro,
|
|
598
|
+
* turning dim scaffolding into lit blocks. Once settled, a slower highlight
|
|
599
|
+
* passes over it every few seconds while the wizard waits for input.
|
|
600
|
+
*/
|
|
601
|
+
function paintWordmark(terminal, row, frame) {
|
|
602
|
+
if (!terminal.color)
|
|
603
|
+
return row;
|
|
604
|
+
const characters = [...row];
|
|
605
|
+
const span = Math.max(1, characters.length - 1);
|
|
606
|
+
const crest = frame * REVEAL_SPEED - 0.3;
|
|
607
|
+
const idleTicks = frame - IDLE_FRAME;
|
|
608
|
+
const shimmerPhase = idleTicks > 0 ? idleTicks % SHIMMER_PERIOD : -1;
|
|
609
|
+
const shimmer = shimmerPhase >= 0 && shimmerPhase < SHIMMER_SPAN
|
|
610
|
+
? (shimmerPhase / SHIMMER_SPAN) * 1.35 - 0.18
|
|
611
|
+
: undefined;
|
|
612
|
+
let rendered = "";
|
|
613
|
+
let openSequence = "";
|
|
614
|
+
let buffered = "";
|
|
615
|
+
for (let index = 0; index < characters.length; index += 1) {
|
|
616
|
+
const position = index / span;
|
|
617
|
+
const sequence = wordmarkSequence(terminal, position, crest, shimmer);
|
|
618
|
+
if (sequence !== openSequence) {
|
|
619
|
+
if (buffered)
|
|
620
|
+
rendered += `${openSequence}${buffered}`;
|
|
621
|
+
openSequence = sequence;
|
|
622
|
+
buffered = "";
|
|
623
|
+
}
|
|
624
|
+
buffered += characters[index];
|
|
625
|
+
}
|
|
626
|
+
return `${rendered}${openSequence}${buffered}\x1b[0m`;
|
|
627
|
+
}
|
|
628
|
+
function wordmarkSequence(terminal, position, crest, shimmer) {
|
|
629
|
+
const arrived = crest - position;
|
|
630
|
+
const highlight = Math.max(arrived >= 0 && arrived < CREST_WIDTH ? 1 - arrived / CREST_WIDTH : 0, shimmer == null
|
|
631
|
+
? 0
|
|
632
|
+
: Math.max(0, 1 - Math.abs(position - shimmer) / SHIMMER_WIDTH));
|
|
633
|
+
if (!terminal.trueColor) {
|
|
634
|
+
if (arrived < 0)
|
|
635
|
+
return "\x1b[2m\x1b[34m";
|
|
636
|
+
if (highlight > 0.45)
|
|
637
|
+
return "\x1b[96m";
|
|
638
|
+
return `\x1b[${BANNER_COLORS[Math.floor(position * 3.5) % BANNER_COLORS.length]}m`;
|
|
639
|
+
}
|
|
640
|
+
if (arrived < 0)
|
|
641
|
+
return truecolor(BANNER_GHOST);
|
|
642
|
+
const scaled = Math.min(0.999, position) * (BANNER_GRADIENT.length - 1);
|
|
643
|
+
const index = Math.floor(scaled);
|
|
644
|
+
const base = mixRgb(BANNER_GRADIENT[index], BANNER_GRADIENT[index + 1], scaled - index);
|
|
645
|
+
return truecolor(highlight > 0
|
|
646
|
+
? mixRgb(base, BANNER_CREST, highlight * highlight * 0.9)
|
|
647
|
+
: base);
|
|
648
|
+
}
|
|
649
|
+
function mixRgb(from, to, amount) {
|
|
650
|
+
const ratio = Math.min(1, Math.max(0, amount));
|
|
651
|
+
return [
|
|
652
|
+
Math.round(from[0] + (to[0] - from[0]) * ratio),
|
|
653
|
+
Math.round(from[1] + (to[1] - from[1]) * ratio),
|
|
654
|
+
Math.round(from[2] + (to[2] - from[2]) * ratio),
|
|
655
|
+
];
|
|
656
|
+
}
|
|
657
|
+
function stepRail(terminal, current, total, frame) {
|
|
658
|
+
const cells = [];
|
|
659
|
+
for (let index = 0; index < total; index += 1) {
|
|
660
|
+
if (index > 0) {
|
|
661
|
+
cells.push(ink(terminal, index < current ? "accent" : "muted", "─"));
|
|
662
|
+
}
|
|
663
|
+
if (index < current - 1) {
|
|
664
|
+
cells.push(ink(terminal, "accent", "●"));
|
|
665
|
+
}
|
|
666
|
+
else if (index === current - 1) {
|
|
667
|
+
cells.push(ink(terminal, frame % 2 === 0 ? "glow" : "accent", "◉", true));
|
|
668
|
+
}
|
|
669
|
+
else {
|
|
670
|
+
cells.push(ink(terminal, "muted", "○"));
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
return cells.join("");
|
|
674
|
+
}
|
|
675
|
+
function screenGlyph(terminal, screen, frame) {
|
|
676
|
+
if (screen === "executing") {
|
|
677
|
+
return ink(terminal, "accent", SPINNER[frame % SPINNER.length]);
|
|
678
|
+
}
|
|
679
|
+
if (screen === "success")
|
|
680
|
+
return ink(terminal, "success", "✓", true);
|
|
681
|
+
if (screen === "error")
|
|
682
|
+
return ink(terminal, "danger", "✗", true);
|
|
683
|
+
if (screen === "confirm")
|
|
684
|
+
return ink(terminal, "warn", "?", true);
|
|
685
|
+
if (screen === "cancelled" || screen === "interrupted") {
|
|
686
|
+
return ink(terminal, "warn", "○");
|
|
687
|
+
}
|
|
688
|
+
return ink(terminal, "accent", "▸");
|
|
689
|
+
}
|
|
690
|
+
function choiceLines(terminal, choices, selectedValue, withHints) {
|
|
691
|
+
const lines = [];
|
|
692
|
+
for (const choice of choices) {
|
|
693
|
+
const selected = choice.value === selectedValue;
|
|
694
|
+
lines.push(selected
|
|
695
|
+
? `${ink(terminal, "glow", "❯")} ${ink(terminal, "glow", "◉")} ${strong(terminal, choice.label)}`
|
|
696
|
+
: ` ${ink(terminal, "muted", "○")} ${choice.label}`);
|
|
697
|
+
if (withHints && choice.hint) {
|
|
698
|
+
lines.push(` ${ink(terminal, "muted", choice.hint)}`);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
return lines;
|
|
702
|
+
}
|
|
703
|
+
function progressBar(terminal, frame, size) {
|
|
704
|
+
const head = frame % (size + 6);
|
|
705
|
+
const cells = Array.from({ length: size }, (_unused, index) => {
|
|
706
|
+
const distance = head - index;
|
|
707
|
+
if (distance < 0 || distance > 5)
|
|
708
|
+
return ink(terminal, "muted", "▱");
|
|
709
|
+
return ink(terminal, distance < 2 ? "glow" : "accent", "▰");
|
|
710
|
+
});
|
|
711
|
+
return ` ${cells.join("")}`;
|
|
712
|
+
}
|
|
713
|
+
function controlLines(terminal, controls, width) {
|
|
714
|
+
const separator = ink(terminal, "muted", " · ");
|
|
715
|
+
const lines = [];
|
|
716
|
+
let rendered = "";
|
|
717
|
+
let plainLength = 0;
|
|
718
|
+
for (const control of controls) {
|
|
719
|
+
if (plainLength > 0 && plainLength + 3 + control.length > width) {
|
|
720
|
+
lines.push(rendered);
|
|
721
|
+
rendered = "";
|
|
722
|
+
plainLength = 0;
|
|
723
|
+
}
|
|
724
|
+
if (plainLength === 0) {
|
|
725
|
+
rendered = controlChip(terminal, control);
|
|
726
|
+
plainLength = control.length;
|
|
727
|
+
}
|
|
728
|
+
else {
|
|
729
|
+
rendered += `${separator}${controlChip(terminal, control)}`;
|
|
730
|
+
plainLength += 3 + control.length;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
if (rendered)
|
|
734
|
+
lines.push(rendered);
|
|
735
|
+
return lines;
|
|
736
|
+
}
|
|
737
|
+
function controlChip(terminal, control) {
|
|
738
|
+
const gap = control.indexOf(" ");
|
|
739
|
+
if (gap < 0)
|
|
740
|
+
return ink(terminal, "glow", control);
|
|
741
|
+
return `${ink(terminal, "glow", control.slice(0, gap))}${ink(terminal, "muted", control.slice(gap))}`;
|
|
742
|
+
}
|
|
743
|
+
function labelColumn(pairs) {
|
|
744
|
+
return pairs.reduce((widest, [label]) => Math.max(widest, label.length), 0);
|
|
745
|
+
}
|
|
746
|
+
export function renderWizard(state, definition, terminal, frame = 0) {
|
|
376
747
|
const view = wizardSemanticView(state, definition);
|
|
377
748
|
const narrow = terminal.columns < 60;
|
|
378
|
-
const
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
749
|
+
const rule = Math.max(12, Math.min(terminal.columns - 1, 72));
|
|
750
|
+
const lines = fitWizard(view, terminal, frame, narrow, rule);
|
|
751
|
+
const home = terminal.color ? "\x1b[H" : "\x1b[2J\x1b[H";
|
|
752
|
+
const clearLine = terminal.color ? "\x1b[K" : "";
|
|
753
|
+
const clearBelow = terminal.color ? "\x1b[J" : "";
|
|
754
|
+
return `${home}${lines
|
|
755
|
+
.map((line) => `${line}${clearLine}`)
|
|
756
|
+
.join("\n")}${clearBelow}\n`;
|
|
757
|
+
}
|
|
758
|
+
/** Richest first: the last candidate that still fits the window wins. */
|
|
759
|
+
const DENSITY_LADDER = [
|
|
760
|
+
{ banner: 2, trail: true, hints: true, details: true },
|
|
761
|
+
{ banner: 1, trail: true, hints: true, details: true },
|
|
762
|
+
{ banner: 1, trail: false, hints: true, details: true },
|
|
763
|
+
{ banner: 1, trail: false, hints: false, details: true },
|
|
764
|
+
{ banner: 0, trail: false, hints: false, details: true },
|
|
765
|
+
{ banner: 0, trail: false, hints: false, details: false },
|
|
766
|
+
];
|
|
767
|
+
function fitWizard(view, terminal, frame, narrow, rule) {
|
|
768
|
+
const widest = terminal.columns >= BOLD_WIDTH + 2
|
|
769
|
+
? 2
|
|
770
|
+
: terminal.columns >= COMPACT_WIDTH + 2
|
|
771
|
+
? 1
|
|
772
|
+
: 0;
|
|
773
|
+
const limit = terminal.rows == null ? undefined : terminal.rows - 1;
|
|
774
|
+
let lines = [];
|
|
775
|
+
for (const candidate of DENSITY_LADDER) {
|
|
776
|
+
lines = composeWizard(view, terminal, frame, narrow, rule, {
|
|
777
|
+
banner: Math.min(candidate.banner, widest),
|
|
778
|
+
trail: candidate.trail && !narrow,
|
|
779
|
+
hints: candidate.hints && !narrow,
|
|
780
|
+
details: candidate.details,
|
|
781
|
+
});
|
|
782
|
+
if (limit == null || lines.length <= limit)
|
|
783
|
+
break;
|
|
784
|
+
}
|
|
785
|
+
return lines;
|
|
786
|
+
}
|
|
787
|
+
function composeWizard(view, terminal, frame, narrow, rule, density) {
|
|
387
788
|
const lines = [
|
|
388
|
-
|
|
389
|
-
|
|
789
|
+
...bannerLines(terminal, frame, density.banner),
|
|
790
|
+
ink(terminal, "muted", density.banner > 0
|
|
791
|
+
? t("wizard.tagline.setup")
|
|
792
|
+
: t("wizard.tagline")),
|
|
390
793
|
"",
|
|
391
794
|
];
|
|
392
795
|
if (view.progress) {
|
|
393
|
-
|
|
796
|
+
const step = t("wizard.progress.step", {
|
|
797
|
+
current: view.progress.current,
|
|
798
|
+
total: view.progress.total,
|
|
799
|
+
});
|
|
800
|
+
lines.push(narrow
|
|
801
|
+
? `${ink(terminal, "muted", step)} · ${strong(terminal, view.title)}`
|
|
802
|
+
: `${stepRail(terminal, view.progress.current, view.progress.total, frame)} ${ink(terminal, "muted", step)} ${strong(terminal, view.title)}`, "");
|
|
394
803
|
}
|
|
395
804
|
else {
|
|
396
|
-
lines.push(view.
|
|
805
|
+
lines.push(`${screenGlyph(terminal, view.screen, frame)} ${strong(terminal, view.title)}`, "");
|
|
806
|
+
}
|
|
807
|
+
if (density.trail && view.completed && view.completed.length > 0) {
|
|
808
|
+
const column = labelColumn(view.completed);
|
|
809
|
+
for (const [label, value] of view.completed) {
|
|
810
|
+
lines.push(`${ink(terminal, "success", "✓")} ${ink(terminal, "muted", label.padEnd(column))} ${value}`);
|
|
811
|
+
}
|
|
812
|
+
lines.push("");
|
|
397
813
|
}
|
|
398
814
|
if (view.activeLabel) {
|
|
815
|
+
lines.push(ink(terminal, "accent", view.activeLabel, true));
|
|
399
816
|
if (view.choices) {
|
|
400
|
-
lines.push(
|
|
401
|
-
for (const choice of view.choices) {
|
|
402
|
-
const selected = choice.value === view.selectedChoice;
|
|
403
|
-
lines.push(paint(terminal.color && selected, "1;96", `${selected ? ">" : " "} [${selected ? "x" : " "}] ${choice.label}`));
|
|
404
|
-
if (!narrow && choice.hint)
|
|
405
|
-
lines.push(` ${choice.hint}`);
|
|
406
|
-
}
|
|
817
|
+
lines.push(...choiceLines(terminal, view.choices, view.selectedChoice, density.hints));
|
|
407
818
|
}
|
|
408
819
|
else {
|
|
409
|
-
lines.push(
|
|
410
|
-
? ` ${
|
|
820
|
+
lines.push(`${ink(terminal, "accent", "│")} ${ink(terminal, view.valueIsDefault ? "muted" : "glow", view.value || "_", !view.valueIsDefault)}${ink(terminal, "glow", caretGlyph(frame))}${view.valueIsDefault
|
|
821
|
+
? ` ${ink(terminal, "muted", t("wizard.value.default"))}`
|
|
411
822
|
: ""}`);
|
|
823
|
+
if (view.valuePreview) {
|
|
824
|
+
lines.push(`${ink(terminal, "muted", "└")} ${ink(terminal, "muted", view.valuePreview)}`);
|
|
825
|
+
}
|
|
412
826
|
}
|
|
413
|
-
if (view.selectedDetails) {
|
|
827
|
+
if (density.details && view.selectedDetails) {
|
|
414
828
|
lines.push("", ...renderChoiceDetails(view.selectedDetails, terminal));
|
|
415
829
|
}
|
|
416
|
-
if (view.validation)
|
|
417
|
-
lines.push("",
|
|
830
|
+
if (view.validation) {
|
|
831
|
+
lines.push("", ink(terminal, "warn", `⚠ ${view.validation}`));
|
|
832
|
+
}
|
|
418
833
|
}
|
|
419
834
|
if (view.review) {
|
|
420
|
-
|
|
421
|
-
|
|
835
|
+
const column = labelColumn(view.review);
|
|
836
|
+
if (view.screen === "confirm") {
|
|
837
|
+
for (const [label, value] of view.review) {
|
|
838
|
+
lines.push(` ${ink(terminal, "muted", label.padEnd(column))} ${ink(terminal, "muted", value)}`);
|
|
839
|
+
}
|
|
840
|
+
lines.push("");
|
|
841
|
+
}
|
|
842
|
+
else {
|
|
843
|
+
const rows = view.review.map(([label, value]) => ({
|
|
844
|
+
text: `${label.padEnd(column)} ${value}`,
|
|
845
|
+
rendered: `${ink(terminal, "muted", label.padEnd(column))} ${strong(terminal, value)}`,
|
|
846
|
+
}));
|
|
847
|
+
const width = Math.max(24, Math.min(Math.max(20, terminal.columns - 4), rows.reduce((widest, row) => Math.max(widest, row.text.length), 0)));
|
|
848
|
+
lines.push(...cardLines(terminal, t("wizard.card.summary"), rows, width));
|
|
849
|
+
}
|
|
422
850
|
}
|
|
423
851
|
if (view.choices && !view.activeLabel) {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
852
|
+
lines.push(...choiceLines(terminal, view.choices, view.selectedChoice, false));
|
|
853
|
+
}
|
|
854
|
+
if (view.screen === "executing") {
|
|
855
|
+
lines.push(progressBar(terminal, frame, narrow ? 12 : 24), "", ink(terminal, "muted", t("wizard.executing.detail")));
|
|
856
|
+
}
|
|
857
|
+
if (view.createdPath) {
|
|
858
|
+
lines.push(`${ink(terminal, "muted", t("wizard.created.at"))} ${view.createdPath}`);
|
|
859
|
+
}
|
|
860
|
+
if (view.nextCommand) {
|
|
861
|
+
lines.push("", ink(terminal, "muted", t("wizard.next.command")), ` ${ink(terminal, "glow", view.nextCommand, true)}`);
|
|
862
|
+
}
|
|
863
|
+
if (view.screen === "success") {
|
|
864
|
+
lines.push("", `${ink(terminal, "success", SPARKLES[frame % SPARKLES.length])} ${ink(terminal, "muted", t("wizard.docs.line"))}`);
|
|
865
|
+
}
|
|
866
|
+
if (view.error) {
|
|
867
|
+
for (const line of view.error.split("\n")) {
|
|
868
|
+
lines.push(ink(terminal, "danger", line));
|
|
427
869
|
}
|
|
428
870
|
}
|
|
429
|
-
if (view.
|
|
430
|
-
lines.push(
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
if (view.error)
|
|
434
|
-
lines.push(view.error);
|
|
435
|
-
if (view.controls.length)
|
|
436
|
-
lines.push("", view.controls.join(" · "));
|
|
437
|
-
return `${lines.join("\n")}\n`;
|
|
871
|
+
if (view.controls.length) {
|
|
872
|
+
lines.push("", ink(terminal, "muted", "─".repeat(rule)), ...controlLines(terminal, view.controls, Math.max(rule, terminal.columns - 1)));
|
|
873
|
+
}
|
|
874
|
+
return lines;
|
|
438
875
|
}
|
|
439
876
|
export function createWizardTerminal(input, output, env) {
|
|
440
877
|
const wasRaw = input.isRaw === true;
|
|
441
878
|
const wasFlowing = input.readableFlowing === true;
|
|
879
|
+
const buffered = [];
|
|
880
|
+
let waiting;
|
|
881
|
+
const onKeypress = (_text, key) => {
|
|
882
|
+
if (waiting) {
|
|
883
|
+
const resolve = waiting;
|
|
884
|
+
waiting = undefined;
|
|
885
|
+
resolve(key);
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
buffered.push(key);
|
|
889
|
+
};
|
|
442
890
|
return {
|
|
443
|
-
|
|
891
|
+
get columns() {
|
|
892
|
+
return output.columns || 80;
|
|
893
|
+
},
|
|
894
|
+
get rows() {
|
|
895
|
+
return output.rows || 24;
|
|
896
|
+
},
|
|
444
897
|
color: env.NO_COLOR == null && output.hasColors?.() !== false,
|
|
898
|
+
trueColor: env.NO_COLOR == null &&
|
|
899
|
+
(env.COLORTERM === "truecolor" ||
|
|
900
|
+
env.COLORTERM === "24bit" ||
|
|
901
|
+
output.hasColors?.(16_777_216) === true),
|
|
445
902
|
hyperlinks: env.TERM !== "dumb" && env.NO_HYPERLINKS == null,
|
|
446
903
|
write(value) {
|
|
447
904
|
output.write(value);
|
|
@@ -449,16 +906,50 @@ export function createWizardTerminal(input, output, env) {
|
|
|
449
906
|
enter() {
|
|
450
907
|
emitKeypressEvents(input);
|
|
451
908
|
input.setRawMode?.(true);
|
|
909
|
+
input.on("keypress", onKeypress);
|
|
452
910
|
input.resume();
|
|
453
911
|
output.write("\x1b[?25l");
|
|
454
912
|
},
|
|
913
|
+
hasPendingKey() {
|
|
914
|
+
return buffered.length > 0;
|
|
915
|
+
},
|
|
455
916
|
async readKey() {
|
|
917
|
+
const next = buffered.shift();
|
|
918
|
+
if (next)
|
|
919
|
+
return next;
|
|
920
|
+
return await new Promise((resolve) => {
|
|
921
|
+
waiting = resolve;
|
|
922
|
+
});
|
|
923
|
+
},
|
|
924
|
+
async readKeyWithin(milliseconds) {
|
|
925
|
+
const next = buffered.shift();
|
|
926
|
+
if (next)
|
|
927
|
+
return next;
|
|
456
928
|
return await new Promise((resolve) => {
|
|
457
|
-
|
|
929
|
+
let settled = false;
|
|
930
|
+
const timer = setTimeout(() => {
|
|
931
|
+
if (settled)
|
|
932
|
+
return;
|
|
933
|
+
settled = true;
|
|
934
|
+
if (waiting === listener)
|
|
935
|
+
waiting = undefined;
|
|
936
|
+
resolve(undefined);
|
|
937
|
+
}, milliseconds);
|
|
938
|
+
timer.unref?.();
|
|
939
|
+
const listener = (key) => {
|
|
940
|
+
if (settled)
|
|
941
|
+
return;
|
|
942
|
+
settled = true;
|
|
943
|
+
clearTimeout(timer);
|
|
944
|
+
resolve(key);
|
|
945
|
+
};
|
|
946
|
+
waiting = listener;
|
|
458
947
|
});
|
|
459
948
|
},
|
|
460
949
|
openUrl: openExternalUrl,
|
|
461
950
|
restore() {
|
|
951
|
+
input.off("keypress", onKeypress);
|
|
952
|
+
waiting = undefined;
|
|
462
953
|
if (!wasRaw)
|
|
463
954
|
input.setRawMode?.(false);
|
|
464
955
|
if (!wasFlowing)
|