@clawos-dev/clawd 0.2.142 → 0.2.143-beta.301.814d293

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.
Files changed (57) hide show
  1. package/dist/cli.cjs +920 -1562
  2. package/dist/deploy-kit/.secrets/aliyun.env.example +4 -0
  3. package/dist/deploy-kit/scripts/new-extension.sh +22 -0
  4. package/dist/deploy-kit/scripts/publish.sh +100 -0
  5. package/dist/deploy-kit/scripts/publish.spec.ts +69 -0
  6. package/dist/{persona-defaults/persona-app-builder/extension-kit → deploy-kit}/scripts/remove-extension.sh +15 -26
  7. package/dist/persona-defaults/persona-app-builder/CLAUDE.md +13 -16
  8. package/dist/persona-defaults/persona-app-builder/extension-kit/README.md +24 -18
  9. package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/ext.conf +5 -0
  10. package/dist/persona-defaults/persona-bug-fixer/CLAUDE.md +0 -2
  11. package/dist/persona-defaults/persona-dataclaw-builder/.mcp.json +4 -0
  12. package/dist/persona-defaults/persona-dataclaw-builder/.secrets/dataclaw.env.example +3 -0
  13. package/dist/persona-defaults/persona-dataclaw-builder/CLAUDE.md +66 -0
  14. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/README.md +99 -0
  15. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/config.env +20 -0
  16. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/contract/s.yaml.tmpl +59 -0
  17. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/ext.conf +5 -0
  18. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/server/.env.example +15 -0
  19. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/server/.fcignore +7 -0
  20. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/server/nest-cli.json +8 -0
  21. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/server/package.json +29 -0
  22. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/server/src/app.module.ts +13 -0
  23. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/server/src/auth/gate.ts +70 -0
  24. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/server/src/auth/session.ts +52 -0
  25. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/server/src/auth/ttc-user-info.ts +23 -0
  26. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/server/src/dataclaw/dataclaw.client.ts +83 -0
  27. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/server/src/dataclaw/dataclaw.config.ts +18 -0
  28. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/server/src/dataclaw/dataclaw.controller.ts +30 -0
  29. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/server/src/dataclaw/dataclaw.module.ts +9 -0
  30. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/server/src/main.ts +116 -0
  31. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/server/src/polyfill.ts +6 -0
  32. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/server/tsconfig.json +14 -0
  33. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/web/index.html +12 -0
  34. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/web/package.json +18 -0
  35. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/web/src/App.jsx +9 -0
  36. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/web/src/api.js +19 -0
  37. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/web/src/auth.js +7 -0
  38. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/web/src/components/BarChart.jsx +38 -0
  39. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/web/src/components/FilterRow.jsx +79 -0
  40. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/web/src/components/ResultCards.jsx +17 -0
  41. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/web/src/components/ResultTable.jsx +21 -0
  42. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/web/src/components/SchemaFilters.jsx +93 -0
  43. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/web/src/enums.js +22 -0
  44. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/web/src/main.jsx +6 -0
  45. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/web/src/pages/PerformanceQueryPage.jsx +102 -0
  46. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/web/src/styles.css +83 -0
  47. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/web/vite.config.js +30 -0
  48. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/hooks/pre-deploy.sh +25 -0
  49. package/dist/persona-defaults/persona-developer/CLAUDE.md +0 -2
  50. package/package.json +2 -5
  51. package/dist/dispatch/mcp-server.cjs +0 -39756
  52. package/dist/persona-defaults/persona-app-builder/extension-kit/scripts/new-extension.sh +0 -66
  53. package/dist/persona-defaults/persona-app-builder/extension-kit/scripts/publish.sh +0 -110
  54. /package/dist/{persona-defaults/persona-app-builder → deploy-kit}/.secrets/aliyun.env +0 -0
  55. /package/dist/{persona-defaults/persona-app-builder/extension-kit → deploy-kit}/contract/bootstrap +0 -0
  56. /package/dist/{persona-defaults/persona-app-builder/extension-kit → deploy-kit}/scripts/ensure-toolchain.sh +0 -0
  57. /package/dist/{persona-defaults/persona-app-builder/extension-kit → deploy-kit}/scripts/verify.sh +0 -0
