@clawos-dev/clawd 0.2.143-beta.300.c2a3792 → 0.2.143-beta.301.814d293

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/dist/cli.cjs +299 -238
  2. package/dist/deploy-kit/.secrets/aliyun.env.example +4 -0
  3. package/dist/deploy-kit/scripts/new-extension.sh +22 -0
  4. package/dist/deploy-kit/scripts/publish.sh +100 -0
  5. package/dist/deploy-kit/scripts/publish.spec.ts +69 -0
  6. package/dist/{persona-defaults/persona-app-builder/extension-kit → deploy-kit}/scripts/remove-extension.sh +15 -26
  7. package/dist/persona-defaults/persona-app-builder/CLAUDE.md +13 -13
  8. package/dist/persona-defaults/persona-app-builder/extension-kit/README.md +24 -18
  9. package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/ext.conf +5 -0
  10. package/dist/persona-defaults/persona-dataclaw-builder/CLAUDE.md +48 -2
  11. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/README.md +28 -25
  12. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/ext.conf +5 -0
  13. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/hooks/pre-deploy.sh +25 -0
  14. package/package.json +1 -1
  15. package/dist/persona-defaults/persona-app-builder/extension-kit/scripts/new-extension.sh +0 -66
  16. package/dist/persona-defaults/persona-app-builder/extension-kit/scripts/publish.sh +0 -110
  17. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/contract/bootstrap +0 -22
  18. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/scripts/ensure-toolchain.sh +0 -62
  19. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/scripts/new-extension.sh +0 -66
  20. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/scripts/publish.sh +0 -129
  21. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/scripts/remove-extension.sh +0 -70
  22. package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/scripts/verify.sh +0 -38
  23. /package/dist/{persona-defaults/persona-app-builder → deploy-kit}/.secrets/aliyun.env +0 -0
  24. /package/dist/{persona-defaults/persona-app-builder/extension-kit → deploy-kit}/contract/bootstrap +0 -0
  25. /package/dist/{persona-defaults/persona-app-builder/extension-kit → deploy-kit}/scripts/ensure-toolchain.sh +0 -0
  26. /package/dist/{persona-defaults/persona-app-builder/extension-kit → deploy-kit}/scripts/verify.sh +0 -0
package/dist/cli.cjs CHANGED
@@ -35316,6 +35316,56 @@ function migrateCodexSandbox(args) {
35316
35316
  }
35317
35317
  if (n > 0) args.logger.info("persona.codex-sandbox.done", { migrated: n });
35318
35318
  }
35319
+ function findDeployKitRoot() {
35320
+ const candidates = [];
35321
+ try {
35322
+ const here = path11.dirname((0, import_node_url.fileURLToPath)(import_meta.url));
35323
+ candidates.push(path11.resolve(here, "..", "deploy-kit"));
35324
+ } catch {
35325
+ }
35326
+ if (process.argv[1]) {
35327
+ candidates.push(path11.resolve(path11.dirname(process.argv[1]), "deploy-kit"));
35328
+ }
35329
+ for (const c of candidates) {
35330
+ try {
35331
+ if (fs9.statSync(c).isDirectory()) return c;
35332
+ } catch {
35333
+ }
35334
+ }
35335
+ return null;
35336
+ }
35337
+ function seedDeployKit(args) {
35338
+ const dst = path11.join(args.dataDir, "deploy-kit");
35339
+ fs9.cpSync(args.deployKitBundleRoot, dst, {
35340
+ recursive: true,
35341
+ dereference: true,
35342
+ force: false,
35343
+ errorOnExist: false
35344
+ });
35345
+ args.logger.info("deploy-kit.seed.done", { dst });
35346
+ }
35347
+ function refreshDeployKit(args) {
35348
+ const dst = path11.join(args.dataDir, "deploy-kit");
35349
+ if (!fs9.existsSync(dst)) {
35350
+ seedDeployKit(args);
35351
+ return;
35352
+ }
35353
+ for (const sub of ["scripts", "contract"]) {
35354
+ const s = path11.join(args.deployKitBundleRoot, sub);
35355
+ if (fs9.existsSync(s)) {
35356
+ fs9.cpSync(s, path11.join(dst, sub), { recursive: true, force: true, dereference: true });
35357
+ }
35358
+ }
35359
+ const secretsSrc = path11.join(args.deployKitBundleRoot, ".secrets");
35360
+ if (fs9.existsSync(secretsSrc)) {
35361
+ fs9.mkdirSync(path11.join(dst, ".secrets"), { recursive: true });
35362
+ for (const f of fs9.readdirSync(secretsSrc)) {
35363
+ if (!f.endsWith(".example")) continue;
35364
+ fs9.copyFileSync(path11.join(secretsSrc, f), path11.join(dst, ".secrets", f));
35365
+ }
35366
+ }
35367
+ args.logger.info("deploy-kit.refresh.done", { dst });
35368
+ }
35319
35369
 
35320
35370
  // src/index.ts
35321
35371
  init_claude();
@@ -43253,8 +43303,218 @@ function buildExtensionHandlers(deps) {
43253
43303
  };
