@mycadet/cli 0.12.7 → 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/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 +6 -4
- 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 +1 -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 +15 -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 +149 -85
- package/src/__tests__/{ps-platform-status.test.ts → ps-cloud-status.test.ts} +80 -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 +180 -211
- package/src/__tests__/tunnel.test.ts +490 -270
- package/src/__tests__/uninstall.test.ts +150 -9
- 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 -78
- 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 +27 -24
- package/src/commands/login.ts +22 -22
- package/src/commands/logs.ts +8 -8
- package/src/commands/pair.ts +140 -37
- package/src/commands/ps.ts +232 -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 +110 -129
- package/src/commands/terminal.ts +158 -18
- package/src/commands/tunnel.ts +156 -175
- package/src/commands/uninstall.ts +137 -54
- 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-gateway-cli.test.ts +42 -0
- 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} +105 -99
- package/src/lib/cloud-login.ts +23 -24
- package/src/lib/{platform-releases.ts → cloud-releases.ts} +11 -9
- package/src/lib/cloud-values.ts +3 -0
- package/src/lib/color.ts +4 -0
- package/src/lib/config-utils.ts +1 -1
- package/src/lib/detached-process.ts +1 -1
- package/src/lib/docker.ts +300 -111
- package/src/lib/ensure-binary.ts +171 -0
- package/src/lib/environments/__tests__/paths.test.ts +29 -3
- package/src/lib/environments/__tests__/resolve.test.ts +26 -26
- package/src/lib/environments/paths.ts +16 -8
- package/src/lib/environments/resolve.ts +10 -12
- 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 +32 -29
- package/src/lib/hatch-next-steps.ts +5 -9
- 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 +46 -34
- package/src/lib/nginx-ingress.ts +40 -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/step-runner.ts +6 -1
- 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
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
* Platform terminal API client.
|
|
3
3
|
*
|
|
4
4
|
* Wraps the Django terminal session endpoints that proxy through vembda to
|
|
5
|
-
* open K8s exec streams into
|
|
5
|
+
* open K8s exec streams into cloud assistant containers. Same transport
|
|
6
6
|
* the web UI's xterm.js terminal uses.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { loopbackSafeFetch } from "./loopback-fetch.js";
|
|
10
|
-
import { authHeaders,
|
|
10
|
+
import { authHeaders, getCloudUrl } from "./cloud-client.js";
|
|
11
11
|
import { z } from "zod";
|
|
12
12
|
|
|
13
13
|
const TerminalSessionResponseSchema = z.object({ session_id: z.string() });
|
|
@@ -29,10 +29,10 @@ export async function createTerminalSession(
|
|
|
29
29
|
assistantId: string,
|
|
30
30
|
cols: number,
|
|
31
31
|
rows: number,
|
|
32
|
-
|
|
32
|
+
cloudUrl?: string,
|
|
33
33
|
service?: string,
|
|
34
34
|
): Promise<{ session_id: string }> {
|
|
35
|
-
const baseUrl =
|
|
35
|
+
const baseUrl = cloudUrl || getCloudUrl();
|
|
36
36
|
const body: Record<string, unknown> = { cols, rows };
|
|
37
37
|
if (service) {
|
|
38
38
|
body.service = service;
|
|
@@ -41,7 +41,7 @@ export async function createTerminalSession(
|
|
|
41
41
|
`${baseUrl}/v1/assistants/${assistantId}/terminal/sessions/`,
|
|
42
42
|
{
|
|
43
43
|
method: "POST",
|
|
44
|
-
headers: await authHeaders(token,
|
|
44
|
+
headers: await authHeaders(token, cloudUrl),
|
|
45
45
|
body: JSON.stringify(body),
|
|
46
46
|
},
|
|
47
47
|
);
|
|
@@ -62,14 +62,14 @@ export async function closeTerminalSession(
|
|
|
62
62
|
token: string,
|
|
63
63
|
assistantId: string,
|
|
64
64
|
sessionId: string,
|
|
65
|
-
|
|
65
|
+
cloudUrl?: string,
|
|
66
66
|
): Promise<void> {
|
|
67
|
-
const baseUrl =
|
|
67
|
+
const baseUrl = cloudUrl || getCloudUrl();
|
|
68
68
|
const response = await loopbackSafeFetch(
|
|
69
69
|
`${baseUrl}/v1/assistants/${assistantId}/terminal/sessions/${sessionId}/`,
|
|
70
70
|
{
|
|
71
71
|
method: "DELETE",
|
|
72
|
-
headers: await authHeaders(token,
|
|
72
|
+
headers: await authHeaders(token, cloudUrl),
|
|
73
73
|
},
|
|
74
74
|
);
|
|
75
75
|
// 404 = already closed, treat as success
|
|
@@ -89,14 +89,14 @@ export async function sendTerminalInput(
|
|
|
89
89
|
assistantId: string,
|
|
90
90
|
sessionId: string,
|
|
91
91
|
data: string,
|
|
92
|
-
|
|
92
|
+
cloudUrl?: string,
|
|
93
93
|
): Promise<void> {
|
|
94
|
-
const baseUrl =
|
|
94
|
+
const baseUrl = cloudUrl || getCloudUrl();
|
|
95
95
|
const response = await loopbackSafeFetch(
|
|
96
96
|
`${baseUrl}/v1/assistants/${assistantId}/terminal/sessions/${sessionId}/input/`,
|
|
97
97
|
{
|
|
98
98
|
method: "POST",
|
|
99
|
-
headers: await authHeaders(token,
|
|
99
|
+
headers: await authHeaders(token, cloudUrl),
|
|
100
100
|
body: JSON.stringify({ data }),
|
|
101
101
|
},
|
|
102
102
|
);
|
|
@@ -113,14 +113,14 @@ export async function resizeTerminalSession(
|
|
|
113
113
|
sessionId: string,
|
|
114
114
|
cols: number,
|
|
115
115
|
rows: number,
|
|
116
|
-
|
|
116
|
+
cloudUrl?: string,
|
|
117
117
|
): Promise<void> {
|
|
118
|
-
const baseUrl =
|
|
118
|
+
const baseUrl = cloudUrl || getCloudUrl();
|
|
119
119
|
const response = await loopbackSafeFetch(
|
|
120
120
|
`${baseUrl}/v1/assistants/${assistantId}/terminal/sessions/${sessionId}/resize/`,
|
|
121
121
|
{
|
|
122
122
|
method: "POST",
|
|
123
|
-
headers: await authHeaders(token,
|
|
123
|
+
headers: await authHeaders(token, cloudUrl),
|
|
124
124
|
body: JSON.stringify({ cols, rows }),
|
|
125
125
|
},
|
|
126
126
|
);
|
|
@@ -143,14 +143,14 @@ export async function* subscribeTerminalEvents(
|
|
|
143
143
|
token: string,
|
|
144
144
|
assistantId: string,
|
|
145
145
|
sessionId: string,
|
|
146
|
-
|
|
146
|
+
cloudUrl?: string,
|
|
147
147
|
signal?: AbortSignal,
|
|
148
148
|
): AsyncGenerator<TerminalOutputEvent> {
|
|
149
|
-
const baseUrl =
|
|
149
|
+
const baseUrl = cloudUrl || getCloudUrl();
|
|
150
150
|
const response = await loopbackSafeFetch(
|
|
151
151
|
`${baseUrl}/v1/assistants/${assistantId}/terminal/sessions/${sessionId}/events/`,
|
|
152
152
|
{
|
|
153
|
-
headers: await authHeaders(token,
|
|
153
|
+
headers: await authHeaders(token, cloudUrl),
|
|
154
154
|
signal,
|
|
155
155
|
},
|
|
156
156
|
);
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared terminal session primitives for managed (cloud-hosted) assistants.
|
|
3
3
|
*
|
|
4
|
-
* Extracted from commands/terminal.ts so that
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Extracted from commands/terminal.ts so that exec.ts and terminal.ts can
|
|
5
|
+
* use the same interactive session and assistant resolver without
|
|
6
|
+
* cross-importing commands (per cli/CONTRIBUTING.md).
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { isCloudHome } from "./cloud-values.js";
|
|
10
10
|
import { resolveAssistant } from "./assistant-config.js";
|
|
11
|
-
import {
|
|
11
|
+
import { getCloudUrl, readCloudToken } from "./cloud-client.js";
|
|
12
12
|
import {
|
|
13
13
|
closeTerminalSession,
|
|
14
14
|
createTerminalSession,
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
export interface ResolvedManagedAssistant {
|
|
25
25
|
assistantId: string;
|
|
26
26
|
token: string;
|
|
27
|
-
|
|
27
|
+
cloudUrl: string;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
// ---------------------------------------------------------------------------
|
|
@@ -32,7 +32,7 @@ export interface ResolvedManagedAssistant {
|
|
|
32
32
|
// ---------------------------------------------------------------------------
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
|
-
* Resolve a
|
|
35
|
+
* Resolve a cloud (cloud-hosted) assistant from the lockfile. Exits with
|
|
36
36
|
* an error if the assistant is not found, not managed, or the user isn't
|
|
37
37
|
* logged in.
|
|
38
38
|
*/
|
|
@@ -51,14 +51,14 @@ export function resolveManagedAssistant(
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
const cloud = entry.cloud;
|
|
54
|
-
if (!
|
|
54
|
+
if (!isCloudHome(cloud)) {
|
|
55
55
|
if (cloud === "local") {
|
|
56
56
|
console.error(
|
|
57
57
|
"This assistant runs locally on your machine. You can access it directly.",
|
|
58
58
|
);
|
|
59
59
|
} else if (cloud === "docker") {
|
|
60
60
|
console.error(
|
|
61
|
-
`Use 'cadet exec -it -- /bin/bash' or 'cadet
|
|
61
|
+
`Use 'cadet exec -it -- /bin/bash' or 'cadet terminal' for ${cloud} instances.`,
|
|
62
62
|
);
|
|
63
63
|
} else {
|
|
64
64
|
console.error(
|
|
@@ -68,7 +68,7 @@ export function resolveManagedAssistant(
|
|
|
68
68
|
process.exit(1);
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
const token =
|
|
71
|
+
const token = readCloudToken();
|
|
72
72
|
if (!token) {
|
|
73
73
|
console.error(
|
|
74
74
|
"Not logged in. Run `cadet login` first to authenticate with Cadet Cloud.",
|
|
@@ -79,7 +79,7 @@ export function resolveManagedAssistant(
|
|
|
79
79
|
return {
|
|
80
80
|
assistantId: entry.assistantId,
|
|
81
81
|
token,
|
|
82
|
-
|
|
82
|
+
cloudUrl: getCloudUrl(),
|
|
83
83
|
};
|
|
84
84
|
}
|
|
85
85
|
|
|
@@ -88,8 +88,8 @@ export function resolveManagedAssistant(
|
|
|
88
88
|
// ---------------------------------------------------------------------------
|
|
89
89
|
|
|
90
90
|
/**
|
|
91
|
-
* Open an interactive raw-tty terminal session to a
|
|
92
|
-
* Bridges local stdin/stdout to the
|
|
91
|
+
* Open an interactive raw-tty terminal session to a cloud assistant.
|
|
92
|
+
* Bridges local stdin/stdout to the cloud terminal session API.
|
|
93
93
|
*/
|
|
94
94
|
export async function interactiveSession(
|
|
95
95
|
assistant: ResolvedManagedAssistant,
|
|
@@ -106,7 +106,7 @@ export async function interactiveSession(
|
|
|
106
106
|
assistant.assistantId,
|
|
107
107
|
cols,
|
|
108
108
|
rows,
|
|
109
|
-
assistant.
|
|
109
|
+
assistant.cloudUrl,
|
|
110
110
|
service,
|
|
111
111
|
);
|
|
112
112
|
|
|
@@ -142,7 +142,7 @@ export async function interactiveSession(
|
|
|
142
142
|
assistant.token,
|
|
143
143
|
assistant.assistantId,
|
|
144
144
|
sessionId,
|
|
145
|
-
assistant.
|
|
145
|
+
assistant.cloudUrl,
|
|
146
146
|
);
|
|
147
147
|
} catch {
|
|
148
148
|
// Best-effort cleanup
|
|
@@ -169,7 +169,7 @@ export async function interactiveSession(
|
|
|
169
169
|
sessionId,
|
|
170
170
|
newCols,
|
|
171
171
|
newRows,
|
|
172
|
-
assistant.
|
|
172
|
+
assistant.cloudUrl,
|
|
173
173
|
).catch(() => {
|
|
174
174
|
// Resize failures are non-fatal
|
|
175
175
|
});
|
|
@@ -190,7 +190,7 @@ export async function interactiveSession(
|
|
|
190
190
|
assistant.assistantId,
|
|
191
191
|
sessionId,
|
|
192
192
|
data,
|
|
193
|
-
assistant.
|
|
193
|
+
assistant.cloudUrl,
|
|
194
194
|
).catch((err) => {
|
|
195
195
|
if (!exiting) {
|
|
196
196
|
console.error(`\r\nInput error: ${err.message}\r\n`);
|
|
@@ -214,7 +214,7 @@ export async function interactiveSession(
|
|
|
214
214
|
assistant.assistantId,
|
|
215
215
|
sessionId,
|
|
216
216
|
initialCommand + "\r",
|
|
217
|
-
assistant.
|
|
217
|
+
assistant.cloudUrl,
|
|
218
218
|
);
|
|
219
219
|
}
|
|
220
220
|
|
|
@@ -224,7 +224,7 @@ export async function interactiveSession(
|
|
|
224
224
|
assistant.token,
|
|
225
225
|
assistant.assistantId,
|
|
226
226
|
sessionId,
|
|
227
|
-
assistant.
|
|
227
|
+
assistant.cloudUrl,
|
|
228
228
|
abortController.signal,
|
|
229
229
|
)) {
|
|
230
230
|
if (exiting) break;
|
|
@@ -271,7 +271,7 @@ export function shellEscapeArgs(args: string[]): string {
|
|
|
271
271
|
// ---------------------------------------------------------------------------
|
|
272
272
|
|
|
273
273
|
/**
|
|
274
|
-
* Run a command non-interactively in a
|
|
274
|
+
* Run a command non-interactively in a cloud assistant service. Creates
|
|
275
275
|
* an ephemeral terminal session, sends the command wrapped in sentinels for
|
|
276
276
|
* reliable output extraction, captures the result, and exits with the
|
|
277
277
|
* remote command's exit code.
|
|
@@ -300,7 +300,7 @@ export async function nonInteractiveExec(
|
|
|
300
300
|
assistant.assistantId,
|
|
301
301
|
120,
|
|
302
302
|
24,
|
|
303
|
-
assistant.
|
|
303
|
+
assistant.cloudUrl,
|
|
304
304
|
options?.service,
|
|
305
305
|
);
|
|
306
306
|
|
|
@@ -333,7 +333,7 @@ export async function nonInteractiveExec(
|
|
|
333
333
|
assistant.token,
|
|
334
334
|
assistant.assistantId,
|
|
335
335
|
sessionId,
|
|
336
|
-
assistant.
|
|
336
|
+
assistant.cloudUrl,
|
|
337
337
|
abortController.signal,
|
|
338
338
|
)) {
|
|
339
339
|
eventCount++;
|
|
@@ -369,7 +369,7 @@ export async function nonInteractiveExec(
|
|
|
369
369
|
assistant.assistantId,
|
|
370
370
|
sessionId,
|
|
371
371
|
fullCmd,
|
|
372
|
-
assistant.
|
|
372
|
+
assistant.cloudUrl,
|
|
373
373
|
);
|
|
374
374
|
}
|
|
375
375
|
}
|
|
@@ -403,7 +403,7 @@ export async function nonInteractiveExec(
|
|
|
403
403
|
assistant.token,
|
|
404
404
|
assistant.assistantId,
|
|
405
405
|
sessionId,
|
|
406
|
-
assistant.
|
|
406
|
+
assistant.cloudUrl,
|
|
407
407
|
).catch(() => {});
|
|
408
408
|
}
|
|
409
409
|
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import type { ChildProcess } from "child_process";
|
|
2
|
-
|
|
3
|
-
import { selectRestartTunnelRecord } from "@mycadet/service-contracts/ingress";
|
|
4
|
-
|
|
5
1
|
import {
|
|
2
|
+
clearRootProcess,
|
|
6
3
|
loadAllAssistants,
|
|
7
4
|
loadAllAssistantsAcrossEnvs,
|
|
5
|
+
readRootProcesses,
|
|
8
6
|
type AssistantEntry,
|
|
7
|
+
type RootProcessRecord,
|
|
9
8
|
} from "./assistant-config.js";
|
|
10
9
|
|
|
11
10
|
import {
|
|
@@ -14,7 +13,6 @@ import {
|
|
|
14
13
|
loadRawConfig,
|
|
15
14
|
parseGatewayPortFromEntryUrls,
|
|
16
15
|
} from "./ingress-config.js";
|
|
17
|
-
import { IngressConfigSchema } from "./ngrok.js";
|
|
18
16
|
import {
|
|
19
17
|
ensureTunnelEdge,
|
|
20
18
|
formatEdgeMode,
|
|
@@ -23,8 +21,187 @@ import {
|
|
|
23
21
|
type TunnelEdge,
|
|
24
22
|
} from "./nginx-ingress.js";
|
|
25
23
|
import { waitForDaemonReady } from "./http-client.js";
|
|
26
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
hasWebhookIntegrations,
|
|
26
|
+
IngressConfigSchema,
|
|
27
|
+
isNgrokProcess,
|
|
28
|
+
maybeStartNgrokTunnel,
|
|
29
|
+
NGROK_TUNNEL_META,
|
|
30
|
+
runNgrokTunnel,
|
|
31
|
+
saveNgrokDomain,
|
|
32
|
+
} from "./tunnels/ngrok.js";
|
|
33
|
+
import { stopProcess } from "./process.js";
|
|
27
34
|
import { shellArg } from "./shell-arg.js";
|
|
35
|
+
import { notifyGatewayOfPublicUrl } from "./tunnels/gateway-notify.js";
|
|
36
|
+
import {
|
|
37
|
+
selectRestartTunnelRecord,
|
|
38
|
+
type TunnelProviderName,
|
|
39
|
+
} from "@mycadet/service-contracts/ingress";
|
|
40
|
+
import { runCadetTunnel, CADET_TUNNEL_META } from "./tunnels/cadet.js";
|
|
41
|
+
import {
|
|
42
|
+
runCloudflareTunnel,
|
|
43
|
+
CLOUDFLARE_TUNNEL_META,
|
|
44
|
+
} from "./tunnels/cloudflare.js";
|
|
45
|
+
import {
|
|
46
|
+
runTailscaleTunnel,
|
|
47
|
+
TAILSCALE_TUNNEL_META,
|
|
48
|
+
} from "./tunnels/tailscale.js";
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The provider the automatic tunnel paths start when no provider is named.
|
|
52
|
+
* ngrok today; the north star has the managed `cadet` tunnel as the eventual
|
|
53
|
+
* default, at which point only this constant flips.
|
|
54
|
+
*/
|
|
55
|
+
export const DEFAULT_TUNNEL_PROVIDER: TunnelProviderName = "ngrok";
|
|
56
|
+
|
|
57
|
+
/** Per-provider copy `cadet tunnel`'s help renders. */
|
|
58
|
+
export interface ProviderMeta {
|
|
59
|
+
/** How far the tunnel URL reaches. */
|
|
60
|
+
reach: string;
|
|
61
|
+
/** Install instruction line, rendered under the provider entry. */
|
|
62
|
+
install: string;
|
|
63
|
+
/** Extra behavior notes, rendered under the install line. */
|
|
64
|
+
notes?: string[];
|
|
65
|
+
/** Example reserved-domain value, for providers that support `--domain`. */
|
|
66
|
+
domainExample?: string;
|
|
67
|
+
/** Whether `--domain`/`--clear-domain` can bind a reserved domain. */
|
|
68
|
+
supportsReservedDomain: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Listed but not yet implemented: the command refuses it before starting
|
|
71
|
+
* any infrastructure, so `--provider <name>` fails fast with the
|
|
72
|
+
* coming-soon copy instead of building an edge nothing will use.
|
|
73
|
+
*/
|
|
74
|
+
comingSoon?: boolean;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The registry: each provider's copy lives in its own `tunnels/<provider>.ts`
|
|
79
|
+
* module and is assembled here, so `cadet tunnel`'s help, `--provider`
|
|
80
|
+
* validation, and dispatch all read one shape.
|
|
81
|
+
*/
|
|
82
|
+
export const PROVIDER_META: Record<TunnelProviderName, ProviderMeta> = {
|
|
83
|
+
cadet: CADET_TUNNEL_META,
|
|
84
|
+
ngrok: NGROK_TUNNEL_META,
|
|
85
|
+
cloudflare: CLOUDFLARE_TUNNEL_META,
|
|
86
|
+
tailscale: TAILSCALE_TUNNEL_META,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Registry check that tolerates any provider spelling: only providers whose
|
|
91
|
+
* meta claims reserved-domain support accept `--domain`/`--clear-domain`.
|
|
92
|
+
*/
|
|
93
|
+
export function providerSupportsReservedDomain(provider: string): boolean {
|
|
94
|
+
for (const [name, meta] of Object.entries(PROVIDER_META)) {
|
|
95
|
+
if (name === provider) return meta.supportsReservedDomain;
|
|
96
|
+
}
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Options every provider tunnel accepts; `runProviderTunnel` maps them. */
|
|
101
|
+
export interface ProviderTunnelOptions {
|
|
102
|
+
port: number;
|
|
103
|
+
assistantId?: string;
|
|
104
|
+
workspaceDir: string;
|
|
105
|
+
/** Reserved domain to bind (providers that support one only). */
|
|
106
|
+
domain?: string;
|
|
107
|
+
/** Tailnet-only runs keep the webhook callback base as it is. */
|
|
108
|
+
preserveIngressUrl?: boolean;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* What every tunnel provider's run hands back: the public URL it established
|
|
113
|
+
* or adopted, and the promise that settles when the run ends. Conformance is
|
|
114
|
+
* the point — the tunnel level can notify the gateway the same way no matter
|
|
115
|
+
* which provider ran.
|
|
116
|
+
*/
|
|
117
|
+
export interface ProviderTunnelResult {
|
|
118
|
+
publicUrl: string;
|
|
119
|
+
waitForExit: Promise<void>;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Run a tunnel provider by name — the dispatch `cadet tunnel` uses, so the
|
|
124
|
+
* command layer never names a provider implementation directly. Notifies the
|
|
125
|
+
* gateway of the resulting public URL identically for every provider, then
|
|
126
|
+
* holds until the run ends.
|
|
127
|
+
*/
|
|
128
|
+
export async function runProviderTunnel(
|
|
129
|
+
provider: TunnelProviderName,
|
|
130
|
+
opts: ProviderTunnelOptions,
|
|
131
|
+
): Promise<{ publicUrl: string }> {
|
|
132
|
+
const result = await runProvider(provider, opts);
|
|
133
|
+
await notifyGatewayOfPublicUrl(result.publicUrl, opts.assistantId);
|
|
134
|
+
await result.waitForExit;
|
|
135
|
+
return { publicUrl: result.publicUrl };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Map the provider name onto its run — no gateway notification here. */
|
|
139
|
+
function runProvider(
|
|
140
|
+
provider: TunnelProviderName,
|
|
141
|
+
opts: ProviderTunnelOptions,
|
|
142
|
+
): Promise<ProviderTunnelResult> {
|
|
143
|
+
const base = {
|
|
144
|
+
port: opts.port,
|
|
145
|
+
assistantId: opts.assistantId,
|
|
146
|
+
workspaceDir: opts.workspaceDir,
|
|
147
|
+
};
|
|
148
|
+
if (provider === "ngrok") {
|
|
149
|
+
return runNgrokTunnel({
|
|
150
|
+
...base,
|
|
151
|
+
...(opts.domain ? { domain: opts.domain } : {}),
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
if (provider === "cadet") {
|
|
155
|
+
return runCadetTunnel(opts);
|
|
156
|
+
}
|
|
157
|
+
if (provider === "cloudflare") {
|
|
158
|
+
return runCloudflareTunnel(base);
|
|
159
|
+
}
|
|
160
|
+
return runTailscaleTunnel({
|
|
161
|
+
...base,
|
|
162
|
+
...(opts.preserveIngressUrl
|
|
163
|
+
? { preserveIngressUrl: opts.preserveIngressUrl }
|
|
164
|
+
: {}),
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Drop the saved reserved-domain record (providers that keep one). */
|
|
169
|
+
export function clearReservedDomain(): void {
|
|
170
|
+
saveNgrokDomain(null);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Stop a tunnel agent this CLI started, per the root lockfile's `tunnel`
|
|
175
|
+
* entry. The pid is verified against the provider's process identity before
|
|
176
|
+
* signaling — a stale entry after PID reuse must not kill an unrelated
|
|
177
|
+
* process.
|
|
178
|
+
*/
|
|
179
|
+
export async function stopTunnelProcess(): Promise<void> {
|
|
180
|
+
const recorded: RootProcessRecord | undefined = readRootProcesses().tunnel;
|
|
181
|
+
if (!recorded) return;
|
|
182
|
+
if (isNgrokProcess(recorded.pid)) {
|
|
183
|
+
await stopProcess(recorded.pid, "tunnel");
|
|
184
|
+
}
|
|
185
|
+
clearRootProcess("tunnel");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* The `cadet tunnel --detach` copy for a run that adopted an already-running
|
|
190
|
+
* agent: killing the supervisor does not stop a tunnel it did not spawn.
|
|
191
|
+
*/
|
|
192
|
+
export function adoptedTunnelStopNotice(pid: number | undefined): string {
|
|
193
|
+
return (
|
|
194
|
+
`That ngrok agent was already running outside this command, so \`kill ${pid}\` only ` +
|
|
195
|
+
"stops this supervisor, not the ngrok tunnel or its saved ingress URL. Stop the ngrok agent " +
|
|
196
|
+
"directly to end the tunnel."
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export {
|
|
201
|
+
ADOPTED_TUNNEL_RE,
|
|
202
|
+
hasWebhookIntegrationsConfigured,
|
|
203
|
+
} from "./tunnels/ngrok.js";
|
|
204
|
+
export { cadetTunnelComingSoonError } from "./tunnels/cadet.js";
|
|
28
205
|
|
|
29
206
|
/** Matches the Docker hatch path's service-readiness allowance. */
|
|
30
207
|
export const DOCKER_GATEWAY_READY_TIMEOUT_MS = 5 * 60_000;
|
|
@@ -221,8 +398,9 @@ function ownsSharedIngress(
|
|
|
221
398
|
* survived across sleep (a tailnet serve, a reserved ngrok domain) reaches the
|
|
222
399
|
* gateway again without a manual `cadet tunnel`.
|
|
223
400
|
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
401
|
+
* The auto-tunnel runs here too, matching the local wake path: the spawned
|
|
402
|
+
* agent records itself in the root lockfile under `tunnel`, so `cadet ps` and
|
|
403
|
+
* the stop paths can see it instead of it leaking past the next sleep.
|
|
226
404
|
*/
|
|
227
405
|
export async function restoreContainerTunnelEdge(
|
|
228
406
|
entry: AssistantEntry,
|
|
@@ -257,20 +435,30 @@ export async function restoreContainerTunnelEdge(
|
|
|
257
435
|
);
|
|
258
436
|
return;
|
|
259
437
|
}
|
|
260
|
-
await
|
|
438
|
+
await restoreTunnelEdgeAndAutoTunnel(
|
|
439
|
+
entry.assistantId,
|
|
440
|
+
gatewayPort,
|
|
441
|
+
workspaceDir,
|
|
442
|
+
);
|
|
261
443
|
}
|
|
262
444
|
|
|
445
|
+
/**
|
|
446
|
+
* Restore the nginx edge, then start the default provider's tunnel at it
|
|
447
|
+
* (the gateway port when the edge is down, keeping webhook delivery alive).
|
|
448
|
+
* The spawned agent records itself in the root lockfile under `tunnel`;
|
|
449
|
+
* adopting an already-running one clears any stale entry.
|
|
450
|
+
*/
|
|
263
451
|
export async function restoreTunnelEdgeAndAutoTunnel(
|
|
264
452
|
assistantId: string,
|
|
265
453
|
gatewayPort: number,
|
|
266
454
|
workspaceDir: string,
|
|
267
|
-
): Promise<
|
|
455
|
+
): Promise<void> {
|
|
268
456
|
const edgePort = await restoreTunnelEdge(
|
|
269
457
|
assistantId,
|
|
270
458
|
gatewayPort,
|
|
271
459
|
workspaceDir,
|
|
272
460
|
);
|
|
273
|
-
|
|
461
|
+
await maybeStartNgrokTunnel(
|
|
274
462
|
edgePort ?? gatewayPort,
|
|
275
463
|
workspaceDir,
|
|
276
464
|
assistantId,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { STALE_CLI_UPDATE_HINT } from "../stale-cli-hint.js";
|
|
2
|
+
import type { ProviderMeta, ProviderTunnelOptions } from "../tunnel.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The managed `cadet` tunnel. It is listed in the shared registry so the
|
|
6
|
+
* command's help and `--provider` validation speak its name, but the tunnel
|
|
7
|
+
* itself ships with Cadet Cloud — until then every run refuses loudly.
|
|
8
|
+
*/
|
|
9
|
+
export const CADET_TUNNEL_META: ProviderMeta = {
|
|
10
|
+
reach: "Managed tunnel via Cadet Cloud (coming soon)",
|
|
11
|
+
install: "no install needed — included with Cadet Cloud",
|
|
12
|
+
supportsReservedDomain: false,
|
|
13
|
+
comingSoon: true,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/** Copy every refusing path shares, so the wording cannot drift apart. */
|
|
17
|
+
export function cadetTunnelComingSoonError(provider: string): Error {
|
|
18
|
+
return new Error(
|
|
19
|
+
`Tunnel provider '${provider}' is not yet implemented. ` +
|
|
20
|
+
`If this provider is documented, ${STALE_CLI_UPDATE_HINT}`,
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Refuse the run: the managed tunnel is not something the CLI can start. */
|
|
25
|
+
export async function runCadetTunnel(
|
|
26
|
+
_opts: ProviderTunnelOptions,
|
|
27
|
+
): Promise<never> {
|
|
28
|
+
throw cadetTunnelComingSoonError("cadet");
|
|
29
|
+
}
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
+
import type { ProviderMeta, ProviderTunnelResult } from "../tunnel.js";
|
|
2
|
+
|
|
1
3
|
import { execFileSync, spawn, type ChildProcess } from "node:child_process";
|
|
2
4
|
|
|
3
|
-
import {
|
|
5
|
+
import { ensureBinaryInstalled } from "../ensure-binary.js";
|
|
6
|
+
|
|
7
|
+
import { GATEWAY_PORT } from "../constants.js";
|
|
4
8
|
import {
|
|
5
9
|
clearIngressUrl,
|
|
6
10
|
getDefaultWorkspaceDir,
|
|
7
11
|
saveIngressUrl,
|
|
8
|
-
} from "
|
|
12
|
+
} from "../ingress-config.js";
|
|
9
13
|
|
|
10
14
|
// ── Cloudflare Tunnel ─────────────────────────────────────────────────────────
|
|
11
15
|
|
|
@@ -135,19 +139,27 @@ export interface RunCloudflareTunnelOptions {
|
|
|
135
139
|
|
|
136
140
|
export async function runCloudflareTunnel(
|
|
137
141
|
opts: RunCloudflareTunnelOptions = {},
|
|
138
|
-
): Promise<
|
|
139
|
-
const version =
|
|
142
|
+
): Promise<ProviderTunnelResult> {
|
|
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
|
+
});
|
|
140
162
|
if (!version) {
|
|
141
|
-
console.error("Error: cloudflared is not installed.");
|
|
142
|
-
console.error("");
|
|
143
|
-
console.error("Install cloudflared:");
|
|
144
|
-
console.error(" macOS: brew install cloudflare/cloudflare/cloudflared");
|
|
145
|
-
console.error(" Linux: https://pkg.cloudflare.com/index.html");
|
|
146
|
-
console.error(
|
|
147
|
-
" Windows: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/",
|
|
148
|
-
);
|
|
149
|
-
console.error("");
|
|
150
|
-
console.error("No Cloudflare account is required for quick tunnels.");
|
|
151
163
|
process.exit(1);
|
|
152
164
|
}
|
|
153
165
|
|
|
@@ -226,7 +238,16 @@ export async function runCloudflareTunnel(
|
|
|
226
238
|
console.log("Press Ctrl+C to stop the tunnel and clear the ingress URL.");
|
|
227
239
|
|
|
228
240
|
// Keep running until cloudflared exits (e.g., network error or user Ctrl+C)
|
|
229
|
-
|
|
241
|
+
const waitForExit = new Promise<void>((resolve) => {
|
|
230
242
|
child.on("exit", () => resolve());
|
|
231
243
|
});
|
|
244
|
+
return { publicUrl, waitForExit };
|
|
232
245
|
}
|
|
246
|
+
|
|
247
|
+
/** Per-provider copy `cadet tunnel`'s help renders for cloudflare. */
|
|
248
|
+
export const CLOUDFLARE_TUNNEL_META: ProviderMeta = {
|
|
249
|
+
reach: "Public tunnel that webhook integrations can reach",
|
|
250
|
+
install: "install: brew install cloudflare/cloudflare/cloudflared",
|
|
251
|
+
notes: ["No Cloudflare account required for quick tunnels."],
|
|
252
|
+
supportsReservedDomain: false,
|
|
253
|
+
};
|