@iamken/cloudtunnel 0.1.5 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -43,6 +43,7 @@ cloudtunnel 3000 # asks for domain + subdomain, then goes live
43
43
  cloudtunnel 3000 -s api # skip the prompts: api.<your-domain>
44
44
  cloudtunnel 3000 -s api -d foo.io # subdomain + domain
45
45
  cloudtunnel 3000 -s @ # the root domain itself (example.com)
46
+ cloudtunnel 3000 --source 192.168.1.5 # forward to another host/IP (IPv4/IPv6), not localhost
46
47
  cloudtunnel 3000 --detach # run in the background
47
48
  ```
48
49
 
@@ -71,14 +72,15 @@ Run `cloudtunnel 3000` with no flags and it guides you:
71
72
  | Command | What it does |
72
73
  | --- | --- |
73
74
  | `cloudtunnel login` | Authenticate; resolve account + list your domains. `--status` to inspect. |
74
- | `cloudtunnel <port>` · `up` | Bring a subdomain online. `-s/--subdomain`, `-d/--domain`, `--detach`, `-f/--force`, `--proto`. |
75
+ | `cloudtunnel <port>` · `up` | Bring a subdomain online. `-s/--subdomain`, `-d/--domain`, `--source`, `--detach`, `-f/--force`, `--proto`, `--protocol`. |
75
76
  | `cloudtunnel ls` · `ps` | List subdomains — `# · SUBDOMAIN · TARGET · STATE · PID`. `--all` scans the whole account. |
76
77
  | `cloudtunnel down <target>` · `rm` · `stop` | Release a subdomain — stop connector + delete tunnel + DNS. `--all`, `--dry-run`, `-f`. |
77
78
  | `cloudtunnel logs <target>` | Show a connector's log. `-f` to follow, `-n` for line count. |
78
79
  | `cloudtunnel zones` | List the domains in your account. |
79
- | `cloudtunnel save <profile> <svc…>` | Save a group of services. `svc` = `name:port[:proto]`, or `--from-running`. |
80
- | `cloudtunnel run <profile> [--detach]` | Bring up every service in a profile at once. |
81
- | `cloudtunnel profiles [--rm <name>]` | List saved profiles (or delete one). |
80
+ | `cloudtunnel save <profile> <svc…>` | Save a group of services. `svc` = `name:port[:proto][@host]`, `-d/--domain`, `--protocol`, or `--from-running`. |
81
+ | `cloudtunnel run <profile> [--detach]` | Bring up every service in a profile at once. `--protocol` overrides the saved transport. |
82
+ | `cloudtunnel profiles [--rm <name>]` | List saved profiles `PROFILE · SERVICES · DOMAIN · PROTOCOL · SERVICE`. |
83
+ | `cloudtunnel service enable\|disable\|status <profile>` | Register a profile as a systemd boot service (Linux, needs sudo). |
82
84
 
83
85
  > 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`.
84
86
 
@@ -103,6 +105,7 @@ Expose a whole project's services with one command:
103
105
 
104
106
  ```bash
105
107
  cloudtunnel save mb api:3000 web:5173:https # define the group (or: save mb --from-running)
108
+ cloudtunnel save mb api:3000@192.168.1.5 # forward a service to another host/IP
106
109
  cloudtunnel run mb --detach # backend + frontend live in the background
107
110
  cloudtunnel logs api -f # follow one service's log
108
111
  cloudtunnel down --all # release them all
@@ -110,6 +113,37 @@ cloudtunnel down --all # release them all
110
113
 
111
114
  ---
112
115
 
116
+ ## 🚄 Edge transport (`--protocol`)
117
+
118
+ cloudflared connects to Cloudflare over **QUIC** (UDP) by default — fastest, but
119
+ some networks drop idle UDP sessions, which surfaces as intermittent **530/502**
120
+ errors. Force **`http2`** (TCP) there:
121
+
122
+ ```bash
123
+ cloudtunnel up 8080 --protocol http2
124
+ cloudtunnel save mb api:3000 web:5173 --protocol http2 # persist per profile
125
+ cloudtunnel run mb --protocol http2 # or override per run
126
+ ```
127
+
128
+ Values: `auto` (default) · `http2` · `quic`.
129
+
130
+ ---
131
+
132
+ ## 🔁 Run on boot (systemd service)
133
+
134
+ Register a profile as a **system service** so it comes up automatically after a
135
+ reboot (Linux + systemd; installs to `/etc/systemd/system`, so it needs sudo):
136
+
137
+ ```bash
138
+ cloudtunnel service enable mb --protocol http2 # install + enable + start now
139
+ cloudtunnel service status mb # active | enabled | disabled | none
140
+ cloudtunnel service disable mb # stop + disable + remove
141
+ ```
142
+
143
+ The `SERVICE` column in `cloudtunnel profiles` shows each profile's current state.
144
+
145
+ ---
146
+
113
147
  ## 🧭 Two states: up & down
114
148
 
115
149
  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).
