vipcare 0.5.0 → 0.5.1

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.
Files changed (3) hide show
  1. package/bin/vip.js +41 -1
  2. package/lib/card.js +32 -1
  3. package/package.json +1 -1
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vipcare",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Auto-build VIP person profiles from Twitter/LinkedIn public data",
5
5
  "type": "module",
6
6
  "bin": {