@dutodev/cli 0.5.0 → 0.7.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
CHANGED
|
@@ -145,13 +145,39 @@ Com os túneis no ar, o terminal mostra cada túnel (URL, destino local, requisi
|
|
|
145
145
|
| `m` | Mostra o QR code do túnel selecionado, para abrir no celular |
|
|
146
146
|
| `f` | Filtra por túnel, método, caminho ou status |
|
|
147
147
|
| `↵` | Detalhes da requisição: cabeçalhos e o começo dos corpos |
|
|
148
|
+
| `r` | No detalhe, reenvia a requisição ao serviço local |
|
|
149
|
+
| `i` | Abre o inspetor no navegador |
|
|
148
150
|
| `tab`, `↑↓` | Alterna entre túneis e requisições e move a seleção |
|
|
149
151
|
| `q` ou Ctrl+C | Fecha os túneis e sai |
|
|
150
152
|
|
|
153
|
+
**Reenviar uma requisição.** No detalhe (`↵`), a tecla `r` manda de novo ao serviço local o mesmo método, caminho, cabeçalhos e corpo que o app recebeu, já com as regras `host:` e `headers:` do túnel aplicadas e sem as credenciais do próprio Duto. O reenvio entra na lista marcado com `↻`, a tela pula para o detalhe dele (`↻ reenvio de #N`) e `esc` volta. Serve para depurar webhooks sem esperar o serviço externo mandar de novo; em especial, uma requisição que ficou com `503` porque o serviço local estava fora do ar pode ser reenviada assim que ele subir. Como os bytes são idênticos, assinaturas de webhook continuam válidas. Já apps que recusam eventos antigos (o Stripe tolera 5 minutos por padrão) vão rejeitar o reenvio depois dessa janela.
|
|
154
|
+
|
|
155
|
+
Não dá para reenviar WebSocket, requisições barradas pela senha do túnel, corpos binários, corpos acima de 64 KB, requisições cujo corpo não terminou de chegar ou quando o túnel já não está aberto; nesses casos uma linha explica o motivo. O reenvio não existe no log simples (sem terminal interativo).
|
|
156
|
+
|
|
151
157
|
Se o túnel está no ar mas nada responde na porta local, quem abrir o endereço no navegador vê uma página do Duto que volta sozinha quando o serviço sobe. Ela espera por uma conexão só (SSE): a CLI checa a porta na sua máquina e avisa a página assim que o serviço responde, sem recarregar de tempos em tempos. Clientes que não são navegador (`curl`, APIs) recebem a mesma resposta em texto. Em ambos os casos o status é `503` com `Retry-After: 3`. Os caminhos `/__duto/…` são reservados para isso e nunca chegam ao seu app.
|
|
152
158
|
|
|
153
159
|
Fora de um terminal interativo (pipe, CI) ou com `--no-ui`, a CLI imprime um log simples. No Windows a tela cheia vem desligada; use `--ui` para tentar.
|
|
154
160
|
|
|
161
|
+
## Inspetor no navegador
|
|
162
|
+
|
|
163
|
+
Junto com a sessão, a CLI sobe um inspetor local: uma página no navegador com as requisições chegando ao vivo, o JSON formatado e botões para copiar e reenviar. Ele é mais confortável que o terminal para corpos grandes.
|
|
164
|
+
|
|
165
|
+
- **Como abrir.** Na tela cheia, a tecla `i` abre o navegador já com o endereço certo, e a tela mostra `inspetor http://127.0.0.1:<porta>`. No modo `plain` (pipe, CI, `--no-ui`), a CLI imprime `inspetor: <url>?t=<token>`; copie esse endereço inteiro.
|
|
166
|
+
- **Porta.** O padrão é `127.0.0.1:4141`. Se estiver ocupada, a CLI tenta as seguintes até a 4161 e, por fim, qualquer porta livre. O inspetor escuta só em `127.0.0.1`: não é acessível por outras máquinas da rede.
|
|
167
|
+
- **Token.** O endereço leva um token da sessão (`?t=...`). Ele existe para que outros sites abertos no seu navegador não consigam ler nem reenviar as suas requisições. Por isso a tela cheia não exibe o token; use a tecla `i`.
|
|
168
|
+
- **Desligar.** `--no-inspect` não sobe o inspetor. No modo `plain` o endereço com token vai para a saída; se ela vai para um log compartilhado, use `--no-inspect`.
|
|
169
|
+
- **Nada vai para o disco.** As requisições ficam só na memória da sessão e somem quando ela termina. Funciona sem internet.
|
|
170
|
+
|
|
171
|
+
A página tem a lista à esquerda e o detalhe à direita, com as abas **Corpo**, **Cabeçalhos**, **Resposta** e **Raw**. O JSON aparece reindentado, exatamente como chegou, com os erros de sintaxe destacados.
|
|
172
|
+
|
|
173
|
+
| Ação | Como |
|
|
174
|
+
|---|---|
|
|
175
|
+
| Copiar o corpo | **Copiar corpo** copia o corpo da aba ativa |
|
|
176
|
+
| Copiar como `curl` | **Copiar como curl**; em túnel com senha, o comando leva `-H 'X-Duto-Auth: <usuário>:SENHA'` e você troca `SENHA` pela senha |
|
|
177
|
+
| Reenviar | **↻ Reenviar** ou a tecla `r`, com as mesmas regras do terminal |
|
|
178
|
+
| Filtrar | `/` foca o filtro |
|
|
179
|
+
| Navegar | `↑` e `↓` movem a seleção |
|
|
180
|
+
|
|
155
181
|
## Comandos
|
|
156
182
|
|
|
157
183
|
| Comando | O que faz |
|
|
@@ -171,6 +197,7 @@ Fora de um terminal interativo (pipe, CI) ou com `--no-ui`, a CLI imprime um log
|
|
|
171
197
|
| `--auth <usuario:senha>` | Pede usuário e senha para abrir o endereço (repetível) |
|
|
172
198
|
| `--permitir <faixa>` | Com `--auth`: IP, faixa ou nome (`stripe`, `github`) que entra sem senha (repetível) |
|
|
173
199
|
| `--no-ui` / `--ui` | Desliga / força a tela cheia |
|
|
200
|
+
| `--no-inspect` | Não sobe o inspetor no navegador (a tecla `i` passa a mostrar "Inspetor desligado (--no-inspect).") |
|
|
174
201
|
|
|
175
202
|
## Configuração
|
|
176
203
|
|
|
@@ -38,6 +38,37 @@ function validateName(name, max = MAX_NAME) {
|
|
|
38
38
|
return { ok: true };
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
// src/ui/theme.ts
|
|
42
|
+
var C = {
|
|
43
|
+
signal: "#2dd4bf",
|
|
44
|
+
muted: "#8a8f8b",
|
|
45
|
+
strong: "#f3f4f1",
|
|
46
|
+
ok: "#4ade80",
|
|
47
|
+
warn: "#facc15",
|
|
48
|
+
err: "#f87171"
|
|
49
|
+
};
|
|
50
|
+
var TUNNEL_COLORS = ["#93c5fd", "#c4b5fd", "#facc15", "#4ade80", "#f9a8d4", "#fdba74", "#67e8f9"];
|
|
51
|
+
function tunnelColor(index) {
|
|
52
|
+
return TUNNEL_COLORS[index % TUNNEL_COLORS.length];
|
|
53
|
+
}
|
|
54
|
+
function fit(text, width) {
|
|
55
|
+
if (width <= 0) return "";
|
|
56
|
+
return text.length > width ? `${text.slice(0, Math.max(0, width - 1))}\u2026` : text.padEnd(width);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/session/bodyNote.ts
|
|
60
|
+
var TRUNCATED_NOTE = "Corpo cortado em 64 KB.";
|
|
61
|
+
function bodyNote(r, side) {
|
|
62
|
+
if (side === "req") {
|
|
63
|
+
if (r.reqBodyBytes === 0) return "Sem corpo.";
|
|
64
|
+
if (r.reqBody === null && !r.reqTruncated) return "Corpo bin\xE1rio; n\xE3o \xE9 guardado.";
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
if (r.bytes === 0) return "Sem corpo.";
|
|
68
|
+
if (r.resBody === null) return "Corpo bin\xE1rio; n\xE3o \xE9 guardado.";
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
41
72
|
// src/ui/format.ts
|
|
42
73
|
function statusColor(status) {
|
|
43
74
|
if (status === null) return "muted";
|
|
@@ -66,6 +97,11 @@ function localLabel(t) {
|
|
|
66
97
|
export {
|
|
67
98
|
normalizeName,
|
|
68
99
|
validateName,
|
|
100
|
+
C,
|
|
101
|
+
tunnelColor,
|
|
102
|
+
fit,
|
|
103
|
+
TRUNCATED_NOTE,
|
|
104
|
+
bodyNote,
|
|
69
105
|
statusColor,
|
|
70
106
|
formatDuration,
|
|
71
107
|
formatBytes,
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
C,
|
|
4
|
+
TRUNCATED_NOTE,
|
|
5
|
+
bodyNote,
|
|
6
|
+
fit,
|
|
3
7
|
formatBytes,
|
|
4
8
|
formatDuration,
|
|
5
9
|
formatTime,
|
|
6
10
|
localLabel,
|
|
7
11
|
normalizeName,
|
|
8
12
|
statusColor,
|
|
13
|
+
tunnelColor,
|
|
9
14
|
validateName
|
|
10
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-74IJFYGT.js";
|
|
11
16
|
|
|
12
17
|
// src/ui/fullscreen.tsx
|
|
13
18
|
import { render } from "ink";
|
|
@@ -18,30 +23,10 @@ import { Box as Box5, Text as Text6, useInput, useStdout } from "ink";
|
|
|
18
23
|
|
|
19
24
|
// src/ui/Detail.tsx
|
|
20
25
|
import { Box, Text } from "ink";
|
|
21
|
-
|
|
22
|
-
// src/ui/theme.ts
|
|
23
|
-
var C = {
|
|
24
|
-
signal: "#2dd4bf",
|
|
25
|
-
muted: "#8a8f8b",
|
|
26
|
-
strong: "#f3f4f1",
|
|
27
|
-
ok: "#4ade80",
|
|
28
|
-
warn: "#facc15",
|
|
29
|
-
err: "#f87171"
|
|
30
|
-
};
|
|
31
|
-
var TUNNEL_COLORS = ["#93c5fd", "#c4b5fd", "#facc15", "#4ade80", "#f9a8d4", "#fdba74", "#67e8f9"];
|
|
32
|
-
function tunnelColor(index) {
|
|
33
|
-
return TUNNEL_COLORS[index % TUNNEL_COLORS.length];
|
|
34
|
-
}
|
|
35
|
-
function fit(text, width) {
|
|
36
|
-
if (width <= 0) return "";
|
|
37
|
-
return text.length > width ? `${text.slice(0, Math.max(0, width - 1))}\u2026` : text.padEnd(width);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// src/ui/Detail.tsx
|
|
41
26
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
42
27
|
var MAX_HEADERS = 12;
|
|
43
28
|
var MAX_BODY_LINES = 8;
|
|
44
|
-
function Section({ title, headers, body, truncated }) {
|
|
29
|
+
function Section({ title, headers, body, truncated, note: note2 }) {
|
|
45
30
|
const entries = Object.entries(headers);
|
|
46
31
|
const lines = body ? body.split("\n") : [];
|
|
47
32
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
|
|
@@ -59,9 +44,9 @@ function Section({ title, headers, body, truncated }) {
|
|
|
59
44
|
entries.length - MAX_HEADERS,
|
|
60
45
|
" cabe\xE7alhos"
|
|
61
46
|
] }),
|
|
62
|
-
body === null ? /* @__PURE__ */ jsx(Text, { color: C.muted, children:
|
|
47
|
+
body === null ? /* @__PURE__ */ jsx(Text, { color: C.muted, children: note2 ?? "" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
63
48
|
lines.slice(0, MAX_BODY_LINES).map((l, i) => /* @__PURE__ */ jsx(Text, { wrap: "truncate-end", children: l }, i)),
|
|
64
|
-
(lines.length > MAX_BODY_LINES || truncated) && /* @__PURE__ */ jsx(Text, { color: C.muted, children: truncated ?
|
|
49
|
+
(lines.length > MAX_BODY_LINES || truncated) && /* @__PURE__ */ jsx(Text, { color: C.muted, children: truncated ? TRUNCATED_NOTE : "\u2026" })
|
|
65
50
|
] })
|
|
66
51
|
] });
|
|
67
52
|
}
|
|
@@ -84,12 +69,16 @@ function Detail({ record }) {
|
|
|
84
69
|
record.tunnel
|
|
85
70
|
] })
|
|
86
71
|
] }),
|
|
72
|
+
record.replayOf !== void 0 && /* @__PURE__ */ jsxs(Text, { color: C.muted, children: [
|
|
73
|
+
"\u21BB reenvio de #",
|
|
74
|
+
record.replayOf
|
|
75
|
+
] }),
|
|
87
76
|
record.error && /* @__PURE__ */ jsx(Text, { color: C.err, children: record.error }),
|
|
88
|
-
/* @__PURE__ */ jsx(Section, { title: "Requisi\xE7\xE3o", headers: record.reqHeaders, body: record.reqBody, truncated: record.
|
|
89
|
-
/* @__PURE__ */ jsx(Section, { title: "Resposta", headers: record.resHeaders, body: record.resBody, truncated: record.
|
|
77
|
+
/* @__PURE__ */ jsx(Section, { title: "Requisi\xE7\xE3o", headers: record.reqHeaders, body: record.reqBody, truncated: record.reqTruncated, note: bodyNote(record, "req") }),
|
|
78
|
+
/* @__PURE__ */ jsx(Section, { title: "Resposta", headers: record.resHeaders, body: record.resBody, truncated: record.resTruncated, note: bodyNote(record, "res") }),
|
|
90
79
|
/* @__PURE__ */ jsxs(Text, { color: C.muted, children: [
|
|
91
80
|
"\n",
|
|
92
|
-
"esc volta para a lista"
|
|
81
|
+
"r reenvia \xB7 esc volta para a lista"
|
|
93
82
|
] })
|
|
94
83
|
] });
|
|
95
84
|
}
|
|
@@ -189,7 +178,7 @@ function RequestList(props) {
|
|
|
189
178
|
const start = Math.max(0, Math.min(selected - Math.floor(height / 2), records.length - height));
|
|
190
179
|
const visible = records.slice(start, start + height);
|
|
191
180
|
const accessW = Math.max(0, ...visible.map((r) => (accessLabel(r.access)?.text.length ?? -1) + 1));
|
|
192
|
-
const pathW = Math.max(10, width -
|
|
181
|
+
const pathW = Math.max(10, width - 54 - accessW);
|
|
193
182
|
const title = props.filter ? `REQUISI\xC7\xD5ES \xB7 filtro: ${props.filter} ` : "REQUISI\xC7\xD5ES ";
|
|
194
183
|
return /* @__PURE__ */ jsxs4(Box3, { flexDirection: "column", children: [
|
|
195
184
|
/* @__PURE__ */ jsx4(Text4, { color: C.muted, children: title + "\u2500".repeat(Math.max(0, width - title.length)) }),
|
|
@@ -204,6 +193,7 @@ function RequestList(props) {
|
|
|
204
193
|
" ",
|
|
205
194
|
/* @__PURE__ */ jsx4(Text4, { color: tunnelColor(props.tunnelIndex(r.tunnel)), children: fit(r.tunnel, 9) }),
|
|
206
195
|
" ",
|
|
196
|
+
/* @__PURE__ */ jsx4(Text4, { color: C.muted, children: r.replayOf !== void 0 ? "\u21BB " : " " }),
|
|
207
197
|
/* @__PURE__ */ jsx4(Text4, { bold: true, children: fit(method, 5) }),
|
|
208
198
|
fit(r.path, pathW),
|
|
209
199
|
" ",
|
|
@@ -286,13 +276,14 @@ var KEYS = [
|
|
|
286
276
|
["n", "novo t\xFAnel"],
|
|
287
277
|
["c", "copiar URL"],
|
|
288
278
|
["o", "abrir no navegador"],
|
|
279
|
+
["i", "inspetor"],
|
|
289
280
|
["m", "QR no celular"],
|
|
290
281
|
["f", "filtrar"],
|
|
291
282
|
["\u21B5", "detalhes"],
|
|
292
283
|
["tab", "foco"],
|
|
293
284
|
["q", "sair"]
|
|
294
285
|
];
|
|
295
|
-
function App({ session, version, system, onExit }) {
|
|
286
|
+
function App({ session, version, system, onExit, inspector = null, inspectorError }) {
|
|
296
287
|
const subState = useCallback((fn) => session.subscribe(fn), [session]);
|
|
297
288
|
const subRecords = useCallback((fn) => session.records.subscribe(fn), [session]);
|
|
298
289
|
const state = useSyncExternalStore(subState, () => session.getState());
|
|
@@ -305,7 +296,7 @@ function App({ session, version, system, onExit }) {
|
|
|
305
296
|
const [focus, setFocus] = useState("requests");
|
|
306
297
|
const [selReq, setSelReq] = useState(0);
|
|
307
298
|
const [selTun, setSelTun] = useState(0);
|
|
308
|
-
const [message, setMessage] = useState(null);
|
|
299
|
+
const [message, setMessage] = useState(inspectorError ? { text: `Inspetor n\xE3o subiu: ${inspectorError}`, tone: "warn" } : null);
|
|
309
300
|
const [now, setNow] = useState(() => Date.now());
|
|
310
301
|
const quitting = useRef(false);
|
|
311
302
|
const records = useMemo(() => {
|
|
@@ -316,6 +307,9 @@ function App({ session, version, system, onExit }) {
|
|
|
316
307
|
useEffect(() => {
|
|
317
308
|
if (selReq >= records.length) setSelReq(Math.max(0, records.length - 1));
|
|
318
309
|
}, [records.length, selReq]);
|
|
310
|
+
useEffect(() => {
|
|
311
|
+
if (mode.kind === "detail" && !all.some((r) => r.id === mode.id)) setMode({ kind: "list" });
|
|
312
|
+
}, [mode, all]);
|
|
319
313
|
useEffect(() => {
|
|
320
314
|
if (!message) return;
|
|
321
315
|
const t = setTimeout(() => setMessage(null), 4e3);
|
|
@@ -342,6 +336,11 @@ function App({ session, version, system, onExit }) {
|
|
|
342
336
|
await session.stop();
|
|
343
337
|
onExit();
|
|
344
338
|
}
|
|
339
|
+
async function openInspector() {
|
|
340
|
+
if (!inspector) return setMessage({ text: "Inspetor desligado (--no-inspect).", tone: "warn" });
|
|
341
|
+
const ok = await system.open(inspector.openUrl);
|
|
342
|
+
setMessage(ok ? { text: "Abrindo o inspetor", tone: "signal" } : { text: "N\xE3o consegui abrir o navegador.", tone: "warn" });
|
|
343
|
+
}
|
|
345
344
|
async function act(kind) {
|
|
346
345
|
const t = currentTunnel();
|
|
347
346
|
if (!t?.url) return setMessage({ text: "Esse t\xFAnel ainda n\xE3o tem URL.", tone: "warn" });
|
|
@@ -396,7 +395,12 @@ function App({ session, version, system, onExit }) {
|
|
|
396
395
|
return;
|
|
397
396
|
}
|
|
398
397
|
if (mode.kind === "detail") {
|
|
399
|
-
if (key.escape || key.return || input === "q") setMode({ kind: "list" });
|
|
398
|
+
if (key.escape || key.return || input === "q") return setMode({ kind: "list" });
|
|
399
|
+
if (input === "r") {
|
|
400
|
+
const res = session.replay(mode.id);
|
|
401
|
+
if (res.ok) setMode({ kind: "detail", id: res.id });
|
|
402
|
+
else setMessage({ text: res.reason, tone: "warn" });
|
|
403
|
+
}
|
|
400
404
|
return;
|
|
401
405
|
}
|
|
402
406
|
if (mode.kind === "qr") {
|
|
@@ -419,18 +423,19 @@ function App({ session, version, system, onExit }) {
|
|
|
419
423
|
else setSelReq((i) => Math.min(Math.max(0, i + d), Math.max(0, records.length - 1)));
|
|
420
424
|
return;
|
|
421
425
|
}
|
|
422
|
-
if (key.return && focus === "requests" && records[selReq]) return setMode({ kind: "detail" });
|
|
426
|
+
if (key.return && focus === "requests" && records[selReq]) return setMode({ kind: "detail", id: records[selReq].id });
|
|
423
427
|
if (key.escape && filter) return setFilter("");
|
|
424
428
|
if (input === "c") return void act("copy");
|
|
425
429
|
if (input === "o") return void act("open");
|
|
430
|
+
if (input === "i") return void openInspector();
|
|
426
431
|
if (input === "m") return showQr();
|
|
427
432
|
if (input === "f") return setMode({ kind: "filter", value: filter });
|
|
428
433
|
if (input === "n") return setMode({ kind: "port", value: "" });
|
|
429
434
|
});
|
|
430
435
|
const live = state.tunnels.filter((t) => t.status === "live").length;
|
|
431
436
|
const conn = state.connection === "online" ? { text: "conectado \u25CF", color: C.signal } : state.connection === "connecting" ? { text: "conectando\u2026", color: C.muted } : { text: "reconectando\u2026", color: C.warn };
|
|
432
|
-
const listHeight = Math.max(3, rows - state.tunnels.length - 9);
|
|
433
|
-
const
|
|
437
|
+
const listHeight = Math.max(3, rows - state.tunnels.length - (inspector ? 10 : 9));
|
|
438
|
+
const detailRecord = mode.kind === "detail" ? all.find((r) => r.id === mode.id) : void 0;
|
|
434
439
|
const qrTunnel = mode.kind === "qr" ? state.tunnels.find((t) => t.key === mode.key) : void 0;
|
|
435
440
|
return /* @__PURE__ */ jsxs6(Box5, { flexDirection: "column", width, children: [
|
|
436
441
|
/* @__PURE__ */ jsxs6(Box5, { justifyContent: "space-between", children: [
|
|
@@ -456,8 +461,9 @@ function App({ session, version, system, onExit }) {
|
|
|
456
461
|
] }),
|
|
457
462
|
/* @__PURE__ */ jsx6(Text6, { children: " " }),
|
|
458
463
|
/* @__PURE__ */ jsx6(TunnelTable, { tunnels: state.tunnels, selected: selTun, focused: focus === "tunnels" }),
|
|
464
|
+
inspector && /* @__PURE__ */ jsx6(Text6, { color: C.muted, children: `inspetor ${inspector.url}` }),
|
|
459
465
|
/* @__PURE__ */ jsx6(Text6, { children: " " }),
|
|
460
|
-
mode.kind === "detail" &&
|
|
466
|
+
mode.kind === "detail" && detailRecord ? /* @__PURE__ */ jsx6(Detail, { record: detailRecord }) : mode.kind === "qr" && qrTunnel ? /* @__PURE__ */ jsx6(QrView, { tunnel: qrTunnel, link: mode.link, now }) : /* @__PURE__ */ jsx6(RequestList, { records, tunnelIndex, selected: selReq, focused: focus === "requests", height: listHeight, width, filter }),
|
|
461
467
|
/* @__PURE__ */ jsx6(Text6, { children: " " }),
|
|
462
468
|
mode.kind === "filter" && /* @__PURE__ */ jsx6(Prompt, { label: "Filtrar (t\xFAnel, m\xE9todo, caminho ou status):", value: mode.value, hint: "\u21B5 aplica \xB7 esc cancela" }),
|
|
463
469
|
mode.kind === "port" && /* @__PURE__ */ jsx6(Prompt, { label: "Novo t\xFAnel \u2014 porta local:", value: mode.value, hint: "\u21B5 continua \xB7 esc cancela" }),
|
|
@@ -541,7 +547,7 @@ var realSystem = {
|
|
|
541
547
|
|
|
542
548
|
// src/ui/fullscreen.tsx
|
|
543
549
|
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
544
|
-
function runFullscreen(session, specs, version) {
|
|
550
|
+
function runFullscreen(session, specs, version, inspector, inspectorError) {
|
|
545
551
|
return withFullscreen(
|
|
546
552
|
process.stdout,
|
|
547
553
|
(signal) => new Promise((resolve) => {
|
|
@@ -552,7 +558,7 @@ function runFullscreen(session, specs, version) {
|
|
|
552
558
|
app.unmount();
|
|
553
559
|
resolve(code);
|
|
554
560
|
};
|
|
555
|
-
const app = render(/* @__PURE__ */ jsx7(App, { session, version, system: realSystem, onExit: () => finish(0) }), { exitOnCtrlC: false });
|
|
561
|
+
const app = render(/* @__PURE__ */ jsx7(App, { session, version, system: realSystem, inspector, inspectorError, onExit: () => finish(0) }), { exitOnCtrlC: false });
|
|
556
562
|
signal.addEventListener("abort", () => void session.stop().then(() => finish(0)), { once: true });
|
|
557
563
|
session.start(specs).catch(() => void 0);
|
|
558
564
|
})
|