vipcare 0.3.11 → 0.3.13
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 +73 -3
- package/lib/card.js +53 -0
- package/package.json +1 -1
package/bin/vip.js
CHANGED
|
@@ -474,16 +474,44 @@ program.command('youtube-search')
|
|
|
474
474
|
|
|
475
475
|
// --- card ---
|
|
476
476
|
program.command('card')
|
|
477
|
-
.description('Generate H5 baseball card page
|
|
477
|
+
.description('Generate and serve H5 baseball card page')
|
|
478
478
|
.option('-o, --output <path>', 'Output HTML file', 'web/index.html')
|
|
479
|
-
.
|
|
479
|
+
.option('-p, --port <port>', 'Server port', '3000')
|
|
480
|
+
.option('--no-serve', 'Only generate, do not start server')
|
|
481
|
+
.action(async (opts) => {
|
|
480
482
|
console.log(c.cyan('Generating baseball cards...'));
|
|
481
483
|
const profiles = listProfiles();
|
|
482
484
|
if (!profiles.length) { console.log(c.dim('No profiles. Use "vip add" first.')); return; }
|
|
483
485
|
|
|
484
486
|
const outputPath = generateCards(profiles, opts.output);
|
|
485
487
|
console.log(c.green(`Cards generated: ${outputPath}`));
|
|
486
|
-
|
|
488
|
+
|
|
489
|
+
if (opts.serve === false) {
|
|
490
|
+
console.log(c.dim(`Open in browser: open ${outputPath}`));
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const http = await import('http');
|
|
495
|
+
const port = parseInt(opts.port);
|
|
496
|
+
const dir = path.dirname(outputPath);
|
|
497
|
+
const file = path.basename(outputPath);
|
|
498
|
+
|
|
499
|
+
const server = http.createServer((req, res) => {
|
|
500
|
+
const filePath = req.url === '/' ? path.join(dir, file) : path.join(dir, req.url);
|
|
501
|
+
if (!fs.existsSync(filePath)) { res.writeHead(404); res.end('Not found'); return; }
|
|
502
|
+
const ext = path.extname(filePath);
|
|
503
|
+
const types = { '.html': 'text/html', '.css': 'text/css', '.js': 'text/javascript', '.json': 'application/json', '.png': 'image/png' };
|
|
504
|
+
res.writeHead(200, { 'Content-Type': types[ext] || 'text/plain' });
|
|
505
|
+
res.end(fs.readFileSync(filePath));
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
server.listen(port, () => {
|
|
509
|
+
const url = `http://localhost:${port}`;
|
|
510
|
+
console.log(c.green(`\nServer running at ${c.bold(url)}`));
|
|
511
|
+
console.log(c.dim('Press Ctrl+C to stop\n'));
|
|
512
|
+
// Auto-open in browser
|
|
513
|
+
try { execFileSync('open', [url], { stdio: 'ignore' }); } catch {}
|
|
514
|
+
});
|
|
487
515
|
});
|
|
488
516
|
|
|
489
517
|
// --- digest ---
|
|
@@ -830,6 +858,48 @@ program.command('init')
|
|
|
830
858
|
const { CONFIG_FILE: cfgPath } = await import('../lib/config.js');
|
|
831
859
|
console.log(c.green(`\nConfig saved to ${cfgPath}`));
|
|
832
860
|
|
|
861
|
+
// --- Check & install dependencies ---
|
|
862
|
+
console.log(c.bold(c.cyan('\nChecking dependencies...\n')));
|
|
863
|
+
|
|
864
|
+
const deps = [
|
|
865
|
+
{ name: 'bird', label: 'Bird CLI (Twitter data)', install: 'npm install -g @nickytonline/bird', check: () => checkTool('bird') },
|
|
866
|
+
{ name: 'ddgs', label: 'DDGS (web search)', install: 'pip install ddgs', check: () => checkTool('ddgs') || fs.existsSync(path.join(os.homedir(), 'Library', 'Python', '3.9', 'bin', 'ddgs')) },
|
|
867
|
+
{ name: 'claude', label: 'Claude Code CLI (AI synthesis)', install: 'npm install -g @anthropic-ai/claude-code', check: () => checkTool('claude') },
|
|
868
|
+
{ name: 'yt-dlp', label: 'yt-dlp (YouTube download)', install: 'pip install yt-dlp', check: () => checkTool('yt-dlp') },
|
|
869
|
+
{ name: 'whisper', label: 'Whisper (YouTube transcription)', install: 'pip install openai-whisper', check: () => checkTool('whisper') },
|
|
870
|
+
];
|
|
871
|
+
|
|
872
|
+
const missing = [];
|
|
873
|
+
for (const dep of deps) {
|
|
874
|
+
const ok = dep.check();
|
|
875
|
+
console.log(` ${ok ? c.green('✓') : c.red('✗')} ${dep.label}`);
|
|
876
|
+
if (!ok) missing.push(dep);
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
if (missing.length > 0) {
|
|
880
|
+
console.log(`\n${c.yellow(`${missing.length} optional dependency(ies) not found.`)}`);
|
|
881
|
+
const installAnswer = await rl.question(' Install missing dependencies? (Y/n) > ');
|
|
882
|
+
if (!installAnswer.trim() || installAnswer.trim().toLowerCase().startsWith('y')) {
|
|
883
|
+
for (const dep of missing) {
|
|
884
|
+
console.log(c.dim(` Installing ${dep.name}...`));
|
|
885
|
+
try {
|
|
886
|
+
const [cmd, ...args] = dep.install.split(' ');
|
|
887
|
+
execFileSync(cmd, args, { stdio: 'inherit', timeout: 120000 });
|
|
888
|
+
console.log(c.green(` ✓ ${dep.name} installed`));
|
|
889
|
+
} catch {
|
|
890
|
+
console.log(c.yellow(` ✗ Failed to install ${dep.name}. Install manually: ${dep.install}`));
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
} else {
|
|
894
|
+
console.log(c.dim(' Skipped. Install later with:'));
|
|
895
|
+
for (const dep of missing) {
|
|
896
|
+
console.log(c.dim(` ${dep.install}`));
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
} else {
|
|
900
|
+
console.log(c.green('\n All dependencies available!'));
|
|
901
|
+
}
|
|
902
|
+
|
|
833
903
|
// Install Claude Code skill by default
|
|
834
904
|
const skillSrc = new URL('../skill/vip.md', import.meta.url);
|
|
835
905
|
const skillDest = path.join(os.homedir(), '.claude', 'commands', 'vip.md');
|
package/lib/card.js
CHANGED
|
@@ -31,6 +31,7 @@ export function generateCards(profiles, outputPath = 'web/index.html') {
|
|
|
31
31
|
const industryMatch = content.match(/\*\*Industry:\*\*\s*(.+)/);
|
|
32
32
|
|
|
33
33
|
cards.push({
|
|
34
|
+
slug: p.slug,
|
|
34
35
|
name: nameMatch ? nameMatch[1] : p.name,
|
|
35
36
|
title: titleMatch ? titleMatch[1].trim() : '',
|
|
36
37
|
company: companyMatch ? companyMatch[1].trim() : '',
|
|
@@ -50,9 +51,20 @@ export function generateCards(profiles, outputPath = 'web/index.html') {
|
|
|
50
51
|
continue;
|
|
51
52
|
}
|
|
52
53
|
|
|
54
|
+
data.slug = p.slug;
|
|
53
55
|
cards.push(data);
|
|
54
56
|
}
|
|
55
57
|
|
|
58
|
+
// Generate individual profile pages
|
|
59
|
+
for (const p of profiles) {
|
|
60
|
+
const content = loadProfile(p.slug);
|
|
61
|
+
if (!content) continue;
|
|
62
|
+
const profileHtml = buildProfilePage(p.name, content);
|
|
63
|
+
const dir = path.dirname(outputPath);
|
|
64
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
65
|
+
fs.writeFileSync(path.join(dir, `${p.slug}.html`), profileHtml, 'utf-8');
|
|
66
|
+
}
|
|
67
|
+
|
|
56
68
|
const html = buildHtml(cards);
|
|
57
69
|
|
|
58
70
|
const dir = path.dirname(outputPath);
|
|
@@ -62,6 +74,45 @@ export function generateCards(profiles, outputPath = 'web/index.html') {
|
|
|
62
74
|
return path.resolve(outputPath);
|
|
63
75
|
}
|
|
64
76
|
|
|
77
|
+
function buildProfilePage(name, markdown) {
|
|
78
|
+
// Convert markdown to simple HTML
|
|
79
|
+
const htmlContent = markdown
|
|
80
|
+
.replace(/^# (.+)$/gm, '<h1>$1</h1>')
|
|
81
|
+
.replace(/^## (.+)$/gm, '<h2>$1</h2>')
|
|
82
|
+
.replace(/^> (.+)$/gm, '<blockquote>$1</blockquote>')
|
|
83
|
+
.replace(/^- \*\*(.+?):\*\* (.+)$/gm, '<p><strong>$1:</strong> $2</p>')
|
|
84
|
+
.replace(/^- (.+)$/gm, '<li>$1</li>')
|
|
85
|
+
.replace(/\n\n/g, '<br><br>');
|
|
86
|
+
|
|
87
|
+
return `<!DOCTYPE html>
|
|
88
|
+
<html lang="en">
|
|
89
|
+
<head>
|
|
90
|
+
<meta charset="UTF-8">
|
|
91
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
92
|
+
<title>${escapeHtml(name)} - VIPCare</title>
|
|
93
|
+
<style>
|
|
94
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
95
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #0f172a; color: #e2e8f0; padding: 24px; max-width: 800px; margin: 0 auto; line-height: 1.7; }
|
|
96
|
+
a { color: #38bdf8; text-decoration: none; }
|
|
97
|
+
a:hover { text-decoration: underline; }
|
|
98
|
+
.back { display: inline-block; margin-bottom: 20px; font-size: 0.9em; }
|
|
99
|
+
h1 { color: #38bdf8; font-size: 2em; margin: 16px 0 8px; }
|
|
100
|
+
h2 { color: #818cf8; font-size: 1.3em; margin: 24px 0 8px; border-bottom: 1px solid #334155; padding-bottom: 6px; }
|
|
101
|
+
blockquote { color: #fbbf24; font-style: italic; padding: 8px 16px; border-left: 3px solid #475569; margin: 8px 0; }
|
|
102
|
+
p, li { color: #cbd5e1; margin: 4px 0; }
|
|
103
|
+
strong { color: #f1f5f9; }
|
|
104
|
+
li { margin-left: 20px; }
|
|
105
|
+
hr { border: none; border-top: 1px solid #334155; margin: 24px 0; }
|
|
106
|
+
.meta { color: #64748b; font-size: 0.85em; }
|
|
107
|
+
</style>
|
|
108
|
+
</head>
|
|
109
|
+
<body>
|
|
110
|
+
<a href="index.html" class="back">← Back to all cards</a>
|
|
111
|
+
${htmlContent}
|
|
112
|
+
</body>
|
|
113
|
+
</html>`;
|
|
114
|
+
}
|
|
115
|
+
|
|
65
116
|
function escapeHtml(str) {
|
|
66
117
|
return String(str)
|
|
67
118
|
.replace(/&/g, '&')
|
|
@@ -286,6 +337,8 @@ function openModal(index) {
|
|
|
286
337
|
\${card.gifts?.length ? \`<p><strong>🎁 Gifts:</strong> \${card.gifts.join(', ')}</p>\` : ''}
|
|
287
338
|
|
|
288
339
|
\${card.tags?.length ? \`<h2>Tags</h2><div class="tags">\${card.tags.map(t => \`<span class="tag">\${t}</span>\`).join('')}</div>\` : ''}
|
|
340
|
+
|
|
341
|
+
\${card.slug ? \`<p style="margin-top:20px;text-align:center"><a href="\${card.slug}.html" style="color:#38bdf8;text-decoration:none;padding:8px 20px;border:1px solid #38bdf8;border-radius:8px;display:inline-block">View Full Profile →</a></p>\` : ''}
|
|
289
342
|
\`;
|
|
290
343
|
|
|
291
344
|
document.getElementById('modal').classList.add('active');
|