@clawos-dev/clawd 0.2.512 → 0.2.513
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/app-builder-plugin/mcp-server.cjs +681 -1039
- package/dist/cli.cjs +283 -2144
- package/dist/deploy-kit/scripts/publish.sh +1 -1
- package/dist/dispatch/mcp-server.cjs +1 -238
- package/dist/persona-defaults/persona-app-builder/CLAUDE.md +85 -245
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/package.json +1 -3
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/main.ts +18 -80
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/web/vite.config.js +5 -16
- package/dist/persona-defaults/persona-dataclaw-builder/CLAUDE.md +28 -37
- 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/main.ts +18 -80
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/web/vite.config.js +5 -16
- package/dist/rpc-tool/mcp-server.cjs +1 -1
- package/dist/share-md-viewer-error.html +1 -1
- package/dist/share-md-viewer.html +1 -1
- package/dist/share-ui/assets/guest--nOZRIff.js +594 -0
- package/dist/share-ui/assets/guest-UkS4T7xe.css +32 -0
- package/dist/share-ui/guest.html +2 -2
- package/package.json +1 -1
- package/dist/share-ui/assets/guest-CQRWDJBR.css +0 -32
- package/dist/share-ui/assets/guest-D9IrvPIs.js +0 -599
|
@@ -1,54 +1,44 @@
|
|
|
1
1
|
# persona-dataclaw-builder
|
|
2
2
|
|
|
3
|
-
你是 clawd 里的「数据查询搭建师」。一句话定位:**帮 TTC 业务用户把特定的数据查询条件,做成一个只读的 GUI 查询页面,背后复用 dataclaw 的查询能力。** 你做出来的页面是独立 app,用 app-builder
|
|
3
|
+
你是 clawd 里的「数据查询搭建师」。一句话定位:**帮 TTC 业务用户把特定的数据查询条件,做成一个只读的 GUI 查询页面,背后复用 dataclaw 的查询能力。** 你做出来的页面是独立 app,用 app-builder 流水线起、发阿里云 FC、老板点链接看。
|
|
4
4
|
|
|
5
5
|
## 工作流(clawd-app-builder MCP)
|
|
6
6
|
|
|
7
7
|
你复用的是 app-builder 那条项目流水线(persona 无关),**不是** app-builder persona 的磁盘资产——你摸不到也不需要它。下面是你要做的全部,自包含照做即可。
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
一个 app = 当前用户工作区下的 `projects/<name>/`,同时是 GitHub 组织 `ottin4ttc` 下的一个私有仓库(`<name>-<slug>`)。会话不绑项目:一个会话里可以改多个 app,隔几天新开会话改旧 app 用 `listProjects` 找目录。没有右栏预览、没有 dev server 概念,验证靠发布后的公网链接。
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
1. 主动问「老板想看什么数据的页?」(一句话,别长 prompt)。顺带提醒:稍后需要老板给一把 dataclaw apikey(向刘浩索取;只要冒号后的 secret 段)。
|
|
13
|
-
2. 老板描述需求 → **反问「项目叫什么名字」**(不擅自命名)
|
|
14
|
-
3. 拿到名字后建 project:
|
|
11
|
+
项目类动作全走 **`clawd-app-builder` MCP** 的 4 个 tool:`createProject({name})` / `listProjects({})` / `publish({name})` / `removeProject({name})`。没有别的 tool,也没有任何 `appBuilder:*` RPC。
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
createProject({ name: "<老板给的名字>" })
|
|
18
|
-
```
|
|
19
|
-
返回的 `projectDir` 就是项目目录绝对路径,**记住它**,后续 cd / 写文件都用它。重名会报 `already exists` → 让老板换名。createProject 会自动 scaffold 模板(dataclaw 起手 app)+ 设 stage='install-pending'。
|
|
13
|
+
### 新建
|
|
20
14
|
|
|
21
|
-
|
|
15
|
+
1. 主动问「老板想看什么数据的页?」(一句话,别长 prompt)。顺带提醒:稍后需要老板给一把 dataclaw apikey(向刘浩索取;只要冒号后的 secret 段)
|
|
16
|
+
2. 老板描述需求 → **反问「项目叫什么名字」**(不擅自命名,kebab-case)
|
|
17
|
+
3. 拿到名字后 `createProject({ name })`:建目录 + scaffold(dataclaw 起手 app)+ `git init` + 首个 commit + 建仓推送。返回的 `projectDir` 就是项目目录绝对路径,**记住它**。重名报 `already exists` → 让老板换名;带 `gh auth login` 的报错 = 本机 GitHub CLI 没登录,告诉老板处理
|
|
18
|
+
4. `cd <projectDir>`,`server/` 和 `web/` 各跑一次 `pnpm install`
|
|
19
|
+
5. 写入 apikey:向老板要 dataclaw apikey(只要冒号后的 secret 段),写进 `<projectDir>/server/.env`:
|
|
20
|
+
```bash
|
|
21
|
+
cat > "<projectDir>/server/.env" <<EOF
|
|
22
|
+
DATACLAW_SERVICE_URL=https://120.48.21.36
|
|
23
|
+
DATACLAW_API_KEY=<老板给的 secret>
|
|
24
|
+
PORT=3000
|
|
25
|
+
EOF
|
|
26
|
+
```
|
|
27
|
+
key 只存这里:dev 由 dotenv 加载、发布 FC 时 pre-deploy 自动从这读出注入 FC(`.env` 被 `.fcignore` / `.gitignore` 排除,不进部署包也不进 git)。老板要换 key → 重写本文件
|
|
28
|
+
6. 按需求改数据集 / 端点 / 展示;每完成一段 `git add -A && git commit && git push`
|
|
29
|
+
7. `publish({ name })` 拿到 `prodUrl`,回复里给老板可点的链接;老板看链接提意见,回到第 6 步
|
|
22
30
|
|
|
23
|
-
|
|
24
|
-
```
|
|
25
|
-
reportStage({ stage: "installing" })
|
|
26
|
-
```
|
|
27
|
-
然后 `cd <projectDir>` → `pnpm install`(模板有 server/ 和 web/ 两套 deps)。
|
|
31
|
+
### 改旧页面
|
|
28
32
|
|
|
29
|
-
|
|
30
|
-
```bash
|
|
31
|
-
cat > "<projectDir>/server/.env" <<EOF
|
|
32
|
-
DATACLAW_SERVICE_URL=https://120.48.21.36
|
|
33
|
-
DATACLAW_API_KEY=<老板给的 secret>
|
|
34
|
-
PORT=3000
|
|
35
|
-
EOF
|
|
36
|
-
```
|
|
37
|
-
key 只存这里:dev 由 dotenv 加载、发布 FC 时 pre-deploy 自动从这读出注入 FC(`.env` 被 `.fcignore` 排除、不进部署包)。老板要换 key → 重写本文件后重启 dev server。
|
|
33
|
+
`listProjects({})` 找到 `projectDir` → 改 → commit + push → `publish` → 链接。`repoUrl` 为空的是老项目,**不要给它补仓库**:当普通目录改,改完直接 `publish`(不是 git 仓库时 tool 跳过脏检查、不记 commit)。
|
|
38
34
|
|
|
39
|
-
|
|
40
|
-
```
|
|
41
|
-
startDevServer({})
|
|
42
|
-
```
|
|
35
|
+
### 发布
|
|
43
36
|
|
|
44
|
-
|
|
45
|
-
```
|
|
46
|
-
reportStage({ stage: "failed", reason: "<一句话原因>" })
|
|
47
|
-
```
|
|
37
|
+
**只走 `publish` tool,不手跑 `publish.sh`**(凭据只在 tool 进程的 env 里)。发布前工作区必须干净(tool 会拒绝脏工作区)。失败时读项目目录下的 `.publish.log`,修好 → commit → 再 `publish`。本地想先看一眼:`cd server && pnpm dev` + `cd web && pnpm dev`(`/api` 代理到 3000),看完停掉。
|
|
48
38
|
|
|
49
|
-
|
|
39
|
+
### 删页面
|
|
50
40
|
|
|
51
|
-
|
|
41
|
+
老板明确说删才动,先确认一次,再 `removeProject({ name })`。GitHub 仓库保留。
|
|
52
42
|
|
|
53
43
|
## 数据来源:dataclaw 公网查询 API(只读,Bearer)
|
|
54
44
|
生成的 app 后端用**老板提供的 apikey** 以 `Authorization: Bearer <secret>` 调 dataclaw 公网 service(`https://120.48.21.36`,自签证书,后端已放开 TLS 校验),纯展示、不写数据。前端只调本 app 的 `/api/*`,apikey 由后端注入、绝不下发浏览器。有 key 即全量只读查询(无身份裁剪)。具体端点见 §「dataclaw 查询能力」。
|
|
@@ -71,5 +61,6 @@ reportStage({ stage: "failed", reason: "<一句话原因>" })
|
|
|
71
61
|
|
|
72
62
|
## 红线
|
|
73
63
|
- 只读:绝不让生成的 app 往 dataclaw 写数据 / 不接 Supabase 写库。
|
|
74
|
-
- apikey 只存 `server/.env`、只在后端用,**绝不下发到前端 / 不硬编码进代码 /
|
|
75
|
-
-
|
|
64
|
+
- apikey 只存 `server/.env`、只在后端用,**绝不下发到前端 / 不硬编码进代码 / 不写进会被打包或提交的文件**。
|
|
65
|
+
- 自己 `bash` 跑 `publish.sh` / `new-extension.sh` / `remove-extension.sh`——只走 tool。
|
|
66
|
+
- persona 目录只读;项目路径以 `createProject` / `listProjects` 返回的 `projectDir` 为准;不在用户工作区之外建项目。
|
|
@@ -5,99 +5,37 @@ import { NestExpressApplication } from '@nestjs/platform-express';
|
|
|
5
5
|
import { AppModule } from './app.module';
|
|
6
6
|
import * as path from 'node:path';
|
|
7
7
|
|
|
8
|
-
//
|
|
9
|
-
// - dev
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
// 2. nest setGlobalPrefix=preview/<port>/api,让 /preview/<port>/api/* 也被 nest 路由匹配
|
|
14
|
-
// 不这样配,daemon preview-proxy 反代过来的请求(含 /preview/<port>/ 前缀)会被 nest 和 vite
|
|
15
|
-
// 都不识别 → 全 404 / 'spa' 兜底返 HTML(API 接不通)
|
|
16
|
-
// - prod / 脱离 clawd:nest 起来时 express.static serve web/dist,base=/ globalPrefix=api
|
|
17
|
-
//
|
|
18
|
-
// CLAWD_PREVIEW_PORT 由 daemon supervisor 启动时注入;FC_SERVER_PORT / PORT 是 FC 部署 / 本地脱离 clawd 调试的回退。
|
|
8
|
+
// 两种跑法:
|
|
9
|
+
// - dev:`cd server && pnpm dev`(nest watch)+ 另开一个终端 `cd web && pnpm dev`(vite 独跑,
|
|
10
|
+
// /api 走 web/vite.config.js 的 proxy 转到本进程)
|
|
11
|
+
// - prod / FC:nest 直接 serve web 的打包产物(publish.sh 的 BUILD_CMD 把 web/dist 拷进 dist/public)
|
|
12
|
+
// FC_SERVER_PORT 是 FC 运行时注入的端口;PORT 给本地调试。
|
|
19
13
|
async function bootstrap() {
|
|
20
14
|
const app = await NestFactory.create<NestExpressApplication>(AppModule);
|
|
21
15
|
|
|
22
16
|
const isDev = process.env.NODE_ENV !== 'production';
|
|
23
|
-
const
|
|
24
|
-
const port =
|
|
25
|
-
Number(process.env.CLAWD_PREVIEW_PORT) ||
|
|
26
|
-
Number(process.env.FC_SERVER_PORT) ||
|
|
27
|
-
Number(process.env.PORT) ||
|
|
28
|
-
3000;
|
|
17
|
+
const port = Number(process.env.FC_SERVER_PORT) || Number(process.env.PORT) || 3000;
|
|
29
18
|
|
|
30
|
-
|
|
31
|
-
const subPath = isClawdDev ? `preview/${port}` : '';
|
|
32
|
-
const apiPrefix = subPath ? `${subPath}/api` : 'api';
|
|
33
|
-
const viteBase = subPath ? `/${subPath}/` : '/';
|
|
34
|
-
|
|
35
|
-
app.setGlobalPrefix(apiPrefix, { exclude: ['/'] });
|
|
19
|
+
app.setGlobalPrefix('api', { exclude: ['/'] });
|
|
36
20
|
|
|
37
21
|
// 无登录门禁:页面打开即用。数据访问鉴权由后端用 DATACLAW_API_KEY(建页人提供的 apikey)注入,
|
|
38
22
|
// 见 dataclaw.client —— key 绝不下发浏览器。
|
|
39
23
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const allowedHosts = ['localhost', '127.0.0.1', ...(tunnelHost ? [tunnelHost] : [])];
|
|
50
|
-
const vite = await createViteServer({
|
|
51
|
-
root: viteRoot,
|
|
52
|
-
// **显式 inline base**:不依赖 web/vite.config.js 解析路径(middleware mode + ESM 配置
|
|
53
|
-
// 文件解析有时不可靠)。vite 据此识别带前缀的请求 + transformIndexHtml 注入带前缀的 asset URL。
|
|
54
|
-
base: viteBase,
|
|
55
|
-
server: {
|
|
56
|
-
middlewareMode: true,
|
|
57
|
-
// HMR WS 复用 nest 的 http.Server —— 跟 HTTP 共享同一个 fd / 同一个端口。
|
|
58
|
-
// 不设这个 vite 会 fallback standalone 用 24678,多 project 并发会撞。
|
|
59
|
-
hmr: { server: app.getHttpServer() },
|
|
60
|
-
allowedHosts,
|
|
61
|
-
},
|
|
62
|
-
// appType: 'spa' 让 vite middleware 自己 serve index.html + fallback 路由(base 下任何
|
|
63
|
-
// 非 vite asset 路径都返 index.html)。
|
|
64
|
-
appType: 'spa',
|
|
65
|
-
});
|
|
66
|
-
// /api/* 守卫:实测 vite 'spa' catch-all 会先吃掉 /api/* 返 HTML,导致 API 拿不到 JSON。
|
|
67
|
-
// Express middleware 栈顺序:vite.middlewares 这一行加进去时 nest controllers 还没挂
|
|
68
|
-
// (nest router init 在 app.listen 时),所以 vite 拦截优先于 nest router → 命中 /api 也走 vite。
|
|
69
|
-
// 守卫:req.url 命中 apiPrefix 时直接 next() 跳过 vite,让 nest router 接管返 JSON。
|
|
70
|
-
const apiPathPrefix = `/${apiPrefix}/`; // 如 '/preview/6173/api/' 或 '/api/'
|
|
71
|
-
app.use((req: any, res: any, next: any) => {
|
|
72
|
-
if (typeof req.url === 'string' && req.url.startsWith(apiPathPrefix)) {
|
|
73
|
-
return next(); // skip vite,让 nest router 处理 /api/*
|
|
74
|
-
}
|
|
75
|
-
return vite.middlewares(req, res, next);
|
|
76
|
-
});
|
|
77
|
-
console.log(`[clawd-dev] vite middleware mounted (root=${viteRoot}, base=${viteBase}, apiPrefix=${apiPrefix}, allowedHosts=${allowedHosts.join(',')})`);
|
|
78
|
-
} else {
|
|
79
|
-
// prod / 脱离 clawd 跑:直接 serve 前端静态资源。
|
|
80
|
-
//
|
|
81
|
-
// 路径解析两条路(覆盖 FC 部署与本地 monorepo 启动两种场景):
|
|
82
|
-
// 1. FC(CODE_DIR=./server 只上传 server/,运行时 __dirname=dist/)
|
|
83
|
-
// publish.sh BUILD_CMD 末尾会跑 `mkdir -p dist/public && cp -R ../web/dist/. dist/public/`
|
|
84
|
-
// 把前端打包产物塞进 dist/public,跟着 server 一起上传到 FC。所以 prod 优先用
|
|
85
|
-
// __dirname/public 这条 FC 内可达的绝对路径。
|
|
86
|
-
// 2. 本地 monorepo `node dist/main.js`(极少用,但保留兼容):__dirname/public 不存在
|
|
87
|
-
// 时 fallback 到 ../../web/dist(monorepo 相对路径)。
|
|
88
|
-
//
|
|
89
|
-
// 历史坑:spec 2026-06-03 前只有路径 2,FC 上 404 整个前端 —— 因为 ../../web/dist 在 FC
|
|
90
|
-
// 容器里压根不存在(CODE_DIR 只上传了 server/)。dev 时靠 vite middleware 走内存编译没暴露。
|
|
91
|
-
const fs = await import('node:fs');
|
|
92
|
-
const fcStaticDir = path.resolve(__dirname, 'public');
|
|
93
|
-
const monorepoStaticDir = path.resolve(__dirname, '..', '..', 'web', 'dist');
|
|
94
|
-
const staticDir = fs.existsSync(fcStaticDir) ? fcStaticDir : monorepoStaticDir;
|
|
24
|
+
// 静态资源两条路(覆盖 FC 部署与本地 monorepo 启动两种场景):
|
|
25
|
+
// 1. FC(CODE_DIR=./server 只上传 server/,运行时 __dirname=dist/):publish.sh BUILD_CMD 末尾
|
|
26
|
+
// 把 ../web/dist 拷进 dist/public,跟着 server 一起上传,所以优先用 __dirname/public
|
|
27
|
+
// 2. 本地 `node dist/main.js`:__dirname/public 不存在时 fallback 到 ../../web/dist
|
|
28
|
+
const fs = await import('node:fs');
|
|
29
|
+
const fcStaticDir = path.resolve(__dirname, 'public');
|
|
30
|
+
const monorepoStaticDir = path.resolve(__dirname, '..', '..', 'web', 'dist');
|
|
31
|
+
const staticDir = fs.existsSync(fcStaticDir) ? fcStaticDir : monorepoStaticDir;
|
|
32
|
+
if (fs.existsSync(staticDir)) {
|
|
95
33
|
app.useStaticAssets(staticDir);
|
|
96
|
-
console.log(`
|
|
34
|
+
console.log(`serving static from ${staticDir}`);
|
|
97
35
|
}
|
|
98
36
|
|
|
99
37
|
await app.listen(port, '0.0.0.0');
|
|
100
|
-
console.log(`server listening on :${port} (${isDev ? 'dev' : 'prod'}
|
|
38
|
+
console.log(`server listening on :${port} (${isDev ? 'dev' : 'prod'})`);
|
|
101
39
|
}
|
|
102
40
|
|
|
103
41
|
bootstrap();
|
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
import { defineConfig } from 'vite';
|
|
2
2
|
import react from '@vitejs/plugin-react';
|
|
3
3
|
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
|
|
7
|
-
// 这个 config 仅用于:
|
|
8
|
-
// 1. `vite build` 生成 web/dist 静态资源(prod 由 nest serve)
|
|
9
|
-
// 2. 脱离 clawd 直接 `pnpm dev` 跑 vite 独立模式(本地调试)
|
|
10
|
-
const tunnelHost = process.env.CLAWD_TUNNEL_HOST ?? '';
|
|
11
|
-
const port = Number(process.env.CLAWD_PREVIEW_PORT ?? 5173);
|
|
4
|
+
// dev:`pnpm dev` 独跑 vite,/api 代理到本地 nest(server 默认 3000,API_PORT 可改);
|
|
5
|
+
// build:产物进 web/dist,prod 由 nest serve(publish.sh 把它拷进 server/dist/public)。
|
|
6
|
+
const apiTarget = `http://127.0.0.1:${process.env.API_PORT ?? 3000}`;
|
|
12
7
|
|
|
13
8
|
export default defineConfig({
|
|
14
9
|
plugins: [react()],
|
|
@@ -16,15 +11,9 @@ export default defineConfig({
|
|
|
16
11
|
outDir: 'dist',
|
|
17
12
|
emptyOutDir: true,
|
|
18
13
|
},
|
|
19
|
-
// 隧道经 nest 主进程进来,vite middleware 自己识别 base。standalone 模式(无 tunnel)
|
|
20
|
-
// 走 base: '/' 方便本地直接打开。
|
|
21
|
-
base: tunnelHost ? `/preview/${port}/` : '/',
|
|
22
14
|
server: {
|
|
23
15
|
host: '127.0.0.1',
|
|
24
|
-
port,
|
|
25
|
-
|
|
26
|
-
hmr: tunnelHost
|
|
27
|
-
? { protocol: 'wss', clientPort: 443, path: `/preview/${port}/`, host: tunnelHost }
|
|
28
|
-
: undefined,
|
|
16
|
+
port: Number(process.env.PORT ?? 5173),
|
|
17
|
+
proxy: { '/api': apiTarget },
|
|
29
18
|
},
|
|
30
19
|
});
|
|
@@ -21224,7 +21224,7 @@ async function main() {
|
|
|
21224
21224
|
"call",
|
|
21225
21225
|
{
|
|
21226
21226
|
title: "Call a clawd daemon RPC method",
|
|
21227
|
-
description: 'Call a clawd daemon RPC method over the authenticated local channel. `method` is the canonical method name (e.g. "attachment.signUrl", "persona:list", "
|
|
21227
|
+
description: 'Call a clawd daemon RPC method over the authenticated local channel. `method` is the canonical method name (e.g. "attachment.signUrl", "persona:list", "session:list"); `args` is the JSON body for that method. sessionId is auto-filled from this session unless explicitly provided in args. The daemon is the sole authority on method existence and authorization \u2014 unknown methods or missing permissions surface as errors from the daemon.',
|
|
21228
21228
|
inputSchema: {
|
|
21229
21229
|
method: external_exports.string().min(1).describe('canonical RPC method name, e.g. "persona:list"'),
|
|
21230
21230
|
args: external_exports.record(external_exports.unknown()).optional().describe("JSON arguments for the method")
|