create-githat-app 1.0.9 → 1.0.11

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 CHANGED
@@ -14,9 +14,9 @@
14
14
 
15
15
  ---
16
16
 
17
- Scaffold a production-ready app with a fully-managed backend — auth, teams, orgs, API keys, MCP verification, and AI agent identity. **No backend to deploy.**
17
+ Scaffold a production-ready app with a fully-managed backend — auth, teams, orgs, API keys, and AI agent identity. **No backend to deploy.**
18
18
 
19
- [GitHat](https://githat.io) is your backend. When you run `create-githat-app`, your generated project connects to GitHat's hosted platform at `api.githat.io`. User accounts, organizations, teams, API keys, MCP servers, and AI agents are all stored and managed by GitHat. You write frontend code only.
19
+ [GitHat](https://githat.io) is your backend. When you run `create-githat-app`, your generated project connects to GitHat's hosted platform at `api.githat.io`. User accounts, organizations, teams, API keys, and AI agents are all stored and managed by GitHat. You write frontend code only.
20
20
 
21
21
  ## Install & Launch
22
22
 
@@ -75,7 +75,6 @@ The CLI asks you a series of questions:
75
75
  │ ◻ Forgot password
76
76
  │ ◻ Email verification
77
77
  │ ◻ Organization management
78
- │ ◻ MCP server identity
79
78
  │ ◻ AI agent identity
80
79
 
81
80
  ◆ Database?
@@ -146,11 +145,11 @@ npx create-githat-app --version
146
145
 
147
146
  A production-ready project connected to GitHat's hosted backend:
148
147
 
149
- - **Fully-managed backend** — `api.githat.io` handles auth, orgs, teams, API keys, agents, MCP
148
+ - **Fully-managed backend** — `api.githat.io` handles auth, orgs, teams, API keys, and agents
150
149
  - **Auth pages** — sign-in, sign-up, forgot password, email verification
151
150
  - **Protected dashboard** — sidebar navigation, org switcher, user button
152
151
  - **`@githat/nextjs` SDK** — `<GitHatProvider>`, `<ProtectedRoute>`, `useAuth()`
153
- - **`githat/` platform folder** — typed API client for all 42+ backend endpoints
152
+ - **`githat/` platform folder** — typed API client for the full REST API
154
153
  - **Dark theme** — zinc/purple design system out of the box
155
154
  - **Database ready** — Prisma or Drizzle for your app's own data (optional)
156
155
  - **Tailwind CSS 4** — utility-first styling (optional)
@@ -168,7 +167,6 @@ githat/
168
167
  auth.ts # Login, register, forgot password, verify email
169
168
  orgs.ts # Create/update orgs, invite/remove members
170
169
  users.ts # List orgs, switch org
171
- mcp.ts # Register/verify MCP servers
172
170
  agents.ts # Register/verify AI agent wallets
173
171
  auth/
174
172
  guard.tsx # Role-based route protection component
@@ -179,7 +177,6 @@ githat/
179
177
  apps.tsx # App + API key management
180
178
  members.tsx # Invite members, manage roles, remove
181
179
  settings.tsx # Edit org name, brand color, save
182
- mcp-servers.tsx # Register MCP servers, verify, remove
183
180
  agents.tsx # Register AI agent wallets, verify, remove
184
181
  ```
185
182
 
@@ -193,23 +190,21 @@ You don't deploy or maintain a backend. GitHat's hosted platform handles:
193
190
  - **Organizations** — create, switch, branding, custom domains
194
191
  - **Team management** — invite members by email, assign roles (owner/admin/member), remove
195
192
  - **API key management** — publishable + secret keys per app, rotation
196
- - **MCP server registration** — domain verification via DNS TXT, OAuth2 credentials
197
193
  - **AI agent registration** — Ethereum wallet verification, challenge-response tokens
198
194
  - **Email delivery** — verification emails, invitations, password resets (via AWS SES)
199
- - **Database** — users, orgs, teams, apps, agents, MCP servers (DynamoDB, managed by GitHat)
200
- - **Public verification** — anyone can verify an agent or MCP server at `githat.io/verify/`
195
+ - **Database** — users, orgs, teams, apps, and agents (DynamoDB, managed by GitHat)
196
+ - **Public verification** — anyone can verify an agent at `githat.io/verify/`
201
197
 
202
198
  Your data lives in GitHat's infrastructure. You write frontend code, GitHat handles the rest.
203
199
 
204
- ## Three Identity Types
200
+ ## Two Identity Types
205
201
 
206
- GitHat supports three types of identity in a single platform:
202
+ GitHat supports two types of identity in a single platform:
207
203
 
208
- | Type | Auth Method | Dashboard Page |
209
- | --------------- | -------------------------- | -------------- |
210
- | **Humans** | Email + password | Members |
211
- | **MCP Servers** | Domain verification | MCP Servers |
212
- | **AI Agents** | Ethereum wallet signatures | AI Agents |
204
+ | Type | Auth Method | Dashboard Page |
205
+ | ------------- | -------------------------- | -------------- |
206
+ | **Humans** | Email + password | Members |
207
+ | **AI Agents** | Ethereum wallet signatures | AI Agents |
213
208
 
214
209
  ## Project Structure
215
210
 
@@ -281,7 +276,7 @@ VITE_GITHAT_API_URL=https://api.githat.io
281
276
 
282
277
  - [GitHat Quick Start](https://githat.io/docs/quickstart) — Get running in 5 minutes
283
278
  - [SDK Reference](https://githat.io/docs/sdk) — `@githat/nextjs` hooks, components, and server utils
284
- - [API Reference](https://githat.io/docs/api) — All 86 REST endpoints
279
+ - [API Reference](https://githat.io/docs/api) — Full REST API reference
285
280
  - [CLI Reference](https://githat.io/docs/cli) — Flags, prompts, and templates
286
281
  - [Next.js Guide](https://githat.io/docs/nextjs) — Auth in Next.js 14-16+
287
282
  - [React Guide](https://githat.io/docs/react) — Auth in any React app
package/dist/cli.js CHANGED
@@ -2,8 +2,8 @@ import { createRequire } from 'module'; const require = createRequire(import.met
2
2
 
3
3
  // src/cli.ts
4
4
  import { Command as Command7 } from "commander";
5
- import * as p11 from "@clack/prompts";
6
- import chalk9 from "chalk";
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.0.9";
14
+ var VERSION = "1.0.11";
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.4"
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.4"
38
+ "@githat/nextjs": "^0.5.0"
39
39
  },
40
40
  devDependencies: {
41
41
  vite: "^7.0.0",
@@ -657,11 +657,11 @@ function answersToContext(answers) {
657
657
  }
658
658
 
659
659
  // src/scaffold/index.ts
660
- import fs3 from "fs-extra";
661
- import path3 from "path";
660
+ import fs4 from "fs-extra";
661
+ import path4 from "path";
662
662
  import { execSync as execSync3 } from "child_process";
663
- import * as p9 from "@clack/prompts";
664
- import chalk2 from "chalk";
663
+ import * as p10 from "@clack/prompts";
664
+ import chalk3 from "chalk";
665
665
 
666
666
  // src/utils/template-engine.ts
667
667
  import Handlebars from "handlebars";
@@ -809,27 +809,109 @@ function initGit(cwd) {
809
809
  }
810
810
  }
811
811
 
812
+ // src/utils/register-app.ts
813
+ import fs3 from "fs-extra";
814
+ import path3 from "path";
815
+ import os from "os";
816
+ import * as p9 from "@clack/prompts";
817
+ import chalk2 from "chalk";
818
+ var CREDENTIALS_PATH = path3.join(os.homedir(), ".githat", "credentials.json");
819
+ function readToken() {
820
+ if (!fs3.existsSync(CREDENTIALS_PATH)) {
821
+ return null;
822
+ }
823
+ try {
824
+ const creds = fs3.readJsonSync(CREDENTIALS_PATH);
825
+ return creds.token || null;
826
+ } catch {
827
+ return null;
828
+ }
829
+ }
830
+ async function registerApp(appName, projectRoot) {
831
+ const token = readToken();
832
+ if (!token) {
833
+ p9.log.warn(
834
+ chalk2.yellow(
835
+ `GitHat credentials not found at ${CREDENTIALS_PATH}.
836
+ Run ${chalk2.cyan("githat login")} then re-run scaffolding, or paste your publishable key
837
+ from ${chalk2.cyan("https://githat.io/dashboard/apps")} into .env.local manually.`
838
+ )
839
+ );
840
+ return null;
841
+ }
842
+ p9.log.step("Registering app on GitHat...");
843
+ let registeredApp;
844
+ try {
845
+ const response = await fetch(`${DEFAULT_API_URL}/apps`, {
846
+ method: "POST",
847
+ headers: {
848
+ "Content-Type": "application/json",
849
+ Authorization: `Bearer ${token}`
850
+ },
851
+ body: JSON.stringify({
852
+ name: appName,
853
+ redirect_uris: ["http://localhost:3000/callback"]
854
+ })
855
+ });
856
+ if (!response.ok) {
857
+ const body = await response.text().catch(() => "");
858
+ throw new Error(`HTTP ${response.status}: ${body}`);
859
+ }
860
+ registeredApp = await response.json();
861
+ } catch (err) {
862
+ p9.log.warn(
863
+ chalk2.yellow(
864
+ `Could not register app on GitHat: ${err.message}
865
+ The scaffold was written successfully. Once the API is available,
866
+ register manually at ${chalk2.cyan("https://githat.io/dashboard/apps")} and paste the
867
+ publishable key into ${chalk2.cyan(".env.local")}.`
868
+ )
869
+ );
870
+ return null;
871
+ }
872
+ const publishableKey = registeredApp.publishable_key;
873
+ const envLocalPath = path3.join(projectRoot, ".env.local");
874
+ try {
875
+ let envContent = fs3.existsSync(envLocalPath) ? fs3.readFileSync(envLocalPath, "utf-8") : "";
876
+ const keyLine = `NEXT_PUBLIC_GITHAT_PUBLISHABLE_KEY=${publishableKey}`;
877
+ if (envContent.includes("NEXT_PUBLIC_GITHAT_PUBLISHABLE_KEY=")) {
878
+ envContent = envContent.replace(
879
+ /NEXT_PUBLIC_GITHAT_PUBLISHABLE_KEY=.*/,
880
+ keyLine
881
+ );
882
+ } else {
883
+ envContent += (envContent.endsWith("\n") || envContent === "" ? "" : "\n") + keyLine + "\n";
884
+ }
885
+ fs3.ensureDirSync(path3.dirname(envLocalPath));
886
+ fs3.writeFileSync(envLocalPath, envContent, "utf-8");
887
+ } catch (err) {
888
+ p9.log.warn(chalk2.yellow(`Registered app but could not write .env.local: ${err.message}`));
889
+ }
890
+ p9.log.success(chalk2.green(`Registered "${appName}" on GitHat. Publishable key written to .env.local.`));
891
+ return publishableKey;
892
+ }
893
+
812
894
  // src/scaffold/index.ts
813
895
  async function scaffold(context, options) {
814
- const root = path3.resolve(process.cwd(), context.projectName);
815
- if (fs3.existsSync(root)) {
816
- p9.cancel(`Directory "${context.projectName}" already exists.`);
896
+ const root = path4.resolve(process.cwd(), context.projectName);
897
+ if (fs4.existsSync(root)) {
898
+ p10.cancel(`Directory "${context.projectName}" already exists.`);
817
899
  process.exit(1);
818
900
  }
819
901
  const isFullstack = context.projectType === "fullstack";
820
902
  await withSpinner("Creating project structure...", async () => {
821
- fs3.ensureDirSync(root);
903
+ fs4.ensureDirSync(root);
822
904
  const templatesRoot = getTemplatesRoot();
823
905
  if (isFullstack) {
824
906
  scaffoldFullstack(templatesRoot, root, context);
825
907
  } else {
826
- const frameworkDir = path3.join(templatesRoot, context.framework);
827
- if (!fs3.existsSync(frameworkDir)) {
908
+ const frameworkDir = path4.join(templatesRoot, context.framework);
909
+ if (!fs4.existsSync(frameworkDir)) {
828
910
  throw new Error(`Templates not found at ${frameworkDir}. This is a bug \u2014 please report it.`);
829
911
  }
830
912
  renderTemplateDirectory(frameworkDir, root, context);
831
- const baseDir = path3.join(templatesRoot, "base");
832
- if (fs3.existsSync(baseDir)) {
913
+ const baseDir = path4.join(templatesRoot, "base");
914
+ if (fs4.existsSync(baseDir)) {
833
915
  renderTemplateDirectory(baseDir, root, context);
834
916
  }
835
917
  }
@@ -840,6 +922,9 @@ async function scaffold(context, options) {
840
922
  writeJson(root, "package.json", pkg);
841
923
  }, "package.json generated");
842
924
  }
925
+ if (!isFullstack && context.framework === "nextjs") {
926
+ await registerApp(context.projectName, root);
927
+ }
843
928
  if (options.initGit) {
844
929
  const gitSpinner = createSpinner("Initializing git repository...");
845
930
  gitSpinner.start();
@@ -860,69 +945,69 @@ async function scaffold(context, options) {
860
945
  } catch (err) {
861
946
  const msg = err.message || "";
862
947
  if (msg.includes("TIMEOUT")) {
863
- p9.log.warn(`Install timed out. Run ${chalk2.cyan(installCmd)} manually.`);
948
+ p10.log.warn(`Install timed out. Run ${chalk3.cyan(installCmd)} manually.`);
864
949
  } else {
865
- p9.log.warn(`Could not auto-install. Run ${chalk2.cyan(installCmd)} manually.`);
950
+ p10.log.warn(`Could not auto-install. Run ${chalk3.cyan(installCmd)} manually.`);
866
951
  }
867
952
  }
868
953
  },
869
954
  "Dependencies installed"
870
955
  );
871
956
  }
872
- p9.outro("Setup complete!");
957
+ p10.outro("Setup complete!");
873
958
  displaySuccess(context.projectName, context.packageManager, context.framework, !!context.publishableKey, isFullstack);
874
959
  if (!options.skipPrompts) {
875
- const starPrompt = await p9.confirm({
960
+ const starPrompt = await p10.confirm({
876
961
  message: "Star GitHat on GitHub? (helps us grow!)",
877
962
  initialValue: false
878
963
  });
879
- if (!p9.isCancel(starPrompt) && starPrompt) {
964
+ if (!p10.isCancel(starPrompt) && starPrompt) {
880
965
  try {
881
966
  const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
882
967
  execSync3(`${cmd} "https://github.com/GitHat-IO/githat"`, { stdio: "ignore" });
883
968
  } catch {
884
- p9.log.info("Visit https://github.com/GitHat-IO/githat to star us!");
969
+ p10.log.info("Visit https://github.com/GitHat-IO/githat to star us!");
885
970
  }
886
971
  }
887
972
  }
888
973
  }
889
974
  function scaffoldFullstack(templatesRoot, root, context) {
890
- const fullstackDir = path3.join(templatesRoot, "fullstack");
891
- const rootDir = path3.join(fullstackDir, "root");
892
- if (fs3.existsSync(rootDir)) {
975
+ const fullstackDir = path4.join(templatesRoot, "fullstack");
976
+ const rootDir = path4.join(fullstackDir, "root");
977
+ if (fs4.existsSync(rootDir)) {
893
978
  renderTemplateDirectory(rootDir, root, context);
894
979
  }
895
- const appsDir = path3.join(root, "apps");
896
- fs3.ensureDirSync(appsDir);
897
- const webDir = path3.join(appsDir, "web");
898
- fs3.ensureDirSync(webDir);
899
- const webTemplateDir = path3.join(fullstackDir, `apps-web-${context.framework}`);
900
- if (fs3.existsSync(webTemplateDir)) {
980
+ const appsDir = path4.join(root, "apps");
981
+ fs4.ensureDirSync(appsDir);
982
+ const webDir = path4.join(appsDir, "web");
983
+ fs4.ensureDirSync(webDir);
984
+ const webTemplateDir = path4.join(fullstackDir, `apps-web-${context.framework}`);
985
+ if (fs4.existsSync(webTemplateDir)) {
901
986
  renderTemplateDirectory(webTemplateDir, webDir, context);
902
987
  } else {
903
988
  throw new Error(`Web app templates not found at ${webTemplateDir}. This is a bug \u2014 please report it.`);
904
989
  }
905
- const apiDir = path3.join(appsDir, "api");
906
- fs3.ensureDirSync(apiDir);
990
+ const apiDir = path4.join(appsDir, "api");
991
+ fs4.ensureDirSync(apiDir);
907
992
  const backendFramework = context.backendFramework || "hono";
908
- const apiTemplateDir = path3.join(fullstackDir, `apps-api-${backendFramework}`);
909
- if (fs3.existsSync(apiTemplateDir)) {
993
+ const apiTemplateDir = path4.join(fullstackDir, `apps-api-${backendFramework}`);
994
+ if (fs4.existsSync(apiTemplateDir)) {
910
995
  renderTemplateDirectory(apiTemplateDir, apiDir, context);
911
996
  } else {
912
997
  throw new Error(`API templates not found at ${apiTemplateDir}. This is a bug \u2014 please report it.`);
913
998
  }
914
- const packagesDir = path3.join(root, "packages");
915
- fs3.ensureDirSync(packagesDir);
916
- fs3.writeFileSync(path3.join(packagesDir, ".gitkeep"), "");
999
+ const packagesDir = path4.join(root, "packages");
1000
+ fs4.ensureDirSync(packagesDir);
1001
+ fs4.writeFileSync(path4.join(packagesDir, ".gitkeep"), "");
917
1002
  }
918
1003
 
919
1004
  // src/commands/skills/index.ts
920
1005
  import { Command as Command6 } from "commander";
921
- import chalk8 from "chalk";
1006
+ import chalk9 from "chalk";
922
1007
 
923
1008
  // src/commands/skills/search.ts
924
1009
  import { Command } from "commander";
925
- import chalk3 from "chalk";
1010
+ import chalk4 from "chalk";
926
1011
 
927
1012
  // src/commands/skills/api.ts
928
1013
  async function fetchApi(endpoint, options = {}) {
@@ -970,99 +1055,99 @@ async function getDownloadUrl(slug, version) {
970
1055
  // src/commands/skills/search.ts
971
1056
  function formatSkill(skill) {
972
1057
  const typeColors = {
973
- template: chalk3.blue,
974
- integration: chalk3.green,
975
- ui: chalk3.magenta,
976
- ai: chalk3.yellow,
977
- workflow: chalk3.cyan
1058
+ template: chalk4.blue,
1059
+ integration: chalk4.green,
1060
+ ui: chalk4.magenta,
1061
+ ai: chalk4.yellow,
1062
+ workflow: chalk4.cyan
978
1063
  };
979
- const typeColor = typeColors[skill.type] || chalk3.white;
1064
+ const typeColor = typeColors[skill.type] || chalk4.white;
980
1065
  return [
981
- `${chalk3.bold(skill.name)} ${chalk3.dim(`@${skill.latestVersion}`)}`,
1066
+ `${chalk4.bold(skill.name)} ${chalk4.dim(`@${skill.latestVersion}`)}`,
982
1067
  ` ${skill.description}`,
983
1068
  ` ${typeColor(skill.type)} \xB7 \u2B07 ${skill.downloads} \xB7 \u2B50 ${skill.stars} \xB7 by ${skill.authorName}`,
984
- ` ${chalk3.dim(`githat skills install ${skill.slug}`)}`
1069
+ ` ${chalk4.dim(`githat skills install ${skill.slug}`)}`
985
1070
  ].join("\n");
986
1071
  }
987
1072
  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) => {
988
1073
  try {
989
- console.log(chalk3.dim(`
1074
+ console.log(chalk4.dim(`
990
1075
  Searching for "${query}"...
991
1076
  `));
992
1077
  const result = await searchSkills(query, options.type);
993
1078
  if (result.skills.length === 0) {
994
- console.log(chalk3.yellow("No skills found matching your query."));
995
- console.log(chalk3.dim("\nTry a different search term or browse all skills:"));
996
- console.log(chalk3.dim(" githat skills list"));
1079
+ console.log(chalk4.yellow("No skills found matching your query."));
1080
+ console.log(chalk4.dim("\nTry a different search term or browse all skills:"));
1081
+ console.log(chalk4.dim(" githat skills list"));
997
1082
  return;
998
1083
  }
999
- console.log(chalk3.cyan(`Found ${result.skills.length} skill(s):
1084
+ console.log(chalk4.cyan(`Found ${result.skills.length} skill(s):
1000
1085
  `));
1001
1086
  for (const skill of result.skills) {
1002
1087
  console.log(formatSkill(skill));
1003
1088
  console.log("");
1004
1089
  }
1005
1090
  } catch (err) {
1006
- console.error(chalk3.red(`Error: ${err.message}`));
1091
+ console.error(chalk4.red(`Error: ${err.message}`));
1007
1092
  process.exit(1);
1008
1093
  }
1009
1094
  });
1010
1095
 
1011
1096
  // src/commands/skills/list.ts
1012
1097
  import { Command as Command2 } from "commander";
1013
- import chalk4 from "chalk";
1098
+ import chalk5 from "chalk";
1014
1099
  function formatSkillCompact(skill) {
1015
1100
  const typeColors = {
1016
- template: chalk4.blue,
1017
- integration: chalk4.green,
1018
- ui: chalk4.magenta,
1019
- ai: chalk4.yellow,
1020
- workflow: chalk4.cyan
1101
+ template: chalk5.blue,
1102
+ integration: chalk5.green,
1103
+ ui: chalk5.magenta,
1104
+ ai: chalk5.yellow,
1105
+ workflow: chalk5.cyan
1021
1106
  };
1022
- const typeColor = typeColors[skill.type] || chalk4.white;
1023
- const name = chalk4.bold(skill.name.padEnd(25));
1107
+ const typeColor = typeColors[skill.type] || chalk5.white;
1108
+ const name = chalk5.bold(skill.name.padEnd(25));
1024
1109
  const type = typeColor(skill.type.padEnd(12));
1025
1110
  const stats = `\u2B07 ${String(skill.downloads).padStart(5)} \u2B50 ${String(skill.stars).padStart(4)}`;
1026
1111
  const desc = skill.description.length > 40 ? skill.description.substring(0, 37) + "..." : skill.description;
1027
- return `${name} ${type} ${stats} ${chalk4.dim(desc)}`;
1112
+ return `${name} ${type} ${stats} ${chalk5.dim(desc)}`;
1028
1113
  }
1029
1114
  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) => {
1030
1115
  try {
1031
1116
  const limit = parseInt(options.limit, 10);
1032
- console.log(chalk4.dim("\nFetching skills...\n"));
1117
+ console.log(chalk5.dim("\nFetching skills...\n"));
1033
1118
  const result = await listSkills({ type: options.type, limit });
1034
1119
  if (result.skills.length === 0) {
1035
- console.log(chalk4.yellow("No skills found."));
1120
+ console.log(chalk5.yellow("No skills found."));
1036
1121
  if (options.type) {
1037
- console.log(chalk4.dim(`
1122
+ console.log(chalk5.dim(`
1038
1123
  Try without the type filter:`));
1039
- console.log(chalk4.dim(" githat skills list"));
1124
+ console.log(chalk5.dim(" githat skills list"));
1040
1125
  }
1041
1126
  return;
1042
1127
  }
1043
1128
  const header = `${"NAME".padEnd(25)} ${"TYPE".padEnd(12)} ${"DOWNLOADS".padStart(10)} DESCRIPTION`;
1044
- console.log(chalk4.dim(header));
1045
- console.log(chalk4.dim("\u2500".repeat(80)));
1129
+ console.log(chalk5.dim(header));
1130
+ console.log(chalk5.dim("\u2500".repeat(80)));
1046
1131
  for (const skill of result.skills) {
1047
1132
  console.log(formatSkillCompact(skill));
1048
1133
  }
1049
- console.log(chalk4.dim("\u2500".repeat(80)));
1050
- console.log(chalk4.dim(`Showing ${result.skills.length} skill(s)`));
1134
+ console.log(chalk5.dim("\u2500".repeat(80)));
1135
+ console.log(chalk5.dim(`Showing ${result.skills.length} skill(s)`));
1051
1136
  if (result.nextCursor) {
1052
- console.log(chalk4.dim("\nMore results available. Use --limit to see more."));
1137
+ console.log(chalk5.dim("\nMore results available. Use --limit to see more."));
1053
1138
  }
1054
- console.log(chalk4.dim("\nTo install: githat skills install <name>"));
1139
+ console.log(chalk5.dim("\nTo install: githat skills install <name>"));
1055
1140
  } catch (err) {
1056
- console.error(chalk4.red(`Error: ${err.message}`));
1141
+ console.error(chalk5.red(`Error: ${err.message}`));
1057
1142
  process.exit(1);
1058
1143
  }
1059
1144
  });
