@npeercy/skills 0.1.1 → 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 +65 -2
- package/lib/config.js +9 -1
- package/lib/skills.js +107 -33
- package/package.json +1 -1
- package/npeercy-skills-0.1.0.tgz +0 -0
package/bin/skills.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { parseArgs } from 'node:util';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
3
6
|
import {
|
|
4
7
|
cmdInit, cmdLogin, cmdLogout, cmdSearch, cmdInfo,
|
|
5
8
|
cmdInstall, cmdList, cmdUpdate, cmdUninstall,
|
|
6
9
|
cmdEdit, cmdValidate, cmdPublish, cmdImport, cmdDoctor,
|
|
7
10
|
cmdShare, cmdUnshare, cmdVisibility,
|
|
8
11
|
} from '../lib/skills.js';
|
|
12
|
+
import { loadConfig, saveConfig } from '../lib/config.js';
|
|
9
13
|
|
|
10
14
|
const USAGE = `skill-sharer — CLI skill manager for coding agents
|
|
11
15
|
|
|
@@ -19,10 +23,10 @@ Setup:
|
|
|
19
23
|
Discovery:
|
|
20
24
|
search [query] Search skills (no query = browse all)
|
|
21
25
|
info <skill> Skill details + versions
|
|
26
|
+
list [--verify] [--outdated] [--json] Show all local skills (managed + unmanaged)
|
|
22
27
|
|
|
23
28
|
Install:
|
|
24
29
|
install <skill>[@ver] [--agent <a>] [--dry-run]
|
|
25
|
-
list [--verify] [--outdated] [--json]
|
|
26
30
|
update [<skill>] Update one or all
|
|
27
31
|
uninstall <skill>
|
|
28
32
|
|
|
@@ -43,9 +47,66 @@ Maintenance:
|
|
|
43
47
|
doctor [--dry] Diagnose + fix issues
|
|
44
48
|
`;
|
|
45
49
|
|
|
50
|
+
const PKG = JSON.parse(
|
|
51
|
+
readFileSync(join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json'), 'utf8')
|
|
52
|
+
);
|
|
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
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
46
99
|
async function main() {
|
|
47
100
|
const args = process.argv.slice(2);
|
|
48
|
-
|
|
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') {
|
|
49
110
|
console.log(USAGE);
|
|
50
111
|
return;
|
|
51
112
|
}
|
|
@@ -53,6 +114,8 @@ async function main() {
|
|
|
53
114
|
const cmd = args[0];
|
|
54
115
|
const rest = args.slice(1);
|
|
55
116
|
|
|
117
|
+
await maybeWarnOutdated();
|
|
118
|
+
|
|
56
119
|
try {
|
|
57
120
|
switch (cmd) {
|
|
58
121
|
case 'init': {
|
package/lib/config.js
CHANGED
|
@@ -23,7 +23,15 @@ function saveJson(path, data) {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
// --- Config ---
|
|
26
|
-
const DEFAULT_CONFIG = {
|
|
26
|
+
const DEFAULT_CONFIG = {
|
|
27
|
+
server: 'https://skills.npeercy.com',
|
|
28
|
+
org: '',
|
|
29
|
+
token: '',
|
|
30
|
+
updateCheck: {
|
|
31
|
+
lastChecked: '',
|
|
32
|
+
latestVersion: ''
|
|
33
|
+
}
|
|
34
|
+
};
|
|
27
35
|
|
|
28
36
|
export function loadConfig() {
|
|
29
37
|
mkdirSync(CONFIG_HOME, { recursive: true });
|
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:
|
|
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 {
|
|
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({
|
|
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
|
|
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 =
|
|
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
package/npeercy-skills-0.1.0.tgz
DELETED
|
Binary file
|