@dutodev/cli 0.7.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.
@@ -12,7 +12,7 @@ import {
12
12
  statusColor,
13
13
  tunnelColor,
14
14
  validateName
15
- } from "./chunk-74IJFYGT.js";
15
+ } from "./chunk-BPOXISIM.js";
16
16
 
17
17
  // src/ui/fullscreen.tsx
18
18
  import { render } from "ink";
@@ -40,9 +40,9 @@ function Section({ title, headers, body, truncated, note: note2 }) {
40
40
  v
41
41
  ] }, k)),
42
42
  entries.length > MAX_HEADERS && /* @__PURE__ */ jsxs(Text, { color: C.muted, children: [
43
- "\u2026 mais ",
43
+ "\u2026 ",
44
44
  entries.length - MAX_HEADERS,
45
- " cabe\xE7alhos"
45
+ " more headers"
46
46
  ] }),
47
47
  body === null ? /* @__PURE__ */ jsx(Text, { color: C.muted, children: note2 ?? "" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
48
48
  lines.slice(0, MAX_BODY_LINES).map((l, i) => /* @__PURE__ */ jsx(Text, { wrap: "truncate-end", children: l }, i)),
@@ -70,15 +70,15 @@ function Detail({ record }) {
70
70
  ] })
71
71
  ] }),
72
72
  record.replayOf !== void 0 && /* @__PURE__ */ jsxs(Text, { color: C.muted, children: [
73
- "\u21BB reenvio de #",
73
+ "\u21BB replay of #",
74
74
  record.replayOf
75
75
  ] }),
76
76
  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") }),
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") }),
79
79
  /* @__PURE__ */ jsxs(Text, { color: C.muted, children: [
80
80
  "\n",
81
- "r reenvia \xB7 esc volta para a lista"
81
+ "r replay \xB7 esc back to list"
82
82
  ] })
83
83
  ] });
84
84
  }
@@ -101,16 +101,16 @@ import { Box as Box2, Text as Text3 } from "ink";
101
101
 
102
102
  // src/ui/qr.ts
103
103
  import qrcode from "qrcode-generator";
