@f-o-h/cli 0.1.3 → 0.1.5

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.
Files changed (3) hide show
  1. package/README.md +9 -1
  2. package/dist/foh.js +812 -87
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -4,7 +4,7 @@ AI-operator provisioning CLI for Front Of House.
4
4
 
5
5
  Public mirror: https://github.com/iiko38/front-of-house-cli
6
6
 
7
- Current published baseline: `@f-o-h/cli@0.1.3`
7
+ Current published baseline: `@f-o-h/cli@0.1.5`
8
8
 
9
9
  This mirror is a generated release artifact. The private product monorepo is not
10
10
  published here, and no open-source license is granted unless stated separately.
@@ -37,6 +37,7 @@ foh auth login
37
37
  foh org list
38
38
  foh org use --org <org-id>
39
39
  foh setup
40
+ foh prove --agent <agent-id> --json
40
41
  ```
41
42
 
42
43
  For AI agents and text-only terminals:
@@ -48,6 +49,7 @@ foh auth login --email "$FOH_EMAIL" --password "$FOH_PASSWORD" --json
48
49
  foh org list --json
49
50
  foh org use --org <org-id> --json
50
51
  foh setup --org <org-id> --agent-template <template-id> --agent-name "Demo Agent" --json
52
+ foh prove --agent <agent-id> --json --out foh-proof.json
51
53
  ```
52
54
 
53
55
  `auth signup --web` opens the console signup page when possible and always
@@ -55,5 +57,11 @@ prints the fallback URL. `auth login --web` starts browser device
55
57
  authorization, opens `/cli-auth`, waits for console approval, and stores the
56
58
  returned short-lived token. Credential auth remains available as fallback.
57
59
 
60
+ `foh prove` produces a compact signed proof report across auth, org context,
61
+ agent validation, contact phone readiness, voice provider health, widget
62
+ channel/embed readiness, widget smoke, and simulation certification. Use
63
+ `--strict` in automation when holds should fail the command, and
64
+ `--require-phone` when a voice/contact number is mandatory for the demo.
65
+
58
66
  The CLI defaults to the production API at `https://api.frontofhouse.okii.uk`.
59
67
 
