@clawos-dev/clawd 0.2.447 → 0.2.449

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 CHANGED
@@ -63666,7 +63666,7 @@ function computeMethodAccess(args) {
63666
63666
  }
63667
63667
 
63668
63668
  // src/version.ts
63669
- var version = "0.2.447".length > 0 ? "0.2.447" : "dev";
63669
+ var version = "0.2.449".length > 0 ? "0.2.449" : "dev";
63670
63670
 
63671
63671
  // src/cli-probe/probe.ts
63672
63672
  var fs68 = __toESM(require("fs"), 1);
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env bash
2
+ # ============================================================
3
+ # 发布/清理动手前把阿里云凭证准备好(publish.sh / remove-extension.sh 共用)。
4
+ # 被 `source` 进来,再调 ensure_credentials。跟 ensure-toolchain.sh 是一对:
5
+ # 那个管「命令在不在」,这个管「命令拿什么身份跑」。
6
+ #
7
+ # 硬约束:**全程不碰用户全局 ~/.s**。clawd 发布用的是 ship 出来的共享 demo AK,
8
+ # 而 ~/.s/access.yaml 里的 default 是用户自己账号的 Serverless Devs 凭证——它是用户的
9
+ # 东西,不是我们的。做法对齐本 kit 处理 aliyun CLI 的方式(凭证只进本次进程的 env,
10
+ # 全局 ~/.aliyun/ 一个字节不碰):把 Serverless Devs 的配置家目录整个指到 deploy-kit
11
+ # 名下,access.yaml 写在那里。
12
+ # ============================================================
13
+
14
+ # s 的 profile 名。刻意不叫 default:万一哪天某个版本的 s 不认 serverless_devs_config_home,
15
+ # 隔离失效时它会退回去用户的 ~/.s/access.yaml 里找 clawd-fc —— 找不到就
16
+ # `Not found access: clawd-fc` 硬报错,而不是默默拿用户的 default 凭证把 extension
17
+ # 部署进别人的账号。调 s 时都显式带 `--access "$S_ACCESS_ALIAS"`:命令行参数覆盖 s.yaml
18
+ # 里的 access 字段,本改动前 scaffold 的老项目(s.yaml 写着 default)照样走这条。
19
+ S_ACCESS_ALIAS=clawd-fc
20
+
21
+ # 分层加载阿里云 AK 进 env:shared demo(OTA 每次启动覆盖)→ 本机 .local override(永不被覆盖)。
22
+ ensure_aliyun_env() {
23
+ local kit="${KIT_DIR:?ensure_aliyun_env 需要调用方先设 KIT_DIR}"
24
+ [ -f "$kit/.secrets/aliyun.env" ] || {
25
+ echo "❌ 缺 $kit/.secrets/aliyun.env(clawd ship 的共享 demo 凭证,OTA 自动刷新)。参考 .secrets/aliyun.env.example 填写。" >&2
26
+ return 1
27
+ }
28
+ set -a; . "$kit/.secrets/aliyun.env"; set +a
29
+ # aliyun.env.local: 本机个人 override(永远不被 OTA 覆盖,见 refreshDeployKit)。有就后 source 覆盖 env。
30
+ # 写成 if 而不是 `[ -f ] && { }`:后者在函数末尾会把「文件不存在」变成函数返回 1,调用方 set -e 直接中止。
31
+ if [ -f "$kit/.secrets/aliyun.env.local" ]; then
32
+ set -a; . "$kit/.secrets/aliyun.env.local"; set +a
33
+ fi
34
+ export ALIBABA_CLOUD_REGION_ID="${REGION:?config.env 缺 REGION}"
35
+ }
36
+
37
+ # 把 Serverless Devs 的配置家目录(access.yaml / 组件缓存 / 日志)指到 deploy-kit 名下,
38
+ # 再把当前 env 里的凭证写成 $S_ACCESS_ALIAS profile。
39
+ # s v3 的 rootHome = `$serverless_devs_config_home/.s`(默认 `$HOME/.s`),`s -v` 打印的 s-home 就是它——
40
+ # 注意差一层:env 指到 X,文件落 X/.s/,所以下面是 "$s_home/.s/access.yaml" 而不是 "$s_home/access.yaml"。
41
+ # 前置:ensure_toolchain(aliyun 可执行)+ ensure_aliyun_env(AK 进 env)。
42
+ # 放 $KIT_DIR 下而不是另起路径:跟它写的那份凭证同源,且 refreshDeployKit 只覆盖
43
+ # scripts/ contract/ .secrets/,OTA 不会动这里的组件缓存。
44
+ ensure_s_access() {
45
+ local kit="${KIT_DIR:?ensure_s_access 需要调用方先设 KIT_DIR}"
46
+ local s_home="$kit/.s-home"
47
+ local ident acc
48
+
49
+ ident="$(aliyun --mode AK sts GetCallerIdentity 2>&1)" || {
50
+ echo "❌ aliyun sts GetCallerIdentity 失败(检查 .secrets/aliyun.env 凭证):" >&2
51
+ echo "$ident" >&2
52
+ return 1
53
+ }
54
+ # 用 node 不用 python3:目标机器保证有 node(clawd 本身就是 node 跑的),不保证有 python
55
+ # —— 清理路径尤其不该多一个装不上就跑不了的依赖。
56
+ acc="$(printf '%s' "$ident" | node -e 'console.log(JSON.parse(require("fs").readFileSync(0,"utf8")).AccountId)')" || {
57
+ echo "❌ 解析 AccountId 失败,aliyun 输出非 JSON:" >&2
58
+ echo "$ident" >&2
59
+ return 1
60
+ }
61
+
62
+ export serverless_devs_config_home="$s_home"
63
+ mkdir -p "$s_home/.s"
64
+ # umask 管「新建」:SK 从落盘第一个字节起就是 0600,不留「先 644 写完再 chmod」的窗口。
65
+ # 后面的 chmod 管「文件已存在」:umask 不改已有文件的 mode,重复发布时靠它兜底。
66
+ ( umask 077; cat > "$s_home/.s/access.yaml" <<EOF
67
+ $S_ACCESS_ALIAS:
68
+ AccountID: '$acc'
69
+ AccessKeyID: $ALIBABA_CLOUD_ACCESS_KEY_ID
70
+ AccessKeySecret: $ALIBABA_CLOUD_ACCESS_KEY_SECRET
71
+ EOF
72
+ )
73
+ chmod 600 "$s_home/.s/access.yaml"
74
+ }
75
+
76
+ ensure_credentials() {
77
+ ensure_aliyun_env
78
+ ensure_s_access
79
+ }
@@ -0,0 +1,204 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest'
2
+ import { execFileSync } from 'node:child_process'
3
+ import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, existsSync, statSync, cpSync, rmSync } from 'node:fs'
4
+ import { join } from 'node:path'
5
+ import { tmpdir } from 'node:os'
6
+
7
+ // ensure-credentials.sh 的契约测试。核心不变量:**发布/清理全程不碰用户全局 ~/.s**。
8
+ // 老实现 `cat > ~/.s/access.yaml` 会把用户自己的 Serverless Devs default 凭证顶掉且不可恢复
9
+ // (所有装了 clawd 的用户点一次「发布上线」就中招),这些用例就是钉死那个回归。
10
+ //
11
+ // 真脚本 + 真 bash 跑,只把两个外部 CLI(aliyun / s)换成记录 argv 的 shim:
12
+ // 断言的是脚本自己的行为(写哪、写什么、传什么 flag),没有 mock 掉被测逻辑。
13
+
14
+ const SCRIPTS_DIR = __dirname
15
+
16
+ interface Sandbox {
17
+ root: string
18
+ kit: string
19
+ home: string
20
+ bin: string
21
+ }
22
+
23
+ let box: Sandbox
24
+
25
+ /** 用户自己的 ~/.s/access.yaml —— 全程必须一个字节都不变。 */
26
+ const USER_ACCESS_YAML = `default:
27
+ AccountID: '1999888877776666'
28
+ AccessKeyID: LTAI-USER-OWN-KEY
29
+ AccessKeySecret: user-own-secret
30
+ `
31
+
32
+ /** aliyun CLI shim:`sts GetCallerIdentity` 回真 CLI 那样的 JSON,其余子命令原样成功。 */
33
+ function writeAliyunShim(bin: string, accountId = '5566778899001122'): void {
34
+ writeFileSync(
35
+ join(bin, 'aliyun'),
36
+ `#!/usr/bin/env bash\nprintf '{"AccountId":"${accountId}","Arn":"acs:ram::${accountId}:user/clawd-fc-developer"}\\n'\n`,
37
+ { mode: 0o755 },
38
+ )
39
+ }
40
+
41
+ /** s shim:把每次调用的 argv 追加到 $S_ARGV_LOG,供断言 --access 传对了没。 */
42
+ function writeSShim(bin: string, log: string): void {
43
+ writeFileSync(
44
+ join(bin, 's'),
45
+ `#!/usr/bin/env bash\nprintf '%s\\n' "$*" >> "${log}"\n`,
46
+ { mode: 0o755 },
47
+ )
48
+ }
49
+
50
+ beforeEach(() => {
51
+ const root = mkdtempSync(join(tmpdir(), 'ensure-creds-'))
52
+ const kit = join(root, 'deploy-kit')
53
+ const home = join(root, 'home')
54
+ const bin = join(root, 'bin')
55
+ mkdirSync(join(kit, '.secrets'), { recursive: true })
56
+ mkdirSync(join(home, '.s'), { recursive: true })
57
+ mkdirSync(bin, { recursive: true })
58
+
59
+ // 真脚本进沙箱 kit(.secrets 用假凭证,绝不碰 repo 里 ship 的那份)
60
+ cpSync(SCRIPTS_DIR, join(kit, 'scripts'), { recursive: true })
61
+ writeFileSync(
62
+ join(kit, '.secrets', 'aliyun.env'),
63
+ 'ALIBABA_CLOUD_ACCESS_KEY_ID=LTAI-SHARED-DEMO\nALIBABA_CLOUD_ACCESS_KEY_SECRET=shared-demo-secret\n',
64
+ )
65
+ writeFileSync(join(home, '.s', 'access.yaml'), USER_ACCESS_YAML)
66
+ writeAliyunShim(bin)
67
+
68
+ box = { root, kit, home, bin }
69
+ })
70
+
71
+ afterEach(() => {
72
+ rmSync(box.root, { recursive: true, force: true })
73
+ })
74
+
75
+ /** 在沙箱里 source ensure-credentials.sh 并跑一段 bash,返回 stdout。 */
76
+ function runInKit(body: string): string {
77
+ const script = `
78
+ set -euo pipefail
79
+ KIT_DIR="${box.kit}"
80
+ REGION="\${REGION:-cn-hangzhou}"
81
+ source "$KIT_DIR/scripts/ensure-credentials.sh"
82
+ ${body}
83
+ `
84
+ return execFileSync('bash', ['-c', script], {
85
+ env: { ...process.env, HOME: box.home, PATH: `${box.bin}:${process.env.PATH}` },
86
+ encoding: 'utf8',
87
+ })
88
+ }
89
+
90
+ function userAccessYaml(): string {
91
+ return readFileSync(join(box.home, '.s', 'access.yaml'), 'utf8')
92
+ }
93
+
94
+ function kitAccessYaml(): string {
95
+ return readFileSync(join(box.kit, '.s-home', '.s', 'access.yaml'), 'utf8')
96
+ }
97
+
98
+ describe('ensure_credentials', () => {
99
+ it('不碰用户全局 ~/.s/access.yaml', () => {
100
+ runInKit('ensure_credentials')
101
+ expect(userAccessYaml()).toBe(USER_ACCESS_YAML)
102
+ })
103
+
104
+ it('凭证写进 kit 私有 s-home', () => {
105
+ runInKit('ensure_credentials')
106
+ const yaml = kitAccessYaml()
107
+ expect(yaml).toContain("AccountID: '5566778899001122'")
108
+ expect(yaml).toContain('AccessKeyID: LTAI-SHARED-DEMO')
109
+ expect(yaml).toContain('AccessKeySecret: shared-demo-secret')
110
+ })
111
+
112
+ it('profile 名是 clawd-fc 而不是 default', () => {
113
+ // 隔离万一失效(某版本 s 不认 serverless_devs_config_home)时,s 会去用户
114
+ // ~/.s/access.yaml 找 clawd-fc —— 找不到就硬报错,而不是默默拿用户的 default
115
+ // 凭证把 extension 部署进别人的账号。
116
+ runInKit('ensure_credentials')
117
+ expect(kitAccessYaml()).toMatch(/^clawd-fc:$/m)
118
+ expect(kitAccessYaml()).not.toMatch(/^default:$/m)
119
+ })
120
+
121
+ it('把 Serverless Devs 的家目录指向 kit 私有目录', () => {
122
+ const out = runInKit('ensure_credentials; echo "S_HOME=$serverless_devs_config_home"')
123
+ expect(out).toContain(`S_HOME=${box.kit}/.s-home`)
124
+ })
125
+
126
+ it('access.yaml 权限 600(含 SK,不给同机其它用户读)', () => {
127
+ runInKit('ensure_credentials')
128
+ const mode = statSync(join(box.kit, '.s-home', '.s', 'access.yaml')).mode & 0o777
129
+ expect(mode.toString(8)).toBe('600')
130
+ })
131
+
132
+ it('aliyun.env.local 覆盖 shared 凭证', () => {
133
+ writeFileSync(
134
+ join(box.kit, '.secrets', 'aliyun.env.local'),
135
+ 'ALIBABA_CLOUD_ACCESS_KEY_ID=LTAI-MY-OWN\nALIBABA_CLOUD_ACCESS_KEY_SECRET=my-own-secret\n',
136
+ )
137
+ runInKit('ensure_credentials')
138
+ expect(kitAccessYaml()).toContain('AccessKeyID: LTAI-MY-OWN')
139
+ expect(kitAccessYaml()).toContain('AccessKeySecret: my-own-secret')
140
+ })
141
+
142
+ it('缺共享凭证文件时 fail-loud', () => {
143
+ rmSync(join(box.kit, '.secrets', 'aliyun.env'))
144
+ expect(() => runInKit('ensure_credentials')).toThrow()
145
+ expect(existsSync(join(box.kit, '.s-home', '.s', 'access.yaml'))).toBe(false)
146
+ })
147
+ })
148
+
149
+ describe('publish.sh', () => {
150
+ it('用 clawd-fc profile 调 s deploy,不碰用户全局 ~/.s', () => {
151
+ const log = join(box.root, 's-argv.log')
152
+ writeSShim(box.bin, log)
153
+ // 自定义域名查询回空 → publish.sh 在 verify 段 exit 1。deploy 之后的事,
154
+ // 不影响本用例要断言的凭证行为,省掉 curl shim。
155
+ writeFileSync(
156
+ join(box.bin, 'aliyun'),
157
+ `#!/usr/bin/env bash\ncase " $* " in\n *" sts "*) printf '{"AccountId":"5566778899001122"}\\n' ;;\n *) printf '{"customDomains":[]}\\n' ;;\nesac\n`,
158
+ { mode: 0o755 },
159
+ )
160
+
161
+ const ext = join(box.root, 'proj')
162
+ const persona = join(box.root, 'persona')
163
+ mkdirSync(join(ext, 'server'), { recursive: true })
164
+ mkdirSync(join(persona, 'extension-kit', 'contract'), { recursive: true })
165
+ mkdirSync(join(box.kit, 'contract'), { recursive: true })
166
+ writeFileSync(join(box.kit, 'contract', 'bootstrap'), '#!/usr/bin/env bash\n')
167
+ writeFileSync(join(ext, 'ext.conf'), 'APP_NAME=demo\nSLUG=ab12\nCODE_DIR=./server\n')
168
+ writeFileSync(join(persona, 'extension-kit', 'config.env'), 'REGION=cn-hangzhou\n')
169
+ writeFileSync(join(persona, 'extension-kit', 'contract', 's.yaml.tmpl'), 'name: __DEPLOY_NAME__-app\naccess: clawd-fc\n')
170
+
171
+ expect(() =>
172
+ execFileSync('bash', [join(box.kit, 'scripts', 'publish.sh'), ext, persona], {
173
+ env: { ...process.env, HOME: box.home, PATH: `${box.bin}:${process.env.PATH}` },
174
+ encoding: 'utf8',
175
+ stdio: 'pipe',
176
+ }),
177
+ ).toThrow() // verify 段无域名 → exit 1,deploy 已经跑过了
178
+
179
+ expect(readFileSync(log, 'utf8')).toContain('deploy -y --access clawd-fc')
180
+ expect(userAccessYaml()).toBe(USER_ACCESS_YAML)
181
+ })
182
+ })
183
+
184
+ describe('remove-extension.sh', () => {
185
+ it('用 clawd-fc profile 调 s remove,不碰用户全局 ~/.s', () => {
186
+ const log = join(box.root, 's-argv.log')
187
+ writeSShim(box.bin, log)
188
+
189
+ const ext = join(box.root, 'proj')
190
+ const persona = join(box.root, 'persona')
191
+ mkdirSync(ext, { recursive: true })
192
+ mkdirSync(join(persona, 'extension-kit'), { recursive: true })
193
+ writeFileSync(join(ext, 'ext.conf'), 'APP_NAME=demo\nSLUG=ab12\n')
194
+ writeFileSync(join(persona, 'extension-kit', 'config.env'), 'REGION=cn-hangzhou\n')
195
+
196
+ execFileSync('bash', [join(box.kit, 'scripts', 'remove-extension.sh'), ext, persona], {
197
+ env: { ...process.env, HOME: box.home, PATH: `${box.bin}:${process.env.PATH}` },
198
+ encoding: 'utf8',
199
+ })
200
+
201
+ expect(readFileSync(log, 'utf8')).toContain('remove -y --access clawd-fc')
202
+ expect(userAccessYaml()).toBe(USER_ACCESS_YAML)
203
+ })
204
+ })
@@ -39,21 +39,8 @@ DEPLOY_NAME="${APP_NAME}-${SLUG}"
39
39
  # 2) 工具链 + 阿里云凭证(分层:shared demo + 可选 local override)
