@npeercy/skills 0.1.2 → 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 +10 -2
- package/lib/skills.js +107 -33
- package/package.json +1 -1
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
|
-
|
|
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,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);
|