summaryrefslogtreecommitdiff
AgeCommit message (Collapse)Author
10 hourspi compact-tools: let the forum tool draw its own rowsHEADmasterIgor Soarez
Compacted, a forum send showed only its name; its own renderer shows the message, folded to 10 lines.
10 hourspi forum: give a fresh session its notice with the first runIgor Soarez
Appended to a session with no conversation yet -- a new pi, or just after /new -- the join notice sat ahead of the system message pi records at the first run, and pi then sent that message without its content: the provider refused every request with a 400. Queue it for the next turn instead, which pi places after the system message.
10 hourspi forum: fold long messages in the transcript, whole on expandIgor Soarez
A message of up to 4000 characters printed in full in both panes; show its first 10 lines, or 1000 characters, and how much more there is, as pi does for tool output. The model still gets the whole text.
2 dayspi forum: let agents the user puts in a forum find and message each otherIgor Soarez
/forum <name> puts this pi in a forum; its members can list each other, post to it (waking only those mentioned), read its history, set its pin, and send each other direct messages, delivered the way alarm.ts delivers alarms. Only the user adds an agent; any pane can remove one. Forums and their posts live in ~/.pi/forum until /forum delete; membership lasts as long as the pi process.
4 daysyubikey-git: three keys, one per YubiKey; any one of them will doIgor Soarez
7 dayspi alarm: keep pending alarms across /reloadIgor Soarez
/reload dropped every pending alarm and stopped its when_done command, without a word. An agent that had ended its turn to wait for one was never woken -- nor a pi-goal goal_wait counting on it. The old copy of the extension now leaves what is pending on globalThis instead: each alarm as plain data, the due queue, the next id, and a when_done command's ChildProcess with its own listeners removed. The copy pi loads next arms fresh timers from each alarm's absolute time, listens on the command again (one that exited meanwhile still holds its status), and resumes polling PIDs. Nothing crosses that would reach the old, stale pi or ctx. A handoff from another session or another HANDOFF_VERSION is refused and its commands stopped, with a notice. If nothing takes it within 30s -- the file failed to load, or was removed -- the old copy stops its commands. Quitting, /new, /resume and /fork still drop everything.
8 dayspi: generate mcp.json from ~/.config/pi/mcp.d fragmentsIgor Soarez
pi reads user-level MCP servers from one file, and a private layer linked that file to the same copy on every host -- so every host got a server only one machine could run. Build it the way models.json is built, from fragments another layer or one machine drops in mcp.d; this repo ships none, and with none the file is left absent. pi-models.py becomes pi-merge.py, which takes the key to merge and an optional base file, and serves both.
10 dayssz-final-review-loop: wait with a wake armed, not blocked in sz-wait-forIgor Soarez
The loop's agents blocked in a foreground sz-wait-for because nothing could bring an agent back from a background one. Both harnesses now can: pi's alarm fires when a when_done command exits, and Claude Code starts a new turn when a run_in_background Bash task exits (checked on 2.1.310). So an agent arms sz-wait-for as a wake and ends its turn: no context resent every 30 minutes to re-arm, the human can talk to it while it waits, and "arm, then signal" becomes literal. The foreground wait stays for a harness with neither. The invariant is unchanged in substance: never idle, where idle now means the other side's turn with no wake armed. sz-wait-for stays the watcher, for its settle window. pi-subagent: §5 says how to be woken in Claude Code too.
11 dayspi alarm: fire when a command exits or when processes exitIgor Soarez
when_done runs a command (bash -c, in pi's cwd, output to a log file) and fires when it exits, with its exit status; cancelling the alarm or ending the session stops it. when_exits polls PIDs already running and fires once all are gone. Either takes an optional deadline, 24 hours by default, at which the alarm fires anyway and a still-running command is stopped. A process exiting is a signal any condition can be turned into -- gh run watch, sz-wait-for, an until-curl loop -- so the agent can start something long, end its turn and be woken, instead of blocking on it. The delivery path is the existing one, unchanged. pi-subagent: §5 sets an alarm on the runs' PIDs instead of waiting.
11 daystmux: advertise OSC 8 hyperlinks so pi and other TUIs link under tmuxIgor Soarez
11 daystmux: Shift+Enter reaches pi, Neovim and Claude Code under GhosttyIgor Soarez
With extended-keys off, tmux hands every pane a plain CR for Shift+Enter and Ctrl+Enter, so pi submits where it should add a line, and warns about it at every start. Turning it on is not enough on its own, though pi's warning then goes quiet: tmux asks the outer terminal for modified keys only when it knows the terminal can send them, and tmux does not recognise Ghostty, whose terminfo does not declare it either. So Ghostty also gets the extkeys feature, and the format is csi-u, the one pi asks for. `on`, not `always`, which would send zsh ESC[13;2u for Shift+Enter. The paste corruption the old comment blamed on extended keys shows up only in a display-popup, where tmux re-encodes the newlines of a paste for an app that asked for modified keys; normal panes get the pasted bytes unchanged. Nothing here opens popups. Checked on tmux 3.7c against a pty answering as Ghostty 1.3.1, with pi itself in the pane, and by hand in Ghostty on a separate tmux server. The csi-u line is set with -q, since extended-keys-format arrived in tmux 3.5; 3.4 was not tried, only an unknown option name standing in for it.
12 daysyubikey-git: opt a host in to git over ssh at git.soarez.net with the ↵Igor Soarez
YubiKey git key; ssh entry includes enabled.d/
13 dayspi: track pirc-extension default branch instead of pinning v0.1.1Igor Soarez
14 daysclaude: never reach the operator's tmux server by accidentIgor Soarez
2026-10-23pi alarm: steer a due alarm into a running taskIgor Soarez
An alarm that fell due mid-task used to wait in the extension until the run settled. Now it is steered in: pi adds it after the tool calls in progress finish, before the next model call, and the model weighs it against the task. The alarm text tells it to say which it chose. pi's Esc empties its queue and drops queued extension messages without a word, so an alarm stays listed until message_end shows it in the conversation. One not seen by the end of a stopped run is held and sent again after the next run completes; resending into that run could deliver it twice, since pi takes one queued steer per turn and other aborts leave the original queued.
2026-10-22pi: alarm extension, timed notes the agent sets for itselfIgor Soarez
An `alarm` tool sets, lists and cancels alarms: after a delay, at a wall-clock time, or repeating (at most 12 fires, the last within 24 hours). A fired alarm is a pi.sendMessage custom message, customType "alarm", rendered as set by the agent rather than typed by the user, and wrapped in fixed text the agent cannot drop: it is the agent's own earlier note, not the user's, and grants no permission -- outward actions still need the user's approval. /alarms lists and cancels. An alarm due while pi is busy waits for the task to finish, held here rather than in pi's queue, which drops a custom message on Esc. A run that ended stopped -- Esc, an error, or the abort before /compact, /new, /resume and /fork -- holds due alarms until a later run completes. Timers die with the session; the tool is withdrawn outside the TUI. Reviewed by Fable and GLM 5.3 Flash. Fixed from the review: unbounded every_minutes overflowed setTimeout into 1 ms fires; a due alarm ran right after Esc, or unseen on a session being replaced; stale repeat fires piled up; the session_compact delivery never ran; impossible dates and times skipped by a clock change were accepted; the note could forge the wrapper's delimiters. Declines dot request 20261021-220352-work-laptop-c46033 (sz-agent-alarm, a skill typing into the agent's tmux pane) in favour of this: a custom message is recorded and shown as the agent's, needs no screen-scraping, and loads only in pi.
2026-10-21pi-review-panel: GLM 5.3 Flash and DeepSeek V4.1 Flash, from OpenRouterIgor Soarez
They replace GLM 5.2 and DeepSeek V4 Flash on both providers: fireworks returns 404 for glm-5p2, and neither fireworks model is in the work laptop's catalog. The default panel is six runs.
2026-10-21pi-subagent: opt-in sessions, to follow up, resume after a timeout, or forkIgor Soarez
run-agent.sh still runs with --no-session by default. --session saves the run as session <name> under <workdir>/sessions/; --followup TEXT sends the next message in it, from any provider and model; --fork FROM starts <name> as a copy of FROM. A follow-up or fork inherits --tools, --thinking, --cwd and --context-files, so a read-only subagent stays read-only, and the earlier round's files move to <name>.round<N>.*. Sessions are opened by file path: by id, pi looks only among sessions started in the current directory and otherwise silently creates an empty one, exit 0. A fresh retry moves the old session aside, since pi would resume it given the same id. pi-review-panel launches members with --session, so a contradiction can be put to both reviewers before it is settled; verdicts are still tallied from the original reviews. From dot request 20261021-123436-work-laptop-d0adf2.
2026-10-21skills: pi-subagent, extracted from pi-review-panel, which now runs on itIgor Soarez
The panel's mechanics -- headless pi runs that don't hang, answer extraction, billed cost, retries, verifying what comes back -- are not review-specific. pi-subagent covers one delegated task, one brief fanned out to several models, or a job split into parallel parts; pi-review-panel keeps only what a review adds. run-agent.sh replaces run-member.sh and fixes what it got wrong: - `timeout` is not on a Mac without coreutils, which failed every member with exit 127; it falls back to gtimeout, then perl's alarm (142 -> 124). - pi exits 0 when the provider refuses the request, or the answer is empty; both are now exit 1, with stopReason and error in the cost file. - a retry moves the earlier attempt to <name>.attempt<N>.* instead of overwriting its cost file. New options: --tools (an allowlist; read,grep,find,ls is read-only by construction), --cwd, --context-files, --brief, --deliver. status.sh replaces the `pgrep -f run-member.sh` loop, which counted every panel on the machine, and prints the cost table. From dot request 20261021-114826-work-laptop-2b868c.
2026-10-19nvim: FantasqueSansM Nerd Font Mono in NeovideIgor Soarez
Keeps the h16 size the Agave line had. From dot request 20261019-093806-work-laptop-3f5dd6, font part only: the cmd+c/cmd+v mappings it also asked for are already set further down the same block and would override them.
2026-10-19ghostty: stable update channel, download updates in the backgroundIgor Soarez
The tip channel ships a build for every commit to main, so ghostty offered an update every day. Stable prompts only for tagged releases, and auto-update = download fetches it before asking. A Mac already on a tip build is ahead of stable and Sparkle will not downgrade it: install the latest stable on it once by hand. From dot request 20261019-093208-work-laptop-2881de.
2026-10-18alacritty-mosh: unique per-launch config pathsIgor Soarez
The per-target files in /tmp/alacritty were deleted by the session's cleanup trap, so a second window to the same host (or a relaunch racing a previous exit) lost its config mid-flight and Alacritty logged "Unable to load config ... File not found" on live reload. Suffix the paths with the launcher PID so each instance owns its own files.
2026-10-17theme-gen: derive tmux and ghostty colours for any themeIgor Soarez
`theme --set <mode> <name>` needs a tmux conf and a ghostty colour file that alacritty-format themes do not provide, so such a name fell back to the generic dark/light tmux colours and left ghostty unchanged, exiting 0. bin/theme-gen reads a theme in alacritty .toml format (by alacritty-theme checkout name or path) or Ghostty's bundled `palette = N=#hex` format, and writes config/tmux/colors/<name>.conf and config/ghostty/colors/<name>, plus config/alacritty/colors/<name>.toml for a ghostty-format source. Generated with it: sonokai, solarized_osaka_night, kanagawabones and dracula_plus_high_contrast, with nvim stand-ins for the three that have no nvim port installed. Parsed keys go through eval, so only plain section.key names and hex values are accepted: a theme file could otherwise run commands. From dot request 20261017-110255-work-laptop-48c22c.
2026-10-13dot-request: --inbox, and xz compression past 400 KBIgor Soarez
--inbox files into another inbox than dot: the path it posts to and the namespace it signs in both follow it, so the key signs for that inbox only. DOT_REQUEST_URL still overrides the whole URL for testing. A signed request over 400 KB -- the inbox's limit on the wire -- now goes xz -9e compressed with Content-Encoding: xz, which the inbox undoes before checking anything; a smaller one goes as it is. --compress and --no-compress override that either way. Compressing needs xz, so it joins the Brewfile; without it a request that must be compressed is refused, never sent plain. Checked against a local inbox: 148 KB sent plain, 740 KB compressed to 37 KB and accepted, --no-compress over the limit refused with 413, --compress on a small request accepted, a bad --inbox name refused before signing.
2026-10-12nvim: one pyright client per project, each on its own venvIgor Soarez
pyright resolves one pythonPath per server instance, so in a monorepo with a venv per service, every file was checked against whichever interpreter the single client found. The root is now the nearest pyproject.toml, falling back to the markers nvim-lspconfig uses, and the client points at the nearest .venv at or above it -- a uv workspace's shared venv covers members without one. The requested version set params.settings in before_init, which the initialize request does not carry, and looked the venv up with vim.fs.find on a name containing slashes, which 0.11 never matches and which searches down the tree rather than up. Setting config.settings there works because each client gets its own copy of the config and keeps that table as client.settings. Checked with the real config and pyright 1.1 on a mock workspace: a service with its own venv, a member using the workspace's, a requirements.txt project with none, and the workspace root each got their own client and interpreter, and each file resolved only the module installed in its own venv. From change request a48b26.
2026-10-12nvim: ~/x in the space+p directory picker, where it existsIgor Soarez
The picker searched only /Volumes/Sense (or ~/src) and ~/.local/share/nvim, so nothing under ~/x could ever be offered. It is added behind the same isdirectory check as the other roots, so a machine without ~/x passes find no missing path. From change request cbe283.
2026-10-07nvim: render markdown in the bufferIgor Soarez
render-markdown.nvim draws headings, code blocks, tables and checkboxes in place, in the one buffer, and leaves the cursor line raw so editing is unaffected; <leader>mt toggles it. The markdown and markdown_inline parsers join the install list, which also gives noice's LSP hover rendering the parsers it was already set up to use. None of the colourschemes here define the @markup.heading.N.markdown captures the plugin colours headings from, so every level rendered alike. A ramp fills them in, only where the theme left the group empty, and again on each theme switch. Checked with the real config: 26 marks on a test file, conceallevel 3, H1-H3 in distinct bold colours. AGENTS.md also stops listing nvim as having no machine-local hook: lua/local.lua is one, gitignored and loaded by cfg-watch after the plugins. From change request 12882c.
2026-10-05zsh: Caps Lock no longer types junk at a prompt without tmuxIgor Soarez
Caps Lock sends F20, which a terminal with no tmux in front of zsh passes on as ESC[34~ (Alacritty 0.17.0, input/keyboard.rs). Nothing bound it, so zsh split the sequence and typed what was left at the prompt, once per repeat. Bound as a whole to nothing, it is consumed as one key; replayed over a pty, three presses typed 4~4~4~ before and nothing after. The zsh half of the fix for Neovim and tmux. From change request 47bf8a.
2026-10-05pi: the session name in herdr's Agent sidebarIgor Soarez
herdr labels every pi pane "pi", so with several open the rows cannot be told apart. herdr 0.9.3's socket API has pane.report_metadata, whose display_agent replaces that label without touching lifecycle state; this reports the session name there on start and on every rename, auto-named titles included. Interactive sessions only: a pi -p or subagent run from inside a pane inherits its HERDR_* variables and would otherwise rename the pane after itself. Checked against a fake herdr socket: a print-mode run sends nothing, while the socket accepted a control message. Outside herdr it returns at once. The pi APIs it uses -- ctx.mode "tui", getSessionName, session_info_changed's name -- are as in pi 1.0.0, and link/pi.sh links it in like any tracked extension. From change request e053eb.
2026-10-05Caps Lock no longer types <F22> into NeovimIgor Soarez
Caps Lock sends F20, and the terminal passes it on as well as Wispr Flow seeing it. tmux reads it as S-F10 and hands the pane ESC[21;2~, which Neovim decodes as <F22> and, with nothing mapped to it, types out as text -- once per repeat while the key is held. Dropped in both places, each enough alone: Neovim maps <F20> and <F22> to nothing, terminal mode included so a :terminal does not pass it to its shell, and tmux binds S-F10 to an empty command, which also swallows a real Shift+F10 that nothing here uses. The empty binding loads on tmux 3.5a and 3.4. From change request cee596.
2026-10-05pi: extensions as a directory of links, so a machine can have its ownIgor Soarez
~/.pi/agent/extensions was a link to the repo's directory, so anything that installs a pi extension wrote into this checkout -- herdr's pi integration did, on one machine. It is now a real directory with the repo's tracked extensions linked in one by one, and a file a machine adds there stays that machine's. pi follows the links, and skips a dangling one silently, so an extension deleted from the repo leaves a harmless stale link. The old directory link is removed first: linking into it would write through to the repo and turn its files into links to themselves. Only tracked files are linked, so a machine's leftovers in the repo's directory are not linked back. The filed version put this loop in link.sh; it lives in link/pi.sh, since link.sh holds one-line links only and a part also obeys ~/.config/dot/skip. From change request 3f6b05.
2026-10-05alacritty: the mux launch in its own import, overridable per machineIgor Soarez
Alacritty merges the importing file last, over all of its imports (v0.17.0: merge(imports, config)), so the inline [terminal.shell] could not be replaced from local.toml at all; local.toml only ever got away with keys the main file leaves unset. The launch now lives in mux.toml, imported first, and local.toml, imported after it, can replace it. But only in the string form. Alacritty's merge appends arrays rather than replacing them, key by key (serde_utils.rs), so a [terminal.shell] table in local.toml would put its args after tmux's, zsh would run the first -c, and the window would open in tmux with no error -- the override the filed request described would not have worked. mux.toml and AGENTS.md say how to do it: `[terminal] shell = "/path/to/launcher"`, which replaces the whole table. No machine changes today: the existing local.toml files set fonts only. From change request 0bd5fb.
2026-10-05pi: compact tool output, pinned, 3-line previews for every toolIgor Soarez
pi-compact-tools draws each tool call as a short, expandable row; Ctrl+O shows the whole result. Every built-in and every custom tool, MCP ones included, is compacted and starts as a 3-line preview -- previewLines only applies to rows that do not start hidden, so nothing does. Pinned to the v0.14.0 commit, byte-identical to the npm tarball: the package had 32 releases in its first three weeks, and unpinned that would be new code across the fleet nearly every day. The config is linked rather than generated so it is the same everywhere, and /compact-tools saves through the link by design. AGENTS.md now says which linked files their tools rewrite, and that such a change is not an agent's to commit or revert. From change request 4d685a.
2026-10-03tmux: machine-local overrides in ~/.tmux.conf.localIgor Soarez
tmux was one of the configs a machine could not adjust without editing the shared file. ~/.tmux.conf.local is now loaded last, so it wins over everything, colours included, and is skipped silently where there is none. Not under ~/.config/tmux, which links into this repo. The filed version loaded it with `if-shell -b`, which runs in the background and applies the file after the rest of the config, in no fixed order; `source-file -q` does the same job in order, without a shell. -q checked on tmux 3.5a and 3.4. From change request 379803.
2026-10-03install.sh: Alacritty from its release DMG, under a stated ruleIgor Soarez
Homebrew disabled the alacritty cask on 2026-09-01: upstream builds are ad-hoc signed and not notarized. Existing installs kept it, but a new Mac got no Alacritty, and this repo's config, Dock, Hammerspoon and terminal-set-opacity all assume one. Clearing a quarantine flag is now allowed under one rule instead of never: the file is pinned by a hash reviewed here, its signature verifies as unmodified, and the app is named explicitly. Alacritty passes -- the v0.17.0 DMG matches GitHub's published digest, and codesign --verify --deep --strict passes. toggleMute failed on the signature, which is why it stays dropped. The hash is hard-coded rather than looked up, since a checksum fetched beside the file catches corruption, not substitution. The filed version wrapped the steps in `( set -euo pipefail ... ) || warn`, and bash ignores set -e on the left of ||: a wrong hash still installed the app. Each step now exits by hand. Tested against a scratch target with the right hash, a wrong hash, and a failing signature check. From change request 56fc45.
2026-10-03pi: auto-named sessions, pinned to a commitIgor Soarez
pi --resume listed each session by its raw first message, often blank or a system-reminder blob. pi-auto-session-name has the active model write a short title instead, through pi's own model call, and never overwrites a name set by hand. Pinned to the repo's commit rather than the npm version: the package is days old with one maintainer and runs inside every pi on every machine, so an update should arrive only when chosen. npm refuses to reuse a version number, but a commit hash does not depend on npm's policy at all. The commit's extension is byte-identical to the published 1.0.0 tarball, and needs no build. From change request 161e51.
2026-10-02Revert a by-hand dotfiles-sync that published one machine's Claude settingsIgor Soarez
Running dotfiles-sync by hand on two machines, to roll out the pi change, also published one machine's local edits to config/claude/settings.json -- "model": "opus" and switchModelsOnFlag -- which every machine would then have inherited. That file changing on a machine in use is its normal state, not a change to share.
2026-10-02SyncIgor Soarez
2026-10-02pi: drop pi-mcp-adapter, MCP is native nowIgor Soarez
pi 1.0.0 has MCP built in, and while the adapter is installed it suppresses that and prints a warning at every start. The browser gates that answered the adapter's approval event now hook pi's own tool_call as well. From change request 562974.
2026-10-01bin/wispr-keys: Flow dictation on the Caps Lock keyIgor Soarez
Push to talk on F20, Command Mode on Ctrl+F20, written into Flow's config.json because its settings screen refuses F20 without a modifier while its key listener accepts it. The key codes and action names come from reading Flow 1.6.1021's app.asar: shortcuts are keyed by macOS key codes string-sorted and joined with "+", f20 is 90, ctrl is 59, ptt is Push to talk and lens is Command Mode. splitKeybinds is kept in step so Flow cannot read back a disagreeing pair. A bin/ tool rather than a link/ part, though it was proposed as one. It has to quit and restart Flow for an edit to stick, and link.sh runs unattended after every sync: a part that can kill a dictation mid-sentence is a poor trade for a binding that can wait until someone asks for it. On demand it is also free to fail loudly, since nothing else's run depends on it. Flow ignores an AppleScript quit, so it gets a TERM; and it is launched by bundle id, because the bundle contains a second app also called "Wispr Flow" -- its Swift helper -- that `open -a` can start instead, which exits at once. The file as first found is kept as config.json.pre-wispr-keys.
2026-10-01Brewfile: drop toggleMuteIgor Soarez
Ad-hoc signed, no TeamIdentifier, and spctl rejects the bundle as modified since signing, so it only runs with its quarantine flag cleared. A setup script that every machine runs is the wrong place to put an unverifiable binary past Gatekeeper, so the xattr step goes with it and install.sh now says it clears no quarantine flags at all. QuickMD stays: Developer ID, notarized.
2026-10-01Brewfile: say what the two taps actually shipIgor Soarez
Checked rather than assumed. QuickMD is signed with a Developer ID and notarized, so its quarantine flag costs a dialog. toggleMute is ad-hoc signed with no TeamIdentifier, and spctl rejects it as modified since signing -- so the quarantine step is not 'it is not notarized' but 'there is no verifiable publisher'. The trust gate also turned out looser than the first note claimed: it refused an untrusted formula, but both casks installed without it.
2026-10-01Brewfile: toggleMute and QuickMD, from their authors' tapsIgor Soarez
Neither is in homebrew-cask. That costs three things this file cannot express on its own, so install.sh carries them: brew will not load a cask from an unofficial tap until it is trusted, which is per machine and recorded outside the repo; the trust is granted per cask rather than per tap, so it covers these two files and not whatever the taps add later; and toggleMute is not notarized, so Gatekeeper refuses it until com.apple.quarantine is cleared -- that one attribute on that one path, not the `xattr -cr` its README asks for. Nothing here vouches for either binary, and a third-party tap can change what it installs between two runs. Both are deliberate exceptions, named as such.
2026-10-01Brewfile: glow and IceIgor Soarez
Neither is read by anything here, so they sit apart from the tools the config depends on: glow renders markdown in the terminal, Ice manages the menu bar like keepingyouawake beside it.
2026-10-01hammerspoon: Caps Lock sends F20, and stays that wayIgor Soarez
hidutil remaps inside the IOKit HID driver, below the window server, so the key stops being Caps Lock before anything above the driver sees it: no toggle to suppress, and F20 is a real key to other apps, to System Settings shortcuts, to a password field. An eventtap cannot do this -- Caps Lock is not reachable from there, which is why hs.hid exists as its own IOKit module. What hidutil will not do is cover a keyboard attached after it ran, so dotlib re-applies on USB attach, on wake, and on a Caps Lock event. That last one is the general answer: a mapped keyboard cannot produce a Caps Lock event, so an event arriving at all is proof of a keyboard the remap missed, and the fault triggers its own repair. The watchers are there to get the common cases before a key is pressed, since the repairing press is spent repairing. No launchd job and no link/ part. The remap lives and dies with Hammerspoon, which already owns the keyboard on these machines, and a plist would only have bought independence from it for the second or two before it starts -- a user agent does not run at the login window either. Nothing is bound to f20 here; bind it like any other key. A machine gives the key back with capslock_off() from its local.lua, which loads last: shadowing cannot undo a side effect that has already run. hs.ipc is required so capslock_status() answers from a shell.
2026-09-28AGENTS.md is the file; CLAUDE.md imports itIgor Soarez
Every agent here except Claude Code reads AGENTS.md -- pi, codex, and aider now that its read: says so. The vendor-neutral name was the symlink and the Claude-specific one the real file, with a header saying the file was guidance "to Claude Code". Swapped, keeping history on the real file through git mv. Claude Code has read AGENTS.md directly since v2.1.277 and the machine this was done on is newer, so the obvious move was to delete CLAUDE.md. Checking the other machines first was worth it: four were on versions between 2.1.158 and 2.1.263, and a session on any of them would have found no instructions at all. So CLAUDE.md stays as one line, `@AGENTS.md`, which the documentation endorses for exactly this case and which -- unlike the symlink it replaces -- works on Windows and does not trip Claude Code's refusal to write through a symlink. The header says to delete it once every machine is current.
2026-09-28CLAUDE.md: an agent files its own change request, without askingIgor Soarez
The text said to hand a request to dot-request "as well as to the reply" but never said an agent may do that unprompted, and asked for it to be written "to be pasted whole", which reads as give-this-to-a-human. So an agent on a read-only clone wrote a request, handed it back, and offered to file it only if asked -- reasonable behaviour against what was written. The paragraph now is the permission, and says why filing is safe: it applies nothing, and every request is still judged before anything changes. One addition beyond what was asked. Filing without asking means text leaves that machine with nothing in the way, and the machine is one whose owner does not control it. So putting the request in the reply is now explicitly not optional: it is the only thing that shows what was sent.
2026-09-28tmux: the command prompt gets an opaque backgroundIgor Soarez
Every theme set message-style with a bg and no fill. From tmux 3.7 the command prompt is drawn over the status line, where only the prompt's own characters take that bg and the rest of the line takes fill; with status-style bg=default everywhere here, the terminal showed through around the prompt. Reported from a machine running 3.7c. fill now matches bg in all eight. It has existed since tmux 3.2 and older versions ignore it, so this is safe on the fleet: checked on 3.5a, where each theme sources cleanly and reports the fill back.
2026-09-28pi: the siliconflow key is a reference, not a nameIgor Soarez
models.json gave the provider an apiKey of "SILICONFLOW_API_KEY", which pi sends verbatim as the credential: it interpolates "$NAME" and "${NAME}", runs "!command", and treats anything else as a literal. So that provider has been authenticating with the 19-character string "SILICONFLOW_API_KEY" and getting a 401 for as long as it has existed. Confirmed by calling pi's own resolver: "SILICONFLOW_API_KEY" resolves to itself, "$SILICONFLOW_API_KEY" to the value. Nothing catches this -- isConfigValueConfigured returns true for both and reports no missing variables, so `pi auth check` says ready either way, and only a real request fails. Hence the note in CLAUDE.md beside the models.d row: the rule is invisible in the file, and the next provider added would repeat it. Reported as a change request from the machine that hit it; the sweep for other instances found none.
2026-09-27macos: hot corners and tap to click, and a way to find the next keyIgor Soarez
Three settings this account has always set by hand on every new Mac, now in the script that exists for exactly that: the bottom-right hot corner sleeping the display, tap to click, and three-finger drag on an external trackpad -- the last a gap, since the script set it only for the built-in one. Each of those is written in more than one place because macOS keeps them in more than one place: an external Magic Trackpad has its own domain, and the login window reads com.apple.mouse.tapBehavior rather than either of them, per-host for this machine and globally for any other. Setting one and not the others is why tap to click sometimes survives a reboot and sometimes does not. bin/macos-defaults-diff is the general answer: snapshot, change one setting, and it prints what moved, including the -currentHost domains a plain `defaults read` never shows. It flushes cfprefsd first, since a change made seconds earlier can otherwise be missing from the read meant to reveal it, and it says plainly when nothing changed -- anything behind Privacy & Security lives in the SIP-protected TCC database and cannot be scripted at all.