@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 +3 -3
- package/lib/skills.js +51 -20
- package/lib/startup.js +72 -3
- package/package.json +1 -1
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>]
|
|
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
|
|
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
|
-
|
|
81
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
441
|
-
const
|
|
442
|
-
const
|
|
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
|
|
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
|
|
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 {
|
|
2
|
-
import {
|
|
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
|
|
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;
|