@safeskill/cli 0.2.1 → 0.2.3
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 +4 -0
- package/README.md +10 -0
- package/dist/cli.mjs +134 -26
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
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
|
-
|
|
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
|
-
|
|
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
|
|
728
|
-
const
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
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
|
-
|
|
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;
|
|
@@ -901,6 +935,8 @@ async function parseSkillMd(skillMdPath, options) {
|
|
|
901
935
|
...metaData
|
|
902
936
|
};
|
|
903
937
|
} catch (metaError) {}
|
|
938
|
+
if (!skillData.name && options?.fallbackName) skillData.name = options.fallbackName;
|
|
939
|
+
if (!skillData.description && options?.fallbackDescription) skillData.description = options.fallbackDescription;
|
|
904
940
|
if (!skillData.name || !skillData.description) return null;
|
|
905
941
|
if (typeof skillData.name !== "string" || typeof skillData.description !== "string") return null;
|
|
906
942
|
if (skillData.metadata?.internal === true && !shouldInstallInternalSkills() && !options?.includeInternal) return null;
|
|
@@ -910,7 +946,8 @@ async function parseSkillMd(skillMdPath, options) {
|
|
|
910
946
|
path: dirname(skillMdPath),
|
|
911
947
|
rawContent: content,
|
|
912
948
|
metadata: skillData.metadata,
|
|
913
|
-
slug: skillData.slug
|
|
949
|
+
slug: skillData.slug || options?.fallbackSlug,
|
|
950
|
+
installName: options?.fallbackInstallName
|
|
914
951
|
};
|
|
915
952
|
} catch {
|
|
916
953
|
return null;
|
|
@@ -2278,7 +2315,7 @@ function createEmptyLocalLock() {
|
|
|
2278
2315
|
skills: {}
|
|
2279
2316
|
};
|
|
2280
2317
|
}
|
|
2281
|
-
var version$1 = "0.2.
|
|
2318
|
+
var version$1 = "0.2.3";
|
|
2282
2319
|
const isCancelled$1 = (value) => typeof value === "symbol";
|
|
2283
2320
|
async function isSourcePrivate(source) {
|
|
2284
2321
|
const ownerRepo = parseOwnerRepo(source);
|
|
@@ -2793,10 +2830,24 @@ async function runAdd(args, options = {}) {
|
|
|
2793
2830
|
}
|
|
2794
2831
|
const includeInternal = !!(options.skill && options.skill.length > 0);
|
|
2795
2832
|
spinner.start("Discovering skills...");
|
|
2796
|
-
|
|
2833
|
+
let skills = await discoverSkills(skillsDir, parsed.subpath, {
|
|
2797
2834
|
includeInternal,
|
|
2798
2835
|
fullDepth: options.fullDepth
|
|
2799
2836
|
});
|
|
2837
|
+
let usedHubMetadataFallback = false;
|
|
2838
|
+
if (skills.length === 0 && parsed.type === "hub" && !parsed.subpath && hubResolved?.skill) {
|
|
2839
|
+
const fallbackSkill = await parseSkillMd(join(skillsDir, "SKILL.md"), {
|
|
2840
|
+
includeInternal,
|
|
2841
|
+
fallbackName: hubResolved.skill.display_name || hubResolved.skill.name,
|
|
2842
|
+
fallbackDescription: hubResolved.skill.summary || hubResolved.skill.name,
|
|
2843
|
+
fallbackSlug: hubResolved.skill.name,
|
|
2844
|
+
fallbackInstallName: hubResolved.skill.name
|
|
2845
|
+
});
|
|
2846
|
+
if (fallbackSkill) {
|
|
2847
|
+
skills = [fallbackSkill];
|
|
2848
|
+
usedHubMetadataFallback = true;
|
|
2849
|
+
}
|
|
2850
|
+
}
|
|
2800
2851
|
if (skills.length === 0) {
|
|
2801
2852
|
spinner.stop(import_picocolors.default.red("No skills found"));
|
|
2802
2853
|
Se(import_picocolors.default.red("No valid skills found. Skills require a SKILL.md with name and description."));
|
|
@@ -2804,6 +2855,7 @@ async function runAdd(args, options = {}) {
|
|
|
2804
2855
|
process.exit(1);
|
|
2805
2856
|
}
|
|
2806
2857
|
spinner.stop(`Found ${import_picocolors.default.green(skills.length)} skill${skills.length > 1 ? "s" : ""}`);
|
|
2858
|
+
if (usedHubMetadataFallback) M.warn("Warning: this hub artifact is missing SKILL.md frontmatter. Some agents may not recognize this skill until name and description are added to SKILL.md.");
|
|
2807
2859
|
if (options.list) {
|
|
2808
2860
|
console.log();
|
|
2809
2861
|
M.step(import_picocolors.default.bold("Available Skills"));
|
|
@@ -4369,6 +4421,8 @@ ${BOLD}List Options:${RESET}
|
|
|
4369
4421
|
${BOLD}Options:${RESET}
|
|
4370
4422
|
--help, -h Show this help message
|
|
4371
4423
|
--version, -v Show version number
|
|
4424
|
+
--region <cn|global>
|
|
4425
|
+
Use a specific SafeSkill service region for this run
|
|
4372
4426
|
|
|
4373
4427
|
${BOLD}Examples:${RESET}
|
|
4374
4428
|
${DIM}$${RESET} safeskill add example-org/agent-skills
|
|
@@ -4655,7 +4709,7 @@ async function runCheck(args = []) {
|
|
|
4655
4709
|
});
|
|
4656
4710
|
console.log();
|
|
4657
4711
|
}
|
|
4658
|
-
async function runUpdate() {
|
|
4712
|
+
async function runUpdate(region) {
|
|
4659
4713
|
console.log(`${TEXT}Checking for skill updates...${RESET}`);
|
|
4660
4714
|
console.log();
|
|
4661
4715
|
const lock = readSkillLock();
|
|
@@ -4751,13 +4805,22 @@ async function runUpdate() {
|
|
|
4751
4805
|
console.log(` ${DIM}✗ Failed to update ${update.name}: CLI entrypoint not found at ${cliEntry}${RESET}`);
|
|
4752
4806
|
continue;
|
|
4753
4807
|
}
|
|
4754
|
-
|
|
4808
|
+
const addArgs = region ? [
|
|
4755
4809
|
cliEntry,
|
|
4810
|
+
"--region",
|
|
4811
|
+
region,
|
|
4756
4812
|
"add",
|
|
4757
4813
|
installUrl,
|
|
4758
4814
|
"-g",
|
|
4759
4815
|
"-y"
|
|
4760
|
-
]
|
|
4816
|
+
] : [
|
|
4817
|
+
cliEntry,
|
|
4818
|
+
"add",
|
|
4819
|
+
installUrl,
|
|
4820
|
+
"-g",
|
|
4821
|
+
"-y"
|
|
4822
|
+
];
|
|
4823
|
+
if (spawnSync(process.execPath, addArgs, {
|
|
4761
4824
|
stdio: [
|
|
4762
4825
|
"inherit",
|
|
4763
4826
|
"pipe",
|
|
@@ -4784,8 +4847,53 @@ async function runUpdate() {
|
|
|
4784
4847
|
});
|
|
4785
4848
|
console.log();
|
|
4786
4849
|
}
|
|
4850
|
+
function parseGlobalOptions(args) {
|
|
4851
|
+
const parsedArgs = [];
|
|
4852
|
+
let region;
|
|
4853
|
+
for (let i = 0; i < args.length; i++) {
|
|
4854
|
+
const arg = args[i];
|
|
4855
|
+
if (arg === void 0) continue;
|
|
4856
|
+
if (arg === "--region") {
|
|
4857
|
+
const value = args[i + 1];
|
|
4858
|
+
if (typeof value !== "string" || value.startsWith("-")) return {
|
|
4859
|
+
args: parsedArgs,
|
|
4860
|
+
error: "Missing value for --region. Expected one of: cn, global"
|
|
4861
|
+
};
|
|
4862
|
+
const normalized = normalizeSafeSkillRegion(value);
|
|
4863
|
+
if (!normalized) return {
|
|
4864
|
+
args: parsedArgs,
|
|
4865
|
+
error: `Invalid region: ${value}\nExpected one of: cn, global`
|
|
4866
|
+
};
|
|
4867
|
+
region = normalized;
|
|
4868
|
+
i++;
|
|
4869
|
+
continue;
|
|
4870
|
+
}
|
|
4871
|
+
if (arg.startsWith("--region=")) {
|
|
4872
|
+
const value = arg.slice(9);
|
|
4873
|
+
const normalized = normalizeSafeSkillRegion(value);
|
|
4874
|
+
if (!normalized) return {
|
|
4875
|
+
args: parsedArgs,
|
|
4876
|
+
error: `Invalid region: ${value}\nExpected one of: cn, global`
|
|
4877
|
+
};
|
|
4878
|
+
region = normalized;
|
|
4879
|
+
continue;
|
|
4880
|
+
}
|
|
4881
|
+
parsedArgs.push(arg);
|
|
4882
|
+
}
|
|
4883
|
+
return {
|
|
4884
|
+
args: parsedArgs,
|
|
4885
|
+
region
|
|
4886
|
+
};
|
|
4887
|
+
}
|
|
4787
4888
|
async function main() {
|
|
4788
|
-
const
|
|
4889
|
+
const parsedGlobalOptions = parseGlobalOptions(process.argv.slice(2));
|
|
4890
|
+
if (parsedGlobalOptions.error) {
|
|
4891
|
+
console.log(parsedGlobalOptions.error);
|
|
4892
|
+
process.exitCode = 1;
|
|
4893
|
+
return;
|
|
4894
|
+
}
|
|
4895
|
+
if (parsedGlobalOptions.region) setSafeSkillRegionOverride(parsedGlobalOptions.region);
|
|
4896
|
+
const args = parsedGlobalOptions.args;
|
|
4789
4897
|
if (args.length === 0) {
|
|
4790
4898
|
showBanner();
|
|
4791
4899
|
return;
|
|
@@ -4844,7 +4952,7 @@ async function main() {
|
|
|
4844
4952
|
break;
|
|
4845
4953
|
case "update":
|
|
4846
4954
|
case "upgrade":
|
|
4847
|
-
runUpdate();
|
|
4955
|
+
runUpdate(parsedGlobalOptions.region);
|
|
4848
4956
|
break;
|
|
4849
4957
|
case "--help":
|
|
4850
4958
|
case "-h":
|