Commit 4bba668b24b5
Changed files (1)
dots
pi
agent
extensions
dots/pi/agent/extensions/auto-theme.ts
@@ -2,11 +2,12 @@
* Auto Theme Extension - Sync pi theme with system color scheme
*
* Automatically switches between light/dark theme based on system preference.
+ * Theme switches happen live without requiring restart.
*
* Features:
* - Reads system color scheme from dconf on startup
* - Watches for dbus signals when color scheme changes
- * - Automatically updates pi theme
+ * - Automatically updates pi theme (live)
* - Commands: /theme-sync, /theme-status
*
* System integration:
@@ -15,18 +16,16 @@
* - Compatible with toggle-color-scheme script
*/
-import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
+import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
import { exec } from "node:child_process";
import { promisify } from "node:util";
-import { promises as fs } from "node:fs";
-import * as path from "node:path";
-import * as os from "node:os";
const execAsync = promisify(exec);
// State
-let isWatching = false;
+let currentContext: ExtensionContext | null = null;
let watchProcess: any = null;
+let currentTheme: "dark" | "light" | null = null;
// =============================================================================
// System Color Scheme Detection
@@ -44,44 +43,44 @@ async function getSystemColorScheme(): Promise<"dark" | "light" | null> {
}
return null;
- } catch (error) {
- console.error("Failed to read system color scheme:", error);
+ } catch {
return null;
}
}
-async function applyTheme(pi: ExtensionAPI, theme: "dark" | "light"): Promise<void> {
+async function applyTheme(theme: "dark" | "light"): Promise<void> {
+ if (!currentContext) {
+ return;
+ }
+
+ if (currentTheme === theme) {
+ return; // Already set
+ }
+
try {
- // Update settings.json directly
- const settingsPath = path.join(os.homedir(), ".pi", "agent", "settings.json");
-
- // Read current settings
- let settings: any = {};
- try {
- const content = await fs.readFile(settingsPath, "utf-8");
- settings = JSON.parse(content);
- } catch {
- // File might not exist yet
- }
-
- // Update theme
- settings.theme = theme;
-
- // Write back
- await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
-
- console.log(`✓ Theme setting updated to: ${theme} (will apply on next restart or /reload)`);
+ // Map to actual theme names
+ // You have: modus-vivendi (dark) and modus-operandi (light)
+ // Pi also has built-in: "dark" and "light"
+ // Prefer modus themes if available, fallback to built-in
+ const themeName = theme === "dark" ? "modus-vivendi" : "modus-operandi";
+ currentContext.ui.setTheme(themeName);
+ currentTheme = theme;
} catch (error) {
- console.error("Failed to update theme:", error);
- throw error;
+ // Fallback to built-in themes if modus themes not found
+ try {
+ currentContext.ui.setTheme(theme);
+ currentTheme = theme;
+ } catch {
+ // Ignore errors - context might not be ready
+ }
}
}
-async function syncTheme(pi: ExtensionAPI): Promise<"dark" | "light" | null> {
+async function syncTheme(): Promise<"dark" | "light" | null> {
const systemScheme = await getSystemColorScheme();
if (systemScheme) {
- await applyTheme(pi, systemScheme);
+ await applyTheme(systemScheme);
return systemScheme;
}
@@ -92,14 +91,12 @@ async function syncTheme(pi: ExtensionAPI): Promise<"dark" | "light" | null> {
// D-Bus Watcher
// =============================================================================
-function startWatching(pi: ExtensionAPI): void {
- if (isWatching) {
- console.log("Already watching for color scheme changes");
- return;
+function startWatching(): void {
+ if (watchProcess) {
+ return; // Already watching
}
// Use dbus-monitor to watch for color-scheme changes
- // This is simpler than using dbus library and works reliably
const dbusMonitor = exec(
"dbus-monitor --session \"type='signal',interface='ca.desrt.dconf.Writer',member='Notify'\"",
{ maxBuffer: 1024 * 1024 }
@@ -115,43 +112,30 @@ function startWatching(pi: ExtensionAPI): void {
for (const line of lines) {
// Look for color-scheme in the signal
if (line.includes("color-scheme")) {
- console.log("Color scheme changed, syncing theme...");
// Debounce: wait a bit for dconf to settle
- setTimeout(() => {
- syncTheme(pi).then((theme) => {
- if (theme) {
- console.log(`Theme synced to: ${theme}`);
- }
- });
+ setTimeout(async () => {
+ await syncTheme();
}, 500);
break;
}
}
});
- dbusMonitor.on("error", (error) => {
- console.error("D-Bus monitor error:", error);
- isWatching = false;
+ dbusMonitor.on("error", () => {
watchProcess = null;
});
- dbusMonitor.on("exit", (code) => {
- console.log(`D-Bus monitor exited with code ${code}`);
- isWatching = false;
+ dbusMonitor.on("exit", () => {
watchProcess = null;
});
watchProcess = dbusMonitor;
- isWatching = true;
- console.log("✓ Watching for system color scheme changes");
}
function stopWatching(): void {
if (watchProcess) {
watchProcess.kill();
watchProcess = null;
- isWatching = false;
- console.log("✓ Stopped watching for color scheme changes");
}
}
@@ -160,35 +144,35 @@ function stopWatching(): void {
// =============================================================================
export default function (pi: ExtensionAPI) {
- // Sync theme on startup
- syncTheme(pi).then((theme) => {
- if (theme) {
- console.log(`✓ Initial theme set to: ${theme} (from system)`);
- } else {
- console.log("Could not detect system color scheme, using current theme");
+ // On session start, sync theme and start watching
+ pi.on("session_start", async (_event, ctx) => {
+ currentContext = ctx;
+
+ const theme = await syncTheme();
+ if (!theme) {
+ // Couldn't detect system scheme, use default
+ currentTheme = "dark"; // Assume dark as fallback
}
+
+ // Start watching for changes
+ startWatching();
});
- // Start watching for changes
- startWatching(pi);
-
- // Cleanup on extension unload
- process.on("exit", () => {
+ // Cleanup on shutdown
+ pi.on("session_shutdown", () => {
stopWatching();
- });
-
- process.on("SIGINT", () => {
- stopWatching();
- process.exit();
+ currentContext = null;
+ currentTheme = null;
});
// Command: /theme-sync
pi.registerCommand("theme-sync", {
- description: "Sync pi theme with system color scheme (applies on next restart)",
+ description: "Sync pi theme with system color scheme",
handler: async (_args, ctx) => {
- const theme = await syncTheme(pi);
+ currentContext = ctx;
+ const theme = await syncTheme();
if (theme) {
- ctx.ui.notify(`Theme set to: ${theme} (restart pi to apply)`, "success");
+ ctx.ui.notify(`Theme synced to: ${theme}`, "success");
} else {
ctx.ui.notify("Could not detect system color scheme", "error");
}
@@ -200,26 +184,11 @@ export default function (pi: ExtensionAPI) {
description: "Show current theme sync status",
handler: async (_args, ctx) => {
const systemScheme = await getSystemColorScheme();
-
- // Read current theme from settings.json
- let currentTheme = "default";
- try {
- const settingsPath = path.join(os.homedir(), ".pi", "agent", "settings.json");
- const content = await fs.readFile(settingsPath, "utf-8");
- const settings = JSON.parse(content);
- currentTheme = settings.theme || "default";
- } catch {
- // Ignore
- }
+ const watching = watchProcess !== null;
+ const themeName = currentTheme === "dark" ? "modus-vivendi" : currentTheme === "light" ? "modus-operandi" : "unknown";
- const status = `
-System color scheme: ${systemScheme || "unknown"}
-Pi theme (in settings.json): ${currentTheme}
-Watching for changes: ${isWatching ? "yes" : "no"}
- `.trim();
-
- ctx.ui.notify(status, "info");
- console.log(status);
+ const message = `System: ${systemScheme || "unknown"} | Theme: ${themeName} | Watching: ${watching ? "yes" : "no"}`;
+ ctx.ui.notify(message, "info");
},
});
@@ -227,11 +196,12 @@ Watching for changes: ${isWatching ? "yes" : "no"}
pi.registerCommand("theme-watch", {
description: "Toggle watching for system color scheme changes",
handler: async (_args, ctx) => {
- if (isWatching) {
+ if (watchProcess) {
stopWatching();
ctx.ui.notify("Stopped watching for color scheme changes", "info");
} else {
- startWatching(pi);
+ currentContext = ctx;
+ startWatching();
ctx.ui.notify("Started watching for color scheme changes", "info");
}
},