bip-skills 1.4.8 → 1.4.9

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.
Files changed (2) hide show
  1. package/dist/cli.mjs +160 -15
  2. package/package.json +1 -1
package/dist/cli.mjs CHANGED
@@ -17,7 +17,10 @@ import { createHash } from "crypto";
17
17
  import { fileURLToPath } from "url";
18
18
  import * as readline from "readline";
19
19
  import { Writable } from "stream";
20
+ import { Buffer } from "node:buffer";
20
21
  import { access, cp, lstat, mkdir, mkdtemp, readFile, readdir, readlink, realpath, rm, stat, symlink, writeFile } from "fs/promises";
22
+ import http from "node:http";
23
+ import https from "node:https";
21
24
  var import_picocolors = /* @__PURE__ */ __toESM(require_picocolors(), 1);
22
25
  const AGENTS_DIR$2 = ".agents";
23
26
  const SKILLS_SUBDIR = "skills";
@@ -31,9 +34,13 @@ const DEFAULT_GIT_HOST = "git.yyrd.com";
31
34
  const DEFAULT_GIT_BASE_URL = `https://${DEFAULT_GIT_HOST}`;
32
35
  const DEFAULT_GIT_FALLBACK_SSH_HOST = "git.yyrd.com";
33
36
  const DEFAULT_INSTALL_REPORT_URL = "https://sun.yyuap.com/api/install-report";
37
+ const TLS_RELAXED_HOSTS = ["sun.yyuap.com"];
34
38
  function isYyuapHost(hostname) {
35
39
  return hostname === "yyuap.com" || hostname.endsWith(".yyuap.com");
36
40
  }
41
+ function shouldRelaxTlsForHost(hostname) {
42
+ return TLS_RELAXED_HOSTS.includes(hostname);
43
+ }
37
44
  function isYyrdHost(hostname) {
38
45
  return hostname === "yyrd.com" || hostname.endsWith(".yyrd.com");
39
46
  }
@@ -515,13 +522,41 @@ async function hasSkillMd(dir) {
515
522
  return false;
516
523
  }
517
524
  }
