create-githat-app 1.0.2 → 1.0.4

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.
Files changed (43) hide show
  1. package/dist/cli.js +316 -99
  2. package/package.json +1 -1
  3. package/templates/fullstack/apps-api-express/.env.example.hbs +6 -0
  4. package/templates/fullstack/apps-api-express/.env.local.hbs +6 -0
  5. package/templates/fullstack/apps-api-express/package.json.hbs +24 -0
  6. package/templates/fullstack/apps-api-express/src/index.ts.hbs +41 -0
  7. package/templates/fullstack/apps-api-express/src/routes/health.ts.hbs +11 -0
  8. package/templates/fullstack/apps-api-express/src/routes/users.ts.hbs +43 -0
  9. package/templates/fullstack/apps-api-express/tsconfig.json.hbs +16 -0
  10. package/templates/fullstack/apps-api-fastify/.env.example.hbs +6 -0
  11. package/templates/fullstack/apps-api-fastify/.env.local.hbs +6 -0
  12. package/templates/fullstack/apps-api-fastify/package.json.hbs +22 -0
  13. package/templates/fullstack/apps-api-fastify/src/index.ts.hbs +28 -0
  14. package/templates/fullstack/apps-api-fastify/src/routes/health.ts.hbs +11 -0
  15. package/templates/fullstack/apps-api-fastify/src/routes/users.ts.hbs +43 -0
  16. package/templates/fullstack/apps-api-fastify/tsconfig.json.hbs +16 -0
  17. package/templates/fullstack/apps-api-hono/.env.example.hbs +6 -0
  18. package/templates/fullstack/apps-api-hono/.env.local.hbs +6 -0
  19. package/templates/fullstack/apps-api-hono/package.json.hbs +22 -0
  20. package/templates/fullstack/apps-api-hono/src/index.ts.hbs +35 -0
  21. package/templates/fullstack/apps-api-hono/src/routes/health.ts.hbs +11 -0
  22. package/templates/fullstack/apps-api-hono/src/routes/users.ts.hbs +43 -0
  23. package/templates/fullstack/apps-api-hono/tsconfig.json.hbs +16 -0
  24. package/templates/fullstack/apps-web-nextjs/.env.example.hbs +5 -0
  25. package/templates/fullstack/apps-web-nextjs/.env.local.hbs +5 -0
  26. package/templates/fullstack/apps-web-nextjs/app/(auth)/forgot-password/page.tsx.hbs +11 -0
  27. package/templates/fullstack/apps-web-nextjs/app/(auth)/reset-password/page.tsx.hbs +11 -0
  28. package/templates/fullstack/apps-web-nextjs/app/(auth)/sign-in/page.tsx.hbs +9 -0
  29. package/templates/fullstack/apps-web-nextjs/app/(auth)/sign-up/page.tsx.hbs +9 -0
  30. package/templates/fullstack/apps-web-nextjs/app/(auth)/verify-email/page.tsx.hbs +11 -0
  31. package/templates/fullstack/apps-web-nextjs/app/dashboard/layout.tsx.hbs +9 -0
  32. package/templates/fullstack/apps-web-nextjs/app/dashboard/page.tsx.hbs +27 -0
  33. package/templates/fullstack/apps-web-nextjs/app/globals.css.hbs +20 -0
  34. package/templates/fullstack/apps-web-nextjs/app/layout.tsx.hbs +26 -0
  35. package/templates/fullstack/apps-web-nextjs/app/page.tsx.hbs +18 -0
  36. package/templates/fullstack/apps-web-nextjs/next.config.ts.hbs +15 -0
  37. package/templates/fullstack/apps-web-nextjs/package.json.hbs +33 -0
  38. package/templates/fullstack/apps-web-nextjs/postcss.config.mjs.hbs +9 -0
  39. package/templates/fullstack/apps-web-nextjs/tsconfig.json.hbs +21 -0
  40. package/templates/fullstack/root/.gitignore.hbs +42 -0
  41. package/templates/fullstack/root/githat.yaml.hbs +17 -0
  42. package/templates/fullstack/root/package.json.hbs +15 -0
  43. package/templates/fullstack/root/turbo.json.hbs +20 -0
