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

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
@@ -22,7 +22,7 @@ siluzan-website content list --site <guid> --type posts
22
22
  siluzan-website media upload --site <guid> --file ./hero.png
23
23
  siluzan-website search --site <guid> --q "关于我们"
24
24
  siluzan-website plugins list --site <guid>
25
- siluzan-website plugins catalog --site <guid>
25
+ siluzan-website plugins catalog
26
26
  siluzan-website plugins install --site <guid> --slug <slug>
27
27
  siluzan-website plugins install --site <guid> --zip ./plugin.zip
28
28
  siluzan-website plugins update --site <guid> --plugin <plugin>
package/dist/index.js CHANGED
@@ -3317,6 +3317,7 @@ import { spawnSync } from "child_process";
3317
3317
  var BUILD_ENV = "test";
3318
3318
  var DEFAULT_API_BASE = "https://api-ci.siluzan.com";
3319
3319
  var DEFAULT_CSO_BASE = "https://cso-ci.siluzan.com";
3320
+ var DEFAULT_CSO_PLUGIN_API = "https://cso-api-ci.siluzan.com";
3320
3321
  var DEFAULT_WEB_BASE = "https://www-ci.siluzan.com";
3321
3322
  var DEFAULT_SITE_STAGE = "ci";
3322
3323
 
@@ -3727,6 +3728,7 @@ function loadConfig() {
3727
3728
  }
3728
3729
  const apiBaseUrl = process.env.SILUZAN_WEBSITE_API_BASE ?? DEFAULT_API_BASE;
3729
3730
  const csoBaseUrl = process.env.SILUZAN_CSO_BASE ?? DEFAULT_CSO_BASE;
3731
+ const csoPluginApiBaseUrl = process.env.SILUZAN_CSO_PLUGIN_API ?? DEFAULT_CSO_PLUGIN_API;
3730
3732
  const apiErr = validateBaseUrl(apiBaseUrl);
3731
3733
  if (apiErr) {
3732
3734
  console.error(`
@@ -3739,9 +3741,16 @@ function loadConfig() {
3739
3741
  \u274C csoBaseUrl \u4E0D\u5408\u6CD5\uFF1A${csoErr}`);
3740
3742
  process.exit(1);
3741
3743
  }