525
+ function getYamlHeuristicHint(content, frontmatterKeys) {
526
+ if (frontmatterKeys.length > 0 || !content.startsWith("---\n")) return null;
527
+ const closingMarkerIndex = content.indexOf("\n---", 4);
528
+ if (closingMarkerIndex === -1) return "frontmatter start marker was found, but the closing --- marker may be missing or malformed";
529
+ const suspiciousLine = content.slice(4, closingMarkerIndex).split(/\r?\n/).find((line) => /^[A-Za-z0-9_-]+:\s.+:\s/.test(line) && !/^[A-Za-z0-9_-]+:\s*["'|>]/.test(line));
530
+ if (suspiciousLine) return `frontmatter looks present but YAML may be invalid; this line contains an unquoted ": " inside a scalar value: ${JSON.stringify(suspiciousLine)}`;
531
+ return "frontmatter looks present but YAML parsing produced no keys; check for hidden characters, invalid YAML syntax, or unquoted \": \" inside values";
532
+ }
518
533
  async function parseSkillMd(skillMdPath, options) {
519
534
  try {
520
535
  const content = await readFile(skillMdPath, "utf-8");
521
536
  const { data } = (0, import_gray_matter.default)(content);
522
- if (!data.name || !data.description) return null;
523
- if (typeof data.name !== "string" || typeof data.description !== "string") return null;
524
- if (data.metadata?.internal === true && !shouldInstallInternalSkills() && !options?.includeInternal) return null;
537
+ const frontmatterKeys = Object.keys(data);
538
+ const frontmatterSummary = frontmatterKeys.length === 0 ? "(none)" : frontmatterKeys.map((key) => {
539
+ const value = data[key];
540
+ return `${key}:${Array.isArray(value) ? "array" : typeof value}`;
541
+ }).join(", ");
542
+ const leadingPreview = JSON.stringify(content.slice(0, 80));
543
+ const firstLine = JSON.stringify(content.split(/\r?\n/, 1)[0] ?? "");
544
+ const leadingCharCodes = Array.from(content.slice(0, 8)).map((char) => `U+${char.charCodeAt(0).toString(16).toUpperCase().padStart(4, "0")}`).join(", ");
545
+ const heuristicHint = getYamlHeuristicHint(content, frontmatterKeys);
546
+ if (!data.name || !data.description) {
547
+ debugLog(`[discover] skipped ${skillMdPath}: missing required frontmatter fields "name" and/or "description" (parsed keys: ${frontmatterSummary}, firstLine: ${firstLine}, leadingChars: ${leadingCharCodes}, leadingPreview: ${leadingPreview})`);
548
+ if (heuristicHint) debugLog(`[discover] hint ${skillMdPath}: ${heuristicHint}`);
549
+ return null;
550
+ }
551
+ if (typeof data.name !== "string" || typeof data.description !== "string") {
552
+ debugLog(`[discover] skipped ${skillMdPath}: frontmatter "name" and "description" must be strings (name=${typeof data.name}, description=${typeof data.description}, parsed keys: ${frontmatterSummary})`);
553
+ return null;
554
+ }
555
+ if (data.metadata?.internal === true && !shouldInstallInternalSkills() && !options?.includeInternal) {
556
+ debugLog(`[discover] skipped ${skillMdPath}: internal skill hidden (set INSTALL_INTERNAL_SKILLS=1 or request it explicitly)`);
557
+ return null;
558
+ }
559
+ debugLog(`[discover] accepted ${skillMdPath}: name="${data.name}" (parsed keys: ${frontmatterSummary})`);
525
560
  return {
526
561
  name: data.name,
527
562
  description: data.description,
@@ -529,7 +564,8 @@ async function parseSkillMd(skillMdPath, options) {
529
564
  rawContent: content,
530
565
  metadata: data.metadata
531
566
  };
532
- } catch {
567
+ } catch (error) {
568
+ debugLog(`[discover] failed to read ${skillMdPath}: ${error instanceof Error ? error.message : String(error)}`);
533
569
  return null;
534
570
  }
535
571
  }
@@ -548,6 +584,7 @@ async function discoverSkills(basePath, subpath, options) {
548
584
  const skills = [];
549
585
  const seenNames = /* @__PURE__ */ new Set();
550
586
  const searchPath = subpath ? join(basePath, subpath) : basePath;
587
+ debugLog(`[discover] start basePath=${basePath} searchPath=${searchPath} includeInternal=${options?.includeInternal === true} fullDepth=${options?.fullDepth === true}`);
551
588
  const pluginGroupings = await getPluginGroupings(searchPath);
552
589
  const enhanceSkill = (skill) => {
553
590
  const resolvedPath = resolve(skill.path);
@@ -555,6 +592,7 @@ async function discoverSkills(basePath, subpath, options) {
555
592
  return skill;
556
593
  };
557
594
  if (await hasSkillMd(searchPath)) {
595
+ debugLog(`[discover] found root SKILL.md at ${join(searchPath, "SKILL.md")}`);
558
596
  let skill = await parseSkillMd(join(searchPath, "SKILL.md"), options);
559
597
  if (skill) {
560
598
  skill = enhanceSkill(skill);
@@ -595,31 +633,38 @@ async function discoverSkills(basePath, subpath, options) {
595
633
  join(searchPath, ".zencoder/skills")
596
634
  ];
597
635
  prioritySearchDirs.push(...await getPluginSkillPaths(searchPath));
636
+ debugLog(`[discover] scanning priority directories (${prioritySearchDirs.length}): ${prioritySearchDirs.join(", ")}`);
598
637
  for (const dir of prioritySearchDirs) try {
599
638
  const entries = await readdir(dir, { withFileTypes: true });
600
639
  for (const entry of entries) if (entry.isDirectory()) {
601
640
  const skillDir = join(dir, entry.name);
602
641
  if (await hasSkillMd(skillDir)) {
642
+ debugLog(`[discover] candidate skill directory ${skillDir}`);
603
643
  let skill = await parseSkillMd(join(skillDir, "SKILL.md"), options);
604
644
  if (skill && !seenNames.has(skill.name)) {
605
645
  skill = enhanceSkill(skill);
606
646
  skills.push(skill);
607
647
  seenNames.add(skill.name);
608
- }
648
+ } else if (skill) debugLog(`[discover] skipped ${join(skillDir, "SKILL.md")}: duplicate skill name "${skill.name}"`);
609
649
  }
610
650
  }
611
- } catch {}
651
+ } catch (error) {
652
+ debugLog(`[discover] unable to scan ${dir}: ${error instanceof Error ? error.message : String(error)}`);
653
+ }
612
654
  if (skills.length === 0 || options?.fullDepth) {
655
+ debugLog(`[discover] ${skills.length === 0 ? "no skills from priority scan" : "fullDepth enabled"}; starting recursive search from ${searchPath}`);
613
656
  const allSkillDirs = await findSkillDirs(searchPath);
657
+ debugLog(`[discover] recursive candidates (${allSkillDirs.length}): ${allSkillDirs.join(", ") || "(none)"}`);
614
658
  for (const skillDir of allSkillDirs) {
615
659
  let skill = await parseSkillMd(join(skillDir, "SKILL.md"), options);
616
660
  if (skill && !seenNames.has(skill.name)) {
617
661
  skill = enhanceSkill(skill);
618
662
  skills.push(skill);
619
663
  seenNames.add(skill.name);
620
- }
664
+ } else if (skill) debugLog(`[discover] skipped ${join(skillDir, "SKILL.md")}: duplicate skill name "${skill.name}"`);
621
665
  }
622
666
  }
667
+ debugLog(`[discover] completed searchPath=${searchPath}; found ${skills.length} skill${skills.length === 1 ? "" : "s"}`);
623
668
  return skills;
624
669
  }
625
670
  function getSkillDisplayName(skill) {
@@ -1411,6 +1456,106 @@ async function listInstalledSkills(options = {}) {
1411
1456
  } catch {}
1412
1457
  return Array.from(skillsMap.values());
1413
1458
  }
1459
+ function getRequestUrl(input) {
1460
+ if (input instanceof URL) return input;
1461
+ if (typeof input === "string") return new URL(input);
1462
+ return new URL(input.url);
1463
+ }
1464
+ function shouldUseRelaxedTls(input) {
1465
+ try {
1466
+ const url = getRequestUrl(input);
1467
+ return url.protocol === "https:" && shouldRelaxTlsForHost(url.hostname);
1468
+ } catch {
1469
+ return false;
1470
+ }
1471
+ }
1472
+ function normalizeRequestMethod(input, init) {
1473
+ if (init?.method) return init.method.toUpperCase();
1474
+ if (typeof Request !== "undefined" && input instanceof Request) return input.method.toUpperCase();
1475
+ return "GET";
1476
+ }
1477
+ function buildHeaders(input, init) {
1478
+ const headers = new Headers(typeof Request !== "undefined" && input instanceof Request ? input.headers : void 0);
1479
+ if (init?.headers) new Headers(init.headers).forEach((value, key) => {
1480
+ headers.set(key, value);
1481
+ });
1482
+ return headers;
1483
+ }
1484
+ async function readRequestBody(init) {
1485
+ const body = init?.body;
1486
+ if (body == null) return;
1487
+ if (typeof body === "string") return Buffer.from(body);
1488
+ if (body instanceof URLSearchParams) return Buffer.from(body.toString());
1489
+ if (body instanceof ArrayBuffer) return Buffer.from(body);
1490
+ if (ArrayBuffer.isView(body)) return Buffer.from(body.buffer, body.byteOffset, body.byteLength);
1491
+ if (body instanceof Blob) return Buffer.from(await body.arrayBuffer());
1492
+ throw new TypeError("Unsupported request body for relaxed TLS fetch");
1493
+ }
1494
+ async function relaxedTlsFetch(input, init, redirectCount = 0) {
1495
+ if (redirectCount > 5) throw new Error("Too many redirects");
1496
+ const url = getRequestUrl(input);
1497
+ const method = normalizeRequestMethod(input, init);
1498
+ const headers = buildHeaders(input, init);
1499
+ const body = await readRequestBody(init);
1500
+ const transport = url.protocol === "https:" ? https : http;
1501
+ return await new Promise((resolve, reject) => {
1502
+ const request = transport.request(url, {
1503
+ method,
1504
+ headers: Object.fromEntries(headers.entries()),
1505
+ rejectUnauthorized: url.protocol === "https:" ? false : void 0
1506
+ }, (response) => {
1507
+ const status = response.statusCode ?? 500;
1508
+ const location = response.headers.location;
1509
+ if (location && [
1510
+ 301,
1511
+ 302,
1512
+ 303,
1513
+ 307,
1514
+ 308
1515
+ ].includes(status) && init?.redirect !== "manual") {
1516
+ response.resume();
1517
+ const nextUrl = new URL(location, url);
1518
+ const nextInit = {
1519
+ ...init,
1520
+ method: status === 303 ? "GET" : method
1521
+ };
1522
+ delete nextInit.body;
1523
+ relaxedTlsFetch(nextUrl, nextInit, redirectCount + 1).then(resolve).catch(reject);
1524
+ return;
1525
+ }
1526
+ const chunks = [];
1527
+ response.on("data", (chunk) => {
1528
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
1529
+ });
1530
+ response.on("end", () => {
1531
+ const responseHeaders = Object.entries(response.headers).flatMap(([key, value]) => value == null ? [] : Array.isArray(value) ? value.map((item) => [key, item]) : [[key, value]]);
1532
+ resolve(new Response(method === "HEAD" ? null : Buffer.concat(chunks), {
1533
+ status,
1534
+ statusText: response.statusMessage ?? "",
1535
+ headers: new Headers(responseHeaders)
1536
+ }));
1537
+ });
1538
+ });
1539
+ request.on("error", reject);
1540
+ if (init?.signal) {
1541
+ const abort = () => {
1542
+ request.destroy(new DOMException("The operation was aborted.", "AbortError"));
1543
+ };
1544
+ if (init.signal.aborted) {
1545
+ abort();
1546
+ return;
1547
+ }
1548
+ init.signal.addEventListener("abort", abort, { once: true });
1549
+ request.on("close", () => init.signal?.removeEventListener("abort", abort));
1550
+ }
1551
+ if (body) request.write(body);
1552
+ request.end();
1553
+ });
1554
+ }
1555
+ async function fetchWithSelectiveTls(input, init) {
1556
+ if (!shouldUseRelaxedTls(input)) return fetch(input, init);
1557
+ return relaxedTlsFetch(input, init);
1558
+ }
1414
1559
  function getAllowedReportingUrl(envVarName) {
1415
1560
  const value = process.env[envVarName];
1416
1561
  if (!value) return null;
@@ -1439,7 +1584,7 @@ async function fetchAuditData(source, skillSlugs, timeoutMs = 3e3) {
1439
1584
  });
1440
1585
  const controller = new AbortController();
1441
1586
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
1442
- const response = await fetch(`${AUDIT_URL}?${params.toString()}`, { signal: controller.signal });
1587
+ const response = await fetchWithSelectiveTls(`${AUDIT_URL}?${params.toString()}`, { signal: controller.signal });
1443
1588
  clearTimeout(timeout);
1444
1589
  if (!response.ok) return null;
1445
1590
  return await response.json();
@@ -1454,7 +1599,7 @@ function sendTelemetry(url, data) {
1454
1599
  if (cliVersion) params.set("v", cliVersion);
1455
1600
  if (isCI()) params.set("ci", "1");
1456
1601
  for (const [key, value] of Object.entries(data)) if (value !== void 0 && value !== null) params.set(key, String(value));
1457
- fetch(`${url}?${params.toString()}`).catch(() => {});
1602
+ fetchWithSelectiveTls(`${url}?${params.toString()}`).catch(() => {});
1458
1603
  } catch {}
1459
1604
  }
1460
1605
  function track(data) {
@@ -1566,7 +1711,7 @@ var WellKnownProvider = class {
1566
1711
  });
1567
1712
  for (const { indexUrl, baseUrl: resolvedBase } of urlsToTry) try {
1568
1713
  debugLog(`[well-known] requesting index: ${indexUrl}`);
1569
- const response = await fetch(indexUrl);
1714
+ const response = await fetchWithSelectiveTls(indexUrl);
1570
1715
  if (!response.ok) {
1571
1716
  debugLog(`[well-known] index response status: ${response.status} ${response.statusText}`);
1572
1717
  continue;
@@ -1650,7 +1795,7 @@ var WellKnownProvider = class {
1650
1795
  indexEntry: entry,
1651
1796
  installTarget
1652
1797
  };
1653
- const response = await fetch(installTarget.skillMdUrl);
1798
+ const response = await fetchWithSelectiveTls(installTarget.skillMdUrl);
1654
1799
  if (!response.ok) return null;
1655
1800
  const content = await response.text();
1656
1801
  const { data } = (0, import_gray_matter.default)(content);
@@ -1660,8 +1805,7 @@ var WellKnownProvider = class {
1660
1805
  try {
1661
1806
  const normalizedFilePath = filePath.replace(/^\/+/, "");
1662
1807
  const fileRelativePath = installTarget.skillRoot ? posix.join(installTarget.skillRoot, normalizedFilePath) : normalizedFilePath;
1663
- const fileUrl = this.buildUrl(this.resolveInlineBaseUrl(installTarget.repositoryUrl) || baseUrl, fileRelativePath);
1664
- const fileResponse = await fetch(fileUrl);
1808
+ const fileResponse = await fetchWithSelectiveTls(this.buildUrl(this.resolveInlineBaseUrl(installTarget.repositoryUrl) || baseUrl, fileRelativePath));
1665
1809
  if (fileResponse.ok) return {
1666
1810
  path: normalizedFilePath,
1667
1811
  content: await fileResponse.text()
@@ -1926,7 +2070,7 @@ function createEmptyLocalLock() {
1926
2070
  skills: {}
1927
2071
  };
1928
2072
  }
1929
- var version$1 = "1.4.8";
2073
+ var version$1 = "1.4.9";
1930
2074
  const isCancelled$1 = (value) => typeof value === "symbol";
1931
2075
  async function isSourcePrivate(source) {
1932
2076
  const ownerRepo = parseOwnerRepo(source);
@@ -2590,6 +2734,7 @@ async function runAdd(args, options = {}) {
2590
2734
  fullDepth: options.fullDepth
2591
2735
  });
2592
2736
  if (skills.length === 0) {
2737
+ debugLog(`[add] discoverSkills returned 0 skills for source=${parsed.type === "local" ? parsed.localPath : parsed.url} searchSubpath=${parsed.subpath ?? "(root)"} includeInternal=${includeInternal} fullDepth=${options.fullDepth === true}`);
2593
2738
  spinner.stop(import_picocolors.default.red("No skills found"));
2594
2739
  Se(import_picocolors.default.red("No valid skills found. Skills require a SKILL.md with name and description."));
2595
2740
  await cleanup(tempDir);
@@ -3145,7 +3290,7 @@ async function searchSkillsAPI(query) {
3145
3290
  try {
3146
3291
  const url = `${SEARCH_API_BASE}/api/search?q=${encodeURIComponent(query)}&limit=10`;
3147
3292
  debugLog(`[find] requesting search: ${url}`);
3148
- const res = await fetch(url);
3293
+ const res = await fetchWithSelectiveTls(url);
3149
3294
  debugLog(`[find] search response status: ${res.status} ${res.statusText}`);
3150
3295
  const responseText = await res.text();
3151
3296
  debugLog(`[find] search response body: ${responseText}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bip-skills",
3
- "version": "1.4.8",
3
+ "version": "1.4.9",
4
4
  "description": "The open agent skills ecosystem",
5
5
  "type": "module",
6
6
  "bin": {