@clawos-dev/clawd 0.2.514 → 0.2.516
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 +197 -280
- package/dist/cli.cjs +8 -9
- package/dist/dispatch/mcp-server.cjs +2 -2
- 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--nOZRIff.js → guest-CGrg1A9a.js} +1 -1
- package/dist/share-ui/guest.html +1 -1
- package/package.json +1 -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,163 @@ 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_node_child_process2 = require("child_process");
|
|
21268
|
+
var import_promises2 = __toESM(require("fs/promises"), 1);
|
|
21269
|
+
var import_node_path4 = __toESM(require("path"), 1);
|
|
21270
|
+
|
|
21115
21271
|
// ../protocol/src/attachment-schemas.ts
|
|
21116
21272
|
var TOKEN_ROLES = ["owner"];
|
|
21117
21273
|
var GROUP_FILE_SOURCES = ["agent", "owner", "user-upload"];
|
|
@@ -21138,7 +21294,7 @@ var AttachmentSignUrlArgs = external_exports.object({
|
|
|
21138
21294
|
sessionId: external_exports.string().min(1),
|
|
21139
21295
|
/** 相对 session.cwd 的路径;允许传绝对路径,daemon 端会归一化(必须在 cwd 内) */
|
|
21140
21296
|
relPath: external_exports.string().min(1),
|
|
21141
|
-
/** TTL 秒数;缺省
|
|
21297
|
+
/** TTL 秒数;缺省 / null = 永久 URL(不带 e 字段);只有显式传秒数才限时 */
|
|
21142
21298
|
ttlSeconds: external_exports.number().int().positive().nullable().optional(),
|
|
21143
21299
|
/** 视图模式:缺省 'raw' 返 /files/... 裸文件 URL;'md-rendered' 返
|
|
21144
21300
|
* /share/md/... viewer URL(同一签名,daemon 端换前缀),外人浏览器打开就是
|
|
@@ -21523,7 +21679,6 @@ var DeployFinishScanArgsSchema = external_exports.object({
|
|
|
21523
21679
|
personaId: external_exports.string().min(1),
|
|
21524
21680
|
notes: external_exports.array(external_exports.string()).default([])
|
|
21525
21681
|
}).strict();
|
|
21526
|
-
var PERSONA_REPO_ORG = "ottin4ttc";
|
|
21527
21682
|
|
|
21528
21683
|
// ../protocol/src/contact-remote-access.ts
|
|
21529
21684
|
var ContactSetRemoteAccessArgsSchema = external_exports.object({
|
|
@@ -24149,7 +24304,7 @@ var METHOD_DOCS = {
|
|
|
24149
24304
|
summary: "\u62C9\u5F53\u524D pty \u5C4F\u5E55\u5168\u91CF\u5FEB\u7167\uFF08\u5207\u56DE\u4F1A\u8BDD / \u5237\u65B0\u65F6\u7528\uFF09\uFF0C\u8FD4\u56DE { data, atSeq, popupKind }\u3002body: { sessionId }\u3002**\u7F3A sessionId \u9759\u9ED8\u8FD4 ok:false\uFF1B\u975E tui / pty \u5DF2\u9000\u51FA\u8FD4 ok:true \u4F46 data=null, atSeq=0** \u2014\u2014 \u4E24\u8005\u90FD\u4E0D\u62A5\u9519\uFF0C\u8981\u5206\u6E05\u5F97\u770B ok\u3002\u53EA\u6709\u8D8A\u6743\u8BBF\u95EE\u8BE5\u4F1A\u8BDD\u624D\u629B\u9519"
|
|
24150
24305
|
},
|
|
24151
24306
|
"attachment.signUrl": {
|
|
24152
|
-
summary: "\u7ED9\u5DF2\u5165\u7FA4**\u4E14\u975E stale** \u7684\u6587\u4EF6\u7B7E\u53D1 HMAC \u4E0B\u8F7D URL\uFF08\u8DE8\u8BBE\u5907\u53EF\u76F4\u63A5\u6253\u5F00\uFF09\u3002**\u9ED8\u8BA4
|
|
24307
|
+
summary: "\u7ED9\u5DF2\u5165\u7FA4**\u4E14\u975E stale** \u7684\u6587\u4EF6\u7B7E\u53D1 HMAC \u4E0B\u8F7D URL\uFF08\u8DE8\u8BBE\u5907\u53EF\u76F4\u63A5\u6253\u5F00\uFF09\u3002**\u9ED8\u8BA4\u6C38\u4E0D\u8FC7\u671F**\uFF1B\u7528\u6237\u8981\u9650\u65F6\u94FE\u63A5\u624D\u663E\u5F0F\u4F20 ttlSeconds\uFF08\u79D2\uFF09\u3002\u4E0D\u5728\u6E05\u5355\u91CC\u548C stale \u62A5\u540C\u4E00\u4E2A\u9519\uFF0C\u5206\u4E0D\u51FA\u662F\u54EA\u79CD",
|
|
24153
24308
|
args: AttachmentSignUrlArgs
|
|
24154
24309
|
},
|
|
24155
24310
|
"attachment.groupAdd": {
|
|
@@ -24383,162 +24538,6 @@ var INNER_TAG_NAMES = "clawd-dispatch-task|clawd-dispatch-result|brief|detail|fi
|
|
|
24383
24538
|
var INNER_TAG_ESCAPE_RE = new RegExp(`<(\\\\*)(/?)(${INNER_TAG_NAMES})\\b`, "g");
|
|
24384
24539
|
var INNER_TAG_UNESCAPE_RE = new RegExp(`<\\\\(\\\\*)(/?)(${INNER_TAG_NAMES})\\b`, "g");
|
|
24385
24540
|
|
|
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
24541
|
// src/deploy/deploy-file.ts
|
|
24543
24542
|
var import_promises = __toESM(require("fs/promises"), 1);
|
|
24544
24543
|
var import_node_path3 = __toESM(require("path"), 1);
|
|
@@ -24816,28 +24815,9 @@ var runCommand = (cmd, args, opts = {}) => new Promise((resolve) => {
|
|
|
24816
24815
|
child.stdin.end(opts.stdin);
|
|
24817
24816
|
}
|
|
24818
24817
|
});
|
|
24819
|
-
var RepoError = class extends Error {
|
|
24820
|
-
};
|
|
24821
|
-
var tail = (s, n = 400) => s.trim().slice(-n);
|
|
24822
24818
|
async function git(exec, dir, args, opts) {
|
|
24823
24819
|
return exec("git", args, { cwd: dir, ...opts });
|
|
24824
24820
|
}
|
|
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
24821
|
async function isToplevel(exec, dir) {
|
|
24842
24822
|
const r = await git(exec, dir, ["rev-parse", "--show-toplevel"]);
|
|
24843
24823
|
if (r.spawnError || r.code !== 0) return false;
|
|
@@ -24853,56 +24833,6 @@ async function samePath(a, b) {
|
|
|
24853
24833
|
};
|
|
24854
24834
|
return await real(a) === await real(b);
|
|
24855
24835
|
}
|
|
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
24836
|
async function workTreeState(exec, dir) {
|
|
24907
24837
|
if (!await isToplevel(exec, dir)) return { isRepo: false };
|
|
24908
24838
|
const status = await git(exec, dir, ["status", "--porcelain"]);
|
|
@@ -24932,31 +24862,22 @@ function tailStderrLines(buf, n) {
|
|
|
24932
24862
|
var errMsg = (err) => err instanceof Error ? err.message : String(err);
|
|
24933
24863
|
async function createProject(args) {
|
|
24934
24864
|
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);
|
|
24865
|
+
const project = await store.create(name);
|
|
24939
24866
|
const dir = store.projectDir(name);
|
|
24940
|
-
let repo2;
|
|
24941
24867
|
try {
|
|
24942
24868
|
const scaffold = await store.scaffold(name, args.templateSrcDir, args.scaffoldScript, args.extraEnv);
|
|
24943
24869
|
logger2?.info("app-builder.scaffold.done", { name, stdout: scaffold.stdout.slice(0, 200) });
|
|
24944
24870
|
const slug = await readSlug(dir);
|
|
24945
24871
|
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 };
|
|
24872
|
+
logger2?.info("app-builder.create.done", { name, slug });
|
|
24873
|
+
return { project, projectDir: dir, slug };
|
|
24952
24874
|
} catch (err) {
|
|
24953
24875
|
const message = errMsg(err);
|
|
24954
24876
|
logger2?.warn("app-builder.create.failed", { name, error: message });
|
|
24955
24877
|
await store.delete(name).catch((e) => {
|
|
24956
24878
|
logger2?.warn("app-builder.create.rollback-failed", { name, error: errMsg(e) });
|
|
24957
24879
|
});
|
|
24958
|
-
|
|
24959
|
-
throw new Error(`createProject \u6CA1\u6210\uFF0C\u521A\u5EFA\u7684\u76EE\u5F55\u5DF2\u56DE\u6EDA\uFF1A${message}${orphan}`);
|
|
24880
|
+
throw new Error(`createProject \u6CA1\u6210\uFF0C\u521A\u5EFA\u7684\u76EE\u5F55\u5DF2\u56DE\u6EDA\uFF1A${message}`);
|
|
24960
24881
|
}
|
|
24961
24882
|
}
|
|
24962
24883
|
async function readSlug(projectDir) {
|
|
@@ -24969,23 +24890,41 @@ async function readSlug(projectDir) {
|
|
|
24969
24890
|
const m = /^SLUG=([A-Za-z0-9]+)\s*$/m.exec(conf);
|
|
24970
24891
|
return m ? m[1] : null;
|
|
24971
24892
|
}
|
|
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
24893
|
var PUBLISH_TIMEOUT_MS = 15 * 6e4;
|
|
24894
|
+
function skillCollectFilesScript(personaRoot) {
|
|
24895
|
+
return (0, import_node_path5.join)(personaRoot, ".claude", "skills", "app-builder-projects", "scripts", "collect-files.mjs");
|
|
24896
|
+
}
|
|
24977
24897
|
async function runPublish(args) {
|
|
24978
24898
|
const { store, name, logger: logger2 } = args;
|
|
24979
24899
|
const exec = args.exec ?? runCommand;
|
|
24980
24900
|
const spawnFn = args.spawnImpl ?? import_node_child_process3.spawn;
|
|
24981
24901
|
const timeoutMs = args.timeoutMs ?? PUBLISH_TIMEOUT_MS;
|
|
24982
|
-
|
|
24902
|
+
const meta = await store.get(name);
|
|
24903
|
+
if (!meta) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
|
|
24983
24904
|
const dir = store.projectDir(name);
|
|
24984
24905
|
const tree = await workTreeState(exec, dir);
|
|
24985
24906
|
if (tree.isRepo && tree.dirty) {
|
|
24986
24907
|
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
24908
|
}
|
|
24988
|
-
|
|
24909
|
+
let head = tree.isRepo ? tree.head : null;
|
|
24910
|
+
if (!tree.isRepo && meta.repoUrl) {
|
|
24911
|
+
if (!meta.pushedCommit || !meta.pushedTreeHash) {
|
|
24912
|
+
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");
|
|
24913
|
+
}
|
|
24914
|
+
const script = args.collectFilesScript ?? skillCollectFilesScript(args.personaRoot);
|
|
24915
|
+
const r = await exec(process.execPath, [script, "--project", dir], { timeoutMs: 6e4 });
|
|
24916
|
+
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}`}`);
|
|
24917
|
+
let treeHash;
|
|
24918
|
+
try {
|
|
24919
|
+
treeHash = JSON.parse(r.stdout).treeHash;
|
|
24920
|
+
} catch {
|
|
24921
|
+
}
|
|
24922
|
+
if (!treeHash) throw new Error(`collect-files.mjs \u7684\u8F93\u51FA\u91CC\u6CA1\u6709 treeHash\uFF08${script}\uFF09`);
|
|
24923
|
+
if (treeHash !== meta.pushedTreeHash) {
|
|
24924
|
+
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");
|
|
24925
|
+
}
|
|
24926
|
+
head = meta.pushedCommit;
|
|
24927
|
+
}
|
|
24989
24928
|
const logPath = (0, import_node_path5.join)(dir, ".publish.log");
|
|
24990
24929
|
const log2 = (0, import_node_fs3.createWriteStream)(logPath, { flags: "w" });
|
|
24991
24930
|
log2.on("error", () => {
|
|
@@ -25129,7 +25068,7 @@ var ENV_NAMES = {
|
|
|
25129
25068
|
removeScript: "CLAWD_APP_BUILDER_REMOVE_SCRIPT"
|
|
25130
25069
|
};
|
|
25131
25070
|
function readEnv(env = process.env) {
|
|
25132
|
-
const out = {
|
|
25071
|
+
const out = {};
|
|
25133
25072
|
for (const [key, name] of Object.entries(ENV_NAMES)) {
|
|
25134
25073
|
const v = env[name];
|
|
25135
25074
|
if (v) out[key] = v;
|
|
@@ -25150,33 +25089,21 @@ async function handleCreateProject(input, ctx) {
|
|
|
25150
25089
|
name: input.name,
|
|
25151
25090
|
templateSrcDir: env.templateDir,
|
|
25152
25091
|
scaffoldScript: env.scaffoldScript,
|
|
25153
|
-
org: env.repoOrg,
|
|
25154
|
-
...ctx.exec ? { exec: ctx.exec } : {},
|
|
25155
25092
|
logger
|
|
25156
25093
|
});
|
|
25157
|
-
|
|
25094
|
+
const repoName = r.slug ? `${r.project.name}-${r.slug}` : r.project.name;
|
|
25095
|
+
log("createProject done", { name: input.name, projectDir: r.projectDir, slug: r.slug, repoName });
|
|
25158
25096
|
return ok({
|
|
25159
25097
|
name: r.project.name,
|
|
25160
25098
|
projectDir: r.projectDir,
|
|
25161
|
-
|
|
25162
|
-
|
|
25163
|
-
next:
|
|
25099
|
+
slug: r.slug,
|
|
25100
|
+
repoName,
|
|
25101
|
+
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
25102
|
});
|
|
25165
25103
|
} catch (err) {
|
|
25166
25104
|
return fail(`createProject failed: ${errMsg2(err)}`);
|
|
25167
25105
|
}
|
|
25168
25106
|
}
|
|
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
25107
|
async function handlePublish(input, ctx) {
|
|
25181
25108
|
const miss = missingEnv(ctx.env, ["root", "publishScript", "personaRoot"]);
|
|
25182
25109
|
if (miss) return fail(`publish: ${miss}`);
|
|
@@ -25233,26 +25160,17 @@ function registerTools(server, ctx) {
|
|
|
25233
25160
|
server.registerTool(
|
|
25234
25161
|
"createProject",
|
|
25235
25162
|
{
|
|
25236
|
-
title: "Create an app project (scaffold
|
|
25237
|
-
description: "Create a new app project in the current user workspace:
|
|
25163
|
+
title: "Create an app project (local scaffold only)",
|
|
25164
|
+
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
25165
|
inputSchema: { name: external_exports.string().min(1).describe("project name given by the user (kebab-case)") }
|
|
25239
25166
|
},
|
|
25240
25167
|
async (input) => handleCreateProject(input, ctx)
|
|
25241
25168
|
);
|
|
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
25169
|
server.registerTool(
|
|
25252
25170
|
"publish",
|
|
25253
25171
|
{
|
|
25254
25172
|
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
|
|
25173
|
+
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
25174
|
inputSchema: { name: external_exports.string().min(1).describe("project name") }
|
|
25257
25175
|
},
|
|
25258
25176
|
async (input) => handlePublish(input, ctx)
|
|
@@ -25270,7 +25188,7 @@ function registerTools(server, ctx) {
|
|
|
25270
25188
|
async function main() {
|
|
25271
25189
|
const env = readEnv();
|
|
25272
25190
|
log("boot", { pid: process.pid, root: env.root ?? "(unset)", personaRoot: env.personaRoot ?? "(unset)" });
|
|
25273
|
-
const server = new McpServer({ name: "clawd-app-builder", version: "0.
|
|
25191
|
+
const server = new McpServer({ name: "clawd-app-builder", version: "0.3.0" });
|
|
25274
25192
|
registerTools(server, { env });
|
|
25275
25193
|
await server.connect(new StdioServerTransport());
|
|
25276
25194
|
}
|
|
@@ -25285,7 +25203,6 @@ if (isMainModule) {
|
|
|
25285
25203
|
// Annotate the CommonJS export names for ESM import in node:
|
|
25286
25204
|
0 && (module.exports = {
|
|
25287
25205
|
handleCreateProject,
|
|
25288
|
-
handleListProjects,
|
|
25289
25206
|
handlePublish,
|
|
25290
25207
|
handleRemoveProject,
|
|
25291
25208
|
readEnv,
|