failproofai 0.0.6-beta.1 → 0.0.6-beta.3
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/.failproofai/policies/review-policies.mjs +4 -3
- 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 +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
- 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 +10 -10
- 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 +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -10
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- 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]__0g72weg._.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]__092s1ta._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0~kmh8w._.js → [root-of-the-server]__096k.db._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__09icjsf._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0g.lg8b._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h..k-e._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0rh.18_._.js → [root-of-the-server]__0kyh86x._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0okos0k._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0w6l33k._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11pa2ra._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12t-wym._.js +2 -2
- 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/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/{0gbf4cphy8ksq.js → 0-dm_9a6nsc2l.js} +1 -1
- package/.next/standalone/.next/static/chunks/{12~yi9oj8av8p.js → 01pmw1-asbek~.js} +2 -2
- package/.next/standalone/.next/static/chunks/{0v.yd0kg_ld3r.js → 051m32nx~n5yr.js} +1 -1
- package/.next/standalone/.next/static/chunks/{09_k80d~cq2wg.js → 0a-yctdwn368y.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0bvhsa6zva2o..js → 0ksdlt_1hucdm.js} +1 -1
- package/.next/standalone/.next/static/chunks/{01b~z8f1ws0rk.js → 0l-mu4okl-cj1.js} +1 -1
- package/.next/standalone/.next/static/chunks/{08t08igdql9yt.js → 0mazj-p-~2kc6.js} +1 -1
- package/.next/standalone/.next/static/chunks/0qakntsrpc~1j.js +6 -0
- package/.next/standalone/.next/static/chunks/{03rz6ykw-a2xi.js → 156zca6aewyr-.js} +1 -1
- package/.next/standalone/CHANGELOG.md +18 -0
- package/.next/standalone/bin/failproofai.mjs +91 -4
- package/.next/standalone/dist/cli.mjs +1156 -55
- package/.next/standalone/docs/ar/built-in-policies.mdx +140 -103
- package/.next/standalone/docs/ar/custom-policies.mdx +72 -72
- package/.next/standalone/docs/ar/examples.mdx +86 -33
- package/.next/standalone/docs/ar/getting-started.mdx +82 -29
- package/.next/standalone/docs/built-in-policies.mdx +3 -3
- package/.next/standalone/docs/de/built-in-policies.mdx +97 -60
- package/.next/standalone/docs/de/custom-policies.mdx +56 -56
- package/.next/standalone/docs/de/examples.mdx +72 -18
- package/.next/standalone/docs/de/getting-started.mdx +72 -20
- package/.next/standalone/docs/es/built-in-policies.mdx +91 -54
- package/.next/standalone/docs/es/custom-policies.mdx +55 -55
- package/.next/standalone/docs/es/examples.mdx +73 -19
- package/.next/standalone/docs/es/getting-started.mdx +72 -20
- package/.next/standalone/docs/fr/built-in-policies.mdx +99 -62
- package/.next/standalone/docs/fr/custom-policies.mdx +51 -51
- package/.next/standalone/docs/fr/examples.mdx +78 -24
- package/.next/standalone/docs/fr/getting-started.mdx +65 -13
- package/.next/standalone/docs/he/built-in-policies.mdx +139 -99
- package/.next/standalone/docs/he/custom-policies.mdx +75 -75
- package/.next/standalone/docs/he/examples.mdx +87 -33
- package/.next/standalone/docs/he/getting-started.mdx +84 -33
- package/.next/standalone/docs/hi/built-in-policies.mdx +203 -166
- package/.next/standalone/docs/hi/custom-policies.mdx +71 -70
- package/.next/standalone/docs/hi/examples.mdx +90 -36
- package/.next/standalone/docs/hi/getting-started.mdx +80 -27
- package/.next/standalone/docs/i18n/README.ar.md +69 -69
- package/.next/standalone/docs/i18n/README.de.md +46 -46
- package/.next/standalone/docs/i18n/README.es.md +42 -42
- package/.next/standalone/docs/i18n/README.fr.md +39 -39
- package/.next/standalone/docs/i18n/README.he.md +83 -83
- package/.next/standalone/docs/i18n/README.hi.md +69 -69
- package/.next/standalone/docs/i18n/README.it.md +72 -72
- package/.next/standalone/docs/i18n/README.ja.md +71 -71
- package/.next/standalone/docs/i18n/README.ko.md +52 -52
- package/.next/standalone/docs/i18n/README.pt-br.md +44 -44
- package/.next/standalone/docs/i18n/README.ru.md +66 -66
- package/.next/standalone/docs/i18n/README.tr.md +82 -83
- package/.next/standalone/docs/i18n/README.vi.md +70 -71
- package/.next/standalone/docs/i18n/README.zh.md +51 -51
- package/.next/standalone/docs/it/built-in-policies.mdx +115 -78
- package/.next/standalone/docs/it/custom-policies.mdx +69 -69
- package/.next/standalone/docs/it/examples.mdx +93 -39
- package/.next/standalone/docs/it/getting-started.mdx +73 -21
- package/.next/standalone/docs/ja/built-in-policies.mdx +155 -118
- package/.next/standalone/docs/ja/custom-policies.mdx +71 -71
- package/.next/standalone/docs/ja/examples.mdx +76 -22
- package/.next/standalone/docs/ja/getting-started.mdx +65 -13
- package/.next/standalone/docs/ko/built-in-policies.mdx +103 -66
- package/.next/standalone/docs/ko/custom-policies.mdx +67 -67
- package/.next/standalone/docs/ko/examples.mdx +87 -33
- package/.next/standalone/docs/ko/getting-started.mdx +61 -9
- package/.next/standalone/docs/pt-br/built-in-policies.mdx +72 -35
- package/.next/standalone/docs/pt-br/custom-policies.mdx +56 -56
- package/.next/standalone/docs/pt-br/examples.mdx +78 -24
- package/.next/standalone/docs/pt-br/getting-started.mdx +64 -12
- package/.next/standalone/docs/ru/built-in-policies.mdx +135 -98
- package/.next/standalone/docs/ru/custom-policies.mdx +82 -81
- package/.next/standalone/docs/ru/examples.mdx +77 -22
- package/.next/standalone/docs/ru/getting-started.mdx +74 -22
- package/.next/standalone/docs/tr/built-in-policies.mdx +126 -89
- package/.next/standalone/docs/tr/custom-policies.mdx +59 -60
- package/.next/standalone/docs/tr/examples.mdx +97 -42
- package/.next/standalone/docs/tr/getting-started.mdx +75 -23
- package/.next/standalone/docs/vi/built-in-policies.mdx +116 -81
- package/.next/standalone/docs/vi/custom-policies.mdx +68 -68
- package/.next/standalone/docs/vi/examples.mdx +93 -38
- package/.next/standalone/docs/vi/getting-started.mdx +74 -22
- package/.next/standalone/docs/zh/built-in-policies.mdx +117 -82
- package/.next/standalone/docs/zh/custom-policies.mdx +49 -49
- package/.next/standalone/docs/zh/examples.mdx +90 -36
- package/.next/standalone/docs/zh/getting-started.mdx +73 -21
- package/.next/standalone/package.json +1 -1
- package/.next/standalone/server.js +1 -1
- package/.next/standalone/src/auth/login.ts +104 -0
- package/.next/standalone/src/auth/logout.ts +50 -0
- package/.next/standalone/src/auth/token-store.ts +64 -0
- package/.next/standalone/src/hooks/builtin-policies.ts +27 -21
- package/.next/standalone/src/hooks/handler.ts +35 -15
- package/.next/standalone/src/relay/daemon.ts +362 -0
- package/.next/standalone/src/relay/pid.ts +76 -0
- package/.next/standalone/src/relay/queue.ts +225 -0
- package/bin/failproofai.mjs +91 -4
- package/dist/cli.mjs +1156 -55
- package/package.json +1 -1
- package/src/auth/login.ts +104 -0
- package/src/auth/logout.ts +50 -0
- package/src/auth/token-store.ts +64 -0
- package/src/hooks/builtin-policies.ts +27 -21
- package/src/hooks/handler.ts +35 -15
- package/src/relay/daemon.ts +362 -0
- package/src/relay/pid.ts +76 -0
- package/src/relay/queue.ts +225 -0
- package/.next/standalone/.next/static/chunks/0wlyoif4_kj_t.js +0 -6
- /package/.next/standalone/.next/static/{CkmOT-ZvDN-sVULinGVKT → r-wX0MuAfCjbhJm3phQc8}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{CkmOT-ZvDN-sVULinGVKT → r-wX0MuAfCjbhJm3phQc8}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{CkmOT-ZvDN-sVULinGVKT → r-wX0MuAfCjbhJm3phQc8}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import {
|
|
2
|
+
appendFileSync,
|
|
3
|
+
mkdirSync,
|
|
4
|
+
existsSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
statSync,
|
|
7
|
+
renameSync,
|
|
8
|
+
unlinkSync,
|
|
9
|
+
readdirSync,
|
|
10
|
+
chmodSync,
|
|
11
|
+
} from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { homedir } from "node:os";
|
|
14
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
15
|
+
import { isLoggedIn } from "../auth/token-store";
|
|
16
|
+
|
|
17
|
+
const QUEUE_DIR = join(homedir(), ".failproofai", "cache", "server-queue");
|
|
18
|
+
const PENDING_FILE = join(QUEUE_DIR, "pending.jsonl");
|
|
19
|
+
const PROCESSING_PREFIX = "processing-";
|
|
20
|
+
|
|
21
|
+
// Cap — if the queue file exceeds this, `appendToServerQueue` is a no-op.
|
|
22
|
+
// Prevents unbounded growth when the daemon is down for a long time or the
|
|
23
|
+
// user installed the CLI but never logged in.
|
|
24
|
+
const MAX_QUEUE_BYTES = 50 * 1024 * 1024; // 50 MB
|
|
25
|
+
|
|
26
|
+
export interface RawEntry {
|
|
27
|
+
timestamp: number;
|
|
28
|
+
eventType: string;
|
|
29
|
+
toolName?: string | null;
|
|
30
|
+
policyName?: string | null;
|
|
31
|
+
policyNames?: string[];
|
|
32
|
+
decision: string;
|
|
33
|
+
reason?: string | null;
|
|
34
|
+
durationMs: number;
|
|
35
|
+
sessionId?: string | null;
|
|
36
|
+
transcriptPath?: string | null;
|
|
37
|
+
cwd?: string | null;
|
|
38
|
+
permissionMode?: string | null;
|
|
39
|
+
hookEventName?: string | null;
|
|
40
|
+
toolInput?: Record<string, unknown>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* What actually gets persisted and sent to the server. Intentionally a
|
|
45
|
+
* narrower shape than RawEntry — we drop / hash anything that could leak
|
|
46
|
+
* secrets or paths:
|
|
47
|
+
* - toolInput: dropped entirely (can contain credentials, file contents, commands)
|
|
48
|
+
* - cwd: replaced with cwd_hash (SHA-256) so the server can group by project
|
|
49
|
+
* - transcriptPath: dropped (local-only filesystem path)
|
|
50
|
+
* - reason: passed through a redactor for common credential patterns
|
|
51
|
+
*/
|
|
52
|
+
export interface QueueEntry {
|
|
53
|
+
client_event_id: string;
|
|
54
|
+
timestamp: number;
|
|
55
|
+
event_type: string;
|
|
56
|
+
tool_name: string | null;
|
|
57
|
+
policy_name: string | null;
|
|
58
|
+
policy_names: string[];
|
|
59
|
+
decision: string;
|
|
60
|
+
reason: string | null;
|
|
61
|
+
duration_ms: number;
|
|
62
|
+
session_id: string | null;
|
|
63
|
+
cwd_hash: string | null;
|
|
64
|
+
permission_mode: string | null;
|
|
65
|
+
hook_event_name: string | null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function hashCwd(cwd: string | null | undefined): string | null {
|
|
69
|
+
if (!cwd) return null;
|
|
70
|
+
return createHash("sha256").update(cwd).digest("hex");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function redactReason(reason: string | null | undefined): string | null {
|
|
74
|
+
if (!reason) return reason ?? null;
|
|
75
|
+
return reason
|
|
76
|
+
.replace(/AKIA[0-9A-Z]{16}/g, "[REDACTED-AWS-KEY]")
|
|
77
|
+
.replace(/eyJ[A-Za-z0-9_=-]+\.[A-Za-z0-9_=-]+\.[A-Za-z0-9_=-]+/g, "[REDACTED-JWT]")
|
|
78
|
+
.replace(/ghp_[A-Za-z0-9]{36,}/g, "[REDACTED-GH-TOKEN]")
|
|
79
|
+
.replace(/sk-[A-Za-z0-9]{20,}/g, "[REDACTED-API-KEY]")
|
|
80
|
+
.replace(/Bearer\s+[A-Za-z0-9_.=+-]+/gi, "Bearer [REDACTED]");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function sanitize(entry: RawEntry): QueueEntry {
|
|
84
|
+
return {
|
|
85
|
+
client_event_id: randomUUID(),
|
|
86
|
+
timestamp: entry.timestamp,
|
|
87
|
+
event_type: entry.eventType,
|
|
88
|
+
tool_name: entry.toolName ?? null,
|
|
89
|
+
policy_name: entry.policyName ?? null,
|
|
90
|
+
policy_names: entry.policyNames ?? [],
|
|
91
|
+
decision: entry.decision,
|
|
92
|
+
reason: redactReason(entry.reason),
|
|
93
|
+
duration_ms: entry.durationMs,
|
|
94
|
+
session_id: entry.sessionId ?? null,
|
|
95
|
+
cwd_hash: hashCwd(entry.cwd),
|
|
96
|
+
permission_mode: entry.permissionMode ?? null,
|
|
97
|
+
hook_event_name: entry.hookEventName ?? null,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function ensureDir(): void {
|
|
102
|
+
if (!existsSync(QUEUE_DIR)) {
|
|
103
|
+
mkdirSync(QUEUE_DIR, { recursive: true, mode: 0o700 });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Hook-side API — append one event to the pending queue.
|
|
109
|
+
*
|
|
110
|
+
* Uses `appendFileSync` (O_APPEND) which is atomic for small writes, so
|
|
111
|
+
* concurrent hook processes interleave lines correctly without clobbering
|
|
112
|
+
* each other. Sanitizes sensitive fields before persisting.
|
|
113
|
+
*
|
|
114
|
+
* No-op cases (keeps hook path fast and safe):
|
|
115
|
+
* - User not logged in (no auth.json exists)
|
|
116
|
+
* - Queue file already exceeds MAX_QUEUE_BYTES (prevents unbounded growth)
|
|
117
|
+
*/
|
|
118
|
+
export function appendToServerQueue(entry: RawEntry): void {
|
|
119
|
+
if (!isLoggedIn()) return;
|
|
120
|
+
ensureDir();
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
if (existsSync(PENDING_FILE) && statSync(PENDING_FILE).size > MAX_QUEUE_BYTES) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
} catch {
|
|
127
|
+
// existsSync/statSync races are fine; proceed
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const sanitized = sanitize(entry);
|
|
131
|
+
appendFileSync(PENDING_FILE, JSON.stringify(sanitized) + "\n", { mode: 0o600 });
|
|
132
|
+
|
|
133
|
+
// Tighten perms on first create in case the umask allowed wider access
|
|
134
|
+
try {
|
|
135
|
+
chmodSync(PENDING_FILE, 0o600);
|
|
136
|
+
} catch {
|
|
137
|
+
// Windows or non-critical; skip
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function queueSizeBytes(): number {
|
|
142
|
+
try {
|
|
143
|
+
return statSync(PENDING_FILE).size;
|
|
144
|
+
} catch {
|
|
145
|
+
return 0;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
interface ClaimError extends Error {
|
|
150
|
+
code?: string;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Daemon-side API — atomically claim all pending events into a new
|
|
155
|
+
* processing file. Returns the processing file path, or null ONLY when
|
|
156
|
+
* there's nothing to claim (ENOENT). Other errors throw so we don't
|
|
157
|
+
* silently strand events.
|
|
158
|
+
*/
|
|
159
|
+
export function claimPendingBatch(): string | null {
|
|
160
|
+
if (!existsSync(PENDING_FILE)) return null;
|
|
161
|
+
try {
|
|
162
|
+
const size = statSync(PENDING_FILE).size;
|
|
163
|
+
if (size === 0) return null;
|
|
164
|
+
} catch {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const seq = `${Date.now()}-${process.pid}`;
|
|
169
|
+
const processingFile = join(QUEUE_DIR, `${PROCESSING_PREFIX}${seq}.jsonl`);
|
|
170
|
+
try {
|
|
171
|
+
renameSync(PENDING_FILE, processingFile);
|
|
172
|
+
try {
|
|
173
|
+
chmodSync(processingFile, 0o600);
|
|
174
|
+
} catch {
|
|
175
|
+
// non-critical
|
|
176
|
+
}
|
|
177
|
+
return processingFile;
|
|
178
|
+
} catch (err) {
|
|
179
|
+
const e = err as ClaimError;
|
|
180
|
+
if (e?.code === "ENOENT") return null;
|
|
181
|
+
// Real failure (EACCES, EIO, etc.) — surface to caller; don't lose events
|
|
182
|
+
throw err;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function findOrphanProcessingFiles(): string[] {
|
|
187
|
+
ensureDir();
|
|
188
|
+
try {
|
|
189
|
+
return readdirSync(QUEUE_DIR)
|
|
190
|
+
.filter((n) => n.startsWith(PROCESSING_PREFIX) && n.endsWith(".jsonl"))
|
|
191
|
+
.map((n) => join(QUEUE_DIR, n))
|
|
192
|
+
.sort();
|
|
193
|
+
} catch {
|
|
194
|
+
return [];
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Parse a processing file into structured events, skipping (and logging
|
|
200
|
+
* via stderr) any malformed JSON lines so one bad entry doesn't wedge
|
|
201
|
+
* the entire file forever.
|
|
202
|
+
*/
|
|
203
|
+
export function readProcessingFile(path: string): QueueEntry[] {
|
|
204
|
+
if (!existsSync(path)) return [];
|
|
205
|
+
const content = readFileSync(path, "utf8");
|
|
206
|
+
const out: QueueEntry[] = [];
|
|
207
|
+
for (const line of content.split("\n")) {
|
|
208
|
+
const trimmed = line.trim();
|
|
209
|
+
if (!trimmed) continue;
|
|
210
|
+
try {
|
|
211
|
+
out.push(JSON.parse(trimmed) as QueueEntry);
|
|
212
|
+
} catch {
|
|
213
|
+
// Skip malformed line — we can't recover it
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return out;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function deleteProcessingFile(path: string): void {
|
|
220
|
+
try {
|
|
221
|
+
unlinkSync(path);
|
|
222
|
+
} catch {
|
|
223
|
+
// best-effort; stale processing files are cleaned up on next run
|
|
224
|
+
}
|
|
225
|
+
}
|
package/bin/failproofai.mjs
CHANGED
|
@@ -57,6 +57,20 @@ if (hookIdx >= 0) {
|
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
// --relay-daemon — internal: long-running background process started by
|
|
61
|
+
// ensureRelayRunning(). Streams queued events to the server via WebSocket.
|
|
62
|
+
if (args.includes("--relay-daemon")) {
|
|
63
|
+
try {
|
|
64
|
+
const { runDaemon } = await import("../src/relay/daemon");
|
|
65
|
+
await runDaemon();
|
|
66
|
+
process.exit(0);
|
|
67
|
+
} catch (err) {
|
|
68
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
69
|
+
console.error(`Relay daemon error: ${msg}`);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
60
74
|
/**
|
|
61
75
|
* Centralised error handler for all CLI subcommands.
|
|
62
76
|
* CliError → clean message, no stack trace, exit exitCode (1 or 2)
|
|
@@ -64,7 +78,7 @@ if (hookIdx >= 0) {
|
|
|
64
78
|
*/
|
|
65
79
|
async function runCli() {
|
|
66
80
|
// --help / -h (only when not inside a subcommand that handles its own --help)
|
|
67
|
-
const SUBCOMMANDS = ["policies"];
|
|
81
|
+
const SUBCOMMANDS = ["policies", "login", "logout", "whoami", "relay", "sync"];
|
|
68
82
|
if ((args.includes("--help") || args.includes("-h")) && !SUBCOMMANDS.includes(args[0])) {
|
|
69
83
|
const extraArgs = args.filter((a) => a !== "--help" && a !== "-h");
|
|
70
84
|
if (extraArgs.length > 0) {
|
|
@@ -94,6 +108,12 @@ COMMANDS
|
|
|
94
108
|
|
|
95
109
|
policies --help, -h Show this help for the policies command
|
|
96
110
|
|
|
111
|
+
login Authenticate with the failproofai cloud (Google OAuth)
|
|
112
|
+
logout Clear local auth tokens and stop relay daemon
|
|
113
|
+
whoami Print current logged-in user
|
|
114
|
+
relay start|stop|status Manage the event relay daemon
|
|
115
|
+
sync One-shot flush of pending events to the server
|
|
116
|
+
|
|
97
117
|
--version, -v Print version and exit
|
|
98
118
|
--help, -h Show this help message
|
|
99
119
|
|
|
@@ -288,6 +308,73 @@ EXAMPLES
|
|
|
288
308
|
process.exit(0);
|
|
289
309
|
}
|
|
290
310
|
|
|
311
|
+
// login — authenticate with failproofai server via Google OAuth
|
|
312
|
+
if (args[0] === "login") {
|
|
313
|
+
const { login } = await import("../src/auth/login");
|
|
314
|
+
await login();
|
|
315
|
+
process.exit(0);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// logout — clear local tokens and stop relay daemon
|
|
319
|
+
if (args[0] === "logout") {
|
|
320
|
+
const { logout } = await import("../src/auth/logout");
|
|
321
|
+
await logout();
|
|
322
|
+
process.exit(0);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// whoami — print current user and auth status
|
|
326
|
+
if (args[0] === "whoami") {
|
|
327
|
+
const { whoami } = await import("../src/auth/logout");
|
|
328
|
+
whoami();
|
|
329
|
+
process.exit(0);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// relay start|stop|status — manage the event relay daemon
|
|
333
|
+
if (args[0] === "relay") {
|
|
334
|
+
const subcmd = args[1];
|
|
335
|
+
const { relayStatus, stopRelay } = await import("../src/relay/pid");
|
|
336
|
+
|
|
337
|
+
if (subcmd === "status") {
|
|
338
|
+
const s = relayStatus();
|
|
339
|
+
if (s.running) console.log(`Relay daemon running (pid ${s.pid})`);
|
|
340
|
+
else if (s.pid !== null) console.log(`Stale PID file (${s.pid}); daemon not running`);
|
|
341
|
+
else console.log("Relay daemon not running");
|
|
342
|
+
process.exit(0);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (subcmd === "stop") {
|
|
346
|
+
const stopped = stopRelay();
|
|
347
|
+
console.log(stopped ? "Relay daemon stopped" : "Relay daemon was not running");
|
|
348
|
+
process.exit(0);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (subcmd === "start") {
|
|
352
|
+
const { ensureRelayRunning, waitForRelayAlive } = await import("../src/relay/daemon");
|
|
353
|
+
ensureRelayRunning();
|
|
354
|
+
// Spawn is async — give the child a moment to write its PID file
|
|
355
|
+
const alive = await waitForRelayAlive();
|
|
356
|
+
const s = relayStatus();
|
|
357
|
+
if (alive && s.running) {
|
|
358
|
+
console.log(`Relay daemon started (pid ${s.pid})`);
|
|
359
|
+
process.exit(0);
|
|
360
|
+
}
|
|
361
|
+
console.log("Failed to start daemon");
|
|
362
|
+
process.exit(1);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
throw new CliError(
|
|
366
|
+
`Usage: failproofai relay <start|stop|status>`
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// sync — one-shot flush of pending events to server (fallback for no daemon)
|
|
371
|
+
if (args[0] === "sync") {
|
|
372
|
+
const { runOneShotSync } = await import("../src/relay/daemon");
|
|
373
|
+
const count = await runOneShotSync();
|
|
374
|
+
console.log(`Synced ${count} event${count === 1 ? "" : "s"} to server`);
|
|
375
|
+
process.exit(0);
|
|
376
|
+
}
|
|
377
|
+
|
|
291
378
|
// Unknown flag guard — must appear after all known-flag branches
|
|
292
379
|
const knownFlags = ["--version", "-v", "--help", "-h", "--hook"];
|
|
293
380
|
const unknownFlag = args.find(a => a.startsWith("-") && !knownFlags.includes(a));
|
|
@@ -306,7 +393,7 @@ EXAMPLES
|
|
|
306
393
|
return dp[m][n];
|
|
307
394
|
}
|
|
308
395
|
|
|
309
|
-
const primary = ["--version", "--help", "--hook", "policies"];
|
|
396
|
+
const primary = ["--version", "--help", "--hook", "policies", "login", "logout", "whoami", "relay", "sync"];
|
|
310
397
|
const closest = primary.reduce((best, flag) => {
|
|
311
398
|
const dist = levenshtein(unknownFlag, flag);
|
|
312
399
|
return dist < best.dist ? { flag, dist } : best;
|
|
@@ -319,8 +406,8 @@ EXAMPLES
|
|
|
319
406
|
);
|
|
320
407
|
}
|
|
321
408
|
|
|
322
|
-
// Unknown subcommand guard (non-flag args that aren't
|
|
323
|
-
const unknownSubcommand = args.find(a => !a.startsWith("-") && a
|
|
409
|
+
// Unknown subcommand guard (non-flag args that aren't a known subcommand)
|
|
410
|
+
const unknownSubcommand = args.find(a => !a.startsWith("-") && !SUBCOMMANDS.includes(a));
|
|
324
411
|
if (unknownSubcommand) {
|
|
325
412
|
throw new CliError(
|
|
326
413
|
`Unknown command: ${unknownSubcommand}\n` +
|