40
40
  source "$KIT_DIR/scripts/ensure-toolchain.sh"
41
41
  ensure_toolchain
42
- [ -f "$KIT_DIR/.secrets/aliyun.env" ] || { echo "❌ 缺 $KIT_DIR/.secrets/aliyun.env(clawd ship 的共享 demo 凭证,OTA 自动刷新)。参考 .secrets/aliyun.env.example 填写。" >&2; exit 1; }
43
- set -a; . "$KIT_DIR/.secrets/aliyun.env"; set +a
44
- # aliyun.env.local: 本机个人 override(永远不被 OTA 覆盖,见 refreshDeployKit)。有就后 source 覆盖 env。
45
- [ -f "$KIT_DIR/.secrets/aliyun.env.local" ] && { set -a; . "$KIT_DIR/.secrets/aliyun.env.local"; set +a; }
46
- export ALIBABA_CLOUD_REGION_ID="${REGION:?config.env 缺 REGION}"
47
- _ident="$(aliyun --mode AK sts GetCallerIdentity 2>&1)" || { echo "❌ aliyun sts GetCallerIdentity 失败(检查 .secrets/aliyun.env 凭证):" >&2; echo "$_ident" >&2; exit 1; }
48
- 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; }
49
- mkdir -p ~/.s
50
- cat > ~/.s/access.yaml <<EOF
51
- default:
52
- AccountID: '$ACC'
53
- AccessKeyID: $ALIBABA_CLOUD_ACCESS_KEY_ID
54
- AccessKeySecret: $ALIBABA_CLOUD_ACCESS_KEY_SECRET
55
- EOF
56
- chmod 600 ~/.s/access.yaml
42
+ source "$KIT_DIR/scripts/ensure-credentials.sh"
43
+ ensure_credentials
57
44
 
