vipcare 0.3.30 โ 0.4.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/bin/vip.js +42 -0
- package/lib/card.js +44 -20
- package/lib/templates.js +6 -0
- package/package.json +1 -1
package/bin/vip.js
CHANGED
|
@@ -847,6 +847,48 @@ program.command('reset')
|
|
|
847
847
|
console.log(c.green('\nAll data cleared. Run "vip init" to start fresh.'));
|
|
848
848
|
});
|
|
849
849
|
|
|
850
|
+
// --- annotate ---
|
|
851
|
+
program.command('annotate')
|
|
852
|
+
.description('Add a personal annotation/comment to a profile')
|
|
853
|
+
.argument('<name>', 'Profile name')
|
|
854
|
+
.argument('<note...>', 'Your annotation')
|
|
855
|
+
.action((name, noteParts) => {
|
|
856
|
+
const content = loadProfile(name);
|
|
857
|
+
if (!content) { console.error(c.red(`Profile not found: ${name}`)); process.exit(1); }
|
|
858
|
+
|
|
859
|
+
const note = noteParts.join(' ');
|
|
860
|
+
const personSlug = slugify(name);
|
|
861
|
+
const rawDir = path.join(getProfilesDir(), '.raw', personSlug);
|
|
862
|
+
fs.mkdirSync(rawDir, { recursive: true });
|
|
863
|
+
|
|
864
|
+
const annotationFile = path.join(rawDir, 'user_annotations.md');
|
|
865
|
+
const timestamp = new Date().toISOString().slice(0, 16).replace('T', ' ');
|
|
866
|
+
const entry = `\n- [${timestamp}] ${note}\n`;
|
|
867
|
+
|
|
868
|
+
if (fs.existsSync(annotationFile)) {
|
|
869
|
+
fs.appendFileSync(annotationFile, entry);
|
|
870
|
+
} else {
|
|
871
|
+
fs.writeFileSync(annotationFile, `# User Annotations for ${name}\n\nPersonal notes, observations, and meeting history.\n${entry}`);
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// Also add to profile's Notes section
|
|
875
|
+
const updatedContent = loadProfile(name);
|
|
876
|
+
if (updatedContent) {
|
|
877
|
+
let newContent;
|
|
878
|
+
if (updatedContent.includes('## Notes')) {
|
|
879
|
+
newContent = updatedContent.replace('## Notes\n', `## Notes\n- [${timestamp}] ${note}\n`);
|
|
880
|
+
} else if (updatedContent.includes('\n---\n')) {
|
|
881
|
+
newContent = updatedContent.replace('\n---\n', `\n## Notes\n- [${timestamp}] ${note}\n\n---\n`);
|
|
882
|
+
} else {
|
|
883
|
+
newContent = updatedContent.trimEnd() + `\n\n## Notes\n- [${timestamp}] ${note}\n`;
|
|
884
|
+
}
|
|
885
|
+
saveProfile(name, newContent);
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
console.log(c.green(`Annotation added to ${name}`));
|
|
889
|
+
console.log(c.dim(` Saved to: ${annotationFile}`));
|
|
890
|
+
});
|
|
891
|
+
|
|
850
892
|
// --- upgrade ---
|
|
851
893
|
program.command('upgrade')
|
|
852
894
|
.description('Update vipcare to the latest version')
|
package/lib/card.js
CHANGED
|
@@ -28,6 +28,24 @@ export function generateCards(profiles, outputPath) {
|
|
|
28
28
|
const twMatch = content.match(/twitter\.com\/(\w+)/i) || content.match(/x\.com\/(\w+)/i);
|
|
29
29
|
const twitterHandle = twMatch ? twMatch[1] : null;
|
|
30
30
|
|
|
31
|
+
// Load user annotations
|
|
32
|
+
const annotationFile = path.join(path.dirname(p.path), '.raw', p.slug, 'user_annotations.md');
|
|
33
|
+
let annotations = [];
|
|
34
|
+
if (fs.existsSync(annotationFile)) {
|
|
35
|
+
const annoContent = fs.readFileSync(annotationFile, 'utf-8');
|
|
36
|
+
annotations = annoContent.split('\n')
|
|
37
|
+
.filter(line => line.match(/^- \[/))
|
|
38
|
+
.map(line => line.replace(/^- /, '').trim());
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Extract last connection from Notes section
|
|
42
|
+
const notesMatch = content.match(/## Notes\n([\s\S]*?)(?=\n##|\n---|$)/);
|
|
43
|
+
let lastConnection = null;
|
|
44
|
+
if (notesMatch) {
|
|
45
|
+
const noteLines = notesMatch[1].split('\n').filter(l => l.startsWith('- ')).map(l => l.replace(/^- /, ''));
|
|
46
|
+
if (noteLines.length) lastConnection = noteLines[noteLines.length - 1];
|
|
47
|
+
}
|
|
48
|
+
|
|
31
49
|
const data = extractVipData(content);
|
|
32
50
|
if (!data) {
|
|
33
51
|
const nameMatch = content.match(/^# (.+)$/m);
|
|
@@ -54,12 +72,16 @@ export function generateCards(profiles, outputPath) {
|
|
|
54
72
|
expertise: [],
|
|
55
73
|
superpower: '',
|
|
56
74
|
quote: summaryMatch ? summaryMatch[1] : (p.summary || ''),
|
|
75
|
+
annotations,
|
|
76
|
+
last_connection: lastConnection,
|
|
57
77
|
});
|
|
58
78
|
continue;
|
|
59
79
|
}
|
|
60
80
|
|
|
61
81
|
data.slug = p.slug;
|
|
62
82
|
data.twitter_handle = data.twitter_handle || twitterHandle;
|
|
83
|
+
data.annotations = annotations;
|
|
84
|
+
data.last_connection = lastConnection;
|
|
63
85
|
cards.push(data);
|
|
64
86
|
}
|
|
65
87
|
|
|
@@ -293,10 +315,6 @@ function radarSvg(scores, size = 200) {
|
|
|
293
315
|
}
|
|
294
316
|
|
|
295
317
|
function renderCard(card, index) {
|
|
296
|
-
const scores = card.scores || {};
|
|
297
|
-
const radar = radarSvg(scores);
|
|
298
|
-
|
|
299
|
-
const twitterUrl = card.twitter || (card.slug ? \`https://unavatar.io/twitter/\${card.slug}\` : '');
|
|
300
318
|
const avatarUrl = \`https://unavatar.io/twitter/\${card.twitter_handle || card.slug || 'unknown'}\`;
|
|
301
319
|
|
|
302
320
|
return \`
|
|
@@ -306,20 +324,17 @@ function renderCard(card, index) {
|
|
|
306
324
|
<img src="\${avatarUrl}" alt="" style="width:48px;height:48px;border-radius:50%;object-fit:cover;border:2px solid #e2e8f0" onerror="this.style.display='none'">
|
|
307
325
|
<div>
|
|
308
326
|
<div class="card-name">\${card.name || 'Unknown'}</div>
|
|
309
|
-
<div class="card-role">\${card.
|
|
327
|
+
<div class="card-role">\${card.one_liner || card.title || ''}</div>
|
|
310
328
|
</div>
|
|
311
329
|
</div>
|
|
312
|
-
<span class="badge badge-mbti">\${card.mbti || '?'}</span>
|
|
313
|
-
</div>
|
|
314
|
-
\${card.quote ? \`<div class="card-quote">"\${card.quote.slice(0, 120)}\${card.quote.length > 120 ? '...' : ''}"</div>\` : ''}
|
|
315
|
-
<div class="radar-container">\${radar}</div>
|
|
316
|
-
\${card.superpower ? \`<div class="superpower">โก \${card.superpower}</div>\` : ''}
|
|
317
|
-
\${card.tags?.length ? \`<div class="tags">\${card.tags.map(t => \`<span class="tag">\${t}</span>\`).join('')}</div>\` : ''}
|
|
318
|
-
<div class="tips">
|
|
319
|
-
\${card.icebreakers?.length ? \`<div class="tip-row"><span class="tip-icon">๐ก</span><span class="tip-label">Icebreaker</span>\${card.icebreakers[0]}</div>\` : ''}
|
|
320
|
-
\${card.dos?.length ? \`<div class="tip-row"><span class="tip-icon">โ
</span><span class="tip-label">Do</span>\${card.dos[0]}</div>\` : ''}
|
|
321
|
-
\${card.donts?.length ? \`<div class="tip-row"><span class="tip-icon">โ</span><span class="tip-label">Don't</span>\${card.donts[0]}</div>\` : ''}
|
|
330
|
+
<span class="badge badge-mbti" title="\${card.mbti_reason || ''}">\${card.mbti || '?'}</span>
|
|
322
331
|
</div>
|
|
332
|
+
\${card.latest_news ? \`<div style="background:#f0f9ff;padding:8px 12px;border-radius:8px;font-size:0.8em;color:#1e40af;margin:8px 0">๐ฐ \${card.latest_news.slice(0, 120)}\${card.latest_news.length > 120 ? '...' : ''}</div>\` : ''}
|
|
333
|
+
\${card.current_focus ? \`<div style="font-size:0.85em;color:#475569;margin:6px 0"><strong>Focus:</strong> \${card.current_focus}</div>\` : ''}
|
|
334
|
+
\${card.wants ? \`<div style="font-size:0.85em;color:#475569;margin:6px 0"><strong>Wants:</strong> \${card.wants}</div>\` : ''}
|
|
335
|
+
\${card.last_connection ? \`<div style="font-size:0.8em;color:#2563eb;margin:6px 0">๐ค \${card.last_connection}</div>\` : \`<div style="font-size:0.8em;color:#94a3b8;margin:6px 0">๐ก \${card.icebreakers?.[0] || 'No connection yet'}</div>\`}
|
|
336
|
+
\${card.talking_points?.length ? \`<div style="margin:8px 0"><div style="font-size:0.75em;color:#94a3b8;text-transform:uppercase;margin-bottom:4px">Talking Points</div>\${card.talking_points.slice(0,2).map(t => \`<div style="font-size:0.8em;color:#475569;padding:2px 0">โข \${t}</div>\`).join('')}</div>\` : ''}
|
|
337
|
+
\${card.annotations?.length ? \`<div style="border-top:1px solid #e2e8f0;margin-top:8px;padding-top:8px;font-size:0.75em;color:#64748b">๐ \${card.annotations[card.annotations.length-1]}</div>\` : ''}
|
|
323
338
|
</div>\`;
|
|
324
339
|
}
|
|
325
340
|
|
|
@@ -336,26 +351,35 @@ function openModal(index) {
|
|
|
336
351
|
<img src="\${avatarUrl}" alt="" style="width:64px;height:64px;border-radius:50%;object-fit:cover;border:2px solid #e2e8f0" onerror="this.style.display='none'">
|
|
337
352
|
<div>
|
|
338
353
|
<h1 style="color:#2563eb;margin-bottom:4px;font-size:1.5em">\${card.name}</h1>
|
|
339
|
-
<p style="color:#64748b">\${card.title || ''}\${card.company ? ' @ ' + card.company : ''}
|
|
354
|
+
<p style="color:#64748b">\${card.one_liner || card.title || ''}\${card.company ? ' @ ' + card.company : ''}</p>
|
|
340
355
|
\${card.twitter_handle ? \`<a href="https://twitter.com/\${card.twitter_handle}" target="_blank" style="color:#2563eb;font-size:0.85em;text-decoration:none">@\${card.twitter_handle}</a>\` : ''}
|
|
341
356
|
</div>
|
|
342
357
|
</div>
|
|
343
|
-
|
|
358
|
+
|
|
359
|
+
\${card.latest_news ? \`<div style="background:#f0f9ff;padding:10px 14px;border-radius:8px;font-size:0.85em;color:#1e40af;margin:12px 0">๐ฐ <strong>Latest:</strong> \${card.latest_news}</div>\` : ''}
|
|
360
|
+
|
|
361
|
+
<h2>Current Focus & Goals</h2>
|
|
362
|
+
\${card.current_focus ? \`<p><strong>๐ฏ Focus:</strong> \${card.current_focus}</p>\` : ''}
|
|
363
|
+
\${card.wants ? \`<p><strong>๐ Wants:</strong> \${card.wants}</p>\` : ''}
|
|
364
|
+
|
|
365
|
+
\${card.talking_points?.length ? \`<h2>Talking Points</h2><ul>\${card.talking_points.map(t => \`<li>\${t}</li>\`).join('')}</ul>\` : ''}
|
|
344
366
|
|
|
345
367
|
<h2>Personality</h2>
|
|
346
|
-
<p><strong>MBTI:</strong> \${card.mbti || '?'}</p>
|
|
368
|
+
<p><strong>MBTI:</strong> <span title="\${card.mbti_reason || ''}" style="cursor:help;border-bottom:1px dashed #94a3b8">\${card.mbti || '?'}</span>\${card.mbti_reason ? \` โ \${card.mbti_reason}\` : ''}</p>
|
|
347
369
|
<div style="display:flex;justify-content:center;margin:16px 0">\${radarSvg(s, 260)}</div>
|
|
348
370
|
|
|
349
|
-
\${card.expertise?.length ? \`<h2>Expertise</h2><ul>\${card.expertise.map(e => \`<li>\${e}</li>\`).join('')}</ul>\` : ''}
|
|
350
371
|
\${card.superpower ? \`<p><strong>โก Superpower:</strong> \${card.superpower}</p>\` : ''}
|
|
351
372
|
|
|
352
373
|
<h2>How to Work With Them</h2>
|
|
374
|
+
\${card.last_connection ? \`<p><strong>๐ค Last connection:</strong> \${card.last_connection}</p>\` : ''}
|
|
353
375
|
\${card.icebreakers?.length ? \`<p><strong>๐ก Icebreakers:</strong> \${card.icebreakers.join(', ')}</p>\` : ''}
|
|
354
376
|
\${card.dos?.length ? \`<p><strong>โ
Do:</strong> \${card.dos.join(' ยท ')}</p>\` : ''}
|
|
355
377
|
\${card.donts?.length ? \`<p><strong>โ Don't:</strong> \${card.donts.join(' ยท ')}</p>\` : ''}
|
|
356
378
|
\${card.gifts?.length ? \`<p><strong>๐ Gifts:</strong> \${card.gifts.join(', ')}</p>\` : ''}
|
|
357
379
|
|
|
358
|
-
\${card.
|
|
380
|
+
\${card.annotations?.length ? \`<h2>Your Notes</h2>\${card.annotations.map(a => \`<p style="font-size:0.85em;color:#64748b">๐ \${a}</p>\`).join('')}\` : ''}
|
|
381
|
+
|
|
382
|
+
\${card.tags?.length ? \`<div class="tags" style="margin-top:12px">\${card.tags.map(t => \`<span class="tag">\${t}</span>\`).join('')}</div>\` : ''}
|
|
359
383
|
|
|
360
384
|
\${card.slug ? \`<p style="margin-top:20px;text-align:center"><a href="\${card.slug}.html" style="color:#2563eb;text-decoration:none;padding:8px 20px;border:1px solid #2563eb;border-radius:8px;display:inline-block">View Full Profile โ</a></p>\` : ''}
|
|
361
385
|
\`;
|
package/lib/templates.js
CHANGED
|
@@ -109,6 +109,7 @@ IMPORTANT: After the Markdown profile above, output a JSON metadata block in EXA
|
|
|
109
109
|
"industry": "{Industry}",
|
|
110
110
|
"disc": "{D/I/S/C primary type letter}",
|
|
111
111
|
"mbti": "{4-letter MBTI}",
|
|
112
|
+
"mbti_reason": "{1-2 sentence explanation of why this MBTI type}",
|
|
112
113
|
"scores": {
|
|
113
114
|
"openness": {1-5},
|
|
114
115
|
"conscientiousness": {1-5},
|
|
@@ -123,6 +124,11 @@ IMPORTANT: After the Markdown profile above, output a JSON metadata block in EXA
|
|
|
123
124
|
},
|
|
124
125
|
"expertise": ["{area1}", "{area2}", "{area3}"],
|
|
125
126
|
"superpower": "{their unique edge in one phrase}",
|
|
127
|
+
"one_liner": "{who is this person in <=10 words}",
|
|
128
|
+
"current_focus": "{what they are focused on RIGHT NOW based on latest data}",
|
|
129
|
+
"wants": "{what they are trying to achieve or looking for}",
|
|
130
|
+
"latest_news": "{most recent notable thing from the data, with date if available}",
|
|
131
|
+
"talking_points": ["{actionable topic 1}", "{actionable topic 2}", "{actionable topic 3}"],
|
|
126
132
|
"tags": ["{tag1}", "{tag2}", "{tag3}", "{tag4}"],
|
|
127
133
|
"icebreakers": ["{topic1}", "{topic2}", "{topic3}"],
|
|
128
134
|
"dos": ["{do1}", "{do2}", "{do3}"],
|