Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions kits/automation/changelog-generator/.env.example
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
42 changes: 42 additions & 0 deletions kits/automation/changelog-generator/.gitignore
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
98 changes: 98 additions & 0 deletions kits/automation/changelog-generator/README.md
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.

![Changelog Generator](./public/preview.png)

## ✨ 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

[![Deploy with Vercel](https://vercel.com/button)](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
```

### 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"
```

### 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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add a language to the fenced structure block.

This block is currently tripping the markdownlint warning that was attached to the review. text is enough here.

📝 Suggested doc fix
-```
+```text
 changelog-generator/
 ├── app/
 │   └── page.tsx          # Main UI
@@
 ├── config.json           # Kit metadata
 └── README.md
</details>

<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion

🧰 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.
67 changes: 67 additions & 0 deletions kits/automation/changelog-generator/actions/orchestrate.ts
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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Wrap URL parsing in try-catch to return a friendly error.

new URL(repoUrl) throws a TypeError if repoUrl is not a valid URL. This surfaces as an opaque server error instead of the descriptive message on line 32.

🛡️ 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;

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;
}
Binary file not shown.
130 changes: 130 additions & 0 deletions kits/automation/changelog-generator/app/globals.css
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);
--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;
}
}
34 changes: 34 additions & 0 deletions kits/automation/changelog-generator/app/layout.tsx
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.",
};

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>
);
}
Loading
Loading