@f-o-h/cli 0.1.55 → 0.1.57

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +4 -4
  2. package/dist/foh.js +148 -23
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -101,10 +101,10 @@ scaffold lane:
101
101
  FOH_CLI_SPEND_POLICY=no_spend foh setup --org <org-id> --agent-template <template-id> --agent-name "Demo Agent" --phone-mode observe --json
102
102
  ```
103
103
 
104
- `--phone-mode observe` checks whether a contact phone already exists without
105
- buying one. `--phone-mode skip` bypasses the phone step. `--phone-mode purchase`
106
- is the explicit paid contact path and is fail-closed when
107
- `FOH_CLI_SPEND_POLICY=no_spend` is set.
104
+ `--phone-mode observe` checks whether a contact phone already exists without
105
+ buying one. `--phone-mode skip` bypasses the phone step. `--phone-mode purchase`
106
+ is the explicit paid contact path and is fail-closed when
107
+ `FOH_CLI_SPEND_POLICY=no_spend` is set.
108
108
 
109
109
  If managed-number provisioning is blocked by account/provider capacity or empty
110
110
  reserve inventory, proof reports `provider_capacity_blocked`; fix capacity or
package/dist/foh.js CHANGED
@@ -32853,7 +32853,7 @@ var StdioServerTransport = class {
32853
32853
  };
32854
32854
 
32855
32855
  // src/lib/cli-version.ts
32856
- var CLI_VERSION = "0.1.55";
32856
+ var CLI_VERSION = "0.1.57";
32857
32857
 
32858
32858
  // src/commands/mcp-serve.ts
32859
32859
  var DEFAULT_TIMEOUT_MS = 12e4;
@@ -39250,6 +39250,8 @@ var CODEX_EXECUTOR_DENIED_ENV_PREFIXES = [
39250
39250
  "OPENAI_",
39251
39251
  "XAI_",
39252
39252
  "ANTHROPIC_",
39253
+ "GOOGLE_",
39254
+ "GEMINI_",
39253
39255
  "WHATSAPP_",
39254
39256
  "TWILIO_",
39255
39257
  "STRIPE_"
@@ -39453,9 +39455,22 @@ function resolveCodexProbeCommand() {
39453
39455
  function resolveCodexExecutionCommand() {
39454
39456
  return resolveCodexProbeCommand();
39455
39457
  }
39458
+ function resolveGeminiProbeCommand() {
39459
+ if (process.platform !== "win32") return "gemini";
39460
+ const appData = process.env.APPDATA;
39461
+ if (appData) {
39462
+ const appDataShim = (0, import_path13.join)(appData, "npm", "gemini.cmd");
39463
+ if ((0, import_fs15.existsSync)(appDataShim)) return appDataShim;
39464
+ }
39465
+ return "gemini.cmd";
39466
+ }
39467
+ function resolveRunnerExecutionCommand(runner) {
39468
+ return runner === "gemini" ? resolveGeminiProbeCommand() : resolveCodexExecutionCommand();
39469
+ }
39456
39470
  function validateCodexRunner(options) {
39457
39471
  if (options.skipRunnerProbe) {
39458
39472
  return {
39473
+ runner: "codex",
39459
39474
  binaryChecked: false,
39460
39475
  requiredFlagsChecked: false,
39461
39476
  version: null,
@@ -39515,6 +39530,7 @@ ${yoloHelp.stderr}`;
39515
39530
  }
39516
39531
  if (supportsModernApprovalMode) {
39517
39532
  return {
39533
+ runner: "codex",
39518
39534
  binaryChecked: true,
39519
39535
  requiredFlagsChecked: true,
39520
39536
  version: versionText,
@@ -39530,6 +39546,7 @@ ${yoloHelp.stderr}`;
39530
39546
  };
39531
39547
  }
39532
39548
  return {
39549
+ runner: "codex",
39533
39550
  binaryChecked: true,
39534
39551
  requiredFlagsChecked: true,
39535
39552
  version: versionText,
@@ -39544,6 +39561,77 @@ ${yoloHelp.stderr}`;
39544
39561
  execArgs: ["--full-auto"]
39545
39562
  };
39546
39563
  }
