@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/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 ora from "ora";
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
- var OWNER_PERM_CHMOD = /\bchmod\s+(?:[0-7]{3,4}|[ugoa]*\+x)\b/i;
18677
- var CHMOD_FETCH_CONTEXT = /\b(?:curl|wget)\b|\bgit\s+clone\b|\bnpx\b[^\n]{0,80}https?:\/\//i;
18678
- var CHMOD_TARGET = /\bchmod\s+(?:[0-7]{3,4}|[ugoa]*\+x)\s+(\S+)/i;
18679
- function escapeRegExp(s) {
18680
- return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
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 implicitDownloadBasename(line) {
18683
- const lastSegment = (urlAfterScheme) => {
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 scanChmodFetchCompound(content, alreadyFlaggedLines, lineContexts) {
18703
- const findings = [];
18704
- const lines = content.split("\n");
18705
- const contexts = lineContexts ?? analyzeMarkdownContext(content);
18706
- const fetchLines = lines.filter((l) => CHMOD_FETCH_CONTEXT.test(l));
18707
- lines.forEach((line, index) => {
18708
- const lineNumber = index + 1;
18709
- if (alreadyFlaggedLines.has(lineNumber))
18710
- return;
18711
- const match = safeRegexTest(OWNER_PERM_CHMOD, line);
18712
- if (!match)
18713
- return;
18714
- const window2 = [lines[index - 1] ?? "", line, lines[index + 1] ?? ""].join("\n");
18715
- const adjacentFetch = CHMOD_FETCH_CONTEXT.test(window2);
18716
- let correlated = false;
18717
- const tm = line.match(CHMOD_TARGET);
18718
- if (tm) {
18719
- const base = tm[1].replace(/['"]/g, "").split("/").pop() ?? "";
18720
- if (base.length >= 3) {
18721
- const re = new RegExp(`(?:-o|-O|--output|>>?)\\s*['"]?(?:[^\\s'"]*/)?${escapeRegExp(base)}(?:[\\s'"?]|$)`);
18722
- correlated = fetchLines.some((l) => re.test(l) || implicitDownloadBasename(l) === base);
18723
- }
18724
- }
18725
- if (!adjacentFetch && !correlated)
18726
- return;
18727
- const ctx = contexts[index];
18728
- const inInlineCode = ctx?.isInlineCode && isWithinInlineCode(line, match.index ?? 0);
18729
- const inDocContext = ctx ? isDocumentationContext(ctx) || inInlineCode : false;
18730
- findings.push({
18731
- type: "privilege_escalation",
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/search.helpers.ts
29807
- async function searchRemoteOrLocal(searchOptions, db) {
29808
- try {
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/search-types.ts
29876
- var PAGE_SIZE = 10;
29877
-
29878
- // src/commands/search-formatters.ts
29879
- import Table from "cli-table3";
29880
- var TRUST_TIER_COLORS = {
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 = ora("Installing skill...").start();
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 = buildRegistryLookupWithApiFallback(skillRepo);
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 : ora("Searching Skillsmith registry...").start();
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 Command("search").description(
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 Command2 } from "commander";
30573
+ import { Command as Command3 } from "commander";
30335
30574
  import { confirm } from "@inquirer/prompts";
30336
30575
  import Table2 from "cli-table3";
30337
- import ora2 from "ora";
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 = ora2(`Checking updates for ${skillName}...`).start();
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 = ora2(`Updating ${skillName}...`).start();
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 = ora2(`Removing ${skillName}...`).start();
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 Command2("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);
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 Command2("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);
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 Command2("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);
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 Command3 } from "commander";
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 ora3 from "ora";
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 = ora3("Creating skill structure...").start();
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 = ora3("Validating skill...").start();
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 = ora3("Preparing skill for publishing...").start();
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 Command3("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(
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 Command3("validate").description("Validate a local SKILL.md file").argument("[path]", "Path to SKILL.md or skill directory", ".").action(validateAction);
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 Command3("publish").description("Prepare skill for sharing").argument("[path]", "Path to skill directory", ".").option("--check-references", "Scan for project-specific references before publishing").option(
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 Command4 } from "commander";
32154
- import ora4 from "ora";
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 = ora4("Generating subagent...").start();
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 Command4("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);
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 Command5 } from "commander";
32402
- import ora5 from "ora";
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 = ora5("Transforming skill...").start();
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 Command5("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);
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 Command6 } from "commander";
32751
+ import { Command as Command7 } from "commander";
32513
32752
  import { input as input3, confirm as confirm3 } from "@inquirer/prompts";
32514
- import ora6 from "ora";
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 = ora6("Creating MCP server...").start();
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 Command6("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);
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 Command7 } from "commander";
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 Command7("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);
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 Command8 } from "commander";
32828
- import ora7 from "ora";
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 = ora7();
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 Command8("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(
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 Command9 } from "commander";
33579
+ import { Command as Command10 } from "commander";
33341
33580
 
33342
33581
  // src/commands/sync.action.ts
33343
- import ora8 from "ora";
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 = ora8();
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 Command9("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) => {
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 Command9("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) => {
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 Command9("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) => {
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 Command9("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) => {
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 Command10 } from "commander";
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 displayResult(result, quiet) {
33978
- if (result.success) {
33979
- console.log(source_default.green("\nSkill installed successfully!"));
33980
- console.log(source_default.dim(` Path: ${result.installPath}`));
33981
- if (result.trustTier) {
33982
- console.log(source_default.dim(` Trust tier: ${result.trustTier}`));
33983
- }
33984
- if (result.optimization?.optimized && !quiet) {
33985
- console.log(source_default.dim(` Optimized: ${result.optimization.tokenReductionPercent}% reduction`));
33986
- if (result.optimization.subSkills && result.optimization.subSkills.length > 0) {
33987
- console.log(source_default.dim(` Sub-skills: ${result.optimization.subSkills.join(", ")}`));
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
- async function installActionImpl(skillId, opts) {
34026
- const quiet = opts.quiet ?? false;
34027
- const jsonOutput = opts.json ?? false;
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
- const rawClient = opts.client ?? "claude-code";
34030
- if (rawClient.includes(",")) {
34031
- throw new Error(
34032
- `--client takes a single value (got '${rawClient}'). Pass --also-link <ids> to fan-out into additional clients.`
34033
- );
34034
- }
34035
- assertClientId(rawClient);
34036
- const client = rawClient;
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 (opts.skipScan !== void 0) {
34074
- installOptions.skipScan = opts.skipScan;
34077
+ if (!targetCompat.isCompatible) {
34078
+ console.error(`Target database: ${targetCompat.message}`);
34079
+ process.exit(1);
34075
34080
  }
34076
- if (opts.skipOptimize !== void 0) {
34077
- installOptions.skipOptimize = opts.skipOptimize;
34081
+ if (!quiet && sourceCompat.action !== "none") {
34082
+ console.log(`Source: ${sourceCompat.message}`);
34078
34083
  }
34079
- const installStart = Date.now();
34080
- const result = await service.install(skillId, installOptions);
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
- if (spinner) {
34113
- if (result.success) {
34114
- spinner.succeed("Skill installed");
34115
- } else {
34116
- spinner.fail("Installation failed");
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
- if (jsonOutput) {
34120
- console.log(formatJsonResult(result));
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
- displayResult(result, quiet);
34109
+ console.log("\u2705 Merge complete!");
34123
34110
  }
34124
- if (!result.success) {
34125
- process.exit(1);
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
- if (jsonOutput) {
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 installAction = withTelemetry(installActionImpl, {
34125
+ var mergeAction = withTelemetry(mergeActionImpl, {
34140
34126
  source: "cli",
34141
- extractSkillId: () => "install",
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";