@f-o-h/cli 0.1.37 → 0.1.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -1
- package/dist/foh.js +90 -8
- package/package.json +1 -1
- package/schemas/cli-envelope.schema.json +5 -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.
|
|
7
|
+
Current published baseline: `@f-o-h/cli@0.1.39`
|
|
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
|
@@ -9927,7 +9927,12 @@ function envelopeOk(status) {
|
|
|
9927
9927
|
function dedupeCommands(commands = []) {
|
|
9928
9928
|
return Array.from(new Set(commands.map((command) => String(command || "").trim()).filter(Boolean)));
|
|
9929
9929
|
}
|
|
9930
|
+
function defaultSafeToRetry(status) {
|
|
9931
|
+
return !(status === "fail" || status === "blocked" || status === "hold");
|
|
9932
|
+
}
|
|
9930
9933
|
function cliEnvelope(input) {
|
|
9934
|
+
const extra = input.extra ?? {};
|
|
9935
|
+
const artifacts = input.artifacts ?? {};
|
|
9931
9936
|
return {
|
|
9932
9937
|
schema_version: input.schemaVersion ?? "foh_cli_envelope.v1",
|
|
9933
9938
|
ok: envelopeOk(input.status),
|
|
@@ -9936,9 +9941,13 @@ function cliEnvelope(input) {
|
|
|
9936
9941
|
summary: input.summary,
|
|
9937
9942
|
ids: input.ids ?? {},
|
|
9938
9943
|
checks: input.checks ?? [],
|
|
9939
|
-
artifacts
|
|
9944
|
+
artifacts,
|
|
9945
|
+
spend_class: input.spendClass ?? extra.spend_class ?? null,
|
|
9946
|
+
safe_to_retry: input.safeToRetry ?? extra.safe_to_retry ?? defaultSafeToRetry(input.status),
|
|
9947
|
+
proof_artifacts: input.proofArtifacts ?? extra.proof_artifacts ?? artifacts.proof_bundle ?? artifacts.proof_report ?? null,
|
|
9948
|
+
operator_note: input.operatorNote ?? extra.operator_note ?? null,
|
|
9940
9949
|
next_commands: dedupeCommands(input.nextCommands),
|
|
9941
|
-
...
|
|
9950
|
+
...extra
|
|
9942
9951
|
};
|
|
9943
9952
|
}
|
|
9944
9953
|
function reasonCodeFromStep(step, fallback = "cli_command_failed") {
|
|
@@ -32755,7 +32764,7 @@ var StdioServerTransport = class {
|
|
|
32755
32764
|
};
|
|
32756
32765
|
|
|
32757
32766
|
// src/lib/cli-version.ts
|
|
32758
|
-
var CLI_VERSION = "0.1.
|
|
32767
|
+
var CLI_VERSION = "0.1.39";
|
|
32759
32768
|
|
|
32760
32769
|
// src/commands/mcp-serve.ts
|
|
32761
32770
|
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
@@ -37173,6 +37182,11 @@ function normalizeMutationMode(raw, repair) {
|
|
|
37173
37182
|
const value = String(raw || "read-only").trim().toLowerCase();
|
|
37174
37183
|
return value === "ensure" ? "ensure" : "read-only";
|
|
37175
37184
|
}
|
|
37185
|
+
function normalizeContactPath(raw) {
|
|
37186
|
+
const value = String(raw || "auto").trim().toLowerCase();
|
|
37187
|
+
if (value === "managed" || value === "byon") return value;
|
|
37188
|
+
return "auto";
|
|
37189
|
+
}
|
|
37176
37190
|
function agentIdFromList(response) {
|
|
37177
37191
|
const agents = Array.isArray(response.agents) ? response.agents : [];
|
|
37178
37192
|
const usable = agents.filter((agent) => typeof agent.id === "string" && agent.id.trim());
|
|
@@ -37186,10 +37200,41 @@ function firstUsableOrgId(response) {
|
|
|
37186
37200
|
const usable = orgs.map((org) => org && typeof org === "object" ? org : {}).map((org) => String(org.org_id ?? org.id ?? "").trim()).filter(Boolean);
|
|
37187
37201
|
return { orgId: usable.length === 1 ? usable[0] : void 0, count: usable.length };
|
|
37188
37202
|
}
|
|
37203
|
+
function readStringField(record2, keys) {
|
|
37204
|
+
for (const key of keys) {
|
|
37205
|
+
const value = record2[key];
|
|
37206
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
37207
|
+
}
|
|
37208
|
+
return "";
|
|
37209
|
+
}
|
|
37210
|
+
function isProviderCapacityBlocked(onboarding) {
|
|
37211
|
+
const code = readStringField(onboarding, [
|
|
37212
|
+
"provisioning_reason_code",
|
|
37213
|
+
"provisioning_error_code",
|
|
37214
|
+
"reason_code",
|
|
37215
|
+
"code"
|
|
37216
|
+
]).toLowerCase();
|
|
37217
|
+
if ([
|
|
37218
|
+
"provider_capacity_blocked",
|
|
37219
|
+
"twilio_subaccount_limit_reached",
|
|
37220
|
+
"reserve_pool_exhausted",
|
|
37221
|
+
"global_safety_limit_reached"
|
|
37222
|
+
].includes(code)) {
|
|
37223
|
+
return true;
|
|
37224
|
+
}
|
|
37225
|
+
const message = readStringField(onboarding, [
|
|
37226
|
+
"provisioning_error",
|
|
37227
|
+
"provisioning_message",
|
|
37228
|
+
"error",
|
|
37229
|
+
"message"
|
|
37230
|
+
]).toLowerCase();
|
|
37231
|
+
return /maximum number of subaccounts|subaccount limit|reserve[- ]number pool|reserve pool exhausted|global safety limit/.test(message);
|
|
37232
|
+
}
|
|
37189
37233
|
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 () => {
|
|
37234
|
+
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
37235
|
const checks = [];
|
|
37192
37236
|
const mission = normalizeMission(opts.mission);
|
|
37237
|
+
const contactPath = normalizeContactPath(opts.contactPath);
|
|
37193
37238
|
const mutationMode = normalizeMutationMode(opts.mutationMode, Boolean(opts.repair));
|
|
37194
37239
|
const ctx = {
|
|
37195
37240
|
tokenPresent: false,
|
|
@@ -37291,18 +37336,41 @@ function registerProve(program3) {
|
|
|
37291
37336
|
if (phoneNumber) {
|
|
37292
37337
|
checks.push(pass("contact_channel", "Contact phone number is provisioned.", {
|
|
37293
37338
|
phone_number_present: true,
|
|
37294
|
-
provisioning_status: provisioningStatus
|
|
37339
|
+
provisioning_status: provisioningStatus,
|
|
37340
|
+
contact_path: contactPath
|
|
37341
|
+
}));
|
|
37342
|
+
} else if (contactPath === "byon" && (opts.requirePhone || mission === "voice")) {
|
|
37343
|
+
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`, {
|
|
37344
|
+
provisioning_status: provisioningStatus,
|
|
37345
|
+
mission,
|
|
37346
|
+
contact_path: contactPath,
|
|
37347
|
+
spend_policy: resolveCliSpendPolicy(),
|
|
37348
|
+
spend_class: "customer_owned",
|
|
37349
|
+
safe_to_retry: false,
|
|
37350
|
+
operator_note: "Attach or configure the customer-owned voice number, then rerun proof. This path should not buy a FOH-owned number."
|
|
37351
|
+
}));
|
|
37352
|
+
} else if (provisioningStatus === "failed" && isProviderCapacityBlocked(onboarding) && (opts.requirePhone || mission === "voice")) {
|
|
37353
|
+
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`, {
|
|
37354
|
+
provisioning_status: provisioningStatus,
|
|
37355
|
+
mission,
|
|
37356
|
+
contact_path: contactPath,
|
|
37357
|
+
spend_policy: resolveCliSpendPolicy(),
|
|
37358
|
+
spend_class: "paid_foh",
|
|
37359
|
+
safe_to_retry: false,
|
|
37360
|
+
operator_note: "Do not retry blindly. Resolve provider/account capacity or use BYON/customer-owned contact path."
|
|
37295
37361
|
}));
|
|
37296
37362
|
} else if (provisioningStatus === "failed" && (opts.requirePhone || mission === "voice")) {
|
|
37297
37363
|
checks.push(hold("contact_channel", "contact_phone_provisioning_failed", "Phone/contact provisioning failed for this org.", `foh provision status --org ${ctx.orgId} --json`, {
|
|
37298
37364
|
provisioning_status: provisioningStatus,
|
|
37299
37365
|
mission,
|
|
37366
|
+
contact_path: contactPath,
|
|
37300
37367
|
spend_policy: resolveCliSpendPolicy()
|
|
37301
37368
|
}));
|
|
37302
37369
|
} else if (isNoSpendPolicy() && (opts.requirePhone || mission === "voice")) {
|
|
37303
37370
|
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
37371
|
provisioning_status: provisioningStatus,
|
|
37305
37372
|
mission,
|
|
37373
|
+
contact_path: contactPath,
|
|
37306
37374
|
spend_policy: resolveCliSpendPolicy(),
|
|
37307
37375
|
spend_class: "free",
|
|
37308
37376
|
safe_to_retry: true,
|
|
@@ -37312,6 +37380,7 @@ function registerProve(program3) {
|
|
|
37312
37380
|
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
37381
|
provisioning_status: provisioningStatus,
|
|
37314
37382
|
mission,
|
|
37383
|
+
contact_path: contactPath,
|
|
37315
37384
|
spend_policy: resolveCliSpendPolicy()
|
|
37316
37385
|
}));
|
|
37317
37386
|
} else {
|
|
@@ -37464,6 +37533,7 @@ function registerProve(program3) {
|
|
|
37464
37533
|
org_id: ctx.orgId ?? null,
|
|
37465
37534
|
agent_id: ctx.agentId ?? null,
|
|
37466
37535
|
mission,
|
|
37536
|
+
contact_path: contactPath,
|
|
37467
37537
|
mutation_mode: mutationMode,
|
|
37468
37538
|
widget_public_key_present: Boolean(ctx.widgetPublicKey),
|
|
37469
37539
|
conversation_id: ctx.conversationId ?? null,
|
|
@@ -39196,6 +39266,12 @@ function classifyRun(input) {
|
|
|
39196
39266
|
if (hasCommandReason(new RegExp(PAID_RESOURCE_BLOCKED_REASON_CODE, "i"))) {
|
|
39197
39267
|
return { status: "hold", reasonCode: PAID_RESOURCE_BLOCKED_REASON_CODE };
|
|
39198
39268
|
}
|
|
39269
|
+
if (hasCommandReason(/provider_capacity_blocked/i)) {
|
|
39270
|
+
return { status: "hold", reasonCode: "provider_capacity_blocked" };
|
|
39271
|
+
}
|
|
39272
|
+
if (hasCommandReason(/byon_voice_number_not_configured/i)) {
|
|
39273
|
+
return { status: "hold", reasonCode: "byon_voice_number_not_configured" };
|
|
39274
|
+
}
|
|
39199
39275
|
if (hasCommandReason(/contact_phone_provisioning_failed/i)) {
|
|
39200
39276
|
return { status: "hold", reasonCode: "voice_contact_phone_provisioning_failed" };
|
|
39201
39277
|
}
|
|
@@ -39230,12 +39306,18 @@ ${stderr}`;
|
|
|
39230
39306
|
if (/ENV_NETWORK_DNS_BLOCK|Could not resolve host|npm ping.*timeout|NO_EXECUTABLE_INSTALL/i.test(combined)) {
|
|
39231
39307
|
return { status: "hold", reasonCode: "codex_network_dns_blocked" };
|
|
39232
39308
|
}
|
|
39233
|
-
if (/contact_phone_provisioning_failed/i.test(combined)) {
|
|
39234
|
-
return { status: "hold", reasonCode: "voice_contact_phone_provisioning_failed" };
|
|
39235
|
-
}
|
|
39236
39309
|
if (new RegExp(PAID_RESOURCE_BLOCKED_REASON_CODE, "i").test(combined)) {
|
|
39237
39310
|
return { status: "hold", reasonCode: PAID_RESOURCE_BLOCKED_REASON_CODE };
|
|
39238
39311
|
}
|
|
39312
|
+
if (/provider_capacity_blocked/i.test(combined)) {
|
|
39313
|
+
return { status: "hold", reasonCode: "provider_capacity_blocked" };
|
|
39314
|
+
}
|
|
39315
|
+
if (/byon_voice_number_not_configured/i.test(combined)) {
|
|
39316
|
+
return { status: "hold", reasonCode: "byon_voice_number_not_configured" };
|
|
39317
|
+
}
|
|
39318
|
+
if (/contact_phone_provisioning_failed/i.test(combined)) {
|
|
39319
|
+
return { status: "hold", reasonCode: "voice_contact_phone_provisioning_failed" };
|
|
39320
|
+
}
|
|
39239
39321
|
if (/voice_contact_expected_no_spend_hold/i.test(combined)) {
|
|
39240
39322
|
return { status: "hold", reasonCode: "voice_contact_expected_no_spend_hold" };
|
|
39241
39323
|
}
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"$id": "https://frontofhouse.okii.uk/schemas/cli-envelope.schema.json",
|
|
4
4
|
"title": "FOH CLI Envelope",
|
|
5
5
|
"type": "object",
|
|
6
|
-
"required": ["schema_version", "ok", "status", "reason_code", "summary", "ids", "checks", "artifacts", "next_commands"],
|
|
6
|
+
"required": ["schema_version", "ok", "status", "reason_code", "summary", "ids", "checks", "artifacts", "spend_class", "safe_to_retry", "proof_artifacts", "operator_note", "next_commands"],
|
|
7
7
|
"properties": {
|
|
8
8
|
"schema_version": { "type": "string" },
|
|
9
9
|
"ok": { "type": "boolean" },
|
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
"ids": { "type": "object" },
|
|
14
14
|
"checks": { "type": "array" },
|
|
15
15
|
"artifacts": { "type": "object" },
|
|
16
|
+
"spend_class": { "type": ["string", "null"] },
|
|
17
|
+
"safe_to_retry": { "type": "boolean" },
|
|
18
|
+
"proof_artifacts": { "type": ["object", "string", "null"] },
|
|
19
|
+
"operator_note": { "type": ["string", "null"] },
|
|
16
20
|
"next_commands": {
|
|
17
21
|
"type": "array",
|
|
18
22
|
"items": { "type": "string" }
|