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.
- package/README.md +29 -2
- package/dist/cli.mjs +321 -126
- 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 [
|
|
6
|
+
Supports **OpenCode**, **Claude Code**, **Codex**, **Cursor**, and [73 more](#supported-agents).
|
|
7
7
|
<!-- agent-list:end -->
|
|
8
8
|
|
|
9
9
|
[](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
|
-
|
|
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
|
|
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,
|
|
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
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
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 ===
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
550
|
+
itemLines.push(`${S_BAR} ${import_picocolors.default.dim(parts.join(" "))}`);
|
|
499
551
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
const
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
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 (
|
|
520
|
-
|
|
521
|
-
|
|
587
|
+
if (maxFrameRows && fitted.frameRows > maxFrameRows && includeSelectedSummary) {
|
|
588
|
+
includeSelectedSummary = false;
|
|
589
|
+
fitted = fitFrame(includeDetail, includeSelectedSummary);
|
|
522
590
|
}
|
|
523
|
-
lines.push(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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 (!
|
|
3815
|
+
if (!(rateLimited || authRetryable)) return null;
|
|
3645
3816
|
if (rateLimited) _rateLimitedThisSession = true;
|
|
3646
|
-
return
|
|
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.
|
|
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
|
-
|
|
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
|
|
4294
|
+
const selected = await searchMultiselect({
|
|
4120
4295
|
message: "Select skills to install",
|
|
4121
|
-
|
|
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
|
-
|
|
4127
|
-
required: true
|
|
4301
|
+
initialSelected: isSkillsShPackUrl(url) ? skills : void 0,
|
|
4302
|
+
required: true,
|
|
4303
|
+
maxVisible: 20,
|
|
4304
|
+
selectAll: true
|
|
4128
4305
|
});
|
|
4129
|
-
if (
|
|
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
|
|
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
|
|
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")
|
|
6152
|
-
|
|
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
|
|
6490
|
-
const existing = bySource.get(
|
|
6689
|
+
const key = `${item.entry.source}\n${item.entry.ref ?? ""}`;
|
|
6690
|
+
const existing = bySource.get(key) || [];
|
|
6491
6691
|
existing.push(item);
|
|
6492
|
-
bySource.set(
|
|
6692
|
+
bySource.set(key, existing);
|
|
6493
6693
|
}
|
|
6494
|
-
for (const [
|
|
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
|
-
|
|
6701
|
+
const isGitHubSource = firstEntry.sourceType === "github";
|
|
6702
|
+
if (isGitHubSource) {
|
|
6501
6703
|
const tree = await fetchRepoTree(source, firstEntry.ref, getGitHubToken);
|
|
6502
|
-
if (
|
|
6503
|
-
|
|
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
|
-
|
|
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
|
|
6690
|
-
const existing = bySource.get(
|
|
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(
|
|
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 [
|
|
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 (
|
|
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
|
|
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 (
|
|
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
|
|
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.
|
|
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",
|