@clawos-dev/clawd 0.2.124-beta.246.7490f60 → 0.2.124-beta.248.02a0096

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.
@@ -163,10 +163,13 @@ createProject 成功后 daemon 自动:
163
163
 
164
164
  ## 后端:supabase MCP
165
165
 
166
- **`supabase`** —— 后端数据/认证/存储,唯一的 MCP 支柱。这份 Supabase **就是 clawos 在用的那一个**(`project-ref=qmhqmopgwsbmfxiqagay`),开工前确认能连上:
166
+ **`supabase`** —— 后端数据/认证/存储,唯一的 MCP 支柱。这份 Supabase **是阿里云自建实例**(`http://120.26.157.138:80`),全局 MCP / clawos / lovagent / moltoffer 都共用这一台。**不是**云上 `supabase.com` 的实例。开工前确认能连上:
167
167
 
168
168
  - 读表结构、跑 SQL、看 auth 用户都走这个 MCP,不要凭记忆猜 schema
169
- - **红线**:这是共享生产库。建表 / 迁移前先 `list_tables` 看清现状,新项目的表用清晰前缀(如 `<项目名>_xxx`)避免和 clawos 的表撞名;**绝不** drop / alter clawos 已有的表,不确定哪些是 clawos 的就先问老板
169
+ - 模板里 `extension-kit/config.env` + `server/.env.example` 已对齐这台阿里云自建实例(SUPABASE_URL / SUPABASE_KEY),创建 project 时拷进项目目录的 `.env` 也是同一套
170
+ - **红线**:这是共享生产库。建表 / 迁移前先 `list_tables` 看清现状,新项目的表**必须**用 `<项目名>_` 前缀沉到 `public` schema(如 `helloworld_click_counter`),避免和 clawos / 其它项目的表撞名;**绝不** drop / alter clawos 已有的表,不确定哪些是 clawos 的就先问老板
171
+
172
+ **易踩的坑**:MCP 工具走阿里云自建那台,假如哪一步把 `.env` / `config.env` 写成云上 `qmhqmopgwsbmfxiqagay.supabase.co`(曾经的旧默认),就会**MCP 建表落阿里云 / server 通过 supabase-js 查云上**,两台 PostgREST schema cache 各自独立 → `PGRST205 schema cache 找不到表`。看到 PGRST 系列错码先 `grep -r supabase.co` 自查模板配置,再怀疑 schema cache。
170
173
 
171
174
  ## 阿里云凭证:在 `.secrets/aliyun.env`,用时现读
172
175
 
@@ -8,11 +8,14 @@ REGION=cn-hangzhou
8
8
  FC_RUNTIME=custom.debian12
9
9
  NODE_LAYER=acs:fc:cn-hangzhou:official:layers/Nodejs22/versions/1
10
10
 
11
- # Supabase(clawos 共享库!建表务必加 <app>_ 前缀,绝不动 clawos 已有表)
12
- SUPABASE_URL=https://qmhqmopgwsbmfxiqagay.supabase.co
13
- SUPABASE_PROJECT_REF=qmhqmopgwsbmfxiqagay
14
- # anon/publishable key(公开可暴露,客户端用)
15
- SUPABASE_KEY=sb_publishable_mpSFrgfQ5UO1yTQeJDjYvg_4aUZgvmG
11
+ # Supabase(clawos 共享阿里云自建实例!建表务必加 <app>_ 前缀沉到 public schema,绝不动 clawos 已有表)
12
+ # 注意:这是阿里云自建 supabase(120.26.157.138),不是云上 supabase.com 实例 ——
13
+ # 全局 MCP / clawos / lovagent / moltoffer 都共用这一台,跟 supabase MCP 操作的也是这台。
14
+ # 之前误指云上 qmhqmopgwsbmfxiqagay.supabase.co 导致 MCP 建表落阿里云,server 走 supabase-js
15
+ # 查云上,两台 PostgREST schema cache 独立 → PGRST205 找不到表。
16
+ SUPABASE_URL=http://120.26.157.138:80
17
+ # anon/publishable key(公开可暴露,客户端用)。阿里云自建实例无 project-ref 概念。
18
+ SUPABASE_KEY=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJvbGUiOiJhbm9uIiwiaWF0IjoxNzY3OTU0MTY0LCJleHAiOjEzMjc4NTk0MTY0fQ.quOr1bQZ2OCQ49_UCto8LGY-n90Hlku1IbJYltFVWPc
16
19
 
