create-githat-app 1.3.0 → 1.4.1

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 (156) hide show
  1. package/README.md +48 -18
  2. package/dist/cli.js +1161 -114
  3. package/package.json +34 -9
  4. package/templates/agent/app/(auth)/sign-in/page.tsx.hbs +9 -0
  5. package/templates/agent/app/(auth)/sign-up/page.tsx.hbs +9 -0
  6. package/templates/agent/app/admin/agent/page.tsx.hbs +127 -0
  7. package/templates/agent/app/globals.css.hbs +87 -0
  8. package/templates/agent/app/layout.tsx.hbs +41 -0
  9. package/templates/agent/app/page.tsx.hbs +100 -0
  10. package/templates/agent/next.config.ts.hbs +9 -0
  11. package/templates/agent/postcss.config.mjs.hbs +14 -0
  12. package/templates/agent/proxy.ts.hbs +10 -0
  13. package/templates/agent/tsconfig.json.hbs +21 -0
  14. package/templates/base/.env.example.hbs +2 -2
  15. package/templates/base/.env.local.example.hbs +20 -0
  16. package/templates/base/.env.local.hbs +13 -2
  17. package/templates/base/.github/CODEOWNERS.hbs +1 -0
  18. package/templates/base/.github/SECURITY.md +10 -0
  19. package/templates/base/.github/dependabot.yml +19 -0
  20. package/templates/base/.github/workflows/ci.yml.hbs +77 -0
  21. package/templates/base/.github/workflows/githat-policy.yml +51 -0
  22. package/templates/base/.gitignore.hbs +17 -2
  23. package/templates/base/README.md.hbs +31 -52
  24. package/templates/classroom/app/(auth)/sign-in/page.tsx.hbs +9 -0
  25. package/templates/classroom/app/(auth)/sign-up/page.tsx.hbs +9 -0
  26. package/templates/classroom/app/globals.css.hbs +87 -0
  27. package/templates/classroom/app/layout.tsx.hbs +41 -0
  28. package/templates/classroom/app/page.tsx.hbs +103 -0
  29. package/templates/classroom/app/projects/[id]/feedback/page.tsx.hbs +159 -0
  30. package/templates/classroom/app/projects/[id]/present/page.tsx.hbs +113 -0
  31. package/templates/classroom/next.config.ts.hbs +9 -0
  32. package/templates/classroom/postcss.config.mjs.hbs +14 -0
  33. package/templates/classroom/proxy.ts.hbs +10 -0
  34. package/templates/classroom/tsconfig.json.hbs +21 -0
  35. package/templates/content/app/(auth)/sign-in/page.tsx.hbs +9 -0
  36. package/templates/content/app/(auth)/sign-up/page.tsx.hbs +9 -0
  37. package/templates/content/app/globals.css.hbs +87 -0
  38. package/templates/content/app/layout.tsx.hbs +41 -0
  39. package/templates/content/app/newsletter/page.tsx.hbs +90 -0
  40. package/templates/content/app/page.tsx.hbs +105 -0
  41. package/templates/content/app/posts/[slug]/page.tsx.hbs +119 -0
  42. package/templates/content/next.config.ts.hbs +9 -0
  43. package/templates/content/postcss.config.mjs.hbs +14 -0
  44. package/templates/content/proxy.ts.hbs +10 -0
  45. package/templates/content/tsconfig.json.hbs +21 -0
  46. package/templates/dashboard/app/(auth)/sign-in/page.tsx.hbs +9 -0
  47. package/templates/dashboard/app/(auth)/sign-up/page.tsx.hbs +9 -0
  48. package/templates/dashboard/app/admin/data/[entity]/page.tsx.hbs +68 -0
  49. package/templates/dashboard/app/admin/page.tsx.hbs +59 -0
  50. package/templates/dashboard/app/globals.css.hbs +87 -0
  51. package/templates/dashboard/app/layout.tsx.hbs +41 -0
  52. package/templates/dashboard/app/page.tsx.hbs +57 -0
  53. package/templates/dashboard/next.config.ts.hbs +9 -0
  54. package/templates/dashboard/postcss.config.mjs.hbs +14 -0
  55. package/templates/dashboard/proxy.ts.hbs +10 -0
  56. package/templates/dashboard/src/lib/db.ts.hbs +39 -0
  57. package/templates/dashboard/tsconfig.json.hbs +21 -0
  58. package/templates/fullstack/apps-api-express/.env.example.hbs +6 -0
  59. package/templates/fullstack/apps-api-express/.env.local.hbs +6 -0
  60. package/templates/fullstack/apps-api-express/package.json.hbs +24 -0
  61. package/templates/fullstack/apps-api-express/src/index.ts.hbs +41 -0
  62. package/templates/fullstack/apps-api-express/src/routes/health.ts.hbs +11 -0
  63. package/templates/fullstack/apps-api-express/src/routes/users.ts.hbs +43 -0
  64. package/templates/fullstack/apps-api-express/tsconfig.json.hbs +16 -0
  65. package/templates/fullstack/apps-api-fastify/.env.example.hbs +6 -0
  66. package/templates/fullstack/apps-api-fastify/.env.local.hbs +6 -0
  67. package/templates/fullstack/apps-api-fastify/package.json.hbs +22 -0
  68. package/templates/fullstack/apps-api-fastify/src/index.ts.hbs +28 -0
  69. package/templates/fullstack/apps-api-fastify/src/routes/health.ts.hbs +11 -0
  70. package/templates/fullstack/apps-api-fastify/src/routes/users.ts.hbs +43 -0
  71. package/templates/fullstack/apps-api-fastify/tsconfig.json.hbs +16 -0
  72. package/templates/fullstack/apps-api-hono/.env.example.hbs +6 -0
  73. package/templates/fullstack/apps-api-hono/.env.local.hbs +6 -0
  74. package/templates/fullstack/apps-api-hono/package.json.hbs +22 -0
  75. package/templates/fullstack/apps-api-hono/src/index.ts.hbs +35 -0
  76. package/templates/fullstack/apps-api-hono/src/routes/health.ts.hbs +11 -0
  77. package/templates/fullstack/apps-api-hono/src/routes/users.ts.hbs +43 -0
  78. package/templates/fullstack/apps-api-hono/tsconfig.json.hbs +16 -0
  79. package/templates/fullstack/apps-web-nextjs/.env.example.hbs +5 -0
  80. package/templates/fullstack/apps-web-nextjs/.env.local.hbs +5 -0
  81. package/templates/fullstack/apps-web-nextjs/app/(auth)/forgot-password/page.tsx.hbs +11 -0
  82. package/templates/fullstack/apps-web-nextjs/app/(auth)/reset-password/page.tsx.hbs +39 -0
  83. package/templates/fullstack/apps-web-nextjs/app/(auth)/sign-in/page.tsx.hbs +9 -0
  84. package/templates/fullstack/apps-web-nextjs/app/(auth)/sign-up/page.tsx.hbs +9 -0
  85. package/templates/fullstack/apps-web-nextjs/app/(auth)/verify-email/page.tsx.hbs +11 -0
  86. package/templates/fullstack/apps-web-nextjs/app/dashboard/layout.tsx.hbs +15 -0
  87. package/templates/fullstack/apps-web-nextjs/app/dashboard/page.tsx.hbs +27 -0
  88. package/templates/fullstack/apps-web-nextjs/app/globals.css.hbs +21 -0
  89. package/templates/fullstack/apps-web-nextjs/app/layout.tsx.hbs +30 -0
  90. package/templates/fullstack/apps-web-nextjs/app/page.tsx.hbs +17 -0
  91. package/templates/fullstack/apps-web-nextjs/next.config.ts.hbs +17 -0
  92. package/templates/fullstack/apps-web-nextjs/package.json.hbs +34 -0
  93. package/templates/fullstack/apps-web-nextjs/postcss.config.mjs.hbs +9 -0
  94. package/templates/fullstack/apps-web-nextjs/tsconfig.json.hbs +21 -0
  95. package/templates/fullstack/root/.gitignore.hbs +42 -0
  96. package/templates/fullstack/root/githat.yaml.hbs +17 -0
  97. package/templates/fullstack/root/package.json.hbs +15 -0
  98. package/templates/fullstack/root/turbo.json.hbs +20 -0
  99. package/templates/marketplace/CULTURE.md +74 -0
  100. package/templates/marketplace/app/(auth)/sign-in/page.tsx.hbs +9 -0
  101. package/templates/marketplace/app/(auth)/sign-up/page.tsx.hbs +9 -0
  102. package/templates/marketplace/app/(shop)/[slug]/p/[productId]/page.tsx.hbs +99 -0
  103. package/templates/marketplace/app/(shop)/[slug]/page.tsx.hbs +90 -0
  104. package/templates/marketplace/app/admin/page.tsx.hbs +95 -0
  105. package/templates/marketplace/app/cart/page.tsx.hbs +157 -0
  106. package/templates/marketplace/app/globals.css.hbs +87 -0
  107. package/templates/marketplace/app/layout.tsx.hbs +77 -0
  108. package/templates/marketplace/app/page.tsx.hbs +178 -0
  109. package/templates/marketplace/app/sell/page.tsx.hbs +78 -0
  110. package/templates/marketplace/next.config.ts.hbs +9 -0
  111. package/templates/marketplace/postcss.config.mjs.hbs +14 -0
  112. package/templates/marketplace/proxy.ts.hbs +10 -0
  113. package/templates/marketplace/src/lib/anon-session.ts.hbs +117 -0
  114. package/templates/marketplace/src/lib/categories.ts.hbs +35 -0
  115. package/templates/marketplace/tsconfig.json.hbs +21 -0
  116. package/templates/nextjs/.github/workflows/deploy.yml.hbs +107 -0
  117. package/templates/nextjs/app/(auth)/reset-password/page.tsx.hbs +106 -0
  118. package/templates/nextjs/app/globals.css.hbs +4 -3
  119. package/templates/nextjs/app/layout.tsx.hbs +5 -1
  120. package/templates/nextjs/app/page.tsx.hbs +3 -6
  121. package/templates/nextjs/next.config.ts.hbs +7 -3
  122. package/templates/nextjs/proxy.ts.hbs +1 -1
  123. package/templates/plain/app/(auth)/sign-in/page.tsx.hbs +9 -0
  124. package/templates/plain/app/(auth)/sign-up/page.tsx.hbs +9 -0
  125. package/templates/plain/app/globals.css.hbs +87 -0
  126. package/templates/plain/app/layout.tsx.hbs +41 -0
  127. package/templates/plain/app/page.tsx.hbs +123 -0
  128. package/templates/plain/next.config.ts.hbs +9 -0
  129. package/templates/plain/postcss.config.mjs.hbs +14 -0
  130. package/templates/plain/proxy.ts.hbs +10 -0
  131. package/templates/plain/tsconfig.json.hbs +21 -0
  132. package/templates/portfolio/app/(auth)/sign-in/page.tsx.hbs +9 -0
  133. package/templates/portfolio/app/(auth)/sign-up/page.tsx.hbs +9 -0
  134. package/templates/portfolio/app/globals.css.hbs +87 -0
  135. package/templates/portfolio/app/layout.tsx.hbs +41 -0
  136. package/templates/portfolio/app/page.tsx.hbs +86 -0
  137. package/templates/portfolio/next.config.ts.hbs +9 -0
  138. package/templates/portfolio/postcss.config.mjs.hbs +14 -0
  139. package/templates/portfolio/proxy.ts.hbs +10 -0
  140. package/templates/portfolio/tsconfig.json.hbs +21 -0
  141. package/templates/react-vite/src/App.tsx.hbs +11 -9
  142. package/templates/react-vite/src/index.css.hbs +4 -3
  143. package/templates/react-vite/src/pages/Home.tsx.hbs +3 -6
  144. package/templates/saas/app/(auth)/sign-in/page.tsx.hbs +9 -0
  145. package/templates/saas/app/(auth)/sign-up/page.tsx.hbs +9 -0
  146. package/templates/saas/app/admin/billing/page.tsx.hbs +145 -0
  147. package/templates/saas/app/admin/page.tsx.hbs +106 -0
  148. package/templates/saas/app/admin/team/page.tsx.hbs +134 -0
  149. package/templates/saas/app/globals.css.hbs +87 -0
  150. package/templates/saas/app/layout.tsx.hbs +41 -0
  151. package/templates/saas/app/page.tsx.hbs +108 -0
  152. package/templates/saas/app/pricing/page.tsx.hbs +131 -0
  153. package/templates/saas/next.config.ts.hbs +9 -0
  154. package/templates/saas/postcss.config.mjs.hbs +14 -0
  155. package/templates/saas/proxy.ts.hbs +10 -0
  156. package/templates/saas/tsconfig.json.hbs +21 -0
