@safeskill/cli 0.2.1 → 0.2.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.2
4
+
5
+ - Added cross-region hub resolve fallback when the selected SafeSkill hub returns a server error.
6
+
3
7
  ## 0.2.1
4
8
 
5
9
  - Added Flocks agent support with `.flocks/plugins/skills` install paths.
package/README.md CHANGED
@@ -76,9 +76,18 @@ npx @safeskill/cli add safeskill://official/acme/code-review@1.2.0
76
76
  | `-y, --yes` | Skip all confirmation prompts |
77
77
  | `--all` | Install all skills to all agents without prompts |
78
78
 
79
+ ### Global Options
80
+
81
+ | Option | Description |
82
+ | ---------------------- | ------------------------------------------------------------------------------------------------------------ |
83
+ | `--region <cn|global>` | Use a specific SafeSkill service region for this run. Overrides `SAFESKILL_REGION`; otherwise auto-detected. |
84
+
79
85
  ### Examples
80
86
 
81
87
  ```bash
88
+ # Force the China service region for this run
89
+ npx @safeskill/cli --region cn add safeskill://official/acme/code-review
90
+
82
91
  # List skills in a repository
83
92
  npx @safeskill/cli add example-org/agent-skills --list
84
93
 
@@ -441,6 +450,7 @@ Ensure you have write access to the target directory.
441
450
  | Variable | Description |
442
451
  | ------------------------- | -------------------------------------------------------------------------- |
443
452
  | `INSTALL_INTERNAL_SKILLS` | Set to `1` or `true` to show and install skills marked as `internal: true` |
453
+ | `SAFESKILL_REGION` | Optional fallback service region (`cn` or `global`) when `--region` is not specified |
444
454
  | `DISABLE_TELEMETRY` | Set to disable anonymous usage telemetry |
445
455
  | `DO_NOT_TRACK` | Alternative way to disable telemetry |
446
456
 
package/dist/cli.mjs CHANGED
@@ -408,13 +408,18 @@ const STATUS_TIMEOUT_MS = 2500;
408
408
  const DETECT_TIMEOUT_MS = 1500;
409
409
  let resolvedRegionPromise = null;
410
410
  let regionAvailabilityCache = /* @__PURE__ */ new Map();
411
- function normalizeRegion(region) {
411
+ let safeSkillRegionOverride = null;
412
+ function normalizeSafeSkillRegion(region) {
412
413
  const value = region?.trim().toLowerCase();
413
414
  if (!value) return null;
414
415
  if (value === "cn" || value === "china") return "cn";
415
416
  if (value === "global" || value === "io" || value === "intl" || value === "international") return "global";
416
417
  return null;
417
418
  }
419
+ function setSafeSkillRegionOverride(region) {
420
+ safeSkillRegionOverride = region;
421
+ resolvedRegionPromise = null;
422
+ }
418
423
  function withTimeout(timeoutMs) {
419
424
  return AbortSignal.timeout(timeoutMs);
420
425
  }
@@ -496,7 +501,8 @@ async function ensureRegionAvailable(region, hubBaseUrl) {
496
501
  async function resolveSafeSkillRegion() {
497
502
  if (resolvedRegionPromise) return resolvedRegionPromise;
498
503
  resolvedRegionPromise = (async () => {
499
- const configuredRegion = normalizeRegion(process.env.SAFESKILL_REGION);
504
+ if (safeSkillRegionOverride) return safeSkillRegionOverride;
505
+ const configuredRegion = normalizeSafeSkillRegion(process.env.SAFESKILL_REGION);
500
506
  if (configuredRegion) return configuredRegion;
501
507
  return detectRegionByPublicIP();
502
508
  })();
@@ -530,6 +536,19 @@ function normalizeApiBase(baseUrl) {
530
536
  function buildApiUrl(baseUrl, path) {
531
537
  return `${normalizeApiBase(baseUrl)}${path.startsWith("/") ? path : `/${path}`}`;
532
538
  }
539
+ function getAlternateHubBaseUrl(baseUrl) {
540
+ if (process.env.SAFESKILL_HUB_URL) return null;
541
+ const parsed = new URL(baseUrl);
542
+ if (parsed.hostname === "hubapi.safeskill.io") {
543
+ parsed.hostname = "hubapi.safeskill.cn";
544
+ return parsed.toString().replace(/\/$/, "");
545
+ }
546
+ if (parsed.hostname === "hubapi.safeskill.cn") {
547
+ parsed.hostname = "hubapi.safeskill.io";
548
+ return parsed.toString().replace(/\/$/, "");
549
+ }
550
+ return null;
551
+ }
533
552
  async function readEnvelope(response) {
534
553
  const payload = await response.json();
535
554
  if (payload && typeof payload === "object" && "data" in payload) return payload.data;
@@ -724,26 +743,41 @@ async function searchSkillsCatalog(query, limit = 10) {
724
743
  }
725
744
  }
726
745
  async function resolveHubSkill(source, agent) {
727
- const resolveUrl = buildApiUrl(await getSafeSkillHubBaseUrl(), "/resolve");
728
- const resolveWithAgent = agent ? await fetch(resolveUrl, {
729
- method: "POST",
730
- headers: getHeaders({ "content-type": "application/json" }),
731
- body: JSON.stringify({
732
- source,
733
- agent
734
- })
735
- }) : null;
736
- const response = resolveWithAgent && resolveWithAgent.ok ? resolveWithAgent : await fetch(resolveUrl, {
737
- method: "POST",
738
- headers: getHeaders({ "content-type": "application/json" }),
739
- body: JSON.stringify({ source })
740
- });
746
+ const baseUrl = await getSafeSkillHubBaseUrl();
747
+ const resolveFromBaseUrl = async (hubBaseUrl) => {
748
+ const resolveUrl = buildApiUrl(hubBaseUrl, "/resolve");
749
+ const resolveWithAgent = agent ? await fetch(resolveUrl, {
750
+ method: "POST",
751
+ headers: getHeaders({ "content-type": "application/json" }),
752
+ body: JSON.stringify({
753
+ source,
754
+ agent
755
+ })
756
+ }) : null;
757
+ return resolveWithAgent && resolveWithAgent.ok ? resolveWithAgent : fetch(resolveUrl, {
758
+ method: "POST",
759
+ headers: getHeaders({ "content-type": "application/json" }),
760
+ body: JSON.stringify({ source })
761
+ });
762
+ };
763
+ let resolvedBaseUrl = baseUrl;
764
+ let response = await resolveFromBaseUrl(baseUrl);
765
+ const alternateBaseUrl = response.status >= 500 ? getAlternateHubBaseUrl(baseUrl) : null;
766
+ if (alternateBaseUrl) {
767
+ const alternateResponse = await resolveFromBaseUrl(alternateBaseUrl);
768
+ if (alternateResponse.ok || !response.ok) {
769
+ response = alternateResponse;
770
+ resolvedBaseUrl = alternateBaseUrl;
771
+ }
772
+ }
741
773
  if (!response.ok) throw new Error(`hub resolve failed: ${response.status}`);
742
- return readEnvelope(response);
774
+ const resolved = await readEnvelope(response);
775
+ resolved.hubBaseUrl = resolvedBaseUrl;
776
+ return resolved;
743
777
  }
744
778
  async function downloadHubSource(source, agent) {
745
- const baseUrl = await getSafeSkillHubBaseUrl();
746
779
  const resolved = await resolveHubSkill(source, agent);
780
+ const baseUrl = resolved.hubBaseUrl || await getSafeSkillHubBaseUrl();
747
781
  const downloadUrl = resolved.download_uri || (resolved.artifact_digest ? buildApiUrl(baseUrl, `/artifacts/${encodeURIComponent(resolved.artifact_digest)}/download`) : "");
748
782
  if (!downloadUrl) throw new Error("hub resolve did not return a downloadable artifact");
749
783
  let response;
@@ -2278,7 +2312,7 @@ function createEmptyLocalLock() {
2278
2312
  skills: {}
2279
2313
  };
2280
2314
  }
2281
- var version$1 = "0.2.1";
2315
+ var version$1 = "0.2.2";
2282
2316
  const isCancelled$1 = (value) => typeof value === "symbol";
2283
2317
  async function isSourcePrivate(source) {
2284
2318
  const ownerRepo = parseOwnerRepo(source);
@@ -4369,6 +4403,8 @@ ${BOLD}List Options:${RESET}
4369
4403
  ${BOLD}Options:${RESET}
4370
4404
  --help, -h Show this help message
4371
4405
  --version, -v Show version number
4406
+ --region <cn|global>
4407
+ Use a specific SafeSkill service region for this run
4372
4408
 
4373
4409
  ${BOLD}Examples:${RESET}
4374
4410
  ${DIM}$${RESET} safeskill add example-org/agent-skills
@@ -4655,7 +4691,7 @@ async function runCheck(args = []) {
4655
4691
  });
4656
4692
  console.log();
4657
4693
  }
4658
- async function runUpdate() {
4694
+ async function runUpdate(region) {
4659
4695
  console.log(`${TEXT}Checking for skill updates...${RESET}`);
4660
4696
  console.log();
4661
4697
  const lock = readSkillLock();
@@ -4751,13 +4787,22 @@ async function runUpdate() {
4751
4787
  console.log(` ${DIM}✗ Failed to update ${update.name}: CLI entrypoint not found at ${cliEntry}${RESET}`);
4752
4788
  continue;
4753
4789
  }
4754
- if (spawnSync(process.execPath, [
4790
+ const addArgs = region ? [
4791
+ cliEntry,
4792
+ "--region",
4793
+ region,
4794
+ "add",
4795
+ installUrl,
4796
+ "-g",
4797
+ "-y"
4798
+ ] : [
4755
4799
  cliEntry,
4756
4800
  "add",
4757
4801
  installUrl,
4758
4802
  "-g",
4759
4803
  "-y"
4760
- ], {
4804
+ ];
4805
+ if (spawnSync(process.execPath, addArgs, {
4761
4806
  stdio: [
4762
4807
  "inherit",
4763
4808
  "pipe",
@@ -4784,8 +4829,53 @@ async function runUpdate() {
4784
4829
  });
4785
4830
  console.log();
4786
4831
  }
4832
+ function parseGlobalOptions(args) {
4833
+ const parsedArgs = [];
4834
+ let region;
4835
+ for (let i = 0; i < args.length; i++) {
4836
+ const arg = args[i];
4837
+ if (arg === void 0) continue;
4838
+ if (arg === "--region") {
4839
+ const value = args[i + 1];
4840
+ if (typeof value !== "string" || value.startsWith("-")) return {
4841
+ args: parsedArgs,
4842
+ error: "Missing value for --region. Expected one of: cn, global"
4843
+ };
4844
+ const normalized = normalizeSafeSkillRegion(value);
4845
+ if (!normalized) return {
4846
+ args: parsedArgs,
4847
+ error: `Invalid region: ${value}\nExpected one of: cn, global`
4848
+ };
4849
+ region = normalized;
4850
+ i++;
4851
+ continue;
4852
+ }
4853
+ if (arg.startsWith("--region=")) {
4854
+ const value = arg.slice(9);
4855
+ const normalized = normalizeSafeSkillRegion(value);
4856
+ if (!normalized) return {
4857
+ args: parsedArgs,
4858
+ error: `Invalid region: ${value}\nExpected one of: cn, global`
4859
+ };
4860
+ region = normalized;
4861
+ continue;
4862
+ }
4863
+ parsedArgs.push(arg);
4864
+ }
4865
+ return {
4866
+ args: parsedArgs,
4867
+ region
4868
+ };
4869
+ }
4787
4870
  async function main() {
4788
- const args = process.argv.slice(2);
4871
+ const parsedGlobalOptions = parseGlobalOptions(process.argv.slice(2));
4872
+ if (parsedGlobalOptions.error) {
4873
+ console.log(parsedGlobalOptions.error);
4874
+ process.exitCode = 1;
4875
+ return;
4876
+ }
4877
+ if (parsedGlobalOptions.region) setSafeSkillRegionOverride(parsedGlobalOptions.region);
4878
+ const args = parsedGlobalOptions.args;
4789
4879
  if (args.length === 0) {
4790
4880
  showBanner();
4791
4881
  return;
@@ -4844,7 +4934,7 @@ async function main() {
4844
4934
  break;
4845
4935
  case "update":
4846
4936
  case "upgrade":
4847
- runUpdate();
4937
+ runUpdate(parsedGlobalOptions.region);
4848
4938
  break;
4849
4939
  case "--help":
4850
4940
  case "-h":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@safeskill/cli",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "SafeSkill CLI, for safe your skills",
5
5
  "type": "module",
6
6
  "bin": {