@f-o-h/cli 0.1.75 → 0.1.77

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 CHANGED
@@ -10050,10 +10050,21 @@ var SERVICE_TOKEN_ENV = "FOH_SERVICE_TOKEN";
10050
10050
  var ORG_ID_ENV = "FOH_ORG_ID";
10051
10051
  var API_URL_ENV = "FOH_API_URL";
10052
10052
  var TOKEN_EXPIRES_AT_ENV = "FOH_TOKEN_EXPIRES_AT";
10053
+ var SERVICE_TOKEN_ENV_ALIAS = "FOH_EXTERNAL_AGENT_EVAL_TOKEN";
10054
+ var ORG_ID_ENV_ALIAS = "FOH_EXTERNAL_AGENT_EVAL_ORG_ID";
10055
+ var API_URL_ENV_ALIAS = "FOH_EXTERNAL_AGENT_EVAL_API_URL";
10056
+ var TOKEN_EXPIRES_AT_ENV_ALIAS = "FOH_EXTERNAL_AGENT_EVAL_TOKEN_EXPIRES_AT";
10053
10057
  function normalizeEnv(value) {
10054
10058
  const normalized = String(value ?? "").trim();
10055
10059
  return normalized ? normalized : void 0;
10056
10060
  }
10061
+ function firstDefinedEnv(env, keys) {
10062
+ for (const key of keys) {
10063
+ const value = normalizeEnv(env[key]);
10064
+ if (value) return value;
10065
+ }
10066
+ return void 0;
10067
+ }
10057
10068
  function decodeBase64UrlJson(value) {
10058
10069
  try {
10059
10070
  const padded = value.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(value.length / 4) * 4, "=");
@@ -10083,25 +10094,25 @@ function credentialExpiresSoon(expiresAt, minimumTtlMs = 0, now = Date.now()) {
10083
10094
  return remainingMs !== null && remainingMs <= minimumTtlMs;
10084
10095
  }
10085
10096
  function credentialsFromEnv(apiUrlOverride, env = process.env) {
10086
- const token = normalizeEnv(env[SERVICE_TOKEN_ENV]);
10097
+ const token = firstDefinedEnv(env, [SERVICE_TOKEN_ENV, SERVICE_TOKEN_ENV_ALIAS]);
10087
10098
  if (!token) return void 0;
10088
- const apiUrl = resolveApiUrlOverride(apiUrlOverride) ?? normalizeEnv(env[API_URL_ENV]) ?? DEFAULT_FOH_API_URL;
10089
- const orgId = normalizeEnv(env[ORG_ID_ENV]);
10099
+ const apiUrl = resolveApiUrlOverride(apiUrlOverride) ?? firstDefinedEnv(env, [API_URL_ENV, API_URL_ENV_ALIAS]) ?? DEFAULT_FOH_API_URL;
10100
+ const orgId = firstDefinedEnv(env, [ORG_ID_ENV, ORG_ID_ENV_ALIAS]);
10090
10101
  return {
10091
10102
  apiUrl,
10092
10103
  token,
10093
- expiresAt: normalizeEnv(env[TOKEN_EXPIRES_AT_ENV]) ?? expiryFromJwt(token) ?? fallbackExpiry(),
10104
+ expiresAt: firstDefinedEnv(env, [TOKEN_EXPIRES_AT_ENV, TOKEN_EXPIRES_AT_ENV_ALIAS]) ?? expiryFromJwt(token) ?? fallbackExpiry(),
10094
10105
  ...isUsableOrgId(orgId) ? { orgId } : {}
10095
10106
  };
10096
10107
  }
10097
10108
  function hasEnvCredentials(env = process.env) {
10098
- return Boolean(normalizeEnv(env[SERVICE_TOKEN_ENV]));
10109
+ return Boolean(firstDefinedEnv(env, [SERVICE_TOKEN_ENV, SERVICE_TOKEN_ENV_ALIAS]));
10099
10110
  }
10100
10111
  function loadCredentials(apiUrlOverride) {
10101
10112
  const envCreds = credentialsFromEnv(apiUrlOverride);
10102
10113
  if (envCreds) {
10103
10114
  if (credentialExpiresSoon(envCreds.expiresAt)) {
10104
- throw new Error("Token expired. Refresh FOH_SERVICE_TOKEN/FOH_TOKEN_EXPIRES_AT or run: foh auth login");
10115
+ throw new Error("Token expired. Refresh FOH_SERVICE_TOKEN/FOH_TOKEN_EXPIRES_AT (or FOH_EXTERNAL_AGENT_EVAL_TOKEN/FOH_EXTERNAL_AGENT_EVAL_TOKEN_EXPIRES_AT) or run: foh auth login");
10105
10116
  }
10106
10117
  return envCreds;
10107
10118
  }
@@ -14461,56 +14472,66 @@ async function validateAgentLocalDraftShape(agentId, opts = {}) {
14461
14472
  warnings: ["local-only draft-shape validation was used"]
14462
14473
  });
14463
14474
  }
