skills 1.5.21 → 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 +39 -9
- package/dist/cli.mjs +604 -181
- package/package.json +3 -1
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()));
|
|
@@ -902,27 +1003,45 @@ function getLocalLockPath(cwd) {
|
|
|
902
1003
|
return join(cwd || process.cwd(), LOCAL_LOCK_FILE);
|
|
903
1004
|
}
|
|
904
1005
|
async function readLocalLock(cwd) {
|
|
905
|
-
const
|
|
1006
|
+
const lockDir = cwd || process.cwd();
|
|
1007
|
+
const lockPath = getLocalLockPath(lockDir);
|
|
906
1008
|
try {
|
|
907
1009
|
const content = await readFile(lockPath, "utf-8");
|
|
908
1010
|
const parsed = JSON.parse(content);
|
|
909
1011
|
if (typeof parsed.version !== "number" || !parsed.skills) return createEmptyLocalLock();
|
|
910
1012
|
if (parsed.version < CURRENT_VERSION$1) return createEmptyLocalLock();
|
|
1013
|
+
for (const entry of Object.values(parsed.skills)) if (entry.sourceType === "local" && !isAbsolute(entry.source)) entry.source = resolve(lockDir, entry.source);
|
|
911
1014
|
return parsed;
|
|
912
1015
|
} catch {
|
|
913
1016
|
return createEmptyLocalLock();
|
|
914
1017
|
}
|
|
915
1018
|
}
|
|
916
1019
|
async function writeLocalLock(lock, cwd) {
|
|
917
|
-
const
|
|
1020
|
+
const lockDir = cwd || process.cwd();
|
|
1021
|
+
const lockPath = getLocalLockPath(lockDir);
|
|
918
1022
|
const sortedSkills = {};
|
|
919
|
-
for (const key of Object.keys(lock.skills).sort())
|
|
1023
|
+
for (const key of Object.keys(lock.skills).sort()) {
|
|
1024
|
+
const entry = lock.skills[key];
|
|
1025
|
+
sortedSkills[key] = entry.sourceType === "local" ? {
|
|
1026
|
+
...entry,
|
|
1027
|
+
source: getPortableLocalSource(entry.source, lockDir)
|
|
1028
|
+
} : entry;
|
|
1029
|
+
}
|
|
920
1030
|
const sorted = {
|
|
921
1031
|
version: lock.version,
|
|
922
1032
|
skills: sortedSkills
|
|
923
1033
|
};
|
|
924
1034
|
await writeFile(lockPath, JSON.stringify(sorted, null, 2) + "\n", "utf-8");
|
|
925
1035
|
}
|
|
1036
|
+
function getPortableLocalSource(source, lockDir) {
|
|
1037
|
+
const absoluteSource = isAbsolute(source) ? source : resolve(lockDir, source);
|
|
1038
|
+
const relativeSource = relative(lockDir, absoluteSource);
|
|
1039
|
+
if (isAbsolute(relativeSource)) return absoluteSource.split(sep).join("/");
|
|
1040
|
+
const portableSource = relativeSource.split(sep).join("/");
|
|
1041
|
+
if (!portableSource) return ".";
|
|
1042
|
+
if (portableSource === ".." || portableSource.startsWith("../")) return portableSource;
|
|
1043
|
+
return `./${portableSource}`;
|
|
1044
|
+
}
|
|
926
1045
|
async function computeSkillFolderHash(skillDir) {
|
|
927
1046
|
const files = [];
|
|
928
1047
|
await collectFiles(skillDir, skillDir, files);
|
|
@@ -969,6 +1088,8 @@ function createEmptyLocalLock() {
|
|
|
969
1088
|
skills: {}
|
|
970
1089
|
};
|
|
971
1090
|
}
|
|
1091
|
+
const AGENTS_DIR$1 = ".agents";
|
|
1092
|
+
const SKILLS_SUBDIR = "skills";
|
|
972
1093
|
const SKIP_DIRS = [
|
|
973
1094
|
"node_modules",
|
|
974
1095
|
".git",
|
|
@@ -992,11 +1113,13 @@ const AGENT_PROJECT_SKILL_DIRS = [
|
|
|
992
1113
|
".kilocode/skills",
|
|
993
1114
|
".kimchi/skills",
|
|
994
1115
|
".kiro/skills",
|
|
1116
|
+
".minimax/skills",
|
|
995
1117
|
".mux/skills",
|
|
996
1118
|
".neovate/skills",
|
|
997
1119
|
".opencode/skills",
|
|
998
1120
|
".openhands/skills",
|
|
999
1121
|
".pi/skills",
|
|
1122
|
+
".posit/assistant/skills",
|
|
1000
1123
|
".qoder/skills",
|
|
1001
1124
|
".roo/skills",
|
|
1002
1125
|
".trae/skills",
|
|
@@ -1138,25 +1261,18 @@ async function discoverSkills(basePath, subpath, options) {
|
|
|
1138
1261
|
seenNames.add(skill.name);
|
|
1139
1262
|
return true;
|
|
1140
1263
|
};
|
|
1141
|
-
|
|
1142
|
-
const walkDeep = deepContainerDirs.has(dir);
|
|
1264
|
+
const walkSkillDirs = async (dir, maxDepth, depth = 1) => {
|
|
1143
1265
|
try {
|
|
1144
1266
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
1145
1267
|
for (const entry of entries) {
|
|
1146
1268
|
if (!entry.isDirectory()) continue;
|
|
1147
1269
|
const childDir = join(dir, entry.name);
|
|
1148
|
-
if (await tryAddSkillAt(childDir) ||
|
|
1149
|
-
|
|
1150
|
-
try {
|
|
1151
|
-
const grandEntries = await readdir(childDir, { withFileTypes: true });
|
|
1152
|
-
for (const grand of grandEntries) {
|
|
1153
|
-
if (!grand.isDirectory() || SKIP_DIRS.includes(grand.name)) continue;
|
|
1154
|
-
await tryAddSkillAt(join(childDir, grand.name));
|
|
1155
|
-
}
|
|
1156
|
-
} catch {}
|
|
1270
|
+
if (await tryAddSkillAt(childDir) || depth >= maxDepth || SKIP_DIRS.includes(entry.name)) continue;
|
|
1271
|
+
await walkSkillDirs(childDir, maxDepth, depth + 1);
|
|
1157
1272
|
}
|
|
1158
1273
|
} catch {}
|
|
1159
|
-
}
|
|
1274
|
+
};
|
|
1275
|
+
for (const dir of prioritySearchDirs) await walkSkillDirs(dir, deepContainerDirs.has(dir) ? 3 : 1);
|
|
1160
1276
|
if (skills.length === 0 || options?.fullDepth) {
|
|
1161
1277
|
const allSkillDirs = await findSkillDirs(searchPath);
|
|
1162
1278
|
for (const skillDir of allSkillDirs) {
|
|
@@ -1211,6 +1327,12 @@ function isZCodeInstalled(homeDir = home, pathExists = existsSync) {
|
|
|
1211
1327
|
function isKimchiInstalled(homeDir = home, pathExists = existsSync) {
|
|
1212
1328
|
return pathExists(join(homeDir, ".config", "kimchi"));
|
|
1213
1329
|
}
|
|
1330
|
+
function isMiniMaxCodeInstalled(homeDir = home, pathExists = existsSync) {
|
|
1331
|
+
return pathExists(join(homeDir, ".minimax")) || pathExists("/Applications/MiniMax Code.app");
|
|
1332
|
+
}
|
|
1333
|
+
function isPositAssistantInstalled(homeDir = home, pathExists = existsSync) {
|
|
1334
|
+
return pathExists(join(homeDir, ".posit/assistant")) || pathExists(join(homeDir, ".positai"));
|
|
1335
|
+
}
|
|
1214
1336
|
const agents = {
|
|
1215
1337
|
"aider-desk": {
|
|
1216
1338
|
name: "aider-desk",
|
|
@@ -1621,6 +1743,15 @@ const agents = {
|
|
|
1621
1743
|
return existsSync(join(home, ".mcpjam"));
|
|
1622
1744
|
}
|
|
1623
1745
|
},
|
|
1746
|
+
"minimax-code": {
|
|
1747
|
+
name: "minimax-code",
|
|
1748
|
+
displayName: "MiniMax Code",
|
|
1749
|
+
skillsDir: ".minimax/skills",
|
|
1750
|
+
globalSkillsDir: join(home, ".minimax/skills"),
|
|
1751
|
+
detectInstalled: async () => {
|
|
1752
|
+
return isMiniMaxCodeInstalled();
|
|
1753
|
+
}
|
|
1754
|
+
},
|
|
1624
1755
|
"mistral-vibe": {
|
|
1625
1756
|
name: "mistral-vibe",
|
|
1626
1757
|
displayName: "Mistral Vibe",
|
|
@@ -1684,6 +1815,15 @@ const agents = {
|
|
|
1684
1815
|
return existsSync(join(home, ".pi/agent"));
|
|
1685
1816
|
}
|
|
1686
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
|
+
},
|
|
1687
1827
|
qoder: {
|
|
1688
1828
|
name: "qoder",
|
|
1689
1829
|
displayName: "Qoder",
|
|
@@ -1921,8 +2061,6 @@ function getNonUniversalAgents() {
|
|
|
1921
2061
|
function isUniversalAgent(type) {
|
|
1922
2062
|
return agents[type].skillsDir === ".agents/skills";
|
|
1923
2063
|
}
|
|
1924
|
-
const AGENTS_DIR$1 = ".agents";
|
|
1925
|
-
const SKILLS_SUBDIR = "skills";
|
|
1926
2064
|
function sanitizeName(name) {
|
|
1927
2065
|
return name.toLowerCase().replace(/[^a-z0-9._]+/g, "-").replace(/^[.\-]+|[.\-]+$/g, "").substring(0, 255) || "unnamed-skill";
|
|
1928
2066
|
}
|
|
@@ -2543,6 +2681,7 @@ function setVersion(version) {
|
|
|
2543
2681
|
cliVersion = version;
|
|
2544
2682
|
}
|
|
2545
2683
|
async function fetchAuditData(source, skillSlugs, timeoutMs = 3e3) {
|
|
2684
|
+
if (!isEnabled()) return null;
|
|
2546
2685
|
if (skillSlugs.length === 0) return null;
|
|
2547
2686
|
try {
|
|
2548
2687
|
const params = new URLSearchParams({
|
|
@@ -2851,6 +2990,16 @@ const DISCOVERY_SCHEMA_V2 = "https://schemas.agentskills.io/discovery/0.2.0/sche
|
|
|
2851
2990
|
const MAX_ARCHIVE_UNPACKED_BYTES = 50 * 1024 * 1024;
|
|
2852
2991
|
const MAX_ARCHIVE_FILES = 1e3;
|
|
2853
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
|
+
};
|
|
2854
3003
|
var WellKnownProvider = class {
|
|
2855
3004
|
id = "well-known";
|
|
2856
3005
|
displayName = "Well-Known Skills";
|
|
@@ -2873,10 +3022,10 @@ var WellKnownProvider = class {
|
|
|
2873
3022
|
return { matches: false };
|
|
2874
3023
|
}
|
|
2875
3024
|
}
|
|
2876
|
-
async fetchIndex(baseUrl) {
|
|
2877
|
-
return (await this.fetchIndexCandidates(baseUrl))[0] ?? null;
|
|
3025
|
+
async fetchIndex(baseUrl, options) {
|
|
3026
|
+
return (await this.fetchIndexCandidates(baseUrl, options))[0] ?? null;
|
|
2878
3027
|
}
|
|
2879
|
-
async fetchIndexCandidates(baseUrl) {
|
|
3028
|
+
async fetchIndexCandidates(baseUrl, options) {
|
|
2880
3029
|
try {
|
|
2881
3030
|
const parsed = new URL(baseUrl);
|
|
2882
3031
|
const basePath = parsed.pathname.replace(/\/$/, "");
|
|
@@ -2896,7 +3045,10 @@ var WellKnownProvider = class {
|
|
|
2896
3045
|
}
|
|
2897
3046
|
const candidates = [];
|
|
2898
3047
|
for (const { indexUrl, baseUrl: resolvedBase, wellKnownPath } of urlsToTry) try {
|
|
2899
|
-
const response = await fetch(indexUrl, {
|
|
3048
|
+
const response = await fetch(indexUrl, {
|
|
3049
|
+
signal,
|
|
3050
|
+
...options?.updateCheck ? { headers: { "X-Skills-Update-Check": "1" } } : {}
|
|
3051
|
+
});
|
|
2900
3052
|
if (!response.ok) continue;
|
|
2901
3053
|
const rawIndex = await response.json();
|
|
2902
3054
|
const normalized = this.normalizeIndex(rawIndex, indexUrl, wellKnownPath);
|
|
@@ -3146,16 +3298,34 @@ var WellKnownProvider = class {
|
|
|
3146
3298
|
indexEntry: input.indexEntry
|
|
3147
3299
|
};
|
|
3148
3300
|
}
|
|
3149
|
-
|
|
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 = {}) {
|
|
3150
3315
|
try {
|
|
3151
3316
|
const candidates = await this.fetchIndexCandidates(url);
|
|
3152
|
-
|
|
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) {
|
|
3153
3321
|
const skillPromises = result.entries.map((entry) => this.fetchSkillByEntry(entry));
|
|
3154
|
-
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);
|
|
3155
3323
|
if (skills.length > 0) return skills;
|
|
3156
3324
|
}
|
|
3325
|
+
if (scope && scopedCandidates.length < candidates.length) throw new WellKnownScopeNotFoundError(scope.scopePath, scope.rootBaseUrl);
|
|
3157
3326
|
return [];
|
|
3158
|
-
} catch {
|
|
3327
|
+
} catch (error) {
|
|
3328
|
+
if (error instanceof WellKnownScopeNotFoundError) throw error;
|
|
3159
3329
|
return [];
|
|
3160
3330
|
}
|
|
3161
3331
|
}
|
|
@@ -3253,6 +3423,17 @@ var WellKnownProvider = class {
|
|
|
3253
3423
|
return await this.fetchIndex(url) !== null;
|
|
3254
3424
|
}
|
|
3255
3425
|
};
|
|
3426
|
+
function computeWellKnownSkillDigest(skill) {
|
|
3427
|
+
if ("digest" in skill.indexEntry && skill.indexEntry.digest) return skill.indexEntry.digest;
|
|
3428
|
+
const hash = createHash$1("sha256");
|
|
3429
|
+
for (const path of Array.from(skill.files.keys()).sort()) {
|
|
3430
|
+
hash.update(path);
|
|
3431
|
+
hash.update("\0");
|
|
3432
|
+
hash.update(skill.files.get(path));
|
|
3433
|
+
hash.update("\0");
|
|
3434
|
+
}
|
|
3435
|
+
return `sha256:${hash.digest("hex")}`;
|
|
3436
|
+
}
|
|
3256
3437
|
const wellKnownProvider = new WellKnownProvider();
|
|
3257
3438
|
const DEFAULT_DOWNLOAD_MAX_BYTES = 10 * 1024 * 1024;
|
|
3258
3439
|
const DEFAULT_EXTRACT_MAX_BYTES = 25 * 1024 * 1024;
|
|
@@ -3460,25 +3641,9 @@ async function writeSkillLock(lock) {
|
|
|
3460
3641
|
await mkdir(dirname(lockPath), { recursive: true });
|
|
3461
3642
|
await writeFile(lockPath, JSON.stringify(lock, null, 2), "utf-8");
|
|
3462
3643
|
}
|
|
3463
|
-
let _ghWarningShown = false;
|
|
3464
3644
|
function getGitHubToken() {
|
|
3465
3645
|
if (process.env.GITHUB_TOKEN) return process.env.GITHUB_TOKEN;
|
|
3466
3646
|
if (process.env.GH_TOKEN) return process.env.GH_TOKEN;
|
|
3467
|
-
if (!_ghWarningShown) {
|
|
3468
|
-
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")}`);
|
|
3469
|
-
_ghWarningShown = true;
|
|
3470
|
-
}
|
|
3471
|
-
try {
|
|
3472
|
-
const token = execSync("gh auth token", {
|
|
3473
|
-
encoding: "utf-8",
|
|
3474
|
-
stdio: [
|
|
3475
|
-
"pipe",
|
|
3476
|
-
"pipe",
|
|
3477
|
-
"pipe"
|
|
3478
|
-
]
|
|
3479
|
-
}).trim();
|
|
3480
|
-
if (token) return token;
|
|
3481
|
-
} catch {}
|
|
3482
3647
|
return null;
|
|
3483
3648
|
}
|
|
3484
3649
|
async function addSkillToLock(skillName, entry) {
|
|
@@ -3532,13 +3697,15 @@ async function saveSelectedAgents(agents) {
|
|
|
3532
3697
|
const DOWNLOAD_BASE_URL = process.env.SKILLS_DOWNLOAD_URL || "https://skills.sh";
|
|
3533
3698
|
const BLOB_ALLOWED_REPOS = { "zapier/connectors": { downloadUrl: (slug) => `https://connectors-skills.zapier.com/download/${encodeURIComponent(slug)}/snapshot.json` } };
|
|
3534
3699
|
const FETCH_TIMEOUT = 1e4;
|
|
3700
|
+
const GH_API_MAX_BUFFER = 16 * 1024 * 1024;
|
|
3535
3701
|
function toSkillSlug(name) {
|
|
3536
3702
|
return name.toLowerCase().replace(/[\s_]+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
3537
3703
|
}
|
|
3538
3704
|
let _rateLimitedThisSession = false;
|
|
3539
3705
|
async function fetchTreeBranch(ownerRepo, branch, token) {
|
|
3540
3706
|
try {
|
|
3541
|
-
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`;
|
|
3542
3709
|
const headers = {
|
|
3543
3710
|
Accept: "application/vnd.github.v3+json",
|
|
3544
3711
|
"User-Agent": "skills-cli"
|
|
@@ -3582,13 +3749,55 @@ async function fetchTreeWithToken(ownerRepo, branches, getToken) {
|
|
|
3582
3749
|
}
|
|
3583
3750
|
return null;
|
|
3584
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
|
+
}
|
|
3585
3794
|
async function fetchRepoTree(ownerRepo, ref, getToken) {
|
|
3586
3795
|
const branches = ref ? [ref] : [
|
|
3587
3796
|
"HEAD",
|
|
3588
3797
|
"main",
|
|
3589
3798
|
"master"
|
|
3590
3799
|
];
|
|
3591
|
-
if (_rateLimitedThisSession
|
|
3800
|
+
if (_rateLimitedThisSession) return fetchTreeWithAvailableAuth(ownerRepo, branches, getToken);
|
|
3592
3801
|
let rateLimited = false;
|
|
3593
3802
|
let authRetryable = false;
|
|
3594
3803
|
for (const branch of branches) {
|
|
@@ -3603,9 +3812,9 @@ async function fetchRepoTree(ownerRepo, ref, getToken) {
|
|
|
3603
3812
|
break;
|
|
3604
3813
|
}
|
|
3605
3814
|
}
|
|
3606
|
-
if (!
|
|
3815
|
+
if (!(rateLimited || authRetryable)) return null;
|
|
3607
3816
|
if (rateLimited) _rateLimitedThisSession = true;
|
|
3608
|
-
return
|
|
3817
|
+
return fetchTreeWithAvailableAuth(ownerRepo, branches, getToken);
|
|
3609
3818
|
}
|
|
3610
3819
|
function getSkillFolderHashFromTree(tree, skillPath) {
|
|
3611
3820
|
let folderPath = skillPath.replace(/\\/g, "/");
|
|
@@ -3636,11 +3845,13 @@ const PRIORITY_PREFIXES = [
|
|
|
3636
3845
|
".kilocode/skills/",
|
|
3637
3846
|
".kimchi/skills/",
|
|
3638
3847
|
".kiro/skills/",
|
|
3848
|
+
".minimax/skills/",
|
|
3639
3849
|
".mux/skills/",
|
|
3640
3850
|
".neovate/skills/",
|
|
3641
3851
|
".opencode/skills/",
|
|
3642
3852
|
".openhands/skills/",
|
|
3643
3853
|
".pi/skills/",
|
|
3854
|
+
".posit/assistant/skills/",
|
|
3644
3855
|
".qoder/skills/",
|
|
3645
3856
|
".roo/skills/",
|
|
3646
3857
|
".trae/skills/",
|
|
@@ -3684,9 +3895,13 @@ function findSkillMdPaths(tree, subpath) {
|
|
|
3684
3895
|
}
|
|
3685
3896
|
continue;
|
|
3686
3897
|
}
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3898
|
+
const skillDirs = parts.slice(0, -1);
|
|
3899
|
+
const hasAncestorSkill = skillDirs.slice(0, -1).some((_, index) => {
|
|
3900
|
+
const ancestorPath = skillDirs.slice(0, index + 1).join("/");
|
|
3901
|
+
return lowerSkillMdSet.has(`${fullPrefix}${ancestorPath}/SKILL.md`.toLowerCase());
|
|
3902
|
+
});
|
|
3903
|
+
if (isContainer && parts.length >= 3 && parts.length <= 4 && parts.at(-1).toLowerCase() === "skill.md" && skillDirs.every((part) => !SKIP_DIRS.has(part)) && !hasAncestorSkill) {
|
|
3904
|
+
if (!seen.has(skillMd)) {
|
|
3690
3905
|
priorityResults.push(skillMd);
|
|
3691
3906
|
seen.add(skillMd);
|
|
3692
3907
|
}
|
|
@@ -3729,6 +3944,7 @@ function computeSnapshotHash(files) {
|
|
|
3729
3944
|
return hash.digest("hex");
|
|
3730
3945
|
}
|
|
3731
3946
|
async function tryBlobInstall(ownerRepo, options = {}) {
|
|
3947
|
+
if (options.ref !== void 0) return null;
|
|
3732
3948
|
const tree = await fetchRepoTree(ownerRepo, options.ref, options.getToken);
|
|
3733
3949
|
if (!tree) return null;
|
|
3734
3950
|
let skillMdPaths = findSkillMdPaths(tree, options.subpath);
|
|
@@ -3801,7 +4017,7 @@ async function tryBlobInstall(ownerRepo, options = {}) {
|
|
|
3801
4017
|
tree
|
|
3802
4018
|
};
|
|
3803
4019
|
}
|
|
3804
|
-
var version$1 = "1.5.
|
|
4020
|
+
var version$1 = "1.5.23";
|
|
3805
4021
|
const isCancelled$1 = (value) => typeof value === "symbol";
|
|
3806
4022
|
const EVE_AGENT_LABEL = "eve agent";
|
|
3807
4023
|
async function isSourcePrivate(source) {
|
|
@@ -3954,13 +4170,6 @@ function buildResultLines(results, targetAgents) {
|
|
|
3954
4170
|
if (failedSymlinks.length > 0) lines.push(` ${import_picocolors.default.yellow("copied:")} ${formatList$1(failedSymlinks)}`);
|
|
3955
4171
|
return lines;
|
|
3956
4172
|
}
|
|
3957
|
-
function multiselect(opts) {
|
|
3958
|
-
return multiselect$1({
|
|
3959
|
-
...opts,
|
|
3960
|
-
options: opts.options,
|
|
3961
|
-
message: `${opts.message} ${import_picocolors.default.dim("(space to toggle)")}`
|
|
3962
|
-
});
|
|
3963
|
-
}
|
|
3964
4173
|
async function promptForAgents(message, choices) {
|
|
3965
4174
|
let lastSelected;
|
|
3966
4175
|
try {
|
|
@@ -4020,9 +4229,26 @@ async function selectAgentsInteractive(options) {
|
|
|
4020
4229
|
return selected;
|
|
4021
4230
|
}
|
|
4022
4231
|
setVersion(version$1);
|
|
4232
|
+
function isSkillsShPackUrl(url) {
|
|
4233
|
+
try {
|
|
4234
|
+
const parsed = new URL(url);
|
|
4235
|
+
return parsed.hostname.replace(/^www\./, "") === "skills.sh" && /^\/p\/[^/]+/.test(parsed.pathname);
|
|
4236
|
+
} catch {
|
|
4237
|
+
return false;
|
|
4238
|
+
}
|
|
4239
|
+
}
|
|
4023
4240
|
async function handleWellKnownSkills(source, url, options, spinner) {
|
|
4024
4241
|
spinner.start("Discovering skills from well-known endpoint...");
|
|
4025
|
-
|
|
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
|
+
}
|
|
4026
4252
|
if (skills.length === 0) {
|
|
4027
4253
|
spinner.stop(import_picocolors.default.dim("No well-known skills found; trying direct download..."));
|
|
4028
4254
|
return false;
|
|
@@ -4065,16 +4291,19 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
4065
4291
|
selectedSkills = skills;
|
|
4066
4292
|
log.info(`Installing all ${skills.length} skills`);
|
|
4067
4293
|
} else {
|
|
4068
|
-
const selected = await
|
|
4294
|
+
const selected = await searchMultiselect({
|
|
4069
4295
|
message: "Select skills to install",
|
|
4070
|
-
|
|
4296
|
+
items: skills.map((s) => ({
|
|
4071
4297
|
value: s,
|
|
4072
4298
|
label: s.installName,
|
|
4073
4299
|
hint: s.description.length > 60 ? s.description.slice(0, 57) + "…" : s.description
|
|
4074
4300
|
})),
|
|
4075
|
-
|
|
4301
|
+
initialSelected: isSkillsShPackUrl(url) ? skills : void 0,
|
|
4302
|
+
required: true,
|
|
4303
|
+
maxVisible: 20,
|
|
4304
|
+
selectAll: true
|
|
4076
4305
|
});
|
|
4077
|
-
if (
|
|
4306
|
+
if (isCancelled$1(selected)) {
|
|
4078
4307
|
cancel("Installation cancelled");
|
|
4079
4308
|
process.exit(0);
|
|
4080
4309
|
}
|
|
@@ -4241,7 +4470,9 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
4241
4470
|
source: sourceIdentifier,
|
|
4242
4471
|
sourceType: "well-known",
|
|
4243
4472
|
sourceUrl: skill.sourceUrl,
|
|
4244
|
-
skillFolderHash: ""
|
|
4473
|
+
skillFolderHash: "",
|
|
4474
|
+
sourceBaseUrl: url,
|
|
4475
|
+
wellKnownDigest: computeWellKnownSkillDigest(skill)
|
|
4245
4476
|
});
|
|
4246
4477
|
} catch {}
|
|
4247
4478
|
}
|
|
@@ -4254,8 +4485,10 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
4254
4485
|
const computedHash = await computeSkillFolderHash(installDir);
|
|
4255
4486
|
await addSkillToLocalLock(skill.installName, {
|
|
4256
4487
|
source: sourceIdentifier,
|
|
4488
|
+
sourceUrl: url,
|
|
4257
4489
|
sourceType: "well-known",
|
|
4258
|
-
computedHash
|
|
4490
|
+
computedHash,
|
|
4491
|
+
wellKnownDigest: computeWellKnownSkillDigest(skill)
|
|
4259
4492
|
}, cwd);
|
|
4260
4493
|
}
|
|
4261
4494
|
} catch {}
|
|
@@ -4368,7 +4601,7 @@ async function runAdd(args, options = {}) {
|
|
|
4368
4601
|
options.skill = options.skill || [];
|
|
4369
4602
|
if (!options.skill.includes(parsed.skillFilter)) options.skill.push(parsed.skillFilter);
|
|
4370
4603
|
}
|
|
4371
|
-
const includeInternal = !!(options.skill && options.skill.length > 0);
|
|
4604
|
+
const includeInternal = !!(options.skill && options.skill.length > 0 && !options.skill.includes("*"));
|
|
4372
4605
|
let skills;
|
|
4373
4606
|
let blobResult = null;
|
|
4374
4607
|
if (parsed.type === "local") {
|
|
@@ -4521,7 +4754,8 @@ async function runAdd(args, options = {}) {
|
|
|
4521
4754
|
searchable: !hasGroups,
|
|
4522
4755
|
showDetail: true,
|
|
4523
4756
|
showSelectedSummary: false,
|
|
4524
|
-
selectGroups: hasGroups
|
|
4757
|
+
selectGroups: hasGroups,
|
|
4758
|
+
selectAll: true
|
|
4525
4759
|
});
|
|
4526
4760
|
if (isCancelled$1(selected)) {
|
|
4527
4761
|
cancel("Installation cancelled");
|
|
@@ -4531,7 +4765,7 @@ async function runAdd(args, options = {}) {
|
|
|
4531
4765
|
selectedSkills = selected;
|
|
4532
4766
|
}
|
|
4533
4767
|
const ownerRepoForAudit = getOwnerRepo(parsed);
|
|
4534
|
-
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);
|
|
4535
4769
|
let targetAgents;
|
|
4536
4770
|
const validAgents = Object.keys(agents);
|
|
4537
4771
|
if (options.agent?.includes("*")) {
|
|
@@ -4611,7 +4845,7 @@ async function runAdd(args, options = {}) {
|
|
|
4611
4845
|
const availableSubagents = getEveSubagents(process.cwd());
|
|
4612
4846
|
if (options.subagent && options.subagent.length > 0) eveSubagentTargets = options.subagent.map((s) => s === "root" || s === "." ? void 0 : s);
|
|
4613
4847
|
else if (availableSubagents.length > 0 && !options.yes) {
|
|
4614
|
-
const selectedSubagents = await multiselect
|
|
4848
|
+
const selectedSubagents = await multiselect({
|
|
4615
4849
|
message: "Where should Eve skills be installed?",
|
|
4616
4850
|
options: [{
|
|
4617
4851
|
value: "",
|
|
@@ -5051,6 +5285,7 @@ const DIM$3 = "\x1B[38;5;102m";
|
|
|
5051
5285
|
const TEXT$2 = "\x1B[38;5;145m";
|
|
5052
5286
|
const CYAN$1 = "\x1B[36m";
|
|
5053
5287
|
const SEARCH_API_BASE = process.env.SKILLS_API_URL || "https://skills.sh";
|
|
5288
|
+
const SEARCH_RESULT_LIMIT = "20";
|
|
5054
5289
|
function formatInstalls(count) {
|
|
5055
5290
|
if (!count || count <= 0) return "";
|
|
5056
5291
|
if (count >= 1e6) return `${(count / 1e6).toFixed(1).replace(/\.0$/, "")}M installs`;
|
|
@@ -5101,7 +5336,7 @@ async function searchSkillsAPI(query, owner) {
|
|
|
5101
5336
|
try {
|
|
5102
5337
|
const params = new URLSearchParams({
|
|
5103
5338
|
q: query,
|
|
5104
|
-
limit:
|
|
5339
|
+
limit: SEARCH_RESULT_LIMIT
|
|
5105
5340
|
});
|
|
5106
5341
|
if (owner) params.set("owner", owner);
|
|
5107
5342
|
const url = `${SEARCH_API_BASE}/api/search?${params.toString()}`;
|
|
@@ -5277,7 +5512,7 @@ ${DIM$3} 2) npx skills add <owner/repo@skill>${RESET$3}`;
|
|
|
5277
5512
|
}
|
|
5278
5513
|
console.log(`${DIM$3}Install with${RESET$3} npx skills add <owner/repo@skill>`);
|
|
5279
5514
|
console.log();
|
|
5280
|
-
for (const skill of results
|
|
5515
|
+
for (const skill of results) {
|
|
5281
5516
|
const pkg = skill.source || skill.slug;
|
|
5282
5517
|
const installs = formatInstalls(skill.installs);
|
|
5283
5518
|
console.log(`${TEXT$2}${pkg}@${skill.name}${RESET$3}${installs ? ` ${CYAN$1}${installs}${RESET$3}` : ""}`);
|
|
@@ -5653,6 +5888,12 @@ function getLocalSource(entry) {
|
|
|
5653
5888
|
if ((entry.sourceType === "git" || entry.sourceType === "gitlab") && isBareShorthand(entry.source)) return null;
|
|
5654
5889
|
return entry.source;
|
|
5655
5890
|
}
|
|
5891
|
+
function buildLocalCloneSource(entry) {
|
|
5892
|
+
const source = getLocalSource(entry);
|
|
5893
|
+
if (!source) return null;
|
|
5894
|
+
if (entry.sourceType === "github" && isBareShorthand(source)) return `https://github.com/${source.replace(/\.git$/, "")}.git`;
|
|
5895
|
+
return source;
|
|
5896
|
+
}
|
|
5656
5897
|
function shouldUseFullDepthForUpdate(entry) {
|
|
5657
5898
|
if (!entry.skillPath) return false;
|
|
5658
5899
|
const source = entry.sourceType && entry.sourceType !== "github" ? getLocalSource(entry) : entry.source;
|
|
@@ -5898,6 +6139,17 @@ async function removeCommand(skillNames, options) {
|
|
|
5898
6139
|
options.yes = true;
|
|
5899
6140
|
log.info(import_picocolors.default.bgCyan(import_picocolors.default.black(import_picocolors.default.bold(` ${agentResult.agent.name} `))) + " Agent detected — removing non-interactively");
|
|
5900
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
|
+
}
|
|
5901
6153
|
const isGlobal = options.global ?? false;
|
|
5902
6154
|
const cwd = process.cwd();
|
|
5903
6155
|
const spinner$1 = spinner();
|
|
@@ -5950,7 +6202,7 @@ async function removeCommand(skillNames, options) {
|
|
|
5950
6202
|
value: s,
|
|
5951
6203
|
label: s
|
|
5952
6204
|
}));
|
|
5953
|
-
const selected = await multiselect
|
|
6205
|
+
const selected = await multiselect({
|
|
5954
6206
|
message: `Select skills to remove ${import_picocolors.default.dim("(space to toggle)")}`,
|
|
5955
6207
|
options: choices,
|
|
5956
6208
|
required: true
|
|
@@ -6029,12 +6281,12 @@ async function removeCommand(skillNames, options) {
|
|
|
6029
6281
|
const lockEntry = await getSkillFromLock(skillName);
|
|
6030
6282
|
effectiveSource = lockEntry?.source || "local";
|
|
6031
6283
|
effectiveSourceType = lockEntry?.sourceType || "local";
|
|
6032
|
-
await removeSkillFromLock(skillName);
|
|
6284
|
+
if (!isStillUsed) await removeSkillFromLock(skillName);
|
|
6033
6285
|
} else {
|
|
6034
6286
|
const lockEntry = (await readLocalLock(cwd)).skills[skillName];
|
|
6035
6287
|
effectiveSource = lockEntry?.source || "local";
|
|
6036
6288
|
effectiveSourceType = lockEntry?.sourceType || "local";
|
|
6037
|
-
await removeSkillFromLocalLock(skillName, cwd);
|
|
6289
|
+
if (!isStillUsed) await removeSkillFromLocalLock(skillName, cwd);
|
|
6038
6290
|
}
|
|
6039
6291
|
results.push({
|
|
6040
6292
|
skill: skillName,
|
|
@@ -6085,8 +6337,19 @@ function parseRemoveOptions(args) {
|
|
|
6085
6337
|
const arg = args[i];
|
|
6086
6338
|
if (arg === "-g" || arg === "--global") options.global = true;
|
|
6087
6339
|
else if (arg === "-y" || arg === "--yes") options.yes = true;
|
|
6088
|
-
else if (arg === "--all")
|
|
6089
|
-
|
|
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") {
|
|
6090
6353
|
options.agent = options.agent || [];
|
|
6091
6354
|
i++;
|
|
6092
6355
|
let nextArg = args[i];
|
|
@@ -6108,6 +6371,13 @@ const RESET$1 = "\x1B[0m";
|
|
|
6108
6371
|
const BOLD$1 = "\x1B[1m";
|
|
6109
6372
|
const DIM$1 = "\x1B[38;5;102m";
|
|
6110
6373
|
const TEXT$1 = "\x1B[38;5;145m";
|
|
6374
|
+
function getUpdateChildEnv(sourceType) {
|
|
6375
|
+
if (sourceType !== "github") return;
|
|
6376
|
+
return {
|
|
6377
|
+
...process.env,
|
|
6378
|
+
GH_HOST: "github.com"
|
|
6379
|
+
};
|
|
6380
|
+
}
|
|
6111
6381
|
function parseUpdateOptions(args) {
|
|
6112
6382
|
const options = {};
|
|
6113
6383
|
const positional = [];
|
|
@@ -6224,29 +6494,143 @@ async function getProjectSkillsForUpdate(skillFilter) {
|
|
|
6224
6494
|
}
|
|
6225
6495
|
return skills;
|
|
6226
6496
|
}
|
|
6497
|
+
async function promptDeletions(source, deletedSkills, isGlobal, options) {
|
|
6498
|
+
if (deletedSkills.length === 0) return;
|
|
6499
|
+
console.log();
|
|
6500
|
+
console.log(`${DIM$1}Warning:${RESET$1} The following skills from ${DIM$1}${source}${RESET$1} appear to have been deleted upstream:`);
|
|
6501
|
+
for (const s of deletedSkills) console.log(` ${DIM$1}•${RESET$1} ${s}`);
|
|
6502
|
+
if (options.yes || !process.stdin.isTTY) {
|
|
6503
|
+
console.log(`${DIM$1}Skipping deletion in non-interactive mode.${RESET$1}`);
|
|
6504
|
+
return;
|
|
6505
|
+
}
|
|
6506
|
+
const confirmed = await confirm({ message: `Would you like to remove the local copies of these deleted skills?` });
|
|
6507
|
+
if (confirmed && !isCancel(confirmed)) for (const s of deletedSkills) {
|
|
6508
|
+
console.log(`${DIM$1}Removing${RESET$1} ${s}…`);
|
|
6509
|
+
await removeCommand([s], {
|
|
6510
|
+
yes: true,
|
|
6511
|
+
global: isGlobal
|
|
6512
|
+
});
|
|
6513
|
+
}
|
|
6514
|
+
}
|
|
6227
6515
|
async function checkAndPromptForDeletions(source, allLockedForSource, lockSkills, isGlobal, options, discoveredPaths) {
|
|
6228
6516
|
const deletedSkills = allLockedForSource.filter((name) => {
|
|
6229
6517
|
const entry = lockSkills[name];
|
|
6230
6518
|
if (!entry?.skillPath) return false;
|
|
6231
6519
|
return !discoveredPaths.includes(entry.skillPath);
|
|
6232
6520
|
});
|
|
6233
|
-
|
|
6234
|
-
|
|
6235
|
-
|
|
6236
|
-
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
|
|
6240
|
-
|
|
6241
|
-
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
|
-
|
|
6245
|
-
|
|
6521
|
+
await promptDeletions(source, deletedSkills, isGlobal, options);
|
|
6522
|
+
return deletedSkills;
|
|
6523
|
+
}
|
|
6524
|
+
async function checkWellKnownForUpdates(baseUrl, items) {
|
|
6525
|
+
let indexResult;
|
|
6526
|
+
try {
|
|
6527
|
+
indexResult = await wellKnownProvider.fetchIndex(baseUrl, { updateCheck: true });
|
|
6528
|
+
} catch {
|
|
6529
|
+
return { status: "error" };
|
|
6530
|
+
}
|
|
6531
|
+
if (!indexResult) return { status: "error" };
|
|
6532
|
+
const byName = new Map(indexResult.entries.map((entry) => [entry.name, entry]));
|
|
6533
|
+
const removedSkills = items.filter((item) => !byName.has(item.name)).map((item) => item.name);
|
|
6534
|
+
const localNames = new Set(items.map((item) => item.name));
|
|
6535
|
+
const newSkills = indexResult.entries.map((entry) => entry.name).filter((name) => !localNames.has(name));
|
|
6536
|
+
const changedSkills = [];
|
|
6537
|
+
const needsContentCheck = [];
|
|
6538
|
+
for (const item of items) {
|
|
6539
|
+
const entry = byName.get(item.name);
|
|
6540
|
+
if (!entry) continue;
|
|
6541
|
+
if (entry.version === "0.2.0") {
|
|
6542
|
+
if (!item.digest || entry.digest !== item.digest) changedSkills.push(item.name);
|
|
6543
|
+
} else needsContentCheck.push(item);
|
|
6544
|
+
}
|
|
6545
|
+
if (needsContentCheck.length > 0) {
|
|
6546
|
+
const tracked = new Set(needsContentCheck.map((item) => item.name));
|
|
6547
|
+
const skills = (await Promise.all(indexResult.entries.filter((entry) => tracked.has(entry.name)).map((entry) => wellKnownProvider.fetchSkillByEntry(entry).catch(() => null)))).filter((skill) => skill !== null);
|
|
6548
|
+
if (skills.length === 0) return { status: "error" };
|
|
6549
|
+
const digests = new Map(skills.map((skill) => [skill.installName, computeWellKnownSkillDigest(skill)]));
|
|
6550
|
+
for (const item of needsContentCheck) {
|
|
6551
|
+
const digest = digests.get(item.name);
|
|
6552
|
+
if (!digest || !item.digest || digest !== item.digest) changedSkills.push(item.name);
|
|
6553
|
+
}
|
|
6554
|
+
}
|
|
6555
|
+
if (changedSkills.length === 0 && removedSkills.length === 0) return {
|
|
6556
|
+
status: "current",
|
|
6557
|
+
newSkills
|
|
6558
|
+
};
|
|
6559
|
+
return {
|
|
6560
|
+
status: "changed",
|
|
6561
|
+
changedSkills,
|
|
6562
|
+
removedSkills,
|
|
6563
|
+
newSkills
|
|
6564
|
+
};
|
|
6565
|
+
}
|
|
6566
|
+
function printNewSkills(baseUrl, newSkills, isGlobal) {
|
|
6567
|
+
if (newSkills.length === 0) return;
|
|
6568
|
+
const names = newSkills.map(sanitizeMetadata);
|
|
6569
|
+
console.log(` ${DIM$1}${newSkills.length} new skill(s) available from this source:${RESET$1} ${names.join(", ")}`);
|
|
6570
|
+
console.log(` ${DIM$1}To install: ${TEXT$1}npx skills add ${baseUrl} --skill ${names.join(" ")}${isGlobal ? " -g" : ""}${RESET$1}`);
|
|
6571
|
+
}
|
|
6572
|
+
async function processWellKnownUpdates(groups, isGlobal, options) {
|
|
6573
|
+
let successCount = 0;
|
|
6574
|
+
let failCount = 0;
|
|
6575
|
+
let changed = false;
|
|
6576
|
+
for (const [baseUrl, items] of groups) {
|
|
6577
|
+
process.stdout.write(`\r${DIM$1}Checking skills from source: ${baseUrl}${RESET$1}\x1b[K\n`);
|
|
6578
|
+
const result = await checkWellKnownForUpdates(baseUrl, items);
|
|
6579
|
+
if (result.status === "error") {
|
|
6580
|
+
console.log(` ${DIM$1}✗ Failed to check skills from ${baseUrl}${RESET$1}`);
|
|
6581
|
+
continue;
|
|
6582
|
+
}
|
|
6583
|
+
if (result.status === "current") {
|
|
6584
|
+
printNewSkills(baseUrl, result.newSkills, isGlobal);
|
|
6585
|
+
continue;
|
|
6586
|
+
}
|
|
6587
|
+
changed = true;
|
|
6588
|
+
await promptDeletions(baseUrl, result.removedSkills, isGlobal, options);
|
|
6589
|
+
printNewSkills(baseUrl, result.newSkills, isGlobal);
|
|
6590
|
+
if (result.changedSkills.length === 0) continue;
|
|
6591
|
+
const cliEntry = join(__dirname$1, "..", "bin", "cli.mjs");
|
|
6592
|
+
if (!existsSync(cliEntry)) {
|
|
6593
|
+
failCount += result.changedSkills.length;
|
|
6594
|
+
console.log(` ${DIM$1}✗ CLI entrypoint not found at ${cliEntry}${RESET$1}`);
|
|
6595
|
+
continue;
|
|
6596
|
+
}
|
|
6597
|
+
const itemByName = new Map(items.map((item) => [item.name, item]));
|
|
6598
|
+
for (const name of result.changedSkills) {
|
|
6599
|
+
const safeName = sanitizeMetadata(name);
|
|
6600
|
+
console.log(`${TEXT$1}Updating ${safeName}…${RESET$1}`);
|
|
6601
|
+
const subagents = itemByName.get(name)?.subagents;
|
|
6602
|
+
const subagentArgs = !isGlobal && subagents?.length ? ["--subagent", ...subagents.map((s) => s === "" ? "root" : s)] : [];
|
|
6603
|
+
if (spawnSync(process.execPath, [
|
|
6604
|
+
cliEntry,
|
|
6605
|
+
"add",
|
|
6606
|
+
baseUrl,
|
|
6607
|
+
"--skill",
|
|
6608
|
+
name,
|
|
6609
|
+
...subagentArgs,
|
|
6610
|
+
...isGlobal ? ["-g"] : [],
|
|
6611
|
+
"-y"
|
|
6612
|
+
], {
|
|
6613
|
+
stdio: [
|
|
6614
|
+
"inherit",
|
|
6615
|
+
"pipe",
|
|
6616
|
+
"pipe"
|
|
6617
|
+
],
|
|
6618
|
+
encoding: "utf-8",
|
|
6619
|
+
shell: false
|
|
6620
|
+
}).status === 0) {
|
|
6621
|
+
successCount++;
|
|
6622
|
+
console.log(` ${TEXT$1}✓${RESET$1} Updated ${safeName}`);
|
|
6623
|
+
} else {
|
|
6624
|
+
failCount++;
|
|
6625
|
+
console.log(` ${DIM$1}✗ Failed to update ${safeName}${RESET$1}`);
|
|
6246
6626
|
}
|
|
6247
6627
|
}
|
|
6248
6628
|
}
|
|
6249
|
-
return
|
|
6629
|
+
return {
|
|
6630
|
+
successCount,
|
|
6631
|
+
failCount,
|
|
6632
|
+
changed
|
|
6633
|
+
};
|
|
6250
6634
|
}
|
|
6251
6635
|
async function updateGlobalSkills(options = {}) {
|
|
6252
6636
|
const lock = await readSkillLock();
|
|
@@ -6267,10 +6651,20 @@ async function updateGlobalSkills(options = {}) {
|
|
|
6267
6651
|
const updates = [];
|
|
6268
6652
|
const skipped = [];
|
|
6269
6653
|
const checkable = [];
|
|
6654
|
+
const wellKnownGroups = /* @__PURE__ */ new Map();
|
|
6270
6655
|
for (const skillName of skillNames) {
|
|
6271
6656
|
if (!matchesSkillFilter(skillName, options.skills)) continue;
|
|
6272
6657
|
const entry = lock.skills[skillName];
|
|
6273
6658
|
if (!entry) continue;
|
|
6659
|
+
if (entry.sourceType === "well-known" && entry.sourceBaseUrl && entry.wellKnownDigest) {
|
|
6660
|
+
const group = wellKnownGroups.get(entry.sourceBaseUrl) || [];
|
|
6661
|
+
group.push({
|
|
6662
|
+
name: skillName,
|
|
6663
|
+
digest: entry.wellKnownDigest
|
|
6664
|
+
});
|
|
6665
|
+
wellKnownGroups.set(entry.sourceBaseUrl, group);
|
|
6666
|
+
continue;
|
|
6667
|
+
}
|
|
6274
6668
|
if (!entry.skillFolderHash || !entry.skillPath) {
|
|
6275
6669
|
skipped.push({
|
|
6276
6670
|
name: skillName,
|
|
@@ -6286,50 +6680,55 @@ async function updateGlobalSkills(options = {}) {
|
|
|
6286
6680
|
entry
|
|
6287
6681
|
});
|
|
6288
6682
|
}
|
|
6683
|
+
const wellKnownCount = Array.from(wellKnownGroups.values()).reduce((sum, items) => sum + items.length, 0);
|
|
6684
|
+
const { successCount: wkSuccessCount, failCount: wkFailCount, changed: wkChanged } = await processWellKnownUpdates(wellKnownGroups, true, options);
|
|
6685
|
+
successCount += wkSuccessCount;
|
|
6686
|
+
failCount += wkFailCount;
|
|
6289
6687
|
const bySource = /* @__PURE__ */ new Map();
|
|
6290
6688
|
for (const item of checkable) {
|
|
6291
|
-
const
|
|
6292
|
-
const existing = bySource.get(
|
|
6689
|
+
const key = `${item.entry.source}\n${item.entry.ref ?? ""}`;
|
|
6690
|
+
const existing = bySource.get(key) || [];
|
|
6293
6691
|
existing.push(item);
|
|
6294
|
-
bySource.set(
|
|
6692
|
+
bySource.set(key, existing);
|
|
6295
6693
|
}
|
|
6296
|
-
for (const [
|
|
6694
|
+
for (const [, itemsForSource] of bySource) {
|
|
6297
6695
|
const firstEntry = itemsForSource[0].entry;
|
|
6696
|
+
const source = firstEntry.source;
|
|
6298
6697
|
const sourceUrl = firstEntry.sourceUrl || firstEntry.source;
|
|
6299
6698
|
let tempDir = null;
|
|
6300
6699
|
process.stdout.write(`\r${DIM$1}Checking skills from source: ${source}${RESET$1}\x1b[K\n`);
|
|
6301
6700
|
try {
|
|
6302
|
-
|
|
6701
|
+
const isGitHubSource = firstEntry.sourceType === "github";
|
|
6702
|
+
if (isGitHubSource) {
|
|
6303
6703
|
const tree = await fetchRepoTree(source, firstEntry.ref, getGitHubToken);
|
|
6304
|
-
if (
|
|
6305
|
-
|
|
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
|
+
}
|
|
6306
6717
|
continue;
|
|
6307
6718
|
}
|
|
6308
|
-
|
|
6309
|
-
const deletedSkills = await checkAndPromptForDeletions(source, Object.entries(lock.skills).filter(([_, entry]) => entry.source === source).map(([name, _]) => name), lock.skills, true, options, discoveredPaths);
|
|
6310
|
-
const deletedSkillSet = new Set(deletedSkills);
|
|
6311
|
-
for (const { name: skillName, entry } of itemsForSource) {
|
|
6312
|
-
if (deletedSkillSet.has(skillName)) continue;
|
|
6313
|
-
const latestHash = getSkillFolderHashFromTree(tree, entry.skillPath);
|
|
6314
|
-
if (latestHash && latestHash !== entry.skillFolderHash) updates.push({
|
|
6315
|
-
name: skillName,
|
|
6316
|
-
source,
|
|
6317
|
-
entry
|
|
6318
|
-
});
|
|
6319
|
-
}
|
|
6320
|
-
continue;
|
|
6719
|
+
console.log(` ${DIM$1}GitHub API unavailable; checking via Git clone${RESET$1}`);
|
|
6321
6720
|
}
|
|
6322
6721
|
tempDir = await cloneRepo(sourceUrl, firstEntry.ref);
|
|
6323
6722
|
const discoveredPaths = (await discoverSkills(tempDir, void 0, { fullDepth: true })).map((skill) => {
|
|
6324
6723
|
return join(relative(tempDir, skill.path), "SKILL.md").split(sep).join("/");
|
|
6325
6724
|
});
|
|
6326
|
-
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);
|
|
6327
6726
|
const deletedSkillSet = new Set(deletedSkills);
|
|
6328
6727
|
for (const { name: skillName, entry } of itemsForSource) {
|
|
6329
6728
|
if (deletedSkillSet.has(skillName)) continue;
|
|
6330
6729
|
const skillPath = entry.skillPath;
|
|
6331
6730
|
if (!discoveredPaths.includes(skillPath)) continue;
|
|
6332
|
-
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)));
|
|
6333
6732
|
if (latestHash && latestHash !== entry.skillFolderHash) updates.push({
|
|
6334
6733
|
name: skillName,
|
|
6335
6734
|
source,
|
|
@@ -6343,8 +6742,8 @@ async function updateGlobalSkills(options = {}) {
|
|
|
6343
6742
|
}
|
|
6344
6743
|
}
|
|
6345
6744
|
if (checkable.length > 0) process.stdout.write("\r\x1B[K");
|
|
6346
|
-
const checkedCount = checkable.length + skipped.length;
|
|
6347
|
-
if (checkable.length === 0 && skipped.length === 0) {
|
|
6745
|
+
const checkedCount = checkable.length + skipped.length + wellKnownCount;
|
|
6746
|
+
if (checkable.length === 0 && skipped.length === 0 && wellKnownCount === 0) {
|
|
6348
6747
|
if (!options.skills) console.log(`${DIM$1}No global skills to check.${RESET$1}`);
|
|
6349
6748
|
return {
|
|
6350
6749
|
successCount,
|
|
@@ -6352,6 +6751,14 @@ async function updateGlobalSkills(options = {}) {
|
|
|
6352
6751
|
checkedCount: 0
|
|
6353
6752
|
};
|
|
6354
6753
|
}
|
|
6754
|
+
if (checkable.length === 0 && skipped.length === 0) {
|
|
6755
|
+
if (!wkChanged) console.log(`${TEXT$1}✓ All global skills are up to date${RESET$1}`);
|
|
6756
|
+
return {
|
|
6757
|
+
successCount,
|
|
6758
|
+
failCount,
|
|
6759
|
+
checkedCount
|
|
6760
|
+
};
|
|
6761
|
+
}
|
|
6355
6762
|
if (checkable.length === 0 && skipped.length > 0) {
|
|
6356
6763
|
printSkippedSkills(skipped);
|
|
6357
6764
|
return {
|
|
@@ -6361,7 +6768,7 @@ async function updateGlobalSkills(options = {}) {
|
|
|
6361
6768
|
};
|
|
6362
6769
|
}
|
|
6363
6770
|
if (updates.length === 0) {
|
|
6364
|
-
console.log(`${TEXT$1}✓ All global skills are up to date${RESET$1}`);
|
|
6771
|
+
if (!wkChanged) console.log(`${TEXT$1}✓ All global skills are up to date${RESET$1}`);
|
|
6365
6772
|
return {
|
|
6366
6773
|
successCount,
|
|
6367
6774
|
failCount,
|
|
@@ -6390,6 +6797,8 @@ async function updateGlobalSkills(options = {}) {
|
|
|
6390
6797
|
cliEntry,
|
|
6391
6798
|
"add",
|
|
6392
6799
|
installUrl,
|
|
6800
|
+
"--skill",
|
|
6801
|
+
update.name,
|
|
6393
6802
|
...fullDepthArgs,
|
|
6394
6803
|
"-g",
|
|
6395
6804
|
"-y"
|
|
@@ -6400,6 +6809,7 @@ async function updateGlobalSkills(options = {}) {
|
|
|
6400
6809
|
"pipe"
|
|
6401
6810
|
],
|
|
6402
6811
|
encoding: "utf-8",
|
|
6812
|
+
env: getUpdateChildEnv(update.entry.sourceType),
|
|
6403
6813
|
shell: false
|
|
6404
6814
|
}).status === 0) {
|
|
6405
6815
|
successCount++;
|
|
@@ -6431,9 +6841,24 @@ async function updateProjectSkills(options = {}) {
|
|
|
6431
6841
|
foundCount: 0
|
|
6432
6842
|
};
|
|
6433
6843
|
}
|
|
6434
|
-
const
|
|
6435
|
-
const
|
|
6436
|
-
|
|
6844
|
+
const wellKnownGroups = /* @__PURE__ */ new Map();
|
|
6845
|
+
const nonWellKnown = [];
|
|
6846
|
+
for (const skill of projectSkills) {
|
|
6847
|
+
const { entry } = skill;
|
|
6848
|
+
if (entry.sourceType === "well-known" && entry.sourceUrl && entry.wellKnownDigest) {
|
|
6849
|
+
const group = wellKnownGroups.get(entry.sourceUrl) || [];
|
|
6850
|
+
group.push({
|
|
6851
|
+
name: skill.name,
|
|
6852
|
+
digest: entry.wellKnownDigest,
|
|
6853
|
+
subagents: entry.subagents
|
|
6854
|
+
});
|
|
6855
|
+
wellKnownGroups.set(entry.sourceUrl, group);
|
|
6856
|
+
} else nonWellKnown.push(skill);
|
|
6857
|
+
}
|
|
6858
|
+
const wellKnownCount = Array.from(wellKnownGroups.values()).reduce((sum, items) => sum + items.length, 0);
|
|
6859
|
+
const updatable = nonWellKnown.filter((s) => s.entry.skillPath);
|
|
6860
|
+
const legacy = nonWellKnown.filter((s) => !s.entry.skillPath);
|
|
6861
|
+
if (updatable.length === 0 && wellKnownCount === 0) {
|
|
6437
6862
|
console.log(`${DIM$1}No project skills can be updated in place.${RESET$1}`);
|
|
6438
6863
|
printLegacyProjectSkills(legacy);
|
|
6439
6864
|
return {
|
|
@@ -6455,39 +6880,43 @@ async function updateProjectSkills(options = {}) {
|
|
|
6455
6880
|
if (hasUniversal) targetParts.push("Universal");
|
|
6456
6881
|
targetParts.push(...targetAgentNames);
|
|
6457
6882
|
if (targetParts.length > 0) console.log(`${TEXT$1}Updating for: ${targetParts.join(", ")}${RESET$1}`);
|
|
6458
|
-
console.log(`${TEXT$1}Refreshing ${updatable.length} skill(s)…${RESET$1}`);
|
|
6883
|
+
console.log(`${TEXT$1}Refreshing ${updatable.length + wellKnownCount} skill(s)…${RESET$1}`);
|
|
6459
6884
|
console.log();
|
|
6885
|
+
const { successCount: wkSuccessCount, failCount: wkFailCount } = await processWellKnownUpdates(wellKnownGroups, false, options);
|
|
6886
|
+
successCount += wkSuccessCount;
|
|
6887
|
+
failCount += wkFailCount;
|
|
6460
6888
|
const bySource = /* @__PURE__ */ new Map();
|
|
6461
6889
|
for (const skill of updatable) {
|
|
6462
|
-
const
|
|
6463
|
-
const existing = bySource.get(
|
|
6890
|
+
const key = `${skill.entry.sourceUrl || skill.entry.source}\n${skill.entry.ref ?? ""}`;
|
|
6891
|
+
const existing = bySource.get(key) || [];
|
|
6464
6892
|
existing.push(skill);
|
|
6465
|
-
bySource.set(
|
|
6893
|
+
bySource.set(key, existing);
|
|
6466
6894
|
}
|
|
6467
6895
|
const localLock = await readLocalLock();
|
|
6468
6896
|
const cliEntry = join(__dirname$1, "..", "bin", "cli.mjs");
|
|
6469
|
-
if (!existsSync(cliEntry)) {
|
|
6897
|
+
if (updatable.length > 0 && !existsSync(cliEntry)) {
|
|
6470
6898
|
console.log(`${DIM$1}✗ CLI entrypoint not found at ${cliEntry}${RESET$1}`);
|
|
6471
6899
|
return {
|
|
6472
6900
|
successCount,
|
|
6473
|
-
failCount: updatable.length,
|
|
6901
|
+
failCount: failCount + updatable.length,
|
|
6474
6902
|
foundCount: projectSkills.length
|
|
6475
6903
|
};
|
|
6476
6904
|
}
|
|
6477
|
-
for (const [
|
|
6905
|
+
for (const [, skillsForSource] of bySource) {
|
|
6478
6906
|
const firstEntry = skillsForSource[0].entry;
|
|
6479
|
-
const
|
|
6907
|
+
const source = firstEntry.sourceUrl || firstEntry.source;
|
|
6908
|
+
const cloneSource = buildLocalCloneSource(firstEntry);
|
|
6480
6909
|
const ref = firstEntry.ref;
|
|
6481
|
-
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);
|
|
6482
6911
|
let tempDir = null;
|
|
6483
6912
|
let deletedSkills = [];
|
|
6484
|
-
if (
|
|
6913
|
+
if (cloneSource === null) {
|
|
6485
6914
|
failCount += skillsForSource.length;
|
|
6486
6915
|
console.log(`${DIM$1}✗ Cannot update ${source}: skills-lock.json is missing sourceUrl for this generic Git source${RESET$1}`);
|
|
6487
6916
|
continue;
|
|
6488
6917
|
}
|
|
6489
6918
|
try {
|
|
6490
|
-
tempDir = await cloneRepo(
|
|
6919
|
+
tempDir = await cloneRepo(cloneSource, ref);
|
|
6491
6920
|
const discoveredPaths = (await discoverSkills(tempDir, void 0, { fullDepth: true })).map((s) => {
|
|
6492
6921
|
return join(relative(tempDir, s.path), "SKILL.md").split(sep).join("/");
|
|
6493
6922
|
});
|
|
@@ -6525,6 +6954,7 @@ async function updateProjectSkills(options = {}) {
|
|
|
6525
6954
|
"pipe"
|
|
6526
6955
|
],
|
|
6527
6956
|
encoding: "utf-8",
|
|
6957
|
+
env: getUpdateChildEnv(skill.entry.sourceType),
|
|
6528
6958
|
shell: false
|
|
6529
6959
|
}).status === 0) {
|
|
6530
6960
|
successCount++;
|
|
@@ -6624,7 +7054,6 @@ function parseUseOptions(args) {
|
|
|
6624
7054
|
if (!arg) continue;
|
|
6625
7055
|
if (arg === "--help" || arg === "-h") options.help = true;
|
|
6626
7056
|
else if (arg === "--full-depth") options.fullDepth = true;
|
|
6627
|
-
else if (arg === "--dangerously-accept-openclaw-risks") options.dangerouslyAcceptOpenclawRisks = true;
|
|
6628
7057
|
else if (arg === "--skill" || arg === "-s") {
|
|
6629
7058
|
const value = args[i + 1];
|
|
6630
7059
|
if (!value || value.startsWith("-")) errors.push(`${arg} requires a skill name`);
|
|
@@ -6695,16 +7124,14 @@ async function runUse(sourceArgs, options = {}, parseErrors = []) {
|
|
|
6695
7124
|
if (useAgent && !USE_AGENT_CONFIGS[useAgent]) fail(formatUnsupportedAgentError(useAgent));
|
|
6696
7125
|
const source = sourceArgs[0];
|
|
6697
7126
|
const parsed = parseSource(source);
|
|
6698
|
-
if (getOwnerRepo(parsed)?.split("/")[0]?.toLowerCase() === "openclaw" && !options.dangerouslyAcceptOpenclawRisks) fail([
|
|
6699
|
-
"OpenClaw skills are unverified community submissions.",
|
|
6700
|
-
"Skills run with full agent permissions and could be malicious.",
|
|
6701
|
-
`If you understand the risks, re-run with: skills use ${source} --dangerously-accept-openclaw-risks`
|
|
6702
|
-
].join("\n"));
|
|
6703
7127
|
const selector = resolveSelector(parsed.skillFilter, options.skill);
|
|
6704
7128
|
const includeInternal = selector !== void 0;
|
|
6705
7129
|
let selectedSkill;
|
|
6706
7130
|
if (parsed.type === "well-known") {
|
|
6707
|
-
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
|
+
});
|
|
6708
7135
|
if (skills.length > 0) selectedSkill = selectWellKnownSkill(skills, selector, source);
|
|
6709
7136
|
else {
|
|
6710
7137
|
const downloaded = await downloadSource(parsed.url);
|
|
@@ -6833,8 +7260,6 @@ Options:
|
|
|
6833
7260
|
-s, --skill <skill> Select the skill to use
|
|
6834
7261
|
-a, --agent <agent> Start one supported agent interactively (${SUPPORTED_USE_AGENTS.join(", ")})
|
|
6835
7262
|
--full-depth Search nested directories like skills add --full-depth
|
|
6836
|
-
--dangerously-accept-openclaw-risks
|
|
6837
|
-
Allow unverified OpenClaw community skills
|
|
6838
7263
|
-h, --help Show this help message
|
|
6839
7264
|
|
|
6840
7265
|
Examples:
|
|
@@ -7084,15 +7509,13 @@ ${BOLD}Use Options:${RESET}
|
|
|
7084
7509
|
-s, --skill <skill> Specify the skill to use
|
|
7085
7510
|
-a, --agent <agent> Start one supported agent interactively
|
|
7086
7511
|
--full-depth Search all subdirectories even when a root SKILL.md exists
|
|
7087
|
-
--dangerously-accept-openclaw-risks
|
|
7088
|
-
Allow unverified OpenClaw community skills
|
|
7089
7512
|
|
|
7090
7513
|
${BOLD}Remove Options:${RESET}
|
|
7091
7514
|
-g, --global Remove from global scope
|
|
7092
|
-
-a, --agent <agents> Remove from specific agents (
|
|
7515
|
+
-a, --agent <agents> Remove from specific agents (omit to clean all agent links)
|
|
7093
7516
|
-s, --skill <skills> Specify skills to remove (use '*' for all skills)
|
|
7094
7517
|
-y, --yes Skip confirmation prompts
|
|
7095
|
-
--all
|
|
7518
|
+
--all Remove every installed skill (-y implied). Do not combine with named skills.
|
|
7096
7519
|
|
|
7097
7520
|
${BOLD}Experimental Sync Options:${RESET}
|
|
7098
7521
|
-a, --agent <agents> Specify agents to install to (use '*' for all agents)
|
|
@@ -7148,10 +7571,10 @@ ${BOLD}Arguments:${RESET}
|
|
|
7148
7571
|
|
|
7149
7572
|
${BOLD}Options:${RESET}
|
|
7150
7573
|
-g, --global Remove from global scope (~/) instead of project scope
|
|
7151
|
-
-a, --agent Remove from specific agents (
|
|
7574
|
+
-a, --agent Remove from specific agents (omit to clean all agent links)
|
|
7152
7575
|
-s, --skill Specify skills to remove (use '*' for all skills)
|
|
7153
7576
|
-y, --yes Skip confirmation prompts
|
|
7154
|
-
--all
|
|
7577
|
+
--all Remove every installed skill (-y implied). Do not combine with named skills.
|
|
7155
7578
|
|
|
7156
7579
|
${BOLD}Examples:${RESET}
|
|
7157
7580
|
${DIM}$${RESET} skills remove ${DIM}# interactive selection${RESET}
|