@f-o-h/cli 0.1.14 → 0.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -10
- package/dist/foh.js +103 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ AI-operator provisioning CLI for Front Of House.
|
|
|
4
4
|
|
|
5
5
|
Public mirror: https://github.com/iiko38/front-of-house-cli
|
|
6
6
|
|
|
7
|
-
Current published baseline: `@f-o-h/cli@0.1.
|
|
7
|
+
Current published baseline: `@f-o-h/cli@0.1.15`
|
|
8
8
|
|
|
9
9
|
This mirror is a generated release artifact. The private product monorepo is not
|
|
10
10
|
published here, and no open-source license is granted unless stated separately.
|
|
@@ -62,15 +62,22 @@ foh auth login --email "$FOH_EMAIL" --password "$FOH_PASSWORD" --json
|
|
|
62
62
|
foh org list --json
|
|
63
63
|
foh org use --org <org-id> --json
|
|
64
64
|
foh setup --org <org-id> --agent-template <template-id> --agent-name "Demo Agent" --json
|
|
65
|
-
foh prove --agent <agent-id> --json --out foh-proof.json
|
|
66
|
-
foh test run --suite ./suite.yml --agent <agent-id> --json --out foh-test-report.json
|
|
67
|
-
foh agent replay --file ./transcript-export.json --json
|
|
68
|
-
foh bug improve --from-file foh-proof.json --out foh-improvement.json --json
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
65
|
+
foh prove --agent <agent-id> --json --out foh-proof.json
|
|
66
|
+
foh test run --suite ./suite.yml --agent <agent-id> --json --out foh-test-report.json
|
|
67
|
+
foh agent replay --file ./transcript-export.json --json
|
|
68
|
+
foh bug improve --from-file foh-proof.json --out foh-improvement.json --json
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Trusted server-side automation can use a scoped service token without browser
|
|
72
|
+
approval:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
FOH_SERVICE_TOKEN="$FOH_SERVICE_TOKEN" FOH_ORG_ID="$FOH_ORG_ID" foh auth whoami --json
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`auth signup --web` opens the console signup page when possible and always
|
|
79
|
+
prints the fallback URL. `auth login --web` starts browser device
|
|
80
|
+
authorization, opens `/cli-auth`, waits for console approval, and stores the
|
|
74
81
|
returned short-lived token. Credential auth remains available as fallback.
|
|
75
82
|
|
|
76
83
|
`foh prove` produces a compact signed proof report across auth, org context,
|
package/dist/foh.js
CHANGED
|
@@ -10029,9 +10029,56 @@ function resolveApiBaseUrl(cliOverride) {
|
|
|
10029
10029
|
return resolveApiUrlOverride(cliOverride) ?? DEFAULT_FOH_API_URL;
|
|
10030
10030
|
}
|
|
10031
10031
|
|
|
10032
|
+
// src/lib/org-id.ts
|
|
10033
|
+
function isUsableOrgId(value) {
|
|
10034
|
+
const orgId = String(value ?? "").trim();
|
|
10035
|
+
return Boolean(orgId);
|
|
10036
|
+
}
|
|
10037
|
+
|
|
10032
10038
|
// src/lib/credentials.ts
|
|
10033
10039
|
var CONFIG_PATH = (0, import_path.join)((0, import_os.homedir)(), ".config", "foh", "credentials.json");
|
|
10040
|
+
var SERVICE_TOKEN_ENV = "FOH_SERVICE_TOKEN";
|
|
10041
|
+
var ORG_ID_ENV = "FOH_ORG_ID";
|
|
10042
|
+
var API_URL_ENV = "FOH_API_URL";
|
|
10043
|
+
var TOKEN_EXPIRES_AT_ENV = "FOH_TOKEN_EXPIRES_AT";
|
|
10044
|
+
function normalizeEnv(value) {
|
|
10045
|
+
const normalized = String(value ?? "").trim();
|
|
10046
|
+
return normalized ? normalized : void 0;
|
|
10047
|
+
}
|
|
10048
|
+
function decodeBase64UrlJson(value) {
|
|
10049
|
+
try {
|
|
10050
|
+
const padded = value.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(value.length / 4) * 4, "=");
|
|
10051
|
+
return JSON.parse(Buffer.from(padded, "base64").toString("utf8"));
|
|
10052
|
+
} catch {
|
|
10053
|
+
return void 0;
|
|
10054
|
+
}
|
|
10055
|
+
}
|
|
10056
|
+
function expiryFromJwt(token) {
|
|
10057
|
+
const [, payload] = token.split(".");
|
|
10058
|
+
if (!payload) return void 0;
|
|
10059
|
+
const decoded = decodeBase64UrlJson(payload);
|
|
10060
|
+
const exp = Number(decoded?.exp);
|
|
10061
|
+
if (!Number.isFinite(exp) || exp <= 0) return void 0;
|
|
10062
|
+
return new Date(exp * 1e3).toISOString();
|
|
10063
|
+
}
|
|
10064
|
+
function fallbackExpiry() {
|
|
10065
|
+
return new Date(Date.now() + 60 * 60 * 1e3).toISOString();
|
|
10066
|
+
}
|
|
10067
|
+
function credentialsFromEnv(apiUrlOverride, env = process.env) {
|
|
10068
|
+
const token = normalizeEnv(env[SERVICE_TOKEN_ENV]);
|
|
10069
|
+
if (!token) return void 0;
|
|
10070
|
+
const apiUrl = resolveApiUrlOverride(apiUrlOverride) ?? normalizeEnv(env[API_URL_ENV]) ?? DEFAULT_FOH_API_URL;
|
|
10071
|
+
const orgId = normalizeEnv(env[ORG_ID_ENV]);
|
|
10072
|
+
return {
|
|
10073
|
+
apiUrl,
|
|
10074
|
+
token,
|
|
10075
|
+
expiresAt: normalizeEnv(env[TOKEN_EXPIRES_AT_ENV]) ?? expiryFromJwt(token) ?? fallbackExpiry(),
|
|
10076
|
+
...isUsableOrgId(orgId) ? { orgId } : {}
|
|
10077
|
+
};
|
|
10078
|
+
}
|
|
10034
10079
|
function loadCredentials(apiUrlOverride) {
|
|
10080
|
+
const envCreds = credentialsFromEnv(apiUrlOverride);
|
|
10081
|
+
if (envCreds) return envCreds;
|
|
10035
10082
|
let creds;
|
|
10036
10083
|
try {
|
|
10037
10084
|
const raw = (0, import_fs.readFileSync)(CONFIG_PATH, "utf-8");
|
|
@@ -10059,12 +10106,6 @@ function clearCredentials() {
|
|
|
10059
10106
|
}
|
|
10060
10107
|
}
|
|
10061
10108
|
|
|
10062
|
-
// src/lib/org-id.ts
|
|
10063
|
-
function isUsableOrgId(value) {
|
|
10064
|
-
const orgId = String(value ?? "").trim();
|
|
10065
|
-
return Boolean(orgId);
|
|
10066
|
-
}
|
|
10067
|
-
|
|
10068
10109
|
// src/lib/command-runtime.ts
|
|
10069
10110
|
function markCommandFailed(code = 1) {
|
|
10070
10111
|
const normalized = Number.isFinite(code) ? Math.max(1, Math.trunc(code)) : 1;
|
|
@@ -16034,9 +16075,9 @@ function registerVoice(program3) {
|
|
|
16034
16075
|
const outputPath = String(opts.out || `foh-voice-preview-${provider}-${voiceId}.mp3`).trim();
|
|
16035
16076
|
const audio = Buffer.from(await res.arrayBuffer());
|
|
16036
16077
|
const { mkdirSync: mkdirSync7, writeFileSync: writeFileSync9 } = await import("fs");
|
|
16037
|
-
const { dirname:
|
|
16078
|
+
const { dirname: dirname6, resolve: resolve12 } = await import("path");
|
|
16038
16079
|
const absolutePath = resolve12(outputPath);
|
|
16039
|
-
mkdirSync7(
|
|
16080
|
+
mkdirSync7(dirname6(absolutePath), { recursive: true });
|
|
16040
16081
|
writeFileSync9(absolutePath, audio);
|
|
16041
16082
|
format({
|
|
16042
16083
|
status: "ok",
|
|
@@ -32640,7 +32681,7 @@ var StdioServerTransport = class {
|
|
|
32640
32681
|
};
|
|
32641
32682
|
|
|
32642
32683
|
// src/lib/cli-version.ts
|
|
32643
|
-
var CLI_VERSION = "0.1.
|
|
32684
|
+
var CLI_VERSION = "0.1.15";
|
|
32644
32685
|
|
|
32645
32686
|
// src/commands/mcp-serve.ts
|
|
32646
32687
|
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
@@ -38281,7 +38322,8 @@ var TEXT_ARTIFACT_NAMES = /* @__PURE__ */ new Set([
|
|
|
38281
38322
|
"run.json",
|
|
38282
38323
|
"terminal-transcript.txt"
|
|
38283
38324
|
]);
|
|
38284
|
-
var SECRET_RE2 = /\b(?:Bearer\s+
|
|
38325
|
+
var SECRET_RE2 = /\b(?:Bearer\s+[A-Za-z0-9_\-.]{12,}|(?:sk|pk|xai|whsec|EAAN|ghp|gho|github_pat)[A-Za-z0-9_\-.]{12,}|npm_[A-Za-z0-9]{20,})\b/g;
|
|
38326
|
+
var SECRET_QUERY_PARAM_RE = /\b((?:device_code|access_token|refresh_token|api_key|token)=)[A-Za-z0-9_.~-]{12,}/gi;
|
|
38285
38327
|
var EMAIL_RE2 = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi;
|
|
38286
38328
|
var PHONE_RE2 = /(?<!\w)(?:\+?\d[\d\s().-]{7,}\d)(?!\w)/g;
|
|
38287
38329
|
function escapeRegExp(value) {
|
|
@@ -38308,6 +38350,9 @@ function matchesFor(pattern, text) {
|
|
|
38308
38350
|
const matches = text.match(pattern);
|
|
38309
38351
|
return matches ? matches.length : 0;
|
|
38310
38352
|
}
|
|
38353
|
+
function secretMatches(text) {
|
|
38354
|
+
return matchesFor(SECRET_RE2, text) + matchesFor(SECRET_QUERY_PARAM_RE, text);
|
|
38355
|
+
}
|
|
38311
38356
|
function redactionPatterns(input) {
|
|
38312
38357
|
return {
|
|
38313
38358
|
secret: SECRET_RE2,
|
|
@@ -38319,11 +38364,14 @@ function redactionPatterns(input) {
|
|
|
38319
38364
|
}
|
|
38320
38365
|
function redactExternalAgentArtifactText(text, input = {}) {
|
|
38321
38366
|
const patterns = redactionPatterns(input);
|
|
38322
|
-
let redacted = text
|
|
38367
|
+
let redacted = redactExternalAgentSecretText(text).replace(patterns.email, "[redacted_email]").replace(patterns.phone, "[redacted_phone]");
|
|
38323
38368
|
if (patterns.privateRepo) redacted = redacted.replace(patterns.privateRepo, "[redacted_private_repo_path]");
|
|
38324
38369
|
if (patterns.home) redacted = redacted.replace(patterns.home, "[redacted_home_path]");
|
|
38325
38370
|
return redacted;
|
|
38326
38371
|
}
|
|
38372
|
+
function redactExternalAgentSecretText(text) {
|
|
38373
|
+
return text.replace(SECRET_QUERY_PARAM_RE, "$1[redacted_secret]").replace(SECRET_RE2, "[redacted_secret]");
|
|
38374
|
+
}
|
|
38327
38375
|
function artifactFiles(runDir) {
|
|
38328
38376
|
if (!(0, import_fs12.existsSync)(runDir)) return [];
|
|
38329
38377
|
return (0, import_fs12.readdirSync)(runDir).map((name) => (0, import_path10.join)(runDir, name)).filter((path2) => {
|
|
@@ -38343,7 +38391,7 @@ function finding(kind, file2, count) {
|
|
|
38343
38391
|
function scanText(input) {
|
|
38344
38392
|
const patterns = redactionPatterns(input);
|
|
38345
38393
|
return [
|
|
38346
|
-
finding("secret_like_token", input.file,
|
|
38394
|
+
finding("secret_like_token", input.file, secretMatches(input.text)),
|
|
38347
38395
|
finding("private_repo_path", input.file, matchesFor(patterns.privateRepo, input.text)),
|
|
38348
38396
|
finding("local_home_path", input.file, matchesFor(patterns.home, input.text)),
|
|
38349
38397
|
finding("email_address", input.file, matchesFor(patterns.email, input.text)),
|
|
@@ -38483,6 +38531,12 @@ var CHILD_ENV_ALLOWLIST = [
|
|
|
38483
38531
|
"USERPROFILE",
|
|
38484
38532
|
"WINDIR"
|
|
38485
38533
|
];
|
|
38534
|
+
var EXTERNAL_AGENT_EVAL_AUTH_ENV_MAP = {
|
|
38535
|
+
FOH_EXTERNAL_AGENT_EVAL_TOKEN: "FOH_SERVICE_TOKEN",
|
|
38536
|
+
FOH_EXTERNAL_AGENT_EVAL_ORG_ID: "FOH_ORG_ID",
|
|
38537
|
+
FOH_EXTERNAL_AGENT_EVAL_API_URL: "FOH_API_URL",
|
|
38538
|
+
FOH_EXTERNAL_AGENT_EVAL_TOKEN_EXPIRES_AT: "FOH_TOKEN_EXPIRES_AT"
|
|
38539
|
+
};
|
|
38486
38540
|
var ExternalAgentExecutorError = class extends Error {
|
|
38487
38541
|
reasonCode;
|
|
38488
38542
|
constructor(reasonCode, message) {
|
|
@@ -38505,6 +38559,12 @@ function buildCodexExecutorEnv(input) {
|
|
|
38505
38559
|
env[key] = value;
|
|
38506
38560
|
}
|
|
38507
38561
|
}
|
|
38562
|
+
for (const [sourceKey, childKey] of Object.entries(EXTERNAL_AGENT_EVAL_AUTH_ENV_MAP)) {
|
|
38563
|
+
const value = source[sourceKey];
|
|
38564
|
+
if (typeof value === "string" && value.trim() && !isDeniedEnvKey(childKey)) {
|
|
38565
|
+
env[childKey] = value;
|
|
38566
|
+
}
|
|
38567
|
+
}
|
|
38508
38568
|
env[EXTERNAL_AGENT_RUN_DIR_ENV] = input.runDir;
|
|
38509
38569
|
env[EXTERNAL_AGENT_PROMPT_VERSION_ENV] = input.promptVersion;
|
|
38510
38570
|
env.FOH_CLI_SUPPRESS_BANNER = "1";
|
|
@@ -38671,7 +38731,6 @@ function createExternalAgentExecutorPlan(options) {
|
|
|
38671
38731
|
"--skip-git-repo-check",
|
|
38672
38732
|
"--ephemeral",
|
|
38673
38733
|
"--ignore-rules",
|
|
38674
|
-
"--ignore-user-config",
|
|
38675
38734
|
"--sandbox",
|
|
38676
38735
|
"workspace-write",
|
|
38677
38736
|
"--full-auto",
|
|
@@ -38744,6 +38803,22 @@ function proofArtifactPasses(runDir) {
|
|
|
38744
38803
|
function readIfExists(path2) {
|
|
38745
38804
|
return (0, import_fs14.existsSync)(path2) ? (0, import_fs14.readFileSync)(path2, "utf8") : "";
|
|
38746
38805
|
}
|
|
38806
|
+
function redactSecretLikeFile(path2) {
|
|
38807
|
+
if (!(0, import_fs14.existsSync)(path2)) return;
|
|
38808
|
+
const original = (0, import_fs14.readFileSync)(path2, "utf8");
|
|
38809
|
+
const redacted = redactExternalAgentSecretText(original);
|
|
38810
|
+
if (redacted !== original) (0, import_fs14.writeFileSync)(path2, redacted, "utf8");
|
|
38811
|
+
}
|
|
38812
|
+
function redactSecretLikeOutputArtifacts(run) {
|
|
38813
|
+
redactSecretLikeFile(run.outputs.jsonl);
|
|
38814
|
+
redactSecretLikeFile(run.outputs.last_message);
|
|
38815
|
+
redactSecretLikeFile(run.outputs.stderr);
|
|
38816
|
+
}
|
|
38817
|
+
function copyCommandCaptureArtifacts(input) {
|
|
38818
|
+
const commandLog = (0, import_path12.join)(input.captureDir, "commands.ndjson");
|
|
38819
|
+
if (!(0, import_fs14.existsSync)(commandLog)) return;
|
|
38820
|
+
(0, import_fs14.writeFileSync)((0, import_path12.join)(input.runDir, "commands.ndjson"), (0, import_fs14.readFileSync)(commandLog, "utf8"), "utf8");
|
|
38821
|
+
}
|
|
38747
38822
|
function relativeArtifactName(path2) {
|
|
38748
38823
|
return (0, import_path12.basename)(path2);
|
|
38749
38824
|
}
|
|
@@ -38824,11 +38899,11 @@ function buildExecutedRunArtifact(input) {
|
|
|
38824
38899
|
function spawnCodex(input) {
|
|
38825
38900
|
return new Promise((resolveRun) => {
|
|
38826
38901
|
const started = Date.now();
|
|
38827
|
-
const
|
|
38828
|
-
const child = (0, import_child_process4.spawn)(
|
|
38902
|
+
const commandInvocation = buildCommandInvocation(input.command, input.args);
|
|
38903
|
+
const child = (0, import_child_process4.spawn)(commandInvocation.command, commandInvocation.args, {
|
|
38829
38904
|
cwd: input.cwd,
|
|
38830
38905
|
env: input.env,
|
|
38831
|
-
shell:
|
|
38906
|
+
shell: false,
|
|
38832
38907
|
stdio: ["pipe", "pipe", "pipe"],
|
|
38833
38908
|
windowsHide: true
|
|
38834
38909
|
});
|
|
@@ -38868,15 +38943,24 @@ function spawnCodex(input) {
|
|
|
38868
38943
|
});
|
|
38869
38944
|
});
|
|
38870
38945
|
}
|
|
38946
|
+
function buildCommandInvocation(command, args) {
|
|
38947
|
+
if (process.platform === "win32" && command.toLowerCase().endsWith(".cmd")) {
|
|
38948
|
+
const codexEntrypoint = (0, import_path12.join)((0, import_path12.dirname)(command), "node_modules", "@openai", "codex", "bin", "codex.js");
|
|
38949
|
+
if ((0, import_fs14.existsSync)(codexEntrypoint)) return { command: process.execPath, args: [codexEntrypoint, ...args] };
|
|
38950
|
+
}
|
|
38951
|
+
return { command, args };
|
|
38952
|
+
}
|
|
38871
38953
|
async function executeExternalAgentExecutorPlan(plan, options = {}) {
|
|
38872
38954
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
38873
38955
|
const results = [];
|
|
38874
38956
|
const runnerCommand = options.runnerCommand || resolveCodexExecutionCommand();
|
|
38875
38957
|
for (const run of plan.runs) {
|
|
38876
38958
|
const runStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
38959
|
+
const commandCaptureDir = (0, import_path12.join)(run.workspace_dir, ".foh-capture");
|
|
38960
|
+
(0, import_fs14.mkdirSync)(commandCaptureDir, { recursive: true });
|
|
38877
38961
|
const env = buildCodexExecutorEnv({
|
|
38878
38962
|
sourceEnv: options.env,
|
|
38879
|
-
runDir:
|
|
38963
|
+
runDir: commandCaptureDir,
|
|
38880
38964
|
promptVersion: run.prompt_version
|
|
38881
38965
|
});
|
|
38882
38966
|
const spawned = await spawnCodex({
|
|
@@ -38889,6 +38973,8 @@ async function executeExternalAgentExecutorPlan(plan, options = {}) {
|
|
|
38889
38973
|
stderrPath: run.outputs.stderr,
|
|
38890
38974
|
timeoutMs: plan.timeout_minutes * 60 * 1e3
|
|
38891
38975
|
});
|
|
38976
|
+
copyCommandCaptureArtifacts({ captureDir: commandCaptureDir, runDir: run.run_dir });
|
|
38977
|
+
redactSecretLikeOutputArtifacts(run);
|
|
38892
38978
|
const artifactSafety = scanExternalAgentArtifacts({
|
|
38893
38979
|
runDir: run.run_dir,
|
|
38894
38980
|
privateRepoRoot: options.privateRepoRoot || plan.private_repo_root,
|