failproofai 1.0.2-beta.8 → 1.0.2-beta.9
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 +5 -5
- 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/build-manifest.json +2 -2
- 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 +6 -6
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +2 -2
- 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 +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/api/audit/invite/route.js +3 -2
- package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js +4 -3
- package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js +2 -2
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js +4 -3
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/logout/route.js +4 -4
- package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/status/route.js +4 -4
- package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/page/build-manifest.json +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/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
- package/.next/standalone/.next/server/app/policies/page.js +4 -4
- 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/build-manifest.json +2 -2
- 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/build-manifest.json +2 -2
- 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/build-manifest.json +2 -2
- 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/app/settings/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +4 -4
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/[externals]__0-9ti7z._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[externals]__1lt47k4._.js → [externals]__0sg9-3p._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[externals]__0u3wrvq._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__01eqny5._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__1p8poxd._.js → [root-of-the-server]__0igxt32._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zlqo2t._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1anxb5k._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1le9jqc._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1p-qi2t._.js +5 -0
- package/.next/standalone/.next/server/chunks/_09dz7xv._.js +27 -0
- package/.next/standalone/.next/server/chunks/_0bs-v8p._.js +3 -0
- package/.next/standalone/.next/server/chunks/_0tovk6q._.js +3 -0
- package/.next/standalone/.next/server/chunks/_0trp3yc._.js +3 -0
- package/.next/standalone/.next/server/chunks/{_19uwam-._.js → _1-akg6w._.js} +2 -2
- package/.next/standalone/.next/server/chunks/_185gyk9._.js +3 -0
- package/.next/standalone/.next/server/chunks/_1ek68ln._.js +5 -7
- package/.next/standalone/.next/server/chunks/{node_modules_posthog-node_dist_entrypoints_index_node_mjs_09d5c79._.js → node_modules_posthog-node_dist_entrypoints_index_node_mjs_04op_ou._.js} +1 -1
- package/.next/standalone/.next/server/chunks/{node_modules_posthog-node_dist_entrypoints_index_node_mjs_1d2g73o._.js → node_modules_posthog-node_dist_entrypoints_index_node_mjs_0dr20uz._.js} +1 -1
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/src_hooks_0iu54mz._.js +3 -0
- package/.next/standalone/.next/server/chunks/src_hooks_custom-hooks-loader_ts_0lnb3n3._.js +10 -0
- package/.next/standalone/.next/server/chunks/src_hooks_fp-home_ts_09kv0bn._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013jr2b._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02npjtd._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0da85px._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ftmoxc._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n_lxhg._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0oa1lav._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p-5p8u._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s740oi._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1-wzs7f._.js → [root-of-the-server]__1_7n3xe._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1p2otjt._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_1h33neq._.js → _0b6xubz._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1n2ywfz._.js → _1es2j7i._.js} +4 -4
- package/.next/standalone/.next/server/chunks/ssr/{_0l7zv8b._.js → _1pj-r7q._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +9 -9
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1cv9_c4._.js +10 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +5 -5
- package/.next/standalone/.next/server/middleware.js +1 -1
- package/.next/standalone/.next/server/pages/404.html +1 -1
- 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 +19 -19
- package/.next/standalone/.next/static/chunks/{0i3a6wf-f_yky.js → 06zoac_7ffkus.js} +1 -1
- package/.next/standalone/.next/static/chunks/{11a_m8yksthtf.js → 0bshgqm5waivd.js} +2 -2
- package/.next/standalone/.next/static/chunks/{1ch5l9t2p16y4.js → 13grq7pifzvu5.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3i8rg2fv9nd6e.js → 16pmxmg9x48ob.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2iryw37qfqy5y.js → 1da7-a7ek66uy.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3927pu4ss_l4k.js → 1fg7drro5kle-.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3r0udrtpn78-e.js → 1opyu56l4cdfk.js} +1 -1
- package/.next/standalone/.next/static/chunks/{34j7nmidlcuni.js → 1p-o1i0lyt9un.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0u2ojcw25c_np.js → 2mfojtsy214sf.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1n04wzsaes73g.js → 32jj4qy40hln_.js} +1 -1
- package/.next/standalone/.next/static/chunks/{416zumre3fvfw.js → 3ww5ik9eo_1b_.js} +1 -1
- package/.next/standalone/.next/static/chunks/{turbopack-2x-o099q64i8h.js → turbopack-1gehh2qpjyj5_.js} +1 -1
- package/.next/standalone/app/actions/get-hooks-config.ts +8 -1
- package/.next/standalone/app/actions/pack-actions.ts +12 -1
- package/.next/standalone/app/policies/hooks-client.tsx +41 -4
- package/.next/standalone/fp-cloud-cli/tests/test_pagination.py +204 -0
- package/.next/standalone/package.json +9 -9
- package/.next/standalone/server.js +1 -1
- package/bin/failproofai.mjs +10 -1
- package/dist/cli.mjs +22429 -21292
- package/dist/worker.mjs +1247 -540
- package/package.json +9 -9
- package/src/audit/index.ts +24 -4
- package/src/hooks/builtin-policies.ts +933 -8
- package/src/hooks/custom-hooks-loader.ts +98 -8
- package/src/hooks/handler.ts +26 -14
- package/src/hooks/manager.ts +36 -2
- package/src/hooks/pack-cli.ts +415 -42
- package/src/hooks/pack-failclosed.ts +100 -4
- package/src/hooks/pack-manifest.ts +29 -0
- package/src/hooks/pack-param-key.ts +36 -0
- package/src/hooks/pack-store.ts +101 -9
- package/src/hooks/policy-catalog.ts +9 -1
- package/src/hooks/policy-evaluator.ts +77 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__06cuf1y._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_usx0j._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_zk340._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0jnjf2t._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0oy4kxb._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zf034w._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tg4pu6._.js +0 -3
- package/.next/standalone/.next/server/chunks/_0lxbzdq._.js +0 -3
- package/.next/standalone/.next/server/chunks/_1zuiiy3._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0mgrjnx._.js +0 -10
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1wlvpe5._.js +0 -5
- /package/.next/standalone/.next/static/{NgD5HvuyPxwgrpeIiafJL → BFSuuqOY0YU7NpThROqre}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{NgD5HvuyPxwgrpeIiafJL → BFSuuqOY0YU7NpThROqre}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{NgD5HvuyPxwgrpeIiafJL → BFSuuqOY0YU7NpThROqre}/_ssgManifest.js +0 -0
|
@@ -86,7 +86,18 @@ export async function addBundledPackWebAction(): Promise<PackActionResult> {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
export async function removePackWebAction(id: string): Promise<PackActionResult> {
|
|
89
|
-
|
|
89
|
+
// The dashboard always sends an id it read off an installed pack, so the
|
|
90
|
+
// ambiguous case is unreachable from here — caught anyway, because an
|
|
91
|
+
// uncaught throw in a server action reaches the browser as a generic failure
|
|
92
|
+
// with the reason stripped, and "something went wrong" is the least useful
|
|
93
|
+
// thing this could say.
|
|
94
|
+
let removed: string | null;
|
|
95
|
+
try {
|
|
96
|
+
removed = removePack(id);
|
|
97
|
+
} catch (err) {
|
|
98
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
99
|
+
}
|
|
100
|
+
return removed ? { ok: true, id: removed } : { ok: false, error: `No installed pack with id ${id}` };
|
|
90
101
|
}
|
|
91
102
|
|
|
92
103
|
/**
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
import type { PackPreviewResult } from "@/app/actions/pack-actions";
|
|
26
26
|
import { installHooksWebAction, removeHooksWebAction } from "@/app/actions/install-hooks-web";
|
|
27
27
|
import { updatePolicyParamsAction } from "@/app/actions/update-policy-params";
|
|
28
|
+
import { packPolicyParamKey } from "@/src/hooks/pack-param-key";
|
|
28
29
|
import { useAutoRefresh } from "@/contexts/AutoRefreshContext";
|
|
29
30
|
import { usePostHog } from "@/contexts/PostHogContext";
|
|
30
31
|
import { useUrlParams } from "@/lib/use-url-params";
|
|
@@ -1284,13 +1285,26 @@ function PoliciesTab({ onHooksInstallChange }: { onHooksInstallChange?: (install
|
|
|
1284
1285
|
return;
|
|
1285
1286
|
}
|
|
1286
1287
|
setHooksWarning(null);
|
|
1288
|
+
// A policy name is unique only WITHIN a pack, so `config.policies` — the
|
|
1289
|
+
// flat list across every installed pack — can hold two `block-sudo` rows.
|
|
1290
|
+
// The write below targets (packId, name); matching the optimistic update on
|
|
1291
|
+
// the name alone flipped the OTHER pack's row too, showing a change nothing
|
|
1292
|
+
// persisted until the next reload silently undid it.
|
|
1293
|
+
//
|
|
1294
|
+
// Matched on the pair with no fallback to the name. `PolicyInfo.packId` is
|
|
1295
|
+
// required — every policy comes from a pack — and `togglePackPolicyAction`
|
|
1296
|
+
// takes a `string`, so a row without one is a type error at the call below,
|
|
1297
|
+
// not a case to widen for here. A `packId === undefined ||` guard would only
|
|
1298
|
+
// ever restore the conflation this line exists to remove.
|
|
1287
1299
|
// Optimistic update
|
|
1288
1300
|
setConfig((prev) => {
|
|
1289
1301
|
if (!prev) return prev;
|
|
1290
1302
|
return {
|
|
1291
1303
|
...prev,
|
|
1292
1304
|
policies: prev.policies.map((p) =>
|
|
1293
|
-
p.name === name
|
|
1305
|
+
p.name === name && p.packId === policy.packId
|
|
1306
|
+
? { ...p, enabled: !currentlyEnabled }
|
|
1307
|
+
: p,
|
|
1294
1308
|
),
|
|
1295
1309
|
packs: prev.packs.map((pack) =>
|
|
1296
1310
|
pack.id === policy.packId
|
|
@@ -1306,7 +1320,19 @@ function PoliciesTab({ onHooksInstallChange }: { onHooksInstallChange?: (install
|
|
|
1306
1320
|
});
|
|
1307
1321
|
startTransition(async () => {
|
|
1308
1322
|
try {
|
|
1309
|
-
|
|
1323
|
+
// The RESULT, not just the absence of a throw. This action reports a
|
|
1324
|
+
// missing pack, an unreadable manifest, or a policy an intervening pack
|
|
1325
|
+
// update removed by RETURNING `{ ok: false, error }` — deliberately,
|
|
1326
|
+
// because none of those is exceptional. Awaiting and discarding it left
|
|
1327
|
+
// the optimistic row showing enforcement that was never written: the
|
|
1328
|
+
// dashboard said a policy was on, and nothing was running it. Every
|
|
1329
|
+
// reachable path there is a stale dashboard, which is the ordinary
|
|
1330
|
+
// state of a tab somebody left open.
|
|
1331
|
+
const result = await togglePackPolicyAction(policy.packId, name, !currentlyEnabled);
|
|
1332
|
+
if (!result.ok) {
|
|
1333
|
+
fireActionError("policy_toggle", result.error ?? "Failed to save policy change.");
|
|
1334
|
+
reload();
|
|
1335
|
+
}
|
|
1310
1336
|
} catch {
|
|
1311
1337
|
fireActionError("policy_toggle", "Failed to save policy change.");
|
|
1312
1338
|
reload();
|
|
@@ -1380,7 +1406,14 @@ function PoliciesTab({ onHooksInstallChange }: { onHooksInstallChange?: (install
|
|
|
1380
1406
|
|
|
1381
1407
|
const handleSaveParams = (params: Record<string, unknown>) => {
|
|
1382
1408
|
if (!configuringPolicy) return;
|
|
1383
|
-
|
|
1409
|
+
// A PACK policy is saved under the stable pack-qualified key, because the
|
|
1410
|
+
// bare name is not what the evaluator looks up — it registers pack policies
|
|
1411
|
+
// as `pack/<id>@<version>/<name>`, so a bare key was written, displayed as
|
|
1412
|
+
// saved, and ignored at runtime. Version-less on purpose: a key carrying
|
|
1413
|
+
// the version would be orphaned by the publisher's next release.
|
|
1414
|
+
const policyName = configuringPolicy.packId
|
|
1415
|
+
? packPolicyParamKey(configuringPolicy.packId, configuringPolicy.name)
|
|
1416
|
+
: configuringPolicy.name;
|
|
1384
1417
|
setConfiguringPolicy(null);
|
|
1385
1418
|
startTransition(async () => {
|
|
1386
1419
|
try {
|
|
@@ -1642,8 +1675,12 @@ function PoliciesTab({ onHooksInstallChange }: { onHooksInstallChange?: (install
|
|
|
1642
1675
|
</div>
|
|
1643
1676
|
{/* Policy rows */}
|
|
1644
1677
|
{policies.map((policy) => (
|
|
1678
|
+
// Keyed by pack AND name: two installed packs may each declare
|
|
1679
|
+
// `block-sudo` in the same category, and a bare name key made
|
|
1680
|
+
// them one React key — React warns and reuses the first row's
|
|
1681
|
+
// state for the second.
|
|
1645
1682
|
<div
|
|
1646
|
-
key={policy.name}
|
|
1683
|
+
key={`${policy.packId}/${policy.name}`}
|
|
1647
1684
|
className="flex items-start gap-3 px-4 py-3 border-b border-border/20 hover:bg-muted/20 transition-colors"
|
|
1648
1685
|
>
|
|
1649
1686
|
<div className="mt-0.5 shrink-0">
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"""When `--all` stops early, the response must say so.
|
|
2
|
+
|
|
3
|
+
The defect these pin was measured against a live instance, on the CLI this one
|
|
4
|
+
was moved from:
|
|
5
|
+
|
|
6
|
+
fp --json sessions --since 7d --limit 200 -> 200 rows, next_cursor set
|
|
7
|
+
fp --json sessions --since 7d --all -> 50 rows (25x undercount)
|
|
8
|
+
fp --json sessions --since 7d --all --limit 5000 -> 1,274 rows
|
|
9
|
+
|
|
10
|
+
`--all` inherits `--limit`'s default of 50 — which is documented and deliberate
|
|
11
|
+
here — but the envelope then hard-coded `next_cursor: null`, so a 4% sample of a
|
|
12
|
+
customer's fleet came back with exit 0, no warning, and a response that read as
|
|
13
|
+
complete. Nothing in the output could tell you otherwise.
|
|
14
|
+
|
|
15
|
+
The fix (`Walk`, carried through `client.paginate`) is present in this repo and
|
|
16
|
+
was untested when these were written. That combination is the one worth
|
|
17
|
+
guarding: a silent-completeness bug cannot be caught by reading the code, only
|
|
18
|
+
by asserting on what the envelope claims.
|
|
19
|
+
|
|
20
|
+
So every test here checks the REPORTED completeness, not just the row count.
|
|
21
|
+
|
|
22
|
+
The fake feed is a real cursor server — it honours `limit` and `cursor` — not a
|
|
23
|
+
list of canned pages. A fixture that ignored `limit` would pass no matter how
|
|
24
|
+
few rows the CLI asked for, which is to say it could not fail on the bug.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import json
|
|
30
|
+
|
|
31
|
+
import httpx
|
|
32
|
+
import pytest
|
|
33
|
+
import respx
|
|
34
|
+
|
|
35
|
+
from fp_cli import client
|
|
36
|
+
from fp_cli.app import app
|
|
37
|
+
|
|
38
|
+
from .conftest import BASE_URL as BASE
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _feed(key: str, total: int, row):
|
|
42
|
+
"""respx side_effect: a cursor-paginated feed holding ``total`` rows.
|
|
43
|
+
|
|
44
|
+
The cursor is the offset of the next row (opaque to the CLI, which only
|
|
45
|
+
round-trips it). ``limit`` is honoured exactly, so the CLI's own page sizing
|
|
46
|
+
decides how much of the feed it can actually reach.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def handler(request: httpx.Request) -> httpx.Response:
|
|
50
|
+
params = request.url.params
|
|
51
|
+
limit = int(params["limit"])
|
|
52
|
+
offset = int(params.get("cursor") or 0)
|
|
53
|
+
rows = [row(i) for i in range(offset, min(offset + limit, total))]
|
|
54
|
+
nxt = offset + len(rows)
|
|
55
|
+
return httpx.Response(
|
|
56
|
+
200, json={key: rows, "next_cursor": str(nxt) if nxt < total else None}
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
return handler
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _event_row(i: int) -> dict:
|
|
63
|
+
return {
|
|
64
|
+
"id": i, "session_id": "s", "agent_id": "a", "event_type": "tool_use",
|
|
65
|
+
"ts": "2026-08-24T12:00:00Z", "environment": "prod", "summary": f"row {i}",
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _session_row(i: int) -> dict:
|
|
70
|
+
return {
|
|
71
|
+
"session_id": f"sess-{i}", "agent_id": "a", "environment": "prod",
|
|
72
|
+
"last_event_at": "2026-08-24T12:00:00Z", "latest_evaluation": None,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _eval_row(i: int) -> dict:
|
|
77
|
+
return {
|
|
78
|
+
"id": f"eval-{i}", "session_id": "s", "agent_id": "a", "environment": "prod",
|
|
79
|
+
"status": "done", "scores": {"helpfulness": 0.9}, "created_at": "2026-08-24T12:00:00Z",
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# (command, endpoint, server key, envelope key, row builder). Every command that
|
|
84
|
+
# takes `--all`; the bug was reported on `sessions` but lived in the shared
|
|
85
|
+
# walker, so all four had it. `errors` is the one where the two keys differ: it
|
|
86
|
+
# reads the shared errored-events feed (`{"events": …}`) and re-labels it
|
|
87
|
+
# `errors` on the way out.
|
|
88
|
+
FEEDS = [
|
|
89
|
+
("events", "/api/events/summary", "events", "events", _event_row),
|
|
90
|
+
("sessions", "/api/sessions", "sessions", "sessions", _session_row),
|
|
91
|
+
("evals", "/api/evaluations", "evaluations", "evaluations", _eval_row),
|
|
92
|
+
("errors", "/api/events/summary", "events", "errors", _event_row),
|
|
93
|
+
]
|
|
94
|
+
IDS = [f[0] for f in FEEDS]
|
|
95
|
+
|
|
96
|
+
TOTAL = 274 # > the 50 default, and not a multiple of the 200 page size
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@pytest.mark.parametrize("command,endpoint,server_key,key,row", FEEDS, ids=IDS)
|
|
100
|
+
@respx.mock
|
|
101
|
+
def test_all_stopped_by_the_default_limit_is_reported_not_silent(
|
|
102
|
+
logged_in, runner, command, endpoint, server_key, key, row
|
|
103
|
+
):
|
|
104
|
+
"""THE REGRESSION. `--all` stopping at 50 is the documented behaviour; saying
|
|
105
|
+
the feed was exhausted is the bug."""
|
|
106
|
+
respx.get(f"{BASE}{endpoint}").mock(side_effect=_feed(server_key, TOTAL, row))
|
|
107
|
+
res = runner.invoke(app, ["--base-url", BASE, "--json", command, "--all"])
|
|
108
|
+
assert res.exit_code == 0, res.output
|
|
109
|
+
out = json.loads(res.stdout)
|
|
110
|
+
assert len(out[key]) == 50, "the documented default bound still applies"
|
|
111
|
+
assert out["next_cursor"] is not None, (
|
|
112
|
+
"a walk that stopped on --limit must hand back a resumable cursor; "
|
|
113
|
+
"null here is the 4%-sample-reported-as-complete bug"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@pytest.mark.parametrize("command,endpoint,server_key,key,row", FEEDS, ids=IDS)
|
|
118
|
+
@respx.mock
|
|
119
|
+
def test_all_that_exhausts_the_feed_reports_complete(
|
|
120
|
+
logged_in, runner, command, endpoint, server_key, key, row
|
|
121
|
+
):
|
|
122
|
+
"""The other direction, and the reason `truncated` cannot just be "we hit the
|
|
123
|
+
limit": a walk that took every row must NOT claim there is more."""
|
|
124
|
+
respx.get(f"{BASE}{endpoint}").mock(side_effect=_feed(server_key, 30, row))
|
|
125
|
+
res = runner.invoke(app, ["--base-url", BASE, "--json", command, "--all", "--limit", "500"])
|
|
126
|
+
assert res.exit_code == 0, res.output
|
|
127
|
+
out = json.loads(res.stdout)
|
|
128
|
+
assert len(out[key]) == 30
|
|
129
|
+
assert out["next_cursor"] is None, "an exhausted feed must not look truncated"
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@respx.mock
|
|
133
|
+
def test_an_explicit_limit_is_honoured_and_reported(logged_in, runner):
|
|
134
|
+
"""`--all --limit N` is how you actually get more than the default, so the
|
|
135
|
+
bound has to hold exactly and still report the remainder."""
|
|
136
|
+
respx.get(f"{BASE}/api/sessions").mock(side_effect=_feed("sessions", TOTAL, _session_row))
|
|
137
|
+
res = runner.invoke(
|
|
138
|
+
app, ["--base-url", BASE, "--json", "sessions", "--all", "--limit", "220"]
|
|
139
|
+
)
|
|
140
|
+
assert res.exit_code == 0, res.output
|
|
141
|
+
out = json.loads(res.stdout)
|
|
142
|
+
assert len(out["sessions"]) == 220
|
|
143
|
+
assert out["next_cursor"] is not None
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@respx.mock
|
|
147
|
+
def test_all_pages_in_chunks_rather_than_one_giant_request(logged_in, runner):
|
|
148
|
+
"""The walk must actually WALK. Asking for 220 in a single request would work
|
|
149
|
+
against this fake and fail against a server that caps a page at 200."""
|
|
150
|
+
route = respx.get(f"{BASE}/api/sessions").mock(
|
|
151
|
+
side_effect=_feed("sessions", TOTAL, _session_row)
|
|
152
|
+
)
|
|
153
|
+
res = runner.invoke(
|
|
154
|
+
app, ["--base-url", BASE, "--json", "sessions", "--all", "--limit", "220"]
|
|
155
|
+
)
|
|
156
|
+
assert res.exit_code == 0, res.output
|
|
157
|
+
assert route.call_count > 1, "a 220-row walk must span more than one request"
|
|
158
|
+
for call in route.calls:
|
|
159
|
+
assert int(call.request.url.params["limit"]) <= 200
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# --- the walker itself, below the CLI ---------------------------------------
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _pages(total: int, page: int):
|
|
166
|
+
"""A `fetch_page` over ``total`` rows, honouring the limit it is given."""
|
|
167
|
+
|
|
168
|
+
def fetch(cursor=None, limit=None):
|
|
169
|
+
offset = int(cursor or 0)
|
|
170
|
+
size = min(limit or page, page)
|
|
171
|
+
items = list(range(offset, min(offset + size, total)))
|
|
172
|
+
nxt = offset + len(items)
|
|
173
|
+
return client.Page(items=items, next_cursor=str(nxt) if nxt < total else None)
|
|
174
|
+
|
|
175
|
+
return fetch
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def test_walk_records_the_cursor_it_stopped_on():
|
|
179
|
+
walk = client.Walk()
|
|
180
|
+
items = list(client.paginate(_pages(500, 200), limit=250, walk=walk))
|
|
181
|
+
assert len(items) == 250
|
|
182
|
+
assert walk.truncated is True
|
|
183
|
+
assert walk.next_cursor is not None, "a bounded walk must be resumable"
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def test_walk_that_exhausts_the_feed_is_not_flagged_truncated():
|
|
187
|
+
walk = client.Walk()
|
|
188
|
+
items = list(client.paginate(_pages(120, 200), limit=500, walk=walk))
|
|
189
|
+
assert len(items) == 120
|
|
190
|
+
assert walk.truncated is False
|
|
191
|
+
assert walk.next_cursor is None
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def test_truncated_is_not_derivable_from_the_row_count():
|
|
195
|
+
"""The subtle case, and why `Walk` exists at all: a walk whose limit lands
|
|
196
|
+
EXACTLY on the last row is complete, and one that lands exactly on a page
|
|
197
|
+
boundary with more behind it is not. Row count cannot tell those apart."""
|
|
198
|
+
exact = client.Walk()
|
|
199
|
+
list(client.paginate(_pages(200, 200), limit=200, walk=exact))
|
|
200
|
+
assert exact.truncated is False, "taking every row is not a truncation"
|
|
201
|
+
|
|
202
|
+
boundary = client.Walk()
|
|
203
|
+
list(client.paginate(_pages(400, 200), limit=200, walk=boundary))
|
|
204
|
+
assert boundary.truncated is True, "stopping on a boundary with more behind it is"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "failproofai",
|
|
3
|
-
"version": "1.0.2-beta.
|
|
3
|
+
"version": "1.0.2-beta.9",
|
|
4
4
|
"description": "Observability and enforcement for AI agent harnesses. 39 built-in policies hooked into 12 of them — Claude Code, Codex, Cursor, Hermes, OpenClaw and more — blocking the tool call before it runs. Local dashboard included, no account needed.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"failproofai": "./dist/cli.mjs",
|
|
@@ -120,15 +120,15 @@
|
|
|
120
120
|
"sharp": "0.35.0"
|
|
121
121
|
},
|
|
122
122
|
"optionalDependencies": {
|
|
123
|
-
"@failproofai/failproofaid-linux-x64": "1.0.2-beta.
|
|
124
|
-
"@failproofai/failproofaid-linux-arm64": "1.0.2-beta.
|
|
125
|
-
"@failproofai/failproofaid-darwin-x64": "1.0.2-beta.
|
|
126
|
-
"@failproofai/failproofaid-darwin-arm64": "1.0.2-beta.
|
|
123
|
+
"@failproofai/failproofaid-linux-x64": "1.0.2-beta.9",
|
|
124
|
+
"@failproofai/failproofaid-linux-arm64": "1.0.2-beta.9",
|
|
125
|
+
"@failproofai/failproofaid-darwin-x64": "1.0.2-beta.9",
|
|
126
|
+
"@failproofai/failproofaid-darwin-arm64": "1.0.2-beta.9"
|
|
127
127
|
},
|
|
128
128
|
"failproofaidBinaries": {
|
|
129
|
-
"linux-x64": "
|
|
130
|
-
"linux-arm64": "
|
|
131
|
-
"darwin-x64": "
|
|
132
|
-
"darwin-arm64": "
|
|
129
|
+
"linux-x64": "906b0c8a7502ad6a0494231a2220fff65db7ff8335ad7bdf15b58079a001562f",
|
|
130
|
+
"linux-arm64": "8b40f161cedf07ec3504dd736c906666d18bf042613af0a6a38f4daae8d7c7d4",
|
|
131
|
+
"darwin-x64": "7407d02234c24932ac9ece07bca348bd40c7a4edff72e0a8db9ac230a1e28a8c",
|
|
132
|
+
"darwin-arm64": "77131a4f0c1b9d3e5611ad3f4834542e26e32a404f1311903a1cf7783fe99fee"
|
|
133
133
|
}
|
|
134
134
|
}
|
|
@@ -9,7 +9,7 @@ const currentPort = parseInt(process.env.PORT, 10) || 3000
|
|
|
9
9
|
const hostname = process.env.HOSTNAME || '0.0.0.0'
|
|
10
10
|
|
|
11
11
|
let keepAliveTimeout = parseInt(process.env.KEEP_ALIVE_TIMEOUT, 10)
|
|
12
|
-
const nextConfig = {"env":{"NEXT_PUBLIC_APP_VERSION":"1.0.2-beta.
|
|
12
|
+
const nextConfig = {"env":{"NEXT_PUBLIC_APP_VERSION":"1.0.2-beta.9"},"typescript":{"ignoreBuildErrors":false},"typedRoutes":false,"distDir":"./.next","cleanDistDir":true,"assetPrefix":"","cacheMaxMemorySize":52428800,"configOrigin":"next.config.ts","useFileSystemPublicRoutes":true,"generateEtags":true,"pageExtensions":["tsx","ts","jsx","js"],"instrumentationClientInject":[],"poweredByHeader":true,"compress":true,"images":{"deviceSizes":[640,750,828,1080,1200,1920,2048,3840],"imageSizes":[32,48,64,96,128,256,384],"path":"/_next/image","loader":"default","loaderFile":"","domains":[],"disableStaticImages":false,"minimumCacheTTL":14400,"formats":["image/webp"],"maximumRedirects":3,"maximumResponseBody":50000000,"dangerouslyAllowLocalIP":false,"dangerouslyAllowSVG":false,"contentSecurityPolicy":"script-src 'none'; frame-src 'none'; sandbox;","contentDispositionType":"attachment","localPatterns":[{"pathname":"**","search":""}],"remotePatterns":[],"qualities":[75],"unoptimized":true,"customCacheHandler":false},"devIndicators":{"position":"bottom-left"},"onDemandEntries":{"maxInactiveAge":60000,"pagesBufferLength":5},"basePath":"","sassOptions":{},"trailingSlash":false,"i18n":null,"productionBrowserSourceMaps":false,"excludeDefaultMomentLocales":true,"reactProductionProfiling":false,"reactStrictMode":null,"reactMaxHeadersLength":6000,"httpAgentOptions":{"keepAlive":true},"logging":{"serverFunctions":true,"browserToTerminal":"warn"},"compiler":{},"expireTime":31536000,"staticPageGenerationTimeout":60,"output":"standalone","modularizeImports":{"@mui/icons-material":{"transform":"@mui/icons-material/{{member}}"},"lodash":{"transform":"lodash/{{member}}"}},"outputFileTracingRoot":"/home/runner/work/failproofai/failproofai","enablePrerenderSourceMaps":true,"cacheComponents":false,"cacheLife":{"default":{"stale":300,"revalidate":900,"expire":4294967294},"seconds":{"stale":30,"revalidate":1,"expire":60},"minutes":{"stale":300,"revalidate":60,"expire":3600},"hours":{"stale":300,"revalidate":3600,"expire":86400},"days":{"stale":300,"revalidate":86400,"expire":604800},"weeks":{"stale":300,"revalidate":604800,"expire":2592000},"max":{"stale":300,"revalidate":2592000,"expire":31536000}},"cacheHandlers":{},"experimental":{"appNewScrollHandler":true,"coldCacheBadge":false,"devValidationWorker":true,"useSkewCookie":false,"cssChunking":true,"multiZoneDraftMode":false,"appNavFailHandling":false,"prerenderEarlyExit":true,"serverMinification":true,"linkNoTouchStart":false,"caseSensitiveRoutes":false,"cachedNavigations":false,"dynamicOnHover":false,"useOffline":false,"varyParams":true,"optimisticRouting":true,"instrumentationClientRouterTransitionEvents":false,"prefetchInlining":{"maxSize":2048,"maxBundleSize":10240},"preloadEntriesOnStart":true,"clientRouterFilter":true,"clientRouterFilterRedirects":false,"fetchCacheKeyPrefix":"","proxyPrefetch":"flexible","optimisticClientCache":true,"manualClientBasePath":false,"cpus":3,"memoryBasedWorkersCount":false,"imgOptConcurrency":null,"imgOptOperationCache":null,"imgOptTimeoutInSeconds":7,"imgOptMaxInputPixels":268402689,"imgOptSequentialRead":null,"isrFlushToDisk":true,"workerThreads":false,"optimizeCss":false,"nextScriptWorkers":false,"scrollRestoration":false,"externalDir":false,"devMemoryThresholdRestart":true,"disableOptimizedLoading":false,"gzipSize":true,"craCompat":false,"esmExternals":true,"fullySpecified":false,"swcTraceProfiling":false,"forceSwcTransforms":false,"requestInsights":false,"largePageDataBytes":128000,"typedEnv":false,"parallelServerCompiles":false,"parallelServerBuildTraces":false,"ppr":false,"authInterrupts":false,"webpackMemoryOptimizations":false,"optimizeServerReact":true,"strictRouteTypes":false,"useTypeScriptCli":true,"removeUncaughtErrorAndRejectionListeners":false,"validateRSCRequestHeaders":true,"staleTimes":{"dynamic":0,"static":300},"reactDebugChannel":true,"serverComponentsHmrCache":true,"serverComponentsHmrCancellation":false,"staticGenerationMaxConcurrency":8,"staticGenerationMinPagesPerWorker":25,"transitionIndicator":false,"gestureTransition":false,"inlineCss":false,"useCache":false,"globalNotFound":false,"browserDebugInfoInTerminal":"warn","lockDistDir":true,"proxyClientMaxBodySize":10485760,"hideLogsAfterAbort":false,"mcpServer":true,"turbopackFileSystemCacheForDev":true,"turbopackFileSystemCacheForBuild":true,"turbopackInferModuleSideEffects":true,"turbopackPluginRuntimeStrategy":"childProcesses","turbopackMemoryEvictionMode":"auto","optimizePackageImports":["lucide-react","date-fns","lodash-es","ramda","antd","react-bootstrap","ahooks","@ant-design/icons","@headlessui/react","@headlessui-float/react","@heroicons/react/20/solid","@heroicons/react/24/solid","@heroicons/react/24/outline","@visx/visx","@tremor/react","rxjs","@mui/material","@mui/icons-material","recharts","react-use","effect","@effect/schema","@effect/platform","@effect/platform-node","@effect/platform-browser","@effect/platform-bun","@effect/sql","@effect/sql-mssql","@effect/sql-mysql2","@effect/sql-pg","@effect/sql-sqlite-node","@effect/sql-sqlite-bun","@effect/sql-sqlite-wasm","@effect/sql-sqlite-react-native","@effect/rpc","@effect/rpc-http","@effect/typeclass","@effect/experimental","@effect/opentelemetry","@material-ui/core","@material-ui/icons","@tabler/icons-react","mui-core","react-icons/ai","react-icons/bi","react-icons/bs","react-icons/cg","react-icons/ci","react-icons/di","react-icons/fa","react-icons/fa6","react-icons/fc","react-icons/fi","react-icons/gi","react-icons/go","react-icons/gr","react-icons/hi","react-icons/hi2","react-icons/im","react-icons/io","react-icons/io5","react-icons/lia","react-icons/lib","react-icons/lu","react-icons/md","react-icons/pi","react-icons/ri","react-icons/rx","react-icons/si","react-icons/sl","react-icons/tb","react-icons/tfi","react-icons/ti","react-icons/vsc","react-icons/wi"],"useCacheTimeout":54,"instantInsights":{"validationLevel":"warning"},"trustHostHeader":false,"isExperimentalCompile":false},"htmlLimitedBots":"[\\w-]+-Google|Google-[\\w-]+|Chrome-Lighthouse|Slurp|DuckDuckBot|baiduspider|yandex|sogou|bitlybot|tumblr|vkShare|quora link preview|redditbot|ia_archiver|Bingbot|BingPreview|applebot|facebookexternalhit|facebookcatalog|Twitterbot|LinkedInBot|Slackbot|Discordbot|WhatsApp|SkypeUriPreview|Yeti|googleweblight","bundlePagesRouterDependencies":false,"configFileName":"next.config.ts","outputFileTracingExcludes":{"*":["node_modules/@img/**","node_modules/sharp/**"]},"turbopack":{"root":"/home/runner/work/failproofai/failproofai"},"repoRoot":"/home/runner/work/failproofai/failproofai","distDirRoot":".next"}
|
|
13
13
|
|
|
14
14
|
process.env.__NEXT_PRIVATE_STANDALONE_CONFIG = JSON.stringify(nextConfig)
|
|
15
15
|
|
package/bin/failproofai.mjs
CHANGED
|
@@ -1408,11 +1408,19 @@ async function runCli() {
|
|
|
1408
1408
|
],
|
|
1409
1409
|
},
|
|
1410
1410
|
{
|
|
1411
|
+
// This screen said "an existing private one still publishes, and
|
|
1412
|
+
// warns" for as long as that was true. It is REFUSED now — exit 1,
|
|
1413
|
+
// nothing created or uploaded — and `--allow-private` was named
|
|
1414
|
+
// nowhere in this help, so the one documented behaviour pointed a
|
|
1415
|
+
// publisher at a command that exits 1 and gave them no way through.
|
|
1411
1416
|
label: "the repo must be public",
|
|
1412
1417
|
lines: [
|
|
1413
1418
|
"Installs are anonymous HTTPS with no credential to offer, so a private",
|
|
1414
1419
|
"repository publishes to nobody. A repo created here is public for that",
|
|
1415
|
-
"reason; an existing private one
|
|
1420
|
+
"reason; an existing private one is REFUSED, before anything is built,",
|
|
1421
|
+
"created or uploaded. --allow-private publishes to one anyway, for",
|
|
1422
|
+
"somebody who will hand the three assets over another way — it still",
|
|
1423
|
+
"says plainly that no `policies add` can reach them.",
|
|
1416
1424
|
"",
|
|
1417
1425
|
"Only the release matters. Installs read releases/download/<tag>/<asset>",
|
|
1418
1426
|
"and never touch your git tree — pushing the source is for humans.",
|
|
@@ -1444,6 +1452,7 @@ async function runCli() {
|
|
|
1444
1452
|
["--out <dir>", "Where to write the assets. Default: dist-pack."],
|
|
1445
1453
|
["--effect <effect>", "enforce or observe. observe records and blocks nothing. Default: enforce."],
|
|
1446
1454
|
["--dry-run", "Build the assets, publish nothing. No credential."],
|
|
1455
|
+
["--allow-private", "Publish to an already-private repo anyway. Nobody can install it."],
|
|
1447
1456
|
],
|
|
1448
1457
|
},
|
|
1449
1458
|
{
|