@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
|
@@ -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
|
-
|
|
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 "$
|
|
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
|
-
|
|
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
|
-
#
|
|
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
|