create-githat-app 1.0.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.
Files changed (55) hide show
  1. package/LICENSE +18 -0
  2. package/README.md +259 -0
  3. package/bin/index.js +6 -0
  4. package/dist/cli.js +623 -0
  5. package/package.json +42 -0
  6. package/templates/base/.env.example.hbs +13 -0
  7. package/templates/base/.env.local.hbs +10 -0
  8. package/templates/base/.gitignore.hbs +11 -0
  9. package/templates/base/githat/api/agents.ts.hbs +17 -0
  10. package/templates/base/githat/api/auth.ts.hbs +38 -0
  11. package/templates/base/githat/api/client.ts.hbs +92 -0
  12. package/templates/base/githat/api/mcp.ts.hbs +21 -0
  13. package/templates/base/githat/api/orgs.ts.hbs +34 -0
  14. package/templates/base/githat/api/types.ts.hbs +70 -0
  15. package/templates/base/githat/api/users.ts.hbs +14 -0
  16. package/templates/base/githat/auth/guard.tsx.hbs +53 -0
  17. package/templates/base/githat/auth/index.ts.hbs +3 -0
  18. package/templates/base/githat/config.ts.hbs +18 -0
  19. package/templates/base/githat/dashboard/agents.tsx.hbs +121 -0
  20. package/templates/base/githat/dashboard/apps.tsx.hbs +53 -0
  21. package/templates/base/githat/dashboard/layout.tsx.hbs +83 -0
  22. package/templates/base/githat/dashboard/mcp-servers.tsx.hbs +111 -0
  23. package/templates/base/githat/dashboard/members.tsx.hbs +123 -0
  24. package/templates/base/githat/dashboard/overview.tsx.hbs +37 -0
  25. package/templates/base/githat/dashboard/settings.tsx.hbs +87 -0
  26. package/templates/nextjs/app/(auth)/forgot-password/page.tsx.hbs +63 -0
  27. package/templates/nextjs/app/(auth)/sign-in/page.tsx.hbs +9 -0
  28. package/templates/nextjs/app/(auth)/sign-up/page.tsx.hbs +9 -0
  29. package/templates/nextjs/app/(auth)/verify-email/page.tsx.hbs +58 -0
  30. package/templates/nextjs/app/dashboard/agents/page.tsx.hbs +9 -0
  31. package/templates/nextjs/app/dashboard/apps/page.tsx.hbs +9 -0
  32. package/templates/nextjs/app/dashboard/layout.tsx.hbs +28 -0
  33. package/templates/nextjs/app/dashboard/mcp/page.tsx.hbs +9 -0
  34. package/templates/nextjs/app/dashboard/members/page.tsx.hbs +9 -0
  35. package/templates/nextjs/app/dashboard/page.tsx.hbs +30 -0
  36. package/templates/nextjs/app/dashboard/settings/page.tsx.hbs +9 -0
  37. package/templates/nextjs/app/globals.css.hbs +20 -0
  38. package/templates/nextjs/app/layout.tsx.hbs +33 -0
  39. package/templates/nextjs/app/page.tsx.hbs +18 -0
  40. package/templates/nextjs/middleware.ts.hbs +10 -0
  41. package/templates/nextjs/next.config.ts.hbs +5 -0
  42. package/templates/nextjs/postcss.config.mjs.hbs +9 -0
  43. package/templates/nextjs/tsconfig.json.hbs +21 -0
  44. package/templates/react-vite/index.html.hbs +12 -0
  45. package/templates/react-vite/src/App.tsx.hbs +46 -0
  46. package/templates/react-vite/src/index.css.hbs +20 -0
  47. package/templates/react-vite/src/main.tsx.hbs +30 -0
  48. package/templates/react-vite/src/pages/Dashboard.tsx.hbs +68 -0
  49. package/templates/react-vite/src/pages/ForgotPassword.tsx.hbs +58 -0
  50. package/templates/react-vite/src/pages/Home.tsx.hbs +18 -0
  51. package/templates/react-vite/src/pages/SignIn.tsx.hbs +9 -0
  52. package/templates/react-vite/src/pages/SignUp.tsx.hbs +9 -0
  53. package/templates/react-vite/src/pages/VerifyEmail.tsx.hbs +51 -0
  54. package/templates/react-vite/tsconfig.json.hbs +16 -0
  55. package/templates/react-vite/vite.config.ts.hbs +14 -0
