Files
codecraft/web/src/components/ProjectPermissions.tsx
T
Richard ZinkandClaude Sonnet 5.5 62fe5bf467 Claude Code engine, image viewer, colour tags, checks per turn and more
- Claude Code as an engine: chats on a "claude-code/<alias>" model run through
  the claude CLI (stream-json); tool approvals go through our permission
  system. Sign in from the web UI (URL + pasted code), CLI version and update
  from Settings; the Docker image contains the CLI.
- Small models (8k context) for side tasks: input is cut to the model's
  context window; one-line chat status, "What does this do?" on approvals,
  "Plan as automation" (job draft from a chat).
- Mobile: top bar no longer widens the page, image viewer (zoom, swipe, mark
  up, use in message), jump to previous own message, message menu, time under
  every message.
- Approvals: push actions (allow/reject), "always allow in this project"
  (never for dangerous commands) with revoke.
- Per turn: changed files with diffs and rollback; project check command with
  a green/red badge and "let the agent fix it".
- Colour tags for chats in the colours of the active theme.
- Project defaults for new chats, project page (timeline), offline queue.

Tested in a throwaway container with a mock model (8k small model): 26 API
checks passed (permissions, diffs, checks, defaults, job draft, side-task
budget, Claude Code status/login URL, unsigned-in chat).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-14 22:30:12 +01:00

56 lines
2.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useEffect, useState } from "react";
import { api, get } from "../api";
import { useT } from "../i18n";
import { toast } from "../dialogs";
import { Icon } from "./Icon";
/** "bash:git status" → "git status"; tool names stay. */
const label = (key: string) => (key.startsWith("bash:") ? `$ ${key.slice(5)}` : key);
/** What the user allowed "for this project" (all chats of the folder) – with a way to revoke it. */
export function ProjectPermissions({ sessionId, cwd, onClose }: { sessionId: string; cwd: string; onClose: () => void }) {
const t = useT();
const [keys, setKeys] = useState<string[]>();
useEffect(() => {
get<string[]>(`/sessions/${sessionId}/approvals`).then(setKeys, (e) => (toast(e.message), setKeys([])));
}, [sessionId]);
const revoke = async (key: string) => {
try {
await api(`/sessions/${sessionId}/approvals`, { method: "DELETE", json: { key } });
setKeys((k) => k?.filter((x) => x !== key));
} catch (e: any) {
toast(e.message);
}
};
return (
<div className="modal-backdrop" onClick={onClose}>
<div className="modal" role="dialog" aria-label={t("permProject.title")} onClick={(e) => e.stopPropagation()}>
<h3>
<Icon name="lock" /> {t("permProject.title")}
</h3>
<p style={{ fontSize: 13, color: "var(--fg3)", marginBottom: 4 }}>{t("permProject.intro")}</p>
<div className="path mono" style={{ marginBottom: 8 }}>{cwd}</div>
{keys === undefined ? (
<div className="empty">…</div>
) : keys.length === 0 ? (
<p style={{ fontSize: 13 }}>{t("permProject.empty")}</p>
) : (
<ul className="rewind-files">
{keys.map((k) => (
<li key={k} className="row" style={{ justifyContent: "space-between" }}>
<code>{label(k)}</code>
<button className="small danger" onClick={() => void revoke(k)}>
{t("permProject.revoke")}
</button>
</li>
))}
</ul>
)}
<div className="row" style={{ justifyContent: "flex-end", marginTop: 10 }}>
<button onClick={onClose}>{t("common.close")}</button>
</div>
</div>
</div>
);
}