1060
1145
 
1061
1146
  // src/commands/skills/install.ts
1062
1147
  import { Command as Command3 } from "commander";
1063
- import chalk5 from "chalk";
1064
- import * as fs4 from "fs";
1065
- import * as path4 from "path";
1148
+ import chalk6 from "chalk";
1149
+ import * as fs5 from "fs";
1150
+ import * as path5 from "path";
1066
1151
  import { pipeline } from "stream/promises";
1067
1152
  import { createWriteStream, mkdirSync } from "fs";
1068
1153
  import { Extract } from "unzipper";
@@ -1071,20 +1156,20 @@ async function downloadAndExtract(url, destDir) {
1071
1156
  if (!response.ok) {
1072
1157
  throw new Error(`Download failed: ${response.statusText}`);
1073
1158
  }
1074
- const tempZip = path4.join(destDir, ".skill-download.zip");
1159
+ const tempZip = path5.join(destDir, ".skill-download.zip");
1075
1160
  const fileStream = createWriteStream(tempZip);
1076
1161
  await pipeline(response.body, fileStream);
1077
1162
  await new Promise((resolve4, reject) => {
1078
- fs4.createReadStream(tempZip).pipe(Extract({ path: destDir })).on("close", resolve4).on("error", reject);
1163
+ fs5.createReadStream(tempZip).pipe(Extract({ path: destDir })).on("close", resolve4).on("error", reject);
1079
1164
  });
1080
- fs4.unlinkSync(tempZip);
1165
+ fs5.unlinkSync(tempZip);
1081
1166
  }
