diff --git a/apps/admin/actions/check-repository.ts b/apps/admin/actions/check-repository.ts
new file mode 100644
index 00000000..47c1e6dc
--- /dev/null
+++ b/apps/admin/actions/check-repository.ts
@@ -0,0 +1,21 @@
+"use server"
+
+import { z } from "zod"
+import { getRepoOwnerAndName } from "~/lib/repositories"
+import { authedProcedure } from "~/lib/safe-actions"
+import { GitHubTechStackAnalyzer } from "~/lib/tech-stack-analyzer"
+import { prisma } from "~/services/prisma"
+
+export const analyzeRepository = authedProcedure
+ .createServerAction()
+ .input(z.object({ id: z.string() }))
+ .handler(async ({ input: { id } }) => {
+ const { repository } = await prisma.tool.findUniqueOrThrow({ where: { id } })
+ const repo = getRepoOwnerAndName(repository)
+
+ if (!repo) return null
+
+ const github = new GitHubTechStackAnalyzer()
+ const analysis = await github.analyzeTechStack(repo.owner, repo.name)
+ return analysis
+ })
diff --git a/apps/admin/app/(dashboard)/tools/_components/tool-actions.tsx b/apps/admin/app/(dashboard)/tools/_components/tool-actions.tsx
index c655a161..a1f21c4e 100644
--- a/apps/admin/app/(dashboard)/tools/_components/tool-actions.tsx
+++ b/apps/admin/app/(dashboard)/tools/_components/tool-actions.tsx
@@ -8,6 +8,7 @@ import type React from "react"
import { useState } from "react"
import { toast } from "sonner"
import { useServerAction } from "zsa-react"
+import { analyzeRepository } from "~/actions/check-repository"
import { ToolScheduleDialog } from "~/app/(dashboard)/tools/_components/tool-schedule-dialog"
import { ToolsDeleteDialog } from "~/app/(dashboard)/tools/_components/tools-delete-dialog"
import { reuploadToolAssets } from "~/app/(dashboard)/tools/_lib/actions"
@@ -42,6 +43,17 @@ export const ToolActions = ({ tool, row, className, ...props }: ToolActionsProps
},
})
+ const { execute: analyzeRepositoryAction } = useServerAction(analyzeRepository, {
+ onSuccess: ({ data }) => {
+ console.log(data)
+ toast.success("Repository analyzed")
+ },
+
+ onError: ({ err }) => {
+ toast.error(err.message)
+ },
+ })
+
const handleDialogSuccess = () => {
setShowDeleteDialog(false)
row?.toggleSelected(false)
@@ -103,6 +115,10 @@ export const ToolActions = ({ tool, row, className, ...props }: ToolActionsProps
Reupload Assets
+ analyzeRepositoryAction({ id: tool.id })}>
+ Analyze Repository
+
+
diff --git a/apps/admin/lib/tech-stack-analyzer.ts b/apps/admin/lib/tech-stack-analyzer.ts
new file mode 100644
index 00000000..af160c57
--- /dev/null
+++ b/apps/admin/lib/tech-stack-analyzer.ts
@@ -0,0 +1,443 @@
+import type { graphql } from "@octokit/graphql/types"
+import { githubClient } from "~/services/github"
+
+interface TechStackSignature {
+ required_files: string[]
+ optional_files: string[]
+ dependencies: string[]
+ files_content: string[]
+ topics?: string[]
+}
+
+interface FileNode {
+ path: string
+ type: string
+ object?: {
+ text?: string
+ }
+}
+
+interface RepositoryContent {
+ repository: {
+ object: {
+ tree: {
+ entries: FileNode[]
+ }
+ }
+ languages: {
+ edges: {
+ node: {
+ name: string
+ }
+ size: number
+ }[]
+ }
+ dependencyGraphManifests: {
+ nodes: {
+ dependencies: {
+ nodes: {
+ packageName: string
+ }[]
+ }
+ }[]
+ }
+ repositoryTopics: {
+ nodes: {
+ topic: {
+ name: string
+ }
+ }[]
+ }
+ }
+}
+
+interface AnalysisResult {
+ repository: string
+ detected_stacks: string[]
+ primary_languages: string[]
+ confidence_scores: Record
+ matched_topics: Record
+}
+
+export class GitHubTechStackAnalyzer {
+ private client: graphql
+ private tech_stacks: Record
+
+ constructor() {
+ this.client = githubClient
+
+ this.tech_stacks = {
+ React: {
+ required_files: ["package.json"],
+ optional_files: [
+ "src/App.tsx",
+ "src/App.jsx",
+ "src/app.tsx",
+ "src/app.jsx",
+ "tsconfig.json",
+ ],
+ dependencies: ["react", "react-dom", "@types/react"],
+ files_content: ["import React", 'from "react"', "React.Component", "useState", "useEffect"],
+ topics: ["react", "reactjs", "react-components", "frontend"],
+ },
+ Vue: {
+ required_files: ["package.json"],
+ optional_files: ["src/App.vue", "src/app.vue", "vue.config.js", "vite.config.ts"],
+ dependencies: ["vue", "@vue/cli-service", "@vitejs/plugin-vue"],
+ files_content: ["createApp", "defineComponent", "", "