@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
|
@@ -12,15 +12,15 @@ import { join } from "node:path";
|
|
|
12
12
|
import { mock } from "bun:test";
|
|
13
13
|
|
|
14
14
|
import {
|
|
15
|
-
|
|
15
|
+
clearCloudToken,
|
|
16
16
|
fetchAssistantDetail,
|
|
17
17
|
fetchUpgradeInProgress,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
} from "../lib/
|
|
18
|
+
getCloudUrl,
|
|
19
|
+
readCloudToken,
|
|
20
|
+
saveCloudToken,
|
|
21
|
+
} from "../lib/cloud-client.js";
|
|
22
22
|
|
|
23
|
-
describe("
|
|
23
|
+
describe("cloud-client token path is env-scoped", () => {
|
|
24
24
|
let tempHome: string;
|
|
25
25
|
let savedXdg: string | undefined;
|
|
26
26
|
let savedEnv: string | undefined;
|
|
@@ -28,7 +28,7 @@ describe("platform-client token path is env-scoped", () => {
|
|
|
28
28
|
beforeEach(() => {
|
|
29
29
|
savedXdg = process.env.XDG_CONFIG_HOME;
|
|
30
30
|
savedEnv = process.env.CADET_ENVIRONMENT;
|
|
31
|
-
tempHome = mkdtempSync(join(tmpdir(), "cli-
|
|
31
|
+
tempHome = mkdtempSync(join(tmpdir(), "cli-cloud-client-test-"));
|
|
32
32
|
process.env.XDG_CONFIG_HOME = tempHome;
|
|
33
33
|
delete process.env.CADET_ENVIRONMENT;
|
|
34
34
|
});
|
|
@@ -47,93 +47,93 @@ describe("platform-client token path is env-scoped", () => {
|
|
|
47
47
|
rmSync(tempHome, { recursive: true, force: true });
|
|
48
48
|
});
|
|
49
49
|
|
|
50
|
-
test("prod (CADET_ENVIRONMENT unset) writes to $XDG_CONFIG_HOME/cadet/
|
|
50
|
+
test("prod (CADET_ENVIRONMENT unset) writes to $XDG_CONFIG_HOME/cadet/cloud-token", () => {
|
|
51
51
|
const token = "vak_prod_token_123";
|
|
52
|
-
|
|
52
|
+
saveCloudToken(token);
|
|
53
53
|
|
|
54
|
-
const prodPath = join(tempHome, "cadet", "
|
|
54
|
+
const prodPath = join(tempHome, "cadet", "cloud-token");
|
|
55
55
|
expect(existsSync(prodPath)).toBe(true);
|
|
56
56
|
expect(readFileSync(prodPath, "utf-8").trim()).toBe(token);
|
|
57
|
-
expect(
|
|
57
|
+
expect(readCloudToken()).toBe(token);
|
|
58
58
|
});
|
|
59
59
|
|
|
60
|
-
test("dev (CADET_ENVIRONMENT=dev) writes to $XDG_CONFIG_HOME/cadet-dev/
|
|
60
|
+
test("dev (CADET_ENVIRONMENT=dev) writes to $XDG_CONFIG_HOME/cadet-dev/cloud-token", () => {
|
|
61
61
|
process.env.CADET_ENVIRONMENT = "dev";
|
|
62
62
|
const token = "vak_dev_token_456";
|
|
63
|
-
|
|
63
|
+
saveCloudToken(token);
|
|
64
64
|
|
|
65
|
-
const devPath = join(tempHome, "cadet-dev", "
|
|
65
|
+
const devPath = join(tempHome, "cadet-dev", "cloud-token");
|
|
66
66
|
expect(existsSync(devPath)).toBe(true);
|
|
67
67
|
expect(readFileSync(devPath, "utf-8").trim()).toBe(token);
|
|
68
68
|
|
|
69
|
-
const prodPath = join(tempHome, "cadet", "
|
|
69
|
+
const prodPath = join(tempHome, "cadet", "cloud-token");
|
|
70
70
|
expect(existsSync(prodPath)).toBe(false);
|
|
71
71
|
|
|
72
|
-
expect(
|
|
72
|
+
expect(readCloudToken()).toBe(token);
|
|
73
73
|
});
|
|
74
74
|
|
|
75
75
|
test("prod and dev tokens are isolated on disk", () => {
|
|
76
76
|
// Save prod token
|
|
77
77
|
delete process.env.CADET_ENVIRONMENT;
|
|
78
|
-
|
|
78
|
+
saveCloudToken("prod-token");
|
|
79
79
|
|
|
80
80
|
// Switch to dev and save a different token
|
|
81
81
|
process.env.CADET_ENVIRONMENT = "dev";
|
|
82
|
-
|
|
82
|
+
saveCloudToken("dev-token");
|
|
83
83
|
|
|
84
84
|
// Dev read returns dev
|
|
85
|
-
expect(
|
|
85
|
+
expect(readCloudToken()).toBe("dev-token");
|
|
86
86
|
|
|
87
87
|
// Switch back to prod — prod value is unchanged
|
|
88
88
|
delete process.env.CADET_ENVIRONMENT;
|
|
89
|
-
expect(
|
|
89
|
+
expect(readCloudToken()).toBe("prod-token");
|
|
90
90
|
|
|
91
91
|
// Files live at distinct paths
|
|
92
92
|
expect(
|
|
93
|
-
readFileSync(join(tempHome, "cadet", "
|
|
93
|
+
readFileSync(join(tempHome, "cadet", "cloud-token"), "utf-8").trim(),
|
|
94
94
|
).toBe("prod-token");
|
|
95
95
|
expect(
|
|
96
96
|
readFileSync(
|
|
97
|
-
join(tempHome, "cadet-dev", "
|
|
97
|
+
join(tempHome, "cadet-dev", "cloud-token"),
|
|
98
98
|
"utf-8",
|
|
99
99
|
).trim(),
|
|
100
100
|
).toBe("dev-token");
|
|
101
101
|
});
|
|
102
102
|
|
|
103
|
-
test("
|
|
103
|
+
test("clearCloudToken removes only the env-scoped token", () => {
|
|
104
104
|
// Prod token
|
|
105
105
|
delete process.env.CADET_ENVIRONMENT;
|
|
106
|
-
|
|
106
|
+
saveCloudToken("prod-token");
|
|
107
107
|
|
|
108
108
|
// Dev token
|
|
109
109
|
process.env.CADET_ENVIRONMENT = "dev";
|
|
110
|
-
|
|
110
|
+
saveCloudToken("dev-token");
|
|
111
111
|
|
|
112
112
|
// Clear dev
|
|
113
|
-
|
|
114
|
-
expect(existsSync(join(tempHome, "cadet-dev", "
|
|
113
|
+
clearCloudToken();
|
|
114
|
+
expect(existsSync(join(tempHome, "cadet-dev", "cloud-token"))).toBe(
|
|
115
115
|
false,
|
|
116
116
|
);
|
|
117
117
|
|
|
118
118
|
// Prod still there
|
|
119
|
-
expect(existsSync(join(tempHome, "cadet", "
|
|
119
|
+
expect(existsSync(join(tempHome, "cadet", "cloud-token"))).toBe(true);
|
|
120
120
|
});
|
|
121
121
|
});
|
|
122
122
|
|
|
123
|
-
describe("
|
|
123
|
+
describe("getCloudUrl resolution order", () => {
|
|
124
124
|
let tempLockDir: string;
|
|
125
125
|
let savedLockDir: string | undefined;
|
|
126
126
|
let savedEnv: string | undefined;
|
|
127
|
-
let
|
|
127
|
+
let savedCloudUrl: string | undefined;
|
|
128
128
|
|
|
129
129
|
beforeEach(() => {
|
|
130
130
|
savedLockDir = process.env.CADET_LOCKFILE_DIR;
|
|
131
131
|
savedEnv = process.env.CADET_ENVIRONMENT;
|
|
132
|
-
|
|
133
|
-
tempLockDir = mkdtempSync(join(tmpdir(), "cli-
|
|
132
|
+
savedCloudUrl = process.env.CADET_CLOUD_URL;
|
|
133
|
+
tempLockDir = mkdtempSync(join(tmpdir(), "cli-cloud-url-test-"));
|
|
134
134
|
process.env.CADET_LOCKFILE_DIR = tempLockDir;
|
|
135
135
|
delete process.env.CADET_ENVIRONMENT;
|
|
136
|
-
delete process.env.
|
|
136
|
+
delete process.env.CADET_CLOUD_URL;
|
|
137
137
|
});
|
|
138
138
|
|
|
139
139
|
afterEach(() => {
|
|
@@ -147,10 +147,10 @@ describe("getPlatformUrl resolution order", () => {
|
|
|
147
147
|
} else {
|
|
148
148
|
process.env.CADET_ENVIRONMENT = savedEnv;
|
|
149
149
|
}
|
|
150
|
-
if (
|
|
151
|
-
delete process.env.
|
|
150
|
+
if (savedCloudUrl === undefined) {
|
|
151
|
+
delete process.env.CADET_CLOUD_URL;
|
|
152
152
|
} else {
|
|
153
|
-
process.env.
|
|
153
|
+
process.env.CADET_CLOUD_URL = savedCloudUrl;
|
|
154
154
|
}
|
|
155
155
|
rmSync(tempLockDir, { recursive: true, force: true });
|
|
156
156
|
});
|
|
@@ -163,47 +163,47 @@ describe("getPlatformUrl resolution order", () => {
|
|
|
163
163
|
);
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
-
test("returns lockfile
|
|
167
|
-
writeLockfile({
|
|
168
|
-
expect(
|
|
166
|
+
test("returns lockfile cloudBaseUrl when set", () => {
|
|
167
|
+
writeLockfile({ cloudBaseUrl: "https://staging.mycadet.ai" });
|
|
168
|
+
expect(getCloudUrl()).toBe("https://staging.mycadet.ai");
|
|
169
169
|
});
|
|
170
170
|
|
|
171
|
-
test("lockfile
|
|
172
|
-
writeLockfile({
|
|
173
|
-
process.env.
|
|
174
|
-
expect(
|
|
171
|
+
test("lockfile cloudBaseUrl takes priority over CADET_CLOUD_URL", () => {
|
|
172
|
+
writeLockfile({ cloudBaseUrl: "https://lockfile.mycadet.ai" });
|
|
173
|
+
process.env.CADET_CLOUD_URL = "https://env.mycadet.ai";
|
|
174
|
+
expect(getCloudUrl()).toBe("https://lockfile.mycadet.ai");
|
|
175
175
|
});
|
|
176
176
|
|
|
177
|
-
test("falls back to
|
|
178
|
-
process.env.
|
|
179
|
-
expect(
|
|
177
|
+
test("falls back to CADET_CLOUD_URL when lockfile is missing", () => {
|
|
178
|
+
process.env.CADET_CLOUD_URL = "https://env-only.mycadet.ai";
|
|
179
|
+
expect(getCloudUrl()).toBe("https://env-only.mycadet.ai");
|
|
180
180
|
});
|
|
181
181
|
|
|
182
|
-
test("falls back to
|
|
182
|
+
test("falls back to CADET_CLOUD_URL when lockfile has no cloudBaseUrl", () => {
|
|
183
183
|
writeLockfile({ assistants: [] });
|
|
184
|
-
process.env.
|
|
185
|
-
expect(
|
|
184
|
+
process.env.CADET_CLOUD_URL = "https://env-fallback.mycadet.ai";
|
|
185
|
+
expect(getCloudUrl()).toBe("https://env-fallback.mycadet.ai");
|
|
186
186
|
});
|
|
187
187
|
|
|
188
|
-
test("falls back to
|
|
189
|
-
writeLockfile({
|
|
190
|
-
process.env.
|
|
191
|
-
expect(
|
|
188
|
+
test("falls back to CADET_CLOUD_URL when lockfile cloudBaseUrl is blank", () => {
|
|
189
|
+
writeLockfile({ cloudBaseUrl: " " });
|
|
190
|
+
process.env.CADET_CLOUD_URL = "https://env-after-blank.mycadet.ai";
|
|
191
|
+
expect(getCloudUrl()).toBe("https://env-after-blank.mycadet.ai");
|
|
192
192
|
});
|
|
193
193
|
|
|
194
|
-
test("falls back to prod env seed URL when lockfile and
|
|
194
|
+
test("falls back to prod env seed URL when lockfile and CADET_CLOUD_URL are unset (prod env)", () => {
|
|
195
195
|
// CADET_ENVIRONMENT is unset → production → prod seed URL.
|
|
196
|
-
expect(
|
|
196
|
+
expect(getCloudUrl()).toBe("https://cloud.mycadet.ai");
|
|
197
197
|
});
|
|
198
198
|
|
|
199
199
|
test("falls back to dev env seed URL when CADET_ENVIRONMENT=dev", () => {
|
|
200
200
|
process.env.CADET_ENVIRONMENT = "dev";
|
|
201
|
-
expect(
|
|
201
|
+
expect(getCloudUrl()).toBe("https://dev-cloud.mycadet.ai");
|
|
202
202
|
});
|
|
203
203
|
|
|
204
|
-
test("trims whitespace from
|
|
205
|
-
process.env.
|
|
206
|
-
expect(
|
|
204
|
+
test("trims whitespace from CADET_CLOUD_URL", () => {
|
|
205
|
+
process.env.CADET_CLOUD_URL = " https://trimmed.mycadet.ai ";
|
|
206
|
+
expect(getCloudUrl()).toBe("https://trimmed.mycadet.ai");
|
|
207
207
|
});
|
|
208
208
|
});
|
|
209
209
|
|
|
@@ -5,9 +5,13 @@ import {
|
|
|
5
5
|
fetchReleases,
|
|
6
6
|
resolveImageRefs,
|
|
7
7
|
resolveOwnImageRefs,
|
|
8
|
-
} from "../lib/
|
|
8
|
+
} from "../lib/cloud-releases.js";
|
|
9
9
|
import { dockerhubImages } from "../lib/docker.js";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
isDevVersion,
|
|
12
|
+
isLocalVersion,
|
|
13
|
+
isStagingVersion,
|
|
14
|
+
} from "../lib/version-compat.js";
|
|
11
15
|
|
|
12
16
|
const originalFetch = globalThis.fetch;
|
|
13
17
|
|
|
@@ -27,12 +31,12 @@ function mockFetchError() {
|
|
|
27
31
|
}
|
|
28
32
|
|
|
29
33
|
beforeEach(() => {
|
|
30
|
-
process.env.
|
|
34
|
+
process.env.CADET_CLOUD_URL = "https://platform.test";
|
|
31
35
|
});
|
|
32
36
|
|
|
33
37
|
afterEach(() => {
|
|
34
38
|
globalThis.fetch = originalFetch;
|
|
35
|
-
delete process.env.
|
|
39
|
+
delete process.env.CADET_CLOUD_URL;
|
|
36
40
|
});
|
|
37
41
|
|
|
38
42
|
const RELEASE = {
|
|
@@ -60,9 +64,9 @@ describe("fetchReleases", () => {
|
|
|
60
64
|
expect(url).toContain("limit=100");
|
|
61
65
|
});
|
|
62
66
|
|
|
63
|
-
test("uses the
|
|
67
|
+
test("uses the cloudUrl override over the resolved default", async () => {
|
|
64
68
|
const fetchMock = mockFetchJson([RELEASE]);
|
|
65
|
-
await fetchReleases({
|
|
69
|
+
await fetchReleases({ cloudUrl: "https://other-platform.test" });
|
|
66
70
|
const url = String(fetchMock.mock.calls[0][0]);
|
|
67
71
|
expect(url).toContain("https://other-platform.test/v1/releases/");
|
|
68
72
|
});
|
|
@@ -79,12 +83,12 @@ describe("fetchReleases", () => {
|
|
|
79
83
|
});
|
|
80
84
|
|
|
81
85
|
describe("resolveImageRefs", () => {
|
|
82
|
-
test("released CLI resolves un-suffixed repos tagged with the
|
|
86
|
+
test("released CLI resolves un-suffixed repos tagged with the bare version", () => {
|
|
83
87
|
const tags = resolveImageRefs("v0.12.6", "0.12.6");
|
|
84
|
-
expect(tags.assistant).toBe("mycadet/mycadet-assistant:
|
|
85
|
-
expect(tags.gateway).toBe("mycadet/mycadet-gateway:
|
|
88
|
+
expect(tags.assistant).toBe("mycadet/mycadet-assistant:0.12.6");
|
|
89
|
+
expect(tags.gateway).toBe("mycadet/mycadet-gateway:0.12.6");
|
|
86
90
|
expect(tags["credential-executor"]).toBe(
|
|
87
|
-
"mycadet/mycadet-credential-executor:
|
|
91
|
+
"mycadet/mycadet-credential-executor:0.12.6",
|
|
88
92
|
);
|
|
89
93
|
});
|
|
90
94
|
|
|
@@ -94,31 +98,32 @@ describe("resolveImageRefs", () => {
|
|
|
94
98
|
"0.12.6-dev.202610081023.3a27b77",
|
|
95
99
|
);
|
|
96
100
|
expect(tags.assistant).toBe(
|
|
97
|
-
"mycadet/mycadet-assistant-dev:
|
|
101
|
+
"mycadet/mycadet-assistant-dev:0.12.6-dev.202610081023.3a27b77",
|
|
98
102
|
);
|
|
99
103
|
expect(tags.gateway).toBe(
|
|
100
|
-
"mycadet/mycadet-gateway-dev:
|
|
104
|
+
"mycadet/mycadet-gateway-dev:0.12.6-dev.202610081023.3a27b77",
|
|
101
105
|
);
|
|
102
106
|
expect(tags["credential-executor"]).toBe(
|
|
103
|
-
"mycadet/mycadet-credential-executor-dev:
|
|
107
|
+
"mycadet/mycadet-credential-executor-dev:0.12.6-dev.202610081023.3a27b77",
|
|
104
108
|
);
|
|
105
109
|
});
|
|
106
110
|
|
|
107
111
|
test("repo suffix follows the CLI version, tag follows the target", () => {
|
|
108
112
|
const tags = resolveImageRefs("v0.12.5", "0.12.6-dev.202610081023.3a27b77");
|
|
109
|
-
expect(tags.assistant).toBe("mycadet/mycadet-assistant-dev:
|
|
113
|
+
expect(tags.assistant).toBe("mycadet/mycadet-assistant-dev:0.12.5");
|
|
110
114
|
});
|
|
111
115
|
|
|
112
116
|
test("does not consult the network", async () => {
|
|
113
117
|
mockFetchError();
|
|
114
118
|
const tags = resolveImageRefs("v0.12.6", "0.12.6");
|
|
115
|
-
expect(tags.assistant).toContain(":
|
|
119
|
+
expect(tags.assistant).toContain(":0.12.6");
|
|
116
120
|
});
|
|
117
121
|
|
|
118
|
-
test("normalizes the v-prefix
|
|
122
|
+
test("normalizes the v-prefix off the tag", () => {
|
|
119
123
|
const withoutPrefix = resolveImageRefs("0.12.6", "0.12.6");
|
|
120
124
|
const withPrefix = resolveImageRefs("v0.12.6", "0.12.6");
|
|
121
|
-
expect(withoutPrefix.assistant).toBe(
|
|
125
|
+
expect(withoutPrefix.assistant).toBe("mycadet/mycadet-assistant:0.12.6");
|
|
126
|
+
expect(withPrefix.assistant).toBe(withoutPrefix.assistant);
|
|
122
127
|
});
|
|
123
128
|
});
|
|
124
129
|
|
|
@@ -126,9 +131,14 @@ describe("resolveOwnImageRefs", () => {
|
|
|
126
131
|
test("uses the CLI's own version for both tag and repo suffix", () => {
|
|
127
132
|
const tags = resolveOwnImageRefs("0.12.6-dev.202610081023.3a27b77");
|
|
128
133
|
expect(tags.assistant).toBe(
|
|
129
|
-
"mycadet/mycadet-assistant-dev:
|
|
134
|
+
"mycadet/mycadet-assistant-dev:0.12.6-dev.202610081023.3a27b77",
|
|
130
135
|
);
|
|
131
136
|
});
|
|
137
|
+
|
|
138
|
+
test("falls back to the literal latest tag when the CLI version is unknown", () => {
|
|
139
|
+
const tags = resolveOwnImageRefs("");
|
|
140
|
+
expect(tags.assistant).toBe("mycadet/mycadet-assistant:latest");
|
|
141
|
+
});
|
|
132
142
|
});
|
|
133
143
|
|
|
134
144
|
describe("dockerhubImages", () => {
|
|
@@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test";
|
|
|
2
2
|
import { EventEmitter } from "node:events";
|
|
3
3
|
import type { ChildProcess } from "node:child_process";
|
|
4
4
|
|
|
5
|
-
import { waitForCloudflareTunnelUrl } from "../lib/cloudflare
|
|
5
|
+
import { waitForCloudflareTunnelUrl } from "../lib/tunnels/cloudflare.js";
|
|
6
6
|
|
|
7
7
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
8
8
|
|
|
@@ -27,7 +27,9 @@ import {
|
|
|
27
27
|
findAssistantByName,
|
|
28
28
|
saveAssistantEntry,
|
|
29
29
|
} from "../lib/assistant-config.js";
|
|
30
|
+
import { z } from "zod";
|
|
30
31
|
import { loadGuardianToken } from "../lib/guardian-token.js";
|
|
32
|
+
import { withPreconnect } from "./helpers/http.js";
|
|
31
33
|
|
|
32
34
|
const HOST = "https://host.example.ts.net";
|
|
33
35
|
const PAIRING_LINK = `${HOST}/assistant/pair#device_code=dev-code`;
|
|
@@ -69,6 +71,9 @@ function approvedBody(overrides: Record<string, unknown> = {}) {
|
|
|
69
71
|
};
|
|
70
72
|
}
|
|
71
73
|
|
|
74
|
+
/** The commands always send flat string-keyed JSON bodies. */
|
|
75
|
+
const JsonRecord = z.record(z.string(), z.unknown());
|
|
76
|
+
|
|
72
77
|
interface FetchCall {
|
|
73
78
|
url: string;
|
|
74
79
|
body: Record<string, unknown>;
|
|
@@ -79,17 +84,20 @@ function stubFetch(
|
|
|
79
84
|
replies: (call: FetchCall, index: number) => Response,
|
|
80
85
|
): FetchCall[] {
|
|
81
86
|
const calls: FetchCall[] = [];
|
|
82
|
-
globalThis.fetch = (
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
string
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
87
|
+
globalThis.fetch = withPreconnect(
|
|
88
|
+
async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
89
|
+
const call = {
|
|
90
|
+
url: String(input),
|
|
91
|
+
// The command always sends a JSON string body; anything else records
|
|
92
|
+
// as an empty record.
|
|
93
|
+
body: JsonRecord.parse(
|
|
94
|
+
JSON.parse(typeof init?.body === "string" ? init.body : "{}"),
|
|
95
|
+
),
|
|
96
|
+
};
|
|
97
|
+
calls.push(call);
|
|
98
|
+
return replies(call, calls.length - 1);
|
|
99
|
+
},
|
|
100
|
+
);
|
|
93
101
|
return calls;
|
|
94
102
|
}
|
|
95
103
|
|
|
@@ -112,14 +120,16 @@ async function runExpectingExit(): Promise<{ exited: boolean; out: string }> {
|
|
|
112
120
|
const logSpy = spyOn(console, "log").mockImplementation((...a: unknown[]) => {
|
|
113
121
|
messages.push(a.join(" "));
|
|
114
122
|
});
|
|
115
|
-
const exitSpy = spyOn(process, "exit").mockImplementation(
|
|
116
|
-
|
|
117
|
-
|
|
123
|
+
const exitSpy = spyOn(process, "exit").mockImplementation(
|
|
124
|
+
(c?: number | string | undefined): never => {
|
|
125
|
+
throw new Error(`exit:${c}`);
|
|
126
|
+
},
|
|
127
|
+
);
|
|
118
128
|
let exited = false;
|
|
119
129
|
try {
|
|
120
130
|
await connectImport();
|
|
121
131
|
} catch (e) {
|
|
122
|
-
exited =
|
|
132
|
+
exited = e instanceof Error && e.message === "exit:1";
|
|
123
133
|
} finally {
|
|
124
134
|
errSpy.mockRestore();
|
|
125
135
|
logSpy.mockRestore();
|
|
@@ -144,10 +154,13 @@ describe("connect import", () => {
|
|
|
144
154
|
// a multi-second cadence is exercised in full without being spent, and
|
|
145
155
|
// both the cadence and the backoff are assertable rather than implicit.
|
|
146
156
|
setSystemTime(new Date());
|
|
147
|
-
sleepSpy = spyOn(Bun, "sleep").mockImplementation(
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
157
|
+
sleepSpy = spyOn(Bun, "sleep").mockImplementation(
|
|
158
|
+
async (ms: number | Date) => {
|
|
159
|
+
const waitMs = typeof ms === "number" ? ms : ms.getTime() - Date.now();
|
|
160
|
+
waits.push(waitMs);
|
|
161
|
+
setSystemTime(new Date(Date.now() + waitMs));
|
|
162
|
+
},
|
|
163
|
+
);
|
|
151
164
|
});
|
|
152
165
|
|
|
153
166
|
afterEach(() => {
|
|
@@ -312,10 +325,10 @@ describe("connect import", () => {
|
|
|
312
325
|
"http://insecure.example.com",
|
|
313
326
|
];
|
|
314
327
|
let fetched = false;
|
|
315
|
-
globalThis.fetch = (async () => {
|
|
328
|
+
globalThis.fetch = withPreconnect(async () => {
|
|
316
329
|
fetched = true;
|
|
317
330
|
return jsonResponse({});
|
|
318
|
-
})
|
|
331
|
+
});
|
|
319
332
|
|
|
320
333
|
const { exited, out } = await runExpectingExit();
|
|
321
334
|
|
|
@@ -333,10 +346,10 @@ describe("connect import", () => {
|
|
|
333
346
|
"https://127.0.0.1:7830",
|
|
334
347
|
];
|
|
335
348
|
let fetched = false;
|
|
336
|
-
globalThis.fetch = (async () => {
|
|
349
|
+
globalThis.fetch = withPreconnect(async () => {
|
|
337
350
|
fetched = true;
|
|
338
351
|
return jsonResponse({});
|
|
339
|
-
})
|
|
352
|
+
});
|
|
340
353
|
|
|
341
354
|
const { exited, out } = await runExpectingExit();
|
|
342
355
|
|
|
@@ -361,10 +374,10 @@ describe("connect import", () => {
|
|
|
361
374
|
"legacy-box",
|
|
362
375
|
];
|
|
363
376
|
let fetched = false;
|
|
364
|
-
globalThis.fetch = (async () => {
|
|
377
|
+
globalThis.fetch = withPreconnect(async () => {
|
|
365
378
|
fetched = true;
|
|
366
379
|
return jsonResponse({});
|
|
367
|
-
})
|
|
380
|
+
});
|
|
368
381
|
const { logs, restore } = captureLogs();
|
|
369
382
|
try {
|
|
370
383
|
await connectImport();
|
|
@@ -385,10 +398,10 @@ describe("connect import", () => {
|
|
|
385
398
|
test("a string that is neither an address nor a bundle keeps the address error", async () => {
|
|
386
399
|
process.argv = ["bun", "cadet", "connect", "import", "not-a-url"];
|
|
387
400
|
let fetched = false;
|
|
388
|
-
globalThis.fetch = (async () => {
|
|
401
|
+
globalThis.fetch = withPreconnect(async () => {
|
|
389
402
|
fetched = true;
|
|
390
403
|
return jsonResponse({});
|
|
391
|
-
})
|
|
404
|
+
});
|
|
392
405
|
|
|
393
406
|
const { exited, out } = await runExpectingExit();
|
|
394
407
|
|
|
@@ -410,7 +423,8 @@ describe("connect import", () => {
|
|
|
410
423
|
"flaky-box",
|
|
411
424
|
];
|
|
412
425
|
let attempt = 0;
|
|
413
|
-
globalThis.fetch = (async (
|
|
426
|
+
globalThis.fetch = withPreconnect(async (input: RequestInfo | URL) => {
|
|
427
|
+
const url = String(input);
|
|
414
428
|
if (url === CHALLENGE_URL) {
|
|
415
429
|
return jsonResponse(challengeBody());
|
|
416
430
|
}
|
|
@@ -420,7 +434,7 @@ describe("connect import", () => {
|
|
|
420
434
|
throw new Error("connect ECONNRESET");
|
|
421
435
|
}
|
|
422
436
|
return jsonResponse(approvedBody());
|
|
423
|
-
})
|
|
437
|
+
});
|
|
424
438
|
const { logs, restore } = captureLogs();
|
|
425
439
|
try {
|
|
426
440
|
await connectImport();
|
|
@@ -448,7 +462,8 @@ describe("connect import", () => {
|
|
|
448
462
|
"repairing-box",
|
|
449
463
|
];
|
|
450
464
|
let attempt = 0;
|
|
451
|
-
globalThis.fetch = (async (
|
|
465
|
+
globalThis.fetch = withPreconnect(async (input: RequestInfo | URL) => {
|
|
466
|
+
const url = String(input);
|
|
452
467
|
if (url === CHALLENGE_URL) {
|
|
453
468
|
return jsonResponse(challengeBody());
|
|
454
469
|
}
|
|
@@ -459,7 +474,7 @@ describe("connect import", () => {
|
|
|
459
474
|
return jsonResponse({ error: "guardian repair required" }, 503);
|
|
460
475
|
}
|
|
461
476
|
return jsonResponse(approvedBody());
|
|
462
|
-
})
|
|
477
|
+
});
|
|
463
478
|
const { logs, restore } = captureLogs();
|
|
464
479
|
try {
|
|
465
480
|
await connectImport();
|
|
@@ -477,7 +492,8 @@ describe("connect import", () => {
|
|
|
477
492
|
test("reports expiry once transient failures outlast the code", async () => {
|
|
478
493
|
process.argv = ["bun", "cadet", "connect", "import", HOST];
|
|
479
494
|
let exchanges = 0;
|
|
480
|
-
globalThis.fetch = (async (
|
|
495
|
+
globalThis.fetch = withPreconnect(async (input: RequestInfo | URL) => {
|
|
496
|
+
const url = String(input);
|
|
481
497
|
if (url === CHALLENGE_URL) {
|
|
482
498
|
return jsonResponse(
|
|
483
499
|
challengeBody({
|
|
@@ -488,7 +504,7 @@ describe("connect import", () => {
|
|
|
488
504
|
}
|
|
489
505
|
exchanges += 1;
|
|
490
506
|
throw new Error("connect ECONNREFUSED");
|
|
491
|
-
})
|
|
507
|
+
});
|
|
492
508
|
|
|
493
509
|
const { exited, out } = await runExpectingExit();
|
|
494
510
|
|
|
@@ -507,10 +523,10 @@ describe("connect import", () => {
|
|
|
507
523
|
// ten-minute TTL.
|
|
508
524
|
process.argv = ["bun", "cadet", "connect", "import", PAIRING_LINK];
|
|
509
525
|
let attempts = 0;
|
|
510
|
-
globalThis.fetch = (async () => {
|
|
526
|
+
globalThis.fetch = withPreconnect(async () => {
|
|
511
527
|
attempts += 1;
|
|
512
528
|
throw new Error("connect ECONNREFUSED");
|
|
513
|
-
})
|
|
529
|
+
});
|
|
514
530
|
|
|
515
531
|
const { exited, out } = await runExpectingExit();
|
|
516
532
|
|
|
@@ -532,10 +548,10 @@ describe("connect import", () => {
|
|
|
532
548
|
test("rejects an unknown --flag instead of pairing against it", async () => {
|
|
533
549
|
process.argv = ["bun", "cadet", "connect", "import", "--qr", HOST];
|
|
534
550
|
let fetched = false;
|
|
535
|
-
globalThis.fetch = (async () => {
|
|
551
|
+
globalThis.fetch = withPreconnect(async () => {
|
|
536
552
|
fetched = true;
|
|
537
553
|
return jsonResponse({});
|
|
538
|
-
})
|
|
554
|
+
});
|
|
539
555
|
|
|
540
556
|
const { exited, out } = await runExpectingExit();
|
|
541
557
|
|
|
@@ -560,10 +576,10 @@ describe("connect import", () => {
|
|
|
560
576
|
"--frobnicate",
|
|
561
577
|
];
|
|
562
578
|
let fetched = false;
|
|
563
|
-
globalThis.fetch = (async () => {
|
|
579
|
+
globalThis.fetch = withPreconnect(async () => {
|
|
564
580
|
fetched = true;
|
|
565
581
|
return jsonResponse({});
|
|
566
|
-
})
|
|
582
|
+
});
|
|
567
583
|
|
|
568
584
|
const { exited, out } = await runExpectingExit();
|
|
569
585
|
|