@mycadet/cli 0.12.8 → 0.12.9-dev.202610131452.9acab64
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/node_modules/@mycadet/assistant-api/events/show-cloud-login.ts +2 -2
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/package.json +1 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/package.json +1 -1
- package/node_modules/@mycadet/service-contracts/package.json +1 -1
- package/package.json +4 -3
- package/src/__tests__/client-web-config.test.ts +0 -1
- package/src/__tests__/cloud-releases.test.ts +23 -13
- package/src/__tests__/ensure-binary.test.ts +182 -0
- package/src/__tests__/nginx-ingress.test.ts +6 -11
- package/src/__tests__/ngrok-spawn.test.ts +49 -0
- package/src/__tests__/pair.test.ts +149 -78
- package/src/__tests__/ps-cloud-status.test.ts +4 -3
- package/src/__tests__/ps-table.test.ts +79 -0
- package/src/__tests__/tailscale.test.ts +1 -1
- package/src/__tests__/teleport.test.ts +1 -16
- package/src/__tests__/tunnel.test.ts +274 -82
- package/src/__tests__/uninstall.test.ts +141 -9
- package/src/__tests__/wake.test.ts +2 -2
- package/src/commands/backup.ts +3 -3
- package/src/commands/client.ts +4 -6
- package/src/commands/completions.test.ts +104 -0
- package/src/commands/completions.ts +87 -0
- package/src/commands/exec.ts +7 -7
- package/src/commands/flags.ts +2 -2
- package/src/commands/hatch.ts +5 -2
- package/src/commands/logs.ts +7 -7
- package/src/commands/pair.ts +140 -37
- package/src/commands/ps.ts +63 -26
- package/src/commands/restore.ts +2 -2
- package/src/commands/retire.ts +2 -2
- package/src/commands/rollback.ts +2 -2
- package/src/commands/sleep.ts +2 -2
- package/src/commands/teleport.ts +18 -37
- package/src/commands/terminal.ts +1 -1
- package/src/commands/tunnel.ts +101 -78
- package/src/commands/uninstall.ts +108 -73
- package/src/commands/upgrade.ts +8 -8
- package/src/commands/use.ts +1 -1
- package/src/commands/wake.ts +6 -6
- package/src/index.ts +15 -0
- package/src/lib/__tests__/docker.test.ts +168 -20
- package/src/lib/__tests__/job-polling.test.ts +1 -1
- package/src/lib/__tests__/local-gateway-cli.test.ts +42 -0
- package/src/lib/cloud-client.ts +0 -3
- package/src/lib/cloud-login.ts +0 -1
- package/src/lib/cloud-releases.ts +6 -4
- package/src/lib/color.ts +4 -0
- package/src/lib/docker.ts +190 -102
- package/src/lib/ensure-binary.ts +171 -0
- package/src/lib/environments/__tests__/paths.test.ts +27 -1
- package/src/lib/environments/paths.ts +16 -8
- package/src/lib/environments/resolve.ts +2 -4
- package/src/lib/flag-args.test.ts +1 -1
- package/src/lib/flag-args.ts +1 -1
- package/src/lib/guardian-token.ts +1 -1
- package/src/lib/hatch-local.ts +15 -8
- package/src/lib/hatch-next-steps.ts +5 -9
- package/src/lib/job-polling.ts +1 -1
- package/src/lib/local.ts +37 -4
- package/src/lib/nginx-ingress.ts +3 -5
- package/src/lib/pair.ts +1 -1
- package/src/lib/step-runner.ts +6 -1
- package/src/lib/sync-cloud-assistants.ts +2 -2
- package/src/lib/tunnels/cloudflare.ts +21 -11
- package/src/lib/tunnels/ngrok.ts +198 -108
- package/src/lib/tunnels/tailscale.ts +34 -4
- package/src/lib/upgrade-lifecycle.ts +5 -4
|
@@ -82,18 +82,26 @@ export function getLogDir(env: EnvironmentDefinition): string {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
/**
|
|
85
|
-
*
|
|
85
|
+
* The interactive CLI's shared state directory — `$XDG_STATE_HOME/cadet`.
|
|
86
86
|
*
|
|
87
|
-
* Follows the XDG Base Directory spec:
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
* Defaults to `~/.local/state/cadet/input-history`.
|
|
87
|
+
* Follows the XDG Base Directory spec: session state is data that persists
|
|
88
|
+
* across runs but is not portable user-owned content, mirroring `bash`,
|
|
89
|
+
* `zsh`, `psql`, and `gh`. Defaults to `~/.local/state/cadet`.
|
|
91
90
|
*
|
|
92
|
-
* Not environment-scoped:
|
|
93
|
-
*
|
|
91
|
+
* Not environment-scoped: it holds per-user state (the terminal input
|
|
92
|
+
* history, below) that dev and prod CLIs share.
|
|
93
|
+
*/
|
|
94
|
+
export function getSharedStateDir(): string {
|
|
95
|
+
return join(xdgStateHome(), "cadet");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Path to the interactive CLI's input history file, inside
|
|
100
|
+
* {@link getSharedStateDir}. Defaults to
|
|
101
|
+
* `~/.local/state/cadet/input-history`.
|
|
94
102
|
*/
|
|
95
103
|
export function getInputHistoryPath(): string {
|
|
96
|
-
return join(
|
|
104
|
+
return join(getSharedStateDir(), "input-history");
|
|
97
105
|
}
|
|
98
106
|
|
|
99
107
|
/**
|
|
@@ -67,10 +67,8 @@ export function getCurrentEnvironment(
|
|
|
67
67
|
const seed = SEEDS[name];
|
|
68
68
|
if (!seed) {
|
|
69
69
|
if (name !== DEFAULT_ENVIRONMENT_NAME) {
|
|
70
|
-
// Warn on stderr instead of throwing
|
|
71
|
-
//
|
|
72
|
-
// which silently falls back to production; the CLI agrees so neither
|
|
73
|
-
// writer ends up in a disjoint state on a typo.
|
|
70
|
+
// Warn on stderr instead of throwing: an unknown environment falls back
|
|
71
|
+
// to production, so a typo never splits writes between two states.
|
|
74
72
|
process.stderr.write(
|
|
75
73
|
`warning: unknown environment "${name}"; falling back to "${DEFAULT_ENVIRONMENT_NAME}". ` +
|
|
76
74
|
`Add it to packages/environments/src/seeds.ts and rebuild if this was intentional.\n`,
|
|
@@ -75,7 +75,7 @@ describe("parseFeatureFlagArgs", () => {
|
|
|
75
75
|
"process.exit",
|
|
76
76
|
);
|
|
77
77
|
expect(errorSpy).toHaveBeenCalledWith(
|
|
78
|
-
'Error: invalid flag key "UPPER". Keys must be kebab-case
|
|
78
|
+
'Error: invalid flag key "UPPER". Keys must be kebab-case, e.g. "browser"',
|
|
79
79
|
);
|
|
80
80
|
|
|
81
81
|
exitSpy.mockRestore();
|
package/src/lib/flag-args.ts
CHANGED
|
@@ -38,7 +38,7 @@ export function parseFeatureFlagArgs(args: string[]): {
|
|
|
38
38
|
|
|
39
39
|
if (!ALLOWED_KEY_RE.test(key)) {
|
|
40
40
|
console.error(
|
|
41
|
-
`Error: invalid flag key "${key}". Keys must be kebab-case
|
|
41
|
+
`Error: invalid flag key "${key}". Keys must be kebab-case, e.g. "browser"`,
|
|
42
42
|
);
|
|
43
43
|
process.exit(1);
|
|
44
44
|
}
|
|
@@ -429,7 +429,7 @@ export async function refreshGuardianTokenResult(
|
|
|
429
429
|
console.warn(
|
|
430
430
|
`Refusing to refresh the guardian token over an insecure URL (${gatewayUrl}). ` +
|
|
431
431
|
"The refresh token is only sent over https or a loopback address: " +
|
|
432
|
-
"use an https URL
|
|
432
|
+
"use an https URL, e.g. a tunnel, or connect over loopback.",
|
|
433
433
|
);
|
|
434
434
|
return {
|
|
435
435
|
ok: false,
|
package/src/lib/hatch-local.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
setActiveAssistant,
|
|
22
22
|
} from "./assistant-config.js";
|
|
23
23
|
import type { AssistantEntry } from "./assistant-config.js";
|
|
24
|
+
import { green, yellow } from "./color.js";
|
|
24
25
|
import { buildHatchConfigValues, writeInitialConfig } from "./config-utils.js";
|
|
25
26
|
import {
|
|
26
27
|
generateLocalSigningKey,
|
|
@@ -379,14 +380,18 @@ export async function hatchLocal(
|
|
|
379
380
|
) {
|
|
380
381
|
const delayMs = 2000 * 2 ** (attempt - 1);
|
|
381
382
|
reporter.error(
|
|
382
|
-
|
|
383
|
+
yellow(
|
|
384
|
+
`⚠️ Guardian token lease attempt ${attempt}/${maxLeaseAttempts} failed — retrying in ${delayMs / 1000}s: ${err}`,
|
|
385
|
+
),
|
|
383
386
|
);
|
|
384
387
|
await new Promise((r) => setTimeout(r, delayMs));
|
|
385
388
|
} else {
|
|
386
389
|
reporter.error(
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
+
yellow(
|
|
391
|
+
`⚠️ Guardian token lease failed${err instanceof GuardianLeasePermanentError ? ", not retrying — a malformed 200 will not fix itself" : ` after ${maxLeaseAttempts} attempts`}: ${err}\n` +
|
|
392
|
+
` The assistant is running but guardian-token.json was not written.\n` +
|
|
393
|
+
` Run \`cadet restart ${JSON.stringify(instanceName)} --repair-guardian\` to re-lease a token.`,
|
|
394
|
+
),
|
|
390
395
|
);
|
|
391
396
|
}
|
|
392
397
|
}
|
|
@@ -398,8 +403,10 @@ export async function hatchLocal(
|
|
|
398
403
|
|
|
399
404
|
if (provider !== undefined && provider !== null && !guardianAccessToken) {
|
|
400
405
|
reporter.error(
|
|
401
|
-
|
|
402
|
-
|
|
406
|
+
yellow(
|
|
407
|
+
`⚠️ Provider credential setup skipped because the guardian token was not leased.\n` +
|
|
408
|
+
` The assistant is still hatched. Run \`cadet setup --provider ${provider}\` after fixing the connection.`,
|
|
409
|
+
),
|
|
403
410
|
);
|
|
404
411
|
} else if (provider !== undefined) {
|
|
405
412
|
await configureHatchProviderApiKey({
|
|
@@ -429,7 +436,7 @@ export async function hatchLocal(
|
|
|
429
436
|
}
|
|
430
437
|
|
|
431
438
|
reporter.log("");
|
|
432
|
-
reporter.log(`✅ Local assistant hatched
|
|
439
|
+
reporter.log(green(`✅ Local assistant hatched!`, process.stdout));
|
|
433
440
|
printInstanceDetails(
|
|
434
441
|
{
|
|
435
442
|
name: instanceName,
|
|
@@ -438,7 +445,7 @@ export async function hatchLocal(
|
|
|
438
445
|
},
|
|
439
446
|
(message) => reporter.log(message),
|
|
440
447
|
);
|
|
441
|
-
logHatchNextSteps((message) => reporter.log(message)
|
|
448
|
+
logHatchNextSteps((message) => reporter.log(message));
|
|
442
449
|
|
|
443
450
|
const result: HatchLocalResult = {
|
|
444
451
|
assistantId: instanceName,
|
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
export function logHatchNextSteps(
|
|
2
|
-
log: (message: string) => void,
|
|
3
|
-
verbose = false,
|
|
4
|
-
): void {
|
|
1
|
+
export function logHatchNextSteps(log: (message: string) => void): void {
|
|
5
2
|
log("Next steps:");
|
|
6
|
-
log(" cadet client");
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
3
|
+
log(" - Run `cadet client` to interact with your cadet via the terminal");
|
|
4
|
+
log(
|
|
5
|
+
" - Run `cadet tunnel` to make your cadet publicly accessible and interact with it via the browser",
|
|
6
|
+
);
|
|
11
7
|
log("");
|
|
12
8
|
}
|
package/src/lib/job-polling.ts
CHANGED
|
@@ -166,7 +166,7 @@ export async function pollJobUntilDone(
|
|
|
166
166
|
throw err;
|
|
167
167
|
}
|
|
168
168
|
const msg = err instanceof Error ? err.message : String(err);
|
|
169
|
-
console.warn(`${options.label} polling failed
|
|
169
|
+
console.warn(`${options.label} polling failed: ${msg}. Retrying...`);
|
|
170
170
|
if (Date.now() >= deadline) {
|
|
171
171
|
throw new Error(
|
|
172
172
|
`Timed out waiting for ${options.label} after ${Math.round(
|
package/src/lib/local.ts
CHANGED
|
@@ -937,7 +937,7 @@ async function startDaemonWatchFromSource(
|
|
|
937
937
|
}
|
|
938
938
|
writeFileSync(pidFile, String(daemonPid), "utf-8");
|
|
939
939
|
|
|
940
|
-
console.log(" Assistant started in watch mode
|
|
940
|
+
console.log(" Assistant started in watch mode with bun --watch");
|
|
941
941
|
return trackDaemonSpawn(child);
|
|
942
942
|
}
|
|
943
943
|
|
|
@@ -984,6 +984,41 @@ function resolveGatewayDir(resources?: LocalInstanceResources): string {
|
|
|
984
984
|
}
|
|
985
985
|
}
|
|
986
986
|
|
|
987
|
+
/**
|
|
988
|
+
* How to invoke a `gateway <subcommand>` CLI process so it resolves the same
|
|
989
|
+
* workspace AND gateway security dir as the locally spawned gateway server:
|
|
990
|
+
* run the gateway package's CLI entry through bun with the spawn state root
|
|
991
|
+
* as cwd and the instance-pinned CADET_WORKSPACE_DIR / GATEWAY_SECURITY_DIR
|
|
992
|
+
* the server was given (the file-backed pairing store must resolve
|
|
993
|
+
* identically in both processes). Callers pass the resolved assistant
|
|
994
|
+
* entry's `resources` (undefined for the default instance, where both
|
|
995
|
+
* processes fall back to the same cwd-relative paths).
|
|
996
|
+
*/
|
|
997
|
+
export function gatewayCliInvocation(resources?: LocalInstanceResources): {
|
|
998
|
+
command: string;
|
|
999
|
+
args: string[];
|
|
1000
|
+
cwd: string;
|
|
1001
|
+
env: Record<string, string | undefined>;
|
|
1002
|
+
} {
|
|
1003
|
+
return {
|
|
1004
|
+
command: resolveBunExecutable(),
|
|
1005
|
+
args: ["run", join(resolveGatewayDir(resources), "src/cli/index.ts")],
|
|
1006
|
+
cwd: spawnStateRoot(),
|
|
1007
|
+
env: {
|
|
1008
|
+
...process.env,
|
|
1009
|
+
...(resources
|
|
1010
|
+
? {
|
|
1011
|
+
CADET_WORKSPACE_DIR: join(resources.instanceDir, "workspace"),
|
|
1012
|
+
GATEWAY_SECURITY_DIR: join(
|
|
1013
|
+
resources.instanceDir,
|
|
1014
|
+
"gateway-security",
|
|
1015
|
+
),
|
|
1016
|
+
}
|
|
1017
|
+
: {}),
|
|
1018
|
+
},
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
|
|
987
1022
|
function resolveCesDir(resources?: LocalInstanceResources): string {
|
|
988
1023
|
const runtimeCesDir = localRuntimeCesDir(resources);
|
|
989
1024
|
if (runtimeCesDir) return runtimeCesDir;
|
|
@@ -1118,9 +1153,7 @@ export async function startCes(
|
|
|
1118
1153
|
// Source tree / bunx: run the CES entry point via bun.
|
|
1119
1154
|
const cesDir = runtimeCesDir ?? resolveCesDir(resources);
|
|
1120
1155
|
const cesEntry = join(cesDir, "src/main.ts");
|
|
1121
|
-
const bunArgs = watch
|
|
1122
|
-
? ["--watch", "run", cesEntry]
|
|
1123
|
-
: ["run", cesEntry];
|
|
1156
|
+
const bunArgs = watch ? ["--watch", "run", cesEntry] : ["run", cesEntry];
|
|
1124
1157
|
const cesLogFd = openLogFile("hatch.log");
|
|
1125
1158
|
ces = spawn(resolveBunExecutable(), bunArgs, {
|
|
1126
1159
|
cwd: spawnStateRoot(),
|
package/src/lib/nginx-ingress.ts
CHANGED
|
@@ -177,8 +177,6 @@ function remoteWebIngressConfig(
|
|
|
177
177
|
return {
|
|
178
178
|
remote: EDGE_SERVED_REMOTE,
|
|
179
179
|
apiBaseUrl: "/v1",
|
|
180
|
-
cloudDisabled: true,
|
|
181
|
-
disableCloud: true,
|
|
182
180
|
...(opts.assistantName ? { assistantName: opts.assistantName } : {}),
|
|
183
181
|
...(opts.assistantId ? { assistantId: opts.assistantId } : {}),
|
|
184
182
|
...(opts.hubUrl ? { hubUrl: opts.hubUrl } : {}),
|
|
@@ -191,7 +189,7 @@ function remoteWebIngressConfig(
|
|
|
191
189
|
* fingerprint matches, so this must change whenever the generated index or
|
|
192
190
|
* nginx template does.
|
|
193
191
|
*/
|
|
194
|
-
export const EDGE_TEMPLATE_VERSION =
|
|
192
|
+
export const EDGE_TEMPLATE_VERSION = 10;
|
|
195
193
|
|
|
196
194
|
/**
|
|
197
195
|
* Stable fingerprint of the SPA config injected into the served index and
|
|
@@ -1162,13 +1160,13 @@ export async function ensureTunnelEdge(opts: {
|
|
|
1162
1160
|
case "nginx-missing":
|
|
1163
1161
|
throw new Error(
|
|
1164
1162
|
"nginx is not installed, so the tunnel edge cannot start. " +
|
|
1165
|
-
"
|
|
1163
|
+
"See https://nginx.org/en/docs/install.html for how to install, " +
|
|
1166
1164
|
"or point NGINX_BIN at an existing binary.",
|
|
1167
1165
|
);
|
|
1168
1166
|
case "web-dist-missing":
|
|
1169
1167
|
throw new Error(
|
|
1170
1168
|
"Unable to locate built web assets for the remote web edge. " +
|
|
1171
|
-
"Build the SPA
|
|
1169
|
+
"Build the SPA with `cd clients/web && VITE_CLOUD_MODE=false bun run build`, " +
|
|
1172
1170
|
"or install @mycadet/web so its packaged dist directory is available.",
|
|
1173
1171
|
);
|
|
1174
1172
|
case "unreachable":
|
package/src/lib/pair.ts
CHANGED
|
@@ -50,6 +50,6 @@ export function formatWebApproveFailure(
|
|
|
50
50
|
return [
|
|
51
51
|
`${rejection} ${gatewayUrl} (assistant "${assistantReference}", environment "${envName}").`,
|
|
52
52
|
`Codes are minted by the gateway that serves the pair page, expire after ${CODE_TTL_MINUTES} minutes, and are single-use.`,
|
|
53
|
-
"If the pair page came from a different assistant or environment
|
|
53
|
+
"If the pair page came from a different assistant or environment, such as the desktop app's, re-run with that environment's CADET_ENVIRONMENT and that assistant's name.",
|
|
54
54
|
].join("\n");
|
|
55
55
|
}
|
package/src/lib/step-runner.ts
CHANGED
|
@@ -112,11 +112,16 @@ export function execWithStdin(
|
|
|
112
112
|
export function execOutput(
|
|
113
113
|
command: string,
|
|
114
114
|
args: string[],
|
|
115
|
-
options: {
|
|
115
|
+
options: {
|
|
116
|
+
cwd?: string;
|
|
117
|
+
timeoutMs?: number;
|
|
118
|
+
env?: Record<string, string | undefined>;
|
|
119
|
+
} = {},
|
|
116
120
|
): Promise<string> {
|
|
117
121
|
return new Promise((resolve, reject) => {
|
|
118
122
|
const child = spawn(command, args, {
|
|
119
123
|
cwd: options.cwd,
|
|
124
|
+
env: options.env,
|
|
120
125
|
stdio: ["pipe", "pipe", "pipe"],
|
|
121
126
|
windowsHide: true,
|
|
122
127
|
});
|
|
@@ -88,7 +88,7 @@ export async function syncCloudAssistants(
|
|
|
88
88
|
log?.("Fetching Cadet Cloud assistants…");
|
|
89
89
|
cloudAssistants = await fetchCloudAssistants(token);
|
|
90
90
|
log?.(
|
|
91
|
-
`Cadet Cloud returned ${cloudAssistants.length} assistant
|
|
91
|
+
`Cadet Cloud returned ${cloudAssistants.length} ${cloudAssistants.length === 1 ? "assistant" : "assistants"}: ${cloudAssistants.map((a) => a.name || a.id).join(", ") || "(none)"}`,
|
|
92
92
|
);
|
|
93
93
|
} catch (err) {
|
|
94
94
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -118,7 +118,7 @@ export async function syncCloudAssistants(
|
|
|
118
118
|
);
|
|
119
119
|
const existingCloudIds = new Set(existingCloudById.keys());
|
|
120
120
|
log?.(
|
|
121
|
-
`Lockfile has ${existingCloudIds.size} cloud assistant
|
|
121
|
+
`Lockfile has ${existingCloudIds.size} ${existingCloudIds.size === 1 ? "cloud assistant" : "cloud assistants"}: ${[...existingCloudIds].join(", ") || "(none)"}`,
|
|
122
122
|
);
|
|
123
123
|
|
|
124
124
|
let added = 0;
|
|
@@ -2,6 +2,8 @@ import type { ProviderMeta, ProviderTunnelResult } from "../tunnel.js";
|
|
|
2
2
|
|
|
3
3
|
import { execFileSync, spawn, type ChildProcess } from "node:child_process";
|
|
4
4
|
|
|
5
|
+
import { ensureBinaryInstalled } from "../ensure-binary.js";
|
|
6
|
+
|
|
5
7
|
import { GATEWAY_PORT } from "../constants.js";
|
|
6
8
|
import {
|
|
7
9
|
clearIngressUrl,
|
|
@@ -138,18 +140,26 @@ export interface RunCloudflareTunnelOptions {
|
|
|
138
140
|
export async function runCloudflareTunnel(
|
|
139
141
|
opts: RunCloudflareTunnelOptions = {},
|
|
140
142
|
): Promise<ProviderTunnelResult> {
|
|
141
|
-
const version =
|
|
143
|
+
const version = await ensureBinaryInstalled({
|
|
144
|
+
binary: "cloudflared",
|
|
145
|
+
purpose: "the cloudflare tunnel",
|
|
146
|
+
packages: {
|
|
147
|
+
brew: ["brew", "install", "cloudflare/cloudflare/cloudflared"],
|
|
148
|
+
winget: [
|
|
149
|
+
"winget",
|
|
150
|
+
"install",
|
|
151
|
+
"--silent",
|
|
152
|
+
"--accept-package-agreements",
|
|
153
|
+
"--accept-source-agreements",
|
|
154
|
+
"Cloudflare.cloudflared",
|
|
155
|
+
],
|
|
156
|
+
choco: ["choco", "install", "-y", "cloudflared"],
|
|
157
|
+
},
|
|
158
|
+
manualHint:
|
|
159
|
+
"See https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/ for how to install",
|
|
160
|
+
probe: getCloudflareTunnelVersion,
|
|
161
|
+
});
|
|
142
162
|
if (!version) {
|
|
143
|
-
console.error("Error: cloudflared is not installed.");
|
|
144
|
-
console.error("");
|
|
145
|
-
console.error("Install cloudflared:");
|
|
146
|
-
console.error(" macOS: brew install cloudflare/cloudflare/cloudflared");
|
|
147
|
-
console.error(" Linux: https://pkg.cloudflare.com/index.html");
|
|
148
|
-
console.error(
|
|
149
|
-
" Windows: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/",
|
|
150
|
-
);
|
|
151
|
-
console.error("");
|
|
152
|
-
console.error("No Cloudflare account is required for quick tunnels.");
|
|
153
163
|
process.exit(1);
|
|
154
164
|
}
|
|
155
165
|
|