@@ -0,0 +1,4 @@
1
+ # 阿里云访问凭证(所有 FC persona 共用一份)。复制为同目录 aliyun.env 并填实际值;
2
+ # aliyun.env 不进 git(见 .gitignore),daemon 启动不覆盖。
3
+ ALIBABA_CLOUD_ACCESS_KEY_ID=replace-me
4
+ ALIBABA_CLOUD_ACCESS_KEY_SECRET=replace-me
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env bash
2
+ # 从模板源目录展开一个新 extension(通用,persona 无关)。
3
+ # 用法: new-extension.sh <name> <模板源目录绝对路径> <目标项目目录绝对路径>
4
+ # - 模板源目录:persona 的 extension-kit/examples/<tmpl>(daemon 按 persona 解析后传入)
5
+ # - 目标目录:daemon createProject 已建好(含 .clawd-project.json)的 projects/<name>/
6
+ set -euo pipefail
7
+
8
+ NAME="${1:?用法: new-extension.sh <name> <模板源目录> <目标目录>}"
9
+ SRC="${2:?缺模板源目录}"
10
+ DEST="${3:?缺目标目录}"
11
+
12
+ [ -d "$SRC" ] || { echo "❌ 模板源目录不存在: $SRC" >&2; exit 1; }
13
+ [ -d "$DEST" ] && [ -f "$DEST/.clawd-project.json" ] || {
14
+ echo "❌ 目标目录无效(必须已存在且含 .clawd-project.json): $DEST" >&2; exit 1
15
+ }
16
+
17
+ # 目标须只含 .clawd-project.json(daemon 预建),否则视为脏目录拒绝
18
+ remaining="$(find "$DEST" -mindepth 1 -maxdepth 1 ! -name '.clawd-project.json' -print -quit)"
19
+ [ -z "$remaining" ] || { echo "❌ 目标已有内容(非空目录): $DEST" >&2; exit 1; }
20
+
21
+ cp -R "$SRC/." "$DEST/"
22
+ echo "✅ 新 extension: $DEST"
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env bash
2
+ # ============================================================
3
+ # 通用一键发布 extension 到阿里云 FC(persona 无关)
4
+ # 用法: publish.sh <extension目录> <persona根目录>
5
+ # - <persona根目录>: daemon 按 session persona 解析后传入(.../personas/<id>)
6
+ # - 平台凭证/脚本骨架/bootstrap 来自本脚本所在的共享 deploy-kit
7
+ # - persona 注入值/占位符/特化逻辑/persona 密钥来自 <persona根>/extension-kit
8
+ # ============================================================
9
+ set -euo pipefail
10
+
11
+ KIT_DIR="$(cd "$(dirname "$0")/.." && pwd)" # 共享 deploy-kit 根
12
+ EXT_DIR="${1:?用法: publish.sh <extension目录> <persona根目录>}"
13
+ EXT_DIR="$(cd "$EXT_DIR" && pwd)"
14
+ PERSONA_ROOT="${2:?缺 persona 根目录}"
15
+ PERSONA_KIT="$PERSONA_ROOT/extension-kit"
16
+
17
+ # 1) 配置:persona config.env(含平台值+注入值)+ extension 自身 ext.conf(覆盖)
18
+ [ -f "$PERSONA_KIT/config.env" ] && source "$PERSONA_KIT/config.env"
19
+ [ -f "$EXT_DIR/ext.conf" ] && source "$EXT_DIR/ext.conf"
20
+ APP_NAME="${APP_NAME:-$(basename "$EXT_DIR")}"
21
+ CODE_DIR="${CODE_DIR:-./server}"
22
+ FC_ENTRY="${FC_ENTRY:-dist/main.js}"
23
+ BUILD_CMD="${BUILD_CMD:-}"
24
+
25
+ # 2) 工具链 + 阿里云凭证(共享单源)
26
+ source "$KIT_DIR/scripts/ensure-toolchain.sh"
27
+ ensure_toolchain
28
+ [ -f "$KIT_DIR/.secrets/aliyun.env" ] || { echo "❌ 缺 $KIT_DIR/.secrets/aliyun.env(阿里云凭证)。参考 .secrets/aliyun.env.example 填写。" >&2; exit 1; }
29
+ set -a; . "$KIT_DIR/.secrets/aliyun.env"; set +a
30
+ export ALIBABA_CLOUD_REGION_ID="${REGION:?config.env 缺 REGION}"
31
+ _ident="$(aliyun sts GetCallerIdentity 2>&1)" || { echo "❌ aliyun sts GetCallerIdentity 失败(检查 .secrets/aliyun.env 凭证):" >&2; echo "$_ident" >&2; exit 1; }
32
+ 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; }
33
+ mkdir -p ~/.s
34
+ cat > ~/.s/access.yaml <<EOF
35
+ default:
36
+ AccountID: '$ACC'
37
+ AccessKeyID: $ALIBABA_CLOUD_ACCESS_KEY_ID
38
+ AccessKeySecret: $ALIBABA_CLOUD_ACCESS_KEY_SECRET
39
+ EOF
40
+ chmod 600 ~/.s/access.yaml
41
+
42
+ # 3) persona hook(可选):校验/导出 persona 特化运行时变量(如 SESSION_SECRET / APP_BASE_URL / persona 密钥)
43
+ if [ -f "$PERSONA_KIT/hooks/pre-deploy.sh" ]; then
44
+ source "$PERSONA_KIT/hooks/pre-deploy.sh"
45
+ fi
46
+
47
+ # 4) build(框架相关)
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
+ # 5) bootstrap(共享,框架无关)
55
+ cp "$KIT_DIR/contract/bootstrap" "$EXT_DIR/$CODE_DIR/bootstrap"
56
+ chmod +x "$EXT_DIR/$CODE_DIR/bootstrap"
57
+
58
+ # 6) 通用占位符替换渲染 s.yaml:扫 persona s.yaml.tmpl 的所有 __X__,从 env 取同名变量
59
+ TMPL="$PERSONA_KIT/contract/s.yaml.tmpl"
60
+ [ -f "$TMPL" ] || { echo "❌ 缺 $TMPL" >&2; exit 1; }
61
+ sed_args=()
62
+ for ph in $(grep -oE '__[A-Z0-9_]+__' "$TMPL" | sort -u); do
63
+ var="${ph#__}"; var="${var%__}"
64
+ # declare -p 测「是否定义」(含空字符串),兼容 bash 3.2;未定义即 fail-loud
65
+ if ! declare -p "$var" >/dev/null 2>&1; then
66
+ echo "❌ s.yaml.tmpl 占位符 $ph 无对应 env 变量 \$$var(必填项缺失,或可选项未在 config.env/hook 里 export 兜底)" >&2
67
+ exit 1
68
+ fi
69
+ # 转义 sed 替换串里的特殊字符(密钥/URL 可能含 \ | &),顺序:先 \ 再 | / &
70
+ val="${!var}"
71
+ val="${val//\\/\\\\}"
72
+ val="${val//|/\\|}"
73
+ val="${val//&/\\&}"
74
+ sed_args+=(-e "s|$ph|$val|g")
75
+ done
76
+ sed "${sed_args[@]}" "$TMPL" > "$EXT_DIR/s.yaml"
77
+
78
+ # 7) deploy + verify
79
+ echo "::stage::deploy"
80
+ echo "==> s deploy ($APP_NAME)"
81
+ ( cd "$EXT_DIR" && s deploy -y )
82
+
83
+ echo "::stage::verify"
84
+ DOM="$(aliyun fc GET /2023-03-30/custom-domains --region "$REGION" 2>/dev/null | python3 -c "
85
+ import sys,json
86
+ d=json.load(sys.stdin)
87
+ m=[c['domainName'] for c in d.get('customDomains',[]) if '$APP_NAME' in c.get('domainName','')]
88
+ print(m[0] if m else '')")"
89
+
90
+ echo ""
91
+ echo "=================================================="
92
+ if [ -n "$DOM" ]; then
93
+ echo "::prod-url::http://$DOM"
94
+ echo " ✅ 发布完成: http://$DOM"
95
+ echo "=================================================="
96
+ bash "$KIT_DIR/scripts/verify.sh" "http://$DOM"
97
+ else
98
+ echo "❌ 部署完成但未取到自定义域名,检查 fc3-domain 是否成功" >&2
99
+ exit 1
100
+ fi
@@ -0,0 +1,69 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { execFileSync } from 'node:child_process'
3
+ import { mkdtempSync, writeFileSync, readFileSync, rmSync } from 'node:fs'
4
+ import { join } from 'node:path'
5
+ import { tmpdir } from 'node:os'
6
+
7
+ // 复刻 publish.sh 渲染段(generic placeholder substitution)的最小脚本,锁定占位符替换契约。
8
+ // 实现若改 publish.sh 的扫描循环,本片段须同步。
9
+ const RENDER_SNIPPET = `
10
+ set -euo pipefail
11
+ TMPL="$1"; OUT="$2"
12
+ sed_args=()
13
+ for ph in $(grep -oE '__[A-Z0-9_]+__' "$TMPL" | sort -u); do
14
+ var="\${ph#__}"; var="\${var%__}"
15
+ if ! declare -p "$var" >/dev/null 2>&1; then
16
+ echo "MISSING:$ph" >&2; exit 1
17
+ fi
18
+ val="\${!var}"
19
+ val="\${val//\\\\/\\\\\\\\}"
20
+ val="\${val//|/\\\\|}"
21
+ val="\${val//&/\\\\&}"
22
+ sed_args+=(-e "s|$ph|$val|g")
23
+ done
24
+ sed "\${sed_args[@]}" "$TMPL" > "$OUT"
25
+ `
26
+
27
+ function render(tmpl: string, env: Record<string, string>): { ok: boolean; out?: string; err?: string } {
28
+ const dir = mkdtempSync(join(tmpdir(), 'pub-'))
29
+ const tmplPath = join(dir, 's.yaml.tmpl')
30
+ const outPath = join(dir, 's.yaml')
31
+ writeFileSync(tmplPath, tmpl)
32
+ try {
33
+ execFileSync('bash', ['-c', RENDER_SNIPPET, 'render', tmplPath, outPath], {
34
+ env: { ...process.env, ...env },
35
+ })
36
+ return { ok: true, out: readFileSync(outPath, 'utf8') }
37
+ } catch (e) {
38
+ return { ok: false, err: (e as { stderr?: Buffer }).stderr?.toString() ?? '' }
39
+ } finally {
40
+ rmSync(dir, { recursive: true, force: true })
41
+ }
42
+ }
43
+
44
+ describe('publish.sh 占位符替换', () => {
45
+ it('定义量正常替换', () => {
46
+ const r = render('url: __FOO__\nkey: __BAR__\n', { FOO: 'http://x', BAR: 'abc' })
47
+ expect(r.ok).toBe(true)
48
+ expect(r.out).toContain('url: http://x')
49
+ expect(r.out).toContain('key: abc')
50
+ })
51
+
52
+ it('未定义量 fail-loud', () => {
53
+ const r = render('x: __NOPE__\n', {})
54
+ expect(r.ok).toBe(false)
55
+ expect(r.err).toContain('MISSING:__NOPE__')
56
+ })
57
+
58
+ it('显式空字符串视为已定义、替换为空', () => {
59
+ const r = render('x: "__OPT__"\n', { OPT: '' })
60
+ expect(r.ok).toBe(true)
61
+ expect(r.out).toContain('x: ""')
62
+ })
63
+
64
+ it('值含 sed 特殊字符(| & \\)按字面替换不损坏', () => {
65
+ const r = render('secret: __SEC__\n', { SEC: 'a|b&c\\d' })
66
+ expect(r.ok).toBe(true)
67
+ expect(r.out).toContain('secret: a|b&c\\d')
68
+ })
69
+ })
@@ -1,19 +1,19 @@
1
1
  #!/usr/bin/env bash
