@npeercy/skills 0.1.2 → 0.1.4

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,23 +95,81 @@ async function promptForToken(serverUrl) {
95
95
  return token;
96
96
  }
97
97
 
98
- async function installBuiltins(agents) {
99
- const st = loadState();
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
+
144
+ function bundledBuiltins() {
145
+ const out = [];
100
146
  const builtinsRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..', 'builtin-skills');
101
- if (!existsSync(builtinsRoot)) return;
147
+ if (!existsSync(builtinsRoot)) return out;
102
148
 
103
- const installed = [];
104
149
  for (const entry of readdirSync(builtinsRoot, { withFileTypes: true })) {
105
150
  if (!entry.isDirectory()) continue;
106
-
107
151
  const srcDir = join(builtinsRoot, entry.name);
108
152
  const skillMd = join(srcDir, 'SKILL.md');
109
153
  if (!existsSync(skillMd)) continue;
110
-
111
154
  const meta = parseFrontmatter(readFileSync(skillMd, 'utf8'));
112
155
  if (!meta) continue;
156
+ out.push({
157
+ id: `__builtin__/local/${meta.name}`,
158
+ name: meta.name,
159
+ srcDir,
160
+ });
161
+ }
162
+
163
+ return out;
164
+ }
165
+
166
+ async function installBuiltins(agents) {
167
+ const st = loadState();
113
168
 
114
- const id = `__builtin__/local/${meta.name}`;
169
+ const installed = [];
170
+ for (const b of bundledBuiltins()) {
171
+ const srcDir = b.srcDir;
172
+ const id = b.id;
115
173
  const version = 'v1';
116
174
  const dest = managedPath(id, version);
117
175
  const files = collectFiles(srcDir);
@@ -149,7 +207,7 @@ async function installBuiltins(agents) {
149
207
  builtin: true,
150
208
  };
151
209
 
152
- installed.push(meta.name);
210
+ installed.push(b.name);
153
211
  }
154
212
 
155
213
  saveState(st);
@@ -207,7 +265,7 @@ export async function cmdInit(args) {
207
265
 
208
266
  // Import existing skills
209
267
  if (!args.noImport) {
210
- await cmdImport({ all: true, quiet: true });
268
+ await cmdImport({ all: true, quiet: false });
211
269
  }
212
270
 
213
271
  // Install built-ins
@@ -234,6 +292,29 @@ export async function cmdLogin(args) {
234
292
  cfg.org = me.org;
235
293
  saveConfig(cfg);
236
294
  console.log(`✓ Logged in as ${me.user} (org: ${me.org}, role: ${me.role})`);
295
+ console.log(` Your publish namespace is: ${me.org}/${me.user}/<skillname>`);
296
+
297
+ // First-time setup guidance
298
+ const st = loadState();
299
+ const agents = detectAgents();
300
+ const unmanaged = discoverUnmanaged(agents, st);
301
+ const hasBuiltins = Object.keys(st.installed).some(id => id.startsWith('__builtin__/local/'));
302
+
303
+ if (!hasBuiltins || unmanaged.size > 0) {
304
+ console.log('\nSetup not complete yet.');
305
+ if (!hasBuiltins) console.log(' - Built-in skills are not installed yet.');
306
+ if (unmanaged.size > 0) console.log(` - Found ${unmanaged.size} unmanaged local skill(s).`);
307
+
308
+ const doSetup = await promptYesNo('Run guided setup now? (recommended)', true);
309
+ if (doSetup) {
310
+ await cmdInit({ server: cfg.server, token: cfg.token, noImport: false });
311
+ return;
312
+ }
313
+
314
+ console.log('\nNext steps:');
315
+ if (!hasBuiltins) console.log(' skills init --no-import');
316
+ if (unmanaged.size > 0) console.log(' skills import');
317
+ }
237
318
  }
238
319
 
239
320
  export async function cmdLogout() {
@@ -342,12 +423,13 @@ export async function cmdInstall(args) {
342
423
  console.log('Installed.');
343
424
  }
344
425
 
345
- export async function cmdList(args) {
346
- const cfg = loadConfig();
426
+ export async function cmdList(args = {}) {
347
427
  const st = loadState();
348
428
  const agents = detectAgents();
349
429
 
350
430
  const rows = [];
431
+
432
+ // Managed installs (skill-sharer state)
351
433
  for (const [id, rec] of Object.entries(st.installed).sort()) {
352
434
  let status = 'ok';
353
435
  if (args.verify) {
@@ -360,15 +442,54 @@ export async function cmdList(args) {
360
442
  const { org, user, name } = splitSkillId(id);
361
443
  const data = await api.info(org, user, name);
362
444
  latest = data.latest_version || rec.version;
363
- } catch { /* offline */ }
445
+ } catch {
446
+ // offline / not in registry
447
+ }
364
448
  }
365
449
 
366
450
  const outdated = latest !== rec.version;
367
451
  if (args.outdated && !outdated) continue;
368
452
  if (outdated && status === 'ok') status = 'outdated';
369
453
 
370
- rows.push({ id, version: rec.version, latest, agents: rec.agents.join(','), status });
371
- }
454
+ rows.push({
455
+ id,
456
+ version: rec.version,
457
+ latest,
458
+ agents: (rec.agents || []).join(','),
459
+ status,
460
+ managed: true,
461
+ });
462
+ }
463
+
464
+ // Bundled built-ins (show even if not installed yet)
465
+ const installedIds = new Set(Object.keys(st.installed));
466
+ for (const b of bundledBuiltins()) {
467
+ if (installedIds.has(b.id)) continue;
468
+ rows.push({
469
+ id: b.id,
470
+ version: '-',
471
+ latest: 'v1',
472
+ agents: '-',
473
+ status: 'bundled',
474
+ managed: false,
475
+ });
476
+ }
477
+
478
+ // Unmanaged installs (present in agent dirs but not in skill-sharer state)
479
+ const unmanaged = discoverUnmanaged(agents, st);
480
+ for (const [name, info] of unmanaged.entries()) {
481
+ const agentList = [...new Set(info.sources.map(s => s.agent))].sort().join(',');
482
+ rows.push({
483
+ id: `(unmanaged) ${name}`,
484
+ version: '-',
485
+ latest: '-',
486
+ agents: agentList,
487
+ status: 'unmanaged',
488
+ managed: false,
489
+ });
490
+ }
491
+
492
+ rows.sort((a, b) => a.id.localeCompare(b.id));
372
493
 
373
494
  if (args.json) {
374
495
  console.log(JSON.stringify(rows, null, 2));
@@ -376,7 +497,7 @@ export async function cmdList(args) {
376
497
  }
377
498
 
378
499
  if (rows.length === 0) {
379
- console.log('No installed skills.');
500
+ console.log('No skills found in managed store or agent directories.');
380
501
  return;
381
502
  }
382
503
 
@@ -384,6 +505,11 @@ export async function cmdList(args) {
384
505
  for (const r of rows) {
385
506
  console.log(r.id.padEnd(56) + r.version.padEnd(10) + r.latest.padEnd(10) + r.agents.padEnd(20) + r.status);
386
507
  }
508
+
509
+ const unmanagedCount = rows.filter(r => r.status === 'unmanaged').length;
510
+ if (unmanagedCount > 0) {
511
+ console.log(`\nFound ${unmanagedCount} unmanaged skill(s). Run: skills import`);
512
+ }
387
513
  }
388
514
 
389
515
  function verifyInstall(id, rec, agents) {
@@ -536,33 +662,7 @@ export async function cmdImport(args) {
536
662
  const agents = detectAgents();
537
663
 
538
664
  // 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
- }
665
+ const unmanaged = discoverUnmanaged(agents, st);
566
666
 
567
667
  if (args.path) {
568
668
  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.4",
4
4
  "description": "CLI-first skill marketplace for coding agents",
5
5
  "type": "module",
6
6
  "bin": {