feat(ags-v3): brain HUD terminal — kitty toggle + single instance + COSMIC window rule

- BrainPower panel: dashboard only (VTE embed pas compatible AGS JSX)
- toggle-brain.sh: ouvre/ferme panel AGS + terminal Kitty (single instance via pgrep)
- Kitty class brain-hud-terminal pour COSMIC window rule (floating)
- app.ts: cleanup focusBrainTerm removed
- style.scss: brain-terminal class + brain-commits-list
This commit is contained in:
2026-03-26 15:54:49 +01:00
parent 9eaaa01663
commit 29b4c54370
3 changed files with 57 additions and 81 deletions

View File

@@ -124,4 +124,11 @@ window.BrainPower {
font-family: "Maple Mono NF", monospace; font-family: "Maple Mono NF", monospace;
padding: 4px 0 4px 16px; padding: 4px 0 4px 16px;
} }
.brain-terminal {
background: #1a0e27;
border-radius: 0 0 $radius 0;
padding: 4px;
min-height: 400px;
}
} }

View File

@@ -1,3 +1,32 @@
#!/bin/bash #!/bin/bash
# Toggle Brain Power panel via AGS IPC # Toggle Brain Power — dashboard AGS + terminal Kitty (single instance)
ags request "toggle-brain" 2>/dev/null BRAIN_ROOT="${BRAIN_ROOT:-$HOME/Dev/Brain}"
KITTY_CLASS="brain-hud-terminal"
# Check if brain kitty is already running (by window class)
kitty_pid=$(pgrep -f "class $KITTY_CLASS" | head -1)
if [ -n "$kitty_pid" ]; then
# Close everything
ags request "toggle-brain" 2>/dev/null
kill "$kitty_pid" 2>/dev/null
else
# Open everything
ags request "toggle-brain" 2>/dev/null
kitty \
--class "$KITTY_CLASS" \
--title "🧠 Brain HUD" \
--override remember_window_size=no \
--override initial_window_width=60c \
--override initial_window_height=30c \
--override background_opacity=0.94 \
--override background=#1a0e27 \
--override foreground=#f0eaf8 \
--override cursor=#ff4da6 \
--override font_size=13 \
--override confirm_os_window_close=0 \
--directory "$BRAIN_ROOT" \
-e zsh -c "echo '🧠 Brain HUD — navigate mode'; echo ''; exec zsh" \
&
disown
fi

View File

