vibebusiness 1.2.37 → 1.2.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/standalone/.env +1 -0
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/app-build-manifest.json +13 -13
- package/.next/standalone/.next/app-path-routes-manifest.json +1 -1
- package/.next/standalone/.next/build-manifest.json +2 -2
- package/.next/standalone/.next/prerender-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +1 -1
- package/.next/standalone/.next/server/app/api/analyze/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/config/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/epics/[id]/ideas/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/epics/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/epics/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/goals/[id]/kpis/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/goals/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/goals/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/hypotheses/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/hypotheses/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/ideas/[id]/comments/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/ideas/[id]/implement/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/ideas/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/ideas/[id]/transition/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/ideas/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/implementations/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/kpis/refresh/route.js +1 -1
- package/.next/standalone/.next/server/app/api/kpis/refresh/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/goals/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/goals/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/goals/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/hypotheses/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/hypotheses/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/hypotheses/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/ideas/[id]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/ideas/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/landing/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/landing.html +1 -1
- package/.next/standalone/.next/server/app/landing.rsc +1 -1
- package/.next/standalone/.next/server/app/page.js +1 -1
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/roadmap/[id]/page.js +1 -1
- package/.next/standalone/.next/server/app/roadmap/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/roadmap/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/roadmap/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/sessions/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings.html +1 -1
- package/.next/standalone/.next/server/app/settings.rsc +1 -1
- package/.next/standalone/.next/server/app-paths-manifest.json +4 -4
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/data/business-context.json +9 -1
- package/.next/standalone/data/email-campaigns/sequences/onboarding.md +183 -0
- package/.next/standalone/data/email-campaigns/welcome-2026-02-20.md +61 -0
- package/.next/standalone/data/goals.json +3 -2
- package/.next/standalone/data/heartbeat-sessions.json +798 -0
- package/.next/standalone/data/ideas.json +668 -80
- package/.next/standalone/data/implementations.json +397 -0
- package/.next/standalone/package.json +1 -1
- package/.next/static/chunks/app/page-6a52a6950963129a.js +1 -0
- package/.next/static/chunks/app/roadmap/[id]/page-3848fd96de497d11.js +1 -0
- package/dist/scripts/analyze.js +1 -0
- package/dist/scripts/chat.js +1 -0
- package/dist/scripts/generate-idea.js +1 -0
- package/dist/scripts/heartbeat.js +1620 -168
- package/dist/scripts/implement.js +1 -0
- package/dist/scripts/init.js +1 -0
- package/dist/scripts/scan.js +1 -0
- package/package.json +1 -1
- package/templates/commands/email-marketing.md +201 -0
- package/.next/static/chunks/app/page-999b97bf50993294.js +0 -1
- package/.next/static/chunks/app/roadmap/[id]/page-da3d51358c8e2014.js +0 -1
- /package/.next/static/{Lbwr50a6hdyjnsxpep4G9 → k40DKimnEdAtBs_zywSQ4}/_buildManifest.js +0 -0
- /package/.next/static/{Lbwr50a6hdyjnsxpep4G9 → k40DKimnEdAtBs_zywSQ4}/_ssgManifest.js +0 -0
|
@@ -2280,9 +2280,9 @@ var require_escape_html = __commonJS({
|
|
|
2280
2280
|
});
|
|
2281
2281
|
|
|
2282
2282
|
// scripts/heartbeat.ts
|
|
2283
|
-
var
|
|
2284
|
-
var
|
|
2285
|
-
var
|
|
2283
|
+
var fs19 = __toESM(require("fs"));
|
|
2284
|
+
var path18 = __toESM(require("path"));
|
|
2285
|
+
var import_child_process6 = require("child_process");
|
|
2286
2286
|
var import_util = require("util");
|
|
2287
2287
|
|
|
2288
2288
|
// scripts/lib/api.ts
|
|
@@ -2290,16 +2290,16 @@ var API_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8001";
|
|
|
2290
2290
|
var INTERNAL_API_KEY = process.env.INTERNAL_API_KEY;
|
|
2291
2291
|
async function apiRequest(endpoint, options = {}) {
|
|
2292
2292
|
const url = `${API_URL}${endpoint}`;
|
|
2293
|
-
const
|
|
2293
|
+
const headers2 = {
|
|
2294
2294
|
"Content-Type": "application/json",
|
|
2295
2295
|
...options.headers
|
|
2296
2296
|
};
|
|
2297
2297
|
if (INTERNAL_API_KEY) {
|
|
2298
|
-
|
|
2298
|
+
headers2["X-Internal-API-Key"] = INTERNAL_API_KEY;
|
|
2299
2299
|
}
|
|
2300
2300
|
const response = await fetch(url, {
|
|
2301
2301
|
...options,
|
|
2302
|
-
headers
|
|
2302
|
+
headers: headers2
|
|
2303
2303
|
});
|
|
2304
2304
|
if (!response.ok) {
|
|
2305
2305
|
const errorText = await response.text();
|
|
@@ -2511,6 +2511,7 @@ var ROADMAP_FILE = path.join(DATA_DIR, "roadmap.json");
|
|
|
2511
2511
|
var COMPETITORS_FILE = path.join(DATA_DIR, "competitors.json");
|
|
2512
2512
|
var POSITIONING_FILE = path.join(DATA_DIR, "positioning.json");
|
|
2513
2513
|
var PAGES_FILE = path.join(DATA_DIR, "pages.json");
|
|
2514
|
+
var PAYMENTS_FILE = path.join(DATA_DIR, "payments.json");
|
|
2514
2515
|
var TEMPLATES_DIR = path.join(__dirname, "..", "..", "templates");
|
|
2515
2516
|
var COMMAND_TEMPLATES_DIR = path.join(TEMPLATES_DIR, "commands");
|
|
2516
2517
|
|
|
@@ -5658,8 +5659,8 @@ function argument(predicate, message) {
|
|
|
5658
5659
|
}
|
|
5659
5660
|
}
|
|
5660
5661
|
var check = { fail, argument, assert: argument };
|
|
5661
|
-
function getPathDefinition(glyph,
|
|
5662
|
-
var _path =
|
|
5662
|
+
function getPathDefinition(glyph, path19) {
|
|
5663
|
+
var _path = path19 || new Path();
|
|
5663
5664
|
return {
|
|
5664
5665
|
configurable: true,
|
|
5665
5666
|
get: function() {
|
|
@@ -5882,9 +5883,9 @@ function ttfGlyphLoader(font, index, parseGlyph2, data, position, buildPath2) {
|
|
|
5882
5883
|
var glyph = new Glyph({ index, font });
|
|
5883
5884
|
glyph.path = function() {
|
|
5884
5885
|
parseGlyph2(glyph, data, position);
|
|
5885
|
-
var
|
|
5886
|
-
|
|
5887
|
-
return
|
|
5886
|
+
var path19 = buildPath2(font.glyphs, glyph);
|
|
5887
|
+
path19.unitsPerEm = font.unitsPerEm;
|
|
5888
|
+
return path19;
|
|
5888
5889
|
};
|
|
5889
5890
|
defineDependentProperty(glyph, "xMin", "_xMin");
|
|
5890
5891
|
defineDependentProperty(glyph, "xMax", "_xMax");
|
|
@@ -5897,9 +5898,9 @@ function cffGlyphLoader(font, index, parseCFFCharstring2, charstring) {
|
|
|
5897
5898
|
return function() {
|
|
5898
5899
|
var glyph = new Glyph({ index, font });
|
|
5899
5900
|
glyph.path = function() {
|
|
5900
|
-
var
|
|
5901
|
-
|
|
5902
|
-
return
|
|
5901
|
+
var path19 = parseCFFCharstring2(font, glyph, charstring);
|
|
5902
|
+
path19.unitsPerEm = font.unitsPerEm;
|
|
5903
|
+
return path19;
|
|
5903
5904
|
};
|
|
5904
5905
|
return glyph;
|
|
5905
5906
|
};
|
|
@@ -20706,12 +20707,1433 @@ async function executeMarketingVisualTask(taskId, _description) {
|
|
|
20706
20707
|
}
|
|
20707
20708
|
}
|
|
20708
20709
|
|
|
20709
|
-
// scripts/
|
|
20710
|
+
// scripts/skills/email-marketing.ts
|
|
20711
|
+
var import_child_process3 = require("child_process");
|
|
20712
|
+
var fs7 = __toESM(require("fs"));
|
|
20710
20713
|
var path5 = __toESM(require("path"));
|
|
20714
|
+
|
|
20715
|
+
// src/lib/loops.ts
|
|
20716
|
+
var LOOPS_BASE_URL = "https://app.loops.so/api/v1";
|
|
20717
|
+
function getApiKey() {
|
|
20718
|
+
const key = process.env.LOOPS_API_KEY;
|
|
20719
|
+
if (!key) throw new Error("LOOPS_API_KEY environment variable is not set");
|
|
20720
|
+
return key;
|
|
20721
|
+
}
|
|
20722
|
+
function headers() {
|
|
20723
|
+
return {
|
|
20724
|
+
Authorization: `Bearer ${getApiKey()}`,
|
|
20725
|
+
"Content-Type": "application/json"
|
|
20726
|
+
};
|
|
20727
|
+
}
|
|
20728
|
+
async function verifyApiKey() {
|
|
20729
|
+
const res = await fetch(`${LOOPS_BASE_URL}/api-key`, {
|
|
20730
|
+
method: "GET",
|
|
20731
|
+
headers: headers()
|
|
20732
|
+
});
|
|
20733
|
+
if (!res.ok) return { success: false };
|
|
20734
|
+
const data = await res.json();
|
|
20735
|
+
return { success: true, teamName: data.teamName };
|
|
20736
|
+
}
|
|
20737
|
+
async function listContacts(options) {
|
|
20738
|
+
const params = new URLSearchParams();
|
|
20739
|
+
if (options?.limit) params.set("limit", String(options.limit));
|
|
20740
|
+
const res = await fetch(
|
|
20741
|
+
`${LOOPS_BASE_URL}/contacts?${params.toString()}`,
|
|
20742
|
+
{ method: "GET", headers: headers() }
|
|
20743
|
+
);
|
|
20744
|
+
if (!res.ok) {
|
|
20745
|
+
throw new Error(`Loops API error: ${res.status} ${await res.text()}`);
|
|
20746
|
+
}
|
|
20747
|
+
const contacts = await res.json();
|
|
20748
|
+
return { contacts, count: Array.isArray(contacts) ? contacts.length : 0 };
|
|
20749
|
+
}
|
|
20750
|
+
async function getMailingLists() {
|
|
20751
|
+
const res = await fetch(`${LOOPS_BASE_URL}/lists`, {
|
|
20752
|
+
method: "GET",
|
|
20753
|
+
headers: headers()
|
|
20754
|
+
});
|
|
20755
|
+
if (!res.ok) {
|
|
20756
|
+
throw new Error(`Loops API error: ${res.status} ${await res.text()}`);
|
|
20757
|
+
}
|
|
20758
|
+
return res.json();
|
|
20759
|
+
}
|
|
20760
|
+
|
|
20761
|
+
// scripts/skills/email-marketing.ts
|
|
20762
|
+
var EMAIL_PREFIXES = [
|
|
20763
|
+
"email-setup",
|
|
20764
|
+
"email-campaign-",
|
|
20765
|
+
"email-sequence-",
|
|
20766
|
+
"email-blast-",
|
|
20767
|
+
"email-health"
|
|
20768
|
+
];
|
|
20769
|
+
function isEmailMarketingTask(taskId) {
|
|
20770
|
+
return EMAIL_PREFIXES.some((prefix) => taskId.startsWith(prefix));
|
|
20771
|
+
}
|
|
20772
|
+
function parseTaskToCommand2(taskId, description) {
|
|
20773
|
+
if (taskId === "email-setup") {
|
|
20774
|
+
return { command: "email-marketing", args: "setup" };
|
|
20775
|
+
}
|
|
20776
|
+
if (taskId.startsWith("email-campaign-")) {
|
|
20777
|
+
const type = taskId.replace("email-campaign-", "");
|
|
20778
|
+
return { command: "email-marketing", args: `campaign ${type}` };
|
|
20779
|
+
}
|
|
20780
|
+
if (taskId.startsWith("email-sequence-")) {
|
|
20781
|
+
const name = taskId.replace("email-sequence-", "");
|
|
20782
|
+
return { command: "email-marketing", args: `sequence ${name}` };
|
|
20783
|
+
}
|
|
20784
|
+
if (taskId.startsWith("email-blast-")) {
|
|
20785
|
+
const slug = taskId.replace("email-blast-", "");
|
|
20786
|
+
return { command: "email-marketing", args: `blast ${slug}` };
|
|
20787
|
+
}
|
|
20788
|
+
if (taskId === "email-health") {
|
|
20789
|
+
return null;
|
|
20790
|
+
}
|
|
20791
|
+
return null;
|
|
20792
|
+
}
|
|
20793
|
+
async function executeHealthCheck() {
|
|
20794
|
+
const lines = ["## Email Marketing Health Check\n"];
|
|
20795
|
+
try {
|
|
20796
|
+
const keyResult = await verifyApiKey();
|
|
20797
|
+
if (!keyResult.success) {
|
|
20798
|
+
return {
|
|
20799
|
+
success: false,
|
|
20800
|
+
output: "Loops API key is invalid or not set. Run email-setup to configure."
|
|
20801
|
+
};
|
|
20802
|
+
}
|
|
20803
|
+
lines.push(`- API Key: valid (team: ${keyResult.teamName || "unknown"})`);
|
|
20804
|
+
const contactResult = await listContacts({ limit: 1 });
|
|
20805
|
+
lines.push(`- Total Contacts: ${contactResult.count}`);
|
|
20806
|
+
const lists = await getMailingLists();
|
|
20807
|
+
lines.push(`- Mailing Lists: ${lists.length}`);
|
|
20808
|
+
for (const list of lists) {
|
|
20809
|
+
lines.push(` - ${list.name} (${list.id})`);
|
|
20810
|
+
}
|
|
20811
|
+
lines.push("\nLoops integration is healthy.");
|
|
20812
|
+
return { success: true, output: lines.join("\n") };
|
|
20813
|
+
} catch (error) {
|
|
20814
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
20815
|
+
return {
|
|
20816
|
+
success: false,
|
|
20817
|
+
output: `Health check failed: ${errMsg}`
|
|
20818
|
+
};
|
|
20819
|
+
}
|
|
20820
|
+
}
|
|
20821
|
+
async function executeEmailMarketingTask(taskId, description, businessContext) {
|
|
20822
|
+
if (taskId === "email-health") {
|
|
20823
|
+
return executeHealthCheck();
|
|
20824
|
+
}
|
|
20825
|
+
const parsed = parseTaskToCommand2(taskId, description);
|
|
20826
|
+
if (!parsed) {
|
|
20827
|
+
return {
|
|
20828
|
+
success: false,
|
|
20829
|
+
output: `Unknown email marketing task: ${taskId}`
|
|
20830
|
+
};
|
|
20831
|
+
}
|
|
20832
|
+
const { command, args: args2 } = parsed;
|
|
20833
|
+
const commandFile = path5.join(PROJECT_DIR, ".claude", "commands", `${command}.md`);
|
|
20834
|
+
if (!fs7.existsSync(commandFile)) {
|
|
20835
|
+
return {
|
|
20836
|
+
success: false,
|
|
20837
|
+
output: `Slash command template not found: ${commandFile}. Run 'init' to scaffold commands.`
|
|
20838
|
+
};
|
|
20839
|
+
}
|
|
20840
|
+
const template = fs7.readFileSync(commandFile, "utf-8");
|
|
20841
|
+
const prompt = args2 ? `${template}
|
|
20842
|
+
|
|
20843
|
+
---
|
|
20844
|
+
|
|
20845
|
+
$ARGUMENTS: ${args2}
|
|
20846
|
+
|
|
20847
|
+
Execute this command now.` : `${template}
|
|
20848
|
+
|
|
20849
|
+
---
|
|
20850
|
+
|
|
20851
|
+
Execute this command now with no arguments (use defaults).`;
|
|
20852
|
+
console.log(`[EmailMarketing] Executing: /${command} ${args2} (task: ${taskId})`);
|
|
20853
|
+
try {
|
|
20854
|
+
const result = (0, import_child_process3.spawnSync)("claude", [
|
|
20855
|
+
"--print",
|
|
20856
|
+
"--dangerously-skip-permissions",
|
|
20857
|
+
"--allowedTools",
|
|
20858
|
+
"Read Write Edit Bash Glob Grep WebSearch WebFetch"
|
|
20859
|
+
], {
|
|
20860
|
+
cwd: PROJECT_DIR,
|
|
20861
|
+
input: prompt,
|
|
20862
|
+
encoding: "utf-8",
|
|
20863
|
+
timeout: 6e5,
|
|
20864
|
+
// 10 minute timeout
|
|
20865
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
20866
|
+
env: process.env
|
|
20867
|
+
});
|
|
20868
|
+
if (result.status !== 0) {
|
|
20869
|
+
const errorOutput = result.stderr || result.stdout || "Unknown error";
|
|
20870
|
+
return {
|
|
20871
|
+
success: false,
|
|
20872
|
+
output: `Claude Code exited with code ${result.status}: ${errorOutput.slice(-1e3)}`
|
|
20873
|
+
};
|
|
20874
|
+
}
|
|
20875
|
+
return {
|
|
20876
|
+
success: true,
|
|
20877
|
+
output: (result.stdout || "").slice(-2e3)
|
|
20878
|
+
// Last 2000 chars
|
|
20879
|
+
};
|
|
20880
|
+
} catch (error) {
|
|
20881
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
20882
|
+
return {
|
|
20883
|
+
success: false,
|
|
20884
|
+
output: `Email marketing task failed: ${errMsg.slice(-1e3)}`
|
|
20885
|
+
};
|
|
20886
|
+
}
|
|
20887
|
+
}
|
|
20888
|
+
|
|
20889
|
+
// scripts/skills/payments.ts
|
|
20890
|
+
var import_child_process4 = require("child_process");
|
|
20891
|
+
var fs10 = __toESM(require("fs"));
|
|
20892
|
+
var path8 = __toESM(require("path"));
|
|
20893
|
+
|
|
20894
|
+
// scripts/lib/payments/framework-detect.ts
|
|
20895
|
+
var fs8 = __toESM(require("fs"));
|
|
20896
|
+
var path6 = __toESM(require("path"));
|
|
20897
|
+
function detectFramework(projectPath) {
|
|
20898
|
+
const result = {
|
|
20899
|
+
framework: "other",
|
|
20900
|
+
typescript: false,
|
|
20901
|
+
appRouter: false,
|
|
20902
|
+
srcDir: false
|
|
20903
|
+
};
|
|
20904
|
+
result.typescript = fs8.existsSync(path6.join(projectPath, "tsconfig.json"));
|
|
20905
|
+
result.srcDir = fs8.existsSync(path6.join(projectPath, "src"));
|
|
20906
|
+
const pkgPath = path6.join(projectPath, "package.json");
|
|
20907
|
+
if (!fs8.existsSync(pkgPath)) {
|
|
20908
|
+
return result;
|
|
20909
|
+
}
|
|
20910
|
+
let pkg;
|
|
20911
|
+
try {
|
|
20912
|
+
pkg = JSON.parse(fs8.readFileSync(pkgPath, "utf-8"));
|
|
20913
|
+
} catch {
|
|
20914
|
+
return result;
|
|
20915
|
+
}
|
|
20916
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
20917
|
+
if (allDeps["next"]) {
|
|
20918
|
+
result.framework = "nextjs";
|
|
20919
|
+
const appDirs = [
|
|
20920
|
+
path6.join(projectPath, "app"),
|
|
20921
|
+
path6.join(projectPath, "src", "app")
|
|
20922
|
+
];
|
|
20923
|
+
result.appRouter = appDirs.some((dir) => fs8.existsSync(dir));
|
|
20924
|
+
} else if (allDeps["@remix-run/node"] || allDeps["@remix-run/react"]) {
|
|
20925
|
+
result.framework = "remix";
|
|
20926
|
+
} else if (allDeps["fastify"]) {
|
|
20927
|
+
result.framework = "fastify";
|
|
20928
|
+
} else if (allDeps["express"]) {
|
|
20929
|
+
result.framework = "express";
|
|
20930
|
+
}
|
|
20931
|
+
return result;
|
|
20932
|
+
}
|
|
20933
|
+
function getFileExtension(info) {
|
|
20934
|
+
return info.typescript ? ".ts" : ".js";
|
|
20935
|
+
}
|
|
20936
|
+
|
|
20937
|
+
// scripts/lib/payments/stripe-api.ts
|
|
20938
|
+
function loadStripeModule(projectPath) {
|
|
20939
|
+
try {
|
|
20940
|
+
const stripePath = require.resolve("stripe", { paths: [projectPath] });
|
|
20941
|
+
return require(stripePath);
|
|
20942
|
+
} catch {
|
|
20943
|
+
return null;
|
|
20944
|
+
}
|
|
20945
|
+
}
|
|
20946
|
+
function createStripeClient(secretKey, projectPath) {
|
|
20947
|
+
const Stripe = loadStripeModule(projectPath);
|
|
20948
|
+
if (!Stripe) {
|
|
20949
|
+
throw new Error(
|
|
20950
|
+
"stripe package not found. Install it with: npm install stripe"
|
|
20951
|
+
);
|
|
20952
|
+
}
|
|
20953
|
+
const StripeConstructor = Stripe.default || Stripe;
|
|
20954
|
+
return new StripeConstructor(secretKey);
|
|
20955
|
+
}
|
|
20956
|
+
async function validateApiKey(stripe) {
|
|
20957
|
+
try {
|
|
20958
|
+
const account = await stripe.accounts.retrieve();
|
|
20959
|
+
return {
|
|
20960
|
+
valid: true,
|
|
20961
|
+
accountName: account.business_profile?.name || account.email || "Unknown"
|
|
20962
|
+
};
|
|
20963
|
+
} catch (error) {
|
|
20964
|
+
return {
|
|
20965
|
+
valid: false,
|
|
20966
|
+
error: error.message || "Invalid API key"
|
|
20967
|
+
};
|
|
20968
|
+
}
|
|
20969
|
+
}
|
|
20970
|
+
async function findExistingProducts(stripe, name) {
|
|
20971
|
+
try {
|
|
20972
|
+
const products = await stripe.products.search({
|
|
20973
|
+
query: `name~"${name}"`
|
|
20974
|
+
});
|
|
20975
|
+
return products.data.map((p) => ({
|
|
20976
|
+
id: p.id,
|
|
20977
|
+
name: p.name,
|
|
20978
|
+
description: p.description
|
|
20979
|
+
}));
|
|
20980
|
+
} catch {
|
|
20981
|
+
try {
|
|
20982
|
+
const products = await stripe.products.list({ limit: 100 });
|
|
20983
|
+
return products.data.filter((p) => p.name.toLowerCase().includes(name.toLowerCase())).map((p) => ({
|
|
20984
|
+
id: p.id,
|
|
20985
|
+
name: p.name,
|
|
20986
|
+
description: p.description
|
|
20987
|
+
}));
|
|
20988
|
+
} catch {
|
|
20989
|
+
return [];
|
|
20990
|
+
}
|
|
20991
|
+
}
|
|
20992
|
+
}
|
|
20993
|
+
async function createProduct(stripe, input) {
|
|
20994
|
+
const product = await stripe.products.create({
|
|
20995
|
+
name: input.name,
|
|
20996
|
+
...input.description && { description: input.description }
|
|
20997
|
+
});
|
|
20998
|
+
return {
|
|
20999
|
+
id: product.id,
|
|
21000
|
+
name: product.name,
|
|
21001
|
+
description: product.description
|
|
21002
|
+
};
|
|
21003
|
+
}
|
|
21004
|
+
async function createPrice(stripe, input) {
|
|
21005
|
+
const params = {
|
|
21006
|
+
product: input.productId,
|
|
21007
|
+
unit_amount: input.unitAmount,
|
|
21008
|
+
currency: input.currency
|
|
21009
|
+
};
|
|
21010
|
+
if (input.interval) {
|
|
21011
|
+
params.recurring = { interval: input.interval };
|
|
21012
|
+
}
|
|
21013
|
+
const price = await stripe.prices.create(params);
|
|
21014
|
+
return {
|
|
21015
|
+
id: price.id,
|
|
21016
|
+
productId: input.productId,
|
|
21017
|
+
unitAmount: input.unitAmount,
|
|
21018
|
+
currency: input.currency,
|
|
21019
|
+
interval: input.interval || null
|
|
21020
|
+
};
|
|
21021
|
+
}
|
|
21022
|
+
|
|
21023
|
+
// scripts/lib/payments/code-templates.ts
|
|
21024
|
+
var DEFAULT_WEBHOOK_EVENTS = [
|
|
21025
|
+
"checkout.session.completed",
|
|
21026
|
+
"customer.subscription.created",
|
|
21027
|
+
"customer.subscription.updated",
|
|
21028
|
+
"customer.subscription.deleted",
|
|
21029
|
+
"invoice.paid",
|
|
21030
|
+
"invoice.payment_failed"
|
|
21031
|
+
];
|
|
21032
|
+
function getTemplatesForFramework(info, config = {}) {
|
|
21033
|
+
switch (info.framework) {
|
|
21034
|
+
case "nextjs":
|
|
21035
|
+
return info.appRouter ? getNextjsAppRouterTemplates(info, config) : getNextjsPagesTemplates(info, config);
|
|
21036
|
+
case "express":
|
|
21037
|
+
return getExpressTemplates(info, config);
|
|
21038
|
+
case "fastify":
|
|
21039
|
+
return getFastifyTemplates(info, config);
|
|
21040
|
+
case "remix":
|
|
21041
|
+
return getRemixTemplates(info, config);
|
|
21042
|
+
default:
|
|
21043
|
+
return getGenericTemplates(info, config);
|
|
21044
|
+
}
|
|
21045
|
+
}
|
|
21046
|
+
function getNextjsAppRouterTemplates(info, config) {
|
|
21047
|
+
const ext = getFileExtension(info);
|
|
21048
|
+
const prefix = info.srcDir ? "src/" : "";
|
|
21049
|
+
const successUrl = config.successUrl || "/success";
|
|
21050
|
+
const cancelUrl = config.cancelUrl || "/pricing";
|
|
21051
|
+
const events = config.webhookEvents || DEFAULT_WEBHOOK_EVENTS;
|
|
21052
|
+
const ts2 = info.typescript;
|
|
21053
|
+
return [
|
|
21054
|
+
{
|
|
21055
|
+
path: `${prefix}app/api/checkout/route${ext}`,
|
|
21056
|
+
description: "Stripe Checkout session creation endpoint",
|
|
21057
|
+
content: `import { NextResponse } from 'next/server';
|
|
21058
|
+
import Stripe from 'stripe';
|
|
21059
|
+
|
|
21060
|
+
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
|
|
21061
|
+
|
|
21062
|
+
export async function POST(request${ts2 ? ": Request" : ""}) {
|
|
21063
|
+
try {
|
|
21064
|
+
const body = await request.json();
|
|
21065
|
+
const { priceId, customerId } = body;
|
|
21066
|
+
|
|
21067
|
+
if (!priceId) {
|
|
21068
|
+
return NextResponse.json({ error: 'priceId is required' }, { status: 400 });
|
|
21069
|
+
}
|
|
21070
|
+
|
|
21071
|
+
const session = await stripe.checkout.sessions.create({
|
|
21072
|
+
mode: 'subscription',
|
|
21073
|
+
payment_method_types: ['card'],
|
|
21074
|
+
line_items: [{ price: priceId, quantity: 1 }],
|
|
21075
|
+
success_url: \`\${process.env.NEXT_PUBLIC_APP_URL}${successUrl}?session_id={CHECKOUT_SESSION_ID}\`,
|
|
21076
|
+
cancel_url: \`\${process.env.NEXT_PUBLIC_APP_URL}${cancelUrl}\`,
|
|
21077
|
+
...(customerId && { customer: customerId }),
|
|
21078
|
+
});
|
|
21079
|
+
|
|
21080
|
+
return NextResponse.json({ url: session.url });
|
|
21081
|
+
} catch (error${ts2 ? ": unknown" : ""}) {
|
|
21082
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
21083
|
+
return NextResponse.json({ error: message }, { status: 500 });
|
|
21084
|
+
}
|
|
21085
|
+
}
|
|
21086
|
+
`
|
|
21087
|
+
},
|
|
21088
|
+
{
|
|
21089
|
+
path: `${prefix}app/api/webhook/route${ext}`,
|
|
21090
|
+
description: "Stripe webhook handler with signature verification",
|
|
21091
|
+
content: `import { NextResponse } from 'next/server';
|
|
21092
|
+
import Stripe from 'stripe';
|
|
21093
|
+
|
|
21094
|
+
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
|
|
21095
|
+
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!;
|
|
21096
|
+
|
|
21097
|
+
export async function POST(request${ts2 ? ": Request" : ""}) {
|
|
21098
|
+
const body = await request.text();
|
|
21099
|
+
const signature = request.headers.get('stripe-signature')!;
|
|
21100
|
+
|
|
21101
|
+
let event${ts2 ? ": Stripe.Event" : ""};
|
|
21102
|
+
try {
|
|
21103
|
+
event = stripe.webhooks.constructEvent(body, signature, webhookSecret);
|
|
21104
|
+
} catch (error${ts2 ? ": unknown" : ""}) {
|
|
21105
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
21106
|
+
console.error('Webhook signature verification failed:', message);
|
|
21107
|
+
return NextResponse.json({ error: 'Invalid signature' }, { status: 400 });
|
|
21108
|
+
}
|
|
21109
|
+
|
|
21110
|
+
switch (event.type) {
|
|
21111
|
+
${events.map((e) => ` case '${e}':
|
|
21112
|
+
console.log('${e}:', event.data.object);
|
|
21113
|
+
// TODO: Handle ${e}
|
|
21114
|
+
break;`).join("\n")}
|
|
21115
|
+
default:
|
|
21116
|
+
console.log('Unhandled event type:', event.type);
|
|
21117
|
+
}
|
|
21118
|
+
|
|
21119
|
+
return NextResponse.json({ received: true });
|
|
21120
|
+
}
|
|
21121
|
+
`
|
|
21122
|
+
},
|
|
21123
|
+
{
|
|
21124
|
+
path: `${prefix}app/api/portal/route${ext}`,
|
|
21125
|
+
description: "Stripe Customer Portal redirect endpoint",
|
|
21126
|
+
content: `import { NextResponse } from 'next/server';
|
|
21127
|
+
import Stripe from 'stripe';
|
|
21128
|
+
|
|
21129
|
+
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
|
|
21130
|
+
|
|
21131
|
+
export async function POST(request${ts2 ? ": Request" : ""}) {
|
|
21132
|
+
try {
|
|
21133
|
+
const body = await request.json();
|
|
21134
|
+
const { customerId } = body;
|
|
21135
|
+
|
|
21136
|
+
if (!customerId) {
|
|
21137
|
+
return NextResponse.json({ error: 'customerId is required' }, { status: 400 });
|
|
21138
|
+
}
|
|
21139
|
+
|
|
21140
|
+
const session = await stripe.billingPortal.sessions.create({
|
|
21141
|
+
customer: customerId,
|
|
21142
|
+
return_url: \`\${process.env.NEXT_PUBLIC_APP_URL}/account\`,
|
|
21143
|
+
});
|
|
21144
|
+
|
|
21145
|
+
return NextResponse.json({ url: session.url });
|
|
21146
|
+
} catch (error${ts2 ? ": unknown" : ""}) {
|
|
21147
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
21148
|
+
return NextResponse.json({ error: message }, { status: 500 });
|
|
21149
|
+
}
|
|
21150
|
+
}
|
|
21151
|
+
`
|
|
21152
|
+
},
|
|
21153
|
+
{
|
|
21154
|
+
path: `${prefix}components/CheckoutButton${ext}x`,
|
|
21155
|
+
description: "React checkout button component",
|
|
21156
|
+
content: `'use client';
|
|
21157
|
+
|
|
21158
|
+
import { useState } from 'react';
|
|
21159
|
+
|
|
21160
|
+
${ts2 ? `interface CheckoutButtonProps {
|
|
21161
|
+
priceId: string;
|
|
21162
|
+
label?: string;
|
|
21163
|
+
}
|
|
21164
|
+
|
|
21165
|
+
` : ""}export default function CheckoutButton(${ts2 ? '{ priceId, label = "Subscribe" }: CheckoutButtonProps' : '{ priceId, label = "Subscribe" }'}) {
|
|
21166
|
+
const [loading, setLoading] = useState(false);
|
|
21167
|
+
|
|
21168
|
+
async function handleCheckout() {
|
|
21169
|
+
setLoading(true);
|
|
21170
|
+
try {
|
|
21171
|
+
const response = await fetch('/api/checkout', {
|
|
21172
|
+
method: 'POST',
|
|
21173
|
+
headers: { 'Content-Type': 'application/json' },
|
|
21174
|
+
body: JSON.stringify({ priceId }),
|
|
21175
|
+
});
|
|
21176
|
+
const data = await response.json();
|
|
21177
|
+
if (data.url) {
|
|
21178
|
+
window.location.href = data.url;
|
|
21179
|
+
}
|
|
21180
|
+
} catch (error) {
|
|
21181
|
+
console.error('Checkout error:', error);
|
|
21182
|
+
} finally {
|
|
21183
|
+
setLoading(false);
|
|
21184
|
+
}
|
|
21185
|
+
}
|
|
21186
|
+
|
|
21187
|
+
return (
|
|
21188
|
+
<button onClick={handleCheckout} disabled={loading}>
|
|
21189
|
+
{loading ? 'Redirecting...' : label}
|
|
21190
|
+
</button>
|
|
21191
|
+
);
|
|
21192
|
+
}
|
|
21193
|
+
`
|
|
21194
|
+
}
|
|
21195
|
+
];
|
|
21196
|
+
}
|
|
21197
|
+
function getNextjsPagesTemplates(info, config) {
|
|
21198
|
+
const ext = getFileExtension(info);
|
|
21199
|
+
const successUrl = config.successUrl || "/success";
|
|
21200
|
+
const cancelUrl = config.cancelUrl || "/pricing";
|
|
21201
|
+
const events = config.webhookEvents || DEFAULT_WEBHOOK_EVENTS;
|
|
21202
|
+
const ts2 = info.typescript;
|
|
21203
|
+
return [
|
|
21204
|
+
{
|
|
21205
|
+
path: `pages/api/checkout${ext}`,
|
|
21206
|
+
description: "Stripe Checkout session creation endpoint",
|
|
21207
|
+
content: `import type { NextApiRequest, NextApiResponse } from 'next';
|
|
21208
|
+
import Stripe from 'stripe';
|
|
21209
|
+
|
|
21210
|
+
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
|
|
21211
|
+
|
|
21212
|
+
export default async function handler(req${ts2 ? ": NextApiRequest" : ""}, res${ts2 ? ": NextApiResponse" : ""}) {
|
|
21213
|
+
if (req.method !== 'POST') {
|
|
21214
|
+
return res.status(405).json({ error: 'Method not allowed' });
|
|
21215
|
+
}
|
|
21216
|
+
|
|
21217
|
+
try {
|
|
21218
|
+
const { priceId, customerId } = req.body;
|
|
21219
|
+
|
|
21220
|
+
if (!priceId) {
|
|
21221
|
+
return res.status(400).json({ error: 'priceId is required' });
|
|
21222
|
+
}
|
|
21223
|
+
|
|
21224
|
+
const session = await stripe.checkout.sessions.create({
|
|
21225
|
+
mode: 'subscription',
|
|
21226
|
+
payment_method_types: ['card'],
|
|
21227
|
+
line_items: [{ price: priceId, quantity: 1 }],
|
|
21228
|
+
success_url: \`\${process.env.NEXT_PUBLIC_APP_URL}${successUrl}?session_id={CHECKOUT_SESSION_ID}\`,
|
|
21229
|
+
cancel_url: \`\${process.env.NEXT_PUBLIC_APP_URL}${cancelUrl}\`,
|
|
21230
|
+
...(customerId && { customer: customerId }),
|
|
21231
|
+
});
|
|
21232
|
+
|
|
21233
|
+
res.json({ url: session.url });
|
|
21234
|
+
} catch (error${ts2 ? ": unknown" : ""}) {
|
|
21235
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
21236
|
+
res.status(500).json({ error: message });
|
|
21237
|
+
}
|
|
21238
|
+
}
|
|
21239
|
+
`
|
|
21240
|
+
},
|
|
21241
|
+
{
|
|
21242
|
+
path: `pages/api/webhook${ext}`,
|
|
21243
|
+
description: "Stripe webhook handler with signature verification",
|
|
21244
|
+
content: `import type { NextApiRequest, NextApiResponse } from 'next';
|
|
21245
|
+
import Stripe from 'stripe';
|
|
21246
|
+
|
|
21247
|
+
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
|
|
21248
|
+
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!;
|
|
21249
|
+
|
|
21250
|
+
// Disable body parsing \u2014 Stripe needs raw body for signature verification
|
|
21251
|
+
export const config = { api: { bodyParser: false } };
|
|
21252
|
+
|
|
21253
|
+
async function getRawBody(req${ts2 ? ": NextApiRequest" : ""}): Promise<string> {
|
|
21254
|
+
return new Promise((resolve, reject) => {
|
|
21255
|
+
const chunks${ts2 ? ": Buffer[]" : ""} = [];
|
|
21256
|
+
req.on('data', (chunk${ts2 ? ": Buffer" : ""}) => chunks.push(chunk));
|
|
21257
|
+
req.on('end', () => resolve(Buffer.concat(chunks).toString()));
|
|
21258
|
+
req.on('error', reject);
|
|
21259
|
+
});
|
|
21260
|
+
}
|
|
21261
|
+
|
|
21262
|
+
export default async function handler(req${ts2 ? ": NextApiRequest" : ""}, res${ts2 ? ": NextApiResponse" : ""}) {
|
|
21263
|
+
if (req.method !== 'POST') {
|
|
21264
|
+
return res.status(405).json({ error: 'Method not allowed' });
|
|
21265
|
+
}
|
|
21266
|
+
|
|
21267
|
+
const body = await getRawBody(req);
|
|
21268
|
+
const signature = req.headers['stripe-signature'] as string;
|
|
21269
|
+
|
|
21270
|
+
let event${ts2 ? ": Stripe.Event" : ""};
|
|
21271
|
+
try {
|
|
21272
|
+
event = stripe.webhooks.constructEvent(body, signature, webhookSecret);
|
|
21273
|
+
} catch (error${ts2 ? ": unknown" : ""}) {
|
|
21274
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
21275
|
+
console.error('Webhook signature verification failed:', message);
|
|
21276
|
+
return res.status(400).json({ error: 'Invalid signature' });
|
|
21277
|
+
}
|
|
21278
|
+
|
|
21279
|
+
switch (event.type) {
|
|
21280
|
+
${events.map((e) => ` case '${e}':
|
|
21281
|
+
console.log('${e}:', event.data.object);
|
|
21282
|
+
// TODO: Handle ${e}
|
|
21283
|
+
break;`).join("\n")}
|
|
21284
|
+
default:
|
|
21285
|
+
console.log('Unhandled event type:', event.type);
|
|
21286
|
+
}
|
|
21287
|
+
|
|
21288
|
+
res.json({ received: true });
|
|
21289
|
+
}
|
|
21290
|
+
`
|
|
21291
|
+
},
|
|
21292
|
+
{
|
|
21293
|
+
path: `pages/api/portal${ext}`,
|
|
21294
|
+
description: "Stripe Customer Portal redirect endpoint",
|
|
21295
|
+
content: `import type { NextApiRequest, NextApiResponse } from 'next';
|
|
21296
|
+
import Stripe from 'stripe';
|
|
21297
|
+
|
|
21298
|
+
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
|
|
21299
|
+
|
|
21300
|
+
export default async function handler(req${ts2 ? ": NextApiRequest" : ""}, res${ts2 ? ": NextApiResponse" : ""}) {
|
|
21301
|
+
if (req.method !== 'POST') {
|
|
21302
|
+
return res.status(405).json({ error: 'Method not allowed' });
|
|
21303
|
+
}
|
|
21304
|
+
|
|
21305
|
+
try {
|
|
21306
|
+
const { customerId } = req.body;
|
|
21307
|
+
|
|
21308
|
+
if (!customerId) {
|
|
21309
|
+
return res.status(400).json({ error: 'customerId is required' });
|
|
21310
|
+
}
|
|
21311
|
+
|
|
21312
|
+
const session = await stripe.billingPortal.sessions.create({
|
|
21313
|
+
customer: customerId,
|
|
21314
|
+
return_url: \`\${process.env.NEXT_PUBLIC_APP_URL}/account\`,
|
|
21315
|
+
});
|
|
21316
|
+
|
|
21317
|
+
res.json({ url: session.url });
|
|
21318
|
+
} catch (error${ts2 ? ": unknown" : ""}) {
|
|
21319
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
21320
|
+
res.status(500).json({ error: message });
|
|
21321
|
+
}
|
|
21322
|
+
}
|
|
21323
|
+
`
|
|
21324
|
+
}
|
|
21325
|
+
];
|
|
21326
|
+
}
|
|
21327
|
+
function getExpressTemplates(info, config) {
|
|
21328
|
+
const ext = getFileExtension(info);
|
|
21329
|
+
const successUrl = config.successUrl || "/success";
|
|
21330
|
+
const cancelUrl = config.cancelUrl || "/pricing";
|
|
21331
|
+
const events = config.webhookEvents || DEFAULT_WEBHOOK_EVENTS;
|
|
21332
|
+
const ts2 = info.typescript;
|
|
21333
|
+
return [
|
|
21334
|
+
{
|
|
21335
|
+
path: `routes/checkout${ext}`,
|
|
21336
|
+
description: "Stripe Checkout session creation route",
|
|
21337
|
+
content: `import { Router } from 'express';
|
|
21338
|
+
${ts2 ? "import type { Request, Response } from 'express';\n" : ""}import Stripe from 'stripe';
|
|
21339
|
+
|
|
21340
|
+
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
|
|
21341
|
+
const router = Router();
|
|
21342
|
+
|
|
21343
|
+
router.post('/checkout', async (req${ts2 ? ": Request" : ""}, res${ts2 ? ": Response" : ""}) => {
|
|
21344
|
+
try {
|
|
21345
|
+
const { priceId, customerId } = req.body;
|
|
21346
|
+
|
|
21347
|
+
if (!priceId) {
|
|
21348
|
+
return res.status(400).json({ error: 'priceId is required' });
|
|
21349
|
+
}
|
|
21350
|
+
|
|
21351
|
+
const session = await stripe.checkout.sessions.create({
|
|
21352
|
+
mode: 'subscription',
|
|
21353
|
+
payment_method_types: ['card'],
|
|
21354
|
+
line_items: [{ price: priceId, quantity: 1 }],
|
|
21355
|
+
success_url: \`\${process.env.APP_URL}${successUrl}?session_id={CHECKOUT_SESSION_ID}\`,
|
|
21356
|
+
cancel_url: \`\${process.env.APP_URL}${cancelUrl}\`,
|
|
21357
|
+
...(customerId && { customer: customerId }),
|
|
21358
|
+
});
|
|
21359
|
+
|
|
21360
|
+
res.json({ url: session.url });
|
|
21361
|
+
} catch (error${ts2 ? ": unknown" : ""}) {
|
|
21362
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
21363
|
+
res.status(500).json({ error: message });
|
|
21364
|
+
}
|
|
21365
|
+
});
|
|
21366
|
+
|
|
21367
|
+
export default router;
|
|
21368
|
+
`
|
|
21369
|
+
},
|
|
21370
|
+
{
|
|
21371
|
+
path: `routes/webhook${ext}`,
|
|
21372
|
+
description: "Stripe webhook handler with signature verification",
|
|
21373
|
+
content: `import { Router } from 'express';
|
|
21374
|
+
${ts2 ? "import type { Request, Response } from 'express';\n" : ""}import Stripe from 'stripe';
|
|
21375
|
+
|
|
21376
|
+
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
|
|
21377
|
+
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!;
|
|
21378
|
+
const router = Router();
|
|
21379
|
+
|
|
21380
|
+
// IMPORTANT: This route needs raw body parsing.
|
|
21381
|
+
// Add to your main app: app.use('/webhook', express.raw({ type: 'application/json' }))
|
|
21382
|
+
router.post('/webhook', (req${ts2 ? ": Request" : ""}, res${ts2 ? ": Response" : ""}) => {
|
|
21383
|
+
const signature = req.headers['stripe-signature'] as string;
|
|
21384
|
+
|
|
21385
|
+
let event${ts2 ? ": Stripe.Event" : ""};
|
|
21386
|
+
try {
|
|
21387
|
+
event = stripe.webhooks.constructEvent(req.body, signature, webhookSecret);
|
|
21388
|
+
} catch (error${ts2 ? ": unknown" : ""}) {
|
|
21389
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
21390
|
+
console.error('Webhook signature verification failed:', message);
|
|
21391
|
+
return res.status(400).json({ error: 'Invalid signature' });
|
|
21392
|
+
}
|
|
21393
|
+
|
|
21394
|
+
switch (event.type) {
|
|
21395
|
+
${events.map((e) => ` case '${e}':
|
|
21396
|
+
console.log('${e}:', event.data.object);
|
|
21397
|
+
// TODO: Handle ${e}
|
|
21398
|
+
break;`).join("\n")}
|
|
21399
|
+
default:
|
|
21400
|
+
console.log('Unhandled event type:', event.type);
|
|
21401
|
+
}
|
|
21402
|
+
|
|
21403
|
+
res.json({ received: true });
|
|
21404
|
+
});
|
|
21405
|
+
|
|
21406
|
+
export default router;
|
|
21407
|
+
`
|
|
21408
|
+
},
|
|
21409
|
+
{
|
|
21410
|
+
path: `routes/portal${ext}`,
|
|
21411
|
+
description: "Stripe Customer Portal redirect route",
|
|
21412
|
+
content: `import { Router } from 'express';
|
|
21413
|
+
${ts2 ? "import type { Request, Response } from 'express';\n" : ""}import Stripe from 'stripe';
|
|
21414
|
+
|
|
21415
|
+
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
|
|
21416
|
+
const router = Router();
|
|
21417
|
+
|
|
21418
|
+
router.post('/portal', async (req${ts2 ? ": Request" : ""}, res${ts2 ? ": Response" : ""}) => {
|
|
21419
|
+
try {
|
|
21420
|
+
const { customerId } = req.body;
|
|
21421
|
+
|
|
21422
|
+
if (!customerId) {
|
|
21423
|
+
return res.status(400).json({ error: 'customerId is required' });
|
|
21424
|
+
}
|
|
21425
|
+
|
|
21426
|
+
const session = await stripe.billingPortal.sessions.create({
|
|
21427
|
+
customer: customerId,
|
|
21428
|
+
return_url: \`\${process.env.APP_URL}/account\`,
|
|
21429
|
+
});
|
|
21430
|
+
|
|
21431
|
+
res.json({ url: session.url });
|
|
21432
|
+
} catch (error${ts2 ? ": unknown" : ""}) {
|
|
21433
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
21434
|
+
res.status(500).json({ error: message });
|
|
21435
|
+
}
|
|
21436
|
+
});
|
|
21437
|
+
|
|
21438
|
+
export default router;
|
|
21439
|
+
`
|
|
21440
|
+
}
|
|
21441
|
+
];
|
|
21442
|
+
}
|
|
21443
|
+
function getFastifyTemplates(info, config) {
|
|
21444
|
+
const ext = getFileExtension(info);
|
|
21445
|
+
const successUrl = config.successUrl || "/success";
|
|
21446
|
+
const cancelUrl = config.cancelUrl || "/pricing";
|
|
21447
|
+
const events = config.webhookEvents || DEFAULT_WEBHOOK_EVENTS;
|
|
21448
|
+
const ts2 = info.typescript;
|
|
21449
|
+
return [
|
|
21450
|
+
{
|
|
21451
|
+
path: `routes/checkout${ext}`,
|
|
21452
|
+
description: "Stripe Checkout session creation route",
|
|
21453
|
+
content: `import Stripe from 'stripe';
|
|
21454
|
+
${ts2 ? "import type { FastifyInstance } from 'fastify';\n" : ""}
|
|
21455
|
+
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
|
|
21456
|
+
|
|
21457
|
+
export default async function checkoutRoutes(fastify${ts2 ? ": FastifyInstance" : ""}) {
|
|
21458
|
+
fastify.post('/checkout', async (request, reply) => {
|
|
21459
|
+
const { priceId, customerId } = request.body as any;
|
|
21460
|
+
|
|
21461
|
+
if (!priceId) {
|
|
21462
|
+
return reply.status(400).send({ error: 'priceId is required' });
|
|
21463
|
+
}
|
|
21464
|
+
|
|
21465
|
+
const session = await stripe.checkout.sessions.create({
|
|
21466
|
+
mode: 'subscription',
|
|
21467
|
+
payment_method_types: ['card'],
|
|
21468
|
+
line_items: [{ price: priceId, quantity: 1 }],
|
|
21469
|
+
success_url: \`\${process.env.APP_URL}${successUrl}?session_id={CHECKOUT_SESSION_ID}\`,
|
|
21470
|
+
cancel_url: \`\${process.env.APP_URL}${cancelUrl}\`,
|
|
21471
|
+
...(customerId && { customer: customerId }),
|
|
21472
|
+
});
|
|
21473
|
+
|
|
21474
|
+
return { url: session.url };
|
|
21475
|
+
});
|
|
21476
|
+
}
|
|
21477
|
+
`
|
|
21478
|
+
},
|
|
21479
|
+
{
|
|
21480
|
+
path: `routes/webhook${ext}`,
|
|
21481
|
+
description: "Stripe webhook handler with signature verification",
|
|
21482
|
+
content: `import Stripe from 'stripe';
|
|
21483
|
+
${ts2 ? "import type { FastifyInstance } from 'fastify';\n" : ""}
|
|
21484
|
+
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
|
|
21485
|
+
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!;
|
|
21486
|
+
|
|
21487
|
+
export default async function webhookRoutes(fastify${ts2 ? ": FastifyInstance" : ""}) {
|
|
21488
|
+
// Register raw body parser for webhook route
|
|
21489
|
+
fastify.addContentTypeParser('application/json', { parseAs: 'string' }, (req, body, done) => {
|
|
21490
|
+
done(null, body);
|
|
21491
|
+
});
|
|
21492
|
+
|
|
21493
|
+
fastify.post('/webhook', async (request, reply) => {
|
|
21494
|
+
const signature = request.headers['stripe-signature'] as string;
|
|
21495
|
+
|
|
21496
|
+
let event${ts2 ? ": Stripe.Event" : ""};
|
|
21497
|
+
try {
|
|
21498
|
+
event = stripe.webhooks.constructEvent(request.body as string, signature, webhookSecret);
|
|
21499
|
+
} catch (error${ts2 ? ": unknown" : ""}) {
|
|
21500
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
21501
|
+
fastify.log.error('Webhook signature verification failed:', message);
|
|
21502
|
+
return reply.status(400).send({ error: 'Invalid signature' });
|
|
21503
|
+
}
|
|
21504
|
+
|
|
21505
|
+
switch (event.type) {
|
|
21506
|
+
${events.map((e) => ` case '${e}':
|
|
21507
|
+
fastify.log.info('${e}:', event.data.object);
|
|
21508
|
+
// TODO: Handle ${e}
|
|
21509
|
+
break;`).join("\n")}
|
|
21510
|
+
default:
|
|
21511
|
+
fastify.log.info('Unhandled event type:', event.type);
|
|
21512
|
+
}
|
|
21513
|
+
|
|
21514
|
+
return { received: true };
|
|
21515
|
+
});
|
|
21516
|
+
}
|
|
21517
|
+
`
|
|
21518
|
+
},
|
|
21519
|
+
{
|
|
21520
|
+
path: `routes/portal${ext}`,
|
|
21521
|
+
description: "Stripe Customer Portal redirect route",
|
|
21522
|
+
content: `import Stripe from 'stripe';
|
|
21523
|
+
${ts2 ? "import type { FastifyInstance } from 'fastify';\n" : ""}
|
|
21524
|
+
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
|
|
21525
|
+
|
|
21526
|
+
export default async function portalRoutes(fastify${ts2 ? ": FastifyInstance" : ""}) {
|
|
21527
|
+
fastify.post('/portal', async (request, reply) => {
|
|
21528
|
+
const { customerId } = request.body as any;
|
|
21529
|
+
|
|
21530
|
+
if (!customerId) {
|
|
21531
|
+
return reply.status(400).send({ error: 'customerId is required' });
|
|
21532
|
+
}
|
|
21533
|
+
|
|
21534
|
+
const session = await stripe.billingPortal.sessions.create({
|
|
21535
|
+
customer: customerId,
|
|
21536
|
+
return_url: \`\${process.env.APP_URL}/account\`,
|
|
21537
|
+
});
|
|
21538
|
+
|
|
21539
|
+
return { url: session.url };
|
|
21540
|
+
});
|
|
21541
|
+
}
|
|
21542
|
+
`
|
|
21543
|
+
}
|
|
21544
|
+
];
|
|
21545
|
+
}
|
|
21546
|
+
function getRemixTemplates(info, config) {
|
|
21547
|
+
const ext = getFileExtension(info);
|
|
21548
|
+
const successUrl = config.successUrl || "/success";
|
|
21549
|
+
const cancelUrl = config.cancelUrl || "/pricing";
|
|
21550
|
+
const events = config.webhookEvents || DEFAULT_WEBHOOK_EVENTS;
|
|
21551
|
+
const ts2 = info.typescript;
|
|
21552
|
+
return [
|
|
21553
|
+
{
|
|
21554
|
+
path: `app/routes/api.checkout${ext}`,
|
|
21555
|
+
description: "Stripe Checkout session creation action",
|
|
21556
|
+
content: `import { json } from '@remix-run/node';
|
|
21557
|
+
${ts2 ? "import type { ActionFunctionArgs } from '@remix-run/node';\n" : ""}import Stripe from 'stripe';
|
|
21558
|
+
|
|
21559
|
+
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
|
|
21560
|
+
|
|
21561
|
+
export async function action({ request }${ts2 ? ": ActionFunctionArgs" : ""}) {
|
|
21562
|
+
const body = await request.json();
|
|
21563
|
+
const { priceId, customerId } = body;
|
|
21564
|
+
|
|
21565
|
+
if (!priceId) {
|
|
21566
|
+
return json({ error: 'priceId is required' }, { status: 400 });
|
|
21567
|
+
}
|
|
21568
|
+
|
|
21569
|
+
const session = await stripe.checkout.sessions.create({
|
|
21570
|
+
mode: 'subscription',
|
|
21571
|
+
payment_method_types: ['card'],
|
|
21572
|
+
line_items: [{ price: priceId, quantity: 1 }],
|
|
21573
|
+
success_url: \`\${process.env.APP_URL}${successUrl}?session_id={CHECKOUT_SESSION_ID}\`,
|
|
21574
|
+
cancel_url: \`\${process.env.APP_URL}${cancelUrl}\`,
|
|
21575
|
+
...(customerId && { customer: customerId }),
|
|
21576
|
+
});
|
|
21577
|
+
|
|
21578
|
+
return json({ url: session.url });
|
|
21579
|
+
}
|
|
21580
|
+
`
|
|
21581
|
+
},
|
|
21582
|
+
{
|
|
21583
|
+
path: `app/routes/api.webhook${ext}`,
|
|
21584
|
+
description: "Stripe webhook handler with signature verification",
|
|
21585
|
+
content: `import { json } from '@remix-run/node';
|
|
21586
|
+
${ts2 ? "import type { ActionFunctionArgs } from '@remix-run/node';\n" : ""}import Stripe from 'stripe';
|
|
21587
|
+
|
|
21588
|
+
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
|
|
21589
|
+
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!;
|
|
21590
|
+
|
|
21591
|
+
export async function action({ request }${ts2 ? ": ActionFunctionArgs" : ""}) {
|
|
21592
|
+
const body = await request.text();
|
|
21593
|
+
const signature = request.headers.get('stripe-signature')!;
|
|
21594
|
+
|
|
21595
|
+
let event${ts2 ? ": Stripe.Event" : ""};
|
|
21596
|
+
try {
|
|
21597
|
+
event = stripe.webhooks.constructEvent(body, signature, webhookSecret);
|
|
21598
|
+
} catch (error${ts2 ? ": unknown" : ""}) {
|
|
21599
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
21600
|
+
console.error('Webhook signature verification failed:', message);
|
|
21601
|
+
return json({ error: 'Invalid signature' }, { status: 400 });
|
|
21602
|
+
}
|
|
21603
|
+
|
|
21604
|
+
switch (event.type) {
|
|
21605
|
+
${events.map((e) => ` case '${e}':
|
|
21606
|
+
console.log('${e}:', event.data.object);
|
|
21607
|
+
// TODO: Handle ${e}
|
|
21608
|
+
break;`).join("\n")}
|
|
21609
|
+
default:
|
|
21610
|
+
console.log('Unhandled event type:', event.type);
|
|
21611
|
+
}
|
|
21612
|
+
|
|
21613
|
+
return json({ received: true });
|
|
21614
|
+
}
|
|
21615
|
+
`
|
|
21616
|
+
},
|
|
21617
|
+
{
|
|
21618
|
+
path: `app/routes/api.portal${ext}`,
|
|
21619
|
+
description: "Stripe Customer Portal redirect action",
|
|
21620
|
+
content: `import { json } from '@remix-run/node';
|
|
21621
|
+
${ts2 ? "import type { ActionFunctionArgs } from '@remix-run/node';\n" : ""}import Stripe from 'stripe';
|
|
21622
|
+
|
|
21623
|
+
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
|
|
21624
|
+
|
|
21625
|
+
export async function action({ request }${ts2 ? ": ActionFunctionArgs" : ""}) {
|
|
21626
|
+
const body = await request.json();
|
|
21627
|
+
const { customerId } = body;
|
|
21628
|
+
|
|
21629
|
+
if (!customerId) {
|
|
21630
|
+
return json({ error: 'customerId is required' }, { status: 400 });
|
|
21631
|
+
}
|
|
21632
|
+
|
|
21633
|
+
const session = await stripe.billingPortal.sessions.create({
|
|
21634
|
+
customer: customerId,
|
|
21635
|
+
return_url: \`\${process.env.APP_URL}/account\`,
|
|
21636
|
+
});
|
|
21637
|
+
|
|
21638
|
+
return json({ url: session.url });
|
|
21639
|
+
}
|
|
21640
|
+
`
|
|
21641
|
+
}
|
|
21642
|
+
];
|
|
21643
|
+
}
|
|
21644
|
+
function getGenericTemplates(info, config) {
|
|
21645
|
+
const ext = getFileExtension(info);
|
|
21646
|
+
const successUrl = config.successUrl || "/success";
|
|
21647
|
+
const cancelUrl = config.cancelUrl || "/pricing";
|
|
21648
|
+
const events = config.webhookEvents || DEFAULT_WEBHOOK_EVENTS;
|
|
21649
|
+
const ts2 = info.typescript;
|
|
21650
|
+
return [
|
|
21651
|
+
{
|
|
21652
|
+
path: `stripe/checkout${ext}`,
|
|
21653
|
+
description: "Stripe Checkout session creation function",
|
|
21654
|
+
content: `import Stripe from 'stripe';
|
|
21655
|
+
|
|
21656
|
+
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
|
|
21657
|
+
|
|
21658
|
+
export async function createCheckoutSession(priceId${ts2 ? ": string" : ""}, customerId${ts2 ? "?: string" : ""}) {
|
|
21659
|
+
const session = await stripe.checkout.sessions.create({
|
|
21660
|
+
mode: 'subscription',
|
|
21661
|
+
payment_method_types: ['card'],
|
|
21662
|
+
line_items: [{ price: priceId, quantity: 1 }],
|
|
21663
|
+
success_url: \`\${process.env.APP_URL}${successUrl}?session_id={CHECKOUT_SESSION_ID}\`,
|
|
21664
|
+
cancel_url: \`\${process.env.APP_URL}${cancelUrl}\`,
|
|
21665
|
+
...(customerId && { customer: customerId }),
|
|
21666
|
+
});
|
|
21667
|
+
|
|
21668
|
+
return session;
|
|
21669
|
+
}
|
|
21670
|
+
`
|
|
21671
|
+
},
|
|
21672
|
+
{
|
|
21673
|
+
path: `stripe/webhook${ext}`,
|
|
21674
|
+
description: "Stripe webhook event handler",
|
|
21675
|
+
content: `import Stripe from 'stripe';
|
|
21676
|
+
|
|
21677
|
+
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
|
|
21678
|
+
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!;
|
|
21679
|
+
|
|
21680
|
+
export function handleWebhookEvent(body${ts2 ? ": string" : ""}, signature${ts2 ? ": string" : ""}) {
|
|
21681
|
+
const event = stripe.webhooks.constructEvent(body, signature, webhookSecret);
|
|
21682
|
+
|
|
21683
|
+
switch (event.type) {
|
|
21684
|
+
${events.map((e) => ` case '${e}':
|
|
21685
|
+
console.log('${e}:', event.data.object);
|
|
21686
|
+
// TODO: Handle ${e}
|
|
21687
|
+
break;`).join("\n")}
|
|
21688
|
+
default:
|
|
21689
|
+
console.log('Unhandled event type:', event.type);
|
|
21690
|
+
}
|
|
21691
|
+
|
|
21692
|
+
return event;
|
|
21693
|
+
}
|
|
21694
|
+
`
|
|
21695
|
+
},
|
|
21696
|
+
{
|
|
21697
|
+
path: `stripe/portal${ext}`,
|
|
21698
|
+
description: "Stripe Customer Portal session creation function",
|
|
21699
|
+
content: `import Stripe from 'stripe';
|
|
21700
|
+
|
|
21701
|
+
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
|
|
21702
|
+
|
|
21703
|
+
export async function createPortalSession(customerId${ts2 ? ": string" : ""}) {
|
|
21704
|
+
const session = await stripe.billingPortal.sessions.create({
|
|
21705
|
+
customer: customerId,
|
|
21706
|
+
return_url: \`\${process.env.APP_URL}/account\`,
|
|
21707
|
+
});
|
|
21708
|
+
|
|
21709
|
+
return session;
|
|
21710
|
+
}
|
|
21711
|
+
`
|
|
21712
|
+
}
|
|
21713
|
+
];
|
|
21714
|
+
}
|
|
21715
|
+
|
|
21716
|
+
// scripts/lib/payments/env-manager.ts
|
|
21717
|
+
var fs9 = __toESM(require("fs"));
|
|
21718
|
+
var path7 = __toESM(require("path"));
|
|
21719
|
+
function readEnvFile(projectPath) {
|
|
21720
|
+
const envPath = path7.join(projectPath, ".env");
|
|
21721
|
+
if (!fs9.existsSync(envPath)) {
|
|
21722
|
+
return {};
|
|
21723
|
+
}
|
|
21724
|
+
const content = fs9.readFileSync(envPath, "utf-8");
|
|
21725
|
+
const vars = {};
|
|
21726
|
+
for (const line of content.split("\n")) {
|
|
21727
|
+
const trimmed = line.trim();
|
|
21728
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
21729
|
+
const eqIndex = trimmed.indexOf("=");
|
|
21730
|
+
if (eqIndex === -1) continue;
|
|
21731
|
+
const key = trimmed.slice(0, eqIndex).trim();
|
|
21732
|
+
let value = trimmed.slice(eqIndex + 1).trim();
|
|
21733
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
21734
|
+
value = value.slice(1, -1);
|
|
21735
|
+
}
|
|
21736
|
+
vars[key] = value;
|
|
21737
|
+
}
|
|
21738
|
+
return vars;
|
|
21739
|
+
}
|
|
21740
|
+
function writeEnvVars(projectPath, vars) {
|
|
21741
|
+
const envPath = path7.join(projectPath, ".env");
|
|
21742
|
+
const existing = readEnvFile(projectPath);
|
|
21743
|
+
const added = [];
|
|
21744
|
+
const lines = [];
|
|
21745
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
21746
|
+
if (existing[key]) continue;
|
|
21747
|
+
lines.push(`${key}=${value}`);
|
|
21748
|
+
added.push(key);
|
|
21749
|
+
}
|
|
21750
|
+
if (lines.length === 0) return added;
|
|
21751
|
+
const prefix = fs9.existsSync(envPath) ? "\n" : "";
|
|
21752
|
+
const content = prefix + lines.join("\n") + "\n";
|
|
21753
|
+
fs9.appendFileSync(envPath, content);
|
|
21754
|
+
return added;
|
|
21755
|
+
}
|
|
21756
|
+
function ensureGitignore(projectPath) {
|
|
21757
|
+
const gitignorePath = path7.join(projectPath, ".gitignore");
|
|
21758
|
+
let content = "";
|
|
21759
|
+
let modified = false;
|
|
21760
|
+
if (fs9.existsSync(gitignorePath)) {
|
|
21761
|
+
content = fs9.readFileSync(gitignorePath, "utf-8");
|
|
21762
|
+
}
|
|
21763
|
+
const lines = content.split("\n").map((l2) => l2.trim());
|
|
21764
|
+
if (!lines.includes(".env")) {
|
|
21765
|
+
const suffix = content.endsWith("\n") || content === "" ? "" : "\n";
|
|
21766
|
+
fs9.appendFileSync(gitignorePath, `${suffix}.env
|
|
21767
|
+
`);
|
|
21768
|
+
modified = true;
|
|
21769
|
+
}
|
|
21770
|
+
return modified;
|
|
21771
|
+
}
|
|
21772
|
+
|
|
21773
|
+
// scripts/skills/payments.ts
|
|
21774
|
+
var EMPTY_STATE = {
|
|
21775
|
+
last_updated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
21776
|
+
integration_status: {
|
|
21777
|
+
sdk_installed: false,
|
|
21778
|
+
api_key_valid: false,
|
|
21779
|
+
products_created: false,
|
|
21780
|
+
checkout_endpoint: false,
|
|
21781
|
+
webhook_endpoint: false,
|
|
21782
|
+
portal_endpoint: false
|
|
21783
|
+
},
|
|
21784
|
+
framework: null,
|
|
21785
|
+
products: [],
|
|
21786
|
+
generated_files: []
|
|
21787
|
+
};
|
|
21788
|
+
var PAYMENTS_PREFIXES = [
|
|
21789
|
+
"payments-setup-stripe",
|
|
21790
|
+
"payments-add-checkout",
|
|
21791
|
+
"payments-add-webhook",
|
|
21792
|
+
"payments-add-portal",
|
|
21793
|
+
"payments-check-status"
|
|
21794
|
+
];
|
|
21795
|
+
function isPaymentsTask(taskId) {
|
|
21796
|
+
return PAYMENTS_PREFIXES.some((prefix) => taskId.startsWith(prefix));
|
|
21797
|
+
}
|
|
21798
|
+
async function executePaymentsTask(taskId, description, _businessContext) {
|
|
21799
|
+
try {
|
|
21800
|
+
switch (taskId) {
|
|
21801
|
+
case "payments-setup-stripe":
|
|
21802
|
+
return await executeFullSetup();
|
|
21803
|
+
case "payments-add-checkout":
|
|
21804
|
+
return await executeAddEndpoint("checkout");
|
|
21805
|
+
case "payments-add-webhook":
|
|
21806
|
+
return await executeAddEndpoint("webhook");
|
|
21807
|
+
case "payments-add-portal":
|
|
21808
|
+
return await executeAddEndpoint("portal");
|
|
21809
|
+
case "payments-check-status":
|
|
21810
|
+
return await executeCheckStatus();
|
|
21811
|
+
default:
|
|
21812
|
+
return { success: false, output: `Unknown payments task: ${taskId}` };
|
|
21813
|
+
}
|
|
21814
|
+
} catch (error) {
|
|
21815
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
21816
|
+
return { success: false, output: `Payments task failed: ${errMsg}` };
|
|
21817
|
+
}
|
|
21818
|
+
}
|
|
21819
|
+
function readPaymentsFreshness() {
|
|
21820
|
+
const result = {
|
|
21821
|
+
configured: false,
|
|
21822
|
+
completeness: 0,
|
|
21823
|
+
missing: []
|
|
21824
|
+
};
|
|
21825
|
+
if (!fs10.existsSync(PAYMENTS_FILE)) {
|
|
21826
|
+
result.missing = ["sdk", "api_key", "products", "checkout", "webhook", "portal"];
|
|
21827
|
+
return result;
|
|
21828
|
+
}
|
|
21829
|
+
try {
|
|
21830
|
+
const state = JSON.parse(fs10.readFileSync(PAYMENTS_FILE, "utf-8"));
|
|
21831
|
+
const status = state.integration_status;
|
|
21832
|
+
const fields = Object.entries(status);
|
|
21833
|
+
const completed = fields.filter(([, v2]) => v2).length;
|
|
21834
|
+
result.configured = completed > 0;
|
|
21835
|
+
result.completeness = Math.round(completed / fields.length * 100);
|
|
21836
|
+
if (!status.sdk_installed) result.missing.push("sdk");
|
|
21837
|
+
if (!status.api_key_valid) result.missing.push("api_key");
|
|
21838
|
+
if (!status.products_created) result.missing.push("products");
|
|
21839
|
+
if (!status.checkout_endpoint) result.missing.push("checkout");
|
|
21840
|
+
if (!status.webhook_endpoint) result.missing.push("webhook");
|
|
21841
|
+
if (!status.portal_endpoint) result.missing.push("portal");
|
|
21842
|
+
} catch {
|
|
21843
|
+
result.missing = ["sdk", "api_key", "products", "checkout", "webhook", "portal"];
|
|
21844
|
+
}
|
|
21845
|
+
return result;
|
|
21846
|
+
}
|
|
21847
|
+
function loadState() {
|
|
21848
|
+
if (!fs10.existsSync(PAYMENTS_FILE)) {
|
|
21849
|
+
return { ...EMPTY_STATE };
|
|
21850
|
+
}
|
|
21851
|
+
try {
|
|
21852
|
+
return JSON.parse(fs10.readFileSync(PAYMENTS_FILE, "utf-8"));
|
|
21853
|
+
} catch {
|
|
21854
|
+
return { ...EMPTY_STATE };
|
|
21855
|
+
}
|
|
21856
|
+
}
|
|
21857
|
+
function saveState(state) {
|
|
21858
|
+
state.last_updated = (/* @__PURE__ */ new Date()).toISOString();
|
|
21859
|
+
const dir = path8.dirname(PAYMENTS_FILE);
|
|
21860
|
+
if (!fs10.existsSync(dir)) {
|
|
21861
|
+
fs10.mkdirSync(dir, { recursive: true });
|
|
21862
|
+
}
|
|
21863
|
+
fs10.writeFileSync(PAYMENTS_FILE, JSON.stringify(state, null, 2) + "\n");
|
|
21864
|
+
}
|
|
21865
|
+
function getUserProjectPath() {
|
|
21866
|
+
const configPath = path8.join(PROJECT_DIR, "data", "config.json");
|
|
21867
|
+
if (fs10.existsSync(configPath)) {
|
|
21868
|
+
try {
|
|
21869
|
+
const config = JSON.parse(fs10.readFileSync(configPath, "utf-8"));
|
|
21870
|
+
if (config.repos?.[0]?.path) {
|
|
21871
|
+
const repoPath = config.repos[0].path;
|
|
21872
|
+
if (fs10.existsSync(repoPath)) {
|
|
21873
|
+
return repoPath;
|
|
21874
|
+
}
|
|
21875
|
+
}
|
|
21876
|
+
} catch {
|
|
21877
|
+
}
|
|
21878
|
+
}
|
|
21879
|
+
return PROJECT_DIR;
|
|
21880
|
+
}
|
|
21881
|
+
function isStripeInstalled(projectPath) {
|
|
21882
|
+
try {
|
|
21883
|
+
require.resolve("stripe", { paths: [projectPath] });
|
|
21884
|
+
return true;
|
|
21885
|
+
} catch {
|
|
21886
|
+
return false;
|
|
21887
|
+
}
|
|
21888
|
+
}
|
|
21889
|
+
function installStripe(projectPath) {
|
|
21890
|
+
try {
|
|
21891
|
+
(0, import_child_process4.execSync)("npm install stripe", {
|
|
21892
|
+
cwd: projectPath,
|
|
21893
|
+
encoding: "utf-8",
|
|
21894
|
+
timeout: 6e4,
|
|
21895
|
+
stdio: "pipe"
|
|
21896
|
+
});
|
|
21897
|
+
return true;
|
|
21898
|
+
} catch {
|
|
21899
|
+
return false;
|
|
21900
|
+
}
|
|
21901
|
+
}
|
|
21902
|
+
function writeGeneratedFile(projectPath, file) {
|
|
21903
|
+
const fullPath = path8.join(projectPath, file.path);
|
|
21904
|
+
if (fs10.existsSync(fullPath)) {
|
|
21905
|
+
return false;
|
|
21906
|
+
}
|
|
21907
|
+
const dir = path8.dirname(fullPath);
|
|
21908
|
+
if (!fs10.existsSync(dir)) {
|
|
21909
|
+
fs10.mkdirSync(dir, { recursive: true });
|
|
21910
|
+
}
|
|
21911
|
+
fs10.writeFileSync(fullPath, file.content);
|
|
21912
|
+
return true;
|
|
21913
|
+
}
|
|
21914
|
+
async function executeFullSetup() {
|
|
21915
|
+
const lines = ["## Stripe Integration Setup\n"];
|
|
21916
|
+
const state = loadState();
|
|
21917
|
+
const projectPath = getUserProjectPath();
|
|
21918
|
+
lines.push("### Step 1: Framework Detection");
|
|
21919
|
+
const frameworkInfo = detectFramework(projectPath);
|
|
21920
|
+
state.framework = frameworkInfo;
|
|
21921
|
+
lines.push(`- Framework: ${frameworkInfo.framework}`);
|
|
21922
|
+
lines.push(`- TypeScript: ${frameworkInfo.typescript}`);
|
|
21923
|
+
lines.push(`- App Router: ${frameworkInfo.appRouter}`);
|
|
21924
|
+
lines.push(`- Src directory: ${frameworkInfo.srcDir}`);
|
|
21925
|
+
lines.push("");
|
|
21926
|
+
lines.push("### Step 2: Stripe SDK");
|
|
21927
|
+
if (isStripeInstalled(projectPath)) {
|
|
21928
|
+
lines.push("- Stripe SDK: already installed");
|
|
21929
|
+
state.integration_status.sdk_installed = true;
|
|
21930
|
+
} else {
|
|
21931
|
+
lines.push("- Installing stripe...");
|
|
21932
|
+
if (installStripe(projectPath)) {
|
|
21933
|
+
lines.push("- Stripe SDK: installed successfully");
|
|
21934
|
+
state.integration_status.sdk_installed = true;
|
|
21935
|
+
} else {
|
|
21936
|
+
lines.push("- Failed to install stripe. Run manually: npm install stripe");
|
|
21937
|
+
saveState(state);
|
|
21938
|
+
return { success: false, output: lines.join("\n") };
|
|
21939
|
+
}
|
|
21940
|
+
}
|
|
21941
|
+
lines.push("");
|
|
21942
|
+
lines.push("### Step 3: API Key Validation");
|
|
21943
|
+
const envVars = readEnvFile(projectPath);
|
|
21944
|
+
const secretKey = envVars["STRIPE_SECRET_KEY"] || process.env.STRIPE_SECRET_KEY;
|
|
21945
|
+
if (!secretKey) {
|
|
21946
|
+
lines.push("- STRIPE_SECRET_KEY not found in .env or environment.");
|
|
21947
|
+
lines.push("- Add it to your .env file: STRIPE_SECRET_KEY=sk_test_...");
|
|
21948
|
+
lines.push("- Get your key from: https://dashboard.stripe.com/apikeys");
|
|
21949
|
+
state.integration_status.api_key_valid = false;
|
|
21950
|
+
saveState(state);
|
|
21951
|
+
return { success: false, output: lines.join("\n") };
|
|
21952
|
+
}
|
|
21953
|
+
const stripe = createStripeClient(secretKey, projectPath);
|
|
21954
|
+
const validation = await validateApiKey(stripe);
|
|
21955
|
+
if (!validation.valid) {
|
|
21956
|
+
lines.push(`- API key invalid: ${validation.error}`);
|
|
21957
|
+
lines.push("- Check your STRIPE_SECRET_KEY and try again.");
|
|
21958
|
+
state.integration_status.api_key_valid = false;
|
|
21959
|
+
saveState(state);
|
|
21960
|
+
return { success: false, output: lines.join("\n") };
|
|
21961
|
+
}
|
|
21962
|
+
lines.push(`- API key valid (account: ${validation.accountName})`);
|
|
21963
|
+
state.integration_status.api_key_valid = true;
|
|
21964
|
+
lines.push("");
|
|
21965
|
+
lines.push("### Step 4: Products");
|
|
21966
|
+
const existing = await findExistingProducts(stripe, "Pro");
|
|
21967
|
+
if (existing.length > 0) {
|
|
21968
|
+
lines.push(`- Found ${existing.length} existing product(s): ${existing.map((p) => p.name).join(", ")}`);
|
|
21969
|
+
state.integration_status.products_created = true;
|
|
21970
|
+
state.products = existing.map((p) => ({
|
|
21971
|
+
name: p.name,
|
|
21972
|
+
stripe_product_id: p.id,
|
|
21973
|
+
stripe_price_id: null,
|
|
21974
|
+
amount_cents: null,
|
|
21975
|
+
currency: "usd",
|
|
21976
|
+
interval: null
|
|
21977
|
+
}));
|
|
21978
|
+
} else {
|
|
21979
|
+
lines.push("- No existing products found. Creating default product...");
|
|
21980
|
+
try {
|
|
21981
|
+
const product = await createProduct(stripe, {
|
|
21982
|
+
name: "Pro Plan",
|
|
21983
|
+
description: "Pro subscription plan"
|
|
21984
|
+
});
|
|
21985
|
+
const price = await createPrice(stripe, {
|
|
21986
|
+
productId: product.id,
|
|
21987
|
+
unitAmount: 2900,
|
|
21988
|
+
currency: "usd",
|
|
21989
|
+
interval: "month"
|
|
21990
|
+
});
|
|
21991
|
+
lines.push(`- Created product: ${product.name} (${product.id})`);
|
|
21992
|
+
lines.push(`- Created price: $29/mo (${price.id})`);
|
|
21993
|
+
state.integration_status.products_created = true;
|
|
21994
|
+
state.products = [{
|
|
21995
|
+
name: product.name,
|
|
21996
|
+
stripe_product_id: product.id,
|
|
21997
|
+
stripe_price_id: price.id,
|
|
21998
|
+
amount_cents: 2900,
|
|
21999
|
+
currency: "usd",
|
|
22000
|
+
interval: "month"
|
|
22001
|
+
}];
|
|
22002
|
+
} catch (error) {
|
|
22003
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
22004
|
+
lines.push(`- Failed to create products: ${msg}`);
|
|
22005
|
+
}
|
|
22006
|
+
}
|
|
22007
|
+
lines.push("");
|
|
22008
|
+
lines.push("### Step 5: Code Generation");
|
|
22009
|
+
const templates = getTemplatesForFramework(frameworkInfo);
|
|
22010
|
+
const written = [];
|
|
22011
|
+
const skipped = [];
|
|
22012
|
+
for (const template of templates) {
|
|
22013
|
+
if (writeGeneratedFile(projectPath, template)) {
|
|
22014
|
+
written.push(template.path);
|
|
22015
|
+
lines.push(`- Created: ${template.path} (${template.description})`);
|
|
22016
|
+
} else {
|
|
22017
|
+
skipped.push(template.path);
|
|
22018
|
+
lines.push(`- Skipped: ${template.path} (already exists)`);
|
|
22019
|
+
}
|
|
22020
|
+
}
|
|
22021
|
+
state.generated_files = Array.from(/* @__PURE__ */ new Set([...state.generated_files, ...written]));
|
|
22022
|
+
const allPaths = templates.map((t) => t.path);
|
|
22023
|
+
const checkoutFiles = allPaths.filter((p) => p.includes("checkout"));
|
|
22024
|
+
const webhookFiles = allPaths.filter((p) => p.includes("webhook"));
|
|
22025
|
+
const portalFiles = allPaths.filter((p) => p.includes("portal"));
|
|
22026
|
+
state.integration_status.checkout_endpoint = checkoutFiles.some((f) => written.includes(f) || skipped.includes(f));
|
|
22027
|
+
state.integration_status.webhook_endpoint = webhookFiles.some((f) => written.includes(f) || skipped.includes(f));
|
|
22028
|
+
state.integration_status.portal_endpoint = portalFiles.some((f) => written.includes(f) || skipped.includes(f));
|
|
22029
|
+
lines.push("");
|
|
22030
|
+
lines.push("### Step 6: Environment Variables");
|
|
22031
|
+
const addedVars = writeEnvVars(projectPath, {
|
|
22032
|
+
STRIPE_SECRET_KEY: secretKey,
|
|
22033
|
+
STRIPE_WEBHOOK_SECRET: "whsec_... # Replace with your webhook secret from Stripe Dashboard"
|
|
22034
|
+
});
|
|
22035
|
+
if (addedVars.length > 0) {
|
|
22036
|
+
lines.push(`- Added to .env: ${addedVars.join(", ")}`);
|
|
22037
|
+
} else {
|
|
22038
|
+
lines.push("- All required env vars already present");
|
|
22039
|
+
}
|
|
22040
|
+
const gitignoreModified = ensureGitignore(projectPath);
|
|
22041
|
+
if (gitignoreModified) {
|
|
22042
|
+
lines.push("- Added .env to .gitignore");
|
|
22043
|
+
}
|
|
22044
|
+
lines.push("");
|
|
22045
|
+
saveState(state);
|
|
22046
|
+
const status = state.integration_status;
|
|
22047
|
+
const completed = Object.values(status).filter(Boolean).length;
|
|
22048
|
+
const total = Object.values(status).length;
|
|
22049
|
+
lines.push(`### Summary: ${completed}/${total} steps complete (${Math.round(completed / total * 100)}%)`);
|
|
22050
|
+
if (completed < total) {
|
|
22051
|
+
const missing = Object.entries(status).filter(([, v2]) => !v2).map(([k]) => k);
|
|
22052
|
+
lines.push(`Missing: ${missing.join(", ")}`);
|
|
22053
|
+
}
|
|
22054
|
+
return { success: true, output: lines.join("\n") };
|
|
22055
|
+
}
|
|
22056
|
+
async function executeAddEndpoint(type) {
|
|
22057
|
+
const lines = [`## Add Stripe ${type} endpoint
|
|
22058
|
+
`];
|
|
22059
|
+
const state = loadState();
|
|
22060
|
+
const projectPath = getUserProjectPath();
|
|
22061
|
+
const frameworkInfo = state.framework || detectFramework(projectPath);
|
|
22062
|
+
state.framework = frameworkInfo;
|
|
22063
|
+
const templates = getTemplatesForFramework(frameworkInfo);
|
|
22064
|
+
const matching = templates.filter((t) => t.path.includes(type));
|
|
22065
|
+
if (matching.length === 0) {
|
|
22066
|
+
return { success: false, output: `No ${type} template found for framework: ${frameworkInfo.framework}` };
|
|
22067
|
+
}
|
|
22068
|
+
for (const template of matching) {
|
|
22069
|
+
if (writeGeneratedFile(projectPath, template)) {
|
|
22070
|
+
lines.push(`Created: ${template.path} \u2014 ${template.description}`);
|
|
22071
|
+
state.generated_files = Array.from(/* @__PURE__ */ new Set([...state.generated_files, template.path]));
|
|
22072
|
+
} else {
|
|
22073
|
+
lines.push(`Skipped: ${template.path} \u2014 file already exists`);
|
|
22074
|
+
}
|
|
22075
|
+
}
|
|
22076
|
+
const statusKey = `${type}_endpoint`;
|
|
22077
|
+
state.integration_status[statusKey] = true;
|
|
22078
|
+
saveState(state);
|
|
22079
|
+
return { success: true, output: lines.join("\n") };
|
|
22080
|
+
}
|
|
22081
|
+
async function executeCheckStatus() {
|
|
22082
|
+
const lines = ["## Stripe Integration Status\n"];
|
|
22083
|
+
const state = loadState();
|
|
22084
|
+
const projectPath = getUserProjectPath();
|
|
22085
|
+
const sdkInstalled = isStripeInstalled(projectPath);
|
|
22086
|
+
state.integration_status.sdk_installed = sdkInstalled;
|
|
22087
|
+
lines.push(`- SDK installed: ${sdkInstalled ? "yes" : "no"}`);
|
|
22088
|
+
const envVars = readEnvFile(projectPath);
|
|
22089
|
+
const secretKey = envVars["STRIPE_SECRET_KEY"] || process.env.STRIPE_SECRET_KEY;
|
|
22090
|
+
if (secretKey && sdkInstalled) {
|
|
22091
|
+
try {
|
|
22092
|
+
const stripe = createStripeClient(secretKey, projectPath);
|
|
22093
|
+
const validation = await validateApiKey(stripe);
|
|
22094
|
+
state.integration_status.api_key_valid = validation.valid;
|
|
22095
|
+
lines.push(`- API key: ${validation.valid ? `valid (${validation.accountName})` : `invalid (${validation.error})`}`);
|
|
22096
|
+
if (validation.valid) {
|
|
22097
|
+
const products = await findExistingProducts(stripe, "");
|
|
22098
|
+
state.integration_status.products_created = products.length > 0;
|
|
22099
|
+
lines.push(`- Products: ${products.length} found`);
|
|
22100
|
+
}
|
|
22101
|
+
} catch (error) {
|
|
22102
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
22103
|
+
lines.push(`- API key check failed: ${msg}`);
|
|
22104
|
+
}
|
|
22105
|
+
} else {
|
|
22106
|
+
state.integration_status.api_key_valid = false;
|
|
22107
|
+
lines.push(`- API key: ${secretKey ? "present but SDK not installed" : "not found"}`);
|
|
22108
|
+
}
|
|
22109
|
+
const hasWebhookSecret = !!(envVars["STRIPE_WEBHOOK_SECRET"] || process.env.STRIPE_WEBHOOK_SECRET);
|
|
22110
|
+
lines.push(`- Webhook secret: ${hasWebhookSecret ? "configured" : "not set"}`);
|
|
22111
|
+
const framework = state.framework || detectFramework(projectPath);
|
|
22112
|
+
const templates = getTemplatesForFramework(framework);
|
|
22113
|
+
for (const template of templates) {
|
|
22114
|
+
const exists = fs10.existsSync(path8.join(projectPath, template.path));
|
|
22115
|
+
const type = template.path.includes("checkout") ? "checkout" : template.path.includes("webhook") ? "webhook" : template.path.includes("portal") ? "portal" : null;
|
|
22116
|
+
if (type) {
|
|
22117
|
+
const statusKey = `${type}_endpoint`;
|
|
22118
|
+
state.integration_status[statusKey] = exists;
|
|
22119
|
+
}
|
|
22120
|
+
lines.push(`- ${template.path}: ${exists ? "exists" : "missing"}`);
|
|
22121
|
+
}
|
|
22122
|
+
lines.push("");
|
|
22123
|
+
const status = state.integration_status;
|
|
22124
|
+
const completed = Object.values(status).filter(Boolean).length;
|
|
22125
|
+
const total = Object.values(status).length;
|
|
22126
|
+
lines.push(`**Completeness: ${completed}/${total} (${Math.round(completed / total * 100)}%)**`);
|
|
22127
|
+
saveState(state);
|
|
22128
|
+
return { success: true, output: lines.join("\n") };
|
|
22129
|
+
}
|
|
22130
|
+
|
|
22131
|
+
// scripts/lib/run.ts
|
|
22132
|
+
var path9 = __toESM(require("path"));
|
|
20711
22133
|
function resolveScript(baseDir, scriptName) {
|
|
20712
|
-
const isCompiled =
|
|
22134
|
+
const isCompiled = path9.extname(__filename) === ".js";
|
|
20713
22135
|
const resolvedName = isCompiled ? scriptName.replace(/\.ts$/, ".js") : scriptName;
|
|
20714
|
-
const scriptPath =
|
|
22136
|
+
const scriptPath = path9.join(baseDir, resolvedName);
|
|
20715
22137
|
return isCompiled ? { command: "node", args: [scriptPath] } : { command: "npx", args: ["tsx", scriptPath] };
|
|
20716
22138
|
}
|
|
20717
22139
|
|
|
@@ -20765,8 +22187,8 @@ function groupTasksByConflicts(tasks) {
|
|
|
20765
22187
|
}
|
|
20766
22188
|
|
|
20767
22189
|
// scripts/lib/json-lock.ts
|
|
20768
|
-
var
|
|
20769
|
-
var
|
|
22190
|
+
var fs11 = __toESM(require("fs"));
|
|
22191
|
+
var path10 = __toESM(require("path"));
|
|
20770
22192
|
var DEFAULT_MAX_WAIT_MS = 1e4;
|
|
20771
22193
|
var BASE_BACKOFF_MS = 50;
|
|
20772
22194
|
var MAX_BACKOFF_MS = 150;
|
|
@@ -20774,10 +22196,10 @@ function withFileLock(lockPath, fn, maxWaitMs = DEFAULT_MAX_WAIT_MS) {
|
|
|
20774
22196
|
const startTime = Date.now();
|
|
20775
22197
|
while (true) {
|
|
20776
22198
|
try {
|
|
20777
|
-
|
|
22199
|
+
fs11.mkdirSync(lockPath, { recursive: false });
|
|
20778
22200
|
try {
|
|
20779
|
-
|
|
20780
|
-
|
|
22201
|
+
fs11.writeFileSync(
|
|
22202
|
+
path10.join(lockPath, "owner"),
|
|
20781
22203
|
JSON.stringify({ pid: process.pid, acquired: Date.now() })
|
|
20782
22204
|
);
|
|
20783
22205
|
} catch {
|
|
@@ -20806,16 +22228,16 @@ function withFileLock(lockPath, fn, maxWaitMs = DEFAULT_MAX_WAIT_MS) {
|
|
|
20806
22228
|
}
|
|
20807
22229
|
function atomicWriteFileSync(filePath, content) {
|
|
20808
22230
|
const tmpPath = `${filePath}.${process.pid}.tmp`;
|
|
20809
|
-
|
|
20810
|
-
|
|
22231
|
+
fs11.writeFileSync(tmpPath, content, "utf-8");
|
|
22232
|
+
fs11.renameSync(tmpPath, filePath);
|
|
20811
22233
|
}
|
|
20812
22234
|
function releaseLock(lockPath) {
|
|
20813
22235
|
try {
|
|
20814
|
-
const ownerFile =
|
|
20815
|
-
if (
|
|
20816
|
-
|
|
22236
|
+
const ownerFile = path10.join(lockPath, "owner");
|
|
22237
|
+
if (fs11.existsSync(ownerFile)) {
|
|
22238
|
+
fs11.unlinkSync(ownerFile);
|
|
20817
22239
|
}
|
|
20818
|
-
|
|
22240
|
+
fs11.rmdirSync(lockPath);
|
|
20819
22241
|
} catch {
|
|
20820
22242
|
}
|
|
20821
22243
|
}
|
|
@@ -20826,57 +22248,57 @@ function sleepSync(ms2) {
|
|
|
20826
22248
|
}
|
|
20827
22249
|
|
|
20828
22250
|
// scripts/lib/worktree.ts
|
|
20829
|
-
var
|
|
20830
|
-
var
|
|
22251
|
+
var fs13 = __toESM(require("fs"));
|
|
22252
|
+
var path12 = __toESM(require("path"));
|
|
20831
22253
|
|
|
20832
22254
|
// scripts/lib/git-utils.ts
|
|
20833
|
-
var
|
|
20834
|
-
var
|
|
20835
|
-
var
|
|
22255
|
+
var import_child_process5 = require("child_process");
|
|
22256
|
+
var fs12 = __toESM(require("fs"));
|
|
22257
|
+
var path11 = __toESM(require("path"));
|
|
20836
22258
|
function cleanGitState(workspacePath) {
|
|
20837
|
-
if (!
|
|
20838
|
-
const gitDir =
|
|
20839
|
-
if (!
|
|
22259
|
+
if (!fs12.existsSync(workspacePath)) return;
|
|
22260
|
+
const gitDir = path11.join(workspacePath, ".git");
|
|
22261
|
+
if (!fs12.existsSync(gitDir)) return;
|
|
20840
22262
|
let actualGitDir = gitDir;
|
|
20841
22263
|
try {
|
|
20842
|
-
const stat =
|
|
22264
|
+
const stat = fs12.statSync(gitDir);
|
|
20843
22265
|
if (stat.isFile()) {
|
|
20844
|
-
const content =
|
|
22266
|
+
const content = fs12.readFileSync(gitDir, "utf-8").trim();
|
|
20845
22267
|
const match = content.match(/^gitdir:\s+(.+)$/);
|
|
20846
22268
|
if (match) actualGitDir = match[1];
|
|
20847
22269
|
}
|
|
20848
22270
|
} catch {
|
|
20849
22271
|
return;
|
|
20850
22272
|
}
|
|
20851
|
-
const lockFile =
|
|
20852
|
-
if (
|
|
22273
|
+
const lockFile = path11.join(actualGitDir, "index.lock");
|
|
22274
|
+
if (fs12.existsSync(lockFile)) {
|
|
20853
22275
|
try {
|
|
20854
|
-
const result = (0,
|
|
22276
|
+
const result = (0, import_child_process5.execSync)(
|
|
20855
22277
|
`lsof "${lockFile}" 2>/dev/null || true`,
|
|
20856
22278
|
{ encoding: "utf-8", timeout: 5e3 }
|
|
20857
22279
|
).trim();
|
|
20858
22280
|
if (!result) {
|
|
20859
|
-
|
|
22281
|
+
fs12.unlinkSync(lockFile);
|
|
20860
22282
|
console.log(`[pre-flight] Removed stale .git/index.lock in ${workspacePath}`);
|
|
20861
22283
|
}
|
|
20862
22284
|
} catch {
|
|
20863
22285
|
try {
|
|
20864
|
-
|
|
22286
|
+
fs12.unlinkSync(lockFile);
|
|
20865
22287
|
console.log(`[pre-flight] Removed stale .git/index.lock in ${workspacePath}`);
|
|
20866
22288
|
} catch {
|
|
20867
22289
|
}
|
|
20868
22290
|
}
|
|
20869
22291
|
}
|
|
20870
22292
|
const staleOps = [
|
|
20871
|
-
{ marker:
|
|
20872
|
-
{ marker:
|
|
20873
|
-
{ marker:
|
|
20874
|
-
{ marker:
|
|
22293
|
+
{ marker: path11.join(actualGitDir, "MERGE_HEAD"), abort: "git merge --abort" },
|
|
22294
|
+
{ marker: path11.join(actualGitDir, "rebase-merge"), abort: "git rebase --abort" },
|
|
22295
|
+
{ marker: path11.join(actualGitDir, "rebase-apply"), abort: "git rebase --abort" },
|
|
22296
|
+
{ marker: path11.join(actualGitDir, "CHERRY_PICK_HEAD"), abort: "git cherry-pick --abort" }
|
|
20875
22297
|
];
|
|
20876
22298
|
for (const { marker, abort } of staleOps) {
|
|
20877
|
-
if (
|
|
22299
|
+
if (fs12.existsSync(marker)) {
|
|
20878
22300
|
try {
|
|
20879
|
-
(0,
|
|
22301
|
+
(0, import_child_process5.execSync)(abort, { cwd: workspacePath, encoding: "utf-8", timeout: 1e4, stdio: "pipe" });
|
|
20880
22302
|
console.log(`[pre-flight] Aborted stale operation: ${abort} in ${workspacePath}`);
|
|
20881
22303
|
} catch {
|
|
20882
22304
|
}
|
|
@@ -20885,7 +22307,7 @@ function cleanGitState(workspacePath) {
|
|
|
20885
22307
|
}
|
|
20886
22308
|
function exec2(cmd, cwd, timeoutMs) {
|
|
20887
22309
|
try {
|
|
20888
|
-
return (0,
|
|
22310
|
+
return (0, import_child_process5.execSync)(cmd, {
|
|
20889
22311
|
cwd,
|
|
20890
22312
|
encoding: "utf-8",
|
|
20891
22313
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -20901,8 +22323,8 @@ ${execError.stderr || execError.message}`);
|
|
|
20901
22323
|
// scripts/lib/worktree.ts
|
|
20902
22324
|
function getWorktreePaths(workspaceDir, repoName) {
|
|
20903
22325
|
return {
|
|
20904
|
-
baseClonePath:
|
|
20905
|
-
worktreeContainerDir:
|
|
22326
|
+
baseClonePath: path12.join(workspaceDir, repoName),
|
|
22327
|
+
worktreeContainerDir: path12.join(workspaceDir, `${repoName}-worktrees`)
|
|
20906
22328
|
};
|
|
20907
22329
|
}
|
|
20908
22330
|
function syncBaseClone(baseClonePath, defaultBranch) {
|
|
@@ -20921,16 +22343,16 @@ function ensureIdeaBranch(baseClonePath, branchName, defaultBranch) {
|
|
|
20921
22343
|
}
|
|
20922
22344
|
}
|
|
20923
22345
|
function createWorktree(baseClonePath, worktreeContainerDir, ideaBranch, groupIndex) {
|
|
20924
|
-
if (!
|
|
20925
|
-
|
|
22346
|
+
if (!fs13.existsSync(worktreeContainerDir)) {
|
|
22347
|
+
fs13.mkdirSync(worktreeContainerDir, { recursive: true });
|
|
20926
22348
|
}
|
|
20927
22349
|
const tempBranch = `wt/${ideaBranch}-g${groupIndex}`;
|
|
20928
|
-
const worktreePath =
|
|
20929
|
-
if (
|
|
22350
|
+
const worktreePath = path12.join(worktreeContainerDir, `${ideaBranch}-g${groupIndex}`);
|
|
22351
|
+
if (fs13.existsSync(worktreePath)) {
|
|
20930
22352
|
try {
|
|
20931
22353
|
exec2(`git worktree remove --force "${worktreePath}"`, baseClonePath, 1e4);
|
|
20932
22354
|
} catch {
|
|
20933
|
-
|
|
22355
|
+
fs13.rmSync(worktreePath, { recursive: true, force: true });
|
|
20934
22356
|
}
|
|
20935
22357
|
}
|
|
20936
22358
|
try {
|
|
@@ -20964,7 +22386,7 @@ function removeWorktree(baseClonePath, worktreePath, tempBranch) {
|
|
|
20964
22386
|
exec2(`git worktree remove --force "${worktreePath}"`, baseClonePath, 1e4);
|
|
20965
22387
|
} catch {
|
|
20966
22388
|
try {
|
|
20967
|
-
|
|
22389
|
+
fs13.rmSync(worktreePath, { recursive: true, force: true });
|
|
20968
22390
|
} catch {
|
|
20969
22391
|
}
|
|
20970
22392
|
}
|
|
@@ -20974,12 +22396,12 @@ function removeWorktree(baseClonePath, worktreePath, tempBranch) {
|
|
|
20974
22396
|
}
|
|
20975
22397
|
}
|
|
20976
22398
|
function cleanupIdeaWorktrees(baseClonePath, worktreeContainerDir, ideaBranch) {
|
|
20977
|
-
if (
|
|
22399
|
+
if (fs13.existsSync(worktreeContainerDir)) {
|
|
20978
22400
|
try {
|
|
20979
|
-
const entries =
|
|
22401
|
+
const entries = fs13.readdirSync(worktreeContainerDir);
|
|
20980
22402
|
for (const entry of entries) {
|
|
20981
22403
|
if (entry.startsWith(`${ideaBranch}-g`)) {
|
|
20982
|
-
const wtPath =
|
|
22404
|
+
const wtPath = path12.join(worktreeContainerDir, entry);
|
|
20983
22405
|
const groupMatch = entry.match(/-g(\d+)$/);
|
|
20984
22406
|
const tempBranch = `wt/${ideaBranch}-g${groupMatch?.[1] ?? "0"}`;
|
|
20985
22407
|
removeWorktree(baseClonePath, wtPath, tempBranch);
|
|
@@ -20993,9 +22415,9 @@ function cleanupIdeaWorktrees(baseClonePath, worktreeContainerDir, ideaBranch) {
|
|
|
20993
22415
|
} catch {
|
|
20994
22416
|
}
|
|
20995
22417
|
try {
|
|
20996
|
-
const remaining =
|
|
22418
|
+
const remaining = fs13.readdirSync(worktreeContainerDir);
|
|
20997
22419
|
if (remaining.length === 0) {
|
|
20998
|
-
|
|
22420
|
+
fs13.rmdirSync(worktreeContainerDir);
|
|
20999
22421
|
}
|
|
21000
22422
|
} catch {
|
|
21001
22423
|
}
|
|
@@ -21005,24 +22427,24 @@ function getTempBranchName(ideaBranch, groupIndex) {
|
|
|
21005
22427
|
}
|
|
21006
22428
|
|
|
21007
22429
|
// scripts/lib/telemetry.ts
|
|
21008
|
-
var
|
|
21009
|
-
var
|
|
21010
|
-
var CONFIG_DIR =
|
|
22430
|
+
var fs14 = __toESM(require("fs"));
|
|
22431
|
+
var path13 = __toESM(require("path"));
|
|
22432
|
+
var CONFIG_DIR = path13.join(
|
|
21011
22433
|
process.env.HOME || process.env.USERPROFILE || "~",
|
|
21012
22434
|
".vibebusiness"
|
|
21013
22435
|
);
|
|
21014
|
-
var TELEMETRY_CONFIG_FILE =
|
|
21015
|
-
var EVENTS_FILE =
|
|
22436
|
+
var TELEMETRY_CONFIG_FILE = path13.join(CONFIG_DIR, "telemetry.json");
|
|
22437
|
+
var EVENTS_FILE = path13.join(CONFIG_DIR, "events.jsonl");
|
|
21016
22438
|
function getVersion() {
|
|
21017
22439
|
try {
|
|
21018
22440
|
let dir = __dirname;
|
|
21019
22441
|
for (let i = 0; i < 4; i++) {
|
|
21020
|
-
const pkgPath =
|
|
21021
|
-
if (
|
|
21022
|
-
const pkg = JSON.parse(
|
|
22442
|
+
const pkgPath = path13.join(dir, "package.json");
|
|
22443
|
+
if (fs14.existsSync(pkgPath)) {
|
|
22444
|
+
const pkg = JSON.parse(fs14.readFileSync(pkgPath, "utf-8"));
|
|
21023
22445
|
return pkg.version || "0.0.0";
|
|
21024
22446
|
}
|
|
21025
|
-
dir =
|
|
22447
|
+
dir = path13.dirname(dir);
|
|
21026
22448
|
}
|
|
21027
22449
|
} catch {
|
|
21028
22450
|
}
|
|
@@ -21030,8 +22452,8 @@ function getVersion() {
|
|
|
21030
22452
|
}
|
|
21031
22453
|
function getTelemetryConfig() {
|
|
21032
22454
|
try {
|
|
21033
|
-
if (!
|
|
21034
|
-
return JSON.parse(
|
|
22455
|
+
if (!fs14.existsSync(TELEMETRY_CONFIG_FILE)) return null;
|
|
22456
|
+
return JSON.parse(fs14.readFileSync(TELEMETRY_CONFIG_FILE, "utf-8"));
|
|
21035
22457
|
} catch {
|
|
21036
22458
|
return null;
|
|
21037
22459
|
}
|
|
@@ -21052,8 +22474,8 @@ function trackEvent(event, properties = {}) {
|
|
|
21052
22474
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
21053
22475
|
version: getVersion()
|
|
21054
22476
|
};
|
|
21055
|
-
|
|
21056
|
-
|
|
22477
|
+
fs14.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
22478
|
+
fs14.appendFileSync(EVENTS_FILE, JSON.stringify(entry) + "\n");
|
|
21057
22479
|
} catch {
|
|
21058
22480
|
}
|
|
21059
22481
|
}
|
|
@@ -21333,22 +22755,22 @@ async function suggestEpics(opts) {
|
|
|
21333
22755
|
}
|
|
21334
22756
|
|
|
21335
22757
|
// scripts/lib/scaffold.ts
|
|
21336
|
-
var
|
|
21337
|
-
var
|
|
22758
|
+
var fs16 = __toESM(require("fs"));
|
|
22759
|
+
var path15 = __toESM(require("path"));
|
|
21338
22760
|
function scaffoldSlashCommands(rootDir) {
|
|
21339
|
-
let templatesDir =
|
|
21340
|
-
if (!
|
|
21341
|
-
templatesDir =
|
|
21342
|
-
}
|
|
21343
|
-
if (!
|
|
21344
|
-
const targetDir =
|
|
21345
|
-
|
|
21346
|
-
const templates =
|
|
22761
|
+
let templatesDir = path15.join(__dirname, "..", "..", "templates", "commands");
|
|
22762
|
+
if (!fs16.existsSync(templatesDir)) {
|
|
22763
|
+
templatesDir = path15.join(__dirname, "..", "..", "..", "templates", "commands");
|
|
22764
|
+
}
|
|
22765
|
+
if (!fs16.existsSync(templatesDir)) return;
|
|
22766
|
+
const targetDir = path15.join(rootDir, ".claude", "commands");
|
|
22767
|
+
fs16.mkdirSync(targetDir, { recursive: true });
|
|
22768
|
+
const templates = fs16.readdirSync(templatesDir).filter((f) => f.endsWith(".md"));
|
|
21347
22769
|
let copied = 0;
|
|
21348
22770
|
for (const file of templates) {
|
|
21349
|
-
const src =
|
|
21350
|
-
const dest =
|
|
21351
|
-
|
|
22771
|
+
const src = path15.join(templatesDir, file);
|
|
22772
|
+
const dest = path15.join(targetDir, file);
|
|
22773
|
+
fs16.copyFileSync(src, dest);
|
|
21352
22774
|
copied++;
|
|
21353
22775
|
}
|
|
21354
22776
|
if (copied > 0) {
|
|
@@ -21357,23 +22779,23 @@ function scaffoldSlashCommands(rootDir) {
|
|
|
21357
22779
|
}
|
|
21358
22780
|
|
|
21359
22781
|
// scripts/lib/vibe-credits.ts
|
|
21360
|
-
var
|
|
21361
|
-
var
|
|
22782
|
+
var fs17 = __toESM(require("fs"));
|
|
22783
|
+
var path16 = __toESM(require("path"));
|
|
21362
22784
|
var WORKER_URL = "https://vibe-credits.luis-e13.workers.dev";
|
|
21363
|
-
var CONFIG_DIR2 =
|
|
21364
|
-
var BUFFER_FILE =
|
|
22785
|
+
var CONFIG_DIR2 = path16.join(process.env.HOME || "", ".vibebusiness");
|
|
22786
|
+
var BUFFER_FILE = path16.join(CONFIG_DIR2, "vibe-buffer.json");
|
|
21365
22787
|
var MAX_BUFFER = 5;
|
|
21366
22788
|
var REQUEST_TIMEOUT_MS = 5e3;
|
|
21367
22789
|
function getClientVersion() {
|
|
21368
22790
|
try {
|
|
21369
22791
|
let dir = __dirname;
|
|
21370
22792
|
for (let i = 0; i < 4; i++) {
|
|
21371
|
-
const pkgPath =
|
|
21372
|
-
if (
|
|
21373
|
-
const pkg = JSON.parse(
|
|
22793
|
+
const pkgPath = path16.join(dir, "package.json");
|
|
22794
|
+
if (fs17.existsSync(pkgPath)) {
|
|
22795
|
+
const pkg = JSON.parse(fs17.readFileSync(pkgPath, "utf-8"));
|
|
21374
22796
|
return pkg.version || "0.0.0";
|
|
21375
22797
|
}
|
|
21376
|
-
dir =
|
|
22798
|
+
dir = path16.dirname(dir);
|
|
21377
22799
|
}
|
|
21378
22800
|
} catch {
|
|
21379
22801
|
}
|
|
@@ -21382,18 +22804,18 @@ function getClientVersion() {
|
|
|
21382
22804
|
var CLIENT_VERSION = getClientVersion();
|
|
21383
22805
|
function loadBuffer() {
|
|
21384
22806
|
try {
|
|
21385
|
-
if (
|
|
21386
|
-
return JSON.parse(
|
|
22807
|
+
if (fs17.existsSync(BUFFER_FILE)) {
|
|
22808
|
+
return JSON.parse(fs17.readFileSync(BUFFER_FILE, "utf-8"));
|
|
21387
22809
|
}
|
|
21388
22810
|
} catch {
|
|
21389
22811
|
}
|
|
21390
22812
|
return { vibes: 0, last_synced: "" };
|
|
21391
22813
|
}
|
|
21392
22814
|
function saveBuffer(buffer) {
|
|
21393
|
-
if (!
|
|
21394
|
-
|
|
22815
|
+
if (!fs17.existsSync(CONFIG_DIR2)) {
|
|
22816
|
+
fs17.mkdirSync(CONFIG_DIR2, { recursive: true });
|
|
21395
22817
|
}
|
|
21396
|
-
|
|
22818
|
+
fs17.writeFileSync(BUFFER_FILE, JSON.stringify(buffer, null, 2));
|
|
21397
22819
|
}
|
|
21398
22820
|
function syncBuffer(vibesRemaining) {
|
|
21399
22821
|
const buffered = Math.min(vibesRemaining, MAX_BUFFER);
|
|
@@ -21467,14 +22889,14 @@ async function consumeVibe(instanceId) {
|
|
|
21467
22889
|
}
|
|
21468
22890
|
|
|
21469
22891
|
// scripts/lib/license.ts
|
|
21470
|
-
var
|
|
21471
|
-
var
|
|
21472
|
-
var CONFIG_DIR3 =
|
|
21473
|
-
var LICENSE_FILE =
|
|
22892
|
+
var fs18 = __toESM(require("fs"));
|
|
22893
|
+
var path17 = __toESM(require("path"));
|
|
22894
|
+
var CONFIG_DIR3 = path17.join(process.env.HOME || "", ".vibebusiness");
|
|
22895
|
+
var LICENSE_FILE = path17.join(CONFIG_DIR3, "license.json");
|
|
21474
22896
|
function loadStoredLicense() {
|
|
21475
22897
|
try {
|
|
21476
|
-
if (
|
|
21477
|
-
return JSON.parse(
|
|
22898
|
+
if (fs18.existsSync(LICENSE_FILE)) {
|
|
22899
|
+
return JSON.parse(fs18.readFileSync(LICENSE_FILE, "utf-8"));
|
|
21478
22900
|
}
|
|
21479
22901
|
} catch {
|
|
21480
22902
|
}
|
|
@@ -21494,10 +22916,10 @@ function getInstanceId() {
|
|
|
21494
22916
|
|
|
21495
22917
|
// scripts/heartbeat.ts
|
|
21496
22918
|
var ROOT_DIR = PROJECT_DIR;
|
|
21497
|
-
var execAsync = (0, import_util.promisify)(
|
|
22919
|
+
var execAsync = (0, import_util.promisify)(import_child_process6.exec);
|
|
21498
22920
|
function spawnAsync(cmd, args2, options) {
|
|
21499
22921
|
return new Promise((resolve, reject) => {
|
|
21500
|
-
const child = (0,
|
|
22922
|
+
const child = (0, import_child_process6.spawn)(cmd, args2, {
|
|
21501
22923
|
cwd: options.cwd,
|
|
21502
22924
|
stdio: ["pipe", "pipe", "pipe"]
|
|
21503
22925
|
});
|
|
@@ -21601,7 +23023,7 @@ function sleep(ms2) {
|
|
|
21601
23023
|
}
|
|
21602
23024
|
function loadJson(filePath, defaultValue) {
|
|
21603
23025
|
try {
|
|
21604
|
-
const content =
|
|
23026
|
+
const content = fs19.readFileSync(filePath, "utf-8");
|
|
21605
23027
|
return JSON.parse(content);
|
|
21606
23028
|
} catch {
|
|
21607
23029
|
return defaultValue;
|
|
@@ -21660,7 +23082,7 @@ function isMetaTaskFalseCompletion(output) {
|
|
|
21660
23082
|
}
|
|
21661
23083
|
function reclassifyAsHumanDependent(taskId) {
|
|
21662
23084
|
try {
|
|
21663
|
-
let content =
|
|
23085
|
+
let content = fs19.readFileSync(TODO_FILE, "utf-8");
|
|
21664
23086
|
const taskPattern = new RegExp(`^- \\[[ x]\\] \`${taskId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\`.*$`, "m");
|
|
21665
23087
|
const match = content.match(taskPattern);
|
|
21666
23088
|
if (!match) return;
|
|
@@ -21673,7 +23095,7 @@ function reclassifyAsHumanDependent(taskId) {
|
|
|
21673
23095
|
const insertIndex = content.indexOf("\n", blockedIndex) + 1;
|
|
21674
23096
|
content = content.slice(0, insertIndex) + "\n" + uncheckedLine + content.slice(insertIndex);
|
|
21675
23097
|
}
|
|
21676
|
-
|
|
23098
|
+
fs19.writeFileSync(TODO_FILE, content);
|
|
21677
23099
|
log(`Reclassified task ${taskId} as human-dependent (moved to Blocked)`);
|
|
21678
23100
|
} catch (error) {
|
|
21679
23101
|
log(`Failed to reclassify task: ${error instanceof Error ? error.message : "Unknown"}`);
|
|
@@ -21681,7 +23103,7 @@ function reclassifyAsHumanDependent(taskId) {
|
|
|
21681
23103
|
}
|
|
21682
23104
|
function parseTodoFile() {
|
|
21683
23105
|
try {
|
|
21684
|
-
const content =
|
|
23106
|
+
const content = fs19.readFileSync(TODO_FILE, "utf-8");
|
|
21685
23107
|
const tasks = [];
|
|
21686
23108
|
let currentSection = "high_priority";
|
|
21687
23109
|
const lines = content.split("\n");
|
|
@@ -21712,7 +23134,7 @@ function parseTodoFile() {
|
|
|
21712
23134
|
return [];
|
|
21713
23135
|
}
|
|
21714
23136
|
}
|
|
21715
|
-
function
|
|
23137
|
+
function loadState2() {
|
|
21716
23138
|
const ideasData = loadJson(IDEAS_FILE, { ideas: [] });
|
|
21717
23139
|
const goalsData = loadJson(GOALS_FILE, { goals: [] });
|
|
21718
23140
|
const sessionsData = loadJson(SESSIONS_FILE, { sessions: [] });
|
|
@@ -21845,10 +23267,10 @@ function captureGitDiffInfo(workspacePath) {
|
|
|
21845
23267
|
has_uncommitted_changes: false
|
|
21846
23268
|
};
|
|
21847
23269
|
try {
|
|
21848
|
-
const status = (0,
|
|
23270
|
+
const status = (0, import_child_process6.execSync)("git status --porcelain", { cwd: workspacePath, encoding: "utf-8" });
|
|
21849
23271
|
result.has_uncommitted_changes = status.trim().length > 0;
|
|
21850
23272
|
try {
|
|
21851
|
-
const diffStat = (0,
|
|
23273
|
+
const diffStat = (0, import_child_process6.execSync)("git diff --stat HEAD~1 HEAD 2>/dev/null || git diff --stat --cached 2>/dev/null || git diff --stat 2>/dev/null", {
|
|
21852
23274
|
cwd: workspacePath,
|
|
21853
23275
|
encoding: "utf-8",
|
|
21854
23276
|
shell: "/bin/bash"
|
|
@@ -22139,10 +23561,10 @@ function updateGoalsWithKPIs(kpis) {
|
|
|
22139
23561
|
log("Updated goals.json with live KPIs");
|
|
22140
23562
|
}
|
|
22141
23563
|
function loadCodebaseSnapshot() {
|
|
22142
|
-
const snapshotPath =
|
|
23564
|
+
const snapshotPath = path18.join(DATA_DIR, "codebase-snapshot.json");
|
|
22143
23565
|
try {
|
|
22144
|
-
if (
|
|
22145
|
-
return JSON.parse(
|
|
23566
|
+
if (fs19.existsSync(snapshotPath)) {
|
|
23567
|
+
return JSON.parse(fs19.readFileSync(snapshotPath, "utf-8"));
|
|
22146
23568
|
}
|
|
22147
23569
|
} catch {
|
|
22148
23570
|
}
|
|
@@ -22296,7 +23718,8 @@ function buildContextForClaude(state, alerts, businessContext) {
|
|
|
22296
23718
|
date: s.completed_at || s.created_at,
|
|
22297
23719
|
ideas_generated: s.ideas_generated.length
|
|
22298
23720
|
})),
|
|
22299
|
-
business_intelligence: readBusinessIntelFreshness()
|
|
23721
|
+
business_intelligence: readBusinessIntelFreshness(),
|
|
23722
|
+
payments: readPaymentsFreshness()
|
|
22300
23723
|
};
|
|
22301
23724
|
}
|
|
22302
23725
|
function buildBusinessConstraints(businessContext) {
|
|
@@ -22460,6 +23883,23 @@ When to recommend BI tasks:
|
|
|
22460
23883
|
5. If business_intelligence.pages.livePagesWithoutMetrics > 0 \u2192 suggest "measure-page-{id}"
|
|
22461
23884
|
6. These form a natural chain: research competitors \u2192 develop positioning \u2192 generate copy \u2192 build pages \u2192 measure results
|
|
22462
23885
|
|
|
23886
|
+
PAYMENTS TASKS:
|
|
23887
|
+
The "payments" field in CURRENT STATE shows Stripe integration completeness.
|
|
23888
|
+
These tasks run directly (no Claude Code delegation) for fast execution.
|
|
23889
|
+
|
|
23890
|
+
Task prefixes:
|
|
23891
|
+
- "payments-setup-stripe" \u2192 Full wizard: detect framework \u2192 install SDK \u2192 validate key \u2192 create products \u2192 generate code \u2192 update .env
|
|
23892
|
+
- "payments-add-checkout" \u2192 Generate only checkout session endpoint
|
|
23893
|
+
- "payments-add-webhook" \u2192 Generate only webhook handler
|
|
23894
|
+
- "payments-add-portal" \u2192 Generate only customer portal redirect
|
|
23895
|
+
- "payments-check-status" \u2192 Audit: SDK installed? Keys present? Products exist? Webhook configured?
|
|
23896
|
+
|
|
23897
|
+
When to recommend payments tasks:
|
|
23898
|
+
1. If payments.completeness == 0 AND monetization goals exist \u2192 suggest "payments-setup-stripe" (high priority)
|
|
23899
|
+
2. If payments.missing includes specific endpoints \u2192 suggest "payments-add-{type}" for each missing endpoint
|
|
23900
|
+
3. If payments.completeness > 0 AND payments.completeness < 100 \u2192 suggest "payments-check-status" to audit
|
|
23901
|
+
4. Payments setup is a prerequisite for any monetization/subscription features
|
|
23902
|
+
|
|
22463
23903
|
- Use codebase_context (if present) to understand what services/integrations actually exist before proposing solutions
|
|
22464
23904
|
|
|
22465
23905
|
${buildBusinessConstraints(businessContext)}
|
|
@@ -22557,7 +23997,7 @@ Focus on actionable, specific recommendations. Be concise.`;
|
|
|
22557
23997
|
},
|
|
22558
23998
|
required: ["analysis", "next_action"]
|
|
22559
23999
|
});
|
|
22560
|
-
const claude = (0,
|
|
24000
|
+
const claude = (0, import_child_process6.spawn)("claude", [
|
|
22561
24001
|
"--print",
|
|
22562
24002
|
"--output-format",
|
|
22563
24003
|
"json",
|
|
@@ -22637,9 +24077,9 @@ Focus on actionable, specific recommendations. Be concise.`;
|
|
|
22637
24077
|
}
|
|
22638
24078
|
function addTasksToTodo(tasks) {
|
|
22639
24079
|
if (tasks.length === 0) return;
|
|
22640
|
-
if (!
|
|
24080
|
+
if (!fs19.existsSync(TODO_FILE)) return;
|
|
22641
24081
|
try {
|
|
22642
|
-
let content =
|
|
24082
|
+
let content = fs19.readFileSync(TODO_FILE, "utf-8");
|
|
22643
24083
|
if (!content.includes("## High Priority (Do Now)")) {
|
|
22644
24084
|
log('TODO.md missing "## High Priority (Do Now)" section \u2014 creating it');
|
|
22645
24085
|
const scheduledIdx = content.indexOf("## Scheduled");
|
|
@@ -22738,15 +24178,15 @@ function addTasksToTodo(tasks) {
|
|
|
22738
24178
|
}
|
|
22739
24179
|
log(`Added TODO: ${task.id} \u2014 ${task.description}`);
|
|
22740
24180
|
}
|
|
22741
|
-
|
|
24181
|
+
fs19.writeFileSync(TODO_FILE, content);
|
|
22742
24182
|
} catch (error) {
|
|
22743
24183
|
log(`Failed to add tasks to TODO.md: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
22744
24184
|
}
|
|
22745
24185
|
}
|
|
22746
24186
|
function archiveCompletedTodos() {
|
|
22747
24187
|
try {
|
|
22748
|
-
if (!
|
|
22749
|
-
let content =
|
|
24188
|
+
if (!fs19.existsSync(TODO_FILE)) return;
|
|
24189
|
+
let content = fs19.readFileSync(TODO_FILE, "utf-8");
|
|
22750
24190
|
const lines = content.split("\n");
|
|
22751
24191
|
const completedLines = [];
|
|
22752
24192
|
const newLines = [];
|
|
@@ -22772,7 +24212,7 @@ function archiveCompletedTodos() {
|
|
|
22772
24212
|
const archiveBlock = completedLines.join("\n") + "\n";
|
|
22773
24213
|
content = content.slice(0, insertIndex) + archiveBlock + content.slice(insertIndex);
|
|
22774
24214
|
}
|
|
22775
|
-
|
|
24215
|
+
fs19.writeFileSync(TODO_FILE, content);
|
|
22776
24216
|
log(`Archived ${completedLines.length} completed TODO(s) to Completed This Week`);
|
|
22777
24217
|
} catch (error) {
|
|
22778
24218
|
log(`Failed to archive completed TODOs: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
@@ -22780,8 +24220,8 @@ function archiveCompletedTodos() {
|
|
|
22780
24220
|
}
|
|
22781
24221
|
function pruneStaleScheduledTodos(state) {
|
|
22782
24222
|
try {
|
|
22783
|
-
if (!
|
|
22784
|
-
const content =
|
|
24223
|
+
if (!fs19.existsSync(TODO_FILE)) return;
|
|
24224
|
+
const content = fs19.readFileSync(TODO_FILE, "utf-8");
|
|
22785
24225
|
const lines = content.split("\n");
|
|
22786
24226
|
const prunedIds = [];
|
|
22787
24227
|
const terminalIdeaIds = new Set(
|
|
@@ -22808,7 +24248,7 @@ function pruneStaleScheduledTodos(state) {
|
|
|
22808
24248
|
return true;
|
|
22809
24249
|
});
|
|
22810
24250
|
if (prunedIds.length === 0) return;
|
|
22811
|
-
|
|
24251
|
+
fs19.writeFileSync(TODO_FILE, newLines.join("\n"));
|
|
22812
24252
|
log(`Pruned ${prunedIds.length} stale TODO(s): ${prunedIds.join(", ")}`);
|
|
22813
24253
|
} catch (error) {
|
|
22814
24254
|
log(`Failed to prune stale TODOs: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
@@ -22816,8 +24256,8 @@ function pruneStaleScheduledTodos(state) {
|
|
|
22816
24256
|
}
|
|
22817
24257
|
function deduplicateActiveTodos() {
|
|
22818
24258
|
try {
|
|
22819
|
-
if (!
|
|
22820
|
-
const content =
|
|
24259
|
+
if (!fs19.existsSync(TODO_FILE)) return;
|
|
24260
|
+
const content = fs19.readFileSync(TODO_FILE, "utf-8");
|
|
22821
24261
|
const lines = content.split("\n");
|
|
22822
24262
|
const removedIds = [];
|
|
22823
24263
|
let currentSection = "";
|
|
@@ -22854,7 +24294,7 @@ function deduplicateActiveTodos() {
|
|
|
22854
24294
|
if (linesToRemove.size === 0) return;
|
|
22855
24295
|
const newLines = lines.filter((_, i) => !linesToRemove.has(i));
|
|
22856
24296
|
const cleaned = newLines.join("\n").replace(/\n{3,}/g, "\n\n");
|
|
22857
|
-
|
|
24297
|
+
fs19.writeFileSync(TODO_FILE, cleaned);
|
|
22858
24298
|
log(`Deduplicated ${removedIds.length} TODO(s): ${removedIds.join(", ")}`);
|
|
22859
24299
|
} catch (error) {
|
|
22860
24300
|
log(`Failed to deduplicate TODOs: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
@@ -22862,14 +24302,14 @@ function deduplicateActiveTodos() {
|
|
|
22862
24302
|
}
|
|
22863
24303
|
function appendToMemory(learnings) {
|
|
22864
24304
|
if (learnings.length === 0) return;
|
|
22865
|
-
if (!
|
|
24305
|
+
if (!fs19.existsSync(MEMORY_FILE)) return;
|
|
22866
24306
|
const MAX_LEARNINGS_PER_HEARTBEAT = 2;
|
|
22867
24307
|
let filtered = learnings.slice(0, MAX_LEARNINGS_PER_HEARTBEAT);
|
|
22868
24308
|
if (learnings.length > MAX_LEARNINGS_PER_HEARTBEAT) {
|
|
22869
24309
|
log(`Capped learnings from ${learnings.length} to ${MAX_LEARNINGS_PER_HEARTBEAT}`);
|
|
22870
24310
|
}
|
|
22871
24311
|
try {
|
|
22872
|
-
let content =
|
|
24312
|
+
let content = fs19.readFileSync(MEMORY_FILE, "utf-8");
|
|
22873
24313
|
const existingLines = content.split("\n").filter((l2) => l2.match(/^- \*\*/));
|
|
22874
24314
|
filtered = filtered.filter((learning) => {
|
|
22875
24315
|
const words = learning.toLowerCase().split(/\s+/).filter((w2) => w2.length > 4);
|
|
@@ -22910,7 +24350,7 @@ function appendToMemory(learnings) {
|
|
|
22910
24350
|
const subsectionEnd = subsectionEndMatch ? subsectionIndex + 22 + (subsectionEndMatch.index || 0) : insertPoint;
|
|
22911
24351
|
content = content.slice(0, subsectionEnd) + newEntries + "\n" + content.slice(subsectionEnd);
|
|
22912
24352
|
}
|
|
22913
|
-
|
|
24353
|
+
fs19.writeFileSync(MEMORY_FILE, content);
|
|
22914
24354
|
log(`Added ${filtered.length} learning(s) to MEMORY.md`);
|
|
22915
24355
|
}
|
|
22916
24356
|
} catch (error) {
|
|
@@ -23027,7 +24467,7 @@ function detectTargetRepo(idea, config) {
|
|
|
23027
24467
|
const filesAnalyzed = idea.source.files_analyzed || [];
|
|
23028
24468
|
for (const file of filesAnalyzed) {
|
|
23029
24469
|
for (const repo of config.repos) {
|
|
23030
|
-
if (file.includes(repo.name) || file.includes(
|
|
24470
|
+
if (file.includes(repo.name) || file.includes(path18.basename(repo.path))) {
|
|
23031
24471
|
return repo;
|
|
23032
24472
|
}
|
|
23033
24473
|
}
|
|
@@ -23225,7 +24665,7 @@ Respond with JSON only (no markdown code blocks, just raw JSON):
|
|
|
23225
24665
|
},
|
|
23226
24666
|
required: ["sub_tasks"]
|
|
23227
24667
|
});
|
|
23228
|
-
const claude = (0,
|
|
24668
|
+
const claude = (0, import_child_process6.spawn)("claude", [
|
|
23229
24669
|
"--print",
|
|
23230
24670
|
"--output-format",
|
|
23231
24671
|
"json",
|
|
@@ -23355,17 +24795,17 @@ function runTestsForRepo(workspacePath, testCommands, sourceRepoPath) {
|
|
|
23355
24795
|
const allOutput = [];
|
|
23356
24796
|
const env = { ...process.env };
|
|
23357
24797
|
if (sourceRepoPath) {
|
|
23358
|
-
const venvBin =
|
|
23359
|
-
if (
|
|
24798
|
+
const venvBin = path18.join(sourceRepoPath, ".venv", "bin");
|
|
24799
|
+
if (fs19.existsSync(venvBin)) {
|
|
23360
24800
|
env.PATH = `${venvBin}:${env.PATH}`;
|
|
23361
|
-
env.VIRTUAL_ENV =
|
|
24801
|
+
env.VIRTUAL_ENV = path18.join(sourceRepoPath, ".venv");
|
|
23362
24802
|
log(`Using venv from ${sourceRepoPath}/.venv`);
|
|
23363
24803
|
}
|
|
23364
24804
|
}
|
|
23365
24805
|
for (const cmd of testCommands) {
|
|
23366
24806
|
log(`Running test: ${cmd}`);
|
|
23367
24807
|
try {
|
|
23368
|
-
const result = (0,
|
|
24808
|
+
const result = (0, import_child_process6.execSync)(cmd, {
|
|
23369
24809
|
cwd: workspacePath,
|
|
23370
24810
|
encoding: "utf-8",
|
|
23371
24811
|
env,
|
|
@@ -23411,7 +24851,7 @@ async function executeSingleSubTask(subTask, ideaId, idea, ideasData, autonomy,
|
|
|
23411
24851
|
});
|
|
23412
24852
|
const scopeBase64 = Buffer.from(scopePayload).toString("base64");
|
|
23413
24853
|
const timeoutMs = autonomy.max_sub_task_timeout_ms || 3e5;
|
|
23414
|
-
const workspacePath = options?.worktreePath ||
|
|
24854
|
+
const workspacePath = options?.worktreePath || path18.join(config.workspace_dir, targetRepo.name);
|
|
23415
24855
|
const executionStartTime = Date.now();
|
|
23416
24856
|
const updateSubTaskWithDetails = (status, updateOpts = {}) => {
|
|
23417
24857
|
const endTime = Date.now();
|
|
@@ -23489,7 +24929,7 @@ async function executeSingleSubTask(subTask, ideaId, idea, ideasData, autonomy,
|
|
|
23489
24929
|
}
|
|
23490
24930
|
let commitHash = null;
|
|
23491
24931
|
try {
|
|
23492
|
-
commitHash = (0,
|
|
24932
|
+
commitHash = (0, import_child_process6.execSync)("git rev-parse HEAD", { cwd: workspacePath, encoding: "utf-8" }).trim();
|
|
23493
24933
|
} catch {
|
|
23494
24934
|
}
|
|
23495
24935
|
updateSubTaskWithDetails("completed", {
|
|
@@ -23533,7 +24973,7 @@ async function analyzeAndRetrySubTask(subTask, ideaId, maxRetries) {
|
|
|
23533
24973
|
"Be specific about what went wrong and how to fix it. Include any workarounds needed."
|
|
23534
24974
|
].filter(Boolean).join("\n");
|
|
23535
24975
|
try {
|
|
23536
|
-
const spawnResult = (0,
|
|
24976
|
+
const spawnResult = (0, import_child_process6.spawnSync)("claude", ["--print", "--model", "sonnet", "-p", errorContext], {
|
|
23537
24977
|
cwd: ROOT_DIR,
|
|
23538
24978
|
encoding: "utf-8",
|
|
23539
24979
|
timeout: 6e4,
|
|
@@ -23630,7 +25070,7 @@ async function executeAutonomousImplementation(ideaId) {
|
|
|
23630
25070
|
}
|
|
23631
25071
|
try {
|
|
23632
25072
|
const { command, args: args2 } = resolveScript(__dirname, "implement.ts");
|
|
23633
|
-
(0,
|
|
25073
|
+
(0, import_child_process6.execSync)(
|
|
23634
25074
|
[command, ...args2, `--idea=${ideaId}`, `--repo=${targetRepo2.name}`, "--create-pr-only"].join(" "),
|
|
23635
25075
|
{ cwd: ROOT_DIR, encoding: "utf-8", stdio: "inherit", timeout: 12e4 }
|
|
23636
25076
|
);
|
|
@@ -23638,16 +25078,16 @@ async function executeAutonomousImplementation(ideaId) {
|
|
|
23638
25078
|
if (autonomy.auto_merge) {
|
|
23639
25079
|
const branchName2 = idea.implementation.branch_name;
|
|
23640
25080
|
const defaultBranch2 = targetRepo2.default_branch || "main";
|
|
23641
|
-
const workspacePath =
|
|
25081
|
+
const workspacePath = path18.join(config.workspace_dir, targetRepo2.name);
|
|
23642
25082
|
if (branchName2) {
|
|
23643
25083
|
try {
|
|
23644
25084
|
log(`Auto-merging branch ${branchName2} into ${defaultBranch2}...`);
|
|
23645
|
-
(0,
|
|
23646
|
-
(0,
|
|
23647
|
-
(0,
|
|
25085
|
+
(0, import_child_process6.execSync)(`git checkout ${defaultBranch2}`, { cwd: workspacePath, encoding: "utf-8", stdio: "pipe" });
|
|
25086
|
+
(0, import_child_process6.execSync)(`git merge ${branchName2} --squash --no-edit`, { cwd: workspacePath, encoding: "utf-8", stdio: "pipe" });
|
|
25087
|
+
(0, import_child_process6.execSync)(`git commit -m "feat: ${idea.title} (auto-merged from ${branchName2})"`, { cwd: workspacePath, encoding: "utf-8", stdio: "pipe" });
|
|
23648
25088
|
log(`Successfully merged ${branchName2} into ${defaultBranch2}`);
|
|
23649
25089
|
try {
|
|
23650
|
-
(0,
|
|
25090
|
+
(0, import_child_process6.execSync)(`git branch -d ${branchName2}`, { cwd: workspacePath, encoding: "utf-8", stdio: "pipe" });
|
|
23651
25091
|
log(`Deleted branch ${branchName2}`);
|
|
23652
25092
|
} catch {
|
|
23653
25093
|
log(`Could not delete branch ${branchName2} (may need force delete)`);
|
|
@@ -23672,8 +25112,8 @@ async function executeAutonomousImplementation(ideaId) {
|
|
|
23672
25112
|
} catch (mergeError) {
|
|
23673
25113
|
log(`Auto-merge failed (likely conflicts): ${mergeError instanceof Error ? mergeError.message : "Unknown"}`);
|
|
23674
25114
|
try {
|
|
23675
|
-
(0,
|
|
23676
|
-
(0,
|
|
25115
|
+
(0, import_child_process6.execSync)(`git merge --abort`, { cwd: workspacePath, encoding: "utf-8", stdio: "pipe" });
|
|
25116
|
+
(0, import_child_process6.execSync)(`git checkout ${branchName2}`, { cwd: workspacePath, encoding: "utf-8", stdio: "pipe" });
|
|
23677
25117
|
} catch {
|
|
23678
25118
|
}
|
|
23679
25119
|
log(`Left idea ${ideaId} in testing stage for manual merge`);
|
|
@@ -23867,7 +25307,7 @@ ${allowedFiles.map((f) => `- ${f}`).join("\n")}
|
|
|
23867
25307
|
7. Be direct \u2014 read the file, make the edit, done
|
|
23868
25308
|
`;
|
|
23869
25309
|
return new Promise((resolve) => {
|
|
23870
|
-
const claude = (0,
|
|
25310
|
+
const claude = (0, import_child_process6.spawn)("claude", [
|
|
23871
25311
|
"--print",
|
|
23872
25312
|
"--dangerously-skip-permissions",
|
|
23873
25313
|
"--model",
|
|
@@ -23951,7 +25391,7 @@ ${kpiLines}`;
|
|
|
23951
25391
|
const contextBase64 = Buffer.from(contextPayload).toString("base64");
|
|
23952
25392
|
return new Promise((resolve) => {
|
|
23953
25393
|
const { command: analyzeCmd, args: analyzeArgs } = resolveScript(__dirname, "analyze.ts");
|
|
23954
|
-
const childProcess = (0,
|
|
25394
|
+
const childProcess = (0, import_child_process6.spawn)(analyzeCmd, [
|
|
23955
25395
|
...analyzeArgs,
|
|
23956
25396
|
`--type=research`,
|
|
23957
25397
|
`--topic=${topic}`,
|
|
@@ -24115,7 +25555,7 @@ async function executeGoalGapResearch(goalId) {
|
|
|
24115
25555
|
return new Promise((resolve) => {
|
|
24116
25556
|
const TIMEOUT_MS = 9e5;
|
|
24117
25557
|
const startTime = Date.now();
|
|
24118
|
-
const claude = (0,
|
|
25558
|
+
const claude = (0, import_child_process6.spawn)("claude", [
|
|
24119
25559
|
"--print",
|
|
24120
25560
|
"--dangerously-skip-permissions"
|
|
24121
25561
|
], {
|
|
@@ -24359,7 +25799,7 @@ async function executeShippedEvaluation(ideaId) {
|
|
|
24359
25799
|
const prompt = buildEvaluationPrompt(idea, goal, liveKPIs, daysShipped);
|
|
24360
25800
|
const TIMEOUT_MS = 12e4;
|
|
24361
25801
|
const result = await new Promise((resolve) => {
|
|
24362
|
-
const claude = (0,
|
|
25802
|
+
const claude = (0, import_child_process6.spawn)("claude", ["--print"], {
|
|
24363
25803
|
cwd: ROOT_DIR,
|
|
24364
25804
|
env: process.env,
|
|
24365
25805
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -24757,7 +26197,7 @@ async function executeSingleResearchTask(task) {
|
|
|
24757
26197
|
task.status = "running";
|
|
24758
26198
|
task.started_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
24759
26199
|
return new Promise((resolve) => {
|
|
24760
|
-
const claude = (0,
|
|
26200
|
+
const claude = (0, import_child_process6.spawn)("claude", ["--print", "--dangerously-skip-permissions"], {
|
|
24761
26201
|
cwd: ROOT_DIR,
|
|
24762
26202
|
env: process.env,
|
|
24763
26203
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -24868,7 +26308,7 @@ Respond with JSON only:
|
|
|
24868
26308
|
"recommendation_reasoning": "Why this recommendation"
|
|
24869
26309
|
}`;
|
|
24870
26310
|
return new Promise((resolve) => {
|
|
24871
|
-
const claude = (0,
|
|
26311
|
+
const claude = (0, import_child_process6.spawn)("claude", ["--print"], {
|
|
24872
26312
|
cwd: ROOT_DIR,
|
|
24873
26313
|
env: process.env,
|
|
24874
26314
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -25052,6 +26492,18 @@ async function executeTask(task, config, businessContext) {
|
|
|
25052
26492
|
trackEvent("heartbeat_task", { taskType: taskTypePrefix, success: result.success });
|
|
25053
26493
|
return result.success;
|
|
25054
26494
|
}
|
|
26495
|
+
if (isEmailMarketingTask(taskId)) {
|
|
26496
|
+
const result = await executeEmailMarketingTask(taskId, taskDesc, businessContext);
|
|
26497
|
+
log(`Email marketing task: ${result.success ? "SUCCESS" : "FAILED"} \u2014 ${result.output.slice(0, 200)}`);
|
|
26498
|
+
trackEvent("heartbeat_task", { taskType: taskTypePrefix, success: result.success });
|
|
26499
|
+
return result.success;
|
|
26500
|
+
}
|
|
26501
|
+
if (isPaymentsTask(taskId)) {
|
|
26502
|
+
const result = await executePaymentsTask(taskId, taskDesc, businessContext);
|
|
26503
|
+
log(`Payments task: ${result.success ? "SUCCESS" : "FAILED"} \u2014 ${result.output.slice(0, 200)}`);
|
|
26504
|
+
trackEvent("heartbeat_task", { taskType: taskTypePrefix, success: result.success });
|
|
26505
|
+
return result.success;
|
|
26506
|
+
}
|
|
25055
26507
|
if (taskId === "suggest-epics") {
|
|
25056
26508
|
return await executeSuggestEpics();
|
|
25057
26509
|
}
|
|
@@ -25081,7 +26533,7 @@ async function runAnalysis(type) {
|
|
|
25081
26533
|
return new Promise((resolve, reject) => {
|
|
25082
26534
|
log(`Running ${type} analysis...`);
|
|
25083
26535
|
const { command: runCmd, args: runArgs } = resolveScript(__dirname, "analyze.ts");
|
|
25084
|
-
const childProcess = (0,
|
|
26536
|
+
const childProcess = (0, import_child_process6.spawn)(runCmd, [...runArgs, `--type=${type}`], {
|
|
25085
26537
|
cwd: ROOT_DIR,
|
|
25086
26538
|
stdio: "inherit"
|
|
25087
26539
|
});
|
|
@@ -25100,7 +26552,7 @@ async function runAnalysis(type) {
|
|
|
25100
26552
|
}
|
|
25101
26553
|
function updateTodoFile(taskId, completed) {
|
|
25102
26554
|
try {
|
|
25103
|
-
let content =
|
|
26555
|
+
let content = fs19.readFileSync(TODO_FILE, "utf-8");
|
|
25104
26556
|
const uncheckedPattern = new RegExp(`- \\[ \\] \`${taskId}\``, "g");
|
|
25105
26557
|
const checkedPattern = new RegExp(`- \\[x\\] \`${taskId}\``, "g");
|
|
25106
26558
|
if (completed) {
|
|
@@ -25108,7 +26560,7 @@ function updateTodoFile(taskId, completed) {
|
|
|
25108
26560
|
} else {
|
|
25109
26561
|
content = content.replace(checkedPattern, `- [ ] \`${taskId}\``);
|
|
25110
26562
|
}
|
|
25111
|
-
|
|
26563
|
+
fs19.writeFileSync(TODO_FILE, content);
|
|
25112
26564
|
log(`Updated TODO.md: ${taskId} marked as ${completed ? "completed" : "pending"}`);
|
|
25113
26565
|
} catch (error) {
|
|
25114
26566
|
log(`Failed to update TODO.md: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
@@ -25212,8 +26664,8 @@ async function runSingleHeartbeat(beatNumber = 1) {
|
|
|
25212
26664
|
if (vibeResult.warning) log(vibeResult.warning);
|
|
25213
26665
|
log(`Vibe consumed (${vibeResult.vibes_remaining} remaining${vibeResult.plan ? `, plan: ${vibeResult.plan}` : ""}${vibeResult.offline ? ", offline" : ""})`);
|
|
25214
26666
|
}
|
|
25215
|
-
const commandsDir =
|
|
25216
|
-
if (!
|
|
26667
|
+
const commandsDir = path18.join(PROJECT_DIR, ".claude", "commands");
|
|
26668
|
+
if (!fs19.existsSync(commandsDir)) {
|
|
25217
26669
|
log("Slash commands missing \u2014 auto-scaffolding...");
|
|
25218
26670
|
scaffoldSlashCommands(PROJECT_DIR);
|
|
25219
26671
|
}
|
|
@@ -25223,7 +26675,7 @@ async function runSingleHeartbeat(beatNumber = 1) {
|
|
|
25223
26675
|
if (SKIP_REASONING) {
|
|
25224
26676
|
log("Skipping Claude reasoning step");
|
|
25225
26677
|
}
|
|
25226
|
-
const state =
|
|
26678
|
+
const state = loadState2();
|
|
25227
26679
|
const businessContext = loadBusinessContext();
|
|
25228
26680
|
const heartbeatConfig = loadConfig();
|
|
25229
26681
|
log(`Loaded state: ${state.ideas.length} ideas, ${state.goals.length} goals, ${state.sessions.length} sessions`);
|
|
@@ -25390,7 +26842,7 @@ async function runSingleHeartbeat(beatNumber = 1) {
|
|
|
25390
26842
|
}
|
|
25391
26843
|
if (!DRY_RUN) {
|
|
25392
26844
|
const statusContent = generateStatusContent(state, alerts, taskToDo, reasoning, systemHealth);
|
|
25393
|
-
|
|
26845
|
+
fs19.writeFileSync(STATUS_FILE, statusContent);
|
|
25394
26846
|
log("Updated STATUS.md");
|
|
25395
26847
|
} else {
|
|
25396
26848
|
log("[DRY RUN] Would update STATUS.md");
|
|
@@ -25505,7 +26957,7 @@ ${"=".repeat(60)}
|
|
|
25505
26957
|
`);
|
|
25506
26958
|
}
|
|
25507
26959
|
function saveSessionToJson(summary) {
|
|
25508
|
-
const sessionsFile =
|
|
26960
|
+
const sessionsFile = path18.join(DATA_DIR, "heartbeat-sessions.json");
|
|
25509
26961
|
const sessionsData = loadJson(sessionsFile, { sessions: [] });
|
|
25510
26962
|
sessionsData.sessions.push(summary);
|
|
25511
26963
|
if (sessionsData.sessions.length > 50) {
|