@mycadet/cli 0.12.7 → 0.12.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/knip.json +1 -1
- package/node_modules/@mycadet/assistant-api/events/cloud-disconnected.ts +21 -0
- package/node_modules/@mycadet/assistant-api/events/host-bash.ts +1 -1
- package/node_modules/@mycadet/assistant-api/events/host-cu.ts +1 -1
- package/node_modules/@mycadet/assistant-api/events/host-file.ts +1 -1
- package/node_modules/@mycadet/assistant-api/events/notification-intent.ts +1 -1
- package/node_modules/@mycadet/assistant-api/events/show-cloud-login.ts +18 -0
- package/node_modules/@mycadet/assistant-api/index.ts +11 -11
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/package.json +2 -2
- package/node_modules/@mycadet/{service-contracts/src/platform-credential.ts → assistant-api/node_modules/@mycadet/service-contracts/src/cloud-credential.ts} +9 -9
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
- package/node_modules/@mycadet/assistant-api/responses/home.ts +1 -1
- package/node_modules/@mycadet/assistant-api/surfaces.ts +4 -4
- package/node_modules/@mycadet/avatar-manifest/src/__tests__/notification-avatar.test.ts +1 -1
- package/node_modules/@mycadet/avatar-manifest/src/notification-avatar.ts +2 -2
- package/node_modules/@mycadet/environments/src/seeds.ts +6 -6
- package/node_modules/@mycadet/environments/src/shell.test.ts +1 -1
- package/node_modules/@mycadet/environments/src/types.ts +6 -6
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/avatar-manifest/src/__tests__/notification-avatar.test.ts +1 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/avatar-manifest/src/notification-avatar.ts +2 -2
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/environments/src/seeds.ts +6 -6
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/environments/src/shell.test.ts +1 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/environments/src/types.ts +6 -6
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/package.json +2 -2
- package/node_modules/@mycadet/{assistant-api/node_modules/@mycadet/service-contracts/src/platform-credential.ts → local-mode/node_modules/@mycadet/service-contracts/src/cloud-credential.ts} +9 -9
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
- package/node_modules/@mycadet/local-mode/src/__tests__/pair.test.ts +5 -5
- package/node_modules/@mycadet/local-mode/src/__tests__/retire.test.ts +1 -1
- package/node_modules/@mycadet/local-mode/src/config.ts +2 -2
- package/node_modules/@mycadet/local-mode/src/index.ts +1 -1
- package/node_modules/@mycadet/local-mode/src/lockfile-contract.test.ts +3 -3
- package/node_modules/@mycadet/local-mode/src/lockfile-contract.ts +10 -5
- package/node_modules/@mycadet/local-mode/src/lockfile.test.ts +15 -15
- package/node_modules/@mycadet/local-mode/src/lockfile.ts +9 -9
- package/node_modules/@mycadet/local-mode/src/pair.ts +2 -2
- package/node_modules/@mycadet/local-mode/src/retire.ts +3 -3
- package/node_modules/@mycadet/local-mode/src/teleport-backup-policy.ts +2 -2
- package/node_modules/@mycadet/local-mode/src/unpair.ts +2 -2
- package/node_modules/@mycadet/service-contracts/package.json +2 -2
- package/node_modules/@mycadet/{local-mode/node_modules/@mycadet/service-contracts/src/platform-credential.ts → service-contracts/src/cloud-credential.ts} +9 -9
- package/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
- package/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
- package/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
- package/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
- package/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
- package/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
- package/package.json +3 -2
- package/src/__tests__/assistant-api-key-resolution.test.ts +6 -6
- package/src/__tests__/{assistant-client-platform-auth.test.ts → assistant-client-cloud-auth.test.ts} +10 -10
- package/src/__tests__/assistant-client-refresh.test.ts +2 -2
- package/src/__tests__/backup.test.ts +86 -91
- package/src/__tests__/client-token.test.ts +1 -1
- package/src/__tests__/client-tui-refresh.test.ts +1 -1
- package/src/__tests__/client-web-config.test.ts +2 -2
- package/src/__tests__/{platform-client.test.ts → cloud-client.test.ts} +59 -59
- package/src/__tests__/{platform-releases.test.ts → cloud-releases.test.ts} +28 -18
- package/src/__tests__/{cloudflare-tunnel.test.ts → cloudflare.test.ts} +1 -1
- package/src/__tests__/connect-import.test.ts +55 -39
- package/src/__tests__/debug.test.ts +38 -38
- package/src/__tests__/ensure-binary.test.ts +182 -0
- package/src/__tests__/guardian-token.test.ts +149 -56
- package/src/__tests__/hatch-provider-secrets.test.ts +2 -2
- package/src/__tests__/helpers/guardian-token.ts +22 -0
- package/src/__tests__/helpers/http.ts +10 -0
- package/src/__tests__/nginx-ingress.test.ts +21 -20
- package/src/__tests__/ngrok-spawn.test.ts +49 -0
- package/src/__tests__/orphan-detection.test.ts +1 -1
- package/src/__tests__/pair.test.ts +1 -8
- package/src/__tests__/{ps-platform-status.test.ts → ps-cloud-status.test.ts} +79 -26
- package/src/__tests__/ps-table.test.ts +79 -0
- package/src/__tests__/{recover.test.ts → restore.test.ts} +29 -28
- package/src/__tests__/retire.test.ts +58 -199
- package/src/__tests__/root-process-lockfile.test.ts +99 -0
- package/src/__tests__/sleep.test.ts +19 -19
- package/src/__tests__/statefulset.test.ts +8 -8
- package/src/__tests__/{tailscale-tunnel.test.ts → tailscale.test.ts} +2 -2
- package/src/__tests__/teleport-backup.test.ts +3 -3
- package/src/__tests__/teleport.test.ts +181 -197
- package/src/__tests__/tunnel.test.ts +257 -222
- package/src/__tests__/uninstall.test.ts +13 -4
- package/src/__tests__/upgrade-local.test.ts +3 -3
- package/src/__tests__/upgrade-replay-env.test.ts +12 -12
- package/src/__tests__/wake.test.ts +260 -43
- package/src/__tests__/workos-pkce.test.ts +2 -2
- package/src/commands/backup.ts +24 -24
- package/src/commands/client.ts +76 -76
- package/src/commands/completions.test.ts +104 -0
- package/src/commands/completions.ts +87 -0
- package/src/commands/debug.ts +20 -20
- package/src/commands/exec.ts +12 -12
- package/src/commands/flags.ts +2 -2
- package/src/commands/hatch.ts +23 -23
- package/src/commands/login.ts +22 -22
- package/src/commands/logs.ts +8 -8
- package/src/commands/ps.ts +244 -86
- package/src/commands/restart.ts +30 -0
- package/src/commands/restore.ts +140 -22
- package/src/commands/retire.ts +48 -150
- package/src/commands/rollback.ts +20 -26
- package/src/commands/setup.ts +2 -2
- package/src/commands/sleep.ts +2 -2
- package/src/commands/teleport.ts +109 -109
- package/src/commands/terminal.ts +158 -18
- package/src/commands/tunnel.ts +138 -157
- package/src/commands/uninstall.ts +57 -9
- package/src/commands/unpair.ts +3 -3
- package/src/commands/upgrade.ts +30 -54
- package/src/commands/use.ts +1 -1
- package/src/commands/wake.ts +166 -50
- package/src/index.ts +18 -9
- package/src/lib/__tests__/{platform-client-signed-url.test.ts → cloud-client-signed-url.test.ts} +16 -16
- package/src/lib/__tests__/docker.test.ts +168 -20
- package/src/lib/__tests__/job-polling.test.ts +2 -2
- package/src/lib/__tests__/local-runtime-client.test.ts +4 -4
- package/src/lib/__tests__/runtime-url.test.ts +1 -1
- package/src/lib/assistant-api-key-resolution.ts +5 -5
- package/src/lib/assistant-client.ts +8 -8
- package/src/lib/assistant-config.ts +58 -12
- package/src/lib/{platform-client.ts → cloud-client.ts} +107 -98
- package/src/lib/cloud-login.ts +24 -24
- package/src/lib/{platform-releases.ts → cloud-releases.ts} +11 -9
- package/src/lib/cloud-values.ts +3 -0
- package/src/lib/config-utils.ts +1 -1
- package/src/lib/detached-process.ts +1 -1
- package/src/lib/docker.ts +260 -53
- package/src/lib/ensure-binary.ts +171 -0
- package/src/lib/environments/__tests__/paths.test.ts +2 -2
- package/src/lib/environments/__tests__/resolve.test.ts +26 -26
- package/src/lib/environments/resolve.ts +8 -8
- package/src/lib/flag-args.test.ts +1 -1
- package/src/lib/flag-args.ts +1 -1
- package/src/lib/guardian-token.ts +37 -5
- package/src/lib/hatch-local.ts +19 -23
- package/src/lib/health-check.ts +6 -6
- package/src/lib/ingress-config.ts +8 -35
- package/src/lib/job-polling.ts +2 -2
- package/src/lib/live-voice/connection.ts +12 -12
- package/src/lib/live-voice/tunnel.test.ts +11 -11
- package/src/lib/live-voice/tunnel.ts +15 -15
- package/src/lib/local-runtime-client.ts +20 -20
- package/src/lib/local.ts +10 -31
- package/src/lib/nginx-ingress.ts +42 -15
- package/src/lib/pair.ts +1 -1
- package/src/lib/process.ts +1 -1
- package/src/lib/provider-secrets.ts +3 -3
- package/src/lib/retire-apple-container.ts +1 -1
- package/src/lib/runtime-url.ts +8 -8
- package/src/lib/statefulset.ts +2 -2
- package/src/lib/sync-cloud-assistants.ts +19 -19
- package/src/lib/teleport-backup.ts +4 -4
- package/src/lib/terminal-client.ts +17 -17
- package/src/lib/terminal-session.ts +24 -24
- package/src/lib/{tunnel-edge.ts → tunnel.ts} +199 -11
- package/src/lib/tunnels/cadet.ts +29 -0
- package/src/lib/{cloudflare-tunnel.ts → tunnels/cloudflare.ts} +36 -15
- package/src/lib/tunnels/gateway-notify.ts +61 -0
- package/src/lib/{ngrok.ts → tunnels/ngrok.ts} +350 -89
- package/src/lib/{tailscale-tunnel.ts → tunnels/tailscale.ts} +53 -9
- package/src/lib/upgrade-lifecycle.ts +8 -21
- package/src/lib/upgrade-preflight.ts +2 -2
- package/src/lib/workos-pkce.ts +5 -5
- package/node_modules/@mycadet/assistant-api/events/platform-disconnected.ts +0 -21
- package/node_modules/@mycadet/assistant-api/events/show-platform-login.ts +0 -20
- package/src/commands/recover.ts +0 -131
- package/src/commands/roadmap.ts +0 -459
- package/src/commands/ssh.ts +0 -144
- package/src/commands/workflows.ts +0 -305
- package/src/lib/managed-cloud.ts +0 -3
package/src/commands/upgrade.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { resolveExistingLocalSigningKey } from "@mycadet/local-mode";
|
|
2
|
-
import {
|
|
2
|
+
import { isCloudHome } from "../lib/cloud-values.js";
|
|
3
3
|
import { spawnSync } from "child_process";
|
|
4
|
-
import { writeFileSync } from "fs";
|
|
5
4
|
import { join } from "path";
|
|
6
5
|
|
|
7
6
|
import cliPkg from "../../package.json";
|
|
@@ -20,6 +19,7 @@ import {
|
|
|
20
19
|
GATEWAY_INTERNAL_PORT,
|
|
21
20
|
ASSISTANT_INTERNAL_PORT,
|
|
22
21
|
dockerResourceNames,
|
|
22
|
+
pullImage,
|
|
23
23
|
startContainers,
|
|
24
24
|
stopContainers,
|
|
25
25
|
} from "../lib/docker";
|
|
@@ -27,14 +27,14 @@ import {
|
|
|
27
27
|
fetchLatestStableVersion,
|
|
28
28
|
fetchReleases,
|
|
29
29
|
resolveImageRefs,
|
|
30
|
-
} from "../lib/
|
|
30
|
+
} from "../lib/cloud-releases";
|
|
31
31
|
import {
|
|
32
32
|
authHeaders,
|
|
33
33
|
fetchAssistantDetail,
|
|
34
34
|
fetchUpgradeInProgress,
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
} from "../lib/
|
|
35
|
+
getCloudUrl,
|
|
36
|
+
readCloudToken,
|
|
37
|
+
} from "../lib/cloud-client";
|
|
38
38
|
import { checkManagedHealth } from "../lib/health-check.js";
|
|
39
39
|
import {
|
|
40
40
|
evaluateUpgradePoll,
|
|
@@ -47,7 +47,6 @@ import {
|
|
|
47
47
|
} from "../lib/backup-ops.js";
|
|
48
48
|
import { emitCliError, categorizeUpgradeError } from "../lib/cli-error.js";
|
|
49
49
|
import { parseAssistantTargetArg } from "../lib/assistant-target-args.js";
|
|
50
|
-
import { exec } from "../lib/step-runner.js";
|
|
51
50
|
import {
|
|
52
51
|
broadcastUpgradeEvent,
|
|
53
52
|
attemptFailedStateRestore,
|
|
@@ -77,7 +76,7 @@ import {
|
|
|
77
76
|
startLocalDaemon,
|
|
78
77
|
stopLocalProcesses,
|
|
79
78
|
} from "../lib/local.js";
|
|
80
|
-
import { restoreTunnelEdgeAndAutoTunnel } from "../lib/tunnel
|
|
79
|
+
import { restoreTunnelEdgeAndAutoTunnel } from "../lib/tunnel.js";
|
|
81
80
|
import {
|
|
82
81
|
leaseGuardianToken,
|
|
83
82
|
resetGuardianBootstrap,
|
|
@@ -119,12 +118,12 @@ function parseArgs(): UpgradeArgs {
|
|
|
119
118
|
console.log("");
|
|
120
119
|
console.log("Arguments:");
|
|
121
120
|
console.log(
|
|
122
|
-
" <name> Name of the assistant to upgrade
|
|
121
|
+
" <name> Name of the assistant to upgrade, defaults to active or only assistant",
|
|
123
122
|
);
|
|
124
123
|
console.log("");
|
|
125
124
|
console.log("Options:");
|
|
126
125
|
console.log(
|
|
127
|
-
" --version <version> Target version to upgrade to
|
|
126
|
+
" --version <version> Target version to upgrade to, defaults to the CLI version",
|
|
128
127
|
);
|
|
129
128
|
console.log(
|
|
130
129
|
" --latest Upgrade to the latest stable release, updating the CLI first if needed",
|
|
@@ -317,7 +316,7 @@ async function upgradeDocker(
|
|
|
317
316
|
);
|
|
318
317
|
return;
|
|
319
318
|
}
|
|
320
|
-
console.log(`🔁 Reinstalling ${versionTag}
|
|
319
|
+
console.log(`🔁 Reinstalling ${versionTag} with --force...\n`);
|
|
321
320
|
}
|
|
322
321
|
|
|
323
322
|
console.log("🔍 Resolving image references...");
|
|
@@ -407,7 +406,7 @@ async function upgradeDocker(
|
|
|
407
406
|
try {
|
|
408
407
|
for (const [service, image] of pullImages) {
|
|
409
408
|
console.log(` Pulling ${service}: ${image}`);
|
|
410
|
-
await
|
|
409
|
+
await pullImage(image);
|
|
411
410
|
}
|
|
412
411
|
} catch (pullErr) {
|
|
413
412
|
const detail = pullErr instanceof Error ? pullErr.message : String(pullErr);
|
|
@@ -454,7 +453,7 @@ async function upgradeDocker(
|
|
|
454
453
|
// Clean up old pre-upgrade backups, keep last 3
|
|
455
454
|
pruneOldBackups(entry.assistantId, 3);
|
|
456
455
|
} else {
|
|
457
|
-
console.warn("⚠️ Pre-upgrade backup failed
|
|
456
|
+
console.warn("⚠️ Pre-upgrade backup failed, continuing with upgrade\n");
|
|
458
457
|
}
|
|
459
458
|
|
|
460
459
|
// Persist the backup path so `cadet rollback` can restore the exact backup
|
|
@@ -502,17 +501,12 @@ async function upgradeDocker(
|
|
|
502
501
|
const ready = await waitForReady(entry.runtimeUrl);
|
|
503
502
|
if (ready) {
|
|
504
503
|
// Update lockfile with new service group topology
|
|
505
|
-
const newDigests = await captureImageRefs(res);
|
|
506
504
|
const updatedEntry: AssistantEntry = {
|
|
507
505
|
...entry,
|
|
508
506
|
containerInfo: {
|
|
509
507
|
assistantImage: imageTags.assistant,
|
|
510
508
|
gatewayImage: imageTags.gateway,
|
|
511
509
|
cesImage: imageTags["credential-executor"],
|
|
512
|
-
assistantDigest: newDigests?.assistant,
|
|
513
|
-
gatewayDigest: newDigests?.gateway,
|
|
514
|
-
cesDigest: newDigests?.["credential-executor"],
|
|
515
|
-
networkName: res.network,
|
|
516
510
|
assistantPort,
|
|
517
511
|
},
|
|
518
512
|
previousContainerInfo: entry.containerInfo,
|
|
@@ -668,13 +662,9 @@ async function upgradeDocker(
|
|
|
668
662
|
);
|
|
669
663
|
}
|
|
670
664
|
|
|
671
|
-
// Capture fresh digests from the now-running rolled-back containers.
|
|
672
|
-
const rollbackDigests = await captureImageRefs(res);
|
|
673
|
-
|
|
674
665
|
// Restore previous container info in lockfile after rollback.
|
|
675
|
-
// The
|
|
676
|
-
//
|
|
677
|
-
// the running containers (or fall back to previousImageRefs).
|
|
666
|
+
// The recorded image refs are the rollback record now (digests are
|
|
667
|
+
// gone from the contract).
|
|
678
668
|
const rolledBackEntry: AssistantEntry = {
|
|
679
669
|
...entry,
|
|
680
670
|
containerInfo: {
|
|
@@ -686,14 +676,6 @@ async function upgradeDocker(
|
|
|
686
676
|
cesImage:
|
|
687
677
|
entry.containerInfo?.cesImage ??
|
|
688
678
|
previousImageRefs["credential-executor"],
|
|
689
|
-
assistantDigest:
|
|
690
|
-
rollbackDigests?.assistant ?? previousImageRefs.assistant,
|
|
691
|
-
gatewayDigest:
|
|
692
|
-
rollbackDigests?.gateway ?? previousImageRefs.gateway,
|
|
693
|
-
cesDigest:
|
|
694
|
-
rollbackDigests?.["credential-executor"] ??
|
|
695
|
-
previousImageRefs["credential-executor"],
|
|
696
|
-
networkName: res.network,
|
|
697
679
|
assistantPort,
|
|
698
680
|
},
|
|
699
681
|
previousContainerInfo: undefined,
|
|
@@ -909,7 +891,7 @@ async function upgradeLocal(
|
|
|
909
891
|
);
|
|
910
892
|
return;
|
|
911
893
|
}
|
|
912
|
-
console.log(`🔁 Reinstalling ${targetVersion}
|
|
894
|
+
console.log(`🔁 Reinstalling ${targetVersion} with --force...\n`);
|
|
913
895
|
}
|
|
914
896
|
|
|
915
897
|
console.log(
|
|
@@ -951,7 +933,7 @@ async function upgradeLocal(
|
|
|
951
933
|
console.log(` Backup saved: ${backupPath}\n`);
|
|
952
934
|
pruneOldBackups(entry.assistantId, 3);
|
|
953
935
|
} else {
|
|
954
|
-
console.warn("⚠️ Pre-upgrade backup failed
|
|
936
|
+
console.warn("⚠️ Pre-upgrade backup failed, continuing with upgrade\n");
|
|
955
937
|
}
|
|
956
938
|
|
|
957
939
|
let bootstrapSecret = entry.guardianBootstrapSecret;
|
|
@@ -1035,17 +1017,11 @@ async function upgradeLocal(
|
|
|
1035
1017
|
console.log("✅ Local assistant processes started\n");
|
|
1036
1018
|
|
|
1037
1019
|
const workspaceDir = join(entry.resources.instanceDir, "workspace");
|
|
1038
|
-
|
|
1020
|
+
await restoreTunnelEdgeAndAutoTunnel(
|
|
1039
1021
|
entry.assistantId,
|
|
1040
1022
|
entry.resources.gatewayPort,
|
|
1041
1023
|
workspaceDir,
|
|
1042
1024
|
);
|
|
1043
|
-
if (ngrokChild?.pid) {
|
|
1044
|
-
writeFileSync(
|
|
1045
|
-
join(entry.resources.instanceDir, "ngrok.pid"),
|
|
1046
|
-
String(ngrokChild.pid),
|
|
1047
|
-
);
|
|
1048
|
-
}
|
|
1049
1025
|
|
|
1050
1026
|
console.log("Waiting for assistant to become ready...");
|
|
1051
1027
|
const ready = await waitForReady(adminUrl);
|
|
@@ -1113,7 +1089,7 @@ function sleep(ms: number): Promise<void> {
|
|
|
1113
1089
|
}
|
|
1114
1090
|
|
|
1115
1091
|
/**
|
|
1116
|
-
* Poll the
|
|
1092
|
+
* Poll the cloud until the upgrade completes, then confirm health.
|
|
1117
1093
|
* Mirrors the web UI's Software Updates flow (poll the DB-backed
|
|
1118
1094
|
* `current_release_version` rather than the gateway, which bounces while
|
|
1119
1095
|
* the service group restarts).
|
|
@@ -1163,7 +1139,7 @@ async function waitForPlatformUpgrade(
|
|
|
1163
1139
|
const healthDeadline = Date.now() + PLATFORM_HEALTH_CONFIRM_TIMEOUT_MS;
|
|
1164
1140
|
while (Date.now() < healthDeadline) {
|
|
1165
1141
|
const health = await checkManagedHealth(
|
|
1166
|
-
entry.runtimeUrl ||
|
|
1142
|
+
entry.runtimeUrl || getCloudUrl(),
|
|
1167
1143
|
entry.assistantId,
|
|
1168
1144
|
);
|
|
1169
1145
|
if (health.status === "healthy") {
|
|
@@ -1187,7 +1163,7 @@ async function waitForPlatformUpgrade(
|
|
|
1187
1163
|
|
|
1188
1164
|
const timeoutMin = Math.round(timeoutMs / 60_000);
|
|
1189
1165
|
console.warn(
|
|
1190
|
-
`⚠️ Upgrade request was accepted but completion was not confirmed within ${timeoutMin} minutes. The
|
|
1166
|
+
`⚠️ Upgrade request was accepted but completion was not confirmed within ${timeoutMin} minutes. The cloud may still be working — check \`cadet ps\` or the web settings page.`,
|
|
1191
1167
|
);
|
|
1192
1168
|
}
|
|
1193
1169
|
|
|
@@ -1197,10 +1173,10 @@ async function upgradePlatform(
|
|
|
1197
1173
|
wait: boolean,
|
|
1198
1174
|
): Promise<void> {
|
|
1199
1175
|
console.log(
|
|
1200
|
-
`🔄 Upgrading
|
|
1176
|
+
`🔄 Upgrading cloud-hosted assistant '${entry.assistantId}'...\n`,
|
|
1201
1177
|
);
|
|
1202
1178
|
|
|
1203
|
-
const token =
|
|
1179
|
+
const token = readCloudToken();
|
|
1204
1180
|
if (!token) {
|
|
1205
1181
|
const msg =
|
|
1206
1182
|
"Error: Not logged in. Run `cadet login --token <token>` first.";
|
|
@@ -1214,7 +1190,7 @@ async function upgradePlatform(
|
|
|
1214
1190
|
// downgrades before POSTing, and bail if an upgrade is already running.
|
|
1215
1191
|
const [assistantDetail, health] = await Promise.all([
|
|
1216
1192
|
fetchAssistantDetail(token, entry.assistantId, entry.runtimeUrl),
|
|
1217
|
-
checkManagedHealth(entry.runtimeUrl ||
|
|
1193
|
+
checkManagedHealth(entry.runtimeUrl || getCloudUrl(), entry.assistantId),
|
|
1218
1194
|
]);
|
|
1219
1195
|
// Health probe first, DB-backed field as the sleeping-assistant fallback.
|
|
1220
1196
|
const currentVersion =
|
|
@@ -1225,7 +1201,7 @@ async function upgradePlatform(
|
|
|
1225
1201
|
// platform may differ from the active lockfile default.
|
|
1226
1202
|
const releases = await fetchReleases({
|
|
1227
1203
|
channel: releaseChannel,
|
|
1228
|
-
|
|
1204
|
+
cloudUrl: entry.runtimeUrl,
|
|
1229
1205
|
});
|
|
1230
1206
|
|
|
1231
1207
|
const resolution = resolveUpgradeTarget({
|
|
@@ -1276,7 +1252,7 @@ async function upgradePlatform(
|
|
|
1276
1252
|
|
|
1277
1253
|
const headers = await authHeaders(token, entry.runtimeUrl);
|
|
1278
1254
|
|
|
1279
|
-
const url = `${entry.runtimeUrl ||
|
|
1255
|
+
const url = `${entry.runtimeUrl || getCloudUrl()}/v1/assistants/${encodeURIComponent(entry.assistantId)}/upgrade/`;
|
|
1280
1256
|
const body: { version?: string } = {};
|
|
1281
1257
|
if (version) {
|
|
1282
1258
|
body.version = version;
|
|
@@ -1345,11 +1321,11 @@ async function upgradePlatform(
|
|
|
1345
1321
|
}
|
|
1346
1322
|
|
|
1347
1323
|
// NOTE: We intentionally do NOT broadcast a "complete" event here.
|
|
1348
|
-
// The
|
|
1324
|
+
// The cloud API returning 200 only means "upgrade request accepted" —
|
|
1349
1325
|
// the service group has not yet restarted with the new version. The
|
|
1350
1326
|
// completion signal will come from the client's health-check
|
|
1351
1327
|
// version-change detection (DaemonConnection.swift) once the new
|
|
1352
|
-
// version actually appears after the
|
|
1328
|
+
// version actually appears after the cloud restarts the service group.
|
|
1353
1329
|
|
|
1354
1330
|
console.log(`✅ ${result.detail}`);
|
|
1355
1331
|
if (result.version) {
|
|
@@ -1483,7 +1459,7 @@ async function resolveLatestStableTag(): Promise<string> {
|
|
|
1483
1459
|
);
|
|
1484
1460
|
emitCliError(
|
|
1485
1461
|
"UNKNOWN",
|
|
1486
|
-
"Could not determine the latest stable release from the
|
|
1462
|
+
"Could not determine the latest stable release from the cloud API",
|
|
1487
1463
|
);
|
|
1488
1464
|
process.exit(1);
|
|
1489
1465
|
}
|
|
@@ -1560,7 +1536,7 @@ export const upgrade = Object.assign(
|
|
|
1560
1536
|
return;
|
|
1561
1537
|
}
|
|
1562
1538
|
|
|
1563
|
-
// When --latest is passed, resolve the target from the
|
|
1539
|
+
// When --latest is passed, resolve the target from the cloud API and
|
|
1564
1540
|
// self-update the CLI if it's behind. The resolved version is then used
|
|
1565
1541
|
// as the explicit target for the rest of the upgrade flow.
|
|
1566
1542
|
let effectiveVersion = version;
|
|
@@ -1586,7 +1562,7 @@ export const upgrade = Object.assign(
|
|
|
1586
1562
|
return;
|
|
1587
1563
|
}
|
|
1588
1564
|
|
|
1589
|
-
if (
|
|
1565
|
+
if (isCloudHome(cloud)) {
|
|
1590
1566
|
await upgradePlatform(entry, effectiveVersion, !noWait);
|
|
1591
1567
|
return;
|
|
1592
1568
|
}
|
package/src/commands/use.ts
CHANGED
|
@@ -38,7 +38,7 @@ export const use = Object.assign(
|
|
|
38
38
|
`Active assistant: ${formatAssistantReference(result.entry)}`,
|
|
39
39
|
);
|
|
40
40
|
} else {
|
|
41
|
-
console.log(`Active assistant: ${active}
|
|
41
|
+
console.log(`Active assistant: ${active} — not found in lockfile`);
|
|
42
42
|
}
|
|
43
43
|
} else {
|
|
44
44
|
console.log("No active assistant set.");
|
package/src/commands/wake.ts
CHANGED
|
@@ -2,16 +2,23 @@ import {
|
|
|
2
2
|
SigningKeyContinuityError,
|
|
3
3
|
resolveExistingLocalSigningKey,
|
|
4
4
|
} from "@mycadet/local-mode";
|
|
5
|
-
import { writeFileSync } from "fs";
|
|
6
5
|
import { join } from "path";
|
|
7
6
|
|
|
8
7
|
import {
|
|
9
8
|
getDaemonPidPath,
|
|
9
|
+
loadAllAssistants,
|
|
10
10
|
resolveTargetAssistant,
|
|
11
11
|
saveAssistantEntry,
|
|
12
12
|
} from "../lib/assistant-config.js";
|
|
13
|
-
import {
|
|
13
|
+
import type { AssistantEntry } from "../lib/assistant-config.js";
|
|
14
14
|
import {
|
|
15
|
+
dockerResourceNames,
|
|
16
|
+
isNoSuchContainerError,
|
|
17
|
+
recreateDockerContainers,
|
|
18
|
+
wakeContainers,
|
|
19
|
+
} from "../lib/docker.js";
|
|
20
|
+
import {
|
|
21
|
+
GuardianLeasePermanentError,
|
|
15
22
|
leaseGuardianToken,
|
|
16
23
|
resetGuardianBootstrap,
|
|
17
24
|
seedGuardianTokenFromSiblingEnv,
|
|
@@ -39,22 +46,25 @@ import {
|
|
|
39
46
|
DOCKER_GATEWAY_READY_TIMEOUT_MS,
|
|
40
47
|
restoreContainerTunnelEdge,
|
|
41
48
|
restoreTunnelEdgeAndAutoTunnel,
|
|
42
|
-
} from "../lib/tunnel
|
|
49
|
+
} from "../lib/tunnel.js";
|
|
43
50
|
|
|
44
51
|
export const wake = Object.assign(
|
|
45
52
|
async function wake(): Promise<void> {
|
|
46
53
|
const args = process.argv.slice(3);
|
|
47
54
|
if (args.includes("--help") || args.includes("-h")) {
|
|
48
|
-
console.log("Usage: cadet wake [<name>] [options]");
|
|
55
|
+
console.log("Usage: cadet wake [<name> | --all] [options]");
|
|
49
56
|
console.log("");
|
|
50
57
|
console.log("Start the assistant and gateway processes.");
|
|
51
58
|
console.log("");
|
|
52
59
|
console.log("Arguments:");
|
|
53
60
|
console.log(
|
|
54
|
-
" <name> Name of the assistant to start
|
|
61
|
+
" <name> Name of the assistant to start, defaults to active or only local",
|
|
55
62
|
);
|
|
56
63
|
console.log("");
|
|
57
64
|
console.log("Options:");
|
|
65
|
+
console.log(
|
|
66
|
+
" --all Start every assistant across all remotes instead of one",
|
|
67
|
+
);
|
|
58
68
|
console.log(
|
|
59
69
|
" --watch Run assistant and gateway in watch mode (hot reload on source changes)",
|
|
60
70
|
);
|
|
@@ -80,7 +90,122 @@ export const wake = Object.assign(
|
|
|
80
90
|
// recovered deliberately via `cadet wake <id> --repair-guardian`.
|
|
81
91
|
const repairGuardian = args.includes("--repair-guardian");
|
|
82
92
|
const nameArg = args.find((a) => !a.startsWith("-"));
|
|
93
|
+
const all = args.includes("--all");
|
|
94
|
+
|
|
95
|
+
if (all && nameArg) {
|
|
96
|
+
console.error("Error: --all cannot be combined with an assistant name.");
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
if (all && (foreground || repairGuardian)) {
|
|
100
|
+
console.error(
|
|
101
|
+
"Error: --all cannot be combined with --foreground or --repair-guardian.",
|
|
102
|
+
);
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (all) {
|
|
107
|
+
await wakeAll({ watch });
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
83
111
|
const entry = resolveTargetAssistant(nameArg);
|
|
112
|
+
try {
|
|
113
|
+
await wakeAssistant(entry, { watch, foreground, repairGuardian });
|
|
114
|
+
} catch (err) {
|
|
115
|
+
if (err instanceof WakeExit) {
|
|
116
|
+
console.error(`Error: ${err.message}`);
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
throw err;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Foreground blocking is a single-assistant concern (--all refuses the
|
|
123
|
+
// flag), and the docker path already warned that foreground is ignored.
|
|
124
|
+
if (foreground && entry.cloud !== "docker") {
|
|
125
|
+
console.log("Running in the foreground, Ctrl+C to stop...\n");
|
|
126
|
+
// Block forever — the daemon is running with inherited stdio so its
|
|
127
|
+
// output streams to this terminal. When the user hits Ctrl+C, SIGINT
|
|
128
|
+
// propagates to the daemon child and both exit.
|
|
129
|
+
await new Promise<void>((resolve) => {
|
|
130
|
+
process.on("SIGINT", () => {
|
|
131
|
+
console.log("\nShutting down...");
|
|
132
|
+
resolve();
|
|
133
|
+
});
|
|
134
|
+
process.on("SIGTERM", () => resolve());
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
{ description: "Start the assistant and gateway" },
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Start every assistant this CLI's environment knows about — `--all`. The
|
|
143
|
+
* CLI only ever reaches one environment, so this is the current env's
|
|
144
|
+
* lockfile. Same per-assistant wake as the single path, but one assistant's
|
|
145
|
+
* failure never blocks the others; failures only decide the exit code.
|
|
146
|
+
*/
|
|
147
|
+
async function wakeAll(opts: { watch: boolean }): Promise<void> {
|
|
148
|
+
const entries = loadAllAssistants();
|
|
149
|
+
if (entries.length === 0) {
|
|
150
|
+
console.error("Error: no assistants found. Run 'cadet hatch' first.");
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
let failures = 0;
|
|
155
|
+
const seen = new Set<string>();
|
|
156
|
+
for (const entry of entries) {
|
|
157
|
+
if (seen.has(entry.assistantId)) continue;
|
|
158
|
+
seen.add(entry.assistantId);
|
|
159
|
+
if (entry.cloud === "apple-container" || entry.cloud === "paired") {
|
|
160
|
+
console.log(
|
|
161
|
+
`Skipping '${entry.assistantId}' (${entry.cloud}) — its lifecycle is managed elsewhere.`,
|
|
162
|
+
);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
try {
|
|
166
|
+
await wakeAssistant(entry, {
|
|
167
|
+
watch: opts.watch,
|
|
168
|
+
foreground: false,
|
|
169
|
+
repairGuardian: false,
|
|
170
|
+
});
|
|
171
|
+
} catch (err) {
|
|
172
|
+
failures++;
|
|
173
|
+
console.error(
|
|
174
|
+
`Error: failed to wake '${entry.assistantId}': ${
|
|
175
|
+
err instanceof Error ? err.message : String(err)
|
|
176
|
+
}`,
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (failures > 0) {
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
type WakeAssistantOptions = {
|
|
186
|
+
watch: boolean;
|
|
187
|
+
foreground: boolean;
|
|
188
|
+
repairGuardian: boolean;
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Marks a deliberate per-assistant refusal (an unmanageable runtime, missing
|
|
193
|
+
* resources, a daemon that died during startup). The single-assistant path
|
|
194
|
+
* renders it as the same "Error: …" + exit 1 the command always printed;
|
|
195
|
+
* `--all` treats it as one entry's failure and keeps waking the rest.
|
|
196
|
+
*/
|
|
197
|
+
class WakeExit extends Error {}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Start one assistant and its gateway. Throws WakeExit on deliberate
|
|
201
|
+
* refusals so the caller decides between exiting (single path) and
|
|
202
|
+
* continuing to the next entry (`--all`).
|
|
203
|
+
*/
|
|
204
|
+
async function wakeAssistant(
|
|
205
|
+
entry: AssistantEntry,
|
|
206
|
+
opts: WakeAssistantOptions,
|
|
207
|
+
): Promise<void> {
|
|
208
|
+
const { watch, foreground, repairGuardian } = opts;
|
|
84
209
|
|
|
85
210
|
if (entry.cloud === "docker") {
|
|
86
211
|
if (watch || foreground) {
|
|
@@ -88,11 +213,23 @@ export const wake = Object.assign(
|
|
|
88
213
|
.filter(Boolean)
|
|
89
214
|
.join(" and ");
|
|
90
215
|
console.warn(
|
|
91
|
-
`Warning: ${ignored} ignored for Docker instances
|
|
216
|
+
`Warning: ${ignored} ignored for Docker instances.`,
|
|
92
217
|
);
|
|
93
218
|
}
|
|
94
219
|
const res = dockerResourceNames(entry.assistantId);
|
|
95
|
-
|
|
220
|
+
try {
|
|
221
|
+
await wakeContainers(res);
|
|
222
|
+
} catch (err) {
|
|
223
|
+
// A migrated install has data in the volumes but containers that were
|
|
224
|
+
// never created (v0.0.10 run feedback: "No such container"). Recreate
|
|
225
|
+
// the service group from the entry's recorded images; anything else
|
|
226
|
+
// is a real wake failure.
|
|
227
|
+
if (!isNoSuchContainerError(err)) throw err;
|
|
228
|
+
console.log(
|
|
229
|
+
`Containers for '${entry.assistantId}' don't exist yet — recreating from the recorded images...`,
|
|
230
|
+
);
|
|
231
|
+
await recreateDockerContainers(entry);
|
|
232
|
+
}
|
|
96
233
|
console.log("Docker containers started.");
|
|
97
234
|
await restoreContainerTunnelEdge(entry, DOCKER_GATEWAY_READY_TIMEOUT_MS);
|
|
98
235
|
console.log("Wake complete.");
|
|
@@ -100,31 +237,27 @@ export const wake = Object.assign(
|
|
|
100
237
|
}
|
|
101
238
|
|
|
102
239
|
if (entry.cloud === "apple-container") {
|
|
103
|
-
|
|
104
|
-
`
|
|
240
|
+
throw new WakeExit(
|
|
241
|
+
`'${entry.assistantId}' uses the Apple Containers runtime. Its lifecycle is managed by the macOS app — use the app to start it.`,
|
|
105
242
|
);
|
|
106
|
-
process.exit(1);
|
|
107
243
|
}
|
|
108
244
|
|
|
109
245
|
if (entry.cloud === "paired") {
|
|
110
|
-
|
|
111
|
-
`
|
|
246
|
+
throw new WakeExit(
|
|
247
|
+
`'${entry.assistantId}' is a remote assistant paired from another machine — its lifecycle is managed on its host machine, not here. Use \`cadet client ${entry.assistantId}\` to chat with it.`,
|
|
112
248
|
);
|
|
113
|
-
process.exit(1);
|
|
114
249
|
}
|
|
115
250
|
|
|
116
251
|
if (entry.cloud && entry.cloud !== "local") {
|
|
117
|
-
|
|
118
|
-
`
|
|
252
|
+
throw new WakeExit(
|
|
253
|
+
`'cadet wake' only works with local and docker assistants. '${entry.assistantId}' is a ${entry.cloud} instance.`,
|
|
119
254
|
);
|
|
120
|
-
process.exit(1);
|
|
121
255
|
}
|
|
122
256
|
|
|
123
257
|
if (!entry.resources) {
|
|
124
|
-
|
|
125
|
-
`
|
|
258
|
+
throw new WakeExit(
|
|
259
|
+
`Local assistant '${entry.assistantId}' is missing resource configuration. Re-hatch to fix.`,
|
|
126
260
|
);
|
|
127
|
-
process.exit(1);
|
|
128
261
|
}
|
|
129
262
|
const resources = entry.resources;
|
|
130
263
|
let signingKey: string;
|
|
@@ -231,7 +364,7 @@ export const wake = Object.assign(
|
|
|
231
364
|
daemonMigrationsFailed = daemonState.status === "migration_failed";
|
|
232
365
|
if (watch) {
|
|
233
366
|
console.log(
|
|
234
|
-
`Assistant running (pid ${daemonState.pid}) — watch mode not available
|
|
367
|
+
`Assistant running (pid ${daemonState.pid}) — watch mode not available, no source files. Keeping existing process.`,
|
|
235
368
|
);
|
|
236
369
|
} else if (daemonMigrationsFailed) {
|
|
237
370
|
console.log(
|
|
@@ -275,10 +408,9 @@ export const wake = Object.assign(
|
|
|
275
408
|
// first and fail the command instead of reporting a successful wake for
|
|
276
409
|
// an assistant that is not running.
|
|
277
410
|
if (!isProcessAlive(pidFile).alive) {
|
|
278
|
-
|
|
279
|
-
`
|
|
411
|
+
throw new WakeExit(
|
|
412
|
+
`the assistant exited during startup and is not running. Its runtime HTTP port ${resources.daemonPort} may be held by another process (check \`lsof -i :${resources.daemonPort}\`); the daemon log records the startup error it reported.`,
|
|
280
413
|
);
|
|
281
|
-
process.exit(1);
|
|
282
414
|
}
|
|
283
415
|
// startLocalDaemon's post-spawn wait is bounded (60s), and a longer
|
|
284
416
|
// migration outlives it. Classify the fresh spawn the same way the
|
|
@@ -356,7 +488,7 @@ export const wake = Object.assign(
|
|
|
356
488
|
} else {
|
|
357
489
|
if (watch) {
|
|
358
490
|
console.log(
|
|
359
|
-
`Gateway running (pid ${gatewayState.pid}) — watch mode not available
|
|
491
|
+
`Gateway running (pid ${gatewayState.pid}) — watch mode not available, no source files. Keeping existing process.`,
|
|
360
492
|
);
|
|
361
493
|
} else {
|
|
362
494
|
console.log(`Gateway already running (pid ${gatewayState.pid}).`);
|
|
@@ -430,11 +562,14 @@ export const wake = Object.assign(
|
|
|
430
562
|
console.log(" Re-provisioned guardian token.");
|
|
431
563
|
break;
|
|
432
564
|
} catch (err) {
|
|
433
|
-
if (
|
|
565
|
+
if (
|
|
566
|
+
attempt < maxAttempts &&
|
|
567
|
+
!(err instanceof GuardianLeasePermanentError)
|
|
568
|
+
) {
|
|
434
569
|
await new Promise((r) => setTimeout(r, 2000 * 2 ** (attempt - 1)));
|
|
435
570
|
} else {
|
|
436
571
|
console.warn(
|
|
437
|
-
` Guardian token re-provision failed after ${maxAttempts} attempts: ${err}`,
|
|
572
|
+
` Guardian token re-provision failed${err instanceof GuardianLeasePermanentError ?", not retrying — a malformed 200 will not fix itself" : ` after ${maxAttempts} attempts`}: ${err}`,
|
|
438
573
|
);
|
|
439
574
|
// The user explicitly confirmed this destructive repair — a
|
|
440
575
|
// success exit here would make callers (the web recovery flow)
|
|
@@ -446,18 +581,15 @@ export const wake = Object.assign(
|
|
|
446
581
|
}
|
|
447
582
|
}
|
|
448
583
|
|
|
449
|
-
// Restore the nginx edge and point the
|
|
450
|
-
//
|
|
584
|
+
// Restore the nginx edge and point the auto-tunnel at it. The spawned agent
|
|
585
|
+
// records itself in the root lockfile under `tunnel`. Non-fatal: a down
|
|
586
|
+
// edge is a degraded remote-web/webhook path, not a broken assistant.
|
|
451
587
|
const workspaceDir = join(resources.instanceDir, "workspace");
|
|
452
|
-
|
|
588
|
+
await restoreTunnelEdgeAndAutoTunnel(
|
|
453
589
|
entry.assistantId,
|
|
454
590
|
resources.gatewayPort,
|
|
455
591
|
workspaceDir,
|
|
456
592
|
);
|
|
457
|
-
if (ngrokChild?.pid) {
|
|
458
|
-
const ngrokPidFile = join(resources.instanceDir, "ngrok.pid");
|
|
459
|
-
writeFileSync(ngrokPidFile, String(ngrokChild.pid));
|
|
460
|
-
}
|
|
461
593
|
|
|
462
594
|
if (daemonMigrationsFailed) {
|
|
463
595
|
console.log(
|
|
@@ -469,21 +601,5 @@ export const wake = Object.assign(
|
|
|
469
601
|
);
|
|
470
602
|
}
|
|
471
603
|
console.log("Wake complete.");
|
|
472
|
-
|
|
473
|
-
if (foreground) {
|
|
474
|
-
console.log("Running in foreground (Ctrl+C to stop)...\n");
|
|
475
|
-
// Block forever — the daemon is running with inherited stdio so its
|
|
476
|
-
// output streams to this terminal. When the user hits Ctrl+C, SIGINT
|
|
477
|
-
// propagates to the daemon child and both exit.
|
|
478
|
-
await new Promise<void>((resolve) => {
|
|
479
|
-
process.on("SIGINT", () => {
|
|
480
|
-
console.log("\nShutting down...");
|
|
481
|
-
resolve();
|
|
482
|
-
});
|
|
483
|
-
process.on("SIGTERM", () => resolve());
|
|
484
|
-
});
|
|
485
|
-
}
|
|
486
|
-
},
|
|
487
|
-
{ description: "Start the assistant and gateway" },
|
|
488
|
-
);
|
|
604
|
+
}
|
|
489
605
|
|