@m8t-stack/cli 0.2.36 → 0.2.38

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
@@ -1298,7 +1298,7 @@ var init_enable_hosted_brain = __esm({
1298
1298
  import { Builtins, Cli } from "clipanion";
1299
1299
 
1300
1300
  // src/lib/package-version.ts
1301
- var CLI_VERSION = "0.2.36";
1301
+ var CLI_VERSION = "0.2.38";
1302
1302
 
1303
1303
  // src/lib/render-error.ts
1304
1304
  init_errors();
@@ -16963,7 +16963,7 @@ async function listModelQuota(region) {
16963
16963
  function usageModelName(usageName) {
16964
16964
  return usageName.split(".").slice(2).join(".");
16965
16965
  }
16966
- var REASONING_USAGE_RE = /gpt-5|o[1-9]/i;
16966
+ var REASONING_USAGE_RE = /gpt-5|o[1-9]|grok-4/i;
16967
16967
  function isReasoningModel(modelDeployment) {
16968
16968
  return REASONING_USAGE_RE.test(modelDeployment);
16969
16969
  }
@@ -25476,7 +25476,7 @@ async function ensureDeployment(args) {
25476
25476
  "--model-version",
25477
25477
  args.modelVersion,
25478
25478
  "--model-format",
25479
- "OpenAI",
25479
+ args.format ?? "OpenAI",
25480
25480
  "--sku-name",
25481
25481
  "GlobalStandard",
25482
25482
  "--sku-capacity",
@@ -26216,8 +26216,8 @@ var BootstrapLaunchCommand = class extends M8tCommand {
26216
26216
  examples: [
26217
26217
  ["Launch in eastus2", "$0 bootstrap launch --location eastus2"],
26218
26218
  ["BYO app registration", "$0 bootstrap launch --location eastus2 --client-id <appId>"],
26219
- ["Pin a specific installer tag", "$0 bootstrap launch --location eastus2 --installer-tag v0.1.33"],
26220
- ["Override the full installer image ref", "$0 bootstrap launch --location eastus2 --installer-image ghcr.io/m8t-labs/m8t-installer:v0.1.33"],
26219
+ ["Pin a specific installer tag", "$0 bootstrap launch --location eastus2 --installer-tag v0.1.42"],
26220
+ ["Override the full installer image ref", "$0 bootstrap launch --location eastus2 --installer-image ghcr.io/m8t-labs/m8t-installer:v0.1.42"],
26221
26221
  ["Share a support contact", "$0 bootstrap launch --location eastus2 --contact-email you@example.com --company 'Acme'"]
26222
26222
  ]
26223
26223
  });
@@ -27466,6 +27466,7 @@ async function deploySimpleStacey(args) {
27466
27466
  repoRoot: args.repoRoot,
27467
27467
  persona: SIMPLE_STACEY_PERSONA,
27468
27468
  agentName: SIMPLE_STACEY_AGENT,
27469
+ model: args.model,
27469
27470
  fieldOverrides: args.fieldOverrides
27470
27471
  });
27471
27472
  } catch (e) {
@@ -27534,6 +27535,7 @@ async function deploySimpleStaceyWithRetry(args) {
27534
27535
  credential: args.credential,
27535
27536
  endpoint: args.endpoint,
27536
27537
  repoRoot: args.repoRoot,
27538
+ model: args.model,
27537
27539
  fieldOverrides: args.fieldOverrides
27538
27540
  });
27539
27541
  } catch (error) {
@@ -27629,12 +27631,13 @@ function writeWebEnvLocal(args) {
27629
27631
  });
27630
27632
  }
