create-githat-app 0.4.1 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +611 -19
- package/package.json +26 -8
package/dist/cli.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { createRequire } from 'module'; const require = createRequire(import.meta.url);
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { Command } from "commander";
|
|
5
|
-
import * as
|
|
6
|
-
import
|
|
4
|
+
import { Command as Command7 } from "commander";
|
|
5
|
+
import * as p9 from "@clack/prompts";
|
|
6
|
+
import chalk9 from "chalk";
|
|
7
7
|
|
|
8
8
|
// src/utils/ascii.ts
|
|
9
9
|
import figlet from "figlet";
|
|
@@ -11,7 +11,7 @@ import gradient from "gradient-string";
|
|
|
11
11
|
import chalk from "chalk";
|
|
12
12
|
|
|
13
13
|
// src/constants.ts
|
|
14
|
-
var VERSION = "
|
|
14
|
+
var VERSION = "0.5.1";
|
|
15
15
|
var DEFAULT_API_URL = "https://api.githat.io";
|
|
16
16
|
var DASHBOARD_URL = "https://githat.io/dashboard/apps";
|
|
17
17
|
var BRAND_COLORS = ["#7c3aed", "#6366f1", "#8b5cf6"];
|
|
@@ -21,7 +21,7 @@ var DEPS = {
|
|
|
21
21
|
next: "^16.0.0",
|
|
22
22
|
react: "^19.0.0",
|
|
23
23
|
"react-dom": "^19.0.0",
|
|
24
|
-
"@githat/nextjs": "^0.
|
|
24
|
+
"@githat/nextjs": "^0.5.0"
|
|
25
25
|
},
|
|
26
26
|
devDependencies: {
|
|
27
27
|
typescript: "^5.9.0",
|
|
@@ -35,7 +35,7 @@ var DEPS = {
|
|
|
35
35
|
react: "^19.0.0",
|
|
36
36
|
"react-dom": "^19.0.0",
|
|
37
37
|
"react-router-dom": "^7.0.0",
|
|
38
|
-
"@githat/nextjs": "^0.
|
|
38
|
+
"@githat/nextjs": "^0.5.0"
|
|
39
39
|
},
|
|
40
40
|
devDependencies: {
|
|
41
41
|
vite: "^7.0.0",
|
|
@@ -123,7 +123,7 @@ function sectionHeader(title) {
|
|
|
123
123
|
console.log(dim(` \u2500\u2500\u2500 ${title} ${"\u2500".repeat(lineLen)}`));
|
|
124
124
|
console.log("");
|
|
125
125
|
}
|
|
126
|
-
function displaySuccess(projectName, packageManager, framework) {
|
|
126
|
+
function displaySuccess(projectName, packageManager, framework, hasPublishableKey = true) {
|
|
127
127
|
const devCmd = packageManager === "npm" ? "npm run dev" : `${packageManager} dev`;
|
|
128
128
|
const port = framework === "react-vite" ? "5173" : "3000";
|
|
129
129
|
console.log("");
|
|
@@ -140,6 +140,11 @@ function displaySuccess(projectName, packageManager, framework) {
|
|
|
140
140
|
`${violet("/sign-up")} Create account`,
|
|
141
141
|
`${violet("/dashboard")} Protected dashboard`,
|
|
142
142
|
"",
|
|
143
|
+
...hasPublishableKey ? [] : [
|
|
144
|
+
chalk.yellow("No key configured \u2014 auth works on localhost."),
|
|
145
|
+
`For production: ${violet("githat.io/dashboard/apps")}`,
|
|
146
|
+
""
|
|
147
|
+
],
|
|
143
148
|
dim("Docs \u2192 https://githat.io/docs/sdk")
|
|
144
149
|
]);
|
|
145
150
|
console.log("");
|
|
@@ -305,8 +310,8 @@ async function promptGitHat(existingKey) {
|
|
|
305
310
|
}
|
|
306
311
|
publishableKey = pastedKey || "";
|
|
307
312
|
} else if (connectChoice === "skip") {
|
|
308
|
-
p3.log.info("
|
|
309
|
-
p3.log.info("
|
|
313
|
+
p3.log.info("Auth works on localhost without a key (CORS bypass for development).");
|
|
314
|
+
p3.log.info("Sign up at githat.io \u2014 a publishable key is auto-created for you.");
|
|
310
315
|
}
|
|
311
316
|
}
|
|
312
317
|
const authFeatures = await p3.multiselect({
|
|
@@ -543,18 +548,18 @@ function sortKeys(obj) {
|
|
|
543
548
|
|
|
544
549
|
// src/utils/spinner.ts
|
|
545
550
|
import ora from "ora";
|
|
546
|
-
function createSpinner(
|
|
547
|
-
return ora({ text:
|
|
551
|
+
function createSpinner(text4) {
|
|
552
|
+
return ora({ text: text4, color: "magenta" });
|
|
548
553
|
}
|
|
549
|
-
async function withSpinner(
|
|
550
|
-
const spinner = createSpinner(
|
|
554
|
+
async function withSpinner(text4, fn, successText) {
|
|
555
|
+
const spinner = createSpinner(text4);
|
|
551
556
|
spinner.start();
|
|
552
557
|
try {
|
|
553
558
|
const result = await fn();
|
|
554
|
-
spinner.succeed(successText ||
|
|
559
|
+
spinner.succeed(successText || text4);
|
|
555
560
|
return result;
|
|
556
561
|
} catch (err) {
|
|
557
|
-
spinner.fail(
|
|
562
|
+
spinner.fail(text4);
|
|
558
563
|
throw err;
|
|
559
564
|
}
|
|
560
565
|
}
|
|
@@ -629,12 +634,598 @@ async function scaffold(context, options) {
|
|
|
629
634
|
);
|
|
630
635
|
}
|
|
631
636
|
p7.outro("Setup complete!");
|
|
632
|
-
displaySuccess(context.projectName, context.packageManager, context.framework);
|
|
637
|
+
displaySuccess(context.projectName, context.packageManager, context.framework, !!context.publishableKey);
|
|
638
|
+
const starPrompt = await p7.confirm({
|
|
639
|
+
message: "Star GitHat on GitHub? (helps us grow!)",
|
|
640
|
+
initialValue: false
|
|
641
|
+
});
|
|
642
|
+
if (!p7.isCancel(starPrompt) && starPrompt) {
|
|
643
|
+
try {
|
|
644
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
645
|
+
execSync3(`${cmd} "https://github.com/GitHat-IO/githat"`, { stdio: "ignore" });
|
|
646
|
+
} catch {
|
|
647
|
+
p7.log.info("Visit https://github.com/GitHat-IO/githat to star us!");
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// src/commands/skills/index.ts
|
|
653
|
+
import { Command as Command6 } from "commander";
|
|
654
|
+
import chalk8 from "chalk";
|
|
655
|
+
|
|
656
|
+
// src/commands/skills/search.ts
|
|
657
|
+
import { Command } from "commander";
|
|
658
|
+
import chalk3 from "chalk";
|
|
659
|
+
|
|
660
|
+
// src/commands/skills/api.ts
|
|
661
|
+
async function fetchApi(endpoint, options = {}) {
|
|
662
|
+
const url = `${DEFAULT_API_URL}${endpoint}`;
|
|
663
|
+
const response = await fetch(url, {
|
|
664
|
+
...options,
|
|
665
|
+
headers: {
|
|
666
|
+
"Content-Type": "application/json",
|
|
667
|
+
...options.headers
|
|
668
|
+
}
|
|
669
|
+
});
|
|
670
|
+
if (!response.ok) {
|
|
671
|
+
const error = await response.json().catch(() => ({ error: response.statusText }));
|
|
672
|
+
throw new Error(error.error || `API error: ${response.status}`);
|
|
673
|
+
}
|
|
674
|
+
return response.json();
|
|
675
|
+
}
|
|
676
|
+
async function searchSkills(query, type) {
|
|
677
|
+
const params = new URLSearchParams();
|
|
678
|
+
if (type) params.set("type", type);
|
|
679
|
+
const url = `/skills?${params.toString()}`;
|
|
680
|
+
const result = await fetchApi(url);
|
|
681
|
+
const q = query.toLowerCase();
|
|
682
|
+
return {
|
|
683
|
+
skills: result.skills.filter(
|
|
684
|
+
(s) => s.name.toLowerCase().includes(q) || s.description.toLowerCase().includes(q) || s.keywords.some((k) => k.toLowerCase().includes(q))
|
|
685
|
+
)
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
async function listSkills(options) {
|
|
689
|
+
const params = new URLSearchParams();
|
|
690
|
+
if (options.type) params.set("type", options.type);
|
|
691
|
+
if (options.limit) params.set("limit", options.limit.toString());
|
|
692
|
+
if (options.cursor) params.set("cursor", options.cursor);
|
|
693
|
+
return fetchApi(`/skills?${params.toString()}`);
|
|
694
|
+
}
|
|
695
|
+
async function getSkill(slug) {
|
|
696
|
+
return fetchApi(`/skills/${slug}`);
|
|
697
|
+
}
|
|
698
|
+
async function getDownloadUrl(slug, version) {
|
|
699
|
+
const params = version ? `?version=${version}` : "";
|
|
700
|
+
return fetchApi(`/skills/${slug}/download${params}`);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// src/commands/skills/search.ts
|
|
704
|
+
function formatSkill(skill) {
|
|
705
|
+
const typeColors = {
|
|
706
|
+
template: chalk3.blue,
|
|
707
|
+
integration: chalk3.green,
|
|
708
|
+
ui: chalk3.magenta,
|
|
709
|
+
ai: chalk3.yellow,
|
|
710
|
+
workflow: chalk3.cyan
|
|
711
|
+
};
|
|
712
|
+
const typeColor = typeColors[skill.type] || chalk3.white;
|
|
713
|
+
return [
|
|
714
|
+
`${chalk3.bold(skill.name)} ${chalk3.dim(`@${skill.latestVersion}`)}`,
|
|
715
|
+
` ${skill.description}`,
|
|
716
|
+
` ${typeColor(skill.type)} \xB7 \u2B07 ${skill.downloads} \xB7 \u2B50 ${skill.stars} \xB7 by ${skill.authorName}`,
|
|
717
|
+
` ${chalk3.dim(`githat skills install ${skill.slug}`)}`
|
|
718
|
+
].join("\n");
|
|
719
|
+
}
|
|
720
|
+
var searchCommand = new Command("search").description("Search skills by keyword").argument("<query>", "Search query").option("-t, --type <type>", "Filter by type (template, integration, ui, ai, workflow)").action(async (query, options) => {
|
|
721
|
+
try {
|
|
722
|
+
console.log(chalk3.dim(`
|
|
723
|
+
Searching for "${query}"...
|
|
724
|
+
`));
|
|
725
|
+
const result = await searchSkills(query, options.type);
|
|
726
|
+
if (result.skills.length === 0) {
|
|
727
|
+
console.log(chalk3.yellow("No skills found matching your query."));
|
|
728
|
+
console.log(chalk3.dim("\nTry a different search term or browse all skills:"));
|
|
729
|
+
console.log(chalk3.dim(" githat skills list"));
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
console.log(chalk3.cyan(`Found ${result.skills.length} skill(s):
|
|
733
|
+
`));
|
|
734
|
+
for (const skill of result.skills) {
|
|
735
|
+
console.log(formatSkill(skill));
|
|
736
|
+
console.log("");
|
|
737
|
+
}
|
|
738
|
+
} catch (err) {
|
|
739
|
+
console.error(chalk3.red(`Error: ${err.message}`));
|
|
740
|
+
process.exit(1);
|
|
741
|
+
}
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
// src/commands/skills/list.ts
|
|
745
|
+
import { Command as Command2 } from "commander";
|
|
746
|
+
import chalk4 from "chalk";
|
|
747
|
+
function formatSkillCompact(skill) {
|
|
748
|
+
const typeColors = {
|
|
749
|
+
template: chalk4.blue,
|
|
750
|
+
integration: chalk4.green,
|
|
751
|
+
ui: chalk4.magenta,
|
|
752
|
+
ai: chalk4.yellow,
|
|
753
|
+
workflow: chalk4.cyan
|
|
754
|
+
};
|
|
755
|
+
const typeColor = typeColors[skill.type] || chalk4.white;
|
|
756
|
+
const name = chalk4.bold(skill.name.padEnd(25));
|
|
757
|
+
const type = typeColor(skill.type.padEnd(12));
|
|
758
|
+
const stats = `\u2B07 ${String(skill.downloads).padStart(5)} \u2B50 ${String(skill.stars).padStart(4)}`;
|
|
759
|
+
const desc = skill.description.length > 40 ? skill.description.substring(0, 37) + "..." : skill.description;
|
|
760
|
+
return `${name} ${type} ${stats} ${chalk4.dim(desc)}`;
|
|
761
|
+
}
|
|
762
|
+
var listCommand = new Command2("list").description("List available skills").option("-t, --type <type>", "Filter by type (template, integration, ui, ai, workflow)").option("-l, --limit <n>", "Number of results (default: 25)", "25").action(async (options) => {
|
|
763
|
+
try {
|
|
764
|
+
const limit = parseInt(options.limit, 10);
|
|
765
|
+
console.log(chalk4.dim("\nFetching skills...\n"));
|
|
766
|
+
const result = await listSkills({ type: options.type, limit });
|
|
767
|
+
if (result.skills.length === 0) {
|
|
768
|
+
console.log(chalk4.yellow("No skills found."));
|
|
769
|
+
if (options.type) {
|
|
770
|
+
console.log(chalk4.dim(`
|
|
771
|
+
Try without the type filter:`));
|
|
772
|
+
console.log(chalk4.dim(" githat skills list"));
|
|
773
|
+
}
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
const header = `${"NAME".padEnd(25)} ${"TYPE".padEnd(12)} ${"DOWNLOADS".padStart(10)} DESCRIPTION`;
|
|
777
|
+
console.log(chalk4.dim(header));
|
|
778
|
+
console.log(chalk4.dim("\u2500".repeat(80)));
|
|
779
|
+
for (const skill of result.skills) {
|
|
780
|
+
console.log(formatSkillCompact(skill));
|
|
781
|
+
}
|
|
782
|
+
console.log(chalk4.dim("\u2500".repeat(80)));
|
|
783
|
+
console.log(chalk4.dim(`Showing ${result.skills.length} skill(s)`));
|
|
784
|
+
if (result.nextCursor) {
|
|
785
|
+
console.log(chalk4.dim("\nMore results available. Use --limit to see more."));
|
|
786
|
+
}
|
|
787
|
+
console.log(chalk4.dim("\nTo install: githat skills install <name>"));
|
|
788
|
+
} catch (err) {
|
|
789
|
+
console.error(chalk4.red(`Error: ${err.message}`));
|
|
790
|
+
process.exit(1);
|
|
791
|
+
}
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
// src/commands/skills/install.ts
|
|
795
|
+
import { Command as Command3 } from "commander";
|
|
796
|
+
import chalk5 from "chalk";
|
|
797
|
+
import * as fs4 from "fs";
|
|
798
|
+
import * as path4 from "path";
|
|
799
|
+
import { pipeline } from "stream/promises";
|
|
800
|
+
import { createWriteStream, mkdirSync } from "fs";
|
|
801
|
+
import { Extract } from "unzipper";
|
|
802
|
+
async function downloadAndExtract(url, destDir) {
|
|
803
|
+
const response = await fetch(url);
|
|
804
|
+
if (!response.ok) {
|
|
805
|
+
throw new Error(`Download failed: ${response.statusText}`);
|
|
806
|
+
}
|
|
807
|
+
const tempZip = path4.join(destDir, ".skill-download.zip");
|
|
808
|
+
const fileStream = createWriteStream(tempZip);
|
|
809
|
+
await pipeline(response.body, fileStream);
|
|
810
|
+
await new Promise((resolve4, reject) => {
|
|
811
|
+
fs4.createReadStream(tempZip).pipe(Extract({ path: destDir })).on("close", resolve4).on("error", reject);
|
|
812
|
+
});
|
|
813
|
+
fs4.unlinkSync(tempZip);
|
|
814
|
+
}
|
|
815
|
+
function updateGithatLock(projectDir, skill) {
|
|
816
|
+
const lockPath = path4.join(projectDir, "githat.lock");
|
|
817
|
+
let lock = {};
|
|
818
|
+
if (fs4.existsSync(lockPath)) {
|
|
819
|
+
try {
|
|
820
|
+
lock = JSON.parse(fs4.readFileSync(lockPath, "utf-8"));
|
|
821
|
+
} catch {
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
lock[skill.slug] = {
|
|
825
|
+
version: skill.version,
|
|
826
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
827
|
+
};
|
|
828
|
+
fs4.writeFileSync(lockPath, JSON.stringify(lock, null, 2));
|
|
829
|
+
}
|
|
830
|
+
function updateEnvExample(projectDir, manifest) {
|
|
831
|
+
if (!manifest.requires?.env?.length) return;
|
|
832
|
+
const envPath = path4.join(projectDir, ".env.local");
|
|
833
|
+
const envExamplePath = path4.join(projectDir, ".env.example");
|
|
834
|
+
let envContent = "";
|
|
835
|
+
if (fs4.existsSync(envPath)) {
|
|
836
|
+
envContent = fs4.readFileSync(envPath, "utf-8");
|
|
837
|
+
} else if (fs4.existsSync(envExamplePath)) {
|
|
838
|
+
envContent = fs4.readFileSync(envExamplePath, "utf-8");
|
|
839
|
+
}
|
|
840
|
+
const existingVars = new Set(
|
|
841
|
+
envContent.split("\n").filter((line) => line.includes("=")).map((line) => line.split("=")[0].trim())
|
|
842
|
+
);
|
|
843
|
+
const newVars = [];
|
|
844
|
+
for (const envVar of manifest.requires.env) {
|
|
845
|
+
if (!existingVars.has(envVar)) {
|
|
846
|
+
newVars.push(`${envVar}=`);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
if (newVars.length > 0) {
|
|
850
|
+
const addition = `
|
|
851
|
+
# Added by skill install
|
|
852
|
+
${newVars.join("\n")}
|
|
853
|
+
`;
|
|
854
|
+
if (fs4.existsSync(envPath)) {
|
|
855
|
+
fs4.appendFileSync(envPath, addition);
|
|
856
|
+
} else {
|
|
857
|
+
fs4.writeFileSync(envPath, `# Environment variables
|
|
858
|
+
${newVars.join("\n")}
|
|
859
|
+
`);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
var installCommand = new Command3("install").description("Install a skill to your project").argument("<slug>", "Skill slug (e.g., stripe-billing)").option("-v, --version <version>", "Specific version to install").option("-d, --dir <dir>", "Project directory (default: current directory)").action(async (slug, options) => {
|
|
864
|
+
try {
|
|
865
|
+
const projectDir = options.dir ? path4.resolve(options.dir) : process.cwd();
|
|
866
|
+
const packageJsonPath = path4.join(projectDir, "package.json");
|
|
867
|
+
if (!fs4.existsSync(packageJsonPath)) {
|
|
868
|
+
console.error(chalk5.red("Error: No package.json found. Are you in a project directory?"));
|
|
869
|
+
process.exit(1);
|
|
870
|
+
}
|
|
871
|
+
console.log(chalk5.dim(`
|
|
872
|
+
Fetching skill info for "${slug}"...
|
|
873
|
+
`));
|
|
874
|
+
const skill = await getSkill(slug);
|
|
875
|
+
console.log(chalk5.cyan(`\u{1F4E6} ${skill.name}`));
|
|
876
|
+
console.log(chalk5.dim(` ${skill.description}`));
|
|
877
|
+
console.log(chalk5.dim(` Type: ${skill.type} \xB7 Author: ${skill.authorName}
|
|
878
|
+
`));
|
|
879
|
+
const download = await getDownloadUrl(slug, options.version);
|
|
880
|
+
const version = download.version.version;
|
|
881
|
+
console.log(chalk5.dim(`Downloading ${skill.name}@${version}...`));
|
|
882
|
+
const skillDir = path4.join(projectDir, "githat", "skills", slug);
|
|
883
|
+
mkdirSync(skillDir, { recursive: true });
|
|
884
|
+
await downloadAndExtract(download.downloadUrl, skillDir);
|
|
885
|
+
console.log(chalk5.green(`\u2713 Downloaded to ${path4.relative(projectDir, skillDir)}`));
|
|
886
|
+
const manifestPath = path4.join(skillDir, "githat-skill.json");
|
|
887
|
+
if (fs4.existsSync(manifestPath)) {
|
|
888
|
+
const manifest = JSON.parse(fs4.readFileSync(manifestPath, "utf-8"));
|
|
889
|
+
updateEnvExample(projectDir, manifest);
|
|
890
|
+
if (manifest.requires?.env?.length) {
|
|
891
|
+
console.log(chalk5.yellow(`
|
|
892
|
+
\u26A0 Required environment variables:`));
|
|
893
|
+
for (const envVar of manifest.requires.env) {
|
|
894
|
+
console.log(chalk5.dim(` ${envVar}`));
|
|
895
|
+
}
|
|
896
|
+
console.log(chalk5.dim(`
|
|
897
|
+
Add these to your .env.local file`));
|
|
898
|
+
}
|
|
899
|
+
if (manifest.install?.dependencies) {
|
|
900
|
+
const deps = Object.entries(manifest.install.dependencies).map(([name, ver]) => `${name}@${ver}`).join(" ");
|
|
901
|
+
console.log(chalk5.yellow(`
|
|
902
|
+
\u26A0 Install npm dependencies:`));
|
|
903
|
+
console.log(chalk5.dim(` npm install ${deps}`));
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
updateGithatLock(projectDir, { slug, version });
|
|
907
|
+
console.log(chalk5.green(`
|
|
908
|
+
\u2705 Successfully installed ${skill.name}@${version}
|
|
909
|
+
`));
|
|
910
|
+
console.log(chalk5.dim("Next steps:"));
|
|
911
|
+
console.log(chalk5.dim(` 1. Check githat/skills/${slug}/README.md for usage`));
|
|
912
|
+
console.log(chalk5.dim(" 2. Add required environment variables to .env.local"));
|
|
913
|
+
console.log(chalk5.dim(" 3. Import and use the skill in your code"));
|
|
914
|
+
} catch (err) {
|
|
915
|
+
console.error(chalk5.red(`Error: ${err.message}`));
|
|
916
|
+
process.exit(1);
|
|
917
|
+
}
|
|
918
|
+
});
|
|
919
|
+
|
|
920
|
+
// src/commands/skills/installed.ts
|
|
921
|
+
import { Command as Command4 } from "commander";
|
|
922
|
+
import chalk6 from "chalk";
|
|
923
|
+
import * as fs5 from "fs";
|
|
924
|
+
import * as path5 from "path";
|
|
925
|
+
var installedCommand = new Command4("installed").alias("ls").description("List installed skills in current project").option("-d, --dir <dir>", "Project directory (default: current directory)").action(async (options) => {
|
|
926
|
+
try {
|
|
927
|
+
const projectDir = options.dir ? path5.resolve(options.dir) : process.cwd();
|
|
928
|
+
const lockPath = path5.join(projectDir, "githat.lock");
|
|
929
|
+
if (!fs5.existsSync(lockPath)) {
|
|
930
|
+
console.log(chalk6.yellow("\nNo skills installed in this project."));
|
|
931
|
+
console.log(chalk6.dim("\nTo install a skill:"));
|
|
932
|
+
console.log(chalk6.dim(" githat skills install <slug>"));
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
let lock;
|
|
936
|
+
try {
|
|
937
|
+
lock = JSON.parse(fs5.readFileSync(lockPath, "utf-8"));
|
|
938
|
+
} catch {
|
|
939
|
+
console.error(chalk6.red("Error: Invalid githat.lock file"));
|
|
940
|
+
process.exit(1);
|
|
941
|
+
}
|
|
942
|
+
const entries = Object.entries(lock);
|
|
943
|
+
if (entries.length === 0) {
|
|
944
|
+
console.log(chalk6.yellow("\nNo skills installed in this project."));
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
947
|
+
console.log(chalk6.cyan(`
|
|
948
|
+
\u{1F4E6} Installed skills (${entries.length}):
|
|
949
|
+
`));
|
|
950
|
+
console.log(chalk6.dim(`${"SKILL".padEnd(30)} ${"VERSION".padEnd(12)} INSTALLED`));
|
|
951
|
+
console.log(chalk6.dim("\u2500".repeat(60)));
|
|
952
|
+
for (const [slug, entry] of entries) {
|
|
953
|
+
const date = new Date(entry.installedAt).toLocaleDateString();
|
|
954
|
+
console.log(`${chalk6.bold(slug.padEnd(30))} ${entry.version.padEnd(12)} ${chalk6.dim(date)}`);
|
|
955
|
+
}
|
|
956
|
+
console.log(chalk6.dim("\u2500".repeat(60)));
|
|
957
|
+
console.log(chalk6.dim("\nTo update a skill:"));
|
|
958
|
+
console.log(chalk6.dim(" githat skills install <slug> --version <new-version>"));
|
|
959
|
+
} catch (err) {
|
|
960
|
+
console.error(chalk6.red(`Error: ${err.message}`));
|
|
961
|
+
process.exit(1);
|
|
962
|
+
}
|
|
963
|
+
});
|
|
964
|
+
|
|
965
|
+
// src/commands/skills/init.ts
|
|
966
|
+
import { Command as Command5 } from "commander";
|
|
967
|
+
import chalk7 from "chalk";
|
|
968
|
+
import * as fs6 from "fs";
|
|
969
|
+
import * as path6 from "path";
|
|
970
|
+
import * as p8 from "@clack/prompts";
|
|
971
|
+
var SKILL_TYPES = ["template", "integration", "ui", "ai", "workflow"];
|
|
972
|
+
function generateReadme(manifest) {
|
|
973
|
+
return `# ${manifest.name}
|
|
974
|
+
|
|
975
|
+
${manifest.description}
|
|
976
|
+
|
|
977
|
+
## Installation
|
|
978
|
+
|
|
979
|
+
\`\`\`bash
|
|
980
|
+
githat skills install ${manifest.name}
|
|
981
|
+
\`\`\`
|
|
982
|
+
|
|
983
|
+
## Requirements
|
|
984
|
+
|
|
985
|
+
${manifest.requires.env.length > 0 ? `
|
|
986
|
+
### Environment Variables
|
|
987
|
+
|
|
988
|
+
${manifest.requires.env.map((e) => `- \`${e}\``).join("\n")}
|
|
989
|
+
` : ""}
|
|
990
|
+
${manifest.requires.tier ? `
|
|
991
|
+
### Minimum Tier
|
|
992
|
+
|
|
993
|
+
This skill requires **${manifest.requires.tier}** tier or higher.
|
|
994
|
+
` : ""}
|
|
995
|
+
|
|
996
|
+
## Usage
|
|
997
|
+
|
|
998
|
+
\`\`\`typescript
|
|
999
|
+
// Import from the installed skill
|
|
1000
|
+
import { /* exports */ } from './githat/skills/${manifest.name}';
|
|
1001
|
+
|
|
1002
|
+
// Use the skill
|
|
1003
|
+
// ...
|
|
1004
|
+
\`\`\`
|
|
1005
|
+
|
|
1006
|
+
## License
|
|
1007
|
+
|
|
1008
|
+
${manifest.license}
|
|
1009
|
+
`;
|
|
1010
|
+
}
|
|
1011
|
+
function generateIndexFile(manifest) {
|
|
1012
|
+
const typeExports = {
|
|
1013
|
+
template: `// Template skill - provides project scaffolding
|
|
1014
|
+
export const templateName = '${manifest.name}';
|
|
1015
|
+
export const templateVersion = '${manifest.version}';
|
|
1016
|
+
|
|
1017
|
+
// Add your template exports here
|
|
1018
|
+
`,
|
|
1019
|
+
integration: `// Integration skill - connects to external services
|
|
1020
|
+
// Replace with your actual integration code
|
|
1021
|
+
|
|
1022
|
+
export interface ${toPascalCase(manifest.name)}Config {
|
|
1023
|
+
apiKey: string;
|
|
1024
|
+
// Add configuration options
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
export function create${toPascalCase(manifest.name)}(config: ${toPascalCase(manifest.name)}Config) {
|
|
1028
|
+
// Initialize your integration
|
|
1029
|
+
return {
|
|
1030
|
+
// Return your integration client/functions
|
|
1031
|
+
};
|
|
1032
|
+
}
|
|
1033
|
+
`,
|
|
1034
|
+
ui: `// UI skill - provides React components
|
|
1035
|
+
import React from 'react';
|
|
1036
|
+
|
|
1037
|
+
export interface ${toPascalCase(manifest.name)}Props {
|
|
1038
|
+
// Add component props
|
|
633
1039
|
}
|
|
634
1040
|
|
|
1041
|
+
export function ${toPascalCase(manifest.name)}({ ...props }: ${toPascalCase(manifest.name)}Props) {
|
|
1042
|
+
return (
|
|
1043
|
+
<div>
|
|
1044
|
+
{/* Your component */}
|
|
1045
|
+
</div>
|
|
1046
|
+
);
|
|
1047
|
+
}
|
|
1048
|
+
`,
|
|
1049
|
+
ai: `// AI skill - MCP server / Claude tools
|
|
1050
|
+
export interface Tool {
|
|
1051
|
+
name: string;
|
|
1052
|
+
description: string;
|
|
1053
|
+
inputSchema: Record<string, unknown>;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
export const tools: Tool[] = [
|
|
1057
|
+
{
|
|
1058
|
+
name: '${manifest.name.replace(/-/g, "_")}',
|
|
1059
|
+
description: '${manifest.description}',
|
|
1060
|
+
inputSchema: {
|
|
1061
|
+
type: 'object',
|
|
1062
|
+
properties: {
|
|
1063
|
+
// Add input properties
|
|
1064
|
+
},
|
|
1065
|
+
},
|
|
1066
|
+
},
|
|
1067
|
+
];
|
|
1068
|
+
|
|
1069
|
+
export async function handleTool(name: string, input: Record<string, unknown>) {
|
|
1070
|
+
// Handle tool invocations
|
|
1071
|
+
}
|
|
1072
|
+
`,
|
|
1073
|
+
workflow: `// Workflow skill - automation recipes
|
|
1074
|
+
export interface WorkflowTrigger {
|
|
1075
|
+
event: string;
|
|
1076
|
+
condition?: string;
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
export interface WorkflowStep {
|
|
1080
|
+
id: string;
|
|
1081
|
+
run?: string;
|
|
1082
|
+
action?: string;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
export const triggers: WorkflowTrigger[] = [
|
|
1086
|
+
{ event: 'deploy.success' },
|
|
1087
|
+
];
|
|
1088
|
+
|
|
1089
|
+
export const steps: WorkflowStep[] = [
|
|
1090
|
+
{ id: 'notify', run: 'echo "Workflow executed"' },
|
|
1091
|
+
];
|
|
1092
|
+
`
|
|
1093
|
+
};
|
|
1094
|
+
return typeExports[manifest.type];
|
|
1095
|
+
}
|
|
1096
|
+
function toPascalCase(str) {
|
|
1097
|
+
return str.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
|
|
1098
|
+
}
|
|
1099
|
+
var initCommand = new Command5("init").description("Initialize a new skill package").argument("<name>", "Skill name (slug format: lowercase-with-hyphens)").option("-t, --type <type>", "Skill type (template, integration, ui, ai, workflow)").option("-d, --dir <dir>", "Parent directory (default: current directory)").action(async (name, options) => {
|
|
1100
|
+
try {
|
|
1101
|
+
if (!/^[a-z][a-z0-9-]{1,62}[a-z0-9]$/.test(name)) {
|
|
1102
|
+
console.error(chalk7.red("Error: Name must be lowercase alphanumeric with hyphens (2-64 chars, start with letter)"));
|
|
1103
|
+
process.exit(1);
|
|
1104
|
+
}
|
|
1105
|
+
const parentDir = options.dir ? path6.resolve(options.dir) : process.cwd();
|
|
1106
|
+
const skillDir = path6.join(parentDir, name);
|
|
1107
|
+
if (fs6.existsSync(skillDir)) {
|
|
1108
|
+
console.error(chalk7.red(`Error: Directory "${name}" already exists`));
|
|
1109
|
+
process.exit(1);
|
|
1110
|
+
}
|
|
1111
|
+
console.log(chalk7.cyan(`
|
|
1112
|
+
\u{1F4E6} Initializing skill: ${name}
|
|
1113
|
+
`));
|
|
1114
|
+
let type;
|
|
1115
|
+
if (options.type && SKILL_TYPES.includes(options.type)) {
|
|
1116
|
+
type = options.type;
|
|
1117
|
+
} else {
|
|
1118
|
+
const result = await p8.select({
|
|
1119
|
+
message: "What type of skill are you creating?",
|
|
1120
|
+
options: [
|
|
1121
|
+
{ value: "integration", label: "Integration", hint: "Connect to external services (Stripe, SendGrid, etc.)" },
|
|
1122
|
+
{ value: "template", label: "Template", hint: "Full project scaffolding" },
|
|
1123
|
+
{ value: "ui", label: "UI Pack", hint: "Reusable React components" },
|
|
1124
|
+
{ value: "ai", label: "AI Skill", hint: "MCP server / Claude tools" },
|
|
1125
|
+
{ value: "workflow", label: "Workflow", hint: "Automation recipes" }
|
|
1126
|
+
]
|
|
1127
|
+
});
|
|
1128
|
+
if (p8.isCancel(result)) {
|
|
1129
|
+
p8.cancel("Operation cancelled");
|
|
1130
|
+
process.exit(0);
|
|
1131
|
+
}
|
|
1132
|
+
type = result;
|
|
1133
|
+
}
|
|
1134
|
+
const description = await p8.text({
|
|
1135
|
+
message: "Short description:",
|
|
1136
|
+
placeholder: `A ${type} skill for...`,
|
|
1137
|
+
validate: (v) => v.length < 10 ? "Description must be at least 10 characters" : void 0
|
|
1138
|
+
});
|
|
1139
|
+
if (p8.isCancel(description)) {
|
|
1140
|
+
p8.cancel("Operation cancelled");
|
|
1141
|
+
process.exit(0);
|
|
1142
|
+
}
|
|
1143
|
+
const manifest = {
|
|
1144
|
+
name,
|
|
1145
|
+
version: "1.0.0",
|
|
1146
|
+
description,
|
|
1147
|
+
type,
|
|
1148
|
+
author: {
|
|
1149
|
+
name: "Your Name",
|
|
1150
|
+
email: "your@email.com"
|
|
1151
|
+
},
|
|
1152
|
+
license: "MIT",
|
|
1153
|
+
requires: {
|
|
1154
|
+
env: []
|
|
1155
|
+
},
|
|
1156
|
+
files: {
|
|
1157
|
+
lib: "src/index.ts"
|
|
1158
|
+
},
|
|
1159
|
+
install: {
|
|
1160
|
+
dependencies: {},
|
|
1161
|
+
envExample: {}
|
|
1162
|
+
},
|
|
1163
|
+
keywords: [type]
|
|
1164
|
+
};
|
|
1165
|
+
fs6.mkdirSync(skillDir, { recursive: true });
|
|
1166
|
+
fs6.mkdirSync(path6.join(skillDir, "src"), { recursive: true });
|
|
1167
|
+
fs6.writeFileSync(
|
|
1168
|
+
path6.join(skillDir, "githat-skill.json"),
|
|
1169
|
+
JSON.stringify(manifest, null, 2)
|
|
1170
|
+
);
|
|
1171
|
+
fs6.writeFileSync(
|
|
1172
|
+
path6.join(skillDir, "README.md"),
|
|
1173
|
+
generateReadme(manifest)
|
|
1174
|
+
);
|
|
1175
|
+
fs6.writeFileSync(
|
|
1176
|
+
path6.join(skillDir, "src", "index.ts"),
|
|
1177
|
+
generateIndexFile(manifest)
|
|
1178
|
+
);
|
|
1179
|
+
fs6.writeFileSync(
|
|
1180
|
+
path6.join(skillDir, ".gitignore"),
|
|
1181
|
+
`node_modules/
|
|
1182
|
+
dist/
|
|
1183
|
+
.env
|
|
1184
|
+
.env.local
|
|
1185
|
+
*.log
|
|
1186
|
+
`
|
|
1187
|
+
);
|
|
1188
|
+
console.log(chalk7.green(`
|
|
1189
|
+
\u2705 Created skill at ${skillDir}
|
|
1190
|
+
`));
|
|
1191
|
+
console.log(chalk7.dim("Files created:"));
|
|
1192
|
+
console.log(chalk7.dim(` githat-skill.json - Skill manifest`));
|
|
1193
|
+
console.log(chalk7.dim(` README.md - Documentation`));
|
|
1194
|
+
console.log(chalk7.dim(` src/index.ts - Main entry point`));
|
|
1195
|
+
console.log(chalk7.dim(` .gitignore - Git ignore rules`));
|
|
1196
|
+
console.log(chalk7.dim("\nNext steps:"));
|
|
1197
|
+
console.log(chalk7.dim(` 1. cd ${name}`));
|
|
1198
|
+
console.log(chalk7.dim(` 2. Edit githat-skill.json with your details`));
|
|
1199
|
+
console.log(chalk7.dim(` 3. Implement your skill in src/index.ts`));
|
|
1200
|
+
console.log(chalk7.dim(` 4. Publish: githat skills publish .`));
|
|
1201
|
+
} catch (err) {
|
|
1202
|
+
console.error(chalk7.red(`Error: ${err.message}`));
|
|
1203
|
+
process.exit(1);
|
|
1204
|
+
}
|
|
1205
|
+
});
|
|
1206
|
+
|
|
1207
|
+
// src/commands/skills/index.ts
|
|
1208
|
+
var skillsCommand = new Command6("skills").description("Manage GitHat skills marketplace").addCommand(searchCommand).addCommand(listCommand).addCommand(installCommand).addCommand(installedCommand).addCommand(initCommand);
|
|
1209
|
+
skillsCommand.action(() => {
|
|
1210
|
+
console.log(chalk8.cyan("\n\u{1F4E6} GitHat Skills Marketplace\n"));
|
|
1211
|
+
console.log("Commands:");
|
|
1212
|
+
console.log(" search <query> Search skills by keyword");
|
|
1213
|
+
console.log(" list List skills (filterable by type)");
|
|
1214
|
+
console.log(" install <slug> Install a skill to your project");
|
|
1215
|
+
console.log(" installed List installed skills");
|
|
1216
|
+
console.log(" init <name> Initialize a new skill package");
|
|
1217
|
+
console.log("\nExamples:");
|
|
1218
|
+
console.log(" githat skills search stripe");
|
|
1219
|
+
console.log(" githat skills list --type=integration");
|
|
1220
|
+
console.log(" githat skills install stripe-billing");
|
|
1221
|
+
console.log(" githat skills init my-skill --type=integration");
|
|
1222
|
+
console.log("");
|
|
1223
|
+
});
|
|
1224
|
+
|
|
635
1225
|
// src/cli.ts
|
|
636
|
-
var program = new
|
|
637
|
-
program.name("
|
|
1226
|
+
var program = new Command7();
|
|
1227
|
+
program.name("githat").description("GitHat CLI - Scaffold apps and manage skills").version(VERSION);
|
|
1228
|
+
program.command("create [project-name]", { isDefault: true }).description("Scaffold a new GitHat app").option("--key <key>", "GitHat publishable key (pk_live_...)").option("--ts", "Use TypeScript (default)").option("--js", "Use JavaScript").action(async (projectName, opts) => {
|
|
638
1229
|
try {
|
|
639
1230
|
displayBanner();
|
|
640
1231
|
const typescript = opts.js ? false : opts.ts ? true : void 0;
|
|
@@ -649,8 +1240,9 @@ program.name("create-githat-app").description("Scaffold enterprise-grade apps wi
|
|
|
649
1240
|
initGit: answers.initGit
|
|
650
1241
|
});
|
|
651
1242
|
} catch (err) {
|
|
652
|
-
|
|
1243
|
+
p9.cancel(chalk9.red(err.message || "Something went wrong."));
|
|
653
1244
|
process.exit(1);
|
|
654
1245
|
}
|
|
655
1246
|
});
|
|
1247
|
+
program.addCommand(skillsCommand);
|
|
656
1248
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,35 +1,53 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-githat-app",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.5.1",
|
|
4
|
+
"description": "GitHat CLI — scaffold apps and manage the skills marketplace",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"create-githat-app": "./bin/index.js"
|
|
7
|
+
"create-githat-app": "./bin/index.js",
|
|
8
|
+
"githat": "./bin/index.js"
|
|
8
9
|
},
|
|
9
|
-
"files": [
|
|
10
|
+
"files": [
|
|
11
|
+
"bin",
|
|
12
|
+
"dist",
|
|
13
|
+
"templates"
|
|
14
|
+
],
|
|
10
15
|
"scripts": {
|
|
11
16
|
"build": "tsup",
|
|
12
17
|
"dev": "tsup --watch",
|
|
13
18
|
"test": "node bin/index.js --help"
|
|
14
19
|
},
|
|
15
20
|
"dependencies": {
|
|
16
|
-
"@clack/prompts": "^0.
|
|
21
|
+
"@clack/prompts": "^1.0.1",
|
|
17
22
|
"chalk": "^5.4.1",
|
|
18
|
-
"commander": "^
|
|
23
|
+
"commander": "^14.0.3",
|
|
19
24
|
"figlet": "^1.8.0",
|
|
20
25
|
"fs-extra": "^11.2.0",
|
|
21
26
|
"gradient-string": "^3.0.0",
|
|
22
27
|
"handlebars": "^4.7.8",
|
|
23
|
-
"ora": "^
|
|
28
|
+
"ora": "^9.3.0",
|
|
29
|
+
"unzipper": "^0.12.3"
|
|
24
30
|
},
|
|
25
31
|
"devDependencies": {
|
|
26
32
|
"@types/figlet": "^1.7.0",
|
|
27
33
|
"@types/fs-extra": "^11.0.4",
|
|
28
34
|
"@types/gradient-string": "^1.1.6",
|
|
35
|
+
"@types/unzipper": "^0.10.10",
|
|
29
36
|
"tsup": "^8.4.0",
|
|
30
37
|
"typescript": "^5.9.0"
|
|
31
38
|
},
|
|
32
|
-
"keywords": [
|
|
39
|
+
"keywords": [
|
|
40
|
+
"githat",
|
|
41
|
+
"nextjs",
|
|
42
|
+
"react",
|
|
43
|
+
"vite",
|
|
44
|
+
"create-app",
|
|
45
|
+
"cli",
|
|
46
|
+
"auth",
|
|
47
|
+
"identity",
|
|
48
|
+
"mcp",
|
|
49
|
+
"ai-agents"
|
|
50
|
+
],
|
|
33
51
|
"license": "SEE LICENSE IN LICENSE",
|
|
34
52
|
"publishConfig": {
|
|
35
53
|
"access": "public",
|