package/dist/index.js CHANGED
@@ -218,7 +218,7 @@ function registerLogin(program) {
218
218
 
219
219
  // src/commands/up.ts
220
220
  import { join as join2 } from "path";
221
- import { readFileSync as readFileSync3 } from "fs";
221
+ import { readFileSync as readFileSync4 } from "fs";
222
222
  import * as clack2 from "@clack/prompts";
223
223
 
224
224
  // src/config/ensure-auth.ts
@@ -431,6 +431,7 @@ async function reconcile() {
431
431
  var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
432
432
  function startConnector(opts) {
433
433
  const args = ["tunnel", "run"];
434
+ if (opts.protocol) args.push("--protocol", opts.protocol);
434
435
  const env = { ...process.env, TUNNEL_TOKEN: opts.token };
435
436
  const fd = openSync(opts.logFile, "a", 384);
436
437
  const child = spawn2(opts.bin, args, { env, detached: opts.detach, stdio: ["ignore", fd, fd] });
@@ -542,9 +543,28 @@ async function waitHealthy(cf, tunnelId, opts = {}) {
542
543
  import { randomInt as randomInt2 } from "crypto";
543
544
 
544
545
  // src/core/ingress.ts
546
+ var HOSTNAME_RE = /^[a-zA-Z0-9.-]+$/;
547
+ var IPV6_RE = /^[0-9a-fA-F:.]+$/;
548
+ function validateHost(host) {
549
+ let h = host.trim();
550
+ const bracketed = h.startsWith("[") && h.endsWith("]");
551
+ if (bracketed) h = h.slice(1, -1);
552
+ const isV6 = bracketed || h.includes("::") || (h.match(/:/g)?.length ?? 0) >= 2;
553
+ const ok = h.length > 0 && (isV6 ? IPV6_RE.test(h) : HOSTNAME_RE.test(h));
554
+ if (!ok) {
555
+ throw new CliError(`Invalid host "${host}".`, {
556
+ hint: "use a hostname, IPv4, or IPv6 literal (e.g. 192.168.1.5 or ::1) \u2014 no port, scheme, or path"
557
+ });
558
+ }
559
+ return h;
560
+ }
561
+ function serviceUrl(proto, host, port) {
562
+ const authority = host.includes(":") ? `[${host}]` : host;
563
+ return `${proto}://${authority}:${port}`;
564
+ }
545
565
  function buildIngress(opts) {
546
566
  return [
547
- { hostname: opts.hostname, service: `${opts.proto}://localhost:${opts.port}` },
567
+ { hostname: opts.hostname, service: serviceUrl(opts.proto, opts.host ?? "localhost", opts.port) },
548
568
  { service: "http_status:404" }
549
569
  ];
550
570
  }
@@ -640,6 +660,7 @@ async function createTunnelSubdomain(cf, opts) {
640
660
  zoneId: zone.id,
641
661
  port: opts.port,
642
662
  proto: opts.proto,
663
+ host: opts.host,
643
664
  state: "provisioning"
644
665
  });
645
666
  let tunnelId;
@@ -650,7 +671,7 @@ async function createTunnelSubdomain(cf, opts) {
650
671
  const tunnel = await createTunnel(cf, `${MANAGED_TUNNEL_PREFIX}${label}-${suffix}`);
651
672
  tunnelId = tunnel.id;
652
673
  const token = await getTunnelToken(cf, tunnelId);
653
- await putIngress(cf, tunnelId, buildIngress({ hostname: host.hostname, port: opts.port, proto: opts.proto }));
674
+ await putIngress(cf, tunnelId, buildIngress({ hostname: host.hostname, port: opts.port, proto: opts.proto, host: opts.host }));
654
675
  const record = await createCname(cf.token, zone.id, host.hostname, tunnelId);
655
676
  dnsRecordId = record.id;
656
677
  await recordRunning(host, zone.id, tunnelId, dnsRecordId, opts);
@@ -671,6 +692,7 @@ async function recordRunning(host, zoneId, tunnelId, dnsRecordId, opts) {
671
692
  dnsRecordId,
672
693
  port: opts.port,
673
694
  proto: opts.proto,
695
+ host: opts.host,
674
696
  bootId: currentBootId(),
675
697
  state: "running"
676
698
  });
@@ -783,7 +805,7 @@ async function listAll(cf, opts = {}) {
783
805
  return {
784
806
  num: e.index ? String(e.index) : "-",
785
807
  hostname: `${e.subdomain}.${e.zone}`,
786
- port: `${e.proto}://localhost:${e.port}`,
808
+ port: serviceUrl(e.proto, e.host ?? "localhost", e.port),
787
809
  state: !gone && e.state === "running" ? "up" : "down",
788
810
  pid: e.state === "running" && e.pid ? String(e.pid) : "-",
789
811
  managed: true
@@ -804,6 +826,58 @@ async function listAll(cf, opts = {}) {
804
826
  return rows;
805
827
  }
806
828
 
829
+ // src/core/profiles.ts
830
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
831
+ function parseTransportProtocol(value) {
832
+ if (value === "auto" || value === "http2" || value === "quic") return value;
833
+ throw new CliError(`Invalid protocol "${value}".`, { hint: "use auto, http2, or quic" });
834
+ }
835
+ function readProfiles() {
836
+ try {
837
+ return JSON.parse(readFileSync3(profilesFile, "utf8"));
838
+ } catch {
839
+ return {};
840
+ }
841
+ }
842
+ function writeProfiles(profiles) {
843
+ ensureDirs();
844
+ writeFileSync3(profilesFile, JSON.stringify(profiles, null, 2), { mode: 384 });
845
+ }
846
+ function listProfiles() {
847
+ return Object.entries(readProfiles()).map(([name, profile]) => ({ name, profile }));
848
+ }
849
+ function getProfile(name) {
850
+ const profile = readProfiles()[name];
851
+ if (!profile) {
852
+ throw new CliError(`No profile named "${name}".`, { hint: "list them with `cloudtunnel profiles`" });
853
+ }
854
+ return profile;
855
+ }
856
+ function saveProfile(name, profile) {
857
+ const profiles = readProfiles();
858
+ profiles[name] = profile;
859
+ writeProfiles(profiles);
860
+ }
861
+ function removeProfile(name) {
862
+ const profiles = readProfiles();
863
+ if (!profiles[name]) throw new CliError(`No profile named "${name}".`);
864
+ delete profiles[name];
865
+ writeProfiles(profiles);
866
+ }
867
+ function parseServiceSpec(spec) {
868
+ const at = spec.indexOf("@");
869
+ const host = at >= 0 ? validateHost(spec.slice(at + 1)) : void 0;
870
+ const [name, portStr, proto] = (at >= 0 ? spec.slice(0, at) : spec).split(":");
871
+ const port = Number(portStr);
872
+ if (!name || !Number.isInteger(port) || port < 1 || port > 65535) {
873
+ throw new CliError(`Invalid service "${spec}".`, { hint: "use name:port, e.g. api:3000 or web:5173:https" });
874
+ }
875
+ if (proto && proto !== "http" && proto !== "https") {
876
+ throw new CliError(`Invalid protocol "${proto}" in "${spec}".`, { hint: "proto must be http or https" });
877
+ }
878
+ return { name, port, proto: proto ?? "http", ...host ? { host } : {} };
879
+ }
880
+
807
881
  // src/commands/up.ts
808
882
  function parsePort(port) {
809
883
  const n = Number(port);
@@ -836,13 +910,15 @@ async function resolveSubdomain(opts) {
836
910
  }
837
911
  function showLogTail(logFile) {
838
912
  try {
839
- const tail = readFileSync3(logFile, "utf8").trim().split("\n").slice(-8).join("\n");
913
+ const tail = readFileSync4(logFile, "utf8").trim().split("\n").slice(-8).join("\n");
840
914
  if (tail) say.dim(tail);
841
915
  } catch {
842
916
  }
843
917
  }
844
918
  async function runUp(portArg, opts) {
845
919
  const port = parsePort(portArg);
920
+ const protocol = opts.protocol ? parseTransportProtocol(opts.protocol) : void 0;
921
+ const host = opts.source ? validateHost(opts.source) : void 0;
846
922
  const creds = await ensureAuth();
847
923
  const cf = resolveCf();
848
924
  const bin = await ensureCloudflared();
@@ -855,6 +931,7 @@ async function runUp(portArg, opts) {
855
931
  name: subdomain,
856
932
  zone: domain,
857
933
  hostname: opts.hostname,
934
+ host,
858
935
  defaultZone: creds.defaultZone,
859
936
  force: opts.force,
860
937
  yes: opts.yes
@@ -862,9 +939,9 @@ async function runUp(portArg, opts) {
862
939
  const fqdn = result.host.hostname;
863
940
  const logLabel = result.host.subdomain === "@" ? "root" : result.host.subdomain;
864
941
  const logFile = join2(logDir, `${logLabel}.log`);
865
- const target = `${opts.proto}://localhost:${port}`;
942
+ const target = serviceUrl(opts.proto, host ?? "localhost", port);
866
943
  if (opts.detach) {
867
- const started2 = startConnector({ bin, token: result.token, detach: true, logFile });
944
+ const started2 = startConnector({ bin, token: result.token, detach: true, logFile, protocol });
868
945
  await patchEntry(fqdn, { pid: started2.pid, bootId: currentBootId(), logFile });
869
946
  clack2.note(formatRoute(fqdn, target), `pid ${started2.pid}`);
870
947
  if (process.stdout.isTTY) clack2.outro(`Stop it with: cloudtunnel down ${result.host.subdomain}`);
@@ -900,6 +977,7 @@ async function runUp(portArg, opts) {
900
977
  token: result.token,
901
978
  detach: false,
902
979
  logFile,
980
+ protocol,
903
981
  onExit: (code) => {
904
982
  if (!tornDown) {
905
983
  stopSpin("cloudflared exited");
@@ -923,7 +1001,7 @@ ${dim("Ctrl-C stops and releases this subdomain")}`, "Live");
923
1001
  }
924
1002
  }
925
1003
  function registerUp(program) {
926
- 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("-y, --yes", "don't ask before replacing an existing record").option("--proto <proto>", "local service protocol: http | https", "http").action((port, opts) => runUp(port, opts));
1004
+ 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("--source <host>", "forward to this host/IP instead of localhost (e.g. a LAN device or ::1)").option("--detach", "run the connector in the background").option("-f, --force", "replace a non-tunnel DNS record occupying the hostname").option("-y, --yes", "don't ask before replacing an existing record").option("--proto <proto>", "local service protocol: http | https", "http").option("--protocol <proto>", "cloudflared edge transport: auto | http2 | quic (http2 for UDP-hostile networks)").action((port, opts) => runUp(port, opts));
927
1005
  }
928
1006
 
929
1007
  // src/commands/ls.ts
@@ -985,69 +1063,24 @@ function registerZones(program) {
985
1063
  });
986
1064
  }
987
1065
 
988
- // src/core/profiles.ts
989
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
990
- function readProfiles() {
991
- try {
992
- return JSON.parse(readFileSync4(profilesFile, "utf8"));
993
- } catch {
994
- return {};
995
- }
996
- }
997
- function writeProfiles(profiles) {
998
- ensureDirs();
999
- writeFileSync3(profilesFile, JSON.stringify(profiles, null, 2), { mode: 384 });
1000
- }
1001
- function listProfiles() {
1002
- return Object.entries(readProfiles()).map(([name, profile]) => ({ name, profile }));
1003
- }
1004
- function getProfile(name) {
1005
- const profile = readProfiles()[name];
1006
- if (!profile) {
1007
- throw new CliError(`No profile named "${name}".`, { hint: "list them with `cloudtunnel profiles`" });
1008
- }
1009
- return profile;
1010
- }
1011
- function saveProfile(name, profile) {
1012
- const profiles = readProfiles();
1013
- profiles[name] = profile;
1014
- writeProfiles(profiles);
1015
- }
1016
- function removeProfile(name) {
1017
- const profiles = readProfiles();
1018
- if (!profiles[name]) throw new CliError(`No profile named "${name}".`);
1019
- delete profiles[name];
1020
- writeProfiles(profiles);
1021
- }
1022
- function parseServiceSpec(spec) {
1023
- const [name, portStr, proto] = spec.split(":");
1024
- const port = Number(portStr);
1025
- if (!name || !Number.isInteger(port) || port < 1 || port > 65535) {
1026
- throw new CliError(`Invalid service "${spec}".`, { hint: "use name:port, e.g. api:3000 or web:5173:https" });
1027
- }
1028
- if (proto && proto !== "http" && proto !== "https") {
1029
- throw new CliError(`Invalid protocol "${proto}" in "${spec}".`, { hint: "proto must be http or https" });
1030
- }
1031
- return { name, port, proto: proto ?? "http" };
1032
- }
1033
-
1034
1066
  // src/commands/save.ts
1035
1067
  function registerSave(program) {
1036
- program.command("save").argument("<profile>", "profile name, e.g. mb").argument("[services...]", "services as name:port[:proto], e.g. api:3000 web:5173").description("Save a group of services as a profile you can `run` together").option("--from-running", "snapshot the currently tracked tunnels instead of listing services").option("-d, --domain <domain>", "default domain for this profile").action((profile, specs, opts) => {
1068
+ program.command("save").argument("<profile>", "profile name, e.g. mb").argument("[services...]", "services as name:port[:proto], e.g. api:3000 web:5173").description("Save a group of services as a profile you can `run` together").option("--from-running", "snapshot the currently tracked tunnels instead of listing services").option("-d, --domain <domain>", "default domain for this profile").option("--protocol <proto>", "edge transport for this profile: auto | http2 | quic").action((profile, specs, opts) => {
1037
1069
  let services;
1038
1070
  if (opts.fromRunning) {
1039
1071
  const entries = listEntries().filter((e) => e.tunnelId);
1040
1072
  if (entries.length === 0) {
1041
1073
  throw new CliError("No tunnels to snapshot.", { hint: "start some with `cloudtunnel up`, or pass services like api:3000" });
1042
1074
  }
1043
- services = entries.map((e) => ({ name: e.subdomain, port: e.port, proto: e.proto, domain: e.zone }));
1075
+ services = entries.map((e) => ({ name: e.subdomain, port: e.port, proto: e.proto, domain: e.zone, ...e.host ? { host: e.host } : {} }));
1044
1076
  } else {
1045
1077
  if (specs.length === 0) {
1046
1078
  throw new CliError("No services given.", { hint: "e.g. `cloudtunnel save mb api:3000 web:5173`" });
1047
1079
  }
1048
1080
  services = specs.map(parseServiceSpec);
1049
1081
  }
1050
- saveProfile(profile, { services, domain: opts.domain });
1082
+ const protocol = opts.protocol ? parseTransportProtocol(opts.protocol) : void 0;
1083
+ saveProfile(profile, { services, domain: opts.domain, protocol });
1051
1084
  say.ok(`Saved profile "${profile}" (${services.length} service${services.length === 1 ? "" : "s"}). Run it: cloudtunnel run ${profile}`);
1052
1085
  });
1053
1086
  }
@@ -1060,6 +1093,7 @@ async function runProfile(name, opts) {
1060
1093
  const cf = resolveCf();
1061
1094
  const bin = await ensureCloudflared();
1062
1095
  const profile = getProfile(name);
1096
+ const protocol = opts.protocol ? parseTransportProtocol(opts.protocol) : profile.protocol;
1063
1097
  if (process.stdout.isTTY) clack3.intro(`cloudtunnel \xB7 profile "${name}"`);
1064
1098
  const spin = clack3.spinner();
1065
1099
  spin.start("Creating tunnels\u2026");
@@ -1070,6 +1104,7 @@ async function runProfile(name, opts) {
1070
1104
  port: svc.port,
1071
1105
  proto: svc.proto,
1072
1106
  name: svc.name,
1107
+ host: svc.host,
1073
1108
  zone: svc.domain ?? opts.domain ?? profile.domain,
1074
1109
  defaultZone: creds.defaultZone,
1075
1110
  force: opts.force,
@@ -1083,10 +1118,11 @@ async function runProfile(name, opts) {
1083
1118
  token: result.token,
1084
1119
  detach: !!opts.detach,
1085
1120
  logFile,
1121
+ protocol,
1086
1122
  onExit: opts.detach ? void 0 : () => say.warn(`Connector for ${fqdn} exited.`)
1087
1123
  });
1088
1124
  await patchEntry(fqdn, { pid: conn.pid, bootId: currentBootId(), logFile });
1089
- started.push({ fqdn, subdomain: result.host.subdomain, tunnelId: result.tunnelId, target: `${svc.proto}://localhost:${svc.port}`, pid: conn.pid });
1125
+ started.push({ fqdn, subdomain: result.host.subdomain, tunnelId: result.tunnelId, target: serviceUrl(svc.proto, svc.host ?? "localhost", svc.port), pid: conn.pid });
1090
1126
  }
1091
1127
  if (opts.detach) {
1092
1128
  spin.stop(`${started.length} service(s) started in the background`);
@@ -1125,10 +1161,102 @@ async function runProfile(name, opts) {
1125
1161
  }
1126
1162
  }
1127
1163
  function registerRun(program) {
1128
- program.command("run").argument("<profile>", "name of a saved profile (see `cloudtunnel profiles`)").description("Start every service in a saved profile at once").option("-f, --force", "take over subdomains already occupied by another record").option("-d, --domain <domain>", "override the profile's domain for this run").option("--detach", "run all connectors in the background (stop with `cloudtunnel down --all`)").action((name, opts) => runProfile(name, opts));
1164
+ program.command("run").argument("<profile>", "name of a saved profile (see `cloudtunnel profiles`)").description("Start every service in a saved profile at once").option("-f, --force", "take over subdomains already occupied by another record").option("-d, --domain <domain>", "override the profile's domain for this run").option("--detach", "run all connectors in the background (stop with `cloudtunnel down --all`)").option("--protocol <proto>", "edge transport: auto | http2 | quic (overrides the profile's saved protocol)").action((name, opts) => runProfile(name, opts));
1165
+ }
1166
+
1167
+ // src/core/systemd.ts
1168
+ import { execFileSync as execFileSync3 } from "child_process";
1169
+ import { writeFileSync as writeFileSync4 } from "fs";
1170
+ import { tmpdir } from "os";
1171
+ import { dirname, join as join4 } from "path";
1172
+ function serviceName(profile) {
1173
+ return `cloudtunnel-${profile}.service`;
1174
+ }
1175
+ function unitPath(profile) {
1176
+ return `/etc/systemd/system/${serviceName(profile)}`;
1177
+ }
1178
+ function buildUnit(p) {
1179
+ const nodeBin = dirname(p.nodePath);
1180
+ const proto = p.protocol ? ` --protocol ${p.protocol}` : "";
1181
+ return [
1182
+ "[Unit]",
1183
+ `Description=cloudtunnel profile "${p.profile}" (Cloudflare Tunnel)`,
1184
+ "After=network-online.target",
1185
+ "Wants=network-online.target",
1186
+ "",
1187
+ "[Service]",
1188
+ "Type=simple",
1189
+ `User=${p.user}`,
1190
+ `Environment=HOME=${p.home}`,
1191
+ `Environment=PATH=${nodeBin}:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin`,
1192
+ `ExecStart=${p.nodePath} ${p.scriptPath} run ${p.profile} -f${proto}`,
1193
+ "Restart=on-failure",
1194
+ "RestartSec=5",
1195
+ "",
1196
+ "[Install]",
1197
+ "WantedBy=multi-user.target",
1198
+ ""
1199
+ ].join("\n");
1200
+ }
1201
+ function assertSystemd() {
1202
+ if (process.platform !== "linux") {
1203
+ throw new CliError("Service registration is Linux/systemd only.", {
1204
+ hint: "on macOS/Windows run `cloudtunnel run <profile> --detach` at login instead"
1205
+ });
1206
+ }
1207
+ try {
1208
+ execFileSync3("systemctl", ["--version"], { stdio: "ignore" });
1209
+ } catch {
1210
+ throw new CliError("systemd (systemctl) was not found on this host.");
1211
+ }
1212
+ }
1213
+ function privileged(args) {
1214
+ const isRoot = typeof process.getuid === "function" && process.getuid() === 0;
1215
+ const argv = isRoot ? args : ["sudo", ...args];
1216
+ execFileSync3(argv[0], argv.slice(1), { stdio: "inherit" });
1217
+ }
1218
+ function query(args) {
1219
+ try {
1220
+ return execFileSync3("systemctl", args, {
1221
+ stdio: ["ignore", "pipe", "ignore"],
1222
+ encoding: "utf8"
1223
+ }).trim();
1224
+ } catch (err) {
1225
+ const out = err.stdout;
1226
+ return out ? out.toString().trim() : "";
1227
+ }
1228
+ }
1229
+ function installService(p) {
1230
+ assertSystemd();
1231
+ const tmp = join4(tmpdir(), serviceName(p.profile));
1232
+ writeFileSync4(tmp, buildUnit(p), { mode: 420 });
1233
+ privileged(["install", "-m", "0644", tmp, unitPath(p.profile)]);
1234
+ privileged(["systemctl", "daemon-reload"]);
1235
+ privileged(["systemctl", "enable", "--now", serviceName(p.profile)]);
1236
+ }
1237
+ function uninstallService(profile) {
1238
+ assertSystemd();
1239
+ try {
1240
+ privileged(["systemctl", "disable", "--now", serviceName(profile)]);
1241
+ } catch {
1242
+ }
1243
+ privileged(["rm", "-f", unitPath(profile)]);
1244
+ privileged(["systemctl", "daemon-reload"]);
1245
+ }
1246
+ function serviceState(profile) {
1247
+ if (process.platform !== "linux") return "none";
1248
+ const name = serviceName(profile);
1249
+ if (query(["is-active", name]) === "active") return "active";
1250
+ const enabled = query(["is-enabled", name]);
1251
+ if (enabled === "enabled" || enabled === "enabled-runtime") return "enabled";
1252
+ if (enabled === "disabled" || enabled === "static") return "disabled";
1253
+ return "none";
1129
1254
  }
1130
1255
 
1131
1256
  // src/commands/profiles.ts
1257
+ function formatService(state) {
1258
+ return state === "none" ? dim("\u2013") : state;
1259
+ }
1132
1260
  function registerProfiles(program) {
1133
1261
  program.command("profiles").description("List saved profiles (or delete one with --rm)").option("--rm <name>", "delete a profile").action((opts) => {
1134
1262
  if (opts.rm) {
@@ -1142,11 +1270,13 @@ function registerProfiles(program) {
1142
1270
  return;
1143
1271
  }
1144
1272
  printTable(
1145
- ["PROFILE", "SERVICES", "DOMAIN"],
1273
+ ["PROFILE", "SERVICES", "DOMAIN", "PROTOCOL", "SERVICE"],
1146
1274
  profiles.map(({ name, profile }) => [
1147
1275
  name,
1148
- profile.services.map((s) => `${s.name}:${s.port}`).join(", "),
1149
- profile.domain ?? "(default)"
1276
+ profile.services.map((s) => `${s.name}:${s.port}${s.host ? `@${s.host}` : ""}`).join(", "),
1277
+ profile.domain ?? "(default)",
1278
+ profile.protocol ?? "auto",
1279
+ formatService(serviceState(name))
1150
1280
  ])
1151
1281
  );
1152
1282
  });
@@ -1196,6 +1326,52 @@ function registerLogs(program) {
1196
1326
  });
1197
1327
  }
1198
1328
 
1329
+ // src/commands/service.ts
1330
+ import os2 from "os";
1331
+ import { realpathSync } from "fs";
1332
+ function entryScript() {
1333
+ const p = process.argv[1];
1334
+ if (!p) throw new CliError("Cannot resolve the cloudtunnel executable path.");
1335
+ return realpathSync(p);
1336
+ }
1337
+ function enable(name, opts) {
1338
+ const profile = getProfile(name);
1339
+ let protocol = profile.protocol;
1340
+ if (opts.protocol) {
1341
+ protocol = parseTransportProtocol(opts.protocol);
1342
+ saveProfile(name, { ...profile, protocol });
1343
+ }
1344
+ if (!protocol) {
1345
+ say.warn("No edge protocol set \u2014 cloudflared will pick QUIC, which some networks drop.");
1346
+ say.dim(" \u2192 set one with: cloudtunnel service enable " + name + " --protocol http2");
1347
+ }
1348
+ installService({
1349
+ profile: name,
1350
+ user: os2.userInfo().username,
1351
+ home: os2.homedir(),
1352
+ nodePath: process.execPath,
1353
+ scriptPath: entryScript(),
1354
+ protocol
1355
+ });
1356
+ say.ok(`Service ${serviceName(name)} enabled \u2014 starts on boot.`);
1357
+ say.dim(` \u2192 check it: cloudtunnel service status ${name}`);
1358
+ }
1359
+ function disable(name) {
1360
+ getProfile(name);
1361
+ uninstallService(name);
1362
+ say.ok(`Service ${serviceName(name)} disabled and removed.`);
1363
+ }
1364
+ function status(name) {
1365
+ getProfile(name);
1366
+ say.info(`${serviceName(name)}: ${serviceState(name)}`);
1367
+ }
1368
+ function registerService(program) {
1369
+ const svc = program.command("service").description("Register a profile as a systemd service that starts on boot");
1370
+ svc.command("enable").argument("<profile>", "profile to register").option("--protocol <proto>", "edge transport for the service: auto | http2 | quic").description("Install + enable a boot service for the profile (needs sudo)").action((name, opts) => enable(name, opts));
1371
+ svc.command("disable").argument("<profile>", "profile to unregister").description("Stop, disable, and remove the profile's boot service (needs sudo)").action((name) => disable(name));
1372
+ svc.command("status").argument("<profile>", "profile to check").description("Show the systemd state of the profile's service").action((name) => status(name));
1373
+ }
1374
+
1199
1375
  // src/index.ts
1200
1376
  var require2 = createRequire(import.meta.url);
1201
1377
  var pkg = require2("../package.json");
@@ -1214,6 +1390,7 @@ var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
1214
1390
  "save",
1215
1391
  "run",
1216
1392
  "profiles",
1393
+ "service",
1217
1394
  "help"
1218
1395
  ]);
1219
1396
  function applyBarePortAlias(argv) {
@@ -1245,6 +1422,7 @@ function buildProgram() {
1245
1422
  registerSave,
1246
1423
  registerRun,
1247
1424
  registerProfiles,
1425
+ registerService,
1248
1426
  registerLogs
1249
1427
  ]) {
1250
1428
  register(program);
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/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, confirm as clackConfirm, 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/** Yes/no prompt (TTY). Cancel (Ctrl-C) counts as \"no\". */\nexport async function confirm(message: string): Promise<boolean> {\n const answer = await clackConfirm({ message });\n return !isCancel(answer) && answer === true;\n}\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 yes?: 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: \"blank = random · @ = root domain\" });\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 // No spinner around create: it may prompt (domain/subdomain already handled,\n // plus a \"replace existing record?\" confirm inside createTunnelSubdomain).\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, yes: opts.yes,\n });\n const fqdn = result.host.hostname;\n const logLabel = result.host.subdomain === \"@\" ? \"root\" : result.host.subdomain;\n const logFile = join(logDir, `${logLabel}.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 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 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 spin.start(\"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(\"-y, --yes\", \"don't ask before replacing an existing 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 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.\n * On systems without the Linux boot_id file (e.g. macOS), fall back to the boot\n * *time* bucketed to the minute — this is constant between invocations (unlike\n * `os.uptime()`, which increases every second and would break connector tracking). */\nexport function currentBootId(): string {\n try {\n return readFileSync(\"/proc/sys/kernel/random/boot_id\", \"utf8\").trim();\n } catch {\n const bootMinute = Math.floor((Date.now() - os.uptime() * 1000) / 60_000);\n return `boot-${bootMinute}-${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 { confirm, 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 yes?: boolean; // skip the \"replace existing record?\" confirmation\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 → replaceable. A non-tunnel DNS record (A record,\n // ordinary CNAME) → refuse unless --force, to avoid clobbering unrelated DNS.\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 // Confirm before replacing an existing record (interactive only; -f/-y skip).\n if (!opts.force && !opts.yes && process.stdin.isTTY) {\n const kind = isTunnelRecord ? \"tunnel\" : \"DNS\";\n if (!(await confirm(`${host.hostname} already has a ${kind} record. Replace it?`))) {\n throw new CliError(\"Cancelled.\", { exitCode: 130 });\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 label = host.subdomain === \"@\" ? \"root\" : host.subdomain;\n const tunnel = await createTunnel(cf, `${MANAGED_TUNNEL_PREFIX}${label}-${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 // `@` means the root/apex domain (Cloudflare flattens the proxied CNAME).\n const hostname = subdomain === \"@\" ? zone : `${subdomain}.${zone}`;\n return { subdomain, zone, hostname };\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, yes: true, // batch: never prompt per service\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"]}
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/core/profiles.ts","../src/commands/ls.ts","../src/commands/down.ts","../src/commands/zones.ts","../src/commands/save.ts","../src/commands/run.ts","../src/core/systemd.ts","../src/commands/profiles.ts","../src/commands/logs.ts","../src/commands/service.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\";\nimport { registerService } from \"./commands/service.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\", \"service\", \"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, registerService, 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, confirm as clackConfirm, 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/** Yes/no prompt (TTY). Cancel (Ctrl-C) counts as \"no\". */\nexport async function confirm(message: string): Promise<boolean> {\n const answer = await clackConfirm({ message });\n return !isCancel(answer) && answer === true;\n}\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\";\nimport { parseTransportProtocol } from \"../core/profiles.js\";\nimport { validateHost, serviceUrl } from \"../core/ingress.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 source?: string; // forward target host/IP (default localhost)\n detach?: boolean;\n proto: \"http\" | \"https\";\n protocol?: string; // edge transport: auto | http2 | quic\n force?: boolean;\n yes?: 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: \"blank = random · @ = root domain\" });\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 protocol = opts.protocol ? parseTransportProtocol(opts.protocol) : undefined;\n const host = opts.source ? validateHost(opts.source) : undefined;\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 // No spinner around create: it may prompt (domain/subdomain already handled,\n // plus a \"replace existing record?\" confirm inside createTunnelSubdomain).\n const result = await createTunnelSubdomain(cf, {\n port, proto: opts.proto, name: subdomain, zone: domain,\n hostname: opts.hostname, host, defaultZone: creds.defaultZone, force: opts.force, yes: opts.yes,\n });\n const fqdn = result.host.hostname;\n const logLabel = result.host.subdomain === \"@\" ? \"root\" : result.host.subdomain;\n const logFile = join(logDir, `${logLabel}.log`);\n const target = serviceUrl(opts.proto, host ?? \"localhost\", port);\n\n if (opts.detach) {\n const started = startConnector({ bin, token: result.token, detach: true, logFile, protocol });\n await patchEntry(fqdn, { pid: started.pid, bootId: currentBootId(), logFile });\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 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 spin.start(\"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, protocol,\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(\"--source <host>\", \"forward to this host/IP instead of localhost (e.g. a LAN device or ::1)\")\n .option(\"--detach\", \"run the connector in the background\")\n .option(\"-f, --force\", \"replace a non-tunnel DNS record occupying the hostname\")\n .option(\"-y, --yes\", \"don't ask before replacing an existing record\")\n .option(\"--proto <proto>\", \"local service protocol: http | https\", \"http\")\n .option(\"--protocol <proto>\", \"cloudflared edge transport: auto | http2 | quic (http2 for UDP-hostile networks)\")\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 /** cloudflared edge transport (quic | http2 | auto). Omitted ⇒ cloudflared's\n * default. Force `http2` on UDP-hostile networks that drop idle QUIC. */\n protocol?: 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 // Edge transport: pass as an explicit flag so it also lands in the connector\n // cmdline (visible/reproducible), not only via env.\n if (opts.protocol) args.push(\"--protocol\", opts.protocol);\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 host?: string; // forward target host (absent = localhost)\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.\n * On systems without the Linux boot_id file (e.g. macOS), fall back to the boot\n * *time* bucketed to the minute — this is constant between invocations (unlike\n * `os.uptime()`, which increases every second and would break connector tracking). */\nexport function currentBootId(): string {\n try {\n return readFileSync(\"/proc/sys/kernel/random/boot_id\", \"utf8\").trim();\n } catch {\n const bootMinute = Math.floor((Date.now() - os.uptime() * 1000) / 60_000);\n return `boot-${bootMinute}-${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 { confirm, 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 host?: string; // forward target host (absent = localhost)\n defaultZone?: string;\n force?: boolean;\n yes?: boolean; // skip the \"replace existing record?\" confirmation\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 → replaceable. A non-tunnel DNS record (A record,\n // ordinary CNAME) → refuse unless --force, to avoid clobbering unrelated DNS.\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 // Confirm before replacing an existing record (interactive only; -f/-y skip).\n if (!opts.force && !opts.yes && process.stdin.isTTY) {\n const kind = isTunnelRecord ? \"tunnel\" : \"DNS\";\n if (!(await confirm(`${host.hostname} already has a ${kind} record. Replace it?`))) {\n throw new CliError(\"Cancelled.\", { exitCode: 130 });\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, host: opts.host, 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 label = host.subdomain === \"@\" ? \"root\" : host.subdomain;\n const tunnel = await createTunnel(cf, `${MANAGED_TUNNEL_PREFIX}${label}-${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, host: opts.host }));\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, host: opts.host,\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\";\nimport { CliError } from \"../ui/errors.js\";\n\nconst HOSTNAME_RE = /^[a-zA-Z0-9.-]+$/; // hostname or IPv4\nconst IPV6_RE = /^[0-9a-fA-F:.]+$/; // IPv6 literal (incl. IPv4-mapped ::ffff:1.2.3.4)\n\n/**\n * Validate a forward-target host before it lands in the ingress service URL.\n * Rejects anything that could break out of `proto://host:port` — a scheme,\n * path, or whitespace — so `--source` can't inject extra ingress syntax.\n *\n * IPv6 is accepted bare (`::1`) or bracketed (`[::1]`) and stored bare. IPv6 is\n * detected by `::` or ≥2 colons, so a single-colon `10.0.0.2:8080` (an IPv4:port\n * mistake) still fails the hostname check instead of passing as a bogus literal.\n */\nexport function validateHost(host: string): string {\n let h = host.trim();\n const bracketed = h.startsWith(\"[\") && h.endsWith(\"]\");\n if (bracketed) h = h.slice(1, -1);\n const isV6 = bracketed || h.includes(\"::\") || (h.match(/:/g)?.length ?? 0) >= 2;\n const ok = h.length > 0 && (isV6 ? IPV6_RE.test(h) : HOSTNAME_RE.test(h));\n if (!ok) {\n throw new CliError(`Invalid host \"${host}\".`, {\n hint: \"use a hostname, IPv4, or IPv6 literal (e.g. 192.168.1.5 or ::1) — no port, scheme, or path\",\n });\n }\n return h;\n}\n\n/** Compose a `proto://host:port` service URL, bracketing an IPv6 literal. */\nexport function serviceUrl(proto: \"http\" | \"https\", host: string, port: number): string {\n const authority = host.includes(\":\") ? `[${host}]` : host;\n return `${proto}://${authority}:${port}`;\n}\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 *\n * `host` defaults to `localhost`; pass another host/IP to forward to a different\n * machine this connector can reach (a LAN device, a container, another server).\n */\nexport function buildIngress(opts: {\n hostname: string;\n port: number;\n proto: \"http\" | \"https\";\n host?: string;\n}): IngressRule[] {\n return [\n { hostname: opts.hostname, service: serviceUrl(opts.proto, opts.host ?? \"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 // `@` means the root/apex domain (Cloudflare flattens the proxied CNAME).\n const hostname = subdomain === \"@\" ? zone : `${subdomain}.${zone}`;\n return { subdomain, zone, hostname };\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\";\nimport { serviceUrl } from \"./ingress.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: serviceUrl(e.proto, e.host ?? \"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 { readFileSync, writeFileSync } from \"node:fs\";\nimport { ensureDirs, profilesFile } from \"../config/paths.js\";\nimport { CliError } from \"../ui/errors.js\";\nimport { validateHost } from \"./ingress.js\";\n\n/**\n * cloudflared edge transport (NOT the local service scheme in ProfileService.proto).\n * `quic` is UDP-based and fastest, but UDP-hostile networks drop idle QUIC sessions\n * (→ Cloudflare 530/502); `http2` runs over TCP and stays stable there. `auto` lets\n * cloudflared choose (defaults to quic when the network probe passes).\n */\nexport type TransportProtocol = \"auto\" | \"http2\" | \"quic\";\n\nexport function parseTransportProtocol(value: string): TransportProtocol {\n if (value === \"auto\" || value === \"http2\" || value === \"quic\") return value;\n throw new CliError(`Invalid protocol \"${value}\".`, { hint: \"use auto, http2, or quic\" });\n}\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 host?: string; // forward target host (absent = localhost)\n}\n\nexport interface Profile {\n services: ProfileService[];\n domain?: string; // default domain for the whole profile\n protocol?: TransportProtocol; // edge transport for every service in this 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][@host]` service spec, e.g. `api:3000`,\n * `web:5173:https`, or `api:3000@192.168.1.5` (forward to another host/IP).\n */\nexport function parseServiceSpec(spec: string): ProfileService {\n const at = spec.indexOf(\"@\");\n const host = at >= 0 ? validateHost(spec.slice(at + 1)) : undefined;\n const [name, portStr, proto] = (at >= 0 ? spec.slice(0, at) : 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\", ...(host ? { host } : {}) };\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 type { Command } from \"commander\";\nimport { CliError } from \"../ui/errors.js\";\nimport { say } from \"../ui/output.js\";\nimport { listEntries } from \"../connector/registry.js\";\nimport { parseServiceSpec, parseTransportProtocol, saveProfile, type ProfileService } from \"../core/profiles.js\";\n\ninterface SaveOptions { fromRunning?: boolean; domain?: string; protocol?: 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 .option(\"--protocol <proto>\", \"edge transport for this profile: auto | http2 | quic\")\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, ...(e.host ? { host: e.host } : {}) }));\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 const protocol = opts.protocol ? parseTransportProtocol(opts.protocol) : undefined;\n saveProfile(profile, { services, domain: opts.domain, protocol });\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, parseTransportProtocol } from \"../core/profiles.js\";\nimport { serviceUrl } from \"../core/ingress.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; protocol?: string }\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 // Per-run override wins over the profile's saved transport.\n const protocol = opts.protocol ? parseTransportProtocol(opts.protocol) : profile.protocol;\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, host: svc.host,\n zone: svc.domain ?? opts.domain ?? profile.domain, defaultZone: creds.defaultZone,\n force: opts.force, yes: true, // batch: never prompt per service\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, protocol,\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: serviceUrl(svc.proto, svc.host ?? \"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 .option(\"--protocol <proto>\", \"edge transport: auto | http2 | quic (overrides the profile's saved protocol)\")\n .action((name: string, opts: RunOptions) => runProfile(name, opts));\n}\n","import { execFileSync } from \"node:child_process\";\nimport { writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { CliError } from \"../ui/errors.js\";\nimport type { TransportProtocol } from \"./profiles.js\";\n\nexport interface UnitParams {\n profile: string;\n user: string;\n home: string;\n nodePath: string; // absolute node binary\n scriptPath: string; // absolute cloudtunnel entry (dist/index.js)\n protocol?: TransportProtocol;\n}\n\nexport type ServiceState = \"active\" | \"enabled\" | \"disabled\" | \"none\";\n\nexport function serviceName(profile: string): string {\n return `cloudtunnel-${profile}.service`;\n}\n\nexport function unitPath(profile: string): string {\n return `/etc/systemd/system/${serviceName(profile)}`;\n}\n\n/**\n * Build the systemd unit text (pure — unit-tested). ExecStart runs the profile\n * in the FOREGROUND so systemd supervises it; `systemctl stop` sends SIGTERM,\n * which makes `run` release its tunnels and exit 0 (so it is not restarted).\n * Absolute node + script and an explicit PATH are used because systemd starts\n * with a minimal environment where `node`/`cloudflared` are not on PATH.\n */\nexport function buildUnit(p: UnitParams): string {\n const nodeBin = dirname(p.nodePath);\n const proto = p.protocol ? ` --protocol ${p.protocol}` : \"\";\n return [\n \"[Unit]\",\n `Description=cloudtunnel profile \"${p.profile}\" (Cloudflare Tunnel)`,\n \"After=network-online.target\",\n \"Wants=network-online.target\",\n \"\",\n \"[Service]\",\n \"Type=simple\",\n `User=${p.user}`,\n `Environment=HOME=${p.home}`,\n `Environment=PATH=${nodeBin}:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin`,\n `ExecStart=${p.nodePath} ${p.scriptPath} run ${p.profile} -f${proto}`,\n \"Restart=on-failure\",\n \"RestartSec=5\",\n \"\",\n \"[Install]\",\n \"WantedBy=multi-user.target\",\n \"\",\n ].join(\"\\n\");\n}\n\n/** Fail early with an actionable message when systemd isn't usable here. */\nexport function assertSystemd(): void {\n if (process.platform !== \"linux\") {\n throw new CliError(\"Service registration is Linux/systemd only.\", {\n hint: \"on macOS/Windows run `cloudtunnel run <profile> --detach` at login instead\",\n });\n }\n try {\n execFileSync(\"systemctl\", [\"--version\"], { stdio: \"ignore\" });\n } catch {\n throw new CliError(\"systemd (systemctl) was not found on this host.\");\n }\n}\n\n/** Run a privileged command, prefixing `sudo` unless already root. Inherits the\n * terminal so sudo can prompt for a password. */\nfunction privileged(args: string[]): void {\n const isRoot = typeof process.getuid === \"function\" && process.getuid() === 0;\n const argv = isRoot ? args : [\"sudo\", ...args];\n execFileSync(argv[0]!, argv.slice(1), { stdio: \"inherit\" });\n}\n\n/** Read-only systemctl query; returns trimmed stdout (\"\" on any error). Never\n * needs root, so it stays quiet and side-effect-free. */\nfunction query(args: string[]): string {\n try {\n return execFileSync(\"systemctl\", args, {\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n encoding: \"utf8\",\n }).trim();\n } catch (err) {\n // is-enabled/is-active exit non-zero for disabled/inactive units but still\n // print the state to stdout — recover it from the thrown error.\n const out = (err as { stdout?: Buffer | string }).stdout;\n return out ? out.toString().trim() : \"\";\n }\n}\n\n/** Install the unit and enable it to start now + on boot. Needs sudo. */\nexport function installService(p: UnitParams): void {\n assertSystemd();\n const tmp = join(tmpdir(), serviceName(p.profile));\n writeFileSync(tmp, buildUnit(p), { mode: 0o644 });\n privileged([\"install\", \"-m\", \"0644\", tmp, unitPath(p.profile)]);\n privileged([\"systemctl\", \"daemon-reload\"]);\n privileged([\"systemctl\", \"enable\", \"--now\", serviceName(p.profile)]);\n}\n\n/** Stop, disable, and delete the unit. Needs sudo. Best-effort on each step so a\n * half-installed service can still be cleaned up. */\nexport function uninstallService(profile: string): void {\n assertSystemd();\n try {\n privileged([\"systemctl\", \"disable\", \"--now\", serviceName(profile)]);\n } catch {\n /* not enabled / already gone */\n }\n privileged([\"rm\", \"-f\", unitPath(profile)]);\n privileged([\"systemctl\", \"daemon-reload\"]);\n}\n\n/** Current systemd state of the profile's service (no root required). */\nexport function serviceState(profile: string): ServiceState {\n if (process.platform !== \"linux\") return \"none\";\n const name = serviceName(profile);\n if (query([\"is-active\", name]) === \"active\") return \"active\";\n const enabled = query([\"is-enabled\", name]);\n if (enabled === \"enabled\" || enabled === \"enabled-runtime\") return \"enabled\";\n if (enabled === \"disabled\" || enabled === \"static\") return \"disabled\";\n return \"none\";\n}\n","import type { Command } from \"commander\";\nimport { dim, printTable, say } from \"../ui/output.js\";\nimport { listProfiles, removeProfile } from \"../core/profiles.js\";\nimport { serviceState, type ServiceState } from \"../core/systemd.js\";\n\n/** Render the boot-service state for the SERVICE column (\"–\" when not registered). */\nfunction formatService(state: ServiceState): string {\n return state === \"none\" ? dim(\"–\") : state;\n}\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\", \"PROTOCOL\", \"SERVICE\"],\n profiles.map(({ name, profile }) => [\n name,\n profile.services.map((s) => `${s.name}:${s.port}${s.host ? `@${s.host}` : \"\"}`).join(\", \"),\n profile.domain ?? \"(default)\",\n profile.protocol ?? \"auto\",\n formatService(serviceState(name)),\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","import type { Command } from \"commander\";\nimport os from \"node:os\";\nimport { realpathSync } from \"node:fs\";\nimport { CliError } from \"../ui/errors.js\";\nimport { say } from \"../ui/output.js\";\nimport {\n getProfile,\n parseTransportProtocol,\n saveProfile,\n type TransportProtocol,\n} from \"../core/profiles.js\";\nimport { installService, serviceName, serviceState, uninstallService } from \"../core/systemd.js\";\n\n/** Absolute path to the running cloudtunnel entry, for a stable systemd ExecStart. */\nfunction entryScript(): string {\n const p = process.argv[1];\n if (!p) throw new CliError(\"Cannot resolve the cloudtunnel executable path.\");\n return realpathSync(p);\n}\n\nfunction enable(name: string, opts: { protocol?: string }): void {\n const profile = getProfile(name); // validates the profile exists\n // A per-command --protocol is also persisted so `profiles`, `run`, and the\n // service all agree on the transport.\n let protocol: TransportProtocol | undefined = profile.protocol;\n if (opts.protocol) {\n protocol = parseTransportProtocol(opts.protocol);\n saveProfile(name, { ...profile, protocol });\n }\n if (!protocol) {\n say.warn(\"No edge protocol set — cloudflared will pick QUIC, which some networks drop.\");\n say.dim(\" → set one with: cloudtunnel service enable \" + name + \" --protocol http2\");\n }\n installService({\n profile: name,\n user: os.userInfo().username,\n home: os.homedir(),\n nodePath: process.execPath,\n scriptPath: entryScript(),\n protocol,\n });\n say.ok(`Service ${serviceName(name)} enabled — starts on boot.`);\n say.dim(` → check it: cloudtunnel service status ${name}`);\n}\n\nfunction disable(name: string): void {\n getProfile(name);\n uninstallService(name);\n say.ok(`Service ${serviceName(name)} disabled and removed.`);\n}\n\nfunction status(name: string): void {\n getProfile(name);\n say.info(`${serviceName(name)}: ${serviceState(name)}`);\n}\n\nexport function registerService(program: Command): void {\n const svc = program\n .command(\"service\")\n .description(\"Register a profile as a systemd service that starts on boot\");\n svc\n .command(\"enable\")\n .argument(\"<profile>\", \"profile to register\")\n .option(\"--protocol <proto>\", \"edge transport for the service: auto | http2 | quic\")\n .description(\"Install + enable a boot service for the profile (needs sudo)\")\n .action((name: string, opts: { protocol?: string }) => enable(name, opts));\n svc\n .command(\"disable\")\n .argument(\"<profile>\", \"profile to unregister\")\n .description(\"Stop, disable, and remove the profile's boot service (needs sudo)\")\n .action((name: string) => disable(name));\n svc\n .command(\"status\")\n .argument(\"<profile>\", \"profile to check\")\n .description(\"Show the systemd state of the profile's service\")\n .action((name: string) => status(name));\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","writeFileSync","readFileSync","writeFileSync","readFileSync","join","started","join","clack","join","lines","execFileSync","writeFileSync","join","execFileSync","join","writeFileSync","existsSync","openSync","readFileSync","readFileSync","openSync","existsSync","os","os","require","pc"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iamken/cloudtunnel",
3
- "version": "0.1.5",
3
+ "version": "0.3.0",
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": {