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.
Files changed (108) hide show
  1. package/.next/standalone/.env +5 -1
  2. package/.next/standalone/.next/BUILD_ID +1 -1
  3. package/.next/standalone/.next/app-build-manifest.json +17 -17
  4. package/.next/standalone/.next/app-path-routes-manifest.json +1 -1
  5. package/.next/standalone/.next/build-manifest.json +2 -2
  6. package/.next/standalone/.next/prerender-manifest.json +1 -1
  7. package/.next/standalone/.next/server/app/_not-found/page.js +1 -1
  8. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  9. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  10. package/.next/standalone/.next/server/app/_not-found.rsc +2 -2
  11. package/.next/standalone/.next/server/app/api/analyze/route.js.nft.json +1 -1
  12. package/.next/standalone/.next/server/app/api/config/route.js.nft.json +1 -1
  13. package/.next/standalone/.next/server/app/api/epics/[id]/ideas/route.js.nft.json +1 -1
  14. package/.next/standalone/.next/server/app/api/epics/[id]/route.js.nft.json +1 -1
  15. package/.next/standalone/.next/server/app/api/epics/route.js.nft.json +1 -1
  16. package/.next/standalone/.next/server/app/api/goals/[id]/kpis/route.js.nft.json +1 -1
  17. package/.next/standalone/.next/server/app/api/goals/[id]/route.js.nft.json +1 -1
  18. package/.next/standalone/.next/server/app/api/goals/route.js.nft.json +1 -1
  19. package/.next/standalone/.next/server/app/api/hypotheses/[id]/route.js.nft.json +1 -1
  20. package/.next/standalone/.next/server/app/api/hypotheses/route.js.nft.json +1 -1
  21. package/.next/standalone/.next/server/app/api/ideas/[id]/comments/route.js.nft.json +1 -1
  22. package/.next/standalone/.next/server/app/api/ideas/[id]/implement/route.js.nft.json +1 -1
  23. package/.next/standalone/.next/server/app/api/ideas/[id]/route.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/ideas/[id]/transition/route.js.nft.json +1 -1
  25. package/.next/standalone/.next/server/app/api/ideas/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/implementations/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/kpis/refresh/route.js +1 -1
  28. package/.next/standalone/.next/server/app/api/kpis/refresh/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/social/[id]/publish/route.js +1 -1
  30. package/.next/standalone/.next/server/app/api/social/[id]/publish/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/social/[id]/route.js +1 -1
  32. package/.next/standalone/.next/server/app/api/social/[id]/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/social/route.js +1 -1
  34. package/.next/standalone/.next/server/app/api/social/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/goals/[id]/page_client-reference-manifest.js +1 -1
  36. package/.next/standalone/.next/server/app/goals/page.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/goals/page_client-reference-manifest.js +1 -1
  38. package/.next/standalone/.next/server/app/hypotheses/[id]/page.js +1 -1
  39. package/.next/standalone/.next/server/app/hypotheses/[id]/page_client-reference-manifest.js +1 -1
  40. package/.next/standalone/.next/server/app/hypotheses/page.js +1 -1
  41. package/.next/standalone/.next/server/app/hypotheses/page.js.nft.json +1 -1
  42. package/.next/standalone/.next/server/app/hypotheses/page_client-reference-manifest.js +1 -1
  43. package/.next/standalone/.next/server/app/ideas/[id]/page.js +1 -1
  44. package/.next/standalone/.next/server/app/ideas/[id]/page.js.nft.json +1 -1
  45. package/.next/standalone/.next/server/app/ideas/[id]/page_client-reference-manifest.js +1 -1
  46. package/.next/standalone/.next/server/app/page.js +1 -1
  47. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  49. package/.next/standalone/.next/server/app/roadmap/[id]/page.js +1 -1
  50. package/.next/standalone/.next/server/app/roadmap/[id]/page_client-reference-manifest.js +1 -1
  51. package/.next/standalone/.next/server/app/roadmap/page.js +1 -1
  52. package/.next/standalone/.next/server/app/roadmap/page.js.nft.json +1 -1
  53. package/.next/standalone/.next/server/app/roadmap/page_client-reference-manifest.js +1 -1
  54. package/.next/standalone/.next/server/app/sessions/page.js +1 -1
  55. package/.next/standalone/.next/server/app/sessions/page.js.nft.json +1 -1
  56. package/.next/standalone/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
  57. package/.next/standalone/.next/server/app/settings/page.js +1 -1
  58. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  59. package/.next/standalone/.next/server/app/settings.html +1 -1
  60. package/.next/standalone/.next/server/app/settings.rsc +2 -2
  61. package/.next/standalone/.next/server/app/social/page.js +1 -1
  62. package/.next/standalone/.next/server/app/social/page_client-reference-manifest.js +1 -1
  63. package/.next/standalone/.next/server/app/social.html +1 -1
  64. package/.next/standalone/.next/server/app/social.rsc +3 -3
  65. package/.next/standalone/.next/server/app-paths-manifest.json +13 -13
  66. package/.next/standalone/.next/server/chunks/3644.js +1 -1
  67. package/.next/standalone/.next/server/chunks/{7809.js → 3794.js} +103 -2
  68. package/.next/standalone/.next/server/pages/404.html +1 -1
  69. package/.next/standalone/.next/server/pages/500.html +1 -1
  70. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  71. package/.next/standalone/data/business-context.json +7 -4
  72. package/.next/standalone/data/copy/landing-landing-page-2026-02-20.md +1 -1
  73. package/.next/standalone/data/heartbeat-sessions.json +211 -10
  74. package/.next/standalone/data/hypotheses.json +2 -2
  75. package/.next/standalone/data/ideas.json +958 -449
  76. package/.next/standalone/data/implementations.json +136 -2
  77. package/.next/standalone/data/positioning.json +1 -1
  78. package/.next/standalone/data/reports/visuals/promo-launch.png +0 -0
  79. package/.next/standalone/data/reports/visuals/promo-ship-fast.png +0 -0
  80. package/.next/standalone/data/reports/visuals/promo-while-you-slept-v2.png +0 -0
  81. package/.next/standalone/data/reports/visuals/promo-while-you-slept-v3.png +0 -0
  82. package/.next/standalone/data/reports/visuals/promo-while-you-slept.png +0 -0
  83. package/.next/standalone/package.json +2 -2
  84. package/.next/standalone/scripts/analyze.ts +2 -2
  85. package/.next/standalone/scripts/implement.ts +5 -5
  86. package/.next/standalone/scripts/skills/social-media.ts +310 -9
  87. package/.next/static/chunks/{444-125b7b0fa8cbdb81.js → 444-1826396aef4dab2f.js} +1 -1
  88. package/.next/static/chunks/app/hypotheses/[id]/{page-65032da79ae146c5.js → page-8999ee91b824d378.js} +1 -1
  89. package/.next/static/chunks/app/hypotheses/page-6206bce440c44569.js +1 -0
  90. package/.next/static/chunks/app/ideas/[id]/{page-dc9746061e58eea5.js → page-89e3625db9017166.js} +1 -1
  91. package/.next/static/chunks/app/{page-fb66ff080390f20a.js → page-8ba52ac74c75461b.js} +1 -1
  92. package/.next/static/chunks/app/roadmap/[id]/{page-c8c4baf233e0d480.js → page-f437e783039534c4.js} +1 -1
  93. package/.next/static/chunks/app/roadmap/{page-b15554a207ed2813.js → page-6177b8774b4af79f.js} +1 -1
  94. package/.next/static/chunks/app/social/{page-6c61fb0c2546313e.js → page-69e480936711ccf2.js} +1 -1
  95. package/README.md +1 -1
  96. package/dist/bin/vibebusiness.js +22 -0
  97. package/dist/scripts/analyze.js +4 -3
  98. package/dist/scripts/chat.js +10 -2
  99. package/dist/scripts/generate-idea.js +1 -0
  100. package/dist/scripts/heartbeat.js +2265 -653
  101. package/dist/scripts/implement.js +5 -4
  102. package/dist/scripts/init.js +5 -0
  103. package/dist/scripts/scan.js +2 -1
  104. package/package.json +2 -2
  105. package/templates/commands/setup-posthog.md +116 -0
  106. package/.next/static/chunks/app/hypotheses/page-8314da4be7c533bd.js +0 -1
  107. /package/.next/static/{Tko0dnk2wKPorUxfNnm0x → zIIaTqrawBK1MmHg37JOr}/_buildManifest.js +0 -0
  108. /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 fs28 = require("fs");