@@ -2,6 +2,7 @@ import app from "ags/gtk3/app"
import { Astal, Gtk, Gdk } from "ags/gtk3" import { Astal, Gtk, Gdk } from "ags/gtk3"
import { createPoll } from "ags/time" import { createPoll } from "ags/time"
import { getBrainState } from "../../lib/brain" import { getBrainState } from "../../lib/brain"
import GLib from "gi://GLib"
function BrainContent() { function BrainContent() {
const state = createPoll("", 10000, () => JSON.stringify(getBrainState())) const state = createPoll("", 10000, () => JSON.stringify(getBrainState()))
@@ -9,109 +10,55 @@ function BrainContent() {
const version = state((s: string) => { const version = state((s: string) => {
try { return `kernel v${JSON.parse(s).kernelVersion}` } catch { return "" } try { return `kernel v${JSON.parse(s).kernelVersion}` } catch { return "" }
}) })
const focus = state((s: string) => { const focus = state((s: string) => {
try { return JSON.parse(s).focus } catch { return "..." } try { return JSON.parse(s).focus } catch { return "..." }
}) })
const session = state((s: string) => { const session = state((s: string) => {
try { try { return JSON.parse(s).session || "aucune session active" } catch { return "..." }
const sess = JSON.parse(s).session
return sess || "aucune session active"
} catch { return "..." }
}) })
const intentionsTitle = state((s: string) => { const intentionsTitle = state((s: string) => {
try { try {
const i = JSON.parse(s).intentions const i = JSON.parse(s).intentions
return ` INTENTIONS (${i.active} active${i.active > 1 ? "s" : ""})` return ` INTENTIONS (${i.active} active${i.active > 1 ? "s" : ""})`
} catch { return " INTENTIONS" } } catch { return " INTENTIONS" }
}) })
const intentionsList = state((s: string) => { const intentionsList = state((s: string) => {
try { try {
const names = JSON.parse(s).intentions.names return JSON.parse(s).intentions.names.map((n: string) => `${n}`).join("\n") || " aucune"
return names.map((n: string) => `${n}`).join("\n") || " aucune"
} catch { return "..." } } catch { return "..." }
}) })
const todosTitle = state((s: string) => { const todosTitle = state((s: string) => {
try { try { const t = JSON.parse(s).todos; return `${t.open} ouverts / ${t.done} faits` } catch { return "" }
const t = JSON.parse(s).todos
return `${t.open} ouverts / ${t.done} faits`
} catch { return "" }
}) })
const todosList = state((s: string) => { const todosList = state((s: string) => {
try { try {
const t = JSON.parse(s).todos.top3 return JSON.parse(s).todos.top3.map((item: string) => `${item}`).join("\n") || " rien"
return t.map((item: string) => `${item}`).join("\n") || " rien"
} catch { return "..." }
})
const reposStatus = state((s: string) => {
try {
const repos = JSON.parse(s).repos
return repos.map((r: any) => ` ${r.name.padEnd(14)} ${r.status}`).join("\n")
} catch { return "..." }
})
const commitsList = state((s: string) => {
try {
const commits = JSON.parse(s).commits
return commits.map((c: string) => ` ${c}`).join("\n") || " aucun"
} catch { return "..." } } catch { return "..." }
}) })
return ( return (
<box orientation={Gtk.Orientation.VERTICAL} class="brain-content"> <box orientation={Gtk.Orientation.VERTICAL} class="brain-content">
{/* FOCUS */}
<box class="brain-section"> <box class="brain-section">
<label class="brain-section-title" label=" FOCUS" xalign={0} /> <label class="brain-section-title" label=" FOCUS" xalign={0} />
<label class="brain-version" label={version} xalign={1} hexpand /> <label class="brain-version" label={version} xalign={1} hexpand />
</box> </box>
<label class="brain-focus" label={focus} xalign={0} wrap /> <label class="brain-focus" label={focus} xalign={0} wrap />
<box class="brain-divider" /> <box class="brain-divider" />
{/* SESSION */}
<box class="brain-section"> <box class="brain-section">
<label class="brain-section-title" label=" SESSION" xalign={0} /> <label class="brain-section-title" label=" SESSION" xalign={0} />
</box> </box>
<label class="brain-session" label={session} xalign={0} /> <label class="brain-session" label={session} xalign={0} />
<box class="brain-divider" /> <box class="brain-divider" />
{/* INTENTIONS */}
<box class="brain-section"> <box class="brain-section">
<label class="brain-section-title" label={intentionsTitle} xalign={0} /> <label class="brain-section-title" label={intentionsTitle} xalign={0} />
</box> </box>
<label class="brain-intentions-list" label={intentionsList} xalign={0} /> <label class="brain-intentions-list" label={intentionsList} xalign={0} />
<box class="brain-divider" /> <box class="brain-divider" />
{/* TODOS */}
<box class="brain-section"> <box class="brain-section">
<label class="brain-section-title" label="󰄲 TODOS" xalign={0} /> <label class="brain-section-title" label="󰄲 TODOS" xalign={0} />
<label class="brain-todos-count" label={todosTitle} xalign={1} hexpand /> <label class="brain-todos-count" label={todosTitle} xalign={1} hexpand />
</box> </box>
<label class="brain-todos-list" label={todosList} xalign={0} /> <label class="brain-todos-list" label={todosList} xalign={0} />
<box class="brain-divider" />
{/* REPOS */}
<box class="brain-section">
<label class="brain-section-title" label=" REPOS" xalign={0} />
</box>
<label class="brain-repos-status" label={reposStatus} xalign={0} />
<box class="brain-divider" />
{/* COMMITS */}
<box class="brain-section">
<label class="brain-section-title" label=" DERNIERS COMMITS" xalign={0} />
</box>
<label class="brain-commits-list" label={commitsList} xalign={0} />
</box> </box>
) )
} }
@@ -130,28 +77,21 @@ export default function BrainPower(gdkmonitor: Gdk.Monitor) {
application={app} application={app}
layer={Astal.Layer.OVERLAY} layer={Astal.Layer.OVERLAY}
> >
<eventbox <box orientation={Gtk.Orientation.VERTICAL} class="brain-panel">
onHoverLost={() => { <box class="brain-header">
const win = app.get_window("brain-power") <label class="brain-title" label=" BRAIN POWER" hexpand xalign={0} />
if (win) win.visible = false <button
}} class="brain-close"
> onClicked={() => {
<box orientation={Gtk.Orientation.VERTICAL} class="brain-panel"> const win = app.get_window("brain-power")
<box class="brain-header"> if (win) win.visible = false
<label class="brain-title" label=" BRAIN POWER" hexpand xalign={0} /> }}
<button >
class="brain-close" <label label="✕" />
onClicked={() => { </button>
const win = app.get_window("brain-power")
if (win) win.visible = false
}}
>
<label label="✕" />
</button>
</box>
<BrainContent />
</box> </box>
</eventbox> <BrainContent />
</box>
</window> </window>
) )
} }