<feed xmlns='http://www.w3.org/2005/Atom'>
<title>dot.git/AGENTS.md, branch master</title>
<subtitle>Shell, editor and terminal configuration, and the tools that go with them
</subtitle>
<id>https://git.soarez.net/sz/dot.git/atom?h=master</id>
<link rel='self' href='https://git.soarez.net/sz/dot.git/atom?h=master'/>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/dot.git/'/>
<updated>2026-10-29T21:40:43+00:00</updated>
<entry>
<title>pi: generate mcp.json from ~/.config/pi/mcp.d fragments</title>
<updated>2026-10-29T21:40:43+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-10-29T21:40:43+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/dot.git/commit/?id=b3448031088447d76d80ac6b2bfa08320973a2d9'/>
<id>urn:sha1:b3448031088447d76d80ac6b2bfa08320973a2d9</id>
<content type='text'>
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.
</content>
</entry>
<entry>
<title>yubikey-git: opt a host in to git over ssh at git.soarez.net with the YubiKey git key; ssh entry includes enabled.d/</title>
<updated>2026-10-25T15:56:18+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-10-25T15:56:18+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/dot.git/commit/?id=4f40b8ea2f224132dddde4f505191c3662620741'/>
<id>urn:sha1:4f40b8ea2f224132dddde4f505191c3662620741</id>
<content type='text'>
</content>
</entry>
<entry>
<title>nvim: render markdown in the buffer</title>
<updated>2026-10-07T16:24:51+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-10-07T16:24:51+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/dot.git/commit/?id=fdaf6742b0823b088c58ac8e26f6556a0c12c43f'/>
<id>urn:sha1:fdaf6742b0823b088c58ac8e26f6556a0c12c43f</id>
<content type='text'>
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; &lt;leader&gt;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.
</content>
</entry>
<entry>
<title>pi: extensions as a directory of links, so a machine can have its own</title>
<updated>2026-10-05T10:53:09+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-10-05T10:53:09+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/dot.git/commit/?id=611dfd90d4e523984a78728ebfee033e4c16b37d'/>
<id>urn:sha1:611dfd90d4e523984a78728ebfee033e4c16b37d</id>
<content type='text'>
~/.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.
</content>
</entry>
<entry>
<title>alacritty: the mux launch in its own import, overridable per machine</title>
<updated>2026-10-05T06:45:05+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-10-05T06:45:05+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/dot.git/commit/?id=fac95146b95ef103e8100b2284671be4bdc1065f'/>
<id>urn:sha1:fac95146b95ef103e8100b2284671be4bdc1065f</id>
<content type='text'>
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.
</content>
</entry>
<entry>
<title>pi: compact tool output, pinned, 3-line previews for every tool</title>
<updated>2026-10-05T06:40:20+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-10-05T06:40:20+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/dot.git/commit/?id=8c1724fc6af8a223666eecd4687c29f1371d6ddd'/>
<id>urn:sha1:8c1724fc6af8a223666eecd4687c29f1371d6ddd</id>
<content type='text'>
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.
</content>
</entry>
<entry>
<title>tmux: machine-local overrides in ~/.tmux.conf.local</title>
<updated>2026-10-03T04:26:40+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-10-03T04:26:40+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/dot.git/commit/?id=b872fd500c67ba19913c64c0c071be8829bedc01'/>
<id>urn:sha1:b872fd500c67ba19913c64c0c071be8829bedc01</id>
<content type='text'>
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.
</content>
</entry>
<entry>
<title>bin/wispr-keys: Flow dictation on the Caps Lock key</title>
<updated>2026-10-01T18:24:09+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-10-01T18:24:09+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/dot.git/commit/?id=5919160d02bca068a3ea8c30160013aad8236700'/>
<id>urn:sha1:5919160d02bca068a3ea8c30160013aad8236700</id>
<content type='text'>
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.
</content>
</entry>
<entry>
<title>hammerspoon: Caps Lock sends F20, and stays that way</title>
<updated>2026-10-01T15:05:25+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-10-01T15:05:25+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/dot.git/commit/?id=6c7eb1b380cec100663c7c11d0252f9e6b46d34a'/>
<id>urn:sha1:6c7eb1b380cec100663c7c11d0252f9e6b46d34a</id>
<content type='text'>
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.
</content>
</entry>
<entry>
<title>AGENTS.md is the file; CLAUDE.md imports it</title>
<updated>2026-09-28T09:53:00+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-09-28T09:52:44+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/dot.git/commit/?id=7fc9494ac2b7d905cebff10ff5b8be5a3de80403'/>
<id>urn:sha1:7fc9494ac2b7d905cebff10ff5b8be5a3de80403</id>
<content type='text'>
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.
</content>
</entry>
</feed>
