@f-o-h/cli 0.1.37 → 0.1.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +8 -1
  2. package/dist/foh.js +79 -6
  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.37`
7
+ Current published baseline: `@f-o-h/cli@0.1.38`
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.
@@ -87,6 +87,9 @@ read-only by default; pass `--mutation-mode ensure` or `--repair` only when you
87
87
  explicitly want proof to ensure missing widget state. Use `--strict` in
88
88
  automation when holds should fail the command, and `--mission voice` or
89
89
  `--require-phone` when a voice/contact number is mandatory for the demo.
90
+ Use `--contact-path byon` when the proof is meant to validate a
91
+ customer-owned/BYON phone route; missing BYON config then reports
92
+ `byon_voice_number_not_configured` instead of suggesting FOH number purchase.
90
93
 
91
94
  For mass AI-agent evals and repeated demo rehearsals, keep setup on the free
92
95
  scaffold lane:
@@ -99,6 +102,10 @@ FOH_CLI_SPEND_POLICY=no_spend foh setup --org <org-id> --agent-template <templat
99
102
  buying one. `--phone-mode skip` bypasses the phone step. `--phone-mode purchase`
100
103
  is the explicit paid contact path and is fail-closed when
101
104
  `FOH_CLI_SPEND_POLICY=no_spend` is set.
105
+
106
+ If managed-number provisioning is blocked by account/provider capacity or empty
107
+ reserve inventory, proof reports `provider_capacity_blocked`; fix capacity or
108
+ switch to BYON rather than retrying blindly.
102
109
 
103
110
  The CLI defaults to the production API at `https://api.frontofhouse.okii.uk`.
104
111
 
package/dist/foh.js CHANGED
@@ -32755,7 +32755,7 @@ var StdioServerTransport = class {
32755
32755
  };
32756
32756
 
32757
32757
  // src/lib/cli-version.ts
32758
- var CLI_VERSION = "0.1.37";
32758
+ var CLI_VERSION = "0.1.38";
32759
32759
 
32760
32760
  // src/commands/mcp-serve.ts
32761
32761
  var DEFAULT_TIMEOUT_MS = 12e4;
@@ -37173,6 +37173,11 @@ function normalizeMutationMode(raw, repair) {
37173
37173
  const value = String(raw || "read-only").trim().toLowerCase();
37174
37174
  return value === "ensure" ? "ensure" : "read-only";
37175
37175
  }
