Initial CodeCraft: web-based coding harness for AI models

Rust backend (axum/tokio): agent loop with streaming, tools (fs, bash,
web_fetch, todo, ask_user, subagents, skills, craft snapshots/compare),
permissions with modes and rules, Anthropic + OpenAI-compatible providers,
MCP client (stdio/HTTP, elicitation), SQLite sessions, usage statistics,
compaction, commented TOML config editable via toml_edit.

React PWA: chat with live streaming, permission/question cards, modes
(build/plan/craft), settings for providers/models/prices/MCP/permissions,
raw config editor, statistics dashboard, i18n (en/de). Docker setup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-08 14:39:24 +01:00
commit f3a2fe3816
62 files changed
+21835

No files matched your search

+93
View File
@@ -0,0 +1,93 @@
import { useMemo } from "react";
import { fmtRelative, useT, type Key } from "../i18n";
import { navigate } from "../router";
import { useStore } from "../store";
import type { Session } from "../types";
function basename(p: string) {
return p.split("/").filter(Boolean).pop() ?? p;
}
function groupKey(ms: number): Key {
const day = 86400000;
const startOfToday = new Date().setHours(0, 0, 0, 0);
if (ms >= startOfToday) return "sidebar.today";
if (ms >= startOfToday - day) return "sidebar.yesterday";
if (ms >= startOfToday - 7 * day) return "sidebar.week";
return "sidebar.older";
}
export function Sidebar({ open, activeId, section }: { open: boolean; activeId?: string; section: string }) {
const t = useT();
const sessions = useStore((s) => s.sessions);
const busy = useStore((s) => s.busy);
const connected = useStore((s) => s.connected);
const permissions = useStore((s) => s.permissions);
const questions = useStore((s) => s.questions);
const attention = useMemo(() => {
const ids = new Set<string>();
for (const p of permissions) ids.add(p.root_session_id ?? p.session_id);
for (const q of questions) ids.add(q.root_session_id ?? q.session_id);
return ids;
}, [permissions, questions]);
const groups = useMemo(() => {
const out: { key: Key; items: Session[] }[] = [];
for (const s of sessions) {
const key = groupKey(s.updated_at);
const g = out.find((x) => x.key === key);
if (g) g.items.push(s);
else out.push({ key, items: [s] });
}
return out;
}, [sessions]);
return (
<aside className={`sidebar ${open ? "open" : ""}`}>
<div className="sidebar-head">
<div className="logo">
<img src="/icon.svg" alt="" />
CodeCraft
</div>
<span className={`conn-dot ${connected ? "on" : ""}`} title={connected ? t("sidebar.connected") : t("sidebar.disconnected")} />
</div>
<div style={{ padding: "0 12px 8px" }}>
<button className="primary" style={{ width: "100%" }} onClick={() => navigate("/new")}>
+ {t("sidebar.newSession")}
</button>
</div>
<nav className="session-list">
{sessions.length === 0 && <div className="empty">{t("sidebar.noSessions")}</div>}
{groups.map((g) => (
<div key={g.key}>
<div className="session-group">{t(g.key)}</div>
{g.items.map((s) => (
<button
key={s.id}
className={`session-item ${s.id === activeId ? "active" : ""}`}
onClick={() => navigate(`/s/${s.id}`)}
>
<div className="title">
{attention.has(s.id) ? <i className="attn-dot" title={t("sidebar.needsInput")} /> : busy[s.id] && <i className="busy-dot" />}
<span>{s.title || t("session.untitled")}</span>
</div>
<div className="sub">
{basename(s.cwd)} · {s.agent} · {fmtRelative(s.updated_at)}
</div>
</button>
))}
</div>
))}
</nav>
<div className="sidebar-foot">
<button className={`ghost ${section === "stats" ? "active" : ""}`} onClick={() => navigate("/stats")}>
{t("nav.stats")}
</button>
<button className={`ghost ${section === "settings" ? "active" : ""}`} onClick={() => navigate("/settings")}>
{t("nav.settings")}
</button>
</div>
</aside>
);
}