@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.
package/README.md CHANGED
@@ -1,44 +1,44 @@
1
1
  # duto
2
2
 
3
- Exponha um serviço local na internet numa URL HTTPS fixa, com um comando:
3
+ Put a local service on the internet at a stable HTTPS URL, with one command:
4
4
 
5
5
  ```
6
- duto 3000 --name web # https://web-<usuário>.duto.dev → localhost:3000
6
+ duto 3000 --name web # https://web-<user>.duto.dev → localhost:3000
7
7
  ```
8
8
 
9
- A URL não muda entre uma sessão e outra. Por isso serve para webhooks, callbacks de login, testar no celular ou mostrar algo para alguém sem fazer deploy.
9
+ The URL stays the same from one session to the next. That makes it good for webhooks, login callbacks, testing on your phone, or showing someone your work without deploying.
10
10
 
11
- O Duto está em acesso por convite. A conta é criada em [duto.dev](https://duto.dev) a partir de um link de convite.
11
+ Duto is invite-only for now. You create your account at [duto.dev](https://duto.dev) from an invite link.
12
12
 
13
- ## Instalação
13
+ ## Install
14
14
 
15
15
  ```
16
16
  npm i -g @dutodev/cli
17
17
  ```
18
18
 
19
- Requer Node 18 ou superior. Na primeira execução, a CLI baixa uma versão fixada do `cloudflared` oficial da Cloudflare, confere o SHA-256 e guarda em `~/.duto/bin`. Não é preciso instalar mais nada.
19
+ Requires Node 18 or later. On first run, the CLI downloads a pinned release of the official Cloudflare `cloudflared`, checks its SHA-256 and keeps it in `~/.duto/bin`. Nothing else to install.
20
20
 
21
- ## Primeiros passos
21
+ ## Get started
22
22
 
23
- 1. Em [duto.dev](https://duto.dev), abra **Authtoken** e gere o seu token.
24
- 2. Conecte a CLI à sua conta (uma vez por máquina):
23
+ 1. At [duto.dev](https://duto.dev), open **Authtoken** and generate your token.
24
+ 2. Connect the CLI to your account (once per machine):
25
25
 
26
26
  ```
27
27
  duto login <token>
28
28
  ```
29
29
 
30
- 3. Abra um túnel:
30
+ 3. Open a tunnel:
31
31
 
32
32
  ```
33
- duto 3000 # https://<usuário>.duto.dev
34
- duto 3000 --name web # https://web-<usuário>.duto.dev
33
+ duto 3000 # https://<user>.duto.dev
34
+ duto 3000 --name web # https://web-<user>.duto.dev
35
35
  ```
36
36
 
37
- Cada nome usado com `--name` fica reservado para você e mantém a mesma URL. Nomes aceitam só letras minúsculas e números, sem hífen.
37
+ Every name you use with `--name` is reserved for you and keeps the same URL. Names take lowercase letters and numbers only, no hyphens.
38
38
 
39
- ## Vários túneis: `duto.yml`
39
+ ## Several tunnels: `duto.yml`
40
40
 
41
- Crie um `duto.yml` (ou `duto.yaml`) na raiz do projeto e rode só `duto`:
41
+ Create a `duto.yml` (or `duto.yaml`) at the root of your project and just run `duto`:
42
42
 
43
43
  ```yaml
44
44
  tunnels:
@@ -47,159 +47,159 @@ tunnels:
47
47
  webhook: 4000
48
48
  ```
49
49
 
50
- Todos os túneis sobem juntos, numa única tela. A CLI procura o arquivo na pasta atual e nas de cima, até a raiz do git. Se ele for para o repositório, cada pessoa do time abre os próprios endereços (`web-<usuário>.duto.dev`) com o mesmo arquivo.
50
+ All tunnels start together, in one screen. The CLI looks for the file in the current folder and its parents, up to the git root. If you commit it, everyone on the team gets their own addresses (`web-<user>.duto.dev`) from the same file.
51
51
 
52
- ### Host e cabeçalhos
52
+ ### Host and headers
53
53
 
54
- Por padrão, o serviço local recebe a requisição como ela chegou da internet: `Host: web-<usuário>.duto.dev`, `X-Forwarded-Proto: https` e os cabeçalhos da Cloudflare (`CF-Connecting-IP` e outros). Na maioria dos apps isso basta. Quando o app decide algo pelo domínio, há duas ferramentas por túnel:
54
+ By default, your local service gets the request as it arrived from the internet: `Host: web-<user>.duto.dev`, `X-Forwarded-Proto: https` and the Cloudflare headers (`CF-Connecting-IP` and others). That is enough for most apps. When your app decides things by domain, you have two tools per tunnel:
55
55
 
56
56
  ```yaml
57
57
  tunnels:
58
- loja:
58
+ shop:
59
59
  port: 8080
60
- headers: # acrescenta ou sobrescreve cabeçalhos
61
- X-Forwarded-Host: loja.meuapp.test
62
- X-Debug: "" # valor vazio remove o cabeçalho
63
- painel:
60
+ headers: # add or override headers
61
+ X-Forwarded-Host: shop.myapp.test
62
+ X-Debug: "" # an empty value removes the header
63
+ admin:
64
64
  port: 8081
65
- host: painel.meuapp.test # troca o Host que o serviço recebe
65
+ host: admin.myapp.test # replace the Host your service receives
66
66
  ```
67
67
 
68
- **Qual usar:**
68
+ **Which one to use:**
69
69
 
70
- | Situação | Use | Por quê |
70
+ | Situation | Use | Why |
71
71
  |---|---|---|
72
- | Um proxy reverso local (Traefik, Nginx, Caddy) escolhe o serviço pelo Host | `host:` | O proxy só conhece os domínios locais |
73
- | Vários túneis na mesma porta, um por domínio | `host:` ou `headers:` diferentes em cada um | A porta pode se repetir; cada túnel tem o próprio endereço |
74
- | O app identifica o domínio ou o cliente pelo `X-Forwarded-Host`, mas monta URLs a partir do `Host` | `headers:` com `X-Forwarded-Host` | O app vê o domínio local e continua gerando links públicos |
75
- | Login OAuth em que o app calcula o `redirect_uri` sozinho | evite `host:`; tente `headers:` | Veja abaixo |
72
+ | A local reverse proxy (Traefik, Nginx, Caddy) picks the service by Host | `host:` | The proxy only knows your local domains |
73
+ | Several tunnels on one port, one per domain | a different `host:` or `headers:` on each | A port can repeat; each tunnel has its own address |
74
+ | The app identifies the domain or customer by `X-Forwarded-Host` but builds URLs from `Host` | `headers:` with `X-Forwarded-Host` | The app sees the local domain and keeps generating public links |
75
+ | OAuth login where the app computes `redirect_uri` itself | avoid `host:`; try `headers:` | See below |
76
76
 
77
- **O que o `host:` faz:**
77
+ **What `host:` does:**
78
78
 
79
- - O serviço recebe o Host informado, e o `X-Forwarded-Host` não é enviado. Para enviá-lo assim mesmo, coloque-o em `headers:`.
80
- - Os redirecionamentos voltam com o endereço público. Um `Location: http://painel.meuapp.test:8081/login` chega ao navegador como `https://painel-<usuário>.duto.dev/login`. Isso vale também dentro de parâmetros codificados no `Location`. Para desligar, use `rewrite_redirects: false` no túnel.
81
- - Links no corpo das respostas (HTML, JSON) não são reescritos. URLs que o app montar a partir do Host só abrem na sua máquina.
82
- - Na linha de comando: `duto 8081 --host painel.meuapp.test`, e `--no-rewrite-redirects` para desligar a troca.
79
+ - Your service receives the Host you set, and `X-Forwarded-Host` is not sent. To send it anyway, put it in `headers:`.
80
+ - Redirects come back with the public address. A `Location: http://admin.myapp.test:8081/login` reaches the browser as `https://admin-<user>.duto.dev/login`. This also applies inside encoded parameters of the `Location`. To turn it off, set `rewrite_redirects: false` on the tunnel.
81
+ - Links in response bodies (HTML, JSON) are not rewritten. URLs your app builds from the Host only open on your machine.
82
+ - On the command line: `duto 8081 --host admin.myapp.test`, and `--no-rewrite-redirects` to turn the swap off.
83
83
 
84
- **OAuth e `host:`.** No fluxo de autorização por código, o app manda um `redirect_uri` duas vezes: na ida do navegador ao provedor de login e depois, servidor a servidor, ao trocar o código por token. O provedor exige que as duas sejam idênticas. Com `host:`, o app gera as duas com o domínio local. O Duto corrige a primeira, que passa pelo navegador, mas não alcança a segunda, que não passa pelo túnel. O provedor recusa a troca, em geral com `invalid_request` ou `invalid_grant`, e o app costuma voltar ao login em loop (`ERR_TOO_MANY_REDIRECTS`).
84
+ **OAuth and `host:`.** In the authorization code flow, the app sends a `redirect_uri` twice: once when the browser goes to the login provider, and again, server to server, when it exchanges the code for a token. The provider requires both to be identical. With `host:`, the app builds both from the local domain. Duto fixes the first, which goes through the browser, but cannot reach the second, which never passes through the tunnel. The provider rejects the exchange, usually with `invalid_request` or `invalid_grant`, and the app often loops back to login (`ERR_TOO_MANY_REDIRECTS`).
85
85
 
86
- Saídas, da mais simples para a mais trabalhosa:
86
+ Ways out, from simplest to most work:
87
87
 
88
- 1. Não troque o Host. O app gera o `redirect_uri` com o endereço público nas duas vezes.
89
- 2. Se o app precisa ver o domínio local para outras decisões e lê o `X-Forwarded-Host`, passe o domínio por `headers:` e mantenha o Host público.
90
- 3. Configure no app a URL pública de retorno, se ele permitir.
88
+ 1. Do not swap the Host. The app builds `redirect_uri` from the public address both times.
89
+ 2. If the app needs the local domain for other decisions and reads `X-Forwarded-Host`, pass the domain through `headers:` and keep the public Host.
90
+ 3. Set the public callback URL in the app, if it lets you.
91
91
 
92
- Em todos os casos, cadastre `https://<nome>-<usuário>.duto.dev/<caminho de retorno>` como URL de retorno no provedor. A comparação costuma ser exata, sem curingas: cada pessoa do time tem o próprio endereço.
92
+ In every case, register `https://<name>-<user>.duto.dev/<callback path>` as the callback URL at the provider. The comparison is usually exact, with no wildcards: each person on the team has their own address.
93
93
 
94
- **Sobre o `X-Forwarded-*`.** `X-Forwarded-Host`, `X-Forwarded-Proto` e `X-Forwarded-For` são uma convenção antiga entre proxies reversos para contar ao app o que o cliente pediu de verdade. A versão padronizada é o cabeçalho `Forwarded` (RFC 7239), menos usado na prática. Frameworks costumam ler esses cabeçalhos só quando o proxy é marcado como confiável (`trusted proxies`, `trust proxy`, `USE_X_FORWARDED_HOST`), então o efeito de `headers:` depende de como o app está configurado. Por outro lado, se um app lê o `X-Forwarded-Host` sem essa checagem, qualquer cliente pode forjá-lo em produção. Vale garantir que o proxy da frente sobrescreva ou remova esse cabeçalho.
94
+ **About `X-Forwarded-*`.** `X-Forwarded-Host`, `X-Forwarded-Proto` and `X-Forwarded-For` are an old convention between reverse proxies for telling the app what the client actually asked for. The standard version is the `Forwarded` header (RFC 7239), which is less used in practice. Frameworks usually read these headers only when the proxy is marked as trusted (`trusted proxies`, `trust proxy`, `USE_X_FORWARDED_HOST`), so what `headers:` does depends on how your app is configured. On the other hand, if an app reads `X-Forwarded-Host` without that check, any client can forge it in production. Make sure the proxy in front overwrites or removes that header.
95
95
 
96
- ## Senha no túnel
96
+ ## Password-protect a tunnel
97
97
 
98
- Sem senha, qualquer pessoa com a URL acessa o seu ambiente de desenvolvimento. Para proteger um túnel, coloque `auth` (e, se precisar, `permitir`) no `duto.yml`:
98
+ Without a password, anyone with the URL can reach your development environment. To protect a tunnel, add `auth` (and `allow`, if you need it) to `duto.yml`:
99
99
 
100
100
  ```yaml
101
101
  tunnels:
102
102
  backend:
103
103
  port: 3000
104
104
  auth:
105
- - cliente:${SENHA_CLIENTE}
106
- - ana:${SENHA_ANA}
107
- permitir: [stripe, github, 203.0.113.0/24]
105
+ - client:${CLIENT_PASSWORD}
106
+ - ana:${ANA_PASSWORD}
107
+ allow: [stripe, github, 203.0.113.0/24]
108
108
  ```
109
109
 
110
- - `auth` é uma lista de `usuario:senha`. Use um par por pessoa. O primeiro `:` separa o usuário da senha; a senha tem de 8 a 128 caracteres.
111
- - `${VAR}` lê o valor de uma variável de ambiente. Use sempre: assim a senha não vai para o git junto com o `duto.yml`. Se a variável não existe, a CLI para com o número da linha. O nome só pode ter letras maiúsculas, dígitos e `_` (`${SENHA_ANA}`); qualquer outra coisa, como `${senha}`, fica no texto sem trocar e vira parte da senha.
112
- - Na lista de uma linha, entre colchetes, o `${VAR}` precisa de aspas (`auth: ["cliente:${SENHA_CLIENTE}"]`), porque `{` é um caractere especial do YAML. Na lista em bloco, como acima, não precisa.
113
- - Na linha de comando, para um túnel só: `duto 3000 --auth cliente:minhasenha1 --permitir stripe`. As duas opções se repetem. Elas só valem com a porta; junto com um `duto.yml`, coloque-as no arquivo.
114
- - Cuidado: a senha passada em `--auth` fica no histórico do shell e aparece para quem rodar `ps` na máquina. Para algo além de um teste rápido, prefira o `duto.yml` com `${VAR}`.
110
+ - `auth` is a list of `user:password`. Use one pair per person. The first `:` separates the user from the password; the password is 8 to 128 characters.
111
+ - `${VAR}` reads an environment variable. Always use it: that keeps the password out of git along with `duto.yml`. If the variable does not exist, the CLI stops and prints the line number. The name can only have uppercase letters, digits and `_` (`${ANA_PASSWORD}`); anything else, like `${password}`, is left as text and becomes part of the password.
112
+ - In a one-line list in brackets, `${VAR}` needs quotes (`auth: ["client:${CLIENT_PASSWORD}"]`), because `{` is special in YAML. In a block list, as above, it does not.
113
+ - On the command line, for a single tunnel: `duto 3000 --auth client:my-password1 --allow stripe`. Both options can be repeated. They only work with a port; with a `duto.yml`, put them in the file.
114
+ - Careful: a password passed to `--auth` ends up in your shell history and is visible to anyone who runs `ps` on the machine. For anything beyond a quick test, use `duto.yml` with `${VAR}`.
115
115
 
116
- Quem usa o túnel entra de um destes jeitos:
116
+ People get in one of these ways:
117
117
 
118
- | Quem | Como |
118
+ | Who | How |
119
119
  |---|---|
120
- | Pessoa no navegador | Abre a URL, vê a página "Este túnel tem senha" e entra com usuário e senha |
121
- | Celular | Escaneia o QR code (tecla `m`, veja abaixo) e entra sem digitar |
122
- | `curl`, Postman, scripts | `curl -u cliente:senha https://backend-<usuário>.duto.dev/` (`Authorization: Basic`) |
123
- | API que já usa `Authorization: Bearer` | Cabeçalho `X-Duto-Auth: cliente:senha`, e o `Bearer` segue intacto até o app |
120
+ | Person in a browser | Opens the URL, sees the "This tunnel is password protected" page and signs in with user and password |
121
+ | Phone | Scans the QR code (key `m`, see below) and gets in without typing |
122
+ | `curl`, Postman, scripts | `curl -u client:password https://backend-<user>.duto.dev/` (`Authorization: Basic`) |
123
+ | An API that already uses `Authorization: Bearer` | Send `X-Duto-Auth: client:password`; the `Bearer` goes through to the app untouched |
124
124
 
125
- Sem credencial, um navegador é levado à página de entrada e os outros clientes recebem `401`. O app local nunca recebe a credencial do Duto: o `Authorization: Basic` que confere, o `X-Duto-Auth` e o cookie do túnel são removidos antes de repassar a requisição (os outros cookies chegam). Um `Authorization: Bearer` do próprio app passa sem mudança.
125
+ Without credentials, a browser is sent to the sign-in page and other clients get `401`. Your local app never receives Duto's credentials: the `Authorization: Basic` that matched, `X-Duto-Auth` and the tunnel cookie are stripped before the request is forwarded (other cookies pass through). An `Authorization: Bearer` from your own app passes unchanged.
126
126
 
127
- **Sair e sessão.** No navegador, o login vira um cookie que vale só para aquele túnel. Para sair, abra `/__duto/sair`: ele apaga o cookie e volta para a página de entrada. O cookie dura até o navegador fechar ou a CLI parar. Reiniciar a CLI desconecta todo mundo, porque a chave que assina os cookies é nova a cada sessão. O cookie só vale em requisições do próprio túnel: uma página de outra origem (outro túnel, outro site) que chame o túnel precisa mandar `X-Duto-Auth` ou `Authorization: Basic`. Por isso, abrir o túnel a partir de um link em outro endereço `*.duto.dev` (o painel ou outro túnel) pede a senha de novo; um link vindo de fora (e-mail, chat) ou a URL digitada entra direto.
127
+ **Logging out and sessions.** In a browser, signing in sets a cookie that is valid for that tunnel only. To log out, open `/__duto/logout`: it clears the cookie and returns to the sign-in page. The cookie lasts until the browser closes or the CLI stops. Restarting the CLI signs everyone out, because the key that signs the cookies is new every session. The cookie only counts on requests to the tunnel itself: a page on another origin (another tunnel, another site) that calls the tunnel has to send `X-Duto-Auth` or `Authorization: Basic`. So opening the tunnel from a link on another `*.duto.dev` address (the dashboard or another tunnel) asks for the password again; a link from outside (email, chat) or a typed URL gets straight in.
128
128
 
129
- **QR code.** Com a tela cheia aberta, a tecla `m` mostra o QR do túnel selecionado no lugar da lista de requisições. Num túnel sem senha, o QR leva a URL do túnel. Num túnel com senha, leva um link que entra direto, sem digitar nada, como o primeiro usuário da lista `auth`. Esse link vale por 5 minutos e só para um aparelho: depois de aberto uma vez, ou passado o prazo, cai na página de entrada. Na tela do QR, `g` gera um link novo (e invalida o anterior) e `esc` volta para a lista.
129
+ **QR code.** With the full screen open, key `m` shows the QR of the selected tunnel in place of the request list. On a tunnel without a password, the QR points to the tunnel URL. On a tunnel with a password, it points to a link that signs in directly, as the first user in the `auth` list. That link is valid for 5 minutes and for one device only: once opened, or after it expires, it falls back to the sign-in page. On the QR screen, `g` generates a new link (and invalidates the previous one) and `esc` goes back to the list.
130
130
 
131
- **CORS.** Um frontend em outro domínio continua funcionando. O preflight (`OPTIONS` com `Origin` e `Access-Control-Request-Method`, sem corpo) passa sem senha, mas dele só voltam o status e os cabeçalhos de CORS da resposta do app. Quando o app responde com CORS, o Duto acrescenta `X-Duto-Auth` ao `Access-Control-Allow-Headers`. Você não muda nada no app. A resposta `401` também leva os cabeçalhos de CORS, para o frontend ler o erro em vez de ver uma falha de rede.
131
+ **CORS.** A frontend on another domain keeps working. The preflight (`OPTIONS` with `Origin` and `Access-Control-Request-Method`, no body) goes through without a password, but only the status and the CORS headers of your app's response come back from it. When the app answers with CORS, Duto adds `X-Duto-Auth` to `Access-Control-Allow-Headers`. You change nothing in the app. The `401` response also carries CORS headers, so the frontend can read the error instead of seeing a network failure.
132
132
 
133
- **Webhooks.** `permitir` libera, sem senha, quem chega de certas origens: os nomes `stripe` e `github` (a CLI baixa a lista oficial de IPs deles, guarda por um dia em `~/.duto/` e usa uma cópia embutida se a rede falhar) e faixas de IP, IPv4 ou IPv6, como `203.0.113.0/24`. `permitir` só vale junto com `auth`.
133
+ **Webhooks.** `allow` lets certain sources in without a password: the names `stripe` and `github` (the CLI downloads their official IP lists, keeps them for a day in `~/.duto/` and falls back to a built-in copy if the network fails) and IP ranges, IPv4 or IPv6, like `203.0.113.0/24`. `allow` only works together with `auth`.
134
134
 
135
- Na tela, os túneis com senha aparecem com um cadeado (🔒) ao lado da URL. A lista de requisições mostra o usuário que entrou e o motivo das liberadas por `permitir`; as barradas com `401` levam "sem senha", e os navegadores barrados aparecem com o redirecionamento `303` para a página de entrada. Um túnel aberto com `n` na tela cheia não tem senha.
135
+ On screen, password-protected tunnels show a lock (🔒) next to the URL. The request list shows who signed in and why `allow` let a request through; requests turned away with `401` are labeled "no password", and browsers turned away show up as a `303` redirect to the sign-in page. A tunnel opened with `n` in the full screen has no password.
136
136
 
137
- ## A tela
137
+ ## The screen
138
138
 
139
- Com os túneis no ar, o terminal mostra cada túnel (URL, destino local, requisições e erros) e as requisições chegando ao vivo: método, caminho, status, tempo e tamanho. WebSocket funciona normalmente e aparece como `WS 101`.
139
+ With your tunnels live, the terminal shows each tunnel (URL, local target, requests and errors) and the requests as they arrive: method, path, status, time and size. WebSocket works as usual and shows up as `WS 101`.
140
140
 
141
- | Tecla | O que faz |
141
+ | Key | What it does |
142
142
  |---|---|
143
- | `n` | Abre mais um túnel (só nesta sessão) |
144
- | `c` / `o` | Copia / abre no navegador a URL do túnel selecionado |
145
- | `m` | Mostra o QR code do túnel selecionado, para abrir no celular |
146
- | `f` | Filtra por túnel, método, caminho ou status |
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 |
150
- | `tab`, `↑↓` | Alterna entre túneis e requisições e move a seleção |
151
- | `q` ou Ctrl+C | Fecha os túneis e sai |
143
+ | `n` | Open another tunnel (this session only) |
144
+ | `c` / `o` | Copy / open in the browser the selected tunnel's URL |
145
+ | `m` | Show the QR code of the selected tunnel, to open on your phone |
146
+ | `f` | Filter by tunnel, method, path or status |
147
+ | `↵` | Request details: headers and the start of the bodies |
148
+ | `r` | In the details, replay the request against your local service |
149
+ | `i` | Open the inspector in the browser |
150
+ | `tab`, `↑↓` | Switch between tunnels and requests, and move the selection |
151
+ | `q` or Ctrl+C | Close the tunnels and quit |
152
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.
153
+ **Replaying a request.** In the details (`↵`), key `r` sends the same method, path, headers and body your app received back to your local service, with the tunnel's `host:` and `headers:` rules applied and without Duto's own credentials. The replay joins the list marked with `↻`, the screen jumps to its details (`↻ replay of #N`) and `esc` goes back. It is handy for debugging webhooks without waiting for the external service to send again; in particular, a request that got `503` because your local service was down can be replayed as soon as it is up. Because the bytes are identical, webhook signatures stay valid. Apps that reject old events (Stripe tolerates 5 minutes by default) will reject a replay after that window.
154
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).
155
+ You cannot replay WebSocket requests, requests blocked by the tunnel password, binary bodies, bodies over 64 KB, requests whose body had not finished arriving, or anything once the tunnel is no longer open; in those cases a line explains why. Replay is not available in the plain log (no interactive terminal).
156
156
 
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.
157
+ If the tunnel is live but nothing answers on the local port, anyone opening the address in a browser sees a Duto page that reloads itself when the service comes up. It waits on a single connection (SSE): the CLI checks the port on your machine and tells the page as soon as the service responds, with no periodic reloading. Clients that are not browsers (`curl`, APIs) get the same response as text. Either way the status is `503` with `Retry-After: 3`. Paths under `/__duto/…` are reserved for this and never reach your app.
158
158
 
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.
159
+ Outside an interactive terminal (pipe, CI) or with `--no-ui`, the CLI prints a plain log. On Windows the full screen is off by default; use `--ui` to try it.
160
160
 
161
- ## Inspetor no navegador
161
+ ## Inspector in the browser
162
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.
163
+ With every session, the CLI starts a local inspector: a browser page with requests arriving live, formatted JSON, and buttons to copy and replay. It is more comfortable than the terminal for large bodies.
164
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.
165
+ - **How to open it.** In the full screen, key `i` opens the browser at the right address, and the screen shows `inspector http://127.0.0.1:<port>`. In plain mode (pipe, CI, `--no-ui`), the CLI prints `inspector: <url>?t=<token>`; copy that whole address.
166
+ - **Port.** The default is `127.0.0.1:4141`. If it is taken, the CLI tries the next ones up to 4161 and then any free port. The inspector listens on `127.0.0.1` only: other machines on your network cannot reach it.
167
+ - **Token.** The address carries a session token (`?t=...`). It exists so other sites open in your browser cannot read or replay your requests. That is why the full screen does not show the token; use key `i`.
168
+ - **Turn it off.** `--no-inspect` does not start the inspector. In plain mode the address with the token goes to the output; if that goes to a shared log, use `--no-inspect`.
169
+ - **Nothing goes to disk.** Requests stay in the session's memory and disappear when it ends. It works offline.
170
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.
171
+ The page has the list on the left and the details on the right, with the tabs **Body**, **Headers**, **Response** and **Raw**. JSON is re-indented exactly as it arrived, with syntax errors highlighted.
172
172
 
173
- | Ação | Como |
173
+ | Action | How |
174
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 |
175
+ | Copy the body | **Copy body** copies the body of the active tab |
176
+ | Copy as `curl` | **Copy as curl**; on a tunnel with a password, the command carries `-H 'X-Duto-Auth: <user>:PASSWORD'` and you swap `PASSWORD` for the password |
177
+ | Replay | **↻ Replay** or key `r`, with the same rules as the terminal |
178
+ | Filter | `/` focuses the filter |
179
+ | Navigate | `↑` and `↓` move the selection |
180
180
 
181
- ## Comandos
181
+ ## Commands
182
182
 
183
- | Comando | O que faz |
183
+ | Command | What it does |
184
184
  |---|---|
185
- | `duto` | Abre os túneis do `duto.yml` |
186
- | `duto <porta>` | Abre um túnel para `localhost:<porta>` |
187
- | `duto http <porta>` | O mesmo, para quem vem do ngrok |
188
- | `duto login <token>` | Salva o authtoken nesta máquina |
189
- | `duto status` | Mostra a conta e os túneis no ar |
190
- | `duto logout` | Remove o authtoken desta máquina |
191
-
192
- | Opção | O que faz |
185
+ | `duto` | Open the tunnels in `duto.yml` |
186
+ | `duto <port>` | Open a tunnel to `localhost:<port>` |
187
+ | `duto http <port>` | Same, for people coming from ngrok |
188
+ | `duto login <token>` | Save the authtoken on this machine |
189
+ | `duto status` | Show your account and live tunnels |
190
+ | `duto logout` | Remove the authtoken from this machine |
191
+
192
+ | Option | What it does |
193
193
  |---|---|
194
- | `--name <nome>` | Endereço fixo `<nome>-<usuário>.duto.dev` |
195
- | `--host <host>` | Host que o serviço local recebe no lugar do público |
196
- | `--no-rewrite-redirects` | Com `--host`, mantém os redirecionamentos como o serviço mandou |
197
- | `--auth <usuario:senha>` | Pede usuário e senha para abrir o endereço (repetível) |
198
- | `--permitir <faixa>` | Com `--auth`: IP, faixa ou nome (`stripe`, `github`) que entra sem senha (repetível) |
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).") |
201
-
202
- ## Configuração
203
-
204
- - A config fica em `~/.duto/config.json` (permissão 600). `DUTO_HOME` muda essa pasta.
205
- - `DUTO_API_URL` aponta a CLI para outro servidor (padrão: `https://duto.dev`).
194
+ | `--name <name>` | Fixed address `<name>-<user>.duto.dev` |
195
+ | `--host <host>` | Host the local service receives instead of the public one |
196
+ | `--no-rewrite-redirects` | With `--host`, keep redirects as the service sent them |
197
+ | `--auth <user:password>` | Ask for a username and password to open the address (repeatable) |
198
+ | `--allow <range>` | With `--auth`: IP, CIDR range or name (`stripe`, `github`) that gets in without a password (repeatable) |
199
+ | `--no-ui` / `--ui` | Turn the full screen off / force it on |
200
+ | `--no-inspect` | Do not start the browser inspector (key `i` then shows "Inspector is off (--no-inspect).") |
201
+
202
+ ## Configuration
203
+
204
+ - Config lives in `~/.duto/config.json` (mode 600). `DUTO_HOME` changes that folder.
205
+ - `DUTO_API_URL` points the CLI at another server (default: `https://duto.dev`).
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // ../shared/src/names.ts
4
+ var EMAIL_SENDING_SUBDOMAINS = ["send"];
4
5
  var RESERVED_NAMES = [
5
6
  "www",
6
7
  "api",
@@ -23,8 +24,7 @@ var RESERVED_NAMES = [
23
24
  "help",
24
25
  "support",
25
26
  "blog",
26
- // Subdomínios usados pelo envio de e-mail (Resend).
27
- "send"
27
+ ...EMAIL_SENDING_SUBDOMAINS
28
28
  ];
29
29
  var MAX_NAME = 40;
30
30
  var FORMAT = /^[a-z0-9]+$/;
@@ -57,15 +57,15 @@ function fit(text, width) {
57
57
  }
58
58
 
59
59
  // src/session/bodyNote.ts
60
- var TRUNCATED_NOTE = "Corpo cortado em 64 KB.";
60
+ var TRUNCATED_NOTE = "Body cut at 64 KB.";
61
61
  function bodyNote(r, side) {
62
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.";
63
+ if (r.reqBodyBytes === 0) return "No body.";
64
+ if (r.reqBody === null && !r.reqTruncated) return "Binary body; not stored.";
65
65
  return null;
66
66
  }
67
- if (r.bytes === 0) return "Sem corpo.";
68
- if (r.resBody === null) return "Corpo bin\xE1rio; n\xE3o \xE9 guardado.";
67
+ if (r.bytes === 0) return "No body.";
68
+ if (r.resBody === null) return "Binary body; not stored.";
69
69
  return null;
70
70
  }
71
71