@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.
Files changed (2) hide show
  1. package/dist/index.js +69 -131
  2. 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 = 5 * 1024 * 1024;
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
- return assets.slice(0, MAX_TOTAL_ASSETS);
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 error = await response.json().catch(() => ({ error: "Unknown error" }));
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
- throw new Error(error.error || `Report submission failed (${response.status})`);
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(error.error || `Analysis failed (${response.status})`);
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
- if (useLocalAI) {
2067
- console.log(chalk.yellow(" \u26A0 Using local Gemini API (deprecated \u2014 will be removed in a future version)"));
2068
- const geminiApiKey = process.env.GEMINI_API_KEY || process.env.GOOGLE_AI_API_KEY;
2069
- const aiSpinner = ora({
2070
- text: "AI is analyzing your project...",
2071
- prefixText: " "
2072
- }).start();
2073
- try {
2074
- const { generateReport } = await import("./report-generator-NMGCGKPS.js");
2075
- report = await generateReport(
2076
- { techStack, config, sdkScan, branding, readmeContent, sourceCode, projectTree },
2077
- geminiApiKey
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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forvibe/cli",
3
- "version": "0.1.0",
3
+ "version": "1.0.1",
4
4
  "description": "Forvibe CLI - AI-powered project analyzer for App Store automation",
5
5
  "type": "module",
6
6
  "bin": {