27631
27633
  const body = [
27632
- "# Written by `m8t bootstrap ui` \u2014 onboarding chat-only run (Simple Stacey).",
27634
+ "# Written by `m8t bootstrap ui` \u2014 local onboarding run (Simple Stacey).",
27635
+ "# Text-first: the intake voice stack is off unless the run passed --voice.",
27633
27636
  "# STORAGE unset \u2192 gateway boot skipped; the webapp forwards your MSAL token to Foundry.",
27634
27637
  `AZURE_TENANT_ID=${args.tenantId}`,
27635
27638
  `AZURE_CLIENT_ID=${args.clientId}`,
27636
27639
  `FOUNDRY_PROJECT_ENDPOINT=${args.foundryEndpoint}`,
27637
- "VOICE_RELAY_PUBLIC_URL=ws://localhost:8790",
27640
+ ...args.voice ? ["VOICE_RELAY_PUBLIC_URL=ws://localhost:8790", "NEXT_PUBLIC_M8T_INTAKE_VOICE=1"] : [],
27638
27641
  "STORAGE_ACCOUNT_NAME=",
27639
27642
  `M8T_INTERNAL_SECRET=${internalSecret}`,
27640
27643
  "M8T_SINGLE_GATEWAY_PROCESS=1",
@@ -27929,6 +27932,257 @@ function stopOnboardingUi(home = os17.homedir()) {
27929
27932
  return uiStopped || relayStopped;
27930
27933
  }
27931
27934
 
27935
+ // src/lib/model-catalog.ts
27936
+ async function listAgentModels(region) {
27937
+ let out;
27938
+ try {
27939
+ out = await runAz(["cognitiveservices", "model", "list", "-l", region, "--output", "json"]);
27940
+ } catch (e) {
27941
+ const msg = e instanceof Error ? e.message : String(e);
27942
+ const denied = /AuthorizationFailed|Forbidden|\b403\b|does not have authorization/i.test(msg);
27943
+ return { ok: false, error: denied ? "denied" : "unavailable" };
27944
+ }
27945
+ let raw;
27946
+ try {
27947
+ raw = JSON.parse(out);
27948
+ } catch {
27949
+ return { ok: false, error: "malformed" };
27950
+ }
27951
+ if (!Array.isArray(raw) || raw.some((e) => typeof e !== "object" || e === null)) {
27952
+ return { ok: false, error: "malformed" };
27953
+ }
27954
+ try {
27955
+ const rows = raw.map((e) => ({
27956
+ format: e.model?.format ?? "",
27957
+ name: e.model?.name ?? "",
27958
+ version: e.model?.version ?? "",
27959
+ skus: [...new Set((e.model?.skus ?? []).map((s) => s.name ?? "").filter(Boolean))],
27960
+ agentsV2: e.model?.capabilities?.agentsV2 === "true"
27961
+ }));
27962
+ return { ok: true, rows };
27963
+ } catch {
27964
+ return { ok: false, error: "malformed" };
27965
+ }
27966
+ }
27967
+
27968
+ // src/lib/model-cascade.ts
27969
+ var WHITELIST = [
27970
+ { model: "gpt-5.6-luna", format: "OpenAI", version: "2026-07-09", capacity: 100 },
27971
+ { model: "gpt-5.6-sol", format: "OpenAI", version: "2026-07-09", capacity: 100 },
27972
+ { model: "gpt-5.6-terra", format: "OpenAI", version: "2026-07-09", capacity: 100 },
27973
+ { model: "gpt-5.4", format: "OpenAI", version: "2026-03-05", capacity: 100 },
27974
+ { model: "grok-4.3", format: "xAI", version: "1", capacity: 100 },
27975
+ { model: "gpt-4.1-mini", format: "OpenAI", version: "2025-04-14", capacity: 50 }
27976
+ ];
27977
+ var GLOBAL_STANDARD = "GlobalStandard";
27978
+ function planCascade(whitelist, catalog, quota) {
27979
+ if (!catalog.ok) {
27980
+ return {
27981
+ candidates: [],
27982
+ skipped: whitelist.map((r) => ({ model: r.model, outcome: "catalog-unverified", detail: catalog.error }))
27983
+ };
27984
+ }
27985
+ const rowsByName = /* @__PURE__ */ new Map();
27986
+ for (const row of catalog.rows) {
27987
+ const list = rowsByName.get(row.name);
27988
+ if (list) list.push(row);
27989
+ else rowsByName.set(row.name, [row]);
27990
+ }
27991
+ const candidates = [];
27992
+ const skipped = [];
27993
+ for (const rung of whitelist) {
27994
+ const rows = rowsByName.get(rung.model);
27995
+ if (!rows || rows.length === 0) {
27996
+ skipped.push({ model: rung.model, outcome: "not-in-catalog" });
27997
+ continue;
27998
+ }
27999
+ const agentRows = rows.filter((r) => r.agentsV2);
28000
+ if (agentRows.length === 0) {
28001
+ skipped.push({ model: rung.model, outcome: "not-agent-eligible" });
28002
+ continue;
28003
+ }
28004
+ if (!agentRows.some((r) => r.skus.includes(GLOBAL_STANDARD))) {
28005
+ skipped.push({ model: rung.model, outcome: "no-global-standard" });
28006
+ continue;
28007
+ }
28008
+ if (modelQuotaVerdict(quota, rung.model).verdict === "no_quota") {
28009
+ skipped.push({ model: rung.model, outcome: "no-quota" });
28010
+ continue;
28011
+ }
28012
+ candidates.push(rung);
28013
+ }
28014
+ return { candidates, skipped };
28015
+ }
28016
+ var QUOTA_RE = /insufficient\s*quota|not enough quota|quota limit|quota\b[^.]{0,40}exceed|exceed\w*\b[^.]{0,40}quota|capacity[^.]{0,40}exceed|exceed\w*\b[^.]{0,40}capacity/i;
28017
+ var REGION_RE = /not available in|not supported in|NotAvailableInRegion|InvalidResourceLocation/i;
28018
+ var CONFLICT_RE = /\bconflict\b|\b409\b/i;
28019
+ function classifyDeployError(message) {
28020
+ if (QUOTA_RE.test(message)) return "deploy-rejected-quota";
28021
+ if (REGION_RE.test(message)) return "deploy-rejected-region";
28022
+ return "deploy-unverified";
28023
+ }
28024
+ async function walkCascade(whitelist, plan, deploy, opts) {
28025
+ const outcomes = /* @__PURE__ */ new Map();
28026
+ for (const s of plan.skipped) outcomes.set(s.model, s);
28027
+ let chosen = null;
28028
+ let aborted = false;
28029
+ for (const rung of plan.candidates) {
28030
+ if (chosen) break;
28031
+ if (opts.now() >= opts.deadlineAt) {
28032
+ aborted = true;
28033
+ break;
28034
+ }
28035
+ let row;
28036
+ try {
28037
+ await deploy(rung);
28038
+ row = { model: rung.model, outcome: "deployed" };
28039
+ chosen = rung;
28040
+ } catch (e) {
28041
+ const msg = e instanceof Error ? e.message : String(e);
28042
+ if (CONFLICT_RE.test(msg)) {
28043
+ try {
28044
+ await deploy(rung);
28045
+ row = { model: rung.model, outcome: "deployed" };
28046
+ chosen = rung;
28047
+ } catch (e2) {
28048
+ const msg2 = e2 instanceof Error ? e2.message : String(e2);
28049
+ row = {
28050
+ model: rung.model,
28051
+ outcome: CONFLICT_RE.test(msg2) ? "deploy-unverified" : classifyDeployError(msg2),
28052
+ detail: msg2
28053
+ };
28054
+ }
28055
+ } else {
28056
+ row = { model: rung.model, outcome: classifyDeployError(msg), detail: msg };
28057
+ }
28058
+ }
28059
+ outcomes.set(rung.model, row);
28060
+ opts.onRung?.(row.model, row.outcome);
28061
+ }
28062
+ const trace = whitelist.map(
28063
+ (r) => outcomes.get(r.model) ?? { model: r.model, outcome: aborted ? "aborted-deadline" : "not-reached" }
28064
+ );
28065
+ return { chosen, trace };
28066
+ }
28067
+ var CERTAIN_UNAVAILABLE = /* @__PURE__ */ new Set([
28068
+ "not-in-catalog",
28069
+ "not-agent-eligible",
28070
+ "no-global-standard",
28071
+ "deploy-rejected-region"
28072
+ ]);
28073
+ var QUOTA_BLOCKED = /* @__PURE__ */ new Set(["no-quota", "deploy-rejected-quota"]);
28074
+ function decideNote(whitelist, result) {
28075
+ const chosenIdx = result.chosen ? whitelist.findIndex((r) => r.model === result.chosen?.model) : whitelist.length;
28076
+ const better = result.trace.slice(0, Math.max(chosenIdx, 0));
28077
+ const runningModel = result.chosen?.model ?? null;
28078
+ if (chosenIdx === 0) {
28079
+ return { status: "top", runningModel, pitchModel: null, unavailableAbovePitch: [] };
28080
+ }
28081
+ const pitch = better.find((t) => QUOTA_BLOCKED.has(t.outcome));
28082
+ if (pitch) {
28083
+ const pitchIdx = better.findIndex((t) => t.model === pitch.model);
28084
+ return {
28085
+ status: "lesser-quota",
28086
+ runningModel,
28087
+ pitchModel: pitch.model,
28088
+ unavailableAbovePitch: better.slice(0, pitchIdx).filter((t) => CERTAIN_UNAVAILABLE.has(t.outcome)).map((t) => t.model)
28089
+ };
28090
+ }
28091
+ const allCertain = better.length > 0 && better.every((t) => CERTAIN_UNAVAILABLE.has(t.outcome));
28092
+ return {
28093
+ status: allCertain ? "lesser-unavailable" : "lesser-unverified",
28094
+ runningModel,
28095
+ pitchModel: null,
28096
+ unavailableAbovePitch: []
28097
+ };
28098
+ }
28099
+ var listNames = (names) => names.length <= 1 ? names[0] ?? "" : `${names.slice(0, -1).join(", ")} and ${names[names.length - 1]}`;
28100
+ function renderChosenModelNote(d) {
28101
+ const subject = d.runningModel ? `You are running on ${d.runningModel}.` : "You are running on this install's default model.";
28102
+ switch (d.status) {
28103
+ case "top":
28104
+ return `${subject} That is the best available model for this install.`;
28105
+ case "lesser-quota": {
28106
+ const aside = d.unavailableAbovePitch.length > 0 ? ` ${listNames(d.unavailableAbovePitch)} ${d.unavailableAbovePitch.length > 1 ? "are" : "is"} not offered for this install.` : "";
28107
+ return `${subject} A stronger model, ${d.pitchModel ?? ""}, is offered here, but this subscription has no quota for it.${aside} You can offer to help request that quota.`;
28108
+ }
28109
+ case "lesser-unavailable":
28110
+ return `${subject} No stronger model is offered for this install right now.`;
28111
+ case "lesser-unverified":
28112
+ return `${subject} It was not possible to check which stronger models this subscription can run, so do not make claims about what is or is not available.`;
28113
+ }
28114
+ }
28115
+
28116
+ // src/lib/intake-model.ts
28117
+ var DEFAULT_DEADLINE_MS = 18e4;
28118
+ var SCOPE_RE = /\/resourceGroups\/([^/]+)\/providers\/Microsoft\.CognitiveServices\/accounts\/([^/]+)/i;
28119
+ function parseAccountScope(scope) {
28120
+ const m = SCOPE_RE.exec(scope);
28121
+ return m ? { resourceGroup: m[1], account: m[2] } : null;
28122
+ }
28123
+ function narrate(model, outcome, region) {
28124
+ if (outcome === "deployed") return `${model} - ready`;
28125
+ return `${model} - not available for your subscription in ${region}`;
28126
+ }
28127
+ async function accountRegion(account, resourceGroup, fallback) {
28128
+ try {
28129
+ const out = await runAz([
28130
+ "cognitiveservices",
28131
+ "account",
28132
+ "show",
28133
+ "--name",
28134
+ account,
28135
+ "--resource-group",
28136
+ resourceGroup,
28137
+ "--query",
28138
+ "location",
28139
+ "-o",
28140
+ "tsv"
28141
+ ]);
28142
+ return out.trim() || fallback;
28143
+ } catch {
28144
+ return fallback;
28145
+ }
28146
+ }
28147
+ async function resolveIntakeModel(args) {
28148
+ const floorTrace = (outcome, detail) => WHITELIST.map((r) => ({ model: r.model, outcome, ...detail === void 0 ? {} : { detail } }));
28149
+ const degradeToFloor = (detail) => {
28150
+ const trace = floorTrace("catalog-unverified", detail);
28151
+ return { model: void 0, note: renderChosenModelNote(decideNote(WHITELIST, { chosen: null, trace })), trace };
28152
+ };
28153
+ const parsed = parseAccountScope(args.accountScope);
28154
+ if (!parsed) return degradeToFloor(`unparseable account scope: ${args.accountScope}`);
28155
+ try {
28156
+ const region = await accountRegion(parsed.account, parsed.resourceGroup, args.fallbackRegion);
28157
+ args.onNarrate?.("choosing the best model your subscription can run...");
28158
+ const [catalog, quota] = await Promise.all([listAgentModels(region), listModelQuota(region)]);
28159
+ const plan = planCascade(WHITELIST, catalog, quota);
28160
+ const deploy = async (rung) => {
28161
+ await ensureDeployment({
28162
+ account: parsed.account,
28163
+ resourceGroup: parsed.resourceGroup,
28164
+ model: rung.model,
28165
+ modelVersion: rung.version,
28166
+ capacity: rung.capacity,
28167
+ format: rung.format
28168
+ });
28169
+ };
28170
+ const start = Date.now();
28171
+ const result = await walkCascade(WHITELIST, plan, deploy, {
28172
+ now: () => Date.now(),
28173
+ deadlineAt: start + (args.deadlineMs ?? DEFAULT_DEADLINE_MS),
28174
+ onRung: (model, outcome) => args.onNarrate?.(narrate(model, outcome, region))
28175
+ });
28176
+ return {
28177
+ model: result.chosen?.model,
28178
+ note: renderChosenModelNote(decideNote(WHITELIST, result)),
28179
+ trace: result.trace
28180
+ };
28181
+ } catch (e) {
28182
+ return degradeToFloor(e instanceof Error ? e.message : String(e));
28183
+ }
28184
+ }
28185
+
27932
28186
  // src/commands/bootstrap/ui.ts
27933
28187
  function renderDeploySuccess(version, envPath) {
27934
28188
  return `${colors.success("\u2713")} Simple Stacey is live (stacey-intake v${version}).
@@ -27951,8 +28205,9 @@ var BootstrapUiCommand = class extends M8tCommand {
27951
28205
  details: [
27952
28206
  "Run after `m8t bootstrap launch`, in parallel with `status --watch`. Waits for the cloud",
27953
28207
  "installer's foundry-create phase, grants you Foundry data-plane access, deploys a no-brain",
27954
- "Simple Stacey (stacey-intake), writes apps/web/.env.local (chat-only), installs deps, and",
27955
- "starts the webapp detached so a coding agent can continue without opening a separate terminal.",
28208
+ "Simple Stacey (stacey-intake), writes apps/web/.env.local (chat-only unless --voice is set),",
28209
+ "installs deps, and starts the webapp detached so a coding agent can continue without opening",
28210
+ "a separate terminal.",
27956
28211
  "",
27957
28212
  "The command returns as soon as the server is ready (or after ~45 s if it is still compiling).",
27958
28213
  "Use `--stop` to shut it down, `--foreground` to keep the old blocking behaviour."
@@ -27961,7 +28216,8 @@ var BootstrapUiCommand = class extends M8tCommand {
27961
28216
  ["Start the onboarding UI (backgrounds the server, returns)", "$0 bootstrap ui --repo-root /path/to/m8t"],
27962
28217
  ["Stop the running onboarding UI", "$0 bootstrap ui --stop"],
27963
28218
  ["Prep only (don't serve at all)", "$0 bootstrap ui --repo-root /path/to/m8t --prep-only"],
27964
- ["Foreground (blocking, old behaviour)", "$0 bootstrap ui --repo-root /path/to/m8t --foreground"]
28219
+ ["Foreground (blocking, old behaviour)", "$0 bootstrap ui --repo-root /path/to/m8t --foreground"],
28220
+ ["Experimental: enable the intake voice call", "$0 bootstrap ui --repo-root /path/to/m8t --voice"]
27965
28221
  ]
27966
28222
  });
27967
28223
  repoRoot = Option51.String("--repo-root");
@@ -27973,6 +28229,9 @@ var BootstrapUiCommand = class extends M8tCommand {
27973
28229
  skipInstall = Option51.Boolean("--skip-install", false);
27974
28230
  stop = Option51.Boolean("--stop", false);
27975
28231
  foreground = Option51.Boolean("--foreground", false);
28232
+ voice = Option51.Boolean("--voice", false, {
28233
+ description: "Experimental/preview: enable the intake voice call and start the voice relay on :8790. Not a supported founder path \u2014 the intake is text-only by default."
28234
+ });
27976
28235
  async executeCommand() {
27977
28236
  if (this.stop === true) {
27978
28237
  const stopped = stopOnboardingUi();
@@ -28025,13 +28284,24 @@ var BootstrapUiCommand = class extends M8tCommand {
28025
28284
  await ensureFounderFoundryRole({ credential: credential2, subscriptionId: state.subscriptionId, principalId: oid, accountScope });
28026
28285
  const identity = await getSignedInUserIdentity();
28027
28286
  out("deploying Simple Stacey (stacey-intake) in the background...");
28028
- const deployOutcome = deploySimpleStaceyWithRetry({
28029
- credential: credential2,
28030
- endpoint,
28031
- repoRoot,
28032
- fieldOverrides: { founder_identity_note: composeFounderIdentityNote(identity) },
28033
- onWait: out
28034
- }).then(
28287
+ const deployOutcome = (async () => {
28288
+ const choice = await resolveIntakeModel({
28289
+ accountScope,
28290
+ fallbackRegion: state.location,
28291
+ onNarrate: out
28292
+ });
28293
+ return deploySimpleStaceyWithRetry({
28294
+ credential: credential2,
28295
+ endpoint,
28296
+ repoRoot,
28297
+ model: choice.model,
28298
+ fieldOverrides: {
28299
+ founder_identity_note: composeFounderIdentityNote(identity),
28300
+ chosen_model_note: choice.note
28301
+ },
28302
+ onWait: out
28303
+ });
28304
+ })().then(
28035
28305
  (version) => ({ ok: true, version }),
28036
28306
  (error) => ({ ok: false, error: error instanceof Error ? error : new Error(String(error)) })
28037
28307
  );
@@ -28039,7 +28309,8 @@ var BootstrapUiCommand = class extends M8tCommand {
28039
28309
  repoRoot,
28040
28310
  tenantId: account.tenantId,
28041
28311
  clientId: state.appRegClientId,
28042
- foundryEndpoint: endpoint
28312
+ foundryEndpoint: endpoint,
28313
+ voice: this.voice === true
28043
28314
  });
28044
28315
  if (!this.skipInstall) {
28045
28316
  out("installing apps/web dependencies (pnpm)\u2026");
@@ -28054,15 +28325,17 @@ var BootstrapUiCommand = class extends M8tCommand {
28054
28325
  return 0;
28055
28326
  }
28056
28327
  if (this.foreground === true) {
28057
- out("starting the voice relay on :8790 in the background\u2026");
28058
- await serveOnboardingRelayDetached({
28059
- repoRoot,
28060
- foundryEndpoint: endpoint,
28061
- tenantId: account.tenantId,
28062
- clientId: state.appRegClientId,
28063
- webInternalBaseUrl: "http://localhost:3000"
28064
- });
28065
- out("voice relay on :8790");
28328
+ if (this.voice === true) {
28329
+ out("starting the voice relay on :8790 in the background\u2026");
28330
+ await serveOnboardingRelayDetached({
28331
+ repoRoot,
28332
+ foundryEndpoint: endpoint,
28333
+ tenantId: account.tenantId,
28334
+ clientId: state.appRegClientId,
28335
+ webInternalBaseUrl: "http://localhost:3000"
28336
+ });
28337
+ out("voice relay on :8790");
28338
+ }
28066
28339
  const outcome2 = await deployOutcome;
28067
28340
  if (!outcome2.ok) {
28068
28341
  this.context.stderr.write(renderDeployFailure(outcome2.error));
@@ -28073,15 +28346,17 @@ var BootstrapUiCommand = class extends M8tCommand {
28073
28346
  await runInherit("pnpm", ["--filter", "web", "dev"], repoRoot, "BOOTSTRAP_UI_DEV_FAILED");
28074
28347
  return 0;
28075
28348
  }
28076
- out("starting the voice relay on :8790 in the background\u2026");
28077
- await serveOnboardingRelayDetached({
28078
- repoRoot,
28079
- foundryEndpoint: endpoint,
28080
- tenantId: account.tenantId,
28081
- clientId: state.appRegClientId,
28082
- webInternalBaseUrl: "http://localhost:3000"
28083
- });
28084
- out("voice relay on :8790");
28349
+ if (this.voice === true) {
28350
+ out("starting the voice relay on :8790 in the background\u2026");
28351
+ await serveOnboardingRelayDetached({
28352
+ repoRoot,
28353
+ foundryEndpoint: endpoint,
28354
+ tenantId: account.tenantId,
28355
+ clientId: state.appRegClientId,
28356
+ webInternalBaseUrl: "http://localhost:3000"
28357
+ });
28358
+ out("voice relay on :8790");
28359
+ }
28085
28360
  out("starting the webapp on :3000 in the background\u2026");
28086
28361
  const { alreadyRunning, logPath } = await serveOnboardingUiDetached({ repoRoot, port: this.port });
28087
28362
  if (alreadyRunning) {