58
45
  # 3) persona hook(可选):校验/导出 persona 特化运行时变量(如 SESSION_SECRET / APP_BASE_URL / persona 密钥)
59
46
  if [ -f "$PERSONA_KIT/hooks/pre-deploy.sh" ]; then
@@ -94,7 +81,7 @@ sed "${sed_args[@]}" "$TMPL" > "$EXT_DIR/s.yaml"
94
81
  # 7) deploy + verify
95
82
  echo "::stage::deploy"
96
83
  echo "==> s deploy ($DEPLOY_NAME)"
97
- ( cd "$EXT_DIR" && s deploy -y )
84
+ ( cd "$EXT_DIR" && s deploy -y --access "$S_ACCESS_ALIAS" )
98
85
 
99
86
  echo "::stage::verify"
100
87
  # 精确匹配 s.yaml.tmpl 里写死的 domainName 模式: <DEPLOY_NAME>.app.clawos.chat
@@ -25,14 +25,13 @@ TABLE_PREFIX="${APP_NAME}_${SLUG:+${SLUG}_}"
25
25
  source "$KIT_DIR/scripts/ensure-toolchain.sh"
26
26
  ensure_toolchain
27
27
 
28
- # 阿里云凭证(分层:shared demo + 可选 local override)
29
- [ -f "$KIT_DIR/.secrets/aliyun.env" ] || { echo "❌ 缺 $KIT_DIR/.secrets/aliyun.env(clawd ship 的共享 demo 凭证,OTA 自动刷新)。" >&2; exit 1; }
30
- set -a; . "$KIT_DIR/.secrets/aliyun.env"; set +a
31
- [ -f "$KIT_DIR/.secrets/aliyun.env.local" ] && { set -a; . "$KIT_DIR/.secrets/aliyun.env.local"; set +a; }
28
+ # 阿里云凭证(分层:shared demo + 可选 local override)+ deploy-kit 私有的 s profile
29
+ source "$KIT_DIR/scripts/ensure-credentials.sh"
30
+ ensure_credentials
32
31
 
