Skip to content
Kura

Interfaces for agents that do real work.

Runs, plans, terminals, diffs, traces and approvals. 15 open-source React components for supervising coding and computer-use agents, with a manifest your own agent can install from.

Get startedBrowse components
One agent run, built entirely from Kura components.

Migrate session API to @acme/auth

Elapsed
–:––
Run running
<RunHeader />
~/acme-web3.4sexit 0
git diff --stat
src/auth/client.ts | ++++++++++++--------------- 29
src/middleware.ts | +++++++++---- 13
src/auth/login.test.ts | ++++++-- 8
src/lib/http.ts | ----------------------------------------- 41
4 files changed, 28 insertions(+), 63 deletions(-)
$ pnpm tsc --noEmit
✓ No type errors (3.4s)
<Terminal />
01

Plan in the open

The agent's todo list as it thinks: what's done, what's running, what failed and why. Sub-tasks, live timers, a folded history.

<TaskList />
Plan3 of 8
Task 4 of 8: Patch src/middleware.ts
02

Review every hunk

Accept or reject the agent's edits hunk by hunk, with syntax highlighting, word-level changes and j/k/a/r on the keyboard. Parses real unified diffs.

<DiffView />
Review changes+22−3322 additions, 33 deletions
@@ −1,7 +1,8 @@Accepted
import { cache } from "react";
removed: import { fetchJson } from "@/lib/http";
removed: import type { Session, User } from "./types";
added: import { createAuthClient, type Session, type User } from "@acme/auth";
​
removed: const SESSION_URL = "/api/v1/session";
removed: const REFRESH_URL = "/api/v1/session/refresh";
added: export const auth = createAuthClient({
added: baseUrl: process.env.NEXT_PUBLIC_AUTH_URL,
added: refresh: "auto",
added: });
​
@@ −9,14 +10,10 @@
export const fetchUser = cache(async (): Promise<User | null> => {
removed: const res = await fetchJson<Session>(SESSION_URL, { credentials: "include" });
removed: if (!res.ok) return null;
removed: return res.data.user;
added: const session = await auth.getSession();
added: return session?.user ?? null;
});
​
removed: export async function refreshSession(token: string): Promise<Session> {
removed: const res = await fetchJson<Session>(REFRESH_URL, {
removed: method: "POST",
removed: body: JSON.stringify({ token }),
removed: });
removed: if (!res.ok) throw new Error(`refresh failed: ${res.status}`);
removed: return res.data;
added: export async function refreshSession(session: Session): Promise<Session> {
added: const next = await auth.refresh(session.refreshToken);
added: if (!next) throw new Error("refresh failed: session revoked");
added: return next;
}
@@ −24,3 +21,3 @@
export function isExpired(session: Session, now = Date.now()): boolean {
removed: return session.expiresAt < now;
added: return session.expiresAt <= now;
}
@@ −28,3 +25,3 @@
export async function signOut(): Promise<void> {
removed: await fetchJson(SESSION_URL, { method: "DELETE", credentials: "include" });
added: await auth.signOut();
}
@@ −1,4 +1,4 @@
import { describe, expect, it, vi } from "vitest";
removed: import { refreshSession, isExpired } from "../client";
added: import { auth, isExpired, refreshSession } from "../client";
​
describe("login", () => {
@@ −8,8 +8,10 @@
​
it("refreshes an expired session", async () => {
removed: vi.spyOn(globalThis, "fetch").mockResolvedValue(
removed: new Response(JSON.stringify({ expiresAt: 10, user: { id: "u_1" } })),
removed: );
removed: const next = await refreshSession("tok_expired");
added: vi.spyOn(auth, "refresh").mockResolvedValue({
added: expiresAt: 10,
added: refreshToken: "tok_next",
added: user: { id: "u_1" },
added: } as never);
added: const next = await refreshSession({ refreshToken: "tok_expired" } as never);
expect(next.user.id).toBe("u_1");
});
@@ −1,10 +0,0 @@
removed: /** @deprecated Use `auth.getSession()` from @acme/auth. */
removed: export function readSessionCookie(cookie: string | null) {
removed: const raw = cookie?.match(/acme_session=([^;]+)/)?.[1];
removed: if (!raw) return null;
removed: try {
removed: return JSON.parse(atob(raw)) as { userId: string; expiresAt: number };
removed: } catch {
removed: return null;
removed: }
removed: }
@@ −1,4 +1,4 @@
import { NextResponse, type NextRequest } from "next/server";
removed: import { readSessionCookie } from "@/lib/legacy-session";
added: import { auth } from "@/auth/client";
​
const PUBLIC_PATHS = ["/login", "/signup", "/api/health"];
@@ −8,5 +8,5 @@
if (PUBLIC_PATHS.some((p) => pathname.startsWith(p))) return NextResponse.next();
​
removed: const session = readSessionCookie(request.headers.get("cookie"));
added: const session = await auth.getSession({ headers: request.headers });
if (!session) {
const url = new URL("/login", request.url);
JKmoveAacceptRrejectUundo1 accepted · 0 rejected
03

Say yes before it runs

Risky commands wait for a human. Risk level, the exact command, details on demand, ⌘↵ to approve and Esc to deny.

<ApprovalPrompt />

Kura wants to delete a directory

High risk

Clears stale output before rebuilding. Affects 1,204 files under ./build only.

rm -rf build
  • Working directory: ~/code/kura-ui
  • Requested by step 3 of 5, “Rebuild the docs”
  • Not tracked by git — cannot be restored from history
Needs approval
04

See where the time went

Every model call, tool and subagent on one time axis. Running spans grow live; select one for its input, output, tokens and cost.

<TraceWaterfall />
Trace4m 40s
Kura
Plan the migration
read_file
search_code
Draft the client patch
edit_file
edit_file
test-writer
Write session tests
edit_file
05

Follow it through the web

Computer-use made legible: the page, the agent's cursor and target, and a step log. Take over when it needs you.

<BrowserSession />
Agent is browsing
github.com/acme/web/compare/main...kura/auth-client
acme/webCodePull requestsActions

Open a pull request

Create a new pull request by comparing changes across two branches.

base: main←compare: kura/auth-client Able to merge
Title
Title
Description
Add a description
Markdown is supportedCreate pull request

4 commits · 6 files changed · +142 −38

  1. Open the compare view (in progress)github.com/acme/web/compare/main...kura/auth-clientnavigate
  2. Focus the title field (pending)input#pr-titleclick
  3. Type the PR title (pending)“Migrate session API to @acme/auth client”type
  4. Write the description (pending)“Replaces fetchUser() and the legacy /api/session…”type
  5. Click “Create pull request” (pending)button[type=submit]click
  6. Read the PR number (pending)#482 · Openextract
Open the compare view
06

Steer without stopping

Queue follow-ups while the agent works. Reorder, edit, or send one now to interrupt and redirect the run.

<PromptQueue />

Queued3

Sends when Kura finishes running pnpm test
  1. Use the refreshSession() helper instead of calling fetch('/api/session') directlysession-flow.png
  2. Don't touch src/legacy/ — it's being deleted next sprint
  3. When tests pass, update the auth section of the README and link the migration guideMIGRATION.md+1

Alt plus arrow keys or Space then arrows to reorder. Enter to edit, Delete to remove, Control or Command plus Enter to send now.

Hover a prompt, or focus it and use Alt+↑/↓, Enter, Delete and ⌘↵.

Install

Your agent can install it, too.

Every component ships a typed manifest: when to use it, when not to, every prop, the anatomy and the keyboard map. The registry, llms.txt, the MCP server and the CLI are generated from it.

# give your coding agent the catalog
claude mcp add kura -- npx -y @kura-ui/mcp

# then ask for what you need
> add per-hunk review for the agent's edits, and a terminal for its test runs

# or install directly
npx shadcn@latest add https://kuro.abstergo.fyi/r/diff-view.json