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/dist/cli.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { createRequire } from 'module'; const require = createRequire(import.meta.url);
2
2
 
3
3
  // src/cli.ts
4
- import { Command } from "commander";
5
- import * as p8 from "@clack/prompts";
6
- import chalk3 from "chalk";
4
+ import { Command as Command7 } from "commander";
5
+ import * as 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 = "1.0.0";
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.2.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.2.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 githatGradient = gradient([...BRAND_COLORS]);
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: "Slant",
104
+ font: "ANSI Shadow",
84
105
  horizontalLayout: "default"
85
106
  });
86
107
  console.log("");
87
- console.log(githatGradient(ascii));
88
- console.log(chalk.dim(" Identity for humans, agents, and MCP servers"));
89
- console.log(chalk.dim(` v${VERSION} | githat.io`));
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 displaySuccess(projectName, packageManager, framework) {
93
- const devCmd = packageManager === "npm" ? "npm run dev" : `${packageManager} dev`;
94
- const port = framework === "react-vite" ? "5173" : "3000";
95
- console.log("");
96
- console.log(githatGradient(" \u2726 Your GitHat app is ready!"));
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(` ${chalk.cyan("cd")} ${projectName}`);
99
- console.log(` ${chalk.cyan(devCmd)}`);
123
+ console.log(dim(` \u2500\u2500\u2500 ${title} ${"\u2500".repeat(lineLen)}`));
100
124
  console.log("");
101
- console.log(chalk.dim(` \u2192 http://localhost:${port}`));
102
- console.log("");
103
- console.log(chalk.dim(" Routes:"));
104
- console.log(chalk.dim(" /sign-in Sign in"));
105
- console.log(chalk.dim(" /sign-up Create account"));
106
- console.log(chalk.dim(" /dashboard Protected dashboard"));
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
- console.log(chalk.dim(" Docs: https://githat.io/docs/sdk"));
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: "Business or app display name",
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: answers.description || `${answers.businessName} \u2014 Built with GitHat identity`
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 detected = detectPackageManager();
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, SSR, middleware auth" },
219
- { value: "react-vite", label: "React 19 + Vite 7", hint: "SPA, client-side routing" }
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: answers.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
- const answers = await p3.group(
257
- {
258
- publishableKey: () => existingKey ? Promise.resolve(existingKey) : p3.text({
259
- message: "GitHat publishable key",
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
- apiUrl: () => p3.text({
265
- message: "GitHat API URL",
266
- placeholder: DEFAULT_API_URL,
267
- initialValue: DEFAULT_API_URL,
268
- validate: validateApiUrl
269
- }),
270
- authFeatures: () => p3.multiselect({
271
- message: "Auth features",
272
- options: [
273
- { value: "forgot-password", label: "Forgot password / Reset password", hint: "recommended" },
274
- { value: "email-verification", label: "Email verification", hint: "recommended" },
275
- { value: "org-management", label: "Organization management", hint: "teams, invites, roles" },
276
- { value: "mcp-servers", label: "MCP server registration", hint: "tool verification" },
277
- { value: "ai-agents", label: "AI agent wallet auth", hint: "Ethereum signatures" }
278
- ],
279
- initialValues: ["forgot-password", "email-verification"],
280
- required: false
281
- })
282
- },
283
- {
284
- onCancel: () => {
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: answers.publishableKey || "",
292
- apiUrl: answers.apiUrl || DEFAULT_API_URL,
293
- authFeatures: answers.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 \u2014 API only", hint: "use GitHat backend directly" },
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: "Include Tailwind CSS?", initialValue: true }),
313
- includeDashboard: () => p4.confirm({
314
- message: "Include full dashboard?",
315
- initialValue: true
316
- }),
317
- includeGithatFolder: () => p4.confirm({
318
- message: "Include githat/ platform folder?",
319
- initialValue: true
320
- })
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: answers.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 answers = await p5.group(
341
- {
342
- initGit: () => p5.confirm({ message: "Initialize git repository?", initialValue: true }),
343
- installDeps: () => p5.confirm({ message: "Install dependencies now?", initialValue: true })
344
- },
345
- {
346
- onCancel: () => {
347
- p5.cancel("Setup cancelled.");
348
- process.exit(0);
349
- }
350
- }
351
- );
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: answers.initGit,
354
- installDeps: answers.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, "..", "..", "templates");
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(text3) {
515
- return ora({ text: text3, color: "magenta" });
551
+ function createSpinner(text4) {
552
+ return ora({ text: text4, color: "magenta" });
516
553
  }
517
- async function withSpinner(text3, fn, successText) {
518
- const spinner = createSpinner(text3);
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 || text3);
559
+ spinner.succeed(successText || text4);
523
560
  return result;
524
561
  } catch (err) {
525
- spinner.fail(text3);
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
- renderTemplateDirectory(frameworkDir, root, context);
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 Command();
604
- program.name("create-githat-app").description("Scaffold enterprise-grade apps with GitHat identity").version(VERSION).argument("[project-name]", "Name of the project directory").option("--key <key>", "GitHat publishable key (pk_live_...)").option("--ts", "Use TypeScript (default)").option("--js", "Use JavaScript").action(async (projectName, opts) => {
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
- p8.cancel(chalk3.red(err.message || "Something went wrong."));
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();