@clawos-dev/clawd 0.2.112-beta.225.5330d3a → 0.2.112-beta.227.cc9477f

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 (31) hide show
  1. package/dist/cli.cjs +2038 -1474
  2. package/dist/persona-defaults/persona-app-builder/CLAUDE.md +285 -0
  3. package/dist/persona-defaults/persona-app-builder/extension-kit/README.md +96 -0
  4. package/dist/persona-defaults/persona-app-builder/extension-kit/config.env +20 -0
  5. package/dist/persona-defaults/persona-app-builder/extension-kit/contract/bootstrap +22 -0
  6. package/dist/persona-defaults/persona-app-builder/extension-kit/contract/s.yaml.tmpl +54 -0
  7. package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/.env.example +3 -0
  8. package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/.fcignore +7 -0
  9. package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/nest-cli.json +8 -0
  10. package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/package.json +28 -0
  11. package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/app.module.ts +10 -0
  12. package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/main.ts +74 -0
  13. package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/messages/messages.controller.ts +27 -0
  14. package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/messages/messages.module.ts +9 -0
  15. package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/messages/messages.service.ts +49 -0
  16. package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/polyfill.ts +8 -0
  17. package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/tsconfig.json +14 -0
  18. package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/web/index.html +12 -0
  19. package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/web/package.json +18 -0
  20. package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/web/src/App.jsx +161 -0
  21. package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/web/src/main.jsx +5 -0
  22. package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/web/vite.config.js +30 -0
  23. package/dist/persona-defaults/persona-app-builder/extension-kit/scripts/new-extension.sh +49 -0
  24. package/dist/persona-defaults/persona-app-builder/extension-kit/scripts/publish.sh +78 -0
  25. package/dist/persona-defaults/persona-app-builder/extension-kit/scripts/remove-extension.sh +57 -0
  26. package/dist/persona-defaults/persona-app-builder/extension-kit/scripts/verify.sh +20 -0
  27. package/dist/persona-defaults/persona-bug-fixer/.claude/settings.json +7 -0
  28. package/dist/persona-defaults/persona-bug-fixer/CLAUDE.md +122 -0
  29. package/dist/persona-defaults/persona-developer/.claude/settings.json +8 -0
  30. package/dist/persona-defaults/persona-developer/CLAUDE.md +126 -0
  31. package/package.json +1 -1
