nuxt-skill-hub 0.0.3 → 0.0.5
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 +2 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +829 -663
- package/package.json +4 -2
package/dist/module.mjs
CHANGED
|
@@ -1,98 +1,28 @@
|
|
|
1
|
-
import { promises, existsSync, lstatSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { join, basename, resolve, dirname, relative, isAbsolute } from 'pathe';
|
|
3
1
|
import { useLogger, defineNuxtModule } from '@nuxt/kit';
|
|
4
2
|
import { isCI, isTest } from 'std-env';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
3
|
+
import { promises, existsSync, lstatSync, writeFileSync, readFileSync } from 'node:fs';
|
|
4
|
+
import { join, basename, resolve, dirname, relative, isAbsolute } from 'pathe';
|
|
5
|
+
import { fileURLToPath } from 'mlly';
|
|
8
6
|
import { homedir } from 'node:os';
|
|
9
7
|
import matter from 'gray-matter';
|
|
10
|
-
import { readPackageJSON, resolvePackageJSON } from 'pkg-types';
|
|
8
|
+
import { readPackageJSON, findWorkspaceDir, resolvePackageJSON, resolveLockfile } from 'pkg-types';
|
|
11
9
|
import { transform } from 'automd';
|
|
12
|
-
import { fileURLToPath } from 'mlly';
|
|
13
10
|
import { downloadTemplate } from 'giget';
|
|
11
|
+
import { detectInstalledAgents, expandPath, detectCurrentAgent, getAgentIds, getAgentConfig } from 'unagent/env';
|
|
14
12
|
import { ofetch } from 'ofetch';
|
|
13
|
+
import { createHash } from 'node:crypto';
|
|
14
|
+
import { hash } from 'ohash';
|
|
15
|
+
import { glob } from 'tinyglobby';
|
|
16
|
+
import { createConsola } from 'consola';
|
|
17
|
+
import { colorize } from 'consola/utils';
|
|
15
18
|
|
|
16
|
-
const version = "0.0.
|
|
19
|
+
const version = "0.0.5";
|
|
17
20
|
const packageJson = {
|
|
18
21
|
version: version};
|
|
19
22
|
|
|
20
23
|
const metadata = packageJson;
|
|
21
24
|
const PACKAGE_VERSION = metadata.version;
|
|
22
25
|
|
|
23
|
-
function resolveSkillsDir(target, config) {
|
|
24
|
-
if (config?.skillsDir) {
|
|
25
|
-
return config.skillsDir;
|
|
26
|
-
}
|
|
27
|
-
if (target === "codex") {
|
|
28
|
-
return "skills";
|
|
29
|
-
}
|
|
30
|
-
return void 0;
|
|
31
|
-
}
|
|
32
|
-
function normalizeTarget(target) {
|
|
33
|
-
return target.trim();
|
|
34
|
-
}
|
|
35
|
-
function getRawTargetConfig(target) {
|
|
36
|
-
return getAgentConfig(target);
|
|
37
|
-
}
|
|
38
|
-
function getSupportedTargets() {
|
|
39
|
-
return getAgentIds().filter((target) => {
|
|
40
|
-
const config = getRawTargetConfig(target);
|
|
41
|
-
return Boolean(resolveSkillsDir(target, config));
|
|
42
|
-
}).sort((a, b) => a.localeCompare(b));
|
|
43
|
-
}
|
|
44
|
-
function resolveAgentTargetConfig(target) {
|
|
45
|
-
const normalized = normalizeTarget(target);
|
|
46
|
-
if (!normalized) {
|
|
47
|
-
return void 0;
|
|
48
|
-
}
|
|
49
|
-
const config = getRawTargetConfig(normalized);
|
|
50
|
-
const skillsDir = resolveSkillsDir(normalized, config);
|
|
51
|
-
if (!config || !skillsDir) {
|
|
52
|
-
return void 0;
|
|
53
|
-
}
|
|
54
|
-
return {
|
|
55
|
-
target: normalized,
|
|
56
|
-
configDir: expandPath(config.configDir),
|
|
57
|
-
skillsDir
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
function validateTargets(targets) {
|
|
61
|
-
const uniqueTargets = Array.from(new Set(targets.map(normalizeTarget).filter(Boolean)));
|
|
62
|
-
const valid = [];
|
|
63
|
-
const invalid = [];
|
|
64
|
-
for (const target of uniqueTargets) {
|
|
65
|
-
const config = getRawTargetConfig(target);
|
|
66
|
-
if (!config) {
|
|
67
|
-
invalid.push({
|
|
68
|
-
target,
|
|
69
|
-
reason: "unknown-target"
|
|
70
|
-
});
|
|
71
|
-
continue;
|
|
72
|
-
}
|
|
73
|
-
if (!resolveSkillsDir(target, config)) {
|
|
74
|
-
invalid.push({
|
|
75
|
-
target,
|
|
76
|
-
reason: "missing-skills-dir"
|
|
77
|
-
});
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
valid.push(target);
|
|
81
|
-
}
|
|
82
|
-
return { valid, invalid };
|
|
83
|
-
}
|
|
84
|
-
function detectCurrentTarget() {
|
|
85
|
-
const current = detectCurrentAgent();
|
|
86
|
-
if (!current) return void 0;
|
|
87
|
-
const skillsDir = resolveSkillsDir(current.id, current.config);
|
|
88
|
-
return skillsDir ? current.id : void 0;
|
|
89
|
-
}
|
|
90
|
-
function detectInstalledTargets() {
|
|
91
|
-
return Array.from(new Set(
|
|
92
|
-
detectInstalledAgents().filter((agent) => agent.detected === "config" && resolveSkillsDir(agent.id, agent.config)).map((agent) => agent.id)
|
|
93
|
-
)).sort((a, b) => a.localeCompare(b));
|
|
94
|
-
}
|
|
95
|
-
|
|
96
26
|
const DEFAULT_NUXT_CONTENT_METADATA = {
|
|
97
27
|
id: "nuxt-best-practices",
|
|
98
28
|
version: PACKAGE_VERSION,
|
|
@@ -207,7 +137,6 @@ const DEFAULT_NUXT_CONTENT_METADATA = {
|
|
|
207
137
|
}
|
|
208
138
|
]
|
|
209
139
|
};
|
|
210
|
-
const EMPTY_MODULE_GUIDANCE_MARKDOWN = "_No module skills discovered. Use Nuxt guidance plus official module docs when module-specific guidance is missing._";
|
|
211
140
|
function yamlString(value) {
|
|
212
141
|
return JSON.stringify(value);
|
|
213
142
|
}
|
|
@@ -315,107 +244,85 @@ ${lines.join("\n")}
|
|
|
315
244
|
function findPack(metadata, id) {
|
|
316
245
|
return metadata.packs.find((pack) => pack.id === id) || metadata.packs[0];
|
|
317
246
|
}
|
|
318
|
-
function createPackSummary(metadata, packIds) {
|
|
319
|
-
return packIds.map((packId) => `\`${findPack(metadata, packId).title}\``).join(", ");
|
|
320
|
-
}
|
|
321
|
-
function getSkillRenderProfile(includeModuleAuthoring = false) {
|
|
322
|
-
if (includeModuleAuthoring) {
|
|
323
|
-
return {
|
|
324
|
-
includeModuleAuthoring,
|
|
325
|
-
packSummaryIds: [
|
|
326
|
-
"module-authoring",
|
|
327
|
-
"plugins",
|
|
328
|
-
"architecture-boundaries",
|
|
329
|
-
"nitro-h3-patterns",
|
|
330
|
-
"migrations"
|
|
331
|
-
]
|
|
332
|
-
};
|
|
333
|
-
}
|
|
334
|
-
return {
|
|
335
|
-
includeModuleAuthoring: false,
|
|
336
|
-
packSummaryIds: [
|
|
337
|
-
"abstraction-disambiguation",
|
|
338
|
-
"page-meta-head-layout",
|
|
339
|
-
"error-surfaces-recovery",
|
|
340
|
-
"verification-finish",
|
|
341
|
-
"data-fetching-ssr"
|
|
342
|
-
]
|
|
343
|
-
};
|
|
344
|
-
}
|
|
345
247
|
function packLink(metadata, id) {
|
|
346
248
|
const pack = findPack(metadata, id);
|
|
347
249
|
return `[${pack.title}](./references/nuxt/${pack.relativePath})`;
|
|
348
250
|
}
|
|
349
251
|
function createRoutingTable(metadata, includeModuleAuthoring = false) {
|
|
350
252
|
const rows = [
|
|
351
|
-
...includeModuleAuthoring ? [
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
253
|
+
...includeModuleAuthoring ? [[
|
|
254
|
+
"Writing or refactoring a Nuxt module (`defineNuxtModule`, hooks, public APIs)",
|
|
255
|
+
packLink(metadata, "module-authoring")
|
|
256
|
+
]] : [],
|
|
257
|
+
[
|
|
258
|
+
"SSR, initial page load, route params, or data fetched for first render",
|
|
259
|
+
packLink(metadata, "data-fetching-ssr")
|
|
260
|
+
],
|
|
261
|
+
[
|
|
262
|
+
"Hydration warnings, `ClientOnly`, browser-only APIs, time/randomness, or SSR/CSR mismatch",
|
|
263
|
+
packLink(metadata, "hydration-consistency")
|
|
264
|
+
],
|
|
265
|
+
[
|
|
266
|
+
"Page options, middleware, layout selection, title, meta tags, canonical URLs, or OG data",
|
|
267
|
+
packLink(metadata, "page-meta-head-layout")
|
|
268
|
+
],
|
|
269
|
+
[
|
|
270
|
+
"`nuxt.config.*`, `runtimeConfig`, env wiring, or public/private config exposure",
|
|
271
|
+
packLink(metadata, "server-routes-runtime-config")
|
|
272
|
+
],
|
|
273
|
+
[
|
|
274
|
+
"`server/api/**`, `server/routes/**`, `defineEventHandler`, route rules, caching, or Nitro plugins",
|
|
275
|
+
packLink(metadata, "nitro-h3-patterns")
|
|
276
|
+
],
|
|
277
|
+
[
|
|
278
|
+
"Plugins, injections, app boot logic, or global runtime initialization",
|
|
279
|
+
packLink(metadata, "plugins")
|
|
280
|
+
],
|
|
281
|
+
[
|
|
282
|
+
"Secrets, privileged API calls, request isolation, or server/client boundary confusion",
|
|
283
|
+
packLink(metadata, "architecture-boundaries")
|
|
284
|
+
],
|
|
285
|
+
[
|
|
286
|
+
"Performance regressions, rendering strategy, lazy loading, or payload/bundle cost",
|
|
287
|
+
packLink(metadata, "performance-rendering")
|
|
288
|
+
],
|
|
289
|
+
[
|
|
290
|
+
"Upgrades, deprecations, compatibility fixes, or version-boundary work",
|
|
291
|
+
packLink(metadata, "migrations")
|
|
292
|
+
],
|
|
293
|
+
[
|
|
294
|
+
"A generic Vue or raw HTML fix looks plausible",
|
|
295
|
+
packLink(metadata, "abstraction-disambiguation")
|
|
296
|
+
],
|
|
297
|
+
[
|
|
298
|
+
"The remaining work is mostly components, composables, reactivity, props/emits, or SFC structure",
|
|
299
|
+
"[Vue Best Practices](./references/vue/SKILL.md)"
|
|
300
|
+
],
|
|
301
|
+
[
|
|
302
|
+
"Global/local errors, `clearError`, `showError`, or recovery flows",
|
|
303
|
+
packLink(metadata, "error-surfaces-recovery")
|
|
304
|
+
]
|
|
391
305
|
];
|
|
392
306
|
return [
|
|
393
|
-
"| Task shape or symptom |
|
|
394
|
-
"| --- | --- |
|
|
395
|
-
...rows.map((
|
|
396
|
-
].join("\n");
|
|
397
|
-
}
|
|
398
|
-
function createNuxtPackTable(metadata) {
|
|
399
|
-
const rows = metadata.packs.map(
|
|
400
|
-
(pack) => `| [${pack.title}](./references/nuxt/${pack.relativePath}) | ${pack.focus} | ${pack.triggers.join("<br>")} |`
|
|
401
|
-
);
|
|
402
|
-
return [
|
|
403
|
-
"| Pack | Focus | Typical triggers |",
|
|
404
|
-
"| --- | --- | --- |",
|
|
405
|
-
...rows
|
|
307
|
+
"| Task shape or symptom | Open first |",
|
|
308
|
+
"| --- | --- |",
|
|
309
|
+
...rows.map(([symptom, target]) => `| ${symptom} | ${target} |`)
|
|
406
310
|
].join("\n");
|
|
407
311
|
}
|
|
408
|
-
function
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
312
|
+
function createRoutingExamples(metadata, includeModuleAuthoring = false) {
|
|
313
|
+
const lines = [
|
|
314
|
+
`- "This page fetches twice on first load" \u2192 ${packLink(metadata, "data-fetching-ssr")}`,
|
|
315
|
+
`- "Why do I get a hydration mismatch from Date.now()?" \u2192 ${packLink(metadata, "hydration-consistency")}`,
|
|
316
|
+
`- "Change canonical URL and OG tags" \u2192 ${packLink(metadata, "page-meta-head-layout")}`,
|
|
317
|
+
`- "Expose only the public runtimeConfig key" \u2192 ${packLink(metadata, "server-routes-runtime-config")}`,
|
|
318
|
+
`- "This server/api handler cache or route rule is wrong" \u2192 ${packLink(metadata, "nitro-h3-patterns")}`,
|
|
319
|
+
`- "This modal/table/dropdown was hand-built in raw HTML" \u2192 ${packLink(metadata, "abstraction-disambiguation")}`,
|
|
320
|
+
`- "\`clearError\` is not behaving as expected" \u2192 ${packLink(metadata, "error-surfaces-recovery")}`,
|
|
321
|
+
`- "Should this secret live in runtime config or client code?" \u2192 ${packLink(metadata, "architecture-boundaries")}`,
|
|
322
|
+
...includeModuleAuthoring ? [`- "Add a Nuxt module option, hook, or runtime extension" \u2192 ${packLink(metadata, "module-authoring")}`] : []
|
|
323
|
+
];
|
|
324
|
+
return `## Routing examples
|
|
325
|
+
${lines.join("\n")}`;
|
|
419
326
|
}
|
|
420
327
|
function createSkillMapSections(metadata, entries, skipped = [], includeModuleAuthoring = false) {
|
|
421
328
|
const includeModuleSections = hasModuleGuidance(entries, skipped);
|
|
@@ -426,137 +333,85 @@ function createSkillMapSections(metadata, entries, skipped = [], includeModuleAu
|
|
|
426
333
|
renderModuleGroup("Metadata-routed skills", grouped.metadataRouted, "./references/modules/"),
|
|
427
334
|
renderSkippedEntries(skipped)
|
|
428
335
|
].filter(Boolean);
|
|
429
|
-
const
|
|
430
|
-
|
|
431
|
-
## Module author focus
|
|
432
|
-
|
|
433
|
-
This skill includes extra guidance for Nuxt module authors on top of the default app-oriented Nuxt packs.
|
|
434
|
-
If the task touches \`defineNuxtModule\`, Nuxt Kit hooks, generated runtime files, prefixed public APIs, or release compatibility, start with ${packLink(metadata, "module-authoring")} before branching into the broader app-oriented packs.
|
|
435
|
-
` : "";
|
|
436
|
-
const moduleWorkflowSteps = includeModuleSections ? `
|
|
437
|
-
5. If an installed module is involved, open its entry under \`references/modules\`.
|
|
438
|
-
6. Copied skills go straight to their \`SKILL.md\`; metadata-routed modules only expose docs and source links.` : "";
|
|
439
|
-
const moduleGuidesSection = includeModuleSections ? `
|
|
440
|
-
## Module guides
|
|
336
|
+
const moduleGuidesSection = includeModuleSections ? `## Module guides
|
|
337
|
+
Use a module entry only when an installed module owns the surface. Module guidance is delta-only and should not replace broad Nuxt rules outside that module.
|
|
441
338
|
|
|
442
|
-
|
|
339
|
+
${moduleSections.join("\n")}` : "";
|
|
340
|
+
return [
|
|
341
|
+
"## Routing",
|
|
342
|
+
"Load one matching guide first. Open more guides only when the first guide points you there or the task clearly crosses boundaries.",
|
|
343
|
+
createRoutingTable(metadata, includeModuleAuthoring),
|
|
344
|
+
createRoutingExamples(metadata, includeModuleAuthoring),
|
|
345
|
+
moduleGuidesSection
|
|
346
|
+
].filter(Boolean).join("\n\n");
|
|
347
|
+
}
|
|
348
|
+
function createSkillEntrypoint(skillName, metadata, monorepoScopePath, includeModuleAuthoring = false, entries = [], skipped = []) {
|
|
349
|
+
const description = "Routes Nuxt work to the smallest relevant guide. Use for `useFetch`/`useAsyncData`, SSR, hydration mismatches, `definePageMeta`, `useHead`/`useSeoMeta`, `runtimeConfig`, Nitro/h3 server routes, plugins, Nuxt UI, Nuxt Content, or Nuxt module authoring.";
|
|
350
|
+
const monorepoScopeSection = monorepoScopePath ? `## Monorepo scope
|
|
351
|
+
This skill applies only to \`${monorepoScopePath}\`. Treat files and tasks outside that subtree as out of scope unless the user explicitly redirects you there.` : "";
|
|
352
|
+
return `---
|
|
353
|
+
name: ${yamlString(skillName)}
|
|
354
|
+
description: ${yamlString(description)}
|
|
355
|
+
---
|
|
443
356
|
|
|
444
|
-
|
|
445
|
-
` : `
|
|
446
|
-
## Module guides
|
|
357
|
+
# Nuxt Skill Router
|
|
447
358
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
359
|
+
Use this skill to make the Nuxt ownership decision first, then route to the smallest relevant Nuxt, Vue, or module guide.
|
|
360
|
+
${monorepoScopeSection ? `
|
|
361
|
+
${monorepoScopeSection}
|
|
362
|
+
` : "\n"}## Loading rules
|
|
363
|
+
1. Repository-global instructions and required workflows win first.
|
|
364
|
+
2. Inspect the exact owned surface first: page, layout, component, server handler, collection, config file, or module-owned file.
|
|
365
|
+
3. Load one matching guide from the routing table below.
|
|
366
|
+
4. Switch to [Vue Best Practices](./references/vue/SKILL.md) only when Nuxt no longer owns the abstraction.
|
|
367
|
+
5. Use module entries only inside module-owned APIs, config, runtime behavior, and files.
|
|
368
|
+
6. Open additional guides only when the first guide points you there or the task clearly crosses boundaries.
|
|
457
369
|
|
|
458
|
-
|
|
370
|
+
${createSkillMapSections(metadata, entries, skipped, includeModuleAuthoring)}
|
|
459
371
|
|
|
460
|
-
|
|
372
|
+
## Finish
|
|
373
|
+
For multi-surface changes or final verification, open ${packLink(metadata, "verification-finish")}.
|
|
461
374
|
|
|
462
|
-
|
|
375
|
+
- Verify paired surfaces when relevant: page/head, server/client, global/local.
|
|
376
|
+
- Verify framework behavior, not only visible output.
|
|
377
|
+
- Keep module guidance scoped to the owning module.
|
|
463
378
|
|
|
464
|
-
|
|
379
|
+
---
|
|
465
380
|
|
|
466
|
-
|
|
467
|
-
${moduleGuidesSection}
|
|
381
|
+
_Generated by nuxt-skill-hub. Do not edit this file manually._
|
|
468
382
|
`;
|
|
469
383
|
}
|
|
470
|
-
function
|
|
471
|
-
const
|
|
472
|
-
const
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
const audienceSection = profile.includeModuleAuthoring ? `
|
|
480
|
-
|
|
481
|
-
## Module Author Focus
|
|
482
|
-
This skill keeps the default Nuxt app guidance and adds an authoring layer for repositories that build Nuxt modules.
|
|
483
|
-
Start with [Module Authoring Conventions](./references/nuxt/rules/module-authoring.md) for \`defineNuxtModule\`, lifecycle hooks, prefixed public APIs, and module-scoped skill boundaries, then fall back to the broader app packs when the task crosses into runtime behavior.
|
|
484
|
-
` : "";
|
|
485
|
-
const activationFlow = `1. Explore the project first: inspect the real page, component, route, server handler, collection, or module surface you are changing.
|
|
486
|
-
2. Use the routing sections in this file and load the smallest matching Nuxt pack.
|
|
487
|
-
3. If the remaining work is mainly Vue component, composable, reactivity, or SFC work, open [Vue Best Practices](./references/vue/SKILL.md).
|
|
488
|
-
4. If module authoring is part of the task, load [Module Authoring Conventions](./references/nuxt/rules/module-authoring.md) before changing \`defineNuxtModule\`, runtime extensions, hooks, or release scaffolding.${includeModuleSections ? `
|
|
489
|
-
5. If an installed module owns the problem, open its entry under [references/modules](./references/modules).
|
|
490
|
-
6. Apply module guidance as delta-only rules inside that module's APIs, config, runtime behavior, and owned files.` : ""}`;
|
|
491
|
-
const frequencyBullets = profile.includeModuleAuthoring ? `- If the task changes module boot or heavy setup work, prefer lightweight \`setup\` plus lifecycle hooks before adding blocking async work.
|
|
492
|
-
- If the module exposes routes, composables, components, or config, prefix public surfaces with module identity before shipping generic names.
|
|
493
|
-
- If the implementation relies on undocumented Nuxt internals, confirm there is not a public \`@nuxt/kit\` API or documented hook first.
|
|
494
|
-
- If the task adds or edits a bundled module skill, keep it strictly scoped to the module's APIs, integration points, and caveats.
|
|
495
|
-
- If runtime config, server handlers, or generated files are involved, pair module-author guidance with the relevant server, Nitro, plugin, or migration pack instead of improvising cross-boundary behavior.
|
|
496
|
-
- If the work is version-sensitive, check compatibility constraints and migration boundaries before expanding the module surface.
|
|
497
|
-
- If the task touches SSR, initial page load, or route-driven data, prefer \`useFetch\` or \`useAsyncData\` before \`onMounted\` plus \`$fetch\`.
|
|
498
|
-
- If the task changes page options, layout selection, route middleware, or page-level behavior, check \`definePageMeta\` before adding ad hoc wiring.
|
|
499
|
-
- If the task changes title, meta tags, canonical URLs, or OG data, check \`useHead\` or \`useSeoMeta\` before page-meta or template markup.
|
|
500
|
-
- If content lives in JSON or YAML records, or the UI needs generated docs navigation, choose data collections and collection-navigation primitives before manual assembly.
|
|
501
|
-
- If the UI surface is page chrome, a table, a form, a modal, a command palette, or a dropdown, prefer a Nuxt UI primitive before raw HTML or custom listeners.
|
|
502
|
-
- If runtime config, tokens, secrets, or privileged API calls are involved, keep them server-side and expose only a server route or the minimum public config.
|
|
503
|
-
- If hydration, browser-only APIs, time, randomness, or cookies are involved, use SSR-safe primitives first and isolate browser-only work behind \`ClientOnly\` or \`onMounted\`.
|
|
504
|
-
- If the fix touches errors, fallback UI, or recovery flow, check both global and local surfaces before concluding the work is complete.
|
|
505
|
-
- If the solution looks correct but uses generic Vue or hand-rolled HTML, confirm Nuxt, Nuxt Content, or Nuxt UI does not already own that abstraction.${includeModuleSections ? `
|
|
506
|
-
- If the task is module-specific, use the module entry and keep module guidance scoped; do not replace broad Nuxt rules with module-specific rules.` : ""}` : `- If the task touches SSR, initial page load, or route-driven data, prefer \`useFetch\` or \`useAsyncData\` before \`onMounted\` plus \`$fetch\`.
|
|
507
|
-
- If the task changes page options, layout selection, route middleware, or page-level behavior, check \`definePageMeta\` before adding ad hoc wiring.
|
|
508
|
-
- If the task changes title, meta tags, canonical URLs, or OG data, check \`useHead\` or \`useSeoMeta\` before page-meta or template markup.
|
|
509
|
-
- If content lives in JSON or YAML records, or the UI needs generated docs navigation, choose data collections and collection-navigation primitives before manual assembly.
|
|
510
|
-
- If the UI surface is page chrome, a table, a form, a modal, a command palette, or a dropdown, prefer a Nuxt UI primitive before raw HTML or custom listeners.
|
|
511
|
-
- If runtime config, tokens, secrets, or privileged API calls are involved, keep them server-side and expose only a server route or the minimum public config.
|
|
512
|
-
- If hydration, browser-only APIs, time, randomness, or cookies are involved, use SSR-safe primitives first and isolate browser-only work behind \`ClientOnly\` or \`onMounted\`.
|
|
513
|
-
- If the fix touches errors, fallback UI, or recovery flow, check both global and local surfaces before concluding the work is complete.
|
|
514
|
-
- If the solution looks correct but uses generic Vue or hand-rolled HTML, confirm Nuxt, Nuxt Content, or Nuxt UI does not already own that abstraction.${includeModuleSections ? `
|
|
515
|
-
- If the task is module-specific, use the module entry and keep module guidance scoped; do not replace broad Nuxt rules with module-specific rules.` : ""}`;
|
|
516
|
-
const beforeCompletion = profile.includeModuleAuthoring ? `- Did I start from Nuxt Kit and documented lifecycle hooks before reaching for private internals?
|
|
517
|
-
- Did I keep public APIs collision-resistant and module-scoped?
|
|
518
|
-
- Did I keep module skill guidance as a delta on top of Nuxt guidance instead of restating framework-global rules?
|
|
519
|
-
- Did I verify compatibility, install/setup cost, and cross-boundary behavior before concluding the work is complete?
|
|
520
|
-
- Did I still verify the relevant app/runtime surfaces when the module work crossed into SSR, Nitro, plugins, config, or UI behavior?` : `- Did I choose a Nuxt primitive where a generic Vue or raw HTML solution would be tempting?
|
|
521
|
-
- Did I check whether the fix needs a second surface such as global or local, or server or client?
|
|
522
|
-
- Did I choose the right concept pair: page meta vs head, data collection vs page collection, component primitive vs custom markup?
|
|
523
|
-
- Did I verify the framework behavior that matters, not just the visible output?`;
|
|
384
|
+
function createStableSkillWrapper(skillName, generatedEntryPath, generatedRootPath, generationMode) {
|
|
385
|
+
const description = "Stable entrypoint for generated Nuxt guidance in this repository. Use for Nuxt SSR, hydration, pages, `runtimeConfig`, Nitro/h3, plugins, Nuxt UI, Nuxt Content, or Nuxt module work. If generated content is missing, regenerate it with `nuxt prepare`.";
|
|
386
|
+
const recoveryInstructions = generationMode === "manual" ? [
|
|
387
|
+
"Automatic skill generation is currently disabled by `skillHub.generationMode: 'manual'`.",
|
|
388
|
+
"Ask the user to switch `skillHub.generationMode` to `'prepare'`, then run `nuxt prepare`."
|
|
389
|
+
].join("\n") : [
|
|
390
|
+
"Run `nuxt prepare` from this project before continuing.",
|
|
391
|
+
"That regenerates the Nuxt build directory, refreshes types, and writes the generated Nuxt skill content."
|
|
392
|
+
].join("\n");
|
|
524
393
|
return `---
|
|
525
394
|
name: ${yamlString(skillName)}
|
|
526
395
|
description: ${yamlString(description)}
|
|
527
396
|
---
|
|
528
397
|
|
|
529
|
-
# Nuxt Skill
|
|
530
|
-
|
|
531
|
-
This file keeps the highest-frequency Nuxt decisions in context.
|
|
532
|
-
Use it to avoid generic Vue fallbacks, then route into the right Nuxt pack, Vue guidance, or module delta skill.
|
|
533
|
-
${monorepoScopeSection}
|
|
534
|
-
${audienceSection}
|
|
535
|
-
|
|
536
|
-
## Activation Flow
|
|
537
|
-
${activationFlow}
|
|
538
|
-
|
|
539
|
-
## High-Frequency Nuxt Decisions
|
|
540
|
-
${frequencyBullets}
|
|
541
|
-
|
|
542
|
-
## Precedence
|
|
543
|
-
- Repository-global instructions and required workflows win first.
|
|
544
|
-
- This file keeps the common Nuxt forks in context.
|
|
545
|
-
- Nuxt packs provide deeper framework guidance.
|
|
546
|
-
- Vue guidance covers component, composable, and SFC patterns after the Nuxt decision is settled.
|
|
547
|
-
${includeModuleSections ? "- Module entries add delta-only guidance inside explicit module scope." : ""}
|
|
548
|
-
|
|
549
|
-
${createSkillMapSections(metadata, entries, skipped, includeModuleAuthoring)}
|
|
398
|
+
# Nuxt Skill Wrapper
|
|
550
399
|
|
|
551
|
-
|
|
552
|
-
|
|
400
|
+
This is the stable entrypoint for Nuxt guidance in this repository.
|
|
401
|
+
The full generated skill tree lives in the Nuxt build directory so the checked-in skill surface stays small.
|
|
553
402
|
|
|
554
|
-
##
|
|
555
|
-
|
|
403
|
+
## Generated content
|
|
404
|
+
- Entry: [${generatedEntryPath}](${generatedEntryPath})
|
|
405
|
+
- Root: [${generatedRootPath}](${generatedRootPath})
|
|
556
406
|
|
|
557
|
-
|
|
407
|
+
## Required workflow
|
|
408
|
+
1. Check whether the generated entry exists.
|
|
409
|
+
2. If it exists, open it and follow that generated skill instead of extending this wrapper.
|
|
410
|
+
3. If it does not exist, ${recoveryInstructions}
|
|
558
411
|
|
|
559
|
-
|
|
412
|
+
## Notes
|
|
413
|
+
- Generated content is ephemeral and may be refreshed by Nuxt.
|
|
414
|
+
- This wrapper is the stable file that should remain in version control.
|
|
560
415
|
`;
|
|
561
416
|
}
|
|
562
417
|
function createCompactMetadataRouterContent(entry) {
|
|
@@ -630,6 +485,79 @@ async function loadVueSkillFiles(cacheRoot) {
|
|
|
630
485
|
return loadVueSkillFilesFromDir(dir);
|
|
631
486
|
}
|
|
632
487
|
|
|
488
|
+
function resolveSkillsDir(target, config) {
|
|
489
|
+
if (config?.skillsDir) {
|
|
490
|
+
return config.skillsDir;
|
|
491
|
+
}
|
|
492
|
+
if (target === "codex") {
|
|
493
|
+
return "skills";
|
|
494
|
+
}
|
|
495
|
+
return void 0;
|
|
496
|
+
}
|
|
497
|
+
function normalizeTarget(target) {
|
|
498
|
+
return target.trim();
|
|
499
|
+
}
|
|
500
|
+
function getRawTargetConfig(target) {
|
|
501
|
+
return getAgentConfig(target);
|
|
502
|
+
}
|
|
503
|
+
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));
|
|
508
|
+
}
|
|
509
|
+
function resolveAgentTargetConfig(target) {
|
|
510
|
+
const normalized = normalizeTarget(target);
|
|
511
|
+
if (!normalized) {
|
|
512
|
+
return void 0;
|
|
513
|
+
}
|
|
514
|
+
const config = getRawTargetConfig(normalized);
|
|
515
|
+
const skillsDir = resolveSkillsDir(normalized, config);
|
|
516
|
+
if (!config || !skillsDir) {
|
|
517
|
+
return void 0;
|
|
518
|
+
}
|
|
519
|
+
return {
|
|
520
|
+
target: normalized,
|
|
521
|
+
configDir: expandPath(config.configDir),
|
|
522
|
+
skillsDir
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
function validateTargets(targets) {
|
|
526
|
+
const uniqueTargets = Array.from(new Set(targets.map(normalizeTarget).filter(Boolean)));
|
|
527
|
+
const valid = [];
|
|
528
|
+
const invalid = [];
|
|
529
|
+
for (const target of uniqueTargets) {
|
|
530
|
+
const config = getRawTargetConfig(target);
|
|
531
|
+
if (!config) {
|
|
532
|
+
invalid.push({
|
|
533
|
+
target,
|
|
534
|
+
reason: "unknown-target"
|
|
535
|
+
});
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
538
|
+
if (!resolveSkillsDir(target, config)) {
|
|
539
|
+
invalid.push({
|
|
540
|
+
target,
|
|
541
|
+
reason: "missing-skills-dir"
|
|
542
|
+
});
|
|
543
|
+
continue;
|
|
544
|
+
}
|
|
545
|
+
valid.push(target);
|
|
546
|
+
}
|
|
547
|
+
return { valid, invalid };
|
|
548
|
+
}
|
|
549
|
+
function detectCurrentTarget() {
|
|
550
|
+
const current = detectCurrentAgent();
|
|
551
|
+
if (!current) return void 0;
|
|
552
|
+
const skillsDir = resolveSkillsDir(current.id, current.config);
|
|
553
|
+
return skillsDir ? current.id : void 0;
|
|
554
|
+
}
|
|
555
|
+
function detectInstalledTargets() {
|
|
556
|
+
return Array.from(new Set(
|
|
557
|
+
detectInstalledAgents().filter((agent) => agent.detected === "config" && resolveSkillsDir(agent.id, agent.config)).map((agent) => agent.id)
|
|
558
|
+
)).sort((a, b) => a.localeCompare(b));
|
|
559
|
+
}
|
|
560
|
+
|
|
633
561
|
const MANAGED_HINT_START = "<!-- nuxt-skill-hub:start -->";
|
|
634
562
|
const MANAGED_HINT_END = "<!-- nuxt-skill-hub:end -->";
|
|
635
563
|
const SKILL_NAME_MAX_LENGTH = 64;
|
|
@@ -683,11 +611,19 @@ async function hasWorkspacePackageConfig(path) {
|
|
|
683
611
|
}
|
|
684
612
|
async function resolveExportRoot(rootDir) {
|
|
685
613
|
const appRoot = resolve(rootDir);
|
|
614
|
+
const workspaceDir = await findWorkspaceDir(appRoot).catch(() => null);
|
|
615
|
+
if (!workspaceDir) {
|
|
616
|
+
return appRoot;
|
|
617
|
+
}
|
|
618
|
+
const resolvedWorkspaceDir = resolve(workspaceDir);
|
|
686
619
|
let current = appRoot;
|
|
687
620
|
while (true) {
|
|
688
621
|
if (await pathExists(join(current, "pnpm-workspace.yaml")) || await hasWorkspacePackageConfig(current)) {
|
|
689
622
|
return current;
|
|
690
623
|
}
|
|
624
|
+
if (current === resolvedWorkspaceDir) {
|
|
625
|
+
return appRoot;
|
|
626
|
+
}
|
|
691
627
|
const parent = dirname(current);
|
|
692
628
|
if (parent === current) {
|
|
693
629
|
return appRoot;
|
|
@@ -1104,217 +1040,20 @@ function formatConflictWarning(conflict) {
|
|
|
1104
1040
|
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`;
|
|
1105
1041
|
}
|
|
1106
1042
|
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
consola.box(
|
|
1122
|
-
`nuxt-skill-hub \u2014 First-time setup
|
|
1123
|
-
Let's configure AI agent skills for your Nuxt project.`
|
|
1124
|
-
);
|
|
1125
|
-
const currentTarget = detectCurrentTarget();
|
|
1126
|
-
let selectedTargets;
|
|
1127
|
-
if (currentTarget) {
|
|
1128
|
-
consola.info(`Running inside ${currentTarget}, auto-selecting it.`);
|
|
1129
|
-
selectedTargets = [currentTarget];
|
|
1130
|
-
} else {
|
|
1131
|
-
const detectedTargets = detectInstalledTargets();
|
|
1132
|
-
const allTargets = getSupportedTargets();
|
|
1133
|
-
if (!detectedTargets.length && !allTargets.length) {
|
|
1134
|
-
consola.warn("No AI agents detected. Skills will be generated when an agent is detected.");
|
|
1135
|
-
consola.info("Supported agents are sourced from unagent.");
|
|
1136
|
-
return;
|
|
1137
|
-
}
|
|
1138
|
-
consola.info(`Detected agents: ${detectedTargets.length ? detectedTargets.join(", ") : "none"}`);
|
|
1139
|
-
selectedTargets = detectedTargets;
|
|
1140
|
-
if (detectedTargets.length > 1) {
|
|
1141
|
-
const keepAll = await consola.prompt("Generate skills for all detected agents?", {
|
|
1142
|
-
type: "confirm",
|
|
1143
|
-
initial: true,
|
|
1144
|
-
cancel: "null"
|
|
1145
|
-
});
|
|
1146
|
-
if (isCancelled(keepAll)) {
|
|
1147
|
-
consola.info("Setup cancelled.");
|
|
1148
|
-
return;
|
|
1149
|
-
}
|
|
1150
|
-
if (!keepAll) {
|
|
1151
|
-
const chosen = await consola.prompt("Select agents to generate skills for:", {
|
|
1152
|
-
type: "multiselect",
|
|
1153
|
-
options: detectedTargets.map((t) => ({ label: t, value: t })),
|
|
1154
|
-
initial: detectedTargets,
|
|
1155
|
-
required: true,
|
|
1156
|
-
cancel: "null"
|
|
1157
|
-
});
|
|
1158
|
-
if (isCancelled(chosen)) {
|
|
1159
|
-
consola.info("Setup cancelled.");
|
|
1160
|
-
return;
|
|
1161
|
-
}
|
|
1162
|
-
selectedTargets = chosen.map((c) => c.value);
|
|
1163
|
-
}
|
|
1164
|
-
}
|
|
1165
|
-
}
|
|
1166
|
-
const moduleSpecifiers = (nuxt.options.modules || []).map((entry) => extractModuleSpecifier(entry)).filter((entry) => Boolean(entry));
|
|
1167
|
-
const uniqueSpecifiers = Array.from(new Set(moduleSpecifiers));
|
|
1168
|
-
const installedModulePackages = [];
|
|
1169
|
-
for (const specifier of uniqueSpecifiers) {
|
|
1170
|
-
const pkg = await discoverInstalledPackageFromSpecifier(specifier, rootDir);
|
|
1171
|
-
if (!pkg || pkg.packageName === "nuxt-skill-hub")
|
|
1172
|
-
continue;
|
|
1173
|
-
installedModulePackages.push(pkg.packageName);
|
|
1174
|
-
}
|
|
1175
|
-
if (installedModulePackages.length) {
|
|
1176
|
-
consola.info(`Detected ${installedModulePackages.length} installed module package(s):`);
|
|
1177
|
-
for (const packageName of installedModulePackages) {
|
|
1178
|
-
consola.log(` ${packageName}`);
|
|
1179
|
-
}
|
|
1180
|
-
consola.info("Module skills and metadata routers are resolved automatically at build time.");
|
|
1181
|
-
} else {
|
|
1182
|
-
consola.info("No installed module entries were discovered.");
|
|
1183
|
-
}
|
|
1184
|
-
for (const conflict of detectConflictingSkills(selectedTargets, skillName)) {
|
|
1185
|
-
consola.warn(formatConflictWarning(conflict));
|
|
1186
|
-
}
|
|
1187
|
-
const gitignorePath = join(exportRoot, ".gitignore");
|
|
1188
|
-
const gitignoreExists = existsSync(gitignorePath);
|
|
1189
|
-
const currentGitignore = gitignoreExists ? readFileSync(gitignorePath, "utf8") : "";
|
|
1190
|
-
const missingPatterns = [];
|
|
1191
|
-
for (const target of selectedTargets) {
|
|
1192
|
-
const { skillRoot } = getTargetSkillRoot(exportRoot, target, skillName);
|
|
1193
|
-
const pattern = relative(exportRoot, skillRoot).replace(/\/?$/, "/");
|
|
1194
|
-
if (!currentGitignore.includes(pattern)) {
|
|
1195
|
-
missingPatterns.push(pattern);
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
if (missingPatterns.length) {
|
|
1199
|
-
const addGitignore = await consola.prompt(
|
|
1200
|
-
`Add generated skill directories to .gitignore?
|
|
1201
|
-
${missingPatterns.map((p) => ` ${p}`).join("\n")}`,
|
|
1202
|
-
{ type: "confirm", initial: true, cancel: "null" }
|
|
1203
|
-
);
|
|
1204
|
-
if (isCancelled(addGitignore)) {
|
|
1205
|
-
consola.info("Setup cancelled.");
|
|
1206
|
-
return;
|
|
1207
|
-
}
|
|
1208
|
-
if (addGitignore) {
|
|
1209
|
-
const block = `
|
|
1210
|
-
# nuxt-skill-hub (generated skills)
|
|
1211
|
-
${missingPatterns.join("\n")}
|
|
1212
|
-
`;
|
|
1213
|
-
const newContent = currentGitignore.trimEnd() + "\n" + block;
|
|
1214
|
-
pendingWrites.push({
|
|
1215
|
-
file: ".gitignore",
|
|
1216
|
-
absPath: gitignorePath,
|
|
1217
|
-
preview: missingPatterns.map((p) => `+ ${p}`).join("\n"),
|
|
1218
|
-
content: newContent,
|
|
1219
|
-
action: gitignoreExists ? "modify" : "create"
|
|
1220
|
-
});
|
|
1221
|
-
}
|
|
1222
|
-
}
|
|
1223
|
-
const hintBlock = `${MANAGED_HINT_START}
|
|
1224
|
-
Use the \`${skillName}\` skill as the first entrypoint for Nuxt tasks in this repository.
|
|
1225
|
-
${MANAGED_HINT_END}`;
|
|
1226
|
-
const hintFileChoice = await consola.prompt("Add a skill hint to help agents discover the skill?", {
|
|
1227
|
-
type: "select",
|
|
1228
|
-
options: [
|
|
1229
|
-
{ label: "CLAUDE.md", value: "claude", hint: "Claude Code project instructions" },
|
|
1230
|
-
{ label: "AGENTS.md", value: "agents", hint: "Universal agent instructions" },
|
|
1231
|
-
{ label: "Both", value: "both" },
|
|
1232
|
-
{ label: "Skip", value: "skip" }
|
|
1233
|
-
],
|
|
1234
|
-
cancel: "null"
|
|
1235
|
-
});
|
|
1236
|
-
if (isCancelled(hintFileChoice)) {
|
|
1237
|
-
consola.info("Setup cancelled.");
|
|
1238
|
-
return;
|
|
1239
|
-
}
|
|
1240
|
-
const addToFile = async (filename) => {
|
|
1241
|
-
const filePath = join(exportRoot, filename);
|
|
1242
|
-
const exists = await pathExists(filePath);
|
|
1243
|
-
const current = exists ? readFileSync(filePath, "utf8") : "";
|
|
1244
|
-
const regex = new RegExp(`${MANAGED_HINT_START}[\\s\\S]*?${MANAGED_HINT_END}`);
|
|
1245
|
-
let newContent;
|
|
1246
|
-
if (regex.test(current)) {
|
|
1247
|
-
newContent = current.replace(regex, hintBlock);
|
|
1248
|
-
} else if (current) {
|
|
1249
|
-
newContent = `${current.trimEnd()}
|
|
1250
|
-
|
|
1251
|
-
${hintBlock}
|
|
1252
|
-
`;
|
|
1253
|
-
} else {
|
|
1254
|
-
newContent = `${hintBlock}
|
|
1255
|
-
`;
|
|
1256
|
-
}
|
|
1257
|
-
pendingWrites.push({
|
|
1258
|
-
file: filename,
|
|
1259
|
-
absPath: filePath,
|
|
1260
|
-
preview: `+ Use the \`${skillName}\` skill as the first entrypoint for Nuxt tasks in this repository.`,
|
|
1261
|
-
content: newContent,
|
|
1262
|
-
action: exists ? "modify" : "create"
|
|
1263
|
-
});
|
|
1264
|
-
};
|
|
1265
|
-
if (hintFileChoice === "claude" || hintFileChoice === "both") {
|
|
1266
|
-
await addToFile("CLAUDE.md");
|
|
1267
|
-
}
|
|
1268
|
-
if (hintFileChoice === "agents" || hintFileChoice === "both") {
|
|
1269
|
-
await addToFile("AGENTS.md");
|
|
1270
|
-
}
|
|
1271
|
-
if (!pendingWrites.length) {
|
|
1272
|
-
consola.success("No file changes needed. Setup complete!");
|
|
1273
|
-
consola.info("Run `nuxt dev` or `nuxt prepare` to generate skills.");
|
|
1274
|
-
return;
|
|
1275
|
-
}
|
|
1276
|
-
consola.log("");
|
|
1277
|
-
consola.info("Planned changes:");
|
|
1278
|
-
for (const write of pendingWrites) {
|
|
1279
|
-
consola.box({ title: write.file, message: colorize("green", write.preview), style: { borderColor: "green" } });
|
|
1280
|
-
}
|
|
1281
|
-
const confirm = await consola.prompt("Apply these changes?", {
|
|
1282
|
-
type: "confirm",
|
|
1283
|
-
initial: true,
|
|
1284
|
-
cancel: "null"
|
|
1285
|
-
});
|
|
1286
|
-
if (isCancelled(confirm) || !confirm) {
|
|
1287
|
-
consola.info("Setup cancelled. No files were modified.");
|
|
1288
|
-
return;
|
|
1289
|
-
}
|
|
1290
|
-
for (const write of pendingWrites) {
|
|
1291
|
-
writeFileSync(write.absPath, write.content, "utf8");
|
|
1292
|
-
const verb = write.action === "create" ? "Created" : "Updated";
|
|
1293
|
-
consola.success(`${verb} ${write.file}`);
|
|
1294
|
-
}
|
|
1295
|
-
consola.log("");
|
|
1296
|
-
consola.box(
|
|
1297
|
-
`Setup complete!
|
|
1298
|
-
|
|
1299
|
-
Run \`nuxt dev\` or \`nuxt prepare\` to generate skills.
|
|
1300
|
-
Configure \`skillHub.skillName\` or \`skillHub.targets\` in nuxt.config.ts if needed.`
|
|
1301
|
-
);
|
|
1302
|
-
}
|
|
1303
|
-
|
|
1304
|
-
const GITHUB_OVERRIDES = [
|
|
1305
|
-
{
|
|
1306
|
-
packageName: "@nuxt/ui",
|
|
1307
|
-
repo: "nuxt/ui",
|
|
1308
|
-
ref: "v4",
|
|
1309
|
-
path: "skills/nuxt-ui",
|
|
1310
|
-
skillName: "nuxt-ui"
|
|
1311
|
-
},
|
|
1312
|
-
{
|
|
1313
|
-
packageName: "@vueuse/core",
|
|
1314
|
-
repo: "vueuse/vueuse",
|
|
1315
|
-
ref: "main",
|
|
1316
|
-
path: "skills/vueuse-functions",
|
|
1317
|
-
skillName: "vueuse-functions"
|
|
1043
|
+
const GITHUB_OVERRIDES = [
|
|
1044
|
+
{
|
|
1045
|
+
packageName: "@nuxt/ui",
|
|
1046
|
+
repo: "nuxt/ui",
|
|
1047
|
+
ref: "v4",
|
|
1048
|
+
path: "skills/nuxt-ui",
|
|
1049
|
+
skillName: "nuxt-ui"
|
|
1050
|
+
},
|
|
1051
|
+
{
|
|
1052
|
+
packageName: "@vueuse/core",
|
|
1053
|
+
repo: "vueuse/vueuse",
|
|
1054
|
+
ref: "main",
|
|
1055
|
+
path: "skills/vueuse-functions",
|
|
1056
|
+
skillName: "vueuse-functions"
|
|
1318
1057
|
}
|
|
1319
1058
|
];
|
|
1320
1059
|
function findGitHubOverride(packageName) {
|
|
@@ -1699,11 +1438,198 @@ async function resolveRemoteContributionsForPackage(packageInfo, options) {
|
|
|
1699
1438
|
};
|
|
1700
1439
|
}
|
|
1701
1440
|
|
|
1702
|
-
const
|
|
1441
|
+
const DEFAULT_SKILL_HUB_GENERATION_MODE = "prepare";
|
|
1442
|
+
function normalizeGenerationMode(value) {
|
|
1443
|
+
return value === "manual" ? "manual" : DEFAULT_SKILL_HUB_GENERATION_MODE;
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
async function resolveLockfileInfo(exportRoot) {
|
|
1447
|
+
let lockfilePath;
|
|
1448
|
+
try {
|
|
1449
|
+
lockfilePath = await resolveLockfile(exportRoot);
|
|
1450
|
+
} catch {
|
|
1451
|
+
return null;
|
|
1452
|
+
}
|
|
1453
|
+
const contents = await promises.readFile(lockfilePath);
|
|
1454
|
+
const hash = createHash("sha256").update(contents).digest("hex");
|
|
1455
|
+
return {
|
|
1456
|
+
path: relative(exportRoot, lockfilePath).replaceAll("\\", "/"),
|
|
1457
|
+
hash
|
|
1458
|
+
};
|
|
1459
|
+
}
|
|
1460
|
+
async function createGenerationFingerprint(input) {
|
|
1461
|
+
const rootPackageHash = await hashFileIfExists(join(input.rootDir, "package.json"));
|
|
1462
|
+
const lockfile = await resolveLockfileInfo(input.exportRoot);
|
|
1463
|
+
const modules = [...input.installedPackages].filter((pkg) => pkg.packageName !== "nuxt-skill-hub").map((pkg) => ({ name: pkg.packageName, version: pkg.version || null })).sort((a, b) => a.name.localeCompare(b.name));
|
|
1464
|
+
const localSources = [...input.localSources].sort((a, b) => {
|
|
1465
|
+
const left = `${a.packageName}::${a.skillName}::${a.sourceDir}`;
|
|
1466
|
+
const right = `${b.packageName}::${b.skillName}::${b.sourceDir}`;
|
|
1467
|
+
return left.localeCompare(right);
|
|
1468
|
+
});
|
|
1469
|
+
return hash({
|
|
1470
|
+
packageVersion: input.packageVersion,
|
|
1471
|
+
skillName: input.skillName,
|
|
1472
|
+
rootDir: input.rootDir,
|
|
1473
|
+
buildDir: input.buildDir,
|
|
1474
|
+
rootPackageHash,
|
|
1475
|
+
lockfile,
|
|
1476
|
+
modules,
|
|
1477
|
+
localSources,
|
|
1478
|
+
skillHub: {
|
|
1479
|
+
skillName: input.options.skillName?.trim() || null,
|
|
1480
|
+
targets: [...input.options.targets || []].sort(),
|
|
1481
|
+
moduleAuthoring: Boolean(input.options.moduleAuthoring),
|
|
1482
|
+
generationMode: normalizeGenerationMode(input.options.generationMode)
|
|
1483
|
+
}
|
|
1484
|
+
});
|
|
1485
|
+
}
|
|
1486
|
+
async function hashFileIfExists(path) {
|
|
1487
|
+
try {
|
|
1488
|
+
const contents = await promises.readFile(path);
|
|
1489
|
+
return createHash("sha256").update(contents).digest("hex");
|
|
1490
|
+
} catch {
|
|
1491
|
+
return null;
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1703
1495
|
async function resolveManualContribution(rootDir, contribution) {
|
|
1704
1496
|
const sourceDir = isAbsolute(contribution.sourceDir) ? contribution.sourceDir : resolve(rootDir, contribution.sourceDir);
|
|
1705
1497
|
return normalizeContribution(contribution, sourceDir, sourceDir);
|
|
1706
1498
|
}
|
|
1499
|
+
function collectWorkspaceDiscoverySources(_rootDir, discoveries) {
|
|
1500
|
+
const contributions = [];
|
|
1501
|
+
for (const discovery of discoveries) {
|
|
1502
|
+
if (!isLocalDiscoveryRoot(discovery.packageRoot)) {
|
|
1503
|
+
continue;
|
|
1504
|
+
}
|
|
1505
|
+
for (const skill of discovery.skills) {
|
|
1506
|
+
const sourceDir = resolve(discovery.packageRoot, skill.path);
|
|
1507
|
+
contributions.push(normalizeContribution({
|
|
1508
|
+
packageName: discovery.packageName,
|
|
1509
|
+
version: discovery.version,
|
|
1510
|
+
skillName: skill.name
|
|
1511
|
+
}, sourceDir, discovery.packageRoot));
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
return sortAndDedupeContributions(contributions);
|
|
1515
|
+
}
|
|
1516
|
+
function isLocalDiscoveryRoot(packageRoot) {
|
|
1517
|
+
const normalizedRoot = resolve(packageRoot).replaceAll("\\", "/");
|
|
1518
|
+
return !/(?:^|\/)node_modules(?:\/|$)/.test(normalizedRoot);
|
|
1519
|
+
}
|
|
1520
|
+
async function createLocalSourceFingerprints(contributions) {
|
|
1521
|
+
const fingerprints = await Promise.all(contributions.map(async (contribution) => ({
|
|
1522
|
+
packageName: contribution.packageName,
|
|
1523
|
+
skillName: contribution.skillName,
|
|
1524
|
+
sourceDir: resolve(contribution.sourceDir).replaceAll("\\", "/"),
|
|
1525
|
+
sourceRoot: resolve(contribution.sourceRoot).replaceAll("\\", "/"),
|
|
1526
|
+
sourceKind: contribution.sourceKind,
|
|
1527
|
+
forceIncludeScripts: contribution.forceIncludeScripts,
|
|
1528
|
+
hash: await hashDirectoryTreeIfExists(contribution.sourceDir)
|
|
1529
|
+
})));
|
|
1530
|
+
return fingerprints.sort((a, b) => {
|
|
1531
|
+
const left = `${a.packageName}::${a.skillName}::${a.sourceDir}`;
|
|
1532
|
+
const right = `${b.packageName}::${b.skillName}::${b.sourceDir}`;
|
|
1533
|
+
return left.localeCompare(right);
|
|
1534
|
+
});
|
|
1535
|
+
}
|
|
1536
|
+
async function hashDirectoryTreeIfExists(rootPath) {
|
|
1537
|
+
let rootStat;
|
|
1538
|
+
try {
|
|
1539
|
+
rootStat = await promises.lstat(rootPath);
|
|
1540
|
+
} catch {
|
|
1541
|
+
return null;
|
|
1542
|
+
}
|
|
1543
|
+
if (!rootStat.isDirectory()) {
|
|
1544
|
+
return null;
|
|
1545
|
+
}
|
|
1546
|
+
const hash = createHash("sha256");
|
|
1547
|
+
hash.update("dir:.\n");
|
|
1548
|
+
const entries = await glob("**/*", {
|
|
1549
|
+
cwd: rootPath,
|
|
1550
|
+
dot: true,
|
|
1551
|
+
expandDirectories: false,
|
|
1552
|
+
followSymbolicLinks: false,
|
|
1553
|
+
onlyFiles: false
|
|
1554
|
+
});
|
|
1555
|
+
entries.sort((a, b) => a.localeCompare(b));
|
|
1556
|
+
for (const entry of entries) {
|
|
1557
|
+
const currentPath = join(rootPath, entry);
|
|
1558
|
+
const stat = await promises.lstat(currentPath);
|
|
1559
|
+
const relativePath = relative(rootPath, currentPath).replaceAll("\\", "/");
|
|
1560
|
+
if (stat.isSymbolicLink()) {
|
|
1561
|
+
hash.update(`symlink:${relativePath}:`);
|
|
1562
|
+
hash.update(await promises.readlink(currentPath));
|
|
1563
|
+
hash.update("\n");
|
|
1564
|
+
continue;
|
|
1565
|
+
}
|
|
1566
|
+
if (stat.isDirectory()) {
|
|
1567
|
+
hash.update(`dir:${relativePath}
|
|
1568
|
+
`);
|
|
1569
|
+
continue;
|
|
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
|
+
}
|
|
1580
|
+
return hash.digest("hex");
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
const GENERATION_STATE_FILE = ".state.json";
|
|
1584
|
+
function getGenerationStatePath(generatedSkillRoot) {
|
|
1585
|
+
return join(generatedSkillRoot, GENERATION_STATE_FILE);
|
|
1586
|
+
}
|
|
1587
|
+
async function readGenerationState(generatedSkillRoot) {
|
|
1588
|
+
const statePath = getGenerationStatePath(generatedSkillRoot);
|
|
1589
|
+
if (!await pathExists(statePath)) {
|
|
1590
|
+
return null;
|
|
1591
|
+
}
|
|
1592
|
+
try {
|
|
1593
|
+
return JSON.parse(await promises.readFile(statePath, "utf8"));
|
|
1594
|
+
} catch {
|
|
1595
|
+
return null;
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
async function writeGenerationState(generatedSkillRoot, fingerprint) {
|
|
1599
|
+
const state = {
|
|
1600
|
+
fingerprint,
|
|
1601
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1602
|
+
packageVersion: PACKAGE_VERSION
|
|
1603
|
+
};
|
|
1604
|
+
await writeFileIfChanged(getGenerationStatePath(generatedSkillRoot), `${JSON.stringify(state, null, 2)}
|
|
1605
|
+
`);
|
|
1606
|
+
}
|
|
1607
|
+
async function isGeneratedSkillFresh(generatedSkillRoot, fingerprint) {
|
|
1608
|
+
const [state, entryExists] = await Promise.all([
|
|
1609
|
+
readGenerationState(generatedSkillRoot),
|
|
1610
|
+
pathExists(join(generatedSkillRoot, "SKILL.md"))
|
|
1611
|
+
]);
|
|
1612
|
+
return Boolean(entryExists && state?.fingerprint === fingerprint);
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
const GITHUB_LOOKUP_TIMEOUT_MS = 1500;
|
|
1616
|
+
function resolveStableSkillBuildDir(rootDir, buildDir) {
|
|
1617
|
+
const rel = relative(resolve(rootDir), resolve(buildDir)).replaceAll("\\", "/");
|
|
1618
|
+
if (rel === ".nuxt" || rel.startsWith(".nuxt/")) {
|
|
1619
|
+
return join(resolve(rootDir), ".nuxt");
|
|
1620
|
+
}
|
|
1621
|
+
return buildDir;
|
|
1622
|
+
}
|
|
1623
|
+
function normalizeRelativePath(from, to) {
|
|
1624
|
+
const rel = relative(from, to).replaceAll("\\", "/");
|
|
1625
|
+
if (!rel || rel === ".") {
|
|
1626
|
+
return "./";
|
|
1627
|
+
}
|
|
1628
|
+
return rel.startsWith(".") ? rel : `./${rel}`;
|
|
1629
|
+
}
|
|
1630
|
+
function getGeneratedSkillRoot(buildDir, skillName) {
|
|
1631
|
+
return join(buildDir, "skill-hub", skillName);
|
|
1632
|
+
}
|
|
1707
1633
|
function mergeSkipped(entries, keyFn) {
|
|
1708
1634
|
const byKey = /* @__PURE__ */ new Map();
|
|
1709
1635
|
for (const entry of entries) {
|
|
@@ -1719,6 +1645,366 @@ function mergeSkipped(entries, keyFn) {
|
|
|
1719
1645
|
}
|
|
1720
1646
|
return Array.from(byKey.values()).sort((a, b) => `${a.packageName}::${a.skillName}`.localeCompare(`${b.packageName}::${b.skillName}`));
|
|
1721
1647
|
}
|
|
1648
|
+
async function removeLegacyRootArtifacts(skillRoot) {
|
|
1649
|
+
await Promise.all([
|
|
1650
|
+
promises.rm(join(skillRoot, "references"), { recursive: true, force: true }),
|
|
1651
|
+
promises.rm(join(skillRoot, "manifest.json"), { force: true })
|
|
1652
|
+
]);
|
|
1653
|
+
}
|
|
1654
|
+
async function collectInstalledPackages(nuxt) {
|
|
1655
|
+
const discoveries = [];
|
|
1656
|
+
const installedPackages = [];
|
|
1657
|
+
const seenPackageRoots = /* @__PURE__ */ new Set();
|
|
1658
|
+
const addInstalledPackage = (installedPackage) => {
|
|
1659
|
+
if (!installedPackage || seenPackageRoots.has(installedPackage.packageRoot)) {
|
|
1660
|
+
return;
|
|
1661
|
+
}
|
|
1662
|
+
seenPackageRoots.add(installedPackage.packageRoot);
|
|
1663
|
+
installedPackages.push(installedPackage);
|
|
1664
|
+
const discovered = discoverPackageSkillsFromInstalledPackage(installedPackage, "dist");
|
|
1665
|
+
if (discovered) {
|
|
1666
|
+
discoveries.push(discovered);
|
|
1667
|
+
}
|
|
1668
|
+
};
|
|
1669
|
+
const moduleSpecifiers = (nuxt.options.modules || []).map((entry) => extractModuleSpecifier(entry)).filter((entry) => Boolean(entry));
|
|
1670
|
+
const seenSpecifiers = Array.from(new Set(moduleSpecifiers));
|
|
1671
|
+
for (const specifier of seenSpecifiers) {
|
|
1672
|
+
addInstalledPackage(await discoverInstalledPackageFromSpecifier(specifier, nuxt.options.rootDir));
|
|
1673
|
+
}
|
|
1674
|
+
const layerDirectories = Array.from(new Set(
|
|
1675
|
+
nuxt.options._layers.map((layer) => resolve(layer.cwd || layer.config.rootDir || "")).filter((layerDir) => layerDir && layerDir !== resolve(nuxt.options.rootDir))
|
|
1676
|
+
));
|
|
1677
|
+
for (const layerDirectory of layerDirectories) {
|
|
1678
|
+
addInstalledPackage(await discoverInstalledPackageFromDirectory(layerDirectory));
|
|
1679
|
+
}
|
|
1680
|
+
const localPackageSkills = await discoverLocalPackageSkills(nuxt.options.rootDir);
|
|
1681
|
+
if (localPackageSkills) {
|
|
1682
|
+
discoveries.push(localPackageSkills);
|
|
1683
|
+
}
|
|
1684
|
+
return {
|
|
1685
|
+
discoveries,
|
|
1686
|
+
installedPackages
|
|
1687
|
+
};
|
|
1688
|
+
}
|
|
1689
|
+
async function ensureStableSkillWrappers(input) {
|
|
1690
|
+
const stableBuildDir = resolveStableSkillBuildDir(input.rootDir, input.buildDir);
|
|
1691
|
+
const generatedSkillRoot = getGeneratedSkillRoot(stableBuildDir, input.skillName);
|
|
1692
|
+
const generatedEntryPath = join(generatedSkillRoot, "SKILL.md");
|
|
1693
|
+
for (const target of input.targets) {
|
|
1694
|
+
const { skillRoot } = getTargetSkillRoot(input.exportRoot, target, input.skillName);
|
|
1695
|
+
await ensureDir(skillRoot);
|
|
1696
|
+
await removeLegacyRootArtifacts(skillRoot);
|
|
1697
|
+
const wrapper = createStableSkillWrapper(
|
|
1698
|
+
input.skillName,
|
|
1699
|
+
normalizeRelativePath(skillRoot, generatedEntryPath),
|
|
1700
|
+
normalizeRelativePath(skillRoot, generatedSkillRoot),
|
|
1701
|
+
input.generationMode
|
|
1702
|
+
);
|
|
1703
|
+
await writeFileIfChanged(join(skillRoot, "SKILL.md"), wrapper);
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
async function generateSkillTree(input) {
|
|
1707
|
+
const { nuxt, logger, options, skillName, exportRoot, generationMode } = input;
|
|
1708
|
+
const stableBuildDir = resolveStableSkillBuildDir(nuxt.options.rootDir, nuxt.options.buildDir);
|
|
1709
|
+
const generatedSkillRoot = getGeneratedSkillRoot(stableBuildDir, skillName);
|
|
1710
|
+
const referencesRoot = join(generatedSkillRoot, "references");
|
|
1711
|
+
const nuxtRoot = join(referencesRoot, "nuxt");
|
|
1712
|
+
const vueRoot = join(referencesRoot, "vue");
|
|
1713
|
+
const modulesRoot = join(referencesRoot, "modules");
|
|
1714
|
+
const monorepoScopePath = resolveMonorepoScopePath(nuxt.options.rootDir, exportRoot);
|
|
1715
|
+
const manualContributions = [];
|
|
1716
|
+
const contributionContext = {
|
|
1717
|
+
add: (contribution) => {
|
|
1718
|
+
manualContributions.push(contribution);
|
|
1719
|
+
}
|
|
1720
|
+
};
|
|
1721
|
+
const callSkillContributeHook = nuxt.callHook;
|
|
1722
|
+
await callSkillContributeHook("skill-hub:contribute", contributionContext);
|
|
1723
|
+
const { discoveries, installedPackages } = await collectInstalledPackages(nuxt);
|
|
1724
|
+
const discoveredContributions = await resolveContributions(discoveries);
|
|
1725
|
+
const workspaceDiscoverySources = collectWorkspaceDiscoverySources(nuxt.options.rootDir, discoveries);
|
|
1726
|
+
const resolvedManual = await Promise.all(
|
|
1727
|
+
manualContributions.map((contribution) => resolveManualContribution(nuxt.options.rootDir, contribution))
|
|
1728
|
+
);
|
|
1729
|
+
const fingerprint = await createGenerationFingerprint({
|
|
1730
|
+
packageVersion: PACKAGE_VERSION,
|
|
1731
|
+
rootDir: nuxt.options.rootDir,
|
|
1732
|
+
buildDir: nuxt.options.buildDir,
|
|
1733
|
+
exportRoot,
|
|
1734
|
+
skillName,
|
|
1735
|
+
options,
|
|
1736
|
+
installedPackages,
|
|
1737
|
+
localSources: await createLocalSourceFingerprints([
|
|
1738
|
+
...workspaceDiscoverySources,
|
|
1739
|
+
...sortAndDedupeContributions(resolvedManual)
|
|
1740
|
+
])
|
|
1741
|
+
});
|
|
1742
|
+
if (generationMode === "prepare" && await isGeneratedSkillFresh(generatedSkillRoot, fingerprint)) {
|
|
1743
|
+
logger.info(`Generated ${skillName} skill is already up to date at ${generatedSkillRoot}`);
|
|
1744
|
+
return;
|
|
1745
|
+
}
|
|
1746
|
+
const distResolvedPackages = new Set(discoveredContributions.contributions.map((item) => item.packageName));
|
|
1747
|
+
const remoteIssues = [];
|
|
1748
|
+
const remoteSkipped = [];
|
|
1749
|
+
const remoteContributions = [];
|
|
1750
|
+
const remoteCacheRoot = join(stableBuildDir, "skill-hub-cache");
|
|
1751
|
+
await emptyDir(remoteCacheRoot);
|
|
1752
|
+
const packagesToResolve = installedPackages.filter((pkg) => pkg.packageName !== "nuxt-skill-hub" && !distResolvedPackages.has(pkg.packageName));
|
|
1753
|
+
const totalToResolve = packagesToResolve.length;
|
|
1754
|
+
for (let i = 0; i < packagesToResolve.length; i++) {
|
|
1755
|
+
const pkg = packagesToResolve[i];
|
|
1756
|
+
logger.start(`[${i + 1}/${totalToResolve}] Resolving skills for ${pkg.packageName}...`);
|
|
1757
|
+
const remote = await resolveRemoteContributionsForPackage(pkg, {
|
|
1758
|
+
cacheRoot: remoteCacheRoot,
|
|
1759
|
+
githubLookupTimeoutMs: GITHUB_LOOKUP_TIMEOUT_MS,
|
|
1760
|
+
enableGithubLookup: true
|
|
1761
|
+
});
|
|
1762
|
+
remoteIssues.push(...remote.issues);
|
|
1763
|
+
remoteSkipped.push(...remote.skipped);
|
|
1764
|
+
remoteContributions.push(...remote.contributions);
|
|
1765
|
+
}
|
|
1766
|
+
const validatedRemote = await validateResolvedContributions(sortAndDedupeContributions(remoteContributions));
|
|
1767
|
+
const validatedManual = await validateResolvedContributions(sortAndDedupeContributions(resolvedManual));
|
|
1768
|
+
const contributions = sortAndDedupeContributions([
|
|
1769
|
+
...discoveredContributions.contributions,
|
|
1770
|
+
...validatedRemote.contributions,
|
|
1771
|
+
...validatedManual.contributions
|
|
1772
|
+
]);
|
|
1773
|
+
const validationIssues = [
|
|
1774
|
+
...discoveredContributions.issues,
|
|
1775
|
+
...remoteIssues,
|
|
1776
|
+
...validatedRemote.issues,
|
|
1777
|
+
...validatedManual.issues
|
|
1778
|
+
];
|
|
1779
|
+
const issuesToSkipped = mergeSkipped(
|
|
1780
|
+
validationIssues.map((issue) => ({ packageName: issue.packageName, skillName: issue.skillName, reason: issue.reason, sourceKind: issue.sourceKind })),
|
|
1781
|
+
(entry) => `${entry.packageName}::${entry.skillName}`
|
|
1782
|
+
);
|
|
1783
|
+
const skipped = mergeSkipped(
|
|
1784
|
+
[...issuesToSkipped, ...remoteSkipped],
|
|
1785
|
+
(entry) => `${entry.packageName}::${entry.skillName}::${entry.sourceKind || ""}`
|
|
1786
|
+
);
|
|
1787
|
+
for (const issue of validationIssues) {
|
|
1788
|
+
logger.warn(`[validation] ${issue.packageName}/${issue.skillName}: ${issue.reason}`);
|
|
1789
|
+
}
|
|
1790
|
+
await emptyDir(generatedSkillRoot);
|
|
1791
|
+
await ensureDir(nuxtRoot);
|
|
1792
|
+
await ensureDir(vueRoot);
|
|
1793
|
+
await ensureDir(modulesRoot);
|
|
1794
|
+
const nuxtTemplateFiles = await buildNuxtTemplateFiles(nuxtRoot);
|
|
1795
|
+
for (const file of nuxtTemplateFiles) {
|
|
1796
|
+
await writeFileIfChanged(file.path, file.contents);
|
|
1797
|
+
}
|
|
1798
|
+
const nuxtIndexTemplatePath = join(nuxtRoot, "index.template.md");
|
|
1799
|
+
const nuxtIndexTemplate = await pathExists(nuxtIndexTemplatePath) ? await promises.readFile(nuxtIndexTemplatePath, "utf8") : "";
|
|
1800
|
+
const nuxtIndexContent = await renderAutomdTemplate(nuxtIndexTemplate, nuxtRoot);
|
|
1801
|
+
await writeFileIfChanged(join(nuxtRoot, "index.md"), nuxtIndexContent);
|
|
1802
|
+
logger.start("Fetching Vue best-practices content...");
|
|
1803
|
+
const vueTemplateFiles = await buildVueTemplateFiles(vueRoot, remoteCacheRoot);
|
|
1804
|
+
for (const file of vueTemplateFiles) {
|
|
1805
|
+
await writeFileIfChanged(file.path, file.contents);
|
|
1806
|
+
}
|
|
1807
|
+
const generatedEntries = [];
|
|
1808
|
+
for (const contribution of contributions) {
|
|
1809
|
+
const includeScripts = contribution.forceIncludeScripts;
|
|
1810
|
+
const destination = createModuleDestination(modulesRoot, contribution);
|
|
1811
|
+
await copySkillTree(contribution.sourceDir, destination, includeScripts);
|
|
1812
|
+
const entryPath = relative(generatedSkillRoot, join(destination, "SKILL.md")).replaceAll("\\", "/");
|
|
1813
|
+
generatedEntries.push({
|
|
1814
|
+
packageName: contribution.packageName,
|
|
1815
|
+
version: contribution.version,
|
|
1816
|
+
skillName: contribution.skillName,
|
|
1817
|
+
entryPath,
|
|
1818
|
+
sourceDir: contribution.sourceDir,
|
|
1819
|
+
destination: relative(generatedSkillRoot, destination).replaceAll("\\", "/"),
|
|
1820
|
+
scriptsIncluded: includeScripts,
|
|
1821
|
+
description: contribution.description,
|
|
1822
|
+
sourceKind: contribution.sourceKind,
|
|
1823
|
+
sourceLabel: getSourceLabel(contribution.sourceKind),
|
|
1824
|
+
sourceRepo: contribution.sourceRepo,
|
|
1825
|
+
sourceRef: contribution.sourceRef,
|
|
1826
|
+
sourcePath: contribution.sourcePath,
|
|
1827
|
+
repoUrl: contribution.repoUrl,
|
|
1828
|
+
docsUrl: contribution.docsUrl,
|
|
1829
|
+
official: contribution.official,
|
|
1830
|
+
trustLevel: getTrustLevel(contribution.official),
|
|
1831
|
+
resolver: contribution.resolver
|
|
1832
|
+
});
|
|
1833
|
+
}
|
|
1834
|
+
const nuxtMetadata = await loadNuxtMetadata();
|
|
1835
|
+
await writeFileIfChanged(join(generatedSkillRoot, "SKILL.md"), createSkillEntrypoint(
|
|
1836
|
+
skillName,
|
|
1837
|
+
nuxtMetadata,
|
|
1838
|
+
monorepoScopePath,
|
|
1839
|
+
Boolean(options.moduleAuthoring),
|
|
1840
|
+
generatedEntries,
|
|
1841
|
+
skipped
|
|
1842
|
+
));
|
|
1843
|
+
await writeGenerationState(generatedSkillRoot, fingerprint);
|
|
1844
|
+
logger.success(`Generated ${skillName} skill at ${generatedSkillRoot}`);
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
function isCancelled(value) {
|
|
1848
|
+
return value === null || typeof value === "symbol";
|
|
1849
|
+
}
|
|
1850
|
+
async function runInstallWizard(nuxt) {
|
|
1851
|
+
const logger = useLogger("nuxt-skill-hub");
|
|
1852
|
+
if (process.env.CI || !process.stdout.isTTY) {
|
|
1853
|
+
logger.info("Non-interactive environment detected, skipping install wizard.");
|
|
1854
|
+
return;
|
|
1855
|
+
}
|
|
1856
|
+
const consola = createConsola();
|
|
1857
|
+
const rootDir = nuxt.options.rootDir;
|
|
1858
|
+
const exportRoot = await resolveExportRoot(rootDir);
|
|
1859
|
+
const skillName = await deriveSkillName(rootDir);
|
|
1860
|
+
const pendingWrites = [];
|
|
1861
|
+
consola.box(
|
|
1862
|
+
`nuxt-skill-hub \u2014 First-time setup
|
|
1863
|
+
Let's configure AI agent skills for your Nuxt project.`
|
|
1864
|
+
);
|
|
1865
|
+
const currentTarget = detectCurrentTarget();
|
|
1866
|
+
let selectedTargets;
|
|
1867
|
+
if (currentTarget) {
|
|
1868
|
+
consola.info(`Running inside ${currentTarget}, auto-selecting it.`);
|
|
1869
|
+
selectedTargets = [currentTarget];
|
|
1870
|
+
} else {
|
|
1871
|
+
const detectedTargets = detectInstalledTargets();
|
|
1872
|
+
const allTargets = getSupportedTargets();
|
|
1873
|
+
if (!detectedTargets.length && !allTargets.length) {
|
|
1874
|
+
consola.warn("No AI agents detected. Skills will be generated when an agent is detected.");
|
|
1875
|
+
consola.info("Supported agents are sourced from unagent.");
|
|
1876
|
+
return;
|
|
1877
|
+
}
|
|
1878
|
+
consola.info(`Detected agents: ${detectedTargets.length ? detectedTargets.join(", ") : "none"}`);
|
|
1879
|
+
selectedTargets = detectedTargets;
|
|
1880
|
+
if (detectedTargets.length > 1) {
|
|
1881
|
+
const keepAll = await consola.prompt("Generate skills for all detected agents?", {
|
|
1882
|
+
type: "confirm",
|
|
1883
|
+
initial: true,
|
|
1884
|
+
cancel: "null"
|
|
1885
|
+
});
|
|
1886
|
+
if (isCancelled(keepAll)) {
|
|
1887
|
+
consola.info("Setup cancelled.");
|
|
1888
|
+
return;
|
|
1889
|
+
}
|
|
1890
|
+
if (!keepAll) {
|
|
1891
|
+
const chosen = await consola.prompt("Select agents to generate skills for:", {
|
|
1892
|
+
type: "multiselect",
|
|
1893
|
+
options: detectedTargets.map((t) => ({ label: t, value: t })),
|
|
1894
|
+
initial: detectedTargets,
|
|
1895
|
+
required: true,
|
|
1896
|
+
cancel: "null"
|
|
1897
|
+
});
|
|
1898
|
+
if (isCancelled(chosen)) {
|
|
1899
|
+
consola.info("Setup cancelled.");
|
|
1900
|
+
return;
|
|
1901
|
+
}
|
|
1902
|
+
selectedTargets = chosen.map((c) => c.value);
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
const moduleSpecifiers = (nuxt.options.modules || []).map((entry) => extractModuleSpecifier(entry)).filter((entry) => Boolean(entry));
|
|
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
|
+
}
|
|
1915
|
+
if (installedModulePackages.length) {
|
|
1916
|
+
consola.info(`Detected ${installedModulePackages.length} installed module package(s):`);
|
|
1917
|
+
for (const packageName of installedModulePackages) {
|
|
1918
|
+
consola.log(` ${packageName}`);
|
|
1919
|
+
}
|
|
1920
|
+
consola.info("Module skills and metadata routers are resolved automatically at build time.");
|
|
1921
|
+
} else {
|
|
1922
|
+
consola.info("No installed module entries were discovered.");
|
|
1923
|
+
}
|
|
1924
|
+
for (const conflict of detectConflictingSkills(selectedTargets, skillName)) {
|
|
1925
|
+
consola.warn(formatConflictWarning(conflict));
|
|
1926
|
+
}
|
|
1927
|
+
const hintBlock = `${MANAGED_HINT_START}
|
|
1928
|
+
Use the \`${skillName}\` skill as the Nuxt router/entrypoint for tasks in this repository.
|
|
1929
|
+
${MANAGED_HINT_END}`;
|
|
1930
|
+
const hintFileChoice = await consola.prompt("Add a skill hint to help agents discover the skill?", {
|
|
1931
|
+
type: "select",
|
|
1932
|
+
options: [
|
|
1933
|
+
{ label: "CLAUDE.md", value: "claude", hint: "Claude Code project instructions" },
|
|
1934
|
+
{ label: "AGENTS.md", value: "agents", hint: "Universal agent instructions" },
|
|
1935
|
+
{ label: "Both", value: "both" },
|
|
1936
|
+
{ label: "Skip", value: "skip" }
|
|
1937
|
+
],
|
|
1938
|
+
cancel: "null"
|
|
1939
|
+
});
|
|
1940
|
+
if (isCancelled(hintFileChoice)) {
|
|
1941
|
+
consola.info("Setup cancelled.");
|
|
1942
|
+
return;
|
|
1943
|
+
}
|
|
1944
|
+
const addToFile = async (filename) => {
|
|
1945
|
+
const filePath = join(exportRoot, filename);
|
|
1946
|
+
const exists = await pathExists(filePath);
|
|
1947
|
+
const current = exists ? readFileSync(filePath, "utf8") : "";
|
|
1948
|
+
const regex = new RegExp(`${MANAGED_HINT_START}[\\s\\S]*?${MANAGED_HINT_END}`);
|
|
1949
|
+
let newContent;
|
|
1950
|
+
if (regex.test(current)) {
|
|
1951
|
+
newContent = current.replace(regex, hintBlock);
|
|
1952
|
+
} else if (current) {
|
|
1953
|
+
newContent = `${current.trimEnd()}
|
|
1954
|
+
|
|
1955
|
+
${hintBlock}
|
|
1956
|
+
`;
|
|
1957
|
+
} else {
|
|
1958
|
+
newContent = `${hintBlock}
|
|
1959
|
+
`;
|
|
1960
|
+
}
|
|
1961
|
+
pendingWrites.push({
|
|
1962
|
+
file: filename,
|
|
1963
|
+
absPath: filePath,
|
|
1964
|
+
preview: `+ Use the \`${skillName}\` skill as the Nuxt router/entrypoint for tasks in this repository.`,
|
|
1965
|
+
content: newContent,
|
|
1966
|
+
action: exists ? "modify" : "create"
|
|
1967
|
+
});
|
|
1968
|
+
};
|
|
1969
|
+
if (hintFileChoice === "claude" || hintFileChoice === "both") {
|
|
1970
|
+
await addToFile("CLAUDE.md");
|
|
1971
|
+
}
|
|
1972
|
+
if (hintFileChoice === "agents" || hintFileChoice === "both") {
|
|
1973
|
+
await addToFile("AGENTS.md");
|
|
1974
|
+
}
|
|
1975
|
+
if (!pendingWrites.length) {
|
|
1976
|
+
consola.success("No file changes needed. Setup complete!");
|
|
1977
|
+
consola.info("Run `nuxt prepare` to generate the build-dir skill output.");
|
|
1978
|
+
return;
|
|
1979
|
+
}
|
|
1980
|
+
consola.log("");
|
|
1981
|
+
consola.info("Planned changes:");
|
|
1982
|
+
for (const write of pendingWrites) {
|
|
1983
|
+
consola.box({ title: write.file, message: colorize("green", write.preview), style: { borderColor: "green" } });
|
|
1984
|
+
}
|
|
1985
|
+
const confirm = await consola.prompt("Apply these changes?", {
|
|
1986
|
+
type: "confirm",
|
|
1987
|
+
initial: true,
|
|
1988
|
+
cancel: "null"
|
|
1989
|
+
});
|
|
1990
|
+
if (isCancelled(confirm) || !confirm) {
|
|
1991
|
+
consola.info("Setup cancelled. No files were modified.");
|
|
1992
|
+
return;
|
|
1993
|
+
}
|
|
1994
|
+
for (const write of pendingWrites) {
|
|
1995
|
+
writeFileSync(write.absPath, write.content, "utf8");
|
|
1996
|
+
const verb = write.action === "create" ? "Created" : "Updated";
|
|
1997
|
+
consola.success(`${verb} ${write.file}`);
|
|
1998
|
+
}
|
|
1999
|
+
consola.log("");
|
|
2000
|
+
consola.box(
|
|
2001
|
+
`Setup complete!
|
|
2002
|
+
|
|
2003
|
+
Run \`nuxt prepare\` to generate the build-dir skill output.
|
|
2004
|
+
Configure \`skillHub.skillName\`, \`skillHub.targets\`, or set \`skillHub.generationMode: 'manual'\` in nuxt.config.ts if needed.`
|
|
2005
|
+
);
|
|
2006
|
+
}
|
|
2007
|
+
|
|
1722
2008
|
const module$1 = defineNuxtModule({
|
|
1723
2009
|
meta: {
|
|
1724
2010
|
name: "nuxt-skill-hub",
|
|
@@ -1734,7 +2020,8 @@ const module$1 = defineNuxtModule({
|
|
|
1734
2020
|
defaults: {
|
|
1735
2021
|
skillName: "",
|
|
1736
2022
|
targets: [],
|
|
1737
|
-
moduleAuthoring: false
|
|
2023
|
+
moduleAuthoring: false,
|
|
2024
|
+
generationMode: DEFAULT_SKILL_HUB_GENERATION_MODE
|
|
1738
2025
|
},
|
|
1739
2026
|
async setup(options, nuxt) {
|
|
1740
2027
|
const logger = useLogger("nuxt-skill-hub");
|
|
@@ -1752,173 +2039,52 @@ const module$1 = defineNuxtModule({
|
|
|
1752
2039
|
}
|
|
1753
2040
|
resolvedSkillName = await deriveSkillName(nuxt.options.rootDir);
|
|
1754
2041
|
}
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
const discoveries = [];
|
|
1767
|
-
const installedPackages = [];
|
|
1768
|
-
const seenPackageRoots = /* @__PURE__ */ new Set();
|
|
1769
|
-
const addInstalledPackage = (installedPackage) => {
|
|
1770
|
-
if (!installedPackage || seenPackageRoots.has(installedPackage.packageRoot)) {
|
|
1771
|
-
return;
|
|
1772
|
-
}
|
|
1773
|
-
seenPackageRoots.add(installedPackage.packageRoot);
|
|
1774
|
-
installedPackages.push(installedPackage);
|
|
1775
|
-
const discovered = discoverPackageSkillsFromInstalledPackage(installedPackage, "dist");
|
|
1776
|
-
if (discovered) {
|
|
1777
|
-
discoveries.push(discovered);
|
|
1778
|
-
}
|
|
1779
|
-
};
|
|
1780
|
-
const moduleSpecifiers = (nuxt.options.modules || []).map((entry) => extractModuleSpecifier(entry)).filter((entry) => Boolean(entry));
|
|
1781
|
-
const seenSpecifiers = Array.from(new Set(moduleSpecifiers));
|
|
1782
|
-
for (const specifier of seenSpecifiers) {
|
|
1783
|
-
addInstalledPackage(await discoverInstalledPackageFromSpecifier(specifier, nuxt.options.rootDir));
|
|
1784
|
-
}
|
|
1785
|
-
const layerDirectories = Array.from(new Set(
|
|
1786
|
-
nuxt.options._layers.map((layer) => resolve(layer.cwd || layer.config.rootDir || "")).filter((layerDir) => layerDir && layerDir !== resolve(nuxt.options.rootDir))
|
|
1787
|
-
));
|
|
1788
|
-
for (const layerDirectory of layerDirectories) {
|
|
1789
|
-
addInstalledPackage(await discoverInstalledPackageFromDirectory(layerDirectory));
|
|
1790
|
-
}
|
|
1791
|
-
const localPackageSkills = await discoverLocalPackageSkills(nuxt.options.rootDir);
|
|
1792
|
-
if (localPackageSkills) {
|
|
1793
|
-
discoveries.push(localPackageSkills);
|
|
1794
|
-
}
|
|
1795
|
-
const discoveredContributions = await resolveContributions(discoveries);
|
|
1796
|
-
const distResolvedPackages = new Set(discoveredContributions.contributions.map((item) => item.packageName));
|
|
1797
|
-
const remoteIssues = [];
|
|
1798
|
-
const remoteSkipped = [];
|
|
1799
|
-
const remoteContributions = [];
|
|
1800
|
-
const remoteCacheRoot = join(nuxt.options.rootDir, ".nuxt", "skill-hub-cache");
|
|
1801
|
-
await emptyDir(remoteCacheRoot);
|
|
1802
|
-
const packagesToResolve = installedPackages.filter((pkg) => pkg.packageName !== "nuxt-skill-hub" && !distResolvedPackages.has(pkg.packageName));
|
|
1803
|
-
const totalToResolve = packagesToResolve.length;
|
|
1804
|
-
for (let i = 0; i < packagesToResolve.length; i++) {
|
|
1805
|
-
const pkg = packagesToResolve[i];
|
|
1806
|
-
logger.start(`[${i + 1}/${totalToResolve}] Resolving skills for ${pkg.packageName}...`);
|
|
1807
|
-
const remote = await resolveRemoteContributionsForPackage(pkg, {
|
|
1808
|
-
cacheRoot: remoteCacheRoot,
|
|
1809
|
-
githubLookupTimeoutMs: GITHUB_LOOKUP_TIMEOUT_MS,
|
|
1810
|
-
enableGithubLookup: true
|
|
1811
|
-
});
|
|
1812
|
-
remoteIssues.push(...remote.issues);
|
|
1813
|
-
remoteSkipped.push(...remote.skipped);
|
|
1814
|
-
remoteContributions.push(...remote.contributions);
|
|
1815
|
-
}
|
|
1816
|
-
const validatedRemote = await validateResolvedContributions(sortAndDedupeContributions(remoteContributions));
|
|
1817
|
-
const resolvedManual = await Promise.all(
|
|
1818
|
-
manualContributions.map((contribution) => resolveManualContribution(nuxt.options.rootDir, contribution))
|
|
1819
|
-
);
|
|
1820
|
-
const validatedManual = await validateResolvedContributions(sortAndDedupeContributions(resolvedManual));
|
|
1821
|
-
const contributions = sortAndDedupeContributions([
|
|
1822
|
-
...discoveredContributions.contributions,
|
|
1823
|
-
...validatedRemote.contributions,
|
|
1824
|
-
...validatedManual.contributions
|
|
1825
|
-
]);
|
|
1826
|
-
const validationIssues = [
|
|
1827
|
-
...discoveredContributions.issues,
|
|
1828
|
-
...remoteIssues,
|
|
1829
|
-
...validatedRemote.issues,
|
|
1830
|
-
...validatedManual.issues
|
|
1831
|
-
];
|
|
1832
|
-
const issuesToSkipped = mergeSkipped(
|
|
1833
|
-
validationIssues.map((i) => ({ packageName: i.packageName, skillName: i.skillName, reason: i.reason, sourceKind: i.sourceKind })),
|
|
1834
|
-
(e) => `${e.packageName}::${e.skillName}`
|
|
1835
|
-
);
|
|
1836
|
-
const skipped = mergeSkipped(
|
|
1837
|
-
[...issuesToSkipped, ...remoteSkipped],
|
|
1838
|
-
(e) => `${e.packageName}::${e.skillName}::${e.sourceKind || ""}`
|
|
1839
|
-
);
|
|
1840
|
-
for (const issue of validationIssues) {
|
|
1841
|
-
logger.warn(`[validation] ${issue.packageName}/${issue.skillName}: ${issue.reason}`);
|
|
1842
|
-
}
|
|
1843
|
-
const { targets, invalidTargets } = resolveTargets(options.targets || []);
|
|
1844
|
-
for (const invalidTarget of invalidTargets) {
|
|
1845
|
-
if (invalidTarget.reason === "unknown-target") {
|
|
1846
|
-
logger.warn(`Target "${invalidTarget.target}" is unknown in unagent and was skipped.`);
|
|
1847
|
-
continue;
|
|
1848
|
-
}
|
|
1849
|
-
logger.warn(`Target "${invalidTarget.target}" has no skillsDir in unagent and was skipped.`);
|
|
1850
|
-
}
|
|
1851
|
-
if (!targets.length) {
|
|
1852
|
-
logger.warn("No detected targets. Set skillHub.targets to force generation for specific agents.");
|
|
1853
|
-
return;
|
|
1854
|
-
}
|
|
1855
|
-
for (const conflict of detectConflictingSkills(targets, resolvedSkillName)) {
|
|
1856
|
-
logger.warn(formatConflictWarning(conflict));
|
|
1857
|
-
}
|
|
1858
|
-
const nuxtMetadata = await loadNuxtMetadata();
|
|
1859
|
-
for (let targetIdx = 0; targetIdx < targets.length; targetIdx++) {
|
|
1860
|
-
const target = targets[targetIdx];
|
|
1861
|
-
const { skillRoot } = getTargetSkillRoot(exportRoot, target, resolvedSkillName);
|
|
1862
|
-
const referencesRoot = join(skillRoot, "references");
|
|
1863
|
-
const nuxtRoot = join(referencesRoot, "nuxt");
|
|
1864
|
-
const vueRoot = join(referencesRoot, "vue");
|
|
1865
|
-
const modulesRoot = join(referencesRoot, "modules");
|
|
1866
|
-
await emptyDir(skillRoot);
|
|
1867
|
-
await ensureDir(nuxtRoot);
|
|
1868
|
-
await ensureDir(vueRoot);
|
|
1869
|
-
await ensureDir(modulesRoot);
|
|
1870
|
-
const nuxtTemplateFiles = await buildNuxtTemplateFiles(nuxtRoot);
|
|
1871
|
-
for (const file of nuxtTemplateFiles) {
|
|
1872
|
-
await writeFileIfChanged(file.path, file.contents);
|
|
1873
|
-
}
|
|
1874
|
-
const nuxtIndexTemplatePath = join(nuxtRoot, "index.template.md");
|
|
1875
|
-
const nuxtIndexTemplate = await pathExists(nuxtIndexTemplatePath) ? await promises.readFile(nuxtIndexTemplatePath, "utf8") : "";
|
|
1876
|
-
const nuxtIndexContent = await renderAutomdTemplate(nuxtIndexTemplate, nuxtRoot);
|
|
1877
|
-
await writeFileIfChanged(join(nuxtRoot, "index.md"), nuxtIndexContent);
|
|
1878
|
-
if (targetIdx === 0)
|
|
1879
|
-
logger.start("Fetching Vue best-practices content...");
|
|
1880
|
-
const vueTemplateFiles = await buildVueTemplateFiles(vueRoot, remoteCacheRoot);
|
|
1881
|
-
for (const file of vueTemplateFiles) {
|
|
1882
|
-
await writeFileIfChanged(file.path, file.contents);
|
|
1883
|
-
}
|
|
1884
|
-
const generatedEntries = [];
|
|
1885
|
-
for (const contribution of contributions) {
|
|
1886
|
-
const includeScripts = contribution.forceIncludeScripts;
|
|
1887
|
-
const destination = createModuleDestination(modulesRoot, contribution);
|
|
1888
|
-
await copySkillTree(contribution.sourceDir, destination, includeScripts);
|
|
1889
|
-
const entryPath = relative(skillRoot, join(destination, "SKILL.md"));
|
|
1890
|
-
generatedEntries.push({
|
|
1891
|
-
packageName: contribution.packageName,
|
|
1892
|
-
version: contribution.version,
|
|
1893
|
-
skillName: contribution.skillName,
|
|
1894
|
-
entryPath,
|
|
1895
|
-
sourceDir: contribution.sourceDir,
|
|
1896
|
-
destination: relative(skillRoot, destination),
|
|
1897
|
-
scriptsIncluded: includeScripts,
|
|
1898
|
-
description: contribution.description,
|
|
1899
|
-
sourceKind: contribution.sourceKind,
|
|
1900
|
-
sourceLabel: getSourceLabel(contribution.sourceKind),
|
|
1901
|
-
sourceRepo: contribution.sourceRepo,
|
|
1902
|
-
sourceRef: contribution.sourceRef,
|
|
1903
|
-
sourcePath: contribution.sourcePath,
|
|
1904
|
-
repoUrl: contribution.repoUrl,
|
|
1905
|
-
docsUrl: contribution.docsUrl,
|
|
1906
|
-
official: contribution.official,
|
|
1907
|
-
trustLevel: getTrustLevel(contribution.official),
|
|
1908
|
-
resolver: contribution.resolver
|
|
1909
|
-
});
|
|
1910
|
-
}
|
|
1911
|
-
await writeFileIfChanged(join(skillRoot, "SKILL.md"), createSkillEntrypoint(
|
|
1912
|
-
resolvedSkillName,
|
|
1913
|
-
nuxtMetadata,
|
|
1914
|
-
monorepoScopePath,
|
|
1915
|
-
Boolean(options.moduleAuthoring),
|
|
1916
|
-
generatedEntries,
|
|
1917
|
-
skipped
|
|
1918
|
-
));
|
|
1919
|
-
logger.success(`Generated ${resolvedSkillName} skill at ${skillRoot}`);
|
|
2042
|
+
const exportRoot = await resolveExportRoot(nuxt.options.rootDir);
|
|
2043
|
+
const rawGenerationMode = options.generationMode;
|
|
2044
|
+
const generationMode = normalizeGenerationMode(rawGenerationMode);
|
|
2045
|
+
const { targets, invalidTargets } = resolveTargets(options.targets || []);
|
|
2046
|
+
if (rawGenerationMode && rawGenerationMode !== generationMode) {
|
|
2047
|
+
logger.warn(`Invalid skillHub.generationMode "${rawGenerationMode}". Falling back to "prepare".`);
|
|
2048
|
+
}
|
|
2049
|
+
for (const invalidTarget of invalidTargets) {
|
|
2050
|
+
if (invalidTarget.reason === "unknown-target") {
|
|
2051
|
+
logger.warn(`Target "${invalidTarget.target}" is unknown in unagent and was skipped.`);
|
|
2052
|
+
continue;
|
|
1920
2053
|
}
|
|
2054
|
+
logger.warn(`Target "${invalidTarget.target}" has no skillsDir in unagent and was skipped.`);
|
|
2055
|
+
}
|
|
2056
|
+
if (!targets.length) {
|
|
2057
|
+
logger.warn("No detected targets. Set skillHub.targets to force generation for specific agents.");
|
|
2058
|
+
return;
|
|
2059
|
+
}
|
|
2060
|
+
await ensureStableSkillWrappers({
|
|
2061
|
+
rootDir: nuxt.options.rootDir,
|
|
2062
|
+
exportRoot,
|
|
2063
|
+
buildDir: nuxt.options.buildDir,
|
|
2064
|
+
skillName: resolvedSkillName,
|
|
2065
|
+
targets,
|
|
2066
|
+
generationMode
|
|
1921
2067
|
});
|
|
2068
|
+
for (const conflict of detectConflictingSkills(targets, resolvedSkillName)) {
|
|
2069
|
+
logger.warn(formatConflictWarning(conflict));
|
|
2070
|
+
}
|
|
2071
|
+
if (generationMode === "manual") {
|
|
2072
|
+
return;
|
|
2073
|
+
}
|
|
2074
|
+
const runGeneration = async () => {
|
|
2075
|
+
await generateSkillTree({
|
|
2076
|
+
nuxt,
|
|
2077
|
+
logger,
|
|
2078
|
+
options,
|
|
2079
|
+
skillName: resolvedSkillName,
|
|
2080
|
+
exportRoot,
|
|
2081
|
+
generationMode
|
|
2082
|
+
});
|
|
2083
|
+
};
|
|
2084
|
+
if (generationMode !== "prepare" || !nuxt.options._prepare) {
|
|
2085
|
+
return;
|
|
2086
|
+
}
|
|
2087
|
+
nuxt.hook("prepare:types", runGeneration);
|
|
1922
2088
|
}
|
|
1923
2089
|
});
|
|
1924
2090
|
|