@dutodev/cli 0.7.0 → 1.1.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.
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  C,
4
+ MAX_NAME,
4
5
  TRUNCATED_NOTE,
5
6
  bodyNote,
6
7
  fit,
@@ -12,7 +13,7 @@ import {
12
13
  statusColor,
13
14
  tunnelColor,
14
15
  validateName
15
- } from "./chunk-74IJFYGT.js";
16
+ } from "./chunk-76YCOMXH.js";
16
17
 
17
18
  // src/ui/fullscreen.tsx
18
19
  import { render } from "ink";
@@ -40,9 +41,9 @@ function Section({ title, headers, body, truncated, note: note2 }) {
40
41
  v
41
42
  ] }, k)),
42
43
  entries.length > MAX_HEADERS && /* @__PURE__ */ jsxs(Text, { color: C.muted, children: [
43
- "\u2026 mais ",
44
+ "\u2026 ",
44
45
  entries.length - MAX_HEADERS,
45
- " cabe\xE7alhos"
46
+ " more headers"
46
47
  ] }),
47
48
  body === null ? /* @__PURE__ */ jsx(Text, { color: C.muted, children: note2 ?? "" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
48
49
  lines.slice(0, MAX_BODY_LINES).map((l, i) => /* @__PURE__ */ jsx(Text, { wrap: "truncate-end", children: l }, i)),
@@ -70,15 +71,15 @@ function Detail({ record }) {
70
71
  ] })
71
72
  ] }),
72
73
  record.replayOf !== void 0 && /* @__PURE__ */ jsxs(Text, { color: C.muted, children: [
73
- "\u21BB reenvio de #",
74
+ "\u21BB replay of #",
74
75
  record.replayOf
75
76
  ] }),
76
77
  record.error && /* @__PURE__ */ jsx(Text, { color: C.err, children: record.error }),
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") }),
78
+ /* @__PURE__ */ jsx(Section, { title: "Request", headers: record.reqHeaders, body: record.reqBody, truncated: record.reqTruncated, note: bodyNote(record, "req") }),
79
+ /* @__PURE__ */ jsx(Section, { title: "Response", headers: record.resHeaders, body: record.resBody, truncated: record.resTruncated, note: bodyNote(record, "res") }),
79
80
  /* @__PURE__ */ jsxs(Text, { color: C.muted, children: [
80
81
  "\n",
81
- "r reenvia \xB7 esc volta para a lista"
82
+ "r replay \xB7 esc back to list"
82
83
  ] })
83
84
  ] });
84
85
  }
@@ -101,16 +102,16 @@ import { Box as Box2, Text as Text3 } from "ink";
101
102
 
102
103
  // src/ui/qr.ts
103
104
  import qrcode from "qrcode-generator";
