@mycadet/cli 0.12.11 → 0.13.0-dev.202610171617.9ea363e
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 +13 -0
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +2 -2
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/channels.ts +0 -1
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/ingress.ts +2 -2
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +2 -2
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/channels.ts +0 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/ingress.ts +2 -2
- package/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +2 -2
- package/node_modules/@mycadet/service-contracts/src/channels.ts +0 -1
- package/node_modules/@mycadet/service-contracts/src/ingress.ts +2 -2
- package/package.json +8 -3
- package/src/__tests__/assistant-client-cloud-auth.test.ts +2 -2
- package/src/__tests__/backup-ops-matching.test.ts +15 -13
- package/src/__tests__/backup-ops.test.ts +105 -0
- package/src/__tests__/backup.test.ts +114 -673
- package/src/__tests__/cloud-releases.test.ts +82 -0
- package/src/__tests__/commands-mock.ts +101 -0
- package/src/__tests__/debug.test.ts +17 -91
- package/src/__tests__/devices.test.ts +2 -2
- package/src/__tests__/gateway-token.test.ts +267 -0
- package/src/__tests__/host-teleport-docker.test.ts +371 -0
- package/src/__tests__/host-teleport.test.ts +405 -0
- package/src/__tests__/nginx-ingress.test.ts +536 -23
- package/src/__tests__/orphan-detection.test.ts +1 -1
- package/src/__tests__/paired-lifecycle.test.ts +2 -2
- package/src/__tests__/ps-cloud-status.test.ts +6 -6
- package/src/__tests__/root-backup-docker.test.ts +294 -0
- package/src/__tests__/root-backup.test.ts +527 -0
- package/src/__tests__/root-restore-command.test.ts +147 -0
- package/src/__tests__/root-signing-key.test.ts +50 -0
- package/src/__tests__/setup.test.ts +3 -3
- package/src/__tests__/shell-mode.test.ts +83 -0
- package/src/__tests__/sleep.test.ts +11 -0
- package/src/__tests__/teleport.test.ts +68 -2996
- package/src/__tests__/tunnel.test.ts +10 -2
- package/src/__tests__/upgrade-local.test.ts +21 -11
- package/src/__tests__/upgrade-replay-env.test.ts +2 -2
- package/src/__tests__/upgrade-root-recovery.test.ts +185 -0
- package/src/__tests__/wake.test.ts +4 -1
- package/src/__tests__/windows-lifecycle.test.ts +25 -0
- package/src/commands/backup.ts +44 -399
- package/src/commands/client.ts +3 -3
- package/src/commands/completions.ts +0 -2
- package/src/commands/debug.ts +75 -131
- package/src/commands/devices.ts +51 -17
- package/src/commands/gateway/token.ts +77 -23
- package/src/commands/pair.ts +2 -2
- package/src/commands/restore.ts +77 -444
- package/src/commands/retire.ts +2 -3
- package/src/commands/sleep.ts +15 -2
- package/src/commands/teleport.ts +47 -1559
- package/src/commands/tunnel.ts +48 -7
- package/src/commands/upgrade.ts +78 -156
- package/src/commands/wake.ts +1 -1
- package/src/index.ts +0 -4
- package/src/lib/__tests__/http-client-version.test.ts +29 -0
- package/src/lib/__tests__/runtime-url.test.ts +3 -75
- package/src/lib/__tests__/web-dist.test.ts +27 -1
- package/src/lib/assistant-client.ts +1 -1
- package/src/lib/assistant-config.ts +23 -18
- package/src/lib/backup-ops.ts +37 -251
- package/src/lib/cloud-client.ts +10 -13
- package/src/lib/cloud-login.ts +2 -1
- package/src/lib/cloud-releases.ts +54 -10
- package/src/lib/cloud-values.ts +1 -1
- package/src/lib/confirm-action.ts +2 -2
- package/src/lib/docker.ts +59 -22
- package/src/lib/github-repository.ts +2 -0
- package/src/lib/guardian-token.ts +1 -23
- package/src/lib/hatch-local.ts +17 -1
- package/src/lib/host-teleport.ts +506 -0
- package/src/lib/http-client.ts +25 -0
- package/src/lib/ingress-config.ts +31 -0
- package/src/lib/live-voice/tunnel.test.ts +1 -1
- package/src/lib/live-voice/tunnel.ts +1 -1
- package/src/lib/local.ts +4 -2
- package/src/lib/nginx-ingress.ts +454 -79
- package/src/lib/process.ts +13 -0
- package/src/lib/root-backup.ts +917 -0
- package/src/lib/root-signing-key.ts +31 -0
- package/src/lib/runtime-url.ts +0 -29
- package/src/lib/teleport-backup.ts +3 -16
- package/src/lib/tunnel.ts +1 -0
- package/src/lib/upgrade-lifecycle.ts +63 -141
- package/src/lib/web-dist.ts +79 -1
- package/src/__tests__/bundle-staging.test.ts +0 -268
- package/src/__tests__/unpair.test.ts +0 -160
- package/src/commands/rollback.ts +0 -479
- package/src/commands/unpair.ts +0 -122
- package/src/lib/__tests__/job-polling.test.ts +0 -278
- package/src/lib/__tests__/local-runtime-client.test.ts +0 -666
- package/src/lib/bundle-staging.ts +0 -220
- package/src/lib/job-polling.ts +0 -195
- package/src/lib/local-runtime-client.ts +0 -374
package/src/commands/rollback.ts
DELETED
|
@@ -1,479 +0,0 @@
|
|
|
1
|
-
import { isCloudHome } from "../lib/cloud-values.js";
|
|
2
|
-
import {
|
|
3
|
-
findAssistantByName,
|
|
4
|
-
getActiveAssistant,
|
|
5
|
-
loadAllAssistants,
|
|
6
|
-
saveAssistantEntry,
|
|
7
|
-
type AssistantEntry,
|
|
8
|
-
} from "../lib/assistant-config";
|
|
9
|
-
import {
|
|
10
|
-
GATEWAY_INTERNAL_PORT,
|
|
11
|
-
ASSISTANT_INTERNAL_PORT,
|
|
12
|
-
dockerResourceNames,
|
|
13
|
-
startContainers,
|
|
14
|
-
stopContainers,
|
|
15
|
-
} from "../lib/docker";
|
|
16
|
-
import type { ServiceName } from "../lib/docker";
|
|
17
|
-
import { emitCliError, categorizeUpgradeError } from "../lib/cli-error.js";
|
|
18
|
-
import {
|
|
19
|
-
readCloudToken,
|
|
20
|
-
rollbackCloudAssistant,
|
|
21
|
-
} from "../lib/cloud-client.js";
|
|
22
|
-
import {
|
|
23
|
-
broadcastUpgradeEvent,
|
|
24
|
-
buildCompleteEvent,
|
|
25
|
-
buildProgressEvent,
|
|
26
|
-
buildStartingEvent,
|
|
27
|
-
buildUpgradeCommitMessage,
|
|
28
|
-
captureReplayState,
|
|
29
|
-
commitWorkspaceViaGateway,
|
|
30
|
-
fetchCurrentVersion,
|
|
31
|
-
fetchPreviousVersion,
|
|
32
|
-
performDockerRollback,
|
|
33
|
-
rollbackMigrations,
|
|
34
|
-
UPGRADE_PROGRESS,
|
|
35
|
-
waitForReady,
|
|
36
|
-
} from "../lib/upgrade-lifecycle.js";
|
|
37
|
-
|
|
38
|
-
function parseArgs(): { name: string | null; version: string | null } {
|
|
39
|
-
const args = process.argv.slice(3);
|
|
40
|
-
let name: string | null = null;
|
|
41
|
-
let version: string | null = null;
|
|
42
|
-
|
|
43
|
-
for (let i = 0; i < args.length; i++) {
|
|
44
|
-
const arg = args[i];
|
|
45
|
-
if (arg === "--help" || arg === "-h") {
|
|
46
|
-
console.log("Usage: cadet rollback [<name>] [--version <version>]");
|
|
47
|
-
console.log("");
|
|
48
|
-
console.log(
|
|
49
|
-
"Roll back a Docker or cloud assistant to a previous version.",
|
|
50
|
-
);
|
|
51
|
-
console.log("");
|
|
52
|
-
console.log("Arguments:");
|
|
53
|
-
console.log(
|
|
54
|
-
" <name> Name of the assistant, defaults to active or only assistant",
|
|
55
|
-
);
|
|
56
|
-
console.log("");
|
|
57
|
-
console.log("Options:");
|
|
58
|
-
console.log(
|
|
59
|
-
" --version <version> Target version, optional for managed — omit to roll back to previous",
|
|
60
|
-
);
|
|
61
|
-
console.log("");
|
|
62
|
-
console.log("Examples:");
|
|
63
|
-
console.log(
|
|
64
|
-
" cadet rollback my-assistant # Roll back to previous version (Docker or managed)",
|
|
65
|
-
);
|
|
66
|
-
console.log(
|
|
67
|
-
" cadet rollback my-assistant --version v1.2.3 # Roll back to a specific version",
|
|
68
|
-
);
|
|
69
|
-
process.exit(0);
|
|
70
|
-
} else if (arg === "--version") {
|
|
71
|
-
const next = args[i + 1];
|
|
72
|
-
if (!next || next.startsWith("-")) {
|
|
73
|
-
console.error("Error: --version requires a value");
|
|
74
|
-
emitCliError("UNKNOWN", "--version requires a value");
|
|
75
|
-
process.exit(1);
|
|
76
|
-
}
|
|
77
|
-
version = next;
|
|
78
|
-
i++;
|
|
79
|
-
} else if (!arg.startsWith("-")) {
|
|
80
|
-
name = arg;
|
|
81
|
-
} else {
|
|
82
|
-
console.error(`Error: Unknown option '${arg}'.`);
|
|
83
|
-
emitCliError("UNKNOWN", `Unknown option '${arg}'`);
|
|
84
|
-
process.exit(1);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return { name, version };
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Resolve which assistant to target for the rollback command. Priority:
|
|
93
|
-
* 1. Explicit name argument
|
|
94
|
-
* 2. Active assistant set via `cadet use`
|
|
95
|
-
* 3. Sole assistant (when exactly one exists)
|
|
96
|
-
*/
|
|
97
|
-
function resolveTargetAssistant(nameArg: string | null): AssistantEntry {
|
|
98
|
-
if (nameArg) {
|
|
99
|
-
const entry = findAssistantByName(nameArg);
|
|
100
|
-
if (!entry) {
|
|
101
|
-
console.error(`No assistant found with name '${nameArg}'.`);
|
|
102
|
-
emitCliError(
|
|
103
|
-
"ASSISTANT_NOT_FOUND",
|
|
104
|
-
`No assistant found with name '${nameArg}'.`,
|
|
105
|
-
);
|
|
106
|
-
process.exit(1);
|
|
107
|
-
}
|
|
108
|
-
return entry;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const active = getActiveAssistant();
|
|
112
|
-
if (active) {
|
|
113
|
-
const entry = findAssistantByName(active);
|
|
114
|
-
if (entry) return entry;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const all = loadAllAssistants();
|
|
118
|
-
if (all.length === 1) return all[0];
|
|
119
|
-
|
|
120
|
-
if (all.length === 0) {
|
|
121
|
-
const msg = "No assistants found. Run 'cadet hatch' first.";
|
|
122
|
-
console.error(msg);
|
|
123
|
-
emitCliError("ASSISTANT_NOT_FOUND", msg);
|
|
124
|
-
} else {
|
|
125
|
-
const msg =
|
|
126
|
-
"Multiple assistants found. Specify a name or set an active assistant with 'cadet use <name>'.";
|
|
127
|
-
console.error(msg);
|
|
128
|
-
emitCliError("ASSISTANT_NOT_FOUND", msg);
|
|
129
|
-
}
|
|
130
|
-
process.exit(1);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
async function rollbackPlatformViaEndpoint(
|
|
134
|
-
entry: AssistantEntry,
|
|
135
|
-
version?: string,
|
|
136
|
-
): Promise<void> {
|
|
137
|
-
// Step 1 — Authenticate
|
|
138
|
-
const token = readCloudToken();
|
|
139
|
-
if (!token) {
|
|
140
|
-
const msg =
|
|
141
|
-
"Error: Not logged in. Run `cadet login --token <token>` first.";
|
|
142
|
-
console.error(msg);
|
|
143
|
-
emitCliError("AUTH_FAILED", msg);
|
|
144
|
-
process.exit(1);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Fetch current version from health endpoint (best-effort)
|
|
148
|
-
const currentVersion = await fetchCurrentVersion(entry.runtimeUrl);
|
|
149
|
-
|
|
150
|
-
// Step 3 — Call rollback endpoint
|
|
151
|
-
if (version) {
|
|
152
|
-
console.log(`Rolling back to ${version}...`);
|
|
153
|
-
} else {
|
|
154
|
-
console.log("Rolling back to previous version...");
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
let result: { detail: string; version: string | null };
|
|
158
|
-
try {
|
|
159
|
-
result = await rollbackCloudAssistant(
|
|
160
|
-
token,
|
|
161
|
-
entry.assistantId,
|
|
162
|
-
version,
|
|
163
|
-
entry.runtimeUrl,
|
|
164
|
-
);
|
|
165
|
-
} catch (err) {
|
|
166
|
-
const detail = err instanceof Error ? err.message : String(err);
|
|
167
|
-
|
|
168
|
-
// Map specific server error messages to actionable CLI output
|
|
169
|
-
if (detail.includes("No previous version")) {
|
|
170
|
-
console.error(
|
|
171
|
-
"No previous version available. A successful upgrade must have been performed first.",
|
|
172
|
-
);
|
|
173
|
-
} else if (detail.includes("not older")) {
|
|
174
|
-
console.error(
|
|
175
|
-
`Target version is not older than the current version. Use 'cadet upgrade --version' instead.`,
|
|
176
|
-
);
|
|
177
|
-
} else if (detail.includes("not found")) {
|
|
178
|
-
console.error(
|
|
179
|
-
version
|
|
180
|
-
? `Version ${version} not found.`
|
|
181
|
-
: `Rollback target not found.`,
|
|
182
|
-
);
|
|
183
|
-
} else if (
|
|
184
|
-
err instanceof TypeError ||
|
|
185
|
-
detail.includes("fetch failed") ||
|
|
186
|
-
detail.includes("ECONNREFUSED")
|
|
187
|
-
) {
|
|
188
|
-
console.error(
|
|
189
|
-
`Connection error: ${detail}\nIs Cadet Cloud reachable? Try 'cadet wake' if the assistant is asleep.`,
|
|
190
|
-
);
|
|
191
|
-
} else {
|
|
192
|
-
console.error(`Error: ${detail}`);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
emitCliError("PLATFORM_API_ERROR", "Cadet Cloud rollback failed", detail);
|
|
196
|
-
await broadcastUpgradeEvent(
|
|
197
|
-
entry.runtimeUrl,
|
|
198
|
-
entry.assistantId,
|
|
199
|
-
buildCompleteEvent(currentVersion ?? version ?? "unknown", false),
|
|
200
|
-
);
|
|
201
|
-
process.exit(1);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const rolledBackVersion = result.version ?? version ?? "unknown";
|
|
205
|
-
|
|
206
|
-
// Step 4 — Print success
|
|
207
|
-
console.log(`Rolled back to version ${rolledBackVersion}.`);
|
|
208
|
-
if (!version) {
|
|
209
|
-
console.log("Tip: Run 'cadet rollback' again to undo.");
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
export const rollback = Object.assign(
|
|
214
|
-
async function rollback(): Promise<void> {
|
|
215
|
-
const { name, version } = parseArgs();
|
|
216
|
-
const entry = resolveTargetAssistant(name);
|
|
217
|
-
const cloud = entry.cloud;
|
|
218
|
-
|
|
219
|
-
if (cloud === "apple-container") {
|
|
220
|
-
console.error(
|
|
221
|
-
`Error: '${entry.assistantId}' uses the Apple Containers runtime. Rollback is not yet supported for this topology.`,
|
|
222
|
-
);
|
|
223
|
-
process.exit(1);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// ---------- Managed (Cadet Cloud) rollback ----------
|
|
227
|
-
if (isCloudHome(cloud)) {
|
|
228
|
-
await rollbackPlatformViaEndpoint(entry, version ?? undefined);
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// ---------- Unsupported topologies ----------
|
|
233
|
-
if (cloud !== "docker") {
|
|
234
|
-
const msg = "Rollback is only supported for Docker and cloud assistants.";
|
|
235
|
-
console.error(msg);
|
|
236
|
-
emitCliError("UNSUPPORTED_TOPOLOGY", msg);
|
|
237
|
-
process.exit(1);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// ---------- Docker: Targeted version rollback (--version specified) ----------
|
|
241
|
-
if (version) {
|
|
242
|
-
try {
|
|
243
|
-
await performDockerRollback(entry, { targetVersion: version });
|
|
244
|
-
} catch (err) {
|
|
245
|
-
const detail = err instanceof Error ? err.message : String(err);
|
|
246
|
-
console.error(`\n❌ Rollback failed: ${detail}`);
|
|
247
|
-
await broadcastUpgradeEvent(
|
|
248
|
-
entry.runtimeUrl,
|
|
249
|
-
entry.assistantId,
|
|
250
|
-
buildCompleteEvent(version ?? "unknown", false),
|
|
251
|
-
);
|
|
252
|
-
emitCliError(categorizeUpgradeError(err), "Rollback failed", detail);
|
|
253
|
-
process.exit(1);
|
|
254
|
-
}
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// ---------- Docker: Saved-state rollback (no --version) ----------
|
|
259
|
-
|
|
260
|
-
// Fetch current + previous version from live APIs
|
|
261
|
-
const currentVersion = await fetchCurrentVersion(entry.runtimeUrl);
|
|
262
|
-
const previousVersion =
|
|
263
|
-
(await fetchPreviousVersion(currentVersion, entry.previousVersion)) ??
|
|
264
|
-
"unknown";
|
|
265
|
-
|
|
266
|
-
// Verify rollback state exists
|
|
267
|
-
if (!entry.previousContainerInfo) {
|
|
268
|
-
const msg =
|
|
269
|
-
"No rollback state available. Run `cadet upgrade` first to create a rollback point.";
|
|
270
|
-
console.error(msg);
|
|
271
|
-
emitCliError("ROLLBACK_NO_STATE", msg);
|
|
272
|
-
process.exit(1);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// Roll back to the previous images. Older entries recorded per-service
|
|
276
|
-
// digests (the image sha captured at upgrade time) — those are gone from
|
|
277
|
-
// the contract now; the recorded image refs are the rollback target.
|
|
278
|
-
const prev = entry.previousContainerInfo;
|
|
279
|
-
if (!prev.assistantImage || !prev.gatewayImage || !prev.cesImage) {
|
|
280
|
-
const msg =
|
|
281
|
-
"Incomplete rollback state. Previous container image refs are missing.";
|
|
282
|
-
console.error(msg);
|
|
283
|
-
emitCliError("ROLLBACK_NO_STATE", msg);
|
|
284
|
-
process.exit(1);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// Build image refs from the previous record
|
|
288
|
-
const previousImageRefs: Record<ServiceName, string> = {
|
|
289
|
-
assistant: prev.assistantImage,
|
|
290
|
-
"credential-executor": prev.cesImage,
|
|
291
|
-
gateway: prev.gatewayImage,
|
|
292
|
-
};
|
|
293
|
-
|
|
294
|
-
const instanceName = entry.assistantId;
|
|
295
|
-
const res = dockerResourceNames(instanceName);
|
|
296
|
-
|
|
297
|
-
try {
|
|
298
|
-
// Record rollback start in workspace git history
|
|
299
|
-
await commitWorkspaceViaGateway(
|
|
300
|
-
entry.runtimeUrl,
|
|
301
|
-
entry.assistantId,
|
|
302
|
-
buildUpgradeCommitMessage({
|
|
303
|
-
action: "rollback",
|
|
304
|
-
phase: "starting",
|
|
305
|
-
from: currentVersion ?? "unknown",
|
|
306
|
-
to: previousVersion,
|
|
307
|
-
topology: "docker",
|
|
308
|
-
assistantId: entry.assistantId,
|
|
309
|
-
}),
|
|
310
|
-
);
|
|
311
|
-
|
|
312
|
-
console.log(
|
|
313
|
-
`🔄 Rolling back Docker assistant '${instanceName}' to ${previousVersion}...\n`,
|
|
314
|
-
);
|
|
315
|
-
|
|
316
|
-
const {
|
|
317
|
-
bootstrapSecret,
|
|
318
|
-
cesServiceToken,
|
|
319
|
-
signingKey,
|
|
320
|
-
extraAssistantEnv,
|
|
321
|
-
extraGatewayEnv,
|
|
322
|
-
} = await captureReplayState(res);
|
|
323
|
-
|
|
324
|
-
// Parse gateway port from entry's runtimeUrl, fall back to default
|
|
325
|
-
let gatewayPort = GATEWAY_INTERNAL_PORT;
|
|
326
|
-
try {
|
|
327
|
-
const parsed = new URL(entry.runtimeUrl);
|
|
328
|
-
const port = parseInt(parsed.port, 10);
|
|
329
|
-
if (!isNaN(port)) {
|
|
330
|
-
gatewayPort = port;
|
|
331
|
-
}
|
|
332
|
-
} catch {
|
|
333
|
-
// use default
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
// Recover the assistant host port from the entry, fall back to default.
|
|
337
|
-
const assistantPort =
|
|
338
|
-
entry.containerInfo?.assistantPort ?? ASSISTANT_INTERNAL_PORT;
|
|
339
|
-
|
|
340
|
-
// Notify connected clients that a rollback is about to begin (best-effort)
|
|
341
|
-
console.log("📢 Notifying connected clients...");
|
|
342
|
-
await broadcastUpgradeEvent(
|
|
343
|
-
entry.runtimeUrl,
|
|
344
|
-
entry.assistantId,
|
|
345
|
-
buildStartingEvent(previousVersion),
|
|
346
|
-
);
|
|
347
|
-
// Brief pause to allow SSE delivery before containers stop.
|
|
348
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
349
|
-
|
|
350
|
-
// Roll back migrations to pre-upgrade state (must happen before containers stop)
|
|
351
|
-
if (
|
|
352
|
-
entry.previousDbMigrationVersion !== undefined ||
|
|
353
|
-
entry.previousWorkspaceMigrationId !== undefined
|
|
354
|
-
) {
|
|
355
|
-
console.log("🔄 Reverting database changes...");
|
|
356
|
-
await broadcastUpgradeEvent(
|
|
357
|
-
entry.runtimeUrl,
|
|
358
|
-
entry.assistantId,
|
|
359
|
-
buildProgressEvent(UPGRADE_PROGRESS.REVERTING_MIGRATIONS),
|
|
360
|
-
);
|
|
361
|
-
await rollbackMigrations(
|
|
362
|
-
entry.runtimeUrl,
|
|
363
|
-
entry.assistantId,
|
|
364
|
-
entry.previousDbMigrationVersion,
|
|
365
|
-
entry.previousWorkspaceMigrationId,
|
|
366
|
-
);
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
// Progress: switching version (must be sent BEFORE stopContainers)
|
|
370
|
-
await broadcastUpgradeEvent(
|
|
371
|
-
entry.runtimeUrl,
|
|
372
|
-
entry.assistantId,
|
|
373
|
-
buildProgressEvent(UPGRADE_PROGRESS.SWITCHING),
|
|
374
|
-
);
|
|
375
|
-
|
|
376
|
-
console.log("🛑 Stopping existing containers...");
|
|
377
|
-
await stopContainers(res);
|
|
378
|
-
console.log("✅ Containers stopped\n");
|
|
379
|
-
|
|
380
|
-
console.log("🚀 Starting containers with previous version...");
|
|
381
|
-
await startContainers(
|
|
382
|
-
{
|
|
383
|
-
signingKey,
|
|
384
|
-
bootstrapSecret,
|
|
385
|
-
cesServiceToken,
|
|
386
|
-
extraAssistantEnv,
|
|
387
|
-
extraGatewayEnv,
|
|
388
|
-
gatewayPort,
|
|
389
|
-
assistantPort,
|
|
390
|
-
imageTags: previousImageRefs,
|
|
391
|
-
instanceName,
|
|
392
|
-
res,
|
|
393
|
-
},
|
|
394
|
-
(msg) => console.log(msg),
|
|
395
|
-
);
|
|
396
|
-
console.log("✅ Containers started\n");
|
|
397
|
-
|
|
398
|
-
console.log("Waiting for assistant to become ready...");
|
|
399
|
-
const ready = await waitForReady(entry.runtimeUrl);
|
|
400
|
-
|
|
401
|
-
if (ready) {
|
|
402
|
-
// Swap current/previous state to enable "rollback the rollback"
|
|
403
|
-
const updatedEntry: AssistantEntry = {
|
|
404
|
-
...entry,
|
|
405
|
-
containerInfo: {
|
|
406
|
-
assistantImage: prev.assistantImage ?? previousImageRefs.assistant,
|
|
407
|
-
gatewayImage: prev.gatewayImage ?? previousImageRefs.gateway,
|
|
408
|
-
cesImage: prev.cesImage ?? previousImageRefs["credential-executor"],
|
|
409
|
-
assistantPort,
|
|
410
|
-
},
|
|
411
|
-
previousContainerInfo: entry.containerInfo,
|
|
412
|
-
// Clear the backup path — it belonged to the upgrade we just rolled back
|
|
413
|
-
preUpgradeBackupPath: undefined,
|
|
414
|
-
previousDbMigrationVersion: undefined,
|
|
415
|
-
previousWorkspaceMigrationId: undefined,
|
|
416
|
-
};
|
|
417
|
-
saveAssistantEntry(updatedEntry);
|
|
418
|
-
|
|
419
|
-
// Notify clients that the rollback succeeded
|
|
420
|
-
await broadcastUpgradeEvent(
|
|
421
|
-
entry.runtimeUrl,
|
|
422
|
-
entry.assistantId,
|
|
423
|
-
buildCompleteEvent(previousVersion, true),
|
|
424
|
-
);
|
|
425
|
-
|
|
426
|
-
// Record successful rollback in workspace git history
|
|
427
|
-
await commitWorkspaceViaGateway(
|
|
428
|
-
entry.runtimeUrl,
|
|
429
|
-
entry.assistantId,
|
|
430
|
-
buildUpgradeCommitMessage({
|
|
431
|
-
action: "rollback",
|
|
432
|
-
phase: "complete",
|
|
433
|
-
from: currentVersion ?? "unknown",
|
|
434
|
-
to: previousVersion,
|
|
435
|
-
topology: "docker",
|
|
436
|
-
assistantId: entry.assistantId,
|
|
437
|
-
result: "success",
|
|
438
|
-
}),
|
|
439
|
-
);
|
|
440
|
-
|
|
441
|
-
console.log(
|
|
442
|
-
`\n✅ Docker assistant '${instanceName}' rolled back to ${previousVersion}.`,
|
|
443
|
-
);
|
|
444
|
-
console.log(
|
|
445
|
-
"\nTip: To also restore data from before the upgrade, use `cadet restore --from <backup-path>`.",
|
|
446
|
-
);
|
|
447
|
-
} else {
|
|
448
|
-
console.error(
|
|
449
|
-
`\n❌ Containers failed to become ready within the timeout.`,
|
|
450
|
-
);
|
|
451
|
-
console.log(
|
|
452
|
-
` Check logs with: docker logs -f ${res.assistantContainer}`,
|
|
453
|
-
);
|
|
454
|
-
await broadcastUpgradeEvent(
|
|
455
|
-
entry.runtimeUrl,
|
|
456
|
-
entry.assistantId,
|
|
457
|
-
buildCompleteEvent(previousVersion, false),
|
|
458
|
-
);
|
|
459
|
-
emitCliError(
|
|
460
|
-
"READINESS_TIMEOUT",
|
|
461
|
-
"Rolled-back containers failed to become ready within the timeout.",
|
|
462
|
-
);
|
|
463
|
-
process.exit(1);
|
|
464
|
-
}
|
|
465
|
-
} catch (err) {
|
|
466
|
-
const detail = err instanceof Error ? err.message : String(err);
|
|
467
|
-
console.error(`\n❌ Rollback failed: ${detail}`);
|
|
468
|
-
await broadcastUpgradeEvent(
|
|
469
|
-
entry.runtimeUrl,
|
|
470
|
-
entry.assistantId,
|
|
471
|
-
buildCompleteEvent(previousVersion, false),
|
|
472
|
-
);
|
|
473
|
-
emitCliError(categorizeUpgradeError(err), "Rollback failed", detail);
|
|
474
|
-
process.exit(1);
|
|
475
|
-
}
|
|
476
|
-
},
|
|
477
|
-
{ description: "Roll back an assistant to a previous version" },
|
|
478
|
-
);
|
|
479
|
-
|
package/src/commands/unpair.ts
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `cadet unpair <name> [--yes]`
|
|
3
|
-
*
|
|
4
|
-
* Forget a pairing imported from another machine via `cadet connect import`:
|
|
5
|
-
* remove its lockfile entry and stored guardian token from THIS machine. Only
|
|
6
|
-
* paired assistants (`cloud: "paired"`) can be unpaired — `cadet retire` owns
|
|
7
|
-
* local and cloud assistants.
|
|
8
|
-
*
|
|
9
|
-
* This is client-side only: it forgets the connection here but does not revoke
|
|
10
|
-
* the device on the host. (Host-side revocation is `cadet devices`.)
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import {
|
|
14
|
-
formatAssistantLookupError,
|
|
15
|
-
getAssistantDisplayName,
|
|
16
|
-
lookupAssistantByIdentifier,
|
|
17
|
-
removeAssistantEntry,
|
|
18
|
-
} from "../lib/assistant-config";
|
|
19
|
-
import { parseAssistantTargetArg } from "../lib/assistant-target-args.js";
|
|
20
|
-
import {
|
|
21
|
-
canPromptForConfirmation,
|
|
22
|
-
confirmAction,
|
|
23
|
-
} from "../lib/confirm-action.js";
|
|
24
|
-
import { deleteGuardianToken } from "../lib/guardian-token";
|
|
25
|
-
|
|
26
|
-
function printUsage(): void {
|
|
27
|
-
console.log(`cadet unpair [beta] - Forget a paired assistant imported from another machine
|
|
28
|
-
|
|
29
|
-
USAGE:
|
|
30
|
-
cadet unpair <name> [--yes]
|
|
31
|
-
|
|
32
|
-
ARGUMENTS:
|
|
33
|
-
<name> Name or id of the paired assistant to forget
|
|
34
|
-
|
|
35
|
-
OPTIONS:
|
|
36
|
-
--yes Skip the interactive confirmation prompt (for automation)
|
|
37
|
-
|
|
38
|
-
Removes the local connection (lockfile entry + stored token). Only paired
|
|
39
|
-
assistants (imported via 'cadet connect import') can be unpaired; use
|
|
40
|
-
'cadet retire' for local or cloud assistants.
|
|
41
|
-
|
|
42
|
-
EXAMPLES:
|
|
43
|
-
cadet unpair paired-desk
|
|
44
|
-
cadet unpair "Desk Box"
|
|
45
|
-
cadet unpair paired-desk --yes
|
|
46
|
-
`);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export const unpair = Object.assign(
|
|
50
|
-
async function unpair(): Promise<void> {
|
|
51
|
-
const args = process.argv.slice(3);
|
|
52
|
-
|
|
53
|
-
if (args.includes("--help") || args.includes("-h")) {
|
|
54
|
-
printUsage();
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const yes = args.includes("--yes");
|
|
59
|
-
const name = parseAssistantTargetArg(args, []);
|
|
60
|
-
if (!name) {
|
|
61
|
-
console.error("Error: assistant name or id is required.");
|
|
62
|
-
printUsage();
|
|
63
|
-
process.exit(1);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const lookup = lookupAssistantByIdentifier(name);
|
|
67
|
-
if (lookup.status !== "found") {
|
|
68
|
-
console.error(formatAssistantLookupError(name, lookup));
|
|
69
|
-
process.exit(1);
|
|
70
|
-
}
|
|
71
|
-
const entry = lookup.entry;
|
|
72
|
-
|
|
73
|
-
if (entry.cloud !== "paired") {
|
|
74
|
-
console.error(
|
|
75
|
-
`Error: '${name}' is not a paired assistant. Use \`cadet retire\` to remove a local or cloud assistant.`,
|
|
76
|
-
);
|
|
77
|
-
process.exit(1);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Print the resolved identity before acting (cli/AGENTS.md).
|
|
81
|
-
const displayName = getAssistantDisplayName(entry);
|
|
82
|
-
console.log("Pairing to unpair:");
|
|
83
|
-
if (displayName !== entry.assistantId) {
|
|
84
|
-
console.log(` Name: ${displayName}`);
|
|
85
|
-
}
|
|
86
|
-
console.log(` ID: ${entry.assistantId}`);
|
|
87
|
-
if (entry.runtimeUrl) {
|
|
88
|
-
console.log(` Host: ${entry.runtimeUrl}`);
|
|
89
|
-
}
|
|
90
|
-
console.log("");
|
|
91
|
-
|
|
92
|
-
if (!yes) {
|
|
93
|
-
if (!canPromptForConfirmation()) {
|
|
94
|
-
console.error(
|
|
95
|
-
"Error: Refusing to unpair without confirmation in a non-interactive terminal.",
|
|
96
|
-
);
|
|
97
|
-
console.error("Re-run with --yes to confirm from automation.");
|
|
98
|
-
process.exit(1);
|
|
99
|
-
}
|
|
100
|
-
const confirmed = await confirmAction(
|
|
101
|
-
"Press Enter to unpair, or Esc/q to cancel: ",
|
|
102
|
-
);
|
|
103
|
-
if (!confirmed) {
|
|
104
|
-
console.log("Unpair cancelled.");
|
|
105
|
-
process.exit(1);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
removeAssistantEntry(entry.assistantId);
|
|
110
|
-
deleteGuardianToken(entry.assistantId);
|
|
111
|
-
|
|
112
|
-
console.log(
|
|
113
|
-
`Unpaired '${name}' — removed the local connection (lockfile entry + token).`,
|
|
114
|
-
);
|
|
115
|
-
console.log("");
|
|
116
|
-
console.log(
|
|
117
|
-
"Note: this only forgets the connection on this machine. The assistant's host can fully revoke this device from its side.",
|
|
118
|
-
);
|
|
119
|
-
},
|
|
120
|
-
{ description: "Forget a paired assistant imported from another machine [beta]" },
|
|
121
|
-
);
|
|
122
|
-
|