@iamken/cloudtunnel 0.1.1 → 0.1.3

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
@@ -20,15 +20,17 @@ cloudtunnel login # once — paste a Cloudflare token; account + domain auto
20
20
  cloudtunnel 3000 # → https://brave-otter-1a2b.example.com is live ✨
21
21
  ```
22
22
 
23
+ > 💨 Prefer less typing? **`ctun`** is a built-in short alias — `ctun 3000`, `ctun ls`, `ctun down 1`.
24
+
23
25
  ---
24
26
 
25
27
  ## ✨ Why cloudtunnel
26
28
 
27
29
  - 🔗 **Your domains, real subdomains** — routes through native Cloudflare Tunnel to `*.your-domain.com`, not a shared third-party host.
28
- - ⚡ **One command** — `cloudtunnel 3000` creates the tunnel, DNS, and connector, then prints a live HTTPS URL. Zero required flags.
30
+ - ⚡ **One command** — `cloudtunnel 3000` creates the tunnel, DNS, and connector, then prints a live HTTPS URL. It even asks you which domain + subdomain to use.
31
+ - 🧭 **Just two states** — `up` brings a subdomain online; `down` (or Ctrl-C) releases it (deletes the tunnel + DNS). Re-running `up` always starts clean — no leftovers, no conflicts.
29
32
  - 🗂️ **Profiles** — save a whole project's services and bring them all up with `cloudtunnel run mb`.
30
33
  - 🌙 **Background mode** — `--detach` keeps connectors running after you close the terminal.
31
- - 🎯 **Zero-downtime** — `update` hot-reloads the port with no restart.
32
34
  - 🔒 **Secure by default** — token passed via env (never argv), stored `0600`, destructive ops are ownership-gated and re-verified.
33
35
 
34
36
  ---
@@ -37,20 +39,25 @@ cloudtunnel 3000 # → https://brave-otter-1a2b.example.com is live ✨
37
39
 
38
40
  ```bash
39
41
  cloudtunnel login # authenticate once
40
- cloudtunnel 3000 # random subdomain live URL (Ctrl-C stops, URL kept)
41
- cloudtunnel 3000 -s api # pick the subdomain: api.<your-domain>
42
- cloudtunnel 3000 -s api -d foo.io # pick subdomain + domain
42
+ cloudtunnel 3000 # asks for domain + subdomain, then goes live
43
+ cloudtunnel 3000 -s api # skip the prompts: api.<your-domain>
44
+ cloudtunnel 3000 -s api -d foo.io # subdomain + domain
43
45
  cloudtunnel 3000 --detach # run in the background
44
46
  ```
45
47
 
46
- A live tunnel looks like this:
48
+ Run `cloudtunnel 3000` with no flags and it guides you:
47
49
 
48
50
  ```
51
+ ┌ cloudtunnel
52
+ ◇ Choose a domain
53
+ │ ● example.com ○ foo.io
54
+ ◇ Subdomain
55
+ │ api (leave blank for a random name)
49
56
  ◇ Connected
50
57
 
51
58
  ◇ Live ─────────────────────────────────────────╮
52
59
  │ https://api.example.com → http://localhost:3000
53
- │ Ctrl-C stops the connector the subdomain is kept
60
+ │ Ctrl-C stops and releases this subdomain
54
61
  ╰─────────────────────────────────────────────────╯
55
62
  ```
56
63
 
@@ -60,30 +67,29 @@ A live tunnel looks like this:
60
67
 
61
68
  | Command | What it does |
62
69
  | --- | --- |
63
- | `cloudtunnel login` | Authenticate; auto-resolve account + default domain. `--status` to inspect. |
64
- | `cloudtunnel <port>` · `up` | Create + serve a subdomain. `-s/--subdomain`, `-d/--domain`, `--detach`, `--ephemeral`, `-f/--force`, `--proto`. |
65
- | `cloudtunnel ls` · `ps` | List subdomains — `ID · SUBDOMAIN · TARGET · STATE · PID`. `--all` scans the whole account. |
66
- | `cloudtunnel logs <id\|name>` | Show a connector's log. `-f` to follow, `-n` for line count. |
67
- | `cloudtunnel update <id\|name> --port <p>` | Repoint to a new port **zero downtime** (hot-reload). |
68
- | `cloudtunnel rm <id\|name>` · `remove` · `delete` | Delete a subdomain (stops connector, removes tunnel + DNS). `--dry-run`, `-f/--force`. |
69
- | `cloudtunnel down [id\|name] [--all]` | Stop connector(s); keep the tunnel + DNS. |
70
- | `cloudtunnel status <id\|name>` | Tunnel health + connector state. |
71
- | `cloudtunnel gc [--yes]` | Prune crash orphans. |
70
+ | `cloudtunnel login` | Authenticate; resolve account + list your domains. `--status` to inspect. |
71
+ | `cloudtunnel <port>` · `up` | Bring a subdomain online. `-s/--subdomain`, `-d/--domain`, `--detach`, `-f/--force`, `--proto`. |
72
+ | `cloudtunnel ls` · `ps` | List subdomains — `# · SUBDOMAIN · TARGET · STATE · PID`. `--all` scans the whole account. |
73
+ | `cloudtunnel down <target>` · `rm` · `stop` | Release a subdomain stop connector + delete tunnel + DNS. `--all`, `--dry-run`, `-f`. |
74
+ | `cloudtunnel logs <target>` | Show a connector's log. `-f` to follow, `-n` for line count. |
72
75
  | `cloudtunnel zones` | List the domains in your account. |
73
76
  | `cloudtunnel save <profile> <svc…>` | Save a group of services. `svc` = `name:port[:proto]`, or `--from-running`. |
74
77
  | `cloudtunnel run <profile> [--detach]` | Bring up every service in a profile at once. |
75
78
  | `cloudtunnel profiles [--rm <name>]` | List saved profiles (or delete one). |
76
79
 
77
- > Targets accept a **full hostname**, a **subdomain name**, or a **tunnel id prefix** (as shown in `ls`).
80
+ > A **`<target>`** is a `#` number, a subdomain name, a full hostname, or a tunnel-id prefix all shown in `ls`. `down` also accepts `rm` / `remove` / `delete` / `stop`.
78
81
 
79
82
  ```
80
83
  $ cloudtunnel ls
81
- ┌──────────────┬─────────────────────┬────────────────────────┬─────────┬───────┐
82
- ID │ SUBDOMAIN │ TARGET │ STATE │ PID │
83
- ├──────────────┼─────────────────────┼────────────────────────┼─────────┼───────┤
84
- 8f3a1c2b4d5e │ api.example.com │ http://localhost:3000 │ running │ 48213 │
85
- 1a2b3c4d5e6f │ web.example.com │ https://localhost:5173 │ stopped │ - │
86
- └──────────────┴─────────────────────┴────────────────────────┴─────────┴───────┘
84
+ ┌───┬─────────────────────┬────────────────────────┬───────┬───────┐
85
+ # │ SUBDOMAIN │ TARGET │ STATE │ PID │
86
+ ├───┼─────────────────────┼────────────────────────┼───────┼───────┤
87
+ 1 │ api.example.com │ http://localhost:3000 │ up │ 48213 │
88
+ 2 │ web.example.com │ https://localhost:5173 │ down │ - │
89
+ └───┴─────────────────────┴────────────────────────┴───────┴───────┘
90
+
91
+ $ cloudtunnel down 1 # release by number
92
+ $ cloudtunnel down --all # release everything
87
93
  ```
88
94
 
89
95
  ---
@@ -96,16 +102,16 @@ Expose a whole project's services with one command:
96
102
  cloudtunnel save mb api:3000 web:5173:https # define the group (or: save mb --from-running)
97
103
  cloudtunnel run mb --detach # backend + frontend live in the background
98
104
  cloudtunnel logs api -f # follow one service's log
99
- cloudtunnel down --all # stop them all
105
+ cloudtunnel down --all # release them all
100
106
  ```
101
107
 
102
108
  ---
103
109
 
104
- ## 🌙 Foreground vs background
110
+ ## 🧭 Two states: up & down
105
111
 
106
- By default `up` and `run` stay in the **foreground** the connector lives as long as the terminal is open, and <kbd>Ctrl-C</kbd> stops it (the subdomain is kept; delete it with `rm`).
112
+ There are only two states. **`up`** brings a subdomain online (creating the tunnel + DNS). **`down`** or pressing <kbd>Ctrl-C</kbd> in a foreground `up` — **releases** it: it stops the connector and deletes the tunnel + DNS on Cloudflare. Running `up` again recreates it cleanly (any leftover tunnel record for that name is cleaned up first, so you never hit conflicts).
107
113
 
108
- Add **`--detach`** to run in the **background** the CLI prints the URL(s) and exits, and the connector keeps running after you close the terminal. Stop them with `cloudtunnel down <id|name>` (or `--all`) and tail output with `cloudtunnel logs <id|name> -f`.
114
+ Add **`--detach`** to keep a connector running in the **background** after the CLI exits (and after you close the terminal). Release it later with `cloudtunnel down <target>` (or `--all`) and tail its output with `cloudtunnel logs <target> -f`.
109
115
 
110
116
  ---
111
117
 
@@ -138,9 +144,10 @@ Provide it via (highest precedence first): `CLOUDFLARE_API_TOKEN` env → `cloud
138
144
 
139
145
  | Symptom | Cause & fix |
140
146
  | --- | --- |
141
- | **HTTP 1016** / subdomain won't load | The connector isn't running. Start it `cloudtunnel <port> -s <name>` re-attaches — or check `cloudtunnel status <name>`. |
147
+ | **HTTP 1016** / subdomain won't load | The connector isn't running (`STATE = down` in `ls`). Bring it back up: `cloudtunnel <port> -s <name>`. |
148
+ | **`down` says "active connections"** | Handled automatically — cloudtunnel cleans up the connections and retries the delete. |
142
149
  | **"grey-clouded" error** | The zone couldn't proxy the record; cfargotunnel routing needs an orange-cloud (proxied) CNAME. |
143
- | **Multiple domains, no default** | Pass `-d <domain>`, or set one with `cloudtunnel login --zone <domain>`. |
150
+ | **A DNS record already occupies the name** | Pick another `-s`/`-d`, or pass `-f/--force` to replace a non-tunnel record. |
144
151
 
145
152
  ---
146
153
 
package/dist/index.js CHANGED
@@ -358,12 +358,21 @@ function upsertEntry(fqdn, patch) {
358
358
  const prev = reg[fqdn];
359
359
  reg[fqdn] = {
360
360
  createdAt: prev?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
361
+ index: prev?.index ?? nextIndex(reg),
361
362
  state: "provisioning",
362
363
  ...prev,
363
364
  ...patch
364
365
  };
365
366
  });
366
367
  }