2
2
  # ============================================================
3
- # 清理一个 extension:删 FC 资源(函数+触发器+域名)+ 可选删它的 Supabase
4
- # 用法: remove-extension.sh <extension目录>
5
- # 删哪些表:读 ext.conf 的 SUPABASE_TABLES(extension 自己声明的)
6
- # 安全:① 校验 <app>_ 前缀防误删 clawos ② 列出+二次确认 ③ 数据不可逆
3
+ # 清理一个 extension:删 FC 资源(函数+触发器+域名)+ 可选删它的 Supabase 表(通用,persona 无关)
4
+ # 用法: remove-extension.sh <extension目录> <persona根目录>
5
+ # 删哪些表:读 ext.conf 的 SUPABASE_TABLES(extension 自己声明的;非 supabase persona 留空即跳过)
6
+ # 安全:① 校验 <app>_ 前缀防误删 clawos ② 列出 DROP 让 agent 用 supabase MCP 接力 ③ 数据不可逆
7
7
  # ============================================================
8
8
  set -euo pipefail
9
9
 
10
- KIT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
11
- PERSONA_DIR="$(cd "$KIT_DIR/.." && pwd)"
12
-
13
- EXT_DIR="${1:?用法: remove-extension.sh <extension目录>}"
10
+ KIT_DIR="$(cd "$(dirname "$0")/.." && pwd)" # 共享 deploy-kit 根
11
+ EXT_DIR="${1:?用法: remove-extension.sh <extension目录> <persona根目录>}"
14
12
  EXT_DIR="$(cd "$EXT_DIR" && pwd)"