104
- var MARGEM = 2;
105
+ var MARGIN = 2;
105
106
  function qrLines(text) {
106
107
  const qr = qrcode(0, "L");
107
108
  qr.addData(text);
108
109
  qr.make();
109
110
  const n = qr.getModuleCount();
110
- const size = n + MARGEM * 2;
111
+ const size = n + MARGIN * 2;
111
112
  const dark = (r, c) => {
112
- const rr = r - MARGEM;
113
- const cc = c - MARGEM;
113
+ const rr = r - MARGIN;
114
+ const cc = c - MARGIN;
114
115
  return rr >= 0 && cc >= 0 && rr < n && cc < n && qr.isDark(rr, cc);
115
116
  };
116
117
  const lines = [];
@@ -137,7 +138,7 @@ function QrView({ tunnel, link, now }) {
137
138
  const left = link.expiresAt === null ? null : link.expiresAt - now;
138
139
  const expired = left !== null && Math.ceil(left / 1e3) <= 0;
139
140
  return /* @__PURE__ */ jsxs3(Box2, { flexDirection: "column", children: [
140
- /* @__PURE__ */ jsx3(Text3, { color: C.muted, children: "Abra no celular" }),
141
+ /* @__PURE__ */ jsx3(Text3, { color: C.muted, children: "Open on your phone" }),
141
142
  /* @__PURE__ */ jsxs3(Box2, { children: [
142
143
  /* @__PURE__ */ jsx3(Box2, { flexDirection: "column", marginRight: 3, children: lines.map((l, i) => /* @__PURE__ */ jsx3(Text3, { backgroundColor: "white", color: "black", children: l }, i)) }),
143
144
  /* @__PURE__ */ jsxs3(Box2, { flexDirection: "column", children: [
@@ -151,14 +152,14 @@ function QrView({ tunnel, link, now }) {
151
152
  ] }),
152
153
  left !== null && /* @__PURE__ */ jsxs3(Fragment2, { children: [
153
154
  /* @__PURE__ */ jsx3(Text3, { children: " " }),
154
- /* @__PURE__ */ jsx3(Text3, { children: "\u{1F512} Este t\xFAnel tem senha." }),
155
- /* @__PURE__ */ jsx3(Text3, { color: C.muted, children: "O QR entra direto, sem digitar a senha." }),
156
- /* @__PURE__ */ jsx3(Text3, { color: C.muted, children: "Vale por 5 minutos e s\xF3 para um aparelho." }),
155
+ /* @__PURE__ */ jsx3(Text3, { children: "\u{1F512} This tunnel has a password." }),
156
+ /* @__PURE__ */ jsx3(Text3, { color: C.muted, children: "The QR signs you in, no password to type." }),
157
+ /* @__PURE__ */ jsx3(Text3, { color: C.muted, children: "Valid for 5 minutes, one device only." }),
157
158
  /* @__PURE__ */ jsx3(Text3, { children: " " }),
158
- expired ? /* @__PURE__ */ jsx3(Text3, { color: C.warn, children: "Expirou. Aperte g para gerar outro." }) : /* @__PURE__ */ jsx3(Text3, { color: C.signal, children: `Expira em ${countdown(left)}` })
159
+ 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)}` })
159
160
  ] }),
160
161
  /* @__PURE__ */ jsx3(Text3, { children: " " }),
161
- /* @__PURE__ */ jsx3(Text3, { color: C.muted, children: left !== null ? "g gerar outro \xB7 esc voltar" : "esc voltar" })
162
+ /* @__PURE__ */ jsx3(Text3, { color: C.muted, children: left !== null ? "g new code \xB7 esc back" : "esc back" })
162
163
  ] })
163
164
  ] })
164
165
  ] });
@@ -171,7 +172,7 @@ function accessLabel(a) {
171
172
  if (!a) return null;
172
173
  if (a.kind === "user") return { text: a.user, color: C.muted };
173
174
  if (a.kind === "allow") return { text: a.reason, color: C.muted };
174
- return { text: "sem senha", color: C.warn };
175
+ return { text: "no password", color: C.warn };
175
176
  }
176
177
  function RequestList(props) {
177
178
  const { records, selected, focused, height, width } = props;
@@ -179,14 +180,14 @@ function RequestList(props) {
179
180
  const visible = records.slice(start, start + height);
180
181
  const accessW = Math.max(0, ...visible.map((r) => (accessLabel(r.access)?.text.length ?? -1) + 1));
181
182
  const pathW = Math.max(10, width - 54 - accessW);
182
- const title = props.filter ? `REQUISI\xC7\xD5ES \xB7 filtro: ${props.filter} ` : "REQUISI\xC7\xD5ES ";
183
+ const title = props.filter ? `REQUESTS \xB7 filter: ${props.filter} ` : "REQUESTS ";
183
184
  return /* @__PURE__ */ jsxs4(Box3, { flexDirection: "column", children: [
184
185
  /* @__PURE__ */ jsx4(Text4, { color: C.muted, children: title + "\u2500".repeat(Math.max(0, width - title.length)) }),
185
- records.length === 0 ? /* @__PURE__ */ jsx4(Text4, { color: C.muted, children: props.filter ? "Nenhuma requisi\xE7\xE3o com esse filtro." : "Nenhuma requisi\xE7\xE3o ainda. Elas aparecem aqui assim que chegarem." }) : visible.map((r, i) => {
186
+ 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) => {
186
187
  const sel = focused && start + i === selected;
187
188
  const method = r.websocket ? "WS" : r.method;
188
189
  const status = r.status === null ? "\u2026" : String(r.status);
189
- const tail = r.websocket && r.status === 101 ? "aberto" : r.durationMs === null ? "" : formatDuration(r.durationMs);
190
+ const tail = r.websocket && r.status === 101 ? "open" : r.durationMs === null ? "" : formatDuration(r.durationMs);
190
191
  const acc = accessLabel(r.access);
191
192
  return /* @__PURE__ */ jsxs4(Text4, { inverse: sel, wrap: "truncate-end", children: [
192
193
  /* @__PURE__ */ jsx4(Text4, { color: C.muted, children: formatTime(r.startedAt) }),
@@ -212,10 +213,10 @@ function RequestList(props) {
212
213
  import { Box as Box4, Text as Text5 } from "ink";
213
214
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
214
215
  function note(t) {
215
- if (t.status === "failed") return { text: t.error ?? "falhou", color: C.err };
216
- if (t.status === "no-listener") return { text: `nada escutando na ${t.port}`, color: C.warn };
217
- if (t.status === "opening") return { text: "abrindo\u2026", color: C.muted };
218
- if (t.adHoc) return { text: "s\xF3 nesta sess\xE3o", color: C.muted };
216
+ if (t.status === "failed") return { text: t.error ?? "failed", color: C.err };
217
+ if (t.status === "no-listener") return { text: `nothing on port ${t.port}`, color: C.warn };
218
+ if (t.status === "opening") return { text: "opening\u2026", color: C.muted };
219
+ if (t.adHoc) return { text: "this session only", color: C.muted };
219
220
  return null;
220
221
  }
221
222
  function dot(t) {
@@ -233,7 +234,7 @@ function TunnelTable({ tunnels, selected, focused }) {
233
234
  return /* @__PURE__ */ jsxs5(Box4, { flexDirection: "column", children: [
234
235
  /* @__PURE__ */ jsxs5(Text5, { color: C.muted, children: [
235
236
  " ",
236
- fit("T\xDANEL", nameW),
237
+ fit("TUNNEL", nameW),
237
238
  " ",
238
239
  fit("URL", urlW),
239
240
  anyAuth ? " " : "",
@@ -242,7 +243,7 @@ function TunnelTable({ tunnels, selected, focused }) {
242
243
  " ",
243
244
  "REQ".padStart(4),
244
245
  " ",
245
- "ERROS".padStart(5)
246
+ "ERR".padStart(5)
246
247
  ] }),
247
248
  tunnels.map((t, i) => {
248
249
  const d = dot(t);
@@ -273,15 +274,15 @@ function TunnelTable({ tunnels, selected, focused }) {
273
274
  // src/ui/App.tsx
274
275
  import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
275
276
  var KEYS = [
276
- ["n", "novo t\xFAnel"],
277
- ["c", "copiar URL"],
278
- ["o", "abrir no navegador"],
279
- ["i", "inspetor"],
280
- ["m", "QR no celular"],
281
- ["f", "filtrar"],
282
- ["\u21B5", "detalhes"],
283
- ["tab", "foco"],
284
- ["q", "sair"]
277
+ ["n", "new tunnel"],
278
+ ["c", "copy URL"],
279
+ ["o", "open in browser"],
280
+ ["i", "inspector"],
281
+ ["m", "QR for phone"],
282
+ ["f", "filter"],
283
+ ["\u21B5", "details"],
284
+ ["tab", "focus"],
285
+ ["q", "quit"]
285
286
  ];
286
287
  function App({ session, version, system, onExit, inspector = null, inspectorError }) {
287
288
  const subState = useCallback((fn) => session.subscribe(fn), [session]);
@@ -296,7 +297,7 @@ function App({ session, version, system, onExit, inspector = null, inspectorErro
296
297
  const [focus, setFocus] = useState("requests");
297
298
  const [selReq, setSelReq] = useState(0);
298
299
  const [selTun, setSelTun] = useState(0);
299
- const [message, setMessage] = useState(inspectorError ? { text: `Inspetor n\xE3o subiu: ${inspectorError}`, tone: "warn" } : null);
300
+ const [message, setMessage] = useState(inspectorError ? { text: `Inspector failed to start: ${inspectorError}`, tone: "warn" } : null);
300
301
  const [now, setNow] = useState(() => Date.now());
301
302
  const quitting = useRef(false);
302
303
  const records = useMemo(() => {
@@ -332,36 +333,36 @@ function App({ session, version, system, onExit, inspector = null, inspectorErro
332
333
  async function quit() {
333
334
  if (quitting.current) return;
334
335
  quitting.current = true;
335
- setMessage({ text: "Encerrando os t\xFAneis\u2026", tone: "muted" });
336
+ setMessage({ text: "Closing tunnels\u2026", tone: "muted" });
336
337
  await session.stop();
337
338
  onExit();
338
339
  }
339
340
  async function openInspector() {
340
- if (!inspector) return setMessage({ text: "Inspetor desligado (--no-inspect).", tone: "warn" });
341
+ if (!inspector) return setMessage({ text: "Inspector is off (--no-inspect).", tone: "warn" });
341
342
  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
+ setMessage(ok ? { text: "Opening the inspector", tone: "signal" } : { text: "Could not open the browser.", tone: "warn" });
343
344
  }
344
345
  async function act(kind) {
345
346
  const t = currentTunnel();
346
- if (!t?.url) return setMessage({ text: "Esse t\xFAnel ainda n\xE3o tem URL.", tone: "warn" });
347
+ if (!t?.url) return setMessage({ text: "That tunnel has no URL yet.", tone: "warn" });
347
348
  if (kind === "copy") {
348
349
  const ok = await system.copy(t.url);
349
- setMessage(ok ? { text: `URL copiada: ${t.url}`, tone: "signal" } : { text: "N\xE3o consegui copiar \u2014 selecione a URL na tabela", tone: "warn" });
350
+ setMessage(ok ? { text: `URL copied: ${t.url}`, tone: "signal" } : { text: "Could not copy \u2014 select the URL in the table", tone: "warn" });
350
351
  } else {
351
352
  const ok = await system.open(t.url);
352
- setMessage(ok ? { text: `Abrindo ${t.url}`, tone: "signal" } : { text: "N\xE3o consegui abrir o navegador", tone: "warn" });
353
+ setMessage(ok ? { text: `Opening ${t.url}`, tone: "signal" } : { text: "Could not open the browser", tone: "warn" });
353
354
  }
354
355
  }
355
356
  function showQr() {
356
357
  const t = currentTunnel();
357
358
  const link = t ? session.qrLink(t.key) : null;
358
- if (!t || !link) return setMessage({ text: "Esse t\xFAnel ainda n\xE3o tem URL.", tone: "warn" });
359
+ if (!t || !link) return setMessage({ text: "That tunnel has no URL yet.", tone: "warn" });
359
360
  setNow(Date.now());
360
361
  setMode({ kind: "qr", key: t.key, link });
361
362
  }
362
363
  function submitPort(value) {
363
364
  const port = Number(value.trim());
364
- if (!Number.isInteger(port) || port < 1 || port > 65535) return setMessage({ text: `Porta inv\xE1lida: ${value || "(vazia)"} (use 1\u201365535)`, tone: "err" });
365
+ if (!Number.isInteger(port) || port < 1 || port > 65535) return setMessage({ text: `Invalid port: ${value || "(empty)"} (use 1\u201365535)`, tone: "err" });
365
366
  setMode({ kind: "name", value: "", port });
366
367
  }
367
368
  function submitName(value, port) {
@@ -369,12 +370,12 @@ function App({ session, version, system, onExit, inspector = null, inspectorErro
369
370
  const name = raw ? normalizeName(raw) : null;
370
371
  if (name !== null) {
371
372
  const v = validateName(name);
372
- if (!v.ok) return setMessage({ text: v.reason === "reserved" ? `Nome reservado: "${raw}"` : "Nome inv\xE1lido: use s\xF3 letras min\xFAsculas e n\xFAmeros (sem h\xEDfen)", tone: "err" });
373
+ if (!v.ok) return setMessage({ text: v.reason === "reserved" ? `"${raw}" is reserved. Pick another name.` : `Invalid name. Use only lowercase letters and digits (no hyphens), up to ${MAX_NAME} characters.`, tone: "err" });
373
374
  }
374
375
  setMode({ kind: "list" });
375
376
  void session.add({ name, port });
376
377
  setMessage(
377
- name ? { text: `Para manter, adicione ao duto.yml: ${name}: ${port}`, tone: "muted" } : { text: `Abrindo localhost:${port} s\xF3 nesta sess\xE3o`, tone: "muted" }
378
+ name ? { text: `To keep it, add to duto.yml: ${name}: ${port}`, tone: "muted" } : { text: `Opening localhost:${port} for this session only`, tone: "muted" }
378
379
  );
379
380
  }
380
381
  useInput((input, key) => {
@@ -433,7 +434,7 @@ function App({ session, version, system, onExit, inspector = null, inspectorErro
433
434
  if (input === "n") return setMode({ kind: "port", value: "" });
434
435
  });
435
436
  const live = state.tunnels.filter((t) => t.status === "live").length;
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 };
437
+ 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
438
  const listHeight = Math.max(3, rows - state.tunnels.length - (inspector ? 10 : 9));
438
439
  const detailRecord = mode.kind === "detail" ? all.find((r) => r.id === mode.id) : void 0;
439
440
  const qrTunnel = mode.kind === "qr" ? state.tunnels.find((t) => t.key === mode.key) : void 0;
@@ -454,20 +455,20 @@ function App({ session, version, system, onExit, inspector = null, inspectorErro
454
455
  live,
455
456
  "/",
456
457
  state.tunnels.length,
457
- " no ar"
458
+ " live"
458
459
  ] })
459
460
  ] }),
460
461
  /* @__PURE__ */ jsx6(Text6, { color: conn.color, children: conn.text })
461
462
  ] }),
462
463
  /* @__PURE__ */ jsx6(Text6, { children: " " }),
463
464
  /* @__PURE__ */ jsx6(TunnelTable, { tunnels: state.tunnels, selected: selTun, focused: focus === "tunnels" }),
464
- inspector && /* @__PURE__ */ jsx6(Text6, { color: C.muted, children: `inspetor ${inspector.url}` }),
465
+ inspector && /* @__PURE__ */ jsx6(Text6, { color: C.muted, children: `inspector ${inspector.url}` }),
465
466
  /* @__PURE__ */ jsx6(Text6, { children: " " }),
466
467
  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 }),
467
468
  /* @__PURE__ */ jsx6(Text6, { children: " " }),
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" }),
469
- mode.kind === "port" && /* @__PURE__ */ jsx6(Prompt, { label: "Novo t\xFAnel \u2014 porta local:", value: mode.value, hint: "\u21B5 continua \xB7 esc cancela" }),
470
- mode.kind === "name" && /* @__PURE__ */ jsx6(Prompt, { label: `Nome para localhost:${mode.port} (opcional):`, value: mode.value, hint: "\u21B5 abre \xB7 esc cancela" }),
469
+ mode.kind === "filter" && /* @__PURE__ */ jsx6(Prompt, { label: "Filter (tunnel, method, path or status):", value: mode.value, hint: "\u21B5 apply \xB7 esc cancel" }),
470
+ mode.kind === "port" && /* @__PURE__ */ jsx6(Prompt, { label: "New tunnel \u2014 local port:", value: mode.value, hint: "\u21B5 continue \xB7 esc cancel" }),
471
+ mode.kind === "name" && /* @__PURE__ */ jsx6(Prompt, { label: `Name for localhost:${mode.port} (optional):`, value: mode.value, hint: "\u21B5 open \xB7 esc cancel" }),
471
472
  mode.kind !== "filter" && mode.kind !== "port" && mode.kind !== "name" && /* @__PURE__ */ jsx6(Text6, { color: message ? C[message.tone] : C.muted, children: message?.text ?? " " }),
472
473
  /* @__PURE__ */ jsx6(Text6, { wrap: "truncate-end", children: KEYS.map(([k, label]) => /* @__PURE__ */ jsxs6(Text6, { children: [
473
474
  /* @__PURE__ */ jsx6(Text6, { inverse: true, color: C.signal, children: ` ${k} ` }),