siluzan-website-cli 1.0.1-beta.4 → 1.0.1-beta.5

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 CHANGED
@@ -28,9 +28,12 @@ siluzan-website plugins install --site <guid> --zip ./plugin.zip
28
28
  siluzan-website plugins update --site <guid> --plugin <plugin>
29
29
  siluzan-website plugins uninstall --site <guid> --plugin <plugin>
30
30
  siluzan-website plugins helper-status --site <guid>
31
+ siluzan-website zasp settings --site <guid>
32
+ siluzan-website zasp set --site <guid> --set related_enabled=1
33
+ siluzan-website zasp sync --site <guid>
31
34
  ```
32
35
 
33
- 页面写入走站点上已有的 `zionbuilder/v1` visual-edit 接口(HTML → Builder)。插件:目录用 `--slug`,外部用 `--zip`;卸载用 `uninstall --plugin`(helper / oauth2 不允许);helper 太旧时会先从镜像本地 zip 升级。
36
+ 页面写入走站点上已有的 `zionbuilder/v1` visual-edit 接口(HTML → Builder)。插件:目录用 `--slug`,外部用 `--zip`;卸载用 `uninstall --plugin`(helper / oauth2 不允许);helper 太旧时会先从镜像本地 zip 升级。`zasp-smart-recommender` 装完后用 `zasp settings / set / sync` 配相关推荐和 AI/向量,不要手写 curl。
34
37
 
35
38
  ## 开发
36
39
 
package/dist/index.js CHANGED
@@ -4225,7 +4225,7 @@ function register6(program2) {
4225
4225
  }
4226
4226
 
4227
4227
  // src/commands/plugins.ts
4228
- import { readFileSync as readFileSync6, existsSync as existsSync3 } from "fs";
4228
+ import { readFileSync as readFileSync7, existsSync as existsSync3 } from "fs";
4229
4229
  import { basename as basename2, resolve as resolve2 } from "path";
4230
4230
 
4231
4231
  // src/utils/wp-plugin-catalog.ts
@@ -4385,6 +4385,381 @@ function downloadOfficialZip(url) {
4385
4385
  });
4386
4386
  }
4387
4387
 
4388
+ // src/commands/zasp.ts
4389
+ import { readFileSync as readFileSync6 } from "fs";
4390
+ var SECRET_KEY = /api_key|password|token|secret|private_key/i;
4391
+ function zaspUrl(base, path6) {
4392
+ const suffix = path6.startsWith("/") ? path6 : `/${path6}`;
4393
+ return `${base}/wp-json/zasp-b2b/v2${suffix}`;
4394
+ }
4395
+ function explainZaspError(err) {
4396
+ const msg = err instanceof Error ? err.message : String(err);
4397
+ if (/404|rest_no_route|not found/i.test(msg)) {
4398
+ return `${msg}
4399
+ \u7AD9\u70B9\u53EF\u80FD\u6CA1\u88C5 zasp-smart-recommender\uFF0C\u6216\u7248\u672C\u592A\u65E7\u3001\u6CA1\u6709\u7BA1\u7406 REST\u3002
4400
+ \u5148 \`plugins list\`\uFF0C\u518D \`plugins catalog\` / \`install --slug zasp-smart-recommender\`\uFF0C
4401
+ \u6216 \`update --plugin zasp-smart-recommender/zasp-smart-recommender\``;
4402
+ }
4403
+ return msg;
4404
+ }
4405
+ function redactSecrets(value) {
4406
+ if (Array.isArray(value)) return value.map(redactSecrets);
4407
+ if (value && typeof value === "object") {
4408
+ const out = {};
4409
+ for (const [key, nested] of Object.entries(value)) {
4410
+ if (SECRET_KEY.test(key) && typeof nested === "string" && nested) {
4411
+ out[key] = "***";
4412
+ } else {
4413
+ out[key] = redactSecrets(nested);
4414
+ }
4415
+ }
4416
+ return out;
4417
+ }
4418
+ return value;
4419
+ }
4420
+ function printJson(data) {
4421
+ console.log(JSON.stringify(redactSecrets(data), null, 2));
4422
+ }
4423
+ function collectPair(value, acc) {
4424
+ acc.push(value);
4425
+ return acc;
4426
+ }
4427
+ function coerceSetValue(raw) {
4428
+ if (raw === "true") return true;
4429
+ if (raw === "false") return false;
4430
+ if (raw === "null") return null;
4431
+ if (/^-?\d+$/.test(raw)) return Number(raw);
4432
+ if (/^-?\d+\.\d+$/.test(raw)) return Number(raw);
4433
+ return raw;
4434
+ }
4435
+ function parseSetPairs(pairs) {
4436
+ const body = {};
4437
+ for (const raw of pairs ?? []) {
4438
+ const eq = raw.indexOf("=");
4439
+ if (eq <= 0) {
4440
+ throw new Error(`--set \u683C\u5F0F\u5E94\u4E3A key=value\uFF0C\u6536\u5230\uFF1A${raw}`);
4441
+ }
4442
+ const key = raw.slice(0, eq).trim();
4443
+ if (!key) {
4444
+ throw new Error(`--set \u7F3A\u5C11\u5B57\u6BB5\u540D\uFF1A${raw}`);
4445
+ }
4446
+ body[key] = coerceSetValue(raw.slice(eq + 1));
4447
+ }
4448
+ return body;
4449
+ }
4450
+ function parseBodyJson(inline, filePath) {
4451
+ const raw = filePath ? readFileSync6(filePath, "utf8") : inline;
4452
+ if (!raw?.trim()) return {};
4453
+ const parsed = JSON.parse(raw);
4454
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
4455
+ throw new Error("--body / --body-file \u5FC5\u987B\u662F JSON \u5BF9\u8C61");
4456
+ }
4457
+ return parsed;
4458
+ }
4459
+ async function zaspFetch(opts, path6, init = {}) {
4460
+ const target = await resolveTarget(opts);
4461
+ try {
4462
+ const data = await apiFetch2(zaspUrl(target.base, path6), target.config, init, opts.verbose);
4463
+ return { data, siteName: target.site.name, base: target.base };
4464
+ } catch (e) {
4465
+ throw new Error(explainZaspError(e));
4466
+ }
4467
+ }
4468
+ async function runZaspSettings(opts) {
4469
+ let pack;
4470
+ try {
4471
+ pack = await zaspFetch(opts, "/settings");
4472
+ } catch (e) {
4473
+ console.error(`
4474
+ \u274C \u8BFB\u53D6\u914D\u7F6E\u5931\u8D25\uFF1A${e.message}
4475
+ `);
4476
+ process.exit(1);
4477
+ return;
4478
+ }
4479
+ if (opts.json) {
4480
+ printJson({ site: pack.siteName, url: pack.base, ...pack.data });
4481
+ return;
4482
+ }
4483
+ const settings = pack.data.settings ?? {};
4484
+ const runtime = pack.data.runtime ?? {};
4485
+ console.log(`
4486
+ ${pack.siteName} AI \u63A8\u8350\u914D\u7F6E (${pack.base})
4487
+ `);
4488
+ printCliTable(
4489
+ [
4490
+ { k: "ai_provider", v: String(settings.ai_provider ?? "-") },
4491
+ { k: "vector_provider", v: String(settings.vector_provider ?? "-") },
4492
+ { k: "related_enabled", v: String(settings.related_enabled ?? "-") },
4493
+ { k: "related_limit", v: String(settings.related_limit ?? "-") },
4494
+ { k: "auto_sync", v: String(settings.auto_sync ?? "-") },
4495
+ { k: "has_ai_api_key", v: String(settings.has_ai_api_key ?? "-") },
4496
+ { k: "has_vector_api_key", v: String(settings.has_vector_api_key ?? "-") },
4497
+ { k: "has_vector_embed_api_key", v: String(settings.has_vector_embed_api_key ?? "-") },
4498
+ { k: "runtime.ai_configured", v: String(runtime.ai_configured ?? "-") },
4499
+ { k: "runtime.vector_configured", v: String(runtime.vector_configured ?? "-") },
4500
+ { k: "runtime.vector_site_id", v: String(runtime.vector_site_id ?? "-") },
4501
+ { k: "runtime.retrieval_mode", v: String(runtime.retrieval_mode ?? "-") }
4502
+ ],
4503
+ [
4504
+ { key: "k", header: "\u5B57\u6BB5" },
4505
+ { key: "v", header: "\u503C" }
4506
+ ]
4507
+ );
4508
+ const sync = runtime.sync_state;
4509
+ if (sync?.error) {
4510
+ console.log(`
4511
+ \u4E0A\u6B21\u540C\u6B65\u5931\u8D25\uFF1A${String(sync.error)}`);
4512
+ }
4513
+ console.log("\n\u5B8C\u6574\u5B57\u6BB5\u7528 --json\uFF1B\u5B57\u6BB5\u5B57\u5178\u7528 `zasp schema`\u3002\n");
4514
+ }
4515
+ async function runZaspSchema(opts) {
4516
+ let pack;
4517
+ try {
4518
+ pack = await zaspFetch(opts, "/settings/schema");
4519
+ } catch (e) {
4520
+ console.error(`
4521
+ \u274C \u8BFB\u53D6\u5B57\u6BB5\u5B57\u5178\u5931\u8D25\uFF1A${e.message}
4522
+ `);
4523
+ process.exit(1);
4524
+ return;
4525
+ }
4526
+ if (opts.json) {
4527
+ printJson(pack.data);
4528
+ return;
4529
+ }
4530
+ console.log(`
4531
+ ${pack.siteName} \u5B57\u6BB5\u5B57\u5178
4532
+ `);
4533
+ printJson(pack.data);
4534
+ console.log();
4535
+ }
4536
+ async function runZaspSet(opts) {
4537
+ let patch;
4538
+ try {
4539
+ patch = {
4540
+ ...parseBodyJson(opts.body, opts.bodyFile),
4541
+ ...parseSetPairs(opts.set)
4542
+ };
4543
+ } catch (e) {
4544
+ console.error(`
4545
+ \u274C ${e.message}
4546
+ `);
4547
+ process.exit(1);
4548
+ return;
4549
+ }
4550
+ if (opts.clearAiKey) patch.clear_ai_key = true;
4551
+ if (opts.clearVectorKey) patch.clear_vector_key = true;
4552
+ if (opts.clearVectorEmbedKey) patch.clear_vector_embed_key = true;
4553
+ if (Object.keys(patch).length === 0) {
4554
+ console.error(`
4555
+ \u274C \u6CA1\u6709\u8981\u6539\u7684\u5B57\u6BB5\u3002\u7528 --set key=value\uFF0C\u6216 --body '{"related_enabled":1}'
4556
+ `);
4557
+ process.exit(1);
4558
+ }
4559
+ let pack;
4560
+ try {
4561
+ pack = await zaspFetch(opts, "/settings", {
4562
+ method: "PATCH",
4563
+ body: JSON.stringify(patch)
4564
+ });
4565
+ } catch (e) {
4566
+ console.error(`
4567
+ \u274C \u66F4\u65B0\u914D\u7F6E\u5931\u8D25\uFF1A${e.message}
4568
+ `);
4569
+ process.exit(1);
4570
+ return;
4571
+ }
4572
+ const changed = Object.keys(patch).filter((k) => !SECRET_KEY.test(k) && !k.startsWith("clear_"));
4573
+ console.log(`
4574
+ \u2705 \u5DF2\u66F4\u65B0 ${pack.siteName} \u7684 AI \u63A8\u8350\u914D\u7F6E`);
4575
+ if (changed.length) console.log(` \u5B57\u6BB5\uFF1A${changed.join(", ")}`);
4576
+ if (opts.clearAiKey || opts.clearVectorKey || opts.clearVectorEmbedKey) {
4577
+ console.log(" \u5DF2\u6309\u5F00\u5173\u6E05\u7A7A\u5BF9\u5E94\u5BC6\u94A5\uFF08\u660E\u6587\u4E0D\u4F1A\u56DE\u663E\uFF09");
4578
+ }
4579
+ console.log(` \u7AD9\u70B9\uFF1A${pack.base}
4580
+ `);
4581
+ if (opts.json) {
4582
+ printJson(pack.data);
4583
+ return;
4584
+ }
4585
+ const settings = pack.data.settings ?? {};
4586
+ const runtime = pack.data.runtime ?? {};
4587
+ console.log(
4588
+ ` ai_provider=${String(settings.ai_provider ?? "-")} vector_provider=${String(settings.vector_provider ?? "-")} ai_configured=${String(runtime.ai_configured ?? "-")} vector_configured=${String(runtime.vector_configured ?? "-")}`
4589
+ );
4590
+ console.log(" \u6539\u4E86\u5411\u91CF\u540E\u7AEF\u7684\u8BDD\uFF0C\u63A5\u7740\u8DD1 `zasp sync`\uFF1B\u53EA\u6539\u76F8\u5173\u63A8\u8350 / custom AI \u4E00\u822C\u4E0D\u7528\u3002\n");
4591
+ }
4592
+ async function runZaspSync(opts) {
4593
+ let pack;
4594
+ try {
4595
+ pack = await zaspFetch(opts, "/sync", { method: "POST" });
4596
+ } catch (e) {
4597
+ console.error(`
4598
+ \u274C \u540C\u6B65\u5931\u8D25\uFF1A${e.message}
4599
+ `);
4600
+ process.exit(1);
4601
+ return;
4602
+ }
4603
+ if (opts.json) {
4604
+ printJson(pack.data);
4605
+ return;
4606
+ }
4607
+ if (pack.data.ok) {
4608
+ console.log(`
4609
+ \u2705 ${pack.siteName} \u5168\u91CF\u540C\u6B65\u5B8C\u6210\uFF0C\u5199\u5165 ${pack.data.count ?? 0} \u6761
4610
+ `);
4611
+ return;
4612
+ }
4613
+ console.error(`
4614
+ \u274C \u540C\u6B65\u672A\u5B8C\u6210\uFF1A${pack.data.error || "\u672A\u77E5\u9519\u8BEF"}`);
4615
+ console.error(" \u5148\u4FEE Collection / key / \u7EF4\u5EA6\uFF0C\u518D `zasp set`\uFF0C\u518D `zasp sync`\n");
4616
+ process.exit(1);
4617
+ }
4618
+ async function runZaspStatus(opts) {
4619
+ let pack;
4620
+ try {
4621
+ pack = await zaspFetch(opts, "/status");
4622
+ } catch (e) {
4623
+ console.error(`
4624
+ \u274C \u8BFB\u53D6\u72B6\u6001\u5931\u8D25\uFF1A${e.message}
4625
+ `);
4626
+ process.exit(1);
4627
+ return;
4628
+ }
4629
+ if (opts.json) {
4630
+ printJson({ site: pack.siteName, url: pack.base, ...pack.data });
4631
+ return;
4632
+ }
4633
+ const counts = pack.data.source_counts ?? {};
4634
+ console.log(`
4635
+ ${pack.siteName} AI \u63A8\u8350\u72B6\u6001 (${pack.base})
4636
+ `);
4637
+ printCliTable(
4638
+ [
4639
+ { k: "ready", v: String(pack.data.ready ?? pack.data.status ?? "-") },
4640
+ { k: "ai_configured", v: String(pack.data.ai_configured ?? "-") },
4641
+ { k: "vector_configured", v: String(pack.data.vector_configured ?? "-") },
4642
+ { k: "vector_site_id", v: String(pack.data.vector_site_id ?? "-") },
4643
+ { k: "mode", v: String(pack.data.mode ?? "-") },
4644
+ { k: "version", v: String(pack.data.version ?? "-") },
4645
+ { k: "documents", v: String(counts.documents ?? "-") },
4646
+ { k: "products", v: String(counts.products ?? "-") }
4647
+ ],
4648
+ [
4649
+ { key: "k", header: "\u5B57\u6BB5" },
4650
+ { key: "v", header: "\u503C" }
4651
+ ]
4652
+ );
4653
+ console.log();
4654
+ }
4655
+ async function runZaspDiagnostics(opts) {
4656
+ let pack;
4657
+ try {
4658
+ pack = await zaspFetch(opts, "/diagnostics");
4659
+ } catch (e) {
4660
+ console.error(`
4661
+ \u274C \u8BCA\u65AD\u5931\u8D25\uFF1A${e.message}
4662
+ `);
4663
+ process.exit(1);
4664
+ return;
4665
+ }
4666
+ console.log(`
4667
+ ${pack.siteName} \u8BCA\u65AD
4668
+ `);
4669
+ printJson(pack.data);
4670
+ console.log();
4671
+ }
4672
+ async function runZaspRecommend(opts) {
4673
+ const placement = opts.placement?.trim() || "assistant";
4674
+ const limit = Number(opts.limit ?? 5);
4675
+ const pageId = Number(opts.pageId ?? 0);
4676
+ if (!Number.isFinite(limit) || limit < 1) {
4677
+ console.error("\n\u274C --limit \u5FC5\u987B\u662F\u6B63\u6574\u6570\n");
4678
+ process.exit(1);
4679
+ }
4680
+ if (placement === "related_products" && (!Number.isFinite(pageId) || pageId <= 0)) {
4681
+ console.error("\n\u274C \u9875\u9762\u76F8\u5173\u63A8\u8350\u5FC5\u987B\u63D0\u4F9B\u6709\u6548\u5546\u54C1 --page-id\n");
4682
+ process.exit(1);
4683
+ }
4684
+ const body = {
4685
+ query: opts.query?.trim() ?? "",
4686
+ placement,
4687
+ limit,
4688
+ page_id: Number.isFinite(pageId) ? pageId : 0,
4689
+ constraints: {},
4690
+ session: { seen: [], interest: {} }
4691
+ };
4692
+ let pack;
4693
+ try {
4694
+ pack = await zaspFetch(opts, "/recommend", {
4695
+ method: "POST",
4696
+ body: JSON.stringify(body)
4697
+ });
4698
+ } catch (e) {
4699
+ console.error(`
4700
+ \u274C \u8BD5\u63A8\u5931\u8D25\uFF1A${e.message}
4701
+ `);
4702
+ process.exit(1);
4703
+ return;
4704
+ }
4705
+ if (opts.json) {
4706
+ printJson(pack.data);
4707
+ return;
4708
+ }
4709
+ const items = pack.data.items ?? [];
4710
+ console.log(`
4711
+ ${pack.siteName} \u8BD5\u63A8 status=${pack.data.status ?? "-"} mode=${pack.data.mode ?? "-"}`);
4712
+ if (items.length === 0) {
4713
+ console.log("\u6CA1\u6709\u5339\u914D\u7ED3\u679C\u3002\n");
4714
+ return;
4715
+ }
4716
+ printCliTable(
4717
+ items.map((item) => ({
4718
+ id: item.id != null ? String(item.id) : "-",
4719
+ title: item.title || "-",
4720
+ type: item.type || "-",
4721
+ score: item.score != null ? String(item.score) : "-",
4722
+ url: item.url || "-"
4723
+ })),
4724
+ [
4725
+ { key: "id", header: "id" },
4726
+ { key: "title", header: "\u6807\u9898" },
4727
+ { key: "type", header: "\u7C7B\u578B" },
4728
+ { key: "score", header: "\u5206" },
4729
+ { key: "url", header: "\u94FE\u63A5" }
4730
+ ]
4731
+ );
4732
+ console.log();
4733
+ }
4734
+ function hintZaspIfNeeded(pluginOrSlug) {
4735
+ if (!pluginOrSlug.toLowerCase().includes("zasp-smart-recommender")) return;
4736
+ console.log(" \u63A5\u4E0B\u6765\u7528 `siluzan-website zasp settings / set / sync` \u914D\u7F6E AI \u63A8\u8350\uFF08\u89C1 references/zasp-recommender.md\uFF09\n");
4737
+ }
4738
+ function register7(program2) {
4739
+ const zasp = program2.command("zasp").description("zasp-smart-recommender\uFF08AI \u63A8\u8350\uFF09\u914D\u7F6E\u4E0E\u9A8C\u6536\uFF0C\u8D70\u7AD9\u70B9 /wp-json/zasp-b2b/v2");
4740
+ zasp.command("settings").description("\u67E5\u770B\u5F53\u524D AI \u63A8\u8350\u914D\u7F6E\uFF08GET /settings\uFF09").option("-s, --site <guidOrName>", "\u7AD9\u70B9 guid \u6216\u540D\u79F0").option("--url <url>", "\u76F4\u63A5\u6307\u5B9A\u7AD9\u70B9\u5730\u5740").option("--json", "\u8F93\u51FA\u5B8C\u6574 JSON", false).option("--verbose", "\u8F93\u51FA\u8BF7\u6C42\u8BE6\u60C5", false).action(async (opts) => {
4741
+ await runZaspSettings(opts);
4742
+ });
4743
+ zasp.command("schema").description("\u67E5\u770B\u53EF\u5199\u5B57\u6BB5\u5B57\u5178\uFF08GET /settings/schema\uFF09").option("-s, --site <guidOrName>", "\u7AD9\u70B9 guid \u6216\u540D\u79F0").option("--url <url>", "\u76F4\u63A5\u6307\u5B9A\u7AD9\u70B9\u5730\u5740").option("--json", "\u8F93\u51FA JSON", false).option("--verbose", "\u8F93\u51FA\u8BF7\u6C42\u8BE6\u60C5", false).action(async (opts) => {
4744
+ await runZaspSchema(opts);
4745
+ });
4746
+ zasp.command("set").description("\u53EA\u6539\u8981\u6539\u7684\u5B57\u6BB5\uFF08PATCH /settings\uFF09").option("-s, --site <guidOrName>", "\u7AD9\u70B9 guid \u6216\u540D\u79F0").option("--url <url>", "\u76F4\u63A5\u6307\u5B9A\u7AD9\u70B9\u5730\u5740").option("--body <json>", "PATCH JSON \u5BF9\u8C61\uFF0C\u53EA\u542B\u8981\u6539\u7684\u5B57\u6BB5").option("--body-file <file>", "\u4ECE\u6587\u4EF6\u8BFB PATCH JSON").option("--set <key=value>", "\u5355\u4E2A\u5B57\u6BB5\uFF0C\u53EF\u91CD\u590D", collectPair, []).option("--clear-ai-key", "\u6E05\u7A7A\u5DF2\u4FDD\u5B58\u7684 AI Key", false).option("--clear-vector-key", "\u6E05\u7A7A\u5DF2\u4FDD\u5B58\u7684\u5411\u91CF\u5E93 Key", false).option("--clear-vector-embed-key", "\u6E05\u7A7A\u5DF2\u4FDD\u5B58\u7684 Embedding Key", false).option("--json", "\u8F93\u51FA\u66F4\u65B0\u540E\u7684\u5B8C\u6574 JSON", false).option("--verbose", "\u8F93\u51FA\u8BF7\u6C42\u8BE6\u60C5", false).action(async (opts) => {
4747
+ await runZaspSet(opts);
4748
+ });
4749
+ zasp.command("sync").description("\u5168\u91CF\u540C\u6B65\u5546\u54C1\u5230\u5411\u91CF\u5E93\uFF08POST /sync\uFF09").option("-s, --site <guidOrName>", "\u7AD9\u70B9 guid \u6216\u540D\u79F0").option("--url <url>", "\u76F4\u63A5\u6307\u5B9A\u7AD9\u70B9\u5730\u5740").option("--json", "\u8F93\u51FA JSON", false).option("--verbose", "\u8F93\u51FA\u8BF7\u6C42\u8BE6\u60C5", false).action(async (opts) => {
4750
+ await runZaspSync(opts);
4751
+ });
4752
+ zasp.command("status").description("\u516C\u5F00\u9A8C\u6536\uFF1Aready / \u662F\u5426\u914D\u597D\uFF08GET /status\uFF09").option("-s, --site <guidOrName>", "\u7AD9\u70B9 guid \u6216\u540D\u79F0").option("--url <url>", "\u76F4\u63A5\u6307\u5B9A\u7AD9\u70B9\u5730\u5740").option("--json", "\u8F93\u51FA JSON", false).option("--verbose", "\u8F93\u51FA\u8BF7\u6C42\u8BE6\u60C5", false).action(async (opts) => {
4753
+ await runZaspStatus(opts);
4754
+ });
4755
+ zasp.command("diagnostics").description("\u7BA1\u7406\u5458\u6DF1\u67E5\uFF08GET /diagnostics\uFF09").option("-s, --site <guidOrName>", "\u7AD9\u70B9 guid \u6216\u540D\u79F0").option("--url <url>", "\u76F4\u63A5\u6307\u5B9A\u7AD9\u70B9\u5730\u5740").option("--json", "\u8F93\u51FA JSON", false).option("--verbose", "\u8F93\u51FA\u8BF7\u6C42\u8BE6\u60C5", false).action(async (opts) => {
4756
+ await runZaspDiagnostics(opts);
4757
+ });
4758
+ zasp.command("recommend").description("\u524D\u53F0\u8BD5\u63A8\u4E00\u6761\uFF08POST /recommend\uFF09").option("-s, --site <guidOrName>", "\u7AD9\u70B9 guid \u6216\u540D\u79F0").option("--url <url>", "\u76F4\u63A5\u6307\u5B9A\u7AD9\u70B9\u5730\u5740").option("--query <q>", "\u68C0\u7D22\u8BCD\uFF1B\u76F8\u5173\u63A8\u8350\u53EF\u7559\u7A7A").option("--placement <p>", "assistant \u6216 related_products", "assistant").option("--limit <n>", "\u6761\u6570", "5").option("--page-id <id>", "related_products \u65F6\u7684\u5546\u54C1\u9875 ID", "0").option("--json", "\u8F93\u51FA JSON", false).option("--verbose", "\u8F93\u51FA\u8BF7\u6C42\u8BE6\u60C5", false).action(async (opts) => {
4759
+ await runZaspRecommend(opts);
4760
+ });
4761
+ }
4762
+
4388
4763
  // src/commands/plugins.ts