13
+ PERSONA_ROOT="${2:?缺 persona 根目录}"
14
+ PERSONA_KIT="$PERSONA_ROOT/extension-kit"
15
15
 
16
- source "$KIT_DIR/config.env"
16
+ [ -f "$PERSONA_KIT/config.env" ] && source "$PERSONA_KIT/config.env"
17
17
  [ -f "$EXT_DIR/ext.conf" ] && source "$EXT_DIR/ext.conf"
18
18
  APP_NAME="${APP_NAME:-$(basename "$EXT_DIR")}"
19
19
  SUPABASE_TABLES="${SUPABASE_TABLES:-}"
@@ -22,28 +22,17 @@ SUPABASE_TABLES="${SUPABASE_TABLES:-}"
22
22
  source "$KIT_DIR/scripts/ensure-toolchain.sh"
23
23
  ensure_toolchain
24
24
 
25
- set -a; . "$PERSONA_DIR/.secrets/aliyun.env"; set +a
25
+ # 阿里云凭证(共享单源)
26
+ [ -f "$KIT_DIR/.secrets/aliyun.env" ] || { echo "❌ 缺 $KIT_DIR/.secrets/aliyun.env(阿里云凭证)。" >&2; exit 1; }
27
+ set -a; . "$KIT_DIR/.secrets/aliyun.env"; set +a
26
28
 
27
29
  # 1) 删 FC 侧(函数 + 触发器 + 自定义域名)
28
30
  echo "==> 删 FC 资源: $APP_NAME"
29
31
  ( cd "$EXT_DIR" && s remove -y )
30
32
 