14464
- function registerAgentValidationCommands(agent) {
14465
- agent.command("validate").description("Validate agent config \u2014 exits 1 if issues found").requiredOption("--agent <id>", "Agent ID").option("--local-only", "Run local draft-shape validation without calling remote validate endpoint").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
14466
- const data = opts.localOnly ? await validateAgentLocalDraftShape(opts.agent, { apiUrlOverride: opts.apiUrl }) : await validateAgentRemoteOnly(opts.agent, { apiUrlOverride: opts.apiUrl });
14467
- format(data, { json: opts.json ?? false });
14468
- if (Array.isArray(data.issues) && data.issues.length > 0) markCommandFailed(1);
14469
- }));
14470
- 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 () => {
14471
- if (opts.breakGlassReason || opts.breakGlassIncident) {
14472
- if (!opts.breakGlassReason || !opts.breakGlassIncident) {
14473
- throw new FohError({
14474
- step: "agent.publish",
14475
- error: "Both --break-glass-reason and --break-glass-incident are required together",
14476
- remediation: "Provide both break-glass fields, or omit both for normal publish.",
14477
- statusCode: 400
14478
- });
14479
- }
14480
- const data2 = await apiFetch(`/v1/console/agents/${opts.agent}/publish`, {
14481
- method: "POST",
14482
- body: JSON.stringify({
14483
- break_glass: {
14484
- reason: String(opts.breakGlassReason),
14485
- incident_id: String(opts.breakGlassIncident)
14486
- }
14487
- }),
14488
- orgId: opts.org,
14489
- apiUrlOverride: opts.apiUrl
14475
+ async function publishAgentAction(opts) {
14476
+ if (opts.breakGlassReason || opts.breakGlassIncident) {
14477
+ if (!opts.breakGlassReason || !opts.breakGlassIncident) {
14478
+ throw new FohError({
14479
+ step: "agent.publish",
14480
+ error: "Both --break-glass-reason and --break-glass-incident are required together",
14481
+ remediation: "Provide both break-glass fields, or omit both for normal publish.",
14482
+ statusCode: 400
14490
14483
  });
14491
- format({
14492
- status: "published_with_break_glass",
14493
- warning: "break-glass bypassed the normal validate -> publish evidence gate CLI sequence",
14494
- publish: data2
14495
- }, { json: opts.json ?? false });
14496
- return;
14497
14484
  }
14498
- const data = await validateCertifyAndPublishAgent({
14499
- agentId: opts.agent,
14485
+ const data2 = await apiFetch(`/v1/console/agents/${opts.agent}/publish`, {
14486
+ method: "POST",
14487
+ body: JSON.stringify({
14488
+ break_glass: {
14489
+ reason: String(opts.breakGlassReason),
14490
+ incident_id: String(opts.breakGlassIncident)
14491
+ }
14492
+ }),
14500
14493
  orgId: opts.org,
14501
- apiUrlOverride: opts.apiUrl,
14502
- certMode: opts.certMode,
14503
- scenarioIds: opts.certScenarioIds,
14504
- adaptiveRuns: Number(opts.certAdaptiveRuns),
14505
- maxImprovementRounds: Number(opts.certMaxImprovementRounds)
14494
+ apiUrlOverride: opts.apiUrl
14506
14495
  });
14507
14496
  format({
14508
- status: "validated_published",
14509
- certification: data.certification,
14510
- publish: data.publish
14497
+ status: "published_with_break_glass",
14498
+ warning: "break-glass bypassed the normal validate -> publish evidence gate CLI sequence",
14499
+ publish: data2
14511
14500
  }, { json: opts.json ?? false });
14501
+ return;
14502
+ }
14503
+ const data = await validateCertifyAndPublishAgent({
14504
+ agentId: opts.agent,
14505
+ orgId: opts.org,
14506
+ apiUrlOverride: opts.apiUrl,
14507
+ certMode: opts.certMode ?? "quick",
14508
+ scenarioIds: opts.certScenarioIds,
14509
+ adaptiveRuns: Number(opts.certAdaptiveRuns ?? "30"),
14510
+ maxImprovementRounds: Number(opts.certMaxImprovementRounds ?? "1")
14511
+ });
14512
+ format({
14513
+ status: "validated_published",
14514
+ certification: data.certification,
14515
+ publish: data.publish
14516
+ }, { json: opts.json ?? false });
14517
+ }
14518
+ function registerAgentPublishCommand(parent, options = {}) {
14519
+ const publish = parent.command("publish").description(options.publicAlias ? "Publish an agent using existing certification evidence" : "Validate and publish an agent using existing certification evidence").requiredOption("--agent <id>", "Agent ID").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON");
14520
+ if (!options.publicAlias) {
14521
+ publish.option("--cert-mode <m>", "Deprecated compatibility flag; publish consumes existing certification evidence", "quick").option("--cert-scenario-ids <csv>", "Deprecated compatibility flag; run foh certify before publish").option("--cert-adaptive-runs <n>", "Deprecated compatibility flag; run foh certify before publish", "30").option("--cert-max-improvement-rounds <n>", "Deprecated compatibility flag; run foh certify before publish", "1").option("--break-glass-reason <reason>", "Break-glass reason for publish override").option("--break-glass-incident <id>", "Break-glass incident ID for publish override");
14522
+ }
14523
+ publish.action(async (opts) => withCommandErrorHandling(async () => {
14524
+ await publishAgentAction(opts);
14512
14525
  }));
14513
14526
  }
14527
+ function registerAgentValidationCommands(agent) {
14528
+ agent.command("validate").description("Validate agent config \u2014 exits 1 if issues found").requiredOption("--agent <id>", "Agent ID").option("--local-only", "Run local draft-shape validation without calling remote validate endpoint").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
14529
+ const data = opts.localOnly ? await validateAgentLocalDraftShape(opts.agent, { apiUrlOverride: opts.apiUrl }) : await validateAgentRemoteOnly(opts.agent, { apiUrlOverride: opts.apiUrl });
14530
+ format(data, { json: opts.json ?? false });
14531
+ if (Array.isArray(data.issues) && data.issues.length > 0) markCommandFailed(1);
14532
+ }));
14533
+ registerAgentPublishCommand(agent);
14534
+ }
14514
14535
 
14515
14536
  // src/commands/agent.ts
14516
14537
  function registerAgent(program3) {
@@ -14851,6 +14872,72 @@ async function agentExport(agentId, opts = {}) {
14851
14872
  return manifest;
14852
14873
  }
14853
14874
 
14875
+ // src/lib/template-proof-plan.ts
14876
+ var ACCEPTED_NO_SPEND_VOICE_HOLD_REASONS = [
14877
+ "voice_contact_expected_no_spend_hold",
14878
+ "contact_phone_missing",
14879
+ "voice_contact_phone_provisioning_failed",
14880
+ "paid_resource_blocked_by_spend_policy",
14881
+ "byon_voice_number_not_configured",
14882
+ "provider_capacity_blocked"
14883
+ ];
14884
+ function missionForGate(gate) {
14885
+ const command = String(gate.command || "");
14886
+ const channels = gate.channels ?? [];
14887
+ if (command.includes("foh certify run")) return "certification";
14888
+ if (command.includes("--mission widget") || channels.includes("widget")) return "widget";
14889
+ if (command.includes("--mission voice") || channels.includes("voice")) return "voice";
14890
+ return "other";
14891
+ }
14892
+ function concreteCommand(gate, agentId) {
14893
+ const command = String(gate.command || "").trim();
14894
+ return command.replaceAll("<agent_id>", agentId);
14895
+ }
14896
+ function buildTemplateProofPlan(templateResponse, agentId) {
14897
+ const template = templateResponse.template ?? {};
14898
+ const contract = template.template_contract ?? {};
14899
+ const templateId = String(contract.template_id || template.id || "");
14900
+ const templateSlug = String(contract.slug || templateId || "unknown-template");
14901
+ const templateName = String(contract.name || template.name || templateSlug);
14902
+ const useCase = String(contract.use_case || "unknown");
14903
+ const proofGates = Array.isArray(contract.proof_gates) ? contract.proof_gates : [];
14904
+ const cloneMode = String(contract.fork_policy?.clone_mode || "unknown");
14905
+ const commands = proofGates.filter((gate) => typeof gate.command === "string" && gate.command.trim().length > 0).map((gate) => {
14906
+ const mission = missionForGate(gate);
14907
+ const command = {
14908
+ gate_id: String(gate.id || mission),
14909
+ mission,
14910
+ command: concreteCommand(gate, agentId),
14911
+ expected_status: mission === "voice" ? "pass_or_expected_hold" : "pass",
14912
+ pass_criteria: Array.isArray(gate.pass_criteria) ? gate.pass_criteria.map(String) : []
14913
+ };
14914
+ if (mission === "voice") {
14915
+ command.environment = { FOH_CLI_SPEND_POLICY: "no_spend" };
14916
+ command.accepted_hold_reason_codes = ACCEPTED_NO_SPEND_VOICE_HOLD_REASONS;
14917
+ }
14918
+ return command;
14919
+ });
14920
+ return {
14921
+ schema_version: "template_proof_plan.v1",
14922
+ template_id: templateId,
14923
+ template_slug: templateSlug,
14924
+ template_name: templateName,
14925
+ use_case: useCase,
14926
+ agent_id: agentId,
14927
+ outcome: {
14928
+ primary_outcome: String(contract.outcome_contract?.primary_outcome || ""),
14929
+ required_lead_fields: (contract.outcome_contract?.required_lead_fields ?? []).map(String)
14930
+ },
14931
+ lineage: {
14932
+ source_template_id: templateId,
14933
+ source_template_slug: templateSlug,
14934
+ clone_mode: cloneMode
14935
+ },
14936
+ commands,
14937
+ next_commands: commands.map((entry) => entry.command)
14938
+ };
14939
+ }
14940
+
14854
14941
  // src/commands/templates.ts
14855
14942
  var VALID_CATEGORIES = ["buyer", "seller", "landlord", "commercial"];
14856
14943
  function registerTemplates(program3) {
@@ -14871,6 +14958,41 @@ function registerTemplates(program3) {
14871
14958
  const data = await apiFetch(`/v1/console/templates/${opts.template}`, { apiUrlOverride: opts.apiUrl });
14872
14959
  format(data, { json: opts.json ?? false });
14873
14960
  }));
14961
+ templates.command("capabilities").description("Show machine-readable template capabilities for planning (delivery, tools, voice options, proof gates)").requiredOption("--template <id>", "Template ID").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
14962
+ const data = await apiFetch(`/v1/console/templates/${opts.template}/capabilities`, { apiUrlOverride: opts.apiUrl });
14963
+ format(data, { json: opts.json ?? false });
14964
+ }));
14965
+ templates.command("recipe").description("Show machine-readable recipe bundle for coding-agent customization (editable paths, guardrails, verification commands)").requiredOption("--template <id>", "Template ID").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
14966
+ const data = await apiFetch(`/v1/console/templates/${opts.template}/recipe`, {
14967
+ orgId: opts.org,
14968
+ apiUrlOverride: opts.apiUrl
14969
+ });
14970
+ format(data, { json: opts.json ?? false });
14971
+ }));
14972
+ templates.command("select").description("Select candidate templates from a business requirement brief").requiredOption("--brief <json|@file>", "Business requirement brief JSON or @path").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
14973
+ const brief = await parseJsonOption(opts.brief, "--brief");
14974
+ const data = await apiFetch("/v1/console/templates/select", {
14975
+ method: "POST",
14976
+ body: JSON.stringify(brief),
14977
+ apiUrlOverride: opts.apiUrl
14978
+ });
14979
+ format(data, { json: opts.json ?? false });
14980
+ }));
14981
+ templates.command("fork").description("Fork a system template into a customer template with explicit lineage and contract diff").requiredOption("--template <id>", "Source template ID").requiredOption("--name <name>", "Forked template name").option("--description <text>", "Fork description").option("--category <c>", "Fork category override").option("--contract <json|@file>", "Optional contract patch JSON or @path").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
14982
+ const payload = {
14983
+ name: opts.name
14984
+ };
14985
+ if (opts.description) payload.description = opts.description;
14986
+ if (opts.category) payload.category = opts.category;
14987
+ if (opts.contract) payload.contract = await parseJsonOption(opts.contract, "--contract");
14988
+ const data = await apiFetch(`/v1/console/templates/${opts.template}/fork`, {
14989
+ method: "POST",
14990
+ body: JSON.stringify(payload),
14991
+ orgId: opts.org,
14992
+ apiUrlOverride: opts.apiUrl
14993
+ });
14994
+ format(data, { json: opts.json ?? false });
14995
+ }));
14874
14996
  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 () => {
14875
14997
  const result = await apiFetch(`/v1/console/templates/${opts.template}/apply`, {
14876
14998
  method: "POST",
@@ -14902,6 +15024,73 @@ function registerTemplates(program3) {
14902
15024
  });
14903
15025
  format({ status: "created_and_published", agent: result.agent, publish: published }, { json: opts.json ?? false });
14904
15026
  }));
