Add security hardening, Ghidra, recreate skill, checkpoints, shells, web search and themes
- Sandbox (bubblewrap) for non-admin shell/git, MCP per-user rights, hard budgets and quotas, login rate limiting, audit log and backups - Ghidra headless decompilation (ARM64 natives built from source), binary_info tool, clean room / deep workflow per session - Recreate skill and commands for analysing and re-building old software - File checkpoints with rewind/restore, background shells (bash_output, kill_shell) - web_search tool (DuckDuckGo, SearXNG, Brave, Tavily) - In-app dialogs and toasts instead of browser dialogs - 15 base themes (trilium-themes palettes plus Nord, VS Code, GitHub, Catppuccin, One, Dracula, Rose Pine, Everforest, Kanagawa) with light/dark/auto switcher and license notes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
fab9add5d5
commit
5b569b6835
108 files changed
+6785
-279
No files matched your search
+57
-18
@@ -1,38 +1,77 @@
|
||||
import "./themes";
|
||||
|
||||
const KEY = "codecraft.theme";
|
||||
const MODE_KEY = "codecraft.themeMode";
|
||||
|
||||
/** Available themes; "system" follows the OS light/dark preference. */
|
||||
/**
|
||||
* Base themes. Each has a dark and a light variant (CSS files
|
||||
* `themes/<id>-dark.css` / `<id>-light.css`); the mode picks the variant.
|
||||
*/
|
||||
export const THEMES = [
|
||||
{ id: "system", name: "System", dark: true },
|
||||
{ id: "gruvbox-dark", name: "Gruvbox Dark", dark: true },
|
||||
{ id: "gruvbox-light", name: "Gruvbox Light", dark: false },
|
||||
{ id: "gruvbox", name: "Gruvbox", source: "morhetz/gruvbox", license: "MIT" },
|
||||
{ id: "ash", name: "Ash", source: "zink/trilium-themes", license: "MIT" },
|
||||
{ id: "harbor", name: "Harbor", source: "HANCORE-linux/omarchy-harbor-theme", license: "MIT" },
|
||||
{ id: "coffee", name: "Coffee", source: "megabyte0x/omarchy-coffee-theme", license: "MIT" },
|
||||
{ id: "solarized", name: "Solarized", source: "Ethan Schoonover", license: "MIT" },
|
||||
{ id: "tokyonight", name: "Tokyo Night", source: "folke/tokyonight.nvim", license: "Apache-2.0" },
|
||||
{ id: "nord", name: "Nord", source: "nordtheme/nord", license: "MIT" },
|
||||
{ id: "vscode", name: "VS Code", source: "microsoft/vscode", license: "MIT" },
|
||||
{ id: "github", name: "GitHub", source: "primer/github-vscode-theme", license: "MIT" },
|
||||
{ id: "catppuccin", name: "Catppuccin", source: "catppuccin/catppuccin", license: "MIT" },
|
||||
{ id: "one", name: "One Dark / Light", source: "atom/atom", license: "MIT" },
|
||||
{ id: "dracula", name: "Dracula / Alucard", source: "dracula/dracula-theme", license: "MIT" },
|
||||
{ id: "rosepine", name: "Rosé Pine", source: "rose-pine/rose-pine-theme", license: "MIT" },
|
||||
{ id: "everforest", name: "Everforest", source: "sainnhe/everforest", license: "MIT" },
|
||||
{ id: "kanagawa", name: "Kanagawa", source: "rebelot/kanagawa.nvim", license: "MIT" },
|
||||
] as const;
|
||||
|
||||
export type Theme = (typeof THEMES)[number]["id"];
|
||||
|
||||
/** Themes used for "system" in dark and light OS mode. */
|
||||
const SYSTEM_DARK = "gruvbox-dark";
|
||||
const SYSTEM_LIGHT = "gruvbox-light";
|
||||
export type ThemeMode = "auto" | "light" | "dark";
|
||||
export const THEME_MODES: ThemeMode[] = ["auto", "light", "dark"];
|
||||
|
||||
export function getTheme(): Theme {
|
||||
const saved = localStorage.getItem(KEY);
|
||||
// Migrate the old values "dark"/"light".
|
||||
if (saved === "dark") return "gruvbox-dark";
|
||||
if (saved === "light") return "gruvbox-light";
|
||||
return THEMES.some((t) => t.id === saved) ? (saved as Theme) : "system";
|
||||
const saved = localStorage.getItem(KEY) ?? "";
|
||||
// Migrate old values like "gruvbox-dark", "dark", "system".
|
||||
const base = saved.replace(/-(dark|light)$/, "");
|
||||
return THEMES.some((t) => t.id === base) ? (base as Theme) : "gruvbox";
|
||||
}
|
||||
|
||||
export function getThemeMode(): ThemeMode {
|
||||
const saved = localStorage.getItem(MODE_KEY);
|
||||
if (saved === "light" || saved === "dark" || saved === "auto") return saved;
|
||||
// Migrate the old combined setting.
|
||||
const old = localStorage.getItem(KEY) ?? "";
|
||||
if (old.endsWith("dark")) return "dark";
|
||||
if (old.endsWith("light")) return "light";
|
||||
return "auto";
|
||||
}
|
||||
|
||||
const media = matchMedia("(prefers-color-scheme: dark)");
|
||||
const listeners = new Set<() => void>();
|
||||
|
||||
export function applyTheme(theme: Theme = getTheme()) {
|
||||
export function onThemeChange(fn: () => void) {
|
||||
listeners.add(fn);
|
||||
return () => void listeners.delete(fn);
|
||||
}
|
||||
|
||||
export function applyTheme(theme: Theme = getTheme(), mode: ThemeMode = getThemeMode()) {
|
||||
localStorage.setItem(KEY, theme);
|
||||
const resolved = theme === "system" ? (media.matches ? SYSTEM_DARK : SYSTEM_LIGHT) : theme;
|
||||
document.documentElement.dataset.theme = resolved;
|
||||
const color = getComputedStyle(document.documentElement).getPropertyValue("--theme-color").trim();
|
||||
localStorage.setItem(MODE_KEY, mode);
|
||||
const dark = mode === "auto" ? media.matches : mode === "dark";
|
||||
const root = document.documentElement;
|
||||
root.dataset.theme = `${theme}-${dark ? "dark" : "light"}`;
|
||||
root.dataset.mode = dark ? "dark" : "light";
|
||||
const color = getComputedStyle(root).getPropertyValue("--theme-color").trim();
|
||||
document.querySelector('meta[name="theme-color"]')?.setAttribute("content", color || "#282828");
|
||||
listeners.forEach((fn) => fn());
|
||||
}
|
||||
|
||||
/** Cycle auto → light → dark (quick switcher). */
|
||||
export function cycleThemeMode() {
|
||||
const modes = THEME_MODES;
|
||||
applyTheme(getTheme(), modes[(modes.indexOf(getThemeMode()) + 1) % modes.length]);
|
||||
}
|
||||
|
||||
media.addEventListener("change", () => {
|
||||
if (getTheme() === "system") applyTheme("system");
|
||||
if (getThemeMode() === "auto") applyTheme();
|
||||
});
|
||||
Reference in new issue
Block a user