diff --git a/ags-v3/app.ts b/ags-v3/app.ts index 81bd64d..f54e507 100644 --- a/ags-v3/app.ts +++ b/ags-v3/app.ts @@ -7,11 +7,14 @@ import BrainPower from "./widget/panels/BrainPower" app.start({ css: style, main() { - for (const monitor of app.get_monitors()) { + const monitors = app.get_monitors() + for (const monitor of monitors) { Heartbeat(monitor) Bar(monitor) - BrainPower(monitor) } + // Brain Power on primary monitor only + const primary = monitors[0] + if (primary) BrainPower(primary) }, requestHandler(request: any, res: (response: any) => void) { const cmd = String(request) diff --git a/ags-v3/lib/brain.ts b/ags-v3/lib/brain.ts index fcf0f1c..2f50831 100644 --- a/ags-v3/lib/brain.ts +++ b/ags-v3/lib/brain.ts @@ -1,4 +1,5 @@ import GLib from "gi://GLib" +import { exec } from "ags/process" const BRAIN_ROOT = GLib.getenv("BRAIN_ROOT") || readFile(GLib.get_home_dir() + "/.config/brain-path")?.trim() @@ -8,6 +9,10 @@ export interface BrainState { focus: string todos: { open: number; done: number; top3: string[] } session: string | null + intentions: { active: number; names: string[] } + repos: { name: string; status: string }[] + commits: string[] + kernelVersion: string } function readFile(path: string): string | null { @@ -20,10 +25,13 @@ function readFile(path: string): string | null { return null } +function sh(cmd: string): string { + try { return exec(`bash -c "${cmd}"`).trim() } catch { return "" } +} + export function getFocus(): string { const content = readFile(`${BRAIN_ROOT}/focus.md`) if (!content) return "no focus" - // skip header lines, get the meat const lines = content.split("\n").filter( (l) => !l.startsWith("#") && !l.startsWith(">") && l.trim() !== "" && !l.startsWith("---") ) @@ -47,11 +55,7 @@ export function getTodos(): { open: number; done: number; top3: string[] } { } } - return { - open: open.length, - done, - top3: open.slice(0, 3), - } + return { open: open.length, done, top3: open.slice(0, 3) } } export function getSession(): string | null { @@ -66,10 +70,53 @@ export function getSession(): string | null { return null } +export function getIntentions(): { active: number; names: string[] } { + try { + const out = sh(`grep -rl 'status: active' ${BRAIN_ROOT}/intentions/*.yml 2>/dev/null | while read f; do grep '^name:' "$f" | head -1 | sed 's/name: //'; done`) + const names = out.split("\n").filter(Boolean) + return { active: names.length, names: names.slice(0, 5) } + } catch { + return { active: 0, names: [] } + } +} + +export function getRepoStatus(): { name: string; status: string }[] { + const repos = [ + { name: "brain", path: BRAIN_ROOT }, + { name: "toolkit", path: `${BRAIN_ROOT}/toolkit` }, + { name: "progression", path: `${BRAIN_ROOT}/progression` }, + ] + + return repos.map(({ name, path }) => { + const count = sh(`git -C ${path} status --short 2>/dev/null | wc -l`) + const n = parseInt(count) || 0 + return { + name, + status: n === 0 ? "✅" : `⚠️ ${n} fichiers`, + } + }) +} + +export function getRecentCommits(): string[] { + const out = sh(`git -C ${BRAIN_ROOT} log --oneline -5 2>/dev/null`) + return out.split("\n").filter(Boolean) +} + +export function getKernelVersion(): string { + const content = readFile(`${BRAIN_ROOT}/brain-compose.yml`) + if (!content) return "?" + const match = content.match(/^version:\s*"?([^"\n]+)"?/m) + return match ? match[1] : "?" +} + export function getBrainState(): BrainState { return { focus: getFocus(), todos: getTodos(), session: getSession(), + intentions: getIntentions(), + repos: getRepoStatus(), + commits: getRecentCommits(), + kernelVersion: getKernelVersion(), } } diff --git a/ags-v3/style.scss b/ags-v3/style.scss index 6f603b7..e103ab7 100644 --- a/ags-v3/style.scss +++ b/ags-v3/style.scss @@ -4,7 +4,7 @@ * { font-family: $font; - font-size: 13px; + font-size: 16px; font-weight: bold; } @@ -96,10 +96,32 @@ window.BrainPower { padding: 4px 0 4px 16px; } - .brain-terminal-placeholder { + .brain-version { color: $muted; - font-size: 11px; - font-style: italic; - padding: 8px 0 4px 16px; + font-size: 12px; + font-weight: normal; + } + + .brain-intentions-list { + color: $mitsuri; + font-size: 13px; + font-weight: normal; + padding: 4px 0 4px 16px; + } + + .brain-repos-status { + color: $subtext1; + font-size: 13px; + font-weight: normal; + font-family: "Maple Mono NF", monospace; + padding: 4px 0 4px 16px; + } + + .brain-commits-list { + color: $subtext0; + font-size: 12px; + font-weight: normal; + font-family: "Maple Mono NF", monospace; + padding: 4px 0 4px 16px; } } diff --git a/ags-v3/styles/_bar.scss b/ags-v3/styles/_bar.scss index f5f7802..ab818b1 100644 --- a/ags-v3/styles/_bar.scss +++ b/ags-v3/styles/_bar.scss @@ -11,7 +11,7 @@ window.Bar { border: 3px solid rgba(255, 77, 166, 0.60); // $magenta @ 0.60 margin: 6px 8px; padding: 0 8px; - min-height: 38px; + min-height: 48px; } .modules-left, @@ -27,20 +27,20 @@ window.Bar { .prompt-name { color: $magenta; - font-size: 14px; - padding: 0 0 0 10px; + font-size: 17px; + padding: 0 0 0 14px; } .prompt-cursor { color: $lilac; - font-size: 14px; + font-size: 17px; font-weight: 900; } .separator { color: rgba(240, 234, 248, 0.12); // $text @ 0.12 - font-size: 11px; - padding: 0 4px; + font-size: 14px; + padding: 0 6px; font-weight: normal; } @@ -48,14 +48,14 @@ window.Bar { .clock { color: $magenta; font-weight: 900; - font-size: 14px; + font-size: 18px; letter-spacing: 0.04em; padding: 0 10px; } .date { color: $lavande; - font-size: 12px; + font-size: 15px; font-weight: normal; padding: 0 10px 0 2px; } @@ -69,7 +69,9 @@ window.Bar { font-weight: normal; } - .cpu, .ram, .temp { + .gpu { color: $mitsuri; } + + .cpu, .ram, .gpu, .temp { &.warning { color: $champagne; } &.critical { color: $danger; } } @@ -77,8 +79,8 @@ window.Bar { // ── network ── .network { color: $lavande; - font-size: 12px; - padding: 0 8px; + font-size: 15px; + padding: 0 10px; &.disconnected { color: $danger; } } @@ -121,7 +123,7 @@ window.Bar { .media-text { color: $lilac; - font-size: 12px; + font-size: 15px; font-weight: normal; padding: 0 6px; } @@ -135,7 +137,7 @@ window.Bar { min-width: 0; min-height: 0; - label { color: $lilac; font-size: 13px; } + label { color: $lilac; font-size: 16px; } &:hover label { color: $magenta; } } diff --git a/ags-v3/widget/Bar.tsx b/ags-v3/widget/Bar.tsx index acc38c8..572e56b 100644 --- a/ags-v3/widget/Bar.tsx +++ b/ags-v3/widget/Bar.tsx @@ -1,12 +1,12 @@ import app from "ags/gtk3/app" import { Astal, Gtk, Gdk } from "ags/gtk3" import Clock from "./modules/Clock" -import Battery from "./modules/Battery" +// import Battery from "./modules/Battery" // desktop — no battery import Volume from "./modules/Volume" import Network from "./modules/Network" import SystemStats from "./modules/SystemStats" import Media from "./modules/Media" -import SysTray from "./modules/SysTray" +// import SysTray from "./modules/SysTray" // TODO: needs astal-tray (appmenu-glib-translator) import Prompt from "./modules/Prompt" export default function Bar(gdkmonitor: Gdk.Monitor) { @@ -33,7 +33,7 @@ export default function Bar(gdkmonitor: Gdk.Monitor) { cancelHide()} - onHoverLost={(self) => { - const win = self.get_toplevel() as Astal.Window - scheduleHide(win) + onHoverLost={(_self) => { + // disabled for debug — auto-hide off }} > @@ -59,13 +58,10 @@ export default function Bar(gdkmonitor: Gdk.Monitor) { - - diff --git a/ags-v3/widget/Heartbeat.tsx b/ags-v3/widget/Heartbeat.tsx index a156a80..f766f6d 100644 --- a/ags-v3/widget/Heartbeat.tsx +++ b/ags-v3/widget/Heartbeat.tsx @@ -11,7 +11,7 @@ export default function Heartbeat(gdkmonitor: Gdk.Monitor) { exclusivity={Astal.Exclusivity.NONE} anchor={TOP | LEFT | RIGHT} application={app} - layer={Astal.Layer.OVERLAY} + layer={Astal.Layer.TOP} > { diff --git a/ags-v3/widget/modules/Battery.tsx b/ags-v3/widget/modules/Battery.tsx index 1654564..1e870ec 100644 --- a/ags-v3/widget/modules/Battery.tsx +++ b/ags-v3/widget/modules/Battery.tsx @@ -1,38 +1,30 @@ import AstalBattery from "gi://AstalBattery" -import { createBinding, createDerivedBinding } from "ags" +import { createBinding } from "ags" export default function Battery() { const bat = AstalBattery.get_default() - const percentage = createBinding(bat, "percentage") - const charging = createBinding(bat, "charging") + const text = createBinding(bat, "percentage")((p: number) => { + const pct = Math.round(p * 100) + const isCharging = bat.charging + let icon = "" + if (isCharging) icon = "󰂄" + else if (pct > 90) icon = "󰁹" + else if (pct > 70) icon = "󰂁" + else if (pct > 50) icon = "󰁿" + else if (pct > 30) icon = "󰁽" + else if (pct > 10) icon = "󰁻" + else icon = "󰂃" + return `${icon} ${pct}%` + }) - const text = createDerivedBinding( - [percentage, charging], - (p: number, isCharging: boolean) => { - const pct = Math.round(p * 100) - let icon = "" - if (isCharging) icon = "󰂄" - else if (pct > 90) icon = "󰁹" - else if (pct > 70) icon = "󰂁" - else if (pct > 50) icon = "󰁿" - else if (pct > 30) icon = "󰁽" - else if (pct > 10) icon = "󰁻" - else icon = "󰂃" - return `${icon} ${pct}%` - }, - ) - - const cls = createDerivedBinding( - [percentage, charging], - (p: number, isCharging: boolean) => { - const pct = Math.round(p * 100) - if (isCharging) return "battery charging" - if (pct <= 10) return "battery low" - if (pct <= 20) return "battery warning" - return "battery" - }, - ) + const cls = createBinding(bat, "percentage")((p: number) => { + const pct = Math.round(p * 100) + if (bat.charging) return "battery charging" + if (pct <= 10) return "battery low" + if (pct <= 20) return "battery warning" + return "battery" + }) return