15027
+ templates.command("proof-plan").description("Expand a template proof contract into concrete prove/certify commands for an applied agent").requiredOption("--template <id>", "Template ID").requiredOption("--agent <id>", "Applied agent ID").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
15028
+ const data = await apiFetch(`/v1/console/templates/${opts.template}/proof-plan`, {
15029
+ method: "POST",
15030
+ body: JSON.stringify({ agent_id: opts.agent }),
15031
+ apiUrlOverride: opts.apiUrl
15032
+ });
15033
+ if (data?.plan) {
15034
+ format(data.plan, { json: opts.json ?? false });
15035
+ return;
15036
+ }
15037
+ const fallbackTemplate = await apiFetch(`/v1/console/templates/${opts.template}`, { apiUrlOverride: opts.apiUrl });
15038
+ const fallbackPlan = buildTemplateProofPlan(fallbackTemplate, opts.agent);
15039
+ format(fallbackPlan, { json: opts.json ?? false });
15040
+ }));
15041
+ templates.command("prove").description("Template-aware prove helper that derives missions from template proof gates (no manual mission inference)").requiredOption("--template <id>", "Template ID").requiredOption("--agent <id>", "Applied agent ID").option("--mode <mode>", "Prove mode: quick (widget+voice) or full (includes certification)", "quick").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
15042
+ const mode = String(opts.mode || "quick").toLowerCase();
15043
+ if (mode !== "quick" && mode !== "full") {
15044
+ throw new FohError({
15045
+ step: "templates.prove",
15046
+ error: `Invalid mode: ${opts.mode}`,
15047
+ remediation: "Use --mode quick or --mode full"
15048
+ });
15049
+ }
15050
+ const data = await apiFetch(`/v1/console/templates/${opts.template}/proof-plan`, {
15051
+ method: "POST",
15052
+ body: JSON.stringify({ agent_id: opts.agent }),
15053
+ apiUrlOverride: opts.apiUrl
15054
+ });
15055
+ const plan = data.plan;
15056
+ const commands = Array.isArray(plan?.commands) ? plan.commands : [];
15057
+ const selected = commands.filter((entry) => mode === "full" ? true : entry?.mission === "widget" || entry?.mission === "voice");
15058
+ format({
15059
+ schema_version: "template_prove_helper.v1",
15060
+ template_id: plan?.template_id ?? opts.template,
15061
+ agent_id: opts.agent,
15062
+ mode,
15063
+ commands: selected,
15064
+ next_commands: selected.map((entry) => entry.command)
15065
+ }, { json: opts.json ?? false });
15066
+ }));
15067
+ templates.command("publish").description("Template-aware publish helper that validates evidence before agent publish").requiredOption("--template <id>", "Template ID").requiredOption("--agent <id>", "Applied agent ID").requiredOption("--evidence <json|@file>", "Template publish evidence JSON or @path").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--dry-run", "Only validate evidence and return publish decision").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
15068
+ const evidence = await parseJsonOption(opts.evidence, "--evidence");
15069
+ const helper = await apiFetch(`/v1/console/templates/${opts.template}/publish-helper`, {
15070
+ method: "POST",
15071
+ body: JSON.stringify({
15072
+ agent_id: opts.agent,
15073
+ evidence
15074
+ }),
15075
+ orgId: opts.org,
15076
+ apiUrlOverride: opts.apiUrl
15077
+ });
15078
+ if (opts.dryRun) {
15079
+ format(helper, { json: opts.json ?? false });
15080
+ return;
15081
+ }
15082
+ const publish = await apiFetch(`/v1/console/agents/${opts.agent}/publish`, {
15083
+ method: "POST",
15084
+ body: JSON.stringify({}),
15085
+ orgId: opts.org,
15086
+ apiUrlOverride: opts.apiUrl
15087
+ });
15088
+ format({
15089
+ status: "template_publish_completed",
15090
+ helper,
15091
+ publish
15092
+ }, { json: opts.json ?? false });
15093
+ }));
14905
15094
  }
14906
15095
 
14907
15096
  // src/commands/widget.ts
@@ -15469,7 +15658,7 @@ async function resolveWhatsAppSetupInputs(opts) {
15469
15658
  if (!appSecret) process.stdout.write("App Secret is required.\n");
15470
15659
  }
15471
15660
  if (opts.audioEnabled === void 0 && process.stdin.isTTY && process.stdout.isTTY && !opts.json) {
15472
- const answer = await promptLine("Enable WhatsApp audio fallback path? [Y/n]", { allowEmpty: true });
15661
+ const answer = await promptLine("Enable inbound WhatsApp audio fallback auto-reply? [Y/n]", { allowEmpty: true });
15473
15662
  audioEnabled = isAffirmative(answer, true);
15474
15663
  }
15475
15664
  return {
@@ -15692,7 +15881,7 @@ function registerWhatsAppChannelCommands(whatsapp, addCommonOptions) {
15692
15881
  }));