33
32
  # 1) 删 FC 侧(函数 + 触发器 + 自定义域名)
34
33
  echo "==> 删 FC 资源: $APP_NAME${SLUG:+ (deploy=${APP_NAME}-${SLUG})}"
35
- ( cd "$EXT_DIR" && s remove -y )
34
+ ( cd "$EXT_DIR" && s remove -y --access "$S_ACCESS_ALIAS" )
36
35
 
37
36
  # 2) 列出要删的 Supabase 表(脚本不直接调 DB,改打印 DROP 让 agent 用 supabase MCP 跑)。
38
37
  # bash 调不到 MCP(MCP 是 LLM 通道),pg 直连要密码 agent 拿不到 —— 最干净:打印 DROP,
@@ -1,15 +1,17 @@
1
1
  {
2
- "_comment": "preinstall ship 进 daemon defaults,daemon 启动时 refreshDaemonManagedDirs 把这文件同步到 ~/.clawd*/personas/persona-app-builder/.mcp.json。cc CLI 启动时(cwd=projects/<name>/)会向上找 persona dir 的 .mcp.json 自动加载。用 @aliyun-rds/supabase-mcp-server Mode 2 单实例直连(不再用 aliyun AK),底下接阿里云托管 supabase(公网 endpoint 121.196.249.178:80,项目 ra-ae0i57kef06s6ix / clawd-app-builder,杭州可用区J)。三件套(url + anon + service + jwt)明文写进这里跟旧的 aliyun AK 模式一样属于共享 demo 凭证——该 supabase 实例只承载 clawd demo 数据,泄漏面锁定在这个实例本身(不会波及阿里云账号内其它资源)。旧自建 supabase 120.26.157.138 已退役,旧共享 aliyun AK LTAI5tSebp9pyBLk2nxF5wDg 已 revoke。",
2
+ "_comment": "preinstall ship 进 daemon defaults,daemon 启动时 refreshDaemonManagedDirs 把这文件同步到 ~/.clawd*/personas/persona-app-builder/.mcp.json。cc CLI 启动时(cwd=projects/<name>/)会向上找 persona dir 的 .mcp.json 自动加载。用 @aliyun-rds/supabase-mcp-server Mode 2 单实例直连(不再用 aliyun AK),底下接阿里云托管 supabase(公网 endpoint 121.196.249.178:80,项目 ra-ae0i57kef06s6ix / clawd-app-builder,杭州可用区J)。旧自建 supabase 120.26.157.138 已退役,旧共享 aliyun AK LTAI5tSebp9pyBLk2nxF5wDg 已 revoke。**不要往这里加 --jwt-secret**:jwt-secret 是该实例的 JWT 签发密钥,拿到它可以自签任意 role/任意 user 的 token(含 service_role),下发出去等于把 anon/service key 的轮换能力一起交出去;而 MCP server 里它唯一的用处是 verify_jwt_secret 这个自检工具报个 found/preview(--jwt-secret 在其 CLI 里就标了 optional),建表读写和 auth user 管理都不经过它。**这份文件随 OTA 发给所有用户,所以这里放的每一把 key 都等于公开**:该实例上除 demo 数据外还有 clawd 自己的 clawd_prod/clawd_int schema(device_bindings 等),收敛方案见 doc/glossary/persona-cloud.md。",
3
3
  "mcpServers": {
4
4
  "supabase": {
5
5
  "type": "stdio",
6
6
  "command": "npx",
7
7
  "args": [
8
8
  "@aliyun-rds/supabase-mcp-server",
9
- "--supabase-url", "http://121.196.249.178:80",
10
- "--supabase-anon-key", "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJvbGUiOiJhbm9uIiwiaWF0IjoxNzgzMzEwODU1LCJleHAiOjEzMjkzOTUwODU1fQ.sHLZH8q2SGWYbNPEFOzTFlQEtjVK3z1DlEqoU_5Uyyg",
11
- "--supabase-service-role-key", "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJvbGUiOiJzZXJ2aWNlX3JvbGUiLCJpYXQiOjE3ODMzMTA4NTUsImV4cCI6MTMyOTM5NTA4NTV9.MdlymSh0DaJdiOVRmM3QnukUDtkOf_Eljh8_fpdLi8Q",
12
- "--jwt-secret", "P9ThaB8USJCYLQa3tcBB3oYGtKWu95LelFpQD3fo"
9
+ "--supabase-url",
10
+ "http://121.196.249.178:80",
11
+ "--supabase-anon-key",
12
+ "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJvbGUiOiJhbm9uIiwiaWF0IjoxNzgzMzEwODU1LCJleHAiOjEzMjkzOTUwODU1fQ.sHLZH8q2SGWYbNPEFOzTFlQEtjVK3z1DlEqoU_5Uyyg",
13
+ "--supabase-service-role-key",
14
+ "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJvbGUiOiJzZXJ2aWNlX3JvbGUiLCJpYXQiOjE3ODMzMTA4NTUsImV4cCI6MTMyOTM5NTA4NTV9.MdlymSh0DaJdiOVRmM3QnukUDtkOf_Eljh8_fpdLi8Q"
13
15
  ]
14
16
  }
15
17
  }
@@ -173,16 +173,20 @@ AK/SK 分两层,都放在共享 deploy-kit 下(`$HOME/.clawd/deploy-kit/.sec
173
173
  - **`aliyun.env`** — clawd ship 的**共享 demo 凭证**(归属 RAM 用户 `clawd-fc-developer`,权限收敛:`AliyunFCFullAccess` + `STSAssumeRole` + `Log` + `OSS` + `YundunCert-RO`)。**每次 daemon 启动 refreshDeployKit 会用 defaults 里的最新版覆盖本文件(OTA 即升级)**——用户不用手动 rotate。
174
174
  - **`aliyun.env.local`**(可选)— 本机个人 override,永远不被 OTA 覆盖。publish.sh / remove-extension.sh 先 source `aliyun.env`(shared)、再 source `aliyun.env.local`(后者的值覆盖前者)。用户想用自己的 AK(如不想让 demo AK 部署到自己账号)就复制 `aliyun.env.local.example` → `aliyun.env.local` 填自己的值。
175
175
 
176
- 手动跑 CLI 时在当前 shell 加载:
176
+ 手动跑 CLI 时在当前 shell 加载(`ensure-credentials.sh` 就是 publish.sh / remove-extension.sh 用的那套,别另起炉灶):
177
177
 
178
178
  ```bash
179
- set -a
180
- . "$HOME/.clawd/deploy-kit/.secrets/aliyun.env"
181
- [ -f "$HOME/.clawd/deploy-kit/.secrets/aliyun.env.local" ] && . "$HOME/.clawd/deploy-kit/.secrets/aliyun.env.local"
182
- set +a
179
+ export KIT_DIR="$HOME/.clawd/deploy-kit"
180
+ source "$HOME/.clawd/personas/persona-app-builder/extension-kit/config.env" # REGION 等平台变量
181
+ source "$KIT_DIR/scripts/ensure-toolchain.sh" && ensure_toolchain # 缺 aliyun/s 则装
182
+ source "$KIT_DIR/scripts/ensure-credentials.sh" && ensure_credentials
183
183
  ```
184
184
 
185
- 之后 `aliyun` / `s` / `ossutil` 会自动读这两个环境变量。Serverless Devs 还需 `~/.s/access.yaml` 含 `AccountID`(用 `aliyun sts GetCallerIdentity` 取),SK 走 heredoc 写入、不进命令行。
185
+ ⚠️ **上面这几行和后续的 `aliyun` / `s` 命令必须在同一个 Bash 调用里跑完**——你的 Bash tool 每次调用是独立进程,shell 变量不跨调用留存。分两次跑 = 第二次丢了 `serverless_devs_config_home`,`s` 会回头去读 `~/.s` 然后报 `Not found access: clawd-fc`。**这时正确做法是把 source 那几行和 `s` 命令拼进同一条命令重跑,绝不是去写 `~/.s/access.yaml`。**
186
+
187
+ 之后 `aliyun` / `ossutil` 直接可用(AK 已进 env)。**Serverless Devs(`s`)要显式带 profile**:凭证写在 deploy-kit 私有的 `.s-home/` 里、profile 名 `clawd-fc`,所以每次都是 `s deploy -y --access "$S_ACCESS_ALIAS"` / `s remove -y --access "$S_ACCESS_ALIAS"`。
188
+
189
+ **红线:绝不写用户全局 `~/.s/access.yaml`**。那里的 `default` 是用户自己账号的凭证,clawd 用的是共享 demo AK,盖上去用户的就没了、不可恢复。
186
190
 
187
191
  - **红线**:绝不 `echo` / 打印 / 写进日志或提交记录里暴露 SK;绝不把 `.secrets/` 拷进被部署的项目代码(已被 `.gitignore` 挡住)。**shared aliyun.env 为空**(demo 凭证意外没 ship)先停下提醒老板;**用户想用自己的 AK** 就引导他建 `aliyun.env.local`,别去改 shared 那份(OTA 会盖回)。
188
192
 
@@ -7,7 +7,9 @@
7
7
  # 保证多用户/多副本同名时不撞 FC 函数 + 不撞域名
8
8
  edition: 3.0.0
9
9
  name: __DEPLOY_NAME__-app
10
- access: default
10
+ # clawd 自己的 Serverless Devs profile,写在 deploy-kit 私有 s-home 里(见 scripts/ensure-credentials.sh)。
11
+ # 刻意不用 default —— 那是用户自己账号的 profile,不该被 clawd 读也不该被 clawd 写。
12
+ access: clawd-fc
11
13
 
12
14
  vars:
13
15
  region: __REGION__
@@ -2,7 +2,9 @@
2
2
  # 框架无关:command 固定走 ./bootstrap,入口由 FC_ENTRY 环境变量决定
3
3
  edition: 3.0.0
4
4
  name: __APP_NAME__-app
5
- access: default
5
+ # clawd 自己的 Serverless Devs profile,写在 deploy-kit 私有 s-home 里(见 scripts/ensure-credentials.sh)。
6
+ # 刻意不用 default —— 那是用户自己账号的 profile,不该被 clawd 读也不该被 clawd 写。
7
+ access: clawd-fc
6
8
 
7
9
  vars:
8
10
  region: __REGION__
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawos-dev/clawd",
3
- "version": "0.2.447",
3
+ "version": "0.2.449",
4
4
  "description": "Standalone clawd daemon — Claude Code (and future Codex) session server over WebSocket",
5
5
  "type": "module",
6
6
  "license": "MIT",