skillvault 0.9.1 → 0.9.3

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 (3) hide show
  1. package/dist/cli.js +114 -110
  2. package/package.json +2 -2
  3. package/dist/cli.d.ts +0 -20
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, createHmac, createPublicKey, diffieHellman, hkdfSync, generateKeyPairSync, } from 'node:crypto';
23
- const VERSION = '0.9.1';
23
+ const VERSION = '0.9.3';
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');
@@ -246,6 +246,17 @@ function cleanupMCPConfig() {
246
246
  *
247
247
  * Also removes legacy PostToolCall/PreToolCall hooks from older versions.
248
248
  */
249
+ /** Check if a hook entry (old or new format) contains a skillvault command. */
250
+ function hasSkillvaultCommand(entry) {
251
+ // Old format: { command: "npx skillvault ..." }
252
+ if (entry.command?.includes('skillvault'))
253
+ return true;
254
+ // New format: { hooks: [{ type: "command", command: "npx skillvault ..." }] }
255
+ if (Array.isArray(entry.hooks)) {
256
+ return entry.hooks.some((h) => h.command?.includes('skillvault'));
257
+ }
258
+ return false;
259
+ }
249
260
  function configureSessionHook() {
250
261
  const settingsPath = join(HOME, '.claude', 'settings.json');
251
262
  try {
@@ -259,50 +270,52 @@ function configureSessionHook() {
259
270
  settings.hooks.SessionStart = [];
260
271
  if (!settings.hooks.SessionEnd)
261
272
  settings.hooks.SessionEnd = [];
262
- // ── SessionStart: auto-sync (permanent) ──
263
- const hasStartHook = settings.hooks.SessionStart.some((group) => group.matcher === 'startup' &&
264
- group.hooks?.some((h) => h.command?.includes('skillvault')));
265
- if (!hasStartHook) {
266
- settings.hooks.SessionStart.push({
267
- matcher: 'startup',
268
- hooks: [{
269
- type: 'command',
270
- command: `npx skillvault@${VERSION} --sync`,
271
- timeout: 30,
272
- }],
273
- });
274
- }
275
- // ── SessionEnd: cleanup (permanent) ──
276
- const hasEndHook = settings.hooks.SessionEnd.some((h) => h.command?.includes('skillvault') && h.command?.includes('session-cleanup'));
277
- if (!hasEndHook) {
278
- settings.hooks.SessionEnd.push({
279
- command: 'npx skillvault --session-cleanup',
280
- });
281
- }
282
- // ── Remove legacy PostToolCall + PreToolCall hooks ──
273
+ // ── SessionStart: auto-sync (permanent, unpinned so it always gets latest) ──
274
+ // Remove all existing skillvault SessionStart hooks and re-add with current format
275
+ settings.hooks.SessionStart = settings.hooks.SessionStart.filter((h) => !hasSkillvaultCommand(h));
276
+ settings.hooks.SessionStart.push({
277
+ matcher: 'startup',
278
+ hooks: [{
279
+ type: 'command',
280
+ command: 'npx -y skillvault --sync',
281
+ timeout: 30,
282
+ }],
283
+ });
284
+ // ── SessionEnd: cleanup (permanent, pinned to current version) ──
285
+ // Remove all existing skillvault SessionEnd hooks and re-add with current version
286
+ settings.hooks.SessionEnd = settings.hooks.SessionEnd.filter((h) => !hasSkillvaultCommand(h));
287
+ settings.hooks.SessionEnd.push({
288
+ hooks: [{
289
+ type: 'command',
290
+ command: `npx -y skillvault@${VERSION} --session-cleanup`,
291
+ }],
292
+ });
293
+ // ── Remove legacy PostToolCall + PreToolCall hooks (invalid event names) ──
283
294
  let removedLegacy = false;
284
- if (Array.isArray(settings.hooks.PostToolCall)) {
285
- const before = settings.hooks.PostToolCall.length;
286
- settings.hooks.PostToolCall = settings.hooks.PostToolCall.filter((h) => !h.command?.includes('skillvault'));
287
- if (settings.hooks.PostToolCall.length < before)
288
- removedLegacy = true;
289
- if (settings.hooks.PostToolCall.length === 0)
290
- delete settings.hooks.PostToolCall;
295
+ for (const legacyKey of ['PostToolCall', 'PreToolCall']) {
296
+ if (Array.isArray(settings.hooks[legacyKey])) {
297
+ const before = settings.hooks[legacyKey].length;
298
+ settings.hooks[legacyKey] = settings.hooks[legacyKey].filter((h) => !hasSkillvaultCommand(h));
299
+ if (settings.hooks[legacyKey].length < before)
300
+ removedLegacy = true;
301
+ if (settings.hooks[legacyKey].length === 0)
302
+ delete settings.hooks[legacyKey];
303
+ }
291
304
  }
292
- if (Array.isArray(settings.hooks.PreToolCall)) {
293
- const before = settings.hooks.PreToolCall.length;
294
- settings.hooks.PreToolCall = settings.hooks.PreToolCall.filter((h) => !h.command?.includes('skillvault'));
295
- if (settings.hooks.PreToolCall.length < before)
296
- removedLegacy = true;
297
- if (settings.hooks.PreToolCall.length === 0)
298
- delete settings.hooks.PreToolCall;
305
+ // ── Remove old-format hooks (command on entry instead of hooks array) ──
306
+ for (const key of ['SessionEnd', 'SessionStart', 'UserPromptSubmit', 'PostToolUse', 'Stop']) {
307
+ if (Array.isArray(settings.hooks[key])) {
308
+ const before = settings.hooks[key].length;
309
+ settings.hooks[key] = settings.hooks[key].filter((h) => !(h.command?.includes('skillvault') && !h.hooks));
310
+ if (settings.hooks[key].length < before)
311
+ removedLegacy = true;
312
+ if (settings.hooks[key].length === 0)
313
+ delete settings.hooks[key];
314
+ }
299
315
  }
300
316
  mkdirSync(join(HOME, '.claude'), { recursive: true });
301
317
  writeFileSync(settingsPath, JSON.stringify(settings, null, 2), { mode: 0o600 });
302
- if (!hasStartHook)
303
- console.error(' ✅ Auto-sync hook installed');
304
- if (!hasEndHook)
305
- console.error(' ✅ Session cleanup hook installed');
318
+ console.error(' ✅ Hooks updated');
306
319
  if (removedLegacy)
307
320
  console.error(' 🧹 Removed legacy monitoring hooks (now session-scoped)');
308
321
  }
@@ -341,34 +354,45 @@ function injectMonitoringHooks(skillName) {
341
354
  };
342
355
  mkdirSync(CONFIG_DIR, { recursive: true });
343
356
  writeFileSync(activeSessionPath, JSON.stringify(session, null, 2), { mode: 0o600 });
344
- // ── UserPromptSubmit hook ──
345
- if (!Array.isArray(settings.hooks.UserPromptSubmit))
346
- settings.hooks.UserPromptSubmit = [];
347
- const hasPromptHook = settings.hooks.UserPromptSubmit.some((h) => h.command?.includes('skillvault') && h.command?.includes('session-keepalive'));
348
- if (!hasPromptHook) {
349
- settings.hooks.UserPromptSubmit.push({
350
- command: 'npx skillvault --session-keepalive --event-type prompt-submit',
351
- });
357
+ // ── Remove old-format monitoring hooks (command on entry, no hooks array) ──
358
+ for (const key of ['UserPromptSubmit', 'PostToolUse', 'Stop']) {
359
+ if (Array.isArray(settings.hooks[key])) {
360
+ settings.hooks[key] = settings.hooks[key].filter((h) => !(h.command?.includes('skillvault') && !h.hooks));
361
+ }
352
362
  }
353
- // ── PostToolUse hook ──
354
- if (!Array.isArray(settings.hooks.PostToolUse))
355
- settings.hooks.PostToolUse = [];
356
- const hasToolHook = settings.hooks.PostToolUse.some((h) => h.command?.includes('skillvault') && h.command?.includes('session-keepalive'));
357
- if (!hasToolHook) {
358
- settings.hooks.PostToolUse.push({
359
- matcher: 'Write|Bash|Edit',
360
- command: 'npx skillvault --session-keepalive --event-type tool-use',
361
- });
363
+ // ── Remove legacy PostToolCall/PreToolCall keys ──
364
+ for (const legacyKey of ['PostToolCall', 'PreToolCall']) {
365
+ if (Array.isArray(settings.hooks[legacyKey])) {
366
+ settings.hooks[legacyKey] = settings.hooks[legacyKey].filter((h) => !hasSkillvaultCommand(h));
367
+ if (settings.hooks[legacyKey].length === 0)
368
+ delete settings.hooks[legacyKey];
369
+ }
362
370
  }
363
- // ── Stop hook ──
364
- if (!Array.isArray(settings.hooks.Stop))
365
- settings.hooks.Stop = [];
366
- const hasStopHook = settings.hooks.Stop.some((h) => h.command?.includes('skillvault') && h.command?.includes('session-keepalive'));
367
- if (!hasStopHook) {
368
- settings.hooks.Stop.push({
369
- command: 'npx skillvault --session-keepalive --event-type stop',
370
- });
371
+ // ── Monitoring hooks: remove existing and re-add with current version ──
372
+ for (const key of ['UserPromptSubmit', 'PostToolUse', 'Stop']) {
373
+ if (!Array.isArray(settings.hooks[key]))
374
+ settings.hooks[key] = [];
375
+ settings.hooks[key] = settings.hooks[key].filter((h) => !hasSkillvaultCommand(h));
371
376
  }
377
+ settings.hooks.UserPromptSubmit.push({
378
+ hooks: [{
379
+ type: 'command',
380
+ command: `npx -y skillvault@${VERSION} --session-keepalive --event-type prompt-submit`,
381
+ }],
382
+ });
383
+ settings.hooks.PostToolUse.push({
384
+ matcher: 'Write|Bash|Edit',
385
+ hooks: [{
386
+ type: 'command',
387
+ command: `npx -y skillvault@${VERSION} --session-keepalive --event-type tool-use`,
388
+ }],
389
+ });
390
+ settings.hooks.Stop.push({
391
+ hooks: [{
392
+ type: 'command',
393
+ command: `npx -y skillvault@${VERSION} --session-keepalive --event-type stop`,
394
+ }],
395
+ });
372
396
  writeFileSync(settingsPath, JSON.stringify(settings, null, 2), { mode: 0o600 });
373
397
  }
374
398
  catch {
@@ -1004,10 +1028,10 @@ function watermarkLayer5(content, id, skillName) {
1004
1028
  const timestamp = new Date().toISOString();
1005
1029
  const secret = 'skillvault-heartbeat-v1'; // shared secret for HMAC
1006
1030
  // HMAC-based 5-digit values
1007
- const hmac1 = createHmac('sha256', secret);
1031
+ const hmac1 = createHmac('sha256', secret); // nosemgrep: hardcoded-hmac-key — intentional forensic fingerprint key, not a secret
1008
1032
  hmac1.update(`${id}:${timestamp}:hb1`);
1009
1033
  const hb1 = (parseInt(hmac1.digest('hex').slice(0, 8), 16) % 100000).toString().padStart(5, '0');
1010
- const hmac2 = createHmac('sha256', secret);
1034
+ const hmac2 = createHmac('sha256', secret); // nosemgrep: hardcoded-hmac-key — intentional forensic fingerprint key, not a secret
1011
1035
  hmac2.update(`${id}:${timestamp}:hb2`);
1012
1036
  const hb2 = (parseInt(hmac2.digest('hex').slice(0, 8), 16) % 100000).toString().padStart(5, '0');
1013
1037
  // Detect language from code blocks
@@ -1053,7 +1077,7 @@ function watermarkLayer5(content, id, skillName) {
1053
1077
  // Context templates (selected by HMAC of skill name)
1054
1078
  const ctx1Templates = ['Initialize workspace:', 'Allow environment to settle:', 'Brief initialization delay:', 'Pre-flight pause:'];
1055
1079
  const ctx2Templates = ['Brief pause between operations:', 'Allow buffers to flush:', 'Cooldown before next step:', 'Rate limit compliance delay:'];
1056
- const ctxHmac = createHmac('sha256', secret);
1080
+ const ctxHmac = createHmac('sha256', secret); // nosemgrep: hardcoded-hmac-key — intentional context template selector
1057
1081
  ctxHmac.update(skillName);
1058
1082
  const ctxHash = ctxHmac.digest();
1059
1083
  const ctx1 = ctx1Templates[ctxHash[0] % ctx1Templates.length];
@@ -1714,49 +1738,27 @@ function handleSessionCleanup() {
1714
1738
  const settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
1715
1739
  if (settings.hooks) {
1716
1740
  let modified = false;
1717
- // Remove UserPromptSubmit skillvault hooks
1718
- if (Array.isArray(settings.hooks.UserPromptSubmit)) {
1719
- const before = settings.hooks.UserPromptSubmit.length;
1720
- settings.hooks.UserPromptSubmit = settings.hooks.UserPromptSubmit.filter((h) => !h.command?.includes('skillvault'));
1721
- if (settings.hooks.UserPromptSubmit.length < before)
1722
- modified = true;
1723
- if (settings.hooks.UserPromptSubmit.length === 0)
1724
- delete settings.hooks.UserPromptSubmit;
1725
- }
1726
- // Remove PostToolUse skillvault session-keepalive hooks
1727
- if (Array.isArray(settings.hooks.PostToolUse)) {
1728
- const before = settings.hooks.PostToolUse.length;
1729
- settings.hooks.PostToolUse = settings.hooks.PostToolUse.filter((h) => !h.command?.includes('skillvault') || !h.command?.includes('session-keepalive'));
1730
- if (settings.hooks.PostToolUse.length < before)
1731
- modified = true;
1732
- if (settings.hooks.PostToolUse.length === 0)
1733
- delete settings.hooks.PostToolUse;
1734
- }
1735
- // Remove Stop skillvault session-keepalive hooks
1736
- if (Array.isArray(settings.hooks.Stop)) {
1737
- const before = settings.hooks.Stop.length;
1738
- settings.hooks.Stop = settings.hooks.Stop.filter((h) => !h.command?.includes('skillvault') || !h.command?.includes('session-keepalive'));
1739
- if (settings.hooks.Stop.length < before)
1740
- modified = true;
1741
- if (settings.hooks.Stop.length === 0)
1742
- delete settings.hooks.Stop;
1743
- }
1744
- // Also remove legacy PostToolCall / PreToolCall hooks
1745
- if (Array.isArray(settings.hooks.PostToolCall)) {
1746
- const before = settings.hooks.PostToolCall.length;
1747
- settings.hooks.PostToolCall = settings.hooks.PostToolCall.filter((h) => !h.command?.includes('skillvault'));
1748
- if (settings.hooks.PostToolCall.length < before)
1749
- modified = true;
1750
- if (settings.hooks.PostToolCall.length === 0)
1751
- delete settings.hooks.PostToolCall;
1741
+ // Remove session-scoped monitoring hooks (both old and new format)
1742
+ for (const key of ['UserPromptSubmit', 'PostToolUse', 'Stop']) {
1743
+ if (Array.isArray(settings.hooks[key])) {
1744
+ const before = settings.hooks[key].length;
1745
+ settings.hooks[key] = settings.hooks[key].filter((h) => !hasSkillvaultCommand(h));
1746
+ if (settings.hooks[key].length < before)
1747
+ modified = true;
1748
+ if (settings.hooks[key].length === 0)
1749
+ delete settings.hooks[key];
1750
+ }
1752
1751
  }
1753
- if (Array.isArray(settings.hooks.PreToolCall)) {
1754
- const before = settings.hooks.PreToolCall.length;
1755
- settings.hooks.PreToolCall = settings.hooks.PreToolCall.filter((h) => !h.command?.includes('skillvault'));
1756
- if (settings.hooks.PreToolCall.length < before)
1757
- modified = true;
1758
- if (settings.hooks.PreToolCall.length === 0)
1759
- delete settings.hooks.PreToolCall;
1752
+ // Remove legacy PostToolCall / PreToolCall keys (invalid event names)
1753
+ for (const legacyKey of ['PostToolCall', 'PreToolCall']) {
1754
+ if (Array.isArray(settings.hooks[legacyKey])) {
1755
+ const before = settings.hooks[legacyKey].length;
1756
+ settings.hooks[legacyKey] = settings.hooks[legacyKey].filter((h) => !hasSkillvaultCommand(h));
1757
+ if (settings.hooks[legacyKey].length < before)
1758
+ modified = true;
1759
+ if (settings.hooks[legacyKey].length === 0)
1760
+ delete settings.hooks[legacyKey];
1761
+ }
1760
1762
  }
1761
1763
  if (modified) {
1762
1764
  writeFileSync(settingsPath, JSON.stringify(settings, null, 2), { mode: 0o600 });
@@ -1850,6 +1852,8 @@ async function main() {
1850
1852
  process.exit(1);
1851
1853
  }
1852
1854
  console.error('🔐 SkillVault Sync\n');
1855
+ // Re-install hooks with current version (auto-upgrades pinned commands)
1856
+ configureSessionHook();
1853
1857
  await syncSkills();
1854
1858
  const result = await installSkillStubs();
1855
1859
  console.error(`\n ✅ ${result.installed} installed, ${result.skipped} up to date\n`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillvault",
3
- "version": "0.9.1",
3
+ "version": "0.9.3",
4
4
  "description": "SkillVault — secure skill distribution for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {
@@ -11,7 +11,7 @@
11
11
  "start": "node dist/cli.js"
12
12
  },
13
13
  "files": [
14
- "dist"
14
+ "dist/**/*.js"
15
15
  ],
16
16
  "keywords": [
17
17
  "skillvault",
package/dist/cli.d.ts DELETED
@@ -1,20 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * SkillVault — Secure skill distribution for Claude Code.
4
- *
5
- * Usage:
6
- * npx skillvault --invite CODE # Setup: redeem invite + sync + install
7
- * npx skillvault --load SKILL # Decrypt a skill (used by Claude Code)
8
- * npx skillvault --status # Show publishers & skills
9
- * npx skillvault --refresh # Re-authenticate tokens + sync
10
- * npx skillvault --sync # Sync vaults + update stubs
11
- *
12
- * How it works:
13
- * 1. Customer runs --invite once → vaults downloaded, stubs installed
14
- * 2. Claude Code discovers stub SKILL.md files in ~/.claude/skills/
15
- * 3. When a skill is triggered, Claude runs: npx skillvault --load <name>
16
- * 4. The CLI decrypts the vault (license checked) and outputs to stdout
17
- * 5. Claude reads the output and follows the instructions
18
- * 6. Decrypted content is never written to disk
19
- */
20
- export {};