@dutodev/cli 0.6.0 → 1.0.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 +121 -99
- package/dist/{chunk-26EQC6HB.js → chunk-BPOXISIM.js} +38 -2
- package/dist/{fullscreen-7P5GBCQX.js → fullscreen-IWZCH3IP.js} +69 -76
- package/dist/index.js +1094 -198
- package/package.json +3 -2
|
@@ -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-BPOXISIM.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: [
|
|
@@ -55,13 +40,13 @@ function Section({ title, headers, body, truncated }) {
|
|
|
55
40
|
v
|
|
56
41
|
] }, k)),
|
|
57
42
|
entries.length > MAX_HEADERS && /* @__PURE__ */ jsxs(Text, { color: C.muted, children: [
|
|
58
|
-
"\u2026
|
|
43
|
+
"\u2026 ",
|
|
59
44
|
entries.length - MAX_HEADERS,
|
|
60
|
-
"
|
|
45
|
+
" more headers"
|
|
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
|
}
|
|
@@ -85,15 +70,15 @@ function Detail({ record }) {
|
|
|
85
70
|
] })
|
|
86
71
|
] }),
|
|
87
72
|
record.replayOf !== void 0 && /* @__PURE__ */ jsxs(Text, { color: C.muted, children: [
|
|
88
|
-
"\u21BB
|
|
73
|
+
"\u21BB replay of #",
|
|
89
74
|
record.replayOf
|
|
90
75
|
] }),
|
|
91
76
|
record.error && /* @__PURE__ */ jsx(Text, { color: C.err, children: record.error }),
|
|
92
|
-
/* @__PURE__ */ jsx(Section, { title: "
|
|
93
|
-
/* @__PURE__ */ jsx(Section, { title: "
|
|
77
|
+
/* @__PURE__ */ jsx(Section, { title: "Request", headers: record.reqHeaders, body: record.reqBody, truncated: record.reqTruncated, note: bodyNote(record, "req") }),
|
|
78
|
+
/* @__PURE__ */ jsx(Section, { title: "Response", headers: record.resHeaders, body: record.resBody, truncated: record.resTruncated, note: bodyNote(record, "res") }),
|
|
94
79
|
/* @__PURE__ */ jsxs(Text, { color: C.muted, children: [
|
|
95
80
|
"\n",
|
|
96
|
-
"r
|
|
81
|
+
"r replay \xB7 esc back to list"
|
|
97
82
|
] })
|
|
98
83
|
] });
|
|
99
84
|
}
|
|
@@ -116,16 +101,16 @@ import { Box as Box2, Text as Text3 } from "ink";
|
|
|
116
101
|
|
|
117
102
|
// src/ui/qr.ts
|
|
118
103
|
import qrcode from "qrcode-generator";
|
|
119
|
-
var
|
|
104
|
+
var MARGIN = 2;
|
|
120
105
|
function qrLines(text) {
|
|
121
106
|
const qr = qrcode(0, "L");
|
|
122
107
|
qr.addData(text);
|
|
123
108
|
qr.make();
|
|
124
109
|
const n = qr.getModuleCount();
|
|
125
|
-
const size = n +
|
|
110
|
+
const size = n + MARGIN * 2;
|
|
126
111
|
const dark = (r, c) => {
|
|
127
|
-
const rr = r -
|
|
128
|
-
const cc = c -
|
|
112
|
+
const rr = r - MARGIN;
|
|
113
|
+
const cc = c - MARGIN;
|
|
129
114
|
return rr >= 0 && cc >= 0 && rr < n && cc < n && qr.isDark(rr, cc);
|
|
130
115
|
};
|
|
131
116
|
const lines = [];
|
|
@@ -152,7 +137,7 @@ function QrView({ tunnel, link, now }) {
|
|
|
152
137
|
const left = link.expiresAt === null ? null : link.expiresAt - now;
|
|
153
138
|
const expired = left !== null && Math.ceil(left / 1e3) <= 0;
|
|
154
139
|
return /* @__PURE__ */ jsxs3(Box2, { flexDirection: "column", children: [
|
|
155
|
-
/* @__PURE__ */ jsx3(Text3, { color: C.muted, children: "
|
|
140
|
+
/* @__PURE__ */ jsx3(Text3, { color: C.muted, children: "Open on your phone" }),
|
|
156
141
|
/* @__PURE__ */ jsxs3(Box2, { children: [
|
|
157
142
|
/* @__PURE__ */ jsx3(Box2, { flexDirection: "column", marginRight: 3, children: lines.map((l, i) => /* @__PURE__ */ jsx3(Text3, { backgroundColor: "white", color: "black", children: l }, i)) }),
|
|
158
143
|
/* @__PURE__ */ jsxs3(Box2, { flexDirection: "column", children: [
|
|
@@ -166,14 +151,14 @@ function QrView({ tunnel, link, now }) {
|
|
|
166
151
|
] }),
|
|
167
152
|
left !== null && /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
168
153
|
/* @__PURE__ */ jsx3(Text3, { children: " " }),
|
|
169
|
-
/* @__PURE__ */ jsx3(Text3, { children: "\u{1F512}
|
|
170
|
-
/* @__PURE__ */ jsx3(Text3, { color: C.muted, children: "
|
|
171
|
-
/* @__PURE__ */ jsx3(Text3, { color: C.muted, children: "
|
|
154
|
+
/* @__PURE__ */ jsx3(Text3, { children: "\u{1F512} This tunnel has a password." }),
|
|
155
|
+
/* @__PURE__ */ jsx3(Text3, { color: C.muted, children: "The QR signs you in, no password to type." }),
|
|
156
|
+
/* @__PURE__ */ jsx3(Text3, { color: C.muted, children: "Valid for 5 minutes, one device only." }),
|
|
172
157
|
/* @__PURE__ */ jsx3(Text3, { children: " " }),
|
|
173
|
-
expired ? /* @__PURE__ */ jsx3(Text3, { color: C.warn, children: "
|
|
158
|
+
expired ? /* @__PURE__ */ jsx3(Text3, { color: C.warn, children: "Expired. Press g to generate another." }) : /* @__PURE__ */ jsx3(Text3, { color: C.signal, children: `Expires in ${countdown(left)}` })
|
|
174
159
|
] }),
|
|
175
160
|
/* @__PURE__ */ jsx3(Text3, { children: " " }),
|
|
176
|
-
/* @__PURE__ */ jsx3(Text3, { color: C.muted, children: left !== null ? "g
|
|
161
|
+
/* @__PURE__ */ jsx3(Text3, { color: C.muted, children: left !== null ? "g new code \xB7 esc back" : "esc back" })
|
|
177
162
|
] })
|
|
178
163
|
] })
|
|
179
164
|
] });
|
|
@@ -186,7 +171,7 @@ function accessLabel(a) {
|
|
|
186
171
|
if (!a) return null;
|
|
187
172
|
if (a.kind === "user") return { text: a.user, color: C.muted };
|
|
188
173
|
if (a.kind === "allow") return { text: a.reason, color: C.muted };
|
|
189
|
-
return { text: "
|
|
174
|
+
return { text: "no password", color: C.warn };
|
|
190
175
|
}
|
|
191
176
|
function RequestList(props) {
|
|
192
177
|
const { records, selected, focused, height, width } = props;
|
|
@@ -194,14 +179,14 @@ function RequestList(props) {
|
|
|
194
179
|
const visible = records.slice(start, start + height);
|
|
195
180
|
const accessW = Math.max(0, ...visible.map((r) => (accessLabel(r.access)?.text.length ?? -1) + 1));
|
|
196
181
|
const pathW = Math.max(10, width - 54 - accessW);
|
|
197
|
-
const title = props.filter ? `
|
|
182
|
+
const title = props.filter ? `REQUESTS \xB7 filter: ${props.filter} ` : "REQUESTS ";
|
|
198
183
|
return /* @__PURE__ */ jsxs4(Box3, { flexDirection: "column", children: [
|
|
199
184
|
/* @__PURE__ */ jsx4(Text4, { color: C.muted, children: title + "\u2500".repeat(Math.max(0, width - title.length)) }),
|
|
200
|
-
records.length === 0 ? /* @__PURE__ */ jsx4(Text4, { color: C.muted, children: props.filter ? "
|
|
185
|
+
records.length === 0 ? /* @__PURE__ */ jsx4(Text4, { color: C.muted, children: props.filter ? "No requests match that filter." : "No requests yet. They show up here as they arrive." }) : visible.map((r, i) => {
|
|
201
186
|
const sel = focused && start + i === selected;
|
|
202
187
|
const method = r.websocket ? "WS" : r.method;
|
|
203
188
|
const status = r.status === null ? "\u2026" : String(r.status);
|
|
204
|
-
const tail = r.websocket && r.status === 101 ? "
|
|
189
|
+
const tail = r.websocket && r.status === 101 ? "open" : r.durationMs === null ? "" : formatDuration(r.durationMs);
|
|
205
190
|
const acc = accessLabel(r.access);
|
|
206
191
|
return /* @__PURE__ */ jsxs4(Text4, { inverse: sel, wrap: "truncate-end", children: [
|
|
207
192
|
/* @__PURE__ */ jsx4(Text4, { color: C.muted, children: formatTime(r.startedAt) }),
|
|
@@ -227,10 +212,10 @@ function RequestList(props) {
|
|
|
227
212
|
import { Box as Box4, Text as Text5 } from "ink";
|
|
228
213
|
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
229
214
|
function note(t) {
|
|
230
|
-
if (t.status === "failed") return { text: t.error ?? "
|
|
231
|
-
if (t.status === "no-listener") return { text: `
|
|
232
|
-
if (t.status === "opening") return { text: "
|
|
233
|
-
if (t.adHoc) return { text: "
|
|
215
|
+
if (t.status === "failed") return { text: t.error ?? "failed", color: C.err };
|
|
216
|
+
if (t.status === "no-listener") return { text: `nothing on port ${t.port}`, color: C.warn };
|
|
217
|
+
if (t.status === "opening") return { text: "opening\u2026", color: C.muted };
|
|
218
|
+
if (t.adHoc) return { text: "this session only", color: C.muted };
|
|
234
219
|
return null;
|
|
235
220
|
}
|
|
236
221
|
function dot(t) {
|
|
@@ -248,7 +233,7 @@ function TunnelTable({ tunnels, selected, focused }) {
|
|
|
248
233
|
return /* @__PURE__ */ jsxs5(Box4, { flexDirection: "column", children: [
|
|
249
234
|
/* @__PURE__ */ jsxs5(Text5, { color: C.muted, children: [
|
|
250
235
|
" ",
|
|
251
|
-
fit("
|
|
236
|
+
fit("TUNNEL", nameW),
|
|
252
237
|
" ",
|
|
253
238
|
fit("URL", urlW),
|
|
254
239
|
anyAuth ? " " : "",
|
|
@@ -257,7 +242,7 @@ function TunnelTable({ tunnels, selected, focused }) {
|
|
|
257
242
|
" ",
|
|
258
243
|
"REQ".padStart(4),
|
|
259
244
|
" ",
|
|
260
|
-
"
|
|
245
|
+
"ERR".padStart(5)
|
|
261
246
|
] }),
|
|
262
247
|
tunnels.map((t, i) => {
|
|
263
248
|
const d = dot(t);
|
|
@@ -288,16 +273,17 @@ function TunnelTable({ tunnels, selected, focused }) {
|
|
|
288
273
|
// src/ui/App.tsx
|
|
289
274
|
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
290
275
|
var KEYS = [
|
|
291
|
-
["n", "
|
|
292
|
-
["c", "
|
|
293
|
-
["o", "
|
|
294
|
-
["
|
|
295
|
-
["
|
|
296
|
-
["
|
|
297
|
-
["
|
|
298
|
-
["
|
|
276
|
+
["n", "new tunnel"],
|
|
277
|
+
["c", "copy URL"],
|
|
278
|
+
["o", "open in browser"],
|
|
279
|
+
["i", "inspector"],
|
|
280
|
+
["m", "QR for phone"],
|
|
281
|
+
["f", "filter"],
|
|
282
|
+
["\u21B5", "details"],
|
|
283
|
+
["tab", "focus"],
|
|
284
|
+
["q", "quit"]
|
|
299
285
|
];
|
|
300
|
-
function App({ session, version, system, onExit }) {
|
|
286
|
+
function App({ session, version, system, onExit, inspector = null, inspectorError }) {
|
|
301
287
|
const subState = useCallback((fn) => session.subscribe(fn), [session]);
|
|
302
288
|
const subRecords = useCallback((fn) => session.records.subscribe(fn), [session]);
|
|
303
289
|
const state = useSyncExternalStore(subState, () => session.getState());
|
|
@@ -310,7 +296,7 @@ function App({ session, version, system, onExit }) {
|
|
|
310
296
|
const [focus, setFocus] = useState("requests");
|
|
311
297
|
const [selReq, setSelReq] = useState(0);
|
|
312
298
|
const [selTun, setSelTun] = useState(0);
|
|
313
|
-
const [message, setMessage] = useState(null);
|
|
299
|
+
const [message, setMessage] = useState(inspectorError ? { text: `Inspector failed to start: ${inspectorError}`, tone: "warn" } : null);
|
|
314
300
|
const [now, setNow] = useState(() => Date.now());
|
|
315
301
|
const quitting = useRef(false);
|
|
316
302
|
const records = useMemo(() => {
|
|
@@ -346,31 +332,36 @@ function App({ session, version, system, onExit }) {
|
|
|
346
332
|
async function quit() {
|
|
347
333
|
if (quitting.current) return;
|
|
348
334
|
quitting.current = true;
|
|
349
|
-
setMessage({ text: "
|
|
335
|
+
setMessage({ text: "Closing tunnels\u2026", tone: "muted" });
|
|
350
336
|
await session.stop();
|
|
351
337
|
onExit();
|
|
352
338
|
}
|
|
339
|
+
async function openInspector() {
|
|
340
|
+
if (!inspector) return setMessage({ text: "Inspector is off (--no-inspect).", tone: "warn" });
|
|
341
|
+
const ok = await system.open(inspector.openUrl);
|
|
342
|
+
setMessage(ok ? { text: "Opening the inspector", tone: "signal" } : { text: "Could not open the browser.", tone: "warn" });
|
|
343
|
+
}
|
|
353
344
|
async function act(kind) {
|
|
354
345
|
const t = currentTunnel();
|
|
355
|
-
if (!t?.url) return setMessage({ text: "
|
|
346
|
+
if (!t?.url) return setMessage({ text: "That tunnel has no URL yet.", tone: "warn" });
|
|
356
347
|
if (kind === "copy") {
|
|
357
348
|
const ok = await system.copy(t.url);
|
|
358
|
-
setMessage(ok ? { text: `URL
|
|
349
|
+
setMessage(ok ? { text: `URL copied: ${t.url}`, tone: "signal" } : { text: "Could not copy \u2014 select the URL in the table", tone: "warn" });
|
|
359
350
|
} else {
|
|
360
351
|
const ok = await system.open(t.url);
|
|
361
|
-
setMessage(ok ? { text: `
|
|
352
|
+
setMessage(ok ? { text: `Opening ${t.url}`, tone: "signal" } : { text: "Could not open the browser", tone: "warn" });
|
|
362
353
|
}
|
|
363
354
|
}
|
|
364
355
|
function showQr() {
|
|
365
356
|
const t = currentTunnel();
|
|
366
357
|
const link = t ? session.qrLink(t.key) : null;
|
|
367
|
-
if (!t || !link) return setMessage({ text: "
|
|
358
|
+
if (!t || !link) return setMessage({ text: "That tunnel has no URL yet.", tone: "warn" });
|
|
368
359
|
setNow(Date.now());
|
|
369
360
|
setMode({ kind: "qr", key: t.key, link });
|
|
370
361
|
}
|
|
371
362
|
function submitPort(value) {
|
|
372
363
|
const port = Number(value.trim());
|
|
373
|
-
if (!Number.isInteger(port) || port < 1 || port > 65535) return setMessage({ text: `
|
|
364
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) return setMessage({ text: `Invalid port: ${value || "(empty)"} (use 1\u201365535)`, tone: "err" });
|
|
374
365
|
setMode({ kind: "name", value: "", port });
|
|
375
366
|
}
|
|
376
367
|
function submitName(value, port) {
|
|
@@ -378,12 +369,12 @@ function App({ session, version, system, onExit }) {
|
|
|
378
369
|
const name = raw ? normalizeName(raw) : null;
|
|
379
370
|
if (name !== null) {
|
|
380
371
|
const v = validateName(name);
|
|
381
|
-
if (!v.ok) return setMessage({ text: v.reason === "reserved" ? `
|
|
372
|
+
if (!v.ok) return setMessage({ text: v.reason === "reserved" ? `Reserved name: "${raw}"` : "Invalid name: use lowercase letters and numbers only (no hyphens)", tone: "err" });
|
|
382
373
|
}
|
|
383
374
|
setMode({ kind: "list" });
|
|
384
375
|
void session.add({ name, port });
|
|
385
376
|
setMessage(
|
|
386
|
-
name ? { text: `
|
|
377
|
+
name ? { text: `To keep it, add to duto.yml: ${name}: ${port}`, tone: "muted" } : { text: `Opening localhost:${port} for this session only`, tone: "muted" }
|
|
387
378
|
);
|
|
388
379
|
}
|
|
389
380
|
useInput((input, key) => {
|
|
@@ -436,13 +427,14 @@ function App({ session, version, system, onExit }) {
|
|
|
436
427
|
if (key.escape && filter) return setFilter("");
|
|
437
428
|
if (input === "c") return void act("copy");
|
|
438
429
|
if (input === "o") return void act("open");
|
|
430
|
+
if (input === "i") return void openInspector();
|
|
439
431
|
if (input === "m") return showQr();
|
|
440
432
|
if (input === "f") return setMode({ kind: "filter", value: filter });
|
|
441
433
|
if (input === "n") return setMode({ kind: "port", value: "" });
|
|
442
434
|
});
|
|
443
435
|
const live = state.tunnels.filter((t) => t.status === "live").length;
|
|
444
|
-
const conn = state.connection === "online" ? { text: "
|
|
445
|
-
const listHeight = Math.max(3, rows - state.tunnels.length - 9);
|
|
436
|
+
const conn = state.connection === "online" ? { text: "connected \u25CF", color: C.signal } : state.connection === "connecting" ? { text: "connecting\u2026", color: C.muted } : { text: "reconnecting\u2026", color: C.warn };
|
|
437
|
+
const listHeight = Math.max(3, rows - state.tunnels.length - (inspector ? 10 : 9));
|
|
446
438
|
const detailRecord = mode.kind === "detail" ? all.find((r) => r.id === mode.id) : void 0;
|
|
447
439
|
const qrTunnel = mode.kind === "qr" ? state.tunnels.find((t) => t.key === mode.key) : void 0;
|
|
448
440
|
return /* @__PURE__ */ jsxs6(Box5, { flexDirection: "column", width, children: [
|
|
@@ -462,19 +454,20 @@ function App({ session, version, system, onExit }) {
|
|
|
462
454
|
live,
|
|
463
455
|
"/",
|
|
464
456
|
state.tunnels.length,
|
|
465
|
-
"
|
|
457
|
+
" live"
|
|
466
458
|
] })
|
|
467
459
|
] }),
|
|
468
460
|
/* @__PURE__ */ jsx6(Text6, { color: conn.color, children: conn.text })
|
|
469
461
|
] }),
|
|
470
462
|
/* @__PURE__ */ jsx6(Text6, { children: " " }),
|
|
471
463
|
/* @__PURE__ */ jsx6(TunnelTable, { tunnels: state.tunnels, selected: selTun, focused: focus === "tunnels" }),
|
|
464
|
+
inspector && /* @__PURE__ */ jsx6(Text6, { color: C.muted, children: `inspector ${inspector.url}` }),
|
|
472
465
|
/* @__PURE__ */ jsx6(Text6, { children: " " }),
|
|
473
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 }),
|
|
474
467
|
/* @__PURE__ */ jsx6(Text6, { children: " " }),
|
|
475
|
-
mode.kind === "filter" && /* @__PURE__ */ jsx6(Prompt, { label: "
|
|
476
|
-
mode.kind === "port" && /* @__PURE__ */ jsx6(Prompt, { label: "
|
|
477
|
-
mode.kind === "name" && /* @__PURE__ */ jsx6(Prompt, { label: `
|
|
468
|
+
mode.kind === "filter" && /* @__PURE__ */ jsx6(Prompt, { label: "Filter (tunnel, method, path or status):", value: mode.value, hint: "\u21B5 apply \xB7 esc cancel" }),
|
|
469
|
+
mode.kind === "port" && /* @__PURE__ */ jsx6(Prompt, { label: "New tunnel \u2014 local port:", value: mode.value, hint: "\u21B5 continue \xB7 esc cancel" }),
|
|
470
|
+
mode.kind === "name" && /* @__PURE__ */ jsx6(Prompt, { label: `Name for localhost:${mode.port} (optional):`, value: mode.value, hint: "\u21B5 open \xB7 esc cancel" }),
|
|
478
471
|
mode.kind !== "filter" && mode.kind !== "port" && mode.kind !== "name" && /* @__PURE__ */ jsx6(Text6, { color: message ? C[message.tone] : C.muted, children: message?.text ?? " " }),
|
|
479
472
|
/* @__PURE__ */ jsx6(Text6, { wrap: "truncate-end", children: KEYS.map(([k, label]) => /* @__PURE__ */ jsxs6(Text6, { children: [
|
|
480
473
|
/* @__PURE__ */ jsx6(Text6, { inverse: true, color: C.signal, children: ` ${k} ` }),
|
|
@@ -554,7 +547,7 @@ var realSystem = {
|
|
|
554
547
|
|
|
555
548
|
// src/ui/fullscreen.tsx
|
|
556
549
|
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
557
|
-
function runFullscreen(session, specs, version) {
|
|
550
|
+
function runFullscreen(session, specs, version, inspector, inspectorError) {
|
|
558
551
|
return withFullscreen(
|
|
559
552
|
process.stdout,
|
|
560
553
|
(signal) => new Promise((resolve) => {
|
|
@@ -565,7 +558,7 @@ function runFullscreen(session, specs, version) {
|
|
|
565
558
|
app.unmount();
|
|
566
559
|
resolve(code);
|
|
567
560
|
};
|
|
568
|
-
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 });
|
|
569
562
|
signal.addEventListener("abort", () => void session.stop().then(() => finish(0)), { once: true });
|
|
570
563
|
session.start(specs).catch(() => void 0);
|
|
571
564
|
})
|