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 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.title || ''}\${card.company ? ' @ ' + card.company : ''}</div>
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 : ''}\${card.location ? ' ยท ' + card.location : ''}</p>
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
- \${card.quote ? \`<div class="card-quote" style="margin:16px 0">"\${card.quote}"</div>\` : ''}
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.tags?.length ? \`<h2>Tags</h2><div class="tags">\${card.tags.map(t => \`<span class="tag">\${t}</span>\`).join('')}</div>\` : ''}
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}"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vipcare",
3
- "version": "0.3.30",
3
+ "version": "0.4.0",
4
4
  "description": "Auto-build VIP person profiles from Twitter/LinkedIn public data",
5
5
  "type": "module",
6
6
  "bin": {