vibebusiness 1.2.46 → 1.2.49
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/.env +5 -1
- 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.js +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 +2 -2
- 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 +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 +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 +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.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 +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.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 +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 +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.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 +2 -2
- package/.next/standalone/.next/server/app/social/page.js +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 +3 -3
- package/.next/standalone/.next/server/app-paths-manifest.json +13 -13
- package/.next/standalone/.next/server/chunks/3644.js +1 -1
- package/.next/standalone/.next/server/chunks/{7809.js → 3794.js} +103 -2
- 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 +7 -4
- package/.next/standalone/data/copy/landing-landing-page-2026-02-20.md +1 -1
- package/.next/standalone/data/heartbeat-sessions.json +211 -10
- package/.next/standalone/data/hypotheses.json +2 -2
- package/.next/standalone/data/ideas.json +958 -449
- package/.next/standalone/data/implementations.json +136 -2
- package/.next/standalone/data/positioning.json +1 -1
- package/.next/standalone/data/reports/visuals/promo-launch.png +0 -0
- package/.next/standalone/data/reports/visuals/promo-ship-fast.png +0 -0
- package/.next/standalone/data/reports/visuals/promo-while-you-slept-v2.png +0 -0
- package/.next/standalone/data/reports/visuals/promo-while-you-slept-v3.png +0 -0
- package/.next/standalone/data/reports/visuals/promo-while-you-slept.png +0 -0
- package/.next/standalone/package.json +2 -2
- package/.next/standalone/scripts/analyze.ts +2 -2
- package/.next/standalone/scripts/implement.ts +5 -5
- package/.next/standalone/scripts/skills/social-media.ts +310 -9
- package/.next/static/chunks/{444-125b7b0fa8cbdb81.js → 444-1826396aef4dab2f.js} +1 -1
- package/.next/static/chunks/app/hypotheses/[id]/{page-65032da79ae146c5.js → page-8999ee91b824d378.js} +1 -1
- package/.next/static/chunks/app/hypotheses/page-6206bce440c44569.js +1 -0
- package/.next/static/chunks/app/ideas/[id]/{page-dc9746061e58eea5.js → page-89e3625db9017166.js} +1 -1
- package/.next/static/chunks/app/{page-fb66ff080390f20a.js → page-8ba52ac74c75461b.js} +1 -1
- package/.next/static/chunks/app/roadmap/[id]/{page-c8c4baf233e0d480.js → page-f437e783039534c4.js} +1 -1
- package/.next/static/chunks/app/roadmap/{page-b15554a207ed2813.js → page-6177b8774b4af79f.js} +1 -1
- package/.next/static/chunks/app/social/{page-6c61fb0c2546313e.js → page-69e480936711ccf2.js} +1 -1
- package/README.md +1 -1
- package/dist/bin/vibebusiness.js +22 -0
- package/dist/scripts/analyze.js +4 -3
- package/dist/scripts/chat.js +10 -2
- package/dist/scripts/generate-idea.js +1 -0
- package/dist/scripts/heartbeat.js +2265 -653
- package/dist/scripts/implement.js +5 -4
- package/dist/scripts/init.js +5 -0
- package/dist/scripts/scan.js +2 -1
- package/package.json +2 -2
- package/templates/commands/setup-posthog.md +116 -0
- package/.next/static/chunks/app/hypotheses/page-8314da4be7c533bd.js +0 -1
- /package/.next/static/{Tko0dnk2wKPorUxfNnm0x → zIIaTqrawBK1MmHg37JOr}/_buildManifest.js +0 -0
- /package/.next/static/{Tko0dnk2wKPorUxfNnm0x → zIIaTqrawBK1MmHg37JOr}/_ssgManifest.js +0 -0
|
@@ -105,8 +105,8 @@ var require_package = __commonJS({
|
|
|
105
105
|
var require_main = __commonJS({
|
|
106
106
|
"node_modules/dotenv/lib/main.js"(exports2, module2) {
|
|
107
107
|
"use strict";
|
|
108
|
-
var
|
|
109
|
-
var
|
|
108
|
+
var fs32 = require("fs");
|
|
109
|
+
var path27 = require("path");
|
|
110
110
|
var os3 = require("os");
|
|
111
111
|
var crypto5 = require("crypto");
|
|
112
112
|
var packageJson = require_package();
|
|
@@ -244,7 +244,7 @@ var require_main = __commonJS({
|
|
|
244
244
|
if (options && options.path && options.path.length > 0) {
|
|
245
245
|
if (Array.isArray(options.path)) {
|
|
246
246
|
for (const filepath of options.path) {
|
|
247
|
-
if (
|
|
247
|
+
if (fs32.existsSync(filepath)) {
|
|
248
248
|
possibleVaultPath = filepath.endsWith(".vault") ? filepath : `${filepath}.vault`;
|
|
249
249
|
}
|
|
250
250
|
}
|
|
@@ -252,15 +252,15 @@ var require_main = __commonJS({
|
|
|
252
252
|
possibleVaultPath = options.path.endsWith(".vault") ? options.path : `${options.path}.vault`;
|
|
253
253
|
}
|
|
254
254
|
} else {
|
|
255
|
-
possibleVaultPath =
|
|
255
|
+
possibleVaultPath = path27.resolve(process.cwd(), ".env.vault");
|
|
256
256
|
}
|
|
257
|
-
if (
|
|
257
|
+
if (fs32.existsSync(possibleVaultPath)) {
|
|
258
258
|
return possibleVaultPath;
|
|
259
259
|
}
|
|
260
260
|
return null;
|
|
261
261
|
}
|
|
262
262
|
function _resolveHome(envPath) {
|
|
263
|
-
return envPath[0] === "~" ?
|
|
263
|
+
return envPath[0] === "~" ? path27.join(os3.homedir(), envPath.slice(1)) : envPath;
|
|
264
264
|
}
|
|
265
265
|
function _configVault(options) {
|
|
266
266
|
const debug = parseBoolean(process.env.DOTENV_CONFIG_DEBUG || options && options.debug);
|
|
@@ -277,7 +277,7 @@ var require_main = __commonJS({
|
|
|
277
277
|
return { parsed };
|
|
278
278
|
}
|
|
279
279
|
function configDotenv(options) {
|
|
280
|
-
const dotenvPath =
|
|
280
|
+
const dotenvPath = path27.resolve(process.cwd(), ".env");
|
|
281
281
|
let encoding = "utf8";
|
|
282
282
|
let processEnv = process.env;
|
|
283
283
|
if (options && options.processEnv != null) {
|
|
@@ -305,13 +305,13 @@ var require_main = __commonJS({
|
|
|
305
305
|
}
|
|
306
306
|
let lastError;
|
|
307
307
|
const parsedAll = {};
|
|
308
|
-
for (const
|
|
308
|
+
for (const path28 of optionPaths) {
|
|
309
309
|
try {
|
|
310
|
-
const parsed = DotenvModule.parse(
|
|
310
|
+
const parsed = DotenvModule.parse(fs32.readFileSync(path28, { encoding }));
|
|
311
311
|
DotenvModule.populate(parsedAll, parsed, options);
|
|
312
312
|
} catch (e) {
|
|
313
313
|
if (debug) {
|
|
314
|
-
_debug(`Failed to load ${
|
|
314
|
+
_debug(`Failed to load ${path28} ${e.message}`);
|
|
315
315
|
}
|
|
316
316
|
lastError = e;
|
|
317
317
|
}
|
|
@@ -324,7 +324,7 @@ var require_main = __commonJS({
|
|
|
324
324
|
const shortPaths = [];
|
|
325
325
|
for (const filePath of optionPaths) {
|
|
326
326
|
try {
|
|
327
|
-
const relative =
|
|
327
|
+
const relative = path27.relative(process.cwd(), filePath);
|
|
328
328
|
shortPaths.push(relative);
|
|
329
329
|
} catch (e) {
|
|
330
330
|
if (debug) {
|
|
@@ -3697,12 +3697,12 @@ var init_request_handler_helper = __esm({
|
|
|
3697
3697
|
this.req.destroy(new Error("Request timeout."));
|
|
3698
3698
|
}
|
|
3699
3699
|
/* Response event handlers */
|
|
3700
|
-
classicResponseHandler(
|
|
3700
|
+
classicResponseHandler(resolve2, reject, res) {
|
|
3701
3701
|
this.res = res;
|
|
3702
3702
|
const dataStream = this.getResponseDataStream(res);
|
|
3703
3703
|
dataStream.on("data", (chunk) => this.responseData.push(chunk));
|
|
3704
|
-
dataStream.on("end", this.onResponseEndHandler.bind(this,
|
|
3705
|
-
dataStream.on("close", this.onResponseCloseHandler.bind(this,
|
|
3704
|
+
dataStream.on("end", this.onResponseEndHandler.bind(this, resolve2, reject));
|
|
3705
|
+
dataStream.on("close", this.onResponseCloseHandler.bind(this, resolve2, reject));
|
|
3706
3706
|
if (this.requestData.requestEventDebugHandler) {
|
|
3707
3707
|
this.requestData.requestEventDebugHandler("response", { res });
|
|
3708
3708
|
res.on("aborted", (error) => this.requestData.requestEventDebugHandler("response-aborted", { error }));
|
|
@@ -3711,7 +3711,7 @@ var init_request_handler_helper = __esm({
|
|
|
3711
3711
|
res.on("end", () => this.requestData.requestEventDebugHandler("response-end"));
|
|
3712
3712
|
}
|
|
3713
3713
|
}
|
|
3714
|
-
onResponseEndHandler(
|
|
3714
|
+
onResponseEndHandler(resolve2, reject) {
|
|
3715
3715
|
const rateLimit = this.getRateLimitFromResponse(this.res);
|
|
3716
3716
|
let data;
|
|
3717
3717
|
try {
|
|
@@ -3729,18 +3729,18 @@ var init_request_handler_helper = __esm({
|
|
|
3729
3729
|
TwitterApiV2Settings.logger.log(`[${this.requestData.options.method} ${this.hrefPathname}]: Request succeeds with code ${this.res.statusCode}`);
|
|
3730
3730
|
TwitterApiV2Settings.logger.log("Response body:", data);
|
|
3731
3731
|
}
|
|
3732
|
-
|
|
3732
|
+
resolve2({
|
|
3733
3733
|
data,
|
|
3734
3734
|
headers: this.res.headers,
|
|
3735
3735
|
rateLimit
|
|
3736
3736
|
});
|
|
3737
3737
|
}
|
|
3738
|
-
onResponseCloseHandler(
|
|
3738
|
+
onResponseCloseHandler(resolve2, reject) {
|
|
3739
3739
|
const res = this.res;
|
|
3740
3740
|
if (res.aborted) {
|
|
3741
3741
|
try {
|
|
3742
3742
|
this.getParsedResponse(this.res);
|
|
3743
|
-
return this.onResponseEndHandler(
|
|
3743
|
+
return this.onResponseEndHandler(resolve2, reject);
|
|
3744
3744
|
} catch (e) {
|
|
3745
3745
|
return reject(this.createPartialResponseError(e, true));
|
|
3746
3746
|
}
|
|
@@ -3749,14 +3749,14 @@ var init_request_handler_helper = __esm({
|
|
|
3749
3749
|
return reject(this.createPartialResponseError(new Error("Response has been interrupted before response could be parsed."), true));
|
|
3750
3750
|
}
|
|
3751
3751
|
}
|
|
3752
|
-
streamResponseHandler(
|
|
3752
|
+
streamResponseHandler(resolve2, reject, res) {
|
|
3753
3753
|
const code = res.statusCode;
|
|
3754
3754
|
if (code < 400) {
|
|
3755
3755
|
if (TwitterApiV2Settings.debug) {
|
|
3756
3756
|
TwitterApiV2Settings.logger.log(`[${this.requestData.options.method} ${this.hrefPathname}]: Request succeeds with code ${res.statusCode} (starting stream)`);
|
|
3757
3757
|
}
|
|
3758
3758
|
const dataStream = this.getResponseDataStream(res);
|
|
3759
|
-
|
|
3759
|
+
resolve2({ req: this.req, res: dataStream, originalResponse: res, requestData: this.requestData });
|
|
3760
3760
|
} else {
|
|
3761
3761
|
this.classicResponseHandler(() => void 0, reject, res);
|
|
3762
3762
|
}
|
|
@@ -3814,7 +3814,7 @@ var init_request_handler_helper = __esm({
|
|
|
3814
3814
|
makeRequest() {
|
|
3815
3815
|
this.buildRequest();
|
|
3816
3816
|
return new Promise((_resolve, _reject) => {
|
|
3817
|
-
const
|
|
3817
|
+
const resolve2 = (value) => {
|
|
3818
3818
|
cleanupListener.emit("complete");
|
|
3819
3819
|
_resolve(value);
|
|
3820
3820
|
};
|
|
@@ -3826,7 +3826,7 @@ var init_request_handler_helper = __esm({
|
|
|
3826
3826
|
const req = this.req;
|
|
3827
3827
|
req.on("error", this.requestErrorHandler.bind(this, reject));
|
|
3828
3828
|
req.on("socket", this.onSocketEventHandler.bind(this, reject, cleanupListener));
|
|
3829
|
-
req.on("response", this.classicResponseHandler.bind(this,
|
|
3829
|
+
req.on("response", this.classicResponseHandler.bind(this, resolve2, reject));
|
|
3830
3830
|
if (this.requestData.options.timeout) {
|
|
3831
3831
|
req.on("timeout", this.timeoutErrorHandler.bind(this));
|
|
3832
3832
|
}
|
|
@@ -3845,10 +3845,10 @@ var init_request_handler_helper = __esm({
|
|
|
3845
3845
|
}
|
|
3846
3846
|
makeRequestAndResolveWhenReady() {
|
|
3847
3847
|
this.buildRequest();
|
|
3848
|
-
return new Promise((
|
|
3848
|
+
return new Promise((resolve2, reject) => {
|
|
3849
3849
|
const req = this.req;
|
|
3850
3850
|
req.on("error", this.requestErrorHandler.bind(this, reject));
|
|
3851
|
-
req.on("response", this.streamResponseHandler.bind(this,
|
|
3851
|
+
req.on("response", this.streamResponseHandler.bind(this, resolve2, reject));
|
|
3852
3852
|
if (this.requestData.body) {
|
|
3853
3853
|
req.write(this.requestData.body);
|
|
3854
3854
|
}
|
|
@@ -6207,12 +6207,12 @@ var init_client_v1_read = __esm({
|
|
|
6207
6207
|
async function readFileIntoBuffer(file) {
|
|
6208
6208
|
const handle = await getFileHandle(file);
|
|
6209
6209
|
if (typeof handle === "number") {
|
|
6210
|
-
return new Promise((
|
|
6211
|
-
|
|
6210
|
+
return new Promise((resolve2, reject) => {
|
|
6211
|
+
fs13.readFile(handle, (err2, data) => {
|
|
6212
6212
|
if (err2) {
|
|
6213
6213
|
return reject(err2);
|
|
6214
6214
|
}
|
|
6215
|
-
|
|
6215
|
+
resolve2(data);
|
|
6216
6216
|
});
|
|
6217
6217
|
});
|
|
6218
6218
|
} else if (handle instanceof Buffer) {
|
|
@@ -6223,7 +6223,7 @@ async function readFileIntoBuffer(file) {
|
|
|
6223
6223
|
}
|
|
6224
6224
|
function getFileHandle(file) {
|
|
6225
6225
|
if (typeof file === "string") {
|
|
6226
|
-
return
|
|
6226
|
+
return fs13.promises.open(file, "r");
|
|
6227
6227
|
} else if (typeof file === "number") {
|
|
6228
6228
|
return file;
|
|
6229
6229
|
} else if (typeof file === "object" && !(file instanceof Buffer)) {
|
|
@@ -6236,11 +6236,11 @@ function getFileHandle(file) {
|
|
|
6236
6236
|
}
|
|
6237
6237
|
async function getFileSizeFromFileHandle(fileHandle) {
|
|
6238
6238
|
if (typeof fileHandle === "number") {
|
|
6239
|
-
const stats = await new Promise((
|
|
6240
|
-
|
|
6239
|
+
const stats = await new Promise((resolve2, reject) => {
|
|
6240
|
+
fs13.fstat(fileHandle, (err2, stats2) => {
|
|
6241
6241
|
if (err2)
|
|
6242
6242
|
reject(err2);
|
|
6243
|
-
|
|
6243
|
+
resolve2(stats2);
|
|
6244
6244
|
});
|
|
6245
6245
|
});
|
|
6246
6246
|
return stats.size;
|
|
@@ -6317,7 +6317,7 @@ function getMediaCategoryByMime(name, target) {
|
|
|
6317
6317
|
return target === "tweet" ? "TweetImage" : "DmImage";
|
|
6318
6318
|
}
|
|
6319
6319
|
function sleepSecs(seconds) {
|
|
6320
|
-
return new Promise((
|
|
6320
|
+
return new Promise((resolve2) => setTimeout(resolve2, seconds * 1e3));
|
|
6321
6321
|
}
|
|
6322
6322
|
async function readNextPartOf(file, chunkLength, bufferOffset = 0, buffer) {
|
|
6323
6323
|
if (file instanceof Buffer) {
|
|
@@ -6329,11 +6329,11 @@ async function readNextPartOf(file, chunkLength, bufferOffset = 0, buffer) {
|
|
|
6329
6329
|
}
|
|
6330
6330
|
let bytesRead;
|
|
6331
6331
|
if (typeof file === "number") {
|
|
6332
|
-
bytesRead = await new Promise((
|
|
6333
|
-
|
|
6332
|
+
bytesRead = await new Promise((resolve2, reject) => {
|
|
6333
|
+
fs13.read(file, buffer, 0, chunkLength, bufferOffset, (err2, nread) => {
|
|
6334
6334
|
if (err2)
|
|
6335
6335
|
reject(err2);
|
|
6336
|
-
|
|
6336
|
+
resolve2(nread);
|
|
6337
6337
|
});
|
|
6338
6338
|
});
|
|
6339
6339
|
} else {
|
|
@@ -6342,22 +6342,22 @@ async function readNextPartOf(file, chunkLength, bufferOffset = 0, buffer) {
|
|
|
6342
6342
|
}
|
|
6343
6343
|
return [buffer, bytesRead];
|
|
6344
6344
|
}
|
|
6345
|
-
var
|
|
6345
|
+
var fs13;
|
|
6346
6346
|
var init_media_helpers_v1 = __esm({
|
|
6347
6347
|
"node_modules/twitter-api-v2/dist/esm/v1/media-helpers.v1.js"() {
|
|
6348
6348
|
"use strict";
|
|
6349
|
-
|
|
6349
|
+
fs13 = __toESM(require("fs"));
|
|
6350
6350
|
init_helpers();
|
|
6351
6351
|
init_types();
|
|
6352
6352
|
}
|
|
6353
6353
|
});
|
|
6354
6354
|
|
|
6355
6355
|
// node_modules/twitter-api-v2/dist/esm/v1/client.v1.write.js
|
|
6356
|
-
var
|
|
6356
|
+
var fs14, UPLOAD_ENDPOINT, TwitterApiv1ReadWrite;
|
|
6357
6357
|
var init_client_v1_write = __esm({
|
|
6358
6358
|
"node_modules/twitter-api-v2/dist/esm/v1/client.v1.write.js"() {
|
|
6359
6359
|
"use strict";
|
|
6360
|
-
|
|
6360
|
+
fs14 = __toESM(require("fs"));
|
|
6361
6361
|
init_globals();
|
|
6362
6362
|
init_helpers();
|
|
6363
6363
|
init_types();
|
|
@@ -6623,7 +6623,7 @@ var init_client_v1_write = __esm({
|
|
|
6623
6623
|
}
|
|
6624
6624
|
} finally {
|
|
6625
6625
|
if (typeof file === "number") {
|
|
6626
|
-
|
|
6626
|
+
fs14.close(file, () => {
|
|
6627
6627
|
});
|
|
6628
6628
|
} else if (typeof fileHandle === "object" && !(fileHandle instanceof Buffer)) {
|
|
6629
6629
|
fileHandle.close();
|
|
@@ -6671,7 +6671,7 @@ var init_client_v1_write = __esm({
|
|
|
6671
6671
|
};
|
|
6672
6672
|
} catch (e) {
|
|
6673
6673
|
if (typeof file === "number") {
|
|
6674
|
-
|
|
6674
|
+
fs14.close(file, () => {
|
|
6675
6675
|
});
|
|
6676
6676
|
} else if (typeof fileHandle === "object" && !(fileHandle instanceof Buffer)) {
|
|
6677
6677
|
fileHandle.close();
|
|
@@ -8249,7 +8249,7 @@ var init_client_v2_read = __esm({
|
|
|
8249
8249
|
if (runningJob.status === "expired" || runningJob.status === "failed") {
|
|
8250
8250
|
throw new Error("Job failed to be completed.");
|
|
8251
8251
|
}
|
|
8252
|
-
await new Promise((
|
|
8252
|
+
await new Promise((resolve2) => setTimeout(resolve2, 3500));
|
|
8253
8253
|
runningJob = (await this.complianceJob(job.id)).data;
|
|
8254
8254
|
}
|
|
8255
8255
|
const result = await this.get(job.download_url, void 0, {
|
|
@@ -8458,7 +8458,7 @@ var init_client_v2_write = __esm({
|
|
|
8458
8458
|
case "in_progress": {
|
|
8459
8459
|
const waitTime = info === null || info === void 0 ? void 0 : info.check_after_secs;
|
|
8460
8460
|
if (waitTime && waitTime > 0) {
|
|
8461
|
-
await new Promise((
|
|
8461
|
+
await new Promise((resolve2) => setTimeout(resolve2, waitTime * 1e3));
|
|
8462
8462
|
await this.waitForMediaProcessing(mediaId);
|
|
8463
8463
|
}
|
|
8464
8464
|
}
|
|
@@ -9381,9 +9381,9 @@ var init_esm = __esm({
|
|
|
9381
9381
|
})();
|
|
9382
9382
|
|
|
9383
9383
|
// scripts/heartbeat.ts
|
|
9384
|
-
var
|
|
9385
|
-
var
|
|
9386
|
-
var
|
|
9384
|
+
var fs31 = __toESM(require("fs"));
|
|
9385
|
+
var path26 = __toESM(require("path"));
|
|
9386
|
+
var import_child_process10 = require("child_process");
|
|
9387
9387
|
var import_util = require("util");
|
|
9388
9388
|
|
|
9389
9389
|
// src/lib/kpi-adapters/rest-api.ts
|
|
@@ -9495,88 +9495,155 @@ var ManualAdapter = class {
|
|
|
9495
9495
|
}
|
|
9496
9496
|
};
|
|
9497
9497
|
|
|
9498
|
-
// src/lib/
|
|
9499
|
-
var
|
|
9500
|
-
|
|
9501
|
-
|
|
9502
|
-
|
|
9503
|
-
|
|
9504
|
-
|
|
9505
|
-
|
|
9506
|
-
|
|
9507
|
-
|
|
9508
|
-
|
|
9509
|
-
|
|
9510
|
-
|
|
9511
|
-
|
|
9512
|
-
|
|
9513
|
-
|
|
9514
|
-
|
|
9515
|
-
|
|
9516
|
-
|
|
9517
|
-
|
|
9518
|
-
|
|
9519
|
-
|
|
9520
|
-
|
|
9521
|
-
|
|
9522
|
-
|
|
9523
|
-
|
|
9524
|
-
`${LOOPS_BASE_URL}/contacts?${params.toString()}`,
|
|
9525
|
-
{ method: "GET", headers: headers() }
|
|
9526
|
-
);
|
|
9527
|
-
if (!res.ok) {
|
|
9528
|
-
throw new Error(`Loops API error: ${res.status} ${await res.text()}`);
|
|
9498
|
+
// src/lib/kpi-adapters/posthog.ts
|
|
9499
|
+
var PostHogAdapter = class {
|
|
9500
|
+
id;
|
|
9501
|
+
type = "posthog";
|
|
9502
|
+
fieldMapping;
|
|
9503
|
+
host;
|
|
9504
|
+
projectId;
|
|
9505
|
+
apiKey;
|
|
9506
|
+
constructor(config) {
|
|
9507
|
+
this.id = config.id;
|
|
9508
|
+
this.fieldMapping = config.field_mapping || {};
|
|
9509
|
+
const hostEnv = config.config?.url_env || "POSTHOG_API_HOST";
|
|
9510
|
+
const projectEnv = config.config?.project_id_env || "POSTHOG_PROJECT_ID";
|
|
9511
|
+
this.host = process.env[hostEnv] || process.env["NEXT_PUBLIC_POSTHOG_HOST"] || "https://us.i.posthog.com";
|
|
9512
|
+
this.projectId = process.env[projectEnv] || "";
|
|
9513
|
+
this.apiKey = process.env["POSTHOG_PERSONAL_API_KEY"] || "";
|
|
9514
|
+
if (!this.apiKey) {
|
|
9515
|
+
throw new Error(
|
|
9516
|
+
"PostHog adapter requires POSTHOG_PERSONAL_API_KEY environment variable. Get a personal API key from: https://app.posthog.com/settings/user-api-keys"
|
|
9517
|
+
);
|
|
9518
|
+
}
|
|
9519
|
+
if (!this.projectId) {
|
|
9520
|
+
throw new Error(
|
|
9521
|
+
"PostHog adapter requires POSTHOG_PROJECT_ID environment variable. Find your project ID in PostHog settings."
|
|
9522
|
+
);
|
|
9523
|
+
}
|
|
9529
9524
|
}
|
|
9530
|
-
|
|
9531
|
-
|
|
9532
|
-
}
|
|
9533
|
-
|
|
9534
|
-
|
|
9535
|
-
|
|
9536
|
-
|
|
9537
|
-
|
|
9538
|
-
|
|
9539
|
-
|
|
9525
|
+
async fetch(startDate, endDate) {
|
|
9526
|
+
const values = {};
|
|
9527
|
+
const metadata = {};
|
|
9528
|
+
const end = endDate || (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
9529
|
+
const start = startDate || new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3).toISOString().split("T")[0];
|
|
9530
|
+
const eventNames = Array.from(new Set(Object.values(this.fieldMapping)));
|
|
9531
|
+
if (eventNames.length === 0) {
|
|
9532
|
+
return { values, metadata, fetched_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
9533
|
+
}
|
|
9534
|
+
for (const eventName of eventNames) {
|
|
9535
|
+
try {
|
|
9536
|
+
const count = await this.fetchEventCount(eventName, start, end);
|
|
9537
|
+
metadata[eventName] = { count, start, end };
|
|
9538
|
+
for (const [kpiId, mappedEvent] of Object.entries(this.fieldMapping)) {
|
|
9539
|
+
if (mappedEvent === eventName) {
|
|
9540
|
+
values[kpiId] = count;
|
|
9541
|
+
}
|
|
9542
|
+
}
|
|
9543
|
+
} catch (error) {
|
|
9544
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
9545
|
+
console.warn(`PostHog: failed to fetch "${eventName}": ${msg}`);
|
|
9546
|
+
for (const [kpiId, mappedEvent] of Object.entries(this.fieldMapping)) {
|
|
9547
|
+
if (mappedEvent === eventName) {
|
|
9548
|
+
values[kpiId] = null;
|
|
9549
|
+
}
|
|
9550
|
+
}
|
|
9551
|
+
}
|
|
9552
|
+
}
|
|
9553
|
+
return {
|
|
9554
|
+
values,
|
|
9555
|
+
metadata,
|
|
9556
|
+
fetched_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
9557
|
+
};
|
|
9540
9558
|
}
|
|
9541
|
-
|
|
9542
|
-
|
|
9559
|
+
async healthCheck() {
|
|
9560
|
+
try {
|
|
9561
|
+
const res = await fetch(`${this.host}/api/projects/${this.projectId}/`, {
|
|
9562
|
+
headers: {
|
|
9563
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
9564
|
+
}
|
|
9565
|
+
});
|
|
9566
|
+
return res.ok;
|
|
9567
|
+
} catch {
|
|
9568
|
+
return false;
|
|
9569
|
+
}
|
|
9570
|
+
}
|
|
9571
|
+
/**
|
|
9572
|
+
* Fetch total event count from PostHog Trends API for a given date range.
|
|
9573
|
+
*/
|
|
9574
|
+
async fetchEventCount(eventName, dateFrom, dateTo) {
|
|
9575
|
+
const url = `${this.host}/api/projects/${this.projectId}/insights/trend/`;
|
|
9576
|
+
const body = {
|
|
9577
|
+
events: [
|
|
9578
|
+
{
|
|
9579
|
+
id: eventName,
|
|
9580
|
+
math: "total",
|
|
9581
|
+
type: "events"
|
|
9582
|
+
}
|
|
9583
|
+
],
|
|
9584
|
+
date_from: dateFrom,
|
|
9585
|
+
date_to: dateTo,
|
|
9586
|
+
display: "ActionsLineGraph"
|
|
9587
|
+
};
|
|
9588
|
+
const res = await fetch(url, {
|
|
9589
|
+
method: "POST",
|
|
9590
|
+
headers: {
|
|
9591
|
+
"Content-Type": "application/json",
|
|
9592
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
9593
|
+
},
|
|
9594
|
+
body: JSON.stringify(body)
|
|
9595
|
+
});
|
|
9596
|
+
if (!res.ok) {
|
|
9597
|
+
throw new Error(`PostHog API returned ${res.status}: ${await res.text()}`);
|
|
9598
|
+
}
|
|
9599
|
+
const data = await res.json();
|
|
9600
|
+
const result = data?.result?.[0];
|
|
9601
|
+
if (!result) return 0;
|
|
9602
|
+
return result.count ?? result.aggregated_value ?? 0;
|
|
9603
|
+
}
|
|
9604
|
+
};
|
|
9543
9605
|
|
|
9544
|
-
// src/lib/kpi-adapters/
|
|
9545
|
-
var
|
|
9606
|
+
// src/lib/kpi-adapters/waitlist-file.ts
|
|
9607
|
+
var fs = __toESM(require("fs"));
|
|
9608
|
+
var path = __toESM(require("path"));
|
|
9609
|
+
var WaitlistFileAdapter = class {
|
|
9546
9610
|
id;
|
|
9547
|
-
type = "
|
|
9611
|
+
type = "waitlist_file";
|
|
9612
|
+
filePath;
|
|
9548
9613
|
fieldMapping;
|
|
9549
9614
|
constructor(config) {
|
|
9550
9615
|
this.id = config.id;
|
|
9551
9616
|
this.fieldMapping = config.field_mapping || {};
|
|
9617
|
+
const relativePath = config.config?.path || "website/data/waitlist.json";
|
|
9618
|
+
this.filePath = path.isAbsolute(relativePath) ? relativePath : path.resolve(process.cwd(), relativePath);
|
|
9552
9619
|
}
|
|
9553
9620
|
async fetch() {
|
|
9554
9621
|
const values = {};
|
|
9555
9622
|
const metadata = {};
|
|
9556
|
-
const
|
|
9557
|
-
|
|
9558
|
-
|
|
9559
|
-
|
|
9560
|
-
|
|
9561
|
-
|
|
9562
|
-
let totalContacts = null;
|
|
9563
|
-
let lists = [];
|
|
9564
|
-
if (needsContacts) {
|
|
9565
|
-
const result = await listContacts({ limit: 1 });
|
|
9566
|
-
totalContacts = result.count;
|
|
9567
|
-
metadata.total_contacts = totalContacts;
|
|
9568
|
-
}
|
|
9569
|
-
if (needsLists) {
|
|
9570
|
-
lists = await getMailingLists();
|
|
9571
|
-
metadata.mailing_lists = lists.map((l2) => ({ id: l2.id, name: l2.name }));
|
|
9623
|
+
const data = this.readFile();
|
|
9624
|
+
if (!data) {
|
|
9625
|
+
for (const kpiId of Object.keys(this.fieldMapping)) {
|
|
9626
|
+
values[kpiId] = null;
|
|
9627
|
+
}
|
|
9628
|
+
return { values, metadata: { error: "File not found or unreadable" }, fetched_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
9572
9629
|
}
|
|
9630
|
+
const entries = data.emails || [];
|
|
9631
|
+
const now = Date.now();
|
|
9632
|
+
metadata.total_count = entries.length;
|
|
9633
|
+
metadata.file_path = this.filePath;
|
|
9573
9634
|
for (const [kpiId, fieldName] of Object.entries(this.fieldMapping)) {
|
|
9574
|
-
|
|
9575
|
-
|
|
9576
|
-
|
|
9577
|
-
|
|
9578
|
-
|
|
9579
|
-
|
|
9635
|
+
switch (fieldName) {
|
|
9636
|
+
case "total_count":
|
|
9637
|
+
values[kpiId] = entries.length;
|
|
9638
|
+
break;
|
|
9639
|
+
case "count_7d":
|
|
9640
|
+
values[kpiId] = this.countSince(entries, now - 7 * 24 * 60 * 60 * 1e3);
|
|
9641
|
+
break;
|
|
9642
|
+
case "count_30d":
|
|
9643
|
+
values[kpiId] = this.countSince(entries, now - 30 * 24 * 60 * 60 * 1e3);
|
|
9644
|
+
break;
|
|
9645
|
+
default:
|
|
9646
|
+
values[kpiId] = null;
|
|
9580
9647
|
}
|
|
9581
9648
|
}
|
|
9582
9649
|
return {
|
|
@@ -9586,13 +9653,22 @@ var LoopsAdapter = class {
|
|
|
9586
9653
|
};
|
|
9587
9654
|
}
|
|
9588
9655
|
async healthCheck() {
|
|
9656
|
+
return this.readFile() !== null;
|
|
9657
|
+
}
|
|
9658
|
+
readFile() {
|
|
9589
9659
|
try {
|
|
9590
|
-
const
|
|
9591
|
-
return
|
|
9660
|
+
const raw = fs.readFileSync(this.filePath, "utf-8");
|
|
9661
|
+
return JSON.parse(raw);
|
|
9592
9662
|
} catch {
|
|
9593
|
-
return
|
|
9663
|
+
return null;
|
|
9594
9664
|
}
|
|
9595
9665
|
}
|
|
9666
|
+
countSince(entries, sinceTimestamp) {
|
|
9667
|
+
return entries.filter((e) => {
|
|
9668
|
+
if (!e.created_at) return false;
|
|
9669
|
+
return new Date(e.created_at).getTime() >= sinceTimestamp;
|
|
9670
|
+
}).length;
|
|
9671
|
+
}
|
|
9596
9672
|
};
|
|
9597
9673
|
|
|
9598
9674
|
// src/lib/kpi-adapters/index.ts
|
|
@@ -9602,8 +9678,10 @@ function createAdapter(config) {
|
|
|
9602
9678
|
return new RestAPIAdapter(config);
|
|
9603
9679
|
case "manual":
|
|
9604
9680
|
return new ManualAdapter(config);
|
|
9605
|
-
case "
|
|
9606
|
-
return new
|
|
9681
|
+
case "posthog":
|
|
9682
|
+
return new PostHogAdapter(config);
|
|
9683
|
+
case "waitlist_file":
|
|
9684
|
+
return new WaitlistFileAdapter(config);
|
|
9607
9685
|
default:
|
|
9608
9686
|
throw new Error(`Unknown KPI adapter type: ${config.type}`);
|
|
9609
9687
|
}
|
|
@@ -9638,7 +9716,7 @@ async function fetchAllKPIs(configs, startDate, endDate) {
|
|
|
9638
9716
|
// src/lib/prompts.ts
|
|
9639
9717
|
function buildBaseContext(businessConfig) {
|
|
9640
9718
|
if (!businessConfig) {
|
|
9641
|
-
return `You are an AI
|
|
9719
|
+
return `You are an AI Product Manager.
|
|
9642
9720
|
|
|
9643
9721
|
Your role is to analyze the codebase and generate actionable improvement ideas.`;
|
|
9644
9722
|
}
|
|
@@ -9655,14 +9733,14 @@ Your role is to analyze the codebase and generate actionable improvement ideas.`
|
|
|
9655
9733
|
The product consists of:
|
|
9656
9734
|
${components}`;
|
|
9657
9735
|
}
|
|
9658
|
-
return `You are an AI
|
|
9736
|
+
return `You are an AI Product Manager for ${product.name}, ${product.summary}.${architectureSection}
|
|
9659
9737
|
|
|
9660
9738
|
Your role is to analyze the codebase and generate actionable improvement ideas.`;
|
|
9661
9739
|
}
|
|
9662
9740
|
|
|
9663
9741
|
// scripts/skills/content-marketing.ts
|
|
9664
9742
|
var import_child_process = require("child_process");
|
|
9665
|
-
var
|
|
9743
|
+
var fs2 = __toESM(require("fs"));
|
|
9666
9744
|
var CONTENT_TASK_PREFIX = "content-";
|
|
9667
9745
|
function isContentTask(taskId) {
|
|
9668
9746
|
return taskId.startsWith(CONTENT_TASK_PREFIX);
|
|
@@ -9714,7 +9792,7 @@ async function executeContentTask(taskId, description, contentflowConfig) {
|
|
|
9714
9792
|
};
|
|
9715
9793
|
}
|
|
9716
9794
|
const repoPath = contentflowConfig?.repo_path;
|
|
9717
|
-
if (!repoPath || !
|
|
9795
|
+
if (!repoPath || !fs2.existsSync(repoPath)) {
|
|
9718
9796
|
return {
|
|
9719
9797
|
success: false,
|
|
9720
9798
|
output: `ContentFlow repo not found at: ${repoPath || "(not configured)"}`
|
|
@@ -9765,24 +9843,24 @@ async function executeContentTask(taskId, description, contentflowConfig) {
|
|
|
9765
9843
|
|
|
9766
9844
|
// scripts/skills/business-intelligence.ts
|
|
9767
9845
|
var import_child_process2 = require("child_process");
|
|
9768
|
-
var
|
|
9769
|
-
var
|
|
9846
|
+
var fs4 = __toESM(require("fs"));
|
|
9847
|
+
var path3 = __toESM(require("path"));
|
|
9770
9848
|
|
|
9771
9849
|
// scripts/lib/paths.ts
|
|
9772
|
-
var
|
|
9773
|
-
var
|
|
9850
|
+
var path2 = __toESM(require("path"));
|
|
9851
|
+
var fs3 = __toESM(require("fs"));
|
|
9774
9852
|
function findProjectRoot() {
|
|
9775
9853
|
const cwd = process.cwd();
|
|
9776
|
-
if (
|
|
9854
|
+
if (fs3.existsSync(path2.join(cwd, "data", "config.json"))) {
|
|
9777
9855
|
return cwd;
|
|
9778
9856
|
}
|
|
9779
9857
|
try {
|
|
9780
|
-
const entries =
|
|
9858
|
+
const entries = fs3.readdirSync(cwd, { withFileTypes: true });
|
|
9781
9859
|
for (const entry of entries) {
|
|
9782
9860
|
if (entry.isDirectory()) {
|
|
9783
|
-
const candidate =
|
|
9784
|
-
if (
|
|
9785
|
-
return
|
|
9861
|
+
const candidate = path2.join(cwd, entry.name, "data", "config.json");
|
|
9862
|
+
if (fs3.existsSync(candidate)) {
|
|
9863
|
+
return path2.join(cwd, entry.name);
|
|
9786
9864
|
}
|
|
9787
9865
|
}
|
|
9788
9866
|
}
|
|
@@ -9790,9 +9868,9 @@ function findProjectRoot() {
|
|
|
9790
9868
|
}
|
|
9791
9869
|
let dir = cwd;
|
|
9792
9870
|
for (let i = 0; i < 5; i++) {
|
|
9793
|
-
const parent =
|
|
9871
|
+
const parent = path2.dirname(dir);
|
|
9794
9872
|
if (parent === dir) break;
|
|
9795
|
-
if (
|
|
9873
|
+
if (fs3.existsSync(path2.join(parent, "data", "config.json"))) {
|
|
9796
9874
|
return parent;
|
|
9797
9875
|
}
|
|
9798
9876
|
dir = parent;
|
|
@@ -9800,25 +9878,26 @@ function findProjectRoot() {
|
|
|
9800
9878
|
return cwd;
|
|
9801
9879
|
}
|
|
9802
9880
|
var PROJECT_DIR = findProjectRoot();
|
|
9803
|
-
var DATA_DIR =
|
|
9804
|
-
var STATUS_FILE =
|
|
9805
|
-
var TODO_FILE =
|
|
9806
|
-
var MEMORY_FILE =
|
|
9807
|
-
var IDEAS_FILE =
|
|
9808
|
-
var GOALS_FILE =
|
|
9809
|
-
var SESSIONS_FILE =
|
|
9810
|
-
var CONFIG_FILE =
|
|
9811
|
-
var BUSINESS_CONTEXT_FILE =
|
|
9812
|
-
var HYPOTHESES_FILE =
|
|
9813
|
-
var IMPLEMENTATIONS_FILE =
|
|
9814
|
-
var ROADMAP_FILE =
|
|
9815
|
-
var COMPETITORS_FILE =
|
|
9816
|
-
var POSITIONING_FILE =
|
|
9817
|
-
var PAGES_FILE =
|
|
9818
|
-
var PAYMENTS_FILE =
|
|
9819
|
-
var
|
|
9820
|
-
var
|
|
9821
|
-
var
|
|
9881
|
+
var DATA_DIR = path2.join(PROJECT_DIR, "data");
|
|
9882
|
+
var STATUS_FILE = path2.join(PROJECT_DIR, "STATUS.md");
|
|
9883
|
+
var TODO_FILE = path2.join(PROJECT_DIR, "TODO.md");
|
|
9884
|
+
var MEMORY_FILE = path2.join(PROJECT_DIR, "MEMORY.md");
|
|
9885
|
+
var IDEAS_FILE = path2.join(DATA_DIR, "ideas.json");
|
|
9886
|
+
var GOALS_FILE = path2.join(DATA_DIR, "goals.json");
|
|
9887
|
+
var SESSIONS_FILE = path2.join(DATA_DIR, "sessions.json");
|
|
9888
|
+
var CONFIG_FILE = path2.join(DATA_DIR, "config.json");
|
|
9889
|
+
var BUSINESS_CONTEXT_FILE = path2.join(DATA_DIR, "business-context.json");
|
|
9890
|
+
var HYPOTHESES_FILE = path2.join(DATA_DIR, "hypotheses.json");
|
|
9891
|
+
var IMPLEMENTATIONS_FILE = path2.join(DATA_DIR, "implementations.json");
|
|
9892
|
+
var ROADMAP_FILE = path2.join(DATA_DIR, "roadmap.json");
|
|
9893
|
+
var COMPETITORS_FILE = path2.join(DATA_DIR, "competitors.json");
|
|
9894
|
+
var POSITIONING_FILE = path2.join(DATA_DIR, "positioning.json");
|
|
9895
|
+
var PAGES_FILE = path2.join(DATA_DIR, "pages.json");
|
|
9896
|
+
var PAYMENTS_FILE = path2.join(DATA_DIR, "payments.json");
|
|
9897
|
+
var POSTHOG_FILE = path2.join(DATA_DIR, "posthog.json");
|
|
9898
|
+
var SOCIAL_FILE = path2.join(DATA_DIR, "social.json");
|
|
9899
|
+
var TEMPLATES_DIR = path2.join(__dirname, "..", "..", "templates");
|
|
9900
|
+
var COMMAND_TEMPLATES_DIR = path2.join(TEMPLATES_DIR, "commands");
|
|
9822
9901
|
|
|
9823
9902
|
// scripts/skills/business-intelligence.ts
|
|
9824
9903
|
var BI_PREFIXES = [
|
|
@@ -9885,14 +9964,14 @@ async function executeBusinessIntelTask(taskId, description, businessContext) {
|
|
|
9885
9964
|
};
|
|
9886
9965
|
}
|
|
9887
9966
|
const { command, args: args2 } = parsed;
|
|
9888
|
-
const commandFile =
|
|
9889
|
-
if (!
|
|
9967
|
+
const commandFile = path3.join(PROJECT_DIR, ".claude", "commands", `${command}.md`);
|
|
9968
|
+
if (!fs4.existsSync(commandFile)) {
|
|
9890
9969
|
return {
|
|
9891
9970
|
success: false,
|
|
9892
9971
|
output: `Slash command template not found: ${commandFile}. Run 'init' to scaffold commands.`
|
|
9893
9972
|
};
|
|
9894
9973
|
}
|
|
9895
|
-
const template =
|
|
9974
|
+
const template = fs4.readFileSync(commandFile, "utf-8");
|
|
9896
9975
|
const prompt = args2 ? `${template}
|
|
9897
9976
|
|
|
9898
9977
|
---
|
|
@@ -9946,9 +10025,9 @@ function readBusinessIntelFreshness() {
|
|
|
9946
10025
|
positioning: { exists: false, lastUpdated: null, staleDays: null },
|
|
9947
10026
|
pages: { count: 0, livePagesWithoutMetrics: 0 }
|
|
9948
10027
|
};
|
|
9949
|
-
if (
|
|
10028
|
+
if (fs4.existsSync(COMPETITORS_FILE)) {
|
|
9950
10029
|
try {
|
|
9951
|
-
const data = JSON.parse(
|
|
10030
|
+
const data = JSON.parse(fs4.readFileSync(COMPETITORS_FILE, "utf-8"));
|
|
9952
10031
|
result.competitors.count = data.competitors?.length || 0;
|
|
9953
10032
|
result.competitors.lastUpdated = data.last_updated || null;
|
|
9954
10033
|
if (data.last_updated) {
|
|
@@ -9958,9 +10037,9 @@ function readBusinessIntelFreshness() {
|
|
|
9958
10037
|
} catch {
|
|
9959
10038
|
}
|
|
9960
10039
|
}
|
|
9961
|
-
if (
|
|
10040
|
+
if (fs4.existsSync(POSITIONING_FILE)) {
|
|
9962
10041
|
try {
|
|
9963
|
-
const data = JSON.parse(
|
|
10042
|
+
const data = JSON.parse(fs4.readFileSync(POSITIONING_FILE, "utf-8"));
|
|
9964
10043
|
result.positioning.exists = !!data.current;
|
|
9965
10044
|
result.positioning.lastUpdated = data.last_updated || null;
|
|
9966
10045
|
if (data.last_updated) {
|
|
@@ -9970,9 +10049,9 @@ function readBusinessIntelFreshness() {
|
|
|
9970
10049
|
} catch {
|
|
9971
10050
|
}
|
|
9972
10051
|
}
|
|
9973
|
-
if (
|
|
10052
|
+
if (fs4.existsSync(PAGES_FILE)) {
|
|
9974
10053
|
try {
|
|
9975
|
-
const data = JSON.parse(
|
|
10054
|
+
const data = JSON.parse(fs4.readFileSync(PAGES_FILE, "utf-8"));
|
|
9976
10055
|
result.pages.count = data.pages?.length || 0;
|
|
9977
10056
|
result.pages.livePagesWithoutMetrics = (data.pages || []).filter(
|
|
9978
10057
|
(p) => p.status === "live" && p.metrics.conversion_rate === null
|
|
@@ -9984,8 +10063,8 @@ function readBusinessIntelFreshness() {
|
|
|
9984
10063
|
}
|
|
9985
10064
|
|
|
9986
10065
|
// scripts/skills/marketing-visual.ts
|
|
9987
|
-
var
|
|
9988
|
-
var
|
|
10066
|
+
var fs7 = __toESM(require("fs"));
|
|
10067
|
+
var path5 = __toESM(require("path"));
|
|
9989
10068
|
|
|
9990
10069
|
// node_modules/linebreak/dist/module.mjs
|
|
9991
10070
|
var import_unicode_trie = __toESM(require_unicode_trie(), 1);
|
|
@@ -12964,8 +13043,8 @@ function argument(predicate, message) {
|
|
|
12964
13043
|
}
|
|
12965
13044
|
}
|
|
12966
13045
|
var check = { fail, argument, assert: argument };
|
|
12967
|
-
function getPathDefinition(glyph,
|
|
12968
|
-
var _path =
|
|
13046
|
+
function getPathDefinition(glyph, path27) {
|
|
13047
|
+
var _path = path27 || new Path();
|
|
12969
13048
|
return {
|
|
12970
13049
|
configurable: true,
|
|
12971
13050
|
get: function() {
|
|
@@ -13188,9 +13267,9 @@ function ttfGlyphLoader(font, index, parseGlyph2, data, position, buildPath2) {
|
|
|
13188
13267
|
var glyph = new Glyph({ index, font });
|
|
13189
13268
|
glyph.path = function() {
|
|
13190
13269
|
parseGlyph2(glyph, data, position);
|
|
13191
|
-
var
|
|
13192
|
-
|
|
13193
|
-
return
|
|
13270
|
+
var path27 = buildPath2(font.glyphs, glyph);
|
|
13271
|
+
path27.unitsPerEm = font.unitsPerEm;
|
|
13272
|
+
return path27;
|
|
13194
13273
|
};
|
|
13195
13274
|
defineDependentProperty(glyph, "xMin", "_xMin");
|
|
13196
13275
|
defineDependentProperty(glyph, "xMax", "_xMax");
|
|
@@ -13203,9 +13282,9 @@ function cffGlyphLoader(font, index, parseCFFCharstring2, charstring) {
|
|
|
13203
13282
|
return function() {
|
|
13204
13283
|
var glyph = new Glyph({ index, font });
|
|
13205
13284
|
glyph.path = function() {
|
|
13206
|
-
var
|
|
13207
|
-
|
|
13208
|
-
return
|
|
13285
|
+
var path27 = parseCFFCharstring2(font, glyph, charstring);
|
|
13286
|
+
path27.unitsPerEm = font.unitsPerEm;
|
|
13287
|
+
return path27;
|
|
13209
13288
|
};
|
|
13210
13289
|
return glyph;
|
|
13211
13290
|
};
|
|
@@ -25229,7 +25308,7 @@ function bt(A) {
|
|
|
25229
25308
|
let e = typeof A;
|
|
25230
25309
|
return !(e === "number" || e === "bigint" || e === "string" || e === "boolean");
|
|
25231
25310
|
}
|
|
25232
|
-
function
|
|
25311
|
+
function fs5(A) {
|
|
25233
25312
|
return /^class\s/.test(A.toString());
|
|
25234
25313
|
}
|
|
25235
25314
|
function Un(A) {
|
|
@@ -26566,7 +26645,7 @@ async function* jt(A, e) {
|
|
|
26566
26645
|
let W;
|
|
26567
26646
|
if (!bt(A)) W = ui(String(A), e), yield (await W.next()).value;
|
|
26568
26647
|
else {
|
|
26569
|
-
if (
|
|
26648
|
+
if (fs5(A.type)) throw new Error("Class component is not supported.");
|
|
26570
26649
|
let NA;
|
|
26571
26650
|
Un(A.type) ? NA = A.type.render : NA = A.type, W = jt(await NA(A.props), e), yield (await W.next()).value;
|
|
26572
26651
|
}
|
|
@@ -27559,8 +27638,8 @@ function nI(A) {
|
|
|
27559
27638
|
}
|
|
27560
27639
|
|
|
27561
27640
|
// scripts/lib/visual-generator.ts
|
|
27562
|
-
var
|
|
27563
|
-
var
|
|
27641
|
+
var fs6 = __toESM(require("fs"));
|
|
27642
|
+
var path4 = __toESM(require("path"));
|
|
27564
27643
|
|
|
27565
27644
|
// src/lib/types.ts
|
|
27566
27645
|
var CATEGORY_LABELS = {
|
|
@@ -27630,7 +27709,7 @@ var EFFORT_DISPLAY = {
|
|
|
27630
27709
|
};
|
|
27631
27710
|
var BRANDING = {
|
|
27632
27711
|
name: "VibeBusiness",
|
|
27633
|
-
tagline: "AI
|
|
27712
|
+
tagline: "AI Product Manager",
|
|
27634
27713
|
emoji: "\u{1F916}"
|
|
27635
27714
|
};
|
|
27636
27715
|
|
|
@@ -27657,22 +27736,22 @@ function computeCodeImpact(idea) {
|
|
|
27657
27736
|
var fontsCache = null;
|
|
27658
27737
|
function loadFonts() {
|
|
27659
27738
|
if (fontsCache) return fontsCache;
|
|
27660
|
-
const assetsDir =
|
|
27661
|
-
const regularPath =
|
|
27662
|
-
const boldPath =
|
|
27739
|
+
const assetsDir = path4.join(__dirname, "..", "assets");
|
|
27740
|
+
const regularPath = path4.join(assetsDir, "Inter-Regular.ttf");
|
|
27741
|
+
const boldPath = path4.join(assetsDir, "Inter-Bold.ttf");
|
|
27663
27742
|
const fonts = [];
|
|
27664
|
-
if (
|
|
27743
|
+
if (fs6.existsSync(regularPath)) {
|
|
27665
27744
|
fonts.push({
|
|
27666
27745
|
name: "Inter",
|
|
27667
|
-
data:
|
|
27746
|
+
data: fs6.readFileSync(regularPath).buffer,
|
|
27668
27747
|
weight: 400,
|
|
27669
27748
|
style: "normal"
|
|
27670
27749
|
});
|
|
27671
27750
|
}
|
|
27672
|
-
if (
|
|
27751
|
+
if (fs6.existsSync(boldPath)) {
|
|
27673
27752
|
fonts.push({
|
|
27674
27753
|
name: "Inter",
|
|
27675
|
-
data:
|
|
27754
|
+
data: fs6.readFileSync(boldPath).buffer,
|
|
27676
27755
|
weight: 700,
|
|
27677
27756
|
style: "normal"
|
|
27678
27757
|
});
|
|
@@ -27953,7 +28032,7 @@ async function generateShipCard(config) {
|
|
|
27953
28032
|
|
|
27954
28033
|
// scripts/skills/marketing-visual.ts
|
|
27955
28034
|
var TASK_PREFIX = "generate-visual-";
|
|
27956
|
-
var VISUALS_DIR =
|
|
28035
|
+
var VISUALS_DIR = path5.join(DATA_DIR, "reports", "visuals");
|
|
27957
28036
|
function isMarketingVisualTask(taskId) {
|
|
27958
28037
|
return taskId.startsWith(TASK_PREFIX);
|
|
27959
28038
|
}
|
|
@@ -27964,7 +28043,7 @@ async function executeMarketingVisualTask(taskId, _description) {
|
|
|
27964
28043
|
}
|
|
27965
28044
|
let idea;
|
|
27966
28045
|
try {
|
|
27967
|
-
const ideasData = JSON.parse(
|
|
28046
|
+
const ideasData = JSON.parse(fs7.readFileSync(IDEAS_FILE, "utf-8"));
|
|
27968
28047
|
idea = ideasData.ideas.find((i) => i.id === ideaId);
|
|
27969
28048
|
} catch {
|
|
27970
28049
|
return { success: false, output: `Failed to load ideas from ${IDEAS_FILE}` };
|
|
@@ -27973,18 +28052,18 @@ async function executeMarketingVisualTask(taskId, _description) {
|
|
|
27973
28052
|
return { success: false, output: `Idea not found: ${ideaId}` };
|
|
27974
28053
|
}
|
|
27975
28054
|
let goal = null;
|
|
27976
|
-
if (idea.goal_id &&
|
|
28055
|
+
if (idea.goal_id && fs7.existsSync(GOALS_FILE)) {
|
|
27977
28056
|
try {
|
|
27978
|
-
const goalsData = JSON.parse(
|
|
28057
|
+
const goalsData = JSON.parse(fs7.readFileSync(GOALS_FILE, "utf-8"));
|
|
27979
28058
|
goal = goalsData.goals.find((g2) => g2.id === idea.goal_id) || null;
|
|
27980
28059
|
} catch {
|
|
27981
28060
|
}
|
|
27982
28061
|
}
|
|
27983
28062
|
let screenshotThumbnail;
|
|
27984
|
-
const screenshotPath =
|
|
27985
|
-
if (
|
|
28063
|
+
const screenshotPath = path5.join(DATA_DIR, "reports", `${ideaId}-screenshot.png`);
|
|
28064
|
+
if (fs7.existsSync(screenshotPath)) {
|
|
27986
28065
|
try {
|
|
27987
|
-
const imgBuffer =
|
|
28066
|
+
const imgBuffer = fs7.readFileSync(screenshotPath);
|
|
27988
28067
|
screenshotThumbnail = `data:image/png;base64,${imgBuffer.toString("base64")}`;
|
|
27989
28068
|
} catch {
|
|
27990
28069
|
}
|
|
@@ -27996,9 +28075,9 @@ async function executeMarketingVisualTask(taskId, _description) {
|
|
|
27996
28075
|
screenshotThumbnail,
|
|
27997
28076
|
format: "png"
|
|
27998
28077
|
});
|
|
27999
|
-
|
|
28000
|
-
const outputPath =
|
|
28001
|
-
|
|
28078
|
+
fs7.mkdirSync(VISUALS_DIR, { recursive: true });
|
|
28079
|
+
const outputPath = path5.join(VISUALS_DIR, `${ideaId}-card.png`);
|
|
28080
|
+
fs7.writeFileSync(outputPath, result.buffer);
|
|
28002
28081
|
return {
|
|
28003
28082
|
success: true,
|
|
28004
28083
|
output: `Ship card generated: ${outputPath} (${Math.round(result.buffer.length / 1024)}KB)`
|
|
@@ -28014,8 +28093,43 @@ async function executeMarketingVisualTask(taskId, _description) {
|
|
|
28014
28093
|
|
|
28015
28094
|
// scripts/skills/email-marketing.ts
|
|
28016
28095
|
var import_child_process3 = require("child_process");
|
|
28017
|
-
var
|
|
28018
|
-
var
|
|
28096
|
+
var fs8 = __toESM(require("fs"));
|
|
28097
|
+
var path6 = __toESM(require("path"));
|
|
28098
|
+
|
|
28099
|
+
// src/lib/loops.ts
|
|
28100
|
+
var LOOPS_BASE_URL = "https://app.loops.so/api/v1";
|
|
28101
|
+
function getApiKey() {
|
|
28102
|
+
const key = process.env.LOOPS_API_KEY;
|
|
28103
|
+
if (!key) throw new Error("LOOPS_API_KEY environment variable is not set");
|
|
28104
|
+
return key;
|
|
28105
|
+
}
|
|
28106
|
+
function headers() {
|
|
28107
|
+
return {
|
|
28108
|
+
Authorization: `Bearer ${getApiKey()}`,
|
|
28109
|
+
"Content-Type": "application/json"
|
|
28110
|
+
};
|
|
28111
|
+
}
|
|
28112
|
+
async function verifyApiKey() {
|
|
28113
|
+
const res = await fetch(`${LOOPS_BASE_URL}/api-key`, {
|
|
28114
|
+
method: "GET",
|
|
28115
|
+
headers: headers()
|
|
28116
|
+
});
|
|
28117
|
+
if (!res.ok) return { success: false };
|
|
28118
|
+
const data = await res.json();
|
|
28119
|
+
return { success: true, teamName: data.teamName };
|
|
28120
|
+
}
|
|
28121
|
+
async function getMailingLists() {
|
|
28122
|
+
const res = await fetch(`${LOOPS_BASE_URL}/lists`, {
|
|
28123
|
+
method: "GET",
|
|
28124
|
+
headers: headers()
|
|
28125
|
+
});
|
|
28126
|
+
if (!res.ok) {
|
|
28127
|
+
throw new Error(`Loops API error: ${res.status} ${await res.text()}`);
|
|
28128
|
+
}
|
|
28129
|
+
return res.json();
|
|
28130
|
+
}
|
|
28131
|
+
|
|
28132
|
+
// scripts/skills/email-marketing.ts
|
|
28019
28133
|
var EMAIL_PREFIXES = [
|
|
28020
28134
|
"email-setup",
|
|
28021
28135
|
"email-verify",
|
|
@@ -28062,8 +28176,6 @@ async function executeHealthCheck() {
|
|
|
28062
28176
|
};
|
|
28063
28177
|
}
|
|
28064
28178
|
lines.push(`- API Key: valid (team: ${keyResult.teamName || "unknown"})`);
|
|
28065
|
-
const contactResult = await listContacts({ limit: 1 });
|
|
28066
|
-
lines.push(`- Total Contacts: ${contactResult.count}`);
|
|
28067
28179
|
const lists = await getMailingLists();
|
|
28068
28180
|
lines.push(`- Mailing Lists: ${lists.length}`);
|
|
28069
28181
|
for (const list of lists) {
|
|
@@ -28091,14 +28203,14 @@ async function executeEmailMarketingTask(taskId, description, businessContext) {
|
|
|
28091
28203
|
};
|
|
28092
28204
|
}
|
|
28093
28205
|
const { command, args: args2 } = parsed;
|
|
28094
|
-
const commandFile =
|
|
28095
|
-
if (!
|
|
28206
|
+
const commandFile = path6.join(PROJECT_DIR, ".claude", "commands", `${command}.md`);
|
|
28207
|
+
if (!fs8.existsSync(commandFile)) {
|
|
28096
28208
|
return {
|
|
28097
28209
|
success: false,
|
|
28098
28210
|
output: `Slash command template not found: ${commandFile}. Run 'init' to scaffold commands.`
|
|
28099
28211
|
};
|
|
28100
28212
|
}
|
|
28101
|
-
const template =
|
|
28213
|
+
const template = fs8.readFileSync(commandFile, "utf-8");
|
|
28102
28214
|
const prompt = args2 ? `${template}
|
|
28103
28215
|
|
|
28104
28216
|
---
|
|
@@ -28149,12 +28261,12 @@ Execute this command now with no arguments (use defaults).`;
|
|
|
28149
28261
|
|
|
28150
28262
|
// scripts/skills/payments.ts
|
|
28151
28263
|
var import_child_process4 = require("child_process");
|
|
28152
|
-
var
|
|
28153
|
-
var
|
|
28264
|
+
var fs11 = __toESM(require("fs"));
|
|
28265
|
+
var path9 = __toESM(require("path"));
|
|
28154
28266
|
|
|
28155
28267
|
// scripts/lib/payments/framework-detect.ts
|
|
28156
|
-
var
|
|
28157
|
-
var
|
|
28268
|
+
var fs9 = __toESM(require("fs"));
|
|
28269
|
+
var path7 = __toESM(require("path"));
|
|
28158
28270
|
function detectFramework(projectPath) {
|
|
28159
28271
|
const result = {
|
|
28160
28272
|
framework: "other",
|
|
@@ -28162,15 +28274,15 @@ function detectFramework(projectPath) {
|
|
|
28162
28274
|
appRouter: false,
|
|
28163
28275
|
srcDir: false
|
|
28164
28276
|
};
|
|
28165
|
-
result.typescript =
|
|
28166
|
-
result.srcDir =
|
|
28167
|
-
const pkgPath =
|
|
28168
|
-
if (!
|
|
28277
|
+
result.typescript = fs9.existsSync(path7.join(projectPath, "tsconfig.json"));
|
|
28278
|
+
result.srcDir = fs9.existsSync(path7.join(projectPath, "src"));
|
|
28279
|
+
const pkgPath = path7.join(projectPath, "package.json");
|
|
28280
|
+
if (!fs9.existsSync(pkgPath)) {
|
|
28169
28281
|
return result;
|
|
28170
28282
|
}
|
|
28171
28283
|
let pkg;
|
|
28172
28284
|
try {
|
|
28173
|
-
pkg = JSON.parse(
|
|
28285
|
+
pkg = JSON.parse(fs9.readFileSync(pkgPath, "utf-8"));
|
|
28174
28286
|
} catch {
|
|
28175
28287
|
return result;
|
|
28176
28288
|
}
|
|
@@ -28178,10 +28290,10 @@ function detectFramework(projectPath) {
|
|
|
28178
28290
|
if (allDeps["next"]) {
|
|
28179
28291
|
result.framework = "nextjs";
|
|
28180
28292
|
const appDirs = [
|
|
28181
|
-
|
|
28182
|
-
|
|
28293
|
+
path7.join(projectPath, "app"),
|
|
28294
|
+
path7.join(projectPath, "src", "app")
|
|
28183
28295
|
];
|
|
28184
|
-
result.appRouter = appDirs.some((dir) =>
|
|
28296
|
+
result.appRouter = appDirs.some((dir) => fs9.existsSync(dir));
|
|
28185
28297
|
} else if (allDeps["@remix-run/node"] || allDeps["@remix-run/react"]) {
|
|
28186
28298
|
result.framework = "remix";
|
|
28187
28299
|
} else if (allDeps["fastify"]) {
|
|
@@ -28975,14 +29087,14 @@ export async function createPortalSession(customerId${ts2 ? ": string" : ""}) {
|
|
|
28975
29087
|
}
|
|
28976
29088
|
|
|
28977
29089
|
// scripts/lib/payments/env-manager.ts
|
|
28978
|
-
var
|
|
28979
|
-
var
|
|
29090
|
+
var fs10 = __toESM(require("fs"));
|
|
29091
|
+
var path8 = __toESM(require("path"));
|
|
28980
29092
|
function readEnvFile(projectPath) {
|
|
28981
|
-
const envPath =
|
|
28982
|
-
if (!
|
|
29093
|
+
const envPath = path8.join(projectPath, ".env");
|
|
29094
|
+
if (!fs10.existsSync(envPath)) {
|
|
28983
29095
|
return {};
|
|
28984
29096
|
}
|
|
28985
|
-
const content =
|
|
29097
|
+
const content = fs10.readFileSync(envPath, "utf-8");
|
|
28986
29098
|
const vars = {};
|
|
28987
29099
|
for (const line of content.split("\n")) {
|
|
28988
29100
|
const trimmed = line.trim();
|
|
@@ -28999,7 +29111,7 @@ function readEnvFile(projectPath) {
|
|
|
28999
29111
|
return vars;
|
|
29000
29112
|
}
|
|
29001
29113
|
function writeEnvVars(projectPath, vars) {
|
|
29002
|
-
const envPath =
|
|
29114
|
+
const envPath = path8.join(projectPath, ".env");
|
|
29003
29115
|
const existing = readEnvFile(projectPath);
|
|
29004
29116
|
const added = [];
|
|
29005
29117
|
const lines = [];
|
|
@@ -29009,22 +29121,22 @@ function writeEnvVars(projectPath, vars) {
|
|
|
29009
29121
|
added.push(key);
|
|
29010
29122
|
}
|
|
29011
29123
|
if (lines.length === 0) return added;
|
|
29012
|
-
const prefix =
|
|
29124
|
+
const prefix = fs10.existsSync(envPath) ? "\n" : "";
|
|
29013
29125
|
const content = prefix + lines.join("\n") + "\n";
|
|
29014
|
-
|
|
29126
|
+
fs10.appendFileSync(envPath, content);
|
|
29015
29127
|
return added;
|
|
29016
29128
|
}
|
|
29017
29129
|
function ensureGitignore(projectPath) {
|
|
29018
|
-
const gitignorePath =
|
|
29130
|
+
const gitignorePath = path8.join(projectPath, ".gitignore");
|
|
29019
29131
|
let content = "";
|
|
29020
29132
|
let modified = false;
|
|
29021
|
-
if (
|
|
29022
|
-
content =
|
|
29133
|
+
if (fs10.existsSync(gitignorePath)) {
|
|
29134
|
+
content = fs10.readFileSync(gitignorePath, "utf-8");
|
|
29023
29135
|
}
|
|
29024
29136
|
const lines = content.split("\n").map((l2) => l2.trim());
|
|
29025
29137
|
if (!lines.includes(".env")) {
|
|
29026
29138
|
const suffix = content.endsWith("\n") || content === "" ? "" : "\n";
|
|
29027
|
-
|
|
29139
|
+
fs10.appendFileSync(gitignorePath, `${suffix}.env
|
|
29028
29140
|
`);
|
|
29029
29141
|
modified = true;
|
|
29030
29142
|
}
|
|
@@ -29083,12 +29195,12 @@ function readPaymentsFreshness() {
|
|
|
29083
29195
|
completeness: 0,
|
|
29084
29196
|
missing: []
|
|
29085
29197
|
};
|
|
29086
|
-
if (!
|
|
29198
|
+
if (!fs11.existsSync(PAYMENTS_FILE)) {
|
|
29087
29199
|
result.missing = ["sdk", "api_key", "products", "checkout", "webhook", "portal"];
|
|
29088
29200
|
return result;
|
|
29089
29201
|
}
|
|
29090
29202
|
try {
|
|
29091
|
-
const state = JSON.parse(
|
|
29203
|
+
const state = JSON.parse(fs11.readFileSync(PAYMENTS_FILE, "utf-8"));
|
|
29092
29204
|
const status = state.integration_status;
|
|
29093
29205
|
const fields = Object.entries(status);
|
|
29094
29206
|
const completed = fields.filter(([, v2]) => v2).length;
|
|
@@ -29106,31 +29218,31 @@ function readPaymentsFreshness() {
|
|
|
29106
29218
|
return result;
|
|
29107
29219
|
}
|
|
29108
29220
|
function loadState() {
|
|
29109
|
-
if (!
|
|
29221
|
+
if (!fs11.existsSync(PAYMENTS_FILE)) {
|
|
29110
29222
|
return { ...EMPTY_STATE };
|
|
29111
29223
|
}
|
|
29112
29224
|
try {
|
|
29113
|
-
return JSON.parse(
|
|
29225
|
+
return JSON.parse(fs11.readFileSync(PAYMENTS_FILE, "utf-8"));
|
|
29114
29226
|
} catch {
|
|
29115
29227
|
return { ...EMPTY_STATE };
|
|
29116
29228
|
}
|
|
29117
29229
|
}
|
|
29118
29230
|
function saveState(state) {
|
|
29119
29231
|
state.last_updated = (/* @__PURE__ */ new Date()).toISOString();
|
|
29120
|
-
const dir =
|
|
29121
|
-
if (!
|
|
29122
|
-
|
|
29232
|
+
const dir = path9.dirname(PAYMENTS_FILE);
|
|
29233
|
+
if (!fs11.existsSync(dir)) {
|
|
29234
|
+
fs11.mkdirSync(dir, { recursive: true });
|
|
29123
29235
|
}
|
|
29124
|
-
|
|
29236
|
+
fs11.writeFileSync(PAYMENTS_FILE, JSON.stringify(state, null, 2) + "\n");
|
|
29125
29237
|
}
|
|
29126
29238
|
function getUserProjectPath() {
|
|
29127
|
-
const configPath =
|
|
29128
|
-
if (
|
|
29239
|
+
const configPath = path9.join(PROJECT_DIR, "data", "config.json");
|
|
29240
|
+
if (fs11.existsSync(configPath)) {
|
|
29129
29241
|
try {
|
|
29130
|
-
const config = JSON.parse(
|
|
29242
|
+
const config = JSON.parse(fs11.readFileSync(configPath, "utf-8"));
|
|
29131
29243
|
if (config.repos?.[0]?.path) {
|
|
29132
29244
|
const repoPath = config.repos[0].path;
|
|
29133
|
-
if (
|
|
29245
|
+
if (fs11.existsSync(repoPath)) {
|
|
29134
29246
|
return repoPath;
|
|
29135
29247
|
}
|
|
29136
29248
|
}
|
|
@@ -29161,15 +29273,15 @@ function installStripe(projectPath) {
|
|
|
29161
29273
|
}
|
|
29162
29274
|
}
|
|
29163
29275
|
function writeGeneratedFile(projectPath, file) {
|
|
29164
|
-
const fullPath =
|
|
29165
|
-
if (
|
|
29276
|
+
const fullPath = path9.join(projectPath, file.path);
|
|
29277
|
+
if (fs11.existsSync(fullPath)) {
|
|
29166
29278
|
return false;
|
|
29167
29279
|
}
|
|
29168
|
-
const dir =
|
|
29169
|
-
if (!
|
|
29170
|
-
|
|
29280
|
+
const dir = path9.dirname(fullPath);
|
|
29281
|
+
if (!fs11.existsSync(dir)) {
|
|
29282
|
+
fs11.mkdirSync(dir, { recursive: true });
|
|
29171
29283
|
}
|
|
29172
|
-
|
|
29284
|
+
fs11.writeFileSync(fullPath, file.content);
|
|
29173
29285
|
return true;
|
|
29174
29286
|
}
|
|
29175
29287
|
async function executeFullSetup() {
|
|
@@ -29372,7 +29484,7 @@ async function executeCheckStatus() {
|
|
|
29372
29484
|
const framework = state.framework || detectFramework(projectPath);
|
|
29373
29485
|
const templates = getTemplatesForFramework(framework);
|
|
29374
29486
|
for (const template of templates) {
|
|
29375
|
-
const exists =
|
|
29487
|
+
const exists = fs11.existsSync(path9.join(projectPath, template.path));
|
|
29376
29488
|
const type = template.path.includes("checkout") ? "checkout" : template.path.includes("webhook") ? "webhook" : template.path.includes("portal") ? "portal" : null;
|
|
29377
29489
|
if (type) {
|
|
29378
29490
|
const statusKey = `${type}_endpoint`;
|
|
@@ -29389,9 +29501,782 @@ async function executeCheckStatus() {
|
|
|
29389
29501
|
return { success: true, output: lines.join("\n") };
|
|
29390
29502
|
}
|
|
29391
29503
|
|
|
29504
|
+
// scripts/skills/posthog.ts
|
|
29505
|
+
var import_child_process5 = require("child_process");
|
|
29506
|
+
var fs12 = __toESM(require("fs"));
|
|
29507
|
+
var path10 = __toESM(require("path"));
|
|
29508
|
+
|
|
29509
|
+
// scripts/lib/posthog/posthog-api.ts
|
|
29510
|
+
var DEFAULT_HOST = "https://us.i.posthog.com";
|
|
29511
|
+
async function validatePostHogKey(apiKey, host = DEFAULT_HOST) {
|
|
29512
|
+
try {
|
|
29513
|
+
const res = await fetch(`${host}/api/projects/`, {
|
|
29514
|
+
headers: {
|
|
29515
|
+
Authorization: `Bearer ${apiKey}`
|
|
29516
|
+
}
|
|
29517
|
+
});
|
|
29518
|
+
if (res.status === 401 || res.status === 403) {
|
|
29519
|
+
return { valid: false, error: "Invalid API key or insufficient permissions" };
|
|
29520
|
+
}
|
|
29521
|
+
if (!res.ok) {
|
|
29522
|
+
return { valid: false, error: `PostHog API returned ${res.status}` };
|
|
29523
|
+
}
|
|
29524
|
+
const data = await res.json();
|
|
29525
|
+
const projects = data?.results || [];
|
|
29526
|
+
if (projects.length > 0) {
|
|
29527
|
+
return { valid: true, projectName: projects[0].name };
|
|
29528
|
+
}
|
|
29529
|
+
return { valid: true };
|
|
29530
|
+
} catch (error) {
|
|
29531
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
29532
|
+
return { valid: false, error: `Failed to connect to PostHog: ${msg}` };
|
|
29533
|
+
}
|
|
29534
|
+
}
|
|
29535
|
+
function isValidKeyFormat(key) {
|
|
29536
|
+
return key.startsWith("phc_") && key.length > 10;
|
|
29537
|
+
}
|
|
29538
|
+
|
|
29539
|
+
// scripts/lib/posthog/posthog-templates.ts
|
|
29540
|
+
function getPostHogTemplates(info) {
|
|
29541
|
+
if (info.framework === "nextjs" && info.appRouter) {
|
|
29542
|
+
return getNextjsAppRouterTemplates2(info);
|
|
29543
|
+
}
|
|
29544
|
+
if (info.framework === "nextjs") {
|
|
29545
|
+
return getNextjsPagesTemplates2(info);
|
|
29546
|
+
}
|
|
29547
|
+
return getGenericTemplates2(info);
|
|
29548
|
+
}
|
|
29549
|
+
function getNextjsAppRouterTemplates2(info) {
|
|
29550
|
+
const ext = info.typescript ? "tsx" : "jsx";
|
|
29551
|
+
const tsExt = info.typescript ? "ts" : "js";
|
|
29552
|
+
const prefix = info.srcDir ? "src/" : "";
|
|
29553
|
+
return [
|
|
29554
|
+
{
|
|
29555
|
+
path: `${prefix}components/PostHogProvider.${ext}`,
|
|
29556
|
+
content: `'use client';
|
|
29557
|
+
|
|
29558
|
+
import posthog from 'posthog-js';
|
|
29559
|
+
import { PostHogProvider as PHProvider } from 'posthog-js/react';
|
|
29560
|
+
import { useEffect } from 'react';
|
|
29561
|
+
|
|
29562
|
+
export default function PostHogProvider({ children }${info.typescript ? ": { children: React.ReactNode }" : ""}) {
|
|
29563
|
+
useEffect(() => {
|
|
29564
|
+
if (!process.env.NEXT_PUBLIC_POSTHOG_KEY) return;
|
|
29565
|
+
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, {
|
|
29566
|
+
api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST || 'https://us.i.posthog.com',
|
|
29567
|
+
capture_pageview: false,
|
|
29568
|
+
capture_pageleave: true,
|
|
29569
|
+
});
|
|
29570
|
+
}, []);
|
|
29571
|
+
|
|
29572
|
+
if (!process.env.NEXT_PUBLIC_POSTHOG_KEY) return <>{children}</>;
|
|
29573
|
+
|
|
29574
|
+
return <PHProvider client={posthog}>{children}</PHProvider>;
|
|
29575
|
+
}
|
|
29576
|
+
`,
|
|
29577
|
+
description: "PostHog provider component with client-side initialization"
|
|
29578
|
+
},
|
|
29579
|
+
{
|
|
29580
|
+
path: `${prefix}components/PostHogPageview.${ext}`,
|
|
29581
|
+
content: `'use client';
|
|
29582
|
+
|
|
29583
|
+
import { usePathname, useSearchParams } from 'next/navigation';
|
|
29584
|
+
import { useEffect, Suspense } from 'react';
|
|
29585
|
+
import { usePostHog } from 'posthog-js/react';
|
|
29586
|
+
|
|
29587
|
+
function PageviewTracker() {
|
|
29588
|
+
const pathname = usePathname();
|
|
29589
|
+
const searchParams = useSearchParams();
|
|
29590
|
+
const posthog = usePostHog();
|
|
29591
|
+
|
|
29592
|
+
useEffect(() => {
|
|
29593
|
+
if (pathname && posthog) {
|
|
29594
|
+
let url = window.origin + pathname;
|
|
29595
|
+
if (searchParams.toString()) {
|
|
29596
|
+
url += '?' + searchParams.toString();
|
|
29597
|
+
}
|
|
29598
|
+
posthog.capture('$pageview', { $current_url: url });
|
|
29599
|
+
}
|
|
29600
|
+
}, [pathname, searchParams, posthog]);
|
|
29601
|
+
|
|
29602
|
+
return null;
|
|
29603
|
+
}
|
|
29604
|
+
|
|
29605
|
+
export default function PostHogPageview() {
|
|
29606
|
+
return (
|
|
29607
|
+
<Suspense fallback={null}>
|
|
29608
|
+
<PageviewTracker />
|
|
29609
|
+
</Suspense>
|
|
29610
|
+
);
|
|
29611
|
+
}
|
|
29612
|
+
`,
|
|
29613
|
+
description: "SPA pageview tracker for Next.js App Router"
|
|
29614
|
+
},
|
|
29615
|
+
{
|
|
29616
|
+
path: `${prefix}lib/posthog-events.${tsExt}`,
|
|
29617
|
+
content: `import posthog from 'posthog-js';
|
|
29618
|
+
|
|
29619
|
+
/**
|
|
29620
|
+
* Track a custom event. No-op if PostHog is not initialized.
|
|
29621
|
+
*/
|
|
29622
|
+
export function trackEvent(
|
|
29623
|
+
event${info.typescript ? ": string" : ""},
|
|
29624
|
+
properties${info.typescript ? "?: Record<string, string | number | boolean>" : ""},
|
|
29625
|
+
) {
|
|
29626
|
+
if (typeof window !== 'undefined' && process.env.NEXT_PUBLIC_POSTHOG_KEY) {
|
|
29627
|
+
posthog.capture(event, properties);
|
|
29628
|
+
}
|
|
29629
|
+
}
|
|
29630
|
+
`,
|
|
29631
|
+
description: "Typed event tracking helper"
|
|
29632
|
+
}
|
|
29633
|
+
];
|
|
29634
|
+
}
|
|
29635
|
+
function getNextjsPagesTemplates2(info) {
|
|
29636
|
+
const ext = info.typescript ? "tsx" : "jsx";
|
|
29637
|
+
const prefix = info.srcDir ? "src/" : "";
|
|
29638
|
+
return [
|
|
29639
|
+
{
|
|
29640
|
+
path: `${prefix}lib/posthog.${info.typescript ? "ts" : "js"}`,
|
|
29641
|
+
content: `import posthog from 'posthog-js';
|
|
29642
|
+
|
|
29643
|
+
export function initPostHog() {
|
|
29644
|
+
if (typeof window !== 'undefined' && process.env.NEXT_PUBLIC_POSTHOG_KEY) {
|
|
29645
|
+
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, {
|
|
29646
|
+
api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST || 'https://us.i.posthog.com',
|
|
29647
|
+
capture_pageleave: true,
|
|
29648
|
+
});
|
|
29649
|
+
}
|
|
29650
|
+
}
|
|
29651
|
+
|
|
29652
|
+
export function trackEvent(
|
|
29653
|
+
event${info.typescript ? ": string" : ""},
|
|
29654
|
+
properties${info.typescript ? "?: Record<string, string | number | boolean>" : ""},
|
|
29655
|
+
) {
|
|
29656
|
+
if (typeof window !== 'undefined') {
|
|
29657
|
+
posthog.capture(event, properties);
|
|
29658
|
+
}
|
|
29659
|
+
}
|
|
29660
|
+
|
|
29661
|
+
export { posthog };
|
|
29662
|
+
`,
|
|
29663
|
+
description: "PostHog initialization and event tracking for Pages Router"
|
|
29664
|
+
},
|
|
29665
|
+
{
|
|
29666
|
+
path: `${prefix}pages/_app.${ext}`,
|
|
29667
|
+
content: `// Add this to your existing _app.${ext}:
|
|
29668
|
+
// import { initPostHog } from '${info.srcDir ? "@" : ".."}/lib/posthog';
|
|
29669
|
+
// import { useRouter } from 'next/router';
|
|
29670
|
+
// import { useEffect } from 'react';
|
|
29671
|
+
// import posthog from 'posthog-js';
|
|
29672
|
+
//
|
|
29673
|
+
// useEffect(() => {
|
|
29674
|
+
// initPostHog();
|
|
29675
|
+
// const handleRouteChange = () => posthog.capture('$pageview');
|
|
29676
|
+
// router.events.on('routeChangeComplete', handleRouteChange);
|
|
29677
|
+
// return () => router.events.off('routeChangeComplete', handleRouteChange);
|
|
29678
|
+
// }, []);
|
|
29679
|
+
`,
|
|
29680
|
+
description: "Instructions for integrating PostHog into _app (do not overwrite)"
|
|
29681
|
+
}
|
|
29682
|
+
];
|
|
29683
|
+
}
|
|
29684
|
+
function getRecommendedEvents(info) {
|
|
29685
|
+
const common = [
|
|
29686
|
+
{
|
|
29687
|
+
name: "signed_up",
|
|
29688
|
+
description: "User creates an account",
|
|
29689
|
+
properties: ["method", "referrer"]
|
|
29690
|
+
},
|
|
29691
|
+
{
|
|
29692
|
+
name: "logged_in",
|
|
29693
|
+
description: "User logs in",
|
|
29694
|
+
properties: ["method"]
|
|
29695
|
+
},
|
|
29696
|
+
{
|
|
29697
|
+
name: "feature_used",
|
|
29698
|
+
description: "User engages with a key feature",
|
|
29699
|
+
properties: ["feature_name", "duration_ms"]
|
|
29700
|
+
},
|
|
29701
|
+
{
|
|
29702
|
+
name: "upgrade_clicked",
|
|
29703
|
+
description: "User clicks on a pricing/upgrade CTA",
|
|
29704
|
+
properties: ["plan", "source"]
|
|
29705
|
+
}
|
|
29706
|
+
];
|
|
29707
|
+
if (info.framework === "nextjs") {
|
|
29708
|
+
return [
|
|
29709
|
+
...common,
|
|
29710
|
+
{
|
|
29711
|
+
name: "page_viewed",
|
|
29712
|
+
description: "SPA pageview (auto-tracked by PostHogPageview component)",
|
|
29713
|
+
properties: ["$current_url"]
|
|
29714
|
+
},
|
|
29715
|
+
{
|
|
29716
|
+
name: "cta_clicked",
|
|
29717
|
+
description: "User clicks a call-to-action button",
|
|
29718
|
+
properties: ["cta_name", "page"]
|
|
29719
|
+
},
|
|
29720
|
+
{
|
|
29721
|
+
name: "form_submitted",
|
|
29722
|
+
description: "User submits a form (signup, contact, waitlist)",
|
|
29723
|
+
properties: ["form_name", "success"]
|
|
29724
|
+
}
|
|
29725
|
+
];
|
|
29726
|
+
}
|
|
29727
|
+
return [
|
|
29728
|
+
...common,
|
|
29729
|
+
{
|
|
29730
|
+
name: "api_called",
|
|
29731
|
+
description: "API endpoint receives a request",
|
|
29732
|
+
properties: ["endpoint", "method", "status_code"]
|
|
29733
|
+
}
|
|
29734
|
+
];
|
|
29735
|
+
}
|
|
29736
|
+
function getGenericTemplates2(info) {
|
|
29737
|
+
const ext = info.typescript ? "ts" : "js";
|
|
29738
|
+
return [
|
|
29739
|
+
{
|
|
29740
|
+
path: `lib/posthog.${ext}`,
|
|
29741
|
+
content: `${info.typescript ? "import { PostHog } from 'posthog-node';" : "const { PostHog } = require('posthog-node');"}
|
|
29742
|
+
|
|
29743
|
+
const client = new PostHog(
|
|
29744
|
+
process.env.POSTHOG_API_KEY || '',
|
|
29745
|
+
{ host: process.env.POSTHOG_HOST || 'https://us.i.posthog.com' }
|
|
29746
|
+
);
|
|
29747
|
+
|
|
29748
|
+
export ${info.typescript ? "default " : ""}${info.typescript ? "" : "module.exports = "}client;
|
|
29749
|
+
|
|
29750
|
+
// Flush events on shutdown
|
|
29751
|
+
process.on('SIGTERM', async () => {
|
|
29752
|
+
await client.shutdown();
|
|
29753
|
+
});
|
|
29754
|
+
`,
|
|
29755
|
+
description: "Server-side PostHog client (posthog-node)"
|
|
29756
|
+
}
|
|
29757
|
+
];
|
|
29758
|
+
}
|
|
29759
|
+
|
|
29760
|
+
// scripts/skills/posthog.ts
|
|
29761
|
+
var EMPTY_STATE2 = {
|
|
29762
|
+
last_updated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
29763
|
+
integration_status: {
|
|
29764
|
+
sdk_installed: false,
|
|
29765
|
+
api_key_valid: false,
|
|
29766
|
+
provider_generated: false,
|
|
29767
|
+
pageview_tracker: false,
|
|
29768
|
+
event_helper: false
|
|
29769
|
+
},
|
|
29770
|
+
framework: null,
|
|
29771
|
+
generated_files: []
|
|
29772
|
+
};
|
|
29773
|
+
var POSTHOG_PREFIXES = [
|
|
29774
|
+
"posthog-setup",
|
|
29775
|
+
"posthog-add-provider",
|
|
29776
|
+
"posthog-add-events",
|
|
29777
|
+
"posthog-check-status",
|
|
29778
|
+
"posthog-connect-kpis"
|
|
29779
|
+
];
|
|
29780
|
+
function isPosthogTask(taskId) {
|
|
29781
|
+
return POSTHOG_PREFIXES.some((prefix) => taskId.startsWith(prefix));
|
|
29782
|
+
}
|
|
29783
|
+
async function executePosthogTask(taskId, description, _businessContext) {
|
|
29784
|
+
try {
|
|
29785
|
+
if (taskId.startsWith("posthog-setup")) {
|
|
29786
|
+
return await executeFullSetup2();
|
|
29787
|
+
}
|
|
29788
|
+
if (taskId.startsWith("posthog-add-provider")) {
|
|
29789
|
+
return await executeAddComponent("provider");
|
|
29790
|
+
}
|
|
29791
|
+
if (taskId.startsWith("posthog-add-events")) {
|
|
29792
|
+
return await executeAddComponent("events");
|
|
29793
|
+
}
|
|
29794
|
+
if (taskId.startsWith("posthog-check-status")) {
|
|
29795
|
+
return executeCheckStatus2();
|
|
29796
|
+
}
|
|
29797
|
+
if (taskId.startsWith("posthog-connect-kpis")) {
|
|
29798
|
+
return executeConnectKpis();
|
|
29799
|
+
}
|
|
29800
|
+
return { success: false, output: `Unknown PostHog task: ${taskId}` };
|
|
29801
|
+
} catch (error) {
|
|
29802
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
29803
|
+
return { success: false, output: `PostHog task failed: ${errMsg}` };
|
|
29804
|
+
}
|
|
29805
|
+
}
|
|
29806
|
+
function readPosthogFreshness() {
|
|
29807
|
+
const result = { configured: false, completeness: 0, missing: [] };
|
|
29808
|
+
if (!fs12.existsSync(POSTHOG_FILE)) {
|
|
29809
|
+
result.missing = ["sdk", "api_key", "provider", "pageview_tracker", "event_helper"];
|
|
29810
|
+
return result;
|
|
29811
|
+
}
|
|
29812
|
+
try {
|
|
29813
|
+
const state = JSON.parse(fs12.readFileSync(POSTHOG_FILE, "utf-8"));
|
|
29814
|
+
const status = state.integration_status;
|
|
29815
|
+
const checks = [
|
|
29816
|
+
{ key: "sdk", done: status.sdk_installed },
|
|
29817
|
+
{ key: "api_key", done: status.api_key_valid },
|
|
29818
|
+
{ key: "provider", done: status.provider_generated },
|
|
29819
|
+
{ key: "pageview_tracker", done: status.pageview_tracker },
|
|
29820
|
+
{ key: "event_helper", done: status.event_helper }
|
|
29821
|
+
];
|
|
29822
|
+
const completed = checks.filter((c2) => c2.done).length;
|
|
29823
|
+
result.completeness = Math.round(completed / checks.length * 100);
|
|
29824
|
+
result.configured = completed === checks.length;
|
|
29825
|
+
result.missing = checks.filter((c2) => !c2.done).map((c2) => c2.key);
|
|
29826
|
+
} catch {
|
|
29827
|
+
result.missing = ["sdk", "api_key", "provider", "pageview_tracker", "event_helper"];
|
|
29828
|
+
}
|
|
29829
|
+
return result;
|
|
29830
|
+
}
|
|
29831
|
+
function loadState2() {
|
|
29832
|
+
if (!fs12.existsSync(POSTHOG_FILE)) {
|
|
29833
|
+
return { ...EMPTY_STATE2, integration_status: { ...EMPTY_STATE2.integration_status } };
|
|
29834
|
+
}
|
|
29835
|
+
try {
|
|
29836
|
+
return JSON.parse(fs12.readFileSync(POSTHOG_FILE, "utf-8"));
|
|
29837
|
+
} catch {
|
|
29838
|
+
return { ...EMPTY_STATE2, integration_status: { ...EMPTY_STATE2.integration_status } };
|
|
29839
|
+
}
|
|
29840
|
+
}
|
|
29841
|
+
function saveState2(state) {
|
|
29842
|
+
state.last_updated = (/* @__PURE__ */ new Date()).toISOString();
|
|
29843
|
+
const dir = path10.dirname(POSTHOG_FILE);
|
|
29844
|
+
if (!fs12.existsSync(dir)) {
|
|
29845
|
+
fs12.mkdirSync(dir, { recursive: true });
|
|
29846
|
+
}
|
|
29847
|
+
fs12.writeFileSync(POSTHOG_FILE, JSON.stringify(state, null, 2) + "\n");
|
|
29848
|
+
}
|
|
29849
|
+
function getUserProjectPath2() {
|
|
29850
|
+
const configPath = path10.join(PROJECT_DIR, "data", "config.json");
|
|
29851
|
+
if (fs12.existsSync(configPath)) {
|
|
29852
|
+
try {
|
|
29853
|
+
const config = JSON.parse(fs12.readFileSync(configPath, "utf-8"));
|
|
29854
|
+
if (config.repos?.[0]?.path) {
|
|
29855
|
+
const repoPath = config.repos[0].path;
|
|
29856
|
+
if (fs12.existsSync(repoPath)) {
|
|
29857
|
+
return repoPath;
|
|
29858
|
+
}
|
|
29859
|
+
}
|
|
29860
|
+
} catch {
|
|
29861
|
+
}
|
|
29862
|
+
}
|
|
29863
|
+
return PROJECT_DIR;
|
|
29864
|
+
}
|
|
29865
|
+
function isPostHogInstalled(projectPath) {
|
|
29866
|
+
const pkgPath = path10.join(projectPath, "package.json");
|
|
29867
|
+
if (!fs12.existsSync(pkgPath)) return false;
|
|
29868
|
+
try {
|
|
29869
|
+
const pkg = JSON.parse(fs12.readFileSync(pkgPath, "utf-8"));
|
|
29870
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
29871
|
+
return !!allDeps["posthog-js"] || !!allDeps["posthog-node"];
|
|
29872
|
+
} catch {
|
|
29873
|
+
return false;
|
|
29874
|
+
}
|
|
29875
|
+
}
|
|
29876
|
+
function installPostHog(projectPath, framework) {
|
|
29877
|
+
const packages = ["posthog-js"];
|
|
29878
|
+
if (framework.framework !== "nextjs") {
|
|
29879
|
+
packages.push("posthog-node");
|
|
29880
|
+
}
|
|
29881
|
+
try {
|
|
29882
|
+
(0, import_child_process5.execSync)(`npm install ${packages.join(" ")}`, {
|
|
29883
|
+
cwd: projectPath,
|
|
29884
|
+
stdio: "pipe",
|
|
29885
|
+
timeout: 6e4
|
|
29886
|
+
});
|
|
29887
|
+
return true;
|
|
29888
|
+
} catch {
|
|
29889
|
+
return false;
|
|
29890
|
+
}
|
|
29891
|
+
}
|
|
29892
|
+
function writeGeneratedFile2(projectPath, file) {
|
|
29893
|
+
const fullPath = path10.join(projectPath, file.path);
|
|
29894
|
+
if (fs12.existsSync(fullPath)) {
|
|
29895
|
+
return false;
|
|
29896
|
+
}
|
|
29897
|
+
const dir = path10.dirname(fullPath);
|
|
29898
|
+
if (!fs12.existsSync(dir)) {
|
|
29899
|
+
fs12.mkdirSync(dir, { recursive: true });
|
|
29900
|
+
}
|
|
29901
|
+
fs12.writeFileSync(fullPath, file.content);
|
|
29902
|
+
return true;
|
|
29903
|
+
}
|
|
29904
|
+
async function executeFullSetup2() {
|
|
29905
|
+
const state = loadState2();
|
|
29906
|
+
const lines = ["### PostHog Integration Setup", ""];
|
|
29907
|
+
const projectPath = getUserProjectPath2();
|
|
29908
|
+
lines.push("**Step 1: Framework Detection**");
|
|
29909
|
+
const framework = detectFramework(projectPath);
|
|
29910
|
+
state.framework = framework;
|
|
29911
|
+
lines.push(`- Framework: ${framework.framework}`);
|
|
29912
|
+
lines.push(`- TypeScript: ${framework.typescript}`);
|
|
29913
|
+
if (framework.framework === "nextjs") {
|
|
29914
|
+
lines.push(`- Router: ${framework.appRouter ? "App Router" : "Pages Router"}`);
|
|
29915
|
+
}
|
|
29916
|
+
lines.push(`- src/ directory: ${framework.srcDir}`);
|
|
29917
|
+
lines.push("");
|
|
29918
|
+
lines.push("**Step 2: Install SDK**");
|
|
29919
|
+
if (isPostHogInstalled(projectPath)) {
|
|
29920
|
+
lines.push("- posthog-js already installed");
|
|
29921
|
+
state.integration_status.sdk_installed = true;
|
|
29922
|
+
} else {
|
|
29923
|
+
lines.push("- Installing posthog-js...");
|
|
29924
|
+
if (installPostHog(projectPath, framework)) {
|
|
29925
|
+
lines.push("- posthog-js installed successfully");
|
|
29926
|
+
state.integration_status.sdk_installed = true;
|
|
29927
|
+
} else {
|
|
29928
|
+
lines.push("- Failed to install posthog-js. Run manually: npm install posthog-js");
|
|
29929
|
+
saveState2(state);
|
|
29930
|
+
return { success: false, output: lines.join("\n") };
|
|
29931
|
+
}
|
|
29932
|
+
}
|
|
29933
|
+
lines.push("");
|
|
29934
|
+
lines.push("**Step 3: Validate API Key**");
|
|
29935
|
+
const envVars = readEnvFile(projectPath);
|
|
29936
|
+
const apiKey = envVars["NEXT_PUBLIC_POSTHOG_KEY"] || envVars["POSTHOG_API_KEY"] || "";
|
|
29937
|
+
if (!apiKey) {
|
|
29938
|
+
lines.push("- No PostHog API key found in .env");
|
|
29939
|
+
lines.push("- Add NEXT_PUBLIC_POSTHOG_KEY=phc_... to your .env file");
|
|
29940
|
+
lines.push("- Get your key from: https://app.posthog.com/settings/project#variables");
|
|
29941
|
+
} else if (!isValidKeyFormat(apiKey)) {
|
|
29942
|
+
lines.push(`- API key format looks invalid (expected phc_...): ${apiKey.slice(0, 8)}...`);
|
|
29943
|
+
} else {
|
|
29944
|
+
const validation = await validatePostHogKey(apiKey);
|
|
29945
|
+
if (validation.valid) {
|
|
29946
|
+
lines.push(`- API key valid${validation.projectName ? ` (project: ${validation.projectName})` : ""}`);
|
|
29947
|
+
state.integration_status.api_key_valid = true;
|
|
29948
|
+
} else {
|
|
29949
|
+
lines.push(`- API key validation failed: ${validation.error}`);
|
|
29950
|
+
}
|
|
29951
|
+
}
|
|
29952
|
+
lines.push("");
|
|
29953
|
+
lines.push("**Step 4: Generate Tracking Code**");
|
|
29954
|
+
const templates = getPostHogTemplates(framework);
|
|
29955
|
+
let filesWritten = 0;
|
|
29956
|
+
for (const template of templates) {
|
|
29957
|
+
const written = writeGeneratedFile2(projectPath, template);
|
|
29958
|
+
if (written) {
|
|
29959
|
+
lines.push(`- Created: ${template.path} \u2014 ${template.description}`);
|
|
29960
|
+
state.generated_files.push(template.path);
|
|
29961
|
+
filesWritten++;
|
|
29962
|
+
} else {
|
|
29963
|
+
lines.push(`- Skipped: ${template.path} (already exists)`);
|
|
29964
|
+
}
|
|
29965
|
+
}
|
|
29966
|
+
for (const template of templates) {
|
|
29967
|
+
if (template.path.includes("Provider")) state.integration_status.provider_generated = true;
|
|
29968
|
+
if (template.path.includes("Pageview")) state.integration_status.pageview_tracker = true;
|
|
29969
|
+
if (template.path.includes("events") || template.path.includes("posthog.")) {
|
|
29970
|
+
state.integration_status.event_helper = true;
|
|
29971
|
+
}
|
|
29972
|
+
}
|
|
29973
|
+
lines.push("");
|
|
29974
|
+
lines.push("**Step 5: Environment Variables**");
|
|
29975
|
+
const envToWrite = {};
|
|
29976
|
+
if (framework.framework === "nextjs") {
|
|
29977
|
+
envToWrite["NEXT_PUBLIC_POSTHOG_KEY"] = apiKey || "phc_YOUR_KEY_HERE";
|
|
29978
|
+
envToWrite["NEXT_PUBLIC_POSTHOG_HOST"] = "https://us.i.posthog.com";
|
|
29979
|
+
} else {
|
|
29980
|
+
envToWrite["POSTHOG_API_KEY"] = apiKey || "phc_YOUR_KEY_HERE";
|
|
29981
|
+
envToWrite["POSTHOG_HOST"] = "https://us.i.posthog.com";
|
|
29982
|
+
}
|
|
29983
|
+
const added = writeEnvVars(projectPath, envToWrite);
|
|
29984
|
+
if (added.length > 0) {
|
|
29985
|
+
lines.push(`- Added to .env: ${added.join(", ")}`);
|
|
29986
|
+
ensureGitignore(projectPath);
|
|
29987
|
+
lines.push("- Ensured .env is in .gitignore");
|
|
29988
|
+
} else {
|
|
29989
|
+
lines.push("- Environment variables already present");
|
|
29990
|
+
}
|
|
29991
|
+
lines.push("");
|
|
29992
|
+
lines.push("**Step 6: KPI Adapter Configuration**");
|
|
29993
|
+
const adapterResult = configureKpiAdapter(projectPath);
|
|
29994
|
+
lines.push(adapterResult.message);
|
|
29995
|
+
lines.push("");
|
|
29996
|
+
lines.push("**Step 7: Recommended Events to Track**");
|
|
29997
|
+
const recommended = getRecommendedEvents(framework);
|
|
29998
|
+
lines.push("");
|
|
29999
|
+
lines.push("Track these events to power your KPI dashboard:");
|
|
30000
|
+
lines.push("");
|
|
30001
|
+
for (const evt of recommended) {
|
|
30002
|
+
const props = evt.properties.length > 0 ? ` (${evt.properties.join(", ")})` : "";
|
|
30003
|
+
lines.push(` - \`${evt.name}\` \u2014 ${evt.description}${props}`);
|
|
30004
|
+
}
|
|
30005
|
+
lines.push("");
|
|
30006
|
+
lines.push("Add them in your code with:");
|
|
30007
|
+
if (framework.framework === "nextjs") {
|
|
30008
|
+
lines.push(" `trackEvent('feature_used', { feature_name: 'dashboard' })`");
|
|
30009
|
+
} else {
|
|
30010
|
+
lines.push(" `client.capture({ distinctId: userId, event: 'feature_used', properties: { feature_name: 'dashboard' } })`");
|
|
30011
|
+
}
|
|
30012
|
+
lines.push("");
|
|
30013
|
+
lines.push("Then run `posthog-connect-kpis` to map events to your KPI dashboard.");
|
|
30014
|
+
lines.push("");
|
|
30015
|
+
const status = state.integration_status;
|
|
30016
|
+
const completedCount = Object.values(status).filter(Boolean).length;
|
|
30017
|
+
const totalCount = Object.keys(status).length;
|
|
30018
|
+
const completeness = Math.round(completedCount / totalCount * 100);
|
|
30019
|
+
lines.push("**Summary**");
|
|
30020
|
+
lines.push(`- Setup completeness: ${completeness}%`);
|
|
30021
|
+
lines.push(`- Files written: ${filesWritten}`);
|
|
30022
|
+
if (framework.framework === "nextjs" && framework.appRouter) {
|
|
30023
|
+
lines.push("");
|
|
30024
|
+
lines.push("**Next step:** Add PostHogProvider to your root layout:");
|
|
30025
|
+
lines.push("```");
|
|
30026
|
+
lines.push("import PostHogProvider from '../components/PostHogProvider';");
|
|
30027
|
+
lines.push("import PostHogPageview from '../components/PostHogPageview';");
|
|
30028
|
+
lines.push("");
|
|
30029
|
+
lines.push("// In your <body>:");
|
|
30030
|
+
lines.push("<PostHogProvider>");
|
|
30031
|
+
lines.push(" <PostHogPageview />");
|
|
30032
|
+
lines.push(" {children}");
|
|
30033
|
+
lines.push("</PostHogProvider>");
|
|
30034
|
+
lines.push("```");
|
|
30035
|
+
}
|
|
30036
|
+
saveState2(state);
|
|
30037
|
+
return { success: true, output: lines.join("\n") };
|
|
30038
|
+
}
|
|
30039
|
+
async function executeAddComponent(type) {
|
|
30040
|
+
const state = loadState2();
|
|
30041
|
+
const projectPath = getUserProjectPath2();
|
|
30042
|
+
const framework = state.framework || detectFramework(projectPath);
|
|
30043
|
+
const templates = getPostHogTemplates(framework);
|
|
30044
|
+
const lines = [];
|
|
30045
|
+
const filtered = templates.filter((t) => {
|
|
30046
|
+
if (type === "provider") return t.path.includes("Provider") || t.path.includes("Pageview");
|
|
30047
|
+
return t.path.includes("events") || t.path.includes("posthog.");
|
|
30048
|
+
});
|
|
30049
|
+
if (filtered.length === 0) {
|
|
30050
|
+
return { success: false, output: `No ${type} templates available for ${framework.framework}` };
|
|
30051
|
+
}
|
|
30052
|
+
for (const template of filtered) {
|
|
30053
|
+
const written = writeGeneratedFile2(projectPath, template);
|
|
30054
|
+
if (written) {
|
|
30055
|
+
lines.push(`Created: ${template.path} \u2014 ${template.description}`);
|
|
30056
|
+
state.generated_files.push(template.path);
|
|
30057
|
+
} else {
|
|
30058
|
+
lines.push(`Skipped: ${template.path} (already exists)`);
|
|
30059
|
+
}
|
|
30060
|
+
}
|
|
30061
|
+
if (type === "provider") {
|
|
30062
|
+
state.integration_status.provider_generated = true;
|
|
30063
|
+
state.integration_status.pageview_tracker = true;
|
|
30064
|
+
} else {
|
|
30065
|
+
state.integration_status.event_helper = true;
|
|
30066
|
+
}
|
|
30067
|
+
state.framework = framework;
|
|
30068
|
+
saveState2(state);
|
|
30069
|
+
return { success: true, output: lines.join("\n") };
|
|
30070
|
+
}
|
|
30071
|
+
function executeCheckStatus2() {
|
|
30072
|
+
const projectPath = getUserProjectPath2();
|
|
30073
|
+
const lines = ["### PostHog Integration Status", ""];
|
|
30074
|
+
const state = loadState2();
|
|
30075
|
+
const sdkInstalled = isPostHogInstalled(projectPath);
|
|
30076
|
+
lines.push(`- SDK installed: ${sdkInstalled ? "Yes" : "No"}`);
|
|
30077
|
+
const envVars = readEnvFile(projectPath);
|
|
30078
|
+
const apiKey = envVars["NEXT_PUBLIC_POSTHOG_KEY"] || envVars["POSTHOG_API_KEY"] || "";
|
|
30079
|
+
lines.push(`- API key configured: ${apiKey ? "Yes" : "No"}`);
|
|
30080
|
+
if (apiKey && !isValidKeyFormat(apiKey)) {
|
|
30081
|
+
lines.push(" (warning: key format looks invalid, expected phc_...)");
|
|
30082
|
+
}
|
|
30083
|
+
const framework = detectFramework(projectPath);
|
|
30084
|
+
const templates = getPostHogTemplates(framework);
|
|
30085
|
+
lines.push("");
|
|
30086
|
+
lines.push("**Generated files:**");
|
|
30087
|
+
for (const template of templates) {
|
|
30088
|
+
const exists = fs12.existsSync(path10.join(projectPath, template.path));
|
|
30089
|
+
lines.push(`- ${exists ? "[x]" : "[ ]"} ${template.path} \u2014 ${template.description}`);
|
|
30090
|
+
}
|
|
30091
|
+
const checks = [sdkInstalled, !!apiKey, ...templates.map(
|
|
30092
|
+
(t) => fs12.existsSync(path10.join(projectPath, t.path))
|
|
30093
|
+
)];
|
|
30094
|
+
const completed = checks.filter(Boolean).length;
|
|
30095
|
+
const completeness = Math.round(completed / checks.length * 100);
|
|
30096
|
+
lines.push("");
|
|
30097
|
+
lines.push(`**Completeness: ${completeness}%**`);
|
|
30098
|
+
if (completeness < 100) {
|
|
30099
|
+
lines.push("");
|
|
30100
|
+
lines.push("**To complete setup, run:** `posthog-setup`");
|
|
30101
|
+
}
|
|
30102
|
+
const ctxPath = getBusinessContextPath();
|
|
30103
|
+
let adapterConfigured = false;
|
|
30104
|
+
if (fs12.existsSync(ctxPath)) {
|
|
30105
|
+
try {
|
|
30106
|
+
const ctx = JSON.parse(fs12.readFileSync(ctxPath, "utf-8"));
|
|
30107
|
+
adapterConfigured = Array.isArray(ctx.kpi_adapters) && ctx.kpi_adapters.some((a) => a.id === "posthog");
|
|
30108
|
+
} catch {
|
|
30109
|
+
}
|
|
30110
|
+
}
|
|
30111
|
+
lines.push("");
|
|
30112
|
+
lines.push(`- KPI adapter configured: ${adapterConfigured ? "Yes" : "No"}`);
|
|
30113
|
+
if (!adapterConfigured) {
|
|
30114
|
+
lines.push(" (run `posthog-setup` to auto-configure the KPI adapter)");
|
|
30115
|
+
}
|
|
30116
|
+
state.integration_status.sdk_installed = sdkInstalled;
|
|
30117
|
+
state.integration_status.api_key_valid = !!apiKey && isValidKeyFormat(apiKey);
|
|
30118
|
+
state.framework = framework;
|
|
30119
|
+
saveState2(state);
|
|
30120
|
+
return { success: true, output: lines.join("\n") };
|
|
30121
|
+
}
|
|
30122
|
+
function getBusinessContextPath() {
|
|
30123
|
+
return path10.join(PROJECT_DIR, "data", "business-context.json");
|
|
30124
|
+
}
|
|
30125
|
+
function configureKpiAdapter(_projectPath) {
|
|
30126
|
+
const ctxPath = getBusinessContextPath();
|
|
30127
|
+
if (!fs12.existsSync(ctxPath)) {
|
|
30128
|
+
return { added: false, message: "- business-context.json not found \u2014 run `vibebusiness init` first" };
|
|
30129
|
+
}
|
|
30130
|
+
try {
|
|
30131
|
+
const ctx = JSON.parse(fs12.readFileSync(ctxPath, "utf-8"));
|
|
30132
|
+
if (!Array.isArray(ctx.kpi_adapters)) {
|
|
30133
|
+
ctx.kpi_adapters = [];
|
|
30134
|
+
}
|
|
30135
|
+
const existing = ctx.kpi_adapters.find((a) => a.id === "posthog");
|
|
30136
|
+
if (existing) {
|
|
30137
|
+
return { added: false, message: "- PostHog KPI adapter already configured" };
|
|
30138
|
+
}
|
|
30139
|
+
ctx.kpi_adapters.push({
|
|
30140
|
+
id: "posthog",
|
|
30141
|
+
type: "posthog",
|
|
30142
|
+
config: {
|
|
30143
|
+
project_id_env: "POSTHOG_PROJECT_ID"
|
|
30144
|
+
},
|
|
30145
|
+
field_mapping: {}
|
|
30146
|
+
});
|
|
30147
|
+
fs12.writeFileSync(ctxPath, JSON.stringify(ctx, null, 2) + "\n");
|
|
30148
|
+
return { added: true, message: "- Added PostHog KPI adapter to business-context.json" };
|
|
30149
|
+
} catch (err2) {
|
|
30150
|
+
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
30151
|
+
return { added: false, message: `- Could not update business-context.json: ${msg}` };
|
|
30152
|
+
}
|
|
30153
|
+
}
|
|
30154
|
+
function findPosthogEvents(projectPath) {
|
|
30155
|
+
const events = /* @__PURE__ */ new Set();
|
|
30156
|
+
const patterns = [
|
|
30157
|
+
/posthog\.capture\(\s*['"`]([^'"`]+)['"`]/g,
|
|
30158
|
+
/trackEvent\(\s*['"`]([^'"`]+)['"`]/g,
|
|
30159
|
+
/client\.capture\(\s*\{[^}]*event:\s*['"`]([^'"`]+)['"`]/g
|
|
30160
|
+
];
|
|
30161
|
+
function scanDir(dir, depth) {
|
|
30162
|
+
if (depth > 5) return;
|
|
30163
|
+
let entries;
|
|
30164
|
+
try {
|
|
30165
|
+
entries = fs12.readdirSync(dir, { withFileTypes: true });
|
|
30166
|
+
} catch {
|
|
30167
|
+
return;
|
|
30168
|
+
}
|
|
30169
|
+
for (const entry of entries) {
|
|
30170
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist") continue;
|
|
30171
|
+
const full = path10.join(dir, entry.name);
|
|
30172
|
+
if (entry.isDirectory()) {
|
|
30173
|
+
scanDir(full, depth + 1);
|
|
30174
|
+
} else if (/\.(ts|tsx|js|jsx)$/.test(entry.name)) {
|
|
30175
|
+
try {
|
|
30176
|
+
const content = fs12.readFileSync(full, "utf-8");
|
|
30177
|
+
for (const pattern2 of patterns) {
|
|
30178
|
+
pattern2.lastIndex = 0;
|
|
30179
|
+
let match;
|
|
30180
|
+
while ((match = pattern2.exec(content)) !== null) {
|
|
30181
|
+
if (match[1] && !match[1].startsWith("$")) {
|
|
30182
|
+
events.add(match[1]);
|
|
30183
|
+
}
|
|
30184
|
+
}
|
|
30185
|
+
}
|
|
30186
|
+
} catch {
|
|
30187
|
+
}
|
|
30188
|
+
}
|
|
30189
|
+
}
|
|
30190
|
+
}
|
|
30191
|
+
scanDir(projectPath, 0);
|
|
30192
|
+
return Array.from(events);
|
|
30193
|
+
}
|
|
30194
|
+
function executeConnectKpis() {
|
|
30195
|
+
const lines = ["### PostHog \u2192 KPI Connection", ""];
|
|
30196
|
+
const projectPath = getUserProjectPath2();
|
|
30197
|
+
const events = findPosthogEvents(projectPath);
|
|
30198
|
+
lines.push(`**Detected PostHog events in codebase:** ${events.length}`);
|
|
30199
|
+
if (events.length === 0) {
|
|
30200
|
+
lines.push("- No posthog.capture() or trackEvent() calls found");
|
|
30201
|
+
lines.push("- Run `posthog-setup` first to generate event helpers");
|
|
30202
|
+
return { success: true, output: lines.join("\n") };
|
|
30203
|
+
}
|
|
30204
|
+
for (const evt of events) {
|
|
30205
|
+
lines.push(` - \`${evt}\``);
|
|
30206
|
+
}
|
|
30207
|
+
lines.push("");
|
|
30208
|
+
const goalsPath = path10.join(PROJECT_DIR, "data", "goals.json");
|
|
30209
|
+
const kpiNames = [];
|
|
30210
|
+
if (fs12.existsSync(goalsPath)) {
|
|
30211
|
+
try {
|
|
30212
|
+
const goals = JSON.parse(fs12.readFileSync(goalsPath, "utf-8"));
|
|
30213
|
+
for (const goal of goals.goals || []) {
|
|
30214
|
+
for (const kpi of goal.kpis || []) {
|
|
30215
|
+
kpiNames.push(kpi.name || kpi.id);
|
|
30216
|
+
}
|
|
30217
|
+
}
|
|
30218
|
+
} catch {
|
|
30219
|
+
}
|
|
30220
|
+
}
|
|
30221
|
+
lines.push(`**Existing KPIs:** ${kpiNames.length}`);
|
|
30222
|
+
if (kpiNames.length > 0) {
|
|
30223
|
+
for (const name of kpiNames) {
|
|
30224
|
+
lines.push(` - ${name}`);
|
|
30225
|
+
}
|
|
30226
|
+
}
|
|
30227
|
+
lines.push("");
|
|
30228
|
+
const suggestedMappings = {};
|
|
30229
|
+
for (const evt of events) {
|
|
30230
|
+
const kpiId = `kpi-${evt.replace(/_/g, "-")}`;
|
|
30231
|
+
suggestedMappings[kpiId] = evt;
|
|
30232
|
+
}
|
|
30233
|
+
const ctxPath = getBusinessContextPath();
|
|
30234
|
+
let mappingUpdated = false;
|
|
30235
|
+
if (fs12.existsSync(ctxPath)) {
|
|
30236
|
+
try {
|
|
30237
|
+
const ctx = JSON.parse(fs12.readFileSync(ctxPath, "utf-8"));
|
|
30238
|
+
if (!Array.isArray(ctx.kpi_adapters)) {
|
|
30239
|
+
ctx.kpi_adapters = [];
|
|
30240
|
+
}
|
|
30241
|
+
let adapter = ctx.kpi_adapters.find((a) => a.id === "posthog");
|
|
30242
|
+
if (!adapter) {
|
|
30243
|
+
adapter = { id: "posthog", type: "posthog", config: { project_id_env: "POSTHOG_PROJECT_ID" }, field_mapping: {} };
|
|
30244
|
+
ctx.kpi_adapters.push(adapter);
|
|
30245
|
+
}
|
|
30246
|
+
const existingMapping = adapter.field_mapping || {};
|
|
30247
|
+
let added = 0;
|
|
30248
|
+
for (const [kpiId, eventName] of Object.entries(suggestedMappings)) {
|
|
30249
|
+
if (!existingMapping[kpiId]) {
|
|
30250
|
+
existingMapping[kpiId] = eventName;
|
|
30251
|
+
added++;
|
|
30252
|
+
}
|
|
30253
|
+
}
|
|
30254
|
+
adapter.field_mapping = existingMapping;
|
|
30255
|
+
fs12.writeFileSync(ctxPath, JSON.stringify(ctx, null, 2) + "\n");
|
|
30256
|
+
mappingUpdated = true;
|
|
30257
|
+
lines.push(`**Updated field_mapping:** ${added} new mapping(s) added`);
|
|
30258
|
+
} catch (err2) {
|
|
30259
|
+
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
30260
|
+
lines.push(`**Warning:** Could not update business-context.json: ${msg}`);
|
|
30261
|
+
}
|
|
30262
|
+
}
|
|
30263
|
+
lines.push("");
|
|
30264
|
+
lines.push("**Suggested mappings:**");
|
|
30265
|
+
for (const [kpiId, eventName] of Object.entries(suggestedMappings)) {
|
|
30266
|
+
lines.push(` - ${kpiId} \u2192 \`${eventName}\``);
|
|
30267
|
+
}
|
|
30268
|
+
lines.push("");
|
|
30269
|
+
if (mappingUpdated) {
|
|
30270
|
+
lines.push(`Connected ${events.length} PostHog event(s) to KPI mappings in business-context.json.`);
|
|
30271
|
+
}
|
|
30272
|
+
lines.push("");
|
|
30273
|
+
lines.push("**Next:** Create matching KPIs in your goals via the dashboard or `vibebusiness manage goals`.");
|
|
30274
|
+
return { success: true, output: lines.join("\n") };
|
|
30275
|
+
}
|
|
30276
|
+
|
|
29392
30277
|
// scripts/skills/social-media.ts
|
|
29393
|
-
var
|
|
29394
|
-
var
|
|
30278
|
+
var fs18 = __toESM(require("fs"));
|
|
30279
|
+
var path12 = __toESM(require("path"));
|
|
29395
30280
|
|
|
29396
30281
|
// node_modules/uuid/dist/esm-node/rng.js
|
|
29397
30282
|
var import_crypto = __toESM(require("crypto"));
|
|
@@ -29511,8 +30396,8 @@ async function postTweet(client, text) {
|
|
|
29511
30396
|
};
|
|
29512
30397
|
}
|
|
29513
30398
|
async function uploadMedia(client, filePath) {
|
|
29514
|
-
const
|
|
29515
|
-
if (!
|
|
30399
|
+
const fs32 = require("fs");
|
|
30400
|
+
if (!fs32.existsSync(filePath)) {
|
|
29516
30401
|
throw new Error(`Media file not found: ${filePath}`);
|
|
29517
30402
|
}
|
|
29518
30403
|
const mediaId = await client.v1.uploadMedia(filePath);
|
|
@@ -29543,9 +30428,83 @@ async function postTweetWithMedia(client, text, mediaIds) {
|
|
|
29543
30428
|
url: `https://x.com/i/status/${tweetId}`
|
|
29544
30429
|
};
|
|
29545
30430
|
}
|
|
30431
|
+
async function getTweetMetrics(client, tweetId) {
|
|
30432
|
+
try {
|
|
30433
|
+
const result = await client.v2.singleTweet(tweetId, {
|
|
30434
|
+
"tweet.fields": ["public_metrics", "non_public_metrics", "organic_metrics"]
|
|
30435
|
+
});
|
|
30436
|
+
const data = result?.data;
|
|
30437
|
+
if (!data) return null;
|
|
30438
|
+
const pub = data.public_metrics ?? {};
|
|
30439
|
+
const nonPub = data.non_public_metrics ?? {};
|
|
30440
|
+
const organic = data.organic_metrics ?? {};
|
|
30441
|
+
const impressions = nonPub.impression_count ?? organic.impression_count ?? 0;
|
|
30442
|
+
const engagements = (pub.like_count ?? 0) + (pub.retweet_count ?? 0) + (pub.reply_count ?? 0) + (pub.bookmark_count ?? 0) + (nonPub.url_link_clicks ?? 0) + (nonPub.user_profile_clicks ?? 0);
|
|
30443
|
+
return {
|
|
30444
|
+
tweet_id: tweetId,
|
|
30445
|
+
impressions,
|
|
30446
|
+
likes: pub.like_count ?? 0,
|
|
30447
|
+
retweets: pub.retweet_count ?? 0,
|
|
30448
|
+
replies: pub.reply_count ?? 0,
|
|
30449
|
+
bookmarks: pub.bookmark_count ?? 0,
|
|
30450
|
+
profile_clicks: nonPub.user_profile_clicks ?? 0,
|
|
30451
|
+
engagements,
|
|
30452
|
+
engagement_rate: impressions > 0 ? engagements / impressions : 0,
|
|
30453
|
+
fetched_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
30454
|
+
};
|
|
30455
|
+
} catch (err2) {
|
|
30456
|
+
console.error(`Failed to fetch metrics for tweet ${tweetId}:`, err2);
|
|
30457
|
+
return null;
|
|
30458
|
+
}
|
|
30459
|
+
}
|
|
30460
|
+
async function getBatchTweetMetrics(client, tweetIds) {
|
|
30461
|
+
const results = [];
|
|
30462
|
+
for (const id of tweetIds) {
|
|
30463
|
+
const metrics = await getTweetMetrics(client, id);
|
|
30464
|
+
if (metrics) {
|
|
30465
|
+
results.push(metrics);
|
|
30466
|
+
}
|
|
30467
|
+
}
|
|
30468
|
+
return results;
|
|
30469
|
+
}
|
|
30470
|
+
async function postThread(client, tweets, mediaIds) {
|
|
30471
|
+
if (tweets.length === 0) {
|
|
30472
|
+
throw new Error("Thread must have at least one tweet");
|
|
30473
|
+
}
|
|
30474
|
+
if (tweets.length > 25) {
|
|
30475
|
+
throw new Error("Thread cannot exceed 25 tweets");
|
|
30476
|
+
}
|
|
30477
|
+
for (let i = 0; i < tweets.length; i++) {
|
|
30478
|
+
if (tweets[i].length > 280) {
|
|
30479
|
+
throw new Error(`Tweet ${i + 1} exceeds 280 characters (${tweets[i].length})`);
|
|
30480
|
+
}
|
|
30481
|
+
}
|
|
30482
|
+
const results = [];
|
|
30483
|
+
for (let i = 0; i < tweets.length; i++) {
|
|
30484
|
+
const tweetText = tweets[i];
|
|
30485
|
+
const mediaId = mediaIds?.[i];
|
|
30486
|
+
const payload = {};
|
|
30487
|
+
if (i > 0) {
|
|
30488
|
+
payload.reply = { in_reply_to_tweet_id: results[i - 1].id };
|
|
30489
|
+
}
|
|
30490
|
+
if (mediaId) {
|
|
30491
|
+
payload.media = { media_ids: [mediaId] };
|
|
30492
|
+
}
|
|
30493
|
+
const result = await client.v2.tweet(tweetText, payload);
|
|
30494
|
+
const tweetId = result?.data?.id;
|
|
30495
|
+
if (!tweetId) {
|
|
30496
|
+
throw new Error(`Thread tweet ${i + 1} posted but no ID returned`);
|
|
30497
|
+
}
|
|
30498
|
+
results.push({
|
|
30499
|
+
id: tweetId,
|
|
30500
|
+
url: `https://x.com/i/status/${tweetId}`
|
|
30501
|
+
});
|
|
30502
|
+
}
|
|
30503
|
+
return results;
|
|
30504
|
+
}
|
|
29546
30505
|
|
|
29547
30506
|
// scripts/lib/social/content-generator.ts
|
|
29548
|
-
var
|
|
30507
|
+
var fs15 = __toESM(require("fs"));
|
|
29549
30508
|
var MAX_TWEET_LENGTH = 280;
|
|
29550
30509
|
var HASHTAG = "#buildinpublic";
|
|
29551
30510
|
function truncateTweet(text, maxLength = MAX_TWEET_LENGTH) {
|
|
@@ -29554,8 +30513,8 @@ function truncateTweet(text, maxLength = MAX_TWEET_LENGTH) {
|
|
|
29554
30513
|
}
|
|
29555
30514
|
function readJsonSafe(filePath, fallback) {
|
|
29556
30515
|
try {
|
|
29557
|
-
if (!
|
|
29558
|
-
return JSON.parse(
|
|
30516
|
+
if (!fs15.existsSync(filePath)) return fallback;
|
|
30517
|
+
return JSON.parse(fs15.readFileSync(filePath, "utf-8"));
|
|
29559
30518
|
} catch {
|
|
29560
30519
|
return fallback;
|
|
29561
30520
|
}
|
|
@@ -29631,8 +30590,418 @@ function generateProductUpdateTweet(positioning) {
|
|
|
29631
30590
|
return truncateTweet(base);
|
|
29632
30591
|
}
|
|
29633
30592
|
|
|
30593
|
+
// scripts/lib/social/ai-content-generator.ts
|
|
30594
|
+
var fs17 = __toESM(require("fs"));
|
|
30595
|
+
|
|
30596
|
+
// scripts/lib/ai-provider.ts
|
|
30597
|
+
var import_child_process6 = require("child_process");
|
|
30598
|
+
var fs16 = __toESM(require("fs"));
|
|
30599
|
+
var path11 = __toESM(require("path"));
|
|
30600
|
+
function detectClaudeCLI(customPath) {
|
|
30601
|
+
const claudeBin = customPath || "claude";
|
|
30602
|
+
try {
|
|
30603
|
+
const version = (0, import_child_process6.execSync)(`${claudeBin} --version 2>/dev/null`, {
|
|
30604
|
+
timeout: 1e4,
|
|
30605
|
+
encoding: "utf-8"
|
|
30606
|
+
}).trim();
|
|
30607
|
+
return { found: true, path: claudeBin, version };
|
|
30608
|
+
} catch {
|
|
30609
|
+
const commonPaths = [
|
|
30610
|
+
"/usr/local/bin/claude",
|
|
30611
|
+
path11.join(process.env.HOME || "", ".claude", "bin", "claude"),
|
|
30612
|
+
path11.join(process.env.HOME || "", ".local", "bin", "claude")
|
|
30613
|
+
];
|
|
30614
|
+
for (const p of commonPaths) {
|
|
30615
|
+
try {
|
|
30616
|
+
if (fs16.existsSync(p)) {
|
|
30617
|
+
const version = (0, import_child_process6.execSync)(`${p} --version 2>/dev/null`, {
|
|
30618
|
+
timeout: 1e4,
|
|
30619
|
+
encoding: "utf-8"
|
|
30620
|
+
}).trim();
|
|
30621
|
+
return { found: true, path: p, version };
|
|
30622
|
+
}
|
|
30623
|
+
} catch {
|
|
30624
|
+
continue;
|
|
30625
|
+
}
|
|
30626
|
+
}
|
|
30627
|
+
return { found: false, path: claudeBin };
|
|
30628
|
+
}
|
|
30629
|
+
}
|
|
30630
|
+
function detectBYOKKeys() {
|
|
30631
|
+
if (process.env.ANTHROPIC_API_KEY) {
|
|
30632
|
+
return { provider: "anthropic-api", key: process.env.ANTHROPIC_API_KEY };
|
|
30633
|
+
}
|
|
30634
|
+
if (process.env.OPENAI_API_KEY) {
|
|
30635
|
+
return { provider: "openai-api", key: process.env.OPENAI_API_KEY };
|
|
30636
|
+
}
|
|
30637
|
+
if (process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY) {
|
|
30638
|
+
return { provider: "google-api", key: process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY };
|
|
30639
|
+
}
|
|
30640
|
+
return null;
|
|
30641
|
+
}
|
|
30642
|
+
function detectProvider(config) {
|
|
30643
|
+
const cli = detectClaudeCLI(config?.claudePath);
|
|
30644
|
+
if (cli.found) {
|
|
30645
|
+
return {
|
|
30646
|
+
provider: "claude-cli",
|
|
30647
|
+
version: cli.version,
|
|
30648
|
+
message: `Found Claude Code CLI${cli.version ? ` (${cli.version})` : ""}. Using your Claude subscription for AI reasoning.`
|
|
30649
|
+
};
|
|
30650
|
+
}
|
|
30651
|
+
const byok = detectBYOKKeys();
|
|
30652
|
+
if (byok) {
|
|
30653
|
+
const providerNames = {
|
|
30654
|
+
"claude-cli": "Claude CLI",
|
|
30655
|
+
"anthropic-api": "Anthropic API",
|
|
30656
|
+
"openai-api": "OpenAI API",
|
|
30657
|
+
"google-api": "Google AI API"
|
|
30658
|
+
};
|
|
30659
|
+
return {
|
|
30660
|
+
provider: byok.provider,
|
|
30661
|
+
message: `Using ${providerNames[byok.provider]} key for AI reasoning.`
|
|
30662
|
+
};
|
|
30663
|
+
}
|
|
30664
|
+
return {
|
|
30665
|
+
provider: "claude-cli",
|
|
30666
|
+
// default, will fail at invocation
|
|
30667
|
+
message: "No AI provider found. Install Claude Code CLI or set an API key (ANTHROPIC_API_KEY, OPENAI_API_KEY, or GOOGLE_API_KEY)."
|
|
30668
|
+
};
|
|
30669
|
+
}
|
|
30670
|
+
var CONFIG_DIR = path11.join(process.env.HOME || "", ".ai-analyst");
|
|
30671
|
+
var PROVIDER_CONFIG_FILE = path11.join(CONFIG_DIR, "provider.json");
|
|
30672
|
+
function loadProviderConfig() {
|
|
30673
|
+
try {
|
|
30674
|
+
if (fs16.existsSync(PROVIDER_CONFIG_FILE)) {
|
|
30675
|
+
return JSON.parse(fs16.readFileSync(PROVIDER_CONFIG_FILE, "utf-8"));
|
|
30676
|
+
}
|
|
30677
|
+
} catch {
|
|
30678
|
+
}
|
|
30679
|
+
return null;
|
|
30680
|
+
}
|
|
30681
|
+
function invokeClaudeCLI(options, claudePath) {
|
|
30682
|
+
const bin = claudePath || "claude";
|
|
30683
|
+
const args2 = ["--print"];
|
|
30684
|
+
if (options.model) {
|
|
30685
|
+
args2.push("--model", options.model);
|
|
30686
|
+
}
|
|
30687
|
+
if (options.claudeFlags) {
|
|
30688
|
+
args2.push(...options.claudeFlags);
|
|
30689
|
+
}
|
|
30690
|
+
const useStdin = options.useStdin !== false && options.prompt.length > 1e4;
|
|
30691
|
+
if (!useStdin) {
|
|
30692
|
+
args2.push("-p", options.prompt);
|
|
30693
|
+
}
|
|
30694
|
+
const startTime = Date.now();
|
|
30695
|
+
const timeoutMs = options.timeoutMs || 3e5;
|
|
30696
|
+
return new Promise((resolve2) => {
|
|
30697
|
+
const child = (0, import_child_process6.spawn)(bin, args2, {
|
|
30698
|
+
cwd: options.cwd || process.cwd(),
|
|
30699
|
+
env: process.env,
|
|
30700
|
+
stdio: useStdin ? ["pipe", "pipe", "pipe"] : ["ignore", "pipe", "pipe"]
|
|
30701
|
+
});
|
|
30702
|
+
let stdout = "";
|
|
30703
|
+
let stderr = "";
|
|
30704
|
+
child.stdout?.on("data", (data) => {
|
|
30705
|
+
stdout += data.toString();
|
|
30706
|
+
});
|
|
30707
|
+
child.stderr?.on("data", (data) => {
|
|
30708
|
+
stderr += data.toString();
|
|
30709
|
+
});
|
|
30710
|
+
if (useStdin && child.stdin) {
|
|
30711
|
+
child.stdin.write(options.prompt);
|
|
30712
|
+
child.stdin.end();
|
|
30713
|
+
}
|
|
30714
|
+
const timeout = setTimeout(() => {
|
|
30715
|
+
child.kill("SIGTERM");
|
|
30716
|
+
resolve2({
|
|
30717
|
+
output: stdout,
|
|
30718
|
+
provider: "claude-cli",
|
|
30719
|
+
durationMs: Date.now() - startTime,
|
|
30720
|
+
error: `Timeout after ${timeoutMs}ms`
|
|
30721
|
+
});
|
|
30722
|
+
}, timeoutMs);
|
|
30723
|
+
child.on("close", (code) => {
|
|
30724
|
+
clearTimeout(timeout);
|
|
30725
|
+
resolve2({
|
|
30726
|
+
output: stdout.trim(),
|
|
30727
|
+
provider: "claude-cli",
|
|
30728
|
+
durationMs: Date.now() - startTime,
|
|
30729
|
+
error: code !== 0 ? `Claude CLI exited with code ${code}: ${stderr}` : void 0
|
|
30730
|
+
});
|
|
30731
|
+
});
|
|
30732
|
+
child.on("error", (err2) => {
|
|
30733
|
+
clearTimeout(timeout);
|
|
30734
|
+
resolve2({
|
|
30735
|
+
output: "",
|
|
30736
|
+
provider: "claude-cli",
|
|
30737
|
+
durationMs: Date.now() - startTime,
|
|
30738
|
+
error: `Failed to spawn Claude CLI: ${err2.message}`
|
|
30739
|
+
});
|
|
30740
|
+
});
|
|
30741
|
+
});
|
|
30742
|
+
}
|
|
30743
|
+
async function invokeAnthropicAPI(options) {
|
|
30744
|
+
const startTime = Date.now();
|
|
30745
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
30746
|
+
if (!apiKey) {
|
|
30747
|
+
return {
|
|
30748
|
+
output: "",
|
|
30749
|
+
provider: "anthropic-api",
|
|
30750
|
+
durationMs: 0,
|
|
30751
|
+
error: "ANTHROPIC_API_KEY not set"
|
|
30752
|
+
};
|
|
30753
|
+
}
|
|
30754
|
+
try {
|
|
30755
|
+
const model = options.model || "claude-sonnet-4-5-20250929";
|
|
30756
|
+
const response = await fetch("https://api.anthropic.com/v1/messages", {
|
|
30757
|
+
method: "POST",
|
|
30758
|
+
headers: {
|
|
30759
|
+
"Content-Type": "application/json",
|
|
30760
|
+
"x-api-key": apiKey,
|
|
30761
|
+
"anthropic-version": "2023-06-01"
|
|
30762
|
+
},
|
|
30763
|
+
body: JSON.stringify({
|
|
30764
|
+
model,
|
|
30765
|
+
max_tokens: 16384,
|
|
30766
|
+
messages: [{ role: "user", content: options.prompt }]
|
|
30767
|
+
}),
|
|
30768
|
+
signal: AbortSignal.timeout(options.timeoutMs || 3e5)
|
|
30769
|
+
});
|
|
30770
|
+
if (!response.ok) {
|
|
30771
|
+
const errorText = await response.text();
|
|
30772
|
+
return {
|
|
30773
|
+
output: "",
|
|
30774
|
+
provider: "anthropic-api",
|
|
30775
|
+
durationMs: Date.now() - startTime,
|
|
30776
|
+
error: `Anthropic API error ${response.status}: ${errorText}`
|
|
30777
|
+
};
|
|
30778
|
+
}
|
|
30779
|
+
const data = await response.json();
|
|
30780
|
+
const text = data.content.filter((block) => block.type === "text").map((block) => block.text).join("\n");
|
|
30781
|
+
return {
|
|
30782
|
+
output: text,
|
|
30783
|
+
provider: "anthropic-api",
|
|
30784
|
+
durationMs: Date.now() - startTime
|
|
30785
|
+
};
|
|
30786
|
+
} catch (err2) {
|
|
30787
|
+
return {
|
|
30788
|
+
output: "",
|
|
30789
|
+
provider: "anthropic-api",
|
|
30790
|
+
durationMs: Date.now() - startTime,
|
|
30791
|
+
error: `Anthropic API call failed: ${err2.message}`
|
|
30792
|
+
};
|
|
30793
|
+
}
|
|
30794
|
+
}
|
|
30795
|
+
async function invokeAI(options) {
|
|
30796
|
+
const savedConfig = loadProviderConfig();
|
|
30797
|
+
const detection = detectProvider(savedConfig || void 0);
|
|
30798
|
+
switch (detection.provider) {
|
|
30799
|
+
case "claude-cli":
|
|
30800
|
+
return invokeClaudeCLI(options, savedConfig?.claudePath);
|
|
30801
|
+
case "anthropic-api":
|
|
30802
|
+
return invokeAnthropicAPI(options);
|
|
30803
|
+
case "openai-api":
|
|
30804
|
+
case "google-api":
|
|
30805
|
+
return {
|
|
30806
|
+
output: "",
|
|
30807
|
+
provider: detection.provider,
|
|
30808
|
+
durationMs: 0,
|
|
30809
|
+
error: `${detection.provider} provider not yet implemented. Use Claude CLI or set ANTHROPIC_API_KEY.`
|
|
30810
|
+
};
|
|
30811
|
+
default:
|
|
30812
|
+
return {
|
|
30813
|
+
output: "",
|
|
30814
|
+
provider: "claude-cli",
|
|
30815
|
+
durationMs: 0,
|
|
30816
|
+
error: detection.message
|
|
30817
|
+
};
|
|
30818
|
+
}
|
|
30819
|
+
}
|
|
30820
|
+
|
|
30821
|
+
// scripts/lib/social/ai-content-generator.ts
|
|
30822
|
+
function readJsonSafe2(filePath, fallback) {
|
|
30823
|
+
try {
|
|
30824
|
+
if (!fs17.existsSync(filePath)) return fallback;
|
|
30825
|
+
return JSON.parse(fs17.readFileSync(filePath, "utf-8"));
|
|
30826
|
+
} catch {
|
|
30827
|
+
return fallback;
|
|
30828
|
+
}
|
|
30829
|
+
}
|
|
30830
|
+
function formatPastPerformance(data) {
|
|
30831
|
+
if (!data || data.length === 0) return "No historical data yet.";
|
|
30832
|
+
const sorted = [...data].sort((a, b) => b.engagement_rate - a.engagement_rate);
|
|
30833
|
+
const avg = data.reduce((s, d2) => s + d2.engagement_rate, 0) / data.length;
|
|
30834
|
+
const best = sorted[0];
|
|
30835
|
+
return [
|
|
30836
|
+
`Average engagement rate: ${(avg * 100).toFixed(2)}%`,
|
|
30837
|
+
`Best tweet: ${best.tweet_id} (${(best.engagement_rate * 100).toFixed(2)}% rate, ${best.impressions} impressions, ${best.bookmarks} bookmarks)`,
|
|
30838
|
+
`Total tweets analyzed: ${data.length}`
|
|
30839
|
+
].join("\n");
|
|
30840
|
+
}
|
|
30841
|
+
var ALGORITHM_KNOWLEDGE = `
|
|
30842
|
+
X/Twitter Algorithm Knowledge (use this to optimize content):
|
|
30843
|
+
- Author replies to own tweet: +75x weight. ALWAYS plan for a self-reply.
|
|
30844
|
+
- Bookmarks: ~10x weight of likes. Write "save-worthy" content (lists, frameworks, data).
|
|
30845
|
+
- User replies: +13.5x weight. End with questions or hot takes that invite responses.
|
|
30846
|
+
- Threads get 200-300% more reach than single tweets. Lead with a killer hook.
|
|
30847
|
+
- Engagement velocity in first 30 minutes determines 80% of reach.
|
|
30848
|
+
- 1-2 hashtags max. #buildinpublic is essential for our audience.
|
|
30849
|
+
- Short paragraphs, line breaks between ideas for readability.
|
|
30850
|
+
- Numbers and specific data points dramatically increase engagement.
|
|
30851
|
+
`;
|
|
30852
|
+
var HOOK_PATTERNS = `
|
|
30853
|
+
Proven Hook Patterns (rotate between these):
|
|
30854
|
+
1. Stat-opening: "I analyzed 100 indie hackers..." / "After 30 days of..."
|
|
30855
|
+
2. Contrarian take: "Hot take: [conventional wisdom] is wrong because..."
|
|
30856
|
+
3. Story hook: "6 months ago I couldn't [X]. Today I [Y]. Here's what changed:"
|
|
30857
|
+
4. Question: "Why do 90% of indie hackers fail at [X]?"
|
|
30858
|
+
5. List tease: "5 things I learned building [product] this week:"
|
|
30859
|
+
6. Before/after: "Before: [pain]. After: [result]. The difference? [insight]"
|
|
30860
|
+
7. Bold claim: "[Product] just did something no other tool can do."
|
|
30861
|
+
`;
|
|
30862
|
+
async function generateViralTweet(opts) {
|
|
30863
|
+
const positioning = opts.positioning ?? readJsonSafe2(POSITIONING_FILE, null);
|
|
30864
|
+
const prompt = `You are a viral tweet copywriter for a developer-focused indie hacker audience.
|
|
30865
|
+
|
|
30866
|
+
${ALGORITHM_KNOWLEDGE}
|
|
30867
|
+
|
|
30868
|
+
${HOOK_PATTERNS}
|
|
30869
|
+
|
|
30870
|
+
PRODUCT CONTEXT:
|
|
30871
|
+
- Product: ${positioning?.current?.tagline ?? "VibeBusiness"}
|
|
30872
|
+
- Value prop: ${positioning?.current?.value_proposition ?? "AI-powered business automation"}
|
|
30873
|
+
- Target audience: ${positioning?.current?.target_audience?.primary ?? "Indie hackers and solo founders"}
|
|
30874
|
+
|
|
30875
|
+
TOPIC: ${opts.topic}
|
|
30876
|
+
CONTEXT: ${opts.context}
|
|
30877
|
+
|
|
30878
|
+
HISTORICAL PERFORMANCE:
|
|
30879
|
+
${formatPastPerformance(opts.pastPerformance ?? [])}
|
|
30880
|
+
|
|
30881
|
+
Generate ONE tweet (max 280 characters). Focus on being genuinely interesting, not salesy.
|
|
30882
|
+
|
|
30883
|
+
Respond in this exact JSON format (no markdown, no code blocks):
|
|
30884
|
+
{
|
|
30885
|
+
"text": "the tweet text here",
|
|
30886
|
+
"hook_type": "stat-opening|contrarian|story|question|list-tease|before-after|bold-claim",
|
|
30887
|
+
"estimated_engagement": "low|medium|high"
|
|
30888
|
+
}`;
|
|
30889
|
+
const result = await invokeAI({ prompt, model: "haiku" });
|
|
30890
|
+
try {
|
|
30891
|
+
const parsed = JSON.parse(extractJson(result.output));
|
|
30892
|
+
if (parsed.text.length > 280) {
|
|
30893
|
+
parsed.text = parsed.text.slice(0, 277).trimEnd() + "...";
|
|
30894
|
+
}
|
|
30895
|
+
return parsed;
|
|
30896
|
+
} catch {
|
|
30897
|
+
const text = result.output.trim().slice(0, 280);
|
|
30898
|
+
return { text, hook_type: "unknown", estimated_engagement: "medium" };
|
|
30899
|
+
}
|
|
30900
|
+
}
|
|
30901
|
+
async function generateViralThread(opts) {
|
|
30902
|
+
const positioning = opts.positioning ?? readJsonSafe2(POSITIONING_FILE, null);
|
|
30903
|
+
const length = opts.threadLength ?? 5;
|
|
30904
|
+
const prompt = `You are a viral thread copywriter for a developer-focused indie hacker audience.
|
|
30905
|
+
|
|
30906
|
+
${ALGORITHM_KNOWLEDGE}
|
|
30907
|
+
|
|
30908
|
+
${HOOK_PATTERNS}
|
|
30909
|
+
|
|
30910
|
+
PRODUCT CONTEXT:
|
|
30911
|
+
- Product: ${positioning?.current?.tagline ?? "VibeBusiness"}
|
|
30912
|
+
- Value prop: ${positioning?.current?.value_proposition ?? "AI-powered business automation"}
|
|
30913
|
+
- Target audience: ${positioning?.current?.target_audience?.primary ?? "Indie hackers and solo founders"}
|
|
30914
|
+
|
|
30915
|
+
THREAD STRUCTURE (${length} tweets):
|
|
30916
|
+
1. HOOK tweet \u2014 the most important tweet. Must stop the scroll. Use a proven hook pattern.
|
|
30917
|
+
2-${length - 1}. VALUE tweets \u2014 each tweet delivers one insight, data point, or lesson. Be specific.
|
|
30918
|
+
${length}. CTA tweet \u2014 end with a question or call-to-action. Invite replies and follows.
|
|
30919
|
+
|
|
30920
|
+
TOPIC: ${opts.topic}
|
|
30921
|
+
CONTEXT: ${opts.context}
|
|
30922
|
+
|
|
30923
|
+
HISTORICAL PERFORMANCE:
|
|
30924
|
+
${formatPastPerformance(opts.pastPerformance ?? [])}
|
|
30925
|
+
|
|
30926
|
+
RULES:
|
|
30927
|
+
- Each tweet MUST be \u2264280 characters
|
|
30928
|
+
- Use line breaks for readability within tweets
|
|
30929
|
+
- Number threads (1/, 2/, etc.) only if it helps clarity
|
|
30930
|
+
- Include #buildinpublic in the first or last tweet only
|
|
30931
|
+
- Make each tweet stand alone \u2014 people may see any tweet in isolation
|
|
30932
|
+
|
|
30933
|
+
Respond in this exact JSON format (no markdown, no code blocks):
|
|
30934
|
+
{
|
|
30935
|
+
"tweets": ["tweet 1 text", "tweet 2 text", ...],
|
|
30936
|
+
"hook_type": "stat-opening|contrarian|story|question|list-tease|before-after|bold-claim"
|
|
30937
|
+
}`;
|
|
30938
|
+
const result = await invokeAI({ prompt, model: "haiku" });
|
|
30939
|
+
try {
|
|
30940
|
+
const parsed = JSON.parse(extractJson(result.output));
|
|
30941
|
+
parsed.tweets = parsed.tweets.map(
|
|
30942
|
+
(t) => t.length > 280 ? t.slice(0, 277).trimEnd() + "..." : t
|
|
30943
|
+
);
|
|
30944
|
+
return parsed;
|
|
30945
|
+
} catch {
|
|
30946
|
+
const lines = result.output.trim().split("\n").filter(Boolean);
|
|
30947
|
+
const tweets = lines.slice(0, length).map(
|
|
30948
|
+
(l2) => l2.length > 280 ? l2.slice(0, 277).trimEnd() + "..." : l2
|
|
30949
|
+
);
|
|
30950
|
+
return { tweets, hook_type: "unknown" };
|
|
30951
|
+
}
|
|
30952
|
+
}
|
|
30953
|
+
async function scoreDraft(text, pastPerformance) {
|
|
30954
|
+
const prompt = `You are an X/Twitter algorithm expert. Score this tweet draft for viral potential.
|
|
30955
|
+
|
|
30956
|
+
${ALGORITHM_KNOWLEDGE}
|
|
30957
|
+
|
|
30958
|
+
DRAFT TO SCORE:
|
|
30959
|
+
"${text}"
|
|
30960
|
+
|
|
30961
|
+
HISTORICAL PERFORMANCE:
|
|
30962
|
+
${formatPastPerformance(pastPerformance ?? [])}
|
|
30963
|
+
|
|
30964
|
+
SCORING CRITERIA (0-100):
|
|
30965
|
+
- Hook strength (0-25): Does it stop the scroll? Is the first line compelling?
|
|
30966
|
+
- Bookmark-worthiness (0-25): Would someone save this? Lists, frameworks, data = high.
|
|
30967
|
+
- Reply-ability (0-25): Does it invite discussion? Questions, hot takes, debates = high.
|
|
30968
|
+
- Clarity (0-25): Is the value immediately clear? No jargon? Scannable format?
|
|
30969
|
+
|
|
30970
|
+
Respond in this exact JSON format (no markdown, no code blocks):
|
|
30971
|
+
{
|
|
30972
|
+
"score": 75,
|
|
30973
|
+
"feedback": ["specific feedback point 1", "specific feedback point 2", "specific feedback point 3"],
|
|
30974
|
+
"optimized_version": "the improved tweet text (max 280 chars)"
|
|
30975
|
+
}`;
|
|
30976
|
+
const result = await invokeAI({ prompt, model: "haiku" });
|
|
30977
|
+
try {
|
|
30978
|
+
const parsed = JSON.parse(extractJson(result.output));
|
|
30979
|
+
if (parsed.optimized_version?.length > 280) {
|
|
30980
|
+
parsed.optimized_version = parsed.optimized_version.slice(0, 277).trimEnd() + "...";
|
|
30981
|
+
}
|
|
30982
|
+
return {
|
|
30983
|
+
score: Math.min(100, Math.max(0, Number(parsed.score) || 50)),
|
|
30984
|
+
feedback: Array.isArray(parsed.feedback) ? parsed.feedback : [],
|
|
30985
|
+
optimized_version: parsed.optimized_version || text
|
|
30986
|
+
};
|
|
30987
|
+
} catch {
|
|
30988
|
+
return {
|
|
30989
|
+
score: 50,
|
|
30990
|
+
feedback: ["Could not analyze draft \u2014 AI response parsing failed."],
|
|
30991
|
+
optimized_version: text
|
|
30992
|
+
};
|
|
30993
|
+
}
|
|
30994
|
+
}
|
|
30995
|
+
function extractJson(text) {
|
|
30996
|
+
const codeBlockMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
30997
|
+
if (codeBlockMatch) return codeBlockMatch[1].trim();
|
|
30998
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
30999
|
+
if (jsonMatch) return jsonMatch[0];
|
|
31000
|
+
return text.trim();
|
|
31001
|
+
}
|
|
31002
|
+
|
|
29634
31003
|
// scripts/skills/social-media.ts
|
|
29635
|
-
var
|
|
31004
|
+
var EMPTY_STATE3 = {
|
|
29636
31005
|
last_updated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
29637
31006
|
platform: "x",
|
|
29638
31007
|
username: null,
|
|
@@ -29645,23 +31014,23 @@ var EMPTY_STATE2 = {
|
|
|
29645
31014
|
last_post_date: null
|
|
29646
31015
|
}
|
|
29647
31016
|
};
|
|
29648
|
-
function
|
|
29649
|
-
if (!
|
|
29650
|
-
return { ...
|
|
31017
|
+
function loadState3() {
|
|
31018
|
+
if (!fs18.existsSync(SOCIAL_FILE)) {
|
|
31019
|
+
return { ...EMPTY_STATE3, metrics: { ...EMPTY_STATE3.metrics } };
|
|
29651
31020
|
}
|
|
29652
31021
|
try {
|
|
29653
|
-
return JSON.parse(
|
|
31022
|
+
return JSON.parse(fs18.readFileSync(SOCIAL_FILE, "utf-8"));
|
|
29654
31023
|
} catch {
|
|
29655
|
-
return { ...
|
|
31024
|
+
return { ...EMPTY_STATE3, metrics: { ...EMPTY_STATE3.metrics } };
|
|
29656
31025
|
}
|
|
29657
31026
|
}
|
|
29658
|
-
function
|
|
31027
|
+
function saveState3(state) {
|
|
29659
31028
|
state.last_updated = (/* @__PURE__ */ new Date()).toISOString();
|
|
29660
|
-
const dir =
|
|
29661
|
-
if (!
|
|
29662
|
-
|
|
31029
|
+
const dir = path12.dirname(SOCIAL_FILE);
|
|
31030
|
+
if (!fs18.existsSync(dir)) {
|
|
31031
|
+
fs18.mkdirSync(dir, { recursive: true });
|
|
29663
31032
|
}
|
|
29664
|
-
|
|
31033
|
+
fs18.writeFileSync(SOCIAL_FILE, JSON.stringify(state, null, 2) + "\n");
|
|
29665
31034
|
}
|
|
29666
31035
|
function computeStreak(state) {
|
|
29667
31036
|
const published = state.drafts.filter((d2) => d2.status === "published" && d2.published_at).map((d2) => d2.published_at.split("T")[0]).sort().reverse();
|
|
@@ -29714,7 +31083,11 @@ var SOCIAL_PREFIXES = [
|
|
|
29714
31083
|
"social-draft-update",
|
|
29715
31084
|
"social-draft-milestone",
|
|
29716
31085
|
"social-draft-digest",
|
|
31086
|
+
"social-draft-viral-",
|
|
31087
|
+
"social-draft-thread-",
|
|
29717
31088
|
"social-publish-",
|
|
31089
|
+
"social-track-metrics",
|
|
31090
|
+
"social-score-",
|
|
29718
31091
|
"social-check-status"
|
|
29719
31092
|
];
|
|
29720
31093
|
function isSocialTask(taskId) {
|
|
@@ -29740,12 +31113,27 @@ async function executeSocialTask(taskId, description, _businessContext) {
|
|
|
29740
31113
|
if (taskId === "social-draft-digest") {
|
|
29741
31114
|
return executeDraftDigest();
|
|
29742
31115
|
}
|
|
31116
|
+
if (taskId.startsWith("social-draft-viral-")) {
|
|
31117
|
+
const topic = taskId.replace("social-draft-viral-", "");
|
|
31118
|
+
return await executeDraftViral(topic, description);
|
|
31119
|
+
}
|
|
31120
|
+
if (taskId.startsWith("social-draft-thread-")) {
|
|
31121
|
+
const topic = taskId.replace("social-draft-thread-", "");
|
|
31122
|
+
return await executeDraftThread(topic, description);
|
|
31123
|
+
}
|
|
29743
31124
|
if (taskId.startsWith("social-publish-")) {
|
|
29744
31125
|
const draftId = taskId.replace("social-publish-", "");
|
|
29745
31126
|
return await executePublish(draftId);
|
|
29746
31127
|
}
|
|
31128
|
+
if (taskId === "social-track-metrics") {
|
|
31129
|
+
return await executeTrackMetrics();
|
|
31130
|
+
}
|
|
31131
|
+
if (taskId.startsWith("social-score-")) {
|
|
31132
|
+
const draftId = taskId.replace("social-score-", "");
|
|
31133
|
+
return await executeScoreDraft(draftId);
|
|
31134
|
+
}
|
|
29747
31135
|
if (taskId === "social-check-status") {
|
|
29748
|
-
return await
|
|
31136
|
+
return await executeCheckStatus3();
|
|
29749
31137
|
}
|
|
29750
31138
|
return { success: false, output: `Unknown social task: ${taskId}` };
|
|
29751
31139
|
} catch (error) {
|
|
@@ -29754,9 +31142,25 @@ async function executeSocialTask(taskId, description, _businessContext) {
|
|
|
29754
31142
|
}
|
|
29755
31143
|
}
|
|
29756
31144
|
function readSocialFreshness() {
|
|
29757
|
-
const state =
|
|
31145
|
+
const state = loadState3();
|
|
29758
31146
|
const pendingDrafts = state.drafts.filter((d2) => d2.status === "draft").length;
|
|
29759
31147
|
const streak = computeStreak(state);
|
|
31148
|
+
const allEngagement = [];
|
|
31149
|
+
for (const draft of state.drafts) {
|
|
31150
|
+
if (draft.engagement_history && draft.engagement_history.length > 0) {
|
|
31151
|
+
allEngagement.push(draft.engagement_history[draft.engagement_history.length - 1]);
|
|
31152
|
+
}
|
|
31153
|
+
}
|
|
31154
|
+
let avgEngagementRate = null;
|
|
31155
|
+
let bestTweetId = null;
|
|
31156
|
+
let bestEngagementRate = null;
|
|
31157
|
+
if (allEngagement.length > 0) {
|
|
31158
|
+
avgEngagementRate = allEngagement.reduce((s, e) => s + e.engagement_rate, 0) / allEngagement.length;
|
|
31159
|
+
const best = allEngagement.reduce((a, b) => a.engagement_rate > b.engagement_rate ? a : b);
|
|
31160
|
+
bestTweetId = best.tweet_id;
|
|
31161
|
+
bestEngagementRate = best.engagement_rate;
|
|
31162
|
+
}
|
|
31163
|
+
const threadCount = state.drafts.filter((d2) => d2.is_thread && d2.status === "published").length;
|
|
29760
31164
|
return {
|
|
29761
31165
|
configured: state.credentials_valid,
|
|
29762
31166
|
credentials_valid: state.credentials_valid,
|
|
@@ -29764,7 +31168,11 @@ function readSocialFreshness() {
|
|
|
29764
31168
|
draft_count: pendingDrafts,
|
|
29765
31169
|
post_streak_days: streak,
|
|
29766
31170
|
total_posts: state.metrics.total_posts,
|
|
29767
|
-
last_post_date: state.metrics.last_post_date
|
|
31171
|
+
last_post_date: state.metrics.last_post_date,
|
|
31172
|
+
avg_engagement_rate: avgEngagementRate,
|
|
31173
|
+
best_tweet_id: bestTweetId,
|
|
31174
|
+
best_engagement_rate: bestEngagementRate,
|
|
31175
|
+
thread_count: threadCount
|
|
29768
31176
|
};
|
|
29769
31177
|
}
|
|
29770
31178
|
async function executeSetupX() {
|
|
@@ -29786,13 +31194,13 @@ async function executeSetupX() {
|
|
|
29786
31194
|
}
|
|
29787
31195
|
const client = createXClient(credentials);
|
|
29788
31196
|
const userInfo = await validateCredentials(client);
|
|
29789
|
-
const state =
|
|
31197
|
+
const state = loadState3();
|
|
29790
31198
|
state.credentials_valid = userInfo.valid;
|
|
29791
31199
|
state.username = userInfo.username;
|
|
29792
31200
|
if (userInfo.followersCount != null) {
|
|
29793
31201
|
state.metrics.followers_count = userInfo.followersCount;
|
|
29794
31202
|
}
|
|
29795
|
-
|
|
31203
|
+
saveState3(state);
|
|
29796
31204
|
if (!userInfo.valid) {
|
|
29797
31205
|
return {
|
|
29798
31206
|
success: false,
|
|
@@ -29805,13 +31213,13 @@ async function executeSetupX() {
|
|
|
29805
31213
|
};
|
|
29806
31214
|
}
|
|
29807
31215
|
function executeDraftShip() {
|
|
29808
|
-
const state =
|
|
31216
|
+
const state = loadState3();
|
|
29809
31217
|
const text = generateShipTweet();
|
|
29810
31218
|
if (!text) {
|
|
29811
31219
|
return { success: false, output: "No shipped ideas found to generate a tweet from." };
|
|
29812
31220
|
}
|
|
29813
|
-
const { ideas } = JSON.parse(
|
|
29814
|
-
|
|
31221
|
+
const { ideas } = JSON.parse(fs18.readFileSync(
|
|
31222
|
+
path12.join(path12.dirname(SOCIAL_FILE), "ideas.json"),
|
|
29815
31223
|
"utf-8"
|
|
29816
31224
|
));
|
|
29817
31225
|
const latestShipped = ideas.filter((i) => i.stage === "shipped").sort((a, b) => b.updated_at.localeCompare(a.updated_at))[0];
|
|
@@ -29819,17 +31227,17 @@ function executeDraftShip() {
|
|
|
29819
31227
|
return { success: false, output: `Draft already exists for shipped idea ${latestShipped.id}` };
|
|
29820
31228
|
}
|
|
29821
31229
|
const draft = createDraft(state, text, "ship", latestShipped?.id || null);
|
|
29822
|
-
|
|
31230
|
+
saveState3(state);
|
|
29823
31231
|
return { success: true, output: `Created ship draft: "${draft.text}" (${draft.id})` };
|
|
29824
31232
|
}
|
|
29825
31233
|
function executeDraftShipVisual() {
|
|
29826
|
-
const state =
|
|
31234
|
+
const state = loadState3();
|
|
29827
31235
|
const text = generateShipTweet();
|
|
29828
31236
|
if (!text) {
|
|
29829
31237
|
return { success: false, output: "No shipped ideas found to generate a tweet from." };
|
|
29830
31238
|
}
|
|
29831
|
-
const { ideas } = JSON.parse(
|
|
29832
|
-
|
|
31239
|
+
const { ideas } = JSON.parse(fs18.readFileSync(
|
|
31240
|
+
path12.join(path12.dirname(SOCIAL_FILE), "ideas.json"),
|
|
29833
31241
|
"utf-8"
|
|
29834
31242
|
));
|
|
29835
31243
|
const latestShipped = ideas.filter((i) => i.stage === "shipped").sort((a, b) => b.updated_at.localeCompare(a.updated_at))[0];
|
|
@@ -29838,12 +31246,12 @@ function executeDraftShipVisual() {
|
|
|
29838
31246
|
}
|
|
29839
31247
|
const mediaPaths = [];
|
|
29840
31248
|
if (latestShipped) {
|
|
29841
|
-
const cardPath =
|
|
29842
|
-
if (
|
|
31249
|
+
const cardPath = path12.join(DATA_DIR, "reports", "visuals", `${latestShipped.id}-card.png`);
|
|
31250
|
+
if (fs18.existsSync(cardPath)) {
|
|
29843
31251
|
mediaPaths.push(cardPath);
|
|
29844
31252
|
}
|
|
29845
|
-
const videoPath =
|
|
29846
|
-
if (
|
|
31253
|
+
const videoPath = path12.join(DATA_DIR, "videos", `${latestShipped.id}-ship.mp4`);
|
|
31254
|
+
if (fs18.existsSync(videoPath)) {
|
|
29847
31255
|
mediaPaths.length = 0;
|
|
29848
31256
|
mediaPaths.push(videoPath);
|
|
29849
31257
|
}
|
|
@@ -29855,44 +31263,44 @@ function executeDraftShipVisual() {
|
|
|
29855
31263
|
};
|
|
29856
31264
|
}
|
|
29857
31265
|
const draft = createDraft(state, text, "ship", latestShipped?.id || null, mediaPaths);
|
|
29858
|
-
|
|
31266
|
+
saveState3(state);
|
|
29859
31267
|
return {
|
|
29860
31268
|
success: true,
|
|
29861
31269
|
output: `Created ship draft with media: "${draft.text}" (${draft.id}) \u2014 ${mediaPaths.length} file(s) attached`
|
|
29862
31270
|
};
|
|
29863
31271
|
}
|
|
29864
31272
|
function executeDraftUpdate() {
|
|
29865
|
-
const state =
|
|
31273
|
+
const state = loadState3();
|
|
29866
31274
|
const text = generateProductUpdateTweet();
|
|
29867
31275
|
if (!text) {
|
|
29868
31276
|
return { success: false, output: "No positioning data found to generate update tweet." };
|
|
29869
31277
|
}
|
|
29870
31278
|
const draft = createDraft(state, text, "update", null);
|
|
29871
|
-
|
|
31279
|
+
saveState3(state);
|
|
29872
31280
|
return { success: true, output: `Created update draft: "${draft.text}" (${draft.id})` };
|
|
29873
31281
|
}
|
|
29874
31282
|
function executeDraftMilestone() {
|
|
29875
|
-
const state =
|
|
31283
|
+
const state = loadState3();
|
|
29876
31284
|
const text = generateMilestoneTweet();
|
|
29877
31285
|
if (!text) {
|
|
29878
31286
|
return { success: false, output: "No KPI milestones found to celebrate." };
|
|
29879
31287
|
}
|
|
29880
31288
|
const draft = createDraft(state, text, "milestone", null);
|
|
29881
|
-
|
|
31289
|
+
saveState3(state);
|
|
29882
31290
|
return { success: true, output: `Created milestone draft: "${draft.text}" (${draft.id})` };
|
|
29883
31291
|
}
|
|
29884
31292
|
function executeDraftDigest() {
|
|
29885
|
-
const state =
|
|
31293
|
+
const state = loadState3();
|
|
29886
31294
|
const text = generateDigestTweet();
|
|
29887
31295
|
if (!text) {
|
|
29888
31296
|
return { success: false, output: "No recent activity to summarize in a digest." };
|
|
29889
31297
|
}
|
|
29890
31298
|
const draft = createDraft(state, text, "digest", null);
|
|
29891
|
-
|
|
31299
|
+
saveState3(state);
|
|
29892
31300
|
return { success: true, output: `Created digest draft: "${draft.text}" (${draft.id})` };
|
|
29893
31301
|
}
|
|
29894
31302
|
async function executePublish(draftId) {
|
|
29895
|
-
const state =
|
|
31303
|
+
const state = loadState3();
|
|
29896
31304
|
const draft = state.drafts.find((d2) => d2.id === draftId);
|
|
29897
31305
|
if (!draft) {
|
|
29898
31306
|
return { success: false, output: `Draft not found: ${draftId}` };
|
|
@@ -29905,9 +31313,26 @@ async function executePublish(draftId) {
|
|
|
29905
31313
|
return { success: false, output: "X API credentials not configured. Run social-setup-x first." };
|
|
29906
31314
|
}
|
|
29907
31315
|
const client = createXClient(credentials);
|
|
31316
|
+
if (draft.is_thread && draft.thread_tweets && draft.thread_tweets.length > 0) {
|
|
31317
|
+
const results = await postThread(client, draft.thread_tweets);
|
|
31318
|
+
draft.status = "published";
|
|
31319
|
+
draft.published_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
31320
|
+
draft.tweet_id = results[0].id;
|
|
31321
|
+
draft.tweet_url = results[0].url;
|
|
31322
|
+
draft.thread_tweet_ids = results.map((r) => r.id);
|
|
31323
|
+
state.metrics.total_posts++;
|
|
31324
|
+
state.metrics.last_post_date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
31325
|
+
state.metrics.post_streak_days = computeStreak(state);
|
|
31326
|
+
saveState3(state);
|
|
31327
|
+
syncKPIs(state);
|
|
31328
|
+
return {
|
|
31329
|
+
success: true,
|
|
31330
|
+
output: `Published thread (${results.length} tweets): ${results[0].url}`
|
|
31331
|
+
};
|
|
31332
|
+
}
|
|
29908
31333
|
let result;
|
|
29909
31334
|
if (draft.media_paths && draft.media_paths.length > 0) {
|
|
29910
|
-
const validPaths = draft.media_paths.filter((p) =>
|
|
31335
|
+
const validPaths = draft.media_paths.filter((p) => fs18.existsSync(p));
|
|
29911
31336
|
if (validPaths.length > 0) {
|
|
29912
31337
|
const mediaIds = [];
|
|
29913
31338
|
for (const mediaPath of validPaths) {
|
|
@@ -29928,15 +31353,166 @@ async function executePublish(draftId) {
|
|
|
29928
31353
|
state.metrics.total_posts++;
|
|
29929
31354
|
state.metrics.last_post_date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
29930
31355
|
state.metrics.post_streak_days = computeStreak(state);
|
|
29931
|
-
|
|
31356
|
+
saveState3(state);
|
|
29932
31357
|
syncKPIs(state);
|
|
29933
31358
|
return {
|
|
29934
31359
|
success: true,
|
|
29935
31360
|
output: `Published tweet: ${result.url}`
|
|
29936
31361
|
};
|
|
29937
31362
|
}
|
|
29938
|
-
async function
|
|
29939
|
-
const state =
|
|
31363
|
+
async function executeDraftViral(topic, description) {
|
|
31364
|
+
const state = loadState3();
|
|
31365
|
+
const pastPerformance = collectEngagementHistory(state);
|
|
31366
|
+
const result = await generateViralTweet({
|
|
31367
|
+
topic,
|
|
31368
|
+
context: description || `A ${topic} tweet about our product`,
|
|
31369
|
+
pastPerformance
|
|
31370
|
+
});
|
|
31371
|
+
const source = topic === "insight" || topic === "question" ? "insight" : topic;
|
|
31372
|
+
const draft = createDraft(state, result.text, source, null);
|
|
31373
|
+
saveState3(state);
|
|
31374
|
+
return {
|
|
31375
|
+
success: true,
|
|
31376
|
+
output: [
|
|
31377
|
+
`Created AI-generated ${topic} draft: "${draft.text}" (${draft.id})`,
|
|
31378
|
+
`Hook type: ${result.hook_type}`,
|
|
31379
|
+
`Estimated engagement: ${result.estimated_engagement}`
|
|
31380
|
+
].join("\n")
|
|
31381
|
+
};
|
|
31382
|
+
}
|
|
31383
|
+
async function executeDraftThread(topic, description) {
|
|
31384
|
+
const state = loadState3();
|
|
31385
|
+
const pastPerformance = collectEngagementHistory(state);
|
|
31386
|
+
const result = await generateViralThread({
|
|
31387
|
+
topic,
|
|
31388
|
+
context: description || `A thread about ${topic}`,
|
|
31389
|
+
threadLength: 5,
|
|
31390
|
+
pastPerformance
|
|
31391
|
+
});
|
|
31392
|
+
if (!result.tweets || result.tweets.length === 0) {
|
|
31393
|
+
return { success: false, output: "AI failed to generate thread content." };
|
|
31394
|
+
}
|
|
31395
|
+
const draft = {
|
|
31396
|
+
id: `draft-${v4_default().slice(0, 8)}`,
|
|
31397
|
+
text: result.tweets[0],
|
|
31398
|
+
source: "thread",
|
|
31399
|
+
source_ref: null,
|
|
31400
|
+
status: "draft",
|
|
31401
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
31402
|
+
published_at: null,
|
|
31403
|
+
tweet_id: null,
|
|
31404
|
+
tweet_url: null,
|
|
31405
|
+
is_thread: true,
|
|
31406
|
+
thread_tweets: result.tweets
|
|
31407
|
+
};
|
|
31408
|
+
state.drafts.push(draft);
|
|
31409
|
+
saveState3(state);
|
|
31410
|
+
return {
|
|
31411
|
+
success: true,
|
|
31412
|
+
output: [
|
|
31413
|
+
`Created thread draft (${result.tweets.length} tweets): ${draft.id}`,
|
|
31414
|
+
`Hook type: ${result.hook_type}`,
|
|
31415
|
+
`Preview:`,
|
|
31416
|
+
...result.tweets.map((t, i) => ` ${i + 1}. "${t.slice(0, 60)}${t.length > 60 ? "..." : ""}"`)
|
|
31417
|
+
].join("\n")
|
|
31418
|
+
};
|
|
31419
|
+
}
|
|
31420
|
+
async function executeTrackMetrics() {
|
|
31421
|
+
const state = loadState3();
|
|
31422
|
+
const credentials = loadCredentials();
|
|
31423
|
+
if (!credentials) {
|
|
31424
|
+
return { success: false, output: "X API credentials not configured. Run social-setup-x first." };
|
|
31425
|
+
}
|
|
31426
|
+
const client = createXClient(credentials);
|
|
31427
|
+
const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1e3).toISOString();
|
|
31428
|
+
const recentPublished = state.drafts.filter(
|
|
31429
|
+
(d2) => d2.status === "published" && d2.published_at && d2.published_at > sevenDaysAgo && d2.tweet_id
|
|
31430
|
+
);
|
|
31431
|
+
if (recentPublished.length === 0) {
|
|
31432
|
+
return { success: true, output: "No published tweets in the last 7 days to track." };
|
|
31433
|
+
}
|
|
31434
|
+
const tweetIds = [];
|
|
31435
|
+
for (const draft of recentPublished) {
|
|
31436
|
+
if (draft.thread_tweet_ids && draft.thread_tweet_ids.length > 0) {
|
|
31437
|
+
tweetIds.push(...draft.thread_tweet_ids);
|
|
31438
|
+
} else if (draft.tweet_id) {
|
|
31439
|
+
tweetIds.push(draft.tweet_id);
|
|
31440
|
+
}
|
|
31441
|
+
}
|
|
31442
|
+
const metrics = await getBatchTweetMetrics(client, tweetIds);
|
|
31443
|
+
if (metrics.length === 0) {
|
|
31444
|
+
return { success: true, output: "Could not fetch metrics (API may require elevated access)." };
|
|
31445
|
+
}
|
|
31446
|
+
for (const draft of recentPublished) {
|
|
31447
|
+
if (!draft.engagement_history) {
|
|
31448
|
+
draft.engagement_history = [];
|
|
31449
|
+
}
|
|
31450
|
+
if (draft.thread_tweet_ids && draft.thread_tweet_ids.length > 0) {
|
|
31451
|
+
const threadMetrics = metrics.filter((m2) => draft.thread_tweet_ids.includes(m2.tweet_id));
|
|
31452
|
+
if (threadMetrics.length > 0) {
|
|
31453
|
+
const aggregated = {
|
|
31454
|
+
tweet_id: draft.tweet_id,
|
|
31455
|
+
impressions: threadMetrics.reduce((s, m2) => s + m2.impressions, 0),
|
|
31456
|
+
likes: threadMetrics.reduce((s, m2) => s + m2.likes, 0),
|
|
31457
|
+
retweets: threadMetrics.reduce((s, m2) => s + m2.retweets, 0),
|
|
31458
|
+
replies: threadMetrics.reduce((s, m2) => s + m2.replies, 0),
|
|
31459
|
+
bookmarks: threadMetrics.reduce((s, m2) => s + m2.bookmarks, 0),
|
|
31460
|
+
profile_clicks: threadMetrics.reduce((s, m2) => s + m2.profile_clicks, 0),
|
|
31461
|
+
engagements: threadMetrics.reduce((s, m2) => s + m2.engagements, 0),
|
|
31462
|
+
engagement_rate: 0,
|
|
31463
|
+
fetched_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
31464
|
+
};
|
|
31465
|
+
aggregated.engagement_rate = aggregated.impressions > 0 ? aggregated.engagements / aggregated.impressions : 0;
|
|
31466
|
+
draft.engagement_history.push(aggregated);
|
|
31467
|
+
}
|
|
31468
|
+
} else {
|
|
31469
|
+
const tweetMetric = metrics.find((m2) => m2.tweet_id === draft.tweet_id);
|
|
31470
|
+
if (tweetMetric) {
|
|
31471
|
+
draft.engagement_history.push(tweetMetric);
|
|
31472
|
+
}
|
|
31473
|
+
}
|
|
31474
|
+
}
|
|
31475
|
+
saveState3(state);
|
|
31476
|
+
const lines = [`Tracked metrics for ${metrics.length} tweet(s):`];
|
|
31477
|
+
for (const m2 of metrics) {
|
|
31478
|
+
lines.push(
|
|
31479
|
+
` ${m2.tweet_id}: ${m2.impressions} impressions, ${m2.likes} likes, ${m2.bookmarks} bookmarks, ${(m2.engagement_rate * 100).toFixed(2)}% rate`
|
|
31480
|
+
);
|
|
31481
|
+
}
|
|
31482
|
+
return { success: true, output: lines.join("\n") };
|
|
31483
|
+
}
|
|
31484
|
+
async function executeScoreDraft(draftId) {
|
|
31485
|
+
const state = loadState3();
|
|
31486
|
+
const draft = state.drafts.find((d2) => d2.id === draftId);
|
|
31487
|
+
if (!draft) {
|
|
31488
|
+
return { success: false, output: `Draft not found: ${draftId}` };
|
|
31489
|
+
}
|
|
31490
|
+
const textToScore = draft.is_thread && draft.thread_tweets ? draft.thread_tweets.join("\n\n---\n\n") : draft.text;
|
|
31491
|
+
const pastPerformance = collectEngagementHistory(state);
|
|
31492
|
+
const result = await scoreDraft(textToScore, pastPerformance);
|
|
31493
|
+
return {
|
|
31494
|
+
success: true,
|
|
31495
|
+
output: [
|
|
31496
|
+
`Draft Score: ${result.score}/100`,
|
|
31497
|
+
"",
|
|
31498
|
+
"Feedback:",
|
|
31499
|
+
...result.feedback.map((f) => ` - ${f}`),
|
|
31500
|
+
"",
|
|
31501
|
+
`Optimized version: "${result.optimized_version}"`
|
|
31502
|
+
].join("\n")
|
|
31503
|
+
};
|
|
31504
|
+
}
|
|
31505
|
+
function collectEngagementHistory(state) {
|
|
31506
|
+
const result = [];
|
|
31507
|
+
for (const draft of state.drafts) {
|
|
31508
|
+
if (draft.engagement_history && draft.engagement_history.length > 0) {
|
|
31509
|
+
result.push(draft.engagement_history[draft.engagement_history.length - 1]);
|
|
31510
|
+
}
|
|
31511
|
+
}
|
|
31512
|
+
return result;
|
|
31513
|
+
}
|
|
31514
|
+
async function executeCheckStatus3() {
|
|
31515
|
+
const state = loadState3();
|
|
29940
31516
|
const freshness = readSocialFreshness();
|
|
29941
31517
|
const lines = ["Social Media Status:"];
|
|
29942
31518
|
if (!freshness.configured) {
|
|
@@ -29954,7 +31530,7 @@ async function executeCheckStatus2() {
|
|
|
29954
31530
|
if (userInfo.valid && userInfo.followersCount != null) {
|
|
29955
31531
|
state.metrics.followers_count = userInfo.followersCount;
|
|
29956
31532
|
state.credentials_valid = true;
|
|
29957
|
-
|
|
31533
|
+
saveState3(state);
|
|
29958
31534
|
lines.push(` Followers: ${userInfo.followersCount}`);
|
|
29959
31535
|
}
|
|
29960
31536
|
} catch {
|
|
@@ -29965,13 +31541,20 @@ async function executeCheckStatus2() {
|
|
|
29965
31541
|
lines.push(` Pending drafts: ${freshness.draft_count}`);
|
|
29966
31542
|
lines.push(` Post streak: ${freshness.post_streak_days} day(s)`);
|
|
29967
31543
|
lines.push(` Total posts: ${freshness.total_posts}`);
|
|
31544
|
+
lines.push(` Threads published: ${freshness.thread_count}`);
|
|
29968
31545
|
lines.push(` Last post: ${freshness.last_post_date || "never"}`);
|
|
31546
|
+
if (freshness.avg_engagement_rate != null) {
|
|
31547
|
+
lines.push(` Avg engagement rate: ${(freshness.avg_engagement_rate * 100).toFixed(2)}%`);
|
|
31548
|
+
}
|
|
31549
|
+
if (freshness.best_tweet_id) {
|
|
31550
|
+
lines.push(` Best tweet: ${freshness.best_tweet_id} (${((freshness.best_engagement_rate ?? 0) * 100).toFixed(2)}% rate)`);
|
|
31551
|
+
}
|
|
29969
31552
|
return { success: true, output: lines.join("\n") };
|
|
29970
31553
|
}
|
|
29971
31554
|
function syncKPIs(state) {
|
|
29972
31555
|
try {
|
|
29973
|
-
if (!
|
|
29974
|
-
const goalsData = JSON.parse(
|
|
31556
|
+
if (!fs18.existsSync(GOALS_FILE)) return;
|
|
31557
|
+
const goalsData = JSON.parse(fs18.readFileSync(GOALS_FILE, "utf-8"));
|
|
29975
31558
|
let changed = false;
|
|
29976
31559
|
for (const goal of goalsData.goals) {
|
|
29977
31560
|
for (const kpi of goal.kpis) {
|
|
@@ -30000,16 +31583,16 @@ function syncKPIs(state) {
|
|
|
30000
31583
|
}
|
|
30001
31584
|
}
|
|
30002
31585
|
if (changed) {
|
|
30003
|
-
|
|
31586
|
+
fs18.writeFileSync(GOALS_FILE, JSON.stringify(goalsData, null, 2) + "\n");
|
|
30004
31587
|
}
|
|
30005
31588
|
} catch {
|
|
30006
31589
|
}
|
|
30007
31590
|
}
|
|
30008
31591
|
|
|
30009
31592
|
// scripts/skills/promo-copy.ts
|
|
30010
|
-
var
|
|
30011
|
-
var
|
|
30012
|
-
var
|
|
31593
|
+
var import_child_process7 = require("child_process");
|
|
31594
|
+
var fs19 = __toESM(require("fs"));
|
|
31595
|
+
var path13 = __toESM(require("path"));
|
|
30013
31596
|
var COPY_PREFIXES = [
|
|
30014
31597
|
"copy-tweet-",
|
|
30015
31598
|
"copy-ad-google-",
|
|
@@ -30049,8 +31632,8 @@ function parseTaskToCommand3(taskId) {
|
|
|
30049
31632
|
}
|
|
30050
31633
|
function hasPositioningData() {
|
|
30051
31634
|
try {
|
|
30052
|
-
if (!
|
|
30053
|
-
const data = JSON.parse(
|
|
31635
|
+
if (!fs19.existsSync(POSITIONING_FILE)) return false;
|
|
31636
|
+
const data = JSON.parse(fs19.readFileSync(POSITIONING_FILE, "utf-8"));
|
|
30054
31637
|
return !!data?.current?.tagline;
|
|
30055
31638
|
} catch {
|
|
30056
31639
|
return false;
|
|
@@ -30071,16 +31654,16 @@ async function executePromoCopyTask(taskId, description, _businessContext) {
|
|
|
30071
31654
|
};
|
|
30072
31655
|
}
|
|
30073
31656
|
const { command, args: args2 } = parsed;
|
|
30074
|
-
const commandFile =
|
|
30075
|
-
const templateFile =
|
|
30076
|
-
const templatePath =
|
|
30077
|
-
if (!
|
|
31657
|
+
const commandFile = path13.join(PROJECT_DIR, ".claude", "commands", `${command}.md`);
|
|
31658
|
+
const templateFile = path13.join(PROJECT_DIR, "templates", "commands", `${command}.md`);
|
|
31659
|
+
const templatePath = fs19.existsSync(commandFile) ? commandFile : templateFile;
|
|
31660
|
+
if (!fs19.existsSync(templatePath)) {
|
|
30078
31661
|
return {
|
|
30079
31662
|
success: false,
|
|
30080
31663
|
output: `Slash command template not found: ${command}.md`
|
|
30081
31664
|
};
|
|
30082
31665
|
}
|
|
30083
|
-
const template =
|
|
31666
|
+
const template = fs19.readFileSync(templatePath, "utf-8");
|
|
30084
31667
|
const prompt = `${template}
|
|
30085
31668
|
|
|
30086
31669
|
---
|
|
@@ -30090,7 +31673,7 @@ $ARGUMENTS: ${args2}
|
|
|
30090
31673
|
Execute this command now.`;
|
|
30091
31674
|
console.log(`[PromoCopy] Executing: /${command} ${args2} (task: ${taskId})`);
|
|
30092
31675
|
try {
|
|
30093
|
-
const result = (0,
|
|
31676
|
+
const result = (0, import_child_process7.spawnSync)("claude", [
|
|
30094
31677
|
"--print",
|
|
30095
31678
|
"--dangerously-skip-permissions",
|
|
30096
31679
|
"--allowedTools",
|
|
@@ -30129,10 +31712,10 @@ function readPromoCopyFreshness() {
|
|
|
30129
31712
|
copy_count: 0,
|
|
30130
31713
|
last_copy_date: null
|
|
30131
31714
|
};
|
|
30132
|
-
const copyDir =
|
|
30133
|
-
if (
|
|
31715
|
+
const copyDir = path13.join(DATA_DIR, "copy");
|
|
31716
|
+
if (fs19.existsSync(copyDir)) {
|
|
30134
31717
|
try {
|
|
30135
|
-
const files =
|
|
31718
|
+
const files = fs19.readdirSync(copyDir).filter((f) => f.endsWith(".md"));
|
|
30136
31719
|
result.copy_count = files.length;
|
|
30137
31720
|
if (files.length > 0) {
|
|
30138
31721
|
const sorted = files.sort().reverse();
|
|
@@ -30146,19 +31729,19 @@ function readPromoCopyFreshness() {
|
|
|
30146
31729
|
}
|
|
30147
31730
|
|
|
30148
31731
|
// scripts/skills/promo-video.ts
|
|
30149
|
-
var
|
|
30150
|
-
var
|
|
31732
|
+
var fs21 = __toESM(require("fs"));
|
|
31733
|
+
var path15 = __toESM(require("path"));
|
|
30151
31734
|
|
|
30152
31735
|
// scripts/lib/video/render-bridge.ts
|
|
30153
|
-
var
|
|
30154
|
-
var
|
|
30155
|
-
var
|
|
31736
|
+
var import_child_process8 = require("child_process");
|
|
31737
|
+
var fs20 = __toESM(require("fs"));
|
|
31738
|
+
var path14 = __toESM(require("path"));
|
|
30156
31739
|
function isContentFlowAvailable(repoPath) {
|
|
30157
|
-
if (!repoPath || !
|
|
30158
|
-
const pkgPath =
|
|
30159
|
-
if (!
|
|
31740
|
+
if (!repoPath || !fs20.existsSync(repoPath)) return false;
|
|
31741
|
+
const pkgPath = path14.join(repoPath, "package.json");
|
|
31742
|
+
if (!fs20.existsSync(pkgPath)) return false;
|
|
30160
31743
|
try {
|
|
30161
|
-
const pkg = JSON.parse(
|
|
31744
|
+
const pkg = JSON.parse(fs20.readFileSync(pkgPath, "utf-8"));
|
|
30162
31745
|
return !!(pkg.scripts?.["render-video"] || pkg.scripts?.["render-carousels"]);
|
|
30163
31746
|
} catch {
|
|
30164
31747
|
return false;
|
|
@@ -30195,7 +31778,7 @@ function generateCarouselJson(idea) {
|
|
|
30195
31778
|
{
|
|
30196
31779
|
type: "key_point",
|
|
30197
31780
|
title: "Why it matters",
|
|
30198
|
-
content: `${idea.title} brings us closer to our goal of building the best autonomous
|
|
31781
|
+
content: `${idea.title} brings us closer to our goal of building the best autonomous product manager.`
|
|
30199
31782
|
},
|
|
30200
31783
|
{
|
|
30201
31784
|
type: "quote",
|
|
@@ -30230,39 +31813,39 @@ function renderVideo(repoPath, carouselJsonPath, format = "reels", outputDir) {
|
|
|
30230
31813
|
output: `ContentFlow repo not available at: ${repoPath}`
|
|
30231
31814
|
};
|
|
30232
31815
|
}
|
|
30233
|
-
const slug =
|
|
31816
|
+
const slug = path14.basename(carouselJsonPath, ".json");
|
|
30234
31817
|
try {
|
|
30235
31818
|
const command = `npm run render-video -- --input "${carouselJsonPath}" --format ${format}`;
|
|
30236
31819
|
console.log(`[RenderBridge] Running: ${command} in ${repoPath}`);
|
|
30237
|
-
const output = (0,
|
|
31820
|
+
const output = (0, import_child_process8.execSync)(command, {
|
|
30238
31821
|
cwd: repoPath,
|
|
30239
31822
|
encoding: "utf-8",
|
|
30240
31823
|
timeout: 3e5,
|
|
30241
31824
|
// 5 minute timeout for rendering
|
|
30242
31825
|
stdio: ["pipe", "pipe", "pipe"]
|
|
30243
31826
|
});
|
|
30244
|
-
const contentVideosDir =
|
|
31827
|
+
const contentVideosDir = path14.join(repoPath, "content", "videos");
|
|
30245
31828
|
const possibleOutputs = [
|
|
30246
|
-
|
|
30247
|
-
|
|
31829
|
+
path14.join(contentVideosDir, `${slug}.mp4`),
|
|
31830
|
+
path14.join(contentVideosDir, `${slug}-${format}.mp4`)
|
|
30248
31831
|
];
|
|
30249
31832
|
let videoPath = null;
|
|
30250
31833
|
for (const candidate of possibleOutputs) {
|
|
30251
|
-
if (
|
|
31834
|
+
if (fs20.existsSync(candidate)) {
|
|
30252
31835
|
videoPath = candidate;
|
|
30253
31836
|
break;
|
|
30254
31837
|
}
|
|
30255
31838
|
}
|
|
30256
31839
|
if (videoPath && outputDir) {
|
|
30257
|
-
|
|
30258
|
-
const destPath =
|
|
30259
|
-
|
|
31840
|
+
fs20.mkdirSync(outputDir, { recursive: true });
|
|
31841
|
+
const destPath = path14.join(outputDir, path14.basename(videoPath));
|
|
31842
|
+
fs20.copyFileSync(videoPath, destPath);
|
|
30260
31843
|
videoPath = destPath;
|
|
30261
31844
|
}
|
|
30262
31845
|
return {
|
|
30263
31846
|
success: !!videoPath,
|
|
30264
31847
|
videoPath,
|
|
30265
|
-
output: videoPath ? `Video rendered: ${videoPath} (${Math.round(
|
|
31848
|
+
output: videoPath ? `Video rendered: ${videoPath} (${Math.round(fs20.statSync(videoPath).size / 1024)}KB)` : `Render completed but output not found. CLI output: ${output.slice(-500)}`
|
|
30266
31849
|
};
|
|
30267
31850
|
} catch (error) {
|
|
30268
31851
|
const errMsg = error instanceof Error ? error.message : String(error);
|
|
@@ -30275,8 +31858,8 @@ function renderVideo(repoPath, carouselJsonPath, format = "reels", outputDir) {
|
|
|
30275
31858
|
}
|
|
30276
31859
|
|
|
30277
31860
|
// scripts/skills/promo-video.ts
|
|
30278
|
-
var VIDEOS_DIR =
|
|
30279
|
-
var CAROUSEL_STAGING_DIR =
|
|
31861
|
+
var VIDEOS_DIR = path15.join(DATA_DIR, "videos");
|
|
31862
|
+
var CAROUSEL_STAGING_DIR = path15.join(DATA_DIR, "videos", "staging");
|
|
30280
31863
|
var VIDEO_PREFIXES = [
|
|
30281
31864
|
"video-render-ship-",
|
|
30282
31865
|
"video-render-ad-",
|
|
@@ -30296,7 +31879,7 @@ async function executePromoVideoTask(taskId, description, businessContext) {
|
|
|
30296
31879
|
return executeRenderAd(topic, businessContext);
|
|
30297
31880
|
}
|
|
30298
31881
|
if (taskId === "video-check-status") {
|
|
30299
|
-
return
|
|
31882
|
+
return executeCheckStatus4(businessContext);
|
|
30300
31883
|
}
|
|
30301
31884
|
return { success: false, output: `Unknown video task: ${taskId}` };
|
|
30302
31885
|
} catch (error) {
|
|
@@ -30317,7 +31900,7 @@ function executeRenderShip(ideaId, businessContext) {
|
|
|
30317
31900
|
}
|
|
30318
31901
|
let idea;
|
|
30319
31902
|
try {
|
|
30320
|
-
const ideasData = JSON.parse(
|
|
31903
|
+
const ideasData = JSON.parse(fs21.readFileSync(IDEAS_FILE, "utf-8"));
|
|
30321
31904
|
idea = ideasData.ideas.find((i) => i.id === ideaId);
|
|
30322
31905
|
} catch {
|
|
30323
31906
|
return { success: false, output: `Failed to load ideas from ${IDEAS_FILE}` };
|
|
@@ -30325,19 +31908,19 @@ function executeRenderShip(ideaId, businessContext) {
|
|
|
30325
31908
|
if (!idea) {
|
|
30326
31909
|
return { success: false, output: `Idea not found: ${ideaId}` };
|
|
30327
31910
|
}
|
|
30328
|
-
const outputPath =
|
|
30329
|
-
if (
|
|
31911
|
+
const outputPath = path15.join(VIDEOS_DIR, `${ideaId}-ship.mp4`);
|
|
31912
|
+
if (fs21.existsSync(outputPath)) {
|
|
30330
31913
|
return { success: true, output: `Ship video already exists: ${outputPath}` };
|
|
30331
31914
|
}
|
|
30332
31915
|
const carouselData = generateCarouselJson(idea);
|
|
30333
|
-
|
|
30334
|
-
const carouselPath =
|
|
30335
|
-
|
|
31916
|
+
fs21.mkdirSync(CAROUSEL_STAGING_DIR, { recursive: true });
|
|
31917
|
+
const carouselPath = path15.join(CAROUSEL_STAGING_DIR, `${ideaId}-ship.json`);
|
|
31918
|
+
fs21.writeFileSync(carouselPath, JSON.stringify(carouselData, null, 2));
|
|
30336
31919
|
const result = renderVideo(repoPath, carouselPath, "reels", VIDEOS_DIR);
|
|
30337
31920
|
if (result.success && result.videoPath) {
|
|
30338
|
-
const finalPath =
|
|
31921
|
+
const finalPath = path15.join(VIDEOS_DIR, `${ideaId}-ship.mp4`);
|
|
30339
31922
|
if (result.videoPath !== finalPath) {
|
|
30340
|
-
|
|
31923
|
+
fs21.renameSync(result.videoPath, finalPath);
|
|
30341
31924
|
}
|
|
30342
31925
|
return {
|
|
30343
31926
|
success: true,
|
|
@@ -30365,7 +31948,7 @@ function executeRenderAd(topic, businessContext) {
|
|
|
30365
31948
|
title: topic,
|
|
30366
31949
|
variation: "v1",
|
|
30367
31950
|
slides: [
|
|
30368
|
-
{ type: "cover", title: topic, subtitle: "Your AI
|
|
31951
|
+
{ type: "cover", title: topic, subtitle: "Your AI Product Manager", background_style: "gradient" },
|
|
30369
31952
|
{ type: "key_point", title: "The Problem", content: `Tired of guessing what to build next? ${topic} solves this.` },
|
|
30370
31953
|
{ type: "key_point", title: "The Solution", content: "An autonomous AI that analyzes, prioritizes, and ships for you." },
|
|
30371
31954
|
{ type: "statistic", title: "Results", value: "10x", label: "faster decisions" },
|
|
@@ -30373,10 +31956,10 @@ function executeRenderAd(topic, businessContext) {
|
|
|
30373
31956
|
],
|
|
30374
31957
|
theme: { background_style: "gradient", primary_color: "#6366f1", text_color: "#ffffff" }
|
|
30375
31958
|
};
|
|
30376
|
-
|
|
30377
|
-
const carouselPath =
|
|
30378
|
-
|
|
30379
|
-
|
|
31959
|
+
fs21.mkdirSync(CAROUSEL_STAGING_DIR, { recursive: true });
|
|
31960
|
+
const carouselPath = path15.join(CAROUSEL_STAGING_DIR, `ad-${topic}.json`);
|
|
31961
|
+
fs21.writeFileSync(carouselPath, JSON.stringify(adCarousel, null, 2));
|
|
31962
|
+
fs21.mkdirSync(VIDEOS_DIR, { recursive: true });
|
|
30380
31963
|
const result = renderVideo(repoPath, carouselPath, "reels", VIDEOS_DIR);
|
|
30381
31964
|
if (result.success && result.videoPath) {
|
|
30382
31965
|
return {
|
|
@@ -30389,7 +31972,7 @@ function executeRenderAd(topic, businessContext) {
|
|
|
30389
31972
|
output: result.output
|
|
30390
31973
|
};
|
|
30391
31974
|
}
|
|
30392
|
-
function
|
|
31975
|
+
function executeCheckStatus4(businessContext) {
|
|
30393
31976
|
const lines = ["## Promo Video Status\n"];
|
|
30394
31977
|
const repoPath = businessContext?.contentflow?.repo_path;
|
|
30395
31978
|
if (!repoPath) {
|
|
@@ -30399,18 +31982,18 @@ function executeCheckStatus3(businessContext) {
|
|
|
30399
31982
|
} else {
|
|
30400
31983
|
lines.push(`- ContentFlow: available at ${repoPath}`);
|
|
30401
31984
|
}
|
|
30402
|
-
if (
|
|
30403
|
-
const videos =
|
|
31985
|
+
if (fs21.existsSync(VIDEOS_DIR)) {
|
|
31986
|
+
const videos = fs21.readdirSync(VIDEOS_DIR).filter((f) => f.endsWith(".mp4"));
|
|
30404
31987
|
lines.push(`- Rendered videos: ${videos.length}`);
|
|
30405
31988
|
for (const video of videos.slice(0, 10)) {
|
|
30406
|
-
const stat =
|
|
31989
|
+
const stat = fs21.statSync(path15.join(VIDEOS_DIR, video));
|
|
30407
31990
|
lines.push(` - ${video} (${Math.round(stat.size / 1024)}KB)`);
|
|
30408
31991
|
}
|
|
30409
31992
|
} else {
|
|
30410
31993
|
lines.push("- Rendered videos: 0");
|
|
30411
31994
|
}
|
|
30412
|
-
if (
|
|
30413
|
-
const staging =
|
|
31995
|
+
if (fs21.existsSync(CAROUSEL_STAGING_DIR)) {
|
|
31996
|
+
const staging = fs21.readdirSync(CAROUSEL_STAGING_DIR).filter((f) => f.endsWith(".json"));
|
|
30414
31997
|
lines.push(`- Staging carousel JSONs: ${staging.length}`);
|
|
30415
31998
|
}
|
|
30416
31999
|
return { success: true, output: lines.join("\n") };
|
|
@@ -30422,9 +32005,9 @@ function readPromoVideoFreshness() {
|
|
|
30422
32005
|
last_video: null
|
|
30423
32006
|
};
|
|
30424
32007
|
try {
|
|
30425
|
-
const contextPath =
|
|
30426
|
-
if (
|
|
30427
|
-
const ctx = JSON.parse(
|
|
32008
|
+
const contextPath = path15.join(DATA_DIR, "business-context.json");
|
|
32009
|
+
if (fs21.existsSync(contextPath)) {
|
|
32010
|
+
const ctx = JSON.parse(fs21.readFileSync(contextPath, "utf-8"));
|
|
30428
32011
|
const repoPath = ctx?.contentflow?.repo_path;
|
|
30429
32012
|
if (repoPath) {
|
|
30430
32013
|
result.contentflow_available = isContentFlowAvailable(repoPath);
|
|
@@ -30432,9 +32015,9 @@ function readPromoVideoFreshness() {
|
|
|
30432
32015
|
}
|
|
30433
32016
|
} catch {
|
|
30434
32017
|
}
|
|
30435
|
-
if (
|
|
32018
|
+
if (fs21.existsSync(VIDEOS_DIR)) {
|
|
30436
32019
|
try {
|
|
30437
|
-
const videos =
|
|
32020
|
+
const videos = fs21.readdirSync(VIDEOS_DIR).filter((f) => f.endsWith(".mp4"));
|
|
30438
32021
|
result.video_count = videos.length;
|
|
30439
32022
|
if (videos.length > 0) {
|
|
30440
32023
|
result.last_video = videos.sort().reverse()[0];
|
|
@@ -30446,9 +32029,9 @@ function readPromoVideoFreshness() {
|
|
|
30446
32029
|
}
|
|
30447
32030
|
|
|
30448
32031
|
// scripts/skills/launch-campaign.ts
|
|
30449
|
-
var
|
|
30450
|
-
var
|
|
30451
|
-
var CAMPAIGNS_DIR =
|
|
32032
|
+
var fs22 = __toESM(require("fs"));
|
|
32033
|
+
var path16 = __toESM(require("path"));
|
|
32034
|
+
var CAMPAIGNS_DIR = path16.join(DATA_DIR, "campaigns");
|
|
30452
32035
|
var CAMPAIGN_PREFIXES = [
|
|
30453
32036
|
"campaign-launch-",
|
|
30454
32037
|
"campaign-milestone-",
|
|
@@ -30465,42 +32048,42 @@ function detectChannels(businessContext) {
|
|
|
30465
32048
|
contentflow: false
|
|
30466
32049
|
};
|
|
30467
32050
|
try {
|
|
30468
|
-
if (
|
|
30469
|
-
const social = JSON.parse(
|
|
32051
|
+
if (fs22.existsSync(SOCIAL_FILE)) {
|
|
32052
|
+
const social = JSON.parse(fs22.readFileSync(SOCIAL_FILE, "utf-8"));
|
|
30470
32053
|
channels.social = social.credentials_valid;
|
|
30471
32054
|
}
|
|
30472
32055
|
} catch {
|
|
30473
32056
|
}
|
|
30474
32057
|
channels.email = !!process.env.LOOPS_API_KEY;
|
|
30475
32058
|
try {
|
|
30476
|
-
if (
|
|
30477
|
-
const pos = JSON.parse(
|
|
32059
|
+
if (fs22.existsSync(POSITIONING_FILE)) {
|
|
32060
|
+
const pos = JSON.parse(fs22.readFileSync(POSITIONING_FILE, "utf-8"));
|
|
30478
32061
|
channels.positioning = !!pos?.current?.tagline;
|
|
30479
32062
|
}
|
|
30480
32063
|
} catch {
|
|
30481
32064
|
}
|
|
30482
32065
|
if (businessContext?.contentflow?.repo_path) {
|
|
30483
|
-
channels.contentflow =
|
|
32066
|
+
channels.contentflow = fs22.existsSync(businessContext.contentflow.repo_path);
|
|
30484
32067
|
}
|
|
30485
32068
|
return channels;
|
|
30486
32069
|
}
|
|
30487
32070
|
function saveCampaign(campaign) {
|
|
30488
|
-
|
|
30489
|
-
const filePath =
|
|
30490
|
-
|
|
32071
|
+
fs22.mkdirSync(CAMPAIGNS_DIR, { recursive: true });
|
|
32072
|
+
const filePath = path16.join(CAMPAIGNS_DIR, `${campaign.id}.json`);
|
|
32073
|
+
fs22.writeFileSync(filePath, JSON.stringify(campaign, null, 2) + "\n");
|
|
30491
32074
|
}
|
|
30492
32075
|
function loadCampaigns() {
|
|
30493
|
-
if (!
|
|
32076
|
+
if (!fs22.existsSync(CAMPAIGNS_DIR)) return [];
|
|
30494
32077
|
try {
|
|
30495
|
-
return
|
|
32078
|
+
return fs22.readdirSync(CAMPAIGNS_DIR).filter((f) => f.endsWith(".json")).map((f) => JSON.parse(fs22.readFileSync(path16.join(CAMPAIGNS_DIR, f), "utf-8"))).sort((a, b) => b.created_at.localeCompare(a.created_at));
|
|
30496
32079
|
} catch {
|
|
30497
32080
|
return [];
|
|
30498
32081
|
}
|
|
30499
32082
|
}
|
|
30500
32083
|
function appendToTodo(tasks) {
|
|
30501
32084
|
let content = "";
|
|
30502
|
-
if (
|
|
30503
|
-
content =
|
|
32085
|
+
if (fs22.existsSync(TODO_FILE)) {
|
|
32086
|
+
content = fs22.readFileSync(TODO_FILE, "utf-8");
|
|
30504
32087
|
}
|
|
30505
32088
|
const newLines = tasks.map(
|
|
30506
32089
|
(t) => `- [ ] **${t.taskId}** \u2014 ${t.description}`
|
|
@@ -30516,7 +32099,7 @@ function appendToTodo(tasks) {
|
|
|
30516
32099
|
${newLines}
|
|
30517
32100
|
`;
|
|
30518
32101
|
}
|
|
30519
|
-
|
|
32102
|
+
fs22.writeFileSync(TODO_FILE, content);
|
|
30520
32103
|
}
|
|
30521
32104
|
async function executeLaunchCampaignTask(taskId, description, businessContext) {
|
|
30522
32105
|
try {
|
|
@@ -30529,7 +32112,7 @@ async function executeLaunchCampaignTask(taskId, description, businessContext) {
|
|
|
30529
32112
|
return executeLaunchMilestone(kpiId, businessContext);
|
|
30530
32113
|
}
|
|
30531
32114
|
if (taskId === "campaign-check-status") {
|
|
30532
|
-
return
|
|
32115
|
+
return executeCheckStatus5();
|
|
30533
32116
|
}
|
|
30534
32117
|
return { success: false, output: `Unknown campaign task: ${taskId}` };
|
|
30535
32118
|
} catch (error) {
|
|
@@ -30543,7 +32126,7 @@ function executeLaunchShip(ideaId, businessContext) {
|
|
|
30543
32126
|
}
|
|
30544
32127
|
let idea;
|
|
30545
32128
|
try {
|
|
30546
|
-
const ideasData = JSON.parse(
|
|
32129
|
+
const ideasData = JSON.parse(fs22.readFileSync(IDEAS_FILE, "utf-8"));
|
|
30547
32130
|
idea = ideasData.ideas.find((i) => i.id === ideaId);
|
|
30548
32131
|
} catch {
|
|
30549
32132
|
return { success: false, output: `Failed to load ideas from ${IDEAS_FILE}` };
|
|
@@ -30564,8 +32147,8 @@ function executeLaunchShip(ideaId, businessContext) {
|
|
|
30564
32147
|
const channels = detectChannels(businessContext);
|
|
30565
32148
|
const activeChannels = [];
|
|
30566
32149
|
const subTasks = [];
|
|
30567
|
-
const cardPath =
|
|
30568
|
-
if (!
|
|
32150
|
+
const cardPath = path16.join(DATA_DIR, "reports", "visuals", `${ideaId}-card.png`);
|
|
32151
|
+
if (!fs22.existsSync(cardPath)) {
|
|
30569
32152
|
subTasks.push({
|
|
30570
32153
|
task_id: `generate-visual-${ideaId}`,
|
|
30571
32154
|
description: `Generate ship card PNG for "${idea.title}"`,
|
|
@@ -30656,8 +32239,8 @@ function executeLaunchMilestone(kpiId, businessContext) {
|
|
|
30656
32239
|
}
|
|
30657
32240
|
let kpiName = kpiId;
|
|
30658
32241
|
try {
|
|
30659
|
-
if (
|
|
30660
|
-
const goalsData = JSON.parse(
|
|
32242
|
+
if (fs22.existsSync(GOALS_FILE)) {
|
|
32243
|
+
const goalsData = JSON.parse(fs22.readFileSync(GOALS_FILE, "utf-8"));
|
|
30661
32244
|
for (const goal of goalsData.goals) {
|
|
30662
32245
|
const kpi = goal.kpis.find((k) => k.id === kpiId);
|
|
30663
32246
|
if (kpi) {
|
|
@@ -30724,7 +32307,7 @@ function executeLaunchMilestone(kpiId, businessContext) {
|
|
|
30724
32307
|
].join("\n")
|
|
30725
32308
|
};
|
|
30726
32309
|
}
|
|
30727
|
-
function
|
|
32310
|
+
function executeCheckStatus5() {
|
|
30728
32311
|
const campaigns = loadCampaigns();
|
|
30729
32312
|
if (campaigns.length === 0) {
|
|
30730
32313
|
return { success: true, output: "No campaigns found." };
|
|
@@ -30757,11 +32340,11 @@ function readCampaignFreshness() {
|
|
|
30757
32340
|
}
|
|
30758
32341
|
|
|
30759
32342
|
// scripts/lib/run.ts
|
|
30760
|
-
var
|
|
32343
|
+
var path17 = __toESM(require("path"));
|
|
30761
32344
|
function resolveScript(baseDir, scriptName) {
|
|
30762
|
-
const isCompiled =
|
|
32345
|
+
const isCompiled = path17.extname(__filename) === ".js";
|
|
30763
32346
|
const resolvedName = isCompiled ? scriptName.replace(/\.ts$/, ".js") : scriptName;
|
|
30764
|
-
const scriptPath =
|
|
32347
|
+
const scriptPath = path17.join(baseDir, resolvedName);
|
|
30765
32348
|
return isCompiled ? { command: "node", args: [scriptPath] } : { command: "npx", args: ["tsx", scriptPath] };
|
|
30766
32349
|
}
|
|
30767
32350
|
|
|
@@ -30815,8 +32398,8 @@ function groupTasksByConflicts(tasks) {
|
|
|
30815
32398
|
}
|
|
30816
32399
|
|
|
30817
32400
|
// scripts/lib/json-lock.ts
|
|
30818
|
-
var
|
|
30819
|
-
var
|
|
32401
|
+
var fs23 = __toESM(require("fs"));
|
|
32402
|
+
var path18 = __toESM(require("path"));
|
|
30820
32403
|
var DEFAULT_MAX_WAIT_MS = 1e4;
|
|
30821
32404
|
var BASE_BACKOFF_MS = 50;
|
|
30822
32405
|
var MAX_BACKOFF_MS = 150;
|
|
@@ -30824,10 +32407,10 @@ function withFileLock(lockPath, fn, maxWaitMs = DEFAULT_MAX_WAIT_MS) {
|
|
|
30824
32407
|
const startTime = Date.now();
|
|
30825
32408
|
while (true) {
|
|
30826
32409
|
try {
|
|
30827
|
-
|
|
32410
|
+
fs23.mkdirSync(lockPath, { recursive: false });
|
|
30828
32411
|
try {
|
|
30829
|
-
|
|
30830
|
-
|
|
32412
|
+
fs23.writeFileSync(
|
|
32413
|
+
path18.join(lockPath, "owner"),
|
|
30831
32414
|
JSON.stringify({ pid: process.pid, acquired: Date.now() })
|
|
30832
32415
|
);
|
|
30833
32416
|
} catch {
|
|
@@ -30856,16 +32439,16 @@ function withFileLock(lockPath, fn, maxWaitMs = DEFAULT_MAX_WAIT_MS) {
|
|
|
30856
32439
|
}
|
|
30857
32440
|
function atomicWriteFileSync(filePath, content) {
|
|
30858
32441
|
const tmpPath = `${filePath}.${process.pid}.tmp`;
|
|
30859
|
-
|
|
30860
|
-
|
|
32442
|
+
fs23.writeFileSync(tmpPath, content, "utf-8");
|
|
32443
|
+
fs23.renameSync(tmpPath, filePath);
|
|
30861
32444
|
}
|
|
30862
32445
|
function releaseLock(lockPath) {
|
|
30863
32446
|
try {
|
|
30864
|
-
const ownerFile =
|
|
30865
|
-
if (
|
|
30866
|
-
|
|
32447
|
+
const ownerFile = path18.join(lockPath, "owner");
|
|
32448
|
+
if (fs23.existsSync(ownerFile)) {
|
|
32449
|
+
fs23.unlinkSync(ownerFile);
|
|
30867
32450
|
}
|
|
30868
|
-
|
|
32451
|
+
fs23.rmdirSync(lockPath);
|
|
30869
32452
|
} catch {
|
|
30870
32453
|
}
|
|
30871
32454
|
}
|
|
@@ -30876,57 +32459,57 @@ function sleepSync(ms2) {
|
|
|
30876
32459
|
}
|
|
30877
32460
|
|
|
30878
32461
|
// scripts/lib/worktree.ts
|
|
30879
|
-
var
|
|
30880
|
-
var
|
|
32462
|
+
var fs25 = __toESM(require("fs"));
|
|
32463
|
+
var path20 = __toESM(require("path"));
|
|
30881
32464
|
|
|
30882
32465
|
// scripts/lib/git-utils.ts
|
|
30883
|
-
var
|
|
30884
|
-
var
|
|
30885
|
-
var
|
|
32466
|
+
var import_child_process9 = require("child_process");
|
|
32467
|
+
var fs24 = __toESM(require("fs"));
|
|
32468
|
+
var path19 = __toESM(require("path"));
|
|
30886
32469
|
function cleanGitState(workspacePath) {
|
|
30887
|
-
if (!
|
|
30888
|
-
const gitDir =
|
|
30889
|
-
if (!
|
|
32470
|
+
if (!fs24.existsSync(workspacePath)) return;
|
|
32471
|
+
const gitDir = path19.join(workspacePath, ".git");
|
|
32472
|
+
if (!fs24.existsSync(gitDir)) return;
|
|
30890
32473
|
let actualGitDir = gitDir;
|
|
30891
32474
|
try {
|
|
30892
|
-
const stat =
|
|
32475
|
+
const stat = fs24.statSync(gitDir);
|
|
30893
32476
|
if (stat.isFile()) {
|
|
30894
|
-
const content =
|
|
32477
|
+
const content = fs24.readFileSync(gitDir, "utf-8").trim();
|
|
30895
32478
|
const match = content.match(/^gitdir:\s+(.+)$/);
|
|
30896
32479
|
if (match) actualGitDir = match[1];
|
|
30897
32480
|
}
|
|
30898
32481
|
} catch {
|
|
30899
32482
|
return;
|
|
30900
32483
|
}
|
|
30901
|
-
const lockFile =
|
|
30902
|
-
if (
|
|
32484
|
+
const lockFile = path19.join(actualGitDir, "index.lock");
|
|
32485
|
+
if (fs24.existsSync(lockFile)) {
|
|
30903
32486
|
try {
|
|
30904
|
-
const result = (0,
|
|
32487
|
+
const result = (0, import_child_process9.execSync)(
|
|
30905
32488
|
`lsof "${lockFile}" 2>/dev/null || true`,
|
|
30906
32489
|
{ encoding: "utf-8", timeout: 5e3 }
|
|
30907
32490
|
).trim();
|
|
30908
32491
|
if (!result) {
|
|
30909
|
-
|
|
32492
|
+
fs24.unlinkSync(lockFile);
|
|
30910
32493
|
console.log(`[pre-flight] Removed stale .git/index.lock in ${workspacePath}`);
|
|
30911
32494
|
}
|
|
30912
32495
|
} catch {
|
|
30913
32496
|
try {
|
|
30914
|
-
|
|
32497
|
+
fs24.unlinkSync(lockFile);
|
|
30915
32498
|
console.log(`[pre-flight] Removed stale .git/index.lock in ${workspacePath}`);
|
|
30916
32499
|
} catch {
|
|
30917
32500
|
}
|
|
30918
32501
|
}
|
|
30919
32502
|
}
|
|
30920
32503
|
const staleOps = [
|
|
30921
|
-
{ marker:
|
|
30922
|
-
{ marker:
|
|
30923
|
-
{ marker:
|
|
30924
|
-
{ marker:
|
|
32504
|
+
{ marker: path19.join(actualGitDir, "MERGE_HEAD"), abort: "git merge --abort" },
|
|
32505
|
+
{ marker: path19.join(actualGitDir, "rebase-merge"), abort: "git rebase --abort" },
|
|
32506
|
+
{ marker: path19.join(actualGitDir, "rebase-apply"), abort: "git rebase --abort" },
|
|
32507
|
+
{ marker: path19.join(actualGitDir, "CHERRY_PICK_HEAD"), abort: "git cherry-pick --abort" }
|
|
30925
32508
|
];
|
|
30926
32509
|
for (const { marker, abort } of staleOps) {
|
|
30927
|
-
if (
|
|
32510
|
+
if (fs24.existsSync(marker)) {
|
|
30928
32511
|
try {
|
|
30929
|
-
(0,
|
|
32512
|
+
(0, import_child_process9.execSync)(abort, { cwd: workspacePath, encoding: "utf-8", timeout: 1e4, stdio: "pipe" });
|
|
30930
32513
|
console.log(`[pre-flight] Aborted stale operation: ${abort} in ${workspacePath}`);
|
|
30931
32514
|
} catch {
|
|
30932
32515
|
}
|
|
@@ -30935,7 +32518,7 @@ function cleanGitState(workspacePath) {
|
|
|
30935
32518
|
}
|
|
30936
32519
|
function exec2(cmd, cwd, timeoutMs) {
|
|
30937
32520
|
try {
|
|
30938
|
-
return (0,
|
|
32521
|
+
return (0, import_child_process9.execSync)(cmd, {
|
|
30939
32522
|
cwd,
|
|
30940
32523
|
encoding: "utf-8",
|
|
30941
32524
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -30951,8 +32534,8 @@ ${execError.stderr || execError.message}`);
|
|
|
30951
32534
|
// scripts/lib/worktree.ts
|
|
30952
32535
|
function getWorktreePaths(workspaceDir, repoName) {
|
|
30953
32536
|
return {
|
|
30954
|
-
baseClonePath:
|
|
30955
|
-
worktreeContainerDir:
|
|
32537
|
+
baseClonePath: path20.join(workspaceDir, repoName),
|
|
32538
|
+
worktreeContainerDir: path20.join(workspaceDir, `${repoName}-worktrees`)
|
|
30956
32539
|
};
|
|
30957
32540
|
}
|
|
30958
32541
|
function syncBaseClone(baseClonePath, defaultBranch) {
|
|
@@ -30971,16 +32554,16 @@ function ensureIdeaBranch(baseClonePath, branchName, defaultBranch) {
|
|
|
30971
32554
|
}
|
|
30972
32555
|
}
|
|
30973
32556
|
function createWorktree(baseClonePath, worktreeContainerDir, ideaBranch, groupIndex) {
|
|
30974
|
-
if (!
|
|
30975
|
-
|
|
32557
|
+
if (!fs25.existsSync(worktreeContainerDir)) {
|
|
32558
|
+
fs25.mkdirSync(worktreeContainerDir, { recursive: true });
|
|
30976
32559
|
}
|
|
30977
32560
|
const tempBranch = `wt/${ideaBranch}-g${groupIndex}`;
|
|
30978
|
-
const worktreePath =
|
|
30979
|
-
if (
|
|
32561
|
+
const worktreePath = path20.join(worktreeContainerDir, `${ideaBranch}-g${groupIndex}`);
|
|
32562
|
+
if (fs25.existsSync(worktreePath)) {
|
|
30980
32563
|
try {
|
|
30981
32564
|
exec2(`git worktree remove --force "${worktreePath}"`, baseClonePath, 1e4);
|
|
30982
32565
|
} catch {
|
|
30983
|
-
|
|
32566
|
+
fs25.rmSync(worktreePath, { recursive: true, force: true });
|
|
30984
32567
|
}
|
|
30985
32568
|
}
|
|
30986
32569
|
try {
|
|
@@ -31014,7 +32597,7 @@ function removeWorktree(baseClonePath, worktreePath, tempBranch) {
|
|
|
31014
32597
|
exec2(`git worktree remove --force "${worktreePath}"`, baseClonePath, 1e4);
|
|
31015
32598
|
} catch {
|
|
31016
32599
|
try {
|
|
31017
|
-
|
|
32600
|
+
fs25.rmSync(worktreePath, { recursive: true, force: true });
|
|
31018
32601
|
} catch {
|
|
31019
32602
|
}
|
|
31020
32603
|
}
|
|
@@ -31024,12 +32607,12 @@ function removeWorktree(baseClonePath, worktreePath, tempBranch) {
|
|
|
31024
32607
|
}
|
|
31025
32608
|
}
|
|
31026
32609
|
function cleanupIdeaWorktrees(baseClonePath, worktreeContainerDir, ideaBranch) {
|
|
31027
|
-
if (
|
|
32610
|
+
if (fs25.existsSync(worktreeContainerDir)) {
|
|
31028
32611
|
try {
|
|
31029
|
-
const entries =
|
|
32612
|
+
const entries = fs25.readdirSync(worktreeContainerDir);
|
|
31030
32613
|
for (const entry of entries) {
|
|
31031
32614
|
if (entry.startsWith(`${ideaBranch}-g`)) {
|
|
31032
|
-
const wtPath =
|
|
32615
|
+
const wtPath = path20.join(worktreeContainerDir, entry);
|
|
31033
32616
|
const groupMatch = entry.match(/-g(\d+)$/);
|
|
31034
32617
|
const tempBranch = `wt/${ideaBranch}-g${groupMatch?.[1] ?? "0"}`;
|
|
31035
32618
|
removeWorktree(baseClonePath, wtPath, tempBranch);
|
|
@@ -31043,9 +32626,9 @@ function cleanupIdeaWorktrees(baseClonePath, worktreeContainerDir, ideaBranch) {
|
|
|
31043
32626
|
} catch {
|
|
31044
32627
|
}
|
|
31045
32628
|
try {
|
|
31046
|
-
const remaining =
|
|
32629
|
+
const remaining = fs25.readdirSync(worktreeContainerDir);
|
|
31047
32630
|
if (remaining.length === 0) {
|
|
31048
|
-
|
|
32631
|
+
fs25.rmdirSync(worktreeContainerDir);
|
|
31049
32632
|
}
|
|
31050
32633
|
} catch {
|
|
31051
32634
|
}
|
|
@@ -31055,24 +32638,24 @@ function getTempBranchName(ideaBranch, groupIndex) {
|
|
|
31055
32638
|
}
|
|
31056
32639
|
|
|
31057
32640
|
// scripts/lib/telemetry.ts
|
|
31058
|
-
var
|
|
31059
|
-
var
|
|
31060
|
-
var
|
|
32641
|
+
var fs26 = __toESM(require("fs"));
|
|
32642
|
+
var path21 = __toESM(require("path"));
|
|
32643
|
+
var CONFIG_DIR2 = path21.join(
|
|
31061
32644
|
process.env.HOME || process.env.USERPROFILE || "~",
|
|
31062
32645
|
".vibebusiness"
|
|
31063
32646
|
);
|
|
31064
|
-
var TELEMETRY_CONFIG_FILE =
|
|
31065
|
-
var EVENTS_FILE =
|
|
32647
|
+
var TELEMETRY_CONFIG_FILE = path21.join(CONFIG_DIR2, "telemetry.json");
|
|
32648
|
+
var EVENTS_FILE = path21.join(CONFIG_DIR2, "events.jsonl");
|
|
31066
32649
|
function getVersion() {
|
|
31067
32650
|
try {
|
|
31068
32651
|
let dir = __dirname;
|
|
31069
32652
|
for (let i = 0; i < 4; i++) {
|
|
31070
|
-
const pkgPath =
|
|
31071
|
-
if (
|
|
31072
|
-
const pkg = JSON.parse(
|
|
32653
|
+
const pkgPath = path21.join(dir, "package.json");
|
|
32654
|
+
if (fs26.existsSync(pkgPath)) {
|
|
32655
|
+
const pkg = JSON.parse(fs26.readFileSync(pkgPath, "utf-8"));
|
|
31073
32656
|
return pkg.version || "0.0.0";
|
|
31074
32657
|
}
|
|
31075
|
-
dir =
|
|
32658
|
+
dir = path21.dirname(dir);
|
|
31076
32659
|
}
|
|
31077
32660
|
} catch {
|
|
31078
32661
|
}
|
|
@@ -31080,8 +32663,8 @@ function getVersion() {
|
|
|
31080
32663
|
}
|
|
31081
32664
|
function getTelemetryConfig() {
|
|
31082
32665
|
try {
|
|
31083
|
-
if (!
|
|
31084
|
-
return JSON.parse(
|
|
32666
|
+
if (!fs26.existsSync(TELEMETRY_CONFIG_FILE)) return null;
|
|
32667
|
+
return JSON.parse(fs26.readFileSync(TELEMETRY_CONFIG_FILE, "utf-8"));
|
|
31085
32668
|
} catch {
|
|
31086
32669
|
return null;
|
|
31087
32670
|
}
|
|
@@ -31102,8 +32685,8 @@ function trackEvent(event, properties = {}) {
|
|
|
31102
32685
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
31103
32686
|
version: getVersion()
|
|
31104
32687
|
};
|
|
31105
|
-
|
|
31106
|
-
|
|
32688
|
+
fs26.mkdirSync(CONFIG_DIR2, { recursive: true });
|
|
32689
|
+
fs26.appendFileSync(EVENTS_FILE, JSON.stringify(entry) + "\n");
|
|
31107
32690
|
} catch {
|
|
31108
32691
|
}
|
|
31109
32692
|
}
|
|
@@ -31334,22 +32917,22 @@ async function suggestEpics(opts) {
|
|
|
31334
32917
|
}
|
|
31335
32918
|
|
|
31336
32919
|
// scripts/lib/scaffold.ts
|
|
31337
|
-
var
|
|
31338
|
-
var
|
|
32920
|
+
var fs28 = __toESM(require("fs"));
|
|
32921
|
+
var path23 = __toESM(require("path"));
|
|
31339
32922
|
function scaffoldSlashCommands(rootDir) {
|
|
31340
|
-
let templatesDir =
|
|
31341
|
-
if (!
|
|
31342
|
-
templatesDir =
|
|
31343
|
-
}
|
|
31344
|
-
if (!
|
|
31345
|
-
const targetDir =
|
|
31346
|
-
|
|
31347
|
-
const templates =
|
|
32923
|
+
let templatesDir = path23.join(__dirname, "..", "..", "templates", "commands");
|
|
32924
|
+
if (!fs28.existsSync(templatesDir)) {
|
|
32925
|
+
templatesDir = path23.join(__dirname, "..", "..", "..", "templates", "commands");
|
|
32926
|
+
}
|
|
32927
|
+
if (!fs28.existsSync(templatesDir)) return;
|
|
32928
|
+
const targetDir = path23.join(rootDir, ".claude", "commands");
|
|
32929
|
+
fs28.mkdirSync(targetDir, { recursive: true });
|
|
32930
|
+
const templates = fs28.readdirSync(templatesDir).filter((f) => f.endsWith(".md"));
|
|
31348
32931
|
let copied = 0;
|
|
31349
32932
|
for (const file of templates) {
|
|
31350
|
-
const src =
|
|
31351
|
-
const dest =
|
|
31352
|
-
|
|
32933
|
+
const src = path23.join(templatesDir, file);
|
|
32934
|
+
const dest = path23.join(targetDir, file);
|
|
32935
|
+
fs28.copyFileSync(src, dest);
|
|
31353
32936
|
copied++;
|
|
31354
32937
|
}
|
|
31355
32938
|
if (copied > 0) {
|
|
@@ -31358,23 +32941,23 @@ function scaffoldSlashCommands(rootDir) {
|
|
|
31358
32941
|
}
|
|
31359
32942
|
|
|
31360
32943
|
// scripts/lib/vibe-credits.ts
|
|
31361
|
-
var
|
|
31362
|
-
var
|
|
32944
|
+
var fs29 = __toESM(require("fs"));
|
|
32945
|
+
var path24 = __toESM(require("path"));
|
|
31363
32946
|
var WORKER_URL = "https://vibe-credits.luis-e13.workers.dev";
|
|
31364
|
-
var
|
|
31365
|
-
var BUFFER_FILE =
|
|
32947
|
+
var CONFIG_DIR3 = path24.join(process.env.HOME || "", ".vibebusiness");
|
|
32948
|
+
var BUFFER_FILE = path24.join(CONFIG_DIR3, "vibe-buffer.json");
|
|
31366
32949
|
var MAX_BUFFER = 5;
|
|
31367
32950
|
var REQUEST_TIMEOUT_MS = 5e3;
|
|
31368
32951
|
function getClientVersion() {
|
|
31369
32952
|
try {
|
|
31370
32953
|
let dir = __dirname;
|
|
31371
32954
|
for (let i = 0; i < 4; i++) {
|
|
31372
|
-
const pkgPath =
|
|
31373
|
-
if (
|
|
31374
|
-
const pkg = JSON.parse(
|
|
32955
|
+
const pkgPath = path24.join(dir, "package.json");
|
|
32956
|
+
if (fs29.existsSync(pkgPath)) {
|
|
32957
|
+
const pkg = JSON.parse(fs29.readFileSync(pkgPath, "utf-8"));
|
|
31375
32958
|
return pkg.version || "0.0.0";
|
|
31376
32959
|
}
|
|
31377
|
-
dir =
|
|
32960
|
+
dir = path24.dirname(dir);
|
|
31378
32961
|
}
|
|
31379
32962
|
} catch {
|
|
31380
32963
|
}
|
|
@@ -31383,18 +32966,18 @@ function getClientVersion() {
|
|
|
31383
32966
|
var CLIENT_VERSION = getClientVersion();
|
|
31384
32967
|
function loadBuffer() {
|
|
31385
32968
|
try {
|
|
31386
|
-
if (
|
|
31387
|
-
return JSON.parse(
|
|
32969
|
+
if (fs29.existsSync(BUFFER_FILE)) {
|
|
32970
|
+
return JSON.parse(fs29.readFileSync(BUFFER_FILE, "utf-8"));
|
|
31388
32971
|
}
|
|
31389
32972
|
} catch {
|
|
31390
32973
|
}
|
|
31391
32974
|
return { vibes: 0, last_synced: "" };
|
|
31392
32975
|
}
|
|
31393
32976
|
function saveBuffer(buffer) {
|
|
31394
|
-
if (!
|
|
31395
|
-
|
|
32977
|
+
if (!fs29.existsSync(CONFIG_DIR3)) {
|
|
32978
|
+
fs29.mkdirSync(CONFIG_DIR3, { recursive: true });
|
|
31396
32979
|
}
|
|
31397
|
-
|
|
32980
|
+
fs29.writeFileSync(BUFFER_FILE, JSON.stringify(buffer, null, 2));
|
|
31398
32981
|
}
|
|
31399
32982
|
function syncBuffer(vibesRemaining) {
|
|
31400
32983
|
const buffered = Math.min(vibesRemaining, MAX_BUFFER);
|
|
@@ -31468,14 +33051,14 @@ async function consumeVibe(instanceId) {
|
|
|
31468
33051
|
}
|
|
31469
33052
|
|
|
31470
33053
|
// scripts/lib/license.ts
|
|
31471
|
-
var
|
|
31472
|
-
var
|
|
31473
|
-
var
|
|
31474
|
-
var LICENSE_FILE =
|
|
33054
|
+
var fs30 = __toESM(require("fs"));
|
|
33055
|
+
var path25 = __toESM(require("path"));
|
|
33056
|
+
var CONFIG_DIR4 = path25.join(process.env.HOME || "", ".vibebusiness");
|
|
33057
|
+
var LICENSE_FILE = path25.join(CONFIG_DIR4, "license.json");
|
|
31475
33058
|
function loadStoredLicense() {
|
|
31476
33059
|
try {
|
|
31477
|
-
if (
|
|
31478
|
-
return JSON.parse(
|
|
33060
|
+
if (fs30.existsSync(LICENSE_FILE)) {
|
|
33061
|
+
return JSON.parse(fs30.readFileSync(LICENSE_FILE, "utf-8"));
|
|
31479
33062
|
}
|
|
31480
33063
|
} catch {
|
|
31481
33064
|
}
|
|
@@ -31495,10 +33078,10 @@ function getInstanceId() {
|
|
|
31495
33078
|
|
|
31496
33079
|
// scripts/heartbeat.ts
|
|
31497
33080
|
var ROOT_DIR = PROJECT_DIR;
|
|
31498
|
-
var execAsync = (0, import_util.promisify)(
|
|
33081
|
+
var execAsync = (0, import_util.promisify)(import_child_process10.exec);
|
|
31499
33082
|
function spawnAsync(cmd, args2, options) {
|
|
31500
|
-
return new Promise((
|
|
31501
|
-
const child = (0,
|
|
33083
|
+
return new Promise((resolve2, reject) => {
|
|
33084
|
+
const child = (0, import_child_process10.spawn)(cmd, args2, {
|
|
31502
33085
|
cwd: options.cwd,
|
|
31503
33086
|
stdio: ["pipe", "pipe", "pipe"]
|
|
31504
33087
|
});
|
|
@@ -31544,7 +33127,7 @@ function spawnAsync(cmd, args2, options) {
|
|
|
31544
33127
|
reject(err2);
|
|
31545
33128
|
return;
|
|
31546
33129
|
}
|
|
31547
|
-
|
|
33130
|
+
resolve2({ stdout, stderr });
|
|
31548
33131
|
});
|
|
31549
33132
|
});
|
|
31550
33133
|
}
|
|
@@ -31555,6 +33138,8 @@ var SESSION_MODE = args.includes("--session");
|
|
|
31555
33138
|
var DURATION_MINUTES = parseInt(args.find((a) => a.startsWith("--duration="))?.split("=")[1] || "30");
|
|
31556
33139
|
var INTERVAL_MINUTES = parseInt(args.find((a) => a.startsWith("--interval="))?.split("=")[1] || "5");
|
|
31557
33140
|
var HEARTBEAT_COUNT = parseInt(args.find((a) => a.startsWith("--count="))?.split("=")[1] || "0");
|
|
33141
|
+
var TASK_OVERRIDE = args.find((a) => a.startsWith("--task="))?.split("=")[1] || null;
|
|
33142
|
+
var TASK_DESCRIPTION = args.find((a) => a.startsWith("--desc="))?.split("=").slice(1).join("=") || null;
|
|
31558
33143
|
var PRIORITY_WEIGHT = { critical: 100, high: 50, medium: 20, low: 5 };
|
|
31559
33144
|
var IMPACT_WEIGHT = { xl: 100, l: 50, m: 20, s: 5, xs: 1 };
|
|
31560
33145
|
var EFFORT_COST = { xs: 1, s: 2, m: 5, l: 10, xl: 20 };
|
|
@@ -31598,11 +33183,11 @@ function log(message) {
|
|
|
31598
33183
|
}
|
|
31599
33184
|
}
|
|
31600
33185
|
function sleep(ms2) {
|
|
31601
|
-
return new Promise((
|
|
33186
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms2));
|
|
31602
33187
|
}
|
|
31603
33188
|
function loadJson(filePath, defaultValue) {
|
|
31604
33189
|
try {
|
|
31605
|
-
const content =
|
|
33190
|
+
const content = fs31.readFileSync(filePath, "utf-8");
|
|
31606
33191
|
return JSON.parse(content);
|
|
31607
33192
|
} catch {
|
|
31608
33193
|
return defaultValue;
|
|
@@ -31657,7 +33242,7 @@ function isMetaTaskFalseCompletion(output) {
|
|
|
31657
33242
|
}
|
|
31658
33243
|
function reclassifyAsHumanDependent(taskId) {
|
|
31659
33244
|
try {
|
|
31660
|
-
let content =
|
|
33245
|
+
let content = fs31.readFileSync(TODO_FILE, "utf-8");
|
|
31661
33246
|
const taskPattern = new RegExp(`^- \\[[ x]\\] \`${taskId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\`.*$`, "m");
|
|
31662
33247
|
const match = content.match(taskPattern);
|
|
31663
33248
|
if (!match) return;
|
|
@@ -31670,7 +33255,7 @@ function reclassifyAsHumanDependent(taskId) {
|
|
|
31670
33255
|
const insertIndex = content.indexOf("\n", blockedIndex) + 1;
|
|
31671
33256
|
content = content.slice(0, insertIndex) + "\n" + uncheckedLine + content.slice(insertIndex);
|
|
31672
33257
|
}
|
|
31673
|
-
|
|
33258
|
+
fs31.writeFileSync(TODO_FILE, content);
|
|
31674
33259
|
log(`Reclassified task ${taskId} as human-dependent (moved to Blocked)`);
|
|
31675
33260
|
} catch (error) {
|
|
31676
33261
|
log(`Failed to reclassify task: ${error instanceof Error ? error.message : "Unknown"}`);
|
|
@@ -31678,7 +33263,7 @@ function reclassifyAsHumanDependent(taskId) {
|
|
|
31678
33263
|
}
|
|
31679
33264
|
function getMetaTaskFailureCount(taskId) {
|
|
31680
33265
|
try {
|
|
31681
|
-
const content =
|
|
33266
|
+
const content = fs31.readFileSync(TODO_FILE, "utf-8");
|
|
31682
33267
|
const escapedId = taskId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
31683
33268
|
const match = content.match(new RegExp(`\`${escapedId}\`.*\\[failed:(\\d+)\\]`));
|
|
31684
33269
|
return match ? parseInt(match[1], 10) : 0;
|
|
@@ -31688,7 +33273,7 @@ function getMetaTaskFailureCount(taskId) {
|
|
|
31688
33273
|
}
|
|
31689
33274
|
function incrementMetaTaskFailureCount(taskId) {
|
|
31690
33275
|
try {
|
|
31691
|
-
let content =
|
|
33276
|
+
let content = fs31.readFileSync(TODO_FILE, "utf-8");
|
|
31692
33277
|
const escapedId = taskId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
31693
33278
|
const linePattern = new RegExp(`^(- \\[[ x]\\] \`${escapedId}\`.*)$`, "m");
|
|
31694
33279
|
const lineMatch = content.match(linePattern);
|
|
@@ -31704,7 +33289,7 @@ function incrementMetaTaskFailureCount(taskId) {
|
|
|
31704
33289
|
newLine = `${line} [failed:${newCount}]`;
|
|
31705
33290
|
}
|
|
31706
33291
|
content = content.replace(line, newLine);
|
|
31707
|
-
|
|
33292
|
+
fs31.writeFileSync(TODO_FILE, content);
|
|
31708
33293
|
log(`Meta-task ${taskId} failure count: ${newCount}`);
|
|
31709
33294
|
return newCount;
|
|
31710
33295
|
} catch (error) {
|
|
@@ -31714,7 +33299,7 @@ function incrementMetaTaskFailureCount(taskId) {
|
|
|
31714
33299
|
}
|
|
31715
33300
|
function parseTodoFile() {
|
|
31716
33301
|
try {
|
|
31717
|
-
const content =
|
|
33302
|
+
const content = fs31.readFileSync(TODO_FILE, "utf-8");
|
|
31718
33303
|
const tasks = [];
|
|
31719
33304
|
let currentSection = "high_priority";
|
|
31720
33305
|
const lines = content.split("\n");
|
|
@@ -31745,7 +33330,7 @@ function parseTodoFile() {
|
|
|
31745
33330
|
return [];
|
|
31746
33331
|
}
|
|
31747
33332
|
}
|
|
31748
|
-
function
|
|
33333
|
+
function loadState4() {
|
|
31749
33334
|
const ideasData = loadJson(IDEAS_FILE, { ideas: [] });
|
|
31750
33335
|
const goalsData = loadJson(GOALS_FILE, { goals: [] });
|
|
31751
33336
|
const sessionsData = loadJson(SESSIONS_FILE, { sessions: [] });
|
|
@@ -31864,10 +33449,10 @@ function captureGitDiffInfo(workspacePath) {
|
|
|
31864
33449
|
has_uncommitted_changes: false
|
|
31865
33450
|
};
|
|
31866
33451
|
try {
|
|
31867
|
-
const status = (0,
|
|
33452
|
+
const status = (0, import_child_process10.execSync)("git status --porcelain", { cwd: workspacePath, encoding: "utf-8" });
|
|
31868
33453
|
result.has_uncommitted_changes = status.trim().length > 0;
|
|
31869
33454
|
try {
|
|
31870
|
-
const diffStat = (0,
|
|
33455
|
+
const diffStat = (0, import_child_process10.execSync)("git diff --stat HEAD~1 HEAD 2>/dev/null || git diff --stat --cached 2>/dev/null || git diff --stat 2>/dev/null", {
|
|
31871
33456
|
cwd: workspacePath,
|
|
31872
33457
|
encoding: "utf-8",
|
|
31873
33458
|
shell: "/bin/bash"
|
|
@@ -32186,10 +33771,10 @@ function updateGoalsWithKPIs(kpis) {
|
|
|
32186
33771
|
log("Updated goals.json with live KPIs");
|
|
32187
33772
|
}
|
|
32188
33773
|
function loadCodebaseSnapshot() {
|
|
32189
|
-
const snapshotPath =
|
|
33774
|
+
const snapshotPath = path26.join(DATA_DIR, "codebase-snapshot.json");
|
|
32190
33775
|
try {
|
|
32191
|
-
if (
|
|
32192
|
-
return JSON.parse(
|
|
33776
|
+
if (fs31.existsSync(snapshotPath)) {
|
|
33777
|
+
return JSON.parse(fs31.readFileSync(snapshotPath, "utf-8"));
|
|
32193
33778
|
}
|
|
32194
33779
|
} catch {
|
|
32195
33780
|
}
|
|
@@ -32345,6 +33930,7 @@ function buildContextForClaude(state, alerts, businessContext) {
|
|
|
32345
33930
|
})),
|
|
32346
33931
|
business_intelligence: readBusinessIntelFreshness(),
|
|
32347
33932
|
payments: readPaymentsFreshness(),
|
|
33933
|
+
posthog: readPosthogFreshness(),
|
|
32348
33934
|
social: readSocialFreshness(),
|
|
32349
33935
|
promo_copy: readPromoCopyFreshness(),
|
|
32350
33936
|
promo_video: readPromoVideoFreshness(),
|
|
@@ -32408,7 +33994,7 @@ async function runClaudeReasoning(state, alerts, businessContext) {
|
|
|
32408
33994
|
LEARNINGS FROM PREVIOUS SESSIONS:
|
|
32409
33995
|
${memoryContext}
|
|
32410
33996
|
` : "";
|
|
32411
|
-
const prompt = `You are the AI
|
|
33997
|
+
const prompt = `You are the AI Product Manager for ${productName}${productSummary}.
|
|
32412
33998
|
|
|
32413
33999
|
CURRENT STATE:
|
|
32414
34000
|
${JSON.stringify(context, null, 2)}
|
|
@@ -32536,7 +34122,7 @@ When to recommend payments tasks:
|
|
|
32536
34122
|
4. Payments setup is a prerequisite for any monetization/subscription features
|
|
32537
34123
|
|
|
32538
34124
|
SOCIAL MEDIA TASKS:
|
|
32539
|
-
The "social" field in CURRENT STATE shows Twitter/X connection status and
|
|
34125
|
+
The "social" field in CURRENT STATE shows Twitter/X connection status, draft activity, and engagement metrics.
|
|
32540
34126
|
These tasks generate tweet drafts for the founder's build-in-public journey.
|
|
32541
34127
|
|
|
32542
34128
|
Task prefixes:
|
|
@@ -32546,17 +34132,24 @@ Task prefixes:
|
|
|
32546
34132
|
- "social-draft-update" \u2192 Generate a product update draft from positioning data
|
|
32547
34133
|
- "social-draft-milestone" \u2192 Generate a milestone draft from a KPI that recently hit its target
|
|
32548
34134
|
- "social-draft-digest" \u2192 Generate a weekly summary draft
|
|
32549
|
-
- "social-
|
|
34135
|
+
- "social-draft-viral-{type}" \u2192 AI-generated viral tweet (type: ship|milestone|update|insight|question)
|
|
34136
|
+
- "social-draft-thread-{topic}" \u2192 AI-generated thread (5-7 tweets) \u2014 threads get 200-300% more reach
|
|
34137
|
+
- "social-track-metrics" \u2192 Fetch engagement data for recent published tweets
|
|
34138
|
+
- "social-score-{draftId}" \u2192 Score a draft for viral potential (0-100) with AI feedback
|
|
34139
|
+
- "social-check-status" \u2192 Audit: credentials valid? Draft count? Post streak? Engagement?
|
|
32550
34140
|
|
|
32551
34141
|
When to recommend social tasks:
|
|
32552
34142
|
1. If social.configured == false AND "Build in Public" goal exists \u2192 suggest "social-setup-x"
|
|
32553
34143
|
2. If an idea was just shipped AND social.configured == true:
|
|
32554
34144
|
- If ship card exists (data/reports/visuals/{ideaId}-card.png) \u2192 prefer "social-draft-ship-visual" (tweets with images get 2-3x engagement)
|
|
32555
|
-
- Otherwise \u2192
|
|
32556
|
-
3. If a KPI just hit target \u2192 suggest "social-draft-milestone"
|
|
34145
|
+
- Otherwise \u2192 prefer "social-draft-viral-ship" over "social-draft-ship" (AI-generated is higher quality)
|
|
34146
|
+
3. If a KPI just hit target \u2192 suggest "social-draft-viral-milestone"
|
|
32557
34147
|
4. If it's been 7+ days since last digest AND there's recent activity \u2192 suggest "social-draft-digest"
|
|
32558
34148
|
5. If social.draft_count > 0 \u2192 mention that drafts are waiting for review in /social
|
|
32559
|
-
6. If social.post_streak_days > 0 AND no post today \u2192 suggest "social-draft-
|
|
34149
|
+
6. If social.post_streak_days > 0 AND no post today \u2192 suggest "social-draft-viral-insight" to maintain streak
|
|
34150
|
+
7. If social.total_posts > 0 AND social.avg_engagement_rate == null \u2192 suggest "social-track-metrics" to start tracking
|
|
34151
|
+
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
|
+
9. Prefer AI-generated drafts (social-draft-viral-*) over template drafts (social-draft-ship/update/milestone) for higher engagement
|
|
32560
34153
|
|
|
32561
34154
|
PROMO COPY TASKS:
|
|
32562
34155
|
The "promo_copy" field shows positioning-driven copy generation status.
|
|
@@ -32669,7 +34262,7 @@ Respond with JSON only (no markdown code blocks, just raw JSON):
|
|
|
32669
34262
|
|
|
32670
34263
|
If there's nothing urgent, set next_action to null and new_todos to an empty array.
|
|
32671
34264
|
Focus on actionable, specific recommendations. Be concise.`;
|
|
32672
|
-
return new Promise((
|
|
34265
|
+
return new Promise((resolve2, reject) => {
|
|
32673
34266
|
const startTime = Date.now();
|
|
32674
34267
|
const promptSize = prompt.length;
|
|
32675
34268
|
log(`Running Claude reasoning (prompt: ${promptSize} chars)...`);
|
|
@@ -32703,7 +34296,7 @@ Focus on actionable, specific recommendations. Be concise.`;
|
|
|
32703
34296
|
},
|
|
32704
34297
|
required: ["analysis", "next_action"]
|
|
32705
34298
|
});
|
|
32706
|
-
const claude = (0,
|
|
34299
|
+
const claude = (0, import_child_process10.spawn)("claude", [
|
|
32707
34300
|
"--print",
|
|
32708
34301
|
"--output-format",
|
|
32709
34302
|
"json",
|
|
@@ -32734,7 +34327,7 @@ Focus on actionable, specific recommendations. Be concise.`;
|
|
|
32734
34327
|
claude.kill("SIGTERM");
|
|
32735
34328
|
const elapsed = Math.round((Date.now() - startTime) / 1e3);
|
|
32736
34329
|
log(`Claude reasoning timed out after ${elapsed}s`);
|
|
32737
|
-
|
|
34330
|
+
resolve2(null);
|
|
32738
34331
|
}
|
|
32739
34332
|
}, TIMEOUT_MS);
|
|
32740
34333
|
claude.stdout.on("data", (data) => {
|
|
@@ -32761,14 +34354,14 @@ Focus on actionable, specific recommendations. Be concise.`;
|
|
|
32761
34354
|
const result = parseCliJsonOutput(output, "Reasoning");
|
|
32762
34355
|
if (result) {
|
|
32763
34356
|
log(`Claude completed in ${elapsed}s: ${result.analysis}`);
|
|
32764
|
-
|
|
34357
|
+
resolve2(result);
|
|
32765
34358
|
} else {
|
|
32766
34359
|
log(`Claude completed in ${elapsed}s but no valid JSON found`);
|
|
32767
|
-
|
|
34360
|
+
resolve2(null);
|
|
32768
34361
|
}
|
|
32769
34362
|
} else {
|
|
32770
34363
|
log(`Claude exited with code ${code} after ${elapsed}s: ${errorOutput}`);
|
|
32771
|
-
|
|
34364
|
+
resolve2(null);
|
|
32772
34365
|
}
|
|
32773
34366
|
});
|
|
32774
34367
|
claude.on("error", (error) => {
|
|
@@ -32777,15 +34370,15 @@ Focus on actionable, specific recommendations. Be concise.`;
|
|
|
32777
34370
|
clearInterval(progressInterval);
|
|
32778
34371
|
clearTimeout(timeout);
|
|
32779
34372
|
log(`Claude spawn error: ${error.message}`);
|
|
32780
|
-
|
|
34373
|
+
resolve2(null);
|
|
32781
34374
|
});
|
|
32782
34375
|
});
|
|
32783
34376
|
}
|
|
32784
34377
|
function addTasksToTodo(tasks) {
|
|
32785
34378
|
if (tasks.length === 0) return;
|
|
32786
|
-
if (!
|
|
34379
|
+
if (!fs31.existsSync(TODO_FILE)) return;
|
|
32787
34380
|
try {
|
|
32788
|
-
let content =
|
|
34381
|
+
let content = fs31.readFileSync(TODO_FILE, "utf-8");
|
|
32789
34382
|
if (!content.includes("## High Priority (Do Now)")) {
|
|
32790
34383
|
log('TODO.md missing "## High Priority (Do Now)" section \u2014 creating it');
|
|
32791
34384
|
const scheduledIdx = content.indexOf("## Scheduled");
|
|
@@ -32873,15 +34466,15 @@ function addTasksToTodo(tasks) {
|
|
|
32873
34466
|
}
|
|
32874
34467
|
log(`Added TODO: ${task.id} \u2014 ${task.description}`);
|
|
32875
34468
|
}
|
|
32876
|
-
|
|
34469
|
+
fs31.writeFileSync(TODO_FILE, content);
|
|
32877
34470
|
} catch (error) {
|
|
32878
34471
|
log(`Failed to add tasks to TODO.md: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
32879
34472
|
}
|
|
32880
34473
|
}
|
|
32881
34474
|
function archiveCompletedTodos() {
|
|
32882
34475
|
try {
|
|
32883
|
-
if (!
|
|
32884
|
-
let content =
|
|
34476
|
+
if (!fs31.existsSync(TODO_FILE)) return;
|
|
34477
|
+
let content = fs31.readFileSync(TODO_FILE, "utf-8");
|
|
32885
34478
|
const lines = content.split("\n");
|
|
32886
34479
|
const completedLines = [];
|
|
32887
34480
|
const newLines = [];
|
|
@@ -32907,7 +34500,7 @@ function archiveCompletedTodos() {
|
|
|
32907
34500
|
const archiveBlock = completedLines.join("\n") + "\n";
|
|
32908
34501
|
content = content.slice(0, insertIndex) + archiveBlock + content.slice(insertIndex);
|
|
32909
34502
|
}
|
|
32910
|
-
|
|
34503
|
+
fs31.writeFileSync(TODO_FILE, content);
|
|
32911
34504
|
log(`Archived ${completedLines.length} completed TODO(s) to Completed This Week`);
|
|
32912
34505
|
} catch (error) {
|
|
32913
34506
|
log(`Failed to archive completed TODOs: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
@@ -32915,8 +34508,8 @@ function archiveCompletedTodos() {
|
|
|
32915
34508
|
}
|
|
32916
34509
|
function pruneStaleScheduledTodos(state) {
|
|
32917
34510
|
try {
|
|
32918
|
-
if (!
|
|
32919
|
-
const content =
|
|
34511
|
+
if (!fs31.existsSync(TODO_FILE)) return;
|
|
34512
|
+
const content = fs31.readFileSync(TODO_FILE, "utf-8");
|
|
32920
34513
|
const lines = content.split("\n");
|
|
32921
34514
|
const prunedIds = [];
|
|
32922
34515
|
const terminalIdeaIds = new Set(
|
|
@@ -32943,7 +34536,7 @@ function pruneStaleScheduledTodos(state) {
|
|
|
32943
34536
|
return true;
|
|
32944
34537
|
});
|
|
32945
34538
|
if (prunedIds.length === 0) return;
|
|
32946
|
-
|
|
34539
|
+
fs31.writeFileSync(TODO_FILE, newLines.join("\n"));
|
|
32947
34540
|
log(`Pruned ${prunedIds.length} stale TODO(s): ${prunedIds.join(", ")}`);
|
|
32948
34541
|
} catch (error) {
|
|
32949
34542
|
log(`Failed to prune stale TODOs: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
@@ -32951,8 +34544,8 @@ function pruneStaleScheduledTodos(state) {
|
|
|
32951
34544
|
}
|
|
32952
34545
|
function deduplicateActiveTodos() {
|
|
32953
34546
|
try {
|
|
32954
|
-
if (!
|
|
32955
|
-
const content =
|
|
34547
|
+
if (!fs31.existsSync(TODO_FILE)) return;
|
|
34548
|
+
const content = fs31.readFileSync(TODO_FILE, "utf-8");
|
|
32956
34549
|
const lines = content.split("\n");
|
|
32957
34550
|
const removedIds = [];
|
|
32958
34551
|
let currentSection = "";
|
|
@@ -32989,7 +34582,7 @@ function deduplicateActiveTodos() {
|
|
|
32989
34582
|
if (linesToRemove.size === 0) return;
|
|
32990
34583
|
const newLines = lines.filter((_, i) => !linesToRemove.has(i));
|
|
32991
34584
|
const cleaned = newLines.join("\n").replace(/\n{3,}/g, "\n\n");
|
|
32992
|
-
|
|
34585
|
+
fs31.writeFileSync(TODO_FILE, cleaned);
|
|
32993
34586
|
log(`Deduplicated ${removedIds.length} TODO(s): ${removedIds.join(", ")}`);
|
|
32994
34587
|
} catch (error) {
|
|
32995
34588
|
log(`Failed to deduplicate TODOs: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
@@ -32997,8 +34590,8 @@ function deduplicateActiveTodos() {
|
|
|
32997
34590
|
}
|
|
32998
34591
|
function readMemoryContext() {
|
|
32999
34592
|
try {
|
|
33000
|
-
if (!
|
|
33001
|
-
const content =
|
|
34593
|
+
if (!fs31.existsSync(MEMORY_FILE)) return "";
|
|
34594
|
+
const content = fs31.readFileSync(MEMORY_FILE, "utf-8");
|
|
33002
34595
|
const MAX_TOTAL_LINES = 100;
|
|
33003
34596
|
const MIN_LEARNING_ENTRIES = 15;
|
|
33004
34597
|
const learningsSplit = content.indexOf("### Heartbeat Learnings");
|
|
@@ -33019,14 +34612,14 @@ function readMemoryContext() {
|
|
|
33019
34612
|
}
|
|
33020
34613
|
function appendToMemory(learnings) {
|
|
33021
34614
|
if (learnings.length === 0) return;
|
|
33022
|
-
if (!
|
|
34615
|
+
if (!fs31.existsSync(MEMORY_FILE)) return;
|
|
33023
34616
|
const MAX_LEARNINGS_PER_HEARTBEAT = 2;
|
|
33024
34617
|
let filtered = learnings.slice(0, MAX_LEARNINGS_PER_HEARTBEAT);
|
|
33025
34618
|
if (learnings.length > MAX_LEARNINGS_PER_HEARTBEAT) {
|
|
33026
34619
|
log(`Capped learnings from ${learnings.length} to ${MAX_LEARNINGS_PER_HEARTBEAT}`);
|
|
33027
34620
|
}
|
|
33028
34621
|
try {
|
|
33029
|
-
let content =
|
|
34622
|
+
let content = fs31.readFileSync(MEMORY_FILE, "utf-8");
|
|
33030
34623
|
const existingLines = content.split("\n").filter((l2) => l2.match(/^- \*\*/));
|
|
33031
34624
|
const KEY_PHRASE_PATTERN = /(?:idea-[a-f0-9]{8}|idea-[a-z0-9-]+|hyp-[a-z0-9-]+|goal-[a-z0-9-]+|pipeline starvation|critical path|batch triage|cooldown|decompos|timeout|inbox bloat|dedup|meta-task|heartbeat|autonomy gate|false completion)/gi;
|
|
33032
34625
|
filtered = filtered.filter((learning) => {
|
|
@@ -33076,7 +34669,7 @@ function appendToMemory(learnings) {
|
|
|
33076
34669
|
const subsectionEnd = subsectionEndMatch ? subsectionIndex + 22 + (subsectionEndMatch.index || 0) : insertPoint;
|
|
33077
34670
|
content = content.slice(0, subsectionEnd) + newEntries + "\n" + content.slice(subsectionEnd);
|
|
33078
34671
|
}
|
|
33079
|
-
|
|
34672
|
+
fs31.writeFileSync(MEMORY_FILE, content);
|
|
33080
34673
|
log(`Added ${filtered.length} learning(s) to MEMORY.md`);
|
|
33081
34674
|
}
|
|
33082
34675
|
} catch (error) {
|
|
@@ -33085,8 +34678,8 @@ function appendToMemory(learnings) {
|
|
|
33085
34678
|
}
|
|
33086
34679
|
function pruneMemoryLearnings() {
|
|
33087
34680
|
try {
|
|
33088
|
-
if (!
|
|
33089
|
-
const content =
|
|
34681
|
+
if (!fs31.existsSync(MEMORY_FILE)) return;
|
|
34682
|
+
const content = fs31.readFileSync(MEMORY_FILE, "utf-8");
|
|
33090
34683
|
const learningsHeader = "### Heartbeat Learnings";
|
|
33091
34684
|
const learningsIdx = content.indexOf(learningsHeader);
|
|
33092
34685
|
if (learningsIdx === -1) return;
|
|
@@ -33122,7 +34715,7 @@ function pruneMemoryLearnings() {
|
|
|
33122
34715
|
if (prunedCount === 0) return;
|
|
33123
34716
|
const newLearningsBody = headerLine + "\n\n" + finalEntries.join("\n") + "\n";
|
|
33124
34717
|
const newContent = beforeLearnings + newLearningsBody + afterLearnings;
|
|
33125
|
-
|
|
34718
|
+
fs31.writeFileSync(MEMORY_FILE, newContent);
|
|
33126
34719
|
log(`Pruned ${prunedCount} duplicate/stale learnings (${entryLines.length} \u2192 ${finalEntries.length})`);
|
|
33127
34720
|
} catch (error) {
|
|
33128
34721
|
log(`Failed to prune memory learnings: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
@@ -33238,7 +34831,7 @@ function detectTargetRepo(idea, config) {
|
|
|
33238
34831
|
const filesAnalyzed = idea.source.files_analyzed || [];
|
|
33239
34832
|
for (const file of filesAnalyzed) {
|
|
33240
34833
|
for (const repo of config.repos) {
|
|
33241
|
-
if (file.includes(repo.name) || file.includes(
|
|
34834
|
+
if (file.includes(repo.name) || file.includes(path26.basename(repo.path))) {
|
|
33242
34835
|
return repo;
|
|
33243
34836
|
}
|
|
33244
34837
|
}
|
|
@@ -33412,7 +35005,7 @@ Respond with JSON only (no markdown code blocks, just raw JSON):
|
|
|
33412
35005
|
}
|
|
33413
35006
|
]
|
|
33414
35007
|
}`;
|
|
33415
|
-
return new Promise((
|
|
35008
|
+
return new Promise((resolve2) => {
|
|
33416
35009
|
const TIMEOUT_MS = config.autonomy?.max_sub_task_timeout_ms || 6e5;
|
|
33417
35010
|
log(`Decomposing idea into sub-tasks via Claude reasoning (timeout: ${Math.round(TIMEOUT_MS / 1e3)}s)...`);
|
|
33418
35011
|
const startTime = Date.now();
|
|
@@ -33436,7 +35029,7 @@ Respond with JSON only (no markdown code blocks, just raw JSON):
|
|
|
33436
35029
|
},
|
|
33437
35030
|
required: ["sub_tasks"]
|
|
33438
35031
|
});
|
|
33439
|
-
const claude = (0,
|
|
35032
|
+
const claude = (0, import_child_process10.spawn)("claude", [
|
|
33440
35033
|
"--print",
|
|
33441
35034
|
"--output-format",
|
|
33442
35035
|
"json",
|
|
@@ -33462,7 +35055,7 @@ Respond with JSON only (no markdown code blocks, just raw JSON):
|
|
|
33462
35055
|
claude.kill("SIGTERM");
|
|
33463
35056
|
const elapsed = Math.round((Date.now() - startTime) / 1e3);
|
|
33464
35057
|
log(`Decomposition timed out after ${elapsed}s (limit: ${Math.round(TIMEOUT_MS / 1e3)}s)`);
|
|
33465
|
-
|
|
35058
|
+
resolve2([]);
|
|
33466
35059
|
}
|
|
33467
35060
|
}, TIMEOUT_MS);
|
|
33468
35061
|
claude.stdout.on("data", (data) => {
|
|
@@ -33489,18 +35082,18 @@ Respond with JSON only (no markdown code blocks, just raw JSON):
|
|
|
33489
35082
|
commit_hash: null
|
|
33490
35083
|
}));
|
|
33491
35084
|
log(`Decomposed into ${subTasks.length} sub-tasks`);
|
|
33492
|
-
|
|
35085
|
+
resolve2(subTasks);
|
|
33493
35086
|
return;
|
|
33494
35087
|
}
|
|
33495
35088
|
}
|
|
33496
|
-
|
|
35089
|
+
resolve2([]);
|
|
33497
35090
|
});
|
|
33498
35091
|
claude.on("error", () => {
|
|
33499
35092
|
if (!isResolved) {
|
|
33500
35093
|
isResolved = true;
|
|
33501
35094
|
clearTimeout(timeout);
|
|
33502
35095
|
clearInterval(progressInterval);
|
|
33503
|
-
|
|
35096
|
+
resolve2([]);
|
|
33504
35097
|
}
|
|
33505
35098
|
});
|
|
33506
35099
|
});
|
|
@@ -33566,17 +35159,17 @@ function runTestsForRepo(workspacePath, testCommands, sourceRepoPath) {
|
|
|
33566
35159
|
const allOutput = [];
|
|
33567
35160
|
const env = { ...process.env };
|
|
33568
35161
|
if (sourceRepoPath) {
|
|
33569
|
-
const venvBin =
|
|
33570
|
-
if (
|
|
35162
|
+
const venvBin = path26.join(sourceRepoPath, ".venv", "bin");
|
|
35163
|
+
if (fs31.existsSync(venvBin)) {
|
|
33571
35164
|
env.PATH = `${venvBin}:${env.PATH}`;
|
|
33572
|
-
env.VIRTUAL_ENV =
|
|
35165
|
+
env.VIRTUAL_ENV = path26.join(sourceRepoPath, ".venv");
|
|
33573
35166
|
log(`Using venv from ${sourceRepoPath}/.venv`);
|
|
33574
35167
|
}
|
|
33575
35168
|
}
|
|
33576
35169
|
for (const cmd of testCommands) {
|
|
33577
35170
|
log(`Running test: ${cmd}`);
|
|
33578
35171
|
try {
|
|
33579
|
-
const result = (0,
|
|
35172
|
+
const result = (0, import_child_process10.execSync)(cmd, {
|
|
33580
35173
|
cwd: workspacePath,
|
|
33581
35174
|
encoding: "utf-8",
|
|
33582
35175
|
env,
|
|
@@ -33622,7 +35215,7 @@ async function executeSingleSubTask(subTask, ideaId, idea, ideasData, autonomy,
|
|
|
33622
35215
|
});
|
|
33623
35216
|
const scopeBase64 = Buffer.from(scopePayload).toString("base64");
|
|
33624
35217
|
const timeoutMs = autonomy.max_sub_task_timeout_ms || 3e5;
|
|
33625
|
-
const workspacePath = options?.worktreePath ||
|
|
35218
|
+
const workspacePath = options?.worktreePath || path26.join(config.workspace_dir, targetRepo.name);
|
|
33626
35219
|
const executionStartTime = Date.now();
|
|
33627
35220
|
const updateSubTaskWithDetails = (status, updateOpts = {}) => {
|
|
33628
35221
|
const endTime = Date.now();
|
|
@@ -33700,7 +35293,7 @@ async function executeSingleSubTask(subTask, ideaId, idea, ideasData, autonomy,
|
|
|
33700
35293
|
}
|
|
33701
35294
|
let commitHash = null;
|
|
33702
35295
|
try {
|
|
33703
|
-
commitHash = (0,
|
|
35296
|
+
commitHash = (0, import_child_process10.execSync)("git rev-parse HEAD", { cwd: workspacePath, encoding: "utf-8" }).trim();
|
|
33704
35297
|
} catch {
|
|
33705
35298
|
}
|
|
33706
35299
|
updateSubTaskWithDetails("completed", {
|
|
@@ -33744,7 +35337,7 @@ async function analyzeAndRetrySubTask(subTask, ideaId, maxRetries) {
|
|
|
33744
35337
|
"Be specific about what went wrong and how to fix it. Include any workarounds needed."
|
|
33745
35338
|
].filter(Boolean).join("\n");
|
|
33746
35339
|
try {
|
|
33747
|
-
const spawnResult = (0,
|
|
35340
|
+
const spawnResult = (0, import_child_process10.spawnSync)("claude", ["--print", "--model", "sonnet", "-p", errorContext], {
|
|
33748
35341
|
cwd: ROOT_DIR,
|
|
33749
35342
|
encoding: "utf-8",
|
|
33750
35343
|
timeout: 6e4,
|
|
@@ -33841,7 +35434,7 @@ async function executeAutonomousImplementation(ideaId) {
|
|
|
33841
35434
|
}
|
|
33842
35435
|
try {
|
|
33843
35436
|
const { command, args: args2 } = resolveScript(__dirname, "implement.ts");
|
|
33844
|
-
(0,
|
|
35437
|
+
(0, import_child_process10.execSync)(
|
|
33845
35438
|
[command, ...args2, `--idea=${ideaId}`, `--repo=${targetRepo2.name}`, "--create-pr-only"].join(" "),
|
|
33846
35439
|
{ cwd: ROOT_DIR, encoding: "utf-8", stdio: "inherit", timeout: 12e4 }
|
|
33847
35440
|
);
|
|
@@ -33849,16 +35442,16 @@ async function executeAutonomousImplementation(ideaId) {
|
|
|
33849
35442
|
if (autonomy.auto_merge) {
|
|
33850
35443
|
const branchName2 = idea.implementation.branch_name;
|
|
33851
35444
|
const defaultBranch2 = targetRepo2.default_branch || "main";
|
|
33852
|
-
const workspacePath =
|
|
35445
|
+
const workspacePath = path26.join(config.workspace_dir, targetRepo2.name);
|
|
33853
35446
|
if (branchName2) {
|
|
33854
35447
|
try {
|
|
33855
35448
|
log(`Auto-merging branch ${branchName2} into ${defaultBranch2}...`);
|
|
33856
|
-
(0,
|
|
33857
|
-
(0,
|
|
33858
|
-
(0,
|
|
35449
|
+
(0, import_child_process10.execSync)(`git checkout ${defaultBranch2}`, { cwd: workspacePath, encoding: "utf-8", stdio: "pipe" });
|
|
35450
|
+
(0, import_child_process10.execSync)(`git merge ${branchName2} --squash --no-edit`, { cwd: workspacePath, encoding: "utf-8", stdio: "pipe" });
|
|
35451
|
+
(0, import_child_process10.execSync)(`git commit -m "feat: ${idea.title} (auto-merged from ${branchName2})"`, { cwd: workspacePath, encoding: "utf-8", stdio: "pipe" });
|
|
33859
35452
|
log(`Successfully merged ${branchName2} into ${defaultBranch2}`);
|
|
33860
35453
|
try {
|
|
33861
|
-
(0,
|
|
35454
|
+
(0, import_child_process10.execSync)(`git branch -d ${branchName2}`, { cwd: workspacePath, encoding: "utf-8", stdio: "pipe" });
|
|
33862
35455
|
log(`Deleted branch ${branchName2}`);
|
|
33863
35456
|
} catch {
|
|
33864
35457
|
log(`Could not delete branch ${branchName2} (may need force delete)`);
|
|
@@ -33883,8 +35476,8 @@ async function executeAutonomousImplementation(ideaId) {
|
|
|
33883
35476
|
} catch (mergeError) {
|
|
33884
35477
|
log(`Auto-merge failed (likely conflicts): ${mergeError instanceof Error ? mergeError.message : "Unknown"}`);
|
|
33885
35478
|
try {
|
|
33886
|
-
(0,
|
|
33887
|
-
(0,
|
|
35479
|
+
(0, import_child_process10.execSync)(`git merge --abort`, { cwd: workspacePath, encoding: "utf-8", stdio: "pipe" });
|
|
35480
|
+
(0, import_child_process10.execSync)(`git checkout ${branchName2}`, { cwd: workspacePath, encoding: "utf-8", stdio: "pipe" });
|
|
33888
35481
|
} catch {
|
|
33889
35482
|
}
|
|
33890
35483
|
log(`Left idea ${ideaId} in testing stage for manual merge`);
|
|
@@ -34069,7 +35662,7 @@ Do PARTIAL work \u2014 process only the first 10-15 items, then stop.
|
|
|
34069
35662
|
At the end, add a follow-up TODO to TODO.md for the remaining work using the format:
|
|
34070
35663
|
- [ ] \`${taskId}-continued\` \u2014 Continue: [describe remaining work]
|
|
34071
35664
|
` : "";
|
|
34072
|
-
const prompt = `You are the AI
|
|
35665
|
+
const prompt = `You are the AI Product Manager for ${metaProductName}.
|
|
34073
35666
|
|
|
34074
35667
|
## Task: ${taskId}
|
|
34075
35668
|
|
|
@@ -34090,8 +35683,8 @@ ${allowedFiles.map((f) => `- ${f}`).join("\n")}
|
|
|
34090
35683
|
6. When modifying TODO.md, preserve the markdown checkbox format: - [ ] or - [x]
|
|
34091
35684
|
7. Be direct \u2014 read the file, make the edit, done
|
|
34092
35685
|
`;
|
|
34093
|
-
return new Promise((
|
|
34094
|
-
const claude = (0,
|
|
35686
|
+
return new Promise((resolve2) => {
|
|
35687
|
+
const claude = (0, import_child_process10.spawn)("claude", [
|
|
34095
35688
|
"--print",
|
|
34096
35689
|
"--dangerously-skip-permissions",
|
|
34097
35690
|
"--model",
|
|
@@ -34107,7 +35700,7 @@ ${allowedFiles.map((f) => `- ${f}`).join("\n")}
|
|
|
34107
35700
|
});
|
|
34108
35701
|
let output = "";
|
|
34109
35702
|
let isResolved = false;
|
|
34110
|
-
const timeoutMs = failureCount > 0 ?
|
|
35703
|
+
const timeoutMs = failureCount > 0 ? 12e5 : 9e5;
|
|
34111
35704
|
const timeout = setTimeout(() => {
|
|
34112
35705
|
if (!isResolved) {
|
|
34113
35706
|
isResolved = true;
|
|
@@ -34119,7 +35712,7 @@ ${allowedFiles.map((f) => `- ${f}`).join("\n")}
|
|
|
34119
35712
|
} catch {
|
|
34120
35713
|
}
|
|
34121
35714
|
}, 5e3);
|
|
34122
|
-
|
|
35715
|
+
resolve2(false);
|
|
34123
35716
|
}
|
|
34124
35717
|
}, timeoutMs);
|
|
34125
35718
|
claude.stdout.on("data", (data) => {
|
|
@@ -34137,14 +35730,14 @@ ${allowedFiles.map((f) => `- ${f}`).join("\n")}
|
|
|
34137
35730
|
log(`Meta-task false completion detected: ${taskId} \u2014 output indicates blocked state`);
|
|
34138
35731
|
log(` Output: ${output.substring(0, 200)}`);
|
|
34139
35732
|
reclassifyAsHumanDependent(taskId);
|
|
34140
|
-
|
|
35733
|
+
resolve2(false);
|
|
34141
35734
|
return;
|
|
34142
35735
|
}
|
|
34143
35736
|
log(`Meta-task completed: ${output.substring(0, 200)}`);
|
|
34144
|
-
|
|
35737
|
+
resolve2(true);
|
|
34145
35738
|
} else {
|
|
34146
35739
|
log(`Meta-task failed (exit ${code})`);
|
|
34147
|
-
|
|
35740
|
+
resolve2(false);
|
|
34148
35741
|
}
|
|
34149
35742
|
});
|
|
34150
35743
|
claude.on("error", (err2) => {
|
|
@@ -34152,7 +35745,7 @@ ${allowedFiles.map((f) => `- ${f}`).join("\n")}
|
|
|
34152
35745
|
isResolved = true;
|
|
34153
35746
|
clearTimeout(timeout);
|
|
34154
35747
|
log(`Meta-task error: ${err2.message}`);
|
|
34155
|
-
|
|
35748
|
+
resolve2(false);
|
|
34156
35749
|
});
|
|
34157
35750
|
});
|
|
34158
35751
|
}
|
|
@@ -34174,9 +35767,9 @@ ${kpiLines}`;
|
|
|
34174
35767
|
goals: goalsContext
|
|
34175
35768
|
});
|
|
34176
35769
|
const contextBase64 = Buffer.from(contextPayload).toString("base64");
|
|
34177
|
-
return new Promise((
|
|
35770
|
+
return new Promise((resolve2) => {
|
|
34178
35771
|
const { command: analyzeCmd, args: analyzeArgs } = resolveScript(__dirname, "analyze.ts");
|
|
34179
|
-
const childProcess = (0,
|
|
35772
|
+
const childProcess = (0, import_child_process10.spawn)(analyzeCmd, [
|
|
34180
35773
|
...analyzeArgs,
|
|
34181
35774
|
`--type=research`,
|
|
34182
35775
|
`--topic=${topic}`,
|
|
@@ -34188,22 +35781,22 @@ ${kpiLines}`;
|
|
|
34188
35781
|
const timeout = setTimeout(() => {
|
|
34189
35782
|
childProcess.kill("SIGTERM");
|
|
34190
35783
|
log("Research task timed out after 15 minutes");
|
|
34191
|
-
|
|
35784
|
+
resolve2(false);
|
|
34192
35785
|
}, 9e5);
|
|
34193
35786
|
childProcess.on("close", (code) => {
|
|
34194
35787
|
clearTimeout(timeout);
|
|
34195
35788
|
if (code === 0) {
|
|
34196
35789
|
log("Research task completed successfully");
|
|
34197
|
-
|
|
35790
|
+
resolve2(true);
|
|
34198
35791
|
} else {
|
|
34199
35792
|
log(`Research task failed with code ${code}`);
|
|
34200
|
-
|
|
35793
|
+
resolve2(false);
|
|
34201
35794
|
}
|
|
34202
35795
|
});
|
|
34203
35796
|
childProcess.on("error", (error) => {
|
|
34204
35797
|
clearTimeout(timeout);
|
|
34205
35798
|
log(`Research task error: ${error.message}`);
|
|
34206
|
-
|
|
35799
|
+
resolve2(false);
|
|
34207
35800
|
});
|
|
34208
35801
|
});
|
|
34209
35802
|
}
|
|
@@ -34337,10 +35930,10 @@ async function executeGoalGapResearch(goalId) {
|
|
|
34337
35930
|
const businessContext = loadBusinessContext();
|
|
34338
35931
|
const prompt = buildGoalGapPrompt(goal, existingIdeas, liveKPIs, config, businessContext);
|
|
34339
35932
|
log(`Goal-gap prompt built (${prompt.length} chars), spawning Claude Code...`);
|
|
34340
|
-
return new Promise((
|
|
35933
|
+
return new Promise((resolve2) => {
|
|
34341
35934
|
const TIMEOUT_MS = 9e5;
|
|
34342
35935
|
const startTime = Date.now();
|
|
34343
|
-
const claude = (0,
|
|
35936
|
+
const claude = (0, import_child_process10.spawn)("claude", [
|
|
34344
35937
|
"--print",
|
|
34345
35938
|
"--dangerously-skip-permissions"
|
|
34346
35939
|
], {
|
|
@@ -34364,7 +35957,7 @@ async function executeGoalGapResearch(goalId) {
|
|
|
34364
35957
|
clearInterval(progressInterval);
|
|
34365
35958
|
claude.kill("SIGTERM");
|
|
34366
35959
|
log("Goal-gap research timed out after 15 minutes");
|
|
34367
|
-
|
|
35960
|
+
resolve2(false);
|
|
34368
35961
|
}
|
|
34369
35962
|
}, TIMEOUT_MS);
|
|
34370
35963
|
claude.stdout.on("data", (data) => {
|
|
@@ -34384,7 +35977,7 @@ async function executeGoalGapResearch(goalId) {
|
|
|
34384
35977
|
const elapsed = Math.round((Date.now() - startTime) / 1e3);
|
|
34385
35978
|
if (code !== 0) {
|
|
34386
35979
|
log(`Goal-gap research failed with code ${code} after ${elapsed}s`);
|
|
34387
|
-
|
|
35980
|
+
resolve2(false);
|
|
34388
35981
|
return;
|
|
34389
35982
|
}
|
|
34390
35983
|
log(`Goal-gap research completed in ${elapsed}s, parsing ideas...`);
|
|
@@ -34392,7 +35985,7 @@ async function executeGoalGapResearch(goalId) {
|
|
|
34392
35985
|
const jsonMatch = sanitized.match(/\[\s*\{[\s\S]*\}\s*\]/);
|
|
34393
35986
|
if (!jsonMatch) {
|
|
34394
35987
|
log("No JSON array found in goal-gap research response");
|
|
34395
|
-
|
|
35988
|
+
resolve2(false);
|
|
34396
35989
|
return;
|
|
34397
35990
|
}
|
|
34398
35991
|
try {
|
|
@@ -34434,7 +36027,7 @@ async function executeGoalGapResearch(goalId) {
|
|
|
34434
36027
|
}));
|
|
34435
36028
|
if (newIdeas.length === 0) {
|
|
34436
36029
|
log("No ideas parsed from goal-gap research");
|
|
34437
|
-
|
|
36030
|
+
resolve2(false);
|
|
34438
36031
|
return;
|
|
34439
36032
|
}
|
|
34440
36033
|
const freshIdeasData = loadJson(IDEAS_FILE, { ideas: [] });
|
|
@@ -34451,10 +36044,10 @@ async function executeGoalGapResearch(goalId) {
|
|
|
34451
36044
|
newIdeas.forEach((idea, i) => {
|
|
34452
36045
|
log(` ${i + 1}. ${idea.title}`);
|
|
34453
36046
|
});
|
|
34454
|
-
|
|
36047
|
+
resolve2(true);
|
|
34455
36048
|
} catch (error) {
|
|
34456
36049
|
log(`Failed to parse goal-gap research ideas: ${error instanceof Error ? error.message : "Unknown"}`);
|
|
34457
|
-
|
|
36050
|
+
resolve2(false);
|
|
34458
36051
|
}
|
|
34459
36052
|
});
|
|
34460
36053
|
claude.on("error", (error) => {
|
|
@@ -34463,7 +36056,7 @@ async function executeGoalGapResearch(goalId) {
|
|
|
34463
36056
|
clearInterval(progressInterval);
|
|
34464
36057
|
clearTimeout(timeout);
|
|
34465
36058
|
log(`Goal-gap research error: ${error.message}`);
|
|
34466
|
-
|
|
36059
|
+
resolve2(false);
|
|
34467
36060
|
}
|
|
34468
36061
|
});
|
|
34469
36062
|
});
|
|
@@ -34583,8 +36176,8 @@ async function executeShippedEvaluation(ideaId) {
|
|
|
34583
36176
|
const liveKPIs = await fetchLiveKPIs();
|
|
34584
36177
|
const prompt = buildEvaluationPrompt(idea, goal, liveKPIs, daysShipped);
|
|
34585
36178
|
const TIMEOUT_MS = 12e4;
|
|
34586
|
-
const result = await new Promise((
|
|
34587
|
-
const claude = (0,
|
|
36179
|
+
const result = await new Promise((resolve2) => {
|
|
36180
|
+
const claude = (0, import_child_process10.spawn)("claude", ["--print"], {
|
|
34588
36181
|
cwd: ROOT_DIR,
|
|
34589
36182
|
env: process.env,
|
|
34590
36183
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -34598,7 +36191,7 @@ async function executeShippedEvaluation(ideaId) {
|
|
|
34598
36191
|
isResolved = true;
|
|
34599
36192
|
claude.kill("SIGTERM");
|
|
34600
36193
|
log("Evaluation timed out");
|
|
34601
|
-
|
|
36194
|
+
resolve2(null);
|
|
34602
36195
|
}
|
|
34603
36196
|
}, TIMEOUT_MS);
|
|
34604
36197
|
claude.stdout.on("data", (data) => {
|
|
@@ -34612,17 +36205,17 @@ async function executeShippedEvaluation(ideaId) {
|
|
|
34612
36205
|
const parsed = robustJsonParse(output, "Evaluation");
|
|
34613
36206
|
if (parsed) {
|
|
34614
36207
|
log(`Evaluation complete: status=${parsed.status}, confidence=${parsed.confidence}`);
|
|
34615
|
-
|
|
36208
|
+
resolve2(parsed);
|
|
34616
36209
|
return;
|
|
34617
36210
|
}
|
|
34618
36211
|
}
|
|
34619
|
-
|
|
36212
|
+
resolve2(null);
|
|
34620
36213
|
});
|
|
34621
36214
|
claude.on("error", () => {
|
|
34622
36215
|
if (!isResolved) {
|
|
34623
36216
|
isResolved = true;
|
|
34624
36217
|
clearTimeout(timeout);
|
|
34625
|
-
|
|
36218
|
+
resolve2(null);
|
|
34626
36219
|
}
|
|
34627
36220
|
});
|
|
34628
36221
|
});
|
|
@@ -34981,8 +36574,8 @@ async function executeSingleResearchTask(task) {
|
|
|
34981
36574
|
log(` Starting research task: ${task.type} - ${task.topic}`);
|
|
34982
36575
|
task.status = "running";
|
|
34983
36576
|
task.started_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
34984
|
-
return new Promise((
|
|
34985
|
-
const claude = (0,
|
|
36577
|
+
return new Promise((resolve2) => {
|
|
36578
|
+
const claude = (0, import_child_process10.spawn)("claude", ["--print", "--dangerously-skip-permissions"], {
|
|
34986
36579
|
cwd: ROOT_DIR,
|
|
34987
36580
|
env: process.env,
|
|
34988
36581
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -34997,7 +36590,7 @@ async function executeSingleResearchTask(task) {
|
|
|
34997
36590
|
task.completed_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
34998
36591
|
task.error_message = "Timed out after 5 minutes";
|
|
34999
36592
|
log(` Research task timed out: ${task.type}`);
|
|
35000
|
-
|
|
36593
|
+
resolve2(task);
|
|
35001
36594
|
}, TIMEOUT_MS);
|
|
35002
36595
|
claude.stdout.on("data", (data) => {
|
|
35003
36596
|
output += data.toString();
|
|
@@ -35026,7 +36619,7 @@ async function executeSingleResearchTask(task) {
|
|
|
35026
36619
|
task.error_message = errorOutput || `Exited with code ${code}`;
|
|
35027
36620
|
log(` Research task failed: ${task.type} - ${task.error_message}`);
|
|
35028
36621
|
}
|
|
35029
|
-
|
|
36622
|
+
resolve2(task);
|
|
35030
36623
|
});
|
|
35031
36624
|
claude.on("error", (error) => {
|
|
35032
36625
|
clearTimeout(timeout);
|
|
@@ -35034,7 +36627,7 @@ async function executeSingleResearchTask(task) {
|
|
|
35034
36627
|
task.completed_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
35035
36628
|
task.error_message = error.message;
|
|
35036
36629
|
log(` Research task error: ${task.type} - ${error.message}`);
|
|
35037
|
-
|
|
36630
|
+
resolve2(task);
|
|
35038
36631
|
});
|
|
35039
36632
|
});
|
|
35040
36633
|
}
|
|
@@ -35092,8 +36685,8 @@ Respond with JSON only:
|
|
|
35092
36685
|
"recommendation": "proceed|revise|reject",
|
|
35093
36686
|
"recommendation_reasoning": "Why this recommendation"
|
|
35094
36687
|
}`;
|
|
35095
|
-
return new Promise((
|
|
35096
|
-
const claude = (0,
|
|
36688
|
+
return new Promise((resolve2) => {
|
|
36689
|
+
const claude = (0, import_child_process10.spawn)("claude", ["--print"], {
|
|
35097
36690
|
cwd: ROOT_DIR,
|
|
35098
36691
|
env: process.env,
|
|
35099
36692
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -35105,7 +36698,7 @@ Respond with JSON only:
|
|
|
35105
36698
|
claude.kill("SIGTERM");
|
|
35106
36699
|
research.summary = findingsSummary;
|
|
35107
36700
|
research.recommendation = "revise";
|
|
35108
|
-
|
|
36701
|
+
resolve2();
|
|
35109
36702
|
}, TIMEOUT_MS);
|
|
35110
36703
|
claude.stdout.on("data", (data) => {
|
|
35111
36704
|
output += data.toString();
|
|
@@ -35134,13 +36727,13 @@ ${parsed.risks_identified?.map((r) => `- ${r}`).join("\n") || "None identified"}
|
|
|
35134
36727
|
research.summary = findingsSummary;
|
|
35135
36728
|
research.recommendation = "revise";
|
|
35136
36729
|
}
|
|
35137
|
-
|
|
36730
|
+
resolve2();
|
|
35138
36731
|
});
|
|
35139
36732
|
claude.on("error", () => {
|
|
35140
36733
|
clearTimeout(timeout);
|
|
35141
36734
|
research.summary = findingsSummary;
|
|
35142
36735
|
research.recommendation = "revise";
|
|
35143
|
-
|
|
36736
|
+
resolve2();
|
|
35144
36737
|
});
|
|
35145
36738
|
});
|
|
35146
36739
|
}
|
|
@@ -35289,6 +36882,12 @@ async function executeTask(task, config, businessContext) {
|
|
|
35289
36882
|
trackEvent("heartbeat_task", { taskType: taskTypePrefix, success: result.success });
|
|
35290
36883
|
return result.success;
|
|
35291
36884
|
}
|
|
36885
|
+
if (isPosthogTask(taskId)) {
|
|
36886
|
+
const result = await executePosthogTask(taskId, taskDesc, businessContext);
|
|
36887
|
+
log(`PostHog task: ${result.success ? "SUCCESS" : "FAILED"} \u2014 ${result.output.slice(0, 200)}`);
|
|
36888
|
+
trackEvent("heartbeat_task", { taskType: taskTypePrefix, success: result.success });
|
|
36889
|
+
return result.success;
|
|
36890
|
+
}
|
|
35292
36891
|
if (isSocialTask(taskId)) {
|
|
35293
36892
|
const result = await executeSocialTask(taskId, taskDesc, businessContext);
|
|
35294
36893
|
log(`Social media task: ${result.success ? "SUCCESS" : "FAILED"} \u2014 ${result.output.slice(0, 200)}`);
|
|
@@ -35349,17 +36948,17 @@ async function executeReviewInbox() {
|
|
|
35349
36948
|
log("Inbox review complete (manual prioritization needed)");
|
|
35350
36949
|
}
|
|
35351
36950
|
async function runAnalysis(type) {
|
|
35352
|
-
return new Promise((
|
|
36951
|
+
return new Promise((resolve2, reject) => {
|
|
35353
36952
|
log(`Running ${type} analysis...`);
|
|
35354
36953
|
const { command: runCmd, args: runArgs } = resolveScript(__dirname, "analyze.ts");
|
|
35355
|
-
const childProcess = (0,
|
|
36954
|
+
const childProcess = (0, import_child_process10.spawn)(runCmd, [...runArgs, `--type=${type}`], {
|
|
35356
36955
|
cwd: ROOT_DIR,
|
|
35357
36956
|
stdio: "inherit"
|
|
35358
36957
|
});
|
|
35359
36958
|
childProcess.on("close", (code) => {
|
|
35360
36959
|
if (code === 0) {
|
|
35361
36960
|
log(`${type} analysis completed successfully`);
|
|
35362
|
-
|
|
36961
|
+
resolve2();
|
|
35363
36962
|
} else {
|
|
35364
36963
|
reject(new Error(`Analysis exited with code ${code}`));
|
|
35365
36964
|
}
|
|
@@ -35371,7 +36970,7 @@ async function runAnalysis(type) {
|
|
|
35371
36970
|
}
|
|
35372
36971
|
function updateTodoFile(taskId, completed) {
|
|
35373
36972
|
try {
|
|
35374
|
-
let content =
|
|
36973
|
+
let content = fs31.readFileSync(TODO_FILE, "utf-8");
|
|
35375
36974
|
const uncheckedPattern = new RegExp(`- \\[ \\] \`${taskId}\``, "g");
|
|
35376
36975
|
const checkedPattern = new RegExp(`- \\[x\\] \`${taskId}\``, "g");
|
|
35377
36976
|
if (completed) {
|
|
@@ -35379,7 +36978,7 @@ function updateTodoFile(taskId, completed) {
|
|
|
35379
36978
|
} else {
|
|
35380
36979
|
content = content.replace(checkedPattern, `- [ ] \`${taskId}\``);
|
|
35381
36980
|
}
|
|
35382
|
-
|
|
36981
|
+
fs31.writeFileSync(TODO_FILE, content);
|
|
35383
36982
|
log(`Updated TODO.md: ${taskId} marked as ${completed ? "completed" : "pending"}`);
|
|
35384
36983
|
} catch (error) {
|
|
35385
36984
|
log(`Failed to update TODO.md: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
@@ -35483,8 +37082,8 @@ async function runSingleHeartbeat(beatNumber = 1) {
|
|
|
35483
37082
|
if (vibeResult.warning) log(vibeResult.warning);
|
|
35484
37083
|
log(`Vibe consumed (${vibeResult.vibes_remaining} remaining${vibeResult.plan ? `, plan: ${vibeResult.plan}` : ""}${vibeResult.offline ? ", offline" : ""})`);
|
|
35485
37084
|
}
|
|
35486
|
-
const commandsDir =
|
|
35487
|
-
if (!
|
|
37085
|
+
const commandsDir = path26.join(PROJECT_DIR, ".claude", "commands");
|
|
37086
|
+
if (!fs31.existsSync(commandsDir)) {
|
|
35488
37087
|
log("Slash commands missing \u2014 auto-scaffolding...");
|
|
35489
37088
|
scaffoldSlashCommands(PROJECT_DIR);
|
|
35490
37089
|
}
|
|
@@ -35494,7 +37093,7 @@ async function runSingleHeartbeat(beatNumber = 1) {
|
|
|
35494
37093
|
if (SKIP_REASONING) {
|
|
35495
37094
|
log("Skipping Claude reasoning step");
|
|
35496
37095
|
}
|
|
35497
|
-
const state =
|
|
37096
|
+
const state = loadState4();
|
|
35498
37097
|
const businessContext = loadBusinessContext();
|
|
35499
37098
|
const heartbeatConfig = loadConfig();
|
|
35500
37099
|
log(`Loaded state: ${state.ideas.length} ideas, ${state.goals.length} goals, ${state.sessions.length} sessions`);
|
|
@@ -35542,71 +37141,84 @@ async function runSingleHeartbeat(beatNumber = 1) {
|
|
|
35542
37141
|
reasoning = await runClaudeReasoning(state, alerts, businessContext);
|
|
35543
37142
|
}
|
|
35544
37143
|
let taskToDo = null;
|
|
35545
|
-
|
|
35546
|
-
const
|
|
35547
|
-
|
|
35548
|
-
|
|
35549
|
-
|
|
35550
|
-
|
|
35551
|
-
|
|
35552
|
-
|
|
35553
|
-
|
|
35554
|
-
|
|
35555
|
-
|
|
35556
|
-
|
|
35557
|
-
|
|
35558
|
-
);
|
|
35559
|
-
if (
|
|
35560
|
-
|
|
35561
|
-
|
|
35562
|
-
|
|
35563
|
-
|
|
35564
|
-
|
|
35565
|
-
|
|
35566
|
-
}
|
|
37144
|
+
if (TASK_OVERRIDE) {
|
|
37145
|
+
const existing = state.todos.find((t) => t.id === TASK_OVERRIDE);
|
|
37146
|
+
taskToDo = existing || {
|
|
37147
|
+
id: TASK_OVERRIDE,
|
|
37148
|
+
description: TASK_DESCRIPTION || `Run task: ${TASK_OVERRIDE}`,
|
|
37149
|
+
completed: false,
|
|
37150
|
+
section: "high_priority",
|
|
37151
|
+
requires_human: false
|
|
37152
|
+
};
|
|
37153
|
+
log(`Task override: ${taskToDo.id} \u2014 ${taskToDo.description}`);
|
|
37154
|
+
}
|
|
37155
|
+
if (!TASK_OVERRIDE) {
|
|
37156
|
+
const inProgressOverride = (() => {
|
|
37157
|
+
const cfgCheck = loadConfig();
|
|
37158
|
+
if (!cfgCheck.autonomy?.enabled) return null;
|
|
37159
|
+
const maxRetries = cfgCheck.autonomy.max_retries_per_sub_task || 2;
|
|
37160
|
+
const inProgressIdeas = state.ideas.filter((i) => i.stage === "in_progress");
|
|
37161
|
+
for (const idea of inProgressIdeas) {
|
|
37162
|
+
const st = idea.implementation.sub_tasks || [];
|
|
37163
|
+
if (st.length === 0 && (idea.implementation.decomposition_attempts || 0) >= maxRetries) {
|
|
37164
|
+
continue;
|
|
37165
|
+
}
|
|
37166
|
+
const hasPending = st.some((s) => s.status === "pending");
|
|
37167
|
+
const hasUnrecoverableFailed = st.some(
|
|
37168
|
+
(s) => s.status === "failed" && (s.retry_count || 0) >= maxRetries
|
|
37169
|
+
);
|
|
37170
|
+
if ((st.length === 0 || hasPending) && !hasUnrecoverableFailed) {
|
|
37171
|
+
return {
|
|
37172
|
+
id: `implement-${idea.id}`,
|
|
37173
|
+
description: `Continue autonomous implementation: ${idea.title}`,
|
|
37174
|
+
completed: false,
|
|
37175
|
+
section: "high_priority",
|
|
37176
|
+
requires_human: false
|
|
37177
|
+
};
|
|
37178
|
+
}
|
|
35567
37179
|
}
|
|
35568
|
-
|
|
35569
|
-
|
|
35570
|
-
|
|
35571
|
-
|
|
35572
|
-
|
|
35573
|
-
|
|
35574
|
-
|
|
35575
|
-
|
|
35576
|
-
|
|
35577
|
-
|
|
35578
|
-
|
|
35579
|
-
|
|
35580
|
-
|
|
37180
|
+
return null;
|
|
37181
|
+
})();
|
|
37182
|
+
if (inProgressOverride) {
|
|
37183
|
+
const claudeHasCriticalIssue = reasoning?.critical_issue && reasoning?.next_action;
|
|
37184
|
+
const claudeRecommendsNonImplementation = reasoning?.next_action && !reasoning.next_action.task_id.startsWith("implement-");
|
|
37185
|
+
const criticalIsHuman = claudeHasCriticalIssue && (reasoning.next_action.autonomous === false || state.todos.find((t) => t.id === reasoning.next_action.task_id)?.requires_human || isHumanDependentTask(reasoning.next_action.description));
|
|
37186
|
+
if (claudeHasCriticalIssue && claudeRecommendsNonImplementation && !criticalIsHuman) {
|
|
37187
|
+
log(`Claude identified critical issue: "${reasoning.critical_issue}"`);
|
|
37188
|
+
log(`Allowing Claude's recommendation (${reasoning.next_action.task_id}) to override in-progress work`);
|
|
37189
|
+
const success = await executeTask(reasoning.next_action, heartbeatConfig, businessContext);
|
|
37190
|
+
if (success) {
|
|
37191
|
+
taskToDo = null;
|
|
37192
|
+
} else {
|
|
37193
|
+
log(`Critical task failed, resuming in-progress work: ${inProgressOverride.id}`);
|
|
37194
|
+
taskToDo = inProgressOverride;
|
|
37195
|
+
}
|
|
35581
37196
|
} else {
|
|
35582
|
-
|
|
37197
|
+
if (reasoning?.next_action) {
|
|
37198
|
+
log(`Claude recommends: ${reasoning.next_action.task_id} \u2014 but overriding to continue in-progress work`);
|
|
37199
|
+
}
|
|
37200
|
+
log(`Prioritizing in-progress work: ${inProgressOverride.id}`);
|
|
35583
37201
|
taskToDo = inProgressOverride;
|
|
35584
37202
|
}
|
|
35585
|
-
} else {
|
|
35586
|
-
|
|
35587
|
-
|
|
35588
|
-
|
|
35589
|
-
|
|
35590
|
-
|
|
35591
|
-
|
|
35592
|
-
} else if (reasoning?.next_action) {
|
|
35593
|
-
log(`Claude recommends: ${reasoning.next_action.task_id} \u2014 ${reasoning.next_action.description}`);
|
|
35594
|
-
const existingTask = state.todos.find((t) => t.id === reasoning.next_action.task_id);
|
|
35595
|
-
const recommendedIsNonAutonomous = reasoning.next_action.autonomous === false || existingTask?.requires_human || isHumanDependentTask(reasoning.next_action.description);
|
|
35596
|
-
if (recommendedIsNonAutonomous) {
|
|
35597
|
-
const reason = reasoning.next_action.autonomous === false ? "Claude flagged as non-autonomous" : existingTask?.requires_human ? "TODO marked requires_human" : "matched human-task pattern";
|
|
35598
|
-
log(`Rejected non-autonomous recommendation: ${reasoning.next_action.task_id} (${reason})`);
|
|
35599
|
-
taskToDo = determineWork(state);
|
|
35600
|
-
} else if (existingTask) {
|
|
35601
|
-
taskToDo = existingTask;
|
|
35602
|
-
} else {
|
|
35603
|
-
const success = await executeTask(reasoning.next_action, heartbeatConfig, businessContext);
|
|
35604
|
-
if (!success) {
|
|
37203
|
+
} else if (reasoning?.next_action) {
|
|
37204
|
+
log(`Claude recommends: ${reasoning.next_action.task_id} \u2014 ${reasoning.next_action.description}`);
|
|
37205
|
+
const existingTask = state.todos.find((t) => t.id === reasoning.next_action.task_id);
|
|
37206
|
+
const recommendedIsNonAutonomous = reasoning.next_action.autonomous === false || existingTask?.requires_human || isHumanDependentTask(reasoning.next_action.description);
|
|
37207
|
+
if (recommendedIsNonAutonomous) {
|
|
37208
|
+
const reason = reasoning.next_action.autonomous === false ? "Claude flagged as non-autonomous" : existingTask?.requires_human ? "TODO marked requires_human" : "matched human-task pattern";
|
|
37209
|
+
log(`Rejected non-autonomous recommendation: ${reasoning.next_action.task_id} (${reason})`);
|
|
35605
37210
|
taskToDo = determineWork(state);
|
|
37211
|
+
} else if (existingTask) {
|
|
37212
|
+
taskToDo = existingTask;
|
|
37213
|
+
} else {
|
|
37214
|
+
const success = await executeTask(reasoning.next_action, heartbeatConfig, businessContext);
|
|
37215
|
+
if (!success) {
|
|
37216
|
+
taskToDo = determineWork(state);
|
|
37217
|
+
}
|
|
35606
37218
|
}
|
|
37219
|
+
} else {
|
|
37220
|
+
taskToDo = determineWork(state);
|
|
35607
37221
|
}
|
|
35608
|
-
} else {
|
|
35609
|
-
taskToDo = determineWork(state);
|
|
35610
37222
|
}
|
|
35611
37223
|
if (taskToDo) {
|
|
35612
37224
|
log(`Task to do: ${taskToDo.id} \u2014 ${taskToDo.description}`);
|
|
@@ -35667,7 +37279,7 @@ async function runSingleHeartbeat(beatNumber = 1) {
|
|
|
35667
37279
|
}
|
|
35668
37280
|
if (!DRY_RUN) {
|
|
35669
37281
|
const statusContent = generateStatusContent(state, alerts, taskToDo, reasoning, systemHealth);
|
|
35670
|
-
|
|
37282
|
+
fs31.writeFileSync(STATUS_FILE, statusContent);
|
|
35671
37283
|
log("Updated STATUS.md");
|
|
35672
37284
|
} else {
|
|
35673
37285
|
log("[DRY RUN] Would update STATUS.md");
|
|
@@ -35782,7 +37394,7 @@ ${"=".repeat(60)}
|
|
|
35782
37394
|
`);
|
|
35783
37395
|
}
|
|
35784
37396
|
function saveSessionToJson(summary) {
|
|
35785
|
-
const sessionsFile =
|
|
37397
|
+
const sessionsFile = path26.join(DATA_DIR, "heartbeat-sessions.json");
|
|
35786
37398
|
const sessionsData = loadJson(sessionsFile, { sessions: [] });
|
|
35787
37399
|
sessionsData.sessions.push(summary);
|
|
35788
37400
|
if (sessionsData.sessions.length > 50) {
|