104
- var MARGEM = 2;
104
+ var MARGIN = 2;
105
105
  function qrLines(text) {
106
106
  const qr = qrcode(0, "L");
107
107
  qr.addData(text);
108
108
  qr.make();
109
109
  const n = qr.getModuleCount();
110
- const size = n + MARGEM * 2;
110
+ const size = n + MARGIN * 2;
111
111
  const dark = (r, c) => {
112
- const rr = r - MARGEM;
113
- const cc = c - MARGEM;
112
+ const rr = r - MARGIN;
113
+ const cc = c - MARGIN;
114
114
  return rr >= 0 && cc >= 0 && rr < n && cc < n && qr.isDark(rr, cc);
115
115
  };
116
116
  const lines = [];
@@ -137,7 +137,7 @@ function QrView({ tunnel, link, now }) {
137
137
  const left = link.expiresAt === null ? null : link.expiresAt - now;
138
138
  const expired = left !== null && Math.ceil(left / 1e3) <= 0;
139
139
  return /* @__PURE__ */ jsxs3(Box2, { flexDirection: "column", children: [
140
- /* @__PURE__ */ jsx3(Text3, { color: C.muted, children: "Abra no celular" }),
140
+ /* @__PURE__ */ jsx3(Text3, { color: C.muted, children: "Open on your phone" }),
141
141
  /* @__PURE__ */ jsxs3(Box2, { children: [
142
142
  /* @__PURE__ */ jsx3(Box2, { flexDirection: "column", marginRight: 3, children: lines.map((l, i) => /* @__PURE__ */ jsx3(Text3, { backgroundColor: "white", color: "black", children: l }, i)) }),
143
143
  /* @__PURE__ */ jsxs3(Box2, { flexDirection: "column", children: [
@@ -151,14 +151,14 @@ function QrView({ tunnel, link, now }) {
151
151
  ] }),
152
152
  left !== null && /* @__PURE__ */ jsxs3(Fragment2, { children: [
153
153
  /* @__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." }),
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." }),
157
157
  /* @__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)}` })
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)}` })
159
159
  ] }),
160
160
  /* @__PURE__ */ jsx3(Text3, { children: " " }),
161
- /* @__PURE__ */ jsx3(Text3, { color: C.muted, children: left !== null ? "g gerar outro \xB7 esc voltar" : "esc voltar" })
161
+ /* @__PURE__ */ jsx3(Text3, { color: C.muted, children: left !== null ? "g new code \xB7 esc back" : "esc back" })
162
162
  ] })
163
163
  ] })
164
164
  ] });
@@ -171,7 +171,7 @@ function accessLabel(a) {
171
171
  if (!a) return null;
172
172
  if (a.kind === "user") return { text: a.user, color: C.muted };
173
173
  if (a.kind === "allow") return { text: a.reason, color: C.muted };
174
- return { text: "sem senha", color: C.warn };
174
+ return { text: "no password", color: C.warn };
175
175
  }
176
176
  function RequestList(props) {
177
177
  const { records, selected, focused, height, width } = props;
@@ -179,14 +179,14 @@ function RequestList(props) {
179
179
  const visible = records.slice(start, start + height);
180
180
  const accessW = Math.max(0, ...visible.map((r) => (accessLabel(r.access)?.text.length ?? -1) + 1));
181
181
  const pathW = Math.max(10, width - 54 - accessW);
182
- const title = props.filter ? `REQUISI\xC7\xD5ES \xB7 filtro: ${props.filter} ` : "REQUISI\xC7\xD5ES ";
182
+ const title = props.filter ? `REQUESTS \xB7 filter: ${props.filter} ` : "REQUESTS ";
183
183
  return /* @__PURE__ */ jsxs4(Box3, { flexDirection: "column", children: [
184
184
  /* @__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) => {
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) => {
186
186
  const sel = focused && start + i === selected;
187
187
  const method = r.websocket ? "WS" : r.method;
188
188
  const status = r.status === null ? "\u2026" : String(r.status);
189
- const tail = r.websocket && r.status === 101 ? "aberto" : r.durationMs === null ? "" : formatDuration(r.durationMs);
189
+ const tail = r.websocket && r.status === 101 ? "open" : r.durationMs === null ? "" : formatDuration(r.durationMs);
190
190
  const acc = accessLabel(r.access);
191
191
  return /* @__PURE__ */ jsxs4(Text4, { inverse: sel, wrap: "truncate-end", children: [
192
192
  /* @__PURE__ */ jsx4(Text4, { color: C.muted, children: formatTime(r.startedAt) }),
@@ -212,10 +212,10 @@ function RequestList(props) {
212
212
  import { Box as Box4, Text as Text5 } from "ink";
213
213
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
214
214
  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 };
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 };
219
219
  return null;
220
220
  }
221
221
  function dot(t) {
@@ -233,7 +233,7 @@ function TunnelTable({ tunnels, selected, focused }) {
233
233
  return /* @__PURE__ */ jsxs5(Box4, { flexDirection: "column", children: [
234
234
  /* @__PURE__ */ jsxs5(Text5, { color: C.muted, children: [
235
235
  " ",
236
- fit("T\xDANEL", nameW),
236
+ fit("TUNNEL", nameW),
237
237
  " ",
238
238
  fit("URL", urlW),
239
239
  anyAuth ? " " : "",
@@ -242,7 +242,7 @@ function TunnelTable({ tunnels, selected, focused }) {
242
242
  " ",
243
243
  "REQ".padStart(4),
244
244
  " ",
245
- "ERROS".padStart(5)
245
+ "ERR".padStart(5)
246
246
  ] }),
247
247
  tunnels.map((t, i) => {
248
248
  const d = dot(t);
@@ -273,15 +273,15 @@ function TunnelTable({ tunnels, selected, focused }) {
273
273
  // src/ui/App.tsx
274
274
  import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
275
275
  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"]
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"]
285
285
  ];
286
286
  function App({ session, version, system, onExit, inspector = null, inspectorError }) {
287
287
  const subState = useCallback((fn) => session.subscribe(fn), [session]);
@@ -296,7 +296,7 @@ function App({ session, version, system, onExit, inspector = null, inspectorErro
296
296
  const [focus, setFocus] = useState("requests");
297
297
  const [selReq, setSelReq] = useState(0);
298
298
  const [selTun, setSelTun] = useState(0);
299
- const [message, setMessage] = useState(inspectorError ? { text: `Inspetor n\xE3o subiu: ${inspectorError}`, tone: "warn" } : null);
299
+ const [message, setMessage] = useState(inspectorError ? { text: `Inspector failed to start: ${inspectorError}`, tone: "warn" } : null);
300
300
  const [now, setNow] = useState(() => Date.now());
301
301
  const quitting = useRef(false);
302
302
  const records = useMemo(() => {
@@ -332,36 +332,36 @@ function App({ session, version, system, onExit, inspector = null, inspectorErro
332
332
  async function quit() {
333
333
  if (quitting.current) return;
334
334
  quitting.current = true;
335
- setMessage({ text: "Encerrando os t\xFAneis\u2026", tone: "muted" });
335
+ setMessage({ text: "Closing tunnels\u2026", tone: "muted" });
336
336
  await session.stop();
337
337
  onExit();
338
338
  }
339
339
  async function openInspector() {
340
- if (!inspector) return setMessage({ text: "Inspetor desligado (--no-inspect).", tone: "warn" });
340
+ if (!inspector) return setMessage({ text: "Inspector is off (--no-inspect).", tone: "warn" });
341
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" });
342
+ setMessage(ok ? { text: "Opening the inspector", tone: "signal" } : { text: "Could not open the browser.", tone: "warn" });
343
343
  }
344
344
  async function act(kind) {
345
345
  const t = currentTunnel();
346
- if (!t?.url) return setMessage({ text: "Esse t\xFAnel ainda n\xE3o tem URL.", tone: "warn" });
346
+ if (!t?.url) return setMessage({ text: "That tunnel has no URL yet.", tone: "warn" });
347
347
  if (kind === "copy") {
348
348
  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" });
349
+ setMessage(ok ? { text: `URL copied: ${t.url}`, tone: "signal" } : { text: "Could not copy \u2014 select the URL in the table", tone: "warn" });
350
350
  } else {
351
351
  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" });
352
+ setMessage(ok ? { text: `Opening ${t.url}`, tone: "signal" } : { text: "Could not open the browser", tone: "warn" });
353
353
  }
354
354
  }
355
355
  function showQr() {
356
356
  const t = currentTunnel();
357
357
  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" });
358
+ if (!t || !link) return setMessage({ text: "That tunnel has no URL yet.", tone: "warn" });
359
359
  setNow(Date.now());
360
360
  setMode({ kind: "qr", key: t.key, link });
361
361
  }
362
362
  function submitPort(value) {
363
363
  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" });
364
+ if (!Number.isInteger(port) || port < 1 || port > 65535) return setMessage({ text: `Invalid port: ${value || "(empty)"} (use 1\u201365535)`, tone: "err" });
365
365
  setMode({ kind: "name", value: "", port });
366
366
  }
367
367
  function submitName(value, port) {
@@ -369,12 +369,12 @@ function App({ session, version, system, onExit, inspector = null, inspectorErro
369
369
  const name = raw ? normalizeName(raw) : null;
370
370
  if (name !== null) {
371
371
  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" });
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" });
373
373
  }
374
374
  setMode({ kind: "list" });
375
375
  void session.add({ name, port });
376
376
  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" }
377
+ name ? { text: `To keep it, add to duto.yml: ${name}: ${port}`, tone: "muted" } : { text: `Opening localhost:${port} for this session only`, tone: "muted" }
378
378
  );
379
379
  }
380
380
  useInput((input, key) => {
@@ -433,7 +433,7 @@ function App({ session, version, system, onExit, inspector = null, inspectorErro
433
433
  if (input === "n") return setMode({ kind: "port", value: "" });
434
434
  });
435
435
  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 };
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
437
  const listHeight = Math.max(3, rows - state.tunnels.length - (inspector ? 10 : 9));
438
438
  const detailRecord = mode.kind === "detail" ? all.find((r) => r.id === mode.id) : void 0;
439
439
  const qrTunnel = mode.kind === "qr" ? state.tunnels.find((t) => t.key === mode.key) : void 0;
@@ -454,20 +454,20 @@ function App({ session, version, system, onExit, inspector = null, inspectorErro
454
454
  live,
455
455
  "/",
456
456
  state.tunnels.length,
457
- " no ar"
457
+ " live"
458
458
  ] })
459
459
  ] }),
460
460
  /* @__PURE__ */ jsx6(Text6, { color: conn.color, children: conn.text })
461
461
  ] }),
462
462
  /* @__PURE__ */ jsx6(Text6, { children: " " }),
463
463
  /* @__PURE__ */ jsx6(TunnelTable, { tunnels: state.tunnels, selected: selTun, focused: focus === "tunnels" }),
464
- inspector && /* @__PURE__ */ jsx6(Text6, { color: C.muted, children: `inspetor ${inspector.url}` }),
464
+ inspector && /* @__PURE__ */ jsx6(Text6, { color: C.muted, children: `inspector ${inspector.url}` }),
465
465
  /* @__PURE__ */ jsx6(Text6, { children: " " }),
466
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 }),
467
467
  /* @__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" }),
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" }),
471
471
  mode.kind !== "filter" && mode.kind !== "port" && mode.kind !== "name" && /* @__PURE__ */ jsx6(Text6, { color: message ? C[message.tone] : C.muted, children: message?.text ?? " " }),
472
472
  /* @__PURE__ */ jsx6(Text6, { wrap: "truncate-end", children: KEYS.map(([k, label]) => /* @__PURE__ */ jsxs6(Text6, { children: [
473
473
  /* @__PURE__ */ jsx6(Text6, { inverse: true, color: C.signal, children: ` ${k} ` }),