@insforge/cli 0.1.33 → 0.1.34

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
@@ -2042,6 +2042,91 @@ async function waitForProjectActive(projectId, apiUrl, timeoutMs = 12e4) {
2042
2042
  }
2043
2043
  throw new CLIError("Project creation timed out. Check the dashboard for status.");
2044
2044
  }
2045
+ var INSFORGE_BANNER = [
2046
+ "\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",
2047
+ "\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",
2048
+ "\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2557 ",
2049
+ "\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D ",
2050
+ "\u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
2051
+ "\u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D"
2052
+ ];
2053
+ async function animateBanner() {
2054
+ const isTTY = process.stderr.isTTY;
2055
+ if (!isTTY || process.env.CI) {
2056
+ for (const line of INSFORGE_BANNER) {
2057
+ process.stderr.write(`${line}
2058
+ `);
2059
+ }
2060
+ process.stderr.write("\n");
2061
+ return;
2062
+ }
2063
+ const totalLines = INSFORGE_BANNER.length;
2064
+ const maxLen = Math.max(...INSFORGE_BANNER.map((l) => l.length));
2065
+ const cols = process.stderr.columns ?? 0;
2066
+ if (cols > 0 && cols < maxLen) {
2067
+ for (const line of INSFORGE_BANNER) {
2068
+ process.stderr.write(`\x1B[97m${line}\x1B[0m
2069
+ `);
2070
+ }
2071
+ process.stderr.write("\n");
2072
+ return;
2073
+ }
2074
+ const REVEAL_STEPS = 10;
2075
+ const REVEAL_DELAY = 30;
2076
+ for (let lineIdx = 0; lineIdx < totalLines; lineIdx++) {
2077
+ const line = INSFORGE_BANNER[lineIdx];
2078
+ for (let step = 0; step <= REVEAL_STEPS; step++) {
2079
+ const pos = Math.floor(step / REVEAL_STEPS * line.length);
2080
+ let rendered = "";
2081
+ for (let i = 0; i < line.length; i++) {
2082
+ if (i < pos) {
2083
+ rendered += `\x1B[97m${line[i]}\x1B[0m`;
2084
+ } else if (i === pos) {
2085
+ rendered += `\x1B[1;37m${line[i]}\x1B[0m`;
2086
+ } else {
2087
+ rendered += `\x1B[90m${line[i]}\x1B[0m`;
2088
+ }
2089
+ }
2090
+ process.stderr.write(`\r${rendered}`);
2091
+ await new Promise((r) => setTimeout(r, REVEAL_DELAY));
2092
+ }
2093
+ process.stderr.write("\n");
2094
+ }
2095
+ const SHIMMER_STEPS = 16;
2096
+ const SHIMMER_DELAY = 40;
2097
+ const SHIMMER_WIDTH = 4;
2098
+ for (let step = 0; step < SHIMMER_STEPS; step++) {
2099
+ const shimmerPos = Math.floor(step / SHIMMER_STEPS * (maxLen + SHIMMER_WIDTH));
2100
+ process.stderr.write(`\x1B[${totalLines}A`);
2101
+ for (const line of INSFORGE_BANNER) {
2102
+ let rendered = "";
2103
+ for (let i = 0; i < line.length; i++) {
2104
+ const dist = Math.abs(i - shimmerPos);
2105
+ if (dist === 0) {
2106
+ rendered += `\x1B[1;97m${line[i]}\x1B[0m`;
2107
+ } else if (dist <= SHIMMER_WIDTH) {
2108
+ rendered += `\x1B[37m${line[i]}\x1B[0m`;
2109
+ } else {
2110
+ rendered += `\x1B[90m${line[i]}\x1B[0m`;
2111
+ }
2112
+ }
2113
+ process.stderr.write(`${rendered}
2114
+ `);
2115
+ }
2116
+ await new Promise((r) => setTimeout(r, SHIMMER_DELAY));
2117
+ }
2118
+ process.stderr.write(`\x1B[${totalLines}A`);
2119
+ for (const line of INSFORGE_BANNER) {
2120
+ process.stderr.write(`\x1B[97m${line}\x1B[0m
2121
+ `);
2122
+ }
2123
+ process.stderr.write("\n");
2124
+ }
2125
+ function getDefaultProjectName() {
2126
+ const dirName = path3.basename(process.cwd());
2127
+ const sanitized = dirName.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
2128
+ return sanitized.length >= 2 ? sanitized : "";
2129
+ }
2045
2130
  async function copyDir(src, dest) {
2046
2131
  const entries = await fs3.readdir(src, { withFileTypes: true });
2047
2132
  for (const entry of entries) {
@@ -2061,7 +2146,8 @@ function registerCreateCommand(program2) {
2061
2146
  try {
2062
2147
  await requireAuth(apiUrl, false);
2063
2148
  if (!json) {
2064
- clack10.intro("Create a new InsForge project");
2149
+ await animateBanner();
2150
+ clack10.intro("Let's build something great");
2065
2151
  }
2066
2152
  let orgId = opts.orgId;
2067
2153
  if (!orgId) {
@@ -2088,13 +2174,19 @@ function registerCreateCommand(program2) {
2088
2174
  let projectName = opts.name;
2089
2175
  if (!projectName) {
2090
2176
  if (json) throw new CLIError("--name is required in JSON mode.");
2177
+ const defaultName = getDefaultProjectName();
2091
2178
  const name = await clack10.text({
2092
2179
  message: "Project name:",
2180
+ ...defaultName ? { defaultValue: defaultName, placeholder: defaultName } : {},
2093
2181
  validate: (v) => v.length >= 2 ? void 0 : "Name must be at least 2 characters"
2094
2182
  });
2095
2183
  if (clack10.isCancel(name)) process.exit(0);
2096
2184
  projectName = name;
2097
2185
  }
2186
+ projectName = path3.basename(projectName).replace(/[^a-zA-Z0-9._-]/g, "-").replace(/\.+/g, ".");
2187
+ if (projectName.length < 2 || projectName === "." || projectName === "..") {
2188
+ throw new CLIError("Project name must be at least 2 safe characters (letters, numbers, hyphens).");
2189
+ }
2098
2190
  const validTemplates = ["react", "nextjs", "chatbot", "crm", "e-commerce", "empty"];
2099
2191
  let template = opts.template;
2100
2192
  if (template && !validTemplates.includes(template)) {
@@ -2104,19 +2196,30 @@ function registerCreateCommand(program2) {
2104
2196
  if (json) {
2105
2197
  template = "empty";
2106
2198
  } else {
2107
- const selected = await clack10.select({
2108
- message: "Choose a starter template:",
2199
+ const approach = await clack10.select({
2200
+ message: "How would you like to start?",
2109
2201
  options: [
2110
- { value: "react", label: "Web app template with React" },
2111
- { value: "nextjs", label: "Web app template with Next.js" },
2112
- { value: "chatbot", label: "AI Chatbot with Next.js" },
2113
- { value: "crm", label: "CRM with Next.js" },
2114
- { value: "e-commerce", label: "E-Commerce store with Next.js" },
2115
- { value: "empty", label: "Empty project" }
2202
+ { value: "blank", label: "Blank project", hint: "Start from scratch with .env.local ready" },
2203
+ { value: "template", label: "Start from a template", hint: "Pre-built starter apps" }
2116
2204
  ]
2117
2205
  });
2118
- if (clack10.isCancel(selected)) process.exit(0);
2119
- template = selected;
2206
+ if (clack10.isCancel(approach)) process.exit(0);
2207
+ if (approach === "blank") {
2208
+ template = "empty";
2209
+ } else {
2210
+ const selected = await clack10.select({
2211
+ message: "Choose a starter template:",
2212
+ options: [
2213
+ { value: "react", label: "Web app template with React" },
2214
+ { value: "nextjs", label: "Web app template with Next.js" },
2215
+ { value: "chatbot", label: "AI Chatbot with Next.js" },
2216
+ { value: "crm", label: "CRM with Next.js" },
2217
+ { value: "e-commerce", label: "E-Commerce store with Next.js" }
2218
+ ]
2219
+ });
2220
+ if (clack10.isCancel(selected)) process.exit(0);
2221
+ template = selected;
2222
+ }
2120
2223
  }
2121
2224
  }
2122
2225
  const s = !json ? clack10.spinner() : null;
@@ -2137,11 +2240,39 @@ function registerCreateCommand(program2) {
2137
2240
  saveProjectConfig(projectConfig);
2138
2241
  s?.stop(`Project "${project.name}" created and linked`);
2139
2242
  const hasTemplate = template !== "empty";
2140
- const githubTemplates = ["chatbot", "crm", "e-commerce"];
2243
+ const githubTemplates = ["chatbot", "crm", "e-commerce", "nextjs", "react"];
2141
2244
  if (githubTemplates.includes(template)) {
2142
2245
  await downloadGitHubTemplate(template, projectConfig, json);
2143
2246
  } else if (hasTemplate) {
2144
2247
  await downloadTemplate(template, projectConfig, projectName, json, apiUrl);
2248
+ } else {
2249
+ try {
2250
+ const anonKey = await getAnonKey();
2251
+ if (!anonKey) {
2252
+ if (!json) clack10.log.warn("Could not retrieve anon key. You can add it to .env.local manually.");
2253
+ } else {
2254
+ const envPath = path3.join(process.cwd(), ".env.local");
2255
+ const envContent = [
2256
+ "# InsForge",
2257
+ `NEXT_PUBLIC_INSFORGE_URL=${projectConfig.oss_host}`,
2258
+ `NEXT_PUBLIC_INSFORGE_ANON_KEY=${anonKey}`,
2259
+ ""
2260
+ ].join("\n");
2261
+ await fs3.writeFile(envPath, envContent, { flag: "wx" });
2262
+ if (!json) {
2263
+ clack10.log.success("Created .env.local with your InsForge credentials");
2264
+ }
2265
+ }
2266
+ } catch (err) {
2267
+ const error = err;
2268
+ if (!json) {
2269
+ if (error.code === "EEXIST") {
2270
+ clack10.log.warn(".env.local already exists; skipping InsForge key seeding.");
2271
+ } else {
2272
+ clack10.log.warn(`Failed to create .env.local: ${error.message}`);
2273
+ }
2274
+ }
2275
+ }
2145
2276
  }
2146
2277
  await installSkills(json);
2147
2278
  await reportCliUsage("cli.create", true, 6);
@@ -2284,7 +2415,16 @@ async function downloadGitHubTemplate(templateName, projectConfig, json) {
2284
2415
  return `${prefix}${_value}`;
2285
2416
  }
2286
2417
  );
2287
- await fs3.writeFile(path3.join(cwd, ".env.local"), envContent);
2418
+ const envLocalPath = path3.join(cwd, ".env.local");
2419
+ try {
2420
+ await fs3.writeFile(envLocalPath, envContent, { flag: "wx" });
2421
+ } catch (e) {
2422
+ if (e.code === "EEXIST") {
2423
+ if (!json) clack10.log.warn(".env.local already exists; skipping env seeding.");
2424
+ } else {
2425
+ throw e;
2426
+ }
2427
+ }
2288
2428
  }
2289
2429
  s?.stop(`${templateName} template downloaded`);
2290
2430
  const migrationPath = path3.join(cwd, "migrations", "db_init.sql");