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.
@@ -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(_options: InitOptions): Promise<void>;
10
+ export declare function init(options: InitOptions): Promise<void>;
5
11
  export {};
@@ -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
- const dirEntries = readdirSync(join(cwd, entry.name));
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(_options) {
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
- console.log(chalk.yellow('⚠ skillset.yaml already exists in this directory.'));
215
- const overwrite = await confirm({
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
- // Gather information
225
- const name = await input({
226
- message: 'Skillset name (alphanumeric, hyphens, underscores):',
227
- validate: (value) => {
228
- if (!/^[A-Za-z0-9_-]+$/.test(value)) {
229
- return 'Name must be alphanumeric with hyphens/underscores only';
230
- }
231
- if (value.length < 1 || value.length > 100) {
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
- return true;
254
- },
255
- });
256
- const authorUrl = await input({
257
- message: 'Author URL (GitHub profile or website):',
258
- default: `https://github.com/${authorHandle.slice(1)}`,
259
- validate: (value) => {
260
- try {
261
- new URL(value);
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
- catch {
265
- return 'Must be a valid URL';
266
- }
267
- },
268
- });
269
- const productionUrl = await input({
270
- message: 'Production URL (live deployment, repo, or case study):',
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
- catch {
277
- return 'Must be a valid URL';
278
- }
279
- },
280
- });
281
- const tagsInput = await input({
282
- message: 'Tags (comma-separated, lowercase, e.g., sdlc,planning,multi-agent):',
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
- return true;
294
- },
295
- });
296
- const tags = tagsInput.split(',').map((t) => t.trim());
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
- filesToCopy = await checkbox({
320
- message: 'Select files to copy to content/:',
321
- choices: allDetected.map((f) => ({ name: f, value: f, checked: true })),
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')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillsets",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "description": "CLI tool for discovering and installing verified skillsets",
5
5
  "type": "module",
6
6
  "bin": {