@clawos-dev/clawd 0.2.619 → 0.2.620
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/app-builder-plugin/mcp-server.cjs +6 -15
- package/dist/cli.cjs +150 -699
- package/dist/dispatch/mcp-server.cjs +6 -28
- package/dist/persona-defaults/persona-app-builder/.claude/skills/app-builder-projects/scripts/collect-files.mjs +6 -15
- package/dist/persona-defaults/persona-app-builder/.claude/skills/app-builder-projects/scripts/list-projects.mjs +1 -2
- package/dist/persona-defaults/persona-app-builder/.claude/skills/app-builder-projects/scripts/record-push.mjs +3 -5
- package/dist/persona-defaults/persona-app-builder/.claude/skills/app-builder-projects/scripts/render-sql.mjs +4 -6
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/app.module.ts +1 -7
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/main.ts +3 -9
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/polyfill.ts +3 -9
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/table-name.ts +2 -5
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/web/src/App.jsx +1 -8
- package/dist/persona-defaults/persona-app-builder/mcp/clawd-app-builder/mcp-server.cjs +6 -15
- package/dist/persona-defaults/persona-clawd-butler/.claude/skills/clawd-session-import/scripts/link-sessions.mjs +7 -23
- package/dist/persona-defaults/persona-clawd-butler/.claude/skills/scan-sessions-suggest-personas/scripts/extract-signals.mjs +6 -14
- package/dist/persona-defaults/persona-dataclaw-builder/.claude/skills/app-builder-projects/scripts/collect-files.mjs +6 -15
- package/dist/persona-defaults/persona-dataclaw-builder/.claude/skills/app-builder-projects/scripts/list-projects.mjs +1 -2
- package/dist/persona-defaults/persona-dataclaw-builder/.claude/skills/app-builder-projects/scripts/record-push.mjs +3 -5
- package/dist/persona-defaults/persona-dataclaw-builder/.claude/skills/app-builder-projects/scripts/render-sql.mjs +4 -6
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/server/src/app.module.ts +2 -6
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/server/src/dataclaw/dataclaw.client.ts +2 -4
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/server/src/dataclaw/dataclaw.controller.ts +1 -2
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/server/src/main.ts +4 -11
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/server/src/polyfill.ts +1 -5
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/web/src/api.js +1 -4
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/web/src/components/BarChart.jsx +0 -1
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/web/src/components/ResultCards.jsx +0 -1
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/web/src/components/ResultTable.jsx +0 -1
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/web/src/enums.js +2 -3
- package/dist/persona-defaults/persona-dataclaw-builder/mcp/clawd-app-builder/mcp-server.cjs +6 -15
- package/package.json +1 -1
|
@@ -25354,10 +25354,8 @@ var CLAUDE_CAPABILITIES = {
|
|
|
25354
25354
|
label: "Effort",
|
|
25355
25355
|
description: "Reasoning effort level",
|
|
25356
25356
|
scope: "tool-specific",
|
|
25357
|
-
// CC
|
|
25358
|
-
//
|
|
25359
|
-
// xhigh,但不传 --effort 时实际生效的是 ~/.claude/settings.json 的 effortLevel,
|
|
25360
|
-
// 而它的常见值就是 high;用 high 作 persona 补齐值对既有会话行为最中性。
|
|
25357
|
+
// CC 2.1.187 --effort values. Use high when backfilling persona defaults to match common
|
|
25358
|
+
// settings.json effortLevel; omitting --effort would instead follow that file.
|
|
25361
25359
|
options: [
|
|
25362
25360
|
{ value: "low", label: "Low" },
|
|
25363
25361
|
{ value: "medium", label: "Medium" },
|
|
@@ -25426,25 +25424,14 @@ async function handlePersonaDispatchToolCall(input, ctx) {
|
|
|
25426
25424
|
body: JSON.stringify({
|
|
25427
25425
|
// sessionId 是普通 body 参数(run handler 用作 sourceSessionId 归因),鉴权只看 Bearer
|
|
25428
25426
|
sessionId: ctx.sessionId,
|
|
25429
|
-
// 续派时可省 targetPersona(daemon 从台账取)
|
|
25430
25427
|
...input.targetPersona ? { targetPersona: input.targetPersona } : {},
|
|
25431
25428
|
prompt: input.prompt,
|
|
25432
|
-
// brief(老板拍板 2026-08-25):进台账 taskBrief + 任务包信封 <brief>
|
|
25433
25429
|
...input.brief ? { brief: input.brief } : {},
|
|
25434
|
-
// 续派(spec 2026-07-21):非空 = 把 prompt 打进该 dispatch 的原 worker session。
|
|
25435
25430
|
...input.dispatchId ? { dispatchId: input.dispatchId } : {},
|
|
25436
|
-
// 跨设备:非空时透传,daemon run handler 据此走 A 角色转发分支。
|
|
25437
25431
|
...input.targetDeviceId ? { targetDeviceId: input.targetDeviceId } : {},
|
|
25438
|
-
// T-20: 可选 override B session 使用的 claude 模型;MCP tool 层已 enum 校验,
|
|
25439
|
-
// daemon handler 层还会再校验一次(fail-fast)。
|
|
25440
25432
|
...input.model ? { model: input.model } : {},
|
|
25441
|
-
// 一次性 engine override(spec 2026-08-24):换 codex 的 model 必须连 engine 一起换。
|
|
25442
|
-
// 合法性 + 与 model 的匹配由 daemon handler 校验(错误消息列出 allowed 值)。
|
|
25443
25433
|
...input.tool ? { tool: input.tool } : {},
|
|
25444
|
-
// butler 顶部入口:可选 sessionLabel 落到新 worker SessionFile.label。
|
|
25445
|
-
// 长度 1-60 由 protocol schema 强约束;续派场景(dispatchId 非空)daemon 层静默丢弃。
|
|
25446
25434
|
...input.sessionLabel ? { sessionLabel: input.sessionLabel } : {},
|
|
25447
|
-
// Master 话题/线语义:原样透传,daemon 不解释(新开写台账 / 续派 merge)。
|
|
25448
25435
|
...input.meta ? { meta: input.meta } : {}
|
|
25449
25436
|
})
|
|
25450
25437
|
});
|
|
@@ -25632,24 +25619,15 @@ async function main() {
|
|
|
25632
25619
|
targetDeviceId: external_exports.string().optional().describe(
|
|
25633
25620
|
"contact deviceId of the remote device when delegating cross-device (omit for a local persona)"
|
|
25634
25621
|
),
|
|
25635
|
-
//
|
|
25636
|
-
// 2026-08-03 起不再用 enum 硬约束:codex 的模型表已运行时化(随安装的 codex 版本变),
|
|
25637
|
-
// 没有编译期常量可枚举——硬 enum 的前提正是一张静态表,而静态表会过期并拒掉 UI 里
|
|
25638
|
-
// 明明能选到的新模型。精确校验下沉到 daemon handler(按 target persona tool 查运行时
|
|
25639
|
-
// capabilities,fail-fast 且错误消息列出 allowed 值,caller 据此自我纠正)。
|
|
25622
|
+
// 模型列表随 engine 版本变化,不能编译期 enum 固定;daemon 按实际 engine capabilities 校验。
|
|
25640
25623
|
model: external_exports.string().optional().describe(
|
|
25641
25624
|
"override the target persona's default model for this one dispatch. New dispatches only (rejected together with dispatchId). Omit to use the persona default. The valid set depends on the engine that will actually run (see tool): claude \u2192 " + CLAUDE_MODEL_IDS.join(" / ") + "; codex \u2192 model ids vary by installed codex version (e.g. gpt-5.6-sol); pi \u2192 ids are '<provider>/<model>' from pi's own model table. Omit unless you know the exact id (invalid values are rejected with the allowed list)."
|
|
25642
25625
|
),
|
|
25643
|
-
//
|
|
25644
|
-
// 「这次用 codex 的某个模型干」必须能连 engine 一起换,否则 model 校验按 persona
|
|
25645
|
-
// 绑定的 engine 选表直接拒。
|
|
25626
|
+
// model 的合法值集属于 engine,跨 engine 换模型须同时指定对应 engine。
|
|
25646
25627
|
tool: external_exports.string().optional().describe(
|
|
25647
25628
|
"run this one dispatch on a specific engine instead of the persona's bound one: 'claude', 'codex' or 'pi'. One-off \u2014 the persona's own binding is untouched. Pass this whenever you pass a model that belongs to the other engine (e.g. tool='codex' + model='gpt-5.6-sol' for a persona bound to claude). New dispatches only: rejected together with dispatchId (follow-ups keep the worker session's engine) or with targetDeviceId (a remote persona keeps its owner's engine)."
|
|
25648
25629
|
),
|
|
25649
|
-
//
|
|
25650
|
-
// Sidebar 直接显示这个 label(否则兜底 cwd-basename 都是 persona 目录名,同一
|
|
25651
|
-
// persona 的 dispatched session 长得都一样)。仅新开 dispatch 生效;续派
|
|
25652
|
-
// (dispatchId set) 时 daemon 静默丢弃,保 worker session 稳定命名。
|
|
25630
|
+
// 只给新 worker 设置 label;续派保留既有命名。
|
|
25653
25631
|
sessionLabel: external_exports.string().optional().describe(
|
|
25654
25632
|
'short human-readable label for the new worker session (shown in sidebar, e.g. "\u4FEE sidebar \u9876\u90E8\u5165\u53E3" / "\u67E5\u6628\u665A SLS \u767B\u5F55\u5931\u8D25"). aim for 8-14 chars, verb-led. ignored when continuing an existing dispatch via dispatchId (worker session keeps its original label).'
|
|
25655
25633
|
),
|
|
@@ -25676,7 +25654,7 @@ async function main() {
|
|
|
25676
25654
|
reason: external_exports.string().optional().describe("failure reason (required when isFailure=true)")
|
|
25677
25655
|
}
|
|
25678
25656
|
},
|
|
25679
|
-
// sessionId
|
|
25657
|
+
// complete 的 sessionId 会被 schema strip;身份鉴权使用 Bearer。
|
|
25680
25658
|
async (input) => handlePersonaDispatchCompleteToolCall(input, { daemonUrl, daemonToken, sessionId, dispatchId })
|
|
25681
25659
|
);
|
|
25682
25660
|
server.registerTool(
|
|
@@ -1,17 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// + 永远排掉 `.git` 和 `.clawd-project.json`(插件的记账文件:publish / record-push 都会改它,进了仓库或哈希就会
|
|
6
|
-
// 「每推一次都变一次」,而且里面的 deployments / pushedCommit 也不是源码)。**不要**在这里另写一份排除表:publish.sh 渲进项目目录的 s.int.yaml / s.prod.yaml 含 supabase key,
|
|
7
|
-
// 它进不进仓库只该由 .gitignore 一处决定。
|
|
8
|
-
// 二进制(前 8KB 含 NUL)和超过 512KB 的文件跳过并列进 `skipped`——push_files 只收文本,
|
|
9
|
-
// 而且每个字节都要经模型上下文走一遍;agent 看到 skipped 里有该进仓库的东西要自己判断。
|
|
10
|
-
//
|
|
2
|
+
// 收集 GitHub MCP push_files 的文本文件;过滤以项目 .gitignore 为准,避免 s.*.yaml 中的 key 被提交。
|
|
3
|
+
// 始终排除 .git 及根 .clawd-project.json,记账变化不属于源码,不能进入推送或树哈希。
|
|
4
|
+
// 二进制(前 8KB 含 NUL)及超过 512KB 的文件列入 skipped,不进哈希;调用方需判断是否另行处理。
|
|
11
5
|
// 用法:collect-files.mjs --project <项目目录绝对路径>
|
|
12
6
|
// 输出:{ files: [{ path, content }], skipped: [{ path, reason }], treeHash }
|
|
13
|
-
//
|
|
14
|
-
// publish tool 再跑一次本脚本比对——不一致就是「改了没推」,拒绝发布。skipped 里的文件不进哈希。
|
|
7
|
+
// treeHash 为按 path 排序的 sha256(path\0content\0…);publish 重算后与 record-push 记账比较,拒绝未推的改动。
|
|
15
8
|
import { createHash } from 'node:crypto'
|
|
16
9
|
import { readdir, readFile, stat } from 'node:fs/promises'
|
|
17
10
|
import { join, relative, sep } from 'node:path'
|
|
@@ -58,7 +51,6 @@ function ignored(rel, isDir, rules) {
|
|
|
58
51
|
const segments = rel.split('/')
|
|
59
52
|
for (const rule of rules) {
|
|
60
53
|
if (rule.anchored) {
|
|
61
|
-
// 前缀匹配:rel 等于它、或在它下面
|
|
62
54
|
for (let i = 1; i <= segments.length; i++) {
|
|
63
55
|
const head = segments.slice(0, i).join('/')
|
|
64
56
|
if (rule.re.test(head) && (!rule.dirOnly || isDir || i < segments.length)) return true
|
|
@@ -67,7 +59,6 @@ function ignored(rel, isDir, rules) {
|
|
|
67
59
|
}
|
|
68
60
|
for (let i = 0; i < segments.length; i++) {
|
|
69
61
|
if (!rule.re.test(segments[i])) continue
|
|
70
|
-
// dirOnly:命中的这一段得是目录——要么它不是最后一段(后面还有东西),要么 rel 本身就是目录
|
|
71
62
|
if (!rule.dirOnly || i < segments.length - 1 || isDir) return true
|
|
72
63
|
}
|
|
73
64
|
}
|
|
@@ -78,7 +69,7 @@ let rules = []
|
|
|
78
69
|
try {
|
|
79
70
|
rules = parseGitignore(await readFile(join(root, '.gitignore'), 'utf8'))
|
|
80
71
|
} catch {
|
|
81
|
-
//
|
|
72
|
+
// 无法读取 .gitignore 时仍排除 .git 和根 .clawd-project.json。
|
|
82
73
|
}
|
|
83
74
|
|
|
84
75
|
const files = []
|
|
@@ -109,7 +100,7 @@ async function walk(dir) {
|
|
|
109
100
|
}
|
|
110
101
|
|
|
111
102
|
await walk(root)
|
|
112
|
-
//
|
|
103
|
+
// 按字符串序而非 localeCompare 排序,避免不同机器 locale 改变树哈希。
|
|
113
104
|
files.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0))
|
|
114
105
|
const hash = createHash('sha256')
|
|
115
106
|
for (const f of files) hash.update(f.path).update('\0').update(f.content).update('\0')
|
|
@@ -24,8 +24,7 @@ for (const dirName of names) {
|
|
|
24
24
|
try {
|
|
25
25
|
const value = JSON.parse(await readFile(join(projectsRoot, dirName, '.clawd-project.json'), 'utf8'))
|
|
26
26
|
if (value?.name !== dirName || typeof value.createdAt !== 'string') continue
|
|
27
|
-
//
|
|
28
|
-
// 插件还没读写过的老文件顶层就有 prodUrl,原样带出。
|
|
27
|
+
// prodUrl 兼容字段优先取 deployments.prod.url,缺席时保留 legacy 顶层值。
|
|
29
28
|
const prodUrl = value.deployments?.prod?.url
|
|
30
29
|
projects.push({ ...value, ...(prodUrl ? { prodUrl } : {}), projectDir: join(projectsRoot, dirName) })
|
|
31
30
|
} catch {
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// 用法:record-push.mjs --project <项目目录绝对路径> --commit <push_files 返回的 object.sha> --tree-hash <collect-files.mjs 输出的 treeHash> [--repo-url <https://github.com/ottin4ttc/...>]
|
|
2
|
+
// GitHub MCP push 后记 repoUrl、pushedCommit 和 pushedTreeHash 到 .clawd-project.json。
|
|
3
|
+
// 无本地 git 的项目发布时重算树哈希;一致才以 pushedCommit 记 publishedCommit。
|
|
4
|
+
// 用法:record-push.mjs --project <项目目录绝对路径> --commit <object.sha> --tree-hash <treeHash> [--repo-url <https://github.com/ottin4ttc/...>]
|
|
7
5
|
// 输出:写回后的元数据 JSON
|
|
8
6
|
import { readFile, writeFile } from 'node:fs/promises'
|
|
9
7
|
import { join } from 'node:path'
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// integration → <APP_NAME>_<SLUG>_int_ prod → <APP_NAME>_<SLUG>_
|
|
6
|
-
// ext.conf 没 SLUG 的老项目退成 <APP_NAME>_int_ / <APP_NAME>_。
|
|
2
|
+
// 渲 server/db/schema.sql 的 __TABLE_PREFIX__ 到 stdout,共用一份 DDL。
|
|
3
|
+
// 前缀与 publish/remove 一致:integration 为 <APP_NAME>_<SLUG>_int_,prod 为 <APP_NAME>_<SLUG>_。
|
|
4
|
+
// legacy 无 SLUG 时分别退为 <APP_NAME>_int_ / <APP_NAME>_。
|
|
7
5
|
import { readFile } from 'node:fs/promises'
|
|
8
6
|
import { join } from 'node:path'
|
|
9
7
|
|
|
@@ -27,7 +25,7 @@ function fail(msg) {
|
|
|
27
25
|
process.exit(1)
|
|
28
26
|
}
|
|
29
27
|
|
|
30
|
-
/**
|
|
28
|
+
/** 只解析单行 KEY=value 及单/双引号,不执行 ext.conf 的 shell 内容。 */
|
|
31
29
|
function readConf(text) {
|
|
32
30
|
const out = {}
|
|
33
31
|
for (const line of text.split('\n')) {
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import { Module } from '@nestjs/common';
|
|
2
2
|
|
|
3
|
-
//
|
|
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。
|
|
3
|
+
// 留空启动骨架,由项目按需求注册业务模块;静态资源由 main.ts 托管,无需 ServeStaticModule。
|
|
10
4
|
@Module({
|
|
11
5
|
imports: [],
|
|
12
6
|
})
|
|
@@ -5,11 +5,8 @@ import { AppModule } from './app.module';
|
|
|
5
5
|
import { TABLE_PREFIX } from './table-name';
|
|
6
6
|
import * as path from 'node:path';
|
|
7
7
|
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// /api 走 web/vite.config.js 的 proxy 转到本进程)
|
|
11
|
-
// - prod / FC:nest 直接 serve web 的打包产物(publish.sh 的 BUILD_CMD 把 web/dist 拷进 dist/public)
|
|
12
|
-
// FC_SERVER_PORT 是 FC 运行时注入的端口;PORT 给本地调试。
|
|
8
|
+
// dev 由 Vite 独跑并代理 /api;FC 托管拷入 dist/public 的 web 产物。
|
|
9
|
+
// 端口优先 FC_SERVER_PORT,其次本地 PORT。
|
|
13
10
|
async function bootstrap() {
|
|
14
11
|
const app = await NestFactory.create<NestExpressApplication>(AppModule);
|
|
15
12
|
|
|
@@ -18,10 +15,7 @@ async function bootstrap() {
|
|
|
18
15
|
|
|
19
16
|
app.setGlobalPrefix('api', { exclude: ['/'] });
|
|
20
17
|
|
|
21
|
-
//
|
|
22
|
-
// 1. FC(CODE_DIR=./server 只上传 server/,运行时 __dirname=dist/):publish.sh BUILD_CMD 末尾
|
|
23
|
-
// 把 ../web/dist 拷进 dist/public,跟着 server 一起上传,所以优先用 __dirname/public
|
|
24
|
-
// 2. 本地 `node dist/main.js`:__dirname/public 不存在时 fallback 到 ../../web/dist
|
|
18
|
+
// FC 仅上传 server/,优先托管 dist/public;本地构建可回落到 web/dist。
|
|
25
19
|
const fs = await import('node:fs');
|
|
26
20
|
const fcStaticDir = path.resolve(__dirname, 'public');
|
|
27
21
|
const monorepoStaticDir = path.resolve(__dirname, '..', '..', 'web', 'dist');
|
|
@@ -1,14 +1,8 @@
|
|
|
1
|
-
//
|
|
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 不会覆盖(默认行为),行为对齐。
|
|
1
|
+
// nest watch 不经过 Node --env-file;bootstrap 前加载 .env,dotenv 不覆盖已注入的 env。
|
|
6
2
|
require('dotenv/config');
|
|
7
3
|
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// (FC 运行时给的 node 可能是 18/20)下都能正常 createClient。
|
|
11
|
-
// 注意:不要用 `WebSocket` 作为局部变量名,会与 DOM lib 的全局类型重声明冲突。
|
|
4
|
+
// Supabase Realtime 初始化需要 WebSocket;缺原生实现时(如 Node 18/20)用 ws 补全。
|
|
5
|
+
// 局部构造器避免命名为 WebSocket,以免与 DOM 全局类型冲突。
|
|
12
6
|
const WsCtor = require('ws');
|
|
13
7
|
if (!(globalThis as any).WebSocket) {
|
|
14
8
|
(globalThis as any).WebSocket = WsCtor;
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
// Supabase
|
|
2
|
-
//
|
|
3
|
-
// 线上由 publish 按 env 注进 FC 的环境变量。代码里凡碰表的地方都走 tableName('visits'),不写死前缀。
|
|
4
|
-
// 表结构写在 server/db/schema.sql(同样用 __TABLE_PREFIX__ 占位),见那边注释。
|
|
1
|
+
// Supabase 表名统一为 TABLE_PREFIX + 基础名,按 integration / prod 隔离。
|
|
2
|
+
// prefix 从 dev .env 或 FC env 注入;DDL 使用同源 __TABLE_PREFIX__ 占位。
|
|
5
3
|
|
|
6
4
|
const prefix = process.env.TABLE_PREFIX;
|
|
7
5
|
if (!prefix) {
|
|
@@ -13,7 +11,6 @@ if (!prefix) {
|
|
|
13
11
|
|
|
14
12
|
export const TABLE_PREFIX: string = prefix;
|
|
15
13
|
|
|
16
|
-
/** `tableName('visits')` → `<APP_NAME>_<SLUG>_int_visits`(integration)/ `<APP_NAME>_<SLUG>_visits`(prod) */
|
|
17
14
|
export function tableName(base: string): string {
|
|
18
15
|
return `${TABLE_PREFIX}${base}`;
|
|
19
16
|
}
|
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
//
|
|
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
|
+
// 空白起手页,避免模板 demo 暗示业务方向;内容由项目按需求补充。
|
|
9
2
|
export default function App() {
|
|
10
3
|
return <div />;
|
|
11
4
|
}
|
|
@@ -21229,17 +21229,12 @@ var ProjectStore = class {
|
|
|
21229
21229
|
await this.write(validated.data);
|
|
21230
21230
|
return validated.data;
|
|
21231
21231
|
}
|
|
21232
|
-
/**
|
|
21233
|
-
* 同步内联 scaffold:spawn `new-extension.sh <name> <模板源> <项目目录>` 等 exit。
|
|
21234
|
-
* 必须在 `create()` 之后调(脚本要求目标目录已存在且只含 .clawd-project.json)。
|
|
21235
|
-
* 失败抛错;上层(core.createProject)catch 后回滚。
|
|
21236
|
-
*/
|
|
21232
|
+
/** create 后才 scaffold;目标仅含元数据,失败抛给 core 尝试回滚。 */
|
|
21237
21233
|
async scaffold(name, templateSrcDir, scaffoldScriptPath, extraEnv) {
|
|
21238
21234
|
const destDir = this.projectDir(name);
|
|
21239
21235
|
return await new Promise((resolve, reject) => {
|
|
21240
21236
|
const child = (0, import_node_child_process.spawn)("bash", [scaffoldScriptPath, name, templateSrcDir, destDir], {
|
|
21241
|
-
//
|
|
21242
|
-
// 脚本以「命令找不到」的形态崩在离现场很远的地方。
|
|
21237
|
+
// 恢复宿主 PATH,避免 extraEnv 的同名值破坏脚本命令查找。
|
|
21243
21238
|
env: { ...process.env, ...extraEnv ?? {}, PATH: process.env.PATH ?? "" },
|
|
21244
21239
|
stdio: ["ignore", "pipe", "pipe"]
|
|
21245
21240
|
});
|
|
@@ -25065,13 +25060,11 @@ var L = [
|
|
|
25065
25060
|
"",
|
|
25066
25061
|
"jobs:",
|
|
25067
25062
|
" build:",
|
|
25068
|
-
//
|
|
25069
|
-
// 中文说明归 clawd 的上云页面,这里保持机器好认。
|
|
25063
|
+
// jobs API 只返 name 不返 key,故 name 必须用段 key。
|
|
25070
25064
|
" name: build",
|
|
25071
25065
|
" runs-on: ubuntu-latest",
|
|
25072
|
-
// ACR
|
|
25066
|
+
// ACR 账号内串行构建,需给其他镜像排队留时间。
|
|
25073
25067
|
" timeout-minutes: 40",
|
|
25074
|
-
// 没有 outputs:——smoke / deploy 段自己读契约、自己解析 digest(文件头)。
|
|
25075
25068
|
" steps:",
|
|
25076
25069
|
...CONTRACT_STEPS,
|
|
25077
25070
|
" - name: \u51ED\u636E\u9F50\u4E0D\u9F50\uFF08\u7F3A\u54EA\u51E0\u4E2A\u540D\u5B57\u4E00\u6B21\u5217\u5168\uFF09",
|
|
@@ -25108,7 +25101,7 @@ var L = [
|
|
|
25108
25101
|
" ALIBABA_CLOUD_ACCESS_KEY_ID: ${{ secrets.ALI_ACS_ACCESS_KEY_ID }}",
|
|
25109
25102
|
" ALIBABA_CLOUD_ACCESS_KEY_SECRET: ${{ secrets.ALI_ACS_ACCESS_KEY_SECRET }}",
|
|
25110
25103
|
" run: |",
|
|
25111
|
-
// pipefail
|
|
25104
|
+
// pipefail 让 ls-remote 失败正确传播,避免被 cut 的成功状态掩盖。
|
|
25112
25105
|
" set -eo pipefail",
|
|
25113
25106
|
' REPO="$ACR_NAMESPACE/${{ steps.plan.outputs.persona_id }}"',
|
|
25114
25107
|
' node clawos/cloud-host/pipeline/acr.mjs ensure-repo --repo "$REPO" \\',
|
|
@@ -25135,9 +25128,7 @@ var L = [
|
|
|
25135
25128
|
" - name: \u4E34\u65F6\u767B\u8BB0 \u2192 \u8D77\u4E00\u6B21 Pod \u2192 \u64A4\u9500",
|
|
25136
25129
|
" # \u767B\u8BB0\u6B63\u5F0F\u53EA\u662F\u5F80 gateway \u5199\u4E00\u884C\uFF0Cgateway \u4E0D\u78B0\u955C\u50CF\uFF1B\u96C6\u7FA4\u62C9\u955C\u50CF\u53D1\u751F\u5728\u7B2C\u4E00\u4E2A\u4EFB\u52A1\u5230\u6765\u65F6\u3002",
|
|
25137
25130
|
" # \u6CA1\u6709\u8FD9\u4E00\u6B65\uFF0C\u6D41\u6C34\u7EBF\u7EFF\u4E86\u53EA\u4EE3\u8868\u300C\u767B\u8BB0\u4E86\u300D\u3002\u955C\u50CF\u7528 digest\uFF08\u96C6\u7FA4 Job \u662F IfNotPresent\uFF0C\u79FB\u52A8 tag \u4F1A\u4E00\u76F4\u8DD1\u65E7\u7684\uFF09\u3002",
|
|
25138
|
-
//
|
|
25139
|
-
// 名字形状 deploy-file.mjs 已卡死(persona_id [a-z0-9-]、model / effort [A-Za-z0-9._/-](`/` 是 pi 的 provider/id,不是元字符)、size 四档枚举、task_concurrency 正整数、connectors 无单引号
|
|
25140
|
-
// 无换行),所以单引号包住直接拼进命令行是安全的。
|
|
25131
|
+
// smoke 与正式发布用同份模型/档位/连接器;deploy-file.mjs 拒绝单引号/换行等危险值,方可单引号拼参。
|
|
25141
25132
|
" env:",
|
|
25142
25133
|
" CLAWD_GATEWAY_URL: ${{ vars.CLAWD_GATEWAY_URL }}",
|
|
25143
25134
|
" CLAWD_GATEWAY_ADMIN_KEY: ${{ secrets.CLAWD_GATEWAY_ADMIN_KEY }}",
|
|
@@ -41,7 +41,6 @@ function die(msg) {
|
|
|
41
41
|
process.exit(1)
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
// ---------------------------------------------------------------- 小工具
|
|
45
44
|
|
|
46
45
|
function clip(text, n) {
|
|
47
46
|
const s = String(text ?? '').split(/\s+/).filter(Boolean).join(' ')
|
|
@@ -121,7 +120,6 @@ function* jsonLines(file) {
|
|
|
121
120
|
}
|
|
122
121
|
}
|
|
123
122
|
|
|
124
|
-
// ---------------------------------------------------------------- RPC
|
|
125
123
|
|
|
126
124
|
function rpcConfig() {
|
|
127
125
|
try {
|
|
@@ -164,9 +162,7 @@ async function rpc(method, args = {}) {
|
|
|
164
162
|
return body
|
|
165
163
|
}
|
|
166
164
|
|
|
167
|
-
//
|
|
168
|
-
|
|
169
|
-
// 真实用户输入的文本;tool_result / 图片等块返回空串
|
|
165
|
+
// 真实用户文本;tool_result / 图片等块返回空串。
|
|
170
166
|
function userText(msg) {
|
|
171
167
|
const content = msg && typeof msg === 'object' ? msg.content : null
|
|
172
168
|
if (typeof content === 'string') return content
|
|
@@ -176,7 +172,6 @@ function userText(msg) {
|
|
|
176
172
|
return ''
|
|
177
173
|
}
|
|
178
174
|
|
|
179
|
-
// ~/.claude/projects/<编码后的 cwd>/<toolSessionId>.jsonl —— 文件名即 CC session id
|
|
180
175
|
function scanClaude(cutoffMs) {
|
|
181
176
|
const out = []
|
|
182
177
|
let dirs
|
|
@@ -210,7 +205,7 @@ function scanClaude(cutoffMs) {
|
|
|
210
205
|
let turns = 0
|
|
211
206
|
let firstTs = 0
|
|
212
207
|
let lastTs = 0
|
|
213
|
-
let hasMainLine = false
|
|
208
|
+
let hasMainLine = false
|
|
214
209
|
|
|
215
210
|
for (const rec of jsonLines(file)) {
|
|
216
211
|
if (!cwd && typeof rec.cwd === 'string') cwd = rec.cwd
|
|
@@ -222,7 +217,7 @@ function scanClaude(cutoffMs) {
|
|
|
222
217
|
}
|
|
223
218
|
}
|
|
224
219
|
if (rec.type === 'ai-title' && rec.aiTitle) {
|
|
225
|
-
title = rec.aiTitle
|
|
220
|
+
title = rec.aiTitle
|
|
226
221
|
continue
|
|
227
222
|
}
|
|
228
223
|
if (rec.type !== 'user' && rec.type !== 'assistant') continue
|
|
@@ -238,7 +233,6 @@ function scanClaude(cutoffMs) {
|
|
|
238
233
|
tool: 'claude',
|
|
239
234
|
toolSessionId: name.slice(0, -'.jsonl'.length),
|
|
240
235
|
cwd,
|
|
241
|
-
// 优先 CC 生成的 ai-title,没有就拿第一句用户发言硬截
|
|
242
236
|
label: clipW(title || firstUser, 56),
|
|
243
237
|
turns,
|
|
244
238
|
createdMs: firstTs || st.mtimeMs,
|
|
@@ -255,9 +249,7 @@ function scanClaude(cutoffMs) {
|
|
|
255
249
|
return out
|
|
256
250
|
}
|
|
257
251
|
|
|
258
|
-
//
|
|
259
|
-
|
|
260
|
-
// 从 rollout jsonl 数真实用户轮次,顺带取第一条用户发言
|
|
252
|
+
// 从 rollout jsonl 统计用户轮次并取第一条用户文本。
|
|
261
253
|
function countCodexTurns(rolloutPath) {
|
|
262
254
|
if (!rolloutPath || !fs.existsSync(rolloutPath)) return { turns: 0, first: '' }
|
|
263
255
|
let turns = 0
|
|
@@ -334,7 +326,7 @@ function codexFromRows(rows, cutoffMs) {
|
|
|
334
326
|
const updatedMs = toMs(r.updated_at)
|
|
335
327
|
if (updatedMs < cutoffMs) continue
|
|
336
328
|
const { turns, first } = countCodexTurns(r.rollout_path || '')
|
|
337
|
-
//
|
|
329
|
+
// 标签可能是用户原话,按显示宽度截断。
|
|
338
330
|
const label = clipW(r.name || r.title || r.preview || r.first_user_message || first, 56)
|
|
339
331
|
const cand = {
|
|
340
332
|
tool: 'codex',
|
|
@@ -394,7 +386,6 @@ function scanCodexRollouts(cutoffMs) {
|
|
|
394
386
|
return out
|
|
395
387
|
}
|
|
396
388
|
|
|
397
|
-
// ---------------------------------------------------------------- 过滤 / 关联
|
|
398
389
|
|
|
399
390
|
async function existingToolSessionIds() {
|
|
400
391
|
const res = await rpc('session:list', { limit: 100000 })
|
|
@@ -457,7 +448,6 @@ async function link(cand, icon, ownerPersonaId = '') {
|
|
|
457
448
|
return { sessionId, linked: !imported.alreadyLinked }
|
|
458
449
|
}
|
|
459
450
|
|
|
460
|
-
// ---------------------------------------------------------------- 台账 / 撤销
|
|
461
451
|
|
|
462
452
|
function ledgerRead() {
|
|
463
453
|
try {
|
|
@@ -485,9 +475,7 @@ async function doUndo(applyIt) {
|
|
|
485
475
|
const res = await rpc('session:list', { limit: 100000 })
|
|
486
476
|
const alive = new Set((res.sessions || []).map((s) => s.sessionId))
|
|
487
477
|
const todo = entries.filter((e) => alive.has(e.sessionId))
|
|
488
|
-
//
|
|
489
|
-
// 把账清掉。扫盘去重会读这本台账(spec 2026-08-19 §4),条目留着就等于该会话永久
|
|
490
|
-
// 无法重新导入,而 undo 的语义本就是「撤销我的导入记录」。
|
|
478
|
+
// 不存在的 session 也需清台账,否则去重会阻止重新导入;删除失败项保留供重试。
|
|
491
479
|
const stale = entries.filter((e) => !alive.has(e.sessionId))
|
|
492
480
|
console.log(`台账 ${entries.length} 条,其中 ${todo.length} 条还在` +
|
|
493
481
|
(stale.length ? `,${stale.length} 条已不存在(只清账)` : '') + '。' +
|
|
@@ -515,10 +503,7 @@ async function doUndo(applyIt) {
|
|
|
515
503
|
console.log(`已删除 ${removed} 条,清账 ${doneIds.size} 条。原 Claude Code / Codex 的会话文件没有任何改动。`)
|
|
516
504
|
}
|
|
517
505
|
|
|
518
|
-
//
|
|
519
|
-
|
|
520
|
-
// clawd UI 认得的图标 key(源:ui/src/lib/session-icons.ts)。协议侧 iconKey 是自由字符串,
|
|
521
|
-
// daemon 不拒绝拼错的值,UI 查不到就不画图标——零提示,所以在参数层拦。
|
|
506
|
+
// iconKey 协议允许任意字符串,但 UI 未识别就不显示;在参数层校验已知值。
|
|
522
507
|
const ICON_KEYS = ['research', 'code', 'loop', 'qa', 'reading', 'debug', 'idea', 'doc', 'assist']
|
|
523
508
|
|
|
524
509
|
function parseArgs(argv) {
|
|
@@ -573,7 +558,6 @@ const HELP = `把本机 Claude Code / Codex 历史会话关联进 clawd
|
|
|
573
558
|
--undo 撤销本工具关联过的会话(配 --apply 才真删)
|
|
574
559
|
--json 输出 JSON(含被跳过的和原因)`
|
|
575
560
|
|
|
576
|
-
// ---------------------------------------------------------------- main
|
|
577
561
|
|
|
578
562
|
async function main() {
|
|
579
563
|
const args = parseArgs(process.argv.slice(2))
|
|
@@ -26,7 +26,7 @@ const CLAUDE_PROJECTS = path.join(HOME, '.claude', 'projects');
|
|
|
26
26
|
const CODEX_DIR = path.join(HOME, '.codex');
|
|
27
27
|
const PERSONAS_DIR = path.join(HOME, '.clawd', 'personas');
|
|
28
28
|
|
|
29
|
-
//
|
|
29
|
+
// 系统注入文本不计用户需求。
|
|
30
30
|
const DROP_PREFIX = [
|
|
31
31
|
'<system-reminder', '<command-name', '<command-message', '<command-args',
|
|
32
32
|
'<local-command-stdout', '<local-command-stderr', '<user-prompt-submit-hook',
|
|
@@ -42,8 +42,7 @@ const B64 = /data:image\/[a-z]+;base64,[A-Za-z0-9+/=]+/g;
|
|
|
42
42
|
|
|
43
43
|
const clean = (t) => t.replace(B64, '[图片]').replace(/[ \t]+/g, ' ').trim();
|
|
44
44
|
|
|
45
|
-
/**
|
|
46
|
-
* 交给下游聚类稀释——占比极低,不值得为它引入一层判断。 */
|
|
45
|
+
/** 只滤结构可识别的噪音,语义噪音交给下游聚类。 */
|
|
47
46
|
function isRealQuery(t) {
|
|
48
47
|
if (!t || t.length < 6) return false;
|
|
49
48
|
if (DROP_PREFIX.some((p) => t.startsWith(p))) return false;
|
|
@@ -71,7 +70,6 @@ function textOf(content) {
|
|
|
71
70
|
return parts.join(' ');
|
|
72
71
|
}
|
|
73
72
|
|
|
74
|
-
// ---------- 小工具 ----------
|
|
75
73
|
const bump = (m, k, n = 1) => m.set(k, (m.get(k) || 0) + n);
|
|
76
74
|
// 计数相同时按名字排——否则并列项谁进前 N 随遍历顺序漂移,同样输入得不到同样输出
|
|
77
75
|
const topN = (m, n) =>
|
|
@@ -123,7 +121,7 @@ async function eachLine(file, fn) {
|
|
|
123
121
|
}
|
|
124
122
|
}
|
|
125
123
|
|
|
126
|
-
//
|
|
124
|
+
// 已有 persona 覆盖的目录一律跳过,无开关。
|
|
127
125
|
function knownPersonaDirs() {
|
|
128
126
|
const out = new Map();
|
|
129
127
|
let ents;
|
|
@@ -167,7 +165,6 @@ const profOf = (prof, k) => {
|
|
|
167
165
|
return prof.get(k);
|
|
168
166
|
};
|
|
169
167
|
|
|
170
|
-
// ---------- Claude Code ----------
|
|
171
168
|
async function scanClaude(cutMs, sig, prof) {
|
|
172
169
|
let dirs;
|
|
173
170
|
try {
|
|
@@ -200,8 +197,7 @@ async function scanClaude(cutMs, sig, prof) {
|
|
|
200
197
|
const typ = d.type;
|
|
201
198
|
const cwd = d.cwd || '';
|
|
202
199
|
|
|
203
|
-
//
|
|
204
|
-
// 比任何启发式取样都准,是画像的首选信号(约 9 成会话有)
|
|
200
|
+
// CC 生成的 ai-title 优先作为任务摘要信号。
|
|
205
201
|
if (typ === 'ai-title' && d.aiTitle) {
|
|
206
202
|
p.titles.push(d.aiTitle);
|
|
207
203
|
return;
|
|
@@ -234,9 +230,7 @@ async function scanClaude(cutMs, sig, prof) {
|
|
|
234
230
|
}
|
|
235
231
|
}
|
|
236
232
|
|
|
237
|
-
|
|
238
|
-
/** threads 表是 codex 的权威索引,比解析 rollout 准,还带 git/model 信息。
|
|
239
|
-
* node:sqlite 要 node>=22.5 且是实验特性——拿不到就返回空,由 rollout 扫描兜底。 */
|
|
233
|
+
/** 可用时从 Codex threads 表取 cwd / branch;旧 Node 或缺数据库时仍扫描 rollout。 */
|
|
240
234
|
async function codexMetaFromDb() {
|
|
241
235
|
const out = new Map();
|
|
242
236
|
let dbs;
|
|
@@ -331,7 +325,6 @@ async function scanCodex(cutMs, sig, prof) {
|
|
|
331
325
|
}
|
|
332
326
|
}
|
|
333
327
|
|
|
334
|
-
// ---------- 主流程 ----------
|
|
335
328
|
function parseArgs(argv) {
|
|
336
329
|
const a = { days: 7, out: '.', samples: 12, minQueries: 5, minDays: 2 };
|
|
337
330
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -393,8 +386,7 @@ async function main() {
|
|
|
393
386
|
const qs = byProj.get(k);
|
|
394
387
|
if (!qs || !qs.length) continue;
|
|
395
388
|
|
|
396
|
-
//
|
|
397
|
-
// 这里的活已经有人干了。不给开关——「要不要看已覆盖目录」不是每次运行要重想的问题。
|
|
389
|
+
// 已覆盖目录不产生候选,不提供开关,避免把既有 persona 的工作再次推荐。
|
|
398
390
|
const owner = coveredBy(v.cwd, known);
|
|
399
391
|
if (owner) {
|
|
400
392
|
skipped.push([k, owner, qs.length]);
|
|
@@ -1,17 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// + 永远排掉 `.git` 和 `.clawd-project.json`(插件的记账文件:publish / record-push 都会改它,进了仓库或哈希就会
|
|
6
|
-
// 「每推一次都变一次」,而且里面的 deployments / pushedCommit 也不是源码)。**不要**在这里另写一份排除表:publish.sh 渲进项目目录的 s.int.yaml / s.prod.yaml 含 supabase key,
|
|
7
|
-
// 它进不进仓库只该由 .gitignore 一处决定。
|
|
8
|
-
// 二进制(前 8KB 含 NUL)和超过 512KB 的文件跳过并列进 `skipped`——push_files 只收文本,
|
|
9
|
-
// 而且每个字节都要经模型上下文走一遍;agent 看到 skipped 里有该进仓库的东西要自己判断。
|
|
10
|
-
//
|
|
2
|
+
// 收集 GitHub MCP push_files 的文本文件;过滤以项目 .gitignore 为准,避免 s.*.yaml 中的 key 被提交。
|
|
3
|
+
// 始终排除 .git 及根 .clawd-project.json,记账变化不属于源码,不能进入推送或树哈希。
|
|
4
|
+
// 二进制(前 8KB 含 NUL)及超过 512KB 的文件列入 skipped,不进哈希;调用方需判断是否另行处理。
|
|
11
5
|
// 用法:collect-files.mjs --project <项目目录绝对路径>
|
|
12
6
|
// 输出:{ files: [{ path, content }], skipped: [{ path, reason }], treeHash }
|
|
13
|
-
//
|
|
14
|
-
// publish tool 再跑一次本脚本比对——不一致就是「改了没推」,拒绝发布。skipped 里的文件不进哈希。
|
|
7
|
+
// treeHash 为按 path 排序的 sha256(path\0content\0…);publish 重算后与 record-push 记账比较,拒绝未推的改动。
|
|
15
8
|
import { createHash } from 'node:crypto'
|
|
16
9
|
import { readdir, readFile, stat } from 'node:fs/promises'
|
|
17
10
|
import { join, relative, sep } from 'node:path'
|
|
@@ -58,7 +51,6 @@ function ignored(rel, isDir, rules) {
|
|
|
58
51
|
const segments = rel.split('/')
|
|
59
52
|
for (const rule of rules) {
|
|
60
53
|
if (rule.anchored) {
|
|
61
|
-
// 前缀匹配:rel 等于它、或在它下面
|
|
62
54
|
for (let i = 1; i <= segments.length; i++) {
|
|
63
55
|
const head = segments.slice(0, i).join('/')
|
|
64
56
|
if (rule.re.test(head) && (!rule.dirOnly || isDir || i < segments.length)) return true
|
|
@@ -67,7 +59,6 @@ function ignored(rel, isDir, rules) {
|
|
|
67
59
|
}
|
|
68
60
|
for (let i = 0; i < segments.length; i++) {
|
|
69
61
|
if (!rule.re.test(segments[i])) continue
|
|
70
|
-
// dirOnly:命中的这一段得是目录——要么它不是最后一段(后面还有东西),要么 rel 本身就是目录
|
|
71
62
|
if (!rule.dirOnly || i < segments.length - 1 || isDir) return true
|
|
72
63
|
}
|
|
73
64
|
}
|
|
@@ -78,7 +69,7 @@ let rules = []
|
|
|
78
69
|
try {
|
|
79
70
|
rules = parseGitignore(await readFile(join(root, '.gitignore'), 'utf8'))
|
|
80
71
|
} catch {
|
|
81
|
-
//
|
|
72
|
+
// 无法读取 .gitignore 时仍排除 .git 和根 .clawd-project.json。
|
|
82
73
|
}
|
|
83
74
|
|
|
84
75
|
const files = []
|
|
@@ -109,7 +100,7 @@ async function walk(dir) {
|
|
|
109
100
|
}
|
|
110
101
|
|
|
111
102
|
await walk(root)
|
|
112
|
-
//
|
|
103
|
+
// 按字符串序而非 localeCompare 排序,避免不同机器 locale 改变树哈希。
|
|
113
104
|
files.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0))
|
|
114
105
|
const hash = createHash('sha256')
|
|
115
106
|
for (const f of files) hash.update(f.path).update('\0').update(f.content).update('\0')
|
|
@@ -24,8 +24,7 @@ for (const dirName of names) {
|
|
|
24
24
|
try {
|
|
25
25
|
const value = JSON.parse(await readFile(join(projectsRoot, dirName, '.clawd-project.json'), 'utf8'))
|
|
26
26
|
if (value?.name !== dirName || typeof value.createdAt !== 'string') continue
|
|
27
|
-
//
|
|
28
|
-
// 插件还没读写过的老文件顶层就有 prodUrl,原样带出。
|
|
27
|
+
// prodUrl 兼容字段优先取 deployments.prod.url,缺席时保留 legacy 顶层值。
|
|
29
28
|
const prodUrl = value.deployments?.prod?.url
|
|
30
29
|
projects.push({ ...value, ...(prodUrl ? { prodUrl } : {}), projectDir: join(projectsRoot, dirName) })
|
|
31
30
|
} catch {
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// 用法:record-push.mjs --project <项目目录绝对路径> --commit <push_files 返回的 object.sha> --tree-hash <collect-files.mjs 输出的 treeHash> [--repo-url <https://github.com/ottin4ttc/...>]
|
|
2
|
+
// GitHub MCP push 后记 repoUrl、pushedCommit 和 pushedTreeHash 到 .clawd-project.json。
|
|
3
|
+
// 无本地 git 的项目发布时重算树哈希;一致才以 pushedCommit 记 publishedCommit。
|
|
4
|
+
// 用法:record-push.mjs --project <项目目录绝对路径> --commit <object.sha> --tree-hash <treeHash> [--repo-url <https://github.com/ottin4ttc/...>]
|
|
7
5
|
// 输出:写回后的元数据 JSON
|
|
8
6
|
import { readFile, writeFile } from 'node:fs/promises'
|
|
9
7
|
import { join } from 'node:path'
|