@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 +24 -74
- package/agents/vruum-engagement-reviewer.md +2 -2
- package/agents/vruum-outreach-reviewer.md +2 -2
- package/bin/vruum-skills-update-check +120 -0
- package/install.js +142 -13
- package/package.json +7 -5
- package/skills/diagnose-reply/SKILL.md +7 -4
- package/skills/engagement-triage/SKILL.md +64 -55
- package/skills/engagement-triage/references/content-subagent-instructions.md +59 -0
- package/skills/engagement-triage/references/engagement-subagent-instructions.md +79 -0
- package/skills/engagement-triage/references/quality-standards.md +45 -0
- package/skills/enrich-prospect/SKILL.md +7 -3
- package/skills/outreach-triage/SKILL.md +114 -71
- package/skills/outreach-triage/references/quality-standards.md +139 -0
- package/skills/outreach-triage/references/subagent-instructions.md +172 -0
- package/skills/vruum-skills-upgrade/SKILL.md +76 -0
- package/.mcp.json +0 -8
package/README.md
CHANGED
|
@@ -1,99 +1,49 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @vruum/skills
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Vruum AI skills for Claude Code, Codex CLI, and any AI assistant with a skill directory.
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
19
|
+
## Skills
|
|
45
20
|
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
29
|
+
## Upgrade
|
|
51
30
|
|
|
52
|
-
|
|
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
|
-
|
|
37
|
+
## Uninstall
|
|
61
38
|
|
|
62
39
|
```bash
|
|
63
|
-
npx @vruum/skills
|
|
40
|
+
npx @vruum/skills uninstall
|
|
64
41
|
```
|
|
65
42
|
|
|
66
|
-
|
|
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
|
-
##
|
|
45
|
+
## Links
|
|
98
46
|
|
|
99
|
-
|
|
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
|
|
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
|
|
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
|
|
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`
|
|
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
|
|
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(
|
|
140
|
+
if (!fs.existsSync(PKG_SKILLS)) return [];
|
|
94
141
|
return fs
|
|
95
|
-
.readdirSync(
|
|
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 ${
|
|
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(
|
|
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}
|
|
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.
|
|
218
|
-
return;
|
|
316
|
+
console.error('No AI harness skill directories detected.');
|
|
219
317
|
}
|
|
220
318
|
|
|
221
|
-
|
|
222
|
-
|
|
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
|
|
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(
|
|
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.
|
|
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/
|
|
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:
|
|
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
|
|
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
|
|
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 `
|
|
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
|
|