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/lib/templates.js CHANGED
@@ -1,16 +1,25 @@
1
- export const PROFILE_SYSTEM_PROMPT = `You are a research assistant building a VIP contact profile.
2
- Given the raw data below from public sources (tweets, LinkedIn snippets, web search results), \
3
- synthesize a clean profile in the exact Markdown format provided.
4
-
5
- Rules:
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 or hallucinate details
9
- - Write in a neutral, professional tone
10
- - For the summary line, write a concise one-line description of who this person is
11
- - Output ONLY the Markdown profile, no extra commentary
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.1.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()">&times;</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 || '?'} &nbsp; <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>
@@ -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
@@ -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
@@ -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
- });
@@ -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
- });
@@ -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
- });