Skip to content
Merged
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
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ Contents of the README.md file:

---

Jules Task Queue is a GitHub-integrated service that solves the "5 concurrent task" bottleneck when using the Google Labs - Jules AI coding assistant. It automatically queues tasks when Jules hits its limit and retries them later, allowing you to seamlessly utilize your full daily quota.
Jules Task Queue is a GitHub-integrated service that solves the "3 concurrent task" bottleneck when using the Google Labs - Jules AI coding assistant. It automatically queues tasks when Jules hits its limit and retries them later, allowing you to seamlessly utilize your full daily quota.

## The Problem: The 5-Task Bottleneck
## The Problem: The 3-Task Bottleneck

> "Jules gives you 60 tasks per day but only 5 concurrent slots. So you're constantly babysitting the queue, manually re-adding labels every time it hits the limit. There has to be a better way."
> "Jules gives you 15 tasks per day but only 3 concurrent slots.\* So you're constantly babysitting the queue, manually re-adding labels every time it hits the limit. There has to be a better way."
> — Every Jules power user, probably

This tool is the better way. It transforms Jules from a tool you have to manage into a true "set it and forget it" automation partner.
Expand Down
6 changes: 3 additions & 3 deletions GEMINI.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ Contents of the README.md file:

---

Jules Task Queue is a GitHub-integrated service that solves the "5 concurrent task" bottleneck when using the Google Labs - Jules AI coding assistant. It automatically queues tasks when Jules hits its limit and retries them later, allowing you to seamlessly utilize your full daily quota.
Jules Task Queue is a GitHub-integrated service that solves the "3 concurrent task" bottleneck when using the Google Labs - Jules AI coding assistant. It automatically queues tasks when Jules hits its limit and retries them later, allowing you to seamlessly utilize your full daily quota.

## The Problem: The 5-Task Bottleneck
## The Problem: The 3-Task Bottleneck

> "Jules gives you 60 tasks per day but only 5 concurrent slots. So you're constantly babysitting the queue, manually re-adding labels every time it hits the limit. There has to be a better way."
> "Jules gives you 15 tasks per day but only 3 concurrent slots.\* So you're constantly babysitting the queue, manually re-adding labels every time it hits the limit. There has to be a better way."
> — Every Jules power user, probably

This tool is the better way. It transforms Jules from a tool you have to manage into a true "set it and forget it" automation partner.
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@

---

Jules Task Queue is a GitHub-integrated service that solves the "5 concurrent task" bottleneck when using the Google Labs - Jules AI coding assistant. It automatically queues tasks when Jules hits its limit and retries them later, allowing you to seamlessly utilize your full daily quota.
Jules Task Queue is a GitHub-integrated service that solves the "3 concurrent task" bottleneck when using the Google Labs - Jules AI coding assistant. It automatically queues tasks when Jules hits its limit and retries them later, allowing you to seamlessly utilize your full daily quota.

## The Problem: The 5-Task Bottleneck
## The Problem: The 3-Task Bottleneck

> "Jules gives you 60 tasks per day but only 5 concurrent slots. So you're constantly babysitting the queue, manually re-adding labels every time it hits the limit. There has to be a better way."
> "Jules gives you 15 tasks per day but only 3 concurrent slots.\* So you're constantly babysitting the queue, manually re-adding labels every time it hits the limit. There has to be a better way."
> — Every Jules power user, probably

This tool is the better way. It transforms Jules from a tool you have to manage into a true "set it and forget it" automation partner.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- CreateIndex
CREATE INDEX "jules_tasks_flaggedForRetry_createdAt_idx" ON "jules_tasks"("flaggedForRetry", "createdAt");
1 change: 1 addition & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ model JulesTask {
@@index([repoOwner, repoName])
@@index([createdAt])
@@index([installationId])
@@index([flaggedForRetry, createdAt]) // Composite index for stats queries
@@map("jules_tasks")
}

Expand Down
20 changes: 20 additions & 0 deletions src/app/api/stats/repositories/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { db } from "@/server/db";
import { NextResponse } from "next/server";

