@clawos-dev/clawd 0.2.515 → 0.2.517
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 +228 -284
- package/dist/cli.cjs +10871 -7110
- package/dist/dispatch/mcp-server.cjs +28 -4
- package/dist/persona-defaults/persona-app-builder/.claude/skills/app-builder-projects/SKILL.md +77 -0
- package/dist/persona-defaults/persona-app-builder/.claude/skills/app-builder-projects/scripts/collect-files.mjs +116 -0
- package/dist/persona-defaults/persona-app-builder/.claude/skills/app-builder-projects/scripts/list-projects.mjs +33 -0
- package/dist/persona-defaults/persona-app-builder/.claude/skills/app-builder-projects/scripts/record-push.mjs +49 -0
- package/dist/persona-defaults/persona-app-builder/.mcp.json +8 -1
- package/dist/persona-defaults/persona-app-builder/CLAUDE.md +27 -24
- package/dist/persona-defaults/persona-dataclaw-builder/.claude/skills/app-builder-projects/SKILL.md +77 -0
- package/dist/persona-defaults/persona-dataclaw-builder/.claude/skills/app-builder-projects/scripts/collect-files.mjs +116 -0
- package/dist/persona-defaults/persona-dataclaw-builder/.claude/skills/app-builder-projects/scripts/list-projects.mjs +33 -0
- package/dist/persona-defaults/persona-dataclaw-builder/.claude/skills/app-builder-projects/scripts/record-push.mjs +49 -0
- package/dist/persona-defaults/persona-dataclaw-builder/.mcp.json +10 -2
- package/dist/persona-defaults/persona-dataclaw-builder/CLAUDE.md +15 -13
- package/dist/share-ui/assets/{guest-CGrg1A9a.js → guest-fV-R3Vmz.js} +125 -125
- package/dist/share-ui/guest.html +1 -1
- package/package.json +3 -1
|
@@ -6891,7 +6891,6 @@ var require_dist = __commonJS({
|
|
|
6891
6891
|
var mcp_server_exports = {};
|
|
6892
6892
|
__export(mcp_server_exports, {
|
|
6893
6893
|
handleCreateProject: () => handleCreateProject,
|
|
6894
|
-
handleListProjects: () => handleListProjects,
|
|
6895
6894
|
handlePublish: () => handlePublish,
|
|
6896
6895
|
handleRemoveProject: () => handleRemoveProject,
|
|
6897
6896
|
readEnv: () => readEnv,
|
|
@@ -21112,6 +21111,162 @@ var StdioServerTransport = class {
|
|
|
21112
21111
|
}
|
|
21113
21112
|
};
|
|
21114
21113
|
|
|
21114
|
+
// src/app-builder-plugin/project-store.ts
|
|
21115
|
+
var import_node_fs = require("fs");
|
|
21116
|
+
var import_node_child_process = require("child_process");
|
|
21117
|
+
var import_node_path = require("path");
|
|
21118
|
+
var PROJECTS_DIR = "projects";
|
|
21119
|
+
var META_FILE = ".clawd-project.json";
|
|
21120
|
+
var SCAFFOLD_TIMEOUT_MS = 3e4;
|
|
21121
|
+
async function exists(p) {
|
|
21122
|
+
try {
|
|
21123
|
+
await import_node_fs.promises.stat(p);
|
|
21124
|
+
return true;
|
|
21125
|
+
} catch {
|
|
21126
|
+
return false;
|
|
21127
|
+
}
|
|
21128
|
+
}
|
|
21129
|
+
var PROJECT_NAME_RE = /^[a-z][a-z0-9-]{0,39}$/;
|
|
21130
|
+
var AppProjectMetadataSchema = external_exports.object({
|
|
21131
|
+
name: external_exports.string().regex(PROJECT_NAME_RE, {
|
|
21132
|
+
message: "kebab-case, \u5C0F\u5199\u5B57\u6BCD\u5F00\u5934\uFF0C\u53EA\u5141\u8BB8\u5C0F\u5199\u5B57\u6BCD / \u6570\u5B57 / -\uFF0C\u6700\u957F 40 \u5B57\u7B26"
|
|
21133
|
+
}),
|
|
21134
|
+
createdAt: external_exports.string().datetime(),
|
|
21135
|
+
/** GitHub 仓库地址(ottin4ttc/<name>-<slug>);skill 经 GitHub MCP 建仓后用 record-push.mjs 记。 */
|
|
21136
|
+
repoUrl: external_exports.string().url().optional(),
|
|
21137
|
+
/** 最近一次经 GitHub MCP push 出去的远端 commit sha(record-push.mjs 记);本地 git 仓库的老项目没有。 */
|
|
21138
|
+
pushedCommit: external_exports.string().min(1).optional(),
|
|
21139
|
+
/** push 那一刻 collect-files.mjs 算的文件树哈希(record-push.mjs 记);publish 拿它判「有没有没推的改动」。 */
|
|
21140
|
+
pushedTreeHash: external_exports.string().min(1).optional(),
|
|
21141
|
+
/** 最近一次发布成功的公网地址。 */
|
|
21142
|
+
prodUrl: external_exports.string().url().optional(),
|
|
21143
|
+
publishedAt: external_exports.string().datetime().optional(),
|
|
21144
|
+
/** 发布那一刻对应的 commit:本地 git 仓库取 HEAD,否则取 pushedCommit;两样都没有时缺席。 */
|
|
21145
|
+
publishedCommit: external_exports.string().min(1).optional()
|
|
21146
|
+
});
|
|
21147
|
+
var ProjectStore = class {
|
|
21148
|
+
/** @param root projects 父目录,例如 `~/.clawd/users/<userId>/` */
|
|
21149
|
+
constructor(root) {
|
|
21150
|
+
this.root = root;
|
|
21151
|
+
}
|
|
21152
|
+
root;
|
|
21153
|
+
metaPath(name) {
|
|
21154
|
+
return (0, import_node_path.join)(this.projectsRoot(), name, META_FILE);
|
|
21155
|
+
}
|
|
21156
|
+
projectDir(name) {
|
|
21157
|
+
return (0, import_node_path.join)(this.projectsRoot(), name);
|
|
21158
|
+
}
|
|
21159
|
+
projectsRoot() {
|
|
21160
|
+
return (0, import_node_path.join)(this.root, PROJECTS_DIR);
|
|
21161
|
+
}
|
|
21162
|
+
async list() {
|
|
21163
|
+
let entries;
|
|
21164
|
+
try {
|
|
21165
|
+
entries = await import_node_fs.promises.readdir(this.projectsRoot());
|
|
21166
|
+
} catch (err) {
|
|
21167
|
+
if (err.code === "ENOENT") return [];
|
|
21168
|
+
throw err;
|
|
21169
|
+
}
|
|
21170
|
+
const out = [];
|
|
21171
|
+
for (const name of entries) {
|
|
21172
|
+
const meta = await this.readMeta(name);
|
|
21173
|
+
if (meta) out.push(meta);
|
|
21174
|
+
}
|
|
21175
|
+
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
21176
|
+
}
|
|
21177
|
+
async get(name) {
|
|
21178
|
+
return this.readMeta(name);
|
|
21179
|
+
}
|
|
21180
|
+
async readMeta(name) {
|
|
21181
|
+
try {
|
|
21182
|
+
const raw = await import_node_fs.promises.readFile(this.metaPath(name), "utf8");
|
|
21183
|
+
const parsed = AppProjectMetadataSchema.safeParse(JSON.parse(raw));
|
|
21184
|
+
return parsed.success ? parsed.data : void 0;
|
|
21185
|
+
} catch {
|
|
21186
|
+
return void 0;
|
|
21187
|
+
}
|
|
21188
|
+
}
|
|
21189
|
+
async create(name) {
|
|
21190
|
+
const meta = { name, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
21191
|
+
const validated = AppProjectMetadataSchema.safeParse(meta);
|
|
21192
|
+
if (!validated.success) {
|
|
21193
|
+
throw new Error(`invalid name "${name}": ${validated.error.message}`);
|
|
21194
|
+
}
|
|
21195
|
+
if (await exists(this.projectDir(name))) {
|
|
21196
|
+
throw new Error(`project "${name}" already exists / \u5DF2\u5B58\u5728\uFF08\u76EE\u5F55 ${this.projectDir(name)} \u5DF2\u5728\uFF09`);
|
|
21197
|
+
}
|
|
21198
|
+
await import_node_fs.promises.mkdir(this.projectDir(name), { recursive: true });
|
|
21199
|
+
await this.write(validated.data);
|
|
21200
|
+
return validated.data;
|
|
21201
|
+
}
|
|
21202
|
+
/**
|
|
21203
|
+
* 同步内联 scaffold:spawn `new-extension.sh <name> <模板源> <项目目录>` 等 exit。
|
|
21204
|
+
* 必须在 `create()` 之后调(脚本要求目标目录已存在且只含 .clawd-project.json)。
|
|
21205
|
+
* 失败抛错;上层(core.createProject)catch 后回滚。
|
|
21206
|
+
*/
|
|
21207
|
+
async scaffold(name, templateSrcDir, scaffoldScriptPath, extraEnv) {
|
|
21208
|
+
const destDir = this.projectDir(name);
|
|
21209
|
+
return await new Promise((resolve, reject) => {
|
|
21210
|
+
const child = (0, import_node_child_process.spawn)("bash", [scaffoldScriptPath, name, templateSrcDir, destDir], {
|
|
21211
|
+
// extraEnv 排在 PATH **之前**:凭据表里若混进一条名叫 PATH 的条目,顶掉 PATH 会让
|
|
21212
|
+
// 脚本以「命令找不到」的形态崩在离现场很远的地方。
|
|
21213
|
+
env: { ...process.env, ...extraEnv ?? {}, PATH: process.env.PATH ?? "" },
|
|
21214
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
21215
|
+
});
|
|
21216
|
+
let stdout = "";
|
|
21217
|
+
let stderr = "";
|
|
21218
|
+
child.stdout.on("data", (d) => {
|
|
21219
|
+
stdout += d.toString("utf8");
|
|
21220
|
+
});
|
|
21221
|
+
child.stderr.on("data", (d) => {
|
|
21222
|
+
stderr += d.toString("utf8");
|
|
21223
|
+
});
|
|
21224
|
+
const timer = setTimeout(() => {
|
|
21225
|
+
child.kill("SIGKILL");
|
|
21226
|
+
reject(new Error(`scaffold timeout (>${SCAFFOLD_TIMEOUT_MS}ms): ${scaffoldScriptPath} ${name}`));
|
|
21227
|
+
}, SCAFFOLD_TIMEOUT_MS);
|
|
21228
|
+
child.on("error", (err) => {
|
|
21229
|
+
clearTimeout(timer);
|
|
21230
|
+
reject(new Error(`scaffold spawn failed: ${err.message}`));
|
|
21231
|
+
});
|
|
21232
|
+
child.on("exit", (code, signal) => {
|
|
21233
|
+
clearTimeout(timer);
|
|
21234
|
+
if (code === 0) resolve({ stdout, stderr });
|
|
21235
|
+
else reject(new Error(`scaffold exit ${code ?? signal}: ${stderr.slice(0, 500)}`));
|
|
21236
|
+
});
|
|
21237
|
+
});
|
|
21238
|
+
}
|
|
21239
|
+
async delete(name) {
|
|
21240
|
+
await import_node_fs.promises.rm(this.projectDir(name), { recursive: true, force: true });
|
|
21241
|
+
}
|
|
21242
|
+
async setPublished(name, fields) {
|
|
21243
|
+
return this.patch(name, fields);
|
|
21244
|
+
}
|
|
21245
|
+
/** 读全量 → 改几个字段 → 校验 → 整份写回。其他字段保持原样。 */
|
|
21246
|
+
async patch(name, fields) {
|
|
21247
|
+
const current = await this.readMeta(name);
|
|
21248
|
+
if (!current) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
|
|
21249
|
+
const validated = AppProjectMetadataSchema.safeParse({ ...current, ...fields });
|
|
21250
|
+
if (!validated.success) {
|
|
21251
|
+
throw new Error(`invalid metadata for "${name}": ${validated.error.message}`);
|
|
21252
|
+
}
|
|
21253
|
+
await this.write(validated.data);
|
|
21254
|
+
return validated.data;
|
|
21255
|
+
}
|
|
21256
|
+
async write(meta) {
|
|
21257
|
+
await import_node_fs.promises.writeFile(this.metaPath(meta.name), JSON.stringify(meta, null, 2) + "\n", "utf8");
|
|
21258
|
+
}
|
|
21259
|
+
};
|
|
21260
|
+
|
|
21261
|
+
// src/app-builder-plugin/core.ts
|
|
21262
|
+
var import_node_child_process3 = require("child_process");
|
|
21263
|
+
var import_node_fs3 = require("fs");
|
|
21264
|
+
var import_node_path5 = require("path");
|
|
21265
|
+
|
|
21266
|
+
// src/deploy/repo.ts
|
|
21267
|
+
var import_promises2 = __toESM(require("fs/promises"), 1);
|
|
21268
|
+
var import_node_path4 = __toESM(require("path"), 1);
|
|
21269
|
+
|
|
21115
21270
|
// ../protocol/src/attachment-schemas.ts
|
|
21116
21271
|
var TOKEN_ROLES = ["owner"];
|
|
21117
21272
|
var GROUP_FILE_SOURCES = ["agent", "owner", "user-upload"];
|
|
@@ -21427,6 +21582,12 @@ var DeployRepoSchema = external_exports.object({
|
|
|
21427
21582
|
/** 本地领先远端几个提交 */
|
|
21428
21583
|
ahead: external_exports.number()
|
|
21429
21584
|
}).strict();
|
|
21585
|
+
var DeployGithubStatusSchema = external_exports.discriminatedUnion("status", [
|
|
21586
|
+
external_exports.object({ status: external_exports.literal("authorized"), login: external_exports.string().min(1), source: external_exports.enum(["git", "file"]) }).strict(),
|
|
21587
|
+
external_exports.object({ status: external_exports.literal("missing") }).strict(),
|
|
21588
|
+
external_exports.object({ status: external_exports.literal("invalid"), message: external_exports.string().min(1) }).strict(),
|
|
21589
|
+
external_exports.object({ status: external_exports.literal("pending"), authUrl: external_exports.string().url() }).strict()
|
|
21590
|
+
]);
|
|
21430
21591
|
var DeploySecretPresenceSchema = external_exports.object({
|
|
21431
21592
|
env: external_exports.string().min(1),
|
|
21432
21593
|
setAt: external_exports.number().optional()
|
|
@@ -21456,13 +21617,15 @@ var DeployRunSchema = external_exports.object({
|
|
|
21456
21617
|
}).strict();
|
|
21457
21618
|
var DeployStateSchema = external_exports.object({
|
|
21458
21619
|
personaId: external_exports.string().min(1),
|
|
21620
|
+
/** 本机 GitHub 身份。daemon 每次现算;老 daemon 不给(可选只为兼容),UI 缺席时按未知处理 */
|
|
21621
|
+
github: DeployGithubStatusSchema.optional(),
|
|
21459
21622
|
repo: DeployRepoSchema.optional(),
|
|
21460
21623
|
file: DeployFileSchema.optional(),
|
|
21461
21624
|
scan: DeployScanSchema.optional(),
|
|
21462
21625
|
/** 没仓库时缺席(查不了) */
|
|
21463
21626
|
secrets: external_exports.array(DeploySecretPresenceSchema).optional(),
|
|
21464
21627
|
/**
|
|
21465
|
-
*
|
|
21628
|
+
* 最新那次流水线走到哪了。**缺席是常态**:没推过、没授权 GitHub、GitHub 查不到、仓库还没有 run,
|
|
21466
21629
|
* 都不该让整页打不开——同 `secrets` 的处理。
|
|
21467
21630
|
*/
|
|
21468
21631
|
run: DeployRunSchema.optional(),
|
|
@@ -21500,6 +21663,14 @@ var DeployPutSecretArgsSchema = external_exports.object({
|
|
|
21500
21663
|
// 环境变量名 → 值。key 必须是该 secret 声明过的 env 之一,handler 校验。
|
|
21501
21664
|
values: external_exports.record(external_exports.string(), external_exports.string())
|
|
21502
21665
|
}).strict();
|
|
21666
|
+
var DeployGithubAuthorizeArgsSchema = external_exports.object({
|
|
21667
|
+
personaId: external_exports.string().min(1),
|
|
21668
|
+
token: external_exports.string().min(1)
|
|
21669
|
+
}).strict();
|
|
21670
|
+
var DeployGithubOauthStartArgsSchema = external_exports.object({
|
|
21671
|
+
personaId: external_exports.string().min(1),
|
|
21672
|
+
returnTo: external_exports.string().url().optional()
|
|
21673
|
+
}).strict();
|
|
21503
21674
|
var DeployRerunJobArgsSchema = external_exports.object({
|
|
21504
21675
|
personaId: external_exports.string().min(1),
|
|
21505
21676
|
/** GitHub 的 job databaseId。**是数字**——字符串会在拼命令时静默出错 */
|
|
@@ -21523,7 +21694,6 @@ var DeployFinishScanArgsSchema = external_exports.object({
|
|
|
21523
21694
|
personaId: external_exports.string().min(1),
|
|
21524
21695
|
notes: external_exports.array(external_exports.string()).default([])
|
|
21525
21696
|
}).strict();
|
|
21526
|
-
var PERSONA_REPO_ORG = "ottin4ttc";
|
|
21527
21697
|
|
|
21528
21698
|
// ../protocol/src/contact-remote-access.ts
|
|
21529
21699
|
var ContactSetRemoteAccessArgsSchema = external_exports.object({
|
|
@@ -23868,7 +24038,7 @@ var METHOD_DOCS = {
|
|
|
23868
24038
|
},
|
|
23869
24039
|
// ---- deploy:*(persona 上云:persona 目录 = git 仓库工作树,发布 = 推送触发仓库流水线)----
|
|
23870
24040
|
"deploy:start": {
|
|
23871
|
-
summary: "\u5BF9\u67D0\u4E2A persona \u8D77\u4E00\u6B21\u4E0A\u4E91\u626B\u63CF\u3002**\u6CA1\u4ED3\u5E93\u5148\u5EFA\u4ED3**\
|
|
24041
|
+
summary: "\u5BF9\u67D0\u4E2A persona \u8D77\u4E00\u6B21\u4E0A\u4E91\u626B\u63CF\u3002**\u6CA1\u4ED3\u5E93\u5148\u5EFA\u4ED3**\uFF08GitHub REST \u5EFA\u5728\u7EC4\u7EC7\u4E0B\u3001\u79C1\u6709\uFF0Cgit \u63A8\u9996\u4E2A\u63D0\u4EA4\uFF1Btoken \u6765\u81EA\u672C\u673A git \u7684\u8EAB\u4EFD\uFF0C\u6CA1\u6388\u6743\u5148\u62D2\uFF09\uFF0C\u518D\u8D77 persona-butler \u7684\u626B\u63CF\u4F1A\u8BDD\uFF0C\u7ED3\u679C\u5199\u8FDB persona \u76EE\u5F55\u7684 .clawd/deploy.json \uFF08\u73AF\u5883\u6E05\u5355\uFF0C\u8FDB\u4ED3\u5E93\uFF09\u4E0E .clawd/deploy-scan.json\uFF08\u8BC1\u636E\uFF0C\u4E0D\u8FDB\u4ED3\u5E93\uFF09\u3002\u5DF2\u7ECF\u5728\u626B\u7684\u8BDD\u539F\u6837\u8FD4\u56DE\u3001\u4E0D\u8D77\u7B2C\u4E8C\u4E2A\u626B\u63CF\u4F1A\u8BDD\uFF1Bowner-only",
|
|
23872
24042
|
args: DeployPersonaArgsSchema
|
|
23873
24043
|
},
|
|
23874
24044
|
"deploy:get": {
|
|
@@ -23880,7 +24050,7 @@ var METHOD_DOCS = {
|
|
|
23880
24050
|
args: DeployPatchArgsSchema
|
|
23881
24051
|
},
|
|
23882
24052
|
"deploy:putSecret": {
|
|
23883
|
-
summary: "\u628A\u4E00\u6761\u51ED\u636E\u7684\u503C\u5199\u8FDB\u4ED3\u5E93\u7684 GitHub Secrets\
|
|
24053
|
+
summary: "\u628A\u4E00\u6761\u51ED\u636E\u7684\u503C\u5199\u8FDB\u4ED3\u5E93\u7684 GitHub Secrets\uFF08GitHub REST\uFF0C\u503C\u7528\u4ED3\u5E93\u516C\u94A5\u5C01\u7BB1\u540E\u4E0A\u4F20\uFF09\u3002**\u8FD9\u662F\u503C\u552F\u4E00\u7684\u5165\u53E3**\uFF1A\u5165\u53C2\u4E0D\u8FDB\u65E5\u5FD7\u3001\u54CD\u5E94\u4E0D\u56DE\u663E\u503C\u3001\u672C\u673A\u4E0D\u843D\u76D8\u3002env \u540D\u5FC5\u987B\u662F\u8BE5\u51ED\u636E\u5728\u6E05\u5355\u91CC\u58F0\u660E\u8FC7\u7684\uFF1Bowner-only",
|
|
23884
24054
|
args: DeployPutSecretArgsSchema
|
|
23885
24055
|
},
|
|
23886
24056
|
"deploy:reportFinding": {
|
|
@@ -23896,9 +24066,17 @@ var METHOD_DOCS = {
|
|
|
23896
24066
|
args: DeployPersonaArgsSchema
|
|
23897
24067
|
},
|
|
23898
24068
|
"deploy:rerunJob": {
|
|
23899
|
-
summary: "\u539F\u6837\u91CD\u8DD1\u6700\u65B0\u90A3\u6761\u6D41\u6C34\u7EBF\u91CC\u5931\u8D25\u7684\u67D0\u4E00\u6BB5\
|
|
24069
|
+
summary: "\u539F\u6837\u91CD\u8DD1\u6700\u65B0\u90A3\u6761\u6D41\u6C34\u7EBF\u91CC\u5931\u8D25\u7684\u67D0\u4E00\u6BB5\uFF08GitHub REST rerun job\uFF09\u3002**\u540C\u4E00\u4E2A commit\u3001\u540C\u4E00\u4EFD\u8F93\u5165**\uFF0C\u4E0D\u4EA7\u751F\u65B0\u63D0\u4EA4\u2014\u2014\u6539\u4E86 persona \u5185\u5BB9\u6216\u51ED\u636E\u8981\u8D70 deploy:release\u3002jobId \u5FC5\u987B\u5C5E\u4E8E\u6700\u65B0\u90A3\u6761 run\uFF1Bowner-only",
|
|
23900
24070
|
args: DeployRerunJobArgsSchema
|
|
23901
24071
|
},
|
|
24072
|
+
"deploy:githubAuthorize": {
|
|
24073
|
+
summary: "\u628A\u7528\u6237\u7C98\u7684 GitHub token \u5B58\u6210\u672C\u673A git \u7684\u8EAB\u4EFD\uFF1A\u5148 GET /user \u9A8C\u8BC1\uFF0C\u518D git credential approve \u4EA4\u7ED9 git \u7684 helper \u5B58\uFF08git \u6CA1\u914D helper \u65F6\u9000\u5230\u672C\u673A\u51ED\u636E\u8868\uFF09\u3002\u4E0A\u4E91\u7684\u5EFA\u4ED3 / \u63A8\u9001 / \u5199 Secrets / \u67E5\u6D41\u6C34\u7EBF\u90FD\u7528\u5B83\u3002\u5165\u53C2\u4E0D\u8FDB\u65E5\u5FD7\u3001\u54CD\u5E94\u4E0D\u56DE\u663E token\uFF1Bowner-only",
|
|
24074
|
+
args: DeployGithubAuthorizeArgsSchema
|
|
24075
|
+
},
|
|
24076
|
+
"deploy:githubOauthStart": {
|
|
24077
|
+
summary: "\u4E0A\u4E91\u9875\u300C\u7528 GitHub \u6388\u6743\u300D\uFF1Adaemon \u5E26\u672C\u673A\u767B\u5F55\u51ED\u8BC1\u5411\u4E2D\u5FC3\u8981\u4E00\u6761 GitHub OAuth \u6388\u6743\u94FE\u63A5\u5E76\u56DE\u5728 DeployState.github\uFF08pending\uFF09\u91CC\uFF0C\u9875\u9762\u6253\u5F00\u5B83\uFF1B\u7528\u6237\u5728 GitHub \u70B9\u540C\u610F\u540E\u56DE\u8DF3\u4E2D\u5FC3\uFF0C\u4E2D\u5FC3\u6362\u597D token \u5B58\u4E00\u4EFD\uFF0Cdaemon \u5728\u4E4B\u540E\u7684 deploy:get \u91CC\u9886\u56DE\u6765\u5B58\u8FDB\u672C\u673A git\u3002\u4E2D\u5FC3\u5DF2\u6709\u672C\u4EBA\u7684 token \u65F6\u4E0D\u8DF3 GitHub\uFF0C\u76F4\u63A5\u9886\uFF1B\u8981\u767B\u5F55\u98DE\u4E66\uFF1Bowner-only",
|
|
24078
|
+
args: DeployGithubOauthStartArgsSchema
|
|
24079
|
+
},
|
|
23902
24080
|
// ---- cloud:connectors:*(云 persona 卡片上的连接器;design 2026-09-11-mcp-connector §3.8)----
|
|
23903
24081
|
"cloud:connectors:list": {
|
|
23904
24082
|
summary: "\u4E91\u4E0A persona \u5361\u7247\u4E0A\u7684\u8FDE\u63A5\u5668\u533A\uFF1A\u8FD9\u4E2A persona \u58F0\u660E\u7684\u3001**\u8981\u4F7F\u7528\u8005\u672C\u4EBA\u6388\u6743\u7684**\u8FDE\u63A5\u5668\u5404\u81EA\u8FDE\u6CA1\u8FDE\u4E0A\uFF08connected / missing / reauth\uFF09\u3001\u6309\u94AE\u662F\u5173\u8054\uFF08OAuth\uFF0C\u5E26\u73B0\u7B97\u7684 authUrl\uFF09\u8FD8\u662F\u7C98\u8D34 API key\uFF08\u5E26\u8981\u586B\u7684\u53D8\u91CF\uFF09\u3002daemon \u62FF gateway \u767B\u8BB0\u884C + \u672C\u673A\u767B\u5F55\u51ED\u8BC1\u5411\u4E2D\u5FC3\u4EE3\u67E5\uFF0C\u51ED\u8BC1\u4E0D\u51FA ws\uFF1Bowner-only",
|
|
@@ -24383,162 +24561,6 @@ var INNER_TAG_NAMES = "clawd-dispatch-task|clawd-dispatch-result|brief|detail|fi
|
|
|
24383
24561
|
var INNER_TAG_ESCAPE_RE = new RegExp(`<(\\\\*)(/?)(${INNER_TAG_NAMES})\\b`, "g");
|
|
24384
24562
|
var INNER_TAG_UNESCAPE_RE = new RegExp(`<\\\\(\\\\*)(/?)(${INNER_TAG_NAMES})\\b`, "g");
|
|
24385
24563
|
|
|
24386
|
-
// src/app-builder-plugin/project-store.ts
|
|
24387
|
-
var import_node_fs = require("fs");
|
|
24388
|
-
var import_node_child_process = require("child_process");
|
|
24389
|
-
var import_node_path = require("path");
|
|
24390
|
-
var PROJECTS_DIR = "projects";
|
|
24391
|
-
var META_FILE = ".clawd-project.json";
|
|
24392
|
-
var SCAFFOLD_TIMEOUT_MS = 3e4;
|
|
24393
|
-
async function exists(p) {
|
|
24394
|
-
try {
|
|
24395
|
-
await import_node_fs.promises.stat(p);
|
|
24396
|
-
return true;
|
|
24397
|
-
} catch {
|
|
24398
|
-
return false;
|
|
24399
|
-
}
|
|
24400
|
-
}
|
|
24401
|
-
var PROJECT_NAME_RE = /^[a-z][a-z0-9-]{0,39}$/;
|
|
24402
|
-
var AppProjectMetadataSchema = external_exports.object({
|
|
24403
|
-
name: external_exports.string().regex(PROJECT_NAME_RE, {
|
|
24404
|
-
message: "kebab-case, \u5C0F\u5199\u5B57\u6BCD\u5F00\u5934\uFF0C\u53EA\u5141\u8BB8\u5C0F\u5199\u5B57\u6BCD / \u6570\u5B57 / -\uFF0C\u6700\u957F 40 \u5B57\u7B26"
|
|
24405
|
-
}),
|
|
24406
|
-
createdAt: external_exports.string().datetime(),
|
|
24407
|
-
/** createProject 建好的 GitHub 仓库地址(ottin4ttc/<name>-<slug>)。 */
|
|
24408
|
-
repoUrl: external_exports.string().url().optional(),
|
|
24409
|
-
/** 最近一次发布成功的公网地址。 */
|
|
24410
|
-
prodUrl: external_exports.string().url().optional(),
|
|
24411
|
-
publishedAt: external_exports.string().datetime().optional(),
|
|
24412
|
-
/** 发布那一刻的 HEAD;项目不是 git 仓库(老项目)时缺席。 */
|
|
24413
|
-
publishedCommit: external_exports.string().min(1).optional()
|
|
24414
|
-
});
|
|
24415
|
-
var ProjectStore = class {
|
|
24416
|
-
/** @param root projects 父目录,例如 `~/.clawd/users/<userId>/` */
|
|
24417
|
-
constructor(root) {
|
|
24418
|
-
this.root = root;
|
|
24419
|
-
}
|
|
24420
|
-
root;
|
|
24421
|
-
metaPath(name) {
|
|
24422
|
-
return (0, import_node_path.join)(this.projectsRoot(), name, META_FILE);
|
|
24423
|
-
}
|
|
24424
|
-
projectDir(name) {
|
|
24425
|
-
return (0, import_node_path.join)(this.projectsRoot(), name);
|
|
24426
|
-
}
|
|
24427
|
-
projectsRoot() {
|
|
24428
|
-
return (0, import_node_path.join)(this.root, PROJECTS_DIR);
|
|
24429
|
-
}
|
|
24430
|
-
async list() {
|
|
24431
|
-
let entries;
|
|
24432
|
-
try {
|
|
24433
|
-
entries = await import_node_fs.promises.readdir(this.projectsRoot());
|
|
24434
|
-
} catch (err) {
|
|
24435
|
-
if (err.code === "ENOENT") return [];
|
|
24436
|
-
throw err;
|
|
24437
|
-
}
|
|
24438
|
-
const out = [];
|
|
24439
|
-
for (const name of entries) {
|
|
24440
|
-
const meta = await this.readMeta(name);
|
|
24441
|
-
if (meta) out.push(meta);
|
|
24442
|
-
}
|
|
24443
|
-
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
24444
|
-
}
|
|
24445
|
-
async get(name) {
|
|
24446
|
-
return this.readMeta(name);
|
|
24447
|
-
}
|
|
24448
|
-
async readMeta(name) {
|
|
24449
|
-
try {
|
|
24450
|
-
const raw = await import_node_fs.promises.readFile(this.metaPath(name), "utf8");
|
|
24451
|
-
const parsed = AppProjectMetadataSchema.safeParse(JSON.parse(raw));
|
|
24452
|
-
return parsed.success ? parsed.data : void 0;
|
|
24453
|
-
} catch {
|
|
24454
|
-
return void 0;
|
|
24455
|
-
}
|
|
24456
|
-
}
|
|
24457
|
-
async create(name) {
|
|
24458
|
-
const meta = { name, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
24459
|
-
const validated = AppProjectMetadataSchema.safeParse(meta);
|
|
24460
|
-
if (!validated.success) {
|
|
24461
|
-
throw new Error(`invalid name "${name}": ${validated.error.message}`);
|
|
24462
|
-
}
|
|
24463
|
-
if (await exists(this.projectDir(name))) {
|
|
24464
|
-
throw new Error(`project "${name}" already exists / \u5DF2\u5B58\u5728\uFF08\u76EE\u5F55 ${this.projectDir(name)} \u5DF2\u5728\uFF09`);
|
|
24465
|
-
}
|
|
24466
|
-
await import_node_fs.promises.mkdir(this.projectDir(name), { recursive: true });
|
|
24467
|
-
await this.write(validated.data);
|
|
24468
|
-
return validated.data;
|
|
24469
|
-
}
|
|
24470
|
-
/**
|
|
24471
|
-
* 同步内联 scaffold:spawn `new-extension.sh <name> <模板源> <项目目录>` 等 exit。
|
|
24472
|
-
* 必须在 `create()` 之后调(脚本要求目标目录已存在且只含 .clawd-project.json)。
|
|
24473
|
-
* 失败抛错;上层(core.createProject)catch 后回滚。
|
|
24474
|
-
*/
|
|
24475
|
-
async scaffold(name, templateSrcDir, scaffoldScriptPath, extraEnv) {
|
|
24476
|
-
const destDir = this.projectDir(name);
|
|
24477
|
-
return await new Promise((resolve, reject) => {
|
|
24478
|
-
const child = (0, import_node_child_process.spawn)("bash", [scaffoldScriptPath, name, templateSrcDir, destDir], {
|
|
24479
|
-
// extraEnv 排在 PATH **之前**:凭据表里若混进一条名叫 PATH 的条目,顶掉 PATH 会让
|
|
24480
|
-
// 脚本以「命令找不到」的形态崩在离现场很远的地方。
|
|
24481
|
-
env: { ...process.env, ...extraEnv ?? {}, PATH: process.env.PATH ?? "" },
|
|
24482
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
24483
|
-
});
|
|
24484
|
-
let stdout = "";
|
|
24485
|
-
let stderr = "";
|
|
24486
|
-
child.stdout.on("data", (d) => {
|
|
24487
|
-
stdout += d.toString("utf8");
|
|
24488
|
-
});
|
|
24489
|
-
child.stderr.on("data", (d) => {
|
|
24490
|
-
stderr += d.toString("utf8");
|
|
24491
|
-
});
|
|
24492
|
-
const timer = setTimeout(() => {
|
|
24493
|
-
child.kill("SIGKILL");
|
|
24494
|
-
reject(new Error(`scaffold timeout (>${SCAFFOLD_TIMEOUT_MS}ms): ${scaffoldScriptPath} ${name}`));
|
|
24495
|
-
}, SCAFFOLD_TIMEOUT_MS);
|
|
24496
|
-
child.on("error", (err) => {
|
|
24497
|
-
clearTimeout(timer);
|
|
24498
|
-
reject(new Error(`scaffold spawn failed: ${err.message}`));
|
|
24499
|
-
});
|
|
24500
|
-
child.on("exit", (code, signal) => {
|
|
24501
|
-
clearTimeout(timer);
|
|
24502
|
-
if (code === 0) resolve({ stdout, stderr });
|
|
24503
|
-
else reject(new Error(`scaffold exit ${code ?? signal}: ${stderr.slice(0, 500)}`));
|
|
24504
|
-
});
|
|
24505
|
-
});
|
|
24506
|
-
}
|
|
24507
|
-
async delete(name) {
|
|
24508
|
-
await import_node_fs.promises.rm(this.projectDir(name), { recursive: true, force: true });
|
|
24509
|
-
}
|
|
24510
|
-
async setRepoUrl(name, repoUrl) {
|
|
24511
|
-
return this.patch(name, { repoUrl });
|
|
24512
|
-
}
|
|
24513
|
-
async setPublished(name, fields) {
|
|
24514
|
-
return this.patch(name, fields);
|
|
24515
|
-
}
|
|
24516
|
-
/** 读全量 → 改几个字段 → 校验 → 整份写回。其他字段保持原样。 */
|
|
24517
|
-
async patch(name, fields) {
|
|
24518
|
-
const current = await this.readMeta(name);
|
|
24519
|
-
if (!current) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
|
|
24520
|
-
const validated = AppProjectMetadataSchema.safeParse({ ...current, ...fields });
|
|
24521
|
-
if (!validated.success) {
|
|
24522
|
-
throw new Error(`invalid metadata for "${name}": ${validated.error.message}`);
|
|
24523
|
-
}
|
|
24524
|
-
await this.write(validated.data);
|
|
24525
|
-
return validated.data;
|
|
24526
|
-
}
|
|
24527
|
-
async write(meta) {
|
|
24528
|
-
await import_node_fs.promises.writeFile(this.metaPath(meta.name), JSON.stringify(meta, null, 2) + "\n", "utf8");
|
|
24529
|
-
}
|
|
24530
|
-
};
|
|
24531
|
-
|
|
24532
|
-
// src/app-builder-plugin/core.ts
|
|
24533
|
-
var import_node_child_process3 = require("child_process");
|
|
24534
|
-
var import_node_fs3 = require("fs");
|
|
24535
|
-
var import_node_path5 = require("path");
|
|
24536
|
-
|
|
24537
|
-
// src/deploy/repo.ts
|
|
24538
|
-
var import_node_child_process2 = require("child_process");
|
|
24539
|
-
var import_promises2 = __toESM(require("fs/promises"), 1);
|
|
24540
|
-
var import_node_path4 = __toESM(require("path"), 1);
|
|
24541
|
-
|
|
24542
24564
|
// src/deploy/deploy-file.ts
|
|
24543
24565
|
var import_promises = __toESM(require("fs/promises"), 1);
|
|
24544
24566
|
var import_node_path3 = __toESM(require("path"), 1);
|
|
@@ -24780,12 +24802,13 @@ var L = [
|
|
|
24780
24802
|
];
|
|
24781
24803
|
var WORKFLOW_TEMPLATE = L.join("\n");
|
|
24782
24804
|
|
|
24783
|
-
// src/deploy/
|
|
24784
|
-
var
|
|
24805
|
+
// src/deploy/exec.ts
|
|
24806
|
+
var import_node_child_process2 = require("child_process");
|
|
24785
24807
|
var DEFAULT_TIMEOUT_MS = 6e4;
|
|
24786
24808
|
var runCommand = (cmd, args, opts = {}) => new Promise((resolve) => {
|
|
24787
24809
|
const child = (0, import_node_child_process2.spawn)(cmd, args, {
|
|
24788
24810
|
cwd: opts.cwd,
|
|
24811
|
+
...opts.env ? { env: { ...process.env, ...opts.env } } : {},
|
|
24789
24812
|
stdio: [opts.stdin === void 0 ? "ignore" : "pipe", "pipe", "pipe"]
|
|
24790
24813
|
});
|
|
24791
24814
|
let stdout = "";
|
|
@@ -24816,28 +24839,12 @@ var runCommand = (cmd, args, opts = {}) => new Promise((resolve) => {
|
|
|
24816
24839
|
child.stdin.end(opts.stdin);
|
|
24817
24840
|
}
|
|
24818
24841
|
});
|
|
24819
|
-
|
|
24820
|
-
|
|
24821
|
-
var
|
|
24842
|
+
|
|
24843
|
+
// src/deploy/repo.ts
|
|
24844
|
+
var DEPLOY_FILE_REL = DEPLOY_FILE.split(import_node_path4.default.sep).join("/");
|
|
24822
24845
|
async function git(exec, dir, args, opts) {
|
|
24823
24846
|
return exec("git", args, { cwd: dir, ...opts });
|
|
24824
24847
|
}
|
|
24825
|
-
async function mustGit(exec, dir, args, what, opts) {
|
|
24826
|
-
const r = await git(exec, dir, args, opts);
|
|
24827
|
-
if (r.spawnError) throw new RepoError(`\u672C\u673A\u8DD1\u4E0D\u4E86 git\uFF1A${r.spawnError}`);
|
|
24828
|
-
if (r.code !== 0) throw new RepoError(`${what}\u6CA1\u6210\uFF08git \u9000\u51FA\u7801 ${r.code}\uFF09\uFF1A${tail(r.stderr) || tail(r.stdout)}`);
|
|
24829
|
-
return r.stdout.trim();
|
|
24830
|
-
}
|
|
24831
|
-
async function ghReady(exec) {
|
|
24832
|
-
const r = await exec("gh", ["auth", "status"], { timeoutMs: 15e3 });
|
|
24833
|
-
if (r.spawnError || r.code !== 0) {
|
|
24834
|
-
return {
|
|
24835
|
-
ok: false,
|
|
24836
|
-
message: "\u672C\u673A\u8FD8\u6CA1\u6709 GitHub \u547D\u4EE4\u884C\u5DE5\u5177\uFF08gh\uFF09\uFF0C\u6216\u8005\u8FD8\u6CA1\u767B\u5F55\u3002\u5EFA\u4ED3\u5E93\u3001\u63A8\u9001\u3001\u5199 Secrets \u90FD\u9760\u5B83\uFF1A\n\n brew install gh\n gh auth login\n\n\u767B\u5F55\u7684\u8D26\u53F7\u8981\u662F\u7EC4\u7EC7\u7684\u6210\u5458\u3002\u5F04\u597D\u540E\u518D\u70B9\u4E00\u6B21\u3002"
|
|
24837
|
-
};
|
|
24838
|
-
}
|
|
24839
|
-
return { ok: true };
|
|
24840
|
-
}
|
|
24841
24848
|
async function isToplevel(exec, dir) {
|
|
24842
24849
|
const r = await git(exec, dir, ["rev-parse", "--show-toplevel"]);
|
|
24843
24850
|
if (r.spawnError || r.code !== 0) return false;
|
|
@@ -24853,56 +24860,6 @@ async function samePath(a, b) {
|
|
|
24853
24860
|
};
|
|
24854
24861
|
return await real(a) === await real(b);
|
|
24855
24862
|
}
|
|
24856
|
-
async function repoInfo(exec, dir) {
|
|
24857
|
-
if (!await isToplevel(exec, dir)) return void 0;
|
|
24858
|
-
const origin = await git(exec, dir, ["remote", "get-url", "origin"]);
|
|
24859
|
-
if (origin.spawnError || origin.code !== 0) return void 0;
|
|
24860
|
-
const url = origin.stdout.trim();
|
|
24861
|
-
const branch = (await git(exec, dir, ["rev-parse", "--abbrev-ref", "HEAD"])).stdout.trim() || "main";
|
|
24862
|
-
const head = (await git(exec, dir, ["rev-parse", "HEAD"])).stdout.trim() || "(\u8FD8\u6CA1\u6709\u63D0\u4EA4)";
|
|
24863
|
-
const status = await git(exec, dir, ["status", "--porcelain", "--", ".", `:(exclude)${DEPLOY_FILE_REL}`]);
|
|
24864
|
-
const dirty = status.spawnError !== void 0 || status.code !== 0 || status.stdout.trim().length > 0;
|
|
24865
|
-
const aheadRaw = await git(exec, dir, ["rev-list", "--count", "@{u}..HEAD"]);
|
|
24866
|
-
const ahead = aheadRaw.code === 0 ? Number(aheadRaw.stdout.trim()) || 0 : 0;
|
|
24867
|
-
return { url, branch, head, dirty, ahead };
|
|
24868
|
-
}
|
|
24869
|
-
async function identityArgs(exec, dir) {
|
|
24870
|
-
const email2 = await git(exec, dir, ["config", "user.email"]);
|
|
24871
|
-
const name = await git(exec, dir, ["config", "user.name"]);
|
|
24872
|
-
const has = (r) => r.code === 0 && r.stdout.trim().length > 0;
|
|
24873
|
-
if (has(email2) && has(name)) return [];
|
|
24874
|
-
return ["-c", "user.name=clawd", "-c", "user.email=clawd@clawos.local"];
|
|
24875
|
-
}
|
|
24876
|
-
var RepoAlreadyExistsError = class extends RepoError {
|
|
24877
|
-
constructor(slug) {
|
|
24878
|
-
super(`\u7EC4\u7EC7\u91CC\u5DF2\u7ECF\u6709 https://github.com/${slug} \u4E86`);
|
|
24879
|
-
this.slug = slug;
|
|
24880
|
-
}
|
|
24881
|
-
slug;
|
|
24882
|
-
};
|
|
24883
|
-
async function createOrgRepo(exec, args) {
|
|
24884
|
-
const { dir, org, repoName, message } = args;
|
|
24885
|
-
if (!await isToplevel(exec, dir)) {
|
|
24886
|
-
await mustGit(exec, dir, ["init", "-b", "main"], "\u521D\u59CB\u5316\u4ED3\u5E93");
|
|
24887
|
-
}
|
|
24888
|
-
const identity = await identityArgs(exec, dir);
|
|
24889
|
-
await mustGit(exec, dir, ["add", "-A"], "\u6682\u5B58\u6587\u4EF6");
|
|
24890
|
-
await mustGit(exec, dir, [...identity, "commit", "--allow-empty", "-m", message], "\u9996\u6B21\u63D0\u4EA4");
|
|
24891
|
-
const slug = `${org}/${repoName}`;
|
|
24892
|
-
const r = await exec(
|
|
24893
|
-
"gh",
|
|
24894
|
-
["repo", "create", slug, "--private", "--source", dir, "--remote", "origin", "--push"],
|
|
24895
|
-
{ cwd: dir, timeoutMs: 12e4 }
|
|
24896
|
-
);
|
|
24897
|
-
if (r.spawnError) throw new RepoError(`\u672C\u673A\u8DD1\u4E0D\u4E86 gh\uFF1A${r.spawnError}`);
|
|
24898
|
-
if (r.code !== 0) {
|
|
24899
|
-
const err = tail(r.stderr) || tail(r.stdout);
|
|
24900
|
-
if (/already exists/i.test(err)) throw new RepoAlreadyExistsError(slug);
|
|
24901
|
-
throw new RepoError(`\u5EFA\u4ED3\u5E93\u6CA1\u6210\uFF08gh repo create \u9000\u51FA\u7801 ${r.code}\uFF09\uFF1A${err}`);
|
|
24902
|
-
}
|
|
24903
|
-
const info = await repoInfo(exec, dir);
|
|
24904
|
-
return { slug, url: info?.url ?? `https://github.com/${slug}` };
|
|
24905
|
-
}
|
|
24906
24863
|
async function workTreeState(exec, dir) {
|
|
24907
24864
|
if (!await isToplevel(exec, dir)) return { isRepo: false };
|
|
24908
24865
|
const status = await git(exec, dir, ["status", "--porcelain"]);
|
|
@@ -24932,31 +24889,22 @@ function tailStderrLines(buf, n) {
|
|
|
24932
24889
|
var errMsg = (err) => err instanceof Error ? err.message : String(err);
|
|
24933
24890
|
async function createProject(args) {
|
|
24934
24891
|
const { store, name, logger: logger2 } = args;
|
|
24935
|
-
const
|
|
24936
|
-
const gh = await ghReady(exec);
|
|
24937
|
-
if (!gh.ok) throw new Error(gh.message);
|
|
24938
|
-
await store.create(name);
|
|
24892
|
+
const project = await store.create(name);
|
|
24939
24893
|
const dir = store.projectDir(name);
|
|
24940
|
-
let repo2;
|
|
24941
24894
|
try {
|
|
24942
24895
|
const scaffold = await store.scaffold(name, args.templateSrcDir, args.scaffoldScript, args.extraEnv);
|
|
24943
24896
|
logger2?.info("app-builder.scaffold.done", { name, stdout: scaffold.stdout.slice(0, 200) });
|
|
24944
24897
|
const slug = await readSlug(dir);
|
|
24945
24898
|
if (!slug) logger2?.warn("app-builder.create.no-slug", { name });
|
|
24946
|
-
|
|
24947
|
-
|
|
24948
|
-
const commit = await headCommit(exec, dir);
|
|
24949
|
-
const project = await store.setRepoUrl(name, repo2.url);
|
|
24950
|
-
logger2?.info("app-builder.create.done", { name, repoUrl: repo2.url, commit });
|
|
24951
|
-
return { project, projectDir: dir, repoUrl: repo2.url, commit };
|
|
24899
|
+
logger2?.info("app-builder.create.done", { name, slug });
|
|
24900
|
+
return { project, projectDir: dir, slug };
|
|
24952
24901
|
} catch (err) {
|
|
24953
24902
|
const message = errMsg(err);
|
|
24954
24903
|
logger2?.warn("app-builder.create.failed", { name, error: message });
|
|
24955
24904
|
await store.delete(name).catch((e) => {
|
|
24956
24905
|
logger2?.warn("app-builder.create.rollback-failed", { name, error: errMsg(e) });
|
|
24957
24906
|
});
|
|
24958
|
-
|
|
24959
|
-
throw new Error(`createProject \u6CA1\u6210\uFF0C\u521A\u5EFA\u7684\u76EE\u5F55\u5DF2\u56DE\u6EDA\uFF1A${message}${orphan}`);
|
|
24907
|
+
throw new Error(`createProject \u6CA1\u6210\uFF0C\u521A\u5EFA\u7684\u76EE\u5F55\u5DF2\u56DE\u6EDA\uFF1A${message}`);
|
|
24960
24908
|
}
|
|
24961
24909
|
}
|
|
24962
24910
|
async function readSlug(projectDir) {
|
|
@@ -24969,23 +24917,41 @@ async function readSlug(projectDir) {
|
|
|
24969
24917
|
const m = /^SLUG=([A-Za-z0-9]+)\s*$/m.exec(conf);
|
|
24970
24918
|
return m ? m[1] : null;
|
|
24971
24919
|
}
|
|
24972
|
-
async function headCommit(exec, dir) {
|
|
24973
|
-
const r = await exec("git", ["rev-parse", "HEAD"], { cwd: dir });
|
|
24974
|
-
return r.code === 0 ? r.stdout.trim() : "";
|
|
24975
|
-
}
|
|
24976
24920
|
var PUBLISH_TIMEOUT_MS = 15 * 6e4;
|
|
24921
|
+
function skillCollectFilesScript(personaRoot) {
|
|
24922
|
+
return (0, import_node_path5.join)(personaRoot, ".claude", "skills", "app-builder-projects", "scripts", "collect-files.mjs");
|
|
24923
|
+
}
|
|
24977
24924
|
async function runPublish(args) {
|
|
24978
24925
|
const { store, name, logger: logger2 } = args;
|
|
24979
24926
|
const exec = args.exec ?? runCommand;
|
|
24980
24927
|
const spawnFn = args.spawnImpl ?? import_node_child_process3.spawn;
|
|
24981
24928
|
const timeoutMs = args.timeoutMs ?? PUBLISH_TIMEOUT_MS;
|
|
24982
|
-
|
|
24929
|
+
const meta = await store.get(name);
|
|
24930
|
+
if (!meta) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
|
|
24983
24931
|
const dir = store.projectDir(name);
|
|
24984
24932
|
const tree = await workTreeState(exec, dir);
|
|
24985
24933
|
if (tree.isRepo && tree.dirty) {
|
|
24986
24934
|
throw new Error("\u5DE5\u4F5C\u533A\u6709\u672A\u63D0\u4EA4\u7684\u6539\u52A8\uFF1A\u5148 commit + push \u518D\u53D1\u5E03\uFF0C\u7EBF\u4E0A\u5FC5\u987B\u5BF9\u5E94\u4ED3\u5E93\u91CC\u7684\u4E00\u4E2A commit");
|
|
24987
24935
|
}
|
|
24988
|
-
|
|
24936
|
+
let head = tree.isRepo ? tree.head : null;
|
|
24937
|
+
if (!tree.isRepo && meta.repoUrl) {
|
|
24938
|
+
if (!meta.pushedCommit || !meta.pushedTreeHash) {
|
|
24939
|
+
throw new Error("\u8FD9\u4E2A\u9879\u76EE\u8FD8\u6CA1\u8BB0\u5F55\u8FC7 push\uFF1A\u5148\u6309 app-builder-projects skill \u628A\u6587\u4EF6\u63A8\u5230\u4ED3\u5E93\u5E76\u8DD1 record-push.mjs\uFF0C\u518D\u53D1\u5E03");
|
|
24940
|
+
}
|
|
24941
|
+
const script = args.collectFilesScript ?? skillCollectFilesScript(args.personaRoot);
|
|
24942
|
+
const r = await exec(process.execPath, [script, "--project", dir], { timeoutMs: 6e4 });
|
|
24943
|
+
if (r.code !== 0) throw new Error(`\u7B97\u4E0D\u51FA\u5F53\u524D\u6587\u4EF6\u6811\u54C8\u5E0C\uFF08${script}\uFF09\uFF1A${r.stderr.trim() || r.spawnError || `exit ${r.code}`}`);
|
|
24944
|
+
let treeHash;
|
|
24945
|
+
try {
|
|
24946
|
+
treeHash = JSON.parse(r.stdout).treeHash;
|
|
24947
|
+
} catch {
|
|
24948
|
+
}
|
|
24949
|
+
if (!treeHash) throw new Error(`collect-files.mjs \u7684\u8F93\u51FA\u91CC\u6CA1\u6709 treeHash\uFF08${script}\uFF09`);
|
|
24950
|
+
if (treeHash !== meta.pushedTreeHash) {
|
|
24951
|
+
throw new Error("\u6709\u6539\u52A8\u8FD8\u6CA1\u63A8\u5230\u4ED3\u5E93\uFF1A\u5148\u6309 skill \u63A8\u4E00\u904D\uFF08collect-files.mjs \u2192 push_files \u2192 record-push.mjs\uFF09\u518D\u53D1\u5E03\uFF0C\u7EBF\u4E0A\u5FC5\u987B\u5BF9\u5E94\u4ED3\u5E93\u91CC\u7684\u4E00\u4E2A commit");
|
|
24952
|
+
}
|
|
24953
|
+
head = meta.pushedCommit;
|
|
24954
|
+
}
|
|
24989
24955
|
const logPath = (0, import_node_path5.join)(dir, ".publish.log");
|
|
24990
24956
|
const log2 = (0, import_node_fs3.createWriteStream)(logPath, { flags: "w" });
|
|
24991
24957
|
log2.on("error", () => {
|
|
@@ -25129,7 +25095,7 @@ var ENV_NAMES = {
|
|
|
25129
25095
|
removeScript: "CLAWD_APP_BUILDER_REMOVE_SCRIPT"
|
|
25130
25096
|
};
|
|
25131
25097
|
function readEnv(env = process.env) {
|
|
25132
|
-
const out = {
|
|
25098
|
+
const out = {};
|
|
25133
25099
|
for (const [key, name] of Object.entries(ENV_NAMES)) {
|
|
25134
25100
|
const v = env[name];
|
|
25135
25101
|
if (v) out[key] = v;
|
|
@@ -25150,33 +25116,21 @@ async function handleCreateProject(input, ctx) {
|
|
|
25150
25116
|
name: input.name,
|
|
25151
25117
|
templateSrcDir: env.templateDir,
|
|
25152
25118
|
scaffoldScript: env.scaffoldScript,
|
|
25153
|
-
org: env.repoOrg,
|
|
25154
|
-
...ctx.exec ? { exec: ctx.exec } : {},
|
|
25155
25119
|
logger
|
|
25156
25120
|
});
|
|
25157
|
-
|
|
25121
|
+
const repoName = r.slug ? `${r.project.name}-${r.slug}` : r.project.name;
|
|
25122
|
+
log("createProject done", { name: input.name, projectDir: r.projectDir, slug: r.slug, repoName });
|
|
25158
25123
|
return ok({
|
|
25159
25124
|
name: r.project.name,
|
|
25160
25125
|
projectDir: r.projectDir,
|
|
25161
|
-
|
|
25162
|
-
|
|
25163
|
-
next:
|
|
25126
|
+
slug: r.slug,
|
|
25127
|
+
repoName,
|
|
25128
|
+
next: `\u6309 app-builder-projects skill \u8D70\uFF1A\u7528 github MCP \u5EFA\u4ED3 ottin4ttc/${repoName} \u5E76\u628A collect-files.mjs \u6536\u7684\u6587\u4EF6 push_files \u4E0A\u53BB\uFF0C\u518D record-push.mjs \u8BB0\u8D26\uFF1B\u7136\u540E cd \u8FDB projectDir \u88C5\u4F9D\u8D56\uFF08server/ \u4E0E web/ \u5404\u4E00\u5957 pnpm install\uFF09`
|
|
25164
25129
|
});
|
|
25165
25130
|
} catch (err) {
|
|
25166
25131
|
return fail(`createProject failed: ${errMsg2(err)}`);
|
|
25167
25132
|
}
|
|
25168
25133
|
}
|
|
25169
|
-
async function handleListProjects(ctx) {
|
|
25170
|
-
const miss = missingEnv(ctx.env, ["root"]);
|
|
25171
|
-
if (miss) return fail(`listProjects: ${miss}`);
|
|
25172
|
-
try {
|
|
25173
|
-
const store = new ProjectStore(ctx.env.root);
|
|
25174
|
-
const projects = (await store.list()).map((p) => ({ ...p, projectDir: store.projectDir(p.name) }));
|
|
25175
|
-
return ok({ projects });
|
|
25176
|
-
} catch (err) {
|
|
25177
|
-
return fail(`listProjects failed: ${errMsg2(err)}`);
|
|
25178
|
-
}
|
|
25179
|
-
}
|
|
25180
25134
|
async function handlePublish(input, ctx) {
|
|
25181
25135
|
const miss = missingEnv(ctx.env, ["root", "publishScript", "personaRoot"]);
|
|
25182
25136
|
if (miss) return fail(`publish: ${miss}`);
|
|
@@ -25233,26 +25187,17 @@ function registerTools(server, ctx) {
|
|
|
25233
25187
|
server.registerTool(
|
|
25234
25188
|
"createProject",
|
|
25235
25189
|
{
|
|
25236
|
-
title: "Create an app project (scaffold
|
|
25237
|
-
description: "Create a new app project in the current user workspace:
|
|
25190
|
+
title: "Create an app project (local scaffold only)",
|
|
25191
|
+
description: "Create a new app project in the current user workspace: mkdir + metadata, then scaffold the starter template (this also renders server/.env from the credentials only this process holds). This is the ONLY way to create a project \u2014 never mkdir one yourself, and never trust a path suggested elsewhere. Returns { name, projectDir, slug, repoName }; projectDir is the absolute path you must cd into, repoName (<name>-<slug>, slug baked into ext.conf) is the GitHub repo to create. Does NOT touch GitHub: create the repo and push through the github MCP as the app-builder-projects skill says. Fails (and rolls back) if the name is taken or the scaffold fails.",
|
|
25238
25192
|
inputSchema: { name: external_exports.string().min(1).describe("project name given by the user (kebab-case)") }
|
|
25239
25193
|
},
|
|
25240
25194
|
async (input) => handleCreateProject(input, ctx)
|
|
25241
25195
|
);
|
|
25242
|
-
server.registerTool(
|
|
25243
|
-
"listProjects",
|
|
25244
|
-
{
|
|
25245
|
-
title: "List app projects",
|
|
25246
|
-
description: "List the projects in the current user workspace: name / projectDir / repoUrl / prodUrl / publishedAt / publishedCommit. Use it to find the directory of an existing app before changing it, or to check for a name clash before createProject. A project without repoUrl predates git support.",
|
|
25247
|
-
inputSchema: {}
|
|
25248
|
-
},
|
|
25249
|
-
async () => handleListProjects(ctx)
|
|
25250
|
-
);
|
|
25251
25196
|
server.registerTool(
|
|
25252
25197
|
"publish",
|
|
25253
25198
|
{
|
|
25254
25199
|
title: "Publish the project to the public internet",
|
|
25255
|
-
description: "Run the publish pipeline (build \u2192 deploy to Aliyun FC \u2192 verify) and wait for it to finish. Returns the public URL (and the commit it corresponds to) on success; on failure returns the failed stage, the error tail and the path of the full log. Refuses to run
|
|
25200
|
+
description: "Run the publish pipeline (build \u2192 deploy to Aliyun FC \u2192 verify) and wait for it to finish. Returns the public URL (and the commit it corresponds to) on success; on failure returns the failed stage, the error tail and the path of the full log. Refuses to run if the project has changes that were not pushed: for a local git repo that means a dirty work tree; for a github-MCP project it recomputes the file tree hash with collect-files.mjs and compares it with what record-push.mjs recorded. Push first, then publish. Never run publish.sh yourself: the credentials only exist in this tool's process.",
|
|
25256
25201
|
inputSchema: { name: external_exports.string().min(1).describe("project name") }
|
|
25257
25202
|
},
|
|
25258
25203
|
async (input) => handlePublish(input, ctx)
|
|
@@ -25270,7 +25215,7 @@ function registerTools(server, ctx) {
|
|
|
25270
25215
|
async function main() {
|
|
25271
25216
|
const env = readEnv();
|
|
25272
25217
|
log("boot", { pid: process.pid, root: env.root ?? "(unset)", personaRoot: env.personaRoot ?? "(unset)" });
|
|
25273
|
-
const server = new McpServer({ name: "clawd-app-builder", version: "0.
|
|
25218
|
+
const server = new McpServer({ name: "clawd-app-builder", version: "0.3.0" });
|
|
25274
25219
|
registerTools(server, { env });
|
|
25275
25220
|
await server.connect(new StdioServerTransport());
|
|
25276
25221
|
}
|
|
@@ -25285,7 +25230,6 @@ if (isMainModule) {
|
|
|
25285
25230
|
// Annotate the CommonJS export names for ESM import in node:
|
|
25286
25231
|
0 && (module.exports = {
|
|
25287
25232
|
handleCreateProject,
|
|
25288
|
-
handleListProjects,
|
|
25289
25233
|
handlePublish,
|
|
25290
25234
|
handleRemoveProject,
|
|
25291
25235
|
readEnv,
|