create-githat-app 1.0.10 → 1.0.13
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 +13 -18
- package/dist/cli.js +267 -180
- package/package.json +1 -1
- package/templates/base/.env.local.example.hbs +20 -0
- package/templates/base/README.md.hbs +31 -52
- package/templates/fullstack/apps-api-express/package.json.hbs +1 -1
- package/templates/fullstack/apps-api-fastify/package.json.hbs +1 -1
- package/templates/fullstack/apps-api-hono/package.json.hbs +1 -1
- package/templates/fullstack/apps-web-nextjs/app/globals.css.hbs +4 -3
- package/templates/fullstack/apps-web-nextjs/app/layout.tsx.hbs +5 -1
- package/templates/fullstack/apps-web-nextjs/app/page.tsx.hbs +2 -3
- package/templates/fullstack/apps-web-nextjs/package.json.hbs +2 -1
- package/templates/nextjs/.github/workflows/deploy.yml.hbs +107 -0
- package/templates/nextjs/app/globals.css.hbs +4 -3
- package/templates/nextjs/app/layout.tsx.hbs +5 -1
- package/templates/nextjs/app/page.tsx.hbs +3 -6
- package/templates/nextjs/next.config.ts.hbs +3 -1
- package/templates/react-vite/src/App.tsx.hbs +11 -9
- package/templates/react-vite/src/index.css.hbs +4 -3
- package/templates/react-vite/src/pages/Home.tsx.hbs +3 -6
package/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
|
|
6
|
-
import
|
|
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.
|
|
14
|
+
var VERSION = "1.0.13";
|
|
15
15
|
var DEFAULT_API_URL = "https://api.githat.io";
|
|
16
16
|
var DASHBOARD_URL = "https://githat.io/dashboard/apps";
|
|
17
17
|
var BRAND_COLORS = ["#7c3aed", "#6366f1", "#8b5cf6"];
|
|
@@ -21,7 +21,8 @@ var DEPS = {
|
|
|
21
21
|
next: "^16.0.0",
|
|
22
22
|
react: "^19.0.0",
|
|
23
23
|
"react-dom": "^19.0.0",
|
|
24
|
-
"@githat/nextjs": "^0.
|
|
24
|
+
"@githat/nextjs": "^0.5.0",
|
|
25
|
+
"@githat/ui": "^1.0.0"
|
|
25
26
|
},
|
|
26
27
|
devDependencies: {
|
|
27
28
|
typescript: "^5.9.0",
|
|
@@ -35,7 +36,8 @@ var DEPS = {
|
|
|
35
36
|
react: "^19.0.0",
|
|
36
37
|
"react-dom": "^19.0.0",
|
|
37
38
|
"react-router-dom": "^7.0.0",
|
|
38
|
-
"@githat/nextjs": "^0.
|
|
39
|
+
"@githat/nextjs": "^0.5.0",
|
|
40
|
+
"@githat/ui": "^1.0.0"
|
|
39
41
|
},
|
|
40
42
|
devDependencies: {
|
|
41
43
|
vite: "^7.0.0",
|
|
@@ -657,11 +659,11 @@ function answersToContext(answers) {
|
|
|
657
659
|
}
|
|
658
660
|
|
|
659
661
|
// src/scaffold/index.ts
|
|
660
|
-
import
|
|
661
|
-
import
|
|
662
|
+
import fs4 from "fs-extra";
|
|
663
|
+
import path4 from "path";
|
|
662
664
|
import { execSync as execSync3 } from "child_process";
|
|
663
|
-
import * as
|
|
664
|
-
import
|
|
665
|
+
import * as p10 from "@clack/prompts";
|
|
666
|
+
import chalk3 from "chalk";
|
|
665
667
|
|
|
666
668
|
// src/utils/template-engine.ts
|
|
667
669
|
import Handlebars from "handlebars";
|
|
@@ -809,27 +811,109 @@ function initGit(cwd) {
|
|
|
809
811
|
}
|
|
810
812
|
}
|
|
811
813
|
|
|
814
|
+
// src/utils/register-app.ts
|
|
815
|
+
import fs3 from "fs-extra";
|
|
816
|
+
import path3 from "path";
|
|
817
|
+
import os from "os";
|
|
818
|
+
import * as p9 from "@clack/prompts";
|
|
819
|
+
import chalk2 from "chalk";
|
|
820
|
+
var CREDENTIALS_PATH = path3.join(os.homedir(), ".githat", "credentials.json");
|
|
821
|
+
function readToken() {
|
|
822
|
+
if (!fs3.existsSync(CREDENTIALS_PATH)) {
|
|
823
|
+
return null;
|
|
824
|
+
}
|
|
825
|
+
try {
|
|
826
|
+
const creds = fs3.readJsonSync(CREDENTIALS_PATH);
|
|
827
|
+
return creds.token || null;
|
|
828
|
+
} catch {
|
|
829
|
+
return null;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
async function registerApp(appName, projectRoot) {
|
|
833
|
+
const token = readToken();
|
|
834
|
+
if (!token) {
|
|
835
|
+
p9.log.warn(
|
|
836
|
+
chalk2.yellow(
|
|
837
|
+
`GitHat credentials not found at ${CREDENTIALS_PATH}.
|
|
838
|
+
Run ${chalk2.cyan("githat login")} then re-run scaffolding, or paste your publishable key
|
|
839
|
+
from ${chalk2.cyan("https://githat.io/dashboard/apps")} into .env.local manually.`
|
|
840
|
+
)
|
|
841
|
+
);
|
|
842
|
+
return null;
|
|
843
|
+
}
|
|
844
|
+
p9.log.step("Registering app on GitHat...");
|
|
845
|
+
let registeredApp;
|
|
846
|
+
try {
|
|
847
|
+
const response = await fetch(`${DEFAULT_API_URL}/apps`, {
|
|
848
|
+
method: "POST",
|
|
849
|
+
headers: {
|
|
850
|
+
"Content-Type": "application/json",
|
|
851
|
+
Authorization: `Bearer ${token}`
|
|
852
|
+
},
|
|
853
|
+
body: JSON.stringify({
|
|
854
|
+
name: appName,
|
|
855
|
+
redirect_uris: ["http://localhost:3000/callback"]
|
|
856
|
+
})
|
|
857
|
+
});
|
|
858
|
+
if (!response.ok) {
|
|
859
|
+
const body = await response.text().catch(() => "");
|
|
860
|
+
throw new Error(`HTTP ${response.status}: ${body}`);
|
|
861
|
+
}
|
|
862
|
+
registeredApp = await response.json();
|
|
863
|
+
} catch (err) {
|
|
864
|
+
p9.log.warn(
|
|
865
|
+
chalk2.yellow(
|
|
866
|
+
`Could not register app on GitHat: ${err.message}
|
|
867
|
+
The scaffold was written successfully. Once the API is available,
|
|
868
|
+
register manually at ${chalk2.cyan("https://githat.io/dashboard/apps")} and paste the
|
|
869
|
+
publishable key into ${chalk2.cyan(".env.local")}.`
|
|
870
|
+
)
|
|
871
|
+
);
|
|
872
|
+
return null;
|
|
873
|
+
}
|
|
874
|
+
const publishableKey = registeredApp.publishable_key;
|
|
875
|
+
const envLocalPath = path3.join(projectRoot, ".env.local");
|
|
876
|
+
try {
|
|
877
|
+
let envContent = fs3.existsSync(envLocalPath) ? fs3.readFileSync(envLocalPath, "utf-8") : "";
|
|
878
|
+
const keyLine = `NEXT_PUBLIC_GITHAT_PUBLISHABLE_KEY=${publishableKey}`;
|
|
879
|
+
if (envContent.includes("NEXT_PUBLIC_GITHAT_PUBLISHABLE_KEY=")) {
|
|
880
|
+
envContent = envContent.replace(
|
|
881
|
+
/NEXT_PUBLIC_GITHAT_PUBLISHABLE_KEY=.*/,
|
|
882
|
+
keyLine
|
|
883
|
+
);
|
|
884
|
+
} else {
|
|
885
|
+
envContent += (envContent.endsWith("\n") || envContent === "" ? "" : "\n") + keyLine + "\n";
|
|
886
|
+
}
|
|
887
|
+
fs3.ensureDirSync(path3.dirname(envLocalPath));
|
|
888
|
+
fs3.writeFileSync(envLocalPath, envContent, "utf-8");
|
|
889
|
+
} catch (err) {
|
|
890
|
+
p9.log.warn(chalk2.yellow(`Registered app but could not write .env.local: ${err.message}`));
|
|
891
|
+
}
|
|
892
|
+
p9.log.success(chalk2.green(`Registered "${appName}" on GitHat. Publishable key written to .env.local.`));
|
|
893
|
+
return publishableKey;
|
|
894
|
+
}
|
|
895
|
+
|
|
812
896
|
// src/scaffold/index.ts
|
|
813
897
|
async function scaffold(context, options) {
|
|
814
|
-
const root =
|
|
815
|
-
if (
|
|
816
|
-
|
|
898
|
+
const root = path4.resolve(process.cwd(), context.projectName);
|
|
899
|
+
if (fs4.existsSync(root)) {
|
|
900
|
+
p10.cancel(`Directory "${context.projectName}" already exists.`);
|
|
817
901
|
process.exit(1);
|
|
818
902
|
}
|
|
819
903
|
const isFullstack = context.projectType === "fullstack";
|
|
820
904
|
await withSpinner("Creating project structure...", async () => {
|
|
821
|
-
|
|
905
|
+
fs4.ensureDirSync(root);
|
|
822
906
|
const templatesRoot = getTemplatesRoot();
|
|
823
907
|
if (isFullstack) {
|
|
824
908
|
scaffoldFullstack(templatesRoot, root, context);
|
|
825
909
|
} else {
|
|
826
|
-
const frameworkDir =
|
|
827
|
-
if (!
|
|
910
|
+
const frameworkDir = path4.join(templatesRoot, context.framework);
|
|
911
|
+
if (!fs4.existsSync(frameworkDir)) {
|
|
828
912
|
throw new Error(`Templates not found at ${frameworkDir}. This is a bug \u2014 please report it.`);
|
|
829
913
|
}
|
|
830
914
|
renderTemplateDirectory(frameworkDir, root, context);
|
|
831
|
-
const baseDir =
|
|
832
|
-
if (
|
|
915
|
+
const baseDir = path4.join(templatesRoot, "base");
|
|
916
|
+
if (fs4.existsSync(baseDir)) {
|
|
833
917
|
renderTemplateDirectory(baseDir, root, context);
|
|
834
918
|
}
|
|
835
919
|
}
|
|
@@ -840,6 +924,9 @@ async function scaffold(context, options) {
|
|
|
840
924
|
writeJson(root, "package.json", pkg);
|
|
841
925
|
}, "package.json generated");
|
|
842
926
|
}
|
|
927
|
+
if (!isFullstack && context.framework === "nextjs") {
|
|
928
|
+
await registerApp(context.projectName, root);
|
|
929
|
+
}
|
|
843
930
|
if (options.initGit) {
|
|
844
931
|
const gitSpinner = createSpinner("Initializing git repository...");
|
|
845
932
|
gitSpinner.start();
|
|
@@ -860,69 +947,69 @@ async function scaffold(context, options) {
|
|
|
860
947
|
} catch (err) {
|
|
861
948
|
const msg = err.message || "";
|
|
862
949
|
if (msg.includes("TIMEOUT")) {
|
|
863
|
-
|
|
950
|
+
p10.log.warn(`Install timed out. Run ${chalk3.cyan(installCmd)} manually.`);
|
|
864
951
|
} else {
|
|
865
|
-
|
|
952
|
+
p10.log.warn(`Could not auto-install. Run ${chalk3.cyan(installCmd)} manually.`);
|
|
866
953
|
}
|
|
867
954
|
}
|
|
868
955
|
},
|
|
869
956
|
"Dependencies installed"
|
|
870
957
|
);
|
|
871
958
|
}
|
|
872
|
-
|
|
959
|
+
p10.outro("Setup complete!");
|
|
873
960
|
displaySuccess(context.projectName, context.packageManager, context.framework, !!context.publishableKey, isFullstack);
|
|
874
961
|
if (!options.skipPrompts) {
|
|
875
|
-
const starPrompt = await
|
|
962
|
+
const starPrompt = await p10.confirm({
|
|
876
963
|
message: "Star GitHat on GitHub? (helps us grow!)",
|
|
877
964
|
initialValue: false
|
|
878
965
|
});
|
|
879
|
-
if (!
|
|
966
|
+
if (!p10.isCancel(starPrompt) && starPrompt) {
|
|
880
967
|
try {
|
|
881
968
|
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
882
969
|
execSync3(`${cmd} "https://github.com/GitHat-IO/githat"`, { stdio: "ignore" });
|
|
883
970
|
} catch {
|
|
884
|
-
|
|
971
|
+
p10.log.info("Visit https://github.com/GitHat-IO/githat to star us!");
|
|
885
972
|
}
|
|
886
973
|
}
|
|
887
974
|
}
|
|
888
975
|
}
|
|
889
976
|
function scaffoldFullstack(templatesRoot, root, context) {
|
|
890
|
-
const fullstackDir =
|
|
891
|
-
const rootDir =
|
|
892
|
-
if (
|
|
977
|
+
const fullstackDir = path4.join(templatesRoot, "fullstack");
|
|
978
|
+
const rootDir = path4.join(fullstackDir, "root");
|
|
979
|
+
if (fs4.existsSync(rootDir)) {
|
|
893
980
|
renderTemplateDirectory(rootDir, root, context);
|
|
894
981
|
}
|
|
895
|
-
const appsDir =
|
|
896
|
-
|
|
897
|
-
const webDir =
|
|
898
|
-
|
|
899
|
-
const webTemplateDir =
|
|
900
|
-
if (
|
|
982
|
+
const appsDir = path4.join(root, "apps");
|
|
983
|
+
fs4.ensureDirSync(appsDir);
|
|
984
|
+
const webDir = path4.join(appsDir, "web");
|
|
985
|
+
fs4.ensureDirSync(webDir);
|
|
986
|
+
const webTemplateDir = path4.join(fullstackDir, `apps-web-${context.framework}`);
|
|
987
|
+
if (fs4.existsSync(webTemplateDir)) {
|
|
901
988
|
renderTemplateDirectory(webTemplateDir, webDir, context);
|
|
902
989
|
} else {
|
|
903
990
|
throw new Error(`Web app templates not found at ${webTemplateDir}. This is a bug \u2014 please report it.`);
|
|
904
991
|
}
|
|
905
|
-
const apiDir =
|
|
906
|
-
|
|
992
|
+
const apiDir = path4.join(appsDir, "api");
|
|
993
|
+
fs4.ensureDirSync(apiDir);
|
|
907
994
|
const backendFramework = context.backendFramework || "hono";
|
|
908
|
-
const apiTemplateDir =
|
|
909
|
-
if (
|
|
995
|
+
const apiTemplateDir = path4.join(fullstackDir, `apps-api-${backendFramework}`);
|
|
996
|
+
if (fs4.existsSync(apiTemplateDir)) {
|
|
910
997
|
renderTemplateDirectory(apiTemplateDir, apiDir, context);
|
|
911
998
|
} else {
|
|
912
999
|
throw new Error(`API templates not found at ${apiTemplateDir}. This is a bug \u2014 please report it.`);
|
|
913
1000
|
}
|
|
914
|
-
const packagesDir =
|
|
915
|
-
|
|
916
|
-
|
|
1001
|
+
const packagesDir = path4.join(root, "packages");
|
|
1002
|
+
fs4.ensureDirSync(packagesDir);
|
|
1003
|
+
fs4.writeFileSync(path4.join(packagesDir, ".gitkeep"), "");
|
|
917
1004
|
}
|
|
918
1005
|
|
|
919
1006
|
// src/commands/skills/index.ts
|
|
920
1007
|
import { Command as Command6 } from "commander";
|
|
921
|
-
import
|
|
1008
|
+
import chalk9 from "chalk";
|
|
922
1009
|
|
|
923
1010
|
// src/commands/skills/search.ts
|
|
924
1011
|
import { Command } from "commander";
|
|
925
|
-
import
|
|
1012
|
+
import chalk4 from "chalk";
|
|
926
1013
|
|
|
927
1014
|
// src/commands/skills/api.ts
|
|
928
1015
|
async function fetchApi(endpoint, options = {}) {
|
|
@@ -970,99 +1057,99 @@ async function getDownloadUrl(slug, version) {
|
|
|
970
1057
|
// src/commands/skills/search.ts
|
|
971
1058
|
function formatSkill(skill) {
|
|
972
1059
|
const typeColors = {
|
|
973
|
-
template:
|
|
974
|
-
integration:
|
|
975
|
-
ui:
|
|
976
|
-
ai:
|
|
977
|
-
workflow:
|
|
1060
|
+
template: chalk4.blue,
|
|
1061
|
+
integration: chalk4.green,
|
|
1062
|
+
ui: chalk4.magenta,
|
|
1063
|
+
ai: chalk4.yellow,
|
|
1064
|
+
workflow: chalk4.cyan
|
|
978
1065
|
};
|
|
979
|
-
const typeColor = typeColors[skill.type] ||
|
|
1066
|
+
const typeColor = typeColors[skill.type] || chalk4.white;
|
|
980
1067
|
return [
|
|
981
|
-
`${
|
|
1068
|
+
`${chalk4.bold(skill.name)} ${chalk4.dim(`@${skill.latestVersion}`)}`,
|
|
982
1069
|
` ${skill.description}`,
|
|
983
1070
|
` ${typeColor(skill.type)} \xB7 \u2B07 ${skill.downloads} \xB7 \u2B50 ${skill.stars} \xB7 by ${skill.authorName}`,
|
|
984
|
-
` ${
|
|
1071
|
+
` ${chalk4.dim(`githat skills install ${skill.slug}`)}`
|
|
985
1072
|
].join("\n");
|
|
986
1073
|
}
|
|
987
1074
|
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
1075
|
try {
|
|
989
|
-
console.log(
|
|
1076
|
+
console.log(chalk4.dim(`
|
|
990
1077
|
Searching for "${query}"...
|
|
991
1078
|
`));
|
|
992
1079
|
const result = await searchSkills(query, options.type);
|
|
993
1080
|
if (result.skills.length === 0) {
|
|
994
|
-
console.log(
|
|
995
|
-
console.log(
|
|
996
|
-
console.log(
|
|
1081
|
+
console.log(chalk4.yellow("No skills found matching your query."));
|
|
1082
|
+
console.log(chalk4.dim("\nTry a different search term or browse all skills:"));
|
|
1083
|
+
console.log(chalk4.dim(" githat skills list"));
|
|
997
1084
|
return;
|
|
998
1085
|
}
|
|
999
|
-
console.log(
|
|
1086
|
+
console.log(chalk4.cyan(`Found ${result.skills.length} skill(s):
|
|
1000
1087
|
`));
|
|
1001
1088
|
for (const skill of result.skills) {
|
|
1002
1089
|
console.log(formatSkill(skill));
|
|
1003
1090
|
console.log("");
|
|
1004
1091
|
}
|
|
1005
1092
|
} catch (err) {
|
|
1006
|
-
console.error(
|
|
1093
|
+
console.error(chalk4.red(`Error: ${err.message}`));
|
|
1007
1094
|
process.exit(1);
|
|
1008
1095
|
}
|
|
1009
1096
|
});
|
|
1010
1097
|
|
|
1011
1098
|
// src/commands/skills/list.ts
|
|
1012
1099
|
import { Command as Command2 } from "commander";
|
|
1013
|
-
import
|
|
1100
|
+
import chalk5 from "chalk";
|
|
1014
1101
|
function formatSkillCompact(skill) {
|
|
1015
1102
|
const typeColors = {
|
|
1016
|
-
template:
|
|
1017
|
-
integration:
|
|
1018
|
-
ui:
|
|
1019
|
-
ai:
|
|
1020
|
-
workflow:
|
|
1103
|
+
template: chalk5.blue,
|
|
1104
|
+
integration: chalk5.green,
|
|
1105
|
+
ui: chalk5.magenta,
|
|
1106
|
+
ai: chalk5.yellow,
|
|
1107
|
+
workflow: chalk5.cyan
|
|
1021
1108
|
};
|
|
1022
|
-
const typeColor = typeColors[skill.type] ||
|
|
1023
|
-
const name =
|
|
1109
|
+
const typeColor = typeColors[skill.type] || chalk5.white;
|
|
1110
|
+
const name = chalk5.bold(skill.name.padEnd(25));
|
|
1024
1111
|
const type = typeColor(skill.type.padEnd(12));
|
|
1025
1112
|
const stats = `\u2B07 ${String(skill.downloads).padStart(5)} \u2B50 ${String(skill.stars).padStart(4)}`;
|
|
1026
1113
|
const desc = skill.description.length > 40 ? skill.description.substring(0, 37) + "..." : skill.description;
|
|
1027
|
-
return `${name} ${type} ${stats} ${
|
|
1114
|
+
return `${name} ${type} ${stats} ${chalk5.dim(desc)}`;
|
|
1028
1115
|
}
|
|
1029
1116
|
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
1117
|
try {
|
|
1031
1118
|
const limit = parseInt(options.limit, 10);
|
|
1032
|
-
console.log(
|
|
1119
|
+
console.log(chalk5.dim("\nFetching skills...\n"));
|
|
1033
1120
|
const result = await listSkills({ type: options.type, limit });
|
|
1034
1121
|
if (result.skills.length === 0) {
|
|
1035
|
-
console.log(
|
|
1122
|
+
console.log(chalk5.yellow("No skills found."));
|
|
1036
1123
|
if (options.type) {
|
|
1037
|
-
console.log(
|
|
1124
|
+
console.log(chalk5.dim(`
|
|
1038
1125
|
Try without the type filter:`));
|
|
1039
|
-
console.log(
|
|
1126
|
+
console.log(chalk5.dim(" githat skills list"));
|
|
1040
1127
|
}
|
|
1041
1128
|
return;
|
|
1042
1129
|
}
|
|
1043
1130
|
const header = `${"NAME".padEnd(25)} ${"TYPE".padEnd(12)} ${"DOWNLOADS".padStart(10)} DESCRIPTION`;
|
|
1044
|
-
console.log(
|
|
1045
|
-
console.log(
|
|
1131
|
+
console.log(chalk5.dim(header));
|
|
1132
|
+
console.log(chalk5.dim("\u2500".repeat(80)));
|
|
1046
1133
|
for (const skill of result.skills) {
|
|
1047
1134
|
console.log(formatSkillCompact(skill));
|
|
1048
1135
|
}
|
|
1049
|
-
console.log(
|
|
1050
|
-
console.log(
|
|
1136
|
+
console.log(chalk5.dim("\u2500".repeat(80)));
|
|
1137
|
+
console.log(chalk5.dim(`Showing ${result.skills.length} skill(s)`));
|
|
1051
1138
|
if (result.nextCursor) {
|
|
1052
|
-
console.log(
|
|
1139
|
+
console.log(chalk5.dim("\nMore results available. Use --limit to see more."));
|
|
1053
1140
|
}
|
|
1054
|
-
console.log(
|
|
1141
|
+
console.log(chalk5.dim("\nTo install: githat skills install <name>"));
|
|
1055
1142
|
} catch (err) {
|
|
1056
|
-
console.error(
|
|
1143
|
+
console.error(chalk5.red(`Error: ${err.message}`));
|
|
1057
1144
|
process.exit(1);
|
|
1058
1145
|
}
|
|
1059
1146
|
});
|
|
1060
1147
|
|
|
1061
1148
|
// src/commands/skills/install.ts
|
|
1062
1149
|
import { Command as Command3 } from "commander";
|
|
1063
|
-
import
|
|
1064
|
-
import * as
|
|
1065
|
-
import * as
|
|
1150
|
+
import chalk6 from "chalk";
|
|
1151
|
+
import * as fs5 from "fs";
|
|
1152
|
+
import * as path5 from "path";
|
|
1066
1153
|
import { pipeline } from "stream/promises";
|
|
1067
1154
|
import { createWriteStream, mkdirSync } from "fs";
|
|
1068
1155
|
import { Extract } from "unzipper";
|
|
@@ -1071,20 +1158,20 @@ async function downloadAndExtract(url, destDir) {
|
|
|
1071
1158
|
if (!response.ok) {
|
|
1072
1159
|
throw new Error(`Download failed: ${response.statusText}`);
|
|
1073
1160
|
}
|
|
1074
|
-
const tempZip =
|
|
1161
|
+
const tempZip = path5.join(destDir, ".skill-download.zip");
|
|
1075
1162
|
const fileStream = createWriteStream(tempZip);
|
|
1076
1163
|
await pipeline(response.body, fileStream);
|
|
1077
1164
|
await new Promise((resolve4, reject) => {
|
|
1078
|
-
|
|
1165
|
+
fs5.createReadStream(tempZip).pipe(Extract({ path: destDir })).on("close", resolve4).on("error", reject);
|
|
1079
1166
|
});
|
|
1080
|
-
|
|
1167
|
+
fs5.unlinkSync(tempZip);
|
|
1081
1168
|
}
|
|
1082
1169
|
function updateGithatLock(projectDir, skill) {
|
|
1083
|
-
const lockPath =
|
|
1170
|
+
const lockPath = path5.join(projectDir, "githat.lock");
|
|
1084
1171
|
let lock = {};
|
|
1085
|
-
if (
|
|
1172
|
+
if (fs5.existsSync(lockPath)) {
|
|
1086
1173
|
try {
|
|
1087
|
-
lock = JSON.parse(
|
|
1174
|
+
lock = JSON.parse(fs5.readFileSync(lockPath, "utf-8"));
|
|
1088
1175
|
} catch {
|
|
1089
1176
|
}
|
|
1090
1177
|
}
|
|
@@ -1092,17 +1179,17 @@ function updateGithatLock(projectDir, skill) {
|
|
|
1092
1179
|
version: skill.version,
|
|
1093
1180
|
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1094
1181
|
};
|
|
1095
|
-
|
|
1182
|
+
fs5.writeFileSync(lockPath, JSON.stringify(lock, null, 2));
|
|
1096
1183
|
}
|
|
1097
1184
|
function updateEnvExample(projectDir, manifest) {
|
|
1098
1185
|
if (!manifest.requires?.env?.length) return;
|
|
1099
|
-
const envPath =
|
|
1100
|
-
const envExamplePath =
|
|
1186
|
+
const envPath = path5.join(projectDir, ".env.local");
|
|
1187
|
+
const envExamplePath = path5.join(projectDir, ".env.example");
|
|
1101
1188
|
let envContent = "";
|
|
1102
|
-
if (
|
|
1103
|
-
envContent =
|
|
1104
|
-
} else if (
|
|
1105
|
-
envContent =
|
|
1189
|
+
if (fs5.existsSync(envPath)) {
|
|
1190
|
+
envContent = fs5.readFileSync(envPath, "utf-8");
|
|
1191
|
+
} else if (fs5.existsSync(envExamplePath)) {
|
|
1192
|
+
envContent = fs5.readFileSync(envExamplePath, "utf-8");
|
|
1106
1193
|
}
|
|
1107
1194
|
const existingVars = new Set(
|
|
1108
1195
|
envContent.split("\n").filter((line) => line.includes("=")).map((line) => line.split("=")[0].trim())
|
|
@@ -1118,10 +1205,10 @@ function updateEnvExample(projectDir, manifest) {
|
|
|
1118
1205
|
# Added by skill install
|
|
1119
1206
|
${newVars.join("\n")}
|
|
1120
1207
|
`;
|
|
1121
|
-
if (
|
|
1122
|
-
|
|
1208
|
+
if (fs5.existsSync(envPath)) {
|
|
1209
|
+
fs5.appendFileSync(envPath, addition);
|
|
1123
1210
|
} else {
|
|
1124
|
-
|
|
1211
|
+
fs5.writeFileSync(envPath, `# Environment variables
|
|
1125
1212
|
${newVars.join("\n")}
|
|
1126
1213
|
`);
|
|
1127
1214
|
}
|
|
@@ -1129,112 +1216,112 @@ ${newVars.join("\n")}
|
|
|
1129
1216
|
}
|
|
1130
1217
|
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
1218
|
try {
|
|
1132
|
-
const projectDir = options.dir ?
|
|
1133
|
-
const packageJsonPath =
|
|
1134
|
-
if (!
|
|
1135
|
-
console.error(
|
|
1219
|
+
const projectDir = options.dir ? path5.resolve(options.dir) : process.cwd();
|
|
1220
|
+
const packageJsonPath = path5.join(projectDir, "package.json");
|
|
1221
|
+
if (!fs5.existsSync(packageJsonPath)) {
|
|
1222
|
+
console.error(chalk6.red("Error: No package.json found. Are you in a project directory?"));
|
|
1136
1223
|
process.exit(1);
|
|
1137
1224
|
}
|
|
1138
|
-
console.log(
|
|
1225
|
+
console.log(chalk6.dim(`
|
|
1139
1226
|
Fetching skill info for "${slug}"...
|
|
1140
1227
|
`));
|
|
1141
1228
|
const skill = await getSkill(slug);
|
|
1142
|
-
console.log(
|
|
1143
|
-
console.log(
|
|
1144
|
-
console.log(
|
|
1229
|
+
console.log(chalk6.cyan(`\u{1F4E6} ${skill.name}`));
|
|
1230
|
+
console.log(chalk6.dim(` ${skill.description}`));
|
|
1231
|
+
console.log(chalk6.dim(` Type: ${skill.type} \xB7 Author: ${skill.authorName}
|
|
1145
1232
|
`));
|
|
1146
1233
|
const download = await getDownloadUrl(slug, options.version);
|
|
1147
1234
|
const version = download.version.version;
|
|
1148
|
-
console.log(
|
|
1149
|
-
const skillDir =
|
|
1235
|
+
console.log(chalk6.dim(`Downloading ${skill.name}@${version}...`));
|
|
1236
|
+
const skillDir = path5.join(projectDir, "githat", "skills", slug);
|
|
1150
1237
|
mkdirSync(skillDir, { recursive: true });
|
|
1151
1238
|
await downloadAndExtract(download.downloadUrl, skillDir);
|
|
1152
|
-
console.log(
|
|
1153
|
-
const manifestPath =
|
|
1154
|
-
if (
|
|
1155
|
-
const manifest = JSON.parse(
|
|
1239
|
+
console.log(chalk6.green(`\u2713 Downloaded to ${path5.relative(projectDir, skillDir)}`));
|
|
1240
|
+
const manifestPath = path5.join(skillDir, "githat-skill.json");
|
|
1241
|
+
if (fs5.existsSync(manifestPath)) {
|
|
1242
|
+
const manifest = JSON.parse(fs5.readFileSync(manifestPath, "utf-8"));
|
|
1156
1243
|
updateEnvExample(projectDir, manifest);
|
|
1157
1244
|
if (manifest.requires?.env?.length) {
|
|
1158
|
-
console.log(
|
|
1245
|
+
console.log(chalk6.yellow(`
|
|
1159
1246
|
\u26A0 Required environment variables:`));
|
|
1160
1247
|
for (const envVar of manifest.requires.env) {
|
|
1161
|
-
console.log(
|
|
1248
|
+
console.log(chalk6.dim(` ${envVar}`));
|
|
1162
1249
|
}
|
|
1163
|
-
console.log(
|
|
1250
|
+
console.log(chalk6.dim(`
|
|
1164
1251
|
Add these to your .env.local file`));
|
|
1165
1252
|
}
|
|
1166
1253
|
if (manifest.install?.dependencies) {
|
|
1167
1254
|
const deps = Object.entries(manifest.install.dependencies).map(([name, ver]) => `${name}@${ver}`).join(" ");
|
|
1168
|
-
console.log(
|
|
1255
|
+
console.log(chalk6.yellow(`
|
|
1169
1256
|
\u26A0 Install npm dependencies:`));
|
|
1170
|
-
console.log(
|
|
1257
|
+
console.log(chalk6.dim(` npm install ${deps}`));
|
|
1171
1258
|
}
|
|
1172
1259
|
}
|
|
1173
1260
|
updateGithatLock(projectDir, { slug, version });
|
|
1174
|
-
console.log(
|
|
1261
|
+
console.log(chalk6.green(`
|
|
1175
1262
|
\u2705 Successfully installed ${skill.name}@${version}
|
|
1176
1263
|
`));
|
|
1177
|
-
console.log(
|
|
1178
|
-
console.log(
|
|
1179
|
-
console.log(
|
|
1180
|
-
console.log(
|
|
1264
|
+
console.log(chalk6.dim("Next steps:"));
|
|
1265
|
+
console.log(chalk6.dim(` 1. Check githat/skills/${slug}/README.md for usage`));
|
|
1266
|
+
console.log(chalk6.dim(" 2. Add required environment variables to .env.local"));
|
|
1267
|
+
console.log(chalk6.dim(" 3. Import and use the skill in your code"));
|
|
1181
1268
|
} catch (err) {
|
|
1182
|
-
console.error(
|
|
1269
|
+
console.error(chalk6.red(`Error: ${err.message}`));
|
|
1183
1270
|
process.exit(1);
|
|
1184
1271
|
}
|
|
1185
1272
|
});
|
|
1186
1273
|
|
|
1187
1274
|
// src/commands/skills/installed.ts
|
|
1188
1275
|
import { Command as Command4 } from "commander";
|
|
1189
|
-
import
|
|
1190
|
-
import * as
|
|
1191
|
-
import * as
|
|
1276
|
+
import chalk7 from "chalk";
|
|
1277
|
+
import * as fs6 from "fs";
|
|
1278
|
+
import * as path6 from "path";
|
|
1192
1279
|
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
1280
|
try {
|
|
1194
|
-
const projectDir = options.dir ?
|
|
1195
|
-
const lockPath =
|
|
1196
|
-
if (!
|
|
1197
|
-
console.log(
|
|
1198
|
-
console.log(
|
|
1199
|
-
console.log(
|
|
1281
|
+
const projectDir = options.dir ? path6.resolve(options.dir) : process.cwd();
|
|
1282
|
+
const lockPath = path6.join(projectDir, "githat.lock");
|
|
1283
|
+
if (!fs6.existsSync(lockPath)) {
|
|
1284
|
+
console.log(chalk7.yellow("\nNo skills installed in this project."));
|
|
1285
|
+
console.log(chalk7.dim("\nTo install a skill:"));
|
|
1286
|
+
console.log(chalk7.dim(" githat skills install <slug>"));
|
|
1200
1287
|
return;
|
|
1201
1288
|
}
|
|
1202
1289
|
let lock;
|
|
1203
1290
|
try {
|
|
1204
|
-
lock = JSON.parse(
|
|
1291
|
+
lock = JSON.parse(fs6.readFileSync(lockPath, "utf-8"));
|
|
1205
1292
|
} catch {
|
|
1206
|
-
console.error(
|
|
1293
|
+
console.error(chalk7.red("Error: Invalid githat.lock file"));
|
|
1207
1294
|
process.exit(1);
|
|
1208
1295
|
}
|
|
1209
1296
|
const entries = Object.entries(lock);
|
|
1210
1297
|
if (entries.length === 0) {
|
|
1211
|
-
console.log(
|
|
1298
|
+
console.log(chalk7.yellow("\nNo skills installed in this project."));
|
|
1212
1299
|
return;
|
|
1213
1300
|
}
|
|
1214
|
-
console.log(
|
|
1301
|
+
console.log(chalk7.cyan(`
|
|
1215
1302
|
\u{1F4E6} Installed skills (${entries.length}):
|
|
1216
1303
|
`));
|
|
1217
|
-
console.log(
|
|
1218
|
-
console.log(
|
|
1304
|
+
console.log(chalk7.dim(`${"SKILL".padEnd(30)} ${"VERSION".padEnd(12)} INSTALLED`));
|
|
1305
|
+
console.log(chalk7.dim("\u2500".repeat(60)));
|
|
1219
1306
|
for (const [slug, entry] of entries) {
|
|
1220
1307
|
const date = new Date(entry.installedAt).toLocaleDateString();
|
|
1221
|
-
console.log(`${
|
|
1308
|
+
console.log(`${chalk7.bold(slug.padEnd(30))} ${entry.version.padEnd(12)} ${chalk7.dim(date)}`);
|
|
1222
1309
|
}
|
|
1223
|
-
console.log(
|
|
1224
|
-
console.log(
|
|
1225
|
-
console.log(
|
|
1310
|
+
console.log(chalk7.dim("\u2500".repeat(60)));
|
|
1311
|
+
console.log(chalk7.dim("\nTo update a skill:"));
|
|
1312
|
+
console.log(chalk7.dim(" githat skills install <slug> --version <new-version>"));
|
|
1226
1313
|
} catch (err) {
|
|
1227
|
-
console.error(
|
|
1314
|
+
console.error(chalk7.red(`Error: ${err.message}`));
|
|
1228
1315
|
process.exit(1);
|
|
1229
1316
|
}
|
|
1230
1317
|
});
|
|
1231
1318
|
|
|
1232
1319
|
// src/commands/skills/init.ts
|
|
1233
1320
|
import { Command as Command5 } from "commander";
|
|
1234
|
-
import
|
|
1235
|
-
import * as
|
|
1236
|
-
import * as
|
|
1237
|
-
import * as
|
|
1321
|
+
import chalk8 from "chalk";
|
|
1322
|
+
import * as fs7 from "fs";
|
|
1323
|
+
import * as path7 from "path";
|
|
1324
|
+
import * as p11 from "@clack/prompts";
|
|
1238
1325
|
var SKILL_TYPES = ["template", "integration", "ui", "ai", "workflow"];
|
|
1239
1326
|
function generateReadme(manifest) {
|
|
1240
1327
|
return `# ${manifest.name}
|
|
@@ -1366,23 +1453,23 @@ function toPascalCase(str) {
|
|
|
1366
1453
|
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
1454
|
try {
|
|
1368
1455
|
if (!/^[a-z][a-z0-9-]{1,62}[a-z0-9]$/.test(name)) {
|
|
1369
|
-
console.error(
|
|
1456
|
+
console.error(chalk8.red("Error: Name must be lowercase alphanumeric with hyphens (2-64 chars, start with letter)"));
|
|
1370
1457
|
process.exit(1);
|
|
1371
1458
|
}
|
|
1372
|
-
const parentDir = options.dir ?
|
|
1373
|
-
const skillDir =
|
|
1374
|
-
if (
|
|
1375
|
-
console.error(
|
|
1459
|
+
const parentDir = options.dir ? path7.resolve(options.dir) : process.cwd();
|
|
1460
|
+
const skillDir = path7.join(parentDir, name);
|
|
1461
|
+
if (fs7.existsSync(skillDir)) {
|
|
1462
|
+
console.error(chalk8.red(`Error: Directory "${name}" already exists`));
|
|
1376
1463
|
process.exit(1);
|
|
1377
1464
|
}
|
|
1378
|
-
console.log(
|
|
1465
|
+
console.log(chalk8.cyan(`
|
|
1379
1466
|
\u{1F4E6} Initializing skill: ${name}
|
|
1380
1467
|
`));
|
|
1381
1468
|
let type;
|
|
1382
1469
|
if (options.type && SKILL_TYPES.includes(options.type)) {
|
|
1383
1470
|
type = options.type;
|
|
1384
1471
|
} else {
|
|
1385
|
-
const result = await
|
|
1472
|
+
const result = await p11.select({
|
|
1386
1473
|
message: "What type of skill are you creating?",
|
|
1387
1474
|
options: [
|
|
1388
1475
|
{ value: "integration", label: "Integration", hint: "Connect to external services (Stripe, SendGrid, etc.)" },
|
|
@@ -1392,19 +1479,19 @@ var initCommand = new Command5("init").description("Initialize a new skill packa
|
|
|
1392
1479
|
{ value: "workflow", label: "Workflow", hint: "Automation recipes" }
|
|
1393
1480
|
]
|
|
1394
1481
|
});
|
|
1395
|
-
if (
|
|
1396
|
-
|
|
1482
|
+
if (p11.isCancel(result)) {
|
|
1483
|
+
p11.cancel("Operation cancelled");
|
|
1397
1484
|
process.exit(0);
|
|
1398
1485
|
}
|
|
1399
1486
|
type = result;
|
|
1400
1487
|
}
|
|
1401
|
-
const description = await
|
|
1488
|
+
const description = await p11.text({
|
|
1402
1489
|
message: "Short description:",
|
|
1403
1490
|
placeholder: `A ${type} skill for...`,
|
|
1404
1491
|
validate: (v) => v.length < 10 ? "Description must be at least 10 characters" : void 0
|
|
1405
1492
|
});
|
|
1406
|
-
if (
|
|
1407
|
-
|
|
1493
|
+
if (p11.isCancel(description)) {
|
|
1494
|
+
p11.cancel("Operation cancelled");
|
|
1408
1495
|
process.exit(0);
|
|
1409
1496
|
}
|
|
1410
1497
|
const manifest = {
|
|
@@ -1429,22 +1516,22 @@ var initCommand = new Command5("init").description("Initialize a new skill packa
|
|
|
1429
1516
|
},
|
|
1430
1517
|
keywords: [type]
|
|
1431
1518
|
};
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1519
|
+
fs7.mkdirSync(skillDir, { recursive: true });
|
|
1520
|
+
fs7.mkdirSync(path7.join(skillDir, "src"), { recursive: true });
|
|
1521
|
+
fs7.writeFileSync(
|
|
1522
|
+
path7.join(skillDir, "githat-skill.json"),
|
|
1436
1523
|
JSON.stringify(manifest, null, 2)
|
|
1437
1524
|
);
|
|
1438
|
-
|
|
1439
|
-
|
|
1525
|
+
fs7.writeFileSync(
|
|
1526
|
+
path7.join(skillDir, "README.md"),
|
|
1440
1527
|
generateReadme(manifest)
|
|
1441
1528
|
);
|
|
1442
|
-
|
|
1443
|
-
|
|
1529
|
+
fs7.writeFileSync(
|
|
1530
|
+
path7.join(skillDir, "src", "index.ts"),
|
|
1444
1531
|
generateIndexFile(manifest)
|
|
1445
1532
|
);
|
|
1446
|
-
|
|
1447
|
-
|
|
1533
|
+
fs7.writeFileSync(
|
|
1534
|
+
path7.join(skillDir, ".gitignore"),
|
|
1448
1535
|
`node_modules/
|
|
1449
1536
|
dist/
|
|
1450
1537
|
.env
|
|
@@ -1452,21 +1539,21 @@ dist/
|
|
|
1452
1539
|
*.log
|
|
1453
1540
|
`
|
|
1454
1541
|
);
|
|
1455
|
-
console.log(
|
|
1542
|
+
console.log(chalk8.green(`
|
|
1456
1543
|
\u2705 Created skill at ${skillDir}
|
|
1457
1544
|
`));
|
|
1458
|
-
console.log(
|
|
1459
|
-
console.log(
|
|
1460
|
-
console.log(
|
|
1461
|
-
console.log(
|
|
1462
|
-
console.log(
|
|
1463
|
-
console.log(
|
|
1464
|
-
console.log(
|
|
1465
|
-
console.log(
|
|
1466
|
-
console.log(
|
|
1467
|
-
console.log(
|
|
1545
|
+
console.log(chalk8.dim("Files created:"));
|
|
1546
|
+
console.log(chalk8.dim(` githat-skill.json - Skill manifest`));
|
|
1547
|
+
console.log(chalk8.dim(` README.md - Documentation`));
|
|
1548
|
+
console.log(chalk8.dim(` src/index.ts - Main entry point`));
|
|
1549
|
+
console.log(chalk8.dim(` .gitignore - Git ignore rules`));
|
|
1550
|
+
console.log(chalk8.dim("\nNext steps:"));
|
|
1551
|
+
console.log(chalk8.dim(` 1. cd ${name}`));
|
|
1552
|
+
console.log(chalk8.dim(` 2. Edit githat-skill.json with your details`));
|
|
1553
|
+
console.log(chalk8.dim(` 3. Implement your skill in src/index.ts`));
|
|
1554
|
+
console.log(chalk8.dim(` 4. Publish: githat skills publish .`));
|
|
1468
1555
|
} catch (err) {
|
|
1469
|
-
console.error(
|
|
1556
|
+
console.error(chalk8.red(`Error: ${err.message}`));
|
|
1470
1557
|
process.exit(1);
|
|
1471
1558
|
}
|
|
1472
1559
|
});
|
|
@@ -1474,7 +1561,7 @@ dist/
|
|
|
1474
1561
|
// src/commands/skills/index.ts
|
|
1475
1562
|
var skillsCommand = new Command6("skills").description("Manage GitHat skills marketplace").addCommand(searchCommand).addCommand(listCommand).addCommand(installCommand).addCommand(installedCommand).addCommand(initCommand);
|
|
1476
1563
|
skillsCommand.action(() => {
|
|
1477
|
-
console.log(
|
|
1564
|
+
console.log(chalk9.cyan("\n\u{1F4E6} GitHat Skills Marketplace\n"));
|
|
1478
1565
|
console.log("Commands:");
|
|
1479
1566
|
console.log(" search <query> Search skills by keyword");
|
|
1480
1567
|
console.log(" list List skills (filterable by type)");
|
|
@@ -1497,7 +1584,7 @@ program.command("create [project-name]", { isDefault: true }).description("Scaff
|
|
|
1497
1584
|
displayBanner();
|
|
1498
1585
|
const typescript = opts.js ? false : opts.ts ? true : void 0;
|
|
1499
1586
|
if (opts.yes && !projectName) {
|
|
1500
|
-
|
|
1587
|
+
p12.cancel(chalk10.red("Project name is required when using --yes flag"));
|
|
1501
1588
|
process.exit(1);
|
|
1502
1589
|
}
|
|
1503
1590
|
const answers = await runPrompts({
|
|
@@ -1515,7 +1602,7 @@ program.command("create [project-name]", { isDefault: true }).description("Scaff
|
|
|
1515
1602
|
skipPrompts: opts.yes
|
|
1516
1603
|
});
|
|
1517
1604
|
} catch (err) {
|
|
1518
|
-
|
|
1605
|
+
p12.cancel(chalk10.red(err.message || "Something went wrong."));
|
|
1519
1606
|
process.exit(1);
|
|
1520
1607
|
}
|
|
1521
1608
|
});
|