15693
15882
  addCommonOptions(
15694
15883
  whatsapp.command("setup").description("Run guided WhatsApp setup and readiness checks")
15695
- ).option("--phone-number-id <id>", "Meta WhatsApp phone number id").option("--access-token <token>", "Meta access token").option("--verify-token <token>", "Webhook verify token").option("--app-secret <secret>", "Meta app secret for signature verification").option("--agent-id <id>", "Agent to bind to the WhatsApp channel").option("--audio-enabled <bool>", "Enable WhatsApp audio fallback path (true/false)").option("--generate-verify-token", "Generate webhook verify token automatically when missing").option("--wizard", "Run guided setup wizard prompts").action(async (opts) => withCommandErrorHandling(async () => {
15884
+ ).option("--phone-number-id <id>", "Meta WhatsApp phone number id").option("--access-token <token>", "Meta access token").option("--verify-token <token>", "Webhook verify token").option("--app-secret <secret>", "Meta app secret for signature verification").option("--agent-id <id>", "Agent to bind to the WhatsApp channel").option("--audio-enabled <bool>", "Enable inbound WhatsApp audio fallback auto-reply (true/false)").option("--generate-verify-token", "Generate webhook verify token automatically when missing").option("--wizard", "Run guided setup wizard prompts").action(async (opts) => withCommandErrorHandling(async () => {
15696
15885
  const inputs = await resolveWhatsAppSetupInputs(opts);
15697
15886
  const creds = loadCredentials(opts.apiUrl);
15698
15887
  const apiBaseUrl = creds.apiUrl;
@@ -15785,7 +15974,7 @@ function registerWhatsAppChannelCommands(whatsapp, addCommonOptions) {
15785
15974
  }));
15786
15975
  addCommonOptions(
15787
15976
  whatsapp.command("connect").description("Connect WhatsApp channel credentials")
15788
- ).requiredOption("--phone-number-id <id>", "Meta WhatsApp phone number id").requiredOption("--access-token <token>", "Meta access token").option("--agent-id <id>", "Agent to bind to the WhatsApp channel").option("--verify-token <token>", "Webhook verify token").option("--app-secret <secret>", "Meta app secret for signature verification").option("--audio-enabled <bool>", "Enable WhatsApp audio fallback path (true/false)", "true").action(async (opts) => withCommandErrorHandling(async () => {
15977
+ ).requiredOption("--phone-number-id <id>", "Meta WhatsApp phone number id").requiredOption("--access-token <token>", "Meta access token").option("--agent-id <id>", "Agent to bind to the WhatsApp channel").option("--verify-token <token>", "Webhook verify token").option("--app-secret <secret>", "Meta app secret for signature verification").option("--audio-enabled <bool>", "Enable inbound WhatsApp audio fallback auto-reply (true/false)", "true").action(async (opts) => withCommandErrorHandling(async () => {
15789
15978
  const data = await apiFetch("/v1/console/channels/whatsapp/connect", {
15790
15979
  method: "POST",
15791
15980
  body: JSON.stringify({
@@ -32798,7 +32987,7 @@ var StdioServerTransport = class {
32798
32987
  };
32799
32988
 
32800
32989
  // src/lib/cli-version.ts
32801
- var CLI_VERSION = "0.1.75";
32990
+ var CLI_VERSION = "0.1.77";
32802
32991
 
32803
32992
  // src/commands/mcp-serve.ts
32804
32993
  var DEFAULT_TIMEOUT_MS = 12e4;
@@ -36463,7 +36652,27 @@ function registerTest(program3) {
36463
36652
  }));
36464
36653
  }
36465
36654
 
36466
- // src/commands/ops.ts
36655
+ // src/commands/ops-agency-setup.ts
36656
+ function registerOpsAgencySetupCommands(reporting) {
36657
+ reporting.command("agency-setup-preview").description("Build a fail-closed setup/release/customer-evidence preview from agency name and official source URL").requiredOption("--agency-name <name>", "Estate agency trading name").option("--source-url <url>", "Official branch/contact/source URL").option("--branch-location <value>", "Branch/location this setup represents").option("--tools <csv>", "Requested tool surfaces", "widget,voice,email,callback,calendar,crm,webhook,whatsapp").option("--target-mode <value>", "Target exposure mode", "founder_assisted").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 () => {
36658
+ const body = {
36659
+ agency_name: String(opts.agencyName),
36660
+ requested_tool_surface: parseCsvOption(opts.tools) ?? [],
36661
+ target_exposure_mode: String(opts.targetMode)
36662
+ };
36663
+ if (opts.sourceUrl) body.source_url = String(opts.sourceUrl);
36664
+ if (opts.branchLocation) body.branch_location = String(opts.branchLocation);
36665
+ const data = await apiFetch("/v1/console/agency-setup/preview", {
36666
+ method: "POST",
36667
+ body: JSON.stringify(body),
36668
+ orgId: opts.org,
36669
+ apiUrlOverride: opts.apiUrl
36670
+ });
36671
+ format(data, { json: opts.json ?? false });
36672
+ }));
36673
+ }
36674
+
36675
+ // src/commands/ops-diagnostics.ts
36467
36676
  function parseClientErrorSource(raw) {
36468
36677
  if (!raw) return void 0;
36469
36678
  const source = String(raw).trim().toLowerCase();
@@ -36477,8 +36686,44 @@ function parseClientErrorSource(raw) {
36477
36686
  statusCode: 400
36478
36687
  });
36479
36688
  }
36480
- function registerOps(program3) {
36481
- const ops = program3.command("ops").description("Operational/reporting workflows (incidents, recommendations, traces, evidence)");
36689
+ function registerOpsIntegrationCommands(ops) {
36690
+ const integrations = ops.command("integrations").description("Integration diagnostics and health checks");
36691
+ integrations.command("health-check").description("Run integration health-check probes for an agent").requiredOption("--agent <id>", "Agent ID").option("--integration-id <id>", "Optional integration filter: crm_webhook|calendar_sync|automation_webhook").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
36692
+ const body = {};
36693
+ if (opts.integrationId) body.integration_id = String(opts.integrationId);
36694
+ const data = await apiFetch(`/v1/console/agents/${opts.agent}/integrations/health-check`, {
36695
+ method: "POST",
36696
+ body: JSON.stringify(body),
36697
+ apiUrlOverride: opts.apiUrl
36698
+ });
36699
+ format(data, { json: opts.json ?? false });
36700
+ }));
36701
+ }
36702
+ function registerOpsClientErrorCommands(ops) {
36703
+ const clientErrors = ops.command("client-errors").description("Client error telemetry and ingestion");
36704
+ clientErrors.command("report").description("Submit a client error packet").requiredOption("--message <text>", "Error message").option("--stack <text>", "Error stack trace").option("--component-stack <text>", "React component stack").option("--url <url>", "Document URL").option("--route <route>", "Application route").option("--user-agent <text>", "Browser user-agent override").option("--source <value>", "Error source: window|react|unhandledrejection").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 () => {
36705
+ const source = parseClientErrorSource(opts.source);
36706
+ const body = {
36707
+ message: String(opts.message)
36708
+ };
36709
+ if (opts.stack) body.stack = String(opts.stack);
36710
+ if (opts.componentStack) body.componentStack = String(opts.componentStack);
36711
+ if (opts.url) body.url = String(opts.url);
36712
+ if (opts.route) body.route = String(opts.route);
36713
+ if (opts.userAgent) body.userAgent = String(opts.userAgent);
36714
+ if (source) body.source = source;
36715
+ const data = await apiFetch("/v1/console/client-errors", {
36716
+ method: "POST",
36717
+ body: JSON.stringify(body),
36718
+ orgId: opts.org,
36719
+ apiUrlOverride: opts.apiUrl
36720
+ });
36721
+ format(data, { json: opts.json ?? false });
36722
+ }));
36723
+ }
36724
+
36725
+ // src/commands/ops-incidents.ts
36726
+ function registerOpsIncidentCommands(ops) {
36482
36727
  const incidents = ops.command("incidents").description("Incident loop operations");
36483
36728
  incidents.command("list").description("List incidents for current org").option("--state <value>", "Incident state filter").option("--agent <id>", "Agent ID filter").option("--limit <n>", "Max rows (1-200)", "50").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 () => {
36484
36729
  const params = new URLSearchParams();
@@ -36530,6 +36775,8 @@ function registerOps(program3) {
36530
36775
  });
36531
36776
  format(data, { json: opts.json ?? false });
36532
36777
  }));
36778
+ }
36779
+ function registerOpsRecommendationCommands(ops) {
36533
36780
  const recommendations = ops.command("recommendations").description("Recommendation queue operations");
36534
36781
  recommendations.command("list").description("List recommendations for current org").option("--status <value>", "Recommendation status").option("--limit <n>", "Max rows (1-200)", "50").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 () => {
36535
36782
  const params = new URLSearchParams();
@@ -36579,84 +36826,10 @@ function registerOps(program3) {
36579
36826
  });
36580
36827
  format(data, { json: opts.json ?? false });
36581
36828
  }));
