@insforge/cli 0.1.70 → 0.1.71

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 readFileSync8 } from "fs";
5
- import { join as join14, dirname as dirname2 } from "path";
4
+ import { readFileSync as readFileSync11 } from "fs";
5
+ import { join as join17, dirname as dirname3 } from "path";
6
6
  import { fileURLToPath } from "url";
7
7
  import { Command } from "commander";
8
- import * as clack16 from "@clack/prompts";
8
+ import * as clack17 from "@clack/prompts";
9
9
 
10
10
  // src/lib/prompts.ts
11
11
  import * as readline from "readline";
@@ -1329,36 +1329,36 @@ function registerBranchCreateCommand(branch) {
1329
1329
  throw new CLIError(`Invalid --mode: ${opts.mode} (must be "full" or "schema-only")`);
1330
1330
  }
1331
1331
  const mode = opts.mode;
1332
- const spinner9 = !json ? clack5.spinner() : null;
1332
+ const spinner10 = !json ? clack5.spinner() : null;
1333
1333
  let ready;
1334
1334
  let provisioned = false;
1335
1335
  try {
1336
- spinner9?.start(`Creating branch '${name}'...`);
1336
+ spinner10?.start(`Creating branch '${name}'...`);
1337
1337
  const created = await createBranchApi(project.project_id, { mode, name }, apiUrl);
1338
1338
  captureEvent(project.project_id, "cli_branch_create", {
1339
1339
  mode,
1340
1340
  parent_project_id: project.project_id
1341
1341
  });
1342
- spinner9?.message(`Branch '${name}' created (appkey: ${created.appkey}). Provisioning...`);
1343
- ready = await pollUntilReady(created.id, apiUrl, spinner9);
1342
+ spinner10?.message(`Branch '${name}' created (appkey: ${created.appkey}). Provisioning...`);
1343
+ ready = await pollUntilReady(created.id, apiUrl, spinner10);
1344
1344
  provisioned = ready.branch_state === "ready";
1345
1345
  if (provisioned && opts.switch) {
1346
- spinner9?.message("Branch ready. Switching context...");
1346
+ spinner10?.message("Branch ready. Switching context...");
1347
1347
  await runBranchSwitch({ name, apiUrl, json, silent: true });
1348
- spinner9?.stop(`Branch '${name}' is ready and active`);
1348
+ spinner10?.stop(`Branch '${name}' is ready and active`);
1349
1349
  } else if (provisioned) {
1350
- spinner9?.stop(`Branch '${name}' is ready`);
1350
+ spinner10?.stop(`Branch '${name}' is ready`);
1351
1351
  } else {
1352
- spinner9?.stop(`Branch '${name}' is in '${ready.branch_state}' state`);
1352
+ spinner10?.stop(`Branch '${name}' is in '${ready.branch_state}' state`);
1353
1353
  }
1354
1354
  } catch (err) {
1355
1355
  if (provisioned) {
1356
- spinner9?.stop(
1356
+ spinner10?.stop(
1357
1357
  `Branch '${name}' is ready, but switching context failed \u2014 run \`insforge branch switch ${name}\` to retry`,
1358
1358
  1
1359
1359
  );
1360
1360
  } else {
1361
- spinner9?.stop(`Branch '${name}' creation failed`, 1);
1361
+ spinner10?.stop(`Branch '${name}' creation failed`, 1);
1362
1362
  }
1363
1363
  throw err;
1364
1364
  }
@@ -1382,7 +1382,7 @@ function registerBranchCreateCommand(branch) {
1382
1382
  }
1383
1383
  });
1384
1384
  }
