@clize/clize 0.10.1 → 0.12.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 +207 -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/dns.js +27 -1
- package/dist/core/dns.js.map +1 -1
- package/dist/core/domains.js +134 -19
- package/dist/core/domains.js.map +1 -1
- package/dist/core/reconcile.js +127 -0
- package/dist/core/reconcile.js.map +1 -0
- package/dist/core/setup.js +3 -26
- package/dist/core/setup.js.map +1 -1
- package/dist/core/sites.js +72 -20
- package/dist/core/sites.js.map +1 -1
- package/dist/index.js +54 -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/lib/zone.js +12 -0
- package/dist/lib/zone.js.map +1 -1
- package/dist/providers/dns/cloudflare.js +8 -0
- package/dist/providers/dns/cloudflare.js.map +1 -1
- package/dist/providers/index.js +15 -3
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/registrar/cloudflare.js +12 -22
- package/dist/providers/registrar/cloudflare.js.map +1 -1
- package/dist/providers/registrar/routing.js +98 -0
- package/dist/providers/registrar/routing.js.map +1 -0
- package/dist/providers/registrar/vercel.js +134 -0
- package/dist/providers/registrar/vercel.js.map +1 -0
- package/dist/remote.js +20 -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,63 @@ 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
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
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())));
|
|
489
|
+
domain
|
|
490
|
+
.command("ns <domain> [nameservers...]")
|
|
491
|
+
.description("查 / 改 NS 委派(不带 ns = 查现状;clize 注册的 Vercel 域可指 CF 或指走、CF 注册锁 CF、外部域引导手动)")
|
|
492
|
+
.action((d, nameservers) => emit(nameservers.length
|
|
493
|
+
? remoteClient.api.domainSetNameservers(rc(), d, nameservers)
|
|
494
|
+
: remoteClient.api.domainNameservers(rc(), d)));
|
|
495
|
+
// ───────── DNS 记录 ─────────
|
|
496
|
+
const dns = program
|
|
497
|
+
.command("dns")
|
|
498
|
+
.description("DNS 记录:在 NS 已托管 CF 的域上增删改 A/CNAME/MX/TXT…");
|
|
499
|
+
dns
|
|
500
|
+
.command("list <domain>")
|
|
501
|
+
.description("列出该域在 Cloudflare 上的全部 DNS 记录")
|
|
502
|
+
.action((d) => emit(remoteClient.api.dnsList(rc(), d)));
|
|
503
|
+
dns
|
|
504
|
+
.command("set <domain>")
|
|
505
|
+
.description("加 / 改一条记录(同 type+name 已存在则覆盖)")
|
|
506
|
+
.requiredOption("--type <type>", "记录类型:A | AAAA | CNAME | MX | TXT | …")
|
|
507
|
+
.requiredOption("--name <name>", "记录名(@ = apex,或 www / mail.example.com)")
|
|
508
|
+
.requiredOption("--content <content>", "记录值(A=IP,CNAME=目标域,MX=邮件服务器,TXT=文本…)")
|
|
509
|
+
.option("--ttl <sec>", "TTL 秒(默认 auto)")
|
|
510
|
+
.option("--priority <n>", "优先级(MX 用)")
|
|
511
|
+
.option("--proxied", "经 Cloudflare 代理(橙云;A/CNAME 适用)")
|
|
512
|
+
.action((d, opts) => emit(remoteClient.api.dnsSet(rc(), {
|
|
513
|
+
domain: d,
|
|
514
|
+
type: opts.type,
|
|
515
|
+
name: opts.name,
|
|
516
|
+
content: opts.content,
|
|
517
|
+
ttl: opts.ttl ? Number(opts.ttl) : undefined,
|
|
518
|
+
priority: opts.priority ? Number(opts.priority) : undefined,
|
|
519
|
+
proxied: opts.proxied || undefined,
|
|
520
|
+
})));
|
|
521
|
+
dns
|
|
522
|
+
.command("rm <domain>")
|
|
523
|
+
.description("删一条记录(按 type+name 定位;幂等)")
|
|
524
|
+
.requiredOption("--type <type>", "记录类型")
|
|
525
|
+
.requiredOption("--name <name>", "记录名")
|
|
526
|
+
.action((d, opts) => emit(remoteClient.api.dnsRm(rc(), d, opts.type, opts.name)));
|
|
557
527
|
// ───────── 邮箱 ─────────
|
|
558
528
|
const email = program.command("email").description("邮箱:配置 / 发信 / 收信");
|
|
559
529
|
email
|
|
560
530
|
.command("setup <domain>")
|
|
561
531
|
.description("配自定义域收发链路(MX / SPF + 发信域名验证)")
|
|
562
|
-
.action((d) => emit(
|
|
532
|
+
.action((d) => emit(remoteClient.api.emailSetup(rc(), d)));
|
|
563
533
|
withProject(email
|
|
564
534
|
.command("send")
|
|
565
535
|
.description("发信(📨 身份对外硬闸:默认只出草稿,加 --confirm 才真发)"))
|
|
@@ -576,31 +546,13 @@ withProject(email
|
|
|
576
546
|
? String(opts.to).split(",").map((s) => s.trim())
|
|
577
547
|
: opts.to;
|
|
578
548
|
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();
|
|
549
|
+
// from 缺省同样从本地 clize.json 推导(CLI 在用户机器上跑,读得到);confirm 透传给后端裁决草稿/真发。
|
|
550
|
+
const sender = opts.from
|
|
551
|
+
? { from: opts.from, replyTo: undefined }
|
|
552
|
+
: setupCore.projectSender();
|
|
601
553
|
if (!sender?.from)
|
|
602
554
|
return emit(Promise.reject(new Error("需 --from <地址>,或先 `clize init --handle <slug>` 绑定项目以自动带出发件人")));
|
|
603
|
-
|
|
555
|
+
return emit(remoteClient.api.emailSend(rc(), {
|
|
604
556
|
from: sender.from,
|
|
605
557
|
to,
|
|
606
558
|
subject: opts.subject,
|
|
@@ -608,17 +560,9 @@ withProject(email
|
|
|
608
560
|
html: opts.html,
|
|
609
561
|
replyTo: sender.replyTo,
|
|
610
562
|
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 }));
|
|
563
|
+
confirm: !!opts.confirm,
|
|
564
|
+
allowDuplicate: !!opts.allowDuplicate,
|
|
565
|
+
}));
|
|
622
566
|
});
|
|
623
567
|
email
|
|
624
568
|
.command("inbox <domain>")
|
|
@@ -626,19 +570,13 @@ email
|
|
|
626
570
|
.option("-n, --limit <n>", "返回条数", "20")
|
|
627
571
|
.option("--wait-for <pattern>", "轮询等待直到出现 subject/from/正文 含该串的信(如验证码)")
|
|
628
572
|
.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))));
|
|
573
|
+
.action((d, opts) => emit(opts.waitFor
|
|
574
|
+
? remoteClient.waitForInboxRemote(rc(), d, opts.waitFor, { timeoutSec: Number(opts.timeout) })
|
|
575
|
+
: remoteClient.api.emailInbox(rc(), d, Number(opts.limit))));
|
|
638
576
|
email
|
|
639
577
|
.command("show <domain> <id>")
|
|
640
578
|
.description("读单封邮件全文")
|
|
641
|
-
.action((d, id) => emit(
|
|
579
|
+
.action((d, id) => emit(remoteClient.api.emailShow(rc(), d, id)));
|
|
642
580
|
email
|
|
643
581
|
.command("thread <contactOrDomain> [contact]")
|
|
644
582
|
.description("读与某联系人的往来。init 过的目录单参即可:email thread <对方地址>;双参 = <收信域> <对方地址>")
|
|
@@ -648,25 +586,21 @@ email
|
|
|
648
586
|
const contact = b ?? a;
|
|
649
587
|
if (!domain)
|
|
650
588
|
return emit(Promise.reject(new Error("没绑定收信域:clize email thread <domain> <contact>,或先 clize init 绑定本目录后单参使用")));
|
|
651
|
-
return emit(
|
|
589
|
+
return emit(remoteClient.api.emailThread(rc(), domain, contact));
|
|
652
590
|
});
|
|
653
591
|
email
|
|
654
592
|
.command("inbox-setup <address>")
|
|
655
593
|
.description("开启 agent 自持收信(Email Worker 接住入站,存收件箱)")
|
|
656
594
|
.option("--forward <to>", "同时转发到个人邮箱")
|
|
657
|
-
.action((address, opts) => emit(
|
|
658
|
-
? remoteClient.api.emailInboxSetup(remote, address, opts.forward)
|
|
659
|
-
: emailCore.setupInbox(ctx(), address, opts.forward)));
|
|
595
|
+
.action((address, opts) => emit(remoteClient.api.emailInboxSetup(rc(), address, opts.forward)));
|
|
660
596
|
email
|
|
661
597
|
.command("route <from> <to>")
|
|
662
598
|
.description("配转发规则:发往 from 的邮件转发到 to")
|
|
663
|
-
.action((from, to) => emit(
|
|
599
|
+
.action((from, to) => emit(remoteClient.api.emailRoute(rc(), from, to)));
|
|
664
600
|
email
|
|
665
601
|
.command("webhook <address> <url>")
|
|
666
602
|
.description("配置入站推送:来信时 POST 信封到 url(与 status pull 并存)")
|
|
667
|
-
.action((address, url) => emit(
|
|
668
|
-
? remoteClient.api.emailWebhook(remote, address, url)
|
|
669
|
-
: emailCore.setEmailWebhook(ctx(), address, url)));
|
|
603
|
+
.action((address, url) => emit(remoteClient.api.emailWebhook(rc(), address, url)));
|
|
670
604
|
// 地址:tag + knowledge(客服等场景;无 build / 角色 / 记忆)
|
|
671
605
|
const address = email.command("address").description("邮箱地址:加 / 改(tag + knowledge)");
|
|
672
606
|
address
|
|
@@ -674,77 +608,49 @@ address
|
|
|
674
608
|
.description("加一个地址(--tag 分类;--knowledge 挂静态知识,作答时进 system prompt)")
|
|
675
609
|
.option("--tag <t>", "标签,如 客服")
|
|
676
610
|
.option("--knowledge <path>", "知识文件或目录")
|
|
677
|
-
.action((addr, opts) => emit(
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
})
|
|
683
|
-
: addressesCore.addAddress(ctx(), addr, { tag: opts.tag, knowledgePath: opts.knowledge })));
|
|
611
|
+
.action((addr, opts) => emit(remoteClient.api.emailAddressAdd(rc(), {
|
|
612
|
+
address: addr,
|
|
613
|
+
tag: opts.tag,
|
|
614
|
+
knowledge: opts.knowledge ? addressesCore.readKnowledge(opts.knowledge) : undefined,
|
|
615
|
+
})));
|
|
684
616
|
address
|
|
685
617
|
.command("update <addr>")
|
|
686
618
|
.description("改地址的 tag / knowledge(未传的不动)")
|
|
687
619
|
.option("--tag <t>", "标签")
|
|
688
620
|
.option("--knowledge <path>", "知识文件或目录")
|
|
689
|
-
.action((addr, opts) => emit(
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
})
|
|
695
|
-
: addressesCore.updateAddress(ctx(), addr, { tag: opts.tag, knowledgePath: opts.knowledge })));
|
|
621
|
+
.action((addr, opts) => emit(remoteClient.api.emailAddressUpdate(rc(), {
|
|
622
|
+
address: addr,
|
|
623
|
+
tag: opts.tag,
|
|
624
|
+
knowledge: opts.knowledge ? addressesCore.readKnowledge(opts.knowledge) : undefined,
|
|
625
|
+
})));
|
|
696
626
|
address
|
|
697
627
|
.command("remove <addr>")
|
|
698
628
|
.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())));
|
|
629
|
+
.action((addr) => emit(remoteClient.api.emailAddressRemove(rc(), addr)));
|
|
630
|
+
withProject(email.command("list").description("列出所有地址 + tag(检出项目时只列该项目域名下的)")).action(() => emit(remoteClient.api.emailListAddresses(rc())));
|
|
703
631
|
// ───────── 部署 ─────────
|
|
704
632
|
withProject(program
|
|
705
633
|
.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) => {
|
|
634
|
+
.description("部署:目录 + <slug>.clize.app → 免费 handle 多文件站")
|
|
635
|
+
.option("--domain <domain>", "目标域(<slug>.clize.app 走免费 handle)")).action((p, opts) => {
|
|
710
636
|
// 缺 --domain 时从 ./clize.json 推导(clize init 绑过的目录免显式传参,与 email send 推 from 对称)。
|
|
711
637
|
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;
|
|
638
|
+
let dir = false;
|
|
731
639
|
try {
|
|
732
|
-
|
|
640
|
+
dir = fs.statSync(p).isDirectory();
|
|
733
641
|
}
|
|
734
642
|
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));
|
|
643
|
+
/* 非路径 */
|
|
741
644
|
}
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
if (!
|
|
745
|
-
return emit(Promise.reject(new Error(
|
|
746
|
-
const
|
|
747
|
-
|
|
645
|
+
if (!dir)
|
|
646
|
+
return emit(Promise.reject(new Error(`${p} 不是存在的目录 —— 先生成站点文件再 deploy(托管部署支持目录部署)`)));
|
|
647
|
+
if (!domain)
|
|
648
|
+
return emit(Promise.reject(new Error("缺目标域:clize deploy <目录> --domain <已 claim 的 host>,或先 clize init --handle <slug> 绑定本目录免传 --domain")));
|
|
649
|
+
const files = sitesCore.readDirFiles(p);
|
|
650
|
+
const bytes = files.reduce((n, f) => n + Math.ceil(f.base64.length * 0.75), 0);
|
|
651
|
+
if (bytes > 25 * 1024 * 1024)
|
|
652
|
+
return emit(Promise.reject(new Error(`站点约 ${(bytes / 1048576).toFixed(1)}MB,超过托管部署上限 25MB;请精简`)));
|
|
653
|
+
return emit(remoteClient.api.deploy(rc(), domain, files));
|
|
748
654
|
});
|
|
749
655
|
// ───────── 本地预览(serve:deploy 的本地对偶)─────────
|
|
750
656
|
// 起一个支持 Range + 媒体不灌 no-store 的静态 server —— 预览含 <video> 的站点 / 片子用它,
|
|
@@ -774,8 +680,7 @@ program
|
|
|
774
680
|
}
|
|
775
681
|
});
|
|
776
682
|
// ───────── 生成式媒体(gen:图 / 视频 / 音乐)─────────
|
|
777
|
-
//
|
|
778
|
-
// 托管(clize 代付 + R2 托管)是 G2,所以这里不按 remote 分流。
|
|
683
|
+
// 纯托管:平台代付 + 字节回 CLI 落盘(G2);提交 / 报价 / 轮询全经控制面,CLI 只负责读参考图、落盘成品。
|
|
779
684
|
const gen = program
|
|
780
685
|
.command("gen")
|
|
781
686
|
.description("生成式媒体:你写 prompt,clize 产字节落盘(图 / 视频 / 音乐;蓝图批量 = check + render)");
|
|
@@ -790,51 +695,30 @@ withProject(gen
|
|
|
790
695
|
.option("--out <path>", "落盘路径(默认 ./clize-assets/<id>_<i>.png)")
|
|
791
696
|
.option("--confirm", "确认花钱生成")
|
|
792
697
|
.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(), {
|
|
698
|
+
if (opts.ref || opts.mask)
|
|
699
|
+
return emit(Promise.reject(new Error("托管模式暂不支持参考图 / 蒙版(--ref/--mask);用纯文生图。")));
|
|
700
|
+
return emit(remoteClient.api
|
|
701
|
+
.genImage(rc(), {
|
|
807
702
|
prompt,
|
|
808
703
|
model: opts.model,
|
|
809
704
|
n: Number(opts.n),
|
|
810
705
|
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 }));
|
|
706
|
+
confirm: !!opts.confirm,
|
|
707
|
+
})
|
|
708
|
+
.then((r) => mediaCore.materializeRemote(r, opts.out)));
|
|
819
709
|
});
|
|
820
710
|
withProject(gen
|
|
821
711
|
.command("list")
|
|
822
712
|
.description("列已生成资产(id / 模型 / prompt / 路径 / 花费;检出项目时只列该项目)")
|
|
823
|
-
.option("--modality <m>", "只看某模态:image | video | music")).action((opts) => emit(
|
|
713
|
+
.option("--modality <m>", "只看某模态:image | video | music")).action((opts) => emit(remoteClient.api.genList(rc(), opts.modality)));
|
|
824
714
|
gen
|
|
825
715
|
.command("show <id>")
|
|
826
716
|
.description("看单个生成任务 / 资产全貌")
|
|
827
|
-
.action((id) => emit(
|
|
717
|
+
.action((id) => emit(remoteClient.api.genShow(rc(), id)));
|
|
828
718
|
gen
|
|
829
719
|
.command("budget [set] [usd]")
|
|
830
720
|
.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
|
-
});
|
|
721
|
+
.action(() => emit(Promise.reject(new Error("gen budget 托管模式暂未支持(二期);托管下每次生成用 --confirm 逐次确认即可。"))));
|
|
838
722
|
withProject(gen
|
|
839
723
|
.command("video <prompt>")
|
|
840
724
|
.description("文生视频 / 图生视频(长任务:默认等到完成,--async 后台返 job id)"))
|
|
@@ -848,27 +732,16 @@ withProject(gen
|
|
|
848
732
|
.option("--timeout <sec>", "默认等待上限(秒)", "300")
|
|
849
733
|
.option("--confirm", "确认花钱生成")
|
|
850
734
|
.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(), {
|
|
735
|
+
// 托管:参考图在 CLI 端读盘转 data URI 上传(首帧排第一,与后端同序)。
|
|
736
|
+
const paths = [...new Set([opts.fromImage, ...splitRefs(opts.ref)].filter(Boolean))];
|
|
737
|
+
return emit((async () => remoteGenAsync(rc(), "video", {
|
|
864
738
|
prompt,
|
|
865
739
|
model: opts.model,
|
|
866
|
-
|
|
867
|
-
refs: splitRefs(opts.ref),
|
|
740
|
+
refs: paths.length ? refsToDataUris(paths) : undefined,
|
|
868
741
|
durationSec: opts.duration ? Number(opts.duration) : undefined,
|
|
869
742
|
aspect: opts.aspect,
|
|
870
|
-
|
|
871
|
-
}, { confirm: !!opts.confirm, async: !!opts.async,
|
|
743
|
+
confirm: !!opts.confirm,
|
|
744
|
+
}, { confirm: !!opts.confirm, async: !!opts.async, out: opts.out, timeout: opts.timeout }))());
|
|
872
745
|
});
|
|
873
746
|
withProject(gen
|
|
874
747
|
.command("music <prompt>")
|
|
@@ -880,37 +753,24 @@ withProject(gen
|
|
|
880
753
|
.option("--async", "后台生成,只返 job id(默认等到完成)")
|
|
881
754
|
.option("--timeout <sec>", "默认等待上限(秒)", "300")
|
|
882
755
|
.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())));
|
|
756
|
+
.action((prompt, opts) => emit(remoteGenAsync(rc(), "music", {
|
|
757
|
+
prompt,
|
|
758
|
+
model: opts.model,
|
|
759
|
+
instrumental: !!opts.instrumental,
|
|
760
|
+
durationSec: opts.duration ? Number(opts.duration) : undefined,
|
|
761
|
+
confirm: !!opts.confirm,
|
|
762
|
+
}, { confirm: !!opts.confirm, async: !!opts.async, out: opts.out, timeout: opts.timeout })));
|
|
763
|
+
withProject(gen.command("jobs").description("列生成任务(running 置顶;断会话后重连长任务用;检出项目时只列该项目)")).action(() => emit(remoteClient.api.genJobs(rc())));
|
|
902
764
|
gen
|
|
903
765
|
.command("status <id>")
|
|
904
766
|
.description("查单任务:running 则向 provider 轮询一次(可能就此完成落盘)")
|
|
905
|
-
.action((id) => emit(
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
.then((r) => mediaCore.materializeRemote(r, undefined))
|
|
909
|
-
: mediaCore.genJobStatus(ctx(), id)));
|
|
767
|
+
.action((id) => emit(remoteClient.api
|
|
768
|
+
.genStatus(rc(), id)
|
|
769
|
+
.then((r) => mediaCore.materializeRemote(r, undefined))));
|
|
910
770
|
gen
|
|
911
771
|
.command("rm <id>")
|
|
912
772
|
.description("删任务记录 + 本地文件")
|
|
913
|
-
.action((id) => emit(
|
|
773
|
+
.action((id) => emit(remoteClient.api.genRm(rc(), id)));
|
|
914
774
|
// ───────── build:作品域(端到端做一个作品;方法 + 校验免费,成品步骤花钱带闸)─────────
|
|
915
775
|
// taxonomy:gen = 原子生成 + 任务池(引擎),build = 作品编排门面(方法 → 你创作 → 校验 → 成品)。
|
|
916
776
|
// 第 N 种作品 = build 加子域(方法三件套 + 该作品特有的校验/成品命令),顶级命令面零膨胀。
|
|
@@ -926,32 +786,32 @@ function siteBuildCommands() {
|
|
|
926
786
|
.argument("<brief...>")
|
|
927
787
|
.description("开工:返回工作流(7 步 + 反 slop)+ 风格目录 + 针对 brief 的推荐")
|
|
928
788
|
.option("--project <name>", "项目名")
|
|
929
|
-
.action((brief, opts) => hostedBuild((
|
|
789
|
+
.action((brief, opts) => hostedBuild((rcfg) => remoteClient.api.buildSiteStart(rcfg, brief.join(" "), opts.project)));
|
|
930
790
|
const recommend = new Command("recommend")
|
|
931
791
|
.argument("<brief...>")
|
|
932
792
|
.description("按 brief 推荐:风格 + 调色板 + 字体对 + 反模式")
|
|
933
793
|
.option("--project <name>", "项目名")
|
|
934
|
-
.action((brief, opts) => hostedBuild((
|
|
794
|
+
.action((brief, opts) => hostedBuild((rcfg) => remoteClient.api.buildSiteRecommend(rcfg, brief.join(" "), opts.project)));
|
|
935
795
|
const list = new Command("list")
|
|
936
796
|
.description("列出全部风格宪法(slug + 一句话 best-for)")
|
|
937
|
-
.action(() => hostedBuild((
|
|
797
|
+
.action(() => hostedBuild((rcfg) => remoteClient.api.buildSiteList(rcfg)));
|
|
938
798
|
const get = new Command("get")
|
|
939
799
|
.argument("<slug>")
|
|
940
800
|
.description("取某风格的完整宪法(色 / 字 / 形 / 签名手法)+ imagery 配图契约 + 调色板 / 字体对 + DESIGN.md 模板")
|
|
941
|
-
.action((slug) => hostedBuild((
|
|
801
|
+
.action((slug) => hostedBuild((rcfg) => remoteClient.api.buildSiteGet(rcfg, slug)));
|
|
942
802
|
const stack = new Command("stack")
|
|
943
803
|
.argument("<stack>")
|
|
944
804
|
.argument("[query...]")
|
|
945
805
|
.description("取某技术栈实现指南(React/Next/SwiftUI/Flutter/shadcn… 惯用法 + 行为规则;跨栈 UI 指南)")
|
|
946
|
-
.action((stackName, query) => hostedBuild((
|
|
806
|
+
.action((stackName, query) => hostedBuild((rcfg) => remoteClient.api.buildSiteStack(rcfg, stackName, (query || []).join(" "))));
|
|
947
807
|
const search = new Command("search")
|
|
948
808
|
.argument("<query...>")
|
|
949
809
|
.description("查 UUPM 库(ux/color/chart/landing/product/typography/icons…;默认自动判域)")
|
|
950
810
|
.option("--domain <d>", "限定域")
|
|
951
|
-
.action((query, opts) => hostedBuild((
|
|
811
|
+
.action((query, opts) => hostedBuild((rcfg) => remoteClient.api.buildSiteSearch(rcfg, query.join(" "), opts.domain)));
|
|
952
812
|
const review = new Command("review")
|
|
953
813
|
.description("取合并自审清单(anti-slop + DESIGN.md 保真 + UX 行为)")
|
|
954
|
-
.action(() => hostedBuild((
|
|
814
|
+
.action(() => hostedBuild((rcfg) => remoteClient.api.buildSiteReview(rcfg)));
|
|
955
815
|
return [start, recommend, list, get, stack, search, review];
|
|
956
816
|
}
|
|
957
817
|
const buildSite = build
|
|
@@ -980,34 +840,8 @@ function readBlueprint(file) {
|
|
|
980
840
|
throw new Error(`蓝图不是合法 JSON:${e instanceof Error ? e.message : String(e)}`);
|
|
981
841
|
}
|
|
982
842
|
}
|
|
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
843
|
/** render 后端(托管):参考图逐镜转 data URI 上传;confirm=false 探价是后端契约(永不生成)。 */
|
|
1010
|
-
function remoteRenderBackend(
|
|
844
|
+
function remoteRenderBackend(rcfg) {
|
|
1011
845
|
const body = (p, confirm) => ({
|
|
1012
846
|
prompt: p.prompt,
|
|
1013
847
|
model: p.model,
|
|
@@ -1018,20 +852,20 @@ function remoteRenderBackend(rc) {
|
|
|
1018
852
|
});
|
|
1019
853
|
return {
|
|
1020
854
|
quote: async (p) => {
|
|
1021
|
-
const r = (await remoteClient.api.genVideo(
|
|
855
|
+
const r = (await remoteClient.api.genVideo(rcfg, body(p, false)));
|
|
1022
856
|
const est = r?.quote?.estUsd;
|
|
1023
857
|
if (typeof est !== "number")
|
|
1024
858
|
throw new Error("探价未返回 quote(后端响应异常)");
|
|
1025
859
|
return est;
|
|
1026
860
|
},
|
|
1027
861
|
submit: async (p) => {
|
|
1028
|
-
const r = (await remoteClient.api.genVideo(
|
|
862
|
+
const r = (await remoteClient.api.genVideo(rcfg, body(p, true)));
|
|
1029
863
|
if (!r.id)
|
|
1030
864
|
throw new Error(`提交未返回任务 id${r.message ? `:${String(r.message)}` : ""}`);
|
|
1031
865
|
return { id: String(r.id), state: String(r.state ?? "running") };
|
|
1032
866
|
},
|
|
1033
867
|
poll: async (id, outPath) => {
|
|
1034
|
-
const r = (await remoteClient.api.genStatus(
|
|
868
|
+
const r = (await remoteClient.api.genStatus(rcfg, id));
|
|
1035
869
|
if (r.state === "succeeded") {
|
|
1036
870
|
await mediaCore.materializeRemote(r, outPath);
|
|
1037
871
|
return { state: "succeeded" };
|
|
@@ -1049,15 +883,15 @@ const buildClip = build
|
|
|
1049
883
|
buildClip
|
|
1050
884
|
.command("start <brief...>")
|
|
1051
885
|
.description("开工:返回方法(4 阶段 + 蓝图 schema + 反穿帮清单)+ 风格目录 + 针对 brief 的推荐")
|
|
1052
|
-
.action((brief) => hostedBuild((
|
|
886
|
+
.action((brief) => hostedBuild((rcfg) => remoteClient.api.buildClipStart(rcfg, brief.join(" "))));
|
|
1053
887
|
buildClip
|
|
1054
888
|
.command("list")
|
|
1055
889
|
.description("列出全部视频风格(id + 一句话适用场景)")
|
|
1056
|
-
.action(() => hostedBuild((
|
|
890
|
+
.action(() => hostedBuild((rcfg) => remoteClient.api.buildClipList(rcfg)));
|
|
1057
891
|
buildClip
|
|
1058
892
|
.command("get <slug>")
|
|
1059
893
|
.description("取某风格完整包(资产图模板 + 视频视觉词汇 + strict 后端替换行)")
|
|
1060
|
-
.action((slug) => hostedBuild((
|
|
894
|
+
.action((slug) => hostedBuild((rcfg) => remoteClient.api.buildClipGet(rcfg, slug)));
|
|
1061
895
|
buildClip
|
|
1062
896
|
.command("check <blueprint>")
|
|
1063
897
|
.description("蓝图机械校验(本地、免费、零凭证):防穿帮 lint + 台词覆盖 + 时长公式 + 结构;error 清零才 render")
|
|
@@ -1069,7 +903,7 @@ withProject(buildClip
|
|
|
1069
903
|
.option("--out-dir <dir>", "输出目录(默认 clize-video/<title>)")
|
|
1070
904
|
.option("--timeout <sec>", "轮询总时限(秒)", "1800")
|
|
1071
905
|
.option("--confirm", "确认花钱生成(报价见无 --confirm 的输出)")
|
|
1072
|
-
.action((file, opts) => emit(videoCore.renderBlueprint(
|
|
906
|
+
.action((file, opts) => emit(videoCore.renderBlueprint(remoteRenderBackend(rc()), readBlueprint(file), {
|
|
1073
907
|
confirm: !!opts.confirm,
|
|
1074
908
|
candidates: Number(opts.candidates) || 1,
|
|
1075
909
|
outDir: opts.outDir,
|