diff --git a/dotfiles/fish/functions/mag.fish b/dotfiles/fish/functions/mag.fish new file mode 100644 index 0000000000..3ea9acb838 --- /dev/null +++ b/dotfiles/fish/functions/mag.fish @@ -0,0 +1,8 @@ +function mag --description 'Privately submit a magnet link and return an opaque transfer ID' + if test (count $argv) -eq 0 + echo "usage: mag 'magnet:?xt=urn:btih:…'" >&2 + return 2 + end + + command node "$HOME/plugins/private-magnet/scripts/private-magnet.mjs" add "$argv[1]" +end diff --git a/slab/bin/acbright.swift b/slab/bin/acbright.swift new file mode 100644 index 0000000000..4f3ede6ff0 --- /dev/null +++ b/slab/bin/acbright.swift @@ -0,0 +1,59 @@ +// acbright.swift — absolute brightness get/set for a Mac's BUILT-IN display, +// via Apple's private DisplayServices framework. This is the one path that +// actually works on Apple Silicon laptops: the Homebrew `brightness` CLI fails +// on built-in panels with `error -536870201`, and AppleScript has no brightness +// verb at all. External monitors are a different world (DDC/CI via `m1ddc`); +// this binary is only for neo/blueberry-style built-in Retina displays. +// +// Usage: +// acbright → print current brightness as an integer percent (0–100) +// acbright <0–100> → set brightness to that percent; prints the value set +// acbright +N / -N → nudge by N points relative to current (clamped 0–100). +// The leading sign is what makes it relative — used by +// the menubar's ⌃⌥⌘↑/↓ hotkey so a nudge is one token +// over ssh (no shell arithmetic, works under any shell). +// +// Built for the `fleet-mcp` fan-out, but standalone and dependency-free — +// compile with `swiftc acbright.swift -o acbright` (Command Line Tools suffice). +import Foundation +import CoreGraphics + +typealias GetFn = @convention(c) (UInt32, UnsafeMutablePointer) -> Int32 +typealias SetFn = @convention(c) (UInt32, Float) -> Int32 + +let path = "/System/Library/PrivateFrameworks/DisplayServices.framework/DisplayServices" +guard let handle = dlopen(path, RTLD_NOW) else { + FileHandle.standardError.write(Data("acbright: DisplayServices unavailable\n".utf8)) + exit(1) +} +let display = CGMainDisplayID() + +func fail(_ msg: String) -> Never { + FileHandle.standardError.write(Data("acbright: \(msg)\n".utf8)) + exit(1) +} + +func readBrightness() -> Double { + guard let sym = dlsym(handle, "DisplayServicesGetBrightness") else { fail("no get symbol") } + let getBrightness = unsafeBitCast(sym, to: GetFn.self) + var value: Float = -1 + let rc = getBrightness(display, &value) + if rc != 0 { fail("get returned rc=\(rc)") } + return Double(value) * 100 +} + +if CommandLine.arguments.count >= 2 { + let arg = CommandLine.arguments[1] + guard let n = Double(arg) else { fail("bad percent argument") } + // A leading + or - means relative: nudge from the current level. + let relative = arg.hasPrefix("+") || arg.hasPrefix("-") + let target = relative ? readBrightness() + n : n + let clamped = max(0, min(100, target)) + guard let sym = dlsym(handle, "DisplayServicesSetBrightness") else { fail("no set symbol") } + let setBrightness = unsafeBitCast(sym, to: SetFn.self) + let rc = setBrightness(display, Float(clamped / 100.0)) + if rc != 0 { fail("set returned rc=\(rc)") } + print(Int(clamped.rounded())) +} else { + print(Int(readBrightness().rounded())) +} diff --git a/slab/bin/fleet-mcp.mjs b/slab/bin/fleet-mcp.mjs new file mode 100755 index 0000000000..d5695d15d4 --- /dev/null +++ b/slab/bin/fleet-mcp.mjs @@ -0,0 +1,232 @@ +#!/usr/bin/env node +// fleet-mcp.mjs — set screen brightness and output volume across every fleet +// Mac at once, from any agent session. This is the programmatic sibling of the +// slab menubar's ⌃⌥⌘A dark-mode fan-out (AppDelegate.applyAppearance): same +// idea — one command, all four Macs — but absolute 0–100 levels instead of a +// boolean, and reachable as MCP tools rather than only a global hotkey. +// +// Brightness is NOT uniform across the fleet, so each host declares its `kind`: +// • builtin (neo, blueberry — MacBooks): the Homebrew `brightness` CLI FAILS +// on Apple Silicon built-in panels (error -536870201) and AppleScript has +// no brightness verb, so we use `acbright` — a tiny DisplayServices helper +// (acbright.swift, sibling file) compiled to ~/.local/bin/acbright. +// • ddc (panda, chicken — Mac minis): no built-in panel; the external monitor +// is driven over DDC/CI with `m1ddc set luminance N`. +// Volume IS uniform — `osascript -e 'set volume output volume N'` everywhere. +// +// Transport per host mirrors the dark-mode fan-out: the process runs the +// command locally on whichever Mac it's on (matched by LocalHostName) and +// SSHes to the other three by their ssh-config alias. Hand-rolled JSON-RPC over +// stdio, same house style as frame-mcp / puppet-mcp — node builtins only. +import { spawn } from "node:child_process"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { httpPort, serveHttp, serveStdio } from "../../toolchain/mcp/http-front.mjs"; + +const HERE = dirname(fileURLToPath(import.meta.url)); +const ACBRIGHT_SRC = join(HERE, "acbright.swift"); + +// The four Macs. `host` is the ssh-config alias used from any OTHER fleet Mac +// (blueberry/neo both already have these aliases); `self` is the LocalHostName +// that means "this is me, run locally, skip ssh". `kind` picks the brightness +// mechanism. Keep this list in sync with the menubar's appearanceHosts. +const FLEET = [ + { name: "neo", host: "neo", self: "neo", kind: "builtin" }, + { name: "blueberry", host: "blueberry", self: "blueberry", kind: "builtin" }, + { name: "panda", host: "panda", self: "panda", kind: "ddc" }, + { name: "chicken", host: "chicken", self: "chicken", kind: "ddc" }, +]; + +const SSH_OPTS = ["-o", "ConnectTimeout=6", "-o", "BatchMode=yes"]; +const HOMEBREW = "/opt/homebrew/bin"; // where m1ddc lives; not always on ssh PATH + +let localHostName = null; // resolved once, lazily +function getLocalHostName() { + if (localHostName !== null) return Promise.resolve(localHostName); + return run("scutil", ["--get", "LocalHostName"]) + .then((out) => (localHostName = out.trim())) + .catch(() => (localHostName = "")); +} + +// Run a command, resolve trimmed stdout or reject with stderr. `input` is fed +// to stdin. No shell here — args are exec'd directly. +function run(cmd, args, { timeoutMs = 20000, input } = {}) { + return new Promise((resolve, reject) => { + const child = spawn(cmd, args, { timeout: timeoutMs }); + let out = "", err = ""; + child.stdout.on("data", (d) => (out += d)); + child.stderr.on("data", (d) => (err += d)); + child.on("error", (e) => reject(e)); + child.on("close", (code) => { + if (code === 0) resolve(out); + else reject(new Error((err || out || `exit ${code}`).trim())); + }); + if (input != null) child.stdin.end(input); + else child.stdin.end(); + }); +} + +// Run `script` on a host under a login bash reading from stdin (`bash -ls`): +// locally if it's us, else over ssh. Feeding the script via stdin makes it +// shell-AGNOSTIC — the remote login shell only ever sees the tokens +// `bash -ls`, so a fish-login host (neo) runs the same bash script as the +// others, with zero nested quoting. `-l` puts Homebrew on PATH for m1ddc. +async function onHost(m, script) { + const self = await getLocalHostName(); + if (m.self === self) return run("bash", ["-ls"], { input: script }); + return run("ssh", [...SSH_OPTS, m.host, "bash", "-ls"], { input: script }); +} + +// The remote command that ensures acbright exists (compiling from the deployed +// source if missing) and then invokes it. Built-in hosts have swiftc (Command +// Line Tools) and the repo checked out, but we don't depend on the repo path: +// the source is copied to ~/.local/bin on first build and cached thereafter. +function acbrightCmd(argPart) { + return [ + 'B="$HOME/.local/bin/acbright"', + '[ -x "$B" ] || { mkdir -p "$HOME/.local/bin"; ' + + `swiftc "${remoteSrcPath()}" -O -o "$B" 2>/dev/null || swiftc /tmp/acbright.swift -O -o "$B"; }`, + `"$B" ${argPart}`, + ].join("; "); +} +// acbright.swift is expected at ~/.local/bin/acbright.swift on built-in hosts +// (staged next to the binary). Falls back to /tmp/acbright.swift if a deploy +// dropped it there. The MCP never has to know the repo layout on the far end. +function remoteSrcPath() { + return "$HOME/.local/bin/acbright.swift"; +} + +async function setBrightnessOn(m, level) { + if (m.kind === "builtin") { + const out = await onHost(m, acbrightCmd(String(level))); + return { host: m.name, ok: true, value: Number(out.trim()) || level }; + } + // ddc — m1ddc set luminance (needs Homebrew on PATH; login shell provides it) + await onHost(m, `PATH=${HOMEBREW}:$PATH m1ddc set luminance ${level}`); + return { host: m.name, ok: true, value: level }; +} + +async function setVolumeOn(m, { level, mute }) { + const parts = []; + if (typeof level === "number") parts.push(`set volume output volume ${level}`); + if (typeof mute === "boolean") parts.push(`set volume output muted ${mute ? "true" : "false"}`); + await onHost(m, `osascript ${parts.map((p) => `-e '${p}'`).join(" ")}`); + const value = level !== undefined ? `${level}%` : mute ? "muted" : "unmuted"; + return { host: m.name, ok: true, value }; +} + +// Which machines a call targets: the `machines` arg (subset) or all four. +function resolve(machines) { + if (!machines || !machines.length) return FLEET; + const want = new Set(machines.map((s) => s.toLowerCase())); + const picked = FLEET.filter((m) => want.has(m.name)); + const unknown = [...want].filter((w) => !FLEET.some((m) => m.name === w)); + if (unknown.length) throw new Error(`unknown machine(s): ${unknown.join(", ")} — fleet is ${FLEET.map((m) => m.name).join(", ")}`); + return picked; +} + +// Fan out `fn` over the targets in parallel; never reject — collect per-host +// ok/err so one asleep or offline Mac can't sink the whole call. +async function fanOut(targets, fn) { + const results = await Promise.all( + targets.map((m) => fn(m).catch((e) => ({ host: m.name, ok: false, error: String(e.message || e).trim() }))), + ); + const lines = results.map((r) => (r.ok ? ` ✓ ${r.host} → ${r.value}` : ` ✗ ${r.host}: ${r.error}`)); + const okN = results.filter((r) => r.ok).length; + return { text: `${okN}/${results.length} ok\n${lines.join("\n")}`, results }; +} + +async function toolBrightness({ level, machines } = {}) { + if (typeof level !== "number" || level < 0 || level > 100) throw new Error("`level` must be a number 0–100"); + const { text } = await fanOut(resolve(machines), (m) => setBrightnessOn(m, Math.round(level))); + return [{ type: "text", text: `brightness → ${Math.round(level)}%\n${text}` }]; +} + +async function toolVolume({ level, mute, machines } = {}) { + if (level === undefined && mute === undefined) throw new Error("provide `level` (0–100) and/or `mute`"); + if (level !== undefined && (typeof level !== "number" || level < 0 || level > 100)) throw new Error("`level` must be 0–100"); + const { text } = await fanOut(resolve(machines), (m) => setVolumeOn(m, { level: level === undefined ? undefined : Math.round(level), mute })); + const label = [level !== undefined ? `volume → ${Math.round(level)}%` : null, mute !== undefined ? `muted=${mute}` : null].filter(Boolean).join(", "); + return [{ type: "text", text: `${label}\n${text}` }]; +} + +async function toolList() { + const self = await getLocalHostName(); + const lines = FLEET.map((m) => ` ${m.name}${m.self === self ? " (this host)" : ""} — brightness:${m.kind}, ssh:${m.host}`); + return [{ type: "text", text: `fleet (${FLEET.length} Macs):\n${lines.join("\n")}` }]; +} + +const TOOLS = [ + { + name: "fleet_brightness", + description: + "Set screen brightness on the fleet Macs (neo, blueberry, panda, chicken) at once, to an absolute level 0–100. Built-in laptop panels (neo, blueberry) use the acbright DisplayServices helper; the Mac minis (panda, chicken) drive their external monitors over DDC/CI. Fans out in parallel and reports per-host success; an offline/asleep Mac just fails its line. Optionally target a subset with `machines`.", + inputSchema: { + type: "object", + properties: { + level: { type: "number", description: "Brightness percent, 0–100 (applied to every targeted Mac)." }, + machines: { type: "array", items: { type: "string" }, description: "Optional subset, e.g. [\"panda\",\"chicken\"]. Omit for all four." }, + }, + required: ["level"], + }, + }, + { + name: "fleet_volume", + description: + "Set output volume and/or mute on the fleet Macs (neo, blueberry, panda, chicken) at once. `level` is 0–100; `mute` is a boolean. Uniform across the fleet via osascript. Fans out in parallel with per-host results. Optionally target a subset with `machines`.", + inputSchema: { + type: "object", + properties: { + level: { type: "number", description: "Output volume percent, 0–100." }, + mute: { type: "boolean", description: "Mute (true) or unmute (false) output." }, + machines: { type: "array", items: { type: "string" }, description: "Optional subset. Omit for all four." }, + }, + }, + }, + { + name: "fleet_list", + description: "List the fleet Macs this server controls, their brightness mechanism (builtin/ddc), ssh alias, and which one is the local host.", + inputSchema: { type: "object", properties: {} }, + }, +]; + +async function callTool(name, args) { + switch (name) { + case "fleet_brightness": return toolBrightness(args || {}); + case "fleet_volume": return toolVolume(args || {}); + case "fleet_list": return toolList(); + default: throw new Error(`Unknown tool: ${name}`); + } +} + +async function handleMessage(message) { + const { id, method, params } = message; + try { + switch (method) { + case "initialize": + return { jsonrpc: "2.0", id, result: { protocolVersion: "2024-11-05", capabilities: { tools: {} }, serverInfo: { name: "fleet-mcp", version: "1.0.0" } } }; + case "initialized": + case "notifications/initialized": + return null; + case "ping": + return { jsonrpc: "2.0", id, result: {} }; + case "tools/list": + return { jsonrpc: "2.0", id, result: { tools: TOOLS } }; + case "tools/call": { + const content = await callTool(params?.name, params?.arguments); + return { jsonrpc: "2.0", id, result: { content } }; + } + default: + return { jsonrpc: "2.0", id, error: { code: -32601, message: `Method not found: ${method}` } }; + } + } catch (error) { + if (method === "tools/call") { + return { jsonrpc: "2.0", id, result: { isError: true, content: [{ type: "text", text: String(error.message || error) }] } }; + } + return { jsonrpc: "2.0", id, error: { code: -32000, message: String(error.message || error) } }; + } +} + +const port = httpPort(process.argv, 7770); // 7767 frame, 7769 puppet, 7768 Spotify +if (port) serveHttp({ handleMessage, port, banner: "🔆 fleet-mcp shared daemon" }); +else serveStdio({ handleMessage, banner: "🔆 fleet-mcp server started (fleet_brightness, fleet_volume, fleet_list)" }); diff --git a/slab/bin/signal.mjs b/slab/bin/signal.mjs old mode 100644 new mode 100755