4389
4764
  var MAX_ZIP_BYTES = 40 * 1024 * 1024;
4390
4765
  var PROTECTED_PLUGIN_PREFIXES = ["siluzan-helper-plugin", "wp-oauth2"];
@@ -4425,7 +4800,7 @@ function readZipPayload(zipPath) {
4425
4800
  `);
4426
4801
  process.exit(1);
4427
4802
  }
4428
- const buf = readFileSync6(abs);
4803
+ const buf = readFileSync7(abs);
4429
4804
  if (buf.length > MAX_ZIP_BYTES) {
4430
4805
  console.error("\n\u274C zip \u8D85\u8FC7 20MB\uFF0C\u8BF7\u6362\u66F4\u5C0F\u7684\u5305\n");
4431
4806
  process.exit(1);
@@ -4672,6 +5047,7 @@ async function runPluginsInstall(opts) {
4672
5047
  return;
4673
5048
  }
4674
5049
  printActionResult(result, site, base, "\u63D2\u4EF6\u5DF2\u5B89\u88C5\u5E76\u542F\u7528");
5050
+ hintZaspIfNeeded(`${result.plugin || ""} ${slug} ${zipPath}`);
4675
5051
  await runPluginsList({
4676
5052
  site: site.guid || opts.site,
4677
5053
  url: site.url,
@@ -4725,6 +5101,7 @@ async function runPluginsUpdate(opts) {
4725
5101
  return;
4726
5102
  }
4727
5103
  printActionResult(result, site, base, "\u63D2\u4EF6\u5DF2\u5347\u7EA7");
5104
+ hintZaspIfNeeded(`${result.plugin || ""} ${plugin}`);
4728
5105
  await runPluginsList({
4729
5106
  site: site.guid || opts.site,
4730
5107
  url: site.url,
@@ -4870,7 +5247,7 @@ async function runPluginsUninstall(opts) {
4870
5247
  exitOnError: false
4871
5248
  });
4872
5249
  }
4873
- function register7(program2) {
5250
+ function register8(program2) {
4874
5251
  const plugins = program2.command("plugins").description("WordPress \u63D2\u4EF6\uFF1A\u5217\u51FA\u3001\u76EE\u5F55\u3001\u5B89\u88C5\u3001\u5347\u7EA7\u3001\u5378\u8F7D");
4875
5252
  plugins.command("list").description("\u5217\u51FA\u7AD9\u70B9\u5DF2\u5B89\u88C5\u7684\u5168\u90E8\u63D2\u4EF6").option("-s, --site <guidOrName>", "\u7AD9\u70B9 guid \u6216\u540D\u79F0").option("--url <url>", "\u76F4\u63A5\u6307\u5B9A\u7AD9\u70B9\u5730\u5740").option("--json", "\u8F93\u51FA JSON", false).option("--verbose", "\u8F93\u51FA\u8BF7\u6C42\u8BE6\u60C5", false).action(async (opts) => {
4876
5253
  await runPluginsList(opts);
@@ -5157,7 +5534,7 @@ async function runContentTrash(opts) {
5157
5534
  \u2705 \u5DF2\u79FB\u5165\u56DE\u6536\u7AD9 ${restBase} #${id}\uFF08status=${item.status || "trash"}\uFF09
5158
5535
  `);
