@npeercy/skills 0.1.2 → 0.1.4
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 +142 -42
- 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,23 +95,81 @@ async function promptForToken(serverUrl) {
|
|
|
95
95
|
return token;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
async function
|
|
99
|
-
const
|
|
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
|
+
|
|
144
|
+
function bundledBuiltins() {
|
|
145
|
+
const out = [];
|
|
100
146
|
const builtinsRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..', 'builtin-skills');
|
|
101
|
-
if (!existsSync(builtinsRoot)) return;
|
|
147
|
+
if (!existsSync(builtinsRoot)) return out;
|
|
102
148
|
|
|
103
|
-
const installed = [];
|
|
104
149
|
for (const entry of readdirSync(builtinsRoot, { withFileTypes: true })) {
|
|
105
150
|
if (!entry.isDirectory()) continue;
|
|
106
|
-
|
|
107
151
|
const srcDir = join(builtinsRoot, entry.name);
|
|
108
152
|
const skillMd = join(srcDir, 'SKILL.md');
|
|
109
153
|
if (!existsSync(skillMd)) continue;
|
|
110
|
-
|
|
111
154
|
const meta = parseFrontmatter(readFileSync(skillMd, 'utf8'));
|
|
112
155
|
if (!meta) continue;
|
|
156
|
+
out.push({
|
|
157
|
+
id: `__builtin__/local/${meta.name}`,
|
|
158
|
+
name: meta.name,
|
|
159
|
+
srcDir,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return out;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function installBuiltins(agents) {
|
|
167
|
+
const st = loadState();
|
|
113
168
|
|
|
114
|
-
|
|
169
|
+
const installed = [];
|
|
170
|
+
for (const b of bundledBuiltins()) {
|
|
171
|
+
const srcDir = b.srcDir;
|
|
172
|
+
const id = b.id;
|
|
115
173
|
const version = 'v1';
|
|
116
174
|
const dest = managedPath(id, version);
|
|
117
175
|
const files = collectFiles(srcDir);
|
|
@@ -149,7 +207,7 @@ async function installBuiltins(agents) {
|
|
|
149
207
|
builtin: true,
|
|
150
208
|
};
|
|
151
209
|
|
|
152
|
-
installed.push(
|
|
210
|
+
installed.push(b.name);
|
|
153
211
|
}
|
|
154
212
|
|
|
155
213
|
saveState(st);
|
|
@@ -207,7 +265,7 @@ export async function cmdInit(args) {
|
|
|
207
265
|
|
|
208
266
|
// Import existing skills
|
|
209
267
|
if (!args.noImport) {
|
|
210
|
-
await cmdImport({ all: true, quiet:
|
|
268
|
+
await cmdImport({ all: true, quiet: false });
|
|
211
269
|
}
|
|
212
270
|
|
|
213
271
|
// Install built-ins
|
|
@@ -234,6 +292,29 @@ export async function cmdLogin(args) {
|
|
|
234
292
|
cfg.org = me.org;
|
|
235
293
|
saveConfig(cfg);
|
|
236
294
|
console.log(`✓ Logged in as ${me.user} (org: ${me.org}, role: ${me.role})`);
|
|
295
|
+
console.log(` Your publish namespace is: ${me.org}/${me.user}/<skillname>`);
|
|
296
|
+
|
|
297
|
+
// First-time setup guidance
|
|
298
|
+
const st = loadState();
|
|
299
|
+
const agents = detectAgents();
|
|
300
|
+
const unmanaged = discoverUnmanaged(agents, st);
|
|
301
|
+
const hasBuiltins = Object.keys(st.installed).some(id => id.startsWith('__builtin__/local/'));
|
|
302
|
+
|
|
303
|
+
if (!hasBuiltins || unmanaged.size > 0) {
|
|
304
|
+
console.log('\nSetup not complete yet.');
|
|
305
|
+
if (!hasBuiltins) console.log(' - Built-in skills are not installed yet.');
|
|
306
|
+
if (unmanaged.size > 0) console.log(` - Found ${unmanaged.size} unmanaged local skill(s).`);
|
|
307
|
+
|
|
308
|
+
const doSetup = await promptYesNo('Run guided setup now? (recommended)', true);
|
|
309
|
+
if (doSetup) {
|
|
310
|
+
await cmdInit({ server: cfg.server, token: cfg.token, noImport: false });
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
console.log('\nNext steps:');
|
|
315
|
+
if (!hasBuiltins) console.log(' skills init --no-import');
|
|
316
|
+
if (unmanaged.size > 0) console.log(' skills import');
|
|
317
|
+
}
|
|
237
318
|
}
|
|
238
319
|
|
|
239
320
|
export async function cmdLogout() {
|
|
@@ -342,12 +423,13 @@ export async function cmdInstall(args) {
|
|
|
342
423
|
console.log('Installed.');
|
|
343
424
|
}
|
|
344
425
|
|
|
345
|
-
export async function cmdList(args) {
|
|
346
|
-
const cfg = loadConfig();
|
|
426
|
+
export async function cmdList(args = {}) {
|
|
347
427
|
const st = loadState();
|
|
348
428
|
const agents = detectAgents();
|
|
349
429
|
|
|
350
430
|
const rows = [];
|
|
431
|
+
|
|
432
|
+
// Managed installs (skill-sharer state)
|
|
351
433
|
for (const [id, rec] of Object.entries(st.installed).sort()) {
|
|
352
434
|
let status = 'ok';
|
|
353
435
|
if (args.verify) {
|
|
@@ -360,15 +442,54 @@ export async function cmdList(args) {
|
|
|
360
442
|
const { org, user, name } = splitSkillId(id);
|
|
361
443
|
const data = await api.info(org, user, name);
|
|
362
444
|
latest = data.latest_version || rec.version;
|
|
363
|
-
} catch {
|
|
445
|
+
} catch {
|
|
446
|
+
// offline / not in registry
|
|
447
|
+
}
|
|
364
448
|
}
|
|
365
449
|
|
|
366
450
|
const outdated = latest !== rec.version;
|
|
367
451
|
if (args.outdated && !outdated) continue;
|
|
368
452
|
if (outdated && status === 'ok') status = 'outdated';
|
|
369
453
|
|
|
370
|
-
rows.push({
|
|
371
|
-
|
|
454
|
+
rows.push({
|
|
455
|
+
id,
|
|
456
|
+
version: rec.version,
|
|
457
|
+
latest,
|
|
458
|
+
agents: (rec.agents || []).join(','),
|
|
459
|
+
status,
|
|
460
|
+
managed: true,
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Bundled built-ins (show even if not installed yet)
|
|
465
|
+
const installedIds = new Set(Object.keys(st.installed));
|
|
466
|
+
for (const b of bundledBuiltins()) {
|
|
467
|
+
if (installedIds.has(b.id)) continue;
|
|
468
|
+
rows.push({
|
|
469
|
+
id: b.id,
|
|
470
|
+
version: '-',
|
|
471
|
+
latest: 'v1',
|
|
472
|
+
agents: '-',
|
|
473
|
+
status: 'bundled',
|
|
474
|
+
managed: false,
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Unmanaged installs (present in agent dirs but not in skill-sharer state)
|
|
479
|
+
const unmanaged = discoverUnmanaged(agents, st);
|
|
480
|
+
for (const [name, info] of unmanaged.entries()) {
|
|
481
|
+
const agentList = [...new Set(info.sources.map(s => s.agent))].sort().join(',');
|
|
482
|
+
rows.push({
|
|
483
|
+
id: `(unmanaged) ${name}`,
|
|
484
|
+
version: '-',
|
|
485
|
+
latest: '-',
|
|
486
|
+
agents: agentList,
|
|
487
|
+
status: 'unmanaged',
|
|
488
|
+
managed: false,
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
rows.sort((a, b) => a.id.localeCompare(b.id));
|
|
372
493
|
|
|
373
494
|
if (args.json) {
|
|
374
495
|
console.log(JSON.stringify(rows, null, 2));
|
|
@@ -376,7 +497,7 @@ export async function cmdList(args) {
|
|
|
376
497
|
}
|
|
377
498
|
|
|
378
499
|
if (rows.length === 0) {
|
|
379
|
-
console.log('No
|
|
500
|
+
console.log('No skills found in managed store or agent directories.');
|
|
380
501
|
return;
|
|
381
502
|
}
|
|
382
503
|
|
|
@@ -384,6 +505,11 @@ export async function cmdList(args) {
|
|
|
384
505
|
for (const r of rows) {
|
|
385
506
|
console.log(r.id.padEnd(56) + r.version.padEnd(10) + r.latest.padEnd(10) + r.agents.padEnd(20) + r.status);
|
|
386
507
|
}
|
|
508
|
+
|
|
509
|
+
const unmanagedCount = rows.filter(r => r.status === 'unmanaged').length;
|
|
510
|
+
if (unmanagedCount > 0) {
|
|
511
|
+
console.log(`\nFound ${unmanagedCount} unmanaged skill(s). Run: skills import`);
|
|
512
|
+
}
|
|
387
513
|
}
|
|
388
514
|
|
|
389
515
|
function verifyInstall(id, rec, agents) {
|
|
@@ -536,33 +662,7 @@ export async function cmdImport(args) {
|
|
|
536
662
|
const agents = detectAgents();
|
|
537
663
|
|
|
538
664
|
// 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
|
-
}
|
|
665
|
+
const unmanaged = discoverUnmanaged(agents, st);
|
|
566
666
|
|
|
567
667
|
if (args.path) {
|
|
568
668
|
const p = resolve(args.path);
|