skills 1.5.22 → 1.5.23

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.
Files changed (3) hide show
  1. package/README.md +29 -2
  2. package/dist/cli.mjs +321 -126
  3. package/package.json +2 -1
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  The CLI for the open agent skills ecosystem.
4
4
 
5
5
  <!-- agent-list:start -->
6
- Supports **OpenCode**, **Claude Code**, **Codex**, **Cursor**, and [72 more](#supported-agents).
6
+ Supports **OpenCode**, **Claude Code**, **Codex**, **Cursor**, and [73 more](#supported-agents).
7
7
  <!-- agent-list:end -->
8
8
 
9
9
  [![skills.sh](https://skills.sh/b/vercel-labs/skills)](https://skills.sh/vercel-labs/skills)
@@ -47,6 +47,28 @@ npx skills add git@github.com:vercel-labs/agent-skills.git
47
47
  npx skills add ./my-local-skills
48
48
  ```
49
49
 
50
+ ### Private Repositories
51
+
52
+ Use the same command for public and private repositories. The CLI uses the authentication already configured for the repository URL:
53
+
54
+ ```bash
55
+ # GitHub shorthand or HTTPS (Git credential helper, GitHub CLI, then SSH fallback)
56
+ npx skills add acme/private-skills
57
+
58
+ # SSH on GitHub, GitLab, or another Git host
59
+ npx skills add git@github.com:acme/private-skills.git
60
+ npx skills add ssh://git@git.example.com/acme/private-skills.git
61
+
62
+ # HTTPS on any Git host (uses your configured Git credential helper)
63
+ npx skills add https://git.example.com/acme/private-skills.git
64
+ ```
65
+
66
+ For GitHub HTTPS and shorthand sources, `skills` first uses normal Git credentials. If that fails and GitHub CLI is authenticated, it tries `gh repo clone`, followed by SSH. It does not execute `gh auth token` or copy the stored GitHub CLI credential into the Node.js process.
67
+
68
+ For GitHub tree lookups, `skills` first tries the API anonymously, then an explicitly supplied environment token, then `gh api`. GitHub CLI applies its own stored authentication and returns only the API response; the credential is never printed to or read by `skills`. If API access still fails, update checks fall back to an authenticated Git clone.
69
+
70
+ `GITHUB_TOKEN` or `GH_TOKEN` can be set explicitly for GitHub API access, including private repository downloads and update checks. They are optional for installs when Git, GitHub CLI, or SSH authentication is already configured.
71
+
50
72
  ### Options
51
73
 
52
74
  | Option | Description |
@@ -297,6 +319,7 @@ Skills can be installed to any of these agents:
297
319
  | OpenHands | `openhands` | `.openhands/skills/` | `~/.openhands/skills/` |
298
320
  | Ona | `ona` | `.ona/skills/` | `~/.ona/skills/` |
299
321
  | Pi | `pi` | `.pi/skills/` | `~/.pi/agent/skills/` |
322
+ | Posit Assistant | `posit-assistant` | `.posit/assistant/skills/` | `~/.posit/assistant/skills/` |
300
323
  | Qoder | `qoder` | `.qoder/skills/` | `~/.qoder/skills/` |
301
324
  | Qoder CN | `qoder-cn` | `.qoder/skills/` | `~/.qoder-cn/skills/` |
302
325
  | Qwen Code | `qwen-code` | `.qwen/skills/` | `~/.qwen/skills/` |
@@ -430,6 +453,7 @@ discover `SKILL.md` files outside these container directories (e.g. under
430
453
  - `.openhands/skills/`
431
454
  - `.ona/skills/`
432
455
  - `.pi/skills/`
456
+ - `.posit/assistant/skills/`
433
457
  - `.qoder/skills/`
434
458
  - `.qwen/skills/`
435
459
  - `.reasonix/skills/`
@@ -504,6 +528,8 @@ Ensure you have write access to the target directory.
504
528
  | `INSTALL_INTERNAL_SKILLS` | Set to `1` or `true` to show and install skills marked as `internal: true` |
505
529
  | `DISABLE_TELEMETRY` | Set to disable anonymous usage telemetry |
506
530
  | `DO_NOT_TRACK` | Alternative way to disable telemetry |
531
+ | `GITHUB_TOKEN` | Optional explicit token for authenticated GitHub API requests |
532
+ | `GH_TOKEN` | Fallback explicit token for authenticated GitHub API requests |
507
533
 
508
534
  ```bash
509
535
  # Install internal skills
@@ -514,7 +540,7 @@ INSTALL_INTERNAL_SKILLS=1 npx skills add vercel-labs/agent-skills --list
514
540
 
515
541
  This CLI collects anonymous usage data to help improve the tool. No personal information is collected.
516
542
 
517
- Telemetry is automatically disabled in CI environments.
543
+ GitHub repository and skill identifiers are sent only for repositories that GitHub positively confirms are public. Other remote source types may include source and skill identifiers in install telemetry because their visibility cannot be checked through GitHub. Security-audit requests remain limited to confirmed-public GitHub repositories. Set `DISABLE_TELEMETRY=1` or `DO_NOT_TRACK=1` to disable both entirely.
518
544
 
519
545
  ## Related Links
520
546
 
@@ -542,6 +568,7 @@ Telemetry is automatically disabled in CI environments.
542
568
  - [Qwen Code Skills Documentation](https://qwenlm.github.io/qwen-code-docs/en/users/features/skills/)
543
569
  - [OpenHands Skills Documentation](https://docs.openhands.ai/modules/usage/how-to/using-skills)
544
570
  - [Pi Skills Documentation](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/docs/skills.md)
571
+ - [Posit Assistant Skills Documentation](https://assistant.posit.co/docs/features/skills/)
545
572
  - [Qoder Skills Documentation](https://docs.qoder.com/cli/Skills)
546
573
  - [Replit Skills Documentation](https://docs.replit.com/replitai/skills)
547
574
  - [Roo Code Skills Documentation](https://docs.roocode.com/features/skills)
package/dist/cli.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { __toESM } from "./_chunks/rolldown-runtime.mjs";
3
3
  import { isCancel } from "./_chunks/libs/@clack/core.mjs";
4
- import { cancel, confirm, intro, log, multiselect as multiselect$1, note, outro, select, spinner } from "./_chunks/libs/@clack/prompts.mjs";
4
+ import { cancel, confirm, intro, log, multiselect, note, outro, select, spinner } from "./_chunks/libs/@clack/prompts.mjs";
5
5
  import { require_picocolors } from "./_chunks/libs/picocolors.mjs";
6
6
  import { esm_default } from "./_chunks/libs/simple-git.mjs";
7
7
  import { xdgConfig } from "./_chunks/libs/xdg-basedir.mjs";
@@ -16,7 +16,7 @@ import { homedir, platform, tmpdir } from "os";
16
16
  import * as readline from "readline";
17
17
  import { Writable } from "stream";
18
18
  import { promisify } from "util";
19
- import { execFile, execSync, spawn, spawnSync } from "child_process";
19
+ import { execFile, spawn, spawnSync } from "child_process";
20
20
  import { access, chmod, cp, lstat, mkdir, mkdtemp, readFile, readdir, readlink, realpath, rm, stat, symlink, writeFile } from "fs/promises";
21
21
  import { parse } from "yaml";
22
22
  import { createHash } from "crypto";
@@ -26,6 +26,7 @@ import { crc32, gunzipSync, inflateRawSync } from "node:zlib";
26
26
  import { tmpdir as tmpdir$1 } from "node:os";
27
27
  import { pipeline } from "node:stream/promises";
28
28
  import * as tar from "tar";
29
+ import { execFile as execFile$1 } from "node:child_process";
29
30
  var import_picocolors = /* @__PURE__ */ __toESM(require_picocolors(), 1);
30
31
  const DEFAULT_GITHUB_HOST = "github.com";
31
32
  function getGitHubHost() {
@@ -416,8 +417,19 @@ function toggleSearchEntry(selected, entry) {
416
417
  } else if (entry?.type === "item") if (selected.has(entry.item.value)) selected.delete(entry.item.value);
417
418
  else selected.add(entry.item.value);
418
419
  }
420
+ function getSelectAllState(selected, items) {
421
+ const selectedCount = items.filter((item) => selected.has(item.value)).length;
422
+ if (selectedCount === 0) return "none";
423
+ if (selectedCount === items.length) return "all";
424
+ return "partial";
425
+ }
426
+ function toggleAllItems(selected, items) {
427
+ const shouldClear = getSelectAllState(selected, items) === "all";
428
+ for (const item of items) if (shouldClear) selected.delete(item.value);
429
+ else selected.add(item.value);
430
+ }
419
431
  async function searchMultiselect(options) {
420
- const { message, items, maxVisible = 8, initialSelected = [], required = false, lockedSection, searchable = true, showDetail = false, detailLines = 2, showSelectedSummary = true, selectGroups = false } = options;
432
+ const { message, items, maxVisible = 8, initialSelected = [], required = false, lockedSection, searchable = true, showDetail = false, detailLines = 2, showSelectedSummary = true, selectGroups = false, selectAll = false } = options;
421
433
  return new Promise((resolve) => {
422
434
  const rl = readline.createInterface({
423
435
  input: process.stdin,
@@ -444,6 +456,8 @@ async function searchMultiselect(options) {
444
456
  const lines = [];
445
457
  const filtered = getFiltered();
446
458
  const entries = buildSearchEntries(filtered, selectGroups, collapsedGroups);
459
+ const hasSelectAll = selectAll && items.length > 0;
460
+ const entryCursor = cursor - (hasSelectAll ? 1 : 0);
447
461
  const icon = state === "active" ? S_STEP_ACTIVE : state === "cancel" ? S_STEP_CANCEL : S_STEP_SUBMIT;
448
462
  lines.push(`${icon} ${import_picocolors.default.bold(message)}`);
449
463
  if (state === "active") {
@@ -462,21 +476,59 @@ async function searchMultiselect(options) {
462
476
  lines.push(`${S_BAR} ${import_picocolors.default.dim("↑↓ move, space select, enter confirm")}`);
463
477
  lines.push(`${S_BAR}`);
464
478
  }
465
- const visibleStart = Math.max(0, Math.min(cursor - Math.floor(maxVisible / 2), entries.length - maxVisible));
466
- const visibleEnd = Math.min(entries.length, visibleStart + maxVisible);
467
- const visibleEntries = entries.slice(visibleStart, visibleEnd);
468
- if (filtered.length === 0) lines.push(`${S_BAR} ${import_picocolors.default.dim("No matches found")}`);
469
- else {
479
+ if (hasSelectAll) {
480
+ const selectedCount = items.filter((item) => selected.has(item.value)).length;
481
+ const selectAllState = getSelectAllState(selected, items);
482
+ const radio = selectAllState === "all" ? S_RADIO_ACTIVE : selectAllState === "partial" ? import_picocolors.default.yellow("◐") : S_RADIO_INACTIVE;
483
+ const isCursor = cursor === 0;
484
+ const prefix = isCursor ? import_picocolors.default.cyan("❯") : " ";
485
+ const label = isCursor ? import_picocolors.default.underline(import_picocolors.default.bold("Select All")) : import_picocolors.default.bold("Select All");
486
+ lines.push(`${S_BAR} ${prefix} ${radio} ${label} ${import_picocolors.default.dim(`(${selectedCount}/${items.length})`)}`);
487
+ lines.push(`${S_BAR} ${S_BAR_H.repeat(36)}`);
488
+ }
489
+ const columns = process.stdout.columns && process.stdout.columns > 0 ? process.stdout.columns : 80;
490
+ const buildFooterLines = (includeDetail, includeSelectedSummary) => {
491
+ const footerLines = [];
492
+ if (includeDetail) {
493
+ const entry = entries[entryCursor];
494
+ const detail = hasSelectAll && cursor === 0 ? `Select or clear all ${items.length} skills.` : entry?.type === "group" ? `Select all ${entry.items.length} skills in ${entry.group}.` : entry?.item.detail;
495
+ const detailWidth = Math.max(1, columns - 5);
496
+ footerLines.push(`${S_BAR}`);
497
+ footerLines.push(`${S_BAR} ${import_picocolors.default.dim("Description")}`);
498
+ for (const line of formatDetailLines(detail, detailWidth, detailLines)) footerLines.push(`${S_BAR} ${import_picocolors.default.dim(line)}`);
499
+ }
500
+ if (includeSelectedSummary) {
501
+ footerLines.push(`${S_BAR}`);
502
+ const allSelectedLabels = [...lockedSection ? lockedSection.items.map((i) => i.label) : [], ...items.filter((item) => selected.has(item.value)).map((item) => item.label)];
503
+ if (allSelectedLabels.length === 0) footerLines.push(`${S_BAR} ${import_picocolors.default.dim("Selected: (none)")}`);
504
+ else {
505
+ const summary = allSelectedLabels.length <= 3 ? allSelectedLabels.join(", ") : `${allSelectedLabels.slice(0, 3).join(", ")} +${allSelectedLabels.length - 3} more`;
506
+ footerLines.push(`${S_BAR} ${import_picocolors.default.green("Selected:")} ${summary}`);
507
+ }
508
+ }
509
+ if (!searchable) {
510
+ footerLines.push(`${S_BAR}`);
511
+ footerLines.push(`${S_BAR} ${import_picocolors.default.dim("↑↓ move, ←→ collapse/expand, space select, enter confirm")}`);
512
+ }
513
+ footerLines.push(`${import_picocolors.default.dim("└")}`);
514
+ return footerLines;
515
+ };
516
+ const buildItemLines = (visibleLimit) => {
517
+ if (filtered.length === 0) return [`${S_BAR} ${import_picocolors.default.dim("No matches found")}`];
518
+ const itemLines = [];
519
+ const visibleStart = Math.max(0, Math.min(entryCursor - Math.floor(visibleLimit / 2), entries.length - visibleLimit));
520
+ const visibleEnd = Math.min(entries.length, visibleStart + visibleLimit);
521
+ const visibleEntries = entries.slice(visibleStart, visibleEnd);
470
522
  for (let i = 0; i < visibleEntries.length; i++) {
471
523
  const entry = visibleEntries[i];
472
- const isCursor = visibleStart + i === cursor;
524
+ const isCursor = visibleStart + i === entryCursor;
473
525
  if (entry.type === "group") {
474
526
  const selectedCount = entry.items.filter((item) => selected.has(item.value)).length;
475
527
  const radio = selectedCount === entry.items.length ? S_RADIO_ACTIVE : selectedCount > 0 ? import_picocolors.default.yellow("◐") : S_RADIO_INACTIVE;
476
528
  const label = isCursor ? import_picocolors.default.underline(import_picocolors.default.bold(entry.group)) : import_picocolors.default.bold(entry.group);
477
529
  const prefix = isCursor ? import_picocolors.default.cyan("❯") : " ";
478
530
  const disclosure = import_picocolors.default.dim(entry.collapsed ? "▸" : "▾");
479
- lines.push(`${S_BAR} ${prefix} ${disclosure} ${radio} ${label}`);
531
+ itemLines.push(`${S_BAR} ${prefix} ${disclosure} ${radio} ${label}`);
480
532
  continue;
481
533
  }
482
534
  const item = entry.item;
@@ -487,7 +539,7 @@ async function searchMultiselect(options) {
487
539
  const groupItems = selectGroups && item.group ? filtered.filter((i) => i.group === item.group) : [];
488
540
  const isLastInGroup = groupItems.at(-1) === item;
489
541
  const tree = groupItems.length > 0 ? `${import_picocolors.default.dim(isLastInGroup ? "└─" : "├─")} ` : "";
490
- lines.push(`${S_BAR} ${prefix} ${tree}${radio} ${label}${hint}`);
542
+ itemLines.push(`${S_BAR} ${prefix} ${tree}${radio} ${label}${hint}`);
491
543
  }
492
544
  const hiddenBefore = visibleStart;
493
545
  const hiddenAfter = entries.length - visibleEnd;
@@ -495,32 +547,48 @@ async function searchMultiselect(options) {
495
547
  const parts = [];
496
548
  if (hiddenBefore > 0) parts.push(`↑ ${hiddenBefore} more`);
497
549
  if (hiddenAfter > 0) parts.push(`↓ ${hiddenAfter} more`);
498
- lines.push(`${S_BAR} ${import_picocolors.default.dim(parts.join(" "))}`);
550
+ itemLines.push(`${S_BAR} ${import_picocolors.default.dim(parts.join(" "))}`);
499
551
  }
500
- }
501
- if (showDetail) {
502
- const entry = entries[cursor];
503
- const detail = entry?.type === "group" ? `Select all ${entry.items.length} skills in ${entry.group}.` : entry?.item.detail;
504
- const columns = process.stdout.columns && process.stdout.columns > 0 ? process.stdout.columns : 80;
505
- const detailWidth = Math.max(1, columns - 5);
506
- lines.push(`${S_BAR}`);
507
- lines.push(`${S_BAR} ${import_picocolors.default.dim("Description")}`);
508
- for (const line of formatDetailLines(detail, detailWidth, detailLines)) lines.push(`${S_BAR} ${import_picocolors.default.dim(line)}`);
509
- }
510
- if (showSelectedSummary) {
511
- lines.push(`${S_BAR}`);
512
- const allSelectedLabels = [...lockedSection ? lockedSection.items.map((i) => i.label) : [], ...items.filter((item) => selected.has(item.value)).map((item) => item.label)];
513
- if (allSelectedLabels.length === 0) lines.push(`${S_BAR} ${import_picocolors.default.dim("Selected: (none)")}`);
514
- else {
515
- const summary = allSelectedLabels.length <= 3 ? allSelectedLabels.join(", ") : `${allSelectedLabels.slice(0, 3).join(", ")} +${allSelectedLabels.length - 3} more`;
516
- lines.push(`${S_BAR} ${import_picocolors.default.green("Selected:")} ${summary}`);
552
+ return itemLines;
553
+ };
554
+ const terminalRows = process.stdout.rows && process.stdout.rows > 0 ? process.stdout.rows : void 0;
555
+ const maxFrameRows = terminalRows ? Math.max(1, terminalRows - 1) : void 0;
556
+ const fitFrame = (includeDetail, includeSelectedSummary) => {
557
+ const footerLines = buildFooterLines(includeDetail, includeSelectedSummary);
558
+ let visibleLimit = Math.max(1, maxVisible);
559
+ let itemLines = buildItemLines(visibleLimit);
560
+ let frameRows = countVisualRowsForLines([
561
+ ...lines,
562
+ ...itemLines,
563
+ ...footerLines
564
+ ], columns);
565
+ while (maxFrameRows && frameRows > maxFrameRows && visibleLimit > 1) {
566
+ visibleLimit -= 1;
567
+ itemLines = buildItemLines(visibleLimit);
568
+ frameRows = countVisualRowsForLines([
569
+ ...lines,
570
+ ...itemLines,
571
+ ...footerLines
572
+ ], columns);
517
573
  }
574
+ return {
575
+ itemLines,
576
+ footerLines,
577
+ frameRows
578
+ };
579
+ };
580
+ let includeDetail = showDetail;
581
+ let includeSelectedSummary = showSelectedSummary;
582
+ let fitted = fitFrame(includeDetail, includeSelectedSummary);
583
+ if (maxFrameRows && fitted.frameRows > maxFrameRows && includeDetail) {
584
+ includeDetail = false;
585
+ fitted = fitFrame(includeDetail, includeSelectedSummary);
518
586
  }
519
- if (!searchable) {
520
- lines.push(`${S_BAR}`);
521
- lines.push(`${S_BAR} ${import_picocolors.default.dim("↑↓ move, ←→ collapse/expand, space select, enter confirm")}`);
587
+ if (maxFrameRows && fitted.frameRows > maxFrameRows && includeSelectedSummary) {
588
+ includeSelectedSummary = false;
589
+ fitted = fitFrame(includeDetail, includeSelectedSummary);
522
590
  }
523
- lines.push(`${import_picocolors.default.dim("└")}`);
591
+ lines.push(...fitted.itemLines, ...fitted.footerLines);
524
592
  } else if (state === "submit") {
525
593
  const allSelectedLabels = [...lockedSection ? lockedSection.items.map((i) => i.label) : [], ...items.filter((item) => selected.has(item.value)).map((item) => item.label)];
526
594
  lines.push(`${S_BAR} ${import_picocolors.default.dim(allSelectedLabels.join(", "))}`);
@@ -548,6 +616,9 @@ async function searchMultiselect(options) {
548
616
  const keypressHandler = (_str, key) => {
549
617
  if (!key) return;
550
618
  const entries = buildSearchEntries(getFiltered(), selectGroups, collapsedGroups);
619
+ const hasSelectAll = selectAll && items.length > 0;
620
+ const cursorOffset = hasSelectAll ? 1 : 0;
621
+ const entry = entries[cursor - cursorOffset];
551
622
  if (key.name === "return") {
552
623
  submit();
553
624
  return;
@@ -562,12 +633,11 @@ async function searchMultiselect(options) {
562
633
  return;
563
634
  }
564
635
  if (key.name === "down") {
565
- cursor = Math.min(entries.length - 1, cursor + 1);
636
+ cursor = Math.min(entries.length + cursorOffset - 1, cursor + 1);
566
637
  render();
567
638
  return;
568
639
  }
569
640
  if (selectGroups && key.name === "right") {
570
- const entry = entries[cursor];
571
641
  if (entry?.type === "group" && entry.collapsed) {
572
642
  collapsedGroups.delete(entry.group);
573
643
  render();
@@ -575,18 +645,17 @@ async function searchMultiselect(options) {
575
645
  return;
576
646
  }
577
647
  if (selectGroups && key.name === "left") {
578
- const entry = entries[cursor];
579
648
  const group = entry?.type === "group" ? entry.group : entry?.item.group;
580
649
  if (group) {
581
650
  collapsedGroups.add(group);
582
- cursor = buildSearchEntries(getFiltered(), selectGroups, collapsedGroups).findIndex((collapsedEntry) => collapsedEntry.type === "group" && collapsedEntry.group === group);
651
+ cursor = buildSearchEntries(getFiltered(), selectGroups, collapsedGroups).findIndex((collapsedEntry) => collapsedEntry.type === "group" && collapsedEntry.group === group) + cursorOffset;
583
652
  render();
584
653
  }
585
654
  return;
586
655
  }
587
656
  if (key.name === "space") {
588
- const entry = entries[cursor];
589
- toggleSearchEntry(selected, entry);
657
+ if (hasSelectAll && cursor === 0) toggleAllItems(selected, items);
658
+ else toggleSearchEntry(selected, entry);
590
659
  render();
591
660
  return;
592
661
  }
@@ -811,6 +880,38 @@ async function cloneRepo(url, ref) {
811
880
  throw new GitCloneError(`Failed to clone ${url}: ${errorMessage}`, url, false, false);
812
881
  }
813
882
  }
883
+ async function getGitTreeHash(repoDir, skillPath) {
884
+ const segments = skillPath.replace(/\\/g, "/").split("/");
885
+ segments.pop();
886
+ const folderPath = segments.join("/");
887
+ const revision = folderPath ? `HEAD:${folderPath}` : "HEAD^{tree}";
888
+ try {
889
+ const hash = (await new Promise((resolve, reject) => {
890
+ execFile("git", [
891
+ "-C",
892
+ repoDir,
893
+ "rev-parse",
894
+ "--verify",
895
+ "--end-of-options",
896
+ revision
897
+ ], {
898
+ encoding: "utf8",
899
+ timeout: CLONE_TIMEOUT_MS,
900
+ env: {
901
+ ...process.env,
902
+ GIT_OPTIONAL_LOCKS: "0",
903
+ GIT_TERMINAL_PROMPT: "0"
904
+ }
905
+ }, (error, output) => {
906
+ if (error) reject(error);
907
+ else resolve(output);
908
+ });
909
+ })).trim();
910
+ return /^[0-9a-f]{40}$/i.test(hash) ? hash.toLowerCase() : null;
911
+ } catch {
912
+ return null;
913
+ }
914
+ }
814
915
  async function cleanupTempDir(dir) {
815
916
  const normalizedDir = normalize(resolve(dir));
816
917
  const normalizedTmpDir = normalize(resolve(tmpdir()));
@@ -1018,6 +1119,7 @@ const AGENT_PROJECT_SKILL_DIRS = [
1018
1119
  ".opencode/skills",
1019
1120
  ".openhands/skills",
1020
1121
  ".pi/skills",
1122
+ ".posit/assistant/skills",
1021
1123
  ".qoder/skills",
1022
1124
  ".roo/skills",
1023
1125
  ".trae/skills",
@@ -1228,6 +1330,9 @@ function isKimchiInstalled(homeDir = home, pathExists = existsSync) {
1228
1330
  function isMiniMaxCodeInstalled(homeDir = home, pathExists = existsSync) {
1229
1331
  return pathExists(join(homeDir, ".minimax")) || pathExists("/Applications/MiniMax Code.app");
1230
1332
  }
1333
+ function isPositAssistantInstalled(homeDir = home, pathExists = existsSync) {
1334
+ return pathExists(join(homeDir, ".posit/assistant")) || pathExists(join(homeDir, ".positai"));
1335
+ }
1231
1336
  const agents = {
1232
1337
  "aider-desk": {
1233
1338
  name: "aider-desk",
@@ -1710,6 +1815,15 @@ const agents = {
1710
1815
  return existsSync(join(home, ".pi/agent"));
1711
1816
  }
1712
1817
  },
1818
+ "posit-assistant": {
1819
+ name: "posit-assistant",
1820
+ displayName: "Posit Assistant",
1821
+ skillsDir: ".posit/assistant/skills",
1822
+ globalSkillsDir: join(home, ".posit/assistant/skills"),
1823
+ detectInstalled: async () => {
1824
+ return isPositAssistantInstalled();
1825
+ }
1826
+ },
1713
1827
  qoder: {
1714
1828
  name: "qoder",
1715
1829
  displayName: "Qoder",
@@ -2567,6 +2681,7 @@ function setVersion(version) {
2567
2681
  cliVersion = version;
2568
2682
  }
2569
2683
  async function fetchAuditData(source, skillSlugs, timeoutMs = 3e3) {
2684
+ if (!isEnabled()) return null;
2570
2685
  if (skillSlugs.length === 0) return null;
2571
2686
  try {
2572
2687
  const params = new URLSearchParams({
@@ -2875,6 +2990,16 @@ const DISCOVERY_SCHEMA_V2 = "https://schemas.agentskills.io/discovery/0.2.0/sche
2875
2990
  const MAX_ARCHIVE_UNPACKED_BYTES = 50 * 1024 * 1024;
2876
2991
  const MAX_ARCHIVE_FILES = 1e3;
2877
2992
  const DISCOVERY_TIMEOUT_MS = 1e4;
2993
+ var WellKnownScopeNotFoundError = class extends Error {
2994
+ scopePath;
2995
+ rootUrl;
2996
+ constructor(scopePath, rootUrl) {
2997
+ super(`No skills found for the scoped path '${scopePath}' on ${rootUrl}. Not falling back to the root skills index because that would install every skill the host publishes. Check the URL, or run 'skills add ${rootUrl}' to install from the root index.`);
2998
+ this.name = "WellKnownScopeNotFoundError";
2999
+ this.scopePath = scopePath;
3000
+ this.rootUrl = rootUrl;
3001
+ }
3002
+ };
2878
3003
  var WellKnownProvider = class {
2879
3004
  id = "well-known";
2880
3005
  displayName = "Well-Known Skills";
@@ -3173,16 +3298,34 @@ var WellKnownProvider = class {
3173
3298
  indexEntry: input.indexEntry
3174
3299
  };
3175
3300
  }
3176
- async fetchAllSkills(url) {
3301
+ getScope(url) {
3302
+ try {
3303
+ const parsed = new URL(url);
3304
+ const scopePath = parsed.pathname.replace(/\/$/, "");
3305
+ if (!scopePath) return null;
3306
+ return {
3307
+ scopePath,
3308
+ rootBaseUrl: `${parsed.protocol}//${parsed.host}`
3309
+ };
3310
+ } catch {
3311
+ return null;
3312
+ }
3313
+ }
3314
+ async fetchAllSkills(url, options = {}) {
3177
3315
  try {
3178
3316
  const candidates = await this.fetchIndexCandidates(url);
3179
- for (const result of candidates) {
3317
+ const scope = this.getScope(url);
3318
+ const scopedCandidates = scope ? candidates.filter((c) => c.resolvedBaseUrl !== scope.rootBaseUrl) : candidates;
3319
+ const includeInternal = options.includeInternal || shouldInstallInternalSkills();
3320
+ for (const result of scopedCandidates) {
3180
3321
  const skillPromises = result.entries.map((entry) => this.fetchSkillByEntry(entry));
3181
- const skills = (await Promise.all(skillPromises)).filter((s) => s !== null);
3322
+ const skills = (await Promise.all(skillPromises)).filter((s) => s !== null).filter((skill) => includeInternal || skill.metadata?.internal !== true);
3182
3323
  if (skills.length > 0) return skills;
3183
3324
  }
3325
+ if (scope && scopedCandidates.length < candidates.length) throw new WellKnownScopeNotFoundError(scope.scopePath, scope.rootBaseUrl);
3184
3326
  return [];
3185
- } catch {
3327
+ } catch (error) {
3328
+ if (error instanceof WellKnownScopeNotFoundError) throw error;
3186
3329
  return [];
3187
3330
  }
3188
3331
  }
@@ -3498,25 +3641,9 @@ async function writeSkillLock(lock) {
3498
3641
  await mkdir(dirname(lockPath), { recursive: true });
3499
3642
  await writeFile(lockPath, JSON.stringify(lock, null, 2), "utf-8");
3500
3643
  }
3501
- let _ghWarningShown = false;
3502
3644
  function getGitHubToken() {
3503
3645
  if (process.env.GITHUB_TOKEN) return process.env.GITHUB_TOKEN;
3504
3646
  if (process.env.GH_TOKEN) return process.env.GH_TOKEN;
3505
- if (!_ghWarningShown) {
3506
- process.stderr.write(`${import_picocolors.default.dim("│ GitHub API request limit reached; checking existing ")}${import_picocolors.default.cyan("gh")}${import_picocolors.default.dim(" authentication…\n")}`);
3507
- _ghWarningShown = true;
3508
- }
3509
- try {
3510
- const token = execSync("gh auth token", {
3511
- encoding: "utf-8",
3512
- stdio: [
3513
- "pipe",
3514
- "pipe",
3515
- "pipe"
3516
- ]
3517
- }).trim();
3518
- if (token) return token;
3519
- } catch {}
3520
3647
  return null;
3521
3648
  }
3522
3649
  async function addSkillToLock(skillName, entry) {
@@ -3570,13 +3697,15 @@ async function saveSelectedAgents(agents) {
3570
3697
  const DOWNLOAD_BASE_URL = process.env.SKILLS_DOWNLOAD_URL || "https://skills.sh";
3571
3698
  const BLOB_ALLOWED_REPOS = { "zapier/connectors": { downloadUrl: (slug) => `https://connectors-skills.zapier.com/download/${encodeURIComponent(slug)}/snapshot.json` } };
3572
3699
  const FETCH_TIMEOUT = 1e4;
3700
+ const GH_API_MAX_BUFFER = 16 * 1024 * 1024;
3573
3701
  function toSkillSlug(name) {
3574
3702
  return name.toLowerCase().replace(/[\s_]+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
3575
3703
  }
3576
3704
  let _rateLimitedThisSession = false;
3577
3705
  async function fetchTreeBranch(ownerRepo, branch, token) {
3578
3706
  try {
3579
- const url = `https://api.github.com/repos/${ownerRepo}/git/trees/${encodeURIComponent(branch)}?recursive=1`;
3707
+ const githubHost = getGitHubHost();
3708
+ const url = `${githubHost === "github.com" ? "https://api.github.com" : `https://${githubHost}/api/v3`}/repos/${ownerRepo}/git/trees/${encodeURIComponent(branch)}?recursive=1`;
3580
3709
  const headers = {
3581
3710
  Accept: "application/vnd.github.v3+json",
3582
3711
  "User-Agent": "skills-cli"
@@ -3620,13 +3749,55 @@ async function fetchTreeWithToken(ownerRepo, branches, getToken) {
3620
3749
  }
3621
3750
  return null;
3622
3751
  }
3752
+ async function fetchTreeWithGitHubCli(ownerRepo, branches) {
3753
+ for (const branch of branches) try {
3754
+ const endpoint = `repos/${ownerRepo}/git/trees/${encodeURIComponent(branch)}?recursive=1`;
3755
+ const stdout = await new Promise((resolve, reject) => {
3756
+ execFile$1("gh", [
3757
+ "api",
3758
+ endpoint,
3759
+ "--method",
3760
+ "GET",
3761
+ "--hostname",
3762
+ getGitHubHost()
3763
+ ], {
3764
+ encoding: "utf8",
3765
+ timeout: FETCH_TIMEOUT,
3766
+ maxBuffer: GH_API_MAX_BUFFER,
3767
+ windowsHide: true,
3768
+ env: {
3769
+ ...process.env,
3770
+ GH_PROMPT_DISABLED: "1"
3771
+ }
3772
+ }, (error, output) => {
3773
+ if (error) reject(error);
3774
+ else resolve(output);
3775
+ });
3776
+ });
3777
+ const data = JSON.parse(stdout);
3778
+ if (typeof data.sha !== "string" || !Array.isArray(data.tree)) continue;
3779
+ return {
3780
+ sha: data.sha,
3781
+ branch,
3782
+ tree: data.tree
3783
+ };
3784
+ } catch {}
3785
+ return null;
3786
+ }
3787
+ async function fetchTreeWithAvailableAuth(ownerRepo, branches, getToken) {
3788
+ if (getToken) {
3789
+ const tree = await fetchTreeWithToken(ownerRepo, branches, getToken);
3790
+ if (tree) return tree;
3791
+ }
3792
+ return fetchTreeWithGitHubCli(ownerRepo, branches);
3793
+ }
3623
3794
  async function fetchRepoTree(ownerRepo, ref, getToken) {
3624
3795
  const branches = ref ? [ref] : [
3625
3796
  "HEAD",
3626
3797
  "main",
3627
3798
  "master"
3628
3799
  ];
3629
- if (_rateLimitedThisSession && getToken) return fetchTreeWithToken(ownerRepo, branches, getToken);
3800
+ if (_rateLimitedThisSession) return fetchTreeWithAvailableAuth(ownerRepo, branches, getToken);
3630
3801
  let rateLimited = false;
3631
3802
  let authRetryable = false;
3632
3803
  for (const branch of branches) {
@@ -3641,9 +3812,9 @@ async function fetchRepoTree(ownerRepo, ref, getToken) {
3641
3812
  break;
3642
3813
  }
3643
3814
  }
3644
- if (!getToken || !(rateLimited || authRetryable)) return null;
3815
+ if (!(rateLimited || authRetryable)) return null;
3645
3816
  if (rateLimited) _rateLimitedThisSession = true;
3646
- return fetchTreeWithToken(ownerRepo, branches, getToken);
3817
+ return fetchTreeWithAvailableAuth(ownerRepo, branches, getToken);
3647
3818
  }
3648
3819
  function getSkillFolderHashFromTree(tree, skillPath) {
3649
3820
  let folderPath = skillPath.replace(/\\/g, "/");
@@ -3680,6 +3851,7 @@ const PRIORITY_PREFIXES = [
3680
3851
  ".opencode/skills/",
3681
3852
  ".openhands/skills/",
3682
3853
  ".pi/skills/",
3854
+ ".posit/assistant/skills/",
3683
3855
  ".qoder/skills/",
3684
3856
  ".roo/skills/",
3685
3857
  ".trae/skills/",
@@ -3772,6 +3944,7 @@ function computeSnapshotHash(files) {
3772
3944
  return hash.digest("hex");
3773
3945
  }
3774
3946
  async function tryBlobInstall(ownerRepo, options = {}) {
3947
+ if (options.ref !== void 0) return null;
3775
3948
  const tree = await fetchRepoTree(ownerRepo, options.ref, options.getToken);
3776
3949
  if (!tree) return null;
3777
3950
  let skillMdPaths = findSkillMdPaths(tree, options.subpath);
@@ -3844,7 +4017,7 @@ async function tryBlobInstall(ownerRepo, options = {}) {
3844
4017
  tree
3845
4018
  };
3846
4019
  }
3847
- var version$1 = "1.5.22";
4020
+ var version$1 = "1.5.23";
3848
4021
  const isCancelled$1 = (value) => typeof value === "symbol";
3849
4022
  const EVE_AGENT_LABEL = "eve agent";
3850
4023
  async function isSourcePrivate(source) {
@@ -3997,13 +4170,6 @@ function buildResultLines(results, targetAgents) {
3997
4170
  if (failedSymlinks.length > 0) lines.push(` ${import_picocolors.default.yellow("copied:")} ${formatList$1(failedSymlinks)}`);
3998
4171
  return lines;
3999
4172
  }
4000
- function multiselect(opts) {
4001
- return multiselect$1({
4002
- ...opts,
4003
- options: opts.options,
4004
- message: `${opts.message} ${import_picocolors.default.dim("(space to toggle)")}`
4005
- });
4006
- }
4007
4173
  async function promptForAgents(message, choices) {
4008
4174
  let lastSelected;
4009
4175
  try {
@@ -4073,7 +4239,16 @@ function isSkillsShPackUrl(url) {
4073
4239
  }
4074
4240
  async function handleWellKnownSkills(source, url, options, spinner) {
4075
4241
  spinner.start("Discovering skills from well-known endpoint...");
4076
- const skills = await wellKnownProvider.fetchAllSkills(url).catch(() => []);
4242
+ let skills = [];
4243
+ try {
4244
+ skills = await wellKnownProvider.fetchAllSkills(url, { includeInternal: Boolean(options.skill && options.skill.length > 0 && !options.skill.includes("*")) });
4245
+ } catch (error) {
4246
+ if (error instanceof WellKnownScopeNotFoundError) {
4247
+ spinner.stop(import_picocolors.default.red("No matching skills"));
4248
+ log.error(error.message);
4249
+ process.exit(1);
4250
+ }
4251
+ }
4077
4252
  if (skills.length === 0) {
4078
4253
  spinner.stop(import_picocolors.default.dim("No well-known skills found; trying direct download..."));
4079
4254
  return false;
@@ -4116,17 +4291,19 @@ async function handleWellKnownSkills(source, url, options, spinner) {
4116
4291
  selectedSkills = skills;
4117
4292
  log.info(`Installing all ${skills.length} skills`);
4118
4293
  } else {
4119
- const selected = await multiselect({
4294
+ const selected = await searchMultiselect({
4120
4295
  message: "Select skills to install",
4121
- options: skills.map((s) => ({
4296
+ items: skills.map((s) => ({
4122
4297
  value: s,
4123
4298
  label: s.installName,
4124
4299
  hint: s.description.length > 60 ? s.description.slice(0, 57) + "…" : s.description
4125
4300
  })),
4126
- initialValues: isSkillsShPackUrl(url) ? skills : void 0,
4127
- required: true
4301
+ initialSelected: isSkillsShPackUrl(url) ? skills : void 0,
4302
+ required: true,
4303
+ maxVisible: 20,
4304
+ selectAll: true
4128
4305
  });
4129
- if (isCancel(selected)) {
4306
+ if (isCancelled$1(selected)) {
4130
4307
  cancel("Installation cancelled");
4131
4308
  process.exit(0);
4132
4309
  }
@@ -4424,7 +4601,7 @@ async function runAdd(args, options = {}) {
4424
4601
  options.skill = options.skill || [];
4425
4602
  if (!options.skill.includes(parsed.skillFilter)) options.skill.push(parsed.skillFilter);
4426
4603
  }
4427
- const includeInternal = !!(options.skill && options.skill.length > 0);
4604
+ const includeInternal = !!(options.skill && options.skill.length > 0 && !options.skill.includes("*"));
4428
4605
  let skills;
4429
4606
  let blobResult = null;
4430
4607
  if (parsed.type === "local") {
@@ -4577,7 +4754,8 @@ async function runAdd(args, options = {}) {
4577
4754
  searchable: !hasGroups,
4578
4755
  showDetail: true,
4579
4756
  showSelectedSummary: false,
4580
- selectGroups: hasGroups
4757
+ selectGroups: hasGroups,
4758
+ selectAll: true
4581
4759
  });
4582
4760
  if (isCancelled$1(selected)) {
4583
4761
  cancel("Installation cancelled");
@@ -4587,7 +4765,7 @@ async function runAdd(args, options = {}) {
4587
4765
  selectedSkills = selected;
4588
4766
  }
4589
4767
  const ownerRepoForAudit = getOwnerRepo(parsed);
4590
- const auditPromise = ownerRepoForAudit ? fetchAuditData(ownerRepoForAudit, selectedSkills.map((s) => getSkillDisplayName(s))) : Promise.resolve(null);
4768
+ const auditPromise = ownerRepoForAudit ? repoPrivacyPromise.then((isPrivate) => isPrivate === false ? fetchAuditData(ownerRepoForAudit, selectedSkills.map((s) => getSkillDisplayName(s))) : null) : Promise.resolve(null);
4591
4769
  let targetAgents;
4592
4770
  const validAgents = Object.keys(agents);
4593
4771
  if (options.agent?.includes("*")) {
@@ -4667,7 +4845,7 @@ async function runAdd(args, options = {}) {
4667
4845
  const availableSubagents = getEveSubagents(process.cwd());
4668
4846
  if (options.subagent && options.subagent.length > 0) eveSubagentTargets = options.subagent.map((s) => s === "root" || s === "." ? void 0 : s);
4669
4847
  else if (availableSubagents.length > 0 && !options.yes) {
4670
- const selectedSubagents = await multiselect$1({
4848
+ const selectedSubagents = await multiselect({
4671
4849
  message: "Where should Eve skills be installed?",
4672
4850
  options: [{
4673
4851
  value: "",
@@ -5961,6 +6139,17 @@ async function removeCommand(skillNames, options) {
5961
6139
  options.yes = true;
5962
6140
  log.info(import_picocolors.default.bgCyan(import_picocolors.default.black(import_picocolors.default.bold(` ${agentResult.agent.name} `))) + " Agent detected — removing non-interactively");
5963
6141
  }
6142
+ if (skillNames.includes("*")) {
6143
+ options.all = true;
6144
+ skillNames = skillNames.filter((name) => name !== "*");
6145
+ }
6146
+ const namedSkills = skillNames.filter((name) => name !== "*");
6147
+ if (options.all && namedSkills.length > 0) {
6148
+ log.error("Cannot combine --all with specific skill names.");
6149
+ log.info("Use `skills remove --all` to remove every skill, or omit --all to remove only the named skills.");
6150
+ log.info(`Example: skills remove ${namedSkills[0]} -y`);
6151
+ process.exit(1);
6152
+ }
5964
6153
  const isGlobal = options.global ?? false;
5965
6154
  const cwd = process.cwd();
5966
6155
  const spinner$1 = spinner();
@@ -6013,7 +6202,7 @@ async function removeCommand(skillNames, options) {
6013
6202
  value: s,
6014
6203
  label: s
6015
6204
  }));
6016
- const selected = await multiselect$1({
6205
+ const selected = await multiselect({
6017
6206
  message: `Select skills to remove ${import_picocolors.default.dim("(space to toggle)")}`,
6018
6207
  options: choices,
6019
6208
  required: true
@@ -6148,8 +6337,19 @@ function parseRemoveOptions(args) {
6148
6337
  const arg = args[i];
6149
6338
  if (arg === "-g" || arg === "--global") options.global = true;
6150
6339
  else if (arg === "-y" || arg === "--yes") options.yes = true;
6151
- else if (arg === "--all") options.all = true;
6152
- else if (arg === "-a" || arg === "--agent") {
6340
+ else if (arg === "--all") {
6341
+ options.all = true;
6342
+ options.yes = true;
6343
+ } else if (arg === "-s" || arg === "--skill") {
6344
+ i++;
6345
+ let nextArg = args[i];
6346
+ while (i < args.length && nextArg && !nextArg.startsWith("-")) {
6347
+ skills.push(nextArg);
6348
+ i++;
6349
+ nextArg = args[i];
6350
+ }
6351
+ i--;
6352
+ } else if (arg === "-a" || arg === "--agent") {
6153
6353
  options.agent = options.agent || [];
6154
6354
  i++;
6155
6355
  let nextArg = args[i];
@@ -6486,48 +6686,49 @@ async function updateGlobalSkills(options = {}) {
6486
6686
  failCount += wkFailCount;
6487
6687
  const bySource = /* @__PURE__ */ new Map();
6488
6688
  for (const item of checkable) {
6489
- const source = item.entry.source;
6490
- const existing = bySource.get(source) || [];
6689
+ const key = `${item.entry.source}\n${item.entry.ref ?? ""}`;
6690
+ const existing = bySource.get(key) || [];
6491
6691
  existing.push(item);
6492
- bySource.set(source, existing);
6692
+ bySource.set(key, existing);
6493
6693
  }
6494
- for (const [source, itemsForSource] of bySource) {
6694
+ for (const [, itemsForSource] of bySource) {
6495
6695
  const firstEntry = itemsForSource[0].entry;
6696
+ const source = firstEntry.source;
6496
6697
  const sourceUrl = firstEntry.sourceUrl || firstEntry.source;
6497
6698
  let tempDir = null;
6498
6699
  process.stdout.write(`\r${DIM$1}Checking skills from source: ${source}${RESET$1}\x1b[K\n`);
6499
6700
  try {
6500
- if (firstEntry.sourceType === "github") {
6701
+ const isGitHubSource = firstEntry.sourceType === "github";
6702
+ if (isGitHubSource) {
6501
6703
  const tree = await fetchRepoTree(source, firstEntry.ref, getGitHubToken);
6502
- if (!tree) {
6503
- console.log(` ${DIM$1}✗ Failed to fetch tree for ${source}${RESET$1}`);
6704
+ if (tree) {
6705
+ const discoveredPaths = tree.tree.filter((entry) => entry.type === "blob").map((entry) => entry.path);
6706
+ const deletedSkills = await checkAndPromptForDeletions(source, Object.entries(lock.skills).filter(([_, entry]) => entry.source === source && entry.ref === firstEntry.ref).map(([name, _]) => name), lock.skills, true, options, discoveredPaths);
6707
+ const deletedSkillSet = new Set(deletedSkills);
6708
+ for (const { name: skillName, entry } of itemsForSource) {
6709
+ if (deletedSkillSet.has(skillName)) continue;
6710
+ const latestHash = getSkillFolderHashFromTree(tree, entry.skillPath);
6711
+ if (latestHash && latestHash !== entry.skillFolderHash) updates.push({
6712
+ name: skillName,
6713
+ source,
6714
+ entry
6715
+ });
6716
+ }
6504
6717
  continue;
6505
6718
  }
6506
- const discoveredPaths = tree.tree.filter((entry) => entry.type === "blob").map((entry) => entry.path);
6507
- const deletedSkills = await checkAndPromptForDeletions(source, Object.entries(lock.skills).filter(([_, entry]) => entry.source === source).map(([name, _]) => name), lock.skills, true, options, discoveredPaths);
6508
- const deletedSkillSet = new Set(deletedSkills);
6509
- for (const { name: skillName, entry } of itemsForSource) {
6510
- if (deletedSkillSet.has(skillName)) continue;
6511
- const latestHash = getSkillFolderHashFromTree(tree, entry.skillPath);
6512
- if (latestHash && latestHash !== entry.skillFolderHash) updates.push({
6513
- name: skillName,
6514
- source,
6515
- entry
6516
- });
6517
- }
6518
- continue;
6719
+ console.log(` ${DIM$1}GitHub API unavailable; checking via Git clone${RESET$1}`);
6519
6720
  }
6520
6721
  tempDir = await cloneRepo(sourceUrl, firstEntry.ref);
6521
6722
  const discoveredPaths = (await discoverSkills(tempDir, void 0, { fullDepth: true })).map((skill) => {
6522
6723
  return join(relative(tempDir, skill.path), "SKILL.md").split(sep).join("/");
6523
6724
  });
6524
- const deletedSkills = await checkAndPromptForDeletions(source, Object.entries(lock.skills).filter(([_, entry]) => entry.source === source).map(([name, _]) => name), lock.skills, true, options, discoveredPaths);
6725
+ const deletedSkills = await checkAndPromptForDeletions(source, Object.entries(lock.skills).filter(([_, entry]) => entry.source === source && entry.ref === firstEntry.ref).map(([name, _]) => name), lock.skills, true, options, discoveredPaths);
6525
6726
  const deletedSkillSet = new Set(deletedSkills);
6526
6727
  for (const { name: skillName, entry } of itemsForSource) {
6527
6728
  if (deletedSkillSet.has(skillName)) continue;
6528
6729
  const skillPath = entry.skillPath;
6529
6730
  if (!discoveredPaths.includes(skillPath)) continue;
6530
- const latestHash = await computeSkillFolderHash(join(tempDir, dirname(skillPath)));
6731
+ const latestHash = isGitHubSource && /^[0-9a-f]{40}$/i.test(entry.skillFolderHash) ? await getGitTreeHash(tempDir, skillPath) : await computeSkillFolderHash(join(tempDir, dirname(skillPath)));
6531
6732
  if (latestHash && latestHash !== entry.skillFolderHash) updates.push({
6532
6733
  name: skillName,
6533
6734
  source,
@@ -6686,10 +6887,10 @@ async function updateProjectSkills(options = {}) {
6686
6887
  failCount += wkFailCount;
6687
6888
  const bySource = /* @__PURE__ */ new Map();
6688
6889
  for (const skill of updatable) {
6689
- const source = skill.entry.sourceUrl || skill.entry.source;
6690
- const existing = bySource.get(source) || [];
6890
+ const key = `${skill.entry.sourceUrl || skill.entry.source}\n${skill.entry.ref ?? ""}`;
6891
+ const existing = bySource.get(key) || [];
6691
6892
  existing.push(skill);
6692
- bySource.set(source, existing);
6893
+ bySource.set(key, existing);
6693
6894
  }
6694
6895
  const localLock = await readLocalLock();
6695
6896
  const cliEntry = join(__dirname$1, "..", "bin", "cli.mjs");
@@ -6701,11 +6902,12 @@ async function updateProjectSkills(options = {}) {
6701
6902
  foundCount: projectSkills.length
6702
6903
  };
6703
6904
  }
6704
- for (const [source, skillsForSource] of bySource) {
6905
+ for (const [, skillsForSource] of bySource) {
6705
6906
  const firstEntry = skillsForSource[0].entry;
6907
+ const source = firstEntry.sourceUrl || firstEntry.source;
6706
6908
  const cloneSource = buildLocalCloneSource(firstEntry);
6707
6909
  const ref = firstEntry.ref;
6708
- const allLockedForSource = Object.entries(localLock.skills).filter(([_, entry]) => (entry.sourceUrl || entry.source) === source).map(([name, _]) => name);
6910
+ const allLockedForSource = Object.entries(localLock.skills).filter(([_, entry]) => (entry.sourceUrl || entry.source) === source && entry.ref === ref).map(([name, _]) => name);
6709
6911
  let tempDir = null;
6710
6912
  let deletedSkills = [];
6711
6913
  if (cloneSource === null) {
@@ -6852,7 +7054,6 @@ function parseUseOptions(args) {
6852
7054
  if (!arg) continue;
6853
7055
  if (arg === "--help" || arg === "-h") options.help = true;
6854
7056
  else if (arg === "--full-depth") options.fullDepth = true;
6855
- else if (arg === "--dangerously-accept-openclaw-risks") options.dangerouslyAcceptOpenclawRisks = true;
6856
7057
  else if (arg === "--skill" || arg === "-s") {
6857
7058
  const value = args[i + 1];
6858
7059
  if (!value || value.startsWith("-")) errors.push(`${arg} requires a skill name`);
@@ -6923,16 +7124,14 @@ async function runUse(sourceArgs, options = {}, parseErrors = []) {
6923
7124
  if (useAgent && !USE_AGENT_CONFIGS[useAgent]) fail(formatUnsupportedAgentError(useAgent));
6924
7125
  const source = sourceArgs[0];
6925
7126
  const parsed = parseSource(source);
6926
- if (getOwnerRepo(parsed)?.split("/")[0]?.toLowerCase() === "openclaw" && !options.dangerouslyAcceptOpenclawRisks) fail([
6927
- "OpenClaw skills are unverified community submissions.",
6928
- "Skills run with full agent permissions and could be malicious.",
6929
- `If you understand the risks, re-run with: skills use ${source} --dangerously-accept-openclaw-risks`
6930
- ].join("\n"));
6931
7127
  const selector = resolveSelector(parsed.skillFilter, options.skill);
6932
7128
  const includeInternal = selector !== void 0;
6933
7129
  let selectedSkill;
6934
7130
  if (parsed.type === "well-known") {
6935
- const skills = await wellKnownProvider.fetchAllSkills(parsed.url);
7131
+ const skills = await wellKnownProvider.fetchAllSkills(parsed.url, { includeInternal }).catch((error) => {
7132
+ if (error instanceof WellKnownScopeNotFoundError) fail(error.message);
7133
+ return [];
7134
+ });
6936
7135
  if (skills.length > 0) selectedSkill = selectWellKnownSkill(skills, selector, source);
6937
7136
  else {
6938
7137
  const downloaded = await downloadSource(parsed.url);
@@ -7061,8 +7260,6 @@ Options:
7061
7260
  -s, --skill <skill> Select the skill to use
7062
7261
  -a, --agent <agent> Start one supported agent interactively (${SUPPORTED_USE_AGENTS.join(", ")})
7063
7262
  --full-depth Search nested directories like skills add --full-depth
7064
- --dangerously-accept-openclaw-risks
7065
- Allow unverified OpenClaw community skills
7066
7263
  -h, --help Show this help message
7067
7264
 
7068
7265
  Examples:
@@ -7312,15 +7509,13 @@ ${BOLD}Use Options:${RESET}
7312
7509
  -s, --skill <skill> Specify the skill to use
7313
7510
  -a, --agent <agent> Start one supported agent interactively
7314
7511
  --full-depth Search all subdirectories even when a root SKILL.md exists
7315
- --dangerously-accept-openclaw-risks
7316
- Allow unverified OpenClaw community skills
7317
7512
 
7318
7513
  ${BOLD}Remove Options:${RESET}
7319
7514
  -g, --global Remove from global scope
7320
- -a, --agent <agents> Remove from specific agents (use '*' for all agents)
7515
+ -a, --agent <agents> Remove from specific agents (omit to clean all agent links)
7321
7516
  -s, --skill <skills> Specify skills to remove (use '*' for all skills)
7322
7517
  -y, --yes Skip confirmation prompts
7323
- --all Shorthand for --skill '*' --agent '*' -y
7518
+ --all Remove every installed skill (-y implied). Do not combine with named skills.
7324
7519
 
7325
7520
  ${BOLD}Experimental Sync Options:${RESET}
7326
7521
  -a, --agent <agents> Specify agents to install to (use '*' for all agents)
@@ -7376,10 +7571,10 @@ ${BOLD}Arguments:${RESET}
7376
7571
 
7377
7572
  ${BOLD}Options:${RESET}
7378
7573
  -g, --global Remove from global scope (~/) instead of project scope
7379
- -a, --agent Remove from specific agents (use '*' for all agents)
7574
+ -a, --agent Remove from specific agents (omit to clean all agent links)
7380
7575
  -s, --skill Specify skills to remove (use '*' for all skills)
7381
7576
  -y, --yes Skip confirmation prompts
7382
- --all Shorthand for --skill '*' --agent '*' -y
7577
+ --all Remove every installed skill (-y implied). Do not combine with named skills.
7383
7578
 
7384
7579
  ${BOLD}Examples:${RESET}
7385
7580
  ${DIM}$${RESET} skills remove ${DIM}# interactive selection${RESET}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skills",
3
- "version": "1.5.22",
3
+ "version": "1.5.23",
4
4
  "description": "The open agent skills ecosystem",
5
5
  "type": "module",
6
6
  "bin": {
@@ -89,6 +89,7 @@
89
89
  "openhands",
90
90
  "ona",
91
91
  "pi",
92
+ "posit-assistant",
92
93
  "qoder",
93
94
  "qoder-cn",
94
95
  "qwen-code",