1385
- async function pollUntilReady(branchId, apiUrl, spinner9) {
1385
+ async function pollUntilReady(branchId, apiUrl, spinner10) {
1386
1386
  const start = Date.now();
1387
1387
  let lastState = "";
1388
1388
  while (Date.now() - start < POLL_TIMEOUT_MS) {
@@ -1391,8 +1391,8 @@ async function pollUntilReady(branchId, apiUrl, spinner9) {
1391
1391
  if (branch2.branch_state === "deleted" || branch2.branch_state === "conflicted") {
1392
1392
  throw new CLIError(`Branch creation failed (state: ${branch2.branch_state})`);
1393
1393
  }
1394
- if (spinner9 && branch2.branch_state !== lastState) {
1395
- spinner9.message(`Provisioning branch (state: ${branch2.branch_state})...`);
1394
+ if (spinner10 && branch2.branch_state !== lastState) {
1395
+ spinner10.message(`Provisioning branch (state: ${branch2.branch_state})...`);
1396
1396
  lastState = branch2.branch_state;
1397
1397
  }
1398
1398
  await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
@@ -2376,8 +2376,8 @@ async function startDirectDeployment(deploymentId, startBody) {
2376
2376
  });
2377
2377
  await response.json();
2378
2378
  }
2379
- async function pollDeployment(deploymentId, spinner9, syncBeforeRead) {
2380
- spinner9?.message("Building and deploying...");
2379
+ async function pollDeployment(deploymentId, spinner10, syncBeforeRead) {
2380
+ spinner10?.message("Building and deploying...");
2381
2381
  const startTime = Date.now();
2382
2382
  let deployment = null;
2383
2383
  while (Date.now() - startTime < POLL_TIMEOUT_MS3) {
@@ -2393,13 +2393,13 @@ async function pollDeployment(deploymentId, spinner9, syncBeforeRead) {
2393
2393
  break;
2394
2394
  }
2395
2395
  if (status === "ERROR" || status === "CANCELED") {
2396
- spinner9?.stop("Deployment failed");
2396
+ spinner10?.stop("Deployment failed");
2397
2397
  throw new CLIError(
2398
2398
  getDeploymentError(deployment.metadata) ?? `Deployment failed with status: ${deployment.status}`
2399
2399
  );
2400
2400
  }
2401
2401
  const elapsed = Math.round((Date.now() - startTime) / 1e3);
2402
- spinner9?.message(`Building and deploying... (${elapsed}s, status: ${deployment.status})`);
2402
+ spinner10?.message(`Building and deploying... (${elapsed}s, status: ${deployment.status})`);
2403
2403
  } catch (err) {
2404
2404
  if (err instanceof CLIError) throw err;
2405
2405
  }
@@ -2409,20 +2409,20 @@ async function pollDeployment(deploymentId, spinner9, syncBeforeRead) {
2409
2409
  return { deploymentId, deployment, isReady, liveUrl };
2410
2410
  }
2411
2411
  async function deployProjectDirect(opts, config) {
2412
- const { sourceDir, startBody = {}, spinner: spinner9 } = opts;
2413
- spinner9?.start("Scanning source files...");
2412
+ const { sourceDir, startBody = {}, spinner: spinner10 } = opts;
2413
+ spinner10?.start("Scanning source files...");
2414
2414
  const localFiles = await collectDeploymentFiles(sourceDir);
2415
2415
  if (localFiles.length === 0) {
2416
2416
  throw new CLIError("No deployable files found in the source directory.");
2417
2417
  }
2418
- spinner9?.message("Creating deployment...");
2418
+ spinner10?.message("Creating deployment...");
2419
2419
  const createResult = await createDirectDeploymentSession(
2420
2420
  config,
2421
2421
  localFiles.map(({ path: relativePath, sha, size }) => ({ path: relativePath, sha, size }))
2422
2422
  );
2423
2423
  const localFileByPath = new Map(localFiles.map((file) => [file.path, file]));
2424
2424
  const pendingFiles = createResult.files.filter((file) => !file.uploadedAt);
2425
- spinner9?.message(`Uploading ${pendingFiles.length} file${pendingFiles.length === 1 ? "" : "s"}...`);
2425
+ spinner10?.message(`Uploading ${pendingFiles.length} file${pendingFiles.length === 1 ? "" : "s"}...`);
2426
2426
  await runWithConcurrency(pendingFiles, DIRECT_UPLOAD_CONCURRENCY, async (manifestFile) => {
2427
2427
  const localFile = localFileByPath.get(manifestFile.path);
2428
2428
  if (!localFile) {
@@ -2433,18 +2433,18 @@ async function deployProjectDirect(opts, config) {
2433
2433
  }
2434
2434
  await uploadDirectDeploymentFile(createResult.id, manifestFile, localFile);
2435
2435
  });
2436
- spinner9?.message("Starting deployment...");
2436
+ spinner10?.message("Starting deployment...");
2437
2437
  await startDirectDeployment(createResult.id, startBody);
2438
- return await pollDeployment(createResult.id, spinner9, !isInsforgeCloudOssHost(config.oss_host));
2438
+ return await pollDeployment(createResult.id, spinner10, !isInsforgeCloudOssHost(config.oss_host));
2439
2439
  }
2440
2440
  async function deployProjectLegacy(opts) {
2441
- const { sourceDir, startBody = {}, spinner: spinner9 } = opts;
2442
- spinner9?.message("Creating deployment...");
2441
+ const { sourceDir, startBody = {}, spinner: spinner10 } = opts;
2442
+ spinner10?.message("Creating deployment...");
2443
2443
  const createRes = await ossFetch("/api/deployments", { method: "POST" });
2444
2444
  const { id: deploymentId, uploadUrl, uploadFields } = await createRes.json();
2445
- spinner9?.message("Compressing source files...");
2445
+ spinner10?.message("Compressing source files...");
2446
2446
  const zipBuffer = await createZipBuffer(sourceDir);
2447
- spinner9?.message("Uploading...");
2447
+ spinner10?.message("Uploading...");
2448
2448
  const formData = new FormData();
2449
2449
  for (const [key, value] of Object.entries(uploadFields)) {
2450
2450
  formData.append(key, value);
@@ -2455,13 +2455,13 @@ async function deployProjectLegacy(opts) {
2455
2455
  const uploadErr = await uploadRes.text();
2456
2456
  throw new CLIError(`Failed to upload: ${uploadErr}`);
2457
2457
  }
2458
- spinner9?.message("Starting deployment...");
2458
+ spinner10?.message("Starting deployment...");
2459
2459
  const startRes = await ossFetch(`/api/deployments/${deploymentId}/start`, {
2460
2460
  method: "POST",
2461
2461
  body: JSON.stringify(startBody)
2462
2462
  });
2463
2463
  await startRes.json();
2464
- return await pollDeployment(deploymentId, spinner9, false);
2464
+ return await pollDeployment(deploymentId, spinner10, false);
2465
2465
  }
2466
2466
  async function deployProject(opts) {
2467
2467
  const config = getProjectConfig();
@@ -2496,7 +2496,7 @@ function registerDeploymentsDeployCommand(deploymentsCmd2) {
2496
2496
  `"${dirName}" is an excluded directory and cannot be used as a deploy source. Please specify your project root or output directory instead.`
2497
2497
  );
2498
2498
  }
2499
- const spinner9 = !json ? clack11.spinner() : null;
2499
+ const spinner10 = !json ? clack11.spinner() : null;
2500
2500
  const startBody = {};
2501
2501
  if (opts.env) {
2502
2502
  try {
@@ -2522,9 +2522,9 @@ function registerDeploymentsDeployCommand(deploymentsCmd2) {
2522
2522
  throw new CLIError("Invalid --meta JSON.");
2523
2523
  }
2524
2524
  }
2525
- const result = await deployProject({ sourceDir, startBody, spinner: spinner9 });
2525
+ const result = await deployProject({ sourceDir, startBody, spinner: spinner10 });
2526
2526
  if (result.isReady) {
2527
- spinner9?.stop("Deployment complete");
2527
+ spinner10?.stop("Deployment complete");
2528
2528
  if (json) {
2529
2529
  outputJson(result.deployment);
2530
2530
  } else {
@@ -2534,7 +2534,7 @@ function registerDeploymentsDeployCommand(deploymentsCmd2) {
2534
2534
  clack11.log.info(`Deployment ID: ${result.deploymentId}`);
2535
2535
  }
2536
2536
  } else {
2537
- spinner9?.stop("Deployment is still building");
2537
+ spinner10?.stop("Deployment is still building");
2538
2538
  if (json) {
2539
2539
  outputJson({
2540
2540
  id: result.deploymentId,
@@ -3100,13 +3100,13 @@ async function downloadGitHubTemplate(templateName, projectConfig, json) {
3100
3100
  // src/commands/projects/link.ts
3101
3101
  var execAsync3 = promisify4(exec3);
3102
3102
  async function runNpmInstall(startMessage = "Installing dependencies...") {
3103
- const spinner9 = clack13.spinner();
3104
- spinner9.start(startMessage);
3103
+ const spinner10 = clack13.spinner();
3104
+ spinner10.start(startMessage);
3105
3105
  try {
3106
3106
  await execAsync3("npm install", { cwd: process.cwd(), maxBuffer: 10 * 1024 * 1024 });
3107
- spinner9.stop("Dependencies installed");
3107
+ spinner10.stop("Dependencies installed");
3108
3108
  } catch (err) {
3109
- spinner9.stop("Failed to install dependencies");
3109
+ spinner10.stop("Failed to install dependencies");
3110
3110
  clack13.log.warn(`npm install failed: ${err.message}`);
3111
3111
  clack13.log.info("Run `npm install` manually to install dependencies.");
3112
3112
  }
@@ -6853,7 +6853,7 @@ function registerDiagnoseCommands(diagnoseCmd2) {
6853
6853
  const s = !json ? clack15.spinner() : null;
6854
6854
  s?.start("Collecting diagnostic data...");
6855
6855
  const data2 = await collectDiagnosticData(projectId, ossMode, apiUrl);
6856
- const cliVersion = "0.1.70";
6856
+ const cliVersion = "0.1.71";
6857
6857
  s?.stop("Data collected");
6858
6858
  if (!json) {
6859
6859
  console.log(`
@@ -8216,9 +8216,739 @@ function registerPaymentsCommands(paymentsCmd2) {
8216
8216
  registerPaymentsHistoryCommand(paymentsCmd2);
8217
8217
  }
8218
8218
 
8219
+ // src/commands/posthog/setup.ts
8220
+ import { existsSync as existsSync13, readFileSync as readFileSync10, writeFileSync as writeFileSync8, mkdirSync as mkdirSync3 } from "fs";
8221
+ import { join as join16, dirname as dirname2 } from "path";
8222
+ import * as clack16 from "@clack/prompts";
8223
+ import pc3 from "picocolors";
8224
+
8225
+ // src/lib/api/posthog.ts
8226
+ var REQUEST_TIMEOUT_MS = 3e4;
8227
+ async function fetchWithTimeout(url, init, callerSignal) {
8228
+ const ac = new AbortController();
8229
+ const timer = setTimeout(() => ac.abort(), REQUEST_TIMEOUT_MS);
8230
+ const onCallerAbort = () => ac.abort();
8231
+ callerSignal?.addEventListener("abort", onCallerAbort);
8232
+ try {
8233
+ return await fetch(url, { ...init, signal: ac.signal });
8234
+ } finally {
8235
+ clearTimeout(timer);
8236
+ callerSignal?.removeEventListener("abort", onCallerAbort);
8237
+ }
8238
+ }
8239
+ async function fetchPosthogConnection(projectId, jwt, apiUrl, signal) {
8240
+ const baseUrl = getPlatformApiUrl(apiUrl);
8241
+ const url = `${baseUrl}/integrations/posthog/v1/connection?project_id=${encodeURIComponent(projectId)}`;
8242
+ let res;
8243
+ try {
8244
+ res = await fetchWithTimeout(
8245
+ url,
8246
+ {
8247
+ method: "GET",
8248
+ headers: {
8249
+ Authorization: `Bearer ${jwt}`,
8250
+ Accept: "application/json"
8251
+ }
8252
+ },
8253
+ signal
8254
+ );
8255
+ } catch (err) {
8256
+ return { kind: "error", message: formatFetchError(err, url) };
8257
+ }
8258
+ if (res.status === 404) {
8259
+ return { kind: "not-connected" };
8260
+ }
8261
+ if (res.status === 403) {
8262
+ const body = await res.json().catch(() => ({}));
8263
+ return {
8264
+ kind: "forbidden",
8265
+ message: body.error ?? "Forbidden \u2014 you may not have access to this project."
8266
+ };
8267
+ }
8268
+ if (!res.ok) {
8269
+ const body = await res.json().catch(() => ({}));
8270
+ return {
8271
+ kind: "error",
8272
+ message: body.error ?? `Request failed: HTTP ${res.status}`,
8273
+ status: res.status
8274
+ };
8275
+ }
8276
+ let data;
8277
+ try {
8278
+ data = await res.json();
8279
+ } catch (err) {
8280
+ return {
8281
+ kind: "error",
8282
+ message: `Could not parse connection response: ${err.message}`
8283
+ };
8284
+ }
8285
+ const conn = data ?? {};
8286
+ if (!conn.apiKey) {
8287
+ return { kind: "not-connected" };
8288
+ }
8289
+ if (conn.status && conn.status !== "active") {
8290
+ return { kind: "not-connected" };
8291
+ }
8292
+ return { kind: "connected", connection: conn };
8293
+ }
8294
+ async function pollPosthogConnection(projectId, jwt, opts, apiUrl) {
8295
+ const start = Date.now();
8296
+ let consecutiveErrors = 0;
8297
+ for (; ; ) {
8298
+ if (opts.signal?.aborted) {
8299
+ throw new CLIError("Connection wait cancelled.");
8300
+ }
8301
+ const elapsed = Date.now() - start;
8302
+ if (elapsed >= opts.timeoutMs) {
8303
+ throw new CLIError(
8304
+ "Timed out waiting for PostHog connection. Re-run `insforge posthog setup` after authorizing."
8305
+ );
8306
+ }
8307
+ opts.onTick?.(elapsed);
8308
+ const result = await fetchPosthogConnection(projectId, jwt, apiUrl, opts.signal);
8309
+ switch (result.kind) {
8310
+ case "connected":
8311
+ return result.connection;
8312
+ case "forbidden":
8313
+ throw new CLIError(`Forbidden: ${result.message}`, 5);
8314
+ case "error":
8315
+ consecutiveErrors += 1;
8316
+ if (consecutiveErrors > opts.maxTransientRetries) {
8317
+ throw new CLIError(
8318
+ `Connection check failed after ${opts.maxTransientRetries} retries: ${result.message}`
8319
+ );
8320
+ }
8321
+ break;
8322
+ case "not-connected":
8323
+ consecutiveErrors = 0;
8324
+ break;
8325
+ }
8326
+ await sleep(opts.intervalMs, opts.signal);
8327
+ }
8328
+ }
8329
+ async function startPosthogCliFlow(projectId, jwt, apiUrl) {
8330
+ const baseUrl = getPlatformApiUrl(apiUrl);
8331
+ const url = `${baseUrl}/integrations/posthog/v1/cli-start?p=${encodeURIComponent(projectId)}`;
8332
+ let res;
8333
+ try {
8334
+ res = await fetchWithTimeout(url, {
8335
+ method: "GET",
8336
+ headers: {
8337
+ Authorization: `Bearer ${jwt}`,
8338
+ Accept: "application/json"
8339
+ }
8340
+ });
8341
+ } catch (err) {
8342
+ throw new CLIError(`Failed to start PostHog connect flow: ${formatFetchError(err, url)}`);
8343
+ }
8344
+ if (!res.ok) {
8345
+ const body = await res.json().catch(() => ({}));
8346
+ const msg = body.error ?? res.statusText ?? `HTTP ${res.status}`;
8347
+ if (res.status === 401) {
8348
+ throw new CLIError(`Not authenticated (HTTP 401): ${msg}. Re-run \`insforge login\`.`);
8349
+ }
8350
+ if (res.status === 403) {
8351
+ throw new CLIError(`Forbidden (HTTP 403): ${msg}`, 5);
8352
+ }
8353
+ if (res.status === 404) {
8354
+ throw new CLIError(
8355
+ `PostHog connect flow unavailable (HTTP 404): ${msg}. Check that the project is linked.`
8356
+ );
8357
+ }
8358
+ throw new CLIError(`PostHog cli-start failed (HTTP ${res.status}): ${msg}`);
8359
+ }
8360
+ const data = await res.json().catch(() => ({}));
8361
+ if (data.type === "connected") {
8362
+ return { type: "connected" };
8363
+ }
8364
+ if (data.type === "authorize" && typeof data.authorizeUrl === "string" && data.authorizeUrl) {
8365
+ return { type: "authorize", authorizeUrl: data.authorizeUrl };
8366
+ }
8367
+ throw new CLIError("PostHog cli-start returned an unexpected response shape.");
8368
+ }
8369
+ function sleep(ms, signal) {
8370
+ return new Promise((resolve5, reject) => {
8371
+ if (signal?.aborted) {
8372
+ reject(new CLIError("Connection wait cancelled."));
8373
+ return;
8374
+ }
8375
+ const timer = setTimeout(() => {
8376
+ signal?.removeEventListener("abort", onAbort);
8377
+ resolve5();
8378
+ }, ms);
8379
+ const onAbort = () => {
8380
+ clearTimeout(timer);
8381
+ reject(new CLIError("Connection wait cancelled."));
8382
+ };
8383
+ signal?.addEventListener("abort", onAbort, { once: true });
8384
+ });
8385
+ }
8386
+
8387
+ // src/lib/framework-detect.ts
8388
+ import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
8389
+ import { join as join14 } from "path";
8390
+ function contextFromCwd(cwd) {
8391
+ let pkg2 = null;
8392
+ const pkgPath = join14(cwd, "package.json");
8393
+ if (existsSync10(pkgPath)) {
8394
+ try {
8395
+ pkg2 = JSON.parse(readFileSync8(pkgPath, "utf-8"));
8396
+ } catch {
8397
+ pkg2 = null;
8398
+ }
8399
+ }
8400
+ return {
8401
+ hasDir: (rel) => existsSync10(join14(cwd, rel)),
8402
+ pkg: pkg2
8403
+ };
8404
+ }
8405
+ function hasDep(pkg2, name) {
8406
+ if (!pkg2) return false;
8407
+ return Boolean(pkg2.dependencies?.[name] ?? pkg2.devDependencies?.[name]);
8408
+ }
8409
+ function detectFramework(ctx) {
8410
+ if (hasDep(ctx.pkg, "next")) {
8411
+ const hasApp = ctx.hasDir("app") || ctx.hasDir("src/app");
8412
+ const hasPages = ctx.hasDir("pages") || ctx.hasDir("src/pages");
8413
+ if (hasApp && !hasPages) return "next-app";
8414
+ if (hasPages && !hasApp) return "next-pages";
8415
+ if (hasApp && hasPages) return "next-app";
8416
+ return "next-app";
8417
+ }
8418
+ if (hasDep(ctx.pkg, "vite") && hasDep(ctx.pkg, "react")) {
8419
+ return "vite-react";
8420
+ }
8421
+ if (hasDep(ctx.pkg, "@sveltejs/kit")) {
8422
+ return "sveltekit";
8423
+ }
8424
+ if (hasDep(ctx.pkg, "astro")) {
8425
+ return "astro";
8426
+ }
8427
+ return null;
8428
+ }
8429
+
8430
+ // src/lib/package-manager.ts
8431
+ import { existsSync as existsSync11 } from "fs";
8432
+ import { join as join15 } from "path";
8433
+ import { exec as exec4 } from "child_process";
8434
+ import { promisify as promisify5 } from "util";
8435
+ var execAsync4 = promisify5(exec4);
8436
+ function detectPackageManager(cwd) {
8437
+ if (existsSync11(join15(cwd, "pnpm-lock.yaml"))) return "pnpm";
8438
+ if (existsSync11(join15(cwd, "yarn.lock"))) return "yarn";
8439
+ if (existsSync11(join15(cwd, "bun.lockb")) || existsSync11(join15(cwd, "bun.lock"))) {
8440
+ return "bun";
8441
+ }
8442
+ return "npm";
8443
+ }
8444
+ function installCommand(pm, pkg2) {
8445
+ switch (pm) {
8446
+ case "pnpm":
8447
+ return `pnpm add ${pkg2}`;
8448
+ case "yarn":
8449
+ return `yarn add ${pkg2}`;
8450
+ case "bun":
8451
+ return `bun add ${pkg2}`;
8452
+ case "npm":
8453
+ default:
8454
+ return `npm install ${pkg2}`;
8455
+ }
8456
+ }
8457
+ function hasPackage(pkg2, name) {
8458
+ if (!pkg2) return false;
8459
+ return Boolean(pkg2.dependencies?.[name] ?? pkg2.devDependencies?.[name]);
8460
+ }
8461
+ async function runInstall(pm, pkgName, cwd) {
8462
+ const cmd = installCommand(pm, pkgName);
8463
+ await execAsync4(cmd, { cwd, maxBuffer: 16 * 1024 * 1024 });
8464
+ }
8465
+
8466
+ // src/lib/env-writer.ts
8467
+ import { existsSync as existsSync12, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "fs";
8468
+ var KEY_LINE_RE = (key) => (
8469
+ // Match `KEY=...` at the start of a line (allowing leading whitespace).
8470
+ // Captures the value side; we only need the value portion to compare.
8471
+ new RegExp(`^\\s*${key.replace(/[$.*+?^()[\\]{}|]/g, "\\$&")}\\s*=\\s*(.*)$`, "m")
8472
+ );
8473
+ function stripQuotes(v) {
8474
+ const t = v.trim();
8475
+ if (t.startsWith('"') && t.endsWith('"') && t.length >= 2 || t.startsWith("'") && t.endsWith("'") && t.length >= 2) {
8476
+ return t.slice(1, -1);
8477
+ }
8478
+ const hash = t.indexOf(" #");
8479
+ return hash >= 0 ? t.slice(0, hash).trimEnd() : t;
8480
+ }
8481
+ function upsertEnvFile(path6, entries) {
8482
+ const exists = existsSync12(path6);
8483
+ let content = exists ? readFileSync9(path6, "utf-8") : "";
8484
+ const result = { added: [], skipped: [], mismatched: [] };
8485
+ const additions = [];
8486
+ for (const [key, value] of Object.entries(entries)) {
8487
+ const re = KEY_LINE_RE(key);
8488
+ const match = content.match(re);
8489
+ if (match) {
8490
+ const existingValue = stripQuotes(match[1] ?? "");
8491
+ if (existingValue === value) {
8492
+ result.skipped.push(key);
8493
+ } else {
8494
+ result.mismatched.push({ key, existingValue, newValue: value });
8495
+ }
8496
+ continue;
8497
+ }
8498
+ additions.push(`${key}=${value}`);
8499
+ result.added.push(key);
8500
+ }
8501
+ if (additions.length > 0) {
8502
+ if (content.length > 0 && !content.endsWith("\n")) {
8503
+ content += "\n";
8504
+ }
8505
+ content += additions.join("\n") + "\n";
8506
+ writeFileSync7(path6, content);
8507
+ } else if (!exists) {
8508
+ }
8509
+ return result;
8510
+ }
8511
+
8512
+ // src/templates/posthog/next-app/posthog-provider.tsx.txt
8513
+ var posthog_provider_tsx_default = "'use client';\n\nimport { useEffect } from 'react';\nimport posthog from 'posthog-js';\n\n// PostHog client-side provider for the Next.js App Router.\n// Initialises posthog-js exactly once on the client; SSR is skipped because\n// `useEffect` only runs in the browser.\nexport function PostHogProvider({ children }: { children: React.ReactNode }) {\n useEffect(() => {\n if (typeof window === 'undefined') return;\n if (posthog.__loaded) return;\n\n const key = process.env.NEXT_PUBLIC_POSTHOG_KEY;\n if (!key) {\n // Fail closed in production: missing env var \u2192 no init, no events.\n // Avoids accidentally firing events without a key in CI/preview builds.\n return;\n }\n\n posthog.init(key, {\n api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST || '{{HOST}}',\n capture_pageview: true,\n capture_pageleave: true,\n });\n }, []);\n\n return <>{children}</>;\n}\n";
8514
+
8515
+ // src/templates/posthog/next-app/layout-snippet.tsx.txt
8516
+ var layout_snippet_tsx_default = `// Wrap your <body> children with <PostHogProvider> in app/layout.tsx:
8517
+ //
8518
+ // import { PostHogProvider } from './posthog-provider';
8519
+ //
8520
+ // export default function RootLayout({ children }: { children: React.ReactNode }) {
8521
+ // return (
8522
+ // <html lang="en">
8523
+ // <body>
8524
+ // <PostHogProvider>{children}</PostHogProvider>
8525
+ // </body>
8526
+ // </html>
8527
+ // );
8528
+ // }
8529
+ `;
8530
+
8531
+ // src/templates/posthog/next-pages/_app.tsx.txt
8532
+ var app_tsx_default = "import type { AppProps } from 'next/app';\nimport { useEffect } from 'react';\nimport posthog from 'posthog-js';\n\nif (typeof window !== 'undefined') {\n const key = process.env.NEXT_PUBLIC_POSTHOG_KEY;\n if (key && !posthog.__loaded) {\n posthog.init(key, {\n api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST || '{{HOST}}',\n capture_pageview: true,\n capture_pageleave: true,\n });\n }\n}\n\nexport default function App({ Component, pageProps }: AppProps) {\n useEffect(() => {\n // Capture pageviews on client-side route changes.\n const handleRouteChange = () => posthog.capture('$pageview');\n if (typeof window !== 'undefined') {\n window.addEventListener('popstate', handleRouteChange);\n return () => window.removeEventListener('popstate', handleRouteChange);\n }\n }, []);\n\n return <Component {...pageProps} />;\n}\n";
8533
+
8534
+ // src/templates/posthog/vite-react/main-snippet.tsx.txt
8535
+ var main_snippet_tsx_default = "// Add this near the top of src/main.tsx, before ReactDOM.createRoot:\nimport posthog from 'posthog-js';\n\nconst posthogKey = import.meta.env.VITE_PUBLIC_POSTHOG_KEY;\nif (posthogKey) {\n posthog.init(posthogKey, {\n api_host: import.meta.env.VITE_PUBLIC_POSTHOG_HOST || '{{HOST}}',\n capture_pageview: true,\n capture_pageleave: true,\n });\n}\n";
8536
+
8537
+ // src/templates/posthog/sveltekit/hooks.client.ts.txt
8538
+ var hooks_client_ts_default = "import posthog from 'posthog-js';\nimport { browser } from '$app/environment';\nimport { PUBLIC_POSTHOG_KEY, PUBLIC_POSTHOG_HOST } from '$env/static/public';\n\n// `hooks.client.ts` only runs in the browser, so we don't need an explicit\n// `typeof window` guard. The `browser` import is included so future edits\n// (e.g. moving init to a non-client hook) don't accidentally fire on the server.\nif (browser && PUBLIC_POSTHOG_KEY) {\n posthog.init(PUBLIC_POSTHOG_KEY, {\n api_host: PUBLIC_POSTHOG_HOST || '{{HOST}}',\n capture_pageview: true,\n capture_pageleave: true,\n });\n}\n\nexport const handleError = ({ error }: { error: unknown }) => {\n posthog.capture('$exception', { error: String(error) });\n};\n";
8539
+
8540
+ // src/templates/posthog/astro/posthog-init.ts.txt
8541
+ var posthog_init_ts_default = "import posthog from 'posthog-js';\n\n// PostHog client init for Astro. This module runs only in the browser bundle\n// (Astro inlines `client:load` / `<script>` imports into client JS). We still\n// guard with `typeof window` because the same file may be transitively\n// imported during SSR \u2014 the guard prevents init from accidentally running on\n// the server during static generation.\nif (typeof window !== 'undefined') {\n const key = import.meta.env.PUBLIC_POSTHOG_KEY;\n if (key) {\n posthog.init(key, {\n api_host: import.meta.env.PUBLIC_POSTHOG_HOST || '{{HOST}}',\n capture_pageview: 'history_change',\n capture_pageleave: true,\n });\n }\n}\n";
8542
+
8543
+ // src/templates/posthog/index.ts
8544
+ var templates = {
8545
+ "next-app": {
8546
+ provider: posthog_provider_tsx_default,
8547
+ layoutSnippet: layout_snippet_tsx_default
8548
+ },
8549
+ "next-pages": {
8550
+ app: app_tsx_default
8551
+ },
8552
+ "vite-react": {
8553
+ mainSnippet: main_snippet_tsx_default
8554
+ },
8555
+ sveltekit: {
8556
+ hooks: hooks_client_ts_default
8557
+ },
8558
+ astro: {
8559
+ init: posthog_init_ts_default
8560
+ }
8561
+ };
8562
+ function renderTemplate(raw, vars) {
8563
+ return raw.replace(/\{\{([A-Z0-9_]+)\}\}/g, (match, key) => {
8564
+ return Object.prototype.hasOwnProperty.call(vars, key) ? vars[key] : match;
8565
+ });
8566
+ }
8567
+
8568
+ // src/commands/posthog/setup.ts
8569
+ var POLL_INTERVAL_MS4 = 2e3;
8570
+ var POLL_TIMEOUT_MS4 = 15 * 60 * 1e3;
8571
+ var MAX_TRANSIENT_RETRIES = 5;
8572
+ function registerPosthogSetupCommand(program2) {
8573
+ program2.command("setup").description("Install the PostHog SDK into the current directory app").option("--framework <name>", "Force framework (next-app|next-pages|vite-react|sveltekit|astro)").option("--skip-install", "Do not run the package manager install step").option("--skip-browser", "Do not auto-open the browser; only print the URL").action(async (opts, cmd) => {
8574
+ const { json, apiUrl } = getRootOpts(cmd);
8575
+ try {
8576
+ const result = await runSetup({
8577
+ json,
8578
+ apiUrl,
8579
+ forceFramework: opts.framework,
8580
+ skipInstall: Boolean(opts.skipInstall),
8581
+ skipBrowser: Boolean(opts.skipBrowser)
8582
+ });
8583
+ if (json) {
8584
+ outputJson({ success: true, ...result });
8585
+ }
8586
+ } catch (err) {
8587
+ handleError(err, json);
8588
+ }
8589
+ });
8590
+ }
8591
+ async function runSetup(opts) {
8592
+ const proj = getProjectConfig();
8593
+ if (!proj || !proj.project_id) {
8594
+ throw new ProjectNotLinkedError();
8595
+ }
8596
+ const token = getAccessToken();
8597
+ if (!token) {
8598
+ throw new AuthError("Not logged in. Run `insforge login` first.");
8599
+ }
8600
+ if (!opts.json) {
8601
+ clack16.intro("PostHog setup");
8602
+ outputSuccess(`Linked to InsForge project: ${proj.project_name} (${proj.project_id})`);
8603
+ }
8604
+ const startResult = await startPosthogCliFlow(proj.project_id, token, opts.apiUrl);
8605
+ let conn;
8606
+ if (startResult.type === "connected") {
8607
+ if (!opts.json) {
8608
+ outputSuccess("PostHog already connected (or auto-provisioned for new user). Continuing...");
8609
+ }
8610
+ const fetchResult = await fetchPosthogConnection(proj.project_id, token, opts.apiUrl);
8611
+ if (fetchResult.kind !== "connected") {
8612
+ throw new CLIError(
8613
+ "cli-start reported connected, but /connection returned not-connected. Try again, or check the dashboard."
8614
+ );
8615
+ }
8616
+ conn = fetchResult.connection;
8617
+ } else {
8618
+ conn = await runConnectFlow(proj.project_id, token, startResult.authorizeUrl, opts);
8619
+ }
8620
+ if (!conn.apiKey) {
8621
+ throw new CLIError(
8622
+ "Connection succeeded but cloud-backend returned no apiKey. Try again or check the dashboard."
8623
+ );
8624
+ }
8625
+ const framework = resolveFramework(opts);
8626
+ if (framework === null) {
8627
+ return reportNoFramework(conn, opts);
8628
+ }
8629
+ if (!opts.json) outputSuccess(`Detected framework: ${frameworkLabel(framework)}`);
8630
+ const cwd = process.cwd();
8631
+ const ctx = contextFromCwd(cwd);
8632
+ const pm = detectPackageManager(cwd);
8633
+ const alreadyInstalled = hasPackage(ctx.pkg, "posthog-js");
8634
+ let installedSdk = false;
8635
+ if (alreadyInstalled) {
8636
+ if (!opts.json) outputInfo(pc3.dim("posthog-js is already installed \u2014 skipping install."));
8637
+ } else if (opts.skipInstall) {
8638
+ if (!opts.json) {
8639
+ outputInfo(pc3.yellow(`Skipping install. Run manually: ${installCommand(pm, "posthog-js")}`));
8640
+ }
8641
+ } else {
8642
+ installedSdk = await installSdk(pm, cwd, opts);
8643
+ }
8644
+ const filesWritten = [];
8645
+ const notes = [];
8646
+ const envResult = writeForFramework(framework, conn, cwd, filesWritten, notes, opts);
8647
+ if (!opts.json) {
8648
+ if (notes.length > 0) {
8649
+ for (const n of notes) clack16.log.info(n);
8650
+ }
8651
+ clack16.outro("Done. Run your dev server to start sending events.");
8652
+ }
8653
+ return {
8654
+ framework,
8655
+ installedSdk,
8656
+ filesWritten,
8657
+ envWritten: envResult,
8658
+ notes
8659
+ };
8660
+ }
8661
+ async function runConnectFlow(projectId, token, authorizeUrl, opts) {
8662
+ if (opts.json) {
8663
+ process.stderr.write(`Authorize PostHog: ${authorizeUrl}
8664
+ `);
8665
+ process.stderr.write("Your browser should open automatically. If not, copy the URL above.\n");
8666
+ } else {
8667
+ clack16.log.info("PostHog is not connected to this project yet.");
8668
+ outputInfo("");
8669
+ outputInfo(`Open this URL to authorize PostHog:
8670
+ ${pc3.cyan(pc3.underline(authorizeUrl))}`);
8671
+ outputInfo("");
8672
+ }
8673
+ if (!opts.skipBrowser) {
8674
+ try {
8675
+ const open = (await import("open")).default;
8676
+ await open(authorizeUrl);
8677
+ } catch {
8678
+ }
8679
+ }
8680
+ const spinner10 = !opts.json && isInteractive ? clack16.spinner() : null;
8681
+ spinner10?.start("Waiting for connection... (timeout: 15 minutes)");
8682
+ try {
8683
+ const conn = await pollPosthogConnection(
8684
+ projectId,
8685
+ token,
8686
+ {
8687
+ intervalMs: POLL_INTERVAL_MS4,
8688
+ timeoutMs: POLL_TIMEOUT_MS4,
8689
+ maxTransientRetries: MAX_TRANSIENT_RETRIES,
8690
+ onTick: (elapsed) => {
8691
+ if (spinner10) {
8692
+ const secs = Math.floor(elapsed / 1e3);
8693
+ const mins = Math.floor(secs / 60);
8694
+ const remaining = `${mins}m ${secs % 60}s elapsed`;
8695
+ spinner10.message(`Waiting for connection... (${remaining})`);
8696
+ }
8697
+ }
8698
+ },
8699
+ opts.apiUrl
8700
+ );
8701
+ spinner10?.stop("Connection received from PostHog.");
8702
+ return conn;
8703
+ } catch (err) {
8704
+ spinner10?.stop("Connection wait failed.");
8705
+ throw err;
8706
+ }
8707
+ }
8708
+ function resolveFramework(opts) {
8709
+ if (opts.forceFramework) {
8710
+ const valid = ["next-app", "next-pages", "vite-react", "sveltekit", "astro"];
8711
+ if (!valid.includes(opts.forceFramework)) {
8712
+ throw new CLIError(
8713
+ `Invalid --framework "${opts.forceFramework}". Valid: ${valid.join(", ")}`
8714
+ );
8715
+ }
8716
+ return opts.forceFramework;
8717
+ }
8718
+ return detectFramework(contextFromCwd(process.cwd()));
8719
+ }
8720
+ async function installSdk(pm, cwd, opts) {
8721
+ const cmd = installCommand(pm, "posthog-js");
8722
+ const spinner10 = !opts.json && isInteractive ? clack16.spinner() : null;
8723
+ spinner10?.start(`Installing posthog-js (${cmd})...`);
8724
+ try {
8725
+ await runInstall(pm, "posthog-js", cwd);
8726
+ spinner10?.stop("Installed posthog-js.");
8727
+ return true;
8728
+ } catch (err) {
8729
+ spinner10?.stop("Install failed.");
8730
+ if (!opts.json) {
8731
+ clack16.log.warn(
8732
+ `Could not run \`${cmd}\` automatically: ${err.message}
8733
+ Run it manually, then re-run \`insforge posthog setup\`.`
8734
+ );
8735
+ }
8736
+ return false;
8737
+ }
8738
+ }
8739
+ function writeForFramework(framework, conn, cwd, filesWritten, notes, opts) {
8740
+ const host = conn.host || "https://us.posthog.com";
8741
+ const phc = conn.apiKey ?? "";
8742
+ switch (framework) {
8743
+ case "next-app":
8744
+ return writeNextApp(cwd, phc, host, filesWritten, notes, opts);
8745
+ case "next-pages":
8746
+ return writeNextPages(cwd, phc, host, filesWritten, notes, opts);
8747
+ case "vite-react":
8748
+ return writeViteReact(cwd, phc, host, filesWritten, notes, opts);
8749
+ case "sveltekit":
8750
+ return writeSveltekit(cwd, phc, host, filesWritten, notes, opts);
8751
+ case "astro":
8752
+ return writeAstro(cwd, phc, host, filesWritten, notes, opts);
8753
+ }
8754
+ }
8755
+ function writeNextApp(cwd, phc, host, filesWritten, notes, opts) {
8756
+ const appDir = existsSync13(join16(cwd, "src/app")) ? "src/app" : "app";
8757
+ const providerPath = join16(cwd, appDir, "posthog-provider.tsx");
8758
+ writeIfMissing(
8759
+ providerPath,
8760
+ renderTemplate(templates["next-app"].provider, { HOST: host }),
8761
+ filesWritten,
8762
+ notes,
8763
+ opts
8764
+ );
8765
+ notes.push(
8766
+ `Add the provider to your ${appDir}/layout.tsx:
8767
+ ${templates["next-app"].layoutSnippet}`
8768
+ );
8769
+ const envFile = ".env.local";
8770
+ return writeEnv(
8771
+ cwd,
8772
+ envFile,
8773
+ {
8774
+ NEXT_PUBLIC_POSTHOG_KEY: phc,
8775
+ NEXT_PUBLIC_POSTHOG_HOST: host
8776
+ },
8777
+ opts
8778
+ );
8779
+ }
8780
+ function writeNextPages(cwd, phc, host, filesWritten, notes, opts) {
8781
+ const pagesDir = existsSync13(join16(cwd, "src/pages")) ? "src/pages" : "pages";
8782
+ const appPath = join16(cwd, pagesDir, "_app.tsx");
8783
+ writeIfMissing(
8784
+ appPath,
8785
+ renderTemplate(templates["next-pages"].app, { HOST: host }),
8786
+ filesWritten,
8787
+ notes,
8788
+ opts,
8789
+ "pages/_app.tsx already exists. Open it and add `posthog.init(...)` near the top \u2014 see PostHog Next.js docs."
8790
+ );
8791
+ const envFile = ".env.local";
8792
+ return writeEnv(
8793
+ cwd,
8794
+ envFile,
8795
+ {
8796
+ NEXT_PUBLIC_POSTHOG_KEY: phc,
8797
+ NEXT_PUBLIC_POSTHOG_HOST: host
8798
+ },
8799
+ opts
8800
+ );
8801
+ }
8802
+ function writeViteReact(cwd, phc, host, _filesWritten, notes, opts) {
8803
+ notes.push(
8804
+ `Add this snippet near the top of src/main.tsx:
8805
+ ${renderTemplate(templates["vite-react"].mainSnippet, { HOST: host })}`
8806
+ );
8807
+ const envFile = ".env";
8808
+ return writeEnv(
8809
+ cwd,
8810
+ envFile,
8811
+ {
8812
+ VITE_PUBLIC_POSTHOG_KEY: phc,
8813
+ VITE_PUBLIC_POSTHOG_HOST: host
8814
+ },
8815
+ opts
8816
+ );
8817
+ }
8818
+ function writeSveltekit(cwd, phc, host, filesWritten, notes, opts) {
8819
+ const hooksPath = join16(cwd, "src/hooks.client.ts");
8820
+ writeIfMissing(
8821
+ hooksPath,
8822
+ renderTemplate(templates.sveltekit.hooks, { HOST: host }),
8823
+ filesWritten,
8824
+ notes,
8825
+ opts,
8826
+ "src/hooks.client.ts already exists. Add `posthog.init(...)` to it \u2014 see PostHog SvelteKit docs."
8827
+ );
8828
+ const envFile = ".env";
8829
+ return writeEnv(
8830
+ cwd,
8831
+ envFile,
8832
+ {
8833
+ PUBLIC_POSTHOG_KEY: phc,
8834
+ PUBLIC_POSTHOG_HOST: host
8835
+ },
8836
+ opts
8837
+ );
8838
+ }
8839
+ function writeAstro(cwd, phc, host, filesWritten, notes, opts) {
8840
+ const initPath = join16(cwd, "src/lib/posthog.ts");
8841
+ writeIfMissing(
8842
+ initPath,
8843
+ renderTemplate(templates.astro.init, { HOST: host }),
8844
+ filesWritten,
8845
+ notes,
8846
+ opts,
8847
+ "src/lib/posthog.ts already exists. Add `posthog.init(...)` per PostHog Astro docs."
8848
+ );
8849
+ notes.push(
8850
+ `Import the init module from your layout to load it on the client:
8851
+ // src/layouts/Layout.astro (inside <head> or <body>)
8852
+ <script>import '../lib/posthog';</script>`
8853
+ );
8854
+ const envFile = ".env";
8855
+ return writeEnv(
8856
+ cwd,
8857
+ envFile,
8858
+ {
8859
+ PUBLIC_POSTHOG_KEY: phc,
8860
+ PUBLIC_POSTHOG_HOST: host
8861
+ },
8862
+ opts
8863
+ );
8864
+ }
8865
+ function writeIfMissing(filePath, contents, filesWritten, notes, opts, conflictNote) {
8866
+ if (existsSync13(filePath)) {
8867
+ const existing = readFileSync10(filePath, "utf-8");
8868
+ if (existing.includes("posthog.init")) {
8869
+ if (!opts.json) {
8870
+ outputInfo(pc3.dim(`${relative3(filePath)} already calls posthog.init \u2014 leaving it alone.`));
8871
+ }
8872
+ return;
8873
+ }
8874
+ if (conflictNote) notes.push(conflictNote);
8875
+ if (!opts.json) {
8876
+ outputInfo(
8877
+ pc3.yellow(
8878
+ `${relative3(filePath)} exists. Skipped writing \u2014 see notes below for manual changes.`
8879
+ )
8880
+ );
8881
+ }
8882
+ return;
8883
+ }
8884
+ mkdirSync3(dirname2(filePath), { recursive: true });
8885
+ writeFileSync8(filePath, contents);
8886
+ filesWritten.push(filePath);
8887
+ if (!opts.json) outputSuccess(`Wrote ${relative3(filePath)}`);
8888
+ }
8889
+ function writeEnv(cwd, envFile, entries, opts) {
8890
+ const path6 = join16(cwd, envFile);
8891
+ const r = upsertEnvFile(path6, entries);
8892
+ if (!opts.json) {
8893
+ if (r.added.length > 0) {
8894
+ outputSuccess(`Wrote ${envFile}: ${r.added.join(", ")}`);
8895
+ }
8896
+ if (r.skipped.length > 0) {
8897
+ outputInfo(
8898
+ pc3.dim(`${envFile}: ${r.skipped.join(", ")} already set (matching) \u2014 left as-is.`)
8899
+ );
8900
+ }
8901
+ for (const m of r.mismatched) {
8902
+ clack16.log.warn(
8903
+ `${envFile} has ${m.key}=${pc3.dim(m.existingValue)}, expected ${m.newValue}. Left existing value untouched.`
8904
+ );
8905
+ }
8906
+ }
8907
+ return {
8908
+ file: envFile,
8909
+ added: r.added,
8910
+ mismatched: r.mismatched.map((m) => m.key)
8911
+ };
8912
+ }
8913
+ function reportNoFramework(conn, opts) {
8914
+ if (!opts.json) {
8915
+ clack16.log.warn("No supported framework detected in this directory.");
8916
+ outputInfo("");
8917
+ outputInfo(`Your PostHog public key: ${pc3.cyan(conn.apiKey ?? "(missing)")}`);
8918
+ outputInfo(`Your PostHog host: ${conn.host ?? "https://us.posthog.com"}`);
8919
+ outputInfo("");
8920
+ outputInfo("See https://posthog.com/docs/libraries to install the SDK manually.");
8921
+ clack16.outro("Done.");
8922
+ }
8923
+ return {
8924
+ framework: null,
8925
+ installedSdk: false,
8926
+ filesWritten: [],
8927
+ envWritten: { file: "", added: [], mismatched: [] },
8928
+ notes: ["No supported framework detected."]
8929
+ };
8930
+ }
8931
+ function frameworkLabel(framework) {
8932
+ switch (framework) {
8933
+ case "next-app":
8934
+ return "Next.js (App Router)";
8935
+ case "next-pages":
8936
+ return "Next.js (Pages Router)";
8937
+ case "vite-react":
8938
+ return "Vite + React";
8939
+ case "sveltekit":
8940
+ return "SvelteKit";
8941
+ case "astro":
8942
+ return "Astro";
8943
+ }
8944
+ }
8945
+ function relative3(p) {
8946
+ return p.replace(process.cwd() + "/", "");
8947
+ }
8948
+
8219
8949
  // src/index.ts
8220
- var __dirname = dirname2(fileURLToPath(import.meta.url));
8221
- var pkg = JSON.parse(readFileSync8(join14(__dirname, "../package.json"), "utf-8"));
8950
+ var __dirname = dirname3(fileURLToPath(import.meta.url));
8951
+ var pkg = JSON.parse(readFileSync11(join17(__dirname, "../package.json"), "utf-8"));
8222
8952
  var INSFORGE_LOGO = `
8223
8953
  \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
8224
8954
  \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
@@ -8300,6 +9030,8 @@ registerComputeDeleteCommand(computeCmd);
8300
9030
  registerComputeStartCommand(computeCmd);
8301
9031
  registerComputeStopCommand(computeCmd);
8302
9032
  registerComputeEventsCommand(computeCmd);
9033
+ var posthogCmd = program.command("posthog").description("Manage PostHog product analytics integration");
9034
+ registerPosthogSetupCommand(posthogCmd);
8303
9035
  var schedulesCmd = program.command("schedules").description("Manage scheduled tasks (cron jobs)");
8304
9036
  registerSchedulesListCommand(schedulesCmd);
8305
9037
  registerSchedulesGetCommand(schedulesCmd);
@@ -8324,7 +9056,7 @@ async function showInteractiveMenu() {
8324
9056
  } catch {
8325
9057
  }
8326
9058
  console.log(INSFORGE_LOGO);
8327
- clack16.intro(`InsForge CLI v${pkg.version}`);
9059
+ clack17.intro(`InsForge CLI v${pkg.version}`);
8328
9060
  const options = [];
8329
9061
  if (!isLoggedIn) {
8330
9062
  options.push({ value: "login", label: "Log in to InsForge" });
@@ -8345,7 +9077,7 @@ async function showInteractiveMenu() {
8345
9077
  options
8346
9078
  });
8347
9079
  if (isCancel2(action)) {
8348
- clack16.cancel("Bye!");
9080
+ clack17.cancel("Bye!");
8349
9081
  process.exit(0);
8350
9082
  }
8351
9083
  switch (action) {