@4xeoz/re-entry 0.2.4 → 0.2.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/README.md +4 -0
- package/package.json +1 -1
- package/src/browser-prompt.mjs +1 -1
- package/src/main.mjs +88 -59
- package/src/terminal-ui.mjs +58 -31
- package/src/workspace-picker.mjs +13 -8
package/README.md
CHANGED
|
@@ -27,6 +27,10 @@ npx @4xeoz/re-entry install \
|
|
|
27
27
|
--codex-cd /absolute/path/to/your/project
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
+
The guided screen stays intentionally small: **Workspace → System check → Connect Re-entry**. It
|
|
31
|
+
shows one clear next command when setup finishes; internal Connector IDs, credential paths, and log
|
|
32
|
+
paths stay out of the normal success screen.
|
|
33
|
+
|
|
30
34
|
Run this from the Host project directory, your home directory, or another normal working
|
|
31
35
|
directory—not from a checked-out `runtime/local-connector` package directory. npm can treat that
|
|
32
36
|
source directory as the package itself and fail to create the temporary executable link.
|
package/package.json
CHANGED
package/src/browser-prompt.mjs
CHANGED
|
@@ -8,7 +8,7 @@ import process from "node:process";
|
|
|
8
8
|
export async function waitForEnterToOpenBrowser(options = {}) {
|
|
9
9
|
const input = options.input ?? process.stdin;
|
|
10
10
|
const output = options.output ?? process.stdout;
|
|
11
|
-
const prompt = options.prompt ?? " Press Enter to open Re-entry
|
|
11
|
+
const prompt = options.prompt ?? "\n Press Enter to open Re-entry → ";
|
|
12
12
|
const readline = createInterface({ input, output });
|
|
13
13
|
try {
|
|
14
14
|
await readline.question(prompt);
|
package/src/main.mjs
CHANGED
|
@@ -149,9 +149,12 @@ function readConnectorVersion() {
|
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
function doctor(flags, ui) {
|
|
152
|
-
if (ui.interactive)
|
|
152
|
+
if (ui.interactive) {
|
|
153
|
+
ui.begin("System check", "Confirm that this Mac is ready for Re-entry.");
|
|
154
|
+
ui.section("CHECK", "Requirements");
|
|
155
|
+
}
|
|
153
156
|
const readiness = inspectReadiness(flags);
|
|
154
|
-
showReadiness(readiness, ui);
|
|
157
|
+
showReadiness(readiness, ui, { detailed: true });
|
|
155
158
|
if (!ui.interactive) {
|
|
156
159
|
process.stdout.write(`${JSON.stringify({
|
|
157
160
|
event: "connector_ready",
|
|
@@ -179,21 +182,26 @@ async function withWorkspaceDirectory(flags, ui) {
|
|
|
179
182
|
if (flags["codex-cd"] !== undefined || !ui.interactive) {
|
|
180
183
|
return { ...flags, "codex-cd": flags["codex-cd"] ?? process.cwd() };
|
|
181
184
|
}
|
|
182
|
-
ui.info("Workspace", "choose the folder Codex will open");
|
|
183
185
|
const selected = await chooseWorkspaceDirectory({ startDirectory: process.cwd() });
|
|
184
186
|
if (!selected) throw cliFailure("connector_codex_cd_missing");
|
|
185
|
-
ui.success("Workspace", selected);
|
|
186
187
|
return { ...flags, "codex-cd": selected };
|
|
187
188
|
}
|
|
188
189
|
|
|
189
|
-
function showReadiness(readiness, ui) {
|
|
190
|
+
function showReadiness(readiness, ui, options = {}) {
|
|
190
191
|
if (!ui.interactive) return;
|
|
191
|
-
ui.success("Node.js", process.versions.node);
|
|
192
|
-
ui.success(
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
192
|
+
ui.success("Node.js", `v${process.versions.node}`);
|
|
193
|
+
ui.success(
|
|
194
|
+
"Codex",
|
|
195
|
+
options.detailed
|
|
196
|
+
? `${readiness.installation.version} · ${readiness.installation.executable}`
|
|
197
|
+
: "ready",
|
|
198
|
+
);
|
|
199
|
+
if (options.includeWorkspace !== false) {
|
|
200
|
+
if (readiness.workingDirectory) {
|
|
201
|
+
ui.success("Workspace", readiness.workingDirectory);
|
|
202
|
+
} else {
|
|
203
|
+
ui.info("Workspace", "selected automatically when Re-entry starts");
|
|
204
|
+
}
|
|
197
205
|
}
|
|
198
206
|
}
|
|
199
207
|
|
|
@@ -215,18 +223,16 @@ async function pair(flags, ui, options = {}) {
|
|
|
215
223
|
if (ui.interactive) ui.wait("Contacting Re-entry…");
|
|
216
224
|
const credentials = await client.pair({ userCode }, async ({ verificationUri }) => {
|
|
217
225
|
if (ui.interactive) {
|
|
218
|
-
ui.stopWait("
|
|
219
|
-
ui.
|
|
220
|
-
ui.info("Open this URL", verificationUri);
|
|
226
|
+
ui.stopWait("Secure link", "ready");
|
|
227
|
+
ui.info("Browser link", verificationUri);
|
|
221
228
|
await waitForEnterToOpenBrowser();
|
|
222
|
-
ui.
|
|
223
|
-
ui.wait("Waiting for you to approve this Mac…");
|
|
229
|
+
ui.wait("Waiting for approval in your browser…");
|
|
224
230
|
} else {
|
|
225
231
|
process.stdout.write(`${JSON.stringify({ event: "pairing_waiting", verification_uri: verificationUri })}\n`);
|
|
226
232
|
}
|
|
227
233
|
});
|
|
228
234
|
if (ui.interactive) {
|
|
229
|
-
ui.stopWait("
|
|
235
|
+
ui.stopWait("Approved", "this Mac is connected");
|
|
230
236
|
if (!credentials.browserOpened) {
|
|
231
237
|
ui.warning("Browser", "did not open automatically; use the URL above");
|
|
232
238
|
}
|
|
@@ -240,7 +246,8 @@ async function pair(flags, ui, options = {}) {
|
|
|
240
246
|
connector_expires_at: credentials.connector_expires_at,
|
|
241
247
|
});
|
|
242
248
|
if (ui.interactive) {
|
|
243
|
-
ui.
|
|
249
|
+
ui.complete("Pairing complete", "Re-entry can now deliver approved work to this Mac.");
|
|
250
|
+
ui.next("re-entry start", "Wait for approved work in this terminal.");
|
|
244
251
|
} else {
|
|
245
252
|
process.stdout.write(`${JSON.stringify({ event: "connector_paired", connector_id: credentials.connector_id })}\n`);
|
|
246
253
|
}
|
|
@@ -257,8 +264,10 @@ async function connect(flags, ui, options = {}) {
|
|
|
257
264
|
const currentIsValid = current && Date.parse(current.connector_expires_at) > Date.now();
|
|
258
265
|
if (currentIsValid && current.receiver_origin === receiver) {
|
|
259
266
|
if (ui.interactive) {
|
|
260
|
-
ui.success("
|
|
261
|
-
|
|
267
|
+
ui.success("Account", "already connected");
|
|
268
|
+
if (!options.guidedInstall) {
|
|
269
|
+
ui.next("re-entry start", "Wait for approved work in this terminal.");
|
|
270
|
+
}
|
|
262
271
|
} else {
|
|
263
272
|
process.stdout.write(`${JSON.stringify({
|
|
264
273
|
event: "connector_already_connected",
|
|
@@ -274,8 +283,8 @@ async function connect(flags, ui, options = {}) {
|
|
|
274
283
|
}
|
|
275
284
|
|
|
276
285
|
if (ui.interactive) {
|
|
277
|
-
ui.
|
|
278
|
-
ui.
|
|
286
|
+
if (!options.guidedInstall) ui.info("Re-entry", displayReceiver(receiver));
|
|
287
|
+
ui.info("This Mac", flags["device-name"] ?? defaultDeviceName());
|
|
279
288
|
}
|
|
280
289
|
const client = new LocalConnectorPairingClient({
|
|
281
290
|
baseUrl: receiver,
|
|
@@ -286,12 +295,10 @@ async function connect(flags, ui, options = {}) {
|
|
|
286
295
|
{ deviceName: flags["device-name"] ?? defaultDeviceName() },
|
|
287
296
|
async ({ verificationUri }) => {
|
|
288
297
|
if (ui.interactive) {
|
|
289
|
-
ui.stopWait("
|
|
290
|
-
ui.
|
|
291
|
-
ui.info("If it does not open", verificationUri);
|
|
298
|
+
ui.stopWait("Secure link", "ready");
|
|
299
|
+
ui.info("Browser link", verificationUri);
|
|
292
300
|
await waitForEnterToOpenBrowser();
|
|
293
|
-
ui.
|
|
294
|
-
ui.wait("Waiting for you to connect this Mac…");
|
|
301
|
+
ui.wait("Waiting for approval in your browser…");
|
|
295
302
|
} else {
|
|
296
303
|
process.stdout.write(`${JSON.stringify({
|
|
297
304
|
event: "connector_authorization_waiting",
|
|
@@ -301,7 +308,7 @@ async function connect(flags, ui, options = {}) {
|
|
|
301
308
|
},
|
|
302
309
|
);
|
|
303
310
|
if (ui.interactive) {
|
|
304
|
-
ui.stopWait("
|
|
311
|
+
ui.stopWait("Account", "connected");
|
|
305
312
|
if (!credentials.browserOpened) {
|
|
306
313
|
ui.warning("Browser", "did not open automatically; use the URL shown above");
|
|
307
314
|
}
|
|
@@ -315,8 +322,10 @@ async function connect(flags, ui, options = {}) {
|
|
|
315
322
|
};
|
|
316
323
|
await store.save(saved);
|
|
317
324
|
if (ui.interactive) {
|
|
318
|
-
|
|
319
|
-
|
|
325
|
+
if (!options.guidedInstall) {
|
|
326
|
+
ui.complete("This Mac is connected", "Re-entry can now route approved work here.");
|
|
327
|
+
ui.next("re-entry start", "Wait for approved work in this terminal.");
|
|
328
|
+
}
|
|
320
329
|
} else {
|
|
321
330
|
process.stdout.write(`${JSON.stringify({
|
|
322
331
|
event: "connector_connected",
|
|
@@ -327,22 +336,32 @@ async function connect(flags, ui, options = {}) {
|
|
|
327
336
|
}
|
|
328
337
|
|
|
329
338
|
async function status(flags, ui) {
|
|
330
|
-
if (ui.interactive) ui.begin("
|
|
339
|
+
if (ui.interactive) ui.begin("Status", "A quick check of this Mac and Re-entry.");
|
|
331
340
|
const credentialFile = flags["credential-file"] ?? defaultCredentialFile();
|
|
332
341
|
const credentials = await new LocalConnectorCredentialStore({ filename: credentialFile }).load();
|
|
333
342
|
const readiness = inspectReadiness(flags);
|
|
334
343
|
const service = await inspectMacConnectorService();
|
|
335
344
|
const receiverReady = credentials ? await inspectReceiver(credentials.receiver_origin) : false;
|
|
336
|
-
showReadiness(readiness, ui);
|
|
337
345
|
const connected = Boolean(credentials && Date.parse(credentials.connector_expires_at) > Date.now());
|
|
338
346
|
if (ui.interactive) {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
else ui.warning("
|
|
344
|
-
if (
|
|
345
|
-
else if (
|
|
347
|
+
ui.section("SYSTEM", "This Mac");
|
|
348
|
+
showReadiness(readiness, ui);
|
|
349
|
+
ui.section("CONNECTION", "Re-entry");
|
|
350
|
+
if (connected) ui.success("Account", "connected");
|
|
351
|
+
else ui.warning("Account", "not connected");
|
|
352
|
+
if (service.running) ui.success("Background", "running");
|
|
353
|
+
else if (service.installed) ui.warning("Background", "stopped");
|
|
354
|
+
else ui.warning("Background", "not installed");
|
|
355
|
+
if (connected && receiverReady) ui.success("Cloud", "online");
|
|
356
|
+
else if (connected) ui.warning("Cloud", "unavailable");
|
|
357
|
+
|
|
358
|
+
if (!connected || !service.running) {
|
|
359
|
+
ui.next("re-entry install", "Finish setup and start Re-entry in the background.");
|
|
360
|
+
} else if (!receiverReady) {
|
|
361
|
+
ui.next("re-entry status", "Check again when the Re-entry Cloud service is available.");
|
|
362
|
+
} else {
|
|
363
|
+
ui.complete("Everything looks good", "Re-entry is ready for approved work.");
|
|
364
|
+
}
|
|
346
365
|
} else {
|
|
347
366
|
process.stdout.write(`${JSON.stringify({
|
|
348
367
|
event: "connector_status",
|
|
@@ -366,8 +385,9 @@ async function stop(flags, ui) {
|
|
|
366
385
|
const result = await stopMacConnectorService();
|
|
367
386
|
if (ui.interactive) {
|
|
368
387
|
if (!result.supported) ui.warning("Platform", "background service control currently supports macOS only");
|
|
369
|
-
else if (result.stopped) ui.
|
|
388
|
+
else if (result.stopped) ui.complete("Re-entry is paused", "Your account connection is still saved.");
|
|
370
389
|
else ui.info("Already stopped", "no running background Connector was found");
|
|
390
|
+
if (result.supported) ui.next("re-entry install", "Start Re-entry in the background again.");
|
|
371
391
|
} else {
|
|
372
392
|
process.stdout.write(`${JSON.stringify({
|
|
373
393
|
event: "connector_stopped",
|
|
@@ -389,8 +409,8 @@ async function uninstall(flags, ui) {
|
|
|
389
409
|
credentialFile: flags["credential-file"] ?? defaultCredentialFile(),
|
|
390
410
|
});
|
|
391
411
|
if (ui.interactive) {
|
|
392
|
-
ui.
|
|
393
|
-
ui.
|
|
412
|
+
ui.complete("Removed from this Mac", "The local service, connection, and logs are gone.");
|
|
413
|
+
ui.next("npx @4xeoz/re-entry install", "Connect this Mac again whenever you are ready.");
|
|
394
414
|
} else {
|
|
395
415
|
process.stdout.write(`${JSON.stringify({
|
|
396
416
|
event: "connector_uninstalled",
|
|
@@ -401,16 +421,18 @@ async function uninstall(flags, ui) {
|
|
|
401
421
|
}
|
|
402
422
|
|
|
403
423
|
async function install(flags, ui) {
|
|
404
|
-
if (ui.interactive) {
|
|
405
|
-
ui.begin("Install Re-entry", "Check Codex, connect your account, then start at login");
|
|
406
|
-
ui.info("Setup", "choose a workspace, approve this Mac, then leave the Connector running");
|
|
407
|
-
}
|
|
408
424
|
const runtimeFlags = await withWorkspaceDirectory(flags, ui);
|
|
409
|
-
if (ui.interactive) ui.info("Receiver", runtimeFlags.receiver ?? DEFAULT_RECEIVER_ORIGIN);
|
|
410
425
|
const readiness = inspectReadiness(runtimeFlags);
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
426
|
+
if (ui.interactive) {
|
|
427
|
+
ui.begin("Set up this Mac", "Three quick steps. You only do this once.");
|
|
428
|
+
ui.section("1 OF 3", "Workspace");
|
|
429
|
+
ui.success("Selected", readiness.workingDirectory);
|
|
430
|
+
ui.section("2 OF 3", "System check");
|
|
431
|
+
showReadiness(readiness, ui, { includeWorkspace: false });
|
|
432
|
+
ui.section("3 OF 3", "Connect Re-entry", "Approve this Mac in your browser.");
|
|
433
|
+
}
|
|
434
|
+
const credentials = await connect(runtimeFlags, ui, { quietHeader: true, guidedInstall: true });
|
|
435
|
+
if (ui.interactive) ui.wait("Starting Re-entry in the background…");
|
|
414
436
|
const service = await installMacConnectorService({
|
|
415
437
|
nodeExecutable: process.execPath,
|
|
416
438
|
entrypoint: fileURLToPath(import.meta.url),
|
|
@@ -418,11 +440,9 @@ async function install(flags, ui) {
|
|
|
418
440
|
credentialFile: flags["credential-file"] ?? defaultCredentialFile(),
|
|
419
441
|
});
|
|
420
442
|
if (ui.interactive) {
|
|
421
|
-
ui.stopWait("
|
|
422
|
-
ui.
|
|
423
|
-
ui.
|
|
424
|
-
ui.info("Commands", "status to inspect · stop to pause · uninstall to remove local setup");
|
|
425
|
-
ui.info("You are done", "the Connector now waits in the background");
|
|
443
|
+
ui.stopWait("Background", "running at login");
|
|
444
|
+
ui.complete("You're all set", "Re-entry is connected and waiting for approved work.");
|
|
445
|
+
ui.next("re-entry status", "Check the connection at any time.");
|
|
426
446
|
} else {
|
|
427
447
|
process.stdout.write(`${JSON.stringify({
|
|
428
448
|
event: "connector_service_installed",
|
|
@@ -435,12 +455,13 @@ async function install(flags, ui) {
|
|
|
435
455
|
}
|
|
436
456
|
|
|
437
457
|
async function start(flags, ui) {
|
|
438
|
-
if (ui.interactive) {
|
|
439
|
-
ui.begin("Re-entry is starting", "Connect once, then wait quietly for work you approve");
|
|
440
|
-
}
|
|
441
458
|
const runtimeFlags = await withWorkspaceDirectory(flags, ui);
|
|
442
459
|
const readiness = inspectReadiness(runtimeFlags);
|
|
443
|
-
|
|
460
|
+
if (ui.interactive) {
|
|
461
|
+
ui.begin("Start Re-entry", "Wait for work you have approved.");
|
|
462
|
+
ui.section("READY", "This Mac");
|
|
463
|
+
showReadiness(readiness, ui);
|
|
464
|
+
}
|
|
444
465
|
if (!ui.interactive) {
|
|
445
466
|
process.stdout.write(`${JSON.stringify({
|
|
446
467
|
event: "connector_ready",
|
|
@@ -726,6 +747,14 @@ function defaultDeviceName() {
|
|
|
726
747
|
: "This Mac";
|
|
727
748
|
}
|
|
728
749
|
|
|
750
|
+
function displayReceiver(origin) {
|
|
751
|
+
try {
|
|
752
|
+
return new URL(origin).host;
|
|
753
|
+
} catch {
|
|
754
|
+
return origin;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
729
758
|
function readBoundedNumber(value, minimum, maximum, code) {
|
|
730
759
|
const number = typeof value === "number" ? value : Number(value);
|
|
731
760
|
if (!Number.isSafeInteger(number) || number < minimum || number > maximum) {
|
package/src/terminal-ui.mjs
CHANGED
|
@@ -8,19 +8,12 @@ const YELLOW = "\u001b[33m";
|
|
|
8
8
|
const RED = "\u001b[31m";
|
|
9
9
|
const CYAN = "\u001b[36m";
|
|
10
10
|
const SPINNER_FRAMES = ["·", "✦", "✧", "✦"];
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
];
|
|
18
|
-
const REENTRY_PANEL = [
|
|
19
|
-
" +-----------------------------------------------+",
|
|
20
|
-
" | RE-ENTRY |",
|
|
21
|
-
" | LOCAL CONNECTOR |",
|
|
22
|
-
" +-----------------------------------------------+",
|
|
23
|
-
];
|
|
11
|
+
const RULE = " ─────────────────────────────────────────────";
|
|
12
|
+
|
|
13
|
+
export const REENTRY_WORDMARK = Object.freeze([
|
|
14
|
+
" █▀█ █▀▀ █▀▀ █▄░█ ▀█▀ █▀█ █▄█",
|
|
15
|
+
" █▀▄ ██▄ ▀ ██▄ █░▀█ ░█░ █▀▄ ░█░",
|
|
16
|
+
]);
|
|
24
17
|
|
|
25
18
|
/**
|
|
26
19
|
* Small dependency-free terminal presentation for the Local Connector CLI.
|
|
@@ -36,7 +29,7 @@ export function createTerminalUi(options = {}) {
|
|
|
36
29
|
let spinnerFrame = 0;
|
|
37
30
|
|
|
38
31
|
const style = (value, code) => color ? `${code}${value}${RESET}` : value;
|
|
39
|
-
const write = (value) => output.write(`${value}\n`);
|
|
32
|
+
const write = (value = "") => output.write(`${value}\n`);
|
|
40
33
|
const clearSpinnerLine = () => {
|
|
41
34
|
if (spinnerTimer === null) return;
|
|
42
35
|
output.write("\r\u001b[2K");
|
|
@@ -44,44 +37,56 @@ export function createTerminalUi(options = {}) {
|
|
|
44
37
|
spinnerTimer = null;
|
|
45
38
|
};
|
|
46
39
|
const renderSpinner = () => {
|
|
47
|
-
output.write(`\r\u001b[2K ${style(SPINNER_FRAMES[spinnerFrame], CYAN)}
|
|
40
|
+
output.write(`\r\u001b[2K ${style(SPINNER_FRAMES[spinnerFrame], CYAN)} ${spinnerMessage}`);
|
|
48
41
|
spinnerFrame = (spinnerFrame + 1) % SPINNER_FRAMES.length;
|
|
49
42
|
};
|
|
43
|
+
const renderState = (symbol, symbolColor, label, detail) => {
|
|
44
|
+
clearSpinnerLine();
|
|
45
|
+
write(` ${style(symbol, symbolColor)} ${style(label, BOLD)}${detail ? ` ${style(detail, DIM)}` : ""}`);
|
|
46
|
+
};
|
|
50
47
|
|
|
51
48
|
return Object.freeze({
|
|
52
49
|
interactive,
|
|
53
50
|
|
|
54
51
|
begin(title, subtitle) {
|
|
55
52
|
if (!interactive) return;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
write(
|
|
59
|
-
|
|
60
|
-
write(
|
|
61
|
-
write(` ${style("RE-ENTRY", BOLD)} ${style("LOCAL CONNECTOR", DIM)}`);
|
|
53
|
+
clearSpinnerLine();
|
|
54
|
+
write();
|
|
55
|
+
for (const line of REENTRY_WORDMARK) write(style(line, `${BOLD}${CYAN}`));
|
|
56
|
+
write(` ${style("LOCAL CONNECTOR", DIM)}`);
|
|
57
|
+
write();
|
|
62
58
|
write(` ${style(title, BOLD)}`);
|
|
63
59
|
if (subtitle) write(` ${style(subtitle, DIM)}`);
|
|
64
|
-
write(
|
|
60
|
+
write(RULE);
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
section(step, title, detail) {
|
|
64
|
+
if (!interactive) return;
|
|
65
|
+
clearSpinnerLine();
|
|
66
|
+
write();
|
|
67
|
+
write(` ${style(step, CYAN)} ${style(title, BOLD)}`);
|
|
68
|
+
if (detail) write(` ${style(detail, DIM)}`);
|
|
69
|
+
write();
|
|
65
70
|
},
|
|
66
71
|
|
|
67
72
|
step(label, detail) {
|
|
68
73
|
if (!interactive) return;
|
|
69
|
-
|
|
74
|
+
renderState("→", CYAN, label, detail);
|
|
70
75
|
},
|
|
71
76
|
|
|
72
77
|
success(label, detail) {
|
|
73
78
|
if (!interactive) return;
|
|
74
|
-
|
|
79
|
+
renderState("✓", GREEN, label, detail);
|
|
75
80
|
},
|
|
76
81
|
|
|
77
82
|
info(label, detail) {
|
|
78
83
|
if (!interactive) return;
|
|
79
|
-
|
|
84
|
+
renderState("·", CYAN, label, detail);
|
|
80
85
|
},
|
|
81
86
|
|
|
82
87
|
warning(label, detail) {
|
|
83
88
|
if (!interactive) return;
|
|
84
|
-
|
|
89
|
+
renderState("!", YELLOW, label, detail);
|
|
85
90
|
},
|
|
86
91
|
|
|
87
92
|
wait(message) {
|
|
@@ -97,16 +102,38 @@ export function createTerminalUi(options = {}) {
|
|
|
97
102
|
stopWait(label, detail, outcome = "success") {
|
|
98
103
|
if (!interactive) return;
|
|
99
104
|
clearSpinnerLine();
|
|
100
|
-
if (outcome === "warning")
|
|
101
|
-
else if (outcome === "info")
|
|
102
|
-
else
|
|
105
|
+
if (outcome === "warning") renderState("!", YELLOW, label, detail);
|
|
106
|
+
else if (outcome === "info") renderState("·", CYAN, label, detail);
|
|
107
|
+
else renderState("✓", GREEN, label, detail);
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
complete(title, detail) {
|
|
111
|
+
if (!interactive) return;
|
|
112
|
+
clearSpinnerLine();
|
|
113
|
+
write();
|
|
114
|
+
write(` ${style("✓", GREEN)} ${style(title, BOLD)}`);
|
|
115
|
+
if (detail) write(` ${style(detail, DIM)}`);
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
next(command, detail) {
|
|
119
|
+
if (!interactive) return;
|
|
120
|
+
clearSpinnerLine();
|
|
121
|
+
write();
|
|
122
|
+
write(` ${style("NEXT", CYAN)}`);
|
|
123
|
+
write(` ${style("$", CYAN)} ${style(command, BOLD)}`);
|
|
124
|
+
if (detail) write(` ${style(detail, DIM)}`);
|
|
125
|
+
write();
|
|
103
126
|
},
|
|
104
127
|
|
|
105
128
|
error(label, detail, hint) {
|
|
106
129
|
if (!interactive) return;
|
|
107
130
|
clearSpinnerLine();
|
|
108
|
-
errorOutput.write(
|
|
109
|
-
if (
|
|
131
|
+
errorOutput.write(`\n ${style("✕", RED)} ${style(label, BOLD)}\n`);
|
|
132
|
+
if (detail) errorOutput.write(` ${style(detail, DIM)}\n`);
|
|
133
|
+
if (hint) {
|
|
134
|
+
errorOutput.write(`\n ${style("NEXT", CYAN)}\n`);
|
|
135
|
+
errorOutput.write(` ${style("→", CYAN)} ${hint}\n\n`);
|
|
136
|
+
}
|
|
110
137
|
},
|
|
111
138
|
|
|
112
139
|
close() {
|
package/src/workspace-picker.mjs
CHANGED
|
@@ -4,6 +4,8 @@ import { dirname, join, resolve } from "node:path";
|
|
|
4
4
|
import { emitKeypressEvents } from "node:readline";
|
|
5
5
|
import process from "node:process";
|
|
6
6
|
|
|
7
|
+
import { REENTRY_WORDMARK } from "./terminal-ui.mjs";
|
|
8
|
+
|
|
7
9
|
const MAX_VISIBLE_DIRECTORIES = 12;
|
|
8
10
|
|
|
9
11
|
/**
|
|
@@ -77,7 +79,7 @@ export async function chooseWorkspaceDirectory(options = {}) {
|
|
|
77
79
|
}
|
|
78
80
|
} finally {
|
|
79
81
|
input.setRawMode(wasRaw);
|
|
80
|
-
output.write("\
|
|
82
|
+
output.write("\u001b[2J\u001b[H");
|
|
81
83
|
}
|
|
82
84
|
}
|
|
83
85
|
|
|
@@ -158,19 +160,22 @@ async function readableDirectories(directory) {
|
|
|
158
160
|
|
|
159
161
|
function renderPicker(output, { title, current, choices, selected, truncated = false }) {
|
|
160
162
|
output.write("\u001b[2J\u001b[H");
|
|
161
|
-
output.write(
|
|
162
|
-
output.write(
|
|
163
|
-
|
|
163
|
+
for (const line of REENTRY_WORDMARK) output.write(`${line}\n`);
|
|
164
|
+
output.write(" LOCAL CONNECTOR\n\n");
|
|
165
|
+
output.write(" 1 OF 3 WORKSPACE\n");
|
|
166
|
+
output.write(` ${title}\n`);
|
|
167
|
+
if (current) output.write(` Current: ${current}\n`);
|
|
168
|
+
output.write(" ─────────────────────────────────────────────\n");
|
|
164
169
|
output.write("\n");
|
|
165
170
|
for (let index = 0; index < choices.length; index += 1) {
|
|
166
171
|
const choice = choices[index];
|
|
167
|
-
output.write(
|
|
168
|
-
if (choice.detail) output.write(`
|
|
172
|
+
output.write(` ${index === selected ? "❯" : " "} ${choice.label}\n`);
|
|
173
|
+
if (choice.detail) output.write(` ${choice.detail}\n`);
|
|
169
174
|
}
|
|
170
175
|
if (truncated) {
|
|
171
|
-
output.write(`\
|
|
176
|
+
output.write(`\n Showing the first ${MAX_VISIBLE_DIRECTORIES} folders.\n`);
|
|
172
177
|
}
|
|
173
|
-
output.write("\n
|
|
178
|
+
output.write("\n ↑↓ Move Enter Select Esc Cancel");
|
|
174
179
|
}
|
|
175
180
|
|
|
176
181
|
function readKey(input) {
|