package/dist/cli.js ADDED
@@ -0,0 +1,623 @@
1
+ import { createRequire } from 'module'; const require = createRequire(import.meta.url);
2
+
3
+ // src/cli.ts
4
+ import { Command } from "commander";
5
+ import * as p8 from "@clack/prompts";
6
+ import chalk3 from "chalk";
7
+
8
+ // src/utils/ascii.ts
9
+ import figlet from "figlet";
10
+ import gradient from "gradient-string";
11
+ import chalk from "chalk";
12
+
13
+ // src/constants.ts
14
+ var VERSION = "1.0.0";
15
+ var DEFAULT_API_URL = "https://api.githat.io";
16
+ var DASHBOARD_URL = "https://githat.io/dashboard/apps";
17
+ var BRAND_COLORS = ["#7c3aed", "#6366f1", "#8b5cf6"];
18
+ var DEPS = {
19
+ nextjs: {
20
+ dependencies: {
21
+ next: "^16.0.0",
22
+ react: "^19.0.0",
23
+ "react-dom": "^19.0.0",
24
+ "@githat/nextjs": "^0.2.0"
25
+ },
26
+ devDependencies: {
27
+ typescript: "^5.9.0",
28
+ "@types/react": "^19.0.0",
29
+ "@types/react-dom": "^19.0.0",
30
+ "@types/node": "^22.0.0"
31
+ }
32
+ },
33
+ "react-vite": {
34
+ dependencies: {
35
+ react: "^19.0.0",
36
+ "react-dom": "^19.0.0",
37
+ "react-router-dom": "^7.0.0",
38
+ "@githat/nextjs": "^0.2.0"
39
+ },
40
+ devDependencies: {
41
+ vite: "^7.0.0",
42
+ "@vitejs/plugin-react": "^4.4.0",
43
+ typescript: "^5.9.0",
44
+ "@types/react": "^19.0.0",
45
+ "@types/react-dom": "^19.0.0"
46
+ }
47
+ },
48
+ tailwind: {
49
+ devDependencies: {
50
+ tailwindcss: "^4.0.0",
51
+ "@tailwindcss/postcss": "^4.0.0",
52
+ postcss: "^8.4.0"
53
+ }
54
+ },
55
+ "tailwind-vite": {
56
+ devDependencies: {
57
+ tailwindcss: "^4.0.0",
58
+ "@tailwindcss/vite": "^4.0.0"
59
+ }
60
+ },
61
+ "prisma-postgres": {
62
+ dependencies: { "@prisma/client": "^6.4.0" },
63
+ devDependencies: { prisma: "^6.4.0" }
64
+ },
65
+ "prisma-mysql": {
66
+ dependencies: { "@prisma/client": "^6.4.0" },
67
+ devDependencies: { prisma: "^6.4.0" }
68
+ },
69
+ "drizzle-postgres": {
70
+ dependencies: { "drizzle-orm": "^0.39.0", postgres: "^3.4.0" },
71
+ devDependencies: { "drizzle-kit": "^0.30.0" }
72
+ },
73
+ "drizzle-sqlite": {
74
+ dependencies: { "drizzle-orm": "^0.39.0", "better-sqlite3": "^11.0.0" },
75
+ devDependencies: { "drizzle-kit": "^0.30.0", "@types/better-sqlite3": "^7.6.0" }
76
+ }
77
+ };
78
+
79
+ // src/utils/ascii.ts
80
+ var githatGradient = gradient([...BRAND_COLORS]);
81
+ function displayBanner() {
82
+ const ascii = figlet.textSync("GitHat", {
83
+ font: "Slant",
84
+ horizontalLayout: "default"
85
+ });
86
+ console.log("");
87
+ console.log(githatGradient(ascii));
88
+ console.log(chalk.dim(" Identity for humans, agents, and MCP servers"));
89
+ console.log(chalk.dim(` v${VERSION} | githat.io`));
90
+ console.log("");
91
+ }
92
+ function displaySuccess(projectName, packageManager, framework) {
93
+ const devCmd = packageManager === "npm" ? "npm run dev" : `${packageManager} dev`;
94
+ const port = framework === "react-vite" ? "5173" : "3000";
95
+ console.log("");
96
+ console.log(githatGradient(" \u2726 Your GitHat app is ready!"));
97
+ console.log("");
98
+ console.log(` ${chalk.cyan("cd")} ${projectName}`);
99
+ console.log(` ${chalk.cyan(devCmd)}`);
100
+ console.log("");
101
+ console.log(chalk.dim(` \u2192 http://localhost:${port}`));
102
+ console.log("");
103
+ console.log(chalk.dim(" Routes:"));
104
+ console.log(chalk.dim(" /sign-in Sign in"));
105
+ console.log(chalk.dim(" /sign-up Create account"));
106
+ console.log(chalk.dim(" /dashboard Protected dashboard"));
107
+ console.log("");
108
+ console.log(chalk.dim(" Docs: https://githat.io/docs/sdk"));
109
+ console.log("");
110
+ }
111
+
112
+ // src/prompts/index.ts
113
+ import * as p6 from "@clack/prompts";
114
+
115
+ // src/prompts/project.ts
116
+ import * as p from "@clack/prompts";
117
+
118
+ // src/utils/validate.ts
119
+ function validateProjectName(name) {
120
+ if (!name) return "Project name is required";
121
+ if (name.length < 2) return "Must be at least 2 characters";
122
+ if (name.length > 50) return "Must be 50 characters or less";
123
+ if (!/^[a-z0-9][a-z0-9._-]*$/.test(name)) {
124
+ return "Must be lowercase, start with a letter or number, and use only a-z, 0-9, -, _, .";
125
+ }
126
+ return void 0;
127
+ }
128
+ function validatePublishableKey(key) {
129
+ if (!key) return void 0;
130
+ if (!key.startsWith("pk_live_") && !key.startsWith("pk_test_")) {
131
+ return "Key must start with pk_live_ or pk_test_ (get one at githat.io/dashboard/apps)";
132
+ }
133
+ return void 0;
134
+ }
135
+ function validateApiUrl(url) {
136
+ if (!url) return "API URL is required";
137
+ try {
138
+ new URL(url);
139
+ return void 0;
140
+ } catch {
141
+ return "Must be a valid URL";
142
+ }
143
+ }
144
+
145
+ // src/prompts/project.ts
146
+ async function promptProject(initialName) {
147
+ const answers = await p.group(
148
+ {
149
+ projectName: () => p.text({
150
+ message: "Project name",
151
+ placeholder: "my-githat-app",
152
+ initialValue: initialName || "",
153
+ validate: validateProjectName
154
+ }),
155
+ businessName: () => p.text({
156
+ message: "Business or app display name",
157
+ placeholder: "Acme Corp",
158
+ validate: (v) => !v ? "Display name is required" : void 0
159
+ }),
160
+ description: () => p.text({
161
+ message: "One-line description",
162
+ placeholder: "A platform for managing identity across humans and AI",
163
+ initialValue: ""
164
+ })
165
+ },
166
+ {
167
+ onCancel: () => {
168
+ p.cancel("Setup cancelled.");
169
+ process.exit(0);
170
+ }
171
+ }
172
+ );
173
+ return {
174
+ projectName: answers.projectName,
175
+ businessName: answers.businessName,
176
+ description: answers.description || `${answers.businessName} \u2014 Built with GitHat identity`
177
+ };
178
+ }
179
+
180
+ // src/prompts/framework.ts
181
+ import * as p2 from "@clack/prompts";
182
+
183
+ // src/utils/package-manager.ts
184
+ import { execSync } from "child_process";
185
+ function detectPackageManager() {
186
+ const userAgent = process.env.npm_config_user_agent || "";
187
+ if (userAgent.startsWith("pnpm")) return "pnpm";
188
+ if (userAgent.startsWith("yarn")) return "yarn";
189
+ if (userAgent.startsWith("bun")) return "bun";
190
+ return "npm";
191
+ }
192
+ function isAvailable(pm) {
193
+ try {
194
+ execSync(`${pm} --version`, { stdio: "ignore" });
195
+ return true;
196
+ } catch {
197
+ return false;
198
+ }
199
+ }
200
+ function getInstallCommand(pm) {
201
+ const cmds = {
202
+ npm: "npm install",
203
+ pnpm: "pnpm install",
204
+ yarn: "yarn",
205
+ bun: "bun install"
206
+ };
207
+ return cmds[pm];
208
+ }
209
+
210
+ // src/prompts/framework.ts
211
+ async function promptFramework(typescriptOverride) {
212
+ const detected = detectPackageManager();
213
+ const answers = await p2.group(
214
+ {
215
+ framework: () => p2.select({
216
+ message: "Framework",
217
+ options: [
218
+ { value: "nextjs", label: "Next.js 16", hint: "App Router, SSR, middleware auth" },
219
+ { value: "react-vite", label: "React 19 + Vite 7", hint: "SPA, client-side routing" }
220
+ ]
221
+ }),
222
+ typescript: () => typescriptOverride !== void 0 ? Promise.resolve(typescriptOverride) : p2.select({
223
+ message: "Language",
224
+ options: [
225
+ { value: true, label: "TypeScript", hint: "recommended" },
226
+ { value: false, label: "JavaScript" }
227
+ ]
228
+ }),
229
+ packageManager: () => p2.select({
230
+ message: "Package manager",
231
+ initialValue: detected,
232
+ options: ["npm", "pnpm", "yarn", "bun"].filter((pm) => pm === detected || isAvailable(pm)).map((pm) => ({
233
+ value: pm,
234
+ label: pm,
235
+ hint: pm === detected ? "detected" : void 0
236
+ }))
237
+ })
238
+ },
239
+ {
240
+ onCancel: () => {
241
+ p2.cancel("Setup cancelled.");
242
+ process.exit(0);
243
+ }
244
+ }
245
+ );
246
+ return {
247
+ framework: answers.framework,
248
+ typescript: answers.typescript,
249
+ packageManager: answers.packageManager
250
+ };
251
+ }
252
+
253
+ // src/prompts/githat.ts
254
+ import * as p3 from "@clack/prompts";
255
+ async function promptGitHat(existingKey) {
256
+ const answers = await p3.group(
257
+ {
258
+ publishableKey: () => existingKey ? Promise.resolve(existingKey) : p3.text({
259
+ message: "GitHat publishable key",
260
+ placeholder: `pk_live_... (get one at ${DASHBOARD_URL})`,
261
+ initialValue: "",
262
+ validate: validatePublishableKey
263
+ }),
264
+ apiUrl: () => p3.text({
265
+ message: "GitHat API URL",
266
+ placeholder: DEFAULT_API_URL,
267
+ initialValue: DEFAULT_API_URL,
268
+ validate: validateApiUrl
269
+ }),
270
+ authFeatures: () => p3.multiselect({
271
+ message: "Auth features",
272
+ options: [
273
+ { value: "forgot-password", label: "Forgot password / Reset password", hint: "recommended" },
274
+ { value: "email-verification", label: "Email verification", hint: "recommended" },
275
+ { value: "org-management", label: "Organization management", hint: "teams, invites, roles" },
276
+ { value: "mcp-servers", label: "MCP server registration", hint: "tool verification" },
277
+ { value: "ai-agents", label: "AI agent wallet auth", hint: "Ethereum signatures" }
278
+ ],
279
+ initialValues: ["forgot-password", "email-verification"],
280
+ required: false
281
+ })
282
+ },
283
+ {
284
+ onCancel: () => {
285
+ p3.cancel("Setup cancelled.");
286
+ process.exit(0);
287
+ }
288
+ }
289
+ );
290
+ return {
291
+ publishableKey: answers.publishableKey || "",
292
+ apiUrl: answers.apiUrl || DEFAULT_API_URL,
293
+ authFeatures: answers.authFeatures || []
294
+ };
295
+ }
296
+
297
+ // src/prompts/features.ts
298
+ import * as p4 from "@clack/prompts";
299
+ async function promptFeatures() {
300
+ const answers = await p4.group(
301
+ {
302
+ databaseChoice: () => p4.select({
303
+ message: "Database",
304
+ options: [
305
+ { value: "none", label: "None \u2014 API only", hint: "use GitHat backend directly" },
306
+ { value: "prisma-postgres", label: "Prisma + PostgreSQL" },
307
+ { value: "prisma-mysql", label: "Prisma + MySQL" },
308
+ { value: "drizzle-postgres", label: "Drizzle + PostgreSQL" },
309
+ { value: "drizzle-sqlite", label: "Drizzle + SQLite" }
310
+ ]
311
+ }),
312
+ useTailwind: () => p4.confirm({ message: "Include Tailwind CSS?", initialValue: true }),
313
+ includeDashboard: () => p4.confirm({
314
+ message: "Include full dashboard?",
315
+ initialValue: true
316
+ }),
317
+ includeGithatFolder: () => p4.confirm({
318
+ message: "Include githat/ platform folder?",
319
+ initialValue: true
320
+ })
321
+ },
322
+ {
323
+ onCancel: () => {
324
+ p4.cancel("Setup cancelled.");
325
+ process.exit(0);
326
+ }
327
+ }
328
+ );
329
+ return {
330
+ databaseChoice: answers.databaseChoice,
331
+ useTailwind: answers.useTailwind,
332
+ includeDashboard: answers.includeDashboard,
333
+ includeGithatFolder: answers.includeGithatFolder
334
+ };
335
+ }
336
+
337
+ // src/prompts/finalize.ts
338
+ import * as p5 from "@clack/prompts";
339
+ async function promptFinalize() {
340
+ const answers = await p5.group(
341
+ {
342
+ initGit: () => p5.confirm({ message: "Initialize git repository?", initialValue: true }),
343
+ installDeps: () => p5.confirm({ message: "Install dependencies now?", initialValue: true })
344
+ },
345
+ {
346
+ onCancel: () => {
347
+ p5.cancel("Setup cancelled.");
348
+ process.exit(0);
349
+ }
350
+ }
351
+ );
352
+ return {
353
+ initGit: answers.initGit,
354
+ installDeps: answers.installDeps
355
+ };
356
+ }
357
+
358
+ // src/prompts/index.ts
359
+ async function runPrompts(args) {
360
+ p6.intro("Let\u2019s set up your GitHat app");
361
+ const project = await promptProject(args.initialName);
362
+ const framework = await promptFramework(args.typescript);
363
+ const githat = await promptGitHat(args.publishableKey);
364
+ const features = await promptFeatures();
365
+ const finalize = await promptFinalize();
366
+ return { ...project, ...framework, ...githat, ...features, ...finalize };
367
+ }
368
+ function answersToContext(answers) {
369
+ return {
370
+ projectName: answers.projectName,
371
+ businessName: answers.businessName,
372
+ description: answers.description,
373
+ framework: answers.framework,
374
+ typescript: answers.typescript,
375
+ packageManager: answers.packageManager,
376
+ publishableKey: answers.publishableKey,
377
+ apiUrl: answers.apiUrl,
378
+ useDatabase: answers.databaseChoice !== "none",
379
+ databaseChoice: answers.databaseChoice,
380
+ useTailwind: answers.useTailwind,
381
+ includeDashboard: answers.includeDashboard,
382
+ includeGithatFolder: answers.includeGithatFolder,
383
+ includeForgotPassword: answers.authFeatures.includes("forgot-password"),
384
+ includeEmailVerification: answers.authFeatures.includes("email-verification"),
385
+ includeOrgManagement: answers.authFeatures.includes("org-management"),
386
+ includeMcpModule: answers.authFeatures.includes("mcp-servers"),
387
+ includeAgentModule: answers.authFeatures.includes("ai-agents"),
388
+ ext: answers.typescript ? "tsx" : "jsx",
389
+ configExt: answers.typescript ? "ts" : "js"
390
+ };
391
+ }
392
+
393
+ // src/scaffold/index.ts
394
+ import fs3 from "fs-extra";
395
+ import path3 from "path";
396
+ import { execSync as execSync3 } from "child_process";
397
+ import * as p7 from "@clack/prompts";
398
+ import chalk2 from "chalk";
399
+
400
+ // src/utils/template-engine.ts
401
+ import Handlebars from "handlebars";
402
+ import fs from "fs-extra";
403
+ import path from "path";
404
+ import { fileURLToPath } from "url";
405
+ var __filename2 = fileURLToPath(import.meta.url);
406
+ var __dirname2 = path.dirname(__filename2);
407
+ var TEMPLATES_ROOT = path.resolve(__dirname2, "..", "..", "templates");
408
+ Handlebars.registerHelper("ifEquals", function(a, b, options) {
409
+ return a === b ? options.fn(this) : options.inverse(this);
410
+ });
411
+ function getTemplatesRoot() {
412
+ return TEMPLATES_ROOT;
413
+ }
414
+ function renderTemplate(templatePath, context) {
415
+ const content = fs.readFileSync(templatePath, "utf-8");
416
+ const template = Handlebars.compile(content, { noEscape: true });
417
+ return template(context);
418
+ }
419
+ function renderTemplateDirectory(templateDir, outputDir, context) {
420
+ const entries = fs.readdirSync(templateDir, { withFileTypes: true });
421
+ for (const entry of entries) {
422
+ const srcPath = path.join(templateDir, entry.name);
423
+ if (entry.isDirectory()) {
424
+ const outDirName = entry.name;
425
+ const outPath = path.join(outputDir, outDirName);
426
+ fs.ensureDirSync(outPath);
427
+ renderTemplateDirectory(srcPath, outPath, context);
428
+ } else if (entry.name.endsWith(".hbs")) {
429
+ const outputName = entry.name.replace(/\.hbs$/, "");
430
+ const finalName = context.typescript ? outputName : outputName.replace(/\.tsx$/, ".jsx").replace(/\.ts$/, ".js");
431
+ const outPath = path.join(outputDir, finalName);
432
+ const rendered = renderTemplate(srcPath, context);
433
+ if (rendered.trim()) {
434
+ fs.ensureDirSync(path.dirname(outPath));
435
+ fs.writeFileSync(outPath, rendered, "utf-8");
436
+ }
437
+ } else {
438
+ const outPath = path.join(outputDir, entry.name);
439
+ fs.copySync(srcPath, outPath);
440
+ }
441
+ }
442
+ }
443
+
444
+ // src/scaffold/file-writer.ts
445
+ import fs2 from "fs-extra";
446
+ import path2 from "path";
447
+ function writeFile(root, relativePath, content) {
448
+ const fullPath = path2.join(root, relativePath);
449
+ fs2.ensureDirSync(path2.dirname(fullPath));
450
+ fs2.writeFileSync(fullPath, content, "utf-8");
451
+ }
452
+ function writeJson(root, relativePath, data) {
453
+ writeFile(root, relativePath, JSON.stringify(data, null, 2) + "\n");
454
+ }
455
+
456
+ // src/scaffold/package-builder.ts
457
+ function buildPackageJson(ctx) {
458
+ const isNext = ctx.framework === "nextjs";
459
+ const deps = {
460
+ ...isNext ? DEPS.nextjs.dependencies : DEPS["react-vite"].dependencies
461
+ };
462
+ const devDeps = {
463
+ ...isNext ? DEPS.nextjs.devDependencies : DEPS["react-vite"].devDependencies
464
+ };
465
+ if (ctx.useTailwind) {
466
+ if (isNext) {
467
+ Object.assign(devDeps, DEPS.tailwind.devDependencies);
468
+ } else {
469
+ Object.assign(devDeps, DEPS["tailwind-vite"].devDependencies);
470
+ }
471
+ }
472
+ if (ctx.useDatabase && ctx.databaseChoice !== "none") {
473
+ const dbDeps = DEPS[ctx.databaseChoice];
474
+ if (dbDeps && "dependencies" in dbDeps) {
475
+ Object.assign(deps, dbDeps.dependencies);
476
+ }
477
+ if (dbDeps && "devDependencies" in dbDeps) {
478
+ Object.assign(devDeps, dbDeps.devDependencies);
479
+ }
480
+ }
481
+ if (!ctx.typescript) {
482
+ delete devDeps.typescript;
483
+ delete devDeps["@types/react"];
484
+ delete devDeps["@types/react-dom"];
485
+ delete devDeps["@types/node"];
486
+ delete devDeps["@types/better-sqlite3"];
487
+ }
488
+ const scripts = isNext ? { dev: "next dev", build: "next build", start: "next start", lint: "next lint" } : { dev: "vite", build: "vite build", preview: "vite preview" };
489
+ if (ctx.databaseChoice.startsWith("prisma")) {
490
+ scripts["db:push"] = "prisma db push";
491
+ scripts["db:studio"] = "prisma studio";
492
+ scripts["db:generate"] = "prisma generate";
493
+ } else if (ctx.databaseChoice.startsWith("drizzle")) {
494
+ scripts["db:push"] = "drizzle-kit push";
495
+ scripts["db:generate"] = "drizzle-kit generate";
496
+ scripts["db:migrate"] = "drizzle-kit migrate";
497
+ }
498
+ return {
499
+ name: ctx.projectName,
500
+ version: "0.1.0",
501
+ private: true,
502
+ ...isNext ? {} : { type: "module" },
503
+ scripts,
504
+ dependencies: sortKeys(deps),
505
+ devDependencies: sortKeys(devDeps)
506
+ };
507
+ }
508
+ function sortKeys(obj) {
509
+ return Object.fromEntries(Object.entries(obj).sort(([a], [b]) => a.localeCompare(b)));
510
+ }
511
+
512
+ // src/utils/spinner.ts
513
+ import ora from "ora";
514
+ function createSpinner(text3) {
515
+ return ora({ text: text3, color: "magenta" });
516
+ }
517
+ async function withSpinner(text3, fn, successText) {
518
+ const spinner = createSpinner(text3);
519
+ spinner.start();
520
+ try {
521
+ const result = await fn();
522
+ spinner.succeed(successText || text3);
523
+ return result;
524
+ } catch (err) {
525
+ spinner.fail(text3);
526
+ throw err;
527
+ }
528
+ }
529
+
530
+ // src/utils/git.ts
531
+ import { execSync as execSync2 } from "child_process";
532
+ function initGit(cwd) {
533
+ try {
534
+ execSync2("git init", { cwd, stdio: "ignore" });
535
+ execSync2("git add -A", { cwd, stdio: "ignore" });
536
+ execSync2('git commit -m "Initial commit from create-githat-app"', {
537
+ cwd,
538
+ stdio: "ignore"
539
+ });
540
+ return true;
541
+ } catch {
542
+ return false;
543
+ }
544
+ }
545
+
546
+ // src/scaffold/index.ts
547
+ async function scaffold(context, options) {
548
+ const root = path3.resolve(process.cwd(), context.projectName);
549
+ if (fs3.existsSync(root)) {
550
+ p7.cancel(`Directory "${context.projectName}" already exists.`);
551
+ process.exit(1);
552
+ }
553
+ await withSpinner("Creating project structure...", async () => {
554
+ fs3.ensureDirSync(root);
555
+ const templatesRoot = getTemplatesRoot();
556
+ const frameworkDir = path3.join(templatesRoot, context.framework);
557
+ if (fs3.existsSync(frameworkDir)) {
558
+ renderTemplateDirectory(frameworkDir, root, context);
559
+ }
560
+ const baseDir = path3.join(templatesRoot, "base");
561
+ if (fs3.existsSync(baseDir)) {
562
+ renderTemplateDirectory(baseDir, root, context);
563
+ }
564
+ }, "Project structure created");
565
+ await withSpinner("Generating package.json...", async () => {
566
+ const pkg = buildPackageJson(context);
567
+ writeJson(root, "package.json", pkg);
568
+ }, "package.json generated");
569
+ if (options.initGit) {
570
+ const gitSpinner = createSpinner("Initializing git repository...");
571
+ gitSpinner.start();
572
+ const ok = initGit(root);
573
+ if (ok) {
574
+ gitSpinner.succeed("Git repository initialized");
575
+ } else {
576
+ gitSpinner.warn("Could not initialize git \u2014 is git installed?");
577
+ }
578
+ }
579
+ if (options.installDeps) {
580
+ const installCmd = getInstallCommand(context.packageManager);
581
+ await withSpinner(
582
+ `Installing dependencies with ${context.packageManager}...`,
583
+ async () => {
584
+ try {
585
+ execSync3(installCmd, { cwd: root, stdio: "ignore", timeout: 12e4 });
586
+ } catch (err) {
587
+ const msg = err.message || "";
588
+ if (msg.includes("TIMEOUT")) {
589
+ p7.log.warn(`Install timed out. Run ${chalk2.cyan(installCmd)} manually.`);
590
+ } else {
591
+ p7.log.warn(`Could not auto-install. Run ${chalk2.cyan(installCmd)} manually.`);
592
+ }
593
+ }
594
+ },
595
+ "Dependencies installed"
596
+ );
597
+ }
598
+ p7.outro("Setup complete!");
599
+ displaySuccess(context.projectName, context.packageManager, context.framework);
600
+ }
601
+
602
+ // src/cli.ts
603
+ var program = new Command();
604
+ program.name("create-githat-app").description("Scaffold enterprise-grade apps with GitHat identity").version(VERSION).argument("[project-name]", "Name of the project directory").option("--key <key>", "GitHat publishable key (pk_live_...)").option("--ts", "Use TypeScript (default)").option("--js", "Use JavaScript").action(async (projectName, opts) => {
605
+ try {
606
+ displayBanner();
607
+ const typescript = opts.js ? false : opts.ts ? true : void 0;
608
+ const answers = await runPrompts({
609
+ initialName: projectName,
610
+ publishableKey: opts.key,
611
+ typescript
612
+ });
613
+ const context = answersToContext(answers);
614
+ await scaffold(context, {
615
+ installDeps: answers.installDeps,
616
+ initGit: answers.initGit
617
+ });
618
+ } catch (err) {
619
+ p8.cancel(chalk3.red(err.message || "Something went wrong."));
620
+ process.exit(1);
621
+ }
622
+ });
623
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "create-githat-app",
3
+ "version": "1.0.0",
4
+ "description": "Scaffold enterprise-grade apps with GitHat identity — for humans, AI agents, and MCP servers",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-githat-app": "./bin/index.js"
8
+ },
9
+ "files": ["bin", "dist", "templates"],
10
+ "scripts": {
11
+ "build": "tsup",
12
+ "dev": "tsup --watch",
13
+ "test": "node bin/index.js --help"
14
+ },
15
+ "dependencies": {
16
+ "@clack/prompts": "^0.9.1",
17
+ "chalk": "^5.4.1",
18
+ "commander": "^13.1.0",
19
+ "figlet": "^1.8.0",
20
+ "fs-extra": "^11.2.0",
21
+ "gradient-string": "^3.0.0",
22
+ "handlebars": "^4.7.8",
23
+ "ora": "^8.1.1"
24
+ },
25
+ "devDependencies": {
26
+ "@types/figlet": "^1.7.0",
27
+ "@types/fs-extra": "^11.0.4",
28
+ "@types/gradient-string": "^1.1.6",
29
+ "tsup": "^8.4.0",
30
+ "typescript": "^5.9.0"
31
+ },
32
+ "keywords": ["githat", "nextjs", "react", "vite", "create-app", "cli", "auth", "identity", "mcp", "ai-agents"],
33
+ "license": "SEE LICENSE IN LICENSE",
34
+ "publishConfig": {
35
+ "access": "public",
36
+ "registry": "https://registry.npmjs.org/"
37
+ },
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "git+https://github.com/GitHat-IO/create-githat-app.git"
41
+ }
42
+ }
@@ -0,0 +1,13 @@
1
+ # GitHat Identity
2
+ {{#ifEquals framework "nextjs"}}
3
+ NEXT_PUBLIC_GITHAT_PUBLISHABLE_KEY=pk_test_your_key_here
4
+ NEXT_PUBLIC_GITHAT_API_URL={{apiUrl}}
5
+ {{else}}
6
+ VITE_GITHAT_PUBLISHABLE_KEY=pk_test_your_key_here
7
+ VITE_GITHAT_API_URL={{apiUrl}}
8
+ {{/ifEquals}}
9
+ {{#if useDatabase}}
10
+
11
+ # Database
12
+ DATABASE_URL="postgresql://user:password@localhost:5432/{{projectName}}"
13
+ {{/if}}
@@ -0,0 +1,10 @@
1
+ {{#ifEquals framework "nextjs"}}
2
+ NEXT_PUBLIC_GITHAT_PUBLISHABLE_KEY={{publishableKey}}
3
+ NEXT_PUBLIC_GITHAT_API_URL={{apiUrl}}
4
+ {{else}}
5
+ VITE_GITHAT_PUBLISHABLE_KEY={{publishableKey}}
6
+ VITE_GITHAT_API_URL={{apiUrl}}
7
+ {{/ifEquals}}
8
+ {{#if useDatabase}}
9
+ DATABASE_URL="postgresql://user:password@localhost:5432/{{projectName}}"
10
+ {{/if}}
@@ -0,0 +1,11 @@
1
+ node_modules
2
+ {{#ifEquals framework "nextjs"}}
3
+ .next
4
+ out
5
+ {{else}}
6
+ dist
7
+ {{/ifEquals}}
8
+ .env
9
+ .env*.local
10
+ .DS_Store
11
+ *.tgz