create-githat-app 1.0.0 → 1.0.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 +23 -4
- package/dist/cli.js +757 -132
- package/package.json +26 -8
- package/templates/base/README.md.hbs +94 -0
- package/templates/nextjs/app/(auth)/reset-password/page.tsx.hbs +98 -0
- package/templates/nextjs/app/page.tsx.hbs +55 -0
- package/templates/nextjs/proxy.ts.hbs +10 -0
- package/templates/react-vite/src/pages/Home.tsx.hbs +55 -0
- package/templates/nextjs/middleware.ts.hbs +0 -10
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 p9 from "@clack/prompts";
|
|
6
|
+
import chalk9 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 = "
|
|
14
|
+
var VERSION = "0.5.1";
|
|
15
15
|
var DEFAULT_API_URL = "https://api.githat.io";
|
|
16
16
|
var DASHBOARD_URL = "https://githat.io/dashboard/apps";
|
|
17
17
|
var BRAND_COLORS = ["#7c3aed", "#6366f1", "#8b5cf6"];
|
|
@@ -21,7 +21,7 @@ var DEPS = {
|
|
|
21
21
|
next: "^16.0.0",
|
|
22
22
|
react: "^19.0.0",
|
|
23
23
|
"react-dom": "^19.0.0",
|
|
24
|
-
"@githat/nextjs": "^0.
|
|
24
|
+
"@githat/nextjs": "^0.5.0"
|
|
25
25
|
},
|
|
26
26
|
devDependencies: {
|
|
27
27
|
typescript: "^5.9.0",
|
|
@@ -35,7 +35,7 @@ var DEPS = {
|
|
|
35
35
|
react: "^19.0.0",
|
|
36
36
|
"react-dom": "^19.0.0",
|
|
37
37
|
"react-router-dom": "^7.0.0",
|
|
38
|
-
"@githat/nextjs": "^0.
|
|
38
|
+
"@githat/nextjs": "^0.5.0"
|
|
39
39
|
},
|
|
40
40
|
devDependencies: {
|
|
41
41
|
vite: "^7.0.0",
|
|
@@ -77,35 +77,76 @@ var DEPS = {
|
|
|
77
77
|
};
|
|
78
78
|
|
|
79
79
|
// src/utils/ascii.ts
|
|
80
|
-
var
|
|
80
|
+
var brand = gradient([...BRAND_COLORS]);
|
|
81
|
+
var violet = chalk.hex("#a78bfa");
|
|
82
|
+
var dim = chalk.dim;
|
|
83
|
+
function visibleLength(str) {
|
|
84
|
+
return str.replace(/\u001b\[.*?m/g, "").length;
|
|
85
|
+
}
|
|
86
|
+
function getBoxWidth() {
|
|
87
|
+
const termWidth = process.stdout.columns || 80;
|
|
88
|
+
return Math.min(70, Math.max(40, termWidth - 6));
|
|
89
|
+
}
|
|
90
|
+
function drawBox(lines) {
|
|
91
|
+
const W = getBoxWidth();
|
|
92
|
+
const hr = "\u2500".repeat(W);
|
|
93
|
+
console.log(dim(` \u256D${hr}\u256E`));
|
|
94
|
+
console.log(dim(` \u2502${" ".repeat(W)}\u2502`));
|
|
95
|
+
for (const line of lines) {
|
|
96
|
+
const pad = W - 2 - visibleLength(line);
|
|
97
|
+
console.log(dim(" \u2502") + ` ${line}${" ".repeat(Math.max(0, pad))}` + dim("\u2502"));
|
|
98
|
+
}
|
|
99
|
+
console.log(dim(` \u2502${" ".repeat(W)}\u2502`));
|
|
100
|
+
console.log(dim(` \u2570${hr}\u256F`));
|
|
101
|
+
}
|
|
81
102
|
function displayBanner() {
|
|
82
103
|
const ascii = figlet.textSync("GitHat", {
|
|
83
|
-
font: "
|
|
104
|
+
font: "ANSI Shadow",
|
|
84
105
|
horizontalLayout: "default"
|
|
85
106
|
});
|
|
86
107
|
console.log("");
|
|
87
|
-
console.log(
|
|
88
|
-
|
|
89
|
-
|
|
108
|
+
console.log(brand(ascii));
|
|
109
|
+
drawBox([
|
|
110
|
+
`${violet("\u2726")} Create a new GitHat app`,
|
|
111
|
+
"",
|
|
112
|
+
dim("The developer platform"),
|
|
113
|
+
dim("for humans, agents & MCP servers"),
|
|
114
|
+
"",
|
|
115
|
+
dim(`v${VERSION} \xB7 githat.io`)
|
|
116
|
+
]);
|
|
90
117
|
console.log("");
|
|
91
118
|
}
|
|
92
|
-
function
|
|
93
|
-
const
|
|
94
|
-
const
|
|
95
|
-
console.log("");
|
|
96
|
-
console.log(githatGradient(" \u2726 Your GitHat app is ready!"));
|
|
119
|
+
function sectionHeader(title) {
|
|
120
|
+
const W = getBoxWidth();
|
|
121
|
+
const lineLen = Math.max(1, W - 6 - title.length);
|
|
97
122
|
console.log("");
|
|
98
|
-
console.log(` ${
|
|
99
|
-
console.log(` ${chalk.cyan(devCmd)}`);
|
|
123
|
+
console.log(dim(` \u2500\u2500\u2500 ${title} ${"\u2500".repeat(lineLen)}`));
|
|
100
124
|
console.log("");
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
console.log(chalk.dim(" /sign-up Create account"));
|
|
106
|
-
console.log(chalk.dim(" /dashboard Protected dashboard"));
|
|
125
|
+
}
|
|
126
|
+
function displaySuccess(projectName, packageManager, framework, hasPublishableKey = true) {
|
|
127
|
+
const devCmd = packageManager === "npm" ? "npm run dev" : `${packageManager} dev`;
|
|
128
|
+
const port = framework === "react-vite" ? "5173" : "3000";
|
|
107
129
|
console.log("");
|
|
108
|
-
|
|
130
|
+
drawBox([
|
|
131
|
+
`${violet("\u2726")} Your GitHat app is ready!`,
|
|
132
|
+
"",
|
|
133
|
+
`${violet("$")} cd ${projectName}`,
|
|
134
|
+
`${violet("$")} ${devCmd}`,
|
|
135
|
+
"",
|
|
136
|
+
dim(`\u2192 http://localhost:${port}`),
|
|
137
|
+
"",
|
|
138
|
+
chalk.bold("Routes"),
|
|
139
|
+
`${violet("/sign-in")} Sign in`,
|
|
140
|
+
`${violet("/sign-up")} Create account`,
|
|
141
|
+
`${violet("/dashboard")} Protected dashboard`,
|
|
142
|
+
"",
|
|
143
|
+
...hasPublishableKey ? [] : [
|
|
144
|
+
chalk.yellow("No key configured \u2014 auth works on localhost."),
|
|
145
|
+
`For production: ${violet("githat.io/dashboard/apps")}`,
|
|
146
|
+
""
|
|
147
|
+
],
|
|
148
|
+
dim("Docs \u2192 https://githat.io/docs/sdk")
|
|
149
|
+
]);
|
|
109
150
|
console.log("");
|
|
110
151
|
}
|
|
111
152
|
|
|
@@ -132,15 +173,6 @@ function validatePublishableKey(key) {
|
|
|
132
173
|
}
|
|
133
174
|
return void 0;
|
|
134
175
|
}
|
|
135
|
-
function validateApiUrl(url) {
|
|
136
|
-
if (!url) return "API URL is required";
|
|
137
|
-
try {
|
|
138
|
-
new URL(url);
|
|
139
|
-
return void 0;
|
|
140
|
-
} catch {
|
|
141
|
-
return "Must be a valid URL";
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
176
|
|
|
145
177
|
// src/prompts/project.ts
|
|
146
178
|
async function promptProject(initialName) {
|
|
@@ -153,14 +185,9 @@ async function promptProject(initialName) {
|
|
|
153
185
|
validate: validateProjectName
|
|
154
186
|
}),
|
|
155
187
|
businessName: () => p.text({
|
|
156
|
-
message: "
|
|
188
|
+
message: "Display name",
|
|
157
189
|
placeholder: "Acme Corp",
|
|
158
190
|
validate: (v) => !v ? "Display name is required" : void 0
|
|
159
|
-
}),
|
|
160
|
-
description: () => p.text({
|
|
161
|
-
message: "One-line description",
|
|
162
|
-
placeholder: "A platform for managing identity across humans and AI",
|
|
163
|
-
initialValue: ""
|
|
164
191
|
})
|
|
165
192
|
},
|
|
166
193
|
{
|
|
@@ -173,7 +200,7 @@ async function promptProject(initialName) {
|
|
|
173
200
|
return {
|
|
174
201
|
projectName: answers.projectName,
|
|
175
202
|
businessName: answers.businessName,
|
|
176
|
-
description:
|
|
203
|
+
description: `${answers.businessName} \u2014 Built with GitHat`
|
|
177
204
|
};
|
|
178
205
|
}
|
|
179
206
|
|
|
@@ -181,7 +208,6 @@ async function promptProject(initialName) {
|
|
|
181
208
|
import * as p2 from "@clack/prompts";
|
|
182
209
|
|
|
183
210
|
// src/utils/package-manager.ts
|
|
184
|
-
import { execSync } from "child_process";
|
|
185
211
|
function detectPackageManager() {
|
|
186
212
|
const userAgent = process.env.npm_config_user_agent || "";
|
|
187
213
|
if (userAgent.startsWith("pnpm")) return "pnpm";
|
|
@@ -189,14 +215,6 @@ function detectPackageManager() {
|
|
|
189
215
|
if (userAgent.startsWith("bun")) return "bun";
|
|
190
216
|
return "npm";
|
|
191
217
|
}
|
|
192
|
-
function isAvailable(pm) {
|
|
193
|
-
try {
|
|
194
|
-
execSync(`${pm} --version`, { stdio: "ignore" });
|
|
195
|
-
return true;
|
|
196
|
-
} catch {
|
|
197
|
-
return false;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
218
|
function getInstallCommand(pm) {
|
|
201
219
|
const cmds = {
|
|
202
220
|
npm: "npm install",
|
|
@@ -209,14 +227,14 @@ function getInstallCommand(pm) {
|
|
|
209
227
|
|
|
210
228
|
// src/prompts/framework.ts
|
|
211
229
|
async function promptFramework(typescriptOverride) {
|
|
212
|
-
const
|
|
230
|
+
const packageManager = detectPackageManager();
|
|
213
231
|
const answers = await p2.group(
|
|
214
232
|
{
|
|
215
233
|
framework: () => p2.select({
|
|
216
234
|
message: "Framework",
|
|
217
235
|
options: [
|
|
218
|
-
{ value: "nextjs", label: "Next.js 16", hint: "App Router
|
|
219
|
-
{ value: "react-vite", label: "React 19 + Vite 7", hint: "SPA
|
|
236
|
+
{ value: "nextjs", label: "Next.js 16", hint: "App Router \xB7 SSR \xB7 middleware auth" },
|
|
237
|
+
{ value: "react-vite", label: "React 19 + Vite 7", hint: "SPA \xB7 client-side routing" }
|
|
220
238
|
]
|
|
221
239
|
}),
|
|
222
240
|
typescript: () => typescriptOverride !== void 0 ? Promise.resolve(typescriptOverride) : p2.select({
|
|
@@ -225,15 +243,6 @@ async function promptFramework(typescriptOverride) {
|
|
|
225
243
|
{ value: true, label: "TypeScript", hint: "recommended" },
|
|
226
244
|
{ value: false, label: "JavaScript" }
|
|
227
245
|
]
|
|
228
|
-
}),
|
|
229
|
-
packageManager: () => p2.select({
|
|
230
|
-
message: "Package manager",
|
|
231
|
-
initialValue: detected,
|
|
232
|
-
options: ["npm", "pnpm", "yarn", "bun"].filter((pm) => pm === detected || isAvailable(pm)).map((pm) => ({
|
|
233
|
-
value: pm,
|
|
234
|
-
label: pm,
|
|
235
|
-
hint: pm === detected ? "detected" : void 0
|
|
236
|
-
}))
|
|
237
246
|
})
|
|
238
247
|
},
|
|
239
248
|
{
|
|
@@ -246,51 +255,85 @@ async function promptFramework(typescriptOverride) {
|
|
|
246
255
|
return {
|
|
247
256
|
framework: answers.framework,
|
|
248
257
|
typescript: answers.typescript,
|
|
249
|
-
packageManager
|
|
258
|
+
packageManager
|
|
250
259
|
};
|
|
251
260
|
}
|
|
252
261
|
|
|
253
262
|
// src/prompts/githat.ts
|
|
263
|
+
import { execSync } from "child_process";
|
|
254
264
|
import * as p3 from "@clack/prompts";
|
|
265
|
+
function openBrowser(url) {
|
|
266
|
+
try {
|
|
267
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
268
|
+
execSync(`${cmd} "${url}"`, { stdio: "ignore" });
|
|
269
|
+
} catch {
|
|
270
|
+
}
|
|
271
|
+
}
|
|
255
272
|
async function promptGitHat(existingKey) {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
273
|
+
let publishableKey = existingKey || "";
|
|
274
|
+
if (!publishableKey) {
|
|
275
|
+
const connectChoice = await p3.select({
|
|
276
|
+
message: "Connect to GitHat",
|
|
277
|
+
options: [
|
|
278
|
+
{ value: "browser", label: "Sign in with browser", hint: "opens githat.io \u2014 recommended" },
|
|
279
|
+
{ value: "paste", label: "I have a key", hint: "paste your pk_live_... key" },
|
|
280
|
+
{ value: "skip", label: "Skip for now", hint: "add key to .env later" }
|
|
281
|
+
]
|
|
282
|
+
});
|
|
283
|
+
if (p3.isCancel(connectChoice)) {
|
|
284
|
+
p3.cancel("Setup cancelled.");
|
|
285
|
+
process.exit(0);
|
|
286
|
+
}
|
|
287
|
+
if (connectChoice === "browser") {
|
|
288
|
+
p3.log.step("Opening githat.io in your browser...");
|
|
289
|
+
openBrowser("https://githat.io/sign-up");
|
|
290
|
+
p3.log.info("Sign up (or sign in), then go to Dashboard \u2192 Apps to copy your key.");
|
|
291
|
+
const pastedKey = await p3.text({
|
|
292
|
+
message: "Paste your publishable key",
|
|
293
|
+
placeholder: "pk_live_...",
|
|
294
|
+
validate: validatePublishableKey
|
|
295
|
+
});
|
|
296
|
+
if (p3.isCancel(pastedKey)) {
|
|
297
|
+
p3.cancel("Setup cancelled.");
|
|
298
|
+
process.exit(0);
|
|
299
|
+
}
|
|
300
|
+
publishableKey = pastedKey || "";
|
|
301
|
+
} else if (connectChoice === "paste") {
|
|
302
|
+
const pastedKey = await p3.text({
|
|
303
|
+
message: "Publishable key",
|
|
260
304
|
placeholder: `pk_live_... (get one at ${DASHBOARD_URL})`,
|
|
261
|
-
initialValue: "",
|
|
262
305
|
validate: validatePublishableKey
|
|
263
|
-
})
|
|
264
|
-
|
|
265
|
-
message: "GitHat API URL",
|
|
266
|
-
placeholder: DEFAULT_API_URL,
|
|
267
|
-
initialValue: DEFAULT_API_URL,
|
|
268
|
-
validate: validateApiUrl
|
|
269
|
-
}),
|
|
270
|
-
authFeatures: () => p3.multiselect({
|
|
271
|
-
message: "Auth features",
|
|
272
|
-
options: [
|
|
273
|
-
{ value: "forgot-password", label: "Forgot password / Reset password", hint: "recommended" },
|
|
274
|
-
{ value: "email-verification", label: "Email verification", hint: "recommended" },
|
|
275
|
-
{ value: "org-management", label: "Organization management", hint: "teams, invites, roles" },
|
|
276
|
-
{ value: "mcp-servers", label: "MCP server registration", hint: "tool verification" },
|
|
277
|
-
{ value: "ai-agents", label: "AI agent wallet auth", hint: "Ethereum signatures" }
|
|
278
|
-
],
|
|
279
|
-
initialValues: ["forgot-password", "email-verification"],
|
|
280
|
-
required: false
|
|
281
|
-
})
|
|
282
|
-
},
|
|
283
|
-
{
|
|
284
|
-
onCancel: () => {
|
|
306
|
+
});
|
|
307
|
+
if (p3.isCancel(pastedKey)) {
|
|
285
308
|
p3.cancel("Setup cancelled.");
|
|
286
309
|
process.exit(0);
|
|
287
310
|
}
|
|
311
|
+
publishableKey = pastedKey || "";
|
|
312
|
+
} else if (connectChoice === "skip") {
|
|
313
|
+
p3.log.info("Auth works on localhost without a key (CORS bypass for development).");
|
|
314
|
+
p3.log.info("Sign up at githat.io \u2014 a publishable key is auto-created for you.");
|
|
288
315
|
}
|
|
289
|
-
|
|
316
|
+
}
|
|
317
|
+
const authFeatures = await p3.multiselect({
|
|
318
|
+
message: "Auth features",
|
|
319
|
+
options: [
|
|
320
|
+
{ value: "forgot-password", label: "Forgot password", hint: "reset via email" },
|
|
321
|
+
{ value: "email-verification", label: "Email verification" },
|
|
322
|
+
{ value: "org-management", label: "Organizations", hint: "teams & roles" },
|
|
323
|
+
{ value: "mcp-servers", label: "MCP servers", hint: "Model Context Protocol" },
|
|
324
|
+
{ value: "ai-agents", label: "AI agents", hint: "wallet-based identity" }
|
|
325
|
+
],
|
|
326
|
+
initialValues: ["forgot-password"],
|
|
327
|
+
required: false
|
|
328
|
+
});
|
|
329
|
+
if (p3.isCancel(authFeatures)) {
|
|
330
|
+
p3.cancel("Setup cancelled.");
|
|
331
|
+
process.exit(0);
|
|
332
|
+
}
|
|
290
333
|
return {
|
|
291
|
-
publishableKey
|
|
292
|
-
apiUrl:
|
|
293
|
-
authFeatures:
|
|
334
|
+
publishableKey,
|
|
335
|
+
apiUrl: DEFAULT_API_URL,
|
|
336
|
+
authFeatures: authFeatures || []
|
|
294
337
|
};
|
|
295
338
|
}
|
|
296
339
|
|
|
@@ -302,22 +345,15 @@ async function promptFeatures() {
|
|
|
302
345
|
databaseChoice: () => p4.select({
|
|
303
346
|
message: "Database",
|
|
304
347
|
options: [
|
|
305
|
-
{ value: "none", label: "None
|
|
348
|
+
{ value: "none", label: "None", hint: "use GitHat backend directly" },
|
|
306
349
|
{ value: "prisma-postgres", label: "Prisma + PostgreSQL" },
|
|
307
350
|
{ value: "prisma-mysql", label: "Prisma + MySQL" },
|
|
308
351
|
{ value: "drizzle-postgres", label: "Drizzle + PostgreSQL" },
|
|
309
352
|
{ value: "drizzle-sqlite", label: "Drizzle + SQLite" }
|
|
310
353
|
]
|
|
311
354
|
}),
|
|
312
|
-
useTailwind: () => p4.confirm({ message: "
|
|
313
|
-
includeDashboard: () => p4.confirm({
|
|
314
|
-
message: "Include full dashboard?",
|
|
315
|
-
initialValue: true
|
|
316
|
-
}),
|
|
317
|
-
includeGithatFolder: () => p4.confirm({
|
|
318
|
-
message: "Include githat/ platform folder?",
|
|
319
|
-
initialValue: true
|
|
320
|
-
})
|
|
355
|
+
useTailwind: () => p4.confirm({ message: "Tailwind CSS?", initialValue: true }),
|
|
356
|
+
includeDashboard: () => p4.confirm({ message: "Include dashboard?", initialValue: true })
|
|
321
357
|
},
|
|
322
358
|
{
|
|
323
359
|
onCancel: () => {
|
|
@@ -330,38 +366,39 @@ async function promptFeatures() {
|
|
|
330
366
|
databaseChoice: answers.databaseChoice,
|
|
331
367
|
useTailwind: answers.useTailwind,
|
|
332
368
|
includeDashboard: answers.includeDashboard,
|
|
333
|
-
includeGithatFolder:
|
|
369
|
+
includeGithatFolder: true
|
|
334
370
|
};
|
|
335
371
|
}
|
|
336
372
|
|
|
337
373
|
// src/prompts/finalize.ts
|
|
338
374
|
import * as p5 from "@clack/prompts";
|
|
339
375
|
async function promptFinalize() {
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
process.exit(0);
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
);
|
|
376
|
+
const installDeps = await p5.confirm({
|
|
377
|
+
message: "Install dependencies?",
|
|
378
|
+
initialValue: true
|
|
379
|
+
});
|
|
380
|
+
if (p5.isCancel(installDeps)) {
|
|
381
|
+
p5.cancel("Setup cancelled.");
|
|
382
|
+
process.exit(0);
|
|
383
|
+
}
|
|
352
384
|
return {
|
|
353
|
-
initGit:
|
|
354
|
-
installDeps
|
|
385
|
+
initGit: true,
|
|
386
|
+
installDeps
|
|
355
387
|
};
|
|
356
388
|
}
|
|
357
389
|
|
|
358
390
|
// src/prompts/index.ts
|
|
359
391
|
async function runPrompts(args) {
|
|
360
392
|
p6.intro("Let\u2019s set up your GitHat app");
|
|
393
|
+
sectionHeader("Project");
|
|
361
394
|
const project = await promptProject(args.initialName);
|
|
395
|
+
sectionHeader("Stack");
|
|
362
396
|
const framework = await promptFramework(args.typescript);
|
|
397
|
+
sectionHeader("Connect");
|
|
363
398
|
const githat = await promptGitHat(args.publishableKey);
|
|
399
|
+
sectionHeader("Features");
|
|
364
400
|
const features = await promptFeatures();
|
|
401
|
+
sectionHeader("Finish");
|
|
365
402
|
const finalize = await promptFinalize();
|
|
366
403
|
return { ...project, ...framework, ...githat, ...features, ...finalize };
|
|
367
404
|
}
|
|
@@ -404,7 +441,7 @@ import path from "path";
|
|
|
404
441
|
import { fileURLToPath } from "url";
|
|
405
442
|
var __filename2 = fileURLToPath(import.meta.url);
|
|
406
443
|
var __dirname2 = path.dirname(__filename2);
|
|
407
|
-
var TEMPLATES_ROOT = path.resolve(__dirname2, "..", "
|
|
444
|
+
var TEMPLATES_ROOT = path.resolve(__dirname2, "..", "templates");
|
|
408
445
|
Handlebars.registerHelper("ifEquals", function(a, b, options) {
|
|
409
446
|
return a === b ? options.fn(this) : options.inverse(this);
|
|
410
447
|
});
|
|
@@ -511,18 +548,18 @@ function sortKeys(obj) {
|
|
|
511
548
|
|
|
512
549
|
// src/utils/spinner.ts
|
|
513
550
|
import ora from "ora";
|
|
514
|
-
function createSpinner(
|
|
515
|
-
return ora({ text:
|
|
551
|
+
function createSpinner(text4) {
|
|
552
|
+
return ora({ text: text4, color: "magenta" });
|
|
516
553
|
}
|
|
517
|
-
async function withSpinner(
|
|
518
|
-
const spinner = createSpinner(
|
|
554
|
+
async function withSpinner(text4, fn, successText) {
|
|
555
|
+
const spinner = createSpinner(text4);
|
|
519
556
|
spinner.start();
|
|
520
557
|
try {
|
|
521
558
|
const result = await fn();
|
|
522
|
-
spinner.succeed(successText ||
|
|
559
|
+
spinner.succeed(successText || text4);
|
|
523
560
|
return result;
|
|
524
561
|
} catch (err) {
|
|
525
|
-
spinner.fail(
|
|
562
|
+
spinner.fail(text4);
|
|
526
563
|
throw err;
|
|
527
564
|
}
|
|
528
565
|
}
|
|
@@ -554,9 +591,10 @@ async function scaffold(context, options) {
|
|
|
554
591
|
fs3.ensureDirSync(root);
|
|
555
592
|
const templatesRoot = getTemplatesRoot();
|
|
556
593
|
const frameworkDir = path3.join(templatesRoot, context.framework);
|
|
557
|
-
if (fs3.existsSync(frameworkDir)) {
|
|
558
|
-
|
|
594
|
+
if (!fs3.existsSync(frameworkDir)) {
|
|
595
|
+
throw new Error(`Templates not found at ${frameworkDir}. This is a bug \u2014 please report it.`);
|
|
559
596
|
}
|
|
597
|
+
renderTemplateDirectory(frameworkDir, root, context);
|
|
560
598
|
const baseDir = path3.join(templatesRoot, "base");
|
|
561
599
|
if (fs3.existsSync(baseDir)) {
|
|
562
600
|
renderTemplateDirectory(baseDir, root, context);
|
|
@@ -596,12 +634,598 @@ async function scaffold(context, options) {
|
|
|
596
634
|
);
|
|
597
635
|
}
|
|
598
636
|
p7.outro("Setup complete!");
|
|
599
|
-
displaySuccess(context.projectName, context.packageManager, context.framework);
|
|
637
|
+
displaySuccess(context.projectName, context.packageManager, context.framework, !!context.publishableKey);
|
|
638
|
+
const starPrompt = await p7.confirm({
|
|
639
|
+
message: "Star GitHat on GitHub? (helps us grow!)",
|
|
640
|
+
initialValue: false
|
|
641
|
+
});
|
|
642
|
+
if (!p7.isCancel(starPrompt) && starPrompt) {
|
|
643
|
+
try {
|
|
644
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
645
|
+
execSync3(`${cmd} "https://github.com/GitHat-IO/githat"`, { stdio: "ignore" });
|
|
646
|
+
} catch {
|
|
647
|
+
p7.log.info("Visit https://github.com/GitHat-IO/githat to star us!");
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// src/commands/skills/index.ts
|
|
653
|
+
import { Command as Command6 } from "commander";
|
|
654
|
+
import chalk8 from "chalk";
|
|
655
|
+
|
|
656
|
+
// src/commands/skills/search.ts
|
|
657
|
+
import { Command } from "commander";
|
|
658
|
+
import chalk3 from "chalk";
|
|
659
|
+
|
|
660
|
+
// src/commands/skills/api.ts
|
|
661
|
+
async function fetchApi(endpoint, options = {}) {
|
|
662
|
+
const url = `${DEFAULT_API_URL}${endpoint}`;
|
|
663
|
+
const response = await fetch(url, {
|
|
664
|
+
...options,
|
|
665
|
+
headers: {
|
|
666
|
+
"Content-Type": "application/json",
|
|
667
|
+
...options.headers
|
|
668
|
+
}
|
|
669
|
+
});
|
|
670
|
+
if (!response.ok) {
|
|
671
|
+
const error = await response.json().catch(() => ({ error: response.statusText }));
|
|
672
|
+
throw new Error(error.error || `API error: ${response.status}`);
|
|
673
|
+
}
|
|
674
|
+
return response.json();
|
|
675
|
+
}
|
|
676
|
+
async function searchSkills(query, type) {
|
|
677
|
+
const params = new URLSearchParams();
|
|
678
|
+
if (type) params.set("type", type);
|
|
679
|
+
const url = `/skills?${params.toString()}`;
|
|
680
|
+
const result = await fetchApi(url);
|
|
681
|
+
const q = query.toLowerCase();
|
|
682
|
+
return {
|
|
683
|
+
skills: result.skills.filter(
|
|
684
|
+
(s) => s.name.toLowerCase().includes(q) || s.description.toLowerCase().includes(q) || s.keywords.some((k) => k.toLowerCase().includes(q))
|
|
685
|
+
)
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
async function listSkills(options) {
|
|
689
|
+
const params = new URLSearchParams();
|
|
690
|
+
if (options.type) params.set("type", options.type);
|
|
691
|
+
if (options.limit) params.set("limit", options.limit.toString());
|
|
692
|
+
if (options.cursor) params.set("cursor", options.cursor);
|
|
693
|
+
return fetchApi(`/skills?${params.toString()}`);
|
|
694
|
+
}
|
|
695
|
+
async function getSkill(slug) {
|
|
696
|
+
return fetchApi(`/skills/${slug}`);
|
|
697
|
+
}
|
|
698
|
+
async function getDownloadUrl(slug, version) {
|
|
699
|
+
const params = version ? `?version=${version}` : "";
|
|
700
|
+
return fetchApi(`/skills/${slug}/download${params}`);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// src/commands/skills/search.ts
|
|
704
|
+
function formatSkill(skill) {
|
|
705
|
+
const typeColors = {
|
|
706
|
+
template: chalk3.blue,
|
|
707
|
+
integration: chalk3.green,
|
|
708
|
+
ui: chalk3.magenta,
|
|
709
|
+
ai: chalk3.yellow,
|
|
710
|
+
workflow: chalk3.cyan
|
|
711
|
+
};
|
|
712
|
+
const typeColor = typeColors[skill.type] || chalk3.white;
|
|
713
|
+
return [
|
|
714
|
+
`${chalk3.bold(skill.name)} ${chalk3.dim(`@${skill.latestVersion}`)}`,
|
|
715
|
+
` ${skill.description}`,
|
|
716
|
+
` ${typeColor(skill.type)} \xB7 \u2B07 ${skill.downloads} \xB7 \u2B50 ${skill.stars} \xB7 by ${skill.authorName}`,
|
|
717
|
+
` ${chalk3.dim(`githat skills install ${skill.slug}`)}`
|
|
718
|
+
].join("\n");
|
|
719
|
+
}
|
|
720
|
+
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) => {
|
|
721
|
+
try {
|
|
722
|
+
console.log(chalk3.dim(`
|
|
723
|
+
Searching for "${query}"...
|
|
724
|
+
`));
|
|
725
|
+
const result = await searchSkills(query, options.type);
|
|
726
|
+
if (result.skills.length === 0) {
|
|
727
|
+
console.log(chalk3.yellow("No skills found matching your query."));
|
|
728
|
+
console.log(chalk3.dim("\nTry a different search term or browse all skills:"));
|
|
729
|
+
console.log(chalk3.dim(" githat skills list"));
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
console.log(chalk3.cyan(`Found ${result.skills.length} skill(s):
|
|
733
|
+
`));
|
|
734
|
+
for (const skill of result.skills) {
|
|
735
|
+
console.log(formatSkill(skill));
|
|
736
|
+
console.log("");
|
|
737
|
+
}
|
|
738
|
+
} catch (err) {
|
|
739
|
+
console.error(chalk3.red(`Error: ${err.message}`));
|
|
740
|
+
process.exit(1);
|
|
741
|
+
}
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
// src/commands/skills/list.ts
|
|
745
|
+
import { Command as Command2 } from "commander";
|
|
746
|
+
import chalk4 from "chalk";
|
|
747
|
+
function formatSkillCompact(skill) {
|
|
748
|
+
const typeColors = {
|
|
749
|
+
template: chalk4.blue,
|
|
750
|
+
integration: chalk4.green,
|
|
751
|
+
ui: chalk4.magenta,
|
|
752
|
+
ai: chalk4.yellow,
|
|
753
|
+
workflow: chalk4.cyan
|
|
754
|
+
};
|
|
755
|
+
const typeColor = typeColors[skill.type] || chalk4.white;
|
|
756
|
+
const name = chalk4.bold(skill.name.padEnd(25));
|
|
757
|
+
const type = typeColor(skill.type.padEnd(12));
|
|
758
|
+
const stats = `\u2B07 ${String(skill.downloads).padStart(5)} \u2B50 ${String(skill.stars).padStart(4)}`;
|
|
759
|
+
const desc = skill.description.length > 40 ? skill.description.substring(0, 37) + "..." : skill.description;
|
|
760
|
+
return `${name} ${type} ${stats} ${chalk4.dim(desc)}`;
|
|
761
|
+
}
|
|
762
|
+
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) => {
|
|
763
|
+
try {
|
|
764
|
+
const limit = parseInt(options.limit, 10);
|
|
765
|
+
console.log(chalk4.dim("\nFetching skills...\n"));
|
|
766
|
+
const result = await listSkills({ type: options.type, limit });
|
|
767
|
+
if (result.skills.length === 0) {
|
|
768
|
+
console.log(chalk4.yellow("No skills found."));
|
|
769
|
+
if (options.type) {
|
|
770
|
+
console.log(chalk4.dim(`
|
|
771
|
+
Try without the type filter:`));
|
|
772
|
+
console.log(chalk4.dim(" githat skills list"));
|
|
773
|
+
}
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
const header = `${"NAME".padEnd(25)} ${"TYPE".padEnd(12)} ${"DOWNLOADS".padStart(10)} DESCRIPTION`;
|
|
777
|
+
console.log(chalk4.dim(header));
|
|
778
|
+
console.log(chalk4.dim("\u2500".repeat(80)));
|
|
779
|
+
for (const skill of result.skills) {
|
|
780
|
+
console.log(formatSkillCompact(skill));
|
|
781
|
+
}
|
|
782
|
+
console.log(chalk4.dim("\u2500".repeat(80)));
|
|
783
|
+
console.log(chalk4.dim(`Showing ${result.skills.length} skill(s)`));
|
|
784
|
+
if (result.nextCursor) {
|
|
785
|
+
console.log(chalk4.dim("\nMore results available. Use --limit to see more."));
|
|
786
|
+
}
|
|
787
|
+
console.log(chalk4.dim("\nTo install: githat skills install <name>"));
|
|
788
|
+
} catch (err) {
|
|
789
|
+
console.error(chalk4.red(`Error: ${err.message}`));
|
|
790
|
+
process.exit(1);
|
|
791
|
+
}
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
// src/commands/skills/install.ts
|
|
795
|
+
import { Command as Command3 } from "commander";
|
|
796
|
+
import chalk5 from "chalk";
|
|
797
|
+
import * as fs4 from "fs";
|
|
798
|
+
import * as path4 from "path";
|
|
799
|
+
import { pipeline } from "stream/promises";
|
|
800
|
+
import { createWriteStream, mkdirSync } from "fs";
|
|
801
|
+
import { Extract } from "unzipper";
|
|
802
|
+
async function downloadAndExtract(url, destDir) {
|
|
803
|
+
const response = await fetch(url);
|
|
804
|
+
if (!response.ok) {
|
|
805
|
+
throw new Error(`Download failed: ${response.statusText}`);
|
|
806
|
+
}
|
|
807
|
+
const tempZip = path4.join(destDir, ".skill-download.zip");
|
|
808
|
+
const fileStream = createWriteStream(tempZip);
|
|
809
|
+
await pipeline(response.body, fileStream);
|
|
810
|
+
await new Promise((resolve4, reject) => {
|
|
811
|
+
fs4.createReadStream(tempZip).pipe(Extract({ path: destDir })).on("close", resolve4).on("error", reject);
|
|
812
|
+
});
|
|
813
|
+
fs4.unlinkSync(tempZip);
|
|
814
|
+
}
|
|
815
|
+
function updateGithatLock(projectDir, skill) {
|
|
816
|
+
const lockPath = path4.join(projectDir, "githat.lock");
|
|
817
|
+
let lock = {};
|
|
818
|
+
if (fs4.existsSync(lockPath)) {
|
|
819
|
+
try {
|
|
820
|
+
lock = JSON.parse(fs4.readFileSync(lockPath, "utf-8"));
|
|
821
|
+
} catch {
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
lock[skill.slug] = {
|
|
825
|
+
version: skill.version,
|
|
826
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
827
|
+
};
|
|
828
|
+
fs4.writeFileSync(lockPath, JSON.stringify(lock, null, 2));
|
|
829
|
+
}
|
|
830
|
+
function updateEnvExample(projectDir, manifest) {
|
|
831
|
+
if (!manifest.requires?.env?.length) return;
|
|
832
|
+
const envPath = path4.join(projectDir, ".env.local");
|
|
833
|
+
const envExamplePath = path4.join(projectDir, ".env.example");
|
|
834
|
+
let envContent = "";
|
|
835
|
+
if (fs4.existsSync(envPath)) {
|
|
836
|
+
envContent = fs4.readFileSync(envPath, "utf-8");
|
|
837
|
+
} else if (fs4.existsSync(envExamplePath)) {
|
|
838
|
+
envContent = fs4.readFileSync(envExamplePath, "utf-8");
|
|
839
|
+
}
|
|
840
|
+
const existingVars = new Set(
|
|
841
|
+
envContent.split("\n").filter((line) => line.includes("=")).map((line) => line.split("=")[0].trim())
|
|
842
|
+
);
|
|
843
|
+
const newVars = [];
|
|
844
|
+
for (const envVar of manifest.requires.env) {
|
|
845
|
+
if (!existingVars.has(envVar)) {
|
|
846
|
+
newVars.push(`${envVar}=`);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
if (newVars.length > 0) {
|
|
850
|
+
const addition = `
|
|
851
|
+
# Added by skill install
|
|
852
|
+
${newVars.join("\n")}
|
|
853
|
+
`;
|
|
854
|
+
if (fs4.existsSync(envPath)) {
|
|
855
|
+
fs4.appendFileSync(envPath, addition);
|
|
856
|
+
} else {
|
|
857
|
+
fs4.writeFileSync(envPath, `# Environment variables
|
|
858
|
+
${newVars.join("\n")}
|
|
859
|
+
`);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
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) => {
|
|
864
|
+
try {
|
|
865
|
+
const projectDir = options.dir ? path4.resolve(options.dir) : process.cwd();
|
|
866
|
+
const packageJsonPath = path4.join(projectDir, "package.json");
|
|
867
|
+
if (!fs4.existsSync(packageJsonPath)) {
|
|
868
|
+
console.error(chalk5.red("Error: No package.json found. Are you in a project directory?"));
|
|
869
|
+
process.exit(1);
|
|
870
|
+
}
|
|
871
|
+
console.log(chalk5.dim(`
|
|
872
|
+
Fetching skill info for "${slug}"...
|
|
873
|
+
`));
|
|
874
|
+
const skill = await getSkill(slug);
|
|
875
|
+
console.log(chalk5.cyan(`\u{1F4E6} ${skill.name}`));
|
|
876
|
+
console.log(chalk5.dim(` ${skill.description}`));
|
|
877
|
+
console.log(chalk5.dim(` Type: ${skill.type} \xB7 Author: ${skill.authorName}
|
|
878
|
+
`));
|
|
879
|
+
const download = await getDownloadUrl(slug, options.version);
|
|
880
|
+
const version = download.version.version;
|
|
881
|
+
console.log(chalk5.dim(`Downloading ${skill.name}@${version}...`));
|
|
882
|
+
const skillDir = path4.join(projectDir, "githat", "skills", slug);
|
|
883
|
+
mkdirSync(skillDir, { recursive: true });
|
|
884
|
+
await downloadAndExtract(download.downloadUrl, skillDir);
|
|
885
|
+
console.log(chalk5.green(`\u2713 Downloaded to ${path4.relative(projectDir, skillDir)}`));
|
|
886
|
+
const manifestPath = path4.join(skillDir, "githat-skill.json");
|
|
887
|
+
if (fs4.existsSync(manifestPath)) {
|
|
888
|
+
const manifest = JSON.parse(fs4.readFileSync(manifestPath, "utf-8"));
|
|
889
|
+
updateEnvExample(projectDir, manifest);
|
|
890
|
+
if (manifest.requires?.env?.length) {
|
|
891
|
+
console.log(chalk5.yellow(`
|
|
892
|
+
\u26A0 Required environment variables:`));
|
|
893
|
+
for (const envVar of manifest.requires.env) {
|
|
894
|
+
console.log(chalk5.dim(` ${envVar}`));
|
|
895
|
+
}
|
|
896
|
+
console.log(chalk5.dim(`
|
|
897
|
+
Add these to your .env.local file`));
|
|
898
|
+
}
|
|
899
|
+
if (manifest.install?.dependencies) {
|
|
900
|
+
const deps = Object.entries(manifest.install.dependencies).map(([name, ver]) => `${name}@${ver}`).join(" ");
|
|
901
|
+
console.log(chalk5.yellow(`
|
|
902
|
+
\u26A0 Install npm dependencies:`));
|
|
903
|
+
console.log(chalk5.dim(` npm install ${deps}`));
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
updateGithatLock(projectDir, { slug, version });
|
|
907
|
+
console.log(chalk5.green(`
|
|
908
|
+
\u2705 Successfully installed ${skill.name}@${version}
|
|
909
|
+
`));
|
|
910
|
+
console.log(chalk5.dim("Next steps:"));
|
|
911
|
+
console.log(chalk5.dim(` 1. Check githat/skills/${slug}/README.md for usage`));
|
|
912
|
+
console.log(chalk5.dim(" 2. Add required environment variables to .env.local"));
|
|
913
|
+
console.log(chalk5.dim(" 3. Import and use the skill in your code"));
|
|
914
|
+
} catch (err) {
|
|
915
|
+
console.error(chalk5.red(`Error: ${err.message}`));
|
|
916
|
+
process.exit(1);
|
|
917
|
+
}
|
|
918
|
+
});
|
|
919
|
+
|
|
920
|
+
// src/commands/skills/installed.ts
|
|
921
|
+
import { Command as Command4 } from "commander";
|
|
922
|
+
import chalk6 from "chalk";
|
|
923
|
+
import * as fs5 from "fs";
|
|
924
|
+
import * as path5 from "path";
|
|
925
|
+
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) => {
|
|
926
|
+
try {
|
|
927
|
+
const projectDir = options.dir ? path5.resolve(options.dir) : process.cwd();
|
|
928
|
+
const lockPath = path5.join(projectDir, "githat.lock");
|
|
929
|
+
if (!fs5.existsSync(lockPath)) {
|
|
930
|
+
console.log(chalk6.yellow("\nNo skills installed in this project."));
|
|
931
|
+
console.log(chalk6.dim("\nTo install a skill:"));
|
|
932
|
+
console.log(chalk6.dim(" githat skills install <slug>"));
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
let lock;
|
|
936
|
+
try {
|
|
937
|
+
lock = JSON.parse(fs5.readFileSync(lockPath, "utf-8"));
|
|
938
|
+
} catch {
|
|
939
|
+
console.error(chalk6.red("Error: Invalid githat.lock file"));
|
|
940
|
+
process.exit(1);
|
|
941
|
+
}
|
|
942
|
+
const entries = Object.entries(lock);
|
|
943
|
+
if (entries.length === 0) {
|
|
944
|
+
console.log(chalk6.yellow("\nNo skills installed in this project."));
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
947
|
+
console.log(chalk6.cyan(`
|
|
948
|
+
\u{1F4E6} Installed skills (${entries.length}):
|
|
949
|
+
`));
|
|
950
|
+
console.log(chalk6.dim(`${"SKILL".padEnd(30)} ${"VERSION".padEnd(12)} INSTALLED`));
|
|
951
|
+
console.log(chalk6.dim("\u2500".repeat(60)));
|
|
952
|
+
for (const [slug, entry] of entries) {
|
|
953
|
+
const date = new Date(entry.installedAt).toLocaleDateString();
|
|
954
|
+
console.log(`${chalk6.bold(slug.padEnd(30))} ${entry.version.padEnd(12)} ${chalk6.dim(date)}`);
|
|
955
|
+
}
|
|
956
|
+
console.log(chalk6.dim("\u2500".repeat(60)));
|
|
957
|
+
console.log(chalk6.dim("\nTo update a skill:"));
|
|
958
|
+
console.log(chalk6.dim(" githat skills install <slug> --version <new-version>"));
|
|
959
|
+
} catch (err) {
|
|
960
|
+
console.error(chalk6.red(`Error: ${err.message}`));
|
|
961
|
+
process.exit(1);
|
|
962
|
+
}
|
|
963
|
+
});
|
|
964
|
+
|
|
965
|
+
// src/commands/skills/init.ts
|
|
966
|
+
import { Command as Command5 } from "commander";
|
|
967
|
+
import chalk7 from "chalk";
|
|
968
|
+
import * as fs6 from "fs";
|
|
969
|
+
import * as path6 from "path";
|
|
970
|
+
import * as p8 from "@clack/prompts";
|
|
971
|
+
var SKILL_TYPES = ["template", "integration", "ui", "ai", "workflow"];
|
|
972
|
+
function generateReadme(manifest) {
|
|
973
|
+
return `# ${manifest.name}
|
|
974
|
+
|
|
975
|
+
${manifest.description}
|
|
976
|
+
|
|
977
|
+
## Installation
|
|
978
|
+
|
|
979
|
+
\`\`\`bash
|
|
980
|
+
githat skills install ${manifest.name}
|
|
981
|
+
\`\`\`
|
|
982
|
+
|
|
983
|
+
## Requirements
|
|
984
|
+
|
|
985
|
+
${manifest.requires.env.length > 0 ? `
|
|
986
|
+
### Environment Variables
|
|
987
|
+
|
|
988
|
+
${manifest.requires.env.map((e) => `- \`${e}\``).join("\n")}
|
|
989
|
+
` : ""}
|
|
990
|
+
${manifest.requires.tier ? `
|
|
991
|
+
### Minimum Tier
|
|
992
|
+
|
|
993
|
+
This skill requires **${manifest.requires.tier}** tier or higher.
|
|
994
|
+
` : ""}
|
|
995
|
+
|
|
996
|
+
## Usage
|
|
997
|
+
|
|
998
|
+
\`\`\`typescript
|
|
999
|
+
// Import from the installed skill
|
|
1000
|
+
import { /* exports */ } from './githat/skills/${manifest.name}';
|
|
1001
|
+
|
|
1002
|
+
// Use the skill
|
|
1003
|
+
// ...
|
|
1004
|
+
\`\`\`
|
|
1005
|
+
|
|
1006
|
+
## License
|
|
1007
|
+
|
|
1008
|
+
${manifest.license}
|
|
1009
|
+
`;
|
|
600
1010
|
}
|
|
1011
|
+
function generateIndexFile(manifest) {
|
|
1012
|
+
const typeExports = {
|
|
1013
|
+
template: `// Template skill - provides project scaffolding
|
|
1014
|
+
export const templateName = '${manifest.name}';
|
|
1015
|
+
export const templateVersion = '${manifest.version}';
|
|
1016
|
+
|
|
1017
|
+
// Add your template exports here
|
|
1018
|
+
`,
|
|
1019
|
+
integration: `// Integration skill - connects to external services
|
|
1020
|
+
// Replace with your actual integration code
|
|
1021
|
+
|
|
1022
|
+
export interface ${toPascalCase(manifest.name)}Config {
|
|
1023
|
+
apiKey: string;
|
|
1024
|
+
// Add configuration options
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
export function create${toPascalCase(manifest.name)}(config: ${toPascalCase(manifest.name)}Config) {
|
|
1028
|
+
// Initialize your integration
|
|
1029
|
+
return {
|
|
1030
|
+
// Return your integration client/functions
|
|
1031
|
+
};
|
|
1032
|
+
}
|
|
1033
|
+
`,
|
|
1034
|
+
ui: `// UI skill - provides React components
|
|
1035
|
+
import React from 'react';
|
|
1036
|
+
|
|
1037
|
+
export interface ${toPascalCase(manifest.name)}Props {
|
|
1038
|
+
// Add component props
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
export function ${toPascalCase(manifest.name)}({ ...props }: ${toPascalCase(manifest.name)}Props) {
|
|
1042
|
+
return (
|
|
1043
|
+
<div>
|
|
1044
|
+
{/* Your component */}
|
|
1045
|
+
</div>
|
|
1046
|
+
);
|
|
1047
|
+
}
|
|
1048
|
+
`,
|
|
1049
|
+
ai: `// AI skill - MCP server / Claude tools
|
|
1050
|
+
export interface Tool {
|
|
1051
|
+
name: string;
|
|
1052
|
+
description: string;
|
|
1053
|
+
inputSchema: Record<string, unknown>;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
export const tools: Tool[] = [
|
|
1057
|
+
{
|
|
1058
|
+
name: '${manifest.name.replace(/-/g, "_")}',
|
|
1059
|
+
description: '${manifest.description}',
|
|
1060
|
+
inputSchema: {
|
|
1061
|
+
type: 'object',
|
|
1062
|
+
properties: {
|
|
1063
|
+
// Add input properties
|
|
1064
|
+
},
|
|
1065
|
+
},
|
|
1066
|
+
},
|
|
1067
|
+
];
|
|
1068
|
+
|
|
1069
|
+
export async function handleTool(name: string, input: Record<string, unknown>) {
|
|
1070
|
+
// Handle tool invocations
|
|
1071
|
+
}
|
|
1072
|
+
`,
|
|
1073
|
+
workflow: `// Workflow skill - automation recipes
|
|
1074
|
+
export interface WorkflowTrigger {
|
|
1075
|
+
event: string;
|
|
1076
|
+
condition?: string;
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
export interface WorkflowStep {
|
|
1080
|
+
id: string;
|
|
1081
|
+
run?: string;
|
|
1082
|
+
action?: string;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
export const triggers: WorkflowTrigger[] = [
|
|
1086
|
+
{ event: 'deploy.success' },
|
|
1087
|
+
];
|
|
1088
|
+
|
|
1089
|
+
export const steps: WorkflowStep[] = [
|
|
1090
|
+
{ id: 'notify', run: 'echo "Workflow executed"' },
|
|
1091
|
+
];
|
|
1092
|
+
`
|
|
1093
|
+
};
|
|
1094
|
+
return typeExports[manifest.type];
|
|
1095
|
+
}
|
|
1096
|
+
function toPascalCase(str) {
|
|
1097
|
+
return str.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
|
|
1098
|
+
}
|
|
1099
|
+
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) => {
|
|
1100
|
+
try {
|
|
1101
|
+
if (!/^[a-z][a-z0-9-]{1,62}[a-z0-9]$/.test(name)) {
|
|
1102
|
+
console.error(chalk7.red("Error: Name must be lowercase alphanumeric with hyphens (2-64 chars, start with letter)"));
|
|
1103
|
+
process.exit(1);
|
|
1104
|
+
}
|
|
1105
|
+
const parentDir = options.dir ? path6.resolve(options.dir) : process.cwd();
|
|
1106
|
+
const skillDir = path6.join(parentDir, name);
|
|
1107
|
+
if (fs6.existsSync(skillDir)) {
|
|
1108
|
+
console.error(chalk7.red(`Error: Directory "${name}" already exists`));
|
|
1109
|
+
process.exit(1);
|
|
1110
|
+
}
|
|
1111
|
+
console.log(chalk7.cyan(`
|
|
1112
|
+
\u{1F4E6} Initializing skill: ${name}
|
|
1113
|
+
`));
|
|
1114
|
+
let type;
|
|
1115
|
+
if (options.type && SKILL_TYPES.includes(options.type)) {
|
|
1116
|
+
type = options.type;
|
|
1117
|
+
} else {
|
|
1118
|
+
const result = await p8.select({
|
|
1119
|
+
message: "What type of skill are you creating?",
|
|
1120
|
+
options: [
|
|
1121
|
+
{ value: "integration", label: "Integration", hint: "Connect to external services (Stripe, SendGrid, etc.)" },
|
|
1122
|
+
{ value: "template", label: "Template", hint: "Full project scaffolding" },
|
|
1123
|
+
{ value: "ui", label: "UI Pack", hint: "Reusable React components" },
|
|
1124
|
+
{ value: "ai", label: "AI Skill", hint: "MCP server / Claude tools" },
|
|
1125
|
+
{ value: "workflow", label: "Workflow", hint: "Automation recipes" }
|
|
1126
|
+
]
|
|
1127
|
+
});
|
|
1128
|
+
if (p8.isCancel(result)) {
|
|
1129
|
+
p8.cancel("Operation cancelled");
|
|
1130
|
+
process.exit(0);
|
|
1131
|
+
}
|
|
1132
|
+
type = result;
|
|
1133
|
+
}
|
|
1134
|
+
const description = await p8.text({
|
|
1135
|
+
message: "Short description:",
|
|
1136
|
+
placeholder: `A ${type} skill for...`,
|
|
1137
|
+
validate: (v) => v.length < 10 ? "Description must be at least 10 characters" : void 0
|
|
1138
|
+
});
|
|
1139
|
+
if (p8.isCancel(description)) {
|
|
1140
|
+
p8.cancel("Operation cancelled");
|
|
1141
|
+
process.exit(0);
|
|
1142
|
+
}
|
|
1143
|
+
const manifest = {
|
|
1144
|
+
name,
|
|
1145
|
+
version: "1.0.0",
|
|
1146
|
+
description,
|
|
1147
|
+
type,
|
|
1148
|
+
author: {
|
|
1149
|
+
name: "Your Name",
|
|
1150
|
+
email: "your@email.com"
|
|
1151
|
+
},
|
|
1152
|
+
license: "MIT",
|
|
1153
|
+
requires: {
|
|
1154
|
+
env: []
|
|
1155
|
+
},
|
|
1156
|
+
files: {
|
|
1157
|
+
lib: "src/index.ts"
|
|
1158
|
+
},
|
|
1159
|
+
install: {
|
|
1160
|
+
dependencies: {},
|
|
1161
|
+
envExample: {}
|
|
1162
|
+
},
|
|
1163
|
+
keywords: [type]
|
|
1164
|
+
};
|
|
1165
|
+
fs6.mkdirSync(skillDir, { recursive: true });
|
|
1166
|
+
fs6.mkdirSync(path6.join(skillDir, "src"), { recursive: true });
|
|
1167
|
+
fs6.writeFileSync(
|
|
1168
|
+
path6.join(skillDir, "githat-skill.json"),
|
|
1169
|
+
JSON.stringify(manifest, null, 2)
|
|
1170
|
+
);
|
|
1171
|
+
fs6.writeFileSync(
|
|
1172
|
+
path6.join(skillDir, "README.md"),
|
|
1173
|
+
generateReadme(manifest)
|
|
1174
|
+
);
|
|
1175
|
+
fs6.writeFileSync(
|
|
1176
|
+
path6.join(skillDir, "src", "index.ts"),
|
|
1177
|
+
generateIndexFile(manifest)
|
|
1178
|
+
);
|
|
1179
|
+
fs6.writeFileSync(
|
|
1180
|
+
path6.join(skillDir, ".gitignore"),
|
|
1181
|
+
`node_modules/
|
|
1182
|
+
dist/
|
|
1183
|
+
.env
|
|
1184
|
+
.env.local
|
|
1185
|
+
*.log
|
|
1186
|
+
`
|
|
1187
|
+
);
|
|
1188
|
+
console.log(chalk7.green(`
|
|
1189
|
+
\u2705 Created skill at ${skillDir}
|
|
1190
|
+
`));
|
|
1191
|
+
console.log(chalk7.dim("Files created:"));
|
|
1192
|
+
console.log(chalk7.dim(` githat-skill.json - Skill manifest`));
|
|
1193
|
+
console.log(chalk7.dim(` README.md - Documentation`));
|
|
1194
|
+
console.log(chalk7.dim(` src/index.ts - Main entry point`));
|
|
1195
|
+
console.log(chalk7.dim(` .gitignore - Git ignore rules`));
|
|
1196
|
+
console.log(chalk7.dim("\nNext steps:"));
|
|
1197
|
+
console.log(chalk7.dim(` 1. cd ${name}`));
|
|
1198
|
+
console.log(chalk7.dim(` 2. Edit githat-skill.json with your details`));
|
|
1199
|
+
console.log(chalk7.dim(` 3. Implement your skill in src/index.ts`));
|
|
1200
|
+
console.log(chalk7.dim(` 4. Publish: githat skills publish .`));
|
|
1201
|
+
} catch (err) {
|
|
1202
|
+
console.error(chalk7.red(`Error: ${err.message}`));
|
|
1203
|
+
process.exit(1);
|
|
1204
|
+
}
|
|
1205
|
+
});
|
|
1206
|
+
|
|
1207
|
+
// src/commands/skills/index.ts
|
|
1208
|
+
var skillsCommand = new Command6("skills").description("Manage GitHat skills marketplace").addCommand(searchCommand).addCommand(listCommand).addCommand(installCommand).addCommand(installedCommand).addCommand(initCommand);
|
|
1209
|
+
skillsCommand.action(() => {
|
|
1210
|
+
console.log(chalk8.cyan("\n\u{1F4E6} GitHat Skills Marketplace\n"));
|
|
1211
|
+
console.log("Commands:");
|
|
1212
|
+
console.log(" search <query> Search skills by keyword");
|
|
1213
|
+
console.log(" list List skills (filterable by type)");
|
|
1214
|
+
console.log(" install <slug> Install a skill to your project");
|
|
1215
|
+
console.log(" installed List installed skills");
|
|
1216
|
+
console.log(" init <name> Initialize a new skill package");
|
|
1217
|
+
console.log("\nExamples:");
|
|
1218
|
+
console.log(" githat skills search stripe");
|
|
1219
|
+
console.log(" githat skills list --type=integration");
|
|
1220
|
+
console.log(" githat skills install stripe-billing");
|
|
1221
|
+
console.log(" githat skills init my-skill --type=integration");
|
|
1222
|
+
console.log("");
|
|
1223
|
+
});
|
|
601
1224
|
|
|
602
1225
|
// src/cli.ts
|
|
603
|
-
var program = new
|
|
604
|
-
program.name("
|
|
1226
|
+
var program = new Command7();
|
|
1227
|
+
program.name("githat").description("GitHat CLI - Scaffold apps and manage skills").version(VERSION);
|
|
1228
|
+
program.command("create [project-name]", { isDefault: true }).description("Scaffold a new GitHat app").option("--key <key>", "GitHat publishable key (pk_live_...)").option("--ts", "Use TypeScript (default)").option("--js", "Use JavaScript").action(async (projectName, opts) => {
|
|
605
1229
|
try {
|
|
606
1230
|
displayBanner();
|
|
607
1231
|
const typescript = opts.js ? false : opts.ts ? true : void 0;
|
|
@@ -616,8 +1240,9 @@ program.name("create-githat-app").description("Scaffold enterprise-grade apps wi
|
|
|
616
1240
|
initGit: answers.initGit
|
|
617
1241
|
});
|
|
618
1242
|
} catch (err) {
|
|
619
|
-
|
|
1243
|
+
p9.cancel(chalk9.red(err.message || "Something went wrong."));
|
|
620
1244
|
process.exit(1);
|
|
621
1245
|
}
|
|
622
1246
|
});
|
|
1247
|
+
program.addCommand(skillsCommand);
|
|
623
1248
|
program.parse();
|