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.
Files changed (2) hide show
  1. package/dist/cli.js +95 -12
  2. 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.5.2';
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
- try {
239
- const token = config.customer_token || (config.publishers.length > 0 ? config.publishers[0].token : null);
240
- if (token) {
241
- const res = await fetch(`${config.api_url}/customer/skills`, {
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
- skills = data.skills || [];
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(' ⚠️ Session expired. Run: npx skillvault --refresh\n');
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)}&current_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
- const resolved = resolveSkillPublisher(skillName, config);
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}". Run: npx skillvault --sync`);
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillvault",
3
- "version": "0.5.2",
3
+ "version": "0.7.0",
4
4
  "description": "SkillVault — secure skill distribution for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {