@f-o-h/cli 0.1.4 → 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 +799 -84
  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.4`
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();
@@ -10377,7 +10377,7 @@ async function storeAuthenticatedSession(params) {
10377
10377
  return output;
10378
10378
  }
10379
10379
  function sleep(ms) {
10380
- return new Promise((resolve5) => setTimeout(resolve5, ms));
10380
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
10381
10381
  }
10382
10382
  async function runDeviceLogin(opts) {
10383
10383
  const jsonMode = Boolean(opts.json);
@@ -10915,7 +10915,7 @@ async function pollUntil(check2, opts) {
10915
10915
  }
10916
10916
  }
10917
10917
  function sleep2(ms) {
10918
- return new Promise((resolve5) => setTimeout(resolve5, ms));
10918
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
10919
10919
  }
10920
10920
 
10921
10921
  // src/commands/compliance.ts
@@ -13783,8 +13783,8 @@ function registerAgentGuardrailCommands(agent) {
13783
13783
  try {
13784
13784
  rule = JSON.parse(opts.rule);
13785
13785
  } catch {
13786
- const { readFileSync: readFileSync6 } = await import("fs");
13787
- rule = JSON.parse(readFileSync6(opts.rule, "utf-8"));
13786
+ const { readFileSync: readFileSync7 } = await import("fs");
13787
+ rule = JSON.parse(readFileSync7(opts.rule, "utf-8"));
13788
13788
  }
13789
13789
  const data = await apiFetch(`/v1/console/agents/${opts.agent}/guardrails`, {
13790
13790
  method: "POST",
@@ -14190,6 +14190,58 @@ function registerAgent(program3) {
14190
14190
  const data = await apiFetch(`/v1/console/agents/${opts.agent}`, { apiUrlOverride: opts.apiUrl });
14191
14191
  format(data, { json: opts.json ?? false });
14192
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
+ }));
14193
14245
  const blueprint = agent.command("blueprint").description("Compile or apply Conversation Blueprint v1");
14194
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 () => {
14195
14247
  const parsedBlueprint = await parseJsonOption(opts.blueprint, "--blueprint");
@@ -14316,9 +14368,9 @@ function registerAgent(program3) {
14316
14368
  process.stdout.write(yaml);
14317
14369
  return;
14318
14370
  }
14319
- const { writeFileSync: writeFileSync4 } = await import("fs");
14371
+ const { writeFileSync: writeFileSync6 } = await import("fs");
14320
14372
  const outputPath = opts.output ?? "tenant.yaml";
14321
- writeFileSync4(
14373
+ writeFileSync6(
14322
14374
  outputPath,
14323
14375
  `# tenant.yaml - Front Of House agent manifest
14324
14376
  # Edit this file and run: foh plan tenant.yaml
@@ -15753,11 +15805,11 @@ function registerVoice(program3) {
15753
15805
  }
15754
15806
  const outputPath = String(opts.out || `foh-voice-preview-${provider}-${voiceId}.mp3`).trim();
15755
15807
  const audio = Buffer.from(await res.arrayBuffer());
15756
- const { mkdirSync: mkdirSync4, writeFileSync: writeFileSync4 } = await import("fs");
15757
- const { dirname: dirname5, resolve: resolve5 } = await import("path");
15758
- 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);
15759
15811
  mkdirSync4(dirname5(absolutePath), { recursive: true });
