@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
- parts.push(`## Required JSON Response Format
55
- {
56
- "appstore": {
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
- }${includePlayStore ? `,
64
- "playstore": {
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
- appstore: {
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
- if (!parsed.appstore) {
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-5I3OEF5J.js");
2602
- const asoContent = await generateASOContent(report, provider);
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
- console.log();
2632
- console.log(chalk.bold(" \u{1F4F1} App Store Listing Preview"));
2633
- 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"));
2634
- const aso = report.aso_content.appstore;
2635
- console.log(` Title: ${chalk.white(aso.app_name)} ${chalk.gray(`(${aso.app_name.length}/30)`)}`);
2636
- console.log(` Subtitle: ${chalk.white(aso.subtitle)} ${chalk.gray(`(${aso.subtitle.length}/30)`)}`);
2637
- console.log(` Keywords: ${chalk.white(aso.keywords)} ${chalk.gray(`(${aso.keywords.length}/100)`)}`);
2638
- console.log(` Promo: ${chalk.white(aso.promotional_text)} ${chalk.gray(`(${aso.promotional_text.length}/170)`)}`);
2639
- const descPreview = aso.description.substring(0, 150).replace(/\n/g, " ");
2640
- console.log(` Description: ${chalk.white(descPreview)}${aso.description.length > 150 ? chalk.gray("...") : ""} ${chalk.gray(`(${aso.description.length}/4000)`)}`);
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
- console.log();
2654
- console.log(chalk.bold(" \u{1F4DD} App Review Information"));
2655
- 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"));
2656
- console.log(chalk.gray(" Apple's review team may need to sign in to your app."));
2657
- console.log(chalk.gray(" You can also provide this later in the Forvibe dashboard."));
2658
- console.log();
2659
- const hasAuthSDKs = (sdkScan.third_party_services.auth?.length ?? 0) > 0;
2660
- if (hasAuthSDKs) {
2661
- const signInAnswer = await askQuestion(
2662
- chalk.cyan(" Does your app require sign-in to use? (y/n): ")
2663
- );
2664
- const signInRequired = signInAnswer.toLowerCase().startsWith("y");
2665
- report.sign_in_required = signInRequired;
2666
- if (signInRequired) {
2667
- console.log();
2668
- console.log(chalk.yellow(" \u26A0 Please create a test account in your app for Apple's review team."));
2669
- console.log(chalk.gray(" This account should have full access to your app's features."));
2670
- console.log();
2671
- const testEmail = await askQuestion(chalk.cyan(" Test account email: "));
2672
- const testPassword = await askQuestion(chalk.cyan(" Test account password: "));
2673
- if (testEmail && testPassword) {
2674
- report.test_account = { email: testEmail, password: testPassword };
2675
- console.log(chalk.green(" \u2713 Test account saved"));
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
- } else {
2679
- report.sign_in_required = false;
2680
- }
2681
- console.log();
2682
- console.log(chalk.gray(" Contact information for Apple's review team (press Enter to skip):"));
2683
- console.log();
2684
- const firstName = await askQuestion(chalk.cyan(" First name: "));
2685
- const lastName = await askQuestion(chalk.cyan(" Last name: "));
2686
- const contactEmail = await askQuestion(chalk.cyan(" Email: "));
2687
- const contactPhone = await askQuestion(chalk.cyan(" Phone (e.g. +1 555 123 4567): "));
2688
- if (firstName && lastName && contactEmail) {
2689
- report.review_contact = {
2690
- first_name: firstName,
2691
- last_name: lastName,
2692
- email: contactEmail,
2693
- phone: contactPhone || ""
2694
- };
2695
- console.log(chalk.green(" \u2713 Contact info saved"));
2696
- } else {
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
- 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"));
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"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forvibe/cli",
3
- "version": "1.0.9",
3
+ "version": "1.1.0",
4
4
  "description": "Forvibe CLI - AI-powered project analyzer for App Store automation",
5
5
  "type": "module",
6
6
  "bin": {