-
Notifications
You must be signed in to change notification settings - Fork 92
feat: Add changelog-generator AgentKit #86
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
8fd36bb
01391d7
e8e98e2
8ca173e
b276c3b
05c009d
b5b1e6c
e4f9260
767cfe8
c6d40f2
4ebcc94
c71bd9a
894406e
887e5a6
2e391c8
92b0276
b597a3b
4917a18
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| LAMATIC_PROJECT_ENDPOINT=YOUR_API_ENDPOINT | ||
| LAMATIC_FLOW_ID=YOUR_FLOW_ID | ||
| LAMATIC_PROJECT_ID=YOUR_PROJECT_ID | ||
| LAMATIC_PROJECT_API_KEY=YOUR_API_KEY |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. | ||
|
|
||
| # dependencies | ||
| /node_modules | ||
| /.pnp | ||
| .pnp.* | ||
| .yarn/* | ||
| !.yarn/patches | ||
| !.yarn/plugins | ||
| !.yarn/releases | ||
| !.yarn/versions | ||
|
|
||
| # testing | ||
| /coverage | ||
|
|
||
| # next.js | ||
| /.next/ | ||
| /out/ | ||
|
|
||
| # production | ||
| /build | ||
|
|
||
| # misc | ||
| .DS_Store | ||
| *.pem | ||
|
|
||
| # debug | ||
| npm-debug.log* | ||
| yarn-debug.log* | ||
| yarn-error.log* | ||
| .pnpm-debug.log* | ||
|
|
||
| # env files (can opt-in for committing if needed) | ||
| .env* | ||
| !.env.example | ||
|
|
||
| # vercel | ||
| .vercel | ||
|
|
||
| # typescript | ||
| *.tsbuildinfo | ||
| next-env.d.ts | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| # 📋 Changelog Generator — AgentKit | ||
|
|
||
| An AI-powered kit that generates professional, well-structured changelogs from GitHub repository information. Paste a repo URL and date range — get a complete changelog in seconds. | ||
|
|
||
|  | ||
|
|
||
| ## ✨ What It Does | ||
|
|
||
| - Takes a **GitHub repository URL** and a **date range** as input | ||
| - Generates a structured changelog with sections: | ||
| - 📋 Summary (plain English for non-technical stakeholders) | ||
| - 🚀 New Features | ||
| - 🐛 Bug Fixes | ||
| - 🔧 Improvements | ||
| - ⚠️ Breaking Changes | ||
| - 📦 Dependencies | ||
| - **One-click copy** of the full markdown output | ||
| - Powered by **Groq LLaMA** via Lamatic Flows | ||
|
|
||
| ## 🚀 Quick Deploy | ||
|
|
||
| [](https://vercel.com/new/clone?repository-url=https://github.com/Lamatic/AgentKit/tree/main/kits/automation/changelog-generator) | ||
|
|
||
| ## 📦 Prerequisites | ||
|
|
||
| - Node.js 18+ | ||
| - A [Lamatic.ai](https://lamatic.ai) account | ||
| - A [Groq](https://console.groq.com) API key (free) | ||
|
|
||
| ## ⚙️ Setup | ||
|
|
||
| ### 1. Clone and Install | ||
|
|
||
| ```bash | ||
| git clone https://github.com/Lamatic/AgentKit.git | ||
| cd AgentKit/kits/automation/changelog-generator | ||
| npm install | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ``` | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| ### 2. Set Up Lamatic Flow | ||
|
|
||
| 1. Sign in to [studio.lamatic.ai](https://studio.lamatic.ai) | ||
| 2. Create a new project → "Changelog Generator" | ||
| 3. Create a new Flow with: | ||
| - **Trigger node** with inputs: `repo_url`, `date_from`, `date_to` (all String) | ||
| - **Generate Text node** using Groq `llama-3.3-70b-versatile` with the prompt from `flows/changelog-flow/README.md` | ||
| 4. Deploy the flow | ||
| 5. Note your **Flow ID**, **API Key**, **Project ID**, and **API URL** | ||
|
|
||
| ### 3. Configure Environment Variables | ||
|
|
||
| ```bash | ||
| cp .env.example .env | ||
| ``` | ||
|
|
||
| Fill in your `.env`: | ||
|
|
||
| ```env | ||
| LAMATIC_FLOW_ID="your-flow-id" | ||
| LAMATIC_PROJECT_ENDPOINT="your-api-endpoint" | ||
| LAMATIC_PROJECT_ID="your-project-id" | ||
| LAMATIC_PROJECT_API_KEY="your-api-key" | ||
| ``` | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| ### 4. Run Locally | ||
|
|
||
| ```bash | ||
| npm run dev | ||
| ``` | ||
|
|
||
| Open [http://localhost:3000](http://localhost:3000) | ||
|
|
||
| ## 🌐 Deploy to Vercel | ||
|
|
||
| 1. Push your fork to GitHub | ||
| 2. Go to [vercel.com](https://vercel.com) → New Project → Import your fork | ||
| 3. Set **Root Directory** to `kits/automation/changelog-generator` | ||
| 4. Add all 4 environment variables | ||
| 5. Click Deploy | ||
|
|
||
| ## 📁 Project Structure | ||
|
|
||
| ``` | ||
| changelog-generator/ | ||
| ├── app/ | ||
| │ └── page.tsx # Main UI | ||
| ├── actions/ | ||
| │ └── orchestrate.ts # Lamatic Flow API call | ||
| ├── flows/ | ||
| │ └── changelog-flow/ # Exported Lamatic flow files | ||
| ├── .env.example # Environment variables template | ||
| ├── config.json # Kit metadata | ||
| └── README.md | ||
| ``` | ||
|
Comment on lines
+83
to
+94
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a language to the fenced structure block. This block is currently tripping the markdownlint warning that was attached to the review. 📝 Suggested doc fix-```
+```text
changelog-generator/
├── app/
│ └── page.tsx # Main UI
@@
├── config.json # Kit metadata
└── README.md🧰 Tools🪛 markdownlint-cli2 (0.21.0)[warning] 83-83: Fenced code blocks should have a language specified (MD040, fenced-code-language) |
||
|
|
||
| ## 🤝 Contributing | ||
|
|
||
| See [CONTRIBUTING.md](../../../CONTRIBUTING.md) for guidelines. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| "use server"; | ||
|
|
||
| import { Lamatic } from "lamatic"; | ||
|
|
||
| function createLamaticClient() { | ||
| const endpoint = process.env.LAMATIC_PROJECT_ENDPOINT; | ||
| const projectId = process.env.LAMATIC_PROJECT_ID; | ||
| const apiKey = process.env.LAMATIC_PROJECT_API_KEY; | ||
| if (!endpoint || !projectId || !apiKey) { | ||
| throw new Error("Missing Lamatic project configuration"); | ||
| } | ||
|
|
||
| return new Lamatic({ endpoint, projectId, apiKey }); | ||
| } | ||
|
|
||
| const lamaticClient = createLamaticClient(); | ||
|
|
||
| interface ChangelogInput { | ||
| repoUrl: string; | ||
| dateFrom: string; | ||
| dateTo: string; | ||
| } | ||
|
|
||
| export async function generateChangelog({ | ||
| repoUrl, | ||
| dateFrom, | ||
| dateTo, | ||
| }: ChangelogInput): Promise<string> { | ||
| const parsedRepoUrl = new URL(repoUrl); | ||
| const [owner, repo] = parsedRepoUrl.pathname.split("/").filter(Boolean); | ||
| if (parsedRepoUrl.hostname !== "github.com" || !owner || !repo) { | ||
| throw new Error("repoUrl must be a valid GitHub repository URL"); | ||
| } | ||
|
Comment on lines
+29
to
+33
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wrap URL parsing in try-catch to return a friendly error.
🛡️ Suggested fix- const parsedRepoUrl = new URL(repoUrl);
- const [owner, repo] = parsedRepoUrl.pathname.split("/").filter(Boolean);
- if (parsedRepoUrl.hostname !== "github.com" || !owner || !repo) {
+ let parsedRepoUrl: URL;
+ try {
+ parsedRepoUrl = new URL(repoUrl);
+ } catch {
+ throw new Error("repoUrl must be a valid GitHub repository URL");
+ }
+ const [owner, repo] = parsedRepoUrl.pathname.split("/").filter(Boolean);
+ if (parsedRepoUrl.hostname !== "github.com" || !owner || !repo) {
throw new Error("repoUrl must be a valid GitHub repository URL");
} |
||
| if (!dateFrom || !dateTo || dateFrom > dateTo) { | ||
| throw new Error("Invalid date range"); | ||
| } | ||
| const flowId = process.env.LAMATIC_FLOW_ID; | ||
| if (!flowId) throw new Error("Missing LAMATIC_FLOW_ID"); | ||
|
|
||
| const response = await lamaticClient.executeFlow(flowId, { | ||
| repo_url: repoUrl, | ||
| date_from: dateFrom, | ||
| date_to: dateTo, | ||
| }) as any; | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| const extractChangelog = (payload: any) => | ||
| payload?.result?.changeLog ?? | ||
| payload?.data?.output?.result?.changeLog ?? | ||
| payload?.data?.changeLog; | ||
|
|
||
| // Handle async flow (returns requestId) | ||
| if (response?.result?.requestId) { | ||
| const finalResult = await lamaticClient.checkStatus( | ||
| response.result.requestId, | ||
| 5, | ||
| 120 | ||
| ) as any; | ||
| const changelog = extractChangelog(finalResult); | ||
| if (!changelog) throw new Error("Failed to extract changelog from Lamatic response"); | ||
| return changelog; | ||
| } | ||
|
|
||
| // Handle sync flow (returns result directly) | ||
| const changelog = extractChangelog(response); | ||
| if (!changelog) throw new Error("Failed to extract changelog from Lamatic response"); | ||
| return changelog; | ||
| } | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,130 @@ | ||
| @import "tailwindcss"; | ||
| @import "tw-animate-css"; | ||
| @import "shadcn/tailwind.css"; | ||
|
|
||
| @custom-variant dark (&:is(.dark *)); | ||
|
|
||
| @theme inline { | ||
| --color-background: var(--background); | ||
| --color-foreground: var(--foreground); | ||
| --font-sans: var(--font-geist-sans); | ||
| --font-mono: var(--font-geist-mono); | ||
| --font-heading: var(--font-sans); | ||
| --color-sidebar-ring: var(--sidebar-ring); | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| --color-sidebar-border: var(--sidebar-border); | ||
| --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); | ||
| --color-sidebar-accent: var(--sidebar-accent); | ||
| --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); | ||
| --color-sidebar-primary: var(--sidebar-primary); | ||
| --color-sidebar-foreground: var(--sidebar-foreground); | ||
| --color-sidebar: var(--sidebar); | ||
| --color-chart-5: var(--chart-5); | ||
| --color-chart-4: var(--chart-4); | ||
| --color-chart-3: var(--chart-3); | ||
| --color-chart-2: var(--chart-2); | ||
| --color-chart-1: var(--chart-1); | ||
| --color-ring: var(--ring); | ||
| --color-input: var(--input); | ||
| --color-border: var(--border); | ||
| --color-destructive: var(--destructive); | ||
| --color-accent-foreground: var(--accent-foreground); | ||
| --color-accent: var(--accent); | ||
| --color-muted-foreground: var(--muted-foreground); | ||
| --color-muted: var(--muted); | ||
| --color-secondary-foreground: var(--secondary-foreground); | ||
| --color-secondary: var(--secondary); | ||
| --color-primary-foreground: var(--primary-foreground); | ||
| --color-primary: var(--primary); | ||
| --color-popover-foreground: var(--popover-foreground); | ||
| --color-popover: var(--popover); | ||
| --color-card-foreground: var(--card-foreground); | ||
| --color-card: var(--card); | ||
| --radius-sm: calc(var(--radius) * 0.6); | ||
| --radius-md: calc(var(--radius) * 0.8); | ||
| --radius-lg: var(--radius); | ||
| --radius-xl: calc(var(--radius) * 1.4); | ||
| --radius-2xl: calc(var(--radius) * 1.8); | ||
| --radius-3xl: calc(var(--radius) * 2.2); | ||
| --radius-4xl: calc(var(--radius) * 2.6); | ||
| } | ||
|
|
||
| :root { | ||
| --background: oklch(1 0 0); | ||
| --foreground: oklch(0.145 0 0); | ||
| --card: oklch(1 0 0); | ||
| --card-foreground: oklch(0.145 0 0); | ||
| --popover: oklch(1 0 0); | ||
| --popover-foreground: oklch(0.145 0 0); | ||
| --primary: oklch(0.205 0 0); | ||
| --primary-foreground: oklch(0.985 0 0); | ||
| --secondary: oklch(0.97 0 0); | ||
| --secondary-foreground: oklch(0.205 0 0); | ||
| --muted: oklch(0.97 0 0); | ||
| --muted-foreground: oklch(0.556 0 0); | ||
| --accent: oklch(0.97 0 0); | ||
| --accent-foreground: oklch(0.205 0 0); | ||
| --destructive: oklch(0.577 0.245 27.325); | ||
| --border: oklch(0.922 0 0); | ||
| --input: oklch(0.922 0 0); | ||
| --ring: oklch(0.708 0 0); | ||
| --chart-1: oklch(0.87 0 0); | ||
| --chart-2: oklch(0.556 0 0); | ||
| --chart-3: oklch(0.439 0 0); | ||
| --chart-4: oklch(0.371 0 0); | ||
| --chart-5: oklch(0.269 0 0); | ||
| --radius: 0.625rem; | ||
| --sidebar: oklch(0.985 0 0); | ||
| --sidebar-foreground: oklch(0.145 0 0); | ||
| --sidebar-primary: oklch(0.205 0 0); | ||
| --sidebar-primary-foreground: oklch(0.985 0 0); | ||
| --sidebar-accent: oklch(0.97 0 0); | ||
| --sidebar-accent-foreground: oklch(0.205 0 0); | ||
| --sidebar-border: oklch(0.922 0 0); | ||
| --sidebar-ring: oklch(0.708 0 0); | ||
| } | ||
|
|
||
| .dark { | ||
| --background: oklch(0.145 0 0); | ||
| --foreground: oklch(0.985 0 0); | ||
| --card: oklch(0.205 0 0); | ||
| --card-foreground: oklch(0.985 0 0); | ||
| --popover: oklch(0.205 0 0); | ||
| --popover-foreground: oklch(0.985 0 0); | ||
| --primary: oklch(0.922 0 0); | ||
| --primary-foreground: oklch(0.205 0 0); | ||
| --secondary: oklch(0.269 0 0); | ||
| --secondary-foreground: oklch(0.985 0 0); | ||
| --muted: oklch(0.269 0 0); | ||
| --muted-foreground: oklch(0.708 0 0); | ||
| --accent: oklch(0.269 0 0); | ||
| --accent-foreground: oklch(0.985 0 0); | ||
| --destructive: oklch(0.704 0.191 22.216); | ||
| --border: oklch(1 0 0 / 10%); | ||
| --input: oklch(1 0 0 / 15%); | ||
| --ring: oklch(0.556 0 0); | ||
| --chart-1: oklch(0.87 0 0); | ||
| --chart-2: oklch(0.556 0 0); | ||
| --chart-3: oklch(0.439 0 0); | ||
| --chart-4: oklch(0.371 0 0); | ||
| --chart-5: oklch(0.269 0 0); | ||
| --sidebar: oklch(0.205 0 0); | ||
| --sidebar-foreground: oklch(0.985 0 0); | ||
| --sidebar-primary: oklch(0.488 0.243 264.376); | ||
| --sidebar-primary-foreground: oklch(0.985 0 0); | ||
| --sidebar-accent: oklch(0.269 0 0); | ||
| --sidebar-accent-foreground: oklch(0.985 0 0); | ||
| --sidebar-border: oklch(1 0 0 / 10%); | ||
| --sidebar-ring: oklch(0.556 0 0); | ||
| } | ||
|
|
||
| @layer base { | ||
| * { | ||
| @apply border-border outline-ring/50; | ||
| } | ||
| body { | ||
| @apply bg-background text-foreground; | ||
| } | ||
| html { | ||
| @apply font-sans; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| import type { Metadata } from "next"; | ||
| import { Geist, Geist_Mono } from "next/font/google"; | ||
| import "./globals.css"; | ||
|
|
||
| const geistSans = Geist({ | ||
| variable: "--font-geist-sans", | ||
| subsets: ["latin"], | ||
| }); | ||
|
|
||
| const geistMono = Geist_Mono({ | ||
| variable: "--font-geist-mono", | ||
| subsets: ["latin"], | ||
| }); | ||
|
|
||
| export const metadata: Metadata = { | ||
| title: "Changelog Generator", | ||
| description: "Generate structured changelogs from GitHub repositories and date ranges.", | ||
| }; | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| export default function RootLayout({ | ||
| children, | ||
| }: Readonly<{ | ||
| children: React.ReactNode; | ||
| }>) { | ||
| return ( | ||
| <html lang="en"> | ||
| <body | ||
| className={`${geistSans.variable} ${geistMono.variable} h-full antialiased min-h-full flex flex-col`} | ||
| > | ||
| {children} | ||
| </body> | ||
| </html> | ||
| ); | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.