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.
- package/README.md +48 -18
- package/dist/cli.js +1161 -114
- package/package.json +34 -9
- package/templates/agent/app/(auth)/sign-in/page.tsx.hbs +9 -0
- package/templates/agent/app/(auth)/sign-up/page.tsx.hbs +9 -0
- package/templates/agent/app/admin/agent/page.tsx.hbs +127 -0
- package/templates/agent/app/globals.css.hbs +87 -0
- package/templates/agent/app/layout.tsx.hbs +41 -0
- package/templates/agent/app/page.tsx.hbs +100 -0
- package/templates/agent/next.config.ts.hbs +9 -0
- package/templates/agent/postcss.config.mjs.hbs +14 -0
- package/templates/agent/proxy.ts.hbs +10 -0
- package/templates/agent/tsconfig.json.hbs +21 -0
- package/templates/base/.env.example.hbs +2 -2
- package/templates/base/.env.local.example.hbs +20 -0
- package/templates/base/.env.local.hbs +13 -2
- package/templates/base/.github/CODEOWNERS.hbs +1 -0
- package/templates/base/.github/SECURITY.md +10 -0
- package/templates/base/.github/dependabot.yml +19 -0
- package/templates/base/.github/workflows/ci.yml.hbs +77 -0
- package/templates/base/.github/workflows/githat-policy.yml +51 -0
- package/templates/base/.gitignore.hbs +17 -2
- package/templates/base/README.md.hbs +31 -52
- package/templates/classroom/app/(auth)/sign-in/page.tsx.hbs +9 -0
- package/templates/classroom/app/(auth)/sign-up/page.tsx.hbs +9 -0
- package/templates/classroom/app/globals.css.hbs +87 -0
- package/templates/classroom/app/layout.tsx.hbs +41 -0
- package/templates/classroom/app/page.tsx.hbs +103 -0
- package/templates/classroom/app/projects/[id]/feedback/page.tsx.hbs +159 -0
- package/templates/classroom/app/projects/[id]/present/page.tsx.hbs +113 -0
- package/templates/classroom/next.config.ts.hbs +9 -0
- package/templates/classroom/postcss.config.mjs.hbs +14 -0
- package/templates/classroom/proxy.ts.hbs +10 -0
- package/templates/classroom/tsconfig.json.hbs +21 -0
- package/templates/content/app/(auth)/sign-in/page.tsx.hbs +9 -0
- package/templates/content/app/(auth)/sign-up/page.tsx.hbs +9 -0
- package/templates/content/app/globals.css.hbs +87 -0
- package/templates/content/app/layout.tsx.hbs +41 -0
- package/templates/content/app/newsletter/page.tsx.hbs +90 -0
- package/templates/content/app/page.tsx.hbs +105 -0
- package/templates/content/app/posts/[slug]/page.tsx.hbs +119 -0
- package/templates/content/next.config.ts.hbs +9 -0
- package/templates/content/postcss.config.mjs.hbs +14 -0
- package/templates/content/proxy.ts.hbs +10 -0
- package/templates/content/tsconfig.json.hbs +21 -0
- package/templates/dashboard/app/(auth)/sign-in/page.tsx.hbs +9 -0
- package/templates/dashboard/app/(auth)/sign-up/page.tsx.hbs +9 -0
- package/templates/dashboard/app/admin/data/[entity]/page.tsx.hbs +68 -0
- package/templates/dashboard/app/admin/page.tsx.hbs +59 -0
- package/templates/dashboard/app/globals.css.hbs +87 -0
- package/templates/dashboard/app/layout.tsx.hbs +41 -0
- package/templates/dashboard/app/page.tsx.hbs +57 -0
- package/templates/dashboard/next.config.ts.hbs +9 -0
- package/templates/dashboard/postcss.config.mjs.hbs +14 -0
- package/templates/dashboard/proxy.ts.hbs +10 -0
- package/templates/dashboard/src/lib/db.ts.hbs +39 -0
- package/templates/dashboard/tsconfig.json.hbs +21 -0
- package/templates/fullstack/apps-api-express/.env.example.hbs +6 -0
- package/templates/fullstack/apps-api-express/.env.local.hbs +6 -0
- package/templates/fullstack/apps-api-express/package.json.hbs +24 -0
- package/templates/fullstack/apps-api-express/src/index.ts.hbs +41 -0
- package/templates/fullstack/apps-api-express/src/routes/health.ts.hbs +11 -0
- package/templates/fullstack/apps-api-express/src/routes/users.ts.hbs +43 -0
- package/templates/fullstack/apps-api-express/tsconfig.json.hbs +16 -0
- package/templates/fullstack/apps-api-fastify/.env.example.hbs +6 -0
- package/templates/fullstack/apps-api-fastify/.env.local.hbs +6 -0
- package/templates/fullstack/apps-api-fastify/package.json.hbs +22 -0
- package/templates/fullstack/apps-api-fastify/src/index.ts.hbs +28 -0
- package/templates/fullstack/apps-api-fastify/src/routes/health.ts.hbs +11 -0
- package/templates/fullstack/apps-api-fastify/src/routes/users.ts.hbs +43 -0
- package/templates/fullstack/apps-api-fastify/tsconfig.json.hbs +16 -0
- package/templates/fullstack/apps-api-hono/.env.example.hbs +6 -0
- package/templates/fullstack/apps-api-hono/.env.local.hbs +6 -0
- package/templates/fullstack/apps-api-hono/package.json.hbs +22 -0
- package/templates/fullstack/apps-api-hono/src/index.ts.hbs +35 -0
- package/templates/fullstack/apps-api-hono/src/routes/health.ts.hbs +11 -0
- package/templates/fullstack/apps-api-hono/src/routes/users.ts.hbs +43 -0
- package/templates/fullstack/apps-api-hono/tsconfig.json.hbs +16 -0
- package/templates/fullstack/apps-web-nextjs/.env.example.hbs +5 -0
- package/templates/fullstack/apps-web-nextjs/.env.local.hbs +5 -0
- package/templates/fullstack/apps-web-nextjs/app/(auth)/forgot-password/page.tsx.hbs +11 -0
- package/templates/fullstack/apps-web-nextjs/app/(auth)/reset-password/page.tsx.hbs +39 -0
- package/templates/fullstack/apps-web-nextjs/app/(auth)/sign-in/page.tsx.hbs +9 -0
- package/templates/fullstack/apps-web-nextjs/app/(auth)/sign-up/page.tsx.hbs +9 -0
- package/templates/fullstack/apps-web-nextjs/app/(auth)/verify-email/page.tsx.hbs +11 -0
- package/templates/fullstack/apps-web-nextjs/app/dashboard/layout.tsx.hbs +15 -0
- package/templates/fullstack/apps-web-nextjs/app/dashboard/page.tsx.hbs +27 -0
- package/templates/fullstack/apps-web-nextjs/app/globals.css.hbs +21 -0
- package/templates/fullstack/apps-web-nextjs/app/layout.tsx.hbs +30 -0
- package/templates/fullstack/apps-web-nextjs/app/page.tsx.hbs +17 -0
- package/templates/fullstack/apps-web-nextjs/next.config.ts.hbs +17 -0
- package/templates/fullstack/apps-web-nextjs/package.json.hbs +34 -0
- package/templates/fullstack/apps-web-nextjs/postcss.config.mjs.hbs +9 -0
- package/templates/fullstack/apps-web-nextjs/tsconfig.json.hbs +21 -0
- package/templates/fullstack/root/.gitignore.hbs +42 -0
- package/templates/fullstack/root/githat.yaml.hbs +17 -0
- package/templates/fullstack/root/package.json.hbs +15 -0
- package/templates/fullstack/root/turbo.json.hbs +20 -0
- package/templates/marketplace/CULTURE.md +74 -0
- package/templates/marketplace/app/(auth)/sign-in/page.tsx.hbs +9 -0
- package/templates/marketplace/app/(auth)/sign-up/page.tsx.hbs +9 -0
- package/templates/marketplace/app/(shop)/[slug]/p/[productId]/page.tsx.hbs +99 -0
- package/templates/marketplace/app/(shop)/[slug]/page.tsx.hbs +90 -0
- package/templates/marketplace/app/admin/page.tsx.hbs +95 -0
- package/templates/marketplace/app/cart/page.tsx.hbs +157 -0
- package/templates/marketplace/app/globals.css.hbs +87 -0
- package/templates/marketplace/app/layout.tsx.hbs +77 -0
- package/templates/marketplace/app/page.tsx.hbs +178 -0
- package/templates/marketplace/app/sell/page.tsx.hbs +78 -0
- package/templates/marketplace/next.config.ts.hbs +9 -0
- package/templates/marketplace/postcss.config.mjs.hbs +14 -0
- package/templates/marketplace/proxy.ts.hbs +10 -0
- package/templates/marketplace/src/lib/anon-session.ts.hbs +117 -0
- package/templates/marketplace/src/lib/categories.ts.hbs +35 -0
- package/templates/marketplace/tsconfig.json.hbs +21 -0
- package/templates/nextjs/.github/workflows/deploy.yml.hbs +107 -0
- package/templates/nextjs/app/(auth)/reset-password/page.tsx.hbs +106 -0
- package/templates/nextjs/app/globals.css.hbs +4 -3
- package/templates/nextjs/app/layout.tsx.hbs +5 -1
- package/templates/nextjs/app/page.tsx.hbs +3 -6
- package/templates/nextjs/next.config.ts.hbs +7 -3
- package/templates/nextjs/proxy.ts.hbs +1 -1
- package/templates/plain/app/(auth)/sign-in/page.tsx.hbs +9 -0
- package/templates/plain/app/(auth)/sign-up/page.tsx.hbs +9 -0
- package/templates/plain/app/globals.css.hbs +87 -0
- package/templates/plain/app/layout.tsx.hbs +41 -0
- package/templates/plain/app/page.tsx.hbs +123 -0
- package/templates/plain/next.config.ts.hbs +9 -0
- package/templates/plain/postcss.config.mjs.hbs +14 -0
- package/templates/plain/proxy.ts.hbs +10 -0
- package/templates/plain/tsconfig.json.hbs +21 -0
- package/templates/portfolio/app/(auth)/sign-in/page.tsx.hbs +9 -0
- package/templates/portfolio/app/(auth)/sign-up/page.tsx.hbs +9 -0
- package/templates/portfolio/app/globals.css.hbs +87 -0
- package/templates/portfolio/app/layout.tsx.hbs +41 -0
- package/templates/portfolio/app/page.tsx.hbs +86 -0
- package/templates/portfolio/next.config.ts.hbs +9 -0
- package/templates/portfolio/postcss.config.mjs.hbs +14 -0
- package/templates/portfolio/proxy.ts.hbs +10 -0
- package/templates/portfolio/tsconfig.json.hbs +21 -0
- package/templates/react-vite/src/App.tsx.hbs +11 -9
- package/templates/react-vite/src/index.css.hbs +4 -3
- package/templates/react-vite/src/pages/Home.tsx.hbs +3 -6
- package/templates/saas/app/(auth)/sign-in/page.tsx.hbs +9 -0
- package/templates/saas/app/(auth)/sign-up/page.tsx.hbs +9 -0
- package/templates/saas/app/admin/billing/page.tsx.hbs +145 -0
- package/templates/saas/app/admin/page.tsx.hbs +106 -0
- package/templates/saas/app/admin/team/page.tsx.hbs +134 -0
- package/templates/saas/app/globals.css.hbs +87 -0
- package/templates/saas/app/layout.tsx.hbs +41 -0
- package/templates/saas/app/page.tsx.hbs +108 -0
- package/templates/saas/app/pricing/page.tsx.hbs +131 -0
- package/templates/saas/next.config.ts.hbs +9 -0
- package/templates/saas/postcss.config.mjs.hbs +14 -0
- package/templates/saas/proxy.ts.hbs +10 -0
- 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
|
|
6
|
-
import
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
|
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/
|
|
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
|
|
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: () =>
|
|
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) :
|
|
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
|
-
|
|
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
|
|
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
|
|
493
|
+
const connectChoice = await p5.select({
|
|
271
494
|
message: "Connect to GitHat",
|
|
272
495
|
options: [
|
|
273
|
-
{ value: "
|
|
274
|
-
{ value: "
|
|
275
|
-
{ value: "
|
|
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 (
|
|
279
|
-
|
|
501
|
+
if (p5.isCancel(connectChoice)) {
|
|
502
|
+
p5.cancel("Setup cancelled.");
|
|
280
503
|
process.exit(0);
|
|
281
504
|
}
|
|
282
505
|
if (connectChoice === "browser") {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
|
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 (
|
|
303
|
-
|
|
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
|
|
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"
|
|
534
|
+
initialValues: ["forgot-password"],
|
|
322
535
|
required: false
|
|
323
536
|
});
|
|
324
|
-
if (
|
|
325
|
-
|
|
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
|
|
549
|
+
import * as p6 from "@clack/prompts";
|
|
337
550
|
async function promptFeatures() {
|
|
338
|
-
const answers = await
|
|
551
|
+
const answers = await p6.group(
|
|
339
552
|
{
|
|
340
|
-
databaseChoice: () =>
|
|
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: () =>
|
|
351
|
-
includeDashboard: () =>
|
|
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
|
-
|
|
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
|
|
582
|
+
import * as p7 from "@clack/prompts";
|
|
370
583
|
async function promptFinalize() {
|
|
371
|
-
const installDeps = await
|
|
584
|
+
const installDeps = await p7.confirm({
|
|
372
585
|
message: "Install dependencies?",
|
|
373
586
|
initialValue: true
|
|
374
587
|
});
|
|
375
|
-
if (
|
|
376
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
427
|
-
import
|
|
684
|
+
import fs4 from "fs-extra";
|
|
685
|
+
import path4 from "path";
|
|
428
686
|
import { execSync as execSync3 } from "child_process";
|
|
429
|
-
import * as
|
|
430
|
-
import
|
|
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
|
|
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.
|
|
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(
|
|
547
|
-
return ora({ text:
|
|
842
|
+
function createSpinner(text4) {
|
|
843
|
+
return ora({ text: text4, color: "magenta" });
|
|
548
844
|
}
|
|
549
|
-
async function withSpinner(
|
|
550
|
-
const
|
|
551
|
-
|
|
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
|
-
|
|
850
|
+
spinner2.succeed(successText || text4);
|
|
555
851
|
return result;
|
|
556
852
|
} catch (err) {
|
|
557
|
-
|
|
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 =
|
|
581
|
-
if (
|
|
582
|
-
|
|
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
|
-
|
|
965
|
+
fs4.ensureDirSync(root);
|
|
587
966
|
const templatesRoot = getTemplatesRoot();
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
renderTemplateDirectory(
|
|
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
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
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
|
-
|
|
1021
|
+
p10.log.warn(`Install timed out. Run ${chalk3.cyan(installCmd)} manually.`);
|
|
623
1022
|
} else {
|
|
624
|
-
|
|
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
|
-
|
|
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
|
|
637
|
-
program.name("
|
|
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
|
-
|
|
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();
|