109
- var path24 = require("path");
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 (fs28.existsSync(filepath)) {
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 = path24.resolve(process.cwd(), ".env.vault");
255
+ possibleVaultPath = path27.resolve(process.cwd(), ".env.vault");
256
256
  }
257
- if (fs28.existsSync(possibleVaultPath)) {
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] === "~" ? path24.join(os3.homedir(), envPath.slice(1)) : envPath;
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 = path24.resolve(process.cwd(), ".env");
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 path25 of optionPaths) {
308
+ for (const path28 of optionPaths) {
309
309
  try {
310
- const parsed = DotenvModule.parse(fs28.readFileSync(path25, { encoding }));
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 ${path25} ${e.message}`);
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 = path24.relative(process.cwd(), filePath);
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(resolve, reject, res) {
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, resolve, reject));
3705
- dataStream.on("close", this.onResponseCloseHandler.bind(this, resolve, reject));
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(resolve, reject) {
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
- resolve({
3732
+ resolve2({
3733
3733
  data,
3734
3734
  headers: this.res.headers,
3735
3735
  rateLimit
3736
3736
  });
3737
3737
  }
3738
- onResponseCloseHandler(resolve, reject) {
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(resolve, reject);
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(resolve, reject, res) {
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
- resolve({ req: this.req, res: dataStream, originalResponse: res, requestData: this.requestData });
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 resolve = (value) => {
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, resolve, reject));
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((resolve, reject) => {
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, resolve, reject));
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((resolve, reject) => {
6211
- fs11.readFile(handle, (err2, data) => {
6210
+ return new Promise((resolve2, reject) => {
6211
+ fs13.readFile(handle, (err2, data) => {
6212
6212
  if (err2) {
6213
6213
  return reject(err2);
6214
6214
  }
6215
- resolve(data);
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 fs11.promises.open(file, "r");
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((resolve, reject) => {
6240
- fs11.fstat(fileHandle, (err2, stats2) => {
6239
+ const stats = await new Promise((resolve2, reject) => {
6240
+ fs13.fstat(fileHandle, (err2, stats2) => {
6241
6241
  if (err2)
6242
6242
  reject(err2);
6243
- resolve(stats2);
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((resolve) => setTimeout(resolve, seconds * 1e3));
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((resolve, reject) => {
6333
- fs11.read(file, buffer, 0, chunkLength, bufferOffset, (err2, nread) => {
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
- resolve(nread);
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 fs11;
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
- fs11 = __toESM(require("fs"));
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 fs12, UPLOAD_ENDPOINT, TwitterApiv1ReadWrite;
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
- fs12 = __toESM(require("fs"));
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
- fs12.close(file, () => {
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
- fs12.close(file, () => {
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((resolve) => setTimeout(resolve, 3500));
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((resolve) => setTimeout(resolve, waitTime * 1e3));
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 fs27 = __toESM(require("fs"));
9385
- var path23 = __toESM(require("path"));
9386
- var import_child_process8 = require("child_process");
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/loops.ts
9499
- var LOOPS_BASE_URL = "https://app.loops.so/api/v1";
9500
- function getApiKey() {
9501
- const key = process.env.LOOPS_API_KEY;
9502
- if (!key) throw new Error("LOOPS_API_KEY environment variable is not set");
9503
- return key;
9504
- }
9505
- function headers() {
9506
- return {
9507
- Authorization: `Bearer ${getApiKey()}`,
9508
- "Content-Type": "application/json"
9509
- };
9510
- }
9511
- async function verifyApiKey() {
9512
- const res = await fetch(`${LOOPS_BASE_URL}/api-key`, {
9513
- method: "GET",
9514
- headers: headers()
9515
- });
9516
- if (!res.ok) return { success: false };
9517
- const data = await res.json();
9518
- return { success: true, teamName: data.teamName };
9519
- }
9520
- async function listContacts(options) {
9521
- const params = new URLSearchParams();
9522
- if (options?.limit) params.set("limit", String(options.limit));
9523
- const res = await fetch(
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
- const contacts = await res.json();
9531
- return { contacts, count: Array.isArray(contacts) ? contacts.length : 0 };
9532
- }
9533
- async function getMailingLists() {
9534
- const res = await fetch(`${LOOPS_BASE_URL}/lists`, {
9535
- method: "GET",
9536
- headers: headers()
9537
- });
9538
- if (!res.ok) {
9539
- throw new Error(`Loops API error: ${res.status} ${await res.text()}`);
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
- return res.json();
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/loops.ts
9545
- var LoopsAdapter = class {
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 = "loops";
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 needsContacts = Object.values(this.fieldMapping).some(
9557
- (v2) => v2 === "total_contacts"
9558
- );
9559
- const needsLists = Object.values(this.fieldMapping).some(
9560
- (v2) => v2.startsWith("list:")
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
- if (fieldName === "total_contacts") {
9575
- values[kpiId] = totalContacts;
9576
- } else if (fieldName.startsWith("list:")) {
9577
- values[kpiId] = lists.length;
9578
- } else {
9579
- values[kpiId] = null;
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 result = await verifyApiKey();
9591
- return result.success;
9660
+ const raw = fs.readFileSync(this.filePath, "utf-8");
9661
+ return JSON.parse(raw);
9592
9662
  } catch {
9593
- return false;
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 "loops":
9606
- return new LoopsAdapter(config);
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 Business Analyst.
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 Business Analyst for ${product.name}, ${product.summary}.${architectureSection}
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 fs = __toESM(require("fs"));
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 || !fs.existsSync(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 fs3 = __toESM(require("fs"));
9769
- var path2 = __toESM(require("path"));
9846
+ var fs4 = __toESM(require("fs"));
9847
+ var path3 = __toESM(require("path"));
9770
9848
 
9771
9849
  // scripts/lib/paths.ts
9772
- var path = __toESM(require("path"));
9773
- var fs2 = __toESM(require("fs"));
9850
+ var path2 = __toESM(require("path"));
9851
+ var fs3 = __toESM(require("fs"));
9774
9852
  function findProjectRoot() {
9775
9853
  const cwd = process.cwd();
9776
- if (fs2.existsSync(path.join(cwd, "data", "config.json"))) {
9854
+ if (fs3.existsSync(path2.join(cwd, "data", "config.json"))) {
9777
9855
  return cwd;
9778
9856
  }
9779
9857
  try {
9780
- const entries = fs2.readdirSync(cwd, { withFileTypes: true });
9858
+ const entries = fs3.readdirSync(cwd, { withFileTypes: true });
9781
9859
  for (const entry of entries) {
9782
9860
  if (entry.isDirectory()) {
9783
- const candidate = path.join(cwd, entry.name, "data", "config.json");
9784
- if (fs2.existsSync(candidate)) {
9785
- return path.join(cwd, entry.name);
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 = path.dirname(dir);
9871
+ const parent = path2.dirname(dir);
9794
9872
  if (parent === dir) break;
9795
- if (fs2.existsSync(path.join(parent, "data", "config.json"))) {
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 = path.join(PROJECT_DIR, "data");
9804
- var STATUS_FILE = path.join(PROJECT_DIR, "STATUS.md");
9805
- var TODO_FILE = path.join(PROJECT_DIR, "TODO.md");
9806
- var MEMORY_FILE = path.join(PROJECT_DIR, "MEMORY.md");
9807
- var IDEAS_FILE = path.join(DATA_DIR, "ideas.json");
9808
- var GOALS_FILE = path.join(DATA_DIR, "goals.json");
9809
- var SESSIONS_FILE = path.join(DATA_DIR, "sessions.json");
9810
- var CONFIG_FILE = path.join(DATA_DIR, "config.json");
9811
- var BUSINESS_CONTEXT_FILE = path.join(DATA_DIR, "business-context.json");
9812
- var HYPOTHESES_FILE = path.join(DATA_DIR, "hypotheses.json");
9813
- var IMPLEMENTATIONS_FILE = path.join(DATA_DIR, "implementations.json");
9814
- var ROADMAP_FILE = path.join(DATA_DIR, "roadmap.json");
9815
- var COMPETITORS_FILE = path.join(DATA_DIR, "competitors.json");
9816
- var POSITIONING_FILE = path.join(DATA_DIR, "positioning.json");
9817
- var PAGES_FILE = path.join(DATA_DIR, "pages.json");
9818
- var PAYMENTS_FILE = path.join(DATA_DIR, "payments.json");
9819
- var SOCIAL_FILE = path.join(DATA_DIR, "social.json");
9820
- var TEMPLATES_DIR = path.join(__dirname, "..", "..", "templates");
9821
- var COMMAND_TEMPLATES_DIR = path.join(TEMPLATES_DIR, "commands");
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 = path2.join(PROJECT_DIR, ".claude", "commands", `${command}.md`);
9889
- if (!fs3.existsSync(commandFile)) {
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 = fs3.readFileSync(commandFile, "utf-8");
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 (fs3.existsSync(COMPETITORS_FILE)) {
10028
+ if (fs4.existsSync(COMPETITORS_FILE)) {
9950
10029
  try {
9951
- const data = JSON.parse(fs3.readFileSync(COMPETITORS_FILE, "utf-8"));
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 (fs3.existsSync(POSITIONING_FILE)) {
10040
+ if (fs4.existsSync(POSITIONING_FILE)) {
9962
10041
  try {
9963
- const data = JSON.parse(fs3.readFileSync(POSITIONING_FILE, "utf-8"));
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 (fs3.existsSync(PAGES_FILE)) {
10052
+ if (fs4.existsSync(PAGES_FILE)) {
9974
10053
  try {
9975
- const data = JSON.parse(fs3.readFileSync(PAGES_FILE, "utf-8"));
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 fs6 = __toESM(require("fs"));
9988
- var path4 = __toESM(require("path"));
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, path24) {
12968
- var _path = path24 || new 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 path24 = buildPath2(font.glyphs, glyph);
13192
- path24.unitsPerEm = font.unitsPerEm;
13193
- return path24;
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 path24 = parseCFFCharstring2(font, glyph, charstring);
13207
- path24.unitsPerEm = font.unitsPerEm;
13208
- return path24;
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 fs4(A) {
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 (fs4(A.type)) throw new Error("Class component is not supported.");
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 fs5 = __toESM(require("fs"));
27563
- var path3 = __toESM(require("path"));
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 Business Analyst",
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 = path3.join(__dirname, "..", "assets");
27661
- const regularPath = path3.join(assetsDir, "Inter-Regular.ttf");
27662
- const boldPath = path3.join(assetsDir, "Inter-Bold.ttf");
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 (fs5.existsSync(regularPath)) {
27743
+ if (fs6.existsSync(regularPath)) {
27665
27744
  fonts.push({
27666
27745
  name: "Inter",
27667
- data: fs5.readFileSync(regularPath).buffer,
27746
+ data: fs6.readFileSync(regularPath).buffer,
27668
27747
  weight: 400,
27669
27748
  style: "normal"
27670
27749
  });
27671
27750
  }
27672
- if (fs5.existsSync(boldPath)) {
27751
+ if (fs6.existsSync(boldPath)) {
27673
27752
  fonts.push({
27674
27753
  name: "Inter",
27675
- data: fs5.readFileSync(boldPath).buffer,
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 = path4.join(DATA_DIR, "reports", "visuals");
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(fs6.readFileSync(IDEAS_FILE, "utf-8"));
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 && fs6.existsSync(GOALS_FILE)) {
28055
+ if (idea.goal_id && fs7.existsSync(GOALS_FILE)) {
27977
28056
  try {
27978
- const goalsData = JSON.parse(fs6.readFileSync(GOALS_FILE, "utf-8"));
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 = path4.join(DATA_DIR, "reports", `${ideaId}-screenshot.png`);
27985
- if (fs6.existsSync(screenshotPath)) {
28063
+ const screenshotPath = path5.join(DATA_DIR, "reports", `${ideaId}-screenshot.png`);
28064
+ if (fs7.existsSync(screenshotPath)) {
27986
28065
  try {
27987
- const imgBuffer = fs6.readFileSync(screenshotPath);
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
- fs6.mkdirSync(VISUALS_DIR, { recursive: true });
28000
- const outputPath = path4.join(VISUALS_DIR, `${ideaId}-card.png`);
28001
- fs6.writeFileSync(outputPath, result.buffer);
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 fs7 = __toESM(require("fs"));
28018
- var path5 = __toESM(require("path"));
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 = path5.join(PROJECT_DIR, ".claude", "commands", `${command}.md`);
28095
- if (!fs7.existsSync(commandFile)) {
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 = fs7.readFileSync(commandFile, "utf-8");
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 fs10 = __toESM(require("fs"));
28153
- var path8 = __toESM(require("path"));
28264
+ var fs11 = __toESM(require("fs"));
28265
+ var path9 = __toESM(require("path"));
28154
28266
 
28155
28267
  // scripts/lib/payments/framework-detect.ts
28156
- var fs8 = __toESM(require("fs"));
28157
- var path6 = __toESM(require("path"));
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 = fs8.existsSync(path6.join(projectPath, "tsconfig.json"));
28166
- result.srcDir = fs8.existsSync(path6.join(projectPath, "src"));
28167
- const pkgPath = path6.join(projectPath, "package.json");
28168
- if (!fs8.existsSync(pkgPath)) {
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(fs8.readFileSync(pkgPath, "utf-8"));
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
- path6.join(projectPath, "app"),
28182
- path6.join(projectPath, "src", "app")
28293
+ path7.join(projectPath, "app"),
28294
+ path7.join(projectPath, "src", "app")
28183
28295
  ];
28184
- result.appRouter = appDirs.some((dir) => fs8.existsSync(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 fs9 = __toESM(require("fs"));
28979
- var path7 = __toESM(require("path"));
29090
+ var fs10 = __toESM(require("fs"));
29091
+ var path8 = __toESM(require("path"));
28980
29092
  function readEnvFile(projectPath) {
28981
- const envPath = path7.join(projectPath, ".env");
28982
- if (!fs9.existsSync(envPath)) {
29093
+ const envPath = path8.join(projectPath, ".env");
29094
+ if (!fs10.existsSync(envPath)) {
28983
29095
  return {};
28984
29096
  }
28985
- const content = fs9.readFileSync(envPath, "utf-8");
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 = path7.join(projectPath, ".env");
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 = fs9.existsSync(envPath) ? "\n" : "";
29124
+ const prefix = fs10.existsSync(envPath) ? "\n" : "";
29013
29125
  const content = prefix + lines.join("\n") + "\n";
29014
- fs9.appendFileSync(envPath, content);
29126
+ fs10.appendFileSync(envPath, content);
29015
29127
  return added;
29016
29128
  }
29017
29129
  function ensureGitignore(projectPath) {
29018
- const gitignorePath = path7.join(projectPath, ".gitignore");
29130
+ const gitignorePath = path8.join(projectPath, ".gitignore");
29019
29131
  let content = "";
29020
29132
  let modified = false;
29021
- if (fs9.existsSync(gitignorePath)) {
29022
- content = fs9.readFileSync(gitignorePath, "utf-8");
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
- fs9.appendFileSync(gitignorePath, `${suffix}.env
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 (!fs10.existsSync(PAYMENTS_FILE)) {
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(fs10.readFileSync(PAYMENTS_FILE, "utf-8"));
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 (!fs10.existsSync(PAYMENTS_FILE)) {
29221
+ if (!fs11.existsSync(PAYMENTS_FILE)) {
29110
29222
  return { ...EMPTY_STATE };
29111
29223
  }
29112
29224
  try {
29113
- return JSON.parse(fs10.readFileSync(PAYMENTS_FILE, "utf-8"));
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 = path8.dirname(PAYMENTS_FILE);
29121
- if (!fs10.existsSync(dir)) {
29122
- fs10.mkdirSync(dir, { recursive: true });
29232
+ const dir = path9.dirname(PAYMENTS_FILE);
29233
+ if (!fs11.existsSync(dir)) {
29234
+ fs11.mkdirSync(dir, { recursive: true });
29123
29235
  }
29124
- fs10.writeFileSync(PAYMENTS_FILE, JSON.stringify(state, null, 2) + "\n");
29236
+ fs11.writeFileSync(PAYMENTS_FILE, JSON.stringify(state, null, 2) + "\n");
29125
29237
  }
29126
29238
  function getUserProjectPath() {
29127
- const configPath = path8.join(PROJECT_DIR, "data", "config.json");
29128
- if (fs10.existsSync(configPath)) {
29239
+ const configPath = path9.join(PROJECT_DIR, "data", "config.json");
29240
+ if (fs11.existsSync(configPath)) {
29129
29241
  try {
29130
- const config = JSON.parse(fs10.readFileSync(configPath, "utf-8"));
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 (fs10.existsSync(repoPath)) {
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 = path8.join(projectPath, file.path);
29165
- if (fs10.existsSync(fullPath)) {
29276
+ const fullPath = path9.join(projectPath, file.path);
29277
+ if (fs11.existsSync(fullPath)) {
29166
29278
  return false;
29167
29279
  }
29168
- const dir = path8.dirname(fullPath);
29169
- if (!fs10.existsSync(dir)) {
29170
- fs10.mkdirSync(dir, { recursive: true });
29280
+ const dir = path9.dirname(fullPath);
29281
+ if (!fs11.existsSync(dir)) {
29282
+ fs11.mkdirSync(dir, { recursive: true });
29171
29283
  }
29172
- fs10.writeFileSync(fullPath, file.content);
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 = fs10.existsSync(path8.join(projectPath, template.path));
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 fs14 = __toESM(require("fs"));
29394
- var path9 = __toESM(require("path"));
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 fs28 = require("fs");
29515
- if (!fs28.existsSync(filePath)) {
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 fs13 = __toESM(require("fs"));
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 (!fs13.existsSync(filePath)) return fallback;
29558
- return JSON.parse(fs13.readFileSync(filePath, "utf-8"));
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 EMPTY_STATE2 = {
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 loadState2() {
29649
- if (!fs14.existsSync(SOCIAL_FILE)) {
29650
- return { ...EMPTY_STATE2, metrics: { ...EMPTY_STATE2.metrics } };
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(fs14.readFileSync(SOCIAL_FILE, "utf-8"));
31022
+ return JSON.parse(fs18.readFileSync(SOCIAL_FILE, "utf-8"));
29654
31023
  } catch {
29655
- return { ...EMPTY_STATE2, metrics: { ...EMPTY_STATE2.metrics } };
31024
+ return { ...EMPTY_STATE3, metrics: { ...EMPTY_STATE3.metrics } };
29656
31025
  }
29657
31026
  }
29658
- function saveState2(state) {
31027
+ function saveState3(state) {
29659
31028
  state.last_updated = (/* @__PURE__ */ new Date()).toISOString();
29660
- const dir = path9.dirname(SOCIAL_FILE);
29661
- if (!fs14.existsSync(dir)) {
29662
- fs14.mkdirSync(dir, { recursive: true });
31029
+ const dir = path12.dirname(SOCIAL_FILE);
31030
+ if (!fs18.existsSync(dir)) {
31031
+ fs18.mkdirSync(dir, { recursive: true });
29663
31032
  }
29664
- fs14.writeFileSync(SOCIAL_FILE, JSON.stringify(state, null, 2) + "\n");
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 executeCheckStatus2();
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 = loadState2();
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 = loadState2();
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
- saveState2(state);
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 = loadState2();
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(fs14.readFileSync(
29814
- path9.join(path9.dirname(SOCIAL_FILE), "ideas.json"),
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
- saveState2(state);
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 = loadState2();
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(fs14.readFileSync(
29832
- path9.join(path9.dirname(SOCIAL_FILE), "ideas.json"),
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 = path9.join(DATA_DIR, "reports", "visuals", `${latestShipped.id}-card.png`);
29842
- if (fs14.existsSync(cardPath)) {
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 = path9.join(DATA_DIR, "videos", `${latestShipped.id}-ship.mp4`);
29846
- if (fs14.existsSync(videoPath)) {
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
- saveState2(state);
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 = loadState2();
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
- saveState2(state);
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 = loadState2();
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
- saveState2(state);
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 = loadState2();
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
- saveState2(state);
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 = loadState2();
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) => fs14.existsSync(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
- saveState2(state);
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 executeCheckStatus2() {
29939
- const state = loadState2();
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
- saveState2(state);
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 (!fs14.existsSync(GOALS_FILE)) return;
29974
- const goalsData = JSON.parse(fs14.readFileSync(GOALS_FILE, "utf-8"));
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
- fs14.writeFileSync(GOALS_FILE, JSON.stringify(goalsData, null, 2) + "\n");
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 import_child_process5 = require("child_process");
30011
- var fs15 = __toESM(require("fs"));
30012
- var path10 = __toESM(require("path"));
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 (!fs15.existsSync(POSITIONING_FILE)) return false;
30053
- const data = JSON.parse(fs15.readFileSync(POSITIONING_FILE, "utf-8"));
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 = path10.join(PROJECT_DIR, ".claude", "commands", `${command}.md`);
30075
- const templateFile = path10.join(PROJECT_DIR, "templates", "commands", `${command}.md`);
30076
- const templatePath = fs15.existsSync(commandFile) ? commandFile : templateFile;
30077
- if (!fs15.existsSync(templatePath)) {
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 = fs15.readFileSync(templatePath, "utf-8");
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, import_child_process5.spawnSync)("claude", [
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 = path10.join(DATA_DIR, "copy");
30133
- if (fs15.existsSync(copyDir)) {
31715
+ const copyDir = path13.join(DATA_DIR, "copy");
31716
+ if (fs19.existsSync(copyDir)) {
30134
31717
  try {
30135
- const files = fs15.readdirSync(copyDir).filter((f) => f.endsWith(".md"));
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 fs17 = __toESM(require("fs"));
30150
- var path12 = __toESM(require("path"));
31732
+ var fs21 = __toESM(require("fs"));
31733
+ var path15 = __toESM(require("path"));
30151
31734
 
30152
31735
  // scripts/lib/video/render-bridge.ts
30153
- var import_child_process6 = require("child_process");
30154
- var fs16 = __toESM(require("fs"));
30155
- var path11 = __toESM(require("path"));
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 || !fs16.existsSync(repoPath)) return false;
30158
- const pkgPath = path11.join(repoPath, "package.json");
30159
- if (!fs16.existsSync(pkgPath)) return false;
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(fs16.readFileSync(pkgPath, "utf-8"));
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 business analyst.`
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 = path11.basename(carouselJsonPath, ".json");
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, import_child_process6.execSync)(command, {
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 = path11.join(repoPath, "content", "videos");
31827
+ const contentVideosDir = path14.join(repoPath, "content", "videos");
30245
31828
  const possibleOutputs = [
30246
- path11.join(contentVideosDir, `${slug}.mp4`),
30247
- path11.join(contentVideosDir, `${slug}-${format}.mp4`)
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 (fs16.existsSync(candidate)) {
31834
+ if (fs20.existsSync(candidate)) {
30252
31835
  videoPath = candidate;
30253
31836
  break;
30254
31837
  }
30255
31838
  }
30256
31839
  if (videoPath && outputDir) {
30257
- fs16.mkdirSync(outputDir, { recursive: true });
30258
- const destPath = path11.join(outputDir, path11.basename(videoPath));
30259
- fs16.copyFileSync(videoPath, destPath);
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(fs16.statSync(videoPath).size / 1024)}KB)` : `Render completed but output not found. CLI output: ${output.slice(-500)}`
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 = path12.join(DATA_DIR, "videos");
30279
- var CAROUSEL_STAGING_DIR = path12.join(DATA_DIR, "videos", "staging");
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 executeCheckStatus3(businessContext);
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(fs17.readFileSync(IDEAS_FILE, "utf-8"));
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 = path12.join(VIDEOS_DIR, `${ideaId}-ship.mp4`);
30329
- if (fs17.existsSync(outputPath)) {
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
- fs17.mkdirSync(CAROUSEL_STAGING_DIR, { recursive: true });
30334
- const carouselPath = path12.join(CAROUSEL_STAGING_DIR, `${ideaId}-ship.json`);
30335
- fs17.writeFileSync(carouselPath, JSON.stringify(carouselData, null, 2));
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 = path12.join(VIDEOS_DIR, `${ideaId}-ship.mp4`);
31921
+ const finalPath = path15.join(VIDEOS_DIR, `${ideaId}-ship.mp4`);
30339
31922
  if (result.videoPath !== finalPath) {
30340
- fs17.renameSync(result.videoPath, finalPath);
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 Business Analyst", background_style: "gradient" },
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
- fs17.mkdirSync(CAROUSEL_STAGING_DIR, { recursive: true });
30377
- const carouselPath = path12.join(CAROUSEL_STAGING_DIR, `ad-${topic}.json`);
30378
- fs17.writeFileSync(carouselPath, JSON.stringify(adCarousel, null, 2));
30379
- fs17.mkdirSync(VIDEOS_DIR, { recursive: true });
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 executeCheckStatus3(businessContext) {
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 (fs17.existsSync(VIDEOS_DIR)) {
30403
- const videos = fs17.readdirSync(VIDEOS_DIR).filter((f) => f.endsWith(".mp4"));
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 = fs17.statSync(path12.join(VIDEOS_DIR, video));
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 (fs17.existsSync(CAROUSEL_STAGING_DIR)) {
30413
- const staging = fs17.readdirSync(CAROUSEL_STAGING_DIR).filter((f) => f.endsWith(".json"));
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 = path12.join(DATA_DIR, "business-context.json");
30426
- if (fs17.existsSync(contextPath)) {
30427
- const ctx = JSON.parse(fs17.readFileSync(contextPath, "utf-8"));
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 (fs17.existsSync(VIDEOS_DIR)) {
32018
+ if (fs21.existsSync(VIDEOS_DIR)) {
30436
32019
  try {
30437
- const videos = fs17.readdirSync(VIDEOS_DIR).filter((f) => f.endsWith(".mp4"));
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 fs18 = __toESM(require("fs"));
30450
- var path13 = __toESM(require("path"));
30451
- var CAMPAIGNS_DIR = path13.join(DATA_DIR, "campaigns");
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 (fs18.existsSync(SOCIAL_FILE)) {
30469
- const social = JSON.parse(fs18.readFileSync(SOCIAL_FILE, "utf-8"));
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 (fs18.existsSync(POSITIONING_FILE)) {
30477
- const pos = JSON.parse(fs18.readFileSync(POSITIONING_FILE, "utf-8"));
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 = fs18.existsSync(businessContext.contentflow.repo_path);
32066
+ channels.contentflow = fs22.existsSync(businessContext.contentflow.repo_path);
30484
32067
  }
30485
32068
  return channels;
30486
32069
  }
30487
32070
  function saveCampaign(campaign) {
30488
- fs18.mkdirSync(CAMPAIGNS_DIR, { recursive: true });
30489
- const filePath = path13.join(CAMPAIGNS_DIR, `${campaign.id}.json`);
30490
- fs18.writeFileSync(filePath, JSON.stringify(campaign, null, 2) + "\n");
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 (!fs18.existsSync(CAMPAIGNS_DIR)) return [];
32076
+ if (!fs22.existsSync(CAMPAIGNS_DIR)) return [];
30494
32077
  try {
30495
- return fs18.readdirSync(CAMPAIGNS_DIR).filter((f) => f.endsWith(".json")).map((f) => JSON.parse(fs18.readFileSync(path13.join(CAMPAIGNS_DIR, f), "utf-8"))).sort((a, b) => b.created_at.localeCompare(a.created_at));
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 (fs18.existsSync(TODO_FILE)) {
30503
- content = fs18.readFileSync(TODO_FILE, "utf-8");
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
- fs18.writeFileSync(TODO_FILE, content);
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 executeCheckStatus4();
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(fs18.readFileSync(IDEAS_FILE, "utf-8"));
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 = path13.join(DATA_DIR, "reports", "visuals", `${ideaId}-card.png`);
30568
- if (!fs18.existsSync(cardPath)) {
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 (fs18.existsSync(GOALS_FILE)) {
30660
- const goalsData = JSON.parse(fs18.readFileSync(GOALS_FILE, "utf-8"));
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 executeCheckStatus4() {
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 path14 = __toESM(require("path"));
32343
+ var path17 = __toESM(require("path"));
30761
32344
  function resolveScript(baseDir, scriptName) {
30762
- const isCompiled = path14.extname(__filename) === ".js";
32345
+ const isCompiled = path17.extname(__filename) === ".js";
30763
32346
  const resolvedName = isCompiled ? scriptName.replace(/\.ts$/, ".js") : scriptName;
30764
- const scriptPath = path14.join(baseDir, resolvedName);
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 fs19 = __toESM(require("fs"));
30819
- var path15 = __toESM(require("path"));
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
- fs19.mkdirSync(lockPath, { recursive: false });
32410
+ fs23.mkdirSync(lockPath, { recursive: false });
30828
32411
  try {
30829
- fs19.writeFileSync(
30830
- path15.join(lockPath, "owner"),
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
- fs19.writeFileSync(tmpPath, content, "utf-8");
30860
- fs19.renameSync(tmpPath, filePath);
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 = path15.join(lockPath, "owner");
30865
- if (fs19.existsSync(ownerFile)) {
30866
- fs19.unlinkSync(ownerFile);
32447
+ const ownerFile = path18.join(lockPath, "owner");
32448
+ if (fs23.existsSync(ownerFile)) {
32449
+ fs23.unlinkSync(ownerFile);
30867
32450
  }
30868
- fs19.rmdirSync(lockPath);
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 fs21 = __toESM(require("fs"));
30880
- var path17 = __toESM(require("path"));
32462
+ var fs25 = __toESM(require("fs"));
32463
+ var path20 = __toESM(require("path"));
30881
32464
 
30882
32465
  // scripts/lib/git-utils.ts
30883
- var import_child_process7 = require("child_process");
30884
- var fs20 = __toESM(require("fs"));
30885
- var path16 = __toESM(require("path"));
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 (!fs20.existsSync(workspacePath)) return;
30888
- const gitDir = path16.join(workspacePath, ".git");
30889
- if (!fs20.existsSync(gitDir)) return;
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 = fs20.statSync(gitDir);
32475
+ const stat = fs24.statSync(gitDir);
30893
32476
  if (stat.isFile()) {
30894
- const content = fs20.readFileSync(gitDir, "utf-8").trim();
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 = path16.join(actualGitDir, "index.lock");
30902
- if (fs20.existsSync(lockFile)) {
32484
+ const lockFile = path19.join(actualGitDir, "index.lock");
32485
+ if (fs24.existsSync(lockFile)) {
30903
32486
  try {
30904
- const result = (0, import_child_process7.execSync)(
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
- fs20.unlinkSync(lockFile);
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
- fs20.unlinkSync(lockFile);
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: path16.join(actualGitDir, "MERGE_HEAD"), abort: "git merge --abort" },
30922
- { marker: path16.join(actualGitDir, "rebase-merge"), abort: "git rebase --abort" },
30923
- { marker: path16.join(actualGitDir, "rebase-apply"), abort: "git rebase --abort" },
30924
- { marker: path16.join(actualGitDir, "CHERRY_PICK_HEAD"), abort: "git cherry-pick --abort" }
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 (fs20.existsSync(marker)) {
32510
+ if (fs24.existsSync(marker)) {
30928
32511
  try {
30929
- (0, import_child_process7.execSync)(abort, { cwd: workspacePath, encoding: "utf-8", timeout: 1e4, stdio: "pipe" });
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, import_child_process7.execSync)(cmd, {
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: path17.join(workspaceDir, repoName),
30955
- worktreeContainerDir: path17.join(workspaceDir, `${repoName}-worktrees`)
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 (!fs21.existsSync(worktreeContainerDir)) {
30975
- fs21.mkdirSync(worktreeContainerDir, { recursive: true });
32557
+ if (!fs25.existsSync(worktreeContainerDir)) {
32558
+ fs25.mkdirSync(worktreeContainerDir, { recursive: true });
30976
32559
  }
30977
32560
  const tempBranch = `wt/${ideaBranch}-g${groupIndex}`;
30978
- const worktreePath = path17.join(worktreeContainerDir, `${ideaBranch}-g${groupIndex}`);
30979
- if (fs21.existsSync(worktreePath)) {
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
- fs21.rmSync(worktreePath, { recursive: true, force: true });
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
- fs21.rmSync(worktreePath, { recursive: true, force: true });
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 (fs21.existsSync(worktreeContainerDir)) {
32610
+ if (fs25.existsSync(worktreeContainerDir)) {
31028
32611
  try {
31029
- const entries = fs21.readdirSync(worktreeContainerDir);
32612
+ const entries = fs25.readdirSync(worktreeContainerDir);
31030
32613
  for (const entry of entries) {
31031
32614
  if (entry.startsWith(`${ideaBranch}-g`)) {
31032
- const wtPath = path17.join(worktreeContainerDir, entry);
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 = fs21.readdirSync(worktreeContainerDir);
32629
+ const remaining = fs25.readdirSync(worktreeContainerDir);
31047
32630
  if (remaining.length === 0) {
31048
- fs21.rmdirSync(worktreeContainerDir);
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 fs22 = __toESM(require("fs"));
31059
- var path18 = __toESM(require("path"));
31060
- var CONFIG_DIR = path18.join(
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 = path18.join(CONFIG_DIR, "telemetry.json");
31065
- var EVENTS_FILE = path18.join(CONFIG_DIR, "events.jsonl");
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 = path18.join(dir, "package.json");
31071
- if (fs22.existsSync(pkgPath)) {
31072
- const pkg = JSON.parse(fs22.readFileSync(pkgPath, "utf-8"));
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 = path18.dirname(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 (!fs22.existsSync(TELEMETRY_CONFIG_FILE)) return null;
31084
- return JSON.parse(fs22.readFileSync(TELEMETRY_CONFIG_FILE, "utf-8"));
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
- fs22.mkdirSync(CONFIG_DIR, { recursive: true });
31106
- fs22.appendFileSync(EVENTS_FILE, JSON.stringify(entry) + "\n");
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 fs24 = __toESM(require("fs"));
31338
- var path20 = __toESM(require("path"));
32920
+ var fs28 = __toESM(require("fs"));
32921
+ var path23 = __toESM(require("path"));
31339
32922
  function scaffoldSlashCommands(rootDir) {
31340
- let templatesDir = path20.join(__dirname, "..", "..", "templates", "commands");
31341
- if (!fs24.existsSync(templatesDir)) {
31342
- templatesDir = path20.join(__dirname, "..", "..", "..", "templates", "commands");
31343
- }
31344
- if (!fs24.existsSync(templatesDir)) return;
31345
- const targetDir = path20.join(rootDir, ".claude", "commands");
31346
- fs24.mkdirSync(targetDir, { recursive: true });
31347
- const templates = fs24.readdirSync(templatesDir).filter((f) => f.endsWith(".md"));
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 = path20.join(templatesDir, file);
31351
- const dest = path20.join(targetDir, file);
31352
- fs24.copyFileSync(src, dest);
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 fs25 = __toESM(require("fs"));
31362
- var path21 = __toESM(require("path"));
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 CONFIG_DIR2 = path21.join(process.env.HOME || "", ".vibebusiness");
31365
- var BUFFER_FILE = path21.join(CONFIG_DIR2, "vibe-buffer.json");
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 = path21.join(dir, "package.json");
31373
- if (fs25.existsSync(pkgPath)) {
31374
- const pkg = JSON.parse(fs25.readFileSync(pkgPath, "utf-8"));
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 = path21.dirname(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 (fs25.existsSync(BUFFER_FILE)) {
31387
- return JSON.parse(fs25.readFileSync(BUFFER_FILE, "utf-8"));
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 (!fs25.existsSync(CONFIG_DIR2)) {
31395
- fs25.mkdirSync(CONFIG_DIR2, { recursive: true });
32977
+ if (!fs29.existsSync(CONFIG_DIR3)) {
32978
+ fs29.mkdirSync(CONFIG_DIR3, { recursive: true });
31396
32979
  }
31397
- fs25.writeFileSync(BUFFER_FILE, JSON.stringify(buffer, null, 2));
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 fs26 = __toESM(require("fs"));
31472
- var path22 = __toESM(require("path"));
31473
- var CONFIG_DIR3 = path22.join(process.env.HOME || "", ".vibebusiness");
31474
- var LICENSE_FILE = path22.join(CONFIG_DIR3, "license.json");
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 (fs26.existsSync(LICENSE_FILE)) {
31478
- return JSON.parse(fs26.readFileSync(LICENSE_FILE, "utf-8"));
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)(import_child_process8.exec);
33081
+ var execAsync = (0, import_util.promisify)(import_child_process10.exec);
31499
33082
  function spawnAsync(cmd, args2, options) {
31500
- return new Promise((resolve, reject) => {
31501
- const child = (0, import_child_process8.spawn)(cmd, args2, {
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
- resolve({ stdout, stderr });
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((resolve) => setTimeout(resolve, ms2));
33186
+ return new Promise((resolve2) => setTimeout(resolve2, ms2));
31602
33187
  }
31603
33188
  function loadJson(filePath, defaultValue) {
31604
33189
  try {
31605
- const content = fs27.readFileSync(filePath, "utf-8");
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 = fs27.readFileSync(TODO_FILE, "utf-8");
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
- fs27.writeFileSync(TODO_FILE, content);
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 = fs27.readFileSync(TODO_FILE, "utf-8");
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 = fs27.readFileSync(TODO_FILE, "utf-8");
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
- fs27.writeFileSync(TODO_FILE, content);
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 = fs27.readFileSync(TODO_FILE, "utf-8");
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 loadState3() {
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, import_child_process8.execSync)("git status --porcelain", { cwd: workspacePath, encoding: "utf-8" });
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, import_child_process8.execSync)("git diff --stat HEAD~1 HEAD 2>/dev/null || git diff --stat --cached 2>/dev/null || git diff --stat 2>/dev/null", {
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 = path23.join(DATA_DIR, "codebase-snapshot.json");
33774
+ const snapshotPath = path26.join(DATA_DIR, "codebase-snapshot.json");
32190
33775
  try {
32191
- if (fs27.existsSync(snapshotPath)) {
32192
- return JSON.parse(fs27.readFileSync(snapshotPath, "utf-8"));
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 Business Analyst for ${productName}${productSummary}.
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 draft activity.
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-check-status" \u2192 Audit: credentials valid? Draft count? Post streak? Followers?
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 suggest "social-draft-ship" (text only)
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-update" to maintain streak
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((resolve, reject) => {
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, import_child_process8.spawn)("claude", [
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
- resolve(null);
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
- resolve(result);
34357
+ resolve2(result);
32765
34358
  } else {
32766
34359
  log(`Claude completed in ${elapsed}s but no valid JSON found`);
32767
- resolve(null);
34360
+ resolve2(null);
32768
34361
  }
32769
34362
  } else {
32770
34363
  log(`Claude exited with code ${code} after ${elapsed}s: ${errorOutput}`);
32771
- resolve(null);
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
- resolve(null);
34373
+ resolve2(null);
32781
34374
  });
32782
34375
  });
32783
34376
  }
32784
34377
  function addTasksToTodo(tasks) {
32785
34378
  if (tasks.length === 0) return;
32786
- if (!fs27.existsSync(TODO_FILE)) return;
34379
+ if (!fs31.existsSync(TODO_FILE)) return;
32787
34380
  try {
32788
- let content = fs27.readFileSync(TODO_FILE, "utf-8");
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
- fs27.writeFileSync(TODO_FILE, content);
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 (!fs27.existsSync(TODO_FILE)) return;
32884
- let content = fs27.readFileSync(TODO_FILE, "utf-8");
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
- fs27.writeFileSync(TODO_FILE, content);
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 (!fs27.existsSync(TODO_FILE)) return;
32919
- const content = fs27.readFileSync(TODO_FILE, "utf-8");
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
- fs27.writeFileSync(TODO_FILE, newLines.join("\n"));
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 (!fs27.existsSync(TODO_FILE)) return;
32955
- const content = fs27.readFileSync(TODO_FILE, "utf-8");
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
- fs27.writeFileSync(TODO_FILE, cleaned);
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 (!fs27.existsSync(MEMORY_FILE)) return "";
33001
- const content = fs27.readFileSync(MEMORY_FILE, "utf-8");
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 (!fs27.existsSync(MEMORY_FILE)) return;
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 = fs27.readFileSync(MEMORY_FILE, "utf-8");
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
- fs27.writeFileSync(MEMORY_FILE, content);
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 (!fs27.existsSync(MEMORY_FILE)) return;
33089
- const content = fs27.readFileSync(MEMORY_FILE, "utf-8");
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
- fs27.writeFileSync(MEMORY_FILE, newContent);
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(path23.basename(repo.path))) {
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((resolve) => {
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, import_child_process8.spawn)("claude", [
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
- resolve([]);
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
- resolve(subTasks);
35085
+ resolve2(subTasks);
33493
35086
  return;
33494
35087
  }
33495
35088
  }
33496
- resolve([]);
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
- resolve([]);
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 = path23.join(sourceRepoPath, ".venv", "bin");
33570
- if (fs27.existsSync(venvBin)) {
35162
+ const venvBin = path26.join(sourceRepoPath, ".venv", "bin");
35163
+ if (fs31.existsSync(venvBin)) {
33571
35164
  env.PATH = `${venvBin}:${env.PATH}`;
33572
- env.VIRTUAL_ENV = path23.join(sourceRepoPath, ".venv");
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, import_child_process8.execSync)(cmd, {
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 || path23.join(config.workspace_dir, targetRepo.name);
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, import_child_process8.execSync)("git rev-parse HEAD", { cwd: workspacePath, encoding: "utf-8" }).trim();
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, import_child_process8.spawnSync)("claude", ["--print", "--model", "sonnet", "-p", errorContext], {
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, import_child_process8.execSync)(
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 = path23.join(config.workspace_dir, targetRepo2.name);
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, import_child_process8.execSync)(`git checkout ${defaultBranch2}`, { cwd: workspacePath, encoding: "utf-8", stdio: "pipe" });
33857
- (0, import_child_process8.execSync)(`git merge ${branchName2} --squash --no-edit`, { cwd: workspacePath, encoding: "utf-8", stdio: "pipe" });
33858
- (0, import_child_process8.execSync)(`git commit -m "feat: ${idea.title} (auto-merged from ${branchName2})"`, { cwd: workspacePath, encoding: "utf-8", stdio: "pipe" });
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, import_child_process8.execSync)(`git branch -d ${branchName2}`, { cwd: workspacePath, encoding: "utf-8", stdio: "pipe" });
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, import_child_process8.execSync)(`git merge --abort`, { cwd: workspacePath, encoding: "utf-8", stdio: "pipe" });
33887
- (0, import_child_process8.execSync)(`git checkout ${branchName2}`, { cwd: workspacePath, encoding: "utf-8", stdio: "pipe" });
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 Business Analyst for ${metaProductName}.
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((resolve) => {
34094
- const claude = (0, import_child_process8.spawn)("claude", [
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 ? 48e4 : 24e4;
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
- resolve(false);
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
- resolve(false);
35733
+ resolve2(false);
34141
35734
  return;
34142
35735
  }
34143
35736
  log(`Meta-task completed: ${output.substring(0, 200)}`);
34144
- resolve(true);
35737
+ resolve2(true);
34145
35738
  } else {
34146
35739
  log(`Meta-task failed (exit ${code})`);
34147
- resolve(false);
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
- resolve(false);
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((resolve) => {
35770
+ return new Promise((resolve2) => {
34178
35771
  const { command: analyzeCmd, args: analyzeArgs } = resolveScript(__dirname, "analyze.ts");
34179
- const childProcess = (0, import_child_process8.spawn)(analyzeCmd, [
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
- resolve(false);
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
- resolve(true);
35790
+ resolve2(true);
34198
35791
  } else {
34199
35792
  log(`Research task failed with code ${code}`);
34200
- resolve(false);
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
- resolve(false);
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((resolve) => {
35933
+ return new Promise((resolve2) => {
34341
35934
  const TIMEOUT_MS = 9e5;
34342
35935
  const startTime = Date.now();
34343
- const claude = (0, import_child_process8.spawn)("claude", [
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
- resolve(false);
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
- resolve(false);
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
- resolve(false);
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
- resolve(false);
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
- resolve(true);
36047
+ resolve2(true);
34455
36048
  } catch (error) {
34456
36049
  log(`Failed to parse goal-gap research ideas: ${error instanceof Error ? error.message : "Unknown"}`);
34457
- resolve(false);
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
- resolve(false);
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((resolve) => {
34587
- const claude = (0, import_child_process8.spawn)("claude", ["--print"], {
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
- resolve(null);
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
- resolve(parsed);
36208
+ resolve2(parsed);
34616
36209
  return;
34617
36210
  }
34618
36211
  }
34619
- resolve(null);
36212
+ resolve2(null);
34620
36213
  });
34621
36214
  claude.on("error", () => {
34622
36215
  if (!isResolved) {
34623
36216
  isResolved = true;
34624
36217
  clearTimeout(timeout);
34625
- resolve(null);
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((resolve) => {
34985
- const claude = (0, import_child_process8.spawn)("claude", ["--print", "--dangerously-skip-permissions"], {
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
- resolve(task);
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
- resolve(task);
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
- resolve(task);
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((resolve) => {
35096
- const claude = (0, import_child_process8.spawn)("claude", ["--print"], {
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
- resolve();
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
- resolve();
36730
+ resolve2();
35138
36731
  });
35139
36732
  claude.on("error", () => {
35140
36733
  clearTimeout(timeout);
35141
36734
  research.summary = findingsSummary;
35142
36735
  research.recommendation = "revise";
35143
- resolve();
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((resolve, reject) => {
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, import_child_process8.spawn)(runCmd, [...runArgs, `--type=${type}`], {
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
- resolve();
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 = fs27.readFileSync(TODO_FILE, "utf-8");
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
- fs27.writeFileSync(TODO_FILE, content);
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 = path23.join(PROJECT_DIR, ".claude", "commands");
35487
- if (!fs27.existsSync(commandsDir)) {
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 = loadState3();
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
- const inProgressOverride = (() => {
35546
- const cfgCheck = loadConfig();
35547
- if (!cfgCheck.autonomy?.enabled) return null;
35548
- const maxRetries = cfgCheck.autonomy.max_retries_per_sub_task || 2;
35549
- const inProgressIdeas = state.ideas.filter((i) => i.stage === "in_progress");
35550
- for (const idea of inProgressIdeas) {
35551
- const st = idea.implementation.sub_tasks || [];
35552
- if (st.length === 0 && (idea.implementation.decomposition_attempts || 0) >= maxRetries) {
35553
- continue;
35554
- }
35555
- const hasPending = st.some((s) => s.status === "pending");
35556
- const hasUnrecoverableFailed = st.some(
35557
- (s) => s.status === "failed" && (s.retry_count || 0) >= maxRetries
35558
- );
35559
- if ((st.length === 0 || hasPending) && !hasUnrecoverableFailed) {
35560
- return {
35561
- id: `implement-${idea.id}`,
35562
- description: `Continue autonomous implementation: ${idea.title}`,
35563
- completed: false,
35564
- section: "high_priority",
35565
- requires_human: false
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
- return null;
35570
- })();
35571
- if (inProgressOverride) {
35572
- const claudeHasCriticalIssue = reasoning?.critical_issue && reasoning?.next_action;
35573
- const claudeRecommendsNonImplementation = reasoning?.next_action && !reasoning.next_action.task_id.startsWith("implement-");
35574
- 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));
35575
- if (claudeHasCriticalIssue && claudeRecommendsNonImplementation && !criticalIsHuman) {
35576
- log(`Claude identified critical issue: "${reasoning.critical_issue}"`);
35577
- log(`Allowing Claude's recommendation (${reasoning.next_action.task_id}) to override in-progress work`);
35578
- const success = await executeTask(reasoning.next_action, heartbeatConfig, businessContext);
35579
- if (success) {
35580
- taskToDo = null;
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
- log(`Critical task failed, resuming in-progress work: ${inProgressOverride.id}`);
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
- if (reasoning?.next_action) {
35587
- log(`Claude recommends: ${reasoning.next_action.task_id} \u2014 but overriding to continue in-progress work`);
35588
- }
35589
- log(`Prioritizing in-progress work: ${inProgressOverride.id}`);
35590
- taskToDo = inProgressOverride;
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
- fs27.writeFileSync(STATUS_FILE, statusContent);
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 = path23.join(DATA_DIR, "heartbeat-sessions.json");
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) {