skillvault 0.5.2 → 0.7.0
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 +95 -12
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, rmSync } from 'node:fs';
|
|
21
21
|
import { join } from 'node:path';
|
|
22
22
|
import { createDecipheriv, createPublicKey, diffieHellman, hkdfSync, generateKeyPairSync, } from 'node:crypto';
|
|
23
|
-
const VERSION = '0.
|
|
23
|
+
const VERSION = '0.7.0';
|
|
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');
|
|
@@ -235,24 +235,33 @@ async function showStatus() {
|
|
|
235
235
|
console.log('');
|
|
236
236
|
let skills = [];
|
|
237
237
|
let online = false;
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
const res = await fetch(`${config.api_url}/
|
|
242
|
-
headers: { 'Authorization': `Bearer ${token}` },
|
|
238
|
+
// Fetch skills from each publisher using companion tokens
|
|
239
|
+
for (const pub of config.publishers) {
|
|
240
|
+
try {
|
|
241
|
+
const res = await fetch(`${config.api_url}/agent/skills?publisher_id=${encodeURIComponent(pub.id)}`, {
|
|
242
|
+
headers: { 'Authorization': `Bearer ${pub.token}` },
|
|
243
243
|
signal: AbortSignal.timeout(5000),
|
|
244
244
|
});
|
|
245
245
|
if (res.ok) {
|
|
246
246
|
const data = await res.json();
|
|
247
|
-
|
|
247
|
+
for (const s of (data.skills || [])) {
|
|
248
|
+
skills.push({
|
|
249
|
+
skill_name: s.skill_name,
|
|
250
|
+
publisher_id: pub.id,
|
|
251
|
+
publisher_name: pub.name,
|
|
252
|
+
status: s.status || 'active',
|
|
253
|
+
expires_at: null,
|
|
254
|
+
last_used: null,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
248
257
|
online = true;
|
|
249
258
|
}
|
|
250
259
|
else if (res.status === 401) {
|
|
251
|
-
console.log(
|
|
260
|
+
console.log(` ⚠️ Token expired for ${pub.name}. Run: npx skillvault --refresh\n`);
|
|
252
261
|
}
|
|
253
262
|
}
|
|
263
|
+
catch { }
|
|
254
264
|
}
|
|
255
|
-
catch { }
|
|
256
265
|
console.log(' Publishers:');
|
|
257
266
|
console.log(' ' + '-'.repeat(60));
|
|
258
267
|
console.log(` ${'Name'.padEnd(25)} ${'Skills'.padEnd(10)} ${'Status'.padEnd(15)}`);
|
|
@@ -348,7 +357,7 @@ async function syncSkills() {
|
|
|
348
357
|
mkdirSync(pubVaultDir, { recursive: true });
|
|
349
358
|
let skills = [];
|
|
350
359
|
try {
|
|
351
|
-
const res = await fetch(`${config.api_url}/skills?publisher_id=${encodeURIComponent(pub.id)}`, {
|
|
360
|
+
const res = await fetch(`${config.api_url}/agent/skills?publisher_id=${encodeURIComponent(pub.id)}`, {
|
|
352
361
|
headers: { 'Authorization': `Bearer ${pub.token}` },
|
|
353
362
|
signal: AbortSignal.timeout(10000),
|
|
354
363
|
});
|
|
@@ -642,8 +651,66 @@ function watermark(content, id) {
|
|
|
642
651
|
function validateSkillName(name) {
|
|
643
652
|
return /^[a-zA-Z0-9_-]+$/.test(name) && name.length > 0 && name.length <= 128;
|
|
644
653
|
}
|
|
654
|
+
/**
|
|
655
|
+
* Quick sync for a single skill — checks for vault update before decrypting.
|
|
656
|
+
* Returns true if the vault was updated. Status goes to stderr.
|
|
657
|
+
*/
|
|
658
|
+
async function syncSingleSkill(skillName, pub, config) {
|
|
659
|
+
try {
|
|
660
|
+
const capabilityName = `skill/${skillName.toLowerCase()}`;
|
|
661
|
+
const res = await fetch(`${config.api_url}/skills/check-update?capability=${encodeURIComponent(capabilityName)}¤t_version=0.0.0`, { signal: AbortSignal.timeout(5000) });
|
|
662
|
+
if (!res.ok)
|
|
663
|
+
return false;
|
|
664
|
+
const data = await res.json();
|
|
665
|
+
// Check if local vault hash matches
|
|
666
|
+
const vaultPath = join(VAULT_DIR, pub.id, `${skillName}.vault`);
|
|
667
|
+
const hashPath = vaultPath + '.hash';
|
|
668
|
+
if (existsSync(hashPath) && data.vault_hash) {
|
|
669
|
+
const localHash = readFileSync(hashPath, 'utf8').trim();
|
|
670
|
+
if (localHash === data.vault_hash)
|
|
671
|
+
return false; // already up to date
|
|
672
|
+
}
|
|
673
|
+
// Download updated vault
|
|
674
|
+
const dlRes = await fetch(`${config.api_url}/skills/download?capability=${encodeURIComponent(capabilityName)}`, { headers: { 'Authorization': `Bearer ${pub.token}` }, signal: AbortSignal.timeout(15000) });
|
|
675
|
+
if (!dlRes.ok)
|
|
676
|
+
return false;
|
|
677
|
+
const dlData = await dlRes.json();
|
|
678
|
+
const vaultBuffer = Buffer.from(dlData.vault_data, 'base64');
|
|
679
|
+
mkdirSync(join(VAULT_DIR, pub.id), { recursive: true });
|
|
680
|
+
writeFileSync(vaultPath, vaultBuffer, { mode: 0o600 });
|
|
681
|
+
if (dlData.vault_hash)
|
|
682
|
+
writeFileSync(hashPath, dlData.vault_hash, { mode: 0o600 });
|
|
683
|
+
writeFileSync(vaultPath + '.meta', JSON.stringify({
|
|
684
|
+
skill_name: skillName,
|
|
685
|
+
description: '',
|
|
686
|
+
capability_name: capabilityName,
|
|
687
|
+
version: dlData.version || data.latest_version,
|
|
688
|
+
publisher_name: pub.name,
|
|
689
|
+
publisher_id: pub.id,
|
|
690
|
+
}), { mode: 0o600 });
|
|
691
|
+
console.error(`[sync] Updated "${skillName}" to v${dlData.version || data.latest_version}`);
|
|
692
|
+
return true;
|
|
693
|
+
}
|
|
694
|
+
catch {
|
|
695
|
+
return false; // sync failure is non-fatal — use existing vault
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* Background sync for all skills across all publishers.
|
|
700
|
+
* Discovers new skills the customer has been granted since last sync.
|
|
701
|
+
* Runs async — doesn't block the load operation.
|
|
702
|
+
*/
|
|
703
|
+
async function backgroundSyncAll(config) {
|
|
704
|
+
try {
|
|
705
|
+
await syncSkills();
|
|
706
|
+
await installSkillStubs();
|
|
707
|
+
}
|
|
708
|
+
catch { } // non-fatal
|
|
709
|
+
}
|
|
645
710
|
/**
|
|
646
711
|
* Load (decrypt) a skill and output to stdout.
|
|
712
|
+
* Syncs the requested skill first to ensure latest version.
|
|
713
|
+
* Triggers background sync for all other skills.
|
|
647
714
|
* Status messages go to stderr so they don't pollute the skill content.
|
|
648
715
|
*/
|
|
649
716
|
async function loadSkill(skillName) {
|
|
@@ -656,11 +723,27 @@ async function loadSkill(skillName) {
|
|
|
656
723
|
console.error('Error: Not configured. Run: npx skillvault --invite YOUR_CODE');
|
|
657
724
|
process.exit(1);
|
|
658
725
|
}
|
|
659
|
-
|
|
726
|
+
// Pre-load sync: ensure we have the latest vault for this skill
|
|
727
|
+
let resolved = resolveSkillPublisher(skillName, config);
|
|
728
|
+
if (resolved) {
|
|
729
|
+
await syncSingleSkill(skillName, resolved.publisher, config);
|
|
730
|
+
// Re-resolve in case the vault was just downloaded
|
|
731
|
+
resolved = resolveSkillPublisher(skillName, config);
|
|
732
|
+
}
|
|
733
|
+
else {
|
|
734
|
+
// Skill not found locally — try a full sync first (may be a newly granted skill)
|
|
735
|
+
console.error(`[sync] Skill "${skillName}" not found locally, syncing...`);
|
|
736
|
+
await syncSkills();
|
|
737
|
+
await installSkillStubs();
|
|
738
|
+
resolved = resolveSkillPublisher(skillName, config);
|
|
739
|
+
}
|
|
660
740
|
if (!resolved) {
|
|
661
|
-
console.error(`Error: Vault not found for "${skillName}"
|
|
741
|
+
console.error(`Error: Vault not found for "${skillName}" after sync.`);
|
|
742
|
+
console.error('You may not have access to this skill. Check with your skill provider.');
|
|
662
743
|
process.exit(1);
|
|
663
744
|
}
|
|
745
|
+
// Kick off background sync for all other skills (non-blocking)
|
|
746
|
+
backgroundSyncAll(config).catch(() => { });
|
|
664
747
|
const licenseeId = config.customer_email || 'unknown';
|
|
665
748
|
// Fetch CEK — validates license on every load
|
|
666
749
|
let cek;
|