@comergehq/cli 0.1.5 → 0.1.7

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/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/cli.ts
4
4
  import { Command } from "commander";
5
- import pc9 from "picocolors";
5
+ import pc10 from "picocolors";
6
6
  import { createRequire } from "module";
7
7
 
8
8
  // src/commands/login.ts
@@ -22,7 +22,7 @@ var CliError = class extends Error {
22
22
 
23
23
  // src/config/config.ts
24
24
  import { z } from "zod";
25
- var API_URL = "http://localhost:8080";
25
+ var API_URL = "https://api.comerge.ai";
26
26
  var SUPABASE_URL = "https://xtfxwbckjpfmqubnsusu.supabase.co";
27
27
  var SUPABASE_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inh0Znh3YmNranBmbXF1Ym5zdXN1Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjA2MDEyMzAsImV4cCI6MjA3NjE3NzIzMH0.dzWGAWrK4CvrmHVHzf8w7JlUZohdap0ZPnLZnABMV8s";
28
28
  function isValidUrl(value) {
@@ -438,9 +438,9 @@ function createApiClient(config, opts) {
438
438
  const cfg = config;
439
439
  const apiKey = (opts?.apiKey ?? "").trim();
440
440
  const CLIENT_KEY_HEADER = "x-comerge-api-key";
441
- async function request(path12, init) {
441
+ async function request(path13, init) {
442
442
  const { token, session, fromEnv } = await getAuthToken(cfg);
443
- const url = new URL(path12, cfg.apiUrl).toString();
443
+ const url = new URL(path13, cfg.apiUrl).toString();
444
444
  const doFetch = async (bearer) => {
445
445
  const res2 = await fetch(url, {
446
446
  ...init,
@@ -484,7 +484,13 @@ function createApiClient(config, opts) {
484
484
  }),
485
485
  getApp: (appId) => request(`/v1/apps/${encodeURIComponent(appId)}`, { method: "GET" }),
486
486
  presignImportUpload: (payload) => request("/v1/apps/import/upload/presign", { method: "POST", body: JSON.stringify(payload) }),
487
- importFromUpload: (payload) => request("/v1/apps/import/upload", { method: "POST", body: JSON.stringify(payload) })
487
+ importFromUpload: (payload) => request("/v1/apps/import/upload", { method: "POST", body: JSON.stringify(payload) }),
488
+ initiateBundle: (appId, payload) => request(`/v1/apps/${encodeURIComponent(appId)}/bundles`, { method: "POST", body: JSON.stringify(payload) }),
489
+ getBundle: (appId, bundleId) => request(`/v1/apps/${encodeURIComponent(appId)}/bundles/${encodeURIComponent(bundleId)}`, { method: "GET" }),
490
+ getBundleDownloadUrl: (appId, bundleId, options) => request(
491
+ `/v1/apps/${encodeURIComponent(appId)}/bundles/${encodeURIComponent(bundleId)}/download?redirect=${options?.redirect ?? false}`,
492
+ { method: "GET" }
493
+ )
488
494
  };
489
495
  }
490
496
 
@@ -584,11 +590,11 @@ function registerWhoamiCommand(program) {
584
590
  }
585
591
 
586
592
  // src/commands/init.ts
587
- import pc8 from "picocolors";
593
+ import pc9 from "picocolors";
588
594
 
589
595
  // src/services/initLocal.ts
590
596
  import os5 from "os";
591
- import pc7 from "picocolors";
597
+ import pc8 from "picocolors";
592
598
  import prompts from "prompts";
593
599
 
594
600
  // src/services/ensureAuth.ts
@@ -1057,9 +1063,9 @@ async function importLocal(params) {
1057
1063
  }
1058
1064
 
1059
1065
  // src/commands/shellInit.ts
1060
- import path9 from "path";
1066
+ import path10 from "path";
1061
1067
  import os3 from "os";
1062
- import pc6 from "picocolors";
1068
+ import pc7 from "picocolors";
1063
1069
  import fse4 from "fs-extra";
1064
1070
 
1065
1071
  // src/utils/packageJson.ts
@@ -1148,16 +1154,25 @@ import { ComergeStudio } from '@comergehq/studio';
1148
1154
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
1149
1155
  // @ts-ignore
1150
1156
  import config from '../comerge.config.json';
1157
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
1158
+ // @ts-ignore
1159
+ import embeddedMeta from '../assets/comerge/base.meta.json';
1151
1160
 
1152
1161
  export default function Index() {
1153
1162
  const appId = String((config as any)?.appId || '');
1154
1163
  const appKey = String((config as any)?.appKey || 'MicroMain');
1155
- const apiKey = String((config as any)?.apiKey || '');
1164
+ const clientKey = String((config as any)?.clientKey || '');
1165
+ const embeddedBaseBundles = {
1166
+ ios: { module: require('../assets/comerge/base.ios.jsbundle'), meta: (embeddedMeta as any)?.ios },
1167
+ android: { module: require('../assets/comerge/base.android.jsbundle'), meta: (embeddedMeta as any)?.android },
1168
+ };
1156
1169
  return (
1157
1170
  <>
1158
1171
  <Stack.Screen options={{ headerShown: false }} />
1159
1172
  <View style={{ flex: 1 }}>
1160
- {appId ? <ComergeStudio appId={appId} apiKey={apiKey} appKey={appKey} /> : null}
1173
+ {appId ? (
1174
+ <ComergeStudio appId={appId} clientKey={clientKey} appKey={appKey} embeddedBaseBundles={embeddedBaseBundles} />
1175
+ ) : null}
1161
1176
  </View>
1162
1177
  </>
1163
1178
  );
@@ -1178,6 +1193,12 @@ function shellMetroConfigJs() {
1178
1193
  return `const { getDefaultConfig } = require('expo/metro-config');
1179
1194
 
1180
1195
  const config = getDefaultConfig(__dirname);
1196
+ const resolver = config.resolver || {};
1197
+ const assetExts = resolver.assetExts || [];
1198
+ if (!assetExts.includes('jsbundle')) {
1199
+ resolver.assetExts = [...assetExts, 'jsbundle'];
1200
+ }
1201
+ config.resolver = resolver;
1181
1202
 
1182
1203
  module.exports = config;
1183
1204
  `;
@@ -1276,7 +1297,7 @@ async function stripProject(params) {
1276
1297
  }
1277
1298
 
1278
1299
  // src/commands/shellInit.ts
1279
- import fs11 from "fs/promises";
1300
+ import fs12 from "fs/promises";
1280
1301
 
1281
1302
  // src/utils/appJsonPatch.ts
1282
1303
  import fs9 from "fs/promises";
@@ -1420,6 +1441,145 @@ function findMatchingBracket(text, openIdx, open, close) {
1420
1441
  return -1;
1421
1442
  }
1422
1443
 
1444
+ // src/services/bundles.ts
1445
+ import fs11 from "fs";
1446
+ import fsPromises from "fs/promises";
1447
+ import path9 from "path";
1448
+ import { Readable } from "stream";
1449
+ import { pipeline } from "stream/promises";
1450
+ import pc6 from "picocolors";
1451
+ function sleep2(ms) {
1452
+ return new Promise((r) => setTimeout(r, ms));
1453
+ }
1454
+ function isRetryableNetworkError(e) {
1455
+ const err = e;
1456
+ const code = typeof err?.code === "string" ? err.code : "";
1457
+ const message = typeof err?.message === "string" ? err.message : "";
1458
+ if (code === "ERR_NETWORK" || code === "ECONNABORTED" || code === "ETIMEDOUT") return true;
1459
+ if (message.toLowerCase().includes("network error")) return true;
1460
+ if (message.toLowerCase().includes("timeout")) return true;
1461
+ return false;
1462
+ }
1463
+ async function withRetry(fn, opts) {
1464
+ let lastErr = null;
1465
+ for (let attempt = 1; attempt <= opts.attempts; attempt += 1) {
1466
+ try {
1467
+ return await fn();
1468
+ } catch (e) {
1469
+ lastErr = e;
1470
+ const retryable = isRetryableNetworkError(e);
1471
+ if (!retryable || attempt >= opts.attempts) {
1472
+ throw e;
1473
+ }
1474
+ const exp = Math.min(opts.maxDelayMs, opts.baseDelayMs * Math.pow(2, attempt - 1));
1475
+ const jitter = Math.floor(Math.random() * 250);
1476
+ await sleep2(exp + jitter);
1477
+ }
1478
+ }
1479
+ throw lastErr;
1480
+ }
1481
+ function unwrapResponseObject(resp, label) {
1482
+ const obj = resp?.responseObject;
1483
+ if (obj === void 0 || obj === null) {
1484
+ const message = typeof resp?.message === "string" && resp.message.trim().length > 0 ? resp.message : `Missing ${label} response`;
1485
+ throw new CliError(message, { exitCode: 1, hint: resp ? JSON.stringify(resp, null, 2) : null });
1486
+ }
1487
+ return obj;
1488
+ }
1489
+ async function pollBundle(api, appId, bundleId, opts) {
1490
+ const start = Date.now();
1491
+ while (true) {
1492
+ try {
1493
+ const bundleResp = await api.getBundle(appId, bundleId);
1494
+ const bundle = unwrapResponseObject(bundleResp, "bundle");
1495
+ if (bundle.status === "succeeded" || bundle.status === "failed") return bundle;
1496
+ } catch (e) {
1497
+ if (!isRetryableNetworkError(e)) {
1498
+ throw e;
1499
+ }
1500
+ }
1501
+ if (Date.now() - start > opts.timeoutMs) {
1502
+ throw new Error("Bundle build timed out.");
1503
+ }
1504
+ await sleep2(opts.intervalMs);
1505
+ }
1506
+ }
1507
+ async function downloadToFile(url, targetPath) {
1508
+ const dir = path9.dirname(targetPath);
1509
+ await fsPromises.mkdir(dir, { recursive: true });
1510
+ const tmpPath = `${targetPath}.tmp-${Date.now()}`;
1511
+ await withRetry(
1512
+ async () => {
1513
+ const res = await fetch(url);
1514
+ if (!res.ok) {
1515
+ throw new Error(`Download failed (status ${res.status})`);
1516
+ }
1517
+ if (!res.body) {
1518
+ throw new Error("Download response has no body.");
1519
+ }
1520
+ const body = Readable.fromWeb(res.body);
1521
+ const out = fs11.createWriteStream(tmpPath);
1522
+ await pipeline(body, out);
1523
+ },
1524
+ { attempts: 3, baseDelayMs: 500, maxDelayMs: 4e3 }
1525
+ );
1526
+ const stat = await fsPromises.stat(tmpPath);
1527
+ if (!stat.size || stat.size <= 0) {
1528
+ await fsPromises.unlink(tmpPath).catch(() => {
1529
+ });
1530
+ throw new Error("Downloaded bundle is empty.");
1531
+ }
1532
+ await fsPromises.rename(tmpPath, targetPath);
1533
+ }
1534
+ async function downloadBundle(api, appId, platform, targetPath) {
1535
+ const initiateResp = await api.initiateBundle(appId, {
1536
+ platform,
1537
+ idempotencyKey: `${appId}:head:${platform}`
1538
+ });
1539
+ const initiate = unwrapResponseObject(initiateResp, "bundle initiate");
1540
+ const finalBundle = initiate.status === "succeeded" || initiate.status === "failed" ? initiate : await pollBundle(api, appId, initiate.id, { timeoutMs: 3 * 60 * 1e3, intervalMs: 1200 });
1541
+ if (finalBundle.status === "failed") {
1542
+ throw new Error("Bundle build failed.");
1543
+ }
1544
+ const signedResp = await api.getBundleDownloadUrl(appId, finalBundle.id, { redirect: false });
1545
+ const signed = unwrapResponseObject(signedResp, "bundle download url");
1546
+ const url = String(signed.url || "").trim();
1547
+ if (!url) throw new Error("Download URL is missing.");
1548
+ await downloadToFile(url, targetPath);
1549
+ const fingerprint = finalBundle.checksumSha256 ?? `id:${finalBundle.id}`;
1550
+ const meta = {
1551
+ fingerprint,
1552
+ bundleId: finalBundle.id,
1553
+ checksumSha256: finalBundle.checksumSha256 ?? null,
1554
+ size: finalBundle.size ?? null,
1555
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1556
+ };
1557
+ return { platform, bundlePath: targetPath, meta };
1558
+ }
1559
+ async function downloadEmbeddedBundles(params) {
1560
+ const log = params.log ?? (() => {
1561
+ });
1562
+ const baseDir = path9.join(params.outRoot, "assets", "comerge");
1563
+ const iosPath = path9.join(baseDir, "base.ios.jsbundle");
1564
+ const androidPath = path9.join(baseDir, "base.android.jsbundle");
1565
+ log(pc6.dim("Downloading base bundles for shell..."));
1566
+ const results = await Promise.allSettled([
1567
+ downloadBundle(params.api, params.appId, "ios", iosPath),
1568
+ downloadBundle(params.api, params.appId, "android", androidPath)
1569
+ ]);
1570
+ const meta = {};
1571
+ for (const res of results) {
1572
+ if (res.status === "fulfilled") {
1573
+ meta[res.value.platform] = res.value.meta;
1574
+ } else {
1575
+ log(pc6.yellow(`Warning: failed to download ${res.reason?.message ?? res.reason}`));
1576
+ }
1577
+ }
1578
+ await fsPromises.mkdir(baseDir, { recursive: true });
1579
+ await fsPromises.writeFile(path9.join(baseDir, "base.meta.json"), JSON.stringify(meta, null, 2) + "\n", "utf8");
1580
+ return meta;
1581
+ }
1582
+
1423
1583
  // src/commands/shellInit.ts
1424
1584
  async function shellInit(params) {
1425
1585
  const projectRoot = await findExpoProjectRoot(process.cwd());
@@ -1442,17 +1602,17 @@ async function shellInit(params) {
1442
1602
  exitCode: 2
1443
1603
  });
1444
1604
  }
1445
- const requestedOutputPath = path9.resolve(projectRoot, outDir);
1605
+ const requestedOutputPath = path10.resolve(projectRoot, outDir);
1446
1606
  const outputPath = await findAvailableDirPath(requestedOutputPath);
1447
- console.log(pc6.dim(`Project: ${projectRoot}`));
1607
+ console.log(pc7.dim(`Project: ${projectRoot}`));
1448
1608
  if (outputPath !== requestedOutputPath) {
1449
- console.log(pc6.dim(`Output: ${requestedOutputPath} (already exists)`));
1450
- console.log(pc6.dim(`Using: ${outputPath}`));
1609
+ console.log(pc7.dim(`Output: ${requestedOutputPath} (already exists)`));
1610
+ console.log(pc7.dim(`Using: ${outputPath}`));
1451
1611
  } else {
1452
- console.log(pc6.dim(`Output: ${outputPath}`));
1612
+ console.log(pc7.dim(`Output: ${outputPath}`));
1453
1613
  }
1454
- const outputIsInsideProject = outputPath === projectRoot || outputPath.startsWith(projectRoot + path9.sep);
1455
- const stagingPath = outputIsInsideProject ? await fs11.mkdtemp(path9.join(os3.tmpdir(), "comerge-shell-")) : outputPath;
1614
+ const outputIsInsideProject = outputPath === projectRoot || outputPath.startsWith(projectRoot + path10.sep);
1615
+ const stagingPath = outputIsInsideProject ? await fs12.mkdtemp(path10.join(os3.tmpdir(), "comerge-shell-")) : outputPath;
1456
1616
  let movedToFinal = false;
1457
1617
  try {
1458
1618
  if (!outputIsInsideProject) {
@@ -1461,28 +1621,40 @@ async function shellInit(params) {
1461
1621
  await copyProject({ projectRoot, outRoot: stagingPath });
1462
1622
  await stripProject({ outRoot: stagingPath });
1463
1623
  try {
1464
- const didPatch = await ensureComergeShellPlugins(path9.join(stagingPath, "app.json"));
1624
+ const didPatch = await ensureComergeShellPlugins(path10.join(stagingPath, "app.json"));
1465
1625
  if (didPatch) {
1466
- console.log(pc6.dim("Updated app.json with required Comerge shell plugins."));
1626
+ console.log(pc7.dim("Updated app.json with required Comerge shell plugins."));
1467
1627
  }
1468
1628
  } catch {
1469
1629
  }
1470
- await writeJsonAtomic(path9.join(stagingPath, "comerge.config.json"), {
1630
+ await writeJsonAtomic(path10.join(stagingPath, "comerge.config.json"), {
1471
1631
  appId,
1472
1632
  appKey: appKey || "MicroMain",
1473
- apiKey
1633
+ clientKey: apiKey
1474
1634
  });
1475
- await writeTextAtomic(path9.join(stagingPath, "app/_layout.tsx"), shellLayoutTsx());
1476
- await writeTextAtomic(path9.join(stagingPath, "app/index.tsx"), shellIndexTsx());
1477
- await ensureTextFile(path9.join(stagingPath, "babel.config.js"), shellBabelConfigJs());
1478
- await ensureTextFile(path9.join(stagingPath, "metro.config.js"), shellMetroConfigJs());
1479
1635
  try {
1480
- const didPatch = await ensureReanimatedBabelPlugin(path9.join(stagingPath, "babel.config.js"));
1481
- if (didPatch) console.log(pc6.dim("Updated babel.config.js to include react-native-reanimated/plugin."));
1636
+ const cfg = await resolveConfig({ yes: true });
1637
+ const api = createApiClient(cfg, { apiKey });
1638
+ await downloadEmbeddedBundles({
1639
+ api,
1640
+ appId,
1641
+ outRoot: stagingPath,
1642
+ log: (msg) => console.log(msg)
1643
+ });
1644
+ } catch (e) {
1645
+ console.log(pc7.yellow(`Warning: failed to embed base bundles: ${e instanceof Error ? e.message : String(e)}`));
1646
+ }
1647
+ await writeTextAtomic(path10.join(stagingPath, "app/_layout.tsx"), shellLayoutTsx());
1648
+ await writeTextAtomic(path10.join(stagingPath, "app/index.tsx"), shellIndexTsx());
1649
+ await ensureTextFile(path10.join(stagingPath, "babel.config.js"), shellBabelConfigJs());
1650
+ await ensureTextFile(path10.join(stagingPath, "metro.config.js"), shellMetroConfigJs());
1651
+ try {
1652
+ const didPatch = await ensureReanimatedBabelPlugin(path10.join(stagingPath, "babel.config.js"));
1653
+ if (didPatch) console.log(pc7.dim("Updated babel.config.js to include react-native-reanimated/plugin."));
1482
1654
  } catch {
1483
1655
  }
1484
1656
  await ensureTextFile(
1485
- path9.join(stagingPath, "tsconfig.json"),
1657
+ path10.join(stagingPath, "tsconfig.json"),
1486
1658
  JSON.stringify(
1487
1659
  {
1488
1660
  extends: "expo/tsconfig.base",
@@ -1496,9 +1668,9 @@ async function shellInit(params) {
1496
1668
  const { pkg: shellPkg, warnings, info } = buildShellPackageJson({
1497
1669
  original: originalPkg
1498
1670
  });
1499
- await writeJsonAtomic(path9.join(stagingPath, "package.json"), shellPkg);
1500
- for (const w of warnings) console.log(pc6.yellow(`Warning: ${w}`));
1501
- for (const i of info) console.log(pc6.dim(`Info: ${i}`));
1671
+ await writeJsonAtomic(path10.join(stagingPath, "package.json"), shellPkg);
1672
+ for (const w of warnings) console.log(pc7.yellow(`Warning: ${w}`));
1673
+ for (const i of info) console.log(pc7.dim(`Info: ${i}`));
1502
1674
  if (outputIsInsideProject) {
1503
1675
  await fse4.move(stagingPath, outputPath, { overwrite: false });
1504
1676
  movedToFinal = true;
@@ -1506,18 +1678,18 @@ async function shellInit(params) {
1506
1678
  const finalPath = outputIsInsideProject ? outputPath : stagingPath;
1507
1679
  const pm = params.packageManager ?? await detectPackageManager(projectRoot);
1508
1680
  const { cmd, args } = installCommand(pm);
1509
- console.log(pc6.dim(`Installing dependencies with ${cmd}...`));
1681
+ console.log(pc7.dim(`Installing dependencies with ${cmd}...`));
1510
1682
  const res = await execa3(cmd, args, { cwd: finalPath, stdio: "inherit" });
1511
1683
  if (res.exitCode !== 0) {
1512
1684
  throw new CliError("Dependency installation failed.", { exitCode: res.exitCode ?? 1 });
1513
1685
  }
1514
- console.log(pc6.dim("Running Expo prebuild..."));
1686
+ console.log(pc7.dim("Running Expo prebuild..."));
1515
1687
  const prebuild = await execa3("npx", ["expo", "prebuild"], { cwd: finalPath, stdio: "inherit" });
1516
1688
  if (prebuild.exitCode !== 0) {
1517
1689
  throw new CliError("Expo prebuild failed.", { exitCode: prebuild.exitCode ?? 1 });
1518
1690
  }
1519
- console.log(pc6.green("Comerge has been installed successfully."));
1520
- console.log(pc6.dim("You can now open the app from the My apps page in the Comerge app."));
1691
+ console.log(pc7.green("Comerge has been installed successfully."));
1692
+ console.log(pc7.dim("You can now open the app from the My apps page in the Comerge app."));
1521
1693
  } finally {
1522
1694
  if (outputIsInsideProject && !movedToFinal) {
1523
1695
  try {
@@ -1529,7 +1701,7 @@ async function shellInit(params) {
1529
1701
  }
1530
1702
  async function ensureTextFile(filePath, contents) {
1531
1703
  try {
1532
- await fs11.access(filePath);
1704
+ await fs12.access(filePath);
1533
1705
  return;
1534
1706
  } catch {
1535
1707
  }
@@ -1537,8 +1709,8 @@ async function ensureTextFile(filePath, contents) {
1537
1709
  }
1538
1710
 
1539
1711
  // src/utils/appDefaults.ts
1540
- import fs12 from "fs/promises";
1541
- import path10 from "path";
1712
+ import fs13 from "fs/promises";
1713
+ import path11 from "path";
1542
1714
  import fg2 from "fast-glob";
1543
1715
  import { execa as execa4 } from "execa";
1544
1716
  function normalizeString(value) {
@@ -1571,8 +1743,8 @@ async function loadExpoConfig(projectRoot) {
1571
1743
  return parsed;
1572
1744
  } catch {
1573
1745
  }
1574
- const appJsonPath = path10.join(projectRoot, "app.json");
1575
- const rawAppJson = await fs12.readFile(appJsonPath, "utf8").catch(() => null);
1746
+ const appJsonPath = path11.join(projectRoot, "app.json");
1747
+ const rawAppJson = await fs13.readFile(appJsonPath, "utf8").catch(() => null);
1576
1748
  if (!rawAppJson) return {};
1577
1749
  try {
1578
1750
  const parsed = JSON.parse(rawAppJson);
@@ -1590,7 +1762,7 @@ async function findIosBundleId(projectRoot) {
1590
1762
  suppressErrors: true
1591
1763
  });
1592
1764
  for (const file of pbxprojMatches) {
1593
- const raw = await fs12.readFile(file, "utf8").catch(() => null);
1765
+ const raw = await fs13.readFile(file, "utf8").catch(() => null);
1594
1766
  if (!raw) continue;
1595
1767
  const match = raw.match(/PRODUCT_BUNDLE_IDENTIFIER\s*=\s*([A-Za-z0-9.\-]+)\s*;/);
1596
1768
  if (match && match[1]) return match[1];
@@ -1601,7 +1773,7 @@ async function findIosBundleId(projectRoot) {
1601
1773
  suppressErrors: true
1602
1774
  });
1603
1775
  for (const file of plistMatches) {
1604
- const raw = await fs12.readFile(file, "utf8").catch(() => null);
1776
+ const raw = await fs13.readFile(file, "utf8").catch(() => null);
1605
1777
  if (!raw) continue;
1606
1778
  const match = raw.match(/<key>CFBundleIdentifier<\/key>\s*<string>([^<]+)<\/string>/);
1607
1779
  if (match && match[1] && !match[1].includes("$")) return match[1];
@@ -1611,8 +1783,8 @@ async function findIosBundleId(projectRoot) {
1611
1783
  async function findAndroidPackageName(projectRoot) {
1612
1784
  const gradlePaths = ["android/app/build.gradle", "android/app/build.gradle.kts"];
1613
1785
  for (const rel of gradlePaths) {
1614
- const file = path10.join(projectRoot, rel);
1615
- const raw = await fs12.readFile(file, "utf8").catch(() => null);
1786
+ const file = path11.join(projectRoot, rel);
1787
+ const raw = await fs13.readFile(file, "utf8").catch(() => null);
1616
1788
  if (!raw) continue;
1617
1789
  const match = raw.match(/applicationId\s+["']([^"']+)["']/) || raw.match(/applicationId\s*=\s*["']([^"']+)["']/);
1618
1790
  if (match && match[1]) return match[1];
@@ -1623,7 +1795,7 @@ async function findAndroidPackageName(projectRoot) {
1623
1795
  suppressErrors: true
1624
1796
  });
1625
1797
  for (const file of manifestMatches) {
1626
- const raw = await fs12.readFile(file, "utf8").catch(() => null);
1798
+ const raw = await fs13.readFile(file, "utf8").catch(() => null);
1627
1799
  if (!raw) continue;
1628
1800
  const match = raw.match(/<manifest[^>]*\spackage="([^"]+)"/);
1629
1801
  if (match && match[1] && !match[1].includes("$")) return match[1];
@@ -1643,9 +1815,9 @@ async function inferExpoAppDefaults(projectRoot) {
1643
1815
  fallbackName = normalizeString(pkg?.name ?? null);
1644
1816
  }
1645
1817
  if (!fallbackName) {
1646
- fallbackName = normalizeString(path10.basename(projectRoot));
1818
+ fallbackName = normalizeString(path11.basename(projectRoot));
1647
1819
  }
1648
- const resolvedSlug = slug ?? normalizeSlug(fallbackName ?? null) ?? normalizeSlug(path10.basename(projectRoot));
1820
+ const resolvedSlug = slug ?? normalizeSlug(fallbackName ?? null) ?? normalizeSlug(path11.basename(projectRoot));
1649
1821
  const suggestedBundleId = suggestBundleId(resolvedSlug);
1650
1822
  const nativeIosBundleId = iosBundleId ?? await findIosBundleId(projectRoot);
1651
1823
  const nativeAndroidPackage = androidPackageName ?? await findAndroidPackageName(projectRoot);
@@ -1661,27 +1833,27 @@ async function inferExpoAppDefaults(projectRoot) {
1661
1833
  }
1662
1834
 
1663
1835
  // src/services/apiKeyStore.ts
1664
- import fs13 from "fs/promises";
1836
+ import fs14 from "fs/promises";
1665
1837
  import os4 from "os";
1666
- import path11 from "path";
1838
+ import path12 from "path";
1667
1839
  var KEYTAR_SERVICE2 = "comerge-cli-api-keys";
1668
1840
  function xdgConfigHome2() {
1669
1841
  const v = process.env.XDG_CONFIG_HOME;
1670
1842
  if (typeof v === "string" && v.trim().length > 0) return v;
1671
- return path11.join(os4.homedir(), ".config");
1843
+ return path12.join(os4.homedir(), ".config");
1672
1844
  }
1673
1845
  function apiKeyFilePath() {
1674
- return path11.join(xdgConfigHome2(), "comerge", "api-keys.json");
1846
+ return path12.join(xdgConfigHome2(), "comerge", "api-keys.json");
1675
1847
  }
1676
1848
  async function ensurePathPermissions2(filePath) {
1677
- const dir = path11.dirname(filePath);
1678
- await fs13.mkdir(dir, { recursive: true });
1849
+ const dir = path12.dirname(filePath);
1850
+ await fs14.mkdir(dir, { recursive: true });
1679
1851
  try {
1680
- await fs13.chmod(dir, 448);
1852
+ await fs14.chmod(dir, 448);
1681
1853
  } catch {
1682
1854
  }
1683
1855
  try {
1684
- await fs13.chmod(filePath, 384);
1856
+ await fs14.chmod(filePath, 384);
1685
1857
  } catch {
1686
1858
  }
1687
1859
  }
@@ -1704,7 +1876,7 @@ function keyId(orgId, clientAppId) {
1704
1876
  }
1705
1877
  async function readKeyFile() {
1706
1878
  const fp = apiKeyFilePath();
1707
- const raw = await fs13.readFile(fp, "utf8").catch(() => null);
1879
+ const raw = await fs14.readFile(fp, "utf8").catch(() => null);
1708
1880
  if (!raw) return {};
1709
1881
  try {
1710
1882
  const parsed = JSON.parse(raw);
@@ -1744,7 +1916,7 @@ async function setStoredApiKey(orgId, clientAppId, apiKey) {
1744
1916
  }
1745
1917
 
1746
1918
  // src/services/initLocal.ts
1747
- function unwrapResponseObject(resp, label) {
1919
+ function unwrapResponseObject2(resp, label) {
1748
1920
  const obj = resp?.responseObject;
1749
1921
  if (obj === void 0 || obj === null) {
1750
1922
  const message = typeof resp?.message === "string" && resp.message.trim().length > 0 ? resp.message : `Missing ${label} response`;
@@ -1788,7 +1960,7 @@ async function selectClientApp(apps, yes) {
1788
1960
  return { app: null };
1789
1961
  }
1790
1962
  console.log(
1791
- pc7.dim(
1963
+ pc8.dim(
1792
1964
  "No apps found. Create one here or in the Comerge dashboard at dashboard.comerge.ai."
1793
1965
  )
1794
1966
  );
@@ -1914,9 +2086,9 @@ async function initLocal(params) {
1914
2086
  await api.autoEnableDeveloper();
1915
2087
  } catch {
1916
2088
  }
1917
- const orgs = unwrapResponseObject(await api.listOrganizations(), "organizations");
2089
+ const orgs = unwrapResponseObject2(await api.listOrganizations(), "organizations");
1918
2090
  const selectedOrg = await selectOrganization(orgs, params.yes);
1919
- const apps = unwrapResponseObject(
2091
+ const apps = unwrapResponseObject2(
1920
2092
  await api.listClientApps({ orgId: selectedOrg.id }),
1921
2093
  "client apps"
1922
2094
  );
@@ -1935,7 +2107,7 @@ async function initLocal(params) {
1935
2107
  iosBundleId: details.iosEnabled ? details.iosBundleId ?? void 0 : void 0,
1936
2108
  androidPackageName: details.androidEnabled ? details.androidPackageName ?? void 0 : void 0
1937
2109
  });
1938
- clientApp = unwrapResponseObject(createdResp, "client app");
2110
+ clientApp = unwrapResponseObject2(createdResp, "client app");
1939
2111
  importAppName = details.name;
1940
2112
  }
1941
2113
  const clientAppId = String(clientApp.id || "").trim();
@@ -1944,17 +2116,17 @@ async function initLocal(params) {
1944
2116
  }
1945
2117
  let apiKey = await getStoredApiKey(selectedOrg.id, clientAppId);
1946
2118
  if (!apiKey) {
1947
- const me = unwrapResponseObject(await api.getMe(), "user");
2119
+ const me = unwrapResponseObject2(await api.getMe(), "user");
1948
2120
  const keyName = buildKeyName({ email: me.email ?? null, id: me.id ?? null });
1949
2121
  const createdKeyResp = await api.createClientAppKey(clientAppId, { name: keyName });
1950
- const keyPayload = unwrapResponseObject(createdKeyResp, "client key");
2122
+ const keyPayload = unwrapResponseObject2(createdKeyResp, "client key");
1951
2123
  apiKey = String(keyPayload.key || "").trim();
1952
2124
  if (!apiKey) {
1953
2125
  throw new CliError("Server did not return a client key.", { exitCode: 1 });
1954
2126
  }
1955
2127
  await setStoredApiKey(selectedOrg.id, clientAppId, apiKey);
1956
2128
  }
1957
- if (!params.json) console.log(pc7.dim("Importing project..."));
2129
+ if (!params.json) console.log(pc8.dim("Importing project..."));
1958
2130
  const imported = await importLocal({
1959
2131
  apiKey,
1960
2132
  yes: params.yes,
@@ -1964,7 +2136,7 @@ async function initLocal(params) {
1964
2136
  includeDotenv: params.includeDotenv,
1965
2137
  dryRun: false
1966
2138
  });
1967
- if (!params.json) console.log(pc7.dim("Generating shell app..."));
2139
+ if (!params.json) console.log(pc8.dim("Generating shell app..."));
1968
2140
  await shellInit({
1969
2141
  outDir: params.outDir,
1970
2142
  appId: imported.appId,
@@ -1999,8 +2171,8 @@ function registerInitCommands(program) {
1999
2171
  console.log(JSON.stringify(res, null, 2));
2000
2172
  return;
2001
2173
  }
2002
- console.log(pc8.green(`Completed. appId=${res.appId}`));
2003
- if (res.status) console.log(pc8.dim(`Status: ${res.status}`));
2174
+ console.log(pc9.green(`Completed. appId=${res.appId}`));
2175
+ if (res.status) console.log(pc9.dim(`Status: ${res.status}`));
2004
2176
  });
2005
2177
  }
2006
2178
 
@@ -2015,19 +2187,19 @@ async function main(argv) {
2015
2187
  registerLogoutCommand(program);
2016
2188
  registerInitCommands(program);
2017
2189
  program.configureOutput({
2018
- outputError: (str, write) => write(pc9.red(str))
2190
+ outputError: (str, write) => write(pc10.red(str))
2019
2191
  });
2020
2192
  try {
2021
2193
  await program.parseAsync(argv);
2022
2194
  } catch (err) {
2023
2195
  const e = err;
2024
2196
  if (e instanceof CliError) {
2025
- console.error(pc9.red(`Error: ${e.message}`));
2026
- if (e.hint) console.error(pc9.dim(e.hint));
2197
+ console.error(pc10.red(`Error: ${e.message}`));
2198
+ if (e.hint) console.error(pc10.dim(e.hint));
2027
2199
  process.exitCode = e.exitCode;
2028
2200
  return;
2029
2201
  }
2030
- console.error(pc9.red(`Unexpected error: ${e?.message ?? String(e)}`));
2202
+ console.error(pc10.red(`Unexpected error: ${e?.message ?? String(e)}`));
2031
2203
  process.exitCode = 1;
2032
2204
  }
2033
2205
  }