@m8t-stack/cli 0.2.53 → 0.2.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -0
- package/dist/cli.js +2289 -381
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1364,7 +1364,7 @@ var init_enable_hosted_brain = __esm({
|
|
|
1364
1364
|
import { Builtins, Cli } from "clipanion";
|
|
1365
1365
|
|
|
1366
1366
|
// src/lib/package-version.ts
|
|
1367
|
-
var CLI_VERSION = "0.2.
|
|
1367
|
+
var CLI_VERSION = "0.2.54";
|
|
1368
1368
|
|
|
1369
1369
|
// src/lib/render-error.ts
|
|
1370
1370
|
init_errors();
|
|
@@ -1911,7 +1911,7 @@ var safeJSON = (text) => {
|
|
|
1911
1911
|
};
|
|
1912
1912
|
|
|
1913
1913
|
// ../../node_modules/.pnpm/openai@6.48.0_ws@8.21.1_zod@4.4.3/node_modules/openai/internal/utils/sleep.mjs
|
|
1914
|
-
var sleep = (ms) => new Promise((
|
|
1914
|
+
var sleep = (ms) => new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
1915
1915
|
|
|
1916
1916
|
// ../../node_modules/.pnpm/openai@6.48.0_ws@8.21.1_zod@4.4.3/node_modules/openai/version.mjs
|
|
1917
1917
|
var VERSION = "6.48.0";
|
|
@@ -3022,8 +3022,8 @@ function addRequestID(value, response) {
|
|
|
3022
3022
|
var _APIPromise_client;
|
|
3023
3023
|
var APIPromise = class _APIPromise extends Promise {
|
|
3024
3024
|
constructor(client, responsePromise, parseResponse2 = defaultParseResponse) {
|
|
3025
|
-
super((
|
|
3026
|
-
|
|
3025
|
+
super((resolve4) => {
|
|
3026
|
+
resolve4(null);
|
|
3027
3027
|
});
|
|
3028
3028
|
this.responsePromise = responsePromise;
|
|
3029
3029
|
this.parseResponse = parseResponse2;
|
|
@@ -3698,12 +3698,12 @@ function encodeURIPath(str4) {
|
|
|
3698
3698
|
return str4.replace(/[^A-Za-z0-9\-._~!$&'()*+,;=:@]+/g, encodeURIComponent);
|
|
3699
3699
|
}
|
|
3700
3700
|
var EMPTY = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.create(null));
|
|
3701
|
-
var createPathTagFunction = (pathEncoder = encodeURIPath) => function
|
|
3701
|
+
var createPathTagFunction = (pathEncoder = encodeURIPath) => function path42(statics, ...params) {
|
|
3702
3702
|
if (statics.length === 1)
|
|
3703
3703
|
return statics[0];
|
|
3704
3704
|
let postPath = false;
|
|
3705
3705
|
const invalidSegments = [];
|
|
3706
|
-
const
|
|
3706
|
+
const path43 = statics.reduce((previousValue, currentValue, index) => {
|
|
3707
3707
|
if (/[?#]/.test(currentValue)) {
|
|
3708
3708
|
postPath = true;
|
|
3709
3709
|
}
|
|
@@ -3720,7 +3720,7 @@ var createPathTagFunction = (pathEncoder = encodeURIPath) => function path38(sta
|
|
|
3720
3720
|
}
|
|
3721
3721
|
return previousValue + currentValue + (index === params.length ? "" : encoded);
|
|
3722
3722
|
}, "");
|
|
3723
|
-
const pathOnly =
|
|
3723
|
+
const pathOnly = path43.split(/[?#]/, 1)[0];
|
|
3724
3724
|
const invalidSegmentPattern = /(?<=^|\/)(?:\.|%2e){1,2}(?=\/|$)/gi;
|
|
3725
3725
|
let match;
|
|
3726
3726
|
while ((match = invalidSegmentPattern.exec(pathOnly)) !== null) {
|
|
@@ -3741,10 +3741,10 @@ var createPathTagFunction = (pathEncoder = encodeURIPath) => function path38(sta
|
|
|
3741
3741
|
}, "");
|
|
3742
3742
|
throw new OpenAIError(`Path parameters result in path with invalid segments:
|
|
3743
3743
|
${invalidSegments.map((e) => e.error).join("\n")}
|
|
3744
|
-
${
|
|
3744
|
+
${path43}
|
|
3745
3745
|
${underline}`);
|
|
3746
3746
|
}
|
|
3747
|
-
return
|
|
3747
|
+
return path43;
|
|
3748
3748
|
};
|
|
3749
3749
|
var path = /* @__PURE__ */ createPathTagFunction(encodeURIPath);
|
|
3750
3750
|
|
|
@@ -3920,12 +3920,12 @@ var EventStream = class {
|
|
|
3920
3920
|
_EventStream_errored.set(this, false);
|
|
3921
3921
|
_EventStream_aborted.set(this, false);
|
|
3922
3922
|
_EventStream_catchingPromiseCreated.set(this, false);
|
|
3923
|
-
__classPrivateFieldSet(this, _EventStream_connectedPromise, new Promise((
|
|
3924
|
-
__classPrivateFieldSet(this, _EventStream_resolveConnectedPromise,
|
|
3923
|
+
__classPrivateFieldSet(this, _EventStream_connectedPromise, new Promise((resolve4, reject) => {
|
|
3924
|
+
__classPrivateFieldSet(this, _EventStream_resolveConnectedPromise, resolve4, "f");
|
|
3925
3925
|
__classPrivateFieldSet(this, _EventStream_rejectConnectedPromise, reject, "f");
|
|
3926
3926
|
}), "f");
|
|
3927
|
-
__classPrivateFieldSet(this, _EventStream_endPromise, new Promise((
|
|
3928
|
-
__classPrivateFieldSet(this, _EventStream_resolveEndPromise,
|
|
3927
|
+
__classPrivateFieldSet(this, _EventStream_endPromise, new Promise((resolve4, reject) => {
|
|
3928
|
+
__classPrivateFieldSet(this, _EventStream_resolveEndPromise, resolve4, "f");
|
|
3929
3929
|
__classPrivateFieldSet(this, _EventStream_rejectEndPromise, reject, "f");
|
|
3930
3930
|
}), "f");
|
|
3931
3931
|
__classPrivateFieldGet(this, _EventStream_connectedPromise, "f").catch(() => {
|
|
@@ -4025,11 +4025,11 @@ var EventStream = class {
|
|
|
4025
4025
|
* const message = await stream.emitted('message') // rejects if the stream errors
|
|
4026
4026
|
*/
|
|
4027
4027
|
emitted(event) {
|
|
4028
|
-
return new Promise((
|
|
4028
|
+
return new Promise((resolve4, reject) => {
|
|
4029
4029
|
__classPrivateFieldSet(this, _EventStream_catchingPromiseCreated, true, "f");
|
|
4030
4030
|
if (event !== "error")
|
|
4031
4031
|
this.once("error", reject);
|
|
4032
|
-
this.once(event,
|
|
4032
|
+
this.once(event, resolve4);
|
|
4033
4033
|
});
|
|
4034
4034
|
}
|
|
4035
4035
|
/**
|
|
@@ -4112,8 +4112,8 @@ var EventStream = class {
|
|
|
4112
4112
|
}
|
|
4113
4113
|
if (ended)
|
|
4114
4114
|
return Promise.resolve(doneResult());
|
|
4115
|
-
return new Promise((
|
|
4116
|
-
readQueue.push({ resolve:
|
|
4115
|
+
return new Promise((resolve4, reject) => {
|
|
4116
|
+
readQueue.push({ resolve: resolve4, reject });
|
|
4117
4117
|
});
|
|
4118
4118
|
},
|
|
4119
4119
|
return: () => {
|
|
@@ -5144,7 +5144,7 @@ var ChatCompletionStream = class _ChatCompletionStream extends AbstractChatCompl
|
|
|
5144
5144
|
if (done) {
|
|
5145
5145
|
return { value: void 0, done: true };
|
|
5146
5146
|
}
|
|
5147
|
-
return new Promise((
|
|
5147
|
+
return new Promise((resolve4, reject) => readQueue.push({ resolve: resolve4, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
|
|
5148
5148
|
}
|
|
5149
5149
|
const chunk = pushQueue.shift();
|
|
5150
5150
|
return { value: chunk, done: false };
|
|
@@ -5319,7 +5319,7 @@ var ChatCompletionStreamingRunner = class _ChatCompletionStreamingRunner extends
|
|
|
5319
5319
|
if (done) {
|
|
5320
5320
|
return { value: void 0, done: true };
|
|
5321
5321
|
}
|
|
5322
|
-
return new Promise((
|
|
5322
|
+
return new Promise((resolve4, reject) => readQueue.push({ resolve: resolve4, reject })).then((event2) => event2 ? { value: event2, done: false } : { value: void 0, done: true });
|
|
5323
5323
|
}
|
|
5324
5324
|
const event = pushQueue.shift();
|
|
5325
5325
|
if (!event) {
|
|
@@ -8482,7 +8482,7 @@ var AssistantStream = class extends EventStream {
|
|
|
8482
8482
|
if (done) {
|
|
8483
8483
|
return { value: void 0, done: true };
|
|
8484
8484
|
}
|
|
8485
|
-
return new Promise((
|
|
8485
|
+
return new Promise((resolve4, reject) => readQueue.push({ resolve: resolve4, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
|
|
8486
8486
|
}
|
|
8487
8487
|
const chunk = pushQueue.shift();
|
|
8488
8488
|
return { value: chunk, done: false };
|
|
@@ -10862,7 +10862,7 @@ var ResponseStream = class _ResponseStream extends EventStream {
|
|
|
10862
10862
|
if (done) {
|
|
10863
10863
|
return { value: void 0, done: true };
|
|
10864
10864
|
}
|
|
10865
|
-
return new Promise((
|
|
10865
|
+
return new Promise((resolve4, reject) => readQueue.push({ resolve: resolve4, reject })).then((event2) => event2 ? { value: event2, done: false } : { value: void 0, done: true });
|
|
10866
10866
|
}
|
|
10867
10867
|
const event = pushQueue.shift();
|
|
10868
10868
|
return { value: event, done: false };
|
|
@@ -12012,9 +12012,9 @@ var OpenAI = class {
|
|
|
12012
12012
|
this.apiKey = token;
|
|
12013
12013
|
return true;
|
|
12014
12014
|
}
|
|
12015
|
-
buildURL(
|
|
12015
|
+
buildURL(path42, query, defaultBaseURL) {
|
|
12016
12016
|
const baseURL = !__classPrivateFieldGet(this, _OpenAI_instances, "m", _OpenAI_baseURLOverridden).call(this) && defaultBaseURL || this.baseURL;
|
|
12017
|
-
const url = isAbsoluteURL(
|
|
12017
|
+
const url = isAbsoluteURL(path42) ? new URL(path42) : new URL(baseURL + (baseURL.endsWith("/") && path42.startsWith("/") ? path42.slice(1) : path42));
|
|
12018
12018
|
const defaultQuery = this.defaultQuery();
|
|
12019
12019
|
const pathQuery = Object.fromEntries(url.searchParams);
|
|
12020
12020
|
if (!isEmptyObj(defaultQuery) || !isEmptyObj(pathQuery)) {
|
|
@@ -12044,24 +12044,24 @@ var OpenAI = class {
|
|
|
12044
12044
|
*/
|
|
12045
12045
|
async prepareRequest(request, { url, options }) {
|
|
12046
12046
|
}
|
|
12047
|
-
get(
|
|
12048
|
-
return this.methodRequest("get",
|
|
12047
|
+
get(path42, opts) {
|
|
12048
|
+
return this.methodRequest("get", path42, opts);
|
|
12049
12049
|
}
|
|
12050
|
-
post(
|
|
12051
|
-
return this.methodRequest("post",
|
|
12050
|
+
post(path42, opts) {
|
|
12051
|
+
return this.methodRequest("post", path42, opts);
|
|
12052
12052
|
}
|
|
12053
|
-
patch(
|
|
12054
|
-
return this.methodRequest("patch",
|
|
12053
|
+
patch(path42, opts) {
|
|
12054
|
+
return this.methodRequest("patch", path42, opts);
|
|
12055
12055
|
}
|
|
12056
|
-
put(
|
|
12057
|
-
return this.methodRequest("put",
|
|
12056
|
+
put(path42, opts) {
|
|
12057
|
+
return this.methodRequest("put", path42, opts);
|
|
12058
12058
|
}
|
|
12059
|
-
delete(
|
|
12060
|
-
return this.methodRequest("delete",
|
|
12059
|
+
delete(path42, opts) {
|
|
12060
|
+
return this.methodRequest("delete", path42, opts);
|
|
12061
12061
|
}
|
|
12062
|
-
methodRequest(method,
|
|
12062
|
+
methodRequest(method, path42, opts) {
|
|
12063
12063
|
return this.request(Promise.resolve(opts).then((opts2) => {
|
|
12064
|
-
return { method, path:
|
|
12064
|
+
return { method, path: path42, ...opts2 };
|
|
12065
12065
|
}));
|
|
12066
12066
|
}
|
|
12067
12067
|
request(options, remainingRetries = null) {
|
|
@@ -12186,8 +12186,8 @@ var OpenAI = class {
|
|
|
12186
12186
|
}));
|
|
12187
12187
|
return { response, options, controller, requestLogID, retryOfRequestLogID, startTime };
|
|
12188
12188
|
}
|
|
12189
|
-
getAPIList(
|
|
12190
|
-
return this.requestAPIList(Page2, opts && "then" in opts ? opts.then((opts2) => ({ method: "get", path:
|
|
12189
|
+
getAPIList(path42, Page2, opts) {
|
|
12190
|
+
return this.requestAPIList(Page2, opts && "then" in opts ? opts.then((opts2) => ({ method: "get", path: path42, ...opts2 })) : { method: "get", path: path42, ...opts });
|
|
12191
12191
|
}
|
|
12192
12192
|
requestAPIList(Page2, options) {
|
|
12193
12193
|
const request = this.makeRequest(options, null, void 0);
|
|
@@ -12281,8 +12281,8 @@ var OpenAI = class {
|
|
|
12281
12281
|
}
|
|
12282
12282
|
async buildRequest(inputOptions, { retryCount = 0 } = {}) {
|
|
12283
12283
|
const options = { ...inputOptions };
|
|
12284
|
-
const { method, path:
|
|
12285
|
-
const url = this.buildURL(
|
|
12284
|
+
const { method, path: path42, query, defaultBaseURL } = options;
|
|
12285
|
+
const url = this.buildURL(path42, query, defaultBaseURL);
|
|
12286
12286
|
if ("timeout" in options)
|
|
12287
12287
|
validatePositiveInteger("timeout", options.timeout);
|
|
12288
12288
|
options.timeout = options.timeout ?? this.timeout;
|
|
@@ -12647,7 +12647,7 @@ function redactAzArgs(args) {
|
|
|
12647
12647
|
return out;
|
|
12648
12648
|
}
|
|
12649
12649
|
function runAz(args) {
|
|
12650
|
-
return new Promise((
|
|
12650
|
+
return new Promise((resolve4, reject) => {
|
|
12651
12651
|
const proc = spawn("az", args, { stdio: ["ignore", "pipe", "pipe"], shell: process.platform === "win32" });
|
|
12652
12652
|
const out = [];
|
|
12653
12653
|
const err = [];
|
|
@@ -12687,7 +12687,7 @@ function runAz(args) {
|
|
|
12687
12687
|
);
|
|
12688
12688
|
return;
|
|
12689
12689
|
}
|
|
12690
|
-
|
|
12690
|
+
resolve4(Buffer.concat(out).toString("utf8"));
|
|
12691
12691
|
});
|
|
12692
12692
|
});
|
|
12693
12693
|
}
|
|
@@ -13325,10 +13325,10 @@ var BindListCommand = class extends M8tCommand {
|
|
|
13325
13325
|
const qs = new URLSearchParams();
|
|
13326
13326
|
if (this.channel) qs.set("channel", this.channel);
|
|
13327
13327
|
if (this.status) qs.set("status", this.status);
|
|
13328
|
-
const
|
|
13328
|
+
const path42 = qs.toString().length > 0 ? `/api/bindings?${qs.toString()}` : "/api/bindings";
|
|
13329
13329
|
const data = await apiCall(
|
|
13330
13330
|
{ gatewayUrl: ctx.gatewayUrl, gatewayClientId: ctx.gatewayClientId },
|
|
13331
|
-
{ method: "GET", path:
|
|
13331
|
+
{ method: "GET", path: path42 },
|
|
13332
13332
|
(msg) => this.context.stderr.write(msg + "\n")
|
|
13333
13333
|
);
|
|
13334
13334
|
if (mode === "json") {
|
|
@@ -14242,13 +14242,13 @@ async function createGitHubApp(args) {
|
|
|
14242
14242
|
const setupUrl = `${base}/setup`;
|
|
14243
14243
|
let createdApp = null;
|
|
14244
14244
|
let polledInstallationId = null;
|
|
14245
|
-
return new Promise((
|
|
14245
|
+
return new Promise((resolve4, reject) => {
|
|
14246
14246
|
let settled = false;
|
|
14247
14247
|
let server;
|
|
14248
14248
|
const complete = (installationId) => {
|
|
14249
14249
|
if (settled || !createdApp) return;
|
|
14250
14250
|
settled = true;
|
|
14251
|
-
|
|
14251
|
+
resolve4({ app: createdApp, installationId });
|
|
14252
14252
|
setTimeout(() => {
|
|
14253
14253
|
server.close();
|
|
14254
14254
|
}, 2500);
|
|
@@ -14362,7 +14362,7 @@ async function findValidBrainApp(credsPath, org, deps = {}) {
|
|
|
14362
14362
|
// src/lib/gh-helpers.ts
|
|
14363
14363
|
init_errors();
|
|
14364
14364
|
import { spawn as spawn2 } from "child_process";
|
|
14365
|
-
var defaultGhExec = (cmd, args) => new Promise((
|
|
14365
|
+
var defaultGhExec = (cmd, args) => new Promise((resolve4) => {
|
|
14366
14366
|
const child = spawn2(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
14367
14367
|
let stdout = "";
|
|
14368
14368
|
let stderr = "";
|
|
@@ -14370,7 +14370,7 @@ var defaultGhExec = (cmd, args) => new Promise((resolve3) => {
|
|
|
14370
14370
|
const done = (r) => {
|
|
14371
14371
|
if (!settled) {
|
|
14372
14372
|
settled = true;
|
|
14373
|
-
|
|
14373
|
+
resolve4(r);
|
|
14374
14374
|
}
|
|
14375
14375
|
};
|
|
14376
14376
|
child.stdout.on("data", (d) => {
|
|
@@ -14539,8 +14539,8 @@ ${r.stderr}`;
|
|
|
14539
14539
|
message: `gh api /repos/${slug} failed (exit ${r.exitCode.toString()}): ${(r.stderr || r.stdout).slice(0, 200)}`
|
|
14540
14540
|
});
|
|
14541
14541
|
},
|
|
14542
|
-
getFile: async (
|
|
14543
|
-
const r = await exec("gh", ["api", "-H", "Accept: application/vnd.github.raw+json", `/repos/${slug}/contents/${
|
|
14542
|
+
getFile: async (path42) => {
|
|
14543
|
+
const r = await exec("gh", ["api", "-H", "Accept: application/vnd.github.raw+json", `/repos/${slug}/contents/${path42}`]);
|
|
14544
14544
|
return r.exitCode === 0 ? r.stdout : null;
|
|
14545
14545
|
},
|
|
14546
14546
|
isEmpty: async () => {
|
|
@@ -15407,7 +15407,7 @@ async function createBlankRepo(args) {
|
|
|
15407
15407
|
return { cloneUrl: j.clone_url, defaultBranch: j.default_branch ?? "main" };
|
|
15408
15408
|
}
|
|
15409
15409
|
function run(cmd, cmdArgs, cwd, env) {
|
|
15410
|
-
return new Promise((
|
|
15410
|
+
return new Promise((resolve4, reject) => {
|
|
15411
15411
|
const c = spawn3(cmd, cmdArgs, { cwd, env: { ...process.env, ...env }, stdio: ["ignore", "pipe", "pipe"] });
|
|
15412
15412
|
let err = "";
|
|
15413
15413
|
c.stderr.on("data", (d) => {
|
|
@@ -15423,7 +15423,7 @@ function run(cmd, cmdArgs, cwd, env) {
|
|
|
15423
15423
|
});
|
|
15424
15424
|
c.on("close", (code) => {
|
|
15425
15425
|
if (code === 0) {
|
|
15426
|
-
|
|
15426
|
+
resolve4();
|
|
15427
15427
|
} else {
|
|
15428
15428
|
reject(
|
|
15429
15429
|
new LocalCliError({
|
|
@@ -15607,7 +15607,7 @@ function appRepoProbe(args) {
|
|
|
15607
15607
|
const res = await doFetch(`${GH_API}/repos/${args.repo}`, { headers: H, signal: AbortSignal.timeout(GH_REQUEST_TIMEOUT_MS) });
|
|
15608
15608
|
return res.status;
|
|
15609
15609
|
},
|
|
15610
|
-
getFile: (
|
|
15610
|
+
getFile: (path42) => readRepoFileViaApp({ token: args.token, repo: args.repo, path: path42, fetchImpl: args.fetchImpl }),
|
|
15611
15611
|
isEmpty: async () => {
|
|
15612
15612
|
const res = await doFetch(`${GH_API}/repos/${args.repo}/contents`, { headers: H, signal: AbortSignal.timeout(GH_REQUEST_TIMEOUT_MS) });
|
|
15613
15613
|
return res.status === 404;
|
|
@@ -19008,8 +19008,8 @@ async function fetchPublicTags(repo, fetchImpl = fetch) {
|
|
|
19008
19008
|
hint: "Pass a ghcr.io/<owner>/<name> repo, or use the BYOC private-ACR deploy flow."
|
|
19009
19009
|
});
|
|
19010
19010
|
}
|
|
19011
|
-
const
|
|
19012
|
-
const tokenRes = await fetchImpl(`https://ghcr.io/token?scope=repository:${
|
|
19011
|
+
const path42 = repo.replace(/^ghcr\.io\//, "");
|
|
19012
|
+
const tokenRes = await fetchImpl(`https://ghcr.io/token?scope=repository:${path42}:pull`);
|
|
19013
19013
|
if (!tokenRes.ok) {
|
|
19014
19014
|
throw new LocalCliError({
|
|
19015
19015
|
code: "PLATFORM_GHCR_TOKEN_FAILED",
|
|
@@ -19025,7 +19025,7 @@ async function fetchPublicTags(repo, fetchImpl = fetch) {
|
|
|
19025
19025
|
hint: "Make the package public (deploy/ghcr-package-visibility.md), then retry."
|
|
19026
19026
|
});
|
|
19027
19027
|
}
|
|
19028
|
-
const tagsRes = await fetchImpl(`https://ghcr.io/v2/${
|
|
19028
|
+
const tagsRes = await fetchImpl(`https://ghcr.io/v2/${path42}/tags/list`, {
|
|
19029
19029
|
headers: { Authorization: `Bearer ${token}` }
|
|
19030
19030
|
});
|
|
19031
19031
|
if (!tagsRes.ok) {
|
|
@@ -19044,6 +19044,16 @@ import * as fs19 from "fs";
|
|
|
19044
19044
|
// ../../packages/platform-release/dist/esm/manifest.js
|
|
19045
19045
|
var SEVERITIES = ["critical", "recommended", "optional"];
|
|
19046
19046
|
var IMAGE_KEYS = ["gateway", "codingAgent", "azureExecutor", "installer"];
|
|
19047
|
+
var COMPANION_TARGETS = [
|
|
19048
|
+
"darwin-arm64",
|
|
19049
|
+
"darwin-x64",
|
|
19050
|
+
"win32-arm64",
|
|
19051
|
+
"win32-x64"
|
|
19052
|
+
];
|
|
19053
|
+
function companionTargetFor(platform, architecture) {
|
|
19054
|
+
const target = `${platform}-${architecture}`;
|
|
19055
|
+
return COMPANION_TARGETS.includes(target) ? target : null;
|
|
19056
|
+
}
|
|
19047
19057
|
function isObj2(v) {
|
|
19048
19058
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
19049
19059
|
}
|
|
@@ -19132,6 +19142,34 @@ function validateManifest(m) {
|
|
|
19132
19142
|
errors.push("components.infra.treeSha is required");
|
|
19133
19143
|
}
|
|
19134
19144
|
}
|
|
19145
|
+
const companion = c.companion;
|
|
19146
|
+
if (companion !== void 0) {
|
|
19147
|
+
if (!isObj2(companion) || companion.kind !== "desktop") {
|
|
19148
|
+
errors.push('components.companion.kind must be "desktop"');
|
|
19149
|
+
} else {
|
|
19150
|
+
for (const f of ["version", "tag"]) {
|
|
19151
|
+
if (typeof companion[f] !== "string" || companion[f].length === 0) {
|
|
19152
|
+
errors.push(`components.companion.${f} is required`);
|
|
19153
|
+
}
|
|
19154
|
+
}
|
|
19155
|
+
if (!isObj2(companion.targets) || Object.keys(companion.targets).length === 0) {
|
|
19156
|
+
errors.push("components.companion.targets must name at least one target");
|
|
19157
|
+
} else {
|
|
19158
|
+
for (const [name, target] of Object.entries(companion.targets)) {
|
|
19159
|
+
if (!COMPANION_TARGETS.includes(name)) {
|
|
19160
|
+
errors.push(`components.companion.targets.${name} is not a known target`);
|
|
19161
|
+
continue;
|
|
19162
|
+
}
|
|
19163
|
+
if (!isObj2(target) || typeof target.asset !== "string" || target.asset.length === 0) {
|
|
19164
|
+
errors.push(`components.companion.targets.${name}.asset is required`);
|
|
19165
|
+
}
|
|
19166
|
+
if (!isObj2(target) || typeof target.sha256 !== "string" || !/^[0-9a-f]{64}$/.test(target.sha256)) {
|
|
19167
|
+
errors.push(`components.companion.targets.${name}.sha256 must be a sha256 hex digest`);
|
|
19168
|
+
}
|
|
19169
|
+
}
|
|
19170
|
+
}
|
|
19171
|
+
}
|
|
19172
|
+
}
|
|
19135
19173
|
return errors;
|
|
19136
19174
|
}
|
|
19137
19175
|
|
|
@@ -19169,6 +19207,9 @@ function platformTag(version) {
|
|
|
19169
19207
|
function channelUrlForVersion(version) {
|
|
19170
19208
|
return `https://github.com/m8t-labs/m8t/releases/download/${platformTag(version)}/manifest.json`;
|
|
19171
19209
|
}
|
|
19210
|
+
function releaseAssetUrl(tag, asset) {
|
|
19211
|
+
return `https://github.com/m8t-labs/m8t/releases/download/${tag}/${asset}`;
|
|
19212
|
+
}
|
|
19172
19213
|
|
|
19173
19214
|
// ../../packages/platform-release/dist/esm/apply-request.js
|
|
19174
19215
|
var APPLY_REQUEST_PK = "system";
|
|
@@ -19221,13 +19262,13 @@ function manifestSourceForVersionTag(channelUrl, version) {
|
|
|
19221
19262
|
return { url: custom };
|
|
19222
19263
|
}
|
|
19223
19264
|
async function fetchManifest(source, deps = {}) {
|
|
19224
|
-
const
|
|
19265
|
+
const readFile9 = deps.readFile ?? ((p) => fs19.readFileSync(p, "utf8"));
|
|
19225
19266
|
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
19226
19267
|
const ghToken = deps.ghToken ?? getGhToken;
|
|
19227
19268
|
let raw;
|
|
19228
19269
|
if ("file" in source) {
|
|
19229
19270
|
try {
|
|
19230
|
-
raw =
|
|
19271
|
+
raw = readFile9(source.file);
|
|
19231
19272
|
} catch (e) {
|
|
19232
19273
|
throw new LocalCliError({ code: "PLATFORM_MANIFEST_READ_FAILED", message: `Could not read manifest file '${source.file}': ${e.message}` });
|
|
19233
19274
|
}
|
|
@@ -20404,25 +20445,25 @@ function mergeThreeWay(ours, base, theirs) {
|
|
|
20404
20445
|
fs23.rmSync(dir, { recursive: true, force: true });
|
|
20405
20446
|
}
|
|
20406
20447
|
}
|
|
20407
|
-
var skip = (
|
|
20448
|
+
var skip = (path42, skipReason) => ({ path: path42, kind: "skip", skipReason });
|
|
20408
20449
|
function classifyCandidate(c) {
|
|
20409
|
-
const { path:
|
|
20410
|
-
if (theirs === base) return { path:
|
|
20450
|
+
const { path: path42, base, ours, theirs } = c;
|
|
20451
|
+
if (theirs === base) return { path: path42, kind: "noop" };
|
|
20411
20452
|
if (base === null) {
|
|
20412
|
-
if (theirs === null) return { path:
|
|
20413
|
-
if (ours === null) return { path:
|
|
20414
|
-
if (ours === theirs) return { path:
|
|
20415
|
-
return skip(
|
|
20453
|
+
if (theirs === null) return { path: path42, kind: "noop" };
|
|
20454
|
+
if (ours === null) return { path: path42, kind: "add", content: theirs };
|
|
20455
|
+
if (ours === theirs) return { path: path42, kind: "noop" };
|
|
20456
|
+
return skip(path42, "you already have a file at this path");
|
|
20416
20457
|
}
|
|
20417
20458
|
if (ours === null) {
|
|
20418
|
-
return theirs === null ? { path:
|
|
20459
|
+
return theirs === null ? { path: path42, kind: "noop" } : skip(path42, "you removed this file; upstream changed it");
|
|
20419
20460
|
}
|
|
20420
20461
|
if (theirs === null) {
|
|
20421
|
-
return ours === base ? { path:
|
|
20462
|
+
return ours === base ? { path: path42, kind: "delete" } : skip(path42, "you edited this file; upstream removed it");
|
|
20422
20463
|
}
|
|
20423
|
-
if (ours === base) return { path:
|
|
20464
|
+
if (ours === base) return { path: path42, kind: "update", content: theirs };
|
|
20424
20465
|
const m = mergeThreeWay(ours, base, theirs);
|
|
20425
|
-
return m.ok ? { path:
|
|
20466
|
+
return m.ok ? { path: path42, kind: "update", content: m.merged } : skip(path42, "your edits conflict with the update");
|
|
20426
20467
|
}
|
|
20427
20468
|
|
|
20428
20469
|
// src/lib/seed-pr-body.ts
|
|
@@ -20607,7 +20648,7 @@ function bindRepoApi(token, repo) {
|
|
|
20607
20648
|
openPr: (head, base, title, body) => openPullRequestViaApp({ token, repo, base, head, title, body }),
|
|
20608
20649
|
updatePr: (n, title, body) => updatePullRequestViaApp({ token, repo, number: n, title, body }),
|
|
20609
20650
|
closePr: (n, comment) => closePullRequestViaApp({ token, repo, number: n, comment }),
|
|
20610
|
-
readFileAtRef: (
|
|
20651
|
+
readFileAtRef: (path42, ref) => readRepoFileViaApp({ token, repo, path: path42, ref })
|
|
20611
20652
|
};
|
|
20612
20653
|
}
|
|
20613
20654
|
async function runSeedRefresh(args) {
|
|
@@ -22683,9 +22724,9 @@ function flattenDelta(delta, prefix = "") {
|
|
|
22683
22724
|
const out = [];
|
|
22684
22725
|
for (const d of delta) {
|
|
22685
22726
|
const segment = /^[0-9]+$/.test(d.path) ? `[${d.path}]` : prefix ? `.${d.path}` : d.path;
|
|
22686
|
-
const
|
|
22687
|
-
if (d.children && d.children.length > 0) out.push(...flattenDelta(d.children,
|
|
22688
|
-
else out.push({ ...d, path:
|
|
22727
|
+
const path42 = prefix ? `${prefix}${segment}` : d.path;
|
|
22728
|
+
if (d.children && d.children.length > 0) out.push(...flattenDelta(d.children, path42));
|
|
22729
|
+
else out.push({ ...d, path: path42 });
|
|
22689
22730
|
}
|
|
22690
22731
|
return out;
|
|
22691
22732
|
}
|
|
@@ -23537,7 +23578,7 @@ import { Command as Command45, Option as Option42 } from "clipanion";
|
|
|
23537
23578
|
init_errors();
|
|
23538
23579
|
import { spawn as spawn4 } from "child_process";
|
|
23539
23580
|
function runAzd(args) {
|
|
23540
|
-
return new Promise((
|
|
23581
|
+
return new Promise((resolve4, reject) => {
|
|
23541
23582
|
const proc = spawn4("azd", args, { stdio: ["ignore", "pipe", "pipe"], shell: process.platform === "win32" });
|
|
23542
23583
|
const out = [];
|
|
23543
23584
|
const err = [];
|
|
@@ -23566,7 +23607,7 @@ function runAzd(args) {
|
|
|
23566
23607
|
);
|
|
23567
23608
|
return;
|
|
23568
23609
|
}
|
|
23569
|
-
|
|
23610
|
+
resolve4(Buffer.concat(out).toString("utf8"));
|
|
23570
23611
|
});
|
|
23571
23612
|
});
|
|
23572
23613
|
}
|
|
@@ -26243,7 +26284,7 @@ function buildAdvancePlan(worker, cursor, physicalPks, consumedRowsByPk, failedT
|
|
|
26243
26284
|
// ../../packages/brain/engine/dist/esm/dream/writer.js
|
|
26244
26285
|
var API2 = "https://api.github.com";
|
|
26245
26286
|
var WRITER_ALLOWED_PREFIX = /^(memory|inbox|quarantine|artifacts)\//;
|
|
26246
|
-
var isAllowedWritePath = (
|
|
26287
|
+
var isAllowedWritePath = (path42, allowedPrefix) => allowedPrefix.test(path42) && !path42.split("/").includes("..");
|
|
26247
26288
|
var unquote = (s) => s.trim().replace(/^["']|["']$/g, "");
|
|
26248
26289
|
function parseFrontmatter(content) {
|
|
26249
26290
|
const m = /^---\r?\n([\s\S]*?)\r?\n---/.exec(content);
|
|
@@ -26286,8 +26327,8 @@ function makeGitDataWriter(args) {
|
|
|
26286
26327
|
if (args.allowedPathPrefix && !args.allowedPathPrefix.source.startsWith("^")) {
|
|
26287
26328
|
throw new Error(`makeGitDataWriter: allowedPathPrefix must be anchored with "^" (got /${args.allowedPathPrefix.source}/) \u2014 an unanchored regex would widen the B1 write-path allowlist`);
|
|
26288
26329
|
}
|
|
26289
|
-
async function gh(token, method,
|
|
26290
|
-
const res = await doFetch(`${API2}/repos/${repoPath}${
|
|
26330
|
+
async function gh(token, method, path42, body) {
|
|
26331
|
+
const res = await doFetch(`${API2}/repos/${repoPath}${path42}`, {
|
|
26291
26332
|
method,
|
|
26292
26333
|
headers: {
|
|
26293
26334
|
Authorization: `Bearer ${token}`,
|
|
@@ -26300,8 +26341,8 @@ function makeGitDataWriter(args) {
|
|
|
26300
26341
|
const text = await res.text();
|
|
26301
26342
|
return { status: res.status, ok: res.ok, json: text ? JSON.parse(text) : {} };
|
|
26302
26343
|
}
|
|
26303
|
-
async function readFileWith(token,
|
|
26304
|
-
const encodedPath =
|
|
26344
|
+
async function readFileWith(token, path42) {
|
|
26345
|
+
const encodedPath = path42.split("/").map(encodeURIComponent).join("/");
|
|
26305
26346
|
const r = await gh(token, "GET", `/contents/${encodedPath}?ref=${args.branch}`);
|
|
26306
26347
|
if (r.status === 404)
|
|
26307
26348
|
return null;
|
|
@@ -26317,9 +26358,9 @@ function makeGitDataWriter(args) {
|
|
|
26317
26358
|
throw new Error(`fetchTip: HTTP ${String(r.status)}`);
|
|
26318
26359
|
return r.json.object.sha;
|
|
26319
26360
|
},
|
|
26320
|
-
async readFile(
|
|
26361
|
+
async readFile(path42) {
|
|
26321
26362
|
const token = await args.mintToken();
|
|
26322
|
-
return readFileWith(token,
|
|
26363
|
+
return readFileWith(token, path42);
|
|
26323
26364
|
},
|
|
26324
26365
|
async listMemory() {
|
|
26325
26366
|
const token = await args.mintToken();
|
|
@@ -26328,9 +26369,9 @@ function makeGitDataWriter(args) {
|
|
|
26328
26369
|
return [];
|
|
26329
26370
|
const paths = r.json.tree.filter((e) => e.type === "blob" && e.path.startsWith("memory/") && e.path.endsWith(".md")).map((e) => e.path);
|
|
26330
26371
|
const out = [];
|
|
26331
|
-
for (const
|
|
26332
|
-
const content = await readFileWith(token,
|
|
26333
|
-
out.push({ path:
|
|
26372
|
+
for (const path42 of paths) {
|
|
26373
|
+
const content = await readFileWith(token, path42);
|
|
26374
|
+
out.push({ path: path42, frontmatter: content ? parseFrontmatter(content) : {} });
|
|
26334
26375
|
}
|
|
26335
26376
|
return out;
|
|
26336
26377
|
},
|
|
@@ -26769,7 +26810,7 @@ async function loadMemoryContext(brain) {
|
|
|
26769
26810
|
return {
|
|
26770
26811
|
files,
|
|
26771
26812
|
indexEntries,
|
|
26772
|
-
originOf: (
|
|
26813
|
+
originOf: (path42) => originByPath.get(path42) ?? "worker"
|
|
26773
26814
|
};
|
|
26774
26815
|
}
|
|
26775
26816
|
|
|
@@ -26790,14 +26831,14 @@ function checkEvidenceMembership(delta, harvested) {
|
|
|
26790
26831
|
}
|
|
26791
26832
|
var BatchAbort = class extends Error {
|
|
26792
26833
|
path;
|
|
26793
|
-
constructor(
|
|
26834
|
+
constructor(path42, message) {
|
|
26794
26835
|
super(message);
|
|
26795
|
-
this.path =
|
|
26836
|
+
this.path = path42;
|
|
26796
26837
|
this.name = "BatchAbort";
|
|
26797
26838
|
}
|
|
26798
26839
|
};
|
|
26799
|
-
var isIndexFile = (
|
|
26800
|
-
var isMemoryIndex = (
|
|
26840
|
+
var isIndexFile = (path42) => /(^|\/)[^/]*_index\.md$/.test(path42);
|
|
26841
|
+
var isMemoryIndex = (path42) => path42 === "memory/MEMORY.md";
|
|
26801
26842
|
function targetPath(delta) {
|
|
26802
26843
|
switch (delta.verb) {
|
|
26803
26844
|
case "new":
|
|
@@ -26814,29 +26855,29 @@ function targetPath(delta) {
|
|
|
26814
26855
|
}
|
|
26815
26856
|
async function checkOriginTier(delta, lookup) {
|
|
26816
26857
|
const evidence = evidenceOf(delta) ?? [];
|
|
26817
|
-
const
|
|
26818
|
-
if (
|
|
26858
|
+
const path42 = targetPath(delta);
|
|
26859
|
+
if (path42 === null)
|
|
26819
26860
|
return { ok: true };
|
|
26820
|
-
if (isIndexFile(
|
|
26821
|
-
return { ok: false, flagged: { kind: "flagged", path:
|
|
26861
|
+
if (isIndexFile(path42) && !isMemoryIndex(path42)) {
|
|
26862
|
+
return { ok: false, flagged: { kind: "flagged", path: path42, tension: `refused: ${path42} is an index file (only memory/MEMORY.md is editable)`, evidence } };
|
|
26822
26863
|
}
|
|
26823
|
-
const target = await lookup(
|
|
26824
|
-
const isStructural =
|
|
26864
|
+
const target = await lookup(path42);
|
|
26865
|
+
const isStructural = path42.startsWith("references/");
|
|
26825
26866
|
const origin = target?.frontmatter.origin ?? (isStructural ? "operator" : "worker");
|
|
26826
26867
|
const editable = origin === "worker" || origin === "dream";
|
|
26827
26868
|
if (editable)
|
|
26828
26869
|
return { ok: true };
|
|
26829
26870
|
const removesOperatorTarget = delta.verb === "retract" || delta.verb === "supersede" && delta.oldPath !== delta.newPath;
|
|
26830
26871
|
if (removesOperatorTarget) {
|
|
26831
|
-
throw new BatchAbort(
|
|
26872
|
+
throw new BatchAbort(path42, `supersede/retract OLD target ${path42} is origin: ${origin} \u2014 aborting batch (never half-apply)`);
|
|
26832
26873
|
}
|
|
26833
|
-
return { ok: false, flagged: { kind: "flagged", path:
|
|
26874
|
+
return { ok: false, flagged: { kind: "flagged", path: path42, tension: `operator-origin (${origin}); flag-only`, evidence } };
|
|
26834
26875
|
}
|
|
26835
26876
|
var ALLOWED_MEMORY = /^memory\/.+\.md$/;
|
|
26836
26877
|
var ALLOWED_INBOX = /^inbox\//;
|
|
26837
26878
|
var ALLOWED_QUARANTINE = /^quarantine\//;
|
|
26838
26879
|
var ALLOWED_FLAG = /^memory\//;
|
|
26839
|
-
var hasTraversal = (
|
|
26880
|
+
var hasTraversal = (path42) => path42.split("/").includes("..");
|
|
26840
26881
|
function checkPathAllowed(delta) {
|
|
26841
26882
|
const evidence = evidenceOf(delta) ?? [];
|
|
26842
26883
|
const reject = (detail) => ({ ok: false, rejected: { kind: "rejected", detail, evidence } });
|
|
@@ -26943,7 +26984,7 @@ ${inject}
|
|
|
26943
26984
|
---
|
|
26944
26985
|
` + body.slice(m[0].length);
|
|
26945
26986
|
}
|
|
26946
|
-
var indexLine = (
|
|
26987
|
+
var indexLine = (path42, title, date, tags) => `- \`${path42}\` \u2014 **${title}**: ${title}. (${date} \xB7 ${tags.join(", ")})`;
|
|
26947
26988
|
function splitIndex(index) {
|
|
26948
26989
|
const all = index.split("\n");
|
|
26949
26990
|
const firstLineIdx = all.findIndex((l) => l.startsWith("- `memory/"));
|
|
@@ -27116,16 +27157,16 @@ function defaultDreamSeams(opts = {}) {
|
|
|
27116
27157
|
const mem = await memory(deps.brain);
|
|
27117
27158
|
const harvested = harvestedIds(input);
|
|
27118
27159
|
const ctx = { readFile: (p) => deps.brain.readFile(p), at: deps.clock() };
|
|
27119
|
-
const lookup = (
|
|
27160
|
+
const lookup = (path42) => Promise.resolve(mem.files.find((f) => f.path === path42) ?? null);
|
|
27120
27161
|
const digest = [...pendingRejected];
|
|
27121
27162
|
let changes = [];
|
|
27122
27163
|
let indexEdit;
|
|
27123
27164
|
try {
|
|
27124
27165
|
for (const raw of deltas) {
|
|
27125
27166
|
const delta = forcePolicyQuarantine(raw);
|
|
27126
|
-
const
|
|
27127
|
-
if (!
|
|
27128
|
-
digest.push(
|
|
27167
|
+
const path42 = checkPathAllowed(delta);
|
|
27168
|
+
if (!path42.ok) {
|
|
27169
|
+
digest.push(path42.rejected);
|
|
27129
27170
|
continue;
|
|
27130
27171
|
}
|
|
27131
27172
|
const ev = checkEvidenceMembership(delta, harvested);
|
|
@@ -27914,8 +27955,8 @@ async function ensureAccount(args) {
|
|
|
27914
27955
|
);
|
|
27915
27956
|
} catch (e) {
|
|
27916
27957
|
const msg = e instanceof Error ? e.message : String(e);
|
|
27917
|
-
const
|
|
27918
|
-
const stderr = msg.includes(
|
|
27958
|
+
const sep3 = "' failed: ";
|
|
27959
|
+
const stderr = msg.includes(sep3) ? msg.slice(msg.indexOf(sep3) + sep3.length) : msg;
|
|
27919
27960
|
if (/CustomDomainInUse|subdomain name .* is not available|already used by a resource/i.test(stderr)) {
|
|
27920
27961
|
throw new LocalCliError({
|
|
27921
27962
|
code: "FOUNDRY_ACCOUNT_SUBDOMAIN_CONFLICT",
|
|
@@ -28248,9 +28289,9 @@ var DISCLOSURE_TIER1 = "Operational data. To support your installation, m8t rece
|
|
|
28248
28289
|
|
|
28249
28290
|
// ../../packages/telemetry-contract/src/endpoints.ts
|
|
28250
28291
|
var INGEST_BASE_URL = (process.env.M8T_INGEST_BASE_URL ?? "").trim() || "https://m8t-admin.wonderfuldesert-721e332f.eastus2.azurecontainerapps.io";
|
|
28251
|
-
function ingestUrl(
|
|
28292
|
+
function ingestUrl(path42) {
|
|
28252
28293
|
const base = INGEST_BASE_URL.replace(/\/+$/, "");
|
|
28253
|
-
return `${base}${
|
|
28294
|
+
return `${base}${path42.startsWith("/") ? path42 : `/${path42}`}`;
|
|
28254
28295
|
}
|
|
28255
28296
|
|
|
28256
28297
|
// src/commands/bootstrap/preflight.ts
|
|
@@ -29238,10 +29279,10 @@ Found ${String(total)} orphaned assignment(s) (${breakdown}) (dry-run). ${colors
|
|
|
29238
29279
|
};
|
|
29239
29280
|
|
|
29240
29281
|
// src/commands/bootstrap/finish.ts
|
|
29241
|
-
import * as
|
|
29242
|
-
import * as
|
|
29243
|
-
import * as
|
|
29244
|
-
import { Command as
|
|
29282
|
+
import * as fs34 from "fs/promises";
|
|
29283
|
+
import * as os18 from "os";
|
|
29284
|
+
import * as path38 from "path";
|
|
29285
|
+
import { Command as Command58, Option as Option55 } from "clipanion";
|
|
29245
29286
|
init_errors();
|
|
29246
29287
|
|
|
29247
29288
|
// src/lib/company-profile-seed.ts
|
|
@@ -30178,10 +30219,10 @@ var DEFAULT_WORKERS = [
|
|
|
30178
30219
|
{ name: "azzy", label: "Azzy \u2014 Azure Advisor", brain: "azzy-brain", executor: "azure-executor" }
|
|
30179
30220
|
];
|
|
30180
30221
|
function renderInstallSummary(args) {
|
|
30181
|
-
const
|
|
30222
|
+
const open3 = args.webappUrl ? `${args.webappUrl} (or run: m8t open)` : "run: m8t open";
|
|
30182
30223
|
const lines = [
|
|
30183
30224
|
"\u2705 Your m8t platform is ready to use.",
|
|
30184
|
-
` Open it: ${
|
|
30225
|
+
` Open it: ${open3}`,
|
|
30185
30226
|
""
|
|
30186
30227
|
];
|
|
30187
30228
|
if (args.brainOrg) {
|
|
@@ -30203,216 +30244,1309 @@ function renderInstallSummary(args) {
|
|
|
30203
30244
|
return lines.join("\n") + "\n";
|
|
30204
30245
|
}
|
|
30205
30246
|
|
|
30206
|
-
// src/
|
|
30207
|
-
|
|
30208
|
-
|
|
30209
|
-
|
|
30210
|
-
|
|
30211
|
-
|
|
30212
|
-
|
|
30213
|
-
|
|
30214
|
-
|
|
30215
|
-
|
|
30216
|
-
|
|
30217
|
-
|
|
30218
|
-
|
|
30219
|
-
|
|
30220
|
-
|
|
30247
|
+
// src/lib/companion-install.ts
|
|
30248
|
+
import { constants as constants2 } from "fs";
|
|
30249
|
+
import * as fs32 from "fs/promises";
|
|
30250
|
+
import * as path35 from "path";
|
|
30251
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
30252
|
+
import { execFile, spawn as spawn7 } from "child_process";
|
|
30253
|
+
|
|
30254
|
+
// src/lib/companion-artifact.ts
|
|
30255
|
+
import { createHash as createHash5 } from "crypto";
|
|
30256
|
+
import { constants } from "fs";
|
|
30257
|
+
import * as fs31 from "fs/promises";
|
|
30258
|
+
import * as path34 from "path";
|
|
30259
|
+
var SHA256 = /^[a-f0-9]{64}$/u;
|
|
30260
|
+
var VERSION2 = /^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$/u;
|
|
30261
|
+
function exactKeys(value, expected) {
|
|
30262
|
+
const actual = Object.keys(value).sort();
|
|
30263
|
+
return actual.length === expected.length && actual.every((key2, index) => key2 === [...expected].sort()[index]);
|
|
30264
|
+
}
|
|
30265
|
+
function normalizedRelative(value) {
|
|
30266
|
+
if (value.length === 0 || value.includes("\\") || value.includes("\0") || path34.posix.isAbsolute(value)) {
|
|
30267
|
+
throw new Error("Artifact path must be a normalized relative POSIX path");
|
|
30268
|
+
}
|
|
30269
|
+
const normalized = path34.posix.normalize(value);
|
|
30270
|
+
if (normalized !== value || normalized === "." || normalized === ".." || normalized.startsWith("../")) {
|
|
30271
|
+
throw new Error("Artifact path escapes the payload root");
|
|
30272
|
+
}
|
|
30273
|
+
return value;
|
|
30274
|
+
}
|
|
30275
|
+
function validateLink(entryPath, target) {
|
|
30276
|
+
if (target.length === 0 || target.includes("\\") || target.includes("\0") || path34.posix.isAbsolute(target)) {
|
|
30277
|
+
throw new Error("Artifact symlink target must be relative");
|
|
30278
|
+
}
|
|
30279
|
+
const resolved = path34.posix.normalize(
|
|
30280
|
+
path34.posix.join(path34.posix.dirname(entryPath), target)
|
|
30281
|
+
);
|
|
30282
|
+
if (resolved === ".." || resolved.startsWith("../") || path34.posix.isAbsolute(resolved)) {
|
|
30283
|
+
throw new Error("Artifact symlink target escapes the payload root");
|
|
30284
|
+
}
|
|
30285
|
+
return target;
|
|
30286
|
+
}
|
|
30287
|
+
function canonicalEntry(entry) {
|
|
30288
|
+
return entry.type === "file" ? `file\0${entry.path}\0${String(entry.mode)}\0${String(entry.size)}\0${entry.sha256}
|
|
30289
|
+
` : `symlink\0${entry.path}\0${entry.target}
|
|
30290
|
+
`;
|
|
30291
|
+
}
|
|
30292
|
+
function artifactTreeSha256(entries) {
|
|
30293
|
+
const hash = createHash5("sha256");
|
|
30294
|
+
for (const entry of entries) hash.update(canonicalEntry(entry), "utf8");
|
|
30295
|
+
return hash.digest("hex");
|
|
30296
|
+
}
|
|
30297
|
+
function parseEntry(value) {
|
|
30298
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
30299
|
+
throw new Error("Artifact manifest entry schema is invalid");
|
|
30300
|
+
}
|
|
30301
|
+
const record = value;
|
|
30302
|
+
if (record.type === "file") {
|
|
30303
|
+
if (!exactKeys(record, ["type", "path", "mode", "size", "sha256"]) || typeof record.path !== "string" || typeof record.mode !== "number" || !Number.isSafeInteger(record.mode) || record.mode < 0 || record.mode > 511 || typeof record.size !== "number" || !Number.isSafeInteger(record.size) || record.size < 0 || typeof record.sha256 !== "string" || !SHA256.test(record.sha256)) {
|
|
30304
|
+
throw new Error("Artifact file entry schema is invalid");
|
|
30221
30305
|
}
|
|
30222
|
-
|
|
30223
|
-
|
|
30224
|
-
|
|
30225
|
-
|
|
30226
|
-
|
|
30227
|
-
|
|
30306
|
+
return {
|
|
30307
|
+
type: "file",
|
|
30308
|
+
path: normalizedRelative(record.path),
|
|
30309
|
+
mode: record.mode,
|
|
30310
|
+
size: record.size,
|
|
30311
|
+
sha256: record.sha256
|
|
30312
|
+
};
|
|
30313
|
+
}
|
|
30314
|
+
if (record.type === "symlink" && exactKeys(record, ["type", "path", "target"]) && typeof record.path === "string" && typeof record.target === "string") {
|
|
30315
|
+
const entryPath = normalizedRelative(record.path);
|
|
30316
|
+
return {
|
|
30317
|
+
type: "symlink",
|
|
30318
|
+
path: entryPath,
|
|
30319
|
+
target: validateLink(entryPath, record.target)
|
|
30320
|
+
};
|
|
30321
|
+
}
|
|
30322
|
+
throw new Error("Artifact symlink entry schema is invalid");
|
|
30323
|
+
}
|
|
30324
|
+
function parseArtifactManifest(value) {
|
|
30325
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
30326
|
+
throw new Error("Artifact manifest schema is invalid");
|
|
30327
|
+
}
|
|
30328
|
+
const record = value;
|
|
30329
|
+
if (!exactKeys(record, [
|
|
30330
|
+
"schemaVersion",
|
|
30331
|
+
"platform",
|
|
30332
|
+
"architecture",
|
|
30333
|
+
"version",
|
|
30334
|
+
"entryRelativePath",
|
|
30335
|
+
"treeSha256",
|
|
30336
|
+
"entries"
|
|
30337
|
+
]) || record.schemaVersion !== 1 || record.platform !== "darwin" && record.platform !== "win32" || record.architecture !== "arm64" && record.architecture !== "x64" || typeof record.version !== "string" || !VERSION2.test(record.version) || typeof record.entryRelativePath !== "string" || typeof record.treeSha256 !== "string" || !SHA256.test(record.treeSha256) || !Array.isArray(record.entries)) {
|
|
30338
|
+
throw new Error("Artifact manifest schema is invalid");
|
|
30339
|
+
}
|
|
30340
|
+
const entries = record.entries.map(parseEntry);
|
|
30341
|
+
const paths = entries.map((entry) => entry.path);
|
|
30342
|
+
const sorted = [...paths].sort((left, right) => left.localeCompare(right));
|
|
30343
|
+
if (paths.some((entryPath, index) => entryPath !== sorted[index]) || new Set(paths).size !== paths.length) {
|
|
30344
|
+
throw new Error("Artifact entries must be unique and sorted");
|
|
30345
|
+
}
|
|
30346
|
+
if (artifactTreeSha256(entries) !== record.treeSha256) {
|
|
30347
|
+
throw new Error("Artifact tree digest does not match entries");
|
|
30348
|
+
}
|
|
30349
|
+
return {
|
|
30350
|
+
schemaVersion: 1,
|
|
30351
|
+
platform: record.platform,
|
|
30352
|
+
architecture: record.architecture,
|
|
30353
|
+
version: record.version,
|
|
30354
|
+
entryRelativePath: normalizedRelative(record.entryRelativePath),
|
|
30355
|
+
treeSha256: record.treeSha256,
|
|
30356
|
+
entries
|
|
30357
|
+
};
|
|
30358
|
+
}
|
|
30359
|
+
async function sha256File2(filePath) {
|
|
30360
|
+
return createHash5("sha256").update(await fs31.readFile(filePath)).digest("hex");
|
|
30361
|
+
}
|
|
30362
|
+
async function walk(root, relative3 = "") {
|
|
30363
|
+
const directory = path34.join(root, ...relative3.split("/").filter(Boolean));
|
|
30364
|
+
const children = await fs31.readdir(directory, { withFileTypes: true });
|
|
30365
|
+
const entries = [];
|
|
30366
|
+
for (const child of children.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
30367
|
+
const childRelative = relative3 ? `${relative3}/${child.name}` : child.name;
|
|
30368
|
+
normalizedRelative(childRelative);
|
|
30369
|
+
const childPath = path34.join(directory, child.name);
|
|
30370
|
+
const stat4 = await fs31.lstat(childPath);
|
|
30371
|
+
if (stat4.isDirectory()) {
|
|
30372
|
+
entries.push(...await walk(root, childRelative));
|
|
30373
|
+
} else if (stat4.isFile()) {
|
|
30374
|
+
entries.push({
|
|
30375
|
+
type: "file",
|
|
30376
|
+
path: childRelative,
|
|
30377
|
+
mode: stat4.mode & 511,
|
|
30378
|
+
size: stat4.size,
|
|
30379
|
+
sha256: await sha256File2(childPath)
|
|
30228
30380
|
});
|
|
30381
|
+
} else if (stat4.isSymbolicLink()) {
|
|
30382
|
+
const target = await fs31.readlink(childPath);
|
|
30383
|
+
entries.push({
|
|
30384
|
+
type: "symlink",
|
|
30385
|
+
path: childRelative,
|
|
30386
|
+
target: validateLink(childRelative, target)
|
|
30387
|
+
});
|
|
30388
|
+
} else {
|
|
30389
|
+
throw new Error(`Unsupported artifact entry: ${childRelative}`);
|
|
30229
30390
|
}
|
|
30230
|
-
|
|
30231
|
-
|
|
30232
|
-
|
|
30233
|
-
|
|
30234
|
-
|
|
30235
|
-
|
|
30236
|
-
|
|
30237
|
-
|
|
30391
|
+
}
|
|
30392
|
+
return entries;
|
|
30393
|
+
}
|
|
30394
|
+
async function ensureRealDirectory(root) {
|
|
30395
|
+
const stat4 = await fs31.lstat(root);
|
|
30396
|
+
if (stat4.isSymbolicLink()) throw new Error("Artifact root is a symbolic link");
|
|
30397
|
+
if (!stat4.isDirectory()) throw new Error("Artifact root is not a directory");
|
|
30398
|
+
}
|
|
30399
|
+
async function validateResolvedLinks(root, entries) {
|
|
30400
|
+
const realRoot = await fs31.realpath(root);
|
|
30401
|
+
for (const entry of entries) {
|
|
30402
|
+
if (entry.type !== "symlink") continue;
|
|
30238
30403
|
try {
|
|
30239
|
-
const
|
|
30240
|
-
await
|
|
30241
|
-
|
|
30242
|
-
|
|
30243
|
-
|
|
30244
|
-
subscriptionId: d.subscriptionId,
|
|
30245
|
-
containerAppResourceId: d.containerAppResourceId,
|
|
30246
|
-
cachedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
30247
|
-
});
|
|
30248
|
-
webappUrl = d.gatewayUrl;
|
|
30249
|
-
this.context.stdout.write(
|
|
30250
|
-
`${colors.success("\u2705 The platform is live and your local tools are pointed at it.")}
|
|
30251
|
-
gateway: ${d.gatewayUrl}
|
|
30252
|
-
foundry: ${doc.result?.foundryEndpoint ?? "-"}
|
|
30253
|
-
|
|
30254
|
-
`
|
|
30255
|
-
);
|
|
30256
|
-
try {
|
|
30257
|
-
await ensureGatewayRedirectUri(d.gatewayClientId, new URL(d.gatewayUrl).host);
|
|
30258
|
-
} catch (e) {
|
|
30259
|
-
const err = e;
|
|
30260
|
-
this.context.stderr.write(
|
|
30261
|
-
` ${colors.error("\u26A0 could not authorize sign-in for your webapp:")} ${err.message}
|
|
30262
|
-
${colors.hint(`Until this is fixed, opening ${d.gatewayUrl} will fail with AADSTS50011 (redirect URI mismatch).`)}
|
|
30263
|
-
${colors.hint("Ask a directory admin to run:")}
|
|
30264
|
-
${colors.hint(` m8t bootstrap finish --repo-root ${repoRoot} # (as an admin), or by hand:`)}
|
|
30265
|
-
${colors.hint(` az ad app update --id ${d.gatewayClientId} --set spa.redirectUris="['${d.gatewayUrl}']" # (merge, do not overwrite)`)}
|
|
30266
|
-
|
|
30267
|
-
`
|
|
30268
|
-
);
|
|
30404
|
+
const linkPath = path34.join(root, ...entry.path.split("/"));
|
|
30405
|
+
const resolved = await fs31.realpath(linkPath);
|
|
30406
|
+
const relative3 = path34.relative(realRoot, resolved);
|
|
30407
|
+
if (relative3 === ".." || relative3.startsWith(`..${path34.sep}`) || path34.isAbsolute(relative3)) {
|
|
30408
|
+
throw new Error("Artifact symlink chain escapes the payload root");
|
|
30269
30409
|
}
|
|
30270
|
-
|
|
30271
|
-
|
|
30272
|
-
|
|
30273
|
-
{ fix: true, onProgress: (m) => {
|
|
30274
|
-
this.context.stdout.write(colors.dim(` ${m}
|
|
30275
|
-
`));
|
|
30276
|
-
} }
|
|
30277
|
-
);
|
|
30278
|
-
const rows = verdict.results.filter((r) => r.slug !== "signin-redirect-uri");
|
|
30279
|
-
const unresolved = rows.filter((r) => r.status === "fail");
|
|
30280
|
-
if (unresolved.length === 0) {
|
|
30281
|
-
this.context.stdout.write(` ${colors.success("\u2713 your account can reach Foundry and read the platform Key Vault")}
|
|
30282
|
-
|
|
30283
|
-
`);
|
|
30284
|
-
}
|
|
30285
|
-
for (const r of unresolved) {
|
|
30286
|
-
this.context.stderr.write(
|
|
30287
|
-
` ${colors.error("\u26A0 could not grant you access:")} ${r.detail}
|
|
30288
|
-
` + (r.remedy ? ` ${colors.hint(`Fix it with: ${r.remedy}`)}
|
|
30289
|
-
` : "") + "\n"
|
|
30290
|
-
);
|
|
30291
|
-
}
|
|
30292
|
-
} catch (e) {
|
|
30293
|
-
this.context.stderr.write(
|
|
30294
|
-
` ${colors.error("\u26A0 could not check or grant your platform access:")} ${e.message}
|
|
30295
|
-
${colors.hint("Run 'm8t prereqs --fix' when you can \u2014 until then your AI team will not load and worker deploys will fail.")}
|
|
30296
|
-
|
|
30297
|
-
`
|
|
30298
|
-
);
|
|
30410
|
+
} catch (error) {
|
|
30411
|
+
if (error instanceof Error && error.message.includes("Artifact symlink chain")) {
|
|
30412
|
+
throw error;
|
|
30299
30413
|
}
|
|
30300
|
-
|
|
30301
|
-
const err = e;
|
|
30302
|
-
this.context.stdout.write(`${colors.success("\u2705 The platform is live.")}
|
|
30303
|
-
|
|
30304
|
-
`);
|
|
30305
|
-
this.context.stderr.write(
|
|
30306
|
-
` ${colors.hint(`\u26A0 couldn't auto-point your local tools at the gateway: ${err.message}`)}
|
|
30307
|
-
` + (err.hint ? ` ${colors.hint(err.hint)}
|
|
30308
|
-
` : "") + "\n"
|
|
30309
|
-
);
|
|
30414
|
+
throw new Error(`Artifact symlink does not resolve inside payload: ${entry.path}`);
|
|
30310
30415
|
}
|
|
30311
|
-
|
|
30312
|
-
|
|
30313
|
-
|
|
30314
|
-
|
|
30315
|
-
|
|
30316
|
-
|
|
30416
|
+
}
|
|
30417
|
+
}
|
|
30418
|
+
async function buildArtifactManifest(payloadRoot, input) {
|
|
30419
|
+
await ensureRealDirectory(payloadRoot);
|
|
30420
|
+
const entries = await walk(payloadRoot);
|
|
30421
|
+
await validateResolvedLinks(payloadRoot, entries);
|
|
30422
|
+
const manifest = {
|
|
30423
|
+
schemaVersion: 1,
|
|
30424
|
+
platform: input.platform,
|
|
30425
|
+
architecture: input.architecture,
|
|
30426
|
+
version: input.version,
|
|
30427
|
+
entryRelativePath: normalizedRelative(input.entryRelativePath),
|
|
30428
|
+
treeSha256: artifactTreeSha256(entries),
|
|
30429
|
+
entries
|
|
30430
|
+
};
|
|
30431
|
+
if (!entries.some((entry) => entry.path === manifest.entryRelativePath && entry.type === "file")) {
|
|
30432
|
+
throw new Error("Artifact executable is missing from the payload");
|
|
30433
|
+
}
|
|
30434
|
+
return manifest;
|
|
30435
|
+
}
|
|
30436
|
+
async function readArtifactManifest(manifestPath) {
|
|
30437
|
+
const before = await fs31.lstat(manifestPath);
|
|
30438
|
+
if (before.isSymbolicLink() || !before.isFile()) {
|
|
30439
|
+
throw new Error("Artifact manifest is a symbolic link or non-file");
|
|
30440
|
+
}
|
|
30441
|
+
let handle;
|
|
30442
|
+
try {
|
|
30443
|
+
handle = await fs31.open(
|
|
30444
|
+
manifestPath,
|
|
30445
|
+
constants.O_RDONLY | constants.O_NOFOLLOW
|
|
30446
|
+
);
|
|
30447
|
+
const opened = await handle.stat();
|
|
30448
|
+
const sameFile = before.ino === 0 && opened.ino === 0 || before.dev === opened.dev && before.ino === opened.ino;
|
|
30449
|
+
if (!opened.isFile() || !sameFile || opened.size > 4 * 1024 * 1024) {
|
|
30450
|
+
throw new Error("Artifact manifest is not a bounded regular file");
|
|
30317
30451
|
}
|
|
30318
|
-
|
|
30319
|
-
|
|
30320
|
-
`${colors.field("Optional local tooling for your coding agent:")}
|
|
30321
|
-
\u2022 Talk to your deployed workers from agent sessions (e.g. /stacey) \u2014 install the m8t plugin: guides/install/m8t-plugin.md.
|
|
30322
|
-
\u2022 Azure-capable MCP servers (microsoft/azure-skills, Microsoft Learn) \u2014 see the optional steps in guides/install.md.
|
|
30323
|
-
\u2022 Deploy workers conversationally with the opt-in persona skills \u2014 see guides/workers.md.
|
|
30324
|
-
\u2022 Make sure your m8t CLI is current: npm i -g @m8t-stack/cli@latest
|
|
30325
|
-
|
|
30326
|
-
${colors.field("Then open a brand new chat/session")} \u2014 new skills + MCP servers load only on a fresh start.
|
|
30327
|
-
`
|
|
30452
|
+
return parseArtifactManifest(
|
|
30453
|
+
JSON.parse(await handle.readFile("utf8"))
|
|
30328
30454
|
);
|
|
30329
|
-
|
|
30330
|
-
|
|
30331
|
-
|
|
30332
|
-
|
|
30333
|
-
|
|
30334
|
-
|
|
30335
|
-
|
|
30336
|
-
|
|
30337
|
-
|
|
30338
|
-
|
|
30339
|
-
|
|
30455
|
+
} finally {
|
|
30456
|
+
await handle?.close().catch(() => void 0);
|
|
30457
|
+
}
|
|
30458
|
+
}
|
|
30459
|
+
function ensureContainedEntry(root, entryPath) {
|
|
30460
|
+
const absolute = path34.join(root, ...entryPath.split("/"));
|
|
30461
|
+
const relative3 = path34.relative(root, absolute);
|
|
30462
|
+
if (relative3.startsWith("..") || path34.isAbsolute(relative3)) {
|
|
30463
|
+
throw new Error("Artifact entry escapes payload root");
|
|
30464
|
+
}
|
|
30465
|
+
return absolute;
|
|
30466
|
+
}
|
|
30467
|
+
async function verifyArtifactSource(payloadRoot, manifest) {
|
|
30468
|
+
await ensureRealDirectory(payloadRoot);
|
|
30469
|
+
const actual = await walk(payloadRoot);
|
|
30470
|
+
await validateResolvedLinks(payloadRoot, actual);
|
|
30471
|
+
const declared = new Map(manifest.entries.map((entry) => [entry.path, entry]));
|
|
30472
|
+
for (const entry of actual) {
|
|
30473
|
+
if (JSON.stringify(declared.get(entry.path)) !== JSON.stringify(entry)) {
|
|
30474
|
+
throw new Error(`Artifact entry does not match manifest: ${entry.path}`);
|
|
30340
30475
|
}
|
|
30341
|
-
return 0;
|
|
30342
30476
|
}
|
|
30343
|
-
|
|
30344
|
-
|
|
30345
|
-
|
|
30346
|
-
|
|
30347
|
-
import * as os18 from "os";
|
|
30348
|
-
import * as path36 from "path";
|
|
30349
|
-
import { Command as Command58, Option as Option55 } from "clipanion";
|
|
30350
|
-
import { DefaultAzureCredential as DefaultAzureCredential24 } from "@azure/identity";
|
|
30351
|
-
init_errors();
|
|
30352
|
-
|
|
30353
|
-
// src/lib/bootstrap-ui.ts
|
|
30354
|
-
import * as fs32 from "fs";
|
|
30355
|
-
import * as net from "net";
|
|
30356
|
-
import * as os17 from "os";
|
|
30357
|
-
import * as path35 from "path";
|
|
30358
|
-
import { randomBytes as randomBytes3 } from "crypto";
|
|
30359
|
-
import { spawn as spawn7, spawnSync as spawnSync6 } from "child_process";
|
|
30360
|
-
init_errors();
|
|
30361
|
-
init_rbac();
|
|
30362
|
-
|
|
30363
|
-
// src/lib/intake-agent.ts
|
|
30364
|
-
init_esm();
|
|
30365
|
-
var INTAKE_PERSONA = "azure-advisor-intake";
|
|
30366
|
-
var INTAKE_AGENT = INTAKE_AGENT_NAME;
|
|
30367
|
-
async function deployIntakeAgent(args) {
|
|
30368
|
-
try {
|
|
30369
|
-
return await deployPromptAdvisor({
|
|
30370
|
-
credential: args.credential,
|
|
30371
|
-
endpoint: args.endpoint,
|
|
30372
|
-
repoRoot: args.repoRoot,
|
|
30373
|
-
persona: INTAKE_PERSONA,
|
|
30374
|
-
agentName: INTAKE_AGENT,
|
|
30375
|
-
model: args.model,
|
|
30376
|
-
fieldOverrides: args.fieldOverrides
|
|
30377
|
-
});
|
|
30378
|
-
} catch (e) {
|
|
30379
|
-
if (e && typeof e === "object" && "code" in e) {
|
|
30380
|
-
const err = e;
|
|
30381
|
-
if (err.code === "ADVISOR_PERSONA_MISSING") {
|
|
30382
|
-
const { LocalCliError: LocalCliError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
|
|
30383
|
-
throw new LocalCliError2({
|
|
30384
|
-
code: "INTAKE_PERSONA_MISSING",
|
|
30385
|
-
message: err.message,
|
|
30386
|
-
hint: err.hint,
|
|
30387
|
-
cause: err.cause
|
|
30388
|
-
});
|
|
30389
|
-
}
|
|
30390
|
-
if (err.code === "ADVISOR_NO_MODEL") {
|
|
30391
|
-
const { LocalCliError: LocalCliError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
|
|
30392
|
-
throw new LocalCliError2({
|
|
30393
|
-
code: "INTAKE_NO_MODEL",
|
|
30394
|
-
message: err.message,
|
|
30395
|
-
hint: err.hint,
|
|
30396
|
-
cause: err.cause
|
|
30397
|
-
});
|
|
30398
|
-
}
|
|
30399
|
-
if (err.code === "ADVISOR_BAD_EFFORT") {
|
|
30400
|
-
const { LocalCliError: LocalCliError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
|
|
30401
|
-
throw new LocalCliError2({
|
|
30402
|
-
code: "INTAKE_BAD_EFFORT",
|
|
30403
|
-
message: err.message,
|
|
30404
|
-
hint: err.hint,
|
|
30405
|
-
cause: err.cause
|
|
30406
|
-
});
|
|
30407
|
-
}
|
|
30477
|
+
const present = new Set(actual.map((entry) => entry.path));
|
|
30478
|
+
for (const entry of manifest.entries) {
|
|
30479
|
+
if (entry.type === "file" && !present.has(entry.path)) {
|
|
30480
|
+
throw new Error(`Artifact file is missing from the payload: ${entry.path}`);
|
|
30408
30481
|
}
|
|
30409
|
-
throw e;
|
|
30410
30482
|
}
|
|
30411
30483
|
}
|
|
30412
|
-
|
|
30413
|
-
|
|
30484
|
+
async function verifyArtifactPayload(payloadRoot, manifest) {
|
|
30485
|
+
await ensureRealDirectory(payloadRoot);
|
|
30486
|
+
const actual = await walk(payloadRoot);
|
|
30487
|
+
await validateResolvedLinks(payloadRoot, actual);
|
|
30488
|
+
if (actual.length !== manifest.entries.length || artifactTreeSha256(actual) !== manifest.treeSha256) {
|
|
30489
|
+
throw new Error("Artifact payload digest or size does not match manifest");
|
|
30490
|
+
}
|
|
30491
|
+
for (const entry of manifest.entries) {
|
|
30492
|
+
const actualEntry = actual.find((candidate2) => candidate2.path === entry.path);
|
|
30493
|
+
if (JSON.stringify(actualEntry) !== JSON.stringify(entry)) {
|
|
30494
|
+
throw new Error(`Artifact entry does not match manifest: ${entry.path}`);
|
|
30495
|
+
}
|
|
30496
|
+
}
|
|
30497
|
+
}
|
|
30498
|
+
async function copyArtifactPayload(sourceRoot, targetRoot, manifest) {
|
|
30499
|
+
await verifyArtifactSource(sourceRoot, manifest);
|
|
30500
|
+
try {
|
|
30501
|
+
const targetStat = await fs31.lstat(targetRoot);
|
|
30502
|
+
if (targetStat.isSymbolicLink()) {
|
|
30503
|
+
throw new Error("Install target is a symbolic link");
|
|
30504
|
+
}
|
|
30505
|
+
if (!targetStat.isDirectory()) throw new Error("Install target is not a directory");
|
|
30506
|
+
if ((await fs31.readdir(targetRoot)).length > 0) {
|
|
30507
|
+
throw new Error("Install staging target is not empty");
|
|
30508
|
+
}
|
|
30509
|
+
} catch (error) {
|
|
30510
|
+
if (error.code !== "ENOENT") throw error;
|
|
30511
|
+
await fs31.mkdir(targetRoot, { recursive: false, mode: 448 });
|
|
30512
|
+
}
|
|
30513
|
+
for (const entry of manifest.entries) {
|
|
30514
|
+
const source = ensureContainedEntry(sourceRoot, entry.path);
|
|
30515
|
+
const target = ensureContainedEntry(targetRoot, entry.path);
|
|
30516
|
+
await fs31.mkdir(path34.dirname(target), { recursive: true, mode: 448 });
|
|
30517
|
+
if (entry.type === "file") {
|
|
30518
|
+
await fs31.copyFile(source, target);
|
|
30519
|
+
await fs31.chmod(target, entry.mode);
|
|
30520
|
+
} else {
|
|
30521
|
+
await fs31.symlink(entry.target, target);
|
|
30522
|
+
}
|
|
30523
|
+
}
|
|
30524
|
+
await verifyArtifactPayload(targetRoot, manifest);
|
|
30525
|
+
}
|
|
30526
|
+
|
|
30527
|
+
// src/lib/companion-install.ts
|
|
30528
|
+
function xmlEscape(value) {
|
|
30529
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
30530
|
+
}
|
|
30531
|
+
function commandError(error, message) {
|
|
30532
|
+
const wrapped = new Error(message, { cause: error });
|
|
30533
|
+
const code = error?.code;
|
|
30534
|
+
if (code !== void 0) wrapped.code = code;
|
|
30535
|
+
return wrapped;
|
|
30536
|
+
}
|
|
30537
|
+
function execFileAsync(file, args) {
|
|
30538
|
+
return new Promise((resolve4, reject) => {
|
|
30539
|
+
execFile(file, [...args], { windowsHide: true }, (error) => {
|
|
30540
|
+
if (error) reject(commandError(error, "Login-item command failed"));
|
|
30541
|
+
else resolve4();
|
|
30542
|
+
});
|
|
30543
|
+
});
|
|
30544
|
+
}
|
|
30545
|
+
function execFileOutput(file, args) {
|
|
30546
|
+
return new Promise((resolve4, reject) => {
|
|
30547
|
+
execFile(
|
|
30548
|
+
file,
|
|
30549
|
+
[...args],
|
|
30550
|
+
{ windowsHide: true },
|
|
30551
|
+
(error, stdout) => {
|
|
30552
|
+
if (error) reject(commandError(error, "Login-item query failed"));
|
|
30553
|
+
else resolve4(stdout);
|
|
30554
|
+
}
|
|
30555
|
+
);
|
|
30556
|
+
});
|
|
30557
|
+
}
|
|
30558
|
+
async function setCompanionStartAtLogin(input) {
|
|
30559
|
+
if (input.platform === "darwin") {
|
|
30560
|
+
const launchAgents = path35.join(input.homeDirectory, "Library", "LaunchAgents");
|
|
30561
|
+
const registration = path35.join(
|
|
30562
|
+
launchAgents,
|
|
30563
|
+
"com.m8t.companion.plist"
|
|
30564
|
+
);
|
|
30565
|
+
await assertNotSymlink(registration, "Start-at-login registration");
|
|
30566
|
+
if (!input.enabled) {
|
|
30567
|
+
await fs32.rm(registration, { force: true });
|
|
30568
|
+
return;
|
|
30569
|
+
}
|
|
30570
|
+
await fs32.mkdir(launchAgents, { recursive: true, mode: 448 });
|
|
30571
|
+
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";
|
|
30572
|
+
await atomicWriteText(registration, plist, 384);
|
|
30573
|
+
return;
|
|
30574
|
+
}
|
|
30575
|
+
if (input.platform === "win32") {
|
|
30576
|
+
const key2 = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
|
|
30577
|
+
const run2 = input.runCommand ?? (async (file, args) => {
|
|
30578
|
+
await execFileAsync(file, args);
|
|
30579
|
+
return "";
|
|
30580
|
+
});
|
|
30581
|
+
if (input.enabled) {
|
|
30582
|
+
await run2(
|
|
30583
|
+
"reg.exe",
|
|
30584
|
+
[
|
|
30585
|
+
"add",
|
|
30586
|
+
key2,
|
|
30587
|
+
"/v",
|
|
30588
|
+
"m8t Companion",
|
|
30589
|
+
"/t",
|
|
30590
|
+
"REG_SZ",
|
|
30591
|
+
"/d",
|
|
30592
|
+
`"${input.executable}"`,
|
|
30593
|
+
"/f"
|
|
30594
|
+
]
|
|
30595
|
+
);
|
|
30596
|
+
} else {
|
|
30597
|
+
try {
|
|
30598
|
+
await run2("reg.exe", [
|
|
30599
|
+
"query",
|
|
30600
|
+
key2,
|
|
30601
|
+
"/v",
|
|
30602
|
+
"m8t Companion"
|
|
30603
|
+
]);
|
|
30604
|
+
} catch (error) {
|
|
30605
|
+
if (error.code === 1) return;
|
|
30606
|
+
throw error;
|
|
30607
|
+
}
|
|
30608
|
+
await run2("reg.exe", [
|
|
30609
|
+
"delete",
|
|
30610
|
+
key2,
|
|
30611
|
+
"/v",
|
|
30612
|
+
"m8t Companion",
|
|
30613
|
+
"/f"
|
|
30614
|
+
]);
|
|
30615
|
+
}
|
|
30616
|
+
return;
|
|
30617
|
+
}
|
|
30618
|
+
throw new Error("Start-at-login is unsupported on this platform");
|
|
30619
|
+
}
|
|
30620
|
+
async function getCompanionStartAtLogin(input) {
|
|
30621
|
+
if (input.platform === "darwin") {
|
|
30622
|
+
const registration = path35.join(
|
|
30623
|
+
input.homeDirectory,
|
|
30624
|
+
"Library",
|
|
30625
|
+
"LaunchAgents",
|
|
30626
|
+
"com.m8t.companion.plist"
|
|
30627
|
+
);
|
|
30628
|
+
try {
|
|
30629
|
+
const contents = await readRegularText(registration, 64 * 1024);
|
|
30630
|
+
return contents.includes(`<string>${xmlEscape(input.executable)}</string>`);
|
|
30631
|
+
} catch (error) {
|
|
30632
|
+
if (error.code === "ENOENT") return false;
|
|
30633
|
+
return false;
|
|
30634
|
+
}
|
|
30635
|
+
}
|
|
30636
|
+
if (input.platform === "win32") {
|
|
30637
|
+
try {
|
|
30638
|
+
const output = await execFileOutput("reg.exe", [
|
|
30639
|
+
"query",
|
|
30640
|
+
"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run",
|
|
30641
|
+
"/v",
|
|
30642
|
+
"m8t Companion"
|
|
30643
|
+
]);
|
|
30644
|
+
return output.includes(`"${input.executable}"`);
|
|
30645
|
+
} catch {
|
|
30646
|
+
return false;
|
|
30647
|
+
}
|
|
30648
|
+
}
|
|
30649
|
+
return false;
|
|
30650
|
+
}
|
|
30651
|
+
var INSTALL_KEYS = [
|
|
30652
|
+
"schemaVersion",
|
|
30653
|
+
"version",
|
|
30654
|
+
"platform",
|
|
30655
|
+
"architecture",
|
|
30656
|
+
"entryRelativePath",
|
|
30657
|
+
"artifactTreeSha256",
|
|
30658
|
+
"ownedTargetRoot"
|
|
30659
|
+
];
|
|
30660
|
+
var RUNTIME_KEYS = [
|
|
30661
|
+
"schemaVersion",
|
|
30662
|
+
"nodeExecutable",
|
|
30663
|
+
"cliEntry",
|
|
30664
|
+
"gatewayOrigin"
|
|
30665
|
+
];
|
|
30666
|
+
function isSupportedPlatform(platform) {
|
|
30667
|
+
return platform === "darwin" || platform === "win32";
|
|
30668
|
+
}
|
|
30669
|
+
function isSupportedArchitecture(architecture) {
|
|
30670
|
+
return architecture === "arm64" || architecture === "x64";
|
|
30671
|
+
}
|
|
30672
|
+
function companionsSupportHost(platform = process.platform, architecture = process.arch) {
|
|
30673
|
+
return isSupportedPlatform(platform) && isSupportedArchitecture(architecture);
|
|
30674
|
+
}
|
|
30675
|
+
function assertSupported(options) {
|
|
30676
|
+
if (!isSupportedPlatform(options.platform) || !isSupportedArchitecture(options.architecture)) {
|
|
30677
|
+
throw new Error(
|
|
30678
|
+
"Desktop companions are supported on macOS and Windows arm64/x64. Run: m8t companion status"
|
|
30679
|
+
);
|
|
30680
|
+
}
|
|
30681
|
+
}
|
|
30682
|
+
function companionInstallPaths(options) {
|
|
30683
|
+
assertSupported(options);
|
|
30684
|
+
const companionState = path35.join(
|
|
30685
|
+
options.homeDirectory,
|
|
30686
|
+
".m8t",
|
|
30687
|
+
"companion"
|
|
30688
|
+
);
|
|
30689
|
+
const targetRoot = options.platform === "darwin" ? path35.join(
|
|
30690
|
+
options.homeDirectory,
|
|
30691
|
+
"Applications",
|
|
30692
|
+
"m8t Companion.app"
|
|
30693
|
+
) : path35.join(
|
|
30694
|
+
options.localAppData ?? path35.join(options.homeDirectory, "AppData", "Local"),
|
|
30695
|
+
"m8t",
|
|
30696
|
+
"companion",
|
|
30697
|
+
"app"
|
|
30698
|
+
);
|
|
30699
|
+
return {
|
|
30700
|
+
targetRoot,
|
|
30701
|
+
installManifest: path35.join(companionState, "install-v1.json"),
|
|
30702
|
+
runtimeBinding: path35.join(companionState, "runtime-v1.json"),
|
|
30703
|
+
preferences: path35.join(companionState, "preferences-v1.json")
|
|
30704
|
+
};
|
|
30705
|
+
}
|
|
30706
|
+
function exactKeys2(value, keys) {
|
|
30707
|
+
return Object.keys(value).length === keys.length && keys.every((key2) => Object.hasOwn(value, key2));
|
|
30708
|
+
}
|
|
30709
|
+
function parseInstallManifest(value) {
|
|
30710
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
30711
|
+
throw new Error("Install manifest schema is invalid");
|
|
30712
|
+
}
|
|
30713
|
+
const record = value;
|
|
30714
|
+
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" || !path35.isAbsolute(record.ownedTargetRoot)) {
|
|
30715
|
+
throw new Error("Install manifest schema is invalid");
|
|
30716
|
+
}
|
|
30717
|
+
return record;
|
|
30718
|
+
}
|
|
30719
|
+
function parseRuntimeBinding(value, platform) {
|
|
30720
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
30721
|
+
throw new Error("Runtime binding schema is invalid");
|
|
30722
|
+
}
|
|
30723
|
+
const record = value;
|
|
30724
|
+
const paths = platform === "win32" ? path35.win32 : path35.posix;
|
|
30725
|
+
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") {
|
|
30726
|
+
throw new Error("Runtime binding schema is invalid");
|
|
30727
|
+
}
|
|
30728
|
+
return {
|
|
30729
|
+
schemaVersion: 1,
|
|
30730
|
+
nodeExecutable: record.nodeExecutable,
|
|
30731
|
+
cliEntry: record.cliEntry,
|
|
30732
|
+
gatewayOrigin: validateGatewayOrigin(record.gatewayOrigin)
|
|
30733
|
+
};
|
|
30734
|
+
}
|
|
30735
|
+
function validateGatewayOrigin(value) {
|
|
30736
|
+
let url;
|
|
30737
|
+
try {
|
|
30738
|
+
url = new URL(value);
|
|
30739
|
+
} catch {
|
|
30740
|
+
throw new Error("Companion gateway origin is invalid");
|
|
30741
|
+
}
|
|
30742
|
+
if (url.protocol !== "https:" || url.origin !== value || url.username !== "" || url.password !== "" || url.pathname !== "/" || url.search !== "" || url.hash !== "") {
|
|
30743
|
+
throw new Error("Companion gateway origin must be an HTTPS origin");
|
|
30744
|
+
}
|
|
30745
|
+
return url.origin;
|
|
30746
|
+
}
|
|
30747
|
+
async function assertNotSymlink(filePath, kind) {
|
|
30748
|
+
try {
|
|
30749
|
+
if ((await fs32.lstat(filePath)).isSymbolicLink()) {
|
|
30750
|
+
throw new Error(`${kind} is a symbolic link`);
|
|
30751
|
+
}
|
|
30752
|
+
} catch (error) {
|
|
30753
|
+
if (error.code === "ENOENT") return;
|
|
30754
|
+
throw error;
|
|
30755
|
+
}
|
|
30756
|
+
}
|
|
30757
|
+
async function assertOwnedDirectoryChain(anchor, targetDirectory) {
|
|
30758
|
+
const relative3 = path35.relative(anchor, targetDirectory);
|
|
30759
|
+
if (relative3 === ".." || relative3.startsWith(`..${path35.sep}`) || path35.isAbsolute(relative3)) {
|
|
30760
|
+
throw new Error("Companion owned directory escapes its trusted anchor");
|
|
30761
|
+
}
|
|
30762
|
+
const segments = relative3.split(path35.sep).filter(Boolean);
|
|
30763
|
+
let current = anchor;
|
|
30764
|
+
const anchorStatus = await fs32.lstat(anchor);
|
|
30765
|
+
if (anchorStatus.isSymbolicLink() || !anchorStatus.isDirectory()) {
|
|
30766
|
+
throw new Error("Companion owned directory anchor is unsafe");
|
|
30767
|
+
}
|
|
30768
|
+
for (const segment of segments) {
|
|
30769
|
+
current = path35.join(current, segment);
|
|
30770
|
+
try {
|
|
30771
|
+
const status = await fs32.lstat(current);
|
|
30772
|
+
if (status.isSymbolicLink() || !status.isDirectory()) {
|
|
30773
|
+
throw new Error("Companion owned directory contains a symbolic link");
|
|
30774
|
+
}
|
|
30775
|
+
} catch (error) {
|
|
30776
|
+
if (error.code === "ENOENT") return;
|
|
30777
|
+
throw error;
|
|
30778
|
+
}
|
|
30779
|
+
}
|
|
30780
|
+
}
|
|
30781
|
+
async function assertOwnedParents(options, paths) {
|
|
30782
|
+
await assertOwnedDirectoryChain(
|
|
30783
|
+
options.homeDirectory,
|
|
30784
|
+
path35.dirname(paths.installManifest)
|
|
30785
|
+
);
|
|
30786
|
+
const targetAnchor = options.platform === "win32" ? options.localAppData ?? path35.join(options.homeDirectory, "AppData", "Local") : options.homeDirectory;
|
|
30787
|
+
await assertOwnedDirectoryChain(targetAnchor, path35.dirname(paths.targetRoot));
|
|
30788
|
+
}
|
|
30789
|
+
async function readRegularText(filePath, maxBytes) {
|
|
30790
|
+
const before = await fs32.lstat(filePath);
|
|
30791
|
+
if (before.isSymbolicLink() || !before.isFile()) {
|
|
30792
|
+
throw new Error("Companion state file is not a regular file");
|
|
30793
|
+
}
|
|
30794
|
+
let handle;
|
|
30795
|
+
try {
|
|
30796
|
+
handle = await fs32.open(
|
|
30797
|
+
filePath,
|
|
30798
|
+
constants2.O_RDONLY | constants2.O_NOFOLLOW
|
|
30799
|
+
);
|
|
30800
|
+
const opened = await handle.stat();
|
|
30801
|
+
const sameFile = before.ino === 0 && opened.ino === 0 || before.dev === opened.dev && before.ino === opened.ino;
|
|
30802
|
+
if (!opened.isFile() || !sameFile || opened.size > maxBytes) {
|
|
30803
|
+
throw new Error("Companion state file is not a bounded regular file");
|
|
30804
|
+
}
|
|
30805
|
+
return await handle.readFile("utf8");
|
|
30806
|
+
} finally {
|
|
30807
|
+
await handle?.close().catch(() => void 0);
|
|
30808
|
+
}
|
|
30809
|
+
}
|
|
30810
|
+
async function readClosedJson(filePath) {
|
|
30811
|
+
return JSON.parse(await readRegularText(filePath, 64 * 1024));
|
|
30812
|
+
}
|
|
30813
|
+
async function atomicWriteJson(filePath, value) {
|
|
30814
|
+
await atomicWriteText(filePath, `${JSON.stringify(value)}
|
|
30815
|
+
`, 384);
|
|
30816
|
+
}
|
|
30817
|
+
async function atomicWriteText(filePath, contents, mode) {
|
|
30818
|
+
await assertNotSymlink(filePath, "Companion state file");
|
|
30819
|
+
await fs32.mkdir(path35.dirname(filePath), {
|
|
30820
|
+
recursive: true,
|
|
30821
|
+
mode: 448
|
|
30822
|
+
});
|
|
30823
|
+
const temporary = `${filePath}.${randomUUID3()}.tmp`;
|
|
30824
|
+
try {
|
|
30825
|
+
await fs32.writeFile(temporary, contents, {
|
|
30826
|
+
mode,
|
|
30827
|
+
flag: "wx"
|
|
30828
|
+
});
|
|
30829
|
+
await fs32.rename(temporary, filePath);
|
|
30830
|
+
await fs32.chmod(filePath, mode).catch(() => void 0);
|
|
30831
|
+
} finally {
|
|
30832
|
+
await fs32.rm(temporary, { force: true }).catch(() => void 0);
|
|
30833
|
+
}
|
|
30834
|
+
}
|
|
30835
|
+
async function realRegularFile(filePath, executable) {
|
|
30836
|
+
const real = await fs32.realpath(filePath);
|
|
30837
|
+
const stat4 = await fs32.stat(real);
|
|
30838
|
+
if (!stat4.isFile()) throw new Error("Companion launch target is not a file");
|
|
30839
|
+
await fs32.access(real, executable ? constants2.X_OK : constants2.R_OK);
|
|
30840
|
+
return real;
|
|
30841
|
+
}
|
|
30842
|
+
function defaultLaunch(executable) {
|
|
30843
|
+
const child = spawn7(executable, [], {
|
|
30844
|
+
detached: true,
|
|
30845
|
+
stdio: "ignore",
|
|
30846
|
+
windowsHide: true
|
|
30847
|
+
});
|
|
30848
|
+
child.unref();
|
|
30849
|
+
return Promise.resolve();
|
|
30850
|
+
}
|
|
30851
|
+
async function readInstalled(options) {
|
|
30852
|
+
const paths = companionInstallPaths(options);
|
|
30853
|
+
await assertNotSymlink(paths.installManifest, "Install manifest");
|
|
30854
|
+
try {
|
|
30855
|
+
return {
|
|
30856
|
+
paths,
|
|
30857
|
+
install: parseInstallManifest(
|
|
30858
|
+
await readClosedJson(paths.installManifest)
|
|
30859
|
+
)
|
|
30860
|
+
};
|
|
30861
|
+
} catch (error) {
|
|
30862
|
+
if (error.code === "ENOENT") return null;
|
|
30863
|
+
throw error;
|
|
30864
|
+
}
|
|
30865
|
+
}
|
|
30866
|
+
async function statusCompanion(options) {
|
|
30867
|
+
assertSupported(options);
|
|
30868
|
+
const resolvedPaths = companionInstallPaths(options);
|
|
30869
|
+
try {
|
|
30870
|
+
await assertOwnedParents(options, resolvedPaths);
|
|
30871
|
+
} catch (error) {
|
|
30872
|
+
return {
|
|
30873
|
+
state: "needs-repair",
|
|
30874
|
+
reason: error instanceof Error ? error.message : "Unsafe owned directory"
|
|
30875
|
+
};
|
|
30876
|
+
}
|
|
30877
|
+
let installed;
|
|
30878
|
+
try {
|
|
30879
|
+
installed = await readInstalled(options);
|
|
30880
|
+
} catch (error) {
|
|
30881
|
+
return {
|
|
30882
|
+
state: "needs-repair",
|
|
30883
|
+
reason: error instanceof Error ? error.message : "Invalid install manifest"
|
|
30884
|
+
};
|
|
30885
|
+
}
|
|
30886
|
+
if (!installed) return { state: "not-installed" };
|
|
30887
|
+
const { paths, install } = installed;
|
|
30888
|
+
if (install.platform !== options.platform || install.architecture !== options.architecture || install.ownedTargetRoot !== paths.targetRoot) {
|
|
30889
|
+
return {
|
|
30890
|
+
state: "needs-repair",
|
|
30891
|
+
reason: "Install manifest does not own the fixed companion target"
|
|
30892
|
+
};
|
|
30893
|
+
}
|
|
30894
|
+
try {
|
|
30895
|
+
await assertNotSymlink(paths.targetRoot, "Install target");
|
|
30896
|
+
const actual = await buildArtifactManifest(paths.targetRoot, {
|
|
30897
|
+
platform: install.platform,
|
|
30898
|
+
architecture: install.architecture,
|
|
30899
|
+
version: install.version,
|
|
30900
|
+
entryRelativePath: install.entryRelativePath
|
|
30901
|
+
});
|
|
30902
|
+
if (actual.treeSha256 !== install.artifactTreeSha256) {
|
|
30903
|
+
throw new Error("Installed artifact digest does not match");
|
|
30904
|
+
}
|
|
30905
|
+
const runtime = parseRuntimeBinding(
|
|
30906
|
+
await readClosedJson(paths.runtimeBinding),
|
|
30907
|
+
options.platform
|
|
30908
|
+
);
|
|
30909
|
+
await Promise.all([
|
|
30910
|
+
realRegularFile(runtime.nodeExecutable, options.platform !== "win32"),
|
|
30911
|
+
realRegularFile(runtime.cliEntry, false)
|
|
30912
|
+
]);
|
|
30913
|
+
const executable = path35.join(
|
|
30914
|
+
paths.targetRoot,
|
|
30915
|
+
...install.entryRelativePath.split("/")
|
|
30916
|
+
);
|
|
30917
|
+
await realRegularFile(executable, options.platform !== "win32");
|
|
30918
|
+
return {
|
|
30919
|
+
state: "installed",
|
|
30920
|
+
version: install.version,
|
|
30921
|
+
executable,
|
|
30922
|
+
startAtLogin: await (options.getStartAtLogin ?? ((ownedExecutable) => getCompanionStartAtLogin({
|
|
30923
|
+
homeDirectory: options.homeDirectory,
|
|
30924
|
+
platform: options.platform,
|
|
30925
|
+
executable: ownedExecutable
|
|
30926
|
+
})))(executable),
|
|
30927
|
+
artifactTreeSha256: install.artifactTreeSha256
|
|
30928
|
+
};
|
|
30929
|
+
} catch (error) {
|
|
30930
|
+
return {
|
|
30931
|
+
state: "needs-repair",
|
|
30932
|
+
reason: error instanceof Error ? error.message : "Installed artifact is invalid"
|
|
30933
|
+
};
|
|
30934
|
+
}
|
|
30935
|
+
}
|
|
30936
|
+
async function snapshotFile(filePath) {
|
|
30937
|
+
try {
|
|
30938
|
+
return Buffer.from(await readRegularText(filePath, 64 * 1024), "utf8");
|
|
30939
|
+
} catch (error) {
|
|
30940
|
+
if (error.code === "ENOENT") return null;
|
|
30941
|
+
throw error;
|
|
30942
|
+
}
|
|
30943
|
+
}
|
|
30944
|
+
async function restoreFile(filePath, bytes) {
|
|
30945
|
+
if (bytes === null) {
|
|
30946
|
+
await fs32.rm(filePath, { force: true });
|
|
30947
|
+
} else {
|
|
30948
|
+
await atomicWriteJson(filePath, JSON.parse(bytes.toString("utf8")));
|
|
30949
|
+
}
|
|
30950
|
+
}
|
|
30951
|
+
async function converge(options, force) {
|
|
30952
|
+
assertSupported(options);
|
|
30953
|
+
const paths = companionInstallPaths(options);
|
|
30954
|
+
await assertOwnedParents(options, paths);
|
|
30955
|
+
const artifactManifest = await readArtifactManifest(
|
|
30956
|
+
options.artifactManifestPath
|
|
30957
|
+
);
|
|
30958
|
+
if (artifactManifest.platform !== options.platform || artifactManifest.architecture !== options.architecture) {
|
|
30959
|
+
throw new Error("Companion artifact does not match this OS and architecture");
|
|
30960
|
+
}
|
|
30961
|
+
const payloadRoot = path35.join(
|
|
30962
|
+
path35.dirname(options.artifactManifestPath),
|
|
30963
|
+
"payload"
|
|
30964
|
+
);
|
|
30965
|
+
await verifyArtifactSource(payloadRoot, artifactManifest);
|
|
30966
|
+
for (const boundary of [
|
|
30967
|
+
paths.targetRoot,
|
|
30968
|
+
paths.installManifest,
|
|
30969
|
+
paths.runtimeBinding,
|
|
30970
|
+
paths.preferences
|
|
30971
|
+
]) {
|
|
30972
|
+
await assertNotSymlink(boundary, "Companion owned boundary");
|
|
30973
|
+
}
|
|
30974
|
+
const current = await statusCompanion(options);
|
|
30975
|
+
const priorInstalled = await readInstalled(options);
|
|
30976
|
+
if (!force && current.state === "installed" && current.version === artifactManifest.version && current.artifactTreeSha256 === artifactManifest.treeSha256 && current.startAtLogin) {
|
|
30977
|
+
return current;
|
|
30978
|
+
}
|
|
30979
|
+
if (current.state === "not-installed") {
|
|
30980
|
+
try {
|
|
30981
|
+
await fs32.lstat(paths.targetRoot);
|
|
30982
|
+
throw new Error(
|
|
30983
|
+
"The fixed companion target exists without an owned install manifest"
|
|
30984
|
+
);
|
|
30985
|
+
} catch (error) {
|
|
30986
|
+
if (error.code !== "ENOENT") throw error;
|
|
30987
|
+
}
|
|
30988
|
+
}
|
|
30989
|
+
const stage = `${paths.targetRoot}.m8t-stage-${randomUUID3()}`;
|
|
30990
|
+
const backup = `${paths.targetRoot}.m8t-backup-${randomUUID3()}`;
|
|
30991
|
+
const copy = options.copyPayload ?? copyArtifactPayload;
|
|
30992
|
+
await fs32.mkdir(path35.dirname(paths.targetRoot), {
|
|
30993
|
+
recursive: true,
|
|
30994
|
+
mode: 448
|
|
30995
|
+
});
|
|
30996
|
+
let movedPrior = false;
|
|
30997
|
+
let installedStage = false;
|
|
30998
|
+
let loginChanged = false;
|
|
30999
|
+
let priorLoginState = false;
|
|
31000
|
+
let priorLoginExecutable;
|
|
31001
|
+
const snapshots = await Promise.all([
|
|
31002
|
+
snapshotFile(paths.installManifest),
|
|
31003
|
+
snapshotFile(paths.runtimeBinding),
|
|
31004
|
+
snapshotFile(paths.preferences)
|
|
31005
|
+
]);
|
|
31006
|
+
try {
|
|
31007
|
+
await copy(payloadRoot, stage, artifactManifest);
|
|
31008
|
+
try {
|
|
31009
|
+
await fs32.rename(paths.targetRoot, backup);
|
|
31010
|
+
movedPrior = true;
|
|
31011
|
+
} catch (error) {
|
|
31012
|
+
if (error.code !== "ENOENT") throw error;
|
|
31013
|
+
}
|
|
31014
|
+
await fs32.rename(stage, paths.targetRoot);
|
|
31015
|
+
installedStage = true;
|
|
31016
|
+
const nodeExecutable = await realRegularFile(
|
|
31017
|
+
options.nodeExecutable,
|
|
31018
|
+
options.platform !== "win32"
|
|
31019
|
+
);
|
|
31020
|
+
const cliEntry = await realRegularFile(options.cliEntry, false);
|
|
31021
|
+
if (options.gatewayOrigin === void 0) {
|
|
31022
|
+
throw new Error("Companion gateway origin is required for installation");
|
|
31023
|
+
}
|
|
31024
|
+
const gatewayOrigin2 = validateGatewayOrigin(options.gatewayOrigin);
|
|
31025
|
+
const install = {
|
|
31026
|
+
schemaVersion: 1,
|
|
31027
|
+
version: artifactManifest.version,
|
|
31028
|
+
platform: artifactManifest.platform,
|
|
31029
|
+
architecture: artifactManifest.architecture,
|
|
31030
|
+
entryRelativePath: artifactManifest.entryRelativePath,
|
|
31031
|
+
artifactTreeSha256: artifactManifest.treeSha256,
|
|
31032
|
+
ownedTargetRoot: paths.targetRoot
|
|
31033
|
+
};
|
|
31034
|
+
const runtime = {
|
|
31035
|
+
schemaVersion: 1,
|
|
31036
|
+
nodeExecutable,
|
|
31037
|
+
cliEntry,
|
|
31038
|
+
gatewayOrigin: gatewayOrigin2
|
|
31039
|
+
};
|
|
31040
|
+
const preferences = {
|
|
31041
|
+
geometry: { edge: "right", offset: 0.5 },
|
|
31042
|
+
userHidden: false,
|
|
31043
|
+
startAtLogin: true,
|
|
31044
|
+
automaticInstall: false,
|
|
31045
|
+
lastRelativeRoute: null,
|
|
31046
|
+
gatewayOrigin: gatewayOrigin2
|
|
31047
|
+
};
|
|
31048
|
+
await atomicWriteJson(paths.installManifest, install);
|
|
31049
|
+
await atomicWriteJson(paths.runtimeBinding, runtime);
|
|
31050
|
+
await atomicWriteJson(paths.preferences, preferences);
|
|
31051
|
+
parseInstallManifest(await readClosedJson(paths.installManifest));
|
|
31052
|
+
parseRuntimeBinding(
|
|
31053
|
+
await readClosedJson(paths.runtimeBinding),
|
|
31054
|
+
options.platform
|
|
31055
|
+
);
|
|
31056
|
+
const executable = path35.join(
|
|
31057
|
+
paths.targetRoot,
|
|
31058
|
+
...artifactManifest.entryRelativePath.split("/")
|
|
31059
|
+
);
|
|
31060
|
+
await realRegularFile(executable, options.platform !== "win32");
|
|
31061
|
+
const getLogin = options.getStartAtLogin ?? ((ownedExecutable) => getCompanionStartAtLogin({
|
|
31062
|
+
homeDirectory: options.homeDirectory,
|
|
31063
|
+
platform: options.platform,
|
|
31064
|
+
executable: ownedExecutable
|
|
31065
|
+
}));
|
|
31066
|
+
priorLoginExecutable = priorInstalled ? path35.join(
|
|
31067
|
+
priorInstalled.paths.targetRoot,
|
|
31068
|
+
...priorInstalled.install.entryRelativePath.split("/")
|
|
31069
|
+
) : executable;
|
|
31070
|
+
priorLoginState = await getLogin(priorLoginExecutable);
|
|
31071
|
+
await (options.setStartAtLogin ?? (() => Promise.resolve()))(
|
|
31072
|
+
executable,
|
|
31073
|
+
true
|
|
31074
|
+
);
|
|
31075
|
+
loginChanged = true;
|
|
31076
|
+
await (options.launch ?? defaultLaunch)(executable);
|
|
31077
|
+
await fs32.rm(backup, { recursive: true, force: true });
|
|
31078
|
+
return {
|
|
31079
|
+
state: "installed",
|
|
31080
|
+
version: artifactManifest.version,
|
|
31081
|
+
executable,
|
|
31082
|
+
startAtLogin: true,
|
|
31083
|
+
artifactTreeSha256: artifactManifest.treeSha256
|
|
31084
|
+
};
|
|
31085
|
+
} catch (error) {
|
|
31086
|
+
if (loginChanged) {
|
|
31087
|
+
await (options.setStartAtLogin ?? (() => Promise.resolve()))(priorLoginExecutable ?? paths.targetRoot, priorLoginState).catch(
|
|
31088
|
+
() => void 0
|
|
31089
|
+
);
|
|
31090
|
+
}
|
|
31091
|
+
await fs32.rm(stage, { recursive: true, force: true }).catch(() => void 0);
|
|
31092
|
+
if (installedStage) {
|
|
31093
|
+
await fs32.rm(paths.targetRoot, {
|
|
31094
|
+
recursive: true,
|
|
31095
|
+
force: true
|
|
31096
|
+
}).catch(() => void 0);
|
|
31097
|
+
}
|
|
31098
|
+
if (movedPrior) {
|
|
31099
|
+
await fs32.rename(backup, paths.targetRoot).catch(() => void 0);
|
|
31100
|
+
}
|
|
31101
|
+
await Promise.all([
|
|
31102
|
+
restoreFile(paths.installManifest, snapshots[0]),
|
|
31103
|
+
restoreFile(paths.runtimeBinding, snapshots[1]),
|
|
31104
|
+
restoreFile(paths.preferences, snapshots[2])
|
|
31105
|
+
]).catch(() => void 0);
|
|
31106
|
+
throw error;
|
|
31107
|
+
}
|
|
31108
|
+
}
|
|
31109
|
+
async function installCompanion(options) {
|
|
31110
|
+
return converge(options, false);
|
|
31111
|
+
}
|
|
31112
|
+
async function repairCompanion(options) {
|
|
31113
|
+
return converge(options, true);
|
|
31114
|
+
}
|
|
31115
|
+
async function uninstallCompanion(options) {
|
|
31116
|
+
assertSupported(options);
|
|
31117
|
+
const expectedPaths = companionInstallPaths(options);
|
|
31118
|
+
await assertOwnedParents(options, expectedPaths);
|
|
31119
|
+
const installed = await readInstalled(options);
|
|
31120
|
+
if (!installed) return { state: "not-installed" };
|
|
31121
|
+
const { paths, install } = installed;
|
|
31122
|
+
if (install.ownedTargetRoot !== paths.targetRoot) {
|
|
31123
|
+
throw new Error("Refusing to remove a non-owned companion target");
|
|
31124
|
+
}
|
|
31125
|
+
const executable = path35.join(
|
|
31126
|
+
paths.targetRoot,
|
|
31127
|
+
...install.entryRelativePath.split("/")
|
|
31128
|
+
);
|
|
31129
|
+
await (options.setStartAtLogin ?? (() => Promise.resolve()))(executable, false);
|
|
31130
|
+
const remove = options.removeOwnedPath ?? (async (ownedPath, recursive) => {
|
|
31131
|
+
await fs32.rm(ownedPath, { recursive, force: true });
|
|
31132
|
+
});
|
|
31133
|
+
await remove(paths.targetRoot, true);
|
|
31134
|
+
await remove(paths.installManifest, false);
|
|
31135
|
+
await remove(paths.runtimeBinding, false);
|
|
31136
|
+
await remove(paths.preferences, false);
|
|
31137
|
+
return { state: "not-installed" };
|
|
31138
|
+
}
|
|
31139
|
+
|
|
31140
|
+
// src/commands/companion/install.ts
|
|
31141
|
+
import * as os17 from "os";
|
|
31142
|
+
import * as path37 from "path";
|
|
31143
|
+
import { Command as Command57, Option as Option54 } from "clipanion";
|
|
31144
|
+
|
|
31145
|
+
// src/lib/companion-channel.ts
|
|
31146
|
+
async function readCompanionRelease(source = { url: CHANNEL_LATEST_URL }, deps = {}) {
|
|
31147
|
+
const manifest = await fetchManifest(source, deps);
|
|
31148
|
+
const component = manifest.components.companion;
|
|
31149
|
+
if (!component) return null;
|
|
31150
|
+
return {
|
|
31151
|
+
component,
|
|
31152
|
+
severity: manifest.platform.severity,
|
|
31153
|
+
platformVersion: manifest.platform.version
|
|
31154
|
+
};
|
|
31155
|
+
}
|
|
31156
|
+
|
|
31157
|
+
// src/lib/companion-download.ts
|
|
31158
|
+
import { createHash as createHash6 } from "crypto";
|
|
31159
|
+
import { execFile as execFile2 } from "child_process";
|
|
31160
|
+
import * as fs33 from "fs/promises";
|
|
31161
|
+
import * as os16 from "os";
|
|
31162
|
+
import * as path36 from "path";
|
|
31163
|
+
init_errors();
|
|
31164
|
+
var MAX_ASSET_BYTES = 512 * 1024 * 1024;
|
|
31165
|
+
function extractArchive(archive, into) {
|
|
31166
|
+
return new Promise((resolve4, reject) => {
|
|
31167
|
+
execFile2("tar", ["-xzf", archive, "-C", into], { windowsHide: true }, (error) => {
|
|
31168
|
+
if (error) reject(new Error(`Could not unpack the companion build: ${error.message}`));
|
|
31169
|
+
else resolve4();
|
|
31170
|
+
});
|
|
31171
|
+
});
|
|
31172
|
+
}
|
|
31173
|
+
function companionAssetFor(component, platform = process.platform, architecture = process.arch) {
|
|
31174
|
+
const target = companionTargetFor(platform, architecture);
|
|
31175
|
+
if (target === null) return null;
|
|
31176
|
+
const pinned = component.targets[target];
|
|
31177
|
+
return pinned ? { target, asset: pinned.asset, sha256: pinned.sha256 } : null;
|
|
31178
|
+
}
|
|
31179
|
+
async function downloadCompanionArtifact(component, deps = {}) {
|
|
31180
|
+
const platform = deps.platform ?? process.platform;
|
|
31181
|
+
const architecture = deps.architecture ?? process.arch;
|
|
31182
|
+
const pinned = companionAssetFor(component, platform, architecture);
|
|
31183
|
+
if (pinned === null) {
|
|
31184
|
+
throw new LocalCliError({
|
|
31185
|
+
code: "COMPANION_NO_BUILD_FOR_HOST",
|
|
31186
|
+
message: `Release ${component.version} carries no desktop companion for ${platform}-${architecture}.`,
|
|
31187
|
+
hint: "The companions are built for macOS and Windows on arm64 and x64."
|
|
31188
|
+
});
|
|
31189
|
+
}
|
|
31190
|
+
const url = releaseAssetUrl(component.tag, pinned.asset);
|
|
31191
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
31192
|
+
const response = await fetchImpl(url);
|
|
31193
|
+
if (!response.ok) {
|
|
31194
|
+
throw new LocalCliError({
|
|
31195
|
+
code: "COMPANION_ASSET_FETCH_FAILED",
|
|
31196
|
+
message: `GET ${url} returned HTTP ${response.status.toString()}.`,
|
|
31197
|
+
hint: "The release the platform is pinned to should carry this file. Try again, or run 'm8t companion repair' once the network is back."
|
|
31198
|
+
});
|
|
31199
|
+
}
|
|
31200
|
+
const bytes = Buffer.from(await response.arrayBuffer());
|
|
31201
|
+
if (bytes.byteLength > MAX_ASSET_BYTES) {
|
|
31202
|
+
throw new LocalCliError({
|
|
31203
|
+
code: "COMPANION_ASSET_TOO_LARGE",
|
|
31204
|
+
message: `${pinned.asset} is larger than this command will accept.`
|
|
31205
|
+
});
|
|
31206
|
+
}
|
|
31207
|
+
const digest = createHash6("sha256").update(bytes).digest("hex");
|
|
31208
|
+
if (digest !== pinned.sha256) {
|
|
31209
|
+
throw new LocalCliError({
|
|
31210
|
+
code: "COMPANION_ASSET_DIGEST_MISMATCH",
|
|
31211
|
+
message: `${pinned.asset} does not match the digest the release pins for it.`,
|
|
31212
|
+
hint: "Nothing was unpacked. This is what a corrupted download or a substituted file looks like \u2014 retry, and report it if it persists."
|
|
31213
|
+
});
|
|
31214
|
+
}
|
|
31215
|
+
const root = await (deps.makeTemporaryDirectory ?? (() => fs33.mkdtemp(path36.join(os16.tmpdir(), "m8t-companion-"))))();
|
|
31216
|
+
const dispose = async () => {
|
|
31217
|
+
await fs33.rm(root, { recursive: true, force: true }).catch(() => void 0);
|
|
31218
|
+
};
|
|
31219
|
+
try {
|
|
31220
|
+
const archive = path36.join(root, pinned.asset);
|
|
31221
|
+
await fs33.writeFile(archive, bytes, { mode: 384 });
|
|
31222
|
+
const unpacked = path36.join(root, "unpacked");
|
|
31223
|
+
await fs33.mkdir(unpacked, { recursive: false, mode: 448 });
|
|
31224
|
+
await (deps.extract ?? extractArchive)(archive, unpacked);
|
|
31225
|
+
await fs33.rm(archive, { force: true });
|
|
31226
|
+
return {
|
|
31227
|
+
version: component.version,
|
|
31228
|
+
artifactManifestPath: path36.join(unpacked, "artifact-v1.json"),
|
|
31229
|
+
dispose
|
|
31230
|
+
};
|
|
31231
|
+
} catch (error) {
|
|
31232
|
+
await dispose();
|
|
31233
|
+
throw error;
|
|
31234
|
+
}
|
|
31235
|
+
}
|
|
31236
|
+
|
|
31237
|
+
// src/commands/companion/install.ts
|
|
31238
|
+
function defaultLocalCompanionInstallOptions() {
|
|
31239
|
+
const cliEntry = process.argv[1];
|
|
31240
|
+
if (!cliEntry) throw new Error("Cannot resolve the installed CLI entry");
|
|
31241
|
+
const homeDirectory = os17.homedir();
|
|
31242
|
+
const platform = process.platform;
|
|
31243
|
+
return {
|
|
31244
|
+
homeDirectory,
|
|
31245
|
+
platform,
|
|
31246
|
+
architecture: process.arch,
|
|
31247
|
+
localAppData: process.env.LOCALAPPDATA,
|
|
31248
|
+
nodeExecutable: process.execPath,
|
|
31249
|
+
cliEntry,
|
|
31250
|
+
setStartAtLogin: (executable, enabled) => setCompanionStartAtLogin({ homeDirectory, platform, executable, enabled }),
|
|
31251
|
+
getStartAtLogin: (executable) => getCompanionStartAtLogin({ homeDirectory, platform, executable })
|
|
31252
|
+
};
|
|
31253
|
+
}
|
|
31254
|
+
async function convergeCompanionFromChannel(converge2, stagedDirectory) {
|
|
31255
|
+
let artifactManifestPath;
|
|
31256
|
+
let dispose = () => Promise.resolve();
|
|
31257
|
+
if (stagedDirectory !== void 0) {
|
|
31258
|
+
artifactManifestPath = path37.resolve(stagedDirectory, "artifact-v1.json");
|
|
31259
|
+
} else {
|
|
31260
|
+
const release = await readCompanionRelease();
|
|
31261
|
+
if (release === null) return { state: "not-released" };
|
|
31262
|
+
const build = await downloadCompanionArtifact(release.component);
|
|
31263
|
+
artifactManifestPath = build.artifactManifestPath;
|
|
31264
|
+
dispose = build.dispose;
|
|
31265
|
+
}
|
|
31266
|
+
const gateway = await resolveGatewayContext({ interactive: false });
|
|
31267
|
+
try {
|
|
31268
|
+
return await converge2({
|
|
31269
|
+
...defaultLocalCompanionInstallOptions(),
|
|
31270
|
+
artifactManifestPath,
|
|
31271
|
+
gatewayOrigin: new URL(gateway.gatewayUrl).origin
|
|
31272
|
+
});
|
|
31273
|
+
} finally {
|
|
31274
|
+
await dispose();
|
|
31275
|
+
}
|
|
31276
|
+
}
|
|
31277
|
+
async function runCompanionInstallCommand(stdout, install) {
|
|
31278
|
+
const state = await install();
|
|
31279
|
+
if (state.state === "not-released") {
|
|
31280
|
+
stdout.write(
|
|
31281
|
+
"The release this platform is pinned to carries no desktop companions.\nThey arrive with a platform release \u2014 run: m8t platform update\n"
|
|
31282
|
+
);
|
|
31283
|
+
return 1;
|
|
31284
|
+
}
|
|
31285
|
+
if (state.state !== "installed") {
|
|
31286
|
+
stdout.write("Desktop companions need repair.\nRun: m8t companion repair\n");
|
|
31287
|
+
return 1;
|
|
31288
|
+
}
|
|
31289
|
+
stdout.write(
|
|
31290
|
+
`Desktop companions installed: Stacey and Azzy are ready.
|
|
31291
|
+
Version: ${state.version}
|
|
31292
|
+
`
|
|
31293
|
+
);
|
|
31294
|
+
return 0;
|
|
31295
|
+
}
|
|
31296
|
+
var CompanionInstallCommand = class extends M8tCommand {
|
|
31297
|
+
static paths = [["companion", "install"]];
|
|
31298
|
+
static usage = Command57.Usage({
|
|
31299
|
+
description: "Install the desktop companions for this user from the release channel.",
|
|
31300
|
+
examples: [
|
|
31301
|
+
["Install the released build", "$0 companion install"],
|
|
31302
|
+
[
|
|
31303
|
+
"Install one built from this clone",
|
|
31304
|
+
"$0 companion install --from apps/companion/dist-artifacts/darwin-arm64"
|
|
31305
|
+
]
|
|
31306
|
+
]
|
|
31307
|
+
});
|
|
31308
|
+
from = Option54.String("--from", {
|
|
31309
|
+
description: "A locally staged build directory instead of the released one."
|
|
31310
|
+
});
|
|
31311
|
+
async executeCommand() {
|
|
31312
|
+
return runCompanionInstallCommand(
|
|
31313
|
+
this.context.stdout,
|
|
31314
|
+
() => convergeCompanionFromChannel(installCompanion, this.from)
|
|
31315
|
+
);
|
|
31316
|
+
}
|
|
31317
|
+
};
|
|
31318
|
+
|
|
31319
|
+
// src/commands/bootstrap/finish.ts
|
|
31320
|
+
var BootstrapFinishCommand = class extends M8tCommand {
|
|
31321
|
+
static paths = [["bootstrap", "finish"]];
|
|
31322
|
+
static usage = Command58.Usage({
|
|
31323
|
+
description: "Point your local tools at the now-live platform (repo-root marker, discovery cache, next steps).",
|
|
31324
|
+
details: "Final step of `m8t bootstrap` (after the install reaches done). Writes ~/.m8t/repo-root, points your local tools at the live gateway, and \u2014 if you completed the onboarding intake \u2014 seeds your brain-backed advisor's brain with your company profile (best-effort; never blocks finish). Run `m8t bootstrap seed-profile` to seed manually later.",
|
|
31325
|
+
examples: [["Finish local", "$0 bootstrap finish --repo-root /path/to/m8t"]]
|
|
31326
|
+
});
|
|
31327
|
+
repoRoot = Option55.String("--repo-root");
|
|
31328
|
+
subscription = Option55.String("--subscription");
|
|
31329
|
+
resourceGroup = Option55.String("--resource-group");
|
|
31330
|
+
async executeCommand() {
|
|
31331
|
+
const state = await readBootstrapState();
|
|
31332
|
+
if (!state) {
|
|
31333
|
+
throw new LocalCliError({ code: "BOOTSTRAP_NO_STATE", message: "No bootstrap state.", hint: "Run 'm8t bootstrap launch' first." });
|
|
31334
|
+
}
|
|
31335
|
+
const doc = await readStatusBlob({ saName: state.statusSaName, resourceGroup: state.resourceGroup, subscriptionId: state.subscriptionId });
|
|
31336
|
+
if (doc.status !== "done") {
|
|
31337
|
+
throw new LocalCliError({
|
|
31338
|
+
code: "BOOTSTRAP_FINISH_NOT_DONE",
|
|
31339
|
+
message: `The platform is not live yet (status: ${doc.status}, phase: ${doc.phase}).`,
|
|
31340
|
+
hint: "Wait for 'm8t bootstrap status --watch' to reach done."
|
|
31341
|
+
});
|
|
31342
|
+
}
|
|
31343
|
+
const repoRoot = (typeof this.repoRoot === "string" ? this.repoRoot : void 0) ?? process.cwd();
|
|
31344
|
+
const markerDir = path38.join(os18.homedir(), ".m8t");
|
|
31345
|
+
await fs34.mkdir(markerDir, { recursive: true });
|
|
31346
|
+
await fs34.writeFile(path38.join(markerDir, "repo-root"), `${repoRoot}
|
|
31347
|
+
`, "utf8");
|
|
31348
|
+
const subscriptionId = (typeof this.subscription === "string" ? this.subscription : void 0) ?? state.subscriptionId;
|
|
31349
|
+
const resourceGroup = (typeof this.resourceGroup === "string" ? this.resourceGroup : void 0) ?? state.resourceGroup;
|
|
31350
|
+
let webappUrl;
|
|
31351
|
+
try {
|
|
31352
|
+
const d = await discoverGateway({ subscriptionId, interactive: false, resourceGroup });
|
|
31353
|
+
await writeConfig({
|
|
31354
|
+
gatewayUrl: d.gatewayUrl,
|
|
31355
|
+
gatewayClientId: d.gatewayClientId,
|
|
31356
|
+
gatewayTenantId: d.gatewayTenantId,
|
|
31357
|
+
subscriptionId: d.subscriptionId,
|
|
31358
|
+
containerAppResourceId: d.containerAppResourceId,
|
|
31359
|
+
cachedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
31360
|
+
});
|
|
31361
|
+
webappUrl = d.gatewayUrl;
|
|
31362
|
+
this.context.stdout.write(
|
|
31363
|
+
`${colors.success("\u2705 The platform is live and your local tools are pointed at it.")}
|
|
31364
|
+
gateway: ${d.gatewayUrl}
|
|
31365
|
+
foundry: ${doc.result?.foundryEndpoint ?? "-"}
|
|
31366
|
+
|
|
31367
|
+
`
|
|
31368
|
+
);
|
|
31369
|
+
try {
|
|
31370
|
+
await ensureGatewayRedirectUri(d.gatewayClientId, new URL(d.gatewayUrl).host);
|
|
31371
|
+
} catch (e) {
|
|
31372
|
+
const err = e;
|
|
31373
|
+
this.context.stderr.write(
|
|
31374
|
+
` ${colors.error("\u26A0 could not authorize sign-in for your webapp:")} ${err.message}
|
|
31375
|
+
${colors.hint(`Until this is fixed, opening ${d.gatewayUrl} will fail with AADSTS50011 (redirect URI mismatch).`)}
|
|
31376
|
+
${colors.hint("Ask a directory admin to run:")}
|
|
31377
|
+
${colors.hint(` m8t bootstrap finish --repo-root ${repoRoot} # (as an admin), or by hand:`)}
|
|
31378
|
+
${colors.hint(` az ad app update --id ${d.gatewayClientId} --set spa.redirectUris="['${d.gatewayUrl}']" # (merge, do not overwrite)`)}
|
|
31379
|
+
|
|
31380
|
+
`
|
|
31381
|
+
);
|
|
31382
|
+
}
|
|
31383
|
+
try {
|
|
31384
|
+
const verdict = await runUsagePhase(
|
|
31385
|
+
buildPrereqDeps({ subscription: subscriptionId, resourceGroup }),
|
|
31386
|
+
{ fix: true, onProgress: (m) => {
|
|
31387
|
+
this.context.stdout.write(colors.dim(` ${m}
|
|
31388
|
+
`));
|
|
31389
|
+
} }
|
|
31390
|
+
);
|
|
31391
|
+
const rows = verdict.results.filter((r) => r.slug !== "signin-redirect-uri");
|
|
31392
|
+
const unresolved = rows.filter((r) => r.status === "fail");
|
|
31393
|
+
if (unresolved.length === 0) {
|
|
31394
|
+
this.context.stdout.write(` ${colors.success("\u2713 your account can reach Foundry and read the platform Key Vault")}
|
|
31395
|
+
|
|
31396
|
+
`);
|
|
31397
|
+
}
|
|
31398
|
+
for (const r of unresolved) {
|
|
31399
|
+
this.context.stderr.write(
|
|
31400
|
+
` ${colors.error("\u26A0 could not grant you access:")} ${r.detail}
|
|
31401
|
+
` + (r.remedy ? ` ${colors.hint(`Fix it with: ${r.remedy}`)}
|
|
31402
|
+
` : "") + "\n"
|
|
31403
|
+
);
|
|
31404
|
+
}
|
|
31405
|
+
} catch (e) {
|
|
31406
|
+
this.context.stderr.write(
|
|
31407
|
+
` ${colors.error("\u26A0 could not check or grant your platform access:")} ${e.message}
|
|
31408
|
+
${colors.hint("Run 'm8t prereqs --fix' when you can \u2014 until then your AI team will not load and worker deploys will fail.")}
|
|
31409
|
+
|
|
31410
|
+
`
|
|
31411
|
+
);
|
|
31412
|
+
}
|
|
31413
|
+
} catch (e) {
|
|
31414
|
+
const err = e;
|
|
31415
|
+
this.context.stdout.write(`${colors.success("\u2705 The platform is live.")}
|
|
31416
|
+
|
|
31417
|
+
`);
|
|
31418
|
+
this.context.stderr.write(
|
|
31419
|
+
` ${colors.hint(`\u26A0 couldn't auto-point your local tools at the gateway: ${err.message}`)}
|
|
31420
|
+
` + (err.hint ? ` ${colors.hint(err.hint)}
|
|
31421
|
+
` : "") + "\n"
|
|
31422
|
+
);
|
|
31423
|
+
}
|
|
31424
|
+
if (webappUrl !== void 0 && companionsSupportHost()) {
|
|
31425
|
+
try {
|
|
31426
|
+
const companion = await convergeCompanionFromChannel(installCompanion);
|
|
31427
|
+
if (companion.state === "installed") {
|
|
31428
|
+
this.context.stdout.write(
|
|
31429
|
+
"Desktop companions installed: Stacey and Azzy are ready.\n\n"
|
|
31430
|
+
);
|
|
31431
|
+
} else if (companion.state !== "not-released") {
|
|
31432
|
+
throw new Error("companion installation is not verified");
|
|
31433
|
+
}
|
|
31434
|
+
} catch (e) {
|
|
31435
|
+
const reason = e instanceof Error ? e.message : String(e);
|
|
31436
|
+
this.context.stdout.write(
|
|
31437
|
+
`Platform installation succeeded, but the desktop companions need repair.
|
|
31438
|
+
Run: m8t companion repair
|
|
31439
|
+
${colors.hint(reason)}
|
|
31440
|
+
|
|
31441
|
+
`
|
|
31442
|
+
);
|
|
31443
|
+
}
|
|
31444
|
+
}
|
|
31445
|
+
let brainOrg = null;
|
|
31446
|
+
try {
|
|
31447
|
+
const credsRaw = await fs34.readFile(path38.join(markerDir, "github-app.json"), "utf8");
|
|
31448
|
+
const creds = JSON.parse(credsRaw);
|
|
31449
|
+
brainOrg = typeof creds.org === "string" ? creds.org : null;
|
|
31450
|
+
} catch {
|
|
31451
|
+
}
|
|
31452
|
+
this.context.stdout.write(renderInstallSummary({ webappUrl, brainOrg }) + "\n");
|
|
31453
|
+
this.context.stdout.write(
|
|
31454
|
+
`${colors.field("Optional local tooling for your coding agent:")}
|
|
31455
|
+
\u2022 Talk to your deployed workers from agent sessions (e.g. /stacey) \u2014 install the m8t plugin: guides/install/m8t-plugin.md.
|
|
31456
|
+
\u2022 Azure-capable MCP servers (microsoft/azure-skills, Microsoft Learn) \u2014 see the optional steps in guides/install.md.
|
|
31457
|
+
\u2022 Deploy workers conversationally with the opt-in persona skills \u2014 see guides/workers.md.
|
|
31458
|
+
\u2022 Make sure your m8t CLI is current: npm i -g @m8t-stack/cli@latest
|
|
31459
|
+
|
|
31460
|
+
${colors.field("Then open a brand new chat/session")} \u2014 new skills + MCP servers load only on a fresh start.
|
|
31461
|
+
`
|
|
31462
|
+
);
|
|
31463
|
+
try {
|
|
31464
|
+
await reactiveSeedOnFinish({
|
|
31465
|
+
endpoint: doc.result?.foundryEndpoint,
|
|
31466
|
+
subscriptionId,
|
|
31467
|
+
stdout: (s) => this.context.stdout.write(s)
|
|
31468
|
+
});
|
|
31469
|
+
} catch (e) {
|
|
31470
|
+
this.context.stderr.write(
|
|
31471
|
+
` ${colors.dim(`(company-profile seed deferred: ${e instanceof Error ? e.message : String(e)} \u2014 run 'm8t bootstrap seed-profile' later)`)}
|
|
31472
|
+
`
|
|
31473
|
+
);
|
|
31474
|
+
}
|
|
31475
|
+
return 0;
|
|
31476
|
+
}
|
|
31477
|
+
};
|
|
31478
|
+
|
|
31479
|
+
// src/commands/bootstrap/ui.ts
|
|
31480
|
+
import * as fs36 from "fs";
|
|
31481
|
+
import * as os20 from "os";
|
|
31482
|
+
import * as path40 from "path";
|
|
31483
|
+
import { Command as Command59, Option as Option56 } from "clipanion";
|
|
31484
|
+
import { DefaultAzureCredential as DefaultAzureCredential24 } from "@azure/identity";
|
|
31485
|
+
init_errors();
|
|
31486
|
+
|
|
31487
|
+
// src/lib/bootstrap-ui.ts
|
|
31488
|
+
import * as fs35 from "fs";
|
|
31489
|
+
import * as net from "net";
|
|
31490
|
+
import * as os19 from "os";
|
|
31491
|
+
import * as path39 from "path";
|
|
31492
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
31493
|
+
import { spawn as spawn8, spawnSync as spawnSync6 } from "child_process";
|
|
31494
|
+
init_errors();
|
|
31495
|
+
init_rbac();
|
|
31496
|
+
|
|
31497
|
+
// src/lib/intake-agent.ts
|
|
31498
|
+
init_esm();
|
|
31499
|
+
var INTAKE_PERSONA = "azure-advisor-intake";
|
|
31500
|
+
var INTAKE_AGENT = INTAKE_AGENT_NAME;
|
|
31501
|
+
async function deployIntakeAgent(args) {
|
|
31502
|
+
try {
|
|
31503
|
+
return await deployPromptAdvisor({
|
|
31504
|
+
credential: args.credential,
|
|
31505
|
+
endpoint: args.endpoint,
|
|
31506
|
+
repoRoot: args.repoRoot,
|
|
31507
|
+
persona: INTAKE_PERSONA,
|
|
31508
|
+
agentName: INTAKE_AGENT,
|
|
31509
|
+
model: args.model,
|
|
31510
|
+
fieldOverrides: args.fieldOverrides
|
|
31511
|
+
});
|
|
31512
|
+
} catch (e) {
|
|
31513
|
+
if (e && typeof e === "object" && "code" in e) {
|
|
31514
|
+
const err = e;
|
|
31515
|
+
if (err.code === "ADVISOR_PERSONA_MISSING") {
|
|
31516
|
+
const { LocalCliError: LocalCliError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
|
|
31517
|
+
throw new LocalCliError2({
|
|
31518
|
+
code: "INTAKE_PERSONA_MISSING",
|
|
31519
|
+
message: err.message,
|
|
31520
|
+
hint: err.hint,
|
|
31521
|
+
cause: err.cause
|
|
31522
|
+
});
|
|
31523
|
+
}
|
|
31524
|
+
if (err.code === "ADVISOR_NO_MODEL") {
|
|
31525
|
+
const { LocalCliError: LocalCliError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
|
|
31526
|
+
throw new LocalCliError2({
|
|
31527
|
+
code: "INTAKE_NO_MODEL",
|
|
31528
|
+
message: err.message,
|
|
31529
|
+
hint: err.hint,
|
|
31530
|
+
cause: err.cause
|
|
31531
|
+
});
|
|
31532
|
+
}
|
|
31533
|
+
if (err.code === "ADVISOR_BAD_EFFORT") {
|
|
31534
|
+
const { LocalCliError: LocalCliError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
|
|
31535
|
+
throw new LocalCliError2({
|
|
31536
|
+
code: "INTAKE_BAD_EFFORT",
|
|
31537
|
+
message: err.message,
|
|
31538
|
+
hint: err.hint,
|
|
31539
|
+
cause: err.cause
|
|
31540
|
+
});
|
|
31541
|
+
}
|
|
31542
|
+
}
|
|
31543
|
+
throw e;
|
|
31544
|
+
}
|
|
31545
|
+
}
|
|
31546
|
+
|
|
31547
|
+
// src/lib/bootstrap-ui.ts
|
|
30414
31548
|
function sleep3(ms) {
|
|
30415
|
-
return new Promise((
|
|
31549
|
+
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
30416
31550
|
}
|
|
30417
31551
|
function isAuthorizationShapedError(error) {
|
|
30418
31552
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -30522,11 +31656,11 @@ async function ensureFounderFoundryRole(args) {
|
|
|
30522
31656
|
});
|
|
30523
31657
|
}
|
|
30524
31658
|
function writeWebEnvLocal(args) {
|
|
30525
|
-
const envPath =
|
|
31659
|
+
const envPath = path39.join(args.repoRoot, "apps", "web", ".env.local");
|
|
30526
31660
|
let prior = "";
|
|
30527
|
-
if (
|
|
30528
|
-
prior =
|
|
30529
|
-
|
|
31661
|
+
if (fs35.existsSync(envPath)) {
|
|
31662
|
+
prior = fs35.readFileSync(envPath, "utf8");
|
|
31663
|
+
fs35.copyFileSync(envPath, envPath + ".bak");
|
|
30530
31664
|
}
|
|
30531
31665
|
const priorSecret = readValidInternalSecret(prior);
|
|
30532
31666
|
const internalSecret = priorSecret ?? (args.randomBytesImpl ?? randomBytes3)(32).toString("base64url");
|
|
@@ -30552,9 +31686,9 @@ function writeWebEnvLocal(args) {
|
|
|
30552
31686
|
"NEXT_PUBLIC_M8T_ONBOARDING=1",
|
|
30553
31687
|
""
|
|
30554
31688
|
].join("\n");
|
|
30555
|
-
|
|
31689
|
+
fs35.writeFileSync(envPath, body, { encoding: "utf8", mode: 384 });
|
|
30556
31690
|
try {
|
|
30557
|
-
|
|
31691
|
+
fs35.chmodSync(envPath, 384);
|
|
30558
31692
|
} catch {
|
|
30559
31693
|
}
|
|
30560
31694
|
return envPath;
|
|
@@ -30570,10 +31704,10 @@ function readValidInternalSecret(envText) {
|
|
|
30570
31704
|
return isValidInternalSecret(values[0]) ? values[0] : void 0;
|
|
30571
31705
|
}
|
|
30572
31706
|
function readWebInternalSecret(repoRoot) {
|
|
30573
|
-
const envPath =
|
|
31707
|
+
const envPath = path39.join(repoRoot, "apps", "web", ".env.local");
|
|
30574
31708
|
let secret;
|
|
30575
31709
|
try {
|
|
30576
|
-
secret = readValidInternalSecret(
|
|
31710
|
+
secret = readValidInternalSecret(fs35.readFileSync(envPath, "utf8"));
|
|
30577
31711
|
} catch {
|
|
30578
31712
|
secret = void 0;
|
|
30579
31713
|
}
|
|
@@ -30597,14 +31731,14 @@ function assertNodeVersion(versionString = process.version) {
|
|
|
30597
31731
|
}
|
|
30598
31732
|
}
|
|
30599
31733
|
function runInherit(cmd, cmdArgs, cwd, failCode) {
|
|
30600
|
-
return new Promise((
|
|
30601
|
-
const child =
|
|
31734
|
+
return new Promise((resolve4, reject) => {
|
|
31735
|
+
const child = spawn8(cmd, cmdArgs, { cwd, stdio: "inherit", shell: process.platform === "win32" });
|
|
30602
31736
|
child.on("error", (e) => {
|
|
30603
31737
|
reject(new LocalCliError({ code: failCode, message: `Failed to start '${cmd}': ${e.message}` }));
|
|
30604
31738
|
});
|
|
30605
31739
|
child.on("close", (code) => {
|
|
30606
31740
|
if (code === 0) {
|
|
30607
|
-
|
|
31741
|
+
resolve4();
|
|
30608
31742
|
} else {
|
|
30609
31743
|
const opts = {
|
|
30610
31744
|
code: failCode,
|
|
@@ -30624,40 +31758,40 @@ async function installWebDeps(repoRoot) {
|
|
|
30624
31758
|
});
|
|
30625
31759
|
await runInherit("pnpm", ["install"], repoRoot, "BOOTSTRAP_UI_INSTALL_FAILED");
|
|
30626
31760
|
}
|
|
30627
|
-
function onboardingUiPaths(home =
|
|
30628
|
-
const dir =
|
|
31761
|
+
function onboardingUiPaths(home = os19.homedir()) {
|
|
31762
|
+
const dir = path39.join(home, ".m8t");
|
|
30629
31763
|
return {
|
|
30630
|
-
logPath:
|
|
30631
|
-
pidPath:
|
|
31764
|
+
logPath: path39.join(dir, "onboarding-ui.log"),
|
|
31765
|
+
pidPath: path39.join(dir, "onboarding-ui.pid")
|
|
30632
31766
|
};
|
|
30633
31767
|
}
|
|
30634
|
-
function onboardingRelayPaths(home =
|
|
30635
|
-
const dir =
|
|
31768
|
+
function onboardingRelayPaths(home = os19.homedir()) {
|
|
31769
|
+
const dir = path39.join(home, ".m8t");
|
|
30636
31770
|
return {
|
|
30637
|
-
logPath:
|
|
30638
|
-
pidPath:
|
|
31771
|
+
logPath: path39.join(dir, "onboarding-relay.log"),
|
|
31772
|
+
pidPath: path39.join(dir, "onboarding-relay.pid")
|
|
30639
31773
|
};
|
|
30640
31774
|
}
|
|
30641
31775
|
function isLocalPortOpen(port) {
|
|
30642
|
-
return new Promise((
|
|
31776
|
+
return new Promise((resolve4) => {
|
|
30643
31777
|
const s = net.createConnection({ host: "127.0.0.1", port });
|
|
30644
31778
|
s.setTimeout(1e3);
|
|
30645
31779
|
s.on("connect", () => {
|
|
30646
31780
|
s.destroy();
|
|
30647
|
-
|
|
31781
|
+
resolve4(true);
|
|
30648
31782
|
});
|
|
30649
31783
|
s.on("error", () => {
|
|
30650
|
-
|
|
31784
|
+
resolve4(false);
|
|
30651
31785
|
});
|
|
30652
31786
|
s.on("timeout", () => {
|
|
30653
31787
|
s.destroy();
|
|
30654
|
-
|
|
31788
|
+
resolve4(false);
|
|
30655
31789
|
});
|
|
30656
31790
|
});
|
|
30657
31791
|
}
|
|
30658
31792
|
function removePidFile(pidPath) {
|
|
30659
31793
|
try {
|
|
30660
|
-
|
|
31794
|
+
fs35.unlinkSync(pidPath);
|
|
30661
31795
|
} catch {
|
|
30662
31796
|
}
|
|
30663
31797
|
}
|
|
@@ -30670,7 +31804,7 @@ function parseManagedPid(text) {
|
|
|
30670
31804
|
function readLiveManagedPid(pidPath) {
|
|
30671
31805
|
let text;
|
|
30672
31806
|
try {
|
|
30673
|
-
text =
|
|
31807
|
+
text = fs35.readFileSync(pidPath, "utf8").trim();
|
|
30674
31808
|
} catch {
|
|
30675
31809
|
return null;
|
|
30676
31810
|
}
|
|
@@ -30709,9 +31843,9 @@ async function serveOnboardingUiDetached(args) {
|
|
|
30709
31843
|
});
|
|
30710
31844
|
}
|
|
30711
31845
|
if (managedPid != null) return { alreadyRunning: true, logPath };
|
|
30712
|
-
|
|
30713
|
-
const fd =
|
|
30714
|
-
const child =
|
|
31846
|
+
fs35.mkdirSync(path39.dirname(logPath), { recursive: true });
|
|
31847
|
+
const fd = fs35.openSync(logPath, "a");
|
|
31848
|
+
const child = spawn8("pnpm", ["--filter", "web", "dev"], {
|
|
30715
31849
|
cwd: args.repoRoot,
|
|
30716
31850
|
detached: true,
|
|
30717
31851
|
stdio: ["ignore", fd, fd],
|
|
@@ -30719,7 +31853,7 @@ async function serveOnboardingUiDetached(args) {
|
|
|
30719
31853
|
// pnpm resolves to pnpm.cmd on Windows, which needs a shell to launch.
|
|
30720
31854
|
shell: process.platform === "win32"
|
|
30721
31855
|
});
|
|
30722
|
-
|
|
31856
|
+
fs35.closeSync(fd);
|
|
30723
31857
|
if (child.pid == null) {
|
|
30724
31858
|
throw new LocalCliError({
|
|
30725
31859
|
code: "BOOTSTRAP_UI_DEV_SPAWN_FAILED",
|
|
@@ -30728,7 +31862,7 @@ async function serveOnboardingUiDetached(args) {
|
|
|
30728
31862
|
});
|
|
30729
31863
|
}
|
|
30730
31864
|
child.unref();
|
|
30731
|
-
|
|
31865
|
+
fs35.writeFileSync(pidPath, String(child.pid), "utf8");
|
|
30732
31866
|
const deadline = Date.now() + 45e3;
|
|
30733
31867
|
const sleep5 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
30734
31868
|
while (Date.now() < deadline) {
|
|
@@ -30760,9 +31894,9 @@ async function serveOnboardingRelayDetached(args) {
|
|
|
30760
31894
|
});
|
|
30761
31895
|
}
|
|
30762
31896
|
if (managedPid != null) return { alreadyRunning: true, logPath };
|
|
30763
|
-
|
|
30764
|
-
const fd =
|
|
30765
|
-
const child =
|
|
31897
|
+
fs35.mkdirSync(path39.dirname(logPath), { recursive: true });
|
|
31898
|
+
const fd = fs35.openSync(logPath, "a");
|
|
31899
|
+
const child = spawn8("pnpm", ["--filter", "web", "exec", "tsx", "voice-relay-entry.ts"], {
|
|
30766
31900
|
cwd: args.repoRoot,
|
|
30767
31901
|
detached: true,
|
|
30768
31902
|
stdio: ["ignore", fd, fd],
|
|
@@ -30779,7 +31913,7 @@ async function serveOnboardingRelayDetached(args) {
|
|
|
30779
31913
|
// pnpm resolves to pnpm.cmd on Windows, which needs a shell to launch.
|
|
30780
31914
|
shell: process.platform === "win32"
|
|
30781
31915
|
});
|
|
30782
|
-
|
|
31916
|
+
fs35.closeSync(fd);
|
|
30783
31917
|
if (child.pid == null) {
|
|
30784
31918
|
throw new LocalCliError({
|
|
30785
31919
|
code: "BOOTSTRAP_UI_RELAY_SPAWN_FAILED",
|
|
@@ -30788,7 +31922,7 @@ async function serveOnboardingRelayDetached(args) {
|
|
|
30788
31922
|
});
|
|
30789
31923
|
}
|
|
30790
31924
|
child.unref();
|
|
30791
|
-
|
|
31925
|
+
fs35.writeFileSync(pidPath, String(child.pid), "utf8");
|
|
30792
31926
|
const deadline = Date.now() + 45e3;
|
|
30793
31927
|
const sleep5 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
30794
31928
|
while (Date.now() < deadline) {
|
|
@@ -30797,9 +31931,9 @@ async function serveOnboardingRelayDetached(args) {
|
|
|
30797
31931
|
}
|
|
30798
31932
|
return { alreadyRunning: false, logPath };
|
|
30799
31933
|
}
|
|
30800
|
-
function autoOpenOnboardingUi(url,
|
|
31934
|
+
function autoOpenOnboardingUi(url, open3 = tryOpenUrl) {
|
|
30801
31935
|
try {
|
|
30802
|
-
const result2 =
|
|
31936
|
+
const result2 = open3(url);
|
|
30803
31937
|
if (result2 instanceof Promise) {
|
|
30804
31938
|
result2.catch(() => {
|
|
30805
31939
|
});
|
|
@@ -30810,7 +31944,7 @@ function autoOpenOnboardingUi(url, open = tryOpenUrl) {
|
|
|
30810
31944
|
function stopPidFile(pidPath) {
|
|
30811
31945
|
let pidStr;
|
|
30812
31946
|
try {
|
|
30813
|
-
pidStr =
|
|
31947
|
+
pidStr = fs35.readFileSync(pidPath, "utf8").trim();
|
|
30814
31948
|
} catch {
|
|
30815
31949
|
return false;
|
|
30816
31950
|
}
|
|
@@ -30827,12 +31961,12 @@ function stopPidFile(pidPath) {
|
|
|
30827
31961
|
}
|
|
30828
31962
|
}
|
|
30829
31963
|
try {
|
|
30830
|
-
|
|
31964
|
+
fs35.unlinkSync(pidPath);
|
|
30831
31965
|
} catch {
|
|
30832
31966
|
}
|
|
30833
31967
|
return true;
|
|
30834
31968
|
}
|
|
30835
|
-
function stopOnboardingUi(home =
|
|
31969
|
+
function stopOnboardingUi(home = os19.homedir()) {
|
|
30836
31970
|
const uiStopped = stopPidFile(onboardingUiPaths(home).pidPath);
|
|
30837
31971
|
const relayStopped = stopPidFile(onboardingRelayPaths(home).pidPath);
|
|
30838
31972
|
return uiStopped || relayStopped;
|
|
@@ -31121,9 +32255,9 @@ async function resolveSubscriptionInventory(args) {
|
|
|
31121
32255
|
const scan2 = scanFn(args.subscriptionId);
|
|
31122
32256
|
void scan2.catch(() => {
|
|
31123
32257
|
});
|
|
31124
|
-
const timeout = new Promise((
|
|
32258
|
+
const timeout = new Promise((resolve4) => {
|
|
31125
32259
|
timer = setTimeout(() => {
|
|
31126
|
-
|
|
32260
|
+
resolve4("timeout");
|
|
31127
32261
|
}, deadlineMs);
|
|
31128
32262
|
});
|
|
31129
32263
|
const raced = await Promise.race([scan2, timeout]);
|
|
@@ -31154,7 +32288,7 @@ function renderDeployFailure(error) {
|
|
|
31154
32288
|
}
|
|
31155
32289
|
var BootstrapUiCommand = class extends M8tCommand {
|
|
31156
32290
|
static paths = [["bootstrap", "ui"]];
|
|
31157
|
-
static usage =
|
|
32291
|
+
static usage = Command59.Usage({
|
|
31158
32292
|
description: "Deploy Azzy + start the local onboarding chat UI in the background (returns immediately).",
|
|
31159
32293
|
details: [
|
|
31160
32294
|
"Run after `m8t bootstrap launch`, in parallel with `status --watch`. Waits for the cloud",
|
|
@@ -31175,16 +32309,16 @@ var BootstrapUiCommand = class extends M8tCommand {
|
|
|
31175
32309
|
["Experimental: start the voice relay (no effect on the text-only intake)", "$0 bootstrap ui --repo-root /path/to/m8t --voice"]
|
|
31176
32310
|
]
|
|
31177
32311
|
});
|
|
31178
|
-
repoRoot =
|
|
31179
|
-
port =
|
|
31180
|
-
endpoint =
|
|
32312
|
+
repoRoot = Option56.String("--repo-root");
|
|
32313
|
+
port = Option56.String("--port", "3000");
|
|
32314
|
+
endpoint = Option56.String("--endpoint", {
|
|
31181
32315
|
description: "Foundry project endpoint to target \u2014 disambiguates when the subscription has multiple projects."
|
|
31182
32316
|
});
|
|
31183
|
-
prepOnly =
|
|
31184
|
-
skipInstall =
|
|
31185
|
-
stop =
|
|
31186
|
-
foreground =
|
|
31187
|
-
voice =
|
|
32317
|
+
prepOnly = Option56.Boolean("--prep-only", false);
|
|
32318
|
+
skipInstall = Option56.Boolean("--skip-install", false);
|
|
32319
|
+
stop = Option56.Boolean("--stop", false);
|
|
32320
|
+
foreground = Option56.Boolean("--foreground", false);
|
|
32321
|
+
voice = Option56.Boolean("--voice", false, {
|
|
31188
32322
|
description: "Experimental: when serving, starts the voice relay and writes the intake voice env var. The onboarding intake is text-only and is unaffected by this flag \u2014 no voice worker is registered for it."
|
|
31189
32323
|
});
|
|
31190
32324
|
async executeCommand() {
|
|
@@ -31219,7 +32353,7 @@ var BootstrapUiCommand = class extends M8tCommand {
|
|
|
31219
32353
|
this.context.stderr.write(` ${colors.dim(m)}
|
|
31220
32354
|
`);
|
|
31221
32355
|
};
|
|
31222
|
-
if (
|
|
32356
|
+
if (fs36.existsSync(path40.join(os20.homedir(), ".m8t", "config.yaml"))) {
|
|
31223
32357
|
out(colors.error("\u26A0 an existing ~/.m8t/config.yaml will override the onboarding app registration in apps/web \u2014 if Microsoft sign-in fails, move it aside and retry."));
|
|
31224
32358
|
}
|
|
31225
32359
|
const credential2 = new DefaultAzureCredential24();
|
|
@@ -31346,10 +32480,10 @@ var BootstrapUiCommand = class extends M8tCommand {
|
|
|
31346
32480
|
};
|
|
31347
32481
|
|
|
31348
32482
|
// src/commands/bootstrap/seed-profile.ts
|
|
31349
|
-
import { Command as
|
|
32483
|
+
import { Command as Command60, Option as Option57 } from "clipanion";
|
|
31350
32484
|
var BootstrapSeedProfileCommand = class extends M8tCommand {
|
|
31351
32485
|
static paths = [["bootstrap", "seed-profile"]];
|
|
31352
|
-
static usage =
|
|
32486
|
+
static usage = Command60.Usage({
|
|
31353
32487
|
description: "Seed your advisors' brains with the founder + company profile from the onboarding intake.",
|
|
31354
32488
|
details: "Reads the latest onboarding conversation, renders memory/founder.md + memory/company-profile.md (+ their MEMORY.md index lines), and commits them to both <org>/stacey-brain and <org>/azzy-brain via the GitHub App. Idempotent. --watch polls until the founder finishes the intake.",
|
|
31355
32489
|
examples: [
|
|
@@ -31357,11 +32491,11 @@ var BootstrapSeedProfileCommand = class extends M8tCommand {
|
|
|
31357
32491
|
["Wait for the founder to finish", "$0 bootstrap seed-profile --watch"]
|
|
31358
32492
|
]
|
|
31359
32493
|
});
|
|
31360
|
-
endpoint =
|
|
31361
|
-
brain =
|
|
31362
|
-
watch =
|
|
31363
|
-
timeout =
|
|
31364
|
-
githubAppCreds =
|
|
32494
|
+
endpoint = Option57.String("--endpoint", { description: "Override the Foundry endpoint (else read from the install status)." });
|
|
32495
|
+
brain = Option57.String("--brain", { description: "Seed only this one brain repo, instead of both <org>/stacey-brain and <org>/azzy-brain." });
|
|
32496
|
+
watch = Option57.Boolean("--watch", false, { description: "Poll until the intake completes (or --timeout)." });
|
|
32497
|
+
timeout = Option57.String("--timeout", { description: "Watch timeout in minutes (default 20)." });
|
|
32498
|
+
githubAppCreds = Option57.String("--github-app-creds");
|
|
31365
32499
|
async executeCommand() {
|
|
31366
32500
|
const ctx = await resolveSeedContext({
|
|
31367
32501
|
endpointOverride: typeof this.endpoint === "string" ? this.endpoint : void 0,
|
|
@@ -31421,10 +32555,10 @@ var BootstrapSeedProfileCommand = class extends M8tCommand {
|
|
|
31421
32555
|
};
|
|
31422
32556
|
|
|
31423
32557
|
// src/commands/telemetry/enroll.ts
|
|
31424
|
-
import * as
|
|
31425
|
-
import * as
|
|
31426
|
-
import * as
|
|
31427
|
-
import { Command as
|
|
32558
|
+
import * as fs37 from "fs";
|
|
32559
|
+
import * as os21 from "os";
|
|
32560
|
+
import * as path41 from "path";
|
|
32561
|
+
import { Command as Command61, Option as Option58 } from "clipanion";
|
|
31428
32562
|
init_errors();
|
|
31429
32563
|
|
|
31430
32564
|
// src/lib/telemetry-enroll.ts
|
|
@@ -31506,7 +32640,7 @@ async function resolveKeyVaultName(containerAppResourceId) {
|
|
|
31506
32640
|
}
|
|
31507
32641
|
var TelemetryEnrollCommand = class extends M8tCommand {
|
|
31508
32642
|
static paths = [["telemetry", "enroll"]];
|
|
31509
|
-
static usage =
|
|
32643
|
+
static usage = Command61.Usage({
|
|
31510
32644
|
description: "Enroll this installation for operational telemetry (pre-existing installs).",
|
|
31511
32645
|
details: "Generates an instance id + ingest key from the m8t telemetry ingest and stores the key in your platform Key Vault, the same place the installer writes it on a fresh install. Your installation is identified only by that random instance id \u2014 no contact, company, or subscription details are sent unless you pass them explicitly. Idempotent: refuses if a key already exists.",
|
|
31512
32646
|
examples: [
|
|
@@ -31514,11 +32648,11 @@ var TelemetryEnrollCommand = class extends M8tCommand {
|
|
|
31514
32648
|
["Enroll and share a support contact", "$0 telemetry enroll --contact-email you@example.com --company 'Acme'"]
|
|
31515
32649
|
]
|
|
31516
32650
|
});
|
|
31517
|
-
company =
|
|
31518
|
-
contactEmail =
|
|
31519
|
-
subscription =
|
|
31520
|
-
resourceGroup =
|
|
31521
|
-
force =
|
|
32651
|
+
company = Option58.String("--company", { description: "Share your company name with m8t support. Not sent unless you pass it." });
|
|
32652
|
+
contactEmail = Option58.String("--contact-email", { description: "Share a contact email with m8t support. Not sent unless you pass it." });
|
|
32653
|
+
subscription = Option58.String("--subscription", { description: "Azure subscription id used to find your deployment. Never sent to m8t." });
|
|
32654
|
+
resourceGroup = Option58.String("--resource-group", { description: "Resource group to disambiguate discovery, if you have more than one m8t deployment." });
|
|
32655
|
+
force = Option58.Boolean("--force", false, { description: "Enroll even when a key is already stored. Use only when m8t support asks you to." });
|
|
31522
32656
|
async executeCommand() {
|
|
31523
32657
|
const account = await getAzAccount();
|
|
31524
32658
|
const subscriptionId = (typeof this.subscription === "string" ? this.subscription : void 0) ?? account.subscriptionId;
|
|
@@ -31551,13 +32685,13 @@ var TelemetryEnrollCommand = class extends M8tCommand {
|
|
|
31551
32685
|
...company ? { company } : {},
|
|
31552
32686
|
...contactEmail ? { contactEmail } : {}
|
|
31553
32687
|
});
|
|
31554
|
-
const keyDir =
|
|
31555
|
-
const keyFile =
|
|
32688
|
+
const keyDir = fs37.mkdtempSync(path41.join(os21.tmpdir(), "m8t-ingest-key-"));
|
|
32689
|
+
const keyFile = path41.join(keyDir, "key");
|
|
31556
32690
|
try {
|
|
31557
|
-
|
|
32691
|
+
fs37.writeFileSync(keyFile, ingestKey, { mode: 384 });
|
|
31558
32692
|
await runAz(["keyvault", "secret", "set", "--vault-name", kvName, "--name", INGEST_KEY_SECRET, "--file", keyFile, "--only-show-errors"]);
|
|
31559
32693
|
} finally {
|
|
31560
|
-
|
|
32694
|
+
fs37.rmSync(keyDir, { recursive: true, force: true });
|
|
31561
32695
|
}
|
|
31562
32696
|
this.context.stdout.write(
|
|
31563
32697
|
`${colors.success("\u2713")} enrolled (instance ${instanceId}); ingest key stored in Key Vault ${kvName}. The gateway picks it up on its next cycle.
|
|
@@ -31567,6 +32701,775 @@ var TelemetryEnrollCommand = class extends M8tCommand {
|
|
|
31567
32701
|
}
|
|
31568
32702
|
};
|
|
31569
32703
|
|
|
32704
|
+
// src/commands/companion/bridge.ts
|
|
32705
|
+
import { Command as Command62 } from "clipanion";
|
|
32706
|
+
|
|
32707
|
+
// ../../packages/companion-bridge-contract/src/index.ts
|
|
32708
|
+
var COMPANION_MESSAGE_MAX_CODE_POINTS = 32768;
|
|
32709
|
+
var COMPANION_REQUEST_ID_MAX_LENGTH = 128;
|
|
32710
|
+
var COMPANION_AGENT_NAME_MAX_LENGTH = 256;
|
|
32711
|
+
var COMPANION_DESKTOP_AFTER_MAX_LENGTH = 256;
|
|
32712
|
+
var COMPANION_BRIDGE_LINE_MAX_LENGTH = 14e4;
|
|
32713
|
+
var PERSONAS = /* @__PURE__ */ new Set([
|
|
32714
|
+
"startup-advisor",
|
|
32715
|
+
"azure-advisor"
|
|
32716
|
+
]);
|
|
32717
|
+
var FAILURE_REASONS = /* @__PURE__ */ new Set([
|
|
32718
|
+
"not-connected",
|
|
32719
|
+
"not-authorized",
|
|
32720
|
+
"mate-unavailable",
|
|
32721
|
+
"busy",
|
|
32722
|
+
"bad-request"
|
|
32723
|
+
]);
|
|
32724
|
+
var CONTROL_CHARACTERS = /[\u0000-\u001f\u007f]/u;
|
|
32725
|
+
var URL_SAFE_OPAQUE_ID = /^[A-Za-z0-9_-]+$/u;
|
|
32726
|
+
var SEVERITIES2 = /* @__PURE__ */ new Set(["critical", "recommended", "optional"]);
|
|
32727
|
+
var VERSION3 = /^[A-Za-z0-9][A-Za-z0-9._+-]{0,63}$/u;
|
|
32728
|
+
function isVersionOrNull(value) {
|
|
32729
|
+
return value === null || typeof value === "string" && VERSION3.test(value);
|
|
32730
|
+
}
|
|
32731
|
+
function isRecord2(value) {
|
|
32732
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
32733
|
+
}
|
|
32734
|
+
function hasExactKeys(value, keys) {
|
|
32735
|
+
const actual = Object.keys(value);
|
|
32736
|
+
return actual.length === keys.length && keys.every((key2) => Object.hasOwn(value, key2));
|
|
32737
|
+
}
|
|
32738
|
+
function isPersonaKey(value) {
|
|
32739
|
+
return typeof value === "string" && PERSONAS.has(value);
|
|
32740
|
+
}
|
|
32741
|
+
function isBoundedPlainString(value, maxLength) {
|
|
32742
|
+
return typeof value === "string" && value.length > 0 && value.length <= maxLength && !CONTROL_CHARACTERS.test(value);
|
|
32743
|
+
}
|
|
32744
|
+
function isRequestId(value) {
|
|
32745
|
+
return isBoundedPlainString(value, COMPANION_REQUEST_ID_MAX_LENGTH);
|
|
32746
|
+
}
|
|
32747
|
+
function isCanonicalInstant(value) {
|
|
32748
|
+
if (typeof value !== "string") return false;
|
|
32749
|
+
const time = Date.parse(value);
|
|
32750
|
+
return Number.isFinite(time) && new Date(time).toISOString() === value && time <= Date.now();
|
|
32751
|
+
}
|
|
32752
|
+
function requestError() {
|
|
32753
|
+
throw new Error("Invalid companion bridge request");
|
|
32754
|
+
}
|
|
32755
|
+
function eventError() {
|
|
32756
|
+
throw new Error("Invalid companion bridge event");
|
|
32757
|
+
}
|
|
32758
|
+
function isGatewayOrigin(value) {
|
|
32759
|
+
if (typeof value !== "string" || !value.startsWith("https://")) {
|
|
32760
|
+
return false;
|
|
32761
|
+
}
|
|
32762
|
+
let parsed;
|
|
32763
|
+
try {
|
|
32764
|
+
parsed = new URL(value);
|
|
32765
|
+
} catch {
|
|
32766
|
+
return false;
|
|
32767
|
+
}
|
|
32768
|
+
return parsed.origin === value && parsed.protocol === "https:" && parsed.username === "" && parsed.password === "" && parsed.pathname === "/" && parsed.search === "" && parsed.hash === "";
|
|
32769
|
+
}
|
|
32770
|
+
function isValidMateRoute(value) {
|
|
32771
|
+
if (typeof value !== "string" || !value.startsWith("/") || value.startsWith("//") || value.includes("#")) {
|
|
32772
|
+
return false;
|
|
32773
|
+
}
|
|
32774
|
+
try {
|
|
32775
|
+
const parsed = new URL(value, "https://companion.invalid");
|
|
32776
|
+
if (parsed.origin !== "https://companion.invalid" || !/^\/mates\/(?:startup-advisor|azure-advisor)$/u.test(parsed.pathname)) {
|
|
32777
|
+
return false;
|
|
32778
|
+
}
|
|
32779
|
+
const entries = [...parsed.searchParams.entries()];
|
|
32780
|
+
const keys = entries.map(([key2]) => key2);
|
|
32781
|
+
if (new Set(keys).size !== keys.length || keys.some(
|
|
32782
|
+
(key2) => key2 !== "desktopPending" && key2 !== "desktopAfter"
|
|
32783
|
+
)) {
|
|
32784
|
+
return false;
|
|
32785
|
+
}
|
|
32786
|
+
if (parsed.searchParams.has("desktopPending") && parsed.searchParams.get("desktopPending") !== "1") {
|
|
32787
|
+
return false;
|
|
32788
|
+
}
|
|
32789
|
+
if (parsed.searchParams.has("desktopAfter")) {
|
|
32790
|
+
const after = parsed.searchParams.get("desktopAfter");
|
|
32791
|
+
if (after === null || after.length === 0 || after.length > COMPANION_DESKTOP_AFTER_MAX_LENGTH || !URL_SAFE_OPAQUE_ID.test(after)) {
|
|
32792
|
+
return false;
|
|
32793
|
+
}
|
|
32794
|
+
}
|
|
32795
|
+
return true;
|
|
32796
|
+
} catch {
|
|
32797
|
+
return false;
|
|
32798
|
+
}
|
|
32799
|
+
}
|
|
32800
|
+
function parseRequest(value) {
|
|
32801
|
+
if (!isRecord2(value)) return requestError();
|
|
32802
|
+
if (value.type === "roster") {
|
|
32803
|
+
if (!hasExactKeys(value, ["type"])) return requestError();
|
|
32804
|
+
return { type: "roster" };
|
|
32805
|
+
}
|
|
32806
|
+
if (value.type === "update") {
|
|
32807
|
+
if (!hasExactKeys(value, ["type"])) return requestError();
|
|
32808
|
+
return { type: "update" };
|
|
32809
|
+
}
|
|
32810
|
+
if (value.type !== "send" || !hasExactKeys(value, [
|
|
32811
|
+
"type",
|
|
32812
|
+
"requestId",
|
|
32813
|
+
"personaKey",
|
|
32814
|
+
"draftRevision",
|
|
32815
|
+
"text"
|
|
32816
|
+
]) || !isRequestId(value.requestId) || !isPersonaKey(value.personaKey) || !Number.isSafeInteger(value.draftRevision) || value.draftRevision < 0 || typeof value.text !== "string" || value.text.trim().length === 0 || [...value.text].length > COMPANION_MESSAGE_MAX_CODE_POINTS) {
|
|
32817
|
+
return requestError();
|
|
32818
|
+
}
|
|
32819
|
+
return {
|
|
32820
|
+
type: "send",
|
|
32821
|
+
requestId: value.requestId,
|
|
32822
|
+
personaKey: value.personaKey,
|
|
32823
|
+
draftRevision: value.draftRevision,
|
|
32824
|
+
text: value.text
|
|
32825
|
+
};
|
|
32826
|
+
}
|
|
32827
|
+
function parseRequestLine(line2) {
|
|
32828
|
+
if (typeof line2 !== "string" || line2.length > COMPANION_BRIDGE_LINE_MAX_LENGTH || /[\r\n]/u.test(line2)) {
|
|
32829
|
+
return requestError();
|
|
32830
|
+
}
|
|
32831
|
+
try {
|
|
32832
|
+
return parseRequest(JSON.parse(line2));
|
|
32833
|
+
} catch {
|
|
32834
|
+
return requestError();
|
|
32835
|
+
}
|
|
32836
|
+
}
|
|
32837
|
+
function parseMate(value) {
|
|
32838
|
+
if (!isRecord2(value) || !hasExactKeys(value, [
|
|
32839
|
+
"personaKey",
|
|
32840
|
+
"agentName",
|
|
32841
|
+
"displayName",
|
|
32842
|
+
"portraitKey",
|
|
32843
|
+
"activeSince"
|
|
32844
|
+
]) || !isPersonaKey(value.personaKey) || !isBoundedPlainString(
|
|
32845
|
+
value.agentName,
|
|
32846
|
+
COMPANION_AGENT_NAME_MAX_LENGTH
|
|
32847
|
+
) || !isCanonicalInstant(value.activeSince)) {
|
|
32848
|
+
return eventError();
|
|
32849
|
+
}
|
|
32850
|
+
const identity = value.personaKey === "startup-advisor" ? { displayName: "Stacey", portraitKey: "stacey" } : { displayName: "Azzy", portraitKey: "azzy" };
|
|
32851
|
+
if (value.displayName !== identity.displayName || value.portraitKey !== identity.portraitKey) {
|
|
32852
|
+
return eventError();
|
|
32853
|
+
}
|
|
32854
|
+
return {
|
|
32855
|
+
personaKey: value.personaKey,
|
|
32856
|
+
agentName: value.agentName,
|
|
32857
|
+
displayName: identity.displayName,
|
|
32858
|
+
portraitKey: identity.portraitKey,
|
|
32859
|
+
activeSince: value.activeSince
|
|
32860
|
+
};
|
|
32861
|
+
}
|
|
32862
|
+
function parseEvent(value) {
|
|
32863
|
+
if (!isRecord2(value)) return eventError();
|
|
32864
|
+
if (value.type === "update") {
|
|
32865
|
+
if (!hasExactKeys(value, ["type", "installed", "available", "severity"]) || !isVersionOrNull(value.installed) || !isVersionOrNull(value.available) || !(value.severity === null || SEVERITIES2.has(value.severity))) {
|
|
32866
|
+
return eventError();
|
|
32867
|
+
}
|
|
32868
|
+
return {
|
|
32869
|
+
type: "update",
|
|
32870
|
+
installed: value.installed,
|
|
32871
|
+
available: value.available,
|
|
32872
|
+
severity: value.severity
|
|
32873
|
+
};
|
|
32874
|
+
}
|
|
32875
|
+
if (value.type === "roster") {
|
|
32876
|
+
if (!hasExactKeys(value, ["type", "origin", "mates"]) || !isGatewayOrigin(value.origin) || !Array.isArray(value.mates) || value.mates.length !== 2) {
|
|
32877
|
+
return eventError();
|
|
32878
|
+
}
|
|
32879
|
+
const mates = value.mates.map(parseMate);
|
|
32880
|
+
if (new Set(mates.map(({ personaKey }) => personaKey)).size !== 2 || new Set(mates.map(({ agentName }) => agentName)).size !== 2) {
|
|
32881
|
+
return eventError();
|
|
32882
|
+
}
|
|
32883
|
+
return { type: "roster", origin: value.origin, mates };
|
|
32884
|
+
}
|
|
32885
|
+
if (value.type === "failed") {
|
|
32886
|
+
const keys = value.requestId === void 0 ? ["type", "reason"] : ["type", "requestId", "reason"];
|
|
32887
|
+
if (!hasExactKeys(value, keys) || value.requestId !== void 0 && !isRequestId(value.requestId) || typeof value.reason !== "string" || !FAILURE_REASONS.has(value.reason)) {
|
|
32888
|
+
return eventError();
|
|
32889
|
+
}
|
|
32890
|
+
return value.requestId === void 0 ? {
|
|
32891
|
+
type: "failed",
|
|
32892
|
+
reason: value.reason
|
|
32893
|
+
} : {
|
|
32894
|
+
type: "failed",
|
|
32895
|
+
requestId: value.requestId,
|
|
32896
|
+
reason: value.reason
|
|
32897
|
+
};
|
|
32898
|
+
}
|
|
32899
|
+
if (value.type === "completed") {
|
|
32900
|
+
if (!hasExactKeys(value, ["type", "requestId"]) || !isRequestId(value.requestId)) {
|
|
32901
|
+
return eventError();
|
|
32902
|
+
}
|
|
32903
|
+
return { type: "completed", requestId: value.requestId };
|
|
32904
|
+
}
|
|
32905
|
+
if (value.type === "dispatching" || value.type === "accepted") {
|
|
32906
|
+
if (!hasExactKeys(value, [
|
|
32907
|
+
"type",
|
|
32908
|
+
"requestId",
|
|
32909
|
+
"personaKey",
|
|
32910
|
+
"route"
|
|
32911
|
+
]) || !isRequestId(value.requestId) || !isPersonaKey(value.personaKey) || !isValidMateRoute(value.route) || !value.route.startsWith(`/mates/${value.personaKey}`)) {
|
|
32912
|
+
return eventError();
|
|
32913
|
+
}
|
|
32914
|
+
return {
|
|
32915
|
+
type: value.type,
|
|
32916
|
+
requestId: value.requestId,
|
|
32917
|
+
personaKey: value.personaKey,
|
|
32918
|
+
route: value.route
|
|
32919
|
+
};
|
|
32920
|
+
}
|
|
32921
|
+
if (value.type === "indeterminate") {
|
|
32922
|
+
if (!hasExactKeys(value, ["type", "requestId", "stage", "route"]) || !isRequestId(value.requestId) || value.stage !== "dispatch" && value.stage !== "after-accept" || !isValidMateRoute(value.route)) {
|
|
32923
|
+
return eventError();
|
|
32924
|
+
}
|
|
32925
|
+
return {
|
|
32926
|
+
type: "indeterminate",
|
|
32927
|
+
requestId: value.requestId,
|
|
32928
|
+
stage: value.stage,
|
|
32929
|
+
route: value.route
|
|
32930
|
+
};
|
|
32931
|
+
}
|
|
32932
|
+
return eventError();
|
|
32933
|
+
}
|
|
32934
|
+
function serializeEvent(value) {
|
|
32935
|
+
return JSON.stringify(parseEvent(value));
|
|
32936
|
+
}
|
|
32937
|
+
|
|
32938
|
+
// src/lib/companion-chat-client.ts
|
|
32939
|
+
import { stat as stat3 } from "fs/promises";
|
|
32940
|
+
var RESPONSE_MAX_BYTES = 1e6;
|
|
32941
|
+
var REQUEST_DEADLINE_MS = 12e4;
|
|
32942
|
+
var OPAQUE_ID = /^[A-Za-z0-9_-]{1,256}$/u;
|
|
32943
|
+
var AGENT_NAME_MAX_LENGTH = 256;
|
|
32944
|
+
var PredispatchFailure = class extends Error {
|
|
32945
|
+
constructor(reason) {
|
|
32946
|
+
super("companion bridge pre-dispatch failure");
|
|
32947
|
+
this.reason = reason;
|
|
32948
|
+
}
|
|
32949
|
+
reason;
|
|
32950
|
+
};
|
|
32951
|
+
function isRecord3(value) {
|
|
32952
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
32953
|
+
}
|
|
32954
|
+
function hasControlCharacter(value) {
|
|
32955
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
32956
|
+
const code = value.charCodeAt(index);
|
|
32957
|
+
if (code <= 31 || code === 127) return true;
|
|
32958
|
+
}
|
|
32959
|
+
return false;
|
|
32960
|
+
}
|
|
32961
|
+
function isAgentName(value) {
|
|
32962
|
+
return typeof value === "string" && value.length > 0 && value.length <= AGENT_NAME_MAX_LENGTH && !hasControlCharacter(value);
|
|
32963
|
+
}
|
|
32964
|
+
function authenticatedHeaders(token) {
|
|
32965
|
+
return {
|
|
32966
|
+
accept: "application/json",
|
|
32967
|
+
authorization: `Bearer ${token}`
|
|
32968
|
+
};
|
|
32969
|
+
}
|
|
32970
|
+
function gatewayOrigin(gateway) {
|
|
32971
|
+
let url;
|
|
32972
|
+
try {
|
|
32973
|
+
url = new URL(gateway.gatewayUrl);
|
|
32974
|
+
} catch {
|
|
32975
|
+
throw new PredispatchFailure("not-connected");
|
|
32976
|
+
}
|
|
32977
|
+
if (url.protocol !== "https:" || url.username !== "" || url.password !== "" || url.pathname !== "/" || url.search !== "" || url.hash !== "") {
|
|
32978
|
+
throw new PredispatchFailure("not-connected");
|
|
32979
|
+
}
|
|
32980
|
+
return url.origin;
|
|
32981
|
+
}
|
|
32982
|
+
async function readBoundedText(response, maxBytes = RESPONSE_MAX_BYTES) {
|
|
32983
|
+
const declared = response.headers.get("content-length");
|
|
32984
|
+
if (declared !== null && Number(declared) > maxBytes) {
|
|
32985
|
+
throw new Error("bounded response rejected");
|
|
32986
|
+
}
|
|
32987
|
+
if (!response.body) throw new Error("response body unavailable");
|
|
32988
|
+
const reader = response.body.getReader();
|
|
32989
|
+
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
32990
|
+
let total = 0;
|
|
32991
|
+
let text = "";
|
|
32992
|
+
for (; ; ) {
|
|
32993
|
+
const part = await reader.read();
|
|
32994
|
+
if (part.done) break;
|
|
32995
|
+
total += part.value.byteLength;
|
|
32996
|
+
if (total > maxBytes) {
|
|
32997
|
+
await reader.cancel().catch(() => void 0);
|
|
32998
|
+
throw new Error("bounded response rejected");
|
|
32999
|
+
}
|
|
33000
|
+
text += decoder.decode(part.value, { stream: true });
|
|
33001
|
+
}
|
|
33002
|
+
text += decoder.decode();
|
|
33003
|
+
return text;
|
|
33004
|
+
}
|
|
33005
|
+
async function readJsonEnvelope(response) {
|
|
33006
|
+
if (!response.ok) {
|
|
33007
|
+
const reason = response.status === 401 || response.status === 403 ? "not-authorized" : response.status === 409 ? "busy" : "not-connected";
|
|
33008
|
+
throw new PredispatchFailure(reason);
|
|
33009
|
+
}
|
|
33010
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
33011
|
+
if (!contentType.toLowerCase().includes("application/json")) {
|
|
33012
|
+
throw new PredispatchFailure("not-connected");
|
|
33013
|
+
}
|
|
33014
|
+
let envelope;
|
|
33015
|
+
try {
|
|
33016
|
+
envelope = JSON.parse(await readBoundedText(response));
|
|
33017
|
+
} catch (error) {
|
|
33018
|
+
if (error instanceof PredispatchFailure) throw error;
|
|
33019
|
+
throw new PredispatchFailure("not-connected");
|
|
33020
|
+
}
|
|
33021
|
+
if (!isRecord3(envelope) || envelope.ok !== true || !("data" in envelope)) {
|
|
33022
|
+
throw new PredispatchFailure("not-connected");
|
|
33023
|
+
}
|
|
33024
|
+
return envelope.data;
|
|
33025
|
+
}
|
|
33026
|
+
function decodeAgents(data) {
|
|
33027
|
+
if (!isRecord3(data) || !Array.isArray(data.agents)) {
|
|
33028
|
+
throw new PredispatchFailure("mate-unavailable");
|
|
33029
|
+
}
|
|
33030
|
+
return data.agents.filter(isRecord3);
|
|
33031
|
+
}
|
|
33032
|
+
function resolveAgentName(agents, personaKey) {
|
|
33033
|
+
const matches = agents.filter(
|
|
33034
|
+
(agent) => agent.personaKey === personaKey && isAgentName(agent.name)
|
|
33035
|
+
);
|
|
33036
|
+
if (matches.length !== 1) {
|
|
33037
|
+
throw new PredispatchFailure("mate-unavailable");
|
|
33038
|
+
}
|
|
33039
|
+
const match = matches[0];
|
|
33040
|
+
if (!isAgentName(match.name)) {
|
|
33041
|
+
throw new PredispatchFailure("mate-unavailable");
|
|
33042
|
+
}
|
|
33043
|
+
return match.name;
|
|
33044
|
+
}
|
|
33045
|
+
function decodeConversationId(data) {
|
|
33046
|
+
if (!isRecord3(data) || typeof data.id !== "string" || !OPAQUE_ID.test(data.id)) {
|
|
33047
|
+
throw new PredispatchFailure("not-connected");
|
|
33048
|
+
}
|
|
33049
|
+
return data.id;
|
|
33050
|
+
}
|
|
33051
|
+
function decodeLastMessageId(data) {
|
|
33052
|
+
if (!isRecord3(data) || !Array.isArray(data.messages)) {
|
|
33053
|
+
throw new PredispatchFailure("not-connected");
|
|
33054
|
+
}
|
|
33055
|
+
const messages = data.messages;
|
|
33056
|
+
const last = messages.at(-1);
|
|
33057
|
+
if (!isRecord3(last) || typeof last.id !== "string" || !OPAQUE_ID.test(last.id)) {
|
|
33058
|
+
return void 0;
|
|
33059
|
+
}
|
|
33060
|
+
return last.id;
|
|
33061
|
+
}
|
|
33062
|
+
function mateRoute(personaKey, desktopAfter) {
|
|
33063
|
+
const marker = desktopAfter === void 0 ? "" : `&desktopAfter=${desktopAfter}`;
|
|
33064
|
+
return `/mates/${personaKey}?desktopPending=1${marker}`;
|
|
33065
|
+
}
|
|
33066
|
+
function emitFailure(sink, reason, requestId) {
|
|
33067
|
+
const event = parseEvent(
|
|
33068
|
+
requestId === void 0 ? { type: "failed", reason } : { type: "failed", requestId, reason }
|
|
33069
|
+
);
|
|
33070
|
+
sink(event);
|
|
33071
|
+
return event;
|
|
33072
|
+
}
|
|
33073
|
+
function classifyUnknownPredispatch(error) {
|
|
33074
|
+
return error instanceof PredispatchFailure ? error.reason : "not-connected";
|
|
33075
|
+
}
|
|
33076
|
+
async function acquireContext(deps) {
|
|
33077
|
+
let gateway;
|
|
33078
|
+
let token;
|
|
33079
|
+
try {
|
|
33080
|
+
gateway = await (deps.resolveGatewayContext ?? resolveGatewayContext)({
|
|
33081
|
+
interactive: false
|
|
33082
|
+
});
|
|
33083
|
+
token = await (deps.getFoundryToken ?? getFoundryToken)();
|
|
33084
|
+
} catch {
|
|
33085
|
+
throw new PredispatchFailure("not-authorized");
|
|
33086
|
+
}
|
|
33087
|
+
return { token, origin: gatewayOrigin(gateway) };
|
|
33088
|
+
}
|
|
33089
|
+
async function fetchAgents(fetchImpl, origin, token, signal) {
|
|
33090
|
+
let response;
|
|
33091
|
+
try {
|
|
33092
|
+
response = await fetchImpl(`${origin}/api/agents`, {
|
|
33093
|
+
headers: authenticatedHeaders(token),
|
|
33094
|
+
signal
|
|
33095
|
+
});
|
|
33096
|
+
} catch {
|
|
33097
|
+
throw new PredispatchFailure("not-connected");
|
|
33098
|
+
}
|
|
33099
|
+
return decodeAgents(await readJsonEnvelope(response));
|
|
33100
|
+
}
|
|
33101
|
+
function createDeadline(deps) {
|
|
33102
|
+
const controller = new AbortController();
|
|
33103
|
+
const timer = setTimeout(
|
|
33104
|
+
() => {
|
|
33105
|
+
controller.abort();
|
|
33106
|
+
},
|
|
33107
|
+
deps.deadlineMs ?? REQUEST_DEADLINE_MS
|
|
33108
|
+
);
|
|
33109
|
+
return { controller, timer };
|
|
33110
|
+
}
|
|
33111
|
+
async function readInstallEpoch() {
|
|
33112
|
+
try {
|
|
33113
|
+
const stats = await stat3(getConfigPath());
|
|
33114
|
+
const candidates = [stats.birthtimeMs, stats.mtimeMs].filter(
|
|
33115
|
+
(value) => Number.isFinite(value) && value > 0
|
|
33116
|
+
);
|
|
33117
|
+
if (candidates.length === 0) return null;
|
|
33118
|
+
return Math.min(...candidates);
|
|
33119
|
+
} catch {
|
|
33120
|
+
return null;
|
|
33121
|
+
}
|
|
33122
|
+
}
|
|
33123
|
+
async function rosterCompanions(sink, deps = {}) {
|
|
33124
|
+
const { controller, timer } = createDeadline(deps);
|
|
33125
|
+
try {
|
|
33126
|
+
const { token, origin } = await acquireContext(deps);
|
|
33127
|
+
const agents = await fetchAgents(
|
|
33128
|
+
deps.fetch ?? fetch,
|
|
33129
|
+
origin,
|
|
33130
|
+
token,
|
|
33131
|
+
controller.signal
|
|
33132
|
+
);
|
|
33133
|
+
const now = (deps.now ?? Date.now)();
|
|
33134
|
+
const installed = await (deps.installEpoch ?? readInstallEpoch)();
|
|
33135
|
+
const activeSince = new Date(
|
|
33136
|
+
installed === null || installed > now ? now : installed
|
|
33137
|
+
).toISOString();
|
|
33138
|
+
const mates = [
|
|
33139
|
+
{
|
|
33140
|
+
personaKey: "startup-advisor",
|
|
33141
|
+
agentName: resolveAgentName(agents, "startup-advisor"),
|
|
33142
|
+
displayName: "Stacey",
|
|
33143
|
+
portraitKey: "stacey",
|
|
33144
|
+
activeSince
|
|
33145
|
+
},
|
|
33146
|
+
{
|
|
33147
|
+
personaKey: "azure-advisor",
|
|
33148
|
+
agentName: resolveAgentName(agents, "azure-advisor"),
|
|
33149
|
+
displayName: "Azzy",
|
|
33150
|
+
portraitKey: "azzy",
|
|
33151
|
+
activeSince
|
|
33152
|
+
}
|
|
33153
|
+
];
|
|
33154
|
+
const event = parseEvent({ type: "roster", origin, mates });
|
|
33155
|
+
sink(event);
|
|
33156
|
+
return event;
|
|
33157
|
+
} catch (error) {
|
|
33158
|
+
return emitFailure(sink, classifyUnknownPredispatch(error));
|
|
33159
|
+
} finally {
|
|
33160
|
+
clearTimeout(timer);
|
|
33161
|
+
}
|
|
33162
|
+
}
|
|
33163
|
+
async function drainAcceptedSse(response) {
|
|
33164
|
+
const text = await readBoundedText(response);
|
|
33165
|
+
const frames = text.split(/\r?\n\r?\n/u);
|
|
33166
|
+
let sawDone = false;
|
|
33167
|
+
for (const frame of frames) {
|
|
33168
|
+
if (frame.trim().length === 0) continue;
|
|
33169
|
+
const dataLines = frame.split(/\r?\n/u).filter((line2) => line2.startsWith("data:")).map((line2) => line2.slice(5).trimStart());
|
|
33170
|
+
if (dataLines.length === 0) continue;
|
|
33171
|
+
const data = dataLines.join("\n");
|
|
33172
|
+
if (sawDone) throw new Error("data after terminal stream marker");
|
|
33173
|
+
if (data === "[DONE]") {
|
|
33174
|
+
sawDone = true;
|
|
33175
|
+
continue;
|
|
33176
|
+
}
|
|
33177
|
+
const parsed = JSON.parse(data);
|
|
33178
|
+
if (!isRecord3(parsed) || typeof parsed.type !== "string" || parsed.type === "error") {
|
|
33179
|
+
throw new Error("invalid stream event");
|
|
33180
|
+
}
|
|
33181
|
+
}
|
|
33182
|
+
if (!sawDone) throw new Error("stream ended without terminal marker");
|
|
33183
|
+
}
|
|
33184
|
+
async function sendCompanionMessage(request, sink, deps = {}) {
|
|
33185
|
+
const { controller, timer } = createDeadline(deps);
|
|
33186
|
+
const fetchImpl = deps.fetch ?? fetch;
|
|
33187
|
+
let route;
|
|
33188
|
+
let dispatched = false;
|
|
33189
|
+
let accepted = false;
|
|
33190
|
+
try {
|
|
33191
|
+
const { token, origin } = await acquireContext(deps);
|
|
33192
|
+
const headers = authenticatedHeaders(token);
|
|
33193
|
+
const agents = await fetchAgents(
|
|
33194
|
+
fetchImpl,
|
|
33195
|
+
origin,
|
|
33196
|
+
token,
|
|
33197
|
+
controller.signal
|
|
33198
|
+
);
|
|
33199
|
+
const agentName = resolveAgentName(agents, request.personaKey);
|
|
33200
|
+
let conversationResponse;
|
|
33201
|
+
try {
|
|
33202
|
+
conversationResponse = await fetchImpl(`${origin}/api/conversations`, {
|
|
33203
|
+
method: "POST",
|
|
33204
|
+
headers: { ...headers, "content-type": "application/json" },
|
|
33205
|
+
body: JSON.stringify({ agentName }),
|
|
33206
|
+
signal: controller.signal
|
|
33207
|
+
});
|
|
33208
|
+
} catch {
|
|
33209
|
+
throw new PredispatchFailure("not-connected");
|
|
33210
|
+
}
|
|
33211
|
+
const conversationId = decodeConversationId(
|
|
33212
|
+
await readJsonEnvelope(conversationResponse)
|
|
33213
|
+
);
|
|
33214
|
+
let messagesResponse;
|
|
33215
|
+
try {
|
|
33216
|
+
messagesResponse = await fetchImpl(
|
|
33217
|
+
`${origin}/api/conversations/${encodeURIComponent(conversationId)}/messages`,
|
|
33218
|
+
{ headers, signal: controller.signal }
|
|
33219
|
+
);
|
|
33220
|
+
} catch {
|
|
33221
|
+
throw new PredispatchFailure("not-connected");
|
|
33222
|
+
}
|
|
33223
|
+
const desktopAfter = decodeLastMessageId(
|
|
33224
|
+
await readJsonEnvelope(messagesResponse)
|
|
33225
|
+
);
|
|
33226
|
+
route = mateRoute(request.personaKey, desktopAfter);
|
|
33227
|
+
const dispatching = parseEvent({
|
|
33228
|
+
type: "dispatching",
|
|
33229
|
+
requestId: request.requestId,
|
|
33230
|
+
personaKey: request.personaKey,
|
|
33231
|
+
route
|
|
33232
|
+
});
|
|
33233
|
+
sink(dispatching);
|
|
33234
|
+
dispatched = true;
|
|
33235
|
+
let chatResponse;
|
|
33236
|
+
try {
|
|
33237
|
+
chatResponse = await fetchImpl(`${origin}/api/chat`, {
|
|
33238
|
+
method: "POST",
|
|
33239
|
+
headers: { ...headers, "content-type": "application/json" },
|
|
33240
|
+
body: JSON.stringify({
|
|
33241
|
+
messages: [{ role: "user", content: request.text }],
|
|
33242
|
+
agentName,
|
|
33243
|
+
conversationId
|
|
33244
|
+
}),
|
|
33245
|
+
signal: controller.signal
|
|
33246
|
+
});
|
|
33247
|
+
} catch {
|
|
33248
|
+
throw new Error("chat dispatch outcome unavailable");
|
|
33249
|
+
}
|
|
33250
|
+
if (!chatResponse.ok || !chatResponse.body || !(chatResponse.headers.get("content-type") ?? "").toLowerCase().includes("text/event-stream")) {
|
|
33251
|
+
throw new Error("chat response not accepted");
|
|
33252
|
+
}
|
|
33253
|
+
const acceptedEvent = parseEvent({
|
|
33254
|
+
type: "accepted",
|
|
33255
|
+
requestId: request.requestId,
|
|
33256
|
+
personaKey: request.personaKey,
|
|
33257
|
+
route
|
|
33258
|
+
});
|
|
33259
|
+
sink(acceptedEvent);
|
|
33260
|
+
accepted = true;
|
|
33261
|
+
await drainAcceptedSse(chatResponse);
|
|
33262
|
+
const completed = parseEvent({
|
|
33263
|
+
type: "completed",
|
|
33264
|
+
requestId: request.requestId
|
|
33265
|
+
});
|
|
33266
|
+
sink(completed);
|
|
33267
|
+
return completed;
|
|
33268
|
+
} catch (error) {
|
|
33269
|
+
if (!dispatched) {
|
|
33270
|
+
return emitFailure(
|
|
33271
|
+
sink,
|
|
33272
|
+
classifyUnknownPredispatch(error),
|
|
33273
|
+
request.requestId
|
|
33274
|
+
);
|
|
33275
|
+
}
|
|
33276
|
+
const indeterminate = parseEvent({
|
|
33277
|
+
type: "indeterminate",
|
|
33278
|
+
requestId: request.requestId,
|
|
33279
|
+
stage: accepted ? "after-accept" : "dispatch",
|
|
33280
|
+
route: route ?? mateRoute(request.personaKey)
|
|
33281
|
+
});
|
|
33282
|
+
sink(indeterminate);
|
|
33283
|
+
return indeterminate;
|
|
33284
|
+
} finally {
|
|
33285
|
+
clearTimeout(timer);
|
|
33286
|
+
}
|
|
33287
|
+
}
|
|
33288
|
+
|
|
33289
|
+
// src/lib/companion-update-check.ts
|
|
33290
|
+
async function checkCompanionUpdate(options, deps = {}) {
|
|
33291
|
+
const installed = await statusCompanion(options).then((state) => state.state === "installed" ? state.version : null).catch(() => null);
|
|
33292
|
+
const release = await (deps.readRelease ?? readCompanionRelease)().catch(() => null);
|
|
33293
|
+
return {
|
|
33294
|
+
type: "update",
|
|
33295
|
+
installed,
|
|
33296
|
+
available: release?.component.version ?? null,
|
|
33297
|
+
severity: release === null ? null : release.severity
|
|
33298
|
+
};
|
|
33299
|
+
}
|
|
33300
|
+
function companionUpdateAvailable(update) {
|
|
33301
|
+
return update.available !== null && update.available !== update.installed;
|
|
33302
|
+
}
|
|
33303
|
+
|
|
33304
|
+
// src/commands/companion/bridge.ts
|
|
33305
|
+
var defaultDeps2 = {
|
|
33306
|
+
roster: rosterCompanions,
|
|
33307
|
+
send: sendCompanionMessage,
|
|
33308
|
+
update: () => checkCompanionUpdate(defaultLocalCompanionInstallOptions())
|
|
33309
|
+
};
|
|
33310
|
+
async function readSingleRequest(stdin) {
|
|
33311
|
+
let input = "";
|
|
33312
|
+
for await (const chunk of stdin) {
|
|
33313
|
+
input += String(chunk);
|
|
33314
|
+
if (input.length > COMPANION_BRIDGE_LINE_MAX_LENGTH + 2) {
|
|
33315
|
+
throw new Error("request exceeds bridge bound");
|
|
33316
|
+
}
|
|
33317
|
+
}
|
|
33318
|
+
if (input.endsWith("\r\n")) {
|
|
33319
|
+
input = input.slice(0, -2);
|
|
33320
|
+
} else if (input.endsWith("\n")) {
|
|
33321
|
+
input = input.slice(0, -1);
|
|
33322
|
+
}
|
|
33323
|
+
if (input.length === 0 || /[\r\n]/u.test(input)) {
|
|
33324
|
+
throw new Error("bridge accepts exactly one request");
|
|
33325
|
+
}
|
|
33326
|
+
return input;
|
|
33327
|
+
}
|
|
33328
|
+
function exitFor(terminal) {
|
|
33329
|
+
if (terminal.type === "roster" || terminal.type === "completed") return 0;
|
|
33330
|
+
if (terminal.type === "update") return 0;
|
|
33331
|
+
if (terminal.type === "indeterminate") return 4;
|
|
33332
|
+
if (terminal.type === "failed" && terminal.reason !== "bad-request") {
|
|
33333
|
+
return 3;
|
|
33334
|
+
}
|
|
33335
|
+
return 2;
|
|
33336
|
+
}
|
|
33337
|
+
async function runCompanionBridge(stdin, stdout, stderr, deps = defaultDeps2) {
|
|
33338
|
+
let request;
|
|
33339
|
+
try {
|
|
33340
|
+
request = parseRequestLine(await readSingleRequest(stdin));
|
|
33341
|
+
} catch {
|
|
33342
|
+
stderr.write("bridge request rejected\n");
|
|
33343
|
+
return 2;
|
|
33344
|
+
}
|
|
33345
|
+
const sink = (event) => {
|
|
33346
|
+
stdout.write(`${serializeEvent(event)}
|
|
33347
|
+
`);
|
|
33348
|
+
};
|
|
33349
|
+
try {
|
|
33350
|
+
let terminal;
|
|
33351
|
+
if (request.type === "roster") terminal = await deps.roster(sink);
|
|
33352
|
+
else if (request.type === "update") {
|
|
33353
|
+
terminal = await deps.update();
|
|
33354
|
+
sink(terminal);
|
|
33355
|
+
} else terminal = await deps.send(request, sink);
|
|
33356
|
+
return exitFor(terminal);
|
|
33357
|
+
} catch {
|
|
33358
|
+
stderr.write("bridge request failed\n");
|
|
33359
|
+
return 3;
|
|
33360
|
+
}
|
|
33361
|
+
}
|
|
33362
|
+
var CompanionBridgeCommand = class extends Command62 {
|
|
33363
|
+
static paths = [["companion", "_bridge"]];
|
|
33364
|
+
async execute() {
|
|
33365
|
+
return runCompanionBridge(
|
|
33366
|
+
this.context.stdin,
|
|
33367
|
+
this.context.stdout,
|
|
33368
|
+
this.context.stderr
|
|
33369
|
+
);
|
|
33370
|
+
}
|
|
33371
|
+
};
|
|
33372
|
+
|
|
33373
|
+
// src/commands/companion/status.ts
|
|
33374
|
+
import { Command as Command63 } from "clipanion";
|
|
33375
|
+
async function runCompanionStatusCommand(stdout, status, update) {
|
|
33376
|
+
const state = await status();
|
|
33377
|
+
if (state.state === "not-installed") {
|
|
33378
|
+
stdout.write("Desktop companions are not installed.\n");
|
|
33379
|
+
return 0;
|
|
33380
|
+
}
|
|
33381
|
+
if (state.state === "needs-repair") {
|
|
33382
|
+
stdout.write(`Desktop companions need repair: ${state.reason}
|
|
33383
|
+
`);
|
|
33384
|
+
return 1;
|
|
33385
|
+
}
|
|
33386
|
+
stdout.write(
|
|
33387
|
+
`Desktop companions are installed (${state.version}); start at login: ${state.startAtLogin ? "on" : "off"}.
|
|
33388
|
+
`
|
|
33389
|
+
);
|
|
33390
|
+
if (update) {
|
|
33391
|
+
const answer = await update().catch(() => null);
|
|
33392
|
+
if (answer !== null && companionUpdateAvailable(answer)) {
|
|
33393
|
+
stdout.write(
|
|
33394
|
+
`A newer companion is available (${String(answer.available)}).
|
|
33395
|
+
Run: m8t companion install
|
|
33396
|
+
`
|
|
33397
|
+
);
|
|
33398
|
+
}
|
|
33399
|
+
}
|
|
33400
|
+
return 0;
|
|
33401
|
+
}
|
|
33402
|
+
var CompanionStatusCommand = class extends M8tCommand {
|
|
33403
|
+
static paths = [["companion", "status"]];
|
|
33404
|
+
static usage = Command63.Usage({
|
|
33405
|
+
description: "Verify the installed desktop companion without launching it."
|
|
33406
|
+
});
|
|
33407
|
+
async executeCommand() {
|
|
33408
|
+
const options = defaultLocalCompanionInstallOptions();
|
|
33409
|
+
return runCompanionStatusCommand(
|
|
33410
|
+
this.context.stdout,
|
|
33411
|
+
() => statusCompanion(options),
|
|
33412
|
+
() => checkCompanionUpdate(options)
|
|
33413
|
+
);
|
|
33414
|
+
}
|
|
33415
|
+
};
|
|
33416
|
+
|
|
33417
|
+
// src/commands/companion/repair.ts
|
|
33418
|
+
import { Command as Command64 } from "clipanion";
|
|
33419
|
+
async function runCompanionRepairCommand(stdout, repair) {
|
|
33420
|
+
const state = await repair();
|
|
33421
|
+
if (state.state === "not-released") {
|
|
33422
|
+
stdout.write(
|
|
33423
|
+
"The release this platform is pinned to carries no desktop companions.\nThere is nothing to repair to \u2014 run: m8t platform update\n"
|
|
33424
|
+
);
|
|
33425
|
+
return 1;
|
|
33426
|
+
}
|
|
33427
|
+
if (state.state !== "installed") {
|
|
33428
|
+
stdout.write("Desktop companions could not be repaired.\n");
|
|
33429
|
+
return 1;
|
|
33430
|
+
}
|
|
33431
|
+
stdout.write(`Desktop companions repaired (${state.version}).
|
|
33432
|
+
`);
|
|
33433
|
+
return 0;
|
|
33434
|
+
}
|
|
33435
|
+
var CompanionRepairCommand = class extends M8tCommand {
|
|
33436
|
+
static paths = [["companion", "repair"]];
|
|
33437
|
+
static usage = Command64.Usage({
|
|
33438
|
+
description: "Restore the internal desktop companion and start-at-login state."
|
|
33439
|
+
});
|
|
33440
|
+
async executeCommand() {
|
|
33441
|
+
return runCompanionRepairCommand(
|
|
33442
|
+
this.context.stdout,
|
|
33443
|
+
() => convergeCompanionFromChannel(repairCompanion)
|
|
33444
|
+
);
|
|
33445
|
+
}
|
|
33446
|
+
};
|
|
33447
|
+
|
|
33448
|
+
// src/commands/companion/uninstall.ts
|
|
33449
|
+
import { Command as Command65 } from "clipanion";
|
|
33450
|
+
async function runCompanionUninstallCommand(stdout, uninstall) {
|
|
33451
|
+
const state = await uninstall();
|
|
33452
|
+
if (state.state !== "not-installed") {
|
|
33453
|
+
stdout.write("Desktop companions were not completely removed.\n");
|
|
33454
|
+
return 1;
|
|
33455
|
+
}
|
|
33456
|
+
stdout.write("Desktop companions removed for this user.\n");
|
|
33457
|
+
return 0;
|
|
33458
|
+
}
|
|
33459
|
+
var CompanionUninstallCommand = class extends M8tCommand {
|
|
33460
|
+
static paths = [["companion", "uninstall"]];
|
|
33461
|
+
static usage = Command65.Usage({
|
|
33462
|
+
description: "Remove only this user's desktop companion installation."
|
|
33463
|
+
});
|
|
33464
|
+
async executeCommand() {
|
|
33465
|
+
const options = defaultLocalCompanionInstallOptions();
|
|
33466
|
+
return runCompanionUninstallCommand(
|
|
33467
|
+
this.context.stdout,
|
|
33468
|
+
() => uninstallCompanion(options)
|
|
33469
|
+
);
|
|
33470
|
+
}
|
|
33471
|
+
};
|
|
33472
|
+
|
|
31570
33473
|
// src/cli.ts
|
|
31571
33474
|
var cli = new Cli({
|
|
31572
33475
|
binaryName: "m8t",
|
|
@@ -31633,6 +33536,11 @@ cli.register(BootstrapFinishCommand);
|
|
|
31633
33536
|
cli.register(BootstrapUiCommand);
|
|
31634
33537
|
cli.register(BootstrapSeedProfileCommand);
|
|
31635
33538
|
cli.register(TelemetryEnrollCommand);
|
|
33539
|
+
cli.register(CompanionBridgeCommand);
|
|
33540
|
+
cli.register(CompanionInstallCommand);
|
|
33541
|
+
cli.register(CompanionStatusCommand);
|
|
33542
|
+
cli.register(CompanionRepairCommand);
|
|
33543
|
+
cli.register(CompanionUninstallCommand);
|
|
31636
33544
|
async function main() {
|
|
31637
33545
|
try {
|
|
31638
33546
|
return await cli.run(process.argv.slice(2));
|