nuxt-skill-hub 0.0.7 → 0.0.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/module.d.mts CHANGED
@@ -2,8 +2,8 @@ import * as _nuxt_schema from '@nuxt/schema';
2
2
 
3
3
  type SkillHubTarget = string;
4
4
 
5
- type SkillSourceKind = 'dist' | 'github' | 'generated';
6
- type SkillResolverKind = 'agentsField' | 'githubHeuristic' | 'metadataRouter';
5
+ type SkillSourceKind = 'dist' | 'github' | 'wellKnown' | 'generated';
6
+ type SkillResolverKind = 'agentsField' | 'githubHeuristic' | 'wellKnownV2' | 'metadataRouter';
7
7
 
8
8
  type SkillHubGenerationMode = 'prepare' | 'manual';
9
9
  interface ModuleOptions {
package/dist/module.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-skill-hub",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "configKey": "skillHub",
5
5
  "compatibility": {
6
6
  "nuxt": ">=4.3.0"
package/dist/module.mjs CHANGED
@@ -8,15 +8,16 @@ import matter from 'gray-matter';
8
8
  import { readPackageJSON, findWorkspaceDir, resolvePackageJSON, resolveLockfile } from 'pkg-types';
9
9
  import { transform } from 'automd';
10
10
  import { downloadTemplate } from 'giget';
11
- import { detectInstalledAgents, expandPath, detectCurrentAgent, getAgentIds, getAgentConfig } from 'unagent/env';
11
+ import { getAgentConfig, detectInstalledAgents, expandPath, detectCurrentAgent, getAgentIds } from 'unagent/env';
12
12
  import { ofetch } from 'ofetch';
13
13
  import { createHash } from 'node:crypto';
14
+ import { isIP } from 'node:net';
14
15
  import { hash } from 'ohash';
15
16
  import { glob } from 'tinyglobby';
16
17
  import { createConsola } from 'consola';
17
18
  import { colorize } from 'consola/utils';
18
19
 
19
- const version = "0.0.7";
20
+ const version = "0.0.8";
20
21
  const packageJson = {
21
22
  version: version};
22
23
 
@@ -146,6 +147,8 @@ function getSourceLabel(sourceKind) {
146
147
  return "Installed package skill";
147
148
  case "github":
148
149
  return "Resolved module skill";
150
+ case "wellKnown":
151
+ return "Docs-discovered skill";
149
152
  case "generated":
150
153
  return "Metadata-routed skill";
151
154
  }
@@ -206,8 +209,9 @@ function relativeModuleLink(entry, prefix) {
206
209
  function groupModuleEntries(entries) {
207
210
  const officialUpstream = entries.filter((entry) => entry.sourceKind === "dist");
208
211
  const githubResolved = entries.filter((entry) => entry.sourceKind === "github");
212
+ const wellKnownResolved = entries.filter((entry) => entry.sourceKind === "wellKnown");
209
213
  const metadataRouted = entries.filter((entry) => entry.sourceKind === "generated");
210
- return { officialUpstream, githubResolved, metadataRouted };
214
+ return { officialUpstream, githubResolved, wellKnownResolved, metadataRouted };
211
215
  }
212
216
  function hasModuleGuidance(entries, skipped = []) {
213
217
  return entries.length > 0 || skipped.length > 0;
@@ -330,6 +334,7 @@ function createSkillMapSections(metadata, entries, skipped = [], includeModuleAu
330
334
  const moduleSections = [
331
335
  renderModuleGroup("Official upstream skills", grouped.officialUpstream, "./references/modules/"),
332
336
  renderModuleGroup("Resolved module skills", grouped.githubResolved, "./references/modules/"),
337
+ renderModuleGroup("Docs-discovered skills", grouped.wellKnownResolved, "./references/modules/"),
333
338
  renderModuleGroup("Metadata-routed skills", grouped.metadataRouted, "./references/modules/"),
334
339
  renderSkippedEntries(skipped)
335
340
  ].filter(Boolean);
@@ -494,24 +499,15 @@ function resolveSkillsDir(target, config) {
494
499
  }
495
500
  return void 0;
496
501
  }
497
- function normalizeTarget(target) {
498
- return target.trim();
499
- }
500
- function getRawTargetConfig(target) {
501
- return getAgentConfig(target);
502
- }
503
502
  function getSupportedTargets() {
504
- return getAgentIds().filter((target) => {
505
- const config = getRawTargetConfig(target);
506
- return Boolean(resolveSkillsDir(target, config));
507
- }).sort((a, b) => a.localeCompare(b));
503
+ return getAgentIds().filter((target) => Boolean(resolveSkillsDir(target, getAgentConfig(target)))).sort((a, b) => a.localeCompare(b));
508
504
  }
509
505
  function resolveAgentTargetConfig(target) {
510
- const normalized = normalizeTarget(target);
506
+ const normalized = target.trim();
511
507
  if (!normalized) {
512
508
  return void 0;
513
509
  }
514
- const config = getRawTargetConfig(normalized);
510
+ const config = getAgentConfig(normalized);
515
511
  const skillsDir = resolveSkillsDir(normalized, config);
516
512
  if (!config || !skillsDir) {
517
513
  return void 0;
@@ -523,11 +519,11 @@ function resolveAgentTargetConfig(target) {
523
519
  };
524
520
  }
525
521
  function validateTargets(targets) {
526
- const uniqueTargets = Array.from(new Set(targets.map(normalizeTarget).filter(Boolean)));
522
+ const uniqueTargets = Array.from(new Set(targets.map((target) => target.trim()).filter(Boolean)));
527
523
  const valid = [];
528
524
  const invalid = [];
529
525
  for (const target of uniqueTargets) {
530
- const config = getRawTargetConfig(target);
526
+ const config = getAgentConfig(target);
531
527
  if (!config) {
532
528
  invalid.push({
533
529
  target,
@@ -562,6 +558,24 @@ const MANAGED_HINT_START = "<!-- nuxt-skill-hub:start -->";
562
558
  const MANAGED_HINT_END = "<!-- nuxt-skill-hub:end -->";
563
559
  const SKILL_NAME_MAX_LENGTH = 64;
564
560
  const SKILL_NAME_PATTERN = /^[a-z0-9-]+$/;
561
+ async function mapWithConcurrency(items, concurrency, mapper) {
562
+ if (!items.length) {
563
+ return [];
564
+ }
565
+ const results = [];
566
+ let nextIndex = 0;
567
+ const workers = Array.from({ length: Math.min(concurrency, items.length) }, async () => {
568
+ while (true) {
569
+ const index = nextIndex++;
570
+ if (index >= items.length) {
571
+ return;
572
+ }
573
+ results[index] = await mapper(items[index], index);
574
+ }
575
+ });
576
+ await Promise.all(workers);
577
+ return results;
578
+ }
565
579
  async function pathExists(path) {
566
580
  try {
567
581
  await promises.access(path);
@@ -746,6 +760,22 @@ async function discoverInstalledPackageFromDirectory(directory) {
746
760
  }
747
761
  return readInstalledPackageMetadata(packageJsonPath);
748
762
  }
763
+ async function collectInstalledModulePackages(modules, rootDir) {
764
+ const specifiers = Array.from(new Set(
765
+ (modules || []).map((entry) => extractModuleSpecifier(entry)).filter((entry) => Boolean(entry))
766
+ ));
767
+ const resolved = await Promise.all(
768
+ specifiers.map((specifier) => discoverInstalledPackageFromSpecifier(specifier, rootDir))
769
+ );
770
+ const seenPackageRoots = /* @__PURE__ */ new Set();
771
+ const packages = [];
772
+ for (const pkg of resolved) {
773
+ if (!pkg || seenPackageRoots.has(pkg.packageRoot)) continue;
774
+ seenPackageRoots.add(pkg.packageRoot);
775
+ packages.push(pkg);
776
+ }
777
+ return packages;
778
+ }
749
779
  async function readInstalledPackageMetadata(packageJsonPath, fallbackSpecifier) {
750
780
  const pkg = await readPackageJSON(packageJsonPath);
751
781
  return {
@@ -863,15 +893,15 @@ async function validateContribution(contribution) {
863
893
  };
864
894
  }
865
895
  async function validateResolvedContributions(contributions) {
896
+ const validations = await Promise.all(contributions.map(validateContribution));
866
897
  const valid = [];
867
898
  const issues = [];
868
- for (const contribution of contributions) {
869
- const validation = await validateContribution(contribution);
870
- if (!validation.issues.length) {
871
- valid.push(validation.contribution);
899
+ for (const validation of validations) {
900
+ if (validation.issues.length) {
901
+ issues.push(...validation.issues);
872
902
  continue;
873
903
  }
874
- issues.push(...validation.issues);
904
+ valid.push(validation.contribution);
875
905
  }
876
906
  return {
877
907
  contributions: sortAndDedupeContributions(valid),
@@ -1040,7 +1070,7 @@ function formatConflictWarning(conflict) {
1040
1070
  return `Found ${conflict.skillNames.length} standalone skill(s) that may conflict with nuxt-skill-hub: ${conflict.skillNames.join(", ")} (global: ${conflict.globalDir}). Remove with: npx skills remove --global`;
1041
1071
  }
1042
1072
 
1043
- const GITHUB_OVERRIDES = [
1073
+ const PACKAGE_OVERRIDES = [
1044
1074
  {
1045
1075
  packageName: "@nuxt/ui",
1046
1076
  repo: "nuxt/ui",
@@ -1054,10 +1084,15 @@ const GITHUB_OVERRIDES = [
1054
1084
  ref: "main",
1055
1085
  path: "skills/vueuse-functions",
1056
1086
  skillName: "vueuse-functions"
1087
+ },
1088
+ {
1089
+ packageName: "docus",
1090
+ repo: "nuxt-content/docus",
1091
+ docsUrls: ["https://docus.dev/"]
1057
1092
  }
1058
1093
  ];
1059
- function findGitHubOverride(packageName) {
1060
- return GITHUB_OVERRIDES.find((entry) => entry.packageName === packageName);
1094
+ function findPackageOverride(packageName) {
1095
+ return PACKAGE_OVERRIDES.find((entry) => entry.packageName === packageName);
1061
1096
  }
1062
1097
 
1063
1098
  const GITHUB_API_BASE = "https://api.github.com";
@@ -1065,6 +1100,8 @@ const UNGH_API_BASE = "https://ungh.cc";
1065
1100
  const githubDirectoryCache = /* @__PURE__ */ new Map();
1066
1101
  const githubDefaultBranchCache = /* @__PURE__ */ new Map();
1067
1102
  const githubFileTextCache = /* @__PURE__ */ new Map();
1103
+ const urlTextCache = /* @__PURE__ */ new Map();
1104
+ const urlBytesCache = /* @__PURE__ */ new Map();
1068
1105
  function githubFetchOptions(timeoutMs) {
1069
1106
  return {
1070
1107
  timeout: timeoutMs,
@@ -1074,6 +1111,9 @@ function githubFetchOptions(timeoutMs) {
1074
1111
  }
1075
1112
  };
1076
1113
  }
1114
+ function fetchStatus(error) {
1115
+ return typeof error === "object" && error && "status" in error ? Number(error.status) : void 0;
1116
+ }
1077
1117
  function encodeGitHubPath(path) {
1078
1118
  return path.split("/").filter(Boolean).map((segment) => encodeURIComponent(segment)).join("/");
1079
1119
  }
@@ -1180,16 +1220,378 @@ async function fetchGitHubFileText(repo, ref, filePath, timeoutMs) {
1180
1220
  );
1181
1221
  return { ok: true, data };
1182
1222
  } catch (error) {
1183
- const status = typeof error === "object" && error && "status" in error ? Number(error.status) : void 0;
1184
1223
  return {
1185
1224
  ok: false,
1186
- status,
1225
+ status: fetchStatus(error),
1226
+ error: error.message
1227
+ };
1228
+ }
1229
+ });
1230
+ }
1231
+ async function fetchUrlText(url, timeoutMs) {
1232
+ return await memoize(urlTextCache, url, async () => {
1233
+ try {
1234
+ const data = await ofetch(url, {
1235
+ ...githubFetchOptions(timeoutMs),
1236
+ responseType: "text"
1237
+ });
1238
+ return { ok: true, data };
1239
+ } catch (error) {
1240
+ return {
1241
+ ok: false,
1242
+ status: fetchStatus(error),
1243
+ error: error.message
1244
+ };
1245
+ }
1246
+ });
1247
+ }
1248
+ async function fetchUrlJson(url, timeoutMs) {
1249
+ const text = await fetchUrlText(url, timeoutMs);
1250
+ if (!text.ok) {
1251
+ return {
1252
+ ok: false,
1253
+ status: text.status,
1254
+ error: text.error
1255
+ };
1256
+ }
1257
+ try {
1258
+ return {
1259
+ ok: true,
1260
+ data: JSON.parse(text.data || "")
1261
+ };
1262
+ } catch (error) {
1263
+ return {
1264
+ ok: false,
1265
+ error: error.message
1266
+ };
1267
+ }
1268
+ }
1269
+ async function fetchUrlBytes(url, timeoutMs) {
1270
+ return await memoize(urlBytesCache, url, async () => {
1271
+ try {
1272
+ const data = await ofetch(url, {
1273
+ ...githubFetchOptions(timeoutMs),
1274
+ responseType: "arrayBuffer"
1275
+ });
1276
+ return {
1277
+ ok: true,
1278
+ data: new Uint8Array(data)
1279
+ };
1280
+ } catch (error) {
1281
+ return {
1282
+ ok: false,
1283
+ status: fetchStatus(error),
1187
1284
  error: error.message
1188
1285
  };
1189
1286
  }
1190
1287
  });
1191
1288
  }
1192
1289
 
1290
+ const WELL_KNOWN_DISCOVERY_V2_SCHEMA = "https://schemas.agentskills.io/discovery/0.2.0/schema.json";
1291
+ const WELL_KNOWN_DISCOVERY_V2_INDEX_PATH = "/.well-known/agent-skills/index.json";
1292
+ function makeSkip$1(packageName, skillName, reason) {
1293
+ return {
1294
+ packageName,
1295
+ skillName,
1296
+ reason,
1297
+ sourceKind: "wellKnown"
1298
+ };
1299
+ }
1300
+ function normalizeHttpUrl$1(url) {
1301
+ const trimmed = url?.trim();
1302
+ if (!trimmed) {
1303
+ return void 0;
1304
+ }
1305
+ try {
1306
+ const parsed = new URL(trimmed);
1307
+ return parsed.protocol === "http:" || parsed.protocol === "https:" ? parsed.toString() : void 0;
1308
+ } catch {
1309
+ return void 0;
1310
+ }
1311
+ }
1312
+ function normalizeHostname(hostname) {
1313
+ return hostname.trim().toLowerCase().replace(/^\[|\]$/g, "");
1314
+ }
1315
+ function parseIpv4(hostname) {
1316
+ const parts = hostname.split(".");
1317
+ if (parts.length !== 4) {
1318
+ return null;
1319
+ }
1320
+ const octets = parts.map((part) => {
1321
+ if (!/^\d+$/.test(part)) {
1322
+ return Number.NaN;
1323
+ }
1324
+ return Number(part);
1325
+ });
1326
+ if (octets.some((octet) => !Number.isInteger(octet) || octet < 0 || octet > 255)) {
1327
+ return null;
1328
+ }
1329
+ return octets;
1330
+ }
1331
+ function isPrivateIpv4(hostname) {
1332
+ const octets = parseIpv4(normalizeHostname(hostname));
1333
+ if (!octets) {
1334
+ return false;
1335
+ }
1336
+ const [a, b] = octets;
1337
+ return a === 0 || a === 10 || a === 127 || a === 169 && b === 254 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a >= 224 && a <= 239 || a >= 240;
1338
+ }
1339
+ function isPrivateIpv6(hostname) {
1340
+ const normalized = normalizeHostname(hostname);
1341
+ return normalized === "::" || normalized === "::1" || normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("fe8") || normalized.startsWith("fe9") || normalized.startsWith("fea") || normalized.startsWith("feb");
1342
+ }
1343
+ function isUnsafeHostname(hostname) {
1344
+ const normalized = normalizeHostname(hostname);
1345
+ if (normalized === "localhost" || normalized.endsWith(".localhost")) {
1346
+ return true;
1347
+ }
1348
+ const ipVersion = isIP(normalized);
1349
+ if (ipVersion === 4) {
1350
+ return isPrivateIpv4(normalized);
1351
+ }
1352
+ if (ipVersion === 6) {
1353
+ return isPrivateIpv6(normalized);
1354
+ }
1355
+ return false;
1356
+ }
1357
+ function isPublicDiscoveryBase(url) {
1358
+ try {
1359
+ const parsed = new URL(url);
1360
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:" || parsed.hostname === "github.com" || parseGitHubRepo(url) || isUnsafeHostname(parsed.hostname)) {
1361
+ return false;
1362
+ }
1363
+ return true;
1364
+ } catch {
1365
+ return false;
1366
+ }
1367
+ }
1368
+ function isSameOriginUrl(url, baseUrl) {
1369
+ return new URL(url).origin === new URL(baseUrl).origin;
1370
+ }
1371
+ function resolveSameOriginUrl(rawUrl, baseUrl) {
1372
+ const trimmed = rawUrl.trim();
1373
+ if (!trimmed) {
1374
+ return null;
1375
+ }
1376
+ try {
1377
+ const resolved = new URL(trimmed, baseUrl);
1378
+ if (!isSameOriginUrl(resolved.toString(), baseUrl) || isUnsafeHostname(resolved.hostname)) {
1379
+ return null;
1380
+ }
1381
+ return resolved.toString();
1382
+ } catch {
1383
+ return null;
1384
+ }
1385
+ }
1386
+ function docsBaseCandidates(packageInfo) {
1387
+ const override = findPackageOverride(packageInfo.packageName);
1388
+ const candidates = [
1389
+ ...override?.docsUrls || [],
1390
+ packageInfo.homepage || ""
1391
+ ];
1392
+ return [...new Set(candidates.map(normalizeHttpUrl$1).filter((url) => Boolean(url)).filter(isPublicDiscoveryBase))];
1393
+ }
1394
+ function normalizeDigest(value) {
1395
+ if (typeof value !== "string") {
1396
+ return null;
1397
+ }
1398
+ const trimmed = value.trim();
1399
+ return /^sha256:[a-f0-9]{64}$/.test(trimmed) ? trimmed : null;
1400
+ }
1401
+ function sha256(bytes) {
1402
+ return `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
1403
+ }
1404
+ async function writeSkillFile(root, relativePath, bytes) {
1405
+ const destination = join(root, relativePath);
1406
+ await ensureDir(dirname(destination));
1407
+ await promises.writeFile(destination, bytes);
1408
+ }
1409
+ function contributionFor(input) {
1410
+ const sourceRepo = parseGitHubRepo(input.packageInfo.repository);
1411
+ return normalizeContribution({
1412
+ packageName: input.packageInfo.packageName,
1413
+ version: input.packageInfo.version,
1414
+ skillName: input.skillName,
1415
+ description: input.description,
1416
+ sourceKind: "wellKnown",
1417
+ sourceRepo: sourceRepo || void 0,
1418
+ sourcePath: input.indexUrl,
1419
+ repoUrl: sourceRepo ? `https://github.com/${sourceRepo}` : void 0,
1420
+ docsUrl: input.docsUrl,
1421
+ official: true,
1422
+ resolver: input.resolver,
1423
+ forceIncludeScripts: true
1424
+ }, input.targetDir, join(input.targetDir, ".."));
1425
+ }
1426
+ async function materializeV2Skill(input) {
1427
+ const skillName = typeof input.entry.name === "string" ? input.entry.name.trim() : "";
1428
+ if (!isValidSkillName(skillName)) {
1429
+ return { skipped: makeSkip$1(input.packageInfo.packageName, skillName || "entry", "well-known v2 skill name is invalid") };
1430
+ }
1431
+ const description = typeof input.entry.description === "string" ? input.entry.description.trim() : "";
1432
+ if (!description) {
1433
+ return { skipped: makeSkip$1(input.packageInfo.packageName, skillName, "well-known v2 skill is missing a description") };
1434
+ }
1435
+ const type = typeof input.entry.type === "string" ? input.entry.type.trim() : "";
1436
+ if (type === "archive") {
1437
+ return { skipped: makeSkip$1(input.packageInfo.packageName, skillName, "archive artifacts are not supported yet") };
1438
+ }
1439
+ if (type !== "skill-md") {
1440
+ return { skipped: makeSkip$1(input.packageInfo.packageName, skillName, `unsupported well-known v2 skill type "${type || "unknown"}"`) };
1441
+ }
1442
+ if (typeof input.entry.url !== "string" || !input.entry.url.trim()) {
1443
+ return { skipped: makeSkip$1(input.packageInfo.packageName, skillName, "well-known v2 skill is missing a URL") };
1444
+ }
1445
+ const digest = normalizeDigest(input.entry.digest);
1446
+ if (!digest) {
1447
+ return { skipped: makeSkip$1(input.packageInfo.packageName, skillName, "well-known v2 skill is missing a valid sha256 digest") };
1448
+ }
1449
+ const skillUrl = resolveSameOriginUrl(input.entry.url, input.indexUrl);
1450
+ if (!skillUrl) {
1451
+ return { skipped: makeSkip$1(input.packageInfo.packageName, skillName, "well-known v2 skill URL must stay on the discovery origin") };
1452
+ }
1453
+ const response = await fetchUrlBytes(skillUrl, input.timeoutMs);
1454
+ if (!response.ok || !response.data) {
1455
+ return { skipped: makeSkip$1(input.packageInfo.packageName, skillName, "failed to fetch well-known v2 skill artifact") };
1456
+ }
1457
+ if (sha256(response.data) !== digest) {
1458
+ return { skipped: makeSkip$1(input.packageInfo.packageName, skillName, "well-known v2 skill digest mismatch") };
1459
+ }
1460
+ const origin = new URL(input.docsUrl).origin;
1461
+ const targetDir = join(
1462
+ input.cacheRoot,
1463
+ "well-known",
1464
+ "rfc",
1465
+ sanitizeSegment(origin),
1466
+ sanitizeSegment(input.packageInfo.packageName),
1467
+ sanitizeSegment(input.packageInfo.version || "unknown"),
1468
+ sanitizeSegment(skillName)
1469
+ );
1470
+ await emptyDir(targetDir);
1471
+ await writeSkillFile(targetDir, "SKILL.md", response.data);
1472
+ return {
1473
+ contribution: contributionFor({
1474
+ packageInfo: input.packageInfo,
1475
+ targetDir,
1476
+ skillName,
1477
+ description,
1478
+ docsUrl: input.docsUrl,
1479
+ resolver: "wellKnownV2",
1480
+ indexUrl: input.indexUrl
1481
+ })
1482
+ };
1483
+ }
1484
+ async function resolveV2Index(input) {
1485
+ const skipped = [];
1486
+ const contributions = [];
1487
+ if (!Array.isArray(input.index.skills)) {
1488
+ return {
1489
+ contributions: [],
1490
+ issues: [],
1491
+ skipped: [makeSkip$1(input.packageInfo.packageName, input.packageInfo.packageName, "well-known v2 index is missing a skills array")]
1492
+ };
1493
+ }
1494
+ for (const rawEntry of input.index.skills) {
1495
+ if (!rawEntry || typeof rawEntry !== "object") {
1496
+ skipped.push(makeSkip$1(input.packageInfo.packageName, "entry", "well-known v2 skill entry must be an object"));
1497
+ continue;
1498
+ }
1499
+ const materialized = await materializeV2Skill({
1500
+ packageInfo: input.packageInfo,
1501
+ entry: rawEntry,
1502
+ indexUrl: input.indexUrl,
1503
+ docsUrl: input.docsUrl,
1504
+ cacheRoot: input.cacheRoot,
1505
+ timeoutMs: input.timeoutMs
1506
+ });
1507
+ if (materialized?.contribution) {
1508
+ contributions.push(materialized.contribution);
1509
+ }
1510
+ if (materialized?.skipped) {
1511
+ skipped.push(materialized.skipped);
1512
+ }
1513
+ }
1514
+ return { contributions, issues: [], skipped };
1515
+ }
1516
+ async function resolveIndex(input) {
1517
+ const response = await fetchUrlJson(input.indexUrl, input.timeoutMs);
1518
+ if (!response.ok) {
1519
+ if (response.status === 404) {
1520
+ return null;
1521
+ }
1522
+ return {
1523
+ contributions: [],
1524
+ issues: [],
1525
+ skipped: [makeSkip$1(input.packageInfo.packageName, input.packageInfo.packageName, `failed to fetch well-known index ${input.indexUrl}`)]
1526
+ };
1527
+ }
1528
+ const index = response.data;
1529
+ if (!index || typeof index !== "object") {
1530
+ return {
1531
+ contributions: [],
1532
+ issues: [],
1533
+ skipped: [makeSkip$1(input.packageInfo.packageName, input.packageInfo.packageName, "well-known index must be an object")]
1534
+ };
1535
+ }
1536
+ if (index.$schema === WELL_KNOWN_DISCOVERY_V2_SCHEMA) {
1537
+ return await resolveV2Index({
1538
+ ...input,
1539
+ index
1540
+ });
1541
+ }
1542
+ if (typeof index.$schema === "string" && index.$schema) {
1543
+ return {
1544
+ contributions: [],
1545
+ issues: [],
1546
+ skipped: [makeSkip$1(input.packageInfo.packageName, input.packageInfo.packageName, `unsupported well-known schema "${index.$schema}"`)]
1547
+ };
1548
+ }
1549
+ return {
1550
+ contributions: [],
1551
+ issues: [],
1552
+ skipped: [makeSkip$1(input.packageInfo.packageName, input.packageInfo.packageName, "well-known index is missing the v2 schema")]
1553
+ };
1554
+ }
1555
+ async function resolveViaWellKnown(packageInfo, cacheRoot, timeoutMs) {
1556
+ const bases = docsBaseCandidates(packageInfo);
1557
+ const skipped = [];
1558
+ const issues = [];
1559
+ for (const docsUrl of bases) {
1560
+ const indexUrl = new URL(WELL_KNOWN_DISCOVERY_V2_INDEX_PATH, docsUrl).toString();
1561
+ const result = await resolveIndex({
1562
+ packageInfo,
1563
+ indexUrl,
1564
+ docsUrl,
1565
+ cacheRoot,
1566
+ timeoutMs
1567
+ });
1568
+ if (!result) {
1569
+ continue;
1570
+ }
1571
+ issues.push(...result.issues);
1572
+ skipped.push(...result.skipped);
1573
+ if (result.contributions.length) {
1574
+ return {
1575
+ contributions: result.contributions,
1576
+ issues,
1577
+ skipped
1578
+ };
1579
+ }
1580
+ }
1581
+ if (!bases.length) {
1582
+ return {
1583
+ contributions: [],
1584
+ issues: [],
1585
+ skipped: []
1586
+ };
1587
+ }
1588
+ return {
1589
+ contributions: [],
1590
+ issues,
1591
+ skipped
1592
+ };
1593
+ }
1594
+
1193
1595
  function makeSkip(packageName, skillName, reason, sourceKind) {
1194
1596
  return {
1195
1597
  packageName,
@@ -1231,8 +1633,14 @@ function normalizeHttpUrl(url) {
1231
1633
  return void 0;
1232
1634
  }
1233
1635
  }
1636
+ function resolveDocsUrl(packageInfo) {
1637
+ const override = findPackageOverride(packageInfo.packageName);
1638
+ const overrideDocsUrl = override?.docsUrls?.map(normalizeHttpUrl).find(Boolean);
1639
+ return overrideDocsUrl || normalizeHttpUrl(packageInfo.homepage);
1640
+ }
1234
1641
  function resolveRepositoryUrl(packageInfo) {
1235
- const sourceRepo = parseGitHubRepo(packageInfo.repository) || parseGitHubRepo(packageInfo.homepage);
1642
+ const override = findPackageOverride(packageInfo.packageName);
1643
+ const sourceRepo = override?.repo || parseGitHubRepo(packageInfo.repository) || parseGitHubRepo(packageInfo.homepage);
1236
1644
  if (sourceRepo) {
1237
1645
  return {
1238
1646
  sourceRepo,
@@ -1304,18 +1712,9 @@ async function materializeCandidate(packageInfo, candidate, cacheRoot) {
1304
1712
  forceIncludeScripts: true
1305
1713
  }, targetDir, join(targetDir, ".."));
1306
1714
  }
1307
- async function resolveViaGitHub(packageInfo, cacheRoot, timeoutMs) {
1308
- const issues = [];
1309
- const skipped = [];
1310
- const override = findGitHubOverride(packageInfo.packageName);
1715
+ function createGitHubResolveContext(packageInfo) {
1716
+ const override = findPackageOverride(packageInfo.packageName);
1311
1717
  const repo = override?.repo || parseGitHubRepo(packageInfo.repository) || parseGitHubRepo(packageInfo.homepage);
1312
- if (!repo) {
1313
- return {
1314
- contributions: [],
1315
- issues,
1316
- skipped: [makeSkip(packageInfo.packageName, override?.skillName || packageInfo.packageName, "No GitHub repository metadata found", "github")]
1317
- };
1318
- }
1319
1718
  const refs = dedupe([
1320
1719
  override?.ref || "",
1321
1720
  packageInfo.version ? `v${packageInfo.version}` : "",
@@ -1335,35 +1734,89 @@ async function resolveViaGitHub(packageInfo, cacheRoot, timeoutMs) {
1335
1734
  ];
1336
1735
  })
1337
1736
  ]);
1338
- const tryResolveForRef = async (ref, allowTreeLookup) => {
1339
- const packageJson = await fetchGitHubFileText(repo, ref, "package.json", timeoutMs);
1340
- if (packageJson.ok && packageJson.data) {
1341
- try {
1342
- const parsed = JSON.parse(packageJson.data);
1343
- const remoteSkills = parseAgentSkillDeclarations(parsed, packageInfo.packageName, "github");
1344
- issues.push(...remoteSkills.issues);
1345
- for (const skill of remoteSkills.skills) {
1346
- const resolved = await materializeCandidate(packageInfo, {
1347
- skillName: skill.name,
1348
- sourcePath: skill.path,
1349
- sourceKind: "github",
1350
- sourceRepo: repo,
1351
- sourceRef: ref,
1352
- official: true,
1353
- resolver: "agentsField"
1354
- }, cacheRoot);
1355
- if (resolved) {
1356
- return [resolved];
1357
- }
1358
- }
1359
- } catch {
1360
- issues.push(createValidationIssue(packageInfo.packageName, packageInfo.packageName, "Failed to parse remote package.json", "github"));
1737
+ return {
1738
+ issues: [],
1739
+ skipped: [],
1740
+ override,
1741
+ repo: repo || void 0,
1742
+ refs,
1743
+ pathCandidates
1744
+ };
1745
+ }
1746
+ async function resolveAgentSkillsForRef(packageInfo, context, ref, cacheRoot, timeoutMs) {
1747
+ if (!context.repo) {
1748
+ return null;
1749
+ }
1750
+ const packageJson = await fetchGitHubFileText(context.repo, ref, "package.json", timeoutMs);
1751
+ if (!packageJson.ok || !packageJson.data) {
1752
+ return null;
1753
+ }
1754
+ try {
1755
+ const parsed = JSON.parse(packageJson.data);
1756
+ const remoteSkills = parseAgentSkillDeclarations(parsed, packageInfo.packageName, "github");
1757
+ context.issues.push(...remoteSkills.issues);
1758
+ for (const skill of remoteSkills.skills) {
1759
+ const resolved = await materializeCandidate(packageInfo, {
1760
+ skillName: skill.name,
1761
+ sourcePath: skill.path,
1762
+ sourceKind: "github",
1763
+ sourceRepo: context.repo,
1764
+ sourceRef: ref,
1765
+ official: true,
1766
+ resolver: "agentsField"
1767
+ }, cacheRoot);
1768
+ if (resolved) {
1769
+ return [resolved];
1361
1770
  }
1362
1771
  }
1363
- if (!allowTreeLookup) {
1364
- return null;
1772
+ } catch {
1773
+ context.issues.push(createValidationIssue(packageInfo.packageName, packageInfo.packageName, "Failed to parse remote package.json", "github"));
1774
+ }
1775
+ return null;
1776
+ }
1777
+ async function resolveViaGitHubAgents(packageInfo, cacheRoot, timeoutMs) {
1778
+ const context = createGitHubResolveContext(packageInfo);
1779
+ if (!context.repo) {
1780
+ return {
1781
+ contributions: [],
1782
+ issues: context.issues,
1783
+ skipped: []
1784
+ };
1785
+ }
1786
+ for (const ref of context.refs) {
1787
+ const resolved = await resolveAgentSkillsForRef(packageInfo, context, ref, cacheRoot, timeoutMs);
1788
+ if (resolved?.length) {
1789
+ return { contributions: resolved, issues: context.issues, skipped: context.skipped };
1790
+ }
1791
+ }
1792
+ const fallbackRefs = dedupe([
1793
+ await fetchGitHubDefaultBranch(context.repo, timeoutMs) || "",
1794
+ "main",
1795
+ "master"
1796
+ ]);
1797
+ for (const ref of fallbackRefs) {
1798
+ const resolved = await resolveAgentSkillsForRef(packageInfo, context, ref, cacheRoot, timeoutMs);
1799
+ if (resolved?.length) {
1800
+ return { contributions: resolved, issues: context.issues, skipped: context.skipped };
1365
1801
  }
1366
- const skillsDirs = await listGitHubDirectory(repo, ref, "skills", timeoutMs);
1802
+ }
1803
+ return {
1804
+ contributions: [],
1805
+ issues: context.issues,
1806
+ skipped: context.skipped
1807
+ };
1808
+ }
1809
+ async function resolveViaGitHubHeuristics(packageInfo, cacheRoot, timeoutMs) {
1810
+ const context = createGitHubResolveContext(packageInfo);
1811
+ if (!context.repo) {
1812
+ return {
1813
+ contributions: [],
1814
+ issues: context.issues,
1815
+ skipped: [makeSkip(packageInfo.packageName, context.override?.skillName || packageInfo.packageName, "No GitHub repository metadata found", "github")]
1816
+ };
1817
+ }
1818
+ const tryResolveForRef = async (ref, allowTreeLookup) => {
1819
+ const skillsDirs = await listGitHubDirectory(context.repo, ref, "skills", timeoutMs) ;
1367
1820
  if (skillsDirs.length) {
1368
1821
  const contributions = [];
1369
1822
  for (const skillName of skillsDirs) {
@@ -1371,7 +1824,7 @@ async function resolveViaGitHub(packageInfo, cacheRoot, timeoutMs) {
1371
1824
  skillName,
1372
1825
  sourcePath: `skills/${skillName}`,
1373
1826
  sourceKind: "github",
1374
- sourceRepo: repo,
1827
+ sourceRepo: context.repo,
1375
1828
  sourceRef: ref,
1376
1829
  official: true,
1377
1830
  resolver: "githubHeuristic"
@@ -1384,13 +1837,13 @@ async function resolveViaGitHub(packageInfo, cacheRoot, timeoutMs) {
1384
1837
  return contributions;
1385
1838
  }
1386
1839
  }
1387
- for (const path of pathCandidates) {
1388
- const candidateSkillName = override?.path && path === override.path ? override.skillName || path.split("/").pop() || packageInfo.packageName : path.split("/").pop() || packageInfo.packageName;
1840
+ for (const path of context.pathCandidates) {
1841
+ const candidateSkillName = context.override?.path && path === context.override.path ? context.override.skillName || path.split("/").pop() || packageInfo.packageName : path.split("/").pop() || packageInfo.packageName;
1389
1842
  const resolved = await materializeCandidate(packageInfo, {
1390
1843
  skillName: candidateSkillName,
1391
1844
  sourcePath: path,
1392
1845
  sourceKind: "github",
1393
- sourceRepo: repo,
1846
+ sourceRepo: context.repo,
1394
1847
  sourceRef: ref,
1395
1848
  official: true,
1396
1849
  resolver: "githubHeuristic"
@@ -1401,39 +1854,33 @@ async function resolveViaGitHub(packageInfo, cacheRoot, timeoutMs) {
1401
1854
  }
1402
1855
  return null;
1403
1856
  };
1404
- for (const ref of refs) {
1405
- const resolved = await tryResolveForRef(ref, false);
1406
- if (resolved?.length) {
1407
- return { contributions: resolved, issues, skipped };
1408
- }
1409
- }
1410
1857
  const fallbackRefs = dedupe([
1411
- await fetchGitHubDefaultBranch(repo, timeoutMs) || "",
1858
+ await fetchGitHubDefaultBranch(context.repo, timeoutMs) || "",
1412
1859
  "main",
1413
1860
  "master"
1414
1861
  ]);
1415
1862
  for (const ref of fallbackRefs) {
1416
- const resolved = await tryResolveForRef(ref, true);
1863
+ const resolved = await tryResolveForRef(ref);
1417
1864
  if (resolved?.length) {
1418
- return { contributions: resolved, issues, skipped };
1865
+ return { contributions: resolved, issues: context.issues, skipped: context.skipped };
1419
1866
  }
1420
1867
  }
1421
- for (const ref of refs) {
1422
- const resolved = await tryResolveForRef(ref, true);
1868
+ for (const ref of context.refs) {
1869
+ const resolved = await tryResolveForRef(ref);
1423
1870
  if (resolved?.length) {
1424
- return { contributions: resolved, issues, skipped };
1871
+ return { contributions: resolved, issues: context.issues, skipped: context.skipped };
1425
1872
  }
1426
1873
  }
1427
- skipped.push(makeSkip(packageInfo.packageName, override?.skillName || packageInfo.packageName, "No GitHub skill found using configured refs and path heuristics", "github"));
1874
+ context.skipped.push(makeSkip(packageInfo.packageName, context.override?.skillName || packageInfo.packageName, "No GitHub skill found using configured refs and path heuristics", "github"));
1428
1875
  return {
1429
1876
  contributions: [],
1430
- issues,
1431
- skipped
1877
+ issues: context.issues,
1878
+ skipped: context.skipped
1432
1879
  };
1433
1880
  }
1434
1881
  async function resolveViaMetadataRouter(packageInfo, cacheRoot) {
1435
1882
  const { repoUrl, sourceRepo } = resolveRepositoryUrl(packageInfo);
1436
- const docsUrl = normalizeHttpUrl(packageInfo.homepage);
1883
+ const docsUrl = resolveDocsUrl(packageInfo);
1437
1884
  if (!repoUrl && !docsUrl) {
1438
1885
  return {
1439
1886
  contributions: [],
@@ -1505,26 +1952,46 @@ async function resolveViaMetadataRouter(packageInfo, cacheRoot) {
1505
1952
  };
1506
1953
  }
1507
1954
  async function resolveRemoteContributionsForPackage(packageInfo, options) {
1508
- const githubResult = options.enableGithubLookup ? await resolveViaGitHub(packageInfo, options.cacheRoot, options.githubLookupTimeoutMs) : {
1955
+ const githubAgentsResult = options.enableGithubLookup ? await resolveViaGitHubAgents(packageInfo, options.cacheRoot, options.githubLookupTimeoutMs) : {
1509
1956
  contributions: [],
1510
1957
  issues: [],
1511
1958
  skipped: []
1512
1959
  };
1513
- if (githubResult.contributions.length) {
1514
- return githubResult;
1960
+ if (githubAgentsResult.contributions.length) {
1961
+ return githubAgentsResult;
1962
+ }
1963
+ const wellKnownResult = await resolveViaWellKnown(packageInfo, options.cacheRoot, options.githubLookupTimeoutMs);
1964
+ if (wellKnownResult.contributions.length) {
1965
+ return {
1966
+ contributions: wellKnownResult.contributions,
1967
+ issues: [...githubAgentsResult.issues, ...wellKnownResult.issues],
1968
+ skipped: wellKnownResult.skipped
1969
+ };
1970
+ }
1971
+ const githubHeuristicResult = options.enableGithubLookup ? await resolveViaGitHubHeuristics(packageInfo, options.cacheRoot, options.githubLookupTimeoutMs) : {
1972
+ contributions: [],
1973
+ issues: [],
1974
+ skipped: []
1975
+ };
1976
+ if (githubHeuristicResult.contributions.length) {
1977
+ return {
1978
+ contributions: githubHeuristicResult.contributions,
1979
+ issues: [...githubAgentsResult.issues, ...wellKnownResult.issues, ...githubHeuristicResult.issues],
1980
+ skipped: wellKnownResult.skipped
1981
+ };
1515
1982
  }
1516
1983
  const generatedResult = await resolveViaMetadataRouter(packageInfo, options.cacheRoot);
1517
1984
  if (generatedResult.contributions.length) {
1518
1985
  return {
1519
1986
  contributions: generatedResult.contributions,
1520
- issues: [...githubResult.issues, ...generatedResult.issues],
1521
- skipped: generatedResult.skipped
1987
+ issues: [...githubAgentsResult.issues, ...wellKnownResult.issues, ...githubHeuristicResult.issues, ...generatedResult.issues],
1988
+ skipped: [...wellKnownResult.skipped, ...generatedResult.skipped]
1522
1989
  };
1523
1990
  }
1524
1991
  return {
1525
1992
  contributions: [],
1526
- issues: [...githubResult.issues, ...generatedResult.issues],
1527
- skipped: [...githubResult.skipped, ...generatedResult.skipped]
1993
+ issues: [...githubAgentsResult.issues, ...wellKnownResult.issues, ...githubHeuristicResult.issues, ...generatedResult.issues],
1994
+ skipped: [...wellKnownResult.skipped, ...githubHeuristicResult.skipped, ...generatedResult.skipped]
1528
1995
  };
1529
1996
  }
1530
1997
 
@@ -1582,6 +2049,7 @@ async function hashFileIfExists(path) {
1582
2049
  }
1583
2050
  }
1584
2051
 
2052
+ const HASH_READ_CONCURRENCY = 16;
1585
2053
  async function resolveManualContribution(rootDir, contribution) {
1586
2054
  const sourceDir = isAbsolute(contribution.sourceDir) ? contribution.sourceDir : resolve(rootDir, contribution.sourceDir);
1587
2055
  return normalizeContribution(contribution, sourceDir, sourceDir);
@@ -1633,8 +2101,6 @@ async function hashDirectoryTreeIfExists(rootPath) {
1633
2101
  if (!rootStat.isDirectory()) {
1634
2102
  return null;
1635
2103
  }
1636
- const hash = createHash("sha256");
1637
- hash.update("dir:.\n");
1638
2104
  const entries = await glob("**/*", {
1639
2105
  cwd: rootPath,
1640
2106
  dot: true,
@@ -1643,30 +2109,20 @@ async function hashDirectoryTreeIfExists(rootPath) {
1643
2109
  onlyFiles: false
1644
2110
  });
1645
2111
  entries.sort((a, b) => a.localeCompare(b));
1646
- for (const entry of entries) {
2112
+ const chunkGroups = await mapWithConcurrency(entries, HASH_READ_CONCURRENCY, async (entry) => {
1647
2113
  const currentPath = join(rootPath, entry);
1648
2114
  const stat = await promises.lstat(currentPath);
1649
- const relativePath = relative(rootPath, currentPath).replaceAll("\\", "/");
1650
- if (stat.isSymbolicLink()) {
1651
- hash.update(`symlink:${relativePath}:`);
1652
- hash.update(await promises.readlink(currentPath));
1653
- hash.update("\n");
1654
- continue;
1655
- }
1656
- if (stat.isDirectory()) {
1657
- hash.update(`dir:${relativePath}
1658
- `);
1659
- continue;
1660
- }
1661
- if (stat.isFile()) {
1662
- hash.update(`file:${relativePath}:`);
1663
- hash.update(await promises.readFile(currentPath));
1664
- hash.update("\n");
1665
- continue;
1666
- }
1667
- hash.update(`other:${relativePath}:${stat.mode}
1668
- `);
1669
- }
2115
+ const rel = relative(rootPath, currentPath).replaceAll("\\", "/");
2116
+ if (stat.isSymbolicLink()) return [`symlink:${rel}:`, await promises.readlink(currentPath), "\n"];
2117
+ if (stat.isDirectory()) return [`dir:${rel}
2118
+ `];
2119
+ if (stat.isFile()) return [`file:${rel}:`, await promises.readFile(currentPath), "\n"];
2120
+ return [`other:${rel}:${stat.mode}
2121
+ `];
2122
+ });
2123
+ const hash = createHash("sha256");
2124
+ hash.update("dir:.\n");
2125
+ for (const chunks of chunkGroups) for (const chunk of chunks) hash.update(chunk);
1670
2126
  return hash.digest("hex");
1671
2127
  }
1672
2128
 
@@ -1727,24 +2183,6 @@ function getPersistentCacheRoot(exportRoot) {
1727
2183
  function getCachedGeneratedSkillRoot(cacheRoot, skillName) {
1728
2184
  return join(cacheRoot, "generated", skillName);
1729
2185
  }
1730
- async function mapWithConcurrency(items, concurrency, mapper) {
1731
- if (!items.length) {
1732
- return [];
1733
- }
1734
- const results = [];
1735
- let nextIndex = 0;
1736
- const workers = Array.from({ length: Math.min(concurrency, items.length) }, async () => {
1737
- while (true) {
1738
- const index = nextIndex++;
1739
- if (index >= items.length) {
1740
- return;
1741
- }
1742
- results[index] = await mapper(items[index], index);
1743
- }
1744
- });
1745
- await Promise.all(workers);
1746
- return results;
1747
- }
1748
2186
  function mergeSkipped(entries, keyFn) {
1749
2187
  const byKey = /* @__PURE__ */ new Map();
1750
2188
  for (const entry of entries) {
@@ -1781,17 +2219,13 @@ async function collectInstalledPackages(nuxt) {
1781
2219
  discoveries.push(discovered);
1782
2220
  }
1783
2221
  };
1784
- const moduleSpecifiers = (nuxt.options.modules || []).map((entry) => extractModuleSpecifier(entry)).filter((entry) => Boolean(entry));
1785
- const seenSpecifiers = Array.from(new Set(moduleSpecifiers));
1786
- for (const specifier of seenSpecifiers) {
1787
- addInstalledPackage(await discoverInstalledPackageFromSpecifier(specifier, nuxt.options.rootDir));
1788
- }
2222
+ const modulePackages = await collectInstalledModulePackages(nuxt.options.modules, nuxt.options.rootDir);
2223
+ for (const pkg of modulePackages) addInstalledPackage(pkg);
1789
2224
  const layerDirectories = Array.from(new Set(
1790
2225
  nuxt.options._layers.map((layer) => resolve(layer.cwd || layer.config.rootDir || "")).filter((layerDir) => layerDir && layerDir !== resolve(nuxt.options.rootDir))
1791
2226
  ));
1792
- for (const layerDirectory of layerDirectories) {
1793
- addInstalledPackage(await discoverInstalledPackageFromDirectory(layerDirectory));
1794
- }
2227
+ const layerPackages = await Promise.all(layerDirectories.map((dir) => discoverInstalledPackageFromDirectory(dir)));
2228
+ for (const pkg of layerPackages) addInstalledPackage(pkg);
1795
2229
  const localPackageSkills = await discoverLocalPackageSkills(nuxt.options.rootDir);
1796
2230
  if (localPackageSkills) {
1797
2231
  discoveries.push(localPackageSkills);
@@ -1971,23 +2405,14 @@ async function generateSkillTree(input) {
1971
2405
  function isCancelled(value) {
1972
2406
  return value === null || typeof value === "symbol";
1973
2407
  }
1974
- const AI_AGENT_TARGET_MAP = {
1975
- codex: "codex",
1976
- claude: "claude-code",
1977
- gemini: "gemini-code-assist",
1978
- replit: "replit-agent"
1979
- };
1980
- function resolveAIGuidanceTarget(currentTarget, detectedAgent) {
1981
- if (currentTarget) {
1982
- return currentTarget;
1983
- }
1984
- if (!detectedAgent) {
1985
- return void 0;
1986
- }
1987
- return AI_AGENT_TARGET_MAP[detectedAgent];
1988
- }
1989
2408
  function buildAIGuidance(currentTarget, detectedAgent) {
1990
- const target = resolveAIGuidanceTarget(currentTarget, detectedAgent);
2409
+ const agentTargetMap = {
2410
+ codex: "codex",
2411
+ claude: "claude-code",
2412
+ gemini: "gemini-code-assist",
2413
+ replit: "replit-agent"
2414
+ };
2415
+ const target = currentTarget || (detectedAgent ? agentTargetMap[detectedAgent] : void 0);
1991
2416
  const sourceLabel = detectedAgent ? ` (${detectedAgent})` : "";
1992
2417
  const snippetLines = [
1993
2418
  "export default defineNuxtConfig({",
@@ -2068,15 +2493,7 @@ Let's configure AI agent skills for your Nuxt project.`
2068
2493
  }
2069
2494
  }
2070
2495
  }
2071
- const moduleSpecifiers = (nuxt.options.modules || []).map((entry) => extractModuleSpecifier(entry)).filter((entry) => Boolean(entry));
2072
- const uniqueSpecifiers = Array.from(new Set(moduleSpecifiers));
2073
- const installedModulePackages = [];
2074
- for (const specifier of uniqueSpecifiers) {
2075
- const pkg = await discoverInstalledPackageFromSpecifier(specifier, rootDir);
2076
- if (!pkg || pkg.packageName === "nuxt-skill-hub")
2077
- continue;
2078
- installedModulePackages.push(pkg.packageName);
2079
- }
2496
+ const installedModulePackages = (await collectInstalledModulePackages(nuxt.options.modules, rootDir)).map((pkg) => pkg.packageName).filter((name) => name !== "nuxt-skill-hub");
2080
2497
  if (installedModulePackages.length) {
2081
2498
  consola.info(`Detected ${installedModulePackages.length} installed module package(s):`);
2082
2499
  for (const packageName of installedModulePackages) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-skill-hub",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "description": "Teach your AI agent the Nuxt way with best practices and module guidance.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -61,11 +61,13 @@
61
61
  "dev:build": "nuxt-module-build build --stub --watch",
62
62
  "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare",
63
63
  "release": "bumpp --push --no-push-all",
64
+ "release:check-version": "node ./scripts/check-npm-version.mjs",
64
65
  "lint": "eslint .",
65
- "test": "pnpm run dev:prepare && vitest run && pnpm run test:pack",
66
+ "test": "pnpm run dev:prepare && pnpm run docs:prepare && vitest run && pnpm run test:pack",
66
67
  "test:pack": "node ./scripts/check-packlist.mjs",
67
68
  "test:watch": "vitest watch",
68
69
  "test:types": "pnpm run dev:prepare && vue-tsc --noEmit",
70
+ "docs:prepare": "pnpm --dir docs exec nuxt prepare",
69
71
  "docs:typecheck": "pnpm --dir docs typecheck",
70
72
  "typecheck": "pnpm run test:types && pnpm run docs:typecheck"
71
73
  }