@npeercy/skills 0.1.2 → 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/bin/skills.js CHANGED
@@ -23,10 +23,10 @@ Setup:
23
23
  Discovery:
24
24
  search [query] Search skills (no query = browse all)
25
25
  info <skill> Skill details + versions
26
+ list [--verify] [--outdated] [--json] Show all local skills (managed + unmanaged)
26
27
 
27
28
  Install:
28
29
  install <skill>[@ver] [--agent <a>] [--dry-run]
29
- list [--verify] [--outdated] [--json]
30
30
  update [<skill>] Update one or all
31
31
  uninstall <skill>
32
32
 
@@ -98,7 +98,15 @@ async function maybeWarnOutdated() {
98
98
 
99
99
  async function main() {
100
100
  const args = process.argv.slice(2);
101
- if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
101
+
102
+ // `skills` by itself -> list all local skills (managed + unmanaged)
103
+ if (args.length === 0) {
104
+ await maybeWarnOutdated();
105
+ await cmdList({ verify: false, outdated: false, json: false });
106
+ return;
107
+ }
108
+
109
+ if (args[0] === '--help' || args[0] === '-h') {
102
110
  console.log(USAGE);
103
111
  return;
104
112
  }
package/lib/skills.js CHANGED
@@ -95,6 +95,52 @@ async function promptForToken(serverUrl) {
95
95
  return token;
96
96
  }
97
97
 
98
+ async function promptYesNo(question, defaultYes = true) {
99
+ const suffix = defaultYes ? ' [Y/n] ' : ' [y/N] ';
100
+ const rl = createInterface({ input, output });
101
+ const ans = (await rl.question(question + suffix)).trim().toLowerCase();
102
+ rl.close();
103
+ if (!ans) return defaultYes;
104
+ return ['y', 'yes'].includes(ans);
105
+ }
106
+
107
+ function discoverUnmanaged(agents, st) {
108
+ const unmanaged = new Map(); // name -> { sources: [{agent,path,skillMdPath}] }
109
+ const managedLinks = new Set(Object.keys(st.installed).map(linkNameForSkill));
110
+
111
+ for (const [agentName, agentPath] of Object.entries(agents)) {
112
+ if (!existsSync(agentPath)) continue;
113
+ for (const entry of readdirSync(agentPath, { withFileTypes: true })) {
114
+ const full = join(agentPath, entry.name);
115
+
116
+ // Skip managed link names
117
+ if (managedLinks.has(entry.name)) continue;
118
+
119
+ // Skip if symlink points to managed store
120
+ try {
121
+ if (lstatSync(full).isSymbolicLink()) {
122
+ const target = readlinkSync(full);
123
+ if (target.includes('skill-sharer')) continue;
124
+ }
125
+ } catch {
126
+ // ignore stat errors
127
+ }
128
+
129
+ const skillMd = entry.isDirectory()
130
+ ? join(full, 'SKILL.md')
131
+ : (lstatSync(full).isSymbolicLink() ? join(resolve(readlinkSync(full)), 'SKILL.md') : null);
132
+
133
+ if (!skillMd || !existsSync(skillMd)) continue;
134
+
135
+ const key = entry.name;
136
+ if (!unmanaged.has(key)) unmanaged.set(key, { sources: [] });
137
+ unmanaged.get(key).sources.push({ agent: agentName, path: full, skillMdPath: skillMd });
138
+ }
139
+ }
140
+
141
+ return unmanaged;
142
+ }
143
+
98
144
  async function installBuiltins(agents) {
99
145
  const st = loadState();
100
146
  const builtinsRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..', 'builtin-skills');
@@ -207,7 +253,7 @@ export async function cmdInit(args) {
207
253
 
208
254
  // Import existing skills
209
255
  if (!args.noImport) {
210
- await cmdImport({ all: true, quiet: true });
256
+ await cmdImport({ all: true, quiet: false });
211
257
  }
212
258
 
213
259
  // Install built-ins
@@ -234,6 +280,29 @@ export async function cmdLogin(args) {
234
280
  cfg.org = me.org;
235
281
  saveConfig(cfg);
236
282
  console.log(`✓ Logged in as ${me.user} (org: ${me.org}, role: ${me.role})`);
283
+ console.log(` Your publish namespace is: ${me.org}/${me.user}/<skillname>`);
284
+
285
+ // First-time setup guidance
286
+ const st = loadState();
287
+ const agents = detectAgents();
288
+ const unmanaged = discoverUnmanaged(agents, st);
289
+ const hasBuiltins = Object.keys(st.installed).some(id => id.startsWith('__builtin__/local/'));
290
+
291
+ if (!hasBuiltins || unmanaged.size > 0) {
292
+ console.log('\nSetup not complete yet.');
293
+ if (!hasBuiltins) console.log(' - Built-in skills are not installed yet.');
294
+ if (unmanaged.size > 0) console.log(` - Found ${unmanaged.size} unmanaged local skill(s).`);
295
+
296
+ const doSetup = await promptYesNo('Run guided setup now? (recommended)', true);
297
+ if (doSetup) {
298
+ await cmdInit({ server: cfg.server, token: cfg.token, noImport: false });
299
+ return;
300
+ }
301
+
302
+ console.log('\nNext steps:');
303
+ if (!hasBuiltins) console.log(' skills init --no-import');
304
+ if (unmanaged.size > 0) console.log(' skills import');
305
+ }
237
306
  }
238
307
 
239
308
  export async function cmdLogout() {
@@ -342,12 +411,13 @@ export async function cmdInstall(args) {
342
411
  console.log('Installed.');
343
412
  }
344
413
 
345
- export async function cmdList(args) {
346
- const cfg = loadConfig();
414
+ export async function cmdList(args = {}) {
347
415
  const st = loadState();
348
416
  const agents = detectAgents();
349
417
 
350
418
  const rows = [];
419
+
420
+ // Managed installs (skill-sharer state)
351
421
  for (const [id, rec] of Object.entries(st.installed).sort()) {
352
422
  let status = 'ok';
353
423
  if (args.verify) {
@@ -360,23 +430,48 @@ export async function cmdList(args) {
360
430
  const { org, user, name } = splitSkillId(id);
361
431
  const data = await api.info(org, user, name);
362
432
  latest = data.latest_version || rec.version;
363
- } catch { /* offline */ }
433
+ } catch {
434
+ // offline / not in registry
435
+ }
364
436
  }
365
437
 
366
438
  const outdated = latest !== rec.version;
367
439
  if (args.outdated && !outdated) continue;
368
440
  if (outdated && status === 'ok') status = 'outdated';
369
441
 
370
- rows.push({ id, version: rec.version, latest, agents: rec.agents.join(','), status });
442
+ rows.push({
443
+ id,
444
+ version: rec.version,
445
+ latest,
446
+ agents: (rec.agents || []).join(','),
447
+ status,
448
+ managed: true,
449
+ });
371
450
  }
372
451
 
452
+ // Unmanaged installs (present in agent dirs but not in skill-sharer state)
453
+ const unmanaged = discoverUnmanaged(agents, st);
454
+ for (const [name, info] of unmanaged.entries()) {
455
+ const agentList = [...new Set(info.sources.map(s => s.agent))].sort().join(',');
456
+ rows.push({
457
+ id: `(unmanaged) ${name}`,
458
+ version: '-',
459
+ latest: '-',
460
+ agents: agentList,
461
+ status: 'unmanaged',
462
+ managed: false,
463
+ });
464
+ }
465
+
466
+ rows.sort((a, b) => a.id.localeCompare(b.id));
467
+
373
468
  if (args.json) {
374
469
  console.log(JSON.stringify(rows, null, 2));
375
470
  return;
376
471
  }
377
472
 
378
473
  if (rows.length === 0) {
379
- console.log('No installed skills.');
474
+ console.log('No skills found in managed store or agent directories.');
380
475
  return;
381
476
  }
382
477
 
@@ -384,6 +479,11 @@ export async function cmdList(args) {
384
479
  for (const r of rows) {
385
480
  console.log(r.id.padEnd(56) + r.version.padEnd(10) + r.latest.padEnd(10) + r.agents.padEnd(20) + r.status);
386
481
  }
482
+
483
+ const unmanagedCount = rows.filter(r => r.status === 'unmanaged').length;
484
+ if (unmanagedCount > 0) {
485
+ console.log(`\nFound ${unmanagedCount} unmanaged skill(s). Run: skills import`);
486
+ }
387
487
  }
388
488
 
389
489
  function verifyInstall(id, rec, agents) {
@@ -536,33 +636,7 @@ export async function cmdImport(args) {
536
636
  const agents = detectAgents();
537
637
 
538
638
  // Find unmanaged skills in agent dirs
539
- const unmanaged = new Map(); // name -> { sources: [{agent, path}] }
540
- const managedLinks = new Set(Object.keys(st.installed).map(linkNameForSkill));
541
-
542
- for (const [agentName, agentPath] of Object.entries(agents)) {
543
- if (!existsSync(agentPath)) continue;
544
- for (const entry of readdirSync(agentPath, { withFileTypes: true })) {
545
- const full = join(agentPath, entry.name);
546
- // Skip managed symlinks
547
- if (managedLinks.has(entry.name)) continue;
548
- // Skip if it's a symlink pointing into our managed store
549
- try {
550
- if (lstatSync(full).isSymbolicLink()) {
551
- const target = readlinkSync(full);
552
- if (target.includes('skill-sharer')) continue;
553
- }
554
- } catch { /* ok */ }
555
-
556
- // Check if it has SKILL.md
557
- const skillMd = entry.isDirectory() ? join(full, 'SKILL.md') :
558
- (lstatSync(full).isSymbolicLink() ? join(resolve(readlinkSync(full)), 'SKILL.md') : null);
559
- if (!skillMd || !existsSync(skillMd)) continue;
560
-
561
- const name = entry.name;
562
- if (!unmanaged.has(name)) unmanaged.set(name, { sources: [] });
563
- unmanaged.get(name).sources.push({ agent: agentName, path: full, skillMdPath: skillMd });
564
- }
565
- }
639
+ const unmanaged = discoverUnmanaged(agents, st);
566
640
 
567
641
  if (args.path) {
568
642
  const p = resolve(args.path);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@npeercy/skills",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "CLI-first skill marketplace for coding agents",
5
5
  "type": "module",
6
6
  "bin": {