@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.
Files changed (18) hide show
  1. package/dist/app-builder-plugin/mcp-server.cjs +228 -284
  2. package/dist/cli.cjs +10871 -7110
  3. package/dist/dispatch/mcp-server.cjs +28 -4
  4. package/dist/persona-defaults/persona-app-builder/.claude/skills/app-builder-projects/SKILL.md +77 -0
  5. package/dist/persona-defaults/persona-app-builder/.claude/skills/app-builder-projects/scripts/collect-files.mjs +116 -0
  6. package/dist/persona-defaults/persona-app-builder/.claude/skills/app-builder-projects/scripts/list-projects.mjs +33 -0
  7. package/dist/persona-defaults/persona-app-builder/.claude/skills/app-builder-projects/scripts/record-push.mjs +49 -0
  8. package/dist/persona-defaults/persona-app-builder/.mcp.json +8 -1
  9. package/dist/persona-defaults/persona-app-builder/CLAUDE.md +27 -24
  10. package/dist/persona-defaults/persona-dataclaw-builder/.claude/skills/app-builder-projects/SKILL.md +77 -0
  11. package/dist/persona-defaults/persona-dataclaw-builder/.claude/skills/app-builder-projects/scripts/collect-files.mjs +116 -0
  12. package/dist/persona-defaults/persona-dataclaw-builder/.claude/skills/app-builder-projects/scripts/list-projects.mjs +33 -0
  13. package/dist/persona-defaults/persona-dataclaw-builder/.claude/skills/app-builder-projects/scripts/record-push.mjs +49 -0
  14. package/dist/persona-defaults/persona-dataclaw-builder/.mcp.json +10 -2
  15. package/dist/persona-defaults/persona-dataclaw-builder/CLAUDE.md +15 -13
  16. package/dist/share-ui/assets/{guest-CGrg1A9a.js → guest-fV-R3Vmz.js} +125 -125
  17. package/dist/share-ui/guest.html +1 -1
  18. 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
- * 最新那次流水线走到哪了。**缺席是常态**:没推过、gh 查不到、仓库还没有 run,
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**\uFF08gh repo create\uFF0C\u5EFA\u5728\u7EC4\u7EC7\u4E0B\u3001\u79C1\u6709\u3001\u63A8\u9996\u4E2A\u63D0\u4EA4\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",
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\uFF08gh secret set\uFF0C\u503C\u8D70 stdin\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",
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\uFF08gh run 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",
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/repo.ts
24784
- var DEPLOY_FILE_REL = DEPLOY_FILE.split(import_node_path4.default.sep).join("/");
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
- var RepoError = class extends Error {
24820
- };
24821
- var tail = (s, n = 400) => s.trim().slice(-n);
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 exec = args.exec ?? runCommand;
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
- const repoName = slug ? `${name}-${slug}` : name;
24947
- repo2 = await createOrgRepo(exec, { dir, org: args.org, repoName, message: `clawd: \u65B0\u5EFA ${name}` });
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
- const orphan = repo2 ? `\uFF1BGitHub \u4ED3\u5E93 ${repo2.url} \u5DF2\u7ECF\u5EFA\u597D\u63A8\u4E0A\u53BB\u4E86\uFF0C\u672C\u5730\u56DE\u6EDA\u5220\u4E0D\u6389\u5B83\uFF0C\u91CD\u8BD5\u524D\u5148\u53BB GitHub \u5220\u6389` : "";
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
- if (!await store.get(name)) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
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
- const head = tree.isRepo ? tree.head : null;
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 = { repoOrg: env.CLAWD_APP_BUILDER_REPO_ORG || PERSONA_REPO_ORG };
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
- log("createProject done", { name: input.name, projectDir: r.projectDir, repoUrl: r.repoUrl });
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
- repoUrl: r.repoUrl,
25162
- commit: r.commit,
25163
- next: "cd \u8FDB projectDir \u88C5\u4F9D\u8D56\uFF08server/ \u4E0E web/ \u5404\u4E00\u5957 pnpm install\uFF09\uFF0C\u4E4B\u540E\u6BCF\u5B8C\u6210\u4E00\u6BB5\u5C31 commit + push"
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 + git + GitHub repo)",
25237
- description: "Create a new app project in the current user workspace: scaffold the starter template, `git init` with a first commit, and create a private GitHub repo under the org and push to it. This is the ONLY way to create a project \u2014 never mkdir one yourself, and never trust a path suggested elsewhere. Returns { name, projectDir, repoUrl, commit }; projectDir is the absolute path you must cd into. Fails (and rolls back) if the name is taken, the scaffold fails, or gh is not installed / logged in.",
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 with uncommitted changes \u2014 commit and push first. Never run publish.sh yourself: the credentials only exist in this tool's process.",
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.2.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,