@clawos-dev/clawd 0.2.511 → 0.2.513

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.
@@ -3227,8 +3227,8 @@ var require_utils = __commonJS({
3227
3227
  }
3228
3228
  return ind;
3229
3229
  }
3230
- function removeDotSegments(path) {
3231
- let input = path;
3230
+ function removeDotSegments(path4) {
3231
+ let input = path4;
3232
3232
  const output = [];
3233
3233
  let nextSlash = -1;
3234
3234
  let len = 0;
@@ -3480,8 +3480,8 @@ var require_schemes = __commonJS({
3480
3480
  wsComponent.secure = void 0;
3481
3481
  }
3482
3482
  if (wsComponent.resourceName) {
3483
- const [path, query] = wsComponent.resourceName.split("?");
3484
- wsComponent.path = path && path !== "/" ? path : void 0;
3483
+ const [path4, query] = wsComponent.resourceName.split("?");
3484
+ wsComponent.path = path4 && path4 !== "/" ? path4 : void 0;
3485
3485
  wsComponent.query = query;
3486
3486
  wsComponent.resourceName = void 0;
3487
3487
  }
@@ -4453,13 +4453,13 @@ var require_core = __commonJS({
4453
4453
  }, warn() {
4454
4454
  }, error() {
4455
4455
  } };
4456
- function getLogger(logger) {
4457
- if (logger === false)
4456
+ function getLogger(logger2) {
4457
+ if (logger2 === false)
4458
4458
  return noLogs;
4459
- if (logger === void 0)
4459
+ if (logger2 === void 0)
4460
4460
  return console;
4461
- if (logger.log && logger.warn && logger.error)
4462
- return logger;
4461
+ if (logger2.log && logger2.warn && logger2.error)
4462
+ return logger2;
4463
4463
  throw new Error("logger must implement log, warn and error methods");
4464
4464
  }
4465
4465
  var KEYWORD_NAME = /^[a-z_$][a-z0-9_$:-]*$/i;
@@ -6874,12 +6874,12 @@ var require_dist = __commonJS({
6874
6874
  throw new Error(`Unknown format "${name}"`);
6875
6875
  return f;
6876
6876
  };
6877
- function addFormats(ajv, list, fs3, exportName) {
6877
+ function addFormats(ajv, list, fs7, exportName) {
6878
6878
  var _a;
6879
6879
  var _b;
6880
6880
  (_a = (_b = ajv.opts.code).formats) !== null && _a !== void 0 ? _a : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
6881
6881
  for (const f of list)
6882
- ajv.addFormat(f, fs3[f]);
6882
+ ajv.addFormat(f, fs7[f]);
6883
6883
  }
6884
6884
  module2.exports = exports2 = formatsPlugin;
6885
6885
  Object.defineProperty(exports2, "__esModule", { value: true });
@@ -6894,15 +6894,11 @@ __export(mcp_server_exports, {
6894
6894
  handleListProjects: () => handleListProjects,
6895
6895
  handlePublish: () => handlePublish,
6896
6896
  handleRemoveProject: () => handleRemoveProject,
6897
- handleReportStage: () => handleReportStage,
6898
- handleSetProdUrl: () => handleSetProdUrl,
6899
- handleStartDevServer: () => handleStartDevServer,
6900
- handleUpdateProjectPort: () => handleUpdateProjectPort,
6901
6897
  readEnv: () => readEnv,
6902
6898
  registerTools: () => registerTools
6903
6899
  });
6904
6900
  module.exports = __toCommonJS(mcp_server_exports);
6905
- var import_node_fs3 = __toESM(require("fs"), 1);
6901
+ var import_node_fs4 = __toESM(require("fs"), 1);
6906
6902
 
6907
6903
  // ../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
6908
6904
  var external_exports = {};
@@ -7382,8 +7378,8 @@ function getErrorMap() {
7382
7378
 
7383
7379
  // ../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
7384
7380
  var makeIssue = (params) => {
7385
- const { data, path, errorMaps, issueData } = params;
7386
- const fullPath = [...path, ...issueData.path || []];
7381
+ const { data, path: path4, errorMaps, issueData } = params;
7382
+ const fullPath = [...path4, ...issueData.path || []];
7387
7383
  const fullIssue = {
7388
7384
  ...issueData,
7389
7385
  path: fullPath
@@ -7499,11 +7495,11 @@ var errorUtil;
7499
7495
 
7500
7496
  // ../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.js
7501
7497
  var ParseInputLazyPath = class {
7502
- constructor(parent, value, path, key) {
7498
+ constructor(parent, value, path4, key) {
7503
7499
  this._cachedPath = [];
7504
7500
  this.parent = parent;
7505
7501
  this.data = value;
7506
- this._path = path;
7502
+ this._path = path4;
7507
7503
  this._key = key;
7508
7504
  }
7509
7505
  get path() {
@@ -11140,10 +11136,10 @@ function assignProp(target, prop, value) {
11140
11136
  configurable: true
11141
11137
  });
11142
11138
  }
11143
- function getElementAtPath(obj, path) {
11144
- if (!path)
11139
+ function getElementAtPath(obj, path4) {
11140
+ if (!path4)
11145
11141
  return obj;
11146
- return path.reduce((acc, key) => acc?.[key], obj);
11142
+ return path4.reduce((acc, key) => acc?.[key], obj);
11147
11143
  }
11148
11144
  function promiseAllObject(promisesObj) {
11149
11145
  const keys = Object.keys(promisesObj);
@@ -11463,11 +11459,11 @@ function aborted(x, startIndex = 0) {
11463
11459
  }
11464
11460
  return false;
11465
11461
  }
11466
- function prefixIssues(path, issues) {
11462
+ function prefixIssues(path4, issues) {
11467
11463
  return issues.map((iss) => {
11468
11464
  var _a;
11469
11465
  (_a = iss).path ?? (_a.path = []);
11470
- iss.path.unshift(path);
11466
+ iss.path.unshift(path4);
11471
11467
  return iss;
11472
11468
  });
11473
11469
  }
@@ -21116,11 +21112,6 @@ var StdioServerTransport = class {
21116
21112
  }
21117
21113
  };
21118
21114
 
21119
- // src/app-builder-plugin/project-store.ts
21120
- var import_node_fs = require("fs");
21121
- var import_node_child_process = require("child_process");
21122
- var import_node_path = require("path");
21123
-
21124
21115
  // ../protocol/src/attachment-schemas.ts
21125
21116
  var TOKEN_ROLES = ["owner"];
21126
21117
  var GROUP_FILE_SOURCES = ["agent", "owner", "user-upload"];
@@ -21532,6 +21523,7 @@ var DeployFinishScanArgsSchema = external_exports.object({
21532
21523
  personaId: external_exports.string().min(1),
21533
21524
  notes: external_exports.array(external_exports.string()).default([])
21534
21525
  }).strict();
21526
+ var PERSONA_REPO_ORG = "ottin4ttc";
21535
21527
 
21536
21528
  // ../protocol/src/contact-remote-access.ts
21537
21529
  var ContactSetRemoteAccessArgsSchema = external_exports.object({
@@ -22919,10 +22911,6 @@ var SessionFileSchema = external_exports.object({
22919
22911
  * 本机资源指针——mirror 上与 ownerPersonaId 同地点 strip(#750 防线)。
22920
22912
  */
22921
22913
  projectPath: external_exports.string().min(1).optional(),
22922
- // app-builder Project 反向索引(spec: superpowers/specs/2026-06-01-app-builder-project-picker-design.md)。
22923
- // 当此 session 绑定到某个 build 中的 project 时填写;name 与 projects/<name>/ 子目录同名,
22924
- // 受 projectNameSchema regex 约束。daemon session:resume 时校验该目录存在,不存在则拒。
22925
- appBuilderProject: external_exports.string().regex(/^[a-z][a-z0-9-]{0,39}$/).optional(),
22926
22914
  // listener-mode sub-session 的原始 chatId(owner-mode 不写;listener-scope 强制写入)。
22927
22915
  // 派生 sessionId 是单向 hash `${personaId}-${tokenHash12}-${chatHash8}`,必须保留原始 chatId
22928
22916
  // 才能让 alice 重连同一 sub-session(持久化在 alice localStorage 之外,由 daemon push 同步)。
@@ -23257,10 +23245,6 @@ var SessionCreateArgs = external_exports.object({
23257
23245
  // owner-mode persona session:传此字段时 daemon 自行派生 cwd 和启动参数,
23258
23246
  // 且按 ownerPersonaId 做 idempotent dedupe(每 persona 永远只有一个 owner session)
23259
23247
  ownerPersonaId: external_exports.string().min(1).optional(),
23260
- // 单栏默认 refactor (spec 2026-06-02):appBuilderProject 字段已从 SessionCreateArgs 砍掉。
23261
- // 绑定路径收敛到 appBuilder:createProject —— 先创建空 session(仅传 ownerPersonaId=persona-app-builder),
23262
- // 后通过 createProject RPC 创建 project 并 daemon 内部 mutate 写入 appBuilderProject。
23263
- // SessionFile schema 仍保留该字段供 daemon 内部 mutate(manager.update 内部调用通道)。
23264
23248
  /**
23265
23249
  * 新建会话选择的 project(spec 2026-08-17-projects-sidebar §4.2):
23266
23250
  * - 单独出现:daemon 以它为 cwd(等价传 cwd)
@@ -23347,9 +23331,6 @@ var SessionUpdateArgs = external_exports.object({
23347
23331
  // 从所有 sidebar 消费者视野消失(App.visibleSessions / buildSessionTree),用户看不见
23348
23332
  // 也删不掉,再被 sweepEphemeralOnStartup 静默删除——数据丢失级别的口子。
23349
23333
  ephemeral: external_exports.literal(false).optional()
23350
- // 注意:appBuilderProject 字段已从 patch 中砍掉(spec 2026-06-02-app-builder-single-pane-default-design)。
23351
- // 绑定单向不可变 —— 由 appBuilder:createProject 在 daemon 内部一次写入;UI / assistant 不再
23352
- // 通过 sessionUpdate 修改这个字段。SessionFile 仍保留 appBuilderProject 字段供 daemon 内部 mutate。
23353
23334
  })
23354
23335
  });
23355
23336
  var QueuedMessage = external_exports.object({
@@ -23696,189 +23677,6 @@ var InfoResponseSchema = external_exports.object({
23696
23677
  * 探测见 daemon/src/cli-probe/probe.ts。旧 daemon 不发 → undefined。 */
23697
23678
  globalCliVersion: external_exports.string().nullable().optional()
23698
23679
  });
23699
- var PROJECT_PORT_MIN = 6173;
23700
- var PROJECT_PORT_MAX = 6272;
23701
- var projectNameRegex = /^[a-z][a-z0-9-]{0,39}$/;
23702
- var DEFAULT_DEV_COMMAND = "cd server && pnpm dev";
23703
- var ProjectMetadataSchema = external_exports.object({
23704
- name: external_exports.string().regex(projectNameRegex, {
23705
- 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"
23706
- }),
23707
- port: external_exports.number().int().min(PROJECT_PORT_MIN).max(PROJECT_PORT_MAX),
23708
- createdAt: external_exports.string().datetime(),
23709
- /** dev server 启动命令(shell 字符串);占位符 $CLAWD_PREVIEW_PORT 替换 + env 注入 */
23710
- devCommand: external_exports.string().min(1),
23711
- /**
23712
- * 发布上线后的公网 URL(spec 2026-06-02-app-builder-single-pane-default-design §5.6.1)。
23713
- * assistant 跑完 publish.sh 后调 appBuilder:setProdUrl 写入;UI 读这个字段切预览栏到线上 URL,
23714
- * 取代旧 `<builder-preview url=... label="prod" />` marker 机制。
23715
- * 老 project 元数据无此字段(optional 兼容)。
23716
- */
23717
- prodUrl: external_exports.string().url().optional(),
23718
- /**
23719
- * dev server 就绪 stdout regex(spec 2026-06-02-app-builder-explicit-dev-server-control §2)。
23720
- * supervisor spawn 后会监听子进程 stdout,命中此 regex 视为 dev server 真 ready;
23721
- * 仅当 await ready 后才把 stage 切到 'running',避免 cold start race 导致 iframe 502。
23722
- * optional:缺省时 supervisor 用默认 /Local:\s+https?:\/\//(vite "Local: http://..." 输出)。
23723
- * 老板换非 vite 技术栈(Next.js "Ready in"、webpack "Compiled" 等)时自定义这个字段。
23724
- */
23725
- readyPattern: external_exports.string().optional(),
23726
- /**
23727
- * 发布上线脚手架化 (spec 2026-06-03-app-builder-publish-scaffold-design §5.1.4):
23728
- * in-flight 发布 job 的元数据。仅承载"in-flight"或"中断残骸"两种状态:
23729
- * - daemon spawn publish.sh + 收到第一个 ::stage::build marker 时写入
23730
- * - 每次 stage 切换更新 stage 字段
23731
- * - 成功 / 正常失败 / 用户 dismiss 时立即清掉
23732
- * - 只有 daemon 崩才会留残骸(子进程随 daemon 一起死)
23733
- * status 字段在 ProjectWithStatus 派生而不在这里 —— 本 schema 是落盘形态,
23734
- * "in-flight vs interrupted" 区分由 daemon 响应时实时拼 registry 状态得出。
23735
- */
23736
- publishJob: external_exports.object({
23737
- jobId: external_exports.string().min(1),
23738
- stage: external_exports.enum(["build", "deploy", "verify"]),
23739
- startedAt: external_exports.number().int().nonnegative()
23740
- }).optional()
23741
- });
23742
- var PROJECT_STAGE_VALUES = [
23743
- "install-pending",
23744
- // createProject 刚返回,scaffold 已完成(daemon 同步内联跑),等 assistant pnpm install
23745
- "installing",
23746
- // assistant pnpm install 中
23747
- "starting-dev-server",
23748
- // startDevServer 已调,supervisor spawn 子进程中(await ready 期间也在此 stage)
23749
- "running",
23750
- // dev server 跑起来了(supervisor stdout 命中 readyPattern + 端口已监听)
23751
- "stopped",
23752
- // 用户显式点"收起预览"停掉 / daemon 重启后默认;UI 渲染折叠条 + "展开预览"按钮
23753
- "failed"
23754
- // 某个阶段失败(stageReason 字段携带原因,含 dev server cold start timeout)
23755
- ];
23756
- var ProjectStageSchema = external_exports.enum(PROJECT_STAGE_VALUES);
23757
- var ProjectWithStatusSchema = ProjectMetadataSchema.extend({
23758
- isRunning: external_exports.boolean(),
23759
- boundSessionId: external_exports.string().nullable(),
23760
- /**
23761
- * 单栏 refactor stage fix (spec 2026-06-02 §5.6.2):daemon 内存维护的当前 stage。
23762
- * UI 据此切 PreviewPane spinner / iframe / error。
23763
- * optional 兼容启动后 daemon 还没有 stage 记录的 fresh project(按 isRunning 推断兜底)。
23764
- */
23765
- stage: ProjectStageSchema.optional(),
23766
- /** stage='failed' 时携带失败原因 */
23767
- stageReason: external_exports.string().optional(),
23768
- /**
23769
- * 发布上线脚手架化 (spec 2026-06-03-app-builder-publish-scaffold-design §5.1.3):
23770
- * ProjectMetadata.publishJob 的 status 派生版。daemon 响应时按以下规则实时拼装:
23771
- * - 落盘字段存在 + PublishJobRegistry 有匹配 jobId → 'in-flight'
23772
- * - 落盘字段存在 + Registry 无匹配(jobId 对不上或 entry 缺失)→ 'interrupted'(daemon 崩残骸)
23773
- * - 落盘字段不存在 → undefined(字段省略)
23774
- * UI 用 status 决定 PreviewPane 渲染分支(in-flight 三段 checklist / interrupted 恢复条)。
23775
- */
23776
- publishJob: external_exports.object({
23777
- jobId: external_exports.string().min(1),
23778
- stage: external_exports.enum(["build", "deploy", "verify"]),
23779
- startedAt: external_exports.number().int().nonnegative(),
23780
- status: external_exports.enum(["in-flight", "interrupted"])
23781
- }).optional()
23782
- });
23783
- var ListProjectsArgsSchema = external_exports.object({}).strict();
23784
- var ListProjectsResultSchema = external_exports.object({
23785
- projects: external_exports.array(ProjectWithStatusSchema)
23786
- }).strict();
23787
- var GetProjectArgsSchema = external_exports.object({
23788
- sessionId: external_exports.string().min(1)
23789
- }).strict();
23790
- var GetProjectResultSchema = external_exports.object({
23791
- /** session 未绑 project / project 不存在时为 null(UI 走单栏 / 折叠) */
23792
- project: ProjectWithStatusSchema.nullable()
23793
- }).strict();
23794
- var CreateProjectArgsSchema = external_exports.object({
23795
- /** 必填:caller session,daemon 自动绑过去(spec 2026-06-02 §3 invariant 1 Session ↔ 1 Project) */
23796
- sessionId: external_exports.string().min(1),
23797
- // .min(1):改造前手写校验是 `!f.name` 明确拒空串;空 name 会让 projectDir('')
23798
- // 落到 projects 根目录,scaffold 往根上铺模板。
23799
- name: external_exports.string().min(1)
23800
- }).strict();
23801
- var CreateProjectResultSchema = external_exports.object({
23802
- project: ProjectMetadataSchema
23803
- }).strict();
23804
- var DeleteProjectArgsSchema = external_exports.object({
23805
- name: external_exports.string()
23806
- }).strict();
23807
- var DeleteProjectResultSchema = external_exports.object({}).strict();
23808
- var UpdateProjectPortArgsSchema = external_exports.object({
23809
- name: external_exports.string(),
23810
- newPort: external_exports.number().int()
23811
- }).strict();
23812
- var UpdateProjectPortResultSchema = external_exports.object({
23813
- project: ProjectMetadataSchema
23814
- }).strict();
23815
- var SetProdUrlArgsSchema = external_exports.object({
23816
- name: external_exports.string().regex(projectNameRegex),
23817
- url: external_exports.string().url()
23818
- }).strict();
23819
- var SetProdUrlResultSchema = external_exports.object({
23820
- project: ProjectMetadataSchema
23821
- }).strict();
23822
- var StartDevServerArgsSchema = external_exports.object({
23823
- sessionId: external_exports.string().min(1),
23824
- force: external_exports.boolean().optional()
23825
- }).strict();
23826
- var StartDevServerResultSchema = external_exports.object({
23827
- project: ProjectMetadataSchema
23828
- }).strict();
23829
- var ASSISTANT_REPORTABLE_STAGES = [
23830
- "installing",
23831
- "failed"
23832
- ];
23833
- var ReportStageArgsSchema = external_exports.object({
23834
- sessionId: external_exports.string().min(1),
23835
- stage: external_exports.enum(ASSISTANT_REPORTABLE_STAGES),
23836
- /** stage='failed' 时必填的原因(一句话),其它 stage optional */
23837
- reason: external_exports.string().optional()
23838
- }).strict();
23839
- var ReportStageResultSchema = external_exports.object({
23840
- ok: external_exports.literal(true)
23841
- }).strict();
23842
- var StopDevServerArgsSchema = external_exports.object({
23843
- sessionId: external_exports.string().min(1)
23844
- }).strict();
23845
- var StopDevServerResultSchema = external_exports.object({
23846
- ok: external_exports.literal(true)
23847
- }).strict();
23848
- var AppBuilderProjectUpdatedEventSchema = external_exports.object({
23849
- type: external_exports.literal("appBuilder:project-updated"),
23850
- project: ProjectWithStatusSchema,
23851
- stage: ProjectStageSchema,
23852
- /** stage='failed' 时的失败原因 */
23853
- stageReason: external_exports.string().optional()
23854
- }).strict();
23855
- var PublishArgsSchema = external_exports.object({
23856
- name: external_exports.string().regex(projectNameRegex)
23857
- });
23858
- var PublishResultSchema = external_exports.object({
23859
- jobId: external_exports.string().min(1),
23860
- status: external_exports.enum(["started", "already-publishing"])
23861
- }).strict();
23862
- var DismissPublishJobArgsSchema = external_exports.object({
23863
- name: external_exports.string().regex(projectNameRegex)
23864
- });
23865
- var DismissPublishJobResultSchema = external_exports.object({
23866
- ok: external_exports.literal(true)
23867
- }).strict();
23868
- var AppBuilderPublishProgressEventSchema = external_exports.object({
23869
- type: external_exports.literal("appBuilder:publish-progress"),
23870
- name: external_exports.string().regex(projectNameRegex),
23871
- jobId: external_exports.string().min(1),
23872
- stage: external_exports.enum(["build", "deploy", "verify"]),
23873
- status: external_exports.enum(["started", "completed"])
23874
- }).strict();
23875
- var AppBuilderPublishFailedEventSchema = external_exports.object({
23876
- type: external_exports.literal("appBuilder:publish-failed"),
23877
- name: external_exports.string().regex(projectNameRegex),
23878
- jobId: external_exports.string().min(1),
23879
- stage: external_exports.enum(["build", "deploy", "verify", "unknown"]),
23880
- errorSummary: external_exports.string()
23881
- }).strict();
23882
23680
 
23883
23681
  // ../protocol/src/methods.ts
23884
23682
  var NO_ARGS = external_exports.object({});
@@ -23914,7 +23712,7 @@ var METHOD_DOCS = {
23914
23712
  args: SessionUpdateArgs
23915
23713
  },
23916
23714
  "session:delete": {
23917
- summary: "\u5220\u4F1A\u8BDD\uFF1A\u505C\u8FDB\u7A0B + \u5220 SessionFile\uFF0C\u5E76\u8054\u52A8\u6E05\u7406\u7ED1\u5B9A\u7684 app-builder project",
23715
+ summary: "\u5220\u4F1A\u8BDD\uFF1A\u505C\u8FDB\u7A0B + \u5220 SessionFile",
23918
23716
  args: SessionIdArgs
23919
23717
  },
23920
23718
  "session:send": {
@@ -24505,50 +24303,6 @@ var METHOD_DOCS = {
24505
24303
  "device:connections": {
24506
24304
  summary: '\u5217\u51FA\u672C daemon \u5F53\u524D\u6240\u6709 owner WS \u8FDE\u63A5\uFF08\u672C\u673A UI / \u624B\u673A / \u7B2C\u4E8C\u53F0\u684C\u9762\uFF09+ \u5404\u81EA clientInfo\u3002owner-only\uFF1Bguest \u8FDE\u63A5\u4E0D\u5728\u5217\uFF08\u4E0D\u662F"\u6211\u7684\u8BBE\u5907"\u4E14\u4E0D\u5411 UI \u6CC4\u9732 guest \u4FE1\u606F\uFF09',
24507
24305
  args: NO_ARGS
24508
- },
24509
- "appBuilder:listProjects": {
24510
- summary: "\u5217\u51FA app-builder project\uFF08\u542B dev server \u72B6\u6001\uFF09",
24511
- args: ListProjectsArgsSchema
24512
- },
24513
- "appBuilder:getProject": {
24514
- summary: "\u6309 sessionId \u53D6\u5355\u4E2A\u7ED1\u5B9A\u7684 project\uFF08\u542B status\uFF09",
24515
- args: GetProjectArgsSchema
24516
- },
24517
- "appBuilder:createProject": {
24518
- summary: "\u7ED9\u4F1A\u8BDD\u7ED1\u5B9A\u65B0 project\uFF1A\u6821\u9A8C\u540D + \u4ECE PROJECT_PORT_MIN..MAX \u6BB5\uFF08\u5F53\u524D 6173-6272\uFF09\u5206\u914D\u672A\u5360\u7528\u7AEF\u53E3\uFF08\u8DE8 user \u5168\u5C40\u552F\u4E00\uFF0C\u6C60\u6EE1\u62A5\u9519\uFF09+ \u5EFA\u76EE\u5F55\u5199\u5143\u6570\u636E + **\u540C\u6B65 scaffold**\uFF08spawn \u811A\u672C\u628A nestjs-react \u6A21\u677F\u62F7\u8FDB\u53BB\uFF1B\u5931\u8D25\u4F1A\u5220\u6389\u521A\u5EFA\u7684\u76EE\u5F55\u518D\u629B INTERNAL\uFF0C\u4E0D\u7559\u534A\u6210\u54C1\uFF09\u3002**\u88C5\u4F9D\u8D56\u548C\u8D77 dev server \u90FD\u4E0D\u5728\u8FD9\u91CC**\uFF1A\u843D\u5730 stage=install-pending\uFF0C\u8C03\u7528\u65B9\u81EA\u5DF1 pnpm install + appBuilder:reportStage + appBuilder:startDevServer",
24519
- args: CreateProjectArgsSchema
24520
- },
24521
- "appBuilder:deleteProject": {
24522
- summary: "\u505C dev server + \u5220 project \u76EE\u5F55\uFF08\u7ED1\u5B9A\u7684\u4F1A\u8BDD\u4E0D\u5728\u8FD9\u91CC\u5220\uFF09",
24523
- args: DeleteProjectArgsSchema
24524
- },
24525
- "appBuilder:updateProjectPort": {
24526
- summary: "\u6539 dev server \u7AEF\u53E3\uFF08\u6821\u9A8C\u5728 PROJECT_PORT_MIN..MAX \u6BB5\u5185\u3001\u5F53\u524D 6173-6272\uFF1B**\u649E\u8F66\u53EA\u67E5\u8C03\u7528\u65B9\u81EA\u5DF1\u540D\u4E0B\u7684 project**\uFF0C\u4E0D\u50CF createProject \u90A3\u6837\u8DE8 user \u67E5\uFF09\uFF1A\u505C\u65E7\u7AEF\u53E3 dev server + \u6539 .clawd-project.json + \u7F6E stage=stopped\u3002**\u4E0D\u81EA\u52A8\u62C9\u8D77\u65B0\u7AEF\u53E3\u7684 dev server**\u2014\u2014\u8981\u8D77\u5F97\u518D\u8C03 appBuilder:startDevServer\uFF08\u90A3\u6761\u624D\u8D70 readiness probe\uFF09",
24527
- args: UpdateProjectPortArgsSchema
24528
- },
24529
- "appBuilder:setProdUrl": {
24530
- summary: "\u53D1\u5E03\u4E0A\u7EBF\u540E\u56DE\u586B\u7EBF\u4E0A\u5730\u5740\u5230 .clawd-project.json",
24531
- args: SetProdUrlArgsSchema
24532
- },
24533
- "appBuilder:startDevServer": {
24534
- summary: "scaffold + \u4F9D\u8D56\u5B89\u88C5\u5B8C\u6210\u540E\u663E\u5F0F\u62C9\u8D77 dev server\uFF08createProject \u4E0D\u518D\u81EA\u52A8\u8D77\uFF09",
24535
- args: StartDevServerArgsSchema
24536
- },
24537
- "appBuilder:reportStage": {
24538
- summary: "\u4E0A\u62A5\u811A\u624B\u67B6\u9636\u6BB5\uFF08scaffolding / installing / failed \u7B49\uFF09\uFF0Cdaemon \u5E7F\u64AD\u5E27\u8BA9 UI \u5207 PreviewPane",
24539
- args: ReportStageArgsSchema
24540
- },
24541
- "appBuilder:stopDevServer": {
24542
- summary: "\u6536\u8D77\u9884\u89C8\uFF1A\u505C\u6389 dev server\uFF08\u5BF9\u79F0 startDevServer\uFF09",
24543
- args: StopDevServerArgsSchema
24544
- },
24545
- "appBuilder:publish": {
24546
- summary: "\u53D1\u5E03\u4E0A\u7EBF\uFF1A\u4E32\u884C\u5316 spawn publish.sh \u5E76\u5E7F\u64AD\u9636\u6BB5\u8FDB\u5EA6\u5E27\uFF1B\u540C project \u5DF2\u6709 in-flight job \u65F6\u8FD4\u56DE already-publishing",
24547
- args: PublishArgsSchema
24548
- },
24549
- "appBuilder:dismissPublishJob": {
24550
- summary: "\u53D6\u6D88 in-flight publish\uFF08SIGTERM \u5B50\u8FDB\u7A0B\uFF09\u6216\u6E05\u6389 daemon \u5D29\u6E83\u7559\u4E0B\u7684\u6B8B\u9AB8 publishJob \u5B57\u6BB5",
24551
- args: DismissPublishJobArgsSchema
24552
24306
  }
24553
24307
  };
24554
24308
  var METHOD_NAMES = Object.keys(METHOD_DOCS);
@@ -24630,37 +24384,43 @@ var INNER_TAG_ESCAPE_RE = new RegExp(`<(\\\\*)(/?)(${INNER_TAG_NAMES})\\b`, "g")
24630
24384
  var INNER_TAG_UNESCAPE_RE = new RegExp(`<\\\\(\\\\*)(/?)(${INNER_TAG_NAMES})\\b`, "g");
24631
24385
 
24632
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");
24633
24390
  var PROJECTS_DIR = "projects";
24634
24391
  var META_FILE = ".clawd-project.json";
24635
24392
  var SCAFFOLD_TIMEOUT_MS = 3e4;
24636
- var metaWriteQueues = /* @__PURE__ */ new Map();
24637
- function serializeMetaWrite(metaPath, op) {
24638
- const prev = metaWriteQueues.get(metaPath) ?? Promise.resolve();
24639
- const next = prev.then(op, op);
24640
- const settled = next.then(
24641
- () => void 0,
24642
- () => void 0
24643
- );
24644
- metaWriteQueues.set(metaPath, settled);
24645
- void settled.then(() => {
24646
- if (metaWriteQueues.get(metaPath) === settled) metaWriteQueues.delete(metaPath);
24647
- });
24648
- return next;
24393
+ async function exists(p) {
24394
+ try {
24395
+ await import_node_fs.promises.stat(p);
24396
+ return true;
24397
+ } catch {
24398
+ return false;
24399
+ }
24649
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
+ });
24650
24415
  var ProjectStore = class {
24651
- /** @param root projects 父目录,例如 `~/.clawd/users/<userId>/`(v2 per-user) */
24416
+ /** @param root projects 父目录,例如 `~/.clawd/users/<userId>/` */
24652
24417
  constructor(root) {
24653
24418
  this.root = root;
24654
24419
  }
24655
24420
  root;
24656
- serializeWrite(name, op) {
24657
- return serializeMetaWrite(this.metaPath(name), op);
24658
- }
24659
- /** projects/<name>/.clawd-project.json 路径 */
24660
24421
  metaPath(name) {
24661
24422
  return (0, import_node_path.join)(this.projectsRoot(), name, META_FILE);
24662
24423
  }
24663
- /** projects/<name>/ 目录路径(cwd 用) */
24664
24424
  projectDir(name) {
24665
24425
  return (0, import_node_path.join)(this.projectsRoot(), name);
24666
24426
  }
@@ -24677,73 +24437,40 @@ var ProjectStore = class {
24677
24437
  }
24678
24438
  const out = [];
24679
24439
  for (const name of entries) {
24680
- try {
24681
- const raw = await import_node_fs.promises.readFile(this.metaPath(name), "utf8");
24682
- const json = JSON.parse(raw);
24683
- let migrated = false;
24684
- if (typeof json.devCommand !== "string" || json.devCommand.length === 0) {
24685
- json.devCommand = DEFAULT_DEV_COMMAND;
24686
- migrated = true;
24687
- }
24688
- const parsed = ProjectMetadataSchema.safeParse(json);
24689
- if (parsed.success) {
24690
- out.push(parsed.data);
24691
- if (migrated) {
24692
- void import_node_fs.promises.writeFile(this.metaPath(name), JSON.stringify(parsed.data, null, 2) + "\n", "utf8").catch(() => {
24693
- });
24694
- }
24695
- }
24696
- } catch {
24697
- }
24440
+ const meta = await this.readMeta(name);
24441
+ if (meta) out.push(meta);
24698
24442
  }
24699
24443
  return out.sort((a, b) => a.name.localeCompare(b.name));
24700
24444
  }
24701
- /**
24702
- * @param reservedPorts 其它 user 已占端口(v2 per-user:端口必须全局唯一,所有 user 的
24703
- * dev server 同机跑;per-user store 各自只 list 自己,靠 handler 收集全局端口传进来,
24704
- * 否则不同 user 的首个项目都从 PROJECT_PORT_MIN 起 → 全撞 6173)。
24705
- */
24706
- async create(name, reservedPorts) {
24707
- const existing = await this.list();
24708
- if (existing.some((p) => p.name === name)) {
24709
- throw new Error(`project "${name}" already exists / \u5DF2\u5B58\u5728`);
24710
- }
24711
- const usedPorts = new Set(existing.map((p) => p.port));
24712
- if (reservedPorts) for (const p of reservedPorts) usedPorts.add(p);
24713
- let port = -1;
24714
- for (let p = PROJECT_PORT_MIN; p <= PROJECT_PORT_MAX; p++) {
24715
- if (!usedPorts.has(p)) {
24716
- port = p;
24717
- break;
24718
- }
24719
- }
24720
- if (port < 0) {
24721
- throw new Error(
24722
- `port pool exhausted / \u7AEF\u53E3\u6C60\u5DF2\u6EE1\uFF08${PROJECT_PORT_MIN}-${PROJECT_PORT_MAX}\uFF09\uFF0C\u5148\u5220\u4E00\u4E2A project \u91CA\u653E\u7AEF\u53E3`
24723
- );
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;
24724
24455
  }
24725
- const meta = {
24726
- name,
24727
- port,
24728
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
24729
- devCommand: DEFAULT_DEV_COMMAND
24730
- };
24731
- const validated = ProjectMetadataSchema.safeParse(meta);
24456
+ }
24457
+ async create(name) {
24458
+ const meta = { name, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
24459
+ const validated = AppProjectMetadataSchema.safeParse(meta);
24732
24460
  if (!validated.success) {
24733
24461
  throw new Error(`invalid name "${name}": ${validated.error.message}`);
24734
24462
  }
24735
- const dir = this.projectDir(name);
24736
- await import_node_fs.promises.mkdir(dir, { recursive: true });
24737
- await import_node_fs.promises.writeFile(this.metaPath(name), JSON.stringify(meta, null, 2) + "\n", "utf8");
24738
- return meta;
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;
24739
24469
  }
24740
24470
  /**
24741
- * 同步内联 scaffold:spawn `extension-kit/scripts/new-extension.sh <name>` 等 exit。
24742
- * 必须在 `create()` 之后调(脚本期望 projects/<name>/.clawd-project.json 已就位 = picker 模式)。
24743
- * 失败抛错;上层(handler)应当 catch 后回滚(删 dir + 不 mutate session)。
24744
- *
24745
- * spec 2026-06-02 §5.6.3:scaffold 对 assistant 不可见,避免 assistant 被诱导去"读 example
24746
- * 代码再参考"。assistant 进 project 时目录已是完整模板,直接 pnpm install 即可。
24471
+ * 同步内联 scaffold:spawn `new-extension.sh <name> <模板源> <项目目录>` 等 exit。
24472
+ * 必须在 `create()` 之后调(脚本要求目标目录已存在且只含 .clawd-project.json)。
24473
+ * 失败抛错;上层(core.createProject)catch 后回滚。
24747
24474
  */
24748
24475
  async scaffold(name, templateSrcDir, scaffoldScriptPath, extraEnv) {
24749
24476
  const destDir = this.projectDir(name);
@@ -24778,151 +24505,415 @@ var ProjectStore = class {
24778
24505
  });
24779
24506
  }
24780
24507
  async delete(name) {
24781
- const dir = this.projectDir(name);
24782
- await import_node_fs.promises.rm(dir, { recursive: true, force: true });
24783
- }
24784
- async updatePort(name, newPort) {
24785
- if (newPort < PROJECT_PORT_MIN || newPort > PROJECT_PORT_MAX) {
24786
- throw new Error(
24787
- `port range invalid: must be ${PROJECT_PORT_MIN}-${PROJECT_PORT_MAX}, got ${newPort}`
24788
- );
24789
- }
24790
- return await this.serializeWrite(name, async () => {
24791
- const list = await this.list();
24792
- const target = list.find((p) => p.name === name);
24793
- if (!target) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
24794
- const conflict = list.find((p) => p.name !== name && p.port === newPort);
24795
- if (conflict) {
24796
- throw new Error(`port ${newPort} already used / \u5DF2\u88AB project "${conflict.name}" \u5360\u7528`);
24797
- }
24798
- const updated = { ...target, port: newPort };
24799
- await import_node_fs.promises.writeFile(this.metaPath(name), JSON.stringify(updated, null, 2) + "\n", "utf8");
24800
- return updated;
24801
- });
24508
+ await import_node_fs.promises.rm(this.projectDir(name), { recursive: true, force: true });
24802
24509
  }
24803
- /**
24804
- * 单栏 refactor (spec 2026-06-02 §5.6.1):发布上线后写公网 URL 到 .clawd-project.json.prodUrl。
24805
- * UI 通过 listProjects 读到 prodUrl 后切预览栏到线上 URL,取代旧 prod marker 机制。
24806
- * 重复调用幂等(覆盖旧 URL)。url 由 ProjectMetadataSchema 校验 url() 格式。
24807
- */
24808
- async setProdUrl(name, url) {
24809
- return await this.serializeWrite(name, async () => {
24810
- const list = await this.list();
24811
- const target = list.find((p) => p.name === name);
24812
- if (!target) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
24813
- const updated = { ...target, prodUrl: url };
24814
- const validated = ProjectMetadataSchema.safeParse(updated);
24815
- if (!validated.success) {
24816
- throw new Error(`invalid prodUrl "${url}": ${validated.error.message}`);
24817
- }
24818
- await import_node_fs.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
24819
- return validated.data;
24820
- });
24510
+ async setRepoUrl(name, repoUrl) {
24511
+ return this.patch(name, { repoUrl });
24821
24512
  }
24822
- /**
24823
- * 发布上线脚手架化 (spec 2026-06-03-app-builder-publish-scaffold-design §5.1.4):
24824
- * 写 publishJob 字段到 .clawd-project.json。仅承载 in-flight 状态 ——
24825
- * - daemon 收到第一个 ::stage::build marker 时写入
24826
- * - 每次 stage 切换更新 stage 字段(jobId / startedAt 不变)
24827
- * - 成功 / 失败 / dismiss 时调 clearPublishJob 清掉
24828
- * - 只有 daemon 崩才会留残骸
24829
- * 写入时保留所有其他字段(prodUrl 等不受影响)。
24830
- */
24831
- async setPublishJob(name, job) {
24832
- return await this.serializeWrite(name, async () => {
24833
- const list = await this.list();
24834
- const target = list.find((p) => p.name === name);
24835
- if (!target) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
24836
- const updated = { ...target, publishJob: job };
24837
- const validated = ProjectMetadataSchema.safeParse(updated);
24838
- if (!validated.success) {
24839
- throw new Error(`invalid publishJob: ${validated.error.message}`);
24840
- }
24841
- await import_node_fs.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
24842
- return validated.data;
24843
- });
24513
+ async setPublished(name, fields) {
24514
+ return this.patch(name, fields);
24844
24515
  }
24845
- /** 清掉 .clawd-project.json.publishJob 字段。其他字段保持原样。 */
24846
- async clearPublishJob(name) {
24847
- return await this.serializeWrite(name, async () => {
24848
- const list = await this.list();
24849
- const target = list.find((p) => p.name === name);
24850
- if (!target) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
24851
- if (!target.publishJob) return target;
24852
- const { publishJob: _drop, ...rest } = target;
24853
- void _drop;
24854
- const validated = ProjectMetadataSchema.safeParse(rest);
24855
- if (!validated.success) {
24856
- throw new Error(`failed to clear publishJob: ${validated.error.message}`);
24857
- }
24858
- await import_node_fs.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
24859
- return validated.data;
24860
- });
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");
24861
24529
  }
24862
24530
  };
24863
24531
 
24864
24532
  // src/app-builder-plugin/core.ts
24865
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
+ // src/deploy/deploy-file.ts
24543
+ var import_promises = __toESM(require("fs/promises"), 1);
24544
+ var import_node_path3 = __toESM(require("path"), 1);
24545
+
24546
+ // src/deploy/connectors.ts
24547
+ var import_node_fs2 = __toESM(require("fs"), 1);
24548
+ var import_node_path2 = __toESM(require("path"), 1);
24549
+
24550
+ // src/deploy/redact.ts
24551
+ var KNOWN_SECRET_SHAPES = [
24552
+ /LTAI[A-Za-z0-9]{6,}/,
24553
+ /eyJ[A-Za-z0-9_-]{10,}/,
24554
+ /sk-[A-Za-z0-9_-]{16,}/,
24555
+ /gh[pousr]_[A-Za-z0-9]{20,}/,
24556
+ /AKIA[A-Z0-9]{12,}/
24557
+ ];
24558
+ var BARE_SECRET = new RegExp(KNOWN_SECRET_SHAPES.map((re) => re.source).join("|"), "g");
24559
+
24560
+ // src/deploy/connectors.ts
24561
+ var CliConnectorFindingSchema = external_exports.object({
24562
+ name: ConnectorNameSchema,
24563
+ fields: external_exports.array(ConnectorFieldSchema).default([])
24564
+ });
24565
+
24566
+ // src/deploy/deploy-file.ts
24567
+ var DEPLOY_FILE = import_node_path3.default.join(".clawd", "deploy.json");
24568
+ var SCAN_FILE = import_node_path3.default.join(".clawd", "deploy-scan.json");
24569
+
24570
+ // src/deploy/templates.ts
24571
+ var CLAWOS_REPO = "ottin4ttc/clawos";
24572
+ var CLAWOS_REF = "release";
24573
+ var L = [
24574
+ "name: clawd deploy",
24575
+ "",
24576
+ "# \u7531 clawd \u751F\u6210\u3001\u7531 clawd \u7EF4\u62A4\uFF1A\u6BCF\u6B21\u300C\u53D1\u5E03\u300D\u90FD\u4F1A\u7528\u5F53\u524D\u6A21\u677F\u91CD\u5199\u8FD9\u4EFD\u6587\u4EF6\u518D\u63D0\u4EA4\uFF0C\u624B\u6539\u4F1A\u88AB\u76D6\u6389\u3002",
24577
+ "# \u4E09\u6BB5\uFF1Abuild \u6784 persona \u5C42\u955C\u50CF\u63A8 ACR \u2192 smoke \u5728\u96C6\u7FA4\u4E0A\u771F\u8D77\u4E00\u6B21 \u2192 deploy \u5728 gateway \u767B\u8BB0\u6210\u6B63\u5F0F\u3002",
24578
+ "# \u6784\u5EFA\u5668 / \u6D41\u6C34\u7EBF\u811A\u672C / \u968F\u884C\u8D44\u4EA7\u5728 ottin4ttc/clawos \u4ED3\u5E93\u91CC\uFF0C\u8FD9\u91CC checkout \u4E00\u4EFD\u6765\u7528\u3002",
24579
+ "#",
24580
+ "# \u8981\u7684\u7EC4\u7EC7\u7EA7 Secrets\uFF1AALI_DOCKER_USERNAME / ALI_DOCKER_PASSWORD\uFF08\u63A8 ACR\uFF09\u3001CLAWOS_READ_TOKEN",
24581
+ "#\uFF08\u8BFB clawos \u4ED3\u5E93\uFF09\u3001CLAWD_GATEWAY_ADMIN_KEY\uFF1B\u7EC4\u7EC7\u7EA7 Variable\uFF1ACLAWD_GATEWAY_URL\u3002",
24582
+ "# \u4ED3\u5E93\u7EA7\u540C\u540D\u914D\u7F6E\u4F1A\u76D6\u8FC7\u7EC4\u7EC7\u7EA7\u2014\u2014\u67D0\u4E2A persona \u8981\u6307\u5230\u53E6\u4E00\u53F0 gateway\uFF0C\u5728\u81EA\u5DF1\u4ED3\u5E93\u91CC\u914D\u5373\u53EF\u3002",
24583
+ "",
24584
+ "on:",
24585
+ " push:",
24586
+ " branches: [main]",
24587
+ " workflow_dispatch:",
24588
+ "",
24589
+ "concurrency:",
24590
+ " # \u540C\u4E00\u4E2A\u4ED3\u5E93\u7684\u53D1\u5E03\u6392\u961F\u8DD1\uFF0C\u4E0D\u53D6\u6D88\uFF1A\u4E0A\u4E00\u6B21\u53EF\u80FD\u5DF2\u7ECF\u5728\u767B\u8BB0\u6B63\u5F0F\u4E86",
24591
+ " group: persona-cloud-${{ github.repository }}",
24592
+ "",
24593
+ "env:",
24594
+ " REGISTRY: registry.cn-hangzhou.aliyuncs.com",
24595
+ " VPC_REGISTRY: registry-vpc.cn-hangzhou.aliyuncs.com",
24596
+ " IMAGE_NAME: ttc-prod/clawd-persona-cloud",
24597
+ ` CLAWOS_REPO: ${CLAWOS_REPO}`,
24598
+ ` CLAWOS_REF: ${CLAWOS_REF}`,
24599
+ "",
24600
+ "permissions:",
24601
+ " contents: read",
24602
+ "",
24603
+ "jobs:",
24604
+ " build:",
24605
+ // name 就是段 key:daemon 按 jobs API 回的 name 认段(那接口不返回 job 的 key)。
24606
+ // 中文说明归 clawd 的上云页面,这里保持机器好认。
24607
+ " name: build",
24608
+ " runs-on: ubuntu-latest",
24609
+ " timeout-minutes: 30",
24610
+ " outputs:",
24611
+ " tag: ${{ steps.plan.outputs.tag }}",
24612
+ " image: ${{ steps.plan.outputs.image }}",
24613
+ " persona_id: ${{ steps.plan.outputs.persona_id }}",
24614
+ " env_names: ${{ steps.plan.outputs.env_names }}",
24615
+ // 云上跑哪个模型 = 发布这一刻 persona 配的(build 段从仓库里的 .clawd/persona.json 读)。
24616
+ // smoke 与 deploy 都要:smoke 得用正式那份模型探活,否则验的不是将要上线的东西。
24617
+ " model: ${{ steps.plan.outputs.model }}",
24618
+ " effort: ${{ steps.plan.outputs.effort }}",
24619
+ // 装了哪些连接器(JSON 数组)。gateway 按它决定要不要为任务签工牌、把授权链接拼进 prompt
24620
+ //(design 2026-09-09-cloud-connector-credentials)。名字形状在 deploy-file.mjs 卡死成 [a-z0-9-],
24621
+ // 所以下面单引号包住直接拼进命令行是安全的。
24622
+ " connectors: ${{ steps.plan.outputs.connectors }}",
24623
+ " steps:",
24624
+ " - name: Checkout persona",
24625
+ " uses: actions/checkout@v4",
24626
+ " with:",
24627
+ " path: persona",
24628
+ "",
24629
+ " - name: Checkout clawos\uFF08\u6784\u5EFA\u5668 / \u6D41\u6C34\u7EBF\u811A\u672C / \u968F\u884C\u8D44\u4EA7\uFF09",
24630
+ " uses: actions/checkout@v4",
24631
+ " with:",
24632
+ " repository: ${{ env.CLAWOS_REPO }}",
24633
+ " ref: ${{ env.CLAWOS_REF }}",
24634
+ " token: ${{ secrets.CLAWOS_READ_TOKEN }}",
24635
+ " path: clawos",
24636
+ "",
24637
+ " - name: \u8BFB\u90E8\u7F72\u6587\u4EF6",
24638
+ " id: plan",
24639
+ " run: |",
24640
+ " node clawos/cloud-host/pipeline/deploy-file.mjs \\",
24641
+ " --persona-dir persona \\",
24642
+ " --repo-name '${{ github.event.repository.name }}' \\",
24643
+ " --sha '${{ github.sha }}' \\",
24644
+ ' --image-repo "$VPC_REGISTRY/$IMAGE_NAME" \\',
24645
+ ' --assets clawos/cloud-host/provision/manifests/assets.json >> "$GITHUB_OUTPUT"',
24646
+ "",
24647
+ " - name: \u51ED\u636E\u9F50\u4E0D\u9F50\uFF08\u7F3A\u54EA\u51E0\u4E2A\u540D\u5B57\u4E00\u6B21\u5217\u5168\uFF09",
24648
+ " env:",
24649
+ " CLAWD_SECRETS_JSON: ${{ toJSON(secrets) }}",
24650
+ " CLAWD_ENV_NAMES: ${{ steps.plan.outputs.env_names }}",
24651
+ " run: node clawos/cloud-host/pipeline/check-secrets.mjs",
24652
+ "",
24653
+ " - uses: pnpm/action-setup@v4",
24654
+ " if: steps.plan.outputs.asset_builds != ''",
24655
+ " with:",
24656
+ " version: 10",
24657
+ "",
24658
+ " - uses: actions/setup-node@v4",
24659
+ " if: steps.plan.outputs.asset_builds != ''",
24660
+ " with:",
24661
+ " node-version: 20",
24662
+ " cache: pnpm",
24663
+ " cache-dependency-path: clawos/clawd/pnpm-lock.yaml",
24664
+ "",
24665
+ " - name: \u6784\u968F\u884C\u8D44\u4EA7\uFF08\u6E05\u5355\u91CC\u70B9\u8FC7\u540D\u3001\u4E14\u662F\u4ED3\u5E93\u6784\u5EFA\u4EA7\u7269\u7684\u90A3\u4E9B\uFF09",
24666
+ " # \u547D\u4EE4\u6765\u81EA assets.json\uFF0C\u5B83\u81EA\u5DF1\u5E26\u88C5\u4F9D\u8D56\u90A3\u4E00\u6B65\u3002< /dev/null\uFF1A\u522B\u8BA9\u67D0\u6761\u547D\u4EE4\u628A here-string \u91CC\u5269\u4E0B\u7684\u884C\u5F53 stdin \u5403\u6389",
24667
+ " if: steps.plan.outputs.asset_builds != ''",
24668
+ " working-directory: clawos",
24669
+ " run: |",
24670
+ " set -e",
24671
+ " while IFS= read -r cmd; do",
24672
+ ' [ -n "$cmd" ] || continue',
24673
+ ' echo "$ $cmd"',
24674
+ ' bash -c "$cmd" < /dev/null',
24675
+ " done <<< '${{ steps.plan.outputs.asset_builds }}'",
24676
+ "",
24677
+ " - name: Login to ACR",
24678
+ " uses: aliyun/acr-login@v1",
24679
+ " with:",
24680
+ " login-server: ${{ env.REGISTRY }}",
24681
+ ' username: "${{ secrets.ALI_DOCKER_USERNAME }}"',
24682
+ ' password: "${{ secrets.ALI_DOCKER_PASSWORD }}"',
24683
+ "",
24684
+ " - name: \u6784 persona \u5C42",
24685
+ " # \u6784\u5EFA\u5668\u6574\u76EE\u5F55\u62F7\uFF0C`.git` \u4E0D\u662F\u4EBA\u683C\uFF0C\u5148\u5220\u3002\u5E95\u5EA7\u7528 :base \u79FB\u52A8 tag\u2014\u2014runner \u6BCF\u6B21\u90FD\u662F\u5E72\u51C0\u673A\u5668\u3001",
24686
+ " # \u5FC5\u7136\u62C9\u6700\u65B0\uFF1Bpersona \u5C42\u81EA\u5DF1\u7684 tag \u5E26 commit sha\u3001\u4E0D\u53EF\u53D8\uFF0C\u96C6\u7FA4\u6309\u5B83\u62C9\u6CA1\u6709\u6B67\u4E49\u3002",
24687
+ " run: |",
24688
+ " rm -rf persona/.git",
24689
+ " node clawos/cloud-host/provision/build-image.mjs \\",
24690
+ " --manifest persona/.clawd/deploy.json \\",
24691
+ ' --from "$REGISTRY/$IMAGE_NAME:base" \\',
24692
+ " --persona-dir persona \\",
24693
+ // persona 在镜像里落 /clawd/personas/<id>/(与本机同构),id 是 gateway 登记的名字;同 smoke / register 用的那个输出
24694
+ " --persona-id '${{ steps.plan.outputs.persona_id }}' \\",
24695
+ ' --tag "$REGISTRY/$IMAGE_NAME:${{ steps.plan.outputs.tag }}"',
24696
+ "",
24697
+ " - name: \u955C\u50CF\u65AD\u8A00\uFF08\u6E05\u5355\u91CC\u7684 CLI \u90FD\u5728\u3001bundle \u91CC persona \u5728\uFF09",
24698
+ " # /opt/versions.sh \u6BCF\u884C\u90FD\u4EE5 `|| echo` \u6536\u5C3E\u3001\u6C38\u8FDC exit 0\uFF0C\u53EA\u80FD\u9A8C\u8F93\u51FA\uFF1A\u6BB5\u6570\u8981\u7B49\u4E8E\u6E05\u5355\u91CC\u7684",
24699
+ " # CLI \u6570\uFF0C\u6BCF\u6BB5\u4E0B\u9762\u8981\u6709\u7248\u672C\u53F7\u3002\u955C\u50CF ENTRYPOINT \u662F sh -c\uFF0C\u547D\u4EE4\u8981\u4F5C\u4E3A\u4E00\u4E2A\u5B57\u7B26\u4E32\u4F20\u3002",
24700
+ " run: |",
24701
+ ' IMG="$REGISTRY/$IMAGE_NAME:${{ steps.plan.outputs.tag }}"',
24702
+ ` OUT=$(docker run --rm "$IMG" '/opt/versions.sh')`,
24703
+ ' echo "$OUT"',
24704
+ ` WANT=$(node -e 'const m=JSON.parse(require("fs").readFileSync("persona/.clawd/deploy.json","utf8"));console.log(((m.manifest??m).clis??[]).length)')`,
24705
+ ` echo "$OUT" | awk -v want="$WANT" '`,
24706
+ ' /^--- / { name=$2; if ((getline v) <= 0) v=""; n++',
24707
+ ' if (v !~ /[0-9]+\\.[0-9]+/) { printf " \u2717 %s \u6CA1\u62A5\u51FA\u7248\u672C\u53F7\uFF1A%s\\n", name, v > "/dev/stderr"; bad++ } }',
24708
+ ' END { if (n != want) { printf " \u2717 \u955C\u50CF\u91CC\u53EA\u6709 %d \u4E2A CLI \u6BB5\uFF0C\u6E05\u5355\u8981\u6C42 %d \u4E2A\\n", n, want > "/dev/stderr"; bad++ }',
24709
+ " exit bad ? 1 : 0 }'",
24710
+ ' echo " \u2713 $WANT \u4E2A CLI \u90FD\u5728\u4E14\u62A5\u5F97\u51FA\u7248\u672C"',
24711
+ ` docker run --rm "$IMG" 'test -d /clawd/personas/\${{ steps.plan.outputs.persona_id }}' \\`,
24712
+ ' || { echo " \u2717 /clawd/personas/${{ steps.plan.outputs.persona_id }} \u4E0D\u5728" >&2; exit 1; }',
24713
+ ' echo " \u2713 persona \u76EE\u5F55\u5728"',
24714
+ "",
24715
+ " - name: Push to ACR",
24716
+ ' run: docker push "$REGISTRY/$IMAGE_NAME:${{ steps.plan.outputs.tag }}"',
24717
+ "",
24718
+ " smoke:",
24719
+ " name: smoke",
24720
+ " needs: build",
24721
+ " runs-on: ubuntu-latest",
24722
+ " timeout-minutes: 20",
24723
+ " steps:",
24724
+ " - name: Checkout clawos\uFF08\u53EA\u8981\u6D41\u6C34\u7EBF\u811A\u672C\uFF09",
24725
+ " uses: actions/checkout@v4",
24726
+ " with:",
24727
+ " repository: ${{ env.CLAWOS_REPO }}",
24728
+ " ref: ${{ env.CLAWOS_REF }}",
24729
+ " token: ${{ secrets.CLAWOS_READ_TOKEN }}",
24730
+ " path: clawos",
24731
+ " sparse-checkout: cloud-host/pipeline",
24732
+ "",
24733
+ " - name: \u4E34\u65F6\u767B\u8BB0 \u2192 \u8D77\u4E00\u6B21 Pod \u2192 \u64A4\u9500",
24734
+ " # \u767B\u8BB0\u6B63\u5F0F\u53EA\u662F\u5F80 gateway \u5199\u4E00\u884C\uFF0Cgateway \u4E0D\u78B0\u955C\u50CF\uFF1B\u96C6\u7FA4\u62C9\u955C\u50CF\u53D1\u751F\u5728\u7B2C\u4E00\u4E2A\u4EFB\u52A1\u5230\u6765\u65F6\u3002",
24735
+ " # \u6CA1\u6709\u8FD9\u4E00\u6B65\uFF0C\u6D41\u6C34\u7EBF\u7EFF\u4E86\u53EA\u4EE3\u8868\u300C\u767B\u8BB0\u4E86\u300D\u3002",
24736
+ " env:",
24737
+ " CLAWD_GATEWAY_URL: ${{ vars.CLAWD_GATEWAY_URL }}",
24738
+ " CLAWD_GATEWAY_ADMIN_KEY: ${{ secrets.CLAWD_GATEWAY_ADMIN_KEY }}",
24739
+ " CLAWD_SECRETS_JSON: ${{ toJSON(secrets) }}",
24740
+ " CLAWD_ENV_NAMES: ${{ needs.build.outputs.env_names }}",
24741
+ " run: |",
24742
+ " node clawos/cloud-host/pipeline/smoke.mjs \\",
24743
+ " --image '${{ needs.build.outputs.image }}' \\",
24744
+ " --persona-id '${{ needs.build.outputs.persona_id }}' \\",
24745
+ " --model '${{ needs.build.outputs.model }}' \\",
24746
+ " --effort '${{ needs.build.outputs.effort }}' \\",
24747
+ " --connectors '${{ needs.build.outputs.connectors }}' \\",
24748
+ " --run-number '${{ github.run_number }}'",
24749
+ "",
24750
+ " deploy:",
24751
+ " name: deploy",
24752
+ " needs: [build, smoke]",
24753
+ " runs-on: ubuntu-latest",
24754
+ " # \u4ED3\u5E93\u914D\u4E86 production \u73AF\u5883\u7684\u300C\u987B\u5BA1\u6838\u4EBA\u6279\u51C6\u300D\uFF0Cjob \u5C31\u505C\u5728\u8FD9\u91CC\u7B49\u4EBA\u70B9\uFF1B\u6CA1\u914D\u5C31\u76F4\u63A5\u8FC7\u3002",
24755
+ " environment: production",
24756
+ " steps:",
24757
+ " - name: Checkout clawos\uFF08\u53EA\u8981\u6D41\u6C34\u7EBF\u811A\u672C\uFF09",
24758
+ " uses: actions/checkout@v4",
24759
+ " with:",
24760
+ " repository: ${{ env.CLAWOS_REPO }}",
24761
+ " ref: ${{ env.CLAWOS_REF }}",
24762
+ " token: ${{ secrets.CLAWOS_READ_TOKEN }}",
24763
+ " path: clawos",
24764
+ " sparse-checkout: cloud-host/pipeline",
24765
+ "",
24766
+ " - name: \u767B\u8BB0\u6B63\u5F0F",
24767
+ " env:",
24768
+ " CLAWD_GATEWAY_URL: ${{ vars.CLAWD_GATEWAY_URL }}",
24769
+ " CLAWD_GATEWAY_ADMIN_KEY: ${{ secrets.CLAWD_GATEWAY_ADMIN_KEY }}",
24770
+ " CLAWD_SECRETS_JSON: ${{ toJSON(secrets) }}",
24771
+ " CLAWD_ENV_NAMES: ${{ needs.build.outputs.env_names }}",
24772
+ " run: |",
24773
+ " node clawos/cloud-host/pipeline/register.mjs \\",
24774
+ " --image '${{ needs.build.outputs.image }}' \\",
24775
+ " --persona-id '${{ needs.build.outputs.persona_id }}' \\",
24776
+ " --model '${{ needs.build.outputs.model }}' \\",
24777
+ " --effort '${{ needs.build.outputs.effort }}' \\",
24778
+ " --connectors '${{ needs.build.outputs.connectors }}'",
24779
+ ""
24780
+ ];
24781
+ var WORKFLOW_TEMPLATE = L.join("\n");
24866
24782
 
24867
- // src/app-builder-plugin/publish-registry.ts
24868
- var import_node_crypto = require("crypto");
24869
- var PublishJobRegistry = class {
24870
- jobs = /* @__PURE__ */ new Map();
24871
- has(name) {
24872
- return this.jobs.has(name);
24873
- }
24874
- get(name) {
24875
- return this.jobs.get(name);
24876
- }
24877
- /**
24878
- * args.jobId 仅用于测试注入;生产代码不传,registry 自己生成。
24879
- * args.now 仅用于测试注入;生产代码不传,用 Date.now()。
24880
- */
24881
- create(args) {
24882
- if (this.jobs.has(args.name)) {
24883
- throw new Error(`already publishing: ${args.name}`);
24884
- }
24885
- const jobId = args.jobId ?? `job-${(0, import_node_crypto.randomUUID)()}`;
24886
- this.jobs.set(args.name, {
24887
- jobId,
24888
- name: args.name,
24889
- stage: "build",
24890
- sessionId: args.sessionId,
24891
- childProc: args.childProc,
24892
- dismissed: false,
24893
- sawAnyStage: false,
24894
- startedAt: args.now ?? Date.now()
24783
+ // src/deploy/repo.ts
24784
+ var DEPLOY_FILE_REL = DEPLOY_FILE.split(import_node_path4.default.sep).join("/");
24785
+ var DEFAULT_TIMEOUT_MS = 6e4;
24786
+ var runCommand = (cmd, args, opts = {}) => new Promise((resolve) => {
24787
+ const child = (0, import_node_child_process2.spawn)(cmd, args, {
24788
+ cwd: opts.cwd,
24789
+ stdio: [opts.stdin === void 0 ? "ignore" : "pipe", "pipe", "pipe"]
24790
+ });
24791
+ let stdout = "";
24792
+ let stderr = "";
24793
+ child.stdout?.on("data", (d) => {
24794
+ stdout += d.toString("utf8");
24795
+ });
24796
+ child.stderr?.on("data", (d) => {
24797
+ stderr += d.toString("utf8");
24798
+ });
24799
+ const timer = setTimeout(() => {
24800
+ child.kill("SIGKILL");
24801
+ stderr += `
24802
+ [clawd] \u8D85\u65F6\uFF08>${Math.round((opts.timeoutMs ?? DEFAULT_TIMEOUT_MS) / 1e3)}s\uFF09\uFF0C\u5DF2\u7ECF\u628A\u5B83\u6390\u4E86
24803
+ `;
24804
+ }, opts.timeoutMs ?? DEFAULT_TIMEOUT_MS);
24805
+ child.on("error", (err) => {
24806
+ clearTimeout(timer);
24807
+ resolve({ code: null, stdout, stderr, spawnError: err.message });
24808
+ });
24809
+ child.on("close", (code) => {
24810
+ clearTimeout(timer);
24811
+ resolve({ code, stdout, stderr });
24812
+ });
24813
+ if (opts.stdin !== void 0 && child.stdin) {
24814
+ child.stdin.on("error", () => {
24895
24815
  });
24896
- return jobId;
24897
- }
24898
- updateStage(name, stage) {
24899
- const job = this.jobs.get(name);
24900
- if (!job) return;
24901
- job.stage = stage;
24902
- job.sawAnyStage = true;
24903
- }
24904
- markDismissed(name) {
24905
- const job = this.jobs.get(name);
24906
- if (!job) return;
24907
- job.dismissed = true;
24816
+ child.stdin.end(opts.stdin);
24908
24817
  }
24909
- remove(name) {
24910
- const job = this.jobs.get(name);
24911
- if (!job) return void 0;
24912
- this.jobs.delete(name);
24913
- return { sessionId: job.sessionId };
24818
+ });
24819
+ var RepoError = class extends Error {
24820
+ };
24821
+ var tail = (s, n = 400) => s.trim().slice(-n);
24822
+ async function git(exec, dir, args, opts) {
24823
+ return exec("git", args, { cwd: dir, ...opts });
24824
+ }
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
+ };
24914
24838
  }
24839
+ return { ok: true };
24840
+ }
24841
+ async function isToplevel(exec, dir) {
24842
+ const r = await git(exec, dir, ["rev-parse", "--show-toplevel"]);
24843
+ if (r.spawnError || r.code !== 0) return false;
24844
+ return samePath(dir, r.stdout.trim());
24845
+ }
24846
+ async function samePath(a, b) {
24847
+ const real = async (x) => {
24848
+ try {
24849
+ return await import_promises2.default.realpath(x);
24850
+ } catch {
24851
+ return import_node_path4.default.resolve(x);
24852
+ }
24853
+ };
24854
+ return await real(a) === await real(b);
24855
+ }
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;
24915
24882
  };
24916
-
24917
- // src/app-builder-plugin/publish-job-runner.ts
24918
- var import_node_child_process2 = require("child_process");
24919
- var import_node_fs2 = require("fs");
24920
- var import_node_path2 = require("path");
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
+ async function workTreeState(exec, dir) {
24907
+ if (!await isToplevel(exec, dir)) return { isRepo: false };
24908
+ const status = await git(exec, dir, ["status", "--porcelain"]);
24909
+ const dirty = status.spawnError !== void 0 || status.code !== 0 || status.stdout.trim().length > 0;
24910
+ const head = await git(exec, dir, ["rev-parse", "HEAD"]);
24911
+ return { isRepo: true, dirty, head: head.code === 0 ? head.stdout.trim() || null : null };
24912
+ }
24921
24913
 
24922
24914
  // src/app-builder-plugin/publish-stage-parser.ts
24923
24915
  var STAGE_RE = /^\s*::stage::(build|deploy|verify)\s*$/;
24924
24916
  var PROD_URL_RE = /^\s*::prod-url::(\S+)\s*$/;
24925
- var URL_RE = /\bhttps?:\/\/[^\s]+/;
24926
24917
  function parsePublishLine(line) {
24927
24918
  const stageMatch = STAGE_RE.exec(line);
24928
24919
  if (stageMatch) return { kind: "stage", stage: stageMatch[1] };
@@ -24930,10 +24921,6 @@ function parsePublishLine(line) {
24930
24921
  if (urlMatch) return { kind: "prod-url", url: urlMatch[1] };
24931
24922
  return null;
24932
24923
  }
24933
- function extractProdUrl(text) {
24934
- const m = URL_RE.exec(text);
24935
- return m ? m[0] : null;
24936
- }
24937
24924
  function tailStderrLines(buf, n) {
24938
24925
  if (!buf) return "";
24939
24926
  const lines = buf.split("\n");
@@ -24941,347 +24928,148 @@ function tailStderrLines(buf, n) {
24941
24928
  return lines.slice(lines.length - n).join("\n");
24942
24929
  }
24943
24930
 
24944
- // src/app-builder-plugin/publish-job-runner.ts
24945
- async function startPublishJob(deps, args) {
24946
- const { registry: registry2, projectDir } = deps;
24947
- const spawn2 = deps.spawnImpl ?? import_node_child_process2.spawn;
24948
- if (registry2.has(args.name)) {
24949
- return { jobId: registry2.get(args.name).jobId, status: "already-publishing" };
24950
- }
24951
- const projDir = projectDir(args.name);
24952
- const logPath = (0, import_node_path2.join)(projDir, ".publish.log");
24953
- let logStream = null;
24931
+ // src/app-builder-plugin/core.ts
24932
+ var errMsg = (err) => err instanceof Error ? err.message : String(err);
24933
+ async function createProject(args) {
24934
+ 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);
24939
+ const dir = store.projectDir(name);
24940
+ let repo2;
24954
24941
  try {
24955
- logStream = (0, import_node_fs2.createWriteStream)(logPath, { flags: "w" });
24956
- } catch {
24957
- logStream = null;
24958
- }
24959
- const child = spawn2("bash", [args.scriptPath, projDir, args.personaRoot ?? ""], {
24960
- cwd: projDir,
24961
- // 宿主给的 env 覆盖在 process.env 之上:daemon 进程本身没有这些变量(它们只在这一个
24962
- // 子进程里活着)。云上宿主不传 extraEnv,凭据本来就在容器 env 里。
24963
- // PATH 排在最后(与 ProjectStore.scaffold 同款):extraEnv 里若混进一条名叫 PATH 的条目,
24964
- // 顶掉 PATH 会让脚本以「命令找不到」的形态崩在离现场很远的地方。
24965
- env: { ...process.env, ...args.extraEnv ?? {}, PATH: process.env.PATH ?? "" },
24966
- stdio: ["ignore", "pipe", "pipe"]
24967
- });
24968
- const jobId = registry2.create({
24969
- name: args.name,
24970
- sessionId: args.sessionId,
24971
- childProc: child
24972
- });
24973
- deps.logger?.info("app-builder.publish.spawned", {
24974
- name: args.name,
24975
- jobId,
24976
- pid: child.pid ?? null,
24977
- scriptPath: args.scriptPath,
24978
- logPath
24979
- });
24980
- child.on("error", (err) => {
24981
- deps.logger?.warn("app-builder.publish.spawn-error", {
24982
- name: args.name,
24983
- jobId,
24984
- error: err instanceof Error ? err.message : String(err)
24985
- });
24986
- void handleExit(deps, {
24987
- name: args.name,
24988
- jobId,
24989
- code: null,
24990
- signal: null,
24991
- urlCandidate: null,
24992
- stderrTail: err instanceof Error ? err.message : String(err),
24993
- logPath
24994
- });
24995
- });
24996
- let urlFromMarker = null;
24997
- let urlFallback = null;
24998
- let stdoutBuf = "";
24999
- child.stdout?.on("data", (chunk) => {
25000
- if (logStream) logStream.write(chunk);
25001
- stdoutBuf += chunk.toString();
25002
- let nlIdx;
25003
- while ((nlIdx = stdoutBuf.indexOf("\n")) >= 0) {
25004
- const line = stdoutBuf.slice(0, nlIdx);
25005
- stdoutBuf = stdoutBuf.slice(nlIdx + 1);
25006
- handleStdoutLine(deps, args.name, jobId, line);
25007
- const parsedForUrl = parsePublishLine(line);
25008
- if (parsedForUrl?.kind === "prod-url") urlFromMarker = parsedForUrl.url;
25009
- const url = extractProdUrl(line);
25010
- if (url && !urlFallback) urlFallback = url;
25011
- }
25012
- });
25013
- let stderrBuf = "";
25014
- child.stderr?.on("data", (chunk) => {
25015
- if (logStream) logStream.write(chunk);
25016
- stderrBuf += chunk.toString();
25017
- });
25018
- child.on("exit", (code, signal) => {
25019
- if (logStream) logStream.end();
25020
- const urlCandidate = urlFromMarker ?? urlFallback;
25021
- deps.logger?.info("app-builder.publish.child-exit", {
25022
- name: args.name,
25023
- jobId,
25024
- code,
25025
- signal,
25026
- hadUrl: !!urlCandidate,
25027
- urlSource: urlFromMarker ? "marker" : urlFallback ? "fallback" : "none",
25028
- stderrBytes: stderrBuf.length
25029
- });
25030
- void handleExit(deps, {
25031
- name: args.name,
25032
- jobId,
25033
- code,
25034
- signal,
25035
- urlCandidate,
25036
- stderrTail: tailStderrLines(stderrBuf, 20),
25037
- logPath
25038
- });
25039
- });
25040
- return { jobId, status: "started" };
25041
- }
25042
- function handleStdoutLine(deps, name, jobId, line) {
25043
- const parsed = parsePublishLine(line);
25044
- if (parsed?.kind !== "stage") return;
25045
- advanceStage(deps, name, jobId, parsed.stage);
25046
- }
25047
- function advanceStage(deps, name, jobId, next) {
25048
- const job = deps.registry.get(name);
25049
- if (!job) return;
25050
- const prev = job.stage;
25051
- const wasFirstStage = !job.sawAnyStage;
25052
- deps.logger?.info("app-builder.publish.stage-advance", {
25053
- name,
25054
- jobId,
25055
- prev: wasFirstStage ? null : prev,
25056
- next,
25057
- firstStage: wasFirstStage
25058
- });
25059
- if (wasFirstStage) {
25060
- deps.registry.updateStage(name, next);
25061
- void deps.setPublishJob(name, {
25062
- jobId,
25063
- stage: next,
25064
- startedAt: job.startedAt
25065
- });
25066
- deps.broadcast({
25067
- type: "appBuilder:publish-progress",
25068
- name,
25069
- jobId,
25070
- stage: next,
25071
- status: "started"
24942
+ const scaffold = await store.scaffold(name, args.templateSrcDir, args.scaffoldScript, args.extraEnv);
24943
+ logger2?.info("app-builder.scaffold.done", { name, stdout: scaffold.stdout.slice(0, 200) });
24944
+ const slug = await readSlug(dir);
24945
+ 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 };
24952
+ } catch (err) {
24953
+ const message = errMsg(err);
24954
+ logger2?.warn("app-builder.create.failed", { name, error: message });
24955
+ await store.delete(name).catch((e) => {
24956
+ logger2?.warn("app-builder.create.rollback-failed", { name, error: errMsg(e) });
25072
24957
  });
25073
- return;
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}`);
25074
24960
  }
25075
- if (prev === next) {
25076
- return;
25077
- }
25078
- deps.broadcast({
25079
- type: "appBuilder:publish-progress",
25080
- name,
25081
- jobId,
25082
- stage: prev,
25083
- status: "completed"
25084
- });
25085
- deps.registry.updateStage(name, next);
25086
- void deps.setPublishJob(name, {
25087
- jobId,
25088
- stage: next,
25089
- startedAt: job.startedAt
25090
- });
25091
- deps.broadcast({
25092
- type: "appBuilder:publish-progress",
25093
- name,
25094
- jobId,
25095
- stage: next,
25096
- status: "started"
25097
- });
25098
24961
  }
25099
- async function handleExit(deps, args) {
25100
- const job = deps.registry.get(args.name);
25101
- if (args.signal === "SIGTERM" && job?.dismissed) {
25102
- try {
25103
- await deps.clearPublishJob(args.name);
25104
- } catch (err) {
25105
- console.error("[publish] clearPublishJob failed (dismissed branch)", err);
25106
- }
25107
- deps.registry.remove(args.name);
25108
- deps.onSettled?.({ kind: "dismissed" });
25109
- return;
25110
- }
25111
- if (args.code === 0 && args.urlCandidate) {
25112
- if (job?.sawAnyStage) {
25113
- deps.broadcast({
25114
- type: "appBuilder:publish-progress",
25115
- name: args.name,
25116
- jobId: args.jobId,
25117
- stage: job.stage,
25118
- status: "completed"
25119
- });
25120
- }
25121
- try {
25122
- await deps.setProdUrl(args.name, args.urlCandidate);
25123
- } catch (err) {
25124
- console.error("[publish] setProdUrl failed; falling through to failure branch", err);
25125
- await emitFailure(deps, args, "\u8C03 setProdUrl \u5931\u8D25\uFF1A" + (err instanceof Error ? err.message : String(err)));
25126
- return;
25127
- }
25128
- try {
25129
- await deps.clearPublishJob(args.name);
25130
- } catch (err) {
25131
- console.error("[publish] clearPublishJob failed (success branch)", err);
25132
- }
25133
- deps.registry.remove(args.name);
25134
- deps.onSettled?.({ kind: "published", url: args.urlCandidate });
25135
- return;
24962
+ async function readSlug(projectDir) {
24963
+ let conf;
24964
+ try {
24965
+ conf = await import_node_fs3.promises.readFile((0, import_node_path5.join)(projectDir, "ext.conf"), "utf8");
24966
+ } catch {
24967
+ return null;
25136
24968
  }
25137
- await emitFailure(deps, args, buildErrorSummary(args));
24969
+ const m = /^SLUG=([A-Za-z0-9]+)\s*$/m.exec(conf);
24970
+ return m ? m[1] : null;
24971
+ }
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() : "";
25138
24975
  }
25139
- async function emitFailure(deps, args, errorSummary) {
25140
- const job = deps.registry.get(args.name);
25141
- const failStage = job?.sawAnyStage ? job.stage : "unknown";
25142
- deps.logger?.warn("app-builder.publish.failed", {
25143
- name: args.name,
25144
- jobId: args.jobId,
25145
- stage: failStage,
25146
- errorSummary: errorSummary.slice(0, 500)
24976
+ var PUBLISH_TIMEOUT_MS = 15 * 6e4;
24977
+ async function runPublish(args) {
24978
+ const { store, name, logger: logger2 } = args;
24979
+ const exec = args.exec ?? runCommand;
24980
+ const spawnFn = args.spawnImpl ?? import_node_child_process3.spawn;
24981
+ const timeoutMs = args.timeoutMs ?? PUBLISH_TIMEOUT_MS;
24982
+ if (!await store.get(name)) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
24983
+ const dir = store.projectDir(name);
24984
+ const tree = await workTreeState(exec, dir);
24985
+ if (tree.isRepo && tree.dirty) {
24986
+ 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
+ }
24988
+ const head = tree.isRepo ? tree.head : null;
24989
+ const logPath = (0, import_node_path5.join)(dir, ".publish.log");
24990
+ const log2 = (0, import_node_fs3.createWriteStream)(logPath, { flags: "w" });
24991
+ log2.on("error", () => {
25147
24992
  });
25148
- try {
25149
- await deps.clearPublishJob(args.name);
25150
- } catch (err) {
25151
- deps.logger?.warn("app-builder.publish.clear-field-failed", {
25152
- name: args.name,
25153
- jobId: args.jobId,
25154
- error: err instanceof Error ? err.message : String(err)
24993
+ logger2?.info("app-builder.publish.start", { name, scriptPath: args.scriptPath, head });
24994
+ return await new Promise((resolve, reject) => {
24995
+ const child = spawnFn("bash", [args.scriptPath, dir, args.personaRoot], {
24996
+ cwd: dir,
24997
+ // 凭据就在本进程 env 里(宿主交进来的),照单全给脚本
24998
+ env: process.env,
24999
+ stdio: ["ignore", "pipe", "pipe"]
25155
25000
  });
25156
- }
25157
- const removed = deps.registry.remove(args.name);
25158
- deps.broadcast({
25159
- type: "appBuilder:publish-failed",
25160
- name: args.name,
25161
- jobId: args.jobId,
25162
- stage: failStage,
25163
- errorSummary
25164
- });
25165
- if (removed) {
25166
- const msg = formatFailureMessage(failStage, errorSummary, args.logPath);
25167
- try {
25168
- await deps.injectUserText(removed.sessionId, msg);
25169
- deps.logger?.info("app-builder.publish.failure-injected-to-chat", {
25170
- name: args.name,
25171
- jobId: args.jobId,
25172
- sessionId: removed.sessionId
25173
- });
25174
- } catch (err) {
25175
- deps.logger?.warn("app-builder.publish.inject-user-text-failed", {
25176
- name: args.name,
25177
- jobId: args.jobId,
25178
- sessionId: removed.sessionId,
25179
- error: err instanceof Error ? err.message : String(err)
25180
- });
25181
- }
25182
- } else {
25183
- deps.logger?.warn("app-builder.publish.no-registry-entry-on-failure", {
25184
- name: args.name,
25185
- jobId: args.jobId
25001
+ let stdout = "";
25002
+ let stderr = "";
25003
+ let partial2 = "";
25004
+ let stage = "unknown";
25005
+ let prodUrl = null;
25006
+ let timedOut = false;
25007
+ const feed = (line) => {
25008
+ const p = parsePublishLine(line);
25009
+ if (p?.kind === "stage") stage = p.stage;
25010
+ else if (p?.kind === "prod-url") prodUrl = p.url;
25011
+ };
25012
+ child.stdout?.on("data", (d) => {
25013
+ const s = d.toString("utf8");
25014
+ stdout += s;
25015
+ log2.write(s);
25016
+ partial2 += s;
25017
+ const lines = partial2.split("\n");
25018
+ partial2 = lines.pop() ?? "";
25019
+ for (const l of lines) feed(l);
25186
25020
  });
25187
- }
25188
- deps.onSettled?.({ kind: "failed", stage: failStage, errorSummary });
25189
- }
25190
- function buildErrorSummary(args) {
25191
- if (args.signal) {
25192
- return `publish.sh \u88AB\u4E2D\u65AD\uFF08signal: ${args.signal}\uFF09`;
25193
- }
25194
- if (args.code === 0 && !args.urlCandidate) {
25195
- return "publish.sh \u6210\u529F\u9000\u51FA\u4F46\u672A\u8F93\u51FA\u516C\u7F51 URL";
25196
- }
25197
- return args.stderrTail || `publish.sh \u5F02\u5E38\u9000\u51FA\uFF08exit code: ${args.code}\uFF09`;
25198
- }
25199
- function formatFailureMessage(stage, errorSummary, logPath) {
25200
- return `\u53D1\u5E03\u5931\u8D25\uFF1A[${stage}] \u9636\u6BB5
25201
- ${errorSummary}
25202
-
25203
- \u5B8C\u6574\u65E5\u5FD7\uFF1A${logPath}
25204
- \u8BF7\u4F60\u63A5\u7BA1\u5B8C\u6210\u672C\u6B21\u53D1\u5E03\uFF1A\u5148\u6839\u636E\u65E5\u5FD7\u4FEE\u590D\u95EE\u9898\uFF0C\u7136\u540E\u91CD\u8DD1 publish.sh \u76F4\u5230\u4E0A\u7EBF\u6210\u529F\uFF0C\u6700\u540E\u7528 appBuilder.setProdUrl \u5199\u5165\u516C\u7F51 URL\u3002`;
25205
- }
25206
-
25207
- // src/app-builder-plugin/core.ts
25208
- function createPublishService() {
25209
- const registry2 = new PublishJobRegistry();
25210
- return {
25211
- inFlightJobId(name) {
25212
- return registry2.get(name)?.jobId ?? null;
25213
- },
25214
- async start(args) {
25215
- const { store, hooks, logger } = args;
25216
- const changed = async (name) => {
25217
- await hooks?.onProjectChanged?.(name);
25218
- };
25219
- return await startPublishJob(
25220
- {
25221
- registry: registry2,
25222
- setPublishJob: async (name, job) => {
25223
- await store.setPublishJob(name, job);
25224
- await changed(name);
25225
- },
25226
- clearPublishJob: async (name) => {
25227
- await store.clearPublishJob(name);
25228
- await changed(name);
25229
- },
25230
- setProdUrl: async (name, url) => {
25231
- await store.setProdUrl(name, url);
25232
- await changed(name);
25233
- },
25234
- broadcast: (frame) => hooks?.broadcast?.(frame),
25235
- injectUserText: async (sessionId, text) => {
25236
- await hooks?.injectUserText?.(sessionId, text);
25237
- },
25238
- projectDir: (name) => store.projectDir(name),
25239
- ...hooks?.onSettled ? { onSettled: hooks.onSettled } : {},
25240
- ...logger ? { logger } : {}
25241
- },
25242
- {
25243
- name: args.name,
25244
- sessionId: args.sessionId,
25245
- scriptPath: args.scriptPath,
25246
- personaRoot: args.personaRoot,
25247
- ...args.extraEnv ? { extraEnv: args.extraEnv } : {}
25021
+ child.stderr?.on("data", (d) => {
25022
+ const s = d.toString("utf8");
25023
+ stderr += s;
25024
+ log2.write(s);
25025
+ });
25026
+ const timeoutSummary = `publish.sh \u8D85\u8FC7 ${Math.round(timeoutMs / 6e4)} \u5206\u949F\u6CA1\u7ED3\u675F\uFF0C\u5DF2\u7ECF\u628A\u5B83\u6390\u4E86`;
25027
+ const timer = setTimeout(() => {
25028
+ timedOut = true;
25029
+ child.kill("SIGKILL");
25030
+ log2.end();
25031
+ logger2?.warn("app-builder.publish.timeout", { name, stage });
25032
+ resolve({ ok: false, stage, errorSummary: timeoutSummary, logPath });
25033
+ }, timeoutMs);
25034
+ child.on("error", (err) => {
25035
+ clearTimeout(timer);
25036
+ log2.end();
25037
+ reject(new Error(`publish spawn failed: ${err.message}`));
25038
+ });
25039
+ child.on("close", (code, signal) => {
25040
+ clearTimeout(timer);
25041
+ if (timedOut) return;
25042
+ if (partial2) feed(partial2);
25043
+ log2.end();
25044
+ void (async () => {
25045
+ if (code === 0 && prodUrl) {
25046
+ const publishedAt = (/* @__PURE__ */ new Date()).toISOString();
25047
+ try {
25048
+ await store.setPublished(name, {
25049
+ prodUrl,
25050
+ publishedAt,
25051
+ ...head ? { publishedCommit: head } : {}
25052
+ });
25053
+ } catch (err) {
25054
+ reject(new Error(`\u53D1\u5E03\u6210\u529F\u4F46\u5199\u56DE\u5143\u6570\u636E\u5931\u8D25\uFF1A${errMsg(err)}`));
25055
+ return;
25056
+ }
25057
+ logger2?.info("app-builder.publish.done", { name, prodUrl, head });
25058
+ resolve({ ok: true, prodUrl, commit: head, publishedAt, logPath });
25059
+ return;
25248
25060
  }
25249
- );
25250
- },
25251
- dismiss(name) {
25252
- const job = registry2.get(name);
25253
- if (!job) return false;
25254
- registry2.markDismissed(name);
25255
- job.childProc.kill("SIGTERM");
25256
- return true;
25257
- }
25258
- };
25259
- }
25260
- async function createProjectOnDisk(args) {
25261
- const { store, name, templateSrcDir, scaffoldScript, logger } = args;
25262
- const project = await store.create(name, args.reservedPorts);
25263
- try {
25264
- const result = await store.scaffold(name, templateSrcDir, scaffoldScript, args.extraEnv);
25265
- logger?.info("app-builder.scaffold.done", { name, stdout: result.stdout.slice(0, 200) });
25266
- } catch (err) {
25267
- const message = err instanceof Error ? err.message : String(err);
25268
- logger?.warn("app-builder.scaffold.failed", { name, error: message });
25269
- await store.delete(name).catch((delErr) => {
25270
- logger?.warn("app-builder.scaffold.rollback-failed", {
25271
- name,
25272
- error: delErr instanceof Error ? delErr.message : String(delErr)
25273
- });
25061
+ const errorSummary = code === 0 ? "\u811A\u672C\u9000\u51FA 0 \u4F46\u6CA1\u6709\u8F93\u51FA ::prod-url:: \u6807\u8BB0\uFF0C\u62FF\u4E0D\u5230\u516C\u7F51\u5730\u5740" : tailStderrLines(stderr, 20) || tailStderrLines(stdout, 20) || `exit ${code ?? signal}`;
25062
+ logger2?.warn("app-builder.publish.failed", { name, stage, code, signal, timedOut });
25063
+ resolve({ ok: false, stage, errorSummary, logPath });
25064
+ })();
25274
25065
  });
25275
- throw new Error(`scaffold failed: ${message}`);
25276
- }
25277
- return { project, projectDir: store.projectDir(name) };
25066
+ });
25278
25067
  }
25279
25068
  var REMOVE_TIMEOUT_MS = 10 * 6e4;
25280
25069
  async function runRemoveExtension(args) {
25281
25070
  const spawnFn = args.spawnImpl ?? import_node_child_process3.spawn;
25282
25071
  return await new Promise((resolve, reject) => {
25283
25072
  const child = spawnFn("bash", [args.script, args.projectDir, args.personaRoot], {
25284
- // 凭据在 process.env 里(gateway → MCP server 的 per-server env → 本进程)
25285
25073
  env: process.env,
25286
25074
  stdio: ["ignore", "pipe", "pipe"]
25287
25075
  });
@@ -25301,7 +25089,7 @@ async function runRemoveExtension(args) {
25301
25089
  clearTimeout(timer);
25302
25090
  reject(new Error(`remove-extension spawn failed: ${err.message}`));
25303
25091
  });
25304
- child.on("exit", (code) => {
25092
+ child.on("close", (code) => {
25305
25093
  clearTimeout(timer);
25306
25094
  args.logger?.info("app-builder.remove.exit", { projectDir: args.projectDir, code });
25307
25095
  resolve({ code, stdout, stderr });
@@ -25316,7 +25104,7 @@ function log(msg, info) {
25316
25104
  `;
25317
25105
  if (LOG_PATH) {
25318
25106
  try {
25319
- import_node_fs3.default.appendFileSync(LOG_PATH, line);
25107
+ import_node_fs4.default.appendFileSync(LOG_PATH, line);
25320
25108
  return;
25321
25109
  } catch {
25322
25110
  }
@@ -25326,282 +25114,145 @@ function log(msg, info) {
25326
25114
  } catch {
25327
25115
  }
25328
25116
  }
25117
+ var logger = { info: log, warn: log };
25329
25118
  var ok = (payload) => ({
25330
- content: [{ type: "text", text: typeof payload === "string" ? payload : JSON.stringify(payload) }]
25119
+ content: [{ type: "text", text: typeof payload === "string" ? payload : JSON.stringify(payload, null, 2) }]
25331
25120
  });
25332
25121
  var fail = (text) => ({ content: [{ type: "text", text }], isError: true });
25122
+ var errMsg2 = (err) => err instanceof Error ? err.message : String(err);
25123
+ var ENV_NAMES = {
25124
+ root: "CLAWD_APP_BUILDER_ROOT",
25125
+ personaRoot: "CLAWD_APP_BUILDER_PERSONA_ROOT",
25126
+ templateDir: "CLAWD_APP_BUILDER_TEMPLATE_DIR",
25127
+ scaffoldScript: "CLAWD_APP_BUILDER_SCAFFOLD_SCRIPT",
25128
+ publishScript: "CLAWD_APP_BUILDER_PUBLISH_SCRIPT",
25129
+ removeScript: "CLAWD_APP_BUILDER_REMOVE_SCRIPT"
25130
+ };
25333
25131
  function readEnv(env = process.env) {
25334
- return {
25335
- ...env.CLAWD_DAEMON_URL ? { daemonUrl: env.CLAWD_DAEMON_URL } : {},
25336
- ...env.CLAWD_DAEMON_TOKEN ? { daemonToken: env.CLAWD_DAEMON_TOKEN } : {},
25337
- ...env.CLAWD_SESSION_ID ? { sessionId: env.CLAWD_SESSION_ID } : {},
25338
- ...env.CLAWD_PERSONA_ID ? { personaId: env.CLAWD_PERSONA_ID } : {},
25339
- root: env.CLAWD_APP_BUILDER_ROOT || process.cwd(),
25340
- ...env.CLAWD_APP_BUILDER_TEMPLATE_DIR ? { templateDir: env.CLAWD_APP_BUILDER_TEMPLATE_DIR } : {},
25341
- ...env.CLAWD_APP_BUILDER_SCAFFOLD_SCRIPT ? { scaffoldScript: env.CLAWD_APP_BUILDER_SCAFFOLD_SCRIPT } : {},
25342
- ...env.CLAWD_APP_BUILDER_PUBLISH_SCRIPT ? { publishScript: env.CLAWD_APP_BUILDER_PUBLISH_SCRIPT } : {},
25343
- ...env.CLAWD_APP_BUILDER_PERSONA_ROOT ? { personaRoot: env.CLAWD_APP_BUILDER_PERSONA_ROOT } : {},
25344
- ...env.CLAWD_APP_BUILDER_REMOVE_SCRIPT ? { removeScript: env.CLAWD_APP_BUILDER_REMOVE_SCRIPT } : {}
25345
- };
25346
- }
25347
- function hasDaemon(env) {
25348
- return Boolean(env.daemonUrl && env.daemonToken);
25349
- }
25350
- async function callDaemon(ctx, method, args) {
25351
- const env = ctx.env;
25352
- if (!hasDaemon(env)) return fail(`${method}: no daemon`);
25353
- const f = ctx.fetch ?? fetch;
25354
- const url = `${env.daemonUrl}/rpc/${method}`;
25355
- log("daemon call", { method });
25356
- let res;
25357
- try {
25358
- res = await f(url, {
25359
- method: "POST",
25360
- headers: { "content-type": "application/json", authorization: `Bearer ${env.daemonToken}` },
25361
- body: JSON.stringify({ ...env.sessionId ? { sessionId: env.sessionId } : {}, ...args })
25362
- });
25363
- } catch (err) {
25364
- const msg = err instanceof Error ? err.message : String(err);
25365
- log("daemon call threw", { method, msg });
25366
- return fail(`${method} transport failed (is the daemon up?): ${msg}`);
25367
- }
25368
- let json;
25369
- try {
25370
- json = await res.json();
25371
- } catch {
25372
- log("daemon call non-JSON", { method, status: res.status });
25373
- return fail(`${method} returned non-JSON (HTTP ${res.status})`);
25374
- }
25375
- if (json.ok === false) {
25376
- log("daemon call rpc error", { method, error: json.error });
25377
- return fail(`${method} failed: ${json.error}: ${json.message}`);
25132
+ const out = { repoOrg: env.CLAWD_APP_BUILDER_REPO_ORG || PERSONA_REPO_ORG };
25133
+ for (const [key, name] of Object.entries(ENV_NAMES)) {
25134
+ const v = env[name];
25135
+ if (v) out[key] = v;
25378
25136
  }
25379
- log("daemon call ok", { method });
25380
- return ok(json.result);
25137
+ return out;
25381
25138
  }
25382
- function storeOf(env) {
25383
- return new ProjectStore(env.root);
25139
+ function missingEnv(env, keys) {
25140
+ const missing = keys.filter((k) => !env[k]).map((k) => ENV_NAMES[k]);
25141
+ return missing.length ? `env not set: ${missing.join(", ")}` : null;
25384
25142
  }
25385
25143
  async function handleCreateProject(input, ctx) {
25386
- if (hasDaemon(ctx.env)) return callDaemon(ctx, "appBuilder:createProject", { name: input.name });
25387
- const { templateDir, scaffoldScript } = ctx.env;
25388
- if (!templateDir || !scaffoldScript) {
25389
- return fail(
25390
- "createProject: no daemon, and CLAWD_APP_BUILDER_TEMPLATE_DIR / CLAWD_APP_BUILDER_SCAFFOLD_SCRIPT are not set; cannot scaffold."
25391
- );
25392
- }
25144
+ const miss = missingEnv(ctx.env, ["root", "templateDir", "scaffoldScript"]);
25145
+ if (miss) return fail(`createProject: ${miss}`);
25146
+ const env = ctx.env;
25393
25147
  try {
25394
- const created = await createProjectOnDisk({
25395
- store: storeOf(ctx.env),
25148
+ const r = await createProject({
25149
+ store: new ProjectStore(env.root),
25396
25150
  name: input.name,
25397
- templateSrcDir: templateDir,
25398
- scaffoldScript,
25399
- logger: { info: (m, i) => log(m, i), warn: (m, i) => log(m, i) }
25151
+ templateSrcDir: env.templateDir,
25152
+ scaffoldScript: env.scaffoldScript,
25153
+ org: env.repoOrg,
25154
+ ...ctx.exec ? { exec: ctx.exec } : {},
25155
+ logger
25156
+ });
25157
+ log("createProject done", { name: input.name, projectDir: r.projectDir, repoUrl: r.repoUrl });
25158
+ return ok({
25159
+ name: r.project.name,
25160
+ 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"
25400
25164
  });
25401
- log("createProject done (no daemon)", { name: input.name, projectDir: created.projectDir });
25402
- return ok(created);
25403
25165
  } catch (err) {
25404
- return fail(`createProject failed: ${err instanceof Error ? err.message : String(err)}`);
25166
+ return fail(`createProject failed: ${errMsg2(err)}`);
25405
25167
  }
25406
25168
  }
25407
25169
  async function handleListProjects(ctx) {
25408
- if (hasDaemon(ctx.env)) return callDaemon(ctx, "appBuilder:listProjects", {});
25170
+ const miss = missingEnv(ctx.env, ["root"]);
25171
+ if (miss) return fail(`listProjects: ${miss}`);
25409
25172
  try {
25410
- return ok({ projects: await storeOf(ctx.env).list() });
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 });
25411
25176
  } catch (err) {
25412
- return fail(`listProjects failed: ${err instanceof Error ? err.message : String(err)}`);
25413
- }
25414
- }
25415
- async function handleReportStage(input, ctx) {
25416
- if (hasDaemon(ctx.env)) {
25417
- return callDaemon(ctx, "appBuilder:reportStage", {
25418
- stage: input.stage,
25419
- ...input.reason !== void 0 ? { reason: input.reason } : {}
25420
- });
25421
- }
25422
- log("reportStage skipped (no daemon)", { stage: input.stage });
25423
- return ok({ ok: true, skipped: "no daemon; stage reporting is a local-UI concern" });
25424
- }
25425
- async function handleStartDevServer(input, ctx) {
25426
- if (hasDaemon(ctx.env)) {
25427
- return callDaemon(ctx, "appBuilder:startDevServer", {
25428
- ...input.force !== void 0 ? { force: input.force } : {}
25429
- });
25177
+ return fail(`listProjects failed: ${errMsg2(err)}`);
25430
25178
  }
25431
- return fail(
25432
- "startDevServer is a local-only capability (daemon dev server supervisor + port pool); it is not available in this environment."
25433
- );
25434
25179
  }
25435
- async function handleUpdateProjectPort(input, ctx) {
25436
- if (hasDaemon(ctx.env)) {
25437
- return callDaemon(ctx, "appBuilder:updateProjectPort", {
25180
+ async function handlePublish(input, ctx) {
25181
+ const miss = missingEnv(ctx.env, ["root", "publishScript", "personaRoot"]);
25182
+ if (miss) return fail(`publish: ${miss}`);
25183
+ const env = ctx.env;
25184
+ try {
25185
+ const r = await runPublish({
25186
+ store: new ProjectStore(env.root),
25438
25187
  name: input.name,
25439
- newPort: input.newPort
25188
+ scriptPath: env.publishScript,
25189
+ personaRoot: env.personaRoot,
25190
+ ...ctx.exec ? { exec: ctx.exec } : {},
25191
+ ...ctx.spawnImpl ? { spawnImpl: ctx.spawnImpl } : {},
25192
+ logger
25440
25193
  });
25441
- }
25442
- return fail(
25443
- "updateProjectPort is a local-only capability (dev server port pool); it is not available in this environment."
25444
- );
25445
- }
25446
- async function handleSetProdUrl(input, ctx) {
25447
- if (hasDaemon(ctx.env)) {
25448
- return callDaemon(ctx, "appBuilder:setProdUrl", { name: input.name, url: input.url });
25449
- }
25450
- try {
25451
- return ok({ project: await storeOf(ctx.env).setProdUrl(input.name, input.url) });
25452
- } catch (err) {
25453
- return fail(`setProdUrl failed: ${err instanceof Error ? err.message : String(err)}`);
25454
- }
25455
- }
25456
- async function handlePublish(input, ctx) {
25457
- if (hasDaemon(ctx.env)) return callDaemon(ctx, "appBuilder:publish", { name: input.name });
25458
- const { publishScript, personaRoot } = ctx.env;
25459
- if (!publishScript) {
25460
- return fail("publish: no daemon, and CLAWD_APP_BUILDER_PUBLISH_SCRIPT is not set.");
25461
- }
25462
- const service = ctx.publishService ?? createPublishService();
25463
- const store = storeOf(ctx.env);
25464
- let settle = () => {
25465
- };
25466
- const done = new Promise((resolve) => {
25467
- settle = resolve;
25468
- });
25469
- const publishStore = {
25470
- setPublishJob: (name, job) => store.setPublishJob(name, job),
25471
- clearPublishJob: (name) => store.clearPublishJob(name),
25472
- setProdUrl: (name, url) => store.setProdUrl(name, url),
25473
- projectDir: (name) => store.projectDir(name)
25474
- };
25475
- const started = await service.start({
25476
- name: input.name,
25477
- sessionId: ctx.env.sessionId ?? "",
25478
- scriptPath: publishScript,
25479
- personaRoot: personaRoot ?? "",
25480
- store: publishStore,
25481
- hooks: {
25482
- broadcast: (frame) => log("publish frame", frame),
25483
- onSettled: (outcome) => {
25484
- log("publish settled", outcome);
25485
- if (outcome.kind === "published") {
25486
- settle(ok({ status: "published", name: input.name, prodUrl: outcome.url }));
25487
- } else if (outcome.kind === "failed") {
25488
- settle(fail(`\u53D1\u5E03\u5931\u8D25\uFF1A[${outcome.stage}] \u9636\u6BB5
25489
- ${outcome.errorSummary}`));
25490
- } else {
25491
- settle(fail("publish was dismissed before it finished"));
25492
- }
25493
- }
25494
- },
25495
- logger: {
25496
- info: (m, i) => log(m, i),
25497
- warn: (m, i) => log(m, i)
25194
+ if (r.ok) {
25195
+ return ok({ status: "published", name: input.name, prodUrl: r.prodUrl, commit: r.commit, publishedAt: r.publishedAt });
25498
25196
  }
25499
- });
25500
- if (started.status === "already-publishing") {
25501
- return ok({ status: "already-publishing", jobId: started.jobId });
25197
+ return fail(`\u53D1\u5E03\u5931\u8D25\uFF1A[${r.stage}] \u9636\u6BB5
25198
+ ${r.errorSummary}
25199
+
25200
+ \u5B8C\u6574\u65E5\u5FD7\uFF1A${r.logPath}`);
25201
+ } catch (err) {
25202
+ return fail(`publish failed: ${errMsg2(err)}`);
25502
25203
  }
25503
- return await done;
25504
25204
  }
25505
25205
  async function handleRemoveProject(input, ctx) {
25506
- if (hasDaemon(ctx.env)) {
25507
- return fail(
25508
- 'removeProject is cloud-only. On this host run the script yourself:\n bash "$HOME/.clawd/deploy-kit/scripts/remove-extension.sh" <projectDir> <personaRoot>'
25509
- );
25510
- }
25511
- const { removeScript, personaRoot } = ctx.env;
25512
- if (!removeScript) {
25513
- return fail("removeProject: no daemon, and CLAWD_APP_BUILDER_REMOVE_SCRIPT is not set.");
25514
- }
25515
- const store = storeOf(ctx.env);
25206
+ const miss = missingEnv(ctx.env, ["root", "removeScript", "personaRoot"]);
25207
+ if (miss) return fail(`removeProject: ${miss}`);
25208
+ const env = ctx.env;
25209
+ const store = new ProjectStore(env.root);
25516
25210
  try {
25517
- const projectDir = store.projectDir(input.name);
25211
+ if (!await store.get(input.name)) return fail(`removeProject: project "${input.name}" not found / \u4E0D\u5B58\u5728`);
25518
25212
  const r = await runRemoveExtension({
25519
- script: removeScript,
25520
- projectDir,
25521
- personaRoot: personaRoot ?? "",
25522
- logger: { info: (m, i) => log(m, i), warn: (m, i) => log(m, i) }
25213
+ script: env.removeScript,
25214
+ projectDir: store.projectDir(input.name),
25215
+ personaRoot: env.personaRoot,
25216
+ ...ctx.spawnImpl ? { spawnImpl: ctx.spawnImpl } : {},
25217
+ logger
25523
25218
  });
25524
25219
  const text = `exit=${r.code}
25525
25220
  --- stdout ---
25526
25221
  ${r.stdout}
25527
25222
  --- stderr ---
25528
25223
  ${r.stderr}`;
25529
- return r.code === 0 ? ok(text) : fail(text);
25224
+ if (r.code !== 0) return fail(text);
25225
+ await store.delete(input.name);
25226
+ return ok(`${text}
25227
+ \u672C\u5730\u76EE\u5F55\u5DF2\u5220\u9664\uFF1BGitHub \u4ED3\u5E93\u4FDD\u7559\uFF0C\u8981\u5220\u81EA\u5DF1\u53BB GitHub \u5F52\u6863\u3002`);
25530
25228
  } catch (err) {
25531
- return fail(`removeProject failed: ${err instanceof Error ? err.message : String(err)}`);
25229
+ return fail(`removeProject failed: ${errMsg2(err)}`);
25532
25230
  }
25533
25231
  }
25534
25232
  function registerTools(server, ctx) {
25535
25233
  server.registerTool(
25536
25234
  "createProject",
25537
25235
  {
25538
- title: "Create and scaffold an app-builder project",
25539
- description: "Create a new app-builder project: allocate its directory + .clawd-project.json and scaffold the starter template into it. This is the ONLY way to create a project \u2014 never mkdir one yourself, and never trust a path suggested elsewhere. Returns { project, projectDir }; projectDir is the absolute path you must cd into. Fails if the name is already taken.",
25540
- inputSchema: { name: external_exports.string().min(1).describe("project name given by the user") }
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.",
25238
+ inputSchema: { name: external_exports.string().min(1).describe("project name given by the user (kebab-case)") }
25541
25239
  },
25542
25240
  async (input) => handleCreateProject(input, ctx)
25543
25241
  );
25544
25242
  server.registerTool(
25545
25243
  "listProjects",
25546
25244
  {
25547
- title: "List app-builder projects",
25548
- description: "List the app-builder projects visible to this session (name / port / stage / prodUrl). Use it to check for a name clash before createProject, or to re-find a project path you forgot.",
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.",
25549
25247
  inputSchema: {}
25550
25248
  },
25551
25249
  async () => handleListProjects(ctx)
25552
25250
  );
25553
- server.registerTool(
25554
- "reportStage",
25555
- {
25556
- title: "Report scaffolding stage",
25557
- description: 'Report the current stage of the project so the UI preview pane can follow along. Only "installing" and "failed" are reportable by the assistant; the rest are set by the host. Pass reason when failing. Silently skipped in environments with no UI.',
25558
- inputSchema: {
25559
- stage: external_exports.string().describe('"installing" or "failed"'),
25560
- reason: external_exports.string().optional().describe('one-line cause, required when stage is "failed"')
25561
- }
25562
- },
25563
- async (input) => handleReportStage(input, ctx)
25564
- );
25565
- server.registerTool(
25566
- "startDevServer",
25567
- {
25568
- title: "Start the project dev server",
25569
- description: "Start the dev server for this session project after dependencies are installed. Idempotent: a running server is left alone unless force is true. Local-only capability \u2014 unavailable when there is no host daemon.",
25570
- inputSchema: {
25571
- force: external_exports.boolean().optional().describe("stop and respawn even if it is already running")
25572
- }
25573
- },
25574
- async (input) => handleStartDevServer(input, ctx)
25575
- );
25576
- server.registerTool(
25577
- "updateProjectPort",
25578
- {
25579
- title: "Change the dev server port of a project",
25580
- description: "Change which local port this project's dev server listens on. Normally the user does this from the project settings menu \u2014 only call it when they ask you to. Local-only capability.",
25581
- inputSchema: {
25582
- name: external_exports.string().min(1).describe("project name"),
25583
- newPort: external_exports.number().int().describe("new port, must be inside the app-builder port range")
25584
- }
25585
- },
25586
- async (input) => handleUpdateProjectPort(input, ctx)
25587
- );
25588
- server.registerTool(
25589
- "setProdUrl",
25590
- {
25591
- title: "Record the public URL of a published project",
25592
- description: "Write the public URL of a successfully published project into its .clawd-project.json so the preview switches to the live site. Call it after a publish that you drove by hand.",
25593
- inputSchema: {
25594
- name: external_exports.string().min(1).describe("project name"),
25595
- url: external_exports.string().url().describe("public https URL")
25596
- }
25597
- },
25598
- async (input) => handleSetProdUrl(input, ctx)
25599
- );
25600
25251
  server.registerTool(
25601
25252
  "publish",
25602
25253
  {
25603
25254
  title: "Publish the project to the public internet",
25604
- description: "Run the publish pipeline (build \u2192 deploy \u2192 verify) for the project. With a host daemon this hands the job to it (progress shows up in the UI, and a failure comes back to you as a chat message); without one the pipeline runs here and this call returns only when it finishes, with the public URL or the failure log tail.",
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.",
25605
25256
  inputSchema: { name: external_exports.string().min(1).describe("project name") }
25606
25257
  },
25607
25258
  async (input) => handlePublish(input, ctx)
@@ -25609,8 +25260,8 @@ function registerTools(server, ctx) {
25609
25260
  server.registerTool(
25610
25261
  "removeProject",
25611
25262
  {
25612
- title: "Tear down a published project",
25613
- description: "Tear down everything a published project created (FC function / trigger / custom domain, plus the DROP statements for its Supabase tables). Cloud-only: on a host with a local daemon this returns the shell command to run yourself instead. Returns the script output verbatim \u2014 read it, it may ask you to run a few DROPs through the supabase MCP to finish the job.",
25263
+ title: "Tear down a project",
25264
+ description: "Tear down everything a published project created (FC function / trigger / custom domain, plus the DROP statements for its Supabase tables) and then delete the local project directory. The GitHub repo is kept. Returns the script output verbatim \u2014 read it, it may ask you to run a few DROPs through the supabase MCP. Confirm with the user before calling.",
25614
25265
  inputSchema: { name: external_exports.string().min(1).describe("project name") }
25615
25266
  },
25616
25267
  async (input) => handleRemoveProject(input, ctx)
@@ -25618,13 +25269,8 @@ function registerTools(server, ctx) {
25618
25269
  }
25619
25270
  async function main() {
25620
25271
  const env = readEnv();
25621
- log("boot", {
25622
- pid: process.pid,
25623
- mode: hasDaemon(env) ? "daemon" : "standalone",
25624
- personaId: env.personaId ?? "(none)",
25625
- root: env.root
25626
- });
25627
- const server = new McpServer({ name: "clawd-app-builder", version: "0.1.0" });
25272
+ 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" });
25628
25274
  registerTools(server, { env });
25629
25275
  await server.connect(new StdioServerTransport());
25630
25276
  }
@@ -25642,10 +25288,6 @@ if (isMainModule) {
25642
25288
  handleListProjects,
25643
25289
  handlePublish,
25644
25290
  handleRemoveProject,
25645
- handleReportStage,
25646
- handleSetProdUrl,
25647
- handleStartDevServer,
25648
- handleUpdateProjectPort,
25649
25291
  readEnv,
25650
25292
  registerTools
25651
25293
  });