vipcare 0.6.3 โ†’ 0.7.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/card.js CHANGED
@@ -49,6 +49,25 @@ export function generateCards(profiles, outputPath) {
49
49
  if (noteLines.length) lastConnection = noteLines[noteLines.length - 1];
50
50
  }
51
51
 
52
+ // Extract Recent Activity section
53
+ const recentMatch = content.match(/## Recent Activity\n([\s\S]*?)(?=\n---|\n## [A-Z])/);
54
+ const recentActivity = recentMatch ? recentMatch[1].trim() : '';
55
+
56
+ // Extract Key Quotes section
57
+ const quotesMatch = content.match(/## Key Quotes\n([\s\S]*?)(?=\n---|\n## [A-Z])/);
58
+ const rawQuotes = quotesMatch ? quotesMatch[1].trim() : '';
59
+
60
+ // Extract Core Philosophy section
61
+ const philoMatch = content.match(/## Core Philosophy\n([\s\S]*?)(?=\n---|\n## [A-Z])/);
62
+ const rawPhilosophy = philoMatch ? philoMatch[1].trim() : '';
63
+
64
+ // Load raw Twitter data if available
65
+ const twitterRawFile = path.join(path.dirname(p.path), '.raw', p.slug, `twitter_${twitterHandle || p.slug}.md`);
66
+ let rawTweets = '';
67
+ if (fs.existsSync(twitterRawFile)) {
68
+ rawTweets = fs.readFileSync(twitterRawFile, 'utf-8');
69
+ }
70
+
52
71
  const data = extractVipData(content);
53
72
  if (!data) {
54
73
  const nameMatch = content.match(/^# (.+)$/m);
@@ -78,6 +97,10 @@ export function generateCards(profiles, outputPath) {
78
97
  updated: p.updated,
79
98
  annotations,
80
99
  last_connection: lastConnection,
100
+ recent_activity: recentActivity,
101
+ raw_quotes: rawQuotes,
102
+ raw_philosophy: rawPhilosophy,
103
+ raw_tweets: rawTweets,
81
104
  });
82
105
  continue;
83
106
  }
@@ -87,6 +110,10 @@ export function generateCards(profiles, outputPath) {
87
110
  data.twitter_handle = data.twitter_handle || twitterHandle;
88
111
  data.annotations = annotations;
89
112
  data.last_connection = lastConnection;
113
+ data.recent_activity = recentActivity;
114
+ data.raw_quotes = rawQuotes;
115
+ data.raw_philosophy = rawPhilosophy;
116
+ data.raw_tweets = rawTweets;
90
117
  cards.push(data);
91
118
  }
92
119
 
@@ -222,6 +249,16 @@ header .count { color: #94a3b8; font-size: 0.85em; }
222
249
  .panel p, .panel li { color: #475569; font-size: 0.85em; line-height: 1.6; }
223
250
  .panel ul { padding-left: 16px; }
224
251
  .panel blockquote { border-left: 3px solid #2563eb; padding: 4px 12px; margin: 6px 0; color: #475569; font-style: italic; font-size: 0.85em; }
252
+ .tabs { display: flex; border-bottom: 2px solid #e2e8f0; margin: 0 -24px 16px; padding: 0 24px; }
253
+ .tab { padding: 8px 16px; font-size: 0.85em; font-weight: 600; color: #94a3b8; cursor: pointer; border-bottom: 2px solid transparent; margin-bottom: -2px; transition: all 0.2s; }
254
+ .tab:hover { color: #475569; }
255
+ .tab.active { color: #2563eb; border-bottom-color: #2563eb; }
256
+ .tab-content { display: none; }
257
+ .tab-content.active { display: block; }
258
+ .tweet { background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 8px; padding: 10px 12px; margin: 8px 0; font-size: 0.8em; color: #475569; line-height: 1.5; }
259
+ .tweet-date { font-size: 0.7em; color: #94a3b8; margin-top: 4px; }
260
+ .activity-item { padding: 8px 0; border-bottom: 1px solid #f1f5f9; font-size: 0.85em; }
261
+ .activity-date { font-size: 0.75em; color: #94a3b8; }
225
262
  .radar { max-width: 100%; }
226
263
 
227
264
  /* Mobile */
@@ -346,13 +383,46 @@ function openPanel(index) {
346
383
  const panel = document.getElementById('panel-content');
347
384
  const avatarUrl = \`https://unavatar.io/twitter/\${card.twitter_handle || card.slug || 'unknown'}\`;
348
385
 
386
+ // Parse raw tweets into individual tweet objects
387
+ const tweets = [];
388
+ if (card.raw_tweets) {
389
+ const tweetBlocks = card.raw_tweets.split(/โ”€{5,}/);
390
+ for (const block of tweetBlocks) {
391
+ const textMatch = block.match(/:\n([\s\S]*?)(?=๐Ÿ“…|$)/);
392
+ const dateMatch = block.match(/๐Ÿ“…\s*(.+)/);
393
+ const linkMatch = block.match(/๐Ÿ”—\s*(https?:\/\/\S+)/);
394
+ if (textMatch && textMatch[1].trim()) {
395
+ tweets.push({
396
+ text: textMatch[1].trim().substring(0, 280),
397
+ date: dateMatch ? dateMatch[1].trim() : '',
398
+ link: linkMatch ? linkMatch[1] : '',
399
+ });
400
+ }
401
+ }
402
+ }
403
+
404
+ // Parse recent activity into items
405
+ const activityItems = [];
406
+ if (card.recent_activity) {
407
+ const lines = card.recent_activity.split('\n').filter(l => l.trim());
408
+ for (const line of lines) {
409
+ const dateMatch = line.match(/###?\s*(\d{4}-\d{2}-\d{2}|\w+\s+\d+)/);
410
+ if (dateMatch) {
411
+ activityItems.push({ date: dateMatch[1], text: '' });
412
+ } else if (activityItems.length) {
413
+ activityItems[activityItems.length - 1].text += line.replace(/^[-โ€ข*]\s*/, '').trim() + ' ';
414
+ } else {
415
+ activityItems.push({ date: '', text: line.replace(/^[-โ€ข*]\s*/, '').trim() });
416
+ }
417
+ }
418
+ }
419
+
349
420
  panel.innerHTML = \`
350
- <div style="display:flex;gap:14px;align-items:center;margin-bottom:16px">
421
+ <div style="display:flex;gap:14px;align-items:center;margin-bottom:12px">
351
422
  <img src="\${avatarUrl}" alt="" style="width:56px;height:56px;border-radius:50%;object-fit:cover;border:2px solid #e2e8f0" onerror="this.style.display='none'">
352
423
  <div>
353
424
  <div style="font-size:1.3em;font-weight:700;color:#0f172a">\${card.name}</div>
354
425
  <div style="font-size:0.85em;color:#64748b">\${card.title || ''}\${card.company ? ' @ ' + card.company : ''}</div>
355
- \${card.previous_role ? \`<div style="font-size:0.75em;color:#94a3b8">Previously: \${card.previous_role}</div>\` : ''}
356
426
  <div style="margin-top:4px">
357
427
  \${card.twitter_handle ? \`<a href="https://twitter.com/\${card.twitter_handle}" target="_blank" style="color:#2563eb;font-size:0.8em;text-decoration:none;margin-right:8px">@\${card.twitter_handle}</a>\` : ''}
358
428
  <span class="badge \${mbtiClass(card.mbti)}" title="\${card.mbti_reason || ''}">\${card.mbti || '?'}</span>
@@ -360,31 +430,45 @@ function openPanel(index) {
360
430
  </div>
361
431
  </div>
362
432
 
363
- \${card.latest_news ? \`<div class="news-chip" style="margin-bottom:12px">๐Ÿ“ฐ \${card.latest_news}</div>\` : ''}
433
+ <div class="tabs">
434
+ <div class="tab active" onclick="switchTab('recent',this)">Latest</div>
435
+ <div class="tab" onclick="switchTab('facts',this)">Facts & Approach</div>
436
+ </div>
437
+
438
+ <!-- RECENT TAB -->
439
+ <div class="tab-content active" id="tab-recent">
440
+ \${card.latest_news ? \`<div class="news-chip" style="margin-bottom:12px">๐Ÿ“ฐ \${card.latest_news}</div>\` : ''}
441
+
442
+ \${tweets.length ? \`<h2>Latest Tweets</h2>\${tweets.slice(0,5).map(t => \`<div class="tweet">\${t.text}\${t.date ? \`<div class="tweet-date">\${t.date}\${t.link ? \` ยท <a href="\${t.link}" target="_blank" style="color:#2563eb">link</a>\` : ''}</div>\` : ''}</div>\`).join('')}\` : ''}
443
+
444
+ \${activityItems.length ? \`<h2>Recent Moves</h2>\${activityItems.slice(0,8).map(a => \`<div class="activity-item">\${a.date ? \`<span class="activity-date">\${a.date}</span> ยท \` : ''}\${a.text.trim()}</div>\`).join('')}\` : ''}
364
445
 
365
- <h2>Focus & Goals</h2>
366
- \${card.current_focus ? \`<p>๐ŸŽฏ \${card.current_focus}</p>\` : ''}
367
- \${card.wants ? \`<p>๐Ÿ’Ž \${card.wants}</p>\` : ''}
446
+ \${card.key_quotes?.length ? \`<h2>In Their Words</h2>\${card.key_quotes.map(q => \`<blockquote>"\${q}"</blockquote>\`).join('')}\` : ''}
368
447
 
369
- \${card.philosophy?.length ? \`<h2>Philosophy</h2>\${card.philosophy.map(p => \`<blockquote>"\${p}"</blockquote>\`).join('')}\` : ''}
448
+ \${card.annotations?.length ? \`<h2>Your Notes</h2>\${card.annotations.map(a => \`<p style="font-size:0.8em;color:#64748b">๐Ÿ“ \${a}</p>\`).join('')}\` : ''}
449
+ </div>
370
450
 
371
- \${card.talking_points?.length ? \`<h2>Talking Points</h2><ul>\${card.talking_points.map(t => \`<li>\${t}</li>\`).join('')}</ul>\` : ''}
451
+ <!-- FACTS TAB -->
452
+ <div class="tab-content" id="tab-facts">
453
+ <h2>Focus & Goals</h2>
454
+ \${card.current_focus ? \`<p>๐ŸŽฏ \${card.current_focus}</p>\` : ''}
455
+ \${card.wants ? \`<p>๐Ÿ’Ž \${card.wants}</p>\` : ''}
372
456
 
373
- <h2>Approach</h2>
374
- \${card.last_connection ? \`<p>๐Ÿค <strong>Last:</strong> \${card.last_connection}</p>\` : '<p style="color:#94a3b8">No interactions yet</p>'}
375
- \${card.dos?.length || card.donts?.length ? \`<div class="do-dont">\${card.dos?.length ? \`<div class="do-box"><strong>โœ… Do</strong><br>\${card.dos.join('<br>')}</div>\` : ''}\${card.donts?.length ? \`<div class="dont-box"><strong>โŒ Don't</strong><br>\${card.donts.join('<br>')}</div>\` : ''}</div>\` : ''}
376
- \${card.gifts?.length ? \`<p style="font-size:0.85em">๐ŸŽ \${card.gifts.join(', ')}</p>\` : ''}
457
+ \${card.philosophy?.length ? \`<h2>Philosophy</h2>\${card.philosophy.map(p => \`<blockquote>"\${p}"</blockquote>\`).join('')}\` : ''}
377
458
 
378
- \${card.competition?.length ? \`<h2>Competition</h2><p style="font-size:0.85em">\${card.competition.join(', ')}</p>\` : ''}
459
+ \${card.talking_points?.length ? \`<h2>Talking Points</h2><ul>\${card.talking_points.map(t => \`<li>\${t}</li>\`).join('')}</ul>\` : ''}
379
460
 
380
- \${card.key_quotes?.length ? \`<h2>Quotes</h2>\${card.key_quotes.slice(0,4).map(q => \`<p style="font-size:0.8em;color:#475569;margin:3px 0">"\${q}"</p>\`).join('')}\` : ''}
461
+ <h2>Approach</h2>
462
+ \${card.last_connection ? \`<p>๐Ÿค <strong>Last:</strong> \${card.last_connection}</p>\` : '<p style="color:#94a3b8">No interactions yet</p>'}
463
+ \${card.dos?.length || card.donts?.length ? \`<div class="do-dont">\${card.dos?.length ? \`<div class="do-box"><strong>โœ… Do</strong><br>\${card.dos.join('<br>')}</div>\` : ''}\${card.donts?.length ? \`<div class="dont-box"><strong>โŒ Don't</strong><br>\${card.donts.join('<br>')}</div>\` : ''}</div>\` : ''}
464
+ \${card.gifts?.length ? \`<p style="font-size:0.85em">๐ŸŽ \${card.gifts.join(', ')}</p>\` : ''}
381
465
 
382
- <h2>Personality</h2>
383
- <p style="font-size:0.85em"><strong>MBTI:</strong> \${card.mbti || '?'}\${card.mbti_reason ? \` โ€” \${card.mbti_reason}\` : ''}</p>
384
- \${card.superpower ? \`<p style="font-size:0.85em">โšก \${card.superpower}</p>\` : ''}
385
- <div style="display:flex;justify-content:center;margin:12px 0">\${radarSvg(s, 220)}</div>
466
+ \${card.allies?.length || card.rivals?.length ? \`<h2>Relationships</h2>
467
+ \${card.allies?.length ? \`<div style="margin-bottom:8px">\${card.allies.map(a => \`<p style="font-size:0.85em;color:#166534">๐Ÿ‘ <strong>\${a.name || a}</strong>\${a.reason ? \` โ€” \${a.reason}\` : ''}</p>\`).join('')}</div>\` : ''}
468
+ \${card.rivals?.length ? \`<div>\${card.rivals.map(r => \`<p style="font-size:0.85em;color:#991b1b">๐Ÿ‘Ž <strong>\${r.name || r}</strong>\${r.reason ? \` โ€” \${r.reason}\` : ''}</p>\`).join('')}</div>\` : ''}
469
+ \` : ''}
386
470
 
387
- \${card.annotations?.length ? \`<h2>Your Notes</h2>\${card.annotations.map(a => \`<p style="font-size:0.8em;color:#64748b">๐Ÿ“ \${a}</p>\`).join('')}\` : ''}
471
+ </div>
388
472
 
389
473
  <div style="display:flex;gap:8px;margin-top:20px">
390
474
  \${card.slug ? \`<a href="\${card.slug}.html" style="flex:1;text-align:center;color:#2563eb;text-decoration:none;padding:8px;border:1px solid #2563eb;border-radius:6px;font-size:0.85em">Full Profile</a>\` : ''}
@@ -396,6 +480,13 @@ function openPanel(index) {
396
480
  document.getElementById('main').classList.add('panel-open');
397
481
  }
398
482
 
483
+ function switchTab(tabId, el) {
484
+ document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
485
+ document.querySelectorAll('.tab-content').forEach(t => t.classList.remove('active'));
486
+ el.classList.add('active');
487
+ document.getElementById('tab-' + tabId).classList.add('active');
488
+ }
489
+
399
490
  function closePanel() {
400
491
  document.getElementById('panel').classList.remove('open');
401
492
  document.getElementById('main').classList.remove('panel-open');
package/lib/templates.js CHANGED
@@ -65,8 +65,18 @@ synthesize an actionable profile focused on HOW to work with this person.
65
65
 
66
66
  ---
67
67
 
68
- ## Competition & Positioning
69
- {Who they see as competition, how they position themselves}
68
+ ## Relationships & Alliances
69
+
70
+ **Allies / People they like:**
71
+ - {Person 1} โ€” {why: shared values, mutual support, public praise, partnership, etc.}
72
+ - {Person 2} โ€” {why}
73
+
74
+ **Rivals / People they clash with:**
75
+ - {Person 1} โ€” {why: public disagreement, competitive tension, ideological difference, etc.}
76
+ - {Person 2} โ€” {why}
77
+
78
+ **Key relationships:**
79
+ - {Notable relationship with context โ€” mentor, investor, partner, adversary}
70
80
 
71
81
  ---
72
82
 
@@ -165,7 +175,8 @@ IMPORTANT: After the profile, output a JSON metadata block in EXACTLY this forma
165
175
  "dos": ["{do1}", "{do2}", "{do3}"],
166
176
  "donts": ["{dont1}", "{dont2}", "{dont3}"],
167
177
  "gifts": ["{gift idea 1}", "{gift idea 2}"],
168
- "competition": ["{competitor/rival 1}", "{competitor/rival 2}"],
178
+ "allies": [{"name": "{person}", "reason": "{why they like them}"}],
179
+ "rivals": [{"name": "{person}", "reason": "{why they clash}"}],
169
180
  "twitter_handle": "{handle without @}"
170
181
  }
171
182
  -->`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vipcare",
3
- "version": "0.6.3",
3
+ "version": "0.7.0",
4
4
  "description": "Auto-build VIP person profiles from Twitter/LinkedIn public data",
5
5
  "type": "module",
6
6
  "bin": {