@dutodev/cli 1.8.0 → 1.8.1

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.
Files changed (3) hide show
  1. package/README.md +17 -12
  2. package/dist/index.js +4 -4
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
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-<user>.duto.dev → localhost:3000
6
+ duto 3000 --name web # https://web-<user>.duto.page → localhost:3000
7
7
  ```
8
8
 
9
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.
@@ -20,8 +20,13 @@ Requires Node 18 or later. On first run, the CLI downloads a pinned release of t
20
20
 
21
21
  ## Get started
22
22
 
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):
23
+ 1. Connect the CLI to your account (once per machine). It opens your browser so you can approve it:
24
+
25
+ ```
26
+ duto login
27
+ ```
28
+
29
+ 2. On CI and servers with no browser, create a token on the **Machines** page at [duto.dev](https://duto.dev) and pass it:
25
30
 
26
31
  ```
27
32
  duto login <token>
@@ -30,8 +35,8 @@ Requires Node 18 or later. On first run, the CLI downloads a pinned release of t
30
35
  3. Open a tunnel:
31
36
 
32
37
  ```
33
- duto 3000 # https://<user>.duto.dev
34
- duto 3000 --name web # https://web-<user>.duto.dev
38
+ duto 3000 # https://<user>.duto.page
39
+ duto 3000 --name web # https://web-<user>.duto.page
35
40
  ```
36
41
 
37
42
  Every name you use with `--name` keeps the same URL, and only you can use it. Names take lowercase letters and numbers only, no hyphens. The account limit counts named tunnels online at once: closed ones do not count, and names unused for 30 days are released.
@@ -47,13 +52,13 @@ tunnels:
47
52
  webhook: 4000
48
53
  ```
49
54
 
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.
55
+ 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.page`) from the same file.
51
56
 
52
57
  **Edit while it runs.** Save `duto.yml` and the change applies right away, without restarting: new tunnels open, removed ones close, and a new `port`, `host`, `headers`, `auth` or `allow` counts from the next request, on the same URL. Visitors signed in with a password stay signed in while their user is still in `auth`. A save with a mistake changes nothing: the tunnels keep the last valid config and the screen shows the error with its line until you save a fixed file. Tunnels opened with `n` on the screen are left alone. Saving `.env` or `.env.local` applies the file again with the new values.
53
58
 
54
59
  ### Host and headers
55
60
 
56
- 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:
61
+ By default, your local service gets the request as it arrived from the internet: `Host: web-<user>.duto.page`, `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:
57
62
 
58
63
  ```yaml
59
64
  tunnels:
@@ -79,7 +84,7 @@ tunnels:
79
84
  **What `host:` does:**
80
85
 
81
86
  - Your service receives the Host you set, and `X-Forwarded-Host` is not sent. To send it anyway, put it in `headers:`.
82
- - 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.
87
+ - Redirects come back with the public address. A `Location: http://admin.myapp.test:8081/login` reaches the browser as `https://admin-<user>.duto.page/login`. This also applies inside encoded parameters of the `Location`. To turn it off, set `rewrite_redirects: false` on the tunnel.
83
88
  - Links in response bodies (HTML, JSON) are not rewritten. URLs your app builds from the Host only open on your machine.
84
89
  - On the command line: `duto 8081 --host admin.myapp.test`, and `--no-rewrite-redirects` to turn the swap off.
85
90
 
@@ -91,7 +96,7 @@ Ways out, from simplest to most work:
91
96
  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.
92
97
  3. Set the public callback URL in the app, if it lets you.
93
98
 
94
- 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.
99
+ In every case, register `https://<name>-<user>.duto.page/<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.
95
100
 
96
101
  **What you cannot set.** Some headers belong to Cloudflare, to Duto or to the HTTP connection itself. `duto.yml` refuses them, with the line and what to do:
97
102
 
@@ -153,12 +158,12 @@ People get in one of these ways:
153
158
  |---|---|
154
159
  | Person in a browser | Opens the URL, sees the "This tunnel is password protected" page and signs in with user and password |
155
160
  | Phone | Scans the QR code (key `m`, see below) and gets in without typing |
156
- | `curl`, Postman, scripts | `curl -u client:password https://backend-<user>.duto.dev/` (`Authorization: Basic`) |
161
+ | `curl`, Postman, scripts | `curl -u client:password https://backend-<user>.duto.page/` (`Authorization: Basic`) |
157
162
  | An API that already uses `Authorization: Bearer` | Send `X-Duto-Auth: client:password`; the `Bearer` goes through to the app untouched |
158
163
 
159
164
  Without credentials, a browser is sent to the sign-in page and other clients get `401`. After 10 wrong passwords from one address in 5 minutes, that address is locked out for the rest of the window (the sign-in page answers `429` with `Retry-After`, other clients keep getting `401`). After 100 wrong passwords from all addresses, every password attempt is locked out the same way. A browser that already signed in keeps working. 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.
160
165
 
