@insforge/cli 0.1.82 → 0.1.85

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 CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { readFileSync as readFileSync12 } from "fs";
5
- import { join as join14, dirname as dirname2 } from "path";
4
+ import { readFileSync as readFileSync13 } from "fs";
5
+ import { join as join15, dirname as dirname2 } from "path";
6
6
  import { fileURLToPath } from "url";
7
7
  import { Command } from "commander";
8
- import * as clack18 from "@clack/prompts";
8
+ import * as clack19 from "@clack/prompts";
9
9
 
10
10
  // src/lib/prompts.ts
11
11
  import * as readline from "readline";
@@ -1172,7 +1172,7 @@ import * as clack5 from "@clack/prompts";
1172
1172
 
1173
1173
  // src/lib/analytics.ts
1174
1174
  import { PostHog } from "posthog-node";
1175
- var POSTHOG_API_KEY = "phc_ueV1ii62wdBTkH7E70ugyeqHIHu8dFDdjs0qq3TZhJz";
1175
+ var POSTHOG_API_KEY = "";
1176
1176
  var POSTHOG_HOST = process.env.POSTHOG_HOST || "https://us.i.posthog.com";
1177
1177
  var client = null;
1178
1178
  function getClient() {
@@ -1215,6 +1215,28 @@ function trackPayments(subcommand, config, properties) {
1215
1215
  ...properties
1216
1216
  });
1217
1217
  }
