@forvibe/cli 0.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.
@@ -0,0 +1,144 @@
1
+ // src/ai/aso-generator.ts
2
+ import { GoogleGenerativeAI } from "@google/generative-ai";
3
+ var ASO_SYSTEM_PROMPT = `You are a world-class App Store Optimization (ASO) specialist with deep expertise in keyword strategy, conversion optimization, and store listing copywriting.
4
+
5
+ Your task is to generate a complete, ASO-optimized store listing for a mobile app.
6
+
7
+ ## APP STORE (iOS) RULES \u2014 STRICTLY ENFORCE CHARACTER LIMITS:
8
+ - app_name: maximum 30 characters. Include primary keyword. Be memorable and brandable.
9
+ - subtitle: maximum 30 characters. Complement the app name with secondary keywords. Do NOT repeat words from the app name.
10
+ - description: maximum 4000 characters. The FIRST SENTENCE is critical \u2014 it's visible before "Read More". Use feature bullets with line breaks. Include a call-to-action at the end. Naturally weave keywords without stuffing. Use only standard dash (-) or bullet (\u2022) characters for lists. NEVER use special unicode symbols like \u2605, \u2713, \u2714, \u2B50, \u2764, \u25CF, \u25A0, \u25B6, \u2611 \u2014 App Store Connect REJECTS these characters.
11
+ - keywords: maximum 100 characters. Comma-separated with NO spaces after commas. Do NOT include words already in the app name or subtitle. Prioritize single words over phrases. Use ALL available characters. Focus on high-search-volume, low-competition terms.
12
+ - promotional_text: maximum 170 characters. Engaging hook highlighting the app's core value proposition.
13
+ - whats_new: maximum 170 characters. For v1.0, highlight key launch features.
14
+
15
+ ## PLAY STORE (Android) RULES \u2014 ONLY IF ANDROID PLATFORM:
16
+ - title: maximum 30 characters. Can differ from iOS app_name for better Play Store optimization.
17
+ - short_description: maximum 80 characters. Concise value proposition. Must be different from subtitle.
18
+ - description: maximum 4000 characters. Play Store descriptions can use HTML formatting (<b>, <i>, <br>). Focus on features and benefits.
19
+ - whats_new: maximum 500 characters. Release notes for initial launch.
20
+
21
+ ## ASO BEST PRACTICES:
22
+ 1. Front-load keywords in title and first line of description
23
+ 2. Avoid generic words ("best", "free", "app") in keywords \u2014 they waste characters
24
+ 3. Use long-tail keyword variations across different fields
25
+ 4. Description should answer: What does the app do? Why should I download it? What makes it unique?
26
+ 5. Each keyword in the keywords field should be a single word or short phrase, never repeat words across keyword field, app name, and subtitle
27
+ 6. Write in English (en-US locale)
28
+
29
+ Respond ONLY with a valid JSON object, no markdown, no explanation.`;
30
+ function buildASOPrompt(report) {
31
+ const parts = [];
32
+ parts.push(`## App Information
33
+ - App Name: ${report.app_name}
34
+ - Bundle ID: ${report.bundle_id}
35
+ - Description: ${report.description}
36
+ - App Type: ${report.app_type}
37
+ - Category: ${report.app_category_suggestion}
38
+ - Tech Stack: ${report.tech_stack}
39
+ - Platforms: ${report.platforms.join(", ")}
40
+ - Version: ${report.version}`);
41
+ parts.push(`## Key Features
42
+ ${report.key_features.map((f, i) => `${i + 1}. ${f}`).join("\n")}`);
43
+ parts.push(`## Target Audience
44
+ ${report.target_audience}`);
45
+ parts.push(`## Unique Selling Points
46
+ ${report.unique_selling_points.map((u) => `- ${u}`).join("\n")}`);
47
+ parts.push(`## Business Model
48
+ - Model: ${report.business_model.model}
49
+ - Purchase Type: ${report.business_model.purchase_type}`);
50
+ if (report.readme_content) {
51
+ parts.push(`## README (for additional context)
52
+ ${report.readme_content.substring(0, 3e3)}`);
53
+ }
54
+ const includePlayStore = report.platforms.includes("android");
55
+ parts.push(`## Required JSON Response Format
56
+ {
57
+ "appstore": {
58
+ "app_name": "max 30 chars, keyword-rich app name",
59
+ "subtitle": "max 30 chars, complementary keywords",
60
+ "description": "max 4000 chars, ASO-optimized with bullets and line breaks",
61
+ "keywords": "keyword1,keyword2,keyword3 (max 100 chars, NO spaces after commas)",
62
+ "promotional_text": "max 170 chars, engaging hook",
63
+ "whats_new": "max 170 chars, launch highlights"
64
+ }${includePlayStore ? `,
65
+ "playstore": {
66
+ "title": "max 30 chars, Play Store optimized title",
67
+ "short_description": "max 80 chars, concise value proposition",
68
+ "description": "max 4000 chars, HTML formatting allowed",
69
+ "whats_new": "max 500 chars, launch release notes"
70
+ }` : ""}
71
+ }`);
72
+ return parts.join("\n\n");
73
+ }
74
+ function truncateKeywords(keywords, maxLength) {
75
+ if (keywords.length <= maxLength) return keywords;
76
+ const parts = keywords.split(",");
77
+ let result = "";
78
+ for (const part of parts) {
79
+ const candidate = result ? `${result},${part}` : part;
80
+ if (candidate.length > maxLength) break;
81
+ result = candidate;
82
+ }
83
+ return result || keywords.substring(0, maxLength);
84
+ }
85
+ function enforceCharLimits(raw) {
86
+ const result = {
87
+ appstore: {
88
+ app_name: (raw.appstore.app_name || "").substring(0, 30),
89
+ subtitle: (raw.appstore.subtitle || "").substring(0, 30),
90
+ description: (raw.appstore.description || "").substring(0, 4e3),
91
+ keywords: truncateKeywords(raw.appstore.keywords || "", 100),
92
+ promotional_text: (raw.appstore.promotional_text || "").substring(0, 170),
93
+ whats_new: (raw.appstore.whats_new || "").substring(0, 170)
94
+ }
95
+ };
96
+ if (raw.playstore) {
97
+ result.playstore = {
98
+ title: (raw.playstore.title || "").substring(0, 30),
99
+ short_description: (raw.playstore.short_description || "").substring(0, 80),
100
+ description: (raw.playstore.description || "").substring(0, 4e3),
101
+ whats_new: (raw.playstore.whats_new || "").substring(0, 500)
102
+ };
103
+ }
104
+ return result;
105
+ }
106
+ async function generateASOContent(report, apiKey) {
107
+ const genAI = new GoogleGenerativeAI(apiKey);
108
+ const model = genAI.getGenerativeModel({
109
+ model: "gemini-2.5-flash",
110
+ generationConfig: {
111
+ temperature: 0.5,
112
+ responseMimeType: "application/json"
113
+ }
114
+ });
115
+ const userPrompt = buildASOPrompt(report);
116
+ const result = await model.generateContent([
117
+ { text: ASO_SYSTEM_PROMPT },
118
+ { text: userPrompt }
119
+ ]);
120
+ const responseText = result.response.text();
121
+ let parsed;
122
+ try {
123
+ parsed = JSON.parse(responseText);
124
+ } catch {
125
+ const jsonMatch = responseText.match(/\{[\s\S]*\}/);
126
+ const rawJson = jsonMatch ? jsonMatch[0] : responseText;
127
+ try {
128
+ const cleaned = rawJson.replace(/,\s*([}\]])/g, "$1").replace(
129
+ /[\x00-\x1F\x7F]/g,
130
+ (ch) => ch === "\n" || ch === "\r" || ch === " " ? ch : ""
131
+ );
132
+ parsed = JSON.parse(cleaned);
133
+ } catch {
134
+ throw new Error("Failed to parse ASO AI response as JSON");
135
+ }
136
+ }
137
+ if (!parsed.appstore) {
138
+ throw new Error("AI response missing appstore field");
139
+ }
140
+ return enforceCharLimits(parsed);
141
+ }
142
+ export {
143
+ generateASOContent
144
+ };
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node