vibebusiness 1.2.65 → 1.2.68
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/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/app-build-manifest.json +19 -19
- package/.next/standalone/.next/app-path-routes-manifest.json +1 -1
- package/.next/standalone/.next/build-manifest.json +2 -2
- package/.next/standalone/.next/prerender-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +1 -1
- package/.next/standalone/.next/server/app/api/investor-updates/generate/route.js +1 -1
- package/.next/standalone/.next/server/app/api/investor-updates/generate/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/social/[id]/publish/route.js +1 -1
- package/.next/standalone/.next/server/app/api/social/[id]/publish/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/goals/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/goals/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/hypotheses/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/hypotheses/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/ideas/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/roadmap/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/roadmap/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings.html +1 -1
- package/.next/standalone/.next/server/app/settings.rsc +1 -1
- package/.next/standalone/.next/server/app/social/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/social.html +1 -1
- package/.next/standalone/.next/server/app/social.rsc +1 -1
- package/.next/standalone/.next/server/app/updates/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/updates/new/page.js +1 -1
- package/.next/standalone/.next/server/app/updates/new/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/updates/new.html +1 -1
- package/.next/standalone/.next/server/app/updates/new.rsc +2 -2
- package/.next/standalone/.next/server/app/updates/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app-paths-manifest.json +13 -13
- package/.next/standalone/.next/server/chunks/{7599.js → 3794.js} +33 -25
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/data/business-context.json +1 -1
- package/.next/standalone/data/goals.json +93 -12
- package/.next/standalone/data/ideas.json +58 -17
- package/.next/standalone/data/implementations.json +53 -0
- package/.next/standalone/data/social.json +49 -1
- package/.next/standalone/package.json +1 -1
- package/.next/standalone/scripts/investor-update.ts +249 -12
- package/.next/standalone/scripts/skills/social-media.ts +239 -34
- package/.next/static/chunks/app/updates/new/page-dcc67ffca587dcc2.js +1 -0
- package/dist/scripts/heartbeat.js +205 -30
- package/dist/scripts/social-routine.js +205 -30
- package/package.json +1 -1
- package/.next/standalone/.next/server/chunks/1685.js +0 -6
- package/.next/standalone/.next/server/chunks/7564.js +0 -81
- package/.next/standalone/scripts/skills/investor-update.ts +0 -476
- package/.next/static/chunks/app/updates/new/page-c518fba0d9ffae98.js +0 -1
- /package/.next/static/{yXi8K8u58qskYid0oHU5f → Gsf15cMKd-7htpbN5eZHd}/_buildManifest.js +0 -0
- /package/.next/static/{yXi8K8u58qskYid0oHU5f → Gsf15cMKd-7htpbN5eZHd}/_ssgManifest.js +0 -0
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
import * as fs from 'fs';
|
|
15
15
|
import * as path from 'path';
|
|
16
16
|
import { execSync } from 'child_process';
|
|
17
|
+
import positioning from '../data/positioning.json' assert { type: 'json' };
|
|
18
|
+
import { fetchNpmStats, type NpmStats } from './lib/data-sources/npm-stats.js';
|
|
19
|
+
import { fetchGitHubStats, type GitHubStats } from './lib/data-sources/github-stats.js';
|
|
17
20
|
|
|
18
21
|
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
19
22
|
|
|
@@ -57,13 +60,15 @@ interface KPI {
|
|
|
57
60
|
id: string;
|
|
58
61
|
name: string;
|
|
59
62
|
description: string;
|
|
60
|
-
|
|
61
|
-
|
|
63
|
+
source?: string; // e.g., 'manual', 'npm', 'github', 'posthog', 'calculated'
|
|
64
|
+
query?: string;
|
|
65
|
+
current_value: number | string | null;
|
|
66
|
+
target_value: number | string | null;
|
|
62
67
|
unit: string;
|
|
63
68
|
direction: 'higher_is_better' | 'lower_is_better';
|
|
64
69
|
history: Array<{
|
|
65
70
|
date: string;
|
|
66
|
-
value: number;
|
|
71
|
+
value: number | string;
|
|
67
72
|
}>;
|
|
68
73
|
}
|
|
69
74
|
|
|
@@ -181,7 +186,8 @@ function detectCompanyStage(goalMetrics: GoalMetrics[]): CompanyStage {
|
|
|
181
186
|
|
|
182
187
|
const totalUsers = userMetrics.reduce((sum, m) => {
|
|
183
188
|
const current = m.goal.kpis[0]?.current_value;
|
|
184
|
-
|
|
189
|
+
const numericValue = typeof current === 'number' ? current : 0;
|
|
190
|
+
return sum + numericValue;
|
|
185
191
|
}, 0);
|
|
186
192
|
|
|
187
193
|
if (totalUsers === 0) return CompanyStage.PreLaunch;
|
|
@@ -196,7 +202,8 @@ function shouldIncludeMetric(kpi: KPI, stage: CompanyStage): boolean {
|
|
|
196
202
|
const lowerName = kpi.name.toLowerCase();
|
|
197
203
|
|
|
198
204
|
if (internalMetrics.some((m) => lowerName.includes(m))) {
|
|
199
|
-
|
|
205
|
+
const numericValue = typeof kpi.current_value === 'number' ? kpi.current_value : 0;
|
|
206
|
+
return numericValue > 0; // Only show if non-zero
|
|
200
207
|
}
|
|
201
208
|
}
|
|
202
209
|
return true;
|
|
@@ -284,7 +291,7 @@ function inferEpicImpact(epic: Epic, allIdeas: BusinessIdea[]): string {
|
|
|
284
291
|
|
|
285
292
|
interface GoalMetrics {
|
|
286
293
|
goal: BusinessGoal;
|
|
287
|
-
previous_value: number;
|
|
294
|
+
previous_value: number | string;
|
|
288
295
|
growth_pct: string;
|
|
289
296
|
delta: number;
|
|
290
297
|
}
|
|
@@ -305,8 +312,14 @@ function getGoalMetrics(goalsFilePath: string, since: Date): GoalMetrics[] {
|
|
|
305
312
|
.sort((a, b) => b.date.localeCompare(a.date))[0];
|
|
306
313
|
|
|
307
314
|
const previous_value = previousEntry?.value ?? kpi.current_value;
|
|
308
|
-
|
|
309
|
-
|
|
315
|
+
|
|
316
|
+
// Only calculate numeric deltas and growth
|
|
317
|
+
const currentNumeric = typeof kpi.current_value === 'number' ? kpi.current_value : null;
|
|
318
|
+
const previousNumeric = typeof previous_value === 'number' ? previous_value : null;
|
|
319
|
+
const delta = (currentNumeric !== null && previousNumeric !== null) ? currentNumeric - previousNumeric : 0;
|
|
320
|
+
const growth_pct = (currentNumeric !== null && previousNumeric !== null)
|
|
321
|
+
? calculateGrowthPct(currentNumeric, previousNumeric)
|
|
322
|
+
: '0';
|
|
310
323
|
|
|
311
324
|
metrics.push({
|
|
312
325
|
goal: {
|
|
@@ -509,24 +522,31 @@ function generateExecutiveSummary(
|
|
|
509
522
|
commitCount: number,
|
|
510
523
|
learnings: string[]
|
|
511
524
|
): string {
|
|
525
|
+
// Extract market context from positioning data
|
|
526
|
+
const marketContext = positioning.current;
|
|
527
|
+
const sam = "$1.8-6B"; // From positioning SAM
|
|
528
|
+
const topDifferentiator = "full lifecycle loop: observe → analyze → recommend → implement → ship → verify"; // First differentiator
|
|
529
|
+
|
|
512
530
|
if (stage === CompanyStage.PreLaunch) {
|
|
513
531
|
const achievedGoals = goalMetrics.filter((m) => m.goal.status === 'achieved');
|
|
514
532
|
|
|
515
533
|
if (achievedGoals.length > 0) {
|
|
534
|
+
const goalTitle = achievedGoals[0].goal.title.toLowerCase();
|
|
516
535
|
const topLearning = learnings.length > 0 ? cleanLearningText(learnings[0]) : '';
|
|
517
|
-
// Only include learning if it's market/customer focused
|
|
518
536
|
const learningClause = topLearning && !topLearning.toLowerCase().includes('data file') && !topLearning.toLowerCase().includes('shipped feature')
|
|
519
537
|
? ` Key insight: ${topLearning.split('.')[0]}.`
|
|
520
538
|
: '';
|
|
521
539
|
|
|
522
|
-
|
|
540
|
+
// Lead with market opportunity, then traction
|
|
541
|
+
return `VibeBusiness is building the first autonomous AI product manager for the ${sam} indie hacker market. Our unique ${topDifferentiator} approach sets us apart. This month we ${goalTitle}, achieving ${epicAccomplishments.length} strategic milestone${epicAccomplishments.length !== 1 ? 's' : ''} with ${commitCount} commits shipped.${learningClause} On track for 20 beta users by Q2 to validate our 10x activation moment.`;
|
|
523
542
|
}
|
|
524
543
|
|
|
525
544
|
if (epicAccomplishments.length > 0) {
|
|
526
|
-
return `
|
|
545
|
+
return `VibeBusiness is building the first autonomous AI product manager for the ${sam} indie hacker market. This month: ${commitCount} commits shipped, ${epicAccomplishments.length} major feature${epicAccomplishments.length > 1 ? 's' : ''} completed. On track for 20 beta users by Q2 to prove PMF velocity.`;
|
|
527
546
|
}
|
|
528
547
|
|
|
529
|
-
|
|
548
|
+
// Fallback without achieved goals
|
|
549
|
+
return `Building toward beta launch in Q2. We're targeting the ${sam} indie hacker market with a unique ${topDifferentiator} approach. This month: ${commitCount} commits across core platform and activation improvements. Next milestone: 20 beta users to prove PMF velocity.`;
|
|
530
550
|
}
|
|
531
551
|
|
|
532
552
|
if (stage === CompanyStage.EarlyTraction) {
|
|
@@ -812,6 +832,73 @@ function generateMarkdown(data: {
|
|
|
812
832
|
}
|
|
813
833
|
}
|
|
814
834
|
|
|
835
|
+
// ─── Investment Thesis ───────────────────────────────────────────────────
|
|
836
|
+
|
|
837
|
+
md += `## 💼 Investment Thesis\n\n`;
|
|
838
|
+
|
|
839
|
+
// Load positioning data
|
|
840
|
+
const marketContext = positioning.current;
|
|
841
|
+
|
|
842
|
+
md += `### Market Opportunity\n`;
|
|
843
|
+
md += `- **TAM:** $80B+ across AI coding tools, agentic AI, and vertical AI for business\n`;
|
|
844
|
+
md += `- **SAM:** $1.8-6B (5-10M software entrepreneurs at $30-50/month)\n`;
|
|
845
|
+
md += `- **Beachhead:** Indie Hackers / Solo Technical Founders (CLI-native, $20-100/mo budgets)\n\n`;
|
|
846
|
+
|
|
847
|
+
md += `### Competitive Moat\n`;
|
|
848
|
+
for (const diff of marketContext.differentiators.slice(0, 4)) {
|
|
849
|
+
const [title, description] = diff.includes(':') ? diff.split(':').map(s => s.trim()) : [diff, ''];
|
|
850
|
+
if (description) {
|
|
851
|
+
md += `- **${title}** — ${description}\n`;
|
|
852
|
+
} else {
|
|
853
|
+
md += `- ${title}\n`;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
md += `\n`;
|
|
857
|
+
|
|
858
|
+
md += `### Why Now / Competitive Window\n`;
|
|
859
|
+
md += `12-18 months before Microsoft/GitHub could replicate full lifecycle approach. Claude Code CLI improves monthly — our implementation quality improves for free.\n\n`;
|
|
860
|
+
|
|
861
|
+
md += `### Traction Milestones\n`;
|
|
862
|
+
const npmVersion = goalMetrics.find(m => m.goal.kpis.some(kpi => kpi.name.includes('npm') && kpi.name.includes('Version')));
|
|
863
|
+
const betaUsers = goalMetrics.find(m => m.goal.kpis.some(kpi => kpi.name.includes('Beta User')));
|
|
864
|
+
const payingUsers = goalMetrics.find(m => m.goal.kpis.some(kpi => kpi.name.includes('Paying Users')));
|
|
865
|
+
|
|
866
|
+
if (npmVersion) {
|
|
867
|
+
const versionKpi = npmVersion.goal.kpis.find(kpi => kpi.name.includes('Version'));
|
|
868
|
+
if (versionKpi) md += `- ✅ Public npm launch (v${versionKpi.current_value})\n`;
|
|
869
|
+
}
|
|
870
|
+
if (betaUsers) {
|
|
871
|
+
const betaKpi = betaUsers.goal.kpis.find(kpi => kpi.name.includes('Beta User'));
|
|
872
|
+
if (betaKpi) md += `- 🎯 Beta users: ${betaKpi.current_value}/${betaKpi.target_value} (targeting Q2)\n`;
|
|
873
|
+
}
|
|
874
|
+
if (payingUsers) {
|
|
875
|
+
const payingKpi = payingUsers.goal.kpis.find(kpi => kpi.name.includes('Paying Users'));
|
|
876
|
+
if (payingKpi) md += `- 🎯 Paying customers: ${payingKpi.current_value}/${payingKpi.target_value} (targeting Q3)\n`;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
// Add unit economics if available
|
|
880
|
+
const unitEconomics = goalMetrics.find(m => m.goal.id === 'goal-unit-economics');
|
|
881
|
+
if (unitEconomics) {
|
|
882
|
+
md += `\n### Unit Economics (Target)\n`;
|
|
883
|
+
const cacKpi = unitEconomics.goal.kpis.find(kpi => kpi.id === 'kpi-cac');
|
|
884
|
+
const ltvKpi = unitEconomics.goal.kpis.find(kpi => kpi.id === 'kpi-ltv');
|
|
885
|
+
const marginKpi = unitEconomics.goal.kpis.find(kpi => kpi.id === 'kpi-gross-margin');
|
|
886
|
+
|
|
887
|
+
if (cacKpi && ltvKpi) {
|
|
888
|
+
const ltvNum = typeof ltvKpi.target_value === 'number' ? ltvKpi.target_value : null;
|
|
889
|
+
const cacNum = typeof cacKpi.target_value === 'number' ? cacKpi.target_value : null;
|
|
890
|
+
const ratio = (ltvNum && cacNum) ? (ltvNum / cacNum).toFixed(1) : 'N/A';
|
|
891
|
+
md += `- **CAC:** $${cacKpi.target_value} (target)\n`;
|
|
892
|
+
md += `- **LTV:** $${ltvKpi.target_value} (target)\n`;
|
|
893
|
+
md += `- **LTV:CAC Ratio:** ${ratio}:1 (target >3:1 for SaaS)\n`;
|
|
894
|
+
}
|
|
895
|
+
if (marginKpi) {
|
|
896
|
+
md += `- **Gross Margin:** ${marginKpi.target_value}% (BYOK = near-zero COGS)\n`;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
md += `\n---\n\n`;
|
|
901
|
+
|
|
815
902
|
// ─── Momentum Indicators ─────────────────────────────────────────────────
|
|
816
903
|
|
|
817
904
|
md += `## 📊 Momentum Indicators\n\n`;
|
|
@@ -821,6 +908,26 @@ function generateMarkdown(data: {
|
|
|
821
908
|
md += `- **Features Shipped:** ${epicAccomplishments.reduce((sum, e) => sum + e.ideas_shipped, 0)} total across ${epicAccomplishments.length} epic${epicAccomplishments.length > 1 ? 's' : ''}\n`;
|
|
822
909
|
}
|
|
823
910
|
|
|
911
|
+
// Show sparklines for top KPIs with history
|
|
912
|
+
const kpisWithHistory = goalMetrics
|
|
913
|
+
.filter(m => m.goal.kpis[0].history.length > 1)
|
|
914
|
+
.slice(0, 5); // Show top 5
|
|
915
|
+
|
|
916
|
+
if (kpisWithHistory.length > 0) {
|
|
917
|
+
md += `\n**Key Metrics Trends:**\n`;
|
|
918
|
+
for (const metric of kpisWithHistory) {
|
|
919
|
+
const kpi = metric.goal.kpis[0];
|
|
920
|
+
const sparkline = generateAsciiSparkline(kpi.history);
|
|
921
|
+
if (sparkline) {
|
|
922
|
+
const currentVal = typeof kpi.current_value === 'number'
|
|
923
|
+
? kpi.current_value.toFixed(0)
|
|
924
|
+
: kpi.current_value;
|
|
925
|
+
md += `- **${kpi.name}:** ${sparkline} ${currentVal} ${kpi.unit}\n`;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
md += `\n`;
|
|
929
|
+
}
|
|
930
|
+
|
|
824
931
|
if (goalMetrics.length > 0) {
|
|
825
932
|
const avgGrowth = goalMetrics
|
|
826
933
|
.map((m) => parseFloat(m.growth_pct.replace(/[+%]/g, '')))
|
|
@@ -843,6 +950,132 @@ function generateMarkdown(data: {
|
|
|
843
950
|
return md;
|
|
844
951
|
}
|
|
845
952
|
|
|
953
|
+
// ─── ASCII Sparkline Generation ──────────────────────────────────────────────
|
|
954
|
+
|
|
955
|
+
/**
|
|
956
|
+
* Generate ASCII sparkline chart for a series of values
|
|
957
|
+
*/
|
|
958
|
+
function generateAsciiSparkline(history: Array<{ date: string; value: number | string }>): string {
|
|
959
|
+
if (history.length === 0) return '';
|
|
960
|
+
|
|
961
|
+
// Convert values to numbers, filter out non-numeric
|
|
962
|
+
const numericValues = history
|
|
963
|
+
.map(h => typeof h.value === 'number' ? h.value : parseFloat(String(h.value)))
|
|
964
|
+
.filter(v => !isNaN(v) && isFinite(v));
|
|
965
|
+
|
|
966
|
+
if (numericValues.length < 2) return ''; // Need at least 2 points for a trend
|
|
967
|
+
|
|
968
|
+
const max = Math.max(...numericValues);
|
|
969
|
+
const min = Math.min(...numericValues);
|
|
970
|
+
const range = max - min || 1;
|
|
971
|
+
|
|
972
|
+
const bars = '▁▂▃▄▅▆▇█';
|
|
973
|
+
|
|
974
|
+
return numericValues.map(v => {
|
|
975
|
+
const normalized = (v - min) / range;
|
|
976
|
+
const barIndex = Math.min(Math.floor(normalized * 8), 7);
|
|
977
|
+
return bars[barIndex];
|
|
978
|
+
}).join('');
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
// ─── Live Data Fetching & KPI Updates ────────────────────────────────────────
|
|
982
|
+
|
|
983
|
+
interface LiveData {
|
|
984
|
+
npm?: NpmStats;
|
|
985
|
+
github?: GitHubStats;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
/**
|
|
989
|
+
* Fetch live data from external sources (npm, GitHub, etc.)
|
|
990
|
+
*/
|
|
991
|
+
async function fetchLiveData(repoPath: string, packageName: string = 'vibebusiness'): Promise<LiveData> {
|
|
992
|
+
console.log('📡 Fetching live metrics from npm and GitHub...\n');
|
|
993
|
+
|
|
994
|
+
const [npmResult, githubResult] = await Promise.allSettled([
|
|
995
|
+
fetchNpmStats(packageName),
|
|
996
|
+
fetchGitHubStats(repoPath),
|
|
997
|
+
]);
|
|
998
|
+
|
|
999
|
+
const data: LiveData = {};
|
|
1000
|
+
|
|
1001
|
+
if (npmResult.status === 'fulfilled') {
|
|
1002
|
+
data.npm = npmResult.value;
|
|
1003
|
+
console.log(` ✓ npm version: ${npmResult.value.version}`);
|
|
1004
|
+
} else {
|
|
1005
|
+
console.warn(` ✗ npm fetch failed: ${npmResult.reason}`);
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
if (githubResult.status === 'fulfilled') {
|
|
1009
|
+
data.github = githubResult.value;
|
|
1010
|
+
console.log(` ✓ GitHub stars: ${githubResult.value.stars}`);
|
|
1011
|
+
} else {
|
|
1012
|
+
console.warn(` ✗ GitHub fetch failed: ${githubResult.reason}`);
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
console.log('');
|
|
1016
|
+
return data;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
/**
|
|
1020
|
+
* Update KPI values in goals.json with live data
|
|
1021
|
+
*/
|
|
1022
|
+
function updateKPIsWithLiveData(goalsFilePath: string, liveData: LiveData): void {
|
|
1023
|
+
const goalsStore = readJsonSafe<{ goals: BusinessGoal[] }>(goalsFilePath, { goals: [] });
|
|
1024
|
+
let updated = false;
|
|
1025
|
+
|
|
1026
|
+
for (const goal of goalsStore.goals) {
|
|
1027
|
+
for (const kpi of goal.kpis) {
|
|
1028
|
+
const today = formatDate(new Date());
|
|
1029
|
+
|
|
1030
|
+
// Update npm-related KPIs
|
|
1031
|
+
if (kpi.source === 'npm' && liveData.npm) {
|
|
1032
|
+
if (kpi.id === 'kpi-npm-version' || kpi.name.includes('npm') && kpi.name.includes('Version')) {
|
|
1033
|
+
const newVersion = liveData.npm.version;
|
|
1034
|
+
if (kpi.current_value !== newVersion) {
|
|
1035
|
+
kpi.current_value = newVersion;
|
|
1036
|
+
// Add to history if not already present for today
|
|
1037
|
+
if (!kpi.history.find(h => h.date === today)) {
|
|
1038
|
+
kpi.history.push({ date: today, value: newVersion as any });
|
|
1039
|
+
}
|
|
1040
|
+
updated = true;
|
|
1041
|
+
}
|
|
1042
|
+
} else if (kpi.id === 'kpi-npm-downloads' || kpi.name.includes('npm') && kpi.name.includes('Downloads')) {
|
|
1043
|
+
const newDownloads = liveData.npm.weeklyDownloads;
|
|
1044
|
+
if (kpi.current_value !== newDownloads) {
|
|
1045
|
+
kpi.current_value = newDownloads;
|
|
1046
|
+
if (!kpi.history.find(h => h.date === today)) {
|
|
1047
|
+
kpi.history.push({ date: today, value: newDownloads });
|
|
1048
|
+
}
|
|
1049
|
+
updated = true;
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
// Update GitHub-related KPIs (if we add them in the future)
|
|
1055
|
+
if (kpi.source === 'github' && liveData.github) {
|
|
1056
|
+
if (kpi.name.includes('Stars')) {
|
|
1057
|
+
const newStars = liveData.github.stars;
|
|
1058
|
+
if (kpi.current_value !== newStars) {
|
|
1059
|
+
kpi.current_value = newStars;
|
|
1060
|
+
if (!kpi.history.find(h => h.date === today)) {
|
|
1061
|
+
kpi.history.push({ date: today, value: newStars });
|
|
1062
|
+
}
|
|
1063
|
+
updated = true;
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
// Write updated goals back to file
|
|
1071
|
+
if (updated) {
|
|
1072
|
+
fs.writeFileSync(goalsFilePath, JSON.stringify({ goals: goalsStore.goals }, null, 2) + '\n');
|
|
1073
|
+
console.log('✅ Updated goals.json with live data\n');
|
|
1074
|
+
} else {
|
|
1075
|
+
console.log('ℹ️ No KPI updates needed\n');
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
|
|
846
1079
|
// ─── Main ────────────────────────────────────────────────────────────────────
|
|
847
1080
|
|
|
848
1081
|
async function main() {
|
|
@@ -871,6 +1104,10 @@ async function main() {
|
|
|
871
1104
|
if (hasVibeBusinessData) {
|
|
872
1105
|
console.log('📊 Using VibeBusiness data sources...\n');
|
|
873
1106
|
|
|
1107
|
+
// Fetch live data from external sources and update KPIs
|
|
1108
|
+
const liveData = await fetchLiveData(repo);
|
|
1109
|
+
updateKPIsWithLiveData(goalsFilePath, liveData);
|
|
1110
|
+
|
|
874
1111
|
epicAccomplishments = getEpicAccomplishments(ideasFilePath, roadmapFilePath, since, until);
|
|
875
1112
|
goalMetrics = getGoalMetrics(goalsFilePath, since);
|
|
876
1113
|
roadmapEpics = getRoadmapEpics(roadmapFilePath, ideasFilePath);
|