@skillsmith/cli 0.7.3 → 0.7.4
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/CHANGELOG.md +4 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/cli.js +703 -728
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1443,7 +1443,7 @@ var init_open = __esm({
|
|
|
1443
1443
|
import { Command as Command28 } from "commander";
|
|
1444
1444
|
|
|
1445
1445
|
// src/commands/search.ts
|
|
1446
|
-
import { Command } from "commander";
|
|
1446
|
+
import { Command as Command2 } from "commander";
|
|
1447
1447
|
|
|
1448
1448
|
// src/config.ts
|
|
1449
1449
|
import { join as join3 } from "path";
|
|
@@ -2145,7 +2145,7 @@ var chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 });
|
|
|
2145
2145
|
var source_default = chalk;
|
|
2146
2146
|
|
|
2147
2147
|
// src/commands/search.action.ts
|
|
2148
|
-
import
|
|
2148
|
+
import ora2 from "ora";
|
|
2149
2149
|
import { input, checkbox, number as number4, select } from "@inquirer/prompts";
|
|
2150
2150
|
|
|
2151
2151
|
// ../core/dist/src/repositories/CacheRepository.js
|
|
@@ -18514,6 +18514,164 @@ function scanSsrfPatterns(content, lineContexts) {
|
|
|
18514
18514
|
return findings;
|
|
18515
18515
|
}
|
|
18516
18516
|
|
|
18517
|
+
// ../core/dist/src/security/scanner/SecurityScanner.pii.js
|
|
18518
|
+
var CREDENTIAL_PII_INDICES = /* @__PURE__ */ new Set([0, 1, 2, 3, 4, 5, 6, 10]);
|
|
18519
|
+
var PLACEHOLDER_SECRET_RE = /EXAMPLE|(?<![A-Za-z0-9])YOUR[_-]?|PLACEHOLDER|CHANGE[_-]?ME|(?<![A-Za-z0-9])DUMMY|(?<![A-Za-z0-9])FAKE|(?<![A-Za-z0-9])SAMPLE|REDACTED|INSERT[_-]|\.\.\.|<[^>]+>/i;
|
|
18520
|
+
var SECRET_ENTROPY_FLOOR = 3;
|
|
18521
|
+
function shannonEntropy(s) {
|
|
18522
|
+
if (!s)
|
|
18523
|
+
return 0;
|
|
18524
|
+
const freq = /* @__PURE__ */ new Map();
|
|
18525
|
+
for (const ch of s)
|
|
18526
|
+
freq.set(ch, (freq.get(ch) ?? 0) + 1);
|
|
18527
|
+
let h = 0;
|
|
18528
|
+
for (const c of freq.values()) {
|
|
18529
|
+
const p = c / s.length;
|
|
18530
|
+
h -= p * Math.log2(p);
|
|
18531
|
+
}
|
|
18532
|
+
return h;
|
|
18533
|
+
}
|
|
18534
|
+
function extractSecretValue(match) {
|
|
18535
|
+
return match.replace(/^[^:=]*[:=]\s*/, "").replace(/^['"]|['"]$/g, "").trim();
|
|
18536
|
+
}
|
|
18537
|
+
function looksLikePlaceholderSecret(match) {
|
|
18538
|
+
if (PLACEHOLDER_SECRET_RE.test(match))
|
|
18539
|
+
return true;
|
|
18540
|
+
const value = extractSecretValue(match);
|
|
18541
|
+
if (value.length === 0)
|
|
18542
|
+
return false;
|
|
18543
|
+
if (/^(.)\1+$/.test(value))
|
|
18544
|
+
return true;
|
|
18545
|
+
return shannonEntropy(value) < SECRET_ENTROPY_FLOOR;
|
|
18546
|
+
}
|
|
18547
|
+
function scanPiiPatterns(content, lineContexts) {
|
|
18548
|
+
const findings = [];
|
|
18549
|
+
const lines = content.split("\n");
|
|
18550
|
+
const contexts = lineContexts ?? analyzeMarkdownContext(content);
|
|
18551
|
+
let frontmatterEnd = -1;
|
|
18552
|
+
if (lines[0]?.trim() === "---") {
|
|
18553
|
+
for (let i = 1; i < lines.length; i++) {
|
|
18554
|
+
if (lines[i].trim() === "---") {
|
|
18555
|
+
frontmatterEnd = i;
|
|
18556
|
+
break;
|
|
18557
|
+
}
|
|
18558
|
+
}
|
|
18559
|
+
}
|
|
18560
|
+
const emailPatternIndex = 7;
|
|
18561
|
+
lines.forEach((line, index) => {
|
|
18562
|
+
const ctx = contexts[index];
|
|
18563
|
+
const inFrontmatter = index > 0 && index < frontmatterEnd;
|
|
18564
|
+
for (let pi = 0; pi < PII_PATTERNS.length; pi++) {
|
|
18565
|
+
const pattern = PII_PATTERNS[pi];
|
|
18566
|
+
const match = safeRegexTest(pattern, line);
|
|
18567
|
+
if (match) {
|
|
18568
|
+
const inInlineCode = ctx?.isInlineCode && isWithinInlineCode(line, match.index ?? 0);
|
|
18569
|
+
const inDocContext = ctx ? isDocumentationContext(ctx) || inInlineCode : false;
|
|
18570
|
+
const isEmailPattern = pi === emailPatternIndex;
|
|
18571
|
+
const isAuthorLine = /^\s*(?:author|contact|support|email)\s*:/i.test(line);
|
|
18572
|
+
const inEmailSafeContext = isEmailPattern && (inFrontmatter || isAuthorLine);
|
|
18573
|
+
let severity;
|
|
18574
|
+
if (inEmailSafeContext)
|
|
18575
|
+
severity = "low";
|
|
18576
|
+
else if (inDocContext)
|
|
18577
|
+
severity = "medium";
|
|
18578
|
+
else if (pi <= 2 || pi === 9)
|
|
18579
|
+
severity = "critical";
|
|
18580
|
+
else
|
|
18581
|
+
severity = "high";
|
|
18582
|
+
let confidence = inDocContext || inEmailSafeContext ? "low" : "high";
|
|
18583
|
+
if (CREDENTIAL_PII_INDICES.has(pi) && looksLikePlaceholderSecret(match[0])) {
|
|
18584
|
+
severity = "low";
|
|
18585
|
+
confidence = "low";
|
|
18586
|
+
}
|
|
18587
|
+
findings.push({
|
|
18588
|
+
type: "pii",
|
|
18589
|
+
severity,
|
|
18590
|
+
message: `PII detected: ${match[0].slice(0, 40)}${match[0].length > 40 ? "..." : ""}`,
|
|
18591
|
+
location: line.trim().slice(0, 100),
|
|
18592
|
+
lineNumber: index + 1,
|
|
18593
|
+
category: "pii",
|
|
18594
|
+
inDocumentationContext: inDocContext || inEmailSafeContext,
|
|
18595
|
+
confidence
|
|
18596
|
+
});
|
|
18597
|
+
break;
|
|
18598
|
+
}
|
|
18599
|
+
}
|
|
18600
|
+
});
|
|
18601
|
+
return findings;
|
|
18602
|
+
}
|
|
18603
|
+
|
|
18604
|
+
// ../core/dist/src/security/scanner/SecurityScanner.compound.js
|
|
18605
|
+
var OWNER_PERM_CHMOD = /\bchmod\s+(?:-[A-Za-z]+\s+)?(?:[0-7]{3,4}|[ugoa]*(?:[+\-=][rwxXstugo]+(?:,[ugoa]*[+\-=][rwxXstugo]*)*)+)/i;
|
|
18606
|
+
var CHMOD_FETCH_CONTEXT = /\b(?:curl|wget)\b|\bgit\s+clone\b|\bnpx\b[^\n]{0,80}https?:\/\//i;
|
|
18607
|
+
var CHMOD_TARGET = /\bchmod\s+(?:-[A-Za-z]+\s+)?(?:[0-7]{3,4}|[ugoa]*(?:[+\-=][rwxXstugo]+(?:,[ugoa]*[+\-=][rwxXstugo]*)*)+)\s+(\S+)/i;
|
|
18608
|
+
function escapeRegExp(s) {
|
|
18609
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
18610
|
+
}
|
|
18611
|
+
function implicitDownloadBasename(line) {
|
|
18612
|
+
const lastSegment = (urlAfterScheme) => {
|
|
18613
|
+
const noFrag = urlAfterScheme.split(/[?#]/)[0];
|
|
18614
|
+
const slash = noFrag.indexOf("/");
|
|
18615
|
+
if (slash < 0)
|
|
18616
|
+
return "";
|
|
18617
|
+
const path22 = noFrag.slice(slash + 1).replace(/\/+$/, "");
|
|
18618
|
+
return path22 === "" ? "" : path22.split("/").pop() ?? "";
|
|
18619
|
+
};
|
|
18620
|
+
const wget = line.match(/\bwget\b(?![^\n]{0,200}\s-[oO]\b)[^\n]{0,200}?https?:\/\/(\S{1,400})/i);
|
|
18621
|
+
if (wget)
|
|
18622
|
+
return lastSegment(wget[1]);
|
|
18623
|
+
const clone2 = line.match(/\bgit\s+clone\b[^\n]{0,200}?https?:\/\/(\S{1,400})/i);
|
|
18624
|
+
if (clone2)
|
|
18625
|
+
return lastSegment(clone2[1]).replace(/\.git$/i, "");
|
|
18626
|
+
const curlEq = line.match(/\bcurl\b[^\n]{0,200}?--output=['"]?(\S{1,400})/i);
|
|
18627
|
+
if (curlEq)
|
|
18628
|
+
return curlEq[1].replace(/['"]/g, "").split("/").pop() ?? "";
|
|
18629
|
+
return "";
|
|
18630
|
+
}
|
|
18631
|
+
function scanChmodFetchCompound(content, alreadyFlaggedLines, lineContexts) {
|
|
18632
|
+
const findings = [];
|
|
18633
|
+
const lines = content.split("\n");
|
|
18634
|
+
const contexts = lineContexts ?? analyzeMarkdownContext(content);
|
|
18635
|
+
const fetchLines = lines.filter((l) => safeRegexTest(CHMOD_FETCH_CONTEXT, l) !== null);
|
|
18636
|
+
lines.forEach((line, index) => {
|
|
18637
|
+
const lineNumber = index + 1;
|
|
18638
|
+
if (alreadyFlaggedLines.has(lineNumber))
|
|
18639
|
+
return;
|
|
18640
|
+
const match = safeRegexTest(OWNER_PERM_CHMOD, line);
|
|
18641
|
+
if (!match)
|
|
18642
|
+
return;
|
|
18643
|
+
const window2 = [lines[index - 1] ?? "", line, lines[index + 1] ?? ""].join("\n");
|
|
18644
|
+
const adjacentFetch = safeRegexTest(CHMOD_FETCH_CONTEXT, window2) !== null;
|
|
18645
|
+
let correlated = false;
|
|
18646
|
+
const tm = safeRegexTest(CHMOD_TARGET, line);
|
|
18647
|
+
if (tm) {
|
|
18648
|
+
const base = tm[1].replace(/['"]/g, "").split("/").pop() ?? "";
|
|
18649
|
+
if (base.length >= 3) {
|
|
18650
|
+
const re = new RegExp(`(?:-o|-O|--output|>>?)\\s*['"]?(?:[^\\s'"]*/)?${escapeRegExp(base)}(?:[\\s'"?]|$)`);
|
|
18651
|
+
correlated = fetchLines.some((l) => re.test(l) || implicitDownloadBasename(l) === base);
|
|
18652
|
+
}
|
|
18653
|
+
}
|
|
18654
|
+
if (!adjacentFetch && !correlated)
|
|
18655
|
+
return;
|
|
18656
|
+
const ctx = contexts[index];
|
|
18657
|
+
const inInlineCode = ctx?.isInlineCode && isWithinInlineCode(line, match.index ?? 0);
|
|
18658
|
+
const inDocContext = ctx ? isDocumentationContext(ctx) || inInlineCode : false;
|
|
18659
|
+
findings.push({
|
|
18660
|
+
type: "privilege_escalation",
|
|
18661
|
+
// HIGH (not critical): enough to trip Gate-A AND serve as an
|
|
18662
|
+
// escalateCodeExecution co-signal, without re-introducing a critical FP.
|
|
18663
|
+
severity: inDocContext ? "low" : "high",
|
|
18664
|
+
message: `chmod of a fetched/downloaded file (compound with a download verb): "${match[0]}"`,
|
|
18665
|
+
location: line.trim().slice(0, 100),
|
|
18666
|
+
lineNumber,
|
|
18667
|
+
category: "privilege_escalation",
|
|
18668
|
+
inDocumentationContext: inDocContext,
|
|
18669
|
+
confidence: inDocContext ? "low" : "high"
|
|
18670
|
+
});
|
|
18671
|
+
});
|
|
18672
|
+
return findings;
|
|
18673
|
+
}
|
|
18674
|
+
|
|
18517
18675
|
// ../core/dist/src/security/scanner/SecurityScanner.scanners.js
|
|
18518
18676
|
var ENV_EXFIL_CONTEXT = /\b(?:cat|cp|mv|scp|rsync|source|curl|wget|fetch|less|more|head|tail|tee|upload|tar|zip|gzip|base64|xxd|dd|nc|netcat)\b|[|>]/i;
|
|
18519
18677
|
var CREDENTIAL_ASSIGNMENT = /(?:api[_-]?key|apikey|auth[_-]?token|authtoken)\s*[:=]\s*.+$/i;
|
|
@@ -18673,234 +18831,80 @@ function scanPrivilegeEscalation(content, lineContexts) {
|
|
|
18673
18831
|
});
|
|
18674
18832
|
return findings;
|
|
18675
18833
|
}
|
|
18676
|
-
|
|
18677
|
-
|
|
18678
|
-
var
|
|
18679
|
-
|
|
18680
|
-
|
|
18834
|
+
|
|
18835
|
+
// ../core/dist/src/security/scanner/SecurityScanner.exec.js
|
|
18836
|
+
var INVISIBLE_RANGE = "\\u0300-\\u036F\\u00AD\\u061C\\u180E\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u206F\\uFEFF";
|
|
18837
|
+
var INVISIBLE_TEST = new RegExp("[" + INVISIBLE_RANGE + "]|[\\u{E0000}-\\u{E007F}]", "u");
|
|
18838
|
+
var INVISIBLE_STRIP = new RegExp("[" + INVISIBLE_RANGE + "]|[\\u{E0000}-\\u{E007F}]", "gu");
|
|
18839
|
+
var CONFUSABLES = {
|
|
18840
|
+
// Cyrillic -> Latin
|
|
18841
|
+
\u0430: "a",
|
|
18842
|
+
\u0435: "e",
|
|
18843
|
+
\u043E: "o",
|
|
18844
|
+
\u0440: "p",
|
|
18845
|
+
\u0441: "c",
|
|
18846
|
+
\u0443: "y",
|
|
18847
|
+
\u0445: "x",
|
|
18848
|
+
\u0456: "i",
|
|
18849
|
+
\u0458: "j",
|
|
18850
|
+
\u0455: "s",
|
|
18851
|
+
"\u0501": "d",
|
|
18852
|
+
\u04BB: "h",
|
|
18853
|
+
\u043A: "k",
|
|
18854
|
+
\u043C: "m",
|
|
18855
|
+
\u0442: "t",
|
|
18856
|
+
\u0432: "b",
|
|
18857
|
+
\u043D: "h",
|
|
18858
|
+
// Greek -> Latin
|
|
18859
|
+
\u03BF: "o",
|
|
18860
|
+
\u03B1: "a",
|
|
18861
|
+
\u03C1: "p",
|
|
18862
|
+
\u03B5: "e",
|
|
18863
|
+
\u03C4: "t",
|
|
18864
|
+
\u03B9: "i",
|
|
18865
|
+
\u03BA: "k",
|
|
18866
|
+
\u03C5: "u",
|
|
18867
|
+
\u03C7: "x",
|
|
18868
|
+
\u03BD: "v",
|
|
18869
|
+
\u03F2: "c",
|
|
18870
|
+
\u03B2: "b"
|
|
18871
|
+
};
|
|
18872
|
+
function isFullwidthLatin(cp) {
|
|
18873
|
+
return cp >= 65313 && cp <= 65338 || cp >= 65345 && cp <= 65370;
|
|
18681
18874
|
}
|
|
18682
|
-
function
|
|
18683
|
-
|
|
18684
|
-
const noFrag = urlAfterScheme.split(/[?#]/)[0];
|
|
18685
|
-
const slash = noFrag.indexOf("/");
|
|
18686
|
-
if (slash < 0)
|
|
18687
|
-
return "";
|
|
18688
|
-
const path22 = noFrag.slice(slash + 1).replace(/\/+$/, "");
|
|
18689
|
-
return path22 === "" ? "" : path22.split("/").pop() ?? "";
|
|
18690
|
-
};
|
|
18691
|
-
const wget = line.match(/\bwget\b(?![^\n]{0,200}\s-[oO]\b)[^\n]{0,200}?https?:\/\/(\S{1,400})/i);
|
|
18692
|
-
if (wget)
|
|
18693
|
-
return lastSegment(wget[1]);
|
|
18694
|
-
const clone2 = line.match(/\bgit\s+clone\b[^\n]{0,200}?https?:\/\/(\S{1,400})/i);
|
|
18695
|
-
if (clone2)
|
|
18696
|
-
return lastSegment(clone2[1]).replace(/\.git$/i, "");
|
|
18697
|
-
const curlEq = line.match(/\bcurl\b[^\n]{0,200}?--output=['"]?(\S{1,400})/i);
|
|
18698
|
-
if (curlEq)
|
|
18699
|
-
return curlEq[1].replace(/['"]/g, "").split("/").pop() ?? "";
|
|
18700
|
-
return "";
|
|
18875
|
+
function isMathAlphanumeric(cp) {
|
|
18876
|
+
return cp >= 119808 && cp <= 120831;
|
|
18701
18877
|
}
|
|
18702
|
-
function
|
|
18703
|
-
|
|
18704
|
-
|
|
18705
|
-
|
|
18706
|
-
|
|
18707
|
-
|
|
18708
|
-
const
|
|
18709
|
-
if (
|
|
18710
|
-
|
|
18711
|
-
|
|
18712
|
-
|
|
18713
|
-
|
|
18714
|
-
|
|
18715
|
-
|
|
18716
|
-
|
|
18717
|
-
|
|
18718
|
-
|
|
18719
|
-
|
|
18720
|
-
|
|
18721
|
-
|
|
18722
|
-
|
|
18723
|
-
|
|
18724
|
-
|
|
18725
|
-
if (
|
|
18726
|
-
return;
|
|
18727
|
-
|
|
18728
|
-
|
|
18729
|
-
|
|
18730
|
-
|
|
18731
|
-
|
|
18732
|
-
// HIGH (not critical): enough to trip Gate-A AND serve as an
|
|
18733
|
-
// escalateCodeExecution co-signal, without re-introducing a critical FP.
|
|
18734
|
-
severity: inDocContext ? "low" : "high",
|
|
18735
|
-
message: `chmod of a fetched/downloaded file (compound with a download verb): "${match[0]}"`,
|
|
18736
|
-
location: line.trim().slice(0, 100),
|
|
18737
|
-
lineNumber,
|
|
18738
|
-
category: "privilege_escalation",
|
|
18739
|
-
inDocumentationContext: inDocContext,
|
|
18740
|
-
confidence: inDocContext ? "low" : "high"
|
|
18741
|
-
});
|
|
18742
|
-
});
|
|
18743
|
-
return findings;
|
|
18744
|
-
}
|
|
18745
|
-
var CREDENTIAL_PII_INDICES = /* @__PURE__ */ new Set([0, 1, 2, 3, 4, 5, 6, 10]);
|
|
18746
|
-
var PLACEHOLDER_SECRET_RE = /EXAMPLE|(?<![A-Za-z0-9])YOUR[_-]?|PLACEHOLDER|CHANGE[_-]?ME|(?<![A-Za-z0-9])DUMMY|(?<![A-Za-z0-9])FAKE|(?<![A-Za-z0-9])SAMPLE|REDACTED|INSERT[_-]|\.\.\.|<[^>]+>/i;
|
|
18747
|
-
var SECRET_ENTROPY_FLOOR = 3;
|
|
18748
|
-
function shannonEntropy(s) {
|
|
18749
|
-
if (!s)
|
|
18750
|
-
return 0;
|
|
18751
|
-
const freq = /* @__PURE__ */ new Map();
|
|
18752
|
-
for (const ch of s)
|
|
18753
|
-
freq.set(ch, (freq.get(ch) ?? 0) + 1);
|
|
18754
|
-
let h = 0;
|
|
18755
|
-
for (const c of freq.values()) {
|
|
18756
|
-
const p = c / s.length;
|
|
18757
|
-
h -= p * Math.log2(p);
|
|
18758
|
-
}
|
|
18759
|
-
return h;
|
|
18760
|
-
}
|
|
18761
|
-
function extractSecretValue(match) {
|
|
18762
|
-
return match.replace(/^[^:=]*[:=]\s*/, "").replace(/^['"]|['"]$/g, "").trim();
|
|
18763
|
-
}
|
|
18764
|
-
function looksLikePlaceholderSecret(match) {
|
|
18765
|
-
if (PLACEHOLDER_SECRET_RE.test(match))
|
|
18766
|
-
return true;
|
|
18767
|
-
const value = extractSecretValue(match);
|
|
18768
|
-
if (value.length === 0)
|
|
18769
|
-
return false;
|
|
18770
|
-
if (/^(.)\1+$/.test(value))
|
|
18771
|
-
return true;
|
|
18772
|
-
return shannonEntropy(value) < SECRET_ENTROPY_FLOOR;
|
|
18773
|
-
}
|
|
18774
|
-
function scanPiiPatterns(content, lineContexts) {
|
|
18775
|
-
const findings = [];
|
|
18776
|
-
const lines = content.split("\n");
|
|
18777
|
-
const contexts = lineContexts ?? analyzeMarkdownContext(content);
|
|
18778
|
-
let frontmatterEnd = -1;
|
|
18779
|
-
if (lines[0]?.trim() === "---") {
|
|
18780
|
-
for (let i = 1; i < lines.length; i++) {
|
|
18781
|
-
if (lines[i].trim() === "---") {
|
|
18782
|
-
frontmatterEnd = i;
|
|
18783
|
-
break;
|
|
18784
|
-
}
|
|
18785
|
-
}
|
|
18786
|
-
}
|
|
18787
|
-
const emailPatternIndex = 7;
|
|
18788
|
-
lines.forEach((line, index) => {
|
|
18789
|
-
const ctx = contexts[index];
|
|
18790
|
-
const inFrontmatter = index > 0 && index < frontmatterEnd;
|
|
18791
|
-
for (let pi = 0; pi < PII_PATTERNS.length; pi++) {
|
|
18792
|
-
const pattern = PII_PATTERNS[pi];
|
|
18793
|
-
const match = safeRegexTest(pattern, line);
|
|
18794
|
-
if (match) {
|
|
18795
|
-
const inInlineCode = ctx?.isInlineCode && isWithinInlineCode(line, match.index ?? 0);
|
|
18796
|
-
const inDocContext = ctx ? isDocumentationContext(ctx) || inInlineCode : false;
|
|
18797
|
-
const isEmailPattern = pi === emailPatternIndex;
|
|
18798
|
-
const isAuthorLine = /^\s*(?:author|contact|support|email)\s*:/i.test(line);
|
|
18799
|
-
const inEmailSafeContext = isEmailPattern && (inFrontmatter || isAuthorLine);
|
|
18800
|
-
let severity;
|
|
18801
|
-
if (inEmailSafeContext)
|
|
18802
|
-
severity = "low";
|
|
18803
|
-
else if (inDocContext)
|
|
18804
|
-
severity = "medium";
|
|
18805
|
-
else if (pi <= 2 || pi === 9)
|
|
18806
|
-
severity = "critical";
|
|
18807
|
-
else
|
|
18808
|
-
severity = "high";
|
|
18809
|
-
let confidence = inDocContext || inEmailSafeContext ? "low" : "high";
|
|
18810
|
-
if (CREDENTIAL_PII_INDICES.has(pi) && looksLikePlaceholderSecret(match[0])) {
|
|
18811
|
-
severity = "low";
|
|
18812
|
-
confidence = "low";
|
|
18813
|
-
}
|
|
18814
|
-
findings.push({
|
|
18815
|
-
type: "pii",
|
|
18816
|
-
severity,
|
|
18817
|
-
message: `PII detected: ${match[0].slice(0, 40)}${match[0].length > 40 ? "..." : ""}`,
|
|
18818
|
-
location: line.trim().slice(0, 100),
|
|
18819
|
-
lineNumber: index + 1,
|
|
18820
|
-
category: "pii",
|
|
18821
|
-
inDocumentationContext: inDocContext || inEmailSafeContext,
|
|
18822
|
-
confidence
|
|
18823
|
-
});
|
|
18824
|
-
break;
|
|
18825
|
-
}
|
|
18826
|
-
}
|
|
18827
|
-
});
|
|
18828
|
-
return findings;
|
|
18829
|
-
}
|
|
18830
|
-
|
|
18831
|
-
// ../core/dist/src/security/scanner/SecurityScanner.exec.js
|
|
18832
|
-
var INVISIBLE_RANGE = "\\u0300-\\u036F\\u00AD\\u061C\\u180E\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u206F\\uFEFF";
|
|
18833
|
-
var INVISIBLE_TEST = new RegExp("[" + INVISIBLE_RANGE + "]|[\\u{E0000}-\\u{E007F}]", "u");
|
|
18834
|
-
var INVISIBLE_STRIP = new RegExp("[" + INVISIBLE_RANGE + "]|[\\u{E0000}-\\u{E007F}]", "gu");
|
|
18835
|
-
var CONFUSABLES = {
|
|
18836
|
-
// Cyrillic -> Latin
|
|
18837
|
-
\u0430: "a",
|
|
18838
|
-
\u0435: "e",
|
|
18839
|
-
\u043E: "o",
|
|
18840
|
-
\u0440: "p",
|
|
18841
|
-
\u0441: "c",
|
|
18842
|
-
\u0443: "y",
|
|
18843
|
-
\u0445: "x",
|
|
18844
|
-
\u0456: "i",
|
|
18845
|
-
\u0458: "j",
|
|
18846
|
-
\u0455: "s",
|
|
18847
|
-
"\u0501": "d",
|
|
18848
|
-
\u04BB: "h",
|
|
18849
|
-
\u043A: "k",
|
|
18850
|
-
\u043C: "m",
|
|
18851
|
-
\u0442: "t",
|
|
18852
|
-
\u0432: "b",
|
|
18853
|
-
\u043D: "h",
|
|
18854
|
-
// Greek -> Latin
|
|
18855
|
-
\u03BF: "o",
|
|
18856
|
-
\u03B1: "a",
|
|
18857
|
-
\u03C1: "p",
|
|
18858
|
-
\u03B5: "e",
|
|
18859
|
-
\u03C4: "t",
|
|
18860
|
-
\u03B9: "i",
|
|
18861
|
-
\u03BA: "k",
|
|
18862
|
-
\u03C5: "u",
|
|
18863
|
-
\u03C7: "x",
|
|
18864
|
-
\u03BD: "v",
|
|
18865
|
-
\u03F2: "c",
|
|
18866
|
-
\u03B2: "b"
|
|
18867
|
-
};
|
|
18868
|
-
function isFullwidthLatin(cp) {
|
|
18869
|
-
return cp >= 65313 && cp <= 65338 || cp >= 65345 && cp <= 65370;
|
|
18870
|
-
}
|
|
18871
|
-
function isMathAlphanumeric(cp) {
|
|
18872
|
-
return cp >= 119808 && cp <= 120831;
|
|
18873
|
-
}
|
|
18874
|
-
function stripInvisible(s) {
|
|
18875
|
-
return s.replace(INVISIBLE_STRIP, "");
|
|
18876
|
-
}
|
|
18877
|
-
function confusableSkeleton(s) {
|
|
18878
|
-
let out = "";
|
|
18879
|
-
for (const ch of s) {
|
|
18880
|
-
const cp = ch.codePointAt(0) ?? 0;
|
|
18881
|
-
if (isFullwidthLatin(cp)) {
|
|
18882
|
-
out += String.fromCodePoint(cp - 65248);
|
|
18883
|
-
} else if (isMathAlphanumeric(cp)) {
|
|
18884
|
-
const folded = ch.normalize("NFKC");
|
|
18885
|
-
out += CONFUSABLES[folded] ?? folded;
|
|
18886
|
-
} else if (CONFUSABLES[ch]) {
|
|
18887
|
-
out += CONFUSABLES[ch];
|
|
18888
|
-
} else {
|
|
18889
|
-
out += ch;
|
|
18890
|
-
}
|
|
18891
|
-
}
|
|
18892
|
-
return out;
|
|
18893
|
-
}
|
|
18894
|
-
function hasConfusable(s) {
|
|
18895
|
-
for (const ch of s) {
|
|
18896
|
-
const cp = ch.codePointAt(0) ?? 0;
|
|
18897
|
-
if (isFullwidthLatin(cp) || isMathAlphanumeric(cp) || CONFUSABLES[ch])
|
|
18898
|
-
return true;
|
|
18899
|
-
}
|
|
18900
|
-
return false;
|
|
18901
|
-
}
|
|
18902
|
-
var OBFUSCATION_DIRECTIVE_PATTERN = /(?:ignore|disregard|forget)\s+(?:all\s+|the\s+)?(?:previous|prior|above|earlier)\s+(?:instruction|prompt|rule|direction)|bypass\s+(?:all\s+)?(?:restriction|filter|safety|guard|security)|(?:reveal|show|print|dump|leak)\s+(?:me\s+)?(?:your\s+|the\s+)?(?:system\s+)?(?:prompt|instruction)|(?:curl|wget)\b[^\n|]{0,120}?(?:https?:\/\/|\d{1,3}(?:\.\d{1,3}){3}|[\w-]{2,}\.[a-z]{2,})[^\n|]{0,120}?\|\s*(?:ba|z)?sh\b/i;
|
|
18903
|
-
function scanCodeExecution(content, lineContexts) {
|
|
18878
|
+
function stripInvisible(s) {
|
|
18879
|
+
return s.replace(INVISIBLE_STRIP, "");
|
|
18880
|
+
}
|
|
18881
|
+
function confusableSkeleton(s) {
|
|
18882
|
+
let out = "";
|
|
18883
|
+
for (const ch of s) {
|
|
18884
|
+
const cp = ch.codePointAt(0) ?? 0;
|
|
18885
|
+
if (isFullwidthLatin(cp)) {
|
|
18886
|
+
out += String.fromCodePoint(cp - 65248);
|
|
18887
|
+
} else if (isMathAlphanumeric(cp)) {
|
|
18888
|
+
const folded = ch.normalize("NFKC");
|
|
18889
|
+
out += CONFUSABLES[folded] ?? folded;
|
|
18890
|
+
} else if (CONFUSABLES[ch]) {
|
|
18891
|
+
out += CONFUSABLES[ch];
|
|
18892
|
+
} else {
|
|
18893
|
+
out += ch;
|
|
18894
|
+
}
|
|
18895
|
+
}
|
|
18896
|
+
return out;
|
|
18897
|
+
}
|
|
18898
|
+
function hasConfusable(s) {
|
|
18899
|
+
for (const ch of s) {
|
|
18900
|
+
const cp = ch.codePointAt(0) ?? 0;
|
|
18901
|
+
if (isFullwidthLatin(cp) || isMathAlphanumeric(cp) || CONFUSABLES[ch])
|
|
18902
|
+
return true;
|
|
18903
|
+
}
|
|
18904
|
+
return false;
|
|
18905
|
+
}
|
|
18906
|
+
var OBFUSCATION_DIRECTIVE_PATTERN = /(?:ignore|disregard|forget)\s+(?:all\s+|the\s+)?(?:previous|prior|above|earlier)\s+(?:instruction|prompt|rule|direction)|bypass\s+(?:all\s+)?(?:restriction|filter|safety|guard|security)|(?:reveal|show|print|dump|leak)\s+(?:me\s+)?(?:your\s+|the\s+)?(?:system\s+)?(?:prompt|instruction)|(?:curl|wget)\b[^\n|]{0,120}?(?:https?:\/\/|\d{1,3}(?:\.\d{1,3}){3}|[\w-]{2,}\.[a-z]{2,})[^\n|]{0,120}?\|\s*(?:ba|z)?sh\b/i;
|
|
18907
|
+
function scanCodeExecution(content, lineContexts) {
|
|
18904
18908
|
const lines = content.split("\n");
|
|
18905
18909
|
const contexts = lineContexts ?? analyzeMarkdownContext(content);
|
|
18906
18910
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -29803,61 +29807,9 @@ async function openCliDatabase(path22, options) {
|
|
|
29803
29807
|
}
|
|
29804
29808
|
}
|
|
29805
29809
|
|
|
29806
|
-
// src/commands/
|
|
29807
|
-
|
|
29808
|
-
|
|
29809
|
-
const jwtToken = await loadStoredAccessToken();
|
|
29810
|
-
const apiClient = createApiClient(jwtToken ? { jwtToken } : {});
|
|
29811
|
-
if (!apiClient.isOffline()) {
|
|
29812
|
-
const response = await apiClient.search(searchOptions);
|
|
29813
|
-
const items = response.data.map((r, i) => ({
|
|
29814
|
-
skill: SkillsmithApiClient.toSkill(r),
|
|
29815
|
-
rank: i,
|
|
29816
|
-
highlights: {}
|
|
29817
|
-
}));
|
|
29818
|
-
const limit = searchOptions.limit ?? 10;
|
|
29819
|
-
const offset = searchOptions.offset ?? 0;
|
|
29820
|
-
const hasMore = items.length >= limit;
|
|
29821
|
-
const totalHint = offset + items.length + (hasMore ? 1 : 0);
|
|
29822
|
-
return { kind: "results", items, hasMore, totalHint };
|
|
29823
|
-
}
|
|
29824
|
-
} catch (error46) {
|
|
29825
|
-
if (error46 instanceof SkillsmithError && error46.code === ErrorCodes.NETWORK_QUOTA_EXCEEDED) {
|
|
29826
|
-
return { kind: "quota", message: error46.message };
|
|
29827
|
-
}
|
|
29828
|
-
if (error46 instanceof ApiClientError && (error46.statusCode === 401 || error46.statusCode === 403)) {
|
|
29829
|
-
return { kind: "auth" };
|
|
29830
|
-
}
|
|
29831
|
-
if (!isNetworkError(error46)) {
|
|
29832
|
-
throw error46;
|
|
29833
|
-
}
|
|
29834
|
-
}
|
|
29835
|
-
if (new SkillRepository(db).count() === 0) {
|
|
29836
|
-
return { kind: "empty" };
|
|
29837
|
-
}
|
|
29838
|
-
const local = new SearchService(db).search(searchOptions);
|
|
29839
|
-
return {
|
|
29840
|
-
kind: "results",
|
|
29841
|
-
items: local.items,
|
|
29842
|
-
hasMore: local.hasMore,
|
|
29843
|
-
totalHint: local.total
|
|
29844
|
-
};
|
|
29845
|
-
}
|
|
29846
|
-
function isLocalIndexEmpty(db) {
|
|
29847
|
-
return new SkillRepository(db).count() === 0;
|
|
29848
|
-
}
|
|
29849
|
-
function formatEmptyIndexHint() {
|
|
29850
|
-
return source_default.yellow(
|
|
29851
|
-
"\n\u2139 Skillsmith is offline and your local skill index is empty. Check your connection and try again.\n"
|
|
29852
|
-
);
|
|
29853
|
-
}
|
|
29854
|
-
function isNetworkError(error46) {
|
|
29855
|
-
if (error46 instanceof TypeError) return true;
|
|
29856
|
-
if (error46 instanceof Error) {
|
|
29857
|
-
return /ENOTFOUND|ECONNREFUSED|AbortError|ETIMEDOUT|ENETUNREACH/.test(error46.message);
|
|
29858
|
-
}
|
|
29859
|
-
return false;
|
|
29860
|
-
}
|
|
29810
|
+
// src/commands/install.ts
|
|
29811
|
+
import { Command } from "commander";
|
|
29812
|
+
import ora from "ora";
|
|
29861
29813
|
|
|
29862
29814
|
// src/utils/sanitize.ts
|
|
29863
29815
|
import { homedir as homedir13 } from "os";
|
|
@@ -29872,12 +29824,330 @@ function sanitizeError(error46) {
|
|
|
29872
29824
|
return sanitized;
|
|
29873
29825
|
}
|
|
29874
29826
|
|
|
29875
|
-
// src/commands/
|
|
29876
|
-
var
|
|
29877
|
-
|
|
29878
|
-
|
|
29879
|
-
|
|
29880
|
-
|
|
29827
|
+
// src/commands/install.ts
|
|
29828
|
+
var VALID_CLIENT_HINT = "Valid IDs: claude-code | cursor | copilot | windsurf | agents (Codex users pass --client agents).";
|
|
29829
|
+
function parseAlsoLink(raw, defaultClient) {
|
|
29830
|
+
if (!raw || raw.trim() === "") return [];
|
|
29831
|
+
const ids = raw.split(",").map((s) => s.trim()).filter((s) => s !== "");
|
|
29832
|
+
const seen = /* @__PURE__ */ new Set();
|
|
29833
|
+
const out = [];
|
|
29834
|
+
for (const id of ids) {
|
|
29835
|
+
assertClientId(id);
|
|
29836
|
+
if (id === defaultClient) {
|
|
29837
|
+
throw new Error(
|
|
29838
|
+
`--also-link target '${id}' is the same as --client; pick a different client or drop it from --also-link.`
|
|
29839
|
+
);
|
|
29840
|
+
}
|
|
29841
|
+
if (seen.has(id)) {
|
|
29842
|
+
throw new Error(`--also-link target '${id}' is listed more than once`);
|
|
29843
|
+
}
|
|
29844
|
+
seen.add(id);
|
|
29845
|
+
out.push(id);
|
|
29846
|
+
}
|
|
29847
|
+
return out;
|
|
29848
|
+
}
|
|
29849
|
+
function isValidSkillId(skillId) {
|
|
29850
|
+
if (isGitHubUrl(skillId)) return true;
|
|
29851
|
+
return /^[a-zA-Z0-9_-]+\/[a-zA-Z0-9_.-]+$/.test(skillId);
|
|
29852
|
+
}
|
|
29853
|
+
function createDbRegistryLookup(skillRepo, db) {
|
|
29854
|
+
let quarantineRepo;
|
|
29855
|
+
return {
|
|
29856
|
+
async lookup(skillId) {
|
|
29857
|
+
const skill = skillRepo.findById(skillId);
|
|
29858
|
+
if (!skill) return null;
|
|
29859
|
+
if (!skill.repoUrl) return null;
|
|
29860
|
+
quarantineRepo ??= new QuarantineRepository(db);
|
|
29861
|
+
return {
|
|
29862
|
+
repoUrl: skill.repoUrl,
|
|
29863
|
+
name: skill.name,
|
|
29864
|
+
trustTier: skill.trustTier,
|
|
29865
|
+
quarantined: quarantineRepo.isQuarantined(skill.id || skillId)
|
|
29866
|
+
};
|
|
29867
|
+
}
|
|
29868
|
+
};
|
|
29869
|
+
}
|
|
29870
|
+
async function createApiBackedRegistryLookup(skillRepo, db) {
|
|
29871
|
+
const dbLookup = createDbRegistryLookup(skillRepo, db);
|
|
29872
|
+
const jwtToken = await loadStoredAccessToken();
|
|
29873
|
+
const apiClient = createApiClient(jwtToken ? { jwtToken } : {});
|
|
29874
|
+
return {
|
|
29875
|
+
async lookup(skillId) {
|
|
29876
|
+
const local = await dbLookup.lookup(skillId);
|
|
29877
|
+
if (local) return local;
|
|
29878
|
+
if (apiClient.isOffline()) return null;
|
|
29879
|
+
try {
|
|
29880
|
+
const response = await apiClient.getSkill(skillId);
|
|
29881
|
+
const r = response.data;
|
|
29882
|
+
if (!r.repo_url) return null;
|
|
29883
|
+
return {
|
|
29884
|
+
repoUrl: r.repo_url,
|
|
29885
|
+
name: r.name,
|
|
29886
|
+
trustTier: SkillsmithApiClient.toSkill(r).trustTier,
|
|
29887
|
+
quarantined: r.quarantined === true || r.installable === false
|
|
29888
|
+
};
|
|
29889
|
+
} catch {
|
|
29890
|
+
return null;
|
|
29891
|
+
}
|
|
29892
|
+
}
|
|
29893
|
+
};
|
|
29894
|
+
}
|
|
29895
|
+
function formatJsonResult(result) {
|
|
29896
|
+
return JSON.stringify(
|
|
29897
|
+
{
|
|
29898
|
+
success: result.success,
|
|
29899
|
+
skillId: result.skillId,
|
|
29900
|
+
installPath: result.installPath,
|
|
29901
|
+
error: result.error,
|
|
29902
|
+
trustTier: result.trustTier,
|
|
29903
|
+
optimization: result.optimization,
|
|
29904
|
+
tips: result.tips
|
|
29905
|
+
},
|
|
29906
|
+
null,
|
|
29907
|
+
2
|
|
29908
|
+
);
|
|
29909
|
+
}
|
|
29910
|
+
function displayResult(result, quiet) {
|
|
29911
|
+
if (result.success) {
|
|
29912
|
+
console.log(source_default.green("\nSkill installed successfully!"));
|
|
29913
|
+
console.log(source_default.dim(` Path: ${result.installPath}`));
|
|
29914
|
+
if (result.trustTier) {
|
|
29915
|
+
console.log(source_default.dim(` Trust tier: ${result.trustTier}`));
|
|
29916
|
+
}
|
|
29917
|
+
if (result.optimization?.optimized && !quiet) {
|
|
29918
|
+
console.log(source_default.dim(` Optimized: ${result.optimization.tokenReductionPercent}% reduction`));
|
|
29919
|
+
if (result.optimization.subSkills && result.optimization.subSkills.length > 0) {
|
|
29920
|
+
console.log(source_default.dim(` Sub-skills: ${result.optimization.subSkills.join(", ")}`));
|
|
29921
|
+
}
|
|
29922
|
+
if (result.optimization.subagentGenerated) {
|
|
29923
|
+
console.log(source_default.dim(` Companion subagent generated`));
|
|
29924
|
+
}
|
|
29925
|
+
}
|
|
29926
|
+
if (result.contentHashMismatch) {
|
|
29927
|
+
console.log(source_default.yellow("\n Warning: Content has changed since last indexed."));
|
|
29928
|
+
console.log(source_default.yellow(" Review recent changes at the skill's repository before using."));
|
|
29929
|
+
}
|
|
29930
|
+
if (result.tips && result.tips.length > 0 && !quiet) {
|
|
29931
|
+
const startIndex = result.contentHashMismatch ? 1 : 0;
|
|
29932
|
+
if (startIndex < result.tips.length) {
|
|
29933
|
+
console.log();
|
|
29934
|
+
for (let i = startIndex; i < result.tips.length; i++) {
|
|
29935
|
+
console.log(source_default.dim(` Tip: ${result.tips[i]}`));
|
|
29936
|
+
}
|
|
29937
|
+
}
|
|
29938
|
+
}
|
|
29939
|
+
} else {
|
|
29940
|
+
console.error(source_default.red(`
|
|
29941
|
+
Installation failed: ${result.error}`));
|
|
29942
|
+
if (result.securityReport && !result.securityReport.passed) {
|
|
29943
|
+
console.error(source_default.red(" Security scan failed."));
|
|
29944
|
+
for (const finding of result.securityReport.findings) {
|
|
29945
|
+
if (finding.severity === "critical" || finding.severity === "high") {
|
|
29946
|
+
console.error(source_default.red(` [${finding.severity}] ${finding.message}`));
|
|
29947
|
+
}
|
|
29948
|
+
}
|
|
29949
|
+
}
|
|
29950
|
+
if (result.tips && result.tips.length > 0 && !quiet) {
|
|
29951
|
+
console.log();
|
|
29952
|
+
for (const tip of result.tips) {
|
|
29953
|
+
console.log(source_default.dim(` ${tip}`));
|
|
29954
|
+
}
|
|
29955
|
+
}
|
|
29956
|
+
}
|
|
29957
|
+
}
|
|
29958
|
+
async function installActionImpl(skillId, opts) {
|
|
29959
|
+
const quiet = opts.quiet ?? false;
|
|
29960
|
+
const jsonOutput = opts.json ?? false;
|
|
29961
|
+
try {
|
|
29962
|
+
const rawClient = opts.client ?? "claude-code";
|
|
29963
|
+
if (rawClient.includes(",")) {
|
|
29964
|
+
throw new Error(
|
|
29965
|
+
`--client takes a single value (got '${rawClient}'). Pass --also-link <ids> to fan-out into additional clients.`
|
|
29966
|
+
);
|
|
29967
|
+
}
|
|
29968
|
+
assertClientId(rawClient);
|
|
29969
|
+
const client = rawClient;
|
|
29970
|
+
const alsoLinkClients = parseAlsoLink(opts.alsoLink, client);
|
|
29971
|
+
const skillsDir = getInstallPath(client);
|
|
29972
|
+
if (!isValidSkillId(skillId)) {
|
|
29973
|
+
const errorMsg = 'Invalid skill ID format. Expected "author/name" or a GitHub URL.\n Examples:\n skillsmith install getsentry/commit\n skillsmith install https://github.com/owner/repo';
|
|
29974
|
+
if (jsonOutput) {
|
|
29975
|
+
console.log(JSON.stringify({ success: false, skillId, error: errorMsg }, null, 2));
|
|
29976
|
+
} else {
|
|
29977
|
+
console.error(source_default.red(errorMsg));
|
|
29978
|
+
}
|
|
29979
|
+
process.exit(1);
|
|
29980
|
+
return;
|
|
29981
|
+
}
|
|
29982
|
+
const dbPath = opts.db ?? DEFAULT_DB_PATH;
|
|
29983
|
+
const db = await openCliDatabase(dbPath);
|
|
29984
|
+
const spinner = jsonOutput ? null : ora("Installing skill...").start();
|
|
29985
|
+
try {
|
|
29986
|
+
const skillRepo = new SkillRepository(db);
|
|
29987
|
+
const skillDependencyRepo = new SkillDependencyRepository(db);
|
|
29988
|
+
const registryLookup = await createApiBackedRegistryLookup(skillRepo, db);
|
|
29989
|
+
const service = new SkillInstallationService({
|
|
29990
|
+
db,
|
|
29991
|
+
skillRepo,
|
|
29992
|
+
skillDependencyRepo,
|
|
29993
|
+
skillsDir,
|
|
29994
|
+
manifestPath: DEFAULT_MANIFEST_PATH,
|
|
29995
|
+
registryLookup,
|
|
29996
|
+
onProgress: (_stage, detail) => {
|
|
29997
|
+
if (spinner) {
|
|
29998
|
+
spinner.text = detail;
|
|
29999
|
+
}
|
|
30000
|
+
}
|
|
30001
|
+
});
|
|
30002
|
+
const installOptions = {};
|
|
30003
|
+
if (opts.force !== void 0) {
|
|
30004
|
+
installOptions.force = opts.force;
|
|
30005
|
+
}
|
|
30006
|
+
if (opts.skipScan !== void 0) {
|
|
30007
|
+
installOptions.skipScan = opts.skipScan;
|
|
30008
|
+
}
|
|
30009
|
+
if (opts.skipOptimize !== void 0) {
|
|
30010
|
+
installOptions.skipOptimize = opts.skipOptimize;
|
|
30011
|
+
}
|
|
30012
|
+
const installStart = Date.now();
|
|
30013
|
+
const result = await service.install(skillId, installOptions);
|
|
30014
|
+
void emitInstallEvent({
|
|
30015
|
+
skillId,
|
|
30016
|
+
source: "cli",
|
|
30017
|
+
success: result.success,
|
|
30018
|
+
durationMs: Date.now() - installStart,
|
|
30019
|
+
...result.trustTier !== void 0 && { trustTier: result.trustTier },
|
|
30020
|
+
...!result.success && result.errorCode !== void 0 && { errorCode: result.errorCode }
|
|
30021
|
+
});
|
|
30022
|
+
if (result.success && alsoLinkClients.length > 0) {
|
|
30023
|
+
for (const target of alsoLinkClients) {
|
|
30024
|
+
try {
|
|
30025
|
+
const linked = await addLink({
|
|
30026
|
+
skillId,
|
|
30027
|
+
fromClient: client,
|
|
30028
|
+
toClient: target,
|
|
30029
|
+
preferSymlink: opts.symlink ?? false,
|
|
30030
|
+
force: opts.force ?? false
|
|
30031
|
+
});
|
|
30032
|
+
if (!quiet && !jsonOutput) {
|
|
30033
|
+
const note = linked.fellBackToCopy ? " (fell back to copy)" : "";
|
|
30034
|
+
console.log(source_default.dim(` Linked into ${target} as ${linked.record.kind}${note}`));
|
|
30035
|
+
}
|
|
30036
|
+
} catch (linkErr) {
|
|
30037
|
+
if (!jsonOutput) {
|
|
30038
|
+
console.warn(
|
|
30039
|
+
source_default.yellow(` Warning: could not link to ${target}: ${sanitizeError(linkErr)}`)
|
|
30040
|
+
);
|
|
30041
|
+
}
|
|
30042
|
+
}
|
|
30043
|
+
}
|
|
30044
|
+
}
|
|
30045
|
+
if (spinner) {
|
|
30046
|
+
if (result.success) {
|
|
30047
|
+
spinner.succeed("Skill installed");
|
|
30048
|
+
} else {
|
|
30049
|
+
spinner.fail("Installation failed");
|
|
30050
|
+
}
|
|
30051
|
+
}
|
|
30052
|
+
if (jsonOutput) {
|
|
30053
|
+
console.log(formatJsonResult(result));
|
|
30054
|
+
} else {
|
|
30055
|
+
displayResult(result, quiet);
|
|
30056
|
+
}
|
|
30057
|
+
if (!result.success) {
|
|
30058
|
+
process.exit(1);
|
|
30059
|
+
}
|
|
30060
|
+
} finally {
|
|
30061
|
+
db.close();
|
|
30062
|
+
}
|
|
30063
|
+
} catch (error46) {
|
|
30064
|
+
if (jsonOutput) {
|
|
30065
|
+
console.log(JSON.stringify({ success: false, skillId, error: sanitizeError(error46) }, null, 2));
|
|
30066
|
+
} else {
|
|
30067
|
+
console.error(source_default.red("Install error:"), sanitizeError(error46));
|
|
30068
|
+
}
|
|
30069
|
+
process.exit(1);
|
|
30070
|
+
}
|
|
30071
|
+
}
|
|
30072
|
+
var installAction = withTelemetry(installActionImpl, {
|
|
30073
|
+
source: "cli",
|
|
30074
|
+
extractSkillId: () => "install",
|
|
30075
|
+
extractFramework: () => "cli"
|
|
30076
|
+
});
|
|
30077
|
+
function createInstallCommand() {
|
|
30078
|
+
return new Command("install").description("Install a skill from the registry or GitHub URL").argument("<skillId>", "Skill ID (author/name) or GitHub URL").option("-f, --force", "Force reinstall if already installed").option("--skip-scan", "Skip security scan (not recommended)").option("--skip-optimize", "Skip Skillsmith optimization").option("-q, --quiet", "Suppress advisory output").option("--json", "Output structured JSON result").option("-d, --db <path>", "Database file path", DEFAULT_DB_PATH).option("--client <id>", `install for a specific agent (${VALID_CLIENT_HINT})`, "claude-code").option(
|
|
30079
|
+
"--also-link <ids>",
|
|
30080
|
+
"comma-separated additional clients to fan-out into (default: copy; pair with --symlink for POSIX symlinks)",
|
|
30081
|
+
""
|
|
30082
|
+
).option(
|
|
30083
|
+
"--symlink",
|
|
30084
|
+
"use relative symlinks instead of file copies for --also-link (POSIX only; falls back to copy on Windows EPERM)",
|
|
30085
|
+
false
|
|
30086
|
+
).action(installAction);
|
|
30087
|
+
}
|
|
30088
|
+
|
|
30089
|
+
// src/commands/search.helpers.ts
|
|
30090
|
+
async function searchRemoteOrLocal(searchOptions, db) {
|
|
30091
|
+
try {
|
|
30092
|
+
const jwtToken = await loadStoredAccessToken();
|
|
30093
|
+
const apiClient = createApiClient(jwtToken ? { jwtToken } : {});
|
|
30094
|
+
if (!apiClient.isOffline()) {
|
|
30095
|
+
const response = await apiClient.search(searchOptions);
|
|
30096
|
+
const items = response.data.map((r, i) => ({
|
|
30097
|
+
skill: SkillsmithApiClient.toSkill(r),
|
|
30098
|
+
rank: i,
|
|
30099
|
+
highlights: {}
|
|
30100
|
+
}));
|
|
30101
|
+
const limit = searchOptions.limit ?? 10;
|
|
30102
|
+
const offset = searchOptions.offset ?? 0;
|
|
30103
|
+
const hasMore = items.length >= limit;
|
|
30104
|
+
const totalHint = offset + items.length + (hasMore ? 1 : 0);
|
|
30105
|
+
return { kind: "results", items, hasMore, totalHint };
|
|
30106
|
+
}
|
|
30107
|
+
} catch (error46) {
|
|
30108
|
+
if (error46 instanceof SkillsmithError && error46.code === ErrorCodes.NETWORK_QUOTA_EXCEEDED) {
|
|
30109
|
+
return { kind: "quota", message: error46.message };
|
|
30110
|
+
}
|
|
30111
|
+
if (error46 instanceof ApiClientError && (error46.statusCode === 401 || error46.statusCode === 403)) {
|
|
30112
|
+
return { kind: "auth" };
|
|
30113
|
+
}
|
|
30114
|
+
if (!isNetworkError(error46)) {
|
|
30115
|
+
throw error46;
|
|
30116
|
+
}
|
|
30117
|
+
}
|
|
30118
|
+
if (new SkillRepository(db).count() === 0) {
|
|
30119
|
+
return { kind: "empty" };
|
|
30120
|
+
}
|
|
30121
|
+
const local = new SearchService(db).search(searchOptions);
|
|
30122
|
+
return {
|
|
30123
|
+
kind: "results",
|
|
30124
|
+
items: local.items,
|
|
30125
|
+
hasMore: local.hasMore,
|
|
30126
|
+
totalHint: local.total
|
|
30127
|
+
};
|
|
30128
|
+
}
|
|
30129
|
+
function isLocalIndexEmpty(db) {
|
|
30130
|
+
return new SkillRepository(db).count() === 0;
|
|
30131
|
+
}
|
|
30132
|
+
function formatEmptyIndexHint() {
|
|
30133
|
+
return source_default.yellow(
|
|
30134
|
+
"\n\u2139 Skillsmith is offline and your local skill index is empty. Check your connection and try again.\n"
|
|
30135
|
+
);
|
|
30136
|
+
}
|
|
30137
|
+
function isNetworkError(error46) {
|
|
30138
|
+
if (error46 instanceof TypeError) return true;
|
|
30139
|
+
if (error46 instanceof Error) {
|
|
30140
|
+
return /ENOTFOUND|ECONNREFUSED|AbortError|ETIMEDOUT|ENETUNREACH/.test(error46.message);
|
|
30141
|
+
}
|
|
30142
|
+
return false;
|
|
30143
|
+
}
|
|
30144
|
+
|
|
30145
|
+
// src/commands/search-types.ts
|
|
30146
|
+
var PAGE_SIZE = 10;
|
|
30147
|
+
|
|
30148
|
+
// src/commands/search-formatters.ts
|
|
30149
|
+
import Table from "cli-table3";
|
|
30150
|
+
var TRUST_TIER_COLORS = {
|
|
29881
30151
|
official: source_default.magenta,
|
|
29882
30152
|
// SMI-5205: Platform/partner — magenta to stand out from verified
|
|
29883
30153
|
verified: source_default.green,
|
|
@@ -30116,11 +30386,11 @@ ${outcome.message}`));
|
|
|
30116
30386
|
]
|
|
30117
30387
|
});
|
|
30118
30388
|
if (nextAction === "install") {
|
|
30119
|
-
const installSpinner =
|
|
30389
|
+
const installSpinner = ora2("Installing skill...").start();
|
|
30120
30390
|
try {
|
|
30121
30391
|
const skillRepo = new SkillRepository(db);
|
|
30122
30392
|
const skillDependencyRepo = new SkillDependencyRepository(db);
|
|
30123
|
-
const registryLookup =
|
|
30393
|
+
const registryLookup = await createApiBackedRegistryLookup(skillRepo, db);
|
|
30124
30394
|
const installService = new SkillInstallationService({
|
|
30125
30395
|
db,
|
|
30126
30396
|
skillRepo,
|
|
@@ -30160,41 +30430,10 @@ ${outcome.message}`));
|
|
|
30160
30430
|
}
|
|
30161
30431
|
console.log(source_default.dim("\nGoodbye!\n"));
|
|
30162
30432
|
}
|
|
30163
|
-
function buildRegistryLookupWithApiFallback(skillRepo) {
|
|
30164
|
-
return {
|
|
30165
|
-
async lookup(sid) {
|
|
30166
|
-
const s = skillRepo.findById(sid);
|
|
30167
|
-
if (s?.repoUrl) {
|
|
30168
|
-
return {
|
|
30169
|
-
repoUrl: s.repoUrl,
|
|
30170
|
-
name: s.name,
|
|
30171
|
-
trustTier: s.trustTier,
|
|
30172
|
-
quarantined: false
|
|
30173
|
-
};
|
|
30174
|
-
}
|
|
30175
|
-
try {
|
|
30176
|
-
const jwtToken = await loadStoredAccessToken();
|
|
30177
|
-
const apiClient = createApiClient(jwtToken ? { jwtToken } : {});
|
|
30178
|
-
if (apiClient.isOffline()) return null;
|
|
30179
|
-
const response = await apiClient.getSkill(sid);
|
|
30180
|
-
const r = response.data;
|
|
30181
|
-
if (!r.repo_url) return null;
|
|
30182
|
-
return {
|
|
30183
|
-
repoUrl: r.repo_url,
|
|
30184
|
-
name: r.name,
|
|
30185
|
-
trustTier: SkillsmithApiClient.toSkill(r).trustTier,
|
|
30186
|
-
quarantined: false
|
|
30187
|
-
};
|
|
30188
|
-
} catch {
|
|
30189
|
-
return null;
|
|
30190
|
-
}
|
|
30191
|
-
}
|
|
30192
|
-
};
|
|
30193
|
-
}
|
|
30194
30433
|
async function runSearch(query, options) {
|
|
30195
30434
|
const db = await openCliDatabase(options.db);
|
|
30196
30435
|
const suppress = options.quiet || options.noProgress || process.env["SKILLSMITH_QUIET"] === "true";
|
|
30197
|
-
const spinner = suppress ? null :
|
|
30436
|
+
const spinner = suppress ? null : ora2("Searching Skillsmith registry...").start();
|
|
30198
30437
|
try {
|
|
30199
30438
|
const searchOptions = {
|
|
30200
30439
|
query,
|
|
@@ -30301,7 +30540,7 @@ var searchAction = withTelemetry(searchActionImpl, {
|
|
|
30301
30540
|
|
|
30302
30541
|
// src/commands/search.ts
|
|
30303
30542
|
function createSearchCommand() {
|
|
30304
|
-
const cmd = new
|
|
30543
|
+
const cmd = new Command2("search").description(
|
|
30305
30544
|
`Search for skills
|
|
30306
30545
|
|
|
30307
30546
|
Quality Score Formula:
|
|
@@ -30331,10 +30570,10 @@ Quality Score Formula:
|
|
|
30331
30570
|
}
|
|
30332
30571
|
|
|
30333
30572
|
// src/commands/manage.ts
|
|
30334
|
-
import { Command as
|
|
30573
|
+
import { Command as Command3 } from "commander";
|
|
30335
30574
|
import { confirm } from "@inquirer/prompts";
|
|
30336
30575
|
import Table2 from "cli-table3";
|
|
30337
|
-
import
|
|
30576
|
+
import ora3 from "ora";
|
|
30338
30577
|
import { mkdir as mkdir4 } from "fs/promises";
|
|
30339
30578
|
import { dirname as dirname9 } from "path";
|
|
30340
30579
|
|
|
@@ -30607,7 +30846,7 @@ async function getSkillDiff(skillName, dbPath) {
|
|
|
30607
30846
|
}
|
|
30608
30847
|
}
|
|
30609
30848
|
async function updateSkill(skillName, dbPath) {
|
|
30610
|
-
const spinner =
|
|
30849
|
+
const spinner = ora3(`Checking updates for ${skillName}...`).start();
|
|
30611
30850
|
try {
|
|
30612
30851
|
const diff = await getSkillDiff(skillName, dbPath);
|
|
30613
30852
|
if (!diff) {
|
|
@@ -30633,7 +30872,7 @@ Changes for ${skillName}:`));
|
|
|
30633
30872
|
console.log(source_default.yellow("Update cancelled"));
|
|
30634
30873
|
return false;
|
|
30635
30874
|
}
|
|
30636
|
-
const updateSpinner =
|
|
30875
|
+
const updateSpinner = ora3(`Updating ${skillName}...`).start();
|
|
30637
30876
|
updateSpinner.stop();
|
|
30638
30877
|
throw new Error("updateSkill not yet implemented");
|
|
30639
30878
|
} catch (error46) {
|
|
@@ -30690,7 +30929,7 @@ Skill to remove:`));
|
|
|
30690
30929
|
return false;
|
|
30691
30930
|
}
|
|
30692
30931
|
}
|
|
30693
|
-
const spinner =
|
|
30932
|
+
const spinner = ora3(`Removing ${skillName}...`).start();
|
|
30694
30933
|
await mkdir4(dirname9(dbPath), { recursive: true });
|
|
30695
30934
|
const db = await openCliDatabase(dbPath);
|
|
30696
30935
|
try {
|
|
@@ -30761,7 +31000,7 @@ var listAction = withTelemetry(listActionImpl, {
|
|
|
30761
31000
|
extractFramework: () => "cli"
|
|
30762
31001
|
});
|
|
30763
31002
|
function createListCommand() {
|
|
30764
|
-
return new
|
|
31003
|
+
return new Command3("list").alias("ls").description("List all installed skills").option("-d, --db <path>", "Database file path", DEFAULT_DB_PATH).option("--outdated", "Show only skills with available updates (requires Individual tier)").action(listAction);
|
|
30765
31004
|
}
|
|
30766
31005
|
async function updateActionImpl(skillName, opts) {
|
|
30767
31006
|
const dbPath = opts["db"];
|
|
@@ -30783,7 +31022,7 @@ var updateAction = withTelemetry(updateActionImpl, {
|
|
|
30783
31022
|
extractFramework: () => "cli"
|
|
30784
31023
|
});
|
|
30785
31024
|
function createUpdateCommand() {
|
|
30786
|
-
return new
|
|
31025
|
+
return new Command3("update").description("Update installed skills").argument("[skill]", "Skill name to update (omit for all)").option("-d, --db <path>", "Database file path", DEFAULT_DB_PATH).option("-a, --all", "Update all installed skills").action(updateAction);
|
|
30787
31026
|
}
|
|
30788
31027
|
async function removeActionImpl(skillName, opts) {
|
|
30789
31028
|
const force = opts["force"] ?? false;
|
|
@@ -30802,11 +31041,11 @@ var removeAction = withTelemetry(removeActionImpl, {
|
|
|
30802
31041
|
extractFramework: () => "cli"
|
|
30803
31042
|
});
|
|
30804
31043
|
function createRemoveCommand() {
|
|
30805
|
-
return new
|
|
31044
|
+
return new Command3("remove").alias("rm").alias("uninstall").description("Remove an installed skill").argument("<skill>", "Skill name to remove").option("-f, --force", "Skip confirmation prompt and force removal of modified/orphan skills").option("-d, --db <path>", "Database file path", DEFAULT_DB_PATH).action(removeAction);
|
|
30806
31045
|
}
|
|
30807
31046
|
|
|
30808
31047
|
// src/commands/author/init.action.ts
|
|
30809
|
-
import { Command as
|
|
31048
|
+
import { Command as Command4 } from "commander";
|
|
30810
31049
|
|
|
30811
31050
|
// src/utils/errors.ts
|
|
30812
31051
|
var InitSkillError = class _InitSkillError extends Error {
|
|
@@ -30826,7 +31065,7 @@ var InitSkillError = class _InitSkillError extends Error {
|
|
|
30826
31065
|
|
|
30827
31066
|
// src/commands/author/init.ts
|
|
30828
31067
|
import { input as input2, confirm as confirm2, select as select2 } from "@inquirer/prompts";
|
|
30829
|
-
import
|
|
31068
|
+
import ora4 from "ora";
|
|
30830
31069
|
import { mkdir as mkdir7, writeFile as writeFile5, readFile as readFile7, stat as stat6, readdir as readdir6 } from "fs/promises";
|
|
30831
31070
|
import { dirname as dirname10, join as join25, resolve as resolve9 } from "path";
|
|
30832
31071
|
import { createHash as createHash8 } from "crypto";
|
|
@@ -31882,7 +32121,7 @@ async function initSkill(name, targetPath, options = {}) {
|
|
|
31882
32121
|
}
|
|
31883
32122
|
} catch {
|
|
31884
32123
|
}
|
|
31885
|
-
const spinner =
|
|
32124
|
+
const spinner = ora4("Creating skill structure...").start();
|
|
31886
32125
|
let createdFresh = false;
|
|
31887
32126
|
try {
|
|
31888
32127
|
await mkdir7(skillDir, { recursive: true });
|
|
@@ -31915,7 +32154,7 @@ async function initSkill(name, targetPath, options = {}) {
|
|
|
31915
32154
|
console.log();
|
|
31916
32155
|
}
|
|
31917
32156
|
async function validateSkill(skillPath) {
|
|
31918
|
-
const spinner =
|
|
32157
|
+
const spinner = ora4("Validating skill...").start();
|
|
31919
32158
|
try {
|
|
31920
32159
|
let filePath = resolve9(skillPath);
|
|
31921
32160
|
try {
|
|
@@ -31960,7 +32199,7 @@ async function validateSkill(skillPath) {
|
|
|
31960
32199
|
}
|
|
31961
32200
|
}
|
|
31962
32201
|
async function publishSkill(skillPath, options = {}) {
|
|
31963
|
-
const spinner =
|
|
32202
|
+
const spinner = ora4("Preparing skill for publishing...").start();
|
|
31964
32203
|
try {
|
|
31965
32204
|
let dirPath = resolve9(skillPath || ".");
|
|
31966
32205
|
try {
|
|
@@ -32100,7 +32339,7 @@ var initAction = withTelemetry(initActionImpl, {
|
|
|
32100
32339
|
extractFramework: () => "cli"
|
|
32101
32340
|
});
|
|
32102
32341
|
function createInitCommand() {
|
|
32103
|
-
return new
|
|
32342
|
+
return new Command4("init").description("Initialize a new skill directory").argument("[name]", "Skill name").option("-p, --path <path>", "Target directory", ".").option("-d, --description <description>", "Skill description (non-interactive)").option("-a, --author <author>", "Skill author (non-interactive)").option(
|
|
32104
32343
|
"-c, --category <category>",
|
|
32105
32344
|
"Skill category: development|productivity|communication|data|security|other (non-interactive)"
|
|
32106
32345
|
).option("-y, --yes", "Auto-confirm overwrite (non-interactive)").action(initAction);
|
|
@@ -32120,7 +32359,7 @@ var validateAction = withTelemetry(validateActionImpl, {
|
|
|
32120
32359
|
extractFramework: () => "cli"
|
|
32121
32360
|
});
|
|
32122
32361
|
function createValidateCommand() {
|
|
32123
|
-
return new
|
|
32362
|
+
return new Command4("validate").description("Validate a local SKILL.md file").argument("[path]", "Path to SKILL.md or skill directory", ".").action(validateAction);
|
|
32124
32363
|
}
|
|
32125
32364
|
async function publishActionImpl(skillPath, opts) {
|
|
32126
32365
|
try {
|
|
@@ -32143,15 +32382,15 @@ var publishAction = withTelemetry(publishActionImpl, {
|
|
|
32143
32382
|
extractFramework: () => "cli"
|
|
32144
32383
|
});
|
|
32145
32384
|
function createPublishCommand() {
|
|
32146
|
-
return new
|
|
32385
|
+
return new Command4("publish").description("Prepare skill for sharing").argument("[path]", "Path to skill directory", ".").option("--check-references", "Scan for project-specific references before publishing").option(
|
|
32147
32386
|
"--reference-patterns <patterns>",
|
|
32148
32387
|
"Additional regex patterns to check (comma-separated)"
|
|
32149
32388
|
).action(publishAction);
|
|
32150
32389
|
}
|
|
32151
32390
|
|
|
32152
32391
|
// src/commands/author/subagent.ts
|
|
32153
|
-
import { Command as
|
|
32154
|
-
import
|
|
32392
|
+
import { Command as Command5 } from "commander";
|
|
32393
|
+
import ora5 from "ora";
|
|
32155
32394
|
import { readFile as readFile8, writeFile as writeFile6, stat as stat7 } from "fs/promises";
|
|
32156
32395
|
import { basename as basename4, dirname as dirname11, join as join26, resolve as resolve10 } from "path";
|
|
32157
32396
|
|
|
@@ -32274,7 +32513,7 @@ function validateTools(tools) {
|
|
|
32274
32513
|
|
|
32275
32514
|
// src/commands/author/subagent.ts
|
|
32276
32515
|
async function generateSubagent2(skillPath, options) {
|
|
32277
|
-
const spinner =
|
|
32516
|
+
const spinner = ora5("Generating subagent...").start();
|
|
32278
32517
|
try {
|
|
32279
32518
|
let dirPath = resolve10(skillPath || ".");
|
|
32280
32519
|
let skillMdPath;
|
|
@@ -32394,17 +32633,17 @@ var subagentAction = withTelemetry(subagentActionImpl, {
|
|
|
32394
32633
|
extractFramework: () => "cli"
|
|
32395
32634
|
});
|
|
32396
32635
|
function createSubagentCommand() {
|
|
32397
|
-
return new
|
|
32636
|
+
return new Command5("subagent").description("Generate a companion subagent for a skill").argument("[path]", "Path to skill directory", ".").option("-o, --output <path>", "Output directory", "~/.claude/agents").option("--tools <tools>", "Override detected tools (comma-separated)").option("--model <model>", "Model for subagent: sonnet|opus|haiku", "sonnet").option("--skip-claude-md", "Skip CLAUDE.md snippet generation").option("--force", "Overwrite existing subagent definition").action(subagentAction);
|
|
32398
32637
|
}
|
|
32399
32638
|
|
|
32400
32639
|
// src/commands/author/transform.ts
|
|
32401
|
-
import { Command as
|
|
32402
|
-
import
|
|
32640
|
+
import { Command as Command6 } from "commander";
|
|
32641
|
+
import ora6 from "ora";
|
|
32403
32642
|
import { readFile as readFile9, readdir as readdir7 } from "fs/promises";
|
|
32404
32643
|
import { join as join27, resolve as resolve11 } from "path";
|
|
32405
32644
|
import { homedir as homedir15 } from "os";
|
|
32406
32645
|
async function transformSkill2(skillPath, options) {
|
|
32407
|
-
const spinner =
|
|
32646
|
+
const spinner = ora6("Transforming skill...").start();
|
|
32408
32647
|
try {
|
|
32409
32648
|
const dirPath = resolve11(skillPath || ".");
|
|
32410
32649
|
if (options.batch) {
|
|
@@ -32505,13 +32744,13 @@ var transformAction = withTelemetry(transformActionImpl, {
|
|
|
32505
32744
|
extractFramework: () => "cli"
|
|
32506
32745
|
});
|
|
32507
32746
|
function createTransformCommand() {
|
|
32508
|
-
return new
|
|
32747
|
+
return new Command6("transform").description("Upgrade existing skill with subagent configuration").argument("[path]", "Path to skill directory", ".").option("--dry-run", "Preview what would be generated").option("--force", "Overwrite existing subagent").option("--batch", "Process directory of skills").option("--tools <tools>", "Override detected tools (comma-separated)").option("--model <model>", "Model for subagent: sonnet|opus|haiku", "sonnet").action(transformAction);
|
|
32509
32748
|
}
|
|
32510
32749
|
|
|
32511
32750
|
// src/commands/author/mcp-init.ts
|
|
32512
|
-
import { Command as
|
|
32751
|
+
import { Command as Command7 } from "commander";
|
|
32513
32752
|
import { input as input3, confirm as confirm3 } from "@inquirer/prompts";
|
|
32514
|
-
import
|
|
32753
|
+
import ora7 from "ora";
|
|
32515
32754
|
import { mkdir as mkdir8, writeFile as writeFile7, stat as stat8 } from "fs/promises";
|
|
32516
32755
|
import { dirname as dirname12, join as join28, resolve as resolve12 } from "path";
|
|
32517
32756
|
async function initMcpServer(name, options) {
|
|
@@ -32616,7 +32855,7 @@ async function initMcpServer(name, options) {
|
|
|
32616
32855
|
}
|
|
32617
32856
|
} catch {
|
|
32618
32857
|
}
|
|
32619
|
-
const spinner =
|
|
32858
|
+
const spinner = ora7("Creating MCP server...").start();
|
|
32620
32859
|
try {
|
|
32621
32860
|
const files = renderMcpServerTemplates({
|
|
32622
32861
|
name: serverName,
|
|
@@ -32678,11 +32917,11 @@ var mcpInitAction = withTelemetry(mcpInitActionImpl, {
|
|
|
32678
32917
|
extractFramework: () => "cli"
|
|
32679
32918
|
});
|
|
32680
32919
|
function createMcpInitCommand() {
|
|
32681
|
-
return new
|
|
32920
|
+
return new Command7("mcp-init").description("Scaffold a new MCP server project").argument("[name]", "MCP server name").option("-o, --output <path>", "Output directory").option("--tools <tools>", "Initial tools (comma-separated)").option("--force", "Overwrite existing directory").action(mcpInitAction);
|
|
32682
32921
|
}
|
|
32683
32922
|
|
|
32684
32923
|
// src/commands/analyze.ts
|
|
32685
|
-
import { Command as
|
|
32924
|
+
import { Command as Command8 } from "commander";
|
|
32686
32925
|
function formatAnalysisResults(context, analyzer) {
|
|
32687
32926
|
const lines = [];
|
|
32688
32927
|
lines.push("");
|
|
@@ -32819,13 +33058,13 @@ var analyzeAction = withTelemetry(analyzeActionImpl, {
|
|
|
32819
33058
|
extractFramework: () => "cli"
|
|
32820
33059
|
});
|
|
32821
33060
|
function createAnalyzeCommand() {
|
|
32822
|
-
const cmd = new
|
|
33061
|
+
const cmd = new Command8("analyze").description("Analyze a codebase to detect frameworks, dependencies, and patterns").argument("[path]", "Path to the codebase to analyze", ".").option("-m, --max-files <number>", "Maximum files to analyze", "1000").option("-e, --exclude <dirs...>", "Directories to exclude (in addition to defaults)").option("--no-dev-deps", "Exclude dev dependencies from analysis").option("-j, --json", "Output results as JSON").action(analyzeAction);
|
|
32823
33062
|
return cmd;
|
|
32824
33063
|
}
|
|
32825
33064
|
|
|
32826
33065
|
// src/commands/recommend.ts
|
|
32827
|
-
import { Command as
|
|
32828
|
-
import
|
|
33066
|
+
import { Command as Command9 } from "commander";
|
|
33067
|
+
import ora8 from "ora";
|
|
32829
33068
|
|
|
32830
33069
|
// src/commands/recommend.helpers.ts
|
|
32831
33070
|
import { existsSync as existsSync11, readdirSync as readdirSync2, readFileSync as readFileSync9, statSync as statSync2 } from "node:fs";
|
|
@@ -33188,7 +33427,7 @@ function getInstalledSkills2() {
|
|
|
33188
33427
|
|
|
33189
33428
|
// src/commands/recommend.ts
|
|
33190
33429
|
async function runRecommend(targetPath, options) {
|
|
33191
|
-
const spinner =
|
|
33430
|
+
const spinner = ora8();
|
|
33192
33431
|
let codebaseContext = null;
|
|
33193
33432
|
try {
|
|
33194
33433
|
spinner.start("Analyzing codebase...");
|
|
@@ -33329,7 +33568,7 @@ var recommendAction = withTelemetry(recommendActionImpl, {
|
|
|
33329
33568
|
extractFramework: () => "cli"
|
|
33330
33569
|
});
|
|
33331
33570
|
function createRecommendCommand() {
|
|
33332
|
-
const cmd = new
|
|
33571
|
+
const cmd = new Command9("recommend").description("Analyze a codebase and recommend relevant skills based on detected patterns").argument("[path]", "Path to the codebase to analyze", ".").option("-l, --limit <number>", "Maximum recommendations to return", "5").option("-j, --json", "Output results as JSON").option("-c, --context <text>", "Additional context for recommendations").option("-i, --installed <skills...>", "Currently installed skill IDs").option("--no-overlap", "Disable overlap detection").option("-m, --max-files <number>", "Maximum files to analyze", "1000").option(
|
|
33333
33572
|
"-r, --role <role>",
|
|
33334
33573
|
`SMI-1631: Filter by skill role (${SKILL_ROLES.join(", ")}). Skills matching the role get a +30 score boost.`
|
|
33335
33574
|
).action(recommendAction);
|
|
@@ -33337,10 +33576,10 @@ function createRecommendCommand() {
|
|
|
33337
33576
|
}
|
|
33338
33577
|
|
|
33339
33578
|
// src/commands/sync.ts
|
|
33340
|
-
import { Command as
|
|
33579
|
+
import { Command as Command10 } from "commander";
|
|
33341
33580
|
|
|
33342
33581
|
// src/commands/sync.action.ts
|
|
33343
|
-
import
|
|
33582
|
+
import ora9 from "ora";
|
|
33344
33583
|
import Table3 from "cli-table3";
|
|
33345
33584
|
|
|
33346
33585
|
// src/commands/run-registry-sync.ts
|
|
@@ -33434,7 +33673,7 @@ function formatAdapterWarnings(warnings) {
|
|
|
33434
33673
|
|
|
33435
33674
|
// src/commands/sync.action.ts
|
|
33436
33675
|
async function syncActionImpl(options) {
|
|
33437
|
-
const spinner =
|
|
33676
|
+
const spinner = ora9();
|
|
33438
33677
|
try {
|
|
33439
33678
|
spinner.start("Opening database...");
|
|
33440
33679
|
const db = await openCliDatabase(options.dbPath);
|
|
@@ -33725,7 +33964,7 @@ var syncConfigAction = withTelemetry(syncConfigActionImpl, {
|
|
|
33725
33964
|
|
|
33726
33965
|
// src/commands/sync.ts
|
|
33727
33966
|
function createStatusCommand() {
|
|
33728
|
-
return new
|
|
33967
|
+
return new Command10("status").description("Show sync status and statistics").option("-d, --db <path>", "Database file path", DEFAULT_DB_PATH).option("--json", "Output as JSON").action(async (opts) => {
|
|
33729
33968
|
await syncStatusAction({
|
|
33730
33969
|
dbPath: opts["db"],
|
|
33731
33970
|
json: opts["json"] ?? false
|
|
@@ -33733,7 +33972,7 @@ function createStatusCommand() {
|
|
|
33733
33972
|
});
|
|
33734
33973
|
}
|
|
33735
33974
|
function createHistoryCommand() {
|
|
33736
|
-
return new
|
|
33975
|
+
return new Command10("history").description("Show sync history").option("-d, --db <path>", "Database file path", DEFAULT_DB_PATH).option("-l, --limit <number>", "Number of entries to show", "10").option("--json", "Output as JSON").action(async (opts) => {
|
|
33737
33976
|
await syncHistoryAction({
|
|
33738
33977
|
dbPath: opts["db"],
|
|
33739
33978
|
limit: parseInt(opts["limit"], 10),
|
|
@@ -33742,7 +33981,7 @@ function createHistoryCommand() {
|
|
|
33742
33981
|
});
|
|
33743
33982
|
}
|
|
33744
33983
|
function createConfigCommand() {
|
|
33745
|
-
return new
|
|
33984
|
+
return new Command10("config").description("Configure automatic sync settings").option("-d, --db <path>", "Database file path", DEFAULT_DB_PATH).option("--enable", "Enable automatic background sync").option("--disable", "Disable automatic background sync").option("--frequency <freq>", "Set sync frequency (daily|weekly)").option("--show", "Show current configuration").option("--json", "Output as JSON").action(async (opts) => {
|
|
33746
33985
|
await syncConfigAction({
|
|
33747
33986
|
dbPath: opts["db"],
|
|
33748
33987
|
enable: opts["enable"],
|
|
@@ -33754,7 +33993,7 @@ function createConfigCommand() {
|
|
|
33754
33993
|
});
|
|
33755
33994
|
}
|
|
33756
33995
|
function createSyncCommand() {
|
|
33757
|
-
const cmd = new
|
|
33996
|
+
const cmd = new Command10("sync").description("Synchronize skills from the Skillsmith registry").option("-d, --db <path>", "Database file path", DEFAULT_DB_PATH).option("-f, --force", "Force full sync (ignore last sync time)").option("--dry-run", "Show what would be synced without making changes").option("--json", "Output results as JSON").action(async (opts) => {
|
|
33758
33997
|
await syncAction({
|
|
33759
33998
|
dbPath: opts["db"],
|
|
33760
33999
|
force: opts["force"] ?? false,
|
|
@@ -33769,389 +34008,125 @@ function createSyncCommand() {
|
|
|
33769
34008
|
}
|
|
33770
34009
|
|
|
33771
34010
|
// src/commands/merge.ts
|
|
33772
|
-
import { Command as
|
|
33773
|
-
import { resolve as resolve13 } from "path";
|
|
33774
|
-
import { existsSync as existsSync12 } from "fs";
|
|
33775
|
-
function formatMergeResult(result) {
|
|
33776
|
-
const lines = [
|
|
33777
|
-
"",
|
|
33778
|
-
"\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557",
|
|
33779
|
-
"\u2551 Merge Results \u2551",
|
|
33780
|
-
"\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563",
|
|
33781
|
-
`\u2551 Skills added: ${result.skillsAdded.toString().padStart(8)} \u2551`,
|
|
33782
|
-
`\u2551 Skills updated: ${result.skillsUpdated.toString().padStart(8)} \u2551`,
|
|
33783
|
-
`\u2551 Skills skipped: ${result.skillsSkipped.toString().padStart(8)} \u2551`,
|
|
33784
|
-
`\u2551 Conflicts: ${result.conflicts.length.toString().padStart(8)} \u2551`,
|
|
33785
|
-
`\u2551 Duration: ${(result.duration / 1e3).toFixed(2).padStart(8)}s \u2551`,
|
|
33786
|
-
"\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D",
|
|
33787
|
-
""
|
|
33788
|
-
];
|
|
33789
|
-
return lines.join("\n");
|
|
33790
|
-
}
|
|
33791
|
-
async function mergeActionImpl(sourcePath, targetPath, options) {
|
|
33792
|
-
const { strategy, dryRun, verbose, quiet, force } = options;
|
|
33793
|
-
const validStrategies = [
|
|
33794
|
-
"keep_target",
|
|
33795
|
-
"keep_source",
|
|
33796
|
-
"keep_newer",
|
|
33797
|
-
"merge_fields"
|
|
33798
|
-
];
|
|
33799
|
-
if (!validStrategies.includes(strategy)) {
|
|
33800
|
-
console.error(`Invalid strategy: ${strategy}`);
|
|
33801
|
-
console.error(`Valid strategies: ${validStrategies.join(", ")}`);
|
|
33802
|
-
process.exit(1);
|
|
33803
|
-
}
|
|
33804
|
-
const resolvedSource = resolve13(sourcePath);
|
|
33805
|
-
const resolvedTarget = targetPath ? resolve13(targetPath) : getDefaultDbPath();
|
|
33806
|
-
if (!existsSync12(resolvedSource)) {
|
|
33807
|
-
console.error(`Source database not found: ${resolvedSource}`);
|
|
33808
|
-
process.exit(1);
|
|
33809
|
-
}
|
|
33810
|
-
if (!existsSync12(resolvedTarget)) {
|
|
33811
|
-
console.error(`Target database not found: ${resolvedTarget}`);
|
|
33812
|
-
console.error("Create a new database first with: skillsmith init");
|
|
33813
|
-
process.exit(1);
|
|
33814
|
-
}
|
|
33815
|
-
if (!quiet) {
|
|
33816
|
-
console.log("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557");
|
|
33817
|
-
console.log("\u2551 Skillsmith Database Merge \u2551");
|
|
33818
|
-
console.log("\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563");
|
|
33819
|
-
console.log(`\u2551 Source: ${resolvedSource.slice(-48).padEnd(48)} \u2551`);
|
|
33820
|
-
console.log(`\u2551 Target: ${resolvedTarget.slice(-48).padEnd(48)} \u2551`);
|
|
33821
|
-
console.log(`\u2551 Strategy: ${strategy.padEnd(48)} \u2551`);
|
|
33822
|
-
console.log(`\u2551 Dry Run: ${(dryRun ? "Yes" : "No").padEnd(48)} \u2551`);
|
|
33823
|
-
console.log("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D");
|
|
33824
|
-
console.log("");
|
|
33825
|
-
}
|
|
33826
|
-
let sourceDb = null;
|
|
33827
|
-
let targetDb = null;
|
|
33828
|
-
try {
|
|
33829
|
-
sourceDb = openDatabase(resolvedSource);
|
|
33830
|
-
targetDb = openDatabase(resolvedTarget);
|
|
33831
|
-
if (!force) {
|
|
33832
|
-
const sourceCompat = checkSchemaCompatibility(sourceDb);
|
|
33833
|
-
const targetCompat = checkSchemaCompatibility(targetDb);
|
|
33834
|
-
if (!sourceCompat.isCompatible) {
|
|
33835
|
-
console.error(`Source database: ${sourceCompat.message}`);
|
|
33836
|
-
process.exit(1);
|
|
33837
|
-
}
|
|
33838
|
-
if (!targetCompat.isCompatible) {
|
|
33839
|
-
console.error(`Target database: ${targetCompat.message}`);
|
|
33840
|
-
process.exit(1);
|
|
33841
|
-
}
|
|
33842
|
-
if (!quiet && sourceCompat.action !== "none") {
|
|
33843
|
-
console.log(`Source: ${sourceCompat.message}`);
|
|
33844
|
-
}
|
|
33845
|
-
if (!quiet && targetCompat.action !== "none") {
|
|
33846
|
-
console.log(`Target: ${targetCompat.message}`);
|
|
33847
|
-
}
|
|
33848
|
-
}
|
|
33849
|
-
const mergeOptions = {
|
|
33850
|
-
strategy,
|
|
33851
|
-
dryRun,
|
|
33852
|
-
skipInvalid: true,
|
|
33853
|
-
...verbose && {
|
|
33854
|
-
onConflict: (conflict) => {
|
|
33855
|
-
console.log(` Conflict: ${conflict.skillId} (${conflict.reason})`);
|
|
33856
|
-
return strategy;
|
|
33857
|
-
}
|
|
33858
|
-
}
|
|
33859
|
-
};
|
|
33860
|
-
if (!quiet) {
|
|
33861
|
-
console.log("Merging databases...");
|
|
33862
|
-
}
|
|
33863
|
-
const result = mergeSkillDatabases(targetDb, sourceDb, mergeOptions);
|
|
33864
|
-
if (!quiet) {
|
|
33865
|
-
console.log(formatMergeResult(result));
|
|
33866
|
-
if (dryRun) {
|
|
33867
|
-
console.log("\u26A0\uFE0F DRY RUN: No changes were made to the target database.");
|
|
33868
|
-
console.log(" Remove --dry-run to apply these changes.");
|
|
33869
|
-
} else {
|
|
33870
|
-
console.log("\u2705 Merge complete!");
|
|
33871
|
-
}
|
|
33872
|
-
}
|
|
33873
|
-
if (result.skillsAdded === 0 && result.skillsUpdated === 0) {
|
|
33874
|
-
if (!quiet) {
|
|
33875
|
-
console.log("\nNo new skills to merge.");
|
|
33876
|
-
}
|
|
33877
|
-
}
|
|
33878
|
-
} catch (error46) {
|
|
33879
|
-
console.error("Merge failed:", error46 instanceof Error ? error46.message : error46);
|
|
33880
|
-
process.exit(1);
|
|
33881
|
-
} finally {
|
|
33882
|
-
sourceDb?.close();
|
|
33883
|
-
targetDb?.close();
|
|
33884
|
-
}
|
|
33885
|
-
}
|
|
33886
|
-
var mergeAction = withTelemetry(mergeActionImpl, {
|
|
33887
|
-
source: "cli",
|
|
33888
|
-
extractSkillId: () => "merge",
|
|
33889
|
-
extractFramework: () => "cli"
|
|
33890
|
-
});
|
|
33891
|
-
|
|
33892
|
-
// src/commands/install.ts
|
|
33893
|
-
import { Command as Command11 } from "commander";
|
|
33894
|
-
import ora9 from "ora";
|
|
33895
|
-
var VALID_CLIENT_HINT = "Valid IDs: claude-code | cursor | copilot | windsurf | agents (Codex users pass --client agents).";
|
|
33896
|
-
function parseAlsoLink(raw, defaultClient) {
|
|
33897
|
-
if (!raw || raw.trim() === "") return [];
|
|
33898
|
-
const ids = raw.split(",").map((s) => s.trim()).filter((s) => s !== "");
|
|
33899
|
-
const seen = /* @__PURE__ */ new Set();
|
|
33900
|
-
const out = [];
|
|
33901
|
-
for (const id of ids) {
|
|
33902
|
-
assertClientId(id);
|
|
33903
|
-
if (id === defaultClient) {
|
|
33904
|
-
throw new Error(
|
|
33905
|
-
`--also-link target '${id}' is the same as --client; pick a different client or drop it from --also-link.`
|
|
33906
|
-
);
|
|
33907
|
-
}
|
|
33908
|
-
if (seen.has(id)) {
|
|
33909
|
-
throw new Error(`--also-link target '${id}' is listed more than once`);
|
|
33910
|
-
}
|
|
33911
|
-
seen.add(id);
|
|
33912
|
-
out.push(id);
|
|
33913
|
-
}
|
|
33914
|
-
return out;
|
|
33915
|
-
}
|
|
33916
|
-
function isValidSkillId(skillId) {
|
|
33917
|
-
if (isGitHubUrl(skillId)) return true;
|
|
33918
|
-
return /^[a-zA-Z0-9_-]+\/[a-zA-Z0-9_.-]+$/.test(skillId);
|
|
33919
|
-
}
|
|
33920
|
-
function createDbRegistryLookup(skillRepo, db) {
|
|
33921
|
-
let quarantineRepo;
|
|
33922
|
-
return {
|
|
33923
|
-
async lookup(skillId) {
|
|
33924
|
-
const skill = skillRepo.findById(skillId);
|
|
33925
|
-
if (!skill) return null;
|
|
33926
|
-
if (!skill.repoUrl) return null;
|
|
33927
|
-
quarantineRepo ??= new QuarantineRepository(db);
|
|
33928
|
-
return {
|
|
33929
|
-
repoUrl: skill.repoUrl,
|
|
33930
|
-
name: skill.name,
|
|
33931
|
-
trustTier: skill.trustTier,
|
|
33932
|
-
quarantined: quarantineRepo.isQuarantined(skill.id || skillId)
|
|
33933
|
-
};
|
|
33934
|
-
}
|
|
33935
|
-
};
|
|
33936
|
-
}
|
|
33937
|
-
async function createApiBackedRegistryLookup(skillRepo, db) {
|
|
33938
|
-
const dbLookup = createDbRegistryLookup(skillRepo, db);
|
|
33939
|
-
const jwtToken = await loadStoredAccessToken();
|
|
33940
|
-
const apiClient = createApiClient(jwtToken ? { jwtToken } : {});
|
|
33941
|
-
return {
|
|
33942
|
-
async lookup(skillId) {
|
|
33943
|
-
const local = await dbLookup.lookup(skillId);
|
|
33944
|
-
if (local) return local;
|
|
33945
|
-
if (apiClient.isOffline()) return null;
|
|
33946
|
-
try {
|
|
33947
|
-
const response = await apiClient.getSkill(skillId);
|
|
33948
|
-
const r = response.data;
|
|
33949
|
-
if (!r.repo_url) return null;
|
|
33950
|
-
return {
|
|
33951
|
-
repoUrl: r.repo_url,
|
|
33952
|
-
name: r.name,
|
|
33953
|
-
trustTier: SkillsmithApiClient.toSkill(r).trustTier,
|
|
33954
|
-
quarantined: r.quarantined === true || r.installable === false
|
|
33955
|
-
};
|
|
33956
|
-
} catch {
|
|
33957
|
-
return null;
|
|
33958
|
-
}
|
|
33959
|
-
}
|
|
33960
|
-
};
|
|
33961
|
-
}
|
|
33962
|
-
function formatJsonResult(result) {
|
|
33963
|
-
return JSON.stringify(
|
|
33964
|
-
{
|
|
33965
|
-
success: result.success,
|
|
33966
|
-
skillId: result.skillId,
|
|
33967
|
-
installPath: result.installPath,
|
|
33968
|
-
error: result.error,
|
|
33969
|
-
trustTier: result.trustTier,
|
|
33970
|
-
optimization: result.optimization,
|
|
33971
|
-
tips: result.tips
|
|
33972
|
-
},
|
|
33973
|
-
null,
|
|
33974
|
-
2
|
|
33975
|
-
);
|
|
34011
|
+
import { Command as Command11 } from "commander";
|
|
34012
|
+
import { resolve as resolve13 } from "path";
|
|
34013
|
+
import { existsSync as existsSync12 } from "fs";
|
|
34014
|
+
function formatMergeResult(result) {
|
|
34015
|
+
const lines = [
|
|
34016
|
+
"",
|
|
34017
|
+
"\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557",
|
|
34018
|
+
"\u2551 Merge Results \u2551",
|
|
34019
|
+
"\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563",
|
|
34020
|
+
`\u2551 Skills added: ${result.skillsAdded.toString().padStart(8)} \u2551`,
|
|
34021
|
+
`\u2551 Skills updated: ${result.skillsUpdated.toString().padStart(8)} \u2551`,
|
|
34022
|
+
`\u2551 Skills skipped: ${result.skillsSkipped.toString().padStart(8)} \u2551`,
|
|
34023
|
+
`\u2551 Conflicts: ${result.conflicts.length.toString().padStart(8)} \u2551`,
|
|
34024
|
+
`\u2551 Duration: ${(result.duration / 1e3).toFixed(2).padStart(8)}s \u2551`,
|
|
34025
|
+
"\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D",
|
|
34026
|
+
""
|
|
34027
|
+
];
|
|
34028
|
+
return lines.join("\n");
|
|
33976
34029
|
}
|
|
33977
|
-
function
|
|
33978
|
-
|
|
33979
|
-
|
|
33980
|
-
|
|
33981
|
-
|
|
33982
|
-
|
|
33983
|
-
|
|
33984
|
-
|
|
33985
|
-
|
|
33986
|
-
|
|
33987
|
-
|
|
33988
|
-
|
|
33989
|
-
if (result.optimization.subagentGenerated) {
|
|
33990
|
-
console.log(source_default.dim(` Companion subagent generated`));
|
|
33991
|
-
}
|
|
33992
|
-
}
|
|
33993
|
-
if (result.contentHashMismatch) {
|
|
33994
|
-
console.log(source_default.yellow("\n Warning: Content has changed since last indexed."));
|
|
33995
|
-
console.log(source_default.yellow(" Review recent changes at the skill's repository before using."));
|
|
33996
|
-
}
|
|
33997
|
-
if (result.tips && result.tips.length > 0 && !quiet) {
|
|
33998
|
-
const startIndex = result.contentHashMismatch ? 1 : 0;
|
|
33999
|
-
if (startIndex < result.tips.length) {
|
|
34000
|
-
console.log();
|
|
34001
|
-
for (let i = startIndex; i < result.tips.length; i++) {
|
|
34002
|
-
console.log(source_default.dim(` Tip: ${result.tips[i]}`));
|
|
34003
|
-
}
|
|
34004
|
-
}
|
|
34005
|
-
}
|
|
34006
|
-
} else {
|
|
34007
|
-
console.error(source_default.red(`
|
|
34008
|
-
Installation failed: ${result.error}`));
|
|
34009
|
-
if (result.securityReport && !result.securityReport.passed) {
|
|
34010
|
-
console.error(source_default.red(" Security scan failed."));
|
|
34011
|
-
for (const finding of result.securityReport.findings) {
|
|
34012
|
-
if (finding.severity === "critical" || finding.severity === "high") {
|
|
34013
|
-
console.error(source_default.red(` [${finding.severity}] ${finding.message}`));
|
|
34014
|
-
}
|
|
34015
|
-
}
|
|
34016
|
-
}
|
|
34017
|
-
if (result.tips && result.tips.length > 0 && !quiet) {
|
|
34018
|
-
console.log();
|
|
34019
|
-
for (const tip of result.tips) {
|
|
34020
|
-
console.log(source_default.dim(` ${tip}`));
|
|
34021
|
-
}
|
|
34022
|
-
}
|
|
34030
|
+
async function mergeActionImpl(sourcePath, targetPath, options) {
|
|
34031
|
+
const { strategy, dryRun, verbose, quiet, force } = options;
|
|
34032
|
+
const validStrategies = [
|
|
34033
|
+
"keep_target",
|
|
34034
|
+
"keep_source",
|
|
34035
|
+
"keep_newer",
|
|
34036
|
+
"merge_fields"
|
|
34037
|
+
];
|
|
34038
|
+
if (!validStrategies.includes(strategy)) {
|
|
34039
|
+
console.error(`Invalid strategy: ${strategy}`);
|
|
34040
|
+
console.error(`Valid strategies: ${validStrategies.join(", ")}`);
|
|
34041
|
+
process.exit(1);
|
|
34023
34042
|
}
|
|
34024
|
-
|
|
34025
|
-
|
|
34026
|
-
|
|
34027
|
-
|
|
34043
|
+
const resolvedSource = resolve13(sourcePath);
|
|
34044
|
+
const resolvedTarget = targetPath ? resolve13(targetPath) : getDefaultDbPath();
|
|
34045
|
+
if (!existsSync12(resolvedSource)) {
|
|
34046
|
+
console.error(`Source database not found: ${resolvedSource}`);
|
|
34047
|
+
process.exit(1);
|
|
34048
|
+
}
|
|
34049
|
+
if (!existsSync12(resolvedTarget)) {
|
|
34050
|
+
console.error(`Target database not found: ${resolvedTarget}`);
|
|
34051
|
+
console.error("Create a new database first with: skillsmith init");
|
|
34052
|
+
process.exit(1);
|
|
34053
|
+
}
|
|
34054
|
+
if (!quiet) {
|
|
34055
|
+
console.log("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557");
|
|
34056
|
+
console.log("\u2551 Skillsmith Database Merge \u2551");
|
|
34057
|
+
console.log("\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563");
|
|
34058
|
+
console.log(`\u2551 Source: ${resolvedSource.slice(-48).padEnd(48)} \u2551`);
|
|
34059
|
+
console.log(`\u2551 Target: ${resolvedTarget.slice(-48).padEnd(48)} \u2551`);
|
|
34060
|
+
console.log(`\u2551 Strategy: ${strategy.padEnd(48)} \u2551`);
|
|
34061
|
+
console.log(`\u2551 Dry Run: ${(dryRun ? "Yes" : "No").padEnd(48)} \u2551`);
|
|
34062
|
+
console.log("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D");
|
|
34063
|
+
console.log("");
|
|
34064
|
+
}
|
|
34065
|
+
let sourceDb = null;
|
|
34066
|
+
let targetDb = null;
|
|
34028
34067
|
try {
|
|
34029
|
-
|
|
34030
|
-
|
|
34031
|
-
|
|
34032
|
-
|
|
34033
|
-
);
|
|
34034
|
-
|
|
34035
|
-
|
|
34036
|
-
|
|
34037
|
-
const alsoLinkClients = parseAlsoLink(opts.alsoLink, client);
|
|
34038
|
-
const skillsDir = getInstallPath(client);
|
|
34039
|
-
if (!isValidSkillId(skillId)) {
|
|
34040
|
-
const errorMsg = 'Invalid skill ID format. Expected "author/name" or a GitHub URL.\n Examples:\n skillsmith install getsentry/commit\n skillsmith install https://github.com/owner/repo';
|
|
34041
|
-
if (jsonOutput) {
|
|
34042
|
-
console.log(JSON.stringify({ success: false, skillId, error: errorMsg }, null, 2));
|
|
34043
|
-
} else {
|
|
34044
|
-
console.error(source_default.red(errorMsg));
|
|
34045
|
-
}
|
|
34046
|
-
process.exit(1);
|
|
34047
|
-
return;
|
|
34048
|
-
}
|
|
34049
|
-
const dbPath = opts.db ?? DEFAULT_DB_PATH;
|
|
34050
|
-
const db = await openCliDatabase(dbPath);
|
|
34051
|
-
const spinner = jsonOutput ? null : ora9("Installing skill...").start();
|
|
34052
|
-
try {
|
|
34053
|
-
const skillRepo = new SkillRepository(db);
|
|
34054
|
-
const skillDependencyRepo = new SkillDependencyRepository(db);
|
|
34055
|
-
const registryLookup = await createApiBackedRegistryLookup(skillRepo, db);
|
|
34056
|
-
const service = new SkillInstallationService({
|
|
34057
|
-
db,
|
|
34058
|
-
skillRepo,
|
|
34059
|
-
skillDependencyRepo,
|
|
34060
|
-
skillsDir,
|
|
34061
|
-
manifestPath: DEFAULT_MANIFEST_PATH,
|
|
34062
|
-
registryLookup,
|
|
34063
|
-
onProgress: (_stage, detail) => {
|
|
34064
|
-
if (spinner) {
|
|
34065
|
-
spinner.text = detail;
|
|
34066
|
-
}
|
|
34067
|
-
}
|
|
34068
|
-
});
|
|
34069
|
-
const installOptions = {};
|
|
34070
|
-
if (opts.force !== void 0) {
|
|
34071
|
-
installOptions.force = opts.force;
|
|
34068
|
+
sourceDb = openDatabase(resolvedSource);
|
|
34069
|
+
targetDb = openDatabase(resolvedTarget);
|
|
34070
|
+
if (!force) {
|
|
34071
|
+
const sourceCompat = checkSchemaCompatibility(sourceDb);
|
|
34072
|
+
const targetCompat = checkSchemaCompatibility(targetDb);
|
|
34073
|
+
if (!sourceCompat.isCompatible) {
|
|
34074
|
+
console.error(`Source database: ${sourceCompat.message}`);
|
|
34075
|
+
process.exit(1);
|
|
34072
34076
|
}
|
|
34073
|
-
if (
|
|
34074
|
-
|
|
34077
|
+
if (!targetCompat.isCompatible) {
|
|
34078
|
+
console.error(`Target database: ${targetCompat.message}`);
|
|
34079
|
+
process.exit(1);
|
|
34075
34080
|
}
|
|
34076
|
-
if (
|
|
34077
|
-
|
|
34081
|
+
if (!quiet && sourceCompat.action !== "none") {
|
|
34082
|
+
console.log(`Source: ${sourceCompat.message}`);
|
|
34078
34083
|
}
|
|
34079
|
-
|
|
34080
|
-
|
|
34081
|
-
void emitInstallEvent({
|
|
34082
|
-
skillId,
|
|
34083
|
-
source: "cli",
|
|
34084
|
-
success: result.success,
|
|
34085
|
-
durationMs: Date.now() - installStart,
|
|
34086
|
-
...result.trustTier !== void 0 && { trustTier: result.trustTier },
|
|
34087
|
-
...!result.success && result.errorCode !== void 0 && { errorCode: result.errorCode }
|
|
34088
|
-
});
|
|
34089
|
-
if (result.success && alsoLinkClients.length > 0) {
|
|
34090
|
-
for (const target of alsoLinkClients) {
|
|
34091
|
-
try {
|
|
34092
|
-
const linked = await addLink({
|
|
34093
|
-
skillId,
|
|
34094
|
-
fromClient: client,
|
|
34095
|
-
toClient: target,
|
|
34096
|
-
preferSymlink: opts.symlink ?? false,
|
|
34097
|
-
force: opts.force ?? false
|
|
34098
|
-
});
|
|
34099
|
-
if (!quiet && !jsonOutput) {
|
|
34100
|
-
const note = linked.fellBackToCopy ? " (fell back to copy)" : "";
|
|
34101
|
-
console.log(source_default.dim(` Linked into ${target} as ${linked.record.kind}${note}`));
|
|
34102
|
-
}
|
|
34103
|
-
} catch (linkErr) {
|
|
34104
|
-
if (!jsonOutput) {
|
|
34105
|
-
console.warn(
|
|
34106
|
-
source_default.yellow(` Warning: could not link to ${target}: ${sanitizeError(linkErr)}`)
|
|
34107
|
-
);
|
|
34108
|
-
}
|
|
34109
|
-
}
|
|
34110
|
-
}
|
|
34084
|
+
if (!quiet && targetCompat.action !== "none") {
|
|
34085
|
+
console.log(`Target: ${targetCompat.message}`);
|
|
34111
34086
|
}
|
|
34112
|
-
|
|
34113
|
-
|
|
34114
|
-
|
|
34115
|
-
|
|
34116
|
-
|
|
34087
|
+
}
|
|
34088
|
+
const mergeOptions = {
|
|
34089
|
+
strategy,
|
|
34090
|
+
dryRun,
|
|
34091
|
+
skipInvalid: true,
|
|
34092
|
+
...verbose && {
|
|
34093
|
+
onConflict: (conflict) => {
|
|
34094
|
+
console.log(` Conflict: ${conflict.skillId} (${conflict.reason})`);
|
|
34095
|
+
return strategy;
|
|
34117
34096
|
}
|
|
34118
34097
|
}
|
|
34119
|
-
|
|
34120
|
-
|
|
34098
|
+
};
|
|
34099
|
+
if (!quiet) {
|
|
34100
|
+
console.log("Merging databases...");
|
|
34101
|
+
}
|
|
34102
|
+
const result = mergeSkillDatabases(targetDb, sourceDb, mergeOptions);
|
|
34103
|
+
if (!quiet) {
|
|
34104
|
+
console.log(formatMergeResult(result));
|
|
34105
|
+
if (dryRun) {
|
|
34106
|
+
console.log("\u26A0\uFE0F DRY RUN: No changes were made to the target database.");
|
|
34107
|
+
console.log(" Remove --dry-run to apply these changes.");
|
|
34121
34108
|
} else {
|
|
34122
|
-
|
|
34109
|
+
console.log("\u2705 Merge complete!");
|
|
34123
34110
|
}
|
|
34124
|
-
|
|
34125
|
-
|
|
34111
|
+
}
|
|
34112
|
+
if (result.skillsAdded === 0 && result.skillsUpdated === 0) {
|
|
34113
|
+
if (!quiet) {
|
|
34114
|
+
console.log("\nNo new skills to merge.");
|
|
34126
34115
|
}
|
|
34127
|
-
} finally {
|
|
34128
|
-
db.close();
|
|
34129
34116
|
}
|
|
34130
34117
|
} catch (error46) {
|
|
34131
|
-
|
|
34132
|
-
console.log(JSON.stringify({ success: false, skillId, error: sanitizeError(error46) }, null, 2));
|
|
34133
|
-
} else {
|
|
34134
|
-
console.error(source_default.red("Install error:"), sanitizeError(error46));
|
|
34135
|
-
}
|
|
34118
|
+
console.error("Merge failed:", error46 instanceof Error ? error46.message : error46);
|
|
34136
34119
|
process.exit(1);
|
|
34120
|
+
} finally {
|
|
34121
|
+
sourceDb?.close();
|
|
34122
|
+
targetDb?.close();
|
|
34137
34123
|
}
|
|
34138
34124
|
}
|
|
34139
|
-
var
|
|
34125
|
+
var mergeAction = withTelemetry(mergeActionImpl, {
|
|
34140
34126
|
source: "cli",
|
|
34141
|
-
extractSkillId: () => "
|
|
34127
|
+
extractSkillId: () => "merge",
|
|
34142
34128
|
extractFramework: () => "cli"
|
|
34143
34129
|
});
|
|
34144
|
-
function createInstallCommand() {
|
|
34145
|
-
return new Command11("install").description("Install a skill from the registry or GitHub URL").argument("<skillId>", "Skill ID (author/name) or GitHub URL").option("-f, --force", "Force reinstall if already installed").option("--skip-scan", "Skip security scan (not recommended)").option("--skip-optimize", "Skip Skillsmith optimization").option("-q, --quiet", "Suppress advisory output").option("--json", "Output structured JSON result").option("-d, --db <path>", "Database file path", DEFAULT_DB_PATH).option("--client <id>", `install for a specific agent (${VALID_CLIENT_HINT})`, "claude-code").option(
|
|
34146
|
-
"--also-link <ids>",
|
|
34147
|
-
"comma-separated additional clients to fan-out into (default: copy; pair with --symlink for POSIX symlinks)",
|
|
34148
|
-
""
|
|
34149
|
-
).option(
|
|
34150
|
-
"--symlink",
|
|
34151
|
-
"use relative symlinks instead of file copies for --also-link (POSIX only; falls back to copy on Windows EPERM)",
|
|
34152
|
-
false
|
|
34153
|
-
).action(installAction);
|
|
34154
|
-
}
|
|
34155
34130
|
|
|
34156
34131
|
// src/commands/install-skill.ts
|
|
34157
34132
|
import { Command as Command12 } from "commander";
|