43254
43304
  }
43255
43305
 
43256
- // src/app-builder/kill-port.ts
43306
+ // src/app-builder/project-store.ts
43307
+ var import_node_fs30 = require("fs");
43257
43308
  var import_node_child_process11 = require("child_process");
43309
+ var import_node_path37 = require("path");
43310
+ init_protocol();
43311
+ var PROJECTS_DIR = "projects";
43312
+ var META_FILE = ".clawd-project.json";
43313
+ var DEFAULT_TEMPLATE = "nestjs-react";
43314
+ var SCAFFOLD_TIMEOUT_MS = 3e4;
43315
+ var ProjectStore = class {
43316
+ /** @param root projects 父目录,例如 `~/.clawd/users/<userId>/`(v2 per-user) */
43317
+ constructor(root) {
43318
+ this.root = root;
43319
+ }
43320
+ root;
43321
+ /** projects/<name>/.clawd-project.json 路径 */
43322
+ metaPath(name) {
43323
+ return (0, import_node_path37.join)(this.projectsRoot(), name, META_FILE);
43324
+ }
43325
+ /** projects/<name>/ 目录路径(cwd 用) */
43326
+ projectDir(name) {
43327
+ return (0, import_node_path37.join)(this.projectsRoot(), name);
43328
+ }
43329
+ projectsRoot() {
43330
+ return (0, import_node_path37.join)(this.root, PROJECTS_DIR);
43331
+ }
43332
+ async list() {
43333
+ let entries;
43334
+ try {
43335
+ entries = await import_node_fs30.promises.readdir(this.projectsRoot());
43336
+ } catch (err) {
43337
+ if (err.code === "ENOENT") return [];
43338
+ throw err;
43339
+ }
43340
+ const out = [];
43341
+ for (const name of entries) {
43342
+ try {
43343
+ const raw = await import_node_fs30.promises.readFile(this.metaPath(name), "utf8");
43344
+ const json = JSON.parse(raw);
43345
+ let migrated = false;
43346
+ if (typeof json.devCommand !== "string" || json.devCommand.length === 0) {
43347
+ json.devCommand = DEFAULT_DEV_COMMAND;
43348
+ migrated = true;
43349
+ }
43350
+ const parsed = ProjectMetadataSchema.safeParse(json);
43351
+ if (parsed.success) {
43352
+ out.push(parsed.data);
43353
+ if (migrated) {
43354
+ void import_node_fs30.promises.writeFile(this.metaPath(name), JSON.stringify(parsed.data, null, 2) + "\n", "utf8").catch(() => {
43355
+ });
43356
+ }
43357
+ }
43358
+ } catch {
43359
+ }
43360
+ }
43361
+ return out.sort((a, b2) => a.name.localeCompare(b2.name));
43362
+ }
43363
+ /**
43364
+ * @param reservedPorts 其它 user 已占端口(v2 per-user:端口必须全局唯一,所有 user 的
43365
+ * dev server 同机跑;per-user store 各自只 list 自己,靠 handler 收集全局端口传进来,
43366
+ * 否则不同 user 的首个项目都从 PROJECT_PORT_MIN 起 → 全撞 6173)。
43367
+ */
43368
+ async create(name, reservedPorts) {
43369
+ const existing = await this.list();
43370
+ if (existing.some((p2) => p2.name === name)) {
43371
+ throw new Error(`project "${name}" already exists / \u5DF2\u5B58\u5728`);
43372
+ }
43373
+ const usedPorts = new Set(existing.map((p2) => p2.port));
43374
+ if (reservedPorts) for (const p2 of reservedPorts) usedPorts.add(p2);
43375
+ let port = -1;
43376
+ for (let p2 = PROJECT_PORT_MIN; p2 <= PROJECT_PORT_MAX; p2++) {
43377
+ if (!usedPorts.has(p2)) {
43378
+ port = p2;
43379
+ break;
43380
+ }
43381
+ }
43382
+ if (port < 0) {
43383
+ throw new Error(
43384
+ `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`
43385
+ );
43386
+ }
43387
+ const meta = {
43388
+ name,
43389
+ port,
43390
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
43391
+ devCommand: DEFAULT_DEV_COMMAND
43392
+ };
43393
+ const validated = ProjectMetadataSchema.safeParse(meta);
43394
+ if (!validated.success) {
43395
+ throw new Error(`invalid name "${name}": ${validated.error.message}`);
43396
+ }
43397
+ const dir = this.projectDir(name);
43398
+ await import_node_fs30.promises.mkdir(dir, { recursive: true });
43399
+ await import_node_fs30.promises.writeFile(this.metaPath(name), JSON.stringify(meta, null, 2) + "\n", "utf8");
43400
+ return meta;
43401
+ }
43402
+ /**
43403
+ * 同步内联 scaffold:spawn `extension-kit/scripts/new-extension.sh <name>` 等 exit。
43404
+ * 必须在 `create()` 之后调(脚本期望 projects/<name>/.clawd-project.json 已就位 = picker 模式)。
43405
+ * 失败抛错;上层(handler)应当 catch 后回滚(删 dir + 不 mutate session)。
43406
+ *
43407
+ * spec 2026-06-02 §5.6.3:scaffold 对 assistant 不可见,避免 assistant 被诱导去"读 example
43408
+ * 代码再参考"。assistant 进 project 时目录已是完整模板,直接 pnpm install 即可。
43409
+ */
43410
+ async scaffold(name, templateSrcDir, scaffoldScriptPath) {
43411
+ const destDir = this.projectDir(name);
43412
+ return await new Promise((resolve6, reject) => {
43413
+ const child = (0, import_node_child_process11.spawn)("bash", [scaffoldScriptPath, name, templateSrcDir, destDir], {
43414
+ env: { ...process.env, PATH: process.env.PATH ?? "" },
43415
+ stdio: ["ignore", "pipe", "pipe"]
43416
+ });
43417
+ let stdout = "";
43418
+ let stderr = "";
43419
+ child.stdout.on("data", (d) => {
43420
+ stdout += d.toString("utf8");
43421
+ });
43422
+ child.stderr.on("data", (d) => {
43423
+ stderr += d.toString("utf8");
43424
+ });
43425
+ const timer = setTimeout(() => {
43426
+ child.kill("SIGKILL");
43427
+ reject(new Error(`scaffold timeout (>${SCAFFOLD_TIMEOUT_MS}ms): ${scaffoldScriptPath} ${name}`));
43428
+ }, SCAFFOLD_TIMEOUT_MS);
43429
+ child.on("error", (err) => {
43430
+ clearTimeout(timer);
43431
+ reject(new Error(`scaffold spawn failed: ${err.message}`));
43432
+ });
43433
+ child.on("exit", (code, signal) => {
43434
+ clearTimeout(timer);
43435
+ if (code === 0) resolve6({ stdout, stderr });
43436
+ else reject(new Error(`scaffold exit ${code ?? signal}: ${stderr.slice(0, 500)}`));
43437
+ });
43438
+ });
43439
+ }
43440
+ async delete(name) {
43441
+ const dir = this.projectDir(name);
43442
+ await import_node_fs30.promises.rm(dir, { recursive: true, force: true });
43443
+ }
43444
+ async updatePort(name, newPort) {
43445
+ if (newPort < PROJECT_PORT_MIN || newPort > PROJECT_PORT_MAX) {
43446
+ throw new Error(
43447
+ `port range invalid: must be ${PROJECT_PORT_MIN}-${PROJECT_PORT_MAX}, got ${newPort}`
43448
+ );
43449
+ }
43450
+ const list = await this.list();
43451
+ const target = list.find((p2) => p2.name === name);
43452
+ if (!target) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
43453
+ const conflict = list.find((p2) => p2.name !== name && p2.port === newPort);
43454
+ if (conflict) {
43455
+ throw new Error(`port ${newPort} already used / \u5DF2\u88AB project "${conflict.name}" \u5360\u7528`);
43456
+ }
43457
+ const updated = { ...target, port: newPort };
43458
+ await import_node_fs30.promises.writeFile(this.metaPath(name), JSON.stringify(updated, null, 2) + "\n", "utf8");
43459
+ return updated;
43460
+ }
43461
+ /**
43462
+ * 单栏 refactor (spec 2026-06-02 §5.6.1):发布上线后写公网 URL 到 .clawd-project.json.prodUrl。
43463
+ * UI 通过 listProjects 读到 prodUrl 后切预览栏到线上 URL,取代旧 prod marker 机制。
43464
+ * 重复调用幂等(覆盖旧 URL)。url 由 ProjectMetadataSchema 校验 url() 格式。
43465
+ */
43466
+ async setProdUrl(name, url) {
43467
+ const list = await this.list();
43468
+ const target = list.find((p2) => p2.name === name);
43469
+ if (!target) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
43470
+ const updated = { ...target, prodUrl: url };
43471
+ const validated = ProjectMetadataSchema.safeParse(updated);
43472
+ if (!validated.success) {
43473
+ throw new Error(`invalid prodUrl "${url}": ${validated.error.message}`);
43474
+ }
43475
+ await import_node_fs30.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
43476
+ return validated.data;
43477
+ }
43478
+ /**
43479
+ * 发布上线脚手架化 (spec 2026-06-03-app-builder-publish-scaffold-design §5.1.4):
43480
+ * 写 publishJob 字段到 .clawd-project.json。仅承载 in-flight 状态 ——
43481
+ * - daemon 收到第一个 ::stage::build marker 时写入
43482
+ * - 每次 stage 切换更新 stage 字段(jobId / startedAt 不变)
43483
+ * - 成功 / 失败 / dismiss 时调 clearPublishJob 清掉
43484
+ * - 只有 daemon 崩才会留残骸
43485
+ * 写入时保留所有其他字段(prodUrl 等不受影响)。
43486
+ */
43487
+ async setPublishJob(name, job) {
43488
+ const list = await this.list();
43489
+ const target = list.find((p2) => p2.name === name);
43490
+ if (!target) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
43491
+ const updated = { ...target, publishJob: job };
43492
+ const validated = ProjectMetadataSchema.safeParse(updated);
43493
+ if (!validated.success) {
43494
+ throw new Error(`invalid publishJob: ${validated.error.message}`);
43495
+ }
43496
+ await import_node_fs30.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
43497
+ return validated.data;
43498
+ }
43499
+ /** 清掉 .clawd-project.json.publishJob 字段。其他字段保持原样。 */
43500
+ async clearPublishJob(name) {
43501
+ const list = await this.list();
43502
+ const target = list.find((p2) => p2.name === name);
43503
+ if (!target) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
43504
+ if (!target.publishJob) return target;
43505
+ const { publishJob: _drop, ...rest } = target;
43506
+ void _drop;
43507
+ const validated = ProjectMetadataSchema.safeParse(rest);
43508
+ if (!validated.success) {
43509
+ throw new Error(`failed to clear publishJob: ${validated.error.message}`);
43510
+ }
43511
+ await import_node_fs30.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
43512
+ return validated.data;
43513
+ }
43514
+ };
43515
+
43516
+ // src/app-builder/kill-port.ts
43517
+ var import_node_child_process12 = require("child_process");
43258
43518
  async function killPortOccupants(port, ownedPids, logger) {
43259
43519
  let pids;
43260
43520
  try {
@@ -43296,7 +43556,7 @@ async function killPortOccupants(port, ownedPids, logger) {
43296
43556
  }
43297
43557
  function listPidsOnPort(port) {
43298
43558
  return new Promise((resolve6, reject) => {
43299
- (0, import_node_child_process11.execFile)(
43559
+ (0, import_node_child_process12.execFile)(
43300
43560
  "lsof",
43301
43561
  ["-ti", `:${port}`],
43302
43562
  { timeout: 3e3 },
@@ -43368,9 +43628,9 @@ var PublishJobRegistry = class {
43368
43628
  };
43369
43629
 
43370
43630
  // src/app-builder/publish-job-runner.ts
43371
- var import_node_child_process12 = require("child_process");
43372
- var import_node_fs30 = require("fs");
43373
- var import_node_path37 = require("path");
43631
+ var import_node_child_process13 = require("child_process");
43632
+ var import_node_fs31 = require("fs");
43633
+ var import_node_path38 = require("path");
43374
43634
 
43375
43635
  // src/app-builder/publish-stage-parser.ts
43376
43636
  var STAGE_RE = /^\s*::stage::(build|deploy|verify)\s*$/;
@@ -43397,19 +43657,19 @@ function tailStderrLines(buf, n) {
43397
43657
  // src/app-builder/publish-job-runner.ts
43398
43658
  async function startPublishJob(deps, args) {
43399
43659
  const { registry: registry2, projectDir } = deps;
43400
- const spawn12 = deps.spawnImpl ?? import_node_child_process12.spawn;
43660
+ const spawn12 = deps.spawnImpl ?? import_node_child_process13.spawn;
43401
43661
  if (registry2.has(args.name)) {
43402
43662
  return { jobId: registry2.get(args.name).jobId, status: "already-publishing" };
43403
43663
  }
43404
43664
  const projDir = projectDir(args.name);
43405
- const logPath = (0, import_node_path37.join)(projDir, ".publish.log");
43665
+ const logPath = (0, import_node_path38.join)(projDir, ".publish.log");
43406
43666
  let logStream = null;
43407
43667
  try {
43408
- logStream = (0, import_node_fs30.createWriteStream)(logPath, { flags: "w" });
43668
+ logStream = (0, import_node_fs31.createWriteStream)(logPath, { flags: "w" });
43409
43669
  } catch {
43410
43670
  logStream = null;
43411
43671
  }
43412
- const child = spawn12("bash", [args.scriptPath, projDir], {
43672
+ const child = spawn12("bash", [args.scriptPath, projDir, args.personaRoot ?? ""], {
43413
43673
  cwd: projDir,
43414
43674
  env: process.env,
43415
43675
  stdio: ["ignore", "pipe", "pipe"]
@@ -43662,10 +43922,9 @@ async function recoverInterruptedJobs(deps) {
43662
43922
 
43663
43923
  // src/handlers/app-builder.ts
43664
43924
  init_protocol();
43665
- var import_node_path38 = require("path");
43666
- var import_node_fs31 = require("fs");
43925
+ var import_node_path39 = require("path");
43926
+ var import_node_fs32 = require("fs");
43667
43927
  var APP_BUILDER_PERSONAS = ["persona-app-builder", "persona-dataclaw-builder"];
43668
- var PUBLISH_SCRIPT_REL = "extension-kit/scripts/publish.sh";
43669
43928
  var DEV_SERVER_READY_TIMEOUT_MS = 3e4;
43670
43929
  async function recoverInterruptedPublishJobs(store, logger) {
43671
43930
  await recoverInterruptedJobs({
@@ -43745,7 +44004,7 @@ function buildAppBuilderHandlers(deps) {
43745
44004
  async function listAllUsersProjects() {
43746
44005
  if (!deps.usersRoot || !deps.getStore) return [];
43747
44006
  const getStore = deps.getStore;
43748
- const userIds = await import_node_fs31.promises.readdir(deps.usersRoot).catch(() => []);
44007
+ const userIds = await import_node_fs32.promises.readdir(deps.usersRoot).catch(() => []);
43749
44008
  const perUser = await Promise.all(
43750
44009
  userIds.map((uid) => getStore(uid).list().catch(() => []))
43751
44010
  );
@@ -43821,7 +44080,9 @@ function buildAppBuilderHandlers(deps) {
43821
44080
  const project = await userStore.create(f.name, reservedPorts);
43822
44081
  try {
43823
44082
  const personaRoot = deps.resolvePersonaRoot ? deps.resolvePersonaRoot(session.ownerPersonaId ?? "") : deps.personaRoot;
43824
- const scaffoldResult = await userStore.scaffold(project.name, void 0, personaRoot);
44083
+ const templateSrcDir = (0, import_node_path39.join)(personaRoot, "extension-kit", "examples", DEFAULT_TEMPLATE);
44084
+ const scaffoldScript = (0, import_node_path39.join)(deps.deployKitRoot, "scripts", "new-extension.sh");
44085
+ const scaffoldResult = await userStore.scaffold(project.name, templateSrcDir, scaffoldScript);
43825
44086
  deps.logger?.info("app-builder.scaffold.done", {
43826
44087
  name: project.name,
43827
44088
  stdout: scaffoldResult.stdout.slice(0, 200)
@@ -44040,11 +44301,13 @@ function buildAppBuilderHandlers(deps) {
44040
44301
  });
44041
44302
  await userStore.clearPublishJob(args.name);
44042
44303
  }
44043
- const scriptPath = (0, import_node_path38.join)(deps.personaRoot, PUBLISH_SCRIPT_REL);
44304
+ const personaRoot = deps.resolvePersonaRoot ? deps.resolvePersonaRoot(boundSession.ownerPersonaId ?? "") : deps.personaRoot;
44305
+ const scriptPath = (0, import_node_path39.join)(deps.deployKitRoot, "scripts", "publish.sh");
44044
44306
  deps.logger?.info("app-builder.publish.start", {
44045
44307
  name: args.name,
44046
44308
  sessionId: boundSession.sessionId,
44047
- scriptPath
44309
+ scriptPath,
44310
+ personaRoot
44048
44311
  });
44049
44312
  const result = await startPublishJob(
44050
44313
  {
@@ -44079,7 +44342,8 @@ function buildAppBuilderHandlers(deps) {
44079
44342
  {
44080
44343
  name: args.name,
44081
44344
  sessionId: boundSession.sessionId,
44082
- scriptPath
44345
+ scriptPath,
44346
+ personaRoot
44083
44347
  }
44084
44348
  );
44085
44349
  return { response: { ...result } };
@@ -44134,7 +44398,7 @@ function buildAppBuilderHandlers(deps) {
44134
44398
 
44135
44399
  // src/extension/registry.ts
44136
44400
  var import_promises8 = __toESM(require("fs/promises"), 1);
44137
- var import_node_path39 = __toESM(require("path"), 1);
44401
+ var import_node_path40 = __toESM(require("path"), 1);
44138
44402
  async function loadAll(root) {
44139
44403
  let entries;
44140
44404
  try {
@@ -44147,13 +44411,13 @@ async function loadAll(root) {
44147
44411
  for (const ent of entries) {
44148
44412
  if (!ent.isDirectory()) continue;
44149
44413
  if (ent.name.startsWith(".")) continue;
44150
- records.push(await loadOne(import_node_path39.default.join(root, ent.name), ent.name));
44414
+ records.push(await loadOne(import_node_path40.default.join(root, ent.name), ent.name));
44151
44415
  }
44152
44416
  records.sort((a, b2) => a.extId < b2.extId ? -1 : a.extId > b2.extId ? 1 : 0);
44153
44417
  return records;
44154
44418
  }
44155
44419
  async function loadOne(dir, dirName) {
44156
- const manifestPath = import_node_path39.default.join(dir, "manifest.json");
44420
+ const manifestPath = import_node_path40.default.join(dir, "manifest.json");
44157
44421
  let raw;
44158
44422
  try {
44159
44423
  raw = await import_promises8.default.readFile(manifestPath, "utf8");
@@ -44198,7 +44462,7 @@ async function loadOne(dir, dirName) {
44198
44462
 
44199
44463
  // src/extension/uninstall.ts
44200
44464
  var import_promises9 = __toESM(require("fs/promises"), 1);
44201
- var import_node_path40 = __toESM(require("path"), 1);
44465
+ var import_node_path41 = __toESM(require("path"), 1);
44202
44466
  var UninstallError = class extends Error {
44203
44467
  constructor(code, message) {
44204
44468
  super(message);
@@ -44207,7 +44471,7 @@ var UninstallError = class extends Error {
44207
44471
  code;
44208
44472
  };
44209
44473
  async function uninstall(deps) {
44210
- const dir = import_node_path40.default.join(deps.root, deps.extId);
44474
+ const dir = import_node_path41.default.join(deps.root, deps.extId);
44211
44475
  try {
44212
44476
  await import_promises9.default.access(dir);
44213
44477
  } catch {
@@ -44277,228 +44541,16 @@ function buildMethodHandlers(deps) {
44277
44541
  broadcastProjectUpdated: deps.broadcastProjectUpdated,
44278
44542
  logger: deps.logger,
44279
44543
  // 发布上线脚手架化 (spec 2026-06-03 §5.2):
44280
- // personaRoot 用于拼 publish.sh 绝对路径;broadcastSessionFrame 用于失败回灌路径
44281
- // runner 调 manager.send 后逐帧 dispatch 到 transport)。
44544
+ // personaRoot 仅作 resolvePersonaRoot 未注入时的兜底;deployKitRoot 为共享脚本根。
44545
+ // broadcastSessionFrame 用于失败回灌路径(runner 调 manager.send 后逐帧 dispatch 到 transport)。
44282
44546
  personaRoot: deps.appBuilderPersonaRoot,
44547
+ deployKitRoot: deps.deployKitRoot,
44283
44548
  resolvePersonaRoot: deps.resolvePersonaRoot,
44284
44549
  broadcastSessionFrame: deps.broadcastSessionFrame
44285
44550
  })
44286
44551
  };
44287
44552
  }
44288
44553
 
44289
- // src/app-builder/project-store.ts
44290
- var import_node_fs32 = require("fs");
44291
- var import_node_child_process13 = require("child_process");
44292
- var import_node_path41 = require("path");
44293
- init_protocol();
44294
- var PROJECTS_DIR = "projects";
44295
- var META_FILE = ".clawd-project.json";
44296
- var SCAFFOLD_SCRIPT_REL = "extension-kit/scripts/new-extension.sh";
44297
- var DEFAULT_TEMPLATE = "nestjs-react";
44298
- var SCAFFOLD_TIMEOUT_MS = 3e4;
44299
- var ProjectStore = class {
44300
- /** @param root projects 父目录,例如 `~/.clawd/users/<userId>/`(v2 per-user) */
44301
- constructor(root) {
44302
- this.root = root;
44303
- }
44304
- root;
44305
- /** projects/<name>/.clawd-project.json 路径 */
44306
- metaPath(name) {
44307
- return (0, import_node_path41.join)(this.projectsRoot(), name, META_FILE);
44308
- }
44309
- /** projects/<name>/ 目录路径(cwd 用) */
44310
- projectDir(name) {
44311
- return (0, import_node_path41.join)(this.projectsRoot(), name);
44312
- }
44313
- projectsRoot() {
44314
- return (0, import_node_path41.join)(this.root, PROJECTS_DIR);
44315
- }
44316
- async list() {
44317
- let entries;
44318
- try {
44319
- entries = await import_node_fs32.promises.readdir(this.projectsRoot());
44320
- } catch (err) {
44321
- if (err.code === "ENOENT") return [];
44322
- throw err;
44323
- }
44324
- const out = [];
44325
- for (const name of entries) {
44326
- try {
44327
- const raw = await import_node_fs32.promises.readFile(this.metaPath(name), "utf8");
44328
- const json = JSON.parse(raw);
44329
- let migrated = false;
44330
- if (typeof json.devCommand !== "string" || json.devCommand.length === 0) {
44331
- json.devCommand = DEFAULT_DEV_COMMAND;
44332
- migrated = true;
44333
- }
44334
- const parsed = ProjectMetadataSchema.safeParse(json);
44335
- if (parsed.success) {
44336
- out.push(parsed.data);
44337
- if (migrated) {
44338
- void import_node_fs32.promises.writeFile(this.metaPath(name), JSON.stringify(parsed.data, null, 2) + "\n", "utf8").catch(() => {
44339
- });
44340
- }
44341
- }
44342
- } catch {
44343
- }
44344
- }
44345
- return out.sort((a, b2) => a.name.localeCompare(b2.name));
44346
- }
44347
- /**
44348
- * @param reservedPorts 其它 user 已占端口(v2 per-user:端口必须全局唯一,所有 user 的
44349
- * dev server 同机跑;per-user store 各自只 list 自己,靠 handler 收集全局端口传进来,
44350
- * 否则不同 user 的首个项目都从 PROJECT_PORT_MIN 起 → 全撞 6173)。
44351
- */
44352
- async create(name, reservedPorts) {
44353
- const existing = await this.list();
44354
- if (existing.some((p2) => p2.name === name)) {
44355
- throw new Error(`project "${name}" already exists / \u5DF2\u5B58\u5728`);
44356
- }
44357
- const usedPorts = new Set(existing.map((p2) => p2.port));
44358
- if (reservedPorts) for (const p2 of reservedPorts) usedPorts.add(p2);
44359
- let port = -1;
44360
- for (let p2 = PROJECT_PORT_MIN; p2 <= PROJECT_PORT_MAX; p2++) {
44361
- if (!usedPorts.has(p2)) {
44362
- port = p2;
44363
- break;
44364
- }
44365
- }
44366
- if (port < 0) {
44367
- throw new Error(
44368
- `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`
44369
- );
44370
- }
44371
- const meta = {
44372
- name,
44373
- port,
44374
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
44375
- devCommand: DEFAULT_DEV_COMMAND
44376
- };
44377
- const validated = ProjectMetadataSchema.safeParse(meta);
44378
- if (!validated.success) {
44379
- throw new Error(`invalid name "${name}": ${validated.error.message}`);
44380
- }
44381
- const dir = this.projectDir(name);
44382
- await import_node_fs32.promises.mkdir(dir, { recursive: true });
44383
- await import_node_fs32.promises.writeFile(this.metaPath(name), JSON.stringify(meta, null, 2) + "\n", "utf8");
44384
- return meta;
44385
- }
44386
- /**
44387
- * 同步内联 scaffold:spawn `extension-kit/scripts/new-extension.sh <name>` 等 exit。
44388
- * 必须在 `create()` 之后调(脚本期望 projects/<name>/.clawd-project.json 已就位 = picker 模式)。
44389
- * 失败抛错;上层(handler)应当 catch 后回滚(删 dir + 不 mutate session)。
44390
- *
44391
- * spec 2026-06-02 §5.6.3:scaffold 对 assistant 不可见,避免 assistant 被诱导去"读 example
44392
- * 代码再参考"。assistant 进 project 时目录已是完整模板,直接 pnpm install 即可。
44393
- */
44394
- async scaffold(name, template = DEFAULT_TEMPLATE, personaRoot = this.root) {
44395
- const scriptPath = (0, import_node_path41.join)(personaRoot, SCAFFOLD_SCRIPT_REL);
44396
- const destDir = this.projectDir(name);
44397
- return await new Promise((resolve6, reject) => {
44398
- const child = (0, import_node_child_process13.spawn)("bash", [scriptPath, name, template, destDir], {
44399
- cwd: personaRoot,
44400
- env: { ...process.env, PATH: process.env.PATH ?? "" },
44401
- stdio: ["ignore", "pipe", "pipe"]
44402
- });
44403
- let stdout = "";
44404
- let stderr = "";
44405
- child.stdout.on("data", (d) => {
44406
- stdout += d.toString("utf8");
44407
- });
44408
- child.stderr.on("data", (d) => {
44409
- stderr += d.toString("utf8");
44410
- });
44411
- const timer = setTimeout(() => {
44412
- child.kill("SIGKILL");
44413
- reject(new Error(`scaffold timeout (>${SCAFFOLD_TIMEOUT_MS}ms): ${scriptPath} ${name} ${template}`));
44414
- }, SCAFFOLD_TIMEOUT_MS);
44415
- child.on("error", (err) => {
44416
- clearTimeout(timer);
44417
- reject(new Error(`scaffold spawn failed: ${err.message}`));
44418
- });
44419
- child.on("exit", (code, signal) => {
44420
- clearTimeout(timer);
44421
- if (code === 0) resolve6({ stdout, stderr });
44422
- else reject(new Error(`scaffold exit ${code ?? signal}: ${stderr.slice(0, 500)}`));
44423
- });
44424
- });
44425
- }
44426
- async delete(name) {
44427
- const dir = this.projectDir(name);
44428
- await import_node_fs32.promises.rm(dir, { recursive: true, force: true });
44429
- }
44430
- async updatePort(name, newPort) {
44431
- if (newPort < PROJECT_PORT_MIN || newPort > PROJECT_PORT_MAX) {
44432
- throw new Error(
44433
- `port range invalid: must be ${PROJECT_PORT_MIN}-${PROJECT_PORT_MAX}, got ${newPort}`
44434
- );
44435
- }
44436
- const list = await this.list();
44437
- const target = list.find((p2) => p2.name === name);
44438
- if (!target) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
44439
- const conflict = list.find((p2) => p2.name !== name && p2.port === newPort);
44440
- if (conflict) {
44441
- throw new Error(`port ${newPort} already used / \u5DF2\u88AB project "${conflict.name}" \u5360\u7528`);
44442
- }
44443
- const updated = { ...target, port: newPort };
44444
- await import_node_fs32.promises.writeFile(this.metaPath(name), JSON.stringify(updated, null, 2) + "\n", "utf8");
44445
- return updated;
44446
- }
44447
- /**
44448
- * 单栏 refactor (spec 2026-06-02 §5.6.1):发布上线后写公网 URL 到 .clawd-project.json.prodUrl。
44449
- * UI 通过 listProjects 读到 prodUrl 后切预览栏到线上 URL,取代旧 prod marker 机制。
44450
- * 重复调用幂等(覆盖旧 URL)。url 由 ProjectMetadataSchema 校验 url() 格式。
44451
- */
44452
- async setProdUrl(name, url) {
44453
- const list = await this.list();
44454
- const target = list.find((p2) => p2.name === name);
44455
- if (!target) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
44456
- const updated = { ...target, prodUrl: url };
44457
- const validated = ProjectMetadataSchema.safeParse(updated);
44458
- if (!validated.success) {
44459
- throw new Error(`invalid prodUrl "${url}": ${validated.error.message}`);
44460
- }
44461
- await import_node_fs32.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
44462
- return validated.data;
44463
- }
44464
- /**
44465
- * 发布上线脚手架化 (spec 2026-06-03-app-builder-publish-scaffold-design §5.1.4):
44466
- * 写 publishJob 字段到 .clawd-project.json。仅承载 in-flight 状态 ——
44467
- * - daemon 收到第一个 ::stage::build marker 时写入
44468
- * - 每次 stage 切换更新 stage 字段(jobId / startedAt 不变)
44469
- * - 成功 / 失败 / dismiss 时调 clearPublishJob 清掉
44470
- * - 只有 daemon 崩才会留残骸
44471
- * 写入时保留所有其他字段(prodUrl 等不受影响)。
44472
- */
44473
- async setPublishJob(name, job) {
44474
- const list = await this.list();
44475
- const target = list.find((p2) => p2.name === name);
44476
- if (!target) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
44477
- const updated = { ...target, publishJob: job };
44478
- const validated = ProjectMetadataSchema.safeParse(updated);
44479
- if (!validated.success) {
44480
- throw new Error(`invalid publishJob: ${validated.error.message}`);
44481
- }
44482
- await import_node_fs32.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
44483
- return validated.data;
44484
- }
44485
- /** 清掉 .clawd-project.json.publishJob 字段。其他字段保持原样。 */
44486
- async clearPublishJob(name) {
44487
- const list = await this.list();
44488
- const target = list.find((p2) => p2.name === name);
44489
- if (!target) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
44490
- if (!target.publishJob) return target;
44491
- const { publishJob: _drop, ...rest } = target;
44492
- void _drop;
44493
- const validated = ProjectMetadataSchema.safeParse(rest);
44494
- if (!validated.success) {
44495
- throw new Error(`failed to clear publishJob: ${validated.error.message}`);
44496
- }
44497
- await import_node_fs32.promises.writeFile(this.metaPath(name), JSON.stringify(validated.data, null, 2) + "\n", "utf8");
44498
- return validated.data;
44499
- }
44500
- };
44501
-
44502
44554
  // src/app-builder/dev-server-supervisor.ts
44503
44555
  var import_node_child_process14 = require("child_process");
44504
44556
  var import_node_events2 = require("events");
@@ -45430,6 +45482,13 @@ async function startDaemon(config) {
45430
45482
  } else {
45431
45483
  logger.warn("persona.seed.skip", { reason: "defaults-root-not-found" });
45432
45484
  }
45485
+ const deployKitBundleRoot = findDeployKitRoot();
45486
+ if (deployKitBundleRoot) {
45487
+ seedDeployKit({ deployKitBundleRoot, dataDir: config.dataDir, logger });
45488
+ refreshDeployKit({ deployKitBundleRoot, dataDir: config.dataDir, logger });
45489
+ } else {
45490
+ logger.warn("deploy-kit.bundle-missing", {});
45491
+ }
45433
45492
  migrateAgentsMirror({ store: personaStore, logger });
45434
45493
  migrateCodexSandbox({ store: personaStore, logger });
45435
45494
  const groupFileStore = new GroupFileStore({ dataDir: config.dataDir, logger });
@@ -45755,7 +45814,9 @@ async function startDaemon(config) {
45755
45814
  // appBuilderPersonaRoot 用于拼 publish.sh 绝对路径(persona-app-builder 安装在
45756
45815
  // dataDir/personas/persona-app-builder 之下,extension-kit/scripts/publish.sh 是相对路径)。
45757
45816
  appBuilderPersonaRoot: import_node_path45.default.join(config.dataDir, "personas", "persona-app-builder"),
45758
- // scaffold 按当前 session persona 解析其安装根,让新 persona 复用流水线用自己的模板。
45817
+ // 共享 deploy-kit 根:scaffold/publish 脚本骨架 + 阿里云凭证单一真源。
45818
+ deployKitRoot: import_node_path45.default.join(config.dataDir, "deploy-kit"),
45819
+ // scaffold/publish 按当前 session 的 persona 解析其安装根,让每个 persona 用自己的模板/注入配置。
45759
45820
  resolvePersonaRoot: (personaId) => import_node_path45.default.join(config.dataDir, "personas", personaId),
45760
45821
  // 发布上线脚手架化 (spec 2026-06-03 §5.2.2):
45761
45822
  // 复用 SessionManagerDeps.broadcastFrame 同款 dispatch 逻辑 —— runner 调 manager.send
@@ -0,0 +1,4 @@
1
+ # 阿里云访问凭证(所有 FC persona 共用一份)。复制为同目录 aliyun.env 并填实际值;
2
+ # aliyun.env 不进 git(见 .gitignore),daemon 启动不覆盖。
3
+ ALIBABA_CLOUD_ACCESS_KEY_ID=replace-me
4
+ ALIBABA_CLOUD_ACCESS_KEY_SECRET=replace-me