@vruum/skills 0.2.0 → 0.4.1

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/README.md CHANGED
@@ -1,99 +1,49 @@
1
- # Vruum AI — skills + MCP for your AI assistant
1
+ # @vruum/skills
2
2
 
3
- **The easy way to connect ChatGPT, Claude, Cursor, and friends to Vruum.** Ships skills and a remote MCP server URL so your AI assistant can search people, start outreach, diagnose replies, and run your whole sales pipeline through natural conversation.
3
+ Vruum AI skills for Claude Code, Codex CLI, and any AI assistant with a skill directory.
4
4
 
5
- Paired with [vruum.ai](https://vruum.ai).
6
-
7
- ---
5
+ Pairs with the Vruum MCP server at [https://api.vruum.ai/mcp](https://api.vruum.ai/mcp). For MCP-native clients (Claude Code, Claude Desktop, Cursor, VS Code Copilot, Cline), connecting to the MCP URL surfaces these skills as native slash commands. This npm package is for assistants that don't yet support MCP prompts (Codex CLI, ChatGPT, Windsurf).
8
6
 
9
7
  ## Install
10
8
 
11
- Pick the path that matches your assistant.
12
-
13
- ### Claude Code (recommended — one command)
14
-
15
9
  ```bash
16
- /plugin marketplace add vruum-gtm/skills
17
- /plugin install vruum@vruum-gtm
18
- ```
19
-
20
- That's it. Skills appear as slash commands. The MCP server connects automatically with OAuth (no secret to paste).
21
-
22
- ### Claude Desktop
23
-
24
- Settings → Connectors → Add custom connector → paste:
25
-
26
- ```
27
- https://api.vruum.ai/mcp
10
+ npx @vruum/skills install
28
11
  ```
29
12
 
30
- Skills appear as slash commands on connection. Approve the OAuth flow when prompted.
31
-
32
- ### ChatGPT (Business / Enterprise / Edu)
33
-
34
- Enable Developer Mode → Settings → Apps & Connectors → Add → paste:
13
+ Detects Claude Code (`~/.claude/skills/`) and Codex CLI (`~/.codex/skills/`). For other harnesses:
35
14
 
15
+ ```bash
16
+ npx @vruum/skills install --target /path/to/skills/dir
36
17
  ```
37
- https://api.vruum.ai/mcp
38
- ```
39
-
40
- Note: ChatGPT uses MCP tools directly and doesn't currently surface prompts as slash commands. You still get every Vruum tool through natural conversation.
41
-
42
- ### Cursor
43
18
 
44
- Settings → MCP → Add server → paste:
19
+ ## Skills
45
20
 
46
- ```
47
- https://api.vruum.ai/mcp
48
- ```
21
+ <!-- generated:skills-begin -->
22
+ - `/diagnose-reply` — Diagnose why a reply happened — what worked or didn't in the outreach that triggered it. Use when: why did they reply, what worked, diagnose reply, reply diagnosis, analyze this reply, what caused this reply, reply analysis.
23
+ - `/engagement-triage` — Review and approve your pending LinkedIn engagement drafts and demand-gen content posts. Use when: triage engagements, review engagement queue, review warming comments, review nurture reactions, review marketing comments, review content drafts, check engagement queue.
24
+ - `/enrich-prospect` — Deep prospect diarization — synthesize everything known about a person into a structured intelligence profile. Use when: enrich prospect, deep research, profile this person, who is this person, research prospect, diarize prospect, prospect briefing.
25
+ - `/outreach-triage` — Review and approve your pending outreach drafts across LinkedIn and email. Use when: triage, review queue, morning review, check messages, approve outreach, what needs review.
26
+ - `/vruum-skills-upgrade` — Upgrade @vruum/skills to the latest npm version and re-sync ~/.vruum/. Use when: upgrade vruum skills, update vruum, pull latest vruum skills, or when the preamble reports UPGRADE_AVAILABLE.
27
+ <!-- generated:skills-end -->
49
28
 
50
- Prompts appear as slash commands on connection.
29
+ ## Upgrade
51
30
 
52
- ### Codex CLI, Windsurf, other harnesses
53
-
54
- These tools don't yet support MCP prompts natively, so we ship the skill files as an npm package you can install into your harness's skill directory:
31
+ Skills auto-prompt to upgrade via the preamble. To upgrade explicitly:
55
32
 
56
33
  ```bash
57
34
  npx @vruum/skills install
58
35
  ```
59
36
 
60
- Auto-detects `~/.claude/skills/` and `~/.codex/skills/`. For anything else:
37
+ ## Uninstall
61
38
 
62
39
  ```bash
63
- npx @vruum/skills install --target <your harness skill dir>
40
+ npx @vruum/skills uninstall
64
41
  ```
65
42
 
66
- See `npx @vruum/skills install --help` for all options.
67
-
68
- ---
69
-
70
- ## What you get
71
-
72
- Four slash commands that wrap the most common Vruum workflows:
73
-
74
- - **`/enrich-prospect`** — synthesize everything known about a person (LinkedIn, research, conversation history, engagement signals) into a structured intelligence profile. Reveals the gap between what their bio says and what they actually focus on.
75
- - **`/diagnose-reply`** — when someone replies, explain exactly what worked in your outreach. Turns every reply into a learning event.
76
- - **`/outreach-triage`** — review your pending outreach drafts with parallel AI review. Dispatches subagents to evaluate each message in your queue, surfaces fixes, and lets you approve in bulk or one at a time. Handles T1s (first touches), follow-ups (with deep research), and reply responses (high-stakes).
77
- - **`/engagement-triage`** — review your LinkedIn engagement queue (warming comments, nurture reactions, marketing comments) and demand-gen content post drafts. Same parallel-subagent pattern, tuned for voice-fit and post-relevance.
78
-
79
- Plus the full Vruum MCP tool surface (60+ tools) accessible via natural conversation: search people, start outreach, review messages, manage deals, check analytics, run market research, and more.
80
-
81
-
82
- ---
83
-
84
- ## Auth
85
-
86
- OAuth 2.1 with PKCE and Dynamic Client Registration — handled by your AI client automatically. You sign in with your Vruum account in a browser popup the first time. Your AI assistant never sees your password or any API key.
87
-
88
- Accounts: sign up at [vruum.ai](https://vruum.ai).
89
-
90
- ---
91
-
92
- ## Support
93
-
94
- - Product questions: [vruum.ai](https://vruum.ai)
95
- - Issues with this plugin: [github.com/vruum-gtm/skills/issues](https://github.com/vruum-gtm/skills/issues)
43
+ Removes all symlinks this installer created. Does not touch `~/.vruum/auth.json` or other co-resident tool state.
96
44
 
97
- ## License
45
+ ## Links
98
46
 
99
- MIT.
47
+ - [Vruum](https://vruum.ai)
48
+ - [MCP server](https://api.vruum.ai/mcp)
49
+ - [Issues](https://github.com/vruum-gtm/skills/issues)
@@ -26,7 +26,7 @@ The orchestrator will tell you which type and provide IDs.
26
26
 
27
27
  ## Step 1: Load your items
28
28
 
29
- Call `get_engagement_review` with your assigned `engagement_ids`, `content_length="full"`, and `for_company` parameter. This returns for each item:
29
+ Call `get_engagement_review` with your assigned `engagement_ids` and `content_length="full"`. This returns for each item:
30
30
  - The draft comment text (if comment or repost_commentary)
31
31
  - The reaction type (if reaction)
32
32
  - The target post text (what the prospect posted)
@@ -137,7 +137,7 @@ ISSUES_FOUND: {comma-separated or "none"}
137
137
 
138
138
  ## Step 1: Load your posts
139
139
 
140
- Call `get_content_review` with `for_company` parameter. This returns for each post:
140
+ Call `get_content_review`. This returns for each post:
141
141
  - The draft content, type, tags, status, scheduled date
142
142
  - Past performance stats (avg engagement for same content type)
143
143
  - Calendar neighbors (other posts within 3 days)
@@ -22,7 +22,7 @@ You do NOT approve or send messages. You review, edit if needed, and return a st
22
22
 
23
23
  ## Step 1: Load your messages
24
24
 
25
- Call `get_outreach_review` with your assigned `message_ids`, `content_length="full"`, and `for_company` parameter. This returns full context for each message including:
25
+ Call `get_outreach_review` with your assigned `message_ids` and `content_length="full"`. This returns full context for each message including:
26
26
 
27
27
  - The draft message content
28
28
  - Person info (name, title, company, match score)
@@ -88,7 +88,7 @@ If personalization is surface level or basic, and you have the tools to go deepe
88
88
 
89
89
  If a message needs better personalization or you need to verify something, you have access to:
90
90
 
91
- - **Knowledge base** (`search_knowledge_base` with `for_company`): Search the company's uploaded sales docs. Browse without filters first to see what's available, then narrow with `doc_type` or `query`.
91
+ - **Knowledge base** (`search_knowledge_base`): Search the company's uploaded sales docs. Browse without filters first to see what's available, then narrow with `doc_type` or `query`.
92
92
  - **Web search**: Search for recent news about the prospect's company, their recent activity, industry trends relevant to them
93
93
  - **LinkedIn data** (`fetch_linkedin_data`): Pull the prospect's recent posts if not already in the review data
94
94
  - **Company research** (`get_company_research`, `fetch_company_website`): Get deeper company context if the match analysis feels thin
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env bash
2
+ # vruum-skills-update-check — periodic version check for the @vruum/skills
3
+ # npm package. Ported from gstack's gstack-update-check, simplified.
4
+ #
5
+ # Output (one line, or nothing):
6
+ # JUST_UPGRADED <old> <new> — marker found from a recent upgrade
7
+ # UPGRADE_AVAILABLE <old> <new> — npm registry reports a newer version
8
+ # (nothing) — up to date, snoozed, or check skipped
9
+ #
10
+ # Env overrides (for testing):
11
+ # VRUUM_STATE_DIR — override ~/.vruum state directory
12
+ # VRUUM_REMOTE_URL — override npm registry URL
13
+ set -euo pipefail
14
+
15
+ STATE_DIR="${VRUUM_STATE_DIR:-$HOME/.vruum}"
16
+ VERSION_FILE="$STATE_DIR/VERSION"
17
+ CACHE_FILE="$STATE_DIR/last-update-check"
18
+ MARKER_FILE="$STATE_DIR/just-upgraded-from"
19
+ SNOOZE_FILE="$STATE_DIR/update-snoozed"
20
+ CONFIG_FILE="$STATE_DIR/config.yaml"
21
+ REMOTE_URL="${VRUUM_REMOTE_URL:-https://registry.npmjs.org/@vruum/skills/latest}"
22
+
23
+ # Force flag busts cache + snooze (used by /vruum-skills-upgrade directly)
24
+ if [ "${1:-}" = "--force" ]; then
25
+ rm -f "$CACHE_FILE" "$SNOOZE_FILE"
26
+ fi
27
+
28
+ # Step 0: if config says updates disabled, bail.
29
+ if [ -f "$CONFIG_FILE" ] && grep -qE '^[[:space:]]*update_check[[:space:]]*:[[:space:]]*false' "$CONFIG_FILE"; then
30
+ exit 0
31
+ fi
32
+
33
+ # Step 1: local version (written by install.js at install time)
34
+ [ -f "$VERSION_FILE" ] || exit 0
35
+ LOCAL="$(tr -d '[:space:]' < "$VERSION_FILE")"
36
+ [ -n "$LOCAL" ] || exit 0
37
+
38
+ # Step 2: just-upgraded marker — emit once, clear snooze, fall through to check
39
+ # remote in case another release landed since the upgrade.
40
+ if [ -f "$MARKER_FILE" ]; then
41
+ OLD="$(tr -d '[:space:]' < "$MARKER_FILE" 2>/dev/null || true)"
42
+ rm -f "$MARKER_FILE" "$SNOOZE_FILE"
43
+ [ -n "$OLD" ] && echo "JUST_UPGRADED $OLD $LOCAL"
44
+ fi
45
+
46
+ # Step 3: snooze helper — returns 0 if snoozed (stay quiet), 1 if not.
47
+ # Snooze file: "<version> <level> <epoch>". Levels: 1=24h, 2=48h, 3+=7d.
48
+ check_snooze() {
49
+ local remote_ver="$1"
50
+ [ -f "$SNOOZE_FILE" ] || return 1
51
+ local sv sl se
52
+ sv="$(awk '{print $1}' "$SNOOZE_FILE" 2>/dev/null || true)"
53
+ sl="$(awk '{print $2}' "$SNOOZE_FILE" 2>/dev/null || true)"
54
+ se="$(awk '{print $3}' "$SNOOZE_FILE" 2>/dev/null || true)"
55
+ [ -n "$sv" ] && [ -n "$sl" ] && [ -n "$se" ] || return 1
56
+ case "$sl" in *[!0-9]*) return 1 ;; esac
57
+ case "$se" in *[!0-9]*) return 1 ;; esac
58
+ # New version drop clears the snooze.
59
+ [ "$sv" = "$remote_ver" ] || return 1
60
+ local dur
61
+ case "$sl" in
62
+ 1) dur=86400 ;;
63
+ 2) dur=172800 ;;
64
+ *) dur=604800 ;;
65
+ esac
66
+ local now expires
67
+ now="$(date +%s)"
68
+ expires=$(( se + dur ))
69
+ [ "$now" -lt "$expires" ]
70
+ }
71
+
72
+ # Step 4: cache — UP_TO_DATE 60 min, UPGRADE_AVAILABLE 12 hours.
73
+ if [ -f "$CACHE_FILE" ]; then
74
+ CACHED="$(cat "$CACHE_FILE" 2>/dev/null || true)"
75
+ case "$CACHED" in
76
+ UP_TO_DATE*) TTL=60 ;;
77
+ UPGRADE_AVAILABLE*) TTL=720 ;;
78
+ *) TTL=0 ;;
79
+ esac
80
+ STALE=$(find "$CACHE_FILE" -mmin +"$TTL" 2>/dev/null || true)
81
+ if [ -z "$STALE" ] && [ "$TTL" -gt 0 ]; then
82
+ case "$CACHED" in
83
+ UP_TO_DATE*)
84
+ CV="$(echo "$CACHED" | awk '{print $2}')"
85
+ [ "$CV" = "$LOCAL" ] && exit 0
86
+ ;;
87
+ UPGRADE_AVAILABLE*)
88
+ CO="$(echo "$CACHED" | awk '{print $2}')"
89
+ if [ "$CO" = "$LOCAL" ]; then
90
+ CN="$(echo "$CACHED" | awk '{print $3}')"
91
+ check_snooze "$CN" && exit 0
92
+ echo "$CACHED"
93
+ exit 0
94
+ fi
95
+ ;;
96
+ esac
97
+ fi
98
+ fi
99
+
100
+ # Step 5: slow path — fetch latest from npm registry.
101
+ mkdir -p "$STATE_DIR"
102
+ RESP="$(curl -fsSL --max-time 5 "$REMOTE_URL" 2>/dev/null || true)"
103
+ # Extract "version":"X.Y.Z" without jq.
104
+ REMOTE="$(echo "$RESP" | grep -oE '"version"[[:space:]]*:[[:space:]]*"[^"]+"' | head -1 | sed -E 's/.*"([^"]+)"$/\1/')"
105
+ REMOTE="$(echo "$REMOTE" | tr -d '[:space:]')"
106
+
107
+ # Reject anything that doesn't look like a semver number.
108
+ if ! echo "$REMOTE" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+'; then
109
+ echo "UP_TO_DATE $LOCAL" > "$CACHE_FILE"
110
+ exit 0
111
+ fi
112
+
113
+ if [ "$LOCAL" = "$REMOTE" ]; then
114
+ echo "UP_TO_DATE $LOCAL" > "$CACHE_FILE"
115
+ exit 0
116
+ fi
117
+
118
+ echo "UPGRADE_AVAILABLE $LOCAL $REMOTE" > "$CACHE_FILE"
119
+ check_snooze "$REMOTE" && exit 0
120
+ echo "UPGRADE_AVAILABLE $LOCAL $REMOTE"
package/install.js CHANGED
@@ -27,7 +27,54 @@ const os = require('node:os');
27
27
 
28
28
  const VERSION = require('./package.json').version;
29
29
  const PACKAGE_ROOT = __dirname;
30
- const SKILLS_SRC = path.join(PACKAGE_ROOT, 'skills');
30
+ const PKG_SKILLS = path.join(PACKAGE_ROOT, 'skills');
31
+ const PKG_AGENTS = path.join(PACKAGE_ROOT, 'agents');
32
+
33
+ // Installer-owned stable path. Mirrors gstack's ~/.claude/skills/gstack/
34
+ // approach: package contents get copied here so symlinks target a path that
35
+ // never moves, even when npm/npx wipes the original package directory.
36
+ const VRUUM_ROOT = path.join(os.homedir(), '.vruum');
37
+ const VRUUM_SKILLS = path.join(VRUUM_ROOT, 'skills');
38
+ const VRUUM_AGENTS = path.join(VRUUM_ROOT, 'agents');
39
+ const VRUUM_BIN = path.join(VRUUM_ROOT, 'bin');
40
+ const VRUUM_VERSION_FILE = path.join(VRUUM_ROOT, 'VERSION');
41
+ const PKG_BIN = path.join(PACKAGE_ROOT, 'bin');
42
+
43
+ // Prelude prepended to every SKILL.md as it lands in ~/.vruum/skills/. Runs
44
+ // an update check (1h cache), then gives Claude natural-language instructions
45
+ // for how to react. Mirrors gstack's preamble pattern but injected at install
46
+ // time rather than via a template-gen step.
47
+ const SKILL_PRELUDE = `## Preamble (run first)
48
+
49
+ \`\`\`bash
50
+ _UPD=$(~/.vruum/bin/vruum-skills-update-check 2>/dev/null || true)
51
+ [ -n "$_UPD" ] && echo "$_UPD" || true
52
+ \`\`\`
53
+
54
+ If output shows \`UPGRADE_AVAILABLE <old> <new>\`: read \`~/.vruum/skills/vruum-skills-upgrade/SKILL.md\` and follow the inline upgrade flow. If \`~/.vruum/config.yaml\` has \`auto_upgrade: true\`, run the upgrade silently; otherwise AskUserQuestion with four options — (A) upgrade now, (B) upgrade with changelog preview, (C) snooze, (D) skip this session. If user picks snooze, bump the snooze level in \`~/.vruum/update-snoozed\` (format: \`<new_version> <level> <unix_epoch>\`; level 1 = 24h, 2 = 48h, 3+ = 7d). Then continue with the skill.
55
+
56
+ If output shows \`JUST_UPGRADED <from> <to>\`: tell the user "Running @vruum/skills v{to} (just updated!)" in one line, then continue.
57
+
58
+ Otherwise proceed silently with the skill below.
59
+
60
+ ---
61
+ `;
62
+
63
+ function injectPrelude(skillDir) {
64
+ const skillFile = path.join(skillDir, 'SKILL.md');
65
+ if (!fs.existsSync(skillFile)) return;
66
+ const raw = fs.readFileSync(skillFile, 'utf8');
67
+ // Find end of YAML frontmatter. If missing, inject at top.
68
+ let insertAt = 0;
69
+ if (raw.startsWith('---\n')) {
70
+ const end = raw.indexOf('\n---\n', 4);
71
+ if (end !== -1) insertAt = end + 5;
72
+ }
73
+ const before = raw.slice(0, insertAt);
74
+ const after = raw.slice(insertAt);
75
+ const patched = before + '\n' + SKILL_PRELUDE + '\n' + after.replace(/^\n+/, '');
76
+ fs.writeFileSync(skillFile, patched);
77
+ }
31
78
 
32
79
  // Known harness skill directories. Add a target here when a new harness
33
80
  // lands on a stable skill-dir convention.
@@ -90,13 +137,56 @@ other assistants that don't yet support MCP prompts.`);
90
137
  }
91
138
 
92
139
  function listAvailableSkills() {
93
- if (!fs.existsSync(SKILLS_SRC)) return [];
140
+ if (!fs.existsSync(PKG_SKILLS)) return [];
94
141
  return fs
95
- .readdirSync(SKILLS_SRC, { withFileTypes: true })
142
+ .readdirSync(PKG_SKILLS, { withFileTypes: true })
96
143
  .filter((entry) => entry.isDirectory())
97
144
  .map((entry) => entry.name);
98
145
  }
99
146
 
147
+ function syncVruumRoot({ dryRun }) {
148
+ if (dryRun) {
149
+ const rows = [
150
+ `would sync ${PKG_SKILLS} -> ${VRUUM_SKILLS} (with auto-update prelude)`,
151
+ ];
152
+ if (fs.existsSync(PKG_AGENTS)) rows.push(`would sync ${PKG_AGENTS} -> ${VRUUM_AGENTS}`);
153
+ if (fs.existsSync(PKG_BIN)) rows.push(`would sync ${PKG_BIN} -> ${VRUUM_BIN}`);
154
+ rows.push(`would write ${VRUUM_VERSION_FILE} = ${VERSION}`);
155
+ return rows;
156
+ }
157
+ fs.mkdirSync(VRUUM_ROOT, { recursive: true });
158
+
159
+ // Skills — copy then inject prelude in-place on the ~/.vruum/ copy.
160
+ fs.rmSync(VRUUM_SKILLS, { recursive: true, force: true });
161
+ fs.cpSync(PKG_SKILLS, VRUUM_SKILLS, { recursive: true });
162
+ for (const skillName of listAvailableSkills()) {
163
+ injectPrelude(path.join(VRUUM_SKILLS, skillName));
164
+ }
165
+ const rows = [`synced ${VRUUM_SKILLS} (prelude injected)`];
166
+
167
+ if (fs.existsSync(PKG_AGENTS)) {
168
+ fs.rmSync(VRUUM_AGENTS, { recursive: true, force: true });
169
+ fs.cpSync(PKG_AGENTS, VRUUM_AGENTS, { recursive: true });
170
+ rows.push(`synced ${VRUUM_AGENTS}`);
171
+ }
172
+
173
+ // Bin scripts — update-check needs to land at a stable path and be exec.
174
+ if (fs.existsSync(PKG_BIN)) {
175
+ fs.rmSync(VRUUM_BIN, { recursive: true, force: true });
176
+ fs.cpSync(PKG_BIN, VRUUM_BIN, { recursive: true });
177
+ for (const entry of fs.readdirSync(VRUUM_BIN)) {
178
+ fs.chmodSync(path.join(VRUUM_BIN, entry), 0o755);
179
+ }
180
+ rows.push(`synced ${VRUUM_BIN}`);
181
+ }
182
+
183
+ // VERSION file — read by vruum-skills-update-check as the local version.
184
+ fs.writeFileSync(VRUUM_VERSION_FILE, VERSION + '\n');
185
+ rows.push(`wrote ${VRUUM_VERSION_FILE} = ${VERSION}`);
186
+
187
+ return rows;
188
+ }
189
+
100
190
  function ensureTargetDir(target, dryRun) {
101
191
  if (fs.existsSync(target)) return { created: false };
102
192
  if (dryRun) return { created: 'would' };
@@ -159,7 +249,7 @@ function commandInstall({ targets: extraTargets, dryRun }) {
159
249
  const skills = listAvailableSkills();
160
250
  if (skills.length === 0) {
161
251
  throw new Error(
162
- `No skills found under ${SKILLS_SRC}. This looks like a broken package.`
252
+ `No skills found under ${PKG_SKILLS}. This looks like a broken package.`
163
253
  );
164
254
  }
165
255
  const targets = detectTargets(extraTargets);
@@ -173,6 +263,8 @@ function commandInstall({ targets: extraTargets, dryRun }) {
173
263
  process.exit(1);
174
264
  }
175
265
 
266
+ const syncRows = syncVruumRoot({ dryRun });
267
+
176
268
  const summary = [];
177
269
  for (const target of targets) {
178
270
  const { created } = ensureTargetDir(target.dir, dryRun);
@@ -182,14 +274,18 @@ function commandInstall({ targets: extraTargets, dryRun }) {
182
274
  summary.push({ target: target.dir, detail: 'created target dir' });
183
275
  }
184
276
  for (const skillName of skills) {
185
- const srcAbs = path.join(SKILLS_SRC, skillName);
277
+ const srcAbs = path.join(VRUUM_SKILLS, skillName);
186
278
  summary.push(linkSkill({ name: skillName, srcAbs, target: target.dir, dryRun }));
187
279
  }
188
280
  }
189
281
 
190
282
  const prefix = dryRun ? '[dry-run] ' : '';
191
283
  console.log(`${prefix}@vruum/skills v${VERSION}`);
192
- console.log(`${prefix}skills source: ${SKILLS_SRC}`);
284
+ console.log(`${prefix}package source: ${PACKAGE_ROOT}`);
285
+ console.log(`${prefix}stable root: ${VRUUM_ROOT}`);
286
+ for (const row of syncRows) {
287
+ console.log(` ${prefix}${row}`);
288
+ }
193
289
  for (const target of targets) {
194
290
  console.log(`${prefix}target: ${target.dir}${target.autoDetected ? ' (auto)' : ''}`);
195
291
  }
@@ -213,19 +309,25 @@ function commandInstall({ targets: extraTargets, dryRun }) {
213
309
  function commandUninstall({ targets: extraTargets, dryRun }) {
214
310
  const skills = listAvailableSkills();
215
311
  const targets = detectTargets(extraTargets);
312
+ const prefix = dryRun ? '[dry-run] ' : '';
313
+ console.log(`${prefix}@vruum/skills v${VERSION} uninstall`);
314
+
216
315
  if (targets.length === 0) {
217
- console.error('No AI harness skill directories detected. Nothing to remove.');
218
- return;
316
+ console.error('No AI harness skill directories detected.');
219
317
  }
220
318
 
221
- const prefix = dryRun ? '[dry-run] ' : '';
222
- console.log(`${prefix}@vruum/skills v${VERSION} uninstall`);
319
+ // A symlink is "ours" if it points into ~/.vruum/skills/. We accept any
320
+ // target there (not just the current skill name) so stale links from
321
+ // renamed skills still get cleaned up.
322
+ const isOurLink = (linkTarget) => {
323
+ const resolved = path.resolve(linkTarget);
324
+ return resolved.startsWith(VRUUM_SKILLS + path.sep) || resolved === VRUUM_SKILLS;
325
+ };
223
326
 
224
327
  for (const target of targets) {
225
328
  console.log(`${prefix}target: ${target.dir}`);
226
329
  for (const skillName of skills) {
227
330
  const dst = path.join(target.dir, skillName);
228
- const srcAbs = path.join(SKILLS_SRC, skillName);
229
331
  let existing = null;
230
332
  try {
231
333
  const lstat = fs.lstatSync(dst);
@@ -242,7 +344,7 @@ function commandUninstall({ targets: extraTargets, dryRun }) {
242
344
  console.log(` ${prefix}not-installed ${skillName}`);
243
345
  continue;
244
346
  }
245
- if (existing.kind !== 'symlink' || existing.target !== srcAbs) {
347
+ if (existing.kind !== 'symlink' || !isOurLink(existing.target)) {
246
348
  console.log(` ${prefix}skipped ${skillName} [not our symlink]`);
247
349
  continue;
248
350
  }
@@ -254,6 +356,33 @@ function commandUninstall({ targets: extraTargets, dryRun }) {
254
356
  }
255
357
  }
256
358
  }
359
+
360
+ // Only clean up our own subdirectories — ~/.vruum/ is a shared state dir
361
+ // (e.g. the .agents/ vruum-update-check keeps config.yaml + snooze state
362
+ // there, and both installers share that config).
363
+ for (const dir of [VRUUM_SKILLS, VRUUM_AGENTS, VRUUM_BIN]) {
364
+ if (!fs.existsSync(dir)) continue;
365
+ if (dryRun) {
366
+ console.log(`${prefix}would remove ${dir}`);
367
+ } else {
368
+ fs.rmSync(dir, { recursive: true, force: true });
369
+ console.log(`${prefix}removed ${dir}`);
370
+ }
371
+ }
372
+ if (fs.existsSync(VRUUM_VERSION_FILE)) {
373
+ if (dryRun) {
374
+ console.log(`${prefix}would remove ${VRUUM_VERSION_FILE}`);
375
+ } else {
376
+ fs.unlinkSync(VRUUM_VERSION_FILE);
377
+ console.log(`${prefix}removed ${VRUUM_VERSION_FILE}`);
378
+ }
379
+ }
380
+ // Best-effort rmdir the root if now empty; ignore ENOTEMPTY.
381
+ if (!dryRun && fs.existsSync(VRUUM_ROOT)) {
382
+ try { fs.rmdirSync(VRUUM_ROOT); } catch (err) {
383
+ if (err.code !== 'ENOTEMPTY' && err.code !== 'EEXIST') throw err;
384
+ }
385
+ }
257
386
  }
258
387
 
259
388
  function commandList({ targets: extraTargets }) {
@@ -273,7 +402,7 @@ function commandList({ targets: extraTargets }) {
273
402
  }
274
403
  for (const skillName of skills) {
275
404
  const dst = path.join(target.dir, skillName);
276
- const srcAbs = path.join(SKILLS_SRC, skillName);
405
+ const srcAbs = path.join(VRUUM_SKILLS, skillName);
277
406
  try {
278
407
  const lstat = fs.lstatSync(dst);
279
408
  if (lstat.isSymbolicLink()) {
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@vruum/skills",
3
- "version": "0.2.0",
3
+ "version": "0.4.1",
4
4
  "description": "Vruum AI skills for Claude Code, Codex CLI, and any AI assistant with a skill directory. 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/skills.git"
8
+ "url": "https://github.com/vruum-gtm/vruum_ai.git",
9
+ "directory": ".agents"
9
10
  },
10
11
  "homepage": "https://vruum.ai",
11
12
  "bugs": {
@@ -18,9 +19,9 @@
18
19
  "install.js",
19
20
  "README.md",
20
21
  "LICENSE",
21
- ".mcp.json",
22
22
  "skills/",
23
- "agents/"
23
+ "agents/",
24
+ "bin/"
24
25
  ],
25
26
  "engines": {
26
27
  "node": ">=18"
@@ -34,5 +35,6 @@
34
35
  "sales",
35
36
  "outreach",
36
37
  "gtm"
37
- ]
38
+ ],
39
+ "contentHash": "fc531edad0e9163122d472579d3f1a37b74201dd8041fe71056ca34671cf1c7f"
38
40
  }
@@ -1,6 +1,9 @@
1
1
  ---
2
2
  name: diagnose-reply
3
- description: "Diagnose why a reply happened — what worked or didn't in the outreach that triggered it. Use when: why did they reply, what worked, diagnose reply, reply diagnosis, analyze this reply, what caused this reply, reply analysis."
3
+ description: >-
4
+ Diagnose why a reply happened — what worked or didn't in the outreach that
5
+ triggered it. Use when: why did they reply, what worked, diagnose reply, reply
6
+ diagnosis, analyze this reply, what caused this reply, reply analysis.
4
7
  ---
5
8
 
6
9
  # /diagnose-reply
@@ -9,9 +12,9 @@ You diagnose individual replies to understand what worked (or didn't) in the out
9
12
 
10
13
  ## Step 1: Identify the reply
11
14
 
12
- The operator will reference a person, a reply, or a conversation. Use `get_person_360` with `for_company` to load the full context: conversation thread, outreach plan, match analysis.
15
+ The user will reference a person, a reply, or a conversation. Use `get_person_360` to load the full context: conversation thread, outreach plan, match analysis.
13
16
 
14
- If the operator doesn't specify a person, ask: "Which reply do you want to diagnose? Give me a name, company, or message."
17
+ If the user doesn't specify a person, ask: "Which reply do you want to diagnose? Give me a name, company, or message."
15
18
 
16
19
  ## Step 2: Read the conversation
17
20
 
@@ -26,7 +29,7 @@ From the person_360 response, read the full conversation thread. Identify:
26
29
 
27
30
  ## Step 3: Diagnose
28
31
 
29
- Call `get_performance_metrics` with `for_company`, `view='funnel'`, and the segment_id to get segment-level reply rates by channel.
32
+ Call `get_performance_metrics` with `view='funnel'` and the segment_id to get segment-level reply rates by channel.
30
33
 
31
34
  Synthesize a diagnosis:
32
35