3744
+ const pluginApiErr = validateBaseUrl(csoPluginApiBaseUrl);
3745
+ if (pluginApiErr) {
3746
+ console.error(`
3747
+ \u274C csoPluginApiBaseUrl \u4E0D\u5408\u6CD5\uFF1A${pluginApiErr}`);
3748
+ process.exit(1);
3749
+ }
3742
3750
  return {
3743
3751
  apiBaseUrl,
3744
3752
  csoBaseUrl,
3753
+ csoPluginApiBaseUrl,
3745
3754
  authToken: "",
3746
3755
  apiKey,
3747
3756
  dataPermission: process.env.SILUZAN_DATA_PERMISSION ?? shared.dataPermission
@@ -4218,7 +4227,166 @@ function register6(program2) {
4218
4227
  // src/commands/plugins.ts
4219
4228
  import { readFileSync as readFileSync6, existsSync as existsSync3 } from "fs";
4220
4229
  import { basename as basename2, resolve as resolve2 } from "path";
4221
- var MAX_ZIP_BYTES = 20 * 1024 * 1024;
4230
+
4231
+ // src/utils/wp-plugin-catalog.ts
4232
+ import * as http2 from "http";
4233
+ import * as https2 from "https";
4234
+ function trimSlash(url) {
4235
+ return url.replace(/\/+$/, "");
4236
+ }
4237
+ function pluginApiBases(config) {
4238
+ const seen = /* @__PURE__ */ new Set();
4239
+ const out = [];
4240
+ for (const raw of [config.csoPluginApiBaseUrl, config.csoBaseUrl]) {
4241
+ const base = trimSlash(raw || "");
4242
+ if (!base || seen.has(base)) continue;
4243
+ seen.add(base);
4244
+ out.push(base);
4245
+ }
4246
+ return out;
4247
+ }
4248
+ function isNetworkish(err) {
4249
+ const msg = err.message || "";
4250
+ return /fetch failed|ENOTFOUND|ECONNREFUSED|ETIMEDOUT|ECONNRESET|EPROTO|TLS|socket disconnected|certificate|HTTP 5\d\d/i.test(
4251
+ msg
4252
+ );
4253
+ }
4254
+ function compareVersion(a, b) {
4255
+ const pa = a.split(".").map((p) => Number(p) || 0);
4256
+ const pb = b.split(".").map((p) => Number(p) || 0);
4257
+ const n = Math.max(pa.length, pb.length);
4258
+ for (let i = 0; i < n; i++) {
4259
+ const da = pa[i] ?? 0;
4260
+ const db = pb[i] ?? 0;
4261
+ if (da !== db) return da - db;
4262
+ }
4263
+ return 0;
4264
+ }
4265
+ function versionFromPath(path6, filename) {
4266
+ const parts = path6.replace(/\\/g, "/").split("/").filter(Boolean);
4267
+ if (parts.length >= 2) return parts[1];
4268
+ const m = filename.match(/\.((?:\d+\.)*\d+)\.zip$/i);
4269
+ return m?.[1] ?? "";
4270
+ }
4271
+ function collectZips(nodes, out = []) {
4272
+ for (const node of nodes || []) {
4273
+ if (!node.isDirectory && /\.zip$/i.test(node.name || "") && node.url) {
4274
+ out.push(node);
4275
+ }
4276
+ if (node.children?.length) collectZips(node.children, out);
4277
+ }
4278
+ return out;
4279
+ }
4280
+ function flattenOfficialPlugins(items) {
4281
+ const best = /* @__PURE__ */ new Map();
4282
+ for (const zip of collectZips(items)) {
4283
+ const path6 = (zip.path || "").replace(/\\/g, "/");
4284
+ const filename = zip.name || path6.split("/").pop() || "";
4285
+ const slug = path6.split("/").filter(Boolean)[0] || filename.replace(/\.zip$/i, "");
4286
+ if (!slug) continue;
4287
+ const candidate = {
4288
+ slug,
4289
+ version: versionFromPath(path6, filename),
4290
+ filename,
4291
+ path: path6,
4292
+ size: Number(zip.size) || 0,
4293
+ url: zip.url || "",
4294
+ lastModified: zip.lastModified || ""
4295
+ };
4296
+ const prev = best.get(slug);
4297
+ if (!prev || compareVersion(candidate.version, prev.version) > 0) {
4298
+ best.set(slug, candidate);
4299
+ }
4300
+ }
4301
+ return [...best.values()].sort((a, b) => a.slug.localeCompare(b.slug));
4302
+ }
4303
+ async function fetchOfficialPluginList(config, opts = {}) {
4304
+ const params = new URLSearchParams();
4305
+ if (opts.prefix?.trim()) params.set("prefix", opts.prefix.trim());
4306
+ const qs = params.toString();
4307
+ const path6 = `/cso/v1/wp-plugin/list${qs ? `?${qs}` : ""}`;
4308
+ const bases = pluginApiBases(config);
4309
+ let lastErr;
4310
+ for (let i = 0; i < bases.length; i++) {
4311
+ const url = `${bases[i]}${path6}`;
4312
+ try {
4313
+ const raw = await apiFetch2(url, config, {}, opts.verbose);
4314
+ const code = raw.code;
4315
+ if (code !== void 0 && code !== 1) {
4316
+ throw new Error(raw.message || `\u76EE\u5F55\u63A5\u53E3\u8FD4\u56DE code=${code}`);
4317
+ }
4318
+ const data = raw.data || {};
4319
+ return {
4320
+ container: data.container || "wp-plugins",
4321
+ items: flattenOfficialPlugins(data.items)
4322
+ };
4323
+ } catch (e) {
4324
+ lastErr = e;
4325
+ if (i < bases.length - 1 && isNetworkish(lastErr)) {
4326
+ console.log(`\u76EE\u5F55\u63A5\u53E3 ${bases[i]} \u4E0D\u53EF\u8FBE\uFF0C\u6539\u8D70 ${bases[i + 1]} \u2026`);
4327
+ continue;
4328
+ }
4329
+ throw lastErr;
4330
+ }
4331
+ }
4332
+ throw lastErr || new Error("\u76EE\u5F55\u63A5\u53E3\u4E0D\u53EF\u7528");
4333
+ }
4334
+ async function findOfficialPlugin(config, slugOrPlugin, verbose = false) {
4335
+ const needle = slugOrPlugin.replace(/\\/g, "/").replace(/\.php$/i, "").toLowerCase();
4336
+ if (!needle) return null;
4337
+ const { items } = await fetchOfficialPluginList(config, { verbose });
4338
+ return items.find((p) => p.slug.toLowerCase() === needle) || items.find((p) => needle === p.slug.toLowerCase() || needle.startsWith(`${p.slug.toLowerCase()}/`)) || null;
4339
+ }
4340
+ function downloadOfficialZip(url) {
4341
+ return new Promise((resolve4, reject) => {
4342
+ let parsed;
4343
+ try {
4344
+ parsed = new URL(url);
4345
+ } catch {
4346
+ reject(new Error("\u76EE\u5F55\u5305\u5730\u5740\u4E0D\u5408\u6CD5"));
4347
+ return;
4348
+ }
4349
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
4350
+ reject(new Error("\u76EE\u5F55\u5305\u5730\u5740\u5FC5\u987B\u662F http(s)"));
4351
+ return;
4352
+ }
4353
+ const transport = parsed.protocol === "https:" ? https2 : http2;
4354
+ const req = transport.get(url, { timeout: 12e4 }, (res) => {
4355
+ const status = res.statusCode ?? 0;
4356
+ if (status >= 300 && status < 400 && res.headers.location) {
4357
+ res.resume();
4358
+ downloadOfficialZip(res.headers.location).then(resolve4, reject);
4359
+ return;
4360
+ }
4361
+ if (status < 200 || status >= 300) {
4362
+ res.resume();
4363
+ reject(new Error(`\u4E0B\u8F7D\u76EE\u5F55\u5305\u5931\u8D25\uFF1AHTTP ${status}`));
4364
+ return;
4365
+ }
4366
+ const chunks = [];
4367
+ let size = 0;
4368
+ res.on("data", (chunk) => {
4369
+ size += chunk.length;
4370
+ if (size > 40 * 1024 * 1024) {
4371
+ res.destroy();
4372
+ reject(new Error("\u76EE\u5F55\u5305\u8D85\u8FC7 40MB"));
4373
+ return;
4374
+ }
4375
+ chunks.push(chunk);
4376
+ });
4377
+ res.on("end", () => resolve4(Buffer.concat(chunks)));
4378
+ res.on("error", reject);
4379
+ });
4380
+ req.on("timeout", () => {
4381
+ req.destroy();
4382
+ reject(new Error("\u4E0B\u8F7D\u76EE\u5F55\u5305\u8D85\u65F6"));
4383
+ });
4384
+ req.on("error", reject);
4385
+ });
4386
+ }
4387
+
4388
+ // src/commands/plugins.ts
4389
+ var MAX_ZIP_BYTES = 40 * 1024 * 1024;
4222
4390
  var PROTECTED_PLUGIN_PREFIXES = ["siluzan-helper-plugin", "wp-oauth2"];
4223
4391
  function isProtectedPlugin(plugin) {
4224
4392
  const key = pluginKey(plugin);
@@ -4234,6 +4402,12 @@ function pluginName(item) {
4234
4402
  function pluginKey(value) {
4235
4403
  return value.replace(/\\/g, "/").replace(/\.php$/i, "").toLowerCase();
4236
4404
  }
4405
+ function formatBytes(n) {
4406
+ if (!n) return "-";
4407
+ if (n < 1024) return `${n} B`;
4408
+ if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
4409
+ return `${(n / 1024 / 1024).toFixed(1)} MB`;
4410
+ }
4237
4411
  async function resolveTarget2(opts) {
4238
4412
  const config = loadConfig();
4239
4413
  const site = await resolveSite(config, opts);
@@ -4310,34 +4484,23 @@ async function runPluginsList(opts) {
4310
4484
  if (opts.exitOnError === false) return;
4311
4485
  process.exit(1);
4312
4486
  }
4313
- let catalog = [];
4487
+ let official = [];
4314
4488
  try {
4315
- const res = await apiFetch2(
4316
- `${base}/wp-json/siluzan-helper/v1/plugins/catalog`,
4317
- config,
4318
- {},
4319
- opts.verbose
4320
- );
4321
- catalog = Array.isArray(res.items) ? res.items : [];
4489
+ official = (await fetchOfficialPluginList(config, { verbose: opts.verbose })).items;
4322
4490
  } catch {
4323
4491
  }
4324
- const catalogByKey = /* @__PURE__ */ new Map();
4325
- for (const item of catalog) {
4326
- if (item.plugin) catalogByKey.set(pluginKey(item.plugin), item);
4327
- if (item.slug) catalogByKey.set(pluginKey(item.slug), item);
4328
- }
4492
+ const officialBySlug = new Map(official.map((item) => [item.slug.toLowerCase(), item]));
4329
4493
  const rows = plugins.map((p) => {
4330
4494
  const plugin = p.plugin ?? "";
4331
- const hit = catalogByKey.get(pluginKey(plugin));
4332
- let updateSource = "none";
4333
- if (hit?.update_available) updateSource = "catalog";
4495
+ const slug = pluginKey(plugin).split("/")[0];
4496
+ const hit = officialBySlug.get(slug);
4334
4497
  return {
4335
4498
  plugin,
4336
4499
  name: pluginName(p),
4337
4500
  status: p.status ?? "",
4338
4501
  version: p.version ?? "",
4339
- updateSource,
4340
- catalogVersion: hit?.new_version ?? ""
4502
+ updateSource: hit ? "catalog" : "none",
4503
+ catalogVersion: hit?.version ?? ""
4341
4504
  };
4342
4505
  });
4343
4506
  if (opts.json) {
@@ -4385,20 +4548,10 @@ ${site.name} \u5DF2\u88C5\u63D2\u4EF6\uFF08${rows.length}\uFF09
4385
4548
  console.log();
4386
4549
  }
4387
4550
  async function runPluginsCatalog(opts) {
4388
- const target = await resolveTarget2(opts);
4389
- const { site, base } = target;
4390
- let res;
4551
+ const config = loadConfig();
4552
+ let pack;
4391
4553
  try {
4392
- res = await withHelperBootstrap(
4393
- opts,
4394
- target,
4395
- () => apiFetch2(
4396
- `${base}/wp-json/siluzan-helper/v1/plugins/catalog`,
4397
- target.config,
4398
- {},
4399
- opts.verbose
4400
- )
4401
- );
4554
+ pack = await fetchOfficialPluginList(config, { prefix: opts.prefix, verbose: opts.verbose });
4402
4555
  } catch (e) {
4403
4556
  console.error(`
4404
4557
  \u274C \u83B7\u53D6\u63D2\u4EF6\u76EE\u5F55\u5931\u8D25\uFF1A${e.message}
@@ -4406,46 +4559,78 @@ async function runPluginsCatalog(opts) {
4406
4559
  process.exit(1);
4407
4560
  return;
4408
4561
  }
4409
- const items = Array.isArray(res.items) ? res.items : [];
4562
+ const installedBySlug = /* @__PURE__ */ new Map();
4563
+ if (opts.site || opts.url) {
4564
+ try {
4565
+ const target = await resolveTarget2(opts);
4566
+ const plugins = await apiFetch2(
4567
+ `${target.base}/wp-json/wp/v2/plugins`,
4568
+ target.config,
4569
+ {},
4570
+ opts.verbose
4571
+ );
4572
+ if (Array.isArray(plugins)) {
4573
+ for (const p of plugins) {
4574
+ const slug = pluginKey(p.plugin || "").split("/")[0];
4575
+ if (slug) installedBySlug.set(slug, { version: p.version || "", status: p.status || "" });
4576
+ }
4577
+ }
4578
+ } catch {
4579
+ }
4580
+ }
4581
+ const rows = pack.items.map((p) => {
4582
+ const onSite = installedBySlug.get(p.slug.toLowerCase());
4583
+ return {
4584
+ slug: p.slug,
4585
+ version: p.version,
4586
+ size: formatBytes(p.size),
4587
+ installed: onSite ? "yes" : opts.site || opts.url ? "no" : "-",
4588
+ filename: p.filename
4589
+ };
4590
+ });
4410
4591
  if (opts.json) {
4411
4592
  console.log(
4412
- JSON.stringify(
4413
- { site: { guid: site.guid, name: site.name, url: site.url }, total: items.length, items },
4414
- null,
4415
- 2
4416
- )
4593
+ JSON.stringify({ container: pack.container, total: pack.items.length, items: pack.items }, null, 2)
4417
4594
  );
4418
4595
  return;
4419
4596
  }
4420
- if (items.length === 0) {
4421
- console.log(`
4422
- \u7AD9\u70B9 ${site.name} \u7684\u4E1D\u8DEF\u8D5E\u63D2\u4EF6\u76EE\u5F55\u4E3A\u7A7A\u3002
4423
- `);
4597
+ if (rows.length === 0) {
4598
+ console.log("\n\u5B98\u65B9\u63D2\u4EF6\u76EE\u5F55\u4E3A\u7A7A\u3002\n");
4424
4599
  return;
4425
4600
  }
4426
- const columns = [
4427
- { key: "slug", header: "slug" },
4428
- { key: "name", header: "\u540D\u79F0" },
4429
- { key: "new_version", header: "\u76EE\u5F55\u7248\u672C" },
4430
- { key: "installed", header: "\u5DF2\u88C5" },
4431
- { key: "active", header: "\u542F\u7528" },
4432
- { key: "update_available", header: "\u53EF\u66F4\u65B0" }
4433
- ];
4434
4601
  console.log(`
4435
- ${site.name} \u53EF\u5B89\u88C5\u76EE\u5F55\uFF08${items.length}\uFF09
4602
+ \u5B98\u65B9\u63D2\u4EF6\u76EE\u5F55 ${pack.container}\uFF08${rows.length}\uFF09
4436
4603
  `);
4437
- printCliTable(
4438
- items.map((p) => ({
4439
- slug: p.slug || p.plugin || "-",
4440
- name: p.name || "-",
4441
- new_version: p.new_version || "-",
4442
- installed: p.installed ? "yes" : "no",
4443
- active: p.active ? "yes" : "no",
4444
- update_available: p.update_available ? "yes" : "no"
4445
- })),
4446
- columns
4604
+ printCliTable(rows, [
4605
+ { key: "slug", header: "slug" },
4606
+ { key: "version", header: "\u76EE\u5F55\u7248\u672C" },
4607
+ { key: "size", header: "\u5927\u5C0F" },
4608
+ { key: "installed", header: "\u672C\u7AD9\u5DF2\u88C5" },
4609
+ { key: "filename", header: "zip" }
4610
+ ]);
4611
+ console.log("\n\u5B89\u88C5\uFF1Asiluzan-website plugins install --site <guid> --slug <slug>\n");
4612
+ }
4613
+ async function installOfficialZip(target, official, verbose = false) {
4614
+ if (official.size > MAX_ZIP_BYTES) {
4615
+ throw new Error(`\u76EE\u5F55\u5305 ${official.filename} \u8D85\u8FC7 40MB`);
4616
+ }
4617
+ console.log(`\u4ECE\u5B98\u65B9\u76EE\u5F55\u4E0B\u8F7D ${official.slug} ${official.version} \u2026`);
4618
+ const buf = await downloadOfficialZip(official.url);
4619
+ if (buf.length > MAX_ZIP_BYTES) {
4620
+ throw new Error(`\u76EE\u5F55\u5305 ${official.filename} \u8D85\u8FC7 40MB`);
4621
+ }
4622
+ return apiFetch2(
4623
+ `${target.base}/wp-json/siluzan-helper/v1/plugins/install-zip`,
4624
+ target.config,
4625
+ {
4626
+ method: "POST",
4627
+ body: JSON.stringify({
4628
+ filename: official.filename,
4629
+ zip_base64: buf.toString("base64")
4630
+ })
4631
+ },
4632
+ verbose
4447
4633
  );
4448
- console.log("\n\u5B89\u88C5 / \u5347\u7EA7\uFF1Asiluzan-website plugins install --site <guid> --slug <slug>\n");
4449
4634
  }
4450
4635
  async function runPluginsInstall(opts) {
4451
4636
  const slug = opts.slug?.trim() ?? "";
@@ -4460,6 +4645,10 @@ async function runPluginsInstall(opts) {
4460
4645
  try {
4461
4646
  result = await withHelperBootstrap(opts, target, async () => {
4462
4647
  if (slug) {
4648
+ const official = await findOfficialPlugin(target.config, slug, opts.verbose);
4649
+ if (official) {
4650
+ return installOfficialZip(target, official, opts.verbose);
4651
+ }
4463
4652
  return apiFetch2(
4464
4653
  `${base}/wp-json/siluzan-helper/v1/plugins/install`,
4465
4654
  target.config,
@@ -4507,16 +4696,20 @@ async function runPluginsUpdate(opts) {
4507
4696
  }
4508
4697
  let result;
4509
4698
  try {
4510
- result = await withHelperBootstrap(
4511
- opts,
4512
- target,
4513
- () => apiFetch2(
4699
+ result = await withHelperBootstrap(opts, target, async () => {
4700
+ if (!zipPath) {
4701
+ const official = await findOfficialPlugin(target.config, plugin, opts.verbose);
4702
+ if (official) {
4703
+ return installOfficialZip(target, official, opts.verbose);
4704
+ }
4705
+ }
4706
+ return apiFetch2(
4514
4707
  `${base}/wp-json/siluzan-helper/v1/plugins/update`,
4515
4708
  target.config,
4516
4709
  { method: "POST", body: JSON.stringify(body) },
4517
4710
  opts.verbose
4518
- )
4519
- );
4711
+ );
4712
+ });
4520
4713
  } catch (e) {
4521
4714
  const msg = e.message || "";
4522
4715
  if (/HTTP 409/.test(msg) && /no_package/i.test(msg)) {
@@ -4682,10 +4875,10 @@ function register7(program2) {
4682
4875
  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) => {
4683
4876
  await runPluginsList(opts);
4684
4877
  });
4685
- plugins.command("catalog").description("\u5217\u51FA\u4E1D\u8DEF\u8D5E\u63D2\u4EF6\u76EE\u5F55\uFF08\u53EF\u5B89\u88C5 / \u53EF\u5347\u7EA7\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) => {
4878
+ plugins.command("catalog").description("\u5217\u51FA\u5B98\u65B9\u63D2\u4EF6\u76EE\u5F55\uFF08sammk8s / wp-plugins\uFF0C\u4E0D\u4F9D\u8D56\u7AD9\u70B9\uFF09").option("-s, --site <guidOrName>", "\u53EF\u9009\uFF0C\u5BF9\u7167\u8BE5\u7AD9\u662F\u5426\u5DF2\u88C5").option("--url <url>", "\u76F4\u63A5\u6307\u5B9A\u7AD9\u70B9\u5730\u5740").option("--prefix <prefix>", "\u53EA\u5217\u67D0\u4E2A\u76EE\u5F55\uFF0C\u5982 zasp-smart-recommender/").option("--json", "\u8F93\u51FA JSON", false).option("--verbose", "\u8F93\u51FA\u8BF7\u6C42\u8BE6\u60C5", false).action(async (opts) => {
4686
4879
  await runPluginsCatalog(opts);
4687
4880
  });
4688
- plugins.command("install").description("\u5B89\u88C5\u5E76\u542F\u7528\u63D2\u4EF6\uFF1A\u76EE\u5F55\u7528 --slug\uFF0C\u5916\u90E8\u7528 --zip").option("-s, --site <guidOrName>", "\u7AD9\u70B9 guid \u6216\u540D\u79F0").option("--url <url>", "\u76F4\u63A5\u6307\u5B9A\u7AD9\u70B9\u5730\u5740").option("--slug <slug>", "\u4E1D\u8DEF\u8D5E\u76EE\u5F55\u4E2D\u7684 slug \u6216 plugin \u8DEF\u5F84").option("--zip <file>", "\u5916\u90E8\u63D2\u4EF6 zip\uFF08\u6839\u76EE\u5F55\u5FC5\u987B\u662F\u63D2\u4EF6\u76EE\u5F55\uFF09").option("--verbose", "\u8F93\u51FA\u8BF7\u6C42\u8BE6\u60C5", false).action(async (opts) => {
4881
+ plugins.command("install").description("\u5B89\u88C5\u5E76\u542F\u7528\u63D2\u4EF6\uFF1A\u5B98\u65B9\u76EE\u5F55\u7528 --slug\uFF0C\u5916\u90E8\u7528 --zip").option("-s, --site <guidOrName>", "\u7AD9\u70B9 guid \u6216\u540D\u79F0").option("--url <url>", "\u76F4\u63A5\u6307\u5B9A\u7AD9\u70B9\u5730\u5740").option("--slug <slug>", "\u4E1D\u8DEF\u8D5E\u76EE\u5F55\u4E2D\u7684 slug \u6216 plugin \u8DEF\u5F84").option("--zip <file>", "\u5916\u90E8\u63D2\u4EF6 zip\uFF08\u6839\u76EE\u5F55\u5FC5\u987B\u662F\u63D2\u4EF6\u76EE\u5F55\uFF09").option("--verbose", "\u8F93\u51FA\u8BF7\u6C42\u8BE6\u60C5", false).action(async (opts) => {
4689
4882
  await runPluginsInstall(opts);
4690
4883
  });
4691
4884
  plugins.command("update").description("\u5347\u7EA7\u5DF2\u88C5\u63D2\u4EF6\uFF1A\u6709\u76EE\u5F55/\u5B98\u65B9\u5305\u5219\u4E0D\u7528 zip\uFF0C\u5916\u90E8\u63D2\u4EF6\u5E26 --zip").option("-s, --site <guidOrName>", "\u7AD9\u70B9 guid \u6216\u540D\u79F0").option("--url <url>", "\u76F4\u63A5\u6307\u5B9A\u7AD9\u70B9\u5730\u5740").requiredOption("--plugin <plugin>", "\u5DF2\u88C5\u63D2\u4EF6\u8DEF\u5F84\uFF0C\u5982 theme-manage/theme-manage").option("--zip <file>", "\u5916\u90E8\u63D2\u4EF6\u5347\u7EA7\u5305").option("--verbose", "\u8F93\u51FA\u8BF7\u6C42\u8BE6\u60C5", false).action(async (opts) => {
@@ -62,7 +62,7 @@ Windows 注意:部分 Agent 通过 PowerShell / cmd 代执行时可能失败
62
62
  | 分类标签 | `references/terms.md` | `terms list` 再 add/update |
63
63
  | 按关键词找内容 | `references/search.md` | `search --q` |
64
64
  | 这个站装了哪些插件 | `references/plugins.md` | `plugins list`(全部已装,不只是目录) |
65
- | 安装 / 启用目录插件 | `references/plugins.md` | `catalog` slug 确认 → `install --slug` |
65
+ | 安装 / 启用目录插件 | `references/plugins.md` | `plugins catalog`(官方存储目录,含 AI 推荐)→ 确认 → `install --slug` |
66
66
  | 安装外部插件 | `references/plugins.md` | 确认 → `install --zip` |
67
67
  | 升级已装插件 | `references/plugins.md` | `list` 拿 plugin → 确认 → `update --plugin`;外部再加 `--zip` |
68
68
  | 卸载插件 | `references/plugins.md` | `list` 拿 plugin → 确认 → `uninstall --plugin` |
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "slug": "siluzan-website",
3
- "version": "1.0.1-beta.3",
4
- "publishedAt": 1789638021415,
3
+ "version": "1.0.1-beta.4",
4
+ "publishedAt": 1789699291560,
5
5
  "homepage": "https://www.siluzan.com",
6
6
  "source": "https://dev.azure.com/jack4it/Sammamish/_git/siluzan-skill",
7
7
  "requiredBinaries": [
@@ -3,9 +3,10 @@
3
3
  鉴权一律 **API Key(x-api-key)**。
4
4
 
5
5
  - **已装列表**走 WordPress 核心 `GET /wp-json/wp/v2/plugins`,能看到站点上的全部插件。
6
- - **目录安装 / zip 升级**走 `siluzan-helper-plugin` REST。目录包来自丝路赞目录,不接受任意 URL
6
+ - **可安装目录**走 CSO `GET /cso/v1/wp-plugin/list`(CI:`cso-api-ci.siluzan.com`,连不上回落 `cso-ci.siluzan.com`)。这是 sammk8s 存储账号 `wp-plugins` 容器,每个插件取最高版本 zip
7
+ - **目录安装 / 无 zip 升级**:CLI 从官方目录下载 zip,再交给站点 helper 的 `install-zip`。不把任意 URL 交给 WordPress。
7
8
  - **外部插件**首次安装和升级都带本地 `--zip`。zip 根目录必须是插件目录本身(`插件名/xxx.php`),不能再套一层版本号目录。
8
- - helper 太旧、没有 REST 时,命令会先打 oauth2 `POST /wp-json/wposso/v1/helper/upgrade`,用镜像里的 `siluzan-helper-plugin.1.0.*.zip` 升 helper,再继续原来的操作。helper **不上架**。
9
+ - helper 太旧、没有 REST 时,命令会先打 oauth2 `POST /wp-json/wposso/v1/helper/upgrade`,用镜像里的 `siluzan-helper-plugin.1.0.*.zip` 升 helper,再继续原来的操作。
9
10
 
10
11
  ## 已装列表
11
12
 
@@ -21,10 +22,12 @@ siluzan-website plugins list --site <guid> --json
21
22
  ## 可安装目录
22
23
 
23
24
  ```bash
25
+ siluzan-website plugins catalog
26
+ siluzan-website plugins catalog --prefix zasp-smart-recommender/
24
27
  siluzan-website plugins catalog --site <guid>
25
28
  ```
26
29
 
27
- 记下 `slug`。目录安装 / 目录升级都用这个值。
30
+ 不传 `--site` 也能列。记下 `slug`。目录里有 `zasp-smart-recommender`(AI 推荐)等官方包。
28
31
 
29
32
  ## 安装
30
33
 
@@ -76,7 +79,7 @@ siluzan-website plugins update-helper --site <guid>
76
79
 
77
80
  1. `sites list` 确定 guid
78
81
  2. 用户要「装了哪些插件」→ `plugins list`
79
- 3. 用户要装目录插件 → `catalog` 对上 slug → 确认 → `install --slug`
82
+ 3. 用户要装目录插件 → `plugins catalog`(不用站点)对上 slug → 确认 → `install --slug`
80
83
  4. 用户要升级已装插件 → `list` 拿到 `plugin` → 确认 → `update --plugin`;外部插件再加 `--zip`
81
84
  5. 用户要卸载 → `list` 拿到 `plugin` → 确认不是 helper / oauth2 → `uninstall --plugin`
82
85
  6. catalog / install / update 报路由 404 → 命令会自动 `update-helper` 再重试;oauth2 本身 404 / 401 → 告诉用户要先 SiteMan 镜像升级
@@ -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.3'
12
+ $PKG_VERSION = '1.0.1-beta.4'
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.3"
12
+ readonly PKG_VERSION="1.0.1-beta.4"
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.3",
3
+ "version": "1.0.1-beta.4",
4
4
  "description": "Siluzan WordPress 站点管理 Skill CLI — 列出站点、新增/编辑页面、安装/升级插件。",
5
5
  "keywords": [
6
6
  "ai-skill",