@forvibe/cli 1.0.9 → 1.1.0
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.
|
@@ -26,7 +26,7 @@ Your task is to generate a complete, ASO-optimized store listing for a mobile ap
|
|
|
26
26
|
6. Write in English (en-US locale)
|
|
27
27
|
|
|
28
28
|
Respond ONLY with a valid JSON object, no markdown, no explanation.`;
|
|
29
|
-
function buildASOPrompt(report) {
|
|
29
|
+
function buildASOPrompt(report, selectedStores) {
|
|
30
30
|
const parts = [];
|
|
31
31
|
parts.push(`## App Information
|
|
32
32
|
- App Name: ${report.app_name}
|
|
@@ -50,23 +50,31 @@ ${report.unique_selling_points.map((u) => `- ${u}`).join("\n")}`);
|
|
|
50
50
|
parts.push(`## README (for additional context)
|
|
51
51
|
${report.readme_content.substring(0, 3e3)}`);
|
|
52
52
|
}
|
|
53
|
-
const includePlayStore = report.platforms.includes("android");
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
const includePlayStore = selectedStores.length > 0 ? selectedStores.includes("playstore") : report.platforms.includes("android");
|
|
54
|
+
const includeAppStore = selectedStores.length > 0 ? selectedStores.includes("appstore") : true;
|
|
55
|
+
const jsonParts = [];
|
|
56
|
+
if (includeAppStore) {
|
|
57
|
+
jsonParts.push(` "appstore": {
|
|
57
58
|
"app_name": "max 30 chars, keyword-rich app name",
|
|
58
59
|
"subtitle": "max 30 chars, complementary keywords",
|
|
59
60
|
"description": "max 4000 chars, ASO-optimized with bullets and line breaks",
|
|
60
61
|
"keywords": "keyword1,keyword2,keyword3 (max 100 chars, NO spaces after commas)",
|
|
61
62
|
"promotional_text": "max 170 chars, engaging hook",
|
|
62
63
|
"whats_new": "max 170 chars, launch highlights"
|
|
63
|
-
}
|
|
64
|
-
|
|
64
|
+
}`);
|
|
65
|
+
}
|
|
66
|
+
if (includePlayStore) {
|
|
67
|
+
jsonParts.push(` "playstore": {
|
|
65
68
|
"title": "max 30 chars, Play Store optimized title",
|
|
66
69
|
"short_description": "max 80 chars, concise value proposition",
|
|
67
70
|
"description": "max 4000 chars, HTML formatting allowed",
|
|
68
71
|
"whats_new": "max 500 chars, launch release notes"
|
|
69
|
-
}`
|
|
72
|
+
}`);
|
|
73
|
+
}
|
|
74
|
+
parts.push(`## Required JSON Response Format
|
|
75
|
+
{
|
|
76
|
+
${jsonParts.join(",\n")}
|
|
77
|
+
}
|
|
70
78
|
}`);
|
|
71
79
|
return parts.join("\n\n");
|
|
72
80
|
}
|
|
@@ -82,16 +90,17 @@ function truncateKeywords(keywords, maxLength) {
|
|
|
82
90
|
return result || keywords.substring(0, maxLength);
|
|
83
91
|
}
|
|
84
92
|
function enforceCharLimits(raw) {
|
|
85
|
-
const result = {
|
|
86
|
-
|
|
93
|
+
const result = {};
|
|
94
|
+
if (raw.appstore) {
|
|
95
|
+
result.appstore = {
|
|
87
96
|
app_name: (raw.appstore.app_name || "").substring(0, 30),
|
|
88
97
|
subtitle: (raw.appstore.subtitle || "").substring(0, 30),
|
|
89
98
|
description: (raw.appstore.description || "").substring(0, 4e3),
|
|
90
99
|
keywords: truncateKeywords(raw.appstore.keywords || "", 100),
|
|
91
100
|
promotional_text: (raw.appstore.promotional_text || "").substring(0, 170),
|
|
92
101
|
whats_new: (raw.appstore.whats_new || "").substring(0, 170)
|
|
93
|
-
}
|
|
94
|
-
}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
95
104
|
if (raw.playstore) {
|
|
96
105
|
result.playstore = {
|
|
97
106
|
title: (raw.playstore.title || "").substring(0, 30),
|
|
@@ -102,8 +111,8 @@ function enforceCharLimits(raw) {
|
|
|
102
111
|
}
|
|
103
112
|
return result;
|
|
104
113
|
}
|
|
105
|
-
async function generateASOContent(report, provider) {
|
|
106
|
-
const userPrompt = buildASOPrompt(report);
|
|
114
|
+
async function generateASOContent(report, provider, selectedStores = []) {
|
|
115
|
+
const userPrompt = buildASOPrompt(report, selectedStores);
|
|
107
116
|
const responseText = await provider.generateJSON(ASO_SYSTEM_PROMPT, userPrompt, 0.5);
|
|
108
117
|
let parsed;
|
|
109
118
|
try {
|
|
@@ -121,9 +130,14 @@ async function generateASOContent(report, provider) {
|
|
|
121
130
|
throw new Error("Failed to parse ASO AI response as JSON");
|
|
122
131
|
}
|
|
123
132
|
}
|
|
124
|
-
|
|
133
|
+
const wantsAppStore = selectedStores.length === 0 || selectedStores.includes("appstore");
|
|
134
|
+
if (wantsAppStore && !parsed.appstore) {
|
|
125
135
|
throw new Error("AI response missing appstore field");
|
|
126
136
|
}
|
|
137
|
+
const wantsPlayStore = selectedStores.includes("playstore") || selectedStores.length === 0 && report.platforms.includes("android");
|
|
138
|
+
if (wantsPlayStore && !parsed.playstore) {
|
|
139
|
+
console.warn("AI response missing playstore field \u2014 will be generated server-side");
|
|
140
|
+
}
|
|
127
141
|
return enforceCharLimits(parsed);
|
|
128
142
|
}
|
|
129
143
|
export {
|
package/dist/index.js
CHANGED
|
@@ -2163,6 +2163,7 @@ var ForvibeClient = class {
|
|
|
2163
2163
|
baseUrl;
|
|
2164
2164
|
sessionToken = null;
|
|
2165
2165
|
sessionId = null;
|
|
2166
|
+
storeConfig = null;
|
|
2166
2167
|
constructor(baseUrl) {
|
|
2167
2168
|
this.baseUrl = (baseUrl || process.env.FORVIBE_API_URL || DEFAULT_API_URL).replace(/\/$/, "");
|
|
2168
2169
|
}
|
|
@@ -2192,6 +2193,7 @@ var ForvibeClient = class {
|
|
|
2192
2193
|
const data = await response.json();
|
|
2193
2194
|
this.sessionToken = data.session_token;
|
|
2194
2195
|
this.sessionId = data.session_id;
|
|
2196
|
+
this.storeConfig = data.store_config || null;
|
|
2195
2197
|
return data;
|
|
2196
2198
|
}
|
|
2197
2199
|
/**
|
|
@@ -2598,8 +2600,9 @@ async function analyzeCommand(options) {
|
|
|
2598
2600
|
prefixText: " "
|
|
2599
2601
|
}).start();
|
|
2600
2602
|
try {
|
|
2601
|
-
const { generateASOContent } = await import("./aso-generator-
|
|
2602
|
-
const
|
|
2603
|
+
const { generateASOContent } = await import("./aso-generator-PF4SDBTL.js");
|
|
2604
|
+
const stores = client.storeConfig?.stores || [];
|
|
2605
|
+
const asoContent = await generateASOContent(report, provider, stores);
|
|
2603
2606
|
report.aso_content = asoContent;
|
|
2604
2607
|
asoSpinner.succeed(chalk.green("Store listing content generated!"));
|
|
2605
2608
|
} catch (error) {
|
|
@@ -2628,16 +2631,19 @@ async function analyzeCommand(options) {
|
|
|
2628
2631
|
console.log(` Assets: ${report.app_assets?.length ? chalk.green(`${report.app_assets.length} found`) : chalk.gray("none")}`);
|
|
2629
2632
|
console.log(chalk.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
2630
2633
|
if (report.aso_content) {
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2634
|
+
if (report.aso_content.appstore) {
|
|
2635
|
+
console.log();
|
|
2636
|
+
console.log(chalk.bold(" \u{1F4F1} App Store Listing Preview"));
|
|
2637
|
+
console.log(chalk.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
2638
|
+
const aso = report.aso_content.appstore;
|
|
2639
|
+
console.log(` Title: ${chalk.white(aso.app_name)} ${chalk.gray(`(${aso.app_name.length}/30)`)}`);
|
|
2640
|
+
console.log(` Subtitle: ${chalk.white(aso.subtitle)} ${chalk.gray(`(${aso.subtitle.length}/30)`)}`);
|
|
2641
|
+
console.log(` Keywords: ${chalk.white(aso.keywords)} ${chalk.gray(`(${aso.keywords.length}/100)`)}`);
|
|
2642
|
+
console.log(` Promo: ${chalk.white(aso.promotional_text)} ${chalk.gray(`(${aso.promotional_text.length}/170)`)}`);
|
|
2643
|
+
const descPreview = aso.description.substring(0, 150).replace(/\n/g, " ");
|
|
2644
|
+
console.log(` Description: ${chalk.white(descPreview)}${aso.description.length > 150 ? chalk.gray("...") : ""} ${chalk.gray(`(${aso.description.length}/4000)`)}`);
|
|
2645
|
+
console.log(chalk.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
2646
|
+
}
|
|
2641
2647
|
if (report.aso_content.playstore) {
|
|
2642
2648
|
console.log();
|
|
2643
2649
|
console.log(chalk.bold(" \u{1F916} Play Store Listing Preview"));
|
|
@@ -2647,57 +2653,62 @@ async function analyzeCommand(options) {
|
|
|
2647
2653
|
console.log(` Short Desc: ${chalk.white(ps.short_description)} ${chalk.gray(`(${ps.short_description.length}/80)`)}`);
|
|
2648
2654
|
const psDescPreview = ps.description.substring(0, 150).replace(/\n/g, " ");
|
|
2649
2655
|
console.log(` Description: ${chalk.white(psDescPreview)}${ps.description.length > 150 ? chalk.gray("...") : ""} ${chalk.gray(`(${ps.description.length}/4000)`)}`);
|
|
2656
|
+
console.log(chalk.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
2650
2657
|
}
|
|
2651
|
-
console.log(chalk.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
2652
2658
|
}
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
);
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2659
|
+
const selectedStores = client.storeConfig?.stores || [];
|
|
2660
|
+
const hasASC = selectedStores.includes("appstore") || selectedStores.length === 0;
|
|
2661
|
+
const hasPS = selectedStores.includes("playstore") || selectedStores.length === 0;
|
|
2662
|
+
if (hasASC) {
|
|
2663
|
+
console.log();
|
|
2664
|
+
console.log(chalk.bold(" \u{1F4DD} App Review Information"));
|
|
2665
|
+
console.log(chalk.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
2666
|
+
console.log(chalk.gray(" Apple's review team may need to sign in to your app."));
|
|
2667
|
+
console.log(chalk.gray(" You can also provide this later in the Forvibe dashboard."));
|
|
2668
|
+
console.log();
|
|
2669
|
+
const hasAuthSDKs = (sdkScan.third_party_services.auth?.length ?? 0) > 0;
|
|
2670
|
+
if (hasAuthSDKs) {
|
|
2671
|
+
const signInAnswer = await askQuestion(
|
|
2672
|
+
chalk.cyan(" Does your app require sign-in to use? (y/n): ")
|
|
2673
|
+
);
|
|
2674
|
+
const signInRequired = signInAnswer.toLowerCase().startsWith("y");
|
|
2675
|
+
report.sign_in_required = signInRequired;
|
|
2676
|
+
if (signInRequired) {
|
|
2677
|
+
console.log();
|
|
2678
|
+
console.log(chalk.yellow(" \u26A0 Please create a test account in your app for Apple's review team."));
|
|
2679
|
+
console.log(chalk.gray(" This account should have full access to your app's features."));
|
|
2680
|
+
console.log();
|
|
2681
|
+
const testEmail = await askQuestion(chalk.cyan(" Test account email: "));
|
|
2682
|
+
const testPassword = await askQuestion(chalk.cyan(" Test account password: "));
|
|
2683
|
+
if (testEmail && testPassword) {
|
|
2684
|
+
report.test_account = { email: testEmail, password: testPassword };
|
|
2685
|
+
console.log(chalk.green(" \u2713 Test account saved"));
|
|
2686
|
+
}
|
|
2676
2687
|
}
|
|
2688
|
+
} else {
|
|
2689
|
+
report.sign_in_required = false;
|
|
2677
2690
|
}
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
console.log(chalk.gray(" \u2139 Skipped \u2014 you can add this later in the dashboard."));
|
|
2691
|
+
console.log();
|
|
2692
|
+
console.log(chalk.gray(" Contact information for Apple's review team (press Enter to skip):"));
|
|
2693
|
+
console.log();
|
|
2694
|
+
const firstName = await askQuestion(chalk.cyan(" First name: "));
|
|
2695
|
+
const lastName = await askQuestion(chalk.cyan(" Last name: "));
|
|
2696
|
+
const contactEmail = await askQuestion(chalk.cyan(" Email: "));
|
|
2697
|
+
const contactPhone = await askQuestion(chalk.cyan(" Phone (e.g. +1 555 123 4567): "));
|
|
2698
|
+
if (firstName && lastName && contactEmail) {
|
|
2699
|
+
report.review_contact = {
|
|
2700
|
+
first_name: firstName,
|
|
2701
|
+
last_name: lastName,
|
|
2702
|
+
email: contactEmail,
|
|
2703
|
+
phone: contactPhone || ""
|
|
2704
|
+
};
|
|
2705
|
+
console.log(chalk.green(" \u2713 Contact info saved"));
|
|
2706
|
+
} else {
|
|
2707
|
+
console.log(chalk.gray(" \u2139 Skipped \u2014 you can add this later in the dashboard."));
|
|
2708
|
+
}
|
|
2709
|
+
console.log(chalk.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
2698
2710
|
}
|
|
2699
|
-
|
|
2700
|
-
if (techStack.platforms.includes("android")) {
|
|
2711
|
+
if (hasPS) {
|
|
2701
2712
|
console.log();
|
|
2702
2713
|
console.log(chalk.bold(" \u{1F916} Play Store Declarations"));
|
|
2703
2714
|
console.log(chalk.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|