package/dist/foh.js CHANGED
@@ -6046,7 +6046,7 @@ var require_compile = __commonJS({
6046
6046
  const schOrFunc = root.refs[ref];
6047
6047
  if (schOrFunc)
6048
6048
  return schOrFunc;
6049
- let _sch = resolve5.call(this, root, ref);
6049
+ let _sch = resolve7.call(this, root, ref);
6050
6050
  if (_sch === void 0) {
6051
6051
  const schema2 = (_a2 = root.localRefs) === null || _a2 === void 0 ? void 0 : _a2[ref];
6052
6052
  const { schemaId } = this.opts;
@@ -6073,7 +6073,7 @@ var require_compile = __commonJS({
6073
6073
  function sameSchemaEnv(s1, s2) {
6074
6074
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
6075
6075
  }
6076
- function resolve5(root, ref) {
6076
+ function resolve7(root, ref) {
6077
6077
  let sch;
6078
6078
  while (typeof (sch = this.refs[ref]) == "string")
6079
6079
  ref = sch;
@@ -6648,7 +6648,7 @@ var require_fast_uri = __commonJS({
6648
6648
  }
6649
6649
  return uri;
6650
6650
  }
6651
- function resolve5(baseURI, relativeURI, options) {
6651
+ function resolve7(baseURI, relativeURI, options) {
6652
6652
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
6653
6653
  const resolved = resolveComponent(parse3(baseURI, schemelessOptions), parse3(relativeURI, schemelessOptions), schemelessOptions, true);
6654
6654
  schemelessOptions.skipEscape = true;
@@ -6875,7 +6875,7 @@ var require_fast_uri = __commonJS({
6875
6875
  var fastUri = {
6876
6876
  SCHEMES,
6877
6877
  normalize,
6878
- resolve: resolve5,
6878
+ resolve: resolve7,
6879
6879
  resolveComponent,
6880
6880
  equal,
6881
6881
  serialize,
@@ -10063,21 +10063,21 @@ async function promptLine(label, {
10063
10063
  allowEmpty = false,
10064
10064
  defaultValue
10065
10065
  } = {}) {
10066
- return await new Promise((resolve5) => {
10066
+ return await new Promise((resolve7) => {
10067
10067
  const suffix = defaultValue ? ` [${defaultValue}]` : "";
10068
10068
  const rl = (0, import_readline.createInterface)({ input: process.stdin, output: process.stdout, terminal: true });
10069
10069
  rl.question(`${label}${suffix}: `, (answer) => {
10070
10070
  rl.close();
10071
10071
  const value = String(answer ?? "").trim();
10072
10072
  if (!value && typeof defaultValue === "string") {
10073
- resolve5(defaultValue);
10073
+ resolve7(defaultValue);
10074
10074
  return;
10075
10075
  }
10076
10076
  if (!value && !allowEmpty) {
10077
- resolve5("");
10077
+ resolve7("");
10078
10078
  return;
10079
10079
  }
10080
- resolve5(value);
10080
+ resolve7(value);
10081
10081
  });
10082
10082
  });
10083
10083
  }
@@ -10085,7 +10085,7 @@ async function promptSecret(label) {
10085
10085
  if (!process.stdin.isTTY || !process.stdout.isTTY || typeof process.stdin.setRawMode !== "function") {
10086
10086
  return await promptLine(label);
10087
10087
  }
10088
- return await new Promise((resolve5) => {
10088
+ return await new Promise((resolve7) => {
10089
10089
  const stdin = process.stdin;
10090
10090
  const stdout = process.stdout;
10091
10091
  const wasRaw = Boolean(stdin.isRaw);
@@ -10099,7 +10099,7 @@ async function promptSecret(label) {
10099
10099
  const finish = () => {
10100
10100
  cleanup();
10101
10101
  stdout.write("\n");
10102
- resolve5(value);
10102
+ resolve7(value);
10103
10103
  };
10104
10104
  const onData = (chunk) => {
10105
10105
  const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
@@ -10108,7 +10108,7 @@ async function promptSecret(label) {
10108
10108
  cleanup();
10109
10109
  process.exitCode = 130;
10110
10110
  stdout.write("\n");
10111
- return resolve5("");
10111
+ return resolve7("");
10112
10112
  }
10113
10113
  if (char === "\r" || char === "\n") {
10114
10114
  finish();
@@ -10192,10 +10192,20 @@ function buildCliSignupFallbackInstructions(signUpUrl) {
10192
10192
 
10193
10193
  // src/lib/open-url.ts
10194
10194
  var import_child_process = require("child_process");
10195
+ function buildOpenUrlCommand(url2, platform = process.platform) {
10196
+ if (platform === "win32") {
10197
+ return {
10198
+ command: "rundll32.exe",
10199
+ args: ["url.dll,FileProtocolHandler", url2]
10200
+ };
10201
+ }
10202
+ if (platform === "darwin") {
10203
+ return { command: "open", args: [url2] };
10204
+ }
10205
+ return { command: "xdg-open", args: [url2] };
10206
+ }
10195
10207
  function openUrl(url2) {
10196
- const platform = process.platform;
10197
- const command = platform === "win32" ? "cmd" : platform === "darwin" ? "open" : "xdg-open";
10198
- const args = platform === "win32" ? ["/c", "start", "", url2] : [url2];
10208
+ const { command, args } = buildOpenUrlCommand(url2);
10199
10209
  try {
10200
10210
  const child = (0, import_child_process.spawn)(command, args, {
10201
10211
  detached: true,
@@ -10367,7 +10377,7 @@ async function storeAuthenticatedSession(params) {
10367
10377
  return output;
10368
10378
  }
10369
10379
  function sleep(ms) {
10370
- return new Promise((resolve5) => setTimeout(resolve5, ms));
10380
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
10371
10381
  }
10372
10382
  async function runDeviceLogin(opts) {
10373
10383
  const jsonMode = Boolean(opts.json);
@@ -10905,7 +10915,7 @@ async function pollUntil(check2, opts) {
10905
10915
  }
10906
10916
  }
10907
10917
  function sleep2(ms) {
10908
- return new Promise((resolve5) => setTimeout(resolve5, ms));
10918
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
10909
10919
  }
10910
10920
 
10911
10921
  // src/commands/compliance.ts
@@ -13773,8 +13783,8 @@ function registerAgentGuardrailCommands(agent) {
13773
13783
  try {
13774
13784
  rule = JSON.parse(opts.rule);
13775
13785
  } catch {
13776
- const { readFileSync: readFileSync6 } = await import("fs");
13777
- rule = JSON.parse(readFileSync6(opts.rule, "utf-8"));
13786
+ const { readFileSync: readFileSync7 } = await import("fs");
13787
+ rule = JSON.parse(readFileSync7(opts.rule, "utf-8"));
13778
13788
  }
13779
13789
  const data = await apiFetch(`/v1/console/agents/${opts.agent}/guardrails`, {
13780
13790
  method: "POST",
@@ -14180,6 +14190,58 @@ function registerAgent(program3) {
14180
14190
  const data = await apiFetch(`/v1/console/agents/${opts.agent}`, { apiUrlOverride: opts.apiUrl });
14181
14191
  format(data, { json: opts.json ?? false });
14182
14192
  }));
14193
+ agent.command("replay").description("Create a replay/debug packet from a trace or conversation").option("--trace <id>", "Trace event ID to replay through the server trace replay endpoint").option("--conversation <id>", "Conversation ID to package with transcript and traces").requiredOption("--agent <id>", "Agent ID").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
14194
+ if (!opts.trace && !opts.conversation) {
14195
+ throw new FohError({
14196
+ step: "agent.replay",
14197
+ error: "Missing replay source",
14198
+ remediation: "Pass --trace <id> or --conversation <id>.",
14199
+ statusCode: 400
14200
+ });
14201
+ }
14202
+ if (opts.trace && opts.conversation) {
14203
+ throw new FohError({
14204
+ step: "agent.replay",
14205
+ error: "Ambiguous replay source",
14206
+ remediation: "Pass only one of --trace or --conversation.",
14207
+ statusCode: 400
14208
+ });
14209
+ }
14210
+ if (opts.trace) {
14211
+ const data2 = await apiFetch(`/v1/console/traces/${opts.trace}/replay`, {
14212
+ method: "POST",
14213
+ orgId: opts.org,
14214
+ apiUrlOverride: opts.apiUrl
14215
+ });
14216
+ format({
14217
+ schema_version: "foh_agent_replay_packet.v1",
14218
+ status: "server_replay_completed",
14219
+ source: { type: "trace", trace_id: opts.trace, agent_id: opts.agent },
14220
+ replay: data2,
14221
+ next_commands: [`foh tests from-trace --agent ${opts.agent} --trace ${opts.trace} --json`]
14222
+ }, { json: opts.json ?? false });
14223
+ return;
14224
+ }
14225
+ const data = await apiFetch(`/v1/console/agents/${opts.agent}/conversations/${opts.conversation}?include_traces=true`, {
14226
+ orgId: opts.org,
14227
+ apiUrlOverride: opts.apiUrl
14228
+ });
14229
+ const traces = Array.isArray(data.traces) ? data.traces : [];
14230
+ const firstTraceId = traces.map((trace) => String(trace.id || "").trim()).find(Boolean);
14231
+ format({
14232
+ schema_version: "foh_agent_replay_packet.v1",
14233
+ status: firstTraceId ? "conversation_replay_packet_created" : "conversation_not_replayable",
14234
+ source: { type: "conversation", conversation_id: opts.conversation, agent_id: opts.agent },
14235
+ conversation: data.conversation ?? null,
14236
+ trace_count: traces.length,
14237
+ traces,
14238
+ not_replayable_reason: firstTraceId ? null : "conversation_has_no_trace_events",
14239
+ next_commands: firstTraceId ? [
14240
+ `foh agent replay --agent ${opts.agent} --trace ${firstTraceId} --json`,
14241
+ `foh tests from-trace --agent ${opts.agent} --trace ${firstTraceId} --json`
14242
+ ] : [`foh transcripts get --agent ${opts.agent} --conversation ${opts.conversation} --include-traces --json`]
14243
+ }, { json: opts.json ?? false });
14244
+ }));
14183
14245
  const blueprint = agent.command("blueprint").description("Compile or apply Conversation Blueprint v1");
14184
14246
  blueprint.command("compile").description("Compile a Conversation Blueprint v1 file to the current policy graph draft without saving it").requiredOption("--agent <id>", "Agent ID").requiredOption("--blueprint <json|@file>", "Conversation Blueprint v1 JSON or @path").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
14185
14247
  const parsedBlueprint = await parseJsonOption(opts.blueprint, "--blueprint");
@@ -14306,9 +14368,9 @@ function registerAgent(program3) {
14306
14368
  process.stdout.write(yaml);
14307
14369
  return;
14308
14370
  }
14309
- const { writeFileSync: writeFileSync4 } = await import("fs");
14371
+ const { writeFileSync: writeFileSync6 } = await import("fs");
14310
14372
  const outputPath = opts.output ?? "tenant.yaml";
14311
- writeFileSync4(
14373
+ writeFileSync6(
14312
14374
  outputPath,
14313
14375
  `# tenant.yaml - Front Of House agent manifest
14314
14376
  # Edit this file and run: foh plan tenant.yaml
@@ -15743,11 +15805,11 @@ function registerVoice(program3) {
15743
15805
  }
15744
15806
  const outputPath = String(opts.out || `foh-voice-preview-${provider}-${voiceId}.mp3`).trim();
15745
15807
  const audio = Buffer.from(await res.arrayBuffer());
15746
- const { mkdirSync: mkdirSync4, writeFileSync: writeFileSync4 } = await import("fs");
15747
- const { dirname: dirname5, resolve: resolve5 } = await import("path");
15748
- const absolutePath = resolve5(outputPath);
15808
+ const { mkdirSync: mkdirSync4, writeFileSync: writeFileSync6 } = await import("fs");
15809
+ const { dirname: dirname5, resolve: resolve7 } = await import("path");
15810
+ const absolutePath = resolve7(outputPath);
15749
15811
  mkdirSync4(dirname5(absolutePath), { recursive: true });
15750
- writeFileSync4(absolutePath, audio);
15812
+ writeFileSync6(absolutePath, audio);
15751
15813
  format({
15752
15814
  status: "ok",
15753
15815
  provider,
@@ -30228,7 +30290,7 @@ var Protocol = class {
30228
30290
  return;
30229
30291
  }
30230
30292
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
30231
- await new Promise((resolve5) => setTimeout(resolve5, pollInterval));
30293
+ await new Promise((resolve7) => setTimeout(resolve7, pollInterval));
30232
30294
  options?.signal?.throwIfAborted();
30233
30295
  }
30234
30296
  } catch (error2) {
@@ -30245,7 +30307,7 @@ var Protocol = class {
30245
30307
  */
30246
30308
  request(request, resultSchema, options) {
30247
30309
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
30248
- return new Promise((resolve5, reject) => {
30310
+ return new Promise((resolve7, reject) => {
30249
30311
  const earlyReject = (error2) => {
30250
30312
  reject(error2);
30251
30313
  };
@@ -30323,7 +30385,7 @@ var Protocol = class {
30323
30385
  if (!parseResult.success) {
30324
30386
  reject(parseResult.error);
30325
30387
  } else {
30326
- resolve5(parseResult.data);
30388
+ resolve7(parseResult.data);
30327
30389
  }
30328
30390
  } catch (error2) {
30329
30391
  reject(error2);
@@ -30584,12 +30646,12 @@ var Protocol = class {
30584
30646
  }
30585
30647
  } catch {
30586
30648
  }
30587
- return new Promise((resolve5, reject) => {
30649
+ return new Promise((resolve7, reject) => {
30588
30650
  if (signal.aborted) {
30589
30651
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
30590
30652
  return;
30591
30653
  }
30592
- const timeoutId = setTimeout(resolve5, interval);
30654
+ const timeoutId = setTimeout(resolve7, interval);
30593
30655
  signal.addEventListener("abort", () => {
30594
30656
  clearTimeout(timeoutId);
30595
30657
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
@@ -31689,7 +31751,7 @@ var McpServer = class {
31689
31751
  let task = createTaskResult.task;
31690
31752
  const pollInterval = task.pollInterval ?? 5e3;
31691
31753
  while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
31692
- await new Promise((resolve5) => setTimeout(resolve5, pollInterval));
31754
+ await new Promise((resolve7) => setTimeout(resolve7, pollInterval));
31693
31755
  const updatedTask = await extra.taskStore.getTask(taskId);
31694
31756
  if (!updatedTask) {
31695
31757
  throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
@@ -32338,19 +32400,19 @@ var StdioServerTransport = class {
32338
32400
  this.onclose?.();
32339
32401
  }
32340
32402
  send(message) {
32341
- return new Promise((resolve5) => {
32403
+ return new Promise((resolve7) => {
32342
32404
  const json3 = serializeMessage(message);
32343
32405
  if (this._stdout.write(json3)) {
32344
- resolve5();
32406
+ resolve7();
32345
32407
  } else {
32346
- this._stdout.once("drain", resolve5);
32408
+ this._stdout.once("drain", resolve7);
32347
32409
  }
32348
32410
  });
32349
32411
  }
32350
32412
  };
32351
32413
 
32352
32414
  // src/lib/cli-version.ts
32353
- var CLI_VERSION = "0.1.3";
32415
+ var CLI_VERSION = "0.1.5";
32354
32416
 
32355
32417
  // src/commands/mcp-serve.ts
32356
32418
  var DEFAULT_TIMEOUT_MS = 12e4;
@@ -32535,7 +32597,7 @@ async function runFohCli(params) {
32535
32597
  effectiveArgv.push("--json");
32536
32598
  }
32537
32599
  const command = `foh ${effectiveArgv.join(" ")}`;
32538
- return await new Promise((resolve5) => {
32600
+ return await new Promise((resolve7) => {
32539
32601
  const child = (0, import_node_child_process.spawn)(process.execPath, [cliEntry, ...effectiveArgv], {
32540
32602
  stdio: ["ignore", "pipe", "pipe"],
32541
32603
  env: {
@@ -32560,7 +32622,7 @@ async function runFohCli(params) {
32560
32622
  });
32561
32623
  child.once("error", (error2) => {
32562
32624
  clearTimeout(timeoutHandle);
32563
- resolve5({
32625
+ resolve7({
32564
32626
  ok: false,
32565
32627
  command,
32566
32628
  argv: effectiveArgv,
@@ -32576,7 +32638,7 @@ async function runFohCli(params) {
32576
32638
  const stderrText = finalizeBoundedText(stderrBuffer);
32577
32639
  const exitCode = Number.isFinite(code ?? NaN) ? Number(code) : 1;
32578
32640
  const stdoutJson = tryParseJson(stdoutText);
32579
- resolve5({
32641
+ resolve7({
32580
32642
  ok: !timedOut && exitCode === 0,
32581
32643
  command,
32582
32644
  argv: effectiveArgv,
@@ -33351,14 +33413,107 @@ function registerMcp(program3) {
33351
33413
  // src/commands/knowledge.ts
33352
33414
  var import_fs2 = require("fs");
33353
33415
  var import_path2 = require("path");
33416
+
33417
+ // src/lib/query-options.ts
33418
+ function parsePositiveInt(value, fallback, min, max) {
33419
+ const parsed = Number(value ?? fallback);
33420
+ if (!Number.isFinite(parsed)) return fallback;
33421
+ return Math.max(min, Math.min(max, Math.trunc(parsed)));
33422
+ }
33423
+ function withQuery(path2, params) {
33424
+ const query = params.toString();
33425
+ return query ? `${path2}?${query}` : path2;
33426
+ }
33427
+
33428
+ // src/commands/knowledge.ts
33354
33429
  function readDraftKnowledgeText(draft) {
33355
33430
  const fromRaw = typeof draft.knowledge_base_raw === "string" ? draft.knowledge_base_raw : "";
33356
33431
  if (fromRaw.trim().length > 0) return fromRaw;
33357
33432
  const fromLegacy = typeof draft.knowledge_base === "string" ? draft.knowledge_base : "";
33358
33433
  return fromLegacy;
33359
33434
  }
33435
+ function tokenize(value) {
33436
+ return value.toLowerCase().split(/[^a-z0-9]+/g).map((token) => token.trim()).filter((token) => token.length >= 3);
33437
+ }
33438
+ function chunkKnowledgeText(text) {
33439
+ return text.split(/\n\s*\n|---+/g).map((chunk) => chunk.trim()).filter(Boolean).map((chunk, index) => ({ index, text: chunk }));
33440
+ }
33441
+ function scoreChunk(queryTokens, chunkText) {
33442
+ if (queryTokens.size === 0) return 0;
33443
+ const chunkTokens = new Set(tokenize(chunkText));
33444
+ let overlap = 0;
33445
+ for (const token of queryTokens) {
33446
+ if (chunkTokens.has(token)) overlap += 1;
33447
+ }
33448
+ return Number((overlap / queryTokens.size).toFixed(6));
33449
+ }
33360
33450
  function registerKnowledge(program3) {
33361
33451
  const knowledge = program3.command("knowledge").description("Manage agent knowledge base");
33452
+ knowledge.command("query").description("Debug agent knowledge retrieval for a question").requiredOption("--agent <id>", "Agent ID").requiredOption("--text <query>", "Question/query text").option("--limit <n>", "Max chunks to return (1-20)", "5").option("--min-score <n>", "Minimum overlap score for pass threshold", "0.2").option("--explain", "Include token/scoring metadata").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
33453
+ const query = String(opts.text || "").trim();
33454
+ if (query.length < 3) {
33455
+ throw new FohError({
33456
+ step: "knowledge.query",
33457
+ error: "Query text must be at least 3 characters",
33458
+ remediation: 'Pass --text "<question>" with 3+ characters.',
33459
+ statusCode: 400
33460
+ });
33461
+ }
33462
+ const draft = await apiFetch(`/v1/console/agents/${opts.agent}/draft`, {
33463
+ orgId: opts.org,
33464
+ apiUrlOverride: opts.apiUrl
33465
+ });
33466
+ const knowledgeText = readDraftKnowledgeText(draft);
33467
+ const chunks = chunkKnowledgeText(knowledgeText);
33468
+ const queryTokens = new Set(tokenize(query));
33469
+ const minScore = Math.max(0, Math.min(1, Number(opts.minScore ?? 0.2) || 0.2));
33470
+ const limit = parsePositiveInt(opts.limit, 5, 1, 20);
33471
+ const matches = chunks.map((chunk) => ({
33472
+ chunk_id: `agent-draft-${opts.agent}#${chunk.index + 1}`,
33473
+ source: "agent_draft_knowledge",
33474
+ citation: `agent:${opts.agent}:chunk:${chunk.index + 1}`,
33475
+ score: scoreChunk(queryTokens, chunk.text),
33476
+ text: chunk.text.slice(0, 1200)
33477
+ })).filter((chunk) => chunk.score > 0).sort((a, b) => b.score - a.score).slice(0, limit);
33478
+ const topScore = matches[0]?.score ?? 0;
33479
+ const status = matches.length === 0 ? "no_match" : topScore >= minScore ? "pass" : "low_confidence";
33480
+ const reasonCode = status === "pass" ? "knowledge_query_match" : status === "low_confidence" ? "knowledge_query_low_confidence" : "knowledge_query_no_match";
33481
+ const packet = status === "pass" ? null : {
33482
+ schema_version: "foh_knowledge_query_failure_packet.v1",
33483
+ agent_id: opts.agent,
33484
+ query,
33485
+ reason_code: reasonCode,
33486
+ top_score: topScore,
33487
+ chunk_count: chunks.length,
33488
+ next_commands: [
33489
+ `foh knowledge ingest-file --agent ${opts.agent} --file <path> --json`,
33490
+ `foh knowledge query --agent ${opts.agent} --text "${query.replace(/"/g, '\\"')}" --explain --json`
33491
+ ]
33492
+ };
33493
+ format({
33494
+ schema_version: "foh_knowledge_query_debug.v1",
33495
+ ok: status === "pass",
33496
+ status,
33497
+ reason_code: reasonCode,
33498
+ agent_id: opts.agent,
33499
+ query,
33500
+ retrieval: {
33501
+ source: "agent_draft_direct",
33502
+ chunk_count: chunks.length,
33503
+ match_count: matches.length,
33504
+ top_score: topScore,
33505
+ min_score: minScore
33506
+ },
33507
+ matches,
33508
+ failure_packet: packet,
33509
+ ...opts.explain ? {
33510
+ explain: {
33511
+ query_tokens: Array.from(queryTokens),
33512
+ scoring: "token_overlap_ratio_v1"
33513
+ }
33514
+ } : {}
33515
+ }, { json: opts.json ?? false });
33516
+ }));
33362
33517
  knowledge.command("ingest-file").description("Ingest a local file into the knowledge base").requiredOption("--file <path>", "Path to file to ingest").option("--agent <id>", "Scope to agent").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
33363
33518
  const content = (0, import_fs2.readFileSync)(opts.file, "utf-8");
33364
33519
  let data;
@@ -33987,7 +34142,7 @@ function registerSetup(program3) {
33987
34142
  const startedAtIso = nowIso();
33988
34143
  const startedAtMs = Date.now();
33989
34144
  if (shouldResumeSkip(name)) {
33990
- const skipped = timedStepResult(
34145
+ const skipped2 = timedStepResult(
33991
34146
  {
33992
34147
  step: name,
33993
34148
  status: "skipped",
@@ -33996,10 +34151,10 @@ function registerSetup(program3) {
33996
34151
  startedAtIso,
33997
34152
  startedAtMs
33998
34153
  );
33999
- completed.push(skipped);
34154
+ completed.push(skipped2);
34000
34155
  process.stderr.write(import_picocolors4.default.dim(` [RESUME] ${name}: skipped (resume-from ${resumeState.resumeFrom})
34001
34156
  `));
34002
- return skipped;
34157
+ return skipped2;
34003
34158
  }
34004
34159
  if (opts.dryRun) {
34005
34160
  const dryRunResult = timedStepResult(
@@ -34355,8 +34510,8 @@ function registerSetup(program3) {
34355
34510
  }
34356
34511
  try {
34357
34512
  const manifest = await agentExport(resolvedAgentId, { apiUrlOverride: opts.apiUrl });
34358
- const { writeFileSync: writeFileSync4 } = await import("fs");
34359
- writeFileSync4(
34513
+ const { writeFileSync: writeFileSync6 } = await import("fs");
34514
+ writeFileSync6(
34360
34515
  "tenant.yaml",
34361
34516
  `# tenant.yaml - Front Of House agent manifest
34362
34517
  # Edit this file and run: foh plan tenant.yaml
@@ -34496,8 +34651,8 @@ function registerSim(program3) {
34496
34651
  }
34497
34652
  const cert = response.certificate;
34498
34653
  if (opts.out) {
34499
- const { writeFileSync: writeFileSync4 } = await import("fs");
34500
- writeFileSync4(opts.out, JSON.stringify(cert, null, 2) + "\n", "utf-8");
34654
+ const { writeFileSync: writeFileSync6 } = await import("fs");
34655
+ writeFileSync6(opts.out, JSON.stringify(cert, null, 2) + "\n", "utf-8");
34501
34656
  process.stderr.write(` Certificate written to ${opts.out}
34502
34657
  `);
34503
34658
  }
@@ -34547,8 +34702,8 @@ function registerSim(program3) {
34547
34702
  });
34548
34703
  }
34549
34704
  if (opts.out) {
34550
- const { writeFileSync: writeFileSync4 } = await import("fs");
34551
- writeFileSync4(opts.out, JSON.stringify(response.certificate, null, 2) + "\n", "utf-8");
34705
+ const { writeFileSync: writeFileSync6 } = await import("fs");
34706
+ writeFileSync6(opts.out, JSON.stringify(response.certificate, null, 2) + "\n", "utf-8");
34552
34707
  process.stderr.write(` Final certificate written to ${opts.out}
34553
34708
  `);
34554
34709
  }
@@ -34585,19 +34740,6 @@ ${passIcon} Certification loop summary
34585
34740
 
34586
34741
  // src/commands/conversations.ts
34587
34742
  var import_crypto4 = require("crypto");
34588
-
34589
- // src/lib/query-options.ts
34590
- function parsePositiveInt(value, fallback, min, max) {
34591
- const parsed = Number(value ?? fallback);
34592
- if (!Number.isFinite(parsed)) return fallback;
34593
- return Math.max(min, Math.min(max, Math.trunc(parsed)));
34594
- }
34595
- function withQuery(path2, params) {
34596
- const query = params.toString();
34597
- return query ? `${path2}?${query}` : path2;
34598
- }
34599
-
34600
- // src/commands/conversations.ts
34601
34743
  function registerConversations(program3) {
34602
34744
  const conversations = program3.command("conversations").description("Search and operate on conversation traces and lead data");
34603
34745
  conversations.command("list").description("List/search conversations for an agent").requiredOption("--agent <id>", "Agent ID").option("--q <query>", "Full-text transcript query").option("--from <iso-date>", "Start datetime (ISO8601)").option("--to <iso-date>", "End datetime (ISO8601)").option("--terminal-state <value>", "Terminal state filter").option("--journey <value>", "Journey filter").option("--has-tool-call <value>", "Tool outcome/tool id filter").option("--scope <value>", "Scope: agent or org").option("--scope-agent-id <id>", "Optional scoped agent id when --scope org").option("--guardrail-triggered", "Only conversations with guardrail_triggered trace events").option("--provider <value>", "Provider filter from trace payload").option("--page <n>", "Page number", "1").option("--limit <n>", "Page size (1-100)", "20").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
@@ -34759,6 +34901,140 @@ function registerConversations(program3) {
34759
34901
  }));
34760
34902
  }
34761
34903
 
34904
+ // src/commands/transcripts.ts
34905
+ var import_fs5 = require("fs");
34906
+ var import_path4 = require("path");
34907
+ function listPath(agentId, opts) {
34908
+ const params = new URLSearchParams();
34909
+ if (opts.q) params.set("q", String(opts.q));
34910
+ if (opts.from) params.set("from", String(opts.from));
34911
+ if (opts.to) params.set("to", String(opts.to));
34912
+ params.set("page", String(parsePositiveInt(opts.page, 1, 1, 1e4)));
34913
+ params.set("limit", String(parsePositiveInt(opts.limit, 20, 1, 100)));
34914
+ return withQuery(`/v1/console/agents/${agentId}/conversations`, params);
34915
+ }
34916
+ function jsonl(rows) {
34917
+ return rows.map((row) => JSON.stringify(row)).join("\n") + (rows.length > 0 ? "\n" : "");
34918
+ }
34919
+ function registerTranscripts(program3) {
34920
+ const transcripts = program3.command("transcripts").description("List, fetch, and export conversation transcripts");
34921
+ transcripts.command("list").description("List transcript-bearing conversations for an agent").requiredOption("--agent <id>", "Agent ID").option("--q <query>", "Full-text transcript query").option("--from <iso-date>", "Start datetime (ISO8601)").option("--to <iso-date>", "End datetime (ISO8601)").option("--page <n>", "Page number", "1").option("--limit <n>", "Page size (1-100)", "20").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
34922
+ const data = await apiFetch(listPath(opts.agent, opts), { orgId: opts.org, apiUrlOverride: opts.apiUrl });
34923
+ format(data, { json: opts.json ?? false });
34924
+ }));
34925
+ transcripts.command("get").description("Fetch one conversation transcript and optional trace events").requiredOption("--agent <id>", "Agent ID").requiredOption("--conversation <id>", "Conversation ID").option("--include-traces", "Include ordered trace events").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
34926
+ const params = new URLSearchParams();
34927
+ if (opts.includeTraces) params.set("include_traces", "true");
34928
+ const path2 = withQuery(`/v1/console/agents/${opts.agent}/conversations/${opts.conversation}`, params);
34929
+ const data = await apiFetch(path2, { orgId: opts.org, apiUrlOverride: opts.apiUrl });
34930
+ format(data, { json: opts.json ?? false });
34931
+ }));
34932
+ transcripts.command("export").description("Export recent transcripts as JSON or JSONL").requiredOption("--agent <id>", "Agent ID").option("--q <query>", "Full-text transcript query").option("--from <iso-date>", "Start datetime (ISO8601)").option("--to <iso-date>", "End datetime (ISO8601)").option("--limit <n>", "Rows to export (1-100)", "100").option("--format <value>", "Export format: jsonl or json", "jsonl").option("--out <path>", "Output file path").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
34933
+ const exportFormat = String(opts.format || "jsonl").trim().toLowerCase();
34934
+ if (!["jsonl", "json"].includes(exportFormat)) {
34935
+ throw new FohError({
34936
+ step: "transcripts.export",
34937
+ error: `Invalid format: ${opts.format}`,
34938
+ remediation: "Use --format jsonl or --format json.",
34939
+ statusCode: 400
34940
+ });
34941
+ }
34942
+ const data = await apiFetch(listPath(opts.agent, { ...opts, page: "1" }), {
34943
+ orgId: opts.org,
34944
+ apiUrlOverride: opts.apiUrl
34945
+ });
34946
+ const rows = Array.isArray(data.conversations) ? data.conversations : [];
34947
+ const content = exportFormat === "json" ? stableStringify({ schema_version: "foh_transcript_export.v1", conversations: rows }) : jsonl(rows);
34948
+ if (opts.out) {
34949
+ const outputPath = (0, import_path4.resolve)(String(opts.out));
34950
+ (0, import_fs5.writeFileSync)(outputPath, content, "utf-8");
34951
+ format({ status: "exported", format: exportFormat, count: rows.length, output_path: outputPath }, { json: opts.json ?? false });
34952
+ return;
34953
+ }
34954
+ if (opts.json || exportFormat === "json") {
34955
+ format({ schema_version: "foh_transcript_export.v1", conversations: rows }, { json: opts.json ?? false });
34956
+ return;
34957
+ }
34958
+ process.stdout.write(content);
34959
+ }));
34960
+ }
34961
+
34962
+ // src/commands/analytics.ts
34963
+ function parsePreset(raw) {
34964
+ const value = String(raw || "7d").trim().toLowerCase();
34965
+ if (value === "today" || value === "7d" || value === "failed" || value === "lead-capture") return value;
34966
+ throw new FohError({
34967
+ step: "analytics.fetch",
34968
+ error: `Invalid preset: ${raw}`,
34969
+ remediation: "Use --preset today, 7d, failed, or lead-capture.",
34970
+ statusCode: 400
34971
+ });
34972
+ }
34973
+ function presetWindowDays(preset, rawWindowDays) {
34974
+ if (rawWindowDays !== void 0) return parsePositiveInt(String(rawWindowDays), preset === "today" ? 1 : 7, 1, 90);
34975
+ if (preset === "today") return 1;
34976
+ return 7;
34977
+ }
34978
+ function registerAnalytics(program3) {
34979
+ const analytics = program3.command("analytics").description("Fetch runtime analytics and inspection summaries");
34980
+ analytics.command("fetch").description("Fetch an agent analytics summary from existing reporting lanes").requiredOption("--agent <id>", "Agent ID").option("--preset <value>", "Preset: today, 7d, failed, lead-capture", "7d").option("--window-days <n>", "Lookback window override (1-90)").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
34981
+ const preset = parsePreset(opts.preset);
34982
+ const windowDays = presetWindowDays(preset, opts.windowDays);
34983
+ const qualityParams = new URLSearchParams({ windowDays: String(windowDays), environment: "production" });
34984
+ const conversationParams = new URLSearchParams({
34985
+ page: "1",
34986
+ limit: "10"
34987
+ });
34988
+ if (preset === "failed") conversationParams.set("terminal_state", "failed_visible");
34989
+ if (preset === "lead-capture") conversationParams.set("journey", "lead_capture");
34990
+ const [qualityScorecard, leadDataTrends, loopKpis, conversations, voiceSlo] = await Promise.all([
34991
+ apiFetch(`/v1/console/agents/${opts.agent}/quality-scorecard?${qualityParams.toString()}`, {
34992
+ orgId: opts.org,
34993
+ apiUrlOverride: opts.apiUrl
34994
+ }),
34995
+ apiFetch(`/v1/console/agents/${opts.agent}/lead-data-trends?windowDays=${windowDays}`, {
34996
+ orgId: opts.org,
34997
+ apiUrlOverride: opts.apiUrl
34998
+ }),
34999
+ apiFetch(`/v1/console/agents/${opts.agent}/loop-kpis?windowDays=${windowDays}`, {
35000
+ orgId: opts.org,
35001
+ apiUrlOverride: opts.apiUrl
35002
+ }),
35003
+ apiFetch(withQuery(`/v1/console/agents/${opts.agent}/conversations`, conversationParams), {
35004
+ orgId: opts.org,
35005
+ apiUrlOverride: opts.apiUrl
35006
+ }),
35007
+ apiFetch(`/v1/console/voice-slo?agentId=${opts.agent}&days=${windowDays}`, {
35008
+ orgId: opts.org,
35009
+ apiUrlOverride: opts.apiUrl
35010
+ }).catch((error2) => ({
35011
+ ok: false,
35012
+ skipped: true,
35013
+ reason_code: "voice_slo_unavailable",
35014
+ message: error2 instanceof Error ? error2.message : String(error2)
35015
+ }))
35016
+ ]);
35017
+ format({
35018
+ schema_version: "foh_analytics_fetch.v1",
35019
+ ok: true,
35020
+ agent_id: opts.agent,
35021
+ preset,
35022
+ window_days: windowDays,
35023
+ summaries: {
35024
+ quality_scorecard: qualityScorecard,
35025
+ lead_data_trends: leadDataTrends,
35026
+ loop_kpis: loopKpis,
35027
+ conversations,
35028
+ voice_slo: voiceSlo
35029
+ },
35030
+ next_commands: [
35031
+ `foh transcripts list --agent ${opts.agent} --limit 10 --json`,
35032
+ `foh ops reporting weekly-report --agent ${opts.agent} --window-days ${Math.min(30, windowDays)} --json`
35033
+ ]
35034
+ }, { json: opts.json ?? false });
35035
+ }));
35036
+ }
35037
+
34762
35038
  // src/commands/tests.ts
34763
35039
  function registerTests(program3) {
34764
35040
  const tests = program3.command("tests").description("Manage agent test catalog and test-run lifecycle");
@@ -34943,6 +35219,164 @@ function registerTests(program3) {
34943
35219
  }));
34944
35220
  }
34945
35221
 
35222
+ // src/commands/test.ts
35223
+ var import_fs6 = require("fs");
35224
+ var import_path5 = require("path");
35225
+ function asStringList(value) {
35226
+ if (typeof value === "string" && value.trim()) return [value.trim()];
35227
+ if (Array.isArray(value)) return value.map((entry) => String(entry || "").trim()).filter(Boolean);
35228
+ return [];
35229
+ }
35230
+ function parseSuiteFile(path2) {
35231
+ const raw = (0, import_fs6.readFileSync)(path2, "utf-8");
35232
+ const parsed = path2.toLowerCase().endsWith(".json") ? JSON.parse(raw) : load(raw);
35233
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
35234
+ throw new FohError({
35235
+ step: "test.run",
35236
+ error: "Suite file must contain an object",
35237
+ remediation: "Use a JSON/YAML object with scenarios[].turns[].",
35238
+ statusCode: 400
35239
+ });
35240
+ }
35241
+ return parsed;
35242
+ }
35243
+ function validateSuite(suite) {
35244
+ const scenarios = Array.isArray(suite.scenarios) ? suite.scenarios : [];
35245
+ if (scenarios.length === 0) {
35246
+ throw new FohError({
35247
+ step: "test.run",
35248
+ error: "Suite contains no scenarios",
35249
+ remediation: "Add at least one scenarios[] entry with turns[].",
35250
+ statusCode: 400
35251
+ });
35252
+ }
35253
+ for (const scenario of scenarios) {
35254
+ if (!Array.isArray(scenario.turns) || scenario.turns.length === 0) {
35255
+ throw new FohError({
35256
+ step: "test.run",
35257
+ error: `Scenario "${scenario.id || scenario.name || "(unnamed)"}" has no turns`,
35258
+ remediation: "Add turns with user/message and expect.contains assertions.",
35259
+ statusCode: 400
35260
+ });
35261
+ }
35262
+ }
35263
+ return scenarios;
35264
+ }
35265
+ function evaluateReply(reply, expect) {
35266
+ const lowerReply = reply.toLowerCase();
35267
+ const contains = asStringList(expect?.contains);
35268
+ const notContains = asStringList(expect?.not_contains);
35269
+ const failures = [];
35270
+ for (const expected of contains) {
35271
+ if (!lowerReply.includes(expected.toLowerCase())) {
35272
+ failures.push(`missing expected text: ${expected}`);
35273
+ }
35274
+ }
35275
+ for (const forbidden of notContains) {
35276
+ if (lowerReply.includes(forbidden.toLowerCase())) {
35277
+ failures.push(`contained forbidden text: ${forbidden}`);
35278
+ }
35279
+ }
35280
+ return failures;
35281
+ }
35282
+ function registerTest(program3) {
35283
+ const test = program3.command("test").description("Run local scenario suites against runtime channels");
35284
+ test.command("run").description("Run a local YAML/JSON scenario suite").requiredOption("--suite <path>", "Suite YAML/JSON path").option("--agent <id>", "Agent ID (defaults to suite.agent)").option("--out <path>", "Write report JSON to path").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
35285
+ const suitePath = (0, import_path5.resolve)(String(opts.suite));
35286
+ const suite = parseSuiteFile(suitePath);
35287
+ const agentId = String(opts.agent || suite.agent || "").trim();
35288
+ if (!agentId) {
35289
+ throw new FohError({
35290
+ step: "test.run",
35291
+ error: "Missing agent ID",
35292
+ remediation: "Pass --agent <id> or include agent in the suite file.",
35293
+ statusCode: 400
35294
+ });
35295
+ }
35296
+ const scenarios = validateSuite(suite);
35297
+ const ensure = await apiFetch("/v1/console/channels/widget/ensure", {
35298
+ method: "POST",
35299
+ body: JSON.stringify({ agentId }),
35300
+ orgId: opts.org,
35301
+ apiUrlOverride: opts.apiUrl
35302
+ });
35303
+ const publicKey = ensure.channel?.public_key;
35304
+ if (!publicKey) {
35305
+ throw new FohError({
35306
+ step: "test.run",
35307
+ error: "Widget channel public key missing",
35308
+ remediation: `Run: foh widget ensure --agent ${agentId} --json`,
35309
+ statusCode: 409
35310
+ });
35311
+ }
35312
+ let passed = 0;
35313
+ let failed = 0;
35314
+ const scenarioResults = [];
35315
+ for (let scenarioIndex = 0; scenarioIndex < scenarios.length; scenarioIndex += 1) {
35316
+ const scenario = scenarios[scenarioIndex];
35317
+ let conversationId;
35318
+ const turnResults = [];
35319
+ for (let turnIndex = 0; turnIndex < (scenario.turns || []).length; turnIndex += 1) {
35320
+ const turn = scenario.turns[turnIndex];
35321
+ const message = String(turn.user || turn.message || "").trim();
35322
+ if (!message) {
35323
+ failed += 1;
35324
+ turnResults.push({ turn: turnIndex + 1, ok: false, failures: ["missing user/message"] });
35325
+ continue;
35326
+ }
35327
+ const response = await apiFetch("/v1/widget/inbound", {
35328
+ method: "POST",
35329
+ body: JSON.stringify({
35330
+ channel_public_key: publicKey,
35331
+ message_body: message,
35332
+ preview: true,
35333
+ ...conversationId ? { conversation_id: conversationId } : {}
35334
+ }),
35335
+ apiUrlOverride: opts.apiUrl
35336
+ });
35337
+ conversationId = response.conversationId || conversationId;
35338
+ const reply = String(response.reply || "");
35339
+ const failures = reply ? evaluateReply(reply, turn.expect) : ["empty reply"];
35340
+ if (failures.length === 0) passed += 1;
35341
+ else failed += 1;
35342
+ turnResults.push({
35343
+ turn: turnIndex + 1,
35344
+ ok: failures.length === 0,
35345
+ message,
35346
+ reply,
35347
+ failures,
35348
+ conversation_id: response.conversationId ?? null,
35349
+ trace_id: response.trace_id ?? null,
35350
+ correlation_id: response.correlation_id ?? null
35351
+ });
35352
+ }
35353
+ scenarioResults.push({
35354
+ id: scenario.id || `scenario-${scenarioIndex + 1}`,
35355
+ name: scenario.name ?? null,
35356
+ ok: turnResults.every((turn) => turn.ok),
35357
+ turns: turnResults
35358
+ });
35359
+ }
35360
+ const report = {
35361
+ schema_version: "foh_local_scenario_suite_report.v1",
35362
+ suite_path: suitePath,
35363
+ agent_id: agentId,
35364
+ ok: failed === 0,
35365
+ passed,
35366
+ failed,
35367
+ scenarios: scenarioResults
35368
+ };
35369
+ if (opts.out) {
35370
+ const out = (0, import_path5.resolve)(String(opts.out));
35371
+ (0, import_fs6.writeFileSync)(out, stableStringify(report), "utf-8");
35372
+ format({ ...report, output_path: out }, { json: opts.json ?? false });
35373
+ } else {
35374
+ format(report, { json: opts.json ?? false });
35375
+ }
35376
+ if (failed > 0) markCommandFailed(1);
35377
+ }));
35378
+ }
35379
+
34946
35380
  // src/commands/ops.ts
34947
35381
  function parseClientErrorSource(raw) {
34948
35382
  if (!raw) return void 0;
@@ -35357,8 +35791,8 @@ function registerDiag(program3) {
35357
35791
  }
35358
35792
 
35359
35793
  // src/commands/bug.ts
35360
- var import_fs5 = require("fs");
35361
- var import_path4 = require("path");
35794
+ var import_fs7 = require("fs");
35795
+ var import_path6 = require("path");
35362
35796
  var ALLOWED_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
35363
35797
  var MAX_BODY_PREVIEW_LENGTH = 200;
35364
35798
  function parseMethod(raw) {
@@ -35474,14 +35908,14 @@ function parseRequestBody(raw) {
35474
35908
  }
35475
35909
  }
35476
35910
  function writeJsonArtifact(path2, value) {
35477
- const absolutePath = (0, import_path4.resolve)(path2);
35478
- (0, import_fs5.mkdirSync)((0, import_path4.dirname)(absolutePath), { recursive: true });
35479
- (0, import_fs5.writeFileSync)(absolutePath, stableStringify(value), "utf-8");
35911
+ const absolutePath = (0, import_path6.resolve)(path2);
35912
+ (0, import_fs7.mkdirSync)((0, import_path6.dirname)(absolutePath), { recursive: true });
35913
+ (0, import_fs7.writeFileSync)(absolutePath, stableStringify(value), "utf-8");
35480
35914
  return absolutePath;
35481
35915
  }
35482
35916
  function defaultArtifactPath() {
35483
35917
  const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
35484
- return (0, import_path4.resolve)(`test-results/bug-report.${timestamp2}.json`);
35918
+ return (0, import_path6.resolve)(`test-results/bug-report.${timestamp2}.json`);
35485
35919
  }
35486
35920
  async function resolveBugReportWizardInputs(opts) {
35487
35921
  if (!opts.wizard) return opts;
@@ -35673,6 +36107,293 @@ function registerBug(program3) {
35673
36107
  });
35674
36108
  }
35675
36109
 
36110
+ // src/commands/prove.ts
36111
+ function pass(name, summary, detail) {
36112
+ return { name, status: "pass", reason_code: `${name}_ok`, summary, detail };
36113
+ }
36114
+ function hold(name, reasonCode, summary, nextCommand, detail) {
36115
+ return { name, status: "hold", reason_code: reasonCode, summary, next_command: nextCommand, detail };
36116
+ }
36117
+ function fail(name, reasonCode, error2, nextCommand) {
36118
+ const message = error2 instanceof Error ? error2.message : String(error2);
36119
+ return { name, status: "fail", reason_code: reasonCode, summary: message, next_command: nextCommand };
36120
+ }
36121
+ function skipped(name, reasonCode, summary, nextCommand) {
36122
+ return { name, status: "skipped", reason_code: reasonCode, summary, next_command: nextCommand };
36123
+ }
36124
+ function hasBlockingChecks(checks) {
36125
+ return checks.some((check2) => check2.status === "hold" || check2.status === "fail");
36126
+ }
36127
+ function publicKeyFromEnsureResponse(response) {
36128
+ const record2 = response && typeof response === "object" ? response : {};
36129
+ const channel = record2.channel && typeof record2.channel === "object" ? record2.channel : {};
36130
+ const publicKey = channel.public_key ?? record2.widget_public_key ?? record2.public_key;
36131
+ return typeof publicKey === "string" && publicKey.trim() ? publicKey.trim() : void 0;
36132
+ }
36133
+ function agentIdFromList(response) {
36134
+ const agents = Array.isArray(response.agents) ? response.agents : [];
36135
+ const usable = agents.filter((agent) => typeof agent.id === "string" && agent.id.trim());
36136
+ if (usable.length === 1) return { agentId: usable[0].id, count: usable.length };
36137
+ if (usable.length === 0) return { count: 0, reason: "no_agents" };
36138
+ return { count: usable.length, reason: "multiple_agents" };
36139
+ }
36140
+ function firstUsableOrgId(response) {
36141
+ const record2 = response && typeof response === "object" ? response : {};
36142
+ const orgs = Array.isArray(record2.orgs) ? record2.orgs : [];
36143
+ const usable = orgs.map((org) => org && typeof org === "object" ? org : {}).map((org) => String(org.org_id ?? org.id ?? "").trim()).filter(Boolean);
36144
+ return { orgId: usable.length === 1 ? usable[0] : void 0, count: usable.length };
36145
+ }
36146
+ function registerProve(program3) {
36147
+ program3.command("prove").description("Produce one setup/runtime proof bundle for an agent").option("--agent <id>", "Agent ID to prove").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--cert-mode <m>", "Simulation cert mode: quick, full, stress", "quick").option("--cert-adaptive-runs <n>", "Adaptive runs for full/stress certification", "30").option("--cert-max-improvement-rounds <n>", "Max prompt improvement rounds in cert loop (0-5)", "1").option("--require-phone", "Hold proof if no phone/contact number is provisioned").option("--skip-cert", "Skip simulation certification check").option("--skip-smoke", "Skip widget runtime smoke check").option("--skip-voice-health", "Skip realtime voice provider health check").option("--out <path>", "Write signed proof report JSON to this path").option("--strict", "Exit non-zero unless all non-skipped checks pass").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
36148
+ const checks = [];
36149
+ const ctx = {
36150
+ tokenPresent: false,
36151
+ traceIds: [],
36152
+ correlationIds: []
36153
+ };
36154
+ try {
36155
+ const creds = loadCredentials(opts.apiUrl);
36156
+ ctx.apiUrl = creds.apiUrl;
36157
+ ctx.tokenPresent = Boolean(creds.token);
36158
+ ctx.orgId = opts.org || creds.orgId;
36159
+ checks.push(pass("auth", "CLI credentials are present and not expired.", {
36160
+ api_url: creds.apiUrl,
36161
+ org_id_from_credentials: creds.orgId ?? null
36162
+ }));
36163
+ } catch (error2) {
36164
+ checks.push(hold("auth", "auth_missing_or_expired", "CLI is not authenticated.", "foh auth login --web", {
36165
+ message: error2 instanceof Error ? error2.message : String(error2)
36166
+ }));
36167
+ }
36168
+ if (ctx.tokenPresent && !ctx.orgId) {
36169
+ try {
36170
+ const orgs = await apiFetch("/v1/console/auth/my-orgs", { apiUrlOverride: opts.apiUrl });
36171
+ const resolved = firstUsableOrgId(orgs);
36172
+ if (resolved.orgId) {
36173
+ ctx.orgId = resolved.orgId;
36174
+ checks.push(pass("org", "Resolved the only available org.", { org_id: resolved.orgId }));
36175
+ } else {
36176
+ checks.push(hold(
36177
+ "org",
36178
+ resolved.count === 0 ? "org_missing" : "org_ambiguous",
36179
+ resolved.count === 0 ? "No usable org was found for this account." : `Found ${resolved.count} orgs; choose one explicitly.`,
36180
+ "foh org list --json && foh org use --org <org-id>",
36181
+ { org_count: resolved.count }
36182
+ ));
36183
+ }
36184
+ } catch (error2) {
36185
+ checks.push(fail("org", "org_resolution_failed", error2, "foh org list --json"));
36186
+ }
36187
+ } else if (ctx.orgId) {
36188
+ checks.push(pass("org", "Org context is selected.", { org_id: ctx.orgId }));
36189
+ } else {
36190
+ checks.push(skipped("org", "auth_required", "Skipped until authentication is fixed.", "foh auth login --web"));
36191
+ }
36192
+ if (ctx.orgId) {
36193
+ if (opts.agent) {
36194
+ ctx.agentId = String(opts.agent);
36195
+ checks.push(pass("agent_selection", "Using explicitly supplied agent.", { agent_id: ctx.agentId }));
36196
+ } else {
36197
+ try {
36198
+ const list = await apiFetch("/v1/console/agents", {
36199
+ orgId: ctx.orgId,
36200
+ apiUrlOverride: opts.apiUrl
36201
+ });
36202
+ const resolved = agentIdFromList(list);
36203
+ if (resolved.agentId) {
36204
+ ctx.agentId = resolved.agentId;
36205
+ checks.push(pass("agent_selection", "Resolved the only available agent.", { agent_id: resolved.agentId }));
36206
+ } else {
36207
+ checks.push(hold(
36208
+ "agent_selection",
36209
+ resolved.reason === "no_agents" ? "agent_missing" : "agent_ambiguous",
36210
+ resolved.reason === "no_agents" ? "No agent exists in this org." : `Found ${resolved.count} agents; choose one explicitly.`,
36211
+ resolved.reason === "no_agents" ? "foh setup --json" : "foh agent list --json && foh prove --agent <agent-id> --json",
36212
+ { agent_count: resolved.count }
36213
+ ));
36214
+ }
36215
+ } catch (error2) {
36216
+ checks.push(fail("agent_selection", "agent_selection_failed", error2, "foh agent list --json"));
36217
+ }
36218
+ }
36219
+ } else {
36220
+ checks.push(skipped("agent_selection", "org_required", "Skipped until org context is fixed.", "foh org use --org <org-id>"));
36221
+ }
36222
+ if (ctx.agentId) {
36223
+ try {
36224
+ const validation = await apiFetch(`/v1/console/agents/${ctx.agentId}/validate`, {
36225
+ method: "POST",
36226
+ orgId: ctx.orgId,
36227
+ apiUrlOverride: opts.apiUrl
36228
+ });
36229
+ const issues = Array.isArray(validation.issues) ? validation.issues : [];
36230
+ if (validation.ok === false || issues.length > 0) {
36231
+ checks.push(hold("agent_validation", "agent_validation_issues", `Agent validation returned ${issues.length} issue(s).`, `foh agent validate --agent ${ctx.agentId} --json`, validation));
36232
+ } else {
36233
+ checks.push(pass("agent_validation", "Agent validation passed.", validation));
36234
+ }
36235
+ } catch (error2) {
36236
+ checks.push(fail("agent_validation", "agent_validation_failed", error2, `foh agent validate --agent ${ctx.agentId} --json`));
36237
+ }
36238
+ if (ctx.orgId) {
36239
+ try {
36240
+ const onboarding = await apiFetch(`/v1/console/org/${ctx.orgId}/onboarding`, {
36241
+ orgId: ctx.orgId,
36242
+ apiUrlOverride: opts.apiUrl
36243
+ });
36244
+ const phoneNumber = typeof onboarding.phone_number === "string" && onboarding.phone_number.trim() ? onboarding.phone_number.trim() : null;
36245
+ if (phoneNumber) {
36246
+ checks.push(pass("contact_channel", "Contact phone number is provisioned.", {
36247
+ phone_number_present: true,
36248
+ provisioning_status: onboarding.provisioning_status ?? null
36249
+ }));
36250
+ } else if (opts.requirePhone) {
36251
+ checks.push(hold("contact_channel", "contact_phone_missing", "No phone/contact number is provisioned for this org.", `foh provision buy --org ${ctx.orgId} --json`, {
36252
+ provisioning_status: onboarding.provisioning_status ?? null
36253
+ }));
36254
+ } else {
36255
+ checks.push(skipped("contact_channel", "contact_phone_not_required", "No phone/contact number is provisioned; pass --require-phone to make this a blocker.", `foh provision buy --org ${ctx.orgId} --json`));
36256
+ }
36257
+ } catch (error2) {
36258
+ checks.push(fail("contact_channel", "contact_channel_check_failed", error2, `foh provision status --org ${ctx.orgId} --json`));
36259
+ }
36260
+ }
36261
+ if (opts.skipVoiceHealth) {
36262
+ checks.push(skipped("voice_realtime_health", "operator_skipped", "Skipped by --skip-voice-health.", "foh voice realtime-health --json"));
36263
+ } else {
36264
+ try {
36265
+ const health = await apiFetch(
36266
+ "/v1/console/realtime/health",
36267
+ { apiUrlOverride: opts.apiUrl }
36268
+ );
36269
+ const providers = Array.isArray(health.providers) ? health.providers : [];
36270
+ if (providers.length === 0) {
36271
+ checks.push(skipped("voice_realtime_health", "voice_health_no_providers", "Realtime voice health returned no providers.", "foh voice realtime-health --json"));
36272
+ } else if (providers.every((provider) => provider?.ready === true)) {
36273
+ checks.push(pass("voice_realtime_health", "Realtime voice providers are ready.", {
36274
+ provider_count: providers.length
36275
+ }));
36276
+ } else {
36277
+ checks.push(hold("voice_realtime_health", "voice_realtime_provider_not_ready", "One or more realtime voice providers are not ready.", "foh voice realtime-health --json", {
36278
+ providers
36279
+ }));
36280
+ }
36281
+ } catch (error2) {
36282
+ checks.push(fail("voice_realtime_health", "voice_realtime_health_failed", error2, "foh voice realtime-health --json"));
36283
+ }
36284
+ }
36285
+ try {
36286
+ const ensure = await apiFetch("/v1/console/channels/widget/ensure", {
36287
+ method: "POST",
36288
+ body: JSON.stringify({ agentId: ctx.agentId }),
36289
+ orgId: ctx.orgId,
36290
+ apiUrlOverride: opts.apiUrl
36291
+ });
36292
+ const publicKey = publicKeyFromEnsureResponse(ensure);
36293
+ if (!publicKey) {
36294
+ checks.push(hold("widget_channel", "widget_public_key_missing", "Widget channel exists but no public key was returned.", `foh widget ensure --agent ${ctx.agentId} --json`, ensure));
36295
+ } else {
36296
+ ctx.widgetPublicKey = publicKey;
36297
+ checks.push(pass("widget_channel", "Widget channel is available.", { public_key_present: true }));
36298
+ }
36299
+ } catch (error2) {
36300
+ checks.push(fail("widget_channel", "widget_channel_failed", error2, `foh widget ensure --agent ${ctx.agentId} --json`));
36301
+ }
36302
+ try {
36303
+ const embed = await apiFetch("/v1/console/channels/widget/embed-snippet", {
36304
+ orgId: ctx.orgId,
36305
+ apiUrlOverride: opts.apiUrl,
36306
+ headers: { "x-agent-id": ctx.agentId }
36307
+ });
36308
+ if (typeof embed.snippet === "string" && embed.snippet.trim()) {
36309
+ checks.push(pass("widget_embed", "Widget embed snippet is available.", { snippet_present: true }));
36310
+ } else {
36311
+ checks.push(hold("widget_embed", "widget_embed_missing", "Widget embed snippet is missing.", `foh widget embed-snippet --agent ${ctx.agentId}`));
36312
+ }
36313
+ } catch (error2) {
36314
+ checks.push(fail("widget_embed", "widget_embed_failed", error2, `foh widget embed-snippet --agent ${ctx.agentId}`));
36315
+ }
36316
+ if (opts.skipSmoke) {
36317
+ checks.push(skipped("widget_smoke", "operator_skipped", "Skipped by --skip-smoke.", `foh widget smoke --agent ${ctx.agentId} --json`));
36318
+ } else if (!ctx.widgetPublicKey) {
36319
+ checks.push(skipped("widget_smoke", "widget_public_key_required", "Skipped because widget public key is unavailable.", `foh widget ensure --agent ${ctx.agentId} --json`));
36320
+ } else {
36321
+ try {
36322
+ const smoke = await runWidgetSmoke(ctx.widgetPublicKey, opts.apiUrl);
36323
+ ctx.conversationId = smoke.conversation_id;
36324
+ ctx.traceIds = smoke.trace_ids;
36325
+ ctx.correlationIds = smoke.correlation_ids;
36326
+ if (smoke.failed > 0) {
36327
+ checks.push(hold("widget_smoke", "widget_smoke_failed", `${smoke.failed} widget smoke turn(s) failed.`, `foh widget smoke --agent ${ctx.agentId} --json`, smoke));
36328
+ } else {
36329
+ checks.push(pass("widget_smoke", "Widget runtime smoke passed.", smoke));
36330
+ }
36331
+ } catch (error2) {
36332
+ checks.push(fail("widget_smoke", "widget_smoke_failed", error2, `foh widget smoke --agent ${ctx.agentId} --json`));
36333
+ }
36334
+ }
36335
+ if (opts.skipCert) {
36336
+ checks.push(skipped("simulation_certification", "operator_skipped", "Skipped by --skip-cert.", `foh sim certify-loop --agent ${ctx.agentId} --json`));
36337
+ } else {
36338
+ try {
36339
+ const certMode = normalizeAgentCertMode(opts.certMode);
36340
+ const loop = await runSetupCertifyLoop(ctx.agentId, {
36341
+ mode: certMode,
36342
+ adaptiveRuns: Math.max(1, Number(opts.certAdaptiveRuns ?? 30) || 30),
36343
+ maxImprovementRounds: Math.max(0, Math.min(5, Number(opts.certMaxImprovementRounds ?? 1) || 1)),
36344
+ orgId: ctx.orgId,
36345
+ apiUrlOverride: opts.apiUrl
36346
+ });
36347
+ if (!loop.overall_pass) {
36348
+ checks.push(hold("simulation_certification", "simulation_certification_failed", "Simulation certification did not pass.", `foh sim certify-loop --agent ${ctx.agentId} --${certMode === "quick" ? "full" : certMode} --json`, loop));
36349
+ } else {
36350
+ checks.push(pass("simulation_certification", "Simulation certification passed.", {
36351
+ mode: loop.mode,
36352
+ attempts: loop.attempts?.length ?? 0,
36353
+ improvement_runs: loop.improvement_runs,
36354
+ scenario_summary: loop.certificate?.scenario_summary
36355
+ }));
36356
+ }
36357
+ } catch (error2) {
36358
+ checks.push(fail("simulation_certification", "simulation_certification_failed", error2, `foh sim certify-loop --agent ${ctx.agentId} --json`));
36359
+ }
36360
+ }
36361
+ } else {
36362
+ checks.push(skipped("agent_validation", "agent_required", "Skipped until an agent is selected.", "foh agent list --json"));
36363
+ checks.push(skipped("contact_channel", "agent_required", "Skipped until an agent is selected.", "foh agent list --json"));
36364
+ checks.push(skipped("voice_realtime_health", "agent_required", "Skipped until an agent is selected.", "foh agent list --json"));
36365
+ checks.push(skipped("widget_channel", "agent_required", "Skipped until an agent is selected.", "foh agent list --json"));
36366
+ checks.push(skipped("widget_embed", "agent_required", "Skipped until an agent is selected.", "foh agent list --json"));
36367
+ checks.push(skipped("widget_smoke", "agent_required", "Skipped until an agent is selected.", "foh agent list --json"));
36368
+ checks.push(skipped("simulation_certification", "agent_required", "Skipped until an agent is selected.", "foh agent list --json"));
36369
+ }
36370
+ const status = hasBlockingChecks(checks) ? "hold" : "pass";
36371
+ const nextCommands = Array.from(new Set(checks.map((check2) => check2.next_command).filter((command) => Boolean(command))));
36372
+ if (status === "pass" && ctx.agentId) {
36373
+ nextCommands.push(`foh agent publish --agent ${ctx.agentId} --json`);
36374
+ }
36375
+ const report = signReport({
36376
+ schema_version: "foh_cli_proof_report.v1",
36377
+ generated_at: (/* @__PURE__ */ new Date()).toISOString(),
36378
+ ok: status === "pass",
36379
+ status,
36380
+ ids: {
36381
+ org_id: ctx.orgId ?? null,
36382
+ agent_id: ctx.agentId ?? null,
36383
+ widget_public_key_present: Boolean(ctx.widgetPublicKey),
36384
+ conversation_id: ctx.conversationId ?? null,
36385
+ trace_ids: ctx.traceIds,
36386
+ correlation_ids: ctx.correlationIds
36387
+ },
36388
+ checks,
36389
+ next_commands: nextCommands
36390
+ });
36391
+ const artifactPath = opts.out ? writeSignedJsonArtifact(String(opts.out), report) : void 0;
36392
+ format(artifactPath ? { ...report, artifact_path: artifactPath } : report, { json: opts.json ?? false });
36393
+ if (opts.strict && status !== "pass") markCommandFailed(1);
36394
+ }));
36395
+ }
36396
+
35676
36397
  // src/tui/command-palette.ts
35677
36398
  function tokenizeInput(value) {
35678
36399
  const tokens = [];
@@ -36118,7 +36839,7 @@ async function runSelf(args, apiUrlOverride) {
36118
36839
  if (apiUrlOverride && !spawnArgs.includes("--api-url")) {
36119
36840
  spawnArgs.push("--api-url", apiUrlOverride);
36120
36841
  }
36121
- return await new Promise((resolve5, reject) => {
36842
+ return await new Promise((resolve7, reject) => {
36122
36843
  const child = (0, import_child_process2.spawn)(process.execPath, [process.argv[1], ...spawnArgs], {
36123
36844
  stdio: "inherit",
36124
36845
  env: {
@@ -36128,7 +36849,7 @@ async function runSelf(args, apiUrlOverride) {
36128
36849
  }
36129
36850
  });
36130
36851
  child.once("error", reject);
36131
- child.once("close", (code) => resolve5(typeof code === "number" ? code : 1));
36852
+ child.once("close", (code) => resolve7(typeof code === "number" ? code : 1));
36132
36853
  });
36133
36854
  }
36134
36855
  function shouldUseInteractiveHome(argv) {
@@ -36418,8 +37139,8 @@ function maybeDefaultToHome(argv = process.argv) {
36418
37139
  }
36419
37140
 
36420
37141
  // src/lib/update.ts
36421
- var import_fs6 = require("fs");
36422
- var import_path5 = require("path");
37142
+ var import_fs8 = require("fs");
37143
+ var import_path7 = require("path");
36423
37144
  var import_child_process3 = require("child_process");
36424
37145
  var import_crypto5 = require("crypto");
36425
37146
  function parseSemver(version2) {
@@ -36440,7 +37161,7 @@ function compareSemver(a, b) {
36440
37161
  }
36441
37162
  function readPackageJsonVersion(path2) {
36442
37163
  try {
36443
- const raw = (0, import_fs6.readFileSync)(path2, "utf-8");
37164
+ const raw = (0, import_fs8.readFileSync)(path2, "utf-8");
36444
37165
  const parsed = JSON.parse(raw);
36445
37166
  const version2 = String(parsed.version ?? "").trim();
36446
37167
  return version2 || void 0;
@@ -36449,13 +37170,13 @@ function readPackageJsonVersion(path2) {
36449
37170
  }
36450
37171
  }
36451
37172
  function findRepoRoot(startCwd = process.cwd()) {
36452
- let current = (0, import_path5.resolve)(startCwd);
37173
+ let current = (0, import_path7.resolve)(startCwd);
36453
37174
  while (true) {
36454
- const rootPackageJsonPath = (0, import_path5.join)(current, "package.json");
36455
- const cliPackageJsonPath = (0, import_path5.join)(current, "packages", "cli", "package.json");
36456
- if ((0, import_fs6.existsSync)(rootPackageJsonPath) && (0, import_fs6.existsSync)(cliPackageJsonPath)) {
37175
+ const rootPackageJsonPath = (0, import_path7.join)(current, "package.json");
37176
+ const cliPackageJsonPath = (0, import_path7.join)(current, "packages", "cli", "package.json");
37177
+ if ((0, import_fs8.existsSync)(rootPackageJsonPath) && (0, import_fs8.existsSync)(cliPackageJsonPath)) {
36457
37178
  try {
36458
- const raw = (0, import_fs6.readFileSync)(rootPackageJsonPath, "utf-8");
37179
+ const raw = (0, import_fs8.readFileSync)(rootPackageJsonPath, "utf-8");
36459
37180
  const parsed = JSON.parse(raw);
36460
37181
  if (String(parsed.name ?? "").trim() === "front-of-house") {
36461
37182
  return current;
@@ -36463,7 +37184,7 @@ function findRepoRoot(startCwd = process.cwd()) {
36463
37184
  } catch {
36464
37185
  }
36465
37186
  }
36466
- const parent = (0, import_path5.dirname)(current);
37187
+ const parent = (0, import_path7.dirname)(current);
36467
37188
  if (parent === current) return void 0;
36468
37189
  current = parent;
36469
37190
  }
@@ -36477,7 +37198,7 @@ function detectUpdateAvailability(currentVersion, cwd = process.cwd()) {
36477
37198
  remediation: "Run this command from the Front Of House repo root to compare/install the latest CLI."
36478
37199
  };
36479
37200
  }
36480
- const cliPackageJsonPath = (0, import_path5.join)(repoRoot, "packages", "cli", "package.json");
37201
+ const cliPackageJsonPath = (0, import_path7.join)(repoRoot, "packages", "cli", "package.json");
36481
37202
  const latestVersion = readPackageJsonVersion(cliPackageJsonPath);
36482
37203
  if (!latestVersion) {
36483
37204
  return {
@@ -36504,19 +37225,19 @@ function detectUpdateAvailability(currentVersion, cwd = process.cwd()) {
36504
37225
  };
36505
37226
  }
36506
37227
  async function applyRepoUpdate(repoRoot) {
36507
- const scriptPath = (0, import_path5.join)(repoRoot, "scripts", "Install-FohCli.ps1");
37228
+ const scriptPath = (0, import_path7.join)(repoRoot, "scripts", "Install-FohCli.ps1");
36508
37229
  if (process.platform === "win32") {
36509
- return await new Promise((resolve5, reject) => {
37230
+ return await new Promise((resolve7, reject) => {
36510
37231
  const child = (0, import_child_process3.spawn)(
36511
37232
  "powershell",
36512
37233
  ["-ExecutionPolicy", "Bypass", "-File", scriptPath],
36513
37234
  { stdio: "inherit" }
36514
37235
  );
36515
37236
  child.once("error", reject);
36516
- child.once("close", (code) => resolve5(typeof code === "number" ? code : 1));
37237
+ child.once("close", (code) => resolve7(typeof code === "number" ? code : 1));
36517
37238
  });
36518
37239
  }
36519
- return await new Promise((resolve5, reject) => {
37240
+ return await new Promise((resolve7, reject) => {
36520
37241
  const child = (0, import_child_process3.spawn)(
36521
37242
  "corepack",
36522
37243
  ["pnpm", "cli:install:global"],
@@ -36526,7 +37247,7 @@ async function applyRepoUpdate(repoRoot) {
36526
37247
  }
36527
37248
  );
36528
37249
  child.once("error", reject);
36529
- child.once("close", (code) => resolve5(typeof code === "number" ? code : 1));
37250
+ child.once("close", (code) => resolve7(typeof code === "number" ? code : 1));
36530
37251
  });
36531
37252
  }
36532
37253
  function shouldShowUpdateNotice(argv = process.argv) {
@@ -36540,7 +37261,7 @@ function shouldShowUpdateNotice(argv = process.argv) {
36540
37261
  }
36541
37262
  function hashFileSha256(filePath) {
36542
37263
  try {
36543
- const bytes = (0, import_fs6.readFileSync)(filePath);
37264
+ const bytes = (0, import_fs8.readFileSync)(filePath);
36544
37265
  return (0, import_crypto5.createHash)("sha256").update(bytes).digest("hex");
36545
37266
  } catch {
36546
37267
  return void 0;
@@ -36550,10 +37271,10 @@ function verifyCliArtifactIntegrity(params = {}) {
36550
37271
  const cwd = params.cwd ?? process.cwd();
36551
37272
  const argv = params.argv ?? process.argv;
36552
37273
  const expectedSha256 = String(params.expectedSha256 ?? "").trim().toLowerCase() || void 0;
36553
- const runtimePath = (0, import_path5.resolve)(String(argv[1] || ""));
37274
+ const runtimePath = (0, import_path7.resolve)(String(argv[1] || ""));
36554
37275
  const runtimeHash = runtimePath ? hashFileSha256(runtimePath) : void 0;
36555
37276
  const warnings = [];
36556
- if (!runtimePath || !(0, import_fs6.existsSync)(runtimePath)) {
37277
+ if (!runtimePath || !(0, import_fs8.existsSync)(runtimePath)) {
36557
37278
  warnings.push("runtime_path_unreadable");
36558
37279
  }
36559
37280
  if (!runtimeHash) {
@@ -36571,8 +37292,8 @@ function verifyCliArtifactIntegrity(params = {}) {
36571
37292
  let repoDistHash;
36572
37293
  let runtimeMatchesRepoDist;
36573
37294
  if (repoRoot) {
36574
- repoDistPath = (0, import_path5.join)(repoRoot, "packages", "cli", "dist", "foh.js");
36575
- if ((0, import_fs6.existsSync)(repoDistPath)) {
37295
+ repoDistPath = (0, import_path7.join)(repoRoot, "packages", "cli", "dist", "foh.js");
37296
+ if ((0, import_fs8.existsSync)(repoDistPath)) {
36576
37297
  repoDistHash = hashFileSha256(repoDistPath);
36577
37298
  if (runtimeHash && repoDistHash) {
36578
37299
  runtimeMatchesRepoDist = runtimeHash === repoDistHash;
@@ -36795,6 +37516,9 @@ registerMcp(program2);
36795
37516
  registerKnowledge(program2);
36796
37517
  registerLeads(program2);
36797
37518
  registerConversations(program2);
37519
+ registerTranscripts(program2);
37520
+ registerAnalytics(program2);
37521
+ registerTest(program2);
36798
37522
  registerTests(program2);
36799
37523
  registerOps(program2);
36800
37524
  registerSetup(program2);
@@ -36802,6 +37526,7 @@ registerManifest(program2);
36802
37526
  registerSim(program2);
36803
37527
  registerDiag(program2);
36804
37528
  registerBug(program2);
37529
+ registerProve(program2);
36805
37530
  registerUpdate(program2);
36806
37531
  registerHome(program2);
36807
37532
  hideInternalApiUrlOptions(program2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@f-o-h/cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "FOH CLI - AI-operator provisioning tool for Front Of House",
5
5
  "license": "UNLICENSED",
6
6
  "bin": {