@vruum/skills 0.6.44 → 0.6.45

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vruum",
3
- "version": "0.6.44",
3
+ "version": "0.6.45",
4
4
  "description": "Vruum AI skills + remote MCP server for B2B GTM teams. Slash commands for outreach triage, engagement triage, pipeline filling, prospect enrichment, and reply diagnosis, paired with the full Vruum MCP tool surface over OAuth 2.1.",
5
5
  "author": {
6
6
  "name": "Vruum AI",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vruum",
3
- "version": "0.6.44",
3
+ "version": "0.6.45",
4
4
  "description": "Vruum AI skills + remote MCP server for B2B GTM teams. Skills for outreach triage, engagement triage, pipeline filling, prospect enrichment, and reply diagnosis, paired with the full Vruum MCP tool surface over OAuth 2.1.",
5
5
  "author": {
6
6
  "name": "Vruum AI",
package/install.js CHANGED
@@ -196,6 +196,45 @@ function syncVruumRoot({ dryRun }) {
196
196
  return rows;
197
197
  }
198
198
 
199
+ function pathApiFor(platform) {
200
+ return platform === 'win32' ? path.win32 : path.posix;
201
+ }
202
+
203
+ function normalizePathForComparison(value, platform = process.platform) {
204
+ const pathApi = pathApiFor(platform);
205
+ let normalized = pathApi.resolve(value);
206
+ if (platform === 'win32') {
207
+ // Junction readlink results may use a \\?\ namespace prefix and different
208
+ // casing from os.homedir(). Normalize both forms for identity/ownership.
209
+ normalized = pathApi.toNamespacedPath(normalized).toLowerCase();
210
+ }
211
+ const root = pathApi.parse(normalized).root;
212
+ while (normalized.length > root.length && normalized.endsWith(pathApi.sep)) {
213
+ normalized = normalized.slice(0, -1);
214
+ }
215
+ return normalized;
216
+ }
217
+
218
+ function pathsEqual(left, right, platform = process.platform) {
219
+ return normalizePathForComparison(left, platform)
220
+ === normalizePathForComparison(right, platform);
221
+ }
222
+
223
+ function pathIsWithin(candidate, root, platform = process.platform) {
224
+ const pathApi = pathApiFor(platform);
225
+ const normalizedCandidate = normalizePathForComparison(candidate, platform);
226
+ const normalizedRoot = normalizePathForComparison(root, platform);
227
+ return normalizedCandidate === normalizedRoot
228
+ || normalizedCandidate.startsWith(normalizedRoot + pathApi.sep);
229
+ }
230
+
231
+ function resolveLinkTarget(dst, linkTarget, platform = process.platform) {
232
+ const pathApi = pathApiFor(platform);
233
+ return pathApi.isAbsolute(linkTarget)
234
+ ? pathApi.resolve(linkTarget)
235
+ : pathApi.resolve(pathApi.dirname(dst), linkTarget);
236
+ }
237
+
199
238
  // A symlink is "ours" if its target resolves into ~/.vruum/skills/. Resolve
200
239
  // textually from the readlink string (relative links against the link's own
201
240
  // dir) — never realpathSync, which THROWS on a dangling link, i.e. exactly the
@@ -204,21 +243,14 @@ function syncVruumRoot({ dryRun }) {
204
243
  // ~/.vruum/skills-operator, so a bare startsWith would wrongly claim the
205
244
  // operator installer's links. Equality covers a link straight at the dir.
206
245
  function isOurLink(dst, linkTarget) {
207
- const resolved = path.isAbsolute(linkTarget)
208
- ? path.resolve(linkTarget)
209
- : path.resolve(path.dirname(dst), linkTarget);
210
- return resolved === VRUUM_SKILLS || resolved.startsWith(VRUUM_SKILLS + path.sep);
246
+ return pathIsWithin(resolveLinkTarget(dst, linkTarget), VRUUM_SKILLS);
211
247
  }
212
248
 
213
249
  // Operator skills are a strict superset with multi-company scoping. When both
214
250
  // installers are present, operator links win for overlapping names regardless
215
251
  // of install order. Public-only names still link normally.
216
252
  function isOperatorLink(dst, linkTarget) {
217
- const resolved = path.isAbsolute(linkTarget)
218
- ? path.resolve(linkTarget)
219
- : path.resolve(path.dirname(dst), linkTarget);
220
- return resolved === VRUUM_OPERATOR_ROOT
221
- || resolved.startsWith(VRUUM_OPERATOR_ROOT + path.sep);
253
+ return pathIsWithin(resolveLinkTarget(dst, linkTarget), VRUUM_OPERATOR_ROOT);
222
254
  }
223
255
 
224
256
  function hasSkillFile(dir) {
@@ -297,14 +329,12 @@ function linkSkill({ name, srcAbs, target, dryRun }) {
297
329
  if (err.code !== 'ENOENT') throw err;
298
330
  }
299
331
 
300
- if (existing?.kind === 'symlink' && existing.target === srcAbs) {
301
- return { name, target, action: 'already-linked' };
302
- }
303
332
  const resolvedExistingTarget = existing?.kind === 'symlink'
304
- ? (path.isAbsolute(existing.target)
305
- ? path.resolve(existing.target)
306
- : path.resolve(path.dirname(dst), existing.target))
333
+ ? resolveLinkTarget(dst, existing.target)
307
334
  : null;
335
+ if (resolvedExistingTarget && pathsEqual(resolvedExistingTarget, srcAbs)) {
336
+ return { name, target, action: 'already-linked' };
337
+ }
308
338
  const operatorSkillIsUsable = resolvedExistingTarget
309
339
  && isOperatorLink(dst, existing.target)
310
340
  && hasSkillFile(resolvedExistingTarget);
@@ -330,7 +360,9 @@ function linkSkill({ name, srcAbs, target, dryRun }) {
330
360
  if (existing?.kind === 'symlink') {
331
361
  fs.unlinkSync(dst);
332
362
  }
333
- fs.symlinkSync(srcAbs, dst, 'dir');
363
+ // Windows directory symlinks require Developer Mode or elevation. Directory
364
+ // junctions work for this absolute, same-machine target without either.
365
+ fs.symlinkSync(srcAbs, dst, process.platform === 'win32' ? 'junction' : 'dir');
334
366
  return { name, target, action: existing ? 'relinked' : 'linked' };
335
367
  }
336
368
 
@@ -545,7 +577,7 @@ function commandList({ targets: extraTargets }) {
545
577
  const lstat = fs.lstatSync(dst);
546
578
  if (lstat.isSymbolicLink()) {
547
579
  const linkTarget = fs.readlinkSync(dst);
548
- const match = linkTarget === srcAbs;
580
+ const match = pathsEqual(resolveLinkTarget(dst, linkTarget), srcAbs);
549
581
  console.log(` ${match ? 'installed' : 'other-link'.padEnd(10)} ${skillName}${match ? '' : ` -> ${linkTarget}`}`);
550
582
  } else {
551
583
  console.log(` present ${skillName} (not our symlink)`);
@@ -598,6 +630,8 @@ if (require.main === module) main();
598
630
  module.exports = {
599
631
  parseArgs,
600
632
  listAvailableSkills,
633
+ pathsEqual,
634
+ pathIsWithin,
601
635
  linkSkill,
602
636
  isOurLink,
603
637
  isOperatorLink,
package/package.json CHANGED
@@ -1,12 +1,11 @@
1
1
  {
2
2
  "name": "@vruum/skills",
3
- "version": "0.6.44",
3
+ "version": "0.6.45",
4
4
  "description": "Vruum AI skills for Claude Code, Claude Desktop, Codex CLI, and any AI assistant with a skill directory. Slash commands for outreach triage, engagement triage, pipeline filling, prospect enrichment, and reply diagnosis. Pairs with the Vruum MCP server at https://api.vruum.ai/mcp.",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
8
- "url": "https://github.com/vruum-gtm/vruum_ai.git",
9
- "directory": ".agents"
8
+ "url": "git+https://github.com/vruum-gtm/skills.git"
10
9
  },
11
10
  "homepage": "https://vruum.ai",
12
11
  "bugs": {
@@ -42,5 +41,5 @@
42
41
  "outreach",
43
42
  "gtm"
44
43
  ],
45
- "contentHash": "dd7363b37525cb10ffce653db525fa540f3877c2fa6e72727e26b6c56e041df3"
44
+ "contentHash": "843a119196d95663bc210d5543eb53a5f48a2a2780d425afce7c713577d4b1ac"
46
45
  }