@mycadet/cli 0.12.7 → 0.12.9
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 +2 -2
- package/knip.json +1 -1
- package/node_modules/@mycadet/assistant-api/events/cloud-disconnected.ts +21 -0
- package/node_modules/@mycadet/assistant-api/events/host-bash.ts +1 -1
- package/node_modules/@mycadet/assistant-api/events/host-cu.ts +1 -1
- package/node_modules/@mycadet/assistant-api/events/host-file.ts +1 -1
- package/node_modules/@mycadet/assistant-api/events/notification-intent.ts +1 -1
- package/node_modules/@mycadet/assistant-api/events/show-cloud-login.ts +18 -0
- package/node_modules/@mycadet/assistant-api/index.ts +11 -11
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/package.json +2 -2
- package/node_modules/@mycadet/{service-contracts/src/platform-credential.ts → assistant-api/node_modules/@mycadet/service-contracts/src/cloud-credential.ts} +9 -9
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
- package/node_modules/@mycadet/assistant-api/responses/home.ts +1 -1
- package/node_modules/@mycadet/assistant-api/surfaces.ts +4 -4
- package/node_modules/@mycadet/avatar-manifest/src/__tests__/notification-avatar.test.ts +1 -1
- package/node_modules/@mycadet/avatar-manifest/src/notification-avatar.ts +2 -2
- package/node_modules/@mycadet/environments/src/seeds.ts +6 -6
- package/node_modules/@mycadet/environments/src/shell.test.ts +1 -1
- package/node_modules/@mycadet/environments/src/types.ts +6 -6
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/avatar-manifest/src/__tests__/notification-avatar.test.ts +1 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/avatar-manifest/src/notification-avatar.ts +2 -2
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/environments/src/seeds.ts +6 -6
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/environments/src/shell.test.ts +1 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/environments/src/types.ts +6 -6
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/package.json +2 -2
- package/node_modules/@mycadet/{assistant-api/node_modules/@mycadet/service-contracts/src/platform-credential.ts → local-mode/node_modules/@mycadet/service-contracts/src/cloud-credential.ts} +9 -9
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
- package/node_modules/@mycadet/local-mode/src/__tests__/pair.test.ts +5 -5
- package/node_modules/@mycadet/local-mode/src/__tests__/retire.test.ts +1 -1
- package/node_modules/@mycadet/local-mode/src/config.ts +2 -2
- package/node_modules/@mycadet/local-mode/src/index.ts +1 -1
- package/node_modules/@mycadet/local-mode/src/lockfile-contract.test.ts +3 -3
- package/node_modules/@mycadet/local-mode/src/lockfile-contract.ts +10 -5
- package/node_modules/@mycadet/local-mode/src/lockfile.test.ts +15 -15
- package/node_modules/@mycadet/local-mode/src/lockfile.ts +9 -9
- package/node_modules/@mycadet/local-mode/src/pair.ts +2 -2
- package/node_modules/@mycadet/local-mode/src/retire.ts +3 -3
- package/node_modules/@mycadet/local-mode/src/teleport-backup-policy.ts +2 -2
- package/node_modules/@mycadet/local-mode/src/unpair.ts +2 -2
- package/node_modules/@mycadet/service-contracts/package.json +2 -2
- package/node_modules/@mycadet/{local-mode/node_modules/@mycadet/service-contracts/src/platform-credential.ts → service-contracts/src/cloud-credential.ts} +9 -9
- package/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
- package/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
- package/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
- package/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
- package/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
- package/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
- package/package.json +3 -2
- package/src/__tests__/assistant-api-key-resolution.test.ts +6 -6
- package/src/__tests__/{assistant-client-platform-auth.test.ts → assistant-client-cloud-auth.test.ts} +10 -10
- package/src/__tests__/assistant-client-refresh.test.ts +2 -2
- package/src/__tests__/backup.test.ts +86 -91
- package/src/__tests__/client-token.test.ts +1 -1
- package/src/__tests__/client-tui-refresh.test.ts +1 -1
- package/src/__tests__/client-web-config.test.ts +2 -2
- package/src/__tests__/{platform-client.test.ts → cloud-client.test.ts} +59 -59
- package/src/__tests__/{platform-releases.test.ts → cloud-releases.test.ts} +28 -18
- package/src/__tests__/{cloudflare-tunnel.test.ts → cloudflare.test.ts} +1 -1
- package/src/__tests__/connect-import.test.ts +55 -39
- package/src/__tests__/debug.test.ts +38 -38
- package/src/__tests__/ensure-binary.test.ts +182 -0
- package/src/__tests__/guardian-token.test.ts +149 -56
- package/src/__tests__/hatch-provider-secrets.test.ts +2 -2
- package/src/__tests__/helpers/guardian-token.ts +22 -0
- package/src/__tests__/helpers/http.ts +10 -0
- package/src/__tests__/nginx-ingress.test.ts +21 -20
- package/src/__tests__/ngrok-spawn.test.ts +49 -0
- package/src/__tests__/orphan-detection.test.ts +1 -1
- package/src/__tests__/pair.test.ts +1 -8
- package/src/__tests__/{ps-platform-status.test.ts → ps-cloud-status.test.ts} +79 -26
- package/src/__tests__/ps-table.test.ts +79 -0
- package/src/__tests__/{recover.test.ts → restore.test.ts} +29 -28
- package/src/__tests__/retire.test.ts +58 -199
- package/src/__tests__/root-process-lockfile.test.ts +99 -0
- package/src/__tests__/sleep.test.ts +19 -19
- package/src/__tests__/statefulset.test.ts +8 -8
- package/src/__tests__/{tailscale-tunnel.test.ts → tailscale.test.ts} +2 -2
- package/src/__tests__/teleport-backup.test.ts +3 -3
- package/src/__tests__/teleport.test.ts +181 -197
- package/src/__tests__/tunnel.test.ts +257 -222
- package/src/__tests__/uninstall.test.ts +13 -4
- package/src/__tests__/upgrade-local.test.ts +3 -3
- package/src/__tests__/upgrade-replay-env.test.ts +12 -12
- package/src/__tests__/wake.test.ts +260 -43
- package/src/__tests__/workos-pkce.test.ts +2 -2
- package/src/commands/backup.ts +24 -24
- package/src/commands/client.ts +76 -76
- package/src/commands/completions.test.ts +104 -0
- package/src/commands/completions.ts +87 -0
- package/src/commands/debug.ts +20 -20
- package/src/commands/exec.ts +12 -12
- package/src/commands/flags.ts +2 -2
- package/src/commands/hatch.ts +23 -23
- package/src/commands/login.ts +22 -22
- package/src/commands/logs.ts +8 -8
- package/src/commands/ps.ts +244 -86
- package/src/commands/restart.ts +30 -0
- package/src/commands/restore.ts +140 -22
- package/src/commands/retire.ts +48 -150
- package/src/commands/rollback.ts +20 -26
- package/src/commands/setup.ts +2 -2
- package/src/commands/sleep.ts +2 -2
- package/src/commands/teleport.ts +109 -109
- package/src/commands/terminal.ts +158 -18
- package/src/commands/tunnel.ts +138 -157
- package/src/commands/uninstall.ts +57 -9
- package/src/commands/unpair.ts +3 -3
- package/src/commands/upgrade.ts +30 -54
- package/src/commands/use.ts +1 -1
- package/src/commands/wake.ts +166 -50
- package/src/index.ts +18 -9
- package/src/lib/__tests__/{platform-client-signed-url.test.ts → cloud-client-signed-url.test.ts} +16 -16
- package/src/lib/__tests__/docker.test.ts +168 -20
- package/src/lib/__tests__/job-polling.test.ts +2 -2
- package/src/lib/__tests__/local-runtime-client.test.ts +4 -4
- package/src/lib/__tests__/runtime-url.test.ts +1 -1
- package/src/lib/assistant-api-key-resolution.ts +5 -5
- package/src/lib/assistant-client.ts +8 -8
- package/src/lib/assistant-config.ts +58 -12
- package/src/lib/{platform-client.ts → cloud-client.ts} +107 -98
- package/src/lib/cloud-login.ts +24 -24
- package/src/lib/{platform-releases.ts → cloud-releases.ts} +11 -9
- package/src/lib/cloud-values.ts +3 -0
- package/src/lib/config-utils.ts +1 -1
- package/src/lib/detached-process.ts +1 -1
- package/src/lib/docker.ts +260 -53
- package/src/lib/ensure-binary.ts +171 -0
- package/src/lib/environments/__tests__/paths.test.ts +2 -2
- package/src/lib/environments/__tests__/resolve.test.ts +26 -26
- package/src/lib/environments/resolve.ts +8 -8
- package/src/lib/flag-args.test.ts +1 -1
- package/src/lib/flag-args.ts +1 -1
- package/src/lib/guardian-token.ts +37 -5
- package/src/lib/hatch-local.ts +19 -23
- package/src/lib/health-check.ts +6 -6
- package/src/lib/ingress-config.ts +8 -35
- package/src/lib/job-polling.ts +2 -2
- package/src/lib/live-voice/connection.ts +12 -12
- package/src/lib/live-voice/tunnel.test.ts +11 -11
- package/src/lib/live-voice/tunnel.ts +15 -15
- package/src/lib/local-runtime-client.ts +20 -20
- package/src/lib/local.ts +10 -31
- package/src/lib/nginx-ingress.ts +42 -15
- package/src/lib/pair.ts +1 -1
- package/src/lib/process.ts +1 -1
- package/src/lib/provider-secrets.ts +3 -3
- package/src/lib/retire-apple-container.ts +1 -1
- package/src/lib/runtime-url.ts +8 -8
- package/src/lib/statefulset.ts +2 -2
- package/src/lib/sync-cloud-assistants.ts +19 -19
- package/src/lib/teleport-backup.ts +4 -4
- package/src/lib/terminal-client.ts +17 -17
- package/src/lib/terminal-session.ts +24 -24
- package/src/lib/{tunnel-edge.ts → tunnel.ts} +199 -11
- package/src/lib/tunnels/cadet.ts +29 -0
- package/src/lib/{cloudflare-tunnel.ts → tunnels/cloudflare.ts} +36 -15
- package/src/lib/tunnels/gateway-notify.ts +61 -0
- package/src/lib/{ngrok.ts → tunnels/ngrok.ts} +350 -89
- package/src/lib/{tailscale-tunnel.ts → tunnels/tailscale.ts} +53 -9
- package/src/lib/upgrade-lifecycle.ts +8 -21
- package/src/lib/upgrade-preflight.ts +2 -2
- package/src/lib/workos-pkce.ts +5 -5
- package/node_modules/@mycadet/assistant-api/events/platform-disconnected.ts +0 -21
- package/node_modules/@mycadet/assistant-api/events/show-platform-login.ts +0 -20
- package/src/commands/recover.ts +0 -131
- package/src/commands/roadmap.ts +0 -459
- package/src/commands/ssh.ts +0 -144
- package/src/commands/workflows.ts +0 -305
- package/src/lib/managed-cloud.ts +0 -3
package/src/commands/tunnel.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isCloudHome } from "../lib/cloud-values.js";
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
|
|
@@ -14,41 +14,37 @@ import {
|
|
|
14
14
|
type AssistantEntry,
|
|
15
15
|
} from "../lib/assistant-config";
|
|
16
16
|
import { parseAssistantTargetArg } from "../lib/assistant-target-args.js";
|
|
17
|
-
import { runCloudflareTunnel } from "../lib/cloudflare-tunnel.js";
|
|
18
17
|
import { relaunchDetached } from "../lib/detached-process.js";
|
|
19
18
|
import {
|
|
20
19
|
getDefaultWorkspaceDir,
|
|
21
20
|
isLocalContainerEntry,
|
|
22
21
|
parseGatewayPortFromEntryUrls,
|
|
23
|
-
saveNgrokDomain,
|
|
24
22
|
} from "../lib/ingress-config.js";
|
|
25
23
|
import {
|
|
26
24
|
ensureTunnelEdge,
|
|
27
|
-
|
|
25
|
+
getNginxVersion,
|
|
28
26
|
type TunnelEdge,
|
|
29
27
|
} from "../lib/nginx-ingress.js";
|
|
30
|
-
import {
|
|
31
|
-
import { shellArg } from "../lib/shell-arg.js";
|
|
28
|
+
import { ensureBinaryInstalled } from "../lib/ensure-binary.js";
|
|
32
29
|
import { STALE_CLI_UPDATE_HINT } from "../lib/stale-cli-hint.js";
|
|
33
|
-
import {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
};
|
|
30
|
+
import {
|
|
31
|
+
ADOPTED_TUNNEL_RE,
|
|
32
|
+
DEFAULT_TUNNEL_PROVIDER,
|
|
33
|
+
PROVIDER_META,
|
|
34
|
+
adoptedTunnelStopNotice,
|
|
35
|
+
cadetTunnelComingSoonError,
|
|
36
|
+
clearReservedDomain,
|
|
37
|
+
hasWebhookIntegrationsConfigured,
|
|
38
|
+
providerSupportsReservedDomain,
|
|
39
|
+
runProviderTunnel,
|
|
40
|
+
} from "../lib/tunnel.js";
|
|
41
|
+
|
|
42
|
+
// The accepted `--provider` values ARE the shared registry, so validation
|
|
43
|
+
// here cannot drift from what the workspace config accepts.
|
|
48
44
|
|
|
49
45
|
interface TunnelArgs {
|
|
50
46
|
assistantName: string | null;
|
|
51
|
-
provider:
|
|
47
|
+
provider: TunnelProviderName;
|
|
52
48
|
domain: string | null;
|
|
53
49
|
clearDomain: boolean;
|
|
54
50
|
detach: boolean;
|
|
@@ -58,7 +54,7 @@ const FLAGS_WITH_VALUES = ["--provider", "--domain"] as const;
|
|
|
58
54
|
|
|
59
55
|
function parseArgs(): TunnelArgs {
|
|
60
56
|
const args = process.argv.slice(3);
|
|
61
|
-
let provider:
|
|
57
|
+
let provider: TunnelProviderName | null = null;
|
|
62
58
|
let domain: string | null = null;
|
|
63
59
|
let clearDomain = false;
|
|
64
60
|
let detach = false;
|
|
@@ -76,42 +72,44 @@ function parseArgs(): TunnelArgs {
|
|
|
76
72
|
);
|
|
77
73
|
console.log("");
|
|
78
74
|
console.log(
|
|
79
|
-
"How far that URL reaches depends on the provider
|
|
75
|
+
"How far that URL reaches depends on the provider; omit --provider for",
|
|
80
76
|
);
|
|
81
77
|
console.log(
|
|
82
|
-
"A tailscale tunnel is reachable only from your
|
|
78
|
+
"the default. A tailscale tunnel is reachable only from your",
|
|
83
79
|
);
|
|
84
80
|
console.log(
|
|
85
|
-
"integrations need a public tunnel
|
|
81
|
+
"own tailnet; webhook integrations need a public tunnel. See Providers",
|
|
86
82
|
);
|
|
83
|
+
console.log("below.");
|
|
87
84
|
console.log("");
|
|
88
85
|
console.log(
|
|
89
86
|
"The tunnel always fronts the local nginx edge, which is started automatically.",
|
|
90
87
|
);
|
|
91
88
|
console.log(
|
|
92
|
-
"nginx must be installed
|
|
89
|
+
"nginx must be installed; if it is missing, cadet offers to install it.",
|
|
93
90
|
);
|
|
94
91
|
console.log("");
|
|
95
92
|
console.log("Arguments:");
|
|
96
93
|
console.log(
|
|
97
|
-
" <name> Name of the assistant
|
|
94
|
+
" <name> Name of the assistant, defaults to active or only local",
|
|
98
95
|
);
|
|
99
96
|
console.log("");
|
|
100
97
|
console.log("Options:");
|
|
101
98
|
console.log(
|
|
102
|
-
` --provider <provider>
|
|
99
|
+
` --provider <provider> Tunnel provider: ${TUNNEL_PROVIDERS.join(", ")} — default ${DEFAULT_TUNNEL_PROVIDER}`,
|
|
103
100
|
);
|
|
104
101
|
console.log(
|
|
105
|
-
" --domain <domain> Reserved
|
|
102
|
+
" --domain <domain> Reserved domain to bind, for providers that support",
|
|
106
103
|
);
|
|
107
104
|
console.log(
|
|
108
|
-
" Saved to the workspace config so `cadet wake` restores
|
|
105
|
+
" one. Saved to the workspace config so `cadet wake` restores",
|
|
109
106
|
);
|
|
107
|
+
console.log(" reuse it.");
|
|
110
108
|
console.log(
|
|
111
|
-
" --clear-domain Clear the saved
|
|
109
|
+
" --clear-domain Clear the saved reserved domain (same providers) and",
|
|
112
110
|
);
|
|
113
111
|
console.log(
|
|
114
|
-
" without one. Cannot be combined with --domain.",
|
|
112
|
+
" tunnel without one. Cannot be combined with --domain.",
|
|
115
113
|
);
|
|
116
114
|
console.log(
|
|
117
115
|
` -d, --detach Run the tunnel in the background. Waits up to ${TUNNEL_BACKGROUND_START_TIMEOUT_MS / 1000}s for the`,
|
|
@@ -123,72 +121,66 @@ function parseArgs(): TunnelArgs {
|
|
|
123
121
|
" the CLI log directory. Stop it with `kill <pid>`, except when it",
|
|
124
122
|
);
|
|
125
123
|
console.log(
|
|
126
|
-
" adopted an already-running
|
|
124
|
+
" adopted an already-running tunnel (started outside this",
|
|
127
125
|
);
|
|
128
126
|
console.log(
|
|
129
127
|
" command): killing the supervisor then leaves that tunnel running.",
|
|
130
128
|
);
|
|
131
129
|
console.log("");
|
|
132
130
|
console.log("Providers:");
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
);
|
|
142
|
-
console.log(
|
|
143
|
-
" No Cloudflare account required for quick tunnels.",
|
|
144
|
-
);
|
|
145
|
-
console.log(
|
|
146
|
-
" tailscale Tailscale serve. Install: https://tailscale.com/download, then run `tailscale up`",
|
|
147
|
-
);
|
|
148
|
-
console.log(
|
|
149
|
-
" Reachable only from your own tailnet (private; LetsEncrypt cert).",
|
|
150
|
-
);
|
|
151
|
-
console.log(
|
|
152
|
-
" When webhook integrations are configured it records its URL for device",
|
|
153
|
-
);
|
|
154
|
-
console.log(
|
|
155
|
-
" pairing only, leaving the saved webhook callback base as it is.",
|
|
156
|
-
);
|
|
131
|
+
for (const name of TUNNEL_PROVIDERS) {
|
|
132
|
+
const meta = PROVIDER_META[name];
|
|
133
|
+
console.log(` ${name.padEnd(12)}${meta.reach}`);
|
|
134
|
+
console.log(` ${" ".repeat(12)}${meta.install}`);
|
|
135
|
+
for (const note of meta.notes ?? []) {
|
|
136
|
+
console.log(` ${" ".repeat(12)}${note}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
157
139
|
console.log("");
|
|
158
140
|
console.log("Examples:");
|
|
159
|
-
console.log(
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
)
|
|
163
|
-
|
|
141
|
+
console.log(` $ cadet tunnel --provider ${DEFAULT_TUNNEL_PROVIDER}`);
|
|
142
|
+
const domainExample =
|
|
143
|
+
PROVIDER_META[DEFAULT_TUNNEL_PROVIDER].domainExample;
|
|
144
|
+
if (domainExample) {
|
|
145
|
+
console.log(
|
|
146
|
+
` $ cadet tunnel --provider ${DEFAULT_TUNNEL_PROVIDER} --domain ${domainExample}`,
|
|
147
|
+
);
|
|
148
|
+
console.log(
|
|
149
|
+
` $ cadet tunnel --provider ${DEFAULT_TUNNEL_PROVIDER} --clear-domain`,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
164
152
|
console.log(" $ cadet tunnel --provider cloudflare");
|
|
165
153
|
console.log(" $ cadet tunnel my-assistant --provider tailscale");
|
|
166
|
-
console.log(
|
|
154
|
+
console.log(
|
|
155
|
+
` $ cadet tunnel --provider ${DEFAULT_TUNNEL_PROVIDER} --detach`,
|
|
156
|
+
);
|
|
167
157
|
process.exit(0);
|
|
168
158
|
} else if (arg === "--provider") {
|
|
169
159
|
const next = args[i + 1];
|
|
170
160
|
if (!next || next.startsWith("-")) {
|
|
171
161
|
console.error(
|
|
172
|
-
`Error: --provider requires one of: ${
|
|
162
|
+
`Error: --provider requires one of: ${TUNNEL_PROVIDERS.join(", ")}`,
|
|
173
163
|
);
|
|
174
164
|
process.exit(1);
|
|
175
165
|
}
|
|
176
|
-
if (!
|
|
166
|
+
if (!TUNNEL_PROVIDERS.includes(next as TunnelProviderName)) {
|
|
177
167
|
console.error(
|
|
178
|
-
`Error: unknown tunnel provider '${next}'. Valid providers: ${
|
|
168
|
+
`Error: unknown tunnel provider '${next}'. Valid providers: ${TUNNEL_PROVIDERS.join(", ")}.`,
|
|
179
169
|
);
|
|
180
170
|
console.error(
|
|
181
171
|
`If this provider is documented, ${STALE_CLI_UPDATE_HINT}`,
|
|
182
172
|
);
|
|
183
173
|
process.exit(1);
|
|
184
174
|
}
|
|
185
|
-
provider = next as
|
|
175
|
+
provider = next as TunnelProviderName;
|
|
186
176
|
i++;
|
|
187
177
|
} else if (arg === "--domain") {
|
|
188
178
|
const next = args[i + 1];
|
|
189
179
|
if (!next || next.startsWith("-")) {
|
|
180
|
+
const domainExample =
|
|
181
|
+
PROVIDER_META[DEFAULT_TUNNEL_PROVIDER].domainExample;
|
|
190
182
|
console.error(
|
|
191
|
-
|
|
183
|
+
`Error: --domain requires a value${domainExample ? `, e.g. --domain ${domainExample}` : ""}`,
|
|
192
184
|
);
|
|
193
185
|
process.exit(1);
|
|
194
186
|
}
|
|
@@ -210,32 +202,20 @@ function parseArgs(): TunnelArgs {
|
|
|
210
202
|
parseAssistantTargetArg(args, FLAGS_WITH_VALUES) ?? null;
|
|
211
203
|
|
|
212
204
|
if (provider === null) {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
);
|
|
216
|
-
console.error("");
|
|
217
|
-
console.error("Run one of:");
|
|
218
|
-
// Carries the assistant through: dropping it would suggest a command that
|
|
219
|
-
// tunnels the active assistant instead of the one that was asked for.
|
|
220
|
-
const target = assistantName ? ` ${shellArg(assistantName)}` : "";
|
|
221
|
-
for (const name of TUNNEL_PROVIDERS) {
|
|
222
|
-
console.error(
|
|
223
|
-
` cadet tunnel${target} --provider ${name.padEnd(13)}${PROVIDER_REACH[name]}`,
|
|
224
|
-
);
|
|
225
|
-
}
|
|
226
|
-
process.exit(1);
|
|
205
|
+
provider = DEFAULT_TUNNEL_PROVIDER;
|
|
206
|
+
console.log(`Using the default provider: ${provider}.`);
|
|
227
207
|
}
|
|
228
208
|
|
|
229
|
-
if (domain && provider
|
|
209
|
+
if (domain && !providerSupportsReservedDomain(provider)) {
|
|
230
210
|
console.error(
|
|
231
|
-
`Error: --domain is
|
|
211
|
+
`Error: --domain is not supported with --provider ${provider} (reserved domains have no equivalent there).`,
|
|
232
212
|
);
|
|
233
213
|
process.exit(1);
|
|
234
214
|
}
|
|
235
215
|
|
|
236
|
-
if (clearDomain && provider
|
|
216
|
+
if (clearDomain && !providerSupportsReservedDomain(provider)) {
|
|
237
217
|
console.error(
|
|
238
|
-
`Error: --clear-domain is
|
|
218
|
+
`Error: --clear-domain is not supported with --provider ${provider} (reserved domains have no equivalent there).`,
|
|
239
219
|
);
|
|
240
220
|
process.exit(1);
|
|
241
221
|
}
|
|
@@ -259,7 +239,7 @@ interface LocalTunnelTarget {
|
|
|
259
239
|
|
|
260
240
|
/**
|
|
261
241
|
* Map an entry to its local tunnel target, or null when it has no locally
|
|
262
|
-
* reachable gateway (e.g.
|
|
242
|
+
* reachable gateway (e.g. cloud-hosted). Entries with `resources` carry
|
|
263
243
|
* their own gateway port and instance workspace. Local container entries
|
|
264
244
|
* (docker, apple-container) run locally without host resources: their gateway
|
|
265
245
|
* port comes from localUrl/runtimeUrl and their ingress state lives in the
|
|
@@ -284,7 +264,7 @@ function toLocalTunnelTarget(entry: AssistantEntry): LocalTunnelTarget | null {
|
|
|
284
264
|
|
|
285
265
|
function describeUntunnelableEntry(entry: AssistantEntry): string {
|
|
286
266
|
const reference = formatAssistantReference(entry);
|
|
287
|
-
return
|
|
267
|
+
return isCloudHome(entry.cloud)
|
|
288
268
|
? `Assistant '${reference}' runs on Cadet Cloud and needs no tunnel.`
|
|
289
269
|
: `Assistant '${reference}' has no locally managed runtime to tunnel.`;
|
|
290
270
|
}
|
|
@@ -292,7 +272,7 @@ function describeUntunnelableEntry(entry: AssistantEntry): string {
|
|
|
292
272
|
/**
|
|
293
273
|
* Resolve the assistant whose gateway and workspace the tunnel edge fronts.
|
|
294
274
|
* Tunnels only make sense for assistants with a local gateway; when the
|
|
295
|
-
* resolved entry has none (e.g. the active assistant is
|
|
275
|
+
* resolved entry has none (e.g. the active assistant is cloud-hosted) and
|
|
296
276
|
* no name was given, fall back to the sole local target, otherwise exit with
|
|
297
277
|
* an error naming the local assistants to pass explicitly.
|
|
298
278
|
*/
|
|
@@ -350,8 +330,8 @@ function guardTailnetOnlyWebhookIngress(workspaceDir: string): boolean {
|
|
|
350
330
|
console.warn(
|
|
351
331
|
"⚠ Webhook integrations are configured, and the tailscale tunnel is reachable only from your own tailnet, " +
|
|
352
332
|
"so it cannot serve their callbacks. The saved ingress base URL stays as it is and the tailnet URL is " +
|
|
353
|
-
"recorded for device pairing only. For a tunnel those integrations can reach too,
|
|
354
|
-
"
|
|
333
|
+
"recorded for device pairing only. For a tunnel those integrations can reach too, run `cadet tunnel --help` " +
|
|
334
|
+
"to see the public providers.",
|
|
355
335
|
);
|
|
356
336
|
return true;
|
|
357
337
|
}
|
|
@@ -359,16 +339,15 @@ function guardTailnetOnlyWebhookIngress(workspaceDir: string): boolean {
|
|
|
359
339
|
// Generous cap so a slow provider handshake (e.g. cloudflared's own ~30s
|
|
360
340
|
// quick-tunnel timeout) still counts as a successful, if slow, start.
|
|
361
341
|
const TUNNEL_BACKGROUND_START_TIMEOUT_MS = 35_000;
|
|
362
|
-
// Matches the "just established" line every provider prints, or
|
|
363
|
-
//
|
|
364
|
-
// that case does not own the tunnel it adopted).
|
|
342
|
+
// Matches the "just established" line every provider prints, or the adoption
|
|
343
|
+
// line printed when a run adopts an already-running agent's tunnel (captured
|
|
344
|
+
// separately since that case does not own the tunnel it adopted).
|
|
365
345
|
const TUNNEL_ESTABLISHED_RE = /Tunnel established: (\S+)/;
|
|
366
|
-
const TUNNEL_ADOPTED_RE = /Found existing ngrok tunnel: (\S+)/;
|
|
367
346
|
|
|
368
347
|
/**
|
|
369
348
|
* Re-invoke `cadet tunnel` (without `-d`/`--detach`) as a detached background
|
|
370
349
|
* process via `relaunchDetached`, waiting for the child to either print its
|
|
371
|
-
* "Tunnel established"
|
|
350
|
+
* "Tunnel established" or adopted-tunnel line (readiness) or
|
|
372
351
|
* exit early (failure), so a misconfigured provider still fails loudly in
|
|
373
352
|
* the terminal that ran `-d`.
|
|
374
353
|
*
|
|
@@ -404,7 +383,7 @@ async function spawnDetachedTunnel(): Promise<void> {
|
|
|
404
383
|
publicUrl = established[1];
|
|
405
384
|
return true;
|
|
406
385
|
}
|
|
407
|
-
const adoptedMatch =
|
|
386
|
+
const adoptedMatch = ADOPTED_TUNNEL_RE.exec(logText);
|
|
408
387
|
if (adoptedMatch) {
|
|
409
388
|
publicUrl = adoptedMatch[1];
|
|
410
389
|
adopted = true;
|
|
@@ -430,11 +409,7 @@ async function spawnDetachedTunnel(): Promise<void> {
|
|
|
430
409
|
}
|
|
431
410
|
console.log(`Running in background (pid ${child.pid}). Logs: ${logPath}`);
|
|
432
411
|
if (adopted) {
|
|
433
|
-
console.log(
|
|
434
|
-
`That ngrok agent was already running outside this command, so \`kill ${child.pid}\` only ` +
|
|
435
|
-
"stops this supervisor, not the ngrok tunnel or its saved ingress URL. Stop the ngrok agent " +
|
|
436
|
-
"directly to end the tunnel.",
|
|
437
|
-
);
|
|
412
|
+
console.log(adoptedTunnelStopNotice(child.pid));
|
|
438
413
|
} else {
|
|
439
414
|
console.log(`Stop with: kill ${child.pid}`);
|
|
440
415
|
}
|
|
@@ -442,72 +417,78 @@ async function spawnDetachedTunnel(): Promise<void> {
|
|
|
442
417
|
|
|
443
418
|
export const tunnel = Object.assign(
|
|
444
419
|
async function tunnel(): Promise<void> {
|
|
445
|
-
|
|
420
|
+
const { assistantName, provider, domain, clearDomain, detach } =
|
|
421
|
+
parseArgs();
|
|
446
422
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
423
|
+
if (detach) {
|
|
424
|
+
await spawnDetachedTunnel();
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
451
427
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
`If this provider is documented, ${STALE_CLI_UPDATE_HINT}`,
|
|
456
|
-
);
|
|
457
|
-
}
|
|
428
|
+
if (PROVIDER_META[provider].comingSoon) {
|
|
429
|
+
throw cadetTunnelComingSoonError(provider);
|
|
430
|
+
}
|
|
458
431
|
|
|
459
|
-
|
|
460
|
-
|
|
432
|
+
const { entry, gatewayPort, workspaceDir } =
|
|
433
|
+
resolveLocalTunnelTarget(assistantName);
|
|
461
434
|
|
|
462
|
-
|
|
463
|
-
|
|
435
|
+
const preserveIngressUrl =
|
|
436
|
+
provider === "tailscale" && guardTailnetOnlyWebhookIngress(workspaceDir);
|
|
464
437
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
438
|
+
if (clearDomain) {
|
|
439
|
+
clearReservedDomain();
|
|
440
|
+
console.log("Cleared the saved reserved domain.");
|
|
441
|
+
}
|
|
469
442
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
443
|
+
const nginxVersion = await ensureBinaryInstalled({
|
|
444
|
+
binary: "nginx",
|
|
445
|
+
purpose: "the tunnel edge",
|
|
446
|
+
packages: {
|
|
447
|
+
brew: ["brew", "install", "nginx"],
|
|
448
|
+
"apt-get": ["sudo", "apt-get", "install", "-y", "nginx"],
|
|
449
|
+
dnf: ["sudo", "dnf", "install", "-y", "nginx"],
|
|
450
|
+
pacman: ["sudo", "pacman", "-S", "--noconfirm", "nginx"],
|
|
451
|
+
zypper: ["sudo", "zypper", "install", "-y", "nginx"],
|
|
452
|
+
choco: ["choco", "install", "-y", "nginx"],
|
|
453
|
+
},
|
|
454
|
+
manualHint:
|
|
455
|
+
"See https://nginx.org/en/docs/install.html for how to install, or point NGINX_BIN at an existing binary.",
|
|
456
|
+
probe: getNginxVersion,
|
|
476
457
|
});
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
458
|
+
if (!nginxVersion) {
|
|
459
|
+
console.error("Error: nginx is required to run the tunnel edge.");
|
|
460
|
+
process.exit(1);
|
|
461
|
+
}
|
|
481
462
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
463
|
+
let edge: TunnelEdge;
|
|
464
|
+
try {
|
|
465
|
+
edge = await ensureTunnelEdge({
|
|
466
|
+
assistantId: entry.assistantId,
|
|
467
|
+
workspaceDir,
|
|
468
|
+
gatewayPort,
|
|
469
|
+
});
|
|
470
|
+
} catch (err) {
|
|
471
|
+
console.error(
|
|
472
|
+
`Error: ${err instanceof Error ? err.message : String(err)}`,
|
|
473
|
+
);
|
|
474
|
+
process.exit(1);
|
|
475
|
+
}
|
|
486
476
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
workspaceDir,
|
|
491
|
-
};
|
|
477
|
+
console.log(
|
|
478
|
+
`${edge.started ? "Started" : "Reusing"} the nginx edge on 127.0.0.1:${edge.port}.`,
|
|
479
|
+
);
|
|
492
480
|
|
|
493
|
-
|
|
494
|
-
|
|
481
|
+
const baseTunnelOpts = {
|
|
482
|
+
port: edge.port,
|
|
483
|
+
assistantId: entry.assistantId,
|
|
484
|
+
workspaceDir,
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
await runProviderTunnel(provider, {
|
|
495
488
|
...baseTunnelOpts,
|
|
496
489
|
...(domain ? { domain } : {}),
|
|
490
|
+
...(preserveIngressUrl ? { preserveIngressUrl } : {}),
|
|
497
491
|
});
|
|
498
|
-
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
if (provider === "cloudflare") {
|
|
502
|
-
await runCloudflareTunnel(baseTunnelOpts);
|
|
503
|
-
return;
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
await runTailscaleTunnel({
|
|
507
|
-
...baseTunnelOpts,
|
|
508
|
-
...(preserveIngressUrl ? { preserveIngressUrl } : {}),
|
|
509
|
-
});
|
|
510
|
-
},
|
|
492
|
+
},
|
|
511
493
|
{ description: "Create a tunnel for a locally hosted assistant" },
|
|
512
494
|
);
|
|
513
|
-
|
|
@@ -2,11 +2,16 @@ import { existsSync, readFileSync, renameSync, rmSync, unlinkSync } from "fs";
|
|
|
2
2
|
import { basename, dirname, join } from "path";
|
|
3
3
|
import { fileURLToPath } from "url";
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
getDaemonPidPath,
|
|
7
|
+
loadAllAssistants,
|
|
8
|
+
type AssistantEntry,
|
|
9
|
+
} from "../lib/assistant-config.js";
|
|
6
10
|
import {
|
|
7
11
|
canPromptForConfirmation,
|
|
8
12
|
confirmAction,
|
|
9
13
|
} from "../lib/confirm-action.js";
|
|
14
|
+
import { retireDocker } from "../lib/docker.js";
|
|
10
15
|
import {
|
|
11
16
|
getConfigDirs,
|
|
12
17
|
getInputHistoryPath,
|
|
@@ -14,6 +19,28 @@ import {
|
|
|
14
19
|
getStateDir,
|
|
15
20
|
} from "../lib/environments/paths.js";
|
|
16
21
|
import { getCurrentEnvironment } from "../lib/environments/resolve.js";
|
|
22
|
+
import { stopProcessByPidFile } from "../lib/process";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The per-assistant teardown uninstall performs before deleting the data:
|
|
26
|
+
* docker assistants keep containers, volumes, and a network in docker that
|
|
27
|
+
* the filesystem sweep never reaches, and local assistants keep processes.
|
|
28
|
+
* Everything on-disk is deleted by the sweep regardless — this is the part
|
|
29
|
+
* uninstall could NOT do before it refused.
|
|
30
|
+
*/
|
|
31
|
+
async function retireAssistantData(entry: AssistantEntry): Promise<void> {
|
|
32
|
+
if (entry.cloud === "docker") {
|
|
33
|
+
await retireDocker(entry.assistantId);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const resources = entry.resources;
|
|
37
|
+
if (!resources) return;
|
|
38
|
+
await stopProcessByPidFile(
|
|
39
|
+
join(resources.instanceDir, "gateway.pid"),
|
|
40
|
+
"gateway",
|
|
41
|
+
);
|
|
42
|
+
await stopProcessByPidFile(getDaemonPidPath(resources), "assistant");
|
|
43
|
+
}
|
|
17
44
|
|
|
18
45
|
/** A directory (or file) scheduled for deletion, with what it holds. */
|
|
19
46
|
interface DeleteTarget {
|
|
@@ -114,7 +141,7 @@ function selfDelete(): void {
|
|
|
114
141
|
"Cadet is running from a source checkout — skipping self-removal.",
|
|
115
142
|
);
|
|
116
143
|
console.log(
|
|
117
|
-
"Remove the installed copy with `bun remove -g mycadet`
|
|
144
|
+
"Remove the installed copy with `bun remove -g mycadet` or `npm uninstall -g mycadet`.",
|
|
118
145
|
);
|
|
119
146
|
return;
|
|
120
147
|
}
|
|
@@ -169,13 +196,34 @@ export const uninstall = Object.assign(
|
|
|
169
196
|
}
|
|
170
197
|
const yes = args.includes("--yes");
|
|
171
198
|
|
|
172
|
-
//
|
|
173
|
-
//
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
199
|
+
// Assistants still on the rack are retired — with their data deleted —
|
|
200
|
+
// as part of the uninstall. Uninstall deletes every location cadet uses
|
|
201
|
+
// INCLUDING the retired archives, so retiring first (which archives)
|
|
202
|
+
// only matters for the teardown it performs: docker assistants keep
|
|
203
|
+
// containers, volumes, and a network in docker that the filesystem
|
|
204
|
+
// sweep below never reaches. A warning, not a refusal (v0.0.10 run
|
|
205
|
+
// feedback: the hard error just blocked the uninstall).
|
|
206
|
+
const existing = loadAllAssistants();
|
|
207
|
+
if (existing.length > 0) {
|
|
208
|
+
console.warn(
|
|
209
|
+
`\n⚠️ ${existing.length} ${existing.length === 1 ? "assistant still exists" : "assistants still exist"}: ${existing.map((a) => a.assistantId).join(", ")}`,
|
|
210
|
+
);
|
|
211
|
+
console.warn(
|
|
212
|
+
" They will be retired now — their data (and any retired archives) will be permanently deleted by this uninstall.",
|
|
177
213
|
);
|
|
178
|
-
|
|
214
|
+
for (const entry of existing) {
|
|
215
|
+
if (entry.cloud === "apple-container" || entry.cloud === "paired") {
|
|
216
|
+
console.log(` Skipping '${entry.assistantId}' on ${entry.cloud} — its lifecycle is managed elsewhere.`);
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
try {
|
|
220
|
+
await retireAssistantData(entry);
|
|
221
|
+
} catch (err) {
|
|
222
|
+
console.error(
|
|
223
|
+
` Warning: could not fully retire '${entry.assistantId}': ${err instanceof Error ? err.message : String(err)}`,
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
179
227
|
}
|
|
180
228
|
|
|
181
229
|
const env = getCurrentEnvironment();
|
|
@@ -191,7 +239,7 @@ export const uninstall = Object.assign(
|
|
|
191
239
|
} else {
|
|
192
240
|
console.log("It will permanently delete:");
|
|
193
241
|
for (const t of targets) {
|
|
194
|
-
console.log(` - ${t.path}
|
|
242
|
+
console.log(` - ${t.path} — ${t.description}`);
|
|
195
243
|
}
|
|
196
244
|
}
|
|
197
245
|
const execBase = basename(execPath);
|
package/src/commands/unpair.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Forget a pairing imported from another machine via `cadet connect import`:
|
|
5
5
|
* remove its lockfile entry and stored guardian token from THIS machine. Only
|
|
6
6
|
* paired assistants (`cloud: "paired"`) can be unpaired — `cadet retire` owns
|
|
7
|
-
* local and
|
|
7
|
+
* local and cloud assistants.
|
|
8
8
|
*
|
|
9
9
|
* This is client-side only: it forgets the connection here but does not revoke
|
|
10
10
|
* the device on the host. (Host-side revocation is `cadet devices`.)
|
|
@@ -37,7 +37,7 @@ OPTIONS:
|
|
|
37
37
|
|
|
38
38
|
Removes the local connection (lockfile entry + stored token). Only paired
|
|
39
39
|
assistants (imported via 'cadet connect import') can be unpaired; use
|
|
40
|
-
'cadet retire' for local or
|
|
40
|
+
'cadet retire' for local or cloud assistants.
|
|
41
41
|
|
|
42
42
|
EXAMPLES:
|
|
43
43
|
cadet unpair paired-desk
|
|
@@ -72,7 +72,7 @@ export const unpair = Object.assign(
|
|
|
72
72
|
|
|
73
73
|
if (entry.cloud !== "paired") {
|
|
74
74
|
console.error(
|
|
75
|
-
`Error: '${name}' is not a paired assistant. Use \`cadet retire\` to remove a local or
|
|
75
|
+
`Error: '${name}' is not a paired assistant. Use \`cadet retire\` to remove a local or cloud assistant.`,
|
|
76
76
|
);
|
|
77
77
|
process.exit(1);
|
|
78
78
|
}
|