17
20
  # FC 资源规格(够跑轻量 extension;按需改)
18
21
  FC_CPU=0.35
@@ -1,3 +1,6 @@
1
- SUPABASE_URL=https://qmhqmopgwsbmfxiqagay.supabase.co
2
- SUPABASE_KEY=sb_publishable_mpSFrgfQ5UO1yTQeJDjYvg_4aUZgvmG
1
+ # Supabase 共享阿里云自建实例(跟 extension-kit/config.env 对齐)。
2
+ # 这台 supabase 也是全局 MCP / clawos / lovagent / moltoffer 在用的同一台 ——
3
+ # 用 MCP 建表落到这里,server 通过 supabase-js 读也走这里,schema cache 对得齐。
4
+ SUPABASE_URL=http://120.26.157.138:80
5
+ SUPABASE_KEY=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJvbGUiOiJhbm9uIiwiaWF0IjoxNzY3OTU0MTY0LCJleHAiOjEzMjc4NTk0MTY0fQ.quOr1bQZ2OCQ49_UCto8LGY-n90Hlku1IbJYltFVWPc
3
6
  PORT=3000
@@ -13,6 +13,7 @@
13
13
  "@nestjs/core": "^10.4.0",
14
14
  "@nestjs/platform-express": "^10.4.0",
15
15
  "@supabase/supabase-js": "^2.45.0",
16
+ "dotenv": "^16.4.5",
16
17
  "reflect-metadata": "^0.2.2",
17
18
  "rxjs": "^7.8.1",
18
19
  "ws": "^8.18.0"
@@ -1,13 +1,10 @@
1
1
  import { Module } from '@nestjs/common';
2
+ import { MessagesModule } from './messages/messages.module';
2
3
 
3
- // 默认空白骨架 —— 没有任何业务 controller / service / module 注册。
4
- // 历史背景:之前导入 MessagesModule 自带留言板 API + Supabase repo,用户起 dev server 时 nest
5
- // 启动会初始化 supabase 模块(控制台 noise + 旁边业务代码会暗示 assistant 沿用留言板风格)。
6
- // 瘦身后只留启动骨架,业务逻辑由 assistant 按用户需求从零加(添 controller/module 进 imports 即可)。
7
- //
8
- // vite middleware / 静态资源 serve 路径仍由 main.ts 处理(dev mount vite,prod express.static),
9
- // 不需要 ServeStaticModule。
4
+ // dev 模式:main.ts mount vite middleware 处理非 /api 路径(含 HMR);
5
+ // prod 模式:main.ts express.static serve build 后的 web/dist。
6
+ // 两种模式都不用 ServeStaticModule(vite middleware 跟它会撞),就近在 main.ts 处理。
10
7
  @Module({
11
- imports: [],
8
+ imports: [MessagesModule],
12
9
  })
13
10
  export class AppModule {}
@@ -56,10 +56,20 @@ async function bootstrap() {
56
56
  allowedHosts,
57
57
  },
58
58
  // appType: 'spa' 让 vite middleware 自己 serve index.html + fallback 路由(base 下任何
59
- // 非 vite asset 路径都返 index.html)。nest router 注册在 vite 之前,/api 路由优先匹配。
59
+ // 非 vite asset 路径都返 index.html)。
60
60
  appType: 'spa',
61
61
  });
