@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 +411 -161
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
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
|
|
925
|
-
for (let attempt = 1; 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 <
|
|
957
|
-
const suffix = retryable ? ` after ${
|
|
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.
|
|
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
|
|
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: ${
|
|
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
|
|
32739
|
-
import * as
|
|
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("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
@@ -33028,7 +33288,7 @@ function commandError(error, message) {
|
|
|
33028
33288
|
}
|
|
33029
33289
|
function execFileAsync(file2, args) {
|
|
33030
33290
|
return new Promise((resolve6, reject) => {
|
|
33031
|
-
|
|
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
|
-
|
|
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 =
|
|
33053
|
-
const registration =
|
|
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
|
|
33319
|
+
await fs38.rm(registration, { force: true });
|
|
33060
33320
|
return;
|
|
33061
33321
|
}
|
|
33062
|
-
await
|
|
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 =
|
|
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 =
|
|
33436
|
+
const companionState = path42.join(
|
|
33177
33437
|
options.homeDirectory,
|
|
33178
33438
|
".m8t",
|
|
33179
33439
|
"companion"
|
|
33180
33440
|
);
|
|
33181
|
-
const targetRoot = options.platform === "darwin" ?
|
|
33441
|
+
const targetRoot = options.platform === "darwin" ? path42.join(
|
|
33182
33442
|
options.homeDirectory,
|
|
33183
33443
|
"Applications",
|
|
33184
33444
|
"m8t Companion.app"
|
|
33185
|
-
) :
|
|
33186
|
-
options.localAppData ??
|
|
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:
|
|
33194
|
-
runtimeBinding:
|
|
33195
|
-
preferences:
|
|
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" || !
|
|
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" ?
|
|
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
|
|
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 =
|
|
33251
|
-
if (relative4 === ".." || relative4.startsWith(`..${
|
|
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(
|
|
33517
|
+
const segments = relative4.split(path42.sep).filter(Boolean);
|
|
33255
33518
|
let current = anchor;
|
|
33256
|
-
const anchorStatus = await
|
|
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 =
|
|
33524
|
+
current = path42.join(current, segment);
|
|
33262
33525
|
try {
|
|
33263
|
-
const status = await
|
|
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
|
-
|
|
33539
|
+
path42.dirname(paths.installManifest)
|
|
33277
33540
|
);
|
|
33278
|
-
const targetAnchor = options.platform === "win32" ? options.localAppData ??
|
|
33279
|
-
await assertOwnedDirectoryChain(targetAnchor,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
33580
|
+
await fs38.writeFile(temporary, contents, {
|
|
33318
33581
|
mode,
|
|
33319
33582
|
flag: "wx"
|
|
33320
33583
|
});
|
|
33321
|
-
await
|
|
33322
|
-
await
|
|
33584
|
+
await fs38.rename(temporary, filePath);
|
|
33585
|
+
await fs38.chmod(filePath, mode).catch(() => void 0);
|
|
33323
33586
|
} finally {
|
|
33324
|
-
await
|
|
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
|
|
33329
|
-
const stat5 = await
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
33477
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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 ?
|
|
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
|
|
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
|
|
33878
|
+
await fs38.rm(stage, { recursive: true, force: true }).catch(() => void 0);
|
|
33616
33879
|
if (installedStage) {
|
|
33617
|
-
await
|
|
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
|
|
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)
|
|
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 =
|
|
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
|
-
|
|
33684
|
-
|
|
33685
|
-
|
|
33686
|
-
|
|
33687
|
-
|
|
33688
|
-
|
|
33689
|
-
|
|
33690
|
-
|
|
33691
|
-
|
|
33692
|
-
|
|
33693
|
-
|
|
33694
|
-
|
|
33695
|
-
|
|
33696
|
-
|
|
33697
|
-
}
|
|
33698
|
-
|
|
33699
|
-
|
|
33700
|
-
|
|
33701
|
-
|
|
33702
|
-
|
|
33703
|
-
|
|
33704
|
-
|
|
33705
|
-
|
|
33706
|
-
|
|
33707
|
-
|
|
33708
|
-
|
|
33709
|
-
|
|
33710
|
-
|
|
33711
|
-
|
|
33712
|
-
|
|
33713
|
-
|
|
33714
|
-
|
|
33715
|
-
|
|
33716
|
-
|
|
33717
|
-
|
|
33718
|
-
|
|
33719
|
-
|
|
33720
|
-
|
|
33721
|
-
|
|
33722
|
-
|
|
33723
|
-
|
|
33724
|
-
|
|
33725
|
-
|
|
33726
|
-
|
|
33727
|
-
|
|
33728
|
-
|
|
33729
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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(
|
|
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
|
|
34929
|
+
const open4 = deps.open ?? tryOpenUrl;
|
|
34684
34930
|
try {
|
|
34685
|
-
await
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
}
|