package/dist/cli.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { createRequire } from 'module'; const require = createRequire(import.meta.url);
2
2
 
3
3
  // src/cli.ts
4
- import { Command } from "commander";
5
- import * as p8 from "@clack/prompts";
6
- import chalk3 from "chalk";
4
+ import { Command as Command7 } from "commander";
5
+ import * as p12 from "@clack/prompts";
6
+ import chalk10 from "chalk";
7
7
 
8
8
  // src/utils/ascii.ts
9
9
  import figlet from "figlet";
@@ -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 = "1.3.0";
14
+ var VERSION = "1.0.17";
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,8 @@ 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.4.0"
24
+ "@githat/nextjs": "^0.8.2",
25
+ "@githat/ui": "^1.0.0"
25
26
  },
26
27
  devDependencies: {
27
28
  typescript: "^5.9.0",
@@ -35,7 +36,8 @@ var DEPS = {
35
36
  react: "^19.0.0",
36
37
  "react-dom": "^19.0.0",
37
38
  "react-router-dom": "^7.0.0",
38
- "@githat/nextjs": "^0.4.0"
39
+ "@githat/nextjs": "^0.8.2",
40
+ "@githat/ui": "^1.0.0"
39
41
  },
40
42
  devDependencies: {
41
43
  vite: "^7.0.0",
@@ -73,6 +75,48 @@ var DEPS = {
73
75
  "drizzle-sqlite": {
74
76
  dependencies: { "drizzle-orm": "^0.39.0", "better-sqlite3": "^11.0.0" },
75
77
  devDependencies: { "drizzle-kit": "^0.30.0", "@types/better-sqlite3": "^7.6.0" }
78
+ },
79
+ // Backend frameworks for fullstack
80
+ hono: {
81
+ dependencies: {
82
+ hono: "^4.6.0",
83
+ "@hono/node-server": "^1.13.0"
84
+ },
85
+ devDependencies: {
86
+ typescript: "^5.9.0",
87
+ "@types/node": "^22.0.0",
88
+ tsx: "^4.19.0"
89
+ }
90
+ },
91
+ express: {
92
+ dependencies: {
93
+ express: "^5.0.0",
94
+ cors: "^2.8.5"
95
+ },
96
+ devDependencies: {
97
+ typescript: "^5.9.0",
98
+ "@types/node": "^22.0.0",
99
+ "@types/express": "^5.0.0",
100
+ "@types/cors": "^2.8.17",
101
+ tsx: "^4.19.0"
102
+ }
103
+ },
104
+ fastify: {
105
+ dependencies: {
106
+ fastify: "^5.2.0",
107
+ "@fastify/cors": "^10.0.0"
108
+ },
109
+ devDependencies: {
110
+ typescript: "^5.9.0",
111
+ "@types/node": "^22.0.0",
112
+ tsx: "^4.19.0"
113
+ }
114
+ },
115
+ // Turborepo for fullstack monorepo
116
+ turbo: {
117
+ devDependencies: {
118
+ turbo: "^2.3.0"
119
+ }
76
120
  }
77
121
  };
78
122
 
@@ -123,30 +167,61 @@ function sectionHeader(title) {
123
167
  console.log(dim(` \u2500\u2500\u2500 ${title} ${"\u2500".repeat(lineLen)}`));
124
168
  console.log("");
125
169
  }
126
- function displaySuccess(projectName, packageManager, framework) {
170
+ function keyNextSteps() {
171
+ return [
172
+ chalk.bold("Next:") + " add your GitHat key",
173
+ ` 1. Open ${violet(".env.local")}`,
174
+ ` 2. Paste your key from ${violet("https://githat.io/dashboard/apps")}`,
175
+ ` 3. Run ${violet("npm run dev")} again`,
176
+ dim(" (.env.local is gitignored \u2014 your key never gets committed.)"),
177
+ ""
178
+ ];
179
+ }
180
+ function displaySuccess(projectName, packageManager, framework, hasPublishableKey = true, isFullstack = false) {
127
181
  const devCmd = packageManager === "npm" ? "npm run dev" : `${packageManager} dev`;
128
182
  const port = framework === "react-vite" ? "5173" : "3000";
129
183
  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
- dim("Docs \u2192 https://githat.io/docs/sdk")
144
- ]);
184
+ if (isFullstack) {
185
+ drawBox([
186
+ `${violet("\u2726")} Your GitHat fullstack app is ready!`,
187
+ "",
188
+ `${violet("$")} cd ${projectName}`,
189
+ `${violet("$")} ${devCmd}`,
190
+ "",
191
+ dim(`\u2192 Web: http://localhost:3000`),
192
+ dim(`\u2192 API: http://localhost:3001`),
193
+ "",
194
+ chalk.bold("Structure"),
195
+ `${violet("apps/web")} Next.js frontend`,
196
+ `${violet("apps/api")} API backend`,
197
+ `${violet("packages/")} Shared code`,
198
+ "",
199
+ ...hasPublishableKey ? [] : keyNextSteps(),
200
+ dim("Docs \u2192 https://githat.io/docs/sdk")
201
+ ]);
202
+ } else {
203
+ drawBox([
204
+ `${violet("\u2726")} Your GitHat app is ready!`,
205
+ "",
206
+ `${violet("$")} cd ${projectName}`,
207
+ `${violet("$")} ${devCmd}`,
208
+ "",
209
+ dim(`\u2192 http://localhost:${port}`),
210
+ "",
211
+ chalk.bold("Routes"),
212
+ `${violet("/sign-in")} Sign in`,
213
+ `${violet("/sign-up")} Create account`,
214
+ `${violet("/dashboard")} Protected dashboard`,
215
+ "",
216
+ ...hasPublishableKey ? [] : keyNextSteps(),
217
+ dim("Docs \u2192 https://githat.io/docs/sdk")
218
+ ]);
219
+ }
145
220
  console.log("");
146
221
  }
147
222
 
148
223
  // src/prompts/index.ts
149
- import * as p6 from "@clack/prompts";
224
+ import * as p8 from "@clack/prompts";
150
225
 
151
226
  // src/prompts/project.ts
152
227
  import * as p from "@clack/prompts";
@@ -183,6 +258,11 @@ async function promptProject(initialName) {
183
258
  message: "Display name",
184
259
  placeholder: "Acme Corp",
185
260
  validate: (v) => !v ? "Display name is required" : void 0
261
+ }),
262
+ githubUsername: () => p.text({
263
+ message: "GitHub username",
264
+ placeholder: "your-github-handle",
265
+ hint: "Used in CODEOWNERS \u2014 you can change it later"
186
266
  })
187
267
  },
