@f-o-h/cli 0.1.65 → 0.1.66
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 +16 -11
- package/dist/foh.js +155 -140
- package/package.json +41 -41
package/README.md
CHANGED
|
@@ -86,19 +86,23 @@ prints the fallback URL. `auth login --web` starts browser device
|
|
|
86
86
|
authorization, opens `/cli-auth`, waits for console approval, and stores the
|
|
87
87
|
returned short-lived token. Credential auth remains available as fallback.
|
|
88
88
|
|
|
89
|
-
`foh prove` produces a compact signed proof report across auth, org context,
|
|
90
|
-
agent validation, contact phone readiness, voice provider health, widget
|
|
91
|
-
channel/embed readiness, widget smoke
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
`--
|
|
89
|
+
`foh prove` produces a compact signed proof report across auth, org context,
|
|
90
|
+
agent validation, contact phone readiness, voice provider health, widget
|
|
91
|
+
channel/embed readiness, and widget smoke. It does not run release
|
|
92
|
+
certification by default; run `foh certify run --agent <id> --profile release`
|
|
93
|
+
before publish, or pass `--include-certification` only when you intentionally
|
|
94
|
+
want the slower certification check inside proof. It is read-only by default;
|
|
95
|
+
pass `--mutation-mode ensure` or `--repair` only when you explicitly want proof
|
|
96
|
+
to ensure missing widget state. Use `--strict` in automation when holds should
|
|
97
|
+
fail the command, and `--mission voice` or `--require-phone` when a
|
|
98
|
+
voice/contact number is mandatory for the demo.
|
|
96
99
|
Use `--contact-path byon` when the proof is meant to validate a
|
|
97
100
|
customer-owned/BYON phone route; missing BYON config then reports
|
|
98
101
|
`byon_voice_number_not_configured` instead of suggesting FOH number purchase.
|
|
99
|
-
For repeated or parallel proof missions in AI-agent
|
|
100
|
-
`--proof-cache-dir .foh/proof-cache` so
|
|
101
|
-
sibling proofs reuse the same signed
|
|
102
|
+
For repeated or parallel explicit-certification proof missions in AI-agent
|
|
103
|
+
evals, pass `--include-certification --proof-cache-dir .foh/proof-cache` so
|
|
104
|
+
simulation certification runs once and sibling proofs reuse the same signed
|
|
105
|
+
certification detail.
|
|
102
106
|
|
|
103
107
|
For mass AI-agent evals and repeated demo rehearsals, keep setup on the free
|
|
104
108
|
scaffold lane:
|
|
@@ -176,8 +180,9 @@ than creating a second bronze-tier agent.
|
|
|
176
180
|
| Start | `foh start` |
|
|
177
181
|
| Setup | `foh setup --phone-mode observe --json` |
|
|
178
182
|
| Prove | `foh prove --agent <agent_id> --mission widget --json` |
|
|
183
|
+
| Certify | `foh certify run --agent <agent_id> --profile release --json` |
|
|
179
184
|
| Debug | `foh debug --out test-results/foh-cli-diag.latest.json --json` |
|
|
180
|
-
|
|
|
185
|
+
| Platform feedback | `foh bug improve --from-file <artifact.json> --json` |
|
|
181
186
|
| Publish | `foh agent publish --agent <agent_id> --json` |
|
|
182
187
|
|
|
183
188
|
For a planted knowledge-miss benchmark:
|
package/dist/foh.js
CHANGED
|
@@ -14311,12 +14311,6 @@ function registerAgentPreviewCommands(agent) {
|
|
|
14311
14311
|
}));
|
|
14312
14312
|
}
|
|
14313
14313
|
|
|
14314
|
-
// src/lib/cert-mode.ts
|
|
14315
|
-
var agentCertModeValues = ["quick", "full", "stress"];
|
|
14316
|
-
function normalizeAgentCertMode(value) {
|
|
14317
|
-
return agentCertModeValues.includes(value) ? value : "quick";
|
|
14318
|
-
}
|
|
14319
|
-
|
|
14320
14314
|
// src/lib/setup-api.ts
|
|
14321
14315
|
function resolvePublishOptions(options) {
|
|
14322
14316
|
if (typeof options === "string") return { apiUrlOverride: options };
|
|
@@ -14389,61 +14383,7 @@ async function runSetupCertifyLoop(agentId, params) {
|
|
|
14389
14383
|
}
|
|
14390
14384
|
|
|
14391
14385
|
// src/lib/agent-publish-gate.ts
|
|
14392
|
-
function boundedInt(value, params) {
|
|
14393
|
-
const parsed = Number(value);
|
|
14394
|
-
if (!Number.isFinite(parsed)) return params.fallback;
|
|
14395
|
-
return Math.max(params.min, Math.min(params.max, Math.trunc(parsed)));
|
|
14396
|
-
}
|
|
14397
|
-
function certModeFlag(mode) {
|
|
14398
|
-
if (mode === "full") return ["--full"];
|
|
14399
|
-
if (mode === "stress") return ["--stress"];
|
|
14400
|
-
return [];
|
|
14401
|
-
}
|
|
14402
|
-
function buildCertificationFailureCommands(params) {
|
|
14403
|
-
const modeFlags = certModeFlag(params.certMode);
|
|
14404
|
-
const orgFlags = params.orgId ? ["--org", params.orgId] : [];
|
|
14405
|
-
const scenarioFlags = params.topBlocker?.scenario_id ? ["--scenario-ids", params.topBlocker.scenario_id] : [];
|
|
14406
|
-
const command = [
|
|
14407
|
-
"foh",
|
|
14408
|
-
"sim",
|
|
14409
|
-
"certify-loop",
|
|
14410
|
-
"--agent",
|
|
14411
|
-
params.agentId,
|
|
14412
|
-
...orgFlags,
|
|
14413
|
-
...modeFlags,
|
|
14414
|
-
...scenarioFlags,
|
|
14415
|
-
"--json"
|
|
14416
|
-
].join(" ");
|
|
14417
|
-
return [
|
|
14418
|
-
command,
|
|
14419
|
-
`foh bug improve --from external-agent-run --file <run_dir>/run.json --json`
|
|
14420
|
-
];
|
|
14421
|
-
}
|
|
14422
|
-
function resolveCertifiedPublishOptions(opts) {
|
|
14423
|
-
const rawMode = String(opts.certMode || "quick").toLowerCase();
|
|
14424
|
-
const certMode = normalizeAgentCertMode(rawMode);
|
|
14425
|
-
if (certMode !== rawMode) {
|
|
14426
|
-
throw new FohError({
|
|
14427
|
-
step: "agent.publish",
|
|
14428
|
-
error: `Invalid cert mode: ${opts.certMode}`,
|
|
14429
|
-
remediation: "Use --cert-mode quick, full, or stress.",
|
|
14430
|
-
statusCode: 400
|
|
14431
|
-
});
|
|
14432
|
-
}
|
|
14433
|
-
return {
|
|
14434
|
-
certMode,
|
|
14435
|
-
adaptiveRuns: boundedInt(opts.adaptiveRuns, { min: 1, max: 120, fallback: 30 }),
|
|
14436
|
-
maxImprovementRounds: boundedInt(opts.maxImprovementRounds, { min: 0, max: 5, fallback: 1 }),
|
|
14437
|
-
scenarioIds: Array.isArray(opts.scenarioIds) ? opts.scenarioIds.map((item) => String(item).trim()).filter(Boolean) : String(opts.scenarioIds || "").split(",").map((item) => item.trim()).filter(Boolean)
|
|
14438
|
-
};
|
|
14439
|
-
}
|
|
14440
14386
|
async function validateCertifyAndPublishAgent(opts) {
|
|
14441
|
-
const { certMode, adaptiveRuns, maxImprovementRounds, scenarioIds } = resolveCertifiedPublishOptions({
|
|
14442
|
-
certMode: opts.certMode,
|
|
14443
|
-
adaptiveRuns: opts.adaptiveRuns,
|
|
14444
|
-
maxImprovementRounds: opts.maxImprovementRounds,
|
|
14445
|
-
scenarioIds: opts.scenarioIds
|
|
14446
|
-
});
|
|
14447
14387
|
const validation = await apiFetch(
|
|
14448
14388
|
`/v1/console/agents/${opts.agentId}/validate`,
|
|
14449
14389
|
{
|
|
@@ -14459,44 +14399,18 @@ async function validateCertifyAndPublishAgent(opts) {
|
|
|
14459
14399
|
remediation: `Run: foh agent validate --agent ${opts.agentId} to see details.`
|
|
14460
14400
|
});
|
|
14461
14401
|
}
|
|
14462
|
-
const certification = await runSetupCertifyLoop(opts.agentId, {
|
|
14463
|
-
mode: certMode,
|
|
14464
|
-
adaptiveRuns,
|
|
14465
|
-
maxImprovementRounds,
|
|
14466
|
-
scenarioIds,
|
|
14467
|
-
orgId: opts.orgId,
|
|
14468
|
-
apiUrlOverride: opts.apiUrlOverride
|
|
14469
|
-
});
|
|
14470
|
-
const certificate = certification.certificate;
|
|
14471
|
-
if (!certification.ok || !certification.overall_pass || !certificate) {
|
|
14472
|
-
const topBlocker = certificate?.blockers?.[0];
|
|
14473
|
-
const blockerLabel = topBlocker?.invariant && topBlocker?.scenario_id ? `${topBlocker.invariant} in ${topBlocker.scenario_id}` : "unknown";
|
|
14474
|
-
const nextCommands = buildCertificationFailureCommands({
|
|
14475
|
-
agentId: opts.agentId,
|
|
14476
|
-
orgId: opts.orgId,
|
|
14477
|
-
certMode,
|
|
14478
|
-
topBlocker
|
|
14479
|
-
});
|
|
14480
|
-
throw new FohError({
|
|
14481
|
-
step: "agent.publish",
|
|
14482
|
-
error: `Simulation certification failed before publish: ${certificate?.scenario_summary?.failed ?? "unknown"}/${certificate?.scenario_summary?.total ?? "unknown"} scenario(s) failed. Top blocker: ${blockerLabel}.`,
|
|
14483
|
-
remediation: [
|
|
14484
|
-
topBlocker?.suggested_fix ?? certificate?.recommendations?.[0] ?? "Fix the top simulation blocker before publishing.",
|
|
14485
|
-
`Re-run: ${nextCommands[0]}`
|
|
14486
|
-
].filter(Boolean).join(" "),
|
|
14487
|
-
reasonCode: "simulation_certification_failed",
|
|
14488
|
-
nextCommands,
|
|
14489
|
-
detail: {
|
|
14490
|
-
certification,
|
|
14491
|
-
top_blocker: topBlocker ?? null
|
|
14492
|
-
}
|
|
14493
|
-
});
|
|
14494
|
-
}
|
|
14495
14402
|
await publishAgentFromCurrentDraft(opts.agentId, {
|
|
14496
14403
|
apiUrlOverride: opts.apiUrlOverride,
|
|
14497
14404
|
orgId: opts.orgId
|
|
14498
14405
|
});
|
|
14499
|
-
return {
|
|
14406
|
+
return {
|
|
14407
|
+
validation,
|
|
14408
|
+
certification: {
|
|
14409
|
+
status: "not_run",
|
|
14410
|
+
reason_code: "publish_consumes_existing_certification_evidence"
|
|
14411
|
+
},
|
|
14412
|
+
publish: { ok: true }
|
|
14413
|
+
};
|
|
14500
14414
|
}
|
|
14501
14415
|
|
|
14502
14416
|
// src/commands/agent-validation.ts
|
|
@@ -14545,7 +14459,7 @@ function registerAgentValidationCommands(agent) {
|
|
|
14545
14459
|
format(data, { json: opts.json ?? false });
|
|
14546
14460
|
if (Array.isArray(data.issues) && data.issues.length > 0) markCommandFailed(1);
|
|
14547
14461
|
}));
|
|
14548
|
-
agent.command("publish").description("Validate
|
|
14462
|
+
agent.command("publish").description("Validate and publish an agent using existing certification evidence").requiredOption("--agent <id>", "Agent ID").option("--cert-mode <m>", "Deprecated compatibility flag; publish consumes existing certification evidence", "quick").option("--cert-scenario-ids <csv>", "Deprecated compatibility flag; run foh sim certify before publish").option("--cert-adaptive-runs <n>", "Deprecated compatibility flag; run foh sim certify before publish", "30").option("--cert-max-improvement-rounds <n>", "Deprecated compatibility flag; run foh sim certify before publish", "1").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--break-glass-reason <reason>", "Break-glass reason for publish override").option("--break-glass-incident <id>", "Break-glass incident ID for publish override").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
|
|
14549
14463
|
if (opts.breakGlassReason || opts.breakGlassIncident) {
|
|
14550
14464
|
if (!opts.breakGlassReason || !opts.breakGlassIncident) {
|
|
14551
14465
|
throw new FohError({
|
|
@@ -14568,7 +14482,7 @@ function registerAgentValidationCommands(agent) {
|
|
|
14568
14482
|
});
|
|
14569
14483
|
format({
|
|
14570
14484
|
status: "published_with_break_glass",
|
|
14571
|
-
warning: "break-glass bypassed the normal validate ->
|
|
14485
|
+
warning: "break-glass bypassed the normal validate -> publish evidence gate CLI sequence",
|
|
14572
14486
|
publish: data2
|
|
14573
14487
|
}, { json: opts.json ?? false });
|
|
14574
14488
|
return;
|
|
@@ -14583,13 +14497,8 @@ function registerAgentValidationCommands(agent) {
|
|
|
14583
14497
|
maxImprovementRounds: Number(opts.certMaxImprovementRounds)
|
|
14584
14498
|
});
|
|
14585
14499
|
format({
|
|
14586
|
-
status: "
|
|
14587
|
-
certification:
|
|
14588
|
-
mode: data.certification.mode,
|
|
14589
|
-
overall_pass: data.certification.overall_pass,
|
|
14590
|
-
attempts: data.certification.attempts?.length ?? 0,
|
|
14591
|
-
improvement_runs: data.certification.improvement_runs
|
|
14592
|
-
},
|
|
14500
|
+
status: "validated_published",
|
|
14501
|
+
certification: data.certification,
|
|
14593
14502
|
publish: data.publish
|
|
14594
14503
|
}, { json: opts.json ?? false });
|
|
14595
14504
|
}));
|
|
@@ -14810,9 +14719,9 @@ function registerAgent(program3) {
|
|
|
14810
14719
|
process.stdout.write(yaml);
|
|
14811
14720
|
return;
|
|
14812
14721
|
}
|
|
14813
|
-
const { writeFileSync:
|
|
14722
|
+
const { writeFileSync: writeFileSync12 } = await import("fs");
|
|
14814
14723
|
const outputPath = opts.output ?? "tenant.yaml";
|
|
14815
|
-
|
|
14724
|
+
writeFileSync12(
|
|
14816
14725
|
outputPath,
|
|
14817
14726
|
`# tenant.yaml - Front Of House agent manifest
|
|
14818
14727
|
# Edit this file and run: foh plan tenant.yaml
|
|
@@ -14954,7 +14863,7 @@ function registerTemplates(program3) {
|
|
|
14954
14863
|
const data = await apiFetch(`/v1/console/templates/${opts.template}`, { apiUrlOverride: opts.apiUrl });
|
|
14955
14864
|
format(data, { json: opts.json ?? false });
|
|
14956
14865
|
}));
|
|
14957
|
-
templates.command("apply").description("Create a new agent in your org from a template (clones draft config, leaves unpublished unless --publish)").requiredOption("--template <id>", "Template ID").requiredOption("--name <name>", "Name for the new agent").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--publish", "Validate
|
|
14866
|
+
templates.command("apply").description("Create a new agent in your org from a template (clones draft config, leaves unpublished unless --publish)").requiredOption("--template <id>", "Template ID").requiredOption("--name <name>", "Name for the new agent").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--publish", "Validate and publish using existing certification evidence after creating").option("--cert-mode <m>", "Deprecated compatibility flag; run foh sim certify before publish", "full").option("--cert-adaptive-runs <n>", "Deprecated compatibility flag; run foh sim certify before publish", "30").option("--cert-max-improvement-rounds <n>", "Deprecated compatibility flag; run foh sim certify before publish", "1").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
|
|
14958
14867
|
const result = await apiFetch(`/v1/console/templates/${opts.template}/apply`, {
|
|
14959
14868
|
method: "POST",
|
|
14960
14869
|
body: JSON.stringify({ name: opts.name }),
|
|
@@ -16025,9 +15934,9 @@ function buildCommonOptions(command) {
|
|
|
16025
15934
|
return command.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");
|
|
16026
15935
|
}
|
|
16027
15936
|
function registerChannel(program3) {
|
|
16028
|
-
const
|
|
16029
|
-
const whatsapp =
|
|
16030
|
-
const instagram =
|
|
15937
|
+
const channel2 = program3.command("channel").description("Manage external channel onboarding and readiness");
|
|
15938
|
+
const whatsapp = channel2.command("whatsapp").description("Manage WhatsApp channel onboarding");
|
|
15939
|
+
const instagram = channel2.command("instagram").description("Manage Instagram channel onboarding");
|
|
16031
15940
|
registerWhatsAppChannelCommands(whatsapp, buildCommonOptions);
|
|
16032
15941
|
registerInstagramChannelCommands(instagram, buildCommonOptions);
|
|
16033
15942
|
}
|
|
@@ -16260,11 +16169,11 @@ function registerVoice(program3) {
|
|
|
16260
16169
|
}
|
|
16261
16170
|
const outputPath = String(opts.out || `foh-voice-preview-${provider}-${voiceId}.mp3`).trim();
|
|
16262
16171
|
const audio = Buffer.from(await res.arrayBuffer());
|
|
16263
|
-
const { mkdirSync: mkdirSync8, writeFileSync:
|
|
16172
|
+
const { mkdirSync: mkdirSync8, writeFileSync: writeFileSync12 } = await import("fs");
|
|
16264
16173
|
const { dirname: dirname8, resolve: resolve13 } = await import("path");
|
|
16265
16174
|
const absolutePath = resolve13(outputPath);
|
|
16266
16175
|
mkdirSync8(dirname8(absolutePath), { recursive: true });
|
|
16267
|
-
|
|
16176
|
+
writeFileSync12(absolutePath, audio);
|
|
16268
16177
|
format({
|
|
16269
16178
|
status: "ok",
|
|
16270
16179
|
provider,
|
|
@@ -16285,7 +16194,7 @@ function registerVoice(program3) {
|
|
|
16285
16194
|
const allReady = providers.length > 0 && providers.every((provider) => provider?.ready === true);
|
|
16286
16195
|
if (!allReady) markCommandFailed(1);
|
|
16287
16196
|
}));
|
|
16288
|
-
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
|
|
16197
|
+
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 and publish using existing certification evidence after configuring").option("--cert-mode <m>", "Deprecated compatibility flag; run foh sim certify before publish", "full").option("--cert-adaptive-runs <n>", "Deprecated compatibility flag; run foh sim certify before publish", "30").option("--cert-max-improvement-rounds <n>", "Deprecated compatibility flag; run foh sim certify before publish", "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 () => {
|
|
16289
16198
|
const provider = String(opts.provider || "").trim().toLowerCase();
|
|
16290
16199
|
const voiceId = String(opts.voice || "").trim();
|
|
16291
16200
|
const catalog = await getSpeechCatalog(opts.apiUrl);
|
|
@@ -16330,7 +16239,7 @@ function registerVoice(program3) {
|
|
|
16330
16239
|
apiUrlOverride: opts.apiUrl
|
|
16331
16240
|
});
|
|
16332
16241
|
if (!opts.publish) {
|
|
16333
|
-
format({ status: "configured", note: "Run: foh
|
|
16242
|
+
format({ status: "configured", note: "Run: foh sim certify --agent " + opts.agent + " --full, then foh agent publish --agent " + opts.agent + " to make live." }, { json: opts.json ?? false });
|
|
16334
16243
|
return;
|
|
16335
16244
|
}
|
|
16336
16245
|
const pub = await validateCertifyAndPublishAgent({
|
|
@@ -32877,7 +32786,7 @@ var StdioServerTransport = class {
|
|
|
32877
32786
|
};
|
|
32878
32787
|
|
|
32879
32788
|
// src/lib/cli-version.ts
|
|
32880
|
-
var CLI_VERSION = "0.1.
|
|
32789
|
+
var CLI_VERSION = "0.1.66";
|
|
32881
32790
|
|
|
32882
32791
|
// src/commands/mcp-serve.ts
|
|
32883
32792
|
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
@@ -33266,7 +33175,7 @@ var TYPED_TOOL_SPECS = [
|
|
|
33266
33175
|
{
|
|
33267
33176
|
name: "foh_agent_publish",
|
|
33268
33177
|
title: "FOH Agent Publish",
|
|
33269
|
-
description: "
|
|
33178
|
+
description: "Validate and publish an agent draft using existing certification evidence.",
|
|
33270
33179
|
commandKey: "agent publish",
|
|
33271
33180
|
risk: "write",
|
|
33272
33181
|
inputSchema: {
|
|
@@ -34364,7 +34273,7 @@ function registerManifest(program3) {
|
|
|
34364
34273
|
throw e;
|
|
34365
34274
|
}
|
|
34366
34275
|
});
|
|
34367
|
-
program3.command("apply").description("Apply a tenant.yaml manifest to an agent").argument("[file]", "Path to manifest file", "tenant.yaml").option("--agent <id>", "Agent ID (overrides agent_id in manifest)").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--publish", "Validate
|
|
34276
|
+
program3.command("apply").description("Apply a tenant.yaml manifest to an agent").argument("[file]", "Path to manifest file", "tenant.yaml").option("--agent <id>", "Agent ID (overrides agent_id in manifest)").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--publish", "Validate and publish using existing certification evidence after applying").option("--cert-mode <m>", "Deprecated compatibility flag; run foh sim certify before publish", "full").option("--cert-adaptive-runs <n>", "Deprecated compatibility flag; run foh sim certify before publish", "30").option("--cert-max-improvement-rounds <n>", "Deprecated compatibility flag; run foh sim certify before publish", "1").option("--dry-run", "Show diff without making any changes (same as: foh plan)").option("--api-url <url>", "API base URL override").option("--json", "Output result as JSON").action(async (file2, opts) => {
|
|
34368
34277
|
try {
|
|
34369
34278
|
const manifest = loadManifestFile(file2);
|
|
34370
34279
|
const agentId = resolveAgentId(manifest, opts.agent);
|
|
@@ -34463,6 +34372,14 @@ function registerManifest(program3) {
|
|
|
34463
34372
|
|
|
34464
34373
|
// src/commands/setup.ts
|
|
34465
34374
|
var import_picocolors4 = __toESM(require_picocolors());
|
|
34375
|
+
|
|
34376
|
+
// src/lib/cert-mode.ts
|
|
34377
|
+
var agentCertModeValues = ["quick", "full", "stress"];
|
|
34378
|
+
function normalizeAgentCertMode(value) {
|
|
34379
|
+
return agentCertModeValues.includes(value) ? value : "quick";
|
|
34380
|
+
}
|
|
34381
|
+
|
|
34382
|
+
// src/commands/setup.ts
|
|
34466
34383
|
var SETUP_STEP_ORDER = [
|
|
34467
34384
|
"check_credentials",
|
|
34468
34385
|
"check_org_access",
|
|
@@ -35223,8 +35140,8 @@ function registerSetup(program3) {
|
|
|
35223
35140
|
}
|
|
35224
35141
|
try {
|
|
35225
35142
|
const manifest = await agentExport(resolvedAgentId, { apiUrlOverride: opts.apiUrl });
|
|
35226
|
-
const { writeFileSync:
|
|
35227
|
-
|
|
35143
|
+
const { writeFileSync: writeFileSync12 } = await import("fs");
|
|
35144
|
+
writeFileSync12(
|
|
35228
35145
|
"tenant.yaml",
|
|
35229
35146
|
`# tenant.yaml - Front Of House agent manifest
|
|
35230
35147
|
# Edit this file and run: foh plan tenant.yaml
|
|
@@ -35374,14 +35291,14 @@ function registerSim(program3) {
|
|
|
35374
35291
|
const adaptiveRuns = Math.max(1, Math.min(120, Number(opts.adaptiveRuns ?? 30)));
|
|
35375
35292
|
const journeys = opts.journeys ? String(opts.journeys).split(",").map((j) => j.trim()).filter(Boolean) : void 0;
|
|
35376
35293
|
const scenarioIds = opts.scenarioIds ? String(opts.scenarioIds).split(",").map((id) => id.trim()).filter(Boolean) : void 0;
|
|
35377
|
-
const
|
|
35294
|
+
const channel2 = ["chat", "voice", "mixed"].includes(String(opts.channel ?? "")) ? opts.channel : "mixed";
|
|
35378
35295
|
process.stderr.write(` Running ${mode} simulation certification for agent ${opts.agent}...
|
|
35379
35296
|
`);
|
|
35380
35297
|
const response = await apiFetch(
|
|
35381
35298
|
`/v1/console/agents/${opts.agent}/sim-certify`,
|
|
35382
35299
|
{
|
|
35383
35300
|
method: "POST",
|
|
35384
|
-
body: JSON.stringify({ mode, adaptive_runs: adaptiveRuns, journeys, scenario_ids: scenarioIds, channel }),
|
|
35301
|
+
body: JSON.stringify({ mode, adaptive_runs: adaptiveRuns, journeys, scenario_ids: scenarioIds, channel: channel2 }),
|
|
35385
35302
|
apiUrlOverride: opts.apiUrl
|
|
35386
35303
|
}
|
|
35387
35304
|
);
|
|
@@ -35394,8 +35311,8 @@ function registerSim(program3) {
|
|
|
35394
35311
|
}
|
|
35395
35312
|
const cert = response.certificate;
|
|
35396
35313
|
if (opts.out) {
|
|
35397
|
-
const { writeFileSync:
|
|
35398
|
-
|
|
35314
|
+
const { writeFileSync: writeFileSync12 } = await import("fs");
|
|
35315
|
+
writeFileSync12(opts.out, JSON.stringify(cert, null, 2) + "\n", "utf-8");
|
|
35399
35316
|
process.stderr.write(` Certificate written to ${opts.out}
|
|
35400
35317
|
`);
|
|
35401
35318
|
}
|
|
@@ -35419,7 +35336,7 @@ function registerSim(program3) {
|
|
|
35419
35336
|
const maxImprovementRounds = Math.max(0, Math.min(5, Number(opts.maxImprovementRounds ?? 1)));
|
|
35420
35337
|
const journeys = opts.journeys ? String(opts.journeys).split(",").map((j) => j.trim()).filter(Boolean) : void 0;
|
|
35421
35338
|
const scenarioIds = opts.scenarioIds ? String(opts.scenarioIds).split(",").map((id) => id.trim()).filter(Boolean) : void 0;
|
|
35422
|
-
const
|
|
35339
|
+
const channel2 = ["chat", "voice", "mixed"].includes(String(opts.channel ?? "")) ? opts.channel : "mixed";
|
|
35423
35340
|
process.stderr.write(` Running ${mode} certification loop for agent ${opts.agent}...
|
|
35424
35341
|
`);
|
|
35425
35342
|
const response = await apiFetch(
|
|
@@ -35432,7 +35349,7 @@ function registerSim(program3) {
|
|
|
35432
35349
|
max_improvement_rounds: maxImprovementRounds,
|
|
35433
35350
|
journeys,
|
|
35434
35351
|
scenario_ids: scenarioIds,
|
|
35435
|
-
channel
|
|
35352
|
+
channel: channel2
|
|
35436
35353
|
}),
|
|
35437
35354
|
apiUrlOverride: opts.apiUrl
|
|
35438
35355
|
}
|
|
@@ -35445,8 +35362,8 @@ function registerSim(program3) {
|
|
|
35445
35362
|
});
|
|
35446
35363
|
}
|
|
35447
35364
|
if (opts.out) {
|
|
35448
|
-
const { writeFileSync:
|
|
35449
|
-
|
|
35365
|
+
const { writeFileSync: writeFileSync12 } = await import("fs");
|
|
35366
|
+
writeFileSync12(opts.out, JSON.stringify(response.certificate, null, 2) + "\n", "utf-8");
|
|
35450
35367
|
process.stderr.write(` Final certificate written to ${opts.out}
|
|
35451
35368
|
`);
|
|
35452
35369
|
}
|
|
@@ -35481,6 +35398,95 @@ ${passIcon} Certification loop summary
|
|
|
35481
35398
|
});
|
|
35482
35399
|
}
|
|
35483
35400
|
|
|
35401
|
+
// src/commands/certify.ts
|
|
35402
|
+
var import_node_fs2 = require("node:fs");
|
|
35403
|
+
function normalizeProfile(raw) {
|
|
35404
|
+
const value = String(raw || "release").trim().toLowerCase();
|
|
35405
|
+
if (value === "smoke" || value === "release" || value === "stress") return value;
|
|
35406
|
+
throw new FohError({
|
|
35407
|
+
step: "certify.run",
|
|
35408
|
+
error: `Invalid certification profile: ${raw}`,
|
|
35409
|
+
remediation: "Use --profile smoke, release, or stress.",
|
|
35410
|
+
statusCode: 400
|
|
35411
|
+
});
|
|
35412
|
+
}
|
|
35413
|
+
function modeForProfile(profile) {
|
|
35414
|
+
if (profile === "smoke") return "quick";
|
|
35415
|
+
if (profile === "stress") return "stress";
|
|
35416
|
+
return "full";
|
|
35417
|
+
}
|
|
35418
|
+
function csv(raw) {
|
|
35419
|
+
if (!raw) return void 0;
|
|
35420
|
+
const values = String(raw).split(",").map((value) => value.trim()).filter(Boolean);
|
|
35421
|
+
return values.length > 0 ? values : void 0;
|
|
35422
|
+
}
|
|
35423
|
+
function channel(raw) {
|
|
35424
|
+
const value = String(raw || "mixed").trim().toLowerCase();
|
|
35425
|
+
if (value === "chat" || value === "voice" || value === "mixed") return value;
|
|
35426
|
+
return "mixed";
|
|
35427
|
+
}
|
|
35428
|
+
function registerCertify(program3) {
|
|
35429
|
+
const certify = program3.command("certify").description("Produce release certification evidence for an agent");
|
|
35430
|
+
certify.command("run").description("Run certification for an exact agent draft/profile and emit release evidence").requiredOption("--agent <id>", "Agent ID to certify").option("--profile <profile>", "Certification profile: smoke, release, or stress", "release").option("--adaptive-runs <n>", "Adaptive runs for release/stress profiles", "30").option("--journeys <list>", "Comma-separated journey allowlist").option("--scenario-ids <list>", "Comma-separated scenario ID allowlist").option("--channel <channel>", "Channel filter: chat, voice, or mixed", "mixed").option("--out <path>", "Write certification run JSON to this file path").option("--api-url <url>", "API base URL override").option("--json", "Output as machine-readable JSON").action(async (opts) => {
|
|
35431
|
+
try {
|
|
35432
|
+
const profile = normalizeProfile(opts.profile);
|
|
35433
|
+
const mode = modeForProfile(profile);
|
|
35434
|
+
const adaptiveRuns = Math.max(1, Math.min(120, Number(opts.adaptiveRuns ?? 30) || 30));
|
|
35435
|
+
const response = await apiFetch(
|
|
35436
|
+
`/v1/console/agents/${opts.agent}/sim-certify`,
|
|
35437
|
+
{
|
|
35438
|
+
method: "POST",
|
|
35439
|
+
body: JSON.stringify({
|
|
35440
|
+
mode,
|
|
35441
|
+
adaptive_runs: adaptiveRuns,
|
|
35442
|
+
journeys: csv(opts.journeys),
|
|
35443
|
+
scenario_ids: csv(opts.scenarioIds),
|
|
35444
|
+
channel: channel(opts.channel)
|
|
35445
|
+
}),
|
|
35446
|
+
apiUrlOverride: opts.apiUrl
|
|
35447
|
+
}
|
|
35448
|
+
);
|
|
35449
|
+
if (!response.ok || !response.certificate) {
|
|
35450
|
+
throw new FohError({
|
|
35451
|
+
step: "certify.run",
|
|
35452
|
+
error: "API did not return a certificate",
|
|
35453
|
+
remediation: "Check that the agent ID is valid and the API is reachable."
|
|
35454
|
+
});
|
|
35455
|
+
}
|
|
35456
|
+
const passed = response.certificate.overall_pass === true;
|
|
35457
|
+
const result = {
|
|
35458
|
+
schema_version: "foh_certification_run.v1",
|
|
35459
|
+
ok: passed,
|
|
35460
|
+
status: passed ? "pass" : "hold",
|
|
35461
|
+
reason_code: passed ? "certification_passed" : "certification_failed",
|
|
35462
|
+
profile,
|
|
35463
|
+
mode,
|
|
35464
|
+
certificate: response.certificate,
|
|
35465
|
+
next_commands: passed ? [`foh agent publish --agent ${opts.agent} --json`] : [`foh sim certify --agent ${opts.agent} --${mode === "quick" ? "full" : mode} --json`]
|
|
35466
|
+
};
|
|
35467
|
+
if (opts.out) {
|
|
35468
|
+
(0, import_node_fs2.writeFileSync)(opts.out, JSON.stringify(result, null, 2) + "\n", "utf-8");
|
|
35469
|
+
}
|
|
35470
|
+
if (opts.json ?? false) {
|
|
35471
|
+
format(result, { json: true });
|
|
35472
|
+
} else {
|
|
35473
|
+
const summary = response.certificate.scenario_summary;
|
|
35474
|
+
process.stderr.write(`${passed ? "PASS" : "HOLD"} certification ${profile} (${mode})`);
|
|
35475
|
+
if (summary) process.stderr.write(`: ${summary.passed}/${summary.total} scenarios passed`);
|
|
35476
|
+
process.stderr.write("\n");
|
|
35477
|
+
}
|
|
35478
|
+
if (!passed) markCommandFailed(1);
|
|
35479
|
+
} catch (error2) {
|
|
35480
|
+
if (error2 instanceof FohError) {
|
|
35481
|
+
formatError(error2, { json: opts.json ?? false });
|
|
35482
|
+
markCommandFailed(1);
|
|
35483
|
+
return;
|
|
35484
|
+
}
|
|
35485
|
+
throw error2;
|
|
35486
|
+
}
|
|
35487
|
+
});
|
|
35488
|
+
}
|
|
35489
|
+
|
|
35484
35490
|
// src/commands/conversations.ts
|
|
35485
35491
|
var import_crypto4 = require("crypto");
|
|
35486
35492
|
function registerConversations(program3) {
|
|
@@ -37423,7 +37429,7 @@ function registerBug(program3) {
|
|
|
37423
37429
|
|
|
37424
37430
|
// src/lib/proof-cache.ts
|
|
37425
37431
|
var import_node_crypto2 = require("node:crypto");
|
|
37426
|
-
var
|
|
37432
|
+
var import_node_fs3 = require("node:fs");
|
|
37427
37433
|
var import_node_path = require("node:path");
|
|
37428
37434
|
var DEFAULT_MAX_AGE_MS = 15 * 60 * 1e3;
|
|
37429
37435
|
var DEFAULT_WAIT_MS = 180 * 1e3;
|
|
@@ -37447,7 +37453,7 @@ function publicPath(filePath) {
|
|
|
37447
37453
|
}
|
|
37448
37454
|
function readFreshCache(filePath, maxAgeMs) {
|
|
37449
37455
|
try {
|
|
37450
|
-
const payload = JSON.parse((0,
|
|
37456
|
+
const payload = JSON.parse((0, import_node_fs3.readFileSync)(filePath, "utf8"));
|
|
37451
37457
|
const createdAt = Date.parse(String(payload.created_at || ""));
|
|
37452
37458
|
if (!Number.isFinite(createdAt)) return null;
|
|
37453
37459
|
if (Date.now() - createdAt > maxAgeMs) return null;
|
|
@@ -37457,8 +37463,8 @@ function readFreshCache(filePath, maxAgeMs) {
|
|
|
37457
37463
|
}
|
|
37458
37464
|
}
|
|
37459
37465
|
function writeCache(filePath, value) {
|
|
37460
|
-
(0,
|
|
37461
|
-
(0,
|
|
37466
|
+
(0, import_node_fs3.mkdirSync)((0, import_node_path.dirname)(filePath), { recursive: true });
|
|
37467
|
+
(0, import_node_fs3.writeFileSync)(filePath, `${JSON.stringify({
|
|
37462
37468
|
schema_version: "foh_proof_cache_entry.v1",
|
|
37463
37469
|
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
37464
37470
|
value
|
|
@@ -37484,7 +37490,7 @@ async function withProofCache(options, run) {
|
|
|
37484
37490
|
const maxAgeMs = options.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
|
|
37485
37491
|
const waitMs = options.waitMs ?? Number(process.env.FOH_PROOF_CACHE_WAIT_MS || DEFAULT_WAIT_MS);
|
|
37486
37492
|
const pollMs = options.pollMs ?? DEFAULT_POLL_MS;
|
|
37487
|
-
(0,
|
|
37493
|
+
(0, import_node_fs3.mkdirSync)(resolvedDir, { recursive: true });
|
|
37488
37494
|
const existing = readFreshCache(cachePath, maxAgeMs);
|
|
37489
37495
|
if (existing) {
|
|
37490
37496
|
return {
|
|
@@ -37494,7 +37500,7 @@ async function withProofCache(options, run) {
|
|
|
37494
37500
|
}
|
|
37495
37501
|
let lockOwner = false;
|
|
37496
37502
|
try {
|
|
37497
|
-
(0,
|
|
37503
|
+
(0, import_node_fs3.mkdirSync)(lockPath);
|
|
37498
37504
|
lockOwner = true;
|
|
37499
37505
|
} catch {
|
|
37500
37506
|
const started = Date.now();
|
|
@@ -37517,7 +37523,7 @@ async function withProofCache(options, run) {
|
|
|
37517
37523
|
metadata: { hit: false, key, cache_path: publicPath(cachePath), waited_ms: 0 }
|
|
37518
37524
|
};
|
|
37519
37525
|
} finally {
|
|
37520
|
-
if (lockOwner) (0,
|
|
37526
|
+
if (lockOwner) (0, import_node_fs3.rmSync)(lockPath, { recursive: true, force: true });
|
|
37521
37527
|
}
|
|
37522
37528
|
}
|
|
37523
37529
|
|
|
@@ -37548,8 +37554,8 @@ function hasBlockingChecks(checks) {
|
|
|
37548
37554
|
}
|
|
37549
37555
|
function publicKeyFromEnsureResponse(response) {
|
|
37550
37556
|
const record2 = response && typeof response === "object" ? response : {};
|
|
37551
|
-
const
|
|
37552
|
-
const publicKey =
|
|
37557
|
+
const channel2 = record2.channel && typeof record2.channel === "object" ? record2.channel : {};
|
|
37558
|
+
const publicKey = channel2.public_key ?? record2.widget_public_key ?? record2.public_key;
|
|
37553
37559
|
return typeof publicKey === "string" && publicKey.trim() ? publicKey.trim() : void 0;
|
|
37554
37560
|
}
|
|
37555
37561
|
function publicKeyFromEmbedResponse(response) {
|
|
@@ -37616,7 +37622,7 @@ function isProviderCapacityBlocked(onboarding) {
|
|
|
37616
37622
|
return /maximum number of subaccounts|subaccount limit|reserve[- ]number pool|reserve pool exhausted|global safety limit/.test(message);
|
|
37617
37623
|
}
|
|
37618
37624
|
function registerProve(program3) {
|
|
37619
|
-
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", "
|
|
37625
|
+
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("--include-certification", "Run explicit simulation certification check (slow)").option("--cert-mode <m>", "Simulation cert mode when --include-certification is set: quick, full, stress", "quick").option("--cert-adaptive-runs <n>", "Adaptive runs for full/stress certification when included", "30").option("--cert-max-improvement-rounds <n>", "Max prompt improvement rounds in included 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", "Deprecated compatibility flag; certification is skipped unless --include-certification is set").option("--skip-smoke", "Skip widget runtime smoke check").option("--skip-voice-health", "Skip realtime voice provider health check").option("--proof-cache-dir <path>", "Optional local proof cache directory for shared certification results").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 () => {
|
|
37620
37626
|
const checks = [];
|
|
37621
37627
|
const mission = normalizeMission(opts.mission);
|
|
37622
37628
|
const contactPath = normalizeContactPath(opts.contactPath);
|
|
@@ -37872,7 +37878,14 @@ function registerProve(program3) {
|
|
|
37872
37878
|
}
|
|
37873
37879
|
}
|
|
37874
37880
|
if (opts.skipCert) {
|
|
37875
|
-
checks.push(skipped("simulation_certification", "operator_skipped", "Skipped by --skip-cert.", `foh sim certify
|
|
37881
|
+
checks.push(skipped("simulation_certification", "operator_skipped", "Skipped by --skip-cert.", `foh sim certify --agent ${ctx.agentId} --full --json`));
|
|
37882
|
+
} else if (!opts.includeCertification) {
|
|
37883
|
+
checks.push(skipped(
|
|
37884
|
+
"simulation_certification",
|
|
37885
|
+
"certification_explicitly_required",
|
|
37886
|
+
"Runtime proof does not run release certification by default.",
|
|
37887
|
+
`foh sim certify --agent ${ctx.agentId} --full --json`
|
|
37888
|
+
));
|
|
37876
37889
|
} else {
|
|
37877
37890
|
try {
|
|
37878
37891
|
const certMode = normalizeAgentCertMode(opts.certMode);
|
|
@@ -37904,7 +37917,7 @@ function registerProve(program3) {
|
|
|
37904
37917
|
proof_cache: cached2.metadata
|
|
37905
37918
|
};
|
|
37906
37919
|
if (!loop.overall_pass) {
|
|
37907
|
-
checks.push(hold("simulation_certification", "simulation_certification_failed", "Simulation certification did not pass.", `foh sim certify
|
|
37920
|
+
checks.push(hold("simulation_certification", "simulation_certification_failed", "Simulation certification did not pass.", `foh sim certify --agent ${agentId} --${certMode === "quick" ? "full" : certMode} --json`, loopWithCache));
|
|
37908
37921
|
} else {
|
|
37909
37922
|
checks.push(pass("simulation_certification", "Simulation certification passed.", {
|
|
37910
37923
|
mode: loop.mode,
|
|
@@ -37915,7 +37928,7 @@ function registerProve(program3) {
|
|
|
37915
37928
|
}));
|
|
37916
37929
|
}
|
|
37917
37930
|
} catch (error2) {
|
|
37918
|
-
checks.push(fail("simulation_certification", "simulation_certification_failed", error2, `foh sim certify
|
|
37931
|
+
checks.push(fail("simulation_certification", "simulation_certification_failed", error2, `foh sim certify --agent ${ctx.agentId} --full --json`));
|
|
37919
37932
|
}
|
|
37920
37933
|
}
|
|
37921
37934
|
} else {
|
|
@@ -40401,7 +40414,7 @@ async function executeExternalAgentExecutorPlan(plan, options = {}) {
|
|
|
40401
40414
|
var DEFAULT_PROMPT_VERSION = "blank-setup.v1";
|
|
40402
40415
|
var DEFAULT_BATCH_MODELS = "openai/codex,anthropic/claude,cursor/agent";
|
|
40403
40416
|
var PROMPTS = {
|
|
40404
|
-
"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. Mass evals reuse existing eval state: run `npx --yes @f-o-h/cli@latest org status --json` and `npx --yes @f-o-h/cli@latest agent list --json` before trying to create a fresh agent; if an existing eval agent is present, configure and prove that agent instead of creating a second bronze-tier agent. 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
|
|
40417
|
+
"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. Mass evals reuse existing eval state: run `npx --yes @f-o-h/cli@latest org status --json` and `npx --yes @f-o-h/cli@latest agent list --json` before trying to create a fresh agent; if an existing eval agent is present, configure and prove that agent instead of creating a second bronze-tier agent. 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, voice proof, and one explicit `foh sim certify --agent <id> --full --json` before publish. `foh prove` does not run release certification by default; only pass `--include-certification --proof-cache-dir .foh/proof-cache` when an explicit combined proof/certification run is required. 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.",
|
|
40405
40418
|
"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.",
|
|
40406
40419
|
"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.",
|
|
40407
40420
|
"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."
|
|
@@ -40978,6 +40991,7 @@ var CLI_MISSION_EXAMPLES = [
|
|
|
40978
40991
|
{ mission: "Start", command: "foh start", description: "guided setup and next action selector" },
|
|
40979
40992
|
{ mission: "Setup", command: "foh setup --phone-mode observe --json", description: "create or update agent, widget, voice config, and proof scaffold" },
|
|
40980
40993
|
{ mission: "Prove", command: "foh prove --agent <agent_id> --mission widget --json", description: "produce a machine-readable proof report" },
|
|
40994
|
+
{ mission: "Certify", command: "foh certify run --agent <agent_id> --profile release --json", description: "produce release evidence before publish" },
|
|
40981
40995
|
{ mission: "Debug", command: "foh debug --out test-results/foh-cli-diag.latest.json --json", description: "collect auth/org/API diagnostics" },
|
|
40982
40996
|
{ mission: "Improve", command: "foh bug improve --from-file <artifact.json> --json", description: "convert a failure artifact into a redacted improvement packet" },
|
|
40983
40997
|
{ mission: "Publish", command: "foh agent publish --agent <agent_id> --json", description: "publish after proof gates pass" }
|
|
@@ -41077,6 +41091,7 @@ registerOps(program2);
|
|
|
41077
41091
|
registerSetup(program2);
|
|
41078
41092
|
registerManifest(program2);
|
|
41079
41093
|
registerSim(program2);
|
|
41094
|
+
registerCertify(program2);
|
|
41080
41095
|
registerDiag(program2);
|
|
41081
41096
|
registerBug(program2);
|
|
41082
41097
|
registerProve(program2);
|
package/package.json
CHANGED
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@f-o-h/cli",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "FOH CLI - AI-operator provisioning tool for Front Of House",
|
|
5
|
-
"license": "UNLICENSED",
|
|
6
|
-
"bin": {
|
|
7
|
-
"foh": "dist/foh.js"
|
|
8
|
-
},
|
|
9
|
-
"main": "dist/foh.js",
|
|
10
|
-
"files": [
|
|
11
|
-
"dist/",
|
|
12
|
-
"examples/",
|
|
13
|
-
"schemas/",
|
|
14
|
-
"README.md",
|
|
15
|
-
"package.json"
|
|
16
|
-
],
|
|
17
|
-
"publishConfig": {
|
|
18
|
-
"access": "public"
|
|
19
|
-
},
|
|
20
|
-
"engines": {
|
|
21
|
-
"node": ">=18"
|
|
22
|
-
},
|
|
23
|
-
"scripts": {
|
|
24
|
-
"build": "node build.mjs",
|
|
25
|
-
"test": "vitest run",
|
|
26
|
-
"typecheck": "tsc --noEmit"
|
|
27
|
-
},
|
|
28
|
-
"dependencies": {
|
|
29
|
-
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
30
|
-
"commander": "^12.1.0",
|
|
31
|
-
"js-yaml": "^4.1.1",
|
|
32
|
-
"picocolors": "^1.1.1",
|
|
33
|
-
"zod": "^4.3.6"
|
|
34
|
-
},
|
|
35
|
-
"devDependencies": {
|
|
36
|
-
"@types/js-yaml": "^4.0.9",
|
|
37
|
-
"@types/node": "^22.0.0",
|
|
38
|
-
"esbuild": "^0.24.0",
|
|
39
|
-
"vitest": "^2.0.0"
|
|
40
|
-
}
|
|
41
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@f-o-h/cli",
|
|
3
|
+
"version": "0.1.66",
|
|
4
|
+
"description": "FOH CLI - AI-operator provisioning tool for Front Of House",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"bin": {
|
|
7
|
+
"foh": "dist/foh.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "dist/foh.js",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist/",
|
|
12
|
+
"examples/",
|
|
13
|
+
"schemas/",
|
|
14
|
+
"README.md",
|
|
15
|
+
"package.json"
|
|
16
|
+
],
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=18"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "node build.mjs",
|
|
25
|
+
"test": "vitest run",
|
|
26
|
+
"typecheck": "tsc --noEmit"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
30
|
+
"commander": "^12.1.0",
|
|
31
|
+
"js-yaml": "^4.1.1",
|
|
32
|
+
"picocolors": "^1.1.1",
|
|
33
|
+
"zod": "^4.3.6"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/js-yaml": "^4.0.9",
|
|
37
|
+
"@types/node": "^22.0.0",
|
|
38
|
+
"esbuild": "^0.24.0",
|
|
39
|
+
"vitest": "^2.0.0"
|
|
40
|
+
}
|
|
41
|
+
}
|