62
- app.use(vite.middlewares);
62
+ // /api/* 守卫:实测 vite 'spa' catch-all 会先吃掉 /api/* 返 HTML,导致 API 拿不到 JSON。
63
+ // Express middleware 栈顺序:vite.middlewares 这一行加进去时 nest controllers 还没挂
64
+ // (nest router init 在 app.listen 时),所以 vite 拦截优先于 nest router → 命中 /api 也走 vite。
65
+ // 守卫:req.url 命中 apiPrefix 时直接 next() 跳过 vite,让 nest router 接管返 JSON。
66
+ const apiPathPrefix = `/${apiPrefix}/`; // 如 '/preview/6173/api/' 或 '/api/'
67
+ app.use((req: any, res: any, next: any) => {
68
+ if (typeof req.url === 'string' && req.url.startsWith(apiPathPrefix)) {
69
+ return next(); // skip vite,让 nest router 处理 /api/*
70
+ }
71
+ return vite.middlewares(req, res, next);
72
+ });
63
73
  console.log(`[clawd-dev] vite middleware mounted (root=${viteRoot}, base=${viteBase}, apiPrefix=${apiPrefix}, allowedHosts=${allowedHosts.join(',')})`);
64
74
  } else {
65
75
  // prod / 脱离 clawd 跑:直接 serve 前端静态资源。
@@ -0,0 +1,27 @@
1
+ import {
2
+ BadRequestException,
3
+ Body,
4
+ Controller,
5
+ Get,
6
+ Post,
7
+ } from '@nestjs/common';
8
+ import { MessagesService } from './messages.service';
9
+
10
+ @Controller('messages')
11
+ export class MessagesController {
12
+ constructor(private readonly messages: MessagesService) {}
13
+
14
+ @Get()
15
+ list() {
16
+ return this.messages.list();
17
+ }
18
+
19
+ @Post()
20
+ create(@Body() body: { name?: string; content?: string }) {
21
+ const content = body?.content?.trim();
22
+ if (!content) {
23
+ throw new BadRequestException('content 不能为空');
24
+ }
25
+ return this.messages.create(body.name?.trim() ?? '', content);
26
+ }
27
+ }
@@ -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
+ }
@@ -1,3 +1,10 @@
1
+ // Dev 模式 .env loader:`nest start --watch` 不会经过 `node --env-file=.env`,
2
+ // server 拿不到 SUPABASE_URL/KEY → createClient 跑了但 process.env 是空 → PGRST 系列错码。
3
+ // 这里在 nest bootstrap 之前显式 require dotenv 把 .env 填进 process.env。
4
+ // prod 路径(node --env-file=.env dist/main.js)Node 自带 loader 已经填了,dotenv 看到
5
+ // 已存在的 env 不会覆盖(默认行为),行为对齐。
6
+ require('dotenv/config');
7
+
1
8
  // Node < 22 没有原生 WebSocket,而 @supabase/supabase-js 的 realtime 客户端
2
9
  // 初始化时需要它。在应用启动前补一个全局 WebSocket,使任意 Node 版本
3
10
  // (FC 运行时给的 node 可能是 18/20)下都能正常 createClient。
@@ -3,7 +3,7 @@
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>App</title>
6
+ <title>留言板 · 全链路测试</title>
7
7
  </head>
8
8
  <body>
9
9
  <div id="root"></div>
@@ -1,11 +1,161 @@
1
- // 默认空白页骨架 —— createProject + startDevServer 起来时 iframe 第一帧就是干净白板,
2
- // 不会闪一下 demo 内容再被 assistant 改成用户实际想要的页面。
3
- //
4
- // 历史背景:之前模板自带留言板 demo(App.jsx + messages controller),用户从「新建项目 →
5
- // 起 dev server → 假设 assistant 改 App.jsx 成 hello world」整个流程里第一眼看到的是留言板
6
- // UI,HMR 替换后才变成真正的内容 —— 视觉闪动 + 业务代码被模板暗示带偏(spec follow-up
7
- // 2026-06-03 老板拍)。瘦身后骨架只保留 vite + nest + react 可启动结构,内容由 assistant
8
- // 按用户需求从零写。
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
+
9
68
  export default function App() {
10
- return <div />;
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
+ );
11
161
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawos-dev/clawd",
3
- "version": "0.2.124-beta.246.7490f60",
3
+ "version": "0.2.124-beta.248.02a0096",
4
4
  "description": "Standalone clawd daemon — Claude Code (and future Codex) session server over WebSocket",
5
5
  "type": "module",
6
6
  "license": "MIT",