@forvibe/cli 0.1.0 → 1.0.1
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/dist/index.js +69 -131
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1576,7 +1576,8 @@ function generateProjectTree(rootDir, maxDepth = 5, maxEntries = 300) {
|
|
|
1576
1576
|
// src/analyzers/asset-scanner.ts
|
|
1577
1577
|
import { readFileSync as readFileSync3, existsSync as existsSync4, readdirSync as readdirSync6, statSync as statSync4 } from "fs";
|
|
1578
1578
|
import { join as join7, extname as extname2, relative as relative3, basename } from "path";
|
|
1579
|
-
var MAX_ASSET_SIZE =
|
|
1579
|
+
var MAX_ASSET_SIZE = 1 * 1024 * 1024;
|
|
1580
|
+
var MAX_TOTAL_BASE64_SIZE = 3 * 1024 * 1024;
|
|
1580
1581
|
var MAX_TOTAL_ASSETS = 10;
|
|
1581
1582
|
var MAX_SCREENSHOTS = 5;
|
|
1582
1583
|
var MIN_ASSET_SIZE = 500;
|
|
@@ -1780,6 +1781,9 @@ function getPromotionalPaths(rootDir, techStack) {
|
|
|
1780
1781
|
}
|
|
1781
1782
|
return paths;
|
|
1782
1783
|
}
|
|
1784
|
+
function totalBase64Size(assets) {
|
|
1785
|
+
return assets.reduce((sum, a) => sum + a.base64_data.length, 0);
|
|
1786
|
+
}
|
|
1783
1787
|
function scanAppAssets(rootDir, techStack) {
|
|
1784
1788
|
const assets = [];
|
|
1785
1789
|
const screenshotDirs = getScreenshotDirs(rootDir, techStack);
|
|
@@ -1818,7 +1822,17 @@ function scanAppAssets(rootDir, techStack) {
|
|
|
1818
1822
|
break;
|
|
1819
1823
|
}
|
|
1820
1824
|
}
|
|
1821
|
-
|
|
1825
|
+
const result = assets.slice(0, MAX_TOTAL_ASSETS);
|
|
1826
|
+
while (result.length > 0 && totalBase64Size(result) > MAX_TOTAL_BASE64_SIZE) {
|
|
1827
|
+
let largestIdx = 0;
|
|
1828
|
+
for (let i = 1; i < result.length; i++) {
|
|
1829
|
+
if (result[i].base64_data.length > result[largestIdx].base64_data.length) {
|
|
1830
|
+
largestIdx = i;
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
result.splice(largestIdx, 1);
|
|
1834
|
+
}
|
|
1835
|
+
return result;
|
|
1822
1836
|
}
|
|
1823
1837
|
|
|
1824
1838
|
// src/api/forvibe-client.ts
|
|
@@ -1874,69 +1888,25 @@ var ForvibeClient = class {
|
|
|
1874
1888
|
body: JSON.stringify({ report })
|
|
1875
1889
|
});
|
|
1876
1890
|
if (!response.ok) {
|
|
1877
|
-
const
|
|
1891
|
+
const bodyText = await response.text().catch(() => "");
|
|
1892
|
+
let errorMessage;
|
|
1893
|
+
try {
|
|
1894
|
+
const errorJson = JSON.parse(bodyText);
|
|
1895
|
+
errorMessage = errorJson.error;
|
|
1896
|
+
} catch {
|
|
1897
|
+
}
|
|
1878
1898
|
if (response.status === 401) {
|
|
1879
1899
|
throw new Error("Session expired. Please generate a new connection code.");
|
|
1880
1900
|
}
|
|
1881
1901
|
if (response.status === 409) {
|
|
1882
1902
|
throw new Error("Report has already been submitted for this session.");
|
|
1883
1903
|
}
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
return await response.json();
|
|
1887
|
-
}
|
|
1888
|
-
/**
|
|
1889
|
-
* Send raw project data to backend for AI analysis (Gemini proxy)
|
|
1890
|
-
*/
|
|
1891
|
-
async analyzeProject(input) {
|
|
1892
|
-
if (!this.sessionToken) {
|
|
1893
|
-
throw new Error("Not connected. Please validate OTC code first.");
|
|
1894
|
-
}
|
|
1895
|
-
const response = await fetch(`${this.baseUrl}/api/agent/analyze`, {
|
|
1896
|
-
method: "POST",
|
|
1897
|
-
headers: {
|
|
1898
|
-
"Content-Type": "application/json",
|
|
1899
|
-
Authorization: `Bearer ${this.sessionToken}`
|
|
1900
|
-
},
|
|
1901
|
-
body: JSON.stringify({
|
|
1902
|
-
tech_stack: {
|
|
1903
|
-
stack: input.techStack.stack,
|
|
1904
|
-
label: input.techStack.label,
|
|
1905
|
-
platforms: input.techStack.platforms,
|
|
1906
|
-
configFiles: input.techStack.configFiles
|
|
1907
|
-
},
|
|
1908
|
-
config: {
|
|
1909
|
-
app_name: input.config.app_name,
|
|
1910
|
-
bundle_id: input.config.bundle_id,
|
|
1911
|
-
version: input.config.version,
|
|
1912
|
-
min_ios_version: input.config.min_ios_version,
|
|
1913
|
-
min_android_sdk: input.config.min_android_sdk,
|
|
1914
|
-
description: input.config.description
|
|
1915
|
-
},
|
|
1916
|
-
sdk_scan: {
|
|
1917
|
-
detected_sdks: input.sdkScan.detected_sdks,
|
|
1918
|
-
data_collected: input.sdkScan.data_collected,
|
|
1919
|
-
advertising_type: input.sdkScan.advertising_type,
|
|
1920
|
-
third_party_services: input.sdkScan.third_party_services,
|
|
1921
|
-
has_iap: input.sdkScan.has_iap
|
|
1922
|
-
},
|
|
1923
|
-
branding: {
|
|
1924
|
-
primary_color: input.branding.primary_color,
|
|
1925
|
-
secondary_color: input.branding.secondary_color,
|
|
1926
|
-
app_icon_base64: input.branding.app_icon_base64
|
|
1927
|
-
},
|
|
1928
|
-
readme_content: input.readmeContent,
|
|
1929
|
-
source_code: input.sourceCode,
|
|
1930
|
-
project_tree: input.projectTree
|
|
1931
|
-
}),
|
|
1932
|
-
signal: AbortSignal.timeout(12e4)
|
|
1933
|
-
});
|
|
1934
|
-
if (!response.ok) {
|
|
1935
|
-
const error = await response.json().catch(() => ({ error: "Unknown error" }));
|
|
1936
|
-
if (response.status === 401) {
|
|
1937
|
-
throw new Error("Session expired. Please generate a new connection code.");
|
|
1904
|
+
if (response.status === 413) {
|
|
1905
|
+
throw new Error("Report too large. Try reducing the number of screenshots.");
|
|
1938
1906
|
}
|
|
1939
|
-
throw new Error(
|
|
1907
|
+
throw new Error(
|
|
1908
|
+
errorMessage || `Report submission failed (HTTP ${response.status}: ${bodyText.substring(0, 200)})`
|
|
1909
|
+
);
|
|
1940
1910
|
}
|
|
1941
1911
|
return await response.json();
|
|
1942
1912
|
}
|
|
@@ -1962,6 +1932,15 @@ async function analyzeCommand(options) {
|
|
|
1962
1932
|
chalk.bold(" Forvibe CLI") + chalk.gray(" \u2014 AI-powered App Store automation")
|
|
1963
1933
|
);
|
|
1964
1934
|
console.log();
|
|
1935
|
+
const geminiApiKey = process.env.GEMINI_API_KEY || process.env.GOOGLE_AI_API_KEY;
|
|
1936
|
+
if (!geminiApiKey) {
|
|
1937
|
+
console.log(chalk.red(" \u2717 Gemini API key is required for project analysis.\n"));
|
|
1938
|
+
console.log(chalk.white(" Set your API key:"));
|
|
1939
|
+
console.log(chalk.cyan(" export GEMINI_API_KEY=your-api-key-here\n"));
|
|
1940
|
+
console.log(chalk.gray(" Get a free API key at: https://aistudio.google.com/apikey"));
|
|
1941
|
+
console.log(chalk.gray(" Your source code is analyzed locally \u2014 it never leaves your machine.\n"));
|
|
1942
|
+
process.exit(1);
|
|
1943
|
+
}
|
|
1965
1944
|
const otcCode = await askQuestion(
|
|
1966
1945
|
chalk.cyan(" \u{1F517} Enter your Forvibe connection code: ")
|
|
1967
1946
|
);
|
|
@@ -2061,78 +2040,40 @@ async function analyzeCommand(options) {
|
|
|
2061
2040
|
`Source code: ${chalk.bold(String(sourceLines))} lines analyzed ${readmeContent ? chalk.gray("(README found)") : ""}`
|
|
2062
2041
|
);
|
|
2063
2042
|
console.log();
|
|
2064
|
-
const useLocalAI = options.local && (process.env.GEMINI_API_KEY || process.env.GOOGLE_AI_API_KEY);
|
|
2065
2043
|
let report;
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
);
|
|
2079
|
-
if (appAssets.length > 0) {
|
|
2080
|
-
report.app_assets = appAssets;
|
|
2081
|
-
}
|
|
2082
|
-
aiSpinner.succeed(chalk.green("Analysis complete!"));
|
|
2083
|
-
} catch (error) {
|
|
2084
|
-
aiSpinner.fail(
|
|
2085
|
-
chalk.red(`AI analysis failed: ${error instanceof Error ? error.message : "Unknown error"}`)
|
|
2086
|
-
);
|
|
2087
|
-
process.exit(1);
|
|
2088
|
-
}
|
|
2089
|
-
const asoSpinner = ora({
|
|
2090
|
-
text: "Generating ASO-optimized store listing...",
|
|
2091
|
-
prefixText: " "
|
|
2092
|
-
}).start();
|
|
2093
|
-
try {
|
|
2094
|
-
const { generateASOContent } = await import("./aso-generator-AZXT6ZCL.js");
|
|
2095
|
-
const asoContent = await generateASOContent(report, geminiApiKey);
|
|
2096
|
-
report.aso_content = asoContent;
|
|
2097
|
-
asoSpinner.succeed(chalk.green("Store listing content generated!"));
|
|
2098
|
-
} catch (error) {
|
|
2099
|
-
asoSpinner.warn(
|
|
2100
|
-
chalk.yellow(`ASO generation skipped: ${error instanceof Error ? error.message : "Unknown error"}`)
|
|
2101
|
-
);
|
|
2102
|
-
}
|
|
2103
|
-
} else {
|
|
2104
|
-
const aiSpinner = ora({
|
|
2105
|
-
text: "Analyzing your project...",
|
|
2106
|
-
prefixText: " "
|
|
2107
|
-
}).start();
|
|
2108
|
-
try {
|
|
2109
|
-
const result = await client.analyzeProject({
|
|
2110
|
-
techStack,
|
|
2111
|
-
config,
|
|
2112
|
-
sdkScan,
|
|
2113
|
-
branding,
|
|
2114
|
-
readmeContent,
|
|
2115
|
-
sourceCode,
|
|
2116
|
-
projectTree
|
|
2117
|
-
});
|
|
2118
|
-
report = result.report;
|
|
2119
|
-
if (appAssets.length > 0) {
|
|
2120
|
-
report.app_assets = appAssets;
|
|
2121
|
-
}
|
|
2122
|
-
if (result.warnings && result.warnings.length > 0) {
|
|
2123
|
-
aiSpinner.succeed(chalk.green("Analysis complete!"));
|
|
2124
|
-
for (const warning of result.warnings) {
|
|
2125
|
-
console.log(chalk.yellow(` \u26A0 ${warning}`));
|
|
2126
|
-
}
|
|
2127
|
-
} else {
|
|
2128
|
-
aiSpinner.succeed(chalk.green("Analysis complete!"));
|
|
2129
|
-
}
|
|
2130
|
-
} catch (error) {
|
|
2131
|
-
aiSpinner.fail(
|
|
2132
|
-
chalk.red(`Analysis failed: ${error instanceof Error ? error.message : "Unknown error"}`)
|
|
2133
|
-
);
|
|
2134
|
-
process.exit(1);
|
|
2044
|
+
const aiSpinner = ora({
|
|
2045
|
+
text: "Analyzing locally with Gemini AI (your source code never leaves your machine)...",
|
|
2046
|
+
prefixText: " "
|
|
2047
|
+
}).start();
|
|
2048
|
+
try {
|
|
2049
|
+
const { generateReport } = await import("./report-generator-NMGCGKPS.js");
|
|
2050
|
+
report = await generateReport(
|
|
2051
|
+
{ techStack, config, sdkScan, branding, readmeContent, sourceCode, projectTree },
|
|
2052
|
+
geminiApiKey
|
|
2053
|
+
);
|
|
2054
|
+
if (appAssets.length > 0) {
|
|
2055
|
+
report.app_assets = appAssets;
|
|
2135
2056
|
}
|
|
2057
|
+
aiSpinner.succeed(chalk.green("Analysis complete!"));
|
|
2058
|
+
} catch (error) {
|
|
2059
|
+
aiSpinner.fail(
|
|
2060
|
+
chalk.red(`AI analysis failed: ${error instanceof Error ? error.message : "Unknown error"}`)
|
|
2061
|
+
);
|
|
2062
|
+
process.exit(1);
|
|
2063
|
+
}
|
|
2064
|
+
const asoSpinner = ora({
|
|
2065
|
+
text: "Generating ASO-optimized store listing...",
|
|
2066
|
+
prefixText: " "
|
|
2067
|
+
}).start();
|
|
2068
|
+
try {
|
|
2069
|
+
const { generateASOContent } = await import("./aso-generator-AZXT6ZCL.js");
|
|
2070
|
+
const asoContent = await generateASOContent(report, geminiApiKey);
|
|
2071
|
+
report.aso_content = asoContent;
|
|
2072
|
+
asoSpinner.succeed(chalk.green("Store listing content generated!"));
|
|
2073
|
+
} catch (error) {
|
|
2074
|
+
asoSpinner.warn(
|
|
2075
|
+
chalk.yellow(`ASO generation skipped: ${error instanceof Error ? error.message : "Unknown error"}`)
|
|
2076
|
+
);
|
|
2136
2077
|
}
|
|
2137
2078
|
console.log();
|
|
2138
2079
|
console.log(chalk.bold(" \u{1F4CB} Report Summary"));
|
|
@@ -2208,8 +2149,5 @@ program.command("analyze", { isDefault: true }).description(
|
|
|
2208
2149
|
"--api-url <url>",
|
|
2209
2150
|
"Forvibe API URL (for development)",
|
|
2210
2151
|
void 0
|
|
2211
|
-
).option(
|
|
2212
|
-
"--local",
|
|
2213
|
-
"Use local Gemini API key instead of Forvibe backend (requires GEMINI_API_KEY env var)"
|
|
2214
2152
|
).action(analyzeCommand);
|
|
2215
2153
|
program.parse();
|