36582
- const traces = ops.command("traces").description("Observability trace operations");
36583
- traces.command("list").description("List traces for current org").option("--agent <id>", "Agent ID filter").option("--conversation <id>", "Conversation ID filter").option("--limit <n>", "Max rows (1-200)", "100").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 () => {
36584
- const params = new URLSearchParams();
36585
- if (opts.agent) params.set("agentId", String(opts.agent));
36586
- if (opts.conversation) params.set("conversationId", String(opts.conversation));
36587
- params.set("limit", String(parsePositiveInt(opts.limit, 100, 1, 200)));
36588
- const data = await apiFetch(withQuery("/v1/console/traces", params), {
36589
- orgId: opts.org,
36590
- apiUrlOverride: opts.apiUrl
36591
- });
36592
- format(data, { json: opts.json ?? false });
36593
- }));
36594
- traces.command("replay").description("Replay a trace input in dry-run mode").requiredOption("--trace <id>", "Trace ID").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
36595
- const data = await apiFetch(`/v1/console/traces/${opts.trace}/replay`, {
36596
- method: "POST",
36597
- body: JSON.stringify({}),
36598
- orgId: opts.org,
36599
- apiUrlOverride: opts.apiUrl
36600
- });
36601
- format(data, { json: opts.json ?? false });
36602
- }));
36603
- const testingOs = ops.command("testing-os").description("Testing OS lane and coverage diagnostics");
36604
- testingOs.command("lane-health").description("Show Testing OS lane-health summary").requiredOption("--agent <id>", "Agent ID").option("--window-days <n>", "Lookback window (1-90 days)", "7").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
36605
- const params = new URLSearchParams({
36606
- windowDays: String(parsePositiveInt(opts.windowDays, 7, 1, 90))
36607
- });
36608
- const data = await apiFetch(withQuery(`/v1/console/agents/${opts.agent}/testing-os/lane-health`, params), {
36609
- apiUrlOverride: opts.apiUrl
36610
- });
36611
- format(data, { json: opts.json ?? false });
36612
- }));
36613
- testingOs.command("fingerprints").description("Show Testing OS failure fingerprints").requiredOption("--agent <id>", "Agent ID").option("--window-days <n>", "Lookback window (1-120 days)", "30").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
36614
- const params = new URLSearchParams({
36615
- windowDays: String(parsePositiveInt(opts.windowDays, 30, 1, 120))
36616
- });
36617
- const data = await apiFetch(withQuery(`/v1/console/agents/${opts.agent}/testing-os/fingerprints`, params), {
36618
- apiUrlOverride: opts.apiUrl
36619
- });
36620
- format(data, { json: opts.json ?? false });
36621
- }));
36622
- testingOs.command("coverage-matrix").description("Show Testing OS coverage matrix baseline/current delta").requiredOption("--agent <id>", "Agent ID").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
36623
- const data = await apiFetch(`/v1/console/agents/${opts.agent}/testing-os/coverage-matrix`, {
36624
- apiUrlOverride: opts.apiUrl
36625
- });
36626
- format(data, { json: opts.json ?? false });
36627
- }));
36628
- const integrations = ops.command("integrations").description("Integration diagnostics and health checks");
36629
- integrations.command("health-check").description("Run integration health-check probes for an agent").requiredOption("--agent <id>", "Agent ID").option("--integration-id <id>", "Optional integration filter: crm_webhook|calendar_sync|automation_webhook").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
36630
- const body = {};
36631
- if (opts.integrationId) body.integration_id = String(opts.integrationId);
36632
- const data = await apiFetch(`/v1/console/agents/${opts.agent}/integrations/health-check`, {
36633
- method: "POST",
36634
- body: JSON.stringify(body),
36635
- apiUrlOverride: opts.apiUrl
36636
- });
36637
- format(data, { json: opts.json ?? false });
36638
- }));
36639
- const clientErrors = ops.command("client-errors").description("Client error telemetry and ingestion");
36640
- clientErrors.command("report").description("Submit a client error packet").requiredOption("--message <text>", "Error message").option("--stack <text>", "Error stack trace").option("--component-stack <text>", "React component stack").option("--url <url>", "Document URL").option("--route <route>", "Application route").option("--user-agent <text>", "Browser user-agent override").option("--source <value>", "Error source: window|react|unhandledrejection").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 () => {
36641
- const source = parseClientErrorSource(opts.source);
36642
- const body = {
36643
- message: String(opts.message)
36644
- };
36645
- if (opts.stack) body.stack = String(opts.stack);
36646
- if (opts.componentStack) body.componentStack = String(opts.componentStack);
36647
- if (opts.url) body.url = String(opts.url);
36648
- if (opts.route) body.route = String(opts.route);
36649
- if (opts.userAgent) body.userAgent = String(opts.userAgent);
36650
- if (source) body.source = source;
36651
- const data = await apiFetch("/v1/console/client-errors", {
36652
- method: "POST",
36653
- body: JSON.stringify(body),
36654
- orgId: opts.org,
36655
- apiUrlOverride: opts.apiUrl
36656
- });
36657
- format(data, { json: opts.json ?? false });
36658
- }));
36659
- const reporting = ops.command("reporting").description("Reporting aggregates and release evidence");
36829
+ }
36830
+
36831
+ // src/commands/ops-reporting.ts
36832
+ function registerOpsReportingCommands(reporting) {
36660
36833
  reporting.command("kb-usage").description("Show KB usage aggregate report").requiredOption("--agent <id>", "Agent ID").option("--days <n>", "Lookback window in days", "7").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 () => {
36661
36834
  const params = new URLSearchParams({
36662
36835
  agentId: String(opts.agent),
@@ -36702,6 +36875,36 @@ function registerOps(program3) {
36702
36875
  });
36703
36876
  format(data, { json: opts.json ?? false });
36704
36877
  }));
36878
+ reporting.command("launch-packet").description("Show latest customer launch packet and grouped go-live blockers").option("--environment <value>", "Environment filter").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 () => {
36879
+ const params = new URLSearchParams();
36880
+ if (opts.environment) params.set("environment", String(opts.environment));
36881
+ const data = await apiFetch(withQuery("/v1/console/release-launch-packet", params), {
36882
+ orgId: opts.org,
36883
+ apiUrlOverride: opts.apiUrl
36884
+ });
36885
+ format(data, { json: opts.json ?? false });
36886
+ }));
36887
+ reporting.command("launch-packet-skeletons").description("Generate customer launch evidence skeletons from latest launch packet blockers").option("--environment <value>", "Environment filter").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 () => {
36888
+ const params = new URLSearchParams();
36889
+ if (opts.environment) params.set("environment", String(opts.environment));
36890
+ const data = await apiFetch(withQuery("/v1/console/release-launch-packet/evidence-skeletons", params), {
36891
+ orgId: opts.org,
36892
+ apiUrlOverride: opts.apiUrl
36893
+ });
36894
+ format(data, { json: opts.json ?? false });
36895
+ }));
36896
+ reporting.command("launch-packet-evidence-check").description("Dry-run customer launch evidence before any release gate can consume it").requiredOption("--evidence <json|@file>", 'Evidence JSON object, usually { "evidence": [{ "id": "...", "payload": {...} }] }').option("--apply", "Request apply mode; currently fails closed until evidence storage contract exists").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 () => {
36897
+ const parsed = await parseJsonOption(opts.evidence, "--evidence");
36898
+ const body = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? { ...parsed } : { evidence: parsed };
36899
+ if (opts.apply) body.apply = true;
36900
+ const data = await apiFetch("/v1/console/release-launch-packet/evidence-dry-run", {
36901
+ method: "POST",
36902
+ body: JSON.stringify(body),
36903
+ orgId: opts.org,
36904
+ apiUrlOverride: opts.apiUrl
36905
+ });
36906
+ format(data, { json: opts.json ?? false });
36907
+ }));
36705
36908
  reporting.command("release-scorecard").description("Show deterministic release scorecard for an agent").requiredOption("--agent <id>", "Agent ID").option("--limit <n>", "Number of runs to include", "10").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
36706
36909
  const limit = Math.max(1, Math.min(100, Number(opts.limit || 10) || 10));
36707
36910
  const data = await apiFetch(`/v1/console/agents/${opts.agent}/release-scorecard?limit=${limit}`, {
@@ -36747,6 +36950,74 @@ function registerOps(program3) {
36747
36950
  }));
36748
36951
  }
36749
36952
 