export async function GET() {
try {
const totalRepositories = await db.installationRepository.count({
where: {
removedAt: null, // only active repositories
},
});

return NextResponse.json({ totalRepositories });
} catch (error) {
console.error("Failed to fetch repository stats:", error);
return NextResponse.json(
{ error: "Failed to fetch stats" },
{ status: 500 },
);
}
}
2 changes: 1 addition & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const geistMono = Geist_Mono({

export const metadata: Metadata = {
title: "Google Jules Task Queue",
description: "Break free from the 5-task bottleneck and ship more.",
description: "Break free from the concurrent task bottleneck and ship more.",
};

export default function RootLayout({
Expand Down
2 changes: 2 additions & 0 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
HowItWorks,
Navigation,
ProblemSection,
StatsSection,
} from "@/components/landing";

export default function Home() {
Expand All @@ -14,6 +15,7 @@ export default function Home() {
<Navigation />
<HeroSection />
<ProblemSection />
<StatsSection />
<HowItWorks />
<FeaturesSection />
<CTASection />
Expand Down
21 changes: 19 additions & 2 deletions src/components/landing/hero-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ import {
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { useRepositoryStats } from "@/hooks/use-repository-stats";
import { Copy, ExternalLink, Zap } from "lucide-react";
import { useState } from "react";
import { GitHubDashboard } from "./github-dashboard";

export function HeroSection() {
const [copied, setCopied] = useState<string | null>(null);
const { stats, isLoading } = useRepositoryStats();

const copyToClipboard = async (text: string, id: string) => {
await navigator.clipboard.writeText(text);
Expand Down Expand Up @@ -66,11 +68,13 @@ export function HeroSection() {

<h1 className="text-4xl sm:text-5xl md:text-7xl font-bold mb-6 leading-tight text-white">
<span className="sm:block">Queue tasks and avoid the</span>{" "}
<span className="text-jules-secondary">5-task bottleneck</span>
<span className="text-jules-secondary">
concurrent task bottleneck
</span>
</h1>

<p className="text-lg sm:text-xl md:text-2xl mb-8 max-w-4xl mx-auto leading-relaxed text-jules-gray">
Jules gives you 60 tasks per day but only 5 concurrent slots. Jules
Jules gives you 15 tasks per day but only 3 concurrent slots.* Jules
Task Queue automatically manages the queue so you can{" "}
<span className="font-semibold text-jules-pink">
actually use them all
Expand All @@ -80,6 +84,19 @@ export function HeroSection() {

<GitHubDashboard />

{/* Stats validation */}
<div className="mb-6">
{!isLoading && stats && (
<p className="text-jules-gray text-sm">
Trusted by{" "}
<span className="font-semibold text-jules-cyan">
{stats.totalRepositories.toLocaleString()}
</span>{" "}
repositories
</p>
)}
</div>

<div className="flex flex-col sm:flex-row items-center justify-center space-y-4 sm:space-y-0 sm:space-x-6 mb-8">
<GitHubInstallButton
onInstallStart={handleInstallStart}
Expand Down
1 change: 1 addition & 0 deletions src/components/landing/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export { HeroSection } from "./hero-section";
export { HowItWorks } from "./how-it-works";
export { Navigation } from "./navigation";
export { ProblemSection } from "./problem-section";
export { StatsSection } from "./stats-section";
2 changes: 1 addition & 1 deletion src/components/landing/problem-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export function ProblemSection() {
</div>

<blockquote className="text-lg sm:text-xl md:text-2xl italic text-center leading-relaxed text-white">
&ldquo;Jules gives you 60 tasks per day but only 5 concurrent slots.
&ldquo;Jules gives you 15 tasks per day but only 3 concurrent slots.*
So you&rsquo;re constantly babysitting the queue, manually re-adding
labels every time it hits the limit. There has to be a better
way.&rdquo;
Expand Down
80 changes: 80 additions & 0 deletions src/components/landing/stats-section.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { Card, CardContent } from "@/components/ui/card";
import { appRouter } from "@/server/api/root";
import { createCallerFactory } from "@/server/api/trpc";
import { db } from "@/server/db";
import { env } from "@/lib/env";

export async function StatsSection() {
const createCaller = createCallerFactory(appRouter);
const caller = createCaller({
headers: new Headers(),
db,
env,
});

let stats;
try {
stats = await caller.tasks.publicStats();
} catch (error) {
console.error("Failed to fetch stats:", error);
return null;
}

const statItems = [
{
label: "Total Tasks Processed",
value: stats.totalTasks.toLocaleString(),
description: "GitHub issues processed through the queue",
borderColor: "border-jules-primary",
textColor: "text-jules-primary",
},
{
label: "Total Retries Handled",
value: stats.totalRetries.toLocaleString(),
description: "Tasks automatically retried when Jules hit limits",
borderColor: "border-jules-pink",
textColor: "text-jules-pink",
},
{
label: "Repositories Connected",
value: stats.totalRepositories.toLocaleString(),
description: "Repositories with the queue integration",
borderColor: "border-jules-cyan",
textColor: "text-jules-cyan",
},
];

return (
<section className="py-16 bg-jules-secondary-5">
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center mb-8">
<h2 className="text-2xl md:text-3xl font-bold text-white mb-6">
Jules Queue in Numbers
</h2>
<p className="text-lg text-gray-300">
Statistics from our hosted deployment
</p>
</div>

<div className="grid grid-cols-1 sm:grid-cols-3 gap-6 max-w-3xl mx-auto">
{statItems.map((stat) => (
<Card
key={stat.label}
className={`border bg-jules-darker ${stat.borderColor}`}
>
<CardContent className="p-6 text-center">
<div className={`text-3xl font-bold mb-2 ${stat.textColor}`}>
{stat.value}
</div>
<div className="text-white">{stat.label}</div>
<div className="text-sm text-gray-400 mt-2">
{stat.description}
</div>
</CardContent>
</Card>
))}
</div>
</div>
</section>
);
}
2 changes: 1 addition & 1 deletion src/components/success/success-state.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ export function SuccessState({
Smart queue management:
</strong>{" "}
When you add the &quot;jules&quot; label to an issue,
we&apos;ll automatically queue it and manage the 5-task limit.
we&apos;ll automatically queue it and manage the 3-task limit.
</span>
</li>
<li className="flex items-start">
Expand Down
35 changes: 35 additions & 0 deletions src/hooks/use-repository-stats.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useEffect, useState } from "react";

interface RepositoryStats {
totalRepositories: number;
}

export function useRepositoryStats() {
const [stats, setStats] = useState<RepositoryStats | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);

useEffect(() => {
const fetchStats = async () => {
try {
setIsLoading(true);
const response = await fetch("/api/stats/repositories");
if (!response.ok) {
throw new Error("Failed to fetch stats");
}
const data = await response.json();
setStats(data);
setError(null);
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to fetch stats");
setStats(null);
} finally {
setIsLoading(false);
}
};

fetchStats();
}, []);

return { stats, isLoading, error };
}
80 changes: 49 additions & 31 deletions src/server/api/routers/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,43 +40,61 @@ export const tasksRouter = createTRPCRouter({
};
}),

// Get task statistics
stats: publicProcedure.query(async ({ ctx }) => {
const [totalTasks, queuedTasks, activeTasks, completedToday] =
await Promise.all([
// Total tasks
ctx.db.julesTask.count(),

// Queued tasks (flagged for retry)
ctx.db.julesTask.count({
where: { flaggedForRetry: true },
}),

// Active tasks (created in last hour, not flagged for retry)
ctx.db.julesTask.count({
where: {
flaggedForRetry: false,
createdAt: {
gte: new Date(Date.now() - 60 * 60 * 1000), // last hour
},
},
}),

// Completed today (approximate - tasks that were retried today)
ctx.db.julesTask.count({
where: {
lastRetryAt: {
gte: new Date(new Date().setHours(0, 0, 0, 0)), // start of today
},
// Get public project statistics
publicStats: publicProcedure.query(async ({ ctx }) => {
const [
totalTasks,
totalRetries,
queuedTasks,
activeTasks,
totalInstallations,
totalRepositories,
] = await Promise.all([
// Total tasks ever created
ctx.db.julesTask.count(),

// Total retry count across all tasks
ctx.db.julesTask.aggregate({
_sum: { retryCount: true },
}),

// Currently queued tasks (flagged for retry)
ctx.db.julesTask.count({
where: { flaggedForRetry: true },
}),

// Active tasks (created in last 24 hours, not flagged for retry)
ctx.db.julesTask.count({
where: {
flaggedForRetry: false,
createdAt: {
gte: new Date(Date.now() - 24 * 60 * 60 * 1000), // last 24 hours
},
}),
]);
},
}),

// Total GitHub installations
ctx.db.gitHubInstallation.count({
where: {
suspendedAt: null, // only active installations
},
}),

// Total repositories with active installations
ctx.db.installationRepository.count({
where: {
removedAt: null, // only active repositories
},
}),
]);

return {
totalTasks,
totalRetries: totalRetries._sum.retryCount ?? 0,
queuedTasks,
activeTasks,
completedToday,
totalInstallations,
totalRepositories,
};
}),

Expand Down
10 changes: 10 additions & 0 deletions src/types/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,13 @@ export const CleanupTasksSchema = z.object({
export const SystemHealthSchema = z.object({
includeDetails: z.boolean().default(false),
});

// Public stats schema
export const PublicStatsSchema = z.object({
totalTasks: z.number(),
totalRetries: z.number(),
queuedTasks: z.number(),
activeTasks: z.number(),
totalInstallations: z.number(),
totalRepositories: z.number(),
});
Loading