@mycadet/cli 0.12.7 → 0.12.8
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-platform-login.ts → show-cloud-login.ts} +3 -5
- package/node_modules/@mycadet/assistant-api/index.ts +11 -11
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/package.json +1 -1
- 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 +1 -1
- 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 +1 -1
- 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} +5 -5
- 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__/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 +18 -18
- 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__/{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} +1 -1
- package/src/__tests__/teleport-backup.test.ts +3 -3
- package/src/__tests__/teleport.test.ts +180 -196
- package/src/__tests__/tunnel.test.ts +255 -227
- 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 +258 -41
- package/src/__tests__/workos-pkce.test.ts +2 -2
- package/src/commands/backup.ts +22 -22
- package/src/commands/client.ts +76 -76
- package/src/commands/debug.ts +20 -20
- package/src/commands/exec.ts +5 -5
- package/src/commands/hatch.ts +22 -22
- package/src/commands/login.ts +22 -22
- package/src/commands/logs.ts +1 -1
- package/src/commands/ps.ts +188 -79
- package/src/commands/restart.ts +30 -0
- package/src/commands/restore.ts +138 -20
- package/src/commands/retire.ts +46 -148
- package/src/commands/rollback.ts +18 -24
- package/src/commands/setup.ts +2 -2
- package/src/commands/teleport.ts +93 -93
- package/src/commands/terminal.ts +157 -17
- package/src/commands/tunnel.ts +80 -122
- package/src/commands/uninstall.ts +55 -7
- package/src/commands/unpair.ts +3 -3
- package/src/commands/upgrade.ts +22 -46
- package/src/commands/wake.ts +162 -46
- package/src/index.ts +3 -9
- package/src/lib/__tests__/{platform-client-signed-url.test.ts → cloud-client-signed-url.test.ts} +16 -16
- package/src/lib/__tests__/job-polling.test.ts +1 -1
- 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} +5 -5
- 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 +111 -10
- 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/guardian-token.ts +36 -4
- 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 +1 -1
- 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 +9 -30
- package/src/lib/nginx-ingress.ts +41 -14
- 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 +18 -18
- 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} +15 -4
- package/src/lib/tunnels/gateway-notify.ts +61 -0
- package/src/lib/{ngrok.ts → tunnels/ngrok.ts} +201 -30
- package/src/lib/{tailscale-tunnel.ts → tunnels/tailscale.ts} +19 -5
- package/src/lib/upgrade-lifecycle.ts +3 -17
- 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/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/lib/nginx-ingress.ts
CHANGED
|
@@ -19,10 +19,13 @@ import { join } from "node:path";
|
|
|
19
19
|
import { cloudAssistantHubUrl } from "@mycadet/environments";
|
|
20
20
|
|
|
21
21
|
import {
|
|
22
|
+
clearRootProcess,
|
|
22
23
|
getAssistantDisplayName,
|
|
23
24
|
lookupAssistantByIdentifier,
|
|
25
|
+
stampRootProcess,
|
|
24
26
|
type AssistantEntry,
|
|
25
27
|
} from "./assistant-config.js";
|
|
28
|
+
import type { RemoteHost } from "./constants.js";
|
|
26
29
|
import { getCurrentEnvironment } from "./environments/resolve.js";
|
|
27
30
|
import { waitForDaemonReady } from "./http-client.js";
|
|
28
31
|
import {
|
|
@@ -159,6 +162,12 @@ export function cloudWebHubUrl(env: string | undefined): string {
|
|
|
159
162
|
return cloudAssistantHubUrl(env);
|
|
160
163
|
}
|
|
161
164
|
|
|
165
|
+
// The edge serves remote browsers, so the bundle's remote-gateway machinery
|
|
166
|
+
// must always run: it is reached through this self-hosted edge however the
|
|
167
|
+
// cadet itself is hosted. "cloud" is never stamped (the cloud serves its
|
|
168
|
+
// own bundles).
|
|
169
|
+
const EDGE_SERVED_REMOTE: RemoteHost = "custom";
|
|
170
|
+
|
|
162
171
|
function remoteWebIngressConfig(
|
|
163
172
|
opts: Pick<
|
|
164
173
|
RemoteWebIngressOptions,
|
|
@@ -166,10 +175,10 @@ function remoteWebIngressConfig(
|
|
|
166
175
|
>,
|
|
167
176
|
): Record<string, unknown> {
|
|
168
177
|
return {
|
|
169
|
-
|
|
178
|
+
remote: EDGE_SERVED_REMOTE,
|
|
170
179
|
apiBaseUrl: "/v1",
|
|
171
|
-
|
|
172
|
-
|
|
180
|
+
cloudDisabled: true,
|
|
181
|
+
disableCloud: true,
|
|
173
182
|
...(opts.assistantName ? { assistantName: opts.assistantName } : {}),
|
|
174
183
|
...(opts.assistantId ? { assistantId: opts.assistantId } : {}),
|
|
175
184
|
...(opts.hubUrl ? { hubUrl: opts.hubUrl } : {}),
|
|
@@ -182,7 +191,7 @@ function remoteWebIngressConfig(
|
|
|
182
191
|
* fingerprint matches, so this must change whenever the generated index or
|
|
183
192
|
* nginx template does.
|
|
184
193
|
*/
|
|
185
|
-
export const EDGE_TEMPLATE_VERSION =
|
|
194
|
+
export const EDGE_TEMPLATE_VERSION = 9;
|
|
186
195
|
|
|
187
196
|
/**
|
|
188
197
|
* Stable fingerprint of the SPA config injected into the served index and
|
|
@@ -353,7 +362,7 @@ http {
|
|
|
353
362
|
# remote client cannot smuggle one. Every caller reaches this loopback-only
|
|
354
363
|
# listener through the TLS-terminating front (tunnel agent), so the raw peer
|
|
355
364
|
# is always 127.0.0.1; the front records the real client as the RIGHTMOST
|
|
356
|
-
# X-Forwarded-For entry (
|
|
365
|
+
# X-Forwarded-For entry (tunnel providers append one; tailscale serve sets it),
|
|
357
366
|
# which the remote client cannot control. Fall back to the raw peer when the
|
|
358
367
|
# front sets no X-Forwarded-For.
|
|
359
368
|
map $http_x_forwarded_for $cadet_edge_client_ip {
|
|
@@ -487,7 +496,8 @@ export function getNginxVersion(): string | null {
|
|
|
487
496
|
* process.ts helpers: stopProcessByPidFile's isCadetProcess() guard only
|
|
488
497
|
* matches command lines containing a cadet path, which fails for a custom
|
|
489
498
|
* CADET_WORKSPACE_DIR and would silently leave nginx running (the same
|
|
490
|
-
* reason local.ts
|
|
499
|
+
* reason local.ts stops the tunnel agent directly). This module is also
|
|
500
|
+
* imported by
|
|
491
501
|
* sleep/retire, whose tests mock.module() process.js process-globally —
|
|
492
502
|
* depending on it here would couple this lib's behavior to those mocks.
|
|
493
503
|
*/
|
|
@@ -618,13 +628,15 @@ function clearIngressState(workspaceDir: string): void {
|
|
|
618
628
|
function clearStoppedIngress(workspaceDir: string, pidPath: string): void {
|
|
619
629
|
clearIngressState(workspaceDir);
|
|
620
630
|
rmSync(pidPath, { force: true });
|
|
631
|
+
// The edge is gone; drop the root lockfile's process entry with it.
|
|
632
|
+
clearRootProcess("app");
|
|
621
633
|
}
|
|
622
634
|
|
|
623
635
|
/**
|
|
624
|
-
* Write the nginx config and spawn nginx detached (
|
|
625
|
-
*
|
|
626
|
-
*
|
|
627
|
-
*
|
|
636
|
+
* Write the nginx config and spawn nginx detached (stdout/stderr to a log
|
|
637
|
+
* file, fd closed after spawn, caller unrefs). nginx runs with `daemon off`
|
|
638
|
+
* so the spawned process is the master; it writes its pid to nginx.pid under
|
|
639
|
+
* the prefix dir.
|
|
628
640
|
*/
|
|
629
641
|
export function startIngressNginx(opts: {
|
|
630
642
|
workspaceDir: string;
|
|
@@ -1028,8 +1040,22 @@ export async function stopContainerTunnelEdge(
|
|
|
1028
1040
|
return stopIngressNginx(workspaceDir);
|
|
1029
1041
|
}
|
|
1030
1042
|
|
|
1031
|
-
/**
|
|
1032
|
-
|
|
1043
|
+
/** Record the running edge's pid in the root lockfile for `cadet ps`. */
|
|
1044
|
+
function stampAppProcessEntry(
|
|
1045
|
+
workspaceDir: string,
|
|
1046
|
+
assistantId?: string,
|
|
1047
|
+
): void {
|
|
1048
|
+
const pid = getIngressPid(workspaceDir);
|
|
1049
|
+
if (pid !== null) {
|
|
1050
|
+
stampRootProcess("app", {
|
|
1051
|
+
pid,
|
|
1052
|
+
startedAt: new Date().toISOString(),
|
|
1053
|
+
assistantId,
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
/** Resolved edge a tunnel (or bring-your-own HTTPS front) should target. */ export interface TunnelEdge {
|
|
1033
1059
|
/** Loopback listen port the HTTPS front should forward to. */
|
|
1034
1060
|
port: number;
|
|
1035
1061
|
/** True when this call started the edge, false when a running one was reused. */
|
|
@@ -1081,7 +1107,6 @@ export async function ensureTunnelEdge(opts: {
|
|
|
1081
1107
|
const assistantName = opts.assistantId
|
|
1082
1108
|
? lockfileAssistantName(opts.assistantId)
|
|
1083
1109
|
: undefined;
|
|
1084
|
-
|
|
1085
1110
|
const result = await startRemoteWebIngress({
|
|
1086
1111
|
workspaceDir: opts.workspaceDir,
|
|
1087
1112
|
gatewayPort: opts.gatewayPort,
|
|
@@ -1093,6 +1118,7 @@ export async function ensureTunnelEdge(opts: {
|
|
|
1093
1118
|
|
|
1094
1119
|
switch (result.status) {
|
|
1095
1120
|
case "started":
|
|
1121
|
+
stampAppProcessEntry(opts.workspaceDir, opts.assistantId);
|
|
1096
1122
|
return {
|
|
1097
1123
|
port: result.listenPort,
|
|
1098
1124
|
started: true,
|
|
@@ -1126,6 +1152,7 @@ export async function ensureTunnelEdge(opts: {
|
|
|
1126
1152
|
stuckEdgeRecoveryHint(opts.workspaceDir),
|
|
1127
1153
|
);
|
|
1128
1154
|
}
|
|
1155
|
+
stampAppProcessEntry(opts.workspaceDir, opts.assistantId);
|
|
1129
1156
|
return {
|
|
1130
1157
|
port: result.listenPort,
|
|
1131
1158
|
started: false,
|
|
@@ -1141,7 +1168,7 @@ export async function ensureTunnelEdge(opts: {
|
|
|
1141
1168
|
case "web-dist-missing":
|
|
1142
1169
|
throw new Error(
|
|
1143
1170
|
"Unable to locate built web assets for the remote web edge. " +
|
|
1144
|
-
"Build the SPA (`cd clients/web &&
|
|
1171
|
+
"Build the SPA (`cd clients/web && VITE_CLOUD_MODE=false bun run build`) " +
|
|
1145
1172
|
"or install @mycadet/web so its packaged dist directory is available.",
|
|
1146
1173
|
);
|
|
1147
1174
|
case "unreachable":
|
package/src/lib/process.ts
CHANGED
|
@@ -76,7 +76,7 @@ export function windowsCommandLineLookupArgs(pid: number): string[] {
|
|
|
76
76
|
];
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
/** Read a process command line using the
|
|
79
|
+
/** Read a process command line using the cloud's process inspection tool. */
|
|
80
80
|
export function readProcessCommandLine(
|
|
81
81
|
pid: number,
|
|
82
82
|
hostPlatform: NodeJS.Platform = platform(),
|
|
@@ -86,7 +86,7 @@ const PROVIDER_LABELS: Record<LlmProviderId, string> = {
|
|
|
86
86
|
typesafe: "TypeSafe",
|
|
87
87
|
};
|
|
88
88
|
|
|
89
|
-
// Ranked for the hatch prompt: Cadet Cloud first (the
|
|
89
|
+
// Ranked for the hatch prompt: Cadet Cloud first (the cloud connection —
|
|
90
90
|
// no key to collect), then the hosted-inference gateways, then the BYO-key
|
|
91
91
|
// incumbents, then the rest, with the endpoint-supplied catalogs last.
|
|
92
92
|
export const HATCH_PROVIDER_CHOICES: readonly LlmProviderId[] = [
|
|
@@ -681,11 +681,11 @@ export async function ensureProviderApiKey(
|
|
|
681
681
|
|
|
682
682
|
if (!envVarName) {
|
|
683
683
|
// Managed-connection provider (e.g. Cadet Cloud): authenticated through
|
|
684
|
-
// the
|
|
684
|
+
// the cloud, not a user-supplied API key. Checked before the
|
|
685
685
|
// supported-provider guard so the keyless flow stays reachable.
|
|
686
686
|
return {
|
|
687
687
|
status: "skipped",
|
|
688
|
-
message: `${providerName} authenticates through the
|
|
688
|
+
message: `${providerName} authenticates through the cloud connection; no API key needed.`,
|
|
689
689
|
};
|
|
690
690
|
}
|
|
691
691
|
|
|
@@ -7,7 +7,7 @@ import type { AssistantEntry } from "./assistant-config.js";
|
|
|
7
7
|
* Retire an Apple Container assistant by sending a retire command to the
|
|
8
8
|
* macOS app via the management socket. The app handles the full lifecycle:
|
|
9
9
|
* stop the pod, archive the instance directory, remove the guardian token,
|
|
10
|
-
* deregister from the
|
|
10
|
+
* deregister from the cloud, and remove the lockfile entry.
|
|
11
11
|
*/
|
|
12
12
|
export async function retireAppleContainer(
|
|
13
13
|
name: string,
|
package/src/lib/runtime-url.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isCloudHome } from "./cloud-values.js";
|
|
2
2
|
import { hostname } from "node:os";
|
|
3
3
|
|
|
4
4
|
import { getLocalLanIPv4 } from "./local";
|
|
@@ -11,12 +11,12 @@ import type { AssistantEntry } from "./assistant-config.js";
|
|
|
11
11
|
* - For local/docker assistants, `runtimeUrl` is the loopback gateway and
|
|
12
12
|
* the runtime serves `/v1/migrations/<subpath>` directly. The CLI hits
|
|
13
13
|
* that path with guardian-token bearer auth.
|
|
14
|
-
* - For
|
|
15
|
-
* platform host (e.g. `https://cloud.mycadet.ai`). The
|
|
14
|
+
* - For cloud-managed (cloud="cadet") assistants, `runtimeUrl` is the
|
|
15
|
+
* platform host (e.g. `https://cloud.mycadet.ai`). The cloud's
|
|
16
16
|
* `MigrationViewSet` does NOT expose `export-to-gcs` or arbitrary runtime
|
|
17
17
|
* migration paths under `/v1/migrations/...`. The wildcard runtime proxy
|
|
18
18
|
* at `/v1/assistants/<id>/<path:rest>` is what forwards arbitrary runtime
|
|
19
|
-
* paths to the
|
|
19
|
+
* paths to the cloud runtime — vembda's unified proxy bootstraps the
|
|
20
20
|
* guardian token internally for the runtime call. From the CLI side it's
|
|
21
21
|
* user-session auth.
|
|
22
22
|
*
|
|
@@ -27,7 +27,7 @@ export function resolveRuntimeMigrationUrl(
|
|
|
27
27
|
entry: Pick<AssistantEntry, "cloud" | "runtimeUrl" | "assistantId">,
|
|
28
28
|
subpath: string,
|
|
29
29
|
): string {
|
|
30
|
-
if (
|
|
30
|
+
if (isCloudHome(entry.cloud)) {
|
|
31
31
|
return `${entry.runtimeUrl}/v1/assistants/${entry.assistantId}/migrations/${subpath}`;
|
|
32
32
|
}
|
|
33
33
|
return `${entry.runtimeUrl}/v1/migrations/${subpath}`;
|
|
@@ -39,9 +39,9 @@ export function resolveRuntimeMigrationUrl(
|
|
|
39
39
|
*
|
|
40
40
|
* - For local/docker assistants, `runtimeUrl` is the loopback gateway and
|
|
41
41
|
* the runtime serves `/v1/<subpath>` directly.
|
|
42
|
-
* - For
|
|
42
|
+
* - For cloud-managed (cloud="cadet") assistants the path is rewritten
|
|
43
43
|
* to the wildcard runtime proxy:
|
|
44
|
-
* `{
|
|
44
|
+
* `{cloudUrl}/v1/assistants/<assistantId>/<subpath>`.
|
|
45
45
|
*
|
|
46
46
|
* The `subpath` is appended verbatim (e.g. `"identity"`).
|
|
47
47
|
*/
|
|
@@ -49,7 +49,7 @@ export function resolveRuntimeUrl(
|
|
|
49
49
|
entry: Pick<AssistantEntry, "cloud" | "runtimeUrl" | "assistantId">,
|
|
50
50
|
subpath: string,
|
|
51
51
|
): string {
|
|
52
|
-
if (
|
|
52
|
+
if (isCloudHome(entry.cloud)) {
|
|
53
53
|
return `${entry.runtimeUrl}/v1/assistants/${entry.assistantId}/${subpath}`;
|
|
54
54
|
}
|
|
55
55
|
return `${entry.runtimeUrl}/v1/${subpath}`;
|
package/src/lib/statefulset.ts
CHANGED
|
@@ -199,7 +199,7 @@ export const DOCKER_STATEFUL_SET_SPEC: DockerStatefulSetSpec = {
|
|
|
199
199
|
(v): HostEnv => ({ kind: "host", name: v }),
|
|
200
200
|
),
|
|
201
201
|
{ kind: "host", name: "CADET_ENVIRONMENT" },
|
|
202
|
-
{ kind: "host", name: "
|
|
202
|
+
{ kind: "host", name: "CADET_CLOUD_URL" },
|
|
203
203
|
],
|
|
204
204
|
volumeMounts: [
|
|
205
205
|
{ volumeName: "assistant-workspace", mountPath: "/workspace" },
|
|
@@ -227,7 +227,7 @@ export const DOCKER_STATEFUL_SET_SPEC: DockerStatefulSetSpec = {
|
|
|
227
227
|
{ kind: "secret", name: "ACTOR_TOKEN_SIGNING_KEY", secret: "signingKey" },
|
|
228
228
|
{ kind: "secret", name: "GUARDIAN_BOOTSTRAP_SECRET", secret: "bootstrapSecret" },
|
|
229
229
|
{ kind: "host", name: "CADET_ENVIRONMENT" },
|
|
230
|
-
{ kind: "host", name: "
|
|
230
|
+
{ kind: "host", name: "CADET_CLOUD_URL" },
|
|
231
231
|
{ kind: "host", name: "TUNNEL_BASE_URL" },
|
|
232
232
|
],
|
|
233
233
|
volumeMounts: [
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Sync cloud-managed (platform) assistants into the local lockfile.
|
|
3
3
|
*
|
|
4
4
|
* - Adds new platform assistants that aren't in the lockfile yet.
|
|
5
|
-
* - Removes lockfile entries whose IDs are no longer returned by the
|
|
5
|
+
* - Removes lockfile entries whose IDs are no longer returned by the cloud
|
|
6
6
|
* (e.g. retired assistants).
|
|
7
7
|
*
|
|
8
8
|
* Used by both `cadet login` and `cadet ps` to keep the lockfile fresh.
|
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
* **Contract:** callers must verify the user is logged in (i.e. a non-empty
|
|
11
11
|
* platform token exists) before invoking this helper. The "is there a token?"
|
|
12
12
|
* decision belongs at the command level so commands can render the right
|
|
13
|
-
* "Platform: …" status without ever entering the
|
|
13
|
+
* "Platform: …" status without ever entering the cloud fetch path.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import {
|
|
16
|
+
import { isCloudHome } from "./cloud-values.js";
|
|
17
17
|
import {
|
|
18
18
|
loadAllAssistants,
|
|
19
19
|
removeAssistantEntry,
|
|
@@ -21,9 +21,9 @@ import {
|
|
|
21
21
|
} from "./assistant-config.js";
|
|
22
22
|
import {
|
|
23
23
|
fetchCurrentUser,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
} from "./
|
|
24
|
+
fetchCloudAssistants,
|
|
25
|
+
getCloudUrl,
|
|
26
|
+
} from "./cloud-client.js";
|
|
27
27
|
|
|
28
28
|
export type SyncLogger = (message: string) => void;
|
|
29
29
|
|
|
@@ -58,12 +58,12 @@ export async function syncCloudAssistants(
|
|
|
58
58
|
): Promise<SyncResult | null> {
|
|
59
59
|
if (!token) {
|
|
60
60
|
throw new Error(
|
|
61
|
-
"syncCloudAssistants called without a token. Callers must check `
|
|
61
|
+
"syncCloudAssistants called without a token. Callers must check `readCloudToken()` first.",
|
|
62
62
|
);
|
|
63
63
|
}
|
|
64
64
|
const log = options?.log;
|
|
65
|
-
const
|
|
66
|
-
log?.(`Platform URL: ${
|
|
65
|
+
const cloudUrl = getCloudUrl();
|
|
66
|
+
log?.(`Platform URL: ${cloudUrl}`);
|
|
67
67
|
log?.(`Token found (${token.length} chars, prefix: ${token.slice(0, 6)}…)`);
|
|
68
68
|
|
|
69
69
|
// Fetch user info for the login status line
|
|
@@ -83,12 +83,12 @@ export async function syncCloudAssistants(
|
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
let
|
|
86
|
+
let cloudAssistants: { id: string; name: string; status: string }[];
|
|
87
87
|
try {
|
|
88
88
|
log?.("Fetching Cadet Cloud assistants…");
|
|
89
|
-
|
|
89
|
+
cloudAssistants = await fetchCloudAssistants(token);
|
|
90
90
|
log?.(
|
|
91
|
-
`Cadet Cloud returned ${
|
|
91
|
+
`Cadet Cloud returned ${cloudAssistants.length} assistant(s): ${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);
|
|
@@ -101,17 +101,17 @@ export async function syncCloudAssistants(
|
|
|
101
101
|
return null;
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
if (
|
|
104
|
+
if (cloudAssistants.length === 0) {
|
|
105
105
|
log?.(
|
|
106
106
|
"Cadet Cloud returned 0 assistants — this may mean the API returned a non-ok status (check token validity)",
|
|
107
107
|
);
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
const platformIds = new Set(
|
|
110
|
+
const platformIds = new Set(cloudAssistants.map((a) => a.id));
|
|
111
111
|
|
|
112
112
|
// Add new platform assistants not yet in the lockfile
|
|
113
113
|
const existingCloudEntries = loadAllAssistants().filter((a) =>
|
|
114
|
-
|
|
114
|
+
isCloudHome(a.cloud),
|
|
115
115
|
);
|
|
116
116
|
const existingCloudById = new Map(
|
|
117
117
|
existingCloudEntries.map((a) => [a.assistantId, a]),
|
|
@@ -123,7 +123,7 @@ export async function syncCloudAssistants(
|
|
|
123
123
|
|
|
124
124
|
let added = 0;
|
|
125
125
|
let updated = 0;
|
|
126
|
-
for (const pa of
|
|
126
|
+
for (const pa of cloudAssistants) {
|
|
127
127
|
const existing = existingCloudById.get(pa.id);
|
|
128
128
|
const assistantName = pa.name.trim();
|
|
129
129
|
const nameFields = assistantName ? { name: assistantName } : {};
|
|
@@ -132,7 +132,7 @@ export async function syncCloudAssistants(
|
|
|
132
132
|
saveAssistantEntry({
|
|
133
133
|
assistantId: pa.id,
|
|
134
134
|
...nameFields,
|
|
135
|
-
runtimeUrl:
|
|
135
|
+
runtimeUrl: getCloudUrl(),
|
|
136
136
|
cloud: "cloud",
|
|
137
137
|
hatchedAt: new Date().toISOString(),
|
|
138
138
|
});
|
|
@@ -147,7 +147,7 @@ export async function syncCloudAssistants(
|
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
// Remove stale lockfile entries that the
|
|
150
|
+
// Remove stale lockfile entries that the cloud no longer knows about
|
|
151
151
|
let removed = 0;
|
|
152
152
|
for (const id of existingCloudIds) {
|
|
153
153
|
if (!platformIds.has(id)) {
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
|
|
24
24
|
import type { AssistantEntry } from "./assistant-config.js";
|
|
25
25
|
import { loopbackSafeFetch } from "./loopback-fetch.js";
|
|
26
|
-
import { authHeaders, invalidateOrgIdCache } from "./
|
|
26
|
+
import { authHeaders, invalidateOrgIdCache } from "./cloud-client.js";
|
|
27
27
|
import { z } from "zod";
|
|
28
28
|
|
|
29
29
|
const ManagedBackupEntrySchema = z.object({
|
|
@@ -53,7 +53,7 @@ async function errorSuffix(response: Response): Promise<string> {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
|
-
* Send a
|
|
56
|
+
* Send a cloud backup request with user-session auth, refreshing the
|
|
57
57
|
* cached organization id once on 401 (the same recovery the runtime identity
|
|
58
58
|
* probe applies).
|
|
59
59
|
*/
|
|
@@ -93,7 +93,7 @@ async function fetchPlatformBackups(
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
/**
|
|
96
|
-
* `created_at` of every restorable PVC snapshot of a
|
|
96
|
+
* `created_at` of every restorable PVC snapshot of a cloud assistant:
|
|
97
97
|
* `GET /v1/assistants/<id>/backups/`.
|
|
98
98
|
*/
|
|
99
99
|
export async function listPlatformBackups(
|
|
@@ -109,7 +109,7 @@ export interface CreatePlatformBackupOptions {
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
/**
|
|
112
|
-
* Take a PVC snapshot of a
|
|
112
|
+
* Take a PVC snapshot of a cloud assistant and wait until it is
|
|
113
113
|
* restorable: `POST /v1/assistants/<id>/backups/`, then re-list until the
|
|
114
114
|
* returned `snapshot_name` reports `ready_to_use`. Throws if the POST fails
|
|
115
115
|
* or the snapshot is not ready within `timeoutMs`.
|
|
@@ -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
|
);
|