36953
+ // src/commands/ops-testing-os.ts
36954
+ function registerOpsTestingOsCommands(ops) {
36955
+ const testingOs = ops.command("testing-os").description("Testing OS lane and coverage diagnostics");
36956
+ testingOs.command("lane-health").description("Show Testing OS lane-health summary").requiredOption("--agent <id>", "Agent ID").option("--window-days <n>", "Lookback window (1-90 days)", "7").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
36957
+ const params = new URLSearchParams({
36958
+ windowDays: String(parsePositiveInt(opts.windowDays, 7, 1, 90))
36959
+ });
36960
+ const data = await apiFetch(withQuery(`/v1/console/agents/${opts.agent}/testing-os/lane-health`, params), {
36961
+ apiUrlOverride: opts.apiUrl
36962
+ });
36963
+ format(data, { json: opts.json ?? false });
36964
+ }));
36965
+ testingOs.command("fingerprints").description("Show Testing OS failure fingerprints").requiredOption("--agent <id>", "Agent ID").option("--window-days <n>", "Lookback window (1-120 days)", "30").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
36966
+ const params = new URLSearchParams({
36967
+ windowDays: String(parsePositiveInt(opts.windowDays, 30, 1, 120))
36968
+ });
36969
+ const data = await apiFetch(withQuery(`/v1/console/agents/${opts.agent}/testing-os/fingerprints`, params), {
36970
+ apiUrlOverride: opts.apiUrl
36971
+ });
36972
+ format(data, { json: opts.json ?? false });
36973
+ }));
36974
+ testingOs.command("coverage-matrix").description("Show Testing OS coverage matrix baseline/current delta").requiredOption("--agent <id>", "Agent ID").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
36975
+ const data = await apiFetch(`/v1/console/agents/${opts.agent}/testing-os/coverage-matrix`, {
36976
+ apiUrlOverride: opts.apiUrl
36977
+ });
36978
+ format(data, { json: opts.json ?? false });
36979
+ }));
36980
+ }
36981
+
36982
+ // src/commands/ops-traces.ts
36983
+ function registerOpsTraceCommands(ops) {
36984
+ const traces = ops.command("traces").description("Observability trace operations");
36985
+ traces.command("list").description("List traces for current org").option("--agent <id>", "Agent ID filter").option("--conversation <id>", "Conversation ID filter").option("--limit <n>", "Max rows (1-200)", "100").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 () => {
36986
+ const params = new URLSearchParams();
36987
+ if (opts.agent) params.set("agentId", String(opts.agent));
36988
+ if (opts.conversation) params.set("conversationId", String(opts.conversation));
36989
+ params.set("limit", String(parsePositiveInt(opts.limit, 100, 1, 200)));
36990
+ const data = await apiFetch(withQuery("/v1/console/traces", params), {
36991
+ orgId: opts.org,
36992
+ apiUrlOverride: opts.apiUrl
36993
+ });
36994
+ format(data, { json: opts.json ?? false });
36995
+ }));
36996
+ traces.command("replay").description("Replay a trace input in dry-run mode").requiredOption("--trace <id>", "Trace ID").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
36997
+ const data = await apiFetch(`/v1/console/traces/${opts.trace}/replay`, {
36998
+ method: "POST",
36999
+ body: JSON.stringify({}),
37000
+ orgId: opts.org,
37001
+ apiUrlOverride: opts.apiUrl
37002
+ });
37003
+ format(data, { json: opts.json ?? false });
37004
+ }));
37005
+ }
37006
+
37007
+ // src/commands/ops.ts
37008
+ function registerOps(program3) {
37009
+ const ops = program3.command("ops").description("Operational/reporting workflows (incidents, recommendations, traces, evidence)");
37010
+ registerOpsIncidentCommands(ops);
37011
+ registerOpsRecommendationCommands(ops);
37012
+ registerOpsTraceCommands(ops);
37013
+ registerOpsTestingOsCommands(ops);
37014
+ registerOpsIntegrationCommands(ops);
37015
+ registerOpsClientErrorCommands(ops);
37016
+ const reporting = ops.command("reporting").description("Reporting aggregates and release evidence");
37017
+ registerOpsReportingCommands(reporting);
37018
+ registerOpsAgencySetupCommands(reporting);
37019
+ }
37020
+
36750
37021
  // src/commands/diag.ts