15760
- writeFileSync4(absolutePath, audio);
15812
+ writeFileSync6(absolutePath, audio);
15761
15813
  format({
15762
15814
  status: "ok",
15763
15815
  provider,
@@ -30238,7 +30290,7 @@ var Protocol = class {
30238
30290
  return;
30239
30291
  }
30240
30292
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
30241
- await new Promise((resolve5) => setTimeout(resolve5, pollInterval));
30293
+ await new Promise((resolve7) => setTimeout(resolve7, pollInterval));
30242
30294
  options?.signal?.throwIfAborted();
30243
30295
  }
30244
30296
  } catch (error2) {
@@ -30255,7 +30307,7 @@ var Protocol = class {
30255
30307
  */
30256
30308
  request(request, resultSchema, options) {
30257
30309
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
30258
- return new Promise((resolve5, reject) => {
30310
+ return new Promise((resolve7, reject) => {
30259
30311
  const earlyReject = (error2) => {
30260
30312
  reject(error2);
30261
30313
  };
@@ -30333,7 +30385,7 @@ var Protocol = class {
30333
30385
  if (!parseResult.success) {
30334
30386
  reject(parseResult.error);
30335
30387
  } else {
30336
- resolve5(parseResult.data);
30388
+ resolve7(parseResult.data);
30337
30389
  }
30338
30390
  } catch (error2) {
30339
30391
  reject(error2);
@@ -30594,12 +30646,12 @@ var Protocol = class {
30594
30646
  }
30595
30647
  } catch {
30596
30648
  }
30597
- return new Promise((resolve5, reject) => {
30649
+ return new Promise((resolve7, reject) => {
30598
30650
  if (signal.aborted) {
30599
30651
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
30600
30652
  return;
30601
30653
  }
30602
- const timeoutId = setTimeout(resolve5, interval);
30654
+ const timeoutId = setTimeout(resolve7, interval);
30603
30655
  signal.addEventListener("abort", () => {
30604
30656
  clearTimeout(timeoutId);
30605
30657
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
@@ -31699,7 +31751,7 @@ var McpServer = class {
31699
31751
  let task = createTaskResult.task;
31700
31752
  const pollInterval = task.pollInterval ?? 5e3;
31701
31753
  while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
31702
- await new Promise((resolve5) => setTimeout(resolve5, pollInterval));
31754
+ await new Promise((resolve7) => setTimeout(resolve7, pollInterval));
31703
31755
  const updatedTask = await extra.taskStore.getTask(taskId);
31704
31756
  if (!updatedTask) {
31705
31757
  throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
@@ -32348,19 +32400,19 @@ var StdioServerTransport = class {
32348
32400
  this.onclose?.();
32349
32401
  }
32350
32402
  send(message) {
32351
- return new Promise((resolve5) => {
32403
+ return new Promise((resolve7) => {
32352
32404
  const json3 = serializeMessage(message);
32353
32405
  if (this._stdout.write(json3)) {
32354
- resolve5();
32406
+ resolve7();
32355
32407
  } else {
32356
- this._stdout.once("drain", resolve5);
32408
+ this._stdout.once("drain", resolve7);
32357
32409
  }
32358
32410
  });
32359
32411
  }
32360
32412
  };
32361
32413
 
32362
32414
  // src/lib/cli-version.ts
32363
- var CLI_VERSION = "0.1.4";
32415
+ var CLI_VERSION = "0.1.5";
32364
32416
 
32365
32417
  // src/commands/mcp-serve.ts
32366
32418
  var DEFAULT_TIMEOUT_MS = 12e4;
@@ -32545,7 +32597,7 @@ async function runFohCli(params) {
32545
32597
  effectiveArgv.push("--json");
32546
32598
  }
32547
32599
  const command = `foh ${effectiveArgv.join(" ")}`;
32548
- return await new Promise((resolve5) => {
32600
+ return await new Promise((resolve7) => {
32549
32601
  const child = (0, import_node_child_process.spawn)(process.execPath, [cliEntry, ...effectiveArgv], {
32550
32602
  stdio: ["ignore", "pipe", "pipe"],
32551
32603
  env: {
@@ -32570,7 +32622,7 @@ async function runFohCli(params) {
32570
32622
  });
32571
32623
  child.once("error", (error2) => {
32572
32624
  clearTimeout(timeoutHandle);
32573
- resolve5({
32625
+ resolve7({
32574
32626
  ok: false,
32575
32627
  command,
32576
32628
  argv: effectiveArgv,
@@ -32586,7 +32638,7 @@ async function runFohCli(params) {
32586
32638
  const stderrText = finalizeBoundedText(stderrBuffer);
32587
32639
  const exitCode = Number.isFinite(code ?? NaN) ? Number(code) : 1;
32588
32640
  const stdoutJson = tryParseJson(stdoutText);
32589
- resolve5({
32641
+ resolve7({
32590
32642
  ok: !timedOut && exitCode === 0,
32591
32643
  command,
32592
32644
  argv: effectiveArgv,
@@ -33361,14 +33413,107 @@ function registerMcp(program3) {
33361
33413
  // src/commands/knowledge.ts
33362
33414
  var import_fs2 = require("fs");
33363
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
33364
33429
  function readDraftKnowledgeText(draft) {
33365
33430
  const fromRaw = typeof draft.knowledge_base_raw === "string" ? draft.knowledge_base_raw : "";
33366
33431
  if (fromRaw.trim().length > 0) return fromRaw;
33367
33432
  const fromLegacy = typeof draft.knowledge_base === "string" ? draft.knowledge_base : "";
33368
33433
  return fromLegacy;
33369
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
+ }
33370
33450
  function registerKnowledge(program3) {
33371
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
+ }));
33372
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 () => {
33373
33518
  const content = (0, import_fs2.readFileSync)(opts.file, "utf-8");
33374
33519
  let data;
@@ -33997,7 +34142,7 @@ function registerSetup(program3) {
33997
34142
  const startedAtIso = nowIso();
33998
34143
  const startedAtMs = Date.now();
33999
34144
  if (shouldResumeSkip(name)) {
34000
- const skipped = timedStepResult(
34145
+ const skipped2 = timedStepResult(
34001
34146
  {
34002
34147
  step: name,
34003
34148
  status: "skipped",
@@ -34006,10 +34151,10 @@ function registerSetup(program3) {
34006
34151
  startedAtIso,
34007
34152
  startedAtMs
34008
34153
  );
34009
- completed.push(skipped);
34154
+ completed.push(skipped2);
34010
34155
  process.stderr.write(import_picocolors4.default.dim(` [RESUME] ${name}: skipped (resume-from ${resumeState.resumeFrom})
34011
34156
  `));
34012
- return skipped;
34157
+ return skipped2;
34013
34158
  }
34014
34159
  if (opts.dryRun) {
34015
34160
  const dryRunResult = timedStepResult(
@@ -34365,8 +34510,8 @@ function registerSetup(program3) {
34365
34510
  }
34366
34511
  try {
34367
34512
  const manifest = await agentExport(resolvedAgentId, { apiUrlOverride: opts.apiUrl });
34368
- const { writeFileSync: writeFileSync4 } = await import("fs");
34369
- writeFileSync4(
34513
+ const { writeFileSync: writeFileSync6 } = await import("fs");
34514
+ writeFileSync6(
34370
34515
  "tenant.yaml",
34371
34516
  `# tenant.yaml - Front Of House agent manifest
34372
34517
  # Edit this file and run: foh plan tenant.yaml
@@ -34506,8 +34651,8 @@ function registerSim(program3) {
34506
34651
  }
34507
34652
  const cert = response.certificate;
34508
34653
  if (opts.out) {
34509
- const { writeFileSync: writeFileSync4 } = await import("fs");
34510
- 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");
34511
34656
  process.stderr.write(` Certificate written to ${opts.out}
34512
34657
  `);
34513
34658
  }
@@ -34557,8 +34702,8 @@ function registerSim(program3) {
34557
34702
  });
34558
34703
  }
34559
34704
  if (opts.out) {
34560
- const { writeFileSync: writeFileSync4 } = await import("fs");
34561
- 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");
34562
34707
  process.stderr.write(` Final certificate written to ${opts.out}
34563
34708
  `);
34564
34709
  }
@@ -34595,19 +34740,6 @@ ${passIcon} Certification loop summary
34595
34740
 
34596
34741
  // src/commands/conversations.ts
34597
34742
  var import_crypto4 = require("crypto");
34598
-
34599
- // src/lib/query-options.ts
34600
- function parsePositiveInt(value, fallback, min, max) {
34601
- const parsed = Number(value ?? fallback);
34602
- if (!Number.isFinite(parsed)) return fallback;
34603
- return Math.max(min, Math.min(max, Math.trunc(parsed)));
34604
- }
34605
- function withQuery(path2, params) {
34606
- const query = params.toString();
34607
- return query ? `${path2}?${query}` : path2;
34608
- }
34609
-
34610
- // src/commands/conversations.ts
34611
34743
  function registerConversations(program3) {
34612
34744
  const conversations = program3.command("conversations").description("Search and operate on conversation traces and lead data");
34613
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 () => {
@@ -34769,6 +34901,140 @@ function registerConversations(program3) {
34769
34901
  }));
34770
34902
  }
34771
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
+
34772
35038
  // src/commands/tests.ts
34773
35039
  function registerTests(program3) {
34774
35040
  const tests = program3.command("tests").description("Manage agent test catalog and test-run lifecycle");
@@ -34953,6 +35219,164 @@ function registerTests(program3) {
34953
35219
  }));
34954
35220
  }
34955
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
+
34956
35380
  // src/commands/ops.ts
34957
35381
  function parseClientErrorSource(raw) {
34958
35382
  if (!raw) return void 0;
@@ -35367,8 +35791,8 @@ function registerDiag(program3) {
35367
35791
  }
35368
35792
 
35369
35793
  // src/commands/bug.ts
35370
- var import_fs5 = require("fs");
35371
- var import_path4 = require("path");
35794
+ var import_fs7 = require("fs");
35795
+ var import_path6 = require("path");
35372
35796
  var ALLOWED_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
35373
35797
  var MAX_BODY_PREVIEW_LENGTH = 200;
35374
35798
  function parseMethod(raw) {
@@ -35484,14 +35908,14 @@ function parseRequestBody(raw) {
35484
35908
  }
35485
35909
  }
35486
35910
  function writeJsonArtifact(path2, value) {
35487
- const absolutePath = (0, import_path4.resolve)(path2);
35488
- (0, import_fs5.mkdirSync)((0, import_path4.dirname)(absolutePath), { recursive: true });
35489
- (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");
35490
35914
  return absolutePath;
35491
35915
  }
35492
35916
  function defaultArtifactPath() {
35493
35917
  const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
35494
- return (0, import_path4.resolve)(`test-results/bug-report.${timestamp2}.json`);
35918
+ return (0, import_path6.resolve)(`test-results/bug-report.${timestamp2}.json`);
35495
35919
  }
35496
35920
  async function resolveBugReportWizardInputs(opts) {
35497
35921
  if (!opts.wizard) return opts;
@@ -35683,6 +36107,293 @@ function registerBug(program3) {
35683
36107
  });
35684
36108
  }
35685
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
+
35686
36397
  // src/tui/command-palette.ts
35687
36398
  function tokenizeInput(value) {
35688
36399
  const tokens = [];
@@ -36128,7 +36839,7 @@ async function runSelf(args, apiUrlOverride) {
36128
36839
  if (apiUrlOverride && !spawnArgs.includes("--api-url")) {
36129
36840
  spawnArgs.push("--api-url", apiUrlOverride);
36130
36841
  }
36131
- return await new Promise((resolve5, reject) => {
36842
+ return await new Promise((resolve7, reject) => {
36132
36843
  const child = (0, import_child_process2.spawn)(process.execPath, [process.argv[1], ...spawnArgs], {
36133
36844
  stdio: "inherit",
36134
36845
  env: {
@@ -36138,7 +36849,7 @@ async function runSelf(args, apiUrlOverride) {
36138
36849
  }
36139
36850
  });
36140
36851
  child.once("error", reject);
36141
- child.once("close", (code) => resolve5(typeof code === "number" ? code : 1));
36852
+ child.once("close", (code) => resolve7(typeof code === "number" ? code : 1));
36142
36853
  });
36143
36854
  }
36144
36855
  function shouldUseInteractiveHome(argv) {
@@ -36428,8 +37139,8 @@ function maybeDefaultToHome(argv = process.argv) {
36428
37139
  }
36429
37140
 
36430
37141
  // src/lib/update.ts
36431
- var import_fs6 = require("fs");
36432
- var import_path5 = require("path");
37142
+ var import_fs8 = require("fs");
37143
+ var import_path7 = require("path");
36433
37144
  var import_child_process3 = require("child_process");
36434
37145
  var import_crypto5 = require("crypto");
36435
37146
  function parseSemver(version2) {
@@ -36450,7 +37161,7 @@ function compareSemver(a, b) {
36450
37161
  }
36451
37162
  function readPackageJsonVersion(path2) {
36452
37163
  try {
36453
- const raw = (0, import_fs6.readFileSync)(path2, "utf-8");
37164
+ const raw = (0, import_fs8.readFileSync)(path2, "utf-8");
36454
37165
  const parsed = JSON.parse(raw);
36455
37166
  const version2 = String(parsed.version ?? "").trim();
36456
37167
  return version2 || void 0;
@@ -36459,13 +37170,13 @@ function readPackageJsonVersion(path2) {
36459
37170
  }
36460
37171
  }
36461
37172
  function findRepoRoot(startCwd = process.cwd()) {
36462
- let current = (0, import_path5.resolve)(startCwd);
37173
+ let current = (0, import_path7.resolve)(startCwd);
36463
37174
  while (true) {
36464
- const rootPackageJsonPath = (0, import_path5.join)(current, "package.json");
36465
- const cliPackageJsonPath = (0, import_path5.join)(current, "packages", "cli", "package.json");
36466
- 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)) {
36467
37178
  try {
36468
- const raw = (0, import_fs6.readFileSync)(rootPackageJsonPath, "utf-8");
37179
+ const raw = (0, import_fs8.readFileSync)(rootPackageJsonPath, "utf-8");
36469
37180
  const parsed = JSON.parse(raw);
36470
37181
  if (String(parsed.name ?? "").trim() === "front-of-house") {
36471
37182
  return current;
@@ -36473,7 +37184,7 @@ function findRepoRoot(startCwd = process.cwd()) {
36473
37184
  } catch {
36474
37185
  }
36475
37186
  }
36476
- const parent = (0, import_path5.dirname)(current);
37187
+ const parent = (0, import_path7.dirname)(current);
36477
37188
  if (parent === current) return void 0;
36478
37189
  current = parent;
36479
37190
  }
@@ -36487,7 +37198,7 @@ function detectUpdateAvailability(currentVersion, cwd = process.cwd()) {
36487
37198
  remediation: "Run this command from the Front Of House repo root to compare/install the latest CLI."
36488
37199
  };
36489
37200
  }
36490
- const cliPackageJsonPath = (0, import_path5.join)(repoRoot, "packages", "cli", "package.json");
37201
+ const cliPackageJsonPath = (0, import_path7.join)(repoRoot, "packages", "cli", "package.json");
36491
37202
  const latestVersion = readPackageJsonVersion(cliPackageJsonPath);
36492
37203
  if (!latestVersion) {
36493
37204
  return {
@@ -36514,19 +37225,19 @@ function detectUpdateAvailability(currentVersion, cwd = process.cwd()) {
36514
37225
  };
36515
37226
  }
36516
37227
  async function applyRepoUpdate(repoRoot) {
36517
- const scriptPath = (0, import_path5.join)(repoRoot, "scripts", "Install-FohCli.ps1");
37228
+ const scriptPath = (0, import_path7.join)(repoRoot, "scripts", "Install-FohCli.ps1");
36518
37229
  if (process.platform === "win32") {
36519
- return await new Promise((resolve5, reject) => {
37230
+ return await new Promise((resolve7, reject) => {
36520
37231
  const child = (0, import_child_process3.spawn)(
36521
37232
  "powershell",
36522
37233
  ["-ExecutionPolicy", "Bypass", "-File", scriptPath],
36523
37234
  { stdio: "inherit" }
36524
37235
  );
36525
37236
  child.once("error", reject);
36526
- child.once("close", (code) => resolve5(typeof code === "number" ? code : 1));
37237
+ child.once("close", (code) => resolve7(typeof code === "number" ? code : 1));
36527
37238
  });
36528
37239
  }
36529
- return await new Promise((resolve5, reject) => {
37240
+ return await new Promise((resolve7, reject) => {
36530
37241
  const child = (0, import_child_process3.spawn)(
36531
37242
  "corepack",
36532
37243
  ["pnpm", "cli:install:global"],
@@ -36536,7 +37247,7 @@ async function applyRepoUpdate(repoRoot) {
36536
37247
  }
36537
37248
  );
36538
37249
  child.once("error", reject);
36539
- child.once("close", (code) => resolve5(typeof code === "number" ? code : 1));
37250
+ child.once("close", (code) => resolve7(typeof code === "number" ? code : 1));
36540
37251
  });
36541
37252
  }
36542
37253
  function shouldShowUpdateNotice(argv = process.argv) {
@@ -36550,7 +37261,7 @@ function shouldShowUpdateNotice(argv = process.argv) {
36550
37261
  }
36551
37262
  function hashFileSha256(filePath) {
36552
37263
  try {
36553
- const bytes = (0, import_fs6.readFileSync)(filePath);
37264
+ const bytes = (0, import_fs8.readFileSync)(filePath);
36554
37265
  return (0, import_crypto5.createHash)("sha256").update(bytes).digest("hex");
36555
37266
  } catch {
36556
37267
  return void 0;
@@ -36560,10 +37271,10 @@ function verifyCliArtifactIntegrity(params = {}) {
36560
37271
  const cwd = params.cwd ?? process.cwd();
36561
37272
  const argv = params.argv ?? process.argv;
36562
37273
  const expectedSha256 = String(params.expectedSha256 ?? "").trim().toLowerCase() || void 0;
36563
- const runtimePath = (0, import_path5.resolve)(String(argv[1] || ""));
37274
+ const runtimePath = (0, import_path7.resolve)(String(argv[1] || ""));
36564
37275
  const runtimeHash = runtimePath ? hashFileSha256(runtimePath) : void 0;
36565
37276
  const warnings = [];
36566
- if (!runtimePath || !(0, import_fs6.existsSync)(runtimePath)) {
37277
+ if (!runtimePath || !(0, import_fs8.existsSync)(runtimePath)) {
36567
37278
  warnings.push("runtime_path_unreadable");
36568
37279
  }
36569
37280
  if (!runtimeHash) {
@@ -36581,8 +37292,8 @@ function verifyCliArtifactIntegrity(params = {}) {
36581
37292
  let repoDistHash;
36582
37293
  let runtimeMatchesRepoDist;
36583
37294
  if (repoRoot) {
36584
- repoDistPath = (0, import_path5.join)(repoRoot, "packages", "cli", "dist", "foh.js");
36585
- 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)) {
36586
37297
  repoDistHash = hashFileSha256(repoDistPath);
36587
37298
  if (runtimeHash && repoDistHash) {
36588
37299
  runtimeMatchesRepoDist = runtimeHash === repoDistHash;
@@ -36805,6 +37516,9 @@ registerMcp(program2);
36805
37516
  registerKnowledge(program2);
36806
37517
  registerLeads(program2);
36807
37518
  registerConversations(program2);
37519
+ registerTranscripts(program2);
37520
+ registerAnalytics(program2);
37521
+ registerTest(program2);
36808
37522
  registerTests(program2);
36809
37523
  registerOps(program2);
36810
37524
  registerSetup(program2);
@@ -36812,6 +37526,7 @@ registerManifest(program2);
36812
37526
  registerSim(program2);
36813
37527
  registerDiag(program2);
36814
37528
  registerBug(program2);
37529
+ registerProve(program2);
36815
37530
  registerUpdate(program2);
36816
37531
  registerHome(program2);
36817
37532
  hideInternalApiUrlOptions(program2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@f-o-h/cli",
3
- "version": "0.1.4",
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": {