@f-o-h/cli 0.1.88 → 0.1.89
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 +1599 -221
- package/package.json +1 -1
- package/schemas/business-requirement-brief.schema.json +3 -3
package/dist/foh.js
CHANGED
|
@@ -963,8 +963,8 @@ var require_command = __commonJS({
|
|
|
963
963
|
"../../node_modules/.pnpm/commander@12.1.0/node_modules/commander/lib/command.js"(exports2) {
|
|
964
964
|
var EventEmitter = require("node:events").EventEmitter;
|
|
965
965
|
var childProcess = require("node:child_process");
|
|
966
|
-
var
|
|
967
|
-
var
|
|
966
|
+
var path5 = require("node:path");
|
|
967
|
+
var fs4 = require("node:fs");
|
|
968
968
|
var process4 = require("node:process");
|
|
969
969
|
var { Argument: Argument2, humanReadableArgName } = require_argument();
|
|
970
970
|
var { CommanderError: CommanderError2 } = require_error();
|
|
@@ -1896,11 +1896,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1896
1896
|
let launchWithNode = false;
|
|
1897
1897
|
const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
|
|
1898
1898
|
function findFile(baseDir, baseName) {
|
|
1899
|
-
const localBin =
|
|
1900
|
-
if (
|
|
1901
|
-
if (sourceExt.includes(
|
|
1899
|
+
const localBin = path5.resolve(baseDir, baseName);
|
|
1900
|
+
if (fs4.existsSync(localBin)) return localBin;
|
|
1901
|
+
if (sourceExt.includes(path5.extname(baseName))) return void 0;
|
|
1902
1902
|
const foundExt = sourceExt.find(
|
|
1903
|
-
(ext) =>
|
|
1903
|
+
(ext) => fs4.existsSync(`${localBin}${ext}`)
|
|
1904
1904
|
);
|
|
1905
1905
|
if (foundExt) return `${localBin}${foundExt}`;
|
|
1906
1906
|
return void 0;
|
|
@@ -1912,21 +1912,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1912
1912
|
if (this._scriptPath) {
|
|
1913
1913
|
let resolvedScriptPath;
|
|
1914
1914
|
try {
|
|
1915
|
-
resolvedScriptPath =
|
|
1915
|
+
resolvedScriptPath = fs4.realpathSync(this._scriptPath);
|
|
1916
1916
|
} catch (err) {
|
|
1917
1917
|
resolvedScriptPath = this._scriptPath;
|
|
1918
1918
|
}
|
|
1919
|
-
executableDir =
|
|
1920
|
-
|
|
1919
|
+
executableDir = path5.resolve(
|
|
1920
|
+
path5.dirname(resolvedScriptPath),
|
|
1921
1921
|
executableDir
|
|
1922
1922
|
);
|
|
1923
1923
|
}
|
|
1924
1924
|
if (executableDir) {
|
|
1925
1925
|
let localFile = findFile(executableDir, executableFile);
|
|
1926
1926
|
if (!localFile && !subcommand._executableFile && this._scriptPath) {
|
|
1927
|
-
const legacyName =
|
|
1927
|
+
const legacyName = path5.basename(
|
|
1928
1928
|
this._scriptPath,
|
|
1929
|
-
|
|
1929
|
+
path5.extname(this._scriptPath)
|
|
1930
1930
|
);
|
|
1931
1931
|
if (legacyName !== this._name) {
|
|
1932
1932
|
localFile = findFile(
|
|
@@ -1937,7 +1937,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1937
1937
|
}
|
|
1938
1938
|
executableFile = localFile || executableFile;
|
|
1939
1939
|
}
|
|
1940
|
-
launchWithNode = sourceExt.includes(
|
|
1940
|
+
launchWithNode = sourceExt.includes(path5.extname(executableFile));
|
|
1941
1941
|
let proc;
|
|
1942
1942
|
if (process4.platform !== "win32") {
|
|
1943
1943
|
if (launchWithNode) {
|
|
@@ -2777,7 +2777,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2777
2777
|
* @return {Command}
|
|
2778
2778
|
*/
|
|
2779
2779
|
nameFromFilename(filename) {
|
|
2780
|
-
this._name =
|
|
2780
|
+
this._name = path5.basename(filename, path5.extname(filename));
|
|
2781
2781
|
return this;
|
|
2782
2782
|
}
|
|
2783
2783
|
/**
|
|
@@ -2791,9 +2791,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2791
2791
|
* @param {string} [path]
|
|
2792
2792
|
* @return {(string|null|Command)}
|
|
2793
2793
|
*/
|
|
2794
|
-
executableDir(
|
|
2795
|
-
if (
|
|
2796
|
-
this._executableDir =
|
|
2794
|
+
executableDir(path6) {
|
|
2795
|
+
if (path6 === void 0) return this._executableDir;
|
|
2796
|
+
this._executableDir = path6;
|
|
2797
2797
|
return this;
|
|
2798
2798
|
}
|
|
2799
2799
|
/**
|
|
@@ -6288,8 +6288,8 @@ var require_utils = __commonJS({
|
|
|
6288
6288
|
}
|
|
6289
6289
|
return ind;
|
|
6290
6290
|
}
|
|
6291
|
-
function removeDotSegments(
|
|
6292
|
-
let input =
|
|
6291
|
+
function removeDotSegments(path5) {
|
|
6292
|
+
let input = path5;
|
|
6293
6293
|
const output = [];
|
|
6294
6294
|
let nextSlash = -1;
|
|
6295
6295
|
let len = 0;
|
|
@@ -6488,8 +6488,8 @@ var require_schemes = __commonJS({
|
|
|
6488
6488
|
wsComponent.secure = void 0;
|
|
6489
6489
|
}
|
|
6490
6490
|
if (wsComponent.resourceName) {
|
|
6491
|
-
const [
|
|
6492
|
-
wsComponent.path =
|
|
6491
|
+
const [path5, query] = wsComponent.resourceName.split("?");
|
|
6492
|
+
wsComponent.path = path5 && path5 !== "/" ? path5 : void 0;
|
|
6493
6493
|
wsComponent.query = query;
|
|
6494
6494
|
wsComponent.resourceName = void 0;
|
|
6495
6495
|
}
|
|
@@ -9851,12 +9851,12 @@ var require_dist = __commonJS({
|
|
|
9851
9851
|
throw new Error(`Unknown format "${name}"`);
|
|
9852
9852
|
return f;
|
|
9853
9853
|
};
|
|
9854
|
-
function addFormats(ajv, list,
|
|
9854
|
+
function addFormats(ajv, list, fs4, exportName) {
|
|
9855
9855
|
var _a2;
|
|
9856
9856
|
var _b;
|
|
9857
9857
|
(_a2 = (_b = ajv.opts.code).formats) !== null && _a2 !== void 0 ? _a2 : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
|
|
9858
9858
|
for (const f of list)
|
|
9859
|
-
ajv.addFormat(f,
|
|
9859
|
+
ajv.addFormat(f, fs4[f]);
|
|
9860
9860
|
}
|
|
9861
9861
|
module2.exports = exports2 = formatsPlugin;
|
|
9862
9862
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
@@ -10787,8 +10787,8 @@ var resolvedOrgCache = /* @__PURE__ */ new Map();
|
|
|
10787
10787
|
function cacheKey(apiUrl, token) {
|
|
10788
10788
|
return `${apiUrl}::${token}`;
|
|
10789
10789
|
}
|
|
10790
|
-
function isOrgRequiredPath(
|
|
10791
|
-
return
|
|
10790
|
+
function isOrgRequiredPath(path5) {
|
|
10791
|
+
return path5.startsWith("/v1/console/") && !path5.startsWith("/v1/console/auth/");
|
|
10792
10792
|
}
|
|
10793
10793
|
async function discoverDefaultOrgId(apiUrl, token) {
|
|
10794
10794
|
const key = cacheKey(apiUrl, token);
|
|
@@ -10810,16 +10810,16 @@ async function discoverDefaultOrgId(apiUrl, token) {
|
|
|
10810
10810
|
}
|
|
10811
10811
|
return usable;
|
|
10812
10812
|
}
|
|
10813
|
-
async function apiFetch(
|
|
10814
|
-
const res = await apiFetchRaw(
|
|
10813
|
+
async function apiFetch(path5, init = {}) {
|
|
10814
|
+
const res = await apiFetchRaw(path5, init);
|
|
10815
10815
|
return res.json();
|
|
10816
10816
|
}
|
|
10817
|
-
async function apiFetchRaw(
|
|
10817
|
+
async function apiFetchRaw(path5, init = {}) {
|
|
10818
10818
|
const { orgId, apiUrlOverride, ...fetchInit } = init;
|
|
10819
10819
|
const creds = loadCredentials(apiUrlOverride);
|
|
10820
|
-
const url2 = creds.apiUrl.replace(/\/$/, "") +
|
|
10820
|
+
const url2 = creds.apiUrl.replace(/\/$/, "") + path5;
|
|
10821
10821
|
let resolvedOrgId = isUsableOrgId(orgId) ? orgId : isUsableOrgId(creds.orgId) ? creds.orgId : void 0;
|
|
10822
|
-
if (!resolvedOrgId && isOrgRequiredPath(
|
|
10822
|
+
if (!resolvedOrgId && isOrgRequiredPath(path5)) {
|
|
10823
10823
|
try {
|
|
10824
10824
|
const discovered = await discoverDefaultOrgId(creds.apiUrl, creds.token);
|
|
10825
10825
|
if (isUsableOrgId(discovered)) {
|
|
@@ -10853,7 +10853,7 @@ async function apiFetchRaw(path2, init = {}) {
|
|
|
10853
10853
|
const errorMsg = body.error || responseText || `HTTP ${res.status}`;
|
|
10854
10854
|
const remediation = body.remediation || getRemediation(res.status);
|
|
10855
10855
|
throw new FohError({
|
|
10856
|
-
step:
|
|
10856
|
+
step: path5,
|
|
10857
10857
|
error: errorMsg,
|
|
10858
10858
|
remediation,
|
|
10859
10859
|
statusCode: res.status,
|
|
@@ -13957,11 +13957,11 @@ function serialiseManifest(manifest) {
|
|
|
13957
13957
|
function flattenObject(obj, prefix = "") {
|
|
13958
13958
|
const result = {};
|
|
13959
13959
|
for (const [key, value] of Object.entries(obj)) {
|
|
13960
|
-
const
|
|
13960
|
+
const path5 = prefix ? `${prefix}.${key}` : key;
|
|
13961
13961
|
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
13962
|
-
Object.assign(result, flattenObject(value,
|
|
13962
|
+
Object.assign(result, flattenObject(value, path5));
|
|
13963
13963
|
} else {
|
|
13964
|
-
result[
|
|
13964
|
+
result[path5] = value;
|
|
13965
13965
|
}
|
|
13966
13966
|
}
|
|
13967
13967
|
return result;
|
|
@@ -13976,12 +13976,12 @@ function diffManifest(current, desired) {
|
|
|
13976
13976
|
"agent"
|
|
13977
13977
|
);
|
|
13978
13978
|
const diffs = [];
|
|
13979
|
-
for (const [
|
|
13980
|
-
const currentVal = currentFlat[
|
|
13979
|
+
for (const [path5, desiredVal] of Object.entries(desiredFlat)) {
|
|
13980
|
+
const currentVal = currentFlat[path5];
|
|
13981
13981
|
const currentSer = JSON.stringify(currentVal);
|
|
13982
13982
|
const desiredSer = JSON.stringify(desiredVal);
|
|
13983
13983
|
if (currentSer !== desiredSer) {
|
|
13984
|
-
diffs.push({ path:
|
|
13984
|
+
diffs.push({ path: path5, from: currentVal, to: desiredVal });
|
|
13985
13985
|
}
|
|
13986
13986
|
}
|
|
13987
13987
|
return diffs;
|
|
@@ -15180,15 +15180,406 @@ function registerTemplates(program3) {
|
|
|
15180
15180
|
}));
|
|
15181
15181
|
}
|
|
15182
15182
|
|
|
15183
|
-
// src/
|
|
15184
|
-
var
|
|
15185
|
-
|
|
15186
|
-
|
|
15187
|
-
|
|
15183
|
+
// src/lib/widget-platforms.ts
|
|
15184
|
+
var import_node_fs = __toESM(require("node:fs"));
|
|
15185
|
+
var import_node_os = __toESM(require("node:os"));
|
|
15186
|
+
var import_node_path = __toESM(require("node:path"));
|
|
15187
|
+
var import_node_child_process = require("node:child_process");
|
|
15188
|
+
|
|
15189
|
+
// src/lib/widget-install-guidance.ts
|
|
15190
|
+
function normalizeOriginHint(originOrHost) {
|
|
15191
|
+
if (!originOrHost) return null;
|
|
15192
|
+
const value = originOrHost.trim();
|
|
15193
|
+
if (!value) return null;
|
|
15194
|
+
if (/^https?:\/\//i.test(value)) return value;
|
|
15195
|
+
return `https://${value}`;
|
|
15196
|
+
}
|
|
15197
|
+
function uniqueCommands(commands) {
|
|
15198
|
+
return Array.from(new Set(commands.filter((command) => Boolean(command && command.trim()))));
|
|
15199
|
+
}
|
|
15200
|
+
function buildWidgetSmokeCommand(params) {
|
|
15201
|
+
const origin = normalizeOriginHint(params.origin);
|
|
15202
|
+
if (params.publicKey) {
|
|
15203
|
+
return `foh widget smoke --channel ${params.publicKey} --json${origin ? ` --origin ${origin}` : ""}`;
|
|
15204
|
+
}
|
|
15205
|
+
if (params.agentId) {
|
|
15206
|
+
return `foh widget smoke --agent ${params.agentId} --json`;
|
|
15207
|
+
}
|
|
15208
|
+
return null;
|
|
15209
|
+
}
|
|
15210
|
+
function buildWidgetVerifyInstallCommand(params) {
|
|
15211
|
+
if (!params.publicKey) return null;
|
|
15212
|
+
const origin = normalizeOriginHint(params.origin);
|
|
15213
|
+
const resolvedOrigin = origin ?? (params.placeholderOrigin ? "https://www.example.com" : null);
|
|
15214
|
+
return `foh widget verify-install --channel ${params.publicKey} --json${resolvedOrigin ? ` --origin ${resolvedOrigin}` : ""}`;
|
|
15215
|
+
}
|
|
15216
|
+
function buildWidgetInstallGuidance(params) {
|
|
15217
|
+
const domainAllowlist = Array.isArray(params.domains) ? params.domains.map((domain2) => String(domain2 || "").trim()).filter(Boolean) : [];
|
|
15218
|
+
const originHint = normalizeOriginHint(params.origin) ?? normalizeOriginHint(domainAllowlist[0]);
|
|
15219
|
+
const installVerificationCommand = buildWidgetVerifyInstallCommand({
|
|
15220
|
+
publicKey: params.publicKey,
|
|
15221
|
+
origin: originHint,
|
|
15222
|
+
placeholderOrigin: params.placeholderOrigin
|
|
15223
|
+
});
|
|
15224
|
+
const runtimeSmokeCommand = buildWidgetSmokeCommand({
|
|
15225
|
+
publicKey: params.publicKey,
|
|
15226
|
+
agentId: params.agentId,
|
|
15227
|
+
origin: originHint
|
|
15228
|
+
});
|
|
15229
|
+
return {
|
|
15230
|
+
widget_public_key: params.publicKey?.trim() || null,
|
|
15231
|
+
domain_allowlist: domainAllowlist,
|
|
15232
|
+
origin_hint: originHint,
|
|
15233
|
+
install_verification_command: installVerificationCommand,
|
|
15234
|
+
runtime_smoke_command: runtimeSmokeCommand,
|
|
15235
|
+
verification_commands: uniqueCommands([installVerificationCommand ?? runtimeSmokeCommand])
|
|
15236
|
+
};
|
|
15237
|
+
}
|
|
15238
|
+
|
|
15239
|
+
// src/lib/widget-platforms.ts
|
|
15240
|
+
var DEFAULT_WIDGET_API_URL = "https://api.frontofhouse.okii.uk";
|
|
15241
|
+
var DEFAULT_WORDPRESS_PLUGIN_NAME = "Front Of House Widget";
|
|
15242
|
+
var SUPPORTED_INSTALL_PLATFORMS = ["custom", "wordpress", "wix", "squarespace", "webflow", "shopify"];
|
|
15243
|
+
var SHARED_BUILDER_INTRO = [
|
|
15244
|
+
"Use the platform custom-code or embed surface, not a theme redesign path.",
|
|
15245
|
+
"Paste the hosted Front Of House snippet exactly as generated unless a native adapter is provided.",
|
|
15246
|
+
"Keep the widget domain allowlist aligned to the real production website hostnames."
|
|
15188
15247
|
];
|
|
15189
|
-
|
|
15190
|
-
|
|
15191
|
-
|
|
15248
|
+
var BUILDER_PLATFORM_GUIDES = {
|
|
15249
|
+
wordpress: {
|
|
15250
|
+
label: "WordPress",
|
|
15251
|
+
paste_surface: "Custom HTML block or site-wide code injection area",
|
|
15252
|
+
steps: [
|
|
15253
|
+
"Preferred path: generate the preconfigured WordPress plugin bundle and upload/activate it in WordPress.",
|
|
15254
|
+
"Fallback path: open the page, template, or footer area where the widget should load.",
|
|
15255
|
+
"Use a Custom HTML block for page-level install, or the site code-injection/header-footer area for site-wide install.",
|
|
15256
|
+
"If using manual embed, paste the snippet once, publish the page/theme change, then verify on the live site domain."
|
|
15257
|
+
],
|
|
15258
|
+
nativeAdapterCommand: ({ publicKey }) => publicKey ? `foh widget wordpress-plugin --channel ${publicKey} --out ./front-of-house-widget --json` : null
|
|
15259
|
+
},
|
|
15260
|
+
wix: {
|
|
15261
|
+
label: "Wix",
|
|
15262
|
+
paste_surface: "Custom code or embed area",
|
|
15263
|
+
steps: [
|
|
15264
|
+
"Open the site custom-code or embed settings for the target page or all pages.",
|
|
15265
|
+
"Paste the snippet into the body/site code area that supports custom scripts.",
|
|
15266
|
+
"Publish the Wix site, then verify on the live site domain."
|
|
15267
|
+
]
|
|
15268
|
+
},
|
|
15269
|
+
squarespace: {
|
|
15270
|
+
label: "Squarespace",
|
|
15271
|
+
paste_surface: "Code Injection or Embed block",
|
|
15272
|
+
steps: [
|
|
15273
|
+
"Open the target page or the global code-injection area.",
|
|
15274
|
+
"Paste the snippet into a code/embed block for page-level install or the global injection area for site-wide install.",
|
|
15275
|
+
"Save, publish, and verify on the live site domain."
|
|
15276
|
+
]
|
|
15277
|
+
},
|
|
15278
|
+
webflow: {
|
|
15279
|
+
label: "Webflow",
|
|
15280
|
+
paste_surface: "Embed element or page/site custom code",
|
|
15281
|
+
steps: [
|
|
15282
|
+
"Open the target page or the site/page custom-code settings.",
|
|
15283
|
+
"Paste the snippet into an Embed element or the footer custom-code area.",
|
|
15284
|
+
"Publish the site, then verify on the live site domain."
|
|
15285
|
+
]
|
|
15286
|
+
},
|
|
15287
|
+
shopify: {
|
|
15288
|
+
label: "Shopify",
|
|
15289
|
+
paste_surface: "Theme custom liquid/embed area",
|
|
15290
|
+
steps: [
|
|
15291
|
+
"Open the active theme customization or theme code area.",
|
|
15292
|
+
"Add the snippet once through a custom liquid/embed block or the theme custom-code area.",
|
|
15293
|
+
"Save the theme change, publish if needed, then verify on the live storefront domain."
|
|
15294
|
+
]
|
|
15295
|
+
}
|
|
15296
|
+
};
|
|
15297
|
+
function resolveWidgetApiUrl(apiUrlOverride) {
|
|
15298
|
+
return (apiUrlOverride || DEFAULT_WIDGET_API_URL).replace(/\/$/, "");
|
|
15299
|
+
}
|
|
15300
|
+
function widgetSnippetFromPublicKey(publicKey, apiUrl) {
|
|
15301
|
+
const resolvedApiUrl = resolveWidgetApiUrl(apiUrl);
|
|
15302
|
+
return `<script src="${resolvedApiUrl}/widget.js" data-channel="${publicKey}" data-api-url="${resolvedApiUrl}" async></script>`;
|
|
15303
|
+
}
|
|
15304
|
+
function normalizeInstallPlatform(value) {
|
|
15305
|
+
const normalized = typeof value === "string" ? value.trim().toLowerCase() : "custom";
|
|
15306
|
+
if (!normalized) return "custom";
|
|
15307
|
+
if (SUPPORTED_INSTALL_PLATFORMS.includes(normalized)) return normalized;
|
|
15308
|
+
throw new FohError({
|
|
15309
|
+
step: "install.platform",
|
|
15310
|
+
error: `Unsupported platform: ${String(value || "")}`,
|
|
15311
|
+
remediation: `Use one of: ${SUPPORTED_INSTALL_PLATFORMS.join(", ")}`
|
|
15312
|
+
});
|
|
15313
|
+
}
|
|
15314
|
+
function isBuilderHostedPlatform(platform) {
|
|
15315
|
+
return platform !== "custom";
|
|
15316
|
+
}
|
|
15317
|
+
function buildBuilderPlatformInstructions(params) {
|
|
15318
|
+
const guide = BUILDER_PLATFORM_GUIDES[params.platform];
|
|
15319
|
+
const guidance = buildWidgetInstallGuidance({
|
|
15320
|
+
publicKey: params.publicKey,
|
|
15321
|
+
domains: params.domains,
|
|
15322
|
+
origin: params.origin
|
|
15323
|
+
});
|
|
15324
|
+
return {
|
|
15325
|
+
schema_version: "foh_widget_builder_install.v1",
|
|
15326
|
+
platform: params.platform,
|
|
15327
|
+
platform_label: guide.label,
|
|
15328
|
+
paste_surface: guide.paste_surface,
|
|
15329
|
+
snippet: params.snippet,
|
|
15330
|
+
...guidance,
|
|
15331
|
+
steps: [...SHARED_BUILDER_INTRO, ...guide.steps],
|
|
15332
|
+
native_adapter_command: guide.nativeAdapterCommand?.({ publicKey: params.publicKey }) ?? null
|
|
15333
|
+
};
|
|
15334
|
+
}
|
|
15335
|
+
function slugifyWordPressPluginName(name) {
|
|
15336
|
+
const normalized = name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
15337
|
+
return normalized || "front-of-house-widget";
|
|
15338
|
+
}
|
|
15339
|
+
function renderWordPressPluginPhp(params) {
|
|
15340
|
+
return `<?php
|
|
15341
|
+
/**
|
|
15342
|
+
* Plugin Name: ${params.pluginName}
|
|
15343
|
+
* Description: Injects the hosted Front Of House widget site-wide.
|
|
15344
|
+
* Version: 0.1.0
|
|
15345
|
+
* Author: Front Of House
|
|
15346
|
+
*/
|
|
15347
|
+
|
|
15348
|
+
if (!defined('ABSPATH')) {
|
|
15349
|
+
exit;
|
|
15350
|
+
}
|
|
15351
|
+
|
|
15352
|
+
function foh_widget_render() {
|
|
15353
|
+
?>
|
|
15354
|
+
<script src="<?php echo esc_url('${params.apiUrl}/widget.js'); ?>" data-channel="<?php echo esc_attr('${params.publicKey}'); ?>" data-api-url="<?php echo esc_url('${params.apiUrl}'); ?>" async></script>
|
|
15355
|
+
<?php
|
|
15356
|
+
}
|
|
15357
|
+
|
|
15358
|
+
add_action('wp_footer', 'foh_widget_render', 100);
|
|
15359
|
+
`;
|
|
15360
|
+
}
|
|
15361
|
+
function renderWordPressPluginReadme(params) {
|
|
15362
|
+
return `${params.pluginName}
|
|
15363
|
+
======================
|
|
15364
|
+
|
|
15365
|
+
This plugin injects the hosted Front Of House widget site-wide.
|
|
15366
|
+
|
|
15367
|
+
Public widget key: ${params.publicKey}
|
|
15368
|
+
API URL: ${params.apiUrl}
|
|
15369
|
+
|
|
15370
|
+
Install:
|
|
15371
|
+
1. Upload this plugin to WordPress.
|
|
15372
|
+
2. Activate it.
|
|
15373
|
+
3. Verify the live site with:
|
|
15374
|
+
${params.verificationCommand}
|
|
15375
|
+
`;
|
|
15376
|
+
}
|
|
15377
|
+
function writeUtf8File(targetPath, content, dryRun) {
|
|
15378
|
+
if (dryRun) return;
|
|
15379
|
+
import_node_fs.default.mkdirSync(import_node_path.default.dirname(targetPath), { recursive: true });
|
|
15380
|
+
import_node_fs.default.writeFileSync(targetPath, content, "utf8");
|
|
15381
|
+
}
|
|
15382
|
+
function packZipDirectory(sourceDir, outputZipPath) {
|
|
15383
|
+
if (process.platform === "win32") {
|
|
15384
|
+
const script = `Compress-Archive -LiteralPath '${sourceDir.replace(/'/g, "''")}' -DestinationPath '${outputZipPath.replace(/'/g, "''")}' -Force`;
|
|
15385
|
+
const result2 = (0, import_node_child_process.spawnSync)("powershell.exe", ["-NoProfile", "-Command", script], {
|
|
15386
|
+
encoding: "utf8",
|
|
15387
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
15388
|
+
windowsHide: true
|
|
15389
|
+
});
|
|
15390
|
+
if (result2.status !== 0) {
|
|
15391
|
+
throw new FohError({
|
|
15392
|
+
step: "widget.wordpress_plugin",
|
|
15393
|
+
error: "Failed to package the WordPress plugin zip.",
|
|
15394
|
+
remediation: "Use a directory output path instead of a .zip path, or rerun on a host with zip packaging support.",
|
|
15395
|
+
detail: {
|
|
15396
|
+
stdout: String(result2.stdout || ""),
|
|
15397
|
+
stderr: String(result2.stderr || "")
|
|
15398
|
+
}
|
|
15399
|
+
});
|
|
15400
|
+
}
|
|
15401
|
+
return;
|
|
15402
|
+
}
|
|
15403
|
+
const result = (0, import_node_child_process.spawnSync)("zip", ["-qr", outputZipPath, import_node_path.default.basename(sourceDir)], {
|
|
15404
|
+
cwd: import_node_path.default.dirname(sourceDir),
|
|
15405
|
+
encoding: "utf8",
|
|
15406
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
15407
|
+
});
|
|
15408
|
+
if (result.error || result.status !== 0) {
|
|
15409
|
+
throw new FohError({
|
|
15410
|
+
step: "widget.wordpress_plugin",
|
|
15411
|
+
error: "Failed to package the WordPress plugin zip.",
|
|
15412
|
+
remediation: "Use a directory output path instead of a .zip path, or install a zip tool and retry.",
|
|
15413
|
+
detail: {
|
|
15414
|
+
stdout: String(result.stdout || ""),
|
|
15415
|
+
stderr: String(result.stderr || "")
|
|
15416
|
+
}
|
|
15417
|
+
});
|
|
15418
|
+
}
|
|
15419
|
+
}
|
|
15420
|
+
function buildWordPressPluginBundle(params) {
|
|
15421
|
+
const pluginName = (params.pluginName || DEFAULT_WORDPRESS_PLUGIN_NAME).trim() || DEFAULT_WORDPRESS_PLUGIN_NAME;
|
|
15422
|
+
const pluginSlug = slugifyWordPressPluginName(pluginName);
|
|
15423
|
+
const apiUrl = resolveWidgetApiUrl(params.apiUrl);
|
|
15424
|
+
const snippet2 = widgetSnippetFromPublicKey(params.publicKey, apiUrl);
|
|
15425
|
+
const guidance = buildWidgetInstallGuidance({
|
|
15426
|
+
publicKey: params.publicKey,
|
|
15427
|
+
placeholderOrigin: true
|
|
15428
|
+
});
|
|
15429
|
+
const requestedOutput = import_node_path.default.resolve(params.outPath);
|
|
15430
|
+
const zipOutput = /\.zip$/i.test(requestedOutput);
|
|
15431
|
+
const artifactType = zipOutput ? "zip" : "directory";
|
|
15432
|
+
const bundleRoot = zipOutput ? import_node_path.default.join(import_node_fs.default.mkdtempSync(import_node_path.default.join(import_node_os.default.tmpdir(), "foh-wp-plugin-")), pluginSlug) : requestedOutput;
|
|
15433
|
+
const entryFile = import_node_path.default.join(bundleRoot, `${pluginSlug}.php`);
|
|
15434
|
+
const readmeFile = import_node_path.default.join(bundleRoot, "README.txt");
|
|
15435
|
+
if (!params.dryRun) {
|
|
15436
|
+
if (artifactType === "directory" && import_node_fs.default.existsSync(requestedOutput)) {
|
|
15437
|
+
throw new FohError({
|
|
15438
|
+
step: "widget.wordpress_plugin",
|
|
15439
|
+
error: `Output path already exists: ${requestedOutput}`,
|
|
15440
|
+
remediation: "Choose a new output path for the plugin bundle."
|
|
15441
|
+
});
|
|
15442
|
+
}
|
|
15443
|
+
if (artifactType === "zip" && import_node_fs.default.existsSync(requestedOutput)) {
|
|
15444
|
+
throw new FohError({
|
|
15445
|
+
step: "widget.wordpress_plugin",
|
|
15446
|
+
error: `Output zip already exists: ${requestedOutput}`,
|
|
15447
|
+
remediation: "Choose a new .zip output path for the plugin bundle."
|
|
15448
|
+
});
|
|
15449
|
+
}
|
|
15450
|
+
}
|
|
15451
|
+
try {
|
|
15452
|
+
writeUtf8File(entryFile, renderWordPressPluginPhp({
|
|
15453
|
+
pluginName,
|
|
15454
|
+
publicKey: params.publicKey,
|
|
15455
|
+
apiUrl
|
|
15456
|
+
}), Boolean(params.dryRun));
|
|
15457
|
+
writeUtf8File(readmeFile, renderWordPressPluginReadme({
|
|
15458
|
+
pluginName,
|
|
15459
|
+
publicKey: params.publicKey,
|
|
15460
|
+
apiUrl,
|
|
15461
|
+
verificationCommand: guidance.install_verification_command || `foh widget verify-install --channel ${params.publicKey} --json --origin https://www.example.com`
|
|
15462
|
+
}), Boolean(params.dryRun));
|
|
15463
|
+
if (!params.dryRun && artifactType === "zip") {
|
|
15464
|
+
import_node_fs.default.mkdirSync(import_node_path.default.dirname(requestedOutput), { recursive: true });
|
|
15465
|
+
packZipDirectory(bundleRoot, requestedOutput);
|
|
15466
|
+
}
|
|
15467
|
+
return {
|
|
15468
|
+
schema_version: "foh_wordpress_plugin_bundle.v1",
|
|
15469
|
+
status: params.dryRun ? "dry_run" : "generated",
|
|
15470
|
+
artifact_type: artifactType,
|
|
15471
|
+
output_path: requestedOutput,
|
|
15472
|
+
plugin_name: pluginName,
|
|
15473
|
+
plugin_slug: pluginSlug,
|
|
15474
|
+
plugin_entry_file: artifactType === "zip" ? `${pluginSlug}/${pluginSlug}.php` : import_node_path.default.relative(process.cwd(), entryFile).replaceAll("\\", "/"),
|
|
15475
|
+
...guidance,
|
|
15476
|
+
api_url: apiUrl,
|
|
15477
|
+
snippet: snippet2
|
|
15478
|
+
};
|
|
15479
|
+
} finally {
|
|
15480
|
+
if (!params.dryRun && artifactType === "zip") {
|
|
15481
|
+
try {
|
|
15482
|
+
import_node_fs.default.rmSync(import_node_path.default.dirname(bundleRoot), { recursive: true, force: true });
|
|
15483
|
+
} catch {
|
|
15484
|
+
}
|
|
15485
|
+
}
|
|
15486
|
+
}
|
|
15487
|
+
}
|
|
15488
|
+
|
|
15489
|
+
// src/lib/widget-site-install.ts
|
|
15490
|
+
var import_node_fs2 = __toESM(require("node:fs"));
|
|
15491
|
+
var import_node_path2 = __toESM(require("node:path"));
|
|
15492
|
+
var WIDGET_MARKER_START = "<!-- Front Of House widget start -->";
|
|
15493
|
+
var WIDGET_MARKER_END = "<!-- Front Of House widget end -->";
|
|
15494
|
+
var WIDGET_JSX_MARKER_START = "{/* Front Of House widget start */}";
|
|
15495
|
+
var WIDGET_JSX_MARKER_END = "{/* Front Of House widget end */}";
|
|
15496
|
+
function isPathInside(basePath, candidatePath) {
|
|
15497
|
+
const relative4 = import_node_path2.default.relative(basePath, candidatePath);
|
|
15498
|
+
return relative4 === "" || !relative4.startsWith("..") && !import_node_path2.default.isAbsolute(relative4);
|
|
15499
|
+
}
|
|
15500
|
+
function normalizeSnippet(snippet2) {
|
|
15501
|
+
return snippet2.trim();
|
|
15502
|
+
}
|
|
15503
|
+
function escapeRegex(value) {
|
|
15504
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
15505
|
+
}
|
|
15506
|
+
function isJsxLayoutPath(targetPath) {
|
|
15507
|
+
return /\.(tsx|jsx)$/i.test(targetPath);
|
|
15508
|
+
}
|
|
15509
|
+
function markedSnippet(snippet2, targetPath) {
|
|
15510
|
+
const normalized = normalizeSnippet(snippet2);
|
|
15511
|
+
if (isJsxLayoutPath(targetPath)) {
|
|
15512
|
+
return `${WIDGET_JSX_MARKER_START}
|
|
15513
|
+
${normalized}
|
|
15514
|
+
${WIDGET_JSX_MARKER_END}`;
|
|
15515
|
+
}
|
|
15516
|
+
return `${WIDGET_MARKER_START}
|
|
15517
|
+
${normalized}
|
|
15518
|
+
${WIDGET_MARKER_END}`;
|
|
15519
|
+
}
|
|
15520
|
+
function extractChannelPublicKeyFromSnippet(snippet2) {
|
|
15521
|
+
const match = snippet2.match(/\bdata-channel=(["'])([^"']+)\1/i);
|
|
15522
|
+
return match?.[2]?.trim() || void 0;
|
|
15523
|
+
}
|
|
15524
|
+
function installWidgetSnippetIntoShell(source, snippet2, targetPath) {
|
|
15525
|
+
const jsxShell = isJsxLayoutPath(targetPath);
|
|
15526
|
+
const markerStart = jsxShell ? WIDGET_JSX_MARKER_START : WIDGET_MARKER_START;
|
|
15527
|
+
const markerEnd = jsxShell ? WIDGET_JSX_MARKER_END : WIDGET_MARKER_END;
|
|
15528
|
+
const block = markedSnippet(snippet2, targetPath);
|
|
15529
|
+
const existingBlockPattern = new RegExp(
|
|
15530
|
+
`${escapeRegex(markerStart)}[\\s\\S]*?${escapeRegex(markerEnd)}`,
|
|
15531
|
+
"m"
|
|
15532
|
+
);
|
|
15533
|
+
if (existingBlockPattern.test(source)) {
|
|
15534
|
+
const html2 = source.replace(existingBlockPattern, block);
|
|
15535
|
+
return {
|
|
15536
|
+
html: html2,
|
|
15537
|
+
changed: html2 !== source,
|
|
15538
|
+
mode: html2 === source ? "unchanged" : "replaced",
|
|
15539
|
+
markerStart,
|
|
15540
|
+
markerEnd
|
|
15541
|
+
};
|
|
15542
|
+
}
|
|
15543
|
+
if (!/<\/body>/i.test(source)) {
|
|
15544
|
+
throw new FohError({
|
|
15545
|
+
step: "widget.install_site",
|
|
15546
|
+
error: "No </body> tag found in target file.",
|
|
15547
|
+
remediation: "Pass --target for a site shell such as index.html or app/layout.tsx, or paste the embed snippet into the site shell manually."
|
|
15548
|
+
});
|
|
15549
|
+
}
|
|
15550
|
+
const html = source.replace(/<\/body>/i, ` ${block}
|
|
15551
|
+
</body>`);
|
|
15552
|
+
return { html, changed: html !== source, mode: "inserted", markerStart, markerEnd };
|
|
15553
|
+
}
|
|
15554
|
+
function resolveWidgetInstallTarget(siteRoot, target) {
|
|
15555
|
+
const root = import_node_path2.default.resolve(siteRoot || ".");
|
|
15556
|
+
const candidates = target ? [target] : [
|
|
15557
|
+
"index.html",
|
|
15558
|
+
import_node_path2.default.join("apps", "web", "index.html"),
|
|
15559
|
+
import_node_path2.default.join("apps", "frontend", "index.html"),
|
|
15560
|
+
import_node_path2.default.join("apps", "site", "index.html"),
|
|
15561
|
+
import_node_path2.default.join("apps", "landing", "index.html"),
|
|
15562
|
+
import_node_path2.default.join("app", "layout.tsx"),
|
|
15563
|
+
import_node_path2.default.join("app", "layout.jsx"),
|
|
15564
|
+
import_node_path2.default.join("src", "app", "layout.tsx"),
|
|
15565
|
+
import_node_path2.default.join("src", "app", "layout.jsx")
|
|
15566
|
+
];
|
|
15567
|
+
for (const candidate of candidates) {
|
|
15568
|
+
const resolved = import_node_path2.default.resolve(root, candidate);
|
|
15569
|
+
if (!isPathInside(root, resolved)) {
|
|
15570
|
+
throw new FohError({
|
|
15571
|
+
step: "widget.install_site",
|
|
15572
|
+
error: `Refusing to patch a file outside --site-root: ${candidate}`,
|
|
15573
|
+
remediation: "Run the command from the website repository root or pass a target inside --site-root."
|
|
15574
|
+
});
|
|
15575
|
+
}
|
|
15576
|
+
if (import_node_fs2.default.existsSync(resolved) && import_node_fs2.default.statSync(resolved).isFile()) return resolved;
|
|
15577
|
+
}
|
|
15578
|
+
throw new FohError({
|
|
15579
|
+
step: "widget.install_site",
|
|
15580
|
+
error: "Could not find a supported site shell to patch.",
|
|
15581
|
+
remediation: "Pass --target index.html, --target apps/web/index.html, or --target app/layout.tsx from the website repository root."
|
|
15582
|
+
});
|
|
15192
15583
|
}
|
|
15193
15584
|
async function resolveChannelPublicKey(agentId, orgId, apiUrlOverride) {
|
|
15194
15585
|
const data = await apiFetch(
|
|
@@ -15204,7 +15595,209 @@ async function resolveChannelPublicKey(agentId, orgId, apiUrlOverride) {
|
|
|
15204
15595
|
}
|
|
15205
15596
|
return data.channel.public_key;
|
|
15206
15597
|
}
|
|
15207
|
-
async function
|
|
15598
|
+
async function installWidgetSite(opts) {
|
|
15599
|
+
if (!opts.snippet && !opts.agent) {
|
|
15600
|
+
throw new FohError({
|
|
15601
|
+
step: "widget.install_site",
|
|
15602
|
+
error: "Missing --agent or --snippet.",
|
|
15603
|
+
remediation: "Run: foh install <install-link>, or foh install --agent <agent-id> --domains example.com,www.example.com"
|
|
15604
|
+
});
|
|
15605
|
+
}
|
|
15606
|
+
const agentId = opts.agent;
|
|
15607
|
+
const domains = Array.isArray(opts.domains) ? opts.domains.map((domain2) => domain2.trim()).filter(Boolean) : [];
|
|
15608
|
+
let snippet2 = typeof opts.snippet === "string" ? opts.snippet : "";
|
|
15609
|
+
let widgetPublicKey;
|
|
15610
|
+
if (!snippet2) {
|
|
15611
|
+
if (!agentId) {
|
|
15612
|
+
throw new FohError({
|
|
15613
|
+
step: "widget.install_site",
|
|
15614
|
+
error: "Missing --agent.",
|
|
15615
|
+
remediation: "Run: foh install <install-link>, or foh install --agent <agent-id>"
|
|
15616
|
+
});
|
|
15617
|
+
}
|
|
15618
|
+
await apiFetch("/v1/console/channels/widget/ensure", {
|
|
15619
|
+
method: "POST",
|
|
15620
|
+
body: JSON.stringify({ agentId }),
|
|
15621
|
+
orgId: opts.org,
|
|
15622
|
+
apiUrlOverride: opts.apiUrl
|
|
15623
|
+
});
|
|
15624
|
+
if (domains.length > 0) {
|
|
15625
|
+
await apiFetch("/v1/console/channels/widget/domains", {
|
|
15626
|
+
method: "POST",
|
|
15627
|
+
body: JSON.stringify({ agentId, domains }),
|
|
15628
|
+
orgId: opts.org,
|
|
15629
|
+
apiUrlOverride: opts.apiUrl
|
|
15630
|
+
});
|
|
15631
|
+
}
|
|
15632
|
+
const embed = await apiFetch(
|
|
15633
|
+
"/v1/console/channels/widget/embed-snippet",
|
|
15634
|
+
{
|
|
15635
|
+
orgId: opts.org,
|
|
15636
|
+
apiUrlOverride: opts.apiUrl,
|
|
15637
|
+
headers: { "x-agent-id": agentId }
|
|
15638
|
+
}
|
|
15639
|
+
);
|
|
15640
|
+
snippet2 = String(embed.snippet || "").trim();
|
|
15641
|
+
widgetPublicKey = embed.widget_public_key;
|
|
15642
|
+
} else {
|
|
15643
|
+
widgetPublicKey = extractChannelPublicKeyFromSnippet(snippet2);
|
|
15644
|
+
}
|
|
15645
|
+
if (!snippet2) {
|
|
15646
|
+
throw new FohError({
|
|
15647
|
+
step: "widget.install_site",
|
|
15648
|
+
error: "Widget embed snippet is empty.",
|
|
15649
|
+
remediation: "Run: foh widget embed-snippet --agent <agent-id> --json"
|
|
15650
|
+
});
|
|
15651
|
+
}
|
|
15652
|
+
const targetPath = resolveWidgetInstallTarget(opts.siteRoot || ".", opts.target);
|
|
15653
|
+
const before = import_node_fs2.default.readFileSync(targetPath, "utf8");
|
|
15654
|
+
const patch = installWidgetSnippetIntoShell(before, snippet2, targetPath);
|
|
15655
|
+
if (!opts.dryRun && patch.changed) {
|
|
15656
|
+
import_node_fs2.default.writeFileSync(targetPath, patch.html, "utf8");
|
|
15657
|
+
}
|
|
15658
|
+
const relativeTarget = import_node_path2.default.relative(process.cwd(), targetPath).replaceAll("\\", "/");
|
|
15659
|
+
const guidance = buildWidgetInstallGuidance({
|
|
15660
|
+
publicKey: widgetPublicKey ?? null,
|
|
15661
|
+
agentId: opts.agent,
|
|
15662
|
+
domains
|
|
15663
|
+
});
|
|
15664
|
+
return {
|
|
15665
|
+
schema_version: "foh_widget_site_install.v1",
|
|
15666
|
+
status: opts.dryRun ? "dry_run" : "installed",
|
|
15667
|
+
target_path: relativeTarget,
|
|
15668
|
+
absolute_target_path: targetPath,
|
|
15669
|
+
changed: patch.changed,
|
|
15670
|
+
mode: patch.mode,
|
|
15671
|
+
marker_start: patch.markerStart,
|
|
15672
|
+
marker_end: patch.markerEnd,
|
|
15673
|
+
...guidance,
|
|
15674
|
+
snippet: snippet2,
|
|
15675
|
+
next_commands: ["build the website", ...guidance.verification_commands]
|
|
15676
|
+
};
|
|
15677
|
+
}
|
|
15678
|
+
async function ensureWidgetChannelPublicKey(agentId, orgId, apiUrlOverride) {
|
|
15679
|
+
return resolveChannelPublicKey(agentId, orgId, apiUrlOverride);
|
|
15680
|
+
}
|
|
15681
|
+
|
|
15682
|
+
// src/lib/widget-public.ts
|
|
15683
|
+
var SMOKE_TURNS = [
|
|
15684
|
+
"Hello, I need help with a property",
|
|
15685
|
+
"I am interested in buying a 3-bedroom house in the area",
|
|
15686
|
+
"Can I book a viewing for this week?"
|
|
15687
|
+
];
|
|
15688
|
+
function isGenericTroubleReply(reply) {
|
|
15689
|
+
const normalized = reply.trim().toLowerCase();
|
|
15690
|
+
return /\bi'?m sorry\b/.test(normalized) && /having trouble|try again|something went wrong|unable to help right now/.test(normalized);
|
|
15691
|
+
}
|
|
15692
|
+
function isProgressPlaceholderReply(reply) {
|
|
15693
|
+
const normalized = reply.trim().toLowerCase();
|
|
15694
|
+
return /still working on that|one moment|just a moment|please hold|bear with me/.test(normalized);
|
|
15695
|
+
}
|
|
15696
|
+
function normalizeOrigin(originOrHost) {
|
|
15697
|
+
if (!originOrHost) return void 0;
|
|
15698
|
+
const value = originOrHost.trim();
|
|
15699
|
+
if (!value) return void 0;
|
|
15700
|
+
if (/^https?:\/\//i.test(value)) return value;
|
|
15701
|
+
return `https://${value}`;
|
|
15702
|
+
}
|
|
15703
|
+
async function postPublicWidgetInbound(payload, apiUrlOverride, originOverride) {
|
|
15704
|
+
const res = await fetch(`${resolveWidgetApiUrl(apiUrlOverride)}/v1/widget/inbound`, {
|
|
15705
|
+
method: "POST",
|
|
15706
|
+
headers: {
|
|
15707
|
+
"Content-Type": "application/json",
|
|
15708
|
+
...originOverride ? { Origin: originOverride } : {}
|
|
15709
|
+
},
|
|
15710
|
+
body: JSON.stringify(payload)
|
|
15711
|
+
});
|
|
15712
|
+
let body = {};
|
|
15713
|
+
try {
|
|
15714
|
+
body = await res.json();
|
|
15715
|
+
} catch {
|
|
15716
|
+
body = {};
|
|
15717
|
+
}
|
|
15718
|
+
if (!res.ok) {
|
|
15719
|
+
throw new FohError({
|
|
15720
|
+
step: "/v1/widget/inbound",
|
|
15721
|
+
error: String(body?.error || `HTTP ${res.status}`),
|
|
15722
|
+
remediation: String(body?.remediation || "Check the widget public key and API health, then retry."),
|
|
15723
|
+
statusCode: res.status,
|
|
15724
|
+
detail: body
|
|
15725
|
+
});
|
|
15726
|
+
}
|
|
15727
|
+
return body;
|
|
15728
|
+
}
|
|
15729
|
+
async function fetchPublicWidgetInstallVerification(publicKey, apiUrlOverride, originOverride) {
|
|
15730
|
+
const apiUrl = resolveWidgetApiUrl(apiUrlOverride);
|
|
15731
|
+
const url2 = new URL(`${apiUrl}/v1/widget/install-verification`);
|
|
15732
|
+
url2.searchParams.set("key", publicKey);
|
|
15733
|
+
const res = await fetch(url2.toString(), {
|
|
15734
|
+
method: "GET",
|
|
15735
|
+
headers: {
|
|
15736
|
+
...originOverride ? { Origin: originOverride } : {}
|
|
15737
|
+
}
|
|
15738
|
+
});
|
|
15739
|
+
let body = {};
|
|
15740
|
+
try {
|
|
15741
|
+
body = await res.json();
|
|
15742
|
+
} catch {
|
|
15743
|
+
body = {};
|
|
15744
|
+
}
|
|
15745
|
+
if (!res.ok) {
|
|
15746
|
+
throw new FohError({
|
|
15747
|
+
step: "/v1/widget/install-verification",
|
|
15748
|
+
error: String(body?.error || `HTTP ${res.status}`),
|
|
15749
|
+
remediation: String(body?.code === "domain_not_allowed" ? "Use the live website origin with --origin https://www.example.com and confirm the domain allowlist." : body?.code === "invalid_widget_public_key" ? "Check the widget public key or regenerate the install link." : "Check the widget public key, origin, and API health, then retry."),
|
|
15750
|
+
statusCode: res.status,
|
|
15751
|
+
detail: body
|
|
15752
|
+
});
|
|
15753
|
+
}
|
|
15754
|
+
return body;
|
|
15755
|
+
}
|
|
15756
|
+
function normalizeReplyText(value) {
|
|
15757
|
+
return value.trim().toLowerCase();
|
|
15758
|
+
}
|
|
15759
|
+
function matchAnyTerms(reply, terms) {
|
|
15760
|
+
if (!Array.isArray(terms) || terms.length === 0) return true;
|
|
15761
|
+
const normalized = normalizeReplyText(reply);
|
|
15762
|
+
return terms.some((term) => normalized.includes(term.toLowerCase()));
|
|
15763
|
+
}
|
|
15764
|
+
function matchForbiddenTerms(reply, terms) {
|
|
15765
|
+
if (!Array.isArray(terms) || terms.length === 0) return false;
|
|
15766
|
+
const normalized = normalizeReplyText(reply);
|
|
15767
|
+
return terms.some((term) => normalized.includes(term.toLowerCase()));
|
|
15768
|
+
}
|
|
15769
|
+
function resolveSemanticEvalPack(profile) {
|
|
15770
|
+
const semanticEvalProfile = typeof profile?.semantic_eval_profile === "string" ? profile.semantic_eval_profile.trim().toLowerCase() : "";
|
|
15771
|
+
const businessName = typeof profile?.business_name === "string" ? profile.business_name.trim().toLowerCase() : "";
|
|
15772
|
+
const businessCategory = typeof profile?.business_category === "string" ? profile.business_category.trim().toLowerCase() : "";
|
|
15773
|
+
if (semanticEvalProfile === "okii_consulting" || businessName === "okii" || businessCategory === "real_estate_ai_consulting") {
|
|
15774
|
+
return {
|
|
15775
|
+
id: "okii_consulting",
|
|
15776
|
+
scenarios: [
|
|
15777
|
+
{
|
|
15778
|
+
id: "identity",
|
|
15779
|
+
message: "What does OKII do?",
|
|
15780
|
+
required_any: ["okii", "ai", "front", "automation"],
|
|
15781
|
+
forbidden_any: ["estate agency", "estate agent", "book a viewing"]
|
|
15782
|
+
},
|
|
15783
|
+
{
|
|
15784
|
+
id: "non_estate_identity",
|
|
15785
|
+
message: "Are you an estate agent?",
|
|
15786
|
+
required_any: ["not an estate agency", "not an estate agent", "okii"],
|
|
15787
|
+
forbidden_any: ["we can book a viewing", "valuation request"]
|
|
15788
|
+
},
|
|
15789
|
+
{
|
|
15790
|
+
id: "consulting_lead",
|
|
15791
|
+
message: "I run an estate agency and want AI help with calls and leads.",
|
|
15792
|
+
required_any: ["ai", "calls", "leads", "workflow", "automate"],
|
|
15793
|
+
forbidden_any: ["which property", "book the viewing"]
|
|
15794
|
+
}
|
|
15795
|
+
]
|
|
15796
|
+
};
|
|
15797
|
+
}
|
|
15798
|
+
return null;
|
|
15799
|
+
}
|
|
15800
|
+
async function runWidgetSmoke(publicKey, apiUrlOverride, originOverride) {
|
|
15208
15801
|
let conversationId;
|
|
15209
15802
|
const traceIds = [];
|
|
15210
15803
|
const correlationIds = [];
|
|
@@ -15213,34 +15806,31 @@ async function runWidgetSmoke(publicKey, apiUrlOverride) {
|
|
|
15213
15806
|
const message = SMOKE_TURNS[i];
|
|
15214
15807
|
const start = Date.now();
|
|
15215
15808
|
try {
|
|
15216
|
-
const data = await
|
|
15217
|
-
|
|
15218
|
-
|
|
15219
|
-
|
|
15220
|
-
|
|
15221
|
-
channel_public_key: publicKey,
|
|
15222
|
-
message_body: message,
|
|
15223
|
-
preview: true,
|
|
15224
|
-
...conversationId ? { conversation_id: conversationId } : {}
|
|
15225
|
-
}),
|
|
15226
|
-
apiUrlOverride
|
|
15227
|
-
}
|
|
15228
|
-
);
|
|
15809
|
+
const data = await postPublicWidgetInbound({
|
|
15810
|
+
channel_public_key: publicKey,
|
|
15811
|
+
message_body: message,
|
|
15812
|
+
...conversationId ? { conversation_id: conversationId } : {}
|
|
15813
|
+
}, apiUrlOverride, originOverride);
|
|
15229
15814
|
const latencyMs = Date.now() - start;
|
|
15230
15815
|
if (!data.reply) throw new Error("Empty reply");
|
|
15231
15816
|
conversationId = data.conversationId;
|
|
15232
15817
|
if (data.trace_id) traceIds.push(data.trace_id);
|
|
15233
15818
|
if (data.correlation_id) correlationIds.push(data.correlation_id);
|
|
15234
15819
|
const genericTroubleReply = isGenericTroubleReply(data.reply);
|
|
15820
|
+
const placeholderReply = isProgressPlaceholderReply(data.reply);
|
|
15821
|
+
const unhealthyReply = genericTroubleReply || placeholderReply;
|
|
15235
15822
|
turns.push({
|
|
15236
15823
|
turn: i + 1,
|
|
15237
15824
|
message,
|
|
15238
|
-
ok: !
|
|
15825
|
+
ok: !unhealthyReply,
|
|
15239
15826
|
latency_ms: latencyMs,
|
|
15240
15827
|
reply: data.reply,
|
|
15241
15828
|
...genericTroubleReply ? {
|
|
15242
15829
|
reason_code: "widget_generic_trouble_reply",
|
|
15243
15830
|
error: "Widget returned a generic trouble reply instead of advancing the customer request."
|
|
15831
|
+
} : placeholderReply ? {
|
|
15832
|
+
reason_code: "widget_progress_placeholder_reply",
|
|
15833
|
+
error: "Widget returned a placeholder holding reply instead of advancing the customer request."
|
|
15244
15834
|
} : {},
|
|
15245
15835
|
conversation_id: data.conversationId,
|
|
15246
15836
|
trace_id: data.trace_id ?? null,
|
|
@@ -15261,6 +15851,119 @@ async function runWidgetSmoke(publicKey, apiUrlOverride) {
|
|
|
15261
15851
|
turns
|
|
15262
15852
|
};
|
|
15263
15853
|
}
|
|
15854
|
+
async function runWidgetSemanticEval(publicKey, apiUrlOverride, originOverride, profile) {
|
|
15855
|
+
const pack = resolveSemanticEvalPack(profile);
|
|
15856
|
+
if (!pack) {
|
|
15857
|
+
return {
|
|
15858
|
+
pack_id: null,
|
|
15859
|
+
passed: 0,
|
|
15860
|
+
failed: 0,
|
|
15861
|
+
turns: []
|
|
15862
|
+
};
|
|
15863
|
+
}
|
|
15864
|
+
const turns = [];
|
|
15865
|
+
for (const scenario of pack.scenarios) {
|
|
15866
|
+
try {
|
|
15867
|
+
const data = await postPublicWidgetInbound({
|
|
15868
|
+
channel_public_key: publicKey,
|
|
15869
|
+
message_body: scenario.message
|
|
15870
|
+
}, apiUrlOverride, originOverride);
|
|
15871
|
+
const reply = String(data.reply || "");
|
|
15872
|
+
const missingRequired = !matchAnyTerms(reply, scenario.required_any);
|
|
15873
|
+
const foundForbidden = matchForbiddenTerms(reply, scenario.forbidden_any);
|
|
15874
|
+
const ok = !missingRequired && !foundForbidden && !isGenericTroubleReply(reply) && !isProgressPlaceholderReply(reply);
|
|
15875
|
+
turns.push({
|
|
15876
|
+
scenario_id: scenario.id,
|
|
15877
|
+
ok,
|
|
15878
|
+
message: scenario.message,
|
|
15879
|
+
reply,
|
|
15880
|
+
...ok ? {} : {
|
|
15881
|
+
reason_code: missingRequired ? "widget_semantic_required_terms_missing" : foundForbidden ? "widget_semantic_forbidden_terms_found" : isGenericTroubleReply(reply) ? "widget_generic_trouble_reply" : "widget_progress_placeholder_reply"
|
|
15882
|
+
},
|
|
15883
|
+
trace_id: data.trace_id ?? null,
|
|
15884
|
+
correlation_id: data.correlation_id ?? null
|
|
15885
|
+
});
|
|
15886
|
+
} catch (err) {
|
|
15887
|
+
turns.push({
|
|
15888
|
+
scenario_id: scenario.id,
|
|
15889
|
+
ok: false,
|
|
15890
|
+
message: scenario.message,
|
|
15891
|
+
error: err?.message || String(err),
|
|
15892
|
+
reason_code: "widget_semantic_request_failed"
|
|
15893
|
+
});
|
|
15894
|
+
}
|
|
15895
|
+
}
|
|
15896
|
+
return {
|
|
15897
|
+
pack_id: pack.id,
|
|
15898
|
+
passed: turns.filter((turn) => turn.ok).length,
|
|
15899
|
+
failed: turns.filter((turn) => !turn.ok).length,
|
|
15900
|
+
turns
|
|
15901
|
+
};
|
|
15902
|
+
}
|
|
15903
|
+
async function runWidgetInstallVerification(publicKey, apiUrlOverride, originOverride) {
|
|
15904
|
+
const checks = [];
|
|
15905
|
+
try {
|
|
15906
|
+
const config2 = await fetchPublicWidgetInstallVerification(publicKey, apiUrlOverride, originOverride);
|
|
15907
|
+
checks.push({
|
|
15908
|
+
name: "install_config",
|
|
15909
|
+
status: "pass",
|
|
15910
|
+
reason_code: "install_config_ok",
|
|
15911
|
+
summary: "Public install verification config is reachable and the origin passed the domain gate.",
|
|
15912
|
+
detail: config2
|
|
15913
|
+
});
|
|
15914
|
+
} catch (error2) {
|
|
15915
|
+
checks.push({
|
|
15916
|
+
name: "install_config",
|
|
15917
|
+
status: "fail",
|
|
15918
|
+
reason_code: error2?.detail?.code === "domain_not_allowed" ? "domain_not_allowed" : error2?.detail?.code === "invalid_widget_public_key" ? "invalid_widget_public_key" : "install_config_request_failed",
|
|
15919
|
+
summary: error2?.message || String(error2),
|
|
15920
|
+
detail: error2?.detail || null,
|
|
15921
|
+
next_command: "Confirm the live site origin and widget public key, then rerun verification."
|
|
15922
|
+
});
|
|
15923
|
+
checks.push({
|
|
15924
|
+
name: "widget_smoke",
|
|
15925
|
+
status: "skipped",
|
|
15926
|
+
reason_code: "install_config_required",
|
|
15927
|
+
summary: "Skipped because install-config verification failed.",
|
|
15928
|
+
next_command: "Fix install-config verification first, then rerun this command."
|
|
15929
|
+
});
|
|
15930
|
+
return {
|
|
15931
|
+
schema_version: "foh_widget_public_install_verification.v1",
|
|
15932
|
+
status: "fail",
|
|
15933
|
+
widget_public_key: publicKey,
|
|
15934
|
+
origin: originOverride ?? null,
|
|
15935
|
+
checks
|
|
15936
|
+
};
|
|
15937
|
+
}
|
|
15938
|
+
const smoke = await runWidgetSmoke(publicKey, apiUrlOverride, originOverride);
|
|
15939
|
+
if (smoke.failed > 0) {
|
|
15940
|
+
checks.push({
|
|
15941
|
+
name: "widget_smoke",
|
|
15942
|
+
status: "fail",
|
|
15943
|
+
reason_code: "widget_smoke_failed",
|
|
15944
|
+
summary: `${smoke.failed} widget smoke turn(s) failed.`,
|
|
15945
|
+
detail: smoke,
|
|
15946
|
+
next_command: `Run: foh widget smoke --channel ${publicKey} --json${originOverride ? ` --origin ${originOverride}` : ""}`
|
|
15947
|
+
});
|
|
15948
|
+
} else {
|
|
15949
|
+
checks.push({
|
|
15950
|
+
name: "widget_smoke",
|
|
15951
|
+
status: "pass",
|
|
15952
|
+
reason_code: "widget_smoke_ok",
|
|
15953
|
+
summary: "Widget smoke passed from the public builder-install path.",
|
|
15954
|
+
detail: smoke
|
|
15955
|
+
});
|
|
15956
|
+
}
|
|
15957
|
+
return {
|
|
15958
|
+
schema_version: "foh_widget_public_install_verification.v1",
|
|
15959
|
+
status: checks.some((check2) => check2.status === "fail") ? "fail" : "pass",
|
|
15960
|
+
widget_public_key: publicKey,
|
|
15961
|
+
origin: originOverride ?? null,
|
|
15962
|
+
checks
|
|
15963
|
+
};
|
|
15964
|
+
}
|
|
15965
|
+
|
|
15966
|
+
// src/commands/widget.ts
|
|
15264
15967
|
function registerWidget(program3) {
|
|
15265
15968
|
const widget = program3.command("widget").description("Manage the web widget channel");
|
|
15266
15969
|
widget.command("ensure").description("Ensure a widget channel exists for an agent (idempotent)").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").action(async (opts) => withCommandErrorHandling(async () => {
|
|
@@ -15274,9 +15977,9 @@ function registerWidget(program3) {
|
|
|
15274
15977
|
}));
|
|
15275
15978
|
widget.command("set-domains").description("Update widget domain allowlist (idempotent)").requiredOption("--domains <d1,d2>", "Comma-separated domain list").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").action(async (opts) => withCommandErrorHandling(async () => {
|
|
15276
15979
|
const domains = opts.domains.split(",").map((domain2) => domain2.trim());
|
|
15277
|
-
const data = await apiFetch(
|
|
15278
|
-
method: "
|
|
15279
|
-
body: JSON.stringify({
|
|
15980
|
+
const data = await apiFetch("/v1/console/channels/widget/domains", {
|
|
15981
|
+
method: "POST",
|
|
15982
|
+
body: JSON.stringify({ agentId: opts.agent, domains }),
|
|
15280
15983
|
orgId: opts.org,
|
|
15281
15984
|
apiUrlOverride: opts.apiUrl
|
|
15282
15985
|
});
|
|
@@ -15294,8 +15997,47 @@ function registerWidget(program3) {
|
|
|
15294
15997
|
process.stdout.write(data.snippet + "\n");
|
|
15295
15998
|
}
|
|
15296
15999
|
}));
|
|
16000
|
+
widget.command("install-link").description("Mint a short-lived one-time website install link for an agent").requiredOption("--agent <id>", "Agent ID").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--domains <d1,d2>", "Optional comma-separated widget domain allowlist to set before minting").option("--expires-in <seconds>", "Install-link TTL in seconds", "3600").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
|
|
16001
|
+
const domains = typeof opts.domains === "string" ? opts.domains.split(",").map((domain2) => domain2.trim()).filter(Boolean) : [];
|
|
16002
|
+
const data = await apiFetch("/v1/console/channels/widget/install-link", {
|
|
16003
|
+
method: "POST",
|
|
16004
|
+
body: JSON.stringify({
|
|
16005
|
+
agentId: opts.agent,
|
|
16006
|
+
expiresInSec: Number(opts.expiresIn),
|
|
16007
|
+
...domains.length > 0 ? { domains } : {}
|
|
16008
|
+
}),
|
|
16009
|
+
orgId: opts.org,
|
|
16010
|
+
apiUrlOverride: opts.apiUrl
|
|
16011
|
+
});
|
|
16012
|
+
const installUrl = typeof data?.install_url === "string" ? String(data.install_url) : null;
|
|
16013
|
+
if (installUrl && domains.length > 0) {
|
|
16014
|
+
const url2 = new URL(installUrl);
|
|
16015
|
+
url2.searchParams.delete("domains");
|
|
16016
|
+
for (const domain2 of domains) {
|
|
16017
|
+
url2.searchParams.append("domains", domain2);
|
|
16018
|
+
}
|
|
16019
|
+
;
|
|
16020
|
+
data.install_url = url2.toString();
|
|
16021
|
+
data.domains = domains;
|
|
16022
|
+
}
|
|
16023
|
+
format(data, { json: opts.json ?? false });
|
|
16024
|
+
}));
|
|
16025
|
+
widget.command("install-site").description("Install or update the hosted widget snippet in a static/Vite website shell").option("--agent <id>", "Agent ID; required unless --snippet is supplied").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--domains <d1,d2>", "Optional comma-separated widget domain allowlist").option("--site-root <path>", "Website repository root", ".").option("--target <path>", "HTML shell to patch, relative to --site-root").option("--snippet <html>", "Embed snippet to install without calling the FOH API").option("--api-url <url>", "API base URL override").option("--dry-run", "Resolve and report the patch without writing the file").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
|
|
16026
|
+
const domains = typeof opts.domains === "string" ? opts.domains.split(",").map((domain2) => domain2.trim()).filter(Boolean) : [];
|
|
16027
|
+
const result = await installWidgetSite({
|
|
16028
|
+
agent: opts.agent,
|
|
16029
|
+
org: opts.org,
|
|
16030
|
+
domains,
|
|
16031
|
+
siteRoot: opts.siteRoot,
|
|
16032
|
+
target: opts.target,
|
|
16033
|
+
snippet: opts.snippet,
|
|
16034
|
+
apiUrl: opts.apiUrl,
|
|
16035
|
+
dryRun: opts.dryRun
|
|
16036
|
+
});
|
|
16037
|
+
format(result, { json: opts.json ?? false });
|
|
16038
|
+
}));
|
|
15297
16039
|
widget.command("chat").description("Send a single message through the widget and return the reply").requiredOption("--agent <id>", "Agent ID").requiredOption("--message <text>", "Message to send").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 () => {
|
|
15298
|
-
const publicKey = await
|
|
16040
|
+
const publicKey = await ensureWidgetChannelPublicKey(opts.agent, opts.org, opts.apiUrl);
|
|
15299
16041
|
const start = Date.now();
|
|
15300
16042
|
const data = await apiFetch("/v1/widget/inbound", {
|
|
15301
16043
|
method: "POST",
|
|
@@ -15316,12 +16058,52 @@ function registerWidget(program3) {
|
|
|
15316
16058
|
};
|
|
15317
16059
|
format(result, { json: opts.json ?? false });
|
|
15318
16060
|
}));
|
|
15319
|
-
widget.command("smoke").description("Run a canned 3-turn conversation to verify end-to-end widget health").
|
|
15320
|
-
const publicKey = await
|
|
15321
|
-
|
|
16061
|
+
widget.command("smoke").description("Run a canned 3-turn conversation to verify end-to-end widget health").option("--agent <id>", "Agent ID").option("--channel <publicKey>", "Widget public key; skips operator auth").option("--origin <urlOrHost>", "Website origin to simulate for domain allowlists, e.g. https://www.example.com").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 () => {
|
|
16062
|
+
const publicKey = typeof opts.channel === "string" && opts.channel.trim() ? opts.channel.trim() : opts.agent ? await ensureWidgetChannelPublicKey(opts.agent, opts.org, opts.apiUrl) : null;
|
|
16063
|
+
if (!publicKey) {
|
|
16064
|
+
throw new FohError({
|
|
16065
|
+
step: "widget.smoke",
|
|
16066
|
+
error: "Missing --agent or --channel.",
|
|
16067
|
+
remediation: "Run: foh widget smoke --channel <public-key> --json, or foh widget smoke --agent <agent-id> --json"
|
|
16068
|
+
});
|
|
16069
|
+
}
|
|
16070
|
+
const origin = normalizeOrigin(typeof opts.origin === "string" ? opts.origin : void 0);
|
|
16071
|
+
const summary = await runWidgetSmoke(publicKey, opts.apiUrl, origin);
|
|
15322
16072
|
format(summary, { json: opts.json ?? false });
|
|
15323
16073
|
if (summary.failed > 0) markCommandFailed(1);
|
|
15324
16074
|
}));
|
|
16075
|
+
widget.command("verify-install").description("Run the public builder-install verification path: config/domain gate plus runtime smoke").option("--agent <id>", "Agent ID").option("--channel <publicKey>", "Widget public key; skips operator auth").option("--origin <urlOrHost>", "Live website origin to verify against, e.g. https://www.example.com").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 () => {
|
|
16076
|
+
const publicKey = typeof opts.channel === "string" && opts.channel.trim() ? opts.channel.trim() : opts.agent ? await ensureWidgetChannelPublicKey(opts.agent, opts.org, opts.apiUrl) : null;
|
|
16077
|
+
if (!publicKey) {
|
|
16078
|
+
throw new FohError({
|
|
16079
|
+
step: "widget.verify_install",
|
|
16080
|
+
error: "Missing --agent or --channel.",
|
|
16081
|
+
remediation: "Run: foh widget verify-install --channel <public-key> --origin https://www.example.com --json"
|
|
16082
|
+
});
|
|
16083
|
+
}
|
|
16084
|
+
const origin = normalizeOrigin(typeof opts.origin === "string" ? opts.origin : void 0);
|
|
16085
|
+
const summary = await runWidgetInstallVerification(publicKey, opts.apiUrl, origin);
|
|
16086
|
+
format(summary, { json: opts.json ?? false });
|
|
16087
|
+
if (summary.status !== "pass") markCommandFailed(1);
|
|
16088
|
+
}));
|
|
16089
|
+
widget.command("wordpress-plugin").description("Generate a preconfigured WordPress plugin bundle for the hosted widget").option("--agent <id>", "Agent ID").option("--channel <publicKey>", "Widget public key; skips operator auth").requiredOption("--out <path>", "Output directory or .zip path for the plugin bundle").option("--plugin-name <name>", "WordPress plugin display name", DEFAULT_WORDPRESS_PLUGIN_NAME).option("--org <id>", "Org ID (default: stored org from foh org use)").option("--api-url <url>", "API base URL override").option("--dry-run", "Resolve the bundle without writing files").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
|
|
16090
|
+
const publicKey = typeof opts.channel === "string" && opts.channel.trim() ? opts.channel.trim() : opts.agent ? await ensureWidgetChannelPublicKey(opts.agent, opts.org, opts.apiUrl) : null;
|
|
16091
|
+
if (!publicKey) {
|
|
16092
|
+
throw new FohError({
|
|
16093
|
+
step: "widget.wordpress_plugin",
|
|
16094
|
+
error: "Missing --agent or --channel.",
|
|
16095
|
+
remediation: "Run: foh widget wordpress-plugin --channel <public-key> --out <path> --json"
|
|
16096
|
+
});
|
|
16097
|
+
}
|
|
16098
|
+
const result = buildWordPressPluginBundle({
|
|
16099
|
+
publicKey,
|
|
16100
|
+
apiUrl: opts.apiUrl,
|
|
16101
|
+
outPath: opts.out,
|
|
16102
|
+
pluginName: opts.pluginName,
|
|
16103
|
+
dryRun: Boolean(opts.dryRun)
|
|
16104
|
+
});
|
|
16105
|
+
format(result, { json: opts.json ?? false });
|
|
16106
|
+
}));
|
|
15325
16107
|
}
|
|
15326
16108
|
|
|
15327
16109
|
// src/commands/channel-instagram.ts
|
|
@@ -15428,8 +16210,8 @@ function buildReasonedNextSteps({
|
|
|
15428
16210
|
}
|
|
15429
16211
|
|
|
15430
16212
|
// src/commands/channel-whatsapp-live-proof.ts
|
|
15431
|
-
var
|
|
15432
|
-
var
|
|
16213
|
+
var import_node_fs3 = require("node:fs");
|
|
16214
|
+
var path3 = __toESM(require("node:path"));
|
|
15433
16215
|
|
|
15434
16216
|
// src/lib/channel-live-proof-evaluator.mjs
|
|
15435
16217
|
function normalizeStatusValue(value) {
|
|
@@ -15514,8 +16296,8 @@ function resolveLiveProof({
|
|
|
15514
16296
|
freshness: { timestamp_field: null, timestamp: null, age_hours: null, max_age_hours: maxAgeHours }
|
|
15515
16297
|
};
|
|
15516
16298
|
}
|
|
15517
|
-
const artifactPath =
|
|
15518
|
-
if (!(0,
|
|
16299
|
+
const artifactPath = path3.resolve(process.cwd(), artifactPathRaw);
|
|
16300
|
+
if (!(0, import_node_fs3.existsSync)(artifactPath)) {
|
|
15519
16301
|
return {
|
|
15520
16302
|
requested: true,
|
|
15521
16303
|
artifact_path: artifactPath,
|
|
@@ -15527,7 +16309,7 @@ function resolveLiveProof({
|
|
|
15527
16309
|
}
|
|
15528
16310
|
let payload;
|
|
15529
16311
|
try {
|
|
15530
|
-
payload = JSON.parse((0,
|
|
16312
|
+
payload = JSON.parse((0, import_node_fs3.readFileSync)(artifactPath, "utf8"));
|
|
15531
16313
|
} catch {
|
|
15532
16314
|
return {
|
|
15533
16315
|
requested: true,
|
|
@@ -15556,7 +16338,7 @@ function resolveLiveProof({
|
|
|
15556
16338
|
}
|
|
15557
16339
|
|
|
15558
16340
|
// src/commands/channel-whatsapp-onboarding.ts
|
|
15559
|
-
var
|
|
16341
|
+
var import_node_fs4 = require("node:fs");
|
|
15560
16342
|
|
|
15561
16343
|
// src/commands/channel-whatsapp-setup.ts
|
|
15562
16344
|
var import_node_crypto = require("node:crypto");
|
|
@@ -15695,7 +16477,7 @@ function parseBatchManifest(manifestPathRaw) {
|
|
|
15695
16477
|
});
|
|
15696
16478
|
}
|
|
15697
16479
|
try {
|
|
15698
|
-
const raw = (0,
|
|
16480
|
+
const raw = (0, import_node_fs4.readFileSync)(manifestPath, "utf8");
|
|
15699
16481
|
const parsed = JSON.parse(raw);
|
|
15700
16482
|
if (!Array.isArray(parsed.businesses) || parsed.businesses.length === 0) {
|
|
15701
16483
|
throw new Error("manifest_missing_businesses");
|
|
@@ -16513,7 +17295,7 @@ function registerTools(program3) {
|
|
|
16513
17295
|
}
|
|
16514
17296
|
|
|
16515
17297
|
// src/commands/mcp-serve.ts
|
|
16516
|
-
var
|
|
17298
|
+
var import_node_child_process2 = require("node:child_process");
|
|
16517
17299
|
|
|
16518
17300
|
// ../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v3/helpers/util.js
|
|
16519
17301
|
var util;
|
|
@@ -16874,8 +17656,8 @@ function getErrorMap() {
|
|
|
16874
17656
|
|
|
16875
17657
|
// ../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v3/helpers/parseUtil.js
|
|
16876
17658
|
var makeIssue = (params) => {
|
|
16877
|
-
const { data, path:
|
|
16878
|
-
const fullPath = [...
|
|
17659
|
+
const { data, path: path5, errorMaps, issueData } = params;
|
|
17660
|
+
const fullPath = [...path5, ...issueData.path || []];
|
|
16879
17661
|
const fullIssue = {
|
|
16880
17662
|
...issueData,
|
|
16881
17663
|
path: fullPath
|
|
@@ -16990,11 +17772,11 @@ var errorUtil;
|
|
|
16990
17772
|
|
|
16991
17773
|
// ../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v3/types.js
|
|
16992
17774
|
var ParseInputLazyPath = class {
|
|
16993
|
-
constructor(parent, value,
|
|
17775
|
+
constructor(parent, value, path5, key) {
|
|
16994
17776
|
this._cachedPath = [];
|
|
16995
17777
|
this.parent = parent;
|
|
16996
17778
|
this.data = value;
|
|
16997
|
-
this._path =
|
|
17779
|
+
this._path = path5;
|
|
16998
17780
|
this._key = key;
|
|
16999
17781
|
}
|
|
17000
17782
|
get path() {
|
|
@@ -20492,7 +21274,7 @@ __export(util_exports, {
|
|
|
20492
21274
|
createTransparentProxy: () => createTransparentProxy,
|
|
20493
21275
|
defineLazy: () => defineLazy,
|
|
20494
21276
|
esc: () => esc,
|
|
20495
|
-
escapeRegex: () =>
|
|
21277
|
+
escapeRegex: () => escapeRegex2,
|
|
20496
21278
|
extend: () => extend3,
|
|
20497
21279
|
finalizeIssue: () => finalizeIssue,
|
|
20498
21280
|
floatSafeRemainder: () => floatSafeRemainder2,
|
|
@@ -20640,10 +21422,10 @@ function mergeDefs(...defs) {
|
|
|
20640
21422
|
function cloneDef(schema2) {
|
|
20641
21423
|
return mergeDefs(schema2._zod.def);
|
|
20642
21424
|
}
|
|
20643
|
-
function getElementAtPath(obj,
|
|
20644
|
-
if (!
|
|
21425
|
+
function getElementAtPath(obj, path5) {
|
|
21426
|
+
if (!path5)
|
|
20645
21427
|
return obj;
|
|
20646
|
-
return
|
|
21428
|
+
return path5.reduce((acc, key) => acc?.[key], obj);
|
|
20647
21429
|
}
|
|
20648
21430
|
function promiseAllObject(promisesObj) {
|
|
20649
21431
|
const keys = Object.keys(promisesObj);
|
|
@@ -20765,7 +21547,7 @@ var getParsedType2 = (data) => {
|
|
|
20765
21547
|
};
|
|
20766
21548
|
var propertyKeyTypes = /* @__PURE__ */ new Set(["string", "number", "symbol"]);
|
|
20767
21549
|
var primitiveTypes = /* @__PURE__ */ new Set(["string", "number", "bigint", "boolean", "symbol", "undefined"]);
|
|
20768
|
-
function
|
|
21550
|
+
function escapeRegex2(str2) {
|
|
20769
21551
|
return str2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
20770
21552
|
}
|
|
20771
21553
|
function clone(inst, def, params) {
|
|
@@ -21026,11 +21808,11 @@ function aborted(x, startIndex = 0) {
|
|
|
21026
21808
|
}
|
|
21027
21809
|
return false;
|
|
21028
21810
|
}
|
|
21029
|
-
function prefixIssues(
|
|
21811
|
+
function prefixIssues(path5, issues) {
|
|
21030
21812
|
return issues.map((iss) => {
|
|
21031
21813
|
var _a2;
|
|
21032
21814
|
(_a2 = iss).path ?? (_a2.path = []);
|
|
21033
|
-
iss.path.unshift(
|
|
21815
|
+
iss.path.unshift(path5);
|
|
21034
21816
|
return iss;
|
|
21035
21817
|
});
|
|
21036
21818
|
}
|
|
@@ -21384,7 +22166,7 @@ function emoji() {
|
|
|
21384
22166
|
var ipv4 = /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/;
|
|
21385
22167
|
var ipv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/;
|
|
21386
22168
|
var mac = (delimiter) => {
|
|
21387
|
-
const escapedDelim =
|
|
22169
|
+
const escapedDelim = escapeRegex2(delimiter ?? ":");
|
|
21388
22170
|
return new RegExp(`^(?:[0-9A-F]{2}${escapedDelim}){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}${escapedDelim}){5}[0-9a-f]{2}$`);
|
|
21389
22171
|
};
|
|
21390
22172
|
var cidrv4 = /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$/;
|
|
@@ -21885,7 +22667,7 @@ var $ZodCheckUpperCase = /* @__PURE__ */ $constructor("$ZodCheckUpperCase", (ins
|
|
|
21885
22667
|
});
|
|
21886
22668
|
var $ZodCheckIncludes = /* @__PURE__ */ $constructor("$ZodCheckIncludes", (inst, def) => {
|
|
21887
22669
|
$ZodCheck.init(inst, def);
|
|
21888
|
-
const escapedRegex =
|
|
22670
|
+
const escapedRegex = escapeRegex2(def.includes);
|
|
21889
22671
|
const pattern = new RegExp(typeof def.position === "number" ? `^.{${def.position}}${escapedRegex}` : escapedRegex);
|
|
21890
22672
|
def.pattern = pattern;
|
|
21891
22673
|
inst._zod.onattach.push((inst2) => {
|
|
@@ -21909,7 +22691,7 @@ var $ZodCheckIncludes = /* @__PURE__ */ $constructor("$ZodCheckIncludes", (inst,
|
|
|
21909
22691
|
});
|
|
21910
22692
|
var $ZodCheckStartsWith = /* @__PURE__ */ $constructor("$ZodCheckStartsWith", (inst, def) => {
|
|
21911
22693
|
$ZodCheck.init(inst, def);
|
|
21912
|
-
const pattern = new RegExp(`^${
|
|
22694
|
+
const pattern = new RegExp(`^${escapeRegex2(def.prefix)}.*`);
|
|
21913
22695
|
def.pattern ?? (def.pattern = pattern);
|
|
21914
22696
|
inst._zod.onattach.push((inst2) => {
|
|
21915
22697
|
const bag = inst2._zod.bag;
|
|
@@ -21932,7 +22714,7 @@ var $ZodCheckStartsWith = /* @__PURE__ */ $constructor("$ZodCheckStartsWith", (i
|
|
|
21932
22714
|
});
|
|
21933
22715
|
var $ZodCheckEndsWith = /* @__PURE__ */ $constructor("$ZodCheckEndsWith", (inst, def) => {
|
|
21934
22716
|
$ZodCheck.init(inst, def);
|
|
21935
|
-
const pattern = new RegExp(`.*${
|
|
22717
|
+
const pattern = new RegExp(`.*${escapeRegex2(def.suffix)}$`);
|
|
21936
22718
|
def.pattern ?? (def.pattern = pattern);
|
|
21937
22719
|
inst._zod.onattach.push((inst2) => {
|
|
21938
22720
|
const bag = inst2._zod.bag;
|
|
@@ -23474,7 +24256,7 @@ var $ZodEnum = /* @__PURE__ */ $constructor("$ZodEnum", (inst, def) => {
|
|
|
23474
24256
|
const values = getEnumValues(def.entries);
|
|
23475
24257
|
const valuesSet = new Set(values);
|
|
23476
24258
|
inst._zod.values = valuesSet;
|
|
23477
|
-
inst._zod.pattern = new RegExp(`^(${values.filter((k) => propertyKeyTypes.has(typeof k)).map((o) => typeof o === "string" ?
|
|
24259
|
+
inst._zod.pattern = new RegExp(`^(${values.filter((k) => propertyKeyTypes.has(typeof k)).map((o) => typeof o === "string" ? escapeRegex2(o) : o.toString()).join("|")})$`);
|
|
23478
24260
|
inst._zod.parse = (payload, _ctx) => {
|
|
23479
24261
|
const input = payload.value;
|
|
23480
24262
|
if (valuesSet.has(input)) {
|
|
@@ -23496,7 +24278,7 @@ var $ZodLiteral = /* @__PURE__ */ $constructor("$ZodLiteral", (inst, def) => {
|
|
|
23496
24278
|
}
|
|
23497
24279
|
const values = new Set(def.values);
|
|
23498
24280
|
inst._zod.values = values;
|
|
23499
|
-
inst._zod.pattern = new RegExp(`^(${def.values.map((o) => typeof o === "string" ?
|
|
24281
|
+
inst._zod.pattern = new RegExp(`^(${def.values.map((o) => typeof o === "string" ? escapeRegex2(o) : o ? escapeRegex2(o.toString()) : String(o)).join("|")})$`);
|
|
23500
24282
|
inst._zod.parse = (payload, _ctx) => {
|
|
23501
24283
|
const input = payload.value;
|
|
23502
24284
|
if (values.has(input)) {
|
|
@@ -23852,7 +24634,7 @@ var $ZodTemplateLiteral = /* @__PURE__ */ $constructor("$ZodTemplateLiteral", (i
|
|
|
23852
24634
|
const end = source.endsWith("$") ? source.length - 1 : source.length;
|
|
23853
24635
|
regexParts.push(source.slice(start, end));
|
|
23854
24636
|
} else if (part === null || primitiveTypes.has(typeof part)) {
|
|
23855
|
-
regexParts.push(
|
|
24637
|
+
regexParts.push(escapeRegex2(`${part}`));
|
|
23856
24638
|
} else {
|
|
23857
24639
|
throw new Error(`Invalid template literal part: ${part}`);
|
|
23858
24640
|
}
|
|
@@ -32941,7 +33723,7 @@ var StdioServerTransport = class {
|
|
|
32941
33723
|
};
|
|
32942
33724
|
|
|
32943
33725
|
// src/lib/cli-version.ts
|
|
32944
|
-
var injectedVersion = true ? String("0.1.
|
|
33726
|
+
var injectedVersion = true ? String("0.1.89").trim() : "";
|
|
32945
33727
|
var envVersion = String(process.env.FOH_CLI_VERSION || process.env.npm_package_version || "").trim();
|
|
32946
33728
|
var CLI_VERSION = injectedVersion || envVersion || "0.0.0-dev";
|
|
32947
33729
|
|
|
@@ -33129,7 +33911,7 @@ async function runFohCli(params) {
|
|
|
33129
33911
|
}
|
|
33130
33912
|
const command = `foh ${effectiveArgv.join(" ")}`;
|
|
33131
33913
|
return await new Promise((resolve16) => {
|
|
33132
|
-
const child = (0,
|
|
33914
|
+
const child = (0, import_node_child_process2.spawn)(process.execPath, [cliEntry, ...effectiveArgv], {
|
|
33133
33915
|
stdio: ["ignore", "pipe", "pipe"],
|
|
33134
33916
|
env: {
|
|
33135
33917
|
...process.env,
|
|
@@ -33207,6 +33989,19 @@ var TOOL_EXECUTION_SCHEMA = {
|
|
|
33207
33989
|
var APPROVAL_TOKEN_SCHEMA = {
|
|
33208
33990
|
approval_token: string2().min(1).max(256).optional()
|
|
33209
33991
|
};
|
|
33992
|
+
var MCP_OBJECTIVE_INDUSTRIES = ["real_estate", "restaurant", "general"];
|
|
33993
|
+
var MCP_TEMPLATE_CATEGORIES = ["buyer", "seller", "landlord", "commercial"];
|
|
33994
|
+
var MCP_PROOF_MISSIONS = ["setup", "widget", "voice", "publish"];
|
|
33995
|
+
var MCP_CONTACT_PATHS = ["auto", "managed", "byon"];
|
|
33996
|
+
var MCP_MUTATION_MODES = ["read-only", "ensure"];
|
|
33997
|
+
function appendOptionalArg(argv, flag, value) {
|
|
33998
|
+
const text = asOptionalString(value);
|
|
33999
|
+
if (text) argv.push(flag, text);
|
|
34000
|
+
}
|
|
34001
|
+
function appendOptionalCsvArg(argv, flag, value) {
|
|
34002
|
+
const values = asStringArray(value);
|
|
34003
|
+
if (values.length > 0) argv.push(flag, values.join(","));
|
|
34004
|
+
}
|
|
33210
34005
|
var TYPED_TOOL_SPECS = [
|
|
33211
34006
|
{
|
|
33212
34007
|
name: "foh_auth_whoami",
|
|
@@ -33263,6 +34058,193 @@ var TYPED_TOOL_SPECS = [
|
|
|
33263
34058
|
return argv;
|
|
33264
34059
|
}
|
|
33265
34060
|
},
|
|
34061
|
+
{
|
|
34062
|
+
name: "foh_templates_list",
|
|
34063
|
+
title: "FOH Templates List",
|
|
34064
|
+
description: "List available setup templates, optionally filtered by category.",
|
|
34065
|
+
commandKey: "templates list",
|
|
34066
|
+
risk: "read",
|
|
34067
|
+
inputSchema: {
|
|
34068
|
+
category: _enum(MCP_TEMPLATE_CATEGORIES).optional(),
|
|
34069
|
+
api_url: string2().url().optional()
|
|
34070
|
+
},
|
|
34071
|
+
toArgv: (args) => {
|
|
34072
|
+
const argv = ["templates", "list"];
|
|
34073
|
+
appendOptionalArg(argv, "--category", args.category);
|
|
34074
|
+
appendOptionalArg(argv, "--api-url", args.api_url);
|
|
34075
|
+
return argv;
|
|
34076
|
+
}
|
|
34077
|
+
},
|
|
34078
|
+
{
|
|
34079
|
+
name: "foh_templates_show",
|
|
34080
|
+
title: "FOH Templates Show",
|
|
34081
|
+
description: "Show a setup template before applying it.",
|
|
34082
|
+
commandKey: "templates show",
|
|
34083
|
+
risk: "read",
|
|
34084
|
+
inputSchema: {
|
|
34085
|
+
template: string2().min(1).max(128),
|
|
34086
|
+
api_url: string2().url().optional()
|
|
34087
|
+
},
|
|
34088
|
+
toArgv: (args) => {
|
|
34089
|
+
const argv = ["templates", "show", "--template", String(args.template)];
|
|
34090
|
+
appendOptionalArg(argv, "--api-url", args.api_url);
|
|
34091
|
+
return argv;
|
|
34092
|
+
}
|
|
34093
|
+
},
|
|
34094
|
+
{
|
|
34095
|
+
name: "foh_templates_select",
|
|
34096
|
+
title: "FOH Templates Select",
|
|
34097
|
+
description: "Select candidate templates from a business requirement brief JSON string.",
|
|
34098
|
+
commandKey: "templates select",
|
|
34099
|
+
risk: "read",
|
|
34100
|
+
inputSchema: {
|
|
34101
|
+
brief_json: string2().min(2).max(5e4),
|
|
34102
|
+
api_url: string2().url().optional()
|
|
34103
|
+
},
|
|
34104
|
+
toArgv: (args) => {
|
|
34105
|
+
const argv = ["templates", "select", "--brief", String(args.brief_json)];
|
|
34106
|
+
appendOptionalArg(argv, "--api-url", args.api_url);
|
|
34107
|
+
return argv;
|
|
34108
|
+
}
|
|
34109
|
+
},
|
|
34110
|
+
{
|
|
34111
|
+
name: "foh_setup_objective",
|
|
34112
|
+
title: "FOH Setup Objective",
|
|
34113
|
+
description: "Plan objective-first setup from explicit business inputs without applying changes.",
|
|
34114
|
+
commandKey: "setup",
|
|
34115
|
+
risk: "read",
|
|
34116
|
+
inputSchema: {
|
|
34117
|
+
objective: string2().min(1).max(1e3),
|
|
34118
|
+
business_name: string2().min(1).max(256),
|
|
34119
|
+
industry: _enum(MCP_OBJECTIVE_INDUSTRIES),
|
|
34120
|
+
source_url: string2().url(),
|
|
34121
|
+
location: string2().min(1).max(256).optional(),
|
|
34122
|
+
tools: array(string2().min(1)).min(1).max(20).optional(),
|
|
34123
|
+
target_mode: string2().min(1).max(128).optional(),
|
|
34124
|
+
org: string2().uuid().optional(),
|
|
34125
|
+
api_url: string2().url().optional()
|
|
34126
|
+
},
|
|
34127
|
+
toArgv: (args) => {
|
|
34128
|
+
const argv = [
|
|
34129
|
+
"setup",
|
|
34130
|
+
"--objective",
|
|
34131
|
+
String(args.objective),
|
|
34132
|
+
"--business-name",
|
|
34133
|
+
String(args.business_name),
|
|
34134
|
+
"--industry",
|
|
34135
|
+
String(args.industry),
|
|
34136
|
+
"--source-url",
|
|
34137
|
+
String(args.source_url)
|
|
34138
|
+
];
|
|
34139
|
+
appendOptionalArg(argv, "--location", args.location);
|
|
34140
|
+
appendOptionalCsvArg(argv, "--tools", args.tools);
|
|
34141
|
+
appendOptionalArg(argv, "--target-mode", args.target_mode);
|
|
34142
|
+
appendOptionalArg(argv, "--org", args.org);
|
|
34143
|
+
appendOptionalArg(argv, "--api-url", args.api_url);
|
|
34144
|
+
return argv;
|
|
34145
|
+
}
|
|
34146
|
+
},
|
|
34147
|
+
{
|
|
34148
|
+
name: "foh_objective_status",
|
|
34149
|
+
title: "FOH Objective Status",
|
|
34150
|
+
description: "Compose setup and launch evidence into one objective status envelope.",
|
|
34151
|
+
commandKey: "objective status",
|
|
34152
|
+
risk: "read",
|
|
34153
|
+
inputSchema: {
|
|
34154
|
+
business_name: string2().min(1).max(256),
|
|
34155
|
+
source_url: string2().url(),
|
|
34156
|
+
industry: _enum(MCP_OBJECTIVE_INDUSTRIES).optional(),
|
|
34157
|
+
business_objective: string2().min(1).max(1e3).optional(),
|
|
34158
|
+
location: string2().min(1).max(256).optional(),
|
|
34159
|
+
tools: array(string2().min(1)).min(1).max(20).optional(),
|
|
34160
|
+
target_mode: string2().min(1).max(128).optional(),
|
|
34161
|
+
environment: string2().min(1).max(128).optional(),
|
|
34162
|
+
org: string2().uuid().optional(),
|
|
34163
|
+
out: string2().min(1).max(1024).optional(),
|
|
34164
|
+
api_url: string2().url().optional()
|
|
34165
|
+
},
|
|
34166
|
+
toArgv: (args) => {
|
|
34167
|
+
const argv = ["objective", "status", "--business-name", String(args.business_name), "--source-url", String(args.source_url)];
|
|
34168
|
+
appendOptionalArg(argv, "--industry", args.industry);
|
|
34169
|
+
appendOptionalArg(argv, "--business-objective", args.business_objective);
|
|
34170
|
+
appendOptionalArg(argv, "--location", args.location);
|
|
34171
|
+
appendOptionalCsvArg(argv, "--tools", args.tools);
|
|
34172
|
+
appendOptionalArg(argv, "--target-mode", args.target_mode);
|
|
34173
|
+
appendOptionalArg(argv, "--environment", args.environment);
|
|
34174
|
+
appendOptionalArg(argv, "--org", args.org);
|
|
34175
|
+
appendOptionalArg(argv, "--out", args.out);
|
|
34176
|
+
appendOptionalArg(argv, "--api-url", args.api_url);
|
|
34177
|
+
return argv;
|
|
34178
|
+
}
|
|
34179
|
+
},
|
|
34180
|
+
{
|
|
34181
|
+
name: "foh_objective_prove",
|
|
34182
|
+
title: "FOH Objective Prove",
|
|
34183
|
+
description: "Run objective proof status against the customer-live gate.",
|
|
34184
|
+
commandKey: "objective prove",
|
|
34185
|
+
risk: "read",
|
|
34186
|
+
inputSchema: {
|
|
34187
|
+
business_name: string2().min(1).max(256),
|
|
34188
|
+
source_url: string2().url(),
|
|
34189
|
+
industry: _enum(MCP_OBJECTIVE_INDUSTRIES).optional(),
|
|
34190
|
+
business_objective: string2().min(1).max(1e3).optional(),
|
|
34191
|
+
location: string2().min(1).max(256).optional(),
|
|
34192
|
+
tools: array(string2().min(1)).min(1).max(20).optional(),
|
|
34193
|
+
target_mode: string2().min(1).max(128).optional(),
|
|
34194
|
+
environment: string2().min(1).max(128).optional(),
|
|
34195
|
+
org: string2().uuid().optional(),
|
|
34196
|
+
out: string2().min(1).max(1024).optional(),
|
|
34197
|
+
api_url: string2().url().optional()
|
|
34198
|
+
},
|
|
34199
|
+
toArgv: (args) => {
|
|
34200
|
+
const argv = ["objective", "prove", "--business-name", String(args.business_name), "--source-url", String(args.source_url)];
|
|
34201
|
+
appendOptionalArg(argv, "--industry", args.industry);
|
|
34202
|
+
appendOptionalArg(argv, "--business-objective", args.business_objective);
|
|
34203
|
+
appendOptionalArg(argv, "--location", args.location);
|
|
34204
|
+
appendOptionalCsvArg(argv, "--tools", args.tools);
|
|
34205
|
+
appendOptionalArg(argv, "--target-mode", args.target_mode);
|
|
34206
|
+
appendOptionalArg(argv, "--environment", args.environment);
|
|
34207
|
+
appendOptionalArg(argv, "--org", args.org);
|
|
34208
|
+
appendOptionalArg(argv, "--out", args.out);
|
|
34209
|
+
appendOptionalArg(argv, "--api-url", args.api_url);
|
|
34210
|
+
return argv;
|
|
34211
|
+
}
|
|
34212
|
+
},
|
|
34213
|
+
{
|
|
34214
|
+
name: "foh_prove_agent",
|
|
34215
|
+
title: "FOH Prove Agent",
|
|
34216
|
+
description: "Produce a setup/runtime proof bundle for an agent.",
|
|
34217
|
+
commandKey: "prove",
|
|
34218
|
+
risk: "read",
|
|
34219
|
+
inputSchema: {
|
|
34220
|
+
agent: string2().min(1).max(128).optional(),
|
|
34221
|
+
org: string2().uuid().optional(),
|
|
34222
|
+
mission: _enum(MCP_PROOF_MISSIONS).optional(),
|
|
34223
|
+
contact_path: _enum(MCP_CONTACT_PATHS).optional(),
|
|
34224
|
+
mutation_mode: _enum(MCP_MUTATION_MODES).optional(),
|
|
34225
|
+
require_phone: boolean2().optional(),
|
|
34226
|
+
skip_smoke: boolean2().optional(),
|
|
34227
|
+
skip_voice_health: boolean2().optional(),
|
|
34228
|
+
out: string2().min(1).max(1024).optional(),
|
|
34229
|
+
strict: boolean2().optional(),
|
|
34230
|
+
api_url: string2().url().optional()
|
|
34231
|
+
},
|
|
34232
|
+
toArgv: (args) => {
|
|
34233
|
+
const argv = ["prove"];
|
|
34234
|
+
appendOptionalArg(argv, "--agent", args.agent);
|
|
34235
|
+
appendOptionalArg(argv, "--org", args.org);
|
|
34236
|
+
appendOptionalArg(argv, "--mission", args.mission);
|
|
34237
|
+
appendOptionalArg(argv, "--contact-path", args.contact_path);
|
|
34238
|
+
appendOptionalArg(argv, "--mutation-mode", args.mutation_mode);
|
|
34239
|
+
if (asOptionalBoolean(args.require_phone) === true) argv.push("--require-phone");
|
|
34240
|
+
if (asOptionalBoolean(args.skip_smoke) === true) argv.push("--skip-smoke");
|
|
34241
|
+
if (asOptionalBoolean(args.skip_voice_health) === true) argv.push("--skip-voice-health");
|
|
34242
|
+
appendOptionalArg(argv, "--out", args.out);
|
|
34243
|
+
if (asOptionalBoolean(args.strict) === true) argv.push("--strict");
|
|
34244
|
+
appendOptionalArg(argv, "--api-url", args.api_url);
|
|
34245
|
+
return argv;
|
|
34246
|
+
}
|
|
34247
|
+
},
|
|
33266
34248
|
{
|
|
33267
34249
|
name: "foh_agent_list",
|
|
33268
34250
|
title: "FOH Agent List",
|
|
@@ -33951,9 +34933,9 @@ function parsePositiveInt(value, fallback, min, max) {
|
|
|
33951
34933
|
if (!Number.isFinite(parsed)) return fallback;
|
|
33952
34934
|
return Math.max(min, Math.min(max, Math.trunc(parsed)));
|
|
33953
34935
|
}
|
|
33954
|
-
function withQuery(
|
|
34936
|
+
function withQuery(path5, params) {
|
|
33955
34937
|
const query = params.toString();
|
|
33956
|
-
return query ? `${
|
|
34938
|
+
return query ? `${path5}?${query}` : path5;
|
|
33957
34939
|
}
|
|
33958
34940
|
|
|
33959
34941
|
// src/commands/knowledge.ts
|
|
@@ -34745,8 +35727,8 @@ function signReport(reportPayload) {
|
|
|
34745
35727
|
}
|
|
34746
35728
|
};
|
|
34747
35729
|
}
|
|
34748
|
-
function writeSignedJsonArtifact(
|
|
34749
|
-
const absolutePath = (0, import_path3.resolve)(
|
|
35730
|
+
function writeSignedJsonArtifact(path5, value) {
|
|
35731
|
+
const absolutePath = (0, import_path3.resolve)(path5);
|
|
34750
35732
|
(0, import_fs5.mkdirSync)((0, import_path3.dirname)(absolutePath), { recursive: true });
|
|
34751
35733
|
(0, import_fs5.writeFileSync)(absolutePath, stableStringify(value), "utf-8");
|
|
34752
35734
|
return absolutePath;
|
|
@@ -35780,7 +36762,7 @@ ${passIcon} Certification loop summary
|
|
|
35780
36762
|
}
|
|
35781
36763
|
|
|
35782
36764
|
// src/commands/certify.ts
|
|
35783
|
-
var
|
|
36765
|
+
var import_node_fs5 = require("node:fs");
|
|
35784
36766
|
function normalizeProfile(raw) {
|
|
35785
36767
|
const value = String(raw || "release").trim().toLowerCase();
|
|
35786
36768
|
if (value === "smoke" || value === "release" || value === "stress") return value;
|
|
@@ -35858,7 +36840,7 @@ function registerCertify(program3) {
|
|
|
35858
36840
|
next_commands: passed ? [`foh agent publish --agent ${opts.agent} --json`] : [`foh certify run --agent ${opts.agent} --profile ${profile} --json`]
|
|
35859
36841
|
};
|
|
35860
36842
|
if (opts.out) {
|
|
35861
|
-
(0,
|
|
36843
|
+
(0, import_node_fs5.writeFileSync)(opts.out, JSON.stringify(result, null, 2) + "\n", "utf-8");
|
|
35862
36844
|
}
|
|
35863
36845
|
if (opts.json ?? false) {
|
|
35864
36846
|
format(result, { json: true });
|
|
@@ -35898,8 +36880,8 @@ function registerConversations(program3) {
|
|
|
35898
36880
|
if (opts.provider) params.set("provider", String(opts.provider));
|
|
35899
36881
|
params.set("page", String(parsePositiveInt(opts.page, 1, 1, 1e4)));
|
|
35900
36882
|
params.set("limit", String(parsePositiveInt(opts.limit, 20, 1, 100)));
|
|
35901
|
-
const
|
|
35902
|
-
const data = await apiFetch(
|
|
36883
|
+
const path5 = withQuery(`/v1/console/agents/${opts.agent}/conversations`, params);
|
|
36884
|
+
const data = await apiFetch(path5, { orgId: opts.org, apiUrlOverride: opts.apiUrl });
|
|
35903
36885
|
format(data, { json: opts.json ?? false });
|
|
35904
36886
|
}));
|
|
35905
36887
|
conversations.command("semantic-search").description("Run semantic ranking search against an agent conversation corpus").requiredOption("--agent <id>", "Agent ID").requiredOption("--q <query>", "Query text (min 3 chars)").option("--limit <n>", "Result limit (1-100)", "20").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 () => {
|
|
@@ -35916,13 +36898,13 @@ function registerConversations(program3) {
|
|
|
35916
36898
|
q: query,
|
|
35917
36899
|
limit: String(parsePositiveInt(opts.limit, 20, 1, 100))
|
|
35918
36900
|
});
|
|
35919
|
-
const
|
|
35920
|
-
const data = await apiFetch(
|
|
36901
|
+
const path5 = withQuery(`/v1/console/agents/${opts.agent}/conversations/semantic-search`, params);
|
|
36902
|
+
const data = await apiFetch(path5, { orgId: opts.org, apiUrlOverride: opts.apiUrl });
|
|
35921
36903
|
format(data, { json: opts.json ?? false });
|
|
35922
36904
|
}));
|
|
35923
36905
|
conversations.command("lead-data").description("Get extracted lead-data fields for a conversation").requiredOption("--agent <id>", "Agent ID").requiredOption("--conversation <id>", "Conversation 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 () => {
|
|
35924
|
-
const
|
|
35925
|
-
const data = await apiFetch(
|
|
36906
|
+
const path5 = `/v1/console/agents/${opts.agent}/conversations/${opts.conversation}/lead-data`;
|
|
36907
|
+
const data = await apiFetch(path5, { orgId: opts.org, apiUrlOverride: opts.apiUrl });
|
|
35926
36908
|
format(data, { json: opts.json ?? false });
|
|
35927
36909
|
}));
|
|
35928
36910
|
conversations.command("inject-variables").description("Inject or merge conversation vars into an active flow-state").requiredOption("--agent <id>", "Agent ID").requiredOption("--conversation <id>", "Conversation ID").requiredOption("--variables <json|@file>", "Variables object JSON or @path").option("--mode <value>", "Mode: merge or replace", "merge").option("--source <value>", "Audit source tag", "cli").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 () => {
|
|
@@ -35944,8 +36926,8 @@ function registerConversations(program3) {
|
|
|
35944
36926
|
statusCode: 400
|
|
35945
36927
|
});
|
|
35946
36928
|
}
|
|
35947
|
-
const
|
|
35948
|
-
const data = await apiFetch(
|
|
36929
|
+
const path5 = `/v1/console/agents/${opts.agent}/conversations/${opts.conversation}/inject-variables`;
|
|
36930
|
+
const data = await apiFetch(path5, {
|
|
35949
36931
|
method: "POST",
|
|
35950
36932
|
body: JSON.stringify({
|
|
35951
36933
|
variables: parsed,
|
|
@@ -35967,8 +36949,8 @@ function registerConversations(program3) {
|
|
|
35967
36949
|
statusCode: 400
|
|
35968
36950
|
});
|
|
35969
36951
|
}
|
|
35970
|
-
const
|
|
35971
|
-
const data = await apiFetch(
|
|
36952
|
+
const path5 = `/v1/console/agents/${opts.agent}/conversations/${opts.conversation}/inject-event`;
|
|
36953
|
+
const data = await apiFetch(path5, {
|
|
35972
36954
|
method: "POST",
|
|
35973
36955
|
body: JSON.stringify({
|
|
35974
36956
|
event_type: String(opts.eventType),
|
|
@@ -36088,8 +37070,8 @@ function registerTranscripts(program3) {
|
|
|
36088
37070
|
transcripts.command("get").description("Fetch one conversation transcript and optional trace events").requiredOption("--agent <id>", "Agent ID").requiredOption("--conversation <id>", "Conversation ID").option("--include-traces", "Include ordered trace events").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 () => {
|
|
36089
37071
|
const params = new URLSearchParams();
|
|
36090
37072
|
if (opts.includeTraces) params.set("include_traces", "true");
|
|
36091
|
-
const
|
|
36092
|
-
const data = await apiFetch(
|
|
37073
|
+
const path5 = withQuery(`/v1/console/agents/${opts.agent}/conversations/${opts.conversation}`, params);
|
|
37074
|
+
const data = await apiFetch(path5, { orgId: opts.org, apiUrlOverride: opts.apiUrl });
|
|
36093
37075
|
format(data, { json: opts.json ?? false });
|
|
36094
37076
|
}));
|
|
36095
37077
|
transcripts.command("export").description("Export recent transcripts as JSON or JSONL").requiredOption("--agent <id>", "Agent ID").option("--q <query>", "Full-text transcript query").option("--from <iso-date>", "Start datetime (ISO8601)").option("--to <iso-date>", "End datetime (ISO8601)").option("--limit <n>", "Rows to export (1-100)", "100").option("--format <value>", "Export format: jsonl or json", "jsonl").option("--hydrate", "Fetch full conversation detail for every exported row").option("--include-traces", "Hydrate each conversation with ordered trace events").option("--no-redact", "Disable default redaction of emails, phone numbers, and obvious secrets").option("--out <path>", "Output file 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 () => {
|
|
@@ -36272,8 +37254,8 @@ function registerTests(program3) {
|
|
|
36272
37254
|
if (opts.status) params.set("status", String(opts.status));
|
|
36273
37255
|
if (opts.type) params.set("type", String(opts.type));
|
|
36274
37256
|
params.set("limit", String(parsePositiveInt(opts.limit, 100, 1, 500)));
|
|
36275
|
-
const
|
|
36276
|
-
const data = await apiFetch(
|
|
37257
|
+
const path5 = withQuery(`/v1/console/agents/${opts.agent}/tests`, params);
|
|
37258
|
+
const data = await apiFetch(path5, { orgId: opts.org, apiUrlOverride: opts.apiUrl });
|
|
36277
37259
|
format(data, { json: opts.json ?? false });
|
|
36278
37260
|
}));
|
|
36279
37261
|
tests.command("create").description("Create a new test in an agent test catalog").requiredOption("--agent <id>", "Agent ID").requiredOption("--name <text>", "Test name").requiredOption("--config <json|@file>", "Test config JSON or @path").option("--description <text>", "Description").option("--status <value>", "Status override").option("--enabled <value>", "Enabled flag: true/false").option("--tags <csv>", "Comma-separated tags").option("--metadata <json|@file>", "Metadata JSON object").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 () => {
|
|
@@ -36399,8 +37381,8 @@ function registerTests(program3) {
|
|
|
36399
37381
|
if (opts.test) params.set("testId", String(opts.test));
|
|
36400
37382
|
if (opts.batch) params.set("batchId", String(opts.batch));
|
|
36401
37383
|
if (opts.includeResults) params.set("includeResults", "true");
|
|
36402
|
-
const
|
|
36403
|
-
const data = await apiFetch(
|
|
37384
|
+
const path5 = withQuery(`/v1/console/agents/${opts.agent}/tests/runs`, params);
|
|
37385
|
+
const data = await apiFetch(path5, {
|
|
36404
37386
|
orgId: opts.org,
|
|
36405
37387
|
apiUrlOverride: opts.apiUrl
|
|
36406
37388
|
});
|
|
@@ -36460,9 +37442,9 @@ function asStringList(value) {
|
|
|
36460
37442
|
if (Array.isArray(value)) return value.map((entry) => String(entry || "").trim()).filter(Boolean);
|
|
36461
37443
|
return [];
|
|
36462
37444
|
}
|
|
36463
|
-
function getPath(source,
|
|
37445
|
+
function getPath(source, path5) {
|
|
36464
37446
|
if (!source || typeof source !== "object") return void 0;
|
|
36465
|
-
return
|
|
37447
|
+
return path5.split(".").reduce((current, part) => {
|
|
36466
37448
|
if (!current || typeof current !== "object") return void 0;
|
|
36467
37449
|
return current[part];
|
|
36468
37450
|
}, source);
|
|
@@ -36476,12 +37458,12 @@ function valuesEqual(actual, expected) {
|
|
|
36476
37458
|
}
|
|
36477
37459
|
return String(actual ?? "") === String(expected ?? "");
|
|
36478
37460
|
}
|
|
36479
|
-
function parseStructuredFile(
|
|
36480
|
-
const raw = (0, import_fs7.readFileSync)(
|
|
36481
|
-
return
|
|
37461
|
+
function parseStructuredFile(path5) {
|
|
37462
|
+
const raw = (0, import_fs7.readFileSync)(path5, "utf-8");
|
|
37463
|
+
return path5.toLowerCase().endsWith(".json") ? JSON.parse(raw) : load(raw);
|
|
36482
37464
|
}
|
|
36483
|
-
function parseSuiteFile(
|
|
36484
|
-
const parsed = parseStructuredFile(
|
|
37465
|
+
function parseSuiteFile(path5) {
|
|
37466
|
+
const parsed = parseStructuredFile(path5);
|
|
36485
37467
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
36486
37468
|
throw new FohError({
|
|
36487
37469
|
step: "test.run",
|
|
@@ -36505,8 +37487,8 @@ function turnFromFixtureEntry(entry) {
|
|
|
36505
37487
|
expect: row.expect && typeof row.expect === "object" && !Array.isArray(row.expect) ? row.expect : void 0
|
|
36506
37488
|
};
|
|
36507
37489
|
}
|
|
36508
|
-
function loadFixtureTurns(
|
|
36509
|
-
const parsed = parseStructuredFile(
|
|
37490
|
+
function loadFixtureTurns(path5) {
|
|
37491
|
+
const parsed = parseStructuredFile(path5);
|
|
36510
37492
|
const entries = Array.isArray(parsed) ? parsed : parsed && typeof parsed === "object" && Array.isArray(parsed.turns) ? parsed.turns : parsed && typeof parsed === "object" && Array.isArray(parsed.messages) ? parsed.messages : [];
|
|
36511
37493
|
return entries.map(turnFromFixtureEntry).filter((entry) => !!entry);
|
|
36512
37494
|
}
|
|
@@ -36566,8 +37548,8 @@ function pickVariables(response) {
|
|
|
36566
37548
|
return null;
|
|
36567
37549
|
}
|
|
36568
37550
|
function pickToolCalls(response) {
|
|
36569
|
-
for (const
|
|
36570
|
-
const value = getPath(response,
|
|
37551
|
+
for (const path5 of ["tool_calls", "toolCalls", "telemetry.tool_calls", "trace.tool_calls"]) {
|
|
37552
|
+
const value = getPath(response, path5);
|
|
36571
37553
|
if (Array.isArray(value)) return value;
|
|
36572
37554
|
}
|
|
36573
37555
|
return [];
|
|
@@ -36623,9 +37605,9 @@ function evaluateStructuredExpectations(response, expect, latencyMs) {
|
|
|
36623
37605
|
const variables = pickVariables(response);
|
|
36624
37606
|
if (!variables) failures.push("variables expected but response had none");
|
|
36625
37607
|
else {
|
|
36626
|
-
for (const [
|
|
36627
|
-
const actual = getPath(variables,
|
|
36628
|
-
if (!valuesEqual(actual, expected)) failures.push(`variables.${
|
|
37608
|
+
for (const [path5, expected] of Object.entries(expect.variables)) {
|
|
37609
|
+
const actual = getPath(variables, path5);
|
|
37610
|
+
if (!valuesEqual(actual, expected)) failures.push(`variables.${path5} expected ${JSON.stringify(expected)} but got ${JSON.stringify(actual)}`);
|
|
36629
37611
|
}
|
|
36630
37612
|
}
|
|
36631
37613
|
}
|
|
@@ -36667,9 +37649,9 @@ function evaluateStructuredExpectations(response, expect, latencyMs) {
|
|
|
36667
37649
|
}
|
|
36668
37650
|
}
|
|
36669
37651
|
if (expect.fields && typeof expect.fields === "object") {
|
|
36670
|
-
for (const [
|
|
36671
|
-
const actual = getPath(response,
|
|
36672
|
-
if (!valuesEqual(actual, expected)) failures.push(`fields.${
|
|
37652
|
+
for (const [path5, expected] of Object.entries(expect.fields)) {
|
|
37653
|
+
const actual = getPath(response, path5);
|
|
37654
|
+
if (!valuesEqual(actual, expected)) failures.push(`fields.${path5} expected ${JSON.stringify(expected)} but got ${JSON.stringify(actual)}`);
|
|
36673
37655
|
}
|
|
36674
37656
|
}
|
|
36675
37657
|
return failures;
|
|
@@ -37364,9 +38346,9 @@ function nonEmpty2(value) {
|
|
|
37364
38346
|
const text = String(value ?? "").trim();
|
|
37365
38347
|
return text.length > 0 ? text : void 0;
|
|
37366
38348
|
}
|
|
37367
|
-
function getPath2(value,
|
|
38349
|
+
function getPath2(value, path5) {
|
|
37368
38350
|
let current = value;
|
|
37369
|
-
for (const segment of
|
|
38351
|
+
for (const segment of path5.split(".")) {
|
|
37370
38352
|
const record2 = asRecord2(current);
|
|
37371
38353
|
if (!record2) return void 0;
|
|
37372
38354
|
current = record2[segment];
|
|
@@ -37523,10 +38505,10 @@ function assertRedacted(value) {
|
|
|
37523
38505
|
});
|
|
37524
38506
|
}
|
|
37525
38507
|
}
|
|
37526
|
-
function readSourceArtifact(
|
|
37527
|
-
if (!
|
|
38508
|
+
function readSourceArtifact(path5) {
|
|
38509
|
+
if (!path5) return null;
|
|
37528
38510
|
try {
|
|
37529
|
-
return JSON.parse((0, import_fs9.readFileSync)(
|
|
38511
|
+
return JSON.parse((0, import_fs9.readFileSync)(path5, "utf-8"));
|
|
37530
38512
|
} catch (error2) {
|
|
37531
38513
|
throw new FohError({
|
|
37532
38514
|
step: "bug.improve",
|
|
@@ -37703,8 +38685,8 @@ function parseRequestBody(raw) {
|
|
|
37703
38685
|
return text;
|
|
37704
38686
|
}
|
|
37705
38687
|
}
|
|
37706
|
-
function writeJsonArtifact(
|
|
37707
|
-
const absolutePath = (0, import_path8.resolve)(
|
|
38688
|
+
function writeJsonArtifact(path5, value) {
|
|
38689
|
+
const absolutePath = (0, import_path8.resolve)(path5);
|
|
37708
38690
|
(0, import_fs10.mkdirSync)((0, import_path8.dirname)(absolutePath), { recursive: true });
|
|
37709
38691
|
(0, import_fs10.writeFileSync)(absolutePath, stableStringify(value), "utf-8");
|
|
37710
38692
|
return absolutePath;
|
|
@@ -37988,8 +38970,8 @@ function registerBug(program3) {
|
|
|
37988
38970
|
|
|
37989
38971
|
// src/lib/proof-cache.ts
|
|
37990
38972
|
var import_node_crypto2 = require("node:crypto");
|
|
37991
|
-
var
|
|
37992
|
-
var
|
|
38973
|
+
var import_node_fs6 = require("node:fs");
|
|
38974
|
+
var import_node_path3 = require("node:path");
|
|
37993
38975
|
var DEFAULT_MAX_AGE_MS = 15 * 60 * 1e3;
|
|
37994
38976
|
var DEFAULT_WAIT_MS = 180 * 1e3;
|
|
37995
38977
|
var DEFAULT_POLL_MS = 500;
|
|
@@ -38007,12 +38989,12 @@ function cacheKey2(kind, keyParts) {
|
|
|
38007
38989
|
return (0, import_node_crypto2.createHash)("sha256").update(stableJson({ kind, keyParts, schema_version: "foh_proof_cache_key.v1" })).digest("hex").slice(0, 32);
|
|
38008
38990
|
}
|
|
38009
38991
|
function publicPath(filePath) {
|
|
38010
|
-
const rel = (0,
|
|
38992
|
+
const rel = (0, import_node_path3.relative)(process.cwd(), filePath).replaceAll("\\", "/");
|
|
38011
38993
|
return rel.startsWith("..") ? filePath.replaceAll("\\", "/") : rel;
|
|
38012
38994
|
}
|
|
38013
38995
|
function readFreshCache(filePath, maxAgeMs) {
|
|
38014
38996
|
try {
|
|
38015
|
-
const payload = JSON.parse((0,
|
|
38997
|
+
const payload = JSON.parse((0, import_node_fs6.readFileSync)(filePath, "utf8"));
|
|
38016
38998
|
const createdAt = Date.parse(String(payload.created_at || ""));
|
|
38017
38999
|
if (!Number.isFinite(createdAt)) return null;
|
|
38018
39000
|
const ageMs = Date.now() - createdAt;
|
|
@@ -38024,8 +39006,8 @@ function readFreshCache(filePath, maxAgeMs) {
|
|
|
38024
39006
|
}
|
|
38025
39007
|
}
|
|
38026
39008
|
function writeCache(filePath, value) {
|
|
38027
|
-
(0,
|
|
38028
|
-
(0,
|
|
39009
|
+
(0, import_node_fs6.mkdirSync)((0, import_node_path3.dirname)(filePath), { recursive: true });
|
|
39010
|
+
(0, import_node_fs6.writeFileSync)(filePath, `${JSON.stringify({
|
|
38029
39011
|
schema_version: "foh_proof_cache_entry.v1",
|
|
38030
39012
|
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
38031
39013
|
value
|
|
@@ -38035,7 +39017,7 @@ function writeCache(filePath, value) {
|
|
|
38035
39017
|
function resolveProofCacheDir(input) {
|
|
38036
39018
|
const value = String(input || process.env.FOH_PROOF_CACHE_DIR || "").trim();
|
|
38037
39019
|
if (!value) return null;
|
|
38038
|
-
return (0,
|
|
39020
|
+
return (0, import_node_path3.isAbsolute)(value) ? value : (0, import_node_path3.resolve)(process.cwd(), value);
|
|
38039
39021
|
}
|
|
38040
39022
|
async function withProofCache(options, run) {
|
|
38041
39023
|
const resolvedDir = resolveProofCacheDir(options.cacheDir);
|
|
@@ -38055,12 +39037,12 @@ async function withProofCache(options, run) {
|
|
|
38055
39037
|
};
|
|
38056
39038
|
}
|
|
38057
39039
|
const key = cacheKey2(options.kind, options.keyParts);
|
|
38058
|
-
const cachePath = (0,
|
|
38059
|
-
const lockPath = (0,
|
|
39040
|
+
const cachePath = (0, import_node_path3.join)(resolvedDir, `${key}.json`);
|
|
39041
|
+
const lockPath = (0, import_node_path3.join)(resolvedDir, `${key}.lock`);
|
|
38060
39042
|
const maxAgeMs = options.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
|
|
38061
39043
|
const waitMs = options.waitMs ?? Number(process.env.FOH_PROOF_CACHE_WAIT_MS || DEFAULT_WAIT_MS);
|
|
38062
39044
|
const pollMs = options.pollMs ?? DEFAULT_POLL_MS;
|
|
38063
|
-
(0,
|
|
39045
|
+
(0, import_node_fs6.mkdirSync)(resolvedDir, { recursive: true });
|
|
38064
39046
|
const existing = readFreshCache(cachePath, maxAgeMs);
|
|
38065
39047
|
if (existing) {
|
|
38066
39048
|
return {
|
|
@@ -38079,7 +39061,7 @@ async function withProofCache(options, run) {
|
|
|
38079
39061
|
}
|
|
38080
39062
|
let lockOwner = false;
|
|
38081
39063
|
try {
|
|
38082
|
-
(0,
|
|
39064
|
+
(0, import_node_fs6.mkdirSync)(lockPath);
|
|
38083
39065
|
lockOwner = true;
|
|
38084
39066
|
} catch {
|
|
38085
39067
|
const started = Date.now();
|
|
@@ -38120,7 +39102,7 @@ async function withProofCache(options, run) {
|
|
|
38120
39102
|
}
|
|
38121
39103
|
};
|
|
38122
39104
|
} finally {
|
|
38123
|
-
if (lockOwner) (0,
|
|
39105
|
+
if (lockOwner) (0, import_node_fs6.rmSync)(lockPath, { recursive: true, force: true });
|
|
38124
39106
|
}
|
|
38125
39107
|
}
|
|
38126
39108
|
|
|
@@ -38253,7 +39235,7 @@ function isProviderCapacityBlocked(onboarding) {
|
|
|
38253
39235
|
return /maximum number of subaccounts|subaccount limit|reserve[- ]number pool|reserve pool exhausted|global safety limit/.test(message);
|
|
38254
39236
|
}
|
|
38255
39237
|
function registerProve(program3) {
|
|
38256
|
-
program3.command("prove").description("Produce one setup/runtime proof bundle for an agent").option("--agent <id>", "Agent ID to prove").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--include-certification", "Run explicit simulation certification check (slow)").option("--cert-mode <m>", "Simulation cert mode when --include-certification is set: quick, full, stress", "quick").option("--cert-adaptive-runs <n>", "Adaptive runs for full/stress certification when included", "30").option("--cert-max-improvement-rounds <n>", "Max prompt improvement rounds in included cert loop (0-5)", "1").option("--mission <mission>", "Proof mission: setup, widget, voice, publish", "setup").option("--contact-path <mode>", "Voice contact path: auto, managed, or byon", "auto").option("--mutation-mode <mode>", "Proof mutation mode: read-only or ensure", "read-only").option("--repair", "Alias for --mutation-mode ensure").option("--require-phone", "Hold proof if no phone/contact number is provisioned").option("--skip-cert", "Deprecated compatibility flag; certification is skipped unless --include-certification is set").option("--skip-smoke", "Skip widget runtime smoke check").option("--skip-voice-health", "Skip realtime voice provider health check").option("--proof-cache-dir <path>", "Optional local proof cache directory for shared certification results").option("--out <path>", "Write signed proof report JSON to this path").option("--strict", "Exit non-zero unless all non-skipped checks pass").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
|
|
39238
|
+
program3.command("prove").description("Produce one setup/runtime proof bundle for an agent").option("--agent <id>", "Agent ID to prove").option("--org <id>", "Org ID (default: stored org from foh org use)").option("--include-certification", "Run explicit simulation certification check (slow)").option("--cert-mode <m>", "Simulation cert mode when --include-certification is set: quick, full, stress", "quick").option("--cert-adaptive-runs <n>", "Adaptive runs for full/stress certification when included", "30").option("--cert-max-improvement-rounds <n>", "Max prompt improvement rounds in included cert loop (0-5)", "1").option("--mission <mission>", "Proof mission: setup, widget, voice, publish", "setup").option("--contact-path <mode>", "Voice contact path: auto, managed, or byon", "auto").option("--mutation-mode <mode>", "Proof mutation mode: read-only or ensure", "read-only").option("--repair", "Alias for --mutation-mode ensure").option("--require-phone", "Hold proof if no phone/contact number is provisioned").option("--skip-cert", "Deprecated compatibility flag; certification is skipped unless --include-certification is set").option("--skip-smoke", "Skip widget runtime smoke check").option("--skip-voice-health", "Skip realtime voice provider health check").option("--proof-cache-dir <path>", "Optional local proof cache directory for shared certification results").option("--origin <urlOrHost>", "Website origin to simulate for domain allowlists, e.g. https://www.example.com").option("--out <path>", "Write signed proof report JSON to this path").option("--strict", "Exit non-zero unless all non-skipped checks pass").option("--api-url <url>", "API base URL override").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
|
|
38257
39239
|
const commandStartedMs = Date.now();
|
|
38258
39240
|
const checks = [];
|
|
38259
39241
|
const checkTimings = /* @__PURE__ */ new Map();
|
|
@@ -38450,16 +39432,25 @@ function registerProve(program3) {
|
|
|
38450
39432
|
headers: { "x-agent-id": String(ctx.agentId) }
|
|
38451
39433
|
}));
|
|
38452
39434
|
const publicKey = publicKeyFromEmbedResponse(embed);
|
|
39435
|
+
const allowedDomains = Array.isArray(embed.allowed_domains) ? embed.allowed_domains.map((domain2) => String(domain2 || "").trim()).filter(Boolean) : [];
|
|
39436
|
+
const origin = normalizeOrigin(typeof opts.origin === "string" && opts.origin.trim() ? opts.origin : allowedDomains[0]);
|
|
38453
39437
|
if (publicKey) {
|
|
38454
39438
|
checkTimings.set("widget_channel", Math.max(checkTimings.get("widget_channel") ?? 0, checkTimings.get("widget_embed") ?? 0));
|
|
38455
39439
|
ctx.widgetPublicKey = publicKey;
|
|
39440
|
+
ctx.widgetOrigin = origin;
|
|
38456
39441
|
checks.push(pass("widget_channel", "Widget channel is available in read-only proof mode.", {
|
|
38457
39442
|
public_key_present: true,
|
|
38458
|
-
mutation_mode: mutationMode
|
|
39443
|
+
mutation_mode: mutationMode,
|
|
39444
|
+
origin,
|
|
39445
|
+
allowed_domains: allowedDomains
|
|
38459
39446
|
}));
|
|
38460
39447
|
}
|
|
38461
39448
|
if (typeof embed.snippet === "string" && embed.snippet.trim()) {
|
|
38462
|
-
checks.push(pass("widget_embed", "Widget embed snippet is available.", {
|
|
39449
|
+
checks.push(pass("widget_embed", "Widget embed snippet is available.", {
|
|
39450
|
+
snippet_present: true,
|
|
39451
|
+
allowed_domains: allowedDomains,
|
|
39452
|
+
customer_runtime_profile_present: Boolean(embed.customer_runtime_profile)
|
|
39453
|
+
}));
|
|
38463
39454
|
} else {
|
|
38464
39455
|
checks.push(hold("widget_embed", "widget_embed_missing", "Widget embed snippet is missing.", `foh widget embed-snippet --agent ${ctx.agentId}`));
|
|
38465
39456
|
}
|
|
@@ -38499,19 +39490,46 @@ function registerProve(program3) {
|
|
|
38499
39490
|
checks.push(skipped("widget_smoke", "operator_skipped", "Skipped by --skip-smoke.", `foh widget smoke --agent ${ctx.agentId} --json`));
|
|
38500
39491
|
} else if (!ctx.widgetPublicKey) {
|
|
38501
39492
|
checks.push(skipped("widget_smoke", "widget_public_key_required", "Skipped because widget public key is unavailable.", `foh widget ensure --agent ${ctx.agentId} --json`));
|
|
39493
|
+
checks.push(skipped("widget_semantic", "widget_public_key_required", "Skipped because widget public key is unavailable.", `foh widget ensure --agent ${ctx.agentId} --json`));
|
|
38502
39494
|
} else {
|
|
38503
39495
|
try {
|
|
38504
|
-
const
|
|
39496
|
+
const embed = await apiFetch("/v1/console/channels/widget/embed-snippet", {
|
|
39497
|
+
orgId: ctx.orgId,
|
|
39498
|
+
apiUrlOverride: opts.apiUrl,
|
|
39499
|
+
headers: { "x-agent-id": String(ctx.agentId) }
|
|
39500
|
+
});
|
|
39501
|
+
const smoke = await timedCheck(checkTimings, "widget_smoke", () => runWidgetSmoke(ctx.widgetPublicKey, opts.apiUrl, ctx.widgetOrigin));
|
|
38505
39502
|
ctx.conversationId = smoke.conversation_id;
|
|
38506
39503
|
ctx.traceIds = smoke.trace_ids;
|
|
38507
39504
|
ctx.correlationIds = smoke.correlation_ids;
|
|
38508
39505
|
if (smoke.failed > 0) {
|
|
38509
|
-
checks.push(hold("widget_smoke", "widget_smoke_failed", `${smoke.failed} widget smoke turn(s) failed.`, `foh widget smoke --agent ${ctx.agentId} --json`,
|
|
39506
|
+
checks.push(hold("widget_smoke", "widget_smoke_failed", `${smoke.failed} widget smoke transport turn(s) failed.`, `foh widget smoke --agent ${ctx.agentId} --json`, {
|
|
39507
|
+
...smoke,
|
|
39508
|
+
origin: ctx.widgetOrigin ?? null
|
|
39509
|
+
}));
|
|
39510
|
+
checks.push(skipped("widget_semantic", "widget_smoke_required", "Skipped because transport smoke failed.", `foh widget smoke --agent ${ctx.agentId} --json`));
|
|
38510
39511
|
} else {
|
|
38511
|
-
checks.push(pass("widget_smoke", "Widget
|
|
39512
|
+
checks.push(pass("widget_smoke", "Widget transport smoke passed.", {
|
|
39513
|
+
...smoke,
|
|
39514
|
+
origin: ctx.widgetOrigin ?? null
|
|
39515
|
+
}));
|
|
39516
|
+
const semantic = await timedCheck(checkTimings, "widget_semantic", () => runWidgetSemanticEval(
|
|
39517
|
+
ctx.widgetPublicKey,
|
|
39518
|
+
opts.apiUrl,
|
|
39519
|
+
ctx.widgetOrigin,
|
|
39520
|
+
embed.customer_runtime_profile ?? null
|
|
39521
|
+
));
|
|
39522
|
+
if (!semantic.pack_id) {
|
|
39523
|
+
checks.push(skipped("widget_semantic", "widget_semantic_pack_not_configured", "No widget semantic eval pack is configured for this business.", void 0));
|
|
39524
|
+
} else if (semantic.failed > 0) {
|
|
39525
|
+
checks.push(hold("widget_semantic", "widget_semantic_failed", `${semantic.failed} widget semantic scenario(s) failed.`, `foh widget smoke --agent ${ctx.agentId} --json`, semantic));
|
|
39526
|
+
} else {
|
|
39527
|
+
checks.push(pass("widget_semantic", "Widget semantic eval passed.", semantic));
|
|
39528
|
+
}
|
|
38512
39529
|
}
|
|
38513
39530
|
} catch (error2) {
|
|
38514
39531
|
checks.push(fail("widget_smoke", "widget_smoke_failed", error2, `foh widget smoke --agent ${ctx.agentId} --json`));
|
|
39532
|
+
checks.push(skipped("widget_semantic", "widget_smoke_required", "Skipped because widget smoke threw before semantic evaluation.", `foh widget smoke --agent ${ctx.agentId} --json`));
|
|
38515
39533
|
}
|
|
38516
39534
|
}
|
|
38517
39535
|
if (opts.skipCert) {
|
|
@@ -38597,6 +39615,7 @@ function registerProve(program3) {
|
|
|
38597
39615
|
contact_path: contactPath,
|
|
38598
39616
|
mutation_mode: mutationMode,
|
|
38599
39617
|
widget_public_key_present: Boolean(ctx.widgetPublicKey),
|
|
39618
|
+
widget_origin: ctx.widgetOrigin ?? null,
|
|
38600
39619
|
conversation_id: ctx.conversationId ?? null,
|
|
38601
39620
|
trace_ids: ctx.traceIds,
|
|
38602
39621
|
correlation_ids: ctx.correlationIds
|
|
@@ -38615,8 +39634,8 @@ function registerProve(program3) {
|
|
|
38615
39634
|
}
|
|
38616
39635
|
|
|
38617
39636
|
// src/commands/objective.ts
|
|
38618
|
-
var
|
|
38619
|
-
var
|
|
39637
|
+
var import_node_fs7 = require("node:fs");
|
|
39638
|
+
var import_node_path4 = require("node:path");
|
|
38620
39639
|
var DEFAULT_OBJECTIVE_REPORT_PATH = "test-results/objective-status.latest.json";
|
|
38621
39640
|
var VALID_OBJECTIVE_INDUSTRIES = ["real_estate", "restaurant", "general"];
|
|
38622
39641
|
var BUSINESS_REQUIREMENT_BRIEF_SCHEMA_VERSION = "business_requirement_brief.v1";
|
|
@@ -38626,6 +39645,12 @@ function asRecord3(value) {
|
|
|
38626
39645
|
function normalizeString2(value) {
|
|
38627
39646
|
return typeof value === "string" ? value.trim() : "";
|
|
38628
39647
|
}
|
|
39648
|
+
function quoteCliArg(value) {
|
|
39649
|
+
const normalized = value.trim();
|
|
39650
|
+
if (!normalized) return '""';
|
|
39651
|
+
if (!/[\s"]/.test(normalized)) return normalized;
|
|
39652
|
+
return `"${normalized.replace(/"/g, '\\"')}"`;
|
|
39653
|
+
}
|
|
38629
39654
|
function uniqueStrings(values) {
|
|
38630
39655
|
return Array.from(new Set(values.map(normalizeString2).filter(Boolean)));
|
|
38631
39656
|
}
|
|
@@ -38684,22 +39709,26 @@ function finiteNumber(value) {
|
|
|
38684
39709
|
return Number.isFinite(number3) ? number3 : null;
|
|
38685
39710
|
}
|
|
38686
39711
|
function normalizeCustomerEvidenceActions(value) {
|
|
38687
|
-
return asArray(value).map(asRecord3).map((action) =>
|
|
38688
|
-
id
|
|
38689
|
-
|
|
38690
|
-
|
|
38691
|
-
|
|
38692
|
-
|
|
38693
|
-
|
|
38694
|
-
|
|
38695
|
-
|
|
38696
|
-
|
|
38697
|
-
|
|
38698
|
-
|
|
38699
|
-
|
|
38700
|
-
|
|
38701
|
-
|
|
38702
|
-
|
|
39712
|
+
return asArray(value).map(asRecord3).map((action) => {
|
|
39713
|
+
const id = firstString(action, ["id", "action_id"]);
|
|
39714
|
+
return {
|
|
39715
|
+
id,
|
|
39716
|
+
title: firstString(action, ["title"]) || null,
|
|
39717
|
+
owner: firstString(action, ["owner"]) || null,
|
|
39718
|
+
blocker_count: finiteNumber(action.blocker_count) ?? (uniqueStrings(asArray(action.reason_codes)).length || null),
|
|
39719
|
+
target_evidence_paths: uniqueStrings([
|
|
39720
|
+
...asArray(action.target_evidence_paths),
|
|
39721
|
+
firstString(action, ["target_evidence_path"]),
|
|
39722
|
+
id ? `customer-evidence/${id}.json` : ""
|
|
39723
|
+
]),
|
|
39724
|
+
validator_commands: uniqueStrings([
|
|
39725
|
+
...asArray(action.validator_commands),
|
|
39726
|
+
...asArray(action.next_commands)
|
|
39727
|
+
]),
|
|
39728
|
+
required_evidence: firstString(action, ["required_evidence"]) || null,
|
|
39729
|
+
unlocks: firstString(action, ["unlocks"]) || null
|
|
39730
|
+
};
|
|
39731
|
+
}).filter((action) => normalizeString2(action.id));
|
|
38703
39732
|
}
|
|
38704
39733
|
function normalizeCustomerEvidenceActionPacket(value) {
|
|
38705
39734
|
const packet = asRecord3(value);
|
|
@@ -38716,7 +39745,63 @@ function normalizeCustomerEvidenceActionPacket(value) {
|
|
|
38716
39745
|
instructions: uniqueStrings(asArray(packet.instructions))
|
|
38717
39746
|
};
|
|
38718
39747
|
}
|
|
39748
|
+
function skeletonHintsById(...sources) {
|
|
39749
|
+
const hints = /* @__PURE__ */ new Map();
|
|
39750
|
+
for (const source of sources) {
|
|
39751
|
+
const record2 = asRecord3(source);
|
|
39752
|
+
const evidencePacket = asRecord3(record2.evidence_packet);
|
|
39753
|
+
const skeletons = asArray(evidencePacket.skeletons).map(asRecord3);
|
|
39754
|
+
for (const skeleton of skeletons) {
|
|
39755
|
+
const id = normalizeString2(skeleton.id);
|
|
39756
|
+
if (!id || hints.has(id)) continue;
|
|
39757
|
+
hints.set(id, skeleton);
|
|
39758
|
+
}
|
|
39759
|
+
}
|
|
39760
|
+
return hints;
|
|
39761
|
+
}
|
|
39762
|
+
function mergeActionPacketWithSkeletonHints(packet, skeletonHints) {
|
|
39763
|
+
if (!packet) return void 0;
|
|
39764
|
+
const actions = asArray(packet.actions).map(asRecord3).filter((action) => normalizeString2(action.id));
|
|
39765
|
+
if (actions.length === 0) return packet;
|
|
39766
|
+
return {
|
|
39767
|
+
...packet,
|
|
39768
|
+
actions: actions.map((action) => {
|
|
39769
|
+
const hint = skeletonHints.get(normalizeString2(action.id));
|
|
39770
|
+
const hintedTarget = normalizeString2(hint?.target_evidence_path);
|
|
39771
|
+
return {
|
|
39772
|
+
...action,
|
|
39773
|
+
target_evidence_paths: uniqueStrings([
|
|
39774
|
+
...asArray(action.target_evidence_paths),
|
|
39775
|
+
hintedTarget
|
|
39776
|
+
]),
|
|
39777
|
+
required_evidence: action.required_evidence ?? (firstString(asRecord3(hint), ["instructions"]) || null)
|
|
39778
|
+
};
|
|
39779
|
+
})
|
|
39780
|
+
};
|
|
39781
|
+
}
|
|
39782
|
+
function buildObjectiveApplyPlanCommand(reportPath) {
|
|
39783
|
+
return `foh objective apply --plan ${quoteCliArg(reportPath)} --dry-run --json`;
|
|
39784
|
+
}
|
|
39785
|
+
function injectObjectiveApplyGuidance(packet, reportPath) {
|
|
39786
|
+
if (!packet) return void 0;
|
|
39787
|
+
const planCommand = buildObjectiveApplyPlanCommand(reportPath);
|
|
39788
|
+
return {
|
|
39789
|
+
...packet,
|
|
39790
|
+
validation_commands: dedupeCommands([
|
|
39791
|
+
planCommand,
|
|
39792
|
+
...uniqueStrings(asArray(packet.validation_commands))
|
|
39793
|
+
]),
|
|
39794
|
+
actions: asArray(packet.actions).map(asRecord3).filter((action) => normalizeString2(action.id)).map((action) => ({
|
|
39795
|
+
...action,
|
|
39796
|
+
validator_commands: dedupeCommands([
|
|
39797
|
+
planCommand,
|
|
39798
|
+
...uniqueStrings(asArray(action.validator_commands))
|
|
39799
|
+
])
|
|
39800
|
+
}))
|
|
39801
|
+
};
|
|
39802
|
+
}
|
|
38719
39803
|
function customerEvidenceActionPacketFromSources(...sources) {
|
|
39804
|
+
const skeletonHints = skeletonHintsById(...sources);
|
|
38720
39805
|
for (const source of sources) {
|
|
38721
39806
|
const report = asRecord3(source);
|
|
38722
39807
|
const requestPackets = asRecord3(report.request_packets);
|
|
@@ -38736,14 +39821,14 @@ function customerEvidenceActionPacketFromSources(...sources) {
|
|
|
38736
39821
|
report.customer_actions ? { actions: report.customer_actions } : null
|
|
38737
39822
|
];
|
|
38738
39823
|
for (const candidate of candidates) {
|
|
38739
|
-
const normalized = normalizeCustomerEvidenceActionPacket(candidate);
|
|
39824
|
+
const normalized = mergeActionPacketWithSkeletonHints(normalizeCustomerEvidenceActionPacket(candidate), skeletonHints);
|
|
38740
39825
|
if (normalized) return normalized;
|
|
38741
39826
|
}
|
|
38742
39827
|
}
|
|
38743
39828
|
return void 0;
|
|
38744
39829
|
}
|
|
38745
39830
|
function normalizeObjectiveArtifactPath(value) {
|
|
38746
|
-
return value.trim() ? (0,
|
|
39831
|
+
return value.trim() ? (0, import_node_path4.resolve)(value.trim()) : "";
|
|
38747
39832
|
}
|
|
38748
39833
|
function resolveObjectiveArtifactPath(value) {
|
|
38749
39834
|
return normalizeObjectiveArtifactPath(value);
|
|
@@ -38758,10 +39843,26 @@ function pickEvidencePacket(value) {
|
|
|
38758
39843
|
const payload = firstNonEmptyObject(record2.payload);
|
|
38759
39844
|
return payload ?? record2;
|
|
38760
39845
|
}
|
|
38761
|
-
function
|
|
38762
|
-
const
|
|
39846
|
+
function evidenceRequestFromSkeletonPacket(value) {
|
|
39847
|
+
const packet = asRecord3(value);
|
|
39848
|
+
const skeletons = asArray(packet.skeletons).map(asRecord3).filter((item) => normalizeString2(item.id));
|
|
39849
|
+
if (skeletons.length === 0) return void 0;
|
|
39850
|
+
const launchScopeId = normalizeString2(packet.launch_scope_id) || normalizeString2(skeletons[0]?.launch_scope_id);
|
|
39851
|
+
return {
|
|
39852
|
+
...launchScopeId ? { launch_scope_id: launchScopeId } : {},
|
|
39853
|
+
evidence: skeletons.map((skeleton) => ({
|
|
39854
|
+
id: normalizeString2(skeleton.id),
|
|
39855
|
+
launch_scope_id: normalizeString2(skeleton.launch_scope_id) || launchScopeId || void 0,
|
|
39856
|
+
target_evidence_path: normalizeString2(skeleton.target_evidence_path) || `customer-evidence/${normalizeString2(skeleton.id)}.json`,
|
|
39857
|
+
payload: asRecord3(skeleton.payload_template)
|
|
39858
|
+
}))
|
|
39859
|
+
};
|
|
39860
|
+
}
|
|
39861
|
+
function readEvidencePacketFromPlan(path5) {
|
|
39862
|
+
const plan = asRecord3(readJsonArtifact(path5));
|
|
38763
39863
|
const sourceReports = asRecord3(plan.source_reports);
|
|
38764
|
-
|
|
39864
|
+
const packet = pickEvidencePacket(plan.evidence) ?? pickEvidencePacket(plan.evidence_packet) ?? pickEvidencePacket(sourceReports.customer_live_status && asRecord3(sourceReports.customer_live_status).evidence) ?? pickEvidencePacket(asRecord3(sourceReports.setup_workflow).evidence_packet) ?? {};
|
|
39865
|
+
return evidenceRequestFromSkeletonPacket(packet) ?? packet;
|
|
38765
39866
|
}
|
|
38766
39867
|
function inferEvidenceFromProofPaths(evidence) {
|
|
38767
39868
|
const record2 = asRecord3(evidence);
|
|
@@ -38876,16 +39977,16 @@ function buildDeveloperReadinessPacket(input) {
|
|
|
38876
39977
|
}
|
|
38877
39978
|
function resolveObjectiveReportPath(value) {
|
|
38878
39979
|
const raw = normalizeString2(value);
|
|
38879
|
-
if (!raw || raw === "latest") return (0,
|
|
38880
|
-
return (0,
|
|
39980
|
+
if (!raw || raw === "latest") return (0, import_node_path4.resolve)(DEFAULT_OBJECTIVE_REPORT_PATH);
|
|
39981
|
+
return (0, import_node_path4.resolve)(raw);
|
|
38881
39982
|
}
|
|
38882
|
-
function writeJsonArtifact2(
|
|
38883
|
-
(0,
|
|
38884
|
-
(0,
|
|
39983
|
+
function writeJsonArtifact2(path5, value) {
|
|
39984
|
+
(0, import_node_fs7.mkdirSync)((0, import_node_path4.dirname)(path5), { recursive: true });
|
|
39985
|
+
(0, import_node_fs7.writeFileSync)(path5, `${JSON.stringify(value, null, 2)}
|
|
38885
39986
|
`, "utf8");
|
|
38886
39987
|
}
|
|
38887
|
-
function readJsonArtifact(
|
|
38888
|
-
return JSON.parse((0,
|
|
39988
|
+
function readJsonArtifact(path5) {
|
|
39989
|
+
return JSON.parse((0, import_node_fs7.readFileSync)(path5, "utf8"));
|
|
38889
39990
|
}
|
|
38890
39991
|
function buildSetupBody(opts) {
|
|
38891
39992
|
const tools = parseCsvOption(opts.tools ?? "widget,voice,email,callback,calendar,crm,webhook,whatsapp") ?? [];
|
|
@@ -39121,8 +40222,14 @@ function buildObjectiveReport(input) {
|
|
|
39121
40222
|
...collectStringArrays(setup, /* @__PURE__ */ new Set(["reason_codes", "blocker_reason_codes"])),
|
|
39122
40223
|
...collectStringArrays(live, /* @__PURE__ */ new Set(["reason_codes", "blocker_reason_codes"]))
|
|
39123
40224
|
]);
|
|
40225
|
+
const objectiveArtifactPath = normalizeString2(input.opts.out) || DEFAULT_OBJECTIVE_REPORT_PATH;
|
|
39124
40226
|
const debugSource = normalizeString2(input.opts.out) || "latest";
|
|
40227
|
+
let customerEvidenceActionPacket = customerEvidenceActionPacketFromSources(live, setup);
|
|
40228
|
+
if (customerEvidenceActionPacket) {
|
|
40229
|
+
customerEvidenceActionPacket = injectObjectiveApplyGuidance(customerEvidenceActionPacket, objectiveArtifactPath);
|
|
40230
|
+
}
|
|
39125
40231
|
const nextCommands = dedupeCommands([
|
|
40232
|
+
...customerEvidenceActionPacket ? [buildObjectiveApplyPlanCommand(objectiveArtifactPath)] : [],
|
|
39126
40233
|
...collectStringArrays(setup, /* @__PURE__ */ new Set(["next_commands"])),
|
|
39127
40234
|
...collectStringArrays(live, /* @__PURE__ */ new Set(["next_commands"])),
|
|
39128
40235
|
`foh objective debug --from ${debugSource} --json`
|
|
@@ -39136,7 +40243,6 @@ function buildObjectiveReport(input) {
|
|
|
39136
40243
|
...collectArtifactRefs(setup),
|
|
39137
40244
|
...collectArtifactRefs(live)
|
|
39138
40245
|
]);
|
|
39139
|
-
const customerEvidenceActionPacket = customerEvidenceActionPacketFromSources(live, setup);
|
|
39140
40246
|
const developerReadinessPacket = buildDeveloperReadinessPacket({
|
|
39141
40247
|
opts: input.opts,
|
|
39142
40248
|
setupWorkflow: setup,
|
|
@@ -39379,7 +40485,7 @@ function registerObjective(program3) {
|
|
|
39379
40485
|
}));
|
|
39380
40486
|
objective.command("debug").description("Explain the next debugging action for an objective report").option("--from <source>", "Report source alias or path", "latest").option("--json", "Output as JSON").action(async (opts) => withCommandErrorHandling(async () => {
|
|
39381
40487
|
const sourcePath = resolveObjectiveReportPath(opts.from);
|
|
39382
|
-
if (!(0,
|
|
40488
|
+
if (!(0, import_node_fs7.existsSync)(sourcePath)) {
|
|
39383
40489
|
format(cliEnvelope({
|
|
39384
40490
|
schemaVersion: "agent_workbench_debug.v1",
|
|
39385
40491
|
status: "fail",
|
|
@@ -40450,6 +41556,277 @@ function registerInteractive(program3) {
|
|
|
40450
41556
|
});
|
|
40451
41557
|
}
|
|
40452
41558
|
|
|
41559
|
+
// src/commands/install.ts
|
|
41560
|
+
var import_node_fs8 = __toESM(require("node:fs"));
|
|
41561
|
+
var import_node_path5 = __toESM(require("node:path"));
|
|
41562
|
+
var import_node_child_process3 = require("node:child_process");
|
|
41563
|
+
function splitCsv(value) {
|
|
41564
|
+
if (typeof value !== "string") return [];
|
|
41565
|
+
return value.split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
41566
|
+
}
|
|
41567
|
+
function pickFirstParam(url2, names) {
|
|
41568
|
+
for (const name of names) {
|
|
41569
|
+
const value = url2.searchParams.get(name);
|
|
41570
|
+
if (value && value.trim()) return value.trim();
|
|
41571
|
+
}
|
|
41572
|
+
return void 0;
|
|
41573
|
+
}
|
|
41574
|
+
function parseInstallLink(raw) {
|
|
41575
|
+
if (!raw) return {};
|
|
41576
|
+
let url2;
|
|
41577
|
+
try {
|
|
41578
|
+
url2 = new URL(raw);
|
|
41579
|
+
} catch {
|
|
41580
|
+
throw new FohError({
|
|
41581
|
+
step: "install.link",
|
|
41582
|
+
error: "Install link is not a valid URL.",
|
|
41583
|
+
remediation: "Use the one-time Front Of House install link, or run: foh install --agent <agent-id>"
|
|
41584
|
+
});
|
|
41585
|
+
}
|
|
41586
|
+
const domainsFromRepeatedParams = [
|
|
41587
|
+
...url2.searchParams.getAll("domain"),
|
|
41588
|
+
...url2.searchParams.getAll("domains")
|
|
41589
|
+
].flatMap((value) => splitCsv(value));
|
|
41590
|
+
return {
|
|
41591
|
+
agent: pickFirstParam(url2, ["agent", "agent_id"]),
|
|
41592
|
+
org: pickFirstParam(url2, ["org", "org_id"]),
|
|
41593
|
+
domains: Array.from(new Set(domainsFromRepeatedParams)),
|
|
41594
|
+
apiUrl: pickFirstParam(url2, ["api_url", "apiUrl"]),
|
|
41595
|
+
publicKey: pickFirstParam(url2, ["channel", "channel_public_key", "public_key"]),
|
|
41596
|
+
installToken: pickFirstParam(url2, ["token", "install_token"])
|
|
41597
|
+
};
|
|
41598
|
+
}
|
|
41599
|
+
async function resolveInstallLinkToken(token, apiUrl) {
|
|
41600
|
+
const baseUrl = (apiUrl || DEFAULT_WIDGET_API_URL).replace(/\/$/, "");
|
|
41601
|
+
const res = await fetch(`${baseUrl}/v1/widget/install-link/resolve`, {
|
|
41602
|
+
method: "POST",
|
|
41603
|
+
headers: { "Content-Type": "application/json" },
|
|
41604
|
+
body: JSON.stringify({ token })
|
|
41605
|
+
});
|
|
41606
|
+
let body = {};
|
|
41607
|
+
try {
|
|
41608
|
+
body = await res.json();
|
|
41609
|
+
} catch {
|
|
41610
|
+
body = {};
|
|
41611
|
+
}
|
|
41612
|
+
if (!res.ok) {
|
|
41613
|
+
throw new FohError({
|
|
41614
|
+
step: "install.link.resolve",
|
|
41615
|
+
error: String(body?.error || `HTTP ${res.status}`),
|
|
41616
|
+
remediation: String(body?.code === "install_link_token_expired" ? "Ask the operator for a fresh install link." : "Check the install link and retry."),
|
|
41617
|
+
statusCode: res.status,
|
|
41618
|
+
detail: body
|
|
41619
|
+
});
|
|
41620
|
+
}
|
|
41621
|
+
return {
|
|
41622
|
+
agent: typeof body?.agent_id === "string" ? body.agent_id.trim() : void 0,
|
|
41623
|
+
org: typeof body?.org_id === "string" ? body.org_id.trim() : void 0,
|
|
41624
|
+
publicKey: typeof body?.widget_public_key === "string" ? body.widget_public_key.trim() : void 0,
|
|
41625
|
+
apiUrl: typeof body?.api_url === "string" ? body.api_url.trim() : void 0,
|
|
41626
|
+
domains: Array.isArray(body?.allowed_domains) ? body.allowed_domains.map((domain2) => String(domain2 || "").trim()).filter(Boolean) : void 0
|
|
41627
|
+
};
|
|
41628
|
+
}
|
|
41629
|
+
function findPackageJsonForTarget(siteRoot, targetPath) {
|
|
41630
|
+
const root = import_node_path5.default.resolve(siteRoot || ".");
|
|
41631
|
+
let current = import_node_path5.default.dirname(targetPath);
|
|
41632
|
+
while (current.startsWith(root)) {
|
|
41633
|
+
const candidate = import_node_path5.default.join(current, "package.json");
|
|
41634
|
+
if (import_node_fs8.default.existsSync(candidate) && import_node_fs8.default.statSync(candidate).isFile()) return candidate;
|
|
41635
|
+
const next = import_node_path5.default.dirname(current);
|
|
41636
|
+
if (next === current) break;
|
|
41637
|
+
current = next;
|
|
41638
|
+
}
|
|
41639
|
+
return null;
|
|
41640
|
+
}
|
|
41641
|
+
function packageManagerFor(cwd, siteRoot) {
|
|
41642
|
+
const root = import_node_path5.default.resolve(siteRoot || ".");
|
|
41643
|
+
let current = cwd;
|
|
41644
|
+
while (current.startsWith(root)) {
|
|
41645
|
+
if (import_node_fs8.default.existsSync(import_node_path5.default.join(current, "pnpm-lock.yaml"))) return "pnpm";
|
|
41646
|
+
if (import_node_fs8.default.existsSync(import_node_path5.default.join(current, "yarn.lock"))) return "yarn";
|
|
41647
|
+
if (import_node_fs8.default.existsSync(import_node_path5.default.join(current, "bun.lockb")) || import_node_fs8.default.existsSync(import_node_path5.default.join(current, "bun.lock"))) return "bun";
|
|
41648
|
+
const next = import_node_path5.default.dirname(current);
|
|
41649
|
+
if (next === current) break;
|
|
41650
|
+
current = next;
|
|
41651
|
+
}
|
|
41652
|
+
return "npm";
|
|
41653
|
+
}
|
|
41654
|
+
function detectBuildCommand(siteRoot, targetPath) {
|
|
41655
|
+
const packageJsonPath = findPackageJsonForTarget(siteRoot, targetPath);
|
|
41656
|
+
if (!packageJsonPath) return null;
|
|
41657
|
+
const packageJson = JSON.parse(import_node_fs8.default.readFileSync(packageJsonPath, "utf8"));
|
|
41658
|
+
if (!packageJson.scripts?.build) return null;
|
|
41659
|
+
const cwd = import_node_path5.default.dirname(packageJsonPath);
|
|
41660
|
+
const packageManager = packageManagerFor(cwd, siteRoot);
|
|
41661
|
+
if (packageManager === "npm") {
|
|
41662
|
+
return {
|
|
41663
|
+
cwd,
|
|
41664
|
+
command: "npm",
|
|
41665
|
+
args: ["run", "build"],
|
|
41666
|
+
display: "npm run build",
|
|
41667
|
+
package_manager: packageManager
|
|
41668
|
+
};
|
|
41669
|
+
}
|
|
41670
|
+
return {
|
|
41671
|
+
cwd,
|
|
41672
|
+
command: packageManager,
|
|
41673
|
+
args: ["build"],
|
|
41674
|
+
display: `${packageManager} build`,
|
|
41675
|
+
package_manager: packageManager
|
|
41676
|
+
};
|
|
41677
|
+
}
|
|
41678
|
+
function runBuildCommand(build) {
|
|
41679
|
+
const command = process.platform === "win32" ? "cmd.exe" : build.command;
|
|
41680
|
+
const args = process.platform === "win32" ? ["/d", "/s", "/c", build.command, ...build.args] : build.args;
|
|
41681
|
+
const result = (0, import_node_child_process3.spawnSync)(command, args, {
|
|
41682
|
+
cwd: build.cwd,
|
|
41683
|
+
encoding: "utf8",
|
|
41684
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
41685
|
+
windowsHide: true
|
|
41686
|
+
});
|
|
41687
|
+
const stdout = String(result.stdout || "").split(/\r?\n/).filter(Boolean).slice(-20);
|
|
41688
|
+
const stderr = String(result.stderr || "").split(/\r?\n/).filter(Boolean).slice(-20);
|
|
41689
|
+
return {
|
|
41690
|
+
ok: result.status === 0,
|
|
41691
|
+
exit_code: result.status,
|
|
41692
|
+
command: build.display,
|
|
41693
|
+
cwd: import_node_path5.default.relative(process.cwd(), build.cwd).replaceAll("\\", "/") || ".",
|
|
41694
|
+
stdout_tail: stdout,
|
|
41695
|
+
stderr_tail: stderr,
|
|
41696
|
+
...result.error ? { error: result.error.message } : {}
|
|
41697
|
+
};
|
|
41698
|
+
}
|
|
41699
|
+
function registerInstall(program3) {
|
|
41700
|
+
program3.command("install [installLink]").description("Install Front Of House on this website").option("--agent <id>", "Agent ID; normally supplied by the install link").option("--org <id>", "Org ID; normally supplied by the install link or stored org context").option("--domains <d1,d2>", "Optional comma-separated widget domain allowlist").option("--platform <platform>", `Install target platform: ${SUPPORTED_INSTALL_PLATFORMS.join("|")}`, "custom").option("--site-root <path>", "Website repository root", ".").option("--target <path>", "HTML shell to patch, relative to --site-root").option("--api-url <url>", "API base URL override").option("--origin <urlOrHost>", "Website origin to simulate for domain allowlists, e.g. https://www.example.com").option("--dry-run", "Resolve and report install actions without writing files or running build/smoke").option("--no-build", "Do not run the website build command after installing").option("--no-smoke", "Do not run the widget smoke check after installing").option("--json", "Output as JSON").action(async (installLink, opts) => withCommandErrorHandling(async () => {
|
|
41701
|
+
const parsedLink = parseInstallLink(installLink);
|
|
41702
|
+
const resolvedLink = parsedLink.installToken ? await resolveInstallLinkToken(parsedLink.installToken, opts.apiUrl || parsedLink.apiUrl) : {};
|
|
41703
|
+
const link = {
|
|
41704
|
+
...parsedLink,
|
|
41705
|
+
...resolvedLink,
|
|
41706
|
+
apiUrl: opts.apiUrl || resolvedLink.apiUrl || parsedLink.apiUrl,
|
|
41707
|
+
publicKey: resolvedLink.publicKey || parsedLink.publicKey
|
|
41708
|
+
};
|
|
41709
|
+
const domains = splitCsv(opts.domains);
|
|
41710
|
+
const agent = opts.agent || link.agent;
|
|
41711
|
+
const org = opts.org || link.org;
|
|
41712
|
+
const apiUrl = opts.apiUrl || link.apiUrl;
|
|
41713
|
+
const domainAllowlist = domains.length > 0 ? domains : link.domains || [];
|
|
41714
|
+
const platform = normalizeInstallPlatform(opts.platform);
|
|
41715
|
+
const smokeOrigin = normalizeOrigin(
|
|
41716
|
+
typeof opts.origin === "string" && opts.origin.trim() ? opts.origin : domainAllowlist[0]
|
|
41717
|
+
);
|
|
41718
|
+
const snippet2 = link.publicKey ? widgetSnippetFromPublicKey(link.publicKey, apiUrl) : void 0;
|
|
41719
|
+
if (!agent && !snippet2) {
|
|
41720
|
+
throw new FohError({
|
|
41721
|
+
step: "install",
|
|
41722
|
+
error: "No Front Of House agent was provided.",
|
|
41723
|
+
remediation: "Run: foh install <install-link>, or foh install --agent <agent-id>"
|
|
41724
|
+
});
|
|
41725
|
+
}
|
|
41726
|
+
if (isBuilderHostedPlatform(platform)) {
|
|
41727
|
+
if (!snippet2) {
|
|
41728
|
+
throw new FohError({
|
|
41729
|
+
step: "install",
|
|
41730
|
+
error: "Builder-hosted install requires a public widget snippet or install link.",
|
|
41731
|
+
remediation: "Run: foh install <install-link-with-channel-or-token> --platform <platform>"
|
|
41732
|
+
});
|
|
41733
|
+
}
|
|
41734
|
+
const instructions = buildBuilderPlatformInstructions({
|
|
41735
|
+
platform,
|
|
41736
|
+
snippet: snippet2,
|
|
41737
|
+
publicKey: link.publicKey,
|
|
41738
|
+
domains: domainAllowlist,
|
|
41739
|
+
origin: smokeOrigin
|
|
41740
|
+
});
|
|
41741
|
+
format({
|
|
41742
|
+
schema_version: "foh_site_install.v1",
|
|
41743
|
+
status: opts.dryRun ? "dry_run" : "ready_for_manual_install",
|
|
41744
|
+
install_mode: "builder_platform",
|
|
41745
|
+
platform,
|
|
41746
|
+
site_root: null,
|
|
41747
|
+
target_path: null,
|
|
41748
|
+
changed: false,
|
|
41749
|
+
install: instructions,
|
|
41750
|
+
widget_public_key: instructions.widget_public_key,
|
|
41751
|
+
domain_allowlist: instructions.domain_allowlist,
|
|
41752
|
+
origin_hint: instructions.origin_hint,
|
|
41753
|
+
install_verification_command: instructions.install_verification_command,
|
|
41754
|
+
runtime_smoke_command: instructions.runtime_smoke_command,
|
|
41755
|
+
verification_commands: instructions.verification_commands,
|
|
41756
|
+
build: {
|
|
41757
|
+
ok: null,
|
|
41758
|
+
skipped: true,
|
|
41759
|
+
reason: "builder_platform_manual_install",
|
|
41760
|
+
command: null
|
|
41761
|
+
},
|
|
41762
|
+
smoke: {
|
|
41763
|
+
ok: null,
|
|
41764
|
+
skipped: true,
|
|
41765
|
+
reason: "builder_platform_manual_install"
|
|
41766
|
+
},
|
|
41767
|
+
next_commands: instructions.verification_commands
|
|
41768
|
+
}, { json: opts.json ?? false });
|
|
41769
|
+
return;
|
|
41770
|
+
}
|
|
41771
|
+
const install = await installWidgetSite({
|
|
41772
|
+
agent,
|
|
41773
|
+
org,
|
|
41774
|
+
domains: domainAllowlist,
|
|
41775
|
+
siteRoot: opts.siteRoot,
|
|
41776
|
+
target: opts.target,
|
|
41777
|
+
snippet: snippet2,
|
|
41778
|
+
apiUrl,
|
|
41779
|
+
dryRun: opts.dryRun
|
|
41780
|
+
});
|
|
41781
|
+
const targetPath = install.absolute_target_path || resolveWidgetInstallTarget(opts.siteRoot, opts.target);
|
|
41782
|
+
const buildCommand = detectBuildCommand(opts.siteRoot, targetPath);
|
|
41783
|
+
const installGuidance = buildWidgetInstallGuidance({
|
|
41784
|
+
publicKey: install.widget_public_key || link.publicKey || null,
|
|
41785
|
+
agentId: agent,
|
|
41786
|
+
domains: domainAllowlist,
|
|
41787
|
+
origin: smokeOrigin
|
|
41788
|
+
});
|
|
41789
|
+
const build = !opts.dryRun && opts.build !== false && buildCommand ? runBuildCommand(buildCommand) : null;
|
|
41790
|
+
const smokePublicKey = installGuidance.widget_public_key;
|
|
41791
|
+
const smoke = !opts.dryRun && opts.smoke !== false && smokePublicKey ? await runWidgetSmoke(smokePublicKey, apiUrl, smokeOrigin) : null;
|
|
41792
|
+
const ok = install.status === "installed" && (build ? build.ok : true) && (smoke ? smoke.failed === 0 : true);
|
|
41793
|
+
const result = {
|
|
41794
|
+
schema_version: "foh_site_install.v1",
|
|
41795
|
+
status: opts.dryRun ? "dry_run" : ok ? "installed" : "failed",
|
|
41796
|
+
install_mode: "repo_patch",
|
|
41797
|
+
platform,
|
|
41798
|
+
site_root: import_node_path5.default.resolve(opts.siteRoot || "."),
|
|
41799
|
+
target_path: install.target_path,
|
|
41800
|
+
changed: install.changed,
|
|
41801
|
+
install,
|
|
41802
|
+
widget_public_key: installGuidance.widget_public_key,
|
|
41803
|
+
domain_allowlist: installGuidance.domain_allowlist,
|
|
41804
|
+
origin_hint: installGuidance.origin_hint,
|
|
41805
|
+
install_verification_command: installGuidance.install_verification_command,
|
|
41806
|
+
runtime_smoke_command: installGuidance.runtime_smoke_command,
|
|
41807
|
+
verification_commands: installGuidance.verification_commands,
|
|
41808
|
+
build: build || {
|
|
41809
|
+
ok: null,
|
|
41810
|
+
skipped: opts.dryRun || opts.build === false || !buildCommand,
|
|
41811
|
+
reason: opts.dryRun ? "dry_run" : opts.build === false ? "disabled" : "no package.json build script found near target",
|
|
41812
|
+
command: buildCommand?.display ?? null
|
|
41813
|
+
},
|
|
41814
|
+
smoke: smoke || {
|
|
41815
|
+
ok: null,
|
|
41816
|
+
skipped: opts.dryRun || opts.smoke === false || !install.widget_public_key,
|
|
41817
|
+
reason: opts.dryRun ? "dry_run" : opts.smoke === false ? "disabled" : "widget public key unavailable"
|
|
41818
|
+
},
|
|
41819
|
+
next_commands: ok ? [] : [
|
|
41820
|
+
build && !build.ok ? build.command : null,
|
|
41821
|
+
smoke && smoke.failed > 0 ? installGuidance.runtime_smoke_command : null,
|
|
41822
|
+
!smoke && installGuidance.verification_commands.length > 0 ? installGuidance.verification_commands[0] : null
|
|
41823
|
+
].filter(Boolean)
|
|
41824
|
+
};
|
|
41825
|
+
format(result, { json: opts.json ?? false });
|
|
41826
|
+
if (!ok && !opts.dryRun) markCommandFailed(1);
|
|
41827
|
+
}));
|
|
41828
|
+
}
|
|
41829
|
+
|
|
40453
41830
|
// src/commands/home-actions.ts
|
|
40454
41831
|
function resolveHomeState(apiUrlOverride) {
|
|
40455
41832
|
try {
|
|
@@ -41067,9 +42444,9 @@ function compareSemver(a, b) {
|
|
|
41067
42444
|
}
|
|
41068
42445
|
return 0;
|
|
41069
42446
|
}
|
|
41070
|
-
function readPackageJsonVersion(
|
|
42447
|
+
function readPackageJsonVersion(path5) {
|
|
41071
42448
|
try {
|
|
41072
|
-
const raw = (0, import_fs12.readFileSync)(
|
|
42449
|
+
const raw = (0, import_fs12.readFileSync)(path5, "utf-8");
|
|
41073
42450
|
const parsed = JSON.parse(raw);
|
|
41074
42451
|
const version2 = String(parsed.version ?? "").trim();
|
|
41075
42452
|
return version2 || void 0;
|
|
@@ -41320,8 +42697,8 @@ var PHONE_RE2 = /(?<!\w)(?:\+?\d[\d\s().-]{7,}\d)(?!\w)/g;
|
|
|
41320
42697
|
function escapeRegExp(value) {
|
|
41321
42698
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
41322
42699
|
}
|
|
41323
|
-
function pathVariants(
|
|
41324
|
-
const resolved = (0, import_path11.resolve)(
|
|
42700
|
+
function pathVariants(path5) {
|
|
42701
|
+
const resolved = (0, import_path11.resolve)(path5);
|
|
41325
42702
|
const slash = resolved.replace(/\\/g, "/");
|
|
41326
42703
|
const backslash = resolved.replace(/\//g, "\\");
|
|
41327
42704
|
return Array.from(new Set([
|
|
@@ -41365,9 +42742,9 @@ function redactExternalAgentSecretText(text) {
|
|
|
41365
42742
|
}
|
|
41366
42743
|
function artifactFiles(runDir) {
|
|
41367
42744
|
if (!(0, import_fs13.existsSync)(runDir)) return [];
|
|
41368
|
-
return (0, import_fs13.readdirSync)(runDir).map((name) => (0, import_path11.join)(runDir, name)).filter((
|
|
41369
|
-
const stat = (0, import_fs13.statSync)(
|
|
41370
|
-
const name =
|
|
42745
|
+
return (0, import_fs13.readdirSync)(runDir).map((name) => (0, import_path11.join)(runDir, name)).filter((path5) => {
|
|
42746
|
+
const stat = (0, import_fs13.statSync)(path5);
|
|
42747
|
+
const name = path5.split(/[\\/]/).pop() || "";
|
|
41371
42748
|
if (name.endsWith(".redacted")) return false;
|
|
41372
42749
|
return stat.isFile() && (TEXT_ARTIFACT_NAMES.has(name) || name.startsWith("command-output-cmd_"));
|
|
41373
42750
|
}).sort();
|
|
@@ -41736,10 +43113,10 @@ function collectDocsFrom(value, docs) {
|
|
|
41736
43113
|
}
|
|
41737
43114
|
function readExternalAgentMetadata(runDir) {
|
|
41738
43115
|
for (const filename of EXTERNAL_AGENT_METADATA_FILENAMES) {
|
|
41739
|
-
const
|
|
41740
|
-
if (!(0, import_fs15.existsSync)(
|
|
43116
|
+
const path5 = (0, import_path14.join)(runDir, filename);
|
|
43117
|
+
if (!(0, import_fs15.existsSync)(path5)) continue;
|
|
41741
43118
|
try {
|
|
41742
|
-
const parsed = JSON.parse((0, import_fs15.readFileSync)(
|
|
43119
|
+
const parsed = JSON.parse((0, import_fs15.readFileSync)(path5, "utf8"));
|
|
41743
43120
|
const docs = /* @__PURE__ */ new Set();
|
|
41744
43121
|
collectDocsFrom(parsed.docs_pages_used, docs);
|
|
41745
43122
|
collectDocsFrom(parsed.docs_pages_observed, docs);
|
|
@@ -41767,11 +43144,11 @@ function readExternalAgentMetadata(runDir) {
|
|
|
41767
43144
|
}
|
|
41768
43145
|
|
|
41769
43146
|
// src/lib/external-agent-executor-artifacts.ts
|
|
41770
|
-
function redactArtifactFile(
|
|
41771
|
-
if (!(0, import_fs16.existsSync)(
|
|
41772
|
-
const original = (0, import_fs16.readFileSync)(
|
|
43147
|
+
function redactArtifactFile(path5, input = {}) {
|
|
43148
|
+
if (!(0, import_fs16.existsSync)(path5)) return;
|
|
43149
|
+
const original = (0, import_fs16.readFileSync)(path5, "utf8");
|
|
41773
43150
|
const redacted = redactExternalAgentArtifactText(original, input);
|
|
41774
|
-
if (redacted !== original) (0, import_fs16.writeFileSync)(
|
|
43151
|
+
if (redacted !== original) (0, import_fs16.writeFileSync)(path5, redacted, "utf8");
|
|
41775
43152
|
}
|
|
41776
43153
|
function redactExternalAgentOutputArtifacts(run, input = {}) {
|
|
41777
43154
|
redactArtifactFile(run.outputs.jsonl, input);
|
|
@@ -41812,11 +43189,11 @@ function proofArtifactPasses(runDir) {
|
|
|
41812
43189
|
return false;
|
|
41813
43190
|
}
|
|
41814
43191
|
}
|
|
41815
|
-
function readIfExists(
|
|
41816
|
-
return (0, import_fs17.existsSync)(
|
|
43192
|
+
function readIfExists(path5) {
|
|
43193
|
+
return (0, import_fs17.existsSync)(path5) ? (0, import_fs17.readFileSync)(path5, "utf8") : "";
|
|
41817
43194
|
}
|
|
41818
|
-
function relativeArtifactName(
|
|
41819
|
-
return (0, import_path16.basename)(
|
|
43195
|
+
function relativeArtifactName(path5) {
|
|
43196
|
+
return (0, import_path16.basename)(path5);
|
|
41820
43197
|
}
|
|
41821
43198
|
function externalAgentSummaryTemplateCommand() {
|
|
41822
43199
|
return [
|
|
@@ -42149,11 +43526,11 @@ async function runExternalAgentEvalAuthPreflight(env = process.env, options = {}
|
|
|
42149
43526
|
matched_org: matchedOrg
|
|
42150
43527
|
};
|
|
42151
43528
|
}
|
|
42152
|
-
function normalizeForCompare(
|
|
42153
|
-
const resolved = (0, import_path18.resolve)(
|
|
43529
|
+
function normalizeForCompare(path5) {
|
|
43530
|
+
const resolved = (0, import_path18.resolve)(path5);
|
|
42154
43531
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
42155
43532
|
}
|
|
42156
|
-
function
|
|
43533
|
+
function isPathInside2(childPath, parentPath) {
|
|
42157
43534
|
const child = normalizeForCompare(childPath);
|
|
42158
43535
|
const parent = normalizeForCompare(parentPath);
|
|
42159
43536
|
const rel = (0, import_path18.relative)(parent, child);
|
|
@@ -42540,7 +43917,7 @@ function createExternalAgentExecutorPlan(options) {
|
|
|
42540
43917
|
});
|
|
42541
43918
|
const privateRepoRoot = privateRepo.root;
|
|
42542
43919
|
const workspaceRoot = resolveWorkspaceRoot({ batchPath, workspaceRoot: options.workspaceRoot, privateRepoRoot });
|
|
42543
|
-
if (
|
|
43920
|
+
if (isPathInside2(workspaceRoot, privateRepoRoot)) {
|
|
42544
43921
|
throw new ExternalAgentExecutorError(
|
|
42545
43922
|
"external_agent_workspace_inside_private_repo",
|
|
42546
43923
|
`Workspace root must be outside the private repository. workspace=${workspaceRoot} repo=${privateRepoRoot}`
|
|
@@ -42670,11 +44047,11 @@ function createExternalAgentExecutorPlan(options) {
|
|
|
42670
44047
|
};
|
|
42671
44048
|
}
|
|
42672
44049
|
function writeExternalAgentExecutorPlan(plan) {
|
|
42673
|
-
const
|
|
44050
|
+
const path5 = (0, import_path18.join)(plan.batch_dir, "executor-plan.json");
|
|
42674
44051
|
(0, import_fs19.mkdirSync)(plan.batch_dir, { recursive: true });
|
|
42675
|
-
(0, import_fs19.writeFileSync)(
|
|
44052
|
+
(0, import_fs19.writeFileSync)(path5, `${JSON.stringify(plan, null, 2)}
|
|
42676
44053
|
`, "utf8");
|
|
42677
|
-
return
|
|
44054
|
+
return path5;
|
|
42678
44055
|
}
|
|
42679
44056
|
async function executeExternalAgentExecutorPlan(plan, options = {}) {
|
|
42680
44057
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -43507,16 +44884,16 @@ function writePrompt(runDir, promptVersion, context = {}) {
|
|
|
43507
44884
|
knowledgeMissPromptContext(context.knowledgeQuestion, context.expectedAnswer),
|
|
43508
44885
|
agencySetupPromptContext(context)
|
|
43509
44886
|
].join("");
|
|
43510
|
-
const
|
|
43511
|
-
(0, import_fs21.writeFileSync)(
|
|
44887
|
+
const path5 = (0, import_path20.join)(runDir, "prompt.txt");
|
|
44888
|
+
(0, import_fs21.writeFileSync)(path5, `${prompt}
|
|
43512
44889
|
`, "utf8");
|
|
43513
|
-
return
|
|
44890
|
+
return path5;
|
|
43514
44891
|
}
|
|
43515
44892
|
function writeSession(runDir, session) {
|
|
43516
|
-
const
|
|
43517
|
-
(0, import_fs21.writeFileSync)(
|
|
44893
|
+
const path5 = (0, import_path20.join)(runDir, "session.json");
|
|
44894
|
+
(0, import_fs21.writeFileSync)(path5, `${JSON.stringify(session, null, 2)}
|
|
43518
44895
|
`, "utf8");
|
|
43519
|
-
return
|
|
44896
|
+
return path5;
|
|
43520
44897
|
}
|
|
43521
44898
|
function buildDefaultEvalState() {
|
|
43522
44899
|
return {
|
|
@@ -44127,6 +45504,7 @@ registerBug(program2);
|
|
|
44127
45504
|
registerProve(program2);
|
|
44128
45505
|
registerObjective(program2);
|
|
44129
45506
|
registerInteractive(program2);
|
|
45507
|
+
registerInstall(program2);
|
|
44130
45508
|
registerAgentPublishCommand(program2, { publicAlias: true });
|
|
44131
45509
|
registerEval(program2);
|
|
44132
45510
|
registerUpdate(program2);
|