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>
This commit is contained in:
1 parent
084452abf6
commit
62fe5bf467
42 files changed
+3734
-55
No files matched your search
+34
-1
@@ -7,6 +7,12 @@ self.addEventListener("push", (event) => {
|
||||
data = { body: event.data ? event.data.text() : "" };
|
||||
}
|
||||
const title = data.title || "CodeCraft";
|
||||
const de = String((self.navigator && self.navigator.language) || "").toLowerCase().startsWith("de");
|
||||
// Approvals can be answered right from the notification.
|
||||
const actions = data.kind === "permission" && data.permission_id ? [
|
||||
{ action: "allow", title: de ? "Erlauben" : "Allow" },
|
||||
{ action: "reject", title: de ? "Ablehnen" : "Reject" },
|
||||
] : [];
|
||||
const prefix = { permission: "🔐 ", question: "❓ ", done: "✅ " }[data.kind] || "";
|
||||
event.waitUntil(
|
||||
self.registration.showNotification(prefix + title, {
|
||||
@@ -15,13 +21,40 @@ self.addEventListener("push", (event) => {
|
||||
renotify: true,
|
||||
icon: "/icon-192.png",
|
||||
badge: "/icon-192.png",
|
||||
data: { url: data.url || "/" },
|
||||
actions,
|
||||
requireInteraction: data.kind === "permission",
|
||||
data: { url: data.url || "/", permission_id: data.permission_id || null },
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener("notificationclick", (event) => {
|
||||
event.notification.close();
|
||||
const info = event.notification.data || {};
|
||||
if ((event.action === "allow" || event.action === "reject") && info.permission_id) {
|
||||
const de = String((self.navigator && self.navigator.language) || "").toLowerCase().startsWith("de");
|
||||
const target = new URL(info.url || "/", self.location.origin).href;
|
||||
event.waitUntil(
|
||||
fetch("/api/permissions/" + encodeURIComponent(info.permission_id), {
|
||||
method: "POST",
|
||||
credentials: "same-origin",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ reply: event.action === "allow" ? "once" : "reject" }),
|
||||
})
|
||||
.then((r) => {
|
||||
if (!r.ok) throw new Error(String(r.status));
|
||||
// Short confirmation, so the tap does not feel lost.
|
||||
return self.registration.showNotification(event.action === "allow" ? (de ? "Erlaubt" : "Allowed") : (de ? "Abgelehnt" : "Rejected"), {
|
||||
tag: "perm-" + info.permission_id,
|
||||
icon: "/icon-192.png",
|
||||
silent: true,
|
||||
data: { url: info.url },
|
||||
}).then(() => new Promise((res) => setTimeout(res, 2500))).then(() => self.registration.getNotifications({ tag: "perm-" + info.permission_id })).then((l) => l.forEach((n) => n.close()));
|
||||
})
|
||||
.catch(() => self.clients.openWindow(target)), // expired, answered elsewhere or signed out: open the app
|
||||
);
|
||||
return;
|
||||
}
|
||||
const url = new URL((event.notification.data && event.notification.data.url) || "/", self.location.origin).href;
|
||||
event.waitUntil(
|
||||
self.clients.matchAll({ type: "window", includeUncontrolled: true }).then((list) => {
|
||||
|
||||
Reference in new issue
Block a user