37176
+ function normalizeContactPath(raw) {
37177
+ const value = String(raw || "auto").trim().toLowerCase();
37178
+ if (value === "managed" || value === "byon") return value;
37179
+ return "auto";
37180
+ }
37176
37181
  function agentIdFromList(response) {
37177
37182
  const agents = Array.isArray(response.agents) ? response.agents : [];
37178
37183
  const usable = agents.filter((agent) => typeof agent.id === "string" && agent.id.trim());
@@ -37186,10 +37191,41 @@ function firstUsableOrgId(response) {
37186
37191
  const usable = orgs.map((org) => org && typeof org === "object" ? org : {}).map((org) => String(org.org_id ?? org.id ?? "").trim()).filter(Boolean);
37187
37192
  return { orgId: usable.length === 1 ? usable[0] : void 0, count: usable.length };
37188
37193
  }
37194
+ function readStringField(record2, keys) {
37195
+ for (const key of keys) {
37196
+ const value = record2[key];
37197
+ if (typeof value === "string" && value.trim()) return value.trim();
37198
+ }
37199
+ return "";
37200
+ }
37201
+ function isProviderCapacityBlocked(onboarding) {
37202
+ const code = readStringField(onboarding, [
37203
+ "provisioning_reason_code",
37204
+ "provisioning_error_code",
37205
+ "reason_code",
37206
+ "code"
37207
+ ]).toLowerCase();
37208
+ if ([
37209
+ "provider_capacity_blocked",
37210
+ "twilio_subaccount_limit_reached",
37211
+ "reserve_pool_exhausted",
37212
+ "global_safety_limit_reached"
37213
+ ].includes(code)) {
37214
+ return true;
37215
+ }
37216
+ const message = readStringField(onboarding, [
37217
+ "provisioning_error",
37218
+ "provisioning_message",
37219
+ "error",
37220
+ "message"
37221
+ ]).toLowerCase();
37222
+ return /maximum number of subaccounts|subaccount limit|reserve[- ]number pool|reserve pool exhausted|global safety limit/.test(message);
37223
+ }
37189
37224
  function registerProve(program3) {
37190
- 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("--mission <mission>", "Proof mission: setup, widget, voice, publish", "setup").option("--mutation-mode <mode>", "Proof mutation mode: read-only or ensure", "read-only").option("--repair", "Alias for --mutation-mode ensure").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 () => {
37225
+ 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("--mission <mission>", "Proof mission: setup, widget, voice, publish", "setup").option("--contact-path <mode>", "Voice contact path: auto, managed, or byon", "auto").option("--mutation-mode <mode>", "Proof mutation mode: read-only or ensure", "read-only").option("--repair", "Alias for --mutation-mode ensure").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 () => {
37191
37226
  const checks = [];
37192
37227
  const mission = normalizeMission(opts.mission);
37228
+ const contactPath = normalizeContactPath(opts.contactPath);
37193
37229
  const mutationMode = normalizeMutationMode(opts.mutationMode, Boolean(opts.repair));
37194
37230
  const ctx = {
37195
37231
  tokenPresent: false,
@@ -37291,18 +37327,41 @@ function registerProve(program3) {
37291
37327
  if (phoneNumber) {
37292
37328
  checks.push(pass("contact_channel", "Contact phone number is provisioned.", {
37293
37329
  phone_number_present: true,
37294
- provisioning_status: provisioningStatus
37330
+ provisioning_status: provisioningStatus,
37331
+ contact_path: contactPath
37332
+ }));
37333
+ } else if (contactPath === "byon" && (opts.requirePhone || mission === "voice")) {
37334
+ checks.push(hold("contact_channel", "byon_voice_number_not_configured", "BYON/customer-owned voice contact path was requested, but no contact phone is configured for this org.", `foh provision status --org ${ctx.orgId} --json`, {
37335
+ provisioning_status: provisioningStatus,
37336
+ mission,
37337
+ contact_path: contactPath,
37338
+ spend_policy: resolveCliSpendPolicy(),
37339
+ spend_class: "customer_owned",
37340
+ safe_to_retry: false,
37341
+ operator_note: "Attach or configure the customer-owned voice number, then rerun proof. This path should not buy a FOH-owned number."
37342
+ }));
37343
+ } else if (provisioningStatus === "failed" && isProviderCapacityBlocked(onboarding) && (opts.requirePhone || mission === "voice")) {
37344
+ checks.push(hold("contact_channel", "provider_capacity_blocked", "Phone/contact provisioning is blocked by provider/account capacity or empty reserve inventory.", `foh provision status --org ${ctx.orgId} --json`, {
37345
+ provisioning_status: provisioningStatus,
37346
+ mission,
37347
+ contact_path: contactPath,
37348
+ spend_policy: resolveCliSpendPolicy(),
37349
+ spend_class: "paid_foh",
37350
+ safe_to_retry: false,
37351
+ operator_note: "Do not retry blindly. Resolve provider/account capacity or use BYON/customer-owned contact path."
37295
37352
  }));
37296
37353
  } else if (provisioningStatus === "failed" && (opts.requirePhone || mission === "voice")) {
37297
37354
  checks.push(hold("contact_channel", "contact_phone_provisioning_failed", "Phone/contact provisioning failed for this org.", `foh provision status --org ${ctx.orgId} --json`, {
37298
37355
  provisioning_status: provisioningStatus,
37299
37356
  mission,
37357
+ contact_path: contactPath,
37300
37358
  spend_policy: resolveCliSpendPolicy()
37301
37359
  }));
37302
37360
  } else if (isNoSpendPolicy() && (opts.requirePhone || mission === "voice")) {
37303
37361
  checks.push(hold("contact_channel", "voice_contact_expected_no_spend_hold", "No phone/contact number is provisioned; this is expected in no-spend eval mode unless a BYON/customer-approved phone path exists.", `foh provision status --org ${ctx.orgId} --json`, {
37304
37362
  provisioning_status: provisioningStatus,
37305
37363
  mission,
37364
+ contact_path: contactPath,
37306
37365
  spend_policy: resolveCliSpendPolicy(),
37307
37366
  spend_class: "free",
37308
37367
  safe_to_retry: true,
@@ -37312,6 +37371,7 @@ function registerProve(program3) {
37312
37371
  checks.push(hold("contact_channel", "contact_phone_missing", "No phone/contact number is provisioned for this org.", `foh provision buy --org ${ctx.orgId} --json`, {
37313
37372
  provisioning_status: provisioningStatus,
37314
37373
  mission,
37374
+ contact_path: contactPath,
37315
37375
  spend_policy: resolveCliSpendPolicy()
37316
37376
  }));
37317
37377
  } else {
@@ -37464,6 +37524,7 @@ function registerProve(program3) {
37464
37524
  org_id: ctx.orgId ?? null,
37465
37525
  agent_id: ctx.agentId ?? null,
37466
37526
  mission,
37527
+ contact_path: contactPath,
37467
37528
  mutation_mode: mutationMode,
37468
37529
  widget_public_key_present: Boolean(ctx.widgetPublicKey),
37469
37530
  conversation_id: ctx.conversationId ?? null,
@@ -39196,6 +39257,12 @@ function classifyRun(input) {
39196
39257
  if (hasCommandReason(new RegExp(PAID_RESOURCE_BLOCKED_REASON_CODE, "i"))) {
39197
39258
  return { status: "hold", reasonCode: PAID_RESOURCE_BLOCKED_REASON_CODE };
39198
39259
  }
39260
+ if (hasCommandReason(/provider_capacity_blocked/i)) {
39261
+ return { status: "hold", reasonCode: "provider_capacity_blocked" };
39262
+ }
39263
+ if (hasCommandReason(/byon_voice_number_not_configured/i)) {
39264
+ return { status: "hold", reasonCode: "byon_voice_number_not_configured" };
39265
+ }
39199
39266
  if (hasCommandReason(/contact_phone_provisioning_failed/i)) {
39200
39267
  return { status: "hold", reasonCode: "voice_contact_phone_provisioning_failed" };
39201
39268
  }
@@ -39230,12 +39297,18 @@ ${stderr}`;
39230
39297
  if (/ENV_NETWORK_DNS_BLOCK|Could not resolve host|npm ping.*timeout|NO_EXECUTABLE_INSTALL/i.test(combined)) {
39231
39298
  return { status: "hold", reasonCode: "codex_network_dns_blocked" };
39232
39299
  }
39233
- if (/contact_phone_provisioning_failed/i.test(combined)) {
39234
- return { status: "hold", reasonCode: "voice_contact_phone_provisioning_failed" };
39235
- }
39236
39300
  if (new RegExp(PAID_RESOURCE_BLOCKED_REASON_CODE, "i").test(combined)) {
39237
39301
  return { status: "hold", reasonCode: PAID_RESOURCE_BLOCKED_REASON_CODE };
39238
39302
  }
39303
+ if (/provider_capacity_blocked/i.test(combined)) {
39304
+ return { status: "hold", reasonCode: "provider_capacity_blocked" };
39305
+ }
39306
+ if (/byon_voice_number_not_configured/i.test(combined)) {
39307
+ return { status: "hold", reasonCode: "byon_voice_number_not_configured" };
39308
+ }
39309
+ if (/contact_phone_provisioning_failed/i.test(combined)) {
39310
+ return { status: "hold", reasonCode: "voice_contact_phone_provisioning_failed" };
39311
+ }
39239
39312
  if (/voice_contact_expected_no_spend_hold/i.test(combined)) {
39240
39313
  return { status: "hold", reasonCode: "voice_contact_expected_no_spend_hold" };
39241
39314
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@f-o-h/cli",
3
- "version": "0.1.37",
3
+ "version": "0.1.38",
4
4
  "description": "FOH CLI - AI-operator provisioning tool for Front Of House",
5
5
  "license": "UNLICENSED",
6
6
  "bin": {