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 +41 -1
- package/lib/card.js +32 -1
- package/lib/templates.js +10 -1
- 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
|
-
|
|
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
|