failproofai 0.0.6-beta.2 → 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/.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]__05akje6._.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]__0i5kvry._.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/{1052sguyd-.ka.js → 0-dm_9a6nsc2l.js} +1 -1
- package/.next/standalone/.next/static/chunks/{05j1px0r8yzh6.js → 01pmw1-asbek~.js} +2 -2
- package/.next/standalone/.next/static/chunks/{14cl9poem30dq.js → 051m32nx~n5yr.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0badv41uxa56..js → 0a-yctdwn368y.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0xpl.oscrakvx.js → 0l-mu4okl-cj1.js} +1 -1
- package/.next/standalone/.next/static/chunks/{00j0rr7rh8ef8.js → 0mazj-p-~2kc6.js} +1 -1
- package/.next/standalone/.next/static/chunks/0qakntsrpc~1j.js +6 -0
- package/.next/standalone/.next/static/chunks/{0npb~873.wvg3.js → 156zca6aewyr-.js} +1 -1
- package/.next/standalone/CHANGELOG.md +7 -0
- package/.next/standalone/bin/failproofai.mjs +91 -4
- package/.next/standalone/dist/cli.mjs +1155 -54
- package/.next/standalone/docs/ar/built-in-policies.mdx +118 -118
- package/.next/standalone/docs/built-in-policies.mdx +2 -2
- package/.next/standalone/docs/de/built-in-policies.mdx +48 -48
- package/.next/standalone/docs/es/built-in-policies.mdx +82 -82
- package/.next/standalone/docs/fr/built-in-policies.mdx +72 -72
- package/.next/standalone/docs/he/built-in-policies.mdx +129 -128
- package/.next/standalone/docs/hi/built-in-policies.mdx +178 -182
- package/.next/standalone/docs/it/built-in-policies.mdx +64 -64
- package/.next/standalone/docs/ja/built-in-policies.mdx +128 -128
- package/.next/standalone/docs/ko/built-in-policies.mdx +111 -111
- package/.next/standalone/docs/pt-br/built-in-policies.mdx +65 -65
- package/.next/standalone/docs/ru/built-in-policies.mdx +72 -72
- package/.next/standalone/docs/tr/built-in-policies.mdx +99 -99
- package/.next/standalone/docs/vi/built-in-policies.mdx +69 -72
- package/.next/standalone/docs/zh/built-in-policies.mdx +76 -78
- 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 +22 -20
- 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 +1155 -54
- 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 +22 -20
- 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/0ijk_kek9_wyx.js +0 -6
- /package/.next/standalone/.next/static/{A9pNTZdoYJTVyPAYwQMx5 → r-wX0MuAfCjbhJm3phQc8}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{A9pNTZdoYJTVyPAYwQMx5 → r-wX0MuAfCjbhJm3phQc8}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{A9pNTZdoYJTVyPAYwQMx5 → r-wX0MuAfCjbhJm3phQc8}/_ssgManifest.js +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: 内置策略
|
|
3
|
-
description: "
|
|
3
|
+
description: "30 条内置策略,可捕获常见的 Agent 故障模式"
|
|
4
4
|
icon: shield
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
failproofai 内置了 30 条策略,用于捕获常见的 Agent
|
|
7
|
+
failproofai 内置了 30 条策略,用于捕获常见的 Agent 故障模式。每条策略会在特定的 Hook 事件类型和工具名称上触发。其中九条策略支持参数配置,让你无需编写代码即可调整其行为。四条工作流策略在 Claude 停止前强制执行 commit → push → PR → CI 流水线。
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -12,8 +12,8 @@ failproofai 内置了 30 条策略,用于捕获常见的 Agent 故障模式。
|
|
|
12
12
|
|
|
13
13
|
策略按类别分组:
|
|
14
14
|
|
|
15
|
-
| 类别 | 策略 |
|
|
16
|
-
|
|
15
|
+
| 类别 | 策略 | Hook 类型 |
|
|
16
|
+
|------|------|-----------|
|
|
17
17
|
| [危险命令](#dangerous-commands) | block-sudo, block-rm-rf, block-curl-pipe-sh, block-failproofai-commands | PreToolUse |
|
|
18
18
|
| [密钥(清理器)](#secrets-sanitizers) | sanitize-jwt, sanitize-api-keys, sanitize-connection-strings, sanitize-private-key-content, sanitize-bearer-tokens | PostToolUse |
|
|
19
19
|
| [环境](#environment) | block-env-files, protect-env-vars | PreToolUse |
|
|
@@ -26,13 +26,13 @@ failproofai 内置了 30 条策略,用于捕获常见的 Agent 故障模式。
|
|
|
26
26
|
|
|
27
27
|
- **`block-`** — 阻止 Agent 继续执行。
|
|
28
28
|
- **`warn-`** — 为 Agent 提供额外上下文,使其能够自我纠正。
|
|
29
|
-
- **`sanitize-`** — 在 Agent
|
|
30
|
-
- **`require-`** —
|
|
29
|
+
- **`sanitize-`** — 在 Agent 看到工具输出之前,从中清除敏感数据。
|
|
30
|
+
- **`require-`** — 在条件满足之前阻断 Stop 事件。
|
|
31
31
|
|
|
32
32
|
---
|
|
33
33
|
|
|
34
34
|
<Tip>
|
|
35
|
-
|
|
35
|
+
每条策略都支持在 `policyParams` 中设置可选的 `hint` 字段。该提示会追加到 Claude 看到的 deny 或 instruct 消息中,提供可操作的指导,无需修改策略代码。适用于内置策略、自定义策略和约定策略。详见 [配置 → hint](/zh/configuration#hint-cross-cutting)。
|
|
36
36
|
</Tip>
|
|
37
37
|
|
|
38
38
|
---
|
|
@@ -44,15 +44,15 @@ failproofai 内置了 30 条策略,用于捕获常见的 Agent 故障模式。
|
|
|
44
44
|
### `block-sudo`
|
|
45
45
|
|
|
46
46
|
**事件:** PreToolUse (Bash)
|
|
47
|
-
**默认行为:**
|
|
47
|
+
**默认行为:** 拒绝所有包含 `sudo` 关键字的命令。
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
拦截包含 `sudo` 关键字的调用。模式匹配基于解析后的命令 token,而非原始字符串,以防止通过 Shell 操作符注入来绕过检测。
|
|
50
50
|
|
|
51
51
|
**参数:**
|
|
52
52
|
|
|
53
|
-
| 参数 | 类型 | 默认值 |
|
|
54
|
-
|
|
55
|
-
| `allowPatterns` | `string[]` | `[]` |
|
|
53
|
+
| 参数 | 类型 | 默认值 | 说明 |
|
|
54
|
+
|------|------|--------|------|
|
|
55
|
+
| `allowPatterns` | `string[]` | `[]` | 允许通过的精确命令前缀。每个条目与解析后的 argv token 进行匹配。 |
|
|
56
56
|
|
|
57
57
|
**示例:**
|
|
58
58
|
|
|
@@ -66,10 +66,10 @@ failproofai 内置了 30 条策略,用于捕获常见的 Agent 故障模式。
|
|
|
66
66
|
}
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
-
使用此配置,`sudo systemctl status nginx`
|
|
69
|
+
使用此配置,`sudo systemctl status nginx` 将被允许,而 `sudo rm /etc/hosts` 将被拒绝。
|
|
70
70
|
|
|
71
71
|
<Note>
|
|
72
|
-
|
|
72
|
+
模式匹配基于解析后的 token,而非原始命令字符串。这可以防止通过附加 Shell 操作符来绕过检测(例如,`sudo systemctl status x; rm -rf /` 不会匹配 `sudo systemctl status *`)。
|
|
73
73
|
</Note>
|
|
74
74
|
|
|
75
75
|
---
|
|
@@ -81,9 +81,9 @@ failproofai 内置了 30 条策略,用于捕获常见的 Agent 故障模式。
|
|
|
81
81
|
|
|
82
82
|
**参数:**
|
|
83
83
|
|
|
84
|
-
| 参数 | 类型 | 默认值 |
|
|
85
|
-
|
|
86
|
-
| `allowPaths` | `string[]` | `[]` |
|
|
84
|
+
| 参数 | 类型 | 默认值 | 说明 |
|
|
85
|
+
|------|------|--------|------|
|
|
86
|
+
| `allowPaths` | `string[]` | `[]` | 允许递归删除的安全路径(如 `/tmp`)。 |
|
|
87
87
|
|
|
88
88
|
**示例:**
|
|
89
89
|
|
|
@@ -111,20 +111,20 @@ failproofai 内置了 30 条策略,用于捕获常见的 Agent 故障模式。
|
|
|
111
111
|
### `block-failproofai-commands`
|
|
112
112
|
|
|
113
113
|
**事件:** PreToolUse (Bash)
|
|
114
|
-
**默认行为:** 拒绝可能卸载或禁用 failproofai
|
|
114
|
+
**默认行为:** 拒绝可能卸载或禁用 failproofai 自身的命令(如 `npm uninstall failproofai`、`failproofai policies --uninstall`)。
|
|
115
115
|
|
|
116
116
|
无参数。
|
|
117
117
|
|
|
118
118
|
---
|
|
119
119
|
|
|
120
|
-
## 密钥(清理器)
|
|
120
|
+
## 密钥(清理器){#secrets-sanitizers}
|
|
121
121
|
|
|
122
|
-
防止 Agent
|
|
122
|
+
防止 Agent 将凭证泄露到其上下文或输出中。清理器策略在 **PostToolUse** 事件上触发。当 Claude 运行 Bash 命令、读取文件或调用任何工具时,这些策略会在输出返回给 Claude 之前对其进行检查。若检测到密钥模式,策略将返回 deny 决策,阻止输出被传回。
|
|
123
123
|
|
|
124
124
|
### `sanitize-jwt`
|
|
125
125
|
|
|
126
126
|
**事件:** PostToolUse(所有工具)
|
|
127
|
-
**默认行为:** 脱敏 JWT
|
|
127
|
+
**默认行为:** 脱敏 JWT token(三段由 `.` 分隔的 base64url 字符串)。
|
|
128
128
|
|
|
129
129
|
无参数。
|
|
130
130
|
|
|
@@ -133,13 +133,13 @@ failproofai 内置了 30 条策略,用于捕获常见的 Agent 故障模式。
|
|
|
133
133
|
### `sanitize-api-keys`
|
|
134
134
|
|
|
135
135
|
**事件:** PostToolUse(所有工具)
|
|
136
|
-
**默认行为:** 脱敏常见 API
|
|
136
|
+
**默认行为:** 脱敏常见 API Key 格式:Anthropic(`sk-ant-`)、OpenAI(`sk-`)、GitHub PAT(`ghp_`)、AWS Access Key(`AKIA`)、Stripe Key(`sk_live_`、`sk_test_`)以及 Google API Key(`AIza`)。
|
|
137
137
|
|
|
138
138
|
**参数:**
|
|
139
139
|
|
|
140
|
-
| 参数 | 类型 | 默认值 |
|
|
141
|
-
|
|
142
|
-
| `additionalPatterns` | `{ regex: string; label: string }[]` | `[]` |
|
|
140
|
+
| 参数 | 类型 | 默认值 | 说明 |
|
|
141
|
+
|------|------|--------|------|
|
|
142
|
+
| `additionalPatterns` | `{ regex: string; label: string }[]` | `[]` | 额外需要视为密钥的正则表达式模式。 |
|
|
143
143
|
|
|
144
144
|
**示例:**
|
|
145
145
|
|
|
@@ -161,7 +161,7 @@ failproofai 内置了 30 条策略,用于捕获常见的 Agent 故障模式。
|
|
|
161
161
|
### `sanitize-connection-strings`
|
|
162
162
|
|
|
163
163
|
**事件:** PostToolUse(所有工具)
|
|
164
|
-
**默认行为:**
|
|
164
|
+
**默认行为:** 脱敏包含内嵌凭证的数据库连接字符串(如 `postgresql://user:password@host/db`)。
|
|
165
165
|
|
|
166
166
|
无参数。
|
|
167
167
|
|
|
@@ -179,7 +179,7 @@ failproofai 内置了 30 条策略,用于捕获常见的 Agent 故障模式。
|
|
|
179
179
|
### `sanitize-bearer-tokens`
|
|
180
180
|
|
|
181
181
|
**事件:** PostToolUse(所有工具)
|
|
182
|
-
**默认行为:** 脱敏 `Authorization: Bearer <token>`
|
|
182
|
+
**默认行为:** 脱敏 `Authorization: Bearer <token>` 请求头中长度大于等于 20 个字符的 token。
|
|
183
183
|
|
|
184
184
|
无参数。
|
|
185
185
|
|
|
@@ -187,14 +187,14 @@ failproofai 内置了 30 条策略,用于捕获常见的 Agent 故障模式。
|
|
|
187
187
|
|
|
188
188
|
## 环境 {#environment}
|
|
189
189
|
|
|
190
|
-
|
|
190
|
+
保护敏感的环境配置,防止 Agent 读取或暴露这些信息。
|
|
191
191
|
|
|
192
192
|
### `block-env-files`
|
|
193
193
|
|
|
194
194
|
**事件:** PreToolUse (Bash, Read)
|
|
195
|
-
**默认行为:** 拒绝通过 `cat .env
|
|
195
|
+
**默认行为:** 拒绝通过 `cat .env`、文件路径为 `.env` 的 `Read` 工具调用等方式读取 `.env` 文件。
|
|
196
196
|
|
|
197
|
-
|
|
197
|
+
不拦截 `.envrc` 或其他相关环境文件,仅拦截名称严格为 `.env` 的文件。
|
|
198
198
|
|
|
199
199
|
无参数。
|
|
200
200
|
|
|
@@ -211,7 +211,7 @@ failproofai 内置了 30 条策略,用于捕获常见的 Agent 故障模式。
|
|
|
211
211
|
|
|
212
212
|
## 文件访问 {#file-access}
|
|
213
213
|
|
|
214
|
-
|
|
214
|
+
将 Agent 限制在项目目录内,并使其远离敏感文件。
|
|
215
215
|
|
|
216
216
|
### `block-read-outside-cwd`
|
|
217
217
|
|
|
@@ -220,8 +220,8 @@ failproofai 内置了 30 条策略,用于捕获常见的 Agent 故障模式。
|
|
|
220
220
|
|
|
221
221
|
**参数:**
|
|
222
222
|
|
|
223
|
-
| 参数 | 类型 | 默认值 |
|
|
224
|
-
|
|
223
|
+
| 参数 | 类型 | 默认值 | 说明 |
|
|
224
|
+
|------|------|--------|------|
|
|
225
225
|
| `allowPaths` | `string[]` | `[]` | 即使在当前工作目录之外也允许访问的绝对路径前缀。 |
|
|
226
226
|
|
|
227
227
|
**示例:**
|
|
@@ -241,13 +241,13 @@ failproofai 内置了 30 条策略,用于捕获常见的 Agent 故障模式。
|
|
|
241
241
|
### `block-secrets-write`
|
|
242
242
|
|
|
243
243
|
**事件:** PreToolUse (Write, Edit)
|
|
244
|
-
**默认行为:**
|
|
244
|
+
**默认行为:** 拒绝写入常用于存储私钥和证书的文件:`id_rsa`、`id_ed25519`、`*.key`、`*.pem`、`*.p12`、`*.pfx`。
|
|
245
245
|
|
|
246
246
|
**参数:**
|
|
247
247
|
|
|
248
|
-
| 参数 | 类型 | 默认值 |
|
|
249
|
-
|
|
250
|
-
| `additionalPatterns` | `string[]` | `[]` |
|
|
248
|
+
| 参数 | 类型 | 默认值 | 说明 |
|
|
249
|
+
|------|------|--------|------|
|
|
250
|
+
| `additionalPatterns` | `string[]` | `[]` | 额外需要拦截的文件名模式(glob 风格)。 |
|
|
251
251
|
|
|
252
252
|
**示例:**
|
|
253
253
|
|
|
@@ -265,7 +265,7 @@ failproofai 内置了 30 条策略,用于捕获常见的 Agent 故障模式。
|
|
|
265
265
|
|
|
266
266
|
## Git {#git}
|
|
267
267
|
|
|
268
|
-
|
|
268
|
+
防止意外推送、强制推送以及难以撤销的分支操作失误。
|
|
269
269
|
|
|
270
270
|
### `block-push-master`
|
|
271
271
|
|
|
@@ -274,8 +274,8 @@ failproofai 内置了 30 条策略,用于捕获常见的 Agent 故障模式。
|
|
|
274
274
|
|
|
275
275
|
**参数:**
|
|
276
276
|
|
|
277
|
-
| 参数 | 类型 | 默认值 |
|
|
278
|
-
|
|
277
|
+
| 参数 | 类型 | 默认值 | 说明 |
|
|
278
|
+
|------|------|--------|------|
|
|
279
279
|
| `protectedBranches` | `string[]` | `["main", "master"]` | 不允许直接推送的分支名称。 |
|
|
280
280
|
|
|
281
281
|
**示例:**
|
|
@@ -291,7 +291,7 @@ failproofai 内置了 30 条策略,用于捕获常见的 Agent 故障模式。
|
|
|
291
291
|
```
|
|
292
292
|
|
|
293
293
|
<Tip>
|
|
294
|
-
|
|
294
|
+
若要允许推送到所有分支(即在不将其从 `enabledPolicies` 中移除的情况下有效禁用此策略),可将 `protectedBranches` 设为 `[]`。
|
|
295
295
|
</Tip>
|
|
296
296
|
|
|
297
297
|
---
|
|
@@ -303,8 +303,8 @@ failproofai 内置了 30 条策略,用于捕获常见的 Agent 故障模式。
|
|
|
303
303
|
|
|
304
304
|
**参数:**
|
|
305
305
|
|
|
306
|
-
| 参数 | 类型 | 默认值 |
|
|
307
|
-
|
|
306
|
+
| 参数 | 类型 | 默认值 | 说明 |
|
|
307
|
+
|------|------|--------|------|
|
|
308
308
|
| `protectedBranches` | `string[]` | `["main", "master"]` | 不允许直接切换的分支名称。 |
|
|
309
309
|
|
|
310
310
|
---
|
|
@@ -314,7 +314,7 @@ failproofai 内置了 30 条策略,用于捕获常见的 Agent 故障模式。
|
|
|
314
314
|
**事件:** PreToolUse (Bash)
|
|
315
315
|
**默认行为:** 拒绝 `git push --force` 和 `git push -f`。
|
|
316
316
|
|
|
317
|
-
|
|
317
|
+
无策略特定参数。可使用通用 [`hint`](/zh/configuration#hint-cross-cutting) 建议替代方案:
|
|
318
318
|
|
|
319
319
|
```json
|
|
320
320
|
{
|
|
@@ -331,7 +331,7 @@ failproofai 内置了 30 条策略,用于捕获常见的 Agent 故障模式。
|
|
|
331
331
|
### `warn-git-amend`
|
|
332
332
|
|
|
333
333
|
**事件:** PreToolUse (Bash)
|
|
334
|
-
**默认行为:**
|
|
334
|
+
**默认行为:** 在运行 `git commit --amend` 时指示 Claude 谨慎操作。不阻止该命令执行。
|
|
335
335
|
|
|
336
336
|
无参数。
|
|
337
337
|
|
|
@@ -340,7 +340,7 @@ failproofai 内置了 30 条策略,用于捕获常见的 Agent 故障模式。
|
|
|
340
340
|
### `warn-git-stash-drop`
|
|
341
341
|
|
|
342
342
|
**事件:** PreToolUse (Bash)
|
|
343
|
-
**默认行为:** 在运行 `git stash drop`
|
|
343
|
+
**默认行为:** 在运行 `git stash drop` 前指示 Claude 进行确认。不阻止该命令执行。
|
|
344
344
|
|
|
345
345
|
无参数。
|
|
346
346
|
|
|
@@ -349,7 +349,7 @@ failproofai 内置了 30 条策略,用于捕获常见的 Agent 故障模式。
|
|
|
349
349
|
### `warn-all-files-staged`
|
|
350
350
|
|
|
351
351
|
**事件:** PreToolUse (Bash)
|
|
352
|
-
**默认行为:**
|
|
352
|
+
**默认行为:** 在运行 `git add -A` 或 `git add .` 时指示 Claude 检查其所暂存的内容。不阻止该命令执行。
|
|
353
353
|
|
|
354
354
|
无参数。
|
|
355
355
|
|
|
@@ -357,7 +357,7 @@ failproofai 内置了 30 条策略,用于捕获常见的 Agent 故障模式。
|
|
|
357
357
|
|
|
358
358
|
## 数据库 {#database}
|
|
359
359
|
|
|
360
|
-
在破坏性 SQL
|
|
360
|
+
在破坏性 SQL 操作对数据库执行之前将其拦截。
|
|
361
361
|
|
|
362
362
|
### `warn-destructive-sql`
|
|
363
363
|
|
|
@@ -379,7 +379,7 @@ failproofai 内置了 30 条策略,用于捕获常见的 Agent 故障模式。
|
|
|
379
379
|
|
|
380
380
|
## 警告 {#warnings}
|
|
381
381
|
|
|
382
|
-
|
|
382
|
+
在执行潜在有风险但非破坏性的操作前,为 Agent 提供额外上下文。
|
|
383
383
|
|
|
384
384
|
### `warn-large-file-write`
|
|
385
385
|
|
|
@@ -388,8 +388,8 @@ failproofai 内置了 30 条策略,用于捕获常见的 Agent 故障模式。
|
|
|
388
388
|
|
|
389
389
|
**参数:**
|
|
390
390
|
|
|
391
|
-
| 参数 | 类型 | 默认值 |
|
|
392
|
-
|
|
391
|
+
| 参数 | 类型 | 默认值 | 说明 |
|
|
392
|
+
|------|------|--------|------|
|
|
393
393
|
| `thresholdKb` | `number` | `1024` | 触发警告的文件大小阈值(单位:KB)。 |
|
|
394
394
|
|
|
395
395
|
**示例:**
|
|
@@ -405,7 +405,7 @@ failproofai 内置了 30 条策略,用于捕获常见的 Agent 故障模式。
|
|
|
405
405
|
```
|
|
406
406
|
|
|
407
407
|
<Note>
|
|
408
|
-
|
|
408
|
+
Hook 处理器对 stdin 负载强制执行 1 MB 的大小限制。若要使用较小内容测试此策略,请将 `thresholdKb` 设为远低于 1024 的值。
|
|
409
409
|
</Note>
|
|
410
410
|
|
|
411
411
|
---
|
|
@@ -422,7 +422,7 @@ failproofai 内置了 30 条策略,用于捕获常见的 Agent 故障模式。
|
|
|
422
422
|
### `warn-background-process`
|
|
423
423
|
|
|
424
424
|
**事件:** PreToolUse (Bash)
|
|
425
|
-
**默认行为:**
|
|
425
|
+
**默认行为:** 在通过 `nohup`、`&`、`disown` 或 `screen` 启动后台进程时,指示 Claude 谨慎操作。
|
|
426
426
|
|
|
427
427
|
无参数。
|
|
428
428
|
|
|
@@ -431,7 +431,7 @@ failproofai 内置了 30 条策略,用于捕获常见的 Agent 故障模式。
|
|
|
431
431
|
### `warn-global-package-install`
|
|
432
432
|
|
|
433
433
|
**事件:** PreToolUse (Bash)
|
|
434
|
-
**默认行为:** 在运行 `npm install -g`、`yarn global add`
|
|
434
|
+
**默认行为:** 在运行 `npm install -g`、`yarn global add` 或未在虚拟环境中执行的 `pip install` 之前,指示 Claude 进行确认。
|
|
435
435
|
|
|
436
436
|
无参数。
|
|
437
437
|
|
|
@@ -439,23 +439,23 @@ failproofai 内置了 30 条策略,用于捕获常见的 Agent 故障模式。
|
|
|
439
439
|
|
|
440
440
|
## 包管理器 {#package-managers}
|
|
441
441
|
|
|
442
|
-
|
|
442
|
+
限制 Agent 只能使用指定的包管理器。
|
|
443
443
|
|
|
444
444
|
### `prefer-package-manager`
|
|
445
445
|
|
|
446
446
|
**事件:** PreToolUse (Bash)
|
|
447
|
-
**默认行为:**
|
|
447
|
+
**默认行为:** 默认禁用。启用后,将拦截不在 `allowed` 列表中的任何包管理器命令,并告知 Claude 使用允许的包管理器重写该命令。
|
|
448
448
|
|
|
449
449
|
可检测的包管理器:pip, pip3, python -m pip, npm, npx, yarn, pnpm, pnpx, bun, bunx, uv, poetry, pipenv, conda, cargo。
|
|
450
450
|
|
|
451
|
-
| 参数 | 类型 | 默认值 |
|
|
452
|
-
|
|
453
|
-
| `allowed` | string[] | `[]` |
|
|
454
|
-
| `blocked` | string[] | `[]` |
|
|
451
|
+
| 参数 | 类型 | 默认值 | 说明 |
|
|
452
|
+
|------|------|--------|------|
|
|
453
|
+
| `allowed` | string[] | `[]` | 允许使用的包管理器名称。检测到的管理器若不在此列表中将被拦截。列表为空时,策略不生效。 |
|
|
454
|
+
| `blocked` | string[] | `[]` | 除内置列表之外需要额外拦截的包管理器名称(如 `['pdm', 'pipx']`)。 |
|
|
455
455
|
|
|
456
|
-
|
|
456
|
+
内置拦截列表包含:pip, pip3, npm, npx, yarn, pnpm, pnpx, bun, bunx, uv, poetry, pipenv, conda, cargo。使用 `blocked` 可追加不在此列表中的包管理器。
|
|
457
457
|
|
|
458
|
-
|
|
458
|
+
**示例配置:**
|
|
459
459
|
|
|
460
460
|
```json
|
|
461
461
|
{
|
|
@@ -469,18 +469,18 @@ failproofai 内置了 30 条策略,用于捕获常见的 Agent 故障模式。
|
|
|
469
469
|
}
|
|
470
470
|
```
|
|
471
471
|
|
|
472
|
-
使用此配置,`pip install flask` 和 `pdm install flask`
|
|
472
|
+
使用此配置,`pip install flask` 和 `pdm install flask` 均会被拒绝,并告知 Claude 改用 `uv` 或 `bun`。而 `uv pip install flask` 则会被允许,因为 `uv` 在允许列表中且优先检查。
|
|
473
473
|
|
|
474
474
|
---
|
|
475
475
|
|
|
476
476
|
## AI 行为
|
|
477
477
|
|
|
478
|
-
检测 Agent
|
|
478
|
+
检测 Agent 陷入循环或行为异常的情况。
|
|
479
479
|
|
|
480
480
|
### `warn-repeated-tool-calls`
|
|
481
481
|
|
|
482
482
|
**事件:** PreToolUse(所有工具)
|
|
483
|
-
**默认行为:**
|
|
483
|
+
**默认行为:** 当同一工具以相同参数被调用 3 次或以上时,指示 Claude 重新考虑——这通常是 Agent 陷入循环的信号。
|
|
484
484
|
|
|
485
485
|
无参数。
|
|
486
486
|
|
|
@@ -488,14 +488,14 @@ failproofai 内置了 30 条策略,用于捕获常见的 Agent 故障模式。
|
|
|
488
488
|
|
|
489
489
|
## 工作流 {#workflow}
|
|
490
490
|
|
|
491
|
-
|
|
491
|
+
强制执行有纪律的会话结束工作流。这些策略在 **Stop** 事件上触发,在每个条件满足之前拒绝 Claude 停止。它们遵循自然的依赖链:commit → push → PR → CI。若某条策略拒绝,则链中后续策略将被跳过(deny 短路)。
|
|
492
492
|
|
|
493
|
-
|
|
493
|
+
所有工作流策略均为**失败开放(fail-open)**模式:如果所需工具不可用(如 `gh` 未安装、无 git 远程仓库),策略将放行并附带说明性消息,解释跳过检查的原因。
|
|
494
494
|
|
|
495
495
|
### `require-commit-before-stop`
|
|
496
496
|
|
|
497
497
|
**事件:** Stop
|
|
498
|
-
**默认行为:**
|
|
498
|
+
**默认行为:** 当存在未提交的更改(已修改、已暂存或未跟踪的文件)时,拒绝停止。当工作目录干净时,返回说明性消息。
|
|
499
499
|
|
|
500
500
|
无参数。
|
|
501
501
|
|
|
@@ -504,12 +504,12 @@ failproofai 内置了 30 条策略,用于捕获常见的 Agent 故障模式。
|
|
|
504
504
|
### `require-push-before-stop`
|
|
505
505
|
|
|
506
506
|
**事件:** Stop
|
|
507
|
-
**默认行为:**
|
|
507
|
+
**默认行为:** 当存在未推送的 commit,或当前分支没有远程追踪分支时,拒绝停止。若需要,建议使用 `git push -u` 创建追踪分支。如果未配置远程仓库,则失败开放。
|
|
508
508
|
|
|
509
509
|
**参数:**
|
|
510
510
|
|
|
511
|
-
| 参数 | 类型 | 默认值 |
|
|
512
|
-
|
|
511
|
+
| 参数 | 类型 | 默认值 | 说明 |
|
|
512
|
+
|------|------|--------|------|
|
|
513
513
|
| `remote` | `string` | `"origin"` | 推送目标的远程仓库名称。 |
|
|
514
514
|
|
|
515
515
|
**示例:**
|
|
@@ -534,8 +534,7 @@ failproofai 内置了 30 条策略,用于捕获常见的 Agent 故障模式。
|
|
|
534
534
|
无参数。
|
|
535
535
|
|
|
536
536
|
<Note>
|
|
537
|
-
|
|
538
|
-
运行 `gh auth login` 时,请使用具有 `repo` 权限(用于读取 Pull Request)的个人访问令牌。若 `gh` 未安装或未通过身份验证,该策略会失败开放并向 Claude 报告原因。
|
|
537
|
+
此策略需要安装并已完成身份验证的 [GitHub CLI](https://cli.github.com/)(`gh`)。请使用具有 `repo` 作用域(用于读取 Pull Request)的个人访问 token 运行 `gh auth login`。如果 `gh` 未安装或未完成身份验证,策略将失败开放并向 Claude 报告原因。
|
|
539
538
|
</Note>
|
|
540
539
|
|
|
541
540
|
---
|
|
@@ -543,13 +542,12 @@ failproofai 内置了 30 条策略,用于捕获常见的 Agent 故障模式。
|
|
|
543
542
|
### `require-ci-green-before-stop`
|
|
544
543
|
|
|
545
544
|
**事件:** Stop
|
|
546
|
-
**默认行为:**
|
|
545
|
+
**默认行为:** 当当前分支上的 CI 检查失败或仍在运行时,拒绝停止。同时检查 GitHub Actions 工作流运行状态和第三方 Bot 检查(如 CodeRabbit、SonarCloud、Codecov)。将 `skipped` 和 `cancelled` 结论视为成功。所有检查通过时,返回说明性消息。
|
|
547
546
|
|
|
548
547
|
无参数。
|
|
549
548
|
|
|
550
549
|
<Note>
|
|
551
|
-
|
|
552
|
-
运行 `gh auth login` 时,请使用具有 `repo` 权限(用于读取 Actions 工作流运行和 Checks API)的个人访问令牌。若 `gh` 未安装或未通过身份验证,该策略会失败开放并向 Claude 报告原因。
|
|
550
|
+
此策略需要安装并已完成身份验证的 [GitHub CLI](https://cli.github.com/)(`gh`)。请使用具有 `repo` 作用域(用于读取 Actions 工作流运行状态和 Checks API)的个人访问 token 运行 `gh auth login`。如果 `gh` 未安装或未完成身份验证,策略将失败开放并向 Claude 报告原因。
|
|
553
551
|
</Note>
|
|
554
552
|
|
|
555
553
|
---
|
|
@@ -558,7 +556,7 @@ failproofai 内置了 30 条策略,用于捕获常见的 Agent 故障模式。
|
|
|
558
556
|
|
|
559
557
|
## 禁用单条策略
|
|
560
558
|
|
|
561
|
-
从配置文件的 `enabledPolicies`
|
|
559
|
+
从配置文件的 `enabledPolicies` 中移除特定策略,或在控制台的策略标签页中将其关闭。
|
|
562
560
|
|
|
563
561
|
```json
|
|
564
562
|
{
|
|
@@ -569,4 +567,4 @@ failproofai 内置了 30 条策略,用于捕获常见的 Agent 故障模式。
|
|
|
569
567
|
}
|
|
570
568
|
```
|
|
571
569
|
|
|
572
|
-
|
|
570
|
+
未在 `enabledPolicies` 中列出的策略不会运行,即使 `policyParams` 中存在对应的条目也不例外。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "failproofai",
|
|
3
|
-
"version": "0.0.6-beta.
|
|
3
|
+
"version": "0.0.6-beta.3",
|
|
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"
|
|
@@ -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":"0.0.6-beta.
|
|
12
|
+
const nextConfig = {"env":{"NEXT_PUBLIC_APP_VERSION":"0.0.6-beta.3"},"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"],"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","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":false,"useSkewCookie":false,"cssChunking":true,"multiZoneDraftMode":false,"appNavFailHandling":false,"prerenderEarlyExit":true,"serverMinification":true,"linkNoTouchStart":false,"caseSensitiveRoutes":false,"cachedNavigations":false,"partialFallbacks":false,"dynamicOnHover":false,"varyParams":false,"prefetchInlining":false,"preloadEntriesOnStart":true,"clientRouterFilter":true,"clientRouterFilterRedirects":false,"fetchCacheKeyPrefix":"","proxyPrefetch":"flexible","optimisticClientCache":true,"manualClientBasePath":false,"cpus":3,"memoryBasedWorkersCount":false,"imgOptConcurrency":null,"imgOptTimeoutInSeconds":7,"imgOptMaxInputPixels":268402689,"imgOptSequentialRead":null,"imgOptSkipMetadata":null,"isrFlushToDisk":true,"workerThreads":false,"optimizeCss":false,"nextScriptWorkers":false,"scrollRestoration":false,"externalDir":false,"disableOptimizedLoading":false,"gzipSize":true,"craCompat":false,"esmExternals":true,"fullySpecified":false,"swcTraceProfiling":false,"forceSwcTransforms":false,"largePageDataBytes":128000,"typedEnv":false,"parallelServerCompiles":false,"parallelServerBuildTraces":false,"ppr":false,"authInterrupts":false,"webpackMemoryOptimizations":false,"optimizeServerReact":true,"strictRouteTypes":false,"viewTransition":false,"removeUncaughtErrorAndRejectionListeners":false,"validateRSCRequestHeaders":false,"staleTimes":{"dynamic":0,"static":300},"reactDebugChannel":true,"serverComponentsHmrCache":true,"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":false,"turbopackInferModuleSideEffects":true,"turbopackPluginRuntimeStrategy":"childProcesses","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"],"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","turbopack":{"root":"/home/runner/work/failproofai/failproofai"},"distDirRoot":".next"}
|
|
13
13
|
|
|
14
14
|
process.env.__NEXT_PRIVATE_STANDALONE_CONFIG = JSON.stringify(nextConfig)
|
|
15
15
|
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { platform } from "node:os";
|
|
3
|
+
import { writeTokens, type AuthTokens } from "./token-store";
|
|
4
|
+
|
|
5
|
+
const DEFAULT_SERVER_URL = process.env.FAILPROOFAI_SERVER_URL ?? "https://api.befailproof.ai";
|
|
6
|
+
const HTTP_TIMEOUT_MS = 10_000;
|
|
7
|
+
|
|
8
|
+
interface DeviceCodeResponse {
|
|
9
|
+
device_code: string;
|
|
10
|
+
user_code: string;
|
|
11
|
+
verification_url: string;
|
|
12
|
+
expires_in: number;
|
|
13
|
+
interval: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface TokenResponse {
|
|
17
|
+
access_token: string;
|
|
18
|
+
refresh_token: string;
|
|
19
|
+
expires_in: number;
|
|
20
|
+
user: { id: string; email: string; name?: string };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function openBrowser(url: string): void {
|
|
24
|
+
const os = platform();
|
|
25
|
+
try {
|
|
26
|
+
if (os === "darwin") {
|
|
27
|
+
spawn("open", [url], { detached: true, stdio: "ignore" }).unref();
|
|
28
|
+
} else if (os === "win32") {
|
|
29
|
+
// On cmd's `start`, the first quoted token is treated as a window
|
|
30
|
+
// title. Pass an empty title so URLs containing "&" or spaces are
|
|
31
|
+
// interpreted as the target, not the title.
|
|
32
|
+
spawn("cmd", ["/c", "start", "", url], { detached: true, stdio: "ignore" }).unref();
|
|
33
|
+
} else {
|
|
34
|
+
spawn("xdg-open", [url], { detached: true, stdio: "ignore" }).unref();
|
|
35
|
+
}
|
|
36
|
+
} catch {
|
|
37
|
+
// Fallback: the URL is already printed above.
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function postJson<T>(url: string, body: unknown, timeoutMs = HTTP_TIMEOUT_MS): Promise<T> {
|
|
42
|
+
const resp = await fetch(url, {
|
|
43
|
+
method: "POST",
|
|
44
|
+
headers: { "Content-Type": "application/json" },
|
|
45
|
+
body: JSON.stringify(body),
|
|
46
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
47
|
+
});
|
|
48
|
+
if (!resp.ok) {
|
|
49
|
+
throw new Error(`${url} → ${resp.status} ${resp.statusText}`);
|
|
50
|
+
}
|
|
51
|
+
return (await resp.json()) as T;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function login(): Promise<void> {
|
|
55
|
+
const serverUrl = DEFAULT_SERVER_URL;
|
|
56
|
+
|
|
57
|
+
console.log("Requesting device code...");
|
|
58
|
+
const dc = await postJson<DeviceCodeResponse>(`${serverUrl}/api/v1/auth/device-code`, {});
|
|
59
|
+
|
|
60
|
+
console.log(`\n Open this URL in your browser (will be opened automatically):`);
|
|
61
|
+
console.log(` ${dc.verification_url}\n`);
|
|
62
|
+
console.log(` Your code: ${dc.user_code}\n`);
|
|
63
|
+
|
|
64
|
+
openBrowser(dc.verification_url);
|
|
65
|
+
|
|
66
|
+
const deadline = Date.now() + dc.expires_in * 1000;
|
|
67
|
+
const intervalMs = dc.interval * 1000;
|
|
68
|
+
|
|
69
|
+
while (Date.now() < deadline) {
|
|
70
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
71
|
+
try {
|
|
72
|
+
const result = await postJson<TokenResponse | { status: string }>(
|
|
73
|
+
`${serverUrl}/api/v1/auth/device-token`,
|
|
74
|
+
{ device_code: dc.device_code },
|
|
75
|
+
);
|
|
76
|
+
if ("access_token" in result) {
|
|
77
|
+
const tokens: AuthTokens = {
|
|
78
|
+
access_token: result.access_token,
|
|
79
|
+
refresh_token: result.refresh_token,
|
|
80
|
+
expires_at: Math.floor(Date.now() / 1000) + result.expires_in,
|
|
81
|
+
user_email: result.user.email,
|
|
82
|
+
user_id: result.user.id,
|
|
83
|
+
server_url: serverUrl,
|
|
84
|
+
};
|
|
85
|
+
writeTokens(tokens);
|
|
86
|
+
console.log(`Logged in as ${result.user.email}`);
|
|
87
|
+
|
|
88
|
+
// Auto-start relay daemon
|
|
89
|
+
try {
|
|
90
|
+
const { ensureRelayRunning } = await import("../relay/daemon");
|
|
91
|
+
ensureRelayRunning();
|
|
92
|
+
console.log("Relay daemon started.");
|
|
93
|
+
} catch (e) {
|
|
94
|
+
console.warn("Failed to auto-start relay daemon:", e);
|
|
95
|
+
}
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
} catch {
|
|
99
|
+
// Pending or transient error — keep polling
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
throw new Error("Login timed out. Run `failproofai login` again.");
|
|
104
|
+
}
|