@f-o-h/cli 0.1.53 → 0.1.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/foh.js +80 -9
- package/package.json +1 -1
package/dist/foh.js
CHANGED
|
@@ -16021,6 +16021,19 @@ function getTtsProviders(catalog) {
|
|
|
16021
16021
|
const fromProviders = Array.isArray(catalog.providers?.tts) ? catalog.providers?.tts : [];
|
|
16022
16022
|
return fromProviders.map((provider) => String(provider || "").trim().toLowerCase()).filter((provider) => provider.length > 0).sort();
|
|
16023
16023
|
}
|
|
16024
|
+
function getSttProviders(catalog) {
|
|
16025
|
+
const providerRows = Array.isArray(catalog.voice_catalog?.providers) ? catalog.voice_catalog?.providers : [];
|
|
16026
|
+
const fromRows = providerRows.filter((provider) => provider?.kind === "stt").map((provider) => String(provider.id || "").trim().toLowerCase()).filter((provider) => provider.length > 0);
|
|
16027
|
+
if (fromRows.length > 0) return [...new Set(fromRows)].sort();
|
|
16028
|
+
const fromProviders = Array.isArray(catalog.providers?.stt) ? catalog.providers?.stt : [];
|
|
16029
|
+
return fromProviders.map((provider) => String(provider || "").trim().toLowerCase()).filter((provider) => provider.length > 0).sort();
|
|
16030
|
+
}
|
|
16031
|
+
function resolveDefaultSttProvider(providers) {
|
|
16032
|
+
for (const preferred of ["deepgram", "azure", "twilio", "none"]) {
|
|
16033
|
+
if (providers.includes(preferred)) return preferred;
|
|
16034
|
+
}
|
|
16035
|
+
return providers[0] || "deepgram";
|
|
16036
|
+
}
|
|
16024
16037
|
function registerVoice(program3) {
|
|
16025
16038
|
const voice = program3.command("voice").description("Manage voice provider configuration");
|
|
16026
16039
|
voice.command("verify").description("Run voice verification lanes (quick/full/release) through authenticated API orchestration").option("--mode <m>", "Verification mode: quick, full, release", "release").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 () => {
|
|
@@ -16248,11 +16261,12 @@ function registerVoice(program3) {
|
|
|
16248
16261
|
const allReady = providers.length > 0 && providers.every((provider) => provider?.ready === true);
|
|
16249
16262
|
if (!allReady) markCommandFailed(1);
|
|
16250
16263
|
}));
|
|
16251
|
-
voice.command("configure").description("Configure voice settings for an agent (does not publish unless --publish)").requiredOption("--agent <id>", "Agent ID").requiredOption("--provider <p>", "TTS provider: openai, azure, twilio").requiredOption("--voice <id>", "Voice ID").option("--stt-provider <p>", "STT provider (default:
|
|
16264
|
+
voice.command("configure").description("Configure voice settings for an agent (does not publish unless --publish)").requiredOption("--agent <id>", "Agent ID").requiredOption("--provider <p>", "TTS provider: openai, azure, twilio").requiredOption("--voice <id>", "Voice ID").option("--stt-provider <p>", "STT provider (default: best available, preferring deepgram)").option("--publish", "Validate, simulation-certify, then publish after configuring").option("--cert-mode <m>", "Simulation cert mode before publish: quick, full, stress", "full").option("--cert-adaptive-runs <n>", "Adaptive runs for full/stress certification (default: 30)", "30").option("--cert-max-improvement-rounds <n>", "Max prompt improvement rounds before publish (0-5)", "1").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 () => {
|
|
16252
16265
|
const provider = String(opts.provider || "").trim().toLowerCase();
|
|
16253
16266
|
const voiceId = String(opts.voice || "").trim();
|
|
16254
16267
|
const catalog = await getSpeechCatalog(opts.apiUrl);
|
|
16255
16268
|
const supportedProviders = getTtsProviders(catalog);
|
|
16269
|
+
const supportedSttProviders = getSttProviders(catalog);
|
|
16256
16270
|
if (!supportedProviders.includes(provider)) {
|
|
16257
16271
|
throw new FohError({
|
|
16258
16272
|
step: "voice.configure",
|
|
@@ -16271,10 +16285,19 @@ function registerVoice(program3) {
|
|
|
16271
16285
|
statusCode: 400
|
|
16272
16286
|
});
|
|
16273
16287
|
}
|
|
16288
|
+
const sttProvider = String(opts.sttProvider || resolveDefaultSttProvider(supportedSttProviders)).trim().toLowerCase();
|
|
16289
|
+
if (supportedSttProviders.length > 0 && !supportedSttProviders.includes(sttProvider)) {
|
|
16290
|
+
throw new FohError({
|
|
16291
|
+
step: "voice.configure",
|
|
16292
|
+
error: `STT provider "${sttProvider}" is not available in speech catalog`,
|
|
16293
|
+
remediation: `Use --stt-provider ${resolveDefaultSttProvider(supportedSttProviders)} or run: foh voice list-providers${opts.apiUrl ? ` --api-url ${opts.apiUrl}` : ""}`,
|
|
16294
|
+
statusCode: 400
|
|
16295
|
+
});
|
|
16296
|
+
}
|
|
16274
16297
|
const voiceConfig = {
|
|
16275
16298
|
tts_provider: provider,
|
|
16276
16299
|
tts_voice_id: voiceId,
|
|
16277
|
-
stt_provider:
|
|
16300
|
+
stt_provider: sttProvider
|
|
16278
16301
|
};
|
|
16279
16302
|
const draft = await apiFetch(`/v1/console/agents/${opts.agent}/draft`, {
|
|
16280
16303
|
method: "PATCH",
|
|
@@ -32830,7 +32853,7 @@ var StdioServerTransport = class {
|
|
|
32830
32853
|
};
|
|
32831
32854
|
|
|
32832
32855
|
// src/lib/cli-version.ts
|
|
32833
|
-
var CLI_VERSION = "0.1.
|
|
32856
|
+
var CLI_VERSION = "0.1.55";
|
|
32834
32857
|
|
|
32835
32858
|
// src/commands/mcp-serve.ts
|
|
32836
32859
|
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
@@ -34479,6 +34502,28 @@ function normalizeSetupPhoneMode(raw) {
|
|
|
34479
34502
|
reasonCode: "setup_invalid_phone_mode"
|
|
34480
34503
|
});
|
|
34481
34504
|
}
|
|
34505
|
+
function complianceSkipDetail(phoneMode) {
|
|
34506
|
+
return {
|
|
34507
|
+
reason_code: `compliance_skipped_phone_mode_${phoneMode}`,
|
|
34508
|
+
phone_mode: phoneMode,
|
|
34509
|
+
spend_policy: resolveCliSpendPolicy(),
|
|
34510
|
+
spend_class: "free",
|
|
34511
|
+
safe_to_retry: true,
|
|
34512
|
+
operator_note: "Compliance is only required before paid FOH-owned phone purchase."
|
|
34513
|
+
};
|
|
34514
|
+
}
|
|
34515
|
+
function isMissingAgentTestsError(error2) {
|
|
34516
|
+
if (!(error2 instanceof FohError)) return false;
|
|
34517
|
+
if (error2.statusCode !== 404) return false;
|
|
34518
|
+
const text = [
|
|
34519
|
+
error2.error,
|
|
34520
|
+
error2.reasonCode,
|
|
34521
|
+
error2.detail?.error,
|
|
34522
|
+
error2.detail?.message,
|
|
34523
|
+
error2.detail?.reason_code
|
|
34524
|
+
].filter((value) => value !== void 0 && value !== null).join(" ").toLowerCase();
|
|
34525
|
+
return text.includes("no tests found") || text.includes("tests_not_found") || text.includes("agent_tests_not_configured");
|
|
34526
|
+
}
|
|
34482
34527
|
function buildMissingOptionsPlan(missing, opts) {
|
|
34483
34528
|
const missingFlags = missing.map(optionNameToFlag);
|
|
34484
34529
|
const signInUrl = buildConsoleSignInUrl(resolveConsoleBaseUrl(opts.consoleUrl));
|
|
@@ -34722,6 +34767,9 @@ function registerSetup(program3) {
|
|
|
34722
34767
|
});
|
|
34723
34768
|
await step("submit_compliance", "Submit standard compliance", async () => {
|
|
34724
34769
|
if (opts.skipCompliance) return { step: "submit_compliance", status: "skipped", detail: "--skip-compliance" };
|
|
34770
|
+
if (phoneMode !== "purchase") {
|
|
34771
|
+
return { step: "submit_compliance", status: "skipped", detail: complianceSkipDetail(phoneMode) };
|
|
34772
|
+
}
|
|
34725
34773
|
const status = await apiFetch("/v1/console/org/compliance/status", {
|
|
34726
34774
|
orgId: opts.org,
|
|
34727
34775
|
apiUrlOverride: opts.apiUrl
|
|
@@ -34742,6 +34790,9 @@ function registerSetup(program3) {
|
|
|
34742
34790
|
});
|
|
34743
34791
|
await step("wait_compliance", "Poll until compliance approved", async () => {
|
|
34744
34792
|
if (opts.skipCompliance) return { step: "wait_compliance", status: "skipped", detail: "--skip-compliance" };
|
|
34793
|
+
if (phoneMode !== "purchase") {
|
|
34794
|
+
return { step: "wait_compliance", status: "skipped", detail: complianceSkipDetail(phoneMode) };
|
|
34795
|
+
}
|
|
34745
34796
|
await pollUntil(
|
|
34746
34797
|
async () => {
|
|
34747
34798
|
const status = await apiFetch("/v1/console/org/compliance/status", {
|
|
@@ -34930,7 +34981,7 @@ function registerSetup(program3) {
|
|
|
34930
34981
|
voice: {
|
|
34931
34982
|
tts_provider: opts.voiceProvider,
|
|
34932
34983
|
tts_voice_id: opts.voiceId,
|
|
34933
|
-
stt_provider: "
|
|
34984
|
+
stt_provider: "deepgram"
|
|
34934
34985
|
}
|
|
34935
34986
|
}),
|
|
34936
34987
|
apiUrlOverride: opts.apiUrl
|
|
@@ -34940,10 +34991,30 @@ function registerSetup(program3) {
|
|
|
34940
34991
|
await step("run_smoke_test", "Run all agent tests", async () => {
|
|
34941
34992
|
if (opts.skipTests) return { step: "run_smoke_test", status: "skipped", detail: "--skip-tests" };
|
|
34942
34993
|
const resolvedAgentId = requireAgentId("run_smoke_test");
|
|
34943
|
-
|
|
34944
|
-
|
|
34945
|
-
|
|
34946
|
-
|
|
34994
|
+
let batch;
|
|
34995
|
+
try {
|
|
34996
|
+
batch = await apiFetch(`/v1/console/agents/${resolvedAgentId}/tests/run-all`, {
|
|
34997
|
+
method: "POST",
|
|
34998
|
+
apiUrlOverride: opts.apiUrl
|
|
34999
|
+
});
|
|
35000
|
+
} catch (error2) {
|
|
35001
|
+
if (isMissingAgentTestsError(error2)) {
|
|
35002
|
+
return {
|
|
35003
|
+
step: "run_smoke_test",
|
|
35004
|
+
status: "skipped",
|
|
35005
|
+
detail: {
|
|
35006
|
+
reason_code: "agent_tests_not_configured",
|
|
35007
|
+
safe_to_retry: true,
|
|
35008
|
+
next_commands: [
|
|
35009
|
+
`foh test run --agent ${resolvedAgentId} --json`,
|
|
35010
|
+
`foh prove --agent ${resolvedAgentId} --mission widget --json`
|
|
35011
|
+
],
|
|
35012
|
+
operator_note: "No saved agent tests exist yet; setup continues to simulation certification and widget proof."
|
|
35013
|
+
}
|
|
35014
|
+
};
|
|
35015
|
+
}
|
|
35016
|
+
throw error2;
|
|
35017
|
+
}
|
|
34947
35018
|
if (!batch.batch_id) return { step: "run_smoke_test", status: "done" };
|
|
34948
35019
|
const result = await pollUntil(
|
|
34949
35020
|
async () => {
|
|
@@ -40037,7 +40108,7 @@ async function executeExternalAgentExecutorPlan(plan, options = {}) {
|
|
|
40037
40108
|
var DEFAULT_PROMPT_VERSION = "blank-setup.v1";
|
|
40038
40109
|
var DEFAULT_BATCH_MODELS = "openai/codex,anthropic/claude,cursor/agent";
|
|
40039
40110
|
var PROMPTS = {
|
|
40040
|
-
"blank-setup.v1": "Go to https://frontofhouse.okii.uk. Use only public docs, public API docs, and the public npm CLI package. Always invoke the CLI with `npx --yes @f-o-h/cli@latest ...`; do not use unpinned `npx @f-o-h/cli ...`, because cached older packages can produce invalid evidence. Install or verify the FOH CLI, authenticate or reach a deterministic auth blocker, then create or configure a Front Of House voice agent and website widget. Prefer the certification-oriented buyer templates: run `npx --yes @f-o-h/cli@latest templates list --category buyer --json` and use `UK Buyer Qualification` or `Viewing Booking` when available; do not use a greeting-only template for proof/certification. Prefer `npx --yes @f-o-h/cli@latest setup --phone-mode observe` for the free scaffold path: agent, widget, voice config, smoke test, certification, and publish readiness together. Treat phone-number purchasing as an explicit paid/scarce contact-path step, not part of high-volume eval setup. If `FOH_CLI_SPEND_POLICY=no_spend` is active and a command returns `paid_resource_blocked_by_spend_policy`, do not try to bypass it; continue widget/setup proof and report that exact reason code for the phone path. Run proof/smoke/certification where available, including widget proof and voice proof. When running more than one `foh prove` mission for the same agent, pass `--proof-cache-dir .foh/proof-cache` so simulation certification can be shared instead of recomputed. If voice proof returns `contact_phone_missing` or `voice_contact_expected_no_spend_hold`, report that exact reason code unless a BYON/customer-approved phone path already exists. If `FOH_EXTERNAL_AGENT_RUN_DIR` is set, write `${FOH_EXTERNAL_AGENT_RUN_DIR}/external-agent-metadata.json` with `schema_version`, `docs_pages_used`, key decisions, and blocker reason codes before finishing. Produce a final evidence summary with commands run, docs used, artifacts created, and any blocker reason codes. Do not assume access to the private source repository.",
|
|
40111
|
+
"blank-setup.v1": "Go to https://frontofhouse.okii.uk. Use only public docs, public API docs, and the public npm CLI package. Always invoke the CLI with `npx --yes @f-o-h/cli@latest ...`; do not use unpinned `npx @f-o-h/cli ...`, because cached older packages can produce invalid evidence. Install or verify the FOH CLI, authenticate or reach a deterministic auth blocker, then create or configure a Front Of House voice agent and website widget. Prefer the certification-oriented buyer templates: run `npx --yes @f-o-h/cli@latest templates list --category buyer --json` and use `UK Buyer Qualification` or `Viewing Booking` when available; do not use a greeting-only template for proof/certification. Prefer `npx --yes @f-o-h/cli@latest setup --phone-mode observe` for the free scaffold path: agent, widget, voice config, smoke test, certification, and publish readiness together. Treat phone-number purchasing as an explicit paid/scarce contact-path step, not part of high-volume eval setup. If `FOH_CLI_SPEND_POLICY=no_spend` is active and a command returns `paid_resource_blocked_by_spend_policy`, do not try to bypass it; continue widget/setup proof and report that exact reason code for the phone path. If the customer/operator explicitly owns a number and asks for real PSTN proof, use `npx --yes @f-o-h/cli@latest provision byon attach --phone-number <e164> --confirm-owned --json`; do not invent ownership or buy a FOH-owned number. Run proof/smoke/certification where available, including widget proof and voice proof. When running more than one `foh prove` mission for the same agent, pass `--proof-cache-dir .foh/proof-cache` so simulation certification can be shared instead of recomputed. If voice proof returns `contact_phone_missing` or `voice_contact_expected_no_spend_hold`, report that exact reason code unless a BYON/customer-approved phone path already exists. If `FOH_EXTERNAL_AGENT_RUN_DIR` is set, write `${FOH_EXTERNAL_AGENT_RUN_DIR}/external-agent-metadata.json` with `schema_version`, `docs_pages_used`, key decisions, and blocker reason codes before finishing. Produce a final evidence summary with commands run, docs used, artifacts created, and any blocker reason codes. Do not assume access to the private source repository.",
|
|
40041
40112
|
"debug-proof-failure.v1": "You are given a FOH proof or debug artifact. Use public docs and FOH CLI/API behavior to classify whether the blocker is docs, auth, org setup, agent config, widget, channel, runtime, or product bug. Produce a redacted improvement packet or the exact command needed to produce one. Do not ask the human to interpret logs manually unless no machine-readable artifact exists.",
|
|
40042
40113
|
"knowledge-miss.v1": "A FOH agent failed to answer a business question. Use CLI/API/docs to determine whether this is a knowledge-ingestion issue, retrieval issue, config issue, prompt/behavior issue, or runtime issue. Prefer foh knowledge query, transcript export, replay, and foh bug improve artifacts over screenshots.",
|
|
40043
40114
|
"replay-failure.v1": "You are given a FOH transcript or replay artifact. Use CLI/API/docs to replay or inspect the failed interaction, identify expected vs actual behavior, and produce a scenario-test or improvement-packet candidate."
|