<feed xmlns='http://www.w3.org/2005/Atom'>
<title>dot.git/link, 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>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>link/pi.sh: the generators move into files, and parse under bash 3.2 again</title>
<updated>2026-09-25T09:20:13+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-09-25T09:20:13+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/dot.git/commit/?id=ed21d1270007a8f9cbaa2ca7da0fee1eefeb405f'/>
<id>urn:sha1:ed21d1270007a8f9cbaa2ca7da0fee1eefeb405f</id>
<content type='text'>
57e49dd made link/pi.sh unparseable on macOS. bash 3.2 does not skip heredoc
content when it scans for the end of a command substitution, so the three
apostrophes in a comment inside the new heredoc read as an unterminated quote
and the script stopped parsing at end of file. ./link.sh on a Mac aborted
partway: settings still generated, models.json stayed the symlink the commit
meant to replace, and everything after that point never ran. Linux was fine,
and so was every check I ran -- `bash` on a Mac with Homebrew is 5.x, which
parses it happily. Reported from the machine it broke.

Rewording the comment would fix it and leave the trap armed for the next edit,
so the two python programs move into link/pi-settings.py and link/pi-models.py
instead, called by path. The text is unchanged; there is no longer a heredoc
inside $( ) to trip over, and both files now compile-check on their own.

Verified under /bin/bash 3.2: first apply replaces both symlinks, a package
removed by hand still survives a relink, fragments merge for settings and for
models, and repeat runs report unchanged.
</content>
</entry>
<entry>
<title>pi: providers can come from ~/.config/pi/models.d, like settings already do</title>
<updated>2026-09-25T08:30:38+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-09-25T08:30:38+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/dot.git/commit/?id=57e49ddde1dffb17043cd4b065c2907fbe5a863d'/>
<id>urn:sha1:57e49ddde1dffb17043cd4b065c2907fbe5a863d</id>
<content type='text'>
models.json was a plain symlink to the tracked file, which left a machine whose
models are served from somewhere other than a vendor's own API with nowhere to
say so. pi reads providers from exactly one path -- join(getAgentDir(),
"models.json") -- and a `providers` block in a settings fragment is inert,
because the settings schema has no such key. So on such a machine pi cannot be
used at all, and the endpoint is not something this repo may name.

link/pi.sh now generates models.json the way it already generates settings.json:
the repo's file, then every ~/.config/pi/models.d/*.json. No three-way merge
here -- pi never writes this file, keeping runtime state in the sibling
models-store.json -- so a hand-edit of ~/.pi/agent/models.json no longer
survives a relink. Put it in a fragment.

It reports which provider came from which fragment, added or replaced, so a
local file shadowing a provider the repo ships cannot do it quietly.

Proposed as a change request from the machine that needs it; verified here
against pi 0.84.2 before applying.
</content>
</entry>
<entry>
<title>hammerspoon: shared helpers move to dotlib.lua, which local.lua can require</title>
<updated>2026-09-21T15:30:47+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-09-21T15:30:47+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/dot.git/commit/?id=94a8f1ff31a804a4dfa46ce287e30bf4691ae637'/>
<id>urn:sha1:94a8f1ff31a804a4dfa46ce287e30bf4691ae637</id>
<content type='text'>
A machine's local.lua is a separate chunk, so init.lua's locals were invisible
to it and anything it wanted -- hotkey(), the mod tables, type_text -- had to
be copied. They live in dotlib.lua now, linked beside init.lua, and both files
reach them the same way: require("dotlib").

init.lua keeps its own vocabulary by aliasing what it uses, so the 500 lines
below are untouched. It prepends its own directory to package.path first:
Hammerspoon already puts the config directory there (Contents/Resources/
setup.lua), but taking it from this file's path holds however the config was
found. A machine that pulls without re-linking has no dotlib.lua beside
init.lua, which would lose every hotkey silently -- that case notifies and
names the fix.

Checked with a stubbed hs: the module's hotkey, focus, execute, type_text and
type_current_date all behave, and local-example.lua binds its four keys
through it.
</content>
</entry>
<entry>
<title>link: a part named in ~/.config/dot/skip does nothing on that machine</title>
<updated>2026-09-21T11:09:58+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-09-21T11:09:58+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/dot.git/commit/?id=dec2702337924b9a004acf2afa7574f95d349077'/>
<id>urn:sha1:dec2702337924b9a004acf2afa7574f95d349077</id>
<content type='text'>
A machine may need one of the parts left out: on a laptop it does not own,
~/.claude/settings.json is the owner's, holding their environment and plugins,
and linking this account's over it is wrong in both directions -- theirs is
lost, and anything of theirs worth keeping would have to be carried here, in
the repo every machine shares.

The file is hand-written and in no repo, like ~/.localenv, so the exception
stays on the machine that needs it. The check is in link/lib.sh, as each part
starts, rather than in link.sh's loop, so `link/claude.sh yes` obeys it too;
link.sh and other repos' link.sh source that file as well, and sit outside
link/, so their names can never be read as a part's.
</content>
</entry>
<entry>
<title>link/claude: secrets in MCP specs, and a separate spec for Desktop</title>
<updated>2026-09-18T12:03:16+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-09-18T12:03:16+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/dot.git/commit/?id=fec40208281589b70228802914e98b3b4823924b'/>
<id>urn:sha1:fec40208281589b70228802914e98b3b4823924b</id>
<content type='text'>
Two things a spec in ~/.config/claude/mcp-servers.d/ may now carry beyond
what Claude Code takes, both resolved at link time:

- "${szecret:NAME}" in any string value, replaced with `szecret get NAME`
  when the server is registered with Claude Code. For a remote server's
  Authorization header: Claude Code's own headersHelper is not run in a
  workspace without persisted trust, which includes $HOME, so a token
  fetched that way is simply missing. A secret that cannot be read skips
  that server, loudly.

- "desktop", a whole spec given to Claude Desktop instead. Desktop only
  starts local stdio servers, so a layer that moves a server to HTTP for
  Claude Code can keep Desktop on a local copy.
</content>
</entry>
<entry>
<title>dotfiles</title>
<updated>2026-09-17T18:45:23+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-09-17T18:45:23+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/dot.git/commit/?id=13cc065ec014b86369dfe4ad60d1a43fa06fd251'/>
<id>urn:sha1:13cc065ec014b86369dfe4ad60d1a43fa06fd251</id>
<content type='text'>
Shell, editor and terminal configuration, the tools that go with them, and
szecret, an age-based secret manager whose store lives elsewhere.

History restarts here. What came before was not written to be read by
anyone else, and carried machine names, addresses and an encrypted secret
store; it is kept privately instead. What is left is meant to be safe on
any machine, including one whose owner is not me: see CLAUDE.md for the
rule it is written to.
</content>
</entry>
</feed>