36751
37022
  function toErrorRecord(error2) {
36752
37023
  if (error2 instanceof Error) {
@@ -41121,6 +41392,11 @@ var DEFAULT_PROMPT_VERSION = "blank-setup.v1";
41121
41392
  var DEFAULT_BATCH_MODELS = "openai/codex,anthropic/claude,cursor/agent";
41122
41393
  var PROMPTS = {
41123
41394
  "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 certify run --agent <id> --profile release --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.",
41395
+ "real-estate-buyer-enquiry.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 ...`. Do not assume access to the private source repository. Mission: configure a buyer-enquiry real-estate agent (UK Buyer Qualification preferred), prove widget behavior, and prove voice behavior in no-spend mode. Required path: 1) verify auth/org scope and reuse existing eval org/agent where possible; 2) select/apply buyer template; 3) configure widget + voice; 4) run widget smoke and `foh certify run --profile release`; 5) run voice proof and treat no-spend contact holds as expected only when all non-contact gates pass. Do not buy numbers; if spend policy blocks purchase, record `paid_resource_blocked_by_spend_policy` and continue no-spend proof path. Final artifact must include: selected template id/slug, commands executed, pass/hold/fail per gate, reason codes, docs_pages_used, and next fix commands.",
41396
+ "real-estate-seller-valuation.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 ...`. Do not assume access to the private source repository. Mission: configure a seller-valuation real-estate agent, prove valuation lead capture on widget and voice, and keep strict no-spend behavior. Required path: 1) verify auth/org scope and reuse existing eval state; 2) select/apply seller valuation template; 3) configure widget + voice; 4) run widget smoke and release certification; 5) run voice proof and classify holds. Do not claim precise valuation output without approved tooling; safe fallback or handoff must remain explicit. Do not buy numbers. Final artifact must include: selected template id/slug, lead fields observed, tool/action behavior, reason codes, docs_pages_used, and next fix commands.",
41397
+ "real-estate-viewing-and-qa.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 ...`. Do not assume access to the private source repository. Mission: configure a viewing-booking/property-QA real-estate agent and prove booking-safe behavior under no-spend policy. Required path: 1) verify auth/org scope and reuse existing eval state; 2) select/apply viewing template; 3) configure widget + voice; 4) run widget smoke, release certification, and voice proof; 5) explicitly check no-duplicate-side-effect behavior and safe fallback/handoff when booking tooling or contact path is unavailable. Do not buy numbers. Final artifact must include: selected template id/slug, booking/fallback evidence, reason codes, docs_pages_used, and next fix commands.",
41398
+ "real-estate-landlord-enquiry.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 ...`. Do not assume access to the private source repository. Mission: configure a landlord-enquiry real-estate agent and prove lead capture + safe escalation under no-spend constraints. Required path: 1) verify auth/org scope and reuse existing eval state; 2) select/apply landlord template; 3) configure widget + voice; 4) run widget smoke, release certification, and voice proof; 5) ensure handoff/escalation remains reason-coded and no-silent-drop. Do not buy numbers. Final artifact must include: selected template id/slug, lead/action/handoff evidence summary, reason codes, docs_pages_used, and next fix commands.",
41399
+ "arbitrary-agency-setup-release.v1": "Go to https://frontofhouse.okii.uk. Use only public docs, public API docs, the deployed production API, 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 ...`, local source, private repo scripts, or unpublished commands. Do not assume access to the private source repository. Mission: prove whether an arbitrary estate agency can be set up or accurately held using the public launch evidence workflow. Required path: 1) verify CLI version and auth/org scope; 2) run `foh ops reporting agency-setup-preview` with the supplied agency name, official source URL, branch location, requested tool surface, and target mode; 3) run `foh ops reporting launch-packet --json`; 4) run `foh ops reporting launch-packet-skeletons --json`; 5) run `foh ops reporting launch-packet-evidence-check --json` only with redacted synthetic or customer-provided evidence, never raw credentials or secrets; 6) classify the outcome as pass, expected hold, or product failure. No-spend boundary: do not buy phone numbers, provision paid resources, scrape private systems, bypass credential holds, or claim live/customer-ready/production unless the launch packet explicitly allows it. Expected holds such as missing customer credentials, missing behavior approval, missing live voice validation, or missing production signoff are acceptable only when the commands expose clear reason codes and next actions. Final artifact must include commands executed, docs_pages_used, manual_intervention_count, CLI/API version readback, launch mode observed, reason codes, evidence artifacts created, and exact next commands. 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.",
41124
41400
  "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.",
41125
41401
  "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.",
41126
41402
  "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."
@@ -41251,11 +41527,38 @@ function knowledgeMissPromptContext(knowledgeQuestion, expectedAnswer) {
41251
41527
  "- Do not patch around the miss manually; produce the smallest redacted artifact that explains whether the fix belongs to docs, ingestion, retrieval, config, or runtime."
41252
41528
  ].join("\n");
41253
41529
  }
41530
+ function agencySetupPromptContext(context = {}) {
41531
+ const agencyName = String(context.agencyName || "").trim();
41532
+ const sourceUrl = String(context.sourceUrl || "").trim();
41533
+ const branchLocation = String(context.branchLocation || "").trim();
41534
+ const targetMode = String(context.targetMode || "").trim();
41535
+ if (!agencyName && !sourceUrl && !branchLocation && !targetMode) return "";
41536
+ const previewArgs = [
41537
+ "ops reporting agency-setup-preview",
41538
+ agencyName ? `--agency-name ${quoteArg(agencyName)}` : "--agency-name <agency-name>",
41539
+ sourceUrl ? `--source-url ${quoteArg(sourceUrl)}` : "--source-url <official-source-url>",
41540
+ branchLocation ? `--branch-location ${quoteArg(branchLocation)}` : "--branch-location <branch-location>",
41541
+ targetMode ? `--target-mode ${quoteArg(targetMode)}` : "--target-mode founder_assisted",
41542
+ "--json"
41543
+ ].join(" ");
41544
+ return [
41545
+ "",
41546
+ "Arbitrary-agency setup context:",
41547
+ ...agencyName ? [`- Agency name: ${agencyName}`] : [],
41548
+ ...sourceUrl ? [`- Official source URL: ${sourceUrl}`] : [],
41549
+ ...branchLocation ? [`- Branch/location: ${branchLocation}`] : [],
41550
+ ...targetMode ? [`- Target mode: ${targetMode}`] : [],
41551
+ `- Start with: npx --yes @f-o-h/cli@latest ${previewArgs}`,
41552
+ "- Then run launch-packet, launch-packet-skeletons, and launch-packet-evidence-check using public CLI commands only.",
41553
+ "- Treat missing customer credentials or approvals as expected holds only if the CLI gives machine-readable reason codes and next actions."
41554
+ ].join("\n");
41555
+ }
41254
41556
  function writePrompt(runDir, promptVersion, context = {}) {
41255
41557
  const prompt = [
41256
41558
  PROMPTS[promptVersion] ?? PROMPTS[DEFAULT_PROMPT_VERSION],
41257
41559
  replayPromptContext(context.replayFile),
41258
- knowledgeMissPromptContext(context.knowledgeQuestion, context.expectedAnswer)
41560
+ knowledgeMissPromptContext(context.knowledgeQuestion, context.expectedAnswer),
41561
+ agencySetupPromptContext(context)
41259
41562
  ].join("");
41260
41563
  const path2 = (0, import_path19.join)(runDir, "prompt.txt");
41261
41564
  (0, import_fs20.writeFileSync)(path2, `${prompt}
@@ -41327,7 +41630,11 @@ function buildRunArtifact(input) {
41327
41630
  context: {
41328
41631
  replay_file: input.session.replay_file ?? null,
41329
41632
  knowledge_question: input.session.knowledge_question ?? null,
41330
- expected_answer: input.session.expected_answer ?? null
41633
+ expected_answer: input.session.expected_answer ?? null,
41634
+ agency_name: input.session.agency_name ?? null,
41635
+ source_url: input.session.source_url ?? null,
41636
+ branch_location: input.session.branch_location ?? null,
41637
+ target_mode: input.session.target_mode ?? null
41331
41638
  },
41332
41639
  artifacts: {
41333
41640
  terminal_transcript: null,
@@ -41349,19 +41656,31 @@ function buildRunArtifact(input) {
41349
41656
  function registerEval(program3) {
41350
41657
  const evalCommand = program3.command("eval").description("Run or summarize external-agent evaluation workflows");
41351
41658
  const external = evalCommand.command("external-agent").description("Capture clean external coding-agent setup attempts");
41352
- external.command("batch").description("Create a deterministic multi-model external-agent batch plan").option("--models <list>", "Comma-separated provider/model list", DEFAULT_BATCH_MODELS).option("--prompt-version <version>", "Prompt version", DEFAULT_PROMPT_VERSION).option("--replay-file <path>", "Local transcript/replay artifact to seed replay-failure prompts").option("--knowledge-question <text>", "Question to seed knowledge-miss prompts").option("--expected-answer <text>", "Expected answer or missing fact for planted knowledge-miss prompts").option("--workspace-type <type>", "Workspace type label", "clean-no-repo").option("--agent-shell <name>", "Agent shell label", "vscode-terminal").option("--out-dir <path>", "Batch output directory").option("--json", "Output as JSON").action(async (opts) => {
41659
+ external.command("batch").description("Create a deterministic multi-model external-agent batch plan").option("--models <list>", "Comma-separated provider/model list", DEFAULT_BATCH_MODELS).option("--prompt-version <version>", "Prompt version", DEFAULT_PROMPT_VERSION).option("--replay-file <path>", "Local transcript/replay artifact to seed replay-failure prompts").option("--knowledge-question <text>", "Question to seed knowledge-miss prompts").option("--expected-answer <text>", "Expected answer or missing fact for planted knowledge-miss prompts").option("--agency-name <name>", "Agency name to seed arbitrary-agency setup prompts").option("--source-url <url>", "Official source URL to seed arbitrary-agency setup prompts").option("--branch-location <value>", "Branch/location to seed arbitrary-agency setup prompts").option("--target-mode <mode>", "Target launch/exposure mode for arbitrary-agency setup prompts").option("--workspace-type <type>", "Workspace type label", "clean-no-repo").option("--agent-shell <name>", "Agent shell label", "vscode-terminal").option("--out-dir <path>", "Batch output directory").option("--json", "Output as JSON").action(async (opts) => {
41353
41660
  const promptVersion = String(opts.promptVersion || DEFAULT_PROMPT_VERSION);
41354
41661
  const batchDir = (0, import_path19.resolve)(String(opts.outDir || defaultBatchDir(promptVersion)));
41355
41662
  const replayFile = opts.replayFile ? (0, import_path19.resolve)(String(opts.replayFile)) : void 0;
41356
41663
  const knowledgeQuestion = opts.knowledgeQuestion ? String(opts.knowledgeQuestion) : void 0;
41357
41664
  const expectedAnswer = opts.expectedAnswer ? String(opts.expectedAnswer) : void 0;
41665
+ const agencyName = opts.agencyName ? String(opts.agencyName) : void 0;
41666
+ const sourceUrl = opts.sourceUrl ? String(opts.sourceUrl) : void 0;
41667
+ const branchLocation = opts.branchLocation ? String(opts.branchLocation) : void 0;
41668
+ const targetMode = opts.targetMode ? String(opts.targetMode) : void 0;
41358
41669
  const models = parseModelList(String(opts.models || DEFAULT_BATCH_MODELS));
41359
41670
  (0, import_fs20.mkdirSync)(batchDir, { recursive: true });
41360
41671
  const runs2 = models.map((model, index) => {
41361
41672
  const runId = `${String(index + 1).padStart(2, "0")}-${safeSlug(model.provider)}-${safeSlug(model.name)}`;
41362
41673
  const runDir = (0, import_path19.join)(batchDir, runId);
41363
41674
  (0, import_fs20.mkdirSync)(runDir, { recursive: true });
41364
- const promptPath = writePrompt(runDir, promptVersion, { replayFile, knowledgeQuestion, expectedAnswer });
41675
+ const promptPath = writePrompt(runDir, promptVersion, {
41676
+ replayFile,
41677
+ knowledgeQuestion,
41678
+ expectedAnswer,
41679
+ agencyName,
41680
+ sourceUrl,
41681
+ branchLocation,
41682
+ targetMode
41683
+ });
41365
41684
  const commandArgs = [
41366
41685
  "eval",
41367
41686
  "external-agent",
@@ -41382,6 +41701,10 @@ function registerEval(program3) {
41382
41701
  if (replayFile) commandArgs.push("--replay-file", replayFile);
41383
41702
  if (knowledgeQuestion) commandArgs.push("--knowledge-question", knowledgeQuestion);
41384
41703
  if (expectedAnswer) commandArgs.push("--expected-answer", expectedAnswer);
41704
+ if (agencyName) commandArgs.push("--agency-name", agencyName);
41705
+ if (sourceUrl) commandArgs.push("--source-url", sourceUrl);
41706
+ if (branchLocation) commandArgs.push("--branch-location", branchLocation);
41707
+ if (targetMode) commandArgs.push("--target-mode", targetMode);
41385
41708
  return {
41386
41709
  run_id: runId,
41387
41710
  model_provider: model.provider,
@@ -41401,6 +41724,10 @@ function registerEval(program3) {
41401
41724
  replay_file: replayFile ?? null,
41402
41725
  knowledge_question: knowledgeQuestion ?? null,
41403
41726
  expected_answer: expectedAnswer ?? null,
41727
+ agency_name: agencyName ?? null,
41728
+ source_url: sourceUrl ?? null,
41729
+ branch_location: branchLocation ?? null,
41730
+ target_mode: targetMode ?? null,
41404
41731
  workspace_type: String(opts.workspaceType || "clean-no-repo"),
41405
41732
  agent_shell: String(opts.agentShell || "vscode-terminal"),
41406
41733
  run_count: runs2.length,
@@ -41425,16 +41752,28 @@ function registerEval(program3) {
41425
41752
  extra: { batch }
41426
41753
  }), { json: Boolean(opts.json) });
41427
41754
  });
41428
- external.command("run").description("Launch an instrumented shell and emit external_agent_run.v1 when it exits").option("--model-provider <name>", "Model provider label", "unknown").option("--model-name <name>", "Model name label", "unknown-model").option("--prompt-version <version>", "Prompt version", DEFAULT_PROMPT_VERSION).option("--replay-file <path>", "Local transcript/replay artifact to seed replay-failure prompts").option("--knowledge-question <text>", "Question to seed knowledge-miss prompts").option("--expected-answer <text>", "Expected answer or missing fact for planted knowledge-miss prompts").option("--workspace-type <type>", "Workspace type label", "clean-no-repo").option("--agent-shell <name>", "Agent shell label", "vscode-terminal").option("--out-dir <path>", "Run output directory").option("--status <status>", "Final status when not interactively classified: pass|hold|fail", "hold").option("--reason-code <code>", "Failure/hold reason code", "external_agent_run_needs_review").option("--shell <command>", "Shell command to launch for capture").option("--no-shell", "Do not launch a shell; create/finalize artifacts immediately").option("--json", "Output as JSON").action(async (opts) => {
41755
+ external.command("run").description("Launch an instrumented shell and emit external_agent_run.v1 when it exits").option("--model-provider <name>", "Model provider label", "unknown").option("--model-name <name>", "Model name label", "unknown-model").option("--prompt-version <version>", "Prompt version", DEFAULT_PROMPT_VERSION).option("--replay-file <path>", "Local transcript/replay artifact to seed replay-failure prompts").option("--knowledge-question <text>", "Question to seed knowledge-miss prompts").option("--expected-answer <text>", "Expected answer or missing fact for planted knowledge-miss prompts").option("--agency-name <name>", "Agency name to seed arbitrary-agency setup prompts").option("--source-url <url>", "Official source URL to seed arbitrary-agency setup prompts").option("--branch-location <value>", "Branch/location to seed arbitrary-agency setup prompts").option("--target-mode <mode>", "Target launch/exposure mode for arbitrary-agency setup prompts").option("--workspace-type <type>", "Workspace type label", "clean-no-repo").option("--agent-shell <name>", "Agent shell label", "vscode-terminal").option("--out-dir <path>", "Run output directory").option("--status <status>", "Final status when not interactively classified: pass|hold|fail", "hold").option("--reason-code <code>", "Failure/hold reason code", "external_agent_run_needs_review").option("--shell <command>", "Shell command to launch for capture").option("--no-shell", "Do not launch a shell; create/finalize artifacts immediately").option("--json", "Output as JSON").action(async (opts) => {
41429
41756
  const status = normalizeStatus(opts.status);
41430
41757
  const promptVersion = String(opts.promptVersion || DEFAULT_PROMPT_VERSION);
41431
41758
  const runDir = (0, import_path19.resolve)(String(opts.outDir || defaultRunDir(opts.modelName, promptVersion)));
41432
41759
  const replayFile = opts.replayFile ? (0, import_path19.resolve)(String(opts.replayFile)) : void 0;
41433
41760
  const knowledgeQuestion = opts.knowledgeQuestion ? String(opts.knowledgeQuestion) : void 0;
41434
41761
  const expectedAnswer = opts.expectedAnswer ? String(opts.expectedAnswer) : void 0;
41762
+ const agencyName = opts.agencyName ? String(opts.agencyName) : void 0;
41763
+ const sourceUrl = opts.sourceUrl ? String(opts.sourceUrl) : void 0;
41764
+ const branchLocation = opts.branchLocation ? String(opts.branchLocation) : void 0;
41765
+ const targetMode = opts.targetMode ? String(opts.targetMode) : void 0;
41435
41766
  (0, import_fs20.mkdirSync)(runDir, { recursive: true });
41436
41767
  const runId = runDir.split(/[\\/]/).filter(Boolean).slice(-1)[0];
41437
- const promptPath = writePrompt(runDir, promptVersion, { replayFile, knowledgeQuestion, expectedAnswer });
41768
+ const promptPath = writePrompt(runDir, promptVersion, {
41769
+ replayFile,
41770
+ knowledgeQuestion,
41771
+ expectedAnswer,
41772
+ agencyName,
41773
+ sourceUrl,
41774
+ branchLocation,
41775
+ targetMode
41776
+ });
41438
41777
  const shell = inferShell(opts.shell);
41439
41778
  const session = {
41440
41779
  schema_version: "external_agent_capture_session.v1",
@@ -41446,6 +41785,10 @@ function registerEval(program3) {
41446
41785
  replay_file: replayFile ?? null,
41447
41786
  knowledge_question: knowledgeQuestion ?? null,
41448
41787
  expected_answer: expectedAnswer ?? null,
41788
+ agency_name: agencyName ?? null,
41789
+ source_url: sourceUrl ?? null,
41790
+ branch_location: branchLocation ?? null,
41791
+ target_mode: targetMode ?? null,
41449
41792
  workspace_type: String(opts.workspaceType || "clean-no-repo"),
41450
41793
  agent_shell: String(opts.agentShell || shell.label),
41451
41794
  manual_intervention_count: 0,
@@ -41728,10 +42071,8 @@ var CLI_MISSION_EXAMPLES = [
41728
42071
  { mission: "Start", command: "foh start", description: "guided setup and next action selector" },
41729
42072
  { mission: "Setup", command: "foh setup --phone-mode observe --json", description: "create or update agent, widget, voice config, and proof scaffold" },
41730
42073
  { mission: "Prove", command: "foh prove --agent <agent_id> --mission widget --json", description: "produce a machine-readable proof report" },
41731
- { mission: "Certify", command: "foh certify run --agent <agent_id> --profile release --json", description: "produce release evidence before publish" },
41732
- { mission: "Debug", command: "foh debug --out test-results/foh-cli-diag.latest.json --json", description: "collect auth/org/API diagnostics" },
41733
- { mission: "Improve", command: "foh bug improve --from-file <artifact.json> --json", description: "convert a failure artifact into a redacted improvement packet" },
41734
- { mission: "Publish", command: "foh agent publish --agent <agent_id> --json", description: "publish after proof gates pass" }
42074
+ { mission: "Publish", command: "foh publish --agent <agent_id> --json", description: "publish when proof and release evidence pass" },
42075
+ { mission: "Debug", command: "foh debug --out test-results/foh-cli-diag.latest.json --json", description: "collect auth/org/API diagnostics" }
41735
42076
  ];
41736
42077
  function missionHelpText() {
41737
42078
  return [
@@ -41739,7 +42080,7 @@ function missionHelpText() {
41739
42080
  "Common missions:",
41740
42081
  ...CLI_MISSION_EXAMPLES.map((item) => ` ${item.command.padEnd(66)} ${item.description}`),
41741
42082
  "",
41742
- "For AI agents: prefer --json and follow next_commands exactly when a command blocks."
42083
+ "For AI agents: prefer --json, follow next_commands exactly, and treat certify/eval/bug as advanced operator surfaces."
41743
42084
  ].join("\n");
41744
42085
  }
41745
42086
  function addMissionHelp(program3) {
@@ -41832,6 +42173,7 @@ registerCertify(program2);
41832
42173
  registerDiag(program2);
41833
42174
  registerBug(program2);
41834
42175
  registerProve(program2);
42176
+ registerAgentPublishCommand(program2, { publicAlias: true });
41835
42177
  registerEval(program2);
41836
42178
  registerUpdate(program2);
41837
42179
  registerHome(program2);