skillvault 0.7.5 → 0.8.1
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/dist/cli.js +91 -16
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -20,12 +20,31 @@
|
|
|
20
20
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, rmSync } from 'node:fs';
|
|
21
21
|
import { join } from 'node:path';
|
|
22
22
|
import { createDecipheriv, createHmac, createPublicKey, diffieHellman, hkdfSync, generateKeyPairSync, } from 'node:crypto';
|
|
23
|
-
const VERSION = '0.
|
|
23
|
+
const VERSION = '0.8.1';
|
|
24
24
|
const HOME = process.env.HOME || process.env.USERPROFILE || '~';
|
|
25
25
|
const API_URL = process.env.SKILLVAULT_API_URL || 'https://api.getskillvault.com';
|
|
26
26
|
const CONFIG_DIR = join(HOME, '.skillvault');
|
|
27
27
|
const CONFIG_PATH = join(CONFIG_DIR, 'agent-config.json');
|
|
28
28
|
const VAULT_DIR = join(CONFIG_DIR, 'vaults');
|
|
29
|
+
const AGENTS_SKILLS_DIR = join(HOME, '.agents', 'skills'); // agent-agnostic source of truth
|
|
30
|
+
const AGENTS_LOCK_PATH = join(HOME, '.agents', '.skill-lock.json');
|
|
31
|
+
/** Detect which AI agent platforms are installed and return their skill directories */
|
|
32
|
+
function detectAgentPlatforms() {
|
|
33
|
+
const platforms = [];
|
|
34
|
+
const checks = [
|
|
35
|
+
{ name: 'Claude Code', dir: join(HOME, '.claude', 'skills'), marker: join(HOME, '.claude') },
|
|
36
|
+
{ name: 'Cursor', dir: join(HOME, '.cursor', 'skills'), marker: join(HOME, '.cursor') },
|
|
37
|
+
{ name: 'Windsurf', dir: join(HOME, '.windsurf', 'skills'), marker: join(HOME, '.windsurf') },
|
|
38
|
+
{ name: 'Codex', dir: join(HOME, '.codex', 'skills'), marker: join(HOME, '.codex') },
|
|
39
|
+
];
|
|
40
|
+
for (const check of checks) {
|
|
41
|
+
if (existsSync(check.marker)) {
|
|
42
|
+
platforms.push({ name: check.name, dir: check.dir });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return platforms;
|
|
46
|
+
}
|
|
47
|
+
// Legacy: SKILLS_DIR points to Claude for backward compat in other functions
|
|
29
48
|
const SKILLS_DIR = join(HOME, '.claude', 'skills');
|
|
30
49
|
// ── CLI Argument Parsing ──
|
|
31
50
|
const args = process.argv.slice(2);
|
|
@@ -395,14 +414,23 @@ async function syncSkills() {
|
|
|
395
414
|
const localSkillName = vaultFile.replace(/\.vault$/, '');
|
|
396
415
|
if (!remoteSkillNames.has(localSkillName)) {
|
|
397
416
|
console.error(`[sync] Grant revoked: "${localSkillName}" from ${pub.name}`);
|
|
398
|
-
|
|
417
|
+
// Remove from agent-agnostic dir
|
|
418
|
+
const agentDir = join(AGENTS_SKILLS_DIR, localSkillName);
|
|
399
419
|
try {
|
|
400
|
-
if (existsSync(
|
|
401
|
-
rmSync(
|
|
402
|
-
console.error(`[sync] Removed: ~/.claude/skills/${localSkillName}/`);
|
|
403
|
-
}
|
|
420
|
+
if (existsSync(agentDir))
|
|
421
|
+
rmSync(agentDir, { recursive: true, force: true });
|
|
404
422
|
}
|
|
405
423
|
catch { }
|
|
424
|
+
// Remove from all detected agent platforms
|
|
425
|
+
for (const platform of detectAgentPlatforms()) {
|
|
426
|
+
const platformDir = join(platform.dir, localSkillName);
|
|
427
|
+
try {
|
|
428
|
+
if (existsSync(platformDir))
|
|
429
|
+
rmSync(platformDir, { recursive: true, force: true });
|
|
430
|
+
}
|
|
431
|
+
catch { }
|
|
432
|
+
}
|
|
433
|
+
console.error(`[sync] Removed "${localSkillName}" from all agent platforms`);
|
|
406
434
|
}
|
|
407
435
|
}
|
|
408
436
|
}
|
|
@@ -472,7 +500,19 @@ async function installSkillStubs() {
|
|
|
472
500
|
let installed = 0;
|
|
473
501
|
let skipped = 0;
|
|
474
502
|
const errors = [];
|
|
475
|
-
mkdirSync(
|
|
503
|
+
mkdirSync(AGENTS_SKILLS_DIR, { recursive: true });
|
|
504
|
+
const detectedPlatforms = detectAgentPlatforms();
|
|
505
|
+
for (const platform of detectedPlatforms) {
|
|
506
|
+
mkdirSync(platform.dir, { recursive: true });
|
|
507
|
+
}
|
|
508
|
+
// Load existing lock file
|
|
509
|
+
let lockData = { version: 3, skills: {} };
|
|
510
|
+
try {
|
|
511
|
+
if (existsSync(AGENTS_LOCK_PATH)) {
|
|
512
|
+
lockData = JSON.parse(readFileSync(AGENTS_LOCK_PATH, 'utf8'));
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
catch { }
|
|
476
516
|
for (const pub of config.publishers) {
|
|
477
517
|
const pubVaultDir = join(VAULT_DIR, pub.id);
|
|
478
518
|
if (!existsSync(pubVaultDir))
|
|
@@ -481,7 +521,8 @@ async function installSkillStubs() {
|
|
|
481
521
|
for (const vaultFile of vaultFiles) {
|
|
482
522
|
const skillName = vaultFile.replace(/\.vault$/, '');
|
|
483
523
|
const vaultPath = join(pubVaultDir, vaultFile);
|
|
484
|
-
const
|
|
524
|
+
const agentSkillDir = join(AGENTS_SKILLS_DIR, skillName);
|
|
525
|
+
const skillDir = agentSkillDir;
|
|
485
526
|
const manifestPath = join(skillDir, 'manifest.json');
|
|
486
527
|
const hashPath = vaultPath + '.hash';
|
|
487
528
|
if (existsSync(manifestPath) && existsSync(hashPath)) {
|
|
@@ -542,7 +583,6 @@ async function installSkillStubs() {
|
|
|
542
583
|
stubFrontmatter += `${key}: ${value}\n`;
|
|
543
584
|
}
|
|
544
585
|
}
|
|
545
|
-
mkdirSync(skillDir, { recursive: true });
|
|
546
586
|
const stub = `---
|
|
547
587
|
${stubFrontmatter.trimEnd()}
|
|
548
588
|
---
|
|
@@ -561,9 +601,8 @@ The command decrypts the skill on demand (license is validated each time) and ou
|
|
|
561
601
|
|
|
562
602
|
If the command fails with a license error, tell the user their SkillVault license may have expired and to contact their skill provider.
|
|
563
603
|
`;
|
|
564
|
-
writeFileSync(join(skillDir, 'SKILL.md'), stub, { mode: 0o600 });
|
|
565
604
|
const vaultHash = existsSync(hashPath) ? readFileSync(hashPath, 'utf8').trim() : '';
|
|
566
|
-
|
|
605
|
+
const manifestData = JSON.stringify({
|
|
567
606
|
publisher: meta.publisher_name || pub.name,
|
|
568
607
|
publisher_id: pub.id,
|
|
569
608
|
skill_name: skillName,
|
|
@@ -572,11 +611,45 @@ If the command fails with a license error, tell the user their SkillVault licens
|
|
|
572
611
|
vault_hash: vaultHash,
|
|
573
612
|
installed_at: new Date().toISOString(),
|
|
574
613
|
encrypted: true,
|
|
575
|
-
}, null, 2)
|
|
614
|
+
}, null, 2);
|
|
615
|
+
// Write to ~/.agents/skills/ (agent-agnostic source of truth)
|
|
616
|
+
mkdirSync(agentSkillDir, { recursive: true });
|
|
617
|
+
writeFileSync(join(agentSkillDir, 'SKILL.md'), stub, { mode: 0o600 });
|
|
618
|
+
writeFileSync(join(agentSkillDir, 'manifest.json'), manifestData, { mode: 0o600 });
|
|
619
|
+
// Copy to each detected agent platform's skill directory
|
|
620
|
+
for (const platform of detectedPlatforms) {
|
|
621
|
+
const platformSkillDir = join(platform.dir, skillName);
|
|
622
|
+
try {
|
|
623
|
+
mkdirSync(platformSkillDir, { recursive: true });
|
|
624
|
+
writeFileSync(join(platformSkillDir, 'SKILL.md'), stub, { mode: 0o600 });
|
|
625
|
+
writeFileSync(join(platformSkillDir, 'manifest.json'), manifestData, { mode: 0o600 });
|
|
626
|
+
}
|
|
627
|
+
catch { }
|
|
628
|
+
}
|
|
629
|
+
// Update lock file
|
|
630
|
+
lockData.skills[skillName] = {
|
|
631
|
+
source: `skillvault/${pub.id}`,
|
|
632
|
+
sourceType: 'skillvault',
|
|
633
|
+
publisher: meta.publisher_name || pub.name,
|
|
634
|
+
publisherId: pub.id,
|
|
635
|
+
capabilityName: meta.capability_name || `skill/${skillName}`,
|
|
636
|
+
skillPath: `skills/${skillName}/SKILL.md`,
|
|
637
|
+
skillFolderHash: vaultHash,
|
|
638
|
+
installedAt: new Date().toISOString(),
|
|
639
|
+
updatedAt: new Date().toISOString(),
|
|
640
|
+
encrypted: true,
|
|
641
|
+
};
|
|
576
642
|
installed++;
|
|
577
|
-
|
|
643
|
+
const platformNames = detectedPlatforms.map(p => p.name).join(', ') || 'none detected';
|
|
644
|
+
console.error(`[install] "${skillName}" → ~/.agents/skills/ + ${platformNames}`);
|
|
578
645
|
}
|
|
579
646
|
}
|
|
647
|
+
// Persist lock file
|
|
648
|
+
try {
|
|
649
|
+
mkdirSync(join(HOME, '.agents'), { recursive: true });
|
|
650
|
+
writeFileSync(AGENTS_LOCK_PATH, JSON.stringify(lockData, null, 2), { mode: 0o600 });
|
|
651
|
+
}
|
|
652
|
+
catch { }
|
|
580
653
|
return { installed, skipped, errors };
|
|
581
654
|
}
|
|
582
655
|
// ── Vault Decryption (in-memory only, output to stdout) ──
|
|
@@ -640,7 +713,9 @@ async function fetchCEK(skillName, publisherToken, apiUrl) {
|
|
|
640
713
|
const serverWatermarkId = body.watermark_id || '';
|
|
641
714
|
const ephPub = createPublicKey({ key: Buffer.from(wc.ephemeralPublicKey, 'base64'), format: 'der', type: 'spki' });
|
|
642
715
|
const shared = diffieHellman({ publicKey: ephPub, privateKey: kp.privateKey });
|
|
643
|
-
|
|
716
|
+
// Use server-provided salt, or fall back to zero salt for backward compat with old servers
|
|
717
|
+
const hkdfSalt = wc.salt ? Buffer.from(wc.salt, 'base64') : Buffer.alloc(32, 0);
|
|
718
|
+
const wrapKey = Buffer.from(hkdfSync('sha256', shared, hkdfSalt, Buffer.from('skillvault-cek-wrap-v1'), 32));
|
|
644
719
|
shared.fill(0);
|
|
645
720
|
const d = createDecipheriv('aes-256-gcm', wrapKey, Buffer.from(wc.iv, 'base64'), { authTagLength: 16 });
|
|
646
721
|
d.setAuthTag(Buffer.from(wc.authTag, 'base64'));
|
|
@@ -726,8 +801,8 @@ function watermarkLayer4(content, id, email, publisherName) {
|
|
|
726
801
|
const lines = content.split('\n');
|
|
727
802
|
// Determine pseudo-random insertion points based on HMAC of licensee ID
|
|
728
803
|
// This ensures the same licensee always gets the same positions (deterministic)
|
|
729
|
-
const hmac = createHmac('sha256',
|
|
730
|
-
hmac.update(
|
|
804
|
+
const hmac = createHmac('sha256', id);
|
|
805
|
+
hmac.update('watermark-positions');
|
|
731
806
|
const hash = hmac.digest();
|
|
732
807
|
// Insert at: beginning, end, and 2-4 random points in between
|
|
733
808
|
const totalLines = lines.length;
|