31
- # 2) 列出要删的 Supabase 表(脚本不直接调 DB,改打印 DROP 让 agent 用 supabase MCP 跑)
32
- #
33
- # 历史 bug(2026-06-03 老板手测 + persona-app-builder 自查发现):旧实现走 python3
34
- # .mcp.json 取 SUPABASE_ACCESS_TOKEN + curl https://api.supabase.com/v1/... 删表,三个
35
- # 错叠在一起:
36
- # ① 当前 .mcp.json 用阿里云 RDS 风格(--aliyun-ak 命令行参数),没有 env.SUPABASE_ACCESS_TOKEN
37
- # 字段 → python3 KeyError → set -euo pipefail exit
38
- # ② https://api.supabase.com 是云上 supabase.com 的 management API,跟阿里云自建实例
39
- # 120.26.157.138 不通(即便通,删的也是云上早废弃的表)
40
- # ③ SUPABASE_PROJECT_REF 是云上 project-ref 概念,阿里云自建用 instance_name
41
- # 结果:第 1 步删 FC 一直对,第 2 步从来没真删过表(脚本提前 crash exit)。
42
- #
43
- # 修法:bash 调不到 MCP(MCP 是 LLM 通道),pg 直连要密码 agent 拿不到,PostgREST RPC 又
44
- # 要装 drop_table_safe + 手刷 schema cache。最干净:打印 DROP,让 agent 看到 Bash tool
45
- # stdout 后用 mcp__supabase__execute_sql 接力。长期方案是参照 publish.sh 模式做
46
- # appBuilder.delete RPC + UI 按钮,等下一轮做。
33
+ # 2) 列出要删的 Supabase 表(脚本不直接调 DB,改打印 DROP 让 agent 用 supabase MCP 跑)
34
+ # bash 调不到 MCP(MCP 是 LLM 通道),pg 直连要密码 agent 拿不到 —— 最干净:打印 DROP,
35
+ # agent 看到 Bash tool stdout 后用 mcp__supabase__execute_sql 接力。
47
36
  if [ -n "$SUPABASE_TABLES" ]; then
48
37
  # 安全锁:每个表必须 <app>_ 前缀,否则拒绝(防误伤 clawos 共享库)
49
38
  for t in $SUPABASE_TABLES; do
@@ -10,8 +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` 看一眼现状再开工。
14
- - **extension-kit 引用用绝对路径**:你的 cwd 在用户工作目录、**不在 persona 目录**,所以本文档里所有 `extension-kit/` 脚本(new-extension.sh / publish.sh)执行时要用绝对路径 `$HOME/.clawd/personas/persona-app-builder/extension-kit/`(daemon 维护的 persona 资产目录)
13
+ - **发布脚本在共享 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。
15
14
 
16
15
  ## 何时找你
17
16
 
@@ -155,13 +154,13 @@ createProject 成功后 daemon 自动:
155
154
 
156
155
  不要用 RPC 删(`appBuilder.deleteProject` 故意不暴露给你)。老板在 clawd 删 session 时 daemon 会自动联动停 dev server + 删 project 目录 —— 这是唯一删除路径。
157
156
 
158
- ## 优先用 extension-kit(已固化的发布流水线)
157
+ ## 发布流水线(daemon 自动跑 + 共享 deploy-kit 脚本)
159
158
 
160
- 部署不要每次从零手搓。`extension-kit/` 已把「前后端 + Supabase → 阿里云 FC」固化成流水线:
159
+ 部署不要每次从零手搓。「前后端 + Supabase → 阿里云 FC」已固化成流水线,脚本骨架在共享 `$HOME/.clawd/deploy-kit/scripts/`(凭证 + publish/new-extension/ensure-toolchain/verify/remove,全 persona 共用一份),persona 目录只留模板 + config.env + contract/s.yaml.tmpl:
161
160
 
162
- - `extension-kit/scripts/new-extension.sh <name>` —— 从示例脚手架生成新 extension(复制模板 + 生成 ext.conf + 写 `.clawd-project.json` 端口)。**老板在 picker 上点"+ 新建"时 daemon 已经把目录 + `.clawd-project.json` 建好,你只需在这个目录里跑 scaffold,不要重复创建目录。**
161
+ - **scaffold 由 daemon 自动跑**(createProject 内联调 `deploy-kit/scripts/new-extension.sh`,把本 persona `extension-kit/examples/nestjs-react/` 模板复制进 project)。你不手动调。
163
162
  - 你/老板设计 Supabase 表(`<name>_` 前缀)+ 写业务逻辑
164
- - `extension-kit/scripts/publish.sh <dir>` —— 一键 build + 部署 FC + 绑自定义域名 + 验证,输出公网地址
163
+ - **publish 由「发布上线」按钮触发 daemon 自动跑** `deploy-kit/scripts/publish.sh <projDir> <persona根>`(build + 部署 FC + 绑域名 + 验证)。仅发布失败时你接管手动重跑(见下文「发布上线」)。
165
164
 
166
165
  设计是「契约层(框架/语言无关)+ 可插拔技术栈示例」:换框架只改 `ext.conf`,换语言改运行时层 + `contract/bootstrap`。**踩过的 FC 坑全固化在配方记忆 `fc-nodejs-deploy-recipe` 和 `contract/` 里,开工前先看。**
167
166
 
@@ -169,7 +168,7 @@ createProject 成功后 daemon 自动:
169
168
 
170
169
  - 一律走 **FC(函数计算,serverless)**:免 Docker、闲置缩到 0 按量、适合海量小 extension。
171
170
  - Node web 应用走 `custom.debian12` + 官方 Node 层 + `bootstrap` + `fc3-domain` 自定义域名(否则 fcapp.run 默认 URL 强制下载、浏览器不渲染)。
172
- - 工具链:**`aliyun` CLI + Serverless Devs(`s`)**——即 `publish.sh` 内部做的事。两者由 `extension-kit/scripts/ensure-toolchain.sh` 在 publish/remove 动手前自动检测+安装(缺 `aliyun`:有 brew 走 `brew install aliyun-cli`,否则下官方二进制到 `~/.clawd/bin`;缺 `s`:`npm i -g @serverless-devs/s`),新机器无需手动装。接管发布失败时**不必再排查"CLI 没装"**。
171
+ - 工具链:**`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 没装"**。
173
172
  - 实时推送用 Supabase Realtime(FC 不维持长连接)。
