vskill 1.0.21 → 1.1.1

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
@@ -305,6 +305,8 @@ Full changelog → [github.com/anton-abyzov/vskill/releases](https://github.com/
305
305
 
306
306
  Issues and PRs welcome at [github.com/anton-abyzov/vskill](https://github.com/anton-abyzov/vskill). To submit a skill to the registry, see [verified-skill.com/docs/submitting](https://verified-skill.com/docs/submitting).
307
307
 
308
+ Inside this repo dependency install scripts are disabled (`.npmrc`): run `npm ci`, then `npm run setup` (rebuilds esbuild), and `npm run security:scan` before pushing — see [SECURITY.md](SECURITY.md).
309
+
308
310
  ## License
309
311
 
310
312
  MIT
package/agents.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 1,
3
- "generatedAt": "2026-06-17T16:49:56.735Z",
3
+ "generatedAt": "2026-09-14T07:11:29.092Z",
4
4
  "agentPrefixes": [
5
5
  ".adal",
6
6
  ".agent",
@@ -1,4 +1,11 @@
1
1
  import type { AgentDefinition } from "../agents/agents-registry.js";
2
+ /**
3
+ * Subdirectories fetched alongside SKILL.md when installing a skill that lives
4
+ * in a repo subdirectory. Must stay a subset of the prefixes the installer
5
+ * accepts in installer/bundle-files.ts, or the write is rejected.
6
+ */
7
+ export declare const BUNDLE_SUBDIRS: readonly ["agents", "references", "scripts", "assets", "tests"];
8
+ export declare function isBundleTextFile(name: string): boolean;
2
9
  interface MarketplaceDetectionResult {
3
10
  isMarketplace: boolean;
4
11
  manifestContent?: string;
@@ -36,6 +36,30 @@ import { extractFrontmatterVersion } from "../utils/version.js";
36
36
  import { claudePluginInstall, claudePluginUninstall, claudePluginMarketplaceAdd, claudePluginMarketplaceList, } from "../utils/claude-plugin.js";
37
37
  import { buildPerAgentReport, resolvePluginId, } from "../lib/skill-lifecycle.js";
38
38
  // ---------------------------------------------------------------------------
39
+ /**
40
+ * Subdirectories fetched alongside SKILL.md when installing a skill that lives
41
+ * in a repo subdirectory. Must stay a subset of the prefixes the installer
42
+ * accepts in installer/bundle-files.ts, or the write is rejected.
43
+ */
44
+ export const BUNDLE_SUBDIRS = ["agents", "references", "scripts", "assets", "tests"];
45
+ /**
46
+ * Extensions carried in a skill bundle. Bundle contents are transported as
47
+ * UTF-8 strings, so this is a text-only allowlist — binaries would be corrupted.
48
+ */
49
+ const BUNDLE_TEXT_EXTENSIONS = [
50
+ ".md", ".txt", ".json", ".jsonc", ".yaml", ".yml", ".toml", ".csv",
51
+ ".py", ".sh", ".bash", ".zsh", ".js", ".mjs", ".cjs", ".ts", ".sql",
52
+ ".excalidraw", ".svg", ".css", ".html",
53
+ ];
54
+ /** Per-file and per-skill ceilings, so one fat repo cannot stall an install. */
55
+ const MAX_BUNDLE_FILE_BYTES = 512 * 1024;
56
+ const MAX_BUNDLE_FILES = 100;
57
+ export function isBundleTextFile(name) {
58
+ const lower = name.toLowerCase();
59
+ if (lower.startsWith("."))
60
+ return false; // .DS_Store, dotfiles
61
+ return BUNDLE_TEXT_EXTENSIONS.some((ext) => lower.endsWith(ext));
62
+ }
39
63
  /** Validate that a download_url from GitHub Contents API points to a trusted GitHub domain. */
40
64
  function isGitHubDownloadUrl(url) {
41
65
  try {
@@ -2400,41 +2424,80 @@ async function installSingleSkillLegacy(owner, repo, skill, opts, skillSubpathOv
2400
2424
  }
2401
2425
  const branch = refOverride || (await getDefaultBranch(owner, repo));
2402
2426
  const sourceCommitSha = await getBranchHeadSha(owner, repo, branch);
2403
- const skillSubpath = skillSubpathOverride || (skill ? `skills/${skill}/SKILL.md` : "SKILL.md");
2404
- const url = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${skillSubpath}`;
2405
- // Fetch SKILL.md
2406
- const content = await fetchSkillContent(url);
2407
- // Fetch agents/*.md if skill is in a subdirectory (skills/{name}/)
2427
+ // Candidate subpaths: explicit override wins; otherwise try the conventional
2428
+ // skills/{name}/ layout first, then root-level {name}/ (e.g. higgsfield-ai/skills).
2429
+ const subpathCandidates = skillSubpathOverride
2430
+ ? [skillSubpathOverride]
2431
+ : skill
2432
+ ? [`skills/${skill}/SKILL.md`, `${skill}/SKILL.md`]
2433
+ : ["SKILL.md"];
2434
+ // Fetch SKILL.md — first candidate that exists wins
2435
+ let skillSubpath = subpathCandidates[0];
2436
+ let content;
2437
+ for (let i = 0; i < subpathCandidates.length; i++) {
2438
+ const candidate = subpathCandidates[i];
2439
+ const candidateUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${candidate}`;
2440
+ const isLast = i === subpathCandidates.length - 1;
2441
+ if (isLast) {
2442
+ content = await fetchSkillContent(candidateUrl);
2443
+ skillSubpath = candidate;
2444
+ break;
2445
+ }
2446
+ const probe = await githubFetch(candidateUrl);
2447
+ if (probe.ok) {
2448
+ content = await probe.text();
2449
+ skillSubpath = candidate;
2450
+ break;
2451
+ }
2452
+ }
2453
+ if (content === undefined)
2454
+ return process.exit(1);
2455
+ // Fetch the skill's bundled subdirectories when the skill lives in a
2456
+ // subdirectory of the repo. These are the skill-authoring convention and the
2457
+ // same prefixes the installer accepts (see installer/bundle-files.ts):
2458
+ // agents/ holds sub-agent prompts, references/ the docs SKILL.md links to,
2459
+ // scripts/ the executable tooling, assets/ static files, tests/ its tests.
2460
+ // A skill whose SKILL.md says "run scripts/foo.py" and installs without
2461
+ // scripts/ is not degraded — it is broken.
2408
2462
  let legacyAgentFiles;
2409
2463
  if (skill) {
2410
- try {
2411
- const agentsBasePath = skillSubpathOverride
2412
- ? skillSubpathOverride.replace(/\/SKILL\.md$/, "/agents")
2413
- : `skills/${skill}/agents`;
2414
- // ?ref= keeps the agents/ listing on the same branch as SKILL.md
2415
- // (deep links may pin a non-default branch).
2416
- const agentsDirUrl = `https://api.github.com/repos/${owner}/${repo}/contents/${agentsBasePath}?ref=${encodeURIComponent(branch)}`;
2417
- const dirRes = await githubFetch(agentsDirUrl, { headers: { Accept: "application/vnd.github.v3+json" } });
2418
- if (dirRes.ok) {
2464
+ let fetched = 0;
2465
+ for (const subdir of BUNDLE_SUBDIRS) {
2466
+ try {
2467
+ const basePath = skillSubpath.replace(/\/SKILL\.md$/, `/${subdir}`);
2468
+ // ?ref= keeps the listing on the same branch as SKILL.md
2469
+ // (deep links may pin a non-default branch).
2470
+ const dirUrl = `https://api.github.com/repos/${owner}/${repo}/contents/${basePath}?ref=${encodeURIComponent(branch)}`;
2471
+ const dirRes = await githubFetch(dirUrl, { headers: { Accept: "application/vnd.github.v3+json" } });
2472
+ if (!dirRes.ok)
2473
+ continue;
2419
2474
  const entries = (await dirRes.json());
2420
- const mdEntries = entries.filter((e) => e.name.endsWith(".md") && e.download_url && isGitHubDownloadUrl(e.download_url));
2421
- if (mdEntries.length > 0) {
2422
- legacyAgentFiles = {};
2423
- const fetches = mdEntries.map(async (entry) => {
2424
- try {
2425
- const res = await githubFetch(entry.download_url);
2426
- if (res.ok)
2427
- legacyAgentFiles[`agents/${entry.name}`] = await res.text();
2428
- }
2429
- catch { /* skip */ }
2430
- });
2431
- await Promise.allSettled(fetches);
2432
- if (Object.keys(legacyAgentFiles).length === 0)
2433
- legacyAgentFiles = undefined;
2434
- }
2475
+ const wanted = entries.filter((e) => e.type !== "dir" &&
2476
+ isBundleTextFile(e.name) &&
2477
+ (e.size ?? 0) <= MAX_BUNDLE_FILE_BYTES &&
2478
+ e.download_url &&
2479
+ isGitHubDownloadUrl(e.download_url));
2480
+ if (wanted.length === 0)
2481
+ continue;
2482
+ legacyAgentFiles ??= {};
2483
+ const budget = wanted.slice(0, Math.max(0, MAX_BUNDLE_FILES - fetched));
2484
+ fetched += budget.length;
2485
+ const fetches = budget.map(async (entry) => {
2486
+ try {
2487
+ const res = await githubFetch(entry.download_url);
2488
+ if (res.ok)
2489
+ legacyAgentFiles[`${subdir}/${entry.name}`] = await res.text();
2490
+ }
2491
+ catch { /* skip */ }
2492
+ });
2493
+ await Promise.allSettled(fetches);
2494
+ if (fetched >= MAX_BUNDLE_FILES)
2495
+ break;
2435
2496
  }
2497
+ catch { /* subdir doesn't exist or API error — fine */ }
2436
2498
  }
2437
- catch { /* agents/ dir doesn't exist or API error — fine */ }
2499
+ if (legacyAgentFiles && Object.keys(legacyAgentFiles).length === 0)
2500
+ legacyAgentFiles = undefined;
2438
2501
  }
2439
2502
  // Platform security check (best-effort, non-blocking on network error)
2440
2503
  const platformSecurity = await checkPlatformSecurity(skillName);