failproofai 0.0.10-beta.0 → 0.0.10-beta.2
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/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +2 -2
- package/.next/standalone/.next/server/app/_not-found.rsc +17 -17
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +17 -17
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +11 -11
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +16 -16
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +16 -16
- package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +11 -11
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +8 -8
- package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0dtn9lr._.js +1 -1
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_05pz9._._.js +1 -1
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_0z7w.hh._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0-2wr.c._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0.~m-w2._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0709m8.._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0bz245.._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0dl0kgt._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0gmhxyo._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0ymn496._.js → [root-of-the-server]__0ils1oq._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ohb3gc._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__10h.ggz._.js → [root-of-the-server]__0tt8-uq._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ymlddl._.js +28 -4
- package/.next/standalone/.next/server/chunks/ssr/_10lm7or._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_0xerkr6._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_0q-m0y-._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_0mebn66._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/pages/404.html +2 -2
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +9 -9
- package/.next/standalone/.next/static/chunks/{0agmlhk5ml7x5.js → 0-dltnti0ew4y.js} +1 -1
- package/.next/standalone/.next/static/chunks/{14lmf8boay-zu.js → 02h_cfxpk4x9..js} +1 -1
- package/.next/standalone/.next/static/chunks/{0s6nux54y~l~r.js → 0a_w-lcg.0tl7.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1400rtd5ywbt..js → 0lscbfo_2_h14.js} +2 -2
- package/.next/standalone/.next/static/chunks/0xr8w5io1-kb9.css +1 -0
- package/.next/standalone/.next/static/chunks/{0tpse0wu2wwo0.js → 0y8oyen~jnjl3.js} +1 -1
- package/.next/standalone/.next/static/chunks/{00ay03h8bq4b~.js → 0yzl~f-qji0...js} +1 -1
- package/.next/standalone/.next/static/chunks/{0en4v5k2nnxks.js → 16k5y9ruha2v4.js} +1 -1
- package/.next/standalone/.next/static/chunks/{17htukxga7bil.js → 17o7-pn_xzt-t.js} +1 -1
- package/.next/standalone/app/components/raw-log-viewer.tsx +25 -5
- package/.next/standalone/package.json +2 -2
- package/.next/standalone/pi-extension/index.ts +21 -4
- package/.next/standalone/server.js +1 -1
- package/dist/cli.mjs +203 -16
- package/package.json +2 -2
- package/pi-extension/index.ts +21 -4
- package/scripts/install-diagnosis.mjs +190 -0
- package/scripts/launch.ts +32 -0
- package/scripts/postinstall.mjs +25 -0
- package/src/hooks/handler.ts +24 -11
- package/src/hooks/integrations.ts +27 -3
- package/src/hooks/types.ts +73 -2
- package/.next/standalone/.next/static/chunks/12po2vpc-4_c1.css +0 -1
- /package/.next/standalone/.next/static/{68TLSFdjAQYIulNHfP0QY → EBbXdrT2rHib3zGLSuY7Q}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{68TLSFdjAQYIulNHfP0QY → EBbXdrT2rHib3zGLSuY7Q}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{68TLSFdjAQYIulNHfP0QY → EBbXdrT2rHib3zGLSuY7Q}/_ssgManifest.js +0 -0
package/dist/cli.mjs
CHANGED
|
@@ -16,7 +16,7 @@ var __export = (target, all) => {
|
|
|
16
16
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
17
17
|
|
|
18
18
|
// src/hooks/types.ts
|
|
19
|
-
var HOOK_SCOPES, INTEGRATION_TYPES, CODEX_HOOK_SCOPES, CODEX_HOOK_EVENT_TYPES, CODEX_EVENT_MAP, COPILOT_HOOK_SCOPES, COPILOT_HOOK_EVENT_TYPES, CURSOR_HOOK_SCOPES, CURSOR_HOOK_EVENT_TYPES, CURSOR_EVENT_MAP, OPENCODE_HOOK_SCOPES, OPENCODE_HOOK_EVENT_TYPES, PI_HOOK_SCOPES, PI_HOOK_EVENT_TYPES, PI_EVENT_MAP, GEMINI_HOOK_SCOPES, GEMINI_HOOK_EVENT_TYPES, GEMINI_EVENT_MAP, GEMINI_TOOL_MAP, HOOK_EVENT_TYPES, FAILPROOFAI_HOOK_MARKER = "__failproofai_hook__";
|
|
19
|
+
var HOOK_SCOPES, INTEGRATION_TYPES, CODEX_HOOK_SCOPES, CODEX_HOOK_EVENT_TYPES, CODEX_EVENT_MAP, COPILOT_HOOK_SCOPES, COPILOT_HOOK_EVENT_TYPES, COPILOT_TOOL_MAP, CURSOR_HOOK_SCOPES, CURSOR_HOOK_EVENT_TYPES, CURSOR_EVENT_MAP, OPENCODE_HOOK_SCOPES, OPENCODE_HOOK_EVENT_TYPES, PI_HOOK_SCOPES, PI_HOOK_EVENT_TYPES, PI_EVENT_MAP, GEMINI_HOOK_SCOPES, GEMINI_HOOK_EVENT_TYPES, GEMINI_EVENT_MAP, GEMINI_TOOL_MAP, HOOK_EVENT_TYPES, FAILPROOFAI_HOOK_MARKER = "__failproofai_hook__";
|
|
20
20
|
var init_types = __esm(() => {
|
|
21
21
|
HOOK_SCOPES = ["user", "project", "local"];
|
|
22
22
|
INTEGRATION_TYPES = ["claude", "codex", "copilot", "cursor", "opencode", "pi", "gemini"];
|
|
@@ -46,6 +46,16 @@ var init_types = __esm(() => {
|
|
|
46
46
|
"PostToolUse",
|
|
47
47
|
"Stop"
|
|
48
48
|
];
|
|
49
|
+
COPILOT_TOOL_MAP = {
|
|
50
|
+
bash: "Bash",
|
|
51
|
+
read: "Read",
|
|
52
|
+
write: "Write",
|
|
53
|
+
edit: "Edit",
|
|
54
|
+
str_replace_editor: "Edit",
|
|
55
|
+
glob: "Glob",
|
|
56
|
+
grep: "Grep",
|
|
57
|
+
ls: "LS"
|
|
58
|
+
};
|
|
49
59
|
CURSOR_HOOK_SCOPES = ["user", "project"];
|
|
50
60
|
CURSOR_HOOK_EVENT_TYPES = [
|
|
51
61
|
"sessionStart",
|
|
@@ -2760,7 +2770,7 @@ var init_hook_activity_store = __esm(() => {
|
|
|
2760
2770
|
});
|
|
2761
2771
|
|
|
2762
2772
|
// package.json
|
|
2763
|
-
var version2 = "0.0.10-beta.
|
|
2773
|
+
var version2 = "0.0.10-beta.2";
|
|
2764
2774
|
var init_package = () => {};
|
|
2765
2775
|
|
|
2766
2776
|
// src/posthog-key.ts
|
|
@@ -5347,6 +5357,9 @@ function canonicalizeEventType(raw, cli) {
|
|
|
5347
5357
|
function canonicalizeToolName(raw, cli) {
|
|
5348
5358
|
if (!raw)
|
|
5349
5359
|
return raw;
|
|
5360
|
+
if (cli === "copilot") {
|
|
5361
|
+
return COPILOT_TOOL_MAP[raw] ?? raw;
|
|
5362
|
+
}
|
|
5350
5363
|
if (cli === "gemini") {
|
|
5351
5364
|
return GEMINI_TOOL_MAP[raw] ?? raw;
|
|
5352
5365
|
}
|
|
@@ -5881,6 +5894,30 @@ const BUS_EVENT_MAP = {
|
|
|
5881
5894
|
// message.updated is handled separately (filter to role:user); see below.
|
|
5882
5895
|
};
|
|
5883
5896
|
|
|
5897
|
+
// Map opencode lowercase tool IDs (\`input.tool\`) → Claude PascalCase canonical
|
|
5898
|
+
// names. Builtin failproofai policies match on PascalCase via case-sensitive
|
|
5899
|
+
// \`Array.includes\`, so without this every Bash/Read/Write/Edit builtin
|
|
5900
|
+
// silently no-ops under opencode. Keep in sync with OPENCODE_TOOL_MAP in
|
|
5901
|
+
// failproofai/src/hooks/types.ts (this shim is loaded in-process by opencode
|
|
5902
|
+
// and must be self-contained — no imports from the failproofai package).
|
|
5903
|
+
// Unknown tools pass through unchanged via \`?? raw\`.
|
|
5904
|
+
const TOOL_NAME_MAP = {
|
|
5905
|
+
bash: "Bash",
|
|
5906
|
+
read: "Read",
|
|
5907
|
+
write: "Write",
|
|
5908
|
+
edit: "Edit",
|
|
5909
|
+
glob: "Glob",
|
|
5910
|
+
grep: "Grep",
|
|
5911
|
+
list: "LS",
|
|
5912
|
+
webfetch: "WebFetch",
|
|
5913
|
+
todowrite: "TodoWrite",
|
|
5914
|
+
todoread: "TodoRead",
|
|
5915
|
+
};
|
|
5916
|
+
function canonicalizeTool(raw) {
|
|
5917
|
+
if (!raw) return raw;
|
|
5918
|
+
return TOOL_NAME_MAP[raw] != null ? TOOL_NAME_MAP[raw] : raw;
|
|
5919
|
+
}
|
|
5920
|
+
|
|
5884
5921
|
const FAILPROOFAI_BIN = ${escapedBin};
|
|
5885
5922
|
const USE_NPX = ${useNpx};
|
|
5886
5923
|
|
|
@@ -5974,7 +6011,7 @@ export default async function failproofaiPlugin({ client, directory }) {
|
|
|
5974
6011
|
const r = runFailproofai("PreToolUse", {
|
|
5975
6012
|
session_id: input.sessionID,
|
|
5976
6013
|
cwd: directory,
|
|
5977
|
-
tool_name: input.tool,
|
|
6014
|
+
tool_name: canonicalizeTool(input.tool),
|
|
5978
6015
|
tool_input: output.args,
|
|
5979
6016
|
hook_event_name: "PreToolUse",
|
|
5980
6017
|
}, directory);
|
|
@@ -5986,7 +6023,7 @@ export default async function failproofaiPlugin({ client, directory }) {
|
|
|
5986
6023
|
const r = runFailproofai("PostToolUse", {
|
|
5987
6024
|
session_id: input.sessionID,
|
|
5988
6025
|
cwd: directory,
|
|
5989
|
-
tool_name: input.tool,
|
|
6026
|
+
tool_name: canonicalizeTool(input.tool),
|
|
5990
6027
|
tool_input: input.args,
|
|
5991
6028
|
tool_response: { title: output.title, output: output.output, metadata: output.metadata },
|
|
5992
6029
|
hook_event_name: "PostToolUse",
|
|
@@ -5999,7 +6036,7 @@ export default async function failproofaiPlugin({ client, directory }) {
|
|
|
5999
6036
|
const r = runFailproofai("PermissionRequest", {
|
|
6000
6037
|
session_id: input.sessionID,
|
|
6001
6038
|
cwd: directory,
|
|
6002
|
-
tool_name: input.tool || input.command || "permission",
|
|
6039
|
+
tool_name: canonicalizeTool(input.tool) || input.command || "permission",
|
|
6003
6040
|
tool_input: input,
|
|
6004
6041
|
hook_event_name: "PermissionRequest",
|
|
6005
6042
|
}, directory);
|
|
@@ -8194,14 +8231,146 @@ function parseScriptArgs(argv) {
|
|
|
8194
8231
|
}
|
|
8195
8232
|
var init_parse_script_args = () => {};
|
|
8196
8233
|
|
|
8234
|
+
// scripts/install-diagnosis.mjs
|
|
8235
|
+
import { existsSync as existsSync14, readFileSync as readFileSync10, realpathSync } from "node:fs";
|
|
8236
|
+
import { dirname as dirname7, resolve as resolve9 } from "node:path";
|
|
8237
|
+
import { homedir as homedir21, platform as platform3 } from "node:os";
|
|
8238
|
+
import { spawnSync } from "node:child_process";
|
|
8239
|
+
function findPackageRoot(start) {
|
|
8240
|
+
try {
|
|
8241
|
+
let dir = realpathSync(start);
|
|
8242
|
+
if (existsSync14(dir) && !existsSync14(resolve9(dir, "package.json"))) {
|
|
8243
|
+
dir = dirname7(dir);
|
|
8244
|
+
}
|
|
8245
|
+
while (true) {
|
|
8246
|
+
const pkgPath = resolve9(dir, "package.json");
|
|
8247
|
+
if (existsSync14(pkgPath)) {
|
|
8248
|
+
try {
|
|
8249
|
+
const pkg = JSON.parse(readFileSync10(pkgPath, "utf8"));
|
|
8250
|
+
if (pkg.name === PKG_NAME)
|
|
8251
|
+
return dir;
|
|
8252
|
+
} catch {}
|
|
8253
|
+
}
|
|
8254
|
+
const parent = dirname7(dir);
|
|
8255
|
+
if (parent === dir)
|
|
8256
|
+
return null;
|
|
8257
|
+
dir = parent;
|
|
8258
|
+
}
|
|
8259
|
+
} catch {
|
|
8260
|
+
return null;
|
|
8261
|
+
}
|
|
8262
|
+
}
|
|
8263
|
+
function readPackageVersion(packageRoot) {
|
|
8264
|
+
if (!packageRoot)
|
|
8265
|
+
return null;
|
|
8266
|
+
try {
|
|
8267
|
+
const pkg = JSON.parse(readFileSync10(resolve9(packageRoot, "package.json"), "utf8"));
|
|
8268
|
+
return typeof pkg.version === "string" ? pkg.version : null;
|
|
8269
|
+
} catch {
|
|
8270
|
+
return null;
|
|
8271
|
+
}
|
|
8272
|
+
}
|
|
8273
|
+
function resolvePathFirstBinary() {
|
|
8274
|
+
try {
|
|
8275
|
+
const isWin = platform3() === "win32";
|
|
8276
|
+
const res = isWin ? spawnSync("where", [PKG_NAME], { encoding: "utf8" }) : spawnSync("sh", ["-c", `command -v ${PKG_NAME}`], { encoding: "utf8" });
|
|
8277
|
+
if (res.status !== 0)
|
|
8278
|
+
return null;
|
|
8279
|
+
const first = (res.stdout || "").split(/\r?\n/).find((l) => l.trim().length > 0);
|
|
8280
|
+
return first ? first.trim() : null;
|
|
8281
|
+
} catch {
|
|
8282
|
+
return null;
|
|
8283
|
+
}
|
|
8284
|
+
}
|
|
8285
|
+
function locateNpmGlobal() {
|
|
8286
|
+
try {
|
|
8287
|
+
const res = spawnSync("npm", ["root", "-g"], { encoding: "utf8" });
|
|
8288
|
+
if (res.status !== 0)
|
|
8289
|
+
return null;
|
|
8290
|
+
const root = (res.stdout || "").trim();
|
|
8291
|
+
if (!root)
|
|
8292
|
+
return null;
|
|
8293
|
+
const candidate = resolve9(root, PKG_NAME);
|
|
8294
|
+
return existsSync14(resolve9(candidate, "package.json")) ? candidate : null;
|
|
8295
|
+
} catch {
|
|
8296
|
+
return null;
|
|
8297
|
+
}
|
|
8298
|
+
}
|
|
8299
|
+
function locateBunGlobal() {
|
|
8300
|
+
try {
|
|
8301
|
+
const candidate = resolve9(homedir21(), ".bun", "install", "global", "node_modules", PKG_NAME);
|
|
8302
|
+
return existsSync14(resolve9(candidate, "package.json")) ? candidate : null;
|
|
8303
|
+
} catch {
|
|
8304
|
+
return null;
|
|
8305
|
+
}
|
|
8306
|
+
}
|
|
8307
|
+
function buildRecommendation(pathFirstBin) {
|
|
8308
|
+
if (!pathFirstBin)
|
|
8309
|
+
return null;
|
|
8310
|
+
const bunBinPrefix = resolve9(homedir21(), ".bun", "bin") + "/";
|
|
8311
|
+
const bunGlobalPrefix = resolve9(homedir21(), ".bun", "install", "global") + "/";
|
|
8312
|
+
const isBun = pathFirstBin.startsWith(bunBinPrefix) || pathFirstBin.startsWith(bunGlobalPrefix);
|
|
8313
|
+
if (isBun) {
|
|
8314
|
+
return `rm -f ~/.bun/bin/${PKG_NAME} && rm -rf ~/.bun/install/global/node_modules/${PKG_NAME}`;
|
|
8315
|
+
}
|
|
8316
|
+
return `npm rm -g ${PKG_NAME}`;
|
|
8317
|
+
}
|
|
8318
|
+
function diagnoseShadow(self) {
|
|
8319
|
+
const selfPackageRoot = (() => {
|
|
8320
|
+
try {
|
|
8321
|
+
return self?.selfPackageRoot ? realpathSync(self.selfPackageRoot) : null;
|
|
8322
|
+
} catch {
|
|
8323
|
+
return self?.selfPackageRoot ?? null;
|
|
8324
|
+
}
|
|
8325
|
+
})();
|
|
8326
|
+
const selfVersion = self?.selfVersion ?? null;
|
|
8327
|
+
const pathFirstBin = resolvePathFirstBinary();
|
|
8328
|
+
const pathFirstPackageRoot = pathFirstBin ? findPackageRoot(pathFirstBin) : null;
|
|
8329
|
+
const pathFirstVersion = readPackageVersion(pathFirstPackageRoot);
|
|
8330
|
+
const npmGlobalPath = locateNpmGlobal();
|
|
8331
|
+
const npmGlobalVersion = readPackageVersion(npmGlobalPath);
|
|
8332
|
+
const bunGlobalPath = locateBunGlobal();
|
|
8333
|
+
const bunGlobalVersion = readPackageVersion(bunGlobalPath);
|
|
8334
|
+
let shadowed = false;
|
|
8335
|
+
if (selfPackageRoot && pathFirstPackageRoot && pathFirstPackageRoot !== selfPackageRoot) {
|
|
8336
|
+
shadowed = true;
|
|
8337
|
+
} else if (pathFirstPackageRoot) {
|
|
8338
|
+
if (npmGlobalPath && npmGlobalPath !== pathFirstPackageRoot)
|
|
8339
|
+
shadowed = true;
|
|
8340
|
+
else if (bunGlobalPath && bunGlobalPath !== pathFirstPackageRoot)
|
|
8341
|
+
shadowed = true;
|
|
8342
|
+
}
|
|
8343
|
+
const recommendation = shadowed ? buildRecommendation(pathFirstBin) : null;
|
|
8344
|
+
let shadowDescription = null;
|
|
8345
|
+
if (shadowed) {
|
|
8346
|
+
shadowDescription = `PATH resolves to ${pathFirstPackageRoot}` + (pathFirstVersion ? ` (v${pathFirstVersion})` : "") + `, but you just installed ${selfPackageRoot}` + (selfVersion ? ` (v${selfVersion})` : "") + ".";
|
|
8347
|
+
}
|
|
8348
|
+
return {
|
|
8349
|
+
selfPackageRoot,
|
|
8350
|
+
selfVersion,
|
|
8351
|
+
pathFirstBin,
|
|
8352
|
+
pathFirstPath: pathFirstPackageRoot,
|
|
8353
|
+
pathFirstVersion,
|
|
8354
|
+
npmGlobalPath,
|
|
8355
|
+
npmGlobalVersion,
|
|
8356
|
+
bunGlobalPath,
|
|
8357
|
+
bunGlobalVersion,
|
|
8358
|
+
shadowed,
|
|
8359
|
+
shadowDescription,
|
|
8360
|
+
recommendation
|
|
8361
|
+
};
|
|
8362
|
+
}
|
|
8363
|
+
var PKG_NAME = "failproofai";
|
|
8364
|
+
var init_install_diagnosis = () => {};
|
|
8365
|
+
|
|
8197
8366
|
// scripts/launch.ts
|
|
8198
8367
|
var exports_launch = {};
|
|
8199
8368
|
__export(exports_launch, {
|
|
8200
8369
|
launch: () => launch
|
|
8201
8370
|
});
|
|
8202
8371
|
import { spawn as spawn4 } from "child_process";
|
|
8203
|
-
import { realpathSync, existsSync as
|
|
8204
|
-
import { resolve as
|
|
8372
|
+
import { realpathSync as realpathSync2, existsSync as existsSync15 } from "node:fs";
|
|
8373
|
+
import { resolve as resolve10, dirname as dirname8 } from "node:path";
|
|
8205
8374
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
8206
8375
|
function launch(mode) {
|
|
8207
8376
|
const { claudeProjectsPath: parsedPath, loggingLevel, disableTelemetry, allowedDevOrigins, remainingArgs } = parseScriptArgs(process.argv.slice(2));
|
|
@@ -8233,10 +8402,27 @@ function launch(mode) {
|
|
|
8233
8402
|
process.env.PORT = port;
|
|
8234
8403
|
process.env.HOSTNAME = "0.0.0.0";
|
|
8235
8404
|
cmd = "node";
|
|
8236
|
-
const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT ??
|
|
8237
|
-
const serverJsPath =
|
|
8238
|
-
if (!
|
|
8239
|
-
|
|
8405
|
+
const packageRoot = process.env.FAILPROOFAI_PACKAGE_ROOT ?? resolve10(dirname8(realpathSync2(fileURLToPath2(import.meta.url))), "..");
|
|
8406
|
+
const serverJsPath = resolve10(packageRoot, ".next/standalone/server.js");
|
|
8407
|
+
if (!existsSync15(serverJsPath)) {
|
|
8408
|
+
let shadowMessage = null;
|
|
8409
|
+
try {
|
|
8410
|
+
const diag = diagnoseShadow({ selfPackageRoot: packageRoot, selfVersion: version2 });
|
|
8411
|
+
if (diag.shadowed) {
|
|
8412
|
+
const alt = (diag.npmGlobalPath && diag.npmGlobalPath !== diag.pathFirstPath ? { path: diag.npmGlobalPath, version: diag.npmGlobalVersion } : null) ?? (diag.bunGlobalPath && diag.bunGlobalPath !== diag.pathFirstPath ? { path: diag.bunGlobalPath, version: diag.bunGlobalVersion } : null);
|
|
8413
|
+
const newer = alt?.path ?? "(unknown)";
|
|
8414
|
+
const newerVer = alt?.version ?? "?";
|
|
8415
|
+
shadowMessage = `
|
|
8416
|
+
Error: failproofai on your PATH is a stale install that no longer has its build output.
|
|
8417
|
+
` + ` Running: ${diag.pathFirstPath}` + (diag.pathFirstVersion ? ` (v${diag.pathFirstVersion})` : "") + `
|
|
8418
|
+
` + ` Newer copy: ${newer} (v${newerVer})
|
|
8419
|
+
|
|
8420
|
+
` + `Remove the shadow with:
|
|
8421
|
+
${diag.recommendation}
|
|
8422
|
+
`;
|
|
8423
|
+
}
|
|
8424
|
+
} catch {}
|
|
8425
|
+
console.error(shadowMessage ?? `
|
|
8240
8426
|
Error: Cannot find server.js at:
|
|
8241
8427
|
${serverJsPath}
|
|
8242
8428
|
|
|
@@ -8272,6 +8458,7 @@ Error: Cannot find server.js at:
|
|
|
8272
8458
|
var init_launch = __esm(() => {
|
|
8273
8459
|
init_paths();
|
|
8274
8460
|
init_parse_script_args();
|
|
8461
|
+
init_install_diagnosis();
|
|
8275
8462
|
init_package();
|
|
8276
8463
|
});
|
|
8277
8464
|
|
|
@@ -8293,18 +8480,18 @@ var init_cli_error2 = __esm(() => {
|
|
|
8293
8480
|
});
|
|
8294
8481
|
|
|
8295
8482
|
// bin/failproofai.mjs
|
|
8296
|
-
import { realpathSync as
|
|
8297
|
-
import { dirname as
|
|
8483
|
+
import { realpathSync as realpathSync3 } from "fs";
|
|
8484
|
+
import { dirname as dirname9, resolve as resolve11 } from "path";
|
|
8298
8485
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
8299
8486
|
// package.json
|
|
8300
|
-
var version = "0.0.10-beta.
|
|
8487
|
+
var version = "0.0.10-beta.2";
|
|
8301
8488
|
|
|
8302
8489
|
// bin/failproofai.mjs
|
|
8303
8490
|
if (!process.env.FAILPROOFAI_PACKAGE_ROOT) {
|
|
8304
|
-
process.env.FAILPROOFAI_PACKAGE_ROOT =
|
|
8491
|
+
process.env.FAILPROOFAI_PACKAGE_ROOT = resolve11(dirname9(realpathSync3(fileURLToPath3(import.meta.url))), "..");
|
|
8305
8492
|
}
|
|
8306
8493
|
if (!process.env.FAILPROOFAI_DIST_PATH) {
|
|
8307
|
-
process.env.FAILPROOFAI_DIST_PATH =
|
|
8494
|
+
process.env.FAILPROOFAI_DIST_PATH = resolve11(dirname9(realpathSync3(fileURLToPath3(import.meta.url))), "..", "dist");
|
|
8308
8495
|
}
|
|
8309
8496
|
var args = process.argv.slice(2);
|
|
8310
8497
|
if (args[0] === "p")
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "failproofai",
|
|
3
|
-
"version": "0.0.10-beta.
|
|
3
|
+
"version": "0.0.10-beta.2",
|
|
4
4
|
"description": "The easiest way to manage policies that keep your AI agents reliable, on-task, and running autonomously — for Claude Code & the Agents SDK",
|
|
5
5
|
"bin": {
|
|
6
6
|
"failproofai": "./dist/cli.mjs"
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
"tailwind-merge": "^3.4.0",
|
|
92
92
|
"tailwindcss": "^4.1.18",
|
|
93
93
|
"typescript": "^6.0.2",
|
|
94
|
-
"@anthropic-ai/sdk": "^0.
|
|
94
|
+
"@anthropic-ai/sdk": "^0.93.0",
|
|
95
95
|
"vitest": "^4.0.18"
|
|
96
96
|
},
|
|
97
97
|
"dependencies": {
|
package/pi-extension/index.ts
CHANGED
|
@@ -102,14 +102,31 @@ interface PiToolCallEvent {
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
/**
|
|
105
|
-
* Pi emits tool names in lowercase (`bash`, `read`, `edit`, `write`
|
|
106
|
-
* failproofai's builtin policies match on Claude
|
|
107
|
-
* (`Bash`, `Read`, `Edit`, `Write`)
|
|
105
|
+
* Pi emits tool names in lowercase (`bash`, `read`, `edit`, `write`, `glob`,
|
|
106
|
+
* `grep`). failproofai's builtin policies match on Claude PascalCase
|
|
107
|
+
* (`Bash`, `Read`, `Edit`, `Write`, `Glob`, `Grep`) via case-sensitive
|
|
108
|
+
* `Array.includes` in policy-registry.ts. Map between the two so existing
|
|
108
109
|
* tool-name match clauses fire on Pi sessions.
|
|
110
|
+
*
|
|
111
|
+
* Keys must stay in sync with PI_TOOL_MAP in src/hooks/types.ts (this shim is
|
|
112
|
+
* loaded in-process by Pi and must be self-contained — no imports from the
|
|
113
|
+
* failproofai package). Update both whenever Pi adds a tool ID we care about.
|
|
114
|
+
*
|
|
115
|
+
* Unknown tools (anything not in this map) pass through unchanged so custom
|
|
116
|
+
* policies that match on raw Pi tool IDs still work.
|
|
109
117
|
*/
|
|
118
|
+
const PI_TOOL_MAP: Record<string, string> = {
|
|
119
|
+
bash: "Bash",
|
|
120
|
+
read: "Read",
|
|
121
|
+
write: "Write",
|
|
122
|
+
edit: "Edit",
|
|
123
|
+
glob: "Glob",
|
|
124
|
+
grep: "Grep",
|
|
125
|
+
};
|
|
126
|
+
|
|
110
127
|
function canonicalizeToolName(piToolName: string | undefined): string | undefined {
|
|
111
128
|
if (!piToolName) return undefined;
|
|
112
|
-
return piToolName
|
|
129
|
+
return PI_TOOL_MAP[piToolName] ?? piToolName;
|
|
113
130
|
}
|
|
114
131
|
|
|
115
132
|
/** Resolve the cwd for the policy payload. Pi events don't include cwd, so
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detects when `failproofai` on the user's PATH is shadowed by a different,
|
|
3
|
+
* older install — typically a leftover `bun link` from a prior dev session, or
|
|
4
|
+
* a `bun install -g failproofai` whose prefix sorts ahead of npm's on PATH.
|
|
5
|
+
*
|
|
6
|
+
* Used by:
|
|
7
|
+
* - scripts/postinstall.mjs — warn at install time so the customer never sees
|
|
8
|
+
* the misleading "missing build output" runtime error.
|
|
9
|
+
* - scripts/launch.ts — when .next/standalone/server.js is missing,
|
|
10
|
+
* produce a shadow-shaped error if the cause is a shadow rather than a
|
|
11
|
+
* genuinely broken build.
|
|
12
|
+
*
|
|
13
|
+
* Pure Node.js built-ins, no external dependencies. Every probe is wrapped in
|
|
14
|
+
* try/catch — diagnoseShadow() is guaranteed not to throw.
|
|
15
|
+
*/
|
|
16
|
+
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
17
|
+
import { dirname, resolve } from "node:path";
|
|
18
|
+
import { homedir, platform } from "node:os";
|
|
19
|
+
import { spawnSync } from "node:child_process";
|
|
20
|
+
|
|
21
|
+
const PKG_NAME = "failproofai";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Walk up from `start` looking for a package.json whose name === "failproofai".
|
|
25
|
+
* Returns its directory, or null when no such package.json is reachable.
|
|
26
|
+
*/
|
|
27
|
+
function findPackageRoot(start) {
|
|
28
|
+
try {
|
|
29
|
+
let dir = realpathSync(start);
|
|
30
|
+
// If `start` was a file (e.g. /usr/local/bin/failproofai), step up to its dir.
|
|
31
|
+
if (existsSync(dir) && !existsSync(resolve(dir, "package.json"))) {
|
|
32
|
+
dir = dirname(dir);
|
|
33
|
+
}
|
|
34
|
+
while (true) {
|
|
35
|
+
const pkgPath = resolve(dir, "package.json");
|
|
36
|
+
if (existsSync(pkgPath)) {
|
|
37
|
+
try {
|
|
38
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
39
|
+
if (pkg.name === PKG_NAME) return dir;
|
|
40
|
+
} catch {
|
|
41
|
+
// unreadable or non-JSON — fall through to parent
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const parent = dirname(dir);
|
|
45
|
+
if (parent === dir) return null;
|
|
46
|
+
dir = parent;
|
|
47
|
+
}
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Read `version` from a package.json; null on any error. */
|
|
54
|
+
function readPackageVersion(packageRoot) {
|
|
55
|
+
if (!packageRoot) return null;
|
|
56
|
+
try {
|
|
57
|
+
const pkg = JSON.parse(readFileSync(resolve(packageRoot, "package.json"), "utf8"));
|
|
58
|
+
return typeof pkg.version === "string" ? pkg.version : null;
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Find which `failproofai` PATH would resolve. POSIX: `command -v`; Win32: `where`. */
|
|
65
|
+
function resolvePathFirstBinary() {
|
|
66
|
+
try {
|
|
67
|
+
const isWin = platform() === "win32";
|
|
68
|
+
const res = isWin
|
|
69
|
+
? spawnSync("where", [PKG_NAME], { encoding: "utf8" })
|
|
70
|
+
: spawnSync("sh", ["-c", `command -v ${PKG_NAME}`], { encoding: "utf8" });
|
|
71
|
+
if (res.status !== 0) return null;
|
|
72
|
+
const first = (res.stdout || "").split(/\r?\n/).find((l) => l.trim().length > 0);
|
|
73
|
+
return first ? first.trim() : null;
|
|
74
|
+
} catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Locate the npm global install of failproofai, if any. */
|
|
80
|
+
function locateNpmGlobal() {
|
|
81
|
+
try {
|
|
82
|
+
const res = spawnSync("npm", ["root", "-g"], { encoding: "utf8" });
|
|
83
|
+
if (res.status !== 0) return null;
|
|
84
|
+
const root = (res.stdout || "").trim();
|
|
85
|
+
if (!root) return null;
|
|
86
|
+
const candidate = resolve(root, PKG_NAME);
|
|
87
|
+
return existsSync(resolve(candidate, "package.json")) ? candidate : null;
|
|
88
|
+
} catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Locate the bun global install of failproofai, if any. */
|
|
94
|
+
function locateBunGlobal() {
|
|
95
|
+
try {
|
|
96
|
+
const candidate = resolve(homedir(), ".bun", "install", "global", "node_modules", PKG_NAME);
|
|
97
|
+
return existsSync(resolve(candidate, "package.json")) ? candidate : null;
|
|
98
|
+
} catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Build a copy-pasteable cleanup command for the offending install.
|
|
105
|
+
*
|
|
106
|
+
* The signal we trust is `pathFirstBin` — the un-resolved binary location PATH
|
|
107
|
+
* pointed to. For bun-link shadows the realpath'd package root is the dev tree
|
|
108
|
+
* (not under ~/.bun/), so checking the package root would mis-classify those
|
|
109
|
+
* shadows as npm and recommend the wrong cleanup.
|
|
110
|
+
*/
|
|
111
|
+
function buildRecommendation(pathFirstBin) {
|
|
112
|
+
if (!pathFirstBin) return null;
|
|
113
|
+
const bunBinPrefix = resolve(homedir(), ".bun", "bin") + "/";
|
|
114
|
+
const bunGlobalPrefix = resolve(homedir(), ".bun", "install", "global") + "/";
|
|
115
|
+
const isBun = pathFirstBin.startsWith(bunBinPrefix) || pathFirstBin.startsWith(bunGlobalPrefix);
|
|
116
|
+
if (isBun) {
|
|
117
|
+
return `rm -f ~/.bun/bin/${PKG_NAME} && rm -rf ~/.bun/install/global/node_modules/${PKG_NAME}`;
|
|
118
|
+
}
|
|
119
|
+
return `npm rm -g ${PKG_NAME}`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Diagnose whether the running binary is being shadowed on PATH by a different
|
|
124
|
+
* failproofai install.
|
|
125
|
+
*
|
|
126
|
+
* @param {{ selfPackageRoot: string, selfVersion: string | null }} self
|
|
127
|
+
* The package root and version of the binary calling diagnoseShadow().
|
|
128
|
+
* Callers (bin/failproofai.mjs, scripts/postinstall.mjs) already have these
|
|
129
|
+
* values; passing them in keeps the helper deterministic and free of
|
|
130
|
+
* import.meta.url assumptions.
|
|
131
|
+
*/
|
|
132
|
+
export function diagnoseShadow(self) {
|
|
133
|
+
const selfPackageRoot = (() => {
|
|
134
|
+
try { return self?.selfPackageRoot ? realpathSync(self.selfPackageRoot) : null; }
|
|
135
|
+
catch { return self?.selfPackageRoot ?? null; }
|
|
136
|
+
})();
|
|
137
|
+
const selfVersion = self?.selfVersion ?? null;
|
|
138
|
+
|
|
139
|
+
const pathFirstBin = resolvePathFirstBinary();
|
|
140
|
+
const pathFirstPackageRoot = pathFirstBin ? findPackageRoot(pathFirstBin) : null;
|
|
141
|
+
const pathFirstVersion = readPackageVersion(pathFirstPackageRoot);
|
|
142
|
+
|
|
143
|
+
const npmGlobalPath = locateNpmGlobal();
|
|
144
|
+
const npmGlobalVersion = readPackageVersion(npmGlobalPath);
|
|
145
|
+
|
|
146
|
+
const bunGlobalPath = locateBunGlobal();
|
|
147
|
+
const bunGlobalVersion = readPackageVersion(bunGlobalPath);
|
|
148
|
+
|
|
149
|
+
// "Shadow" covers two scenarios:
|
|
150
|
+
// 1. Postinstall case — `selfPackageRoot` is the just-installed copy and
|
|
151
|
+
// PATH resolves elsewhere. Flag when the two roots differ.
|
|
152
|
+
// 2. Runtime case — the running binary IS the shadow (so selfPackageRoot
|
|
153
|
+
// === pathFirstPackageRoot), but a *different* failproofai install
|
|
154
|
+
// exists at the npm or bun global. Flag when one of those differs from
|
|
155
|
+
// pathFirstPackageRoot.
|
|
156
|
+
let shadowed = false;
|
|
157
|
+
if (selfPackageRoot && pathFirstPackageRoot && pathFirstPackageRoot !== selfPackageRoot) {
|
|
158
|
+
shadowed = true;
|
|
159
|
+
} else if (pathFirstPackageRoot) {
|
|
160
|
+
if (npmGlobalPath && npmGlobalPath !== pathFirstPackageRoot) shadowed = true;
|
|
161
|
+
else if (bunGlobalPath && bunGlobalPath !== pathFirstPackageRoot) shadowed = true;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const recommendation = shadowed ? buildRecommendation(pathFirstBin) : null;
|
|
165
|
+
|
|
166
|
+
// A short human-readable summary used by callers that want a one-liner.
|
|
167
|
+
let shadowDescription = null;
|
|
168
|
+
if (shadowed) {
|
|
169
|
+
shadowDescription =
|
|
170
|
+
`PATH resolves to ${pathFirstPackageRoot}` +
|
|
171
|
+
(pathFirstVersion ? ` (v${pathFirstVersion})` : "") +
|
|
172
|
+
`, but you just installed ${selfPackageRoot}` +
|
|
173
|
+
(selfVersion ? ` (v${selfVersion})` : "") + ".";
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
selfPackageRoot,
|
|
178
|
+
selfVersion,
|
|
179
|
+
pathFirstBin,
|
|
180
|
+
pathFirstPath: pathFirstPackageRoot,
|
|
181
|
+
pathFirstVersion,
|
|
182
|
+
npmGlobalPath,
|
|
183
|
+
npmGlobalVersion,
|
|
184
|
+
bunGlobalPath,
|
|
185
|
+
bunGlobalVersion,
|
|
186
|
+
shadowed,
|
|
187
|
+
shadowDescription,
|
|
188
|
+
recommendation,
|
|
189
|
+
};
|
|
190
|
+
}
|
package/scripts/launch.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { realpathSync, existsSync } from "node:fs";
|
|
|
7
7
|
import { resolve, dirname } from "node:path";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
9
|
import { parseScriptArgs } from "./parse-script-args";
|
|
10
|
+
import { diagnoseShadow } from "./install-diagnosis.mjs";
|
|
10
11
|
import { version } from "../package.json";
|
|
11
12
|
|
|
12
13
|
export function launch(mode: "dev" | "start"): void {
|
|
@@ -49,7 +50,38 @@ export function launch(mode: "dev" | "start"): void {
|
|
|
49
50
|
?? resolve(dirname(realpathSync(fileURLToPath(import.meta.url))), "..");
|
|
50
51
|
const serverJsPath = resolve(packageRoot, ".next/standalone/server.js");
|
|
51
52
|
if (!existsSync(serverJsPath)) {
|
|
53
|
+
// Most "missing server.js" reports come from a PATH shadow (an older
|
|
54
|
+
// `bun link` or a `bun install -g` whose prefix wins over npm), not from
|
|
55
|
+
// a genuinely broken build. Diagnose first so the error message names
|
|
56
|
+
// the actual cause when that's what's going on.
|
|
57
|
+
let shadowMessage: string | null = null;
|
|
58
|
+
try {
|
|
59
|
+
const diag = diagnoseShadow({ selfPackageRoot: packageRoot, selfVersion: version });
|
|
60
|
+
if (diag.shadowed) {
|
|
61
|
+
// Pick whichever alternate install exists at npm/bun globals AND
|
|
62
|
+
// differs from PATH-first. In the runtime stale-binary scenario the
|
|
63
|
+
// running install IS the PATH-first one, so we'd otherwise point the
|
|
64
|
+
// user back at themselves.
|
|
65
|
+
const alt =
|
|
66
|
+
(diag.npmGlobalPath && diag.npmGlobalPath !== diag.pathFirstPath
|
|
67
|
+
? { path: diag.npmGlobalPath, version: diag.npmGlobalVersion }
|
|
68
|
+
: null)
|
|
69
|
+
?? (diag.bunGlobalPath && diag.bunGlobalPath !== diag.pathFirstPath
|
|
70
|
+
? { path: diag.bunGlobalPath, version: diag.bunGlobalVersion }
|
|
71
|
+
: null);
|
|
72
|
+
const newer = alt?.path ?? "(unknown)";
|
|
73
|
+
const newerVer = alt?.version ?? "?";
|
|
74
|
+
shadowMessage =
|
|
75
|
+
`\nError: failproofai on your PATH is a stale install that no longer has its build output.\n` +
|
|
76
|
+
` Running: ${diag.pathFirstPath}` + (diag.pathFirstVersion ? ` (v${diag.pathFirstVersion})` : "") + `\n` +
|
|
77
|
+
` Newer copy: ${newer} (v${newerVer})\n\n` +
|
|
78
|
+
`Remove the shadow with:\n ${diag.recommendation}\n`;
|
|
79
|
+
}
|
|
80
|
+
} catch {
|
|
81
|
+
// Diagnosis is best-effort; fall back to the original message.
|
|
82
|
+
}
|
|
52
83
|
console.error(
|
|
84
|
+
shadowMessage ??
|
|
53
85
|
`\nError: Cannot find server.js at:\n ${serverJsPath}\n\n` +
|
|
54
86
|
`The package may be missing its build output.\n` +
|
|
55
87
|
`Try reinstalling:\n npm install -g failproofai@latest\n`
|
package/scripts/postinstall.mjs
CHANGED
|
@@ -12,6 +12,7 @@ import { resolve } from "node:path";
|
|
|
12
12
|
import { platform, arch, release, homedir, hostname } from "node:os";
|
|
13
13
|
import { createHmac } from "node:crypto";
|
|
14
14
|
import { trackInstallEvent } from "./install-telemetry.mjs";
|
|
15
|
+
import { diagnoseShadow } from "./install-diagnosis.mjs";
|
|
15
16
|
|
|
16
17
|
// Skip when running in development context (e.g. `bun install` in the source repo).
|
|
17
18
|
// INIT_CWD is set by npm/bun to the directory where install was invoked; it differs
|
|
@@ -29,6 +30,30 @@ if (!existsSync(serverJsPath)) {
|
|
|
29
30
|
process.exit(1);
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
// Detect when an older `failproofai` is shadowing this fresh install on PATH —
|
|
34
|
+
// classic case is a leftover `bun link` from a prior dev session, or a
|
|
35
|
+
// `bun install -g` whose ~/.bun/bin sorts ahead of npm's prefix. Without this
|
|
36
|
+
// warning the user only finds out later via a confusing runtime error from
|
|
37
|
+
// scripts/launch.ts pointing at the *old* install's missing build output.
|
|
38
|
+
try {
|
|
39
|
+
let selfVersion = null;
|
|
40
|
+
try {
|
|
41
|
+
selfVersion = JSON.parse(readFileSync(resolve(process.cwd(), "package.json"), "utf8")).version ?? null;
|
|
42
|
+
} catch {}
|
|
43
|
+
const diag = diagnoseShadow({ selfPackageRoot: process.cwd(), selfVersion });
|
|
44
|
+
if (diag.shadowed) {
|
|
45
|
+
console.warn(
|
|
46
|
+
`\n[failproofai] Warning: another failproofai install is earlier on your PATH.\n` +
|
|
47
|
+
` Just installed: ${diag.selfPackageRoot}` + (diag.selfVersion ? ` (v${diag.selfVersion})` : "") + `\n` +
|
|
48
|
+
` PATH resolves : ${diag.pathFirstPath}` + (diag.pathFirstVersion ? ` (v${diag.pathFirstVersion})` : "") + `\n\n` +
|
|
49
|
+
` Your shell will run the older copy. Remove the shadow with:\n` +
|
|
50
|
+
` ${diag.recommendation}\n`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
} catch {
|
|
54
|
+
// Diagnosis is best-effort — never fail the install over a warning.
|
|
55
|
+
}
|
|
56
|
+
|
|
32
57
|
const FAILPROOFAI_HOOK_MARKER = "__failproofai_hook__";
|
|
33
58
|
const NAMESPACE = "failproofai-telemetry-v1";
|
|
34
59
|
|