package/dist/cli.js CHANGED
@@ -2,7 +2,7 @@ import { createRequire } from 'module'; const require = createRequire(import.met
2
2
 
3
3
  // src/cli.ts
4
4
  import { Command as Command7 } from "commander";
5
- import * as p9 from "@clack/prompts";
5
+ import * as p11 from "@clack/prompts";
6
6
  import chalk9 from "chalk";
7
7
 
8
8
  // src/utils/ascii.ts
@@ -11,7 +11,7 @@ import gradient from "gradient-string";
11
11
  import chalk from "chalk";
12
12
 
13
13
  // src/constants.ts
14
- var VERSION = "0.5.1";
14
+ var VERSION = "1.0.4";
15
15
  var DEFAULT_API_URL = "https://api.githat.io";
16
16
  var DASHBOARD_URL = "https://githat.io/dashboard/apps";
17
17
  var BRAND_COLORS = ["#7c3aed", "#6366f1", "#8b5cf6"];
@@ -21,7 +21,7 @@ var DEPS = {
21
21
  next: "^16.0.0",
22
22
  react: "^19.0.0",
23
23
  "react-dom": "^19.0.0",
24
- "@githat/nextjs": "^0.5.0"
24
+ "@githat/nextjs": "^0.2.2"
25
25
  },
26
26
  devDependencies: {
27
27
  typescript: "^5.9.0",
@@ -35,7 +35,7 @@ var DEPS = {
35
35
  react: "^19.0.0",
36
36
  "react-dom": "^19.0.0",
37
37
  "react-router-dom": "^7.0.0",
38
- "@githat/nextjs": "^0.5.0"
38
+ "@githat/nextjs": "^0.2.2"
39
39
  },
40
40
  devDependencies: {
41
41
  vite: "^7.0.0",
@@ -73,6 +73,48 @@ var DEPS = {
73
73
  "drizzle-sqlite": {
74
74
  dependencies: { "drizzle-orm": "^0.39.0", "better-sqlite3": "^11.0.0" },
75
75
  devDependencies: { "drizzle-kit": "^0.30.0", "@types/better-sqlite3": "^7.6.0" }
76
+ },
77
+ // Backend frameworks for fullstack
78
+ hono: {
79
+ dependencies: {
80
+ hono: "^4.6.0",
81
+ "@hono/node-server": "^1.13.0"
82
+ },
83
+ devDependencies: {
84
+ typescript: "^5.9.0",
85
+ "@types/node": "^22.0.0",
86
+ tsx: "^4.19.0"
87
+ }
88
+ },
89
+ express: {
90
+ dependencies: {
91
+ express: "^5.0.0",
92
+ cors: "^2.8.5"
93
+ },
94
+ devDependencies: {
95
+ typescript: "^5.9.0",
96
+ "@types/node": "^22.0.0",
97
+ "@types/express": "^5.0.0",
98
+ "@types/cors": "^2.8.17",
99
+ tsx: "^4.19.0"
100
+ }
101
+ },
102
+ fastify: {
103
+ dependencies: {
104
+ fastify: "^5.2.0",
105
+ "@fastify/cors": "^10.0.0"
106
+ },
107
+ devDependencies: {
108
+ typescript: "^5.9.0",
109
+ "@types/node": "^22.0.0",
110
+ tsx: "^4.19.0"
111
+ }
112
+ },
113
+ // Turborepo for fullstack monorepo
114
+ turbo: {
115
+ devDependencies: {
116
+ turbo: "^2.3.0"
117
+ }
76
118
  }
77
119
  };
78
120
 
@@ -123,35 +165,59 @@ function sectionHeader(title) {
123
165
  console.log(dim(` \u2500\u2500\u2500 ${title} ${"\u2500".repeat(lineLen)}`));
124
166
  console.log("");
125
167
  }
126
- function displaySuccess(projectName, packageManager, framework, hasPublishableKey = true) {
168
+ function displaySuccess(projectName, packageManager, framework, hasPublishableKey = true, isFullstack = false) {
127
169
  const devCmd = packageManager === "npm" ? "npm run dev" : `${packageManager} dev`;
128
170
  const port = framework === "react-vite" ? "5173" : "3000";
129
171
  console.log("");
130
- drawBox([
131
- `${violet("\u2726")} Your GitHat app is ready!`,
132
- "",
133
- `${violet("$")} cd ${projectName}`,
134
- `${violet("$")} ${devCmd}`,
135
- "",
136
- dim(`\u2192 http://localhost:${port}`),
137
- "",
138
- chalk.bold("Routes"),
139
- `${violet("/sign-in")} Sign in`,
140
- `${violet("/sign-up")} Create account`,
141
- `${violet("/dashboard")} Protected dashboard`,
142
- "",
143
- ...hasPublishableKey ? [] : [
144
- chalk.yellow("No key configured \u2014 auth works on localhost."),
145
- `For production: ${violet("githat.io/dashboard/apps")}`,
146
- ""
147
- ],
148
- dim("Docs \u2192 https://githat.io/docs/sdk")
149
- ]);
172
+ if (isFullstack) {
173
+ drawBox([
174
+ `${violet("\u2726")} Your GitHat fullstack app is ready!`,
175
+ "",
176
+ `${violet("$")} cd ${projectName}`,
177
+ `${violet("$")} ${devCmd}`,
178
+ "",
179
+ dim(`\u2192 Web: http://localhost:3000`),
180
+ dim(`\u2192 API: http://localhost:3001`),
181
+ "",
182
+ chalk.bold("Structure"),
183
+ `${violet("apps/web")} Next.js frontend`,
184
+ `${violet("apps/api")} API backend`,
185
+ `${violet("packages/")} Shared code`,
186
+ "",
187
+ ...hasPublishableKey ? [] : [
188
+ chalk.yellow("No key configured \u2014 auth works on localhost."),
189
+ `For production: ${violet("githat.io/dashboard/apps")}`,
190
+ ""
191
+ ],
192
+ dim("Docs \u2192 https://githat.io/docs/sdk")
193
+ ]);
194
+ } else {
195
+ drawBox([
196
+ `${violet("\u2726")} Your GitHat app is ready!`,
197
+ "",
198
+ `${violet("$")} cd ${projectName}`,
199
+ `${violet("$")} ${devCmd}`,
200
+ "",
201
+ dim(`\u2192 http://localhost:${port}`),
202
+ "",
203
+ chalk.bold("Routes"),
204
+ `${violet("/sign-in")} Sign in`,
205
+ `${violet("/sign-up")} Create account`,
206
+ `${violet("/dashboard")} Protected dashboard`,
207
+ "",
208
+ ...hasPublishableKey ? [] : [
209
+ chalk.yellow("No key configured \u2014 auth works on localhost."),
210
+ `For production: ${violet("githat.io/dashboard/apps")}`,
211
+ ""
212
+ ],
213
+ dim("Docs \u2192 https://githat.io/docs/sdk")
214
+ ]);
215
+ }
150
216
  console.log("");
151
217
  }
152
218
 
153
219
  // src/prompts/index.ts
154
- import * as p6 from "@clack/prompts";
220
+ import * as p8 from "@clack/prompts";
155
221
 
156
222
  // src/prompts/project.ts
157
223
  import * as p from "@clack/prompts";
@@ -204,8 +270,41 @@ async function promptProject(initialName) {
204
270
  };
205
271
  }
206
272
 
207
- // src/prompts/framework.ts
273
+ // src/prompts/project-type.ts
208
274
  import * as p2 from "@clack/prompts";
275
+ async function promptProjectType() {
276
+ const answers = await p2.group(
277
+ {
278
+ projectType: () => p2.select({
279
+ message: "Project type",
280
+ options: [
281
+ {
282
+ value: "frontend",
283
+ label: "Frontend only",
284
+ hint: "Next.js with API routes"
285
+ },
286
+ {
287
+ value: "fullstack",
288
+ label: "Fullstack",
289
+ hint: "Next.js + separate API (Turborepo)"
290
+ }
291
+ ]
292
+ })
293
+ },
294
+ {
295
+ onCancel: () => {
296
+ p2.cancel("Setup cancelled.");
297
+ process.exit(0);
298
+ }
299
+ }
300
+ );
301
+ return {
302
+ projectType: answers.projectType
303
+ };
304
+ }
305
+
306
+ // src/prompts/framework.ts
307
+ import * as p3 from "@clack/prompts";
209
308
 
210
309
  // src/utils/package-manager.ts
211
310
  function detectPackageManager() {
@@ -228,16 +327,16 @@ function getInstallCommand(pm) {
228
327
  // src/prompts/framework.ts
229
328
  async function promptFramework(typescriptOverride) {
230
329
  const packageManager = detectPackageManager();
231
- const answers = await p2.group(
330
+ const answers = await p3.group(
232
331
  {
233
- framework: () => p2.select({
332
+ framework: () => p3.select({
234
333
  message: "Framework",
235
334
  options: [
236
335
  { value: "nextjs", label: "Next.js 16", hint: "App Router \xB7 SSR \xB7 middleware auth" },
237
336
  { value: "react-vite", label: "React 19 + Vite 7", hint: "SPA \xB7 client-side routing" }
238
337
  ]
239
338
  }),
240
- typescript: () => typescriptOverride !== void 0 ? Promise.resolve(typescriptOverride) : p2.select({
339
+ typescript: () => typescriptOverride !== void 0 ? Promise.resolve(typescriptOverride) : p3.select({
241
340
  message: "Language",
242
341
  options: [
243
342
  { value: true, label: "TypeScript", hint: "recommended" },
@@ -247,7 +346,7 @@ async function promptFramework(typescriptOverride) {
247
346
  },
248
347
  {
249
348
  onCancel: () => {
250
- p2.cancel("Setup cancelled.");
349
+ p3.cancel("Setup cancelled.");
251
350
  process.exit(0);
252
351
  }
253
352
  }
@@ -259,9 +358,47 @@ async function promptFramework(typescriptOverride) {
259
358
  };
260
359
  }
261
360
 
361
+ // src/prompts/backend.ts
362
+ import * as p4 from "@clack/prompts";
363
+ async function promptBackend() {
364
+ const answers = await p4.group(
365
+ {
366
+ backendFramework: () => p4.select({
367
+ message: "Backend framework",
368
+ options: [
369
+ {
370
+ value: "hono",
371
+ label: "Hono",
372
+ hint: "recommended \xB7 serverless-native \xB7 14KB"
373
+ },
374
+ {
375
+ value: "express",
376
+ label: "Express",
377
+ hint: "classic Node.js \xB7 large ecosystem"
378
+ },
379
+ {
380
+ value: "fastify",
381
+ label: "Fastify",
382
+ hint: "high performance \xB7 schema validation"
383
+ }
384
+ ]
385
+ })
386
+ },
387
+ {
388
+ onCancel: () => {
389
+ p4.cancel("Setup cancelled.");
390
+ process.exit(0);
391
+ }
392
+ }
393
+ );
394
+ return {
395
+ backendFramework: answers.backendFramework
396
+ };
397
+ }
398
+
262
399
  // src/prompts/githat.ts
263
400
  import { execSync } from "child_process";
264
- import * as p3 from "@clack/prompts";
401
+ import * as p5 from "@clack/prompts";
265
402
  function openBrowser(url) {
266
403
  try {
267
404
  const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
@@ -272,7 +409,7 @@ function openBrowser(url) {
272
409
  async function promptGitHat(existingKey) {
273
410
  let publishableKey = existingKey || "";
274
411
  if (!publishableKey) {
275
- const connectChoice = await p3.select({
412
+ const connectChoice = await p5.select({
276
413
  message: "Connect to GitHat",
277
414
  options: [
278
415
  { value: "browser", label: "Sign in with browser", hint: "opens githat.io \u2014 recommended" },
@@ -280,41 +417,41 @@ async function promptGitHat(existingKey) {
280
417
  { value: "skip", label: "Skip for now", hint: "add key to .env later" }
281
418
  ]
282
419
  });
283
- if (p3.isCancel(connectChoice)) {
284
- p3.cancel("Setup cancelled.");
420
+ if (p5.isCancel(connectChoice)) {
421
+ p5.cancel("Setup cancelled.");
285
422
  process.exit(0);
286
423
  }
287
424
  if (connectChoice === "browser") {
288
- p3.log.step("Opening githat.io in your browser...");
425
+ p5.log.step("Opening githat.io in your browser...");
289
426
  openBrowser("https://githat.io/sign-up");
290
- p3.log.info("Sign up (or sign in), then go to Dashboard \u2192 Apps to copy your key.");
291
- const pastedKey = await p3.text({
427
+ p5.log.info("Sign up (or sign in), then go to Dashboard \u2192 Apps to copy your key.");
428
+ const pastedKey = await p5.text({
292
429
  message: "Paste your publishable key",
293
430
  placeholder: "pk_live_...",
294
431
  validate: validatePublishableKey
295
432
  });
296
- if (p3.isCancel(pastedKey)) {
297
- p3.cancel("Setup cancelled.");
433
+ if (p5.isCancel(pastedKey)) {
434
+ p5.cancel("Setup cancelled.");
298
435
  process.exit(0);
299
436
  }
300
437
  publishableKey = pastedKey || "";
301
438
  } else if (connectChoice === "paste") {
302
- const pastedKey = await p3.text({
439
+ const pastedKey = await p5.text({
303
440
  message: "Publishable key",
304
441
  placeholder: `pk_live_... (get one at ${DASHBOARD_URL})`,
305
442
  validate: validatePublishableKey
306
443
  });
307
- if (p3.isCancel(pastedKey)) {
308
- p3.cancel("Setup cancelled.");
444
+ if (p5.isCancel(pastedKey)) {
445
+ p5.cancel("Setup cancelled.");
309
446
  process.exit(0);
310
447
  }
311
448
  publishableKey = pastedKey || "";
312
449
  } else if (connectChoice === "skip") {
313
- p3.log.info("Auth works on localhost without a key (CORS bypass for development).");
314
- p3.log.info("Sign up at githat.io \u2014 a publishable key is auto-created for you.");
450
+ p5.log.info("Auth works on localhost without a key (CORS bypass for development).");
451
+ p5.log.info("Sign up at githat.io \u2014 a publishable key is auto-created for you.");
315
452
  }
316
453
  }
317
- const authFeatures = await p3.multiselect({
454
+ const authFeatures = await p5.multiselect({
318
455
  message: "Auth features",
319
456
  options: [
320
457
  { value: "forgot-password", label: "Forgot password", hint: "reset via email" },
@@ -326,8 +463,8 @@ async function promptGitHat(existingKey) {
326
463
  initialValues: ["forgot-password"],
327
464
  required: false
328
465
  });
329
- if (p3.isCancel(authFeatures)) {
330
- p3.cancel("Setup cancelled.");
466
+ if (p5.isCancel(authFeatures)) {
467
+ p5.cancel("Setup cancelled.");
331
468
  process.exit(0);
332
469
  }
333
470
  return {
@@ -338,11 +475,11 @@ async function promptGitHat(existingKey) {
338
475
  }
339
476
 
340
477
  // src/prompts/features.ts
341
- import * as p4 from "@clack/prompts";
478
+ import * as p6 from "@clack/prompts";
342
479
  async function promptFeatures() {
343
- const answers = await p4.group(
480
+ const answers = await p6.group(
344
481
  {
345
- databaseChoice: () => p4.select({
482
+ databaseChoice: () => p6.select({
346
483
  message: "Database",
347
484
  options: [
348
485
  { value: "none", label: "None", hint: "use GitHat backend directly" },
@@ -352,12 +489,12 @@ async function promptFeatures() {
352
489
  { value: "drizzle-sqlite", label: "Drizzle + SQLite" }
353
490
  ]
354
491
  }),
355
- useTailwind: () => p4.confirm({ message: "Tailwind CSS?", initialValue: true }),
356
- includeDashboard: () => p4.confirm({ message: "Include dashboard?", initialValue: true })
492
+ useTailwind: () => p6.confirm({ message: "Tailwind CSS?", initialValue: true }),
493
+ includeDashboard: () => p6.confirm({ message: "Include dashboard?", initialValue: true })
357
494
  },
358
495
  {
359
496
  onCancel: () => {
360
- p4.cancel("Setup cancelled.");
497
+ p6.cancel("Setup cancelled.");
361
498
  process.exit(0);
362
499
  }
363
500
  }
@@ -371,14 +508,14 @@ async function promptFeatures() {
371
508
  }
372
509
 
373
510
  // src/prompts/finalize.ts
374
- import * as p5 from "@clack/prompts";
511
+ import * as p7 from "@clack/prompts";
375
512
  async function promptFinalize() {
376
- const installDeps = await p5.confirm({
513
+ const installDeps = await p7.confirm({
377
514
  message: "Install dependencies?",
378
515
  initialValue: true
379
516
  });
380
- if (p5.isCancel(installDeps)) {
381
- p5.cancel("Setup cancelled.");
517
+ if (p7.isCancel(installDeps)) {
518
+ p7.cancel("Setup cancelled.");
382
519
  process.exit(0);
383
520
  }
384
521
  return {
@@ -388,19 +525,52 @@ async function promptFinalize() {
388
525
  }
389
526
 
390
527
  // src/prompts/index.ts
528
+ function toDisplayName(projectName) {
529
+ return projectName.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
530
+ }
531
+ function getDefaults(projectName, publishableKey, typescript) {
532
+ const displayName = toDisplayName(projectName);
533
+ return {
534
+ projectName,
535
+ businessName: displayName,
536
+ description: `${displayName} \u2014 Built with GitHat`,
537
+ projectType: "frontend",
538
+ framework: "nextjs",
539
+ typescript: typescript ?? true,
540
+ packageManager: detectPackageManager(),
541
+ publishableKey: publishableKey || "",
542
+ apiUrl: DEFAULT_API_URL,
543
+ authFeatures: ["forgot-password"],
544
+ databaseChoice: "none",
545
+ useTailwind: true,
546
+ includeDashboard: true,
547
+ includeGithatFolder: true,
548
+ initGit: true,
549
+ installDeps: true
550
+ };
551
+ }
391
552
  async function runPrompts(args) {
392
- p6.intro("Let\u2019s set up your GitHat app");
553
+ if (args.yes && args.initialName) {
554
+ p8.log.info("Using defaults (--yes flag)");
555
+ return getDefaults(args.initialName, args.publishableKey, args.typescript);
556
+ }
557
+ p8.intro("Let's set up your GitHat app");
393
558
  sectionHeader("Project");
394
559
  const project = await promptProject(args.initialName);
560
+ const projectType = await promptProjectType();
395
561
  sectionHeader("Stack");
396
562
  const framework = await promptFramework(args.typescript);
563
+ let backend = {};
564
+ if (projectType.projectType === "fullstack") {
565
+ backend = await promptBackend();
566
+ }
397
567
  sectionHeader("Connect");
398
568
  const githat = await promptGitHat(args.publishableKey);
399
569
  sectionHeader("Features");
400
570
  const features = await promptFeatures();
401
571
  sectionHeader("Finish");
402
572
  const finalize = await promptFinalize();
403
- return { ...project, ...framework, ...githat, ...features, ...finalize };
573
+ return { ...project, ...projectType, ...framework, ...backend, ...githat, ...features, ...finalize };
404
574
  }
405
575
  function answersToContext(answers) {
406
576
  return {
@@ -412,6 +582,9 @@ function answersToContext(answers) {
412
582
  packageManager: answers.packageManager,
413
583
  publishableKey: answers.publishableKey,
414
584
  apiUrl: answers.apiUrl,
585
+ // Project type
586
+ projectType: answers.projectType,
587
+ backendFramework: answers.backendFramework,
415
588
  useDatabase: answers.databaseChoice !== "none",
416
589
  databaseChoice: answers.databaseChoice,
417
590
  useTailwind: answers.useTailwind,
@@ -431,7 +604,7 @@ function answersToContext(answers) {
431
604
  import fs3 from "fs-extra";
432
605
  import path3 from "path";
433
606
  import { execSync as execSync3 } from "child_process";
434
- import * as p7 from "@clack/prompts";
607
+ import * as p9 from "@clack/prompts";
435
608
  import chalk2 from "chalk";
436
609
 
437
610
  // src/utils/template-engine.ts
@@ -584,26 +757,33 @@ function initGit(cwd) {
584
757
  async function scaffold(context, options) {
585
758
  const root = path3.resolve(process.cwd(), context.projectName);
586
759
  if (fs3.existsSync(root)) {
587
- p7.cancel(`Directory "${context.projectName}" already exists.`);
760
+ p9.cancel(`Directory "${context.projectName}" already exists.`);
588
761
  process.exit(1);
589
762
  }
763
+ const isFullstack = context.projectType === "fullstack";
590
764
  await withSpinner("Creating project structure...", async () => {
591
765
  fs3.ensureDirSync(root);
592
766
  const templatesRoot = getTemplatesRoot();
593
- const frameworkDir = path3.join(templatesRoot, context.framework);
594
- if (!fs3.existsSync(frameworkDir)) {
595
- throw new Error(`Templates not found at ${frameworkDir}. This is a bug \u2014 please report it.`);
596
- }
597
- renderTemplateDirectory(frameworkDir, root, context);
598
- const baseDir = path3.join(templatesRoot, "base");
599
- if (fs3.existsSync(baseDir)) {
600
- renderTemplateDirectory(baseDir, root, context);
767
+ if (isFullstack) {
768
+ scaffoldFullstack(templatesRoot, root, context);
769
+ } else {
770
+ const frameworkDir = path3.join(templatesRoot, context.framework);
771
+ if (!fs3.existsSync(frameworkDir)) {
772
+ throw new Error(`Templates not found at ${frameworkDir}. This is a bug \u2014 please report it.`);
773
+ }
774
+ renderTemplateDirectory(frameworkDir, root, context);
775
+ const baseDir = path3.join(templatesRoot, "base");
776
+ if (fs3.existsSync(baseDir)) {
777
+ renderTemplateDirectory(baseDir, root, context);
778
+ }
601
779
  }
602
780
  }, "Project structure created");
603
- await withSpinner("Generating package.json...", async () => {
604
- const pkg = buildPackageJson(context);
605
- writeJson(root, "package.json", pkg);
606
- }, "package.json generated");
781
+ if (!isFullstack) {
782
+ await withSpinner("Generating package.json...", async () => {
783
+ const pkg = buildPackageJson(context);
784
+ writeJson(root, "package.json", pkg);
785
+ }, "package.json generated");
786
+ }
607
787
  if (options.initGit) {
608
788
  const gitSpinner = createSpinner("Initializing git repository...");
609
789
  gitSpinner.start();
@@ -624,30 +804,61 @@ async function scaffold(context, options) {
624
804
  } catch (err) {
625
805
  const msg = err.message || "";
626
806
  if (msg.includes("TIMEOUT")) {
627
- p7.log.warn(`Install timed out. Run ${chalk2.cyan(installCmd)} manually.`);
807
+ p9.log.warn(`Install timed out. Run ${chalk2.cyan(installCmd)} manually.`);
628
808
  } else {
629
- p7.log.warn(`Could not auto-install. Run ${chalk2.cyan(installCmd)} manually.`);
809
+ p9.log.warn(`Could not auto-install. Run ${chalk2.cyan(installCmd)} manually.`);
630
810
  }
631
811
  }
632
812
  },
633
813
  "Dependencies installed"
634
814
  );
635
815
  }
636
- p7.outro("Setup complete!");
637
- displaySuccess(context.projectName, context.packageManager, context.framework, !!context.publishableKey);
638
- const starPrompt = await p7.confirm({
639
- message: "Star GitHat on GitHub? (helps us grow!)",
640
- initialValue: false
641
- });
642
- if (!p7.isCancel(starPrompt) && starPrompt) {
643
- try {
644
- const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
645
- execSync3(`${cmd} "https://github.com/GitHat-IO/githat"`, { stdio: "ignore" });
646
- } catch {
647
- p7.log.info("Visit https://github.com/GitHat-IO/githat to star us!");
816
+ p9.outro("Setup complete!");
817
+ displaySuccess(context.projectName, context.packageManager, context.framework, !!context.publishableKey, isFullstack);
818
+ if (!options.skipPrompts) {
819
+ const starPrompt = await p9.confirm({
820
+ message: "Star GitHat on GitHub? (helps us grow!)",
821
+ initialValue: false
822
+ });
823
+ if (!p9.isCancel(starPrompt) && starPrompt) {
824
+ try {
825
+ const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
826
+ execSync3(`${cmd} "https://github.com/GitHat-IO/githat"`, { stdio: "ignore" });
827
+ } catch {
828
+ p9.log.info("Visit https://github.com/GitHat-IO/githat to star us!");
829
+ }
648
830
  }
649
831
  }
650
832
  }
833
+ function scaffoldFullstack(templatesRoot, root, context) {
834
+ const fullstackDir = path3.join(templatesRoot, "fullstack");
835
+ const rootDir = path3.join(fullstackDir, "root");
836
+ if (fs3.existsSync(rootDir)) {
837
+ renderTemplateDirectory(rootDir, root, context);
838
+ }
839
+ const appsDir = path3.join(root, "apps");
840
+ fs3.ensureDirSync(appsDir);
841
+ const webDir = path3.join(appsDir, "web");
842
+ fs3.ensureDirSync(webDir);
843
+ const webTemplateDir = path3.join(fullstackDir, `apps-web-${context.framework}`);
844
+ if (fs3.existsSync(webTemplateDir)) {
845
+ renderTemplateDirectory(webTemplateDir, webDir, context);
846
+ } else {
847
+ throw new Error(`Web app templates not found at ${webTemplateDir}. This is a bug \u2014 please report it.`);
848
+ }
849
+ const apiDir = path3.join(appsDir, "api");
850
+ fs3.ensureDirSync(apiDir);
851
+ const backendFramework = context.backendFramework || "hono";
852
+ const apiTemplateDir = path3.join(fullstackDir, `apps-api-${backendFramework}`);
853
+ if (fs3.existsSync(apiTemplateDir)) {
854
+ renderTemplateDirectory(apiTemplateDir, apiDir, context);
855
+ } else {
856
+ throw new Error(`API templates not found at ${apiTemplateDir}. This is a bug \u2014 please report it.`);
857
+ }
858
+ const packagesDir = path3.join(root, "packages");
859
+ fs3.ensureDirSync(packagesDir);
860
+ fs3.writeFileSync(path3.join(packagesDir, ".gitkeep"), "");
861
+ }
651
862
 
652
863
  // src/commands/skills/index.ts
653
864
  import { Command as Command6 } from "commander";
@@ -967,7 +1178,7 @@ import { Command as Command5 } from "commander";
967
1178
  import chalk7 from "chalk";
968
1179
  import * as fs6 from "fs";
969
1180
  import * as path6 from "path";
970
- import * as p8 from "@clack/prompts";
1181
+ import * as p10 from "@clack/prompts";
971
1182
  var SKILL_TYPES = ["template", "integration", "ui", "ai", "workflow"];
972
1183
  function generateReadme(manifest) {
973
1184
  return `# ${manifest.name}
@@ -1115,7 +1326,7 @@ var initCommand = new Command5("init").description("Initialize a new skill packa
1115
1326
  if (options.type && SKILL_TYPES.includes(options.type)) {
1116
1327
  type = options.type;
1117
1328
  } else {
1118
- const result = await p8.select({
1329
+ const result = await p10.select({
1119
1330
  message: "What type of skill are you creating?",
1120
1331
  options: [
1121
1332
  { value: "integration", label: "Integration", hint: "Connect to external services (Stripe, SendGrid, etc.)" },
@@ -1125,19 +1336,19 @@ var initCommand = new Command5("init").description("Initialize a new skill packa
1125
1336
  { value: "workflow", label: "Workflow", hint: "Automation recipes" }
1126
1337
  ]
1127
1338
  });
1128
- if (p8.isCancel(result)) {
1129
- p8.cancel("Operation cancelled");
1339
+ if (p10.isCancel(result)) {
1340
+ p10.cancel("Operation cancelled");
1130
1341
  process.exit(0);
1131
1342
  }
1132
1343
  type = result;
1133
1344
  }
1134
- const description = await p8.text({
1345
+ const description = await p10.text({
1135
1346
  message: "Short description:",
1136
1347
  placeholder: `A ${type} skill for...`,
1137
1348
  validate: (v) => v.length < 10 ? "Description must be at least 10 characters" : void 0
1138
1349
  });
1139
- if (p8.isCancel(description)) {
1140
- p8.cancel("Operation cancelled");
1350
+ if (p10.isCancel(description)) {
1351
+ p10.cancel("Operation cancelled");
1141
1352
  process.exit(0);
1142
1353
  }
1143
1354
  const manifest = {
@@ -1225,22 +1436,28 @@ skillsCommand.action(() => {
1225
1436
  // src/cli.ts
1226
1437
  var program = new Command7();
1227
1438
  program.name("githat").description("GitHat CLI - Scaffold apps and manage skills").version(VERSION);
1228
- program.command("create [project-name]", { isDefault: true }).description("Scaffold a new GitHat app").option("--key <key>", "GitHat publishable key (pk_live_...)").option("--ts", "Use TypeScript (default)").option("--js", "Use JavaScript").action(async (projectName, opts) => {
1439
+ program.command("create [project-name]", { isDefault: true }).description("Scaffold a new GitHat app").option("--key <key>", "GitHat publishable key (pk_live_...)").option("--ts", "Use TypeScript (default)").option("--js", "Use JavaScript").option("-y, --yes", "Skip prompts and use defaults").action(async (projectName, opts) => {
1229
1440
  try {
1230
1441
  displayBanner();
1231
1442
  const typescript = opts.js ? false : opts.ts ? true : void 0;
1443
+ if (opts.yes && !projectName) {
1444
+ p11.cancel(chalk9.red("Project name is required when using --yes flag"));
1445
+ process.exit(1);
1446
+ }
1232
1447
  const answers = await runPrompts({
1233
1448
  initialName: projectName,
1234
1449
  publishableKey: opts.key,
1235
- typescript
1450
+ typescript,
1451
+ yes: opts.yes
1236
1452
  });
1237
1453
  const context = answersToContext(answers);
1238
1454
  await scaffold(context, {
1239
1455
  installDeps: answers.installDeps,
1240
- initGit: answers.initGit
1456
+ initGit: answers.initGit,
1457
+ skipPrompts: opts.yes
1241
1458
  });
1242
1459
  } catch (err) {
1243
- p9.cancel(chalk9.red(err.message || "Something went wrong."));
1460
+ p11.cancel(chalk9.red(err.message || "Something went wrong."));
1244
1461
  process.exit(1);
1245
1462
  }
1246
1463
  });