@skillsmith/cli 0.8.7 → 0.8.8

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
@@ -1767,7 +1767,7 @@ async function removeLinks(skillId) {
1767
1767
  }
1768
1768
 
1769
1769
  // ../core/dist/src/install/agent-pack-installer.js
1770
- import { existsSync as existsSync9 } from "node:fs";
1770
+ import { existsSync as existsSync10 } from "node:fs";
1771
1771
  import { homedir as homedir6 } from "node:os";
1772
1772
  import { join as join16 } from "node:path";
1773
1773
 
@@ -2551,7 +2551,7 @@ var PAYWALL_TRIGGERS = [
2551
2551
  {
2552
2552
  id: "T2",
2553
2553
  title: "T2 - quota forecast (to Individual)",
2554
- body: `When usage is on track to exhaust the free 1,000-call monthly quota, you may note the forecast ("at this pace you reach the cap in about K days") and mention Individual's 10,000 calls. Use this sparingly; a quota nag reads as a tax.`
2554
+ body: `When usage is on track to exhaust the free 100-call monthly quota, you may note the forecast ("at this pace you reach the cap in about K days") and mention Individual's 1,000 calls. Use this sparingly; a quota nag reads as a tax.`
2555
2555
  },
2556
2556
  {
2557
2557
  id: "T4",
@@ -3382,9 +3382,12 @@ function mergeJsonArrayEntry(opts) {
3382
3382
  let missingDefaults = false;
3383
3383
  if (ensureTopLevelDefaults) {
3384
3384
  for (const [key, value] of Object.entries(ensureTopLevelDefaults)) {
3385
- if (doc[key] === void 0) {
3385
+ const existingValue = doc[key];
3386
+ if (existingValue === void 0) {
3386
3387
  doc[key] = value;
3387
3388
  missingDefaults = true;
3389
+ } else if (!deepEqualJson(existingValue, value)) {
3390
+ return { status: "conflict", path: path27, backupPath: null };
3388
3391
  }
3389
3392
  }
3390
3393
  }
@@ -3695,6 +3698,7 @@ function installMcpConfig(harness, ctx, report) {
3695
3698
  }
3696
3699
 
3697
3700
  // ../core/dist/src/install/agent-pack-installer.cursor-hooks.js
3701
+ import { existsSync as existsSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync9 } from "node:fs";
3698
3702
  import { join as join15 } from "node:path";
3699
3703
  function mergeSucceeded2(status) {
3700
3704
  return status === "created" || status === "updated" || status === "unchanged";
@@ -3753,16 +3757,81 @@ function installCursorHooks(startArtifact, endArtifact, ctx, report) {
3753
3757
  ensureTopLevelDefaults: CURSOR_HOOKS_JSON_DEFAULTS
3754
3758
  });
3755
3759
  report.hookConfig.push(startWire, endWire);
3756
- if (mergeSucceeded2(startWire.status) || mergeSucceeded2(endWire.status)) {
3760
+ const wireConflict = startWire.status === "conflict" || endWire.status === "conflict";
3761
+ const legacyCleanup = wireConflict ? null : stripStaleLegacyHookKeys(configPath2, startPath, endPath, ctx);
3762
+ if (legacyCleanup)
3763
+ report.hookConfig.push(legacyCleanup);
3764
+ if (mergeSucceeded2(startWire.status) || mergeSucceeded2(endWire.status) || legacyCleanup !== null && mergeSucceeded2(legacyCleanup.status)) {
3757
3765
  ctx.entries.push({
3758
3766
  path: configPath2,
3759
3767
  kind: "hook-config",
3760
3768
  harness: "cursor",
3761
- backupPath: startWire.backupPath ?? endWire.backupPath,
3769
+ backupPath: startWire.backupPath ?? endWire.backupPath ?? legacyCleanup?.backupPath ?? null,
3762
3770
  executable: false
3763
3771
  });
3764
3772
  }
3765
3773
  }
3774
+ function stripStaleLegacyHookKeys(configPath2, startPath, endPath, ctx) {
3775
+ const unchanged = () => ({ status: "unchanged", path: configPath2, backupPath: null });
3776
+ if (!existsSync9(configPath2))
3777
+ return unchanged();
3778
+ let doc;
3779
+ try {
3780
+ const parsed = JSON.parse(readFileSync8(configPath2, "utf-8"));
3781
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
3782
+ return {
3783
+ status: "error",
3784
+ path: configPath2,
3785
+ backupPath: null,
3786
+ errorMessage: "not a JSON object"
3787
+ };
3788
+ }
3789
+ doc = parsed;
3790
+ } catch (e) {
3791
+ return {
3792
+ status: "error",
3793
+ path: configPath2,
3794
+ backupPath: null,
3795
+ errorMessage: e.message
3796
+ };
3797
+ }
3798
+ const hooks = doc.hooks;
3799
+ if (!hooks || typeof hooks !== "object" || Array.isArray(hooks))
3800
+ return unchanged();
3801
+ const hooksObj = hooks;
3802
+ const claudeTarget = AGENT_HOOK_TARGETS["claude-code"];
3803
+ const legacyStartKey = claudeTarget.sessionStartKeyPath.at(-1);
3804
+ const legacyEndKey = claudeTarget.sessionEndKeyPath.at(-1);
3805
+ let changed = false;
3806
+ for (const [legacyKey, ownPath] of [
3807
+ [legacyStartKey, startPath],
3808
+ [legacyEndKey, endPath]
3809
+ ]) {
3810
+ if (legacyKey === void 0)
3811
+ continue;
3812
+ const legacyValue = hooksObj[legacyKey];
3813
+ if (legacyValue === void 0)
3814
+ continue;
3815
+ if (!Array.isArray(legacyValue))
3816
+ continue;
3817
+ const remaining = legacyValue.filter((item) => hookEntryCommand(item) !== ownPath);
3818
+ const needsCleanup = legacyValue.length === 0 || remaining.length !== legacyValue.length;
3819
+ if (!needsCleanup)
3820
+ continue;
3821
+ changed = true;
3822
+ if (remaining.length === 0) {
3823
+ delete hooksObj[legacyKey];
3824
+ } else {
3825
+ hooksObj[legacyKey] = remaining;
3826
+ }
3827
+ }
3828
+ if (!changed)
3829
+ return unchanged();
3830
+ const backupPath = shouldBackup(configPath2, ctx.backedUpPaths) ? writeBackup5(configPath2, ctx.backupDir) : null;
3831
+ markBackedUp(configPath2, ctx.backedUpPaths);
3832
+ writeFileSync9(configPath2, JSON.stringify(doc, null, 2) + "\n", { mode: 384 });
3833
+ return { status: "updated", path: configPath2, backupPath };
3834
+ }
3766
3835
  function cursorHookEntry(scriptPath) {
3767
3836
  return { command: scriptPath };
3768
3837
  }
@@ -3787,10 +3856,10 @@ var HARNESS_SUPPORT_TIER = {
3787
3856
  // ../core/dist/src/install/agent-pack-installer.js
3788
3857
  var OPTIONAL_SKILL_PACK_HARNESSES = ["windsurf", "opencode", "hermes"];
3789
3858
  function isPresent(nativePath, homeDir) {
3790
- return existsSync9(relocateUnderHome(nativePath, homeDir));
3859
+ return existsSync10(relocateUnderHome(nativePath, homeDir));
3791
3860
  }
3792
3861
  function isCodexPresent(homeDir) {
3793
- return existsSync9(relocateUnderHome(join16(homedir6(), ".codex"), homeDir));
3862
+ return existsSync10(relocateUnderHome(join16(homedir6(), ".codex"), homeDir));
3794
3863
  }
3795
3864
  function writeSkillPackFor(clientNativePath, content, ctx, harness) {
3796
3865
  const path27 = join16(relocateUnderHome(clientNativePath, ctx.homeDir), AGENT_PACK_SKILL_NAME, "SKILL.md");
@@ -3824,7 +3893,7 @@ function newReport(harness) {
3824
3893
  function carryForwardPriorBackups(entries) {
3825
3894
  const priorBackupByPath = /* @__PURE__ */ new Map();
3826
3895
  for (const prior of loadAgentManifest().entries) {
3827
- if (prior.backupPath && existsSync9(prior.backupPath)) {
3896
+ if (prior.backupPath && existsSync10(prior.backupPath)) {
3828
3897
  priorBackupByPath.set(prior.path, prior.backupPath);
3829
3898
  }
3830
3899
  }
@@ -3921,7 +3990,7 @@ function installAgentPack(opts = {}) {
3921
3990
 
3922
3991
  // ../core/dist/src/install/agent-pack-uninstaller.js
3923
3992
  import { dirname as dirname9 } from "node:path";
3924
- import { existsSync as existsSync10, readFileSync as readFileSync8, rmdirSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync9 } from "node:fs";
3993
+ import { existsSync as existsSync11, readFileSync as readFileSync9, rmdirSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync10 } from "node:fs";
3925
3994
 
3926
3995
  // ../core/dist/src/install/agent-manifest-path-guard.js
3927
3996
  import { homedir as homedir7 } from "node:os";
@@ -3989,14 +4058,14 @@ function uninstallAgentPack(_opts = {}) {
3989
4058
  rejected.push(entry.path);
3990
4059
  continue;
3991
4060
  }
3992
- if (!existsSync10(entry.path)) {
4061
+ if (!existsSync11(entry.path)) {
3993
4062
  alreadyGone.push(entry.path);
3994
4063
  continue;
3995
4064
  }
3996
4065
  touchedDirs.add(dirname9(entry.path));
3997
- if (entry.backupPath && existsSync10(entry.backupPath)) {
3998
- const content = readFileSync8(entry.backupPath, "utf-8");
3999
- writeFileSync9(entry.path, content, "utf-8");
4066
+ if (entry.backupPath && existsSync11(entry.backupPath)) {
4067
+ const content = readFileSync9(entry.backupPath, "utf-8");
4068
+ writeFileSync10(entry.path, content, "utf-8");
4000
4069
  restored.push(entry.path);
4001
4070
  } else {
4002
4071
  unlinkSync2(entry.path);
@@ -20476,6 +20545,36 @@ function assertEvidenceCoverage() {
20476
20545
  }
20477
20546
  assertEvidenceCoverage();
20478
20547
 
20548
+ // ../core/dist/src/security/scanner/patterns.exec.js
20549
+ var CODE_EXECUTION_PATTERNS = [
20550
+ // curl|wget <target> | [sudo] <interpreter> (fetch piped to a shell or scripting interpreter)
20551
+ /(?:curl|wget)\b[^\n|]{0,150}?(?:https?:\/\/|\d{1,3}(?:\.\d{1,3}){3}|[\w-]{2,63}\.[a-z]{2,24})[^\n|]{0,150}?\|\s*(?:sudo\s+(?:-[A-Za-z]+\s+)?)?(?:(?:ba|z|da)?sh|python[23]?|node|ruby|perl|php|fish|bun|deno)\b/i,
20552
+ // process substitution: bash/sh/zsh/source/. <(curl|wget <target> ...)
20553
+ /(?:^|[\s;&])(?:source|\.|ba?sh|zsh|exec)\s+<\(\s*(?:curl|wget)\b[^\n)]{0,150}?(?:https?:\/\/|\d{1,3}(?:\.\d{1,3}){3}|[\w-]{2,63}\.[a-z]{2,24})/i,
20554
+ // command substitution into eval or `sh -c`: eval "$(curl <target>...)", bash -c "`wget <target>...`"
20555
+ /(?:\beval\b|(?:ba|z)?sh\s+-c)\s+["']?[$`]\(?\s*(?:curl|wget)\b[^\n)]{0,150}?(?:https?:\/\/|\d{1,3}(?:\.\d{1,3}){3}|[\w-]{2,63}\.[a-z]{2,24})/i,
20556
+ // PowerShell download-and-execute: iex(irm ...), Invoke-Expression(... DownloadString/Invoke-WebRequest)
20557
+ /\b(?:iex|invoke-expression)\b[^\n]{0,100}?(?:\birm\b|\biwr\b|invoke-webrequest|invoke-restmethod|downloadstring|net\.webclient)/i,
20558
+ // PowerShell encoded command (base64 payload handed to the interpreter)
20559
+ /\bpowershell\b[^\n]{0,60}?\s-e(?:nc|ncodedcommand)?\b\s*[A-Za-z0-9+/=]{16,}/i,
20560
+ // decode-then-exec: ... base64 -d ... | <interpreter> (SMI-5359 retro NIT: da sink, matches the curl pattern)
20561
+ /\bbase64\s+(?:-d|--decode|-D)\b[^\n|]{0,60}?\|\s*(?:(?:ba|z|da)?sh|python[23]?|node|ruby|perl|php|fish|bun|deno)\b/i,
20562
+ // SMI-5424 FN-1: chained / redirect download-then-execute (curl URL -o /tmp/x && bash /tmp/x)
20563
+ /(?:curl|wget)\b[^\n]{0,150}?(?:https?:\/\/|\d{1,3}(?:\.\d{1,3}){3}|[\w-]{2,63}\.[a-z]{2,24})[^\n]{0,150}?(?:&&|;)\s*(?:sudo\s+(?:-[A-Za-z]+\s+)?)?(?:(?:ba|z|da)?sh|python[23]?|node|ruby|perl|php|fish|bun|deno)\b/i,
20564
+ // SMI-5424 FN-2: npx executing a REMOTE source (URL or github:), never a local package (npx tsc is clean)
20565
+ /\bnpx\s+(?:--yes\s+|-y\s+)?(?:https?:\/\/\S+|github:\S+)/i,
20566
+ // SMI-5424 FN-4: node/python/deno/bun inline-eval (-e/-c) with a dangerous payload
20567
+ /\b(?:node|python[23]?|deno|bun)\s+(?:-e|-c|--eval|--exec)\s+['"][^'"]{0,200}?(?:require\(|child_process|fetch\(|\bexec\b|eval\(|base64|urllib|os\.system|subprocess)/i
20568
+ ];
20569
+ var IMPERATIVE_FETCH_EXEC_PROSE = [
20570
+ // P1: <fetch verb> … <exec noun> … <remote target> … <execution imperative>
20571
+ // "Download the installer from thisurl.com and run it"
20572
+ /\b(?:download|fetch|grab|get)\b[\s\S]{0,40}?\b(?:file|binary|executable|script|installer|setup)\b[\s\S]{0,60}?(?:https?:\/\/[^\s"'<>)\]]{1,200}|\b(?:from|at|on|via)\s+(?:the\s+)?[\w-]{2,63}(?:\.[\w-]{2,63}){0,3}\.(?!(?:sh|bash|zsh|exe|py|js|mjs|cjs|ts|md|txt|zip|tar|gz|tgz|json|ya?ml|toml|bin|dmg|pkg|msi|deb|rpm|jar|php|rb|pl|ps1|bat|cmd|app)\b)[a-z]{2,24}\b)[\s\S]{0,120}?\b(?:run|execute|open|install)\s+(?:it|this|that|them|(?:the|this|that|your)\s+(?:file|binary|executable|script|installer|setup))\b/i,
20573
+ // P2: <execution imperative + exec noun> … <fetch verb> … <remote target>
20574
+ // "Run the installer you downloaded from thisurl.com"
20575
+ /\b(?:run|execute|open|install)\s+(?:the|this|that|your)\s+(?:file|binary|executable|script|installer|setup)\b[\s\S]{0,80}?\b(?:download|fetch|grab|get)(?:ed|s|ing)?\b[\s\S]{0,60}?(?:https?:\/\/[^\s"'<>)\]]{1,200}|\b(?:from|at|on|via)\s+(?:the\s+)?[\w-]{2,63}(?:\.[\w-]{2,63}){0,3}\.(?!(?:sh|bash|zsh|exe|py|js|mjs|cjs|ts|md|txt|zip|tar|gz|tgz|json|ya?ml|toml|bin|dmg|pkg|msi|deb|rpm|jar|php|rb|pl|ps1|bat|cmd|app)\b)[a-z]{2,24}\b)/i
20576
+ ];
20577
+
20479
20578
  // ../core/dist/src/security/scanner/patterns.js
20480
20579
  var DEFAULT_ALLOWED_DOMAINS = [
20481
20580
  "github.com",
@@ -20491,6 +20590,24 @@ var DEFAULT_ALLOWED_DOMAINS = [
20491
20590
  "nodejs.org",
20492
20591
  "typescriptlang.org"
20493
20592
  ];
20593
+ var ANON_PASTE_HOSTS = [
20594
+ "glot.io",
20595
+ "pastebin.com",
20596
+ "paste.ee",
20597
+ "hastebin.com",
20598
+ "ix.io",
20599
+ "0x0.st",
20600
+ "dpaste.org",
20601
+ "dpaste.com",
20602
+ "ghostbin.com",
20603
+ "paste.rs",
20604
+ "controlc.com",
20605
+ "rentry.co",
20606
+ "paste.gg",
20607
+ "justpaste.it"
20608
+ ];
20609
+ var TRANSIENT_TRANSFER_HOSTS = ["transfer.sh", "file.io", "tmpfiles.org", "temp.sh"];
20610
+ var URL_SHORTENER_DOMAINS = ["bit.ly", "tinyurl.com", "t.co", "is.gd"];
20494
20611
  var ENV_PATH_PATTERN = /\.env(?![A-Za-z])(?!\.(?:example|sample|template|schema|dist))/i;
20495
20612
  var API_KEY_KEYWORD = /api[_-]?key/i;
20496
20613
  var AUTH_TOKEN_KEYWORD = /auth[_-]?token/i;
@@ -20521,7 +20638,7 @@ var VALUE_GATED_KEYWORD_PATTERNS = /* @__PURE__ */ new Set([
20521
20638
  API_KEY_KEYWORD,
20522
20639
  AUTH_TOKEN_KEYWORD
20523
20640
  ]);
20524
- var SCANNER_RULESET_VERSION = "2026-07-29.1";
20641
+ var SCANNER_RULESET_VERSION = "2026-08-15.1";
20525
20642
  var SUSPICIOUS_PATTERNS = [
20526
20643
  /eval\s*\(/i,
20527
20644
  /exec\s*\(/i,
@@ -20537,26 +20654,6 @@ var SUSPICIOUS_PATTERNS = [
20537
20654
  // Curl pipe to shell
20538
20655
  /wget\s+.*\|\s*(bash|sh)/i
20539
20656
  ];
20540
- var CODE_EXECUTION_PATTERNS = [
20541
- // curl|wget <target> | [sudo] <interpreter> (fetch piped to a shell or scripting interpreter)
20542
- /(?:curl|wget)\b[^\n|]{0,150}?(?:https?:\/\/|\d{1,3}(?:\.\d{1,3}){3}|[\w-]{2,63}\.[a-z]{2,24})[^\n|]{0,150}?\|\s*(?:sudo\s+(?:-[A-Za-z]+\s+)?)?(?:(?:ba|z|da)?sh|python[23]?|node|ruby|perl|php|fish|bun|deno)\b/i,
20543
- // process substitution: bash/sh/zsh/source/. <(curl|wget <target> ...)
20544
- /(?:^|[\s;&])(?:source|\.|ba?sh|zsh|exec)\s+<\(\s*(?:curl|wget)\b[^\n)]{0,150}?(?:https?:\/\/|\d{1,3}(?:\.\d{1,3}){3}|[\w-]{2,63}\.[a-z]{2,24})/i,
20545
- // command substitution into eval or `sh -c`: eval "$(curl <target>...)", bash -c "`wget <target>...`"
20546
- /(?:\beval\b|(?:ba|z)?sh\s+-c)\s+["']?[$`]\(?\s*(?:curl|wget)\b[^\n)]{0,150}?(?:https?:\/\/|\d{1,3}(?:\.\d{1,3}){3}|[\w-]{2,63}\.[a-z]{2,24})/i,
20547
- // PowerShell download-and-execute: iex(irm ...), Invoke-Expression(... DownloadString/Invoke-WebRequest)
20548
- /\b(?:iex|invoke-expression)\b[^\n]{0,100}?(?:\birm\b|\biwr\b|invoke-webrequest|invoke-restmethod|downloadstring|net\.webclient)/i,
20549
- // PowerShell encoded command (base64 payload handed to the interpreter)
20550
- /\bpowershell\b[^\n]{0,60}?\s-e(?:nc|ncodedcommand)?\b\s*[A-Za-z0-9+/=]{16,}/i,
20551
- // decode-then-exec: ... base64 -d ... | <interpreter> (SMI-5359 retro NIT: da sink, matches the curl pattern)
20552
- /\bbase64\s+(?:-d|--decode|-D)\b[^\n|]{0,60}?\|\s*(?:(?:ba|z|da)?sh|python[23]?|node|ruby|perl|php|fish|bun|deno)\b/i,
20553
- // SMI-5424 FN-1: chained / redirect download-then-execute (curl URL -o /tmp/x && bash /tmp/x)
20554
- /(?:curl|wget)\b[^\n]{0,150}?(?:https?:\/\/|\d{1,3}(?:\.\d{1,3}){3}|[\w-]{2,63}\.[a-z]{2,24})[^\n]{0,150}?(?:&&|;)\s*(?:sudo\s+(?:-[A-Za-z]+\s+)?)?(?:(?:ba|z|da)?sh|python[23]?|node|ruby|perl|php|fish|bun|deno)\b/i,
20555
- // SMI-5424 FN-2: npx executing a REMOTE source (URL or github:), never a local package (npx tsc is clean)
20556
- /\bnpx\s+(?:--yes\s+|-y\s+)?(?:https?:\/\/\S+|github:\S+)/i,
20557
- // SMI-5424 FN-4: node/python/deno/bun inline-eval (-e/-c) with a dangerous payload
20558
- /\b(?:node|python[23]?|deno|bun)\s+(?:-e|-c|--eval|--exec)\s+['"][^'"]{0,200}?(?:require\(|child_process|fetch\(|\bexec\b|eval\(|base64|urllib|os\.system|subprocess)/i
20559
- ];
20560
20657
  var SOCIAL_ENGINEERING_PATTERNS = [
20561
20658
  /pretend\s+(to\s+be|you\s+are|that\s+you)/i,
20562
20659
  /roleplay\s+as/i,
@@ -20823,7 +20920,47 @@ var CATEGORY_WEIGHTS = {
20823
20920
  // ~1) to the total; a saturated breakdown (capped at 100) contributes 4 —
20824
20921
  // comfortably under the riskThreshold: 40 quarantine cutoff on its own. See the
20825
20922
  // stacked-risk test in SecurityScanner.scoring.test.ts.
20826
- typosquat: 1.2
20923
+ typosquat: 1.2,
20924
+ // SMI-6033 Wave 2 (Gap 5/Gap 3): top-tier weight matching code_execution/
20925
+ // obfuscated_directive — the SAME single weight is used for both the
20926
+ // critical (standalone-quarantining) and medium (advisory, co-signal-
20927
+ // eligible) forms of each type; severity alone (SEVERITY_WEIGHTS 50 vs 15)
20928
+ // does the two-tier split, exactly as scanChmodFetchCompound's own
20929
+ // privilege_escalation compound signal already does with ONE weight
20930
+ // (1.9/0.11) across its high/low severities. Paired with the 0.40
20931
+ // coefficient in calculateRiskScore: a single CRITICAL finding reaches
20932
+ // exactly the 40 quarantine threshold on its own (50 * 2.0 * 1.0 = 100 ->
20933
+ // capped 100 -> * 0.40 = 40); a single MEDIUM finding contributes 12
20934
+ // (15 * 2.0 * 1.0 = 30 -> * 0.40 = 12) — well under threshold alone.
20935
+ gatekeeper_bypass: 2,
20936
+ archive_evasion: 2,
20937
+ // SMI-6033 Wave 2 (Gap 4): same top-tier weight as gatekeeper_bypass/
20938
+ // archive_evasion, for the same reason — a paste-host URL that is the
20939
+ // TARGET of a fetch command reaches exactly the 40 quarantine threshold
20940
+ // standalone (50 * 2.0 * 1.0 = 100 -> capped 100 -> * 0.40 = 40), while a
20941
+ // paste-host URL that is merely linked never reaches this detector at all
20942
+ // (see SecurityScanner.paste-host.ts) and stays covered by the existing
20943
+ // url:medium finding instead.
20944
+ paste_host_fetch: 2,
20945
+ // SMI-6033 Wave 2 (Gap 2): the sensitive_path/typosquat tier (1.2/0.04 —
20946
+ // NOT the 2.0/0.40 tier every other Wave 2 detector above uses). This
20947
+ // detector's wrapper finding is deliberately advisory-only: the escalation
20948
+ // Gap 2 achieves comes for free from the decoded content's OWN findings
20949
+ // (e.g. a decoded `curl|bash` natively trips code_execution at ITS OWN
20950
+ // top-tier weight), not from encoded_payload itself. A single medium,
20951
+ // high-confidence finding scores 15 * 1.2 * 1.0 = 18 -> * 0.04 = 0.72
20952
+ // (rounds to ~1); a saturated breakdown (capped at 100) contributes 4 —
20953
+ // comfortably under the riskThreshold: 40 quarantine cutoff on its own.
20954
+ encoded_payload: 1.2,
20955
+ // SMI-6033 Wave 4 (Gap 6): same advisory tier as typosquat/encoded_payload/
20956
+ // sensitive_path (1.2/0.04) — NOT the 2.0/0.40 top tier gatekeeper_bypass/
20957
+ // archive_evasion/paste_host_fetch use, since this finding type must never
20958
+ // be standalone-critical (plan §9: "N/A — never standalone... Approximate
20959
+ // NL heuristic by construction; co-signal required"). A single medium,
20960
+ // high-confidence finding scores 15 * 1.2 * 1.0 = 18 -> * 0.04 = 0.72
20961
+ // (rounds to ~1); a saturated breakdown (capped at 100) contributes 4 —
20962
+ // comfortably under the riskThreshold: 40 quarantine cutoff on its own.
20963
+ decoy_misdirection: 1.2
20827
20964
  };
20828
20965
 
20829
20966
  // ../core/dist/src/security/scanner/regex-utils.js
@@ -21076,7 +21213,7 @@ function assertScopeCoverage() {
21076
21213
  assertScopeCoverage();
21077
21214
 
21078
21215
  // ../core/dist/src/security/scanner/SecurityScanner.helpers.js
21079
- function analyzeMarkdownContext(content) {
21216
+ function analyzeMarkdownContext(content, isMarkdown = true) {
21080
21217
  const lines = content.split("\n");
21081
21218
  const contexts = [];
21082
21219
  let inFencedCodeBlock = false;
@@ -21104,7 +21241,7 @@ function analyzeMarkdownContext(content) {
21104
21241
  inFencedCodeBlock = !inFencedCodeBlock;
21105
21242
  }
21106
21243
  const inTable = !lineInFrontmatter && trimmedLine.startsWith("|");
21107
- const isIndentedCode = !lineInFrontmatter && /^( {4,}|\t)/.test(line) && !inFencedCodeBlock && !trimmedLine.startsWith("-") && !trimmedLine.startsWith("*");
21244
+ const isIndentedCode = isMarkdown && !lineInFrontmatter && /^( {4,}|\t)/.test(line) && !inFencedCodeBlock && !trimmedLine.startsWith("-") && !trimmedLine.startsWith("*");
21108
21245
  const isInlineCode = !lineInFrontmatter && /`[^`]+`/.test(line) && !inFencedCodeBlock;
21109
21246
  contexts.push({
21110
21247
  lineNumber: i + 1,
@@ -21216,95 +21353,6 @@ function scanPatternsWithMultilineSupport(content, config2, lineContexts, maxLen
21216
21353
  }
21217
21354
  return findings;
21218
21355
  }
21219
- function calculateRiskScore(findings) {
21220
- const breakdown = {
21221
- jailbreak: 0,
21222
- socialEngineering: 0,
21223
- promptLeaking: 0,
21224
- dataExfiltration: 0,
21225
- privilegeEscalation: 0,
21226
- suspiciousCode: 0,
21227
- sensitivePaths: 0,
21228
- externalUrls: 0,
21229
- aiDefence: 0,
21230
- ssrf: 0,
21231
- pii: 0,
21232
- codeExecution: 0,
21233
- obfuscatedDirective: 0,
21234
- typosquat: 0
21235
- };
21236
- const confidenceWeights = {
21237
- high: 1,
21238
- medium: 0.7,
21239
- low: 0.3
21240
- };
21241
- for (const finding of findings) {
21242
- const severityWeight = SEVERITY_WEIGHTS[finding.severity];
21243
- const categoryWeight = CATEGORY_WEIGHTS[finding.type] ?? 1;
21244
- const confidenceWeight = confidenceWeights[finding.confidence ?? "high"];
21245
- const score = severityWeight * categoryWeight * confidenceWeight;
21246
- switch (finding.type) {
21247
- case "jailbreak":
21248
- breakdown.jailbreak += score;
21249
- break;
21250
- case "social_engineering":
21251
- breakdown.socialEngineering += score;
21252
- break;
21253
- case "prompt_leaking":
21254
- breakdown.promptLeaking += score;
21255
- break;
21256
- case "data_exfiltration":
21257
- breakdown.dataExfiltration += score;
21258
- break;
21259
- case "privilege_escalation":
21260
- breakdown.privilegeEscalation += score;
21261
- break;
21262
- case "suspicious_pattern":
21263
- breakdown.suspiciousCode += score;
21264
- break;
21265
- case "sensitive_path":
21266
- breakdown.sensitivePaths += score;
21267
- break;
21268
- case "url":
21269
- breakdown.externalUrls += score;
21270
- break;
21271
- case "ai_defence":
21272
- breakdown.aiDefence += score;
21273
- break;
21274
- case "ssrf":
21275
- breakdown.ssrf += score;
21276
- break;
21277
- case "pii":
21278
- breakdown.pii += score;
21279
- break;
21280
- case "code_execution":
21281
- breakdown.codeExecution += score;
21282
- break;
21283
- case "obfuscated_directive":
21284
- breakdown.obfuscatedDirective += score;
21285
- break;
21286
- case "typosquat":
21287
- breakdown.typosquat += score;
21288
- break;
21289
- }
21290
- }
21291
- breakdown.jailbreak = Math.min(100, breakdown.jailbreak);
21292
- breakdown.socialEngineering = Math.min(100, breakdown.socialEngineering);
21293
- breakdown.promptLeaking = Math.min(100, breakdown.promptLeaking);
21294
- breakdown.dataExfiltration = Math.min(100, breakdown.dataExfiltration);
21295
- breakdown.privilegeEscalation = Math.min(100, breakdown.privilegeEscalation);
21296
- breakdown.suspiciousCode = Math.min(100, breakdown.suspiciousCode);
21297
- breakdown.sensitivePaths = Math.min(100, breakdown.sensitivePaths);
21298
- breakdown.externalUrls = Math.min(100, breakdown.externalUrls);
21299
- breakdown.aiDefence = Math.min(100, breakdown.aiDefence);
21300
- breakdown.ssrf = Math.min(100, breakdown.ssrf);
21301
- breakdown.pii = Math.min(100, breakdown.pii);
21302
- breakdown.codeExecution = Math.min(100, breakdown.codeExecution);
21303
- breakdown.obfuscatedDirective = Math.min(100, breakdown.obfuscatedDirective);
21304
- breakdown.typosquat = Math.min(100, breakdown.typosquat);
21305
- const total = Math.min(100, Math.round(breakdown.jailbreak * 0.2 + breakdown.socialEngineering * 0.11 + breakdown.promptLeaking * 0.11 + breakdown.dataExfiltration * 0.08 + breakdown.privilegeEscalation * 0.11 + breakdown.suspiciousCode * 0.07 + breakdown.sensitivePaths * 0.04 + breakdown.externalUrls * 0.04 + breakdown.aiDefence * 0.12 + breakdown.ssrf * 0.04 + breakdown.pii * 0.08 + breakdown.codeExecution * 0.4 + breakdown.obfuscatedDirective * 0.4 + breakdown.typosquat * 0.04));
21306
- return { total, breakdown };
21307
- }
21308
21356
 
21309
21357
  // ../core/dist/src/security/scanner/confusables.js
21310
21358
  var CONFUSABLES = {
@@ -21383,25 +21431,33 @@ var OBFUSCATION_DIRECTIVE_PATTERN = /(?:ignore|disregard|forget)\s+(?:all\s+|the
21383
21431
  function scanCodeExecution(content, lineContexts) {
21384
21432
  const lines = content.split("\n");
21385
21433
  const contexts = lineContexts ?? analyzeMarkdownContext(content);
21434
+ const emit = (i, line, matched, prefix) => {
21435
+ const ctx = contexts[i];
21436
+ const inDocContext = ctx ? isDocumentationContext(ctx) : false;
21437
+ return [
21438
+ {
21439
+ type: "code_execution",
21440
+ severity: "medium",
21441
+ message: `${prefix}: "${matched.slice(0, 60)}${matched.length > 60 ? "..." : ""}"`,
21442
+ location: line.trim().slice(0, 100),
21443
+ lineNumber: i + 1,
21444
+ category: "code_execution",
21445
+ inDocumentationContext: inDocContext,
21446
+ confidence: "high"
21447
+ }
21448
+ ];
21449
+ };
21386
21450
  for (let i = 0; i < lines.length; i++) {
21387
21451
  const line = lines[i];
21388
21452
  for (const pattern of CODE_EXECUTION_PATTERNS) {
21453
+ const match = safeRegexTest(pattern, line);
21454
+ if (match)
21455
+ return emit(i, line, match[0], "Remote fetch piped to an interpreter");
21456
+ }
21457
+ for (const pattern of IMPERATIVE_FETCH_EXEC_PROSE) {
21389
21458
  const match = safeRegexTest(pattern, line);
21390
21459
  if (match) {
21391
- const ctx = contexts[i];
21392
- const inDocContext = ctx ? isDocumentationContext(ctx) : false;
21393
- return [
21394
- {
21395
- type: "code_execution",
21396
- severity: "medium",
21397
- message: `Remote fetch piped to an interpreter: "${match[0].slice(0, 60)}${match[0].length > 60 ? "..." : ""}"`,
21398
- location: line.trim().slice(0, 100),
21399
- lineNumber: i + 1,
21400
- category: "code_execution",
21401
- inDocumentationContext: inDocContext,
21402
- confidence: "high"
21403
- }
21404
- ];
21460
+ return emit(i, line, match[0], "Natural-language instruction to fetch a remote file and execute it");
21405
21461
  }
21406
21462
  }
21407
21463
  }
@@ -21446,29 +21502,71 @@ function scanObfuscatedDirective(content) {
21446
21502
  }
21447
21503
  return [];
21448
21504
  }
21449
- var CODE_EXECUTION_CO_OCCURRENCE = /* @__PURE__ */ new Set([
21450
- "data_exfiltration",
21451
- "privilege_escalation",
21452
- "sensitive_path",
21453
- "obfuscated_directive"
21454
- ]);
21505
+ var CO_SIGNAL_MIN_SEVERITY = {
21506
+ // Existing four — behavior byte-identical to today, min 'high'.
21507
+ data_exfiltration: "high",
21508
+ privilege_escalation: "high",
21509
+ sensitive_path: "high",
21510
+ obfuscated_directive: "high",
21511
+ // New ClawHavoc advisory-tier categories — eligible at 'medium'.
21512
+ decoy_misdirection: "medium",
21513
+ archive_evasion: "medium",
21514
+ paste_host_fetch: "medium",
21515
+ gatekeeper_bypass: "medium"
21516
+ // its correlated/critical form already quarantines on its own
21517
+ };
21518
+ var SEVERITY_RANK = { low: 0, medium: 1, high: 2, critical: 3 };
21455
21519
  var MAX_CODE_EXECUTION_CO_SIGNAL_LINE_DISTANCE = 40;
21456
21520
  function isWithinCoSignalWindow(codeExecLine, coSignalLine) {
21457
21521
  if (typeof codeExecLine !== "number" || typeof coSignalLine !== "number")
21458
21522
  return true;
21459
21523
  return Math.abs(codeExecLine - coSignalLine) <= MAX_CODE_EXECUTION_CO_SIGNAL_LINE_DISTANCE;
21460
21524
  }
21525
+ var CO_SIGNAL_MEDIUM_CONFIDENCE_EXCEPTION = /* @__PURE__ */ new Set([
21526
+ "paste_host_fetch"
21527
+ ]);
21461
21528
  function escalateCodeExecution(findings) {
21462
21529
  const codeExec = findings.find((f) => f.type === "code_execution");
21463
21530
  if (!codeExec)
21464
21531
  return;
21465
- const hasDangerousCoSignal = findings.some((f) => f !== codeExec && CODE_EXECUTION_CO_OCCURRENCE.has(f.type) && f.inDocumentationContext !== true && (f.severity === "high" || f.severity === "critical") && isWithinCoSignalWindow(codeExec.lineNumber, f.lineNumber));
21532
+ if (codeExec.inDocumentationContext === true)
21533
+ return;
21534
+ const eligible = findings.filter((f) => f !== codeExec && CO_SIGNAL_MIN_SEVERITY[f.type] !== void 0 && f.inDocumentationContext !== true && isWithinCoSignalWindow(codeExec.lineNumber, f.lineNumber));
21535
+ const hasDangerousCoSignal = eligible.some((f) => CO_SIGNAL_MIN_SEVERITY[f.type] === "high" && (f.severity === "high" || f.severity === "critical"));
21466
21536
  if (hasDangerousCoSignal) {
21467
21537
  codeExec.severity = "critical";
21468
21538
  codeExec.message = `Remote fetch piped to an interpreter, co-occurring with exfiltration/privilege/credential signals \u2014 likely supply-chain execution. ${codeExec.message}`;
21539
+ return;
21540
+ }
21541
+ const advisoryTypes = new Set(eligible.filter((f) => {
21542
+ const min = CO_SIGNAL_MIN_SEVERITY[f.type];
21543
+ const confidenceOk = CO_SIGNAL_MEDIUM_CONFIDENCE_EXCEPTION.has(f.type) ? f.confidence !== "low" : f.confidence === "high";
21544
+ return min === "medium" && SEVERITY_RANK[f.severity] >= SEVERITY_RANK[min] && confidenceOk;
21545
+ }).map((f) => f.type));
21546
+ if (advisoryTypes.size >= 2) {
21547
+ codeExec.severity = "critical";
21548
+ codeExec.message = `Remote fetch/execute instruction corroborated by two independent advisory-tier signals (${[
21549
+ ...advisoryTypes
21550
+ ].sort().join(", ")}) \u2014 likely supply-chain execution. ${codeExec.message}`;
21469
21551
  }
21470
21552
  }
21471
21553
 
21554
+ // ../core/dist/src/security/scanner/typosquat.js
21555
+ var BRAND_ALIASES = {
21556
+ anthropic: "anthropics",
21557
+ claude: "anthropics",
21558
+ gemini: "google-gemini",
21559
+ copilot: "microsoft",
21560
+ vercel: "vercel-labs",
21561
+ salesforce: "SalesforceCommerceCloud"
21562
+ };
21563
+ var AUTHORITY_CLAIMING_AFFIXES = /* @__PURE__ */ new Set([
21564
+ "official",
21565
+ "verified",
21566
+ "authentic",
21567
+ "genuine"
21568
+ ]);
21569
+
21472
21570
  // ../core/dist/src/security/scanner/SecurityScanner.formatters.js
21473
21571
  function toMinimalRefs(report) {
21474
21572
  return report.findings.map((finding) => {
@@ -21570,6 +21668,137 @@ function toSummary(report) {
21570
21668
  };
21571
21669
  }
21572
21670
 
21671
+ // ../core/dist/src/security/scanner/SecurityScanner.urls.js
21672
+ function extractUrls(content) {
21673
+ const urlPattern = /https?:\/\/[^\s<>"')\]]+/gi;
21674
+ const lines = content.split("\n");
21675
+ const results = [];
21676
+ lines.forEach((line, index) => {
21677
+ let match;
21678
+ while ((match = urlPattern.exec(line)) !== null) {
21679
+ results.push({ url: match[0], line: index + 1 });
21680
+ }
21681
+ });
21682
+ return results;
21683
+ }
21684
+
21685
+ // ../core/dist/src/security/scanner/SecurityScanner.risk-score.js
21686
+ function calculateRiskScore(findings) {
21687
+ const breakdown = {
21688
+ jailbreak: 0,
21689
+ socialEngineering: 0,
21690
+ promptLeaking: 0,
21691
+ dataExfiltration: 0,
21692
+ privilegeEscalation: 0,
21693
+ suspiciousCode: 0,
21694
+ sensitivePaths: 0,
21695
+ externalUrls: 0,
21696
+ aiDefence: 0,
21697
+ ssrf: 0,
21698
+ pii: 0,
21699
+ codeExecution: 0,
21700
+ obfuscatedDirective: 0,
21701
+ typosquat: 0,
21702
+ gatekeeperBypass: 0,
21703
+ archiveEvasion: 0,
21704
+ pasteHostFetch: 0,
21705
+ encodedPayload: 0,
21706
+ decoyMisdirection: 0
21707
+ };
21708
+ const confidenceWeights = {
21709
+ high: 1,
21710
+ medium: 0.7,
21711
+ low: 0.3
21712
+ };
21713
+ for (const finding of findings) {
21714
+ const severityWeight = SEVERITY_WEIGHTS[finding.severity];
21715
+ const categoryWeight = CATEGORY_WEIGHTS[finding.type] ?? 1;
21716
+ const confidenceWeight = confidenceWeights[finding.confidence ?? "high"];
21717
+ const score = severityWeight * categoryWeight * confidenceWeight;
21718
+ switch (finding.type) {
21719
+ case "jailbreak":
21720
+ breakdown.jailbreak += score;
21721
+ break;
21722
+ case "social_engineering":
21723
+ breakdown.socialEngineering += score;
21724
+ break;
21725
+ case "prompt_leaking":
21726
+ breakdown.promptLeaking += score;
21727
+ break;
21728
+ case "data_exfiltration":
21729
+ breakdown.dataExfiltration += score;
21730
+ break;
21731
+ case "privilege_escalation":
21732
+ breakdown.privilegeEscalation += score;
21733
+ break;
21734
+ case "suspicious_pattern":
21735
+ breakdown.suspiciousCode += score;
21736
+ break;
21737
+ case "sensitive_path":
21738
+ breakdown.sensitivePaths += score;
21739
+ break;
21740
+ case "url":
21741
+ breakdown.externalUrls += score;
21742
+ break;
21743
+ case "ai_defence":
21744
+ breakdown.aiDefence += score;
21745
+ break;
21746
+ case "ssrf":
21747
+ breakdown.ssrf += score;
21748
+ break;
21749
+ case "pii":
21750
+ breakdown.pii += score;
21751
+ break;
21752
+ case "code_execution":
21753
+ breakdown.codeExecution += score;
21754
+ break;
21755
+ case "obfuscated_directive":
21756
+ breakdown.obfuscatedDirective += score;
21757
+ break;
21758
+ case "typosquat":
21759
+ breakdown.typosquat += score;
21760
+ break;
21761
+ case "gatekeeper_bypass":
21762
+ breakdown.gatekeeperBypass += score;
21763
+ break;
21764
+ case "archive_evasion":
21765
+ breakdown.archiveEvasion += score;
21766
+ break;
21767
+ case "paste_host_fetch":
21768
+ breakdown.pasteHostFetch += score;
21769
+ break;
21770
+ case "encoded_payload":
21771
+ breakdown.encodedPayload += score;
21772
+ break;
21773
+ case "decoy_misdirection":
21774
+ breakdown.decoyMisdirection += score;
21775
+ break;
21776
+ }
21777
+ }
21778
+ breakdown.jailbreak = Math.min(100, breakdown.jailbreak);
21779
+ breakdown.socialEngineering = Math.min(100, breakdown.socialEngineering);
21780
+ breakdown.promptLeaking = Math.min(100, breakdown.promptLeaking);
21781
+ breakdown.dataExfiltration = Math.min(100, breakdown.dataExfiltration);
21782
+ breakdown.privilegeEscalation = Math.min(100, breakdown.privilegeEscalation);
21783
+ breakdown.suspiciousCode = Math.min(100, breakdown.suspiciousCode);
21784
+ breakdown.sensitivePaths = Math.min(100, breakdown.sensitivePaths);
21785
+ breakdown.externalUrls = Math.min(100, breakdown.externalUrls);
21786
+ breakdown.aiDefence = Math.min(100, breakdown.aiDefence);
21787
+ breakdown.ssrf = Math.min(100, breakdown.ssrf);
21788
+ breakdown.pii = Math.min(100, breakdown.pii);
21789
+ breakdown.codeExecution = Math.min(100, breakdown.codeExecution);
21790
+ breakdown.obfuscatedDirective = Math.min(100, breakdown.obfuscatedDirective);
21791
+ breakdown.typosquat = Math.min(100, breakdown.typosquat);
21792
+ breakdown.gatekeeperBypass = Math.min(100, breakdown.gatekeeperBypass);
21793
+ breakdown.archiveEvasion = Math.min(100, breakdown.archiveEvasion);
21794
+ breakdown.pasteHostFetch = Math.min(100, breakdown.pasteHostFetch);
21795
+ breakdown.encodedPayload = Math.min(100, breakdown.encodedPayload);
21796
+ breakdown.decoyMisdirection = Math.min(100, breakdown.decoyMisdirection);
21797
+ const total = Math.min(100, Math.round(breakdown.jailbreak * 0.2 + breakdown.socialEngineering * 0.11 + breakdown.promptLeaking * 0.11 + breakdown.dataExfiltration * 0.08 + breakdown.privilegeEscalation * 0.11 + breakdown.suspiciousCode * 0.07 + breakdown.sensitivePaths * 0.04 + breakdown.externalUrls * 0.04 + breakdown.aiDefence * 0.12 + breakdown.ssrf * 0.04 + breakdown.pii * 0.08 + breakdown.codeExecution * 0.4 + breakdown.obfuscatedDirective * 0.4 + breakdown.typosquat * 0.04 + breakdown.gatekeeperBypass * 0.4 + breakdown.archiveEvasion * 0.4 + breakdown.pasteHostFetch * 0.4 + breakdown.encodedPayload * 0.04 + // SMI-6033 Wave 4 (Gap 6): additive 0.04, same advisory tier as encodedPayload/typosquat.
21798
+ breakdown.decoyMisdirection * 0.04));
21799
+ return { total, breakdown };
21800
+ }
21801
+
21573
21802
  // ../core/dist/src/security/scanner/SecurityScanner.ssrf.js
21574
21803
  function scanSsrfPatterns(content, lineContexts, maxLength) {
21575
21804
  const findings = [];
@@ -21718,14 +21947,40 @@ function scanPiiPatterns(content, lineContexts) {
21718
21947
  return findings;
21719
21948
  }
21720
21949
 
21721
- // ../core/dist/src/security/scanner/SecurityScanner.compound.js
21722
- var OWNER_PERM_CHMOD = /\bchmod\s+(?:-[A-Za-z]+\s+)?(?:[0-7]{3,4}|[ugoa]*(?:[+\-=][rwxXstugo]+(?:,[ugoa]*[+\-=][rwxXstugo]*)*)+)/i;
21723
- var CHMOD_FETCH_CONTEXT = /\b(?:curl|wget)\b|\bgit\s+clone\b|\bnpx\b[^\n]{0,80}https?:\/\//i;
21724
- var CHMOD_TARGET = /\bchmod\s+(?:-[A-Za-z]+\s+)?(?:[0-7]{3,4}|[ugoa]*(?:[+\-=][rwxXstugo]+(?:,[ugoa]*[+\-=][rwxXstugo]*)*)+)\s+(\S+)/i;
21950
+ // ../core/dist/src/security/scanner/SecurityScanner.fetch-correlation.js
21951
+ var FETCH_COMMAND_PATTERN = /\b(?:curl|wget)\b|\bgit\s+clone\b|\bnpx\b[^\n]{0,80}https?:\/\//i;
21725
21952
  function escapeRegExp2(s) {
21726
21953
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
21727
21954
  }
21728
- function implicitDownloadBasename(line) {
21955
+ function normalizeCorrelationPath(raw) {
21956
+ const cleaned = raw.replace(/['"]/g, "");
21957
+ const isAbsolute5 = cleaned.startsWith("/");
21958
+ const segments = [];
21959
+ for (const segment of cleaned.split("/")) {
21960
+ if (segment === "" || segment === ".")
21961
+ continue;
21962
+ if (segment === "..") {
21963
+ const last = segments[segments.length - 1];
21964
+ if (last !== void 0 && last !== "..")
21965
+ segments.pop();
21966
+ else if (!isAbsolute5)
21967
+ segments.push("..");
21968
+ continue;
21969
+ }
21970
+ segments.push(segment);
21971
+ }
21972
+ const base = segments.pop() ?? "";
21973
+ return { dir: isAbsolute5 ? "/" + segments.join("/") : segments.join("/"), base };
21974
+ }
21975
+ function correlationTargetBasename(rawPath) {
21976
+ return normalizeCorrelationPath(rawPath).base;
21977
+ }
21978
+ function directoriesCorrelate(a, b) {
21979
+ if (a === "" || b === "")
21980
+ return true;
21981
+ return a === b;
21982
+ }
21983
+ function implicitDownloadDestination(line) {
21729
21984
  const lastSegment = (urlAfterScheme) => {
21730
21985
  const noFrag = urlAfterScheme.split(/[?#]/)[0];
21731
21986
  const slash = noFrag.indexOf("/");
@@ -21742,14 +21997,39 @@ function implicitDownloadBasename(line) {
21742
21997
  return lastSegment(clone2[1]).replace(/\.git$/i, "");
21743
21998
  const curlEq = line.match(/\bcurl\b[^\n]{0,200}?--output=['"]?(\S{1,400})/i);
21744
21999
  if (curlEq)
21745
- return curlEq[1].replace(/['"]/g, "").split("/").pop() ?? "";
22000
+ return curlEq[1].replace(/['"]/g, "");
21746
22001
  return "";
21747
22002
  }
22003
+ function isCorrelatedWithFetchDestination(targetPath, fetchLines) {
22004
+ const target = normalizeCorrelationPath(targetPath);
22005
+ if (target.base === "")
22006
+ return false;
22007
+ const re = new RegExp(`(?:-o|-O|--output|>>?)\\s*['"]?((?:[^\\s'"]*/)?${escapeRegExp2(target.base)})(?:[\\s'"?]|$)`, "g");
22008
+ for (const line of fetchLines) {
22009
+ for (const match of line.matchAll(re)) {
22010
+ if (directoriesCorrelate(target.dir, normalizeCorrelationPath(match[1]).dir))
22011
+ return true;
22012
+ }
22013
+ const implicit = implicitDownloadDestination(line);
22014
+ if (implicit === "")
22015
+ continue;
22016
+ const destination = normalizeCorrelationPath(implicit);
22017
+ if (destination.base !== target.base)
22018
+ continue;
22019
+ if (directoriesCorrelate(target.dir, destination.dir))
22020
+ return true;
22021
+ }
22022
+ return false;
22023
+ }
22024
+
22025
+ // ../core/dist/src/security/scanner/SecurityScanner.compound.js
22026
+ var OWNER_PERM_CHMOD = /\bchmod\s+(?:-[A-Za-z]+\s+)?(?:[0-7]{3,4}|[ugoa]*(?:[+\-=][rwxXstugo]+(?:,[ugoa]*[+\-=][rwxXstugo]*)*)+)/i;
22027
+ var CHMOD_TARGET = /\bchmod\s+(?:-[A-Za-z]+\s+)?(?:[0-7]{3,4}|[ugoa]*(?:[+\-=][rwxXstugo]+(?:,[ugoa]*[+\-=][rwxXstugo]*)*)+)\s+(\S+)/i;
21748
22028
  function scanChmodFetchCompound(content, alreadyFlaggedLines, lineContexts) {
21749
22029
  const findings = [];
21750
22030
  const lines = content.split("\n");
21751
22031
  const contexts = lineContexts ?? analyzeMarkdownContext(content);
21752
- const fetchLines = lines.filter((l) => safeRegexTest(CHMOD_FETCH_CONTEXT, l) !== null);
22032
+ const fetchLines = lines.filter((l) => safeRegexTest(FETCH_COMMAND_PATTERN, l) !== null);
21753
22033
  lines.forEach((line, index) => {
21754
22034
  const lineNumber = index + 1;
21755
22035
  if (alreadyFlaggedLines.has(lineNumber))
@@ -21758,14 +22038,13 @@ function scanChmodFetchCompound(content, alreadyFlaggedLines, lineContexts) {
21758
22038
  if (!match)
21759
22039
  return;
21760
22040
  const window2 = [lines[index - 1] ?? "", line, lines[index + 1] ?? ""].join("\n");
21761
- const adjacentFetch = safeRegexTest(CHMOD_FETCH_CONTEXT, window2) !== null;
22041
+ const adjacentFetch = safeRegexTest(FETCH_COMMAND_PATTERN, window2) !== null;
21762
22042
  let correlated = false;
21763
22043
  const tm = safeRegexTest(CHMOD_TARGET, line);
21764
22044
  if (tm) {
21765
- const base = tm[1].replace(/['"]/g, "").split("/").pop() ?? "";
21766
- if (base.length >= 3) {
21767
- const re = new RegExp(`(?:-o|-O|--output|>>?)\\s*['"]?(?:[^\\s'"]*/)?${escapeRegExp2(base)}(?:[\\s'"?]|$)`);
21768
- correlated = fetchLines.some((l) => re.test(l) || implicitDownloadBasename(l) === base);
22045
+ const targetPath = tm[1].replace(/['"]/g, "");
22046
+ if (correlationTargetBasename(targetPath).length >= 3) {
22047
+ correlated = isCorrelatedWithFetchDestination(targetPath, fetchLines);
21769
22048
  }
21770
22049
  }
21771
22050
  if (!adjacentFetch && !correlated)
@@ -21788,6 +22067,44 @@ function scanChmodFetchCompound(content, alreadyFlaggedLines, lineContexts) {
21788
22067
  });
21789
22068
  return findings;
21790
22069
  }
22070
+ var XATTR_CLEAR_ALL = /\bxattr\b[^\n]{0,40}-[a-zA-Z]*c[a-zA-Z]*\b/i;
22071
+ var XATTR_DELETE_QUARANTINE = /\bxattr\b[^\n]{0,40}-[a-zA-Z]*d[a-zA-Z]*\s+['"]?com\.apple\.quarantine\b/i;
22072
+ var XATTR_CLEAR_ALL_TARGET = /\bxattr\b[^\n]{0,40}-[a-zA-Z]*c[a-zA-Z]*\b\s+['"]?(\S+)/i;
22073
+ var XATTR_DELETE_QUARANTINE_TARGET = /\bxattr\b[^\n]{0,40}-[a-zA-Z]*d[a-zA-Z]*\s+['"]?com\.apple\.quarantine\b['"]?\s+['"]?(\S+)/i;
22074
+ function extractXattrTargetPath(line) {
22075
+ const m = safeRegexTest(XATTR_CLEAR_ALL_TARGET, line) ?? safeRegexTest(XATTR_DELETE_QUARANTINE_TARGET, line);
22076
+ if (!m)
22077
+ return "";
22078
+ return m[1].replace(/['"]/g, "");
22079
+ }
22080
+ function scanGatekeeperBypass(content, lineContexts, isHighTrustAuthor = false) {
22081
+ const findings = [];
22082
+ const lines = content.split("\n");
22083
+ const contexts = lineContexts ?? analyzeMarkdownContext(content);
22084
+ const fetchLines = lines.filter((l) => safeRegexTest(FETCH_COMMAND_PATTERN, l) !== null);
22085
+ lines.forEach((line, index) => {
22086
+ const match = safeRegexTest(XATTR_CLEAR_ALL, line) ?? safeRegexTest(XATTR_DELETE_QUARANTINE, line);
22087
+ if (!match)
22088
+ return;
22089
+ const ctx = contexts[index];
22090
+ const inInlineCode = ctx?.isInlineCode && isWithinInlineCode(line, match.index ?? 0);
22091
+ const inDocContext = ctx ? isDocumentationContext(ctx) || inInlineCode : false;
22092
+ const targetPath = extractXattrTargetPath(line);
22093
+ const correlated = correlationTargetBasename(targetPath).length >= 3 && isCorrelatedWithFetchDestination(targetPath, fetchLines);
22094
+ const critical = !inDocContext && correlated && !isHighTrustAuthor;
22095
+ findings.push({
22096
+ type: "gatekeeper_bypass",
22097
+ severity: inDocContext ? "low" : critical ? "critical" : "medium",
22098
+ message: `xattr command strips the macOS Gatekeeper quarantine attribute: "${match[0].trim().slice(0, 100)}"`,
22099
+ location: line.trim().slice(0, 100),
22100
+ lineNumber: index + 1,
22101
+ category: "gatekeeper_bypass",
22102
+ inDocumentationContext: inDocContext,
22103
+ confidence: inDocContext ? "low" : "high"
22104
+ });
22105
+ });
22106
+ return findings;
22107
+ }
21791
22108
 
21792
22109
  // ../core/dist/src/security/scanner/SecurityScanner.scanners.js
21793
22110
  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;
@@ -21950,6 +22267,541 @@ function scanPrivilegeEscalation(content, lineContexts) {
21950
22267
  return findings;
21951
22268
  }
21952
22269
 
22270
+ // ../core/dist/src/security/scanner/SecurityScanner.archive.js
22271
+ var UNZIP_INVOCATION = /\bunzip\b[^\n]{0,200}/i;
22272
+ var UNRAR_INVOCATION = /\bunrar\s+x\b[^\n]{0,200}/i;
22273
+ var SEVENZIP_INVOCATION = /\b7z\s+x\b[^\n]{0,200}/i;
22274
+ var ZIP_INVOCATION = /\bzip\b[^\n]{0,200}/i;
22275
+ var UNZIP_PASSWORD_ARG = /-P\s+(\S{1,200})/;
22276
+ var UNRAR_SEVENZIP_PASSWORD_ARG = /-p(\S{1,200})/i;
22277
+ var ZIP_PASSWORD_ARG = /-P\s+(\S{1,200})/;
22278
+ var ZIP_ENCRYPT_FLAG = /(?:^|\s)-e(?:\s|$)/;
22279
+ var ARCHIVE_FILENAME = /(\S+\.(?:zip|rar|7z|tar\.gz|tgz))\b/i;
22280
+ function findArchiveCliPassword(line) {
22281
+ const unzip = safeRegexTest(UNZIP_INVOCATION, line);
22282
+ if (unzip) {
22283
+ const pw = safeRegexTest(UNZIP_PASSWORD_ARG, unzip[0]);
22284
+ if (pw)
22285
+ return { tool: "unzip", password: pw[1], index: unzip.index ?? 0 };
22286
+ }
22287
+ const unrar = safeRegexTest(UNRAR_INVOCATION, line);
22288
+ if (unrar) {
22289
+ const pw = safeRegexTest(UNRAR_SEVENZIP_PASSWORD_ARG, unrar[0]);
22290
+ if (pw)
22291
+ return { tool: "unrar", password: pw[1], index: unrar.index ?? 0 };
22292
+ }
22293
+ const sevenZip = safeRegexTest(SEVENZIP_INVOCATION, line);
22294
+ if (sevenZip) {
22295
+ const pw = safeRegexTest(UNRAR_SEVENZIP_PASSWORD_ARG, sevenZip[0]);
22296
+ if (pw)
22297
+ return { tool: "7z", password: pw[1], index: sevenZip.index ?? 0 };
22298
+ }
22299
+ const zip = safeRegexTest(ZIP_INVOCATION, line);
22300
+ if (zip) {
22301
+ const pw = safeRegexTest(ZIP_PASSWORD_ARG, zip[0]);
22302
+ if (pw && safeRegexCheck(ZIP_ENCRYPT_FLAG, zip[0])) {
22303
+ return { tool: "zip", password: pw[1], index: zip.index ?? 0 };
22304
+ }
22305
+ }
22306
+ return null;
22307
+ }
22308
+ function extractArchiveTargetPath(line) {
22309
+ const m = safeRegexTest(ARCHIVE_FILENAME, line);
22310
+ if (!m)
22311
+ return "";
22312
+ return m[1].replace(/['"]/g, "");
22313
+ }
22314
+ var SHELL_VAR_REF = /^\$\{?[A-Za-z_][A-Za-z0-9_]*\}?$/;
22315
+ function isInlineLiteralPassword(password2) {
22316
+ if (!password2)
22317
+ return false;
22318
+ const unquoted = password2.replace(/^['"]|['"]$/g, "");
22319
+ if (safeRegexCheck(SHELL_VAR_REF, unquoted))
22320
+ return false;
22321
+ return !looksLikePlaceholderSecret(unquoted);
22322
+ }
22323
+ var ARCHIVE_NOUN = /\b(?:zip|rar|7z|tar\.gz|tgz|archive)\b/i;
22324
+ var PASSWORD_NOUN = /\bpassword\b|\bpasscode\b|\bpassphrase\b/i;
22325
+ function findArchivePasswordProseLines(lines) {
22326
+ const flagged = /* @__PURE__ */ new Set();
22327
+ lines.forEach((line, index) => {
22328
+ if (!safeRegexCheck(ARCHIVE_NOUN, line))
22329
+ return;
22330
+ const start = Math.max(0, index - 2);
22331
+ const end = Math.min(lines.length - 1, index + 2);
22332
+ for (let i = start; i <= end; i++) {
22333
+ if (safeRegexCheck(PASSWORD_NOUN, lines[i])) {
22334
+ flagged.add(index + 1);
22335
+ break;
22336
+ }
22337
+ }
22338
+ });
22339
+ return Array.from(flagged);
22340
+ }
22341
+ function scanArchiveEvasion(content, lineContexts) {
22342
+ const findings = [];
22343
+ const lines = content.split("\n");
22344
+ const contexts = lineContexts ?? analyzeMarkdownContext(content);
22345
+ const fetchLines = lines.filter((l) => safeRegexTest(FETCH_COMMAND_PATTERN, l) !== null);
22346
+ const emittedLines = /* @__PURE__ */ new Set();
22347
+ lines.forEach((line, index) => {
22348
+ const lineNumber = index + 1;
22349
+ const cli = findArchiveCliPassword(line);
22350
+ if (!cli)
22351
+ return;
22352
+ const ctx = contexts[index];
22353
+ const inInlineCode = ctx?.isInlineCode && isWithinInlineCode(line, cli.index);
22354
+ const inDocContext = ctx ? isDocumentationContext(ctx) || inInlineCode : false;
22355
+ const inlineLiteral = isInlineLiteralPassword(cli.password);
22356
+ const targetPath = extractArchiveTargetPath(line);
22357
+ const correlated = correlationTargetBasename(targetPath).length >= 3 && isCorrelatedWithFetchDestination(targetPath, fetchLines);
22358
+ const critical = !inDocContext && inlineLiteral && correlated;
22359
+ findings.push({
22360
+ type: "archive_evasion",
22361
+ severity: inDocContext ? "low" : critical ? "critical" : "medium",
22362
+ message: `Password-protected archive CLI usage (${cli.tool}): "${line.trim().slice(0, 100)}"`,
22363
+ location: line.trim().slice(0, 100),
22364
+ lineNumber,
22365
+ category: "archive_evasion",
22366
+ inDocumentationContext: inDocContext,
22367
+ confidence: inDocContext ? "low" : "high"
22368
+ });
22369
+ emittedLines.add(lineNumber);
22370
+ });
22371
+ for (const lineNumber of findArchivePasswordProseLines(lines)) {
22372
+ if (emittedLines.has(lineNumber))
22373
+ continue;
22374
+ const ctx = contexts[lineNumber - 1];
22375
+ const inDocContext = ctx ? isDocumentationContext(ctx) : false;
22376
+ findings.push({
22377
+ type: "archive_evasion",
22378
+ severity: inDocContext ? "low" : "medium",
22379
+ message: `Archive and password mentioned in close proximity: "${lines[lineNumber - 1].trim().slice(0, 100)}"`,
22380
+ location: lines[lineNumber - 1].trim().slice(0, 100),
22381
+ lineNumber,
22382
+ category: "archive_evasion",
22383
+ inDocumentationContext: inDocContext,
22384
+ confidence: inDocContext ? "low" : "medium"
22385
+ });
22386
+ }
22387
+ return findings;
22388
+ }
22389
+
22390
+ // ../core/dist/src/security/scanner/SecurityScanner.paste-host.js
22391
+ var ANON_PASTE_HOST_SET = new Set([...ANON_PASTE_HOSTS, ...URL_SHORTENER_DOMAINS].map((d) => d.toLowerCase()));
22392
+ var TRANSIENT_TRANSFER_HOST_SET = new Set(TRANSIENT_TRANSFER_HOSTS.map((d) => d.toLowerCase()));
22393
+ function classifyPasteHostTier(url2) {
22394
+ let hostname7;
22395
+ try {
22396
+ hostname7 = new URL(url2).hostname.toLowerCase();
22397
+ } catch {
22398
+ return null;
22399
+ }
22400
+ const matchesSet = (set2) => {
22401
+ for (const domain2 of set2) {
22402
+ if (hostname7 === domain2 || hostname7.endsWith("." + domain2))
22403
+ return true;
22404
+ }
22405
+ return false;
22406
+ };
22407
+ if (matchesSet(ANON_PASTE_HOST_SET))
22408
+ return "anon";
22409
+ if (matchesSet(TRANSIENT_TRANSFER_HOST_SET))
22410
+ return "transient";
22411
+ return null;
22412
+ }
22413
+ var FETCH_VERB_PATTERN = /\b(?:curl|wget)\b/i;
22414
+ var PIPE_TO_INTERPRETER_TAIL = /^\|\s*(?:sudo\s+(?:-[A-Za-z]+\s+)?)?(?:(?:ba|z|da)?sh|python[23]?|node|ruby|perl|php|fish|bun|deno)\b/i;
22415
+ function isDirectPipeToInterpreter(url2, lineContent) {
22416
+ const pipeIndex = lineContent.indexOf("|");
22417
+ if (pipeIndex < 0)
22418
+ return false;
22419
+ const beforePipe = lineContent.slice(0, pipeIndex);
22420
+ if (!beforePipe.includes(url2))
22421
+ return false;
22422
+ if (!safeRegexCheck(FETCH_VERB_PATTERN, beforePipe))
22423
+ return false;
22424
+ return safeRegexCheck(PIPE_TO_INTERPRETER_TAIL, lineContent.slice(pipeIndex));
22425
+ }
22426
+ var NPX_DIRECT_EXEC_PATTERN = /\bnpx\b[^\n]{0,80}https?:\/\//i;
22427
+ var FETCH_VERB_TOKENS = /* @__PURE__ */ new Set(["curl", "wget", "npx"]);
22428
+ var FLAG_TOKEN = /^-{1,2}[A-Za-z][\w-]*$/;
22429
+ var VALUE_TAKING_FLAGS = /* @__PURE__ */ new Set([
22430
+ "-o",
22431
+ "-x",
22432
+ "-h",
22433
+ "-d",
22434
+ "-a",
22435
+ "-e",
22436
+ "-u",
22437
+ "-b",
22438
+ "--output",
22439
+ "--request",
22440
+ "--header",
22441
+ "--data",
22442
+ "--data-raw",
22443
+ "--data-binary",
22444
+ "--data-urlencode",
22445
+ "--user-agent",
22446
+ "--referer",
22447
+ "--proxy",
22448
+ "--cookie"
22449
+ ]);
22450
+ function bareToken(token) {
22451
+ return token.replace(/^[^A-Za-z0-9/.-]+/, "").replace(/[^A-Za-z0-9/._-]+$/, "").toLowerCase();
22452
+ }
22453
+ function isFetchVerbArgument(prefix) {
22454
+ const segment = prefix.split(/[;|&]/).pop() ?? "";
22455
+ const tokens = segment.trim().split(/\s+/).map(bareToken).filter((t) => t.length > 0);
22456
+ let afterVerb = -1;
22457
+ for (let i = 0; i < tokens.length; i++) {
22458
+ if (tokens[i] === "git" && tokens[i + 1] === "clone")
22459
+ afterVerb = i + 2;
22460
+ else if (FETCH_VERB_TOKENS.has(tokens[i]))
22461
+ afterVerb = i + 1;
22462
+ }
22463
+ if (afterVerb < 0)
22464
+ return false;
22465
+ for (let i = afterVerb; i < tokens.length; i++) {
22466
+ const token = tokens[i];
22467
+ if (!safeRegexCheck(FLAG_TOKEN, token))
22468
+ return false;
22469
+ if (VALUE_TAKING_FLAGS.has(token) && i + 1 < tokens.length)
22470
+ i++;
22471
+ }
22472
+ return true;
22473
+ }
22474
+ function isActualFetchTarget(lineContent, url2) {
22475
+ for (let from = 0; ; ) {
22476
+ const urlIndex = lineContent.indexOf(url2, from);
22477
+ if (urlIndex < 0)
22478
+ return false;
22479
+ if (isFetchVerbArgument(lineContent.slice(0, urlIndex)))
22480
+ return true;
22481
+ from = urlIndex + 1;
22482
+ }
22483
+ }
22484
+ var CHMOD_TARGET2 = /\bchmod\s+(?:-[A-Za-z]+\s+)?(?:[0-7]{3,4}|[ugoa]*(?:[+\-=][rwxXstugo]+(?:,[ugoa]*[+\-=][rwxXstugo]*)*)+)\s+(\S+)/i;
22485
+ var DIRECT_EXEC_TARGET = /\b(?:sudo\s+)?(?:(?:ba|z|da)?sh|python[23]?|node|ruby|perl|php|fish|bun|deno)\s+(\.{0,2}\/?[\w-]+(?:[./][\w-]+)*)/i;
22486
+ var DOT_SLASH_EXEC_TARGET = /(?:^|[\s;&|])\.\/([\w-]+(?:[./][\w-]+)*)/;
22487
+ var SOURCE_TARGET = /(?:^|[;&|]\s*)source\s+(\.{0,2}\/?[\w-]+(?:[./][\w-]+)+)/i;
22488
+ var EXEC_TARGET_PATTERNS = [
22489
+ CHMOD_TARGET2,
22490
+ DIRECT_EXEC_TARGET,
22491
+ DOT_SLASH_EXEC_TARGET,
22492
+ SOURCE_TARGET
22493
+ ];
22494
+ function collectExecTargetPaths(lines) {
22495
+ const paths = [];
22496
+ for (const line of lines) {
22497
+ for (const pattern of EXEC_TARGET_PATTERNS) {
22498
+ const m = safeRegexTest(pattern, line);
22499
+ if (!m?.[1])
22500
+ continue;
22501
+ const targetPath = m[1].replace(/['"]/g, "");
22502
+ if (correlationTargetBasename(targetPath).length >= 3)
22503
+ paths.push(targetPath);
22504
+ }
22505
+ }
22506
+ return paths;
22507
+ }
22508
+ function isExecutedElsewhereCorrelated(fetchLine, execTargetPaths) {
22509
+ return execTargetPaths.some((path27) => isCorrelatedWithFetchDestination(path27, [fetchLine]));
22510
+ }
22511
+ function scanPasteHostFetch(content, lineContexts) {
22512
+ const findings = [];
22513
+ const lines = content.split("\n");
22514
+ const contexts = lineContexts ?? analyzeMarkdownContext(content);
22515
+ const urls = extractUrls(content);
22516
+ const execTargetPaths = collectExecTargetPaths(lines);
22517
+ for (const { url: url2, line } of urls) {
22518
+ const tier = classifyPasteHostTier(url2);
22519
+ if (!tier)
22520
+ continue;
22521
+ const lineIndex = line - 1;
22522
+ const lineContent = lines[lineIndex] ?? "";
22523
+ if (!isActualFetchTarget(lineContent, url2))
22524
+ continue;
22525
+ const ctx = contexts[lineIndex];
22526
+ const inDocContext = ctx ? isDocumentationContext(ctx) : false;
22527
+ if (tier === "transient") {
22528
+ findings.push({
22529
+ type: "paste_host_fetch",
22530
+ severity: inDocContext ? "low" : "medium",
22531
+ message: `Ephemeral file-transfer host URL fetched (never standalone-critical by design): ${url2}`,
22532
+ location: lineContent.trim().slice(0, 100),
22533
+ lineNumber: line,
22534
+ category: "paste_host_fetch",
22535
+ inDocumentationContext: inDocContext,
22536
+ confidence: inDocContext ? "low" : "medium"
22537
+ });
22538
+ continue;
22539
+ }
22540
+ const executed = isDirectPipeToInterpreter(url2, lineContent) || safeRegexCheck(NPX_DIRECT_EXEC_PATTERN, lineContent) || isExecutedElsewhereCorrelated(lineContent, execTargetPaths);
22541
+ if (!executed)
22542
+ continue;
22543
+ findings.push({
22544
+ type: "paste_host_fetch",
22545
+ // Standalone-critical (execution evidence required, see above) — doc
22546
+ // context is the only downgrade, matching every other detector's
22547
+ // noise-reduction convention in this Wave.
22548
+ severity: inDocContext ? "low" : "critical",
22549
+ message: `Paste/snippet-host URL is the target of an execution instruction: ${url2}`,
22550
+ location: lineContent.trim().slice(0, 100),
22551
+ lineNumber: line,
22552
+ category: "paste_host_fetch",
22553
+ inDocumentationContext: inDocContext,
22554
+ confidence: inDocContext ? "low" : "high"
22555
+ });
22556
+ }
22557
+ return findings;
22558
+ }
22559
+
22560
+ // ../core/dist/src/security/scanner/SecurityScanner.encoding.js
22561
+ var MAX_ENCODED_CANDIDATE_BYTES = 2e5;
22562
+ var MAX_OVERSIZED_ADVISORIES = 8;
22563
+ var MAX_BASE64_CANDIDATES = 8;
22564
+ var MAX_DECODED_TOTAL_BYTES = 256e3;
22565
+ var BASE64_CANDIDATE = /[A-Za-z0-9+/]{120,}={0,2}/g;
22566
+ var DATA_URI_LOOKBACK = 60;
22567
+ var DATA_URI_PREFIX = /data:(?:image|font|audio)\//i;
22568
+ var CONTROL_CHAR = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/;
22569
+ var MIN_PRINTABLE_RATIO = 0.9;
22570
+ function tryDecodeBase64ToPlausibleText(candidate) {
22571
+ let binary;
22572
+ try {
22573
+ binary = atob(candidate);
22574
+ } catch {
22575
+ return null;
22576
+ }
22577
+ if (binary.length === 0)
22578
+ return null;
22579
+ if (btoa(binary).replace(/=+$/, "") !== candidate.replace(/=+$/, ""))
22580
+ return null;
22581
+ const bytes = new Uint8Array(binary.length);
22582
+ for (let i = 0; i < binary.length; i++)
22583
+ bytes[i] = binary.charCodeAt(i);
22584
+ let text;
22585
+ try {
22586
+ text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
22587
+ } catch {
22588
+ return null;
22589
+ }
22590
+ if (text.length === 0)
22591
+ return null;
22592
+ let controlCount = 0;
22593
+ for (const ch of text) {
22594
+ if (CONTROL_CHAR.test(ch))
22595
+ controlCount++;
22596
+ }
22597
+ const printableRatio = (text.length - controlCount) / text.length;
22598
+ if (printableRatio < MIN_PRINTABLE_RATIO)
22599
+ return null;
22600
+ return text;
22601
+ }
22602
+ function scanEncodedPayload(content, lineContexts, rescan) {
22603
+ const findings = [];
22604
+ const lines = content.split("\n");
22605
+ const contexts = lineContexts ?? analyzeMarkdownContext(content);
22606
+ let candidatesProcessed = 0;
22607
+ let decodedTotalBytes = 0;
22608
+ let aggregateBudgetExhausted = false;
22609
+ let oversizedAdvisories = 0;
22610
+ lines.forEach((line, index) => {
22611
+ const lineNumber = index + 1;
22612
+ for (const match of line.matchAll(BASE64_CANDIDATE)) {
22613
+ const candidate = match[0];
22614
+ const start = match.index ?? 0;
22615
+ const before = line.slice(Math.max(0, start - DATA_URI_LOOKBACK), start);
22616
+ if (DATA_URI_PREFIX.test(before))
22617
+ continue;
22618
+ if (candidate.length > MAX_ENCODED_CANDIDATE_BYTES) {
22619
+ if (oversizedAdvisories >= MAX_OVERSIZED_ADVISORIES)
22620
+ continue;
22621
+ oversizedAdvisories++;
22622
+ const oversizeCtx = contexts[index];
22623
+ const oversizeInInlineCode = oversizeCtx?.isInlineCode && isWithinInlineCode(line, start);
22624
+ const oversizeInDocContext = oversizeCtx ? isDocumentationContext(oversizeCtx) || oversizeInInlineCode : false;
22625
+ findings.push({
22626
+ type: "encoded_payload",
22627
+ severity: "low",
22628
+ message: `Base64-encoded payload exceeds the ${MAX_ENCODED_CANDIDATE_BYTES}-byte per-candidate cap \u2014 oversized candidate, not decoded/rescanned (${candidate.length} chars)`,
22629
+ location: line.trim().slice(0, 100),
22630
+ lineNumber,
22631
+ category: "encoded_payload",
22632
+ inDocumentationContext: oversizeInDocContext,
22633
+ confidence: "low"
22634
+ });
22635
+ continue;
22636
+ }
22637
+ if (candidatesProcessed >= MAX_BASE64_CANDIDATES || aggregateBudgetExhausted)
22638
+ continue;
22639
+ candidatesProcessed++;
22640
+ const decoded = tryDecodeBase64ToPlausibleText(candidate);
22641
+ if (decoded === null)
22642
+ continue;
22643
+ const decodedByteLength = new TextEncoder().encode(decoded).length;
22644
+ if (decodedTotalBytes + decodedByteLength > MAX_DECODED_TOTAL_BYTES) {
22645
+ aggregateBudgetExhausted = true;
22646
+ continue;
22647
+ }
22648
+ decodedTotalBytes += decodedByteLength;
22649
+ const ctx = contexts[index];
22650
+ const inInlineCode = ctx?.isInlineCode && isWithinInlineCode(line, start);
22651
+ const inDocContext = ctx ? isDocumentationContext(ctx) || inInlineCode : false;
22652
+ findings.push({
22653
+ type: "encoded_payload",
22654
+ // Advisory-tier only (weights.ts: 1.2 / 0.04, the sensitive_path/
22655
+ // typosquat tier) — see module header for why this is deliberately
22656
+ // NOT the 2.0/0.40 tier the other three Wave 2 detectors use.
22657
+ severity: inDocContext ? "low" : "medium",
22658
+ message: `Base64-encoded payload decoded and rescanned (${candidate.length} chars)`,
22659
+ location: line.trim().slice(0, 100),
22660
+ lineNumber,
22661
+ category: "encoded_payload",
22662
+ inDocumentationContext: inDocContext,
22663
+ confidence: inDocContext ? "low" : "high"
22664
+ });
22665
+ for (const inner of rescan(decoded)) {
22666
+ findings.push({ ...inner, decodedFrom: lineNumber });
22667
+ }
22668
+ }
22669
+ });
22670
+ return findings;
22671
+ }
22672
+
22673
+ // ../core/dist/src/security/scanner/SecurityScanner.decoy.js
22674
+ var DECOY_WINDOW_LINES = 5;
22675
+ var DECOY_AUTHORITY_AFFIX_PROXIMITY_LINES = 0;
22676
+ var FETCH_VERBS = /* @__PURE__ */ new Set(["curl", "wget", "npx"]);
22677
+ var FLAG_TOKEN2 = /^-{1,2}[A-Za-z][\w-]*$/;
22678
+ var VALUE_TAKING_FLAGS2 = /* @__PURE__ */ new Set([
22679
+ "-o",
22680
+ "-x",
22681
+ "-h",
22682
+ "-d",
22683
+ "-a",
22684
+ "-e",
22685
+ "-u",
22686
+ "-b",
22687
+ "--output",
22688
+ "--request",
22689
+ "--header",
22690
+ "--data",
22691
+ "--data-raw",
22692
+ "--data-binary",
22693
+ "--data-urlencode",
22694
+ "--user-agent",
22695
+ "--referer",
22696
+ "--proxy",
22697
+ "--cookie"
22698
+ ]);
22699
+ function isActualFetchTarget2(lineContent, url2) {
22700
+ const urlIndex = lineContent.indexOf(url2);
22701
+ if (urlIndex < 0)
22702
+ return false;
22703
+ const prefix = lineContent.slice(0, urlIndex);
22704
+ if (/[;|&]/.test(prefix))
22705
+ return false;
22706
+ const tokens = prefix.trim().split(/\s+/).filter((t) => t.length > 0);
22707
+ if (tokens.length === 0)
22708
+ return false;
22709
+ let i;
22710
+ if (tokens[0]?.toLowerCase() === "git" && tokens[1]?.toLowerCase() === "clone") {
22711
+ i = 2;
22712
+ } else if (FETCH_VERBS.has(tokens[0]?.toLowerCase() ?? "")) {
22713
+ i = 1;
22714
+ } else {
22715
+ return false;
22716
+ }
22717
+ for (; i < tokens.length; i++) {
22718
+ const token = tokens[i];
22719
+ if (!FLAG_TOKEN2.test(token))
22720
+ return false;
22721
+ if (VALUE_TAKING_FLAGS2.has(token.toLowerCase()) && i + 1 < tokens.length) {
22722
+ i++;
22723
+ }
22724
+ }
22725
+ return true;
22726
+ }
22727
+ var BRAND_CANONICAL_DOMAINS = {
22728
+ anthropic: ["anthropic.com", "claude.ai"],
22729
+ claude: ["anthropic.com", "claude.ai"],
22730
+ gemini: ["google.com", "ai.google.dev", "deepmind.google"],
22731
+ copilot: ["github.com", "microsoft.com"],
22732
+ vercel: ["vercel.com"],
22733
+ salesforce: ["salesforce.com"]
22734
+ };
22735
+ function tokenize(raw) {
22736
+ return raw.toLowerCase().split(/[^a-z0-9]+/i).filter((t) => t.length > 0);
22737
+ }
22738
+ function matchesDomainSet(hostname7, domains) {
22739
+ return domains.some((d) => hostname7 === d || hostname7.endsWith("." + d));
22740
+ }
22741
+ function findVendorClaimInWindow(lines, lineIndex) {
22742
+ const start = Math.max(0, lineIndex - DECOY_WINDOW_LINES);
22743
+ const end = Math.min(lines.length - 1, lineIndex + DECOY_WINDOW_LINES);
22744
+ let brandToken = null;
22745
+ let brandLineIndex = -1;
22746
+ for (let i = start; i <= end && !brandToken; i++) {
22747
+ for (const token of tokenize(lines[i] ?? "")) {
22748
+ if (Object.prototype.hasOwnProperty.call(BRAND_ALIASES, token)) {
22749
+ brandToken = token;
22750
+ brandLineIndex = i;
22751
+ break;
22752
+ }
22753
+ }
22754
+ }
22755
+ if (!brandToken)
22756
+ return null;
22757
+ const affixStart = Math.max(0, brandLineIndex - DECOY_AUTHORITY_AFFIX_PROXIMITY_LINES);
22758
+ const affixEnd = Math.min(lines.length - 1, brandLineIndex + DECOY_AUTHORITY_AFFIX_PROXIMITY_LINES);
22759
+ const hasAuthorityAffix = tokenize(lines.slice(affixStart, affixEnd + 1).join(" ")).some((t) => AUTHORITY_CLAIMING_AFFIXES.has(t));
22760
+ return { brandToken, hasAuthorityAffix };
22761
+ }
22762
+ function scanDecoyMisdirection(content, lineContexts) {
22763
+ const findings = [];
22764
+ const lines = content.split("\n");
22765
+ const contexts = lineContexts ?? analyzeMarkdownContext(content);
22766
+ const urls = extractUrls(content);
22767
+ for (const { url: url2, line } of urls) {
22768
+ const lineIndex = line - 1;
22769
+ const lineContent = lines[lineIndex] ?? "";
22770
+ if (!isActualFetchTarget2(lineContent, url2))
22771
+ continue;
22772
+ let hostname7;
22773
+ try {
22774
+ hostname7 = new URL(url2).hostname.toLowerCase();
22775
+ } catch {
22776
+ continue;
22777
+ }
22778
+ const claim = findVendorClaimInWindow(lines, lineIndex);
22779
+ if (!claim)
22780
+ continue;
22781
+ const canonicalDomains = BRAND_CANONICAL_DOMAINS[claim.brandToken] ?? [];
22782
+ if (matchesDomainSet(hostname7, canonicalDomains))
22783
+ continue;
22784
+ if (matchesDomainSet(hostname7, DEFAULT_ALLOWED_DOMAINS))
22785
+ continue;
22786
+ const ctx = contexts[lineIndex];
22787
+ const inDocContext = ctx ? isDocumentationContext(ctx) : false;
22788
+ findings.push({
22789
+ type: "decoy_misdirection",
22790
+ // Never high/critical — an approximate NL heuristic by construction
22791
+ // (plan §9 reconciliation table); the escalation-into-code_execution
22792
+ // co-signal mechanism lives in a separate dispatch.
22793
+ severity: inDocContext ? "low" : "medium",
22794
+ message: `Fetch target domain ("${hostname7}") does not match the "${claim.brandToken}" vendor claimed nearby in the skill's own prose: ${url2}`,
22795
+ location: lineContent.trim().slice(0, 100),
22796
+ lineNumber: line,
22797
+ category: "decoy_misdirection",
22798
+ inDocumentationContext: inDocContext,
22799
+ confidence: inDocContext ? "low" : claim.hasAuthorityAffix ? "high" : "medium"
22800
+ });
22801
+ }
22802
+ return findings;
22803
+ }
22804
+
21953
22805
  // ../core/dist/src/security/scanner/SecurityScanner.js
21954
22806
  var SecurityScanner = class {
21955
22807
  allowedDomains;
@@ -21962,18 +22814,6 @@ var SecurityScanner = class {
21962
22814
  this.maxContentLength = options.maxContentLength ?? 1e6;
21963
22815
  this.riskThreshold = options.riskThreshold ?? 40;
21964
22816
  }
21965
- extractUrls(content) {
21966
- const urlPattern = /https?:\/\/[^\s<>"')\]]+/gi;
21967
- const lines = content.split("\n");
21968
- const results = [];
21969
- lines.forEach((line, index) => {
21970
- let match;
21971
- while ((match = urlPattern.exec(line)) !== null) {
21972
- results.push({ url: match[0], line: index + 1 });
21973
- }
21974
- });
21975
- return results;
21976
- }
21977
22817
  isAllowedDomain(url2) {
21978
22818
  try {
21979
22819
  const parsed = new URL(url2);
@@ -21985,7 +22825,7 @@ var SecurityScanner = class {
21985
22825
  }
21986
22826
  scanUrls(content) {
21987
22827
  const findings = [];
21988
- const urls = this.extractUrls(content);
22828
+ const urls = extractUrls(content);
21989
22829
  for (const { url: url2, line } of urls) {
21990
22830
  if (!this.isAllowedDomain(url2)) {
21991
22831
  findings.push({
@@ -22066,25 +22906,25 @@ var SecurityScanner = class {
22066
22906
  }
22067
22907
  /** @deprecated Use standalone calculateRiskScore function for new code */
22068
22908
  calculateRiskScore = calculateRiskScore;
22069
- scan(skillId, content) {
22070
- const startTime = performance.now();
22909
+ /**
22910
+ * Run every content-scanning detector against `content` and return the
22911
+ * combined findings. Factored out of `scan()` (SMI-6033 Wave 2, Gap 2) so
22912
+ * the encoded-payload detector's recursive rescan of DECODED content can
22913
+ * reuse the exact same detector suite instead of a parallel, narrower
22914
+ * reimplementation.
22915
+ *
22916
+ * `skipEncodedPayload` is the STRUCTURAL depth-1 recursion guarantee: the
22917
+ * recursive callback passed to `scanEncodedPayload` below always calls this
22918
+ * method with `skipEncodedPayload: true`, so a base64 blob discovered
22919
+ * INSIDE already-decoded content can never itself be decoded — the inner
22920
+ * call cannot reach `scanEncodedPayload` again no matter what the decoded
22921
+ * text contains. This disables ONLY the encoded-payload detector on the
22922
+ * inner call, not the rest of the suite — a decoded `curl|bash` still
22923
+ * trips `code_execution`, decoded secrets still trip `sensitive_path`, etc.
22924
+ */
22925
+ runDetectors(content, lineContexts, skipEncodedPayload, isHighTrustAuthor = false, isMarkdown = true) {
22071
22926
  const findings = [];
22072
- const lineContexts = analyzeMarkdownContext(content);
22073
- if (content.length > this.maxContentLength) {
22074
- findings.push({
22075
- type: "suspicious_pattern",
22076
- severity: "low",
22077
- message: `Content exceeds maximum length (${this.maxContentLength} code units)`
22078
- });
22079
- }
22080
22927
  const effectiveMultilineLimit = Math.min(MAX_CONTENT_LENGTH_FOR_REGEX, this.maxContentLength);
22081
- if (content.length > effectiveMultilineLimit) {
22082
- findings.push({
22083
- type: "suspicious_pattern",
22084
- severity: "low",
22085
- message: `Multiline regex scan truncated at ${effectiveMultilineLimit} code units (content is ${content.length} code units; configured maxContentLength is ${this.maxContentLength} code units)`
22086
- });
22087
- }
22088
22928
  findings.push(...this.scanUrls(content));
22089
22929
  findings.push(...scanSensitivePaths(content, lineContexts));
22090
22930
  findings.push(...this.scanJailbreakPatterns(content, lineContexts, effectiveMultilineLimit));
@@ -22095,6 +22935,10 @@ var SecurityScanner = class {
22095
22935
  findings.push(...scanPrivilegeEscalation(content, lineContexts));
22096
22936
  const privEscLines = new Set(findings.filter((f) => f.type === "privilege_escalation" && f.lineNumber).map((f) => f.lineNumber));
22097
22937
  findings.push(...scanChmodFetchCompound(content, privEscLines, lineContexts));
22938
+ findings.push(...scanGatekeeperBypass(content, lineContexts, isHighTrustAuthor));
22939
+ findings.push(...scanArchiveEvasion(content, lineContexts));
22940
+ findings.push(...scanPasteHostFetch(content, lineContexts));
22941
+ findings.push(...scanDecoyMisdirection(content, lineContexts));
22098
22942
  findings.push(...this.scanAIDefenceVulnerabilities(content, lineContexts, effectiveMultilineLimit));
22099
22943
  findings.push(...scanSsrfPatterns(content, lineContexts, effectiveMultilineLimit));
22100
22944
  findings.push(...scanPiiPatterns(content, lineContexts));
@@ -22102,6 +22946,50 @@ var SecurityScanner = class {
22102
22946
  findings.push(...scanObfuscatedDirective(content));
22103
22947
  escalateCodeExecution(findings);
22104
22948
  escalateCorroboratedMentions(findings);
22949
+ if (!skipEncodedPayload) {
22950
+ findings.push(...scanEncodedPayload(content, lineContexts, (decodedContent) => this.runDetectors(decodedContent, analyzeMarkdownContext(decodedContent, isMarkdown), true, isHighTrustAuthor, isMarkdown)));
22951
+ }
22952
+ return findings;
22953
+ }
22954
+ /**
22955
+ * SMI-6033 Wave 3 (Gap 5): `isHighTrustAuthor` (default `false`) is the
22956
+ * Gatekeeper-bypass trust-tier carve-out — see `scanGatekeeperBypass`'s own
22957
+ * header (`SecurityScanner.compound.ts`) for the full policy. No in-repo
22958
+ * caller of this method currently has a verified author signal to pass
22959
+ * here (the indexer scans via the edge twin, not this core class); the
22960
+ * parameter exists so a future verified-author caller can opt in, and so
22961
+ * every existing call site (skill_validate, skill_rescan,
22962
+ * bundled-sibling-scan, skill-installation.*) defaults closed by
22963
+ * construction, not by convention.
22964
+ *
22965
+ * SMI-6033 Wave 2 (Gap 8) fix (2026-08-17): `isMarkdown` defaults `true`,
22966
+ * preserving byte-identical behavior for every existing caller. Pass
22967
+ * `false` when `content` is a real source file, not markdown — see
22968
+ * `analyzeMarkdownContext`'s own header (SecurityScanner.helpers.ts) for
22969
+ * why the markdown-only indented-code-block heuristic must never apply to
22970
+ * non-markdown content. `bundled-sibling-scan.ts`'s
22971
+ * `collectExecutableCodeFiles` candidates are the first real caller.
22972
+ */
22973
+ scan(skillId, content, isHighTrustAuthor = false, isMarkdown = true) {
22974
+ const startTime = performance.now();
22975
+ const findings = [];
22976
+ const lineContexts = analyzeMarkdownContext(content, isMarkdown);
22977
+ if (content.length > this.maxContentLength) {
22978
+ findings.push({
22979
+ type: "suspicious_pattern",
22980
+ severity: "low",
22981
+ message: `Content exceeds maximum length (${this.maxContentLength} code units)`
22982
+ });
22983
+ }
22984
+ const effectiveMultilineLimit = Math.min(MAX_CONTENT_LENGTH_FOR_REGEX, this.maxContentLength);
22985
+ if (content.length > effectiveMultilineLimit) {
22986
+ findings.push({
22987
+ type: "suspicious_pattern",
22988
+ severity: "low",
22989
+ message: `Multiline regex scan truncated at ${effectiveMultilineLimit} code units (content is ${content.length} code units; configured maxContentLength is ${this.maxContentLength} code units)`
22990
+ });
22991
+ }
22992
+ findings.push(...this.runDetectors(content, lineContexts, false, isHighTrustAuthor, isMarkdown));
22105
22993
  const endTime = performance.now();
22106
22994
  const { total: riskScore, breakdown: riskBreakdown } = calculateRiskScore(findings);
22107
22995
  const hasCritical = findings.some((f) => f.severity === "critical");
@@ -24429,10 +25317,10 @@ function isBetterSqlite3Available() {
24429
25317
 
24430
25318
  // ../core/dist/src/db/drivers/sqljsDriver.js
24431
25319
  import { createRequire as createRequire2 } from "node:module";
24432
- import { existsSync as existsSync13, readFileSync as readFileSync10, writeFileSync as writeFileSync10, renameSync as renameSync3, unlinkSync as unlinkSync3 } from "node:fs";
25320
+ import { existsSync as existsSync14, readFileSync as readFileSync11, writeFileSync as writeFileSync11, renameSync as renameSync3, unlinkSync as unlinkSync3 } from "node:fs";
24433
25321
 
24434
25322
  // ../core/dist/src/db/drivers/corruption.js
24435
- import { existsSync as existsSync12, renameSync as renameSync2 } from "node:fs";
25323
+ import { existsSync as existsSync13, renameSync as renameSync2 } from "node:fs";
24436
25324
  var CORRUPTION_MARKERS = [
24437
25325
  "sqlite_corrupt",
24438
25326
  "malformed",
@@ -24448,7 +25336,7 @@ function backupCorruptDbFile(path27) {
24448
25336
  if (path27 === ":memory:") {
24449
25337
  throw new Error("[Skillsmith] backupCorruptDbFile: cannot back up an in-memory database");
24450
25338
  }
24451
- if (!existsSync12(path27)) {
25339
+ if (!existsSync13(path27)) {
24452
25340
  throw new Error(`[Skillsmith] backupCorruptDbFile: file does not exist: ${path27}`);
24453
25341
  }
24454
25342
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
@@ -24666,11 +25554,11 @@ var SqlJsDatabaseAdapter = class {
24666
25554
  const data = this.db.export();
24667
25555
  const tmpPath = `${this.filePath}.tmp`;
24668
25556
  try {
24669
- writeFileSync10(tmpPath, Buffer.from(data));
25557
+ writeFileSync11(tmpPath, Buffer.from(data));
24670
25558
  renameSync3(tmpPath, this.filePath);
24671
25559
  } catch (error46) {
24672
25560
  try {
24673
- if (existsSync13(tmpPath))
25561
+ if (existsSync14(tmpPath))
24674
25562
  unlinkSync3(tmpPath);
24675
25563
  } catch {
24676
25564
  }
@@ -24707,8 +25595,8 @@ var SqlJsDatabaseAdapter = class {
24707
25595
  async function createSqlJsDatabase(path27 = ":memory:", options) {
24708
25596
  const SQL = await loadSqlJs();
24709
25597
  let data;
24710
- if (path27 !== ":memory:" && existsSync13(path27)) {
24711
- data = readFileSync10(path27);
25598
+ if (path27 !== ":memory:" && existsSync14(path27)) {
25599
+ data = readFileSync11(path27);
24712
25600
  } else if (path27 !== ":memory:" && options?.fileMustExist) {
24713
25601
  throw new Error(`SQLITE_CANTOPEN: unable to open database file: ${path27}`);
24714
25602
  }
@@ -24728,7 +25616,7 @@ async function createSqlJsDatabase(path27 = ":memory:", options) {
24728
25616
  } catch {
24729
25617
  }
24730
25618
  }
24731
- if (!isCorruptionError(error46) || path27 === ":memory:" || !existsSync13(path27)) {
25619
+ if (!isCorruptionError(error46) || path27 === ":memory:" || !existsSync14(path27)) {
24732
25620
  throw error46;
24733
25621
  }
24734
25622
  const backupPath = backupCorruptDbFile(path27);
@@ -24892,7 +25780,7 @@ function findSimilarBruteForceFromMap(embeddings, queryEmbedding, topK) {
24892
25780
  }
24893
25781
 
24894
25782
  // ../core/dist/src/embeddings/hnsw-search.js
24895
- import { existsSync as existsSync14, mkdirSync as mkdirSync8, readFileSync as readFileSync11, renameSync as renameSync4, unlinkSync as unlinkSync4, writeFileSync as writeFileSync11 } from "fs";
25783
+ import { existsSync as existsSync15, mkdirSync as mkdirSync8, readFileSync as readFileSync12, renameSync as renameSync4, unlinkSync as unlinkSync4, writeFileSync as writeFileSync12 } from "fs";
24896
25784
  import { dirname as dirname13, join as join22 } from "path";
24897
25785
  var cachedCtor = null;
24898
25786
  async function loadHnswCtor() {
@@ -24928,10 +25816,10 @@ function cachePaths(modelName) {
24928
25816
  };
24929
25817
  }
24930
25818
  function readMeta(metaPath) {
24931
- if (!existsSync14(metaPath))
25819
+ if (!existsSync15(metaPath))
24932
25820
  return null;
24933
25821
  try {
24934
- const parsed = JSON.parse(readFileSync11(metaPath, "utf-8"));
25822
+ const parsed = JSON.parse(readFileSync12(metaPath, "utf-8"));
24935
25823
  if (parsed.version !== 1)
24936
25824
  return null;
24937
25825
  return parsed;
@@ -24940,10 +25828,10 @@ function readMeta(metaPath) {
24940
25828
  }
24941
25829
  }
24942
25830
  function readLabels(labelsPath) {
24943
- if (!existsSync14(labelsPath))
25831
+ if (!existsSync15(labelsPath))
24944
25832
  return null;
24945
25833
  try {
24946
- const parsed = JSON.parse(readFileSync11(labelsPath, "utf-8"));
25834
+ const parsed = JSON.parse(readFileSync12(labelsPath, "utf-8"));
24947
25835
  if (!Array.isArray(parsed))
24948
25836
  return null;
24949
25837
  return parsed;
@@ -24953,7 +25841,7 @@ function readLabels(labelsPath) {
24953
25841
  }
24954
25842
  function writeAtomic(tmp, final, contents) {
24955
25843
  mkdirSync8(dirname13(tmp), { recursive: true });
24956
- writeFileSync11(tmp, contents, typeof contents === "string" ? { encoding: "utf-8" } : void 0);
25844
+ writeFileSync12(tmp, contents, typeof contents === "string" ? { encoding: "utf-8" } : void 0);
24957
25845
  renameSync4(tmp, final);
24958
25846
  }
24959
25847
  async function loadOrBuildHnsw(args) {
@@ -24972,7 +25860,7 @@ async function loadOrBuildHnsw(args) {
24972
25860
  const efConstruction = args.efConstruction ?? 400;
24973
25861
  const efSearch = args.efSearch ?? 200;
24974
25862
  const capacity = Math.max(args.maxElements ?? Math.max(count * 2, 1024), 1024);
24975
- const reusable = meta3 !== null && labels !== null && meta3.modelName === args.modelName && meta3.dim === args.dim && meta3.count === count && existsSync14(paths.bin);
25863
+ const reusable = meta3 !== null && labels !== null && meta3.modelName === args.modelName && meta3.dim === args.dim && meta3.count === count && existsSync15(paths.bin);
24976
25864
  let index;
24977
25865
  let labelToId;
24978
25866
  let idToLabel;
@@ -24987,11 +25875,11 @@ async function loadOrBuildHnsw(args) {
24987
25875
  nextLabel = labels.reduce((max, [label]) => Math.max(max, label), -1) + 1;
24988
25876
  } catch (err) {
24989
25877
  try {
24990
- if (existsSync14(paths.bin))
25878
+ if (existsSync15(paths.bin))
24991
25879
  unlinkSync4(paths.bin);
24992
- if (existsSync14(paths.meta))
25880
+ if (existsSync15(paths.meta))
24993
25881
  unlinkSync4(paths.meta);
24994
- if (existsSync14(paths.labels))
25882
+ if (existsSync15(paths.labels))
24995
25883
  unlinkSync4(paths.labels);
24996
25884
  } catch {
24997
25885
  }
@@ -25051,7 +25939,7 @@ function createHandle(args) {
25051
25939
  clearTimeout(timer);
25052
25940
  timer = null;
25053
25941
  }
25054
- if (!dirty && existsSync14(args.paths.bin) && existsSync14(args.paths.meta)) {
25942
+ if (!dirty && existsSync15(args.paths.bin) && existsSync15(args.paths.meta)) {
25055
25943
  return;
25056
25944
  }
25057
25945
  args.index.writeIndexSync(args.paths.binTmp);
@@ -26497,7 +27385,7 @@ function redactSensitiveObject(obj, seen = /* @__PURE__ */ new WeakSet()) {
26497
27385
  }
26498
27386
 
26499
27387
  // ../core/dist/src/logging/rotation.js
26500
- import { createWriteStream, existsSync as existsSync15, statSync as statSync2 } from "node:fs";
27388
+ import { createWriteStream, existsSync as existsSync16, statSync as statSync2 } from "node:fs";
26501
27389
  import { mkdir as mkdir2, readdir as readdir2, stat, unlink as unlink2 } from "node:fs/promises";
26502
27390
  import { homedir as homedir10 } from "node:os";
26503
27391
  import { join as join23 } from "node:path";
@@ -26517,7 +27405,7 @@ function dailyFilePath(surface, date5) {
26517
27405
  function nextRolledFilePath(surface, date5) {
26518
27406
  const base = dailyFilePath(surface, date5);
26519
27407
  let n = 1;
26520
- while (existsSync15(`${base}.${n}`))
27408
+ while (existsSync16(`${base}.${n}`))
26521
27409
  n++;
26522
27410
  return `${base}.${n}`;
26523
27411
  }
@@ -26618,7 +27506,7 @@ function writeLogLine(surface, line) {
26618
27506
  async function pruneExpiredLogs() {
26619
27507
  const dir = getLogDir();
26620
27508
  try {
26621
- if (!existsSync15(dir))
27509
+ if (!existsSync16(dir))
26622
27510
  return;
26623
27511
  const entries = await readdir2(dir);
26624
27512
  const cutoff = Date.now() - RETENTION_DAYS * 24 * 60 * 60 * 1e3;
@@ -27015,7 +27903,7 @@ async function buildInventoryPayload(opts) {
27015
27903
  // ../core/dist/src/config/token-credentials.js
27016
27904
  import { homedir as homedir11 } from "os";
27017
27905
  import { join as join25 } from "path";
27018
- import { existsSync as existsSync16, readFileSync as readFileSync12, writeFileSync as writeFileSync12, chmodSync as chmodSync6 } from "fs";
27906
+ import { existsSync as existsSync17, readFileSync as readFileSync13, writeFileSync as writeFileSync13, chmodSync as chmodSync6 } from "fs";
27019
27907
 
27020
27908
  // ../core/dist/src/api/utils.js
27021
27909
  function calculateBackoff(attempt, baseDelay = 1e3) {
@@ -27048,10 +27936,10 @@ function getConfigPath2() {
27048
27936
  }
27049
27937
  function readConfigFile() {
27050
27938
  const p = getConfigPath2();
27051
- if (!existsSync16(p))
27939
+ if (!existsSync17(p))
27052
27940
  return {};
27053
27941
  try {
27054
- return JSON.parse(readFileSync12(p, "utf-8"));
27942
+ return JSON.parse(readFileSync13(p, "utf-8"));
27055
27943
  } catch {
27056
27944
  return {};
27057
27945
  }
@@ -27059,7 +27947,7 @@ function readConfigFile() {
27059
27947
  function writeConfigFile(data) {
27060
27948
  ensureConfigDir();
27061
27949
  const p = getConfigPath2();
27062
- writeFileSync12(p, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
27950
+ writeFileSync13(p, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
27063
27951
  try {
27064
27952
  chmodSync6(p, 384);
27065
27953
  } catch {
@@ -27118,6 +28006,46 @@ async function loadCredentials() {
27118
28006
  version: 2
27119
28007
  };
27120
28008
  }
28009
+ async function clearCredentials() {
28010
+ const sources = [];
28011
+ let keyringError;
28012
+ const keytar = await getKeytar2();
28013
+ if (keytar) {
28014
+ try {
28015
+ const deleted = await keytar.deletePassword(KEYTAR_SERVICE2, KEYTAR_ACCOUNT_REFRESH);
28016
+ if (deleted) {
28017
+ sources.push("keyring");
28018
+ }
28019
+ } catch (err) {
28020
+ keyringError = err instanceof Error ? err.message : String(err);
28021
+ }
28022
+ }
28023
+ const configPath2 = getConfigPath2();
28024
+ ensureConfigDir();
28025
+ const release = acquireConfigLock(configPath2);
28026
+ try {
28027
+ const existing = readConfigFile();
28028
+ delete existing.accessToken;
28029
+ delete existing.refreshToken;
28030
+ delete existing.expiresAt;
28031
+ delete existing.version;
28032
+ atomicWriteFile(configPath2, JSON.stringify(existing, null, 2), 384);
28033
+ } finally {
28034
+ release();
28035
+ }
28036
+ sources.push("config file");
28037
+ if (keyringError) {
28038
+ return {
28039
+ success: false,
28040
+ source: sources.join(" and "),
28041
+ error: keyringError
28042
+ };
28043
+ }
28044
+ return {
28045
+ success: true,
28046
+ source: sources.join(" and ")
28047
+ };
28048
+ }
27121
28049
  async function refreshAccessToken(refreshToken) {
27122
28050
  try {
27123
28051
  const resp = await fetch(`${SUPABASE_AUTH_URL}/token?grant_type=refresh_token`, {
@@ -27726,7 +28654,7 @@ function addInferredMcp(inferred, declaredMcpServers, result) {
27726
28654
 
27727
28655
  // ../core/dist/src/services/skill-installation.helpers.js
27728
28656
  import * as fs5 from "fs/promises";
27729
- import { existsSync as existsSync17, readFileSync as readFileSync13 } from "fs";
28657
+ import { existsSync as existsSync18, readFileSync as readFileSync14 } from "fs";
27730
28658
  import * as path4 from "path";
27731
28659
  import { createHash as createHash7 } from "crypto";
27732
28660
 
@@ -28159,10 +29087,10 @@ async function ensureDirNoFollow(dirPath) {
28159
29087
  }
28160
29088
  }
28161
29089
  async function mkdirNoFollow(baseDir, dir) {
28162
- const relative8 = path3.relative(baseDir, dir);
28163
- if (relative8 === "" || relative8.startsWith(".."))
29090
+ const relative10 = path3.relative(baseDir, dir);
29091
+ if (relative10 === "" || relative10.startsWith(".."))
28164
29092
  return;
28165
- const segments = relative8.split(path3.sep);
29093
+ const segments = relative10.split(path3.sep);
28166
29094
  let current = baseDir;
28167
29095
  for (const segment of segments) {
28168
29096
  current = path3.join(current, segment);
@@ -28298,10 +29226,10 @@ function generateTips(skillName, optimizationInfo, client = CANONICAL_CLIENT, sk
28298
29226
  }
28299
29227
  function getRegisteredMcpServers(projectRoot = process.cwd()) {
28300
29228
  const mcpJsonPath = path4.join(projectRoot, ".mcp.json");
28301
- if (!existsSync17(mcpJsonPath))
29229
+ if (!existsSync18(mcpJsonPath))
28302
29230
  return void 0;
28303
29231
  try {
28304
- const raw = readFileSync13(mcpJsonPath, "utf-8");
29232
+ const raw = readFileSync14(mcpJsonPath, "utf-8");
28305
29233
  const parsed = JSON.parse(raw);
28306
29234
  if (!parsed || typeof parsed !== "object")
28307
29235
  return void 0;
@@ -29282,7 +30210,7 @@ function buildEmptyStackGuidance() {
29282
30210
  return "No technology stack could be derived for recommendations \u2014 this usually means a non-Node project, a stack with no production dependencies, or an unsupported language, not a backend or registry problem. Provide project context (a short description of the project or its tooling) or an explicit list of installed/currently-used skills, then try again.";
29283
30211
  }
29284
30212
  function getRecommendAutoDetectedFooterText() {
29285
- return "auto-detected from your installed skills across all clients";
30213
+ return "auto-detected from your installed skills";
29286
30214
  }
29287
30215
 
29288
30216
  // ../core/dist/src/services/context-words.js
@@ -31543,6 +32471,11 @@ var ApiSearchResultSchema = external_exports.object({
31543
32471
  last_scanned_at: external_exports.string().nullable().optional(),
31544
32472
  security_findings: external_exports.array(external_exports.unknown()).nullable().optional(),
31545
32473
  quarantined: external_exports.boolean().optional(),
32474
+ // SMI-6033 Wave 2 (Gap 8): partial-scan coverage columns. Declared here so
32475
+ // Zod does not strip them before get-skill.ts / search.ts read them (same
32476
+ // reason as compatibility/license below).
32477
+ scan_coverage_incomplete: external_exports.boolean().optional(),
32478
+ scan_coverage_note: external_exports.string().nullable().optional(),
31546
32479
  // SMI-5327: SPDX license surfaced by skills-get / skills-search. Declared here
31547
32480
  // so Zod does not strip the field before get-skill.ts / search.ts read it
31548
32481
  // (same reason as compatibility above). skills-search may omit it → optional.
@@ -32098,7 +33031,12 @@ function deriveSecuritySummaryFromApiSkill(apiSkill) {
32098
33031
  passed: apiSkill.quarantined === true ? false : apiSkill.security_score == null ? null : true,
32099
33032
  riskScore: apiSkill.security_score ?? null,
32100
33033
  findingsCount: Array.isArray(apiSkill.security_findings) ? apiSkill.security_findings.length : 0,
32101
- scannedAt: apiSkill.last_scanned_at
33034
+ scannedAt: apiSkill.last_scanned_at,
33035
+ // SMI-6033 Wave 2 (Gap 8): default to complete coverage when the column
33036
+ // is absent (older cached rows / pre-Wave-2 API responses) — never
33037
+ // fabricate an incomplete-scan caveat for data that predates this field.
33038
+ scanCoverageIncomplete: apiSkill.scan_coverage_incomplete ?? false,
33039
+ scanCoverageNote: apiSkill.scan_coverage_note ?? null
32102
33040
  };
32103
33041
  }
32104
33042
 
@@ -33159,11 +34097,11 @@ function defaultParseFrontmatter(content) {
33159
34097
  const line = rawLine.trim();
33160
34098
  if (!line || line.startsWith("#"))
33161
34099
  continue;
33162
- const sep6 = line.indexOf(":");
33163
- if (sep6 === -1)
34100
+ const sep7 = line.indexOf(":");
34101
+ if (sep7 === -1)
33164
34102
  continue;
33165
- const key = line.slice(0, sep6).trim();
33166
- const value = line.slice(sep6 + 1).trim();
34103
+ const key = line.slice(0, sep7).trim();
34104
+ const value = line.slice(sep7 + 1).trim();
33167
34105
  assignKey(result, key, value);
33168
34106
  }
33169
34107
  return result;
@@ -33584,7 +34522,7 @@ var SourceRecoveryService = class {
33584
34522
  };
33585
34523
 
33586
34524
  // ../core/dist/src/provenance/backfill.js
33587
- import { existsSync as existsSync19 } from "fs";
34525
+ import { existsSync as existsSync20 } from "fs";
33588
34526
  import * as fs11 from "fs/promises";
33589
34527
  import * as os4 from "os";
33590
34528
  import * as path13 from "path";
@@ -33683,7 +34621,7 @@ function mergeEntry(existing, planned) {
33683
34621
  };
33684
34622
  }
33685
34623
  async function maybeWriteFrontmatter(dir, sourceUrl) {
33686
- if (existsSync19(path13.join(dir, ".git", "config")))
34624
+ if (existsSync20(path13.join(dir, ".git", "config")))
33687
34625
  return false;
33688
34626
  const skillMdPath = path13.join(dir, "SKILL.md");
33689
34627
  let content;
@@ -33906,7 +34844,7 @@ async function probeEmbeddingCapability(opts = {}) {
33906
34844
  }
33907
34845
 
33908
34846
  // src/version.ts
33909
- import { readFileSync as readFileSync17 } from "node:fs";
34847
+ import { readFileSync as readFileSync18 } from "node:fs";
33910
34848
  import { join as join39 } from "node:path";
33911
34849
 
33912
34850
  // src/utils/package-root.ts
@@ -33920,7 +34858,7 @@ function packageRoot() {
33920
34858
  function readVersion() {
33921
34859
  try {
33922
34860
  const pkgPath = join39(packageRoot(), "package.json");
33923
- const pkg = JSON.parse(readFileSync17(pkgPath, "utf-8"));
34861
+ const pkg = JSON.parse(readFileSync18(pkgPath, "utf-8"));
33924
34862
  return pkg.version ?? "0.0.0";
33925
34863
  } catch {
33926
34864
  return "0.0.0";
@@ -33938,7 +34876,7 @@ function getCliLogger() {
33938
34876
  }
33939
34877
 
33940
34878
  // src/utils/open-database.ts
33941
- import { existsSync as existsSync20 } from "node:fs";
34879
+ import { existsSync as existsSync21 } from "node:fs";
33942
34880
  var logger8 = getCliLogger();
33943
34881
  async function openCliDatabase(path27, options) {
33944
34882
  if (options?.readonly) {
@@ -33950,7 +34888,7 @@ async function openCliDatabase(path27, options) {
33950
34888
  initializeSchema(db);
33951
34889
  return db;
33952
34890
  } catch (err) {
33953
- if (!isCorruptionError(err) || path27 === ":memory:" || !existsSync20(path27)) {
34891
+ if (!isCorruptionError(err) || path27 === ":memory:" || !existsSync21(path27)) {
33954
34892
  throw err;
33955
34893
  }
33956
34894
  if (db) {
@@ -34772,10 +35710,19 @@ import { dirname as dirname20 } from "path";
34772
35710
  // src/utils/skills-directory.ts
34773
35711
  import { readdir as readdir6, readFile as readFile6, realpath as realpath3, stat as stat6 } from "fs/promises";
34774
35712
  import { createHash as createHash8 } from "crypto";
34775
- import { join as join40 } from "path";
35713
+ import { join as join41 } from "path";
35714
+
35715
+ // src/utils/local-skills-dir.ts
35716
+ import { join as join40, relative as relative7, sep as sep5 } from "path";
35717
+ var LOCAL_SKILLS_DIR_SEGMENTS = [".claude", "skills"];
34776
35718
  function getLocalSkillsDir() {
34777
- return join40(process.cwd(), ".claude", "skills");
35719
+ return join40(process.cwd(), ...LOCAL_SKILLS_DIR_SEGMENTS);
34778
35720
  }
35721
+ function getLocalSkillsDirDisplay() {
35722
+ return `./${relative7(process.cwd(), getLocalSkillsDir()).split(sep5).join("/")}`;
35723
+ }
35724
+
35725
+ // src/utils/skills-directory.ts
34779
35726
  async function resolvesToDirectory2(entryPath, isDirectory, isSymbolicLink) {
34780
35727
  if (isDirectory) return true;
34781
35728
  if (!isSymbolicLink) return false;
@@ -34802,14 +35749,14 @@ async function getSkillsFromDirectory(skillsDir, dbPath, installedVia = CANONICA
34802
35749
  const entries = await readdir6(skillsDir, { withFileTypes: true });
34803
35750
  for (const entry of entries) {
34804
35751
  if (entry.name.startsWith(".")) continue;
34805
- const skillPath = join40(skillsDir, entry.name);
35752
+ const skillPath = join41(skillsDir, entry.name);
34806
35753
  const isSkillDir = await resolvesToDirectory2(
34807
35754
  skillPath,
34808
35755
  entry.isDirectory(),
34809
35756
  entry.isSymbolicLink?.() ?? false
34810
35757
  );
34811
35758
  if (isSkillDir) {
34812
- const skillMdPath = join40(skillPath, "SKILL.md");
35759
+ const skillMdPath = join41(skillPath, "SKILL.md");
34813
35760
  try {
34814
35761
  const skillMdStat = await stat6(skillMdPath);
34815
35762
  const content = await readFile6(skillMdPath, "utf-8");
@@ -34878,7 +35825,7 @@ async function safeRealpath2(p) {
34878
35825
  }
34879
35826
  async function readSkillMd(skillPath) {
34880
35827
  try {
34881
- const content = await readFile6(join40(skillPath, "SKILL.md"), "utf-8");
35828
+ const content = await readFile6(join41(skillPath, "SKILL.md"), "utf-8");
34882
35829
  const contentHash = createHash8("sha256").update(content, "utf8").digest("hex");
34883
35830
  const parser2 = new SkillParser();
34884
35831
  const parsed = parser2.parse(content);
@@ -34984,15 +35931,15 @@ async function getInstalledSkillsForClient(client, dbPath) {
34984
35931
  import { confirm } from "@inquirer/prompts";
34985
35932
  import ora3 from "ora";
34986
35933
  import { readFile as readFile8 } from "fs/promises";
34987
- import { basename as basename6, join as join42 } from "path";
35934
+ import { basename as basename6, join as join43 } from "path";
34988
35935
 
34989
35936
  // src/utils/manifest.ts
34990
35937
  import { createHash as createHash9, randomUUID as randomUUID7 } from "crypto";
34991
35938
  import { readFile as readFile7, writeFile as writeFile4, mkdir as mkdir5, rename as rename3, unlink as unlink5 } from "fs/promises";
34992
- import { join as join41, dirname as dirname19 } from "path";
35939
+ import { join as join42, dirname as dirname19 } from "path";
34993
35940
  import { homedir as homedir18 } from "os";
34994
- var SKILLSMITH_DIR = join41(homedir18(), ".skillsmith");
34995
- var MANIFEST_PATH = join41(SKILLSMITH_DIR, "manifest.json");
35941
+ var SKILLSMITH_DIR = join42(homedir18(), ".skillsmith");
35942
+ var MANIFEST_PATH = join42(SKILLSMITH_DIR, "manifest.json");
34996
35943
  async function loadManifest2() {
34997
35944
  try {
34998
35945
  const content = await readFile7(MANIFEST_PATH, "utf-8");
@@ -35100,7 +36047,7 @@ var AUTO_APPLY_RECOVERY_CONFIDENCES = /* @__PURE__ */ new Set([
35100
36047
  async function recoverConfidentSourceId(skillName, installed, db) {
35101
36048
  let skillMd;
35102
36049
  try {
35103
- skillMd = await readFile8(join42(installed.path, "SKILL.md"), "utf-8");
36050
+ skillMd = await readFile8(join43(installed.path, "SKILL.md"), "utf-8");
35104
36051
  } catch {
35105
36052
  skillMd = null;
35106
36053
  }
@@ -35120,6 +36067,16 @@ async function recoverConfidentSourceId(skillName, installed, db) {
35120
36067
  }
35121
36068
  return result.recoveredSource?.url ?? result.registryId ?? null;
35122
36069
  }
36070
+ async function readClaimedAuthor(installedPath) {
36071
+ try {
36072
+ const skillMd = await readFile8(join43(installedPath, "SKILL.md"), "utf-8");
36073
+ const parsed = new SkillParser().parse(skillMd);
36074
+ const author = parsed?.["author"];
36075
+ return typeof author === "string" && author.trim().length > 0 ? author.trim() : null;
36076
+ } catch {
36077
+ return null;
36078
+ }
36079
+ }
35123
36080
  async function getSkillDiff(skillName, dbPath, client = CANONICAL_CLIENT) {
35124
36081
  const installed = (await getInstalledSkillsForClient(client, dbPath)).find(
35125
36082
  (s) => s.name.toLowerCase() === skillName.toLowerCase()
@@ -35131,9 +36088,13 @@ async function getSkillDiff(skillName, dbPath, client = CANONICAL_CLIENT) {
35131
36088
  const skillRepo = new SkillRepository(db);
35132
36089
  try {
35133
36090
  const allSkills = skillRepo.findAll(1e3, 0);
35134
- const skill = allSkills.items.find(
36091
+ const claimedAuthor = await readClaimedAuthor(installed.path);
36092
+ const nameMatches = allSkills.items.filter(
35135
36093
  (s) => s.name.toLowerCase() === skillName.toLowerCase()
35136
36094
  );
36095
+ const skill = claimedAuthor ? nameMatches.find(
36096
+ (s) => s.author && s.author.toLowerCase() === claimedAuthor.toLowerCase()
36097
+ ) : void 0;
35137
36098
  if (skill) {
35138
36099
  const changes = [];
35139
36100
  const skillWithVersion = skill;
@@ -35347,7 +36308,7 @@ function displaySkillsTable(skills, client = CANONICAL_CLIENT) {
35347
36308
  console.log(
35348
36309
  source_default.dim(
35349
36310
  `
35350
- ${skills.length} skill(s) found (global: ${getInstallPath(client)}, local: ./.claude/skills)
36311
+ ${skills.length} skill(s) found (global: ${getInstallPath(client)}, local: ${getLocalSkillsDirDisplay()})
35351
36312
  `
35352
36313
  )
35353
36314
  );
@@ -35550,7 +36511,7 @@ var InitSkillError = class _InitSkillError extends Error {
35550
36511
  import { input as input2, confirm as confirm3, select as select2 } from "@inquirer/prompts";
35551
36512
  import ora5 from "ora";
35552
36513
  import { mkdir as mkdir9, writeFile as writeFile6, readFile as readFile9, stat as stat7, readdir as readdir7 } from "fs/promises";
35553
- import { dirname as dirname21, join as join44, resolve as resolve12 } from "path";
36514
+ import { dirname as dirname21, join as join45, resolve as resolve12 } from "path";
35554
36515
  import { createHash as createHash10 } from "crypto";
35555
36516
 
35556
36517
  // src/utils/skill-name.ts
@@ -35655,7 +36616,7 @@ function validateSubagentDefinition(content) {
35655
36616
 
35656
36617
  // src/commands/author/init.helpers.ts
35657
36618
  import { mkdir as mkdir8, writeFile as writeFile5, rm as rm4 } from "fs/promises";
35658
- import { join as join43 } from "path";
36619
+ import { join as join44 } from "path";
35659
36620
 
35660
36621
  // src/templates/skill.md.template.ts
35661
36622
  var SKILL_MD_TEMPLATE = `---
@@ -36044,11 +37005,22 @@ var CLIENT_SNIPPETS = {
36044
37005
  // SMI-5894 Wave 1 Step 7: SKILLSMITH_CLIENT tells the server to install
36045
37006
  // to ~/.cursor/skills instead of the default ~/.claude/skills — without
36046
37007
  // it, Cursor users silently get Claude Code's install path.
37008
+ //
37009
+ // SMI-5893 Wave 11 (GH#2368 C-01): `command` is a resolved-path
37010
+ // placeholder, not `npx` — Cursor's bundled Node cannot resolve `npx`
37011
+ // packages (a real ENOENT on a missing Resources/app/resources/lib
37012
+ // directory), confirmed by an external tester hitting this on two
37013
+ // separate live UAT passes even after `npx` carried strong caveat text.
37014
+ // A guessed default path (e.g. a Homebrew-style tab) can *also* be
37015
+ // wrong for an nvm/asdf/custom-prefix install, which fails silently
37016
+ // differently instead of predictably — so this deliberately never
37017
+ // guesses a path at all; the primary copied snippet can never be wrong,
37018
+ // only require one resolve-and-paste step. `npx` stays available as an
37019
+ // explicit, clearly-labeled fallback in `notes` below, not removed.
36047
37020
  body: `{
36048
37021
  "mcpServers": {
36049
37022
  "{{name}}": {
36050
- "command": "npx",
36051
- "args": ["-y", "{{name}}"],
37023
+ "command": "<paste output of: which {{name}} (macOS/Linux) or where {{name}} (Windows)>",
36052
37024
  "env": {
36053
37025
  "SKILLSMITH_API_KEY": "sk_live_...",
36054
37026
  "SKILLSMITH_CLIENT": "cursor"
@@ -36056,7 +37028,14 @@ var CLIENT_SNIPPETS = {
36056
37028
  }
36057
37029
  }
36058
37030
  }`,
36059
- notes: "Cursor 2.4+ required. Reload the window after saving. Recommended: `npm install -g {{name}}` first, then point `command` at the installed `skillsmith-mcp` binary (run `which skillsmith-mcp` after installing to get the exact path \u2014 it varies by platform/npm prefix, e.g. `/opt/homebrew/bin/skillsmith-mcp` on macOS/Homebrew, a different path on Linux/Windows). The `npx -y {{name}}` form above still works as a fallback, but re-resolves the package on every launch \u2014 expect a slower cold start, and watch for EBADENGINE (Cursor bundles its own Node, sometimes older than the >=22.22 this package requires) or ENOTEMPTY errors on repeated installs."
37031
+ // Assumes the package's `bin` name matches its npm package name true for
37032
+ // every server scaffolded by mcp-server.template.ts (PACKAGE_JSON_TEMPLATE's
37033
+ // `bin` field is literally `{{name}}`), the only production caller of this
37034
+ // matrix today. The real @skillsmith/mcp-server package is the one exception
37035
+ // (bin is the shorter `skillsmith-mcp`, not the scoped package name) — its
37036
+ // docs are hand-maintained separately (root README, packages/mcp-server/README.md,
37037
+ // website's mcp-client-snippets.ts) rather than rendered through this file.
37038
+ notes: 'Cursor 2.4+ required, Node >=22.22 (Cursor\'s own bundled Node meets this). Setup: run `npm install -g {{name}}`, then run `which {{name}}` (macOS/Linux) or `where {{name}}` (Windows) and paste that path into `command` above \u2014 Cursor\'s bundled Node cannot resolve packages via `npx` (a real ENOENT on a missing Resources/app/resources/lib directory), so pointing directly at the installed binary is the only form confirmed to work inside Cursor. Prefer to try `npx` first anyway? Replace `command` with `"npx"` and add `"args": ["-y", "{{name}}"]` \u2014 simpler, but may hit the same ENOENT, plus EBADENGINE or ENOTEMPTY on repeated installs. After saving: enable the server in Cursor\'s Settings -> MCP panel and start a new chat \u2014 a correctly-configured entry still shows disconnected until toggled on there \u2014 then reload the window.'
36060
37039
  },
36061
37040
  copilot: {
36062
37041
  label: "GitHub Copilot (VS Code)",
@@ -36205,9 +37184,10 @@ function renderAllSnippetsAsMarkdown(packageName) {
36205
37184
  const sections = Object.keys(CLIENT_SNIPPETS).map((id) => {
36206
37185
  const snippet = CLIENT_SNIPPETS[id];
36207
37186
  const body = renderSnippet(id, packageName);
36208
- const notes = snippet.notes ? `
37187
+ const renderedNotes = snippet.notes?.replace(/\{\{name\}\}/g, packageName);
37188
+ const notes = renderedNotes ? `
36209
37189
 
36210
- ${snippet.notes}` : "";
37190
+ ${renderedNotes}` : "";
36211
37191
  return [
36212
37192
  `<details>`,
36213
37193
  `<summary><strong>${snippet.label}</strong> \u2014 \`${snippet.configPath}\`</summary>`,
@@ -36577,15 +37557,15 @@ function renderMcpServerTemplates(data) {
36577
37557
  async function scaffoldSkillDirectory(input7) {
36578
37558
  const { skillDir, skillName, description, author, category, createdFresh } = input7;
36579
37559
  try {
36580
- await mkdir8(join43(skillDir, "scripts"), { recursive: true });
36581
- await mkdir8(join43(skillDir, "resources"), { recursive: true });
37560
+ await mkdir8(join44(skillDir, "scripts"), { recursive: true });
37561
+ await mkdir8(join44(skillDir, "resources"), { recursive: true });
36582
37562
  const skillMdContent = SKILL_MD_TEMPLATE.replace(/\{\{name\}\}/g, skillName).replace(/\{\{description\}\}/g, description).replace(/\{\{author\}\}/g, author).replace(/\{\{category\}\}/g, category).replace(/\{\{date\}\}/g, (/* @__PURE__ */ new Date()).toISOString().split("T")[0] || "").replace(/\{\{behavioralClassification\}\}/g, "");
36583
- await writeFile5(join43(skillDir, "SKILL.md"), skillMdContent, "utf-8");
37563
+ await writeFile5(join44(skillDir, "SKILL.md"), skillMdContent, "utf-8");
36584
37564
  const readmeContent = README_MD_TEMPLATE.replace(/\{\{name\}\}/g, skillName).replace(
36585
37565
  /\{\{description\}\}/g,
36586
37566
  description
36587
37567
  );
36588
- await writeFile5(join43(skillDir, "README.md"), readmeContent, "utf-8");
37568
+ await writeFile5(join44(skillDir, "README.md"), readmeContent, "utf-8");
36589
37569
  const placeholderScript = `#!/usr/bin/env node
36590
37570
  /**
36591
37571
  * ${skillName} - Example Script
@@ -36595,7 +37575,7 @@ async function scaffoldSkillDirectory(input7) {
36595
37575
 
36596
37576
  console.log('${skillName} script executed');
36597
37577
  `;
36598
- await writeFile5(join43(skillDir, "scripts", "example.js"), placeholderScript, "utf-8");
37578
+ await writeFile5(join44(skillDir, "scripts", "example.js"), placeholderScript, "utf-8");
36599
37579
  const gitignore = `# Dependencies
36600
37580
  node_modules/
36601
37581
 
@@ -36610,7 +37590,7 @@ dist/
36610
37590
  .DS_Store
36611
37591
  Thumbs.db
36612
37592
  `;
36613
- await writeFile5(join43(skillDir, ".gitignore"), gitignore, "utf-8");
37593
+ await writeFile5(join44(skillDir, ".gitignore"), gitignore, "utf-8");
36614
37594
  return { ok: true };
36615
37595
  } catch (error46) {
36616
37596
  await rollbackPartialScaffold(skillDir, createdFresh);
@@ -36726,11 +37706,11 @@ async function validateSkill(skillPath) {
36726
37706
  try {
36727
37707
  const stats = await stat7(filePath);
36728
37708
  if (stats.isDirectory()) {
36729
- filePath = join44(filePath, "SKILL.md");
37709
+ filePath = join45(filePath, "SKILL.md");
36730
37710
  }
36731
37711
  } catch {
36732
37712
  if (!filePath.endsWith(".md")) {
36733
- filePath = join44(filePath, "SKILL.md");
37713
+ filePath = join45(filePath, "SKILL.md");
36734
37714
  }
36735
37715
  }
36736
37716
  const content = await readFile9(filePath, "utf-8");
@@ -36777,7 +37757,7 @@ async function publishSkill(skillPath, options = {}) {
36777
37757
  spinner.fail(`Directory not found: ${dirPath}`);
36778
37758
  return false;
36779
37759
  }
36780
- const skillMdPath = join44(dirPath, "SKILL.md");
37760
+ const skillMdPath = join45(dirPath, "SKILL.md");
36781
37761
  spinner.text = "Validating skill...";
36782
37762
  const content = await readFile9(skillMdPath, "utf-8");
36783
37763
  const parser2 = new SkillParser({ requireName: true });
@@ -36823,7 +37803,7 @@ async function publishSkill(skillPath, options = {}) {
36823
37803
  }).filter((p) => p !== null);
36824
37804
  let totalWarnings = 0;
36825
37805
  for (const mdFile of mdFiles) {
36826
- const filePath = join44(dirPath, mdFile);
37806
+ const filePath = join45(dirPath, mdFile);
36827
37807
  const fileContent = await readFile9(filePath, "utf-8");
36828
37808
  const result = SkillParser.checkReferences(fileContent, customPatterns);
36829
37809
  if (result.matches.length > 0) {
@@ -36852,7 +37832,7 @@ async function publishSkill(skillPath, options = {}) {
36852
37832
  spinner.start();
36853
37833
  }
36854
37834
  }
36855
- const manifestPath = join44(dirPath, ".skillsmith-publish.json");
37835
+ const manifestPath = join45(dirPath, ".skillsmith-publish.json");
36856
37836
  await writeFile6(manifestPath, JSON.stringify(publishInfo, null, 2), "utf-8");
36857
37837
  spinner.succeed("Skill prepared for publishing");
36858
37838
  console.log(source_default.bold("\nPublish Information:"));
@@ -36959,7 +37939,7 @@ function createPublishCommand() {
36959
37939
  import { Command as Command5 } from "commander";
36960
37940
  import ora6 from "ora";
36961
37941
  import { readFile as readFile10, writeFile as writeFile7, stat as stat8 } from "fs/promises";
36962
- import { basename as basename7, dirname as dirname22, join as join45, resolve as resolve13 } from "path";
37942
+ import { basename as basename7, dirname as dirname22, join as join46, resolve as resolve13 } from "path";
36963
37943
 
36964
37944
  // src/utils/tool-analyzer.ts
36965
37945
  var TOOL_PATTERNS3 = {
@@ -37088,13 +38068,13 @@ async function generateSubagent2(skillPath, options) {
37088
38068
  try {
37089
38069
  const stats = await stat8(dirPath);
37090
38070
  if (stats.isDirectory()) {
37091
- skillMdPath = join45(dirPath, "SKILL.md");
38071
+ skillMdPath = join46(dirPath, "SKILL.md");
37092
38072
  } else {
37093
38073
  skillMdPath = dirPath;
37094
38074
  dirPath = dirname22(dirPath);
37095
38075
  }
37096
38076
  } catch {
37097
- skillMdPath = dirPath.endsWith(".md") ? dirPath : join45(dirPath, "SKILL.md");
38077
+ skillMdPath = dirPath.endsWith(".md") ? dirPath : join46(dirPath, "SKILL.md");
37098
38078
  }
37099
38079
  spinner.text = "Reading SKILL.md...";
37100
38080
  const content = await readFile10(skillMdPath, "utf-8");
@@ -37145,7 +38125,7 @@ async function generateSubagent2(skillPath, options) {
37145
38125
  "{name}",
37146
38126
  basename7(metadata.name)
37147
38127
  );
37148
- const subagentPath = join45(agentsDir, subagentFilename);
38128
+ const subagentPath = join46(agentsDir, subagentFilename);
37149
38129
  if (await fileExists(subagentPath)) {
37150
38130
  if (!options.force) {
37151
38131
  spinner.warn(`Subagent already exists: ${subagentPath}`);
@@ -37212,7 +38192,7 @@ function createSubagentCommand() {
37212
38192
  import { Command as Command6 } from "commander";
37213
38193
  import ora7 from "ora";
37214
38194
  import { readFile as readFile11, readdir as readdir8 } from "fs/promises";
37215
- import { join as join46, resolve as resolve14 } from "path";
38195
+ import { join as join47, resolve as resolve14 } from "path";
37216
38196
  var logger14 = getCliLogger();
37217
38197
  async function transformSkill2(skillPath, options) {
37218
38198
  const spinner = ora7("Transforming skill...").start();
@@ -37227,9 +38207,9 @@ async function transformSkill2(skillPath, options) {
37227
38207
  const subdirs = await readdir8(dirPath, { withFileTypes: true });
37228
38208
  for (const entry of subdirs) {
37229
38209
  if (entry.isDirectory()) {
37230
- const skillMdPath2 = join46(dirPath, entry.name, "SKILL.md");
38210
+ const skillMdPath2 = join47(dirPath, entry.name, "SKILL.md");
37231
38211
  if (await fileExists(skillMdPath2)) {
37232
- skillDirs.push(join46(dirPath, entry.name));
38212
+ skillDirs.push(join47(dirPath, entry.name));
37233
38213
  }
37234
38214
  }
37235
38215
  }
@@ -37251,7 +38231,7 @@ Processing: ${skillDir}`));
37251
38231
  }
37252
38232
  return;
37253
38233
  }
37254
- const skillMdPath = join46(dirPath, "SKILL.md");
38234
+ const skillMdPath = join47(dirPath, "SKILL.md");
37255
38235
  if (!await fileExists(skillMdPath)) {
37256
38236
  spinner.fail(`No SKILL.md found at: ${skillMdPath}`);
37257
38237
  throw new Error(`No SKILL.md found at: ${skillMdPath}`);
@@ -37323,7 +38303,7 @@ import { Command as Command7 } from "commander";
37323
38303
  import { input as input3, confirm as confirm4 } from "@inquirer/prompts";
37324
38304
  import ora8 from "ora";
37325
38305
  import { mkdir as mkdir10, writeFile as writeFile8, stat as stat9 } from "fs/promises";
37326
- import { dirname as dirname23, join as join47, resolve as resolve15 } from "path";
38306
+ import { dirname as dirname23, join as join48, resolve as resolve15 } from "path";
37327
38307
  var logger15 = getCliLogger();
37328
38308
  async function initMcpServer(name, options) {
37329
38309
  const serverName = name || await input3({
@@ -37436,10 +38416,10 @@ async function initMcpServer(name, options) {
37436
38416
  author
37437
38417
  });
37438
38418
  await mkdir10(targetDir, { recursive: true });
37439
- await mkdir10(join47(targetDir, "src"), { recursive: true });
37440
- await mkdir10(join47(targetDir, "src", "tools"), { recursive: true });
38419
+ await mkdir10(join48(targetDir, "src"), { recursive: true });
38420
+ await mkdir10(join48(targetDir, "src", "tools"), { recursive: true });
37441
38421
  for (const [filePath, content] of files) {
37442
- const fullPath = join47(targetDir, filePath);
38422
+ const fullPath = join48(targetDir, filePath);
37443
38423
  const dir = dirname23(fullPath);
37444
38424
  await mkdir10(dir, { recursive: true });
37445
38425
  await writeFile8(fullPath, content, "utf-8");
@@ -37459,7 +38439,7 @@ async function initMcpServer(name, options) {
37459
38439
  "mcpServers": {
37460
38440
  "${serverName}": {
37461
38441
  "command": "npx",
37462
- "args": ["tsx", "${join47(targetDir, "src", "index.ts")}"]
38442
+ "args": ["tsx", "${join48(targetDir, "src", "index.ts")}"]
37463
38443
  }
37464
38444
  }
37465
38445
  }`)
@@ -37640,8 +38620,8 @@ import { Command as Command9 } from "commander";
37640
38620
  import ora9 from "ora";
37641
38621
 
37642
38622
  // src/commands/recommend.helpers.ts
37643
- import { existsSync as existsSync21, readdirSync as readdirSync2, readFileSync as readFileSync18, statSync as statSync4 } from "node:fs";
37644
- import { join as join48 } from "node:path";
38623
+ import { existsSync as existsSync22, readdirSync as readdirSync2, readFileSync as readFileSync19, statSync as statSync4 } from "node:fs";
38624
+ import { join as join49 } from "node:path";
37645
38625
 
37646
38626
  // src/commands/recommend.types.ts
37647
38627
  var VALID_TRUST_TIERS = [
@@ -37971,14 +38951,14 @@ function buildStackFromAnalysis(context) {
37971
38951
  }
37972
38952
  function getInstalledSkills2() {
37973
38953
  const skillsDir = getCanonicalInstallPath();
37974
- if (!existsSync21(skillsDir)) {
38954
+ if (!existsSync22(skillsDir)) {
37975
38955
  return [];
37976
38956
  }
37977
38957
  const installedSkills = [];
37978
38958
  try {
37979
38959
  const entries = readdirSync2(skillsDir);
37980
38960
  for (const entry of entries) {
37981
- const skillPath = join48(skillsDir, entry);
38961
+ const skillPath = join49(skillsDir, entry);
37982
38962
  const stat13 = statSync4(skillPath);
37983
38963
  if (!stat13.isDirectory()) continue;
37984
38964
  const skill = {
@@ -37987,10 +38967,10 @@ function getInstalledSkills2() {
37987
38967
  tags: [],
37988
38968
  category: null
37989
38969
  };
37990
- const skillMdPath = join48(skillPath, "SKILL.md");
37991
- if (existsSync21(skillMdPath)) {
38970
+ const skillMdPath = join49(skillPath, "SKILL.md");
38971
+ if (existsSync22(skillMdPath)) {
37992
38972
  try {
37993
- const content = readFileSync18(skillMdPath, "utf-8");
38973
+ const content = readFileSync19(skillMdPath, "utf-8");
37994
38974
  const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
37995
38975
  const frontmatter = frontmatterMatch?.[1];
37996
38976
  if (frontmatter) {
@@ -38645,7 +39625,7 @@ function createSyncCommand() {
38645
39625
  // src/commands/merge.ts
38646
39626
  import { Command as Command11 } from "commander";
38647
39627
  import { resolve as resolve16 } from "path";
38648
- import { existsSync as existsSync22 } from "fs";
39628
+ import { existsSync as existsSync23 } from "fs";
38649
39629
  var logger19 = getCliLogger();
38650
39630
  function formatMergeResult(result) {
38651
39631
  const lines = [
@@ -38679,11 +39659,11 @@ async function mergeActionImpl(sourcePath, targetPath, options) {
38679
39659
  }
38680
39660
  const resolvedSource = resolve16(sourcePath);
38681
39661
  const resolvedTarget = targetPath ? resolve16(targetPath) : getDefaultDbPath();
38682
- if (!existsSync22(resolvedSource)) {
39662
+ if (!existsSync23(resolvedSource)) {
38683
39663
  logger19.error(`Source database not found: ${resolvedSource}`);
38684
39664
  process.exit(1);
38685
39665
  }
38686
- if (!existsSync22(resolvedTarget)) {
39666
+ if (!existsSync23(resolvedTarget)) {
38687
39667
  logger19.error(`Target database not found: ${resolvedTarget}`);
38688
39668
  logger19.error("Create a new database first with: skillsmith init");
38689
39669
  process.exit(1);
@@ -38948,13 +39928,13 @@ function createRegistryCommand() {
38948
39928
  import { Command as Command13 } from "commander";
38949
39929
  import ora12 from "ora";
38950
39930
  import { mkdir as mkdir11, copyFile as copyFile2, stat as stat10, readdir as readdir9 } from "fs/promises";
38951
- import { join as join49, dirname as dirname24 } from "path";
39931
+ import { join as join50, dirname as dirname24 } from "path";
38952
39932
  var logger21 = getCliLogger();
38953
39933
  function getAssetsPath() {
38954
- return join49(packageRoot(), "assets", "skillsmith-skill");
39934
+ return join50(packageRoot(), "assets", "skillsmith-skill");
38955
39935
  }
38956
39936
  function getTargetPath(client) {
38957
- return join49(getInstallPath(client), "skillsmith");
39937
+ return join50(getInstallPath(client), "skillsmith");
38958
39938
  }
38959
39939
  async function directoryExists(path27) {
38960
39940
  try {
@@ -38971,8 +39951,8 @@ async function copyDirectory(src, dest) {
38971
39951
  if (entry.isSymbolicLink()) {
38972
39952
  continue;
38973
39953
  }
38974
- const srcPath = join49(src, entry.name);
38975
- const destPath = join49(dest, entry.name);
39954
+ const srcPath = join50(src, entry.name);
39955
+ const destPath = join50(dest, entry.name);
38976
39956
  if (entry.isDirectory()) {
38977
39957
  await mkdir11(destPath, { recursive: true });
38978
39958
  filesCopied += await copyDirectory(srcPath, destPath);
@@ -39350,28 +40330,34 @@ import { Command as Command15 } from "commander";
39350
40330
  import { confirm as confirm5 } from "@inquirer/prompts";
39351
40331
  async function logoutActionImpl() {
39352
40332
  const status = await getAuthStatus();
39353
- if (!status.authenticated) {
40333
+ const jwtSession = await loadCredentials();
40334
+ const hasJwtSession = jwtSession !== null;
40335
+ if (!status.authenticated && !hasJwtSession) {
39354
40336
  console.log("Not authenticated. Nothing to log out.");
39355
40337
  process.exit(0);
39356
40338
  }
39357
40339
  const confirmed = await confirm5({
39358
- message: "Log out and remove stored API key?",
40340
+ message: "Log out and remove stored credentials?",
39359
40341
  default: false
39360
40342
  });
39361
40343
  if (!confirmed) {
39362
40344
  console.log("Cancelled.");
39363
40345
  process.exit(0);
39364
40346
  }
39365
- const result = await clearApiKey();
39366
- if (result.success) {
39367
- console.log(source_default.green(`Logged out. Key removed from ${result.source}.`));
40347
+ const apiKeyResult = await clearApiKey();
40348
+ const jwtResult = await clearCredentials();
40349
+ const results = [apiKeyResult, jwtResult];
40350
+ if (results.every((r) => r.success)) {
40351
+ const sources = [...new Set(results.flatMap((r) => r.source.split(" and ")))].join(" and ");
40352
+ console.log(source_default.green(`Logged out. Credentials removed from ${sources}.`));
39368
40353
  } else {
39369
- console.log(
39370
- source_default.yellow(
39371
- `Logged out (config file cleared), but could not remove from keyring: ${result.error}`
39372
- )
39373
- );
39374
- console.log(source_default.dim("The key may still be stored in your OS keyring."));
40354
+ console.log(source_default.yellow("Logged out (config file cleared), but with keyring warnings:"));
40355
+ for (const result of results) {
40356
+ if (!result.success) {
40357
+ console.log(source_default.yellow(` Could not remove from keyring: ${result.error}`));
40358
+ }
40359
+ }
40360
+ console.log(source_default.dim("Some credentials may still be stored in your OS keyring."));
39375
40361
  }
39376
40362
  process.exit(0);
39377
40363
  }
@@ -39381,7 +40367,7 @@ var logoutAction = withTelemetry(logoutActionImpl, {
39381
40367
  extractFramework: () => "cli"
39382
40368
  });
39383
40369
  function createLogoutCommand() {
39384
- return new Command15("logout").description("Remove stored Skillsmith API key").action(logoutAction);
40370
+ return new Command15("logout").description("Remove stored Skillsmith credentials").action(logoutAction);
39385
40371
  }
39386
40372
 
39387
40373
  // src/commands/whoami.ts
@@ -39393,6 +40379,17 @@ var SOURCE_LABELS = {
39393
40379
  none: "none"
39394
40380
  };
39395
40381
  async function whoamiActionImpl() {
40382
+ const jwtSession = await loadCredentials();
40383
+ if (jwtSession) {
40384
+ console.log(source_default.bold("Skillsmith CLI"));
40385
+ console.log(source_default.dim(" Session: ") + source_default.cyan("device-code login"));
40386
+ const expired = Date.now() >= jwtSession.expiresAt;
40387
+ const expiresLabel = new Date(jwtSession.expiresAt).toLocaleString();
40388
+ console.log(
40389
+ source_default.dim(" Access token: ") + (expired ? source_default.yellow(`expired ${expiresLabel} (refreshes automatically on next use)`) : source_default.green(`valid until ${expiresLabel}`))
40390
+ );
40391
+ process.exit(0);
40392
+ }
39396
40393
  const status = await getAuthStatus();
39397
40394
  if (!status.authenticated || !status.keyPrefix) {
39398
40395
  console.log(`Not authenticated. Run ${source_default.cyan("`skillsmith login`")} to authenticate.`);
@@ -39424,7 +40421,7 @@ function createWhoamiCommand() {
39424
40421
  // src/commands/diff.ts
39425
40422
  import { Command as Command17 } from "commander";
39426
40423
  import { readFile as readFile12 } from "fs/promises";
39427
- import { join as join50 } from "path";
40424
+ import { join as join51 } from "path";
39428
40425
 
39429
40426
  // src/utils/license-types.ts
39430
40427
  var TIER_FEATURES = {
@@ -39554,7 +40551,7 @@ var TIER_PRICING = {
39554
40551
  community: "$0/month",
39555
40552
  individual: "$9.99/month",
39556
40553
  team: "$25/user/month",
39557
- enterprise: "$55/user/month"
40554
+ enterprise: "Custom pricing \u2014 Contact Sales"
39558
40555
  };
39559
40556
  async function requireTier(minimumTier) {
39560
40557
  if (process.env["SKILLSMITH_SKIP_LICENSE_CHECK"] === "true") {
@@ -39632,7 +40629,7 @@ function diffSections(oldContent, newContent) {
39632
40629
  return { added, removed, modified };
39633
40630
  }
39634
40631
  async function readInstalledSkillContent(skillName) {
39635
- const skillPath = join50(getCanonicalInstallPath(), skillName, "SKILL.md");
40632
+ const skillPath = join51(getCanonicalInstallPath(), skillName, "SKILL.md");
39636
40633
  try {
39637
40634
  return await readFile12(skillPath, "utf-8");
39638
40635
  } catch {
@@ -39844,7 +40841,7 @@ import { Command as Command22 } from "commander";
39844
40841
  import * as crypto12 from "node:crypto";
39845
40842
  import * as fs34 from "node:fs";
39846
40843
  import { homedir as homedir28 } from "node:os";
39847
- import { join as join62 } from "node:path";
40844
+ import { join as join63 } from "node:path";
39848
40845
  import { Command as Command19 } from "commander";
39849
40846
  import { input as input4, select as select3 } from "@inquirer/prompts";
39850
40847
 
@@ -44423,6 +45420,9 @@ function makeClaudeMdEntry(claudeMdPath, phrase, mtime) {
44423
45420
  identifier: hashClaudeMdLine(claudeMdPath, phrase),
44424
45421
  triggerSurface: [phrase],
44425
45422
  mtime,
45423
+ // CLAUDE.md rules are Claude Code-only (SMI-6077) — no other supported
45424
+ // client reads this file today.
45425
+ client: CANONICAL_CLIENT,
44426
45426
  meta: { description: phrase }
44427
45427
  };
44428
45428
  }
@@ -45067,8 +46067,14 @@ import * as os10 from "node:os";
45067
46067
  import * as fs20 from "node:fs";
45068
46068
  import * as os7 from "node:os";
45069
46069
  import * as path18 from "node:path";
45070
- var DEFAULT_HOME_CLAUDE_DIR = path18.join(os7.homedir(), ".claude");
45071
- var DEFAULT_MANIFEST_PATH3 = path18.join(os7.homedir(), ".skillsmith", "manifest.json");
46070
+ var REAL_HOME_DIR = os7.homedir();
46071
+ var DEFAULT_HOME_CLAUDE_DIR = path18.join(REAL_HOME_DIR, ".claude");
46072
+ var DEFAULT_MANIFEST_PATH3 = path18.join(REAL_HOME_DIR, ".skillsmith", "manifest.json");
46073
+ function resolveClientSkillsDir(client, homeDir) {
46074
+ const nativePath = CLIENT_NATIVE_PATHS[client];
46075
+ const relativeToRealHome = path18.relative(REAL_HOME_DIR, nativePath);
46076
+ return path18.join(homeDir, relativeToRealHome);
46077
+ }
45072
46078
  async function scanLocalInventory(opts = {}) {
45073
46079
  const startedAt = process.hrtime.bigint();
45074
46080
  const homeDir = opts.homeDir ?? os7.homedir();
@@ -45077,9 +46083,10 @@ async function scanLocalInventory(opts = {}) {
45077
46083
  const warnings = [];
45078
46084
  const entries = [];
45079
46085
  const manifest = loadManifest3(manifestPath);
45080
- entries.push(...scanSkills(path18.join(claudeDir, "skills"), manifest, warnings));
45081
- const agentsSkillsDir = opts.homeDir ? path18.join(opts.homeDir, ".agents", "skills") : path18.join(os7.homedir(), ".agents", "skills");
45082
- entries.push(...scanSkills(agentsSkillsDir, manifest, warnings));
46086
+ for (const client of CLIENT_IDS) {
46087
+ const skillsDir = resolveClientSkillsDir(client, homeDir);
46088
+ entries.push(...scanSkills(skillsDir, manifest, warnings, client));
46089
+ }
45083
46090
  entries.push(...scanCommands(path18.join(claudeDir, "commands"), warnings));
45084
46091
  entries.push(...scanAgents(path18.join(claudeDir, "agents"), warnings));
45085
46092
  const userClaudeMd = path18.join(claudeDir, "CLAUDE.md");
@@ -45099,10 +46106,9 @@ async function scanLocalInventory(opts = {}) {
45099
46106
  });
45100
46107
  const elapsedNs = process.hrtime.bigint() - startedAt;
45101
46108
  const durationMs = Number(elapsedNs) / 1e6;
45102
- void homeDir;
45103
46109
  return { entries, warnings, durationMs };
45104
46110
  }
45105
- function scanSkills(skillsDir, manifest, warnings) {
46111
+ function scanSkills(skillsDir, manifest, warnings, client) {
45106
46112
  if (!fs20.existsSync(skillsDir))
45107
46113
  return [];
45108
46114
  const out = [];
@@ -45144,6 +46150,7 @@ function scanSkills(skillsDir, manifest, warnings) {
45144
46150
  identifier,
45145
46151
  triggerSurface: phrases,
45146
46152
  mtime,
46153
+ client,
45147
46154
  meta: {
45148
46155
  description,
45149
46156
  author: author.author,
@@ -45193,6 +46200,7 @@ function scanMdDir(dir, kind, warnings) {
45193
46200
  identifier,
45194
46201
  triggerSurface: phrases,
45195
46202
  mtime: readMtime(filePath),
46203
+ client: CANONICAL_CLIENT,
45196
46204
  meta: {
45197
46205
  description: triggerLine || void 0
45198
46206
  }
@@ -46811,11 +47819,11 @@ import * as os14 from "node:os";
46811
47819
 
46812
47820
  // ../core/dist/src/audit/exclusions.js
46813
47821
  import { promises as fs30 } from "node:fs";
46814
- import { join as join60 } from "node:path";
47822
+ import { join as join61 } from "node:path";
46815
47823
  var EXCLUSIONS_FILE = "audit-exclusions.json";
46816
47824
  var EMPTY_CONFIG = { version: 1, exclusions: [] };
46817
47825
  function getExclusionsPath(opts) {
46818
- return join60(opts?.configDir ?? getConfigDir(), EXCLUSIONS_FILE);
47826
+ return join61(opts?.configDir ?? getConfigDir(), EXCLUSIONS_FILE);
46819
47827
  }
46820
47828
  async function loadExclusions(opts = {}) {
46821
47829
  const path27 = opts.configPath ?? getExclusionsPath();
@@ -47213,7 +48221,7 @@ function formatTierBadge(tier) {
47213
48221
  function displayLicenseStatus(status) {
47214
48222
  const tierBadge = formatTierBadge(status.tier);
47215
48223
  if (status.tier === "community") {
47216
- console.error(`License: ${tierBadge} ${source_default.dim("(free tier - 1,000 API calls/month)")}`);
48224
+ console.error(`License: ${tierBadge} ${source_default.dim("(free tier - 100 API calls/month)")}`);
47217
48225
  } else if (status.tier === "individual") {
47218
48226
  const expiresInfo = status.expiresAt ? source_default.green(`(expires: ${status.expiresAt.toISOString().split("T")[0]})`) : "";
47219
48227
  console.error(`License: ${tierBadge} ${expiresInfo}`);
@@ -47277,10 +48285,10 @@ async function requireConfirmationPhrase(expected, prompt) {
47277
48285
  }
47278
48286
  }
47279
48287
  function ledgerPath() {
47280
- return join62(homedir28(), ".skillsmith", "namespace-overrides.json");
48288
+ return join63(homedir28(), ".skillsmith", "namespace-overrides.json");
47281
48289
  }
47282
48290
  function backupsDir() {
47283
- return join62(homedir28(), ".skillsmith", "backups");
48291
+ return join63(homedir28(), ".skillsmith", "backups");
47284
48292
  }
47285
48293
  function backupLedgerForReset() {
47286
48294
  const src = ledgerPath();
@@ -47289,7 +48297,7 @@ function backupLedgerForReset() {
47289
48297
  fs34.mkdirSync(dir, { recursive: true, mode: 448 });
47290
48298
  const ts2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
47291
48299
  const suffix = crypto12.randomBytes(4).toString("hex");
47292
- const backupFile = join62(dir, `ledger-${ts2}-${suffix}.json`);
48300
+ const backupFile = join63(dir, `ledger-${ts2}-${suffix}.json`);
47293
48301
  fs34.copyFileSync(src, backupFile);
47294
48302
  return backupFile;
47295
48303
  }
@@ -47548,9 +48556,9 @@ function methodCell(skill) {
47548
48556
  function printHumanReport(report, applying) {
47549
48557
  console.log(source_default.bold.blue("\n=== Skillsmith \u2014 Source Recovery ===\n"));
47550
48558
  const hdr = pad("skill", COL_SKILL) + "| " + pad("source", COL_SOURCE) + "| " + pad("confidence", COL_CONF) + "| method";
47551
- const sep6 = "-".repeat(hdr.length);
48559
+ const sep7 = "-".repeat(hdr.length);
47552
48560
  console.log(hdr);
47553
- console.log(sep6);
48561
+ console.log(sep7);
47554
48562
  for (const skill of report.skills) {
47555
48563
  const row = pad(skill.skillName, COL_SKILL) + "| " + pad(sourceCell(skill), COL_SOURCE) + "| " + pad(confCell(skill), COL_CONF) + "| " + methodCell(skill);
47556
48564
  console.log(row);
@@ -47703,10 +48711,10 @@ function createAuditSourcesSubcommand() {
47703
48711
  import { Command as Command21 } from "commander";
47704
48712
 
47705
48713
  // src/commands/audit-security.candidates.ts
47706
- var SEVERITY_RANK = { critical: 0, high: 1, medium: 2, low: 3 };
48714
+ var SEVERITY_RANK2 = { critical: 0, high: 1, medium: 2, low: 3 };
47707
48715
  function compareCandidates(a, b) {
47708
- const sa = SEVERITY_RANK[a.finding.severity] ?? 4;
47709
- const sb = SEVERITY_RANK[b.finding.severity] ?? 4;
48716
+ const sa = SEVERITY_RANK2[a.finding.severity] ?? 4;
48717
+ const sb = SEVERITY_RANK2[b.finding.severity] ?? 4;
47710
48718
  if (sa !== sb) return sa - sb;
47711
48719
  if (a.identifier !== b.identifier) return a.identifier < b.identifier ? -1 : 1;
47712
48720
  const fa = a.finding.filePath ?? "";
@@ -48326,7 +49334,7 @@ import { Command as Command23 } from "commander";
48326
49334
  import { input as input6, confirm as confirm6, select as select4 } from "@inquirer/prompts";
48327
49335
  import ora13 from "ora";
48328
49336
  import { mkdir as mkdir17, writeFile as writeFile15, stat as stat12 } from "fs/promises";
48329
- import { join as join63 } from "path";
49337
+ import { join as join64 } from "path";
48330
49338
  var logger29 = getCliLogger();
48331
49339
  var VALID_TYPES = ["basic", "intermediate", "advanced"];
48332
49340
  var VALID_BEHAVIORS = ["autonomous", "guided", "interactive", "configurable"];
@@ -48462,7 +49470,7 @@ async function createSkill(name, options = {}) {
48462
49470
  default: false
48463
49471
  });
48464
49472
  const outputDir = options.output ?? getCanonicalInstallPath();
48465
- const skillDir = join63(outputDir, skillName);
49473
+ const skillDir = join64(outputDir, skillName);
48466
49474
  let exists = false;
48467
49475
  try {
48468
49476
  await stat12(skillDir);
@@ -48536,16 +49544,16 @@ Thumbs.db
48536
49544
  const spinner = ora13("Scaffolding skill...").start();
48537
49545
  try {
48538
49546
  await mkdir17(skillDir, { recursive: true });
48539
- await mkdir17(join63(skillDir, "resources"), { recursive: true });
49547
+ await mkdir17(join64(skillDir, "resources"), { recursive: true });
48540
49548
  if (includeScripts) {
48541
- await mkdir17(join63(skillDir, "scripts"), { recursive: true });
49549
+ await mkdir17(join64(skillDir, "scripts"), { recursive: true });
48542
49550
  }
48543
- await writeFile15(join63(skillDir, "SKILL.md"), skillMdContent, "utf-8");
48544
- await writeFile15(join63(skillDir, "README.md"), readmeContent, "utf-8");
48545
- await writeFile15(join63(skillDir, "CHANGELOG.md"), changelogContent, "utf-8");
48546
- await writeFile15(join63(skillDir, ".gitignore"), gitignoreContent, "utf-8");
49551
+ await writeFile15(join64(skillDir, "SKILL.md"), skillMdContent, "utf-8");
49552
+ await writeFile15(join64(skillDir, "README.md"), readmeContent, "utf-8");
49553
+ await writeFile15(join64(skillDir, "CHANGELOG.md"), changelogContent, "utf-8");
49554
+ await writeFile15(join64(skillDir, ".gitignore"), gitignoreContent, "utf-8");
48547
49555
  if (includeScripts) {
48548
- await writeFile15(join63(skillDir, "scripts", "example.js"), scriptContent, "utf-8");
49556
+ await writeFile15(join64(skillDir, "scripts", "example.js"), scriptContent, "utf-8");
48549
49557
  }
48550
49558
  spinner.succeed(`Skill scaffolded at ${skillDir}`);
48551
49559
  } catch (error46) {
@@ -48924,7 +49932,7 @@ import { promises as fs36 } from "node:fs";
48924
49932
  // src/commands/import-local.helpers.ts
48925
49933
  import { createHash as createHash19 } from "node:crypto";
48926
49934
  import { promises as fs35 } from "node:fs";
48927
- import { join as join64, resolve as resolve17, dirname as dirname29, basename as basename10, sep as sep5, relative as relative7 } from "node:path";
49935
+ import { join as join65, resolve as resolve17, dirname as dirname29, basename as basename10, sep as sep6, relative as relative9 } from "node:path";
48928
49936
  import matter from "gray-matter";
48929
49937
  var SKILL_FILENAME = "SKILL.md";
48930
49938
  var MAX_DEPTH = 8;
@@ -48945,7 +49953,7 @@ async function walkSkillFiles(rootDir) {
48945
49953
  return;
48946
49954
  }
48947
49955
  for (const entry of entries) {
48948
- const entryPath = join64(dir, entry.name);
49956
+ const entryPath = join65(dir, entry.name);
48949
49957
  if (entry.isSymbolicLink()) {
48950
49958
  let realPath;
48951
49959
  try {
@@ -48953,8 +49961,8 @@ async function walkSkillFiles(rootDir) {
48953
49961
  } catch {
48954
49962
  continue;
48955
49963
  }
48956
- const rel = relative7(canonicalRoot, realPath);
48957
- if (rel.startsWith("..") || rel === ".." || rel.startsWith(`..${sep5}`)) {
49964
+ const rel = relative9(canonicalRoot, realPath);
49965
+ if (rel.startsWith("..") || rel === ".." || rel.startsWith(`..${sep6}`)) {
48958
49966
  skipped.push({ path: entryPath, reason: "symlink-escapes-root" });
48959
49967
  continue;
48960
49968
  }
@@ -49254,7 +50262,7 @@ function printHumanSummary(result) {
49254
50262
  import * as crypto13 from "node:crypto";
49255
50263
  import * as fs37 from "node:fs";
49256
50264
  import { homedir as homedir29 } from "node:os";
49257
- import { join as join65, dirname as dirname30 } from "node:path";
50265
+ import { join as join66, dirname as dirname30 } from "node:path";
49258
50266
  import { Command as Command27 } from "commander";
49259
50267
  var logger33 = getCliLogger();
49260
50268
  var CONFIG_DIR3 = ".skillsmith";
@@ -49278,7 +50286,7 @@ function isSupportedKey(key) {
49278
50286
  return SUPPORTED_KEYS.includes(key);
49279
50287
  }
49280
50288
  function configPath() {
49281
- return join65(homedir29(), CONFIG_DIR3, CONFIG_FILE3);
50289
+ return join66(homedir29(), CONFIG_DIR3, CONFIG_FILE3);
49282
50290
  }
49283
50291
  function readConfigFile2() {
49284
50292
  const path27 = configPath();
@@ -49395,15 +50403,15 @@ function createConfigCommand2() {
49395
50403
  import { Command as Command28 } from "commander";
49396
50404
 
49397
50405
  // src/commands/telemetry.action.ts
49398
- import { existsSync as existsSync28, copyFileSync as copyFileSync2, chmodSync as chmodSync9, mkdirSync as mkdirSync14 } from "node:fs";
50406
+ import { existsSync as existsSync29, copyFileSync as copyFileSync2, chmodSync as chmodSync9, mkdirSync as mkdirSync14 } from "node:fs";
49399
50407
  import { homedir as homedir31 } from "node:os";
49400
- import { join as join67, dirname as dirname32 } from "node:path";
50408
+ import { join as join68, dirname as dirname32 } from "node:path";
49401
50409
  import { readdirSync as readdirSync4, unlinkSync as unlinkSync5, statSync as statSync6 } from "node:fs";
49402
50410
 
49403
50411
  // src/commands/telemetry.helpers.ts
49404
50412
  import * as crypto14 from "node:crypto";
49405
50413
  import * as fs38 from "node:fs";
49406
- import { join as join66, dirname as dirname31 } from "node:path";
50414
+ import { join as join67, dirname as dirname31 } from "node:path";
49407
50415
  import { homedir as homedir30 } from "node:os";
49408
50416
  var TelemetryHookError = class extends Error {
49409
50417
  constructor(code, message) {
@@ -49415,9 +50423,9 @@ var TelemetryHookError = class extends Error {
49415
50423
  };
49416
50424
  function resolveSettingsPath(scope) {
49417
50425
  if (scope === "user") {
49418
- return join66(homedir30(), ".claude", "settings.json");
50426
+ return join67(homedir30(), ".claude", "settings.json");
49419
50427
  }
49420
- return join66(process.cwd(), ".claude", "settings.json");
50428
+ return join67(process.cwd(), ".claude", "settings.json");
49421
50429
  }
49422
50430
  function loadClaudeSettings(scope) {
49423
50431
  const path27 = resolveSettingsPath(scope);
@@ -49509,10 +50517,10 @@ var PRIVACY_URL = "https://skillsmith.app/privacy#telemetry";
49509
50517
  var DEFAULT_ENDPOINT = "https://vrcnzpmndtroqxxoqkzy.supabase.co/functions/v1/events";
49510
50518
  var ORPHAN_TTL_MS = 60 * 60 * 1e3;
49511
50519
  function hookScriptPath() {
49512
- return join67(homedir31(), ".skillsmith", "hooks", "skill-telemetry.sh");
50520
+ return join68(homedir31(), ".skillsmith", "hooks", "skill-telemetry.sh");
49513
50521
  }
49514
50522
  function runDir() {
49515
- return join67(homedir31(), ".skillsmith", "run");
50523
+ return join68(homedir31(), ".skillsmith", "run");
49516
50524
  }
49517
50525
  function idTail(id) {
49518
50526
  if (!id) return "(none)";
@@ -49521,11 +50529,11 @@ function idTail(id) {
49521
50529
  function gcOrphanRunFiles() {
49522
50530
  try {
49523
50531
  const dir = runDir();
49524
- if (!existsSync28(dir)) return;
50532
+ if (!existsSync29(dir)) return;
49525
50533
  const now = Date.now();
49526
50534
  for (const f of readdirSync4(dir)) {
49527
50535
  if (!f.startsWith("skill-")) continue;
49528
- const fp = join67(dir, f);
50536
+ const fp = join68(dir, f);
49529
50537
  try {
49530
50538
  const st = statSync6(fp);
49531
50539
  if (now - st.mtimeMs > ORPHAN_TTL_MS) unlinkSync5(fp);
@@ -49625,8 +50633,8 @@ async function runStatus() {
49625
50633
  }
49626
50634
  }
49627
50635
  async function runInstallHook(options) {
49628
- const templateSrc = join67(packageRoot(), "templates", "skill-telemetry.sh");
49629
- if (!existsSync28(templateSrc)) {
50636
+ const templateSrc = join68(packageRoot(), "templates", "skill-telemetry.sh");
50637
+ if (!existsSync29(templateSrc)) {
49630
50638
  throw new Error(
49631
50639
  "skill-telemetry.sh template not found. Ensure the CLI package is fully built: npm run build"
49632
50640
  );
@@ -49669,7 +50677,7 @@ async function runUninstallHook(options) {
49669
50677
  writeClaudeSettings(scope, updated);
49670
50678
  try {
49671
50679
  const scriptPath = hookScriptPath();
49672
- if (existsSync28(scriptPath)) unlinkSync5(scriptPath);
50680
+ if (existsSync29(scriptPath)) unlinkSync5(scriptPath);
49673
50681
  } catch {
49674
50682
  }
49675
50683
  const scopeLabel = scope === "user" ? "~/.claude/settings.json" : "./.claude/settings.json";
@@ -49880,7 +50888,7 @@ async function runStatus2(opts) {
49880
50888
  const localCount = countByHarness.get("local") ?? 0;
49881
50889
  if (localCount > 0) {
49882
50890
  console.log(
49883
- ` Local skills: ${source_default.dim(`${localCount} skill${localCount === 1 ? "" : "s"} in ./.claude/skills (repo-local \u2014 not synced to your account)`)}`
50891
+ ` Local skills: ${source_default.dim(`${localCount} skill${localCount === 1 ? "" : "s"} in ${getLocalSkillsDirDisplay()} (repo-local \u2014 not synced to your account)`)}`
49884
50892
  );
49885
50893
  if (opts?.verbose) {
49886
50894
  for (const s of allSkills.filter((e) => e.harness === "local")) {
@@ -50088,14 +51096,14 @@ function createAgentCommand() {
50088
51096
  }
50089
51097
 
50090
51098
  // src/commands/diagnose.ts
50091
- import { mkdirSync as mkdirSync15, readFileSync as readFileSync26, writeFileSync as writeFileSync16 } from "node:fs";
51099
+ import { mkdirSync as mkdirSync15, readFileSync as readFileSync27, writeFileSync as writeFileSync17 } from "node:fs";
50092
51100
  import { basename as basename11, dirname as dirname33, resolve as resolve19 } from "node:path";
50093
51101
  import { Command as Command31 } from "commander";
50094
51102
 
50095
51103
  // src/commands/log-records.helpers.ts
50096
- import { existsSync as existsSync29, readFileSync as readFileSync25, readdirSync as readdirSync5, statSync as statSync7 } from "node:fs";
51104
+ import { existsSync as existsSync30, readFileSync as readFileSync26, readdirSync as readdirSync5, statSync as statSync7 } from "node:fs";
50097
51105
  import { homedir as homedir32 } from "node:os";
50098
- import { join as join68 } from "node:path";
51106
+ import { join as join69 } from "node:path";
50099
51107
  var LOG_FILE_PATTERN = /^skillsmith-[a-z]+-\d{4}-\d{2}-\d{2}\.jsonl(\.\d+)?$/;
50100
51108
  var LOG_LEVEL_ORDER = {
50101
51109
  debug: 0,
@@ -50108,12 +51116,12 @@ function isLogLevel(value) {
50108
51116
  return VALID_LEVELS.has(value);
50109
51117
  }
50110
51118
  function resolveLogDir() {
50111
- return process.env["SKILLSMITH_LOG_DIR"] || join68(homedir32(), ".skillsmith", "logs");
51119
+ return process.env["SKILLSMITH_LOG_DIR"] || join69(homedir32(), ".skillsmith", "logs");
50112
51120
  }
50113
51121
  function listLogFiles(dir) {
50114
- if (!existsSync29(dir)) return [];
51122
+ if (!existsSync30(dir)) return [];
50115
51123
  try {
50116
- return readdirSync5(dir).filter((name) => LOG_FILE_PATTERN.test(name)).sort().map((name) => join68(dir, name));
51124
+ return readdirSync5(dir).filter((name) => LOG_FILE_PATTERN.test(name)).sort().map((name) => join69(dir, name));
50117
51125
  } catch {
50118
51126
  return [];
50119
51127
  }
@@ -50121,7 +51129,7 @@ function listLogFiles(dir) {
50121
51129
  function readLogRecords(filePath) {
50122
51130
  let content;
50123
51131
  try {
50124
- content = readFileSync25(filePath, "utf8");
51132
+ content = readFileSync26(filePath, "utf8");
50125
51133
  } catch {
50126
51134
  return [];
50127
51135
  }
@@ -50227,7 +51235,7 @@ function buildBundleContent(summary, files) {
50227
51235
  parts.push("");
50228
51236
  parts.push(`===== ${basename11(file2)} (${fileSizeBytes(file2)} bytes) =====`);
50229
51237
  try {
50230
- parts.push(readFileSync26(file2, "utf8"));
51238
+ parts.push(readFileSync27(file2, "utf8"));
50231
51239
  } catch (error46) {
50232
51240
  parts.push(`[failed to read: ${sanitizeError(error46)}]`);
50233
51241
  }
@@ -50240,7 +51248,7 @@ function writeBundle(bundleOption, summary, files) {
50240
51248
  const targetPath = resolve19(rawPath);
50241
51249
  const content = buildBundleContent(summary, files);
50242
51250
  mkdirSync15(dirname33(targetPath), { recursive: true });
50243
- writeFileSync16(targetPath, content, "utf8");
51251
+ writeFileSync17(targetPath, content, "utf8");
50244
51252
  return targetPath;
50245
51253
  }
50246
51254
  async function runDiagnose(options) {
@@ -50298,8 +51306,8 @@ function createDiagnoseCommand() {
50298
51306
  }
50299
51307
 
50300
51308
  // src/commands/logs.ts
50301
- import { existsSync as existsSync30, readFileSync as readFileSync27, statSync as statSync8 } from "node:fs";
50302
- import { join as join69 } from "node:path";
51309
+ import { existsSync as existsSync31, readFileSync as readFileSync28, statSync as statSync8 } from "node:fs";
51310
+ import { join as join70 } from "node:path";
50303
51311
  import { Command as Command32 } from "commander";
50304
51312
  var logger38 = getCliLogger();
50305
51313
  var TAIL_SURFACES = ["cli", "mcp", "vscode", "doc-retrieval"];
@@ -50308,7 +51316,7 @@ function todayDateString2() {
50308
51316
  }
50309
51317
  function todaysFilePaths(dir) {
50310
51318
  const date5 = todayDateString2();
50311
- return TAIL_SURFACES.map((surface) => join69(dir, `skillsmith-${surface}-${date5}.jsonl`));
51319
+ return TAIL_SURFACES.map((surface) => join70(dir, `skillsmith-${surface}-${date5}.jsonl`));
50312
51320
  }
50313
51321
  function resolveLevel(raw) {
50314
51322
  if (raw === void 0) return void 0;
@@ -50341,7 +51349,7 @@ async function startTail(dir, level, opts = {}) {
50341
51349
  const offsets = /* @__PURE__ */ new Map();
50342
51350
  let printedAny = false;
50343
51351
  for (const path27 of paths) {
50344
- if (!existsSync30(path27)) {
51352
+ if (!existsSync31(path27)) {
50345
51353
  offsets.set(path27, 0);
50346
51354
  continue;
50347
51355
  }
@@ -50380,7 +51388,7 @@ async function startTail(dir, level, opts = {}) {
50380
51388
  }
50381
51389
  let content;
50382
51390
  try {
50383
- content = readFileSync27(path27).subarray(previousOffset).toString("utf8");
51391
+ content = readFileSync28(path27).subarray(previousOffset).toString("utf8");
50384
51392
  } catch {
50385
51393
  return;
50386
51394
  }
@@ -50457,12 +51465,12 @@ function applyRootQuietOption(rootQuiet) {
50457
51465
  }
50458
51466
 
50459
51467
  // src/utils/node-version.ts
50460
- import { readFileSync as readFileSync28 } from "fs";
50461
- import { join as join70 } from "path";
51468
+ import { readFileSync as readFileSync29 } from "fs";
51469
+ import { join as join71 } from "path";
50462
51470
  function loadMinNodeVersion() {
50463
51471
  try {
50464
- const packageJsonPath2 = join70(packageRoot(), "package.json");
50465
- const packageJson2 = JSON.parse(readFileSync28(packageJsonPath2, "utf-8"));
51472
+ const packageJsonPath2 = join71(packageRoot(), "package.json");
51473
+ const packageJson2 = JSON.parse(readFileSync29(packageJsonPath2, "utf-8"));
50466
51474
  const engineConstraint = packageJson2.engines?.node ?? ">=22.22.0";
50467
51475
  return engineConstraint.replace(/[>=<^~\s]/g, "");
50468
51476
  } catch {
@@ -50533,21 +51541,21 @@ function checkNodeVersion() {
50533
51541
  }
50534
51542
 
50535
51543
  // src/index.ts
50536
- import { readFileSync as readFileSync29 } from "fs";
50537
- import { join as join71 } from "path";
51544
+ import { readFileSync as readFileSync30 } from "fs";
51545
+ import { join as join72 } from "path";
50538
51546
  var logger39 = getCliLogger();
50539
51547
  var versionError = checkNodeVersion();
50540
51548
  if (versionError) {
50541
51549
  logger39.error(versionError);
50542
51550
  process.exit(1);
50543
51551
  }
50544
- var packageJsonPath = join71(packageRoot(), "package.json");
50545
- var packageJson = JSON.parse(readFileSync29(packageJsonPath, "utf-8"));
51552
+ var packageJsonPath = join72(packageRoot(), "package.json");
51553
+ var packageJson = JSON.parse(readFileSync30(packageJsonPath, "utf-8"));
50546
51554
  var CLI_VERSION = packageJson.version;
50547
51555
  var program = new Command33();
50548
51556
  var commandName = process.argv[1]?.endsWith("sklx") ? "sklx" : "skillsmith";
50549
51557
  program.name(commandName).description(
50550
- "Publish versioned agent skills to a team-scoped registry, catch drift across installs, and deprecate what's gone stale. (alias: sklx)"
51558
+ "A registry for sharing, scanning, and tracking agent skills across teams. (alias: sklx)"
50551
51559
  ).version(CLI_VERSION).option(
50552
51560
  "--quiet",
50553
51561
  "Suppress advisory/progress output across all commands (sets SKILLSMITH_QUIET)"