174
173
 
175
174
  ## 后端:supabase MCP
@@ -182,26 +181,26 @@ createProject 成功后 daemon 自动:
182
181
 
183
182
  **易踩的坑**:MCP 工具走阿里云自建那台,假如哪一步把 `.env` / `config.env` 写成云上 `qmhqmopgwsbmfxiqagay.supabase.co`(曾经的旧默认),就会**MCP 建表落阿里云 / server 通过 supabase-js 查云上**,两台 PostgREST schema cache 各自独立 → `PGRST205 schema cache 找不到表`。看到 PGRST 系列错码先 `grep -r supabase.co` 自查模板配置,再怀疑 schema cache。
184
183
 
185
- ## 阿里云凭证:在 `.secrets/aliyun.env`,用时现读
184
+ ## 阿里云凭证:在共享 deploy-kit 的 `.secrets/aliyun.env`,用时现读
186
185
 
187
- AK/SK 放在本 persona 项目下的 **`.secrets/aliyun.env`**(`ALIBABA_CLOUD_ACCESS_KEY_ID` + `ALIBABA_CLOUD_ACCESS_KEY_SECRET`),是唯一真源,不在系统环境变量里。部署全走 CLI,动手前先在当前 shell 加载凭证:
186
+ 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 加载:
188
187
 
189
188
  ```bash
190
- set -a; . "$HOME/.clawd/personas/persona-app-builder/.secrets/aliyun.env"; set +a
189
+ set -a; . "$HOME/.clawd/deploy-kit/.secrets/aliyun.env"; set +a
191
190
  ```
192
191
 
193
192
  之后 `aliyun` / `s` / `ossutil` 会自动读这两个环境变量。Serverless Devs 还需 `~/.s/access.yaml` 含 `AccountID`(用 `aliyun sts GetCallerIdentity` 取),SK 走 heredoc 写入、不进命令行。
194
193
 
195
- - **红线**:绝不 `echo` / 打印 / 写进日志或提交记录里暴露 SK;绝不把 `.secrets/` 拷进被部署的项目代码(它已被 `.gitignore` 挡住)。SK 为空时先停下提醒老板去 `.secrets/aliyun.env` 填,别瞎试。
194
+ - **红线**:绝不 `echo` / 打印 / 写进日志或提交记录里暴露 SK;绝不把 `.secrets/` 拷进被部署的项目代码(已被 `.gitignore` 挡住)。SK 为空时先停下提醒老板去 `$HOME/.clawd/deploy-kit/.secrets/aliyun.env` 填,别瞎试。
196
195
 
197
196
  ## 工作主线
198
197
 
199
198
  一个 project 大致走这四段,按需推进、不必僵化:
200
199
 
201
200
  1. **创建 project**(fresh session 进来时)—— 问老板想做啥 app + 反问名字 + 调 `appBuilder.createProject` RPC(详见上文「触发 createProject」)。已绑 session 跳过这一步
202
- 2. **开发前后端** —— scaffold 优先 `extension-kit/scripts/new-extension.sh`(脚本读 `.clawd-project.json` 写端口化的 `vite.config`);复杂任务走 superpowers 节奏,简单任务 TodoWrite 跟踪(分级见下)
201
+ 2. **开发前后端** —— scaffold 已由 daemon createProject 时自动跑(你摸不到模板源);你直接在 projectDir 里写业务。复杂任务走 superpowers 节奏,简单任务 TodoWrite 跟踪(分级见下)
203
202
  3. **接 Supabase** —— 数据/认证/存储走 supabase MCP,schema 以线上为准,新表加 `<项目名>_` 前缀
