aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-10-19 10:23:45 +0200
committerDanilo M. <danix@danix.xyz>2026-10-19 10:23:45 +0200
commit31f1c15112748eecbdc3ad33891b0c3629504fe7 (patch)
treee674ef5fe890e64c274029d86a30956876173f1d
parented70f032e30ae13485610a9027371d22d3cab14e (diff)
downloaddesktop-assistant-31f1c15112748eecbdc3ad33891b0c3629504fe7.tar.gz
desktop-assistant-31f1c15112748eecbdc3ad33891b0c3629504fe7.zip
mode tool: read and set the desktop modes
dnd, presentation, nolock and quiet through statusctl. The output is read back after a set, so a set that did not land is not reported as done, and a running game's hold on presentation is named. test-mode.sh checks it against a scratch runtime dir. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
-rw-r--r--docs/superpowers/plans/2026-10-19-mode-tool.md100
-rwxr-xr-xexamples/tools/mode.sh48
-rw-r--r--examples/tools/mode.toml11
-rwxr-xr-xexamples/tools/test-mode.sh39
4 files changed, 198 insertions, 0 deletions
diff --git a/docs/superpowers/plans/2026-10-19-mode-tool.md b/docs/superpowers/plans/2026-10-19-mode-tool.md
new file mode 100644
index 0000000..7871903
--- /dev/null
+++ b/docs/superpowers/plans/2026-10-19-mode-tool.md
@@ -0,0 +1,100 @@
+# Desktop Modes Tool Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Let the assistant read and set the desktop modes dnd, presentation, nolock and quiet.
+
+**Architecture:** One tool file (`mode.toml`) and one script (`mode.sh`) in the existing tool format, calling `statusctl`. No change to `assistant.py`. Spec: `docs/superpowers/specs/2026-10-19-mode-tool-design.md`.
+
+**Tech Stack:** bash, statusctl (`~/bin`, on the daemon's PATH).
+
+---
+
+### Task 1: mode.sh with its check
+
+**Files:**
+- Create: `examples/tools/test-mode.sh`
+- Create: `examples/tools/mode.sh`
+
+- [ ] **Step 1: Write the check.** It runs `mode.sh` against a throwaway
+ `XDG_RUNTIME_DIR`, so the real modes are never touched.
+
+```bash
+#!/bin/bash
+# test-mode.sh: self-check for mode.sh against a scratch runtime dir.
+set -u
+here=$(dirname "$0")
+export XDG_RUNTIME_DIR=$(mktemp -d)
+trap 'rm -rf "$XDG_RUNTIME_DIR"' EXIT
+fail=0
+check() { [ "$2" = "$3" ] || { echo "FAIL $1: got '$2', want '$3'"; fail=1; }; }
+
+check "read missing" "$("$here/mode.sh" dnd "")" "dnd: off"
+check "set on" "$("$here/mode.sh" dnd 1)" "dnd: on"
+check "file written" "$(cat "$XDG_RUNTIME_DIR/status.dnd")" "1"
+check "set off" "$("$here/mode.sh" dnd 0)" "dnd: off"
+"$here/mode.sh" dnd yes >/dev/null 2>&1; check "bad value exit" "$?" "1"
+"$here/mode.sh" bogus 1 >/dev/null 2>&1; check "bad mode exit" "$?" "1"
+echo 1 > "$XDG_RUNTIME_DIR/status.gaming"
+check "gaming hold" "$("$here/mode.sh" presentation 0)" \
+ "presentation: on (held on by a running game; manual setting off)"
+[ $fail = 0 ] && echo "ok"
+exit $fail
+```
+
+- [ ] **Step 2: Run it, expect failure** (`mode.sh` missing).
+
+Run: `bash examples/tools/test-mode.sh`
+
+- [ ] **Step 3: Write `examples/tools/mode.sh`** (GPLv2 header as in `playing.sh`).
+
+```bash
+set -uo pipefail
+
+mode=${1:-}
+on=${2:-}
+
+case "$on" in
+ "") ;;
+ 0|1) statusctl "$mode" set "$on" || exit 1 ;;
+ *) echo "on must be 1, 0 or empty, not: $on" >&2; exit 1 ;;
+esac
+
+# Read back rather than echo the request: a set that did not land must not
+# be reported as done. An unknown mode fails here, in statusctl.
+v=$(statusctl "$mode" get) || exit 1
+[ "$v" = 1 ] && s=on || s=off
+
+# statusctl get reads only the manual half of presentation; a running game
+# holds it on regardless (see statusctl's gaming_on).
+g=$(tr -d '[:space:]' < "${XDG_RUNTIME_DIR:-/tmp}/status.gaming" 2>/dev/null)
+if [ "$mode" = presentation ] && [ "$g" = 1 ]; then
+ echo "presentation: on (held on by a running game; manual setting $s)"
+else
+ echo "$mode: $s"
+fi
+```
+
+- [ ] **Step 4: Run the check, expect `ok`.**
+
+Run: `chmod +x examples/tools/mode.sh examples/tools/test-mode.sh && examples/tools/test-mode.sh`
+
+### Task 2: mode.toml
+
+**Files:**
+- Create: `examples/tools/mode.toml`
+
+- [ ] **Step 1: Write it**, exactly as in the spec's "Tool" section.
+- [ ] **Step 2: Self-check still passes:** `python3 assistant.py --test`.
+- [ ] **Step 3: Commit** `examples/tools/mode.sh`, `test-mode.sh`, `mode.toml`.
+
+### Task 3: Install and live check
+
+- [ ] **Step 1:** `cp examples/tools/mode.sh examples/tools/mode.toml ~/.config/desktop-assistant/tools/` then `./assistant.sh restart`.
+- [ ] **Step 2:** Live, over the socket: 10 phrasings (it/en, on/off/query),
+ note tool call, arguments, reply, and `statusctl <mode> get` after. Restore
+ each mode to its starting value afterwards.
+- [ ] **Step 3:** Set-then-ask after unrelated history: the model calls the
+ tool again.
+- [ ] **Step 4:** Record results under Findings in `AGENTS.md`, mark the spec
+ implemented, commit.
diff --git a/examples/tools/mode.sh b/examples/tools/mode.sh
new file mode 100755
index 0000000..d35ca8f
--- /dev/null
+++ b/examples/tools/mode.sh
@@ -0,0 +1,48 @@
+#!/bin/bash
+# desktop-assistant tool: read or set a desktop mode.
+# Copyright (C) 2026 Danilo M. <danix@danix.xyz>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 as published
+# by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+# Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# mode.sh <mode> [0|1]: the desktop modes in the status registry
+# (statusctl, quickshell repo). Empty second argument only reads. Check:
+# test-mode.sh.
+
+set -uo pipefail
+
+mode=${1:-}
+on=${2:-}
+
+case "$on" in
+ "") ;;
+ 0|1) statusctl "$mode" set "$on" || exit 1 ;;
+ *) echo "on must be 1, 0 or empty, not: $on" >&2; exit 1 ;;
+esac
+
+# Read back rather than echo the request: a set that did not land must not
+# be reported as done. An unknown mode fails here, in statusctl.
+v=$(statusctl "$mode" get) || exit 1
+[ "$v" = 1 ] && s=on || s=off
+
+# statusctl get reads only the manual half of presentation; a running game
+# holds it on regardless (see statusctl's gaming_on).
+# Guarded on existence: 2>/dev/null cannot silence a failed input redirect.
+gf="${XDG_RUNTIME_DIR:-/tmp}/status.gaming"
+g=""
+[ -e "$gf" ] && g=$(tr -d '[:space:]' < "$gf" 2>/dev/null)
+if [ "$mode" = presentation ] && [ "$g" = 1 ]; then
+ echo "presentation: on (held on by a running game; manual setting $s)"
+else
+ echo "$mode: $s"
+fi
diff --git a/examples/tools/mode.toml b/examples/tools/mode.toml
new file mode 100644
index 0000000..2fb981e
--- /dev/null
+++ b/examples/tools/mode.toml
@@ -0,0 +1,11 @@
+description = "Desktop modes: dnd (do not disturb), presentation (calls, screen sharing), nolock (screen does not lock), quiet (no notification sounds). Read one, or turn it on or off. Changes it immediately; say what changed."
+command = ["mode.sh", "{mode}", "{on}"]
+timeout = 5
+
+[params.mode]
+type = "string"
+description = "dnd, presentation, nolock or quiet"
+
+[params.on]
+type = "string"
+description = "1 to turn on, 0 to turn off. Empty to only read it."
diff --git a/examples/tools/test-mode.sh b/examples/tools/test-mode.sh
new file mode 100755
index 0000000..e273f88
--- /dev/null
+++ b/examples/tools/test-mode.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+# desktop-assistant tool check: mode.sh against a scratch runtime dir.
+# Copyright (C) 2026 Danilo M. <danix@danix.xyz>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 as published
+# by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+# Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# test-mode.sh: the real modes are never touched, XDG_RUNTIME_DIR is a
+# throwaway directory. Prints "ok" or one FAIL line per broken case.
+
+set -u
+here=$(dirname "$0")
+XDG_RUNTIME_DIR=$(mktemp -d)
+export XDG_RUNTIME_DIR
+trap 'rm -rf "$XDG_RUNTIME_DIR"' EXIT
+fail=0
+check() { [ "$2" = "$3" ] || { echo "FAIL $1: got '$2', want '$3'"; fail=1; }; }
+
+check "read missing" "$("$here/mode.sh" dnd "")" "dnd: off"
+check "set on" "$("$here/mode.sh" dnd 1)" "dnd: on"
+check "file written" "$(cat "$XDG_RUNTIME_DIR/status.dnd")" "1"
+check "set off" "$("$here/mode.sh" dnd 0)" "dnd: off"
+"$here/mode.sh" dnd yes >/dev/null 2>&1; check "bad value exit" "$?" "1"
+"$here/mode.sh" bogus 1 >/dev/null 2>&1; check "bad mode exit" "$?" "1"
+echo 1 > "$XDG_RUNTIME_DIR/status.gaming"
+check "gaming hold" "$("$here/mode.sh" presentation 0)" \
+ "presentation: on (held on by a running game; manual setting off)"
+[ $fail = 0 ] && echo "ok"
+exit $fail