368
+ function nextIndex(reg) {
369
+ const used = new Set(
370
+ Object.values(reg).map((e) => e.index).filter((n) => typeof n === "number")
371
+ );
372
+ let i = 1;
373
+ while (used.has(i)) i++;
374
+ return i;
375
+ }
367
376
  function patchEntry(fqdn, patch) {
368
377
  return mutateRegistry((reg) => {
369
378
  const prev = reg[fqdn];
@@ -476,6 +485,21 @@ async function getTunnel(cf, id) {
476
485
  async function deleteTunnel(cf, id) {
477
486
  await cfRequest(cf.token, "DELETE", `/accounts/${cf.accountId}/cfd_tunnel/${id}`);
478
487
  }
488
+ async function cleanupConnections(cf, id) {
489
+ await cfRequest(cf.token, "DELETE", `/accounts/${cf.accountId}/cfd_tunnel/${id}/connections`);
490
+ }
491
+ async function deleteTunnelWithConnections(cf, id) {
492
+ try {
493
+ await deleteTunnel(cf, id);
494
+ } catch (err) {
495
+ if (err instanceof CliError && /active connections/i.test(err.message)) {
496
+ await cleanupConnections(cf, id);
497
+ await deleteTunnel(cf, id);
498
+ } else {
499
+ throw err;
500
+ }
501
+ }
502
+ }
479
503
  async function getTunnelToken(cf, id) {
480
504
  return (await cfRequest(cf.token, "GET", `/accounts/${cf.accountId}/cfd_tunnel/${id}/token`)).result;
481
505
  }
@@ -590,21 +614,13 @@ async function createTunnelSubdomain(cf, opts) {
590
614
  const zone = await resolveZone(cf.token, host.zone);
591
615
  const existing = await findCname(cf.token, zone.id, host.hostname);
592
616
  if (existing) {
593
- if (isManagedDns(existing) && !opts.force) {
594
- const tunnelId2 = tunnelIdFromCname(existing.content);
595
- const token = await getTunnelToken(cf, tunnelId2);
596
- await putIngress(cf, tunnelId2, buildIngress({ hostname: host.hostname, port: opts.port, proto: opts.proto }));
597
- await recordRunning(host, zone.id, tunnelId2, existing.id, opts);
598
- say.dim(`Re-attaching to existing tunnel for ${host.hostname}.`);
599
- return { host, tunnelId: tunnelId2, token, adopted: true };
600
- }
601
- if (!opts.force) {
602
- throw new CliError(`${host.hostname} is already taken by a record not managed by cloudtunnel.`, {
603
- hint: "pick another --subdomain/--hostname, or pass -f/--force to take it over"
617
+ const isTunnelRecord = existing.content.endsWith(".cfargotunnel.com");
618
+ if (!isTunnelRecord && !opts.force) {
619
+ throw new CliError(`${host.hostname} is taken by a non-tunnel DNS record.`, {
620
+ hint: "pick another --subdomain/--hostname, or pass -f/--force to replace it"
604
621
  });
605
622
  }
606
623
  await releaseHostname(cf, zone.id, existing);
607
- say.dim(`Released ${host.hostname} (--force) \u2014 recreating.`);
608
624
  }
609
625
  await upsertEntry(host.hostname, {
610
626
  subdomain: host.subdomain,
@@ -625,7 +641,7 @@ async function createTunnelSubdomain(cf, opts) {
625
641
  const record = await createCname(cf.token, zone.id, host.hostname, tunnelId);
626
642
  dnsRecordId = record.id;
627
643
  await recordRunning(host, zone.id, tunnelId, dnsRecordId, opts);
628
- return { host, tunnelId, token, adopted: false };
644
+ return { host, tunnelId, token };
629
645
  } catch (err) {
630
646
  const clean = await rollback(cf, zone.id, tunnelId, dnsRecordId, host.hostname);
631
647
  if (clean) await removeEntry(host.hostname);
@@ -651,7 +667,7 @@ async function releaseHostname(cf, zoneId, record) {
651
667
  const oldTunnelId = tunnelIdFromCname(record.content);
652
668
  try {
653
669
  const tunnel = await getTunnel(cf, oldTunnelId);
654
- if (isManagedTunnel(tunnel)) await deleteTunnel(cf, oldTunnelId);
670
+ if (isManagedTunnel(tunnel)) await deleteTunnelWithConnections(cf, oldTunnelId);
655
671
  } catch {
656
672
  }
657
673
  }
@@ -664,7 +680,7 @@ async function rollback(cf, zoneId, tunnelId, dnsRecordId, hostname) {
664
680
  await deleteDnsRecord(cf.token, zoneId, dnsRecordId);
665
681
  } catch {
666
682
  clean = false;
667
- say.warn(`Left a DNS record behind for ${hostname} (${dnsRecordId}) \u2014 run \`cloudtunnel rm --force ${hostname}\`.`);
683
+ say.warn(`Left a DNS record behind for ${hostname} (${dnsRecordId}).`);
668
684
  }
669
685
  }
670
686
  if (tunnelId) {
@@ -672,7 +688,7 @@ async function rollback(cf, zoneId, tunnelId, dnsRecordId, hostname) {
672
688
  await deleteTunnel(cf, tunnelId);
673
689
  } catch {
674
690
  clean = false;
675
- say.warn(`Left tunnel ${tunnelId} behind \u2014 run \`cloudtunnel gc\`.`);
691
+ say.warn(`Left tunnel ${tunnelId} behind \u2014 remove it with \`cloudtunnel down ${hostname}\`.`);
676
692
  }
677
693
  }
678
694
  return clean;
@@ -685,6 +701,10 @@ var zoneFromFqdn = (fqdn) => fqdn.slice(fqdn.indexOf(".") + 1);
685
701
  function resolveTarget(target) {
686
702
  if (target.includes(".")) return { fqdn: target, entry: getEntry(target) };
687
703
  const entries = listEntries();
704
+ if (/^\d+$/.test(target)) {
705
+ const byIndex = entries.find((e) => e.index === Number(target));
706
+ if (byIndex) return { fqdn: `${byIndex.subdomain}.${byIndex.zone}`, entry: byIndex };
707
+ }
688
708
  const byId = entries.filter((e) => e.tunnelId?.startsWith(target));
689
709
  const matches = byId.length > 0 ? byId : entries.filter((e) => e.subdomain === target);
690
710
  if (matches.length > 1) {
@@ -694,23 +714,23 @@ function resolveTarget(target) {
694
714
  }
695
715
  const entry = matches[0];
696
716
  if (!entry) {
697
- throw new CliError(`No tracked subdomain matching "${target}".`, { hint: "pass a hostname, name, or id (see `cloudtunnel ls`)" });
717
+ throw new CliError(`No tracked subdomain matching "${target}".`, { hint: "see `cloudtunnel ls` for the #, name, or id" });
698
718
  }
699
719
  return { fqdn: `${entry.subdomain}.${entry.zone}`, entry };
700
720
  }
701
721
  async function removeTunnelSubdomain(cf, target, opts = {}) {
702
722
  const { fqdn, entry } = resolveTarget(target);
703
723
  if (!entry && !opts.force) {
704
- throw new CliError(`${fqdn} is not managed by cloudtunnel.`, { hint: "pass --force to delete it anyway" });
724
+ throw new CliError(`${fqdn} is not managed by cloudtunnel.`, { hint: "pass --force to release it anyway" });
705
725
  }
706
726
  const zoneId = entry?.zoneId ?? (await resolveZone(cf.token, zoneFromFqdn(fqdn))).id;
707
727
  const record = await findCname(cf.token, zoneId, fqdn);
708
728
  if (record && !isManagedDns(record) && !opts.force) {
709
- throw new CliError(`${fqdn} points to a record not managed by cloudtunnel.`, { hint: "pass --force to delete it" });
729
+ throw new CliError(`${fqdn} points to a record not managed by cloudtunnel.`, { hint: "pass --force to release it" });
710
730
  }
711
731
  const tunnelId = record ? tunnelIdFromCname2(record.content) : entry?.tunnelId;
712
732
  if (opts.dryRun) {
713
- say.info(`Would delete: tunnel ${tunnelId ?? "(none)"}${record && !opts.keepDns ? `, DNS ${record.id}` : ""}`);
733
+ say.info(`Would release: tunnel ${tunnelId ?? "(none)"}${record ? `, DNS ${record.id}` : ""}`);
714
734
  return;
715
735
  }
716
736
  if (entry) await stopConnector(entry);
@@ -726,13 +746,13 @@ async function removeTunnelSubdomain(cf, target, opts = {}) {
726
746
  }
727
747
  if (tunnel) {
728
748
  try {
729
- await deleteTunnel(cf, tunnelId);
749
+ await deleteTunnelWithConnections(cf, tunnelId);
730
750
  } catch (err) {
731
751
  if (!isNotFound(err)) throw err;
732
752
  }
733
753
  }
734
754
  }
735
- if (record && !opts.keepDns) {
755
+ if (record) {
736
756
  try {
737
757
  await deleteDnsRecord(cf.token, zoneId, record.id);
738
758
  } catch (err) {
@@ -740,27 +760,22 @@ async function removeTunnelSubdomain(cf, target, opts = {}) {
740
760
  }
741
761
  }
742
762
  await removeEntry(fqdn);
743
- say.ok(`Removed ${fqdn}`);
744
- }
745
- async function updateIngress(cf, target, port, proto) {
746
- const { fqdn, entry } = resolveTarget(target);
747
- if (!entry?.tunnelId) throw new CliError(`No tracked tunnel for ${fqdn}.`);
748
- const nextProto = proto ?? entry.proto;
749
- await putIngress(cf, entry.tunnelId, buildIngress({ hostname: fqdn, port, proto: nextProto }));
750
- await patchEntry(fqdn, { port, proto: nextProto });
751
- say.ok(`${fqdn} now points to ${nextProto}://localhost:${port} (no restart needed)`);
763
+ if (!opts.quiet) say.ok(`Released ${fqdn}`);
752
764
  }
753
765
  async function listAll(cf, opts = {}) {
754
766
  const entries = await reconcile();
755
767
  const tunnels = new Map((await listTunnels(cf)).map((t) => [t.id, t]));
756
- const rows = entries.map((e) => ({
757
- id: e.tunnelId ? e.tunnelId.slice(0, 12) : "-",
758
- hostname: `${e.subdomain}.${e.zone}`,
759
- port: `${e.proto}://localhost:${e.port}`,
760
- state: e.tunnelId && !tunnels.has(e.tunnelId) ? "dangling" : e.state,
761
- pid: e.state === "running" && e.pid ? String(e.pid) : "-",
762
- managed: true
763
- }));
768
+ const rows = entries.map((e) => {
769
+ const gone = e.tunnelId ? !tunnels.has(e.tunnelId) : false;
770
+ return {
771
+ num: e.index ? String(e.index) : "-",
772
+ hostname: `${e.subdomain}.${e.zone}`,
773
+ port: `${e.proto}://localhost:${e.port}`,
774
+ state: !gone && e.state === "running" ? "up" : "down",
775
+ pid: e.state === "running" && e.pid ? String(e.pid) : "-",
776
+ managed: true
777
+ };
778
+ });
764
779
  if (opts.all) {
765
780
  const { listCargoCnames } = await import("./dns-PAPFSYFP.js");
766
781
  const { listZones: listZones3 } = await import("./zones-YNGQYXAF.js");
@@ -768,7 +783,7 @@ async function listAll(cf, opts = {}) {
768
783
  for (const zone of await listZones3(cf.token)) {
769
784
  for (const rec of await listCargoCnames(cf.token, zone.id)) {
770
785
  if (!tracked.has(rec.name)) {
771
- rows.push({ id: tunnelIdFromCname2(rec.content).slice(0, 12), hostname: rec.name, port: "-", state: "unmanaged", pid: "-", managed: false });
786
+ rows.push({ num: "-", hostname: rec.name, port: "-", state: "unmanaged", pid: "-", managed: false });
772
787
  }
773
788
  }
774
789
  }
@@ -784,19 +799,27 @@ function parsePort(port) {
784
799
  }
785
800
  return n;
786
801
  }
787
- async function resolveDomain(token, explicit, saved) {
802
+ async function resolveDomain(cf, opts, creds) {
803
+ if (opts.hostname) return void 0;
804
+ const explicit = opts.domain ?? opts.zone;
788
805
  if (explicit) return explicit;
789
- if (saved) return saved;
790
- const zones = await listZones(token);
806
+ const zones = await listZones(cf.token);
791
807
  if (zones.length === 0) throw new CliError("No domains found in this Cloudflare account.");
792
808
  if (zones.length === 1) return zones[0].name;
793
- if (!process.stdin.isTTY) {
794
- throw new CliError("Multiple domains in this account \u2014 pick one.", { hint: "pass -d <domain>, e.g. -d example.com" });
809
+ if (process.stdin.isTTY) return (await selectOne("Choose a domain", zones, (z) => z.name)).name;
810
+ if (creds.defaultZone) return creds.defaultZone;
811
+ throw new CliError("Multiple domains in this account \u2014 pick one.", { hint: "pass -d <domain>" });
812
+ }
813
+ async function resolveSubdomain(opts) {
814
+ const explicit = opts.subdomain ?? opts.name;
815
+ if (explicit || opts.hostname) return explicit;
816
+ if (!process.stdin.isTTY) return void 0;
817
+ const input = await clack2.text({ message: "Subdomain", placeholder: "leave blank for a random name" });
818
+ if (clack2.isCancel(input)) {
819
+ clack2.cancel("Cancelled.");
820
+ process.exit(130);
795
821
  }
796
- const chosen = await selectOne("Choose a domain", zones, (z) => z.name);
797
- saveConfig({ ...loadConfig(), defaultZone: chosen.name });
798
- say.dim(`Saved ${chosen.name} as your default domain (change it with \`cloudtunnel login --zone <domain>\`).`);
799
- return chosen.name;
822
+ return input.trim() || void 0;
800
823
  }
801
824
  function showLogTail(logFile) {
802
825
  try {
@@ -810,9 +833,9 @@ async function runUp(portArg, opts) {
810
833
  const creds = await ensureAuth();
811
834
  const cf = resolveCf();
812
835
  const bin = await ensureCloudflared();
813
- const subdomain = opts.subdomain ?? opts.name;
814
- const domain = opts.hostname ? void 0 : await resolveDomain(cf.token, opts.domain ?? opts.zone, creds.defaultZone);
815
836
  if (process.stdout.isTTY) clack2.intro("cloudtunnel");
837
+ const domain = await resolveDomain(cf, opts, creds);
838
+ const subdomain = await resolveSubdomain(opts);
816
839
  const spin = clack2.spinner();
817
840
  let spinnerActive = true;
818
841
  const stopSpin = (msg) => {
@@ -854,14 +877,8 @@ async function runUp(portArg, opts) {
854
877
  controller.abort();
855
878
  stopSpin("Stopping\u2026");
856
879
  try {
857
- const entry = getEntry(fqdn);
858
- if (entry) await stopConnector(entry);
859
- if (opts.ephemeral) {
860
- await removeTunnelSubdomain(cf, fqdn, { force: true });
861
- clack2.outro(`Stopped \xB7 ${fqdn} deleted`);
862
- } else {
863
- clack2.outro(`Stopped \xB7 ${fqdn} kept \u2014 re-attach: cloudtunnel ${port} -s ${result.host.subdomain}`);
864
- }
880
+ await removeTunnelSubdomain(cf, fqdn, { force: true, quiet: true });
881
+ clack2.outro(`Stopped \xB7 ${fqdn} released`);
865
882
  } catch (err) {
866
883
  reportError(err);
867
884
  } finally {
@@ -889,14 +906,14 @@ async function runUp(portArg, opts) {
889
906
  if (health === "healthy") {
890
907
  stopSpin("Connected");
891
908
  clack2.note(`${formatRoute(fqdn, target)}
892
- ${dim("Ctrl-C stops the connector \u2014 the subdomain is kept")}`, "Live");
909
+ ${dim("Ctrl-C stops and releases this subdomain")}`, "Live");
893
910
  } else if (health === "provisioning") {
894
911
  stopSpin("Provisioning");
895
912
  say.warn(`${fqdn} is not healthy yet \u2014 it should be live shortly.`);
896
913
  }
897
914
  }
898
915
  function registerUp(program) {
899
- program.command("up").argument("<port>", "local port to expose (e.g. 3000)").description("Expose a local port at an HTTPS subdomain (also: `cloudtunnel <port>`)").option("-s, --subdomain <name>", "subdomain label (default: a friendly random slug)").option("-d, --domain <domain>", "domain to create the subdomain under (default: your default; picks interactively if unset)").option("--name <name>", "alias of --subdomain").option("--zone <domain>", "alias of --domain").option("--hostname <fqdn>", "full hostname override (instead of --subdomain + --domain)").option("--detach", "run the connector in the background").option("--ephemeral", "delete the tunnel + DNS on exit (default keeps them)").option("-f, --force", "take over a subdomain already occupied by another record").option("--proto <proto>", "local service protocol: http | https", "http").action((port, opts) => runUp(port, opts));
916
+ program.command("up").argument("<port>", "local port to expose (e.g. 3000)").description("Expose a local port at an HTTPS subdomain (also: `cloudtunnel <port>`)").option("-s, --subdomain <name>", "subdomain label (prompted, or random if left blank)").option("-d, --domain <domain>", "domain to create the subdomain under (prompted from a list if unset)").option("--name <name>", "alias of --subdomain").option("--zone <domain>", "alias of --domain").option("--hostname <fqdn>", "full hostname override (instead of --subdomain + --domain)").option("--detach", "run the connector in the background").option("-f, --force", "replace a non-tunnel DNS record occupying the hostname").option("--proto <proto>", "local service protocol: http | https", "http").action((port, opts) => runUp(port, opts));
900
917
  }
901
918
 
902
919
  // src/commands/ls.ts
@@ -910,102 +927,34 @@ function registerLs(program) {
910
927
  return;
911
928
  }
912
929
  printTable(
913
- ["ID", "SUBDOMAIN", "TARGET", "STATE", "PID"],
914
- rows.map((r) => [r.id, r.hostname, r.port, r.state, r.pid])
930
+ ["#", "SUBDOMAIN", "TARGET", "STATE", "PID"],
931
+ rows.map((r) => [r.num, r.hostname, r.port, r.state, r.pid])
915
932
  );
916
933
  });
917
934
  }
918
935
 
919
- // src/commands/rm.ts
920
- function registerRm(program) {
921
- program.command("rm").aliases(["remove", "delete"]).argument("<target>", "subdomain name, hostname, or id to delete").description("Delete a tunnel subdomain (stops connector, removes tunnel + DNS)").option("--force", "allow deleting a resource not created by cloudtunnel").option("--dry-run", "show what would be deleted without deleting").option("--keep-dns", "delete the tunnel but leave the DNS record").action(async (target, opts) => {
922
- await ensureAuth();
923
- const cf = resolveCf();
924
- await removeTunnelSubdomain(cf, target, opts);
925
- });
926
- }
927
-
928
- // src/commands/update.ts
929
- function registerUpdate(program) {
930
- program.command("update").argument("<name>", "subdomain name or full hostname to update").description("Change the local port/protocol a subdomain points to (zero-downtime)").option("--port <port>", "new local port").option("--proto <proto>", "new local protocol: http | https").action(async (name, opts) => {
931
- if (!opts.port) throw new CliError("--port is required", { hint: "e.g. `cloudtunnel update myapp --port 8080`" });
932
- const port = Number(opts.port);
933
- if (!Number.isInteger(port) || port < 1 || port > 65535) throw new CliError(`Invalid port: ${opts.port}`);
934
- await ensureAuth();
935
- const cf = resolveCf();
936
- await updateIngress(cf, name, port, opts.proto);
937
- });
938
- }
939
-
940
- // src/commands/status.ts
941
- function registerStatus(program) {
942
- program.command("status").argument("<name>", "subdomain name or full hostname").description("Show tunnel health and connector state for a subdomain").action(async (name) => {
943
- await ensureAuth();
944
- const cf = resolveCf();
945
- const { fqdn, entry } = resolveTarget(name);
946
- if (!entry?.tunnelId) throw new CliError(`No tracked tunnel for ${fqdn}.`);
947
- const connections = await getConnections(cf, entry.tunnelId);
948
- const connectorAlive = await isOurConnector(entry);
949
- say.info(`Host: https://${fqdn}`);
950
- say.info(`Tunnel: ${entry.tunnelId} \u2014 ${connections.length} edge connection(s)`);
951
- say.info(`Connector: ${connectorAlive ? `running (pid ${entry.pid})` : "stopped"}`);
952
- say.info(`Target: ${entry.proto}://localhost:${entry.port}`);
953
- });
954
- }
955
-
956
936
  // src/commands/down.ts
957
- async function stopEntry(entry) {
958
- const stopped = await stopConnector(entry);
959
- await mutateRegistry((reg) => {
960
- const e = reg[`${entry.subdomain}.${entry.zone}`];
961
- if (e) {
962
- e.state = "stopped";
963
- delete e.pid;
964
- }
965
- });
966
- return stopped;
967
- }
968
937
  function registerDown(program) {
969
- program.command("down").argument("[name]", "subdomain name / hostname / id to stop (omit with --all)").description("Stop a running connector, leaving the tunnel + DNS intact").option("--all", "stop all running connectors").action(async (name, opts) => {
970
- if (opts.all) {
971
- const running = listEntries().filter((e) => e.pid);
972
- let stopped = 0;
973
- for (const entry2 of running) if (await stopEntry(entry2)) stopped++;
974
- say.ok(`Stopped ${stopped} connector(s).`);
975
- return;
976
- }
977
- if (!name) throw new CliError("Pass a subdomain name or --all.");
978
- const { fqdn, entry } = resolveTarget(name);
979
- if (!entry) throw new CliError(`No tracked subdomain for ${fqdn}.`);
980
- await stopEntry(entry);
981
- say.ok(`Stopped ${fqdn}.`);
982
- });
983
- }
984
-
985
- // src/commands/gc.ts
986
- function registerGc(program) {
987
- program.command("gc").description("Prune crash orphans (provisioning/orphaned entries) after confirmation").option("--yes", "skip the confirmation prompt").action(async (opts) => {
938
+ program.command("down").aliases(["rm", "remove", "delete", "stop"]).argument("[target]", "subdomain name / hostname / id / # to release (omit with --all)").description("Stop and release a subdomain \u2014 removes the tunnel + DNS on Cloudflare").option("--all", "release every tracked subdomain").option("-f, --force", "release even a resource not created by cloudtunnel").option("--dry-run", "show what would be released without doing it").action(async (target, opts) => {
988
939
  await ensureAuth();
989
940
  const cf = resolveCf();
990
- await reconcile();
991
- const orphans = listEntries().filter((e) => e.state === "provisioning" || e.state === "orphaned");
992
- if (orphans.length === 0) {
993
- say.info("Nothing to clean up.");
994
- return;
995
- }
996
- say.info(`Found ${orphans.length} orphaned entr${orphans.length === 1 ? "y" : "ies"}:`);
997
- for (const o of orphans) say.dim(` ${o.subdomain}.${o.zone} (${o.state})`);
998
- if (!opts.yes) {
999
- say.warn("Re-run with --yes to delete these tunnels/records.");
1000
- return;
1001
- }
1002
- for (const o of orphans) {
1003
- try {
1004
- await removeTunnelSubdomain(cf, `${o.subdomain}.${o.zone}`, { force: true });
1005
- } catch {
1006
- say.warn(`Could not fully clean ${o.subdomain}.${o.zone} \u2014 check the dashboard.`);
941
+ if (opts.all) {
942
+ const entries = listEntries();
943
+ if (entries.length === 0) {
944
+ say.info("Nothing to release.");
945
+ return;
946
+ }
947
+ for (const e of entries) {
948
+ try {
949
+ await removeTunnelSubdomain(cf, `${e.subdomain}.${e.zone}`, { force: opts.force, dryRun: opts.dryRun });
950
+ } catch (err) {
951
+ say.warn(`Could not release ${e.subdomain}.${e.zone}: ${err.message}`);
952
+ }
1007
953
  }
954
+ return;
1008
955
  }
956
+ if (!target) throw new CliError("Pass a subdomain (name / id / #) or --all.");
957
+ await removeTunnelSubdomain(cf, target, { force: opts.force, dryRun: opts.dryRun });
1009
958
  });
1010
959
  }
1011
960
 
@@ -1122,7 +1071,7 @@ async function runProfile(name, opts) {
1122
1071
  token: result.token,
1123
1072
  detach: !!opts.detach,
1124
1073
  logFile,
1125
- onExit: opts.detach ? void 0 : () => say.warn(`Connector for ${fqdn} exited \u2014 check \`cloudtunnel status ${result.host.subdomain}\`.`)
1074
+ onExit: opts.detach ? void 0 : () => say.warn(`Connector for ${fqdn} exited.`)
1126
1075
  });
1127
1076
  await patchEntry(fqdn, { pid: conn.pid, bootId: currentBootId(), logFile });
1128
1077
  started.push({ fqdn, subdomain: result.host.subdomain, tunnelId: result.tunnelId, target: `${svc.proto}://localhost:${svc.port}`, pid: conn.pid });
@@ -1140,17 +1089,19 @@ async function runProfile(name, opts) {
1140
1089
  spin.stop(`${started.length} service(s) started`);
1141
1090
  const lines = started.map((s, i) => `${formatRoute(s.fqdn, s.target)}${healths[i] === "healthy" ? "" : dim(` (${healths[i]})`)}`);
1142
1091
  clack3.note(lines.join("\n"), `profile "${name}" \u2014 ${live}/${started.length} live`);
1143
- say.dim("Ctrl-C stops all connectors (subdomains are kept).");
1092
+ say.dim("Ctrl-C stops and releases all of them.");
1144
1093
  let tornDown = false;
1145
1094
  const teardownAll = async (code) => {
1146
1095
  if (tornDown) return;
1147
1096
  tornDown = true;
1148
1097
  try {
1149
1098
  for (const s of started) {
1150
- const entry = getEntry(s.fqdn);
1151
- if (entry) await stopConnector(entry);
1099
+ try {
1100
+ await removeTunnelSubdomain(cf, s.fqdn, { force: true, quiet: true });
1101
+ } catch {
1102
+ }
1152
1103
  }
1153
- if (process.stdout.isTTY) clack3.outro(`Stopped ${started.length} connector(s) \xB7 subdomains kept`);
1104
+ if (process.stdout.isTTY) clack3.outro(`Stopped \xB7 released ${started.length} subdomain(s)`);
1154
1105
  } catch (err) {
1155
1106
  reportError(err);
1156
1107
  } finally {
@@ -1222,7 +1173,7 @@ function follow(file, fromPos) {
1222
1173
  });
1223
1174
  }
1224
1175
  function registerLogs(program) {
1225
- program.command("logs").argument("<name>", "subdomain name or full hostname").description("Show the connector log for a subdomain (use -f to follow)").option("-f, --follow", "keep printing new log lines (like tail -f)").option("-n, --lines <n>", "number of lines to show", "50").action((name, opts) => {
1176
+ program.command("logs").argument("<target>", "subdomain name / hostname / id / #").description("Show the connector log for a subdomain (use -f to follow)").option("-f, --follow", "keep printing new log lines (like tail -f)").option("-n, --lines <n>", "number of lines to show", "50").action((name, opts) => {
1226
1177
  const { fqdn, entry } = resolveTarget(name);
1227
1178
  if (!entry?.logFile || !existsSync3(entry.logFile)) {
1228
1179
  throw new CliError(`No logs for ${fqdn} yet.`, { hint: "start it with `cloudtunnel up` or `cloudtunnel run`" });
@@ -1241,18 +1192,16 @@ var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
1241
1192
  "up",
1242
1193
  "ls",
1243
1194
  "ps",
1195
+ "down",
1244
1196
  "rm",
1245
1197
  "remove",
1246
1198
  "delete",
1247
- "update",
1248
- "status",
1249
- "down",
1250
- "gc",
1199
+ "stop",
1200
+ "logs",
1251
1201
  "zones",
1252
1202
  "save",
1253
1203
  "run",
1254
1204
  "profiles",
1255
- "logs",
1256
1205
  "help"
1257
1206
  ]);
1258
1207
  function applyBarePortAlias(argv) {
@@ -1279,11 +1228,7 @@ function buildProgram() {
1279
1228
  registerLogin,
1280
1229
  registerUp,
1281
1230
  registerLs,
1282
- registerRm,
1283
- registerUpdate,
1284
- registerStatus,
1285
1231
  registerDown,
1286
- registerGc,
1287
1232
  registerZones,
1288
1233
  registerSave,
1289
1234
  registerRun,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/commands/login.ts","../src/ui/output.ts","../src/config/token-url.ts","../src/config/resolve-identity.ts","../src/commands/up.ts","../src/config/ensure-auth.ts","../src/connector/binary.ts","../src/connector/process.ts","../src/connector/registry.ts","../src/cloudflare/tunnels.ts","../src/connector/health.ts","../src/core/orchestrator-create.ts","../src/core/ingress.ts","../src/core/slug.ts","../src/core/orchestrator-manage.ts","../src/commands/ls.ts","../src/commands/rm.ts","../src/commands/update.ts","../src/commands/status.ts","../src/commands/down.ts","../src/commands/gc.ts","../src/commands/zones.ts","../src/core/profiles.ts","../src/commands/save.ts","../src/commands/run.ts","../src/commands/profiles.ts","../src/commands/logs.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { createRequire } from \"node:module\";\nimport pc from \"picocolors\";\nimport { reportError } from \"./ui/errors.js\";\n\nimport { registerLogin } from \"./commands/login.js\";\nimport { registerUp } from \"./commands/up.js\";\nimport { registerLs } from \"./commands/ls.js\";\nimport { registerRm } from \"./commands/rm.js\";\nimport { registerUpdate } from \"./commands/update.js\";\nimport { registerStatus } from \"./commands/status.js\";\nimport { registerDown } from \"./commands/down.js\";\nimport { registerGc } from \"./commands/gc.js\";\nimport { registerZones } from \"./commands/zones.js\";\nimport { registerSave } from \"./commands/save.js\";\nimport { registerRun } from \"./commands/run.js\";\nimport { registerProfiles } from \"./commands/profiles.js\";\nimport { registerLogs } from \"./commands/logs.js\";\n\nconst require = createRequire(import.meta.url);\nconst pkg = require(\"../package.json\") as { version: string };\n\nconst KNOWN_COMMANDS = new Set([\n \"login\", \"up\", \"ls\", \"ps\", \"rm\", \"remove\", \"delete\", \"update\", \"status\",\n \"down\", \"gc\", \"zones\", \"save\", \"run\", \"profiles\", \"logs\", \"help\",\n]);\n\n/**\n * Bare-port sugar: `cloudtunnel 3000` ≡ `cloudtunnel up 3000`.\n * If the first non-flag arg is a port-like number and not a known command,\n * splice `up` in front. Keeps commander's own parsing untouched.\n */\nfunction applyBarePortAlias(argv: string[]): string[] {\n const args = argv.slice(2);\n const first = args[0];\n // Only the leading token: `cloudtunnel 3000 …` → `up 3000 …`. Avoids mistaking\n // a flag value (e.g. `--proto 3000`) for the port.\n if (first && /^\\d{1,5}$/.test(first) && !KNOWN_COMMANDS.has(first)) {\n args.unshift(\"up\");\n }\n return [argv[0]!, argv[1]!, ...args];\n}\n\nfunction buildProgram(): Command {\n const program = new Command();\n program\n .name(\"cloudtunnel\")\n .description(\"Manage Cloudflare Tunnels and subdomains account-wide, from your terminal.\")\n .version(pkg.version, \"-v, --version\")\n .showHelpAfterError();\n\n program.addHelpText(\n \"before\",\n [\n pc.bold(\"Quickstart:\"),\n ` ${pc.cyan(\"cloudtunnel login\")} once — paste a token (or set CLOUDFLARE_API_TOKEN)`,\n ` ${pc.cyan(\"cloudtunnel 3000\")} → your local :3000 goes live at an HTTPS URL`,\n \"\",\n ].join(\"\\n\"),\n );\n\n for (const register of [\n registerLogin, registerUp, registerLs, registerRm, registerUpdate,\n registerStatus, registerDown, registerGc, registerZones,\n registerSave, registerRun, registerProfiles, registerLogs,\n ]) {\n register(program);\n }\n return program;\n}\n\nasync function main(): Promise<void> {\n const program = buildProgram();\n try {\n await program.parseAsync(applyBarePortAlias(process.argv));\n } catch (err) {\n process.exitCode = reportError(err);\n }\n}\n\nvoid main();\n","import type { Command } from \"commander\";\nimport * as clack from \"@clack/prompts\";\nimport { CliError } from \"../ui/errors.js\";\nimport { redactToken, say, selectOne } from \"../ui/output.js\";\nimport { configFile } from \"../config/paths.js\";\nimport { loadConfig, saveConfig } from \"../config/store.js\";\nimport { REQUIRED_SCOPES, openBrowser, tokenCreateUrl } from \"../config/token-url.js\";\nimport { listAccounts, listZones } from \"../config/resolve-identity.js\";\n\ninterface LoginOptions {\n tokenStdin?: boolean;\n token?: string; // deprecated: leaks into shell history\n account?: string;\n zone?: string;\n status?: boolean;\n}\n\n/** Read the whole stdin pipe (for `--token-stdin`). */\nasync function readStdin(): Promise<string> {\n const chunks: Buffer[] = [];\n for await (const chunk of process.stdin) chunks.push(chunk as Buffer);\n return Buffer.concat(chunks).toString(\"utf8\").trim();\n}\n\n/** Acquire the API token: env (silent) → stdin → deprecated flag → masked prompt.\n * Env tokens are NOT persisted (the env stays the source of truth). */\nasync function acquireToken(opts: LoginOptions): Promise<{ token: string; fromEnv: boolean }> {\n const envToken = process.env.CLOUDFLARE_API_TOKEN;\n if (envToken) {\n say.dim(\"Using token from CLOUDFLARE_API_TOKEN.\");\n return { token: envToken, fromEnv: true };\n }\n if (opts.tokenStdin) return { token: await readStdin(), fromEnv: false };\n if (opts.token) {\n say.warn(\"--token puts the token in your shell history — prefer --token-stdin or the prompt. Rotate it if this is a shared host.\");\n return { token: opts.token, fromEnv: false };\n }\n if (!process.stdin.isTTY) {\n throw new CliError(\"No token provided and no interactive terminal.\", {\n hint: \"pipe it: `printf %s $TOKEN | cloudtunnel login --token-stdin`\",\n });\n }\n clack.note(REQUIRED_SCOPES.map((s) => `• ${s}`).join(\"\\n\"), \"Create a token with these scopes\");\n openBrowser(tokenCreateUrl());\n say.dim(`(opened ${tokenCreateUrl()})`);\n const token = await clack.password({ message: \"Paste your Cloudflare API token\", mask: \"•\" });\n if (clack.isCancel(token) || !token) {\n clack.cancel(\"Cancelled.\");\n throw new CliError(\"Cancelled.\", { exitCode: 130 });\n }\n return { token, fromEnv: false };\n}\n\nasync function runLoginFlow(opts: LoginOptions = {}): Promise<void> {\n if (process.stdout.isTTY) clack.intro(\"cloudtunnel · connect to Cloudflare\");\n const { token, fromEnv } = await acquireToken(opts);\n\n const spin = clack.spinner();\n spin.start(\"Verifying token…\");\n const [accounts, zones] = await Promise.all([listAccounts(token), listZones(token)]).catch((err: unknown) => {\n spin.stop(\"Token check failed\");\n throw err;\n });\n spin.stop(\"Token verified\");\n\n if (accounts.length === 0) throw new CliError(\"Token can't see any Cloudflare account.\");\n let account = opts.account ? accounts.find((a) => a.id === opts.account) : undefined;\n if (opts.account && !account) throw new CliError(`Account ${opts.account} not visible to this token.`);\n if (!account) {\n account = accounts.length === 1 || !process.stdin.isTTY\n ? accounts[0]!\n : await selectOne(\"Select an account\", accounts, (a) => `${a.name} (${a.id})`);\n }\n\n let defaultZone = opts.zone;\n if (!defaultZone) {\n if (zones.length === 1) defaultZone = zones[0]!.name;\n else if (zones.length > 1 && process.stdin.isTTY) {\n defaultZone = (await selectOne(\"Select a default domain\", zones, (z) => z.name)).name;\n }\n }\n\n saveConfig({ apiToken: fromEnv ? undefined : token, accountId: account.id, defaultZone });\n const summary = `Logged in as ${account.name}${defaultZone ? ` · default domain ${defaultZone}` : \"\"}`;\n if (process.stdout.isTTY) clack.outro(summary);\n else say.ok(summary);\n if (!defaultZone) say.dim(\"No default domain set — pass -d <domain> on `up`, or re-run `login --zone <domain>`.\");\n}\n\nfunction showStatus(): void {\n const config = loadConfig();\n const token = process.env.CLOUDFLARE_API_TOKEN ?? config.apiToken;\n if (!token) {\n say.warn(\"Not logged in. Run `cloudtunnel login`.\");\n return;\n }\n const source = process.env.CLOUDFLARE_API_TOKEN ? \"env\" : \"config\";\n say.info(`Token: ${redactToken(token)} (${source})`);\n say.info(`Account: ${config.accountId ?? \"(from env / unresolved)\"}`);\n say.info(`Domain: ${config.defaultZone ?? \"(none)\"}`);\n say.dim(`Config: ${configFile}`);\n}\n\nexport function registerLogin(program: Command): void {\n program\n .command(\"login\")\n .description(\"Authenticate with Cloudflare (paste a token once; account + domain auto-resolved)\")\n .option(\"--token-stdin\", \"read the API token from stdin (scriptable, avoids shell history)\")\n .option(\"--token <token>\", \"[discouraged] token as an argument (leaks into shell history)\")\n .option(\"--account <id>\", \"Cloudflare account id (auto-resolved when you have one account)\")\n .option(\"--zone <domain>\", \"default domain for new tunnels (auto-resolved when you have one)\")\n .option(\"--status\", \"show current identity (redacted) and exit\")\n .action(async (opts: LoginOptions) => {\n if (opts.status) return showStatus();\n await runLoginFlow(opts);\n });\n}\n\nexport { runLoginFlow };\n","import pc from \"picocolors\";\nimport Table from \"cli-table3\";\nimport { cancel, intro, isCancel, note, outro, select, spinner } from \"@clack/prompts\";\nimport { CliError } from \"./errors.js\";\n\n// Re-export the clack primitives used to build modern multi-step flows.\nexport { intro, note, outro, spinner };\n\n/** Redact a secret to `••••{last4}` so tokens never appear in output/logs. */\nexport function redactToken(token: string): string {\n if (!token) return \"\";\n const last4 = token.length > 4 ? token.slice(-4) : token;\n return `••••${last4}`;\n}\n\n// Lightweight one-off lines for non-flow commands (ls, zones, status, …).\nexport const say = {\n info: (msg: string) => console.log(msg),\n ok: (msg: string) => console.log(pc.green(`✓ ${msg}`)),\n warn: (msg: string) => console.warn(pc.yellow(`! ${msg}`)),\n dim: (msg: string) => console.log(pc.dim(msg)),\n step: (msg: string) => console.log(pc.cyan(`→ ${msg}`)),\n};\n\nexport const dim = (s: string): string => pc.dim(s);\n\n/** Format a live tunnel as `https://host → proto://localhost:port`. */\nexport function formatRoute(host: string, target: string): string {\n return `${pc.green(pc.bold(`https://${host}`))} ${pc.dim(\"→\")} ${pc.cyan(target)}`;\n}\n\n/** Render a simple table. `head` = column titles, `rows` = string cells. */\nexport function printTable(head: string[], rows: string[][]): void {\n const table = new Table({\n head: head.map((h) => pc.bold(h)),\n style: { head: [], border: [] },\n });\n for (const row of rows) table.push(row);\n console.log(table.toString());\n}\n\n/**\n * Modern arrow-key single-select (↑/↓ to move, Enter to choose). Callers must\n * guard non-TTY before calling. Ctrl-C cancels cleanly (exit 130).\n */\nexport async function selectOne<T>(\n message: string,\n items: T[],\n label: (item: T) => string,\n): Promise<T> {\n // Use the item index as the (primitive) option value to avoid clack's\n // conditional Option<T> type fighting the generic, then map back.\n const value = await select({\n message,\n options: items.map((item, i) => ({ value: String(i), label: label(item) })),\n });\n if (isCancel(value)) {\n cancel(\"Cancelled.\");\n throw new CliError(\"Cancelled.\", { exitCode: 130 });\n }\n return items[Number(value)]!;\n}\n","import { spawn } from \"node:child_process\";\n\n/** The exact scopes cloudtunnel needs. Printed so the user selects them when\n * minting a token — least-privilege, account-wide only where required. */\nexport const REQUIRED_SCOPES = [\n \"Account · Cloudflare Tunnel · Edit\",\n \"Account · Account Settings · Read\",\n \"Zone · DNS · Edit\",\n \"Zone · Zone · Read\",\n] as const;\n\n/** Cloudflare \"Create Custom Token\" page. `name` is pre-filled best-effort;\n * the user still selects the scopes above (dashboard pre-fill params are not a\n * versioned API, so we rely on the printed scope list, not URL params). */\nexport function tokenCreateUrl(): string {\n return \"https://dash.cloudflare.com/profile/api-tokens?name=cloudtunnel\";\n}\n\n/** Best-effort open a URL in the default browser. Never throws — if no opener\n * exists (headless/CI), the caller still prints the URL. */\nexport function openBrowser(url: string): void {\n const cmd =\n process.platform === \"darwin\" ? \"open\"\n : process.platform === \"win32\" ? \"cmd\"\n : \"xdg-open\";\n const args = process.platform === \"win32\" ? [\"/c\", \"start\", \"\", url] : [url];\n try {\n const child = spawn(cmd, args, { stdio: \"ignore\", detached: true });\n child.on(\"error\", () => {}); // swallow: opener may not exist\n child.unref();\n } catch {\n // ignore — printing the URL is the fallback\n }\n}\n","import { CliError } from \"../ui/errors.js\";\nimport { REQUIRED_SCOPES, tokenCreateUrl } from \"./token-url.js\";\n\nconst API_BASE = \"https://api.cloudflare.com/client/v4\";\n\nexport interface CfAccount { id: string; name: string }\nexport interface CfZone { id: string; name: string; account?: { id: string } }\n\n/**\n * Raw Cloudflare GET used only for login-time validation (the typed SDK client\n * is wired in Phase 3). Errors are sanitized: the token never appears in any\n * thrown message. A 403 is mapped to a missing-scope hint.\n */\nasync function cfGet<T>(path: string, token: string): Promise<T[]> {\n let res: Response;\n try {\n res = await fetch(`${API_BASE}${path}`, {\n headers: { Authorization: `Bearer ${token}`, \"Content-Type\": \"application/json\" },\n });\n } catch {\n throw new CliError(\"Could not reach the Cloudflare API (network error).\");\n }\n if (res.status === 401) {\n throw new CliError(\"Cloudflare rejected the token (invalid or expired).\", {\n hint: `mint a new token: ${tokenCreateUrl()}`,\n });\n }\n if (res.status === 403) {\n throw new CliError(`Token is missing a required scope for ${path}.`, {\n hint: `token needs: ${REQUIRED_SCOPES.join(\", \")}`,\n });\n }\n const body = (await res.json().catch(() => ({}))) as { success?: boolean; result?: T[] };\n if (!res.ok || !body.success) {\n throw new CliError(`Cloudflare API error (${res.status}) on ${path}.`);\n }\n return body.result ?? [];\n}\n\nexport function listAccounts(token: string): Promise<CfAccount[]> {\n return cfGet<CfAccount>(\"/accounts?per_page=50\", token);\n}\n\nexport function listZones(token: string): Promise<CfZone[]> {\n return cfGet<CfZone>(\"/zones?per_page=50\", token);\n}\n","import type { Command } from \"commander\";\nimport { join } from \"node:path\";\nimport { readFileSync } from \"node:fs\";\nimport * as clack from \"@clack/prompts\";\nimport { CliError, reportError } from \"../ui/errors.js\";\nimport { dim, formatRoute, say, selectOne } from \"../ui/output.js\";\nimport { ensureAuth } from \"../config/ensure-auth.js\";\nimport { loadConfig, saveConfig } from \"../config/store.js\";\nimport { resolveCf } from \"../cloudflare/client.js\";\nimport { listZones } from \"../cloudflare/zones.js\";\nimport { logDir } from \"../config/paths.js\";\nimport { ensureCloudflared } from \"../connector/binary.js\";\nimport { startConnector, stopConnector } from \"../connector/process.js\";\nimport { waitHealthy } from \"../connector/health.js\";\nimport { currentBootId, getEntry, patchEntry } from \"../connector/registry.js\";\nimport { createTunnelSubdomain } from \"../core/orchestrator-create.js\";\nimport { removeTunnelSubdomain } from \"../core/orchestrator-manage.js\";\n\ninterface UpOptions {\n subdomain?: string;\n domain?: string;\n name?: string; // alias of --subdomain\n zone?: string; // alias of --domain\n hostname?: string;\n detach?: boolean;\n ephemeral?: boolean;\n proto: \"http\" | \"https\";\n force?: boolean;\n}\n\nfunction parsePort(port: string): number {\n const n = Number(port);\n if (!Number.isInteger(n) || n < 1 || n > 65535) {\n throw new CliError(`Invalid port: ${port}`, { hint: \"use a number 1–65535, e.g. `cloudtunnel 3000`\" });\n }\n return n;\n}\n\n/** Resolve which domain (zone) to use: explicit `-d` → saved default → auto\n * (single zone) → interactive pick (multiple + TTY, remembered) → error (non-TTY). */\nexport async function resolveDomain(token: string, explicit?: string, saved?: string): Promise<string> {\n if (explicit) return explicit;\n if (saved) return saved;\n const zones = await listZones(token);\n if (zones.length === 0) throw new CliError(\"No domains found in this Cloudflare account.\");\n if (zones.length === 1) return zones[0]!.name;\n if (!process.stdin.isTTY) {\n throw new CliError(\"Multiple domains in this account — pick one.\", { hint: \"pass -d <domain>, e.g. -d example.com\" });\n }\n const chosen = await selectOne(\"Choose a domain\", zones, (z) => z.name);\n saveConfig({ ...loadConfig(), defaultZone: chosen.name });\n say.dim(`Saved ${chosen.name} as your default domain (change it with \\`cloudtunnel login --zone <domain>\\`).`);\n return chosen.name;\n}\n\n/** Print the last few lines of a connector logfile (shown when it crashes). */\nfunction showLogTail(logFile: string): void {\n try {\n const tail = readFileSync(logFile, \"utf8\").trim().split(\"\\n\").slice(-8).join(\"\\n\");\n if (tail) say.dim(tail);\n } catch {\n /* no log yet */\n }\n}\n\nasync function runUp(portArg: string, opts: UpOptions): Promise<void> {\n const port = parsePort(portArg);\n const creds = await ensureAuth();\n const cf = resolveCf();\n const bin = await ensureCloudflared(); // before any CF create: unsupported platform fails clean\n\n const subdomain = opts.subdomain ?? opts.name;\n const domain = opts.hostname ? undefined : await resolveDomain(cf.token, opts.domain ?? opts.zone, creds.defaultZone);\n\n if (process.stdout.isTTY) clack.intro(\"cloudtunnel\");\n const spin = clack.spinner();\n let spinnerActive = true;\n const stopSpin = (msg: string) => {\n if (spinnerActive) {\n spinnerActive = false;\n spin.stop(msg);\n }\n };\n\n spin.start(\"Creating tunnel…\");\n const result = await createTunnelSubdomain(cf, {\n port, proto: opts.proto, name: subdomain, zone: domain,\n hostname: opts.hostname, defaultZone: creds.defaultZone, force: opts.force,\n }).catch((err: unknown) => {\n stopSpin(\"Failed to create the tunnel\");\n throw err;\n });\n const fqdn = result.host.hostname;\n const logFile = join(logDir, `${result.host.subdomain}.log`);\n const target = `${opts.proto}://localhost:${port}`;\n\n if (opts.detach) {\n const started = startConnector({ bin, token: result.token, detach: true, logFile });\n await patchEntry(fqdn, { pid: started.pid, bootId: currentBootId(), logFile });\n stopSpin(\"Started in the background\");\n clack.note(formatRoute(fqdn, target), `pid ${started.pid}`);\n if (process.stdout.isTTY) clack.outro(`Stop it with: cloudtunnel down ${result.host.subdomain}`);\n return;\n }\n\n spin.message(\"Connecting to the Cloudflare edge…\");\n const controller = new AbortController();\n let tornDown = false;\n const teardown = async (exitCode: number): Promise<void> => {\n if (tornDown) return;\n tornDown = true;\n controller.abort();\n stopSpin(\"Stopping…\");\n try {\n const entry = getEntry(fqdn);\n if (entry) await stopConnector(entry);\n if (opts.ephemeral) {\n await removeTunnelSubdomain(cf, fqdn, { force: true });\n clack.outro(`Stopped · ${fqdn} deleted`);\n } else {\n clack.outro(`Stopped · ${fqdn} kept — re-attach: cloudtunnel ${port} -s ${result.host.subdomain}`);\n }\n } catch (err) {\n reportError(err); // never let teardown become an unhandled rejection\n } finally {\n process.exit(exitCode);\n }\n };\n\n const started = startConnector({\n bin, token: result.token, detach: false, logFile,\n onExit: (code) => {\n if (!tornDown) {\n stopSpin(\"cloudflared exited\");\n showLogTail(logFile);\n void teardown(code ?? 1);\n }\n },\n });\n await patchEntry(fqdn, { pid: started.pid, bootId: currentBootId(), logFile });\n for (const sig of [\"SIGINT\", \"SIGHUP\", \"SIGTERM\"] as const) {\n process.on(sig, () => void teardown(0));\n }\n\n const health = await waitHealthy(cf, result.tunnelId, { signal: controller.signal });\n if (health === \"healthy\") {\n stopSpin(\"Connected\");\n clack.note(`${formatRoute(fqdn, target)}\\n${dim(\"Ctrl-C stops the connector — the subdomain is kept\")}`, \"Live\");\n } else if (health === \"provisioning\") {\n stopSpin(\"Provisioning\");\n say.warn(`${fqdn} is not healthy yet — it should be live shortly.`);\n }\n // health === \"dead\" → onExit already handled teardown.\n}\n\nexport function registerUp(program: Command): void {\n program\n .command(\"up\")\n .argument(\"<port>\", \"local port to expose (e.g. 3000)\")\n .description(\"Expose a local port at an HTTPS subdomain (also: `cloudtunnel <port>`)\")\n .option(\"-s, --subdomain <name>\", \"subdomain label (default: a friendly random slug)\")\n .option(\"-d, --domain <domain>\", \"domain to create the subdomain under (default: your default; picks interactively if unset)\")\n .option(\"--name <name>\", \"alias of --subdomain\")\n .option(\"--zone <domain>\", \"alias of --domain\")\n .option(\"--hostname <fqdn>\", \"full hostname override (instead of --subdomain + --domain)\")\n .option(\"--detach\", \"run the connector in the background\")\n .option(\"--ephemeral\", \"delete the tunnel + DNS on exit (default keeps them)\")\n .option(\"-f, --force\", \"take over a subdomain already occupied by another record\")\n .option(\"--proto <proto>\", \"local service protocol: http | https\", \"http\")\n .action((port: string, opts: UpOptions) => runUp(port, opts));\n}\n","import { CliError } from \"../ui/errors.js\";\nimport { say } from \"../ui/output.js\";\nimport { getCredentials, type Credentials } from \"./store.js\";\nimport { runLoginFlow } from \"../commands/login.js\";\n\n/**\n * Single auth entry point for every command. Returns credentials if present;\n * on a fresh machine with a TTY it runs onboarding inline and continues, so\n * `cloudtunnel 3000` on a new box just works. Non-TTY (CI) → actionable error.\n */\nexport async function ensureAuth(): Promise<Credentials> {\n try {\n return getCredentials();\n } catch (err) {\n if (err instanceof CliError && process.stdin.isTTY) {\n say.info(\"Welcome to cloudtunnel — let's get you connected to Cloudflare first.\");\n await runLoginFlow();\n return getCredentials();\n }\n throw err;\n }\n}\n","import { execFileSync } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport { chmodSync, existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { CliError } from \"../ui/errors.js\";\nimport { say } from \"../ui/output.js\";\nimport { binDir, ensureDirs } from \"../config/paths.js\";\n\n// Pinned release for reproducible, checksum-verified auto-install. Bump both the\n// version and the checksums together (values from the release's sha256 sums).\nconst PINNED_VERSION = \"2025.1.0\";\nconst RELEASE_BASE = `https://github.com/cloudflare/cloudflared/releases/download/${PINNED_VERSION}`;\n\ninterface Asset { file: string; archive: boolean; sha256: string }\n\n// Fill sha256 from the pinned release before shipping auto-install for a target.\n// Empty string ⇒ fail closed (never run an unverified binary).\nconst ASSETS: Record<string, Asset | undefined> = {\n \"linux-x64\": { file: \"cloudflared-linux-amd64\", archive: false, sha256: \"\" },\n \"linux-arm64\": { file: \"cloudflared-linux-arm64\", archive: false, sha256: \"\" },\n \"darwin-x64\": { file: \"cloudflared-darwin-amd64.tgz\", archive: true, sha256: \"\" },\n \"darwin-arm64\": { file: \"cloudflared-darwin-arm64.tgz\", archive: true, sha256: \"\" },\n \"win32-x64\": { file: \"cloudflared-windows-amd64.exe\", archive: false, sha256: \"\" },\n};\n\nfunction binaryWorks(bin: string): boolean {\n try {\n execFileSync(bin, [\"--version\"], { stdio: \"ignore\" });\n return true;\n } catch {\n return false;\n }\n}\n\nfunction cachedPath(): string {\n return join(binDir, process.platform === \"win32\" ? \"cloudflared.exe\" : \"cloudflared\");\n}\n\n/** True on Alpine/musl, where cloudflared has no prebuilt binary. */\nfunction isMusl(): boolean {\n try {\n return process.platform === \"linux\" && readFileSync(\"/usr/bin/ldd\", \"utf8\").includes(\"musl\");\n } catch {\n return false;\n }\n}\n\n/**\n * Return a runnable `cloudflared` with zero user action: PATH → cached download\n * → verified auto-download. Fails closed (never runs an unverified binary).\n */\nexport async function ensureCloudflared(): Promise<string> {\n if (binaryWorks(\"cloudflared\")) return \"cloudflared\";\n const cached = cachedPath();\n if (existsSync(cached) && binaryWorks(cached)) return cached;\n return downloadCloudflared(cached);\n}\n\nasync function downloadCloudflared(dest: string): Promise<string> {\n if (isMusl()) {\n throw new CliError(\"cloudflared has no musl (Alpine) build.\", {\n hint: \"install it manually: https://github.com/cloudflare/cloudflared/releases\",\n });\n }\n const key = `${process.platform}-${process.arch}`;\n const asset = ASSETS[key];\n if (!asset || !asset.sha256) {\n throw new CliError(`Auto-install unavailable for ${key} (no pinned checksum).`, {\n hint: \"install cloudflared manually: https://github.com/cloudflare/cloudflared/releases\",\n });\n }\n\n say.step(`cloudflared not found — downloading v${PINNED_VERSION} (checksum-verified)…`);\n const res = await fetch(`${RELEASE_BASE}/${asset.file}`);\n if (!res.ok) throw new CliError(`Download failed (HTTP ${res.status}).`);\n const bytes = Buffer.from(await res.arrayBuffer());\n\n const digest = createHash(\"sha256\").update(bytes).digest(\"hex\");\n if (digest !== asset.sha256) {\n throw new CliError(\"cloudflared checksum mismatch — refusing to run the download.\", {\n hint: \"network tampering or an outdated pin; install manually instead\",\n });\n }\n\n ensureDirs();\n const binary = asset.archive ? extractTgz(bytes) : bytes;\n writeFileSync(dest, binary, { mode: 0o755 });\n chmodSync(dest, 0o755);\n if (!binaryWorks(dest)) throw new CliError(\"Downloaded cloudflared is not runnable.\");\n return dest;\n}\n\n/** Extract the single `cloudflared` entry from a .tgz (darwin assets). */\nfunction extractTgz(_bytes: Buffer): Buffer {\n // gunzip + untar of a single-file archive; implemented when a darwin\n // checksum is pinned (dormant until then — see ASSETS).\n throw new CliError(\"darwin .tgz extraction not yet wired.\", {\n hint: \"install cloudflared via `brew install cloudflared`\",\n });\n}\n","import { type ChildProcess, execFileSync, spawn } from \"node:child_process\";\nimport { openSync } from \"node:fs\";\nimport { CliError } from \"../ui/errors.js\";\nimport { isOurConnector, type RegistryEntry } from \"./registry.js\";\n\nexport interface StartOptions {\n bin: string;\n token: string;\n detach: boolean;\n logFile: string;\n /** Foreground only: fired when the connector exits for ANY reason (crash,\n * bad token, or a signal) so the caller can tear down / report. */\n onExit?: (code: number | null) => void;\n}\n\nexport interface StartedConnector {\n pid: number;\n child?: ChildProcess;\n}\n\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\n\n/**\n * Spawn `cloudflared tunnel run`. The token is passed via the TUNNEL_TOKEN env\n * var — NEVER as an argv arg (argv is world-readable via `ps`/proc). Output goes\n * to a 0600 logfile (both foreground and detached) so the CLI can render its own\n * clean status instead of cloudflared's raw logs.\n */\nexport function startConnector(opts: StartOptions): StartedConnector {\n const args = [\"tunnel\", \"run\"];\n const env = { ...process.env, TUNNEL_TOKEN: opts.token };\n const fd = openSync(opts.logFile, \"a\", 0o600);\n const child = spawn(opts.bin, args, { env, detached: opts.detach, stdio: [\"ignore\", fd, fd] });\n if (!child.pid) throw new CliError(\"Failed to start the cloudflared connector.\");\n\n if (opts.detach) {\n child.unref();\n return { pid: child.pid };\n }\n child.on(\"exit\", (code) => opts.onExit?.(code));\n child.on(\"error\", () => opts.onExit?.(1));\n return { pid: child.pid, child };\n}\n\n/**\n * Stop a connector by registry entry. Verifies the pid is still OUR cloudflared\n * (alive, same boot, right cmdline) BEFORE signalling, so a reused pid held by\n * an unrelated process is never killed. Returns true if a stop was issued.\n */\nexport async function stopConnector(entry: RegistryEntry): Promise<boolean> {\n if (!entry.pid || !(await isOurConnector(entry))) return false;\n const pid = entry.pid;\n\n if (process.platform === \"win32\") {\n try {\n execFileSync(\"taskkill\", [\"/pid\", String(pid), \"/T\", \"/F\"], { stdio: \"ignore\" });\n } catch {\n return false;\n }\n return true;\n }\n\n try {\n process.kill(pid, \"SIGTERM\");\n } catch {\n return false;\n }\n await sleep(3000);\n if (await isOurConnector(entry)) {\n try {\n process.kill(pid, \"SIGKILL\");\n } catch {\n // already gone\n }\n }\n return true;\n}\n","import { existsSync, readFileSync, renameSync, writeFileSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport os from \"node:os\";\nimport lockfile from \"proper-lockfile\";\nimport { ensureDirs, registryFile } from \"../config/paths.js\";\n\nexport type EntryState = \"provisioning\" | \"running\" | \"stopped\" | \"orphaned\";\n\nexport interface RegistryEntry {\n subdomain: string;\n zone: string;\n zoneId: string;\n tunnelId?: string;\n dnsRecordId?: string;\n port: number;\n proto: \"http\" | \"https\";\n pid?: number;\n bootId?: string;\n logFile?: string;\n createdAt: string;\n state: EntryState;\n}\n\ntype Registry = Record<string, RegistryEntry>;\n\n/** Stable per-boot id so a pid reused after a reboot is never mistaken for ours. */\nexport function currentBootId(): string {\n try {\n return readFileSync(\"/proc/sys/kernel/random/boot_id\", \"utf8\").trim();\n } catch {\n return `uptime-${Math.round(os.uptime())}-${os.hostname()}`;\n }\n}\n\nfunction readRegistry(): Registry {\n try {\n return JSON.parse(readFileSync(registryFile, \"utf8\")) as Registry;\n } catch {\n return {};\n }\n}\n\nfunction writeRegistry(reg: Registry): void {\n ensureDirs();\n const tmp = `${registryFile}.tmp`;\n writeFileSync(tmp, JSON.stringify(reg, null, 2), { mode: 0o600 });\n renameSync(tmp, registryFile); // atomic on the same filesystem\n}\n\n/** Lock-guarded read-modify-write (prevents lost updates across concurrent runs). */\nexport async function mutateRegistry<T>(fn: (reg: Registry) => T): Promise<T> {\n ensureDirs();\n if (!existsSync(registryFile)) writeFileSync(registryFile, \"{}\", { mode: 0o600 });\n const release = await lockfile.lock(registryFile, { retries: { retries: 10, minTimeout: 50 } });\n try {\n const reg = readRegistry();\n const result = fn(reg);\n writeRegistry(reg);\n return result;\n } finally {\n await release();\n }\n}\n\nexport function listEntries(): RegistryEntry[] {\n return Object.values(readRegistry());\n}\n\nexport function getEntry(fqdn: string): RegistryEntry | undefined {\n return readRegistry()[fqdn];\n}\n\nexport function upsertEntry(fqdn: string, patch: Partial<RegistryEntry> & Pick<RegistryEntry, \"subdomain\" | \"zone\" | \"zoneId\" | \"port\" | \"proto\">): Promise<void> {\n return mutateRegistry((reg) => {\n const prev = reg[fqdn];\n reg[fqdn] = {\n createdAt: prev?.createdAt ?? new Date().toISOString(),\n state: \"provisioning\",\n ...prev,\n ...patch,\n };\n });\n}\n\n/** Merge changed fields onto an existing entry under the lock (no stale\n * full-snapshot read outside the lock — avoids lost updates). No-op if absent. */\nexport function patchEntry(fqdn: string, patch: Partial<RegistryEntry>): Promise<void> {\n return mutateRegistry((reg) => {\n const prev = reg[fqdn];\n if (prev) reg[fqdn] = { ...prev, ...patch };\n });\n}\n\nexport function removeEntry(fqdn: string): Promise<void> {\n return mutateRegistry((reg) => {\n delete reg[fqdn];\n });\n}\n\nfunction pidAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\n/** Verify a pid is still OUR cloudflared: alive, same boot, and (Linux) its\n * cmdline is cloudflared — so we never signal a reused pid. */\nexport async function isOurConnector(entry: RegistryEntry): Promise<boolean> {\n if (!entry.pid || entry.bootId !== currentBootId()) return false;\n if (!pidAlive(entry.pid)) return false;\n if (process.platform === \"linux\") {\n try {\n const cmdline = await readFile(`/proc/${entry.pid}/cmdline`, \"utf8\");\n return cmdline.includes(\"cloudflared\");\n } catch {\n return false;\n }\n }\n return true; // non-Linux: bootId + liveness (best effort)\n}\n\n/** Mark entries whose connector is no longer alive as `stopped`. */\nexport async function reconcile(): Promise<RegistryEntry[]> {\n const entries = listEntries();\n for (const entry of entries) {\n if (entry.state === \"running\" && !(await isOurConnector(entry))) {\n const fqdn = `${entry.subdomain}.${entry.zone}`;\n await mutateRegistry((reg) => {\n const e = reg[fqdn];\n if (e) {\n e.state = \"stopped\";\n delete e.pid;\n }\n });\n }\n }\n return listEntries();\n}\n","import { cfPaginate, cfRequest, type Cf } from \"./client.js\";\nimport type { Connection, IngressRule, Tunnel } from \"./types.js\";\n\n/** Tunnels created by cloudtunnel carry this name prefix (ownership marker). */\nexport const MANAGED_TUNNEL_PREFIX = \"ct-\";\n\nexport function isManagedTunnel(tunnel: Tunnel): boolean {\n return tunnel.name.startsWith(MANAGED_TUNNEL_PREFIX);\n}\n\nexport async function createTunnel(cf: Cf, name: string): Promise<Tunnel> {\n const env = await cfRequest<Tunnel>(cf.token, \"POST\", `/accounts/${cf.accountId}/cfd_tunnel`, {\n name,\n config_src: \"cloudflare\",\n });\n return env.result;\n}\n\nexport function listTunnels(cf: Cf): Promise<Tunnel[]> {\n return cfPaginate<Tunnel>(cf.token, `/accounts/${cf.accountId}/cfd_tunnel?is_deleted=false`);\n}\n\nexport async function getTunnel(cf: Cf, id: string): Promise<Tunnel> {\n return (await cfRequest<Tunnel>(cf.token, \"GET\", `/accounts/${cf.accountId}/cfd_tunnel/${id}`)).result;\n}\n\nexport async function deleteTunnel(cf: Cf, id: string): Promise<void> {\n await cfRequest<unknown>(cf.token, \"DELETE\", `/accounts/${cf.accountId}/cfd_tunnel/${id}`);\n}\n\n/** The connector token (encodes tunnelId + secret) passed to `cloudflared`. */\nexport async function getTunnelToken(cf: Cf, id: string): Promise<string> {\n return (await cfRequest<string>(cf.token, \"GET\", `/accounts/${cf.accountId}/cfd_tunnel/${id}/token`)).result;\n}\n\n/** Full-replace ingress config (safe: one hostname + catch-all per tunnel). */\nexport async function putIngress(cf: Cf, id: string, ingress: IngressRule[]): Promise<void> {\n await cfRequest<unknown>(cf.token, \"PUT\", `/accounts/${cf.accountId}/cfd_tunnel/${id}/configurations`, {\n config: { ingress },\n });\n}\n\n/** Active connector instances (≥1 ⇒ tunnel is serving). */\nexport async function getConnections(cf: Cf, id: string): Promise<Connection[]> {\n const env = await cfRequest<Connection[]>(\n cf.token,\n \"GET\",\n `/accounts/${cf.accountId}/cfd_tunnel/${id}/connections`,\n );\n return env.result ?? [];\n}\n","import { getConnections } from \"../cloudflare/tunnels.js\";\nimport type { Cf } from \"../cloudflare/client.js\";\n\nexport type HealthResult = \"healthy\" | \"provisioning\" | \"dead\";\n\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\n\n/**\n * Poll the tunnel's connections until it's serving. `signal` is fired by the\n * caller when the connector process exits, so a dead connector returns `dead`\n * immediately instead of waiting out the timeout. `provisioning` is only\n * returned if the process is still alive at the deadline (never a false\n * \"healthy\"). Note: this measures connector↔edge, not local-origin, health.\n */\nexport async function waitHealthy(\n cf: Cf,\n tunnelId: string,\n opts: { signal?: AbortSignal; timeoutMs?: number } = {},\n): Promise<HealthResult> {\n const deadline = Date.now() + (opts.timeoutMs ?? 30_000);\n while (Date.now() < deadline) {\n if (opts.signal?.aborted) return \"dead\";\n try {\n const connections = await getConnections(cf, tunnelId);\n if (connections.length > 0) return \"healthy\";\n } catch {\n // transient API error — keep polling until the deadline\n }\n await sleep(2000);\n }\n return opts.signal?.aborted ? \"dead\" : \"provisioning\";\n}\n","import { randomInt } from \"node:crypto\";\nimport type { Cf } from \"../cloudflare/client.js\";\nimport { resolveZone } from \"../cloudflare/zones.js\";\nimport {\n MANAGED_TUNNEL_PREFIX,\n createTunnel,\n deleteTunnel,\n getTunnel,\n getTunnelToken,\n isManagedTunnel,\n putIngress,\n} from \"../cloudflare/tunnels.js\";\nimport { createCname, deleteDnsRecord, findCname, isManagedDns } from \"../cloudflare/dns.js\";\nimport type { DnsRecord } from \"../cloudflare/types.js\";\nimport { buildIngress } from \"./ingress.js\";\nimport { resolveHostSpec, type HostSpec } from \"./slug.js\";\nimport { currentBootId, patchEntry, removeEntry, upsertEntry } from \"../connector/registry.js\";\nimport { CliError } from \"../ui/errors.js\";\nimport { say } from \"../ui/output.js\";\n\nexport interface CreateOptions {\n port: number;\n proto: \"http\" | \"https\";\n name?: string;\n zone?: string;\n hostname?: string;\n defaultZone?: string;\n force?: boolean;\n}\n\nexport interface CreateResult {\n host: HostSpec;\n tunnelId: string;\n token: string;\n adopted: boolean;\n}\n\nconst tunnelIdFromCname = (content: string): string => content.replace(/\\.cfargotunnel\\.com\\.?$/, \"\");\n\n/**\n * Create (or adopt) a tunnel subdomain transactionally. A `provisioning`\n * registry entry is written BEFORE any Cloudflare resource, so a crash leaves a\n * tracked orphan (recoverable via `gc`). On failure, resources are unwound in\n * reverse; the original error is always surfaced.\n */\nexport async function createTunnelSubdomain(cf: Cf, opts: CreateOptions): Promise<CreateResult> {\n const host = resolveHostSpec(opts, opts.defaultZone);\n const zone = await resolveZone(cf.token, host.zone);\n\n const existing = await findCname(cf.token, zone.id, host.hostname);\n if (existing) {\n // Re-running our own subdomain (no --force) → adopt: reuse the tunnel, update the port.\n if (isManagedDns(existing) && !opts.force) {\n const tunnelId = tunnelIdFromCname(existing.content);\n const token = await getTunnelToken(cf, tunnelId);\n await putIngress(cf, tunnelId, buildIngress({ hostname: host.hostname, port: opts.port, proto: opts.proto }));\n await recordRunning(host, zone.id, tunnelId, existing.id, opts);\n say.dim(`Re-attaching to existing tunnel for ${host.hostname}.`);\n return { host, tunnelId, token, adopted: true };\n }\n // Occupied (someone else's record, or a --force reset) → require --force, then release it.\n if (!opts.force) {\n throw new CliError(`${host.hostname} is already taken by a record not managed by cloudtunnel.`, {\n hint: \"pick another --subdomain/--hostname, or pass -f/--force to take it over\",\n });\n }\n await releaseHostname(cf, zone.id, existing);\n say.dim(`Released ${host.hostname} (--force) — recreating.`);\n }\n\n // Track provisioning BEFORE creating anything irreversible.\n await upsertEntry(host.hostname, {\n subdomain: host.subdomain, zone: host.zone, zoneId: zone.id,\n port: opts.port, proto: opts.proto, state: \"provisioning\",\n });\n\n let tunnelId: string | undefined;\n let dnsRecordId: string | undefined;\n try {\n const suffix = randomInt(0x10000).toString(16).padStart(4, \"0\");\n const tunnel = await createTunnel(cf, `${MANAGED_TUNNEL_PREFIX}${host.subdomain}-${suffix}`);\n tunnelId = tunnel.id;\n const token = await getTunnelToken(cf, tunnelId);\n await putIngress(cf, tunnelId, buildIngress({ hostname: host.hostname, port: opts.port, proto: opts.proto }));\n const record = await createCname(cf.token, zone.id, host.hostname, tunnelId);\n dnsRecordId = record.id;\n await recordRunning(host, zone.id, tunnelId, dnsRecordId, opts);\n return { host, tunnelId, token, adopted: false };\n } catch (err) {\n const clean = await rollback(cf, zone.id, tunnelId, dnsRecordId, host.hostname);\n // Clean unwind ⇒ drop the provisioning entry; a failed unwind ⇒ mark it\n // `orphaned` so `ls`/`gc` flag it for manual cleanup.\n if (clean) await removeEntry(host.hostname);\n else await patchEntry(host.hostname, { state: \"orphaned\" });\n throw err;\n }\n}\n\nasync function recordRunning(host: HostSpec, zoneId: string, tunnelId: string, dnsRecordId: string, opts: CreateOptions): Promise<void> {\n await upsertEntry(host.hostname, {\n subdomain: host.subdomain, zone: host.zone, zoneId,\n tunnelId, dnsRecordId, port: opts.port, proto: opts.proto,\n bootId: currentBootId(), state: \"running\",\n });\n}\n\n/** Free an occupied hostname (for --force): delete its DNS record, and if it\n * pointed at a cloudtunnel-managed tunnel, delete that tunnel too. A foreign\n * tunnel is left alone — we only free the DNS name so our CNAME can be created. */\nasync function releaseHostname(cf: Cf, zoneId: string, record: DnsRecord): Promise<void> {\n if (record.content.endsWith(\".cfargotunnel.com\")) {\n const oldTunnelId = tunnelIdFromCname(record.content);\n try {\n const tunnel = await getTunnel(cf, oldTunnelId);\n if (isManagedTunnel(tunnel)) await deleteTunnel(cf, oldTunnelId);\n } catch {\n /* tunnel already gone or not accessible — freeing the DNS name is enough */\n }\n }\n await deleteDnsRecord(cf.token, zoneId, record.id);\n}\n\n/** Unwind created resources in reverse. Never masks the original error; if a\n * step fails, report the leaked id and return false so the caller marks the\n * entry `orphaned`. */\nasync function rollback(cf: Cf, zoneId: string, tunnelId?: string, dnsRecordId?: string, hostname?: string): Promise<boolean> {\n let clean = true;\n if (dnsRecordId) {\n try { await deleteDnsRecord(cf.token, zoneId, dnsRecordId); }\n catch { clean = false; say.warn(`Left a DNS record behind for ${hostname} (${dnsRecordId}) — run \\`cloudtunnel rm --force ${hostname}\\`.`); }\n }\n if (tunnelId) {\n try { await deleteTunnel(cf, tunnelId); }\n catch { clean = false; say.warn(`Left tunnel ${tunnelId} behind — run \\`cloudtunnel gc\\`.`); }\n }\n return clean;\n}\n","import type { IngressRule } from \"../cloudflare/types.js\";\n\n/**\n * Build the ingress config for a single-hostname tunnel. The mandatory\n * catch-all `http_status:404` rule must come last (Cloudflare rejects configs\n * without it). One-tunnel-per-subdomain keeps this a fixed two-rule list, so\n * the full-replace PUT is always safe (no merge with other hostnames).\n */\nexport function buildIngress(opts: {\n hostname: string;\n port: number;\n proto: \"http\" | \"https\";\n}): IngressRule[] {\n return [\n { hostname: opts.hostname, service: `${opts.proto}://localhost:${opts.port}` },\n { service: \"http_status:404\" },\n ];\n}\n","import { randomInt } from \"node:crypto\";\nimport { CliError } from \"../ui/errors.js\";\n\nconst ADJECTIVES = [\n \"brave\", \"calm\", \"clever\", \"eager\", \"gentle\", \"happy\", \"jolly\", \"kind\",\n \"lively\", \"mighty\", \"nimble\", \"proud\", \"quick\", \"royal\", \"swift\", \"witty\",\n];\nconst NOUNS = [\n \"otter\", \"falcon\", \"maple\", \"comet\", \"harbor\", \"lynx\", \"willow\", \"cedar\",\n \"raven\", \"meadow\", \"pixel\", \"quartz\", \"river\", \"sparrow\", \"tiger\", \"walnut\",\n];\n\nconst pick = <T>(arr: T[]): T => arr[randomInt(arr.length)]!;\n\n/** A friendly random subdomain, e.g. `brave-otter-1a2b` (the default when unnamed). */\nexport function randomSlug(): string {\n const suffix = randomInt(0x10000).toString(16).padStart(4, \"0\");\n return `${pick(ADJECTIVES)}-${pick(NOUNS)}-${suffix}`;\n}\n\nexport interface HostSpec {\n subdomain: string;\n zone: string;\n hostname: string;\n}\n\n/**\n * Resolve the target hostname from flags. Precedence: --hostname > --name+zone >\n * random-slug+zone. Zone comes from --zone or the saved default; missing zone is\n * an actionable error. (--hostname assumes `label.zone`; deeper subdomains need\n * the zone to be an actual Cloudflare zone.)\n */\nexport function resolveHostSpec(\n opts: { name?: string; zone?: string; hostname?: string },\n defaultZone?: string,\n): HostSpec {\n if (opts.hostname) {\n const dot = opts.hostname.indexOf(\".\");\n if (dot <= 0) throw new CliError(`Invalid hostname: ${opts.hostname}`);\n return {\n subdomain: opts.hostname.slice(0, dot),\n zone: opts.hostname.slice(dot + 1),\n hostname: opts.hostname,\n };\n }\n const zone = opts.zone ?? defaultZone;\n if (!zone) {\n throw new CliError(\"No zone specified and no default zone set.\", {\n hint: \"pass --zone <domain>, or run `cloudtunnel login --zone <domain>`\",\n });\n }\n const subdomain = opts.name ?? randomSlug();\n return { subdomain, zone, hostname: `${subdomain}.${zone}` };\n}\n","import type { Cf } from \"../cloudflare/client.js\";\nimport { resolveZone } from \"../cloudflare/zones.js\";\nimport { deleteTunnel, getTunnel, isManagedTunnel, listTunnels, putIngress } from \"../cloudflare/tunnels.js\";\nimport { deleteDnsRecord, findCname, isManagedDns } from \"../cloudflare/dns.js\";\nimport type { Tunnel } from \"../cloudflare/types.js\";\nimport { buildIngress } from \"./ingress.js\";\nimport { CliError } from \"../ui/errors.js\";\nimport { say } from \"../ui/output.js\";\nimport { getEntry, listEntries, patchEntry, reconcile, removeEntry, type RegistryEntry } from \"../connector/registry.js\";\nimport { stopConnector } from \"../connector/process.js\";\n\nconst tunnelIdFromCname = (content: string): string => content.replace(/\\.cfargotunnel\\.com\\.?$/, \"\");\nconst isNotFound = (err: unknown): boolean => err instanceof CliError && err.status === 404;\nconst zoneFromFqdn = (fqdn: string): string => fqdn.slice(fqdn.indexOf(\".\") + 1);\n\n/** Resolve a target to its registry entry / fqdn. Accepts a full hostname, a\n * subdomain name, or a tunnel-id prefix (as shown in `cloudtunnel ls`). Refuses\n * an ambiguous match. */\nexport function resolveTarget(target: string): { fqdn: string; entry?: RegistryEntry } {\n if (target.includes(\".\")) return { fqdn: target, entry: getEntry(target) };\n const entries = listEntries();\n const byId = entries.filter((e) => e.tunnelId?.startsWith(target));\n const matches = byId.length > 0 ? byId : entries.filter((e) => e.subdomain === target);\n if (matches.length > 1) {\n throw new CliError(`\"${target}\" matches multiple subdomains.`, {\n hint: `use a full hostname or a longer id: ${matches.map((m) => `${m.subdomain}.${m.zone}`).join(\", \")}`,\n });\n }\n const entry = matches[0];\n if (!entry) {\n throw new CliError(`No tracked subdomain matching \"${target}\".`, { hint: \"pass a hostname, name, or id (see `cloudtunnel ls`)\" });\n }\n return { fqdn: `${entry.subdomain}.${entry.zone}`, entry };\n}\n\nexport interface RemoveOptions { force?: boolean; dryRun?: boolean; keepDns?: boolean }\n\n/** Delete a tunnel subdomain. Re-verifies fresh Cloudflare state (cached ids are\n * hints), ownership-gates unmanaged resources, and tolerates already-deleted parts. */\nexport async function removeTunnelSubdomain(cf: Cf, target: string, opts: RemoveOptions = {}): Promise<void> {\n const { fqdn, entry } = resolveTarget(target);\n if (!entry && !opts.force) {\n throw new CliError(`${fqdn} is not managed by cloudtunnel.`, { hint: \"pass --force to delete it anyway\" });\n }\n const zoneId = entry?.zoneId ?? (await resolveZone(cf.token, zoneFromFqdn(fqdn))).id;\n\n const record = await findCname(cf.token, zoneId, fqdn); // fresh, authoritative\n if (record && !isManagedDns(record) && !opts.force) {\n throw new CliError(`${fqdn} points to a record not managed by cloudtunnel.`, { hint: \"pass --force to delete it\" });\n }\n const tunnelId = record ? tunnelIdFromCname(record.content) : entry?.tunnelId;\n\n if (opts.dryRun) {\n say.info(`Would delete: tunnel ${tunnelId ?? \"(none)\"}${record && !opts.keepDns ? `, DNS ${record.id}` : \"\"}`);\n return;\n }\n\n if (entry) await stopConnector(entry);\n if (tunnelId) {\n let tunnel: Tunnel | undefined;\n try {\n tunnel = await getTunnel(cf, tunnelId);\n } catch (err) {\n if (!isNotFound(err)) throw err; // transient error → don't silently orphan\n }\n if (tunnel && !isManagedTunnel(tunnel) && !opts.force) {\n throw new CliError(`Tunnel ${tunnelId} is not managed by cloudtunnel.`, { hint: \"pass --force\" });\n }\n if (tunnel) {\n try {\n await deleteTunnel(cf, tunnelId);\n } catch (err) {\n if (!isNotFound(err)) throw err;\n }\n }\n }\n if (record && !opts.keepDns) {\n try {\n await deleteDnsRecord(cf.token, zoneId, record.id);\n } catch (err) {\n if (!isNotFound(err)) throw err;\n }\n }\n await removeEntry(fqdn);\n say.ok(`Removed ${fqdn}`);\n}\n\n/** Change the served port/proto. PUT-only — the connector hot-reloads ingress\n * over its edge RPC, so no restart (and no downtime) is needed. */\nexport async function updateIngress(cf: Cf, target: string, port: number, proto?: \"http\" | \"https\"): Promise<void> {\n const { fqdn, entry } = resolveTarget(target);\n if (!entry?.tunnelId) throw new CliError(`No tracked tunnel for ${fqdn}.`);\n const nextProto = proto ?? entry.proto;\n await putIngress(cf, entry.tunnelId, buildIngress({ hostname: fqdn, port, proto: nextProto }));\n await patchEntry(fqdn, { port, proto: nextProto });\n say.ok(`${fqdn} now points to ${nextProto}://localhost:${port} (no restart needed)`);\n}\n\nexport interface LsRow { id: string; hostname: string; port: string; state: string; pid: string; managed: boolean }\n\n/** Reconcile + list tracked subdomains (id, target, state, connector pid). `all`\n * also scans every zone for cfargotunnel CNAMEs created outside cloudtunnel. */\nexport async function listAll(cf: Cf, opts: { all?: boolean } = {}): Promise<LsRow[]> {\n const entries = await reconcile();\n const tunnels = new Map((await listTunnels(cf)).map((t) => [t.id, t]));\n const rows: LsRow[] = entries.map((e) => ({\n id: e.tunnelId ? e.tunnelId.slice(0, 12) : \"-\",\n hostname: `${e.subdomain}.${e.zone}`,\n port: `${e.proto}://localhost:${e.port}`,\n state: e.tunnelId && !tunnels.has(e.tunnelId) ? \"dangling\" : e.state,\n pid: e.state === \"running\" && e.pid ? String(e.pid) : \"-\",\n managed: true,\n }));\n if (opts.all) {\n const { listCargoCnames } = await import(\"../cloudflare/dns.js\");\n const { listZones } = await import(\"../cloudflare/zones.js\");\n const tracked = new Set(entries.map((e) => `${e.subdomain}.${e.zone}`));\n for (const zone of await listZones(cf.token)) {\n for (const rec of await listCargoCnames(cf.token, zone.id)) {\n if (!tracked.has(rec.name)) {\n rows.push({ id: tunnelIdFromCname(rec.content).slice(0, 12), hostname: rec.name, port: \"-\", state: \"unmanaged\", pid: \"-\", managed: false });\n }\n }\n }\n }\n return rows;\n}\n","import type { Command } from \"commander\";\nimport { printTable, say } from \"../ui/output.js\";\nimport { ensureAuth } from \"../config/ensure-auth.js\";\nimport { resolveCf } from \"../cloudflare/client.js\";\nimport { listAll } from \"../core/orchestrator-manage.js\";\n\nexport function registerLs(program: Command): void {\n program\n .command(\"ls\")\n .alias(\"ps\")\n .description(\"List tunnel subdomains (managed by default; --all scans the whole account)\")\n .option(\"--all\", \"scan every zone in the account (slower; shows unmanaged tunnels too)\")\n .action(async (opts: { all?: boolean }) => {\n await ensureAuth();\n const cf = resolveCf();\n const rows = await listAll(cf, { all: opts.all });\n if (rows.length === 0) {\n say.info(\"No tunnel subdomains yet. Create one: `cloudtunnel 3000`\");\n return;\n }\n printTable(\n [\"ID\", \"SUBDOMAIN\", \"TARGET\", \"STATE\", \"PID\"],\n rows.map((r) => [r.id, r.hostname, r.port, r.state, r.pid]),\n );\n });\n}\n","import type { Command } from \"commander\";\nimport { ensureAuth } from \"../config/ensure-auth.js\";\nimport { resolveCf } from \"../cloudflare/client.js\";\nimport { removeTunnelSubdomain } from \"../core/orchestrator-manage.js\";\n\ninterface RmOptions { force?: boolean; dryRun?: boolean; keepDns?: boolean }\n\nexport function registerRm(program: Command): void {\n program\n .command(\"rm\")\n .aliases([\"remove\", \"delete\"])\n .argument(\"<target>\", \"subdomain name, hostname, or id to delete\")\n .description(\"Delete a tunnel subdomain (stops connector, removes tunnel + DNS)\")\n .option(\"--force\", \"allow deleting a resource not created by cloudtunnel\")\n .option(\"--dry-run\", \"show what would be deleted without deleting\")\n .option(\"--keep-dns\", \"delete the tunnel but leave the DNS record\")\n .action(async (target: string, opts: RmOptions) => {\n await ensureAuth();\n const cf = resolveCf();\n await removeTunnelSubdomain(cf, target, opts);\n });\n}\n","import type { Command } from \"commander\";\nimport { CliError } from \"../ui/errors.js\";\nimport { ensureAuth } from \"../config/ensure-auth.js\";\nimport { resolveCf } from \"../cloudflare/client.js\";\nimport { updateIngress } from \"../core/orchestrator-manage.js\";\n\ninterface UpdateOptions { port?: string; proto?: \"http\" | \"https\" }\n\nexport function registerUpdate(program: Command): void {\n program\n .command(\"update\")\n .argument(\"<name>\", \"subdomain name or full hostname to update\")\n .description(\"Change the local port/protocol a subdomain points to (zero-downtime)\")\n .option(\"--port <port>\", \"new local port\")\n .option(\"--proto <proto>\", \"new local protocol: http | https\")\n .action(async (name: string, opts: UpdateOptions) => {\n if (!opts.port) throw new CliError(\"--port is required\", { hint: \"e.g. `cloudtunnel update myapp --port 8080`\" });\n const port = Number(opts.port);\n if (!Number.isInteger(port) || port < 1 || port > 65535) throw new CliError(`Invalid port: ${opts.port}`);\n await ensureAuth();\n const cf = resolveCf();\n await updateIngress(cf, name, port, opts.proto);\n });\n}\n","import type { Command } from \"commander\";\nimport { CliError } from \"../ui/errors.js\";\nimport { say } from \"../ui/output.js\";\nimport { ensureAuth } from \"../config/ensure-auth.js\";\nimport { resolveCf } from \"../cloudflare/client.js\";\nimport { getConnections } from \"../cloudflare/tunnels.js\";\nimport { isOurConnector } from \"../connector/registry.js\";\nimport { resolveTarget } from \"../core/orchestrator-manage.js\";\n\nexport function registerStatus(program: Command): void {\n program\n .command(\"status\")\n .argument(\"<name>\", \"subdomain name or full hostname\")\n .description(\"Show tunnel health and connector state for a subdomain\")\n .action(async (name: string) => {\n await ensureAuth();\n const cf = resolveCf();\n const { fqdn, entry } = resolveTarget(name);\n if (!entry?.tunnelId) throw new CliError(`No tracked tunnel for ${fqdn}.`);\n const connections = await getConnections(cf, entry.tunnelId);\n const connectorAlive = await isOurConnector(entry);\n say.info(`Host: https://${fqdn}`);\n say.info(`Tunnel: ${entry.tunnelId} — ${connections.length} edge connection(s)`);\n say.info(`Connector: ${connectorAlive ? `running (pid ${entry.pid})` : \"stopped\"}`);\n say.info(`Target: ${entry.proto}://localhost:${entry.port}`);\n });\n}\n","import type { Command } from \"commander\";\nimport { CliError } from \"../ui/errors.js\";\nimport { say } from \"../ui/output.js\";\nimport { listEntries, mutateRegistry, type RegistryEntry } from \"../connector/registry.js\";\nimport { stopConnector } from \"../connector/process.js\";\nimport { resolveTarget } from \"../core/orchestrator-manage.js\";\n\nasync function stopEntry(entry: RegistryEntry): Promise<boolean> {\n const stopped = await stopConnector(entry);\n await mutateRegistry((reg) => {\n const e = reg[`${entry.subdomain}.${entry.zone}`];\n if (e) {\n e.state = \"stopped\";\n delete e.pid;\n }\n });\n return stopped;\n}\n\nexport function registerDown(program: Command): void {\n program\n .command(\"down\")\n .argument(\"[name]\", \"subdomain name / hostname / id to stop (omit with --all)\")\n .description(\"Stop a running connector, leaving the tunnel + DNS intact\")\n .option(\"--all\", \"stop all running connectors\")\n .action(async (name: string | undefined, opts: { all?: boolean }) => {\n // Purely local — no Cloudflare auth needed to stop a connector process.\n if (opts.all) {\n const running = listEntries().filter((e) => e.pid);\n let stopped = 0;\n for (const entry of running) if (await stopEntry(entry)) stopped++;\n say.ok(`Stopped ${stopped} connector(s).`);\n return;\n }\n if (!name) throw new CliError(\"Pass a subdomain name or --all.\");\n const { fqdn, entry } = resolveTarget(name);\n if (!entry) throw new CliError(`No tracked subdomain for ${fqdn}.`);\n await stopEntry(entry);\n say.ok(`Stopped ${fqdn}.`);\n });\n}\n","import type { Command } from \"commander\";\nimport { say } from \"../ui/output.js\";\nimport { ensureAuth } from \"../config/ensure-auth.js\";\nimport { resolveCf } from \"../cloudflare/client.js\";\nimport { listEntries, reconcile } from \"../connector/registry.js\";\nimport { removeTunnelSubdomain } from \"../core/orchestrator-manage.js\";\n\nexport function registerGc(program: Command): void {\n program\n .command(\"gc\")\n .description(\"Prune crash orphans (provisioning/orphaned entries) after confirmation\")\n .option(\"--yes\", \"skip the confirmation prompt\")\n .action(async (opts: { yes?: boolean }) => {\n await ensureAuth();\n const cf = resolveCf();\n await reconcile();\n const orphans = listEntries().filter((e) => e.state === \"provisioning\" || e.state === \"orphaned\");\n if (orphans.length === 0) {\n say.info(\"Nothing to clean up.\");\n return;\n }\n say.info(`Found ${orphans.length} orphaned entr${orphans.length === 1 ? \"y\" : \"ies\"}:`);\n for (const o of orphans) say.dim(` ${o.subdomain}.${o.zone} (${o.state})`);\n if (!opts.yes) {\n say.warn(\"Re-run with --yes to delete these tunnels/records.\");\n return;\n }\n for (const o of orphans) {\n try {\n await removeTunnelSubdomain(cf, `${o.subdomain}.${o.zone}`, { force: true });\n } catch {\n say.warn(`Could not fully clean ${o.subdomain}.${o.zone} — check the dashboard.`);\n }\n }\n });\n}\n","import type { Command } from \"commander\";\nimport { printTable, say } from \"../ui/output.js\";\nimport { ensureAuth } from \"../config/ensure-auth.js\";\nimport { resolveCf } from \"../cloudflare/client.js\";\nimport { listZones } from \"../cloudflare/zones.js\";\n\nexport function registerZones(program: Command): void {\n program\n .command(\"zones\")\n .description(\"List the zones (domains) available in your Cloudflare account\")\n .action(async () => {\n await ensureAuth();\n const cf = resolveCf();\n const zones = await listZones(cf.token);\n if (zones.length === 0) {\n say.info(\"No zones in this account.\");\n return;\n }\n printTable(\n [\"ZONE\", \"STATUS\", \"ID\"],\n zones.map((z) => [z.name, z.status ?? \"-\", z.id]),\n );\n });\n}\n","import { readFileSync, writeFileSync } from \"node:fs\";\nimport { ensureDirs, profilesFile } from \"../config/paths.js\";\nimport { CliError } from \"../ui/errors.js\";\n\n/** One service in a profile — e.g. `{ name: \"api\", port: 3000 }`. */\nexport interface ProfileService {\n name: string;\n port: number;\n proto: \"http\" | \"https\";\n domain?: string; // overrides the profile/default domain\n}\n\nexport interface Profile {\n services: ProfileService[];\n domain?: string; // default domain for the whole profile\n}\n\ntype Profiles = Record<string, Profile>;\n\nfunction readProfiles(): Profiles {\n try {\n return JSON.parse(readFileSync(profilesFile, \"utf8\")) as Profiles;\n } catch {\n return {};\n }\n}\n\nfunction writeProfiles(profiles: Profiles): void {\n ensureDirs();\n writeFileSync(profilesFile, JSON.stringify(profiles, null, 2), { mode: 0o600 });\n}\n\nexport function listProfiles(): Array<{ name: string; profile: Profile }> {\n return Object.entries(readProfiles()).map(([name, profile]) => ({ name, profile }));\n}\n\nexport function getProfile(name: string): Profile {\n const profile = readProfiles()[name];\n if (!profile) {\n throw new CliError(`No profile named \"${name}\".`, { hint: \"list them with `cloudtunnel profiles`\" });\n }\n return profile;\n}\n\nexport function saveProfile(name: string, profile: Profile): void {\n const profiles = readProfiles();\n profiles[name] = profile;\n writeProfiles(profiles);\n}\n\nexport function removeProfile(name: string): void {\n const profiles = readProfiles();\n if (!profiles[name]) throw new CliError(`No profile named \"${name}\".`);\n delete profiles[name];\n writeProfiles(profiles);\n}\n\n/**\n * Parse a `name:port[:proto]` service spec, e.g. `api:3000` or `web:5173:https`.\n */\nexport function parseServiceSpec(spec: string): ProfileService {\n const [name, portStr, proto] = spec.split(\":\");\n const port = Number(portStr);\n if (!name || !Number.isInteger(port) || port < 1 || port > 65535) {\n throw new CliError(`Invalid service \"${spec}\".`, { hint: \"use name:port, e.g. api:3000 or web:5173:https\" });\n }\n if (proto && proto !== \"http\" && proto !== \"https\") {\n throw new CliError(`Invalid protocol \"${proto}\" in \"${spec}\".`, { hint: \"proto must be http or https\" });\n }\n return { name, port, proto: (proto as \"http\" | \"https\") ?? \"http\" };\n}\n","import type { Command } from \"commander\";\nimport { CliError } from \"../ui/errors.js\";\nimport { say } from \"../ui/output.js\";\nimport { listEntries } from \"../connector/registry.js\";\nimport { parseServiceSpec, saveProfile, type ProfileService } from \"../core/profiles.js\";\n\ninterface SaveOptions { fromRunning?: boolean; domain?: string }\n\nexport function registerSave(program: Command): void {\n program\n .command(\"save\")\n .argument(\"<profile>\", \"profile name, e.g. mb\")\n .argument(\"[services...]\", \"services as name:port[:proto], e.g. api:3000 web:5173\")\n .description(\"Save a group of services as a profile you can `run` together\")\n .option(\"--from-running\", \"snapshot the currently tracked tunnels instead of listing services\")\n .option(\"-d, --domain <domain>\", \"default domain for this profile\")\n .action((profile: string, specs: string[], opts: SaveOptions) => {\n let services: ProfileService[];\n if (opts.fromRunning) {\n const entries = listEntries().filter((e) => e.tunnelId);\n if (entries.length === 0) {\n throw new CliError(\"No tunnels to snapshot.\", { hint: \"start some with `cloudtunnel up`, or pass services like api:3000\" });\n }\n services = entries.map((e) => ({ name: e.subdomain, port: e.port, proto: e.proto, domain: e.zone }));\n } else {\n if (specs.length === 0) {\n throw new CliError(\"No services given.\", { hint: \"e.g. `cloudtunnel save mb api:3000 web:5173`\" });\n }\n services = specs.map(parseServiceSpec);\n }\n saveProfile(profile, { services, domain: opts.domain });\n say.ok(`Saved profile \"${profile}\" (${services.length} service${services.length === 1 ? \"\" : \"s\"}). Run it: cloudtunnel run ${profile}`);\n });\n}\n","import type { Command } from \"commander\";\nimport { join } from \"node:path\";\nimport * as clack from \"@clack/prompts\";\nimport { reportError } from \"../ui/errors.js\";\nimport { dim, formatRoute, say } from \"../ui/output.js\";\nimport { ensureAuth } from \"../config/ensure-auth.js\";\nimport { resolveCf } from \"../cloudflare/client.js\";\nimport { logDir } from \"../config/paths.js\";\nimport { ensureCloudflared } from \"../connector/binary.js\";\nimport { startConnector, stopConnector } from \"../connector/process.js\";\nimport { waitHealthy, type HealthResult } from \"../connector/health.js\";\nimport { currentBootId, getEntry, patchEntry } from \"../connector/registry.js\";\nimport { createTunnelSubdomain } from \"../core/orchestrator-create.js\";\nimport { getProfile } from \"../core/profiles.js\";\n\n/**\n * Run every service in a saved profile at once (e.g. `cloudtunnel run mb` →\n * backend + frontend live together). All connectors run in the foreground;\n * Ctrl-C stops them all (subdomains are kept).\n */\ninterface RunOptions { force?: boolean; domain?: string; detach?: boolean }\n\nasync function runProfile(name: string, opts: RunOptions): Promise<void> {\n const creds = await ensureAuth();\n const cf = resolveCf();\n const bin = await ensureCloudflared();\n const profile = getProfile(name);\n\n if (process.stdout.isTTY) clack.intro(`cloudtunnel · profile \"${name}\"`);\n const spin = clack.spinner();\n spin.start(\"Creating tunnels…\");\n\n const started: Array<{ fqdn: string; subdomain: string; tunnelId: string; target: string; pid: number }> = [];\n for (const svc of profile.services) {\n spin.message(`Creating ${svc.name} (:${svc.port})…`);\n const result = await createTunnelSubdomain(cf, {\n port: svc.port, proto: svc.proto, name: svc.name,\n zone: svc.domain ?? opts.domain ?? profile.domain, defaultZone: creds.defaultZone,\n force: opts.force,\n });\n const fqdn = result.host.hostname;\n const logFile = join(logDir, `${result.host.subdomain}.log`);\n const conn = startConnector({\n bin, token: result.token, detach: !!opts.detach, logFile,\n onExit: opts.detach ? undefined : () => say.warn(`Connector for ${fqdn} exited — check \\`cloudtunnel status ${result.host.subdomain}\\`.`),\n });\n await patchEntry(fqdn, { pid: conn.pid, bootId: currentBootId(), logFile });\n started.push({ fqdn, subdomain: result.host.subdomain, tunnelId: result.tunnelId, target: `${svc.proto}://localhost:${svc.port}`, pid: conn.pid });\n }\n\n // Detached: print URLs + pids and exit; the connectors keep running.\n if (opts.detach) {\n spin.stop(`${started.length} service(s) started in the background`);\n const lines = started.map((s) => `${formatRoute(s.fqdn, s.target)} ${dim(`pid ${s.pid}`)}`);\n clack.note(lines.join(\"\\n\"), `profile \"${name}\" — running in background`);\n if (process.stdout.isTTY) clack.outro(\"Stop them with: cloudtunnel down --all\");\n return;\n }\n\n spin.message(\"Connecting to the Cloudflare edge…\");\n const healths = await Promise.all(started.map((s) => waitHealthy(cf, s.tunnelId, { timeoutMs: 30_000 })));\n const live = healths.filter((h: HealthResult) => h === \"healthy\").length;\n spin.stop(`${started.length} service(s) started`);\n\n const lines = started.map((s, i) => `${formatRoute(s.fqdn, s.target)}${healths[i] === \"healthy\" ? \"\" : dim(` (${healths[i]})`)}`);\n clack.note(lines.join(\"\\n\"), `profile \"${name}\" — ${live}/${started.length} live`);\n say.dim(\"Ctrl-C stops all connectors (subdomains are kept).\");\n\n let tornDown = false;\n const teardownAll = async (code: number): Promise<void> => {\n if (tornDown) return;\n tornDown = true;\n try {\n for (const s of started) {\n const entry = getEntry(s.fqdn);\n if (entry) await stopConnector(entry);\n }\n if (process.stdout.isTTY) clack.outro(`Stopped ${started.length} connector(s) · subdomains kept`);\n } catch (err) {\n reportError(err);\n } finally {\n process.exit(code);\n }\n };\n for (const sig of [\"SIGINT\", \"SIGHUP\", \"SIGTERM\"] as const) {\n process.on(sig, () => void teardownAll(0));\n }\n}\n\nexport function registerRun(program: Command): void {\n program\n .command(\"run\")\n .argument(\"<profile>\", \"name of a saved profile (see `cloudtunnel profiles`)\")\n .description(\"Start every service in a saved profile at once\")\n .option(\"-f, --force\", \"take over subdomains already occupied by another record\")\n .option(\"-d, --domain <domain>\", \"override the profile's domain for this run\")\n .option(\"--detach\", \"run all connectors in the background (stop with `cloudtunnel down --all`)\")\n .action((name: string, opts: RunOptions) => runProfile(name, opts));\n}\n","import type { Command } from \"commander\";\nimport { printTable, say } from \"../ui/output.js\";\nimport { listProfiles, removeProfile } from \"../core/profiles.js\";\n\nexport function registerProfiles(program: Command): void {\n program\n .command(\"profiles\")\n .description(\"List saved profiles (or delete one with --rm)\")\n .option(\"--rm <name>\", \"delete a profile\")\n .action((opts: { rm?: string }) => {\n if (opts.rm) {\n removeProfile(opts.rm);\n say.ok(`Deleted profile \"${opts.rm}\".`);\n return;\n }\n const profiles = listProfiles();\n if (profiles.length === 0) {\n say.info(\"No profiles yet. Create one: `cloudtunnel save mb api:3000 web:5173`\");\n return;\n }\n printTable(\n [\"PROFILE\", \"SERVICES\", \"DOMAIN\"],\n profiles.map(({ name, profile }) => [\n name,\n profile.services.map((s) => `${s.name}:${s.port}`).join(\", \"),\n profile.domain ?? \"(default)\",\n ]),\n );\n });\n}\n","import type { Command } from \"commander\";\nimport { closeSync, existsSync, openSync, readFileSync, readSync, statSync, watch } from \"node:fs\";\nimport { CliError } from \"../ui/errors.js\";\nimport { say } from \"../ui/output.js\";\nimport { resolveTarget } from \"../core/orchestrator-manage.js\";\n\ninterface LogsOptions {\n follow?: boolean;\n lines?: string;\n}\n\n/** Print the last `n` lines of a file; return the file's byte size (follow start). */\nfunction printTail(file: string, n: number): number {\n const lines = readFileSync(file, \"utf8\").split(\"\\n\");\n const tail = lines.slice(-n).join(\"\\n\");\n process.stdout.write(tail.endsWith(\"\\n\") ? tail : `${tail}\\n`);\n return statSync(file).size;\n}\n\n/** Tail -f: print appended bytes as the connector writes them. Ctrl-C to stop. */\nfunction follow(file: string, fromPos: number): void {\n let pos = fromPos;\n say.dim(\"— following (Ctrl-C to stop) —\");\n const watcher = watch(file, () => {\n const size = statSync(file).size;\n if (size < pos) {\n pos = 0; // file was truncated/rotated\n return;\n }\n if (size > pos) {\n const fd = openSync(file, \"r\");\n const buf = Buffer.alloc(size - pos);\n readSync(fd, buf, 0, size - pos, pos);\n closeSync(fd);\n process.stdout.write(buf.toString(\"utf8\"));\n pos = size;\n }\n });\n process.on(\"SIGINT\", () => {\n watcher.close();\n process.exit(0);\n });\n}\n\nexport function registerLogs(program: Command): void {\n program\n .command(\"logs\")\n .argument(\"<name>\", \"subdomain name or full hostname\")\n .description(\"Show the connector log for a subdomain (use -f to follow)\")\n .option(\"-f, --follow\", \"keep printing new log lines (like tail -f)\")\n .option(\"-n, --lines <n>\", \"number of lines to show\", \"50\")\n .action((name: string, opts: LogsOptions) => {\n const { fqdn, entry } = resolveTarget(name);\n if (!entry?.logFile || !existsSync(entry.logFile)) {\n throw new CliError(`No logs for ${fqdn} yet.`, { hint: \"start it with `cloudtunnel up` or `cloudtunnel run`\" });\n }\n const n = Math.max(1, Number(opts.lines) || 50);\n const pos = printTail(entry.logFile, n);\n if (opts.follow) follow(entry.logFile, pos);\n });\n}\n"],"mappings":"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","names":["pc","listZones","listZones","join","readFileSync","clack","execFileSync","spawn","existsSync","readFileSync","writeFileSync","readFileSync","writeFileSync","existsSync","spawn","execFileSync","sleep","randomInt","tunnelId","randomInt","tunnelIdFromCname","listZones","readFileSync","join","started","entry","readFileSync","writeFileSync","readFileSync","writeFileSync","join","clack","join","lines","existsSync","openSync","readFileSync","readFileSync","openSync","existsSync","require","pc"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/commands/login.ts","../src/ui/output.ts","../src/config/token-url.ts","../src/config/resolve-identity.ts","../src/commands/up.ts","../src/config/ensure-auth.ts","../src/connector/binary.ts","../src/connector/process.ts","../src/connector/registry.ts","../src/cloudflare/tunnels.ts","../src/connector/health.ts","../src/core/orchestrator-create.ts","../src/core/ingress.ts","../src/core/slug.ts","../src/core/orchestrator-manage.ts","../src/commands/ls.ts","../src/commands/down.ts","../src/commands/zones.ts","../src/core/profiles.ts","../src/commands/save.ts","../src/commands/run.ts","../src/commands/profiles.ts","../src/commands/logs.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { createRequire } from \"node:module\";\nimport pc from \"picocolors\";\nimport { reportError } from \"./ui/errors.js\";\n\nimport { registerLogin } from \"./commands/login.js\";\nimport { registerUp } from \"./commands/up.js\";\nimport { registerLs } from \"./commands/ls.js\";\nimport { registerDown } from \"./commands/down.js\";\nimport { registerZones } from \"./commands/zones.js\";\nimport { registerSave } from \"./commands/save.js\";\nimport { registerRun } from \"./commands/run.js\";\nimport { registerProfiles } from \"./commands/profiles.js\";\nimport { registerLogs } from \"./commands/logs.js\";\n\nconst require = createRequire(import.meta.url);\nconst pkg = require(\"../package.json\") as { version: string };\n\nconst KNOWN_COMMANDS = new Set([\n \"login\", \"up\", \"ls\", \"ps\", \"down\", \"rm\", \"remove\", \"delete\", \"stop\",\n \"logs\", \"zones\", \"save\", \"run\", \"profiles\", \"help\",\n]);\n\n/**\n * Bare-port sugar: `cloudtunnel 3000` ≡ `cloudtunnel up 3000`.\n * If the first non-flag arg is a port-like number and not a known command,\n * splice `up` in front. Keeps commander's own parsing untouched.\n */\nfunction applyBarePortAlias(argv: string[]): string[] {\n const args = argv.slice(2);\n const first = args[0];\n // Only the leading token: `cloudtunnel 3000 …` → `up 3000 …`. Avoids mistaking\n // a flag value (e.g. `--proto 3000`) for the port.\n if (first && /^\\d{1,5}$/.test(first) && !KNOWN_COMMANDS.has(first)) {\n args.unshift(\"up\");\n }\n return [argv[0]!, argv[1]!, ...args];\n}\n\nfunction buildProgram(): Command {\n const program = new Command();\n program\n .name(\"cloudtunnel\")\n .description(\"Manage Cloudflare Tunnels and subdomains account-wide, from your terminal.\")\n .version(pkg.version, \"-v, --version\")\n .showHelpAfterError();\n\n program.addHelpText(\n \"before\",\n [\n pc.bold(\"Quickstart:\"),\n ` ${pc.cyan(\"cloudtunnel login\")} once — paste a token (or set CLOUDFLARE_API_TOKEN)`,\n ` ${pc.cyan(\"cloudtunnel 3000\")} → your local :3000 goes live at an HTTPS URL`,\n \"\",\n ].join(\"\\n\"),\n );\n\n for (const register of [\n registerLogin, registerUp, registerLs, registerDown, registerZones,\n registerSave, registerRun, registerProfiles, registerLogs,\n ]) {\n register(program);\n }\n return program;\n}\n\nasync function main(): Promise<void> {\n const program = buildProgram();\n try {\n await program.parseAsync(applyBarePortAlias(process.argv));\n } catch (err) {\n process.exitCode = reportError(err);\n }\n}\n\nvoid main();\n","import type { Command } from \"commander\";\nimport * as clack from \"@clack/prompts\";\nimport { CliError } from \"../ui/errors.js\";\nimport { redactToken, say, selectOne } from \"../ui/output.js\";\nimport { configFile } from \"../config/paths.js\";\nimport { loadConfig, saveConfig } from \"../config/store.js\";\nimport { REQUIRED_SCOPES, openBrowser, tokenCreateUrl } from \"../config/token-url.js\";\nimport { listAccounts, listZones } from \"../config/resolve-identity.js\";\n\ninterface LoginOptions {\n tokenStdin?: boolean;\n token?: string; // deprecated: leaks into shell history\n account?: string;\n zone?: string;\n status?: boolean;\n}\n\n/** Read the whole stdin pipe (for `--token-stdin`). */\nasync function readStdin(): Promise<string> {\n const chunks: Buffer[] = [];\n for await (const chunk of process.stdin) chunks.push(chunk as Buffer);\n return Buffer.concat(chunks).toString(\"utf8\").trim();\n}\n\n/** Acquire the API token: env (silent) → stdin → deprecated flag → masked prompt.\n * Env tokens are NOT persisted (the env stays the source of truth). */\nasync function acquireToken(opts: LoginOptions): Promise<{ token: string; fromEnv: boolean }> {\n const envToken = process.env.CLOUDFLARE_API_TOKEN;\n if (envToken) {\n say.dim(\"Using token from CLOUDFLARE_API_TOKEN.\");\n return { token: envToken, fromEnv: true };\n }\n if (opts.tokenStdin) return { token: await readStdin(), fromEnv: false };\n if (opts.token) {\n say.warn(\"--token puts the token in your shell history — prefer --token-stdin or the prompt. Rotate it if this is a shared host.\");\n return { token: opts.token, fromEnv: false };\n }\n if (!process.stdin.isTTY) {\n throw new CliError(\"No token provided and no interactive terminal.\", {\n hint: \"pipe it: `printf %s $TOKEN | cloudtunnel login --token-stdin`\",\n });\n }\n clack.note(REQUIRED_SCOPES.map((s) => `• ${s}`).join(\"\\n\"), \"Create a token with these scopes\");\n openBrowser(tokenCreateUrl());\n say.dim(`(opened ${tokenCreateUrl()})`);\n const token = await clack.password({ message: \"Paste your Cloudflare API token\", mask: \"•\" });\n if (clack.isCancel(token) || !token) {\n clack.cancel(\"Cancelled.\");\n throw new CliError(\"Cancelled.\", { exitCode: 130 });\n }\n return { token, fromEnv: false };\n}\n\nasync function runLoginFlow(opts: LoginOptions = {}): Promise<void> {\n if (process.stdout.isTTY) clack.intro(\"cloudtunnel · connect to Cloudflare\");\n const { token, fromEnv } = await acquireToken(opts);\n\n const spin = clack.spinner();\n spin.start(\"Verifying token…\");\n const [accounts, zones] = await Promise.all([listAccounts(token), listZones(token)]).catch((err: unknown) => {\n spin.stop(\"Token check failed\");\n throw err;\n });\n spin.stop(\"Token verified\");\n\n if (accounts.length === 0) throw new CliError(\"Token can't see any Cloudflare account.\");\n let account = opts.account ? accounts.find((a) => a.id === opts.account) : undefined;\n if (opts.account && !account) throw new CliError(`Account ${opts.account} not visible to this token.`);\n if (!account) {\n account = accounts.length === 1 || !process.stdin.isTTY\n ? accounts[0]!\n : await selectOne(\"Select an account\", accounts, (a) => `${a.name} (${a.id})`);\n }\n\n let defaultZone = opts.zone;\n if (!defaultZone) {\n if (zones.length === 1) defaultZone = zones[0]!.name;\n else if (zones.length > 1 && process.stdin.isTTY) {\n defaultZone = (await selectOne(\"Select a default domain\", zones, (z) => z.name)).name;\n }\n }\n\n saveConfig({ apiToken: fromEnv ? undefined : token, accountId: account.id, defaultZone });\n const summary = `Logged in as ${account.name}${defaultZone ? ` · default domain ${defaultZone}` : \"\"}`;\n if (process.stdout.isTTY) clack.outro(summary);\n else say.ok(summary);\n if (!defaultZone) say.dim(\"No default domain set — pass -d <domain> on `up`, or re-run `login --zone <domain>`.\");\n}\n\nfunction showStatus(): void {\n const config = loadConfig();\n const token = process.env.CLOUDFLARE_API_TOKEN ?? config.apiToken;\n if (!token) {\n say.warn(\"Not logged in. Run `cloudtunnel login`.\");\n return;\n }\n const source = process.env.CLOUDFLARE_API_TOKEN ? \"env\" : \"config\";\n say.info(`Token: ${redactToken(token)} (${source})`);\n say.info(`Account: ${config.accountId ?? \"(from env / unresolved)\"}`);\n say.info(`Domain: ${config.defaultZone ?? \"(none)\"}`);\n say.dim(`Config: ${configFile}`);\n}\n\nexport function registerLogin(program: Command): void {\n program\n .command(\"login\")\n .description(\"Authenticate with Cloudflare (paste a token once; account + domain auto-resolved)\")\n .option(\"--token-stdin\", \"read the API token from stdin (scriptable, avoids shell history)\")\n .option(\"--token <token>\", \"[discouraged] token as an argument (leaks into shell history)\")\n .option(\"--account <id>\", \"Cloudflare account id (auto-resolved when you have one account)\")\n .option(\"--zone <domain>\", \"default domain for new tunnels (auto-resolved when you have one)\")\n .option(\"--status\", \"show current identity (redacted) and exit\")\n .action(async (opts: LoginOptions) => {\n if (opts.status) return showStatus();\n await runLoginFlow(opts);\n });\n}\n\nexport { runLoginFlow };\n","import pc from \"picocolors\";\nimport Table from \"cli-table3\";\nimport { cancel, intro, isCancel, note, outro, select, spinner } from \"@clack/prompts\";\nimport { CliError } from \"./errors.js\";\n\n// Re-export the clack primitives used to build modern multi-step flows.\nexport { intro, note, outro, spinner };\n\n/** Redact a secret to `••••{last4}` so tokens never appear in output/logs. */\nexport function redactToken(token: string): string {\n if (!token) return \"\";\n const last4 = token.length > 4 ? token.slice(-4) : token;\n return `••••${last4}`;\n}\n\n// Lightweight one-off lines for non-flow commands (ls, zones, status, …).\nexport const say = {\n info: (msg: string) => console.log(msg),\n ok: (msg: string) => console.log(pc.green(`✓ ${msg}`)),\n warn: (msg: string) => console.warn(pc.yellow(`! ${msg}`)),\n dim: (msg: string) => console.log(pc.dim(msg)),\n step: (msg: string) => console.log(pc.cyan(`→ ${msg}`)),\n};\n\nexport const dim = (s: string): string => pc.dim(s);\n\n/** Format a live tunnel as `https://host → proto://localhost:port`. */\nexport function formatRoute(host: string, target: string): string {\n return `${pc.green(pc.bold(`https://${host}`))} ${pc.dim(\"→\")} ${pc.cyan(target)}`;\n}\n\n/** Render a simple table. `head` = column titles, `rows` = string cells. */\nexport function printTable(head: string[], rows: string[][]): void {\n const table = new Table({\n head: head.map((h) => pc.bold(h)),\n style: { head: [], border: [] },\n });\n for (const row of rows) table.push(row);\n console.log(table.toString());\n}\n\n/**\n * Modern arrow-key single-select (↑/↓ to move, Enter to choose). Callers must\n * guard non-TTY before calling. Ctrl-C cancels cleanly (exit 130).\n */\nexport async function selectOne<T>(\n message: string,\n items: T[],\n label: (item: T) => string,\n): Promise<T> {\n // Use the item index as the (primitive) option value to avoid clack's\n // conditional Option<T> type fighting the generic, then map back.\n const value = await select({\n message,\n options: items.map((item, i) => ({ value: String(i), label: label(item) })),\n });\n if (isCancel(value)) {\n cancel(\"Cancelled.\");\n throw new CliError(\"Cancelled.\", { exitCode: 130 });\n }\n return items[Number(value)]!;\n}\n","import { spawn } from \"node:child_process\";\n\n/** The exact scopes cloudtunnel needs. Printed so the user selects them when\n * minting a token — least-privilege, account-wide only where required. */\nexport const REQUIRED_SCOPES = [\n \"Account · Cloudflare Tunnel · Edit\",\n \"Account · Account Settings · Read\",\n \"Zone · DNS · Edit\",\n \"Zone · Zone · Read\",\n] as const;\n\n/** Cloudflare \"Create Custom Token\" page. `name` is pre-filled best-effort;\n * the user still selects the scopes above (dashboard pre-fill params are not a\n * versioned API, so we rely on the printed scope list, not URL params). */\nexport function tokenCreateUrl(): string {\n return \"https://dash.cloudflare.com/profile/api-tokens?name=cloudtunnel\";\n}\n\n/** Best-effort open a URL in the default browser. Never throws — if no opener\n * exists (headless/CI), the caller still prints the URL. */\nexport function openBrowser(url: string): void {\n const cmd =\n process.platform === \"darwin\" ? \"open\"\n : process.platform === \"win32\" ? \"cmd\"\n : \"xdg-open\";\n const args = process.platform === \"win32\" ? [\"/c\", \"start\", \"\", url] : [url];\n try {\n const child = spawn(cmd, args, { stdio: \"ignore\", detached: true });\n child.on(\"error\", () => {}); // swallow: opener may not exist\n child.unref();\n } catch {\n // ignore — printing the URL is the fallback\n }\n}\n","import { CliError } from \"../ui/errors.js\";\nimport { REQUIRED_SCOPES, tokenCreateUrl } from \"./token-url.js\";\n\nconst API_BASE = \"https://api.cloudflare.com/client/v4\";\n\nexport interface CfAccount { id: string; name: string }\nexport interface CfZone { id: string; name: string; account?: { id: string } }\n\n/**\n * Raw Cloudflare GET used only for login-time validation (the typed SDK client\n * is wired in Phase 3). Errors are sanitized: the token never appears in any\n * thrown message. A 403 is mapped to a missing-scope hint.\n */\nasync function cfGet<T>(path: string, token: string): Promise<T[]> {\n let res: Response;\n try {\n res = await fetch(`${API_BASE}${path}`, {\n headers: { Authorization: `Bearer ${token}`, \"Content-Type\": \"application/json\" },\n });\n } catch {\n throw new CliError(\"Could not reach the Cloudflare API (network error).\");\n }\n if (res.status === 401) {\n throw new CliError(\"Cloudflare rejected the token (invalid or expired).\", {\n hint: `mint a new token: ${tokenCreateUrl()}`,\n });\n }\n if (res.status === 403) {\n throw new CliError(`Token is missing a required scope for ${path}.`, {\n hint: `token needs: ${REQUIRED_SCOPES.join(\", \")}`,\n });\n }\n const body = (await res.json().catch(() => ({}))) as { success?: boolean; result?: T[] };\n if (!res.ok || !body.success) {\n throw new CliError(`Cloudflare API error (${res.status}) on ${path}.`);\n }\n return body.result ?? [];\n}\n\nexport function listAccounts(token: string): Promise<CfAccount[]> {\n return cfGet<CfAccount>(\"/accounts?per_page=50\", token);\n}\n\nexport function listZones(token: string): Promise<CfZone[]> {\n return cfGet<CfZone>(\"/zones?per_page=50\", token);\n}\n","import type { Command } from \"commander\";\nimport { join } from \"node:path\";\nimport { readFileSync } from \"node:fs\";\nimport * as clack from \"@clack/prompts\";\nimport { CliError, reportError } from \"../ui/errors.js\";\nimport { dim, formatRoute, say, selectOne } from \"../ui/output.js\";\nimport { ensureAuth } from \"../config/ensure-auth.js\";\nimport { resolveCf, type Cf } from \"../cloudflare/client.js\";\nimport { listZones } from \"../cloudflare/zones.js\";\nimport type { Credentials } from \"../config/store.js\";\nimport { logDir } from \"../config/paths.js\";\nimport { ensureCloudflared } from \"../connector/binary.js\";\nimport { startConnector } from \"../connector/process.js\";\nimport { waitHealthy } from \"../connector/health.js\";\nimport { currentBootId, patchEntry } from \"../connector/registry.js\";\nimport { createTunnelSubdomain } from \"../core/orchestrator-create.js\";\nimport { removeTunnelSubdomain } from \"../core/orchestrator-manage.js\";\n\ninterface UpOptions {\n subdomain?: string;\n domain?: string;\n name?: string; // alias of --subdomain\n zone?: string; // alias of --domain\n hostname?: string;\n detach?: boolean;\n proto: \"http\" | \"https\";\n force?: boolean;\n}\n\nfunction parsePort(port: string): number {\n const n = Number(port);\n if (!Number.isInteger(n) || n < 1 || n > 65535) {\n throw new CliError(`Invalid port: ${port}`, { hint: \"use a number 1–65535, e.g. `cloudtunnel 3000`\" });\n }\n return n;\n}\n\n/** Which domain to use: `-d` → the single zone → an interactive picker (TTY) →\n * saved default (non-TTY) → error. `-d` is skipped when `--hostname` is given. */\nasync function resolveDomain(cf: Cf, opts: UpOptions, creds: Credentials): Promise<string | undefined> {\n if (opts.hostname) return undefined;\n const explicit = opts.domain ?? opts.zone;\n if (explicit) return explicit;\n const zones = await listZones(cf.token);\n if (zones.length === 0) throw new CliError(\"No domains found in this Cloudflare account.\");\n if (zones.length === 1) return zones[0]!.name;\n if (process.stdin.isTTY) return (await selectOne(\"Choose a domain\", zones, (z) => z.name)).name;\n if (creds.defaultZone) return creds.defaultZone;\n throw new CliError(\"Multiple domains in this account — pick one.\", { hint: \"pass -d <domain>\" });\n}\n\n/** The subdomain to use: `-s` → typed at the prompt → a friendly random name if\n * left blank (or non-TTY). */\nasync function resolveSubdomain(opts: UpOptions): Promise<string | undefined> {\n const explicit = opts.subdomain ?? opts.name;\n if (explicit || opts.hostname) return explicit;\n if (!process.stdin.isTTY) return undefined; // random\n const input = await clack.text({ message: \"Subdomain\", placeholder: \"leave blank for a random name\" });\n if (clack.isCancel(input)) {\n clack.cancel(\"Cancelled.\");\n process.exit(130);\n }\n return (input as string).trim() || undefined; // blank → random\n}\n\n/** Print the last few lines of a connector logfile (shown when it crashes). */\nfunction showLogTail(logFile: string): void {\n try {\n const tail = readFileSync(logFile, \"utf8\").trim().split(\"\\n\").slice(-8).join(\"\\n\");\n if (tail) say.dim(tail);\n } catch {\n /* no log yet */\n }\n}\n\nasync function runUp(portArg: string, opts: UpOptions): Promise<void> {\n const port = parsePort(portArg);\n const creds = await ensureAuth();\n const cf = resolveCf();\n const bin = await ensureCloudflared();\n\n if (process.stdout.isTTY) clack.intro(\"cloudtunnel\");\n const domain = await resolveDomain(cf, opts, creds);\n const subdomain = await resolveSubdomain(opts);\n\n const spin = clack.spinner();\n let spinnerActive = true;\n const stopSpin = (msg: string) => {\n if (spinnerActive) {\n spinnerActive = false;\n spin.stop(msg);\n }\n };\n\n spin.start(\"Creating tunnel…\");\n const result = await createTunnelSubdomain(cf, {\n port, proto: opts.proto, name: subdomain, zone: domain,\n hostname: opts.hostname, defaultZone: creds.defaultZone, force: opts.force,\n }).catch((err: unknown) => {\n stopSpin(\"Failed to create the tunnel\");\n throw err;\n });\n const fqdn = result.host.hostname;\n const logFile = join(logDir, `${result.host.subdomain}.log`);\n const target = `${opts.proto}://localhost:${port}`;\n\n if (opts.detach) {\n const started = startConnector({ bin, token: result.token, detach: true, logFile });\n await patchEntry(fqdn, { pid: started.pid, bootId: currentBootId(), logFile });\n stopSpin(\"Started in the background\");\n clack.note(formatRoute(fqdn, target), `pid ${started.pid}`);\n if (process.stdout.isTTY) clack.outro(`Stop it with: cloudtunnel down ${result.host.subdomain}`);\n return;\n }\n\n spin.message(\"Connecting to the Cloudflare edge…\");\n const controller = new AbortController();\n // Foreground is up-while-running: any exit (Ctrl-C or a connector crash)\n // releases the tunnel + DNS (2-state model).\n let tornDown = false;\n const teardown = async (exitCode: number): Promise<void> => {\n if (tornDown) return;\n tornDown = true;\n controller.abort();\n stopSpin(\"Stopping…\");\n try {\n await removeTunnelSubdomain(cf, fqdn, { force: true, quiet: true });\n clack.outro(`Stopped · ${fqdn} released`);\n } catch (err) {\n reportError(err);\n } finally {\n process.exit(exitCode);\n }\n };\n\n const started = startConnector({\n bin, token: result.token, detach: false, logFile,\n onExit: (code) => {\n if (!tornDown) {\n stopSpin(\"cloudflared exited\");\n showLogTail(logFile);\n void teardown(code ?? 1);\n }\n },\n });\n await patchEntry(fqdn, { pid: started.pid, bootId: currentBootId(), logFile });\n for (const sig of [\"SIGINT\", \"SIGHUP\", \"SIGTERM\"] as const) {\n process.on(sig, () => void teardown(0));\n }\n\n const health = await waitHealthy(cf, result.tunnelId, { signal: controller.signal });\n if (health === \"healthy\") {\n stopSpin(\"Connected\");\n clack.note(`${formatRoute(fqdn, target)}\\n${dim(\"Ctrl-C stops and releases this subdomain\")}`, \"Live\");\n } else if (health === \"provisioning\") {\n stopSpin(\"Provisioning\");\n say.warn(`${fqdn} is not healthy yet — it should be live shortly.`);\n }\n}\n\nexport function registerUp(program: Command): void {\n program\n .command(\"up\")\n .argument(\"<port>\", \"local port to expose (e.g. 3000)\")\n .description(\"Expose a local port at an HTTPS subdomain (also: `cloudtunnel <port>`)\")\n .option(\"-s, --subdomain <name>\", \"subdomain label (prompted, or random if left blank)\")\n .option(\"-d, --domain <domain>\", \"domain to create the subdomain under (prompted from a list if unset)\")\n .option(\"--name <name>\", \"alias of --subdomain\")\n .option(\"--zone <domain>\", \"alias of --domain\")\n .option(\"--hostname <fqdn>\", \"full hostname override (instead of --subdomain + --domain)\")\n .option(\"--detach\", \"run the connector in the background\")\n .option(\"-f, --force\", \"replace a non-tunnel DNS record occupying the hostname\")\n .option(\"--proto <proto>\", \"local service protocol: http | https\", \"http\")\n .action((port: string, opts: UpOptions) => runUp(port, opts));\n}\n","import { CliError } from \"../ui/errors.js\";\nimport { say } from \"../ui/output.js\";\nimport { getCredentials, type Credentials } from \"./store.js\";\nimport { runLoginFlow } from \"../commands/login.js\";\n\n/**\n * Single auth entry point for every command. Returns credentials if present;\n * on a fresh machine with a TTY it runs onboarding inline and continues, so\n * `cloudtunnel 3000` on a new box just works. Non-TTY (CI) → actionable error.\n */\nexport async function ensureAuth(): Promise<Credentials> {\n try {\n return getCredentials();\n } catch (err) {\n if (err instanceof CliError && process.stdin.isTTY) {\n say.info(\"Welcome to cloudtunnel — let's get you connected to Cloudflare first.\");\n await runLoginFlow();\n return getCredentials();\n }\n throw err;\n }\n}\n","import { execFileSync } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport { chmodSync, existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { CliError } from \"../ui/errors.js\";\nimport { say } from \"../ui/output.js\";\nimport { binDir, ensureDirs } from \"../config/paths.js\";\n\n// Pinned release for reproducible, checksum-verified auto-install. Bump both the\n// version and the checksums together (values from the release's sha256 sums).\nconst PINNED_VERSION = \"2025.1.0\";\nconst RELEASE_BASE = `https://github.com/cloudflare/cloudflared/releases/download/${PINNED_VERSION}`;\n\ninterface Asset { file: string; archive: boolean; sha256: string }\n\n// Fill sha256 from the pinned release before shipping auto-install for a target.\n// Empty string ⇒ fail closed (never run an unverified binary).\nconst ASSETS: Record<string, Asset | undefined> = {\n \"linux-x64\": { file: \"cloudflared-linux-amd64\", archive: false, sha256: \"\" },\n \"linux-arm64\": { file: \"cloudflared-linux-arm64\", archive: false, sha256: \"\" },\n \"darwin-x64\": { file: \"cloudflared-darwin-amd64.tgz\", archive: true, sha256: \"\" },\n \"darwin-arm64\": { file: \"cloudflared-darwin-arm64.tgz\", archive: true, sha256: \"\" },\n \"win32-x64\": { file: \"cloudflared-windows-amd64.exe\", archive: false, sha256: \"\" },\n};\n\nfunction binaryWorks(bin: string): boolean {\n try {\n execFileSync(bin, [\"--version\"], { stdio: \"ignore\" });\n return true;\n } catch {\n return false;\n }\n}\n\nfunction cachedPath(): string {\n return join(binDir, process.platform === \"win32\" ? \"cloudflared.exe\" : \"cloudflared\");\n}\n\n/** True on Alpine/musl, where cloudflared has no prebuilt binary. */\nfunction isMusl(): boolean {\n try {\n return process.platform === \"linux\" && readFileSync(\"/usr/bin/ldd\", \"utf8\").includes(\"musl\");\n } catch {\n return false;\n }\n}\n\n/**\n * Return a runnable `cloudflared` with zero user action: PATH → cached download\n * → verified auto-download. Fails closed (never runs an unverified binary).\n */\nexport async function ensureCloudflared(): Promise<string> {\n if (binaryWorks(\"cloudflared\")) return \"cloudflared\";\n const cached = cachedPath();\n if (existsSync(cached) && binaryWorks(cached)) return cached;\n return downloadCloudflared(cached);\n}\n\nasync function downloadCloudflared(dest: string): Promise<string> {\n if (isMusl()) {\n throw new CliError(\"cloudflared has no musl (Alpine) build.\", {\n hint: \"install it manually: https://github.com/cloudflare/cloudflared/releases\",\n });\n }\n const key = `${process.platform}-${process.arch}`;\n const asset = ASSETS[key];\n if (!asset || !asset.sha256) {\n throw new CliError(`Auto-install unavailable for ${key} (no pinned checksum).`, {\n hint: \"install cloudflared manually: https://github.com/cloudflare/cloudflared/releases\",\n });\n }\n\n say.step(`cloudflared not found — downloading v${PINNED_VERSION} (checksum-verified)…`);\n const res = await fetch(`${RELEASE_BASE}/${asset.file}`);\n if (!res.ok) throw new CliError(`Download failed (HTTP ${res.status}).`);\n const bytes = Buffer.from(await res.arrayBuffer());\n\n const digest = createHash(\"sha256\").update(bytes).digest(\"hex\");\n if (digest !== asset.sha256) {\n throw new CliError(\"cloudflared checksum mismatch — refusing to run the download.\", {\n hint: \"network tampering or an outdated pin; install manually instead\",\n });\n }\n\n ensureDirs();\n const binary = asset.archive ? extractTgz(bytes) : bytes;\n writeFileSync(dest, binary, { mode: 0o755 });\n chmodSync(dest, 0o755);\n if (!binaryWorks(dest)) throw new CliError(\"Downloaded cloudflared is not runnable.\");\n return dest;\n}\n\n/** Extract the single `cloudflared` entry from a .tgz (darwin assets). */\nfunction extractTgz(_bytes: Buffer): Buffer {\n // gunzip + untar of a single-file archive; implemented when a darwin\n // checksum is pinned (dormant until then — see ASSETS).\n throw new CliError(\"darwin .tgz extraction not yet wired.\", {\n hint: \"install cloudflared via `brew install cloudflared`\",\n });\n}\n","import { type ChildProcess, execFileSync, spawn } from \"node:child_process\";\nimport { openSync } from \"node:fs\";\nimport { CliError } from \"../ui/errors.js\";\nimport { isOurConnector, type RegistryEntry } from \"./registry.js\";\n\nexport interface StartOptions {\n bin: string;\n token: string;\n detach: boolean;\n logFile: string;\n /** Foreground only: fired when the connector exits for ANY reason (crash,\n * bad token, or a signal) so the caller can tear down / report. */\n onExit?: (code: number | null) => void;\n}\n\nexport interface StartedConnector {\n pid: number;\n child?: ChildProcess;\n}\n\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\n\n/**\n * Spawn `cloudflared tunnel run`. The token is passed via the TUNNEL_TOKEN env\n * var — NEVER as an argv arg (argv is world-readable via `ps`/proc). Output goes\n * to a 0600 logfile (both foreground and detached) so the CLI can render its own\n * clean status instead of cloudflared's raw logs.\n */\nexport function startConnector(opts: StartOptions): StartedConnector {\n const args = [\"tunnel\", \"run\"];\n const env = { ...process.env, TUNNEL_TOKEN: opts.token };\n const fd = openSync(opts.logFile, \"a\", 0o600);\n const child = spawn(opts.bin, args, { env, detached: opts.detach, stdio: [\"ignore\", fd, fd] });\n if (!child.pid) throw new CliError(\"Failed to start the cloudflared connector.\");\n\n if (opts.detach) {\n child.unref();\n return { pid: child.pid };\n }\n child.on(\"exit\", (code) => opts.onExit?.(code));\n child.on(\"error\", () => opts.onExit?.(1));\n return { pid: child.pid, child };\n}\n\n/**\n * Stop a connector by registry entry. Verifies the pid is still OUR cloudflared\n * (alive, same boot, right cmdline) BEFORE signalling, so a reused pid held by\n * an unrelated process is never killed. Returns true if a stop was issued.\n */\nexport async function stopConnector(entry: RegistryEntry): Promise<boolean> {\n if (!entry.pid || !(await isOurConnector(entry))) return false;\n const pid = entry.pid;\n\n if (process.platform === \"win32\") {\n try {\n execFileSync(\"taskkill\", [\"/pid\", String(pid), \"/T\", \"/F\"], { stdio: \"ignore\" });\n } catch {\n return false;\n }\n return true;\n }\n\n try {\n process.kill(pid, \"SIGTERM\");\n } catch {\n return false;\n }\n await sleep(3000);\n if (await isOurConnector(entry)) {\n try {\n process.kill(pid, \"SIGKILL\");\n } catch {\n // already gone\n }\n }\n return true;\n}\n","import { existsSync, readFileSync, renameSync, writeFileSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport os from \"node:os\";\nimport lockfile from \"proper-lockfile\";\nimport { ensureDirs, registryFile } from \"../config/paths.js\";\n\nexport type EntryState = \"provisioning\" | \"running\" | \"stopped\" | \"orphaned\";\n\nexport interface RegistryEntry {\n subdomain: string;\n zone: string;\n zoneId: string;\n index?: number; // small stable handle shown as `#` in `ls` (target by number)\n tunnelId?: string;\n dnsRecordId?: string;\n port: number;\n proto: \"http\" | \"https\";\n pid?: number;\n bootId?: string;\n logFile?: string;\n createdAt: string;\n state: EntryState;\n}\n\ntype Registry = Record<string, RegistryEntry>;\n\n/** Stable per-boot id so a pid reused after a reboot is never mistaken for ours. */\nexport function currentBootId(): string {\n try {\n return readFileSync(\"/proc/sys/kernel/random/boot_id\", \"utf8\").trim();\n } catch {\n return `uptime-${Math.round(os.uptime())}-${os.hostname()}`;\n }\n}\n\nfunction readRegistry(): Registry {\n try {\n return JSON.parse(readFileSync(registryFile, \"utf8\")) as Registry;\n } catch {\n return {};\n }\n}\n\nfunction writeRegistry(reg: Registry): void {\n ensureDirs();\n const tmp = `${registryFile}.tmp`;\n writeFileSync(tmp, JSON.stringify(reg, null, 2), { mode: 0o600 });\n renameSync(tmp, registryFile); // atomic on the same filesystem\n}\n\n/** Lock-guarded read-modify-write (prevents lost updates across concurrent runs). */\nexport async function mutateRegistry<T>(fn: (reg: Registry) => T): Promise<T> {\n ensureDirs();\n if (!existsSync(registryFile)) writeFileSync(registryFile, \"{}\", { mode: 0o600 });\n const release = await lockfile.lock(registryFile, { retries: { retries: 10, minTimeout: 50 } });\n try {\n const reg = readRegistry();\n const result = fn(reg);\n writeRegistry(reg);\n return result;\n } finally {\n await release();\n }\n}\n\nexport function listEntries(): RegistryEntry[] {\n return Object.values(readRegistry());\n}\n\nexport function getEntry(fqdn: string): RegistryEntry | undefined {\n return readRegistry()[fqdn];\n}\n\nexport function upsertEntry(fqdn: string, patch: Partial<RegistryEntry> & Pick<RegistryEntry, \"subdomain\" | \"zone\" | \"zoneId\" | \"port\" | \"proto\">): Promise<void> {\n return mutateRegistry((reg) => {\n const prev = reg[fqdn];\n reg[fqdn] = {\n createdAt: prev?.createdAt ?? new Date().toISOString(),\n index: prev?.index ?? nextIndex(reg),\n state: \"provisioning\",\n ...prev,\n ...patch,\n };\n });\n}\n\n/** Smallest positive integer not currently used as an entry index (reused when\n * an entry is removed) — the friendly `#` handle shown in `ls`. */\nfunction nextIndex(reg: Registry): number {\n const used = new Set(\n Object.values(reg)\n .map((e) => e.index)\n .filter((n): n is number => typeof n === \"number\"),\n );\n let i = 1;\n while (used.has(i)) i++;\n return i;\n}\n\n/** Merge changed fields onto an existing entry under the lock (no stale\n * full-snapshot read outside the lock — avoids lost updates). No-op if absent. */\nexport function patchEntry(fqdn: string, patch: Partial<RegistryEntry>): Promise<void> {\n return mutateRegistry((reg) => {\n const prev = reg[fqdn];\n if (prev) reg[fqdn] = { ...prev, ...patch };\n });\n}\n\nexport function removeEntry(fqdn: string): Promise<void> {\n return mutateRegistry((reg) => {\n delete reg[fqdn];\n });\n}\n\nfunction pidAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\n/** Verify a pid is still OUR cloudflared: alive, same boot, and (Linux) its\n * cmdline is cloudflared — so we never signal a reused pid. */\nexport async function isOurConnector(entry: RegistryEntry): Promise<boolean> {\n if (!entry.pid || entry.bootId !== currentBootId()) return false;\n if (!pidAlive(entry.pid)) return false;\n if (process.platform === \"linux\") {\n try {\n const cmdline = await readFile(`/proc/${entry.pid}/cmdline`, \"utf8\");\n return cmdline.includes(\"cloudflared\");\n } catch {\n return false;\n }\n }\n return true; // non-Linux: bootId + liveness (best effort)\n}\n\n/** Mark entries whose connector is no longer alive as `stopped`. */\nexport async function reconcile(): Promise<RegistryEntry[]> {\n const entries = listEntries();\n for (const entry of entries) {\n if (entry.state === \"running\" && !(await isOurConnector(entry))) {\n const fqdn = `${entry.subdomain}.${entry.zone}`;\n await mutateRegistry((reg) => {\n const e = reg[fqdn];\n if (e) {\n e.state = \"stopped\";\n delete e.pid;\n }\n });\n }\n }\n return listEntries();\n}\n","import { cfPaginate, cfRequest, type Cf } from \"./client.js\";\nimport type { Connection, IngressRule, Tunnel } from \"./types.js\";\nimport { CliError } from \"../ui/errors.js\";\n\n/** Tunnels created by cloudtunnel carry this name prefix (ownership marker). */\nexport const MANAGED_TUNNEL_PREFIX = \"ct-\";\n\nexport function isManagedTunnel(tunnel: Tunnel): boolean {\n return tunnel.name.startsWith(MANAGED_TUNNEL_PREFIX);\n}\n\nexport async function createTunnel(cf: Cf, name: string): Promise<Tunnel> {\n const env = await cfRequest<Tunnel>(cf.token, \"POST\", `/accounts/${cf.accountId}/cfd_tunnel`, {\n name,\n config_src: \"cloudflare\",\n });\n return env.result;\n}\n\nexport function listTunnels(cf: Cf): Promise<Tunnel[]> {\n return cfPaginate<Tunnel>(cf.token, `/accounts/${cf.accountId}/cfd_tunnel?is_deleted=false`);\n}\n\nexport async function getTunnel(cf: Cf, id: string): Promise<Tunnel> {\n return (await cfRequest<Tunnel>(cf.token, \"GET\", `/accounts/${cf.accountId}/cfd_tunnel/${id}`)).result;\n}\n\nexport async function deleteTunnel(cf: Cf, id: string): Promise<void> {\n await cfRequest<unknown>(cf.token, \"DELETE\", `/accounts/${cf.accountId}/cfd_tunnel/${id}`);\n}\n\n/** Force-disconnect a tunnel's (possibly stale) connectors so it can be deleted. */\nexport async function cleanupConnections(cf: Cf, id: string): Promise<void> {\n await cfRequest<unknown>(cf.token, \"DELETE\", `/accounts/${cf.accountId}/cfd_tunnel/${id}/connections`);\n}\n\n/** Delete a tunnel; if Cloudflare refuses because it still has active\n * connections (a connector died but the edge hasn't reaped it yet), clean the\n * connections up and retry once. */\nexport async function deleteTunnelWithConnections(cf: Cf, id: string): Promise<void> {\n try {\n await deleteTunnel(cf, id);\n } catch (err) {\n if (err instanceof CliError && /active connections/i.test(err.message)) {\n await cleanupConnections(cf, id);\n await deleteTunnel(cf, id);\n } else {\n throw err;\n }\n }\n}\n\n/** The connector token (encodes tunnelId + secret) passed to `cloudflared`. */\nexport async function getTunnelToken(cf: Cf, id: string): Promise<string> {\n return (await cfRequest<string>(cf.token, \"GET\", `/accounts/${cf.accountId}/cfd_tunnel/${id}/token`)).result;\n}\n\n/** Full-replace ingress config (safe: one hostname + catch-all per tunnel). */\nexport async function putIngress(cf: Cf, id: string, ingress: IngressRule[]): Promise<void> {\n await cfRequest<unknown>(cf.token, \"PUT\", `/accounts/${cf.accountId}/cfd_tunnel/${id}/configurations`, {\n config: { ingress },\n });\n}\n\n/** Active connector instances (≥1 ⇒ tunnel is serving). */\nexport async function getConnections(cf: Cf, id: string): Promise<Connection[]> {\n const env = await cfRequest<Connection[]>(\n cf.token,\n \"GET\",\n `/accounts/${cf.accountId}/cfd_tunnel/${id}/connections`,\n );\n return env.result ?? [];\n}\n","import { getConnections } from \"../cloudflare/tunnels.js\";\nimport type { Cf } from \"../cloudflare/client.js\";\n\nexport type HealthResult = \"healthy\" | \"provisioning\" | \"dead\";\n\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\n\n/**\n * Poll the tunnel's connections until it's serving. `signal` is fired by the\n * caller when the connector process exits, so a dead connector returns `dead`\n * immediately instead of waiting out the timeout. `provisioning` is only\n * returned if the process is still alive at the deadline (never a false\n * \"healthy\"). Note: this measures connector↔edge, not local-origin, health.\n */\nexport async function waitHealthy(\n cf: Cf,\n tunnelId: string,\n opts: { signal?: AbortSignal; timeoutMs?: number } = {},\n): Promise<HealthResult> {\n const deadline = Date.now() + (opts.timeoutMs ?? 30_000);\n while (Date.now() < deadline) {\n if (opts.signal?.aborted) return \"dead\";\n try {\n const connections = await getConnections(cf, tunnelId);\n if (connections.length > 0) return \"healthy\";\n } catch {\n // transient API error — keep polling until the deadline\n }\n await sleep(2000);\n }\n return opts.signal?.aborted ? \"dead\" : \"provisioning\";\n}\n","import { randomInt } from \"node:crypto\";\nimport type { Cf } from \"../cloudflare/client.js\";\nimport { resolveZone } from \"../cloudflare/zones.js\";\nimport {\n MANAGED_TUNNEL_PREFIX,\n createTunnel,\n deleteTunnel,\n deleteTunnelWithConnections,\n getTunnel,\n getTunnelToken,\n isManagedTunnel,\n putIngress,\n} from \"../cloudflare/tunnels.js\";\nimport { createCname, deleteDnsRecord, findCname } from \"../cloudflare/dns.js\";\nimport type { DnsRecord } from \"../cloudflare/types.js\";\nimport { buildIngress } from \"./ingress.js\";\nimport { resolveHostSpec, type HostSpec } from \"./slug.js\";\nimport { currentBootId, patchEntry, removeEntry, upsertEntry } from \"../connector/registry.js\";\nimport { CliError } from \"../ui/errors.js\";\nimport { say } from \"../ui/output.js\";\n\nexport interface CreateOptions {\n port: number;\n proto: \"http\" | \"https\";\n name?: string;\n zone?: string;\n hostname?: string;\n defaultZone?: string;\n force?: boolean;\n}\n\nexport interface CreateResult {\n host: HostSpec;\n tunnelId: string;\n token: string;\n}\n\nconst tunnelIdFromCname = (content: string): string => content.replace(/\\.cfargotunnel\\.com\\.?$/, \"\");\n\n/**\n * Create a tunnel subdomain transactionally (idempotent). Any leftover tunnel\n * record for the same hostname is cleaned up first, so re-running `up` never\n * conflicts. A `provisioning` registry entry is written BEFORE any Cloudflare\n * resource; on failure everything is unwound in reverse and the original error\n * is surfaced.\n */\nexport async function createTunnelSubdomain(cf: Cf, opts: CreateOptions): Promise<CreateResult> {\n const host = resolveHostSpec(opts, opts.defaultZone);\n const zone = await resolveZone(cf.token, host.zone);\n\n const existing = await findCname(cf.token, zone.id, host.hostname);\n if (existing) {\n // A leftover tunnel record → clean it and recreate. A non-tunnel DNS record\n // (A record, ordinary CNAME) → refuse unless --force, to avoid clobbering\n // unrelated DNS the user owns.\n const isTunnelRecord = existing.content.endsWith(\".cfargotunnel.com\");\n if (!isTunnelRecord && !opts.force) {\n throw new CliError(`${host.hostname} is taken by a non-tunnel DNS record.`, {\n hint: \"pick another --subdomain/--hostname, or pass -f/--force to replace it\",\n });\n }\n await releaseHostname(cf, zone.id, existing);\n }\n\n // Track provisioning BEFORE creating anything irreversible.\n await upsertEntry(host.hostname, {\n subdomain: host.subdomain, zone: host.zone, zoneId: zone.id,\n port: opts.port, proto: opts.proto, state: \"provisioning\",\n });\n\n let tunnelId: string | undefined;\n let dnsRecordId: string | undefined;\n try {\n const suffix = randomInt(0x10000).toString(16).padStart(4, \"0\");\n const tunnel = await createTunnel(cf, `${MANAGED_TUNNEL_PREFIX}${host.subdomain}-${suffix}`);\n tunnelId = tunnel.id;\n const token = await getTunnelToken(cf, tunnelId);\n await putIngress(cf, tunnelId, buildIngress({ hostname: host.hostname, port: opts.port, proto: opts.proto }));\n const record = await createCname(cf.token, zone.id, host.hostname, tunnelId);\n dnsRecordId = record.id;\n await recordRunning(host, zone.id, tunnelId, dnsRecordId, opts);\n return { host, tunnelId, token };\n } catch (err) {\n const clean = await rollback(cf, zone.id, tunnelId, dnsRecordId, host.hostname);\n if (clean) await removeEntry(host.hostname);\n else await patchEntry(host.hostname, { state: \"orphaned\" });\n throw err;\n }\n}\n\nasync function recordRunning(host: HostSpec, zoneId: string, tunnelId: string, dnsRecordId: string, opts: CreateOptions): Promise<void> {\n await upsertEntry(host.hostname, {\n subdomain: host.subdomain, zone: host.zone, zoneId,\n tunnelId, dnsRecordId, port: opts.port, proto: opts.proto,\n bootId: currentBootId(), state: \"running\",\n });\n}\n\n/** Free a hostname before recreating: delete its DNS record, and if it pointed\n * at a cloudtunnel-managed tunnel, delete that tunnel too (cleaning up any\n * lingering connections). A foreign tunnel is left alone — we only free the name. */\nasync function releaseHostname(cf: Cf, zoneId: string, record: DnsRecord): Promise<void> {\n if (record.content.endsWith(\".cfargotunnel.com\")) {\n const oldTunnelId = tunnelIdFromCname(record.content);\n try {\n const tunnel = await getTunnel(cf, oldTunnelId);\n if (isManagedTunnel(tunnel)) await deleteTunnelWithConnections(cf, oldTunnelId);\n } catch {\n /* tunnel already gone or not accessible — freeing the DNS name is enough */\n }\n }\n await deleteDnsRecord(cf.token, zoneId, record.id);\n}\n\n/** Unwind created resources in reverse. Never masks the original error; if a\n * step fails, report the leaked id and return false so the caller marks the\n * entry `orphaned`. */\nasync function rollback(cf: Cf, zoneId: string, tunnelId?: string, dnsRecordId?: string, hostname?: string): Promise<boolean> {\n let clean = true;\n if (dnsRecordId) {\n try { await deleteDnsRecord(cf.token, zoneId, dnsRecordId); }\n catch { clean = false; say.warn(`Left a DNS record behind for ${hostname} (${dnsRecordId}).`); }\n }\n if (tunnelId) {\n try { await deleteTunnel(cf, tunnelId); }\n catch { clean = false; say.warn(`Left tunnel ${tunnelId} behind — remove it with \\`cloudtunnel down ${hostname}\\`.`); }\n }\n return clean;\n}\n","import type { IngressRule } from \"../cloudflare/types.js\";\n\n/**\n * Build the ingress config for a single-hostname tunnel. The mandatory\n * catch-all `http_status:404` rule must come last (Cloudflare rejects configs\n * without it). One-tunnel-per-subdomain keeps this a fixed two-rule list, so\n * the full-replace PUT is always safe (no merge with other hostnames).\n */\nexport function buildIngress(opts: {\n hostname: string;\n port: number;\n proto: \"http\" | \"https\";\n}): IngressRule[] {\n return [\n { hostname: opts.hostname, service: `${opts.proto}://localhost:${opts.port}` },\n { service: \"http_status:404\" },\n ];\n}\n","import { randomInt } from \"node:crypto\";\nimport { CliError } from \"../ui/errors.js\";\n\nconst ADJECTIVES = [\n \"brave\", \"calm\", \"clever\", \"eager\", \"gentle\", \"happy\", \"jolly\", \"kind\",\n \"lively\", \"mighty\", \"nimble\", \"proud\", \"quick\", \"royal\", \"swift\", \"witty\",\n];\nconst NOUNS = [\n \"otter\", \"falcon\", \"maple\", \"comet\", \"harbor\", \"lynx\", \"willow\", \"cedar\",\n \"raven\", \"meadow\", \"pixel\", \"quartz\", \"river\", \"sparrow\", \"tiger\", \"walnut\",\n];\n\nconst pick = <T>(arr: T[]): T => arr[randomInt(arr.length)]!;\n\n/** A friendly random subdomain, e.g. `brave-otter-1a2b` (the default when unnamed). */\nexport function randomSlug(): string {\n const suffix = randomInt(0x10000).toString(16).padStart(4, \"0\");\n return `${pick(ADJECTIVES)}-${pick(NOUNS)}-${suffix}`;\n}\n\nexport interface HostSpec {\n subdomain: string;\n zone: string;\n hostname: string;\n}\n\n/**\n * Resolve the target hostname from flags. Precedence: --hostname > --name+zone >\n * random-slug+zone. Zone comes from --zone or the saved default; missing zone is\n * an actionable error. (--hostname assumes `label.zone`; deeper subdomains need\n * the zone to be an actual Cloudflare zone.)\n */\nexport function resolveHostSpec(\n opts: { name?: string; zone?: string; hostname?: string },\n defaultZone?: string,\n): HostSpec {\n if (opts.hostname) {\n const dot = opts.hostname.indexOf(\".\");\n if (dot <= 0) throw new CliError(`Invalid hostname: ${opts.hostname}`);\n return {\n subdomain: opts.hostname.slice(0, dot),\n zone: opts.hostname.slice(dot + 1),\n hostname: opts.hostname,\n };\n }\n const zone = opts.zone ?? defaultZone;\n if (!zone) {\n throw new CliError(\"No zone specified and no default zone set.\", {\n hint: \"pass --zone <domain>, or run `cloudtunnel login --zone <domain>`\",\n });\n }\n const subdomain = opts.name ?? randomSlug();\n return { subdomain, zone, hostname: `${subdomain}.${zone}` };\n}\n","import type { Cf } from \"../cloudflare/client.js\";\nimport { resolveZone } from \"../cloudflare/zones.js\";\nimport { deleteTunnelWithConnections, getTunnel, isManagedTunnel, listTunnels } from \"../cloudflare/tunnels.js\";\nimport { deleteDnsRecord, findCname, isManagedDns } from \"../cloudflare/dns.js\";\nimport type { Tunnel } from \"../cloudflare/types.js\";\nimport { CliError } from \"../ui/errors.js\";\nimport { say } from \"../ui/output.js\";\nimport { getEntry, listEntries, reconcile, removeEntry, type RegistryEntry } from \"../connector/registry.js\";\nimport { stopConnector } from \"../connector/process.js\";\n\nconst tunnelIdFromCname = (content: string): string => content.replace(/\\.cfargotunnel\\.com\\.?$/, \"\");\nconst isNotFound = (err: unknown): boolean => err instanceof CliError && err.status === 404;\nconst zoneFromFqdn = (fqdn: string): string => fqdn.slice(fqdn.indexOf(\".\") + 1);\n\n/** Resolve a target to its registry entry / fqdn. Accepts a full hostname, the\n * `#` number, a subdomain name, or a tunnel-id prefix (all shown in `ls`).\n * Refuses an ambiguous match. */\nexport function resolveTarget(target: string): { fqdn: string; entry?: RegistryEntry } {\n if (target.includes(\".\")) return { fqdn: target, entry: getEntry(target) };\n const entries = listEntries();\n if (/^\\d+$/.test(target)) {\n const byIndex = entries.find((e) => e.index === Number(target));\n if (byIndex) return { fqdn: `${byIndex.subdomain}.${byIndex.zone}`, entry: byIndex };\n }\n const byId = entries.filter((e) => e.tunnelId?.startsWith(target));\n const matches = byId.length > 0 ? byId : entries.filter((e) => e.subdomain === target);\n if (matches.length > 1) {\n throw new CliError(`\"${target}\" matches multiple subdomains.`, {\n hint: `use a full hostname or a longer id: ${matches.map((m) => `${m.subdomain}.${m.zone}`).join(\", \")}`,\n });\n }\n const entry = matches[0];\n if (!entry) {\n throw new CliError(`No tracked subdomain matching \"${target}\".`, { hint: \"see `cloudtunnel ls` for the #, name, or id\" });\n }\n return { fqdn: `${entry.subdomain}.${entry.zone}`, entry };\n}\n\nexport interface RemoveOptions { force?: boolean; dryRun?: boolean; quiet?: boolean }\n\n/** Release a subdomain: stop the connector, then delete the tunnel + DNS on\n * Cloudflare. Re-verifies fresh state (cached ids are hints), ownership-gates\n * unmanaged resources, and tolerates already-deleted parts. */\nexport async function removeTunnelSubdomain(cf: Cf, target: string, opts: RemoveOptions = {}): Promise<void> {\n const { fqdn, entry } = resolveTarget(target);\n if (!entry && !opts.force) {\n throw new CliError(`${fqdn} is not managed by cloudtunnel.`, { hint: \"pass --force to release it anyway\" });\n }\n const zoneId = entry?.zoneId ?? (await resolveZone(cf.token, zoneFromFqdn(fqdn))).id;\n\n const record = await findCname(cf.token, zoneId, fqdn); // fresh, authoritative\n if (record && !isManagedDns(record) && !opts.force) {\n throw new CliError(`${fqdn} points to a record not managed by cloudtunnel.`, { hint: \"pass --force to release it\" });\n }\n const tunnelId = record ? tunnelIdFromCname(record.content) : entry?.tunnelId;\n\n if (opts.dryRun) {\n say.info(`Would release: tunnel ${tunnelId ?? \"(none)\"}${record ? `, DNS ${record.id}` : \"\"}`);\n return;\n }\n\n if (entry) await stopConnector(entry);\n if (tunnelId) {\n let tunnel: Tunnel | undefined;\n try {\n tunnel = await getTunnel(cf, tunnelId);\n } catch (err) {\n if (!isNotFound(err)) throw err; // transient error → don't silently orphan\n }\n if (tunnel && !isManagedTunnel(tunnel) && !opts.force) {\n throw new CliError(`Tunnel ${tunnelId} is not managed by cloudtunnel.`, { hint: \"pass --force\" });\n }\n if (tunnel) {\n try {\n await deleteTunnelWithConnections(cf, tunnelId);\n } catch (err) {\n if (!isNotFound(err)) throw err;\n }\n }\n }\n if (record) {\n try {\n await deleteDnsRecord(cf.token, zoneId, record.id);\n } catch (err) {\n if (!isNotFound(err)) throw err;\n }\n }\n await removeEntry(fqdn);\n if (!opts.quiet) say.ok(`Released ${fqdn}`);\n}\n\nexport interface LsRow { num: string; hostname: string; port: string; state: string; pid: string; managed: boolean }\n\n/** Reconcile + list tracked subdomains (`#`, target, up/down, connector pid).\n * `all` also scans every zone for cfargotunnel CNAMEs created outside cloudtunnel. */\nexport async function listAll(cf: Cf, opts: { all?: boolean } = {}): Promise<LsRow[]> {\n const entries = await reconcile();\n const tunnels = new Map((await listTunnels(cf)).map((t) => [t.id, t]));\n const rows: LsRow[] = entries.map((e) => {\n const gone = e.tunnelId ? !tunnels.has(e.tunnelId) : false;\n return {\n num: e.index ? String(e.index) : \"-\",\n hostname: `${e.subdomain}.${e.zone}`,\n port: `${e.proto}://localhost:${e.port}`,\n state: !gone && e.state === \"running\" ? \"up\" : \"down\",\n pid: e.state === \"running\" && e.pid ? String(e.pid) : \"-\",\n managed: true,\n };\n });\n if (opts.all) {\n const { listCargoCnames } = await import(\"../cloudflare/dns.js\");\n const { listZones } = await import(\"../cloudflare/zones.js\");\n const tracked = new Set(entries.map((e) => `${e.subdomain}.${e.zone}`));\n for (const zone of await listZones(cf.token)) {\n for (const rec of await listCargoCnames(cf.token, zone.id)) {\n if (!tracked.has(rec.name)) {\n rows.push({ num: \"-\", hostname: rec.name, port: \"-\", state: \"unmanaged\", pid: \"-\", managed: false });\n }\n }\n }\n }\n return rows;\n}\n","import type { Command } from \"commander\";\nimport { printTable, say } from \"../ui/output.js\";\nimport { ensureAuth } from \"../config/ensure-auth.js\";\nimport { resolveCf } from \"../cloudflare/client.js\";\nimport { listAll } from \"../core/orchestrator-manage.js\";\n\nexport function registerLs(program: Command): void {\n program\n .command(\"ls\")\n .alias(\"ps\")\n .description(\"List tunnel subdomains (managed by default; --all scans the whole account)\")\n .option(\"--all\", \"scan every zone in the account (slower; shows unmanaged tunnels too)\")\n .action(async (opts: { all?: boolean }) => {\n await ensureAuth();\n const cf = resolveCf();\n const rows = await listAll(cf, { all: opts.all });\n if (rows.length === 0) {\n say.info(\"No tunnel subdomains yet. Create one: `cloudtunnel 3000`\");\n return;\n }\n printTable(\n [\"#\", \"SUBDOMAIN\", \"TARGET\", \"STATE\", \"PID\"],\n rows.map((r) => [r.num, r.hostname, r.port, r.state, r.pid]),\n );\n });\n}\n","import type { Command } from \"commander\";\nimport { CliError } from \"../ui/errors.js\";\nimport { say } from \"../ui/output.js\";\nimport { ensureAuth } from \"../config/ensure-auth.js\";\nimport { resolveCf } from \"../cloudflare/client.js\";\nimport { listEntries } from \"../connector/registry.js\";\nimport { removeTunnelSubdomain } from \"../core/orchestrator-manage.js\";\n\ninterface DownOptions { all?: boolean; force?: boolean; dryRun?: boolean }\n\nexport function registerDown(program: Command): void {\n program\n .command(\"down\")\n .aliases([\"rm\", \"remove\", \"delete\", \"stop\"])\n .argument(\"[target]\", \"subdomain name / hostname / id / # to release (omit with --all)\")\n .description(\"Stop and release a subdomain — removes the tunnel + DNS on Cloudflare\")\n .option(\"--all\", \"release every tracked subdomain\")\n .option(\"-f, --force\", \"release even a resource not created by cloudtunnel\")\n .option(\"--dry-run\", \"show what would be released without doing it\")\n .action(async (target: string | undefined, opts: DownOptions) => {\n await ensureAuth();\n const cf = resolveCf();\n\n if (opts.all) {\n const entries = listEntries();\n if (entries.length === 0) {\n say.info(\"Nothing to release.\");\n return;\n }\n for (const e of entries) {\n try {\n await removeTunnelSubdomain(cf, `${e.subdomain}.${e.zone}`, { force: opts.force, dryRun: opts.dryRun });\n } catch (err) {\n say.warn(`Could not release ${e.subdomain}.${e.zone}: ${(err as Error).message}`);\n }\n }\n return;\n }\n\n if (!target) throw new CliError(\"Pass a subdomain (name / id / #) or --all.\");\n await removeTunnelSubdomain(cf, target, { force: opts.force, dryRun: opts.dryRun });\n });\n}\n","import type { Command } from \"commander\";\nimport { printTable, say } from \"../ui/output.js\";\nimport { ensureAuth } from \"../config/ensure-auth.js\";\nimport { resolveCf } from \"../cloudflare/client.js\";\nimport { listZones } from \"../cloudflare/zones.js\";\n\nexport function registerZones(program: Command): void {\n program\n .command(\"zones\")\n .description(\"List the zones (domains) available in your Cloudflare account\")\n .action(async () => {\n await ensureAuth();\n const cf = resolveCf();\n const zones = await listZones(cf.token);\n if (zones.length === 0) {\n say.info(\"No zones in this account.\");\n return;\n }\n printTable(\n [\"ZONE\", \"STATUS\", \"ID\"],\n zones.map((z) => [z.name, z.status ?? \"-\", z.id]),\n );\n });\n}\n","import { readFileSync, writeFileSync } from \"node:fs\";\nimport { ensureDirs, profilesFile } from \"../config/paths.js\";\nimport { CliError } from \"../ui/errors.js\";\n\n/** One service in a profile — e.g. `{ name: \"api\", port: 3000 }`. */\nexport interface ProfileService {\n name: string;\n port: number;\n proto: \"http\" | \"https\";\n domain?: string; // overrides the profile/default domain\n}\n\nexport interface Profile {\n services: ProfileService[];\n domain?: string; // default domain for the whole profile\n}\n\ntype Profiles = Record<string, Profile>;\n\nfunction readProfiles(): Profiles {\n try {\n return JSON.parse(readFileSync(profilesFile, \"utf8\")) as Profiles;\n } catch {\n return {};\n }\n}\n\nfunction writeProfiles(profiles: Profiles): void {\n ensureDirs();\n writeFileSync(profilesFile, JSON.stringify(profiles, null, 2), { mode: 0o600 });\n}\n\nexport function listProfiles(): Array<{ name: string; profile: Profile }> {\n return Object.entries(readProfiles()).map(([name, profile]) => ({ name, profile }));\n}\n\nexport function getProfile(name: string): Profile {\n const profile = readProfiles()[name];\n if (!profile) {\n throw new CliError(`No profile named \"${name}\".`, { hint: \"list them with `cloudtunnel profiles`\" });\n }\n return profile;\n}\n\nexport function saveProfile(name: string, profile: Profile): void {\n const profiles = readProfiles();\n profiles[name] = profile;\n writeProfiles(profiles);\n}\n\nexport function removeProfile(name: string): void {\n const profiles = readProfiles();\n if (!profiles[name]) throw new CliError(`No profile named \"${name}\".`);\n delete profiles[name];\n writeProfiles(profiles);\n}\n\n/**\n * Parse a `name:port[:proto]` service spec, e.g. `api:3000` or `web:5173:https`.\n */\nexport function parseServiceSpec(spec: string): ProfileService {\n const [name, portStr, proto] = spec.split(\":\");\n const port = Number(portStr);\n if (!name || !Number.isInteger(port) || port < 1 || port > 65535) {\n throw new CliError(`Invalid service \"${spec}\".`, { hint: \"use name:port, e.g. api:3000 or web:5173:https\" });\n }\n if (proto && proto !== \"http\" && proto !== \"https\") {\n throw new CliError(`Invalid protocol \"${proto}\" in \"${spec}\".`, { hint: \"proto must be http or https\" });\n }\n return { name, port, proto: (proto as \"http\" | \"https\") ?? \"http\" };\n}\n","import type { Command } from \"commander\";\nimport { CliError } from \"../ui/errors.js\";\nimport { say } from \"../ui/output.js\";\nimport { listEntries } from \"../connector/registry.js\";\nimport { parseServiceSpec, saveProfile, type ProfileService } from \"../core/profiles.js\";\n\ninterface SaveOptions { fromRunning?: boolean; domain?: string }\n\nexport function registerSave(program: Command): void {\n program\n .command(\"save\")\n .argument(\"<profile>\", \"profile name, e.g. mb\")\n .argument(\"[services...]\", \"services as name:port[:proto], e.g. api:3000 web:5173\")\n .description(\"Save a group of services as a profile you can `run` together\")\n .option(\"--from-running\", \"snapshot the currently tracked tunnels instead of listing services\")\n .option(\"-d, --domain <domain>\", \"default domain for this profile\")\n .action((profile: string, specs: string[], opts: SaveOptions) => {\n let services: ProfileService[];\n if (opts.fromRunning) {\n const entries = listEntries().filter((e) => e.tunnelId);\n if (entries.length === 0) {\n throw new CliError(\"No tunnels to snapshot.\", { hint: \"start some with `cloudtunnel up`, or pass services like api:3000\" });\n }\n services = entries.map((e) => ({ name: e.subdomain, port: e.port, proto: e.proto, domain: e.zone }));\n } else {\n if (specs.length === 0) {\n throw new CliError(\"No services given.\", { hint: \"e.g. `cloudtunnel save mb api:3000 web:5173`\" });\n }\n services = specs.map(parseServiceSpec);\n }\n saveProfile(profile, { services, domain: opts.domain });\n say.ok(`Saved profile \"${profile}\" (${services.length} service${services.length === 1 ? \"\" : \"s\"}). Run it: cloudtunnel run ${profile}`);\n });\n}\n","import type { Command } from \"commander\";\nimport { join } from \"node:path\";\nimport * as clack from \"@clack/prompts\";\nimport { reportError } from \"../ui/errors.js\";\nimport { dim, formatRoute, say } from \"../ui/output.js\";\nimport { ensureAuth } from \"../config/ensure-auth.js\";\nimport { resolveCf } from \"../cloudflare/client.js\";\nimport { logDir } from \"../config/paths.js\";\nimport { ensureCloudflared } from \"../connector/binary.js\";\nimport { startConnector } from \"../connector/process.js\";\nimport { waitHealthy, type HealthResult } from \"../connector/health.js\";\nimport { currentBootId, patchEntry } from \"../connector/registry.js\";\nimport { createTunnelSubdomain } from \"../core/orchestrator-create.js\";\nimport { removeTunnelSubdomain } from \"../core/orchestrator-manage.js\";\nimport { getProfile } from \"../core/profiles.js\";\n\n/**\n * Run every service in a saved profile at once (e.g. `cloudtunnel run mb` →\n * backend + frontend live together). Foreground: Ctrl-C releases them all.\n * `--detach`: they keep running until `cloudtunnel down --all`.\n */\ninterface RunOptions { force?: boolean; domain?: string; detach?: boolean }\n\nasync function runProfile(name: string, opts: RunOptions): Promise<void> {\n const creds = await ensureAuth();\n const cf = resolveCf();\n const bin = await ensureCloudflared();\n const profile = getProfile(name);\n\n if (process.stdout.isTTY) clack.intro(`cloudtunnel · profile \"${name}\"`);\n const spin = clack.spinner();\n spin.start(\"Creating tunnels…\");\n\n const started: Array<{ fqdn: string; subdomain: string; tunnelId: string; target: string; pid: number }> = [];\n for (const svc of profile.services) {\n spin.message(`Creating ${svc.name} (:${svc.port})…`);\n const result = await createTunnelSubdomain(cf, {\n port: svc.port, proto: svc.proto, name: svc.name,\n zone: svc.domain ?? opts.domain ?? profile.domain, defaultZone: creds.defaultZone,\n force: opts.force,\n });\n const fqdn = result.host.hostname;\n const logFile = join(logDir, `${result.host.subdomain}.log`);\n const conn = startConnector({\n bin, token: result.token, detach: !!opts.detach, logFile,\n onExit: opts.detach ? undefined : () => say.warn(`Connector for ${fqdn} exited.`),\n });\n await patchEntry(fqdn, { pid: conn.pid, bootId: currentBootId(), logFile });\n started.push({ fqdn, subdomain: result.host.subdomain, tunnelId: result.tunnelId, target: `${svc.proto}://localhost:${svc.port}`, pid: conn.pid });\n }\n\n // Detached: print URLs + pids and exit; the connectors keep running.\n if (opts.detach) {\n spin.stop(`${started.length} service(s) started in the background`);\n const lines = started.map((s) => `${formatRoute(s.fqdn, s.target)} ${dim(`pid ${s.pid}`)}`);\n clack.note(lines.join(\"\\n\"), `profile \"${name}\" — running in background`);\n if (process.stdout.isTTY) clack.outro(\"Stop them with: cloudtunnel down --all\");\n return;\n }\n\n spin.message(\"Connecting to the Cloudflare edge…\");\n const healths = await Promise.all(started.map((s) => waitHealthy(cf, s.tunnelId, { timeoutMs: 30_000 })));\n const live = healths.filter((h: HealthResult) => h === \"healthy\").length;\n spin.stop(`${started.length} service(s) started`);\n\n const lines = started.map((s, i) => `${formatRoute(s.fqdn, s.target)}${healths[i] === \"healthy\" ? \"\" : dim(` (${healths[i]})`)}`);\n clack.note(lines.join(\"\\n\"), `profile \"${name}\" — ${live}/${started.length} live`);\n say.dim(\"Ctrl-C stops and releases all of them.\");\n\n let tornDown = false;\n const teardownAll = async (code: number): Promise<void> => {\n if (tornDown) return;\n tornDown = true;\n try {\n for (const s of started) {\n try {\n await removeTunnelSubdomain(cf, s.fqdn, { force: true, quiet: true });\n } catch {\n /* best-effort release */\n }\n }\n if (process.stdout.isTTY) clack.outro(`Stopped · released ${started.length} subdomain(s)`);\n } catch (err) {\n reportError(err);\n } finally {\n process.exit(code);\n }\n };\n for (const sig of [\"SIGINT\", \"SIGHUP\", \"SIGTERM\"] as const) {\n process.on(sig, () => void teardownAll(0));\n }\n}\n\nexport function registerRun(program: Command): void {\n program\n .command(\"run\")\n .argument(\"<profile>\", \"name of a saved profile (see `cloudtunnel profiles`)\")\n .description(\"Start every service in a saved profile at once\")\n .option(\"-f, --force\", \"take over subdomains already occupied by another record\")\n .option(\"-d, --domain <domain>\", \"override the profile's domain for this run\")\n .option(\"--detach\", \"run all connectors in the background (stop with `cloudtunnel down --all`)\")\n .action((name: string, opts: RunOptions) => runProfile(name, opts));\n}\n","import type { Command } from \"commander\";\nimport { printTable, say } from \"../ui/output.js\";\nimport { listProfiles, removeProfile } from \"../core/profiles.js\";\n\nexport function registerProfiles(program: Command): void {\n program\n .command(\"profiles\")\n .description(\"List saved profiles (or delete one with --rm)\")\n .option(\"--rm <name>\", \"delete a profile\")\n .action((opts: { rm?: string }) => {\n if (opts.rm) {\n removeProfile(opts.rm);\n say.ok(`Deleted profile \"${opts.rm}\".`);\n return;\n }\n const profiles = listProfiles();\n if (profiles.length === 0) {\n say.info(\"No profiles yet. Create one: `cloudtunnel save mb api:3000 web:5173`\");\n return;\n }\n printTable(\n [\"PROFILE\", \"SERVICES\", \"DOMAIN\"],\n profiles.map(({ name, profile }) => [\n name,\n profile.services.map((s) => `${s.name}:${s.port}`).join(\", \"),\n profile.domain ?? \"(default)\",\n ]),\n );\n });\n}\n","import type { Command } from \"commander\";\nimport { closeSync, existsSync, openSync, readFileSync, readSync, statSync, watch } from \"node:fs\";\nimport { CliError } from \"../ui/errors.js\";\nimport { say } from \"../ui/output.js\";\nimport { resolveTarget } from \"../core/orchestrator-manage.js\";\n\ninterface LogsOptions {\n follow?: boolean;\n lines?: string;\n}\n\n/** Print the last `n` lines of a file; return the file's byte size (follow start). */\nfunction printTail(file: string, n: number): number {\n const lines = readFileSync(file, \"utf8\").split(\"\\n\");\n const tail = lines.slice(-n).join(\"\\n\");\n process.stdout.write(tail.endsWith(\"\\n\") ? tail : `${tail}\\n`);\n return statSync(file).size;\n}\n\n/** Tail -f: print appended bytes as the connector writes them. Ctrl-C to stop. */\nfunction follow(file: string, fromPos: number): void {\n let pos = fromPos;\n say.dim(\"— following (Ctrl-C to stop) —\");\n const watcher = watch(file, () => {\n const size = statSync(file).size;\n if (size < pos) {\n pos = 0; // file was truncated/rotated\n return;\n }\n if (size > pos) {\n const fd = openSync(file, \"r\");\n const buf = Buffer.alloc(size - pos);\n readSync(fd, buf, 0, size - pos, pos);\n closeSync(fd);\n process.stdout.write(buf.toString(\"utf8\"));\n pos = size;\n }\n });\n process.on(\"SIGINT\", () => {\n watcher.close();\n process.exit(0);\n });\n}\n\nexport function registerLogs(program: Command): void {\n program\n .command(\"logs\")\n .argument(\"<target>\", \"subdomain name / hostname / id / #\")\n .description(\"Show the connector log for a subdomain (use -f to follow)\")\n .option(\"-f, --follow\", \"keep printing new log lines (like tail -f)\")\n .option(\"-n, --lines <n>\", \"number of lines to show\", \"50\")\n .action((name: string, opts: LogsOptions) => {\n const { fqdn, entry } = resolveTarget(name);\n if (!entry?.logFile || !existsSync(entry.logFile)) {\n throw new CliError(`No logs for ${fqdn} yet.`, { hint: \"start it with `cloudtunnel up` or `cloudtunnel run`\" });\n }\n const n = Math.max(1, Number(opts.lines) || 50);\n const pos = printTail(entry.logFile, n);\n if (opts.follow) follow(entry.logFile, pos);\n });\n}\n"],"mappings":"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","names":["pc","listZones","listZones","join","readFileSync","clack","execFileSync","spawn","existsSync","readFileSync","writeFileSync","readFileSync","writeFileSync","existsSync","spawn","execFileSync","sleep","randomInt","randomInt","tunnelIdFromCname","listZones","readFileSync","join","started","readFileSync","writeFileSync","readFileSync","writeFileSync","join","clack","join","lines","existsSync","openSync","readFileSync","readFileSync","openSync","existsSync","require","pc"]}
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@iamken/cloudtunnel",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Manage Cloudflare Tunnels and subdomains account-wide from the CLI — instant HTTPS sharing on your own domains.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "cloudtunnel": "dist/index.js",
8
- "ct": "dist/index.js"
8
+ "ctun": "dist/index.js"
9
9
  },
10
10
  "files": [
11
11
  "dist"