admin-ui-starter-kit 0.1.1 → 0.1.3
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/.agents/skills/admin-ui-consumer-migration/SKILL.md +126 -0
- package/.agents/skills/component-library-rules/SKILL.md +17 -2
- package/.agents/skills/component-library-rules/references/import-paths.md +6 -0
- package/AGENTS.md +11 -6
- package/COMPONENT_SELECTION.md +115 -0
- package/INTEGRATION.md +10 -0
- package/MIGRATION.md +235 -0
- package/PUBLISHING.md +8 -3
- package/README.md +34 -12
- package/package.json +254 -23
- package/scripts/audit-consumer.mjs +326 -0
- package/scripts/install-skill.mjs +50 -36
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* audit-consumer — checks a consuming app for common admin-ui-starter-kit
|
|
4
|
+
* migration mistakes:
|
|
5
|
+
*
|
|
6
|
+
* - stale copied import paths such as @/components/ui/base/buttons
|
|
7
|
+
* - invalid package imports not listed in package.json exports
|
|
8
|
+
* - local files that only re-export package components
|
|
9
|
+
*
|
|
10
|
+
* Run from a consumer app:
|
|
11
|
+
*
|
|
12
|
+
* npx admin-ui-starter-kit-audit
|
|
13
|
+
* npx admin-ui-starter-kit-audit --fix
|
|
14
|
+
*/
|
|
15
|
+
import { promises as fs } from 'node:fs';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
import { fileURLToPath } from 'node:url';
|
|
18
|
+
|
|
19
|
+
const PACKAGE_NAME = 'admin-ui-starter-kit';
|
|
20
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
21
|
+
const PACKAGE_ROOT = path.resolve(path.dirname(__filename), '..');
|
|
22
|
+
const PACKAGE_JSON = JSON.parse(
|
|
23
|
+
await fs.readFile(path.join(PACKAGE_ROOT, 'package.json'), 'utf8'),
|
|
24
|
+
);
|
|
25
|
+
const PUBLIC_EXPORTS = new Set(Object.keys(PACKAGE_JSON.exports ?? {}));
|
|
26
|
+
|
|
27
|
+
const DEFAULT_PATHS = ['src', 'app', 'resources/js', 'components', 'pages'];
|
|
28
|
+
const CODE_EXTENSIONS = new Set(['.cjs', '.cts', '.js', '.jsx', '.mjs', '.mts', '.ts', '.tsx']);
|
|
29
|
+
const SKIP_DIRS = new Set([
|
|
30
|
+
'.git',
|
|
31
|
+
'.next',
|
|
32
|
+
'.nuxt',
|
|
33
|
+
'.turbo',
|
|
34
|
+
'.vercel',
|
|
35
|
+
'build',
|
|
36
|
+
'coverage',
|
|
37
|
+
'dist',
|
|
38
|
+
'node_modules',
|
|
39
|
+
'public',
|
|
40
|
+
'vendor',
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
function parseArgs(argv) {
|
|
44
|
+
const opts = { fix: false, json: false, help: false, paths: [] };
|
|
45
|
+
for (const arg of argv.slice(2)) {
|
|
46
|
+
if (arg === '--fix') {
|
|
47
|
+
opts.fix = true;
|
|
48
|
+
} else if (arg === '--json') {
|
|
49
|
+
opts.json = true;
|
|
50
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
51
|
+
opts.help = true;
|
|
52
|
+
} else if (arg.startsWith('--paths=')) {
|
|
53
|
+
opts.paths.push(...arg.slice('--paths='.length).split(',').filter(Boolean));
|
|
54
|
+
} else if (arg.startsWith('-')) {
|
|
55
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
56
|
+
} else {
|
|
57
|
+
opts.paths.push(arg);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return opts;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function printHelp() {
|
|
64
|
+
console.log(
|
|
65
|
+
`Usage: npx ${PACKAGE_NAME}-audit [options] [paths...]\n\n` +
|
|
66
|
+
`Audits a consuming app for stale local UI imports, invalid package subpaths,\n` +
|
|
67
|
+
`and local files that only mirror ${PACKAGE_NAME} exports.\n\n` +
|
|
68
|
+
`Options:\n` +
|
|
69
|
+
` --fix Rewrite known copied import paths to package imports\n` +
|
|
70
|
+
` --paths=a,b Comma-separated paths to scan\n` +
|
|
71
|
+
` --json Print machine-readable JSON\n` +
|
|
72
|
+
` --help, -h Show this message\n`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function exists(p) {
|
|
77
|
+
try {
|
|
78
|
+
await fs.access(p);
|
|
79
|
+
return true;
|
|
80
|
+
} catch {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function walk(dir, files = []) {
|
|
86
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
87
|
+
for (const entry of entries) {
|
|
88
|
+
if (SKIP_DIRS.has(entry.name)) continue;
|
|
89
|
+
const full = path.join(dir, entry.name);
|
|
90
|
+
if (entry.isDirectory()) {
|
|
91
|
+
await walk(full, files);
|
|
92
|
+
} else if (entry.isFile() && CODE_EXTENSIONS.has(path.extname(entry.name))) {
|
|
93
|
+
files.push(full);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return files;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function getScanFiles(cwd, requestedPaths) {
|
|
100
|
+
const roots = requestedPaths.length > 0 ? requestedPaths : DEFAULT_PATHS;
|
|
101
|
+
const files = [];
|
|
102
|
+
for (const relative of roots) {
|
|
103
|
+
const full = path.resolve(cwd, relative);
|
|
104
|
+
if (!(await exists(full))) continue;
|
|
105
|
+
const stat = await fs.stat(full);
|
|
106
|
+
if (stat.isDirectory()) {
|
|
107
|
+
await walk(full, files);
|
|
108
|
+
} else if (stat.isFile() && CODE_EXTENSIONS.has(path.extname(full))) {
|
|
109
|
+
files.push(full);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return [...new Set(files)].sort();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function getImportSpecifiers(source) {
|
|
116
|
+
const specifiers = [];
|
|
117
|
+
const patterns = [
|
|
118
|
+
/\bimport\s+(?:type\s+)?(?:[^'"]*?\s+from\s+)?['"]([^'"]+)['"]/g,
|
|
119
|
+
/\bexport\s+(?:type\s+)?[^'"]*?\s+from\s+['"]([^'"]+)['"]/g,
|
|
120
|
+
/\bimport\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
121
|
+
];
|
|
122
|
+
for (const pattern of patterns) {
|
|
123
|
+
for (const match of source.matchAll(pattern)) specifiers.push(match[1]);
|
|
124
|
+
}
|
|
125
|
+
return specifiers;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function normalizePackageKey(specifier) {
|
|
129
|
+
if (specifier === PACKAGE_NAME) return '.';
|
|
130
|
+
if (!specifier.startsWith(`${PACKAGE_NAME}/`)) return null;
|
|
131
|
+
return `.${specifier.slice(PACKAGE_NAME.length)}`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function packageImportIssue(specifier) {
|
|
135
|
+
const key = normalizePackageKey(specifier);
|
|
136
|
+
if (key === null) return null;
|
|
137
|
+
if (PUBLIC_EXPORTS.has(key)) return null;
|
|
138
|
+
return {
|
|
139
|
+
kind: 'invalid-package-import',
|
|
140
|
+
specifier,
|
|
141
|
+
message: `${specifier} is not a public ${PACKAGE_NAME} export`,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function mapOldSpecifier(specifier) {
|
|
146
|
+
const typographyPrefixes = [
|
|
147
|
+
'@/components/ui/base/typography',
|
|
148
|
+
'@/components/ui/typography',
|
|
149
|
+
'@/components/typography',
|
|
150
|
+
];
|
|
151
|
+
if (typographyPrefixes.some((prefix) => specifier === prefix || specifier.startsWith(`${prefix}/`))) {
|
|
152
|
+
return `${PACKAGE_NAME}/typography`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const layerMappings = [
|
|
156
|
+
{ prefix: '@/components/ui/base/', target: 'base' },
|
|
157
|
+
{ prefix: '@/components/base/', target: 'base' },
|
|
158
|
+
{ prefix: '@/components/composed/', target: 'composed' },
|
|
159
|
+
{ prefix: '@/components/features/', target: 'features' },
|
|
160
|
+
{ prefix: '@/components/layout/', target: 'layout' },
|
|
161
|
+
];
|
|
162
|
+
|
|
163
|
+
for (const mapping of layerMappings) {
|
|
164
|
+
if (!specifier.startsWith(mapping.prefix)) continue;
|
|
165
|
+
const rest = specifier.slice(mapping.prefix.length);
|
|
166
|
+
const parts = rest.split('/').filter(Boolean);
|
|
167
|
+
if (parts.length === 0) continue;
|
|
168
|
+
|
|
169
|
+
if (mapping.target === 'base' && parts[0] === 'display' && parts[1] === 'metadata') {
|
|
170
|
+
return `${PACKAGE_NAME}/base/display/metadata`;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const candidate = `${PACKAGE_NAME}/${mapping.target}/${parts[0]}`;
|
|
174
|
+
if (PUBLIC_EXPORTS.has(normalizePackageKey(candidate))) return candidate;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (specifier === '@/components/layout') return `${PACKAGE_NAME}/layout`;
|
|
178
|
+
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function isKnownCopiedImport(specifier) {
|
|
183
|
+
return mapOldSpecifier(specifier) !== null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function isMirrorFile(source) {
|
|
187
|
+
const withoutComments = source
|
|
188
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
189
|
+
.replace(/^\s*\/\/.*$/gm, '')
|
|
190
|
+
.trim();
|
|
191
|
+
if (!withoutComments) return false;
|
|
192
|
+
|
|
193
|
+
const statements = withoutComments
|
|
194
|
+
.split(';')
|
|
195
|
+
.map((statement) => statement.trim())
|
|
196
|
+
.filter(Boolean);
|
|
197
|
+
if (statements.length === 0) return false;
|
|
198
|
+
|
|
199
|
+
return statements.every((statement) =>
|
|
200
|
+
/^export\s+(?:type\s+)?(?:\{[\s\S]*\}|\*)\s+from\s+['"]admin-ui-starter-kit(?:\/[^'"]+)?['"]$/m.test(
|
|
201
|
+
statement,
|
|
202
|
+
),
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function replaceSpecifiers(source) {
|
|
207
|
+
let changed = false;
|
|
208
|
+
const next = source.replace(
|
|
209
|
+
/(['"])(@\/components\/(?:ui\/base|ui\/typography|base|typography|composed|features|layout)(?:\/[^'"]*)?)\1/g,
|
|
210
|
+
(match, quote, specifier) => {
|
|
211
|
+
const replacement = mapOldSpecifier(specifier);
|
|
212
|
+
if (!replacement) return match;
|
|
213
|
+
changed = true;
|
|
214
|
+
return `${quote}${replacement}${quote}`;
|
|
215
|
+
},
|
|
216
|
+
);
|
|
217
|
+
return { changed, source: next };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async function audit(opts) {
|
|
221
|
+
const cwd = process.cwd();
|
|
222
|
+
const files = await getScanFiles(cwd, opts.paths);
|
|
223
|
+
const issues = [];
|
|
224
|
+
const fixedFiles = [];
|
|
225
|
+
|
|
226
|
+
for (const file of files) {
|
|
227
|
+
const source = await fs.readFile(file, 'utf8');
|
|
228
|
+
const relative = path.relative(cwd, file);
|
|
229
|
+
const specifiers = getImportSpecifiers(source);
|
|
230
|
+
|
|
231
|
+
for (const specifier of specifiers) {
|
|
232
|
+
if (isKnownCopiedImport(specifier)) {
|
|
233
|
+
issues.push({
|
|
234
|
+
kind: 'stale-local-import',
|
|
235
|
+
file: relative,
|
|
236
|
+
specifier,
|
|
237
|
+
suggested: mapOldSpecifier(specifier),
|
|
238
|
+
message: `${specifier} should import directly from ${PACKAGE_NAME}`,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const invalidPackageImport = packageImportIssue(specifier);
|
|
243
|
+
if (invalidPackageImport) {
|
|
244
|
+
issues.push({ ...invalidPackageImport, file: relative });
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (isMirrorFile(source)) {
|
|
249
|
+
issues.push({
|
|
250
|
+
kind: 'local-package-mirror',
|
|
251
|
+
file: relative,
|
|
252
|
+
message: 'local file only re-exports admin-ui-starter-kit; replace call-site imports directly',
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (opts.fix) {
|
|
257
|
+
const replacement = replaceSpecifiers(source);
|
|
258
|
+
if (replacement.changed) {
|
|
259
|
+
await fs.writeFile(file, replacement.source);
|
|
260
|
+
fixedFiles.push(relative);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return { filesScanned: files.length, issues, fixedFiles: [...new Set(fixedFiles)].sort() };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function printReport(result, opts) {
|
|
269
|
+
if (opts.json) {
|
|
270
|
+
console.log(JSON.stringify(result, null, 2));
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (result.fixedFiles.length > 0) {
|
|
275
|
+
console.log(`Rewrote imports in ${result.fixedFiles.length} file(s):`);
|
|
276
|
+
for (const file of result.fixedFiles) console.log(` - ${file}`);
|
|
277
|
+
console.log('');
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (result.issues.length === 0) {
|
|
281
|
+
console.log(`Admin UI audit passed (${result.filesScanned} files scanned).`);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
console.error(`Admin UI audit found ${result.issues.length} issue(s):`);
|
|
286
|
+
for (const issue of result.issues) {
|
|
287
|
+
const suggestion = issue.suggested ? ` -> ${issue.suggested}` : '';
|
|
288
|
+
console.error(`- ${issue.file}: ${issue.message}${suggestion}`);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (!opts.fix) {
|
|
292
|
+
console.error('\nRun with --fix to rewrite known copied import paths.');
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async function main() {
|
|
297
|
+
const opts = parseArgs(process.argv);
|
|
298
|
+
if (opts.help) {
|
|
299
|
+
printHelp();
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const first = await audit(opts);
|
|
304
|
+
if (!opts.fix || first.fixedFiles.length === 0) {
|
|
305
|
+
printReport(first, opts);
|
|
306
|
+
process.exit(first.issues.length > 0 ? 1 : 0);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const second = await audit({ ...opts, fix: false });
|
|
310
|
+
const merged = {
|
|
311
|
+
filesScanned: second.filesScanned,
|
|
312
|
+
issues: second.issues,
|
|
313
|
+
fixedFiles: first.fixedFiles,
|
|
314
|
+
};
|
|
315
|
+
printReport(merged, opts);
|
|
316
|
+
process.exit(second.issues.length > 0 ? 1 : 0);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
main().catch((error) => {
|
|
320
|
+
if (error instanceof Error) {
|
|
321
|
+
console.error(`admin-ui-starter-kit-audit: ${error.message}`);
|
|
322
|
+
} else {
|
|
323
|
+
console.error(error);
|
|
324
|
+
}
|
|
325
|
+
process.exit(2);
|
|
326
|
+
});
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* install-skill — consumer-facing installer for the
|
|
4
|
-
* `
|
|
5
|
-
* `admin-ui-starter-kit` npm package.
|
|
3
|
+
* install-skill — consumer-facing installer for the AI skills that ship
|
|
4
|
+
* inside the `admin-ui-starter-kit` npm package.
|
|
6
5
|
*
|
|
7
6
|
* Run from a consumer project (after `npm install admin-ui-starter-kit`):
|
|
8
7
|
*
|
|
9
|
-
* npx admin-ui-starter-kit-install-skill [--target=claude|agents|both] [--force]
|
|
8
|
+
* npx admin-ui-starter-kit-install-skill [--skill=name|all] [--target=claude|agents|both] [--force]
|
|
10
9
|
*
|
|
11
10
|
* Default behaviour: copies the skill into BOTH
|
|
12
|
-
* <cwd>/.claude/skills
|
|
13
|
-
* <cwd>/.agents/skills
|
|
11
|
+
* <cwd>/.claude/skills/<skill-name>/
|
|
12
|
+
* <cwd>/.agents/skills/<skill-name>/
|
|
14
13
|
*
|
|
15
14
|
* The script is idempotent. It refuses to run inside the library itself
|
|
16
15
|
* (the maintainer has `scripts/publish-skill.mjs` for that workflow).
|
|
@@ -20,18 +19,16 @@ import path from 'node:path';
|
|
|
20
19
|
import readline from 'node:readline';
|
|
21
20
|
import { fileURLToPath } from 'node:url';
|
|
22
21
|
|
|
23
|
-
const
|
|
22
|
+
const SKILL_NAMES = ['component-library-rules', 'admin-ui-consumer-migration'];
|
|
24
23
|
const PACKAGE_NAME = 'admin-ui-starter-kit';
|
|
25
24
|
|
|
26
25
|
const __filename = fileURLToPath(import.meta.url);
|
|
27
26
|
// scripts/install-skill.mjs lives at <pkg>/scripts/, so package root is one up.
|
|
28
27
|
const PACKAGE_ROOT = path.resolve(path.dirname(__filename), '..');
|
|
29
|
-
const SOURCE = path.join(PACKAGE_ROOT, '.agents', 'skills', SKILL_NAME);
|
|
30
|
-
|
|
31
28
|
const CWD = process.cwd();
|
|
32
29
|
|
|
33
30
|
function parseArgs(argv) {
|
|
34
|
-
const opts = { target: 'both', force: false, help: false };
|
|
31
|
+
const opts = { target: 'both', skill: 'all', force: false, help: false };
|
|
35
32
|
for (const arg of argv.slice(2)) {
|
|
36
33
|
if (arg === '--force' || arg === '-f') {
|
|
37
34
|
opts.force = true;
|
|
@@ -44,6 +41,15 @@ function parseArgs(argv) {
|
|
|
44
41
|
process.exit(2);
|
|
45
42
|
}
|
|
46
43
|
opts.target = value;
|
|
44
|
+
} else if (arg.startsWith('--skill=')) {
|
|
45
|
+
const value = arg.slice('--skill='.length);
|
|
46
|
+
if (value !== 'all' && !SKILL_NAMES.includes(value)) {
|
|
47
|
+
console.error(
|
|
48
|
+
`✖ Invalid --skill=${value}. Expected one of: all, ${SKILL_NAMES.join(', ')}.`,
|
|
49
|
+
);
|
|
50
|
+
process.exit(2);
|
|
51
|
+
}
|
|
52
|
+
opts.skill = value;
|
|
47
53
|
} else {
|
|
48
54
|
console.error(`✖ Unknown argument: ${arg}`);
|
|
49
55
|
opts.help = true;
|
|
@@ -55,8 +61,9 @@ function parseArgs(argv) {
|
|
|
55
61
|
function printHelp() {
|
|
56
62
|
console.log(
|
|
57
63
|
`Usage: npx ${PACKAGE_NAME}-install-skill [options]\n\n` +
|
|
58
|
-
`Installs
|
|
64
|
+
`Installs ${PACKAGE_NAME} AI skills into the current project.\n\n` +
|
|
59
65
|
`Options:\n` +
|
|
66
|
+
` --skill=all|${SKILL_NAMES.join('|')} Which skill to install (default: all)\n` +
|
|
60
67
|
` --target=claude|agents|both Where to install (default: both)\n` +
|
|
61
68
|
` --force, -f Overwrite existing skill dir without prompting\n` +
|
|
62
69
|
` --help, -h Show this message\n`,
|
|
@@ -107,7 +114,7 @@ function confirm(question) {
|
|
|
107
114
|
});
|
|
108
115
|
}
|
|
109
116
|
|
|
110
|
-
async function installInto(targetDir, force) {
|
|
117
|
+
async function installInto(skillName, sourceDir, targetDir, force) {
|
|
111
118
|
// Safety: the only thing we ever delete is the existing skill dir at the
|
|
112
119
|
// exact target path. We never touch the parent directory or siblings.
|
|
113
120
|
if (await exists(targetDir)) {
|
|
@@ -120,8 +127,8 @@ async function installInto(targetDir, force) {
|
|
|
120
127
|
}
|
|
121
128
|
await fs.rm(targetDir, { recursive: true, force: true });
|
|
122
129
|
}
|
|
123
|
-
const count = await copyDir(
|
|
124
|
-
console.log(` ✓ Installed ${
|
|
130
|
+
const count = await copyDir(sourceDir, targetDir);
|
|
131
|
+
console.log(` ✓ Installed ${skillName} → ${targetDir} (${count} files)`);
|
|
125
132
|
return count;
|
|
126
133
|
}
|
|
127
134
|
|
|
@@ -143,31 +150,38 @@ async function main() {
|
|
|
143
150
|
process.exit(2);
|
|
144
151
|
}
|
|
145
152
|
|
|
146
|
-
|
|
147
|
-
console.error(
|
|
148
|
-
`✖ Source skill not found at ${SOURCE}\n` +
|
|
149
|
-
` This usually means the ${PACKAGE_NAME} package is missing the skill files.\n` +
|
|
150
|
-
` Try reinstalling: npm install ${PACKAGE_NAME}@latest`,
|
|
151
|
-
);
|
|
152
|
-
process.exit(1);
|
|
153
|
-
}
|
|
153
|
+
const skillNames = opts.skill === 'all' ? SKILL_NAMES : [opts.skill];
|
|
154
154
|
|
|
155
|
-
|
|
156
|
-
if (opts.target === 'claude' || opts.target === 'both') {
|
|
157
|
-
targets.push(path.join(CWD, '.claude', 'skills', SKILL_NAME));
|
|
158
|
-
}
|
|
159
|
-
if (opts.target === 'agents' || opts.target === 'both') {
|
|
160
|
-
targets.push(path.join(CWD, '.agents', 'skills', SKILL_NAME));
|
|
161
|
-
}
|
|
155
|
+
console.log(`Installing ${skillNames.length === 1 ? `"${skillNames[0]}"` : 'AI skills'} from ${PACKAGE_NAME}\n`);
|
|
162
156
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
157
|
+
for (const skillName of skillNames) {
|
|
158
|
+
const source = path.join(PACKAGE_ROOT, '.agents', 'skills', skillName);
|
|
159
|
+
if (!(await exists(source))) {
|
|
160
|
+
console.error(
|
|
161
|
+
`✖ Source skill not found at ${source}\n` +
|
|
162
|
+
` This usually means the ${PACKAGE_NAME} package is missing the skill files.\n` +
|
|
163
|
+
` Try reinstalling: npm install ${PACKAGE_NAME}@latest`,
|
|
164
|
+
);
|
|
170
165
|
process.exitCode = 1;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const targets = [];
|
|
170
|
+
if (opts.target === 'claude' || opts.target === 'both') {
|
|
171
|
+
targets.push(path.join(CWD, '.claude', 'skills', skillName));
|
|
172
|
+
}
|
|
173
|
+
if (opts.target === 'agents' || opts.target === 'both') {
|
|
174
|
+
targets.push(path.join(CWD, '.agents', 'skills', skillName));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
for (const target of targets) {
|
|
178
|
+
try {
|
|
179
|
+
await installInto(skillName, source, target, opts.force);
|
|
180
|
+
} catch (err) {
|
|
181
|
+
console.error(` ✖ ${target}`);
|
|
182
|
+
console.error(` ${err instanceof Error ? err.message : String(err)}`);
|
|
183
|
+
process.exitCode = 1;
|
|
184
|
+
}
|
|
171
185
|
}
|
|
172
186
|
}
|
|
173
187
|
|