@clize/clize 0.10.0 → 0.11.0

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 (40) hide show
  1. package/README.md +9 -11
  2. package/dist/cli.js +169 -373
  3. package/dist/cli.js.map +1 -1
  4. package/dist/config.js +1 -12
  5. package/dist/config.js.map +1 -1
  6. package/dist/context.js +32 -50
  7. package/dist/context.js.map +1 -1
  8. package/dist/core/billing.js +21 -17
  9. package/dist/core/billing.js.map +1 -1
  10. package/dist/core/credentials.js +30 -0
  11. package/dist/core/credentials.js.map +1 -0
  12. package/dist/core/domains.js +59 -18
  13. package/dist/core/domains.js.map +1 -1
  14. package/dist/core/setup.js +3 -26
  15. package/dist/core/setup.js.map +1 -1
  16. package/dist/core/sites.js +59 -20
  17. package/dist/core/sites.js.map +1 -1
  18. package/dist/index.js +40 -90
  19. package/dist/index.js.map +1 -1
  20. package/dist/lib/cloudflare.js +4 -0
  21. package/dist/lib/cloudflare.js.map +1 -1
  22. package/dist/lib/crypto.js +57 -0
  23. package/dist/lib/crypto.js.map +1 -0
  24. package/dist/lib/vercel.js +67 -0
  25. package/dist/lib/vercel.js.map +1 -0
  26. package/dist/providers/index.js +15 -3
  27. package/dist/providers/index.js.map +1 -1
  28. package/dist/providers/registrar/cloudflare.js +4 -0
  29. package/dist/providers/registrar/cloudflare.js.map +1 -1
  30. package/dist/providers/registrar/routing.js +91 -0
  31. package/dist/providers/registrar/routing.js.map +1 -0
  32. package/dist/providers/registrar/vercel.js +114 -0
  33. package/dist/providers/registrar/vercel.js.map +1 -0
  34. package/dist/remote.js +13 -8
  35. package/dist/remote.js.map +1 -1
  36. package/dist/state/file-store.js +7 -0
  37. package/dist/state/file-store.js.map +1 -1
  38. package/package.json +1 -1
  39. package/skills/clize/SKILL.md +2 -1
  40. package/skills/clize-site-debug/SKILL.md +80 -0
package/dist/cli.js CHANGED
@@ -1,40 +1,34 @@
1
1
  #!/usr/bin/env node
2
2
  // ============================================================
3
- // clize CLI —— 两种模式共用一套命令面:
4
- // • 本地(self-host):自带 Cloudflare token,命令跑本地 core,直连你自己的 CF。
5
- // • 托管(remote):配置里有 CLIZE_API_KEY(由 `clize login --api` 写),命令改走
6
- // HTTP clize 控制面后端;用户永不接触 CF。后端已就绪的命令见 remote.ts;
7
- // 其余命令在 remote 模式给友好报错。
8
- // 判定:remote.loadRemote() 非空 = 托管模式。
3
+ // clize CLI —— 纯托管瘦客户端:
4
+ // • CLI 不含 core 业务逻辑、不直连 CF/Vercel/OpenAI;所有真实动作经 clize 控制面 worker
5
+ // • 登录态 = 配置/env 里有 CLIZE_API_KEY(由 `clize login` 写)。未登录则需凭证的命令引导去登录。
6
+ // 客户端仅保留「读盘 / 落盘 / 本地 lint / 本地预览」这类不碰云的活儿
7
+ // (站点文件读取、知识文件读取、媒体落盘、蓝图校验、serve)。
9
8
  // ============================================================
10
9
  import fs from "node:fs";
11
10
  import path from "node:path";
12
11
  import { spawn } from "node:child_process";
13
12
  import { Command } from "commander";
14
- import { createLocalContext } from "./context.js";
15
13
  import * as githubClient from "./github.js";
16
- import * as domainsCore from "./core/domains.js";
17
- import * as emailCore from "./core/email.js";
18
- import * as siteCore from "./core/site.js";
19
14
  import * as addressesCore from "./core/addresses.js";
20
- import * as handleCore from "./core/handle.js";
21
15
  import * as sitesCore from "./core/sites.js";
22
16
  import * as setupCore from "./core/setup.js";
23
- import * as projectsCore from "./core/projects.js";
24
- import * as billingCore from "./core/billing.js";
25
17
  import * as installCore from "./core/install.js";
26
18
  import * as mediaCore from "./core/media.js";
27
19
  import * as videoCore from "./core/video.js";
28
20
  import * as serveCore from "./core/serve.js";
29
- import { selfcheck } from "./selfcheck.js";
30
21
  import * as remoteClient from "./remote.js";
31
22
  import { VERSION } from "./version.js";
32
- // 托管模式探测(配置里有 CLIZE_API_KEY 即启用)。
23
+ // 托管模式探测(配置里有 CLIZE_API_KEY 即登录态)。
33
24
  const remote = remoteClient.loadRemote();
34
- // 本地 ctx 惰性构造:remote 模式下完全不碰 CF / FileStore。
35
- let _ctx;
36
25
  let projectOverride; // -p <slug>:覆盖当前目录检出(clize.json)
37
- const ctx = () => (_ctx ??= createLocalContext({ projectSlug: projectOverride }));
26
+ /** 取登录态 RemoteConfig;未登录直接报错引导登录(纯托管:无凭证就没法干活) */
27
+ function rc() {
28
+ if (!remote)
29
+ throw new Error("未登录:先 clize login(网页用 GitHub / Google / 邮箱授权)");
30
+ return remote;
31
+ }
38
32
  /** 统一输出:成功打印美化 JSON(字符串原样),失败 → stderr + 退出码 1。 */
39
33
  async function emit(value) {
40
34
  try {
@@ -165,10 +159,10 @@ function splitRefs(spec) {
165
159
  : [];
166
160
  }
167
161
  /** 托管模式异步生成(视频 / 音乐):提交 →(默认)long-poll 到完成并落盘 /(--async)返 job id。 */
168
- async function remoteGenAsync(rc, modality, body, opts) {
162
+ async function remoteGenAsync(rcfg, modality, body, opts) {
169
163
  const submit = (await (modality === "video"
170
- ? remoteClient.api.genVideo(rc, body)
171
- : remoteClient.api.genMusic(rc, body)));
164
+ ? remoteClient.api.genVideo(rcfg, body)
165
+ : remoteClient.api.genMusic(rcfg, body)));
172
166
  if (!opts.confirm)
173
167
  return submit; // 只报价
174
168
  if (submit.state !== "running" || !submit.id)
@@ -182,7 +176,7 @@ async function remoteGenAsync(rc, modality, body, opts) {
182
176
  await new Promise((r) => setTimeout(r, 6000));
183
177
  let st;
184
178
  try {
185
- st = (await remoteClient.api.genStatus(rc, id));
179
+ st = (await remoteClient.api.genStatus(rcfg, id));
186
180
  }
187
181
  catch {
188
182
  continue; // 轮询偶发出错:不判失败,继续等到超时
@@ -211,7 +205,7 @@ const inCommandTree = (cmd, name) => {
211
205
  return true;
212
206
  return false;
213
207
  };
214
- // 全局 -p:命令动作前从该命令的 --project 选项取值 → 本地进 ctx.projectSlug,remote call() 统一注入 ?project=。
208
+ // 全局 -p:命令动作前从该命令的 --project 选项取值 → remote call() 统一注入 ?project=。
215
209
  program.hook("preAction", (_thisCmd, actionCmd) => {
216
210
  // build 树(及 design 兼容别名树)排除:--project 在 build site start 下是「设计项目名」
217
211
  // (给方法后端做记录),不是资源项目 slug。build clip render 仍要 -p(挂了 withProject,
@@ -229,37 +223,27 @@ program
229
223
  .description("Clize —— 给 agent 的真实世界能力层:域名 / 邮箱 / 部署 / 媒体")
230
224
  .version(VERSION)
231
225
  .showHelpAfterError();
232
- // 「未登录」闸:既没有 clize key(托管)也没有 Cloudflare 凭证(自带-CF)时,
233
- // 任何需要凭证的命令都直接引导去 `clize login`,而不是吐底层 Cloudflare 错误。
226
+ // 「未登录」闸:没有 clize key 时,任何需要凭证的命令都引导去 `clize login`(纯托管:动作经控制面)
234
227
  const NO_AUTH_CMDS = new Set(["login", "signup", "init", "install", "update", "help"]);
235
228
  program.hook("preAction", (_thisCmd, actionCmd) => {
236
229
  if (NO_AUTH_CMDS.has(actionCmd.name()))
237
230
  return;
238
- // 生成式媒体(gen *):本地只需自带 OPENAI / GEMINI key(由 core 自检),不要求 CF / clize 凭证。
239
- if (actionCmd.name() === "gen" || actionCmd.parent?.name() === "gen")
240
- return;
241
231
  // 作品域(build *,含 design 兼容别名树):hosted 方法命令自带比通用闸更准的登录引导;
242
- // build clip check 纯本地零凭证、render 同 gen 由 core 自检。
232
+ // build clip check 纯本地零凭证(蓝图机械校验,不碰云)。
243
233
  if (inCommandTree(actionCmd, "build") || inCommandTree(actionCmd, "design"))
244
234
  return;
245
- if (remote || remoteClient.hasLocalCfCreds())
235
+ if (remote)
246
236
  return;
247
237
  console.error("❌ 未登录。\n" +
248
- " 登录即用(免配任何 key):clize login # GitHub 授权,资源跑在 clize 托管\n" +
249
- " (已有 Cloudflare 想自托管?进阶:clize login --help)");
238
+ " 登录即用(免配任何 key):clize login # GitHub / Google / 邮箱授权,资源跑在 clize 托管\n" +
239
+ " CI / headless:clize login --token <clize key> --api <url>");
250
240
  process.exit(1);
251
241
  });
252
242
  // ───────── 总览 / 自检 / 审计 ─────────
253
243
  withProject(program
254
244
  .command("status")
255
245
  .description("工作空间总览:域名、邮箱 / 站点状态、本月开销;--assets 加到期巡检")
256
- .option("--assets", "含注册商处真实到期列表(资产健康)")).action((opts) => emit(remote
257
- ? opts.assets
258
- ? remoteClient.api.statusAssets(remote)
259
- : remoteClient.api.status(remote)
260
- : opts.assets
261
- ? domainsCore.assetsHealth(ctx())
262
- : domainsCore.workspaceStatus(ctx())));
246
+ .option("--assets", "含注册商处真实到期列表(资产健康)")).action((opts) => emit(opts.assets ? remoteClient.api.statusAssets(rc()) : remoteClient.api.status(rc())));
263
247
  program
264
248
  .command("signup")
265
249
  .description("注册一个 clize 托管账户:开租户 + 发 API key(随后用 clize login --api 登录)")
@@ -269,15 +253,12 @@ program
269
253
  program
270
254
  .command("login")
271
255
  .description("登录:默认在网页用 GitHub / Google / 邮箱任一授权(托管);--github 走 device flow;--token 手动")
272
- .option("--token <token>", "手动凭证(托管=clize key;本地=Cloudflare API token)")
256
+ .option("--token <token>", "手动塞 clize key(CI / headless)")
273
257
  .option("--github", "走 GitHub device flow(只认 GitHub、不想开网页时的捷径)")
274
258
  .option("--api <url>", "托管控制面地址")
275
- .option("--account <id>", "账户 id(本地模式:Cloudflare Account ID)")
276
259
  .action((opts) => {
277
260
  if (opts.token)
278
- return emit(opts.api
279
- ? setupCore.saveRemoteLogin(opts.api, opts.token)
280
- : setupCore.saveLogin(opts.token, opts.account));
261
+ return emit(setupCore.saveRemoteLogin(opts.api ?? remoteClient.DEFAULT_API_URL, opts.token));
281
262
  return emit(opts.github ? githubLogin(opts.api) : pairingLogin(opts.api));
282
263
  });
283
264
  program
@@ -297,16 +278,14 @@ const projects = program
297
278
  projects
298
279
  .command("list", { isDefault: true })
299
280
  .description("列出你的所有项目(各带域名数)")
300
- .action(() => emit(remote ? remoteClient.api.projectsList(remote) : projectsCore.listProjects(ctx())));
281
+ .action(() => emit(remoteClient.api.projectsList(rc())));
301
282
  projects
302
283
  .command("new <name>")
303
284
  .description('新建项目(名字带空格用引号,如 "Shop A";slug 缺省由名字规整)')
304
285
  .option("--slug <slug>", "项目标识(租户内唯一,CLI/URL 友好)")
305
286
  .option("--use", "建完顺手把当前目录检出到它(写 ./clize.json)")
306
287
  .action((name, opts) => emit((async () => {
307
- const rec = (remote
308
- ? await remoteClient.api.projectCreate(remote, name, opts.slug)
309
- : await projectsCore.createProject(ctx(), { name, slug: opts.slug }));
288
+ const rec = (await remoteClient.api.projectCreate(rc(), name, opts.slug));
310
289
  // 输出瘦身:slug/name 足矣(id/tenantId/createdAt 是内部细节,不进 transcript)。
311
290
  const lean = { slug: rec?.slug, name: rec?.name };
312
291
  if (opts.use && rec?.slug)
@@ -322,29 +301,23 @@ projects
322
301
  projects
323
302
  .command("move <domain> <project>")
324
303
  .description("把域名挪进某项目(它的邮箱地址跟着走;媒体资产不动)")
325
- .action((d, p) => emit(remote ? remoteClient.api.projectMove(remote, d, p) : projectsCore.moveDomain(ctx(), d, p)));
304
+ .action((d, p) => emit(remoteClient.api.projectMove(rc(), d, p)));
326
305
  projects
327
306
  .command("rename <slug>")
328
307
  .description("改项目显示名 / 标识(default 不能改标识)")
329
308
  .option("--name <name>", "新显示名")
330
309
  .option("--slug <newSlug>", "新标识(检出过旧标识的目录要重新 clize use)")
331
- .action((slug, opts) => emit(remote
332
- ? remoteClient.api.projectRename(remote, slug, { name: opts.name, slug: opts.slug })
333
- : projectsCore.renameProject(ctx(), slug, { name: opts.name, slug: opts.slug })));
310
+ .action((slug, opts) => emit(remoteClient.api.projectRename(rc(), slug, { name: opts.name, slug: opts.slug })));
334
311
  projects
335
312
  .command("rm <slug>")
336
313
  .description("删项目(default 不可删;非空项目必须 --into 指定资源去向,绝不级联删资源)")
337
314
  .option("--into <project>", "把项目下资源(域名 + 媒体资产)归并到哪个项目,如 default")
338
- .action((slug, opts) => emit(remote
339
- ? remoteClient.api.projectRemove(remote, slug, opts.into)
340
- : projectsCore.removeProject(ctx(), slug, { into: opts.into })));
315
+ .action((slug, opts) => emit(remoteClient.api.projectRemove(rc(), slug, opts.into)));
341
316
  program
342
317
  .command("use <project>")
343
318
  .description("把当前目录切到某个项目(此后本目录的命令默认作用于它;写 ./clize.json)")
344
319
  .action((project) => emit((async () => {
345
- const list = (remote
346
- ? await remoteClient.api.projectsList(remote)
347
- : await projectsCore.listProjects(ctx()));
320
+ const list = (await remoteClient.api.projectsList(rc()));
348
321
  if (!Array.isArray(list) || !list.some((p) => p.slug === project))
349
322
  throw new Error(`没有叫 "${project}" 的项目;先看看有哪些:clize projects(新建:clize projects new "<名字>")`);
350
323
  return setupCore.useProject(project);
@@ -371,69 +344,55 @@ program
371
344
  program
372
345
  .command("check [domain]")
373
346
  .description("连通性自检:验证凭证 / 账户 / 域名查价(只读,不花钱)")
374
- .action((domain) => remote ? emit(remoteClient.check(remote)) : selfcheck(ctx(), domain));
347
+ .action(() => emit(remoteClient.check(rc())));
375
348
  withProject(program
376
349
  .command("audit")
377
350
  .description("查花钱 / 对外动作的审计记录(检出项目时只看该项目的花钱动作)")
378
- .option("-n, --limit <n>", "返回条数", "20")).action((opts) => emit(remote
379
- ? remoteClient.api.audit(remote, Number(opts.limit))
380
- : domainsCore.auditLog(ctx(), Number(opts.limit))));
351
+ .option("-n, --limit <n>", "返回条数", "20")).action((opts) => emit(remoteClient.api.audit(rc(), Number(opts.limit))));
381
352
  program
382
353
  .command("balance")
383
- .description("查 clize 余额 + 最近流水(托管模式;本地自带-CF 模式无余额概念)")
384
- .action(() => emit(remote ? remoteClient.api.balance(remote) : billingCore.balance(ctx())));
354
+ .description("查 clize 余额 + 流水")
355
+ .action(() => emit(remoteClient.api.balance(rc())));
385
356
  program
386
357
  .command("recharge")
387
- .description("充值 clize 余额(托管模式,最低 $25):自动打开 Stripe 付款页,付完余额自动到账")
358
+ .description("充值 clize 余额(最低 $25):自动打开 Stripe 付款页,付完余额自动到账")
388
359
  .requiredOption("--amount <usd>", "充值金额(美元)")
389
360
  .option("--no-open", "不自动打开浏览器,只返回付款链接(headless / agent 用)")
390
- .action((opts) => {
391
- if (!remote)
392
- return emit(Promise.reject(new Error("充值仅托管模式可用;本地自带-CF 模式费用直接走你的 Cloudflare 账户。")));
393
- return emit((async () => {
394
- const r = (await remoteClient.api.rechargeSession(remote, Number(opts.amount)));
395
- const opened = opts.open !== false && !!r.checkoutUrl;
396
- if (opened)
397
- openBrowser(r.checkoutUrl);
398
- return {
399
- ...r,
400
- opened,
401
- message: opened
402
- ? "已在浏览器打开付款页;没弹出就手动打开上面的 checkoutUrl。付完 `clize balance` 查看。"
403
- : "在浏览器打开 checkoutUrl 完成支付;付完 `clize balance` 查看。",
404
- };
405
- })());
406
- });
361
+ .action((opts) => emit((async () => {
362
+ const r = (await remoteClient.api.rechargeSession(rc(), Number(opts.amount)));
363
+ const opened = opts.open !== false && !!r.checkoutUrl;
364
+ if (opened)
365
+ openBrowser(r.checkoutUrl);
366
+ return {
367
+ ...r,
368
+ opened,
369
+ message: opened
370
+ ? "已在浏览器打开付款页;没弹出就手动打开上面的 checkoutUrl。付完 `clize balance` 查看。"
371
+ : "在浏览器打开 checkoutUrl 完成支付;付完 `clize balance` 查看。",
372
+ };
373
+ })()));
407
374
  const pay = program.command("pay").description("代客收费(托管):连接你的 Stripe,向你的客户收款");
408
375
  pay
409
376
  .command("connect")
410
377
  .description("连接你的 Stripe 账户(开浏览器授权;之后即可向客户收款)")
411
378
  .option("--no-open", "不自动打开浏览器,只返回授权链接(headless / agent 用)")
412
- .action((opts) => {
413
- if (!remote)
414
- return emit(Promise.reject(new Error("代客收费仅托管模式可用;先 clize login。")));
415
- return emit((async () => {
416
- const r = (await remoteClient.api.connectStart(remote));
417
- const opened = opts.open !== false && !!r.authorizeUrl;
418
- if (opened)
419
- openBrowser(r.authorizeUrl);
420
- return {
421
- ...r,
422
- opened,
423
- message: opened
424
- ? "已在浏览器打开 Stripe 授权页;授权后回来跑 `clize pay status`。"
425
- : "在浏览器打开 authorizeUrl 授权;完成后 `clize pay status` 查看。",
426
- };
427
- })());
428
- });
379
+ .action((opts) => emit((async () => {
380
+ const r = (await remoteClient.api.connectStart(rc()));
381
+ const opened = opts.open !== false && !!r.authorizeUrl;
382
+ if (opened)
383
+ openBrowser(r.authorizeUrl);
384
+ return {
385
+ ...r,
386
+ opened,
387
+ message: opened
388
+ ? "已在浏览器打开 Stripe 授权页;授权后回来跑 `clize pay status`。"
389
+ : "在浏览器打开 authorizeUrl 授权;完成后 `clize pay status` 查看。",
390
+ };
391
+ })()));
429
392
  pay
430
393
  .command("status")
431
394
  .description("查看你的收款账户状态(是否已连接 / 可收款)")
432
- .action(() => {
433
- if (!remote)
434
- return emit(Promise.reject(new Error("代客收费仅托管模式可用;先 clize login。")));
435
- return emit(remoteClient.api.connectStatus(remote));
436
- });
395
+ .action(() => emit(remoteClient.api.connectStatus(rc())));
437
396
  pay
438
397
  .command("link")
439
398
  .description("生成一条收款链接,发给你的客户付款(--mode direct=进你的 Stripe / balance=进 clize 余额)")
@@ -441,25 +400,17 @@ pay
441
400
  .option("--mode <mode>", "direct=进你的 Stripe(默认,需先 connect)| balance=进 clize 余额", "direct")
442
401
  .option("--to <ref>", "客户标识(邮箱 / 备注,仅记录展示)")
443
402
  .option("--for <purpose>", "收款事由(发票号 / 订单 / 询价 id)")
444
- .action((opts) => {
445
- if (!remote)
446
- return emit(Promise.reject(new Error("代客收费仅托管模式可用;先 clize login。")));
447
- return emit(remoteClient.api.payLink(remote, {
448
- amountUsd: Number(opts.amount),
449
- mode: opts.mode,
450
- customerRef: opts.to,
451
- purpose: opts.for,
452
- }));
453
- });
403
+ .action((opts) => emit(remoteClient.api.payLink(rc(), {
404
+ amountUsd: Number(opts.amount),
405
+ mode: opts.mode,
406
+ customerRef: opts.to,
407
+ purpose: opts.for,
408
+ })));
454
409
  pay
455
410
  .command("list")
456
411
  .description("列出你开出的收款单(最新在前)")
457
412
  .option("--limit <n>", "条数(默认 20)")
458
- .action((opts) => {
459
- if (!remote)
460
- return emit(Promise.reject(new Error("代客收费仅托管模式可用;先 clize login。")));
461
- return emit(remoteClient.api.payList(remote, opts.limit ? Number(opts.limit) : undefined));
462
- });
413
+ .action((opts) => emit(remoteClient.api.payList(rc(), opts.limit ? Number(opts.limit) : undefined)));
463
414
  const billing = program.command("billing").description("计费:绑卡 / 自动续充(托管模式)");
464
415
  billing
465
416
  .command("card")
@@ -467,12 +418,10 @@ billing
467
418
  .option("--remove", "移除已绑卡(同时关闭自动续充)")
468
419
  .option("--no-open", "不自动打开浏览器,只返回绑卡链接(headless / agent 用)")
469
420
  .action((opts) => {
470
- if (!remote)
471
- return emit(Promise.reject(new Error("自动续充仅托管模式可用;本地自带-CF 模式费用直接走你的 Cloudflare 账户。")));
472
421
  if (opts.remove)
473
- return emit(remoteClient.api.billingCardRemove(remote));
422
+ return emit(remoteClient.api.billingCardRemove(rc()));
474
423
  return emit((async () => {
475
- const r = (await remoteClient.api.billingSetupSession(remote));
424
+ const r = (await remoteClient.api.billingSetupSession(rc()));
476
425
  const opened = opts.open !== false && !!r.checkoutUrl;
477
426
  if (opened)
478
427
  openBrowser(r.checkoutUrl);
@@ -492,8 +441,6 @@ billing
492
441
  .option("--off", "关闭自动续充")
493
442
  .option("--amount <usd>", "每次自动续充额(美元,>=最低充值额)")
494
443
  .action((opts) => {
495
- if (!remote)
496
- return emit(Promise.reject(new Error("自动续充仅托管模式可用;本地自带-CF 模式费用直接走你的 Cloudflare 账户。")));
497
444
  if (opts.on && opts.off)
498
445
  return emit(Promise.reject(new Error("--on 和 --off 不能同时用。")));
499
446
  const patch = {};
@@ -505,21 +452,19 @@ billing
505
452
  patch.amountUsd = Number(opts.amount);
506
453
  if (patch.enabled === undefined && patch.amountUsd === undefined)
507
454
  return emit(Promise.reject(new Error("指定 --on / --off / --amount 之一。")));
508
- return emit(remoteClient.api.billingAutopay(remote, patch));
455
+ return emit(remoteClient.api.billingAutopay(rc(), patch));
509
456
  });
510
457
  withProject(program
511
458
  .command("context [address]")
512
- .description("会话开头 rehydrate:某地址的 身份+知识+操作规约 → 进 agent 的 system prompt(无地址=列地址)")).action((address) => emit(remote ? remoteClient.api.context(remote, address) : addressesCore.addressContext(ctx(), address)));
459
+ .description("会话开头 rehydrate:某地址的 身份+知识+操作规约 → 进 agent 的 system prompt(无地址=列地址)")).action((address) => emit(remoteClient.api.context(rc(), address)));
513
460
  withProject(program
514
461
  .command("claim <slug>")
515
462
  .description("占一个免费 handle <slug>.clize.app(先到先得;默认自动开 support@ 收信;归当前检出项目)")
516
- .option("--no-email", "只占名,不自动开邮箱")).action((slug, opts) => emit(remote
517
- ? remoteClient.api.claim(remote, slug)
518
- : handleCore.claimHandle(ctx(), slug, { provisionEmail: opts.email })));
463
+ .option("--no-email", "只占名,不自动开邮箱")).action((slug) => emit(remoteClient.api.claim(rc(), slug)));
519
464
  program
520
465
  .command("release <slug>")
521
466
  .description("释放自己的免费 handle(删 DNS + 身份,腾出名字;best-effort 拆站点绑定)")
522
- .action((slug) => emit(remote ? remoteClient.api.release(remote, slug) : handleCore.releaseHandle(ctx(), slug)));
467
+ .action((slug) => emit(remoteClient.api.release(rc(), slug)));
523
468
  // ───────── 域名 ─────────
524
469
  const domain = program.command("domain").description("域名:查可注册 / 买 / 接入 / 列表");
525
470
  domain
@@ -528,38 +473,25 @@ domain
528
473
  .option("--tld <tld>", "顶级域(query 不含点时用)", "com")
529
474
  .action((query, opts) => {
530
475
  const domains = query.map((q) => (q.includes(".") ? q : `${q}.${opts.tld}`));
531
- return emit(remote ? remoteClient.api.domainSearch(remote, domains) : domainsCore.searchDomains(ctx(), domains));
476
+ return emit(remoteClient.api.domainSearch(rc(), domains));
532
477
  });
533
478
  domain
534
479
  .command("tlds")
535
- .description("列出 domain search 支持的后缀(其余返回 extension_not_supported_via_api)")
536
- .action(() => emit({
537
- supportedTlds: domainsCore.SUPPORTED_TLDS,
538
- note: "Cloudflare 注册商 beta;.co / .io / .ai / .consulting / .agency 等暂不支持。",
539
- }));
480
+ .description("列出可注册后缀(CF 直注 + Vercel 补充;按 TLD 自动选注册商)")
481
+ .action(() => emit(remoteClient.api.listSupportedTlds(rc())));
540
482
  withProject(domain
541
483
  .command("buy <domain>")
542
484
  .description("注册域名(💰 花钱:必须 --confirm;缺则只返报价;归当前检出项目)")
543
485
  .option("--confirm", "确认花钱注册")
544
- .option("--no-auto-renew", "关闭自动续费(默认开,防过期)")).action((d, opts) => {
545
- if (remote)
546
- return emit(remoteClient.api.domainBuy(remote, d, !!opts.confirm, opts.autoRenew));
547
- if (!opts.confirm) {
548
- return emit(domainsCore.searchDomains(ctx(), [d]).then((quote) => ({
549
- quote,
550
- message: `这是报价。确认后加 --confirm 才会真注册:clize domain buy ${d} --confirm`,
551
- })));
552
- }
553
- return emit(domainsCore.registerDomain(ctx(), d, { autoRenew: opts.autoRenew }));
554
- });
555
- withProject(domain.command("import <domain>").description("接入已有域名(迁 NS 托管,幂等;新接入归当前检出项目)")).action((d) => emit(remote ? remoteClient.api.domainImport(remote, d) : domainsCore.importDomain(ctx(), d)));
556
- withProject(domain.command("list").description("列出本租户的域名(检出项目时只列该项目)")).action(() => emit(remote ? remoteClient.api.domainList(remote) : domainsCore.listDomains(ctx())));
486
+ .option("--no-auto-renew", "关闭自动续费(默认开,防过期)")).action((d, opts) => emit(remoteClient.api.domainBuy(rc(), d, !!opts.confirm, opts.autoRenew)));
487
+ withProject(domain.command("import <domain>").description("接入已有域名(迁 NS 托管,幂等;新接入归当前检出项目)")).action((d) => emit(remoteClient.api.domainImport(rc(), d)));
488
+ withProject(domain.command("list").description("列出本租户的域名(检出项目时只列该项目)")).action(() => emit(remoteClient.api.domainList(rc())));
557
489
  // ───────── 邮箱 ─────────
558
490
  const email = program.command("email").description("邮箱:配置 / 发信 / 收信");
559
491
  email
560
492
  .command("setup <domain>")
561
493
  .description("配自定义域收发链路(MX / SPF + 发信域名验证)")
562
- .action((d) => emit(remote ? remoteClient.api.emailSetup(remote, d) : emailCore.setupEmail(ctx(), d)));
494
+ .action((d) => emit(remoteClient.api.emailSetup(rc(), d)));
563
495
  withProject(email
564
496
  .command("send")
565
497
  .description("发信(📨 身份对外硬闸:默认只出草稿,加 --confirm 才真发)"))
@@ -576,31 +508,13 @@ withProject(email
576
508
  ? String(opts.to).split(",").map((s) => s.trim())
577
509
  : opts.to;
578
510
  const attachments = loadAttachments(opts.attach);
579
- // 托管模式:from 缺省同样从本地 clize.json 推导(CLI 在用户机器上跑,读得到);
580
- // confirm 透传给后端裁决草稿/真发。
581
- if (remote) {
582
- const rSender = opts.from
583
- ? { from: opts.from, replyTo: undefined }
584
- : setupCore.projectSender();
585
- if (!rSender?.from)
586
- return emit(Promise.reject(new Error("需 --from <地址>,或先 `clize init --handle <slug>` 绑定项目以自动带出发件人")));
587
- return emit(remoteClient.api.emailSend(remote, {
588
- from: rSender.from,
589
- to,
590
- subject: opts.subject,
591
- text: opts.text,
592
- html: opts.html,
593
- replyTo: rSender.replyTo,
594
- attachments,
595
- confirm: !!opts.confirm,
596
- allowDuplicate: !!opts.allowDuplicate,
597
- }));
598
- }
599
- // from 缺省时从项目(clize.json)带出发件人 + 回信地址
600
- const sender = opts.from ? { from: opts.from, replyTo: undefined } : setupCore.projectSender();
511
+ // from 缺省同样从本地 clize.json 推导(CLI 在用户机器上跑,读得到);confirm 透传给后端裁决草稿/真发。
512
+ const sender = opts.from
513
+ ? { from: opts.from, replyTo: undefined }
514
+ : setupCore.projectSender();
601
515
  if (!sender?.from)
602
516
  return emit(Promise.reject(new Error("需 --from <地址>,或先 `clize init --handle <slug>` 绑定项目以自动带出发件人")));
603
- const msg = {
517
+ return emit(remoteClient.api.emailSend(rc(), {
604
518
  from: sender.from,
605
519
  to,
606
520
  subject: opts.subject,
@@ -608,17 +522,9 @@ withProject(email
608
522
  html: opts.html,
609
523
  replyTo: sender.replyTo,
610
524
  attachments,
611
- };
612
- if (!opts.confirm)
613
- return emit({
614
- // 草稿里把附件 base64 折叠成「文件名 (类型, ~大小)」摘要,不污染 transcript。
615
- draft: {
616
- ...msg,
617
- attachments: attachments?.map((a) => `${a.filename} (${a.contentType ?? "?"}, ~${Math.round((a.content.length * 0.75) / 1024)}KB)`),
618
- },
619
- message: "📨 这是草稿,还没发。把它给用户看、确认后加 --confirm 才真发:clize email send … --confirm",
620
- });
621
- return emit(emailCore.sendEmail(ctx(), msg, { allowDuplicate: !!opts.allowDuplicate }));
525
+ confirm: !!opts.confirm,
526
+ allowDuplicate: !!opts.allowDuplicate,
527
+ }));
622
528
  });
623
529
  email
624
530
  .command("inbox <domain>")
@@ -626,19 +532,13 @@ email
626
532
  .option("-n, --limit <n>", "返回条数", "20")
627
533
  .option("--wait-for <pattern>", "轮询等待直到出现 subject/from/正文 含该串的信(如验证码)")
628
534
  .option("--timeout <sec>", "等待上限秒数(配 --wait-for)", "120")
629
- .action((d, opts) => emit(remote
630
- ? opts.waitFor
631
- ? remoteClient.waitForInboxRemote(remote, d, opts.waitFor, {
632
- timeoutSec: Number(opts.timeout),
633
- })
634
- : remoteClient.api.emailInbox(remote, d, Number(opts.limit))
635
- : opts.waitFor
636
- ? emailCore.waitForInbox(ctx(), d, opts.waitFor, { timeoutSec: Number(opts.timeout) })
637
- : emailCore.checkInbox(ctx(), d, Number(opts.limit))));
535
+ .action((d, opts) => emit(opts.waitFor
536
+ ? remoteClient.waitForInboxRemote(rc(), d, opts.waitFor, { timeoutSec: Number(opts.timeout) })
537
+ : remoteClient.api.emailInbox(rc(), d, Number(opts.limit))));
638
538
  email
639
539
  .command("show <domain> <id>")
640
540
  .description("读单封邮件全文")
641
- .action((d, id) => emit(remote ? remoteClient.api.emailShow(remote, d, id) : emailCore.getMessage(ctx(), d, id)));
541
+ .action((d, id) => emit(remoteClient.api.emailShow(rc(), d, id)));
642
542
  email
643
543
  .command("thread <contactOrDomain> [contact]")
644
544
  .description("读与某联系人的往来。init 过的目录单参即可:email thread <对方地址>;双参 = <收信域> <对方地址>")
@@ -648,25 +548,21 @@ email
648
548
  const contact = b ?? a;
649
549
  if (!domain)
650
550
  return emit(Promise.reject(new Error("没绑定收信域:clize email thread <domain> <contact>,或先 clize init 绑定本目录后单参使用")));
651
- return emit(remote ? remoteClient.api.emailThread(remote, domain, contact) : emailCore.emailThread(ctx(), domain, contact));
551
+ return emit(remoteClient.api.emailThread(rc(), domain, contact));
652
552
  });
653
553
  email
654
554
  .command("inbox-setup <address>")
655
555
  .description("开启 agent 自持收信(Email Worker 接住入站,存收件箱)")
656
556
  .option("--forward <to>", "同时转发到个人邮箱")
657
- .action((address, opts) => emit(remote
658
- ? remoteClient.api.emailInboxSetup(remote, address, opts.forward)
659
- : emailCore.setupInbox(ctx(), address, opts.forward)));
557
+ .action((address, opts) => emit(remoteClient.api.emailInboxSetup(rc(), address, opts.forward)));
660
558
  email
661
559
  .command("route <from> <to>")
662
560
  .description("配转发规则:发往 from 的邮件转发到 to")
663
- .action((from, to) => emit(remote ? remoteClient.api.emailRoute(remote, from, to) : emailCore.routeEmail(ctx(), from, to)));
561
+ .action((from, to) => emit(remoteClient.api.emailRoute(rc(), from, to)));
664
562
  email
665
563
  .command("webhook <address> <url>")
666
564
  .description("配置入站推送:来信时 POST 信封到 url(与 status pull 并存)")
667
- .action((address, url) => emit(remote
668
- ? remoteClient.api.emailWebhook(remote, address, url)
669
- : emailCore.setEmailWebhook(ctx(), address, url)));
565
+ .action((address, url) => emit(remoteClient.api.emailWebhook(rc(), address, url)));
670
566
  // 地址:tag + knowledge(客服等场景;无 build / 角色 / 记忆)
671
567
  const address = email.command("address").description("邮箱地址:加 / 改(tag + knowledge)");
672
568
  address
@@ -674,77 +570,49 @@ address
674
570
  .description("加一个地址(--tag 分类;--knowledge 挂静态知识,作答时进 system prompt)")
675
571
  .option("--tag <t>", "标签,如 客服")
676
572
  .option("--knowledge <path>", "知识文件或目录")
677
- .action((addr, opts) => emit(remote
678
- ? remoteClient.api.emailAddressAdd(remote, {
679
- address: addr,
680
- tag: opts.tag,
681
- knowledge: opts.knowledge ? addressesCore.readKnowledge(opts.knowledge) : undefined,
682
- })
683
- : addressesCore.addAddress(ctx(), addr, { tag: opts.tag, knowledgePath: opts.knowledge })));
573
+ .action((addr, opts) => emit(remoteClient.api.emailAddressAdd(rc(), {
574
+ address: addr,
575
+ tag: opts.tag,
576
+ knowledge: opts.knowledge ? addressesCore.readKnowledge(opts.knowledge) : undefined,
577
+ })));
684
578
  address
685
579
  .command("update <addr>")
686
580
  .description("改地址的 tag / knowledge(未传的不动)")
687
581
  .option("--tag <t>", "标签")
688
582
  .option("--knowledge <path>", "知识文件或目录")
689
- .action((addr, opts) => emit(remote
690
- ? remoteClient.api.emailAddressUpdate(remote, {
691
- address: addr,
692
- tag: opts.tag,
693
- knowledge: opts.knowledge ? addressesCore.readKnowledge(opts.knowledge) : undefined,
694
- })
695
- : addressesCore.updateAddress(ctx(), addr, { tag: opts.tag, knowledgePath: opts.knowledge })));
583
+ .action((addr, opts) => emit(remoteClient.api.emailAddressUpdate(rc(), {
584
+ address: addr,
585
+ tag: opts.tag,
586
+ knowledge: opts.knowledge ? addressesCore.readKnowledge(opts.knowledge) : undefined,
587
+ })));
696
588
  address
697
589
  .command("remove <addr>")
698
590
  .description("删一个地址配置(收件箱历史邮件不动)")
699
- .action((addr) => emit(remote
700
- ? remoteClient.api.emailAddressRemove(remote, addr)
701
- : addressesCore.removeAddress(ctx(), addr)));
702
- withProject(email.command("list").description("列出所有地址 + tag(检出项目时只列该项目域名下的)")).action(() => emit(remote ? remoteClient.api.emailListAddresses(remote) : addressesCore.listAddresses(ctx())));
591
+ .action((addr) => emit(remoteClient.api.emailAddressRemove(rc(), addr)));
592
+ withProject(email.command("list").description("列出所有地址 + tag(检出项目时只列该项目域名下的)")).action(() => emit(remoteClient.api.emailListAddresses(rc())));
703
593
  // ───────── 部署 ─────────
704
594
  withProject(program
705
595
  .command("deploy <path>")
706
- .description("部署:目录 + <slug>.clize.app → 免费 handle 多文件站;或 <名> --html 内联单页")
707
- .option("--domain <domain>", "目标域(<slug>.clize.app 走免费 handle)")
708
- .option("--html <html>", "内联 HTML(单页 worker,不读目录)")
709
- .option("--html-file <file>", "从文件读 HTML")).action((p, opts) => {
596
+ .description("部署:目录 + <slug>.clize.app → 免费 handle 多文件站")
597
+ .option("--domain <domain>", "目标域(<slug>.clize.app 走免费 handle)")).action((p, opts) => {
710
598
  // 缺 --domain 时从 ./clize.json 推导(clize init 绑过的目录免显式传参,与 email send 推 from 对称)。
711
599
  const domain = opts.domain ?? setupCore.projectDomain();
712
- if (remote) {
713
- let dir = false;
714
- try {
715
- dir = fs.statSync(p).isDirectory();
716
- }
717
- catch {
718
- /* 非路径 */
719
- }
720
- if (!dir)
721
- return emit(Promise.reject(new Error(`${p} 不是存在的目录 —— 先生成站点文件再 deploy(托管模式只支持目录部署;--html 内联单页暂仅本地模式)`)));
722
- if (!domain)
723
- return emit(Promise.reject(new Error("缺目标域:clize deploy <目录> --domain <已 claim 的 host>,或先 clize init --handle <slug> 绑定本目录免传 --domain")));
724
- const files = sitesCore.readDirFiles(p);
725
- const bytes = files.reduce((n, f) => n + Math.ceil(f.base64.length * 0.75), 0);
726
- if (bytes > 25 * 1024 * 1024)
727
- return emit(Promise.reject(new Error(`站点约 ${(bytes / 1048576).toFixed(1)}MB,超过托管部署上限 25MB;请精简`)));
728
- return emit(remoteClient.api.deploy(remote, domain, files));
729
- }
730
- let isDir = false;
600
+ let dir = false;
731
601
  try {
732
- isDir = fs.statSync(p).isDirectory();
602
+ dir = fs.statSync(p).isDirectory();
733
603
  }
734
604
  catch {
735
- /* 非路径:仅显式 --html/--html-file 时才允许当 worker 名走内联(否则下面报"目录不存在") */
736
- }
737
- if (isDir) {
738
- if (!domain)
739
- return emit(Promise.reject(new Error("目录(多文件)部署需要 --domain <域名>(需已 claim / buy / import),或先 clize init 绑定本目录")));
740
- return emit(sitesCore.deployToHandle(ctx(), domain, p));
605
+ /* 非路径 */
741
606
  }
742
- // 路径不存在且没给内联 HTML:大概率是站点还没生成 / 路径打错 —— 直接说清,
743
- // 绝不带着错参数去打 CF(那会报回毫不相干的 workers.dev 错误,误导排查方向)
744
- if (!opts.html && !opts.htmlFile)
745
- return emit(Promise.reject(new Error(`${p} 不是存在的目录 —— 先生成站点文件再 deploy;内联单页要显式 --html "<...>" --html-file <path>`)));
746
- const html = opts.htmlFile ? fs.readFileSync(opts.htmlFile, "utf8") : opts.html;
747
- return emit(siteCore.deploySite(ctx(), { workerName: p, html, domain: opts.domain }));
607
+ if (!dir)
608
+ return emit(Promise.reject(new Error(`${p} 不是存在的目录 —— 先生成站点文件再 deploy(托管部署支持目录部署)`)));
609
+ if (!domain)
610
+ return emit(Promise.reject(new Error("缺目标域:clize deploy <目录> --domain <已 claim host>,或先 clize init --handle <slug> 绑定本目录免传 --domain")));
611
+ const files = sitesCore.readDirFiles(p);
612
+ const bytes = files.reduce((n, f) => n + Math.ceil(f.base64.length * 0.75), 0);
613
+ if (bytes > 25 * 1024 * 1024)
614
+ return emit(Promise.reject(new Error(`站点约 ${(bytes / 1048576).toFixed(1)}MB,超过托管部署上限 25MB;请精简`)));
615
+ return emit(remoteClient.api.deploy(rc(), domain, files));
748
616
  });
749
617
  // ───────── 本地预览(serve:deploy 的本地对偶)─────────
750
618
  // 起一个支持 Range + 媒体不灌 no-store 的静态 server —— 预览含 <video> 的站点 / 片子用它,
@@ -774,8 +642,7 @@ program
774
642
  }
775
643
  });
776
644
  // ───────── 生成式媒体(gen:图 / 视频 / 音乐)─────────
777
- // G1 = 本地算力 + 你自带的 model key,与 clize 登录模式无关(remote 登录也在本地跑、写本地盘);
778
- // 托管(clize 代付 + R2 托管)是 G2,所以这里不按 remote 分流。
645
+ // 纯托管:平台代付 + 字节回 CLI 落盘(G2);提交 / 报价 / 轮询全经控制面,CLI 只负责读参考图、落盘成品。
779
646
  const gen = program
780
647
  .command("gen")
781
648
  .description("生成式媒体:你写 prompt,clize 产字节落盘(图 / 视频 / 音乐;蓝图批量 = check + render)");
@@ -790,51 +657,30 @@ withProject(gen
790
657
  .option("--out <path>", "落盘路径(默认 ./clize-assets/<id>_<i>.png)")
791
658
  .option("--confirm", "确认花钱生成")
792
659
  .action((prompt, opts) => {
793
- if (remote) {
794
- if (opts.ref || opts.mask)
795
- return emit(Promise.reject(new Error("托管模式暂不支持参考图 / 蒙版(--ref/--mask);用纯文生图,或切本地模式。")));
796
- return emit(remoteClient.api
797
- .genImage(remote, {
798
- prompt,
799
- model: opts.model,
800
- n: Number(opts.n),
801
- size: opts.size,
802
- confirm: !!opts.confirm,
803
- })
804
- .then((r) => mediaCore.materializeRemote(r, opts.out)));
805
- }
806
- return emit(mediaCore.genImage(ctx(), {
660
+ if (opts.ref || opts.mask)
661
+ return emit(Promise.reject(new Error("托管模式暂不支持参考图 / 蒙版(--ref/--mask);用纯文生图。")));
662
+ return emit(remoteClient.api
663
+ .genImage(rc(), {
807
664
  prompt,
808
665
  model: opts.model,
809
666
  n: Number(opts.n),
810
667
  size: opts.size,
811
- refs: opts.ref
812
- ? String(opts.ref)
813
- .split(",")
814
- .map((s) => s.trim())
815
- : undefined,
816
- mask: opts.mask,
817
- out: opts.out,
818
- }, { confirm: !!opts.confirm }));
668
+ confirm: !!opts.confirm,
669
+ })
670
+ .then((r) => mediaCore.materializeRemote(r, opts.out)));
819
671
  });
820
672
  withProject(gen
821
673
  .command("list")
822
674
  .description("列已生成资产(id / 模型 / prompt / 路径 / 花费;检出项目时只列该项目)")
823
- .option("--modality <m>", "只看某模态:image | video | music")).action((opts) => emit(remote ? remoteClient.api.genList(remote, opts.modality) : mediaCore.listAssets(ctx(), opts.modality)));
675
+ .option("--modality <m>", "只看某模态:image | video | music")).action((opts) => emit(remoteClient.api.genList(rc(), opts.modality)));
824
676
  gen
825
677
  .command("show <id>")
826
678
  .description("看单个生成任务 / 资产全貌")
827
- .action((id) => emit(remote ? remoteClient.api.genShow(remote, id) : mediaCore.getAsset(ctx(), id)));
679
+ .action((id) => emit(remoteClient.api.genShow(rc(), id)));
828
680
  gen
829
681
  .command("budget [set] [usd]")
830
682
  .description("设 / 查生成预批额度:额度内免逐次 --confirm(clize gen budget set 2.00 / clize gen budget)")
831
- .action((setOrUsd, usd) => {
832
- if (remote)
833
- return emit(Promise.reject(new Error("gen budget 托管模式暂未支持(二期);托管下每次生成用 --confirm 逐次确认即可。")));
834
- const raw = setOrUsd === "set" ? usd : setOrUsd;
835
- const amount = raw != null && raw !== "" && !Number.isNaN(Number(raw)) ? Number(raw) : undefined;
836
- return emit(amount != null ? mediaCore.setGenBudget(ctx(), amount) : mediaCore.genBudget(ctx()));
837
- });
683
+ .action(() => emit(Promise.reject(new Error("gen budget 托管模式暂未支持(二期);托管下每次生成用 --confirm 逐次确认即可。"))));
838
684
  withProject(gen
839
685
  .command("video <prompt>")
840
686
  .description("文生视频 / 图生视频(长任务:默认等到完成,--async 后台返 job id)"))
@@ -848,27 +694,16 @@ withProject(gen
848
694
  .option("--timeout <sec>", "默认等待上限(秒)", "300")
849
695
  .option("--confirm", "确认花钱生成")
850
696
  .action((prompt, opts) => {
851
- if (remote) {
852
- // 托管:参考图在 CLI 端读盘转 data URI 上传(首帧排第一,与本地 core 同序)
853
- const paths = [...new Set([opts.fromImage, ...splitRefs(opts.ref)].filter(Boolean))];
854
- return emit((async () => remoteGenAsync(remote, "video", {
855
- prompt,
856
- model: opts.model,
857
- refs: paths.length ? refsToDataUris(paths) : undefined,
858
- durationSec: opts.duration ? Number(opts.duration) : undefined,
859
- aspect: opts.aspect,
860
- confirm: !!opts.confirm,
861
- }, { confirm: !!opts.confirm, async: !!opts.async, out: opts.out, timeout: opts.timeout }))());
862
- }
863
- return emit(mediaCore.genVideo(ctx(), {
697
+ // 托管:参考图在 CLI 端读盘转 data URI 上传(首帧排第一,与后端同序)
698
+ const paths = [...new Set([opts.fromImage, ...splitRefs(opts.ref)].filter(Boolean))];
699
+ return emit((async () => remoteGenAsync(rc(), "video", {
864
700
  prompt,
865
701
  model: opts.model,
866
- fromImage: opts.fromImage,
867
- refs: splitRefs(opts.ref),
702
+ refs: paths.length ? refsToDataUris(paths) : undefined,
868
703
  durationSec: opts.duration ? Number(opts.duration) : undefined,
869
704
  aspect: opts.aspect,
870
- out: opts.out,
871
- }, { confirm: !!opts.confirm, async: !!opts.async, timeoutSec: Number(opts.timeout) }));
705
+ confirm: !!opts.confirm,
706
+ }, { confirm: !!opts.confirm, async: !!opts.async, out: opts.out, timeout: opts.timeout }))());
872
707
  });
873
708
  withProject(gen
874
709
  .command("music <prompt>")
@@ -880,37 +715,24 @@ withProject(gen
880
715
  .option("--async", "后台生成,只返 job id(默认等到完成)")
881
716
  .option("--timeout <sec>", "默认等待上限(秒)", "300")
882
717
  .option("--confirm", "确认花钱生成")
883
- .action((prompt, opts) => {
884
- if (remote) {
885
- return emit(remoteGenAsync(remote, "music", {
886
- prompt,
887
- model: opts.model,
888
- instrumental: !!opts.instrumental,
889
- durationSec: opts.duration ? Number(opts.duration) : undefined,
890
- confirm: !!opts.confirm,
891
- }, { confirm: !!opts.confirm, async: !!opts.async, out: opts.out, timeout: opts.timeout }));
892
- }
893
- return emit(mediaCore.genMusic(ctx(), {
894
- prompt,
895
- model: opts.model,
896
- instrumental: !!opts.instrumental,
897
- durationSec: opts.duration ? Number(opts.duration) : undefined,
898
- out: opts.out,
899
- }, { confirm: !!opts.confirm, async: !!opts.async, timeoutSec: Number(opts.timeout) }));
900
- });
901
- withProject(gen.command("jobs").description("列生成任务(running 置顶;断会话后重连长任务用;检出项目时只列该项目)")).action(() => emit(remote ? remoteClient.api.genJobs(remote) : mediaCore.genJobs(ctx())));
718
+ .action((prompt, opts) => emit(remoteGenAsync(rc(), "music", {
719
+ prompt,
720
+ model: opts.model,
721
+ instrumental: !!opts.instrumental,
722
+ durationSec: opts.duration ? Number(opts.duration) : undefined,
723
+ confirm: !!opts.confirm,
724
+ }, { confirm: !!opts.confirm, async: !!opts.async, out: opts.out, timeout: opts.timeout })));
725
+ withProject(gen.command("jobs").description("列生成任务(running 置顶;断会话后重连长任务用;检出项目时只列该项目)")).action(() => emit(remoteClient.api.genJobs(rc())));
902
726
  gen
903
727
  .command("status <id>")
904
728
  .description("查单任务:running 则向 provider 轮询一次(可能就此完成落盘)")
905
- .action((id) => emit(remote
906
- ? remoteClient.api
907
- .genStatus(remote, id)
908
- .then((r) => mediaCore.materializeRemote(r, undefined))
909
- : mediaCore.genJobStatus(ctx(), id)));
729
+ .action((id) => emit(remoteClient.api
730
+ .genStatus(rc(), id)
731
+ .then((r) => mediaCore.materializeRemote(r, undefined))));
910
732
  gen
911
733
  .command("rm <id>")
912
734
  .description("删任务记录 + 本地文件")
913
- .action((id) => emit(remote ? remoteClient.api.genRm(remote, id) : mediaCore.genRm(ctx(), id)));
735
+ .action((id) => emit(remoteClient.api.genRm(rc(), id)));
914
736
  // ───────── build:作品域(端到端做一个作品;方法 + 校验免费,成品步骤花钱带闸)─────────
915
737
  // taxonomy:gen = 原子生成 + 任务池(引擎),build = 作品编排门面(方法 → 你创作 → 校验 → 成品)。
916
738
  // 第 N 种作品 = build 加子域(方法三件套 + 该作品特有的校验/成品命令),顶级命令面零膨胀。
@@ -926,32 +748,32 @@ function siteBuildCommands() {
926
748
  .argument("<brief...>")
927
749
  .description("开工:返回工作流(7 步 + 反 slop)+ 风格目录 + 针对 brief 的推荐")
928
750
  .option("--project <name>", "项目名")
929
- .action((brief, opts) => hostedBuild((rc) => remoteClient.api.buildSiteStart(rc, brief.join(" "), opts.project)));
751
+ .action((brief, opts) => hostedBuild((rcfg) => remoteClient.api.buildSiteStart(rcfg, brief.join(" "), opts.project)));
930
752
  const recommend = new Command("recommend")
931
753
  .argument("<brief...>")
932
754
  .description("按 brief 推荐:风格 + 调色板 + 字体对 + 反模式")
933
755
  .option("--project <name>", "项目名")
934
- .action((brief, opts) => hostedBuild((rc) => remoteClient.api.buildSiteRecommend(rc, brief.join(" "), opts.project)));
756
+ .action((brief, opts) => hostedBuild((rcfg) => remoteClient.api.buildSiteRecommend(rcfg, brief.join(" "), opts.project)));
935
757
  const list = new Command("list")
936
758
  .description("列出全部风格宪法(slug + 一句话 best-for)")
937
- .action(() => hostedBuild((rc) => remoteClient.api.buildSiteList(rc)));
759
+ .action(() => hostedBuild((rcfg) => remoteClient.api.buildSiteList(rcfg)));
938
760
  const get = new Command("get")
939
761
  .argument("<slug>")
940
762
  .description("取某风格的完整宪法(色 / 字 / 形 / 签名手法)+ imagery 配图契约 + 调色板 / 字体对 + DESIGN.md 模板")
941
- .action((slug) => hostedBuild((rc) => remoteClient.api.buildSiteGet(rc, slug)));
763
+ .action((slug) => hostedBuild((rcfg) => remoteClient.api.buildSiteGet(rcfg, slug)));
942
764
  const stack = new Command("stack")
943
765
  .argument("<stack>")
944
766
  .argument("[query...]")
945
767
  .description("取某技术栈实现指南(React/Next/SwiftUI/Flutter/shadcn… 惯用法 + 行为规则;跨栈 UI 指南)")
946
- .action((stackName, query) => hostedBuild((rc) => remoteClient.api.buildSiteStack(rc, stackName, (query || []).join(" "))));
768
+ .action((stackName, query) => hostedBuild((rcfg) => remoteClient.api.buildSiteStack(rcfg, stackName, (query || []).join(" "))));
947
769
  const search = new Command("search")
948
770
  .argument("<query...>")
949
771
  .description("查 UUPM 库(ux/color/chart/landing/product/typography/icons…;默认自动判域)")
950
772
  .option("--domain <d>", "限定域")
951
- .action((query, opts) => hostedBuild((rc) => remoteClient.api.buildSiteSearch(rc, query.join(" "), opts.domain)));
773
+ .action((query, opts) => hostedBuild((rcfg) => remoteClient.api.buildSiteSearch(rcfg, query.join(" "), opts.domain)));
952
774
  const review = new Command("review")
953
775
  .description("取合并自审清单(anti-slop + DESIGN.md 保真 + UX 行为)")
954
- .action(() => hostedBuild((rc) => remoteClient.api.buildSiteReview(rc)));
776
+ .action(() => hostedBuild((rcfg) => remoteClient.api.buildSiteReview(rcfg)));
955
777
  return [start, recommend, list, get, stack, search, review];
956
778
  }
957
779
  const buildSite = build
@@ -980,34 +802,8 @@ function readBlueprint(file) {
980
802
  throw new Error(`蓝图不是合法 JSON:${e instanceof Error ? e.message : String(e)}`);
981
803
  }
982
804
  }
983
- /** render 后端(本地):探价走 provider.quote;提交/轮询走 core/media(任务进 gen jobs 体系)。 */
984
- function localRenderBackend() {
985
- return {
986
- quote: (p) => mediaCore.quoteVideo(ctx(), p),
987
- submit: async (p) => {
988
- const r = (await mediaCore.genVideo(ctx(), { ...p }, { confirm: true, async: true }));
989
- if (!r.id)
990
- throw new Error(`提交未返回任务 id${r.message ? `:${String(r.message)}` : ""}`);
991
- return { id: String(r.id), state: String(r.state ?? "running") };
992
- },
993
- poll: async (id, outPath) => {
994
- const r = (await mediaCore.genJobStatus(ctx(), id));
995
- if (r.state === "succeeded") {
996
- const src = r.files?.[0]?.path;
997
- if (src && path.resolve(src) !== path.resolve(outPath)) {
998
- fs.mkdirSync(path.dirname(outPath), { recursive: true });
999
- fs.copyFileSync(src, outPath);
1000
- }
1001
- return { state: "succeeded" };
1002
- }
1003
- if (r.state === "failed")
1004
- return { state: "failed", error: r.error ? String(r.error) : undefined };
1005
- return { state: "running" };
1006
- },
1007
- };
1008
- }
1009
805
  /** render 后端(托管):参考图逐镜转 data URI 上传;confirm=false 探价是后端契约(永不生成)。 */
1010
- function remoteRenderBackend(rc) {
806
+ function remoteRenderBackend(rcfg) {
1011
807
  const body = (p, confirm) => ({
1012
808
  prompt: p.prompt,
1013
809
  model: p.model,
@@ -1018,20 +814,20 @@ function remoteRenderBackend(rc) {
1018
814
  });
1019
815
  return {
1020
816
  quote: async (p) => {
1021
- const r = (await remoteClient.api.genVideo(rc, body(p, false)));
817
+ const r = (await remoteClient.api.genVideo(rcfg, body(p, false)));
1022
818
  const est = r?.quote?.estUsd;
1023
819
  if (typeof est !== "number")
1024
820
  throw new Error("探价未返回 quote(后端响应异常)");
1025
821
  return est;
1026
822
  },
1027
823
  submit: async (p) => {
1028
- const r = (await remoteClient.api.genVideo(rc, body(p, true)));
824
+ const r = (await remoteClient.api.genVideo(rcfg, body(p, true)));
1029
825
  if (!r.id)
1030
826
  throw new Error(`提交未返回任务 id${r.message ? `:${String(r.message)}` : ""}`);
1031
827
  return { id: String(r.id), state: String(r.state ?? "running") };
1032
828
  },
1033
829
  poll: async (id, outPath) => {
1034
- const r = (await remoteClient.api.genStatus(rc, id));
830
+ const r = (await remoteClient.api.genStatus(rcfg, id));
1035
831
  if (r.state === "succeeded") {
1036
832
  await mediaCore.materializeRemote(r, outPath);
1037
833
  return { state: "succeeded" };
@@ -1049,15 +845,15 @@ const buildClip = build
1049
845
  buildClip
1050
846
  .command("start <brief...>")
1051
847
  .description("开工:返回方法(4 阶段 + 蓝图 schema + 反穿帮清单)+ 风格目录 + 针对 brief 的推荐")
1052
- .action((brief) => hostedBuild((rc) => remoteClient.api.buildClipStart(rc, brief.join(" "))));
848
+ .action((brief) => hostedBuild((rcfg) => remoteClient.api.buildClipStart(rcfg, brief.join(" "))));
1053
849
  buildClip
1054
850
  .command("list")
1055
851
  .description("列出全部视频风格(id + 一句话适用场景)")
1056
- .action(() => hostedBuild((rc) => remoteClient.api.buildClipList(rc)));
852
+ .action(() => hostedBuild((rcfg) => remoteClient.api.buildClipList(rcfg)));
1057
853
  buildClip
1058
854
  .command("get <slug>")
1059
855
  .description("取某风格完整包(资产图模板 + 视频视觉词汇 + strict 后端替换行)")
1060
- .action((slug) => hostedBuild((rc) => remoteClient.api.buildClipGet(rc, slug)));
856
+ .action((slug) => hostedBuild((rcfg) => remoteClient.api.buildClipGet(rcfg, slug)));
1061
857
  buildClip
1062
858
  .command("check <blueprint>")
1063
859
  .description("蓝图机械校验(本地、免费、零凭证):防穿帮 lint + 台词覆盖 + 时长公式 + 结构;error 清零才 render")
@@ -1069,7 +865,7 @@ withProject(buildClip
1069
865
  .option("--out-dir <dir>", "输出目录(默认 clize-video/<title>)")
1070
866
  .option("--timeout <sec>", "轮询总时限(秒)", "1800")
1071
867
  .option("--confirm", "确认花钱生成(报价见无 --confirm 的输出)")
1072
- .action((file, opts) => emit(videoCore.renderBlueprint(remote ? remoteRenderBackend(remote) : localRenderBackend(), readBlueprint(file), {
868
+ .action((file, opts) => emit(videoCore.renderBlueprint(remoteRenderBackend(rc()), readBlueprint(file), {
1073
869
  confirm: !!opts.confirm,
1074
870
  candidates: Number(opts.candidates) || 1,
1075
871
  outDir: opts.outDir,