nuxt-skill-hub 0.0.6 → 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 +7 -2
- package/dist/module.json +1 -1
- package/dist/module.mjs +799 -172
- package/dist/runtime/eslint/no-redundant-import.d.ts +2 -0
- package/dist/runtime/eslint/no-redundant-import.js +114 -0
- package/dist/runtime/eslint/plugin.d.ts +9 -0
- package/dist/runtime/eslint/plugin.js +5 -0
- package/package.json +7 -2
package/dist/module.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useLogger, defineNuxtModule } from '@nuxt/kit';
|
|
2
|
-
import { isCI, isTest } from 'std-env';
|
|
2
|
+
import { isAgent, agent, isCI, isTest } from 'std-env';
|
|
3
3
|
import { promises, existsSync, lstatSync, writeFileSync, readFileSync } from 'node:fs';
|
|
4
4
|
import { join, basename, resolve, dirname, relative, isAbsolute } from 'pathe';
|
|
5
5
|
import { fileURLToPath } from 'mlly';
|
|
@@ -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
|
|
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.
|
|
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 =
|
|
506
|
+
const normalized = target.trim();
|
|
511
507
|
if (!normalized) {
|
|
512
508
|
return void 0;
|
|
513
509
|
}
|
|
514
|
-
const config =
|
|
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(
|
|
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 =
|
|
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
|
|
869
|
-
|
|
870
|
-
|
|
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
|
-
|
|
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
|
|
1073
|
+
const PACKAGE_OVERRIDES = [
|
|
1044
1074
|
{
|
|
1045
1075
|
packageName: "@nuxt/ui",
|
|
1046
1076
|
repo: "nuxt/ui",
|
|
@@ -1054,14 +1084,24 @@ 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
|
|
1060
|
-
return
|
|
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";
|
|
1064
1099
|
const UNGH_API_BASE = "https://ungh.cc";
|
|
1100
|
+
const githubDirectoryCache = /* @__PURE__ */ new Map();
|
|
1101
|
+
const githubDefaultBranchCache = /* @__PURE__ */ new Map();
|
|
1102
|
+
const githubFileTextCache = /* @__PURE__ */ new Map();
|
|
1103
|
+
const urlTextCache = /* @__PURE__ */ new Map();
|
|
1104
|
+
const urlBytesCache = /* @__PURE__ */ new Map();
|
|
1065
1105
|
function githubFetchOptions(timeoutMs) {
|
|
1066
1106
|
return {
|
|
1067
1107
|
timeout: timeoutMs,
|
|
@@ -1071,6 +1111,9 @@ function githubFetchOptions(timeoutMs) {
|
|
|
1071
1111
|
}
|
|
1072
1112
|
};
|
|
1073
1113
|
}
|
|
1114
|
+
function fetchStatus(error) {
|
|
1115
|
+
return typeof error === "object" && error && "status" in error ? Number(error.status) : void 0;
|
|
1116
|
+
}
|
|
1074
1117
|
function encodeGitHubPath(path) {
|
|
1075
1118
|
return path.split("/").filter(Boolean).map((segment) => encodeURIComponent(segment)).join("/");
|
|
1076
1119
|
}
|
|
@@ -1096,6 +1139,15 @@ function toRepoPath(value) {
|
|
|
1096
1139
|
}
|
|
1097
1140
|
return null;
|
|
1098
1141
|
}
|
|
1142
|
+
function memoize(cache, key, factory) {
|
|
1143
|
+
const cached = cache.get(key);
|
|
1144
|
+
if (cached) {
|
|
1145
|
+
return cached;
|
|
1146
|
+
}
|
|
1147
|
+
const pending = factory();
|
|
1148
|
+
cache.set(key, pending);
|
|
1149
|
+
return pending;
|
|
1150
|
+
}
|
|
1099
1151
|
function parseGitHubRepo(input) {
|
|
1100
1152
|
if (!input) {
|
|
1101
1153
|
return null;
|
|
@@ -1109,25 +1161,27 @@ async function listGitHubDirectory(repo, ref, dirPath, timeoutMs) {
|
|
|
1109
1161
|
}
|
|
1110
1162
|
const normalizedDirPath = dirPath.replace(/^\/+|\/+$/g, "");
|
|
1111
1163
|
const prefix = normalizedDirPath ? `${normalizedDirPath}/` : "";
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1164
|
+
return await memoize(githubDirectoryCache, `${repoPath}::${ref}::${normalizedDirPath}`, async () => {
|
|
1165
|
+
try {
|
|
1166
|
+
const data = await ofetch(
|
|
1167
|
+
`${GITHUB_API_BASE}/repos/${repoPath}/git/trees/${encodeURIComponent(ref)}?recursive=1`,
|
|
1168
|
+
githubFetchOptions(timeoutMs)
|
|
1169
|
+
);
|
|
1170
|
+
const entries = /* @__PURE__ */ new Set();
|
|
1171
|
+
for (const entry of data.tree || []) {
|
|
1172
|
+
if (entry.type !== "tree" || !entry.path.startsWith(prefix)) {
|
|
1173
|
+
continue;
|
|
1174
|
+
}
|
|
1175
|
+
const remainder = entry.path.slice(prefix.length);
|
|
1176
|
+
if (remainder && !remainder.includes("/")) {
|
|
1177
|
+
entries.add(remainder);
|
|
1178
|
+
}
|
|
1125
1179
|
}
|
|
1180
|
+
return Array.from(entries).sort((a, b) => a.localeCompare(b));
|
|
1181
|
+
} catch {
|
|
1182
|
+
return [];
|
|
1126
1183
|
}
|
|
1127
|
-
|
|
1128
|
-
} catch {
|
|
1129
|
-
return [];
|
|
1130
|
-
}
|
|
1184
|
+
});
|
|
1131
1185
|
}
|
|
1132
1186
|
async function fetchGitHubDefaultBranch(repo, timeoutMs) {
|
|
1133
1187
|
const repoPath = toRepoPath(repo);
|
|
@@ -1138,39 +1192,405 @@ async function fetchGitHubDefaultBranch(repo, timeoutMs) {
|
|
|
1138
1192
|
if (!owner || !name) {
|
|
1139
1193
|
return null;
|
|
1140
1194
|
}
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1195
|
+
return await memoize(githubDefaultBranchCache, repoPath, async () => {
|
|
1196
|
+
try {
|
|
1197
|
+
const data = await ofetch(
|
|
1198
|
+
`${UNGH_API_BASE}/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}`,
|
|
1199
|
+
githubFetchOptions(timeoutMs)
|
|
1200
|
+
);
|
|
1201
|
+
return data.repo?.defaultBranch || null;
|
|
1202
|
+
} catch {
|
|
1203
|
+
return null;
|
|
1204
|
+
}
|
|
1205
|
+
});
|
|
1150
1206
|
}
|
|
1151
1207
|
async function fetchGitHubFileText(repo, ref, filePath, timeoutMs) {
|
|
1152
1208
|
const repoPath = toRepoPath(repo);
|
|
1153
1209
|
if (!repoPath) {
|
|
1154
1210
|
return { ok: false, error: "Invalid GitHub repository format" };
|
|
1155
1211
|
}
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1212
|
+
return await memoize(githubFileTextCache, `${repoPath}::${ref}::${filePath}`, async () => {
|
|
1213
|
+
try {
|
|
1214
|
+
const data = await ofetch(
|
|
1215
|
+
`https://raw.githubusercontent.com/${repoPath}/${encodeURIComponent(ref)}/${encodeGitHubPath(filePath)}`,
|
|
1216
|
+
{
|
|
1217
|
+
...githubFetchOptions(timeoutMs),
|
|
1218
|
+
responseType: "text"
|
|
1219
|
+
}
|
|
1220
|
+
);
|
|
1221
|
+
return { ok: true, data };
|
|
1222
|
+
} catch (error) {
|
|
1223
|
+
return {
|
|
1224
|
+
ok: false,
|
|
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, {
|
|
1160
1235
|
...githubFetchOptions(timeoutMs),
|
|
1161
1236
|
responseType: "text"
|
|
1162
|
-
}
|
|
1163
|
-
|
|
1164
|
-
|
|
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
|
+
};
|
|
1165
1262
|
} catch (error) {
|
|
1166
|
-
const status = typeof error === "object" && error && "status" in error ? Number(error.status) : void 0;
|
|
1167
1263
|
return {
|
|
1168
1264
|
ok: false,
|
|
1169
|
-
status,
|
|
1170
1265
|
error: error.message
|
|
1171
1266
|
};
|
|
1172
1267
|
}
|
|
1173
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),
|
|
1284
|
+
error: error.message
|
|
1285
|
+
};
|
|
1286
|
+
}
|
|
1287
|
+
});
|
|
1288
|
+
}
|
|
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
|
+
}
|
|
1174
1594
|
|
|
1175
1595
|
function makeSkip(packageName, skillName, reason, sourceKind) {
|
|
1176
1596
|
return {
|
|
@@ -1213,8 +1633,14 @@ function normalizeHttpUrl(url) {
|
|
|
1213
1633
|
return void 0;
|
|
1214
1634
|
}
|
|
1215
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
|
+
}
|
|
1216
1641
|
function resolveRepositoryUrl(packageInfo) {
|
|
1217
|
-
const
|
|
1642
|
+
const override = findPackageOverride(packageInfo.packageName);
|
|
1643
|
+
const sourceRepo = override?.repo || parseGitHubRepo(packageInfo.repository) || parseGitHubRepo(packageInfo.homepage);
|
|
1218
1644
|
if (sourceRepo) {
|
|
1219
1645
|
return {
|
|
1220
1646
|
sourceRepo,
|
|
@@ -1225,6 +1651,15 @@ function resolveRepositoryUrl(packageInfo) {
|
|
|
1225
1651
|
repoUrl: normalizeHttpUrl(packageInfo.repository)
|
|
1226
1652
|
};
|
|
1227
1653
|
}
|
|
1654
|
+
function isImmutableRef(ref, version) {
|
|
1655
|
+
if (!ref) {
|
|
1656
|
+
return false;
|
|
1657
|
+
}
|
|
1658
|
+
if (version && (ref === version || ref === `v${version}`)) {
|
|
1659
|
+
return true;
|
|
1660
|
+
}
|
|
1661
|
+
return /^[a-f0-9]{7,40}$/i.test(ref);
|
|
1662
|
+
}
|
|
1228
1663
|
async function materializeCandidate(packageInfo, candidate, cacheRoot) {
|
|
1229
1664
|
const targetDir = join(
|
|
1230
1665
|
cacheRoot,
|
|
@@ -1232,8 +1667,24 @@ async function materializeCandidate(packageInfo, candidate, cacheRoot) {
|
|
|
1232
1667
|
sanitizeSegment(candidate.sourceRepo),
|
|
1233
1668
|
sanitizeSegment(candidate.sourceRef),
|
|
1234
1669
|
sanitizeSegment(packageInfo.packageName),
|
|
1670
|
+
sanitizeSegment(packageInfo.version || "unknown"),
|
|
1235
1671
|
sanitizeSegment(candidate.skillName)
|
|
1236
1672
|
);
|
|
1673
|
+
const skillFilePath = join(targetDir, "SKILL.md");
|
|
1674
|
+
if (isImmutableRef(candidate.sourceRef, packageInfo.version) && await pathExists(skillFilePath)) {
|
|
1675
|
+
return normalizeContribution({
|
|
1676
|
+
packageName: packageInfo.packageName,
|
|
1677
|
+
version: packageInfo.version,
|
|
1678
|
+
skillName: candidate.skillName,
|
|
1679
|
+
sourceKind: candidate.sourceKind,
|
|
1680
|
+
sourceRepo: candidate.sourceRepo,
|
|
1681
|
+
sourceRef: candidate.sourceRef,
|
|
1682
|
+
sourcePath: candidate.sourcePath,
|
|
1683
|
+
official: candidate.official,
|
|
1684
|
+
resolver: candidate.resolver,
|
|
1685
|
+
forceIncludeScripts: true
|
|
1686
|
+
}, targetDir, join(targetDir, ".."));
|
|
1687
|
+
}
|
|
1237
1688
|
try {
|
|
1238
1689
|
await downloadTemplate(`gh:${candidate.sourceRepo}/${candidate.sourcePath}#${candidate.sourceRef}`, {
|
|
1239
1690
|
dir: targetDir,
|
|
@@ -1245,7 +1696,7 @@ async function materializeCandidate(packageInfo, candidate, cacheRoot) {
|
|
|
1245
1696
|
} catch {
|
|
1246
1697
|
return null;
|
|
1247
1698
|
}
|
|
1248
|
-
if (!await pathExists(
|
|
1699
|
+
if (!await pathExists(skillFilePath)) {
|
|
1249
1700
|
return null;
|
|
1250
1701
|
}
|
|
1251
1702
|
return normalizeContribution({
|
|
@@ -1261,25 +1712,13 @@ async function materializeCandidate(packageInfo, candidate, cacheRoot) {
|
|
|
1261
1712
|
forceIncludeScripts: true
|
|
1262
1713
|
}, targetDir, join(targetDir, ".."));
|
|
1263
1714
|
}
|
|
1264
|
-
|
|
1265
|
-
const
|
|
1266
|
-
const skipped = [];
|
|
1267
|
-
const override = findGitHubOverride(packageInfo.packageName);
|
|
1715
|
+
function createGitHubResolveContext(packageInfo) {
|
|
1716
|
+
const override = findPackageOverride(packageInfo.packageName);
|
|
1268
1717
|
const repo = override?.repo || parseGitHubRepo(packageInfo.repository) || parseGitHubRepo(packageInfo.homepage);
|
|
1269
|
-
if (!repo) {
|
|
1270
|
-
return {
|
|
1271
|
-
contributions: [],
|
|
1272
|
-
issues,
|
|
1273
|
-
skipped: [makeSkip(packageInfo.packageName, override?.skillName || packageInfo.packageName, "No GitHub repository metadata found", "github")]
|
|
1274
|
-
};
|
|
1275
|
-
}
|
|
1276
1718
|
const refs = dedupe([
|
|
1277
1719
|
override?.ref || "",
|
|
1278
1720
|
packageInfo.version ? `v${packageInfo.version}` : "",
|
|
1279
|
-
packageInfo.version || ""
|
|
1280
|
-
await fetchGitHubDefaultBranch(repo, timeoutMs) || "",
|
|
1281
|
-
"main",
|
|
1282
|
-
"master"
|
|
1721
|
+
packageInfo.version || ""
|
|
1283
1722
|
]);
|
|
1284
1723
|
const candidates = dedupe([
|
|
1285
1724
|
override?.skillName || "",
|
|
@@ -1295,32 +1734,89 @@ async function resolveViaGitHub(packageInfo, cacheRoot, timeoutMs) {
|
|
|
1295
1734
|
];
|
|
1296
1735
|
})
|
|
1297
1736
|
]);
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
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];
|
|
1321
1770
|
}
|
|
1322
1771
|
}
|
|
1323
|
-
|
|
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 };
|
|
1801
|
+
}
|
|
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) ;
|
|
1324
1820
|
if (skillsDirs.length) {
|
|
1325
1821
|
const contributions = [];
|
|
1326
1822
|
for (const skillName of skillsDirs) {
|
|
@@ -1328,7 +1824,7 @@ async function resolveViaGitHub(packageInfo, cacheRoot, timeoutMs) {
|
|
|
1328
1824
|
skillName,
|
|
1329
1825
|
sourcePath: `skills/${skillName}`,
|
|
1330
1826
|
sourceKind: "github",
|
|
1331
|
-
sourceRepo: repo,
|
|
1827
|
+
sourceRepo: context.repo,
|
|
1332
1828
|
sourceRef: ref,
|
|
1333
1829
|
official: true,
|
|
1334
1830
|
resolver: "githubHeuristic"
|
|
@@ -1338,35 +1834,53 @@ async function resolveViaGitHub(packageInfo, cacheRoot, timeoutMs) {
|
|
|
1338
1834
|
}
|
|
1339
1835
|
}
|
|
1340
1836
|
if (contributions.length) {
|
|
1341
|
-
return
|
|
1837
|
+
return contributions;
|
|
1342
1838
|
}
|
|
1343
1839
|
}
|
|
1344
|
-
for (const path of pathCandidates) {
|
|
1345
|
-
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;
|
|
1346
1842
|
const resolved = await materializeCandidate(packageInfo, {
|
|
1347
1843
|
skillName: candidateSkillName,
|
|
1348
1844
|
sourcePath: path,
|
|
1349
1845
|
sourceKind: "github",
|
|
1350
|
-
sourceRepo: repo,
|
|
1846
|
+
sourceRepo: context.repo,
|
|
1351
1847
|
sourceRef: ref,
|
|
1352
1848
|
official: true,
|
|
1353
1849
|
resolver: "githubHeuristic"
|
|
1354
1850
|
}, cacheRoot);
|
|
1355
1851
|
if (resolved) {
|
|
1356
|
-
return
|
|
1852
|
+
return [resolved];
|
|
1357
1853
|
}
|
|
1358
1854
|
}
|
|
1855
|
+
return null;
|
|
1856
|
+
};
|
|
1857
|
+
const fallbackRefs = dedupe([
|
|
1858
|
+
await fetchGitHubDefaultBranch(context.repo, timeoutMs) || "",
|
|
1859
|
+
"main",
|
|
1860
|
+
"master"
|
|
1861
|
+
]);
|
|
1862
|
+
for (const ref of fallbackRefs) {
|
|
1863
|
+
const resolved = await tryResolveForRef(ref);
|
|
1864
|
+
if (resolved?.length) {
|
|
1865
|
+
return { contributions: resolved, issues: context.issues, skipped: context.skipped };
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
for (const ref of context.refs) {
|
|
1869
|
+
const resolved = await tryResolveForRef(ref);
|
|
1870
|
+
if (resolved?.length) {
|
|
1871
|
+
return { contributions: resolved, issues: context.issues, skipped: context.skipped };
|
|
1872
|
+
}
|
|
1359
1873
|
}
|
|
1360
|
-
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"));
|
|
1361
1875
|
return {
|
|
1362
1876
|
contributions: [],
|
|
1363
|
-
issues,
|
|
1364
|
-
skipped
|
|
1877
|
+
issues: context.issues,
|
|
1878
|
+
skipped: context.skipped
|
|
1365
1879
|
};
|
|
1366
1880
|
}
|
|
1367
1881
|
async function resolveViaMetadataRouter(packageInfo, cacheRoot) {
|
|
1368
1882
|
const { repoUrl, sourceRepo } = resolveRepositoryUrl(packageInfo);
|
|
1369
|
-
const docsUrl =
|
|
1883
|
+
const docsUrl = resolveDocsUrl(packageInfo);
|
|
1370
1884
|
if (!repoUrl && !docsUrl) {
|
|
1371
1885
|
return {
|
|
1372
1886
|
contributions: [],
|
|
@@ -1380,8 +1894,31 @@ async function resolveViaMetadataRouter(packageInfo, cacheRoot) {
|
|
|
1380
1894
|
"generated",
|
|
1381
1895
|
"metadata-router",
|
|
1382
1896
|
sanitizeSegment(packageInfo.packageName),
|
|
1897
|
+
sanitizeSegment(packageInfo.version || "unknown"),
|
|
1383
1898
|
sanitizeSegment(skillName)
|
|
1384
1899
|
);
|
|
1900
|
+
const skillFilePath = join(targetDir, "SKILL.md");
|
|
1901
|
+
if (await pathExists(skillFilePath)) {
|
|
1902
|
+
return {
|
|
1903
|
+
contributions: [
|
|
1904
|
+
normalizeContribution({
|
|
1905
|
+
packageName: packageInfo.packageName,
|
|
1906
|
+
version: packageInfo.version,
|
|
1907
|
+
skillName,
|
|
1908
|
+
description: packageInfo.description,
|
|
1909
|
+
sourceKind: "generated",
|
|
1910
|
+
sourceRepo,
|
|
1911
|
+
repoUrl,
|
|
1912
|
+
docsUrl,
|
|
1913
|
+
official: true,
|
|
1914
|
+
resolver: "metadataRouter",
|
|
1915
|
+
forceIncludeScripts: false
|
|
1916
|
+
}, targetDir, join(targetDir, ".."))
|
|
1917
|
+
],
|
|
1918
|
+
issues: [],
|
|
1919
|
+
skipped: []
|
|
1920
|
+
};
|
|
1921
|
+
}
|
|
1385
1922
|
const files = createMetadataRouterSkillFiles({
|
|
1386
1923
|
packageName: packageInfo.packageName,
|
|
1387
1924
|
skillName,
|
|
@@ -1415,26 +1952,46 @@ async function resolveViaMetadataRouter(packageInfo, cacheRoot) {
|
|
|
1415
1952
|
};
|
|
1416
1953
|
}
|
|
1417
1954
|
async function resolveRemoteContributionsForPackage(packageInfo, options) {
|
|
1418
|
-
const
|
|
1955
|
+
const githubAgentsResult = options.enableGithubLookup ? await resolveViaGitHubAgents(packageInfo, options.cacheRoot, options.githubLookupTimeoutMs) : {
|
|
1419
1956
|
contributions: [],
|
|
1420
1957
|
issues: [],
|
|
1421
1958
|
skipped: []
|
|
1422
1959
|
};
|
|
1423
|
-
if (
|
|
1424
|
-
return
|
|
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
|
+
};
|
|
1425
1982
|
}
|
|
1426
1983
|
const generatedResult = await resolveViaMetadataRouter(packageInfo, options.cacheRoot);
|
|
1427
1984
|
if (generatedResult.contributions.length) {
|
|
1428
1985
|
return {
|
|
1429
1986
|
contributions: generatedResult.contributions,
|
|
1430
|
-
issues: [...
|
|
1431
|
-
skipped: generatedResult.skipped
|
|
1987
|
+
issues: [...githubAgentsResult.issues, ...wellKnownResult.issues, ...githubHeuristicResult.issues, ...generatedResult.issues],
|
|
1988
|
+
skipped: [...wellKnownResult.skipped, ...generatedResult.skipped]
|
|
1432
1989
|
};
|
|
1433
1990
|
}
|
|
1434
1991
|
return {
|
|
1435
1992
|
contributions: [],
|
|
1436
|
-
issues: [...
|
|
1437
|
-
skipped: [...
|
|
1993
|
+
issues: [...githubAgentsResult.issues, ...wellKnownResult.issues, ...githubHeuristicResult.issues, ...generatedResult.issues],
|
|
1994
|
+
skipped: [...wellKnownResult.skipped, ...githubHeuristicResult.skipped, ...generatedResult.skipped]
|
|
1438
1995
|
};
|
|
1439
1996
|
}
|
|
1440
1997
|
|
|
@@ -1492,6 +2049,7 @@ async function hashFileIfExists(path) {
|
|
|
1492
2049
|
}
|
|
1493
2050
|
}
|
|
1494
2051
|
|
|
2052
|
+
const HASH_READ_CONCURRENCY = 16;
|
|
1495
2053
|
async function resolveManualContribution(rootDir, contribution) {
|
|
1496
2054
|
const sourceDir = isAbsolute(contribution.sourceDir) ? contribution.sourceDir : resolve(rootDir, contribution.sourceDir);
|
|
1497
2055
|
return normalizeContribution(contribution, sourceDir, sourceDir);
|
|
@@ -1543,8 +2101,6 @@ async function hashDirectoryTreeIfExists(rootPath) {
|
|
|
1543
2101
|
if (!rootStat.isDirectory()) {
|
|
1544
2102
|
return null;
|
|
1545
2103
|
}
|
|
1546
|
-
const hash = createHash("sha256");
|
|
1547
|
-
hash.update("dir:.\n");
|
|
1548
2104
|
const entries = await glob("**/*", {
|
|
1549
2105
|
cwd: rootPath,
|
|
1550
2106
|
dot: true,
|
|
@@ -1553,30 +2109,20 @@ async function hashDirectoryTreeIfExists(rootPath) {
|
|
|
1553
2109
|
onlyFiles: false
|
|
1554
2110
|
});
|
|
1555
2111
|
entries.sort((a, b) => a.localeCompare(b));
|
|
1556
|
-
|
|
2112
|
+
const chunkGroups = await mapWithConcurrency(entries, HASH_READ_CONCURRENCY, async (entry) => {
|
|
1557
2113
|
const currentPath = join(rootPath, entry);
|
|
1558
2114
|
const stat = await promises.lstat(currentPath);
|
|
1559
|
-
const
|
|
1560
|
-
if (stat.isSymbolicLink()) {
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
}
|
|
1571
|
-
if (stat.isFile()) {
|
|
1572
|
-
hash.update(`file:${relativePath}:`);
|
|
1573
|
-
hash.update(await promises.readFile(currentPath));
|
|
1574
|
-
hash.update("\n");
|
|
1575
|
-
continue;
|
|
1576
|
-
}
|
|
1577
|
-
hash.update(`other:${relativePath}:${stat.mode}
|
|
1578
|
-
`);
|
|
1579
|
-
}
|
|
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);
|
|
1580
2126
|
return hash.digest("hex");
|
|
1581
2127
|
}
|
|
1582
2128
|
|
|
@@ -1613,6 +2159,7 @@ async function isGeneratedSkillFresh(generatedSkillRoot, fingerprint) {
|
|
|
1613
2159
|
}
|
|
1614
2160
|
|
|
1615
2161
|
const GITHUB_LOOKUP_TIMEOUT_MS = 1500;
|
|
2162
|
+
const REMOTE_RESOLUTION_CONCURRENCY = 4;
|
|
1616
2163
|
function resolveStableSkillBuildDir(rootDir, buildDir) {
|
|
1617
2164
|
const rel = relative(resolve(rootDir), resolve(buildDir)).replaceAll("\\", "/");
|
|
1618
2165
|
if (rel === ".nuxt" || rel.startsWith(".nuxt/")) {
|
|
@@ -1630,6 +2177,12 @@ function normalizeRelativePath(from, to) {
|
|
|
1630
2177
|
function getGeneratedSkillRoot(buildDir, skillName) {
|
|
1631
2178
|
return join(buildDir, "skill-hub", skillName);
|
|
1632
2179
|
}
|
|
2180
|
+
function getPersistentCacheRoot(exportRoot) {
|
|
2181
|
+
return join(exportRoot, "node_modules", ".cache", "nuxt-skill-hub");
|
|
2182
|
+
}
|
|
2183
|
+
function getCachedGeneratedSkillRoot(cacheRoot, skillName) {
|
|
2184
|
+
return join(cacheRoot, "generated", skillName);
|
|
2185
|
+
}
|
|
1633
2186
|
function mergeSkipped(entries, keyFn) {
|
|
1634
2187
|
const byKey = /* @__PURE__ */ new Map();
|
|
1635
2188
|
for (const entry of entries) {
|
|
@@ -1666,17 +2219,13 @@ async function collectInstalledPackages(nuxt) {
|
|
|
1666
2219
|
discoveries.push(discovered);
|
|
1667
2220
|
}
|
|
1668
2221
|
};
|
|
1669
|
-
const
|
|
1670
|
-
const
|
|
1671
|
-
for (const specifier of seenSpecifiers) {
|
|
1672
|
-
addInstalledPackage(await discoverInstalledPackageFromSpecifier(specifier, nuxt.options.rootDir));
|
|
1673
|
-
}
|
|
2222
|
+
const modulePackages = await collectInstalledModulePackages(nuxt.options.modules, nuxt.options.rootDir);
|
|
2223
|
+
for (const pkg of modulePackages) addInstalledPackage(pkg);
|
|
1674
2224
|
const layerDirectories = Array.from(new Set(
|
|
1675
2225
|
nuxt.options._layers.map((layer) => resolve(layer.cwd || layer.config.rootDir || "")).filter((layerDir) => layerDir && layerDir !== resolve(nuxt.options.rootDir))
|
|
1676
2226
|
));
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
}
|
|
2227
|
+
const layerPackages = await Promise.all(layerDirectories.map((dir) => discoverInstalledPackageFromDirectory(dir)));
|
|
2228
|
+
for (const pkg of layerPackages) addInstalledPackage(pkg);
|
|
1680
2229
|
const localPackageSkills = await discoverLocalPackageSkills(nuxt.options.rootDir);
|
|
1681
2230
|
if (localPackageSkills) {
|
|
1682
2231
|
discoveries.push(localPackageSkills);
|
|
@@ -1706,6 +2255,8 @@ async function ensureStableSkillWrappers(input) {
|
|
|
1706
2255
|
async function generateSkillTree(input) {
|
|
1707
2256
|
const { nuxt, logger, options, skillName, exportRoot, generationMode } = input;
|
|
1708
2257
|
const stableBuildDir = resolveStableSkillBuildDir(nuxt.options.rootDir, nuxt.options.buildDir);
|
|
2258
|
+
const persistentCacheRoot = getPersistentCacheRoot(exportRoot);
|
|
2259
|
+
const cachedGeneratedSkillRoot = getCachedGeneratedSkillRoot(persistentCacheRoot, skillName);
|
|
1709
2260
|
const generatedSkillRoot = getGeneratedSkillRoot(stableBuildDir, skillName);
|
|
1710
2261
|
const referencesRoot = join(generatedSkillRoot, "references");
|
|
1711
2262
|
const nuxtRoot = join(referencesRoot, "nuxt");
|
|
@@ -1739,26 +2290,32 @@ async function generateSkillTree(input) {
|
|
|
1739
2290
|
...sortAndDedupeContributions(resolvedManual)
|
|
1740
2291
|
])
|
|
1741
2292
|
});
|
|
1742
|
-
if (generationMode === "prepare" && await isGeneratedSkillFresh(
|
|
1743
|
-
|
|
2293
|
+
if (generationMode === "prepare" && await isGeneratedSkillFresh(cachedGeneratedSkillRoot, fingerprint)) {
|
|
2294
|
+
await copySkillTree(cachedGeneratedSkillRoot, generatedSkillRoot, true);
|
|
2295
|
+
logger.info(`Restored cached ${skillName} skill from ${cachedGeneratedSkillRoot}`);
|
|
1744
2296
|
return;
|
|
1745
2297
|
}
|
|
1746
2298
|
const distResolvedPackages = new Set(discoveredContributions.contributions.map((item) => item.packageName));
|
|
1747
2299
|
const remoteIssues = [];
|
|
1748
2300
|
const remoteSkipped = [];
|
|
1749
2301
|
const remoteContributions = [];
|
|
1750
|
-
const remoteCacheRoot = join(
|
|
1751
|
-
await
|
|
2302
|
+
const remoteCacheRoot = join(persistentCacheRoot, "remote");
|
|
2303
|
+
await ensureDir(remoteCacheRoot);
|
|
1752
2304
|
const packagesToResolve = installedPackages.filter((pkg) => pkg.packageName !== "nuxt-skill-hub" && !distResolvedPackages.has(pkg.packageName));
|
|
1753
2305
|
const totalToResolve = packagesToResolve.length;
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
2306
|
+
const remoteResults = await mapWithConcurrency(
|
|
2307
|
+
packagesToResolve,
|
|
2308
|
+
REMOTE_RESOLUTION_CONCURRENCY,
|
|
2309
|
+
async (pkg, index) => {
|
|
2310
|
+
logger.start(`Resolving skills for ${pkg.packageName} (${index + 1}/${totalToResolve})...`);
|
|
2311
|
+
return await resolveRemoteContributionsForPackage(pkg, {
|
|
2312
|
+
cacheRoot: remoteCacheRoot,
|
|
2313
|
+
githubLookupTimeoutMs: GITHUB_LOOKUP_TIMEOUT_MS,
|
|
2314
|
+
enableGithubLookup: true
|
|
2315
|
+
});
|
|
2316
|
+
}
|
|
2317
|
+
);
|
|
2318
|
+
for (const remote of remoteResults) {
|
|
1762
2319
|
remoteIssues.push(...remote.issues);
|
|
1763
2320
|
remoteSkipped.push(...remote.skipped);
|
|
1764
2321
|
remoteContributions.push(...remote.contributions);
|
|
@@ -1841,14 +2398,47 @@ async function generateSkillTree(input) {
|
|
|
1841
2398
|
skipped
|
|
1842
2399
|
));
|
|
1843
2400
|
await writeGenerationState(generatedSkillRoot, fingerprint);
|
|
2401
|
+
await copySkillTree(generatedSkillRoot, cachedGeneratedSkillRoot, true);
|
|
1844
2402
|
logger.success(`Generated ${skillName} skill at ${generatedSkillRoot}`);
|
|
1845
2403
|
}
|
|
1846
2404
|
|
|
1847
2405
|
function isCancelled(value) {
|
|
1848
2406
|
return value === null || typeof value === "symbol";
|
|
1849
2407
|
}
|
|
2408
|
+
function buildAIGuidance(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);
|
|
2416
|
+
const sourceLabel = detectedAgent ? ` (${detectedAgent})` : "";
|
|
2417
|
+
const snippetLines = [
|
|
2418
|
+
"export default defineNuxtConfig({",
|
|
2419
|
+
" skillHub: {",
|
|
2420
|
+
...target ? [` targets: ['${target}'],`] : [],
|
|
2421
|
+
` generationMode: 'prepare',`,
|
|
2422
|
+
" },",
|
|
2423
|
+
"})"
|
|
2424
|
+
];
|
|
2425
|
+
return {
|
|
2426
|
+
intro: `Install wizard skipped because nuxt-skill-hub was installed by an AI agent${sourceLabel}. Add this to your nuxt.config.ts:`,
|
|
2427
|
+
snippet: snippetLines.join("\n"),
|
|
2428
|
+
optionalFlags: target ? "Optional flags: set `skillHub.skillName`, enable `skillHub.moduleAuthoring`, or switch to `skillHub.generationMode: 'manual'` if needed." : "Optional flags: add `skillHub.targets` to pin an agent target, set `skillHub.skillName`, enable `skillHub.moduleAuthoring`, or switch to `skillHub.generationMode: 'manual'` if needed."
|
|
2429
|
+
};
|
|
2430
|
+
}
|
|
1850
2431
|
async function runInstallWizard(nuxt) {
|
|
1851
2432
|
const logger = useLogger("nuxt-skill-hub");
|
|
2433
|
+
if (isAgent) {
|
|
2434
|
+
const guidance = buildAIGuidance(detectCurrentTarget(), agent);
|
|
2435
|
+
logger.info(guidance.intro);
|
|
2436
|
+
logger.info(`\`\`\`ts
|
|
2437
|
+
${guidance.snippet}
|
|
2438
|
+
\`\`\``);
|
|
2439
|
+
logger.info(guidance.optionalFlags);
|
|
2440
|
+
return;
|
|
2441
|
+
}
|
|
1852
2442
|
if (process.env.CI || !process.stdout.isTTY) {
|
|
1853
2443
|
logger.info("Non-interactive environment detected, skipping install wizard.");
|
|
1854
2444
|
return;
|
|
@@ -1903,15 +2493,7 @@ Let's configure AI agent skills for your Nuxt project.`
|
|
|
1903
2493
|
}
|
|
1904
2494
|
}
|
|
1905
2495
|
}
|
|
1906
|
-
const
|
|
1907
|
-
const uniqueSpecifiers = Array.from(new Set(moduleSpecifiers));
|
|
1908
|
-
const installedModulePackages = [];
|
|
1909
|
-
for (const specifier of uniqueSpecifiers) {
|
|
1910
|
-
const pkg = await discoverInstalledPackageFromSpecifier(specifier, rootDir);
|
|
1911
|
-
if (!pkg || pkg.packageName === "nuxt-skill-hub")
|
|
1912
|
-
continue;
|
|
1913
|
-
installedModulePackages.push(pkg.packageName);
|
|
1914
|
-
}
|
|
2496
|
+
const installedModulePackages = (await collectInstalledModulePackages(nuxt.options.modules, rootDir)).map((pkg) => pkg.packageName).filter((name) => name !== "nuxt-skill-hub");
|
|
1915
2497
|
if (installedModulePackages.length) {
|
|
1916
2498
|
consola.info(`Detected ${installedModulePackages.length} installed module package(s):`);
|
|
1917
2499
|
for (const packageName of installedModulePackages) {
|
|
@@ -2005,6 +2587,49 @@ Configure \`skillHub.skillName\`, \`skillHub.targets\`, or set \`skillHub.genera
|
|
|
2005
2587
|
);
|
|
2006
2588
|
}
|
|
2007
2589
|
|
|
2590
|
+
function createAutoImportAddon(nuxt) {
|
|
2591
|
+
let unimport;
|
|
2592
|
+
let nitroUnimport;
|
|
2593
|
+
const hook = nuxt.hook;
|
|
2594
|
+
hook("imports:context", (context) => {
|
|
2595
|
+
unimport = context;
|
|
2596
|
+
});
|
|
2597
|
+
hook("nitro:init", (nitro) => {
|
|
2598
|
+
nitroUnimport = nitro.unimport;
|
|
2599
|
+
});
|
|
2600
|
+
hook("eslint:config:addons", (addons) => {
|
|
2601
|
+
addons.push({
|
|
2602
|
+
name: "skill-hub:no-redundant-import",
|
|
2603
|
+
async getConfigs() {
|
|
2604
|
+
const imports = [
|
|
2605
|
+
...await unimport?.getImports() || [],
|
|
2606
|
+
...await nitroUnimport?.getImports() || []
|
|
2607
|
+
];
|
|
2608
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2609
|
+
const entries = imports.filter((i) => {
|
|
2610
|
+
const key = `${i.as || i.name}:${i.from}:${i.type ? "type" : "value"}`;
|
|
2611
|
+
if (seen.has(key)) return false;
|
|
2612
|
+
seen.add(key);
|
|
2613
|
+
return true;
|
|
2614
|
+
}).map((i) => ({ name: i.name, ...i.as && i.as !== i.name ? { as: i.as } : {}, from: i.from, ...i.type ? { type: true } : {} })).sort((a, b) => a.from.localeCompare(b.from) || a.name.localeCompare(b.name));
|
|
2615
|
+
return {
|
|
2616
|
+
imports: [{ from: "nuxt-skill-hub/eslint-plugin", name: "default", as: "skillHubPlugin" }],
|
|
2617
|
+
configs: [
|
|
2618
|
+
[
|
|
2619
|
+
"{",
|
|
2620
|
+
` name: 'skill-hub/no-redundant-import',`,
|
|
2621
|
+
` plugins: { 'skill-hub': skillHubPlugin },`,
|
|
2622
|
+
` settings: { 'skill-hub/autoImports': ${JSON.stringify(entries)} },`,
|
|
2623
|
+
` rules: { 'skill-hub/no-redundant-import': 'warn' },`,
|
|
2624
|
+
"}"
|
|
2625
|
+
].join("\n")
|
|
2626
|
+
]
|
|
2627
|
+
};
|
|
2628
|
+
}
|
|
2629
|
+
});
|
|
2630
|
+
});
|
|
2631
|
+
}
|
|
2632
|
+
|
|
2008
2633
|
const module$1 = defineNuxtModule({
|
|
2009
2634
|
meta: {
|
|
2010
2635
|
name: "nuxt-skill-hub",
|
|
@@ -2025,6 +2650,8 @@ const module$1 = defineNuxtModule({
|
|
|
2025
2650
|
},
|
|
2026
2651
|
async setup(options, nuxt) {
|
|
2027
2652
|
const logger = useLogger("nuxt-skill-hub");
|
|
2653
|
+
if (options.eslint !== false)
|
|
2654
|
+
createAutoImportAddon(nuxt);
|
|
2028
2655
|
if (isCI && !isTest) {
|
|
2029
2656
|
logger.info("Skipping skill generation in CI");
|
|
2030
2657
|
return;
|