1218
+ function trackDeployments(subcommand, config, properties) {
1219
+ captureEvent(config.project_id, "cli_deployments_invoked", {
1220
+ subcommand,
1221
+ project_id: config.project_id,
1222
+ project_name: config.project_name,
1223
+ org_id: config.org_id,
1224
+ region: config.region,
1225
+ oss_mode: config.project_id === FAKE_PROJECT_ID,
1226
+ ...properties
1227
+ });
1228
+ }
1229
+ function trackPosthog(subcommand, config, properties) {
1230
+ captureEvent(config.project_id, "cli_posthog_invoked", {
1231
+ subcommand,
1232
+ project_id: config.project_id,
1233
+ project_name: config.project_name,
1234
+ org_id: config.org_id,
1235
+ region: config.region,
1236
+ oss_mode: config.project_id === FAKE_PROJECT_ID,
1237
+ ...properties
1238
+ });
1239
+ }
1218
1240
  function trackConfig(subcommand, config, properties) {
1219
1241
  const distinctId = config?.project_id ?? FAKE_PROJECT_ID;
1220
1242
  captureEvent(distinctId, "cli_config_invoked", {
@@ -1707,15 +1729,91 @@ import { exec as exec3 } from "child_process";
1707
1729
  import { promisify as promisify4 } from "util";
1708
1730
  import * as fs5 from "fs/promises";
1709
1731
  import * as path5 from "path";
1710
- import * as clack13 from "@clack/prompts";
1732
+ import * as clack14 from "@clack/prompts";
1711
1733
  import pc2 from "picocolors";
1712
1734
 
1713
1735
  // src/lib/skills.ts
1714
1736
  import { exec } from "child_process";
1715
- import { existsSync as existsSync3, readFileSync as readFileSync2, appendFileSync } from "fs";
1716
- import { join as join2 } from "path";
1737
+ import { existsSync as existsSync4, readFileSync as readFileSync3, appendFileSync } from "fs";
1738
+ import { join as join3 } from "path";
1717
1739
  import { promisify } from "util";
1740
+ import * as clack10 from "@clack/prompts";
1741
+
1742
+ // src/lib/agents-md.ts
1743
+ import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "fs";
1744
+ import { join as join2 } from "path";
1718
1745
  import * as clack9 from "@clack/prompts";
1746
+ var AGENTS_MD_START = "<!-- INSFORGE:START -->";
1747
+ var AGENTS_MD_END = "<!-- INSFORGE:END -->";
1748
+ function buildInsforgeBlock(config) {
1749
+ const lines = [
1750
+ AGENTS_MD_START,
1751
+ "## InsForge backend",
1752
+ "",
1753
+ "This project uses [InsForge](https://insforge.dev): an all-in-one, open-source Postgres-based backend (BaaS) that gives this app a database, authentication, file storage, edge functions, realtime, an AI model gateway, and payments through one platform.",
1754
+ ""
1755
+ ];
1756
+ if (config?.project_name || config?.oss_host) {
1757
+ const name = config.project_name ? `**${config.project_name}**` : "This project";
1758
+ const host = config.oss_host ? ` (API base \`${config.oss_host}\`)` : "";
1759
+ lines.push(`- **Project:** ${name}${host}`);
1760
+ }
1761
+ lines.push(
1762
+ "- **Skills:** these InsForge skills are installed for supported coding agents. Reach for them before implementing any InsForge feature instead of guessing the API:",
1763
+ " - `insforge`: app code with the `@insforge/sdk` client (database CRUD, auth, storage, edge functions, realtime, AI, email, and Stripe payments).",
1764
+ " - `insforge-cli`: backend and infrastructure via the `insforge` CLI (projects, SQL, migrations, RLS policies, storage buckets, functions, secrets, payment setup, schedules, deploys).",
1765
+ " - `insforge-debug`: diagnosing failures (SDK/HTTP errors, RLS denials, auth and OAuth issues) and running security or performance audits.",
1766
+ " - `insforge-integrations`: wiring external auth providers (Clerk, Auth0, WorkOS, Better Auth, etc.) for JWT-based RLS, or the OKX x402 payment facilitator.",
1767
+ " - `find-skills`: discovering additional skills on demand.",
1768
+ "- **Credentials:** app code reads keys from `.env.local`; the CLI reads `.insforge/project.json`. Never hardcode or commit keys.",
1769
+ "",
1770
+ "Key patterns:",
1771
+ "",
1772
+ "- Database inserts take an array: `insert([{ ... }])`.",
1773
+ "- Reference users with `auth.users(id)`; use `auth.uid()` in RLS policies.",
1774
+ "- For storage uploads, persist both the returned `url` and `key`.",
1775
+ AGENTS_MD_END
1776
+ );
1777
+ return lines.join("\n");
1778
+ }
1779
+ function mergeAgentsMd(existing, config) {
1780
+ const block = buildInsforgeBlock(config);
1781
+ if (existing === null || existing.trim() === "") {
1782
+ return `# AGENTS.md
1783
+
1784
+ ${block}
1785
+ `;
1786
+ }
1787
+ const startIdx = existing.indexOf(AGENTS_MD_START);
1788
+ if (startIdx !== -1) {
1789
+ const endMarkerIdx = existing.indexOf(AGENTS_MD_END, startIdx + AGENTS_MD_START.length);
1790
+ let before = existing.slice(0, startIdx);
1791
+ if (before.length > 0 && !before.endsWith("\n")) before += "\n";
1792
+ const after = endMarkerIdx === -1 ? "\n" : existing.slice(endMarkerIdx + AGENTS_MD_END.length);
1793
+ return `${before}${block}${after}`;
1794
+ }
1795
+ return `${existing.replace(/\s+$/, "")}
1796
+
1797
+ ${block}
1798
+ `;
1799
+ }
1800
+ function writeLocalAgentsMd(json, opts) {
1801
+ const cwd = opts?.cwd ?? process.cwd();
1802
+ const config = opts?.config !== void 0 ? opts.config : getProjectConfig();
1803
+ const path6 = join2(cwd, "AGENTS.md");
1804
+ const existed = existsSync3(path6);
1805
+ const existing = existed ? readFileSync2(path6, "utf-8") : null;
1806
+ const next = mergeAgentsMd(existing, config);
1807
+ if (existing === next) return;
1808
+ writeFileSync3(path6, next);
1809
+ if (!json) {
1810
+ clack9.log.success(
1811
+ existed ? "Updated AGENTS.md with InsForge guidance." : "Created AGENTS.md with InsForge guidance."
1812
+ );
1813
+ }
1814
+ }
1815
+
1816
+ // src/lib/skills.ts
1719
1817
  var execAsync = promisify(exec);
1720
1818
  var SKILL_INSTALL_TIMEOUT_MS = 6e4;
1721
1819
  function describeExecError(err) {
@@ -1755,8 +1853,8 @@ var GITIGNORE_ENTRIES = [
1755
1853
  ".windsurf"
1756
1854
  ];
1757
1855
  function updateGitignore() {
1758
- const gitignorePath = join2(process.cwd(), ".gitignore");
1759
- const existing = existsSync3(gitignorePath) ? readFileSync2(gitignorePath, "utf-8") : "";
1856
+ const gitignorePath = join3(process.cwd(), ".gitignore");
1857
+ const existing = existsSync4(gitignorePath) ? readFileSync3(gitignorePath, "utf-8") : "";
1760
1858
  const lines = new Set(existing.split("\n").map((l) => l.trim()));
1761
1859
  const missing = GITIGNORE_ENTRIES.filter((entry) => !lines.has(entry));
1762
1860
  if (!missing.length) return;
@@ -1772,44 +1870,44 @@ var PROVIDER_SKILLS = {
1772
1870
  };
1773
1871
  async function installSkills(json, authProvider) {
1774
1872
  try {
1775
- if (!json) clack9.log.info("Installing InsForge agent skills (global)...");
1873
+ if (!json) clack10.log.info("Installing InsForge agent skills (global)...");
1776
1874
  await execAsync(`npx skills add insforge/agent-skills -g -y ${AGENT_FLAGS}`, {
1777
1875
  cwd: process.cwd(),
1778
1876
  timeout: SKILL_INSTALL_TIMEOUT_MS
1779
1877
  });
1780
- if (!json) clack9.log.success("InsForge agent skills installed.");
1878
+ if (!json) clack10.log.success("InsForge agent skills installed.");
1781
1879
  } catch (err) {
1782
1880
  if (!json) {
1783
- clack9.log.warn(`Could not install agent skills: ${describeExecError(err)}`);
1784
- clack9.log.info("Run `npx skills add insforge/agent-skills` once resolved to see the full output.");
1881
+ clack10.log.warn(`Could not install agent skills: ${describeExecError(err)}`);
1882
+ clack10.log.info("Run `npx skills add insforge/agent-skills` once resolved to see the full output.");
1785
1883
  }
1786
1884
  }
1787
1885
  try {
1788
- if (!json) clack9.log.info("Installing find-skills (global)...");
1886
+ if (!json) clack10.log.info("Installing find-skills (global)...");
1789
1887
  await execAsync("npx skills add https://github.com/vercel-labs/skills --skill find-skills -g -y", {
1790
1888
  cwd: process.cwd(),
1791
1889
  timeout: SKILL_INSTALL_TIMEOUT_MS
1792
1890
  });
1793
- if (!json) clack9.log.success("find-skills installed.");
1891
+ if (!json) clack10.log.success("find-skills installed.");
1794
1892
  } catch (err) {
1795
1893
  if (!json) {
1796
- clack9.log.warn(`Could not install find-skills: ${describeExecError(err)}`);
1797
- clack9.log.info("Run `npx skills add https://github.com/vercel-labs/skills --skill find-skills` once resolved.");
1894
+ clack10.log.warn(`Could not install find-skills: ${describeExecError(err)}`);
1895
+ clack10.log.info("Run `npx skills add https://github.com/vercel-labs/skills --skill find-skills` once resolved.");
1798
1896
  }
1799
1897
  }
1800
1898
  const providerEntry = authProvider ? PROVIDER_SKILLS[authProvider] : void 0;
1801
1899
  if (providerEntry) {
1802
1900
  try {
1803
- if (!json) clack9.log.info(`Installing ${providerEntry.label} (global)...`);
1901
+ if (!json) clack10.log.info(`Installing ${providerEntry.label} (global)...`);
1804
1902
  await execAsync(`npx skills add ${providerEntry.repo} -g -y ${AGENT_FLAGS}`, {
1805
1903
  cwd: process.cwd(),
1806
1904
  timeout: SKILL_INSTALL_TIMEOUT_MS
1807
1905
  });
1808
- if (!json) clack9.log.success(`${providerEntry.label} installed.`);
1906
+ if (!json) clack10.log.success(`${providerEntry.label} installed.`);
1809
1907
  } catch (err) {
1810
1908
  if (!json) {
1811
- clack9.log.warn(`Could not install ${providerEntry.label}: ${describeExecError(err)}`);
1812
- clack9.log.info(`Run \`npx skills add ${providerEntry.repo}\` once resolved to see the full output.`);
1909
+ clack10.log.warn(`Could not install ${providerEntry.label}: ${describeExecError(err)}`);
1910
+ clack10.log.info(`Run \`npx skills add ${providerEntry.repo}\` once resolved to see the full output.`);
1813
1911
  }
1814
1912
  }
1815
1913
  }
@@ -1817,6 +1915,10 @@ async function installSkills(json, authProvider) {
1817
1915
  updateGitignore();
1818
1916
  } catch {
1819
1917
  }
1918
+ try {
1919
+ writeLocalAgentsMd(json);
1920
+ } catch {
1921
+ }
1820
1922
  }
1821
1923
  async function reportCliUsage(toolName, success, maxRetries = 1, explicitConfig) {
1822
1924
  let config = explicitConfig;
@@ -1866,7 +1968,7 @@ import { tmpdir } from "os";
1866
1968
  import { execFile } from "child_process";
1867
1969
  import { promisify as promisify2 } from "util";
1868
1970
  import { randomBytes as randomBytes2 } from "crypto";
1869
- import * as clack10 from "@clack/prompts";
1971
+ import * as clack11 from "@clack/prompts";
1870
1972
 
1871
1973
  // src/lib/api/oss.ts
1872
1974
  function requireProjectConfig() {
@@ -2107,7 +2209,7 @@ async function applyAuthProvider(provider, cwd, projectConfig, json) {
2107
2209
  if (!VALID_AUTH_PROVIDERS.includes(provider)) {
2108
2210
  throw new Error(`Unknown auth provider: ${provider}`);
2109
2211
  }
2110
- const fetchSpinner = !json ? clack10.spinner() : null;
2212
+ const fetchSpinner = !json ? clack11.spinner() : null;
2111
2213
  fetchSpinner?.start(`Fetching ${provider} scaffold from templates repo...`);
2112
2214
  const { dir: providerDir, cleanup } = await fetchProviderTree(provider);
2113
2215
  fetchSpinner?.stop(`${provider} scaffold ready`);
@@ -2206,15 +2308,15 @@ async function applyAuthProvider(provider, cwd, projectConfig, json) {
2206
2308
  result.envKeysRefreshed = Array.from(/* @__PURE__ */ new Set([...result.envKeysRefreshed, ...refreshed]));
2207
2309
  }
2208
2310
  if (!jwtSecret && !json) {
2209
- clack10.log.warn("Could not auto-fill JWT_SECRET \u2014 run `npx @insforge/cli secrets get JWT_SECRET` and paste it into .env.local.");
2311
+ clack11.log.warn("Could not auto-fill JWT_SECRET \u2014 run `npx @insforge/cli secrets get JWT_SECRET` and paste it into .env.local.");
2210
2312
  }
2211
2313
  if (result.envKeysSkipped.length > 0 && !json) {
2212
- clack10.log.warn(
2314
+ clack11.log.warn(
2213
2315
  `Kept your existing values for: ${result.envKeysSkipped.join(", ")}. If any of these need the auth-provider's defaults, see .env.example for reference.`
2214
2316
  );
2215
2317
  }
2216
2318
  if (result.envKeysRefreshed.length > 0 && !json) {
2217
- clack10.log.info(
2319
+ clack11.log.info(
2218
2320
  `Refreshed stale platform defaults: ${result.envKeysRefreshed.join(", ")}. Your value matched the manifest's default, so we replaced it with the live one.`
2219
2321
  );
2220
2322
  }
@@ -2230,7 +2332,7 @@ import { tmpdir as tmpdir2 } from "os";
2230
2332
  import { promisify as promisify3 } from "util";
2231
2333
  import * as fs4 from "fs/promises";
2232
2334
  import * as path4 from "path";
2233
- import * as clack12 from "@clack/prompts";
2335
+ import * as clack13 from "@clack/prompts";
2234
2336
 
2235
2337
  // src/lib/env.ts
2236
2338
  import * as fs2 from "fs/promises";
@@ -2265,8 +2367,26 @@ import * as path3 from "path";
2265
2367
  import * as fs3 from "fs/promises";
2266
2368
  import { createReadStream } from "fs";
2267
2369
  import { createHash as createHash2 } from "crypto";
2268
- import * as clack11 from "@clack/prompts";
2370
+ import * as clack12 from "@clack/prompts";
2269
2371
  import archiver from "archiver";
2372
+
2373
+ // src/commands/deployments/utils.ts
2374
+ async function trackDeploymentUsage(subcommand, success, properties = {}) {
2375
+ try {
2376
+ const config = getProjectConfig();
2377
+ if (config) {
2378
+ trackDeployments(subcommand, config, {
2379
+ success,
2380
+ ...properties
2381
+ });
2382
+ }
2383
+ } catch {
2384
+ } finally {
2385
+ await shutdownAnalytics();
2386
+ }
2387
+ }
2388
+
2389
+ // src/commands/deployments/deploy.ts
2270
2390
  var POLL_INTERVAL_MS3 = 5e3;
2271
2391
  var POLL_TIMEOUT_MS3 = 3e5;
2272
2392
  var DIRECT_UPLOAD_CONCURRENCY = 8;
@@ -2565,7 +2685,7 @@ function registerDeploymentsDeployCommand(deploymentsCmd2) {
2565
2685
  `"${dirName}" is an excluded directory and cannot be used as a deploy source. Please specify your project root or output directory instead.`
2566
2686
  );
2567
2687
  }
2568
- const spinner11 = !json ? clack11.spinner() : null;
2688
+ const spinner11 = !json ? clack12.spinner() : null;
2569
2689
  const startBody = {};
2570
2690
  if (opts.env) {
2571
2691
  try {
@@ -2598,9 +2718,9 @@ function registerDeploymentsDeployCommand(deploymentsCmd2) {
2598
2718
  outputJson(result.deployment);
2599
2719
  } else {
2600
2720
  if (result.liveUrl) {
2601
- clack11.log.success(`Live at: ${result.liveUrl}`);
2721
+ clack12.log.success(`Live at: ${result.liveUrl}`);
2602
2722
  }
2603
- clack11.log.info(`Deployment ID: ${result.deploymentId}`);
2723
+ clack12.log.info(`Deployment ID: ${result.deploymentId}`);
2604
2724
  }
2605
2725
  } else {
2606
2726
  spinner11?.stop("Deployment is still building");
@@ -2611,14 +2731,21 @@ function registerDeploymentsDeployCommand(deploymentsCmd2) {
2611
2731
  timedOut: true
2612
2732
  });
2613
2733
  } else {
2614
- clack11.log.info(`Deployment ID: ${result.deploymentId}`);
2615
- clack11.log.warn("Deployment did not finish within 5 minutes.");
2616
- clack11.log.info(`Check status with: npx @insforge/cli deployments status ${result.deploymentId}`);
2734
+ clack12.log.info(`Deployment ID: ${result.deploymentId}`);
2735
+ clack12.log.warn("Deployment did not finish within 5 minutes.");
2736
+ clack12.log.info(`Check status with: npx @insforge/cli deployments status ${result.deploymentId}`);
2617
2737
  }
2618
2738
  }
2619
- await reportCliUsage("cli.deployments.deploy", true);
2739
+ await trackDeploymentUsage("deploy", true, {
2740
+ has_env: opts.env !== void 0,
2741
+ has_meta: opts.meta !== void 0,
2742
+ ready: result.isReady
2743
+ });
2620
2744
  } catch (err) {
2621
- await reportCliUsage("cli.deployments.deploy", false);
2745
+ await trackDeploymentUsage("deploy", false, {
2746
+ has_env: opts.env !== void 0,
2747
+ has_meta: opts.meta !== void 0
2748
+ });
2622
2749
  handleError(err, json);
2623
2750
  }
2624
2751
  });
@@ -2753,7 +2880,7 @@ Browse available templates: https://insforge.dev/templates`
2753
2880
  await requireAuth(apiUrl, false);
2754
2881
  if (!json) {
2755
2882
  await animateBanner();
2756
- clack12.intro("Let's build something great");
2883
+ clack13.intro("Let's build something great");
2757
2884
  }
2758
2885
  let orgId = opts.orgId;
2759
2886
  if (!orgId) {
@@ -2763,7 +2890,7 @@ Browse available templates: https://insforge.dev/templates`
2763
2890
  }
2764
2891
  if (orgs.length === 1) {
2765
2892
  orgId = orgs[0].id;
2766
- if (!json) clack12.log.info(`Using organization: ${orgs[0].name}`);
2893
+ if (!json) clack13.log.info(`Using organization: ${orgs[0].name}`);
2767
2894
  } else {
2768
2895
  if (json) {
2769
2896
  throw new CLIError("Multiple organizations found. Specify --org-id.");
@@ -2879,7 +3006,7 @@ Browse available templates: https://insforge.dev/templates`
2879
3006
  process.chdir(projectDir);
2880
3007
  }
2881
3008
  let projectLinked = false;
2882
- const s = !json ? clack12.spinner() : null;
3009
+ const s = !json ? clack13.spinner() : null;
2883
3010
  try {
2884
3011
  s?.start("Creating project...");
2885
3012
  const project = await createProject(orgId, projectName, opts.region, apiUrl);
@@ -2906,10 +3033,7 @@ Browse available templates: https://insforge.dev/templates`
2906
3033
  json
2907
3034
  );
2908
3035
  if (downloaded) {
2909
- void reportMarketplaceDownload(
2910
- opts.marketplace,
2911
- apiUrl ?? "https://api.insforge.dev"
2912
- );
3036
+ void reportMarketplaceDownload(opts.marketplace);
2913
3037
  }
2914
3038
  } else if (githubTemplates.includes(template)) {
2915
3039
  await downloadGitHubTemplate(template, projectConfig, json);
@@ -2919,7 +3043,7 @@ Browse available templates: https://insforge.dev/templates`
2919
3043
  try {
2920
3044
  const anonKey = await getAnonKey();
2921
3045
  if (!anonKey) {
2922
- if (!json) clack12.log.warn("Could not retrieve anon key. You can add it to .env.local manually.");
3046
+ if (!json) clack13.log.warn("Could not retrieve anon key. You can add it to .env.local manually.");
2923
3047
  } else {
2924
3048
  const envPath = path4.join(process.cwd(), ".env.local");
2925
3049
  const envContent = [
@@ -2930,16 +3054,16 @@ Browse available templates: https://insforge.dev/templates`
2930
3054
  ].join("\n");
2931
3055
  await fs4.writeFile(envPath, envContent, { flag: "wx" });
2932
3056
  if (!json) {
2933
- clack12.log.success("Created .env.local with your InsForge credentials");
3057
+ clack13.log.success("Created .env.local with your InsForge credentials");
2934
3058
  }
2935
3059
  }
2936
3060
  } catch (err) {
2937
3061
  const error = err;
2938
3062
  if (!json) {
2939
3063
  if (error.code === "EEXIST") {
2940
- clack12.log.warn(".env.local already exists; skipping InsForge key seeding.");
3064
+ clack13.log.warn(".env.local already exists; skipping InsForge key seeding.");
2941
3065
  } else {
2942
- clack12.log.warn(`Failed to create .env.local: ${error.message}`);
3066
+ clack13.log.warn(`Failed to create .env.local: ${error.message}`);
2943
3067
  }
2944
3068
  }
2945
3069
  }
@@ -2948,12 +3072,12 @@ Browse available templates: https://insforge.dev/templates`
2948
3072
  try {
2949
3073
  const result = await applyAuthProvider(opts.auth, process.cwd(), projectConfig, json);
2950
3074
  if (!json) {
2951
- clack12.log.success(`Wired in ${opts.auth}: ${result.written.length} new, ${result.overwritten.length} replaced`);
3075
+ clack13.log.success(`Wired in ${opts.auth}: ${result.written.length} new, ${result.overwritten.length} replaced`);
2952
3076
  }
2953
3077
  } catch (err) {
2954
3078
  const msg = `Failed to apply --auth ${opts.auth}: ${err.message}`;
2955
3079
  if (json) console.error(JSON.stringify({ warning: msg }));
2956
- else clack12.log.warn(msg);
3080
+ else clack13.log.warn(msg);
2957
3081
  }
2958
3082
  }
2959
3083
  await installSkills(json, opts.auth);
@@ -2961,7 +3085,7 @@ Browse available templates: https://insforge.dev/templates`
2961
3085
  await reportCliUsage("cli.create", true, 6);
2962
3086
  const templateDownloaded = hasTemplate ? await fs4.stat(path4.join(process.cwd(), "package.json")).catch(() => null) : null;
2963
3087
  if (templateDownloaded) {
2964
- const installSpinner = !json ? clack12.spinner() : null;
3088
+ const installSpinner = !json ? clack13.spinner() : null;
2965
3089
  installSpinner?.start("Installing dependencies...");
2966
3090
  try {
2967
3091
  await execAsync2("npm install", { cwd: process.cwd(), maxBuffer: 10 * 1024 * 1024 });
@@ -2969,8 +3093,8 @@ Browse available templates: https://insforge.dev/templates`
2969
3093
  } catch (err) {
2970
3094
  installSpinner?.stop("Failed to install dependencies");
2971
3095
  if (!json) {
2972
- clack12.log.warn(`npm install failed: ${err.message}`);
2973
- clack12.log.info("Run `npm install` manually to install dependencies.");
3096
+ clack13.log.warn(`npm install failed: ${err.message}`);
3097
+ clack13.log.info("Run `npm install` manually to install dependencies.");
2974
3098
  }
2975
3099
  }
2976
3100
  }
@@ -2986,7 +3110,7 @@ Browse available templates: https://insforge.dev/templates`
2986
3110
  if (envVars.length > 0) {
2987
3111
  startBody.envVars = envVars;
2988
3112
  }
2989
- const deploySpinner = clack12.spinner();
3113
+ const deploySpinner = clack13.spinner();
2990
3114
  const result = await deployProject({
2991
3115
  sourceDir: process.cwd(),
2992
3116
  startBody,
@@ -2997,12 +3121,12 @@ Browse available templates: https://insforge.dev/templates`
2997
3121
  liveUrl = result.liveUrl;
2998
3122
  } else {
2999
3123
  deploySpinner.stop("Deployment is still building");
3000
- clack12.log.info(`Deployment ID: ${result.deploymentId}`);
3001
- clack12.log.warn("Deployment did not finish within 2 minutes.");
3002
- clack12.log.info(`Check status with: npx @insforge/cli deployments status ${result.deploymentId}`);
3124
+ clack13.log.info(`Deployment ID: ${result.deploymentId}`);
3125
+ clack13.log.warn("Deployment did not finish within 2 minutes.");
3126
+ clack13.log.info(`Check status with: npx @insforge/cli deployments status ${result.deploymentId}`);
3003
3127
  }
3004
3128
  } catch (err) {
3005
- clack12.log.warn(`Deploy failed: ${err.message}`);
3129
+ clack13.log.warn(`Deploy failed: ${err.message}`);
3006
3130
  }
3007
3131
  }
3008
3132
  }
@@ -3019,33 +3143,33 @@ Browse available templates: https://insforge.dev/templates`
3019
3143
  }
3020
3144
  });