@@ -0,0 +1,9 @@
1
+ import { Module } from '@nestjs/common';
2
+ import { MessagesController } from './messages.controller';
3
+ import { MessagesService } from './messages.service';
4
+
5
+ @Module({
6
+ controllers: [MessagesController],
7
+ providers: [MessagesService],
8
+ })
9
+ export class MessagesModule {}
@@ -0,0 +1,49 @@
1
+ import { Injectable } from '@nestjs/common';
2
+ import { createClient, SupabaseClient } from '@supabase/supabase-js';
3
+
4
+ const TABLE = 'guestbook_messages';
5
+
6
+ // 模板示例 service。Supabase client **lazy 初始化**:
7
+ // - constructor 不读 env / 不 throw —— 让整个 nest server 能起来即使 SUPABASE_URL/KEY 没配
8
+ // (比如 fresh project 还没接 Supabase,老板只想看个 welcome 页面)
9
+ // - 实际调 list / create 时再 require,缺 env 时只报给这条 API 的 caller,不阻塞 server 启动
10
+ //
11
+ // 老 bug:constructor 里硬 require + throw → 没配 env 时 NestApplication 启动直接 crash,
12
+ // dev server 进 exit 1 → preview iframe upstream error 糊脸。
13
+ @Injectable()
14
+ export class MessagesService {
15
+ private _supabase: SupabaseClient | null = null;
16
+
17
+ private get supabase(): SupabaseClient {
18
+ if (this._supabase) return this._supabase;
19
+ const url = process.env.SUPABASE_URL;
20
+ const key = process.env.SUPABASE_KEY;
21
+ if (!url || !key) {
22
+ throw new Error(
23
+ 'SUPABASE_URL / SUPABASE_KEY 未配置 —— 请先接 Supabase 后端再调 /api/messages',
24
+ );
25
+ }
26
+ this._supabase = createClient(url, key);
27
+ return this._supabase;
28
+ }
29
+
30
+ async list() {
31
+ const { data, error } = await this.supabase
32
+ .from(TABLE)
33
+ .select('*')
34
+ .order('created_at', { ascending: false })
35
+ .limit(100);
36
+ if (error) throw new Error(`读取留言失败: ${error.message}`);
37
+ return data;
38
+ }
39
+
40
+ async create(name: string, content: string) {
41
+ const { data, error } = await this.supabase
42
+ .from(TABLE)
43
+ .insert({ name: name || 'anonymous', content })
44
+ .select()
45
+ .single();
46
+ if (error) throw new Error(`写入留言失败: ${error.message}`);
47
+ return data;
48
+ }
49
+ }
@@ -0,0 +1,8 @@
1
+ // Node < 22 没有原生 WebSocket,而 @supabase/supabase-js 的 realtime 客户端
2
+ // 初始化时需要它。在应用启动前补一个全局 WebSocket,使任意 Node 版本
3
+ // (FC 运行时给的 node 可能是 18/20)下都能正常 createClient。
4
+ // 注意:不要用 `WebSocket` 作为局部变量名,会与 DOM lib 的全局类型重声明冲突。
5
+ const WsCtor = require('ws');
6
+ if (!(globalThis as any).WebSocket) {
7
+ (globalThis as any).WebSocket = WsCtor;
8
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "commonjs",
4
+ "target": "ES2021",
5
+ "outDir": "./dist",
6
+ "rootDir": "./src",
7
+ "experimentalDecorators": true,
8
+ "emitDecoratorMetadata": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "sourceMap": false,
12
+ "declaration": false
13
+ }
14
+ }
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="zh">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>留言板 · 全链路测试</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.jsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "guestbook-web",
3
+ "private": true,
4
+ "version": "1.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build"
9
+ },
10
+ "dependencies": {
11
+ "react": "^18.3.1",
12
+ "react-dom": "^18.3.1"
13
+ },
14
+ "devDependencies": {
15
+ "@vitejs/plugin-react": "^4.4.0",
16
+ "vite": "^6.0.0"
17
+ }
18
+ }
@@ -0,0 +1,161 @@
1
+ import React, { useEffect, useState } from 'react';
2
+
3
+ const styles = {
4
+ page: {
5
+ minHeight: '100vh',
6
+ margin: 0,
7
+ background: 'linear-gradient(160deg, #0f172a 0%, #1e293b 100%)',
8
+ color: '#e2e8f0',
9
+ fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif',
10
+ display: 'flex',
11
+ justifyContent: 'center',
12
+ padding: '48px 16px',
13
+ },
14
+ card: {
15
+ width: '100%',
16
+ maxWidth: 560,
17
+ },
18
+ title: { fontSize: 28, fontWeight: 700, margin: '0 0 4px' },
19
+ subtitle: { color: '#94a3b8', margin: '0 0 24px', fontSize: 14 },
20
+ form: { display: 'flex', flexDirection: 'column', gap: 10, marginBottom: 28 },
21
+ row: { display: 'flex', gap: 10 },
22
+ input: {
23
+ flex: 1,
24
+ padding: '10px 12px',
25
+ borderRadius: 8,
26
+ border: '1px solid #334155',
27
+ background: '#0f172a',
28
+ color: '#e2e8f0',
29
+ fontSize: 14,
30
+ outline: 'none',
31
+ },
32
+ textarea: {
33
+ padding: '10px 12px',
34
+ borderRadius: 8,
35
+ border: '1px solid #334155',
36
+ background: '#0f172a',
37
+ color: '#e2e8f0',
38
+ fontSize: 14,
39
+ outline: 'none',
40
+ resize: 'vertical',
41
+ minHeight: 64,
42
+ },
43
+ button: {
44
+ padding: '10px 20px',
45
+ borderRadius: 8,
46
+ border: 'none',
47
+ background: '#6366f1',
48
+ color: '#fff',
49
+ fontSize: 14,
50
+ fontWeight: 600,
51
+ cursor: 'pointer',
52
+ },
53
+ msg: {
54
+ padding: '12px 14px',
55
+ borderRadius: 10,
56
+ background: '#1e293b',
57
+ border: '1px solid #334155',
58
+ marginBottom: 10,
59
+ },
60
+ msgHead: { display: 'flex', justifyContent: 'space-between', marginBottom: 4 },
61
+ msgName: { fontWeight: 600, color: '#a5b4fc', fontSize: 14 },
62
+ msgTime: { color: '#64748b', fontSize: 12 },
63
+ msgBody: { fontSize: 14, lineHeight: 1.5, whiteSpace: 'pre-wrap' },
64
+ error: { color: '#f87171', fontSize: 13, marginBottom: 12 },
65
+ empty: { color: '#64748b', fontSize: 14, textAlign: 'center', padding: '24px 0' },
66
+ };
67
+
68
+ export default function App() {
69
+ const [messages, setMessages] = useState([]);
70
+ const [name, setName] = useState('');
71
+ const [content, setContent] = useState('');
72
+ const [error, setError] = useState('');
73
+ const [loading, setLoading] = useState(false);
74
+
75
+ async function load() {
76
+ try {
77
+ const res = await fetch(`${import.meta.env.BASE_URL}api/messages`);
78
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
79
+ setMessages(await res.json());
80
+ setError('');
81
+ } catch (e) {
82
+ setError(`加载失败: ${e.message}`);
83
+ }
84
+ }
85
+
86
+ useEffect(() => {
87
+ load();
88
+ }, []);
89
+
90
+ async function submit(e) {
91
+ e.preventDefault();
92
+ if (!content.trim()) return;
93
+ setLoading(true);
94
+ try {
95
+ const res = await fetch(`${import.meta.env.BASE_URL}api/messages`, {
96
+ method: 'POST',
97
+ headers: { 'Content-Type': 'application/json' },
98
+ body: JSON.stringify({ name, content }),
99
+ });
100
+ if (!res.ok) {
101
+ const body = await res.json().catch(() => ({}));
102
+ throw new Error(body.message || `HTTP ${res.status}`);
103
+ }
104
+ setContent('');
105
+ await load();
106
+ } catch (e) {
107
+ setError(`提交失败: ${e.message}`);
108
+ } finally {
109
+ setLoading(false);
110
+ }
111
+ }
112
+
113
+ return (
114
+ <div style={styles.page}>
115
+ <div style={styles.card}>
116
+ <h1 style={styles.title}>留言板</h1>
117
+ <p style={styles.subtitle}>
118
+ 前端 React → NestJS → Supabase 全链路连通性测试
119
+ </p>
120
+
121
+ <form style={styles.form} onSubmit={submit}>
122
+ <input
123
+ style={styles.input}
124
+ placeholder="你的名字(可选)"
125
+ value={name}
126
+ onChange={(e) => setName(e.target.value)}
127
+ />
128
+ <textarea
129
+ style={styles.textarea}
130
+ placeholder="写点什么…"
131
+ value={content}
132
+ onChange={(e) => setContent(e.target.value)}
133
+ />
134
+ <div style={styles.row}>
135
+ <button style={styles.button} type="submit" disabled={loading}>
136
+ {loading ? '提交中…' : '提交留言'}
137
+ </button>
138
+ </div>
139
+ </form>
140
+
141
+ {error && <div style={styles.error}>{error}</div>}
142
+
143
+ {messages.length === 0 ? (
144
+ <div style={styles.empty}>还没有留言,来写第一条吧</div>
145
+ ) : (
146
+ messages.map((m) => (
147
+ <div key={m.id} style={styles.msg}>
148
+ <div style={styles.msgHead}>
149
+ <span style={styles.msgName}>{m.name}</span>
150
+ <span style={styles.msgTime}>
151
+ {new Date(m.created_at).toLocaleString('zh-CN')}
152
+ </span>
153
+ </div>
154
+ <div style={styles.msgBody}>{m.content}</div>
155
+ </div>
156
+ ))
157
+ )}
158
+ </div>
159
+ </div>
160
+ );
161
+ }
@@ -0,0 +1,5 @@
1
+ import React from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import App from './App.jsx';
4
+
5
+ createRoot(document.getElementById('root')).render(<App />);
@@ -0,0 +1,30 @@
1
+ import { defineConfig } from 'vite';
2
+ import react from '@vitejs/plugin-react';
3
+
4
+ // app-builder G 方案:dev 时 vite 不独立跑,而是被 server/src/main.ts 的
5
+ // `createViteServer({ middlewareMode: true })` 当 middleware 挂进 nest,前端 HMR
6
+ // 和 /api 都从 nest 监听的 CLAWD_PREVIEW_PORT 出(spec 2026-06-01 §5.6)。
7
+ // 这个 config 仅用于:
8
+ // 1. `vite build` 生成 web/dist 静态资源(prod 由 nest serve)
9
+ // 2. 脱离 clawd 直接 `pnpm dev` 跑 vite 独立模式(本地调试)
10
+ const tunnelHost = process.env.CLAWD_TUNNEL_HOST ?? '';
11
+ const port = Number(process.env.CLAWD_PREVIEW_PORT ?? 5173);
12
+
13
+ export default defineConfig({
14
+ plugins: [react()],
15
+ build: {
16
+ outDir: 'dist',
17
+ emptyOutDir: true,
18
+ },
19
+ // 隧道经 nest 主进程进来,vite middleware 自己识别 base。standalone 模式(无 tunnel)
20
+ // 走 base: '/' 方便本地直接打开。
21
+ base: tunnelHost ? `/preview/${port}/` : '/',
22
+ server: {
23
+ host: '127.0.0.1',
24
+ port,
25
+ strictPort: true,
26
+ hmr: tunnelHost
27
+ ? { protocol: 'wss', clientPort: 443, path: `/preview/${port}/`, host: tunnelHost }
28
+ : undefined,
29
+ },
30
+ });
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env bash
2
+ # 从示例模板生成一个新 extension
3
+ # 用法: new-extension.sh <name> [示例模板, 默认 nestjs-react]
4
+ #
5
+ # 目标目录解析:
6
+ # - picker 模式(推荐):persona-app-builder/projects/<name>/ 已由 daemon createProject
7
+ # 建好(含 .clawd-project.json)→ 把模板内容展开到此目录,保留 .clawd-project.json。
8
+ # - 老路径兼容:projects/<name>/ 不存在时回退到 persona-app-builder/<name>/。
9
+ set -euo pipefail
10
+ KIT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
11
+ PERSONA_DIR="$(cd "$KIT_DIR/.." && pwd)"
12
+
13
+ NAME="${1:?用法: new-extension.sh <name> [模板,默认 nestjs-react]}"
14
+ TMPL="${2:-nestjs-react}"
15
+ SRC="$KIT_DIR/examples/$TMPL"
16
+
17
+ [ -d "$SRC" ] || { echo "示例模板不存在: $SRC (现有: $(ls "$KIT_DIR/examples"))"; exit 1; }
18
+
19
+ PICKER_DEST="$PERSONA_DIR/projects/$NAME"
20
+ if [ -d "$PICKER_DEST" ] && [ -f "$PICKER_DEST/.clawd-project.json" ]; then
21
+ DEST="$PICKER_DEST"
22
+ # picker 模式:目录已存在且仅含 .clawd-project.json,把模板内容展开进去
23
+ REMAINING="$(find "$DEST" -mindepth 1 -maxdepth 1 ! -name '.clawd-project.json' -print -quit)"
24
+ [ -z "$REMAINING" ] || { echo "目标已有内容(非空目录): $DEST"; exit 1; }
25
+ cp -R "$SRC/." "$DEST/"
26
+ else
27
+ DEST="$PERSONA_DIR/$NAME"
28
+ [ -e "$DEST" ] && { echo "目标已存在: $DEST"; exit 1; }
29
+ cp -r "$SRC" "$DEST"
30
+ fi
31
+
32
+ # 写 ext.conf(框架相关配置,publish.sh 会读)
33
+ cat > "$DEST/ext.conf" <<EOF
34
+ APP_NAME=$NAME
35
+ CODE_DIR=./server
36
+ FC_ENTRY=dist/main.js
37
+ BUILD_CMD="cd web && npm install && npm run build && cd ../server && npm install && npm run build"
38
+ # 本 extension 拥有的 Supabase 表(空格分隔,必须 ${NAME}_ 前缀);remove-extension.sh 据此清理
39
+ SUPABASE_TABLES=""
40
+ EOF
41
+
42
+ echo "✅ 新 extension: $DEST"
43
+ echo ""
44
+ echo "接下来:"
45
+ echo " 1) 设计 Supabase 表(务必用 ${NAME}_ 前缀,别碰 clawos 已有表)"
46
+ echo " 2) 改前端(web/)和后端(server/)业务逻辑"
47
+ echo " 3) 本地验证后发布: extension-kit/scripts/publish.sh $DEST"
48
+ echo ""
49
+ echo "换框架/语言: 改 ext.conf 的 BUILD_CMD/FC_ENTRY;换语言再看 extension-kit/README.md"
@@ -0,0 +1,78 @@
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) 阿里云凭证(现读)+ Serverless Devs 认证(必须含 AccountID,SK 走 heredoc 不进命令行)
24
+ set -a; . "$PERSONA_DIR/.secrets/aliyun.env"; set +a
25
+ ACC="$(aliyun sts GetCallerIdentity 2>/dev/null | python3 -c 'import sys,json;print(json.load(sys.stdin)["AccountId"])')"
26
+ mkdir -p ~/.s
27
+ cat > ~/.s/access.yaml <<EOF
28
+ default:
29
+ AccountID: '$ACC'
30
+ AccessKeyID: $ALIBABA_CLOUD_ACCESS_KEY_ID
31
+ AccessKeySecret: $ALIBABA_CLOUD_ACCESS_KEY_SECRET
32
+ EOF
33
+ chmod 600 ~/.s/access.yaml
34
+
35
+ # 3) 构建产物(框架相关,由 ext.conf 的 BUILD_CMD 定义)
36
+ if [ -n "$BUILD_CMD" ]; then
37
+ echo "==> build: $BUILD_CMD"
38
+ ( cd "$EXT_DIR" && eval "$BUILD_CMD" )
39
+ fi
40
+
41
+ # 4) 放置框架无关的 bootstrap 到 code 目录
42
+ cp "$KIT_DIR/contract/bootstrap" "$EXT_DIR/$CODE_DIR/bootstrap"
43
+ chmod +x "$EXT_DIR/$CODE_DIR/bootstrap"
44
+
45
+ # 5) 渲染 s.yaml(| 作分隔符,避免 URL/层 ARN 里的 / : 冲突)
46
+ sed -e "s|__APP_NAME__|$APP_NAME|g" \
47
+ -e "s|__REGION__|$REGION|g" \
48
+ -e "s|__FC_RUNTIME__|$FC_RUNTIME|g" \
49
+ -e "s|__NODE_LAYER__|$NODE_LAYER|g" \
50
+ -e "s|__FC_CPU__|$FC_CPU|g" \
51
+ -e "s|__FC_MEMORY__|$FC_MEMORY|g" \
52
+ -e "s|__FC_TIMEOUT__|$FC_TIMEOUT|g" \
53
+ -e "s|__CODE_DIR__|$CODE_DIR|g" \
54
+ -e "s|__FC_ENTRY__|$FC_ENTRY|g" \
55
+ -e "s|__SUPABASE_URL__|$SUPABASE_URL|g" \
56
+ -e "s|__SUPABASE_KEY__|$SUPABASE_KEY|g" \
57
+ "$KIT_DIR/contract/s.yaml.tmpl" > "$EXT_DIR/s.yaml"
58
+
59
+ # 6) 部署
60
+ echo "==> s deploy ($APP_NAME)"
61
+ ( cd "$EXT_DIR" && s deploy -y )
62
+
63
+ # 7) 取自定义域名 + 验证
64
+ DOM="$(aliyun fc GET /2023-03-30/custom-domains --region "$REGION" 2>/dev/null | python3 -c "
65
+ import sys,json
66
+ d=json.load(sys.stdin)
67
+ m=[c['domainName'] for c in d.get('customDomains',[]) if '$APP_NAME' in c.get('domainName','')]
68
+ print(m[0] if m else '')")"
69
+
70
+ echo ""
71
+ echo "=================================================="
72
+ if [ -n "$DOM" ]; then
73
+ echo " ✅ 发布完成: http://$DOM"
74
+ echo "=================================================="
75
+ "$KIT_DIR/scripts/verify.sh" "http://$DOM" || true
76
+ else
77
+ echo " ⚠️ 部署完成但未取到自定义域名,检查 fc3-domain 是否成功"
78
+ fi
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env bash
2
+ # ============================================================
3
+ # 清理一个 extension:删 FC 资源(函数+触发器+域名)+ 可选删它的 Supabase 表
4
+ # 用法: remove-extension.sh <extension目录>
5
+ # 删哪些表:读 ext.conf 的 SUPABASE_TABLES(extension 自己声明的)
6
+ # 安全:① 校验 <app>_ 前缀防误删 clawos ② 列出+二次确认 ③ 数据不可逆
7
+ # ============================================================
8
+ set -euo pipefail
9
+
10
+ KIT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
11
+ PERSONA_DIR="$(cd "$KIT_DIR/.." && pwd)"
12
+
13
+ EXT_DIR="${1:?用法: remove-extension.sh <extension目录>}"
14
+ EXT_DIR="$(cd "$EXT_DIR" && pwd)"
15
+
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
+ SUPABASE_TABLES="${SUPABASE_TABLES:-}"
20
+
21
+ set -a; . "$PERSONA_DIR/.secrets/aliyun.env"; set +a
22
+
23
+ # 1) 删 FC 侧(函数 + 触发器 + 自定义域名)
24
+ echo "==> 删 FC 资源: $APP_NAME"
25
+ ( cd "$EXT_DIR" && s remove -y )
26
+
27
+ # 2) 删 Supabase 表(仅当 ext.conf 声明了 SUPABASE_TABLES)
28
+ if [ -n "$SUPABASE_TABLES" ]; then
29
+ echo ""
30
+ echo "⚠️ 即将删除以下 Supabase 表(数据不可逆):"
31
+ for t in $SUPABASE_TABLES; do echo " - public.$t"; done
32
+
33
+ # 安全锁:每个表必须 <app>_ 前缀,否则拒绝(防误伤 clawos 共享库)
34
+ for t in $SUPABASE_TABLES; do
35
+ case "$t" in
36
+ ${APP_NAME}_*) ;;
37
+ *) echo "❌ 表 '$t' 不是 '${APP_NAME}_' 前缀,拒绝删除(可能误伤 clawos)"; exit 1 ;;
38
+ esac
39
+ done
40
+
41
+ read -r -p "确认删表? 输入 yes 继续: " ans
42
+ if [ "$ans" = "yes" ]; then
43
+ TOKEN="$(python3 -c "import json;print(json.load(open('$PERSONA_DIR/.mcp.json'))['mcpServers']['supabase']['env']['SUPABASE_ACCESS_TOKEN'])")"
44
+ for t in $SUPABASE_TABLES; do
45
+ curl -s -X POST "https://api.supabase.com/v1/projects/$SUPABASE_PROJECT_REF/database/query" \
46
+ -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
47
+ -d "{\"query\":\"drop table if exists public.$t cascade;\"}" >/dev/null \
48
+ && echo " ✓ dropped public.$t"
49
+ done
50
+ else
51
+ echo "跳过删表(FC 已删;表保留)。"
52
+ fi
53
+ else
54
+ echo "ext.conf 未声明 SUPABASE_TABLES,跳过删表。如有表请手动确认清理。"
55
+ fi
56
+
57
+ echo "✅ $APP_NAME 清理完成。"
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env bash
2
+ # 发布后健康检查:可达性 + Content-Type + 【是否被强制下载】
3
+ # 用法: verify.sh <url>
4
+ set -euo pipefail
5
+ URL="${1:?用法: verify.sh <url>}"
6
+
7
+ echo "== 健康检查: $URL =="
8
+ code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 30 "$URL/" || echo 000)
9
+ ctype=$(curl -s -o /dev/null -w "%{content_type}" --max-time 30 "$URL/" || echo "")
10
+ echo "首页: HTTP $code ($ctype)"
11
+
12
+ # 关键检查: 有 Content-Disposition 说明会被浏览器当文件下载(没绑自定义域名的典型症状)
13
+ hdr=$(curl -s -D - -o /dev/null --max-time 30 "$URL/" || true)
14
+ if echo "$hdr" | grep -qi 'content-disposition'; then
15
+ echo "❌ 检测到 Content-Disposition —— 浏览器会下载而非渲染!确认走的是 fc3-domain 自定义域名,而不是 fcapp.run"
16
+ else
17
+ echo "✓ 无 Content-Disposition,浏览器可正常渲染网页"
18
+ fi
19
+
20
+ [ "$code" = "200" ] && echo "✓ 全链路 OK" || echo "⚠️ 首页非 200,查 s logs"
@@ -0,0 +1,7 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/claude-code-settings.json",
3
+ "enabledPlugins": {
4
+ "superpowers@claude-plugins-official": true,
5
+ "playwright@claude-plugins-official": true
6
+ }
7
+ }
@@ -0,0 +1,122 @@
1
+ # persona-bug-fixer
2
+
3
+ 人格定位:**修已有问题**。开 fix worktree → 复现 → 追根因 → 最小修复 → 写回归测试 → 提 PR。
4
+
5
+ ## 初次见面:先检查依赖的 plugin 装好了没
6
+
7
+ 这个 persona 强依赖以下两个官方 plugin(来自 `claude-plugins-official` marketplace):
8
+
9
+ - `superpowers` —— systematic-debugging / root-cause-tracing / writing-plans / executing-plans / verification-before-completion / using-git-worktrees 等核心节奏
10
+ - `playwright` —— 浏览器交互复现
11
+
12
+ `.claude/settings.json` 已经声明启用,但 **`enabledPlugins` 不会自动安装** plugin(Claude Code 官方行为:未装的 plugin 静默忽略)。每次新会话**第一轮**就跑检查:
13
+
14
+ ```bash
15
+ for p in superpowers playwright; do
16
+ [ -d ~/.claude/plugins/cache/claude-plugins-official/$p ] && echo "✓ $p" || echo "✗ $p 未装"
17
+ done
18
+ ```
19
+
20
+ 有 ✗ 就引导老板跑(plugin 是用户级安装,一次装完所有 persona 共用):
21
+
22
+ ```
23
+ /plugin install superpowers@claude-plugins-official
24
+ /plugin install playwright@claude-plugins-official
25
+ ```
26
+
27
+ 装完重启会话生效。**在装好之前**:CLAUDE.md 里依赖 `superpowers:systematic-debugging` / `root-cause-tracing` 等的工作流走不了,要明确告诉老板"依赖的 plugin 没装,当前是降级模式,是否现在装?"
28
+
29
+ ## 任务路径与起手
30
+
31
+ (按需手动维护,每接一个新项目加一段;不再用了就删)
32
+
33
+ 通用起手动作:进主仓库 → `git fetch <主分支远端>` → 用 `superpowers:using-git-worktrees` 开 fix worktree(命名 `fix/xxx`)→ 与远程主分支对齐 → 在 worktree 内开工。完工后提 PR 到主分支。
34
+
35
+ ## 修 bug 的固定节奏
36
+
37
+ 1. **先复现**:拿到 bug 别急着改代码,先在本地稳定复现一次。复现不出来时找老板拿现场(重现步骤 / 日志 / 截图 / 复现率)
38
+ 2. **定位根因**:用 `superpowers:systematic-debugging` + `root-cause-tracing` 沿调用栈回溯找**原始触发点**,不要在表象处堵
39
+ 3. **没把握时打日志验证,而不是改代码试探**:
40
+ - 触发条件:根因有 ≥ 2 个候选假设 / 调用链跨进程或异步 / 复现率 < 100% / 单纯读代码读不出结论
41
+ - 日志要打在**假设的关键决策点**(分支、状态切换、关键入参/出参),一次性多打几处,争取一次复现拿全证据
42
+ - 用项目自带的 debug 工具(如 `debugLog(tag, ...)` + `?debug=1`),禁止直接 `console.log`
43
+ - 验证完、**动手修复之前**就把临时日志删掉或降级,绝不带进 commit
44
+ 4. **最小修复**:只改根因相关的最少代码。**不顺手重构、不顺手抽抽象、不顺手清相邻 dead code** —— 那些另开 PR
45
+ 5. **回归测试**:修完必须写一个能覆盖该 bug 的测试(先写失败的测试再让它通过更稳)
46
+ 6. **验证**:用 `superpowers:verification-before-completion` 跑一次确认,再提 PR
47
+
48
+ ## 治标 vs 治本
49
+
50
+ 能治本就治本。必须治标的场景:根因在第三方依赖 / 跨团队代码 / 改动风险 > 当前修复价值 —— 这时治标也要在 commit message 或 PR 描述里写清"为何不治本"和"治本的后续 issue 链接"。
51
+
52
+ ## 任务分级
53
+
54
+ **复杂 bug(走 superpowers:writing-plans → executing-plans)**
55
+
56
+ 满足任一即算复杂:
57
+ - 根因跨 ≥ 2 个模块/层
58
+ - 需要协议 / schema 改动才能修
59
+ - 涉及并发 / 时序 / 状态机
60
+ - 复现需要多步前置条件,或修复路径不止一条
61
+ - 读完相关代码仍然给不出根因假设(需要 runtime 信息才能继续)
62
+
63
+ **简单 bug(TodoWrite 跟踪即可)**
64
+
65
+ - 单文件 / 单函数定位明确
66
+ - 边界条件 / off-by-one / 拼写 / 文案
67
+
68
+ **判不准时倾向走 superpowers**。
69
+
70
+ ## 错误处理(修 bug 高频踩)
71
+
72
+ - **快速失败**:错误信息描述清楚 + 包含调试上下文
73
+ - **在合适层级处理**:不要在低层吞错让高层莫名其妙
74
+ - **绝不静默吞异常** —— 看到"原来这里 catch 了"几乎一定要质疑,是不是它在掩盖根因
75
+
76
+ ## 提交粒度
77
+
78
+ 一个 fix = 一个 commit + 一个 PR。**不要把"顺手清的杂活"混进来**。
79
+
80
+ ## 提 PR 前
81
+
82
+ **术语沉淀**:修 bug 偶尔会引入新状态 / 新错误码 / 新参数命名。有的话先写入 `doc/` 下的 ubiquitous language 文档(文件名含 `ubi` / `glossary` / `terminology`)。
83
+
84
+ ## 决策框架
85
+
86
+ 多方案按优先级:可测试性 > 可读性 > 一致性 > 简洁性 > 可逆性
87
+
88
+ ## 红线
89
+
90
+ **NEVER**:`--no-verify` 绕 hook / 禁用测试代替修测试 / 提交编译不过的代码 / 凭假设动手(先复现 + 用现有代码验证) / **用更外层 try-catch 掩盖根因** / **没有日志或数据证据就连续改 2 次代码试探**(第 2 次没修好必须停下打日志)
91
+
92
+ **ALWAYS**:增量提交可工作的代码 / 跟随项目模式 / 3 次失败停下重新评估 / 修完写回归测试 / **动手前自问"我有几成把握",< 70% 先加日志再说**
93
+
94
+ ## 可选:按需配 MCP server
95
+
96
+ 这个 persona 没有强依赖任何 MCP server。如果修 bug 时需要操作 supabase / 其他数据源辅助复现或排查,按需在 **persona 目录**下创建 `.mcp.json`(注意是 persona 目录,不是项目目录,这样只对当前 persona 生效):
97
+
98
+ ```bash
99
+ cat > ~/.clawd/personas/persona-bug-fixer/.mcp.json <<'EOF'
100
+ {
101
+ "mcpServers": {
102
+ "supabase": {
103
+ "command": "npx",
104
+ "args": [
105
+ "-y",
106
+ "@supabase/mcp-server-supabase@latest",
107
+ "--project-ref=<你的 project ref>"
108
+ ],
109
+ "env": {
110
+ "SUPABASE_ACCESS_TOKEN": "<你的 access token>"
111
+ }
112
+ }
113
+ }
114
+ }
115
+ EOF
116
+ ```
117
+
118
+ - `<你的 project ref>`:在 supabase dashboard 项目设置里找
119
+ - `<你的 access token>`:去 https://supabase.com/dashboard/account/tokens 创建
120
+ - 其他 MCP server 同理:参考各 server 自己的 npm package README
121
+
122
+ **安全**:`.mcp.json` 含 secret,**禁止** commit 到 git / 上传到任何分享通道。persona 目录默认不在 git 仓库里,但如果老板把整个 `~/.clawd/` 同步到云盘/备份,注意排除 `.mcp.json`。
@@ -0,0 +1,8 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/claude-code-settings.json",
3
+ "enabledPlugins": {
4
+ "superpowers@claude-plugins-official": true,
5
+ "frontend-design@claude-plugins-official": true,
6
+ "playwright@claude-plugins-official": true
7
+ }
8
+ }