@m8t-stack/cli 0.2.90 → 0.2.91

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/dist/cli.js CHANGED
@@ -921,8 +921,8 @@ async function mirrorBrainYaml(args) {
921
921
  Accept: "application/vnd.github+json",
922
922
  "X-GitHub-Api-Version": "2022-11-28"
923
923
  };
924
- const MAX_ATTEMPTS2 = 3;
925
- for (let attempt = 1; attempt <= MAX_ATTEMPTS2; attempt++) {
924
+ const MAX_ATTEMPTS3 = 3;
925
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS3; attempt++) {
926
926
  const getRes = await fetch(url, { headers });
927
927
  let sha;
928
928
  let existing;
@@ -953,8 +953,8 @@ async function mirrorBrainYaml(args) {
953
953
  if (putRes.ok) return;
954
954
  const text = await putRes.text();
955
955
  const retryable = putRes.status === 409 || putRes.status === 422;
956
- if (retryable && attempt < MAX_ATTEMPTS2) continue;
957
- const suffix = retryable ? ` after ${MAX_ATTEMPTS2.toString()} attempts` : "";
956
+ if (retryable && attempt < MAX_ATTEMPTS3) continue;
957
+ const suffix = retryable ? ` after ${MAX_ATTEMPTS3.toString()} attempts` : "";
958
958
  throw new LocalCliError({
959
959
  code: "BRAIN_YAML_MIRROR_FAILED",
960
960
  message: `PUT .m8t/brain.yaml: HTTP ${putRes.status.toString()}${suffix}
@@ -1487,7 +1487,7 @@ function installFoundryDnsShim() {
1487
1487
  }
1488
1488
 
1489
1489
  // src/lib/package-version.ts
1490
- var CLI_VERSION = "0.2.90";
1490
+ var CLI_VERSION = "0.2.91";
1491
1491
 
1492
1492
  // src/lib/render-error.ts
1493
1493
  init_errors();
@@ -31792,6 +31792,7 @@ async function getAciState(opts) {
31792
31792
  }
31793
31793
 
31794
31794
  // src/lib/bootstrap-finalize.ts
31795
+ init_errors();
31795
31796
  import * as fs39 from "fs/promises";
31796
31797
  import * as os20 from "os";
31797
31798
  import * as path44 from "path";
@@ -32708,10 +32709,10 @@ var DEFAULT_WORKERS = [
32708
32709
  { name: "ezra", label: "Ezra - Azure Expert", brain: "ezra-brain" }
32709
32710
  ];
32710
32711
  function renderInstallSummary(args) {
32711
- const open3 = args.webappUrl ? `${args.webappUrl} (or run: m8t open)` : "run: m8t open";
32712
+ const open4 = args.webappUrl ? `${args.webappUrl} (or run: m8t open)` : "run: m8t open";
32712
32713
  const lines = [
32713
32714
  "\u2705 Your m8t platform is ready to use.",
32714
- ` Open it: ${open3}`,
32715
+ ` Open it: ${open4}`,
32715
32716
  ""
32716
32717
  ];
32717
32718
  if (args.brainOrg) {
@@ -32735,10 +32736,10 @@ function renderInstallSummary(args) {
32735
32736
 
32736
32737
  // src/lib/companion-install.ts
32737
32738
  import { constants as constants2 } from "fs";
32738
- import * as fs37 from "fs/promises";
32739
- import * as path41 from "path";
32739
+ import * as fs38 from "fs/promises";
32740
+ import * as path42 from "path";
32740
32741
  import { randomUUID as randomUUID3 } from "crypto";
32741
- import { execFile, spawn as spawn7 } from "child_process";
32742
+ import { execFile as execFile2, spawn as spawn7 } from "child_process";
32742
32743
 
32743
32744
  // src/lib/companion-artifact.ts
32744
32745
  import { createHash as createHash7 } from "crypto";
@@ -33016,6 +33017,265 @@ async function copyArtifactPayload(sourceRoot, targetRoot, manifest) {
33016
33017
  await verifyArtifactPayload(targetRoot, manifest);
33017
33018
  }
33018
33019
 
33020
+ // src/lib/companion-download.ts
33021
+ import { createHash as createHash8 } from "crypto";
33022
+ import { execFile } from "child_process";
33023
+ import { createReadStream } from "fs";
33024
+ import * as fs37 from "fs/promises";
33025
+ import * as os18 from "os";
33026
+ import * as path41 from "path";
33027
+ init_errors();
33028
+ var MAX_ASSET_BYTES = 512 * 1024 * 1024;
33029
+ var MAX_ATTEMPTS2 = 3;
33030
+ var BACKOFF_MS = [1e3, 3e3];
33031
+ function megabytes(bytes) {
33032
+ return `${Math.round(bytes / 1e6).toString()} MB`;
33033
+ }
33034
+ function extractArchive(archive, into) {
33035
+ return new Promise((resolve6, reject) => {
33036
+ execFile("tar", ["-xzf", archive, "-C", into], { windowsHide: true }, (error) => {
33037
+ if (error) reject(new Error(`Could not unpack the companion build: ${error.message}`));
33038
+ else resolve6();
33039
+ });
33040
+ });
33041
+ }
33042
+ function companionDownloadDirectory(homeDirectory) {
33043
+ return path41.join(homeDirectory, ".m8t", "companion", "downloads");
33044
+ }
33045
+ function companionAssetFor(component, platform = process.platform, architecture = process.arch) {
33046
+ const target = companionTargetFor(platform, architecture);
33047
+ if (target === null) return null;
33048
+ const pinned = component.targets[target];
33049
+ return pinned ? { target, asset: pinned.asset, sha256: pinned.sha256 } : null;
33050
+ }
33051
+ function tooLarge(asset) {
33052
+ return new LocalCliError({
33053
+ code: "COMPANION_ASSET_TOO_LARGE",
33054
+ message: `${asset} is larger than this command will accept.`
33055
+ });
33056
+ }
33057
+ function parseContentRange(value) {
33058
+ const matched = /^bytes (\d+)-(\d+)\/(\d+|\*)$/u.exec((value ?? "").trim());
33059
+ if (!matched?.[1] || !matched[3]) return null;
33060
+ return {
33061
+ start: Number(matched[1]),
33062
+ total: matched[3] === "*" ? null : Number(matched[3])
33063
+ };
33064
+ }
33065
+ function declaredLength(response) {
33066
+ const raw = response.headers.get("content-length");
33067
+ if (raw === null) return null;
33068
+ const value = Number(raw);
33069
+ return Number.isSafeInteger(value) && value >= 0 ? value : null;
33070
+ }
33071
+ async function usableBytes(partial) {
33072
+ const status = await fs37.lstat(partial).catch(() => null);
33073
+ if (status === null) return 0;
33074
+ if (!status.isFile()) {
33075
+ await fs37.rm(partial, { recursive: true, force: true }).catch(() => void 0);
33076
+ if (await fs37.lstat(partial).then(() => true, () => false)) {
33077
+ throw new LocalCliError({
33078
+ code: "COMPANION_DOWNLOAD_PATH_OCCUPIED",
33079
+ message: `${partial} is not a file and could not be cleared.`,
33080
+ hint: "Something that is not a download is sitting where the companion build goes. Remove it, then run this again."
33081
+ });
33082
+ }
33083
+ return 0;
33084
+ }
33085
+ return status.size;
33086
+ }
33087
+ async function sweepForeignPartials(directory, keep) {
33088
+ const entries = await fs37.readdir(directory).catch(() => []);
33089
+ await Promise.all(
33090
+ entries.filter((name) => name.endsWith(".part") && name !== keep).map((name) => fs37.rm(path41.join(directory, name), { recursive: true, force: true }))
33091
+ );
33092
+ }
33093
+ function sha256File3(filePath) {
33094
+ return new Promise((resolve6, reject) => {
33095
+ const hash = createHash8("sha256");
33096
+ const stream = createReadStream(filePath);
33097
+ stream.on("error", reject);
33098
+ stream.on("data", (chunk) => hash.update(chunk));
33099
+ stream.on("end", () => {
33100
+ resolve6(hash.digest("hex"));
33101
+ });
33102
+ });
33103
+ }
33104
+ async function transferToDisk(input) {
33105
+ let lastError;
33106
+ for (let attempt = 0; attempt < MAX_ATTEMPTS2; attempt++) {
33107
+ let existing = await usableBytes(input.partial);
33108
+ if (existing > input.maxAssetBytes) {
33109
+ await fs37.rm(input.partial, { force: true }).catch(() => void 0);
33110
+ existing = 0;
33111
+ }
33112
+ let received = existing;
33113
+ let answered = false;
33114
+ try {
33115
+ const controller = new AbortController();
33116
+ const response = await input.fetchImpl(input.url, {
33117
+ signal: controller.signal,
33118
+ ...existing > 0 ? { headers: { Range: `bytes=${existing.toString()}-` } } : {}
33119
+ });
33120
+ answered = true;
33121
+ if (response.status === 416) {
33122
+ await response.body?.cancel().catch(() => void 0);
33123
+ if (existing > 0 && await sha256File3(input.partial).catch(() => null) === input.expectedDigest) {
33124
+ return;
33125
+ }
33126
+ await fs37.rm(input.partial, { force: true }).catch(() => void 0);
33127
+ lastError = new Error("the interrupted download no longer fits the published asset");
33128
+ continue;
33129
+ }
33130
+ if (!response.ok) {
33131
+ await response.body?.cancel().catch(() => void 0);
33132
+ const failed = new LocalCliError({
33133
+ code: "COMPANION_ASSET_FETCH_FAILED",
33134
+ message: `GET ${input.url} returned HTTP ${response.status.toString()}.`,
33135
+ hint: "The release the platform is pinned to should carry this file. Try again, or run 'm8t companion repair' once the network is back."
33136
+ });
33137
+ if (response.status < 500) throw failed;
33138
+ lastError = failed;
33139
+ if (attempt + 1 < MAX_ATTEMPTS2) {
33140
+ input.onEvent({ kind: "retrying", received: existing, attempt: attempt + 1, cause: "server-error" });
33141
+ await input.delay(BACKOFF_MS[Math.min(attempt, BACKOFF_MS.length - 1)]);
33142
+ }
33143
+ continue;
33144
+ }
33145
+ let offset;
33146
+ let total;
33147
+ if (response.status === 206) {
33148
+ const range = parseContentRange(response.headers.get("content-range"));
33149
+ if (range?.start !== existing) {
33150
+ await response.body?.cancel().catch(() => void 0);
33151
+ await fs37.rm(input.partial, { force: true }).catch(() => void 0);
33152
+ lastError = new Error("the server answered a different range than the one requested");
33153
+ continue;
33154
+ }
33155
+ offset = existing;
33156
+ total = range.total;
33157
+ } else {
33158
+ offset = 0;
33159
+ total = declaredLength(response);
33160
+ }
33161
+ const declaredBody = declaredLength(response);
33162
+ if (total !== null && total > input.maxAssetBytes || declaredBody !== null && declaredBody > input.maxAssetBytes) {
33163
+ await response.body?.cancel().catch(() => void 0);
33164
+ await fs37.rm(input.partial, { force: true }).catch(() => void 0);
33165
+ throw tooLarge(input.asset);
33166
+ }
33167
+ if (offset > 0) input.onEvent({ kind: "resumed", received: offset, total });
33168
+ received = offset;
33169
+ const body = response.body;
33170
+ if (body === null) throw new Error("the response carried no body");
33171
+ const handle = await fs37.open(input.partial, offset > 0 ? "a" : "w", 384);
33172
+ try {
33173
+ for await (const chunk of body) {
33174
+ received += chunk.byteLength;
33175
+ if (received > input.maxAssetBytes) {
33176
+ controller.abort();
33177
+ throw tooLarge(input.asset);
33178
+ }
33179
+ await handle.write(chunk);
33180
+ input.onEvent({ kind: "progress", received, total });
33181
+ }
33182
+ } finally {
33183
+ await handle.close();
33184
+ }
33185
+ if (total !== null && received < total) {
33186
+ throw new Error(
33187
+ `the transfer stopped at ${received.toString()} of ${total.toString()} bytes`
33188
+ );
33189
+ }
33190
+ return;
33191
+ } catch (error) {
33192
+ if (error instanceof LocalCliError) {
33193
+ if (error.code === "COMPANION_ASSET_TOO_LARGE") {
33194
+ await fs37.rm(input.partial, { force: true }).catch(() => void 0);
33195
+ }
33196
+ throw error;
33197
+ }
33198
+ lastError = error;
33199
+ if (attempt + 1 < MAX_ATTEMPTS2) {
33200
+ input.onEvent({
33201
+ kind: "retrying",
33202
+ received,
33203
+ attempt: attempt + 1,
33204
+ cause: answered ? "dropped" : "not-connected"
33205
+ });
33206
+ await input.delay(BACKOFF_MS[Math.min(attempt, BACKOFF_MS.length - 1)]);
33207
+ }
33208
+ }
33209
+ }
33210
+ const kept = await fs37.lstat(input.partial).then(
33211
+ (status) => status.isFile() ? status.size : 0,
33212
+ () => 0
33213
+ );
33214
+ const keptLabel = megabytes(kept);
33215
+ throw new LocalCliError({
33216
+ code: "COMPANION_DOWNLOAD_INCOMPLETE",
33217
+ message: "The companion build could not be downloaded.",
33218
+ ...keptLabel === "0 MB" ? {} : { hint: `The ${keptLabel} already downloaded is kept. Run it again to carry on from there.` },
33219
+ cause: lastError
33220
+ });
33221
+ }
33222
+ async function downloadCompanionArtifact(component, deps = {}) {
33223
+ const platform = deps.platform ?? process.platform;
33224
+ const architecture = deps.architecture ?? process.arch;
33225
+ const pinned = companionAssetFor(component, platform, architecture);
33226
+ if (pinned === null) {
33227
+ throw new LocalCliError({
33228
+ code: "COMPANION_NO_BUILD_FOR_HOST",
33229
+ message: `Release ${component.version} carries no desktop companion for ${platform}-${architecture}.`,
33230
+ hint: "The companions are built for macOS and Windows on arm64 and x64."
33231
+ });
33232
+ }
33233
+ const url = releaseAssetUrl(component.tag, pinned.asset);
33234
+ const directory = deps.downloadDirectory ?? companionDownloadDirectory(os18.homedir());
33235
+ const partialName = `${pinned.sha256}.part`;
33236
+ const partial = path41.join(directory, partialName);
33237
+ await fs37.mkdir(directory, { recursive: true, mode: 448 });
33238
+ await sweepForeignPartials(directory, partialName);
33239
+ await transferToDisk({
33240
+ url,
33241
+ asset: pinned.asset,
33242
+ partial,
33243
+ fetchImpl: deps.fetchImpl ?? fetch,
33244
+ onEvent: deps.onEvent ?? (() => void 0),
33245
+ delay: deps.delay ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms))),
33246
+ maxAssetBytes: deps.maxAssetBytes ?? MAX_ASSET_BYTES,
33247
+ expectedDigest: pinned.sha256
33248
+ });
33249
+ const digest = await sha256File3(partial);
33250
+ if (digest !== pinned.sha256) {
33251
+ await fs37.rm(partial, { force: true }).catch(() => void 0);
33252
+ throw new LocalCliError({
33253
+ code: "COMPANION_ASSET_DIGEST_MISMATCH",
33254
+ message: `${pinned.asset} does not match the digest the release pins for it.`,
33255
+ hint: "Nothing was unpacked. This is what a corrupted download or a substituted file looks like \u2014 retry, and report it if it persists."
33256
+ });
33257
+ }
33258
+ const root = await (deps.makeTemporaryDirectory ?? (() => fs37.mkdtemp(path41.join(os18.tmpdir(), "m8t-companion-"))))();
33259
+ const dispose = async () => {
33260
+ await fs37.rm(root, { recursive: true, force: true }).catch(() => void 0);
33261
+ };
33262
+ try {
33263
+ const unpacked = path41.join(root, "unpacked");
33264
+ await fs37.mkdir(unpacked, { recursive: false, mode: 448 });
33265
+ await (deps.extract ?? extractArchive)(partial, unpacked);
33266
+ return {
33267
+ version: component.version,
33268
+ artifactManifestPath: path41.join(unpacked, "artifact-v1.json"),
33269
+ dispose
33270
+ };
33271
+ } catch (error) {
33272
+ await dispose();
33273
+ throw error;
33274
+ } finally {
33275
+ await fs37.rm(partial, { force: true }).catch(() => void 0);
33276
+ }
33277
+ }
33278
+
33019
33279
  // src/lib/companion-install.ts
33020
33280
  function xmlEscape(value) {
33021
33281
  return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
@@ -33028,7 +33288,7 @@ function commandError(error, message) {
33028
33288
  }
33029
33289
  function execFileAsync(file2, args) {
33030
33290
  return new Promise((resolve6, reject) => {
33031
- execFile(file2, [...args], { windowsHide: true }, (error) => {
33291
+ execFile2(file2, [...args], { windowsHide: true }, (error) => {
33032
33292
  if (error) reject(commandError(error, "Login-item command failed"));
33033
33293
  else resolve6();
33034
33294
  });
@@ -33036,7 +33296,7 @@ function execFileAsync(file2, args) {
33036
33296
  }
33037
33297
  function execFileOutput(file2, args) {
33038
33298
  return new Promise((resolve6, reject) => {
33039
- execFile(
33299
+ execFile2(
33040
33300
  file2,
33041
33301
  [...args],
33042
33302
  { windowsHide: true },
@@ -33049,17 +33309,17 @@ function execFileOutput(file2, args) {
33049
33309
  }
33050
33310
  async function setCompanionStartAtLogin(input) {
33051
33311
  if (input.platform === "darwin") {
33052
- const launchAgents = path41.join(input.homeDirectory, "Library", "LaunchAgents");
33053
- const registration = path41.join(
33312
+ const launchAgents = path42.join(input.homeDirectory, "Library", "LaunchAgents");
33313
+ const registration = path42.join(
33054
33314
  launchAgents,
33055
33315
  "com.m8t.companion.plist"
33056
33316
  );
33057
33317
  await assertNotSymlink(registration, "Start-at-login registration");
33058
33318
  if (!input.enabled) {
33059
- await fs37.rm(registration, { force: true });
33319
+ await fs38.rm(registration, { force: true });
33060
33320
  return;
33061
33321
  }
33062
- await fs37.mkdir(launchAgents, { recursive: true, mode: 448 });
33322
+ await fs38.mkdir(launchAgents, { recursive: true, mode: 448 });
33063
33323
  const plist = '<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0"><dict><key>Label</key><string>com.m8t.companion</string><key>ProgramArguments</key><array><string>' + xmlEscape(input.executable) + "</string></array><key>RunAtLoad</key><true/></dict></plist>\n";
33064
33324
  await atomicWriteText(registration, plist, 384);
33065
33325
  return;
@@ -33111,7 +33371,7 @@ async function setCompanionStartAtLogin(input) {
33111
33371
  }
33112
33372
  async function getCompanionStartAtLogin(input) {
33113
33373
  if (input.platform === "darwin") {
33114
- const registration = path41.join(
33374
+ const registration = path42.join(
33115
33375
  input.homeDirectory,
33116
33376
  "Library",
33117
33377
  "LaunchAgents",
@@ -33173,26 +33433,29 @@ function assertSupported(options) {
33173
33433
  }
33174
33434
  function companionInstallPaths(options) {
33175
33435
  assertSupported(options);
33176
- const companionState = path41.join(
33436
+ const companionState = path42.join(
33177
33437
  options.homeDirectory,
33178
33438
  ".m8t",
33179
33439
  "companion"
33180
33440
  );
33181
- const targetRoot = options.platform === "darwin" ? path41.join(
33441
+ const targetRoot = options.platform === "darwin" ? path42.join(
33182
33442
  options.homeDirectory,
33183
33443
  "Applications",
33184
33444
  "m8t Companion.app"
33185
- ) : path41.join(
33186
- options.localAppData ?? path41.join(options.homeDirectory, "AppData", "Local"),
33445
+ ) : path42.join(
33446
+ options.localAppData ?? path42.join(options.homeDirectory, "AppData", "Local"),
33187
33447
  "m8t",
33188
33448
  "companion",
33189
33449
  "app"
33190
33450
  );
33191
33451
  return {
33192
33452
  targetRoot,
33193
- installManifest: path41.join(companionState, "install-v1.json"),
33194
- runtimeBinding: path41.join(companionState, "runtime-v1.json"),
33195
- preferences: path41.join(companionState, "preferences-v1.json")
33453
+ installManifest: path42.join(companionState, "install-v1.json"),
33454
+ runtimeBinding: path42.join(companionState, "runtime-v1.json"),
33455
+ preferences: path42.join(companionState, "preferences-v1.json"),
33456
+ // Asked of the downloader rather than rebuilt here. Two modules computing
33457
+ // one path is how an uninstall comes to sweep a directory nothing writes.
33458
+ downloads: companionDownloadDirectory(options.homeDirectory)
33196
33459
  };
33197
33460
  }
33198
33461
  function exactKeys2(value, keys) {
@@ -33203,7 +33466,7 @@ function parseInstallManifest(value) {
33203
33466
  throw new Error("Install manifest schema is invalid");
33204
33467
  }
33205
33468
  const record = value;
33206
- if (!exactKeys2(record, INSTALL_KEYS) || record.schemaVersion !== 1 || typeof record.version !== "string" || record.platform !== "darwin" && record.platform !== "win32" || record.architecture !== "arm64" && record.architecture !== "x64" || typeof record.entryRelativePath !== "string" || typeof record.artifactTreeSha256 !== "string" || !/^[a-f0-9]{64}$/u.test(record.artifactTreeSha256) || typeof record.ownedTargetRoot !== "string" || !path41.isAbsolute(record.ownedTargetRoot)) {
33469
+ if (!exactKeys2(record, INSTALL_KEYS) || record.schemaVersion !== 1 || typeof record.version !== "string" || record.platform !== "darwin" && record.platform !== "win32" || record.architecture !== "arm64" && record.architecture !== "x64" || typeof record.entryRelativePath !== "string" || typeof record.artifactTreeSha256 !== "string" || !/^[a-f0-9]{64}$/u.test(record.artifactTreeSha256) || typeof record.ownedTargetRoot !== "string" || !path42.isAbsolute(record.ownedTargetRoot)) {
33207
33470
  throw new Error("Install manifest schema is invalid");
33208
33471
  }
33209
33472
  return record;
@@ -33213,7 +33476,7 @@ function parseRuntimeBinding(value, platform) {
33213
33476
  throw new Error("Runtime binding schema is invalid");
33214
33477
  }
33215
33478
  const record = value;
33216
- const paths = platform === "win32" ? path41.win32 : path41.posix;
33479
+ const paths = platform === "win32" ? path42.win32 : path42.posix;
33217
33480
  if (!exactKeys2(record, RUNTIME_KEYS) || record.schemaVersion !== 1 || typeof record.nodeExecutable !== "string" || !paths.isAbsolute(record.nodeExecutable) || typeof record.cliEntry !== "string" || !paths.isAbsolute(record.cliEntry) || typeof record.gatewayOrigin !== "string") {
33218
33481
  throw new Error("Runtime binding schema is invalid");
33219
33482
  }
@@ -33238,7 +33501,7 @@ function validateGatewayOrigin(value) {
33238
33501
  }
33239
33502
  async function assertNotSymlink(filePath, kind) {
33240
33503
  try {
33241
- if ((await fs37.lstat(filePath)).isSymbolicLink()) {
33504
+ if ((await fs38.lstat(filePath)).isSymbolicLink()) {
33242
33505
  throw new Error(`${kind} is a symbolic link`);
33243
33506
  }
33244
33507
  } catch (error) {
@@ -33247,20 +33510,20 @@ async function assertNotSymlink(filePath, kind) {
33247
33510
  }
33248
33511
  }
33249
33512
  async function assertOwnedDirectoryChain(anchor, targetDirectory) {
33250
- const relative4 = path41.relative(anchor, targetDirectory);
33251
- if (relative4 === ".." || relative4.startsWith(`..${path41.sep}`) || path41.isAbsolute(relative4)) {
33513
+ const relative4 = path42.relative(anchor, targetDirectory);
33514
+ if (relative4 === ".." || relative4.startsWith(`..${path42.sep}`) || path42.isAbsolute(relative4)) {
33252
33515
  throw new Error("Companion owned directory escapes its trusted anchor");
33253
33516
  }
33254
- const segments = relative4.split(path41.sep).filter(Boolean);
33517
+ const segments = relative4.split(path42.sep).filter(Boolean);
33255
33518
  let current = anchor;
33256
- const anchorStatus = await fs37.lstat(anchor);
33519
+ const anchorStatus = await fs38.lstat(anchor);
33257
33520
  if (anchorStatus.isSymbolicLink() || !anchorStatus.isDirectory()) {
33258
33521
  throw new Error("Companion owned directory anchor is unsafe");
33259
33522
  }
33260
33523
  for (const segment of segments) {
33261
- current = path41.join(current, segment);
33524
+ current = path42.join(current, segment);
33262
33525
  try {
33263
- const status = await fs37.lstat(current);
33526
+ const status = await fs38.lstat(current);
33264
33527
  if (status.isSymbolicLink() || !status.isDirectory()) {
33265
33528
  throw new Error("Companion owned directory contains a symbolic link");
33266
33529
  }
@@ -33273,19 +33536,19 @@ async function assertOwnedDirectoryChain(anchor, targetDirectory) {
33273
33536
  async function assertOwnedParents(options, paths) {
33274
33537
  await assertOwnedDirectoryChain(
33275
33538
  options.homeDirectory,
33276
- path41.dirname(paths.installManifest)
33539
+ path42.dirname(paths.installManifest)
33277
33540
  );
33278
- const targetAnchor = options.platform === "win32" ? options.localAppData ?? path41.join(options.homeDirectory, "AppData", "Local") : options.homeDirectory;
33279
- await assertOwnedDirectoryChain(targetAnchor, path41.dirname(paths.targetRoot));
33541
+ const targetAnchor = options.platform === "win32" ? options.localAppData ?? path42.join(options.homeDirectory, "AppData", "Local") : options.homeDirectory;
33542
+ await assertOwnedDirectoryChain(targetAnchor, path42.dirname(paths.targetRoot));
33280
33543
  }
33281
33544
  async function readRegularText(filePath, maxBytes) {
33282
- const before = await fs37.lstat(filePath);
33545
+ const before = await fs38.lstat(filePath);
33283
33546
  if (before.isSymbolicLink() || !before.isFile()) {
33284
33547
  throw new Error("Companion state file is not a regular file");
33285
33548
  }
33286
33549
  let handle;
33287
33550
  try {
33288
- handle = await fs37.open(
33551
+ handle = await fs38.open(
33289
33552
  filePath,
33290
33553
  constants2.O_RDONLY | constants2.O_NOFOLLOW
33291
33554
  );
@@ -33308,27 +33571,27 @@ async function atomicWriteJson(filePath, value) {
33308
33571
  }
33309
33572
  async function atomicWriteText(filePath, contents, mode) {
33310
33573
  await assertNotSymlink(filePath, "Companion state file");
33311
- await fs37.mkdir(path41.dirname(filePath), {
33574
+ await fs38.mkdir(path42.dirname(filePath), {
33312
33575
  recursive: true,
33313
33576
  mode: 448
33314
33577
  });
33315
33578
  const temporary = `${filePath}.${randomUUID3()}.tmp`;
33316
33579
  try {
33317
- await fs37.writeFile(temporary, contents, {
33580
+ await fs38.writeFile(temporary, contents, {
33318
33581
  mode,
33319
33582
  flag: "wx"
33320
33583
  });
33321
- await fs37.rename(temporary, filePath);
33322
- await fs37.chmod(filePath, mode).catch(() => void 0);
33584
+ await fs38.rename(temporary, filePath);
33585
+ await fs38.chmod(filePath, mode).catch(() => void 0);
33323
33586
  } finally {
33324
- await fs37.rm(temporary, { force: true }).catch(() => void 0);
33587
+ await fs38.rm(temporary, { force: true }).catch(() => void 0);
33325
33588
  }
33326
33589
  }
33327
33590
  async function realRegularFile(filePath, executable) {
33328
- const real = await fs37.realpath(filePath);
33329
- const stat5 = await fs37.stat(real);
33591
+ const real = await fs38.realpath(filePath);
33592
+ const stat5 = await fs38.stat(real);
33330
33593
  if (!stat5.isFile()) throw new Error("Companion launch target is not a file");
33331
- await fs37.access(real, executable ? constants2.X_OK : constants2.R_OK);
33594
+ await fs38.access(real, executable ? constants2.X_OK : constants2.R_OK);
33332
33595
  return real;
33333
33596
  }
33334
33597
  function defaultLaunch(executable) {
@@ -33344,7 +33607,7 @@ async function companionIsRunning(platform, executable) {
33344
33607
  if (platform !== "win32") return false;
33345
33608
  let handle;
33346
33609
  try {
33347
- handle = await fs37.open(executable, "r+");
33610
+ handle = await fs38.open(executable, "r+");
33348
33611
  } catch (error) {
33349
33612
  const code = error.code;
33350
33613
  if (code === "ENOENT") return false;
@@ -33425,7 +33688,7 @@ async function statusCompanion(options) {
33425
33688
  realRegularFile(runtime.nodeExecutable, options.platform !== "win32"),
33426
33689
  realRegularFile(runtime.cliEntry, false)
33427
33690
  ]);
33428
- const executable = path41.join(
33691
+ const executable = path42.join(
33429
33692
  paths.targetRoot,
33430
33693
  ...install.entryRelativePath.split("/")
33431
33694
  );
@@ -33458,7 +33721,7 @@ async function snapshotFile(filePath) {
33458
33721
  }
33459
33722
  async function restoreFile(filePath, bytes) {
33460
33723
  if (bytes === null) {
33461
- await fs37.rm(filePath, { force: true });
33724
+ await fs38.rm(filePath, { force: true });
33462
33725
  } else {
33463
33726
  await atomicWriteJson(filePath, JSON.parse(bytes.toString("utf8")));
33464
33727
  }
@@ -33473,8 +33736,8 @@ async function converge(options, force) {
33473
33736
  if (artifactManifest.platform !== options.platform || artifactManifest.architecture !== options.architecture) {
33474
33737
  throw new Error("Companion artifact does not match this OS and architecture");
33475
33738
  }
33476
- const payloadRoot = path41.join(
33477
- path41.dirname(options.artifactManifestPath),
33739
+ const payloadRoot = path42.join(
33740
+ path42.dirname(options.artifactManifestPath),
33478
33741
  "payload"
33479
33742
  );
33480
33743
  await verifyArtifactSource(payloadRoot, artifactManifest);
@@ -33494,7 +33757,7 @@ async function converge(options, force) {
33494
33757
  if (priorInstalled) {
33495
33758
  await assertCompanionNotRunning(
33496
33759
  options,
33497
- path41.join(
33760
+ path42.join(
33498
33761
  priorInstalled.paths.targetRoot,
33499
33762
  ...priorInstalled.install.entryRelativePath.split("/")
33500
33763
  )
@@ -33502,7 +33765,7 @@ async function converge(options, force) {
33502
33765
  }
33503
33766
  if (current.state === "not-installed") {
33504
33767
  try {
33505
- await fs37.lstat(paths.targetRoot);
33768
+ await fs38.lstat(paths.targetRoot);
33506
33769
  throw new Error(
33507
33770
  "The fixed companion target exists without an owned install manifest"
33508
33771
  );
@@ -33513,7 +33776,7 @@ async function converge(options, force) {
33513
33776
  const stage = `${paths.targetRoot}.m8t-stage-${randomUUID3()}`;
33514
33777
  const backup = `${paths.targetRoot}.m8t-backup-${randomUUID3()}`;
33515
33778
  const copy = options.copyPayload ?? copyArtifactPayload;
33516
- await fs37.mkdir(path41.dirname(paths.targetRoot), {
33779
+ await fs38.mkdir(path42.dirname(paths.targetRoot), {
33517
33780
  recursive: true,
33518
33781
  mode: 448
33519
33782
  });
@@ -33530,12 +33793,12 @@ async function converge(options, force) {
33530
33793
  try {
33531
33794
  await copy(payloadRoot, stage, artifactManifest);
33532
33795
  try {
33533
- await fs37.rename(paths.targetRoot, backup);
33796
+ await fs38.rename(paths.targetRoot, backup);
33534
33797
  movedPrior = true;
33535
33798
  } catch (error) {
33536
33799
  if (error.code !== "ENOENT") throw error;
33537
33800
  }
33538
- await fs37.rename(stage, paths.targetRoot);
33801
+ await fs38.rename(stage, paths.targetRoot);
33539
33802
  installedStage = true;
33540
33803
  const nodeExecutable = await realRegularFile(
33541
33804
  options.nodeExecutable,
@@ -33577,7 +33840,7 @@ async function converge(options, force) {
33577
33840
  await readClosedJson(paths.runtimeBinding),
33578
33841
  options.platform
33579
33842
  );
33580
- const executable = path41.join(
33843
+ const executable = path42.join(
33581
33844
  paths.targetRoot,
33582
33845
  ...artifactManifest.entryRelativePath.split("/")
33583
33846
  );
@@ -33587,7 +33850,7 @@ async function converge(options, force) {
33587
33850
  platform: options.platform,
33588
33851
  executable: ownedExecutable
33589
33852
  }));
33590
- priorLoginExecutable = priorInstalled ? path41.join(
33853
+ priorLoginExecutable = priorInstalled ? path42.join(
33591
33854
  priorInstalled.paths.targetRoot,
33592
33855
  ...priorInstalled.install.entryRelativePath.split("/")
33593
33856
  ) : executable;
@@ -33598,7 +33861,7 @@ async function converge(options, force) {
33598
33861
  );
33599
33862
  loginChanged = true;
33600
33863
  await (options.launch ?? defaultLaunch)(executable);
33601
- await fs37.rm(backup, { recursive: true, force: true });
33864
+ await fs38.rm(backup, { recursive: true, force: true });
33602
33865
  return {
33603
33866
  state: "installed",
33604
33867
  version: artifactManifest.version,
@@ -33612,15 +33875,15 @@ async function converge(options, force) {
33612
33875
  () => void 0
33613
33876
  );
33614
33877
  }
33615
- await fs37.rm(stage, { recursive: true, force: true }).catch(() => void 0);
33878
+ await fs38.rm(stage, { recursive: true, force: true }).catch(() => void 0);
33616
33879
  if (installedStage) {
33617
- await fs37.rm(paths.targetRoot, {
33880
+ await fs38.rm(paths.targetRoot, {
33618
33881
  recursive: true,
33619
33882
  force: true
33620
33883
  }).catch(() => void 0);
33621
33884
  }
33622
33885
  if (movedPrior) {
33623
- await fs37.rename(backup, paths.targetRoot).catch(() => void 0);
33886
+ await fs38.rename(backup, paths.targetRoot).catch(() => void 0);
33624
33887
  }
33625
33888
  await Promise.all([
33626
33889
  restoreFile(paths.installManifest, snapshots[0]),
@@ -33640,25 +33903,29 @@ async function uninstallCompanion(options) {
33640
33903
  assertSupported(options);
33641
33904
  const expectedPaths = companionInstallPaths(options);
33642
33905
  await assertOwnedParents(options, expectedPaths);
33906
+ const remove = options.removeOwnedPath ?? (async (ownedPath, recursive) => {
33907
+ await fs38.rm(ownedPath, { recursive, force: true });
33908
+ });
33643
33909
  const installed2 = await readInstalled(options);
33644
- if (!installed2) return { state: "not-installed" };
33910
+ if (!installed2) {
33911
+ await remove(expectedPaths.downloads, true);
33912
+ return { state: "not-installed" };
33913
+ }
33645
33914
  const { paths, install } = installed2;
33646
33915
  if (install.ownedTargetRoot !== paths.targetRoot) {
33647
33916
  throw new Error("Refusing to remove a non-owned companion target");
33648
33917
  }
33649
- const executable = path41.join(
33918
+ const executable = path42.join(
33650
33919
  paths.targetRoot,
33651
33920
  ...install.entryRelativePath.split("/")
33652
33921
  );
33653
33922
  await assertCompanionNotRunning(options, executable);
33654
33923
  await (options.setStartAtLogin ?? (() => Promise.resolve()))(executable, false);
33655
- const remove = options.removeOwnedPath ?? (async (ownedPath, recursive) => {
33656
- await fs37.rm(ownedPath, { recursive, force: true });
33657
- });
33658
33924
  await remove(paths.targetRoot, true);
33659
33925
  await remove(paths.installManifest, false);
33660
33926
  await remove(paths.runtimeBinding, false);
33661
33927
  await remove(paths.preferences, false);
33928
+ await remove(paths.downloads, true);
33662
33929
  return { state: "not-installed" };
33663
33930
  }
33664
33931
 
@@ -33679,84 +33946,55 @@ async function readCompanionRelease(source = { url: CHANNEL_LATEST_URL }, deps =
33679
33946
  };
33680
33947
  }
33681
33948
 
33682
- // src/lib/companion-download.ts
33683
- import { createHash as createHash8 } from "crypto";
33684
- import { execFile as execFile2 } from "child_process";
33685
- import * as fs38 from "fs/promises";
33686
- import * as os18 from "os";
33687
- import * as path42 from "path";
33688
- init_errors();
33689
- var MAX_ASSET_BYTES = 512 * 1024 * 1024;
33690
- function extractArchive(archive, into) {
33691
- return new Promise((resolve6, reject) => {
33692
- execFile2("tar", ["-xzf", archive, "-C", into], { windowsHide: true }, (error) => {
33693
- if (error) reject(new Error(`Could not unpack the companion build: ${error.message}`));
33694
- else resolve6();
33695
- });
33696
- });
33697
- }
33698
- function companionAssetFor(component, platform = process.platform, architecture = process.arch) {
33699
- const target = companionTargetFor(platform, architecture);
33700
- if (target === null) return null;
33701
- const pinned = component.targets[target];
33702
- return pinned ? { target, asset: pinned.asset, sha256: pinned.sha256 } : null;
33703
- }
33704
- async function downloadCompanionArtifact(component, deps = {}) {
33705
- const platform = deps.platform ?? process.platform;
33706
- const architecture = deps.architecture ?? process.arch;
33707
- const pinned = companionAssetFor(component, platform, architecture);
33708
- if (pinned === null) {
33709
- throw new LocalCliError({
33710
- code: "COMPANION_NO_BUILD_FOR_HOST",
33711
- message: `Release ${component.version} carries no desktop companion for ${platform}-${architecture}.`,
33712
- hint: "The companions are built for macOS and Windows on arm64 and x64."
33713
- });
33714
- }
33715
- const url = releaseAssetUrl(component.tag, pinned.asset);
33716
- const fetchImpl = deps.fetchImpl ?? fetch;
33717
- const response = await fetchImpl(url);
33718
- if (!response.ok) {
33719
- throw new LocalCliError({
33720
- code: "COMPANION_ASSET_FETCH_FAILED",
33721
- message: `GET ${url} returned HTTP ${response.status.toString()}.`,
33722
- hint: "The release the platform is pinned to should carry this file. Try again, or run 'm8t companion repair' once the network is back."
33723
- });
33724
- }
33725
- const bytes = Buffer.from(await response.arrayBuffer());
33726
- if (bytes.byteLength > MAX_ASSET_BYTES) {
33727
- throw new LocalCliError({
33728
- code: "COMPANION_ASSET_TOO_LARGE",
33729
- message: `${pinned.asset} is larger than this command will accept.`
33730
- });
33731
- }
33732
- const digest = createHash8("sha256").update(bytes).digest("hex");
33733
- if (digest !== pinned.sha256) {
33734
- throw new LocalCliError({
33735
- code: "COMPANION_ASSET_DIGEST_MISMATCH",
33736
- message: `${pinned.asset} does not match the digest the release pins for it.`,
33737
- hint: "Nothing was unpacked. This is what a corrupted download or a substituted file looks like \u2014 retry, and report it if it persists."
33738
- });
33739
- }
33740
- const root = await (deps.makeTemporaryDirectory ?? (() => fs38.mkdtemp(path42.join(os18.tmpdir(), "m8t-companion-"))))();
33741
- const dispose = async () => {
33742
- await fs38.rm(root, { recursive: true, force: true }).catch(() => void 0);
33949
+ // src/lib/companion-download-progress.ts
33950
+ var STEP_FRACTION = 0.25;
33951
+ var INTERVAL_MS = 3e4;
33952
+ function downloadProgressLines(options) {
33953
+ const now = options.now ?? Date.now;
33954
+ let lastStep = 0;
33955
+ let lastReceived = 0;
33956
+ let lastEmitAt = null;
33957
+ const emit = (line2) => {
33958
+ lastEmitAt = now();
33959
+ options.write(line2);
33960
+ };
33961
+ return (event) => {
33962
+ if (event.kind === "retrying") {
33963
+ emit(
33964
+ event.cause === "not-connected" ? " Couldn't connect \u2014 retrying.\n" : event.cause === "server-error" ? " The download service answered with an error \u2014 retrying.\n" : ` The connection dropped at ${megabytes(event.received)} \u2014 retrying.
33965
+ `
33966
+ );
33967
+ return;
33968
+ }
33969
+ if (event.kind === "resumed") {
33970
+ if (event.total !== null && event.total > 0) {
33971
+ lastStep = Math.floor(event.received / event.total / STEP_FRACTION);
33972
+ }
33973
+ lastReceived = event.received;
33974
+ emit(` Resuming an interrupted download from ${megabytes(event.received)}.
33975
+ `);
33976
+ return;
33977
+ }
33978
+ lastEmitAt ??= now();
33979
+ if (event.received < lastReceived) lastStep = 0;
33980
+ lastReceived = event.received;
33981
+ const total = event.total !== null && event.total > 0 ? event.total : null;
33982
+ if (total === null) {
33983
+ if (now() - lastEmitAt < INTERVAL_MS) return;
33984
+ emit(` ${megabytes(event.received)} downloaded.
33985
+ `);
33986
+ return;
33987
+ }
33988
+ const fraction = event.received / total;
33989
+ if (fraction >= 1) return;
33990
+ const step = Math.floor(fraction / STEP_FRACTION);
33991
+ if (step <= lastStep && now() - lastEmitAt < INTERVAL_MS) return;
33992
+ lastStep = step;
33993
+ emit(
33994
+ ` ${Math.floor(fraction * 100).toString()}% \xB7 ${megabytes(event.received)} of ${megabytes(total)}
33995
+ `
33996
+ );
33743
33997
  };
33744
- try {
33745
- const archive = path42.join(root, pinned.asset);
33746
- await fs38.writeFile(archive, bytes, { mode: 384 });
33747
- const unpacked = path42.join(root, "unpacked");
33748
- await fs38.mkdir(unpacked, { recursive: false, mode: 448 });
33749
- await (deps.extract ?? extractArchive)(archive, unpacked);
33750
- await fs38.rm(archive, { force: true });
33751
- return {
33752
- version: component.version,
33753
- artifactManifestPath: path42.join(unpacked, "artifact-v1.json"),
33754
- dispose
33755
- };
33756
- } catch (error) {
33757
- await dispose();
33758
- throw error;
33759
- }
33760
33998
  }
33761
33999
 
33762
34000
  // src/commands/companion/install.ts
@@ -33777,7 +34015,7 @@ function defaultLocalCompanionInstallOptions() {
33777
34015
  };
33778
34016
  }
33779
34017
  async function convergeCompanionFromChannel(converge2, options = {}) {
33780
- const { from: stagedDirectory, resourceGroup, platformVersion } = options;
34018
+ const { from: stagedDirectory, resourceGroup, platformVersion, onDownloadEvent } = options;
33781
34019
  let artifactManifestPath;
33782
34020
  let dispose = () => Promise.resolve();
33783
34021
  if (stagedDirectory !== void 0) {
@@ -33787,7 +34025,9 @@ async function convergeCompanionFromChannel(converge2, options = {}) {
33787
34025
  platformVersion !== void 0 ? { channel: true, version: platformTag(platformVersion) } : { url: CHANNEL_LATEST_URL }
33788
34026
  );
33789
34027
  if (release === null) return { state: "not-released" };
33790
- const build = await downloadCompanionArtifact(release.component);
34028
+ const build = await downloadCompanionArtifact(release.component, {
34029
+ ...onDownloadEvent !== void 0 ? { onEvent: onDownloadEvent } : {}
34030
+ });
33791
34031
  artifactManifestPath = build.artifactManifestPath;
33792
34032
  dispose = build.dispose;
33793
34033
  }
@@ -33840,11 +34080,13 @@ var CompanionInstallCommand = class extends M8tCommand {
33840
34080
  description: "Which deployment to bind to, when the subscription holds more than one."
33841
34081
  });
33842
34082
  async executeCommand() {
34083
+ const stdout = this.context.stdout;
33843
34084
  return runCompanionInstallCommand(
33844
- this.context.stdout,
34085
+ stdout,
33845
34086
  () => convergeCompanionFromChannel(installCompanion, {
33846
34087
  ...this.from !== void 0 ? { from: this.from } : {},
33847
- ...this.resourceGroup !== void 0 ? { resourceGroup: this.resourceGroup } : {}
34088
+ ...this.resourceGroup !== void 0 ? { resourceGroup: this.resourceGroup } : {},
34089
+ onDownloadEvent: downloadProgressLines({ write: (line2) => stdout.write(line2) })
33848
34090
  })
33849
34091
  );
33850
34092
  }
@@ -33864,7 +34106,7 @@ async function looksLikeCheckout(dir2) {
33864
34106
  return false;
33865
34107
  }
33866
34108
  }
33867
- var defaultDeps3 = {
34109
+ var defaultFinalizeDeps = Object.freeze({
33868
34110
  discoverGateway,
33869
34111
  writeConfig,
33870
34112
  ensureGatewayRedirectUri,
@@ -33872,12 +34114,13 @@ var defaultDeps3 = {
33872
34114
  reactiveSeed: reactiveSeedOnInstallComplete,
33873
34115
  companionsSupportHost,
33874
34116
  companionStatus: () => statusCompanion(defaultLocalCompanionInstallOptions()),
33875
- convergeCompanion: (platformVersion) => convergeCompanionFromChannel(installCompanion, {
33876
- ...platformVersion !== void 0 ? { platformVersion } : {}
34117
+ convergeCompanion: (platformVersion, onDownloadEvent) => convergeCompanionFromChannel(installCompanion, {
34118
+ ...platformVersion !== void 0 ? { platformVersion } : {},
34119
+ ...onDownloadEvent !== void 0 ? { onDownloadEvent } : {}
33877
34120
  }),
33878
34121
  homedir: () => os20.homedir()
33879
- };
33880
- async function finalizeInstall(args, deps = defaultDeps3) {
34122
+ });
34123
+ async function finalizeInstall(args, deps = defaultFinalizeDeps) {
33881
34124
  const markerDir = path44.join(deps.homedir(), ".m8t");
33882
34125
  const markerPath = path44.join(markerDir, "repo-root");
33883
34126
  const cwd = process.cwd();
@@ -34015,7 +34258,10 @@ ${colors.field("Then open a brand new chat/session")} \u2014 new skills + MCP se
34015
34258
  `
34016
34259
  );
34017
34260
  try {
34018
- const companion = await deps.convergeCompanion(pinned);
34261
+ const companion = await deps.convergeCompanion(
34262
+ pinned,
34263
+ downloadProgressLines({ write: args.stdout })
34264
+ );
34019
34265
  if (companion.state === "installed") {
34020
34266
  args.stdout("Desktop companions installed - they are at the edge of your screen.\n\n");
34021
34267
  } else if (companion.state === "not-released") {
@@ -34031,7 +34277,7 @@ They arrive with a platform release \u2014 run: m8t platform update
34031
34277
  );
34032
34278
  }
34033
34279
  } catch (e) {
34034
- const reason = e instanceof Error ? e.message : String(e);
34280
+ const reason = e instanceof LocalCliError && e.code === "COMPANION_DOWNLOAD_INCOMPLETE" && e.hint !== void 0 ? e.hint : e instanceof Error ? e.message : String(e);
34035
34281
  args.stdout(
34036
34282
  `Platform installation succeeded, but the desktop companions need repair.
34037
34283
  Run: m8t companion repair
@@ -34680,9 +34926,9 @@ async function openChatInvite(deps = {}) {
34680
34926
  if (!invite.ok) return CHAT_UNAVAILABLE_LINE;
34681
34927
  const print = deps.print === true;
34682
34928
  if (!print) {
34683
- const open3 = deps.open ?? tryOpenUrl;
34929
+ const open4 = deps.open ?? tryOpenUrl;
34684
34930
  try {
34685
- await open3(invite.url);
34931
+ await open4(invite.url);
34686
34932
  } catch {
34687
34933
  }
34688
34934
  }
@@ -36449,7 +36695,7 @@ async function runCompanionBridgeServe(stdin, stdout, stderr, deps = {}) {
36449
36695
  }
36450
36696
 
36451
36697
  // src/commands/companion/bridge.ts
36452
- var defaultDeps4 = {
36698
+ var defaultDeps3 = {
36453
36699
  roster: rosterCompanions,
36454
36700
  send: sendCompanionMessage,
36455
36701
  converse: converseCompanionMessage,
@@ -36487,7 +36733,7 @@ function exitFor(terminal) {
36487
36733
  }
36488
36734
  return 2;
36489
36735
  }
36490
- async function runCompanionBridge(stdin, stdout, stderr, deps = defaultDeps4) {
36736
+ async function runCompanionBridge(stdin, stdout, stderr, deps = defaultDeps3) {
36491
36737
  let request;
36492
36738
  try {
36493
36739
  request = parseRequestLine(await readSingleRequest(stdin));
@@ -36659,10 +36905,14 @@ var CompanionRepairCommand = class extends M8tCommand {
36659
36905
  description: "Which deployment to bind to, when the subscription holds more than one."
36660
36906
  });
36661
36907
  async executeCommand() {
36908
+ const stdout = this.context.stdout;
36662
36909
  return runCompanionRepairCommand(
36663
- this.context.stdout,
36910
+ stdout,
36664
36911
  () => convergeCompanionFromChannel(repairCompanion, {
36665
- ...this.resourceGroup !== void 0 ? { resourceGroup: this.resourceGroup } : {}
36912
+ ...this.resourceGroup !== void 0 ? { resourceGroup: this.resourceGroup } : {},
36913
+ // Repair is the command a founder reaches for after an interrupted
36914
+ // install, so it is the one most likely to pick up a partial download.
36915
+ onDownloadEvent: downloadProgressLines({ write: (line2) => stdout.write(line2) })
36666
36916
  })
36667
36917
  );
36668
36918
  }