3021
3145
  } else {
3022
- clack12.log.step(`Dashboard: ${dashboardUrl}`);
3146
+ clack13.log.step(`Dashboard: ${dashboardUrl}`);
3023
3147
  if (liveUrl) {
3024
- clack12.log.success(`Live site: ${liveUrl}`);
3148
+ clack13.log.success(`Live site: ${liveUrl}`);
3025
3149
  }
3026
3150
  if (templateDownloaded) {
3027
3151
  const steps = [
3028
3152
  `cd ${dirName}`,
3029
3153
  "npm run dev"
3030
3154
  ];
3031
- clack12.note(steps.join("\n"), "Next steps");
3032
- clack12.note("Open your coding agent (Claude Code, Codex, Cursor, etc.) to add new features.", "Keep building");
3155
+ clack13.note(steps.join("\n"), "Next steps");
3156
+ clack13.note("Open your coding agent (Claude Code, Codex, Cursor, etc.) to add new features.", "Keep building");
3033
3157
  } else if (hasTemplate && !templateDownloaded) {
3034
- clack12.log.warn("Template download failed. You can retry or set up manually.");
3158
+ clack13.log.warn("Template download failed. You can retry or set up manually.");
3035
3159
  } else {
3036
3160
  const prompts = [
3037
3161
  "Build a todo app with Google OAuth sign-in",
3038
3162
  "Build an Instagram clone where users can upload photos, like, and comment",
3039
3163
  "Build an AI chatbot with conversation history"
3040
3164
  ];
3041
- clack12.note(
3165
+ clack13.note(
3042
3166
  `Open your coding agent (Claude Code, Codex, Cursor, etc.) and try:
3043
3167
 
3044
3168
  ${prompts.map((p3) => `\u2022 "${p3}"`).join("\n")}`,
3045
3169
  "Start building"
3046
3170
  );
3047
3171
  }
3048
- clack12.outro("Done!");
3172
+ clack13.outro("Done!");
3049
3173
  }
3050
3174
  } catch (err) {
3051
3175
  if (!projectLinked && hasTemplate && projectDir !== originalCwd) {
@@ -3064,7 +3188,7 @@ ${prompts.map((p3) => `\u2022 "${p3}"`).join("\n")}`,
3064
3188
  });
3065
3189
  }
3066
3190
  async function downloadTemplate(framework, projectConfig, projectName, json, _apiUrl) {
3067
- const s = !json ? clack12.spinner() : null;
3191
+ const s = !json ? clack13.spinner() : null;
3068
3192
  s?.start("Downloading template...");
3069
3193
  try {
3070
3194
  const anonKey = await getAnonKey();
@@ -3095,13 +3219,13 @@ async function downloadTemplate(framework, projectConfig, projectName, json, _ap
3095
3219
  } catch (err) {
3096
3220
  s?.stop("Template download failed");
3097
3221
  if (!json) {
3098
- clack12.log.warn(`Failed to download template: ${err.message}`);
3099
- clack12.log.info("You can manually set up the template later.");
3222
+ clack13.log.warn(`Failed to download template: ${err.message}`);
3223
+ clack13.log.info("You can manually set up the template later.");
3100
3224
  }
3101
3225
  }
3102
3226
  }
3103
3227
  async function downloadGitHubTemplate(templateName, projectConfig, json) {
3104
- const s = !json ? clack12.spinner() : null;
3228
+ const s = !json ? clack13.spinner() : null;
3105
3229
  s?.start(`Downloading ${templateName} template...`);
3106
3230
  const tempDir = path4.join(tmpdir2(), `insforge-template-${Date.now()}`);
3107
3231
  try {
@@ -3150,7 +3274,7 @@ async function downloadGitHubTemplate(templateName, projectConfig, json) {
3150
3274
  await fs4.writeFile(envLocalPath, envFinal, { flag: "wx" });
3151
3275
  } catch (e) {
3152
3276
  if (e.code === "EEXIST") {
3153
- if (!json) clack12.log.warn(".env.local already exists; skipping env seeding.");
3277
+ if (!json) clack13.log.warn(".env.local already exists; skipping env seeding.");
3154
3278
  } else {
3155
3279
  throw e;
3156
3280
  }
@@ -3160,7 +3284,7 @@ async function downloadGitHubTemplate(templateName, projectConfig, json) {
3160
3284
  const migrationPath = path4.join(cwd, "migrations", "db_init.sql");
3161
3285
  const migrationExists = await fs4.stat(migrationPath).catch(() => null);
3162
3286
  if (migrationExists) {
3163
- const dbSpinner = !json ? clack12.spinner() : null;
3287
+ const dbSpinner = !json ? clack13.spinner() : null;
3164
3288
  dbSpinner?.start("Running database migrations...");
3165
3289
  try {
3166
3290
  const sql = await fs4.readFile(migrationPath, "utf-8");
@@ -3169,8 +3293,8 @@ async function downloadGitHubTemplate(templateName, projectConfig, json) {
3169
3293
  } catch (err) {
3170
3294
  dbSpinner?.stop("Database migration failed");
3171
3295
  if (!json) {
3172
- clack12.log.warn(`Migration failed: ${err.message}`);
3173
- clack12.log.info('You can run the migration manually: npx @insforge/cli db query --unrestricted "$(cat migrations/db_init.sql)"');
3296
+ clack13.log.warn(`Migration failed: ${err.message}`);
3297
+ clack13.log.info('You can run the migration manually: npx @insforge/cli db query --unrestricted "$(cat migrations/db_init.sql)"');
3174
3298
  } else {
3175
3299
  throw err;
3176
3300
  }
@@ -3183,8 +3307,8 @@ async function downloadGitHubTemplate(templateName, projectConfig, json) {
3183
3307
  if (json) {
3184
3308
  console.error(JSON.stringify({ warning: msg }));
3185
3309
  } else {
3186
- clack12.log.warn(msg);
3187
- clack12.log.info("You can manually clone from: https://github.com/InsForge/insforge-templates");
3310
+ clack13.log.warn(msg);
3311
+ clack13.log.info("You can manually clone from: https://github.com/InsForge/insforge-templates");
3188
3312
  }
3189
3313
  return false;
3190
3314
  } finally {
@@ -3192,12 +3316,14 @@ async function downloadGitHubTemplate(templateName, projectConfig, json) {
3192
3316
  });
3193
3317
  }
3194
3318
  }
3195
- async function reportMarketplaceDownload(slug, apiUrl) {
3319
+ var MARKETPLACE_REPORT_URL = process.env.INSFORGE_MARKETPLACE_REPORT_URL ?? "https://p8n7m7ci.us-east.insforge.app/functions/report-download";
3320
+ async function reportMarketplaceDownload(slug) {
3196
3321
  try {
3197
- const res = await fetch(`${apiUrl}/templates/v1/${encodeURIComponent(slug)}/downloads`, {
3322
+ const res = await fetch(MARKETPLACE_REPORT_URL, {
3198
3323
  method: "POST",
3199
3324
  headers: { "Content-Type": "application/json" },
3200
- body: "{}"
3325
+ body: JSON.stringify({ slug }),
3326
+ signal: AbortSignal.timeout(5e3)
3201
3327
  });
3202
3328
  if (!res.ok) {
3203
3329
  return;
@@ -3209,15 +3335,15 @@ async function reportMarketplaceDownload(slug, apiUrl) {
3209
3335
  // src/commands/projects/link.ts
3210
3336
  var execAsync3 = promisify4(exec3);
3211
3337
  async function runNpmInstall(startMessage = "Installing dependencies...") {
3212
- const spinner11 = clack13.spinner();
3338
+ const spinner11 = clack14.spinner();
3213
3339
  spinner11.start(startMessage);
3214
3340
  try {
3215
3341
  await execAsync3("npm install", { cwd: process.cwd(), maxBuffer: 10 * 1024 * 1024 });
3216
3342
  spinner11.stop("Dependencies installed");
3217
3343
  } catch (err) {
3218
3344
  spinner11.stop("Failed to install dependencies");
3219
- clack13.log.warn(`npm install failed: ${err.message}`);
3220
- clack13.log.info("Run `npm install` manually to install dependencies.");
3345
+ clack14.log.warn(`npm install failed: ${err.message}`);
3346
+ clack14.log.info("Run `npm install` manually to install dependencies.");
3221
3347
  }
3222
3348
  }
3223
3349
  async function runNpmSetupIfPresent() {
@@ -3229,15 +3355,15 @@ async function runNpmSetupIfPresent() {
3229
3355
  } catch {
3230
3356
  }
3231
3357
  if (!hasSetup) return;
3232
- const spinner11 = clack13.spinner();
3358
+ const spinner11 = clack14.spinner();
3233
3359
  spinner11.start("Running setup (schema + migrations)...");
3234
3360
  try {
3235
3361
  await execAsync3("npm run setup", { cwd: process.cwd(), maxBuffer: 20 * 1024 * 1024 });
3236
3362
  spinner11.stop("Setup complete");
3237
3363
  } catch (err) {
3238
3364
  spinner11.stop("Setup failed");
3239
- clack13.log.warn(`npm run setup failed: ${err.message.split("\n")[0]}`);
3240
- clack13.log.info("Inspect the error, fix DATABASE_URL or network access, then run `npm run setup` manually.");
3365
+ clack14.log.warn(`npm run setup failed: ${err.message.split("\n")[0]}`);
3366
+ clack14.log.info("Inspect the error, fix DATABASE_URL or network access, then run `npm run setup` manually.");
3241
3367
  }
3242
3368
  }
3243
3369
  function registerProjectLinkCommand(program2) {
@@ -3260,7 +3386,7 @@ function registerProjectLinkCommand(program2) {
3260
3386
  if (json) {
3261
3387
  outputJson({ success: true, skills_only: true });
3262
3388
  } else {
3263
- clack13.note(
3389
+ clack14.note(
3264
3390
  `Open your coding agent (Claude Code, Codex, Cursor, etc.) and ask it to build something. It will walk you through provisioning an InsForge project when needed. If you're not signed in yet, your browser will open for sign-in at that point.`,
3265
3391
  "What's next"
3266
3392
  );
@@ -3338,11 +3464,11 @@ function registerProjectLinkCommand(program2) {
3338
3464
  if (opts.auth) {
3339
3465
  try {
3340
3466
  const result = await applyAuthProvider(opts.auth, process.cwd(), projectConfig2, json);
3341
- if (!json) clack13.log.success(`Wired in ${opts.auth}: ${result.written.length} new, ${result.overwritten.length} replaced`);
3467
+ if (!json) clack14.log.success(`Wired in ${opts.auth}: ${result.written.length} new, ${result.overwritten.length} replaced`);
3342
3468
  } catch (err) {
3343
3469
  const msg = `Failed to apply --auth ${opts.auth}: ${err.message}`;
3344
3470
  if (json) console.error(JSON.stringify({ warning: msg }));
3345
- else clack13.log.warn(msg);
3471
+ else clack14.log.warn(msg);
3346
3472
  }
3347
3473
  }
3348
3474
  if (templateDownloaded && !json) {
@@ -3368,9 +3494,9 @@ function registerProjectLinkCommand(program2) {
3368
3494
  `${pc2.bold("1.")} ${runCommand}`,
3369
3495
  `${pc2.bold("2.")} Open ${pc2.cyan("Claude Code")} or ${pc2.cyan("Cursor")} and prompt your agent to add more features`
3370
3496
  ];
3371
- clack13.note(steps.join("\n"), "What's next");
3497
+ clack14.note(steps.join("\n"), "What's next");
3372
3498
  } else {
3373
- clack13.log.warn("Template download failed. You can retry or set up manually.");
3499
+ clack14.log.warn("Template download failed. You can retry or set up manually.");
3374
3500
  }
3375
3501
  }
3376
3502
  return;
@@ -3385,7 +3511,7 @@ function registerProjectLinkCommand(program2) {
3385
3511
  try {
3386
3512
  const result = await applyAuthProvider(opts.auth, process.cwd(), projectConfig2, json);
3387
3513
  if (!json) {
3388
- clack13.log.success(`Wired in ${opts.auth}: ${result.written.length} new, ${result.overwritten.length} replaced`);
3514
+ clack14.log.success(`Wired in ${opts.auth}: ${result.written.length} new, ${result.overwritten.length} replaced`);
3389
3515
  }
3390
3516
  if (result.packageJsonPatched && !json) {
3391
3517
  await runNpmInstall("Installing new dependencies...");
@@ -3394,7 +3520,7 @@ function registerProjectLinkCommand(program2) {
3394
3520
  } catch (err) {
3395
3521
  const msg = `Failed to apply --auth ${opts.auth}: ${err.message}`;
3396
3522
  if (json) console.error(JSON.stringify({ warning: msg }));
3397
- else clack13.log.warn(msg);
3523
+ else clack14.log.warn(msg);
3398
3524
  }
3399
3525
  }
3400
3526
  trackCommand("link", "oss-org", { direct: true });
@@ -3424,7 +3550,7 @@ function registerProjectLinkCommand(program2) {
3424
3550
  }
3425
3551
  if (orgs.length === 1) {
3426
3552
  orgId = orgs[0].id;
3427
- if (!json) clack13.log.info(`Using organization: ${orgs[0].name}`);
3553
+ if (!json) clack14.log.info(`Using organization: ${orgs[0].name}`);
3428
3554
  } else {
3429
3555
  if (json) {
3430
3556
  throw new CLIError("Multiple organizations found. Specify --org-id.");
@@ -3535,11 +3661,11 @@ function registerProjectLinkCommand(program2) {
3535
3661
  if (opts.auth) {
3536
3662
  try {
3537
3663
  const result = await applyAuthProvider(opts.auth, process.cwd(), projectConfig, json);
3538
- if (!json) clack13.log.success(`Wired in ${opts.auth}: ${result.written.length} new, ${result.overwritten.length} replaced`);
3664
+ if (!json) clack14.log.success(`Wired in ${opts.auth}: ${result.written.length} new, ${result.overwritten.length} replaced`);
3539
3665
  } catch (err) {
3540
3666
  const msg = `Failed to apply --auth ${opts.auth}: ${err.message}`;
3541
3667
  if (json) console.error(JSON.stringify({ warning: msg }));
3542
- else clack13.log.warn(msg);
3668
+ else clack14.log.warn(msg);
3543
3669
  }
3544
3670
  }
3545
3671
  if (templateDownloaded && !json) {
@@ -3552,16 +3678,16 @@ function registerProjectLinkCommand(program2) {
3552
3678
  await reportCliUsage("cli.link", true, 6, projectConfig);
3553
3679
  if (!json) {
3554
3680
  const dashboardUrl = `${getFrontendUrl()}/dashboard/project/${project.id}`;
3555
- clack13.log.step(`Dashboard: ${pc2.underline(dashboardUrl)}`);
3681
+ clack14.log.step(`Dashboard: ${pc2.underline(dashboardUrl)}`);
3556
3682
  if (templateDownloaded) {
3557
3683
  const runCommand = `${pc2.cyan("cd")} ${pc2.green(dirName)} ${pc2.dim("&&")} ${pc2.cyan("npm run dev")}`;
3558
3684
  const steps = [
3559
3685
  `${pc2.bold("1.")} ${runCommand}`,
3560
3686
  `${pc2.bold("2.")} Open ${pc2.cyan("Claude Code")} or ${pc2.cyan("Cursor")} and prompt your agent to add more features`
3561
3687
  ];
3562
- clack13.note(steps.join("\n"), "What's next");
3688
+ clack14.note(steps.join("\n"), "What's next");
3563
3689
  } else {
3564
- clack13.log.warn("Template download failed. You can retry or set up manually.");
3690
+ clack14.log.warn("Template download failed. You can retry or set up manually.");
3565
3691
  }
3566
3692
  }
3567
3693
  } else {
@@ -3569,7 +3695,7 @@ function registerProjectLinkCommand(program2) {
3569
3695
  try {
3570
3696
  const result = await applyAuthProvider(opts.auth, process.cwd(), projectConfig, json);
3571
3697
  if (!json) {
3572
- clack13.log.success(`Wired in ${opts.auth}: ${result.written.length} new, ${result.overwritten.length} replaced`);
3698
+ clack14.log.success(`Wired in ${opts.auth}: ${result.written.length} new, ${result.overwritten.length} replaced`);
3573
3699
  }
3574
3700
  if (result.packageJsonPatched && !json) {
3575
3701
  await runNpmInstall("Installing new dependencies...");
@@ -3578,20 +3704,20 @@ function registerProjectLinkCommand(program2) {
3578
3704
  } catch (err) {
3579
3705
  const msg = `Failed to apply --auth ${opts.auth}: ${err.message}`;
3580
3706
  if (json) console.error(JSON.stringify({ warning: msg }));
3581
- else clack13.log.warn(msg);
3707
+ else clack14.log.warn(msg);
3582
3708
  }
3583
3709
  }
3584
3710
  await installSkills(json, opts.auth);
3585
3711
  await reportCliUsage("cli.link", true, 6, projectConfig);
3586
3712
  if (!json) {
3587
3713
  const dashboardUrl = `${getFrontendUrl()}/dashboard/project/${project.id}`;
3588
- clack13.log.step(`Dashboard: ${dashboardUrl}`);
3714
+ clack14.log.step(`Dashboard: ${dashboardUrl}`);
3589
3715
  const prompts = [
3590
3716
  "Build a todo app with Google OAuth sign-in",
3591
3717
  "Build an Instagram clone where users can upload photos, like, and comment",
3592
3718
  "Build an AI chatbot with conversation history and deploy it to a live URL"
3593
3719
  ];
3594
- clack13.note(
3720
+ clack14.note(
3595
3721
  `Open your coding agent (Claude Code, Codex, Cursor, etc.) and try:
3596
3722
 
3597
3723
  ${prompts.map((p3) => `\u2022 "${p3}"`).join("\n")}`,
@@ -3831,7 +3957,7 @@ function registerDbRpcCommand(dbCmd2) {
3831
3957
  }
3832
3958
 
3833
3959
  // src/commands/db/export.ts
3834
- import { writeFileSync as writeFileSync3 } from "fs";
3960
+ import { writeFileSync as writeFileSync4 } from "fs";
3835
3961
  function registerDbExportCommand(dbCmd2) {
3836
3962
  dbCmd2.command("export").description("Export database schema and/or data").option("--format <format>", "Export format: sql or json", "sql").option("--tables <tables>", "Comma-separated list of tables to export (default: all)").option("--no-data", "Exclude table data (schema only)").option("--include-functions", "Include database functions").option("--include-sequences", "Include sequences").option("--include-views", "Include views").option("--row-limit <n>", "Maximum rows per table").option("-o, --output <file>", "Output file path (default: stdout)").action(async (opts, cmd) => {
3837
3963
  const { json } = getRootOpts(cmd);
@@ -3871,7 +3997,7 @@ function registerDbExportCommand(dbCmd2) {
3871
3997
  return;
3872
3998
  }
3873
3999
  if (opts.output) {
3874
- writeFileSync3(opts.output, content);
4000
+ writeFileSync4(opts.output, content);
3875
4001
  const tableCount = meta?.tables?.length;
3876
4002
  const suffix = tableCount ? ` (${tableCount} tables, format: ${meta?.format ?? opts.format})` : "";
3877
4003
  outputSuccess(`Exported to ${opts.output}${suffix}`);
@@ -3885,7 +4011,7 @@ function registerDbExportCommand(dbCmd2) {
3885
4011
  }
3886
4012
 
3887
4013
  // src/commands/db/import.ts
3888
- import { readFileSync as readFileSync3 } from "fs";
4014
+ import { readFileSync as readFileSync4 } from "fs";
3889
4015
  import { basename as basename5 } from "path";
3890
4016
  function registerDbImportCommand(dbCmd2) {
3891
4017
  dbCmd2.command("import <file>").description("Import database from a local SQL file").option("--truncate", "Truncate existing tables before import").action(async (file, opts, cmd) => {
@@ -3894,7 +4020,7 @@ function registerDbImportCommand(dbCmd2) {
3894
4020
  await requireAuth();
3895
4021
  const config = getProjectConfig();
3896
4022
  if (!config) throw new ProjectNotLinkedError();
3897
- const fileContent = readFileSync3(file);
4023
+ const fileContent = readFileSync4(file);
3898
4024
  const fileName = basename5(file);
3899
4025
  const formData = new FormData();
3900
4026
  formData.append("file", new Blob([fileContent]), fileName);
@@ -3925,12 +4051,12 @@ function registerDbImportCommand(dbCmd2) {
3925
4051
  }
3926
4052
 
3927
4053
  // src/commands/db/migrations.ts
3928
- import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
3929
- import { join as join9 } from "path";
4054
+ import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "fs";
4055
+ import { join as join10 } from "path";
3930
4056
 
3931
4057
  // src/lib/migrations.ts
3932
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, readdirSync } from "fs";
3933
- import { join as join8 } from "path";
4058
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync } from "fs";
4059
+ import { join as join9 } from "path";
3934
4060
  var MIGRATION_VERSION_REGEX = /^\d{1,64}$/u;
3935
4061
  var MIGRATION_FILENAME_REGEX = /^(\d{1,64})_([a-z0-9-]+)\.sql$/u;
3936
4062
  function assertValidMigrationVersion(version) {
@@ -3994,18 +4120,18 @@ function incrementMigrationVersion(version) {
3994
4120
  return formatMigrationVersion(new Date(nextTimestamp));
3995
4121
  }
3996
4122
  function getMigrationsDir(cwd = process.cwd()) {
3997
- return join8(cwd, "migrations");
4123
+ return join9(cwd, "migrations");
3998
4124
  }
3999
4125
  function ensureMigrationsDir(cwd = process.cwd()) {
4000
4126
  const migrationsDir = getMigrationsDir(cwd);
4001
- if (!existsSync4(migrationsDir)) {
4127
+ if (!existsSync5(migrationsDir)) {
4002
4128
  mkdirSync2(migrationsDir, { recursive: true });
4003
4129
  }
4004
4130
  return migrationsDir;
4005
4131
  }
4006
4132
  function listLocalMigrationFilenames(cwd = process.cwd()) {
4007
4133
  const migrationsDir = getMigrationsDir(cwd);
4008
- if (!existsSync4(migrationsDir)) {
4134
+ if (!existsSync5(migrationsDir)) {
4009
4135
  return [];
4010
4136
  }
4011
4137
  return readdirSync(migrationsDir).sort((left, right) => left.localeCompare(right));
@@ -4193,12 +4319,12 @@ function registerDbMigrationsCommand(dbCmd2) {
4193
4319
  migration.version,
4194
4320
  migration.name
4195
4321
  );
4196
- const filePath = join9(migrationsDir, filename);
4197
- if (existingLocalVersions.has(migration.version) || existsSync5(filePath)) {
4322
+ const filePath = join10(migrationsDir, filename);
4323
+ if (existingLocalVersions.has(migration.version) || existsSync6(filePath)) {
4198
4324
  skippedFiles.push(filename);
4199
4325
  continue;
4200
4326
  }
4201
- writeFileSync4(filePath, formatMigrationSql(migration.statements));
4327
+ writeFileSync5(filePath, formatMigrationSql(migration.statements));
4202
4328
  createdFiles.push(filename);
4203
4329
  existingLocalVersions.add(migration.version);
4204
4330
  }
@@ -4236,9 +4362,9 @@ function registerDbMigrationsCommand(dbCmd2) {
4236
4362
  );
4237
4363
  const filename = buildMigrationFilename(nextVersion, migrationName);
4238
4364
  const migrationsDir = ensureMigrationsDir();
4239
- const filePath = join9(migrationsDir, filename);
4365
+ const filePath = join10(migrationsDir, filename);
4240
4366
  try {
4241
- writeFileSync4(filePath, "", { flag: "wx" });
4367
+ writeFileSync5(filePath, "", { flag: "wx" });
4242
4368
  } catch (error) {
4243
4369
  if (error.code === "EEXIST") {
4244
4370
  throw new CLIError(`Migration file already exists: ${filename}`);
@@ -4294,11 +4420,11 @@ function registerDbMigrationsCommand(dbCmd2) {
4294
4420
  `Migration ${targetMigration.filename} is not the next pending local migration. Apply ${earlierPendingMigration.filename} first, or fix/delete it locally if it is invalid or no longer needed.`
4295
4421
  );
4296
4422
  }
4297
- const filePath = join9(getMigrationsDir(), targetMigration.filename);
4298
- if (!existsSync5(filePath)) {
4423
+ const filePath = join10(getMigrationsDir(), targetMigration.filename);
4424
+ if (!existsSync6(filePath)) {
4299
4425
  throw new CLIError(`Local migration file not found: ${targetMigration.filename}`);
4300
4426
  }
4301
- const sql = readFileSync4(filePath, "utf-8");
4427
+ const sql = readFileSync5(filePath, "utf-8");
4302
4428
  if (!sql.trim()) {
4303
4429
  throw new CLIError(`Migration file is empty: ${targetMigration.filename}`);
4304
4430
  }
@@ -4360,11 +4486,11 @@ function registerDbMigrationsCommand(dbCmd2) {
4360
4486
  }
4361
4487
  }
4362
4488
  for (const migration of migrationsToApply) {
4363
- const filePath = join9(getMigrationsDir(), migration.filename);
4364
- if (!existsSync5(filePath)) {
4489
+ const filePath = join10(getMigrationsDir(), migration.filename);
4490
+ if (!existsSync6(filePath)) {
4365
4491
  throw new CLIError(`Local migration file not found: ${migration.filename}`);
4366
4492
  }
4367
- const sql = readFileSync4(filePath, "utf-8");
4493
+ const sql = readFileSync5(filePath, "utf-8");
4368
4494
  if (!sql.trim()) {
4369
4495
  throw new CLIError(`Migration file is empty: ${migration.filename}`);
4370
4496
  }
@@ -4593,12 +4719,12 @@ function registerFunctionsCommands(functionsCmd2) {
4593
4719
  }
4594
4720
 
4595
4721
  // src/commands/functions/deploy.ts
4596
- import { readFileSync as readFileSync5, existsSync as existsSync6 } from "fs";
4722
+ import { readFileSync as readFileSync6, existsSync as existsSync7 } from "fs";
4597
4723
  function resolveDeployFilePath(opts) {
4598
4724
  if (!opts.file) {
4599
4725
  throw new CLIError("Missing required option: --file <path>");
4600
4726
  }
4601
- if (!existsSync6(opts.file)) {
4727
+ if (!existsSync7(opts.file)) {
4602
4728
  throw new CLIError(`Source file not found: ${opts.file}`);
4603
4729
  }
4604
4730
  return opts.file;
@@ -4609,7 +4735,7 @@ function registerFunctionsDeployCommand(functionsCmd2) {
4609
4735
  try {
4610
4736
  await requireAuth();
4611
4737
  const filePath = resolveDeployFilePath(opts);
4612
- const code = readFileSync5(filePath, "utf-8");
4738
+ const code = readFileSync6(filePath, "utf-8");
4613
4739
  const name = opts.name ?? slug;
4614
4740
  const description = opts.description ?? "";
4615
4741
  let exists = false;
@@ -4730,7 +4856,7 @@ function registerFunctionsCodeCommand(functionsCmd2) {
4730
4856
  }
4731
4857
 
4732
4858
  // src/commands/functions/delete.ts
4733
- import * as clack14 from "@clack/prompts";
4859
+ import * as clack15 from "@clack/prompts";
4734
4860
  function registerFunctionsDeleteCommand(functionsCmd2) {
4735
4861
  functionsCmd2.command("delete <slug>").description("Delete an edge function").action(async (slug, _opts, cmd) => {
4736
4862
  const { json, yes } = getRootOpts(cmd);
@@ -4741,7 +4867,7 @@ function registerFunctionsDeleteCommand(functionsCmd2) {
4741
4867
  message: `Delete function "${slug}"? This cannot be undone.`
4742
4868
  });
4743
4869
  if (isCancel2(confirmed) || !confirmed) {
4744
- clack14.log.info("Cancelled.");
4870
+ clack15.log.info("Cancelled.");
4745
4871
  return;
4746
4872
  }
4747
4873
  }
@@ -4796,7 +4922,7 @@ function registerStorageBucketsCommand(storageCmd2) {
4796
4922
  }
4797
4923
 
4798
4924
  // src/commands/storage/upload.ts
4799
- import { readFileSync as readFileSync6, existsSync as existsSync7 } from "fs";
4925
+ import { readFileSync as readFileSync7, existsSync as existsSync8 } from "fs";
4800
4926
  import { basename as basename6 } from "path";
4801
4927
  function registerStorageUploadCommand(storageCmd2) {
4802
4928
  storageCmd2.command("upload <file>").description("Upload a file to a storage bucket").requiredOption("--bucket <name>", "Target bucket name").option("--key <objectKey>", "Object key (defaults to filename)").action(async (file, opts, cmd) => {
@@ -4805,10 +4931,10 @@ function registerStorageUploadCommand(storageCmd2) {
4805
4931
  await requireAuth();
4806
4932
  const config = getProjectConfig();
4807
4933
  if (!config) throw new ProjectNotLinkedError();
4808
- if (!existsSync7(file)) {
4934
+ if (!existsSync8(file)) {
4809
4935
  throw new CLIError(`File not found: ${file}`);
4810
4936
  }
4811
- const fileContent = readFileSync6(file);
4937
+ const fileContent = readFileSync7(file);
4812
4938
  const objectKey = opts.key ?? basename6(file);
4813
4939
  const bucketName = opts.bucket;
4814
4940
  const formData = new FormData();
@@ -4839,8 +4965,8 @@ function registerStorageUploadCommand(storageCmd2) {
4839
4965
  }
4840
4966
 
4841
4967
  // src/commands/storage/download.ts
4842
- import { writeFileSync as writeFileSync5 } from "fs";
4843
- import { join as join10, basename as basename7 } from "path";
4968
+ import { writeFileSync as writeFileSync6 } from "fs";
4969
+ import { join as join11, basename as basename7 } from "path";
4844
4970
  function registerStorageDownloadCommand(storageCmd2) {
4845
4971
  storageCmd2.command("download <objectKey>").description("Download a file from a storage bucket").requiredOption("--bucket <name>", "Source bucket name").option("--output <path>", "Output file path (defaults to current directory)").action(async (objectKey, opts, cmd) => {
4846
4972
  const { json } = getRootOpts(cmd);
@@ -4860,8 +4986,8 @@ function registerStorageDownloadCommand(storageCmd2) {
4860
4986
  throw new CLIError(err.error ?? `Download failed: ${res.status}`);
4861
4987
  }
4862
4988
  const buffer = Buffer.from(await res.arrayBuffer());
4863
- const outputPath = opts.output ?? join10(process.cwd(), basename7(objectKey));
4864
- writeFileSync5(outputPath, buffer);
4989
+ const outputPath = opts.output ?? join11(process.cwd(), basename7(objectKey));
4990
+ writeFileSync6(outputPath, buffer);
4865
4991
  if (json) {
4866
4992
  outputJson({ success: true, path: outputPath, size: buffer.length });
4867
4993
  } else {
@@ -5102,11 +5228,9 @@ function registerDeploymentsListCommand(deploymentsCmd2) {
5102
5228
  const deployments = Array.isArray(raw) ? raw : raw && typeof raw === "object" && "data" in raw ? raw.data ?? [] : [];
5103
5229
  if (json) {
5104
5230
  outputJson(raw);
5231
+ } else if (!deployments.length) {
5232
+ console.log("No deployments found.");
5105
5233
  } else {
5106
- if (!deployments.length) {
5107
- console.log("No deployments found.");
5108
- return;
5109
- }
5110
5234
  outputTable(
5111
5235
  ["ID", "Status", "Provider", "URL", "Created"],
5112
5236
  deployments.map((d) => [
@@ -5118,9 +5242,9 @@ function registerDeploymentsListCommand(deploymentsCmd2) {
5118
5242
  ])
5119
5243
  );
5120
5244
  }
5121
- await reportCliUsage("cli.deployments.list", true);
5245
+ await trackDeploymentUsage("list", true);
5122
5246
  } catch (err) {
5123
- await reportCliUsage("cli.deployments.list", false);
5247
+ await trackDeploymentUsage("list", false);
5124
5248
  handleError(err, json);
5125
5249
  }
5126
5250
  });
@@ -5156,7 +5280,9 @@ function registerDeploymentsStatusCommand(deploymentsCmd2) {
5156
5280
  ]
5157
5281
  );
5158
5282
  }
5283
+ await trackDeploymentUsage("status", true, { sync: Boolean(opts.sync) });
5159
5284
  } catch (err) {
5285
+ await trackDeploymentUsage("status", false, { sync: Boolean(opts.sync) });
5160
5286
  handleError(err, json);
5161
5287
  }
5162
5288
  });
@@ -5182,7 +5308,9 @@ function registerDeploymentsCancelCommand(deploymentsCmd2) {
5182
5308
  } else {
5183
5309
  outputSuccess(`Deployment ${id} cancelled.`);
5184
5310
  }
5311
+ await trackDeploymentUsage("cancel", true);
5185
5312
  } catch (err) {
5313
+ await trackDeploymentUsage("cancel", false);
5186
5314
  handleError(err, json);
5187
5315
  }
5188
5316
  });
@@ -5201,11 +5329,9 @@ function registerDeploymentsEnvVarsCommand(deploymentsCmd2) {
5201
5329
  const envVars = data.envVars ?? [];
5202
5330
  if (json) {
5203
5331
  outputJson(data);
5332
+ } else if (!envVars.length) {
5333
+ console.log("No environment variables found.");
5204
5334
  } else {
5205
- if (!envVars.length) {
5206
- console.log("No environment variables found.");
5207
- return;
5208
- }
5209
5335
  outputTable(
5210
5336
  ["ID", "Key", "Type", "Updated At"],
5211
5337
  envVars.map((v) => [
@@ -5216,9 +5342,9 @@ function registerDeploymentsEnvVarsCommand(deploymentsCmd2) {
5216
5342
  ])
5217
5343
  );
5218
5344
  }
5219
- await reportCliUsage("cli.deployments.env.list", true);
5345
+ await trackDeploymentUsage("env.list", true);
5220
5346
  } catch (err) {
5221
- await reportCliUsage("cli.deployments.env.list", false);
5347
+ await trackDeploymentUsage("env.list", false);
5222
5348
  handleError(err, json);
5223
5349
  }
5224
5350
  });
@@ -5237,9 +5363,9 @@ function registerDeploymentsEnvVarsCommand(deploymentsCmd2) {
5237
5363
  } else {
5238
5364
  outputSuccess(data.message);
5239
5365
  }
5240
- await reportCliUsage("cli.deployments.env.set", true);
5366
+ await trackDeploymentUsage("env.set", true);
5241
5367
  } catch (err) {
5242
- await reportCliUsage("cli.deployments.env.set", false);
5368
+ await trackDeploymentUsage("env.set", false);
5243
5369
  handleError(err, json);
5244
5370
  }
5245
5371
  });
@@ -5257,9 +5383,9 @@ function registerDeploymentsEnvVarsCommand(deploymentsCmd2) {
5257
5383
  } else {
5258
5384
  outputSuccess(data.message);
5259
5385
  }
5260
- await reportCliUsage("cli.deployments.env.delete", true);
5386
+ await trackDeploymentUsage("env.delete", true);
5261
5387
  } catch (err) {
5262
- await reportCliUsage("cli.deployments.env.delete", false);
5388
+ await trackDeploymentUsage("env.delete", false);
5263
5389
  handleError(err, json);
5264
5390
  }
5265
5391
  });
@@ -5729,14 +5855,17 @@ function registerComputeListCommand(computeCmd2) {
5729
5855
  }
5730
5856
  outputTable(
5731
5857
  ["Name", "Status", "Image", "CPU", "Memory", "Endpoint"],
5732
- services.map((s) => [
5733
- String(s.name ?? "-"),
5734
- String(s.status ?? "-"),
5735
- String(s.imageUrl ?? "-"),
5736
- String(s.cpu ?? "-"),
5737
- s.memory ? `${s.memory}MB` : "-",
5738
- String(s.endpointUrl ?? "-")
5739
- ])
5858
+ services.map((s) => {
5859
+ const endpoint = s.protocol === "tcp" && s.endpointUrl && s.port ? `${String(s.endpointUrl).replace(/^https?:\/\//, "")}:${s.port}` : String(s.endpointUrl ?? "-");
5860
+ return [
5861
+ String(s.name ?? "-"),
5862
+ String(s.status ?? "-"),
5863
+ String(s.imageUrl ?? "-"),
5864
+ String(s.cpu ?? "-"),
5865
+ s.memory ? `${s.memory}MB` : "-",
5866
+ endpoint
5867
+ ];
5868
+ })
5740
5869
  );
5741
5870
  }
5742
5871
  await reportCliUsage("cli.compute.list", true);
@@ -5758,14 +5887,16 @@ function registerComputeGetCommand(computeCmd2) {
5758
5887
  if (json) {
5759
5888
  outputJson(service);
5760
5889
  } else {
5890
+ const endpoint = service.protocol === "tcp" && service.endpointUrl && service.port ? `${String(service.endpointUrl).replace(/^https?:\/\//, "")}:${service.port}` : service.endpointUrl ?? "n/a";
5761
5891
  outputInfo(`Name: ${service.name}`);
5762
5892
  outputInfo(`ID: ${service.id}`);
5763
5893
  outputInfo(`Status: ${service.status}`);
5764
5894
  outputInfo(`Image: ${service.imageUrl}`);
5895
+ outputInfo(`Protocol: ${service.protocol ?? "http"}`);
5765
5896
  outputInfo(`CPU: ${service.cpu}`);
5766
5897
  outputInfo(`Memory: ${service.memory}MB`);
5767
5898
  outputInfo(`Region: ${service.region}`);
5768
- outputInfo(`Endpoint: ${service.endpointUrl ?? "n/a"}`);
5899
+ outputInfo(`Endpoint: ${endpoint}`);
5769
5900
  outputInfo(`Created: ${service.createdAt}`);
5770
5901
  }
5771
5902
  await reportCliUsage("cli.compute.get", true);
@@ -5991,16 +6122,16 @@ function registerComputeEventsCommand(computeCmd2) {
5991
6122
  }
5992
6123
 
5993
6124
  // src/commands/compute/deploy.ts
5994
- import { existsSync as existsSync9 } from "fs";
5995
- import { join as join12, resolve as resolve4 } from "path";
6125
+ import { existsSync as existsSync10 } from "fs";
6126
+ import { join as join13, resolve as resolve4 } from "path";
5996
6127
 
5997
6128
  // src/lib/env-file.ts
5998
- import { readFileSync as readFileSync7 } from "fs";
6129
+ import { readFileSync as readFileSync8 } from "fs";
5999
6130
  var ENV_KEY_REGEX2 = /^[A-Z_][A-Z0-9_]*$/;
6000
6131
  function parseEnvFile(path6) {
6001
6132
  let raw;
6002
6133
  try {
6003
- raw = readFileSync7(path6, "utf-8");
6134
+ raw = readFileSync8(path6, "utf-8");
6004
6135
  } catch (err) {
6005
6136
  const msg = err instanceof Error ? err.message : String(err);
6006
6137
  throw new CLIError(`Could not read --env-file at ${path6}: ${msg}`);
@@ -6036,8 +6167,8 @@ function parseEnvFile(path6) {
6036
6167
 
6037
6168
  // src/lib/flyctl.ts
6038
6169
  import { spawn, spawnSync } from "child_process";
6039
- import { existsSync as existsSync8, writeFileSync as writeFileSync6, unlinkSync as unlinkSync3 } from "fs";
6040
- import { join as join11 } from "path";
6170
+ import { existsSync as existsSync9, writeFileSync as writeFileSync7, unlinkSync as unlinkSync3 } from "fs";
6171
+ import { join as join12 } from "path";
6041
6172
  function ensureFlyctlAvailable() {
6042
6173
  const r = spawnSync("flyctl", ["version"], {
6043
6174
  encoding: "utf8",
@@ -6050,25 +6181,35 @@ function ensureFlyctlAvailable() {
6050
6181
  }
6051
6182
  }
6052
6183
  function ensureFlyTomlStub(opts) {
6053
- const path6 = join11(opts.dir, "fly.toml");
6054
- if (existsSync8(path6)) {
6184
+ const path6 = join12(opts.dir, "fly.toml");
6185
+ if (existsSync9(path6)) {
6055
6186
  return () => {
6056
6187
  };
6057
6188
  }
6058
- const stub = `# Auto-generated by @insforge/cli for compute deploy. Safe to delete.
6059
- app = "${opts.appId}"
6060
- primary_region = "${opts.region}"
6061
-
6062
- [build]
6189
+ const serviceBlock = opts.protocol === "tcp" ? `[[services]]
6190
+ internal_port = ${opts.port}
6191
+ protocol = "tcp"
6192
+ auto_stop_machines = false
6193
+ auto_start_machines = true
6194
+ min_machines_running = 0
6063
6195
 
6064
- [http_service]
6196
+ [[services.ports]]
6197
+ port = ${opts.port}
6198
+ ` : `[http_service]
6065
6199
  internal_port = ${opts.port}
6066
6200
  force_https = true
6067
6201
  auto_stop_machines = false
6068
6202
  auto_start_machines = true
6069
6203
  min_machines_running = 0
6070
6204
  `;
6071
- writeFileSync6(path6, stub, "utf8");
6205
+ const stub = `# Auto-generated by @insforge/cli for compute deploy. Safe to delete.
6206
+ app = "${opts.appId}"
6207
+ primary_region = "${opts.region}"
6208
+
6209
+ [build]
6210
+
6211
+ ` + serviceBlock;
6212
+ writeFileSync7(path6, stub, "utf8");
6072
6213
  return () => {
6073
6214
  try {
6074
6215
  unlinkSync3(path6);
@@ -6082,7 +6223,8 @@ function flyctlBuildAndPush(opts) {
6082
6223
  dir: opts.dir,
6083
6224
  appId: opts.appId,
6084
6225
  region: opts.region,
6085
- port: opts.port
6226
+ port: opts.port,
6227
+ protocol: opts.protocol
6086
6228
  });
6087
6229
  const child = spawn(
6088
6230
  "flyctl",
@@ -6149,6 +6291,10 @@ function registerComputeDeployCommand(computeCmd2) {
6149
6291
  ).option("--memory <mb>", "Memory in MB", "512").option("--region <region>", "Fly.io region", "iad").option("--env <json>", "Env vars as JSON object").option(
6150
6292
  "--env-file <path>",
6151
6293
  "Path to a .env file (KEY=VALUE per line, #-comments + blank lines ok). Mutually exclusive with --env."
6294
+ ).option(
6295
+ "--protocol <protocol>",
6296
+ 'Edge protocol: "http" (default) or "tcp" (raw pass-through for Redis, etc.)',
6297
+ "http"
6152
6298
  ).action(async (dir, opts, cmd) => {
6153
6299
  const { json } = getRootOpts(cmd);
6154
6300
  try {
@@ -6165,6 +6311,9 @@ function registerComputeDeployCommand(computeCmd2) {
6165
6311
  if (!Number.isInteger(port) || port < 1 || port > 65535) {
6166
6312
  throw new CLIError(`Invalid --port: ${opts.port}`);
6167
6313
  }
6314
+ if (opts.protocol !== "http" && opts.protocol !== "tcp") {
6315
+ throw new CLIError(`Invalid --protocol: ${opts.protocol} (expected "http" or "tcp")`);
6316
+ }
6168
6317
  const memory = Number(opts.memory);
6169
6318
  if (!Number.isInteger(memory) || memory <= 0) {
6170
6319
  throw new CLIError(`Invalid --memory: ${opts.memory}`);
@@ -6204,6 +6353,7 @@ function registerComputeDeployCommand(computeCmd2) {
6204
6353
  region: opts.region
6205
6354
  };
6206
6355
  if (envVars) baseBody.envVars = envVars;
6356
+ if (opts.protocol === "tcp") baseBody.protocol = "tcp";
6207
6357
  if (!dir) {
6208
6358
  const body = { ...baseBody, imageUrl: opts.image };
6209
6359
  const listRes2 = await ossFetch("/api/compute/services");
@@ -6215,6 +6365,7 @@ function registerComputeDeployCommand(computeCmd2) {
6215
6365
  if (!json) outputInfo(`Found existing service "${opts.name}", updating...`);
6216
6366
  const updateBody2 = { ...body };
6217
6367
  delete updateBody2.name;
6368
+ if (opts.protocol === "tcp") updateBody2.protocol = "tcp";
6218
6369
  res = await ossFetch(`/api/compute/services/${encodeURIComponent(existing2.id)}`, {
6219
6370
  method: "PATCH",
6220
6371
  body: JSON.stringify(updateBody2)
@@ -6231,15 +6382,22 @@ function registerComputeDeployCommand(computeCmd2) {
6231
6382
  } else {
6232
6383
  const verb = existing2 ? "updated" : "deployed";
6233
6384
  outputSuccess(`Service "${service2.name}" ${verb} [${service2.status}]`);
6234
- if (service2.endpointUrl) console.log(` Endpoint: ${service2.endpointUrl}`);
6385
+ if (service2.endpointUrl && opts.protocol === "tcp") {
6386
+ const host = String(service2.endpointUrl).replace(/^https?:\/\//, "");
6387
+ console.log(` Endpoint: ${host}:${service2.port} (connect with <scheme>://${host}:${service2.port})`);
6388
+ console.log(` Note: TCP services are reachable from the public internet.`);
6389
+ console.log(` Configure auth on your container (e.g. redis --requirepass <secret>).`);
6390
+ } else if (service2.endpointUrl) {
6391
+ console.log(` Endpoint: ${service2.endpointUrl}`);
6392
+ }
6235
6393
  if (service2.port !== void 0) console.log(` Port: ${service2.port} (container must listen on this port)`);
6236
6394
  }
6237
6395
  await reportCliUsage("cli.compute.deploy", true);
6238
6396
  return;
6239
6397
  }
6240
6398
  const absDir = resolve4(dir);
6241
- const dockerfilePath = join12(absDir, "Dockerfile");
6242
- if (!existsSync9(dockerfilePath)) {
6399
+ const dockerfilePath = join13(absDir, "Dockerfile");
6400
+ if (!existsSync10(dockerfilePath)) {
6243
6401
  throw new CLIError(
6244
6402
  `No Dockerfile at ${dockerfilePath}.
6245
6403
  Either:
@@ -6289,7 +6447,8 @@ function registerComputeDeployCommand(computeCmd2) {
6289
6447
  imageLabel,
6290
6448
  token: tokenJson.token,
6291
6449
  region: opts.region,
6292
- port
6450
+ port,
6451
+ protocol: opts.protocol === "tcp" ? "tcp" : "http"
6293
6452
  }));
6294
6453
  } catch (buildErr) {
6295
6454
  if (!existing) {
@@ -6317,6 +6476,7 @@ function registerComputeDeployCommand(computeCmd2) {
6317
6476
  region: opts.region
6318
6477
  };
6319
6478
  if (envVars) updateBody.envVars = envVars;
6479
+ if (opts.protocol === "tcp") updateBody.protocol = "tcp";
6320
6480
  const finalRes = await ossFetch(
6321
6481
  `/api/compute/services/${encodeURIComponent(serviceId)}`,
6322
6482
  { method: "PATCH", body: JSON.stringify(updateBody) }
@@ -6327,7 +6487,14 @@ function registerComputeDeployCommand(computeCmd2) {
6327
6487
  } else {
6328
6488
  const verb = existing ? "updated" : "deployed";
6329
6489
  outputSuccess(`Service "${service.name}" ${verb} [${service.status}]`);
6330
- if (service.endpointUrl) console.log(` Endpoint: ${service.endpointUrl}`);
6490
+ if (service.endpointUrl && opts.protocol === "tcp") {
6491
+ const host = String(service.endpointUrl).replace(/^https?:\/\//, "");
6492
+ console.log(` Endpoint: ${host}:${service.port} (connect with <scheme>://${host}:${service.port})`);
6493
+ console.log(` Note: TCP services are reachable from the public internet.`);
6494
+ console.log(` Configure auth on your container (e.g. redis --requirepass <secret>).`);
6495
+ } else if (service.endpointUrl) {
6496
+ console.log(` Endpoint: ${service.endpointUrl}`);
6497
+ }
6331
6498
  if (service.port !== void 0) console.log(` Port: ${service.port} (container must listen on this port)`);
6332
6499
  console.log(` Image: ${imageRef} (built remotely; no local image to clean up)`);
6333
6500
  }
@@ -6474,7 +6641,7 @@ function formatSize2(gb) {
6474
6641
 
6475
6642
  // src/commands/diagnose/index.ts
6476
6643
  import * as os from "os";
6477
- import * as clack15 from "@clack/prompts";
6644
+ import * as clack16 from "@clack/prompts";
6478
6645
 
6479
6646
  // src/commands/diagnose/metrics.ts
6480
6647
  var METRIC_LABELS = {
@@ -7015,10 +7182,10 @@ function registerDiagnoseCommands(diagnoseCmd2) {
7015
7182
  if (question.length === 0 || question.length > 2e3) {
7016
7183
  throw new CLIError("Question must be between 1 and 2000 characters.");
7017
7184
  }
7018
- const s = !json ? clack15.spinner() : null;
7185
+ const s = !json ? clack16.spinner() : null;
7019
7186
  s?.start("Collecting diagnostic data...");
7020
7187
  const data2 = await collectDiagnosticData(projectId, ossMode, apiUrl);
7021
- const cliVersion = "0.1.82";
7188
+ const cliVersion = "0.1.85";
7022
7189
  s?.stop("Data collected");
7023
7190
  if (!json) {
7024
7191
  console.log(`
@@ -7151,9 +7318,9 @@ function registerDiagnoseCommands(diagnoseCmd2) {
7151
7318
  void 0,
7152
7319
  apiUrl
7153
7320
  );
7154
- clack15.log.success("Thanks for your feedback!");
7321
+ clack16.log.success("Thanks for your feedback!");
7155
7322
  } catch {
7156
- clack15.log.warn("Failed to submit rating.");
7323
+ clack16.log.warn("Failed to submit rating.");
7157
7324
  }
7158
7325
  }
7159
7326
  }
@@ -7499,16 +7666,14 @@ function formatRecurring(interval, intervalCount) {
7499
7666
  }
7500
7667
  async function trackPaymentUsage(subcommand, success, properties = {}) {
7501
7668
  try {
7502
- try {
7503
- const config = getProjectConfig();
7504
- if (config) {
7505
- trackPayments(subcommand, config, {
7506
- success,
7507
- ...properties
7508
- });
7509
- }
7510
- } catch {
7669
+ const config = getProjectConfig();
7670
+ if (config) {
7671
+ trackPayments(subcommand, config, {
7672
+ success,
7673
+ ...properties
7674
+ });
7511
7675
  }
7676
+ } catch {
7512
7677
  } finally {
7513
7678
  await shutdownAnalytics();
7514
7679
  }
@@ -8382,7 +8547,7 @@ function registerPaymentsCommands(paymentsCmd2) {
8382
8547
  }
8383
8548
 
8384
8549
  // src/commands/posthog/setup.ts
8385
- import * as clack16 from "@clack/prompts";
8550
+ import * as clack17 from "@clack/prompts";
8386
8551
  import pc3 from "picocolors";
8387
8552
 
8388
8553
  // src/lib/api/posthog.ts
@@ -8567,6 +8732,8 @@ function registerPosthogSetupCommand(program2) {
8567
8732
  }
8568
8733
  } catch (err) {
8569
8734
  handleError(err, json);
8735
+ } finally {
8736
+ await shutdownAnalytics();
8570
8737
  }
8571
8738
  });
8572
8739
  }
@@ -8579,13 +8746,14 @@ async function runSetup(opts) {
8579
8746
  if (!token) {
8580
8747
  throw new AuthError("Not logged in. Run `insforge login` first.");
8581
8748
  }
8749
+ trackPosthog("setup", proj);
8582
8750
  if (!opts.json) {
8583
- clack16.intro("PostHog setup");
8751
+ clack17.intro("PostHog setup");
8584
8752
  outputSuccess(`Linked to InsForge project: ${proj.project_name} (${proj.project_id})`);
8585
8753
  }
8586
8754
  const dashboardConnection = await ensureDashboardConnection(proj.project_id, token, opts);
8587
8755
  if (!opts.json) {
8588
- clack16.note(
8756
+ clack17.note(
8589
8757
  `Run this in your terminal to wire PostHog into your app code:
8590
8758
 
8591
8759
  ${WIZARD_COMMAND}
@@ -8623,13 +8791,13 @@ async function runConnectFlow(projectId, token, authorizeUrl, opts) {
8623
8791
  `);
8624
8792
  process.stderr.write("Your browser should open automatically. If not, copy the URL above.\n");
8625
8793
  } else {
8626
- clack16.log.info("PostHog is not yet connected to your InsForge dashboard.");
8794
+ clack17.log.info("PostHog is not yet connected to your InsForge dashboard.");
8627
8795
  if (opts.skipBrowser) {
8628
- clack16.log.info(`Open this URL to authorize PostHog:
8796
+ clack17.log.info(`Open this URL to authorize PostHog:
8629
8797
  ${pc3.cyan(pc3.underline(authorizeUrl))}`);
8630
8798
  } else {
8631
- clack16.log.info("Opening browser to authorize PostHog...");
8632
- clack16.log.info(`If browser doesn't open, visit:
8799
+ clack17.log.info("Opening browser to authorize PostHog...");
8800
+ clack17.log.info(`If browser doesn't open, visit:
8633
8801
  ${pc3.cyan(pc3.underline(authorizeUrl))}`);
8634
8802
  }
8635
8803
  }
@@ -8640,11 +8808,11 @@ ${pc3.cyan(pc3.underline(authorizeUrl))}`);
8640
8808
  } catch {
8641
8809
  }
8642
8810
  }
8643
- const spinner11 = !opts.json && isInteractive ? clack16.spinner() : null;
8811
+ const spinner11 = !opts.json && isInteractive ? clack17.spinner() : null;
8644
8812
  if (spinner11) {
8645
8813
  spinner11.start("Waiting for InsForge dashboard connection... (timeout: 15 minutes)");
8646
8814
  } else if (!opts.json) {
8647
- clack16.log.info("Waiting for InsForge dashboard connection (up to 15 minutes)...");
8815
+ clack17.log.info("Waiting for InsForge dashboard connection (up to 15 minutes)...");
8648
8816
  }
8649
8817
  try {
8650
8818
  await pollPosthogConnection(
@@ -8668,20 +8836,20 @@ ${pc3.cyan(pc3.underline(authorizeUrl))}`);
8668
8836
  if (spinner11) {
8669
8837
  spinner11.stop("InsForge dashboard connection received.");
8670
8838
  } else if (!opts.json) {
8671
- clack16.log.success("InsForge dashboard connection received.");
8839
+ clack17.log.success("InsForge dashboard connection received.");
8672
8840
  }
8673
8841
  } catch (err) {
8674
8842
  if (spinner11) {
8675
8843
  spinner11.stop("InsForge dashboard connection wait failed.");
8676
8844
  } else if (!opts.json) {
8677
- clack16.log.error("InsForge dashboard connection wait failed.");
8845
+ clack17.log.error("InsForge dashboard connection wait failed.");
8678
8846
  }
8679
8847
  throw err;
8680
8848
  }
8681
8849
  }
8682
8850
 
8683
8851
  // src/commands/config/export.ts
8684
- import { writeFileSync as writeFileSync7, existsSync as existsSync10 } from "fs";
8852
+ import { writeFileSync as writeFileSync8, existsSync as existsSync11 } from "fs";
8685
8853
  import { resolve as resolve5 } from "path";
8686
8854
  import * as p from "@clack/prompts";
8687
8855
  import pc4 from "picocolors";
@@ -9167,7 +9335,7 @@ function registerConfigExportCommand(cfg) {
9167
9335
  projectConfig = getProjectConfig();
9168
9336
  await requireAuth();
9169
9337
  const target = resolve5(process.cwd(), opts.out);
9170
- if (existsSync10(target) && !opts.force) {
9338
+ if (existsSync11(target) && !opts.force) {
9171
9339
  if (json) {
9172
9340
  throw new CLIError(
9173
9341
  `${opts.out} exists. Re-run with --force to overwrite.`,
@@ -9194,7 +9362,7 @@ function registerConfigExportCommand(cfg) {
9194
9362
  const raw = await res.json();
9195
9363
  const { config, skipped } = configFromMetadata(raw);
9196
9364
  const toml = stringifyConfigToml(config);
9197
- writeFileSync7(target, toml, "utf8");
9365
+ writeFileSync8(target, toml, "utf8");
9198
9366
  if (json) {
9199
9367
  console.log(JSON.stringify({ written: target, config, skipped }, null, 2));
9200
9368
  } else {
@@ -9230,7 +9398,7 @@ function registerConfigExportCommand(cfg) {
9230
9398
  }
9231
9399
 
9232
9400
  // src/commands/config/plan.ts
9233
- import { readFileSync as readFileSync8 } from "fs";
9401
+ import { readFileSync as readFileSync9 } from "fs";
9234
9402
  import { resolve as resolve6 } from "path";
9235
9403
  import pc5 from "picocolors";
9236
9404
 
@@ -9538,7 +9706,7 @@ function registerConfigPlanCommand(cfg) {
9538
9706
  projectConfig = getProjectConfig();
9539
9707
  await requireAuth();
9540
9708
  const tomlPath = resolve6(process.cwd(), opts.file);
9541
- const tomlSource = readFileSync8(tomlPath, "utf8");
9709
+ const tomlSource = readFileSync9(tomlPath, "utf8");
9542
9710
  const file = parseConfigToml(tomlSource);
9543
9711
  const res = await ossFetch("/api/metadata");
9544
9712
  const raw = await res.json();
@@ -9582,7 +9750,7 @@ function registerConfigPlanCommand(cfg) {
9582
9750
  }
9583
9751
 
9584
9752
  // src/commands/config/apply.ts
9585
- import { readFileSync as readFileSync9 } from "fs";
9753
+ import { readFileSync as readFileSync10 } from "fs";
9586
9754
  import { resolve as resolve7 } from "path";
9587
9755
  import * as p2 from "@clack/prompts";
9588
9756
  import pc6 from "picocolors";
@@ -9594,7 +9762,7 @@ function registerConfigApplyCommand(cfg) {
9594
9762
  projectConfig = getProjectConfig();
9595
9763
  await requireAuth();
9596
9764
  const tomlPath = resolve7(process.cwd(), opts.file);
9597
- const tomlSource = readFileSync9(tomlPath, "utf8");
9765
+ const tomlSource = readFileSync10(tomlPath, "utf8");
9598
9766
  const file = parseConfigToml(tomlSource);
9599
9767
  const res = await ossFetch("/api/metadata");
9600
9768
  const raw = await res.json();
@@ -9783,9 +9951,9 @@ function registerConfigCommand(program2) {
9783
9951
  }
9784
9952
 
9785
9953
  // src/commands/ai/setup.ts
9786
- import { appendFileSync as appendFileSync2, existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
9787
- import { isAbsolute, join as join13, relative as relative3, resolve as resolve8 } from "path";
9788
- import * as clack17 from "@clack/prompts";
9954
+ import { appendFileSync as appendFileSync2, existsSync as existsSync13, readFileSync as readFileSync12 } from "fs";
9955
+ import { isAbsolute, join as join14, relative as relative3, resolve as resolve8 } from "path";
9956
+ import * as clack18 from "@clack/prompts";
9789
9957
  import pc7 from "picocolors";
9790
9958
 
9791
9959
  // src/lib/api/ai.ts
@@ -9806,7 +9974,7 @@ async function getOpenRouterApiKey() {
9806
9974
  }
9807
9975
 
9808
9976
  // src/lib/env-writer.ts
9809
- import { existsSync as existsSync11, readFileSync as readFileSync10, writeFileSync as writeFileSync8 } from "fs";
9977
+ import { existsSync as existsSync12, readFileSync as readFileSync11, writeFileSync as writeFileSync9 } from "fs";
9810
9978
  var KEY_LINE_RE = (key) => (
9811
9979
  // Match `KEY=...` at the start of a line (allowing leading whitespace).
9812
9980
  // Captures the value side; we only need the value portion to compare.
@@ -9821,8 +9989,8 @@ function stripQuotes(v) {
9821
9989
  return hash >= 0 ? t.slice(0, hash).trimEnd() : t;
9822
9990
  }
9823
9991
  function upsertEnvFile(path6, entries) {
9824
- const exists = existsSync11(path6);
9825
- let content = exists ? readFileSync10(path6, "utf-8") : "";
9992
+ const exists = existsSync12(path6);
9993
+ let content = exists ? readFileSync11(path6, "utf-8") : "";
9826
9994
  const result = { added: [], skipped: [], mismatched: [] };
9827
9995
  const additions = [];
9828
9996
  for (const [key, value] of Object.entries(entries)) {
@@ -9845,7 +10013,7 @@ function upsertEnvFile(path6, entries) {
9845
10013
  content += "\n";
9846
10014
  }
9847
10015
  content += additions.join("\n") + "\n";
9848
- writeFileSync8(path6, content);
10016
+ writeFileSync9(path6, content);
9849
10017
  } else if (!exists) {
9850
10018
  }
9851
10019
  return result;
@@ -9878,10 +10046,10 @@ async function runAiSetup(opts) {
9878
10046
  throw new ProjectNotLinkedError();
9879
10047
  }
9880
10048
  if (!opts.json) {
9881
- clack17.intro("AI setup");
10049
+ clack18.intro("AI setup");
9882
10050
  outputSuccess(`Linked to InsForge project: ${project.project_name} (${project.project_id})`);
9883
10051
  }
9884
- const spinner11 = !opts.json && isInteractive ? clack17.spinner() : null;
10052
+ const spinner11 = !opts.json && isInteractive ? clack18.spinner() : null;
9885
10053
  spinner11?.start("Fetching OpenRouter key...");
9886
10054
  let key;
9887
10055
  try {
@@ -9914,7 +10082,7 @@ async function runAiSetup(opts) {
9914
10082
  outputInfo(pc7.dim(`${envLabel}: ${update.skipped.join(", ")} already set (matching) - left as-is.`));
9915
10083
  }
9916
10084
  for (const m of update.mismatched) {
9917
- clack17.log.warn(
10085
+ clack18.log.warn(
9918
10086
  `${envLabel} already has ${m.key}; left existing value untouched. Remove it or pass --env-file to write elsewhere.`
9919
10087
  );
9920
10088
  }
@@ -9922,7 +10090,7 @@ async function runAiSetup(opts) {
9922
10090
  outputInfo(pc7.dim("Added .env*.local to .gitignore."));
9923
10091
  }
9924
10092
  if (!isLocalEnvFile(envFile)) {
9925
- clack17.log.warn(
10093
+ clack18.log.warn(
9926
10094
  `${envLabel} may be committed unless it is listed in .gitignore. Keep ${OPENROUTER_ENV_KEY} server-only.`
9927
10095
  );
9928
10096
  }
@@ -9930,7 +10098,7 @@ async function runAiSetup(opts) {
9930
10098
  outputInfo("Use this key only from server-side code as process.env.OPENROUTER_API_KEY.");
9931
10099
  outputInfo("For deployment, add OPENROUTER_API_KEY to your hosting provider environment.");
9932
10100
  outputInfo(`Do not rename it to ${pc7.bold("NEXT_PUBLIC_")}, ${pc7.bold("VITE_")}, or ${pc7.bold("PUBLIC_")}.`);
9933
- clack17.outro("Done.");
10101
+ clack18.outro("Done.");
9934
10102
  }
9935
10103
  return {
9936
10104
  envFile: envLabel,
@@ -9960,8 +10128,8 @@ function ensureLocalEnvIgnored(cwd, envFile) {
9960
10128
  if (!relEnvPath || relEnvPath.startsWith("..") || isAbsolute(relEnvPath)) {
9961
10129
  return false;
9962
10130
  }
9963
- const gitignorePath = join13(cwd, ".gitignore");
9964
- const existing = existsSync12(gitignorePath) ? readFileSync11(gitignorePath, "utf-8") : "";
10131
+ const gitignorePath = join14(cwd, ".gitignore");
10132
+ const existing = existsSync13(gitignorePath) ? readFileSync12(gitignorePath, "utf-8") : "";
9965
10133
  const lines = new Set(existing.split(/\r?\n/).map((line) => line.trim()));
9966
10134
  const envBasename = envFile.replace(/\\/g, "/").split("/").pop() ?? envFile;
9967
10135
  if (lines.has(".env*") || lines.has(".env.*") || lines.has(".env*.local") || lines.has(".env.local") && envBasename === ".env.local") {
@@ -9982,7 +10150,7 @@ function registerAiCommands(aiCmd2) {
9982
10150
 
9983
10151
  // src/index.ts
9984
10152
  var __dirname = dirname2(fileURLToPath(import.meta.url));
9985
- var pkg = JSON.parse(readFileSync12(join14(__dirname, "../package.json"), "utf-8"));
10153
+ var pkg = JSON.parse(readFileSync13(join15(__dirname, "../package.json"), "utf-8"));
9986
10154
  var INSFORGE_LOGO = `
9987
10155
  \u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
9988
10156
  \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D
@@ -10093,7 +10261,7 @@ async function showInteractiveMenu() {
10093
10261
  } catch {
10094
10262
  }
10095
10263
  console.log(INSFORGE_LOGO);
10096
- clack18.intro(`InsForge CLI v${pkg.version}`);
10264
+ clack19.intro(`InsForge CLI v${pkg.version}`);
10097
10265
  const options = [];
10098
10266
  if (!isLoggedIn) {
10099
10267
  options.push({ value: "login", label: "Log in to InsForge" });
@@ -10114,7 +10282,7 @@ async function showInteractiveMenu() {
10114
10282
  options
10115
10283
  });
10116
10284
  if (isCancel2(action)) {
10117
- clack18.cancel("Bye!");
10285
+ clack19.cancel("Bye!");
10118
10286
  process.exit(0);
10119
10287
  }
10120
10288
  switch (action) {