204
- 4. **部署** —— `extension-kit/scripts/publish.sh` 一键发 FC(自动绑域名 + 验证)。部署完 **调 `appBuilder.setProdUrl` 把公网 URL 绑到 project**(让 UI 右栏自动切线上预览,详见上文),**再给老板可访问的地址 + 怎么更新(`s deploy`)/看日志(`s logs`)/删除(`s remove`)**,不要只说"部署好了"
203
+ 4. **部署** —— 老板点「发布上线」按钮,daemon 自动跑 `deploy-kit/scripts/publish.sh`(build + FC + 绑域名 + 验证 + prodUrl),happy path 跳过你。仅发布失败时你接管手动重跑(见下文「发布上线」)。
205
204
 
206
205
  ## build 模式(clawd 双栏实时预览)
207
206
 
@@ -261,7 +260,7 @@ await app.listen(Number(process.env.CLAWD_PREVIEW_PORT));
261
260
 
262
261
  1. 读项目目录下的 `.publish.log` 完整日志
263
262
  2. 定位并修复问题(改源码 / 改 `ext.conf` / 检查 `.secrets/aliyun.env` 等)
264
- 3. 重跑 `bash "$HOME/.clawd/personas/persona-app-builder/extension-kit/scripts/publish.sh" <projDir>` 直到 exit 0 且输出公网 URL
263
+ 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)
265
264
  4. 调 `appBuilder.setProdUrl` 把 URL 写入项目元数据(同上文)
266
265
 
267
266
  **注意**:重跑期间不要自己调 `appBuilder.publish` RPC(那是给 UI 按钮用的,会被 daemon 当作"已 in-flight"拒绝),手动跑脚本即可。失败回灌的消息开头固定是 `发布失败:[`,识别此前缀即触发接管流程。
@@ -308,5 +307,3 @@ await app.listen(Number(process.env.CLAWD_PREVIEW_PORT));
308
307
  - 部署前后都验证服务真的起来了(健康检查 / 公网访问 / 看日志),不靠"应该没问题"
309
308
  - 增量提交可工作的代码 / 从既有实现学 / 3 次失败停下重新评估
310
309
  - 错误处理快速失败 + 描述清楚 + 不静默吞异常
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
- - **契约层(框架/语言无关,固定)**:`contract/` + `scripts/` + `config.env`
10
- - **技术栈示例(可换的起点)**:`examples/`(默认 `nestjs-react`)
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 平台级固定变量(region / Node层 / Supabase)
17
- ├── contract/
18
- │ ├── bootstrap FC 启动入口(Node 参考实现:find node + cd + exec)
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
- # 1) 起一个新 extension(从默认示例)
32
- extension-kit/scripts/new-extension.sh myapp
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
- extension-kit/scripts/publish.sh ../myapp
45
+ "$DK/scripts/publish.sh" <projDir> "$PR"
40
46
 
41
47
  # 4) 不要了就一键清理(FC 资源 + 它的 Supabase 表)