1082
1167
  function updateGithatLock(projectDir, skill) {
1083
- const lockPath = path4.join(projectDir, "githat.lock");
1168
+ const lockPath = path5.join(projectDir, "githat.lock");
1084
1169
  let lock = {};
1085
- if (fs4.existsSync(lockPath)) {
1170
+ if (fs5.existsSync(lockPath)) {
1086
1171
  try {
1087
- lock = JSON.parse(fs4.readFileSync(lockPath, "utf-8"));
1172
+ lock = JSON.parse(fs5.readFileSync(lockPath, "utf-8"));
1088
1173
  } catch {
1089
1174
  }
1090
1175
  }
@@ -1092,17 +1177,17 @@ function updateGithatLock(projectDir, skill) {
1092
1177
  version: skill.version,
1093
1178
  installedAt: (/* @__PURE__ */ new Date()).toISOString()
1094
1179
  };
1095
- fs4.writeFileSync(lockPath, JSON.stringify(lock, null, 2));
1180
+ fs5.writeFileSync(lockPath, JSON.stringify(lock, null, 2));
1096
1181
  }
1097
1182
  function updateEnvExample(projectDir, manifest) {
1098
1183
  if (!manifest.requires?.env?.length) return;
1099
- const envPath = path4.join(projectDir, ".env.local");
1100
- const envExamplePath = path4.join(projectDir, ".env.example");
1184
+ const envPath = path5.join(projectDir, ".env.local");
1185
+ const envExamplePath = path5.join(projectDir, ".env.example");
1101
1186
  let envContent = "";
1102
- if (fs4.existsSync(envPath)) {
1103
- envContent = fs4.readFileSync(envPath, "utf-8");
1104
- } else if (fs4.existsSync(envExamplePath)) {
1105
- envContent = fs4.readFileSync(envExamplePath, "utf-8");
1187
+ if (fs5.existsSync(envPath)) {
1188
+ envContent = fs5.readFileSync(envPath, "utf-8");
1189
+ } else if (fs5.existsSync(envExamplePath)) {
1190
+ envContent = fs5.readFileSync(envExamplePath, "utf-8");
1106
1191
  }
1107
1192
  const existingVars = new Set(
1108
1193
  envContent.split("\n").filter((line) => line.includes("=")).map((line) => line.split("=")[0].trim())
@@ -1118,10 +1203,10 @@ function updateEnvExample(projectDir, manifest) {
1118
1203
  # Added by skill install
1119
1204
  ${newVars.join("\n")}
1120
1205
  `;
1121
- if (fs4.existsSync(envPath)) {
1122
- fs4.appendFileSync(envPath, addition);
1206
+ if (fs5.existsSync(envPath)) {
1207
+ fs5.appendFileSync(envPath, addition);
1123
1208
  } else {
1124
- fs4.writeFileSync(envPath, `# Environment variables
1209
+ fs5.writeFileSync(envPath, `# Environment variables
1125
1210
  ${newVars.join("\n")}
1126
1211
  `);
1127
1212
  }
@@ -1129,112 +1214,112 @@ ${newVars.join("\n")}
1129
1214
  }
1130
1215
  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) => {
1131
1216
  try {
1132
- const projectDir = options.dir ? path4.resolve(options.dir) : process.cwd();
1133
- const packageJsonPath = path4.join(projectDir, "package.json");
1134
- if (!fs4.existsSync(packageJsonPath)) {
1135
- console.error(chalk5.red("Error: No package.json found. Are you in a project directory?"));
1217
+ const projectDir = options.dir ? path5.resolve(options.dir) : process.cwd();
1218
+ const packageJsonPath = path5.join(projectDir, "package.json");
1219
+ if (!fs5.existsSync(packageJsonPath)) {
1220
+ console.error(chalk6.red("Error: No package.json found. Are you in a project directory?"));
1136
1221
  process.exit(1);
1137
1222
  }
1138
- console.log(chalk5.dim(`
1223
+ console.log(chalk6.dim(`
1139
1224
  Fetching skill info for "${slug}"...
1140
1225
  `));
1141
1226
  const skill = await getSkill(slug);
1142
- console.log(chalk5.cyan(`\u{1F4E6} ${skill.name}`));
1143
- console.log(chalk5.dim(` ${skill.description}`));
1144
- console.log(chalk5.dim(` Type: ${skill.type} \xB7 Author: ${skill.authorName}
1227
+ console.log(chalk6.cyan(`\u{1F4E6} ${skill.name}`));
1228
+ console.log(chalk6.dim(` ${skill.description}`));
1229
+ console.log(chalk6.dim(` Type: ${skill.type} \xB7 Author: ${skill.authorName}
1145
1230
  `));
1146
1231
  const download = await getDownloadUrl(slug, options.version);
1147
1232
  const version = download.version.version;
1148
- console.log(chalk5.dim(`Downloading ${skill.name}@${version}...`));
1149
- const skillDir = path4.join(projectDir, "githat", "skills", slug);
1233
+ console.log(chalk6.dim(`Downloading ${skill.name}@${version}...`));
1234
+ const skillDir = path5.join(projectDir, "githat", "skills", slug);
1150
1235
  mkdirSync(skillDir, { recursive: true });
1151
1236
  await downloadAndExtract(download.downloadUrl, skillDir);
1152
- console.log(chalk5.green(`\u2713 Downloaded to ${path4.relative(projectDir, skillDir)}`));
1153
- const manifestPath = path4.join(skillDir, "githat-skill.json");
1154
- if (fs4.existsSync(manifestPath)) {
1155
- const manifest = JSON.parse(fs4.readFileSync(manifestPath, "utf-8"));
1237
+ console.log(chalk6.green(`\u2713 Downloaded to ${path5.relative(projectDir, skillDir)}`));
1238
+ const manifestPath = path5.join(skillDir, "githat-skill.json");
1239
+ if (fs5.existsSync(manifestPath)) {
1240
+ const manifest = JSON.parse(fs5.readFileSync(manifestPath, "utf-8"));
1156
1241
  updateEnvExample(projectDir, manifest);
1157
1242
  if (manifest.requires?.env?.length) {
1158
- console.log(chalk5.yellow(`
1243
+ console.log(chalk6.yellow(`
1159
1244
  \u26A0 Required environment variables:`));
1160
1245
  for (const envVar of manifest.requires.env) {
1161
- console.log(chalk5.dim(` ${envVar}`));
1246
+ console.log(chalk6.dim(` ${envVar}`));
1162
1247
  }
1163
- console.log(chalk5.dim(`
1248
+ console.log(chalk6.dim(`
1164
1249
  Add these to your .env.local file`));
1165
1250
  }
1166
1251
  if (manifest.install?.dependencies) {
1167
1252
  const deps = Object.entries(manifest.install.dependencies).map(([name, ver]) => `${name}@${ver}`).join(" ");
1168
- console.log(chalk5.yellow(`
1253
+ console.log(chalk6.yellow(`
1169
1254
  \u26A0 Install npm dependencies:`));
1170
- console.log(chalk5.dim(` npm install ${deps}`));
1255
+ console.log(chalk6.dim(` npm install ${deps}`));
1171
1256
  }
1172
1257
  }
1173
1258
  updateGithatLock(projectDir, { slug, version });
1174
- console.log(chalk5.green(`
1259
+ console.log(chalk6.green(`
1175
1260
  \u2705 Successfully installed ${skill.name}@${version}
1176
1261
  `));
1177
- console.log(chalk5.dim("Next steps:"));
1178
- console.log(chalk5.dim(` 1. Check githat/skills/${slug}/README.md for usage`));
1179
- console.log(chalk5.dim(" 2. Add required environment variables to .env.local"));
1180
- console.log(chalk5.dim(" 3. Import and use the skill in your code"));
1262
+ console.log(chalk6.dim("Next steps:"));
1263
+ console.log(chalk6.dim(` 1. Check githat/skills/${slug}/README.md for usage`));
1264
+ console.log(chalk6.dim(" 2. Add required environment variables to .env.local"));
1265
+ console.log(chalk6.dim(" 3. Import and use the skill in your code"));
1181
1266
  } catch (err) {
1182
- console.error(chalk5.red(`Error: ${err.message}`));
1267
+ console.error(chalk6.red(`Error: ${err.message}`));
1183
1268
  process.exit(1);
1184
1269
  }
1185
1270
  });
1186
1271
 
1187
1272
  // src/commands/skills/installed.ts
1188
1273
  import { Command as Command4 } from "commander";
1189
- import chalk6 from "chalk";
1190
- import * as fs5 from "fs";
1191
- import * as path5 from "path";
1274
+ import chalk7 from "chalk";
1275
+ import * as fs6 from "fs";
1276
+ import * as path6 from "path";
1192
1277
  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) => {
1193
1278
  try {
1194
- const projectDir = options.dir ? path5.resolve(options.dir) : process.cwd();
1195
- const lockPath = path5.join(projectDir, "githat.lock");
1196
- if (!fs5.existsSync(lockPath)) {
1197
- console.log(chalk6.yellow("\nNo skills installed in this project."));
1198
- console.log(chalk6.dim("\nTo install a skill:"));
1199
- console.log(chalk6.dim(" githat skills install <slug>"));
1279
+ const projectDir = options.dir ? path6.resolve(options.dir) : process.cwd();
1280
+ const lockPath = path6.join(projectDir, "githat.lock");
1281
+ if (!fs6.existsSync(lockPath)) {
1282
+ console.log(chalk7.yellow("\nNo skills installed in this project."));
1283
+ console.log(chalk7.dim("\nTo install a skill:"));
1284
+ console.log(chalk7.dim(" githat skills install <slug>"));
1200
1285
  return;
1201
1286
  }
1202
1287
  let lock;
1203
1288
  try {
1204
- lock = JSON.parse(fs5.readFileSync(lockPath, "utf-8"));
1289
+ lock = JSON.parse(fs6.readFileSync(lockPath, "utf-8"));
1205
1290
  } catch {
1206
- console.error(chalk6.red("Error: Invalid githat.lock file"));
1291
+ console.error(chalk7.red("Error: Invalid githat.lock file"));
1207
1292
  process.exit(1);
1208
1293
  }
1209
1294
  const entries = Object.entries(lock);
1210
1295
  if (entries.length === 0) {
1211
- console.log(chalk6.yellow("\nNo skills installed in this project."));
1296
+ console.log(chalk7.yellow("\nNo skills installed in this project."));
1212
1297
  return;
1213
1298
  }
1214
- console.log(chalk6.cyan(`
1299
+ console.log(chalk7.cyan(`
1215
1300
  \u{1F4E6} Installed skills (${entries.length}):
1216
1301
  `));
1217
- console.log(chalk6.dim(`${"SKILL".padEnd(30)} ${"VERSION".padEnd(12)} INSTALLED`));
1218
- console.log(chalk6.dim("\u2500".repeat(60)));
1302
+ console.log(chalk7.dim(`${"SKILL".padEnd(30)} ${"VERSION".padEnd(12)} INSTALLED`));
1303
+ console.log(chalk7.dim("\u2500".repeat(60)));
1219
1304
  for (const [slug, entry] of entries) {
1220
1305
  const date = new Date(entry.installedAt).toLocaleDateString();
1221
- console.log(`${chalk6.bold(slug.padEnd(30))} ${entry.version.padEnd(12)} ${chalk6.dim(date)}`);
1306
+ console.log(`${chalk7.bold(slug.padEnd(30))} ${entry.version.padEnd(12)} ${chalk7.dim(date)}`);
1222
1307
  }
1223
- console.log(chalk6.dim("\u2500".repeat(60)));
1224
- console.log(chalk6.dim("\nTo update a skill:"));
1225
- console.log(chalk6.dim(" githat skills install <slug> --version <new-version>"));
1308
+ console.log(chalk7.dim("\u2500".repeat(60)));
1309
+ console.log(chalk7.dim("\nTo update a skill:"));
1310
+ console.log(chalk7.dim(" githat skills install <slug> --version <new-version>"));
1226
1311
  } catch (err) {
1227
- console.error(chalk6.red(`Error: ${err.message}`));
1312
+ console.error(chalk7.red(`Error: ${err.message}`));
1228
1313
  process.exit(1);
1229
1314
  }
1230
1315
  });
1231
1316
 
1232
1317
  // src/commands/skills/init.ts
1233
1318
  import { Command as Command5 } from "commander";
1234
- import chalk7 from "chalk";
1235
- import * as fs6 from "fs";
1236
- import * as path6 from "path";
1237
- import * as p10 from "@clack/prompts";
1319
+ import chalk8 from "chalk";
1320
+ import * as fs7 from "fs";
1321
+ import * as path7 from "path";
1322
+ import * as p11 from "@clack/prompts";
1238
1323
  var SKILL_TYPES = ["template", "integration", "ui", "ai", "workflow"];
1239
1324
  function generateReadme(manifest) {
1240
1325
  return `# ${manifest.name}
@@ -1366,23 +1451,23 @@ function toPascalCase(str) {
1366
1451
  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) => {
1367
1452
  try {
1368
1453
  if (!/^[a-z][a-z0-9-]{1,62}[a-z0-9]$/.test(name)) {
1369
- console.error(chalk7.red("Error: Name must be lowercase alphanumeric with hyphens (2-64 chars, start with letter)"));
1454
+ console.error(chalk8.red("Error: Name must be lowercase alphanumeric with hyphens (2-64 chars, start with letter)"));
1370
1455
  process.exit(1);
1371
1456
  }
1372
- const parentDir = options.dir ? path6.resolve(options.dir) : process.cwd();
1373
- const skillDir = path6.join(parentDir, name);
1374
- if (fs6.existsSync(skillDir)) {
1375
- console.error(chalk7.red(`Error: Directory "${name}" already exists`));
1457
+ const parentDir = options.dir ? path7.resolve(options.dir) : process.cwd();
1458
+ const skillDir = path7.join(parentDir, name);
1459
+ if (fs7.existsSync(skillDir)) {
1460
+ console.error(chalk8.red(`Error: Directory "${name}" already exists`));
1376
1461
  process.exit(1);
1377
1462
  }
1378
- console.log(chalk7.cyan(`
1463
+ console.log(chalk8.cyan(`
1379
1464
  \u{1F4E6} Initializing skill: ${name}
1380
1465
  `));
1381
1466
  let type;
1382
1467
  if (options.type && SKILL_TYPES.includes(options.type)) {
1383
1468
  type = options.type;
1384
1469
  } else {
1385
- const result = await p10.select({
1470
+ const result = await p11.select({
1386
1471
  message: "What type of skill are you creating?",
1387
1472
  options: [
1388
1473
  { value: "integration", label: "Integration", hint: "Connect to external services (Stripe, SendGrid, etc.)" },
@@ -1392,19 +1477,19 @@ var initCommand = new Command5("init").description("Initialize a new skill packa
1392
1477
  { value: "workflow", label: "Workflow", hint: "Automation recipes" }
1393
1478
  ]
1394
1479
  });
1395
- if (p10.isCancel(result)) {
1396
- p10.cancel("Operation cancelled");
1480
+ if (p11.isCancel(result)) {
1481
+ p11.cancel("Operation cancelled");
1397
1482
  process.exit(0);
1398
1483
  }
1399
1484
  type = result;
1400
1485
  }
1401
- const description = await p10.text({
1486
+ const description = await p11.text({
1402
1487
  message: "Short description:",
1403
1488
  placeholder: `A ${type} skill for...`,
1404
1489
  validate: (v) => v.length < 10 ? "Description must be at least 10 characters" : void 0
1405
1490
  });
1406
- if (p10.isCancel(description)) {
1407
- p10.cancel("Operation cancelled");
1491
+ if (p11.isCancel(description)) {
1492
+ p11.cancel("Operation cancelled");
1408
1493
  process.exit(0);
1409
1494
  }
1410
1495
  const manifest = {
@@ -1429,22 +1514,22 @@ var initCommand = new Command5("init").description("Initialize a new skill packa
1429
1514
  },
1430
1515
  keywords: [type]
1431
1516
  };
1432
- fs6.mkdirSync(skillDir, { recursive: true });
1433
- fs6.mkdirSync(path6.join(skillDir, "src"), { recursive: true });
1434
- fs6.writeFileSync(
1435
- path6.join(skillDir, "githat-skill.json"),
1517
+ fs7.mkdirSync(skillDir, { recursive: true });
1518
+ fs7.mkdirSync(path7.join(skillDir, "src"), { recursive: true });
1519
+ fs7.writeFileSync(
1520
+ path7.join(skillDir, "githat-skill.json"),
1436
1521
  JSON.stringify(manifest, null, 2)
1437
1522
  );
1438
- fs6.writeFileSync(
1439
- path6.join(skillDir, "README.md"),
1523
+ fs7.writeFileSync(
1524
+ path7.join(skillDir, "README.md"),
1440
1525
  generateReadme(manifest)
1441
1526
  );
1442
- fs6.writeFileSync(
1443
- path6.join(skillDir, "src", "index.ts"),
1527
+ fs7.writeFileSync(
1528
+ path7.join(skillDir, "src", "index.ts"),
1444
1529
  generateIndexFile(manifest)
1445
1530
  );
1446
- fs6.writeFileSync(
1447
- path6.join(skillDir, ".gitignore"),
1531
+ fs7.writeFileSync(
1532
+ path7.join(skillDir, ".gitignore"),
1448
1533
  `node_modules/
1449
1534
  dist/
1450
1535
  .env
@@ -1452,21 +1537,21 @@ dist/
1452
1537
  *.log
1453
1538
  `
1454
1539
  );
1455
- console.log(chalk7.green(`
1540
+ console.log(chalk8.green(`
1456
1541
  \u2705 Created skill at ${skillDir}
1457
1542
  `));
1458
- console.log(chalk7.dim("Files created:"));
1459
- console.log(chalk7.dim(` githat-skill.json - Skill manifest`));
1460
- console.log(chalk7.dim(` README.md - Documentation`));
1461
- console.log(chalk7.dim(` src/index.ts - Main entry point`));
1462
- console.log(chalk7.dim(` .gitignore - Git ignore rules`));
1463
- console.log(chalk7.dim("\nNext steps:"));
1464
- console.log(chalk7.dim(` 1. cd ${name}`));
1465
- console.log(chalk7.dim(` 2. Edit githat-skill.json with your details`));
1466
- console.log(chalk7.dim(` 3. Implement your skill in src/index.ts`));
1467
- console.log(chalk7.dim(` 4. Publish: githat skills publish .`));
1543
+ console.log(chalk8.dim("Files created:"));
1544
+ console.log(chalk8.dim(` githat-skill.json - Skill manifest`));
1545
+ console.log(chalk8.dim(` README.md - Documentation`));
1546
+ console.log(chalk8.dim(` src/index.ts - Main entry point`));
1547
+ console.log(chalk8.dim(` .gitignore - Git ignore rules`));
1548
+ console.log(chalk8.dim("\nNext steps:"));
1549
+ console.log(chalk8.dim(` 1. cd ${name}`));
1550
+ console.log(chalk8.dim(` 2. Edit githat-skill.json with your details`));
1551
+ console.log(chalk8.dim(` 3. Implement your skill in src/index.ts`));
1552
+ console.log(chalk8.dim(` 4. Publish: githat skills publish .`));
1468
1553
  } catch (err) {
1469
- console.error(chalk7.red(`Error: ${err.message}`));
1554
+ console.error(chalk8.red(`Error: ${err.message}`));
1470
1555
  process.exit(1);
1471
1556
  }
1472
1557
  });
@@ -1474,7 +1559,7 @@ dist/
1474
1559
  // src/commands/skills/index.ts
1475
1560
  var skillsCommand = new Command6("skills").description("Manage GitHat skills marketplace").addCommand(searchCommand).addCommand(listCommand).addCommand(installCommand).addCommand(installedCommand).addCommand(initCommand);
1476
1561
  skillsCommand.action(() => {
1477
- console.log(chalk8.cyan("\n\u{1F4E6} GitHat Skills Marketplace\n"));
1562
+ console.log(chalk9.cyan("\n\u{1F4E6} GitHat Skills Marketplace\n"));
1478
1563
  console.log("Commands:");
1479
1564
  console.log(" search <query> Search skills by keyword");
1480
1565
  console.log(" list List skills (filterable by type)");
@@ -1497,7 +1582,7 @@ program.command("create [project-name]", { isDefault: true }).description("Scaff
1497
1582
  displayBanner();
1498
1583
  const typescript = opts.js ? false : opts.ts ? true : void 0;
1499
1584
  if (opts.yes && !projectName) {
1500
- p11.cancel(chalk9.red("Project name is required when using --yes flag"));
1585
+ p12.cancel(chalk10.red("Project name is required when using --yes flag"));
1501
1586
  process.exit(1);
1502
1587
  }
1503
1588
  const answers = await runPrompts({
@@ -1515,7 +1600,7 @@ program.command("create [project-name]", { isDefault: true }).description("Scaff
1515
1600
  skipPrompts: opts.yes
1516
1601
  });
1517
1602
  } catch (err) {
1518
- p11.cancel(chalk9.red(err.message || "Something went wrong."));
1603
+ p12.cancel(chalk10.red(err.message || "Something went wrong."));
1519
1604
  process.exit(1);
1520
1605
  }
1521
1606
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-githat-app",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "description": "GitHat CLI — scaffold apps and manage the skills marketplace",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,20 @@
1
+ {{#ifEquals framework "nextjs"}}
2
+ # GitHat — publishable key for this app.
3
+ # Get yours at https://githat.io/dashboard/apps
4
+ NEXT_PUBLIC_GITHAT_PUBLISHABLE_KEY=pk_live_your_key_here
5
+
6
+ # GitHat API base URL (leave as-is unless using a self-hosted deployment)
7
+ NEXT_PUBLIC_GITHAT_API_URL={{apiUrl}}
8
+ {{else}}
9
+ # GitHat — publishable key for this app.
10
+ # Get yours at https://githat.io/dashboard/apps
11
+ VITE_GITHAT_PUBLISHABLE_KEY=pk_live_your_key_here
12
+
13
+ # GitHat API base URL (leave as-is unless using a self-hosted deployment)
14
+ VITE_GITHAT_API_URL={{apiUrl}}
15
+ {{/ifEquals}}
16
+ {{#if useDatabase}}
17
+
18
+ # Database
19
+ DATABASE_URL="postgresql://user:password@localhost:5432/{{projectName}}"
20
+ {{/if}}
@@ -1,23 +1,21 @@
1
1
  # {{projectName}}
2
2
 
3
- Built with [GitHat](https://githat.io) — your backend is already set up. Auth, teams, orgs, API keys, MCP verification, and AI agent identity are handled by GitHat's hosted platform.
3
+ Built with [GitHat](https://githat.io) — auth, teams, orgs, API keys, MCP verification, and AI agent identity are handled by GitHat's hosted platform.
4
4
 
5
5
  ## Getting Started
6
6
 
7
- 1. Install dependencies:
7
+ 1. Copy the env file and fill in your GitHat key:
8
8
 
9
9
  ```bash
10
- {{#ifEquals packageManager "yarn"}}yarn{{else}}{{packageManager}} install{{/ifEquals}}
10
+ cp .env.local.example .env.local
11
11
  ```
12
12
 
13
- 2. Add your GitHat publishable key to `{{#ifEquals framework "nextjs"}}.env.local{{else}}.env{{/ifEquals}}`:
13
+ 2. Install dependencies:
14
14
 
15
- ```env
16
- {{#ifEquals framework "nextjs"}}NEXT_PUBLIC_GITHAT_PUBLISHABLE_KEY{{else}}VITE_GITHAT_PUBLISHABLE_KEY{{/ifEquals}}=pk_live_your_key_here
15
+ ```bash
16
+ {{#ifEquals packageManager "yarn"}}yarn{{else}}{{packageManager}} install{{/ifEquals}}
17
17
  ```
18
18
 
19
- Get your key at [githat.io/dashboard/apps](https://githat.io/dashboard/apps).
20
-
21
19
  3. Start the dev server:
22
20
 
23
21
  ```bash
@@ -26,36 +24,11 @@ Get your key at [githat.io/dashboard/apps](https://githat.io/dashboard/apps).
26
24
 
27
25
  4. Open [http://localhost:{{#ifEquals framework "nextjs"}}3000{{else}}5173{{/ifEquals}}](http://localhost:{{#ifEquals framework "nextjs"}}3000{{else}}5173{{/ifEquals}})
28
26
 
29
- ## Your Backend
30
-
31
- All API calls go to `api.githat.io`. Your data is stored in GitHat's database. **No backend to deploy.**
32
-
33
- GitHat handles:
34
-
35
- - User auth (sign-up, sign-in, email verification, password reset)
36
- - Organizations (create, switch, branding)
37
- - Team management (invite members, assign roles)
38
- - API key management (publishable + secret keys)
39
- {{#if includeMcpModule}}- MCP server registration and domain verification
40
- {{/if}}{{#if includeAgentModule}}- AI agent registration and wallet verification
41
- {{/if}}- Email delivery (verification, invitations, password resets)
42
-
43
- The `githat/` folder in your project is a typed API client that talks to `api.githat.io`. You write frontend code, GitHat handles the rest.
44
-
45
- Manage everything at [githat.io/dashboard](https://githat.io/dashboard).
46
-
47
- ## Pages
48
-
49
- - `/sign-in` — Sign in
50
- - `/sign-up` — Create account
51
- {{#if includeForgotPassword}}- `/forgot-password` — Password recovery
52
- {{/if}}{{#if includeDashboard}}- `/dashboard` — Protected dashboard
53
- {{/if}}{{#if includeOrgManagement}}- `/dashboard/members` — Invite members, manage roles
54
- {{/if}}
27
+ Your `NEXT_PUBLIC_GITHAT_PUBLISHABLE_KEY` is required — get it at [githat.io/dashboard/apps](https://githat.io/dashboard/apps) or re-run `create-githat-app` after running `githat login` to have it auto-registered.
55
28
 
56
29
  ## GitHat SDK
57
30
 
58
- This project uses [`@githat/nextjs`](https://www.npmjs.com/package/@githat/nextjs) for connecting to the GitHat platform:
31
+ This project uses [`@githat/nextjs`](https://www.npmjs.com/package/@githat/nextjs):
59
32
 
60
33
  ```{{#if typescript}}tsx{{else}}jsx{{/if}}
61
34
  import { useAuth, UserButton, OrgSwitcher } from '@githat/nextjs';
@@ -63,32 +36,38 @@ import { useAuth, UserButton, OrgSwitcher } from '@githat/nextjs';
63
36
  const { user, org, signIn, signUp, signOut } = useAuth();
64
37
  ```
65
38
 
66
- ## Deploy
39
+ ## Deploy to EC2 via GitHub Actions
67
40
 
68
- {{#ifEquals framework "nextjs"}}### Vercel (recommended)
41
+ The scaffolded `.github/workflows/deploy.yml` deploys the Next.js standalone bundle to an EC2 instance on every push to `main`.
69
42
 
70
- ```bash
71
- npx vercel
72
- ```
43
+ **Required GitHub repo secrets:**
73
44
 
74
- Add `NEXT_PUBLIC_GITHAT_PUBLISHABLE_KEY` to your Vercel project environment variables.
75
- {{else}}### Vercel
45
+ | Secret | Description |
46
+ |--------|-------------|
47
+ | `SSH_PRIVATE_KEY` | Private key whose public key is in the EC2 instance's `~/.ssh/authorized_keys` |
48
+ | `EC2_HOST` | `ubuntu@<ec2-ip-or-hostname>` |
49
+ | `PROD_ENV` | Full contents of your production `.env` file (set via `gh secret set PROD_ENV < .env.local`) |
50
+ | `GITHAT_PUBLISHABLE_KEY` | Your GitHat publishable key (baked into the client bundle at build time) |
76
51
 
77
- ```bash
78
- npx vercel
79
- ```
52
+ **One-time EC2 setup:**
80
53
 
81
- ### Netlify
54
+ 1. Create a systemd service at `/etc/systemd/system/{{projectName}}.service` that runs `node /opt/{{projectName}}/server.js` with `PORT=3000`.
55
+ 2. Create `/opt/{{projectName}}/` owned by your deploy user.
56
+ 3. Enable the service: `sudo systemctl enable {{projectName}}`.
82
57
 
83
- ```bash
84
- npx netlify deploy
85
- ```
58
+ After that, every `git push origin main` triggers a build + rsync + restart. The workflow smoke-tests `/api/health` before reporting success.
86
59
 
87
- Add `VITE_GITHAT_PUBLISHABLE_KEY` to your hosting provider's environment variables.
88
- {{/ifEquals}}
60
+ ## Pages
61
+
62
+ - `/sign-in` — Sign in
63
+ - `/sign-up` — Create account
64
+ {{#if includeForgotPassword}}- `/forgot-password` — Password recovery
65
+ {{/if}}{{#if includeDashboard}}- `/dashboard` — Protected dashboard
66
+ {{/if}}{{#if includeOrgManagement}}- `/dashboard/members` — Invite members, manage roles
67
+ {{/if}}
89
68
 
90
69
  ## Learn More
91
70
 
92
71
  - [GitHat Documentation](https://githat.io/docs)
93
72
  - [SDK Reference](https://www.npmjs.com/package/@githat/nextjs)
94
- - [API Reference (42+ endpoints)](https://githat.io/docs/api)
73
+ - [API Reference](https://githat.io/docs/api)
@@ -10,7 +10,7 @@
10
10
  "typecheck": "tsc --noEmit"
11
11
  },
12
12
  "dependencies": {
13
- "@githat/nextjs": "^0.2.2",
13
+ "@githat/nextjs": "^0.2.6",
14
14
  "cors": "^2.8.5",
15
15
  "express": "^5.0.0"
16
16
  },
@@ -11,7 +11,7 @@
11
11
  },
12
12
  "dependencies": {
13
13
  "@fastify/cors": "^10.0.0",
14
- "@githat/nextjs": "^0.2.2",
14
+ "@githat/nextjs": "^0.2.6",
15
15
  "fastify": "^5.2.0"
16
16
  },
17
17
  "devDependencies": {
@@ -10,7 +10,7 @@
10
10
  "typecheck": "tsc --noEmit"
11
11
  },
12
12
  "dependencies": {
13
- "@githat/nextjs": "^0.2.2",
13
+ "@githat/nextjs": "^0.2.6",
14
14
  "@hono/node-server": "^1.13.0",
15
15
  "hono": "^4.6.0"
16
16
  },
@@ -20,6 +20,11 @@ export default function RootLayout({ children }{{#if typescript}}: { children: R
20
20
  }}>
21
21
  {children}
22
22
  </GitHatProvider>
23
+ <footer style=\{{ textAlign: 'center', padding: '1rem 0', fontSize: '0.75rem', color: '#52525b' }}>
24
+ <a href="https://githat.io" target="_blank" rel="noopener noreferrer" style=\{{ color: '#7c3aed', textDecoration: 'none' }}>
25
+ Powered by GitHat
26
+ </a>
27
+ </footer>
23
28
  </body>
24
29
  </html>
25
30
  );
@@ -10,7 +10,7 @@
10
10
  "typecheck": "tsc --noEmit"
11
11
  },
12
12
  "dependencies": {
13
- "@githat/nextjs": "^0.2.2",
13
+ "@githat/nextjs": "^0.2.6",
14
14
  "next": "^16.0.0",
15
15
  "react": "^19.0.0",
16
16
  "react-dom": "^19.0.0"
@@ -0,0 +1,107 @@
1
+ name: Deploy to EC2
2
+
3
+ # Auto-deploy every push to main. Also supports manual runs from the
4
+ # Actions tab (workflow_dispatch) so you can re-ship without a dummy commit.
5
+ on:
6
+ push:
7
+ branches: [main]
8
+ workflow_dispatch:
9
+
10
+ # Only one deploy at a time. Queue rather than race if a second push
11
+ # lands mid-deploy.
12
+ concurrency:
13
+ group: deploy-ec2
14
+ cancel-in-progress: false
15
+
16
+ jobs:
17
+ build-and-deploy:
18
+ runs-on: ubuntu-latest
19
+ # Skip if commit message starts with "docs:" unless it includes [deploy].
20
+ if: $\{{ !startsWith(github.event.head_commit.message, 'docs:') || contains(github.event.head_commit.message, '[deploy]') }}
21
+ timeout-minutes: 12
22
+
23
+ steps:
24
+ - uses: actions/checkout@v4
25
+
26
+ - name: Setup Node 20
27
+ uses: actions/setup-node@v4
28
+ with:
29
+ node-version: "20"
30
+ cache: "npm"
31
+
32
+ - name: Install deps
33
+ run: npm ci
34
+
35
+ - name: Type-check
36
+ run: npx tsc --noEmit
37
+
38
+ - name: Compute BUILD_ID
39
+ id: build_id
40
+ run: |
41
+ BUILD_ID=$(git rev-parse --short HEAD || echo "${GITHUB_SHA:0:7}")
42
+ echo "BUILD_ID=$BUILD_ID" >> "$GITHUB_OUTPUT"
43
+ echo "Resolved BUILD_ID=$BUILD_ID"
44
+
45
+ - name: Build (Next.js standalone)
46
+ run: npm run build
47
+ env:
48
+ # NEXT_PUBLIC_* vars are baked into the client bundle at build time.
49
+ # The real value comes from the GITHAT_PUBLISHABLE_KEY repo secret.
50
+ NEXT_PUBLIC_GITHAT_PUBLISHABLE_KEY: $\{{ secrets.GITHAT_PUBLISHABLE_KEY }}
51
+ BUILD_ID: $\{{ steps.build_id.outputs.BUILD_ID }}
52
+
53
+ - name: Load SSH key
54
+ run: |
55
+ mkdir -p ~/.ssh
56
+ echo "$\{{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/deploy_key
57
+ chmod 600 ~/.ssh/deploy_key
58
+ ssh-keyscan -H "${EC2_HOST#*@}" >> ~/.ssh/known_hosts
59
+ env:
60
+ EC2_HOST: $\{{ secrets.EC2_HOST }}
61
+
62
+ - name: Sync production .env
63
+ env:
64
+ PROD_ENV: $\{{ secrets.PROD_ENV }}
65
+ EC2_HOST: $\{{ secrets.EC2_HOST }}
66
+ run: |
67
+ if [ -z "$PROD_ENV" ]; then
68
+ echo "PROD_ENV secret is empty — skipping env sync."
69
+ exit 0
70
+ fi
71
+ SSH_OPTS="-i ~/.ssh/deploy_key -o StrictHostKeyChecking=yes"
72
+ printf '%s' "$PROD_ENV" > /tmp/prod.env
73
+ rsync -az -e "ssh $SSH_OPTS" /tmp/prod.env "$EC2_HOST:/opt/{{projectName}}/.env"
74
+ shred -u /tmp/prod.env 2>/dev/null || rm -f /tmp/prod.env
75
+ ssh $SSH_OPTS "$EC2_HOST" \
76
+ "sudo chgrp {{projectName}} /opt/{{projectName}}/.env && sudo chmod 640 /opt/{{projectName}}/.env"
77
+
78
+ - name: Deploy to EC2
79
+ env:
80
+ EC2_HOST: $\{{ secrets.EC2_HOST }}
81
+ run: |
82
+ SSH_OPTS="-i ~/.ssh/deploy_key -o StrictHostKeyChecking=yes"
83
+
84
+ rsync -az --delete -e "ssh $SSH_OPTS" \
85
+ --exclude .env \
86
+ .next/standalone/ "$EC2_HOST:/opt/{{projectName}}/"
87
+
88
+ rsync -az -e "ssh $SSH_OPTS" \
89
+ .next/static/ "$EC2_HOST:/opt/{{projectName}}/.next/static/"
90
+
91
+ rsync -az -e "ssh $SSH_OPTS" \
92
+ public/ "$EC2_HOST:/opt/{{projectName}}/public/"
93
+
94
+ ssh $SSH_OPTS "$EC2_HOST" "
95
+ sudo systemctl restart {{projectName}}
96
+ sleep 5
97
+ sudo systemctl is-active --quiet {{projectName}} || { sudo journalctl -u {{projectName}} -n 30 --no-pager; exit 1; }
98
+ echo \"deployed build \$(cat /opt/{{projectName}}/.next/BUILD_ID)\"
99
+ "
100
+
101
+ - name: Smoke-test the deploy
102
+ env:
103
+ EC2_HOST: $\{{ secrets.EC2_HOST }}
104
+ run: |
105
+ SSH_OPTS="-i ~/.ssh/deploy_key -o StrictHostKeyChecking=yes"
106
+ ssh $SSH_OPTS "$EC2_HOST" \
107
+ "curl --fail --silent --show-error --max-time 10 http://127.0.0.1:3000/api/health > /dev/null && echo OK"
@@ -27,6 +27,11 @@ export default function RootLayout({ children }{{#if typescript}}: { children: R
27
27
  }}>
28
28
  {children}
29
29
  </GitHatProvider>
30
+ <footer style=\{{ textAlign: 'center', padding: '1rem 0', fontSize: '0.75rem', color: '#52525b' }}>
31
+ <a href="https://githat.io" target="_blank" rel="noopener noreferrer" style=\{{ color: '#7c3aed', textDecoration: 'none' }}>
32
+ Powered by GitHat
33
+ </a>
34
+ </footer>
30
35
  </body>
31
36
  </html>
32
37
  );
@@ -1,5 +1,7 @@
1
1
  import type { NextConfig } from 'next';
2
2
 
3
- const nextConfig: NextConfig = {};
3
+ const nextConfig: NextConfig = {
4
+ output: 'standalone',
5
+ };
4
6
 
5
7
  export default nextConfig;
@@ -27,20 +27,27 @@ function NotFound() {
27
27
 
28
28
  export default function App() {
29
29
  return (
30
- <Routes>
31
- <Route path="/" element={<Home />} />
32
- <Route path="/sign-in" element={<SignIn />} />
33
- <Route path="/sign-up" element={<SignUp />} />
30
+ <>
31
+ <Routes>
32
+ <Route path="/" element={<Home />} />
33
+ <Route path="/sign-in" element={<SignIn />} />
34
+ <Route path="/sign-up" element={<SignUp />} />
34
35
  {{#if includeForgotPassword}}
35
- <Route path="/forgot-password" element={<ForgotPassword />} />
36
+ <Route path="/forgot-password" element={<ForgotPassword />} />
36
37
  {{/if}}
37
38
  {{#if includeEmailVerification}}
38
- <Route path="/verify-email" element={<VerifyEmail />} />
39
+ <Route path="/verify-email" element={<VerifyEmail />} />
39
40
  {{/if}}
40
41
  {{#if includeDashboard}}
41
- <Route path="/dashboard/*" element={<ProtectedRoute><Dashboard /></ProtectedRoute>} />
42
+ <Route path="/dashboard/*" element={<ProtectedRoute><Dashboard /></ProtectedRoute>} />
42
43
  {{/if}}
43
- <Route path="*" element={<NotFound />} />
44
- </Routes>
44
+ <Route path="*" element={<NotFound />} />
45
+ </Routes>
46
+ <footer style=\{{ textAlign: 'center', padding: '1rem 0', fontSize: '0.75rem', color: '#52525b' }}>
47
+ <a href="https://githat.io" target="_blank" rel="noopener noreferrer" style=\{{ color: '#7c3aed', textDecoration: 'none' }}>
48
+ Powered by GitHat
49
+ </a>
50
+ </footer>
51
+ </>
45
52
  );
46
53
  }