skillvault 0.5.0 → 0.5.2
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 -5
- 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.
|
|
23
|
+
const VERSION = '0.5.2';
|
|
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');
|
|
@@ -151,6 +151,8 @@ async function setup(code) {
|
|
|
151
151
|
mkdirSync(join(VAULT_DIR, data.publisher_id), { recursive: true });
|
|
152
152
|
// Clean up legacy MCP config if present
|
|
153
153
|
cleanupMCPConfig();
|
|
154
|
+
// Install session hook for auto-sync
|
|
155
|
+
configureSessionHook();
|
|
154
156
|
// Sync vaults and install stubs
|
|
155
157
|
console.error('');
|
|
156
158
|
console.error(' Syncing skills...');
|
|
@@ -161,6 +163,7 @@ async function setup(code) {
|
|
|
161
163
|
console.error(` ✅ ${installResult.installed} skill${installResult.installed !== 1 ? 's' : ''} installed to ~/.claude/skills/`);
|
|
162
164
|
}
|
|
163
165
|
console.error(' ✅ Setup complete! Restart Claude Code to use your skills.');
|
|
166
|
+
console.error(' Skills will auto-sync at the start of each Claude Code session.');
|
|
164
167
|
console.error('');
|
|
165
168
|
}
|
|
166
169
|
/** Remove legacy MCP server config from ~/.claude/.mcp.json */
|
|
@@ -178,6 +181,43 @@ function cleanupMCPConfig() {
|
|
|
178
181
|
}
|
|
179
182
|
catch { }
|
|
180
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* Install a SessionStart hook in Claude Code settings so skills auto-sync
|
|
186
|
+
* at the start of each session. This discovers new skills from all
|
|
187
|
+
* existing publishers without the customer doing anything.
|
|
188
|
+
*/
|
|
189
|
+
function configureSessionHook() {
|
|
190
|
+
const settingsPath = join(HOME, '.claude', 'settings.json');
|
|
191
|
+
try {
|
|
192
|
+
let settings = {};
|
|
193
|
+
if (existsSync(settingsPath)) {
|
|
194
|
+
settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
|
|
195
|
+
}
|
|
196
|
+
if (!settings.hooks)
|
|
197
|
+
settings.hooks = {};
|
|
198
|
+
if (!settings.hooks.SessionStart)
|
|
199
|
+
settings.hooks.SessionStart = [];
|
|
200
|
+
// Check if we already have a skillvault sync hook
|
|
201
|
+
const hasHook = settings.hooks.SessionStart.some((group) => group.matcher === 'startup' &&
|
|
202
|
+
group.hooks?.some((h) => h.command?.includes('skillvault')));
|
|
203
|
+
if (!hasHook) {
|
|
204
|
+
settings.hooks.SessionStart.push({
|
|
205
|
+
matcher: 'startup',
|
|
206
|
+
hooks: [{
|
|
207
|
+
type: 'command',
|
|
208
|
+
command: `npx skillvault@${VERSION} --sync`,
|
|
209
|
+
timeout: 30,
|
|
210
|
+
}],
|
|
211
|
+
});
|
|
212
|
+
mkdirSync(join(HOME, '.claude'), { recursive: true });
|
|
213
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2), { mode: 0o600 });
|
|
214
|
+
console.error(' ✅ Auto-sync hook installed');
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
console.error(' ⚠️ Could not install auto-sync hook — run npx skillvault --sync manually');
|
|
219
|
+
}
|
|
220
|
+
}
|
|
181
221
|
async function showStatus() {
|
|
182
222
|
const config = loadConfig();
|
|
183
223
|
if (!config) {
|
|
@@ -439,14 +479,60 @@ async function installSkillStubs() {
|
|
|
439
479
|
meta = JSON.parse(readFileSync(metaPath, 'utf8'));
|
|
440
480
|
}
|
|
441
481
|
catch { }
|
|
482
|
+
// Decrypt vault to extract frontmatter (name, description, triggers)
|
|
483
|
+
// The frontmatter is the "public storefront" — body stays encrypted
|
|
484
|
+
let frontmatter = '';
|
|
485
|
+
let frontmatterFields = {};
|
|
486
|
+
try {
|
|
487
|
+
const cek = await fetchCEK(skillName, pub.token);
|
|
488
|
+
const vaultData = readFileSync(vaultPath);
|
|
489
|
+
const vault = decryptVault(vaultData, cek);
|
|
490
|
+
cek.fill(0);
|
|
491
|
+
const skillMd = vault.files.find(f => f.path === 'SKILL.md');
|
|
492
|
+
if (skillMd) {
|
|
493
|
+
const fmMatch = skillMd.content.match(/^---\n([\s\S]*?)\n---/);
|
|
494
|
+
if (fmMatch) {
|
|
495
|
+
frontmatter = fmMatch[1];
|
|
496
|
+
// Parse YAML-like frontmatter fields
|
|
497
|
+
for (const line of frontmatter.split('\n')) {
|
|
498
|
+
const kv = line.match(/^(\S+):\s*(.+)$/);
|
|
499
|
+
if (kv)
|
|
500
|
+
frontmatterFields[kv[1]] = kv[2].replace(/^["']|["']$/g, '');
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
catch {
|
|
506
|
+
// If decrypt fails during stub install (e.g. offline), fall back to meta
|
|
507
|
+
}
|
|
508
|
+
const stubName = frontmatterFields['name'] || skillName;
|
|
509
|
+
const stubDescription = frontmatterFields['description'] || meta.description || '';
|
|
510
|
+
// Build frontmatter for stub — copy all fields except body-related ones
|
|
511
|
+
let stubFrontmatter = `name: ${stubName}\n`;
|
|
512
|
+
stubFrontmatter += `description: "${stubDescription.replace(/"/g, '\\"')}"\n`;
|
|
513
|
+
// Preserve publisher's allowed-tools, trigger config, etc. but always include our load tool
|
|
514
|
+
const publisherAllowedTools = frontmatterFields['allowed-tools'] || '';
|
|
515
|
+
const loadTool = `"Bash(npx skillvault@${VERSION} --load *)"`;
|
|
516
|
+
if (publisherAllowedTools && !publisherAllowedTools.includes('skillvault')) {
|
|
517
|
+
// Merge publisher's allowed-tools with ours
|
|
518
|
+
const merged = publisherAllowedTools.replace(/\]$/, `, ${loadTool}]`);
|
|
519
|
+
stubFrontmatter += `allowed-tools: ${merged}\n`;
|
|
520
|
+
}
|
|
521
|
+
else {
|
|
522
|
+
stubFrontmatter += `allowed-tools: [${loadTool}]\n`;
|
|
523
|
+
}
|
|
524
|
+
// Copy through other frontmatter fields the publisher set (for Claude triggering)
|
|
525
|
+
for (const [key, value] of Object.entries(frontmatterFields)) {
|
|
526
|
+
if (!['name', 'description', 'allowed-tools'].includes(key)) {
|
|
527
|
+
stubFrontmatter += `${key}: ${value}\n`;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
442
530
|
mkdirSync(skillDir, { recursive: true });
|
|
443
531
|
const stub = `---
|
|
444
|
-
|
|
445
|
-
description: "${(meta.description || '').replace(/"/g, '\\"')}"
|
|
446
|
-
allowed-tools: ["Bash(npx skillvault@${VERSION} --load *)"]
|
|
532
|
+
${stubFrontmatter.trimEnd()}
|
|
447
533
|
---
|
|
448
534
|
|
|
449
|
-
# ${
|
|
535
|
+
# ${stubName}
|
|
450
536
|
|
|
451
537
|
This is an encrypted SkillVault skill from **${meta.publisher_name || pub.name}**.
|
|
452
538
|
|