@clize/clize 0.10.1 → 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.
- package/README.md +9 -11
- package/dist/cli.js +169 -373
- package/dist/cli.js.map +1 -1
- package/dist/config.js +1 -12
- package/dist/config.js.map +1 -1
- package/dist/context.js +32 -50
- package/dist/context.js.map +1 -1
- package/dist/core/billing.js +21 -17
- package/dist/core/billing.js.map +1 -1
- package/dist/core/credentials.js +30 -0
- package/dist/core/credentials.js.map +1 -0
- package/dist/core/domains.js +59 -18
- package/dist/core/domains.js.map +1 -1
- package/dist/core/setup.js +3 -26
- package/dist/core/setup.js.map +1 -1
- package/dist/core/sites.js +59 -20
- package/dist/core/sites.js.map +1 -1
- package/dist/index.js +40 -90
- package/dist/index.js.map +1 -1
- package/dist/lib/cloudflare.js +4 -0
- package/dist/lib/cloudflare.js.map +1 -1
- package/dist/lib/crypto.js +57 -0
- package/dist/lib/crypto.js.map +1 -0
- package/dist/lib/vercel.js +67 -0
- package/dist/lib/vercel.js.map +1 -0
- package/dist/providers/index.js +15 -3
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/registrar/cloudflare.js +4 -0
- package/dist/providers/registrar/cloudflare.js.map +1 -1
- package/dist/providers/registrar/routing.js +91 -0
- package/dist/providers/registrar/routing.js.map +1 -0
- package/dist/providers/registrar/vercel.js +114 -0
- package/dist/providers/registrar/vercel.js.map +1 -0
- package/dist/remote.js +13 -8
- package/dist/remote.js.map +1 -1
- package/dist/state/file-store.js +7 -0
- package/dist/state/file-store.js.map +1 -1
- package/package.json +1 -1
- 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
|
-
// •
|
|
5
|
-
// •
|
|
6
|
-
//
|
|
7
|
-
//
|
|
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
|
-
|
|
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(
|
|
162
|
+
async function remoteGenAsync(rcfg, modality, body, opts) {
|
|
169
163
|
const submit = (await (modality === "video"
|
|
170
|
-
? remoteClient.api.genVideo(
|
|
171
|
-
: remoteClient.api.genMusic(
|
|
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(
|
|
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 选项取值 →
|
|
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
|
-
//
|
|
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
|
|
232
|
+
// build clip check 纯本地零凭证(蓝图机械校验,不碰云)。
|
|
243
233
|
if (inCommandTree(actionCmd, "build") || inCommandTree(actionCmd, "design"))
|
|
244
234
|
return;
|
|
245
|
-
if (remote
|
|
235
|
+
if (remote)
|
|
246
236
|
return;
|
|
247
237
|
console.error("❌ 未登录。\n" +
|
|
248
|
-
" 登录即用(免配任何 key):clize login # GitHub
|
|
249
|
-
"
|
|
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(
|
|
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>", "
|
|
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(
|
|
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 = (
|
|
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(
|
|
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(
|
|
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(
|
|
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 = (
|
|
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((
|
|
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(
|
|
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 余额 +
|
|
384
|
-
.action(() => emit(
|
|
354
|
+
.description("查 clize 余额 + 流水")
|
|
355
|
+
.action(() => emit(remoteClient.api.balance(rc())));
|
|
385
356
|
program
|
|
386
357
|
.command("recharge")
|
|
387
|
-
.description("充值 clize 余额(
|
|
358
|
+
.description("充值 clize 余额(最低 $25):自动打开 Stripe 付款页,付完余额自动到账")
|
|
388
359
|
.requiredOption("--amount <usd>", "充值金额(美元)")
|
|
389
360
|
.option("--no-open", "不自动打开浏览器,只返回付款链接(headless / agent 用)")
|
|
390
|
-
.action((opts) => {
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
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
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
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
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
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(
|
|
422
|
+
return emit(remoteClient.api.billingCardRemove(rc()));
|
|
474
423
|
return emit((async () => {
|
|
475
|
-
const r = (await remoteClient.api.billingSetupSession(
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
476
|
+
return emit(remoteClient.api.domainSearch(rc(), domains));
|
|
532
477
|
});
|
|
533
478
|
domain
|
|
534
479
|
.command("tlds")
|
|
535
|
-
.description("
|
|
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
|
-
|
|
546
|
-
|
|
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(
|
|
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
|
-
//
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
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
|
-
|
|
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
|
-
|
|
613
|
-
|
|
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(
|
|
630
|
-
? opts.waitFor
|
|
631
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
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(
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
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(
|
|
700
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
602
|
+
dir = fs.statSync(p).isDirectory();
|
|
733
603
|
}
|
|
734
604
|
catch {
|
|
735
|
-
/*
|
|
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
|
-
|
|
743
|
-
|
|
744
|
-
if (!
|
|
745
|
-
return emit(Promise.reject(new Error(
|
|
746
|
-
const
|
|
747
|
-
|
|
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
|
-
//
|
|
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 (
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
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
|
-
|
|
812
|
-
|
|
813
|
-
|
|
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(
|
|
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(
|
|
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((
|
|
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
|
-
|
|
852
|
-
|
|
853
|
-
|
|
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
|
-
|
|
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
|
-
|
|
871
|
-
}, { confirm: !!opts.confirm, async: !!opts.async,
|
|
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
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
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(
|
|
906
|
-
|
|
907
|
-
|
|
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(
|
|
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((
|
|
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((
|
|
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((
|
|
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((
|
|
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((
|
|
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((
|
|
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((
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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((
|
|
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((
|
|
852
|
+
.action(() => hostedBuild((rcfg) => remoteClient.api.buildClipList(rcfg)));
|
|
1057
853
|
buildClip
|
|
1058
854
|
.command("get <slug>")
|
|
1059
855
|
.description("取某风格完整包(资产图模板 + 视频视觉词汇 + strict 后端替换行)")
|
|
1060
|
-
.action((slug) => hostedBuild((
|
|
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(
|
|
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,
|