188
268
  {
@@ -195,12 +275,46 @@ async function promptProject(initialName) {
195
275
  return {
196
276
  projectName: answers.projectName,
197
277
  businessName: answers.businessName,
198
- description: `${answers.businessName} \u2014 Built with GitHat`
278
+ description: `${answers.businessName} \u2014 Built with GitHat`,
279
+ githubUsername: answers.githubUsername || ""
199
280
  };
200
281
  }
201
282
 
202
- // src/prompts/framework.ts
283
+ // src/prompts/project-type.ts
203
284
  import * as p2 from "@clack/prompts";
285
+ async function promptProjectType() {
286
+ const answers = await p2.group(
287
+ {
288
+ projectType: () => p2.select({
289
+ message: "Project type",
290
+ options: [
291
+ {
292
+ value: "frontend",
293
+ label: "Frontend only",
294
+ hint: "Next.js or React+Vite"
295
+ },
296
+ {
297
+ value: "fullstack",
298
+ label: "Fullstack",
299
+ hint: "Next.js + API (Turborepo)"
300
+ }
301
+ ]
302
+ })
303
+ },
304
+ {
305
+ onCancel: () => {
306
+ p2.cancel("Setup cancelled.");
307
+ process.exit(0);
308
+ }
309
+ }
310
+ );
311
+ return {
312
+ projectType: answers.projectType
313
+ };
314
+ }
315
+
316
+ // src/prompts/framework.ts
317
+ import * as p3 from "@clack/prompts";
204
318
 
205
319
  // src/utils/package-manager.ts
206
320
  function detectPackageManager() {
@@ -221,18 +335,27 @@ function getInstallCommand(pm) {
221
335
  }
222
336
 
223
337
  // src/prompts/framework.ts
224
- async function promptFramework(typescriptOverride) {
338
+ async function promptFramework(typescriptOverride, isFullstack) {
225
339
  const packageManager = detectPackageManager();
226
- const answers = await p2.group(
340
+ const frameworkOptions = isFullstack ? [{ value: "nextjs", label: "Next.js 16", hint: "App Router \xB7 SSR \xB7 middleware auth" }] : [
341
+ { value: "plain", label: "Plain", hint: "Auth + a homepage. Smallest possible GitHat app." },
342
+ { value: "saas", label: "SaaS", hint: "Orgs, teams, RBAC, subscription billing. Replaces Clerk + Stripe." },
343
+ { value: "marketplace", label: "Marketplace", hint: "Multi-vendor commerce. Anonymous-first, Sebastn Connect." },
344
+ { value: "content", label: "Content", hint: "Paywalled posts, newsletter. Replaces Substack." },
345
+ { value: "dashboard", label: "Dashboard", hint: "Auth-gated admin UI over your existing database." },
346
+ { value: "portfolio", label: "Portfolio", hint: "Personal site: public projects, auth-gated editor." },
347
+ { value: "classroom", label: "Classroom", hint: "Live student presentations + real-time audience feedback." },
348
+ { value: "agent", label: "AI Agent", hint: "Web4 wallet-bound agent + MCP server. Public verification." },
349
+ { value: "nextjs", label: "Next.js (full kit)", hint: "Legacy: dashboard + orgs + agents + MCP scaffolding." },
350
+ { value: "react-vite", label: "React + Vite", hint: "SPA \xB7 client-side routing." }
351
+ ];
352
+ const answers = await p3.group(
227
353
  {
228
- framework: () => p2.select({
354
+ framework: () => p3.select({
229
355
  message: "Framework",
230
- options: [
231
- { value: "nextjs", label: "Next.js 16", hint: "App Router \xB7 SSR \xB7 middleware auth" },
232
- { value: "react-vite", label: "React 19 + Vite 7", hint: "SPA \xB7 client-side routing" }
233
- ]
356
+ options: frameworkOptions
234
357
  }),
235
- typescript: () => typescriptOverride !== void 0 ? Promise.resolve(typescriptOverride) : p2.select({
358
+ typescript: () => typescriptOverride !== void 0 ? Promise.resolve(typescriptOverride) : p3.select({
236
359
  message: "Language",
237
360
  options: [
238
361
  { value: true, label: "TypeScript", hint: "recommended" },
@@ -242,7 +365,7 @@ async function promptFramework(typescriptOverride) {
242
365
  },
243
366
  {
244
367
  onCancel: () => {
245
- p2.cancel("Setup cancelled.");
368
+ p3.cancel("Setup cancelled.");
246
369
  process.exit(0);
247
370
  }
248
371
  }
@@ -254,9 +377,47 @@ async function promptFramework(typescriptOverride) {
254
377
  };
255
378
  }
256
379
 
380
+ // src/prompts/backend.ts
381
+ import * as p4 from "@clack/prompts";
382
+ async function promptBackend() {
383
+ const answers = await p4.group(
384
+ {
385
+ backendFramework: () => p4.select({
386
+ message: "Backend framework",
387
+ options: [
388
+ {
389
+ value: "hono",
390
+ label: "Hono",
391
+ hint: "recommended \xB7 serverless-native \xB7 14KB"
392
+ },
393
+ {
394
+ value: "express",
395
+ label: "Express",
396
+ hint: "classic Node.js \xB7 large ecosystem"
397
+ },
398
+ {
399
+ value: "fastify",
400
+ label: "Fastify",
401
+ hint: "high performance \xB7 schema validation"
402
+ }
403
+ ]
404
+ })
405
+ },
406
+ {
407
+ onCancel: () => {
408
+ p4.cancel("Setup cancelled.");
409
+ process.exit(0);
410
+ }
411
+ }
412
+ );
413
+ return {
414
+ backendFramework: answers.backendFramework
415
+ };
416
+ }
417
+
257
418
  // src/prompts/githat.ts
258
419
  import { execSync } from "child_process";
259
- import * as p3 from "@clack/prompts";
420
+ import * as p5 from "@clack/prompts";
260
421
  function openBrowser(url) {
261
422
  try {
262
423
  const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
@@ -264,52 +425,104 @@ function openBrowser(url) {
264
425
  } catch {
265
426
  }
266
427
  }
428
+ function sleep(ms) {
429
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
430
+ }
431
+ async function deviceAuthFlow() {
432
+ const spinner2 = p5.spinner();
433
+ try {
434
+ spinner2.start("Requesting device code...");
435
+ const codeRes = await fetch(`${DEFAULT_API_URL}/auth/device/code`, {
436
+ method: "POST",
437
+ headers: { "Content-Type": "application/json" },
438
+ body: JSON.stringify({ client_name: "create-githat-app" })
439
+ });
440
+ if (!codeRes.ok) {
441
+ spinner2.stop("Failed to get device code");
442
+ return null;
443
+ }
444
+ const codeData = await codeRes.json();
445
+ spinner2.stop("Device code generated");
446
+ p5.note(
447
+ `Code: ${codeData.user_code}
448
+
449
+ Opening browser to complete sign-in...
450
+ If it doesn't open, visit: ${codeData.verification_uri_complete}`,
451
+ "Authorize Device"
452
+ );
453
+ openBrowser(codeData.verification_uri_complete);
454
+ spinner2.start("Waiting for browser authorization...");
455
+ const expiresAt = Date.now() + codeData.expires_in * 1e3;
456
+ const pollInterval = (codeData.interval || 5) * 1e3;
457
+ while (Date.now() < expiresAt) {
458
+ await sleep(pollInterval);
459
+ const tokenRes = await fetch(`${DEFAULT_API_URL}/auth/device/token`, {
460
+ method: "POST",
461
+ headers: { "Content-Type": "application/json" },
462
+ body: JSON.stringify({ device_code: codeData.device_code })
463
+ });
464
+ const tokenData = await tokenRes.json();
465
+ if (tokenData.error === "authorization_pending") {
466
+ continue;
467
+ }
468
+ if (tokenData.error === "expired_token") {
469
+ spinner2.stop("Device code expired");
470
+ p5.log.error("Authorization timed out. Please try again.");
471
+ return null;
472
+ }
473
+ if (tokenData.publishable_key) {
474
+ spinner2.stop("Authorized!");
475
+ p5.log.success(`Connected to ${tokenData.app_name || "your app"} (${tokenData.org_name || "your org"})`);
476
+ return tokenData.publishable_key;
477
+ }
478
+ spinner2.stop("Authorization failed");
479
+ return null;
480
+ }
481
+ spinner2.stop("Timed out");
482
+ p5.log.error("Authorization timed out. Please try again.");
483
+ return null;
484
+ } catch (err) {
485
+ spinner2.stop("Connection error");
486
+ p5.log.error("Failed to connect to GitHat API. Check your internet connection.");
487
+ return null;
488
+ }
489
+ }
267
490
  async function promptGitHat(existingKey) {
268
491
  let publishableKey = existingKey || "";
269
492
  if (!publishableKey) {
270
- const connectChoice = await p3.select({
493
+ const connectChoice = await p5.select({
271
494
  message: "Connect to GitHat",
272
495
  options: [
273
- { value: "browser", label: "Sign in with browser", hint: "opens githat.io \u2014 recommended" },
274
- { value: "paste", label: "I have a key", hint: "paste your pk_live_... key" },
275
- { value: "skip", label: "Skip for now", hint: "add key to .env later" }
496
+ { value: "skip", label: "Skip for now", hint: "auth works on localhost \u2014 add key later" },
497
+ { value: "browser", label: "Sign in with browser", hint: "opens githat.io to authorize" },
498
+ { value: "paste", label: "I have a key", hint: "paste your pk_live_... key" }
276
499
  ]
277
500
  });
278
- if (p3.isCancel(connectChoice)) {
279
- p3.cancel("Setup cancelled.");
501
+ if (p5.isCancel(connectChoice)) {
502
+ p5.cancel("Setup cancelled.");
280
503
  process.exit(0);
281
504
  }
282
505
  if (connectChoice === "browser") {
283
- p3.log.step("Opening githat.io in your browser...");
284
- openBrowser("https://githat.io/sign-up");
285
- p3.log.info("Sign up (or sign in), then go to Dashboard \u2192 Apps to copy your key.");
286
- const pastedKey = await p3.text({
287
- message: "Paste your publishable key",
288
- placeholder: "pk_live_...",
289
- validate: validatePublishableKey
290
- });
291
- if (p3.isCancel(pastedKey)) {
292
- p3.cancel("Setup cancelled.");
293
- process.exit(0);
506
+ const key = await deviceAuthFlow();
507
+ if (key) {
508
+ publishableKey = key;
509
+ } else {
510
+ p5.log.warn("Authorization failed. Continuing without key...");
294
511
  }
295
- publishableKey = pastedKey || "";
296
512
  } else if (connectChoice === "paste") {
297
- const pastedKey = await p3.text({
513
+ const pastedKey = await p5.text({
298
514
  message: "Publishable key",
299
515
  placeholder: `pk_live_... (get one at ${DASHBOARD_URL})`,
300
516
  validate: validatePublishableKey
301
517
  });
302
- if (p3.isCancel(pastedKey)) {
303
- p3.cancel("Setup cancelled.");
518
+ if (p5.isCancel(pastedKey)) {
519
+ p5.cancel("Setup cancelled.");
304
520
  process.exit(0);
305
521
  }
306
522
  publishableKey = pastedKey || "";
307
- } else if (connectChoice === "skip") {
308
- p3.log.info("Your app will work on localhost without a key!");
309
- p3.log.info("For production, get your key at https://githat.io/dashboard/apps");
310
523
  }
311
524
  }
312
- const authFeatures = await p3.multiselect({
525
+ const authFeatures = await p5.multiselect({
313
526
  message: "Auth features",
314
527
  options: [
315
528
  { value: "forgot-password", label: "Forgot password", hint: "reset via email" },
@@ -318,11 +531,11 @@ async function promptGitHat(existingKey) {
318
531
  { value: "mcp-servers", label: "MCP servers", hint: "Model Context Protocol" },
319
532
  { value: "ai-agents", label: "AI agents", hint: "wallet-based identity" }
320
533
  ],
321
- initialValues: ["forgot-password", "email-verification"],
534
+ initialValues: ["forgot-password"],
322
535
  required: false
323
536
  });
324
- if (p3.isCancel(authFeatures)) {
325
- p3.cancel("Setup cancelled.");
537
+ if (p5.isCancel(authFeatures)) {
538
+ p5.cancel("Setup cancelled.");
326
539
  process.exit(0);
327
540
  }
328
541
  return {
@@ -333,11 +546,11 @@ async function promptGitHat(existingKey) {
333
546
  }
334
547
 
335
548
  // src/prompts/features.ts
336
- import * as p4 from "@clack/prompts";
549
+ import * as p6 from "@clack/prompts";
337
550
  async function promptFeatures() {
338
- const answers = await p4.group(
551
+ const answers = await p6.group(
339
552
  {
340
- databaseChoice: () => p4.select({
553
+ databaseChoice: () => p6.select({
341
554
  message: "Database",
342
555
  options: [
343
556
  { value: "none", label: "None", hint: "use GitHat backend directly" },
@@ -347,12 +560,12 @@ async function promptFeatures() {
347
560
  { value: "drizzle-sqlite", label: "Drizzle + SQLite" }
348
561
  ]
349
562
  }),
350
- useTailwind: () => p4.confirm({ message: "Tailwind CSS?", initialValue: true }),
351
- includeDashboard: () => p4.confirm({ message: "Include dashboard?", initialValue: true })
563
+ useTailwind: () => p6.confirm({ message: "Tailwind CSS?", initialValue: true }),
564
+ includeDashboard: () => p6.confirm({ message: "Include dashboard?", initialValue: true })
352
565
  },
353
566
  {
354
567
  onCancel: () => {
355
- p4.cancel("Setup cancelled.");
568
+ p6.cancel("Setup cancelled.");
356
569
  process.exit(0);
357
570
  }
358
571
  }
@@ -366,14 +579,14 @@ async function promptFeatures() {
366
579
  }
367
580
 
368
581
  // src/prompts/finalize.ts
369
- import * as p5 from "@clack/prompts";
582
+ import * as p7 from "@clack/prompts";
370
583
  async function promptFinalize() {
371
- const installDeps = await p5.confirm({
584
+ const installDeps = await p7.confirm({
372
585
  message: "Install dependencies?",
373
586
  initialValue: true
374
587
  });
375
- if (p5.isCancel(installDeps)) {
376
- p5.cancel("Setup cancelled.");
588
+ if (p7.isCancel(installDeps)) {
589
+ p7.cancel("Setup cancelled.");
377
590
  process.exit(0);
378
591
  }
379
592
  return {
@@ -383,30 +596,75 @@ async function promptFinalize() {
383
596
  }
384
597
 
385
598
  // src/prompts/index.ts
599
+ function toDisplayName(projectName) {
600
+ return projectName.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
601
+ }
602
+ function getDefaults(projectName, publishableKey, typescript, fullstack, backendFramework, template, githubUsername) {
603
+ const displayName = toDisplayName(projectName);
604
+ const projectType = fullstack ? "fullstack" : "frontend";
605
+ const framework = template ?? "nextjs";
606
+ const isMinimal = template !== void 0;
607
+ return {
608
+ projectName,
609
+ businessName: displayName,
610
+ description: `${displayName} \u2014 Built with GitHat`,
611
+ githubUsername: githubUsername || projectName,
612
+ projectType,
613
+ backendFramework: fullstack ? backendFramework || "hono" : void 0,
614
+ framework,
615
+ typescript: typescript ?? true,
616
+ packageManager: detectPackageManager(),
617
+ publishableKey: publishableKey || "",
618
+ apiUrl: DEFAULT_API_URL,
619
+ authFeatures: isMinimal ? [] : ["forgot-password"],
620
+ databaseChoice: "none",
621
+ useTailwind: true,
622
+ includeDashboard: !isMinimal,
623
+ includeGithatFolder: !isMinimal && projectType === "frontend",
624
+ initGit: true,
625
+ installDeps: true
626
+ };
627
+ }
386
628
  async function runPrompts(args) {
387
- p6.intro("Let\u2019s set up your GitHat app");
629
+ if (args.yes && args.initialName) {
630
+ p8.log.info(
631
+ args.template ? `Using ${args.template} defaults (--yes --${args.template})` : "Using defaults (--yes flag)"
632
+ );
633
+ return getDefaults(args.initialName, args.publishableKey, args.typescript, args.fullstack, args.backendFramework, args.template, args.githubUsername);
634
+ }
635
+ p8.intro("Let's set up your GitHat app");
388
636
  sectionHeader("Project");
389
637
  const project = await promptProject(args.initialName);
638
+ const projectType = await promptProjectType();
390
639
  sectionHeader("Stack");
391
- const framework = await promptFramework(args.typescript);
640
+ const isFullstack = projectType.projectType === "fullstack";
641
+ const framework = await promptFramework(args.typescript, isFullstack);
642
+ let backend = {};
643
+ if (isFullstack) {
644
+ backend = await promptBackend();
645
+ }
392
646
  sectionHeader("Connect");
393
647
  const githat = await promptGitHat(args.publishableKey);
394
648
  sectionHeader("Features");
395
649
  const features = await promptFeatures();
396
650
  sectionHeader("Finish");
397
651
  const finalize = await promptFinalize();
398
- return { ...project, ...framework, ...githat, ...features, ...finalize };
652
+ return { ...project, ...projectType, ...framework, ...backend, ...githat, ...features, ...finalize };
399
653
  }
400
654
  function answersToContext(answers) {
401
655
  return {
402
656
  projectName: answers.projectName,
403
657
  businessName: answers.businessName,
404
658
  description: answers.description,
659
+ githubUsername: answers.githubUsername || answers.projectName,
405
660
  framework: answers.framework,
406
661
  typescript: answers.typescript,
407
662
  packageManager: answers.packageManager,
408
663
  publishableKey: answers.publishableKey,
409
664
  apiUrl: answers.apiUrl,
665
+ // Project type
666
+ projectType: answers.projectType,
667
+ backendFramework: answers.backendFramework,
410
668
  useDatabase: answers.databaseChoice !== "none",
411
669
  databaseChoice: answers.databaseChoice,
412
670
  useTailwind: answers.useTailwind,
@@ -423,11 +681,11 @@ function answersToContext(answers) {
423
681
  }
424
682
 
425
683
  // src/scaffold/index.ts
426
- import fs3 from "fs-extra";
427
- import path3 from "path";
684
+ import fs4 from "fs-extra";
685
+ import path4 from "path";
428
686
  import { execSync as execSync3 } from "child_process";
429
- import * as p7 from "@clack/prompts";
430
- import chalk2 from "chalk";
687
+ import * as p10 from "@clack/prompts";
688
+ import chalk3 from "chalk";
431
689
 
432
690
  // src/utils/template-engine.ts
433
691
  import Handlebars from "handlebars";
@@ -440,6 +698,20 @@ var TEMPLATES_ROOT = path.resolve(__dirname2, "..", "templates");
440
698
  Handlebars.registerHelper("ifEquals", function(a, b, options) {
441
699
  return a === b ? options.fn(this) : options.inverse(this);
442
700
  });
701
+ var NEXT_LIKE = /* @__PURE__ */ new Set([
702
+ "nextjs",
703
+ "plain",
704
+ "saas",
705
+ "marketplace",
706
+ "agent",
707
+ "content",
708
+ "dashboard",
709
+ "portfolio",
710
+ "classroom"
711
+ ]);
712
+ Handlebars.registerHelper("ifNext", function(framework, options) {
713
+ return NEXT_LIKE.has(framework) ? options.fn(this) : options.inverse(this);
714
+ });
443
715
  function getTemplatesRoot() {
444
716
  return TEMPLATES_ROOT;
445
717
  }
@@ -486,15 +758,39 @@ function writeJson(root, relativePath, data) {
486
758
  }
487
759
 
488
760
  // src/scaffold/package-builder.ts
761
+ var NEXT_LIKE2 = /* @__PURE__ */ new Set([
762
+ "nextjs",
763
+ "plain",
764
+ "saas",
765
+ "marketplace",
766
+ "agent",
767
+ "content",
768
+ "dashboard",
769
+ "portfolio",
770
+ "classroom"
771
+ ]);
772
+ var MINIMAL = /* @__PURE__ */ new Set([
773
+ "plain",
774
+ "saas",
775
+ "marketplace",
776
+ "agent",
777
+ "content",
778
+ "dashboard",
779
+ "portfolio",
780
+ "classroom"
781
+ ]);
489
782
  function buildPackageJson(ctx) {
490
- const isNext = ctx.framework === "nextjs";
783
+ const isNext = NEXT_LIKE2.has(ctx.framework);
491
784
  const deps = {
492
785
  ...isNext ? DEPS.nextjs.dependencies : DEPS["react-vite"].dependencies
493
786
  };
494
787
  const devDeps = {
495
788
  ...isNext ? DEPS.nextjs.devDependencies : DEPS["react-vite"].devDependencies
496
789
  };
497
- if (ctx.useTailwind) {
790
+ if (MINIMAL.has(ctx.framework)) {
791
+ delete deps["@githat/ui"];
792
+ }
793
+ if (ctx.useTailwind || MINIMAL.has(ctx.framework)) {
498
794
  if (isNext) {
499
795
  Object.assign(devDeps, DEPS.tailwind.devDependencies);
500
796
  } else {
@@ -543,18 +839,18 @@ function sortKeys(obj) {
543
839
 
544
840
  // src/utils/spinner.ts
545
841
  import ora from "ora";
546
- function createSpinner(text3) {
547
- return ora({ text: text3, color: "magenta" });
842
+ function createSpinner(text4) {
843
+ return ora({ text: text4, color: "magenta" });
548
844
  }
549
- async function withSpinner(text3, fn, successText) {
550
- const spinner = createSpinner(text3);
551
- spinner.start();
845
+ async function withSpinner(text4, fn, successText) {
846
+ const spinner2 = createSpinner(text4);
847
+ spinner2.start();
552
848
  try {
553
849
  const result = await fn();
554
- spinner.succeed(successText || text3);
850
+ spinner2.succeed(successText || text4);
555
851
  return result;
556
852
  } catch (err) {
557
- spinner.fail(text3);
853
+ spinner2.fail(text4);
558
854
  throw err;
559
855
  }
560
856
  }
@@ -575,30 +871,133 @@ function initGit(cwd) {
575
871
  }
576
872
  }
577
873
 
874
+ // src/utils/register-app.ts
875
+ import fs3 from "fs-extra";
876
+ import path3 from "path";
877
+ import os from "os";
878
+ import * as p9 from "@clack/prompts";
879
+ import chalk2 from "chalk";
880
+ var CREDENTIALS_PATH = path3.join(os.homedir(), ".githat", "credentials.json");
881
+ function readToken() {
882
+ if (!fs3.existsSync(CREDENTIALS_PATH)) {
883
+ return null;
884
+ }
885
+ try {
886
+ const creds = fs3.readJsonSync(CREDENTIALS_PATH);
887
+ return creds.token || null;
888
+ } catch {
889
+ return null;
890
+ }
891
+ }
892
+ async function registerApp(appName, projectRoot) {
893
+ const token = readToken();
894
+ if (!token) {
895
+ p9.log.warn(
896
+ chalk2.yellow(
897
+ `GitHat credentials not found at ${CREDENTIALS_PATH}.
898
+ Run ${chalk2.cyan("githat login")} then re-run scaffolding, or paste your publishable key
899
+ from ${chalk2.cyan("https://githat.io/dashboard/apps")} into .env.local manually.`
900
+ )
901
+ );
902
+ return null;
903
+ }
904
+ p9.log.step("Registering app on GitHat...");
905
+ let registeredApp;
906
+ try {
907
+ const response = await fetch(`${DEFAULT_API_URL}/apps`, {
908
+ method: "POST",
909
+ headers: {
910
+ "Content-Type": "application/json",
911
+ Authorization: `Bearer ${token}`
912
+ },
913
+ body: JSON.stringify({
914
+ name: appName,
915
+ redirect_uris: ["http://localhost:3000/callback"]
916
+ })
917
+ });
918
+ if (!response.ok) {
919
+ const body = await response.text().catch(() => "");
920
+ throw new Error(`HTTP ${response.status}: ${body}`);
921
+ }
922
+ registeredApp = await response.json();
923
+ } catch (err) {
924
+ p9.log.warn(
925
+ chalk2.yellow(
926
+ `Could not register app on GitHat: ${err.message}
927
+ The scaffold was written successfully. Once the API is available,
928
+ register manually at ${chalk2.cyan("https://githat.io/dashboard/apps")} and paste the
929
+ publishable key into ${chalk2.cyan(".env.local")}.`
930
+ )
931
+ );
932
+ return null;
933
+ }
934
+ const publishableKey = registeredApp.publishable_key;
935
+ const envLocalPath = path3.join(projectRoot, ".env.local");
936
+ try {
937
+ let envContent = fs3.existsSync(envLocalPath) ? fs3.readFileSync(envLocalPath, "utf-8") : "";
938
+ const keyLine = `NEXT_PUBLIC_GITHAT_PUBLISHABLE_KEY=${publishableKey}`;
939
+ if (envContent.includes("NEXT_PUBLIC_GITHAT_PUBLISHABLE_KEY=")) {
940
+ envContent = envContent.replace(
941
+ /NEXT_PUBLIC_GITHAT_PUBLISHABLE_KEY=.*/,
942
+ keyLine
943
+ );
944
+ } else {
945
+ envContent += (envContent.endsWith("\n") || envContent === "" ? "" : "\n") + keyLine + "\n";
946
+ }
947
+ fs3.ensureDirSync(path3.dirname(envLocalPath));
948
+ fs3.writeFileSync(envLocalPath, envContent, "utf-8");
949
+ } catch (err) {
950
+ p9.log.warn(chalk2.yellow(`Registered app but could not write .env.local: ${err.message}`));
951
+ }
952
+ p9.log.success(chalk2.green(`Registered "${appName}" on GitHat. Publishable key written to .env.local.`));
953
+ return publishableKey;
954
+ }
955
+
578
956
  // src/scaffold/index.ts
579
957
  async function scaffold(context, options) {
580
- const root = path3.resolve(process.cwd(), context.projectName);
581
- if (fs3.existsSync(root)) {
582
- p7.cancel(`Directory "${context.projectName}" already exists.`);
958
+ const root = path4.resolve(process.cwd(), context.projectName);
959
+ if (fs4.existsSync(root)) {
960
+ p10.cancel(`Directory "${context.projectName}" already exists.`);
583
961
  process.exit(1);
584
962
  }
963
+ const isFullstack = context.projectType === "fullstack";
585
964
  await withSpinner("Creating project structure...", async () => {
586
- fs3.ensureDirSync(root);
965
+ fs4.ensureDirSync(root);
587
966
  const templatesRoot = getTemplatesRoot();
588
- const frameworkDir = path3.join(templatesRoot, context.framework);
589
- if (!fs3.existsSync(frameworkDir)) {
590
- throw new Error(`Templates not found at ${frameworkDir}. This is a bug \u2014 please report it.`);
591
- }
592
- renderTemplateDirectory(frameworkDir, root, context);
593
- const baseDir = path3.join(templatesRoot, "base");
594
- if (fs3.existsSync(baseDir)) {
595
- renderTemplateDirectory(baseDir, root, context);
967
+ if (isFullstack) {
968
+ scaffoldFullstack(templatesRoot, root, context);
969
+ } else {
970
+ const frameworkDir = path4.join(templatesRoot, context.framework);
971
+ if (!fs4.existsSync(frameworkDir)) {
972
+ throw new Error(`Templates not found at ${frameworkDir}. This is a bug \u2014 please report it.`);
973
+ }
974
+ renderTemplateDirectory(frameworkDir, root, context);
975
+ const baseDir = path4.join(templatesRoot, "base");
976
+ if (fs4.existsSync(baseDir)) {
977
+ renderTemplateDirectory(baseDir, root, context);
978
+ }
596
979
  }
597
980
  }, "Project structure created");
598
- await withSpinner("Generating package.json...", async () => {
599
- const pkg = buildPackageJson(context);
600
- writeJson(root, "package.json", pkg);
601
- }, "package.json generated");
981
+ if (!isFullstack) {
982
+ await withSpinner("Generating package.json...", async () => {
983
+ const pkg = buildPackageJson(context);
984
+ writeJson(root, "package.json", pkg);
985
+ }, "package.json generated");
986
+ }
987
+ const NEXT_LIKE3 = /* @__PURE__ */ new Set([
988
+ "nextjs",
989
+ "plain",
990
+ "saas",
991
+ "marketplace",
992
+ "agent",
993
+ "content",
994
+ "dashboard",
995
+ "portfolio",
996
+ "classroom"
997
+ ]);
998
+ if (!isFullstack && NEXT_LIKE3.has(context.framework)) {
999
+ await registerApp(context.projectName, root);
1000
+ }
602
1001
  if (options.initGit) {
603
1002
  const gitSpinner = createSpinner("Initializing git repository...");
604
1003
  gitSpinner.start();
@@ -619,38 +1018,686 @@ async function scaffold(context, options) {
619
1018
  } catch (err) {
620
1019
  const msg = err.message || "";
621
1020
  if (msg.includes("TIMEOUT")) {
622
- p7.log.warn(`Install timed out. Run ${chalk2.cyan(installCmd)} manually.`);
1021
+ p10.log.warn(`Install timed out. Run ${chalk3.cyan(installCmd)} manually.`);
623
1022
  } else {
624
- p7.log.warn(`Could not auto-install. Run ${chalk2.cyan(installCmd)} manually.`);
1023
+ p10.log.warn(`Could not auto-install. Run ${chalk3.cyan(installCmd)} manually.`);
625
1024
  }
626
1025
  }
627
1026
  },
628
1027
  "Dependencies installed"
629
1028
  );
630
1029
  }
631
- p7.outro("Setup complete!");
632
- displaySuccess(context.projectName, context.packageManager, context.framework);
1030
+ p10.outro("Setup complete!");
1031
+ displaySuccess(context.projectName, context.packageManager, context.framework, !!context.publishableKey, isFullstack);
1032
+ if (!options.skipPrompts) {
1033
+ const starPrompt = await p10.confirm({
1034
+ message: "Star GitHat on GitHub? (helps us grow!)",
1035
+ initialValue: false
1036
+ });
1037
+ if (!p10.isCancel(starPrompt) && starPrompt) {
1038
+ try {
1039
+ const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
1040
+ execSync3(`${cmd} "https://github.com/GitHat-IO/githat"`, { stdio: "ignore" });
1041
+ } catch {
1042
+ p10.log.info("Visit https://github.com/GitHat-IO/githat to star us!");
1043
+ }
1044
+ }
1045
+ }
1046
+ }
1047
+ function scaffoldFullstack(templatesRoot, root, context) {
1048
+ const fullstackDir = path4.join(templatesRoot, "fullstack");
1049
+ const rootDir = path4.join(fullstackDir, "root");
1050
+ if (fs4.existsSync(rootDir)) {
1051
+ renderTemplateDirectory(rootDir, root, context);
1052
+ }
1053
+ const appsDir = path4.join(root, "apps");
1054
+ fs4.ensureDirSync(appsDir);
1055
+ const webDir = path4.join(appsDir, "web");
1056
+ fs4.ensureDirSync(webDir);
1057
+ const webTemplateDir = path4.join(fullstackDir, `apps-web-${context.framework}`);
1058
+ if (fs4.existsSync(webTemplateDir)) {
1059
+ renderTemplateDirectory(webTemplateDir, webDir, context);
1060
+ } else {
1061
+ throw new Error(`Web app templates not found at ${webTemplateDir}. This is a bug \u2014 please report it.`);
1062
+ }
1063
+ const apiDir = path4.join(appsDir, "api");
1064
+ fs4.ensureDirSync(apiDir);
1065
+ const backendFramework = context.backendFramework || "hono";
1066
+ const apiTemplateDir = path4.join(fullstackDir, `apps-api-${backendFramework}`);
1067
+ if (fs4.existsSync(apiTemplateDir)) {
1068
+ renderTemplateDirectory(apiTemplateDir, apiDir, context);
1069
+ } else {
1070
+ throw new Error(`API templates not found at ${apiTemplateDir}. This is a bug \u2014 please report it.`);
1071
+ }
1072
+ const packagesDir = path4.join(root, "packages");
1073
+ fs4.ensureDirSync(packagesDir);
1074
+ fs4.writeFileSync(path4.join(packagesDir, ".gitkeep"), "");
1075
+ }
1076
+
1077
+ // src/commands/skills/index.ts
1078
+ import { Command as Command6 } from "commander";
1079
+ import chalk9 from "chalk";
1080
+
1081
+ // src/commands/skills/search.ts
1082
+ import { Command } from "commander";
1083
+ import chalk4 from "chalk";
1084
+
1085
+ // src/commands/skills/api.ts
1086
+ async function fetchApi(endpoint, options = {}) {
1087
+ const url = `${DEFAULT_API_URL}${endpoint}`;
1088
+ const response = await fetch(url, {
1089
+ ...options,
1090
+ headers: {
1091
+ "Content-Type": "application/json",
1092
+ ...options.headers
1093
+ }
1094
+ });
1095
+ if (!response.ok) {
1096
+ const error = await response.json().catch(() => ({ error: response.statusText }));
1097
+ throw new Error(error.error || `API error: ${response.status}`);
1098
+ }
1099
+ return response.json();
1100
+ }
1101
+ async function searchSkills(query, type) {
1102
+ const params = new URLSearchParams();
1103
+ if (type) params.set("type", type);
1104
+ const url = `/skills?${params.toString()}`;
1105
+ const result = await fetchApi(url);
1106
+ const q = query.toLowerCase();
1107
+ return {
1108
+ skills: result.skills.filter(
1109
+ (s) => s.name.toLowerCase().includes(q) || s.description.toLowerCase().includes(q) || s.keywords.some((k) => k.toLowerCase().includes(q))
1110
+ )
1111
+ };
1112
+ }
1113
+ async function listSkills(options) {
1114
+ const params = new URLSearchParams();
1115
+ if (options.type) params.set("type", options.type);
1116
+ if (options.limit) params.set("limit", options.limit.toString());
1117
+ if (options.cursor) params.set("cursor", options.cursor);
1118
+ return fetchApi(`/skills?${params.toString()}`);
1119
+ }
1120
+ async function getSkill(slug) {
1121
+ return fetchApi(`/skills/${slug}`);
1122
+ }
1123
+ async function getDownloadUrl(slug, version) {
1124
+ const params = version ? `?version=${version}` : "";
1125
+ return fetchApi(`/skills/${slug}/download${params}`);
1126
+ }
1127
+
1128
+ // src/commands/skills/search.ts
1129
+ function formatSkill(skill) {
1130
+ const typeColors = {
1131
+ template: chalk4.blue,
1132
+ integration: chalk4.green,
1133
+ ui: chalk4.magenta,
1134
+ ai: chalk4.yellow,
1135
+ workflow: chalk4.cyan
1136
+ };
1137
+ const typeColor = typeColors[skill.type] || chalk4.white;
1138
+ return [
1139
+ `${chalk4.bold(skill.name)} ${chalk4.dim(`@${skill.latestVersion}`)}`,
1140
+ ` ${skill.description}`,
1141
+ ` ${typeColor(skill.type)} \xB7 \u2B07 ${skill.downloads} \xB7 \u2B50 ${skill.stars} \xB7 by ${skill.authorName}`,
1142
+ ` ${chalk4.dim(`githat skills install ${skill.slug}`)}`
1143
+ ].join("\n");
1144
+ }
1145
+ var searchCommand = new Command("search").description("Search skills by keyword").argument("<query>", "Search query").option("-t, --type <type>", "Filter by type (template, integration, ui, ai, workflow)").action(async (query, options) => {
1146
+ try {
1147
+ console.log(chalk4.dim(`
1148
+ Searching for "${query}"...
1149
+ `));
1150
+ const result = await searchSkills(query, options.type);
1151
+ if (result.skills.length === 0) {
1152
+ console.log(chalk4.yellow("No skills found matching your query."));
1153
+ console.log(chalk4.dim("\nTry a different search term or browse all skills:"));
1154
+ console.log(chalk4.dim(" githat skills list"));
1155
+ return;
1156
+ }
1157
+ console.log(chalk4.cyan(`Found ${result.skills.length} skill(s):
1158
+ `));
1159
+ for (const skill of result.skills) {
1160
+ console.log(formatSkill(skill));
1161
+ console.log("");
1162
+ }
1163
+ } catch (err) {
1164
+ console.error(chalk4.red(`Error: ${err.message}`));
1165
+ process.exit(1);
1166
+ }
1167
+ });
1168
+
1169
+ // src/commands/skills/list.ts
1170
+ import { Command as Command2 } from "commander";
1171
+ import chalk5 from "chalk";
1172
+ function formatSkillCompact(skill) {
1173
+ const typeColors = {
1174
+ template: chalk5.blue,
1175
+ integration: chalk5.green,
1176
+ ui: chalk5.magenta,
1177
+ ai: chalk5.yellow,
1178
+ workflow: chalk5.cyan
1179
+ };
1180
+ const typeColor = typeColors[skill.type] || chalk5.white;
1181
+ const name = chalk5.bold(skill.name.padEnd(25));
1182
+ const type = typeColor(skill.type.padEnd(12));
1183
+ const stats = `\u2B07 ${String(skill.downloads).padStart(5)} \u2B50 ${String(skill.stars).padStart(4)}`;
1184
+ const desc = skill.description.length > 40 ? skill.description.substring(0, 37) + "..." : skill.description;
1185
+ return `${name} ${type} ${stats} ${chalk5.dim(desc)}`;
1186
+ }
1187
+ var listCommand = new Command2("list").description("List available skills").option("-t, --type <type>", "Filter by type (template, integration, ui, ai, workflow)").option("-l, --limit <n>", "Number of results (default: 25)", "25").action(async (options) => {
1188
+ try {
1189
+ const limit = parseInt(options.limit, 10);
1190
+ console.log(chalk5.dim("\nFetching skills...\n"));
1191
+ const result = await listSkills({ type: options.type, limit });
1192
+ if (result.skills.length === 0) {
1193
+ console.log(chalk5.yellow("No skills found."));
1194
+ if (options.type) {
1195
+ console.log(chalk5.dim(`
1196
+ Try without the type filter:`));
1197
+ console.log(chalk5.dim(" githat skills list"));
1198
+ }
1199
+ return;
1200
+ }
1201
+ const header = `${"NAME".padEnd(25)} ${"TYPE".padEnd(12)} ${"DOWNLOADS".padStart(10)} DESCRIPTION`;
1202
+ console.log(chalk5.dim(header));
1203
+ console.log(chalk5.dim("\u2500".repeat(80)));
1204
+ for (const skill of result.skills) {
1205
+ console.log(formatSkillCompact(skill));
1206
+ }
1207
+ console.log(chalk5.dim("\u2500".repeat(80)));
1208
+ console.log(chalk5.dim(`Showing ${result.skills.length} skill(s)`));
1209
+ if (result.nextCursor) {
1210
+ console.log(chalk5.dim("\nMore results available. Use --limit to see more."));
1211
+ }
1212
+ console.log(chalk5.dim("\nTo install: githat skills install <name>"));
1213
+ } catch (err) {
1214
+ console.error(chalk5.red(`Error: ${err.message}`));
1215
+ process.exit(1);
1216
+ }
1217
+ });
1218
+
1219
+ // src/commands/skills/install.ts
1220
+ import { Command as Command3 } from "commander";
1221
+ import chalk6 from "chalk";
1222
+ import * as fs5 from "fs";
1223
+ import * as path5 from "path";
1224
+ import { pipeline } from "stream/promises";
1225
+ import { createWriteStream, mkdirSync } from "fs";
1226
+ import { Extract } from "unzipper";
1227
+ async function downloadAndExtract(url, destDir) {
1228
+ const response = await fetch(url);
1229
+ if (!response.ok) {
1230
+ throw new Error(`Download failed: ${response.statusText}`);
1231
+ }
1232
+ const tempZip = path5.join(destDir, ".skill-download.zip");
1233
+ const fileStream = createWriteStream(tempZip);
1234
+ await pipeline(response.body, fileStream);
1235
+ await new Promise((resolve4, reject) => {
1236
+ fs5.createReadStream(tempZip).pipe(Extract({ path: destDir })).on("close", resolve4).on("error", reject);
1237
+ });
1238
+ fs5.unlinkSync(tempZip);
1239
+ }
1240
+ function updateGithatLock(projectDir, skill) {
1241
+ const lockPath = path5.join(projectDir, "githat.lock");
1242
+ let lock = {};
1243
+ if (fs5.existsSync(lockPath)) {
1244
+ try {
1245
+ lock = JSON.parse(fs5.readFileSync(lockPath, "utf-8"));
1246
+ } catch {
1247
+ }
1248
+ }
1249
+ lock[skill.slug] = {
1250
+ version: skill.version,
1251
+ installedAt: (/* @__PURE__ */ new Date()).toISOString()
1252
+ };
1253
+ fs5.writeFileSync(lockPath, JSON.stringify(lock, null, 2));
1254
+ }
1255
+ function updateEnvExample(projectDir, manifest) {
1256
+ if (!manifest.requires?.env?.length) return;
1257
+ const envPath = path5.join(projectDir, ".env.local");
1258
+ const envExamplePath = path5.join(projectDir, ".env.example");
1259
+ let envContent = "";
1260
+ if (fs5.existsSync(envPath)) {
1261
+ envContent = fs5.readFileSync(envPath, "utf-8");
1262
+ } else if (fs5.existsSync(envExamplePath)) {
1263
+ envContent = fs5.readFileSync(envExamplePath, "utf-8");
1264
+ }
1265
+ const existingVars = new Set(
1266
+ envContent.split("\n").filter((line) => line.includes("=")).map((line) => line.split("=")[0].trim())
1267
+ );
1268
+ const newVars = [];
1269
+ for (const envVar of manifest.requires.env) {
1270
+ if (!existingVars.has(envVar)) {
1271
+ newVars.push(`${envVar}=`);
1272
+ }
1273
+ }
1274
+ if (newVars.length > 0) {
1275
+ const addition = `
1276
+ # Added by skill install
1277
+ ${newVars.join("\n")}
1278
+ `;
1279
+ if (fs5.existsSync(envPath)) {
1280
+ fs5.appendFileSync(envPath, addition);
1281
+ } else {
1282
+ fs5.writeFileSync(envPath, `# Environment variables
1283
+ ${newVars.join("\n")}
1284
+ `);
1285
+ }
1286
+ }
1287
+ }
1288
+ var installCommand = new Command3("install").description("Install a skill to your project").argument("<slug>", "Skill slug (e.g., stripe-billing)").option("-v, --version <version>", "Specific version to install").option("-d, --dir <dir>", "Project directory (default: current directory)").action(async (slug, options) => {
1289
+ try {
1290
+ const projectDir = options.dir ? path5.resolve(options.dir) : process.cwd();
1291
+ const packageJsonPath = path5.join(projectDir, "package.json");
1292
+ if (!fs5.existsSync(packageJsonPath)) {
1293
+ console.error(chalk6.red("Error: No package.json found. Are you in a project directory?"));
1294
+ process.exit(1);
1295
+ }
1296
+ console.log(chalk6.dim(`
1297
+ Fetching skill info for "${slug}"...
1298
+ `));
1299
+ const skill = await getSkill(slug);
1300
+ console.log(chalk6.cyan(`\u{1F4E6} ${skill.name}`));
1301
+ console.log(chalk6.dim(` ${skill.description}`));
1302
+ console.log(chalk6.dim(` Type: ${skill.type} \xB7 Author: ${skill.authorName}
1303
+ `));
1304
+ const download = await getDownloadUrl(slug, options.version);
1305
+ const version = download.version.version;
1306
+ console.log(chalk6.dim(`Downloading ${skill.name}@${version}...`));
1307
+ const skillDir = path5.join(projectDir, "githat", "skills", slug);
1308
+ mkdirSync(skillDir, { recursive: true });
1309
+ await downloadAndExtract(download.downloadUrl, skillDir);
1310
+ console.log(chalk6.green(`\u2713 Downloaded to ${path5.relative(projectDir, skillDir)}`));
1311
+ const manifestPath = path5.join(skillDir, "githat-skill.json");
1312
+ if (fs5.existsSync(manifestPath)) {
1313
+ const manifest = JSON.parse(fs5.readFileSync(manifestPath, "utf-8"));
1314
+ updateEnvExample(projectDir, manifest);
1315
+ if (manifest.requires?.env?.length) {
1316
+ console.log(chalk6.yellow(`
1317
+ \u26A0 Required environment variables:`));
1318
+ for (const envVar of manifest.requires.env) {
1319
+ console.log(chalk6.dim(` ${envVar}`));
1320
+ }
1321
+ console.log(chalk6.dim(`
1322
+ Add these to your .env.local file`));
1323
+ }
1324
+ if (manifest.install?.dependencies) {
1325
+ const deps = Object.entries(manifest.install.dependencies).map(([name, ver]) => `${name}@${ver}`).join(" ");
1326
+ console.log(chalk6.yellow(`
1327
+ \u26A0 Install npm dependencies:`));
1328
+ console.log(chalk6.dim(` npm install ${deps}`));
1329
+ }
1330
+ }
1331
+ updateGithatLock(projectDir, { slug, version });
1332
+ console.log(chalk6.green(`
1333
+ \u2705 Successfully installed ${skill.name}@${version}
1334
+ `));
1335
+ console.log(chalk6.dim("Next steps:"));
1336
+ console.log(chalk6.dim(` 1. Check githat/skills/${slug}/README.md for usage`));
1337
+ console.log(chalk6.dim(" 2. Add required environment variables to .env.local"));
1338
+ console.log(chalk6.dim(" 3. Import and use the skill in your code"));
1339
+ } catch (err) {
1340
+ console.error(chalk6.red(`Error: ${err.message}`));
1341
+ process.exit(1);
1342
+ }
1343
+ });
1344
+
1345
+ // src/commands/skills/installed.ts
1346
+ import { Command as Command4 } from "commander";
1347
+ import chalk7 from "chalk";
1348
+ import * as fs6 from "fs";
1349
+ import * as path6 from "path";
1350
+ var installedCommand = new Command4("installed").alias("ls").description("List installed skills in current project").option("-d, --dir <dir>", "Project directory (default: current directory)").action(async (options) => {
1351
+ try {
1352
+ const projectDir = options.dir ? path6.resolve(options.dir) : process.cwd();
1353
+ const lockPath = path6.join(projectDir, "githat.lock");
1354
+ if (!fs6.existsSync(lockPath)) {
1355
+ console.log(chalk7.yellow("\nNo skills installed in this project."));
1356
+ console.log(chalk7.dim("\nTo install a skill:"));
1357
+ console.log(chalk7.dim(" githat skills install <slug>"));
1358
+ return;
1359
+ }
1360
+ let lock;
1361
+ try {
1362
+ lock = JSON.parse(fs6.readFileSync(lockPath, "utf-8"));
1363
+ } catch {
1364
+ console.error(chalk7.red("Error: Invalid githat.lock file"));
1365
+ process.exit(1);
1366
+ }
1367
+ const entries = Object.entries(lock);
1368
+ if (entries.length === 0) {
1369
+ console.log(chalk7.yellow("\nNo skills installed in this project."));
1370
+ return;
1371
+ }
1372
+ console.log(chalk7.cyan(`
1373
+ \u{1F4E6} Installed skills (${entries.length}):
1374
+ `));
1375
+ console.log(chalk7.dim(`${"SKILL".padEnd(30)} ${"VERSION".padEnd(12)} INSTALLED`));
1376
+ console.log(chalk7.dim("\u2500".repeat(60)));
1377
+ for (const [slug, entry] of entries) {
1378
+ const date = new Date(entry.installedAt).toLocaleDateString();
1379
+ console.log(`${chalk7.bold(slug.padEnd(30))} ${entry.version.padEnd(12)} ${chalk7.dim(date)}`);
1380
+ }
1381
+ console.log(chalk7.dim("\u2500".repeat(60)));
1382
+ console.log(chalk7.dim("\nTo update a skill:"));
1383
+ console.log(chalk7.dim(" githat skills install <slug> --version <new-version>"));
1384
+ } catch (err) {
1385
+ console.error(chalk7.red(`Error: ${err.message}`));
1386
+ process.exit(1);
1387
+ }
1388
+ });
1389
+
1390
+ // src/commands/skills/init.ts
1391
+ import { Command as Command5 } from "commander";
1392
+ import chalk8 from "chalk";
1393
+ import * as fs7 from "fs";
1394
+ import * as path7 from "path";
1395
+ import * as p11 from "@clack/prompts";
1396
+ var SKILL_TYPES = ["template", "integration", "ui", "ai", "workflow"];
1397
+ function generateReadme(manifest) {
1398
+ return `# ${manifest.name}
1399
+
1400
+ ${manifest.description}
1401
+
1402
+ ## Installation
1403
+
1404
+ \`\`\`bash
1405
+ githat skills install ${manifest.name}
1406
+ \`\`\`
1407
+
1408
+ ## Requirements
1409
+
1410
+ ${manifest.requires.env.length > 0 ? `
1411
+ ### Environment Variables
1412
+
1413
+ ${manifest.requires.env.map((e) => `- \`${e}\``).join("\n")}
1414
+ ` : ""}
1415
+ ${manifest.requires.tier ? `
1416
+ ### Minimum Tier
1417
+
1418
+ This skill requires **${manifest.requires.tier}** tier or higher.
1419
+ ` : ""}
1420
+
1421
+ ## Usage
1422
+
1423
+ \`\`\`typescript
1424
+ // Import from the installed skill
1425
+ import { /* exports */ } from './githat/skills/${manifest.name}';
1426
+
1427
+ // Use the skill
1428
+ // ...
1429
+ \`\`\`
1430
+
1431
+ ## License
1432
+
1433
+ ${manifest.license}
1434
+ `;
633
1435
  }
1436
+ function generateIndexFile(manifest) {
1437
+ const typeExports = {
1438
+ template: `// Template skill - provides project scaffolding
1439
+ export const templateName = '${manifest.name}';
1440
+ export const templateVersion = '${manifest.version}';
1441
+
1442
+ // Add your template exports here
1443
+ `,
1444
+ integration: `// Integration skill - connects to external services
1445
+ // Replace with your actual integration code
1446
+
1447
+ export interface ${toPascalCase(manifest.name)}Config {
1448
+ apiKey: string;
1449
+ // Add configuration options
1450
+ }
1451
+
1452
+ export function create${toPascalCase(manifest.name)}(config: ${toPascalCase(manifest.name)}Config) {
1453
+ // Initialize your integration
1454
+ return {
1455
+ // Return your integration client/functions
1456
+ };
1457
+ }
1458
+ `,
1459
+ ui: `// UI skill - provides React components
1460
+ import React from 'react';
1461
+
1462
+ export interface ${toPascalCase(manifest.name)}Props {
1463
+ // Add component props
1464
+ }
1465
+
1466
+ export function ${toPascalCase(manifest.name)}({ ...props }: ${toPascalCase(manifest.name)}Props) {
1467
+ return (
1468
+ <div>
1469
+ {/* Your component */}
1470
+ </div>
1471
+ );
1472
+ }
1473
+ `,
1474
+ ai: `// AI skill - MCP server / Claude tools
1475
+ export interface Tool {
1476
+ name: string;
1477
+ description: string;
1478
+ inputSchema: Record<string, unknown>;
1479
+ }
1480
+
1481
+ export const tools: Tool[] = [
1482
+ {
1483
+ name: '${manifest.name.replace(/-/g, "_")}',
1484
+ description: '${manifest.description}',
1485
+ inputSchema: {
1486
+ type: 'object',
1487
+ properties: {
1488
+ // Add input properties
1489
+ },
1490
+ },
1491
+ },
1492
+ ];
1493
+
1494
+ export async function handleTool(name: string, input: Record<string, unknown>) {
1495
+ // Handle tool invocations
1496
+ }
1497
+ `,
1498
+ workflow: `// Workflow skill - automation recipes
1499
+ export interface WorkflowTrigger {
1500
+ event: string;
1501
+ condition?: string;
1502
+ }
1503
+
1504
+ export interface WorkflowStep {
1505
+ id: string;
1506
+ run?: string;
1507
+ action?: string;
1508
+ }
1509
+
1510
+ export const triggers: WorkflowTrigger[] = [
1511
+ { event: 'deploy.success' },
1512
+ ];
1513
+
1514
+ export const steps: WorkflowStep[] = [
1515
+ { id: 'notify', run: 'echo "Workflow executed"' },
1516
+ ];
1517
+ `
1518
+ };
1519
+ return typeExports[manifest.type];
1520
+ }
1521
+ function toPascalCase(str) {
1522
+ return str.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
1523
+ }
1524
+ var initCommand = new Command5("init").description("Initialize a new skill package").argument("<name>", "Skill name (slug format: lowercase-with-hyphens)").option("-t, --type <type>", "Skill type (template, integration, ui, ai, workflow)").option("-d, --dir <dir>", "Parent directory (default: current directory)").action(async (name, options) => {
1525
+ try {
1526
+ if (!/^[a-z][a-z0-9-]{1,62}[a-z0-9]$/.test(name)) {
1527
+ console.error(chalk8.red("Error: Name must be lowercase alphanumeric with hyphens (2-64 chars, start with letter)"));
1528
+ process.exit(1);
1529
+ }
1530
+ const parentDir = options.dir ? path7.resolve(options.dir) : process.cwd();
1531
+ const skillDir = path7.join(parentDir, name);
1532
+ if (fs7.existsSync(skillDir)) {
1533
+ console.error(chalk8.red(`Error: Directory "${name}" already exists`));
1534
+ process.exit(1);
1535
+ }
1536
+ console.log(chalk8.cyan(`
1537
+ \u{1F4E6} Initializing skill: ${name}
1538
+ `));
1539
+ let type;
1540
+ if (options.type && SKILL_TYPES.includes(options.type)) {
1541
+ type = options.type;
1542
+ } else {
1543
+ const result = await p11.select({
1544
+ message: "What type of skill are you creating?",
1545
+ options: [
1546
+ { value: "integration", label: "Integration", hint: "Connect to external services (Stripe, SendGrid, etc.)" },
1547
+ { value: "template", label: "Template", hint: "Full project scaffolding" },
1548
+ { value: "ui", label: "UI Pack", hint: "Reusable React components" },
1549
+ { value: "ai", label: "AI Skill", hint: "MCP server / Claude tools" },
1550
+ { value: "workflow", label: "Workflow", hint: "Automation recipes" }
1551
+ ]
1552
+ });
1553
+ if (p11.isCancel(result)) {
1554
+ p11.cancel("Operation cancelled");
1555
+ process.exit(0);
1556
+ }
1557
+ type = result;
1558
+ }
1559
+ const description = await p11.text({
1560
+ message: "Short description:",
1561
+ placeholder: `A ${type} skill for...`,
1562
+ validate: (v) => v.length < 10 ? "Description must be at least 10 characters" : void 0
1563
+ });
1564
+ if (p11.isCancel(description)) {
1565
+ p11.cancel("Operation cancelled");
1566
+ process.exit(0);
1567
+ }
1568
+ const manifest = {
1569
+ name,
1570
+ version: "1.0.0",
1571
+ description,
1572
+ type,
1573
+ author: {
1574
+ name: "Your Name",
1575
+ email: "your@email.com"
1576
+ },
1577
+ license: "MIT",
1578
+ requires: {
1579
+ env: []
1580
+ },
1581
+ files: {
1582
+ lib: "src/index.ts"
1583
+ },
1584
+ install: {
1585
+ dependencies: {},
1586
+ envExample: {}
1587
+ },
1588
+ keywords: [type]
1589
+ };
1590
+ fs7.mkdirSync(skillDir, { recursive: true });
1591
+ fs7.mkdirSync(path7.join(skillDir, "src"), { recursive: true });
1592
+ fs7.writeFileSync(
1593
+ path7.join(skillDir, "githat-skill.json"),
1594
+ JSON.stringify(manifest, null, 2)
1595
+ );
1596
+ fs7.writeFileSync(
1597
+ path7.join(skillDir, "README.md"),
1598
+ generateReadme(manifest)
1599
+ );
1600
+ fs7.writeFileSync(
1601
+ path7.join(skillDir, "src", "index.ts"),
1602
+ generateIndexFile(manifest)
1603
+ );
1604
+ fs7.writeFileSync(
1605
+ path7.join(skillDir, ".gitignore"),
1606
+ `node_modules/
1607
+ dist/
1608
+ .env
1609
+ .env.local
1610
+ *.log
1611
+ `
1612
+ );
1613
+ console.log(chalk8.green(`
1614
+ \u2705 Created skill at ${skillDir}
1615
+ `));
1616
+ console.log(chalk8.dim("Files created:"));
1617
+ console.log(chalk8.dim(` githat-skill.json - Skill manifest`));
1618
+ console.log(chalk8.dim(` README.md - Documentation`));
1619
+ console.log(chalk8.dim(` src/index.ts - Main entry point`));
1620
+ console.log(chalk8.dim(` .gitignore - Git ignore rules`));
1621
+ console.log(chalk8.dim("\nNext steps:"));
1622
+ console.log(chalk8.dim(` 1. cd ${name}`));
1623
+ console.log(chalk8.dim(` 2. Edit githat-skill.json with your details`));
1624
+ console.log(chalk8.dim(` 3. Implement your skill in src/index.ts`));
1625
+ console.log(chalk8.dim(` 4. Publish: githat skills publish .`));
1626
+ } catch (err) {
1627
+ console.error(chalk8.red(`Error: ${err.message}`));
1628
+ process.exit(1);
1629
+ }
1630
+ });
1631
+
1632
+ // src/commands/skills/index.ts
1633
+ var skillsCommand = new Command6("skills").description("Manage GitHat skills marketplace").addCommand(searchCommand).addCommand(listCommand).addCommand(installCommand).addCommand(installedCommand).addCommand(initCommand);
1634
+ skillsCommand.action(() => {
1635
+ console.log(chalk9.cyan("\n\u{1F4E6} GitHat Skills Marketplace\n"));
1636
+ console.log("Commands:");
1637
+ console.log(" search <query> Search skills by keyword");
1638
+ console.log(" list List skills (filterable by type)");
1639
+ console.log(" install <slug> Install a skill to your project");
1640
+ console.log(" installed List installed skills");
1641
+ console.log(" init <name> Initialize a new skill package");
1642
+ console.log("\nExamples:");
1643
+ console.log(" githat skills search stripe");
1644
+ console.log(" githat skills list --type=integration");
1645
+ console.log(" githat skills install stripe-billing");
1646
+ console.log(" githat skills init my-skill --type=integration");
1647
+ console.log("");
1648
+ });
634
1649
 
635
1650
  // src/cli.ts
636
- var program = new Command();
637
- 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) => {
1651
+ var program = new Command7();
1652
+ program.name("githat").description("GitHat CLI - Scaffold apps and manage skills").version(VERSION);
1653
+ program.command("create [project-name]", { isDefault: true }).description("Scaffold a new GitHat app").option("--key <key>", "CI only: bake key into .env.local. Default flow is paste into .env.local after scaffold.").option("--ts", "Use TypeScript (default)").option("--js", "Use JavaScript").option("--plain", "Smallest scaffold: auth + a homepage. No dashboard.").option("--saas", "B2B starter: orgs, teams, RBAC, subscription billing.").option("--marketplace", "Multi-vendor commerce: anonymous-first browsing, Sebastn Connect.").option("--agent", "Web4 wallet-bound autonomous agent + MCP server registration.").option("--content", "Paywalled posts, newsletter, one-time purchases via Sebastn.").option("--dashboard", "Admin UI over your existing database, auth-gated.").option("--portfolio", "Personal portfolio: public projects, auth-gated editor.").option("--classroom", "Live student presentations with real-time audience feedback.").option("--fullstack", "Create fullstack project (Turborepo)").option("--backend <framework>", "Backend framework (hono, express, fastify)").option("-y, --yes", "Skip prompts and use defaults").action(async (projectName, opts) => {
638
1654
  try {
639
1655
  displayBanner();
1656
+ if (!opts.yes && !opts.key) {
1657
+ p12.note(
1658
+ [
1659
+ chalk10.bold("How the GitHat key flow works:"),
1660
+ "",
1661
+ ` ${chalk10.cyan("1.")} We'll scaffold your project with a placeholder`,
1662
+ ` in ${chalk10.bold(".env.local")} (gitignored \u2014 safe to keep secrets here)`,
1663
+ "",
1664
+ ` ${chalk10.cyan("2.")} You open ${chalk10.cyan("https://githat.io/dashboard/apps")}`,
1665
+ ` and copy your publishable key`,
1666
+ "",
1667
+ ` ${chalk10.cyan("3.")} You paste it into ${chalk10.bold(".env.local")} and run`,
1668
+ ` ${chalk10.dim("npm run dev")}`,
1669
+ "",
1670
+ chalk10.dim("Why not pass the key on the command line? Shell"),
1671
+ chalk10.dim("history is forever. .env.local is more secure.")
1672
+ ].join("\n"),
1673
+ "First time with GitHat?"
1674
+ );
1675
+ }
640
1676
  const typescript = opts.js ? false : opts.ts ? true : void 0;
1677
+ if (opts.yes && !projectName) {
1678
+ p12.cancel(chalk10.red("Project name is required when using --yes flag"));
1679
+ process.exit(1);
1680
+ }
1681
+ const template = opts.marketplace ? "marketplace" : opts.classroom ? "classroom" : opts.portfolio ? "portfolio" : opts.agent ? "agent" : opts.saas ? "saas" : opts.content ? "content" : opts.dashboard ? "dashboard" : opts.plain ? "plain" : void 0;
641
1682
  const answers = await runPrompts({
642
1683
  initialName: projectName,
643
1684
  publishableKey: opts.key,
644
- typescript
1685
+ typescript,
1686
+ yes: opts.yes,
1687
+ template,
1688
+ fullstack: opts.fullstack,
1689
+ backendFramework: opts.backend
645
1690
  });
646
1691
  const context = answersToContext(answers);
647
1692
  await scaffold(context, {
648
1693
  installDeps: answers.installDeps,
649
- initGit: answers.initGit
1694
+ initGit: answers.initGit,
1695
+ skipPrompts: opts.yes
650
1696
  });
651
1697
  } catch (err) {
652
- p8.cancel(chalk3.red(err.message || "Something went wrong."));
1698
+ p12.cancel(chalk10.red(err.message || "Something went wrong."));
653
1699
  process.exit(1);
654
1700
  }
655
1701
  });
1702
+ program.addCommand(skillsCommand);
656
1703
  program.parse();