42
- extension-kit/scripts/remove-extension.sh ../myapp
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
- extension-kit/scripts/remove-extension.sh ../myapp
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` 删它的表。
@@ -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,5 +98,3 @@ 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`。
@@ -0,0 +1,4 @@
1
+ {
2
+ "_comment": "数据查询搭建师:通路 A 直调 dataclaw-service HTTP,不挂任何数据 MCP。daemon refreshDaemonManagedDirs 会把本文件同步到 ~/.clawd/personas/persona-dataclaw-builder/.mcp.json。",
3
+ "mcpServers": {}
4
+ }
@@ -0,0 +1,3 @@
1
+ # dataclaw 服务间共享密钥(= dataclaw-service 的 SEND_AS_BOT_SHARED_SECRET)。
2
+ # 复制为同目录 dataclaw.env 并填实际值;dataclaw.env 不进 git(见 .gitignore)。
3
+ DATACLAW_INTERNAL_SECRET=replace-me
@@ -0,0 +1,66 @@
1
+ # persona-dataclaw-builder
2
+
3
+ 你是 clawd 里的「数据查询搭建师」。一句话定位:**帮 TTC 业务用户把特定的数据查询条件,做成一个只读的 GUI 查询页面,背后复用 dataclaw 的查询能力。** 你做出来的页面是独立 app,用 app-builder 流水线起、双栏实时预览、发阿里云 FC。
4
+
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 刷新,不重新部署。
53
+
54
+ ## 数据来源:dataclaw-service(只读,通路 A)
55
+ 生成的 app 后端直调 dataclaw-service HTTP(`/api/flexible-query`、`/api/intent-execute`、`/api/schema`),用**建页人(builder)的身份**取数、纯展示、不写数据。具体查询能力见 §「dataclaw 查询能力」。
56
+
57
+ ## dataclaw 查询能力
58
+ 本章节在后续模板/知识充实阶段填全(来源:dataclaw-src 仓库 workspace/docs/dataclaw-builder-architecture.md)。当前仅建骨架。
59
+
60
+ ## 双客户
61
+ 业务用户自助建简单页(语气业务化、屏蔽技术细节);管理员代建复杂页(可技术化)。
62
+
63
+ ## 红线
64
+ - 只读:绝不让生成的 app 往 dataclaw 写数据 / 不接 Supabase 写库。
65
+ - 数据范围是隐式 AND(按建页人身份兜),**绝不把 scope(self/team/all)做成用户可选下拉**——等于越权。
66
+ - persona 目录只读;项目路径以 createProject 返回的 projectDir 为准。
@@ -0,0 +1,99 @@
1
+ # extension-kit —— 从零到发布的 extension 流水线
2
+
3
+ 把「前端 + 后端 + Supabase 的 extension 发布到阿里云 FC(serverless,免 Docker)」固化成可复用流水线。开发靠人/agent,发布全自动。
4
+
5
+ ## 设计理念:契约层 + 可插拔技术栈
6
+
7
+ FC 对应用只有一条要求:**一个监听 `$PORT` 的 HTTP server**。所以固化的是「发布管道」,不是技术栈。
8
+
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
+
12
+ ## 目录
13
+
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(只放特化资产)
26
+ extension-kit/
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)
31
+ ```
32
+
33
+ ## 用法
34
+
35
+ 日常 happy path 由 daemon 自动跑(createProject 内联 scaffold、「发布上线」按钮触发 publish)。需手动跑时用共享 deploy-kit 绝对路径,**第二参是本 persona 根**:
36
+
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"
46
+ ```
47
+
48
+ ## 流程与固化程度
49
+
50
+ | 步骤 | 谁做 |
51
+ |------|------|
52
+ | new-extension 脚手架 | 🟢 脚本 |
53
+ | 设计 Supabase schema | 🔴 人/agent(每个 extension 不同) |
54
+ | 写前后端业务 | 🔴 人/agent |
55
+ | publish(build/部署/域名/验证) | 🟢 脚本 |
56
+ | remove(清 FC + 表) | 🟢 脚本 |
57
+
58
+ ## 清理一个 extension
59
+
60
+ ```bash
61
+ "$HOME/.clawd/deploy-kit/scripts/remove-extension.sh" <projDir> "$HOME/.clawd/personas/persona-dataclaw-builder"
62
+ ```
63
+
64
+ 先 `s remove` 删 FC 函数+触发器+自定义域名,再按 `ext.conf` 的 `SUPABASE_TABLES` 删它的表。
65
+
66
+ **脚本怎么知道删哪个表**:靠 `ext.conf` 里显式声明的 `SUPABASE_TABLES="myapp_xxx"`(建表时填进去,extension 的"自描述")。三道安全锁:
67
+
68
+ 1. 每个表必须 `<app>_` 前缀,否则**拒绝删**(物理上防误伤 clawos 共享库)
69
+ 2. 删前列出所有要删的表
70
+ 3. 要手输 `yes` 确认(数据不可逆,不用 `-y` 静默删)
71
+
72
+ > SLS 日志 project / OSS 代码桶是**账号级共享**(多 extension 共用),不随单个 extension 删。
73
+
74
+ ## 换框架(同 Node,几乎零成本)
75
+
76
+ 改 extension 目录里的 `ext.conf`:
77
+ - `FC_ENTRY` —— 入口文件(Express: `index.js`;NestJS: `dist/main.js`;Fastify/Hono 同理)
78
+ - `BUILD_CMD` —— 构建命令
79
+
80
+ `contract/bootstrap` 和 `s.yaml.tmpl` **不用动**。只要你的 server 最终 `listen(process.env.PORT)` 即可。
81
+
82
+ ## 换语言(Node → Python/Go…)
83
+
84
+ 1. `config.env` 的 `NODE_LAYER` 换成对应语言的 FC 运行时层
85
+ 2. 替换 `contract/bootstrap`(如 `exec python app.py`)
86
+ 3. `ext.conf` 的 `BUILD_CMD` 改成对应构建命令
87
+
88
+ `s.yaml.tmpl` 结构不变。
89
+
90
+ ## 红线(都是踩坑换来的,见记忆 fc-nodejs-deploy-recipe)
91
+
92
+ - **Supabase 是 clawos 共享生产库**:新表必须 `<app>_` 前缀,**绝不 drop/alter clawos 已有表**
93
+ - **必须绑自定义域名**(fc3-domain auto),否则 `fcapp.run` 默认 URL 强制下载网页、浏览器不渲染
94
+ - **不用 SAE**(Node 无代码包,只能镜像=要 Docker);不用 `custom.debian10`/裸 command(node 找不到 / CAExited)
95
+ - **长连接 / WebSocket 推送不适合 FC** → 用 Supabase Realtime(FC 不维持长连接)
96
+
97
+ ## 不固化的(留给人/agent)
98
+
99
+ 数据模型设计、业务逻辑、建表前的归属判断、发布失败时的异常排查。