skild 0.5.2 → 0.6.0
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/dist/index.js +642 -41
- package/package.json +5 -1
package/dist/index.js
CHANGED
|
@@ -6,17 +6,18 @@ import chalk16 from "chalk";
|
|
|
6
6
|
import { createRequire } from "module";
|
|
7
7
|
|
|
8
8
|
// src/commands/install.ts
|
|
9
|
-
import
|
|
10
|
-
import
|
|
9
|
+
import fs3 from "fs";
|
|
10
|
+
import path3 from "path";
|
|
11
11
|
import chalk3 from "chalk";
|
|
12
12
|
import {
|
|
13
|
-
deriveChildSource,
|
|
14
|
-
fetchWithTimeout,
|
|
13
|
+
deriveChildSource as deriveChildSource2,
|
|
14
|
+
fetchWithTimeout as fetchWithTimeout2,
|
|
15
15
|
installRegistrySkill,
|
|
16
16
|
installSkill,
|
|
17
17
|
isValidAlias,
|
|
18
|
+
listSkills,
|
|
18
19
|
loadRegistryAuth,
|
|
19
|
-
materializeSourceToTemp,
|
|
20
|
+
materializeSourceToTemp as materializeSourceToTemp2,
|
|
20
21
|
resolveRegistryAlias,
|
|
21
22
|
resolveRegistryUrl,
|
|
22
23
|
stripSourceRef,
|
|
@@ -70,10 +71,10 @@ var logger = {
|
|
|
70
71
|
/**
|
|
71
72
|
* Log a skill entry with status indicator.
|
|
72
73
|
*/
|
|
73
|
-
skillEntry: (name,
|
|
74
|
+
skillEntry: (name, path5, hasSkillMd) => {
|
|
74
75
|
const status = hasSkillMd ? chalk.green("\u2713") : chalk.yellow("\u26A0");
|
|
75
76
|
console.log(` ${status} ${chalk.cyan(name)}`);
|
|
76
|
-
console.log(chalk.dim(` \u2514\u2500 ${
|
|
77
|
+
console.log(chalk.dim(` \u2514\u2500 ${path5}`));
|
|
77
78
|
},
|
|
78
79
|
/**
|
|
79
80
|
* Log installation result details.
|
|
@@ -119,6 +120,35 @@ function buildPlatformTree(items) {
|
|
|
119
120
|
}
|
|
120
121
|
return wrapWithRoot(allNode);
|
|
121
122
|
}
|
|
123
|
+
function buildTreeFromSkillNodes(nodes, totalSkills) {
|
|
124
|
+
const allNode = createTreeNode("all", "All Skills", 1, false);
|
|
125
|
+
const attach = (node, depth) => {
|
|
126
|
+
const treeNode = createTreeNode(
|
|
127
|
+
node.id,
|
|
128
|
+
node.label,
|
|
129
|
+
depth,
|
|
130
|
+
Boolean(node.skillIndex != null && (!node.children || node.children.length === 0)),
|
|
131
|
+
node.skillIndex != null ? [node.skillIndex] : []
|
|
132
|
+
);
|
|
133
|
+
if (node.children?.length) {
|
|
134
|
+
for (const child of node.children) {
|
|
135
|
+
const childNode = attach(child, depth + 1);
|
|
136
|
+
treeNode.children.push(childNode);
|
|
137
|
+
treeNode.leafIndices.push(...childNode.leafIndices);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return treeNode;
|
|
141
|
+
};
|
|
142
|
+
for (const node of nodes) {
|
|
143
|
+
const childNode = attach(node, 2);
|
|
144
|
+
allNode.children.push(childNode);
|
|
145
|
+
allNode.leafIndices.push(...childNode.leafIndices);
|
|
146
|
+
}
|
|
147
|
+
if (allNode.leafIndices.length === 0 && totalSkills > 0) {
|
|
148
|
+
for (let i = 0; i < totalSkills; i++) allNode.leafIndices.push(i);
|
|
149
|
+
}
|
|
150
|
+
return wrapWithRoot(allNode);
|
|
151
|
+
}
|
|
122
152
|
function createTreeNode(id, name, depth, isLeaf, leafIndices = []) {
|
|
123
153
|
return { id, name, depth, children: [], leafIndices, isLeaf };
|
|
124
154
|
}
|
|
@@ -353,8 +383,25 @@ async function promptSkillsInteractive(skills, options = {}) {
|
|
|
353
383
|
`));
|
|
354
384
|
return selectedSkills;
|
|
355
385
|
}
|
|
386
|
+
async function promptSkillsTreeInteractive(skills, tree, options = {}) {
|
|
387
|
+
const selectedIndices = await interactiveTreeSelect(skills, {
|
|
388
|
+
title: "Select skills from markdown",
|
|
389
|
+
subtitle: "\u2191\u2193 navigate \u2022 Space toggle \u2022 Enter confirm",
|
|
390
|
+
buildTree: () => buildTreeFromSkillNodes(tree, skills.length),
|
|
391
|
+
formatNode: (node, selection, isCursor) => formatTreeNode(node, selection, isCursor),
|
|
392
|
+
defaultAll: options.defaultAll !== false
|
|
393
|
+
});
|
|
394
|
+
if (!selectedIndices) return null;
|
|
395
|
+
const selectedSkills = selectedIndices.map((i) => skills[i]);
|
|
396
|
+
const names = selectedSkills.map((s) => s.displayName || s.relPath || s.suggestedSource);
|
|
397
|
+
console.log(chalk2.green(`
|
|
398
|
+
\u2713 Selected ${selectedSkills.length} skill${selectedSkills.length > 1 ? "s" : ""}: ${chalk2.cyan(names.join(", "))}
|
|
399
|
+
`));
|
|
400
|
+
return selectedSkills;
|
|
401
|
+
}
|
|
356
402
|
async function promptPlatformsInteractive(options = {}) {
|
|
357
|
-
const
|
|
403
|
+
const platforms = options.platforms && options.platforms.length > 0 ? options.platforms : PLATFORMS;
|
|
404
|
+
const platformItems = platforms.map((p) => ({ platform: p }));
|
|
358
405
|
const selectedIndices = await interactiveTreeSelect(platformItems, {
|
|
359
406
|
title: "Select target platforms",
|
|
360
407
|
subtitle: "\u2191\u2193 navigate \u2022 Space toggle \u2022 Enter confirm",
|
|
@@ -367,7 +414,7 @@ async function promptPlatformsInteractive(options = {}) {
|
|
|
367
414
|
defaultAll: options.defaultAll !== false
|
|
368
415
|
});
|
|
369
416
|
if (!selectedIndices) return null;
|
|
370
|
-
const selected = selectedIndices.map((i) =>
|
|
417
|
+
const selected = selectedIndices.map((i) => platforms[i]);
|
|
371
418
|
const names = selected.map((p) => PLATFORM_DISPLAY[p] || p);
|
|
372
419
|
console.log(chalk2.green(`
|
|
373
420
|
\u2713 Installing to ${selected.length} platform${selected.length > 1 ? "s" : ""}: ${chalk2.cyan(names.join(", "))}
|
|
@@ -457,6 +504,518 @@ function discoverSkillDirsWithHeuristics(rootDir, options) {
|
|
|
457
504
|
return discoverSkillDirs(root, options);
|
|
458
505
|
}
|
|
459
506
|
|
|
507
|
+
// src/commands/install-markdown.ts
|
|
508
|
+
import fs2 from "fs";
|
|
509
|
+
import path2 from "path";
|
|
510
|
+
import { unified } from "unified";
|
|
511
|
+
import remarkParse from "remark-parse";
|
|
512
|
+
import { toString } from "mdast-util-to-string";
|
|
513
|
+
import { deriveChildSource, fetchWithTimeout, materializeSourceToTemp } from "@skild/core";
|
|
514
|
+
var README_CANDIDATES = ["README.md", "readme.md", "Readme.md", "README.markdown", "readme.markdown"];
|
|
515
|
+
async function discoverMarkdownSkillsFromSource(input) {
|
|
516
|
+
const ctx = {
|
|
517
|
+
maxDepth: input.maxDepth,
|
|
518
|
+
maxSkills: input.maxSkills,
|
|
519
|
+
maxLinks: Math.min(400, Math.max(200, input.maxSkills * 2)),
|
|
520
|
+
linkLimitReached: false,
|
|
521
|
+
onProgress: input.onProgress,
|
|
522
|
+
docsScanned: 0,
|
|
523
|
+
linksChecked: 0,
|
|
524
|
+
skillsFound: 0,
|
|
525
|
+
repoCache: /* @__PURE__ */ new Map(),
|
|
526
|
+
skillCache: /* @__PURE__ */ new Map(),
|
|
527
|
+
skillIndexBySource: /* @__PURE__ */ new Map(),
|
|
528
|
+
skills: [],
|
|
529
|
+
skillCleanups: [],
|
|
530
|
+
nodeId: 0
|
|
531
|
+
};
|
|
532
|
+
try {
|
|
533
|
+
const entryDoc = await resolveMarkdownDoc(input.source, ctx);
|
|
534
|
+
if (!entryDoc) {
|
|
535
|
+
cleanupRepoCache(ctx);
|
|
536
|
+
return null;
|
|
537
|
+
}
|
|
538
|
+
const visitedDocs = /* @__PURE__ */ new Set();
|
|
539
|
+
const tree = await parseMarkdownDoc(entryDoc, ctx, 0, visitedDocs);
|
|
540
|
+
cleanupRepoCache(ctx);
|
|
541
|
+
const compacted = collapseSingleChildNodes(tree);
|
|
542
|
+
return {
|
|
543
|
+
skills: ctx.skills,
|
|
544
|
+
tree: compacted,
|
|
545
|
+
cleanup: () => {
|
|
546
|
+
for (const cleanup of ctx.skillCleanups) cleanup();
|
|
547
|
+
}
|
|
548
|
+
};
|
|
549
|
+
} catch {
|
|
550
|
+
cleanupRepoCache(ctx);
|
|
551
|
+
return null;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
function cleanupRepoCache(ctx) {
|
|
555
|
+
for (const entry of ctx.repoCache.values()) {
|
|
556
|
+
entry.cleanup?.();
|
|
557
|
+
}
|
|
558
|
+
ctx.repoCache.clear();
|
|
559
|
+
}
|
|
560
|
+
async function parseMarkdownDoc(doc, ctx, depth, visitedDocs) {
|
|
561
|
+
if (depth > ctx.maxDepth) return [];
|
|
562
|
+
const docKey = `${doc.repo.owner}/${doc.repo.repo}#${doc.repo.ref || ""}:${doc.docPath}`;
|
|
563
|
+
if (visitedDocs.has(docKey)) return [];
|
|
564
|
+
visitedDocs.add(docKey);
|
|
565
|
+
let content;
|
|
566
|
+
try {
|
|
567
|
+
if (doc.content != null) {
|
|
568
|
+
content = doc.content;
|
|
569
|
+
} else if (doc.filePath) {
|
|
570
|
+
content = fs2.readFileSync(doc.filePath, "utf-8");
|
|
571
|
+
} else {
|
|
572
|
+
return [];
|
|
573
|
+
}
|
|
574
|
+
} catch {
|
|
575
|
+
return [];
|
|
576
|
+
}
|
|
577
|
+
ctx.docsScanned += 1;
|
|
578
|
+
updateProgress(ctx, doc.docPath ? path2.posix.basename(doc.docPath) : void 0);
|
|
579
|
+
const ast = unified().use(remarkParse).parse(content);
|
|
580
|
+
const rootNode = createNode(ctx, "root", "doc", []);
|
|
581
|
+
const headingStack = [];
|
|
582
|
+
for (const child of ast.children) {
|
|
583
|
+
if (child.type === "heading") {
|
|
584
|
+
const label = normalizeLabel(toString(child)) || `Section ${child.depth}`;
|
|
585
|
+
const headingNode = createNode(ctx, label, "heading", []);
|
|
586
|
+
const parent = findHeadingParent(headingStack, child.depth, rootNode);
|
|
587
|
+
parent.children.push(headingNode);
|
|
588
|
+
headingStack.push({ depth: child.depth, node: headingNode });
|
|
589
|
+
} else if (child.type === "list") {
|
|
590
|
+
const parent = currentHeading(headingStack, rootNode);
|
|
591
|
+
await parseListNode(child, parent, doc, ctx, depth, visitedDocs);
|
|
592
|
+
} else {
|
|
593
|
+
const parent = currentHeading(headingStack, rootNode);
|
|
594
|
+
await parseInlineLinks(child, parent, doc, ctx, depth, visitedDocs);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
return rootNode.children;
|
|
598
|
+
}
|
|
599
|
+
function currentHeading(stack, fallback) {
|
|
600
|
+
return stack.length ? stack[stack.length - 1].node : fallback;
|
|
601
|
+
}
|
|
602
|
+
function findHeadingParent(stack, depth, fallback) {
|
|
603
|
+
while (stack.length > 0 && stack[stack.length - 1].depth >= depth) {
|
|
604
|
+
stack.pop();
|
|
605
|
+
}
|
|
606
|
+
return stack.length ? stack[stack.length - 1].node : fallback;
|
|
607
|
+
}
|
|
608
|
+
async function parseListNode(node, parent, doc, ctx, depth, visitedDocs) {
|
|
609
|
+
if (ctx.linkLimitReached || ctx.skillsFound >= ctx.maxSkills) return;
|
|
610
|
+
for (const item of node.children) {
|
|
611
|
+
const label = normalizeLabel(toString(item)) || "Item";
|
|
612
|
+
const listNode = createNode(ctx, label, "list", []);
|
|
613
|
+
let hasContent = false;
|
|
614
|
+
for (const child of item.children) {
|
|
615
|
+
if (child.type === "list") {
|
|
616
|
+
await parseListNode(child, listNode, doc, ctx, depth, visitedDocs);
|
|
617
|
+
if (listNode.children.length > 0) hasContent = true;
|
|
618
|
+
} else {
|
|
619
|
+
const added = await parseInlineLinks(child, listNode, doc, ctx, depth, visitedDocs);
|
|
620
|
+
if (added) hasContent = true;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
if (hasContent) {
|
|
624
|
+
parent.children.push(listNode);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
async function parseInlineLinks(node, parent, doc, ctx, depth, visitedDocs) {
|
|
629
|
+
const links = collectLinks(node);
|
|
630
|
+
let added = false;
|
|
631
|
+
if (ctx.skillsFound >= ctx.maxSkills) {
|
|
632
|
+
ctx.linkLimitReached = true;
|
|
633
|
+
updateProgress(ctx);
|
|
634
|
+
return false;
|
|
635
|
+
}
|
|
636
|
+
for (const link of links) {
|
|
637
|
+
if (ctx.linkLimitReached) return added;
|
|
638
|
+
if (ctx.linksChecked >= ctx.maxLinks) {
|
|
639
|
+
ctx.linkLimitReached = true;
|
|
640
|
+
updateProgress(ctx);
|
|
641
|
+
return added;
|
|
642
|
+
}
|
|
643
|
+
ctx.linksChecked += 1;
|
|
644
|
+
updateProgress(ctx);
|
|
645
|
+
const resolved = resolveLink(doc, link.url);
|
|
646
|
+
if (!resolved) continue;
|
|
647
|
+
const label = normalizeLabel(link.label) || resolved.displayName;
|
|
648
|
+
const maybeMarkdown = !isLikelyFilePath(resolved.pathHint) || looksLikeMarkdownPath(resolved.pathHint);
|
|
649
|
+
if (maybeMarkdown) {
|
|
650
|
+
const childDoc = await resolveMarkdownDoc(resolved.source, ctx);
|
|
651
|
+
if (childDoc) {
|
|
652
|
+
const childNodes = await parseMarkdownDoc(childDoc, ctx, depth + 1, visitedDocs);
|
|
653
|
+
if (childNodes.length > 0) {
|
|
654
|
+
const docNode = createNode(ctx, label, "doc", childNodes);
|
|
655
|
+
parent.children.push(docNode);
|
|
656
|
+
added = true;
|
|
657
|
+
continue;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
if (isLikelyFilePath(resolved.pathHint)) continue;
|
|
662
|
+
const skillIndices = await resolveSkillsFromSource(resolved.source, label, ctx, resolved.sameRepo ? { repo: doc.repo, pathHint: resolved.pathHint } : void 0);
|
|
663
|
+
if (skillIndices.length === 0) continue;
|
|
664
|
+
added = true;
|
|
665
|
+
if (skillIndices.length === 1) {
|
|
666
|
+
parent.children.push(createSkillLeaf(ctx, skillIndices[0], label));
|
|
667
|
+
} else {
|
|
668
|
+
const groupLabel = label || resolved.displayName || "Skills";
|
|
669
|
+
const groupNode = createNode(ctx, groupLabel, "list", []);
|
|
670
|
+
for (const skillIndex of skillIndices) {
|
|
671
|
+
groupNode.children.push(createSkillLeaf(ctx, skillIndex));
|
|
672
|
+
}
|
|
673
|
+
parent.children.push(groupNode);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
return added;
|
|
677
|
+
}
|
|
678
|
+
function createNode(ctx, label, kind, children) {
|
|
679
|
+
ctx.nodeId += 1;
|
|
680
|
+
return {
|
|
681
|
+
id: `md-${ctx.nodeId}`,
|
|
682
|
+
label: label.trim(),
|
|
683
|
+
kind,
|
|
684
|
+
children
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
function createSkillLeaf(ctx, skillIndex, labelOverride) {
|
|
688
|
+
const label = labelOverride?.trim() || ctx.skills[skillIndex]?.displayName || ctx.skills[skillIndex]?.relPath || "Skill";
|
|
689
|
+
const node = createNode(ctx, label, "skill", []);
|
|
690
|
+
node.skillIndex = skillIndex;
|
|
691
|
+
return node;
|
|
692
|
+
}
|
|
693
|
+
function collapseSingleChildNodes(nodes) {
|
|
694
|
+
const collapsed = [];
|
|
695
|
+
for (const node of nodes) {
|
|
696
|
+
const next = collapseNode(node);
|
|
697
|
+
if (next) collapsed.push(next);
|
|
698
|
+
}
|
|
699
|
+
return collapsed;
|
|
700
|
+
}
|
|
701
|
+
function collapseNode(node) {
|
|
702
|
+
node.children = node.children.map(collapseNode).filter(Boolean);
|
|
703
|
+
if (node.kind !== "heading" && node.kind !== "skill" && node.children.length === 1 && !node.skillIndex) {
|
|
704
|
+
return node.children[0];
|
|
705
|
+
}
|
|
706
|
+
if (node.kind !== "skill" && node.children.length === 0 && !node.skillIndex) {
|
|
707
|
+
return null;
|
|
708
|
+
}
|
|
709
|
+
return node;
|
|
710
|
+
}
|
|
711
|
+
function normalizeLabel(value) {
|
|
712
|
+
if (!value) return "";
|
|
713
|
+
return value.replace(/\s+/g, " ").trim();
|
|
714
|
+
}
|
|
715
|
+
function collectLinks(node) {
|
|
716
|
+
const links = [];
|
|
717
|
+
const visit = (n) => {
|
|
718
|
+
if (n.type === "link") {
|
|
719
|
+
const url = typeof n.url === "string" ? n.url : "";
|
|
720
|
+
if (url) {
|
|
721
|
+
links.push({ url, label: toString(n) });
|
|
722
|
+
}
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
if ("children" in n && Array.isArray(n.children)) {
|
|
726
|
+
for (const child of n.children) {
|
|
727
|
+
visit(child);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
};
|
|
731
|
+
visit(node);
|
|
732
|
+
return links;
|
|
733
|
+
}
|
|
734
|
+
function isLikelyFilePath(value) {
|
|
735
|
+
if (!value) return false;
|
|
736
|
+
const trimmed = value.split("?")[0].split("#")[0];
|
|
737
|
+
const base = trimmed.split("/").pop() || "";
|
|
738
|
+
if (!base.includes(".")) return false;
|
|
739
|
+
const lower = base.toLowerCase();
|
|
740
|
+
return !lower.endsWith(".md") && !lower.endsWith(".markdown");
|
|
741
|
+
}
|
|
742
|
+
function updateProgress(ctx, current) {
|
|
743
|
+
if (!ctx.onProgress) return;
|
|
744
|
+
ctx.onProgress({
|
|
745
|
+
docsScanned: ctx.docsScanned,
|
|
746
|
+
linksChecked: ctx.linksChecked,
|
|
747
|
+
skillsFound: ctx.skillsFound,
|
|
748
|
+
current,
|
|
749
|
+
linkLimitReached: ctx.linkLimitReached
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
async function resolveSkillsFromSource(source, displayName, ctx, repoHint) {
|
|
753
|
+
const cached = ctx.skillCache.get(source);
|
|
754
|
+
if (cached) return cached;
|
|
755
|
+
const localIndices = repoHint ? resolveSkillsFromLocal(repoHint, source, displayName, ctx) : [];
|
|
756
|
+
if (localIndices.length > 0) {
|
|
757
|
+
ctx.skillCache.set(source, localIndices);
|
|
758
|
+
return localIndices;
|
|
759
|
+
}
|
|
760
|
+
const quickIndex = await tryFetchSkillManifest(source, displayName, ctx);
|
|
761
|
+
if (quickIndex.length > 0) {
|
|
762
|
+
ctx.skillCache.set(source, quickIndex);
|
|
763
|
+
return quickIndex;
|
|
764
|
+
}
|
|
765
|
+
let materializedDir;
|
|
766
|
+
try {
|
|
767
|
+
const materialized = await materializeSourceToTemp(source);
|
|
768
|
+
ctx.skillCleanups.push(materialized.cleanup);
|
|
769
|
+
materializedDir = materialized.dir;
|
|
770
|
+
} catch {
|
|
771
|
+
ctx.skillCache.set(source, []);
|
|
772
|
+
return [];
|
|
773
|
+
}
|
|
774
|
+
const skillMd = path2.join(materializedDir, "SKILL.md");
|
|
775
|
+
if (fs2.existsSync(skillMd)) {
|
|
776
|
+
const skillIndex = registerSkill(ctx, {
|
|
777
|
+
relPath: ".",
|
|
778
|
+
suggestedSource: source,
|
|
779
|
+
materializedDir,
|
|
780
|
+
displayName: displayName || deriveDisplayName(source)
|
|
781
|
+
});
|
|
782
|
+
const indices2 = [skillIndex];
|
|
783
|
+
ctx.skillCache.set(source, indices2);
|
|
784
|
+
return indices2;
|
|
785
|
+
}
|
|
786
|
+
const discovered = discoverSkillDirsWithHeuristics(materializedDir, { maxDepth: ctx.maxDepth, maxSkills: ctx.maxSkills });
|
|
787
|
+
if (discovered.length === 0) {
|
|
788
|
+
ctx.skillCache.set(source, []);
|
|
789
|
+
return [];
|
|
790
|
+
}
|
|
791
|
+
const indices = [];
|
|
792
|
+
for (const skill of discovered) {
|
|
793
|
+
const childSource = deriveChildSource(source, skill.relPath);
|
|
794
|
+
const skillIndex = registerSkill(ctx, {
|
|
795
|
+
relPath: skill.relPath,
|
|
796
|
+
suggestedSource: childSource,
|
|
797
|
+
materializedDir: skill.absDir,
|
|
798
|
+
displayName: deriveDisplayName(childSource)
|
|
799
|
+
});
|
|
800
|
+
indices.push(skillIndex);
|
|
801
|
+
}
|
|
802
|
+
ctx.skillCache.set(source, indices);
|
|
803
|
+
return indices;
|
|
804
|
+
}
|
|
805
|
+
function resolveSkillsFromLocal(repoHint, source, displayName, ctx) {
|
|
806
|
+
if (!repoHint.repo.dir) return [];
|
|
807
|
+
const base = repoHint.pathHint ? path2.join(repoHint.repo.dir, repoHint.pathHint) : repoHint.repo.dir;
|
|
808
|
+
if (!fs2.existsSync(base)) return [];
|
|
809
|
+
const skillMd = path2.join(base, "SKILL.md");
|
|
810
|
+
if (fs2.existsSync(skillMd)) {
|
|
811
|
+
return [
|
|
812
|
+
registerSkill(ctx, {
|
|
813
|
+
relPath: ".",
|
|
814
|
+
suggestedSource: source,
|
|
815
|
+
displayName: displayName || deriveDisplayName(source)
|
|
816
|
+
})
|
|
817
|
+
];
|
|
818
|
+
}
|
|
819
|
+
const discovered = discoverSkillDirsWithHeuristics(base, { maxDepth: ctx.maxDepth, maxSkills: ctx.maxSkills });
|
|
820
|
+
if (discovered.length === 0) return [];
|
|
821
|
+
const indices = [];
|
|
822
|
+
for (const skill of discovered) {
|
|
823
|
+
const childSource = deriveChildSource(source, skill.relPath);
|
|
824
|
+
const skillIndex = registerSkill(ctx, {
|
|
825
|
+
relPath: skill.relPath,
|
|
826
|
+
suggestedSource: childSource,
|
|
827
|
+
displayName: deriveDisplayName(childSource)
|
|
828
|
+
});
|
|
829
|
+
indices.push(skillIndex);
|
|
830
|
+
}
|
|
831
|
+
return indices;
|
|
832
|
+
}
|
|
833
|
+
async function tryFetchSkillManifest(source, displayName, ctx) {
|
|
834
|
+
const parsed = parseGitHubSource(source);
|
|
835
|
+
if (!parsed) return [];
|
|
836
|
+
const ref = parsed.ref || "HEAD";
|
|
837
|
+
const pathPrefix = parsed.path ? `${parsed.path.replace(/\/+$/, "")}/` : "";
|
|
838
|
+
const rawUrl = `https://raw.githubusercontent.com/${parsed.owner}/${parsed.repo}/${ref}/${pathPrefix}SKILL.md`;
|
|
839
|
+
try {
|
|
840
|
+
const res = await fetchWithTimeout(rawUrl, { method: "GET" }, 5e3);
|
|
841
|
+
if (!res.ok) {
|
|
842
|
+
if (res.status === 404) return [];
|
|
843
|
+
return [];
|
|
844
|
+
}
|
|
845
|
+
} catch {
|
|
846
|
+
return [];
|
|
847
|
+
}
|
|
848
|
+
return [
|
|
849
|
+
registerSkill(ctx, {
|
|
850
|
+
relPath: ".",
|
|
851
|
+
suggestedSource: source,
|
|
852
|
+
displayName: displayName || deriveDisplayName(source)
|
|
853
|
+
})
|
|
854
|
+
];
|
|
855
|
+
}
|
|
856
|
+
function registerSkill(ctx, skill) {
|
|
857
|
+
const key = skill.suggestedSource;
|
|
858
|
+
const existing = ctx.skillIndexBySource.get(key);
|
|
859
|
+
if (existing != null) {
|
|
860
|
+
if (!ctx.skills[existing].displayName && skill.displayName) {
|
|
861
|
+
ctx.skills[existing].displayName = skill.displayName;
|
|
862
|
+
}
|
|
863
|
+
return existing;
|
|
864
|
+
}
|
|
865
|
+
if (ctx.skills.length >= ctx.maxSkills) {
|
|
866
|
+
ctx.linkLimitReached = true;
|
|
867
|
+
updateProgress(ctx);
|
|
868
|
+
return ctx.skills.length - 1;
|
|
869
|
+
}
|
|
870
|
+
ctx.skills.push(skill);
|
|
871
|
+
const index = ctx.skills.length - 1;
|
|
872
|
+
ctx.skillIndexBySource.set(key, index);
|
|
873
|
+
ctx.skillsFound = ctx.skills.length;
|
|
874
|
+
if (ctx.skillsFound >= ctx.maxSkills) {
|
|
875
|
+
ctx.linkLimitReached = true;
|
|
876
|
+
updateProgress(ctx);
|
|
877
|
+
}
|
|
878
|
+
return index;
|
|
879
|
+
}
|
|
880
|
+
function deriveDisplayName(source) {
|
|
881
|
+
const clean = source.replace(/[#?].*$/, "");
|
|
882
|
+
return clean.split("/").filter(Boolean).pop() || source;
|
|
883
|
+
}
|
|
884
|
+
function resolveLink(doc, url) {
|
|
885
|
+
const trimmed = url.trim();
|
|
886
|
+
if (!trimmed || trimmed.startsWith("#")) return null;
|
|
887
|
+
if (trimmed.startsWith("mailto:")) return null;
|
|
888
|
+
const parsed = parseGitHubSource(trimmed);
|
|
889
|
+
if (parsed) {
|
|
890
|
+
const source = toRepoSource(parsed);
|
|
891
|
+
const pathHint = parsed.path;
|
|
892
|
+
const displayName2 = parsed.path ? parsed.path.split("/").pop() || source : source;
|
|
893
|
+
const parsedRef = parsed.ref || doc.repo.ref || "HEAD";
|
|
894
|
+
const docRef = doc.repo.ref || "HEAD";
|
|
895
|
+
const sameRepo = parsed.owner === doc.repo.owner && parsed.repo === doc.repo.repo && parsedRef === docRef;
|
|
896
|
+
return { source, displayName: displayName2, pathHint, sameRepo };
|
|
897
|
+
}
|
|
898
|
+
if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) return null;
|
|
899
|
+
const relative = normalizeRepoRelative(trimmed);
|
|
900
|
+
if (!relative) return null;
|
|
901
|
+
const baseDir = path2.posix.dirname(doc.docPath);
|
|
902
|
+
const relativePath = relative.isAbsolute ? path2.posix.normalize(relative.path) : path2.posix.normalize(path2.posix.join(baseDir, relative.path));
|
|
903
|
+
const resolved = buildRepoSource({
|
|
904
|
+
owner: doc.repo.owner,
|
|
905
|
+
repo: doc.repo.repo,
|
|
906
|
+
ref: doc.repo.ref,
|
|
907
|
+
path: relativePath.replace(/^\/+/, "")
|
|
908
|
+
});
|
|
909
|
+
const displayName = relativePath.split("/").filter(Boolean).pop() || relative.path;
|
|
910
|
+
return { source: resolved, displayName, pathHint: relativePath, sameRepo: true };
|
|
911
|
+
}
|
|
912
|
+
function normalizeRepoRelative(link) {
|
|
913
|
+
const cleaned = link.split("#")[0].split("?")[0].trim();
|
|
914
|
+
if (!cleaned) return null;
|
|
915
|
+
if (cleaned.startsWith("/")) return { path: cleaned.slice(1), isAbsolute: true };
|
|
916
|
+
return { path: cleaned, isAbsolute: false };
|
|
917
|
+
}
|
|
918
|
+
function looksLikeMarkdownPath(value) {
|
|
919
|
+
if (!value) return false;
|
|
920
|
+
const lower = value.toLowerCase();
|
|
921
|
+
return lower.endsWith(".md") || lower.endsWith(".markdown") || README_CANDIDATES.some((name) => lower.endsWith(name.toLowerCase()));
|
|
922
|
+
}
|
|
923
|
+
function parseGitHubSource(input) {
|
|
924
|
+
if (input.includes("github.com") || input.includes("raw.githubusercontent.com")) {
|
|
925
|
+
try {
|
|
926
|
+
const url = new URL(input);
|
|
927
|
+
if (url.hostname === "raw.githubusercontent.com") {
|
|
928
|
+
const parts2 = url.pathname.split("/").filter(Boolean);
|
|
929
|
+
if (parts2.length < 4) return null;
|
|
930
|
+
const [owner2, repo2, ref2, ...rest2] = parts2;
|
|
931
|
+
return { owner: owner2, repo: repo2.replace(/\.git$/, ""), ref: ref2, path: rest2.join("/"), isFile: true };
|
|
932
|
+
}
|
|
933
|
+
if (url.hostname !== "github.com") return null;
|
|
934
|
+
const parts = url.pathname.split("/").filter(Boolean);
|
|
935
|
+
if (parts.length < 2) return null;
|
|
936
|
+
const [owner, repo, type, ref, ...rest] = parts;
|
|
937
|
+
if (!type) return { owner, repo: repo.replace(/\.git$/, "") };
|
|
938
|
+
if (type === "tree" || type === "blob") {
|
|
939
|
+
return { owner, repo: repo.replace(/\.git$/, ""), ref, path: rest.join("/"), isFile: type === "blob" };
|
|
940
|
+
}
|
|
941
|
+
return { owner, repo: repo.replace(/\.git$/, ""), path: [type, ref, ...rest].filter(Boolean).join("/") };
|
|
942
|
+
} catch {
|
|
943
|
+
return null;
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
if (/^[^/]+\/[^/]+/.test(input)) {
|
|
947
|
+
const [base, ref] = input.split("#", 2);
|
|
948
|
+
const parts = base.split("/").filter(Boolean);
|
|
949
|
+
if (parts.length < 2) return null;
|
|
950
|
+
const [owner, repo, ...rest] = parts;
|
|
951
|
+
const pathPart = rest.length ? rest.join("/") : void 0;
|
|
952
|
+
return { owner, repo, ref, path: pathPart, isFile: looksLikeMarkdownPath(pathPart) };
|
|
953
|
+
}
|
|
954
|
+
return null;
|
|
955
|
+
}
|
|
956
|
+
function toRepoSource(parsed) {
|
|
957
|
+
return buildRepoSource(parsed);
|
|
958
|
+
}
|
|
959
|
+
function buildRepoSource(parsed) {
|
|
960
|
+
const base = `${parsed.owner}/${parsed.repo}${parsed.path ? `/${parsed.path}` : ""}`;
|
|
961
|
+
return parsed.ref ? `${base}#${parsed.ref}` : base;
|
|
962
|
+
}
|
|
963
|
+
async function resolveMarkdownDoc(source, ctx) {
|
|
964
|
+
const parsed = parseGitHubSource(source);
|
|
965
|
+
if (!parsed) return null;
|
|
966
|
+
const ref = parsed.ref || "HEAD";
|
|
967
|
+
const repoKey = `${parsed.owner}/${parsed.repo}#${ref}`;
|
|
968
|
+
let repo = ctx.repoCache.get(repoKey);
|
|
969
|
+
if (!repo) {
|
|
970
|
+
repo = { owner: parsed.owner, repo: parsed.repo, ref };
|
|
971
|
+
ctx.repoCache.set(repoKey, repo);
|
|
972
|
+
}
|
|
973
|
+
const targetPath = parsed.path ? parsed.path.replace(/^\/+/, "") : "";
|
|
974
|
+
if (parsed.isFile || looksLikeMarkdownPath(targetPath)) {
|
|
975
|
+
const content = await fetchMarkdownContent(repo, targetPath);
|
|
976
|
+
if (!content) return null;
|
|
977
|
+
return { repo, docPath: targetPath, content };
|
|
978
|
+
}
|
|
979
|
+
const dirPath = targetPath;
|
|
980
|
+
for (const candidate of README_CANDIDATES) {
|
|
981
|
+
const docPath = dirPath ? path2.posix.join(dirPath, candidate) : candidate;
|
|
982
|
+
const content = await fetchMarkdownContent(repo, docPath);
|
|
983
|
+
if (content) return { repo, docPath, content };
|
|
984
|
+
}
|
|
985
|
+
const repoSpec = `${repo.owner}/${repo.repo}#${repo.ref}`;
|
|
986
|
+
try {
|
|
987
|
+
const materialized = await materializeSourceToTemp(repoSpec);
|
|
988
|
+
const localRepo = { ...repo, dir: materialized.dir, cleanup: materialized.cleanup };
|
|
989
|
+
ctx.repoCache.set(repoKey, localRepo);
|
|
990
|
+
const localTarget = targetPath;
|
|
991
|
+
if (parsed.isFile || looksLikeMarkdownPath(localTarget)) {
|
|
992
|
+
const filePath = path2.join(materialized.dir, localTarget);
|
|
993
|
+
if (fs2.existsSync(filePath)) return { repo: localRepo, docPath: localTarget, filePath };
|
|
994
|
+
return null;
|
|
995
|
+
}
|
|
996
|
+
for (const candidate of README_CANDIDATES) {
|
|
997
|
+
const filePath = path2.join(materialized.dir, dirPath, candidate);
|
|
998
|
+
if (fs2.existsSync(filePath)) {
|
|
999
|
+
return { repo: localRepo, docPath: path2.posix.join(dirPath, candidate), filePath };
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
} catch {
|
|
1003
|
+
return null;
|
|
1004
|
+
}
|
|
1005
|
+
return null;
|
|
1006
|
+
}
|
|
1007
|
+
async function fetchMarkdownContent(repo, docPath) {
|
|
1008
|
+
const ref = repo.ref || "HEAD";
|
|
1009
|
+
const rawUrl = `https://raw.githubusercontent.com/${repo.owner}/${repo.repo}/${ref}/${docPath}`;
|
|
1010
|
+
try {
|
|
1011
|
+
const res = await fetchWithTimeout(rawUrl, { method: "GET" }, 5e3);
|
|
1012
|
+
if (!res.ok) return null;
|
|
1013
|
+
return await res.text();
|
|
1014
|
+
} catch {
|
|
1015
|
+
return null;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
|
|
460
1019
|
// src/commands/install.ts
|
|
461
1020
|
function looksLikeAlias(input) {
|
|
462
1021
|
const s = input.trim();
|
|
@@ -464,7 +1023,7 @@ function looksLikeAlias(input) {
|
|
|
464
1023
|
if (s.startsWith("@")) return false;
|
|
465
1024
|
if (s.includes("/") || s.includes("\\")) return false;
|
|
466
1025
|
if (/^https?:\/\//i.test(s) || s.includes("github.com")) return false;
|
|
467
|
-
if (
|
|
1026
|
+
if (fs3.existsSync(path3.resolve(s))) return false;
|
|
468
1027
|
if (!isValidAlias(s)) return false;
|
|
469
1028
|
return true;
|
|
470
1029
|
}
|
|
@@ -472,6 +1031,21 @@ function printJson(value) {
|
|
|
472
1031
|
process.stdout.write(`${JSON.stringify(value, null, 2)}
|
|
473
1032
|
`);
|
|
474
1033
|
}
|
|
1034
|
+
function appendCleanup(current, next) {
|
|
1035
|
+
if (!next) return current;
|
|
1036
|
+
if (!current) return next;
|
|
1037
|
+
return () => {
|
|
1038
|
+
current();
|
|
1039
|
+
next();
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
function getInstalledPlatforms(scope) {
|
|
1043
|
+
return PLATFORMS2.filter((platform) => listSkills({ platform, scope }).length > 0);
|
|
1044
|
+
}
|
|
1045
|
+
function getPlatformPromptList(scope) {
|
|
1046
|
+
const installed = getInstalledPlatforms(scope);
|
|
1047
|
+
return installed.length > 0 ? installed : [...PLATFORMS2];
|
|
1048
|
+
}
|
|
475
1049
|
function asDiscoveredSkills(discovered, toSuggestedSource, toMaterializedDir) {
|
|
476
1050
|
return discovered.map((d) => ({
|
|
477
1051
|
relPath: d.relPath,
|
|
@@ -519,6 +1093,7 @@ function createContext(source, options) {
|
|
|
519
1093
|
isSingleSkill: false,
|
|
520
1094
|
materializedDir: null,
|
|
521
1095
|
cleanupMaterialized: null,
|
|
1096
|
+
markdownTree: null,
|
|
522
1097
|
results: [],
|
|
523
1098
|
errors: [],
|
|
524
1099
|
skipped: [],
|
|
@@ -564,10 +1139,10 @@ async function discoverSkills(ctx) {
|
|
|
564
1139
|
ctx.selectedSkills = [{ relPath: resolvedSource, suggestedSource: resolvedSource }];
|
|
565
1140
|
return true;
|
|
566
1141
|
}
|
|
567
|
-
const maybeLocalRoot =
|
|
568
|
-
const isLocal =
|
|
1142
|
+
const maybeLocalRoot = path3.resolve(resolvedSource);
|
|
1143
|
+
const isLocal = fs3.existsSync(maybeLocalRoot);
|
|
569
1144
|
if (isLocal) {
|
|
570
|
-
const hasSkillMd2 =
|
|
1145
|
+
const hasSkillMd2 = fs3.existsSync(path3.join(maybeLocalRoot, "SKILL.md"));
|
|
571
1146
|
if (hasSkillMd2) {
|
|
572
1147
|
ctx.isSingleSkill = true;
|
|
573
1148
|
ctx.selectedSkills = [{ relPath: maybeLocalRoot, suggestedSource: resolvedSource }];
|
|
@@ -585,13 +1160,33 @@ async function discoverSkills(ctx) {
|
|
|
585
1160
|
process.exitCode = 1;
|
|
586
1161
|
return false;
|
|
587
1162
|
}
|
|
588
|
-
ctx.discoveredSkills = asDiscoveredSkills(discovered2, (d) =>
|
|
1163
|
+
ctx.discoveredSkills = asDiscoveredSkills(discovered2, (d) => path3.join(maybeLocalRoot, d.relPath));
|
|
589
1164
|
return true;
|
|
590
1165
|
}
|
|
591
|
-
const
|
|
1166
|
+
const markdownResult = await discoverMarkdownSkillsFromSource({
|
|
1167
|
+
source: resolvedSource,
|
|
1168
|
+
maxDepth,
|
|
1169
|
+
maxSkills,
|
|
1170
|
+
onProgress: (update2) => {
|
|
1171
|
+
if (ctx.spinner) {
|
|
1172
|
+
const current = update2.current ? ` \xB7 ${update2.current}` : "";
|
|
1173
|
+
const capped = update2.linkLimitReached ? " \xB7 link cap reached" : "";
|
|
1174
|
+
ctx.spinner.text = `Parsing markdown (${update2.docsScanned} docs, ${update2.linksChecked} links, ${update2.skillsFound} skills)${current}${capped}`;
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
});
|
|
1178
|
+
if (markdownResult && markdownResult.skills.length > 0) {
|
|
1179
|
+
ctx.discoveredSkills = markdownResult.skills;
|
|
1180
|
+
ctx.markdownTree = markdownResult.tree;
|
|
1181
|
+
ctx.isSingleSkill = markdownResult.skills.length === 1;
|
|
1182
|
+
ctx.selectedSkills = ctx.isSingleSkill ? [markdownResult.skills[0]] : null;
|
|
1183
|
+
ctx.cleanupMaterialized = appendCleanup(ctx.cleanupMaterialized, markdownResult.cleanup);
|
|
1184
|
+
return true;
|
|
1185
|
+
}
|
|
1186
|
+
const materialized = await materializeSourceToTemp2(resolvedSource);
|
|
592
1187
|
ctx.materializedDir = materialized.dir;
|
|
593
|
-
ctx.cleanupMaterialized = materialized.cleanup;
|
|
594
|
-
const hasSkillMd =
|
|
1188
|
+
ctx.cleanupMaterialized = appendCleanup(ctx.cleanupMaterialized, materialized.cleanup);
|
|
1189
|
+
const hasSkillMd = fs3.existsSync(path3.join(ctx.materializedDir, "SKILL.md"));
|
|
595
1190
|
if (hasSkillMd) {
|
|
596
1191
|
ctx.isSingleSkill = true;
|
|
597
1192
|
ctx.selectedSkills = [{ relPath: ".", suggestedSource: resolvedSource, materializedDir: ctx.materializedDir }];
|
|
@@ -611,7 +1206,7 @@ async function discoverSkills(ctx) {
|
|
|
611
1206
|
}
|
|
612
1207
|
ctx.discoveredSkills = asDiscoveredSkills(
|
|
613
1208
|
discovered,
|
|
614
|
-
(d) =>
|
|
1209
|
+
(d) => deriveChildSource2(resolvedSource, d.relPath),
|
|
615
1210
|
(d) => d.absDir
|
|
616
1211
|
);
|
|
617
1212
|
return true;
|
|
@@ -631,7 +1226,10 @@ async function promptSelections(ctx) {
|
|
|
631
1226
|
if (isSingleSkill) {
|
|
632
1227
|
if (ctx.needsPlatformPrompt) {
|
|
633
1228
|
if (ctx.spinner) ctx.spinner.stop();
|
|
634
|
-
const selectedPlatforms = await promptPlatformsInteractive({
|
|
1229
|
+
const selectedPlatforms = await promptPlatformsInteractive({
|
|
1230
|
+
defaultAll: true,
|
|
1231
|
+
platforms: getPlatformPromptList(ctx.scope)
|
|
1232
|
+
});
|
|
635
1233
|
if (!selectedPlatforms) {
|
|
636
1234
|
console.log(chalk3.red("No platforms selected."));
|
|
637
1235
|
process.exitCode = 1;
|
|
@@ -685,7 +1283,7 @@ async function promptSelections(ctx) {
|
|
|
685
1283
|
`;
|
|
686
1284
|
console.log(chalk3.yellow(headline));
|
|
687
1285
|
}
|
|
688
|
-
const selected = await promptSkillsInteractive(discoveredSkills, { defaultAll: true });
|
|
1286
|
+
const selected = ctx.markdownTree ? await promptSkillsTreeInteractive(discoveredSkills, ctx.markdownTree, { defaultAll: true }) : await promptSkillsInteractive(discoveredSkills, { defaultAll: true });
|
|
689
1287
|
if (!selected) {
|
|
690
1288
|
console.log(chalk3.red("No skills selected."));
|
|
691
1289
|
process.exitCode = 1;
|
|
@@ -693,7 +1291,10 @@ async function promptSelections(ctx) {
|
|
|
693
1291
|
}
|
|
694
1292
|
ctx.selectedSkills = selected;
|
|
695
1293
|
if (ctx.needsPlatformPrompt) {
|
|
696
|
-
const selectedPlatforms = await promptPlatformsInteractive({
|
|
1294
|
+
const selectedPlatforms = await promptPlatformsInteractive({
|
|
1295
|
+
defaultAll: true,
|
|
1296
|
+
platforms: getPlatformPromptList(ctx.scope)
|
|
1297
|
+
});
|
|
697
1298
|
if (!selectedPlatforms) {
|
|
698
1299
|
console.log(chalk3.red("No platforms selected."));
|
|
699
1300
|
process.exitCode = 1;
|
|
@@ -905,7 +1506,7 @@ async function reportDownload(record, registryOverride) {
|
|
|
905
1506
|
} else {
|
|
906
1507
|
return;
|
|
907
1508
|
}
|
|
908
|
-
await
|
|
1509
|
+
await fetchWithTimeout2(
|
|
909
1510
|
endpoint,
|
|
910
1511
|
{
|
|
911
1512
|
method: "POST",
|
|
@@ -920,7 +1521,7 @@ async function reportDownload(record, registryOverride) {
|
|
|
920
1521
|
|
|
921
1522
|
// src/commands/list.ts
|
|
922
1523
|
import chalk4 from "chalk";
|
|
923
|
-
import { PLATFORMS as PLATFORMS3, listAllSkills, listSkills } from "@skild/core";
|
|
1524
|
+
import { PLATFORMS as PLATFORMS3, listAllSkills, listSkills as listSkills2 } from "@skild/core";
|
|
924
1525
|
function isSkillset(skill) {
|
|
925
1526
|
return Boolean(skill.record?.skillset || skill.record?.skill?.frontmatter?.skillset);
|
|
926
1527
|
}
|
|
@@ -1019,7 +1620,7 @@ async function list(options = {}) {
|
|
|
1019
1620
|
const verbose = Boolean(options.verbose);
|
|
1020
1621
|
const platform = options.target;
|
|
1021
1622
|
if (platform) {
|
|
1022
|
-
const skills =
|
|
1623
|
+
const skills = listSkills2({ platform, scope });
|
|
1023
1624
|
if (options.json) {
|
|
1024
1625
|
console.log(JSON.stringify(skills, null, 2));
|
|
1025
1626
|
return;
|
|
@@ -1185,7 +1786,7 @@ async function init(name, options = {}) {
|
|
|
1185
1786
|
|
|
1186
1787
|
// src/commands/signup.ts
|
|
1187
1788
|
import chalk10 from "chalk";
|
|
1188
|
-
import { fetchWithTimeout as
|
|
1789
|
+
import { fetchWithTimeout as fetchWithTimeout3, resolveRegistryUrl as resolveRegistryUrl2, SkildError as SkildError6 } from "@skild/core";
|
|
1189
1790
|
|
|
1190
1791
|
// src/utils/prompt.ts
|
|
1191
1792
|
import readline2 from "readline";
|
|
@@ -1273,7 +1874,7 @@ async function signup(options) {
|
|
|
1273
1874
|
}
|
|
1274
1875
|
let text = "";
|
|
1275
1876
|
try {
|
|
1276
|
-
const res = await
|
|
1877
|
+
const res = await fetchWithTimeout3(
|
|
1277
1878
|
`${registry}/auth/signup`,
|
|
1278
1879
|
{
|
|
1279
1880
|
method: "POST",
|
|
@@ -1325,7 +1926,7 @@ async function signup(options) {
|
|
|
1325
1926
|
|
|
1326
1927
|
// src/commands/login.ts
|
|
1327
1928
|
import chalk11 from "chalk";
|
|
1328
|
-
import { fetchWithTimeout as
|
|
1929
|
+
import { fetchWithTimeout as fetchWithTimeout4, resolveRegistryUrl as resolveRegistryUrl3, saveRegistryAuth, SkildError as SkildError7 } from "@skild/core";
|
|
1329
1930
|
async function login(options) {
|
|
1330
1931
|
const registry = resolveRegistryUrl3(options.registry);
|
|
1331
1932
|
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
@@ -1352,7 +1953,7 @@ async function login(options) {
|
|
|
1352
1953
|
const finalTokenName = options.tokenName?.trim() || void 0;
|
|
1353
1954
|
let text = "";
|
|
1354
1955
|
try {
|
|
1355
|
-
const res = await
|
|
1956
|
+
const res = await fetchWithTimeout4(
|
|
1356
1957
|
`${registry}/auth/login`,
|
|
1357
1958
|
{
|
|
1358
1959
|
method: "POST",
|
|
@@ -1406,7 +2007,7 @@ async function logout() {
|
|
|
1406
2007
|
|
|
1407
2008
|
// src/commands/whoami.ts
|
|
1408
2009
|
import chalk13 from "chalk";
|
|
1409
|
-
import { fetchWithTimeout as
|
|
2010
|
+
import { fetchWithTimeout as fetchWithTimeout5, loadRegistryAuth as loadRegistryAuth2, resolveRegistryUrl as resolveRegistryUrl4, SkildError as SkildError8 } from "@skild/core";
|
|
1410
2011
|
async function whoami() {
|
|
1411
2012
|
const auth = loadRegistryAuth2();
|
|
1412
2013
|
if (!auth) {
|
|
@@ -1416,7 +2017,7 @@ async function whoami() {
|
|
|
1416
2017
|
}
|
|
1417
2018
|
const registryUrl = resolveRegistryUrl4(auth.registryUrl);
|
|
1418
2019
|
try {
|
|
1419
|
-
const res = await
|
|
2020
|
+
const res = await fetchWithTimeout5(
|
|
1420
2021
|
`${registryUrl}/auth/me`,
|
|
1421
2022
|
{ headers: { authorization: `Bearer ${auth.token}`, accept: "application/json" } },
|
|
1422
2023
|
5e3
|
|
@@ -1438,13 +2039,13 @@ async function whoami() {
|
|
|
1438
2039
|
}
|
|
1439
2040
|
|
|
1440
2041
|
// src/commands/publish.ts
|
|
1441
|
-
import
|
|
2042
|
+
import fs4 from "fs";
|
|
1442
2043
|
import os from "os";
|
|
1443
|
-
import
|
|
2044
|
+
import path4 from "path";
|
|
1444
2045
|
import crypto from "crypto";
|
|
1445
2046
|
import * as tar from "tar";
|
|
1446
2047
|
import chalk14 from "chalk";
|
|
1447
|
-
import { assertValidAlias, fetchWithTimeout as
|
|
2048
|
+
import { assertValidAlias, fetchWithTimeout as fetchWithTimeout6, loadRegistryAuth as loadRegistryAuth3, normalizeAlias, resolveRegistryUrl as resolveRegistryUrl5, SkildError as SkildError9, splitCanonicalName, validateSkillDir } from "@skild/core";
|
|
1448
2049
|
function sha256Hex(buf) {
|
|
1449
2050
|
const h = crypto.createHash("sha256");
|
|
1450
2051
|
h.update(buf);
|
|
@@ -1463,7 +2064,7 @@ async function publish(options = {}) {
|
|
|
1463
2064
|
process.exitCode = 1;
|
|
1464
2065
|
return;
|
|
1465
2066
|
}
|
|
1466
|
-
const dir =
|
|
2067
|
+
const dir = path4.resolve(options.dir || process.cwd());
|
|
1467
2068
|
const validation = validateSkillDir(dir);
|
|
1468
2069
|
if (!validation.ok) {
|
|
1469
2070
|
console.error(chalk14.red("Skill validation failed:"));
|
|
@@ -1492,7 +2093,7 @@ async function publish(options = {}) {
|
|
|
1492
2093
|
process.exitCode = 1;
|
|
1493
2094
|
return;
|
|
1494
2095
|
}
|
|
1495
|
-
const meRes = await
|
|
2096
|
+
const meRes = await fetchWithTimeout6(
|
|
1496
2097
|
`${registry}/auth/me`,
|
|
1497
2098
|
{ headers: { authorization: `Bearer ${token}` } },
|
|
1498
2099
|
1e4
|
|
@@ -1533,8 +2134,8 @@ async function publish(options = {}) {
|
|
|
1533
2134
|
}
|
|
1534
2135
|
}
|
|
1535
2136
|
const spinner = createSpinner(`Publishing ${chalk14.cyan(`${name}@${version2}`)} to ${chalk14.dim(registry)}...`);
|
|
1536
|
-
const tempDir =
|
|
1537
|
-
const tarballPath =
|
|
2137
|
+
const tempDir = fs4.mkdtempSync(path4.join(os.tmpdir(), "skild-publish-"));
|
|
2138
|
+
const tarballPath = path4.join(tempDir, "skill.tgz");
|
|
1538
2139
|
try {
|
|
1539
2140
|
await tar.c(
|
|
1540
2141
|
{
|
|
@@ -1546,7 +2147,7 @@ async function publish(options = {}) {
|
|
|
1546
2147
|
},
|
|
1547
2148
|
["."]
|
|
1548
2149
|
);
|
|
1549
|
-
const buf =
|
|
2150
|
+
const buf = fs4.readFileSync(tarballPath);
|
|
1550
2151
|
const integrity = sha256Hex(buf);
|
|
1551
2152
|
const form = new FormData();
|
|
1552
2153
|
form.set("version", version2);
|
|
@@ -1557,7 +2158,7 @@ async function publish(options = {}) {
|
|
|
1557
2158
|
form.set("dependencies", JSON.stringify(dependencies));
|
|
1558
2159
|
form.append("tarball", new Blob([buf], { type: "application/gzip" }), "skill.tgz");
|
|
1559
2160
|
const { scope, name: skillName } = splitCanonicalName(name);
|
|
1560
|
-
const res = await
|
|
2161
|
+
const res = await fetchWithTimeout6(
|
|
1561
2162
|
`${registry}/skills/${encodeURIComponent(scope)}/${encodeURIComponent(skillName)}/publish`,
|
|
1562
2163
|
{
|
|
1563
2164
|
method: "POST",
|
|
@@ -1575,7 +2176,7 @@ async function publish(options = {}) {
|
|
|
1575
2176
|
}
|
|
1576
2177
|
if (alias) {
|
|
1577
2178
|
spinner.text = `Publishing ${chalk14.cyan(`${name}@${version2}`)} \u2014 setting alias ${chalk14.cyan(alias)}...`;
|
|
1578
|
-
const aliasRes = await
|
|
2179
|
+
const aliasRes = await fetchWithTimeout6(
|
|
1579
2180
|
`${registry}/publisher/skills/${encodeURIComponent(scope)}/${encodeURIComponent(skillName)}/alias`,
|
|
1580
2181
|
{
|
|
1581
2182
|
method: "POST",
|
|
@@ -1613,7 +2214,7 @@ async function publish(options = {}) {
|
|
|
1613
2214
|
console.error(chalk14.red(message));
|
|
1614
2215
|
process.exitCode = 1;
|
|
1615
2216
|
} finally {
|
|
1616
|
-
|
|
2217
|
+
fs4.rmSync(tempDir, { recursive: true, force: true });
|
|
1617
2218
|
}
|
|
1618
2219
|
}
|
|
1619
2220
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skild",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "The npm for Agent Skills — Discover, install, manage, and publish AI Agent Skills with ease.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -36,11 +36,15 @@
|
|
|
36
36
|
"@inquirer/prompts": "^8.2.0",
|
|
37
37
|
"chalk": "^5.3.0",
|
|
38
38
|
"commander": "^12.1.0",
|
|
39
|
+
"mdast-util-to-string": "^4.0.0",
|
|
39
40
|
"ora": "^8.0.1",
|
|
41
|
+
"remark-parse": "^11.0.0",
|
|
40
42
|
"tar": "^7.4.3",
|
|
43
|
+
"unified": "^11.0.4",
|
|
41
44
|
"@skild/core": "^0.5.2"
|
|
42
45
|
},
|
|
43
46
|
"devDependencies": {
|
|
47
|
+
"@types/mdast": "^4.0.4",
|
|
44
48
|
"@types/node": "^20.10.0",
|
|
45
49
|
"tsup": "^8.0.0",
|
|
46
50
|
"typescript": "^5.3.0"
|