@clawos-dev/clawd 0.2.143-beta.300.c2a3792 → 0.2.143-beta.302.5df5cc1
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 +1632 -824
- package/dist/deploy-kit/.secrets/aliyun.env.example +4 -0
- package/dist/deploy-kit/scripts/new-extension.sh +22 -0
- package/dist/deploy-kit/scripts/publish.sh +100 -0
- package/dist/deploy-kit/scripts/publish.spec.ts +69 -0
- package/dist/{persona-defaults/persona-app-builder/extension-kit → deploy-kit}/scripts/remove-extension.sh +15 -26
- package/dist/dispatch/mcp-server.cjs +39756 -0
- package/dist/persona-defaults/persona-app-builder/CLAUDE.md +16 -13
- package/dist/persona-defaults/persona-app-builder/extension-kit/README.md +24 -18
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/ext.conf +5 -0
- package/dist/persona-defaults/persona-bug-fixer/CLAUDE.md +2 -0
- package/dist/persona-defaults/persona-dataclaw-builder/CLAUDE.md +48 -2
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/README.md +28 -25
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/ext.conf +5 -0
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/hooks/pre-deploy.sh +25 -0
- package/dist/persona-defaults/persona-developer/CLAUDE.md +2 -0
- package/package.json +5 -2
- package/dist/persona-defaults/persona-app-builder/extension-kit/scripts/new-extension.sh +0 -66
- package/dist/persona-defaults/persona-app-builder/extension-kit/scripts/publish.sh +0 -110
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/contract/bootstrap +0 -22
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/scripts/ensure-toolchain.sh +0 -62
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/scripts/new-extension.sh +0 -66
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/scripts/publish.sh +0 -129
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/scripts/remove-extension.sh +0 -70
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/scripts/verify.sh +0 -38
- /package/dist/{persona-defaults/persona-app-builder → deploy-kit}/.secrets/aliyun.env +0 -0
- /package/dist/{persona-defaults/persona-app-builder/extension-kit → deploy-kit}/contract/bootstrap +0 -0
- /package/dist/{persona-defaults/persona-app-builder/extension-kit → deploy-kit}/scripts/ensure-toolchain.sh +0 -0
- /package/dist/{persona-defaults/persona-app-builder/extension-kit → deploy-kit}/scripts/verify.sh +0 -0
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
- **你的文件工作目录**:如果连接上下文(connection prompt)里写了"你的文件工作目录是 xxx",那就是当前用户的专属目录,所有文件读写都放在那里;项目目录在它下面的 `projects/<name>/`
|
|
11
11
|
- **persona 目录只读**:persona 自己的目录(人格 / extension-kit 资产)对你只读,不要往里写任何文件
|
|
12
12
|
- **路径永远以 createProject 返回的 `projectDir` 为准**:不要假设、不要拼接、不要写死任何项目路径
|
|
13
|
-
-
|
|
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` 看一眼现状再开工。
|
|
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
|
## 何时找你
|
|
16
17
|
|
|
@@ -154,13 +155,13 @@ createProject 成功后 daemon 自动:
|
|
|
154
155
|
|
|
155
156
|
不要用 RPC 删(`appBuilder.deleteProject` 故意不暴露给你)。老板在 clawd 删 session 时 daemon 会自动联动停 dev server + 删 project 目录 —— 这是唯一删除路径。
|
|
156
157
|
|
|
157
|
-
##
|
|
158
|
+
## 发布流水线(daemon 自动跑 + 共享 deploy-kit 脚本)
|
|
158
159
|
|
|
159
|
-
|
|
160
|
+
部署不要每次从零手搓。「前后端 + Supabase → 阿里云 FC」已固化成流水线,脚本骨架在共享 `$HOME/.clawd/deploy-kit/scripts/`(凭证 + publish/new-extension/ensure-toolchain/verify/remove,全 persona 共用一份),persona 目录只留模板 + config.env + contract/s.yaml.tmpl:
|
|
160
161
|
|
|
161
|
-
- `
|
|
162
|
+
- **scaffold 由 daemon 自动跑**(createProject 内联调 `deploy-kit/scripts/new-extension.sh`,把本 persona 的 `extension-kit/examples/nestjs-react/` 模板复制进 project)。你不手动调。
|
|
162
163
|
- 你/老板设计 Supabase 表(`<name>_` 前缀)+ 写业务逻辑
|
|
163
|
-
- `
|
|
164
|
+
- **publish 由「发布上线」按钮触发 daemon 自动跑** `deploy-kit/scripts/publish.sh <projDir> <persona根>`(build + 部署 FC + 绑域名 + 验证)。仅发布失败时你接管手动重跑(见下文「发布上线」)。
|
|
164
165
|
|
|
165
166
|
设计是「契约层(框架/语言无关)+ 可插拔技术栈示例」:换框架只改 `ext.conf`,换语言改运行时层 + `contract/bootstrap`。**踩过的 FC 坑全固化在配方记忆 `fc-nodejs-deploy-recipe` 和 `contract/` 里,开工前先看。**
|
|
166
167
|
|
|
@@ -168,7 +169,7 @@ createProject 成功后 daemon 自动:
|
|
|
168
169
|
|
|
169
170
|
- 一律走 **FC(函数计算,serverless)**:免 Docker、闲置缩到 0 按量、适合海量小 extension。
|
|
170
171
|
- Node web 应用走 `custom.debian12` + 官方 Node 层 + `bootstrap` + `fc3-domain` 自定义域名(否则 fcapp.run 默认 URL 强制下载、浏览器不渲染)。
|
|
171
|
-
- 工具链:**`aliyun` CLI + Serverless Devs(`s`)**——即 `publish.sh` 内部做的事。两者由 `
|
|
172
|
+
- 工具链:**`aliyun` CLI + Serverless Devs(`s`)**——即 `publish.sh` 内部做的事。两者由 `deploy-kit/scripts/ensure-toolchain.sh` 在 publish/remove 动手前自动检测+安装(缺 `aliyun`:有 brew 走 `brew install aliyun-cli`,否则下官方二进制到 `~/.clawd/bin`;缺 `s`:`npm i -g @serverless-devs/s`),新机器无需手动装。接管发布失败时**不必再排查"CLI 没装"**。
|
|
172
173
|
- 实时推送用 Supabase Realtime(FC 不维持长连接)。
|
|
173
174
|
|
|
174
175
|
## 后端:supabase MCP
|
|
@@ -181,26 +182,26 @@ createProject 成功后 daemon 自动:
|
|
|
181
182
|
|
|
182
183
|
**易踩的坑**:MCP 工具走阿里云自建那台,假如哪一步把 `.env` / `config.env` 写成云上 `qmhqmopgwsbmfxiqagay.supabase.co`(曾经的旧默认),就会**MCP 建表落阿里云 / server 通过 supabase-js 查云上**,两台 PostgREST schema cache 各自独立 → `PGRST205 schema cache 找不到表`。看到 PGRST 系列错码先 `grep -r supabase.co` 自查模板配置,再怀疑 schema cache。
|
|
183
184
|
|
|
184
|
-
##
|
|
185
|
+
## 阿里云凭证:在共享 deploy-kit 的 `.secrets/aliyun.env`,用时现读
|
|
185
186
|
|
|
186
|
-
AK/SK
|
|
187
|
+
AK/SK 放在**共享 deploy-kit** 下的 **`$HOME/.clawd/deploy-kit/.secrets/aliyun.env`**(`ALIBABA_CLOUD_ACCESS_KEY_ID` + `ALIBABA_CLOUD_ACCESS_KEY_SECRET`,全 FC persona 共用一份),是唯一真源,不在系统环境变量里。publish.sh 内部会自己加载它;你手动跑 CLI 时先在当前 shell 加载:
|
|
187
188
|
|
|
188
189
|
```bash
|
|
189
|
-
set -a; . "$HOME/.clawd/
|
|
190
|
+
set -a; . "$HOME/.clawd/deploy-kit/.secrets/aliyun.env"; set +a
|
|
190
191
|
```
|
|
191
192
|
|
|
192
193
|
之后 `aliyun` / `s` / `ossutil` 会自动读这两个环境变量。Serverless Devs 还需 `~/.s/access.yaml` 含 `AccountID`(用 `aliyun sts GetCallerIdentity` 取),SK 走 heredoc 写入、不进命令行。
|
|
193
194
|
|
|
194
|
-
- **红线**:绝不 `echo` / 打印 / 写进日志或提交记录里暴露 SK;绝不把 `.secrets/`
|
|
195
|
+
- **红线**:绝不 `echo` / 打印 / 写进日志或提交记录里暴露 SK;绝不把 `.secrets/` 拷进被部署的项目代码(已被 `.gitignore` 挡住)。SK 为空时先停下提醒老板去 `$HOME/.clawd/deploy-kit/.secrets/aliyun.env` 填,别瞎试。
|
|
195
196
|
|
|
196
197
|
## 工作主线
|
|
197
198
|
|
|
198
199
|
一个 project 大致走这四段,按需推进、不必僵化:
|
|
199
200
|
|
|
200
201
|
1. **创建 project**(fresh session 进来时)—— 问老板想做啥 app + 反问名字 + 调 `appBuilder.createProject` RPC(详见上文「触发 createProject」)。已绑 session 跳过这一步
|
|
201
|
-
2. **开发前后端** —— scaffold
|
|
202
|
+
2. **开发前后端** —— scaffold 已由 daemon 在 createProject 时自动跑(你摸不到模板源);你直接在 projectDir 里写业务。复杂任务走 superpowers 节奏,简单任务 TodoWrite 跟踪(分级见下)
|
|
202
203
|
3. **接 Supabase** —— 数据/认证/存储走 supabase MCP,schema 以线上为准,新表加 `<项目名>_` 前缀
|
|
203
|
-
4. **部署** —— `
|
|
204
|
+
4. **部署** —— 老板点「发布上线」按钮,daemon 自动跑 `deploy-kit/scripts/publish.sh`(build + 发 FC + 绑域名 + 验证 + 写 prodUrl),happy path 跳过你。仅发布失败时你接管手动重跑(见下文「发布上线」)。
|
|
204
205
|
|
|
205
206
|
## build 模式(clawd 双栏实时预览)
|
|
206
207
|
|
|
@@ -260,7 +261,7 @@ await app.listen(Number(process.env.CLAWD_PREVIEW_PORT));
|
|
|
260
261
|
|
|
261
262
|
1. 读项目目录下的 `.publish.log` 完整日志
|
|
262
263
|
2. 定位并修复问题(改源码 / 改 `ext.conf` / 检查 `.secrets/aliyun.env` 等)
|
|
263
|
-
3. 重跑 `bash "$HOME/.clawd/
|
|
264
|
+
3. 重跑 `bash "$HOME/.clawd/deploy-kit/scripts/publish.sh" <projDir> "$HOME/.clawd/personas/persona-app-builder"` 直到 exit 0 且输出公网 URL(第二参是本 persona 根,脚本据此读 config.env / s.yaml.tmpl)
|
|
264
265
|
4. 调 `appBuilder.setProdUrl` 把 URL 写入项目元数据(同上文)
|
|
265
266
|
|
|
266
267
|
**注意**:重跑期间不要自己调 `appBuilder.publish` RPC(那是给 UI 按钮用的,会被 daemon 当作"已 in-flight"拒绝),手动跑脚本即可。失败回灌的消息开头固定是 `发布失败:[`,识别此前缀即触发接管流程。
|
|
@@ -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`。
|
|
@@ -6,40 +6,46 @@
|
|
|
6
6
|
|
|
7
7
|
FC 对应用只有一条要求:**一个监听 `$PORT` 的 HTTP server**。所以固化的是「发布管道」,不是技术栈。
|
|
8
8
|
|
|
9
|
-
-
|
|
10
|
-
-
|
|
9
|
+
- **共享 deploy-kit(凭证 + 脚本骨架 + bootstrap,全 FC persona 一份)**:`$HOME/.clawd/deploy-kit/`(`scripts/` + `contract/bootstrap` + `.secrets/aliyun.env`)
|
|
10
|
+
- **persona 特化(本目录)**:`config.env`(注入值) + `contract/s.yaml.tmpl`(占位符清单) + `examples/`(默认 `nestjs-react`)
|
|
11
11
|
|
|
12
12
|
## 目录
|
|
13
13
|
|
|
14
14
|
```
|
|
15
|
+
# 共享 deploy-kit($HOME/.clawd/deploy-kit/,daemon 维护,全 persona 共用)
|
|
16
|
+
deploy-kit/
|
|
17
|
+
├── .secrets/aliyun.env 阿里云凭证(单源)
|
|
18
|
+
├── contract/bootstrap FC 启动入口(Node 参考实现:find node + cd + exec)
|
|
19
|
+
└── scripts/
|
|
20
|
+
├── new-extension.sh 从示例生成新 extension(纯 copy,daemon 自动跑)
|
|
21
|
+
├── publish.sh 一键发布(占位符通用替换 → build → 部署 → 绑域名 → 验证)
|
|
22
|
+
├── verify.sh 健康检查(含「是否被强制下载」检测)
|
|
23
|
+
└── remove-extension.sh 清理(s remove FC 资源 + 按 ext.conf 删 Supabase 表)
|
|
24
|
+
|
|
25
|
+
# 本 persona 的 extension-kit(只放特化资产)
|
|
15
26
|
extension-kit/
|
|
16
|
-
├── config.env
|
|
17
|
-
├── contract/
|
|
18
|
-
|
|
19
|
-
│ └── s.yaml.tmpl FC 部署模板(占位符)
|
|
20
|
-
├── scripts/
|
|
21
|
-
│ ├── new-extension.sh 从示例生成新 extension + 写 ext.conf
|
|
22
|
-
│ ├── publish.sh 一键发布(build → 部署 → 绑域名 → 验证)
|
|
23
|
-
│ ├── verify.sh 健康检查(含「是否被强制下载」检测)
|
|
24
|
-
│ └── remove-extension.sh 清理(s remove FC 资源 + 按 ext.conf 删 Supabase 表)
|
|
25
|
-
└── examples/nestjs-react/ 默认示例(就是跑通的留言板)
|
|
27
|
+
├── config.env persona 注入值(region / Node层 / Supabase)
|
|
28
|
+
├── contract/s.yaml.tmpl FC 部署模板(占位符,publish.sh 通用扫描替换)
|
|
29
|
+
└── examples/nestjs-react/ 默认示例(含模板自带 ext.conf)
|
|
26
30
|
```
|
|
27
31
|
|
|
28
32
|
## 用法
|
|
29
33
|
|
|
34
|
+
日常发布 happy path 由 daemon 自动跑(createProject 内联 scaffold、「发布上线」按钮触发 publish),你一般不手动调脚本。需手动跑时(如发布失败接管)用共享 deploy-kit 的绝对路径,**第二参是本 persona 根**:
|
|
35
|
+
|
|
30
36
|
```bash
|
|
31
|
-
|
|
32
|
-
|
|
37
|
+
PR="$HOME/.clawd/personas/persona-app-builder"; DK="$HOME/.clawd/deploy-kit"
|
|
38
|
+
|
|
39
|
+
# 1) scaffold(daemon 已自动跑;手动签名: new-extension.sh <name> <模板源目录> <目标目录>)
|
|
33
40
|
|
|
34
41
|
# 2) 设计 Supabase 表(务必 myapp_ 前缀)、改 web/ 和 server/ 业务逻辑
|
|
35
42
|
# 建表后把表名填进 myapp/ext.conf 的 SUPABASE_TABLES,清理时才删得掉
|
|
36
|
-
# 本地跑: cd myapp/server && npm i && npm run build && node --env-file=.env dist/main.js
|
|
37
43
|
|
|
38
44
|
# 3) 一键发布到 FC,输出公网地址
|
|
39
|
-
|
|
45
|
+
"$DK/scripts/publish.sh" <projDir> "$PR"
|
|
40
46
|
|
|
41
47
|
# 4) 不要了就一键清理(FC 资源 + 它的 Supabase 表)
|
|
42
|
-
|
|
48
|
+
"$DK/scripts/remove-extension.sh" <projDir> "$PR"
|
|
43
49
|
```
|
|
44
50
|
|
|
45
51
|
## 流程与固化程度
|
|
@@ -55,7 +61,7 @@ extension-kit/scripts/remove-extension.sh ../myapp
|
|
|
55
61
|
## 清理一个 extension
|
|
56
62
|
|
|
57
63
|
```bash
|
|
58
|
-
|
|
64
|
+
"$HOME/.clawd/deploy-kit/scripts/remove-extension.sh" <projDir> "$HOME/.clawd/personas/persona-app-builder"
|
|
59
65
|
```
|
|
60
66
|
|
|
61
67
|
先 `s remove` 删 FC 函数+触发器+自定义域名,再按 `ext.conf` 的 `SUPABASE_TABLES` 删它的表。
|
package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/ext.conf
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
CODE_DIR=./server
|
|
2
|
+
FC_ENTRY=dist/main.js
|
|
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
|
+
# 本 extension 拥有的 Supabase 表(空格分隔,必须 <name>_ 前缀);remove-extension.sh 据此清理
|
|
5
|
+
SUPABASE_TABLES=""
|
|
@@ -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`。
|
|
@@ -2,8 +2,54 @@
|
|
|
2
2
|
|
|
3
3
|
你是 clawd 里的「数据查询搭建师」。一句话定位:**帮 TTC 业务用户把特定的数据查询条件,做成一个只读的 GUI 查询页面,背后复用 dataclaw 的查询能力。** 你做出来的页面是独立 app,用 app-builder 流水线起、双栏实时预览、发阿里云 FC。
|
|
4
4
|
|
|
5
|
-
##
|
|
6
|
-
|
|
5
|
+
## 工作流(daemon HTTP RPC)
|
|
6
|
+
|
|
7
|
+
你复用的是 daemon 里那条 `appBuilder.*` RPC 流水线(persona 无关,走本机 HTTP 到达),**不是** app-builder persona 的磁盘资产——你摸不到也不需要它。下面是你要做的全部,自包含照做即可。
|
|
8
|
+
|
|
9
|
+
daemon 启动你时已注入两个 env:
|
|
10
|
+
- `CLAWD_DAEMON_URL` = `http://127.0.0.1:<port>`(RPC base)
|
|
11
|
+
- `CLAWD_SESSION_ID` = 当前 session id
|
|
12
|
+
|
|
13
|
+
所有调用都是 `POST $CLAWD_DAEMON_URL/api/rpc/<method>` + JSON body + `X-Clawd-Session-Id` header。
|
|
14
|
+
|
|
15
|
+
**状态 A:未绑 project(fresh session)** —— 起手三步:
|
|
16
|
+
1. 主动问「老板想看什么数据的页?」(一句话,别长 prompt)
|
|
17
|
+
2. 老板描述需求 → **反问「项目叫什么名字」**(不擅自命名)
|
|
18
|
+
3. 拿到名字后建 project:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
curl -sS -X POST "$CLAWD_DAEMON_URL/api/rpc/appBuilder.createProject" \
|
|
22
|
+
-H "Content-Type: application/json" -H "X-Clawd-Session-Id: $CLAWD_SESSION_ID" \
|
|
23
|
+
-d '{"name":"<老板给的名字>"}'
|
|
24
|
+
```
|
|
25
|
+
返回 `result.projectDir` 就是项目目录绝对路径,**记住它**,后续 cd / 写文件都用它。重名报 `VALIDATION_ERROR` → 让老板换名。createProject 后 daemon 自动 scaffold 模板(dataclaw 起手 app)+ 设 stage='install-pending'。
|
|
26
|
+
|
|
27
|
+
**状态 B:已绑 project** —— UI 双栏(左 chat + 右实时预览)。接着按顺序做 3 步:
|
|
28
|
+
|
|
29
|
+
1. 上报安装 + 装依赖:
|
|
30
|
+
```bash
|
|
31
|
+
curl -sS -X POST "$CLAWD_DAEMON_URL/api/rpc/appBuilder.reportStage" \
|
|
32
|
+
-H "Content-Type: application/json" -H "X-Clawd-Session-Id: $CLAWD_SESSION_ID" \
|
|
33
|
+
-d '{"stage":"installing"}'
|
|
34
|
+
```
|
|
35
|
+
然后 `cd <projectDir>` → `pnpm install`(模板有 server/ 和 web/ 两套 deps)。
|
|
36
|
+
|
|
37
|
+
2. 起 dev server(双栏预览):
|
|
38
|
+
```bash
|
|
39
|
+
curl -sS -X POST "$CLAWD_DAEMON_URL/api/rpc/appBuilder.startDevServer" \
|
|
40
|
+
-H "Content-Type: application/json" -H "X-Clawd-Session-Id: $CLAWD_SESSION_ID" -d '{}'
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
3. 任一步失败上报 failed:
|
|
44
|
+
```bash
|
|
45
|
+
curl -sS -X POST "$CLAWD_DAEMON_URL/api/rpc/appBuilder.reportStage" \
|
|
46
|
+
-H "Content-Type: application/json" -H "X-Clawd-Session-Id: $CLAWD_SESSION_ID" \
|
|
47
|
+
-d '{"stage":"failed","reason":"<一句话原因>"}'
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**stage 规则**:你只能上报 `installing` / `failed`;`install-pending` / `starting-dev-server` / `running` / `paused` 由 daemon 自动设(防谎报)。**scaffold 不是你的步骤**(daemon 自动跑,你摸不到模板源)。
|
|
51
|
+
|
|
52
|
+
**发布上线**:老板点 PreviewPane「发布上线」按钮走 daemon RPC,happy path 完全跳过你。只有失败时 daemon 才发来一条以 `发布失败:[` 开头的消息(含 `.publish.log` 路径),那时你读日志、修问题、重跑发布。改完代码即时 HMR 刷新,不重新部署。
|
|
7
53
|
|
|
8
54
|
## 数据来源:dataclaw-service(只读,通路 A)
|
|
9
55
|
生成的 app 后端直调 dataclaw-service HTTP(`/api/flexible-query`、`/api/intent-execute`、`/api/schema`),用**建页人(builder)的身份**取数、纯展示、不写数据。具体查询能力见 §「dataclaw 查询能力」。
|
|
@@ -6,40 +6,43 @@
|
|
|
6
6
|
|
|
7
7
|
FC 对应用只有一条要求:**一个监听 `$PORT` 的 HTTP server**。所以固化的是「发布管道」,不是技术栈。
|
|
8
8
|
|
|
9
|
-
-
|
|
10
|
-
-
|
|
9
|
+
- **共享 deploy-kit(凭证 + 脚本骨架 + bootstrap,全 FC persona 一份)**:`$HOME/.clawd/deploy-kit/`(`scripts/` + `contract/bootstrap` + `.secrets/aliyun.env`)
|
|
10
|
+
- **persona 特化(本目录)**:`config.env`(dataclaw/TTC 注入值) + `contract/s.yaml.tmpl`(占位符清单) + `hooks/pre-deploy.sh`(发布前置) + `examples/`(默认 `nestjs-react`)
|
|
11
11
|
|
|
12
12
|
## 目录
|
|
13
13
|
|
|
14
14
|
```
|
|
15
|
+
# 共享 deploy-kit($HOME/.clawd/deploy-kit/,daemon 维护,全 persona 共用)
|
|
16
|
+
deploy-kit/
|
|
17
|
+
├── .secrets/aliyun.env 阿里云凭证(单源)
|
|
18
|
+
├── contract/bootstrap FC 启动入口(Node 参考实现:find node + cd + exec)
|
|
19
|
+
└── scripts/
|
|
20
|
+
├── new-extension.sh 从示例生成新 extension(纯 copy,daemon 自动跑)
|
|
21
|
+
├── publish.sh 一键发布(占位符通用替换 → build → 部署 → 绑域名 → 验证)
|
|
22
|
+
├── verify.sh 健康检查(含「是否被强制下载」检测)
|
|
23
|
+
└── remove-extension.sh 清理(s remove FC 资源)
|
|
24
|
+
|
|
25
|
+
# 本 persona 的 extension-kit(只放特化资产)
|
|
15
26
|
extension-kit/
|
|
16
|
-
├── config.env
|
|
17
|
-
├── contract/
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
├── scripts/
|
|
21
|
-
│ ├── new-extension.sh 从示例生成新 extension + 写 ext.conf
|
|
22
|
-
│ ├── publish.sh 一键发布(build → 部署 → 绑域名 → 验证)
|
|
23
|
-
│ ├── verify.sh 健康检查(含「是否被强制下载」检测)
|
|
24
|
-
│ └── remove-extension.sh 清理(s remove FC 资源 + 按 ext.conf 删 Supabase 表)
|
|
25
|
-
└── examples/nestjs-react/ 默认示例(就是跑通的留言板)
|
|
27
|
+
├── config.env dataclaw/TTC 注入值(region / Node层 / DATACLAW_SERVICE_URL / TTC_*)
|
|
28
|
+
├── contract/s.yaml.tmpl FC 部署模板(占位符,publish.sh 通用扫描替换)
|
|
29
|
+
├── hooks/pre-deploy.sh 发布前置(校验 dataclaw.env、生成 SESSION_SECRET、推导 APP_BASE_URL)
|
|
30
|
+
└── examples/nestjs-react/ 默认示例(含模板自带 ext.conf)
|
|
26
31
|
```
|
|
27
32
|
|
|
28
33
|
## 用法
|
|
29
34
|
|
|
30
|
-
|
|
31
|
-
# 1) 起一个新 extension(从默认示例)
|
|
32
|
-
extension-kit/scripts/new-extension.sh myapp
|
|
33
|
-
|
|
34
|
-
# 2) 设计 Supabase 表(务必 myapp_ 前缀)、改 web/ 和 server/ 业务逻辑
|
|
35
|
-
# 建表后把表名填进 myapp/ext.conf 的 SUPABASE_TABLES,清理时才删得掉
|
|
36
|
-
# 本地跑: cd myapp/server && npm i && npm run build && node --env-file=.env dist/main.js
|
|
35
|
+
日常 happy path 由 daemon 自动跑(createProject 内联 scaffold、「发布上线」按钮触发 publish)。需手动跑时用共享 deploy-kit 绝对路径,**第二参是本 persona 根**:
|
|
37
36
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
#
|
|
42
|
-
|
|
37
|
+
```bash
|
|
38
|
+
PR="$HOME/.clawd/personas/persona-dataclaw-builder"; DK="$HOME/.clawd/deploy-kit"
|
|
39
|
+
|
|
40
|
+
# 1) scaffold(daemon 已自动跑;手动签名: new-extension.sh <name> <模板源目录> <目标目录>)
|
|
41
|
+
# 2) 改 web/ 和 server/ 业务逻辑(只读查 dataclaw-service,不接 Supabase 写库)
|
|
42
|
+
# 3) 一键发布到 FC,输出公网地址(发布前需在 $PR/.secrets/dataclaw.env 填 DATACLAW_INTERNAL_SECRET)
|
|
43
|
+
"$DK/scripts/publish.sh" <projDir> "$PR"
|
|
44
|
+
# 4) 不要了就一键清理 FC 资源
|
|
45
|
+
"$DK/scripts/remove-extension.sh" <projDir> "$PR"
|
|
43
46
|
```
|
|
44
47
|
|
|
45
48
|
## 流程与固化程度
|
|
@@ -55,7 +58,7 @@ extension-kit/scripts/remove-extension.sh ../myapp
|
|
|
55
58
|
## 清理一个 extension
|
|
56
59
|
|
|
57
60
|
```bash
|
|
58
|
-
|
|
61
|
+
"$HOME/.clawd/deploy-kit/scripts/remove-extension.sh" <projDir> "$HOME/.clawd/personas/persona-dataclaw-builder"
|
|
59
62
|
```
|
|
60
63
|
|
|
61
64
|
先 `s remove` 删 FC 函数+触发器+自定义域名,再按 `ext.conf` 的 `SUPABASE_TABLES` 删它的表。
|
package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/ext.conf
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
CODE_DIR=./server
|
|
2
|
+
FC_ENTRY=dist/main.js
|
|
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
|
+
# 建页人 union_id(dataclaw 数据范围按它裁剪);persona 建页时调 auth.getIdentity 取到后填入。
|
|
5
|
+
DATACLAW_AS_UNION_ID=""
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# dataclaw 发布前置:校验 persona 密钥、生成/推导运行时变量并 export,供通用占位符替换取用。
|
|
3
|
+
# 由 deploy-kit/publish.sh 在 aliyun 就绪后、渲染 s.yaml 前 source(共享 EXT_DIR/APP_NAME/ACC/REGION/PERSONA_KIT 作用域)。
|
|
4
|
+
|
|
5
|
+
# dataclaw 服务间密钥(现读,不进 git);缺失则拒发布(app 没它查不到数)。
|
|
6
|
+
# 密钥在 persona-root 的 .secrets/(example/gitignore 白名单都在这一层),不是 extension-kit/.secrets/。
|
|
7
|
+
[ -f "$PERSONA_ROOT/.secrets/dataclaw.env" ] || {
|
|
8
|
+
echo "❌ 缺 $PERSONA_ROOT/.secrets/dataclaw.env(含 DATACLAW_INTERNAL_SECRET)。参考 $PERSONA_ROOT/.secrets/dataclaw.env.example 填写。" >&2
|
|
9
|
+
exit 1
|
|
10
|
+
}
|
|
11
|
+
set -a; . "$PERSONA_ROOT/.secrets/dataclaw.env"; set +a
|
|
12
|
+
|
|
13
|
+
# SESSION_SECRET:每个 app 一份、跨重部署稳定(否则重发后所有人 session 失效)。
|
|
14
|
+
# 首次发布生成并持久化到 ext.conf;之后 source ext.conf 已带上。
|
|
15
|
+
if [ -z "${SESSION_SECRET:-}" ]; then
|
|
16
|
+
SESSION_SECRET="$(openssl rand -hex 32)"
|
|
17
|
+
echo "SESSION_SECRET=$SESSION_SECRET" >> "$EXT_DIR/ext.conf"
|
|
18
|
+
fi
|
|
19
|
+
export SESSION_SECRET
|
|
20
|
+
|
|
21
|
+
# APP_BASE_URL:飞书登录回调用。fc3 自定义域名由 APP_NAME+ACC+REGION 推导,用 https(cookie secure 要求)。
|
|
22
|
+
export APP_BASE_URL="https://${APP_NAME}.fcv3.${ACC}.${REGION}.fc.devsapp.net"
|
|
23
|
+
|
|
24
|
+
# 可选占位符显式兜底(未填时通用扫描需「已定义」才放行)
|
|
25
|
+
export DATACLAW_AS_UNION_ID="${DATACLAW_AS_UNION_ID:-}"
|
|
@@ -115,3 +115,5 @@ done
|
|
|
115
115
|
⚠️ **`.mcp.json` 是 daemon 管理文件**(在 `DAEMON_MANAGED_PATHS` 里),每次 daemon 启动会用 bundle 版本覆盖,**不要手改本地**——要改 MCP 配置去改 daemon defaults 源 + 提 PR。
|
|
116
116
|
|
|
117
117
|
**要加别的 MCP server**(项目专属 / 个人用):在**项目目录**(不是 persona 目录)下加 `.mcp.json`,跟 persona 这份不冲突。或者扩 daemon defaults 给所有用户。
|
|
118
|
+
|
|
119
|
+
> dispatch 教学(`@persona/<id>` 委派 / `personaDispatchComplete` 回报)由 daemon 通过 `--append-system-prompt` 给所有 cc spawn 统一注入,不在这份 CLAUDE.md 重复。源见 `daemon/src/dispatch/system-prompt.ts`。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clawos-dev/clawd",
|
|
3
|
-
"version": "0.2.143-beta.
|
|
3
|
+
"version": "0.2.143-beta.302.5df5cc1",
|
|
4
4
|
"description": "Standalone clawd daemon — Claude Code (and future Codex) session server over WebSocket",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@clawos/clawd-protocol": "workspace:*",
|
|
30
|
+
"@clawos/log-client": "workspace:*",
|
|
30
31
|
"@types/diff": "^7.0.2",
|
|
31
32
|
"@types/node": "^22.0.0",
|
|
32
33
|
"@types/uuid": "^10.0.0",
|
|
@@ -48,8 +49,10 @@
|
|
|
48
49
|
},
|
|
49
50
|
"dependencies": {
|
|
50
51
|
"@lydell/node-pty": "1.2.0-beta.12",
|
|
52
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
51
53
|
"@xterm/addon-serialize": "^0.14.0",
|
|
52
54
|
"@xterm/headless": "^6.0.0",
|
|
53
|
-
"jszip": "^3.10.1"
|
|
55
|
+
"jszip": "^3.10.1",
|
|
56
|
+
"undici": "^6"
|
|
54
57
|
}
|
|
55
58
|
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# 从示例模板生成一个新 extension
|
|
3
|
-
# 用法: new-extension.sh <name> [示例模板, 默认 nestjs-react] [目标项目目录绝对路径]
|
|
4
|
-
#
|
|
5
|
-
# 目标目录解析(优先级从高到低):
|
|
6
|
-
# - 显式目标目录(第三参,v2 per-user):daemon createProject 已在
|
|
7
|
-
# users/<userId>/projects/<name>/ 建好(含 .clawd-project.json)→ 把模板展开到此目录。
|
|
8
|
-
# v2 多人隔离后项目不再落 persona 目录下,脚本无法自行推导,必须由 daemon 显式传入。
|
|
9
|
-
# - picker 模式(老布局兼容):persona-app-builder/projects/<name>/ 已由 daemon 建好
|
|
10
|
-
# - 老路径兼容:以上都不存在时回退到 persona-app-builder/<name>/
|
|
11
|
-
set -euo pipefail
|
|
12
|
-
KIT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
13
|
-
PERSONA_DIR="$(cd "$KIT_DIR/.." && pwd)"
|
|
14
|
-
|
|
15
|
-
NAME="${1:?用法: new-extension.sh <name> [模板,默认 nestjs-react] [目标目录]}"
|
|
16
|
-
TMPL="${2:-nestjs-react}"
|
|
17
|
-
DEST_OVERRIDE="${3:-}"
|
|
18
|
-
SRC="$KIT_DIR/examples/$TMPL"
|
|
19
|
-
|
|
20
|
-
[ -d "$SRC" ] || { echo "示例模板不存在: $SRC (现有: $(ls "$KIT_DIR/examples"))"; exit 1; }
|
|
21
|
-
|
|
22
|
-
# 把模板内容展开到一个已含 .clawd-project.json 的目录(daemon 预建)
|
|
23
|
-
expand_into() {
|
|
24
|
-
local dest="$1"
|
|
25
|
-
local remaining
|
|
26
|
-
remaining="$(find "$dest" -mindepth 1 -maxdepth 1 ! -name '.clawd-project.json' -print -quit)"
|
|
27
|
-
[ -z "$remaining" ] || { echo "目标已有内容(非空目录): $dest"; exit 1; }
|
|
28
|
-
cp -R "$SRC/." "$dest/"
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
PICKER_DEST="$PERSONA_DIR/projects/$NAME"
|
|
32
|
-
if [ -n "$DEST_OVERRIDE" ]; then
|
|
33
|
-
# v2 per-user:daemon 显式传入目标目录
|
|
34
|
-
DEST="$DEST_OVERRIDE"
|
|
35
|
-
[ -d "$DEST" ] && [ -f "$DEST/.clawd-project.json" ] || {
|
|
36
|
-
echo "目标目录无效(必须已存在且含 .clawd-project.json): $DEST"; exit 1
|
|
37
|
-
}
|
|
38
|
-
expand_into "$DEST"
|
|
39
|
-
elif [ -d "$PICKER_DEST" ] && [ -f "$PICKER_DEST/.clawd-project.json" ]; then
|
|
40
|
-
# picker 模式(老布局):目录已存在且仅含 .clawd-project.json
|
|
41
|
-
DEST="$PICKER_DEST"
|
|
42
|
-
expand_into "$DEST"
|
|
43
|
-
else
|
|
44
|
-
DEST="$PERSONA_DIR/$NAME"
|
|
45
|
-
[ -e "$DEST" ] && { echo "目标已存在: $DEST"; exit 1; }
|
|
46
|
-
cp -r "$SRC" "$DEST"
|
|
47
|
-
fi
|
|
48
|
-
|
|
49
|
-
# 写 ext.conf(框架相关配置,publish.sh 会读)
|
|
50
|
-
cat > "$DEST/ext.conf" <<EOF
|
|
51
|
-
APP_NAME=$NAME
|
|
52
|
-
CODE_DIR=./server
|
|
53
|
-
FC_ENTRY=dist/main.js
|
|
54
|
-
BUILD_CMD="cd web && pnpm install && pnpm build && cd ../server && pnpm install && pnpm build && mkdir -p dist/public && cp -R ../web/dist/. dist/public/"
|
|
55
|
-
# 本 extension 拥有的 Supabase 表(空格分隔,必须 ${NAME}_ 前缀);remove-extension.sh 据此清理
|
|
56
|
-
SUPABASE_TABLES=""
|
|
57
|
-
EOF
|
|
58
|
-
|
|
59
|
-
echo "✅ 新 extension: $DEST"
|
|
60
|
-
echo ""
|
|
61
|
-
echo "接下来:"
|
|
62
|
-
echo " 1) 设计 Supabase 表(务必用 ${NAME}_ 前缀,别碰 clawos 已有表)"
|
|
63
|
-
echo " 2) 改前端(web/)和后端(server/)业务逻辑"
|
|
64
|
-
echo " 3) 本地验证后发布: extension-kit/scripts/publish.sh $DEST"
|
|
65
|
-
echo ""
|
|
66
|
-
echo "换框架/语言: 改 ext.conf 的 BUILD_CMD/FC_ENTRY;换语言再看 extension-kit/README.md"
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# ============================================================
|
|
3
|
-
# 一键发布 extension 到阿里云 FC(框架无关)
|
|
4
|
-
# 用法: publish.sh <extension目录>
|
|
5
|
-
# extension 目录里可放 ext.conf 声明框架相关的: APP_NAME/CODE_DIR/FC_ENTRY/BUILD_CMD
|
|
6
|
-
# ============================================================
|
|
7
|
-
set -euo pipefail
|
|
8
|
-
|
|
9
|
-
KIT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
10
|
-
PERSONA_DIR="$(cd "$KIT_DIR/.." && pwd)"
|
|
11
|
-
|
|
12
|
-
EXT_DIR="${1:?用法: publish.sh <extension目录>}"
|
|
13
|
-
EXT_DIR="$(cd "$EXT_DIR" && pwd)"
|
|
14
|
-
|
|
15
|
-
# 1) 平台变量 + extension 自身配置(后者覆盖前者)
|
|
16
|
-
source "$KIT_DIR/config.env"
|
|
17
|
-
[ -f "$EXT_DIR/ext.conf" ] && source "$EXT_DIR/ext.conf"
|
|
18
|
-
APP_NAME="${APP_NAME:-$(basename "$EXT_DIR")}"
|
|
19
|
-
CODE_DIR="${CODE_DIR:-./server}"
|
|
20
|
-
FC_ENTRY="${FC_ENTRY:-dist/main.js}"
|
|
21
|
-
BUILD_CMD="${BUILD_CMD:-}"
|
|
22
|
-
|
|
23
|
-
# 2) 工具链就绪(检测环境,缺 aliyun CLI / Serverless Devs 则装,已装跳过)+ 阿里云凭证
|
|
24
|
-
source "$KIT_DIR/scripts/ensure-toolchain.sh"
|
|
25
|
-
ensure_toolchain
|
|
26
|
-
|
|
27
|
-
# 阿里云凭证(现读)+ Serverless Devs 认证(必须含 AccountID,SK 走 heredoc 不进命令行)
|
|
28
|
-
set -a; . "$PERSONA_DIR/.secrets/aliyun.env"; set +a
|
|
29
|
-
# aliyun CLI 需要 region;新机器没跑过 `aliyun configure`(无 ~/.aliyun/config.json) → "region can't be empty"。
|
|
30
|
-
# 用 env 给 region(REGION 来自 config.env):一处覆盖脚本里所有 aliyun 调用,不进 argv,和 AK/SK 走 env 的方式对称。
|
|
31
|
-
export ALIBABA_CLOUD_REGION_ID="$REGION"
|
|
32
|
-
# aliyun 报错不再吞进 2>/dev/null —— 否则凭证失效 / CLI 异常时只剩一条无头 python traceback(老板踩过)。
|
|
33
|
-
_ident="$(aliyun sts GetCallerIdentity 2>&1)" || { echo "❌ aliyun sts GetCallerIdentity 失败(检查 .secrets/aliyun.env 凭证):" >&2; echo "$_ident" >&2; exit 1; }
|
|
34
|
-
ACC="$(printf '%s' "$_ident" | python3 -c 'import sys,json;print(json.load(sys.stdin)["AccountId"])')" || { echo "❌ 解析 AccountId 失败,aliyun 输出非 JSON:" >&2; echo "$_ident" >&2; exit 1; }
|
|
35
|
-
mkdir -p ~/.s
|
|
36
|
-
cat > ~/.s/access.yaml <<EOF
|
|
37
|
-
default:
|
|
38
|
-
AccountID: '$ACC'
|
|
39
|
-
AccessKeyID: $ALIBABA_CLOUD_ACCESS_KEY_ID
|
|
40
|
-
AccessKeySecret: $ALIBABA_CLOUD_ACCESS_KEY_SECRET
|
|
41
|
-
EOF
|
|
42
|
-
chmod 600 ~/.s/access.yaml
|
|
43
|
-
|
|
44
|
-
# 3) 构建产物(框架相关,由 ext.conf 的 BUILD_CMD 定义)
|
|
45
|
-
# 发布上线脚手架化 (spec 2026-06-03-app-builder-publish-scaffold-design §5.4):
|
|
46
|
-
# echo ::stage::build 在 if 外,BUILD_CMD 为空时也通知 daemon 进入 build 阶段(即使啥也没构建)。
|
|
47
|
-
# daemon 解析 stdout 命中此 marker 后 broadcast appBuilder:publish-progress 帧推 UI checklist。
|
|
48
|
-
echo "::stage::build"
|
|
49
|
-
if [ -n "$BUILD_CMD" ]; then
|
|
50
|
-
echo "==> build: $BUILD_CMD"
|
|
51
|
-
( cd "$EXT_DIR" && eval "$BUILD_CMD" )
|
|
52
|
-
fi
|
|
53
|
-
|
|
54
|
-
# 4) 放置框架无关的 bootstrap 到 code 目录
|
|
55
|
-
cp "$KIT_DIR/contract/bootstrap" "$EXT_DIR/$CODE_DIR/bootstrap"
|
|
56
|
-
chmod +x "$EXT_DIR/$CODE_DIR/bootstrap"
|
|
57
|
-
|
|
58
|
-
# 5) 渲染 s.yaml(| 作分隔符,避免 URL/层 ARN 里的 / : 冲突)
|
|
59
|
-
sed -e "s|__APP_NAME__|$APP_NAME|g" \
|
|
60
|
-
-e "s|__REGION__|$REGION|g" \
|
|
61
|
-
-e "s|__FC_RUNTIME__|$FC_RUNTIME|g" \
|
|
62
|
-
-e "s|__NODE_LAYER__|$NODE_LAYER|g" \
|
|
63
|
-
-e "s|__FC_CPU__|$FC_CPU|g" \
|
|
64
|
-
-e "s|__FC_MEMORY__|$FC_MEMORY|g" \
|
|
65
|
-
-e "s|__FC_TIMEOUT__|$FC_TIMEOUT|g" \
|
|
66
|
-
-e "s|__CODE_DIR__|$CODE_DIR|g" \
|
|
67
|
-
-e "s|__FC_ENTRY__|$FC_ENTRY|g" \
|
|
68
|
-
-e "s|__SUPABASE_URL__|$SUPABASE_URL|g" \
|
|
69
|
-
-e "s|__SUPABASE_KEY__|$SUPABASE_KEY|g" \
|
|
70
|
-
"$KIT_DIR/contract/s.yaml.tmpl" > "$EXT_DIR/s.yaml"
|
|
71
|
-
|
|
72
|
-
# 6) 部署
|
|
73
|
-
echo "::stage::deploy"
|
|
74
|
-
echo "==> s deploy ($APP_NAME)"
|
|
75
|
-
( cd "$EXT_DIR" && s deploy -y )
|
|
76
|
-
|
|
77
|
-
# 7) 取自定义域名 + 验证
|
|
78
|
-
echo "::stage::verify"
|
|
79
|
-
DOM="$(aliyun fc GET /2023-03-30/custom-domains --region "$REGION" 2>/dev/null | python3 -c "
|
|
80
|
-
import sys,json
|
|
81
|
-
d=json.load(sys.stdin)
|
|
82
|
-
m=[c['domainName'] for c in d.get('customDomains',[]) if '$APP_NAME' in c.get('domainName','')]
|
|
83
|
-
print(m[0] if m else '')")"
|
|
84
|
-
|
|
85
|
-
echo ""
|
|
86
|
-
echo "=================================================="
|
|
87
|
-
if [ -n "$DOM" ]; then
|
|
88
|
-
# daemon runner 优先解析这个 marker 作为 prodUrl(避免抠到 s deploy 中间的 FC system_url
|
|
89
|
-
# fcapp.run,阿里云对它强制 Content-Disposition: attachment,iframe / 浏览器直访都会变成下载)。
|
|
90
|
-
# 必须用 custom domain(<app>.fcv3.<accountId>.cn-hangzhou.fc.devsapp.net)。
|
|
91
|
-
echo "::prod-url::http://$DOM"
|
|
92
|
-
echo " ✅ 发布完成: http://$DOM"
|
|
93
|
-
echo "=================================================="
|
|
94
|
-
# verify.sh 失败(非 200 / Content-Disposition)→ exit 非 0,由 set -e 让 publish.sh
|
|
95
|
-
# 整体失败 → daemon runner 走 publish-failed 路径 + 回灌 chat 让 assistant 接管修复。
|
|
96
|
-
# 之前的 `|| true` 兜底已去掉,理由:线上挂了 prodUrl 仍写盘 = 老板看到"打开线上"按钮
|
|
97
|
-
# 但点开 404,不如 fail-loud。
|
|
98
|
-
#
|
|
99
|
-
# 显式走 bash 解释器调用(不依赖 verify.sh 自己的 +x 权限):
|
|
100
|
-
# daemon 调 publish.sh / new-extension.sh 都是 `spawn('bash', [scriptPath, ...])`,所以即使脚本
|
|
101
|
-
# +x bit 丢了(历史上有过:OTA 解包 / cp -p 未带 / desktop 旧包 mode 不对)也能跑。但这一行
|
|
102
|
-
# 之前是 direct-exec `"$KIT_DIR/scripts/verify.sh"`,OS 会检查 +x —— verify.sh 因此成为唯一
|
|
103
|
-
# 暴露 +x 丢失问题的执行点(老板撞到过一次"verify.sh permission denied")。
|
|
104
|
-
# 改 bash 调用后永远不再依赖 fs +x bit。
|
|
105
|
-
bash "$KIT_DIR/scripts/verify.sh" "http://$DOM"
|
|
106
|
-
else
|
|
107
|
-
# 域名没拿到 = 部署本身有问题(fc3-domain 未成功),也算失败
|
|
108
|
-
echo "❌ 部署完成但未取到自定义域名,检查 fc3-domain 是否成功" >&2
|
|
109
|
-
exit 1
|
|
110
|
-
fi
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# ============================================================
|
|
3
|
-
# FC custom runtime 启动入口 —— 框架无关的 Node 参考实现
|
|
4
|
-
# ============================================================
|
|
5
|
-
# 契约: FC 只要求"一个监听 $PORT 的 HTTP server"。本脚本负责把它拉起来。
|
|
6
|
-
# - 换 Node 框架(Express/Fastify/NestJS/Hono...): 只改 s.yaml 的 APP_ENTRY 环境变量
|
|
7
|
-
# 指向你的入口文件(如 index.js / dist/main.js),本文件不用动。
|
|
8
|
-
# - 换语言(Python/Go...): 替换本文件(如 `exec python app.py`),并在 config.env
|
|
9
|
-
# 换 NODE_LAYER 为对应语言的运行时层。
|
|
10
|
-
# ------------------------------------------------------------
|
|
11
|
-
cd "$(dirname "$0")" || exit 1
|
|
12
|
-
|
|
13
|
-
# custom.debian* 不自带 node,从挂载的官方 Node 层(/opt)动态定位,不赌固定路径
|
|
14
|
-
NODE_BIN="$(command -v node || find /opt -maxdepth 6 -type f -name node 2>/dev/null | head -1)"
|
|
15
|
-
if [ -z "$NODE_BIN" ]; then
|
|
16
|
-
echo "[bootstrap] FATAL: node 不在 PATH 也不在 /opt,检查是否挂了 Node 运行时层" >&2
|
|
17
|
-
exit 1
|
|
18
|
-
fi
|
|
19
|
-
|
|
20
|
-
ENTRY="${APP_ENTRY:-dist/main.js}"
|
|
21
|
-
echo "[bootstrap] node=$NODE_BIN entry=$ENTRY cwd=$(pwd) port=${PORT:-9000}"
|
|
22
|
-
exec "$NODE_BIN" "$ENTRY"
|