chore: remove landing app (#154)
This commit is contained in:
@@ -4,7 +4,6 @@
|
||||
# Directories
|
||||
BACKEND_DIR := apps/backend
|
||||
WEB_DIR := apps/web
|
||||
LANDING_DIR := apps/landing
|
||||
APPS_DIR := apps
|
||||
|
||||
# Tools
|
||||
@@ -46,20 +45,18 @@ help:
|
||||
@echo " dev Run backend + web via CLI (auto ports)"
|
||||
@echo " dev-backend Run backend in development mode (port 8080)"
|
||||
@echo " dev-web Run web app in development mode (port 3000)"
|
||||
@echo " dev-landing Run landing page in development mode (port 3001)"
|
||||
@echo " dev Note: Uses apps/cli launcher (auto ports)"
|
||||
@echo ""
|
||||
@echo "Production Commands:"
|
||||
@echo " start Install deps, build, and start backend + web in production mode"
|
||||
@echo ""
|
||||
@echo "Build Commands:"
|
||||
@echo " build Build backend, web app, and landing page"
|
||||
@echo " build Build backend and web app"
|
||||
@echo " build-backend Build backend binary"
|
||||
@echo " build-web Build web app for production"
|
||||
@echo " build-landing Build landing page for production"
|
||||
@echo ""
|
||||
@echo "Installation:"
|
||||
@echo " install Install all dependencies (backend + web + landing)"
|
||||
@echo " install Install all dependencies (backend + web)"
|
||||
@echo " install-backend Install backend dependencies"
|
||||
@echo " install-web Install web dependencies (uses pnpm workspace)"
|
||||
@echo ""
|
||||
@@ -103,17 +100,12 @@ dev-web:
|
||||
@echo "Starting web app on http://localhost:3000"
|
||||
@cd $(APPS_DIR) && $(PNPM) --filter @kandev/web dev
|
||||
|
||||
.PHONY: dev-landing
|
||||
dev-landing:
|
||||
@echo "Starting landing page on http://localhost:3001"
|
||||
@cd $(APPS_DIR) && $(PNPM) --filter @kandev/landing dev
|
||||
|
||||
#
|
||||
# Build
|
||||
#
|
||||
|
||||
.PHONY: build
|
||||
build: build-backend build-web build-landing
|
||||
build: build-backend build-web
|
||||
@printf "\n$(GREEN)$(BOLD)✓ Build complete!$(RESET)\n"
|
||||
|
||||
#
|
||||
@@ -153,16 +145,6 @@ build-web-quiet:
|
||||
@printf " $(DIM)Web app$(RESET)\n"
|
||||
@cd $(APPS_DIR) && $(PNPM) --filter @kandev/web build 2>&1 | grep -v "Warning:" | grep -v "parseLineType" | grep -v "^$$" || true
|
||||
|
||||
.PHONY: build-landing
|
||||
build-landing:
|
||||
@printf "$(CYAN)Building landing page...$(RESET)\n"
|
||||
@cd $(APPS_DIR) && $(PNPM) --filter @kandev/landing build
|
||||
|
||||
.PHONY: build-landing-quiet
|
||||
build-landing-quiet:
|
||||
@printf " $(DIM)Landing page$(RESET)\n"
|
||||
@cd $(APPS_DIR) && $(PNPM) --filter @kandev/landing build 2>&1 | grep -v "Warning:" | grep -v "parseLineType" | grep -v "^$$" || true
|
||||
|
||||
#
|
||||
# Installation
|
||||
#
|
||||
@@ -178,7 +160,7 @@ install-backend:
|
||||
|
||||
.PHONY: install-web
|
||||
install-web:
|
||||
@printf "$(CYAN)Installing web and landing dependencies...$(RESET)\n"
|
||||
@printf "$(CYAN)Installing web dependencies...$(RESET)\n"
|
||||
@cd $(APPS_DIR) && $(PNPM) install --silent 2>/dev/null || cd $(APPS_DIR) && $(PNPM) install
|
||||
|
||||
#
|
||||
@@ -256,8 +238,8 @@ clean-backend:
|
||||
|
||||
.PHONY: clean-web
|
||||
clean-web:
|
||||
@printf "$(CYAN)Cleaning web and landing artifacts...$(RESET)\n"
|
||||
@rm -rf $(WEB_DIR)/.next $(LANDING_DIR)/.next $(APPS_DIR)/node_modules
|
||||
@printf "$(CYAN)Cleaning web artifacts...$(RESET)\n"
|
||||
@rm -rf $(WEB_DIR)/.next $(APPS_DIR)/node_modules
|
||||
@rm -rf $(APPS_DIR)/packages/*/node_modules
|
||||
|
||||
.PHONY: clean-db
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@kandev/ui/card';
|
||||
import { IconTarget, IconRocket, IconHeartHandshake } from '@tabler/icons-react';
|
||||
|
||||
const values = [
|
||||
{
|
||||
icon: IconTarget,
|
||||
title: 'Developer First',
|
||||
description: 'Built by developers, for developers. We understand your workflow because we live it every day.',
|
||||
},
|
||||
{
|
||||
icon: IconRocket,
|
||||
title: 'Ship Fast, Ship Safe',
|
||||
description: 'Speed without sacrificing security. AI-powered automation with human oversight and approval.',
|
||||
},
|
||||
{
|
||||
icon: IconHeartHandshake,
|
||||
title: 'Open & Transparent',
|
||||
description: 'Open source at heart. We believe in building in public and learning from the community.',
|
||||
},
|
||||
];
|
||||
|
||||
const timeline = [
|
||||
{
|
||||
year: '2026',
|
||||
title: 'The Beginning',
|
||||
description: 'kandev.ai is born from the need to combine visual task management with AI-assisted development.',
|
||||
},
|
||||
];
|
||||
|
||||
export default function AboutPage() {
|
||||
return (
|
||||
<div className="container mx-auto py-24 max-w-6xl px-4">
|
||||
<div className="flex flex-col gap-4 mb-12">
|
||||
<h1 className="text-4xl font-bold">About kandev.ai</h1>
|
||||
<p className="text-lg text-muted-foreground">
|
||||
We're building the future of development workflows.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="prose prose-slate dark:prose-invert max-w-none mb-12">
|
||||
<h2>Our Mission</h2>
|
||||
<p>
|
||||
kandev.ai exists to make software development more visual, more collaborative, and more efficient.
|
||||
We believe that combining Kanban methodology with AI-assisted coding creates a workflow that's
|
||||
greater than the sum of its parts.
|
||||
</p>
|
||||
<p>
|
||||
Traditional development tools force you to context switch between project management, coding,
|
||||
and deployment. We're building a unified experience where your Kanban board is the control center
|
||||
for AI agents working in isolated, secure environments.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mb-12">
|
||||
<h2 className="text-2xl font-bold mb-6">Our Values</h2>
|
||||
<div className="grid gap-6 md:grid-cols-3">
|
||||
{values.map((value) => {
|
||||
const Icon = value.icon;
|
||||
return (
|
||||
<Card key={value.title}>
|
||||
<CardHeader>
|
||||
<Icon className="h-8 w-8 text-primary mb-2" />
|
||||
<CardTitle>{value.title}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardDescription>{value.description}</CardDescription>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-12">
|
||||
<h2 className="text-2xl font-bold mb-6">Our Journey</h2>
|
||||
<div className="space-y-6">
|
||||
{timeline.map((item) => (
|
||||
<Card key={item.year}>
|
||||
<CardHeader>
|
||||
<div className="text-sm text-muted-foreground mb-1">{item.year}</div>
|
||||
<CardTitle>{item.title}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-muted-foreground">{item.description}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="prose prose-slate dark:prose-invert max-w-none">
|
||||
<h2>Why Kanban + Development + AI?</h2>
|
||||
<p>
|
||||
Kanban boards provide visual clarity. Development tools provide execution. AI provides automation.
|
||||
Together, they create a workflow where you can see all your work, execute it in parallel, and
|
||||
automate the routine parts while maintaining full control.
|
||||
</p>
|
||||
<p>
|
||||
We're just getting started. As we grow, we'll continue to focus on making development more
|
||||
intuitive, more powerful, and more enjoyable.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
export default function BlogPage() {
|
||||
redirect('/changelog');
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
import { notFound } from 'next/navigation';
|
||||
import { MDXRemote } from 'next-mdx-remote/rsc';
|
||||
import { getChangelogEntry, getChangelogSlugs } from '@/lib/changelog';
|
||||
import { mdxComponents } from '@/components/mdx-components';
|
||||
|
||||
function formatDate(date: string) {
|
||||
if (!date) return '';
|
||||
return new Intl.DateTimeFormat('en-US', {
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
}).format(new Date(date));
|
||||
}
|
||||
|
||||
export async function generateStaticParams() {
|
||||
const slugs = await getChangelogSlugs();
|
||||
return slugs.map((slug) => ({ slug }));
|
||||
}
|
||||
|
||||
export default async function ChangelogEntryPage({
|
||||
params,
|
||||
}: {
|
||||
params: { slug?: string } | Promise<{ slug?: string }>;
|
||||
}) {
|
||||
const resolvedParams = await Promise.resolve(params);
|
||||
const slug = resolvedParams.slug;
|
||||
|
||||
if (!slug) {
|
||||
notFound();
|
||||
}
|
||||
const entry = await getChangelogEntry(slug);
|
||||
|
||||
if (!entry) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative overflow-hidden">
|
||||
<div className="absolute inset-0 -z-10 bg-[radial-gradient(circle_at_top,_rgba(109,40,217,0.12),_transparent_55%)]" />
|
||||
<div className="container mx-auto max-w-4xl px-4 pb-24 pt-24">
|
||||
<div className="mb-10">
|
||||
<p className="text-sm uppercase tracking-[0.3em] text-muted-foreground">{entry.surface}</p>
|
||||
<h1 className="mt-4 text-4xl font-semibold text-foreground md:text-5xl">{entry.title}</h1>
|
||||
<p className="mt-4 text-sm text-muted-foreground">{formatDate(entry.date)}</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
{entry.summary.map((line) => (
|
||||
<p key={line} className="text-lg text-muted-foreground">
|
||||
{line}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-10 border-t border-border/60 pt-8">
|
||||
<MDXRemote source={entry.content} components={mdxComponents} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
import Link from 'next/link';
|
||||
import { getChangelogEntries } from '@/lib/changelog';
|
||||
|
||||
function formatDate(date: string) {
|
||||
if (!date) return '';
|
||||
return new Intl.DateTimeFormat('en-US', {
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
}).format(new Date(date));
|
||||
}
|
||||
|
||||
export default async function ChangelogPage() {
|
||||
const entries = await getChangelogEntries();
|
||||
|
||||
return (
|
||||
<div className="relative overflow-hidden">
|
||||
<div className="absolute inset-0 -z-10 bg-[radial-gradient(circle_at_top,_rgba(109,40,217,0.12),_transparent_55%)]" />
|
||||
<div className="container mx-auto max-w-6xl px-4 pb-24 pt-24">
|
||||
<div className="mb-12">
|
||||
<p className="text-sm uppercase tracking-[0.3em] text-muted-foreground">Changelog</p>
|
||||
<h1 className="mt-4 text-4xl font-semibold text-foreground md:text-5xl">Product updates</h1>
|
||||
<p className="mt-4 max-w-2xl text-lg text-muted-foreground">
|
||||
A running log of kandev.ai releases, shipped features, and operational improvements.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-12">
|
||||
{entries.map((entry, index) => (
|
||||
<article
|
||||
key={entry.slug}
|
||||
className="grid gap-6 border-b border-border/60 pb-12 md:grid-cols-[180px_1fr]"
|
||||
>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
<p className="font-medium text-foreground">{entry.surface}</p>
|
||||
<p>{formatDate(entry.date)}</p>
|
||||
{index === 0 ? (
|
||||
<span className="mt-3 inline-flex items-center rounded-full border border-border bg-background px-2 py-1 text-[11px] font-semibold uppercase tracking-[0.2em] text-foreground">
|
||||
Latest
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 className="text-2xl font-semibold text-foreground">
|
||||
<Link href={`/changelog/${entry.slug}`} className="hover:text-primary">
|
||||
{entry.title}
|
||||
</Link>
|
||||
</h2>
|
||||
<div className="mt-4 space-y-3 text-muted-foreground">
|
||||
{entry.summary.map((line) => (
|
||||
<p key={line}>{line}</p>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<Link
|
||||
href={`/changelog/${entry.slug}`}
|
||||
className="text-sm font-semibold text-primary hover:text-primary/80"
|
||||
>
|
||||
Read full update →
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import { DocsShell } from '@/components/docs-shell';
|
||||
import { DocsCard } from '@/components/docs-card';
|
||||
import { DocsCode } from '@/components/docs-code';
|
||||
|
||||
export default function AgentsDocsPage() {
|
||||
return (
|
||||
<DocsShell
|
||||
eyebrow="Features"
|
||||
title="AI Agents"
|
||||
description="Agents are containerized workers that execute tasks. They communicate with the backend using ACP, a JSON-RPC protocol over stdin/stdout."
|
||||
>
|
||||
<section className="grid gap-4 md:grid-cols-2">
|
||||
<DocsCard title="Agent lifecycle">
|
||||
<ol className="space-y-2 list-decimal list-inside">
|
||||
<li>Container starts with agentctl on port 9999.</li>
|
||||
<li>Backend calls <code>initialize</code> and <code>session/new</code>.</li>
|
||||
<li>Tasks are sent via <code>session/prompt</code>.</li>
|
||||
<li>Agent streams <code>session/update</code> notifications.</li>
|
||||
</ol>
|
||||
</DocsCard>
|
||||
<DocsCard title="What agents can do" variant="accent">
|
||||
<ul className="space-y-2">
|
||||
<li>Read and edit files inside the worktree</li>
|
||||
<li>Run shell commands with approval gates</li>
|
||||
<li>Stream progress updates to the UI</li>
|
||||
</ul>
|
||||
</DocsCard>
|
||||
</section>
|
||||
|
||||
<DocsCard title="ACP example" variant="muted">
|
||||
<DocsCode>{`{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"1.0"}}`}</DocsCode>
|
||||
</DocsCard>
|
||||
</DocsShell>
|
||||
);
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
import { DocsShell } from '@/components/docs-shell';
|
||||
import { DocsCard } from '@/components/docs-card';
|
||||
import { DocsCode } from '@/components/docs-code';
|
||||
|
||||
const stages = [
|
||||
{ title: 'Backlog', desc: 'Collect ideas and future work.' },
|
||||
{ title: 'In Progress', desc: 'Active tasks owned by agents.' },
|
||||
{ title: 'Review', desc: 'Approvals and QA before merge.' },
|
||||
{ title: 'Done', desc: 'Completed and shipped items.' },
|
||||
];
|
||||
|
||||
export default function BoardsDocsPage() {
|
||||
return (
|
||||
<DocsShell
|
||||
eyebrow="Core Concepts"
|
||||
title="Boards"
|
||||
description="Boards are the Kanban surface for your workspace. Each board defines columns that represent the lifecycle of a task, from backlog to review to done."
|
||||
>
|
||||
<section className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||
{stages.map((stage) => (
|
||||
<DocsCard key={stage.title} title={stage.title}>
|
||||
<p>{stage.desc}</p>
|
||||
</DocsCard>
|
||||
))}
|
||||
</section>
|
||||
|
||||
<DocsCard title="Example board config" variant="muted">
|
||||
<p>Keep columns minimal and apply WIP limits where review capacity is tight.</p>
|
||||
<DocsCode>{`board:
|
||||
name: "Product Roadmap"
|
||||
columns:
|
||||
- name: "Backlog"
|
||||
wipLimit: null
|
||||
- name: "In Progress"
|
||||
wipLimit: 4
|
||||
- name: "Review"
|
||||
wipLimit: 2
|
||||
- name: "Done"
|
||||
wipLimit: null`}</DocsCode>
|
||||
</DocsCard>
|
||||
|
||||
<DocsCard title="Tips" variant="accent">
|
||||
<ul className="space-y-2">
|
||||
<li>Use a single “Review” column so approvals are centralized.</li>
|
||||
<li>Keep WIP limits low to prevent agent overload.</li>
|
||||
<li>Archive finished tasks weekly to keep focus on active work.</li>
|
||||
</ul>
|
||||
</DocsCard>
|
||||
</DocsShell>
|
||||
);
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import { DocsShell } from '@/components/docs-shell';
|
||||
import { DocsCard } from '@/components/docs-card';
|
||||
import { DocsCode } from '@/components/docs-code';
|
||||
|
||||
export default function DockerDocsPage() {
|
||||
return (
|
||||
<DocsShell
|
||||
eyebrow="Features"
|
||||
title="Docker Execution"
|
||||
description="Every agent runs inside a Docker container to keep execution isolated from your host machine. Containers are created per task and destroyed after completion."
|
||||
>
|
||||
<section className="grid gap-4 md:grid-cols-2">
|
||||
<DocsCard title="What is mounted">
|
||||
<ul className="space-y-2">
|
||||
<li><code>/workspace</code> — your repository worktree</li>
|
||||
<li><code>/root/.augment/sessions</code> — agent session storage (when enabled)</li>
|
||||
</ul>
|
||||
</DocsCard>
|
||||
<DocsCard title="Why containers" variant="accent">
|
||||
<ol className="space-y-2 list-decimal list-inside">
|
||||
<li>Consistent, reproducible environments</li>
|
||||
<li>Security boundaries for file and network access</li>
|
||||
<li>Easy cleanup after task completion</li>
|
||||
</ol>
|
||||
</DocsCard>
|
||||
</section>
|
||||
|
||||
<DocsCard title="Sample run (conceptual)" variant="muted">
|
||||
<DocsCode>{`docker run -it --rm \
|
||||
-v /path/to/repo:/workspace \
|
||||
-e TASK_DESCRIPTION="Fix the login bug" \
|
||||
kandev/augment-agent:latest`}</DocsCode>
|
||||
</DocsCard>
|
||||
</DocsShell>
|
||||
);
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import { DocsShell } from '@/components/docs-shell';
|
||||
import { DocsCard } from '@/components/docs-card';
|
||||
import { DocsCode } from '@/components/docs-code';
|
||||
|
||||
export default function InstallationDocsPage() {
|
||||
return (
|
||||
<DocsShell
|
||||
eyebrow="Getting Started"
|
||||
title="Installation"
|
||||
description="These steps outline a typical local installation. Exact commands may change as we finalize the installer, but the flow remains the same."
|
||||
>
|
||||
<section className="grid gap-4 md:grid-cols-2">
|
||||
<DocsCard title="Prerequisites" variant="accent">
|
||||
<ul className="space-y-2">
|
||||
<li>Docker Desktop (or Docker Engine)</li>
|
||||
<li>Git 2.30+</li>
|
||||
<li>Node.js 20+</li>
|
||||
</ul>
|
||||
</DocsCard>
|
||||
<DocsCard title="Install the repo">
|
||||
<DocsCode>{`git clone https://github.com/kdlbs/kandev.git
|
||||
cd kandev
|
||||
pnpm install`}</DocsCode>
|
||||
</DocsCard>
|
||||
</section>
|
||||
|
||||
<DocsCard title="Run the services" variant="muted">
|
||||
<p>Start the backend, app, and landing site in separate terminals.</p>
|
||||
<DocsCode>{`pnpm --filter @kandev/backend dev
|
||||
pnpm --filter @kandev/web dev
|
||||
pnpm --filter @kandev/landing dev`}</DocsCode>
|
||||
</DocsCard>
|
||||
|
||||
<DocsCard title="Verify">
|
||||
<p>
|
||||
Open the UI and create a test workspace. The system should start an agent container on demand and stream
|
||||
progress back over WebSocket.
|
||||
</p>
|
||||
</DocsCard>
|
||||
</DocsShell>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import { DocsSidebar } from '@/components/docs-sidebar';
|
||||
|
||||
export default function DocsLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="min-h-screen bg-[radial-gradient(circle_at_top,_rgba(109,40,217,0.12),_transparent_55%)]">
|
||||
<div className="mx-auto flex w-full max-w-6xl gap-8 px-6">
|
||||
<DocsSidebar />
|
||||
<main className="flex-1 py-16">{children}</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
import { DocsShell } from '@/components/docs-shell';
|
||||
import { DocsCard } from '@/components/docs-card';
|
||||
import { DocsCode } from '@/components/docs-code';
|
||||
|
||||
export default function DocsPage() {
|
||||
return (
|
||||
<DocsShell
|
||||
eyebrow="Getting Started"
|
||||
title="Documentation"
|
||||
description="Learn how kandev.ai orchestrates AI agents across isolated worktrees, Docker containers, and a WebSocket-first control plane."
|
||||
>
|
||||
<section className="grid gap-4 md:grid-cols-2">
|
||||
<DocsCard title="What is kandev.ai?" variant="accent">
|
||||
<p>
|
||||
kandev.ai is a WebSocket-first platform that pairs Kanban task management with AI-assisted execution.
|
||||
Every task runs inside a dedicated git worktree and a Docker container, with approvals for sensitive
|
||||
operations.
|
||||
</p>
|
||||
</DocsCard>
|
||||
<DocsCard title="Why teams use it">
|
||||
<ul className="space-y-2">
|
||||
<li>Ship in parallel without branch conflicts.</li>
|
||||
<li>Keep execution auditable and reviewable.</li>
|
||||
<li>Stream progress updates in real time.</li>
|
||||
</ul>
|
||||
</DocsCard>
|
||||
</section>
|
||||
|
||||
<DocsCard title="Core workflow" variant="muted">
|
||||
<p>From task creation to approval, kandev.ai follows a consistent loop.</p>
|
||||
<DocsCode>{`1. Create task on the board
|
||||
2. Start agent container via agentctl
|
||||
3. Initialize ACP session
|
||||
4. Stream progress over WebSocket
|
||||
5. Review and approve output`}</DocsCode>
|
||||
</DocsCard>
|
||||
|
||||
<section className="grid gap-4 md:grid-cols-3">
|
||||
<DocsCard title="Workspaces">
|
||||
<p>Group repositories, boards, and team settings.</p>
|
||||
</DocsCard>
|
||||
<DocsCard title="Boards" variant="accent">
|
||||
<p>Model your workflow with Kanban columns.</p>
|
||||
</DocsCard>
|
||||
<DocsCard title="Tasks">
|
||||
<p>Define the scope, constraints, and agent requirements.</p>
|
||||
</DocsCard>
|
||||
</section>
|
||||
</DocsShell>
|
||||
);
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import { DocsShell } from '@/components/docs-shell';
|
||||
import { DocsCard } from '@/components/docs-card';
|
||||
import { DocsCode } from '@/components/docs-code';
|
||||
|
||||
const steps = [
|
||||
{
|
||||
step: '1. Create a workspace',
|
||||
body: 'Create a workspace for the repository you want to manage.',
|
||||
},
|
||||
{
|
||||
step: '2. Add a board and task',
|
||||
body: 'Add a board with columns, then create a task that describes the desired change.',
|
||||
},
|
||||
{
|
||||
step: '3. Run an agent',
|
||||
body: 'Start an agent from the task. The UI should stream progress updates as the agent works.',
|
||||
},
|
||||
{
|
||||
step: '4. Review and merge',
|
||||
body: 'Review the diff, approve any gated actions, and merge the worktree.',
|
||||
},
|
||||
];
|
||||
|
||||
export default function QuickStartDocsPage() {
|
||||
return (
|
||||
<DocsShell
|
||||
eyebrow="Getting Started"
|
||||
title="Quick Start"
|
||||
description="This guide walks through a minimal workflow so you can see the agent loop end-to-end."
|
||||
>
|
||||
<section className="grid gap-4 md:grid-cols-2">
|
||||
{steps.map((item) => (
|
||||
<DocsCard key={item.step} title={item.step}>
|
||||
<p>{item.body}</p>
|
||||
</DocsCard>
|
||||
))}
|
||||
</section>
|
||||
|
||||
<DocsCard title="Example task prompt" variant="muted">
|
||||
<DocsCode>{`Task: Update onboarding copy
|
||||
Constraints: Touch only apps/landing
|
||||
Acceptance: Copy updated and passes lint`}</DocsCode>
|
||||
</DocsCard>
|
||||
</DocsShell>
|
||||
);
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import { DocsShell } from '@/components/docs-shell';
|
||||
import { DocsCard } from '@/components/docs-card';
|
||||
import { DocsCode } from '@/components/docs-code';
|
||||
|
||||
export default function TasksDocsPage() {
|
||||
return (
|
||||
<DocsShell
|
||||
eyebrow="Core Concepts"
|
||||
title="Tasks"
|
||||
description="Tasks capture the work that agents will execute. A good task includes a goal, constraints, and the desired output so the agent can plan accurately."
|
||||
>
|
||||
<section className="grid gap-4 md:grid-cols-2">
|
||||
<DocsCard title="Recommended fields">
|
||||
<ul className="space-y-2">
|
||||
<li>Title and description</li>
|
||||
<li>Priority and due date</li>
|
||||
<li>Repository and base branch</li>
|
||||
<li>Agent type and environment</li>
|
||||
<li>Approval requirements</li>
|
||||
</ul>
|
||||
</DocsCard>
|
||||
<DocsCard title="Writing effective tasks" variant="accent">
|
||||
<ol className="space-y-2 list-decimal list-inside">
|
||||
<li>State the outcome, not the steps.</li>
|
||||
<li>Call out sensitive files or commands.</li>
|
||||
<li>Include acceptance criteria.</li>
|
||||
</ol>
|
||||
</DocsCard>
|
||||
</section>
|
||||
|
||||
<DocsCard title="Example task" variant="muted">
|
||||
<DocsCode>{`Title: Add changelog page
|
||||
Repo: github.com/kdlbs/kandev
|
||||
Branch: main
|
||||
Agent: augment-agent
|
||||
Goal: Replace blog with changelog
|
||||
Constraints: Keep existing navbar/footer
|
||||
Output: New route + MDX entries`}</DocsCode>
|
||||
</DocsCard>
|
||||
</DocsShell>
|
||||
);
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import { DocsShell } from '@/components/docs-shell';
|
||||
import { DocsCard } from '@/components/docs-card';
|
||||
import { DocsCode } from '@/components/docs-code';
|
||||
|
||||
export default function WorkspacesDocsPage() {
|
||||
return (
|
||||
<DocsShell
|
||||
eyebrow="Core Concepts"
|
||||
title="Workspaces"
|
||||
description="Workspaces are the top-level container in kandev.ai. They group repositories, boards, agent settings, and members so teams can ship without stepping on each other."
|
||||
>
|
||||
<section className="grid gap-4 md:grid-cols-2">
|
||||
<DocsCard title="What a workspace includes">
|
||||
<ul className="space-y-2">
|
||||
<li>Boards that define workflow stages</li>
|
||||
<li>Repositories connected to those boards</li>
|
||||
<li>Default agent settings and approvals</li>
|
||||
<li>Member roles and permissions</li>
|
||||
</ul>
|
||||
</DocsCard>
|
||||
<DocsCard title="Best practices" variant="accent">
|
||||
<ol className="space-y-2 list-decimal list-inside">
|
||||
<li>Use one workspace per product or client.</li>
|
||||
<li>Keep board naming consistent across teams.</li>
|
||||
<li>Set approvals at the workspace level.</li>
|
||||
</ol>
|
||||
</DocsCard>
|
||||
</section>
|
||||
|
||||
<DocsCard title="Example structure" variant="muted">
|
||||
<p>A workspace keeps everything organized in one place.</p>
|
||||
<DocsCode>{`Workspace: "Core Platform"
|
||||
Boards:
|
||||
- Product Roadmap
|
||||
- Incident Response
|
||||
Repositories:
|
||||
- github.com/kdlbs/kandev
|
||||
- github.com/kdlbs/kandev-docs`}</DocsCode>
|
||||
</DocsCard>
|
||||
</DocsShell>
|
||||
);
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
import { DocsShell } from '@/components/docs-shell';
|
||||
import { DocsCard } from '@/components/docs-card';
|
||||
import { DocsCode } from '@/components/docs-code';
|
||||
|
||||
const benefits = [
|
||||
{ title: 'Parallel tasks', desc: 'Multiple agents can work without branch collisions.' },
|
||||
{ title: 'Clean diffs', desc: 'Each task produces an isolated patch.' },
|
||||
{ title: 'Fast setup', desc: 'No extra clones required.' },
|
||||
];
|
||||
|
||||
export default function WorktreesDocsPage() {
|
||||
return (
|
||||
<DocsShell
|
||||
eyebrow="Features"
|
||||
title="Git Worktrees"
|
||||
description="kandev.ai uses git worktrees to isolate each agent run. Every task gets its own directory that shares the same repository history while keeping file changes separate."
|
||||
>
|
||||
<section className="grid gap-4 md:grid-cols-3">
|
||||
{benefits.map((item) => (
|
||||
<DocsCard key={item.title} title={item.title}>
|
||||
<p>{item.desc}</p>
|
||||
</DocsCard>
|
||||
))}
|
||||
</section>
|
||||
|
||||
<DocsCard title="How it looks on disk" variant="muted">
|
||||
<DocsCode>{`/repos/kandev
|
||||
/main (primary checkout)
|
||||
/.worktrees/task-123
|
||||
/.worktrees/task-124`}</DocsCode>
|
||||
</DocsCard>
|
||||
|
||||
<DocsCard title="Cleanup" variant="accent">
|
||||
<p>
|
||||
When a task is completed and merged, the worktree can be safely removed without affecting the main checkout.
|
||||
</p>
|
||||
</DocsCard>
|
||||
</DocsShell>
|
||||
);
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
'use client';
|
||||
|
||||
export default function GlobalError({
|
||||
error,
|
||||
reset,
|
||||
}: {
|
||||
error: Error & { digest?: string };
|
||||
reset: () => void;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Error - kandev.ai</title>
|
||||
</head>
|
||||
<body style={{
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
fontFamily: 'system-ui, sans-serif',
|
||||
display: 'flex',
|
||||
minHeight: '100vh',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: '#ffffff'
|
||||
}}>
|
||||
<div style={{
|
||||
textAlign: 'center',
|
||||
padding: '2rem'
|
||||
}}>
|
||||
<h1 style={{
|
||||
fontSize: '2rem',
|
||||
fontWeight: 'bold',
|
||||
marginBottom: '1rem',
|
||||
color: '#000000'
|
||||
}}>
|
||||
Something went wrong!
|
||||
</h1>
|
||||
<p style={{
|
||||
marginBottom: '2rem',
|
||||
color: '#666666'
|
||||
}}>
|
||||
We're sorry, but something unexpected happened.
|
||||
</p>
|
||||
<button
|
||||
onClick={() => reset()}
|
||||
style={{
|
||||
padding: '0.75rem 1.5rem',
|
||||
backgroundColor: '#6B46C1',
|
||||
color: '#ffffff',
|
||||
border: 'none',
|
||||
borderRadius: '0.5rem',
|
||||
fontSize: '1rem',
|
||||
fontWeight: '500',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
Try again
|
||||
</button>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
@import "tailwindcss";
|
||||
@import "tw-animate-css";
|
||||
@import "shadcn/tailwind.css";
|
||||
@import "@kandev/theme/fonts.css";
|
||||
|
||||
@source "../components/**/*.{js,ts,jsx,tsx}";
|
||||
@source "../../packages/ui/src/**/*.{js,ts,jsx,tsx}";
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--font-sans: var(--font-sans);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
--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: var(--radius);
|
||||
--radius-md: var(--radius);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: var(--radius);
|
||||
--radius-2xl: var(--radius);
|
||||
--radius-3xl: var(--radius);
|
||||
--radius-4xl: var(--radius);
|
||||
}
|
||||
|
||||
:root {
|
||||
--font-geist-sans: "Geist", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
||||
--font-sans: "Figtree", "Geist", ui-sans-serif, system-ui, -apple-system, "Segoe UI",
|
||||
sans-serif;
|
||||
--font-geist-mono: "Geist Mono", ui-monospace, "SFMono-Regular", Menlo, Monaco, Consolas,
|
||||
"Liberation Mono", "Courier New", monospace;
|
||||
--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.51 0.23 277);
|
||||
--primary-foreground: oklch(0.96 0.02 272);
|
||||
--secondary: oklch(0.967 0.001 286.375);
|
||||
--secondary-foreground: oklch(0.21 0.006 285.885);
|
||||
--muted: oklch(0.97 0 0);
|
||||
--muted-foreground: oklch(0.556 0 0);
|
||||
--accent: oklch(0.68 0.18 276);
|
||||
--accent-foreground: oklch(0.98 0 0);
|
||||
--destructive: oklch(0.58 0.22 27);
|
||||
--border: oklch(0.922 0 0);
|
||||
--input: oklch(0.922 0 0);
|
||||
--ring: oklch(0.708 0 0);
|
||||
--chart-1: oklch(0.79 0.10 275);
|
||||
--chart-2: oklch(0.68 0.16 277);
|
||||
--chart-3: oklch(0.59 0.20 277);
|
||||
--chart-4: oklch(0.51 0.23 277);
|
||||
--chart-5: oklch(0.46 0.21 277);
|
||||
--radius: 0.375rem;
|
||||
--sidebar: oklch(0.985 0 0);
|
||||
--sidebar-foreground: oklch(0.145 0 0);
|
||||
--sidebar-primary: oklch(0.51 0.23 277);
|
||||
--sidebar-primary-foreground: oklch(0.96 0.02 272);
|
||||
--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.59 0.20 277);
|
||||
--primary-foreground: oklch(0.96 0.02 272);
|
||||
--secondary: oklch(0.274 0.006 286.033);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--muted: oklch(0.269 0 0);
|
||||
--muted-foreground: oklch(0.708 0 0);
|
||||
--accent: oklch(0.62 0.18 276);
|
||||
--accent-foreground: oklch(0.98 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.79 0.10 275);
|
||||
--chart-2: oklch(0.68 0.16 277);
|
||||
--chart-3: oklch(0.59 0.20 277);
|
||||
--chart-4: oklch(0.51 0.23 277);
|
||||
--chart-5: oklch(0.46 0.21 277);
|
||||
--sidebar: oklch(0.205 0 0);
|
||||
--sidebar-foreground: oklch(0.985 0 0);
|
||||
--sidebar-primary: oklch(0.68 0.16 277);
|
||||
--sidebar-primary-foreground: oklch(0.96 0.02 272);
|
||||
--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;
|
||||
}
|
||||
html {
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.animate-pulse-slow {
|
||||
animation: pulse-slow 8s ease-in-out infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse-slow {
|
||||
0%, 100% {
|
||||
transform: scale(1);
|
||||
opacity: 0.6;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.1);
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import type { Metadata } from 'next';
|
||||
import './globals.css';
|
||||
import { ThemeProvider } from '@/components/theme-provider';
|
||||
import { Navbar } from '@/components/navbar';
|
||||
import { Footer } from '@/components/footer';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'kandev.ai - AI-powered Kanban for Developers',
|
||||
description: 'Kanban meets AI development. Manage tasks with isolated worktrees, Docker containers, and AI agents.',
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<body className="font-sans antialiased">
|
||||
<ThemeProvider>
|
||||
<Navbar />
|
||||
<main className="pt-14">
|
||||
{children}
|
||||
</main>
|
||||
<Footer />
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import { HeroSection } from '@/components/hero-section';
|
||||
import { FeaturesSection } from '@/components/features-section';
|
||||
import { AgentsSection } from '@/components/agents-section';
|
||||
import { FutureFeaturesSection } from '@/components/future-features-section';
|
||||
|
||||
export default function HomePage() {
|
||||
return (
|
||||
<main>
|
||||
<HeroSection />
|
||||
<FeaturesSection />
|
||||
<AgentsSection />
|
||||
<FutureFeaturesSection />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@kandev/ui/card';
|
||||
|
||||
const agents = [
|
||||
{
|
||||
name: 'Claude Code',
|
||||
description: "Anthropic's official CLI for Claude. Powerful agent with excellent reasoning and code generation.",
|
||||
icon: '🤖',
|
||||
},
|
||||
{
|
||||
name: 'OpenAI Codex',
|
||||
description: 'GPT-4 powered coding assistant with strong performance across multiple languages.',
|
||||
icon: '🔮',
|
||||
},
|
||||
{
|
||||
name: 'Augment',
|
||||
description: 'AI coding assistant focused on code completion and refactoring workflows.',
|
||||
icon: '⚡',
|
||||
},
|
||||
{
|
||||
name: 'Opencode',
|
||||
description: 'Open-source AI coding agent with customizable models and integrations.',
|
||||
icon: '🌐',
|
||||
},
|
||||
];
|
||||
|
||||
export function AgentsSection() {
|
||||
return (
|
||||
<section className="w-full bg-muted/50 py-16 md:py-24">
|
||||
<div className="container mx-auto max-w-6xl px-4">
|
||||
<div className="flex flex-col items-center gap-4 text-center">
|
||||
<h2 className="text-3xl font-bold tracking-tight sm:text-4xl md:text-5xl">
|
||||
Supported AI Agents
|
||||
</h2>
|
||||
<p className="max-w-2xl text-lg text-muted-foreground">
|
||||
Work with your favorite AI coding assistants. All agents run in isolated, secure environments.
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-12 grid gap-6 md:grid-cols-2 lg:grid-cols-4">
|
||||
{agents.map((agent) => (
|
||||
<Card key={agent.name} className="transition-transform hover:scale-105 cursor-pointer">
|
||||
<CardHeader>
|
||||
<div className="text-4xl mb-2">{agent.icon}</div>
|
||||
<CardTitle>{agent.name}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardDescription>{agent.description}</CardDescription>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import type { HTMLAttributes } from 'react';
|
||||
|
||||
type ChangelogVideoProps = {
|
||||
src: string;
|
||||
poster?: string;
|
||||
caption?: string;
|
||||
} & HTMLAttributes<HTMLVideoElement>;
|
||||
|
||||
export function ChangelogVideo({ src, poster, caption, className, ...props }: ChangelogVideoProps) {
|
||||
return (
|
||||
<figure className="mt-6">
|
||||
<video
|
||||
className={`w-full rounded-2xl border border-border bg-muted/40 ${className ?? ''}`}
|
||||
src={src}
|
||||
poster={poster}
|
||||
autoPlay
|
||||
loop
|
||||
muted
|
||||
playsInline
|
||||
preload="metadata"
|
||||
{...props}
|
||||
/>
|
||||
{caption ? (
|
||||
<figcaption className="mt-2 text-sm text-muted-foreground">{caption}</figcaption>
|
||||
) : null}
|
||||
</figure>
|
||||
);
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export function DocsCard({
|
||||
title,
|
||||
children,
|
||||
variant = 'default',
|
||||
}: {
|
||||
title: string;
|
||||
children: ReactNode;
|
||||
variant?: 'default' | 'accent' | 'muted';
|
||||
}) {
|
||||
const base =
|
||||
'rounded-2xl border border-border/60 bg-card/50 p-6 shadow-[0_12px_40px_-28px_rgba(37,99,235,0.5)] backdrop-blur';
|
||||
const variants: Record<typeof variant, string> = {
|
||||
default: 'bg-card/50',
|
||||
accent: 'border-primary/30 bg-primary/10',
|
||||
muted: 'bg-muted/30',
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`${base} ${variants[variant]}`}>
|
||||
<h2 className="text-lg font-semibold text-foreground">{title}</h2>
|
||||
<div className="mt-3 text-sm text-muted-foreground">{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export function DocsCode({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<pre className="mt-4 rounded-xl border border-primary/30 bg-background/80 p-4 text-sm text-foreground shadow-[0_16px_30px_-24px_rgba(59,130,246,0.6)]">
|
||||
<code>{children}</code>
|
||||
</pre>
|
||||
);
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
export function DocsShell({
|
||||
eyebrow,
|
||||
title,
|
||||
description,
|
||||
children,
|
||||
}: {
|
||||
eyebrow: string;
|
||||
title: string;
|
||||
description: string;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div className="relative overflow-hidden">
|
||||
<div className="absolute inset-0 -z-10 " />
|
||||
<div className="space-y-10">
|
||||
<header className="space-y-3">
|
||||
<p className="text-sm uppercase tracking-[0.3em] text-muted-foreground">{eyebrow}</p>
|
||||
<h1 className="text-4xl font-semibold text-foreground">{title}</h1>
|
||||
<p className="text-lg text-muted-foreground">{description}</p>
|
||||
</header>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const sections = [
|
||||
{
|
||||
title: 'Getting Started',
|
||||
items: [
|
||||
{ title: 'Introduction', href: '/docs' },
|
||||
{ title: 'Installation', href: '/docs/installation' },
|
||||
{ title: 'Quick Start', href: '/docs/quick-start' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Core Concepts',
|
||||
items: [
|
||||
{ title: 'Workspaces', href: '/docs/workspaces' },
|
||||
{ title: 'Boards', href: '/docs/boards' },
|
||||
{ title: 'Tasks', href: '/docs/tasks' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Features',
|
||||
items: [
|
||||
{ title: 'Git Worktrees', href: '/docs/worktrees' },
|
||||
{ title: 'AI Agents', href: '/docs/agents' },
|
||||
{ title: 'Docker Execution', href: '/docs/docker' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export function DocsSidebar() {
|
||||
const pathname = usePathname();
|
||||
|
||||
return (
|
||||
<aside className="sticky top-22 h-[calc(100vh-3.5rem)] w-64 shrink-0 overflow-y-auto border-r border-border py-8 pr-6">
|
||||
<nav className="space-y-6">
|
||||
{sections.map((section) => (
|
||||
<div key={section.title}>
|
||||
<h4 className="mb-2 font-semibold text-sm">{section.title}</h4>
|
||||
<ul className="space-y-2">
|
||||
{section.items.map((item) => (
|
||||
<li key={item.href}>
|
||||
<Link
|
||||
href={item.href}
|
||||
className={cn(
|
||||
'block text-sm transition-colors hover:text-foreground',
|
||||
pathname === item.href
|
||||
? 'font-medium text-foreground'
|
||||
: 'text-muted-foreground'
|
||||
)}
|
||||
>
|
||||
{item.title}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</nav>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
import { IconGitBranch, IconCode, IconShieldCheck, IconPlugConnected, IconFolders, IconBrandDocker } from '@tabler/icons-react';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@kandev/ui/card';
|
||||
|
||||
const features = [
|
||||
{
|
||||
icon: IconCode,
|
||||
title: 'Parallel Agent Execution',
|
||||
description:
|
||||
'Run multiple AI coding agents simultaneously on different tasks. Each agent works in isolated environments, accelerating development without conflicts.',
|
||||
},
|
||||
{
|
||||
icon: IconGitBranch,
|
||||
title: 'Isolated Worktrees',
|
||||
description:
|
||||
'Each task gets its own git worktree. Work on multiple features, bugs, and experiments in parallel without branch switching or stashing.',
|
||||
},
|
||||
{
|
||||
icon: IconShieldCheck,
|
||||
title: 'Built-in Code Review',
|
||||
description:
|
||||
'Automated code review and approval workflows. Review agent changes before merging, with full audit logs and rollback capabilities.',
|
||||
},
|
||||
{
|
||||
icon: IconPlugConnected,
|
||||
title: 'MCP Integration',
|
||||
description:
|
||||
'Connect with Model Context Protocol servers. Extend agent capabilities with custom tools, APIs, and integrations.',
|
||||
},
|
||||
{
|
||||
icon: IconFolders,
|
||||
title: 'Multiple Workspaces',
|
||||
description:
|
||||
'Organize projects by client, team, or product. Switch between workspaces seamlessly and keep your work isolated.',
|
||||
},
|
||||
{
|
||||
icon: IconBrandDocker,
|
||||
title: 'Local Docker',
|
||||
description:
|
||||
'AI agents run in local Docker containers for complete isolation. Secure execution with approval workflows and audit logs for every operation.',
|
||||
},
|
||||
];
|
||||
|
||||
export function FeaturesSection() {
|
||||
return (
|
||||
<section id="features" className="container mx-auto max-w-6xl py-16 md:py-24 px-4">
|
||||
<div className="flex flex-col items-center gap-4 text-center">
|
||||
<h2 className="text-3xl font-bold tracking-tight sm:text-4xl md:text-5xl">
|
||||
Build for development velocity
|
||||
</h2>
|
||||
<p className="max-w-2xl text-lg text-muted-foreground">
|
||||
Combine the visual clarity of Kanban boards with the power of AI-assisted development.
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-12 grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||
{features.map((feature) => {
|
||||
const Icon = feature.icon;
|
||||
return (
|
||||
<Card key={feature.title}>
|
||||
<CardHeader>
|
||||
<Icon className="h-8 w-8 text-primary mb-2" />
|
||||
<CardTitle>{feature.title}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardDescription>{feature.description}</CardDescription>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
import Link from 'next/link';
|
||||
import { IconBrandGithub, IconBrandTwitter, IconBrandDiscord } from '@tabler/icons-react';
|
||||
|
||||
export function Footer() {
|
||||
return (
|
||||
<footer className="border-t border-border bg-background">
|
||||
<div className="container mx-auto max-w-6xl py-12 md:py-16 px-4">
|
||||
<div className="grid gap-8 md:grid-cols-3">
|
||||
<div>
|
||||
<h3 className="font-bold mb-4">kandev.ai</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
AI-powered Kanban for developers. Ship faster with isolated worktrees and secure Docker execution.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-semibold mb-4">Product</h4>
|
||||
<ul className="space-y-2 text-sm">
|
||||
<li>
|
||||
<Link href="/#features" className="text-muted-foreground hover:text-foreground">
|
||||
Features
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/docs" className="text-muted-foreground hover:text-foreground">
|
||||
Documentation
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-semibold mb-4">Company</h4>
|
||||
<ul className="space-y-2 text-sm">
|
||||
<li>
|
||||
<Link href="/about" className="text-muted-foreground hover:text-foreground">
|
||||
About
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/changelog" className="text-muted-foreground hover:text-foreground">
|
||||
Changelog
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-8 border-t border-border pt-8 flex flex-col md:flex-row justify-between items-center gap-4">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
© 2026 kandev.ai. All rights reserved. Built with respect for your privacy and GDPR compliance.
|
||||
</p>
|
||||
<div className="flex gap-4">
|
||||
<a
|
||||
href="https://github.com/kdlbs/kandev"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<IconBrandGithub className="h-5 w-5" />
|
||||
</a>
|
||||
<a
|
||||
href="https://twitter.com/kandev_ai"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<IconBrandTwitter className="h-5 w-5" />
|
||||
</a>
|
||||
<a
|
||||
href="https://discord.gg/kandev"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<IconBrandDiscord className="h-5 w-5" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
import { IconCloud, IconDeviceMobile, IconUsers } from '@tabler/icons-react';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@kandev/ui/card';
|
||||
import { Badge } from '@kandev/ui/badge';
|
||||
|
||||
const futureFeatures = [
|
||||
{
|
||||
icon: IconCloud,
|
||||
title: 'Remote Executors',
|
||||
description: 'Run AI agents on cloud or remote machines. Scale your development with powerful remote compute.',
|
||||
status: 'Coming Soon',
|
||||
},
|
||||
{
|
||||
icon: IconDeviceMobile,
|
||||
title: 'Mobile App',
|
||||
description: 'Launch and monitor tasks from your mobile device. Stay connected to your development workflow.',
|
||||
status: 'In Development',
|
||||
},
|
||||
{
|
||||
icon: IconUsers,
|
||||
title: 'Team Collaboration',
|
||||
description: 'Share workspaces, assign tasks, and collaborate with your team in real-time.',
|
||||
status: 'Planned',
|
||||
},
|
||||
];
|
||||
|
||||
export function FutureFeaturesSection() {
|
||||
return (
|
||||
<section className="container mx-auto max-w-6xl py-16 md:py-24 px-4">
|
||||
<div className="flex flex-col items-center gap-4 text-center">
|
||||
<h2 className="text-3xl font-bold tracking-tight sm:text-4xl md:text-5xl">
|
||||
Coming Soon
|
||||
</h2>
|
||||
<p className="max-w-2xl text-lg text-muted-foreground">
|
||||
We're constantly improving kandev.ai. Here's what's on our roadmap.
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-12 grid gap-6 md:grid-cols-3">
|
||||
{futureFeatures.map((feature) => {
|
||||
const Icon = feature.icon;
|
||||
return (
|
||||
<Card key={feature.title} className="relative">
|
||||
<CardHeader>
|
||||
<Icon className="h-8 w-8 text-primary mb-2" />
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle>{feature.title}</CardTitle>
|
||||
</div>
|
||||
<Badge variant="secondary" className="w-fit">
|
||||
{feature.status}
|
||||
</Badge>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardDescription>{feature.description}</CardDescription>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Button } from '@kandev/ui/button';
|
||||
import { Badge } from '@kandev/ui/badge';
|
||||
import { IconCheck, IconCopy } from '@tabler/icons-react';
|
||||
|
||||
export function HeroSection() {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const copyCommand = () => {
|
||||
navigator.clipboard.writeText('npx kandev');
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="relative overflow-hidden">
|
||||
<div className="absolute inset-0 -z-10 bg-[radial-gradient(circle_at_top,_rgba(109,40,217,0.12),_transparent_55%)]" />
|
||||
<div className="container mx-auto max-w-6xl flex flex-col items-center gap-8 px-4 pt-16 pb-16 md:pb-24">
|
||||
<Badge variant="secondary" className="cursor-pointer border border-border px-6 py-3">
|
||||
<span className="text-sm">kandev.ai – AI-powered Kanban for developers</span>
|
||||
</Badge>
|
||||
<h1 className="max-w-4xl text-center text-4xl font-bold tracking-tight sm:text-5xl md:text-6xl lg:text-7xl">
|
||||
Kanban meets <span className="text-primary">AI development</span>
|
||||
</h1>
|
||||
<p className="max-w-2xl text-center text-lg text-muted-foreground md:text-xl">
|
||||
Manage development tasks with isolated git worktrees, secure Docker execution, and AI agents.
|
||||
Ship faster without breaking your workflow.
|
||||
</p>
|
||||
<div className="flex flex-col items-center gap-4 sm:flex-row">
|
||||
<Button size="lg" asChild className="cursor-pointer">
|
||||
<a href="#features">Get Started</a>
|
||||
</Button>
|
||||
<Button size="lg" variant="outline" asChild className="cursor-pointer">
|
||||
<a href="#demo">Watch Demo</a>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-8 w-full max-w-2xl">
|
||||
<div className="flex items-center justify-center gap-2 rounded-lg border border-border bg-card p-4">
|
||||
<code className="flex-1 text-sm font-mono text-foreground">npx kandev</code>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
onClick={copyCommand}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
{copied ? (
|
||||
<IconCheck className="h-4 w-4 text-green-500" />
|
||||
) : (
|
||||
<IconCopy className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-8 w-full max-w-5xl">
|
||||
<div className="aspect-video rounded-lg border border-border bg-muted flex items-center justify-center">
|
||||
<p className="text-muted-foreground">Video placeholder</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import type { ComponentProps, ReactElement } from 'react';
|
||||
import { ChangelogVideo } from '@/components/changelog-video';
|
||||
|
||||
function MdxHeading({ className, ...props }: ComponentProps<'h2'>) {
|
||||
return <h2 className={`mt-10 text-2xl font-semibold text-foreground ${className ?? ''}`} {...props} />;
|
||||
}
|
||||
|
||||
function MdxSubheading({ className, ...props }: ComponentProps<'h3'>) {
|
||||
return <h3 className={`mt-8 text-xl font-semibold text-foreground ${className ?? ''}`} {...props} />;
|
||||
}
|
||||
|
||||
function MdxParagraph({ className, ...props }: ComponentProps<'p'>) {
|
||||
return <p className={`mt-4 text-base leading-relaxed text-muted-foreground ${className ?? ''}`} {...props} />;
|
||||
}
|
||||
|
||||
function MdxList({ className, ...props }: ComponentProps<'ul'>) {
|
||||
return <ul className={`mt-4 space-y-2 pl-6 text-muted-foreground ${className ?? ''}`} {...props} />;
|
||||
}
|
||||
|
||||
function MdxListItem({ className, ...props }: ComponentProps<'li'>) {
|
||||
return <li className={`list-disc text-base ${className ?? ''}`} {...props} />;
|
||||
}
|
||||
|
||||
function MdxImage({ className, alt, ...props }: ComponentProps<'img'>): ReactElement {
|
||||
return (
|
||||
<img
|
||||
className={`mt-6 w-full rounded-2xl border border-border bg-muted/40 ${className ?? ''}`}
|
||||
alt={alt ?? ''}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const mdxComponents = {
|
||||
h2: MdxHeading,
|
||||
h3: MdxSubheading,
|
||||
p: MdxParagraph,
|
||||
ul: MdxList,
|
||||
li: MdxListItem,
|
||||
img: MdxImage,
|
||||
ChangelogVideo,
|
||||
};
|
||||
@@ -1,69 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { IconBrandGithub } from '@tabler/icons-react';
|
||||
import { Button } from '@kandev/ui/button';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
export function Navbar() {
|
||||
const pathname = usePathname();
|
||||
const stars = '2k'; // Dummy star count display
|
||||
|
||||
return (
|
||||
<header className="fixed top-0 z-50 w-full border-b border-border/40 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||
<div className="container mx-auto flex h-14 max-w-6xl items-center justify-between px-4">
|
||||
<Link href="/" className="flex items-center space-x-2">
|
||||
<span className="font-bold">kandev.ai</span>
|
||||
</Link>
|
||||
|
||||
<nav className="flex items-center gap-6 text-sm">
|
||||
<Link
|
||||
href="/#product"
|
||||
className={cn(
|
||||
'text-sm font-medium transition-colors hover:text-foreground cursor-pointer',
|
||||
pathname === '/' ? 'text-foreground' : 'text-muted-foreground'
|
||||
)}
|
||||
>
|
||||
Product
|
||||
</Link>
|
||||
<Link
|
||||
href="/docs"
|
||||
className={cn(
|
||||
'text-sm font-medium transition-colors hover:text-foreground cursor-pointer',
|
||||
pathname?.startsWith('/docs') ? 'text-foreground' : 'text-muted-foreground'
|
||||
)}
|
||||
>
|
||||
Documentation
|
||||
</Link>
|
||||
<Link
|
||||
href="/changelog"
|
||||
className={cn(
|
||||
'text-sm font-medium transition-colors hover:text-foreground cursor-pointer',
|
||||
pathname?.startsWith('/changelog') ? 'text-foreground' : 'text-muted-foreground'
|
||||
)}
|
||||
>
|
||||
Changelog
|
||||
</Link>
|
||||
</nav>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button variant="outline" size="sm" asChild>
|
||||
<a
|
||||
href="https://github.com/kdlbs/kandev"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<IconBrandGithub className="h-4 w-4" />
|
||||
<span>Star</span>
|
||||
<span className="ml-1 rounded-full bg-muted px-2 py-0.5 text-xs font-medium">
|
||||
{stars}
|
||||
</span>
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { ThemeProvider as NextThemesProvider } from 'next-themes';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export function ThemeProvider({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<NextThemesProvider attribute="class" defaultTheme="dark" enableSystem={false}>
|
||||
{children}
|
||||
</NextThemesProvider>
|
||||
);
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
---
|
||||
title: "kandev.ai Launch"
|
||||
date: "2026-01-17"
|
||||
surface: "Platform"
|
||||
summary:
|
||||
- "kandev.ai ships a WebSocket-first Kanban + AI agent workflow for developer teams."
|
||||
- "Tasks run inside isolated git worktrees and Docker containers with approval gates."
|
||||
---
|
||||
|
||||
## What is kandev.ai?
|
||||
|
||||
kandev.ai is a Kanban-native development platform that pairs task management with AI agents. Every task can be executed by an agent in a clean, isolated environment, while you keep control over approvals and merge decisions.
|
||||
|
||||

|
||||
|
||||
## Initial launch features
|
||||
|
||||
- **Kanban boards for engineering work** with task metadata, priorities, and agent selection.
|
||||
- **Isolated git worktrees** so parallel tasks never collide.
|
||||
- **Docker-based execution** for secure, repeatable runs.
|
||||
- **Agent control plane** via `agentctl` with start/stop/status and ACP relays.
|
||||
- **WebSocket-only client API** for real-time orchestration and updates.
|
||||
- **Approval gates** for sensitive paths and commands.
|
||||
|
||||
## How it works (high level)
|
||||
|
||||
1. The client opens a single WebSocket connection to the backend.
|
||||
2. The backend spins up an agent container with `agentctl` as a sidecar.
|
||||
3. The backend initializes ACP over stdin/stdout and creates a session.
|
||||
4. The task prompt is streamed to the agent and progress events flow back in real time.
|
||||
5. Results are reviewed, approved, and merged back into the main branch.
|
||||
|
||||

|
||||
|
||||
### What’s next
|
||||
|
||||
Expect expanded agent types, richer approvals, and deeper task analytics in upcoming releases.
|
||||
@@ -1,71 +0,0 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import matter from 'gray-matter';
|
||||
|
||||
export type ChangelogEntry = {
|
||||
slug: string;
|
||||
title: string;
|
||||
date: string;
|
||||
surface: string;
|
||||
summary: string[];
|
||||
content: string;
|
||||
};
|
||||
|
||||
const changelogRoot = path.join(process.cwd(), 'content', 'changelog');
|
||||
|
||||
function normalizeSummary(summary: unknown): string[] {
|
||||
if (Array.isArray(summary)) {
|
||||
return summary.filter((item): item is string => typeof item === 'string');
|
||||
}
|
||||
if (typeof summary === 'string') {
|
||||
return [summary];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function parseEntry(slug: string, fileContent: string): ChangelogEntry {
|
||||
const { data, content } = matter(fileContent);
|
||||
|
||||
return {
|
||||
slug,
|
||||
title: String(data.title ?? slug),
|
||||
date: String(data.date ?? ''),
|
||||
surface: String(data.surface ?? 'Product'),
|
||||
summary: normalizeSummary(data.summary),
|
||||
content,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getChangelogEntries(): Promise<ChangelogEntry[]> {
|
||||
const entries = await fs.readdir(changelogRoot, { withFileTypes: true });
|
||||
const slugs = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
||||
|
||||
const items = await Promise.all(
|
||||
slugs.map(async (slug) => {
|
||||
const entryPath = path.join(changelogRoot, slug, 'index.mdx');
|
||||
const fileContent = await fs.readFile(entryPath, 'utf8');
|
||||
return parseEntry(slug, fileContent);
|
||||
})
|
||||
);
|
||||
|
||||
return items.sort((a, b) => {
|
||||
const aTime = new Date(a.date).getTime();
|
||||
const bTime = new Date(b.date).getTime();
|
||||
return bTime - aTime;
|
||||
});
|
||||
}
|
||||
|
||||
export async function getChangelogEntry(slug: string): Promise<ChangelogEntry | null> {
|
||||
const entryPath = path.join(changelogRoot, slug, 'index.mdx');
|
||||
try {
|
||||
const fileContent = await fs.readFile(entryPath, 'utf8');
|
||||
return parseEntry(slug, fileContent);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getChangelogSlugs(): Promise<string[]> {
|
||||
const entries = await fs.readdir(changelogRoot, { withFileTypes: true });
|
||||
return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import { type ClassValue, clsx } from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
transpilePackages: ["@kandev/ui", "@kandev/theme", "@kandev/types"],
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
@@ -1,36 +0,0 @@
|
||||
{
|
||||
"name": "@kandev/landing",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev -p 3001",
|
||||
"build": "NODE_ENV=production next build",
|
||||
"start": "next start -p 3001",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@kandev/theme": "workspace:*",
|
||||
"@kandev/ui": "workspace:*",
|
||||
"@tabler/icons-react": "^3.24.0",
|
||||
"clsx": "^2.1.1",
|
||||
"next": "16.1.1",
|
||||
"next-themes": "^0.4.6",
|
||||
"next-mdx-remote": "^5.0.0",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"gray-matter": "^4.0.3",
|
||||
"shadcn": "^3.6.3",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
"tw-animate-css": "^1.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4.0.0",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "16.1.1",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
const config = {
|
||||
plugins: {
|
||||
"@tailwindcss/postcss": {},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -1,18 +0,0 @@
|
||||
<svg width="1200" height="640" viewBox="0 0 1200 640" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="1200" height="640" rx="32" fill="#0B1220"/>
|
||||
<rect x="80" y="120" width="240" height="120" rx="20" fill="#111827" stroke="#1F2937" stroke-width="2"/>
|
||||
<rect x="360" y="120" width="240" height="120" rx="20" fill="#111827" stroke="#1F2937" stroke-width="2"/>
|
||||
<rect x="640" y="120" width="240" height="120" rx="20" fill="#111827" stroke="#1F2937" stroke-width="2"/>
|
||||
<rect x="920" y="120" width="200" height="120" rx="20" fill="#111827" stroke="#1F2937" stroke-width="2"/>
|
||||
<path d="M320 180H360" stroke="#38BDF8" stroke-width="6" stroke-linecap="round"/>
|
||||
<path d="M600 180H640" stroke="#38BDF8" stroke-width="6" stroke-linecap="round"/>
|
||||
<path d="M880 180H920" stroke="#38BDF8" stroke-width="6" stroke-linecap="round"/>
|
||||
<text x="110" y="190" fill="#E2E8F0" font-family="Figtree, sans-serif" font-size="16">Client WS</text>
|
||||
<text x="390" y="190" fill="#E2E8F0" font-family="Figtree, sans-serif" font-size="16">Backend</text>
|
||||
<text x="670" y="190" fill="#E2E8F0" font-family="Figtree, sans-serif" font-size="16">agentctl</text>
|
||||
<text x="950" y="190" fill="#E2E8F0" font-family="Figtree, sans-serif" font-size="16">Agent</text>
|
||||
<rect x="80" y="300" width="1040" height="200" rx="24" fill="#0F172A" stroke="#1E293B" stroke-width="2"/>
|
||||
<text x="120" y="360" fill="#94A3B8" font-family="Figtree, sans-serif" font-size="16">orchestrator.start → /api/v1/start → ACP initialize/session/prompt → session/update</text>
|
||||
<text x="120" y="400" fill="#94A3B8" font-family="Figtree, sans-serif" font-size="16">Real-time progress streamed back over WebSocket</text>
|
||||
<text x="96" y="96" fill="#E2E8F0" font-family="Figtree, sans-serif" font-size="24" font-weight="600">High-level execution flow</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.8 KiB |
@@ -1,14 +0,0 @@
|
||||
<svg width="1200" height="640" viewBox="0 0 1200 640" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="1200" height="640" rx="32" fill="#0B1120"/>
|
||||
<rect x="64" y="72" width="1072" height="496" rx="28" fill="#111827" stroke="#1F2937" stroke-width="2"/>
|
||||
<rect x="104" y="120" width="320" height="120" rx="20" fill="#0F172A" stroke="#1F2937" stroke-width="2"/>
|
||||
<rect x="448" y="120" width="320" height="120" rx="20" fill="#0F172A" stroke="#1F2937" stroke-width="2"/>
|
||||
<rect x="792" y="120" width="320" height="120" rx="20" fill="#0F172A" stroke="#1F2937" stroke-width="2"/>
|
||||
<rect x="104" y="272" width="640" height="44" rx="14" fill="#1E293B"/>
|
||||
<rect x="104" y="332" width="560" height="44" rx="14" fill="#1E293B"/>
|
||||
<rect x="104" y="392" width="480" height="44" rx="14" fill="#1E293B"/>
|
||||
<circle cx="980" cy="364" r="82" fill="#0F172A" stroke="#38BDF8" stroke-width="3"/>
|
||||
<path d="M940 364L968 392L1024 336" stroke="#38BDF8" stroke-width="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<text x="104" y="112" fill="#E2E8F0" font-family="Figtree, sans-serif" font-size="24" font-weight="600">kandev.ai Launch</text>
|
||||
<text x="104" y="500" fill="#94A3B8" font-family="Figtree, sans-serif" font-size="16">Kanban + AI agents with secure execution</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./*"],
|
||||
"@kandev/ui": ["../packages/ui/src"],
|
||||
"@kandev/ui/*": ["../packages/ui/src/*"],
|
||||
"@kandev/theme": ["../packages/theme/src"],
|
||||
"@kandev/types": ["../packages/types/src"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
@@ -5,10 +5,8 @@
|
||||
"scripts": {
|
||||
"dev": "pnpm -r run dev",
|
||||
"dev:web": "pnpm --filter @kandev/web dev",
|
||||
"dev:landing": "pnpm --filter @kandev/landing dev",
|
||||
"build": "pnpm -r run build",
|
||||
"build:web": "pnpm --filter @kandev/web build",
|
||||
"build:landing": "pnpm --filter @kandev/landing build",
|
||||
"lint": "pnpm -r run lint",
|
||||
"clean": "pnpm -r run clean && rm -rf node_modules"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
packages:
|
||||
- 'cli'
|
||||
- 'web'
|
||||
- 'landing'
|
||||
- 'packages/*'
|
||||
|
||||
Reference in New Issue
Block a user