@m8t-stack/cli 0.2.53 → 0.2.55

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -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.53";
1367
+ var CLI_VERSION = "0.2.55";
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((resolve3) => setTimeout(resolve3, ms));
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((resolve3) => {
3026
- resolve3(null);
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 path38(statics, ...params) {
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 path39 = statics.reduce((previousValue, currentValue, index) => {
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 = path39.split(/[?#]/, 1)[0];
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
- ${path39}
3744
+ ${path43}
3745
3745
  ${underline}`);
3746
3746
  }
3747
- return path39;
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((resolve3, reject) => {
3924
- __classPrivateFieldSet(this, _EventStream_resolveConnectedPromise, resolve3, "f");
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((resolve3, reject) => {
3928
- __classPrivateFieldSet(this, _EventStream_resolveEndPromise, resolve3, "f");
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((resolve3, reject) => {
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, resolve3);
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((resolve3, reject) => {
4116
- readQueue.push({ resolve: resolve3, reject });
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((resolve3, reject) => readQueue.push({ resolve: resolve3, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
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((resolve3, reject) => readQueue.push({ resolve: resolve3, reject })).then((event2) => event2 ? { value: event2, done: false } : { value: void 0, done: true });
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((resolve3, reject) => readQueue.push({ resolve: resolve3, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
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((resolve3, reject) => readQueue.push({ resolve: resolve3, reject })).then((event2) => event2 ? { value: event2, done: false } : { value: void 0, done: true });
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(path38, query, defaultBaseURL) {
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(path38) ? new URL(path38) : new URL(baseURL + (baseURL.endsWith("/") && path38.startsWith("/") ? path38.slice(1) : path38));
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(path38, opts) {
12048
- return this.methodRequest("get", path38, opts);
12047
+ get(path42, opts) {
12048
+ return this.methodRequest("get", path42, opts);
12049
12049
  }
12050
- post(path38, opts) {
12051
- return this.methodRequest("post", path38, opts);
12050
+ post(path42, opts) {
12051
+ return this.methodRequest("post", path42, opts);
12052
12052
  }
12053
- patch(path38, opts) {
12054
- return this.methodRequest("patch", path38, opts);
12053
+ patch(path42, opts) {
12054
+ return this.methodRequest("patch", path42, opts);
12055
12055
  }
12056
- put(path38, opts) {
12057
- return this.methodRequest("put", path38, opts);
12056
+ put(path42, opts) {
12057
+ return this.methodRequest("put", path42, opts);
12058
12058
  }
12059
- delete(path38, opts) {
12060
- return this.methodRequest("delete", path38, opts);
12059
+ delete(path42, opts) {
12060
+ return this.methodRequest("delete", path42, opts);
12061
12061
  }
12062
- methodRequest(method, path38, opts) {
12062
+ methodRequest(method, path42, opts) {
12063
12063
  return this.request(Promise.resolve(opts).then((opts2) => {
12064
- return { method, path: path38, ...opts2 };
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(path38, Page2, opts) {
12190
- return this.requestAPIList(Page2, opts && "then" in opts ? opts.then((opts2) => ({ method: "get", path: path38, ...opts2 })) : { method: "get", path: path38, ...opts });
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: path38, query, defaultBaseURL } = options;
12285
- const url = this.buildURL(path38, query, defaultBaseURL);
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((resolve3, reject) => {
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
- resolve3(Buffer.concat(out).toString("utf8"));
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 path38 = qs.toString().length > 0 ? `/api/bindings?${qs.toString()}` : "/api/bindings";
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: path38 },
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((resolve3, reject) => {
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
- resolve3({ app: createdApp, installationId });
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((resolve3) => {
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
- resolve3(r);
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 (path38) => {
14543
- const r = await exec("gh", ["api", "-H", "Accept: application/vnd.github.raw+json", `/repos/${slug}/contents/${path38}`]);
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((resolve3, reject) => {
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
- resolve3();
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: (path38) => readRepoFileViaApp({ token: args.token, repo: args.repo, path: path38, fetchImpl: args.fetchImpl }),
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 path38 = repo.replace(/^ghcr\.io\//, "");
19012
- const tokenRes = await fetchImpl(`https://ghcr.io/token?scope=repository:${path38}:pull`);
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/${path38}/tags/list`, {
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 readFile8 = deps.readFile ?? ((p) => fs19.readFileSync(p, "utf8"));
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 = readFile8(source.file);
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 = (path38, skipReason) => ({ path: path38, kind: "skip", skipReason });
20448
+ var skip = (path42, skipReason) => ({ path: path42, kind: "skip", skipReason });
20408
20449
  function classifyCandidate(c) {
20409
- const { path: path38, base, ours, theirs } = c;
20410
- if (theirs === base) return { path: path38, kind: "noop" };
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: path38, kind: "noop" };
20413
- if (ours === null) return { path: path38, kind: "add", content: theirs };
20414
- if (ours === theirs) return { path: path38, kind: "noop" };
20415
- return skip(path38, "you already have a file at this path");
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: path38, kind: "noop" } : skip(path38, "you removed this file; upstream changed it");
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: path38, kind: "delete" } : skip(path38, "you edited this file; upstream removed it");
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: path38, kind: "update", content: theirs };
20464
+ if (ours === base) return { path: path42, kind: "update", content: theirs };
20424
20465
  const m = mergeThreeWay(ours, base, theirs);
20425
- return m.ok ? { path: path38, kind: "update", content: m.merged } : skip(path38, "your edits conflict with the update");
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: (path38, ref) => readRepoFileViaApp({ token, repo, path: path38, ref })
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 path38 = prefix ? `${prefix}${segment}` : d.path;
22687
- if (d.children && d.children.length > 0) out.push(...flattenDelta(d.children, path38));
22688
- else out.push({ ...d, path: path38 });
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((resolve3, reject) => {
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
- resolve3(Buffer.concat(out).toString("utf8"));
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 = (path38, allowedPrefix) => allowedPrefix.test(path38) && !path38.split("/").includes("..");
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, path38, body) {
26290
- const res = await doFetch(`${API2}/repos/${repoPath}${path38}`, {
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, path38) {
26304
- const encodedPath = path38.split("/").map(encodeURIComponent).join("/");
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(path38) {
26361
+ async readFile(path42) {
26321
26362
  const token = await args.mintToken();
26322
- return readFileWith(token, path38);
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 path38 of paths) {
26332
- const content = await readFileWith(token, path38);
26333
- out.push({ path: path38, frontmatter: content ? parseFrontmatter(content) : {} });
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: (path38) => originByPath.get(path38) ?? "worker"
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(path38, message) {
26834
+ constructor(path42, message) {
26794
26835
  super(message);
26795
- this.path = path38;
26836
+ this.path = path42;
26796
26837
  this.name = "BatchAbort";
26797
26838
  }
26798
26839
  };
26799
- var isIndexFile = (path38) => /(^|\/)[^/]*_index\.md$/.test(path38);
26800
- var isMemoryIndex = (path38) => path38 === "memory/MEMORY.md";
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 path38 = targetPath(delta);
26818
- if (path38 === null)
26858
+ const path42 = targetPath(delta);
26859
+ if (path42 === null)
26819
26860
  return { ok: true };
26820
- if (isIndexFile(path38) && !isMemoryIndex(path38)) {
26821
- return { ok: false, flagged: { kind: "flagged", path: path38, tension: `refused: ${path38} is an index file (only memory/MEMORY.md is editable)`, evidence } };
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(path38);
26824
- const isStructural = path38.startsWith("references/");
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(path38, `supersede/retract OLD target ${path38} is origin: ${origin} \u2014 aborting batch (never half-apply)`);
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: path38, tension: `operator-origin (${origin}); flag-only`, evidence } };
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 = (path38) => path38.split("/").includes("..");
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 = (path38, title, date, tags) => `- \`${path38}\` \u2014 **${title}**: ${title}. (${date} \xB7 ${tags.join(", ")})`;
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 = (path38) => Promise.resolve(mem.files.find((f) => f.path === path38) ?? null);
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 path38 = checkPathAllowed(delta);
27127
- if (!path38.ok) {
27128
- digest.push(path38.rejected);
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 sep = "' failed: ";
27918
- const stderr = msg.includes(sep) ? msg.slice(msg.indexOf(sep) + sep.length) : msg;
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(path38) {
28292
+ function ingestUrl(path42) {
28252
28293
  const base = INGEST_BASE_URL.replace(/\/+$/, "");
28253
- return `${base}${path38.startsWith("/") ? path38 : `/${path38}`}`;
28294
+ return `${base}${path42.startsWith("/") ? path42 : `/${path42}`}`;
28254
28295
  }
28255
28296
 
28256
28297
  // src/commands/bootstrap/preflight.ts
@@ -28735,7 +28776,7 @@ ${colors.error("\u2717")} The GitHub App on disk is installed on ${colors.field(
28735
28776
  // src/commands/bootstrap/launch.ts
28736
28777
  var DEFAULT_RG = "rg-m8t-stack";
28737
28778
  var DEFAULT_INSTALLER = "ghcr.io/m8t-labs/m8t-installer";
28738
- var DEFAULT_INSTALLER_TAG = "v0.1.48";
28779
+ var DEFAULT_INSTALLER_TAG = "v0.1.49";
28739
28780
  var ACI_NAME = "m8t-installer";
28740
28781
  var MI_NAME = "m8t-installer-mi";
28741
28782
  var BootstrapLaunchCommand = class extends M8tCommand {
@@ -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 fs31 from "fs/promises";
29242
- import * as os16 from "os";
29243
- import * as path34 from "path";
29244
- import { Command as Command57, Option as Option54 } from "clipanion";
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 open = args.webappUrl ? `${args.webappUrl} (or run: m8t open)` : "run: m8t open";
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: ${open}`,
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/commands/bootstrap/finish.ts
30207
- var BootstrapFinishCommand = class extends M8tCommand {
30208
- static paths = [["bootstrap", "finish"]];
30209
- static usage = Command57.Usage({
30210
- description: "Point your local tools at the now-live platform (repo-root marker, discovery cache, next steps).",
30211
- 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.",
30212
- examples: [["Finish local", "$0 bootstrap finish --repo-root /path/to/m8t"]]
30213
- });
30214
- repoRoot = Option54.String("--repo-root");
30215
- subscription = Option54.String("--subscription");
30216
- resourceGroup = Option54.String("--resource-group");
30217
- async executeCommand() {
30218
- const state = await readBootstrapState();
30219
- if (!state) {
30220
- throw new LocalCliError({ code: "BOOTSTRAP_NO_STATE", message: "No bootstrap state.", hint: "Run 'm8t bootstrap launch' first." });
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
- const doc = await readStatusBlob({ saName: state.statusSaName, resourceGroup: state.resourceGroup, subscriptionId: state.subscriptionId });
30223
- if (doc.status !== "done") {
30224
- throw new LocalCliError({
30225
- code: "BOOTSTRAP_FINISH_NOT_DONE",
30226
- message: `The platform is not live yet (status: ${doc.status}, phase: ${doc.phase}).`,
30227
- hint: "Wait for 'm8t bootstrap status --watch' to reach done."
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
- const repoRoot = (typeof this.repoRoot === "string" ? this.repoRoot : void 0) ?? process.cwd();
30231
- const markerDir = path34.join(os16.homedir(), ".m8t");
30232
- await fs31.mkdir(markerDir, { recursive: true });
30233
- await fs31.writeFile(path34.join(markerDir, "repo-root"), `${repoRoot}
30234
- `, "utf8");
30235
- const subscriptionId = (typeof this.subscription === "string" ? this.subscription : void 0) ?? state.subscriptionId;
30236
- const resourceGroup = (typeof this.resourceGroup === "string" ? this.resourceGroup : void 0) ?? state.resourceGroup;
30237
- let webappUrl;
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 d = await discoverGateway({ subscriptionId, interactive: false, resourceGroup });
30240
- await writeConfig({
30241
- gatewayUrl: d.gatewayUrl,
30242
- gatewayClientId: d.gatewayClientId,
30243
- gatewayTenantId: d.gatewayTenantId,
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
- try {
30271
- const verdict = await runUsagePhase(
30272
- buildPrereqDeps({ subscription: subscriptionId, resourceGroup }),
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
- } catch (e) {
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
- let brainOrg = null;
30312
- try {
30313
- const credsRaw = await fs31.readFile(path34.join(markerDir, "github-app.json"), "utf8");
30314
- const creds = JSON.parse(credsRaw);
30315
- brainOrg = typeof creds.org === "string" ? creds.org : null;
30316
- } catch {
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
- this.context.stdout.write(renderInstallSummary({ webappUrl, brainOrg }) + "\n");
30319
- this.context.stdout.write(
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
- try {
30330
- await reactiveSeedOnFinish({
30331
- endpoint: doc.result?.foundryEndpoint,
30332
- subscriptionId,
30333
- stdout: (s) => this.context.stdout.write(s)
30334
- });
30335
- } catch (e) {
30336
- this.context.stderr.write(
30337
- ` ${colors.dim(`(company-profile seed deferred: ${e instanceof Error ? e.message : String(e)} \u2014 run 'm8t bootstrap seed-profile' later)`)}
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
- // src/commands/bootstrap/ui.ts
30346
- import * as fs33 from "fs";
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
- // src/lib/bootstrap-ui.ts
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("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
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((resolve3) => setTimeout(resolve3, ms));
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 = path35.join(args.repoRoot, "apps", "web", ".env.local");
31659
+ const envPath = path39.join(args.repoRoot, "apps", "web", ".env.local");
30526
31660
  let prior = "";
30527
- if (fs32.existsSync(envPath)) {
30528
- prior = fs32.readFileSync(envPath, "utf8");
30529
- fs32.copyFileSync(envPath, envPath + ".bak");
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
- fs32.writeFileSync(envPath, body, { encoding: "utf8", mode: 384 });
31689
+ fs35.writeFileSync(envPath, body, { encoding: "utf8", mode: 384 });
30556
31690
  try {
30557
- fs32.chmodSync(envPath, 384);
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 = path35.join(repoRoot, "apps", "web", ".env.local");
31707
+ const envPath = path39.join(repoRoot, "apps", "web", ".env.local");
30574
31708
  let secret;
30575
31709
  try {
30576
- secret = readValidInternalSecret(fs32.readFileSync(envPath, "utf8"));
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((resolve3, reject) => {
30601
- const child = spawn7(cmd, cmdArgs, { cwd, stdio: "inherit", shell: process.platform === "win32" });
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
- resolve3();
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 = os17.homedir()) {
30628
- const dir = path35.join(home, ".m8t");
31761
+ function onboardingUiPaths(home = os19.homedir()) {
31762
+ const dir = path39.join(home, ".m8t");
30629
31763
  return {
30630
- logPath: path35.join(dir, "onboarding-ui.log"),
30631
- pidPath: path35.join(dir, "onboarding-ui.pid")
31764
+ logPath: path39.join(dir, "onboarding-ui.log"),
31765
+ pidPath: path39.join(dir, "onboarding-ui.pid")
30632
31766
  };
30633
31767
  }
30634
- function onboardingRelayPaths(home = os17.homedir()) {
30635
- const dir = path35.join(home, ".m8t");
31768
+ function onboardingRelayPaths(home = os19.homedir()) {
31769
+ const dir = path39.join(home, ".m8t");
30636
31770
  return {
30637
- logPath: path35.join(dir, "onboarding-relay.log"),
30638
- pidPath: path35.join(dir, "onboarding-relay.pid")
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((resolve3) => {
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
- resolve3(true);
31781
+ resolve4(true);
30648
31782
  });
30649
31783
  s.on("error", () => {
30650
- resolve3(false);
31784
+ resolve4(false);
30651
31785
  });
30652
31786
  s.on("timeout", () => {
30653
31787
  s.destroy();
30654
- resolve3(false);
31788
+ resolve4(false);
30655
31789
  });
30656
31790
  });
30657
31791
  }
30658
31792
  function removePidFile(pidPath) {
30659
31793
  try {
30660
- fs32.unlinkSync(pidPath);
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 = fs32.readFileSync(pidPath, "utf8").trim();
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
- fs32.mkdirSync(path35.dirname(logPath), { recursive: true });
30713
- const fd = fs32.openSync(logPath, "a");
30714
- const child = spawn7("pnpm", ["--filter", "web", "dev"], {
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
- fs32.closeSync(fd);
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
- fs32.writeFileSync(pidPath, String(child.pid), "utf8");
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
- fs32.mkdirSync(path35.dirname(logPath), { recursive: true });
30764
- const fd = fs32.openSync(logPath, "a");
30765
- const child = spawn7("pnpm", ["--filter", "web", "exec", "tsx", "voice-relay-entry.ts"], {
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
- fs32.closeSync(fd);
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
- fs32.writeFileSync(pidPath, String(child.pid), "utf8");
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, open = tryOpenUrl) {
31934
+ function autoOpenOnboardingUi(url, open3 = tryOpenUrl) {
30801
31935
  try {
30802
- const result2 = open(url);
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 = fs32.readFileSync(pidPath, "utf8").trim();
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
- fs32.unlinkSync(pidPath);
31964
+ fs35.unlinkSync(pidPath);
30831
31965
  } catch {
30832
31966
  }
30833
31967
  return true;
30834
31968
  }
30835
- function stopOnboardingUi(home = os17.homedir()) {
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((resolve3) => {
32258
+ const timeout = new Promise((resolve4) => {
31125
32259
  timer = setTimeout(() => {
31126
- resolve3("timeout");
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 = Command58.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 = Option55.String("--repo-root");
31179
- port = Option55.String("--port", "3000");
31180
- endpoint = Option55.String("--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 = Option55.Boolean("--prep-only", false);
31184
- skipInstall = Option55.Boolean("--skip-install", false);
31185
- stop = Option55.Boolean("--stop", false);
31186
- foreground = Option55.Boolean("--foreground", false);
31187
- voice = Option55.Boolean("--voice", false, {
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 (fs33.existsSync(path36.join(os18.homedir(), ".m8t", "config.yaml"))) {
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 Command59, Option as Option56 } from "clipanion";
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 = Command59.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 = Option56.String("--endpoint", { description: "Override the Foundry endpoint (else read from the install status)." });
31361
- brain = Option56.String("--brain", { description: "Seed only this one brain repo, instead of both <org>/stacey-brain and <org>/azzy-brain." });
31362
- watch = Option56.Boolean("--watch", false, { description: "Poll until the intake completes (or --timeout)." });
31363
- timeout = Option56.String("--timeout", { description: "Watch timeout in minutes (default 20)." });
31364
- githubAppCreds = Option56.String("--github-app-creds");
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 fs34 from "fs";
31425
- import * as os19 from "os";
31426
- import * as path37 from "path";
31427
- import { Command as Command60, Option as Option57 } from "clipanion";
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 = Command60.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 = Option57.String("--company", { description: "Share your company name with m8t support. Not sent unless you pass it." });
31518
- contactEmail = Option57.String("--contact-email", { description: "Share a contact email with m8t support. Not sent unless you pass it." });
31519
- subscription = Option57.String("--subscription", { description: "Azure subscription id used to find your deployment. Never sent to m8t." });
31520
- resourceGroup = Option57.String("--resource-group", { description: "Resource group to disambiguate discovery, if you have more than one m8t deployment." });
31521
- force = Option57.Boolean("--force", false, { description: "Enroll even when a key is already stored. Use only when m8t support asks you to." });
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 = fs34.mkdtempSync(path37.join(os19.tmpdir(), "m8t-ingest-key-"));
31555
- const keyFile = path37.join(keyDir, "key");
32688
+ const keyDir = fs37.mkdtempSync(path41.join(os21.tmpdir(), "m8t-ingest-key-"));
32689
+ const keyFile = path41.join(keyDir, "key");
31556
32690
  try {
31557
- fs34.writeFileSync(keyFile, ingestKey, { mode: 384 });
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
- fs34.rmSync(keyDir, { recursive: true, force: true });
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));