vibebusiness 1.2.49 → 1.2.51
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 +17 -17
- 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/analyze/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/config/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/epics/[id]/ideas/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/epics/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/epics/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/goals/[id]/kpis/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/goals/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/goals/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/hypotheses/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/hypotheses/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/ideas/[id]/comments/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/ideas/[id]/implement/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/ideas/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/ideas/[id]/transition/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/ideas/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/implementations/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/kpis/refresh/route.js +1 -1
- package/.next/standalone/.next/server/app/api/kpis/refresh/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/social/[id]/publish/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/social/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/social/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.js.nft.json +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.js +1 -1
- package/.next/standalone/.next/server/app/hypotheses/page.js.nft.json +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.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/ideas/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/page.js +1 -1
- package/.next/standalone/.next/server/app/page.js.nft.json +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.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/roadmap/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/sessions/page.js.nft.json +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-paths-manifest.json +13 -13
- package/.next/standalone/.next/server/chunks/3794.js +64 -4
- 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 +45 -6
- package/.next/standalone/data/goals.json +50 -10
- package/.next/standalone/data/ideas.json +1278 -597
- package/.next/standalone/data/implementations.json +101 -0
- package/.next/standalone/data/posthog.json +68 -0
- package/.next/standalone/data/sessions.json +5 -4
- package/.next/standalone/data/social.json +2 -2
- package/.next/standalone/package.json +1 -1
- package/.next/standalone/scripts/skills/social-media.ts +172 -2
- package/dist/scripts/heartbeat.js +286 -26
- package/package.json +1 -1
- /package/.next/static/{zIIaTqrawBK1MmHg37JOr → pCt9lOHHLoEA8SuY832zV}/_buildManifest.js +0 -0
- /package/.next/static/{zIIaTqrawBK1MmHg37JOr → pCt9lOHHLoEA8SuY832zV}/_ssgManifest.js +0 -0
|
@@ -9406,7 +9406,11 @@ var RestAPIAdapter = class {
|
|
|
9406
9406
|
this.headers[key] = this.interpolateEnvVars(value);
|
|
9407
9407
|
}
|
|
9408
9408
|
}
|
|
9409
|
-
|
|
9409
|
+
const raw = config.field_mapping || {};
|
|
9410
|
+
this.fieldMapping = {};
|
|
9411
|
+
for (const [k, v2] of Object.entries(raw)) {
|
|
9412
|
+
if (typeof v2 === "string") this.fieldMapping[k] = v2;
|
|
9413
|
+
}
|
|
9410
9414
|
this.transforms = config.transforms || {};
|
|
9411
9415
|
}
|
|
9412
9416
|
/**
|
|
@@ -9496,7 +9500,7 @@ var ManualAdapter = class {
|
|
|
9496
9500
|
};
|
|
9497
9501
|
|
|
9498
9502
|
// src/lib/kpi-adapters/posthog.ts
|
|
9499
|
-
var PostHogAdapter = class {
|
|
9503
|
+
var PostHogAdapter = class _PostHogAdapter {
|
|
9500
9504
|
id;
|
|
9501
9505
|
type = "posthog";
|
|
9502
9506
|
fieldMapping;
|
|
@@ -9527,26 +9531,32 @@ var PostHogAdapter = class {
|
|
|
9527
9531
|
const metadata = {};
|
|
9528
9532
|
const end = endDate || (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
9529
9533
|
const start = startDate || new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3).toISOString().split("T")[0];
|
|
9530
|
-
const
|
|
9531
|
-
|
|
9534
|
+
const queryMap = /* @__PURE__ */ new Map();
|
|
9535
|
+
for (const [kpiId, mapping] of Object.entries(this.fieldMapping)) {
|
|
9536
|
+
const query = _PostHogAdapter.normalizeMapping(mapping);
|
|
9537
|
+
const key = _PostHogAdapter.queryCacheKey(query);
|
|
9538
|
+
const entry = queryMap.get(key);
|
|
9539
|
+
if (entry) {
|
|
9540
|
+
entry.kpiIds.push(kpiId);
|
|
9541
|
+
} else {
|
|
9542
|
+
queryMap.set(key, { query, kpiIds: [kpiId] });
|
|
9543
|
+
}
|
|
9544
|
+
}
|
|
9545
|
+
if (queryMap.size === 0) {
|
|
9532
9546
|
return { values, metadata, fetched_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
9533
9547
|
}
|
|
9534
|
-
for (const
|
|
9548
|
+
for (const [key, { query, kpiIds }] of Array.from(queryMap.entries())) {
|
|
9535
9549
|
try {
|
|
9536
|
-
const count = await this.fetchEventCount(
|
|
9537
|
-
metadata[
|
|
9538
|
-
for (const
|
|
9539
|
-
|
|
9540
|
-
values[kpiId] = count;
|
|
9541
|
-
}
|
|
9550
|
+
const count = await this.fetchEventCount(query.event, start, end, query.properties);
|
|
9551
|
+
metadata[key] = { count, start, end, event: query.event, properties: query.properties };
|
|
9552
|
+
for (const kpiId of kpiIds) {
|
|
9553
|
+
values[kpiId] = count;
|
|
9542
9554
|
}
|
|
9543
9555
|
} catch (error) {
|
|
9544
9556
|
const msg = error instanceof Error ? error.message : String(error);
|
|
9545
|
-
console.warn(`PostHog: failed to fetch "${
|
|
9546
|
-
for (const
|
|
9547
|
-
|
|
9548
|
-
values[kpiId] = null;
|
|
9549
|
-
}
|
|
9557
|
+
console.warn(`PostHog: failed to fetch "${key}": ${msg}`);
|
|
9558
|
+
for (const kpiId of kpiIds) {
|
|
9559
|
+
values[kpiId] = null;
|
|
9550
9560
|
}
|
|
9551
9561
|
}
|
|
9552
9562
|
}
|
|
@@ -9556,6 +9566,21 @@ var PostHogAdapter = class {
|
|
|
9556
9566
|
fetched_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
9557
9567
|
};
|
|
9558
9568
|
}
|
|
9569
|
+
/** Normalize a field_mapping value (string or object) into an EventQuery. */
|
|
9570
|
+
static normalizeMapping(mapping) {
|
|
9571
|
+
if (typeof mapping === "string") {
|
|
9572
|
+
return { event: mapping };
|
|
9573
|
+
}
|
|
9574
|
+
return { event: mapping.event, properties: mapping.properties };
|
|
9575
|
+
}
|
|
9576
|
+
/** Stable cache key for deduplicating identical queries. */
|
|
9577
|
+
static queryCacheKey(query) {
|
|
9578
|
+
if (!query.properties || Object.keys(query.properties).length === 0) {
|
|
9579
|
+
return query.event;
|
|
9580
|
+
}
|
|
9581
|
+
const sortedProps = Object.entries(query.properties).sort(([a], [b]) => a.localeCompare(b));
|
|
9582
|
+
return `${query.event}[${sortedProps.map(([k, v2]) => `${k}=${v2}`).join(",")}]`;
|
|
9583
|
+
}
|
|
9559
9584
|
async healthCheck() {
|
|
9560
9585
|
try {
|
|
9561
9586
|
const res = await fetch(`${this.host}/api/projects/${this.projectId}/`, {
|
|
@@ -9570,17 +9595,25 @@ var PostHogAdapter = class {
|
|
|
9570
9595
|
}
|
|
9571
9596
|
/**
|
|
9572
9597
|
* Fetch total event count from PostHog Trends API for a given date range.
|
|
9598
|
+
* Optionally filters by event properties (e.g. { command: "init" }).
|
|
9573
9599
|
*/
|
|
9574
|
-
async fetchEventCount(eventName, dateFrom, dateTo) {
|
|
9600
|
+
async fetchEventCount(eventName, dateFrom, dateTo, properties) {
|
|
9575
9601
|
const url = `${this.host}/api/projects/${this.projectId}/insights/trend/`;
|
|
9602
|
+
const eventDef = {
|
|
9603
|
+
id: eventName,
|
|
9604
|
+
math: "total",
|
|
9605
|
+
type: "events"
|
|
9606
|
+
};
|
|
9607
|
+
if (properties && Object.keys(properties).length > 0) {
|
|
9608
|
+
eventDef.properties = Object.entries(properties).map(([key, value]) => ({
|
|
9609
|
+
key,
|
|
9610
|
+
value,
|
|
9611
|
+
operator: "exact",
|
|
9612
|
+
type: "event"
|
|
9613
|
+
}));
|
|
9614
|
+
}
|
|
9576
9615
|
const body = {
|
|
9577
|
-
events: [
|
|
9578
|
-
{
|
|
9579
|
-
id: eventName,
|
|
9580
|
-
math: "total",
|
|
9581
|
-
type: "events"
|
|
9582
|
-
}
|
|
9583
|
-
],
|
|
9616
|
+
events: [eventDef],
|
|
9584
9617
|
date_from: dateFrom,
|
|
9585
9618
|
date_to: dateTo,
|
|
9586
9619
|
display: "ActionsLineGraph"
|
|
@@ -9613,7 +9646,11 @@ var WaitlistFileAdapter = class {
|
|
|
9613
9646
|
fieldMapping;
|
|
9614
9647
|
constructor(config) {
|
|
9615
9648
|
this.id = config.id;
|
|
9616
|
-
|
|
9649
|
+
const raw = config.field_mapping || {};
|
|
9650
|
+
this.fieldMapping = {};
|
|
9651
|
+
for (const [k, v2] of Object.entries(raw)) {
|
|
9652
|
+
if (typeof v2 === "string") this.fieldMapping[k] = v2;
|
|
9653
|
+
}
|
|
9617
9654
|
const relativePath = config.config?.path || "website/data/waitlist.json";
|
|
9618
9655
|
this.filePath = path.isAbsolute(relativePath) ? relativePath : path.resolve(process.cwd(), relativePath);
|
|
9619
9656
|
}
|
|
@@ -30992,6 +31029,106 @@ Respond in this exact JSON format (no markdown, no code blocks):
|
|
|
30992
31029
|
};
|
|
30993
31030
|
}
|
|
30994
31031
|
}
|
|
31032
|
+
async function generateWeekPlan(opts) {
|
|
31033
|
+
const positioning = opts.positioning ?? readJsonSafe2(POSITIONING_FILE, null);
|
|
31034
|
+
const today = /* @__PURE__ */ new Date();
|
|
31035
|
+
const dayOfWeek = today.getDay();
|
|
31036
|
+
const daysUntilMonday = dayOfWeek === 0 ? 1 : dayOfWeek === 1 ? 0 : 8 - dayOfWeek;
|
|
31037
|
+
const monday = new Date(today);
|
|
31038
|
+
monday.setDate(today.getDate() + daysUntilMonday);
|
|
31039
|
+
const weekDates = [];
|
|
31040
|
+
const dayNames = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"];
|
|
31041
|
+
for (let i = 0; i < 7; i++) {
|
|
31042
|
+
const d2 = new Date(monday);
|
|
31043
|
+
d2.setDate(monday.getDate() + i);
|
|
31044
|
+
weekDates.push({ day: dayNames[i], date: d2.toISOString().split("T")[0] });
|
|
31045
|
+
}
|
|
31046
|
+
const prompt = `You are a social media strategist for a developer-focused indie hacker audience on X/Twitter.
|
|
31047
|
+
|
|
31048
|
+
${ALGORITHM_KNOWLEDGE}
|
|
31049
|
+
|
|
31050
|
+
${HOOK_PATTERNS}
|
|
31051
|
+
|
|
31052
|
+
PRODUCT CONTEXT:
|
|
31053
|
+
- Product: ${positioning?.current?.tagline ?? "VibeBusiness"}
|
|
31054
|
+
- Value prop: ${positioning?.current?.value_proposition ?? "AI-powered business automation"}
|
|
31055
|
+
- Target audience: ${positioning?.current?.target_audience?.primary ?? "Indie hackers and solo founders"}
|
|
31056
|
+
|
|
31057
|
+
CURRENT GOALS:
|
|
31058
|
+
${opts.goalsContext || "No specific goals set."}
|
|
31059
|
+
|
|
31060
|
+
RECENTLY SHIPPED:
|
|
31061
|
+
${opts.recentShips || "No recent ships."}
|
|
31062
|
+
|
|
31063
|
+
KPI HIGHLIGHTS:
|
|
31064
|
+
${opts.kpiHighlights || "No KPI data yet."}
|
|
31065
|
+
|
|
31066
|
+
HISTORICAL PERFORMANCE:
|
|
31067
|
+
${formatPastPerformance(opts.pastPerformance ?? [])}
|
|
31068
|
+
|
|
31069
|
+
WEEK DATES:
|
|
31070
|
+
${weekDates.map((d2) => `${d2.day}: ${d2.date}`).join("\n")}
|
|
31071
|
+
|
|
31072
|
+
Plan a full week of content (Monday-Sunday). Rules:
|
|
31073
|
+
- 5 content days (Mon-Fri) + 2 rest days (Sat-Sun)
|
|
31074
|
+
- Include 1-2 threads per week (threads get 200-300% more reach)
|
|
31075
|
+
- Mix content types: ship, viral, thread, milestone, insight, question
|
|
31076
|
+
- Align content with current goals (especially behind-schedule ones)
|
|
31077
|
+
- Use diverse hook patterns \u2014 don't repeat the same hook type consecutively
|
|
31078
|
+
- Write FULL tweet text for each content day (max 280 chars for single tweets)
|
|
31079
|
+
- For threads, provide all tweet texts (5-7 tweets each, max 280 chars per tweet)
|
|
31080
|
+
- Each rest day should have type "rest" with empty content
|
|
31081
|
+
|
|
31082
|
+
Respond in this exact JSON format (no markdown, no code blocks):
|
|
31083
|
+
{
|
|
31084
|
+
"days": [
|
|
31085
|
+
{
|
|
31086
|
+
"day": "monday",
|
|
31087
|
+
"date": "YYYY-MM-DD",
|
|
31088
|
+
"type": "thread",
|
|
31089
|
+
"topic": "topic description",
|
|
31090
|
+
"content": "First tweet / hook text",
|
|
31091
|
+
"thread_tweets": ["tweet 1", "tweet 2", "..."],
|
|
31092
|
+
"hook_type": "story",
|
|
31093
|
+
"rationale": "Why this content on this day"
|
|
31094
|
+
},
|
|
31095
|
+
{
|
|
31096
|
+
"day": "tuesday",
|
|
31097
|
+
"date": "YYYY-MM-DD",
|
|
31098
|
+
"type": "viral",
|
|
31099
|
+
"topic": "ship",
|
|
31100
|
+
"content": "Full tweet text here",
|
|
31101
|
+
"hook_type": "bold-claim",
|
|
31102
|
+
"rationale": "Why this content on this day"
|
|
31103
|
+
}
|
|
31104
|
+
],
|
|
31105
|
+
"strategy_summary": "One paragraph explaining the week's strategy"
|
|
31106
|
+
}`;
|
|
31107
|
+
const result = await invokeAI({ prompt, model: "sonnet" });
|
|
31108
|
+
try {
|
|
31109
|
+
const parsed = JSON.parse(extractJson(result.output));
|
|
31110
|
+
for (const day of parsed.days) {
|
|
31111
|
+
if (day.type === "rest") continue;
|
|
31112
|
+
if (day.content && day.content.length > 280) {
|
|
31113
|
+
day.content = day.content.slice(0, 277).trimEnd() + "...";
|
|
31114
|
+
}
|
|
31115
|
+
if (day.thread_tweets) {
|
|
31116
|
+
day.thread_tweets = day.thread_tweets.map(
|
|
31117
|
+
(t) => t.length > 280 ? t.slice(0, 277).trimEnd() + "..." : t
|
|
31118
|
+
);
|
|
31119
|
+
}
|
|
31120
|
+
}
|
|
31121
|
+
return {
|
|
31122
|
+
days: parsed.days || [],
|
|
31123
|
+
strategy_summary: parsed.strategy_summary || ""
|
|
31124
|
+
};
|
|
31125
|
+
} catch {
|
|
31126
|
+
return {
|
|
31127
|
+
days: [],
|
|
31128
|
+
strategy_summary: "AI failed to generate a valid week plan."
|
|
31129
|
+
};
|
|
31130
|
+
}
|
|
31131
|
+
}
|
|
30995
31132
|
function extractJson(text) {
|
|
30996
31133
|
const codeBlockMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
30997
31134
|
if (codeBlockMatch) return codeBlockMatch[1].trim();
|
|
@@ -31078,6 +31215,7 @@ function createDraft(state, text, source, sourceRef, mediaPaths = null) {
|
|
|
31078
31215
|
}
|
|
31079
31216
|
var SOCIAL_PREFIXES = [
|
|
31080
31217
|
"social-setup-x",
|
|
31218
|
+
"social-plan-week",
|
|
31081
31219
|
"social-draft-ship-visual",
|
|
31082
31220
|
"social-draft-ship",
|
|
31083
31221
|
"social-draft-update",
|
|
@@ -31098,6 +31236,9 @@ async function executeSocialTask(taskId, description, _businessContext) {
|
|
|
31098
31236
|
if (taskId === "social-setup-x") {
|
|
31099
31237
|
return await executeSetupX();
|
|
31100
31238
|
}
|
|
31239
|
+
if (taskId === "social-plan-week") {
|
|
31240
|
+
return await executePlanWeek();
|
|
31241
|
+
}
|
|
31101
31242
|
if (taskId === "social-draft-ship-visual") {
|
|
31102
31243
|
return executeDraftShipVisual();
|
|
31103
31244
|
}
|
|
@@ -31161,6 +31302,10 @@ function readSocialFreshness() {
|
|
|
31161
31302
|
bestEngagementRate = best.engagement_rate;
|
|
31162
31303
|
}
|
|
31163
31304
|
const threadCount = state.drafts.filter((d2) => d2.is_thread && d2.status === "published").length;
|
|
31305
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
31306
|
+
const scheduledDraftCount = state.drafts.filter(
|
|
31307
|
+
(d2) => d2.status === "draft" && d2.scheduled_date && d2.scheduled_date >= today
|
|
31308
|
+
).length;
|
|
31164
31309
|
return {
|
|
31165
31310
|
configured: state.credentials_valid,
|
|
31166
31311
|
credentials_valid: state.credentials_valid,
|
|
@@ -31172,7 +31317,8 @@ function readSocialFreshness() {
|
|
|
31172
31317
|
avg_engagement_rate: avgEngagementRate,
|
|
31173
31318
|
best_tweet_id: bestTweetId,
|
|
31174
31319
|
best_engagement_rate: bestEngagementRate,
|
|
31175
|
-
thread_count: threadCount
|
|
31320
|
+
thread_count: threadCount,
|
|
31321
|
+
scheduled_draft_count: scheduledDraftCount
|
|
31176
31322
|
};
|
|
31177
31323
|
}
|
|
31178
31324
|
async function executeSetupX() {
|
|
@@ -31417,6 +31563,117 @@ async function executeDraftThread(topic, description) {
|
|
|
31417
31563
|
].join("\n")
|
|
31418
31564
|
};
|
|
31419
31565
|
}
|
|
31566
|
+
async function executePlanWeek() {
|
|
31567
|
+
const state = loadState3();
|
|
31568
|
+
const pastPerformance = collectEngagementHistory(state);
|
|
31569
|
+
let positioning = null;
|
|
31570
|
+
try {
|
|
31571
|
+
if (fs18.existsSync(POSITIONING_FILE)) {
|
|
31572
|
+
positioning = JSON.parse(fs18.readFileSync(POSITIONING_FILE, "utf-8"));
|
|
31573
|
+
}
|
|
31574
|
+
} catch {
|
|
31575
|
+
}
|
|
31576
|
+
let goalsContext = "";
|
|
31577
|
+
try {
|
|
31578
|
+
if (fs18.existsSync(GOALS_FILE)) {
|
|
31579
|
+
const { goals } = JSON.parse(fs18.readFileSync(GOALS_FILE, "utf-8"));
|
|
31580
|
+
const relevant = goals.filter((g2) => g2.status === "on_track" || g2.status === "at_risk" || g2.status === "behind").map((g2) => {
|
|
31581
|
+
const progress = g2.current_value != null && g2.target_value ? `${Math.round(g2.current_value / g2.target_value * 100)}%` : "unknown";
|
|
31582
|
+
return `- ${g2.title} (${g2.status}, ${progress} complete, deadline: ${g2.deadline})`;
|
|
31583
|
+
});
|
|
31584
|
+
goalsContext = relevant.length > 0 ? relevant.join("\n") : "No active goals.";
|
|
31585
|
+
}
|
|
31586
|
+
} catch {
|
|
31587
|
+
}
|
|
31588
|
+
let recentShips = "";
|
|
31589
|
+
try {
|
|
31590
|
+
if (fs18.existsSync(IDEAS_FILE)) {
|
|
31591
|
+
const { ideas } = JSON.parse(fs18.readFileSync(IDEAS_FILE, "utf-8"));
|
|
31592
|
+
const twoWeeksAgo = new Date(Date.now() - 14 * 24 * 60 * 60 * 1e3).toISOString();
|
|
31593
|
+
const shipped = ideas.filter((i) => i.stage === "shipped" && i.updated_at > twoWeeksAgo).map((i) => `- ${i.title} (shipped ${i.updated_at.split("T")[0]})`);
|
|
31594
|
+
recentShips = shipped.length > 0 ? shipped.join("\n") : "No recent ships.";
|
|
31595
|
+
}
|
|
31596
|
+
} catch {
|
|
31597
|
+
}
|
|
31598
|
+
let kpiHighlights = "";
|
|
31599
|
+
try {
|
|
31600
|
+
if (fs18.existsSync(GOALS_FILE)) {
|
|
31601
|
+
const { goals } = JSON.parse(fs18.readFileSync(GOALS_FILE, "utf-8"));
|
|
31602
|
+
const highlights = [];
|
|
31603
|
+
for (const goal of goals) {
|
|
31604
|
+
for (const kpi of goal.kpis) {
|
|
31605
|
+
if (kpi.current_value != null && kpi.target_value != null) {
|
|
31606
|
+
const pct = Math.round(kpi.current_value / kpi.target_value * 100);
|
|
31607
|
+
highlights.push(`- ${kpi.name}: ${kpi.current_value}/${kpi.target_value} ${kpi.unit} (${pct}%)`);
|
|
31608
|
+
}
|
|
31609
|
+
}
|
|
31610
|
+
}
|
|
31611
|
+
kpiHighlights = highlights.length > 0 ? highlights.join("\n") : "No KPI data.";
|
|
31612
|
+
}
|
|
31613
|
+
} catch {
|
|
31614
|
+
}
|
|
31615
|
+
const plan = await generateWeekPlan({
|
|
31616
|
+
positioning,
|
|
31617
|
+
pastPerformance,
|
|
31618
|
+
goalsContext,
|
|
31619
|
+
recentShips,
|
|
31620
|
+
kpiHighlights
|
|
31621
|
+
});
|
|
31622
|
+
if (plan.days.length === 0) {
|
|
31623
|
+
return { success: false, output: "AI failed to generate a weekly content plan." };
|
|
31624
|
+
}
|
|
31625
|
+
const createdDrafts = [];
|
|
31626
|
+
for (const day of plan.days) {
|
|
31627
|
+
if (day.type === "rest" || !day.content) continue;
|
|
31628
|
+
if (day.type === "thread" && day.thread_tweets && day.thread_tweets.length > 0) {
|
|
31629
|
+
const draft = {
|
|
31630
|
+
id: `draft-${v4_default().slice(0, 8)}`,
|
|
31631
|
+
text: day.thread_tweets[0],
|
|
31632
|
+
source: "thread",
|
|
31633
|
+
source_ref: null,
|
|
31634
|
+
status: "draft",
|
|
31635
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
31636
|
+
published_at: null,
|
|
31637
|
+
tweet_id: null,
|
|
31638
|
+
tweet_url: null,
|
|
31639
|
+
is_thread: true,
|
|
31640
|
+
thread_tweets: day.thread_tweets,
|
|
31641
|
+
scheduled_date: day.date
|
|
31642
|
+
};
|
|
31643
|
+
state.drafts.push(draft);
|
|
31644
|
+
createdDrafts.push({ day: day.day, date: day.date, type: "thread", id: draft.id });
|
|
31645
|
+
} else {
|
|
31646
|
+
const source = ["ship", "milestone", "update", "digest", "insight"].includes(day.topic) ? day.topic : "insight";
|
|
31647
|
+
const draft = createDraft(state, day.content, source, null);
|
|
31648
|
+
draft.scheduled_date = day.date;
|
|
31649
|
+
createdDrafts.push({ day: day.day, date: day.date, type: day.type, id: draft.id });
|
|
31650
|
+
}
|
|
31651
|
+
}
|
|
31652
|
+
saveState3(state);
|
|
31653
|
+
const monday = plan.days.find((d2) => d2.day === "monday")?.date ?? "";
|
|
31654
|
+
const sunday = plan.days.find((d2) => d2.day === "sunday")?.date ?? "";
|
|
31655
|
+
const dateRange = monday && sunday ? `${formatDateShort(monday)} - ${formatDateShort(sunday)}` : "This Week";
|
|
31656
|
+
const lines = [`Weekly Content Plan (${dateRange})`, ""];
|
|
31657
|
+
for (const day of plan.days) {
|
|
31658
|
+
const dayLabel = day.day.charAt(0).toUpperCase() + day.day.slice(1, 3);
|
|
31659
|
+
if (day.type === "rest") {
|
|
31660
|
+
lines.push(`${dayLabel} -- Rest`);
|
|
31661
|
+
} else {
|
|
31662
|
+
const threadInfo = day.type === "thread" && day.thread_tweets ? ` (${day.thread_tweets.length} tweets)` : "";
|
|
31663
|
+
const preview = day.content.length > 60 ? `"${day.content.slice(0, 57)}..."` : `"${day.content}"`;
|
|
31664
|
+
lines.push(`${dayLabel} [${day.type}] ${preview}${threadInfo}`);
|
|
31665
|
+
lines.push(` Hook: ${day.hook_type} | ${day.rationale}`);
|
|
31666
|
+
}
|
|
31667
|
+
}
|
|
31668
|
+
lines.push("");
|
|
31669
|
+
lines.push(`${createdDrafts.length} drafts created. Review and edit at: vibebusiness start -> /social`);
|
|
31670
|
+
return { success: true, output: lines.join("\n") };
|
|
31671
|
+
}
|
|
31672
|
+
function formatDateShort(isoDate) {
|
|
31673
|
+
const d2 = /* @__PURE__ */ new Date(isoDate + "T00:00:00");
|
|
31674
|
+
const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
31675
|
+
return `${months[d2.getMonth()]} ${d2.getDate()}`;
|
|
31676
|
+
}
|
|
31420
31677
|
async function executeTrackMetrics() {
|
|
31421
31678
|
const state = loadState3();
|
|
31422
31679
|
const credentials = loadCredentials();
|
|
@@ -31539,6 +31796,7 @@ async function executeCheckStatus3() {
|
|
|
31539
31796
|
}
|
|
31540
31797
|
}
|
|
31541
31798
|
lines.push(` Pending drafts: ${freshness.draft_count}`);
|
|
31799
|
+
lines.push(` Scheduled drafts: ${freshness.scheduled_draft_count}`);
|
|
31542
31800
|
lines.push(` Post streak: ${freshness.post_streak_days} day(s)`);
|
|
31543
31801
|
lines.push(` Total posts: ${freshness.total_posts}`);
|
|
31544
31802
|
lines.push(` Threads published: ${freshness.thread_count}`);
|
|
@@ -34127,6 +34385,7 @@ These tasks generate tweet drafts for the founder's build-in-public journey.
|
|
|
34127
34385
|
|
|
34128
34386
|
Task prefixes:
|
|
34129
34387
|
- "social-setup-x" \u2192 Validate X API credentials from .env, save username
|
|
34388
|
+
- "social-plan-week" \u2192 Generate a 7-day content calendar with AI-optimized drafts (5 content days + 2 rest)
|
|
34130
34389
|
- "social-draft-ship-visual" \u2192 Generate a "just shipped" tweet WITH ship card image or video attached
|
|
34131
34390
|
- "social-draft-ship" \u2192 Generate a "just shipped" tweet (text only, no media)
|
|
34132
34391
|
- "social-draft-update" \u2192 Generate a product update draft from positioning data
|
|
@@ -34150,6 +34409,7 @@ When to recommend social tasks:
|
|
|
34150
34409
|
7. If social.total_posts > 0 AND social.avg_engagement_rate == null \u2192 suggest "social-track-metrics" to start tracking
|
|
34151
34410
|
8. If social.thread_count == 0 AND social.total_posts >= 3 \u2192 suggest "social-draft-thread-{relevant-topic}" (threads get 200-300% more reach)
|
|
34152
34411
|
9. Prefer AI-generated drafts (social-draft-viral-*) over template drafts (social-draft-ship/update/milestone) for higher engagement
|
|
34412
|
+
10. If social.scheduled_draft_count == 0 AND social.configured == true \u2192 suggest "social-plan-week" to plan next week's content calendar
|
|
34153
34413
|
|
|
34154
34414
|
PROMO COPY TASKS:
|
|
34155
34415
|
The "promo_copy" field shows positioning-driven copy generation status.
|
package/package.json
CHANGED
|
File without changes
|
|
File without changes
|