vipcare 0.5.0 → 0.5.2

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
@@ -522,7 +522,47 @@ program.command('card')
522
522
  const dir = path.dirname(outputPath);
523
523
  const file = path.basename(outputPath);
524
524
 
525
- const server = http.createServer((req, res) => {
525
+ const server = http.createServer(async (req, res) => {
526
+ // API: regenerate a profile
527
+ if (req.url.startsWith('/api/regenerate/')) {
528
+ const slug = req.url.replace('/api/regenerate/', '').replace(/\//g, '');
529
+ res.setHeader('Content-Type', 'application/json');
530
+ res.setHeader('Access-Control-Allow-Origin', '*');
531
+
532
+ console.log(c.cyan(` Regenerating ${slug}...`));
533
+ try {
534
+ const content = loadProfile(slug);
535
+ if (!content) { res.writeHead(404); res.end(JSON.stringify({ error: 'Profile not found' })); return; }
536
+
537
+ const { extractMetadata } = await import('../lib/monitor.js');
538
+ const meta = extractMetadata(content);
539
+ const personName = meta.name || slug;
540
+
541
+ const person = resolveFromName(personName);
542
+ if (meta.twitterHandle) person.twitterHandle = person.twitterHandle || meta.twitterHandle;
543
+ if (meta.linkedinUrl) person.linkedinUrl = person.linkedinUrl || meta.linkedinUrl;
544
+
545
+ const [rawData, sources] = gatherData(person);
546
+ if (!rawData.trim()) { res.writeHead(400); res.end(JSON.stringify({ error: 'No data found' })); return; }
547
+
548
+ const profile = await synthesizeProfile(rawData, sources);
549
+ saveProfile(personName, profile);
550
+
551
+ // Regenerate cards
552
+ regenerate();
553
+
554
+ console.log(c.green(` ${personName} regenerated.`));
555
+ res.writeHead(200);
556
+ res.end(JSON.stringify({ ok: true, name: personName }));
557
+ } catch (e) {
558
+ console.error(c.red(` Error: ${e.message}`));
559
+ res.writeHead(500);
560
+ res.end(JSON.stringify({ error: e.message }));
561
+ }
562
+ return;
563
+ }
564
+
565
+ // Static files
526
566
  const filePath = req.url === '/' ? path.join(dir, file) : path.join(dir, req.url);
527
567
  if (!fs.existsSync(filePath)) { res.writeHead(404); res.end('Not found'); return; }
528
568
  const ext = path.extname(filePath);
package/lib/card.js CHANGED
@@ -335,6 +335,7 @@ function renderCard(card, index) {
335
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
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
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>\` : ''}
338
+ <button onclick="event.stopPropagation();regenerateProfile('\${card.slug}',this)" style="margin-top:10px;width:100%;padding:6px;border:1px solid #e2e8f0;border-radius:6px;background:#f8fafc;color:#64748b;font-size:0.75em;cursor:pointer;transition:all 0.2s" onmouseover="this.style.borderColor='#2563eb';this.style.color='#2563eb'" onmouseout="this.style.borderColor='#e2e8f0';this.style.color='#64748b'">🔄 Update Profile</button>
338
339
  </div>\`;
339
340
  }
340
341
 
@@ -386,7 +387,10 @@ function openModal(index) {
386
387
 
387
388
  \${card.tags?.length ? \`<div class="tags" style="margin-top:12px">\${card.tags.map(t => \`<span class="tag">\${t}</span>\`).join('')}</div>\` : ''}
388
389
 
389
- \${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>\` : ''}
390
+ <div style="display:flex;gap:10px;justify-content:center;margin-top:20px">
391
+ \${card.slug ? \`<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>\` : ''}
392
+ \${card.slug ? \`<button onclick="regenerateProfile('\${card.slug}',this)" style="padding:8px 20px;border:1px solid #e2e8f0;border-radius:8px;background:#f8fafc;color:#64748b;cursor:pointer;font-size:0.9em">🔄 Update</button>\` : ''}
393
+ </div>
390
394
  \`;
391
395
 
392
396
  document.getElementById('modal').classList.add('active');
@@ -398,6 +402,33 @@ function closeModal() {
398
402
 
399
403
  document.addEventListener('keydown', e => { if (e.key === 'Escape') closeModal(); });
400
404
 
405
+ async function regenerateProfile(slug, btn) {
406
+ const originalText = btn.textContent;
407
+ btn.textContent = '⏳ Updating...';
408
+ btn.disabled = true;
409
+ btn.style.opacity = '0.6';
410
+
411
+ try {
412
+ const resp = await fetch(\`/api/regenerate/\${slug}\`);
413
+ const data = await resp.json();
414
+
415
+ if (resp.ok) {
416
+ btn.textContent = '✅ Updated!';
417
+ btn.style.color = '#16a34a';
418
+ // Reload page after a short delay to show new data
419
+ setTimeout(() => location.reload(), 1500);
420
+ } else {
421
+ btn.textContent = '❌ ' + (data.error || 'Failed');
422
+ btn.style.color = '#dc2626';
423
+ setTimeout(() => { btn.textContent = originalText; btn.disabled = false; btn.style.opacity = '1'; btn.style.color = ''; }, 3000);
424
+ }
425
+ } catch (e) {
426
+ btn.textContent = '❌ Error';
427
+ btn.style.color = '#dc2626';
428
+ setTimeout(() => { btn.textContent = originalText; btn.disabled = false; btn.style.opacity = '1'; btn.style.color = ''; }, 3000);
429
+ }
430
+ }
431
+
401
432
  // Render
402
433
  const grid = document.getElementById('grid');
403
434
  grid.innerHTML = cards.map((card, i) => renderCard(card, i)).join('');
package/lib/templates.js CHANGED
@@ -4,13 +4,22 @@ synthesize an actionable profile focused on HOW to work with this person.
4
4
 
5
5
  ## Rules
6
6
  - Only include information supported by the provided data
7
- - For personality/MBTI inferences, note these are estimates based on public behavior
8
7
  - Focus on ACTIONABLE intelligence: what they care about NOW, what they want, how to approach them
9
8
  - Include their own words whenever possible (direct quotes)
10
9
  - If user annotations exist, incorporate them into interaction history
11
10
  - If a section has no data, write "No data available."
12
11
  - Output ONLY the Markdown profile followed by the JSON metadata block
13
12
 
13
+ ## MBTI/Personality Rules (CRITICAL)
14
+ - Do NOT default to INTJ. Most people are NOT INTJ.
15
+ - Analyze ACTUAL behavior from the data: How do they communicate? Are they verbose or terse? Do they use humor? Do they share personal feelings? Do they focus on big picture or details? Do they seek consensus or decide alone?
16
+ - E vs I: Frequent public engagement, many tweets, social energy = E. Rare posts, private, reflective = I.
17
+ - S vs N: Focus on concrete details, practical matters = S. Focus on vision, future possibilities = N.
18
+ - T vs F: Decisions based on logic/data = T. Decisions mentioning people/values/impact = F.
19
+ - J vs P: Structured, plans, deadlines = J. Flexible, spontaneous, exploratory = P.
20
+ - Each letter MUST have a specific evidence citation from the data. If insufficient data, write "Insufficient data to estimate" instead of guessing.
21
+ - DISC type must also cite specific behavioral evidence.
22
+
14
23
  ## Output Format
15
24
 
16
25
  # {Full Name} — Profile
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vipcare",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Auto-build VIP person profiles from Twitter/LinkedIn public data",
5
5
  "type": "module",
6
6
  "bin": {