@npeercy/skills 0.1.7 → 0.1.9

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
@@ -16,7 +16,7 @@ const USAGE = `skill-sharer — CLI skill manager for coding agents
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
 
@@ -85,9 +85,9 @@ async function main() {
85
85
  const { values } = parseArgs({ args: rest, options: {
86
86
  server: { type: 'string' },
87
87
  token: { type: 'string' },
88
- 'no-import': { type: 'boolean', default: false },
88
+ 'no-import': { type: 'boolean', default: false }, // deprecated compatibility; init never imports
89
89
  }, allowPositionals: false });
90
- await cmdInit({ server: values.server, token: values.token, noImport: values['no-import'] });
90
+ await cmdInit({ server: values.server, token: values.token });
91
91
  break;
92
92
  }
93
93
  case 'login': {
package/lib/skills.js CHANGED
@@ -77,10 +77,9 @@ async function resolveSkillId(input, config) {
77
77
  }
78
78
 
79
79
  function linkNameForSkill(skillId) {
80
- if (skillId.startsWith('__builtin__/local/')) {
81
- return `builtin--${splitSkillId(skillId).name}`;
82
- }
83
- return symlinkName(skillId);
80
+ // Use bare skill name so directory matches name: field in SKILL.md.
81
+ // Pi expects these to match; mismatches show as "[Skill conflicts]".
82
+ return splitSkillId(skillId).name;
84
83
  }
85
84
 
86
85
  async function promptForToken(serverUrl) {
@@ -262,12 +261,20 @@ export function getLocalDiagnostics() {
262
261
  const unmanaged = discoverUnmanaged(agents, st);
263
262
  const conflicts = discoverAgentNameConflicts(agents);
264
263
 
264
+ const installedBuiltinIds = new Set(
265
+ Object.keys(st.installed).filter(id => id.startsWith('__builtin__/local/')),
266
+ );
267
+ const missingBuiltins = bundled.filter(b => !installedBuiltinIds.has(b.id));
268
+
265
269
  return {
266
270
  state: st,
267
271
  agents,
268
272
  managedCount: Object.keys(st.installed).length,
269
273
  bundledBuiltinsCount: bundled.length,
270
- hasBuiltinsInstalled: Object.keys(st.installed).some(id => id.startsWith('__builtin__/local/')),
274
+ installedBuiltinsCount: installedBuiltinIds.size,
275
+ missingBuiltinsCount: missingBuiltins.length,
276
+ hasBuiltinsInstalled: missingBuiltins.length === 0,
277
+ missingBuiltins,
271
278
  unmanagedCount: unmanaged.size,
272
279
  conflictCount: conflicts.length,
273
280
  unmanaged,
@@ -275,7 +282,7 @@ export function getLocalDiagnostics() {
275
282
  };
276
283
  }
277
284
 
278
- async function installBuiltins(agents) {
285
+ async function installBuiltins(agents, { quiet = false } = {}) {
279
286
  const st = loadState();
280
287
 
281
288
  const installed = [];
@@ -344,14 +351,14 @@ async function installBuiltins(agents) {
344
351
 
345
352
  saveState(st);
346
353
 
347
- if (installed.length > 0) {
354
+ if (!quiet && installed.length > 0) {
348
355
  console.log('\nInstalled built-in skills:');
349
356
  for (const name of installed) {
350
357
  console.log(` ✓ ${name} → ${Object.keys(agents).join(', ') || '(no agents detected)'}`);
351
358
  }
352
359
  }
353
360
 
354
- if (skipped.length > 0) {
361
+ if (!quiet && skipped.length > 0) {
355
362
  console.log('\nSkipped built-ins to avoid skill-name conflicts in agents:');
356
363
  for (const s of skipped) {
357
364
  const where = s.conflicts.map(c => `${c.agent}/${c.entryName}`).join(', ');
@@ -359,6 +366,22 @@ async function installBuiltins(agents) {
359
366
  }
360
367
  console.log(' Tip: remove/rename duplicates if you want bundled docs linked automatically.');
361
368
  }
369
+
370
+ return { installed, skipped };
371
+ }
372
+
373
+ export async function ensureBuiltinsInstalled({ quiet = true } = {}) {
374
+ const d = getLocalDiagnostics();
375
+ if (d.missingBuiltinsCount === 0) {
376
+ return { changed: false, installed: [], skipped: [] };
377
+ }
378
+
379
+ const result = await installBuiltins(d.agents, { quiet });
380
+ return {
381
+ changed: result.installed.length > 0,
382
+ installed: result.installed,
383
+ skipped: result.skipped,
384
+ };
362
385
  }
363
386
 
364
387
  // --- Commands ---
@@ -405,14 +428,17 @@ export async function cmdInit(args) {
405
428
  throw new Error(`Token invalid: ${e.message}`);
406
429
  }
407
430
 
408
- // Import existing skills
409
- if (!args.noImport) {
410
- await cmdImport({ all: true, quiet: false });
411
- }
412
-
413
- // Install built-ins
431
+ // Install built-ins (safe local setup)
414
432
  await installBuiltins(agents);
415
433
 
434
+ const unmanaged = discoverUnmanaged(agents, loadState());
435
+
436
+ console.log('\nSetup complete.');
437
+ console.log('Next command: skills');
438
+ if (unmanaged.size > 0) {
439
+ console.log(`Optional publish step: skills import (found ${unmanaged.size} unmanaged local skill(s))`);
440
+ }
441
+
416
442
  console.log('\nReady! Ask your agent to "install a skill" or "create a new skill".');
417
443
  }
418
444
 
@@ -437,10 +463,9 @@ export async function cmdLogin(args) {
437
463
  console.log(` Your publish namespace is: ${me.org}/${me.user}/<skillname>`);
438
464
 
439
465
  // First-time setup guidance
440
- const st = loadState();
441
- const agents = detectAgents();
442
- const unmanaged = discoverUnmanaged(agents, st);
443
- const hasBuiltins = Object.keys(st.installed).some(id => id.startsWith('__builtin__/local/'));
466
+ const diag = getLocalDiagnostics();
467
+ const unmanaged = diag.unmanaged;
468
+ const hasBuiltins = diag.hasBuiltinsInstalled;
444
469
 
445
470
  if (!hasBuiltins || unmanaged.size > 0) {
446
471
  console.log('\nSetup not complete yet.');
@@ -449,12 +474,18 @@ export async function cmdLogin(args) {
449
474
 
450
475
  const doSetup = await promptYesNo('Run guided setup now? (recommended)', true);
451
476
  if (doSetup) {
452
- await cmdInit({ server: cfg.server, token: cfg.token, noImport: false });
477
+ await cmdInit({ server: cfg.server, token: cfg.token });
478
+ if (unmanaged.size > 0) {
479
+ const doImport = await promptYesNo('Import unmanaged local skills to registry now?', false);
480
+ if (doImport) {
481
+ await cmdImport({ all: true, quiet: false });
482
+ }
483
+ }
453
484
  return;
454
485
  }
455
486
 
456
487
  console.log('\nNext steps:');
457
- if (!hasBuiltins) console.log(' skills init --no-import');
488
+ if (!hasBuiltins) console.log(' skills init');
458
489
  if (unmanaged.size > 0) console.log(' skills import');
459
490
  }
460
491
  }
package/lib/startup.js CHANGED
@@ -1,5 +1,7 @@
1
- import { loadConfig, saveConfig } from './config.js';
2
- import { getLocalDiagnostics } from './skills.js';
1
+ import { existsSync, readdirSync, lstatSync, readlinkSync, unlinkSync, symlinkSync, mkdirSync } from 'fs';
2
+ import { join, resolve, dirname } from 'path';
3
+ import { loadConfig, saveConfig, loadState, saveState, detectAgents } from './config.js';
4
+ import { ensureBuiltinsInstalled, getLocalDiagnostics } from './skills.js';
3
5
 
4
6
  function cmpSemver(a, b) {
5
7
  const pa = String(a || '').split('.').map(n => parseInt(n, 10) || 0);
@@ -52,6 +54,72 @@ async function runUpdateCheck(currentVersion, { silent = false } = {}) {
52
54
  }
53
55
  }
54
56
 
57
+ function migrateOldSymlinks() {
58
+ // Migrate from old naming (org--user--name, builtin--name) to bare skill name.
59
+ // Pi expects directory name == name: field in SKILL.md.
60
+ const st = loadState();
61
+ const agents = detectAgents();
62
+ let migrated = 0;
63
+
64
+ for (const [id, rec] of Object.entries(st.installed)) {
65
+ const parts = id.split('/');
66
+ const bareName = parts[parts.length - 1]; // the skill name
67
+
68
+ // Figure out old link name
69
+ let oldLinkName;
70
+ if (id.startsWith('__builtin__/local/')) {
71
+ oldLinkName = `builtin--${bareName}`;
72
+ } else {
73
+ oldLinkName = id.replace(/\//g, '--');
74
+ }
75
+
76
+ // If old == new, nothing to do
77
+ if (oldLinkName === bareName) continue;
78
+
79
+ for (const agentName of (rec.agents || [])) {
80
+ const agentPath = agents[agentName];
81
+ if (!agentPath || !existsSync(agentPath)) continue;
82
+
83
+ const oldLink = join(agentPath, oldLinkName);
84
+ const newLink = join(agentPath, bareName);
85
+
86
+ // Remove old symlink if it exists
87
+ try {
88
+ if (lstatSync(oldLink).isSymbolicLink()) {
89
+ unlinkSync(oldLink);
90
+ }
91
+ } catch { /* doesn't exist */ }
92
+
93
+ // Create new symlink if not already there
94
+ if (!existsSync(newLink) && rec.path && existsSync(rec.path)) {
95
+ try {
96
+ mkdirSync(agentPath, { recursive: true });
97
+ symlinkSync(rec.path, newLink);
98
+ migrated++;
99
+ } catch { /* best effort */ }
100
+ }
101
+ }
102
+ }
103
+
104
+ return migrated;
105
+ }
106
+
107
+ async function runAutoHeal(command, { jsonMode = false } = {}) {
108
+ if (process.env.SKILLS_NO_STARTUP_HEAL === '1') return;
109
+ if (['init'].includes(command)) return;
110
+
111
+ // Migrate old-style symlinks (org--user--name → bare name)
112
+ const migrated = migrateOldSymlinks();
113
+ if (!jsonMode && migrated > 0) {
114
+ console.log(`✓ Startup heal: migrated ${migrated} symlink(s) to match Pi naming.`);
115
+ }
116
+
117
+ const result = await ensureBuiltinsInstalled({ quiet: true });
118
+ if (!jsonMode && result.changed) {
119
+ console.log(`✓ Startup heal: installed ${result.installed.length} missing built-in skill(s).`);
120
+ }
121
+ }
122
+
55
123
  function printSetupWarnings(command, startupInfo, { jsonMode = false } = {}) {
56
124
  if (jsonMode) return;
57
125
 
@@ -60,7 +128,7 @@ function printSetupWarnings(command, startupInfo, { jsonMode = false } = {}) {
60
128
 
61
129
  const warnings = [];
62
130
  if (!startupInfo.hasBuiltinsInstalled && startupInfo.bundledBuiltinsCount > 0) {
63
- warnings.push('Built-in skills are not installed. Run: skills init --no-import');
131
+ warnings.push('Built-in skills are not installed. Run: skills init');
64
132
  }
65
133
  if (startupInfo.unmanagedCount > 0) {
66
134
  warnings.push(`Found ${startupInfo.unmanagedCount} unmanaged local skill(s). Run: skills import`);
@@ -80,6 +148,7 @@ function printSetupWarnings(command, startupInfo, { jsonMode = false } = {}) {
80
148
 
81
149
  export async function runStartupChecks({ currentVersion, command, jsonMode = false }) {
82
150
  await runUpdateCheck(currentVersion, { silent: jsonMode });
151
+ await runAutoHeal(command, { jsonMode });
83
152
  const startupInfo = getLocalDiagnostics();
84
153
  printSetupWarnings(command, startupInfo, { jsonMode });
85
154
  return startupInfo;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@npeercy/skills",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "CLI-first skill marketplace for coding agents",
5
5
  "type": "module",
6
6
  "bin": {