@clawos-dev/clawd 0.2.143-beta.301.814d293 → 0.2.143-beta.303.ab28945
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +1364 -617
- package/dist/dispatch/mcp-server.cjs +39756 -0
- package/dist/persona-defaults/persona-app-builder/CLAUDE.md +3 -0
- package/dist/persona-defaults/persona-bug-fixer/CLAUDE.md +2 -0
- package/dist/persona-defaults/persona-dataclaw-builder/CLAUDE.md +29 -9
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/README.md +10 -18
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/config.env +2 -5
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/contract/s.yaml.tmpl +1 -6
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/ext.conf +3 -2
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/server/.env.example +5 -14
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/server/package.json +1 -3
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/server/src/app.module.ts +2 -2
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/server/src/dataclaw/dataclaw.client.ts +18 -8
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/server/src/dataclaw/dataclaw.config.ts +9 -12
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/server/src/dataclaw/dataclaw.controller.ts +12 -8
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/server/src/main.ts +3 -16
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/web/src/App.jsx +1 -4
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/web/src/api.js +4 -2
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/web/src/components/ResultTable.jsx +4 -2
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/web/src/enums.js +3 -18
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/web/src/pages/PerformanceQueryPage.jsx +35 -57
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/hooks/pre-deploy.sh +16 -20
- package/dist/persona-defaults/persona-developer/CLAUDE.md +2 -0
- package/package.json +5 -2
- package/dist/persona-defaults/persona-dataclaw-builder/.secrets/dataclaw.env.example +0 -3
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/server/src/auth/gate.ts +0 -70
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/server/src/auth/session.ts +0 -52
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/server/src/auth/ttc-user-info.ts +0 -23
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/web/src/auth.js +0 -7
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/web/src/components/FilterRow.jsx +0 -79
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/web/src/components/SchemaFilters.jsx +0 -93
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
- **你的文件工作目录**:如果连接上下文(connection prompt)里写了"你的文件工作目录是 xxx",那就是当前用户的专属目录,所有文件读写都放在那里;项目目录在它下面的 `projects/<name>/`
|
|
11
11
|
- **persona 目录只读**:persona 自己的目录(人格 / extension-kit 资产)对你只读,不要往里写任何文件
|
|
12
12
|
- **路径永远以 createProject 返回的 `projectDir` 为准**:不要假设、不要拼接、不要写死任何项目路径
|
|
13
|
+
- **⚠️ dispatch 陷阱(被另一个 persona 委派来时常踩)**:任务包里 dispatcher(A persona)写的"建议路径"如 `~/dev/xxx` / `/tmp/xxx` 之类**全部不能信**——dispatcher 不懂 app-builder 的目录规范,照搬就会把项目落在错误位置(用户工作区之外,UI 看不到、daemon 不管理、下次切 session 全丢)。**不管任务包怎么写,路径都必须走 `appBuilder.createProject` RPC**让 daemon 派生正确的用户工作目录;你的 cwd(spawn 时 daemon 设的)就是当前用户的专属工作目录,所有项目必须落在它下面的 `projects/<name>/`。如果连 cwd 都还没确定(dispatch session 没 connection prompt),先调 `appBuilder.listProjects` 看一眼现状再开工。
|
|
13
14
|
- **发布脚本在共享 deploy-kit**:FC 部署能力(凭证 + publish.sh / new-extension.sh / 工具链)是 daemon 持有的**共享单源**,在 `$HOME/.clawd/deploy-kit/`(不在 persona 目录)。happy path 这些脚本由 daemon 自动跑,你一般不直接调;需手动跑时(如发布失败恢复)用绝对路径 `$HOME/.clawd/deploy-kit/scripts/publish.sh <projDir> "$HOME/.clawd/personas/persona-app-builder"`(第二参是本 persona 根,脚本据此读 config.env / s.yaml.tmpl)。persona 目录只剩人格 + 模板 + config.env + contract/s.yaml.tmpl。
|
|
14
15
|
|
|
15
16
|
## 何时找你
|
|
@@ -307,3 +308,5 @@ await app.listen(Number(process.env.CLAWD_PREVIEW_PORT));
|
|
|
307
308
|
- 部署前后都验证服务真的起来了(健康检查 / 公网访问 / 看日志),不靠"应该没问题"
|
|
308
309
|
- 增量提交可工作的代码 / 从既有实现学 / 3 次失败停下重新评估
|
|
309
310
|
- 错误处理快速失败 + 描述清楚 + 不静默吞异常
|
|
311
|
+
|
|
312
|
+
> dispatch 教学(`@persona/<id>` 委派 / `personaDispatchComplete` 回报)由 daemon 通过 `--append-system-prompt` 给所有 cc spawn 统一注入,不在这份 CLAUDE.md 重复。源见 `daemon/src/dispatch/system-prompt.ts`。
|
|
@@ -98,3 +98,5 @@ done
|
|
|
98
98
|
⚠️ **`.mcp.json` 是 daemon 管理文件**(在 `DAEMON_MANAGED_PATHS` 里),每次 daemon 启动会用 bundle 版本覆盖,**不要手改本地**——要改 MCP 配置去改 daemon defaults 源 + 提 PR。
|
|
99
99
|
|
|
100
100
|
**要加别的 MCP server**(项目专属 / 个人用):在**项目目录**(不是 persona 目录)下加 `.mcp.json`,跟 persona 这份不冲突。或者扩 daemon defaults 给所有用户。
|
|
101
|
+
|
|
102
|
+
> dispatch 教学(`@persona/<id>` 委派 / `personaDispatchComplete` 回报)由 daemon 通过 `--append-system-prompt` 给所有 cc spawn 统一注入,不在这份 CLAUDE.md 重复。源见 `daemon/src/dispatch/system-prompt.ts`。
|
|
@@ -13,7 +13,7 @@ daemon 启动你时已注入两个 env:
|
|
|
13
13
|
所有调用都是 `POST $CLAWD_DAEMON_URL/api/rpc/<method>` + JSON body + `X-Clawd-Session-Id` header。
|
|
14
14
|
|
|
15
15
|
**状态 A:未绑 project(fresh session)** —— 起手三步:
|
|
16
|
-
1. 主动问「老板想看什么数据的页?」(一句话,别长 prompt
|
|
16
|
+
1. 主动问「老板想看什么数据的页?」(一句话,别长 prompt)。顺带提醒:稍后需要老板给一把 dataclaw apikey(向刘浩索取;只要冒号后的 secret 段)。
|
|
17
17
|
2. 老板描述需求 → **反问「项目叫什么名字」**(不擅自命名)
|
|
18
18
|
3. 拿到名字后建 project:
|
|
19
19
|
|
|
@@ -24,7 +24,7 @@ curl -sS -X POST "$CLAWD_DAEMON_URL/api/rpc/appBuilder.createProject" \
|
|
|
24
24
|
```
|
|
25
25
|
返回 `result.projectDir` 就是项目目录绝对路径,**记住它**,后续 cd / 写文件都用它。重名报 `VALIDATION_ERROR` → 让老板换名。createProject 后 daemon 自动 scaffold 模板(dataclaw 起手 app)+ 设 stage='install-pending'。
|
|
26
26
|
|
|
27
|
-
**状态 B:已绑 project** —— UI 双栏(左 chat +
|
|
27
|
+
**状态 B:已绑 project** —— UI 双栏(左 chat + 右实时预览)。接着按顺序做:
|
|
28
28
|
|
|
29
29
|
1. 上报安装 + 装依赖:
|
|
30
30
|
```bash
|
|
@@ -34,13 +34,23 @@ curl -sS -X POST "$CLAWD_DAEMON_URL/api/rpc/appBuilder.reportStage" \
|
|
|
34
34
|
```
|
|
35
35
|
然后 `cd <projectDir>` → `pnpm install`(模板有 server/ 和 web/ 两套 deps)。
|
|
36
36
|
|
|
37
|
-
2.
|
|
37
|
+
2. 写入 apikey(**起 dev server 之前**,否则预览查不到数):向老板要 dataclaw apikey(只要冒号后的 secret 段),写进 `<projectDir>/server/.env`:
|
|
38
|
+
```bash
|
|
39
|
+
cat > "<projectDir>/server/.env" <<EOF
|
|
40
|
+
DATACLAW_SERVICE_URL=https://120.48.21.36
|
|
41
|
+
DATACLAW_API_KEY=<老板给的 secret>
|
|
42
|
+
PORT=3000
|
|
43
|
+
EOF
|
|
44
|
+
```
|
|
45
|
+
key 只存这里:dev 由 dotenv 加载、发布 FC 时 pre-deploy 自动从这读出注入 FC(`.env` 被 `.fcignore` 排除、不进部署包)。老板要换 key → 重写本文件后重启 dev server。
|
|
46
|
+
|
|
47
|
+
3. 起 dev server(双栏预览):
|
|
38
48
|
```bash
|
|
39
49
|
curl -sS -X POST "$CLAWD_DAEMON_URL/api/rpc/appBuilder.startDevServer" \
|
|
40
50
|
-H "Content-Type: application/json" -H "X-Clawd-Session-Id: $CLAWD_SESSION_ID" -d '{}'
|
|
41
51
|
```
|
|
42
52
|
|
|
43
|
-
|
|
53
|
+
4. 任一步失败上报 failed:
|
|
44
54
|
```bash
|
|
45
55
|
curl -sS -X POST "$CLAWD_DAEMON_URL/api/rpc/appBuilder.reportStage" \
|
|
46
56
|
-H "Content-Type: application/json" -H "X-Clawd-Session-Id: $CLAWD_SESSION_ID" \
|
|
@@ -51,16 +61,26 @@ curl -sS -X POST "$CLAWD_DAEMON_URL/api/rpc/appBuilder.reportStage" \
|
|
|
51
61
|
|
|
52
62
|
**发布上线**:老板点 PreviewPane「发布上线」按钮走 daemon RPC,happy path 完全跳过你。只有失败时 daemon 才发来一条以 `发布失败:[` 开头的消息(含 `.publish.log` 路径),那时你读日志、修问题、重跑发布。改完代码即时 HMR 刷新,不重新部署。
|
|
53
63
|
|
|
54
|
-
## 数据来源:dataclaw
|
|
55
|
-
生成的 app
|
|
64
|
+
## 数据来源:dataclaw 公网查询 API(只读,Bearer)
|
|
65
|
+
生成的 app 后端用**老板提供的 apikey** 以 `Authorization: Bearer <secret>` 调 dataclaw 公网 service(`https://120.48.21.36`,自签证书,后端已放开 TLS 校验),纯展示、不写数据。前端只调本 app 的 `/api/*`,apikey 由后端注入、绝不下发浏览器。有 key 即全量只读查询(无身份裁剪)。具体端点见 §「dataclaw 查询能力」。
|
|
66
|
+
|
|
67
|
+
## dataclaw 查询能力(公网端点速查)
|
|
68
|
+
模板后端 BFF(`server/src/dataclaw`)已封好以下端点,前端经本 app `/api/*` 调用:
|
|
69
|
+
|
|
70
|
+
| 端点 | 方法 | 入参 | 返回要点 |
|
|
71
|
+
|---|---|---|---|
|
|
72
|
+
| `/api/schema` | GET | `dataset?` | 数据集字段定义(metadata_user / consultant_progress / performance),字段带 aliases + operators |
|
|
73
|
+
| `/api/performance` | POST | `{startDate,endDate}` 必填 | `total` / `queryRange` / `byDateField`(billing/invoice/payment 各 total+amount) / `items[]` 明细 |
|
|
74
|
+
| `/api/ppl` | POST | `{startDate,endDate}` 必填 | `total` / `byStatus` / `byCustomer` 聚合(**只聚合无明细**) |
|
|
75
|
+
| `/api/consultants` | GET | `keyword` | `matches[]`(顾问基本信息) |
|
|
76
|
+
| `/api/intent` | POST | 意图 JSON(domain+intent+过滤,字段见 schema) | 结构化查询结果 |
|
|
56
77
|
|
|
57
|
-
|
|
58
|
-
本章节在后续模板/知识充实阶段填全(来源:dataclaw-src 仓库 workspace/docs/dataclaw-builder-architecture.md)。当前仅建骨架。
|
|
78
|
+
约定:所有返回都带 `_provenance` 溯源字段,前端用 `INTERNAL_FIELDS` 过滤不展示;业绩 / 进展广查询慢,务必带较短时间窗或收窄条件。起手页(业绩查询)是范例,按老板需求改数据集 / 端点 / 展示即可。
|
|
59
79
|
|
|
60
80
|
## 双客户
|
|
61
81
|
业务用户自助建简单页(语气业务化、屏蔽技术细节);管理员代建复杂页(可技术化)。
|
|
62
82
|
|
|
63
83
|
## 红线
|
|
64
84
|
- 只读:绝不让生成的 app 往 dataclaw 写数据 / 不接 Supabase 写库。
|
|
65
|
-
-
|
|
85
|
+
- apikey 只存 `server/.env`、只在后端用,**绝不下发到前端 / 不硬编码进代码 / 不写进会被打包的文件**。
|
|
66
86
|
- persona 目录只读;项目路径以 createProject 返回的 projectDir 为准。
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# extension-kit —— 从零到发布的 extension 流水线
|
|
2
2
|
|
|
3
|
-
把「前端 +
|
|
3
|
+
把「前端 + 后端的 extension 发布到阿里云 FC(serverless,免 Docker)」固化成可复用流水线。开发靠人/agent,发布全自动。
|
|
4
4
|
|
|
5
5
|
## 设计理念:契约层 + 可插拔技术栈
|
|
6
6
|
|
|
7
7
|
FC 对应用只有一条要求:**一个监听 `$PORT` 的 HTTP server**。所以固化的是「发布管道」,不是技术栈。
|
|
8
8
|
|
|
9
9
|
- **共享 deploy-kit(凭证 + 脚本骨架 + bootstrap,全 FC persona 一份)**:`$HOME/.clawd/deploy-kit/`(`scripts/` + `contract/bootstrap` + `.secrets/aliyun.env`)
|
|
10
|
-
- **persona 特化(本目录)**:`config.env`(dataclaw
|
|
10
|
+
- **persona 特化(本目录)**:`config.env`(dataclaw 注入值) + `contract/s.yaml.tmpl`(占位符清单) + `hooks/pre-deploy.sh`(发布前置) + `examples/`(默认 `nestjs-react`)
|
|
11
11
|
|
|
12
12
|
## 目录
|
|
13
13
|
|
|
@@ -24,9 +24,9 @@ deploy-kit/
|
|
|
24
24
|
|
|
25
25
|
# 本 persona 的 extension-kit(只放特化资产)
|
|
26
26
|
extension-kit/
|
|
27
|
-
├── config.env dataclaw
|
|
27
|
+
├── config.env dataclaw 注入值(region / Node层 / DATACLAW_SERVICE_URL)
|
|
28
28
|
├── contract/s.yaml.tmpl FC 部署模板(占位符,publish.sh 通用扫描替换)
|
|
29
|
-
├── hooks/pre-deploy.sh 发布前置(
|
|
29
|
+
├── hooks/pre-deploy.sh 发布前置(从生成 app 的 server/.env 读出 DATACLAW_API_KEY、注入 FC)
|
|
30
30
|
└── examples/nestjs-react/ 默认示例(含模板自带 ext.conf)
|
|
31
31
|
```
|
|
32
32
|
|
|
@@ -38,8 +38,8 @@ extension-kit/
|
|
|
38
38
|
PR="$HOME/.clawd/personas/persona-dataclaw-builder"; DK="$HOME/.clawd/deploy-kit"
|
|
39
39
|
|
|
40
40
|
# 1) scaffold(daemon 已自动跑;手动签名: new-extension.sh <name> <模板源目录> <目标目录>)
|
|
41
|
-
# 2) 改 web/ 和 server/ 业务逻辑(只读查 dataclaw
|
|
42
|
-
# 3) 一键发布到 FC,输出公网地址(
|
|
41
|
+
# 2) 改 web/ 和 server/ 业务逻辑(只读查 dataclaw 公网 API,不接 Supabase 写库)
|
|
42
|
+
# 3) 一键发布到 FC,输出公网地址(apikey 在建页时已写进生成 app 的 server/.env,无需额外配置)
|
|
43
43
|
"$DK/scripts/publish.sh" <projDir> "$PR"
|
|
44
44
|
# 4) 不要了就一键清理 FC 资源
|
|
45
45
|
"$DK/scripts/remove-extension.sh" <projDir> "$PR"
|
|
@@ -50,10 +50,9 @@ PR="$HOME/.clawd/personas/persona-dataclaw-builder"; DK="$HOME/.clawd/deploy-kit
|
|
|
50
50
|
| 步骤 | 谁做 |
|
|
51
51
|
|------|------|
|
|
52
52
|
| new-extension 脚手架 | 🟢 脚本 |
|
|
53
|
-
| 设计 Supabase schema | 🔴 人/agent(每个 extension 不同) |
|
|
54
53
|
| 写前后端业务 | 🔴 人/agent |
|
|
55
54
|
| publish(build/部署/域名/验证) | 🟢 脚本 |
|
|
56
|
-
| remove(清 FC
|
|
55
|
+
| remove(清 FC) | 🟢 脚本 |
|
|
57
56
|
|
|
58
57
|
## 清理一个 extension
|
|
59
58
|
|
|
@@ -61,13 +60,7 @@ PR="$HOME/.clawd/personas/persona-dataclaw-builder"; DK="$HOME/.clawd/deploy-kit
|
|
|
61
60
|
"$HOME/.clawd/deploy-kit/scripts/remove-extension.sh" <projDir> "$HOME/.clawd/personas/persona-dataclaw-builder"
|
|
62
61
|
```
|
|
63
62
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
**脚本怎么知道删哪个表**:靠 `ext.conf` 里显式声明的 `SUPABASE_TABLES="myapp_xxx"`(建表时填进去,extension 的"自描述")。三道安全锁:
|
|
67
|
-
|
|
68
|
-
1. 每个表必须 `<app>_` 前缀,否则**拒绝删**(物理上防误伤 clawos 共享库)
|
|
69
|
-
2. 删前列出所有要删的表
|
|
70
|
-
3. 要手输 `yes` 确认(数据不可逆,不用 `-y` 静默删)
|
|
63
|
+
`s remove` 删 FC 函数+触发器+自定义域名。
|
|
71
64
|
|
|
72
65
|
> SLS 日志 project / OSS 代码桶是**账号级共享**(多 extension 共用),不随单个 extension 删。
|
|
73
66
|
|
|
@@ -89,11 +82,10 @@ PR="$HOME/.clawd/personas/persona-dataclaw-builder"; DK="$HOME/.clawd/deploy-kit
|
|
|
89
82
|
|
|
90
83
|
## 红线(都是踩坑换来的,见记忆 fc-nodejs-deploy-recipe)
|
|
91
84
|
|
|
92
|
-
- **Supabase 是 clawos 共享生产库**:新表必须 `<app>_` 前缀,**绝不 drop/alter clawos 已有表**
|
|
93
85
|
- **必须绑自定义域名**(fc3-domain auto),否则 `fcapp.run` 默认 URL 强制下载网页、浏览器不渲染
|
|
94
86
|
- **不用 SAE**(Node 无代码包,只能镜像=要 Docker);不用 `custom.debian10`/裸 command(node 找不到 / CAExited)
|
|
95
|
-
- **长连接 / WebSocket 推送不适合 FC**
|
|
87
|
+
- **长连接 / WebSocket 推送不适合 FC**(FC 不维持长连接)
|
|
96
88
|
|
|
97
89
|
## 不固化的(留给人/agent)
|
|
98
90
|
|
|
99
|
-
|
|
91
|
+
业务逻辑、发布失败时的异常排查。
|
|
@@ -8,11 +8,8 @@ REGION=cn-hangzhou
|
|
|
8
8
|
FC_RUNTIME=custom.debian12
|
|
9
9
|
NODE_LAYER=acs:fc:cn-hangzhou:official:layers/Nodejs22/versions/1
|
|
10
10
|
|
|
11
|
-
# dataclaw
|
|
12
|
-
DATACLAW_SERVICE_URL=
|
|
13
|
-
# TTC 飞书登录门禁(无密钥经纪,只重定向到 TTC)
|
|
14
|
-
TTC_AUTH_BASE=https://app.ttcadvisory.com
|
|
15
|
-
TTC_API_BASE=https://api.ttcadvisory.com
|
|
11
|
+
# dataclaw 公网查询 API(只读,Bearer)—— ext.conf 可覆盖;apikey 走生成 app 的 server/.env,不在这里
|
|
12
|
+
DATACLAW_SERVICE_URL=https://120.48.21.36
|
|
16
13
|
|
|
17
14
|
# FC 资源规格(够跑轻量 extension;按需改)
|
|
18
15
|
FC_CPU=0.35
|
|
@@ -32,12 +32,7 @@ resources:
|
|
|
32
32
|
PORT: '9000'
|
|
33
33
|
APP_ENTRY: __FC_ENTRY__
|
|
34
34
|
DATACLAW_SERVICE_URL: __DATACLAW_SERVICE_URL__
|
|
35
|
-
|
|
36
|
-
DATACLAW_INTERNAL_SECRET: __DATACLAW_INTERNAL_SECRET__
|
|
37
|
-
TTC_AUTH_BASE: __TTC_AUTH_BASE__
|
|
38
|
-
TTC_API_BASE: __TTC_API_BASE__
|
|
39
|
-
APP_BASE_URL: __APP_BASE_URL__
|
|
40
|
-
SESSION_SECRET: __SESSION_SECRET__
|
|
35
|
+
DATACLAW_API_KEY: __DATACLAW_API_KEY__
|
|
41
36
|
triggers:
|
|
42
37
|
- triggerName: httpTrigger
|
|
43
38
|
triggerType: http
|
package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/ext.conf
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
CODE_DIR=./server
|
|
2
2
|
FC_ENTRY=dist/main.js
|
|
3
3
|
BUILD_CMD="cd web && pnpm install && pnpm build && cd ../server && pnpm install && pnpm build && mkdir -p dist/public && cp -R ../web/dist/. dist/public/"
|
|
4
|
-
#
|
|
5
|
-
|
|
4
|
+
# dataclaw 公网查询 API(只读,Bearer)。apikey 不在这里——走 server/.env 的 DATACLAW_API_KEY,
|
|
5
|
+
# 发布时 pre-deploy.sh 从 server/.env 读出、注入 FC(避免 key 落入随项目走的非密文件)。
|
|
6
|
+
DATACLAW_SERVICE_URL=https://120.48.21.36
|
|
@@ -1,15 +1,6 @@
|
|
|
1
|
-
# dataclaw
|
|
2
|
-
DATACLAW_SERVICE_URL=
|
|
3
|
-
#
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
DATACLAW_INTERNAL_SECRET=replace-me
|
|
7
|
-
# 本地起页跳过门禁(仅 dev;上线删或置 0)
|
|
8
|
-
DATACLAW_AUTH_DISABLED=1
|
|
1
|
+
# dataclaw 公网查询 API(只读,Bearer 鉴权;自签证书,后端已放开 TLS 校验)
|
|
2
|
+
DATACLAW_SERVICE_URL=https://120.48.21.36
|
|
3
|
+
# 建页人提供的 dataclaw apikey —— 只填冒号后的 secret 段(如拿到 colleague-a:xxxx 则填 xxxx)。
|
|
4
|
+
# dev 预览由 dotenv 加载本文件;发布 FC 时 pre-deploy.sh 从这里读出、注入 FC 环境变量(.env 不进部署包)。
|
|
5
|
+
DATACLAW_API_KEY=
|
|
9
6
|
PORT=3000
|
|
10
|
-
|
|
11
|
-
# TTC 飞书登录门禁(无密钥经纪:重定向到 TTC,不持飞书密钥)
|
|
12
|
-
TTC_AUTH_BASE=https://app.ttcadvisory.com
|
|
13
|
-
TTC_API_BASE=https://api.ttcadvisory.com
|
|
14
|
-
APP_BASE_URL=https://your-app.example.com # 发布后的公网地址,回调用;HTTPS(cookie secure)
|
|
15
|
-
SESSION_SECRET=replace-with-openssl-rand-hex-32
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"private": true,
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "nest build",
|
|
7
|
-
"start": "node
|
|
7
|
+
"start": "node dist/main.js",
|
|
8
8
|
"start:dev": "nest start --watch",
|
|
9
9
|
"dev": "nest start --watch"
|
|
10
10
|
},
|
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
"@nestjs/common": "^10.4.0",
|
|
13
13
|
"@nestjs/core": "^10.4.0",
|
|
14
14
|
"@nestjs/platform-express": "^10.4.0",
|
|
15
|
-
"cookie-parser": "^1.4.6",
|
|
16
15
|
"dotenv": "^16.4.5",
|
|
17
16
|
"reflect-metadata": "^0.2.2",
|
|
18
17
|
"rxjs": "^7.8.1"
|
|
@@ -20,7 +19,6 @@
|
|
|
20
19
|
"devDependencies": {
|
|
21
20
|
"@nestjs/cli": "^10.4.0",
|
|
22
21
|
"@nestjs/schematics": "^10.2.0",
|
|
23
|
-
"@types/cookie-parser": "^1.4.7",
|
|
24
22
|
"@types/node": "^22.0.0",
|
|
25
23
|
"@vitejs/plugin-react": "^4.4.0",
|
|
26
24
|
"typescript": "^5.5.0",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Module } from '@nestjs/common';
|
|
2
2
|
import { DataclawModule } from './dataclaw/dataclaw.module';
|
|
3
3
|
|
|
4
|
-
// dataclaw 只读后端 BFF 起手骨架:默认只注册 DataclawModule(schema /
|
|
5
|
-
// consultants
|
|
4
|
+
// dataclaw 只读后端 BFF 起手骨架:默认只注册 DataclawModule(schema / performance / ppl /
|
|
5
|
+
// consultants / intent 五个只读端点,用建页人提供的 apikey 以 Bearer 调公网 dataclaw service)。
|
|
6
6
|
// 业务逻辑由 assistant 按用户需求从零加(添 controller/module 进 imports 即可)。
|
|
7
7
|
//
|
|
8
8
|
// vite middleware / 静态资源 serve 路径仍由 main.ts 处理(dev mount vite,prod express.static),
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { Injectable, HttpException, OnModuleInit } from '@nestjs/common'
|
|
2
2
|
import { DataclawConfig, loadDataclawConfig } from './dataclaw.config'
|
|
3
3
|
|
|
4
|
-
//
|
|
4
|
+
// dataclaw 公网 service 用自签证书(文档要求 curl -k)。Node fetch(undici) 默认校验 TLS 会握手失败。
|
|
5
|
+
// 本 app 唯一出站目标就是 dataclaw 这一个自签 https,进程级放开 TLS 校验即可(最小改动、零依赖)。
|
|
6
|
+
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
|
|
7
|
+
|
|
8
|
+
// 端点超时:schema / 固定查询走默认;intent-execute 可能跑 LLM 编排,给 90s。
|
|
5
9
|
const TIMEOUT_MS = { default: 30_000, intent: 90_000 }
|
|
6
10
|
|
|
7
11
|
@Injectable()
|
|
@@ -33,9 +37,8 @@ export class DataclawClient implements OnModuleInit {
|
|
|
33
37
|
signal: ctrl.signal,
|
|
34
38
|
headers: {
|
|
35
39
|
'Content-Type': 'application/json',
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
'X-Internal-Secret': this.cfg.internalSecret,
|
|
40
|
+
// 鉴权:建页人提供的 dataclaw apikey,Bearer 传给公网 service(有 key 即全量只读查询)。
|
|
41
|
+
Authorization: `Bearer ${this.cfg.apiKey}`,
|
|
39
42
|
},
|
|
40
43
|
body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,
|
|
41
44
|
})
|
|
@@ -69,15 +72,22 @@ export class DataclawClient implements OnModuleInit {
|
|
|
69
72
|
return this.call<any>('GET', '/api/schema', { query: { dataset } })
|
|
70
73
|
}
|
|
71
74
|
|
|
72
|
-
|
|
73
|
-
|
|
75
|
+
// 业绩查询(必填 startDate/endDate;可按顾问/团队/办公地点等收窄,参数名见 dataclaw 文档)。
|
|
76
|
+
performance(body: unknown) {
|
|
77
|
+
return this.call<any>('POST', '/api/performance', { body })
|
|
74
78
|
}
|
|
75
79
|
|
|
76
|
-
|
|
77
|
-
|
|
80
|
+
// 候选人进展 pipeline(必填 startDate/endDate)。返回聚合 byStatus/byCustomer。
|
|
81
|
+
ppl(body: unknown) {
|
|
82
|
+
return this.call<any>('POST', '/api/ppl', { body })
|
|
78
83
|
}
|
|
79
84
|
|
|
80
85
|
consultants(keyword: string) {
|
|
81
86
|
return this.call<any>('GET', '/api/consultants', { query: { keyword } })
|
|
82
87
|
}
|
|
88
|
+
|
|
89
|
+
// 结构化意图查询(高级):上游已抽好的意图 JSON(domain+intent+过滤),字段定义见 /api/schema。
|
|
90
|
+
intentExecute(body: unknown) {
|
|
91
|
+
return this.call<any>('POST', '/api/intent-execute', { body, timeout: TIMEOUT_MS.intent })
|
|
92
|
+
}
|
|
83
93
|
}
|
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
export interface DataclawConfig {
|
|
2
2
|
baseUrl: string
|
|
3
|
-
|
|
4
|
-
internalSecret: string
|
|
3
|
+
apiKey: string
|
|
5
4
|
}
|
|
6
5
|
|
|
7
6
|
export function loadDataclawConfig(): DataclawConfig {
|
|
8
|
-
const baseUrl = (process.env.DATACLAW_SERVICE_URL || '').replace(/\/+$/, '')
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if (missing.length) throw new Error(`[dataclaw] 缺少必需 env: ${missing.join(', ')}`)
|
|
17
|
-
return { baseUrl, asUnionId, internalSecret }
|
|
7
|
+
const baseUrl = (process.env.DATACLAW_SERVICE_URL || 'https://120.48.21.36').replace(/\/+$/, '')
|
|
8
|
+
// 建页人提供的 dataclaw apikey(Bearer token,只填冒号后的 secret 段)。写进 server/.env,
|
|
9
|
+
// dev 由 dotenv 加载、FC 由 s.yaml 注入。缺失则拒启动(app 没它查不到数)。
|
|
10
|
+
const apiKey = process.env.DATACLAW_API_KEY || ''
|
|
11
|
+
if (!apiKey) {
|
|
12
|
+
throw new Error('[dataclaw] 缺少必需 env DATACLAW_API_KEY(建页人提供的 dataclaw apikey,写进 server/.env)')
|
|
13
|
+
}
|
|
14
|
+
return { baseUrl, apiKey }
|
|
18
15
|
}
|
|
@@ -2,8 +2,7 @@ import { Body, Controller, Get, Post, Query } from '@nestjs/common'
|
|
|
2
2
|
import { DataclawClient } from './dataclaw.client'
|
|
3
3
|
|
|
4
4
|
// app 自己的只读 BFF。main.ts 已 setGlobalPrefix('api' 或子路径 'preview/<port>/api'),
|
|
5
|
-
// 故这里的路径不带 /api
|
|
6
|
-
// 登录门禁由 main.ts 的 Express authGate 中间件统一覆盖(含 /api/*),不再用 Nest guard。
|
|
5
|
+
// 故这里的路径不带 /api。前端只调本 app 的 /api/*,apikey 由后端注入,绝不下发浏览器。
|
|
7
6
|
@Controller()
|
|
8
7
|
export class DataclawController {
|
|
9
8
|
constructor(private readonly dc: DataclawClient) {}
|
|
@@ -13,18 +12,23 @@ export class DataclawController {
|
|
|
13
12
|
return this.dc.getSchema(dataset)
|
|
14
13
|
}
|
|
15
14
|
|
|
16
|
-
@Post('
|
|
17
|
-
|
|
18
|
-
return this.dc.
|
|
15
|
+
@Post('performance')
|
|
16
|
+
performance(@Body() body: unknown) {
|
|
17
|
+
return this.dc.performance(body)
|
|
19
18
|
}
|
|
20
19
|
|
|
21
|
-
@Post('
|
|
22
|
-
|
|
23
|
-
return this.dc.
|
|
20
|
+
@Post('ppl')
|
|
21
|
+
ppl(@Body() body: unknown) {
|
|
22
|
+
return this.dc.ppl(body)
|
|
24
23
|
}
|
|
25
24
|
|
|
26
25
|
@Get('consultants')
|
|
27
26
|
consultants(@Query('keyword') keyword = '') {
|
|
28
27
|
return this.dc.consultants(keyword)
|
|
29
28
|
}
|
|
29
|
+
|
|
30
|
+
@Post('intent')
|
|
31
|
+
intent(@Body() body: unknown) {
|
|
32
|
+
return this.dc.intentExecute(body)
|
|
33
|
+
}
|
|
30
34
|
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import './polyfill';
|
|
2
|
+
import 'dotenv/config'; // 加载 server/.env:dev 由 dotenv 读取 DATACLAW_API_KEY;FC 由 s.yaml 注入 env(.env 被 .fcignore 排除不进部署包)
|
|
2
3
|
import { NestFactory } from '@nestjs/core';
|
|
3
4
|
import { NestExpressApplication } from '@nestjs/platform-express';
|
|
4
5
|
import { AppModule } from './app.module';
|
|
5
6
|
import * as path from 'node:path';
|
|
6
|
-
import cookieParser from 'cookie-parser';
|
|
7
|
-
import { authGate, loginHandler, callbackHandler } from './auth/gate';
|
|
8
7
|
|
|
9
8
|
// app-builder build 模式(spec 2026-06-01 + 2026-06-02 §5.6.4 子路径 fix):
|
|
10
9
|
// - dev(通过 clawd 启):nest 单进程 mount vite middleware,前端 HMR + /api 都从
|
|
@@ -35,20 +34,8 @@ async function bootstrap() {
|
|
|
35
34
|
|
|
36
35
|
app.setGlobalPrefix(apiPrefix, { exclude: ['/'] });
|
|
37
36
|
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
// (约:75) / useStaticAssets(约:99) 之前。/api/* 也先经过它(gate 在 vite 守卫和 nest router 之前)。
|
|
41
|
-
// /auth/login、/auth/callback 直接在 express 层注册,不带 globalPrefix;子路径部署带同样前缀。
|
|
42
|
-
// fail-loud:门禁启用却没配 SESSION_SECRET,会用空密钥签 cookie(可被伪造)—— 直接拒启动。
|
|
43
|
-
if (process.env.DATACLAW_AUTH_DISABLED !== '1' && !process.env.SESSION_SECRET) {
|
|
44
|
-
throw new Error('[auth] 缺 SESSION_SECRET:登录门禁启用时必填(否则 session cookie 可被伪造)。本地调试可设 DATACLAW_AUTH_DISABLED=1 跳过门禁。');
|
|
45
|
-
}
|
|
46
|
-
app.use(cookieParser());
|
|
47
|
-
const httpAdapter = app.getHttpAdapter();
|
|
48
|
-
const authBasePath = subPath ? `/${subPath}` : '';
|
|
49
|
-
httpAdapter.get(`${authBasePath}/auth/login`, loginHandler);
|
|
50
|
-
httpAdapter.get(`${authBasePath}/auth/callback`, callbackHandler);
|
|
51
|
-
app.use(authGate);
|
|
37
|
+
// 无登录门禁:页面打开即用。数据访问鉴权由后端用 DATACLAW_API_KEY(建页人提供的 apikey)注入,
|
|
38
|
+
// 见 dataclaw.client —— key 绝不下发浏览器。
|
|
52
39
|
|
|
53
40
|
if (isDev && isClawdDev) {
|
|
54
41
|
// dev 模式 + 通过 clawd 启的 → mount vite middleware,root=../../web
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import { useAuthGate } from './auth';
|
|
2
1
|
import PerformanceQueryPage from './pages/PerformanceQueryPage';
|
|
3
2
|
|
|
3
|
+
// 无登录门禁:页面打开即用。数据鉴权由后端用建页人提供的 apikey 注入(见 server/dataclaw)。
|
|
4
4
|
export default function App() {
|
|
5
|
-
const { ready, authed } = useAuthGate();
|
|
6
|
-
if (!ready) return <div>加载中…</div>;
|
|
7
|
-
if (!authed) return <div>请先飞书登录</div>;
|
|
8
5
|
return <PerformanceQueryPage />;
|
|
9
6
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// 只调 app 自己的 BFF(/api/*),绝不直连 dataclaw ——
|
|
1
|
+
// 只调 app 自己的 BFF(/api/*),绝不直连 dataclaw —— apikey 在 server 侧注入,不进浏览器。
|
|
2
2
|
//
|
|
3
3
|
// dev 隧道模式下页面挂在子路径 /preview/<port>/ 下(见 vite.config.js base),
|
|
4
4
|
// 所以 API_BASE 要拼上 import.meta.env.BASE_URL,否则 fetch 会打到错误的绝对路径。
|
|
@@ -14,6 +14,8 @@ async function req(path, opts = {}) {
|
|
|
14
14
|
return json;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
// dataclaw 公网端点(经本 app BFF 转发,见 server/dataclaw.controller)。
|
|
17
18
|
export const getSchema = (dataset) => req(`/schema?dataset=${encodeURIComponent(dataset || '')}`);
|
|
18
|
-
export const
|
|
19
|
+
export const queryPerformance = (body) => req('/performance', { method: 'POST', body: JSON.stringify(body) });
|
|
20
|
+
export const queryPpl = (body) => req('/ppl', { method: 'POST', body: JSON.stringify(body) });
|
|
19
21
|
export const searchConsultants = (kw) => req(`/consultants?keyword=${encodeURIComponent(kw)}`);
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import { INTERNAL_FIELDS } from '../enums';
|
|
2
|
+
|
|
3
|
+
// 结果表格。列取 rows[0] 的 key(过滤 _provenance 等内部字段),数字按 zh-CN 千分位展示,空数组显示「无数据」。
|
|
2
4
|
export default function ResultTable({ rows }) {
|
|
3
5
|
if (!rows || rows.length === 0) return <div className="empty">无数据</div>;
|
|
4
|
-
const cols = Object.keys(rows[0]);
|
|
6
|
+
const cols = Object.keys(rows[0]).filter((c) => !INTERNAL_FIELDS.has(c));
|
|
5
7
|
return (
|
|
6
8
|
<table className="result-table">
|
|
7
9
|
<thead>
|
|
@@ -1,22 +1,7 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
// 为什么前端要自带这份常量:
|
|
4
|
-
// - /api/schema 只给 field.type / field.operators / aliases,**不给枚举候选值**,
|
|
5
|
-
// 所以状态类字段的下拉选项只能在前端硬编码(ENUM_HINTS)。
|
|
6
|
-
// - describeDatasets 仍会返回 ID / 各级编号 等内部字段,前端要主动过滤掉不渲染
|
|
7
|
-
// (INTERNAL_FIELDS)。
|
|
8
|
-
// 后端 query-filters.js 改了这里也要同步改。
|
|
9
|
-
|
|
10
|
-
export const ENUM_HINTS = {
|
|
11
|
-
候选人状态: ['推荐', '1面', '2面', '3面', '4面', '终面', 'Offer', '已入职', '淘汰'],
|
|
12
|
-
顾问类型: ['正式', '实习', '外部合作顾问', '兼职', '外包', '虚拟账号'],
|
|
13
|
-
顾问在职状态: ['在职', '离职'],
|
|
14
|
-
};
|
|
15
|
-
|
|
1
|
+
// 内部字段:dataclaw 返回里不展示给用户的列(编号 / ID / 溯源元数据等)。
|
|
2
|
+
// ResultTable 渲染时按此 Set 过滤掉对应列。
|
|
16
3
|
export const INTERNAL_FIELDS = new Set([
|
|
17
4
|
'ID', 'unique_id', '链接', 'SourceID', '发票ID', '客户ID', '职位ID',
|
|
18
5
|
'业绩所属一级团队编号', '业绩所属二级团队编号', '业绩所属三级团队编号', '所属OP名称', '候选人ID',
|
|
6
|
+
'_provenance', // dataclaw 每条返回都带的溯源元数据(source/record_type/fetched_at/trace_id),不展示
|
|
19
7
|
]);
|
|
20
|
-
|
|
21
|
-
// 每种字段类型添加筛选时的默认操作符。
|
|
22
|
-
export const TYPE_DEFAULT_OP = { string: 'contains', number: 'eq', date: 'eq' };
|