161
- **Signing out and sessions.** In a browser, signing in sets a cookie that is valid for that tunnel only. To sign 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.
166
+ **Signing out and sessions.** In a browser, signing in sets a cookie that is valid for that tunnel only. To sign 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.page` address (the dashboard or another tunnel) asks for the password again; a link from outside (email, chat) or a typed URL gets straight in.
162
167
 
163
168
  **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.
164
169
 
@@ -231,7 +236,7 @@ The page has the list on the left and the details on the right, with the tabs **
231
236
 
232
237
  | Option | What it does |
233
238
  |---|---|
234
- | `--name <name>` | Fixed address `<name>-<user>.duto.dev` |
239
+ | `--name <name>` | Fixed address `<name>-<user>.duto.page` |
235
240
  | `--host <host>` | Host the local service receives instead of the public one |
236
241
  | `--no-rewrite-redirects` | With `--host`, keep redirects as the service sent them |
237
242
  | `--auth <user:password>` | Ask for a username and password to open the address (repeatable) |
package/dist/index.js CHANGED
@@ -36,7 +36,7 @@ import { join as join7, relative } from "path";
36
36
  import cac from "cac";
37
37
 
38
38
  // src/version.ts
39
- var CLI_VERSION = "1.8.0";
39
+ var CLI_VERSION = "1.8.1";
40
40
 
41
41
  // src/api.ts
42
42
  var DutoApiError = class extends Error {
@@ -561,7 +561,7 @@ async function browserLogin(o) {
561
561
  const start = await api.startLogin({
562
562
  deviceId: o.deviceId,
563
563
  deviceName: hostname(),
564
- cliVersion: "1.8.0",
564
+ cliVersion: "1.8.1",
565
565
  nodeVersion: process.version,
566
566
  os: process.platform,
567
567
  arch: process.arch
@@ -1729,7 +1729,7 @@ function redirectIssues(r, ctx) {
1729
1729
  {
1730
1730
  level: "warn",
1731
1731
  title: "Redirect drops to http",
1732
- detail: `Location is ${location}. duto.dev only serves https, so the browser takes an extra hop and loses POST bodies. Have the app trust X-Forwarded-Proto.`
1732
+ detail: `Location is ${location}. Duto tunnels only serve https, so the browser takes an extra hop and loses POST bodies. Have the app trust X-Forwarded-Proto.`
1733
1733
  }
1734
1734
  ];
1735
1735
  }
@@ -3650,7 +3650,7 @@ ${paint("signal", "Tip:")} create a duto.yml with your project's tunnels and jus
3650
3650
  return runSession([{ name, port, ...host ? { host } : {}, ...keepRedirects ? { rewriteRedirects: false } : {}, ...auth.length ? { auth } : {}, ...allow.length ? { allow } : {} }], "command line", flags);
3651
3651
  }
3652
3652
  var cli = cac("duto");
3653
- cli.command("[port]", "No port: open the tunnels in duto.yml. With a port: open a tunnel to it").option("--name <name>", "Fixed address name (e.g. web \u2192 https://web-<user>.duto.dev)").option("--host <host>", "Host the local service receives instead of the public one (e.g. myapp.test)").option("--no-rewrite-redirects", "With --host: do not swap the local host for the public one in redirects").option("--auth <user:password>", "Ask for a username and password to open the address (repeatable)").option("--allow <range>", "With --auth: IP, CIDR range or name (stripe, github) that gets in without a password (repeatable)").option("--no-ui", "No full screen: just a request log (useful in scripts)").option("--no-inspect", "Do not start the browser inspector (127.0.0.1:4141)").option("--ui", "Force full screen (off by default on Windows)").action((port, opts) => tunnel(port, opts));
3653
+ cli.command("[port]", "No port: open the tunnels in duto.yml. With a port: open a tunnel to it").option("--name <name>", "Fixed address name (e.g. web \u2192 https://web-<user>.duto.page)").option("--host <host>", "Host the local service receives instead of the public one (e.g. myapp.test)").option("--no-rewrite-redirects", "With --host: do not swap the local host for the public one in redirects").option("--auth <user:password>", "Ask for a username and password to open the address (repeatable)").option("--allow <range>", "With --auth: IP, CIDR range or name (stripe, github) that gets in without a password (repeatable)").option("--no-ui", "No full screen: just a request log (useful in scripts)").option("--no-inspect", "Do not start the browser inspector (127.0.0.1:4141)").option("--ui", "Force full screen (off by default on Windows)").action((port, opts) => tunnel(port, opts));
3654
3654
  cli.command("http <port>", "Same as duto <port> (for people coming from ngrok)").option("--name <name>", "Fixed address name").option("--host <host>", "Host the local service receives instead of the public one").option("--no-rewrite-redirects", "With --host: keep redirects as the service sent them").option("--auth <user:password>", "Ask for a username and password to open the address (repeatable)").option("--allow <range>", "With --auth: IP, CIDR range or name (stripe, github) that gets in without a password (repeatable)").option("--no-ui", "No full screen").option("--no-inspect", "Do not start the browser inspector (127.0.0.1:4141)").action((port, opts) => tunnel(port, opts));
3655
3655
  cli.command("login [token]", "Log in through the browser, or save an authtoken").action(async (token) => {
3656
3656
  try {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dutodev/cli",
3
- "version": "1.8.0",
4
- "description": "Expose a local port at https://<name>-<user>.duto.dev with one command.",
3
+ "version": "1.8.1",
4
+ "description": "Expose a local port at https://<name>-<user>.duto.page with one command.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "bin": {