39564
+ function validateGeminiRunner(options) {
39565
+ const execArgs = [
39566
+ "--approval-mode",
39567
+ "yolo",
39568
+ "--sandbox",
39569
+ "--output-format",
39570
+ "stream-json",
39571
+ "--prompt",
39572
+ "Execute the FOH external-agent prompt supplied on stdin."
39573
+ ];
39574
+ if (options.skipRunnerProbe) {
39575
+ return {
39576
+ runner: "gemini",
39577
+ binaryChecked: false,
39578
+ requiredFlagsChecked: false,
39579
+ version: null,
39580
+ rootHelpChecked: false,
39581
+ execHelpChecked: false,
39582
+ supportsModernApprovalMode: false,
39583
+ supportsLegacyFullAuto: false,
39584
+ supportsYoloAlias: null,
39585
+ yoloPolicy: "not_checked",
39586
+ supportsGeminiHeadless: true,
39587
+ automationMode: "gemini-yolo",
39588
+ globalArgs: [],
39589
+ execArgs
39590
+ };
39591
+ }
39592
+ const probe = options.runnerProbe ?? defaultRunnerProbe;
39593
+ const probeCommand = resolveGeminiProbeCommand();
39594
+ const version2 = probe(probeCommand, ["--version"]);
39595
+ if (version2.error || version2.status !== 0) {
39596
+ throw new ExternalAgentExecutorError("external_agent_runner_binary_missing", "Gemini runner probe failed: `gemini --version` did not exit 0.");
39597
+ }
39598
+ const versionText = `${version2.stdout}
39599
+ ${version2.stderr}`.trim() || null;
39600
+ const help = probe(probeCommand, ["--help"]);
39601
+ if (help.error || help.status !== 0) {
39602
+ throw new ExternalAgentExecutorError("external_agent_runner_help_unavailable", "Gemini runner probe failed: `gemini --help` did not exit 0.");
39603
+ }
39604
+ const helpText = `${help.stdout}
39605
+ ${help.stderr}`;
39606
+ const requiredFlags = ["--prompt", "--approval-mode", "--sandbox", "--output-format"];
39607
+ const missing = requiredFlags.filter((flag) => !helpText.includes(flag));
39608
+ if (missing.length > 0) {
39609
+ throw new ExternalAgentExecutorError(
39610
+ "external_agent_runner_required_flags_missing",
39611
+ `Gemini runner is missing required headless flag(s): ${missing.join(", ")}`
39612
+ );
39613
+ }
39614
+ return {
39615
+ runner: "gemini",
39616
+ binaryChecked: true,
39617
+ requiredFlagsChecked: true,
39618
+ version: versionText,
39619
+ rootHelpChecked: true,
39620
+ execHelpChecked: true,
39621
+ supportsModernApprovalMode: false,
39622
+ supportsLegacyFullAuto: false,
39623
+ supportsYoloAlias: helpText.includes("--yolo"),
39624
+ yoloPolicy: helpText.includes("--yolo") ? "observed_not_canonical" : "unsupported",
39625
+ supportsGeminiHeadless: true,
39626
+ automationMode: "gemini-yolo",
39627
+ globalArgs: [],
39628
+ execArgs
39629
+ };
39630
+ }
39631
+ function validateRunner(options, runner) {
39632
+ if (runner === "gemini") return validateGeminiRunner(options);
39633
+ return validateCodexRunner(options);
39634
+ }
39547
39635
  function normalizeCodexSandboxBackend(value) {
39548
39636
  const normalized = (value || "default").trim().toLowerCase();
39549
39637
  if (normalized === "default" || normalized === "legacy-landlock") return normalized;
@@ -39591,12 +39679,12 @@ function promptVersionFromPath(promptPath) {
39591
39679
  }
39592
39680
  function createExternalAgentExecutorPlan(options) {
39593
39681
  const runner = String(options.runner || "codex");
39594
- if (runner !== "codex") {
39682
+ if (runner !== "codex" && runner !== "gemini") {
39595
39683
  throw new ExternalAgentExecutorError("unsupported_external_agent_runner", `Unsupported runner: ${runner}`);
39596
39684
  }
39597
39685
  const batchPath = (0, import_path13.resolve)(options.batchPath);
39598
39686
  const batch = readBatch(batchPath);
39599
- const runnerProbe = validateCodexRunner(options);
39687
+ const runnerProbe = validateRunner(options, runner);
39600
39688
  const codexSandboxBackend = normalizeCodexSandboxBackend(options.codexSandboxBackend);
39601
39689
  const codexSandboxMode = normalizeCodexSandboxMode(options.codexSandboxMode);
39602
39690
  const codexNetworkAccess = options.codexNetworkAccess === true;
@@ -39635,12 +39723,16 @@ function createExternalAgentExecutorPlan(options) {
39635
39723
  promptVersion: promptVersionFromPath(promptPath)
39636
39724
  });
39637
39725
  const promptVersion = String(env[EXTERNAL_AGENT_PROMPT_VERSION_ENV] || "unknown");
39638
- const jsonlPath = (0, import_path13.join)(runDir, "codex-exec.jsonl");
39639
- const lastMessagePath = (0, import_path13.join)(runDir, "codex-last-message.md");
39640
- const stderrPath = (0, import_path13.join)(runDir, "codex-stderr.txt");
39726
+ const outputStem = runner === "gemini" ? "gemini" : "codex";
39727
+ const jsonlPath = (0, import_path13.join)(runDir, `${outputStem}-exec.jsonl`);
39728
+ const lastMessagePath = (0, import_path13.join)(runDir, `${outputStem}-last-message.md`);
39729
+ const stderrPath = (0, import_path13.join)(runDir, `${outputStem}-stderr.txt`);
39641
39730
  const runPath = (0, import_path13.join)(runDir, "run.json");
39642
39731
  const artifactSafetyPath = (0, import_path13.join)(runDir, "artifact-safety.json");
39643
- const args = [
39732
+ const args = runner === "gemini" ? [
39733
+ ...runnerProbe.globalArgs,
39734
+ ...runnerProbe.execArgs
39735
+ ] : [
39644
39736
  ...runnerProbe.globalArgs,
39645
39737
  "exec",
39646
39738
  ...codexConfigArgs({ backend: codexSandboxBackend, networkAccess: codexNetworkAccess }),
@@ -39665,7 +39757,7 @@ function createExternalAgentExecutorPlan(options) {
39665
39757
  run_dir: runDir,
39666
39758
  prompt_path: promptPath,
39667
39759
  workspace_dir: workspaceDir,
39668
- command: "codex",
39760
+ command: runner,
39669
39761
  args,
39670
39762
  env_keys: Object.keys(env).sort(),
39671
39763
  outputs: {
@@ -39695,6 +39787,7 @@ function createExternalAgentExecutorPlan(options) {
39695
39787
  denied_env_names: [...CODEX_EXECUTOR_DENIED_ENV_NAMES],
39696
39788
  runner_probe: {
39697
39789
  binary_checked: runnerProbe.binaryChecked,
39790
+ runner: runnerProbe.runner,
39698
39791
  required_flags_checked: runnerProbe.requiredFlagsChecked,
39699
39792
  version: runnerProbe.version,
39700
39793
  root_help_checked: runnerProbe.rootHelpChecked,
@@ -39704,9 +39797,11 @@ function createExternalAgentExecutorPlan(options) {
39704
39797
  supports_yolo_alias: runnerProbe.supportsYoloAlias,
39705
39798
  selected_automation_mode: runnerProbe.automationMode,
39706
39799
  canonical_command_policy: "explicit-flags",
39707
- yolo_policy: runnerProbe.yoloPolicy
39800
+ yolo_policy: runnerProbe.yoloPolicy,
39801
+ ...runnerProbe.runner === "gemini" ? { supports_gemini_headless: runnerProbe.supportsGeminiHeadless } : {}
39708
39802
  },
39709
- codex_automation_mode: runnerProbe.automationMode,
39803
+ runner_automation_mode: runnerProbe.automationMode,
39804
+ codex_automation_mode: runner === "codex" ? runnerProbe.automationMode : null,
39710
39805
  codex_sandbox_mode: codexSandboxMode,
39711
39806
  codex_sandbox_backend: codexSandboxBackend,
39712
39807
  codex_network_access: codexNetworkAccess
@@ -39769,7 +39864,7 @@ function relativeArtifactName(path2) {
39769
39864
  return (0, import_path13.basename)(path2);
39770
39865
  }
39771
39866
  function classifyRun(input) {
39772
- if (input.timedOut) return { status: "hold", reasonCode: "codex_runner_timeout" };
39867
+ if (input.timedOut) return { status: "hold", reasonCode: `${input.run.command}_runner_timeout` };
39773
39868
  if (!input.artifactSafetyOk) return { status: "fail", reasonCode: "external_agent_artifact_safety_blocked" };
39774
39869
  const completedCommands = readCommandRecords(input.run.run_dir).filter((record2) => record2.phase === "completed");
39775
39870
  const observedVersions = completedCommands.map((record2) => String(record2.cli_version || "").trim()).filter((version2) => /^\d+\.\d+\.\d+$/.test(version2));
@@ -39854,7 +39949,7 @@ ${stderr}`;
39854
39949
  if (/browser|approve|approval|login|auth|sign in/i.test(combined) && !proofArtifactPasses(input.run.run_dir)) {
39855
39950
  return { status: "hold", reasonCode: "auth_browser_approval_required" };
39856
39951
  }
39857
- if (input.exitCode !== 0) return { status: "hold", reasonCode: "codex_runner_nonzero_exit" };
39952
+ if (input.exitCode !== 0) return { status: "hold", reasonCode: `${input.run.command}_runner_nonzero_exit` };
39858
39953
  if (proofArtifactPasses(input.run.run_dir)) return { status: "pass", reasonCode: null };
39859
39954
  return { status: "hold", reasonCode: "external_agent_proof_artifact_missing" };
39860
39955
  }
@@ -39868,7 +39963,7 @@ function buildExecutedRunArtifact(input) {
39868
39963
  failure_reason_code: input.reasonCode,
39869
39964
  model_provider: input.run.model_provider,
39870
39965
  model_name: input.run.model_name,
39871
- agent_shell: "codex-exec",
39966
+ agent_shell: `${input.run.command}-exec`,
39872
39967
  workspace_type: "clean-no-repo-programmatic",
39873
39968
  prompt_version: input.run.prompt_version,
39874
39969
  prompt_path: "prompt.txt",
@@ -39918,11 +40013,13 @@ function buildExecutedRunArtifact(input) {
39918
40013
  improvement_packet: input.status === "pass" ? null : "improvement-packet.json",
39919
40014
  agent_metadata: agentMetadata.path,
39920
40015
  notes: (0, import_fs15.existsSync)((0, import_path13.join)(input.run.run_dir, "notes.md")) ? "notes.md" : null,
39921
- codex_last_message: relativeArtifactName(input.run.outputs.last_message),
39922
- codex_stderr: relativeArtifactName(input.run.outputs.stderr),
40016
+ runner_last_message: relativeArtifactName(input.run.outputs.last_message),
40017
+ runner_stderr: relativeArtifactName(input.run.outputs.stderr),
40018
+ codex_last_message: input.run.command === "codex" ? relativeArtifactName(input.run.outputs.last_message) : null,
40019
+ codex_stderr: input.run.command === "codex" ? relativeArtifactName(input.run.outputs.stderr) : null,
39923
40020
  artifact_safety: relativeArtifactName(input.run.outputs.artifact_safety)
39924
40021
  },
39925
- summary: input.status === "pass" ? "Controlled Codex external-agent run produced passing proof evidence." : `Controlled Codex external-agent run ended as ${input.status} with reason ${input.reasonCode}.`,
40022
+ summary: input.status === "pass" ? `Controlled ${input.run.command} external-agent run produced passing proof evidence.` : `Controlled ${input.run.command} external-agent run ended as ${input.status} with reason ${input.reasonCode}.`,
39926
40023
  next_commands: input.status === "pass" ? [externalAgentSummaryCommand((0, import_path13.dirname)(input.run.run_dir))] : [
39927
40024
  "foh eval external-agent scan-artifacts --run-dir <run_dir> --private-repo-root <private_repo_root> --write-redacted --json",
39928
40025
  "foh bug improve --from external-agent-run --file <run_dir>/run.json --out <run_dir>/improvement-packet.json --json",
@@ -39930,7 +40027,7 @@ function buildExecutedRunArtifact(input) {
39930
40027
  ]
39931
40028
  };
39932
40029
  }
39933
- function spawnCodex(input) {
40030
+ function spawnRunner(input) {
39934
40031
  return new Promise((resolveRun) => {
39935
40032
  const started = Date.now();
39936
40033
  const commandInvocation = buildCommandInvocation(input.command, input.args);
@@ -39979,15 +40076,18 @@ function spawnCodex(input) {
39979
40076
  }
39980
40077
  function buildCommandInvocation(command, args) {
39981
40078
  if (process.platform === "win32" && command.toLowerCase().endsWith(".cmd")) {
39982
- const codexEntrypoint = (0, import_path13.join)((0, import_path13.dirname)(command), "node_modules", "@openai", "codex", "bin", "codex.js");
40079
+ const binDir = (0, import_path13.dirname)(command);
40080
+ const codexEntrypoint = (0, import_path13.join)(binDir, "node_modules", "@openai", "codex", "bin", "codex.js");
39983
40081
  if ((0, import_fs15.existsSync)(codexEntrypoint)) return { command: process.execPath, args: [codexEntrypoint, ...args] };
40082
+ const geminiEntrypoint = (0, import_path13.join)(binDir, "node_modules", "@google", "gemini-cli", "bundle", "gemini.js");
40083
+ if ((0, import_fs15.existsSync)(geminiEntrypoint)) return { command: process.execPath, args: ["--no-warnings=DEP0040", geminiEntrypoint, ...args] };
39984
40084
  }
39985
40085
  return { command, args };
39986
40086
  }
39987
40087
  async function executeExternalAgentExecutorPlan(plan, options = {}) {
39988
40088
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
39989
40089
  const results = [];
39990
- const runnerCommand = options.runnerCommand || resolveCodexExecutionCommand();
40090
+ const runnerCommand = options.runnerCommand || resolveRunnerExecutionCommand(plan.runner);
39991
40091
  const authPreflight = options.env ? await runExternalAgentEvalAuthPreflight(options.env) : null;
39992
40092
  if (authPreflight && !authPreflight.ok) {
39993
40093
  const endedAt2 = (/* @__PURE__ */ new Date()).toISOString();
@@ -40037,7 +40137,7 @@ async function executeExternalAgentExecutorPlan(plan, options = {}) {
40037
40137
  runDir: commandCaptureDir,
40038
40138
  promptVersion: run.prompt_version
40039
40139
  });
40040
- const spawned = await spawnCodex({
40140
+ const spawned = await spawnRunner({
40041
40141
  command: runnerCommand,
40042
40142
  args: run.args,
40043
40143
  cwd: run.workspace_dir,
@@ -40153,6 +40253,33 @@ function externalAgentSummaryCommand2(root) {
40153
40253
  quoteArg(reportPath)
40154
40254
  ].join(" ");
40155
40255
  }
40256
+ function executorRecoveryCommands(reasonCode, runner) {
40257
+ const normalizedRunner = String(runner || "codex").trim().toLowerCase();
40258
+ if (reasonCode === "external_agent_runner_binary_missing") {
40259
+ if (normalizedRunner === "gemini") {
40260
+ return [
40261
+ "gemini --version",
40262
+ "gemini --help",
40263
+ "Install and authenticate Gemini CLI on the isolated eval host, then rerun the same `foh eval external-agent execute --runner gemini --dry-run --json` command."
40264
+ ];
40265
+ }
40266
+ return [
40267
+ "codex --version",
40268
+ "codex exec --help",
40269
+ "Run `codex login` on the isolated eval host if the binary is installed but not usable, then rerun the dry-run executor plan."
40270
+ ];
40271
+ }
40272
+ if (reasonCode === "external_agent_runner_required_flags_missing" || reasonCode === "external_agent_runner_help_unavailable") {
40273
+ return [
40274
+ `${normalizedRunner} --version`,
40275
+ `${normalizedRunner} --help`,
40276
+ "Upgrade or reinstall the runner CLI on the isolated eval host, then rerun the dry-run executor plan."
40277
+ ];
40278
+ }
40279
+ return [
40280
+ "Fix the executor plan input or workspace path and rerun with --dry-run."
40281
+ ];
40282
+ }
40156
40283
  function parseModelSpec(raw) {
40157
40284
  const [provider, ...nameParts] = String(raw || "").split("/");
40158
40285
  const name = nameParts.join("/");
@@ -40536,9 +40663,7 @@ Exit the shell to finalize run.json.
40536
40663
  status: "blocked",
40537
40664
  reasonCode: error2.reasonCode,
40538
40665
  summary: error2.message,
40539
- nextCommands: [
40540
- "Fix the executor plan input or workspace path and rerun with --dry-run."
40541
- ]
40666
+ nextCommands: executorRecoveryCommands(error2.reasonCode, String(opts.runner || "codex"))
40542
40667
  }), { json: Boolean(opts.json) });
40543
40668
  process.exitCode = 1;
40544
40669
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@f-o-h/cli",
3
- "version": "0.1.55",
3
+ "version": "0.1.57",
4
4
  "description": "FOH CLI - AI-operator provisioning tool for Front Of House",
5
5
  "license": "UNLICENSED",
6
6
  "bin": {