@insforge/cli 0.1.81 → 0.1.84
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/README.md +2 -0
- package/dist/index.js +310 -175
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
4
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
5
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
|
|
8
|
+
import * as clack19 from "@clack/prompts";
|
|
9
9
|
|
|
10
10
|
// src/lib/prompts.ts
|
|
11
11
|
import * as readline from "readline";
|
|
@@ -1215,6 +1215,17 @@ function trackPayments(subcommand, config, properties) {
|
|
|
1215
1215
|
...properties
|
|
1216
1216
|
});
|
|
1217
1217
|
}
|
|
1218
|
+
function trackPosthog(subcommand, config, properties) {
|
|
1219
|
+
captureEvent(config.project_id, "cli_posthog_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
|
+
}
|
|
1218
1229
|
function trackConfig(subcommand, config, properties) {
|
|
1219
1230
|
const distinctId = config?.project_id ?? FAKE_PROJECT_ID;
|
|
1220
1231
|
captureEvent(distinctId, "cli_config_invoked", {
|
|
@@ -1707,15 +1718,91 @@ import { exec as exec3 } from "child_process";
|
|
|
1707
1718
|
import { promisify as promisify4 } from "util";
|
|
1708
1719
|
import * as fs5 from "fs/promises";
|
|
1709
1720
|
import * as path5 from "path";
|
|
1710
|
-
import * as
|
|
1721
|
+
import * as clack14 from "@clack/prompts";
|
|
1711
1722
|
import pc2 from "picocolors";
|
|
1712
1723
|
|
|
1713
1724
|
// src/lib/skills.ts
|
|
1714
1725
|
import { exec } from "child_process";
|
|
1715
|
-
import { existsSync as
|
|
1716
|
-
import { join as
|
|
1726
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, appendFileSync } from "fs";
|
|
1727
|
+
import { join as join3 } from "path";
|
|
1717
1728
|
import { promisify } from "util";
|
|
1729
|
+
import * as clack10 from "@clack/prompts";
|
|
1730
|
+
|
|
1731
|
+
// src/lib/agents-md.ts
|
|
1732
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
1733
|
+
import { join as join2 } from "path";
|
|
1718
1734
|
import * as clack9 from "@clack/prompts";
|
|
1735
|
+
var AGENTS_MD_START = "<!-- INSFORGE:START -->";
|
|
1736
|
+
var AGENTS_MD_END = "<!-- INSFORGE:END -->";
|
|
1737
|
+
function buildInsforgeBlock(config) {
|
|
1738
|
+
const lines = [
|
|
1739
|
+
AGENTS_MD_START,
|
|
1740
|
+
"## InsForge backend",
|
|
1741
|
+
"",
|
|
1742
|
+
"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.",
|
|
1743
|
+
""
|
|
1744
|
+
];
|
|
1745
|
+
if (config?.project_name || config?.oss_host) {
|
|
1746
|
+
const name = config.project_name ? `**${config.project_name}**` : "This project";
|
|
1747
|
+
const host = config.oss_host ? ` (API base \`${config.oss_host}\`)` : "";
|
|
1748
|
+
lines.push(`- **Project:** ${name}${host}`);
|
|
1749
|
+
}
|
|
1750
|
+
lines.push(
|
|
1751
|
+
"- **Skills:** these InsForge skills are installed for supported coding agents. Reach for them before implementing any InsForge feature instead of guessing the API:",
|
|
1752
|
+
" - `insforge`: app code with the `@insforge/sdk` client (database CRUD, auth, storage, edge functions, realtime, AI, email, and Stripe payments).",
|
|
1753
|
+
" - `insforge-cli`: backend and infrastructure via the `insforge` CLI (projects, SQL, migrations, RLS policies, storage buckets, functions, secrets, payment setup, schedules, deploys).",
|
|
1754
|
+
" - `insforge-debug`: diagnosing failures (SDK/HTTP errors, RLS denials, auth and OAuth issues) and running security or performance audits.",
|
|
1755
|
+
" - `insforge-integrations`: wiring external auth providers (Clerk, Auth0, WorkOS, Better Auth, etc.) for JWT-based RLS, or the OKX x402 payment facilitator.",
|
|
1756
|
+
" - `find-skills`: discovering additional skills on demand.",
|
|
1757
|
+
"- **Credentials:** app code reads keys from `.env.local`; the CLI reads `.insforge/project.json`. Never hardcode or commit keys.",
|
|
1758
|
+
"",
|
|
1759
|
+
"Key patterns:",
|
|
1760
|
+
"",
|
|
1761
|
+
"- Database inserts take an array: `insert([{ ... }])`.",
|
|
1762
|
+
"- Reference users with `auth.users(id)`; use `auth.uid()` in RLS policies.",
|
|
1763
|
+
"- For storage uploads, persist both the returned `url` and `key`.",
|
|
1764
|
+
AGENTS_MD_END
|
|
1765
|
+
);
|
|
1766
|
+
return lines.join("\n");
|
|
1767
|
+
}
|
|
1768
|
+
function mergeAgentsMd(existing, config) {
|
|
1769
|
+
const block = buildInsforgeBlock(config);
|
|
1770
|
+
if (existing === null || existing.trim() === "") {
|
|
1771
|
+
return `# AGENTS.md
|
|
1772
|
+
|
|
1773
|
+
${block}
|
|
1774
|
+
`;
|
|
1775
|
+
}
|
|
1776
|
+
const startIdx = existing.indexOf(AGENTS_MD_START);
|
|
1777
|
+
if (startIdx !== -1) {
|
|
1778
|
+
const endMarkerIdx = existing.indexOf(AGENTS_MD_END, startIdx + AGENTS_MD_START.length);
|
|
1779
|
+
let before = existing.slice(0, startIdx);
|
|
1780
|
+
if (before.length > 0 && !before.endsWith("\n")) before += "\n";
|
|
1781
|
+
const after = endMarkerIdx === -1 ? "\n" : existing.slice(endMarkerIdx + AGENTS_MD_END.length);
|
|
1782
|
+
return `${before}${block}${after}`;
|
|
1783
|
+
}
|
|
1784
|
+
return `${existing.replace(/\s+$/, "")}
|
|
1785
|
+
|
|
1786
|
+
${block}
|
|
1787
|
+
`;
|
|
1788
|
+
}
|
|
1789
|
+
function writeLocalAgentsMd(json, opts) {
|
|
1790
|
+
const cwd = opts?.cwd ?? process.cwd();
|
|
1791
|
+
const config = opts?.config !== void 0 ? opts.config : getProjectConfig();
|
|
1792
|
+
const path6 = join2(cwd, "AGENTS.md");
|
|
1793
|
+
const existed = existsSync3(path6);
|
|
1794
|
+
const existing = existed ? readFileSync2(path6, "utf-8") : null;
|
|
1795
|
+
const next = mergeAgentsMd(existing, config);
|
|
1796
|
+
if (existing === next) return;
|
|
1797
|
+
writeFileSync3(path6, next);
|
|
1798
|
+
if (!json) {
|
|
1799
|
+
clack9.log.success(
|
|
1800
|
+
existed ? "Updated AGENTS.md with InsForge guidance." : "Created AGENTS.md with InsForge guidance."
|
|
1801
|
+
);
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
// src/lib/skills.ts
|
|
1719
1806
|
var execAsync = promisify(exec);
|
|
1720
1807
|
var SKILL_INSTALL_TIMEOUT_MS = 6e4;
|
|
1721
1808
|
function describeExecError(err) {
|
|
@@ -1755,8 +1842,8 @@ var GITIGNORE_ENTRIES = [
|
|
|
1755
1842
|
".windsurf"
|
|
1756
1843
|
];
|
|
1757
1844
|
function updateGitignore() {
|
|
1758
|
-
const gitignorePath =
|
|
1759
|
-
const existing =
|
|
1845
|
+
const gitignorePath = join3(process.cwd(), ".gitignore");
|
|
1846
|
+
const existing = existsSync4(gitignorePath) ? readFileSync3(gitignorePath, "utf-8") : "";
|
|
1760
1847
|
const lines = new Set(existing.split("\n").map((l) => l.trim()));
|
|
1761
1848
|
const missing = GITIGNORE_ENTRIES.filter((entry) => !lines.has(entry));
|
|
1762
1849
|
if (!missing.length) return;
|
|
@@ -1772,44 +1859,44 @@ var PROVIDER_SKILLS = {
|
|
|
1772
1859
|
};
|
|
1773
1860
|
async function installSkills(json, authProvider) {
|
|
1774
1861
|
try {
|
|
1775
|
-
if (!json)
|
|
1862
|
+
if (!json) clack10.log.info("Installing InsForge agent skills (global)...");
|
|
1776
1863
|
await execAsync(`npx skills add insforge/agent-skills -g -y ${AGENT_FLAGS}`, {
|
|
1777
1864
|
cwd: process.cwd(),
|
|
1778
1865
|
timeout: SKILL_INSTALL_TIMEOUT_MS
|
|
1779
1866
|
});
|
|
1780
|
-
if (!json)
|
|
1867
|
+
if (!json) clack10.log.success("InsForge agent skills installed.");
|
|
1781
1868
|
} catch (err) {
|
|
1782
1869
|
if (!json) {
|
|
1783
|
-
|
|
1784
|
-
|
|
1870
|
+
clack10.log.warn(`Could not install agent skills: ${describeExecError(err)}`);
|
|
1871
|
+
clack10.log.info("Run `npx skills add insforge/agent-skills` once resolved to see the full output.");
|
|
1785
1872
|
}
|
|
1786
1873
|
}
|
|
1787
1874
|
try {
|
|
1788
|
-
if (!json)
|
|
1875
|
+
if (!json) clack10.log.info("Installing find-skills (global)...");
|
|
1789
1876
|
await execAsync("npx skills add https://github.com/vercel-labs/skills --skill find-skills -g -y", {
|
|
1790
1877
|
cwd: process.cwd(),
|
|
1791
1878
|
timeout: SKILL_INSTALL_TIMEOUT_MS
|
|
1792
1879
|
});
|
|
1793
|
-
if (!json)
|
|
1880
|
+
if (!json) clack10.log.success("find-skills installed.");
|
|
1794
1881
|
} catch (err) {
|
|
1795
1882
|
if (!json) {
|
|
1796
|
-
|
|
1797
|
-
|
|
1883
|
+
clack10.log.warn(`Could not install find-skills: ${describeExecError(err)}`);
|
|
1884
|
+
clack10.log.info("Run `npx skills add https://github.com/vercel-labs/skills --skill find-skills` once resolved.");
|
|
1798
1885
|
}
|
|
1799
1886
|
}
|
|
1800
1887
|
const providerEntry = authProvider ? PROVIDER_SKILLS[authProvider] : void 0;
|
|
1801
1888
|
if (providerEntry) {
|
|
1802
1889
|
try {
|
|
1803
|
-
if (!json)
|
|
1890
|
+
if (!json) clack10.log.info(`Installing ${providerEntry.label} (global)...`);
|
|
1804
1891
|
await execAsync(`npx skills add ${providerEntry.repo} -g -y ${AGENT_FLAGS}`, {
|
|
1805
1892
|
cwd: process.cwd(),
|
|
1806
1893
|
timeout: SKILL_INSTALL_TIMEOUT_MS
|
|
1807
1894
|
});
|
|
1808
|
-
if (!json)
|
|
1895
|
+
if (!json) clack10.log.success(`${providerEntry.label} installed.`);
|
|
1809
1896
|
} catch (err) {
|
|
1810
1897
|
if (!json) {
|
|
1811
|
-
|
|
1812
|
-
|
|
1898
|
+
clack10.log.warn(`Could not install ${providerEntry.label}: ${describeExecError(err)}`);
|
|
1899
|
+
clack10.log.info(`Run \`npx skills add ${providerEntry.repo}\` once resolved to see the full output.`);
|
|
1813
1900
|
}
|
|
1814
1901
|
}
|
|
1815
1902
|
}
|
|
@@ -1817,6 +1904,10 @@ async function installSkills(json, authProvider) {
|
|
|
1817
1904
|
updateGitignore();
|
|
1818
1905
|
} catch {
|
|
1819
1906
|
}
|
|
1907
|
+
try {
|
|
1908
|
+
writeLocalAgentsMd(json);
|
|
1909
|
+
} catch {
|
|
1910
|
+
}
|
|
1820
1911
|
}
|
|
1821
1912
|
async function reportCliUsage(toolName, success, maxRetries = 1, explicitConfig) {
|
|
1822
1913
|
let config = explicitConfig;
|
|
@@ -1866,7 +1957,7 @@ import { tmpdir } from "os";
|
|
|
1866
1957
|
import { execFile } from "child_process";
|
|
1867
1958
|
import { promisify as promisify2 } from "util";
|
|
1868
1959
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
1869
|
-
import * as
|
|
1960
|
+
import * as clack11 from "@clack/prompts";
|
|
1870
1961
|
|
|
1871
1962
|
// src/lib/api/oss.ts
|
|
1872
1963
|
function requireProjectConfig() {
|
|
@@ -2107,7 +2198,7 @@ async function applyAuthProvider(provider, cwd, projectConfig, json) {
|
|
|
2107
2198
|
if (!VALID_AUTH_PROVIDERS.includes(provider)) {
|
|
2108
2199
|
throw new Error(`Unknown auth provider: ${provider}`);
|
|
2109
2200
|
}
|
|
2110
|
-
const fetchSpinner = !json ?
|
|
2201
|
+
const fetchSpinner = !json ? clack11.spinner() : null;
|
|
2111
2202
|
fetchSpinner?.start(`Fetching ${provider} scaffold from templates repo...`);
|
|
2112
2203
|
const { dir: providerDir, cleanup } = await fetchProviderTree(provider);
|
|
2113
2204
|
fetchSpinner?.stop(`${provider} scaffold ready`);
|
|
@@ -2206,15 +2297,15 @@ async function applyAuthProvider(provider, cwd, projectConfig, json) {
|
|
|
2206
2297
|
result.envKeysRefreshed = Array.from(/* @__PURE__ */ new Set([...result.envKeysRefreshed, ...refreshed]));
|
|
2207
2298
|
}
|
|
2208
2299
|
if (!jwtSecret && !json) {
|
|
2209
|
-
|
|
2300
|
+
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
2301
|
}
|
|
2211
2302
|
if (result.envKeysSkipped.length > 0 && !json) {
|
|
2212
|
-
|
|
2303
|
+
clack11.log.warn(
|
|
2213
2304
|
`Kept your existing values for: ${result.envKeysSkipped.join(", ")}. If any of these need the auth-provider's defaults, see .env.example for reference.`
|
|
2214
2305
|
);
|
|
2215
2306
|
}
|
|
2216
2307
|
if (result.envKeysRefreshed.length > 0 && !json) {
|
|
2217
|
-
|
|
2308
|
+
clack11.log.info(
|
|
2218
2309
|
`Refreshed stale platform defaults: ${result.envKeysRefreshed.join(", ")}. Your value matched the manifest's default, so we replaced it with the live one.`
|
|
2219
2310
|
);
|
|
2220
2311
|
}
|
|
@@ -2230,7 +2321,7 @@ import { tmpdir as tmpdir2 } from "os";
|
|
|
2230
2321
|
import { promisify as promisify3 } from "util";
|
|
2231
2322
|
import * as fs4 from "fs/promises";
|
|
2232
2323
|
import * as path4 from "path";
|
|
2233
|
-
import * as
|
|
2324
|
+
import * as clack13 from "@clack/prompts";
|
|
2234
2325
|
|
|
2235
2326
|
// src/lib/env.ts
|
|
2236
2327
|
import * as fs2 from "fs/promises";
|
|
@@ -2265,7 +2356,7 @@ import * as path3 from "path";
|
|
|
2265
2356
|
import * as fs3 from "fs/promises";
|
|
2266
2357
|
import { createReadStream } from "fs";
|
|
2267
2358
|
import { createHash as createHash2 } from "crypto";
|
|
2268
|
-
import * as
|
|
2359
|
+
import * as clack12 from "@clack/prompts";
|
|
2269
2360
|
import archiver from "archiver";
|
|
2270
2361
|
var POLL_INTERVAL_MS3 = 5e3;
|
|
2271
2362
|
var POLL_TIMEOUT_MS3 = 3e5;
|
|
@@ -2565,7 +2656,7 @@ function registerDeploymentsDeployCommand(deploymentsCmd2) {
|
|
|
2565
2656
|
`"${dirName}" is an excluded directory and cannot be used as a deploy source. Please specify your project root or output directory instead.`
|
|
2566
2657
|
);
|
|
2567
2658
|
}
|
|
2568
|
-
const spinner11 = !json ?
|
|
2659
|
+
const spinner11 = !json ? clack12.spinner() : null;
|
|
2569
2660
|
const startBody = {};
|
|
2570
2661
|
if (opts.env) {
|
|
2571
2662
|
try {
|
|
@@ -2598,9 +2689,9 @@ function registerDeploymentsDeployCommand(deploymentsCmd2) {
|
|
|
2598
2689
|
outputJson(result.deployment);
|
|
2599
2690
|
} else {
|
|
2600
2691
|
if (result.liveUrl) {
|
|
2601
|
-
|
|
2692
|
+
clack12.log.success(`Live at: ${result.liveUrl}`);
|
|
2602
2693
|
}
|
|
2603
|
-
|
|
2694
|
+
clack12.log.info(`Deployment ID: ${result.deploymentId}`);
|
|
2604
2695
|
}
|
|
2605
2696
|
} else {
|
|
2606
2697
|
spinner11?.stop("Deployment is still building");
|
|
@@ -2611,9 +2702,9 @@ function registerDeploymentsDeployCommand(deploymentsCmd2) {
|
|
|
2611
2702
|
timedOut: true
|
|
2612
2703
|
});
|
|
2613
2704
|
} else {
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2705
|
+
clack12.log.info(`Deployment ID: ${result.deploymentId}`);
|
|
2706
|
+
clack12.log.warn("Deployment did not finish within 5 minutes.");
|
|
2707
|
+
clack12.log.info(`Check status with: npx @insforge/cli deployments status ${result.deploymentId}`);
|
|
2617
2708
|
}
|
|
2618
2709
|
}
|
|
2619
2710
|
await reportCliUsage("cli.deployments.deploy", true);
|
|
@@ -2629,6 +2720,7 @@ var execAsync2 = promisify3(exec2);
|
|
|
2629
2720
|
var execFileAsync2 = promisify3(execFile2);
|
|
2630
2721
|
var SAFE_REPO_PATTERN2 = /^(https?:\/\/|git@)[A-Za-z0-9._:/@~+-]+(\.git)?$/;
|
|
2631
2722
|
var SAFE_BRANCH_PATTERN2 = /^[A-Za-z0-9._/-]+$/;
|
|
2723
|
+
var SAFE_MARKETPLACE_SLUG = /^[a-z0-9][a-z0-9-]{0,99}$/;
|
|
2632
2724
|
async function waitForProjectActive(projectId, apiUrl, timeoutMs = 12e4) {
|
|
2633
2725
|
const start = Date.now();
|
|
2634
2726
|
while (Date.now() - start < timeoutMs) {
|
|
@@ -2737,13 +2829,22 @@ async function copyDir(src, dest) {
|
|
|
2737
2829
|
}
|
|
2738
2830
|
}
|
|
2739
2831
|
function registerCreateCommand(program2) {
|
|
2740
|
-
program2.command("create").description("Create a new InsForge project").option("--name <name>", "Project name").option("--org-id <id>", "Organization ID").option("--region <region>", "Deployment region (us-east, us-west, eu-central, ap-southeast)").option("--template <template>", "Template to use: react, nextjs, chatbot, crm, e-commerce, todo, or empty").option("--auth <provider>", "Wire a third-party auth provider into the chosen template (currently: better-auth)").action(async (opts, cmd) => {
|
|
2832
|
+
program2.command("create").description("Create a new InsForge project").option("--name <name>", "Project name").option("--org-id <id>", "Organization ID").option("--region <region>", "Deployment region (us-east, us-west, eu-central, ap-southeast)").option("--template <template>", "Template to use: react, nextjs, chatbot, crm, e-commerce, todo, or empty").option("--marketplace <slug>", "Install a marketplace template by slug (browse: https://insforge.dev/templates)").option("--auth <provider>", "Wire a third-party auth provider into the chosen template (currently: better-auth)").action(async (opts, cmd) => {
|
|
2741
2833
|
const { json, apiUrl } = getRootOpts(cmd);
|
|
2742
2834
|
try {
|
|
2835
|
+
if (opts.marketplace && opts.template) {
|
|
2836
|
+
throw new CLIError("--marketplace and --template are mutually exclusive");
|
|
2837
|
+
}
|
|
2838
|
+
if (opts.marketplace && !SAFE_MARKETPLACE_SLUG.test(opts.marketplace)) {
|
|
2839
|
+
throw new CLIError(
|
|
2840
|
+
`Invalid --marketplace slug "${opts.marketplace}". Slugs must match ${SAFE_MARKETPLACE_SLUG}.
|
|
2841
|
+
Browse available templates: https://insforge.dev/templates`
|
|
2842
|
+
);
|
|
2843
|
+
}
|
|
2743
2844
|
await requireAuth(apiUrl, false);
|
|
2744
2845
|
if (!json) {
|
|
2745
2846
|
await animateBanner();
|
|
2746
|
-
|
|
2847
|
+
clack13.intro("Let's build something great");
|
|
2747
2848
|
}
|
|
2748
2849
|
let orgId = opts.orgId;
|
|
2749
2850
|
if (!orgId) {
|
|
@@ -2753,7 +2854,7 @@ function registerCreateCommand(program2) {
|
|
|
2753
2854
|
}
|
|
2754
2855
|
if (orgs.length === 1) {
|
|
2755
2856
|
orgId = orgs[0].id;
|
|
2756
|
-
if (!json)
|
|
2857
|
+
if (!json) clack13.log.info(`Using organization: ${orgs[0].name}`);
|
|
2757
2858
|
} else {
|
|
2758
2859
|
if (json) {
|
|
2759
2860
|
throw new CLIError("Multiple organizations found. Specify --org-id.");
|
|
@@ -2796,6 +2897,9 @@ function registerCreateCommand(program2) {
|
|
|
2796
2897
|
if (template && !validTemplates.includes(template)) {
|
|
2797
2898
|
throw new CLIError(`Invalid template "${template}". Valid options: ${validTemplates.join(", ")}`);
|
|
2798
2899
|
}
|
|
2900
|
+
if (opts.marketplace) {
|
|
2901
|
+
template = opts.marketplace;
|
|
2902
|
+
}
|
|
2799
2903
|
if (!template) {
|
|
2800
2904
|
if (json) {
|
|
2801
2905
|
template = "empty";
|
|
@@ -2866,7 +2970,7 @@ function registerCreateCommand(program2) {
|
|
|
2866
2970
|
process.chdir(projectDir);
|
|
2867
2971
|
}
|
|
2868
2972
|
let projectLinked = false;
|
|
2869
|
-
const s = !json ?
|
|
2973
|
+
const s = !json ? clack13.spinner() : null;
|
|
2870
2974
|
try {
|
|
2871
2975
|
s?.start("Creating project...");
|
|
2872
2976
|
const project = await createProject(orgId, projectName, opts.region, apiUrl);
|
|
@@ -2886,7 +2990,16 @@ function registerCreateCommand(program2) {
|
|
|
2886
2990
|
projectLinked = true;
|
|
2887
2991
|
s?.stop(`Project "${project.name}" created and linked`);
|
|
2888
2992
|
const githubTemplates = ["chatbot", "crm", "e-commerce", "nextjs", "react", "todo"];
|
|
2889
|
-
if (
|
|
2993
|
+
if (opts.marketplace) {
|
|
2994
|
+
const downloaded = await downloadGitHubTemplate(
|
|
2995
|
+
opts.marketplace,
|
|
2996
|
+
projectConfig,
|
|
2997
|
+
json
|
|
2998
|
+
);
|
|
2999
|
+
if (downloaded) {
|
|
3000
|
+
void reportMarketplaceDownload(opts.marketplace);
|
|
3001
|
+
}
|
|
3002
|
+
} else if (githubTemplates.includes(template)) {
|
|
2890
3003
|
await downloadGitHubTemplate(template, projectConfig, json);
|
|
2891
3004
|
} else if (hasTemplate) {
|
|
2892
3005
|
await downloadTemplate(template, projectConfig, projectName, json, apiUrl);
|
|
@@ -2894,7 +3007,7 @@ function registerCreateCommand(program2) {
|
|
|
2894
3007
|
try {
|
|
2895
3008
|
const anonKey = await getAnonKey();
|
|
2896
3009
|
if (!anonKey) {
|
|
2897
|
-
if (!json)
|
|
3010
|
+
if (!json) clack13.log.warn("Could not retrieve anon key. You can add it to .env.local manually.");
|
|
2898
3011
|
} else {
|
|
2899
3012
|
const envPath = path4.join(process.cwd(), ".env.local");
|
|
2900
3013
|
const envContent = [
|
|
@@ -2905,16 +3018,16 @@ function registerCreateCommand(program2) {
|
|
|
2905
3018
|
].join("\n");
|
|
2906
3019
|
await fs4.writeFile(envPath, envContent, { flag: "wx" });
|
|
2907
3020
|
if (!json) {
|
|
2908
|
-
|
|
3021
|
+
clack13.log.success("Created .env.local with your InsForge credentials");
|
|
2909
3022
|
}
|
|
2910
3023
|
}
|
|
2911
3024
|
} catch (err) {
|
|
2912
3025
|
const error = err;
|
|
2913
3026
|
if (!json) {
|
|
2914
3027
|
if (error.code === "EEXIST") {
|
|
2915
|
-
|
|
3028
|
+
clack13.log.warn(".env.local already exists; skipping InsForge key seeding.");
|
|
2916
3029
|
} else {
|
|
2917
|
-
|
|
3030
|
+
clack13.log.warn(`Failed to create .env.local: ${error.message}`);
|
|
2918
3031
|
}
|
|
2919
3032
|
}
|
|
2920
3033
|
}
|
|
@@ -2923,12 +3036,12 @@ function registerCreateCommand(program2) {
|
|
|
2923
3036
|
try {
|
|
2924
3037
|
const result = await applyAuthProvider(opts.auth, process.cwd(), projectConfig, json);
|
|
2925
3038
|
if (!json) {
|
|
2926
|
-
|
|
3039
|
+
clack13.log.success(`Wired in ${opts.auth}: ${result.written.length} new, ${result.overwritten.length} replaced`);
|
|
2927
3040
|
}
|
|
2928
3041
|
} catch (err) {
|
|
2929
3042
|
const msg = `Failed to apply --auth ${opts.auth}: ${err.message}`;
|
|
2930
3043
|
if (json) console.error(JSON.stringify({ warning: msg }));
|
|
2931
|
-
else
|
|
3044
|
+
else clack13.log.warn(msg);
|
|
2932
3045
|
}
|
|
2933
3046
|
}
|
|
2934
3047
|
await installSkills(json, opts.auth);
|
|
@@ -2936,7 +3049,7 @@ function registerCreateCommand(program2) {
|
|
|
2936
3049
|
await reportCliUsage("cli.create", true, 6);
|
|
2937
3050
|
const templateDownloaded = hasTemplate ? await fs4.stat(path4.join(process.cwd(), "package.json")).catch(() => null) : null;
|
|
2938
3051
|
if (templateDownloaded) {
|
|
2939
|
-
const installSpinner = !json ?
|
|
3052
|
+
const installSpinner = !json ? clack13.spinner() : null;
|
|
2940
3053
|
installSpinner?.start("Installing dependencies...");
|
|
2941
3054
|
try {
|
|
2942
3055
|
await execAsync2("npm install", { cwd: process.cwd(), maxBuffer: 10 * 1024 * 1024 });
|
|
@@ -2944,8 +3057,8 @@ function registerCreateCommand(program2) {
|
|
|
2944
3057
|
} catch (err) {
|
|
2945
3058
|
installSpinner?.stop("Failed to install dependencies");
|
|
2946
3059
|
if (!json) {
|
|
2947
|
-
|
|
2948
|
-
|
|
3060
|
+
clack13.log.warn(`npm install failed: ${err.message}`);
|
|
3061
|
+
clack13.log.info("Run `npm install` manually to install dependencies.");
|
|
2949
3062
|
}
|
|
2950
3063
|
}
|
|
2951
3064
|
}
|
|
@@ -2961,7 +3074,7 @@ function registerCreateCommand(program2) {
|
|
|
2961
3074
|
if (envVars.length > 0) {
|
|
2962
3075
|
startBody.envVars = envVars;
|
|
2963
3076
|
}
|
|
2964
|
-
const deploySpinner =
|
|
3077
|
+
const deploySpinner = clack13.spinner();
|
|
2965
3078
|
const result = await deployProject({
|
|
2966
3079
|
sourceDir: process.cwd(),
|
|
2967
3080
|
startBody,
|
|
@@ -2972,12 +3085,12 @@ function registerCreateCommand(program2) {
|
|
|
2972
3085
|
liveUrl = result.liveUrl;
|
|
2973
3086
|
} else {
|
|
2974
3087
|
deploySpinner.stop("Deployment is still building");
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
3088
|
+
clack13.log.info(`Deployment ID: ${result.deploymentId}`);
|
|
3089
|
+
clack13.log.warn("Deployment did not finish within 2 minutes.");
|
|
3090
|
+
clack13.log.info(`Check status with: npx @insforge/cli deployments status ${result.deploymentId}`);
|
|
2978
3091
|
}
|
|
2979
3092
|
} catch (err) {
|
|
2980
|
-
|
|
3093
|
+
clack13.log.warn(`Deploy failed: ${err.message}`);
|
|
2981
3094
|
}
|
|
2982
3095
|
}
|
|
2983
3096
|
}
|
|
@@ -2994,33 +3107,33 @@ function registerCreateCommand(program2) {
|
|
|
2994
3107
|
}
|
|
2995
3108
|
});
|
|
2996
3109
|
} else {
|
|
2997
|
-
|
|
3110
|
+
clack13.log.step(`Dashboard: ${dashboardUrl}`);
|
|
2998
3111
|
if (liveUrl) {
|
|
2999
|
-
|
|
3112
|
+
clack13.log.success(`Live site: ${liveUrl}`);
|
|
3000
3113
|
}
|
|
3001
3114
|
if (templateDownloaded) {
|
|
3002
3115
|
const steps = [
|
|
3003
3116
|
`cd ${dirName}`,
|
|
3004
3117
|
"npm run dev"
|
|
3005
3118
|
];
|
|
3006
|
-
|
|
3007
|
-
|
|
3119
|
+
clack13.note(steps.join("\n"), "Next steps");
|
|
3120
|
+
clack13.note("Open your coding agent (Claude Code, Codex, Cursor, etc.) to add new features.", "Keep building");
|
|
3008
3121
|
} else if (hasTemplate && !templateDownloaded) {
|
|
3009
|
-
|
|
3122
|
+
clack13.log.warn("Template download failed. You can retry or set up manually.");
|
|
3010
3123
|
} else {
|
|
3011
3124
|
const prompts = [
|
|
3012
3125
|
"Build a todo app with Google OAuth sign-in",
|
|
3013
3126
|
"Build an Instagram clone where users can upload photos, like, and comment",
|
|
3014
3127
|
"Build an AI chatbot with conversation history"
|
|
3015
3128
|
];
|
|
3016
|
-
|
|
3129
|
+
clack13.note(
|
|
3017
3130
|
`Open your coding agent (Claude Code, Codex, Cursor, etc.) and try:
|
|
3018
3131
|
|
|
3019
3132
|
${prompts.map((p3) => `\u2022 "${p3}"`).join("\n")}`,
|
|
3020
3133
|
"Start building"
|
|
3021
3134
|
);
|
|
3022
3135
|
}
|
|
3023
|
-
|
|
3136
|
+
clack13.outro("Done!");
|
|
3024
3137
|
}
|
|
3025
3138
|
} catch (err) {
|
|
3026
3139
|
if (!projectLinked && hasTemplate && projectDir !== originalCwd) {
|
|
@@ -3039,7 +3152,7 @@ ${prompts.map((p3) => `\u2022 "${p3}"`).join("\n")}`,
|
|
|
3039
3152
|
});
|
|
3040
3153
|
}
|
|
3041
3154
|
async function downloadTemplate(framework, projectConfig, projectName, json, _apiUrl) {
|
|
3042
|
-
const s = !json ?
|
|
3155
|
+
const s = !json ? clack13.spinner() : null;
|
|
3043
3156
|
s?.start("Downloading template...");
|
|
3044
3157
|
try {
|
|
3045
3158
|
const anonKey = await getAnonKey();
|
|
@@ -3070,13 +3183,13 @@ async function downloadTemplate(framework, projectConfig, projectName, json, _ap
|
|
|
3070
3183
|
} catch (err) {
|
|
3071
3184
|
s?.stop("Template download failed");
|
|
3072
3185
|
if (!json) {
|
|
3073
|
-
|
|
3074
|
-
|
|
3186
|
+
clack13.log.warn(`Failed to download template: ${err.message}`);
|
|
3187
|
+
clack13.log.info("You can manually set up the template later.");
|
|
3075
3188
|
}
|
|
3076
3189
|
}
|
|
3077
3190
|
}
|
|
3078
3191
|
async function downloadGitHubTemplate(templateName, projectConfig, json) {
|
|
3079
|
-
const s = !json ?
|
|
3192
|
+
const s = !json ? clack13.spinner() : null;
|
|
3080
3193
|
s?.start(`Downloading ${templateName} template...`);
|
|
3081
3194
|
const tempDir = path4.join(tmpdir2(), `insforge-template-${Date.now()}`);
|
|
3082
3195
|
try {
|
|
@@ -3125,7 +3238,7 @@ async function downloadGitHubTemplate(templateName, projectConfig, json) {
|
|
|
3125
3238
|
await fs4.writeFile(envLocalPath, envFinal, { flag: "wx" });
|
|
3126
3239
|
} catch (e) {
|
|
3127
3240
|
if (e.code === "EEXIST") {
|
|
3128
|
-
if (!json)
|
|
3241
|
+
if (!json) clack13.log.warn(".env.local already exists; skipping env seeding.");
|
|
3129
3242
|
} else {
|
|
3130
3243
|
throw e;
|
|
3131
3244
|
}
|
|
@@ -3135,7 +3248,7 @@ async function downloadGitHubTemplate(templateName, projectConfig, json) {
|
|
|
3135
3248
|
const migrationPath = path4.join(cwd, "migrations", "db_init.sql");
|
|
3136
3249
|
const migrationExists = await fs4.stat(migrationPath).catch(() => null);
|
|
3137
3250
|
if (migrationExists) {
|
|
3138
|
-
const dbSpinner = !json ?
|
|
3251
|
+
const dbSpinner = !json ? clack13.spinner() : null;
|
|
3139
3252
|
dbSpinner?.start("Running database migrations...");
|
|
3140
3253
|
try {
|
|
3141
3254
|
const sql = await fs4.readFile(migrationPath, "utf-8");
|
|
@@ -3144,40 +3257,57 @@ async function downloadGitHubTemplate(templateName, projectConfig, json) {
|
|
|
3144
3257
|
} catch (err) {
|
|
3145
3258
|
dbSpinner?.stop("Database migration failed");
|
|
3146
3259
|
if (!json) {
|
|
3147
|
-
|
|
3148
|
-
|
|
3260
|
+
clack13.log.warn(`Migration failed: ${err.message}`);
|
|
3261
|
+
clack13.log.info('You can run the migration manually: npx @insforge/cli db query --unrestricted "$(cat migrations/db_init.sql)"');
|
|
3149
3262
|
} else {
|
|
3150
3263
|
throw err;
|
|
3151
3264
|
}
|
|
3152
3265
|
}
|
|
3153
3266
|
}
|
|
3267
|
+
return true;
|
|
3154
3268
|
} catch (err) {
|
|
3155
3269
|
s?.stop(`${templateName} template download failed`);
|
|
3156
3270
|
const msg = `Failed to download ${templateName} template: ${err.message}`;
|
|
3157
3271
|
if (json) {
|
|
3158
3272
|
console.error(JSON.stringify({ warning: msg }));
|
|
3159
3273
|
} else {
|
|
3160
|
-
|
|
3161
|
-
|
|
3274
|
+
clack13.log.warn(msg);
|
|
3275
|
+
clack13.log.info("You can manually clone from: https://github.com/InsForge/insforge-templates");
|
|
3162
3276
|
}
|
|
3277
|
+
return false;
|
|
3163
3278
|
} finally {
|
|
3164
3279
|
await fs4.rm(tempDir, { recursive: true, force: true }).catch(() => {
|
|
3165
3280
|
});
|
|
3166
3281
|
}
|
|
3167
3282
|
}
|
|
3283
|
+
var MARKETPLACE_REPORT_URL = process.env.INSFORGE_MARKETPLACE_REPORT_URL ?? "https://p8n7m7ci.us-east.insforge.app/functions/report-download";
|
|
3284
|
+
async function reportMarketplaceDownload(slug) {
|
|
3285
|
+
try {
|
|
3286
|
+
const res = await fetch(MARKETPLACE_REPORT_URL, {
|
|
3287
|
+
method: "POST",
|
|
3288
|
+
headers: { "Content-Type": "application/json" },
|
|
3289
|
+
body: JSON.stringify({ slug }),
|
|
3290
|
+
signal: AbortSignal.timeout(5e3)
|
|
3291
|
+
});
|
|
3292
|
+
if (!res.ok) {
|
|
3293
|
+
return;
|
|
3294
|
+
}
|
|
3295
|
+
} catch {
|
|
3296
|
+
}
|
|
3297
|
+
}
|
|
3168
3298
|
|
|
3169
3299
|
// src/commands/projects/link.ts
|
|
3170
3300
|
var execAsync3 = promisify4(exec3);
|
|
3171
3301
|
async function runNpmInstall(startMessage = "Installing dependencies...") {
|
|
3172
|
-
const spinner11 =
|
|
3302
|
+
const spinner11 = clack14.spinner();
|
|
3173
3303
|
spinner11.start(startMessage);
|
|
3174
3304
|
try {
|
|
3175
3305
|
await execAsync3("npm install", { cwd: process.cwd(), maxBuffer: 10 * 1024 * 1024 });
|
|
3176
3306
|
spinner11.stop("Dependencies installed");
|
|
3177
3307
|
} catch (err) {
|
|
3178
3308
|
spinner11.stop("Failed to install dependencies");
|
|
3179
|
-
|
|
3180
|
-
|
|
3309
|
+
clack14.log.warn(`npm install failed: ${err.message}`);
|
|
3310
|
+
clack14.log.info("Run `npm install` manually to install dependencies.");
|
|
3181
3311
|
}
|
|
3182
3312
|
}
|
|
3183
3313
|
async function runNpmSetupIfPresent() {
|
|
@@ -3189,15 +3319,15 @@ async function runNpmSetupIfPresent() {
|
|
|
3189
3319
|
} catch {
|
|
3190
3320
|
}
|
|
3191
3321
|
if (!hasSetup) return;
|
|
3192
|
-
const spinner11 =
|
|
3322
|
+
const spinner11 = clack14.spinner();
|
|
3193
3323
|
spinner11.start("Running setup (schema + migrations)...");
|
|
3194
3324
|
try {
|
|
3195
3325
|
await execAsync3("npm run setup", { cwd: process.cwd(), maxBuffer: 20 * 1024 * 1024 });
|
|
3196
3326
|
spinner11.stop("Setup complete");
|
|
3197
3327
|
} catch (err) {
|
|
3198
3328
|
spinner11.stop("Setup failed");
|
|
3199
|
-
|
|
3200
|
-
|
|
3329
|
+
clack14.log.warn(`npm run setup failed: ${err.message.split("\n")[0]}`);
|
|
3330
|
+
clack14.log.info("Inspect the error, fix DATABASE_URL or network access, then run `npm run setup` manually.");
|
|
3201
3331
|
}
|
|
3202
3332
|
}
|
|
3203
3333
|
function registerProjectLinkCommand(program2) {
|
|
@@ -3220,7 +3350,7 @@ function registerProjectLinkCommand(program2) {
|
|
|
3220
3350
|
if (json) {
|
|
3221
3351
|
outputJson({ success: true, skills_only: true });
|
|
3222
3352
|
} else {
|
|
3223
|
-
|
|
3353
|
+
clack14.note(
|
|
3224
3354
|
`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.`,
|
|
3225
3355
|
"What's next"
|
|
3226
3356
|
);
|
|
@@ -3298,11 +3428,11 @@ function registerProjectLinkCommand(program2) {
|
|
|
3298
3428
|
if (opts.auth) {
|
|
3299
3429
|
try {
|
|
3300
3430
|
const result = await applyAuthProvider(opts.auth, process.cwd(), projectConfig2, json);
|
|
3301
|
-
if (!json)
|
|
3431
|
+
if (!json) clack14.log.success(`Wired in ${opts.auth}: ${result.written.length} new, ${result.overwritten.length} replaced`);
|
|
3302
3432
|
} catch (err) {
|
|
3303
3433
|
const msg = `Failed to apply --auth ${opts.auth}: ${err.message}`;
|
|
3304
3434
|
if (json) console.error(JSON.stringify({ warning: msg }));
|
|
3305
|
-
else
|
|
3435
|
+
else clack14.log.warn(msg);
|
|
3306
3436
|
}
|
|
3307
3437
|
}
|
|
3308
3438
|
if (templateDownloaded && !json) {
|
|
@@ -3328,9 +3458,9 @@ function registerProjectLinkCommand(program2) {
|
|
|
3328
3458
|
`${pc2.bold("1.")} ${runCommand}`,
|
|
3329
3459
|
`${pc2.bold("2.")} Open ${pc2.cyan("Claude Code")} or ${pc2.cyan("Cursor")} and prompt your agent to add more features`
|
|
3330
3460
|
];
|
|
3331
|
-
|
|
3461
|
+
clack14.note(steps.join("\n"), "What's next");
|
|
3332
3462
|
} else {
|
|
3333
|
-
|
|
3463
|
+
clack14.log.warn("Template download failed. You can retry or set up manually.");
|
|
3334
3464
|
}
|
|
3335
3465
|
}
|
|
3336
3466
|
return;
|
|
@@ -3345,7 +3475,7 @@ function registerProjectLinkCommand(program2) {
|
|
|
3345
3475
|
try {
|
|
3346
3476
|
const result = await applyAuthProvider(opts.auth, process.cwd(), projectConfig2, json);
|
|
3347
3477
|
if (!json) {
|
|
3348
|
-
|
|
3478
|
+
clack14.log.success(`Wired in ${opts.auth}: ${result.written.length} new, ${result.overwritten.length} replaced`);
|
|
3349
3479
|
}
|
|
3350
3480
|
if (result.packageJsonPatched && !json) {
|
|
3351
3481
|
await runNpmInstall("Installing new dependencies...");
|
|
@@ -3354,7 +3484,7 @@ function registerProjectLinkCommand(program2) {
|
|
|
3354
3484
|
} catch (err) {
|
|
3355
3485
|
const msg = `Failed to apply --auth ${opts.auth}: ${err.message}`;
|
|
3356
3486
|
if (json) console.error(JSON.stringify({ warning: msg }));
|
|
3357
|
-
else
|
|
3487
|
+
else clack14.log.warn(msg);
|
|
3358
3488
|
}
|
|
3359
3489
|
}
|
|
3360
3490
|
trackCommand("link", "oss-org", { direct: true });
|
|
@@ -3384,7 +3514,7 @@ function registerProjectLinkCommand(program2) {
|
|
|
3384
3514
|
}
|
|
3385
3515
|
if (orgs.length === 1) {
|
|
3386
3516
|
orgId = orgs[0].id;
|
|
3387
|
-
if (!json)
|
|
3517
|
+
if (!json) clack14.log.info(`Using organization: ${orgs[0].name}`);
|
|
3388
3518
|
} else {
|
|
3389
3519
|
if (json) {
|
|
3390
3520
|
throw new CLIError("Multiple organizations found. Specify --org-id.");
|
|
@@ -3495,11 +3625,11 @@ function registerProjectLinkCommand(program2) {
|
|
|
3495
3625
|
if (opts.auth) {
|
|
3496
3626
|
try {
|
|
3497
3627
|
const result = await applyAuthProvider(opts.auth, process.cwd(), projectConfig, json);
|
|
3498
|
-
if (!json)
|
|
3628
|
+
if (!json) clack14.log.success(`Wired in ${opts.auth}: ${result.written.length} new, ${result.overwritten.length} replaced`);
|
|
3499
3629
|
} catch (err) {
|
|
3500
3630
|
const msg = `Failed to apply --auth ${opts.auth}: ${err.message}`;
|
|
3501
3631
|
if (json) console.error(JSON.stringify({ warning: msg }));
|
|
3502
|
-
else
|
|
3632
|
+
else clack14.log.warn(msg);
|
|
3503
3633
|
}
|
|
3504
3634
|
}
|
|
3505
3635
|
if (templateDownloaded && !json) {
|
|
@@ -3512,16 +3642,16 @@ function registerProjectLinkCommand(program2) {
|
|
|
3512
3642
|
await reportCliUsage("cli.link", true, 6, projectConfig);
|
|
3513
3643
|
if (!json) {
|
|
3514
3644
|
const dashboardUrl = `${getFrontendUrl()}/dashboard/project/${project.id}`;
|
|
3515
|
-
|
|
3645
|
+
clack14.log.step(`Dashboard: ${pc2.underline(dashboardUrl)}`);
|
|
3516
3646
|
if (templateDownloaded) {
|
|
3517
3647
|
const runCommand = `${pc2.cyan("cd")} ${pc2.green(dirName)} ${pc2.dim("&&")} ${pc2.cyan("npm run dev")}`;
|
|
3518
3648
|
const steps = [
|
|
3519
3649
|
`${pc2.bold("1.")} ${runCommand}`,
|
|
3520
3650
|
`${pc2.bold("2.")} Open ${pc2.cyan("Claude Code")} or ${pc2.cyan("Cursor")} and prompt your agent to add more features`
|
|
3521
3651
|
];
|
|
3522
|
-
|
|
3652
|
+
clack14.note(steps.join("\n"), "What's next");
|
|
3523
3653
|
} else {
|
|
3524
|
-
|
|
3654
|
+
clack14.log.warn("Template download failed. You can retry or set up manually.");
|
|
3525
3655
|
}
|
|
3526
3656
|
}
|
|
3527
3657
|
} else {
|
|
@@ -3529,7 +3659,7 @@ function registerProjectLinkCommand(program2) {
|
|
|
3529
3659
|
try {
|
|
3530
3660
|
const result = await applyAuthProvider(opts.auth, process.cwd(), projectConfig, json);
|
|
3531
3661
|
if (!json) {
|
|
3532
|
-
|
|
3662
|
+
clack14.log.success(`Wired in ${opts.auth}: ${result.written.length} new, ${result.overwritten.length} replaced`);
|
|
3533
3663
|
}
|
|
3534
3664
|
if (result.packageJsonPatched && !json) {
|
|
3535
3665
|
await runNpmInstall("Installing new dependencies...");
|
|
@@ -3538,20 +3668,20 @@ function registerProjectLinkCommand(program2) {
|
|
|
3538
3668
|
} catch (err) {
|
|
3539
3669
|
const msg = `Failed to apply --auth ${opts.auth}: ${err.message}`;
|
|
3540
3670
|
if (json) console.error(JSON.stringify({ warning: msg }));
|
|
3541
|
-
else
|
|
3671
|
+
else clack14.log.warn(msg);
|
|
3542
3672
|
}
|
|
3543
3673
|
}
|
|
3544
3674
|
await installSkills(json, opts.auth);
|
|
3545
3675
|
await reportCliUsage("cli.link", true, 6, projectConfig);
|
|
3546
3676
|
if (!json) {
|
|
3547
3677
|
const dashboardUrl = `${getFrontendUrl()}/dashboard/project/${project.id}`;
|
|
3548
|
-
|
|
3678
|
+
clack14.log.step(`Dashboard: ${dashboardUrl}`);
|
|
3549
3679
|
const prompts = [
|
|
3550
3680
|
"Build a todo app with Google OAuth sign-in",
|
|
3551
3681
|
"Build an Instagram clone where users can upload photos, like, and comment",
|
|
3552
3682
|
"Build an AI chatbot with conversation history and deploy it to a live URL"
|
|
3553
3683
|
];
|
|
3554
|
-
|
|
3684
|
+
clack14.note(
|
|
3555
3685
|
`Open your coding agent (Claude Code, Codex, Cursor, etc.) and try:
|
|
3556
3686
|
|
|
3557
3687
|
${prompts.map((p3) => `\u2022 "${p3}"`).join("\n")}`,
|
|
@@ -3791,7 +3921,7 @@ function registerDbRpcCommand(dbCmd2) {
|
|
|
3791
3921
|
}
|
|
3792
3922
|
|
|
3793
3923
|
// src/commands/db/export.ts
|
|
3794
|
-
import { writeFileSync as
|
|
3924
|
+
import { writeFileSync as writeFileSync4 } from "fs";
|
|
3795
3925
|
function registerDbExportCommand(dbCmd2) {
|
|
3796
3926
|
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) => {
|
|
3797
3927
|
const { json } = getRootOpts(cmd);
|
|
@@ -3831,7 +3961,7 @@ function registerDbExportCommand(dbCmd2) {
|
|
|
3831
3961
|
return;
|
|
3832
3962
|
}
|
|
3833
3963
|
if (opts.output) {
|
|
3834
|
-
|
|
3964
|
+
writeFileSync4(opts.output, content);
|
|
3835
3965
|
const tableCount = meta?.tables?.length;
|
|
3836
3966
|
const suffix = tableCount ? ` (${tableCount} tables, format: ${meta?.format ?? opts.format})` : "";
|
|
3837
3967
|
outputSuccess(`Exported to ${opts.output}${suffix}`);
|
|
@@ -3845,7 +3975,7 @@ function registerDbExportCommand(dbCmd2) {
|
|
|
3845
3975
|
}
|
|
3846
3976
|
|
|
3847
3977
|
// src/commands/db/import.ts
|
|
3848
|
-
import { readFileSync as
|
|
3978
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
3849
3979
|
import { basename as basename5 } from "path";
|
|
3850
3980
|
function registerDbImportCommand(dbCmd2) {
|
|
3851
3981
|
dbCmd2.command("import <file>").description("Import database from a local SQL file").option("--truncate", "Truncate existing tables before import").action(async (file, opts, cmd) => {
|
|
@@ -3854,7 +3984,7 @@ function registerDbImportCommand(dbCmd2) {
|
|
|
3854
3984
|
await requireAuth();
|
|
3855
3985
|
const config = getProjectConfig();
|
|
3856
3986
|
if (!config) throw new ProjectNotLinkedError();
|
|
3857
|
-
const fileContent =
|
|
3987
|
+
const fileContent = readFileSync4(file);
|
|
3858
3988
|
const fileName = basename5(file);
|
|
3859
3989
|
const formData = new FormData();
|
|
3860
3990
|
formData.append("file", new Blob([fileContent]), fileName);
|
|
@@ -3885,12 +4015,12 @@ function registerDbImportCommand(dbCmd2) {
|
|
|
3885
4015
|
}
|
|
3886
4016
|
|
|
3887
4017
|
// src/commands/db/migrations.ts
|
|
3888
|
-
import { existsSync as
|
|
3889
|
-
import { join as
|
|
4018
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
4019
|
+
import { join as join10 } from "path";
|
|
3890
4020
|
|
|
3891
4021
|
// src/lib/migrations.ts
|
|
3892
|
-
import { existsSync as
|
|
3893
|
-
import { join as
|
|
4022
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync } from "fs";
|
|
4023
|
+
import { join as join9 } from "path";
|
|
3894
4024
|
var MIGRATION_VERSION_REGEX = /^\d{1,64}$/u;
|
|
3895
4025
|
var MIGRATION_FILENAME_REGEX = /^(\d{1,64})_([a-z0-9-]+)\.sql$/u;
|
|
3896
4026
|
function assertValidMigrationVersion(version) {
|
|
@@ -3954,18 +4084,18 @@ function incrementMigrationVersion(version) {
|
|
|
3954
4084
|
return formatMigrationVersion(new Date(nextTimestamp));
|
|
3955
4085
|
}
|
|
3956
4086
|
function getMigrationsDir(cwd = process.cwd()) {
|
|
3957
|
-
return
|
|
4087
|
+
return join9(cwd, "migrations");
|
|
3958
4088
|
}
|
|
3959
4089
|
function ensureMigrationsDir(cwd = process.cwd()) {
|
|
3960
4090
|
const migrationsDir = getMigrationsDir(cwd);
|
|
3961
|
-
if (!
|
|
4091
|
+
if (!existsSync5(migrationsDir)) {
|
|
3962
4092
|
mkdirSync2(migrationsDir, { recursive: true });
|
|
3963
4093
|
}
|
|
3964
4094
|
return migrationsDir;
|
|
3965
4095
|
}
|
|
3966
4096
|
function listLocalMigrationFilenames(cwd = process.cwd()) {
|
|
3967
4097
|
const migrationsDir = getMigrationsDir(cwd);
|
|
3968
|
-
if (!
|
|
4098
|
+
if (!existsSync5(migrationsDir)) {
|
|
3969
4099
|
return [];
|
|
3970
4100
|
}
|
|
3971
4101
|
return readdirSync(migrationsDir).sort((left, right) => left.localeCompare(right));
|
|
@@ -4153,12 +4283,12 @@ function registerDbMigrationsCommand(dbCmd2) {
|
|
|
4153
4283
|
migration.version,
|
|
4154
4284
|
migration.name
|
|
4155
4285
|
);
|
|
4156
|
-
const filePath =
|
|
4157
|
-
if (existingLocalVersions.has(migration.version) ||
|
|
4286
|
+
const filePath = join10(migrationsDir, filename);
|
|
4287
|
+
if (existingLocalVersions.has(migration.version) || existsSync6(filePath)) {
|
|
4158
4288
|
skippedFiles.push(filename);
|
|
4159
4289
|
continue;
|
|
4160
4290
|
}
|
|
4161
|
-
|
|
4291
|
+
writeFileSync5(filePath, formatMigrationSql(migration.statements));
|
|
4162
4292
|
createdFiles.push(filename);
|
|
4163
4293
|
existingLocalVersions.add(migration.version);
|
|
4164
4294
|
}
|
|
@@ -4196,9 +4326,9 @@ function registerDbMigrationsCommand(dbCmd2) {
|
|
|
4196
4326
|
);
|
|
4197
4327
|
const filename = buildMigrationFilename(nextVersion, migrationName);
|
|
4198
4328
|
const migrationsDir = ensureMigrationsDir();
|
|
4199
|
-
const filePath =
|
|
4329
|
+
const filePath = join10(migrationsDir, filename);
|
|
4200
4330
|
try {
|
|
4201
|
-
|
|
4331
|
+
writeFileSync5(filePath, "", { flag: "wx" });
|
|
4202
4332
|
} catch (error) {
|
|
4203
4333
|
if (error.code === "EEXIST") {
|
|
4204
4334
|
throw new CLIError(`Migration file already exists: ${filename}`);
|
|
@@ -4254,11 +4384,11 @@ function registerDbMigrationsCommand(dbCmd2) {
|
|
|
4254
4384
|
`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.`
|
|
4255
4385
|
);
|
|
4256
4386
|
}
|
|
4257
|
-
const filePath =
|
|
4258
|
-
if (!
|
|
4387
|
+
const filePath = join10(getMigrationsDir(), targetMigration.filename);
|
|
4388
|
+
if (!existsSync6(filePath)) {
|
|
4259
4389
|
throw new CLIError(`Local migration file not found: ${targetMigration.filename}`);
|
|
4260
4390
|
}
|
|
4261
|
-
const sql =
|
|
4391
|
+
const sql = readFileSync5(filePath, "utf-8");
|
|
4262
4392
|
if (!sql.trim()) {
|
|
4263
4393
|
throw new CLIError(`Migration file is empty: ${targetMigration.filename}`);
|
|
4264
4394
|
}
|
|
@@ -4320,11 +4450,11 @@ function registerDbMigrationsCommand(dbCmd2) {
|
|
|
4320
4450
|
}
|
|
4321
4451
|
}
|
|
4322
4452
|
for (const migration of migrationsToApply) {
|
|
4323
|
-
const filePath =
|
|
4324
|
-
if (!
|
|
4453
|
+
const filePath = join10(getMigrationsDir(), migration.filename);
|
|
4454
|
+
if (!existsSync6(filePath)) {
|
|
4325
4455
|
throw new CLIError(`Local migration file not found: ${migration.filename}`);
|
|
4326
4456
|
}
|
|
4327
|
-
const sql =
|
|
4457
|
+
const sql = readFileSync5(filePath, "utf-8");
|
|
4328
4458
|
if (!sql.trim()) {
|
|
4329
4459
|
throw new CLIError(`Migration file is empty: ${migration.filename}`);
|
|
4330
4460
|
}
|
|
@@ -4553,21 +4683,23 @@ function registerFunctionsCommands(functionsCmd2) {
|
|
|
4553
4683
|
}
|
|
4554
4684
|
|
|
4555
4685
|
// src/commands/functions/deploy.ts
|
|
4556
|
-
import { readFileSync as
|
|
4557
|
-
|
|
4686
|
+
import { readFileSync as readFileSync6, existsSync as existsSync7 } from "fs";
|
|
4687
|
+
function resolveDeployFilePath(opts) {
|
|
4688
|
+
if (!opts.file) {
|
|
4689
|
+
throw new CLIError("Missing required option: --file <path>");
|
|
4690
|
+
}
|
|
4691
|
+
if (!existsSync7(opts.file)) {
|
|
4692
|
+
throw new CLIError(`Source file not found: ${opts.file}`);
|
|
4693
|
+
}
|
|
4694
|
+
return opts.file;
|
|
4695
|
+
}
|
|
4558
4696
|
function registerFunctionsDeployCommand(functionsCmd2) {
|
|
4559
4697
|
functionsCmd2.command("deploy <slug>").description("Deploy an edge function (create or update)").option("--file <path>", "Path to the function source file").option("--name <name>", "Function display name").option("--description <desc>", "Function description").action(async (slug, opts, cmd) => {
|
|
4560
4698
|
const { json } = getRootOpts(cmd);
|
|
4561
4699
|
try {
|
|
4562
4700
|
await requireAuth();
|
|
4563
|
-
const filePath = opts
|
|
4564
|
-
|
|
4565
|
-
throw new CLIError(
|
|
4566
|
-
`Source file not found: ${filePath}
|
|
4567
|
-
Specify --file <path> or create ${join10("insforge", "functions", slug, "index.ts")}`
|
|
4568
|
-
);
|
|
4569
|
-
}
|
|
4570
|
-
const code = readFileSync5(filePath, "utf-8");
|
|
4701
|
+
const filePath = resolveDeployFilePath(opts);
|
|
4702
|
+
const code = readFileSync6(filePath, "utf-8");
|
|
4571
4703
|
const name = opts.name ?? slug;
|
|
4572
4704
|
const description = opts.description ?? "";
|
|
4573
4705
|
let exists = false;
|
|
@@ -4688,7 +4820,7 @@ function registerFunctionsCodeCommand(functionsCmd2) {
|
|
|
4688
4820
|
}
|
|
4689
4821
|
|
|
4690
4822
|
// src/commands/functions/delete.ts
|
|
4691
|
-
import * as
|
|
4823
|
+
import * as clack15 from "@clack/prompts";
|
|
4692
4824
|
function registerFunctionsDeleteCommand(functionsCmd2) {
|
|
4693
4825
|
functionsCmd2.command("delete <slug>").description("Delete an edge function").action(async (slug, _opts, cmd) => {
|
|
4694
4826
|
const { json, yes } = getRootOpts(cmd);
|
|
@@ -4699,7 +4831,7 @@ function registerFunctionsDeleteCommand(functionsCmd2) {
|
|
|
4699
4831
|
message: `Delete function "${slug}"? This cannot be undone.`
|
|
4700
4832
|
});
|
|
4701
4833
|
if (isCancel2(confirmed) || !confirmed) {
|
|
4702
|
-
|
|
4834
|
+
clack15.log.info("Cancelled.");
|
|
4703
4835
|
return;
|
|
4704
4836
|
}
|
|
4705
4837
|
}
|
|
@@ -4754,7 +4886,7 @@ function registerStorageBucketsCommand(storageCmd2) {
|
|
|
4754
4886
|
}
|
|
4755
4887
|
|
|
4756
4888
|
// src/commands/storage/upload.ts
|
|
4757
|
-
import { readFileSync as
|
|
4889
|
+
import { readFileSync as readFileSync7, existsSync as existsSync8 } from "fs";
|
|
4758
4890
|
import { basename as basename6 } from "path";
|
|
4759
4891
|
function registerStorageUploadCommand(storageCmd2) {
|
|
4760
4892
|
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) => {
|
|
@@ -4763,10 +4895,10 @@ function registerStorageUploadCommand(storageCmd2) {
|
|
|
4763
4895
|
await requireAuth();
|
|
4764
4896
|
const config = getProjectConfig();
|
|
4765
4897
|
if (!config) throw new ProjectNotLinkedError();
|
|
4766
|
-
if (!
|
|
4898
|
+
if (!existsSync8(file)) {
|
|
4767
4899
|
throw new CLIError(`File not found: ${file}`);
|
|
4768
4900
|
}
|
|
4769
|
-
const fileContent =
|
|
4901
|
+
const fileContent = readFileSync7(file);
|
|
4770
4902
|
const objectKey = opts.key ?? basename6(file);
|
|
4771
4903
|
const bucketName = opts.bucket;
|
|
4772
4904
|
const formData = new FormData();
|
|
@@ -4797,7 +4929,7 @@ function registerStorageUploadCommand(storageCmd2) {
|
|
|
4797
4929
|
}
|
|
4798
4930
|
|
|
4799
4931
|
// src/commands/storage/download.ts
|
|
4800
|
-
import { writeFileSync as
|
|
4932
|
+
import { writeFileSync as writeFileSync6 } from "fs";
|
|
4801
4933
|
import { join as join11, basename as basename7 } from "path";
|
|
4802
4934
|
function registerStorageDownloadCommand(storageCmd2) {
|
|
4803
4935
|
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) => {
|
|
@@ -4819,7 +4951,7 @@ function registerStorageDownloadCommand(storageCmd2) {
|
|
|
4819
4951
|
}
|
|
4820
4952
|
const buffer = Buffer.from(await res.arrayBuffer());
|
|
4821
4953
|
const outputPath = opts.output ?? join11(process.cwd(), basename7(objectKey));
|
|
4822
|
-
|
|
4954
|
+
writeFileSync6(outputPath, buffer);
|
|
4823
4955
|
if (json) {
|
|
4824
4956
|
outputJson({ success: true, path: outputPath, size: buffer.length });
|
|
4825
4957
|
} else {
|
|
@@ -5949,16 +6081,16 @@ function registerComputeEventsCommand(computeCmd2) {
|
|
|
5949
6081
|
}
|
|
5950
6082
|
|
|
5951
6083
|
// src/commands/compute/deploy.ts
|
|
5952
|
-
import { existsSync as
|
|
6084
|
+
import { existsSync as existsSync10 } from "fs";
|
|
5953
6085
|
import { join as join13, resolve as resolve4 } from "path";
|
|
5954
6086
|
|
|
5955
6087
|
// src/lib/env-file.ts
|
|
5956
|
-
import { readFileSync as
|
|
6088
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
5957
6089
|
var ENV_KEY_REGEX2 = /^[A-Z_][A-Z0-9_]*$/;
|
|
5958
6090
|
function parseEnvFile(path6) {
|
|
5959
6091
|
let raw;
|
|
5960
6092
|
try {
|
|
5961
|
-
raw =
|
|
6093
|
+
raw = readFileSync8(path6, "utf-8");
|
|
5962
6094
|
} catch (err) {
|
|
5963
6095
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5964
6096
|
throw new CLIError(`Could not read --env-file at ${path6}: ${msg}`);
|
|
@@ -5994,7 +6126,7 @@ function parseEnvFile(path6) {
|
|
|
5994
6126
|
|
|
5995
6127
|
// src/lib/flyctl.ts
|
|
5996
6128
|
import { spawn, spawnSync } from "child_process";
|
|
5997
|
-
import { existsSync as
|
|
6129
|
+
import { existsSync as existsSync9, writeFileSync as writeFileSync7, unlinkSync as unlinkSync3 } from "fs";
|
|
5998
6130
|
import { join as join12 } from "path";
|
|
5999
6131
|
function ensureFlyctlAvailable() {
|
|
6000
6132
|
const r = spawnSync("flyctl", ["version"], {
|
|
@@ -6009,7 +6141,7 @@ function ensureFlyctlAvailable() {
|
|
|
6009
6141
|
}
|
|
6010
6142
|
function ensureFlyTomlStub(opts) {
|
|
6011
6143
|
const path6 = join12(opts.dir, "fly.toml");
|
|
6012
|
-
if (
|
|
6144
|
+
if (existsSync9(path6)) {
|
|
6013
6145
|
return () => {
|
|
6014
6146
|
};
|
|
6015
6147
|
}
|
|
@@ -6026,7 +6158,7 @@ primary_region = "${opts.region}"
|
|
|
6026
6158
|
auto_start_machines = true
|
|
6027
6159
|
min_machines_running = 0
|
|
6028
6160
|
`;
|
|
6029
|
-
|
|
6161
|
+
writeFileSync7(path6, stub, "utf8");
|
|
6030
6162
|
return () => {
|
|
6031
6163
|
try {
|
|
6032
6164
|
unlinkSync3(path6);
|
|
@@ -6197,7 +6329,7 @@ function registerComputeDeployCommand(computeCmd2) {
|
|
|
6197
6329
|
}
|
|
6198
6330
|
const absDir = resolve4(dir);
|
|
6199
6331
|
const dockerfilePath = join13(absDir, "Dockerfile");
|
|
6200
|
-
if (!
|
|
6332
|
+
if (!existsSync10(dockerfilePath)) {
|
|
6201
6333
|
throw new CLIError(
|
|
6202
6334
|
`No Dockerfile at ${dockerfilePath}.
|
|
6203
6335
|
Either:
|
|
@@ -6432,7 +6564,7 @@ function formatSize2(gb) {
|
|
|
6432
6564
|
|
|
6433
6565
|
// src/commands/diagnose/index.ts
|
|
6434
6566
|
import * as os from "os";
|
|
6435
|
-
import * as
|
|
6567
|
+
import * as clack16 from "@clack/prompts";
|
|
6436
6568
|
|
|
6437
6569
|
// src/commands/diagnose/metrics.ts
|
|
6438
6570
|
var METRIC_LABELS = {
|
|
@@ -6973,10 +7105,10 @@ function registerDiagnoseCommands(diagnoseCmd2) {
|
|
|
6973
7105
|
if (question.length === 0 || question.length > 2e3) {
|
|
6974
7106
|
throw new CLIError("Question must be between 1 and 2000 characters.");
|
|
6975
7107
|
}
|
|
6976
|
-
const s = !json ?
|
|
7108
|
+
const s = !json ? clack16.spinner() : null;
|
|
6977
7109
|
s?.start("Collecting diagnostic data...");
|
|
6978
7110
|
const data2 = await collectDiagnosticData(projectId, ossMode, apiUrl);
|
|
6979
|
-
const cliVersion = "0.1.
|
|
7111
|
+
const cliVersion = "0.1.84";
|
|
6980
7112
|
s?.stop("Data collected");
|
|
6981
7113
|
if (!json) {
|
|
6982
7114
|
console.log(`
|
|
@@ -7109,9 +7241,9 @@ function registerDiagnoseCommands(diagnoseCmd2) {
|
|
|
7109
7241
|
void 0,
|
|
7110
7242
|
apiUrl
|
|
7111
7243
|
);
|
|
7112
|
-
|
|
7244
|
+
clack16.log.success("Thanks for your feedback!");
|
|
7113
7245
|
} catch {
|
|
7114
|
-
|
|
7246
|
+
clack16.log.warn("Failed to submit rating.");
|
|
7115
7247
|
}
|
|
7116
7248
|
}
|
|
7117
7249
|
}
|
|
@@ -8340,7 +8472,7 @@ function registerPaymentsCommands(paymentsCmd2) {
|
|
|
8340
8472
|
}
|
|
8341
8473
|
|
|
8342
8474
|
// src/commands/posthog/setup.ts
|
|
8343
|
-
import * as
|
|
8475
|
+
import * as clack17 from "@clack/prompts";
|
|
8344
8476
|
import pc3 from "picocolors";
|
|
8345
8477
|
|
|
8346
8478
|
// src/lib/api/posthog.ts
|
|
@@ -8525,6 +8657,8 @@ function registerPosthogSetupCommand(program2) {
|
|
|
8525
8657
|
}
|
|
8526
8658
|
} catch (err) {
|
|
8527
8659
|
handleError(err, json);
|
|
8660
|
+
} finally {
|
|
8661
|
+
await shutdownAnalytics();
|
|
8528
8662
|
}
|
|
8529
8663
|
});
|
|
8530
8664
|
}
|
|
@@ -8537,13 +8671,14 @@ async function runSetup(opts) {
|
|
|
8537
8671
|
if (!token) {
|
|
8538
8672
|
throw new AuthError("Not logged in. Run `insforge login` first.");
|
|
8539
8673
|
}
|
|
8674
|
+
trackPosthog("setup", proj);
|
|
8540
8675
|
if (!opts.json) {
|
|
8541
|
-
|
|
8676
|
+
clack17.intro("PostHog setup");
|
|
8542
8677
|
outputSuccess(`Linked to InsForge project: ${proj.project_name} (${proj.project_id})`);
|
|
8543
8678
|
}
|
|
8544
8679
|
const dashboardConnection = await ensureDashboardConnection(proj.project_id, token, opts);
|
|
8545
8680
|
if (!opts.json) {
|
|
8546
|
-
|
|
8681
|
+
clack17.note(
|
|
8547
8682
|
`Run this in your terminal to wire PostHog into your app code:
|
|
8548
8683
|
|
|
8549
8684
|
${WIZARD_COMMAND}
|
|
@@ -8581,13 +8716,13 @@ async function runConnectFlow(projectId, token, authorizeUrl, opts) {
|
|
|
8581
8716
|
`);
|
|
8582
8717
|
process.stderr.write("Your browser should open automatically. If not, copy the URL above.\n");
|
|
8583
8718
|
} else {
|
|
8584
|
-
|
|
8719
|
+
clack17.log.info("PostHog is not yet connected to your InsForge dashboard.");
|
|
8585
8720
|
if (opts.skipBrowser) {
|
|
8586
|
-
|
|
8721
|
+
clack17.log.info(`Open this URL to authorize PostHog:
|
|
8587
8722
|
${pc3.cyan(pc3.underline(authorizeUrl))}`);
|
|
8588
8723
|
} else {
|
|
8589
|
-
|
|
8590
|
-
|
|
8724
|
+
clack17.log.info("Opening browser to authorize PostHog...");
|
|
8725
|
+
clack17.log.info(`If browser doesn't open, visit:
|
|
8591
8726
|
${pc3.cyan(pc3.underline(authorizeUrl))}`);
|
|
8592
8727
|
}
|
|
8593
8728
|
}
|
|
@@ -8598,11 +8733,11 @@ ${pc3.cyan(pc3.underline(authorizeUrl))}`);
|
|
|
8598
8733
|
} catch {
|
|
8599
8734
|
}
|
|
8600
8735
|
}
|
|
8601
|
-
const spinner11 = !opts.json && isInteractive ?
|
|
8736
|
+
const spinner11 = !opts.json && isInteractive ? clack17.spinner() : null;
|
|
8602
8737
|
if (spinner11) {
|
|
8603
8738
|
spinner11.start("Waiting for InsForge dashboard connection... (timeout: 15 minutes)");
|
|
8604
8739
|
} else if (!opts.json) {
|
|
8605
|
-
|
|
8740
|
+
clack17.log.info("Waiting for InsForge dashboard connection (up to 15 minutes)...");
|
|
8606
8741
|
}
|
|
8607
8742
|
try {
|
|
8608
8743
|
await pollPosthogConnection(
|
|
@@ -8626,20 +8761,20 @@ ${pc3.cyan(pc3.underline(authorizeUrl))}`);
|
|
|
8626
8761
|
if (spinner11) {
|
|
8627
8762
|
spinner11.stop("InsForge dashboard connection received.");
|
|
8628
8763
|
} else if (!opts.json) {
|
|
8629
|
-
|
|
8764
|
+
clack17.log.success("InsForge dashboard connection received.");
|
|
8630
8765
|
}
|
|
8631
8766
|
} catch (err) {
|
|
8632
8767
|
if (spinner11) {
|
|
8633
8768
|
spinner11.stop("InsForge dashboard connection wait failed.");
|
|
8634
8769
|
} else if (!opts.json) {
|
|
8635
|
-
|
|
8770
|
+
clack17.log.error("InsForge dashboard connection wait failed.");
|
|
8636
8771
|
}
|
|
8637
8772
|
throw err;
|
|
8638
8773
|
}
|
|
8639
8774
|
}
|
|
8640
8775
|
|
|
8641
8776
|
// src/commands/config/export.ts
|
|
8642
|
-
import { writeFileSync as
|
|
8777
|
+
import { writeFileSync as writeFileSync8, existsSync as existsSync11 } from "fs";
|
|
8643
8778
|
import { resolve as resolve5 } from "path";
|
|
8644
8779
|
import * as p from "@clack/prompts";
|
|
8645
8780
|
import pc4 from "picocolors";
|
|
@@ -9125,7 +9260,7 @@ function registerConfigExportCommand(cfg) {
|
|
|
9125
9260
|
projectConfig = getProjectConfig();
|
|
9126
9261
|
await requireAuth();
|
|
9127
9262
|
const target = resolve5(process.cwd(), opts.out);
|
|
9128
|
-
if (
|
|
9263
|
+
if (existsSync11(target) && !opts.force) {
|
|
9129
9264
|
if (json) {
|
|
9130
9265
|
throw new CLIError(
|
|
9131
9266
|
`${opts.out} exists. Re-run with --force to overwrite.`,
|
|
@@ -9152,7 +9287,7 @@ function registerConfigExportCommand(cfg) {
|
|
|
9152
9287
|
const raw = await res.json();
|
|
9153
9288
|
const { config, skipped } = configFromMetadata(raw);
|
|
9154
9289
|
const toml = stringifyConfigToml(config);
|
|
9155
|
-
|
|
9290
|
+
writeFileSync8(target, toml, "utf8");
|
|
9156
9291
|
if (json) {
|
|
9157
9292
|
console.log(JSON.stringify({ written: target, config, skipped }, null, 2));
|
|
9158
9293
|
} else {
|
|
@@ -9188,7 +9323,7 @@ function registerConfigExportCommand(cfg) {
|
|
|
9188
9323
|
}
|
|
9189
9324
|
|
|
9190
9325
|
// src/commands/config/plan.ts
|
|
9191
|
-
import { readFileSync as
|
|
9326
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
9192
9327
|
import { resolve as resolve6 } from "path";
|
|
9193
9328
|
import pc5 from "picocolors";
|
|
9194
9329
|
|
|
@@ -9496,7 +9631,7 @@ function registerConfigPlanCommand(cfg) {
|
|
|
9496
9631
|
projectConfig = getProjectConfig();
|
|
9497
9632
|
await requireAuth();
|
|
9498
9633
|
const tomlPath = resolve6(process.cwd(), opts.file);
|
|
9499
|
-
const tomlSource =
|
|
9634
|
+
const tomlSource = readFileSync9(tomlPath, "utf8");
|
|
9500
9635
|
const file = parseConfigToml(tomlSource);
|
|
9501
9636
|
const res = await ossFetch("/api/metadata");
|
|
9502
9637
|
const raw = await res.json();
|
|
@@ -9540,7 +9675,7 @@ function registerConfigPlanCommand(cfg) {
|
|
|
9540
9675
|
}
|
|
9541
9676
|
|
|
9542
9677
|
// src/commands/config/apply.ts
|
|
9543
|
-
import { readFileSync as
|
|
9678
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
9544
9679
|
import { resolve as resolve7 } from "path";
|
|
9545
9680
|
import * as p2 from "@clack/prompts";
|
|
9546
9681
|
import pc6 from "picocolors";
|
|
@@ -9552,7 +9687,7 @@ function registerConfigApplyCommand(cfg) {
|
|
|
9552
9687
|
projectConfig = getProjectConfig();
|
|
9553
9688
|
await requireAuth();
|
|
9554
9689
|
const tomlPath = resolve7(process.cwd(), opts.file);
|
|
9555
|
-
const tomlSource =
|
|
9690
|
+
const tomlSource = readFileSync10(tomlPath, "utf8");
|
|
9556
9691
|
const file = parseConfigToml(tomlSource);
|
|
9557
9692
|
const res = await ossFetch("/api/metadata");
|
|
9558
9693
|
const raw = await res.json();
|
|
@@ -9741,9 +9876,9 @@ function registerConfigCommand(program2) {
|
|
|
9741
9876
|
}
|
|
9742
9877
|
|
|
9743
9878
|
// src/commands/ai/setup.ts
|
|
9744
|
-
import { appendFileSync as appendFileSync2, existsSync as
|
|
9879
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync13, readFileSync as readFileSync12 } from "fs";
|
|
9745
9880
|
import { isAbsolute, join as join14, relative as relative3, resolve as resolve8 } from "path";
|
|
9746
|
-
import * as
|
|
9881
|
+
import * as clack18 from "@clack/prompts";
|
|
9747
9882
|
import pc7 from "picocolors";
|
|
9748
9883
|
|
|
9749
9884
|
// src/lib/api/ai.ts
|
|
@@ -9764,7 +9899,7 @@ async function getOpenRouterApiKey() {
|
|
|
9764
9899
|
}
|
|
9765
9900
|
|
|
9766
9901
|
// src/lib/env-writer.ts
|
|
9767
|
-
import { existsSync as
|
|
9902
|
+
import { existsSync as existsSync12, readFileSync as readFileSync11, writeFileSync as writeFileSync9 } from "fs";
|
|
9768
9903
|
var KEY_LINE_RE = (key) => (
|
|
9769
9904
|
// Match `KEY=...` at the start of a line (allowing leading whitespace).
|
|
9770
9905
|
// Captures the value side; we only need the value portion to compare.
|
|
@@ -9779,8 +9914,8 @@ function stripQuotes(v) {
|
|
|
9779
9914
|
return hash >= 0 ? t.slice(0, hash).trimEnd() : t;
|
|
9780
9915
|
}
|
|
9781
9916
|
function upsertEnvFile(path6, entries) {
|
|
9782
|
-
const exists =
|
|
9783
|
-
let content = exists ?
|
|
9917
|
+
const exists = existsSync12(path6);
|
|
9918
|
+
let content = exists ? readFileSync11(path6, "utf-8") : "";
|
|
9784
9919
|
const result = { added: [], skipped: [], mismatched: [] };
|
|
9785
9920
|
const additions = [];
|
|
9786
9921
|
for (const [key, value] of Object.entries(entries)) {
|
|
@@ -9803,7 +9938,7 @@ function upsertEnvFile(path6, entries) {
|
|
|
9803
9938
|
content += "\n";
|
|
9804
9939
|
}
|
|
9805
9940
|
content += additions.join("\n") + "\n";
|
|
9806
|
-
|
|
9941
|
+
writeFileSync9(path6, content);
|
|
9807
9942
|
} else if (!exists) {
|
|
9808
9943
|
}
|
|
9809
9944
|
return result;
|
|
@@ -9836,10 +9971,10 @@ async function runAiSetup(opts) {
|
|
|
9836
9971
|
throw new ProjectNotLinkedError();
|
|
9837
9972
|
}
|
|
9838
9973
|
if (!opts.json) {
|
|
9839
|
-
|
|
9974
|
+
clack18.intro("AI setup");
|
|
9840
9975
|
outputSuccess(`Linked to InsForge project: ${project.project_name} (${project.project_id})`);
|
|
9841
9976
|
}
|
|
9842
|
-
const spinner11 = !opts.json && isInteractive ?
|
|
9977
|
+
const spinner11 = !opts.json && isInteractive ? clack18.spinner() : null;
|
|
9843
9978
|
spinner11?.start("Fetching OpenRouter key...");
|
|
9844
9979
|
let key;
|
|
9845
9980
|
try {
|
|
@@ -9872,7 +10007,7 @@ async function runAiSetup(opts) {
|
|
|
9872
10007
|
outputInfo(pc7.dim(`${envLabel}: ${update.skipped.join(", ")} already set (matching) - left as-is.`));
|
|
9873
10008
|
}
|
|
9874
10009
|
for (const m of update.mismatched) {
|
|
9875
|
-
|
|
10010
|
+
clack18.log.warn(
|
|
9876
10011
|
`${envLabel} already has ${m.key}; left existing value untouched. Remove it or pass --env-file to write elsewhere.`
|
|
9877
10012
|
);
|
|
9878
10013
|
}
|
|
@@ -9880,7 +10015,7 @@ async function runAiSetup(opts) {
|
|
|
9880
10015
|
outputInfo(pc7.dim("Added .env*.local to .gitignore."));
|
|
9881
10016
|
}
|
|
9882
10017
|
if (!isLocalEnvFile(envFile)) {
|
|
9883
|
-
|
|
10018
|
+
clack18.log.warn(
|
|
9884
10019
|
`${envLabel} may be committed unless it is listed in .gitignore. Keep ${OPENROUTER_ENV_KEY} server-only.`
|
|
9885
10020
|
);
|
|
9886
10021
|
}
|
|
@@ -9888,7 +10023,7 @@ async function runAiSetup(opts) {
|
|
|
9888
10023
|
outputInfo("Use this key only from server-side code as process.env.OPENROUTER_API_KEY.");
|
|
9889
10024
|
outputInfo("For deployment, add OPENROUTER_API_KEY to your hosting provider environment.");
|
|
9890
10025
|
outputInfo(`Do not rename it to ${pc7.bold("NEXT_PUBLIC_")}, ${pc7.bold("VITE_")}, or ${pc7.bold("PUBLIC_")}.`);
|
|
9891
|
-
|
|
10026
|
+
clack18.outro("Done.");
|
|
9892
10027
|
}
|
|
9893
10028
|
return {
|
|
9894
10029
|
envFile: envLabel,
|
|
@@ -9919,7 +10054,7 @@ function ensureLocalEnvIgnored(cwd, envFile) {
|
|
|
9919
10054
|
return false;
|
|
9920
10055
|
}
|
|
9921
10056
|
const gitignorePath = join14(cwd, ".gitignore");
|
|
9922
|
-
const existing =
|
|
10057
|
+
const existing = existsSync13(gitignorePath) ? readFileSync12(gitignorePath, "utf-8") : "";
|
|
9923
10058
|
const lines = new Set(existing.split(/\r?\n/).map((line) => line.trim()));
|
|
9924
10059
|
const envBasename = envFile.replace(/\\/g, "/").split("/").pop() ?? envFile;
|
|
9925
10060
|
if (lines.has(".env*") || lines.has(".env.*") || lines.has(".env*.local") || lines.has(".env.local") && envBasename === ".env.local") {
|
|
@@ -9940,7 +10075,7 @@ function registerAiCommands(aiCmd2) {
|
|
|
9940
10075
|
|
|
9941
10076
|
// src/index.ts
|
|
9942
10077
|
var __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
9943
|
-
var pkg = JSON.parse(
|
|
10078
|
+
var pkg = JSON.parse(readFileSync13(join15(__dirname, "../package.json"), "utf-8"));
|
|
9944
10079
|
var INSFORGE_LOGO = `
|
|
9945
10080
|
\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
|
|
9946
10081
|
\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
|
|
@@ -10051,7 +10186,7 @@ async function showInteractiveMenu() {
|
|
|
10051
10186
|
} catch {
|
|
10052
10187
|
}
|
|
10053
10188
|
console.log(INSFORGE_LOGO);
|
|
10054
|
-
|
|
10189
|
+
clack19.intro(`InsForge CLI v${pkg.version}`);
|
|
10055
10190
|
const options = [];
|
|
10056
10191
|
if (!isLoggedIn) {
|
|
10057
10192
|
options.push({ value: "login", label: "Log in to InsForge" });
|
|
@@ -10072,7 +10207,7 @@ async function showInteractiveMenu() {
|
|
|
10072
10207
|
options
|
|
10073
10208
|
});
|
|
10074
10209
|
if (isCancel2(action)) {
|
|
10075
|
-
|
|
10210
|
+
clack19.cancel("Bye!");
|
|
10076
10211
|
process.exit(0);
|
|
10077
10212
|
}
|
|
10078
10213
|
switch (action) {
|