skillsets 0.6.0 ā 0.6.2
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/commands/init.d.ts +7 -1
- package/dist/commands/init.js +159 -85
- package/dist/index.js +6 -0
- package/package.json +1 -1
package/dist/commands/init.d.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
interface InitOptions {
|
|
2
2
|
yes?: boolean;
|
|
3
|
+
name?: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
handle?: string;
|
|
6
|
+
authorUrl?: string;
|
|
7
|
+
productionUrl?: string;
|
|
8
|
+
tags?: string;
|
|
3
9
|
}
|
|
4
|
-
export declare function init(
|
|
10
|
+
export declare function init(options: InitOptions): Promise<void>;
|
|
5
11
|
export {};
|
package/dist/commands/init.js
CHANGED
|
@@ -156,6 +156,18 @@ function copyDirRecursive(src, dest, exclusions) {
|
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
|
+
/** Check if a directory tree contains any support stack marker file */
|
|
160
|
+
function hasMarkerDeep(dir) {
|
|
161
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
162
|
+
if (COPY_EXCLUSIONS.has(entry.name))
|
|
163
|
+
continue;
|
|
164
|
+
if (entry.isFile() && SUPPORT_STACK_MARKERS.includes(entry.name))
|
|
165
|
+
return true;
|
|
166
|
+
if (entry.isDirectory() && hasMarkerDeep(join(dir, entry.name)))
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
159
171
|
/** Scan top-level directories for self-contained support stacks */
|
|
160
172
|
function detectSupportStacks(cwd) {
|
|
161
173
|
const stacks = [];
|
|
@@ -164,14 +176,13 @@ function detectSupportStacks(cwd) {
|
|
|
164
176
|
continue;
|
|
165
177
|
if (entry.name.startsWith('.') || SCAN_SKIP.has(entry.name))
|
|
166
178
|
continue;
|
|
167
|
-
|
|
168
|
-
if (dirEntries.some(f => SUPPORT_STACK_MARKERS.includes(f))) {
|
|
179
|
+
if (hasMarkerDeep(join(cwd, entry.name))) {
|
|
169
180
|
stacks.push(entry.name + '/');
|
|
170
181
|
}
|
|
171
182
|
}
|
|
172
183
|
return stacks;
|
|
173
184
|
}
|
|
174
|
-
export async function init(
|
|
185
|
+
export async function init(options) {
|
|
175
186
|
console.log(chalk.blue('\nš¦ Initialize a new skillset submission\n'));
|
|
176
187
|
// 1. Verify gh CLI is available and authenticated
|
|
177
188
|
try {
|
|
@@ -210,90 +221,147 @@ export async function init(_options) {
|
|
|
210
221
|
}
|
|
211
222
|
const cwd = process.cwd();
|
|
212
223
|
// Check if already initialized
|
|
224
|
+
const hasAllFlags = options.name && options.description && options.handle
|
|
225
|
+
&& options.productionUrl && options.tags;
|
|
213
226
|
if (existsSync(join(cwd, 'skillset.yaml'))) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
message: 'Overwrite existing files?',
|
|
217
|
-
default: false,
|
|
218
|
-
});
|
|
219
|
-
if (!overwrite) {
|
|
220
|
-
console.log(chalk.gray('Aborted.'));
|
|
221
|
-
return;
|
|
227
|
+
if (hasAllFlags) {
|
|
228
|
+
console.log(chalk.yellow('ā skillset.yaml already exists ā overwriting (flags provided).'));
|
|
222
229
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
return 'Name must be 1-100 characters';
|
|
233
|
-
}
|
|
234
|
-
return true;
|
|
235
|
-
},
|
|
236
|
-
});
|
|
237
|
-
const description = await input({
|
|
238
|
-
message: 'Description (10-200 characters):',
|
|
239
|
-
validate: (value) => {
|
|
240
|
-
if (value.length < 10 || value.length > 200) {
|
|
241
|
-
return 'Description must be 10-200 characters';
|
|
242
|
-
}
|
|
243
|
-
return true;
|
|
244
|
-
},
|
|
245
|
-
});
|
|
246
|
-
const authorHandle = await input({
|
|
247
|
-
message: 'GitHub handle (e.g., @username):',
|
|
248
|
-
default: `@${login}`,
|
|
249
|
-
validate: (value) => {
|
|
250
|
-
if (!/^@[A-Za-z0-9_-]+$/.test(value)) {
|
|
251
|
-
return 'Handle must start with @ followed by alphanumeric characters';
|
|
230
|
+
else {
|
|
231
|
+
console.log(chalk.yellow('ā skillset.yaml already exists in this directory.'));
|
|
232
|
+
const overwrite = await confirm({
|
|
233
|
+
message: 'Overwrite existing files?',
|
|
234
|
+
default: false,
|
|
235
|
+
});
|
|
236
|
+
if (!overwrite) {
|
|
237
|
+
console.log(chalk.gray('Aborted.'));
|
|
238
|
+
return;
|
|
252
239
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
// Gather information ā skip prompts when flags are provided
|
|
243
|
+
let name;
|
|
244
|
+
let description;
|
|
245
|
+
let authorHandle;
|
|
246
|
+
let authorUrl;
|
|
247
|
+
let productionUrl;
|
|
248
|
+
let tags;
|
|
249
|
+
if (hasAllFlags) {
|
|
250
|
+
name = options.name;
|
|
251
|
+
description = options.description;
|
|
252
|
+
authorHandle = options.handle.startsWith('@') ? options.handle : `@${options.handle}`;
|
|
253
|
+
authorUrl = options.authorUrl || `https://github.com/${authorHandle.slice(1)}`;
|
|
254
|
+
productionUrl = options.productionUrl;
|
|
255
|
+
tags = options.tags.split(',').map((t) => t.trim());
|
|
256
|
+
// Validate
|
|
257
|
+
if (!/^[A-Za-z0-9_-]+$/.test(name) || name.length < 1 || name.length > 100) {
|
|
258
|
+
throw new Error('Name must be 1-100 alphanumeric characters with hyphens/underscores');
|
|
259
|
+
}
|
|
260
|
+
if (description.length < 10 || description.length > 200) {
|
|
261
|
+
throw new Error('Description must be 10-200 characters');
|
|
262
|
+
}
|
|
263
|
+
if (!/^@[A-Za-z0-9_-]+$/.test(authorHandle)) {
|
|
264
|
+
throw new Error('Handle must start with @ followed by alphanumeric characters');
|
|
265
|
+
}
|
|
266
|
+
try {
|
|
267
|
+
new URL(authorUrl);
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
throw new Error(`Invalid author URL: ${authorUrl}`);
|
|
271
|
+
}
|
|
272
|
+
try {
|
|
273
|
+
new URL(productionUrl);
|
|
274
|
+
}
|
|
275
|
+
catch {
|
|
276
|
+
throw new Error(`Invalid production URL: ${productionUrl}`);
|
|
277
|
+
}
|
|
278
|
+
if (tags.length < 1 || tags.length > 10)
|
|
279
|
+
throw new Error('Must have 1-10 tags');
|
|
280
|
+
for (const tag of tags) {
|
|
281
|
+
if (!/^[a-z0-9-]+$/.test(tag))
|
|
282
|
+
throw new Error(`Tag "${tag}" must be lowercase alphanumeric with hyphens`);
|
|
283
|
+
}
|
|
284
|
+
console.log(chalk.green(`\nā Using provided values:`));
|
|
285
|
+
console.log(` Name: ${name}`);
|
|
286
|
+
console.log(` Description: ${description}`);
|
|
287
|
+
console.log(` Handle: ${authorHandle}`);
|
|
288
|
+
console.log(` Production: ${productionUrl}`);
|
|
289
|
+
console.log(` Tags: ${tags.join(', ')}`);
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
name = await input({
|
|
293
|
+
message: 'Skillset name (alphanumeric, hyphens, underscores):',
|
|
294
|
+
validate: (value) => {
|
|
295
|
+
if (!/^[A-Za-z0-9_-]+$/.test(value)) {
|
|
296
|
+
return 'Name must be alphanumeric with hyphens/underscores only';
|
|
297
|
+
}
|
|
298
|
+
if (value.length < 1 || value.length > 100) {
|
|
299
|
+
return 'Name must be 1-100 characters';
|
|
300
|
+
}
|
|
262
301
|
return true;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
validate: (value) => {
|
|
272
|
-
try {
|
|
273
|
-
new URL(value);
|
|
302
|
+
},
|
|
303
|
+
});
|
|
304
|
+
description = await input({
|
|
305
|
+
message: 'Description (10-200 characters):',
|
|
306
|
+
validate: (value) => {
|
|
307
|
+
if (value.length < 10 || value.length > 200) {
|
|
308
|
+
return 'Description must be 10-200 characters';
|
|
309
|
+
}
|
|
274
310
|
return true;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
validate: (value) => {
|
|
284
|
-
const tags = value.split(',').map((t) => t.trim());
|
|
285
|
-
if (tags.length < 1 || tags.length > 10) {
|
|
286
|
-
return 'Must have 1-10 tags';
|
|
287
|
-
}
|
|
288
|
-
for (const tag of tags) {
|
|
289
|
-
if (!/^[a-z0-9-]+$/.test(tag)) {
|
|
290
|
-
return `Tag "${tag}" must be lowercase alphanumeric with hyphens only`;
|
|
311
|
+
},
|
|
312
|
+
});
|
|
313
|
+
authorHandle = await input({
|
|
314
|
+
message: 'GitHub handle (e.g., @username):',
|
|
315
|
+
default: `@${login}`,
|
|
316
|
+
validate: (value) => {
|
|
317
|
+
if (!/^@[A-Za-z0-9_-]+$/.test(value)) {
|
|
318
|
+
return 'Handle must start with @ followed by alphanumeric characters';
|
|
291
319
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
|
|
320
|
+
return true;
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
authorUrl = await input({
|
|
324
|
+
message: 'Author URL (GitHub profile or website):',
|
|
325
|
+
default: `https://github.com/${authorHandle.slice(1)}`,
|
|
326
|
+
validate: (value) => {
|
|
327
|
+
try {
|
|
328
|
+
new URL(value);
|
|
329
|
+
return true;
|
|
330
|
+
}
|
|
331
|
+
catch {
|
|
332
|
+
return 'Must be a valid URL';
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
});
|
|
336
|
+
productionUrl = await input({
|
|
337
|
+
message: 'Production URL (live deployment, repo, or case study):',
|
|
338
|
+
validate: (value) => {
|
|
339
|
+
try {
|
|
340
|
+
new URL(value);
|
|
341
|
+
return true;
|
|
342
|
+
}
|
|
343
|
+
catch {
|
|
344
|
+
return 'Must be a valid URL';
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
});
|
|
348
|
+
const tagsInput = await input({
|
|
349
|
+
message: 'Tags (comma-separated, lowercase, e.g., sdlc,planning,multi-agent):',
|
|
350
|
+
validate: (value) => {
|
|
351
|
+
const t = value.split(',').map((s) => s.trim());
|
|
352
|
+
if (t.length < 1 || t.length > 10) {
|
|
353
|
+
return 'Must have 1-10 tags';
|
|
354
|
+
}
|
|
355
|
+
for (const tag of t) {
|
|
356
|
+
if (!/^[a-z0-9-]+$/.test(tag)) {
|
|
357
|
+
return `Tag "${tag}" must be lowercase alphanumeric with hyphens only`;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return true;
|
|
361
|
+
},
|
|
362
|
+
});
|
|
363
|
+
tags = tagsInput.split(',').map((t) => t.trim());
|
|
364
|
+
}
|
|
297
365
|
// Auto-detect existing files ā core skillset files and primitives
|
|
298
366
|
const coreFiles = [
|
|
299
367
|
'CLAUDE.md', 'README.md', 'QUICKSTART.md',
|
|
@@ -316,10 +384,16 @@ export async function init(_options) {
|
|
|
316
384
|
console.log(chalk.green('\nā Detected support stacks:'));
|
|
317
385
|
detectedStacks.forEach((f) => console.log(` - ${f}`));
|
|
318
386
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
}
|
|
387
|
+
if (hasAllFlags) {
|
|
388
|
+
filesToCopy = allDetected;
|
|
389
|
+
console.log(chalk.green('\nā Auto-selecting all detected files'));
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
filesToCopy = await checkbox({
|
|
393
|
+
message: 'Select files to copy to content/:',
|
|
394
|
+
choices: allDetected.map((f) => ({ name: f, value: f, checked: true })),
|
|
395
|
+
});
|
|
396
|
+
}
|
|
323
397
|
}
|
|
324
398
|
// Create structure
|
|
325
399
|
const spinner = ora('Creating skillset structure...').start();
|
package/dist/index.js
CHANGED
|
@@ -53,6 +53,12 @@ program
|
|
|
53
53
|
.command('init')
|
|
54
54
|
.description('Initialize a new skillset submission')
|
|
55
55
|
.option('-y, --yes', 'Accept defaults without prompting')
|
|
56
|
+
.option('--name <name>', 'Skillset name')
|
|
57
|
+
.option('--description <desc>', 'Description (10-200 chars)')
|
|
58
|
+
.option('--handle <handle>', 'GitHub handle (e.g., @username)')
|
|
59
|
+
.option('--author-url <url>', 'Author URL')
|
|
60
|
+
.option('--production-url <url>', 'Production URL')
|
|
61
|
+
.option('--tags <tags>', 'Comma-separated tags')
|
|
56
62
|
.action(run(init));
|
|
57
63
|
program
|
|
58
64
|
.command('audit')
|