5159
5536
  }
5160
- function register8(program2) {
5537
+ function register9(program2) {
5161
5538
  const content = program2.command("content").description("\u901A\u7528\u5185\u5BB9\uFF1A\u6587\u7AE0/\u4EA7\u54C1\u7B49 REST \u7C7B\u578B\uFF08\u9875\u9762\u6B63\u6587\u8BF7\u7528 pages\uFF09");
5162
5539
  content.command("types").description("\u5217\u51FA\u7AD9\u70B9\u53EF REST \u7BA1\u7406\u7684\u5185\u5BB9\u7C7B\u578B").option("-s, --site <guidOrName>", "\u7AD9\u70B9 guid \u6216\u540D\u79F0").option("--url <url>", "\u76F4\u63A5\u6307\u5B9A\u7AD9\u70B9\u5730\u5740").option("--json", "\u8F93\u51FA JSON", false).option("--verbose", "\u8F93\u51FA\u8BF7\u6C42\u8BE6\u60C5", false).action(async (opts) => {
5163
5540
  await runContentTypes(opts);
@@ -5180,7 +5557,7 @@ function register8(program2) {
5180
5557
  }
5181
5558
 
5182
5559
  // src/commands/media.ts
5183
- import { existsSync as existsSync4, readFileSync as readFileSync8 } from "fs";
5560
+ import { existsSync as existsSync4, readFileSync as readFileSync9 } from "fs";
5184
5561
  import { basename as basename3, extname, resolve as resolve3 } from "path";
5185
5562
  var MIME_BY_EXT = {
5186
5563
  ".jpg": "image/jpeg",
@@ -5305,7 +5682,7 @@ async function runMediaUpload(opts) {
5305
5682
  `);
5306
5683
  process.exit(1);
5307
5684
  }
5308
- const data = readFileSync8(abs);
5685
+ const data = readFileSync9(abs);
5309
5686
  if (data.length > MAX_MEDIA_BYTES) {
5310
5687
  console.error("\n\u274C \u6587\u4EF6\u8D85\u8FC7 20MB\n");
5311
5688
  process.exit(1);
@@ -5347,7 +5724,7 @@ async function runMediaUpload(opts) {
5347
5724
  console.log(` \u6807\u9898 : ${wpRenderedText(item.title) || filename}`);
5348
5725
  console.log();
5349
5726
  }
5350
- function register9(program2) {
5727
+ function register10(program2) {
5351
5728
  const media = program2.command("media").description("\u5A92\u4F53\u5E93\uFF1A\u5217\u51FA\u3001\u67E5\u770B\u3001\u4E0A\u4F20");
5352
5729
  media.command("list").description("\u5217\u51FA\u5A92\u4F53\u5E93\uFF08\u6700\u8FD1 50 \u6761\uFF09").option("-s, --site <guidOrName>", "\u7AD9\u70B9 guid \u6216\u540D\u79F0").option("--url <url>", "\u76F4\u63A5\u6307\u5B9A\u7AD9\u70B9\u5730\u5740").option("--json", "\u8F93\u51FA JSON", false).option("--verbose", "\u8F93\u51FA\u8BF7\u6C42\u8BE6\u60C5", false).action(async (opts) => {
5353
5730
  await runMediaList(opts);
@@ -5528,7 +5905,7 @@ async function runTermsDelete(opts) {
5528
5905
  \u2705 \u5DF2\u5220\u9664 ${restBase} #${id}
5529
5906
  `);
5530
5907
  }
5531
- function register10(program2) {
5908
+ function register11(program2) {
5532
5909
  const terms = program2.command("terms").description("\u5206\u7C7B / \u6807\u7B7E / \u81EA\u5B9A\u4E49\u5206\u7C7B\u6CD5");
5533
5910
  terms.command("taxonomies").description("\u5217\u51FA\u5206\u7C7B\u6CD5").option("-s, --site <guidOrName>", "\u7AD9\u70B9 guid \u6216\u540D\u79F0").option("--url <url>", "\u76F4\u63A5\u6307\u5B9A\u7AD9\u70B9\u5730\u5740").option("--json", "\u8F93\u51FA JSON", false).option("--verbose", "\u8F93\u51FA\u8BF7\u6C42\u8BE6\u60C5", false).action(async (opts) => {
5534
5911
  await runTermsTaxonomies(opts);
@@ -5608,7 +5985,7 @@ ${target.site.name} \u641C\u7D22\u300C${q}\u300D\uFF08${hits.length}\uFF09
5608
5985
  );
5609
5986
  console.log();
5610
5987
  }
5611
- function register11(program2) {
5988
+ function register12(program2) {
5612
5989
  program2.command("search").description("\u5168\u7AD9\u641C\u7D22\uFF08\u9875\u9762/\u6587\u7AE0/\u5A92\u4F53\u7B49\uFF09").option("-s, --site <guidOrName>", "\u7AD9\u70B9 guid \u6216\u540D\u79F0").option("--url <url>", "\u76F4\u63A5\u6307\u5B9A\u7AD9\u70B9\u5730\u5740").requiredOption("-q, --q <keyword>", "\u5173\u952E\u8BCD").option("--type <subtype>", "\u9650\u5B9A rest \u5B50\u7C7B\u578B\uFF0C\u5982 page / post").option("--json", "\u8F93\u51FA JSON", false).option("--verbose", "\u8F93\u51FA\u8BF7\u6C42\u8BE6\u60C5", false).action(async (opts) => {
5613
5990
  await runSearch(opts);
5614
5991
  });
@@ -5617,7 +5994,7 @@ function register11(program2) {
5617
5994
  // src/index.ts
5618
5995
  installProcessHandlers();
5619
5996
  var program = new Command();
5620
- program.name("siluzan-website").description("Siluzan WordPress \u7AD9\u70B9\u7BA1\u7406\uFF1A\u7AD9\u70B9\u3001\u9875\u9762\u3001\u6587\u7AE0\u3001\u5A92\u4F53\u3001\u5206\u7C7B\u3001\u63D2\u4EF6").version(getCurrentVersion2());
5997
+ program.name("siluzan-website").description("Siluzan WordPress \u7AD9\u70B9\u7BA1\u7406\uFF1A\u7AD9\u70B9\u3001\u9875\u9762\u3001\u6587\u7AE0\u3001\u5A92\u4F53\u3001\u5206\u7C7B\u3001\u63D2\u4EF6\u3001AI \u63A8\u8350").version(getCurrentVersion2());
5621
5998
  var REGISTRARS = [
5622
5999
  register,
5623
6000
  register2,
@@ -5625,10 +6002,11 @@ var REGISTRARS = [
5625
6002
  register4,
5626
6003
  register5,
5627
6004
  register6,
5628
- register8,
5629
6005
  register9,
5630
6006
  register10,
5631
6007
  register11,
6008
+ register12,
6009
+ register8,
5632
6010
  register7
5633
6011
  ];
5634
6012
  for (const reg of REGISTRARS) reg(program);
@@ -3,6 +3,7 @@ name: siluzan-website
3
3
  description: >-
4
4
  管理丝路赞 WordPress 站点(wp-k8s 托管):列出站点、页面/文章/产品、媒体、分类、插件。
5
5
  当用户提到 WordPress、建站后台、新增页面、发文章、上传图片、改官网、装插件、
6
+ AI 推荐、相关推荐、zasp、zasp-smart-recommender、B2B Site AI、
6
7
  站点 guid、siluzan-website、zionbuilder、visual-edit 时使用。不是 LPS / AI Sites,也不是 SEO JSON 生成。
7
8
  ---
8
9
 
@@ -10,7 +11,7 @@ description: >-
10
11
 
11
12
  管理用户账号下的 **WordPress 站点**(TSO 建站 / wp-k8s)。通过 CLI 调平台 all-sites 与站点 WP REST,不要手写 curl,不要猜 guid / 页面 ID。
12
13
 
13
- **不是** `landing-page-system` 的 LPS Agent,也不是 `siluzan-seo` 的 schema 生成。用户要的是「我的官网加一页 / 发一篇资讯 / 上传图片 / 装插件」才用本 Skill。
14
+ **不是** `landing-page-system` 的 LPS Agent,也不是 `siluzan-seo` 的 schema 生成。用户要的是「我的官网加一页 / 发一篇资讯 / 上传图片 / 装插件 / 配 AI 推荐」才用本 Skill。
14
15
 
15
16
  ## 一键安装
16
17
 
@@ -46,6 +47,7 @@ Windows 注意:部分 Agent 通过 PowerShell / cmd 代执行时可能失败
46
47
  | `siluzan-website search` | 全站搜索 | [references/search.md](references/search.md) |
47
48
  | `siluzan-website plugins list/catalog/install/update/uninstall` | 已装列表 / 目录 / 安装 / 升级 / 卸载 | [references/plugins.md](references/plugins.md) |
48
49
  | `siluzan-website plugins helper-status/update-helper` | 查看 / 用镜像 zip 升级 helper | [references/plugins.md](references/plugins.md) |
50
+ | `siluzan-website zasp settings/set/sync/status/recommend` | AI 推荐插件配置与验收 | [references/zasp-recommender.md](references/zasp-recommender.md) |
49
51
 
50
52
  ---
51
53
 
@@ -66,14 +68,15 @@ Windows 注意:部分 Agent 通过 PowerShell / cmd 代执行时可能失败
66
68
  | 安装外部插件 | `references/plugins.md` | 确认 → `install --zip` |
67
69
  | 升级已装插件 | `references/plugins.md` | `list` 拿 plugin → 确认 → `update --plugin`;外部再加 `--zip` |
68
70
  | 卸载插件 | `references/plugins.md` | `list` 拿 plugin → 确认 → `uninstall --plugin` |
71
+ | 配置 / 验收 AI 推荐(zasp) | `references/zasp-recommender.md` | 先确认已装 `zasp-smart-recommender` → `zasp settings` → 确认后 `set`;改向量再 `sync` |
69
72
 
70
73
  ---
71
74
 
72
75
  ## AI 行为规范
73
76
 
74
77
  1. **计划 → 确认 → 执行 → 验证**
75
- - 写操作(`pages add/update/meta/trash`、`content add/update/trash`、`media upload`、`terms add/update/delete`、`plugins install/update/uninstall/update-helper`)会立刻生效,执行前必须复述目标站点与内容,获得用户确认。
76
- - 只读(`sites list`、`pages list/get`、`content types/list/get`、`media list/get`、`terms taxonomies/list`、`search`、`plugins list/catalog/helper-status`、`config show`)可直接跑。
78
+ - 写操作(`pages add/update/meta/trash`、`content add/update/trash`、`media upload`、`terms add/update/delete`、`plugins install/update/uninstall/update-helper`、`zasp set/sync`)会立刻生效,执行前必须复述目标站点与内容,获得用户确认。
79
+ - 只读(`sites list`、`pages list/get`、`content types/list/get`、`media list/get`、`terms taxonomies/list`、`search`、`plugins list/catalog/helper-status`、`zasp settings/schema/status/diagnostics`、`config show`)可直接跑。
77
80
  2. **不猜 ID**:站点用 `sites list` 的 `guid`;页面用 `pages list` 的 `id`。
78
81
  3. **先查再写**:改页面前先 `pages list` 确认目标页存在。
79
82
  4. **只认 API Key**:所有命令走 `x-api-key`(`SILUZAN_API_KEY` 或 `login` 写入的 config)。Agent 沙箱只注入 Key,不要向用户要 JWT,也不要设 `SILUZAN_AUTH_TOKEN`。
@@ -86,5 +89,5 @@ Windows 注意:部分 Agent 通过 PowerShell / cmd 代执行时可能失败
86
89
  - 创建 / 删除 / 重启站点(SiteMan)
87
90
  - 绑定域名 / CDN / SSL
88
91
  - 单独停用插件但留在磁盘(停用只作为卸载的一步)
89
- - 推荐插件的业务配置(选择器、Cron、模型)
92
+ - 用 API 写入 `ZASP_PROXY_KEY`(K8s 环境变量)
90
93
  - LPS / AI Sites / SEO JSON 灌入
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "slug": "siluzan-website",
3
- "version": "1.0.1-beta.4",
4
- "publishedAt": 1789699291560,
3
+ "version": "1.0.1-beta.5",
4
+ "publishedAt": 1789721509096,
5
5
  "homepage": "https://www.siluzan.com",
6
6
  "source": "https://dev.azure.com/jack4it/Sammamish/_git/siluzan-skill",
7
7
  "requiredBinaries": [
@@ -66,6 +66,8 @@ siluzan-website plugins uninstall --site <guid> --plugin dummy-plugin/dummy-plug
66
66
 
67
67
  `--plugin` 与 `plugins list` 的 plugin 列一致;只写目录名且能唯一对上也可以。
68
68
 
69
+ 装完或升完 `zasp-smart-recommender` 之后,用 [zasp-recommender.md](zasp-recommender.md) 配相关推荐、内置/自定义 AI、向量库和同步。不要手写 curl,不要向用户要 Application Password。
70
+
69
71
  ## helper
70
72
 
71
73
  ```bash
@@ -84,6 +86,7 @@ siluzan-website plugins update-helper --site <guid>
84
86
  5. 用户要卸载 → `list` 拿到 `plugin` → 确认不是 helper / oauth2 → `uninstall --plugin`
85
87
  6. catalog / install / update 报路由 404 → 命令会自动 `update-helper` 再重试;oauth2 本身 404 / 401 → 告诉用户要先 SiteMan 镜像升级
86
88
  7. 装完看复核列表是否 `active`;卸完确认列表里已经没有该项
89
+ 8. 装的是 `zasp-smart-recommender` → 转 [zasp-recommender.md](zasp-recommender.md) 用 `zasp settings / set / sync` 配业务,不要停在「已安装」
87
90
 
88
91
  匹配到多个目录项时让用户选,不要猜。
89
92
 
@@ -0,0 +1,256 @@
1
+ # zasp-smart-recommender(AI 推荐)
2
+
3
+ 装完官方目录里的 `zasp-smart-recommender` 之后,用本组命令改插件设置。不要打开后台表单,不要改 `options.php`,不要手写 curl。
4
+
5
+ 鉴权与其它 website 命令一样:**API Key(x-api-key)**。不要向用户要 WordPress Application Password。
6
+
7
+ 底层是站点 `GET/PATCH /wp-json/zasp-b2b/v2/settings`、`POST /sync`、`GET /status`。`ZASP_PROXY_KEY` 是 K8s 环境变量,**本命令写不了**(仅 `ai_provider` / `vector_provider` 为 builtin 时需要)。
8
+
9
+ **两个开关互相独立**:
10
+
11
+ | 开关 | 取值 | 含义 |
12
+ |------|------|------|
13
+ | `ai_provider` | `builtin` | 对话走丝路赞平台代理 |
14
+ | | `custom` | 对话走自己的 OpenAI 兼容接口(场景 3) |
15
+ | `vector_provider` | `builtin` | 向量走平台代理 |
16
+ | | `dashvector` | 自建阿里云 DashVector(场景 4) |
17
+ | | `generic` | 自建 HTTP 向量网关(场景 5) |
18
+ | | `none` | 不用向量库 |
19
+
20
+ 改设置只传要改的字段,其它保持原值。密钥字段 `ai_api_key` / `vector_api_key` / `vector_embed_api_key`:GET 只回 `has_*` 布尔;`--set` 传新字符串=覆盖;不传=保留;`--clear-*-key`=清空。不要把密钥打印进聊天记录。
21
+
22
+ 路由 404:插件没装或版本太旧。先 `plugins list`,再 `plugins catalog` / `install --slug zasp-smart-recommender` 或 `update --plugin zasp-smart-recommender/zasp-smart-recommender`。
23
+
24
+ 关系审核 `POST /relations/{id}/review` 目前可能不可用,用户未明确要求时跳过。不要编造不存在的字段。
25
+
26
+ ---
27
+
28
+ ## 命令
29
+
30
+ ```bash
31
+ siluzan-website zasp settings --site <guid>
32
+ siluzan-website zasp settings --site <guid> --json
33
+ siluzan-website zasp schema --site <guid>
34
+ siluzan-website zasp set --site <guid> --set related_enabled=1 --set related_limit=6
35
+ siluzan-website zasp set --site <guid> --body "{\"ai_provider\":\"builtin\",\"vector_provider\":\"builtin\"}"
36
+ siluzan-website zasp set --site <guid> --body-file ./patch.json
37
+ siluzan-website zasp set --site <guid> --clear-ai-key
38
+ siluzan-website zasp sync --site <guid>
39
+ siluzan-website zasp status --site <guid>
40
+ siluzan-website zasp diagnostics --site <guid>
41
+ siluzan-website zasp recommend --site <guid> --query "stainless steel valve"
42
+ ```
43
+
44
+ `set` 是写操作,执行前必须复述站点与将要改的字段,获得用户确认。`settings` / `schema` / `status` 只读,可直接跑。
45
+
46
+ ---
47
+
48
+ ## 场景 0:先看当前配置(每次改之前建议先做)
49
+
50
+ ```bash
51
+ siluzan-website zasp settings --site <guid> --json
52
+ siluzan-website zasp schema --site <guid>
53
+ ```
54
+
55
+ 看 `settings.*`(已保存)和 `runtime.*`(是否真能连上、上次同步)。`runtime.sync_state.error` 非空 = 上次同步失败,先修配置再 sync。
56
+
57
+ ---
58
+
59
+ ## 场景 1:打开页面「相关推荐」,显示 6 条
60
+
61
+ 商品详情页 Smart Related Products,最多 6 个(上限 10)。对话框推荐上限仍是 50,不受 `related_limit` 影响。
62
+
63
+ ```bash
64
+ siluzan-website zasp set --site <guid> --set related_enabled=1 --set related_limit=6 --set related_selector=section.related.products
65
+ ```
66
+
67
+ | 字段 | 含义 |
68
+ |------|------|
69
+ | `related_enabled` | `1` 开 / `0` 关 |
70
+ | `related_limit` | 页面条数,1–10 |
71
+ | `related_selector` | 前端注入节点;Woo 默认 `section.related.products` |
72
+
73
+ 关掉:`--set related_enabled=0`。只改相关推荐一般**不用** sync。
74
+
75
+ ---
76
+
77
+ ## 场景 2:用丝路赞内置代理(最常见上线配置)
78
+
79
+ AI + 向量都走平台代理,站点侧不存 DashVector/百炼密钥。前提:站点环境已注入 `ZASP_PROXY_KEY`。
80
+
81
+ ```bash
82
+ siluzan-website zasp set --site <guid> --body "{\"ai_provider\":\"builtin\",\"vector_provider\":\"builtin\",\"platform_proxy_base\":\"\",\"auto_sync\":1}"
83
+ ```
84
+
85
+ | 字段 | 含义 |
86
+ |------|------|
87
+ | `ai_provider: builtin` | 对话走平台代理 |
88
+ | `vector_provider: builtin` | 向量 upsert/search 走平台代理 |
89
+ | `platform_proxy_base: ""` | 空=按站点域名自动选 CI/Prod 代理;只有要强制指定时才填 URL |
90
+ | `auto_sync: 1` | 商品发布/更新/下架时自动推向量 |
91
+
92
+ 强制 CI 代理:`--set platform_proxy_base=https://chatgpt-ci.mysiluzan.com`。然后全量同步(场景 8)。
93
+
94
+ ---
95
+
96
+ ## 场景 3:自定义 AI(用自己的大模型,不走平台代理)
97
+
98
+ 对应后台「AI → 自定义」。`ai_provider` 必须 `"custom"`,否则填了 endpoint 也不会走。接口需兼容 `POST …/v1/chat/completions`。只影响对话 / 查询规划 / AI 抽取;向量仍由 `vector_provider` 单独决定。
99
+
100
+ ```bash
101
+ siluzan-website zasp set --site <guid> --body "{\"ai_provider\":\"custom\",\"ai_endpoint\":\"https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions\",\"ai_model\":\"qwen-plus\",\"ai_api_key\":\"sk-你的模型Key\",\"ai_timeout\":30}"
102
+ ```
103
+
104
+ | 字段 | 必填 | 含义 |
105
+ |------|------|------|
106
+ | `ai_provider` | 是 | 固定 `custom` |
107
+ | `ai_endpoint` | 是 | 完整 Chat Completions URL |
108
+ | `ai_model` | 是 | 如 `qwen-plus`、`deepseek-chat` |
109
+ | `ai_api_key` | 视网关 | Bearer;不传则保留旧值 |
110
+ | `ai_timeout` | 否 | 10–90 秒,默认 30 |
111
+
112
+ DeepSeek:`ai_endpoint=https://api.deepseek.com/chat/completions`,`ai_model=deepseek-chat`。切回内置:`--set ai_provider=builtin`。只改 AI **不必** sync。验收:`settings --json` 里 `runtime.ai_configured` 为 true。
113
+
114
+ ---
115
+
116
+ ## 场景 4:自定义向量库 — DashVector(阿里云)
117
+
118
+ `vector_provider` 必须 `dashvector`。Collection 维度必须 = `vector_embed_dimension`(常用 1024)。可与 AI 的 builtin/custom 任意组合。
119
+
120
+ ```bash
121
+ siluzan-website zasp set --site <guid> --body-file dashvector.json
122
+ ```
123
+
124
+ `dashvector.json` 示例:
125
+
126
+ ```json
127
+ {
128
+ "vector_provider": "dashvector",
129
+ "vector_endpoint": "vrs-cn-xxx.dashvector.cn-hangzhou.aliyuncs.com",
130
+ "vector_collection": "zasp_wp_products",
131
+ "vector_api_key": "你的DashVector-API-KEY",
132
+ "vector_site_id": "",
133
+ "vector_embed_endpoint": "https://dashscope.aliyuncs.com/compatible-mode/v1/embeddings",
134
+ "vector_embed_model": "text-embedding-v4",
135
+ "vector_embed_dimension": 1024,
136
+ "vector_embed_api_key": "sk-你的百炼EmbeddingKey",
137
+ "auto_sync": 1
138
+ }
139
+ ```
140
+
141
+ | 字段 | 必填 | 含义 |
142
+ |------|------|------|
143
+ | `vector_endpoint` | 是 | Cluster,可只填主机名 |
144
+ | `vector_collection` | 是 | 不存在时可按维度自动创建 |
145
+ | `vector_api_key` | 是 | DashVector 控制台 API-KEY |
146
+ | `vector_site_id` | 否 | 空=自动探测;改了必须重新全量 sync |
147
+ | `vector_embed_*` | 是 | 商品文本 → 向量;新加坡用 `dashscope-intl.aliyuncs.com` |
148
+
149
+ 改完必须 `zasp sync`。验收:`runtime.vector_configured === true` 且 sync `ok: true`。
150
+
151
+ ---
152
+
153
+ ## 场景 5:自定义向量库 — Generic HTTP 网关
154
+
155
+ `vector_provider` 必须 `generic`。Search / Upsert / Delete 都 POST JSON,带 `action`、`collection`、`site_id`;Search 响应需含 `post_ids`。Embedding 仍由插件侧 `vector_embed_*` 完成。
156
+
157
+ ```json
158
+ {
159
+ "vector_provider": "generic",
160
+ "vector_endpoint": "https://vector-gateway.example.com/v1/search",
161
+ "vector_upsert_endpoint": "https://vector-gateway.example.com/v1/write",
162
+ "vector_collection": "zasp_wp_products",
163
+ "vector_api_key": "你的网关Token",
164
+ "vector_site_id": "",
165
+ "vector_embed_endpoint": "https://dashscope.aliyuncs.com/compatible-mode/v1/embeddings",
166
+ "vector_embed_model": "text-embedding-v4",
167
+ "vector_embed_dimension": 1024,
168
+ "vector_embed_api_key": "sk-你的EmbeddingKey",
169
+ "auto_sync": 1
170
+ }
171
+ ```
172
+
173
+ 网关协议:Search body `{"action":"search","collection":"...","site_id":"...","query":"...","top_k":50,"filter_post_ids":[1]}`;响应 `{"post_ids":[1,2,3]}`。改完必须 `zasp sync`。
174
+
175
+ 关闭向量库:`--set vector_provider=none`。
176
+
177
+ ---
178
+
179
+ ## 场景 6:只改咨询链接 / 筛选项 / 索引范围
180
+
181
+ ```bash
182
+ siluzan-website zasp set --site <guid> --set contact_url=mailto:sales@example.com?subject=Product%20consultation
183
+ siluzan-website zasp set --site <guid> --set max_facets=8
184
+ siluzan-website zasp set --site <guid> --set product_post_types=product --set content_post_types=post,product
185
+ siluzan-website zasp set --site <guid> --set meta_exclude=password,passwd,secret,token,api_key,session,cookie,nonce,private_key,internal_cost
186
+ ```
187
+
188
+ `product_post_types` / `content_post_types`:`auto` = 自动发现;逗号分隔 = 固定类型名。
189
+
190
+ ---
191
+
192
+ ## 场景 7:清空 / 轮换密钥
193
+
194
+ ```bash
195
+ siluzan-website zasp set --site <guid> --clear-vector-key
196
+ siluzan-website zasp set --site <guid> --set vector_api_key=新的DashVector-API-KEY
197
+ siluzan-website zasp set --site <guid> --clear-ai-key
198
+ siluzan-website zasp set --site <guid> --clear-vector-embed-key
199
+ ```
200
+
201
+ 换成新 key 直接传新值即可,不必先 clear。
202
+
203
+ ---
204
+
205
+ ## 场景 8:全量同步商品到向量库
206
+
207
+ 改了 `vector_*` / `product_post_types` 之后,或怀疑向量库缺货。只改 custom AI 一般不必 sync。
208
+
209
+ ```bash
210
+ siluzan-website zasp sync --site <guid>
211
+ ```
212
+
213
+ 成功看写入条数。失败(常见 502 / `Collection Not Exist`):先修 Collection / key / 维度,再 `set`,再 `sync`。
214
+
215
+ ---
216
+
217
+ ## 场景 9:验收是否可用
218
+
219
+ ```bash
220
+ siluzan-website zasp status --site <guid>
221
+ siluzan-website zasp diagnostics --site <guid>
222
+ ```
223
+
224
+ `status` 关注:`ready`、`ai_configured`、`vector_configured`、`vector_site_id`、`source_counts`。`diagnostics` 含 profile、关系统计,需管理员权限。
225
+
226
+ ---
227
+
228
+ ## 场景 10:前台试推一条(可选验收)
229
+
230
+ ```bash
231
+ siluzan-website zasp recommend --site <guid> --query "stainless steel valve" --placement assistant --limit 5
232
+ siluzan-website zasp recommend --site <guid> --placement related_products --limit 4 --page-id 31
233
+ ```
234
+
235
+ `related_products` 必须带有效商品 `page-id`,同分类优先;`assistant` 走对话检索。
236
+
237
+ ---
238
+
239
+ ## Agent 标准流程
240
+
241
+ 1. `plugins list` 确认已装并启用 `zasp-smart-recommender`
242
+ 2. `zasp settings --json` 看现状
243
+ 3. 按用户意图选上面某一场景 → `zasp set`(只带必要字段,先确认)
244
+ 4. 若动过向量后端 → `zasp sync`
245
+ 5. `zasp status` 确认
246
+ 6. 用中文简短回复:改了什么、同步条数、失败原因(如有)。密钥只用 `has_*` 描述
247
+
248
+ ## 组合速查
249
+
250
+ | 需求 | AI | 向量 |
251
+ |------|----|------|
252
+ | 全托管(最常见) | `builtin` | `builtin` |
253
+ | 自有大模型 + 平台向量 | `custom` + endpoint/model/key | `builtin` |
254
+ | 平台对话 + 自建 DashVector | `builtin` | `dashvector` + endpoint/collection/keys + embed |
255
+ | 全自建 | `custom` | `dashvector` 或 `generic` |
256
+ | 不用向量 | 任意 | `none` |
@@ -9,7 +9,7 @@ $ErrorActionPreference = 'Stop'
9
9
  # -- Package info (injected at build time) ------------------------------------
10
10
  $PKG_NAME = 'siluzan-website-cli'
11
11
  # PKG_VERSION 锁定到与本脚本同批构建产物一致的版本,避免与 dist/skill 错位
12
- $PKG_VERSION = '1.0.1-beta.4'
12
+ $PKG_VERSION = '1.0.1-beta.5'
13
13
  $CLI_BIN = 'siluzan-website'
14
14
  $SKILL_LABEL = 'Siluzan Website'
15
15
  $INSTALL_CMD = 'npm install -g siluzan-website-cli@beta'
@@ -9,7 +9,7 @@ set -euo pipefail
9
9
  # -- Package info (injected at build time) ------------------------------------
10
10
  readonly PKG_NAME="siluzan-website-cli"
11
11
  # PKG_VERSION 锁定到与本脚本同批构建产物一致的版本,避免与 dist/skill 错位
12
- readonly PKG_VERSION="1.0.1-beta.4"
12
+ readonly PKG_VERSION="1.0.1-beta.5"
13
13
  readonly CLI_BIN="siluzan-website"
14
14
  readonly SKILL_LABEL="Siluzan Website"
15
15
  readonly INSTALL_CMD="npm install -g siluzan-website-cli@beta"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "siluzan-website-cli",
3
- "version": "1.0.1-beta.4",
3
+ "version": "1.0.1-beta.5",
4
4
  "description": "Siluzan WordPress 站点管理 Skill CLI — 列出站点、新增/编辑页面、安装/升级插件。",
5
5
  "keywords": [
6
6
  "ai-skill",