@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.
Files changed (29) hide show
  1. package/dist/cli.cjs +1632 -824
  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/dispatch/mcp-server.cjs +39756 -0
  8. package/dist/persona-defaults/persona-app-builder/CLAUDE.md +16 -13
  9. package/dist/persona-defaults/persona-app-builder/extension-kit/README.md +24 -18
  10. package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/ext.conf +5 -0
  11. package/dist/persona-defaults/persona-bug-fixer/CLAUDE.md +2 -0
  12. package/dist/persona-defaults/persona-dataclaw-builder/CLAUDE.md +48 -2
  13. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/README.md +28 -25
  14. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/ext.conf +5 -0
  15. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/hooks/pre-deploy.sh +25 -0
  16. package/dist/persona-defaults/persona-developer/CLAUDE.md +2 -0
  17. package/package.json +5 -2
  18. package/dist/persona-defaults/persona-app-builder/extension-kit/scripts/new-extension.sh +0 -66
  19. package/dist/persona-defaults/persona-app-builder/extension-kit/scripts/publish.sh +0 -110
  20. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/contract/bootstrap +0 -22
  21. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/scripts/ensure-toolchain.sh +0 -62
  22. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/scripts/new-extension.sh +0 -66
  23. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/scripts/publish.sh +0 -129
  24. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/scripts/remove-extension.sh +0 -70
  25. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/scripts/verify.sh +0 -38
  26. /package/dist/{persona-defaults/persona-app-builder → deploy-kit}/.secrets/aliyun.env +0 -0
  27. /package/dist/{persona-defaults/persona-app-builder/extension-kit → deploy-kit}/contract/bootstrap +0 -0
  28. /package/dist/{persona-defaults/persona-app-builder/extension-kit → deploy-kit}/scripts/ensure-toolchain.sh +0 -0
  29. /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