@npeercy/skills 0.1.6 → 0.1.8

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
@@ -9,14 +9,14 @@ import {
9
9
  cmdEdit, cmdValidate, cmdPublish, cmdImport, cmdDoctor,
10
10
  cmdShare, cmdUnshare, cmdVisibility,
11
11
  } from '../lib/skills.js';
12
- import { loadConfig, saveConfig } from '../lib/config.js';
12
+ import { runStartupChecks } from '../lib/startup.js';
13
13
 
14
14
  const USAGE = `skill-sharer — CLI skill manager for coding agents
15
15
 
16
16
  Usage: skills <command> [options]
17
17
 
18
18
  Setup:
19
- init [--server <url>] [--token <token>] [--no-import] Setup skill-sharer
19
+ init [--server <url>] [--token <token>] Setup skill-sharer (local-only; never publishes)
20
20
  login [--server <url>] [--token <token>] Set/refresh token (prompts if missing)
21
21
  logout Remove stored token
22
22
 
@@ -51,49 +51,8 @@ const PKG = JSON.parse(
51
51
  readFileSync(join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json'), 'utf8')
52
52
  );
53
53
 
54
- function cmpSemver(a, b) {
55
- const pa = String(a || '').split('.').map(n => parseInt(n, 10) || 0);
56
- const pb = String(b || '').split('.').map(n => parseInt(n, 10) || 0);
57
- const len = Math.max(pa.length, pb.length);
58
- for (let i = 0; i < len; i++) {
59
- const da = pa[i] || 0;
60
- const db = pb[i] || 0;
61
- if (da > db) return 1;
62
- if (da < db) return -1;
63
- }
64
- return 0;
65
- }
66
-
67
- async function maybeWarnOutdated() {
68
- if (process.env.SKILLS_NO_UPDATE_CHECK === '1') return;
69
-
70
- const cfg = loadConfig();
71
- const now = Date.now();
72
- const oneDayMs = 24 * 60 * 60 * 1000;
73
- const lastChecked = cfg.updateCheck?.lastChecked ? Date.parse(cfg.updateCheck.lastChecked) : 0;
74
- let latest = cfg.updateCheck?.latestVersion || '';
75
-
76
- if (!lastChecked || now - lastChecked > oneDayMs || !latest) {
77
- try {
78
- const res = await fetch('https://registry.npmjs.org/@npeercy%2fskills/latest');
79
- if (res.ok) {
80
- const data = await res.json();
81
- latest = data.version || latest;
82
- cfg.updateCheck = {
83
- lastChecked: new Date(now).toISOString(),
84
- latestVersion: latest,
85
- };
86
- saveConfig(cfg);
87
- }
88
- } catch {
89
- // Silent fail; command should continue even offline.
90
- }
91
- }
92
-
93
- if (latest && cmpSemver(latest, PKG.version) > 0) {
94
- console.log(`⚠ Update available: @npeercy/skills ${PKG.version} → ${latest}`);
95
- console.log(' Run: npm install -g @npeercy/skills\n');
96
- }
54
+ function hasJsonFlag(args) {
55
+ return args.includes('--json');
97
56
  }
98
57
 
99
58
  async function main() {
@@ -101,20 +60,24 @@ async function main() {
101
60
 
102
61
  // `skills` by itself -> list all local skills (managed + unmanaged + bundled)
103
62
  if (args.length === 0) {
104
- await maybeWarnOutdated();
63
+ await runStartupChecks({ currentVersion: PKG.version, command: 'list', jsonMode: false });
105
64
  await cmdList({ verify: false, outdated: false, json: false });
106
65
  return;
107
66
  }
108
67
 
109
68
  if (args[0] === '--help' || args[0] === '-h') {
69
+ await runStartupChecks({ currentVersion: PKG.version, command: 'help', jsonMode: false });
110
70
  console.log(USAGE);
111
71
  return;
112
72
  }
113
73
 
114
74
  const cmd = args[0];
115
75
  const rest = args.slice(1);
116
-
117
- await maybeWarnOutdated();
76
+ await runStartupChecks({
77
+ currentVersion: PKG.version,
78
+ command: cmd,
79
+ jsonMode: cmd === 'list' && hasJsonFlag(rest),
80
+ });
118
81
 
119
82
  try {
120
83
  switch (cmd) {
@@ -122,9 +85,9 @@ async function main() {
122
85
  const { values } = parseArgs({ args: rest, options: {
123
86
  server: { type: 'string' },
124
87
  token: { type: 'string' },
125
- 'no-import': { type: 'boolean', default: false },
88
+ 'no-import': { type: 'boolean', default: false }, // deprecated compatibility; init never imports
126
89
  }, allowPositionals: false });
127
- await cmdInit({ server: values.server, token: values.token, noImport: values['no-import'] });
90
+ await cmdInit({ server: values.server, token: values.token });
128
91
  break;
129
92
  }
130
93
  case 'login': {
package/lib/skills.js CHANGED
@@ -255,7 +255,35 @@ function bundledBuiltins() {
255
255
  return out;
256
256
  }
257
257
 
258
- async function installBuiltins(agents) {
258
+ export function getLocalDiagnostics() {
259
+ const st = loadState();
260
+ const agents = detectAgents();
261
+ const bundled = bundledBuiltins();
262
+ const unmanaged = discoverUnmanaged(agents, st);
263
+ const conflicts = discoverAgentNameConflicts(agents);
264
+
265
+ const installedBuiltinIds = new Set(
266
+ Object.keys(st.installed).filter(id => id.startsWith('__builtin__/local/')),
267
+ );
268
+ const missingBuiltins = bundled.filter(b => !installedBuiltinIds.has(b.id));
269
+
270
+ return {
271
+ state: st,
272
+ agents,
273
+ managedCount: Object.keys(st.installed).length,
274
+ bundledBuiltinsCount: bundled.length,
275
+ installedBuiltinsCount: installedBuiltinIds.size,
276
+ missingBuiltinsCount: missingBuiltins.length,
277
+ hasBuiltinsInstalled: missingBuiltins.length === 0,
278
+ missingBuiltins,
279
+ unmanagedCount: unmanaged.size,
280
+ conflictCount: conflicts.length,
281
+ unmanaged,
282
+ conflicts,
283
+ };
284
+ }
285
+
286
+ async function installBuiltins(agents, { quiet = false } = {}) {
259
287
  const st = loadState();
260
288
 
261
289
  const installed = [];
@@ -324,14 +352,14 @@ async function installBuiltins(agents) {
324
352
 
325
353
  saveState(st);
326
354
 
327
- if (installed.length > 0) {
355
+ if (!quiet && installed.length > 0) {
328
356
  console.log('\nInstalled built-in skills:');
329
357
  for (const name of installed) {
330
358
  console.log(` ✓ ${name} → ${Object.keys(agents).join(', ') || '(no agents detected)'}`);
331
359
  }
332
360
  }
333
361
 
334
- if (skipped.length > 0) {
362
+ if (!quiet && skipped.length > 0) {
335
363
  console.log('\nSkipped built-ins to avoid skill-name conflicts in agents:');
336
364
  for (const s of skipped) {
337
365
  const where = s.conflicts.map(c => `${c.agent}/${c.entryName}`).join(', ');
@@ -339,6 +367,22 @@ async function installBuiltins(agents) {
339
367
  }
340
368
  console.log(' Tip: remove/rename duplicates if you want bundled docs linked automatically.');
341
369
  }
370
+
371
+ return { installed, skipped };
372
+ }
373
+
374
+ export async function ensureBuiltinsInstalled({ quiet = true } = {}) {
375
+ const d = getLocalDiagnostics();
376
+ if (d.missingBuiltinsCount === 0) {
377
+ return { changed: false, installed: [], skipped: [] };
378
+ }
379
+
380
+ const result = await installBuiltins(d.agents, { quiet });
381
+ return {
382
+ changed: result.installed.length > 0,
383
+ installed: result.installed,
384
+ skipped: result.skipped,
385
+ };
342
386
  }
343
387
 
344
388
  // --- Commands ---
@@ -385,14 +429,17 @@ export async function cmdInit(args) {
385
429
  throw new Error(`Token invalid: ${e.message}`);
386
430
  }
387
431
 
388
- // Import existing skills
389
- if (!args.noImport) {
390
- await cmdImport({ all: true, quiet: false });
391
- }
392
-
393
- // Install built-ins
432
+ // Install built-ins (safe local setup)
394
433
  await installBuiltins(agents);
395
434
 
435
+ const unmanaged = discoverUnmanaged(agents, loadState());
436
+
437
+ console.log('\nSetup complete.');
438
+ console.log('Next command: skills');
439
+ if (unmanaged.size > 0) {
440
+ console.log(`Optional publish step: skills import (found ${unmanaged.size} unmanaged local skill(s))`);
441
+ }
442
+
396
443
  console.log('\nReady! Ask your agent to "install a skill" or "create a new skill".');
397
444
  }
398
445
 
@@ -417,10 +464,9 @@ export async function cmdLogin(args) {
417
464
  console.log(` Your publish namespace is: ${me.org}/${me.user}/<skillname>`);
418
465
 
419
466
  // First-time setup guidance
420
- const st = loadState();
421
- const agents = detectAgents();
422
- const unmanaged = discoverUnmanaged(agents, st);
423
- const hasBuiltins = Object.keys(st.installed).some(id => id.startsWith('__builtin__/local/'));
467
+ const diag = getLocalDiagnostics();
468
+ const unmanaged = diag.unmanaged;
469
+ const hasBuiltins = diag.hasBuiltinsInstalled;
424
470
 
425
471
  if (!hasBuiltins || unmanaged.size > 0) {
426
472
  console.log('\nSetup not complete yet.');
@@ -429,12 +475,18 @@ export async function cmdLogin(args) {
429
475
 
430
476
  const doSetup = await promptYesNo('Run guided setup now? (recommended)', true);
431
477
  if (doSetup) {
432
- await cmdInit({ server: cfg.server, token: cfg.token, noImport: false });
478
+ await cmdInit({ server: cfg.server, token: cfg.token });
479
+ if (unmanaged.size > 0) {
480
+ const doImport = await promptYesNo('Import unmanaged local skills to registry now?', false);
481
+ if (doImport) {
482
+ await cmdImport({ all: true, quiet: false });
483
+ }
484
+ }
433
485
  return;
434
486
  }
435
487
 
436
488
  console.log('\nNext steps:');
437
- if (!hasBuiltins) console.log(' skills init --no-import');
489
+ if (!hasBuiltins) console.log(' skills init');
438
490
  if (unmanaged.size > 0) console.log(' skills import');
439
491
  }
440
492
  }
package/lib/startup.js ADDED
@@ -0,0 +1,97 @@
1
+ import { loadConfig, saveConfig } from './config.js';
2
+ import { ensureBuiltinsInstalled, getLocalDiagnostics } from './skills.js';
3
+
4
+ function cmpSemver(a, b) {
5
+ const pa = String(a || '').split('.').map(n => parseInt(n, 10) || 0);
6
+ const pb = String(b || '').split('.').map(n => parseInt(n, 10) || 0);
7
+ const len = Math.max(pa.length, pb.length);
8
+ for (let i = 0; i < len; i++) {
9
+ const da = pa[i] || 0;
10
+ const db = pb[i] || 0;
11
+ if (da > db) return 1;
12
+ if (da < db) return -1;
13
+ }
14
+ return 0;
15
+ }
16
+
17
+ async function latestPublishedVersion() {
18
+ const controller = new AbortController();
19
+ const timeout = setTimeout(() => controller.abort(), 2500);
20
+ try {
21
+ const res = await fetch('https://registry.npmjs.org/@npeercy%2fskills/latest', {
22
+ signal: controller.signal,
23
+ });
24
+ if (!res.ok) return '';
25
+ const data = await res.json();
26
+ return data.version || '';
27
+ } catch {
28
+ return '';
29
+ } finally {
30
+ clearTimeout(timeout);
31
+ }
32
+ }
33
+
34
+ async function runUpdateCheck(currentVersion, { silent = false } = {}) {
35
+ if (process.env.SKILLS_NO_UPDATE_CHECK === '1') return;
36
+
37
+ const cfg = loadConfig();
38
+ const latestLive = await latestPublishedVersion();
39
+ const latest = latestLive || cfg.updateCheck?.latestVersion || '';
40
+
41
+ if (latestLive) {
42
+ cfg.updateCheck = {
43
+ lastChecked: new Date().toISOString(),
44
+ latestVersion: latestLive,
45
+ };
46
+ saveConfig(cfg);
47
+ }
48
+
49
+ if (!silent && latest && cmpSemver(latest, currentVersion) > 0) {
50
+ console.log(`⚠ Update available: @npeercy/skills ${currentVersion} → ${latest}`);
51
+ console.log(' Run: npm install -g @npeercy/skills\n');
52
+ }
53
+ }
54
+
55
+ async function runAutoHeal(command, { jsonMode = false } = {}) {
56
+ if (process.env.SKILLS_NO_STARTUP_HEAL === '1') return;
57
+ if (['init'].includes(command)) return;
58
+
59
+ const result = await ensureBuiltinsInstalled({ quiet: true });
60
+ if (!jsonMode && result.changed) {
61
+ console.log(`✓ Startup heal: installed ${result.installed.length} missing built-in skill(s).`);
62
+ }
63
+ }
64
+
65
+ function printSetupWarnings(command, startupInfo, { jsonMode = false } = {}) {
66
+ if (jsonMode) return;
67
+
68
+ // Avoid noisy preflight banner for direct setup/auth commands.
69
+ if (['init', 'login'].includes(command)) return;
70
+
71
+ const warnings = [];
72
+ if (!startupInfo.hasBuiltinsInstalled && startupInfo.bundledBuiltinsCount > 0) {
73
+ warnings.push('Built-in skills are not installed. Run: skills init');
74
+ }
75
+ if (startupInfo.unmanagedCount > 0) {
76
+ warnings.push(`Found ${startupInfo.unmanagedCount} unmanaged local skill(s). Run: skills import`);
77
+ }
78
+ if (startupInfo.conflictCount > 0) {
79
+ warnings.push(`Found ${startupInfo.conflictCount} duplicate skill-name conflict(s). Run: skills doctor`);
80
+ }
81
+
82
+ if (warnings.length > 0) {
83
+ console.log('⚠ Startup checks:');
84
+ for (const w of warnings) {
85
+ console.log(` - ${w}`);
86
+ }
87
+ console.log('');
88
+ }
89
+ }
90
+
91
+ export async function runStartupChecks({ currentVersion, command, jsonMode = false }) {
92
+ await runUpdateCheck(currentVersion, { silent: jsonMode });
93
+ await runAutoHeal(command, { jsonMode });
94
+ const startupInfo = getLocalDiagnostics();
95
+ printSetupWarnings(command, startupInfo, { jsonMode });
96
+ return startupInfo;
97
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@npeercy/skills",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "CLI-first skill marketplace for coding agents",
5
5
  "type": "module",
6
6
  "bin": {