vipcare 0.1.0 → 0.2.0
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/CLAUDE.md +58 -0
- package/README.md +21 -0
- package/bin/vip.js +253 -14
- package/lib/card.js +306 -0
- package/lib/config.js +9 -2
- package/lib/fetchers/search.js +17 -16
- package/lib/fetchers/twitter.js +3 -3
- package/lib/fetchers/youtube.js +108 -0
- package/lib/monitor.js +2 -2
- package/lib/profile.js +11 -1
- package/lib/scheduler.js +5 -5
- package/lib/synthesizer.js +5 -5
- package/lib/templates.js +94 -11
- package/package.json +2 -2
- package/web/index.html +204 -0
- package/lib/fetchers/web.js +0 -29
- package/profiles/.gitkeep +0 -0
- package/profiles/sam-altman.md +0 -49
- package/skill/vip.md +0 -96
- package/tests/fetchers.test.js +0 -21
- package/tests/monitor.test.js +0 -28
- package/tests/profile.test.js +0 -89
- package/tests/resolver.test.js +0 -40
- package/tests/scheduler.test.js +0 -22
package/lib/templates.js
CHANGED
|
@@ -1,16 +1,25 @@
|
|
|
1
|
-
export const PROFILE_SYSTEM_PROMPT = `You are
|
|
2
|
-
Given the raw data below from public sources (tweets, LinkedIn snippets, web search results), \
|
|
3
|
-
synthesize a
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
export const PROFILE_SYSTEM_PROMPT = `You are an expert behavioral analyst building a comprehensive VIP contact profile.
|
|
2
|
+
Given the raw data below from public sources (tweets, LinkedIn snippets, web search results, video transcripts), \
|
|
3
|
+
synthesize a deep-analysis profile in the exact Markdown format provided.
|
|
4
|
+
|
|
5
|
+
## Analysis Framework
|
|
6
|
+
Use these theories to infer personality and behavior:
|
|
7
|
+
- **DISC Model** — Dominance, Influence, Steadiness, Conscientiousness
|
|
8
|
+
- **Big Five (OCEAN)** — Openness, Conscientiousness, Extraversion, Agreeableness, Neuroticism
|
|
9
|
+
- **Cialdini's Influence** — Which persuasion principles this person uses/responds to
|
|
10
|
+
- **Situational Leadership** — Their decision-making and leadership approach
|
|
11
|
+
|
|
12
|
+
## Rules
|
|
6
13
|
- Only include information you can directly support from the provided data
|
|
14
|
+
- For personality analysis, these are INFERENCES based on public behavior — always note this
|
|
15
|
+
- Score each dimension 1-5 based on evidence strength
|
|
7
16
|
- If a section has no data, write "No public information found."
|
|
8
|
-
- Do not fabricate
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
|
|
17
|
+
- Do not fabricate details
|
|
18
|
+
- If video transcripts are included, pay special attention to the person's own words, speaking patterns, and how they frame arguments
|
|
19
|
+
- Output ONLY the Markdown profile followed by the JSON metadata block — no extra commentary
|
|
20
|
+
|
|
21
|
+
## Output Format
|
|
12
22
|
|
|
13
|
-
Format:
|
|
14
23
|
# {Full Name}
|
|
15
24
|
|
|
16
25
|
> {One-line summary / tagline}
|
|
@@ -29,6 +38,44 @@ Format:
|
|
|
29
38
|
## Bio
|
|
30
39
|
{2-3 paragraph biography synthesized from public sources}
|
|
31
40
|
|
|
41
|
+
## Personality & Communication Style
|
|
42
|
+
- **DISC Type:** {Primary type (D/I/S/C) with brief rationale}
|
|
43
|
+
- **MBTI Estimate:** {e.g. ENTJ — with brief rationale}
|
|
44
|
+
- **Communication Style:** {Direct/Diplomatic/Analytical/Storyteller — with evidence}
|
|
45
|
+
- **Decision-Making:** {Data-driven/Intuition-led/Consensus-seeking/Decisive}
|
|
46
|
+
- **Tone:** {Formal/Casual/Inspiring/Technical}
|
|
47
|
+
- **Key Phrases:** {Recurring phrases or vocabulary patterns they use}
|
|
48
|
+
|
|
49
|
+
## Expertise & Strengths
|
|
50
|
+
- **Core Expertise:** {The domains where they have deep knowledge and proven track record}
|
|
51
|
+
- **Superpower:** {What they do better than almost anyone — their unique edge}
|
|
52
|
+
- **Known For:** {What peers/media consistently cite them for}
|
|
53
|
+
- **Skills Matrix:**
|
|
54
|
+
- {Skill 1}: {★★★★★ level + brief evidence}
|
|
55
|
+
- {Skill 2}: {★★★★☆ level + brief evidence}
|
|
56
|
+
- {Skill 3}: {★★★★☆ level + brief evidence}
|
|
57
|
+
|
|
58
|
+
## Interests & Values
|
|
59
|
+
- **Core Beliefs:** {What they consistently advocate for}
|
|
60
|
+
- **Topics They Care About:** {Recurring themes from speeches, tweets, writing}
|
|
61
|
+
- **Public Positions:** {Publicly stated opinions on relevant issues}
|
|
62
|
+
- **Motivations:** {What drives them — mission, money, impact, legacy, etc.}
|
|
63
|
+
|
|
64
|
+
## Character & Leadership
|
|
65
|
+
- **Leadership Style:** {Visionary/Operational/Servant-leader/Transformational — with evidence}
|
|
66
|
+
- **Risk Tolerance:** {Conservative/Calculated/Aggressive — with evidence}
|
|
67
|
+
- **Management Philosophy:** {How they describe running teams/companies}
|
|
68
|
+
- **Under Pressure:** {How they handle crises, based on public evidence}
|
|
69
|
+
- **Influence Strategy:** {Authority/Reciprocity/Vision/Data/Social-proof — per Cialdini}
|
|
70
|
+
|
|
71
|
+
## How to Work With Them
|
|
72
|
+
- **Icebreaker Topics:** {3 conversation starters based on their interests}
|
|
73
|
+
- **Do:** {3 things that would resonate positively}
|
|
74
|
+
- **Don't:** {3 things to avoid based on known preferences}
|
|
75
|
+
- **Gift Ideas:** {Based on hobbies, interests, values}
|
|
76
|
+
- **Communication Tips:** {Best way to reach/engage this person}
|
|
77
|
+
- **Meeting Prep:** {What to prepare before meeting them}
|
|
78
|
+
|
|
32
79
|
## Key Interests & Topics
|
|
33
80
|
- {Topic 1}
|
|
34
81
|
- {Topic 2}
|
|
@@ -48,13 +95,49 @@ Format:
|
|
|
48
95
|
{Family info, hobbies, or personal details only if publicly shared}
|
|
49
96
|
|
|
50
97
|
## Notes
|
|
51
|
-
{Any other relevant public information}
|
|
98
|
+
{Any other relevant public information}
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
IMPORTANT: After the Markdown profile above, output a JSON metadata block in EXACTLY this format:
|
|
102
|
+
|
|
103
|
+
<!-- VIP_DATA
|
|
104
|
+
{
|
|
105
|
+
"name": "{Full Name}",
|
|
106
|
+
"title": "{Current role}",
|
|
107
|
+
"company": "{Company}",
|
|
108
|
+
"location": "{City, Country}",
|
|
109
|
+
"industry": "{Industry}",
|
|
110
|
+
"disc": "{D/I/S/C primary type letter}",
|
|
111
|
+
"mbti": "{4-letter MBTI}",
|
|
112
|
+
"scores": {
|
|
113
|
+
"openness": {1-5},
|
|
114
|
+
"conscientiousness": {1-5},
|
|
115
|
+
"extraversion": {1-5},
|
|
116
|
+
"agreeableness": {1-5},
|
|
117
|
+
"resilience": {1-5},
|
|
118
|
+
"decision_style": {1-5, 1=intuition 5=data-driven},
|
|
119
|
+
"risk_appetite": {1-5, 1=conservative 5=aggressive},
|
|
120
|
+
"communication": {1-5, 1=reserved 5=expressive},
|
|
121
|
+
"influence": {1-5},
|
|
122
|
+
"leadership": {1-5}
|
|
123
|
+
},
|
|
124
|
+
"expertise": ["{area1}", "{area2}", "{area3}"],
|
|
125
|
+
"superpower": "{their unique edge in one phrase}",
|
|
126
|
+
"tags": ["{tag1}", "{tag2}", "{tag3}", "{tag4}"],
|
|
127
|
+
"icebreakers": ["{topic1}", "{topic2}", "{topic3}"],
|
|
128
|
+
"dos": ["{do1}", "{do2}", "{do3}"],
|
|
129
|
+
"donts": ["{dont1}", "{dont2}", "{dont3}"],
|
|
130
|
+
"gifts": ["{gift1}", "{gift2}"],
|
|
131
|
+
"quote": "{a representative quote from this person}"
|
|
132
|
+
}
|
|
133
|
+
-->`;
|
|
52
134
|
|
|
53
135
|
export const CHANGE_DETECTION_PROMPT = `Compare the OLD profile and NEW data below. Identify any significant changes such as:
|
|
54
136
|
- Job title or company change
|
|
55
137
|
- New achievements or milestones
|
|
56
138
|
- Notable new public statements or positions
|
|
57
139
|
- Changes in focus areas or interests
|
|
140
|
+
- Personality insights from new data
|
|
58
141
|
|
|
59
142
|
If there are significant changes, output a brief summary (2-3 sentences) of what changed.
|
|
60
143
|
If there are no significant changes, output exactly: NO_SIGNIFICANT_CHANGES
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vipcare",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Auto-build VIP person profiles from Twitter/LinkedIn public data",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"vip": "bin/vip.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
"test": "node --test tests/*.test.js"
|
|
10
|
+
"test": "node --experimental-test-module-mocks --test tests/*.test.js"
|
|
11
11
|
},
|
|
12
12
|
"keywords": [
|
|
13
13
|
"crm",
|
package/web/index.html
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>VIPCare - Baseball Cards</title>
|
|
7
|
+
<style>
|
|
8
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
9
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #0f172a; color: #e2e8f0; min-height: 100vh; padding: 20px; }
|
|
10
|
+
h1 { text-align: center; font-size: 1.8em; margin: 20px 0 30px; color: #38bdf8; }
|
|
11
|
+
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: 20px; max-width: 1200px; margin: 0 auto; }
|
|
12
|
+
|
|
13
|
+
.card {
|
|
14
|
+
background: linear-gradient(145deg, #1e293b, #334155);
|
|
15
|
+
border-radius: 16px;
|
|
16
|
+
padding: 24px;
|
|
17
|
+
border: 1px solid #475569;
|
|
18
|
+
cursor: pointer;
|
|
19
|
+
transition: transform 0.2s, box-shadow 0.2s;
|
|
20
|
+
position: relative;
|
|
21
|
+
overflow: hidden;
|
|
22
|
+
}
|
|
23
|
+
.card:hover { transform: translateY(-4px); box-shadow: 0 12px 40px rgba(56,189,248,0.15); }
|
|
24
|
+
.card::before {
|
|
25
|
+
content: '';
|
|
26
|
+
position: absolute;
|
|
27
|
+
top: 0; left: 0; right: 0;
|
|
28
|
+
height: 4px;
|
|
29
|
+
background: linear-gradient(90deg, #38bdf8, #818cf8, #c084fc);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.card-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 12px; }
|
|
33
|
+
.card-name { font-size: 1.4em; font-weight: 700; color: #f1f5f9; }
|
|
34
|
+
.card-role { font-size: 0.85em; color: #94a3b8; margin-top: 2px; }
|
|
35
|
+
.card-badges { display: flex; gap: 6px; }
|
|
36
|
+
.badge { padding: 3px 8px; border-radius: 6px; font-size: 0.7em; font-weight: 700; }
|
|
37
|
+
.badge-disc { background: #38bdf8; color: #0f172a; }
|
|
38
|
+
.badge-mbti { background: #818cf8; color: #0f172a; }
|
|
39
|
+
|
|
40
|
+
.card-quote { font-style: italic; color: #94a3b8; font-size: 0.8em; margin: 10px 0; padding: 8px 12px; border-left: 3px solid #475569; }
|
|
41
|
+
|
|
42
|
+
.radar-container { display: flex; justify-content: center; margin: 16px 0; }
|
|
43
|
+
.radar { width: 200px; height: 200px; }
|
|
44
|
+
|
|
45
|
+
.tags { display: flex; flex-wrap: wrap; gap: 6px; margin: 12px 0; }
|
|
46
|
+
.tag { background: #1e3a5f; color: #38bdf8; padding: 3px 10px; border-radius: 12px; font-size: 0.75em; }
|
|
47
|
+
|
|
48
|
+
.expertise { margin: 10px 0; }
|
|
49
|
+
.expertise-title { font-size: 0.75em; color: #64748b; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 6px; }
|
|
50
|
+
.expertise-item { font-size: 0.8em; color: #cbd5e1; padding: 2px 0; }
|
|
51
|
+
.superpower { color: #fbbf24; font-weight: 600; font-size: 0.85em; margin: 6px 0; }
|
|
52
|
+
|
|
53
|
+
.tips { margin-top: 12px; border-top: 1px solid #475569; padding-top: 12px; }
|
|
54
|
+
.tip-row { display: flex; gap: 4px; font-size: 0.8em; margin: 4px 0; color: #cbd5e1; }
|
|
55
|
+
.tip-icon { width: 20px; text-align: center; }
|
|
56
|
+
.tip-label { color: #64748b; min-width: 55px; }
|
|
57
|
+
|
|
58
|
+
/* Modal */
|
|
59
|
+
.modal-overlay { display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.7); z-index: 100; justify-content: center; align-items: center; padding: 20px; }
|
|
60
|
+
.modal-overlay.active { display: flex; }
|
|
61
|
+
.modal {
|
|
62
|
+
background: #1e293b; border-radius: 16px; max-width: 600px; width: 100%; max-height: 90vh; overflow-y: auto; padding: 32px;
|
|
63
|
+
border: 1px solid #475569;
|
|
64
|
+
}
|
|
65
|
+
.modal-close { float: right; background: none; border: none; color: #94a3b8; font-size: 1.5em; cursor: pointer; }
|
|
66
|
+
.modal h2 { color: #38bdf8; margin: 16px 0 8px; font-size: 1.1em; }
|
|
67
|
+
.modal p, .modal li { color: #cbd5e1; font-size: 0.9em; line-height: 1.6; }
|
|
68
|
+
.modal ul { padding-left: 20px; }
|
|
69
|
+
</style>
|
|
70
|
+
</head>
|
|
71
|
+
<body>
|
|
72
|
+
|
|
73
|
+
<h1>VIPCare</h1>
|
|
74
|
+
<div class="grid" id="grid"></div>
|
|
75
|
+
|
|
76
|
+
<div class="modal-overlay" id="modal" onclick="if(event.target===this)closeModal()">
|
|
77
|
+
<div class="modal" id="modal-content"></div>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<script>
|
|
81
|
+
const cards = [{"name":"Sam Altman","title":"CEO","company":"OpenAI","location":"San Francisco, USA","disc":"?","mbti":"?","scores":{},"tags":["Artificial Intelligence"],"icebreakers":[],"dos":[],"donts":[],"gifts":[],"expertise":[],"superpower":"","quote":"CEO of OpenAI, leading the development and deployment of artificial general intelligence"}];
|
|
82
|
+
|
|
83
|
+
const SCORE_LABELS = {
|
|
84
|
+
openness: 'Openness',
|
|
85
|
+
conscientiousness: 'Conscientiousness',
|
|
86
|
+
extraversion: 'Extraversion',
|
|
87
|
+
agreeableness: 'Agreeableness',
|
|
88
|
+
resilience: 'Resilience',
|
|
89
|
+
decision_style: 'Decision',
|
|
90
|
+
risk_appetite: 'Risk',
|
|
91
|
+
communication: 'Communication',
|
|
92
|
+
influence: 'Influence',
|
|
93
|
+
leadership: 'Leadership'
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
function radarSvg(scores, size = 200) {
|
|
97
|
+
const keys = Object.keys(SCORE_LABELS);
|
|
98
|
+
const cx = size / 2, cy = size / 2, r = size * 0.38;
|
|
99
|
+
const n = keys.length;
|
|
100
|
+
|
|
101
|
+
let gridLines = '';
|
|
102
|
+
for (let level = 1; level <= 5; level++) {
|
|
103
|
+
const lr = r * level / 5;
|
|
104
|
+
let pts = [];
|
|
105
|
+
for (let i = 0; i < n; i++) {
|
|
106
|
+
const angle = (Math.PI * 2 * i / n) - Math.PI / 2;
|
|
107
|
+
pts.push(`${cx + lr * Math.cos(angle)},${cy + lr * Math.sin(angle)}`);
|
|
108
|
+
}
|
|
109
|
+
gridLines += `<polygon points="${pts.join(' ')}" fill="none" stroke="#334155" stroke-width="0.5"/>`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
let axes = '', labels = '', dataPoints = [];
|
|
113
|
+
for (let i = 0; i < n; i++) {
|
|
114
|
+
const angle = (Math.PI * 2 * i / n) - Math.PI / 2;
|
|
115
|
+
const x = cx + r * Math.cos(angle);
|
|
116
|
+
const y = cy + r * Math.sin(angle);
|
|
117
|
+
axes += `<line x1="${cx}" y1="${cy}" x2="${x}" y2="${y}" stroke="#334155" stroke-width="0.5"/>`;
|
|
118
|
+
|
|
119
|
+
const lx = cx + (r + 18) * Math.cos(angle);
|
|
120
|
+
const ly = cy + (r + 18) * Math.sin(angle);
|
|
121
|
+
const label = SCORE_LABELS[keys[i]] || keys[i];
|
|
122
|
+
labels += `<text x="${lx}" y="${ly}" text-anchor="middle" dominant-baseline="middle" fill="#64748b" font-size="7">${label}</text>`;
|
|
123
|
+
|
|
124
|
+
const val = (scores[keys[i]] || 0) / 5;
|
|
125
|
+
const dx = cx + r * val * Math.cos(angle);
|
|
126
|
+
const dy = cy + r * val * Math.sin(angle);
|
|
127
|
+
dataPoints.push(`${dx},${dy}`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const dataPolygon = `<polygon points="${dataPoints.join(' ')}" fill="rgba(56,189,248,0.2)" stroke="#38bdf8" stroke-width="1.5"/>`;
|
|
131
|
+
|
|
132
|
+
return `<svg viewBox="0 0 ${size} ${size}" class="radar">${gridLines}${axes}${dataPolygon}${labels}</svg>`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function renderCard(card, index) {
|
|
136
|
+
const scores = card.scores || {};
|
|
137
|
+
const radar = radarSvg(scores);
|
|
138
|
+
|
|
139
|
+
return `
|
|
140
|
+
<div class="card" onclick="openModal(${index})">
|
|
141
|
+
<div class="card-header">
|
|
142
|
+
<div>
|
|
143
|
+
<div class="card-name">${card.name || 'Unknown'}</div>
|
|
144
|
+
<div class="card-role">${card.title || ''}${card.company ? ' @ ' + card.company : ''}</div>
|
|
145
|
+
</div>
|
|
146
|
+
<div class="card-badges">
|
|
147
|
+
<span class="badge badge-disc">${card.disc || '?'}</span>
|
|
148
|
+
<span class="badge badge-mbti">${card.mbti || '?'}</span>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
${card.quote ? `<div class="card-quote">"${card.quote.slice(0, 120)}${card.quote.length > 120 ? '...' : ''}"</div>` : ''}
|
|
152
|
+
<div class="radar-container">${radar}</div>
|
|
153
|
+
${card.superpower ? `<div class="superpower">⚡ ${card.superpower}</div>` : ''}
|
|
154
|
+
${card.tags?.length ? `<div class="tags">${card.tags.map(t => `<span class="tag">${t}</span>`).join('')}</div>` : ''}
|
|
155
|
+
<div class="tips">
|
|
156
|
+
${card.icebreakers?.length ? `<div class="tip-row"><span class="tip-icon">💡</span><span class="tip-label">Icebreaker</span>${card.icebreakers[0]}</div>` : ''}
|
|
157
|
+
${card.dos?.length ? `<div class="tip-row"><span class="tip-icon">✅</span><span class="tip-label">Do</span>${card.dos[0]}</div>` : ''}
|
|
158
|
+
${card.donts?.length ? `<div class="tip-row"><span class="tip-icon">❌</span><span class="tip-label">Don't</span>${card.donts[0]}</div>` : ''}
|
|
159
|
+
</div>
|
|
160
|
+
</div>`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function openModal(index) {
|
|
164
|
+
const card = cards[index];
|
|
165
|
+
const s = card.scores || {};
|
|
166
|
+
const modal = document.getElementById('modal-content');
|
|
167
|
+
|
|
168
|
+
modal.innerHTML = `
|
|
169
|
+
<button class="modal-close" onclick="closeModal()">×</button>
|
|
170
|
+
<h1 style="color:#38bdf8;margin-bottom:4px">${card.name}</h1>
|
|
171
|
+
<p style="color:#94a3b8">${card.title || ''}${card.company ? ' @ ' + card.company : ''}${card.location ? ' · ' + card.location : ''}</p>
|
|
172
|
+
${card.quote ? `<div class="card-quote" style="margin:16px 0">"${card.quote}"</div>` : ''}
|
|
173
|
+
|
|
174
|
+
<h2>Personality</h2>
|
|
175
|
+
<p><strong>DISC:</strong> ${card.disc || '?'} <strong>MBTI:</strong> ${card.mbti || '?'}</p>
|
|
176
|
+
<div style="display:flex;justify-content:center;margin:16px 0">${radarSvg(s, 260)}</div>
|
|
177
|
+
|
|
178
|
+
${card.expertise?.length ? `<h2>Expertise</h2><ul>${card.expertise.map(e => `<li>${e}</li>`).join('')}</ul>` : ''}
|
|
179
|
+
${card.superpower ? `<p><strong>⚡ Superpower:</strong> ${card.superpower}</p>` : ''}
|
|
180
|
+
|
|
181
|
+
<h2>How to Work With Them</h2>
|
|
182
|
+
${card.icebreakers?.length ? `<p><strong>💡 Icebreakers:</strong> ${card.icebreakers.join(', ')}</p>` : ''}
|
|
183
|
+
${card.dos?.length ? `<p><strong>✅ Do:</strong> ${card.dos.join(' · ')}</p>` : ''}
|
|
184
|
+
${card.donts?.length ? `<p><strong>❌ Don't:</strong> ${card.donts.join(' · ')}</p>` : ''}
|
|
185
|
+
${card.gifts?.length ? `<p><strong>🎁 Gifts:</strong> ${card.gifts.join(', ')}</p>` : ''}
|
|
186
|
+
|
|
187
|
+
${card.tags?.length ? `<h2>Tags</h2><div class="tags">${card.tags.map(t => `<span class="tag">${t}</span>`).join('')}</div>` : ''}
|
|
188
|
+
`;
|
|
189
|
+
|
|
190
|
+
document.getElementById('modal').classList.add('active');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function closeModal() {
|
|
194
|
+
document.getElementById('modal').classList.remove('active');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
document.addEventListener('keydown', e => { if (e.key === 'Escape') closeModal(); });
|
|
198
|
+
|
|
199
|
+
// Render
|
|
200
|
+
const grid = document.getElementById('grid');
|
|
201
|
+
grid.innerHTML = cards.map((card, i) => renderCard(card, i)).join('');
|
|
202
|
+
</script>
|
|
203
|
+
</body>
|
|
204
|
+
</html>
|
package/lib/fetchers/web.js
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
export async function fetchPageText(url, timeout = 15000) {
|
|
2
|
-
try {
|
|
3
|
-
const controller = new AbortController();
|
|
4
|
-
const timer = setTimeout(() => controller.abort(), timeout);
|
|
5
|
-
|
|
6
|
-
const resp = await fetch(url, {
|
|
7
|
-
headers: { 'User-Agent': 'Mozilla/5.0 (compatible; VIPCare/0.1)' },
|
|
8
|
-
signal: controller.signal,
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
clearTimeout(timer);
|
|
12
|
-
|
|
13
|
-
if (!resp.ok) return '';
|
|
14
|
-
|
|
15
|
-
const html = await resp.text();
|
|
16
|
-
// Strip tags
|
|
17
|
-
let text = html
|
|
18
|
-
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
|
|
19
|
-
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
|
|
20
|
-
.replace(/<[^>]+>/g, ' ')
|
|
21
|
-
.replace(/\s+/g, ' ')
|
|
22
|
-
.trim();
|
|
23
|
-
|
|
24
|
-
if (text.length > 5000) text = text.slice(0, 5000) + '...';
|
|
25
|
-
return text;
|
|
26
|
-
} catch {
|
|
27
|
-
return '';
|
|
28
|
-
}
|
|
29
|
-
}
|
package/profiles/.gitkeep
DELETED
|
File without changes
|
package/profiles/sam-altman.md
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
# Sam Altman
|
|
2
|
-
|
|
3
|
-
> CEO of OpenAI, leading the development and deployment of artificial general intelligence
|
|
4
|
-
|
|
5
|
-
## Basic Info
|
|
6
|
-
- **Title:** CEO
|
|
7
|
-
- **Company:** OpenAI
|
|
8
|
-
- **Location:** San Francisco, USA
|
|
9
|
-
- **Industry:** Artificial Intelligence
|
|
10
|
-
|
|
11
|
-
## Links
|
|
12
|
-
- Twitter: https://x.com/sama
|
|
13
|
-
- LinkedIn: No public information found.
|
|
14
|
-
- Website: No public information found.
|
|
15
|
-
|
|
16
|
-
## Bio
|
|
17
|
-
Sam Altman is the CEO of OpenAI, the artificial intelligence research and deployment company behind ChatGPT and the GPT series of large language models. He is a prominent figure in the technology industry, known for his leadership in advancing AI capabilities while advocating for AI safety.
|
|
18
|
-
|
|
19
|
-
Before leading OpenAI, Altman served as President of Y Combinator (YC), one of Silicon Valley's most influential startup accelerators. In that role he funded and mentored numerous startups, building a reputation as a key connector and investor in the tech ecosystem.
|
|
20
|
-
|
|
21
|
-
Under his leadership, OpenAI has launched products including GPT-4, GPT-5, and ChatGPT, which have become some of the most widely used AI systems in the world. Altman has also navigated significant partnerships, including agreements with the U.S. Department of War for classified AI deployments.
|
|
22
|
-
|
|
23
|
-
## Key Interests & Topics
|
|
24
|
-
- Artificial intelligence safety and alignment
|
|
25
|
-
- Broad distribution of AI benefits
|
|
26
|
-
- AI product development and deployment (GPT series, ChatGPT)
|
|
27
|
-
|
|
28
|
-
## Notable Achievements
|
|
29
|
-
- Led OpenAI as CEO through the launch of ChatGPT and the GPT model series
|
|
30
|
-
- Served as President of Y Combinator, mentoring and funding hundreds of startups
|
|
31
|
-
|
|
32
|
-
## Recent Activity
|
|
33
|
-
- **Aug 2025:** Announced GPT-5 rollout updates, including doubled rate limits for ChatGPT Plus users and continued access to legacy models like GPT-4o
|
|
34
|
-
- **Feb 2026:** Announced an agreement with the Department of War to deploy OpenAI models on their classified network, emphasizing AI safety principles
|
|
35
|
-
- **Mar 2026:** Published an internal post detailing amendments to the DoW agreement, including language reinforcing constitutional and legal safeguards (Fourth Amendment, National Security Act, FISA)
|
|
36
|
-
|
|
37
|
-
## Background
|
|
38
|
-
Sam Altman was President of Y Combinator before becoming CEO of OpenAI. He has been active in the startup and technology community since at least the early 2010s, funding companies and serving as a mentor to entrepreneurs.
|
|
39
|
-
|
|
40
|
-
## Personal
|
|
41
|
-
No public information found.
|
|
42
|
-
|
|
43
|
-
## Notes
|
|
44
|
-
- Twitter handle @sama has been active since July 2006
|
|
45
|
-
- Note: multiple unrelated individuals share the "Sama" name on LinkedIn; none of those profiles correspond to Sam Altman
|
|
46
|
-
|
|
47
|
-
---
|
|
48
|
-
*Last updated: 2026-04-07*
|
|
49
|
-
*Sources: https://www.linkedin.com/in/andrew-a-sama-md-4bb65242*
|
package/skill/vip.md
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
You are a VIP Profile Manager assistant. You use the `vip` CLI tool at `/Users/iris/Library/Python/3.9/bin/vip` for data management, and YOU (Claude) provide the intelligence layer — synthesizing, analyzing, and enriching profiles.
|
|
2
|
-
|
|
3
|
-
The user's request is: $ARGUMENTS
|
|
4
|
-
|
|
5
|
-
## Architecture
|
|
6
|
-
|
|
7
|
-
- **`vip` CLI** = pure data tool (fetch, store, list, search, edit, delete). No AI inside.
|
|
8
|
-
- **You (Claude)** = the smart layer. You read raw data from profiles, synthesize structured content, answer questions, and write polished profiles.
|
|
9
|
-
|
|
10
|
-
## Available CLI Commands
|
|
11
|
-
|
|
12
|
-
```
|
|
13
|
-
vip add "Name" --company "Company" # Fetch public data, save raw profile
|
|
14
|
-
vip add <twitter_url> # Fetch from Twitter URL
|
|
15
|
-
vip list # List all profiles
|
|
16
|
-
vip show <name> # Display a profile
|
|
17
|
-
vip search <keyword> # Search across profiles
|
|
18
|
-
vip edit <name> --title/--company/--twitter/--linkedin/--note # Edit fields
|
|
19
|
-
vip update <name> # Re-fetch data
|
|
20
|
-
vip rm <name> -y # Delete a profile
|
|
21
|
-
vip raw <name> # Show raw gathered data
|
|
22
|
-
vip digest # Recent changes
|
|
23
|
-
vip monitor start/stop/status/run # Auto-monitoring
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
## Workflow for Adding a Person
|
|
27
|
-
|
|
28
|
-
1. Run `vip add "Name" --company "Company"` to gather raw data
|
|
29
|
-
2. Run `vip show <name>` to read the raw profile
|
|
30
|
-
3. **You synthesize**: Read the raw data, then rewrite the profile into a polished, structured format with:
|
|
31
|
-
- Proper summary line
|
|
32
|
-
- Filled-in Basic Info (title, company, location, industry)
|
|
33
|
-
- Bio (2-3 paragraphs)
|
|
34
|
-
- Key interests, achievements, recent activity
|
|
35
|
-
- Background, personal info
|
|
36
|
-
4. Write the polished profile back using the Edit tool to the file at `~/Projects/vip-crm/profiles/<slug>.md`
|
|
37
|
-
|
|
38
|
-
## Workflow for Answering Questions
|
|
39
|
-
|
|
40
|
-
- If the user asks about a person: run `vip show` or `vip search`, then answer based on the profile content
|
|
41
|
-
- If they ask to compare people: read multiple profiles and synthesize a comparison
|
|
42
|
-
- If they ask "who do I know in AI?": run `vip search "AI"` and summarize
|
|
43
|
-
|
|
44
|
-
## Profile Format (your output when synthesizing)
|
|
45
|
-
|
|
46
|
-
```markdown
|
|
47
|
-
# {Full Name}
|
|
48
|
-
|
|
49
|
-
> {One-line summary}
|
|
50
|
-
|
|
51
|
-
## Basic Info
|
|
52
|
-
- **Title:** {role}
|
|
53
|
-
- **Company:** {company}
|
|
54
|
-
- **Location:** {city, country}
|
|
55
|
-
- **Industry:** {domain}
|
|
56
|
-
|
|
57
|
-
## Links
|
|
58
|
-
- Twitter: {url}
|
|
59
|
-
- LinkedIn: {url}
|
|
60
|
-
- Website: {url}
|
|
61
|
-
|
|
62
|
-
## Bio
|
|
63
|
-
{2-3 paragraph biography}
|
|
64
|
-
|
|
65
|
-
## Key Interests & Topics
|
|
66
|
-
- {topic 1}
|
|
67
|
-
- {topic 2}
|
|
68
|
-
|
|
69
|
-
## Notable Achievements
|
|
70
|
-
- {achievement 1}
|
|
71
|
-
- {achievement 2}
|
|
72
|
-
|
|
73
|
-
## Recent Activity
|
|
74
|
-
- {recent news/posts}
|
|
75
|
-
|
|
76
|
-
## Background
|
|
77
|
-
{education, career history}
|
|
78
|
-
|
|
79
|
-
## Personal
|
|
80
|
-
{family, hobbies — only if publicly known}
|
|
81
|
-
|
|
82
|
-
## Notes
|
|
83
|
-
{any extra observations}
|
|
84
|
-
|
|
85
|
-
---
|
|
86
|
-
*Last updated: {date}*
|
|
87
|
-
*Sources: {urls}*
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
## Rules
|
|
91
|
-
|
|
92
|
-
- Always use the full path `/Users/iris/Library/Python/3.9/bin/vip` for CLI commands
|
|
93
|
-
- Only include information supported by the raw data — do not fabricate
|
|
94
|
-
- If a section has no data, write "No public information found."
|
|
95
|
-
- For batch operations (adding multiple people), process them one at a time
|
|
96
|
-
- After synthesizing a profile, write it directly to the .md file
|
package/tests/fetchers.test.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { describe, it } from 'node:test';
|
|
2
|
-
import assert from 'node:assert';
|
|
3
|
-
import { parseTweets } from '../lib/fetchers/twitter.js';
|
|
4
|
-
|
|
5
|
-
describe('parseTweets', () => {
|
|
6
|
-
it('filters separators and metadata', () => {
|
|
7
|
-
const output = 'Hello world tweet\n─────────\n2024-01-01\n5 likes\nAnother tweet here\n';
|
|
8
|
-
const tweets = parseTweets(output);
|
|
9
|
-
assert.ok(tweets.includes('Hello world tweet'));
|
|
10
|
-
assert.ok(tweets.includes('Another tweet here'));
|
|
11
|
-
assert.strictEqual(tweets.length, 2);
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it('empty input', () => {
|
|
15
|
-
assert.deepStrictEqual(parseTweets(''), []);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it('short lines ignored', () => {
|
|
19
|
-
assert.deepStrictEqual(parseTweets('hi\n'), []);
|
|
20
|
-
});
|
|
21
|
-
});
|
package/tests/monitor.test.js
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { describe, it, beforeEach } from 'node:test';
|
|
2
|
-
import assert from 'node:assert';
|
|
3
|
-
import { extractMetadata } from '../lib/monitor.js';
|
|
4
|
-
|
|
5
|
-
describe('extractMetadata', () => {
|
|
6
|
-
it('extracts twitter', () => {
|
|
7
|
-
const meta = extractMetadata('# Test\n\n- Twitter: https://twitter.com/testuser\n');
|
|
8
|
-
assert.strictEqual(meta.twitterHandle, 'testuser');
|
|
9
|
-
assert.strictEqual(meta.name, 'Test');
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
it('extracts x.com', () => {
|
|
13
|
-
const meta = extractMetadata('# Test\n\n- Twitter: https://x.com/testuser\n');
|
|
14
|
-
assert.strictEqual(meta.twitterHandle, 'testuser');
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('extracts linkedin', () => {
|
|
18
|
-
const meta = extractMetadata('# Test\n\n- LinkedIn: https://linkedin.com/in/test-user\n');
|
|
19
|
-
assert.strictEqual(meta.linkedinUrl, 'https://linkedin.com/in/test-user');
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('handles empty', () => {
|
|
23
|
-
const meta = extractMetadata('No links here');
|
|
24
|
-
assert.strictEqual(meta.twitterHandle, null);
|
|
25
|
-
assert.strictEqual(meta.linkedinUrl, null);
|
|
26
|
-
assert.strictEqual(meta.name, null);
|
|
27
|
-
});
|
|
28
|
-
});
|
package/tests/profile.test.js
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { describe, it, beforeEach } from 'node:test';
|
|
2
|
-
import assert from 'node:assert';
|
|
3
|
-
import fs from 'fs';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
import os from 'os';
|
|
6
|
-
import { slugify, saveProfile, loadProfile, listProfiles, searchProfiles, profileExists, deleteProfile } from '../lib/profile.js';
|
|
7
|
-
|
|
8
|
-
let tmpDir;
|
|
9
|
-
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'vip-test-'));
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
describe('slugify', () => {
|
|
15
|
-
it('basic name', () => assert.strictEqual(slugify('Sam Altman'), 'sam-altman'));
|
|
16
|
-
it('special chars', () => assert.strictEqual(slugify("Dr. John O'Brien"), 'dr-john-obrien'));
|
|
17
|
-
it('extra spaces', () => assert.strictEqual(slugify(' Elon Musk '), 'elon-musk'));
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
describe('save and load', () => {
|
|
21
|
-
it('round-trips content', () => {
|
|
22
|
-
saveProfile('Test Person', '# Test\nContent', tmpDir);
|
|
23
|
-
assert.strictEqual(loadProfile('Test Person', tmpDir), '# Test\nContent');
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('returns null for missing', () => {
|
|
27
|
-
assert.strictEqual(loadProfile('Nobody', tmpDir), null);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it('fuzzy matches', () => {
|
|
31
|
-
saveProfile('Sam Altman', '# Sam Altman\nContent', tmpDir);
|
|
32
|
-
const result = loadProfile('sam', tmpDir);
|
|
33
|
-
assert.ok(result?.includes('Sam Altman'));
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
describe('listProfiles', () => {
|
|
38
|
-
it('empty dir', () => assert.deepStrictEqual(listProfiles(tmpDir), []));
|
|
39
|
-
|
|
40
|
-
it('lists profiles', () => {
|
|
41
|
-
saveProfile('Sam Altman', '# Sam Altman\n\n> CEO of OpenAI', tmpDir);
|
|
42
|
-
saveProfile('Elon Musk', '# Elon Musk\n\n> CEO of Tesla', tmpDir);
|
|
43
|
-
const profiles = listProfiles(tmpDir);
|
|
44
|
-
assert.strictEqual(profiles.length, 2);
|
|
45
|
-
assert.ok(profiles.some(p => p.name === 'Sam Altman'));
|
|
46
|
-
assert.ok(profiles.some(p => p.name === 'Elon Musk'));
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('extracts summary', () => {
|
|
50
|
-
saveProfile('Test', '# Test\n\n> Summary here', tmpDir);
|
|
51
|
-
assert.strictEqual(listProfiles(tmpDir)[0].summary, 'Summary here');
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
describe('searchProfiles', () => {
|
|
56
|
-
it('finds matches', () => {
|
|
57
|
-
saveProfile('Sam', '# Sam\nOpenAI CEO', tmpDir);
|
|
58
|
-
saveProfile('Elon', '# Elon\nTesla CEO', tmpDir);
|
|
59
|
-
assert.strictEqual(searchProfiles('OpenAI', tmpDir).length, 1);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('case insensitive', () => {
|
|
63
|
-
saveProfile('Sam', '# Sam\nOpenAI', tmpDir);
|
|
64
|
-
assert.strictEqual(searchProfiles('openai', tmpDir).length, 1);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('no match', () => {
|
|
68
|
-
saveProfile('Sam', '# Sam\nOpenAI', tmpDir);
|
|
69
|
-
assert.strictEqual(searchProfiles('nonexistent', tmpDir).length, 0);
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
describe('profileExists / deleteProfile', () => {
|
|
74
|
-
it('exists check', () => {
|
|
75
|
-
saveProfile('Test', 'content', tmpDir);
|
|
76
|
-
assert.ok(profileExists('Test', tmpDir));
|
|
77
|
-
assert.ok(!profileExists('Nobody', tmpDir));
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('delete', () => {
|
|
81
|
-
saveProfile('Test', 'content', tmpDir);
|
|
82
|
-
assert.ok(deleteProfile('Test', tmpDir));
|
|
83
|
-
assert.ok(!profileExists('Test', tmpDir));
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it('delete missing returns false', () => {
|
|
87
|
-
assert.ok(!deleteProfile('Nobody', tmpDir));
|
|
88
|
-
});
|
|
89
|
-
});
|