@nimbuslab/cli 0.7.0 → 0.8.0

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
@@ -844,12 +844,133 @@ var Y2 = ({ indicator: t = "dots" } = {}) => {
844
844
  // src/commands/create.ts
845
845
  var import_picocolors3 = __toESM(require_picocolors(), 1);
846
846
  var {$: $2 } = globalThis.Bun;
847
- import { rm } from "fs/promises";
847
+ import { rm, mkdir } from "fs/promises";
848
848
  import { join } from "path";
849
+ var AI_CONFIGS = {
850
+ claude: {
851
+ filename: "CLAUDE.md",
852
+ content: (type) => `# ${type === "landing" ? "Landing Page" : type === "app" ? "Web App" : "Monorepo"}
853
+
854
+ ## Stack
855
+ - Next.js 16 (App Router, Turbopack)
856
+ - React 19 (Server Components)
857
+ - TypeScript (strict)
858
+ - Tailwind CSS 4
859
+ - shadcn/ui
860
+ - Bun
861
+ ${type === "app" ? `- Better Auth
862
+ - Drizzle + PostgreSQL` : ""}
863
+
864
+ ## Commands
865
+ \`\`\`bash
866
+ bun dev # Start development
867
+ bun build # Production build
868
+ bun lint # Run ESLint
869
+ ${type === "app" ? "bun setup # Setup database" : ""}
870
+ \`\`\`
871
+
872
+ ## Conventions
873
+ - Use \`bun\` for all package operations
874
+ - Server Components by default
875
+ - Dark mode first design
876
+ - Use \`cn()\` for conditional classes
877
+ - Add components: \`bunx --bun shadcn@latest add [component]\`
878
+ `
879
+ },
880
+ cursor: {
881
+ filename: ".cursorrules",
882
+ content: (type) => `# Cursor Rules
883
+
884
+ Stack: Next.js 16, React 19, TypeScript, Tailwind CSS 4, shadcn/ui, Bun
885
+ ${type === "app" ? "Auth: Better Auth | DB: Drizzle + PostgreSQL" : ""}
886
+
887
+ - Server Components by default
888
+ - "use client" only when needed
889
+ - Tailwind utility classes
890
+ - cn() for conditional classes
891
+ - Dark mode first
892
+ `
893
+ },
894
+ gemini: {
895
+ filename: ".gemini/GEMINI.md",
896
+ content: (type) => `# ${type === "landing" ? "Landing Page" : type === "app" ? "Web App" : "Monorepo"}
897
+
898
+ ## Stack
899
+ - Next.js 16 (App Router, Turbopack)
900
+ - React 19 (Server Components)
901
+ - TypeScript (strict)
902
+ - Tailwind CSS 4
903
+ - shadcn/ui
904
+ - Bun
905
+ ${type === "app" ? `- Better Auth
906
+ - Drizzle + PostgreSQL` : ""}
907
+
908
+ ## Conventions
909
+ - Use \`bun\` for all package operations
910
+ - Server Components by default
911
+ - Dark mode first design
912
+ - Use \`cn()\` for conditional classes
913
+ `
914
+ },
915
+ copilot: {
916
+ filename: ".github/copilot-instructions.md",
917
+ content: (type) => `# GitHub Copilot Instructions
918
+
919
+ Stack: Next.js 16, React 19, TypeScript, Tailwind CSS 4, shadcn/ui, Bun
920
+ ${type === "app" ? "Auth: Better Auth | DB: Drizzle + PostgreSQL" : ""}
921
+
922
+ ## Do
923
+ - Use TypeScript strict mode
924
+ - Prefer Server Components
925
+ - Use Tailwind for styling
926
+ - Use cn() for class merging
927
+
928
+ ## Don't
929
+ - Use CSS modules or styled-components
930
+ - Use class components
931
+ - Add unnecessary dependencies
932
+ `
933
+ },
934
+ windsurf: {
935
+ filename: ".windsurfrules",
936
+ content: (type) => `# Windsurf Rules
937
+
938
+ Stack: Next.js 16, React 19, TypeScript, Tailwind CSS 4, shadcn/ui, Bun
939
+ ${type === "app" ? "Auth: Better Auth | DB: Drizzle + PostgreSQL" : ""}
940
+
941
+ - Server Components by default
942
+ - "use client" only when needed
943
+ - Tailwind utility classes
944
+ - cn() for conditional classes
945
+ - Dark mode first
946
+ `
947
+ }
948
+ };
949
+ async function checkGitHubCli() {
950
+ const checkCmd = process.platform === "win32" ? "where" : "which";
951
+ try {
952
+ const hasGh = await $2`${checkCmd} gh`.quiet().nothrow();
953
+ if (hasGh.exitCode !== 0) {
954
+ return { installed: false, authenticated: false, username: null, orgs: [] };
955
+ }
956
+ const authStatus = await $2`gh auth status`.quiet().nothrow();
957
+ if (authStatus.exitCode !== 0) {
958
+ return { installed: true, authenticated: false, username: null, orgs: [] };
959
+ }
960
+ const username = (await $2`gh api user --jq '.login'`.quiet().text()).trim();
961
+ const orgsJson = await $2`gh api user/orgs --jq '.[].login'`.quiet().text();
962
+ const orgs = orgsJson.trim().split(`
963
+ `).filter(Boolean);
964
+ return { installed: true, authenticated: true, username, orgs };
965
+ } catch {
966
+ return { installed: false, authenticated: false, username: null, orgs: [] };
967
+ }
968
+ }
849
969
  var PRIVATE_TEMPLATES = {
850
970
  fast: "nimbuslab-templates/fast-template",
851
971
  "fast+": "nimbuslab-templates/fastplus-template",
852
- "fast+-monorepo": "nimbuslab-templates/fastplus-monorepo-template"
972
+ "fast+-monorepo": "nimbuslab-templates/fastplus-monorepo-template",
973
+ "nimbus-core": "nimbuslab/nimbus-core"
853
974
  };
854
975
  var PUBLIC_TEMPLATES = {
855
976
  landing: "nimbuslab/create-next-landing",
@@ -877,6 +998,7 @@ function parseFlags(args) {
877
998
  fast: false,
878
999
  fastPlus: false,
879
1000
  fastTurborepo: false,
1001
+ core: false,
880
1002
  noGit: false,
881
1003
  noInstall: false,
882
1004
  railway: false,
@@ -900,6 +1022,8 @@ function parseFlags(args) {
900
1022
  flags.fastPlus = true;
901
1023
  } else if (arg === "--fast-turborepo") {
902
1024
  flags.fastTurborepo = true;
1025
+ } else if (arg === "--core") {
1026
+ flags.core = true;
903
1027
  } else if (arg === "--no-git") {
904
1028
  flags.noGit = true;
905
1029
  } else if (arg === "--no-install") {
@@ -978,22 +1102,9 @@ async function create(args) {
978
1102
  process.exit(1);
979
1103
  }
980
1104
  if (!hasGh) {
981
- console.log(import_picocolors3.default.red("Error: GitHub CLI (gh) not found."));
982
- console.log(import_picocolors3.default.dim("Install from: https://cli.github.com"));
1105
+ console.log(import_picocolors3.default.dim(" GitHub CLI not found (repo creation will be skipped)"));
1106
+ console.log(import_picocolors3.default.dim(" Install from: https://cli.github.com"));
983
1107
  console.log();
984
- if (process.platform === "win32") {
985
- console.log(import_picocolors3.default.cyan("winget install GitHub.cli"));
986
- } else {
987
- console.log(import_picocolors3.default.cyan("sudo apt install gh # ou brew install gh"));
988
- }
989
- console.log();
990
- process.exit(1);
991
- }
992
- const ghAuth = await $2`gh auth status`.quiet().then(() => true).catch(() => false);
993
- if (!ghAuth) {
994
- console.log(import_picocolors3.default.red("Error: GitHub CLI not authenticated."));
995
- console.log(import_picocolors3.default.dim("Run: gh auth login"));
996
- process.exit(1);
997
1108
  }
998
1109
  const hasRailway = await ensureRailwayCli();
999
1110
  if (hasRailway) {
@@ -1007,8 +1118,8 @@ async function create(args) {
1007
1118
  const { flags, projectName } = parseFlags(args);
1008
1119
  Ie(import_picocolors3.default.bgCyan(import_picocolors3.default.black(" New nimbuslab Project ")));
1009
1120
  let config;
1010
- const hasTypeFlag = flags.landing || flags.app || flags.turborepo || flags.fast || flags.fastPlus || flags.fastTurborepo;
1011
- const typeFromFlag = flags.landing ? "landing" : flags.app ? "app" : flags.turborepo ? "turborepo" : flags.fastTurborepo ? "fast+" : flags.fastPlus ? "fast+" : flags.fast ? "fast" : null;
1121
+ const hasTypeFlag = flags.landing || flags.app || flags.turborepo || flags.fast || flags.fastPlus || flags.fastTurborepo || flags.core;
1122
+ const typeFromFlag = flags.landing ? "landing" : flags.app ? "app" : flags.turborepo ? "turborepo" : flags.fastTurborepo ? "fast+" : flags.fastPlus ? "fast+" : flags.fast ? "fast" : flags.core ? "nimbus-core" : null;
1012
1123
  const monorepoFromFlag = flags.fastTurborepo;
1013
1124
  if ((flags.yes || hasTypeFlag) && projectName) {
1014
1125
  const defaultType = flags.landing || flags.app || flags.turborepo ? "landing" : "fast";
@@ -1021,6 +1132,8 @@ async function create(args) {
1021
1132
  github: false,
1022
1133
  githubOrg: null,
1023
1134
  githubDescription: "",
1135
+ theme: "dark",
1136
+ aiAssistant: null,
1024
1137
  contractNumber: "",
1025
1138
  resendApiKey: "",
1026
1139
  resendFromEmail: "",
@@ -1107,7 +1220,7 @@ async function promptConfig(initialName, flags) {
1107
1220
  {
1108
1221
  value: "app",
1109
1222
  label: "Web App",
1110
- hint: "Landing + Better Auth + Prisma"
1223
+ hint: "Landing + Better Auth + Drizzle"
1111
1224
  },
1112
1225
  {
1113
1226
  value: "turborepo",
@@ -1125,6 +1238,11 @@ async function promptConfig(initialName, flags) {
1125
1238
  value: "fast+",
1126
1239
  label: "fast+",
1127
1240
  hint: "Complete SaaS"
1241
+ },
1242
+ {
1243
+ value: "nimbus-core",
1244
+ label: "nimbus-core",
1245
+ hint: "External projects (stealth mode)"
1128
1246
  }
1129
1247
  ] : [];
1130
1248
  const type = await ve({
@@ -1138,6 +1256,44 @@ async function promptConfig(initialName, flags) {
1138
1256
  console.log(import_picocolors3.default.red("Error: Template available only for nimbuslab members"));
1139
1257
  process.exit(1);
1140
1258
  }
1259
+ if (type === "nimbus-core") {
1260
+ console.log();
1261
+ console.log(import_picocolors3.default.dim(" nimbus-core: Motor para projetos externos (stealth mode)"));
1262
+ console.log();
1263
+ const createGithub = await ye({
1264
+ message: "Create GitHub repository? (nimbuslab, private)",
1265
+ initialValue: true
1266
+ });
1267
+ if (pD(createGithub))
1268
+ return createGithub;
1269
+ const clientRepo = await he({
1270
+ message: "Client repo URL (optional, to clone in workspace):",
1271
+ placeholder: "git@github.com:client/repo.git"
1272
+ });
1273
+ if (pD(clientRepo))
1274
+ return clientRepo;
1275
+ return {
1276
+ name,
1277
+ type: "nimbus-core",
1278
+ monorepo: false,
1279
+ git: true,
1280
+ install: false,
1281
+ github: createGithub,
1282
+ githubOrg: "nimbuslab",
1283
+ githubDescription: `nimbus-core for ${name} - external project`,
1284
+ theme: "dark",
1285
+ aiAssistant: null,
1286
+ contractNumber: "",
1287
+ resendApiKey: "",
1288
+ resendFromEmail: "",
1289
+ contactEmail: "",
1290
+ railwayProject: "",
1291
+ railwayToken: "",
1292
+ stagingUrl: "",
1293
+ productionUrl: "",
1294
+ customTemplate: clientRepo || null
1295
+ };
1296
+ }
1141
1297
  let monorepo = false;
1142
1298
  if (type === "fast+") {
1143
1299
  const useMonorepo = await ye({
@@ -1200,6 +1356,66 @@ async function promptConfig(initialName, flags) {
1200
1356
  contractNumber = contract;
1201
1357
  }
1202
1358
  if (isPublicTemplate) {
1359
+ const theme = await ve({
1360
+ message: "Default theme:",
1361
+ options: [
1362
+ { value: "dark", label: "Dark", hint: "recommended" },
1363
+ { value: "light", label: "Light" },
1364
+ { value: "system", label: "System", hint: "follows OS preference" }
1365
+ ]
1366
+ });
1367
+ if (pD(theme))
1368
+ return theme;
1369
+ const aiAssistant = await ve({
1370
+ message: "Which AI assistant do you use?",
1371
+ options: [
1372
+ { value: "claude", label: "Claude Code", hint: "Anthropic" },
1373
+ { value: "cursor", label: "Cursor", hint: "AI-first editor" },
1374
+ { value: "gemini", label: "Gemini CLI", hint: "Google" },
1375
+ { value: "copilot", label: "GitHub Copilot" },
1376
+ { value: "windsurf", label: "Windsurf", hint: "Codeium" },
1377
+ { value: "none", label: "None", hint: "skip AI config" }
1378
+ ]
1379
+ });
1380
+ if (pD(aiAssistant))
1381
+ return aiAssistant;
1382
+ let publicGithub = false;
1383
+ let publicGithubOrg = null;
1384
+ if (git) {
1385
+ const gh = await checkGitHubCli();
1386
+ if (gh.installed && gh.authenticated) {
1387
+ const createRepo = await ye({
1388
+ message: "Create GitHub repository?",
1389
+ initialValue: false
1390
+ });
1391
+ if (pD(createRepo))
1392
+ return createRepo;
1393
+ publicGithub = createRepo;
1394
+ if (publicGithub) {
1395
+ const repoOptions = [
1396
+ { value: gh.username, label: gh.username, hint: "personal account" },
1397
+ ...gh.orgs.map((org) => ({ value: org, label: org }))
1398
+ ];
1399
+ const repoOwner = await ve({
1400
+ message: "Where to create the repository?",
1401
+ options: repoOptions
1402
+ });
1403
+ if (pD(repoOwner))
1404
+ return repoOwner;
1405
+ publicGithubOrg = repoOwner;
1406
+ const repoVisibility = await ve({
1407
+ message: "Repository visibility:",
1408
+ options: [
1409
+ { value: "private", label: "Private", hint: "recommended" },
1410
+ { value: "public", label: "Public" }
1411
+ ]
1412
+ });
1413
+ if (pD(repoVisibility))
1414
+ return repoVisibility;
1415
+ githubDescription = repoVisibility;
1416
+ }
1417
+ }
1418
+ }
1203
1419
  const install2 = await ye({
1204
1420
  message: "Install dependencies?",
1205
1421
  initialValue: true
@@ -1212,9 +1428,11 @@ async function promptConfig(initialName, flags) {
1212
1428
  monorepo: false,
1213
1429
  git,
1214
1430
  install: install2,
1215
- github,
1216
- githubOrg,
1431
+ github: publicGithub,
1432
+ githubOrg: publicGithubOrg,
1217
1433
  githubDescription,
1434
+ theme,
1435
+ aiAssistant: aiAssistant === "none" ? null : aiAssistant,
1218
1436
  contractNumber: "",
1219
1437
  resendApiKey: "",
1220
1438
  resendFromEmail: "",
@@ -1362,6 +1580,8 @@ async function promptConfig(initialName, flags) {
1362
1580
  github,
1363
1581
  githubOrg,
1364
1582
  githubDescription,
1583
+ theme: "dark",
1584
+ aiAssistant: null,
1365
1585
  contractNumber,
1366
1586
  resendApiKey,
1367
1587
  resendFromEmail,
@@ -1400,15 +1620,59 @@ async function createProject(config) {
1400
1620
  s.stop("Error cloning template");
1401
1621
  throw new Error(`Failed to clone template ${templateRepo}. Check your connection or repository access.`);
1402
1622
  }
1403
- s.start("Configuring project...");
1404
- try {
1405
- const pkgPath = `${config.name}/package.json`;
1406
- const pkg = await Bun.file(pkgPath).json();
1407
- pkg.name = config.name;
1408
- await Bun.write(pkgPath, JSON.stringify(pkg, null, 2));
1409
- s.stop("Project configured");
1410
- } catch (error) {
1411
- s.stop("Error configuring");
1623
+ if (config.type === "nimbus-core" && config.customTemplate) {
1624
+ const clientRepoUrl = config.customTemplate;
1625
+ s.start(`Cloning client repo in workspace...`);
1626
+ try {
1627
+ const projectName = clientRepoUrl.split("/").pop()?.replace(".git", "") || "client-project";
1628
+ await $2`git clone ${clientRepoUrl} ${config.name}/workspace/${projectName}`.quiet();
1629
+ s.stop(`Client repo cloned: workspace/${projectName}`);
1630
+ } catch (error) {
1631
+ s.stop("Error cloning client repo");
1632
+ console.log(import_picocolors3.default.dim(" You can clone manually: cd workspace && git clone <url>"));
1633
+ }
1634
+ }
1635
+ if (config.type !== "nimbus-core") {
1636
+ s.start("Configuring project...");
1637
+ try {
1638
+ const pkgPath = `${config.name}/package.json`;
1639
+ const pkg = await Bun.file(pkgPath).json();
1640
+ pkg.name = config.name;
1641
+ await Bun.write(pkgPath, JSON.stringify(pkg, null, 2));
1642
+ s.stop("Project configured");
1643
+ } catch (error) {
1644
+ s.stop("Error configuring");
1645
+ }
1646
+ }
1647
+ if (isPublicTemplate && config.theme) {
1648
+ s.start(`Setting theme to ${config.theme}...`);
1649
+ try {
1650
+ const layoutPath = `${config.name}/src/app/layout.tsx`;
1651
+ let layout = await Bun.file(layoutPath).text();
1652
+ layout = layout.replace(/defaultTheme="(dark|light|system)"/, `defaultTheme="${config.theme}"`);
1653
+ await Bun.write(layoutPath, layout);
1654
+ s.stop(`Theme set to ${config.theme}`);
1655
+ } catch {
1656
+ s.stop("Theme config skipped");
1657
+ }
1658
+ }
1659
+ if (isPublicTemplate && config.aiAssistant) {
1660
+ const aiConfig = AI_CONFIGS[config.aiAssistant];
1661
+ if (aiConfig) {
1662
+ s.start(`Generating ${config.aiAssistant} config...`);
1663
+ try {
1664
+ const content = aiConfig.content(config.type);
1665
+ const filePath = `${config.name}/${aiConfig.filename}`;
1666
+ if (aiConfig.filename.includes("/")) {
1667
+ const dir = aiConfig.filename.split("/").slice(0, -1).join("/");
1668
+ await mkdir(`${config.name}/${dir}`, { recursive: true });
1669
+ }
1670
+ await Bun.write(filePath, content);
1671
+ s.stop(`${aiConfig.filename} created`);
1672
+ } catch {
1673
+ s.stop("AI config skipped");
1674
+ }
1675
+ }
1412
1676
  }
1413
1677
  if (config.type === "fast+") {
1414
1678
  s.start("Configurando fast+ (SaaS)...");
@@ -1432,15 +1696,26 @@ async function createProject(config) {
1432
1696
  try {
1433
1697
  const cwd = config.name;
1434
1698
  const repoName = config.githubOrg ? `${config.githubOrg}/${config.name}` : config.name;
1435
- const visibility = config.githubOrg === "fast-by-nimbuslab" ? "--private" : "--public";
1436
- await $2`gh repo create ${repoName} ${visibility} --description ${config.githubDescription} --source . --remote origin`.cwd(cwd).quiet();
1699
+ let visibility;
1700
+ if (config.type === "nimbus-core") {
1701
+ visibility = "--private";
1702
+ } else if (isPublicTemplate) {
1703
+ visibility = config.githubDescription === "public" ? "--public" : "--private";
1704
+ } else {
1705
+ visibility = config.githubOrg === "fast-by-nimbuslab" ? "--private" : "--public";
1706
+ }
1707
+ if (isPublicTemplate) {
1708
+ await $2`gh repo create ${repoName} ${visibility} --source . --remote origin`.cwd(cwd).quiet();
1709
+ } else {
1710
+ await $2`gh repo create ${repoName} ${visibility} --description ${config.githubDescription} --source . --remote origin`.cwd(cwd).quiet();
1711
+ }
1437
1712
  await $2`git checkout main`.cwd(cwd).quiet();
1438
1713
  await $2`git push -u origin main`.cwd(cwd).quiet();
1439
1714
  await $2`git checkout staging`.cwd(cwd).quiet();
1440
1715
  await $2`git push -u origin staging`.cwd(cwd).quiet();
1441
1716
  await $2`git checkout develop`.cwd(cwd).quiet();
1442
1717
  await $2`git push -u origin develop`.cwd(cwd).quiet();
1443
- s.stop(`GitHub: ${repoName} criado`);
1718
+ s.stop(`GitHub: ${repoName}`);
1444
1719
  } catch (error) {
1445
1720
  s.stop("Error creating GitHub repository");
1446
1721
  console.log(import_picocolors3.default.dim(" You can create manually with: gh repo create"));
@@ -1518,56 +1793,104 @@ function generateEnvFile(config) {
1518
1793
  }
1519
1794
  function showNextSteps(config) {
1520
1795
  const isPublicTemplate = ["landing", "app", "turborepo"].includes(config.type);
1796
+ const needsSetup = config.type === "app";
1521
1797
  console.log();
1522
1798
  console.log(import_picocolors3.default.bold("Next steps:"));
1523
1799
  console.log();
1524
1800
  console.log(` ${import_picocolors3.default.cyan("cd")} ${config.name}`);
1801
+ if (config.type === "nimbus-core") {
1802
+ console.log();
1803
+ console.log(import_picocolors3.default.dim(" nimbus-core: Motor para projetos externos"));
1804
+ console.log();
1805
+ console.log(import_picocolors3.default.dim(" Para clonar repo do cliente:"));
1806
+ console.log(` ${import_picocolors3.default.cyan("cd")} workspace`);
1807
+ console.log(` ${import_picocolors3.default.cyan("git clone")} <repo-do-cliente>`);
1808
+ console.log();
1809
+ console.log(import_picocolors3.default.dim(" Para usar a Lola:"));
1810
+ console.log(` ${import_picocolors3.default.cyan("gemini")} ${import_picocolors3.default.dim("# Gemini CLI")}`);
1811
+ console.log(` ${import_picocolors3.default.cyan("claude --append-system-prompt-file .claude/agents/lola.md")}`);
1812
+ console.log();
1813
+ console.log(import_picocolors3.default.yellow(" STEALTH MODE: Commits sem mencao a nimbuslab/Lola/IA"));
1814
+ console.log();
1815
+ if (config.github) {
1816
+ const repoUrl = `https://github.com/nimbuslab/${config.name}`;
1817
+ console.log(import_picocolors3.default.green(` GitHub (private): ${repoUrl}`));
1818
+ console.log();
1819
+ }
1820
+ console.log(import_picocolors3.default.dim(" Docs: See README.md for full instructions"));
1821
+ console.log();
1822
+ return;
1823
+ }
1525
1824
  if (!config.install) {
1526
1825
  console.log(` ${import_picocolors3.default.cyan("bun")} install`);
1527
1826
  }
1528
- if (!isPublicTemplate) {
1827
+ if (!isPublicTemplate || needsSetup) {
1529
1828
  console.log(` ${import_picocolors3.default.cyan("bun")} setup`);
1530
1829
  }
1531
1830
  console.log(` ${import_picocolors3.default.cyan("bun")} dev`);
1532
1831
  console.log();
1533
- if (config.git) {
1534
- console.log(import_picocolors3.default.dim(" Git flow: main -> staging -> develop (current branch)"));
1832
+ if (needsSetup && isPublicTemplate) {
1833
+ console.log(import_picocolors3.default.dim(" bun setup will:"));
1834
+ console.log(import_picocolors3.default.dim(" - Start PostgreSQL with Docker"));
1835
+ console.log(import_picocolors3.default.dim(" - Run database migrations"));
1836
+ console.log(import_picocolors3.default.dim(" - Create demo user (demo@example.com / demo1234)"));
1535
1837
  console.log();
1838
+ }
1839
+ if (config.git) {
1840
+ console.log(import_picocolors3.default.dim(" Git: main -> staging -> develop (current branch)"));
1536
1841
  if (config.github) {
1537
1842
  const repoUrl = config.githubOrg ? `https://github.com/${config.githubOrg}/${config.name}` : `https://github.com/${config.name}`;
1538
1843
  console.log(import_picocolors3.default.green(` GitHub: ${repoUrl}`));
1539
- console.log();
1540
- } else {
1541
- console.log(import_picocolors3.default.yellow(" Tip: To create GitHub repo, use 'gh repo create' ou 'bun setup'."));
1844
+ }
1845
+ console.log();
1846
+ }
1847
+ if (isPublicTemplate) {
1848
+ if (config.theme !== "dark") {
1849
+ console.log(import_picocolors3.default.dim(` Theme: ${config.theme}`));
1850
+ }
1851
+ if (config.aiAssistant) {
1852
+ const aiConfig = AI_CONFIGS[config.aiAssistant];
1853
+ if (aiConfig) {
1854
+ console.log(import_picocolors3.default.dim(` AI config: ${aiConfig.filename}`));
1855
+ }
1856
+ }
1857
+ if (config.theme !== "dark" || config.aiAssistant) {
1542
1858
  console.log();
1543
1859
  }
1544
1860
  }
1545
1861
  if (config.type === "fast+") {
1546
- console.log(import_picocolors3.default.dim(" Tip: For fast+, configure DATABASE_URL e BETTER_AUTH_SECRET no .env"));
1862
+ console.log(import_picocolors3.default.dim(" bun setup will:"));
1863
+ console.log(import_picocolors3.default.dim(" - Start PostgreSQL with Docker"));
1864
+ console.log(import_picocolors3.default.dim(" - Run database migrations"));
1865
+ console.log(import_picocolors3.default.dim(" - Create demo user (demo@example.com / demo1234)"));
1866
+ console.log();
1867
+ console.log(import_picocolors3.default.dim(" Tip: Configure DATABASE_URL and BETTER_AUTH_SECRET in .env"));
1547
1868
  if (!config.railwayToken) {
1548
1869
  console.log(import_picocolors3.default.dim(" Railway: Create a project at https://railway.app/new"));
1549
1870
  }
1550
1871
  console.log();
1551
1872
  }
1552
- if (config.resendApiKey || config.stagingUrl) {
1553
- console.log(import_picocolors3.default.green(" .env configured successfully!"));
1554
- console.log();
1555
- } else {
1556
- console.log(import_picocolors3.default.yellow(" Tip: Configure .env manually or use 'bun setup'."));
1557
- console.log();
1873
+ if (!isPublicTemplate) {
1874
+ if (config.resendApiKey || config.stagingUrl) {
1875
+ console.log(import_picocolors3.default.green(" .env configured!"));
1876
+ console.log();
1877
+ } else {
1878
+ console.log(import_picocolors3.default.yellow(" Tip: Configure .env manually or use 'bun setup'."));
1879
+ console.log();
1880
+ }
1558
1881
  }
1559
1882
  if (isPublicTemplate) {
1560
1883
  console.log(import_picocolors3.default.dim(" Open source template (MIT) by nimbuslab"));
1561
- console.log(import_picocolors3.default.dim(` Documentation: https://github.com/nimbuslab/create-next-${config.type === "turborepo" ? "turborepo" : config.type}`));
1884
+ console.log(import_picocolors3.default.dim(` https://github.com/nimbuslab/create-next-${config.type === "turborepo" ? "turborepo" : config.type}`));
1562
1885
  } else {
1563
- console.log(import_picocolors3.default.dim(" Documentation: https://github.com/nimbuslab-templates"));
1886
+ console.log(import_picocolors3.default.dim(" https://github.com/nimbuslab-templates"));
1564
1887
  }
1565
1888
  console.log();
1566
1889
  }
1567
1890
 
1568
1891
  // src/index.ts
1569
1892
  var PACKAGE_NAME = "@nimbuslab/cli";
1570
- var CURRENT_VERSION = "0.7.0";
1893
+ var CURRENT_VERSION = "0.8.0";
1571
1894
  var LOGO = `
1572
1895
  \u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
1573
1896
  \u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D
package/docs/CI-CD.md CHANGED
@@ -161,12 +161,21 @@ O npm suporta autenticacao via OIDC, eliminando necessidade de tokens.
161
161
 
162
162
  ### 2026-01-26: Publicacao falhando silenciosamente
163
163
 
164
- **Problema**: CI mostrava sucesso mas versao nao aparecia no npm.
164
+ **Problema**: CI mostrava sucesso (`+ @nimbuslab/cli@0.6.x`) mas versao nao aparecia no npm. Tarball retornava 404 com mensagem "Access token expired or revoked".
165
165
 
166
- **Causa**: OIDC Trusted Publishing falhando silenciosamente (config no npmjs.com ou bug do npm).
166
+ **Causa**: OIDC Trusted Publishing falhando silenciosamente. Os tarballs eram criados mas ficavam inacessiveis.
167
167
 
168
168
  **Solucao**: Adicionar `NODE_AUTH_TOKEN` como fallback junto com OIDC.
169
169
 
170
+ ```yaml
171
+ - name: Publish to npm
172
+ run: npm publish --access public
173
+ env:
174
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
175
+ ```
176
+
177
+ **Importante**: Mesmo com OIDC configurado, SEMPRE manter o `NODE_AUTH_TOKEN` como fallback. O token deve ser do tipo "Granular Access Token" (nao "Classic", que foi deprecado em dez/2025).
178
+
170
179
  ---
171
180
 
172
181
  *Ultima atualizacao: 2026-01-26*
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nimbuslab/cli",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "CLI para criar projetos nimbuslab",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,14 +1,144 @@
1
1
  import * as p from "@clack/prompts"
2
2
  import pc from "picocolors"
3
- import { $} from "bun"
4
- import { rm } from "node:fs/promises"
3
+ import { $ } from "bun"
4
+ import { rm, mkdir } from "node:fs/promises"
5
5
  import { join } from "node:path"
6
6
 
7
+ // AI Assistant configs
8
+ const AI_CONFIGS: Record<string, { filename: string; content: (type: string) => string }> = {
9
+ claude: {
10
+ filename: "CLAUDE.md",
11
+ content: (type) => `# ${type === "landing" ? "Landing Page" : type === "app" ? "Web App" : "Monorepo"}
12
+
13
+ ## Stack
14
+ - Next.js 16 (App Router, Turbopack)
15
+ - React 19 (Server Components)
16
+ - TypeScript (strict)
17
+ - Tailwind CSS 4
18
+ - shadcn/ui
19
+ - Bun
20
+ ${type === "app" ? "- Better Auth\n- Drizzle + PostgreSQL" : ""}
21
+
22
+ ## Commands
23
+ \`\`\`bash
24
+ bun dev # Start development
25
+ bun build # Production build
26
+ bun lint # Run ESLint
27
+ ${type === "app" ? "bun setup # Setup database" : ""}
28
+ \`\`\`
29
+
30
+ ## Conventions
31
+ - Use \`bun\` for all package operations
32
+ - Server Components by default
33
+ - Dark mode first design
34
+ - Use \`cn()\` for conditional classes
35
+ - Add components: \`bunx --bun shadcn@latest add [component]\`
36
+ `,
37
+ },
38
+ cursor: {
39
+ filename: ".cursorrules",
40
+ content: (type) => `# Cursor Rules
41
+
42
+ Stack: Next.js 16, React 19, TypeScript, Tailwind CSS 4, shadcn/ui, Bun
43
+ ${type === "app" ? "Auth: Better Auth | DB: Drizzle + PostgreSQL" : ""}
44
+
45
+ - Server Components by default
46
+ - "use client" only when needed
47
+ - Tailwind utility classes
48
+ - cn() for conditional classes
49
+ - Dark mode first
50
+ `,
51
+ },
52
+ gemini: {
53
+ filename: ".gemini/GEMINI.md",
54
+ content: (type) => `# ${type === "landing" ? "Landing Page" : type === "app" ? "Web App" : "Monorepo"}
55
+
56
+ ## Stack
57
+ - Next.js 16 (App Router, Turbopack)
58
+ - React 19 (Server Components)
59
+ - TypeScript (strict)
60
+ - Tailwind CSS 4
61
+ - shadcn/ui
62
+ - Bun
63
+ ${type === "app" ? "- Better Auth\n- Drizzle + PostgreSQL" : ""}
64
+
65
+ ## Conventions
66
+ - Use \`bun\` for all package operations
67
+ - Server Components by default
68
+ - Dark mode first design
69
+ - Use \`cn()\` for conditional classes
70
+ `,
71
+ },
72
+ copilot: {
73
+ filename: ".github/copilot-instructions.md",
74
+ content: (type) => `# GitHub Copilot Instructions
75
+
76
+ Stack: Next.js 16, React 19, TypeScript, Tailwind CSS 4, shadcn/ui, Bun
77
+ ${type === "app" ? "Auth: Better Auth | DB: Drizzle + PostgreSQL" : ""}
78
+
79
+ ## Do
80
+ - Use TypeScript strict mode
81
+ - Prefer Server Components
82
+ - Use Tailwind for styling
83
+ - Use cn() for class merging
84
+
85
+ ## Don't
86
+ - Use CSS modules or styled-components
87
+ - Use class components
88
+ - Add unnecessary dependencies
89
+ `,
90
+ },
91
+ windsurf: {
92
+ filename: ".windsurfrules",
93
+ content: (type) => `# Windsurf Rules
94
+
95
+ Stack: Next.js 16, React 19, TypeScript, Tailwind CSS 4, shadcn/ui, Bun
96
+ ${type === "app" ? "Auth: Better Auth | DB: Drizzle + PostgreSQL" : ""}
97
+
98
+ - Server Components by default
99
+ - "use client" only when needed
100
+ - Tailwind utility classes
101
+ - cn() for conditional classes
102
+ - Dark mode first
103
+ `,
104
+ },
105
+ }
106
+
107
+ // GitHub CLI check
108
+ async function checkGitHubCli(): Promise<{
109
+ installed: boolean
110
+ authenticated: boolean
111
+ username: string | null
112
+ orgs: string[]
113
+ }> {
114
+ const checkCmd = process.platform === "win32" ? "where" : "which"
115
+ try {
116
+ const hasGh = await $`${checkCmd} gh`.quiet().nothrow()
117
+ if (hasGh.exitCode !== 0) {
118
+ return { installed: false, authenticated: false, username: null, orgs: [] }
119
+ }
120
+
121
+ const authStatus = await $`gh auth status`.quiet().nothrow()
122
+ if (authStatus.exitCode !== 0) {
123
+ return { installed: true, authenticated: false, username: null, orgs: [] }
124
+ }
125
+
126
+ const username = (await $`gh api user --jq '.login'`.quiet().text()).trim()
127
+ const orgsJson = await $`gh api user/orgs --jq '.[].login'`.quiet().text()
128
+ const orgs = orgsJson.trim().split("\n").filter(Boolean)
129
+
130
+ return { installed: true, authenticated: true, username, orgs }
131
+ } catch {
132
+ return { installed: false, authenticated: false, username: null, orgs: [] }
133
+ }
134
+ }
135
+
7
136
  // Templates privados (nimbuslab-templates) - uso interno
8
137
  const PRIVATE_TEMPLATES = {
9
138
  "fast": "nimbuslab-templates/fast-template",
10
139
  "fast+": "nimbuslab-templates/fastplus-template",
11
140
  "fast+-monorepo": "nimbuslab-templates/fastplus-monorepo-template",
141
+ "nimbus-core": "nimbuslab/nimbus-core",
12
142
  }
13
143
 
14
144
  // Templates públicos (nimbuslab)
@@ -39,7 +169,7 @@ async function isNimbuslabMember(): Promise<{ isMember: boolean; user: string |
39
169
  }
40
170
  }
41
171
 
42
- type ProjectType = "fast" | "fast+" | "landing" | "app" | "turborepo"
172
+ type ProjectType = "fast" | "fast+" | "landing" | "app" | "turborepo" | "nimbus-core"
43
173
 
44
174
  interface ProjectConfig {
45
175
  name: string
@@ -50,6 +180,9 @@ interface ProjectConfig {
50
180
  github: boolean
51
181
  githubOrg: string | null
52
182
  githubDescription: string
183
+ // Public template configs
184
+ theme: "dark" | "light" | "system"
185
+ aiAssistant: string | null
53
186
  // M26: Número do contrato (fast only)
54
187
  contractNumber: string
55
188
  // M21-M23: Configs de infra
@@ -75,6 +208,7 @@ interface CreateFlags {
75
208
  fast: boolean
76
209
  fastPlus: boolean
77
210
  fastTurborepo: boolean
211
+ core: boolean
78
212
  // Outros
79
213
  noGit: boolean
80
214
  noInstall: boolean
@@ -91,6 +225,7 @@ function parseFlags(args: string[]): { flags: CreateFlags; projectName: string |
91
225
  fast: false,
92
226
  fastPlus: false,
93
227
  fastTurborepo: false,
228
+ core: false,
94
229
  noGit: false,
95
230
  noInstall: false,
96
231
  railway: false,
@@ -117,6 +252,8 @@ function parseFlags(args: string[]): { flags: CreateFlags; projectName: string |
117
252
  flags.fastPlus = true
118
253
  } else if (arg === "--fast-turborepo") {
119
254
  flags.fastTurborepo = true
255
+ } else if (arg === "--core") {
256
+ flags.core = true
120
257
  } else if (arg === "--no-git") {
121
258
  flags.noGit = true
122
259
  } else if (arg === "--no-install") {
@@ -210,25 +347,12 @@ export async function create(args: string[]) {
210
347
  process.exit(1)
211
348
  }
212
349
 
350
+ // GitHub CLI is optional for public templates
351
+ // Will be checked later if user wants to create a repo
213
352
  if (!hasGh) {
214
- console.log(pc.red("Error: GitHub CLI (gh) not found."))
215
- console.log(pc.dim("Install from: https://cli.github.com"))
353
+ console.log(pc.dim(" GitHub CLI not found (repo creation will be skipped)"))
354
+ console.log(pc.dim(" Install from: https://cli.github.com"))
216
355
  console.log()
217
- if (process.platform === "win32") {
218
- console.log(pc.cyan("winget install GitHub.cli"))
219
- } else {
220
- console.log(pc.cyan("sudo apt install gh # ou brew install gh"))
221
- }
222
- console.log()
223
- process.exit(1)
224
- }
225
-
226
- // Verifica se gh esta autenticado
227
- const ghAuth = await $`gh auth status`.quiet().then(() => true).catch(() => false)
228
- if (!ghAuth) {
229
- console.log(pc.red("Error: GitHub CLI not authenticated."))
230
- console.log(pc.dim("Run: gh auth login"))
231
- process.exit(1)
232
356
  }
233
357
 
234
358
  // M30: Verificar/instalar Railway CLI
@@ -249,13 +373,14 @@ export async function create(args: string[]) {
249
373
  let config: ProjectConfig | symbol
250
374
 
251
375
  // Determina tipo baseado nas flags
252
- const hasTypeFlag = flags.landing || flags.app || flags.turborepo || flags.fast || flags.fastPlus || flags.fastTurborepo
376
+ const hasTypeFlag = flags.landing || flags.app || flags.turborepo || flags.fast || flags.fastPlus || flags.fastTurborepo || flags.core
253
377
  const typeFromFlag: ProjectType | null = flags.landing ? "landing"
254
378
  : flags.app ? "app"
255
379
  : flags.turborepo ? "turborepo"
256
380
  : flags.fastTurborepo ? "fast+"
257
381
  : flags.fastPlus ? "fast+"
258
382
  : flags.fast ? "fast"
383
+ : flags.core ? "nimbus-core"
259
384
  : null
260
385
  const monorepoFromFlag = flags.fastTurborepo
261
386
 
@@ -272,6 +397,8 @@ export async function create(args: string[]) {
272
397
  github: false,
273
398
  githubOrg: null,
274
399
  githubDescription: "",
400
+ theme: "dark" as const,
401
+ aiAssistant: null,
275
402
  contractNumber: "",
276
403
  resendApiKey: "",
277
404
  resendFromEmail: "",
@@ -370,7 +497,7 @@ async function promptConfig(initialName?: string, flags?: CreateFlags): Promise<
370
497
  {
371
498
  value: "app",
372
499
  label: "Web App",
373
- hint: "Landing + Better Auth + Prisma",
500
+ hint: "Landing + Better Auth + Drizzle",
374
501
  },
375
502
  {
376
503
  value: "turborepo",
@@ -391,6 +518,11 @@ async function promptConfig(initialName?: string, flags?: CreateFlags): Promise<
391
518
  label: "fast+",
392
519
  hint: "Complete SaaS",
393
520
  },
521
+ {
522
+ value: "nimbus-core",
523
+ label: "nimbus-core",
524
+ hint: "External projects (stealth mode)",
525
+ },
394
526
  ] : []
395
527
 
396
528
  const type = await p.select({
@@ -409,6 +541,49 @@ async function promptConfig(initialName?: string, flags?: CreateFlags): Promise<
409
541
  process.exit(1)
410
542
  }
411
543
 
544
+ // Fluxo especial para nimbus-core (projetos externos)
545
+ if (type === "nimbus-core") {
546
+ console.log()
547
+ console.log(pc.dim(" nimbus-core: Motor para projetos externos (stealth mode)"))
548
+ console.log()
549
+
550
+ // Sempre cria repo no GitHub (org nimbuslab, privado)
551
+ const createGithub = await p.confirm({
552
+ message: "Create GitHub repository? (nimbuslab, private)",
553
+ initialValue: true,
554
+ })
555
+ if (p.isCancel(createGithub)) return createGithub
556
+
557
+ // Perguntar URL do repo do cliente para clonar no workspace
558
+ const clientRepo = await p.text({
559
+ message: "Client repo URL (optional, to clone in workspace):",
560
+ placeholder: "git@github.com:client/repo.git",
561
+ })
562
+ if (p.isCancel(clientRepo)) return clientRepo
563
+
564
+ return {
565
+ name: name as string,
566
+ type: "nimbus-core" as ProjectType,
567
+ monorepo: false,
568
+ git: true, // sempre init git
569
+ install: false, // nimbus-core não tem package.json
570
+ github: createGithub as boolean,
571
+ githubOrg: "nimbuslab", // sempre na org nimbuslab
572
+ githubDescription: `nimbus-core for ${name} - external project`,
573
+ theme: "dark" as const,
574
+ aiAssistant: null,
575
+ contractNumber: "",
576
+ resendApiKey: "",
577
+ resendFromEmail: "",
578
+ contactEmail: "",
579
+ railwayProject: "",
580
+ railwayToken: "",
581
+ stagingUrl: "",
582
+ productionUrl: "",
583
+ customTemplate: (clientRepo as string) || null, // reusa campo para URL do cliente
584
+ }
585
+ }
586
+
412
587
  // Pergunta sobre monorepo apenas para fast+
413
588
  let monorepo = false
414
589
  if (type === "fast+") {
@@ -481,8 +656,75 @@ async function promptConfig(initialName?: string, flags?: CreateFlags): Promise<
481
656
  contractNumber = contract as string
482
657
  }
483
658
 
484
- // Templates públicos têm fluxo simplificado
659
+ // Templates públicos têm fluxo simplificado mas com configs globais
485
660
  if (isPublicTemplate) {
661
+ // Theme selection
662
+ const theme = await p.select({
663
+ message: "Default theme:",
664
+ options: [
665
+ { value: "dark", label: "Dark", hint: "recommended" },
666
+ { value: "light", label: "Light" },
667
+ { value: "system", label: "System", hint: "follows OS preference" },
668
+ ],
669
+ })
670
+ if (p.isCancel(theme)) return theme
671
+
672
+ // AI Assistant selection
673
+ const aiAssistant = await p.select({
674
+ message: "Which AI assistant do you use?",
675
+ options: [
676
+ { value: "claude", label: "Claude Code", hint: "Anthropic" },
677
+ { value: "cursor", label: "Cursor", hint: "AI-first editor" },
678
+ { value: "gemini", label: "Gemini CLI", hint: "Google" },
679
+ { value: "copilot", label: "GitHub Copilot" },
680
+ { value: "windsurf", label: "Windsurf", hint: "Codeium" },
681
+ { value: "none", label: "None", hint: "skip AI config" },
682
+ ],
683
+ })
684
+ if (p.isCancel(aiAssistant)) return aiAssistant
685
+
686
+ // GitHub repo for public templates (uses user's orgs)
687
+ let publicGithub = false
688
+ let publicGithubOrg: string | null = null
689
+
690
+ if (git) {
691
+ const gh = await checkGitHubCli()
692
+
693
+ if (gh.installed && gh.authenticated) {
694
+ const createRepo = await p.confirm({
695
+ message: "Create GitHub repository?",
696
+ initialValue: false,
697
+ })
698
+ if (p.isCancel(createRepo)) return createRepo
699
+
700
+ publicGithub = createRepo as boolean
701
+
702
+ if (publicGithub) {
703
+ const repoOptions: { value: string | null; label: string; hint?: string }[] = [
704
+ { value: gh.username, label: gh.username!, hint: "personal account" },
705
+ ...gh.orgs.map((org) => ({ value: org, label: org })),
706
+ ]
707
+
708
+ const repoOwner = await p.select({
709
+ message: "Where to create the repository?",
710
+ options: repoOptions,
711
+ })
712
+ if (p.isCancel(repoOwner)) return repoOwner
713
+ publicGithubOrg = repoOwner as string | null
714
+
715
+ const repoVisibility = await p.select({
716
+ message: "Repository visibility:",
717
+ options: [
718
+ { value: "private", label: "Private", hint: "recommended" },
719
+ { value: "public", label: "Public" },
720
+ ],
721
+ })
722
+ if (p.isCancel(repoVisibility)) return repoVisibility
723
+ githubDescription = repoVisibility as string // reusing for visibility
724
+ }
725
+ }
726
+ }
727
+
486
728
  const install = await p.confirm({
487
729
  message: "Install dependencies?",
488
730
  initialValue: true,
@@ -496,9 +738,11 @@ async function promptConfig(initialName?: string, flags?: CreateFlags): Promise<
496
738
  monorepo: false,
497
739
  git: git as boolean,
498
740
  install: install as boolean,
499
- github,
500
- githubOrg,
741
+ github: publicGithub,
742
+ githubOrg: publicGithubOrg,
501
743
  githubDescription,
744
+ theme: theme as "dark" | "light" | "system",
745
+ aiAssistant: aiAssistant === "none" ? null : aiAssistant as string,
502
746
  contractNumber: "",
503
747
  resendApiKey: "",
504
748
  resendFromEmail: "",
@@ -676,6 +920,8 @@ async function promptConfig(initialName?: string, flags?: CreateFlags): Promise<
676
920
  github,
677
921
  githubOrg,
678
922
  githubDescription,
923
+ theme: "dark" as const,
924
+ aiAssistant: null,
679
925
  contractNumber,
680
926
  resendApiKey,
681
927
  resendFromEmail,
@@ -725,17 +971,73 @@ async function createProject(config: ProjectConfig) {
725
971
  throw new Error(`Failed to clone template ${templateRepo}. Check your connection or repository access.`)
726
972
  }
727
973
 
728
- // Update package.json
729
- s.start("Configuring project...")
730
- try {
731
- const pkgPath = `${config.name}/package.json`
732
- const pkg = await Bun.file(pkgPath).json()
733
- pkg.name = config.name
734
- await Bun.write(pkgPath, JSON.stringify(pkg, null, 2))
735
- s.stop("Project configured")
736
- } catch (error) {
737
- s.stop("Error configuring")
738
- // Continue anyway
974
+ // nimbus-core: clone client repo in workspace if provided
975
+ if (config.type === "nimbus-core" && config.customTemplate) {
976
+ const clientRepoUrl = config.customTemplate
977
+ s.start(`Cloning client repo in workspace...`)
978
+ try {
979
+ const projectName = clientRepoUrl.split("/").pop()?.replace(".git", "") || "client-project"
980
+ await $`git clone ${clientRepoUrl} ${config.name}/workspace/${projectName}`.quiet()
981
+ s.stop(`Client repo cloned: workspace/${projectName}`)
982
+ } catch (error) {
983
+ s.stop("Error cloning client repo")
984
+ console.log(pc.dim(" You can clone manually: cd workspace && git clone <url>"))
985
+ }
986
+ }
987
+
988
+ // Update package.json (skip for nimbus-core)
989
+ if (config.type !== "nimbus-core") {
990
+ s.start("Configuring project...")
991
+ try {
992
+ const pkgPath = `${config.name}/package.json`
993
+ const pkg = await Bun.file(pkgPath).json()
994
+ pkg.name = config.name
995
+ await Bun.write(pkgPath, JSON.stringify(pkg, null, 2))
996
+ s.stop("Project configured")
997
+ } catch (error) {
998
+ s.stop("Error configuring")
999
+ // Continue anyway
1000
+ }
1001
+ }
1002
+
1003
+ // Apply theme config (public templates only)
1004
+ if (isPublicTemplate && config.theme) {
1005
+ s.start(`Setting theme to ${config.theme}...`)
1006
+ try {
1007
+ const layoutPath = `${config.name}/src/app/layout.tsx`
1008
+ let layout = await Bun.file(layoutPath).text()
1009
+ layout = layout.replace(
1010
+ /defaultTheme="(dark|light|system)"/,
1011
+ `defaultTheme="${config.theme}"`
1012
+ )
1013
+ await Bun.write(layoutPath, layout)
1014
+ s.stop(`Theme set to ${config.theme}`)
1015
+ } catch {
1016
+ s.stop("Theme config skipped")
1017
+ }
1018
+ }
1019
+
1020
+ // Generate AI config (public templates only)
1021
+ if (isPublicTemplate && config.aiAssistant) {
1022
+ const aiConfig = AI_CONFIGS[config.aiAssistant]
1023
+ if (aiConfig) {
1024
+ s.start(`Generating ${config.aiAssistant} config...`)
1025
+ try {
1026
+ const content = aiConfig.content(config.type)
1027
+ const filePath = `${config.name}/${aiConfig.filename}`
1028
+
1029
+ // Create directory if needed
1030
+ if (aiConfig.filename.includes("/")) {
1031
+ const dir = aiConfig.filename.split("/").slice(0, -1).join("/")
1032
+ await mkdir(`${config.name}/${dir}`, { recursive: true })
1033
+ }
1034
+
1035
+ await Bun.write(filePath, content)
1036
+ s.stop(`${aiConfig.filename} created`)
1037
+ } catch {
1038
+ s.stop("AI config skipped")
1039
+ }
1040
+ }
739
1041
  }
740
1042
 
741
1043
  // Setup fast+ if selected
@@ -773,11 +1075,24 @@ async function createProject(config: ProjectConfig) {
773
1075
  ? `${config.githubOrg}/${config.name}`
774
1076
  : config.name
775
1077
 
776
- // Create repo with description (private by default for client projects)
777
- const visibility = config.githubOrg === "fast-by-nimbuslab" ? "--private" : "--public"
1078
+ // For public templates, githubDescription contains visibility (private/public)
1079
+ // For private templates, use org-based visibility
1080
+ // nimbus-core is ALWAYS private
1081
+ let visibility: string
1082
+ if (config.type === "nimbus-core") {
1083
+ visibility = "--private" // nimbus-core sempre privado
1084
+ } else if (isPublicTemplate) {
1085
+ visibility = config.githubDescription === "public" ? "--public" : "--private"
1086
+ } else {
1087
+ visibility = config.githubOrg === "fast-by-nimbuslab" ? "--private" : "--public"
1088
+ }
778
1089
 
779
- // Criar repo sem push automático
780
- await $`gh repo create ${repoName} ${visibility} --description ${config.githubDescription} --source . --remote origin`.cwd(cwd).quiet()
1090
+ // Create repo
1091
+ if (isPublicTemplate) {
1092
+ await $`gh repo create ${repoName} ${visibility} --source . --remote origin`.cwd(cwd).quiet()
1093
+ } else {
1094
+ await $`gh repo create ${repoName} ${visibility} --description ${config.githubDescription} --source . --remote origin`.cwd(cwd).quiet()
1095
+ }
781
1096
 
782
1097
  // Push todas as branches na ordem correta: main -> staging -> develop
783
1098
  await $`git checkout main`.cwd(cwd).quiet()
@@ -787,7 +1102,7 @@ async function createProject(config: ProjectConfig) {
787
1102
  await $`git checkout develop`.cwd(cwd).quiet()
788
1103
  await $`git push -u origin develop`.cwd(cwd).quiet()
789
1104
 
790
- s.stop(`GitHub: ${repoName} criado`)
1105
+ s.stop(`GitHub: ${repoName}`)
791
1106
  } catch (error) {
792
1107
  s.stop("Error creating GitHub repository")
793
1108
  console.log(pc.dim(" You can create manually with: gh repo create"))
@@ -877,27 +1192,65 @@ function generateEnvFile(config: ProjectConfig): string {
877
1192
 
878
1193
  function showNextSteps(config: ProjectConfig) {
879
1194
  const isPublicTemplate = ["landing", "app", "turborepo"].includes(config.type)
1195
+ const needsSetup = config.type === "app" // app needs bun setup for DB
880
1196
 
881
1197
  console.log()
882
1198
  console.log(pc.bold("Next steps:"))
883
1199
  console.log()
884
1200
  console.log(` ${pc.cyan("cd")} ${config.name}`)
885
1201
 
1202
+ // nimbus-core tem fluxo especial
1203
+ if (config.type === "nimbus-core") {
1204
+ console.log()
1205
+ console.log(pc.dim(" nimbus-core: Motor para projetos externos"))
1206
+ console.log()
1207
+ console.log(pc.dim(" Para clonar repo do cliente:"))
1208
+ console.log(` ${pc.cyan("cd")} workspace`)
1209
+ console.log(` ${pc.cyan("git clone")} <repo-do-cliente>`)
1210
+ console.log()
1211
+ console.log(pc.dim(" Para usar a Lola:"))
1212
+ console.log(` ${pc.cyan("gemini")} ${pc.dim("# Gemini CLI")}`)
1213
+ console.log(` ${pc.cyan("claude --append-system-prompt-file .claude/agents/lola.md")}`)
1214
+ console.log()
1215
+ console.log(pc.yellow(" STEALTH MODE: Commits sem mencao a nimbuslab/Lola/IA"))
1216
+ console.log()
1217
+
1218
+ // GitHub info
1219
+ if (config.github) {
1220
+ const repoUrl = `https://github.com/nimbuslab/${config.name}`
1221
+ console.log(pc.green(` GitHub (private): ${repoUrl}`))
1222
+ console.log()
1223
+ }
1224
+
1225
+ console.log(pc.dim(" Docs: See README.md for full instructions"))
1226
+ console.log()
1227
+ return
1228
+ }
1229
+
886
1230
  if (!config.install) {
887
1231
  console.log(` ${pc.cyan("bun")} install`)
888
1232
  }
889
1233
 
890
- // Templates públicos não têm bun setup
891
- if (!isPublicTemplate) {
1234
+ // Templates que precisam de setup adicional
1235
+ if (!isPublicTemplate || needsSetup) {
892
1236
  console.log(` ${pc.cyan("bun")} setup`)
893
1237
  }
1238
+
894
1239
  console.log(` ${pc.cyan("bun")} dev`)
895
1240
  console.log()
896
1241
 
1242
+ // Explicar o que o bun setup faz para o app
1243
+ if (needsSetup && isPublicTemplate) {
1244
+ console.log(pc.dim(" bun setup will:"))
1245
+ console.log(pc.dim(" - Start PostgreSQL with Docker"))
1246
+ console.log(pc.dim(" - Run database migrations"))
1247
+ console.log(pc.dim(" - Create demo user (demo@example.com / demo1234)"))
1248
+ console.log()
1249
+ }
1250
+
897
1251
  // Git flow info
898
1252
  if (config.git) {
899
- console.log(pc.dim(" Git flow: main -> staging -> develop (current branch)"))
900
- console.log()
1253
+ console.log(pc.dim(" Git: main -> staging -> develop (current branch)"))
901
1254
 
902
1255
  // GitHub info
903
1256
  if (config.github) {
@@ -905,36 +1258,56 @@ function showNextSteps(config: ProjectConfig) {
905
1258
  ? `https://github.com/${config.githubOrg}/${config.name}`
906
1259
  : `https://github.com/${config.name}`
907
1260
  console.log(pc.green(` GitHub: ${repoUrl}`))
908
- console.log()
909
- } else {
910
- console.log(pc.yellow(" Tip: To create GitHub repo, use 'gh repo create' ou 'bun setup'."))
1261
+ }
1262
+ console.log()
1263
+ }
1264
+
1265
+ // Theme e AI info
1266
+ if (isPublicTemplate) {
1267
+ if (config.theme !== "dark") {
1268
+ console.log(pc.dim(` Theme: ${config.theme}`))
1269
+ }
1270
+ if (config.aiAssistant) {
1271
+ const aiConfig = AI_CONFIGS[config.aiAssistant]
1272
+ if (aiConfig) {
1273
+ console.log(pc.dim(` AI config: ${aiConfig.filename}`))
1274
+ }
1275
+ }
1276
+ if (config.theme !== "dark" || config.aiAssistant) {
911
1277
  console.log()
912
1278
  }
913
1279
  }
914
1280
 
915
1281
  if (config.type === "fast+") {
916
- console.log(pc.dim(" Tip: For fast+, configure DATABASE_URL e BETTER_AUTH_SECRET no .env"))
1282
+ console.log(pc.dim(" bun setup will:"))
1283
+ console.log(pc.dim(" - Start PostgreSQL with Docker"))
1284
+ console.log(pc.dim(" - Run database migrations"))
1285
+ console.log(pc.dim(" - Create demo user (demo@example.com / demo1234)"))
1286
+ console.log()
1287
+ console.log(pc.dim(" Tip: Configure DATABASE_URL and BETTER_AUTH_SECRET in .env"))
917
1288
  if (!config.railwayToken) {
918
1289
  console.log(pc.dim(" Railway: Create a project at https://railway.app/new"))
919
1290
  }
920
1291
  console.log()
921
1292
  }
922
1293
 
923
- // Info sobre .env
924
- if (config.resendApiKey || config.stagingUrl) {
925
- console.log(pc.green(" .env configured successfully!"))
926
- console.log()
927
- } else {
928
- console.log(pc.yellow(" Tip: Configure .env manually or use 'bun setup'."))
929
- console.log()
1294
+ // Info sobre .env (templates privados)
1295
+ if (!isPublicTemplate) {
1296
+ if (config.resendApiKey || config.stagingUrl) {
1297
+ console.log(pc.green(" .env configured!"))
1298
+ console.log()
1299
+ } else {
1300
+ console.log(pc.yellow(" Tip: Configure .env manually or use 'bun setup'."))
1301
+ console.log()
1302
+ }
930
1303
  }
931
1304
 
932
- // Info sobre templates públicos
1305
+ // Info sobre templates
933
1306
  if (isPublicTemplate) {
934
1307
  console.log(pc.dim(" Open source template (MIT) by nimbuslab"))
935
- console.log(pc.dim(` Documentation: https://github.com/nimbuslab/create-next-${config.type === "turborepo" ? "turborepo" : config.type}`))
1308
+ console.log(pc.dim(` https://github.com/nimbuslab/create-next-${config.type === "turborepo" ? "turborepo" : config.type}`))
936
1309
  } else {
937
- console.log(pc.dim(" Documentation: https://github.com/nimbuslab-templates"))
1310
+ console.log(pc.dim(" https://github.com/nimbuslab-templates"))
938
1311
  }
939
1312
  console.log()
940
1313
  }
package/src/index.ts CHANGED
@@ -5,7 +5,7 @@ import pc from "picocolors"
5
5
  import { create } from "./commands/create"
6
6
 
7
7
  const PACKAGE_NAME = "@nimbuslab/cli"
8
- const CURRENT_VERSION = "0.7.0"
8
+ const CURRENT_VERSION = "0.8.0"
9
9
 
10
10
  const LOGO = `
11
11
  ███╗ ██╗██╗███╗ ███╗██████╗ ██╗ ██╗███████╗