Commit Graph
259 Commits
Author SHA1 Message Date
rootiest 28a88a9bdf feat(privacy): add DO_NOT_TRACK and DISABLE_TELEMETRY env vars under C3 privacy 2026-09-03 22:35:05 -04:00
rootiest f9d400699f fix(agents-vault): keep the user's ssh command and drop the quadratic walk
The connect bound was delivered by injecting GIT_SSH_COMMAND, and an
environment variable outranks git's core.sshCommand -- so the guard,
which read only the environment, did not merely miss a configured ssh
command, it overruled one. A vault remote reachable only as
`ssh -i ~/.ssh/vault_key` failed to authenticate on every push, autopush
and --push alike, for the sake of a ten-second timeout. Both spellings
now count, and `set -qx` rather than `set -q` on the environment side so
an unexported fish variable -- which git never sees -- does not leave the
push with neither the user's ssh command nor a bound.

The agy knowledge walk appended each find with `set -a`, which rewrites
the whole variable every time; 500 files cost 21ms but 20,000 cost 58s,
on a path that runs in front of every agent launch. The walk now prints
NUL-separated and the list is built once, which is flat: the same 20,000
files take 707ms. NUL rather than newline because a filename may legally
contain one. What the walk collects, and its symlink and dot-led
semantics, are byte-for-byte unchanged.

Autopush is bounded by timeout(1) alone, so without it the launch path
was quietly back to an open-ended network call. It now says so and skips
the push instead; --push was never wrapped and is unaffected.
2026-09-03 18:56:47 -04:00
rootiest d9b56790c5 fix(agents-vault): keep the knowledge walk and the launch push inside their bounds
The agy knowledge allowlist walked the store with `**` and copied with
plain cp, so a symlink inside the store was both followed and dereferenced.
The extension rule still bounded what kind of file was collected, but not
whose: a link to a home directory hands over settings.json, CLAUDE.md and
every cached .json in it, and those reached a commit. A link to / made the
walk itself unbounded, on the path that runs before every agent launch.
The tree is now walked a level at a time and nothing that is a symlink is
followed or copied.

Autopush had the same shape one layer out. Neither GIT_TERMINAL_PROMPT nor
GIT_ASKPASS closes a socket, and git has no connect timeout to set: against
a blackholed address a push took 135s with http.lowSpeedLimit and
http.lowSpeedTime set as well as without them. ssh can time itself out and
is now told to; the autopush pull and push are additionally capped with
timeout(1). An explicit --push stays uncapped, since it is watched and has
to report what a real transfer really did.

Also: scaffold /.migrate-stash into .gitignore beside /.adopt-stash, which
the comment already claimed was covered; and drop the live memory path
during a slug migration only when it is a link. Reached from the
path-derived fallback candidate it can be a real populated directory, where
rm -f correctly refuses -- but said so in rm's voice, so a --silent run that
had succeeded printed what read as an error.
2026-09-03 18:56:46 -04:00
rootiest 16ea31289d fix(agents-vault): repair slug migration, keep the network off the launch path
Six findings from the whole-branch review, all of which end in the same
place: a backup tool reporting success while nothing was backed up.

Slug migration nested the old entry inside the new one. The clear before
the rename was gated on the destination's claude/memory subdirectory
rather than on the destination itself, so an entry that exists without
one survived, `git mv A B` moved A *inside* B, and the mkdir below
fabricated a fresh empty memory directory for the live link to point at.
The real memory ended up one level deeper than --status and --restore
ever look, and the run returned 0. That shape is not exotic: git cannot
track an empty directory, so an entry committed while its memory was
empty comes back from a clone as projects/<slug>/origin and nothing
else -- and cloning the vault is this feature's own recovery path. The
destination is now moved aside the way --adopt already does it rather
than deleted (widening the rm -rf would have destroyed the clone's
origin log), its provenance is folded into the migrated entry, and every
failure path rolls back and reports.

The launch path pulled over the network. Both wrappers call agents-vault
synchronously before starting an agent, and the pull in the shared sync
helper was unguarded once an upstream existed: against a blackholed
remote it blocked the launch indefinitely and then aborted the commit,
so an offline laptop silently stopped being backed up at all. Committing
never needed a remote, so the pull moved to the push path, which was
already opt-in for exactly this reason. A failure there now distinguishes
a real rebase conflict (rebase-merge/ or rebase-apply/ present) from an
unreachable remote instead of calling both a conflict, and both network
calls set GIT_TERMINAL_PROMPT=0 and GIT_ASKPASS so they fail fast rather
than prompt with nobody watching. The helper still refuses to commit a
rebase in progress, and leaves it standing rather than aborting one it
did not start. This also restores agents-init's pre-refactor ability to
commit while offline.

The agy knowledge copy was unfiltered. The allowlist held at the agy root
and nowhere below it, so a planted .credentials.json inside knowledge/
was committed verbatim while the documentation promised nothing new
upstream added could leak in. Only *.md and *.json are copied now --
which is what the store actually holds -- so lock files, transcripts and
conversation databases are excluded by having no business in a backup
rather than by being known about. The scaffolded .gitignore also ignored
only the SQLite sidecars and not the databases, which is worse than
ignoring neither: a torn database landed in history with the write-ahead
log that would have completed it deliberately excluded. Both changes are
template-only, on a feature that has never shipped.

agents-init reported success when nothing was committed. It ended on a
branchless `if` with no arm for a failed commit, which fish resolves to
0 -- the same false zero already fixed in agents-vault, left in the
function the refactor was rewriting. It now has the arm and an explicit
final status.

The --adopt forward-failure path with no stash left a raw coreutils `mv:`
line and no statement that the adopt had been abandoned cleanly; it is
branded like every other error exit in the function.

Tests: the suite now clones a vault with git and runs agents-vault
against the clone, instead of trusting hand-built fixtures to have shapes
git can actually produce -- that blind spot shipped both of the merge
blockers. The "present but empty" migration fixture is rebuilt as the
origin-only directory a clone leaves behind, with the hand-built shape
kept as a separate case. Reverting each fix drops the suite from 285 to
279 (migration), 261 (network), 275 (knowledge allowlist) and 283
(agents-init status).
2026-09-03 18:56:46 -04:00
rootiest 2ad5bf75d2 feat(agents-vault): sync the vault from the claude and agy wrappers
Both wrappers stay behind the C1 guard, so disabling
__fish_config_op_aliases still passes straight through to the real binary.

Launch commits but never pushes, keeping the network and any credential
prompt off the critical path; pushing is left to the Claude Code SessionEnd
hook. agy has no such hook, so its memory lands one launch later.
2026-09-03 18:56:46 -04:00
rootiest 11f4551fa5 fix(agents-vault): repair the adopt rollback and see dot-led entries
The adopt rollback restored the worktree but not the index. Every move it
makes is a plain rename as far as git is concerned -- the stash move out
from under the index most of all -- so a rolled-back adopt left a
half-applied rename staged against a clean vault. No bytes were at risk
and the next ordinary run's `git add -A` healed it, but a hand
`git commit` in that window recorded the half-applied state. Both
rollback sites now re-read projects/ once the worktree is whole again,
the stash restore included. projects/ is named whole rather than the two
entries, because `git add` refuses a pathspec that matches nothing --
which one of the two always is, once it has been moved back -- and then
stages neither.

The stash itself moves from the vault root into .git/, where neither the
entry walk nor `git add -A` can reach it, so a crash between the two
moves can no longer leave junk at the vault root for the next run to
commit. A vault whose .git is not a directory falls back to the root,
which the scaffolded .gitignore now covers.

--status and --restore walked the vault with a fish glob, which does not
match dot-led names. A dot-led slug is both reachable and sanctioned: the
sibling-bare-mirror idiom (`git remote add origin ../mirror.git`) keys as
..-mirror, a dot-led host keys as .hidden.example.com-o-r, and --adopt
accepts a leading dot on purpose. Such an entry is scaffolded, linked,
committed and pushed normally, yet --status under-reported it and batch
--restore left that project unlinked, both without saying so. Both walks
now list the directory instead. The --adopt completion gains -A for the
same reason: an entry that cannot be completed reads as one that is not
there.

The now-fatal push failure is painted as an error rather than a warning,
matching its sibling on the commit path.

The header notes that --adopt does not pin a name. The slug is re-derived
on every run, so the next ordinary run migrates the adopted entry back to
the canonical key, memory and live link following. Behaviour unchanged;
only the documentation gap is closed.

Tests, 173 -> 209 checks. The whole stash branch of --adopt was
uncovered, because the existing atomicity test adopts onto a slug with no
entry at all: a successful stash-adopt and a stash-adopt whose relink
fails are both pinned now, the latter asserting an empty
`git status --porcelain` and a still-reachable live memory. The stash
location is pinned by making the vault root unwritable for the duration,
which only a stash at the root would need. agents-vault's own propagation
of a failed sync had no test at all -- the third recurrence of fish's
branchless-`if` false zero here -- so both ways it can fail are now
driven end to end: a rejecting pre-commit at the vault's own
core.hooksPath, and a real rebase conflict against a bare remote. A
dot-led entry is asserted in --status and in --restore.
2026-09-03 18:56:46 -04:00
rootiest 090779ae5d fix(agents-vault): make push failure fatal and adopt atomic
A push that fails against a configured remote warned on stderr and then
fell through to the trailing branchless `if`, which resolves to 0, so
`--push` reported a successful backup while nothing had left the machine.
That is the exact loss the vault exists to prevent. It now returns
non-zero, verified against a real unreachable remote rather than a mock.

The same audit found two more false zeros in this function, both fixed:
the commit block warned about a rebase conflict and walked past it, and
swallowed a hook-rejected commit entirely (neither branch of its if/else
if matched, since the error goes to stderr rather than stdout); and
--restore reported a relink failure and then returned 0 regardless. All
three now feed one flag and the function ends on an explicit status
rather than on whatever the last branchless `if` left behind.

--adopt is now atomic. A rename that landed while the relink failed left
the memory intact at the new slug but unreferenced: the next ordinary run
found no live link, recomputed the old slug, found nothing there, and
fabricated a fresh empty entry, so the agent wrote history-less memory
from then on. No bytes were lost, but continuity was, with no automated
recovery. The live link is no longer removed first -- ensure_symlink
repins a link that points elsewhere on its own -- a contentless target
entry is moved aside rather than deleted, the origin note is appended
only after the relink succeeds, and a failed relink rolls the rename back
so the vault is exactly as it was.

The --adopt validator no longer refuses a leading dot. _agents_repo_slug
legitimately emits one for a dot-led subdomain, so refusing it made such
an entry impossible to adopt; inside projects/ it is a hidden directory,
not an escape. The traversal cases are still refused: no slash survives
the charset, and "." and ".." are refused by name.

Adds the RETURNS section the header was missing. --status prints a
structured report, which this repo's convention treats as return value
rather than as progress output.
2026-09-03 18:56:46 -04:00
rootiest 45f0fb9688 feat(agents-vault): add status, restore, adopt, remote, and push
--status reports link health, orphaned entries, and how far the vault is
ahead of its remote, which is how an unpushed backup gets noticed. --adopt
rebinds a machine-specific local-* entry by hand. --push is explicit;
autopush stays opt-in via __fish_agent_vault_autopush.

Three corrections to the planned shape:

--status is dispatched ahead of the scaffold instead of behind it. As
planned it sat after the tool install, the agy knowledge copy, and the
global memory link, so asking for a report would first sync global state
and claim ~/.claude/memory. It is now read-only and reports a missing
vault rather than creating one. The global-state block moved below the
mode dispatch so it runs only on a default or --link run; the mutating
modes still need the vault repo, so they sit between the scaffold and it.

--adopt validates its slug before using it. It is interpolated into
"$vault/projects/$slug" and handed to `git mv`, so --adopt=../../../etc
walked straight out of the vault. Only the charset the slug formula emits
is accepted, with no slash and no leading dot.

--remote captures the git exit status explicitly rather than chaining an
`or` off the block terminator. That construct does work in fish, but it
reads as the silent-false-success shape that a hook-rejected commit once
produced here, and it stops working the moment the `else` goes away.

Also pins the dangling-global-symlink case the suite never covered: for a
broken ~/.claude/memory link both -d and -e are false, so the -L disjunct
in the global-memory guard is the only thing that notices it. That is the
state a buggy earlier run left on a real machine; the test asserts it is
detected, repinned into the vault, and exits 0.
2026-09-03 18:56:46 -04:00
rootiest 4c7334c46e fix(agents-vault): keep a global-state fault from aborting project backup
The global block runs before the per-project link and the commit, but its
mkdir and link failures returned 1 outright. Global memory is optional and
frequently absent, so a stray file or a permission problem at
~/.claude/memory would abort the per-project memory backup and its commit
for every project, on every agent launch -- a fault in the secondary
feature killing the primary one.

Both failures now warn to stderr and continue, matching the treatment the
agy copy already had; the whole global block is best-effort by design.
Continuing is safe because _agents_repo_ensure_symlink validates and
refuses before mutating anything. $changed is set only when the link
actually succeeded, and nothing is recorded that would make a later run
believe the global memory is linked when it is not.

The live-side test widens from -d to -e so a stray regular file where the
global memory directory belongs is reported on every run instead of being
silently skipped and mistaken for the absent-by-default case.

Also documents that the agy knowledge copy is merge-only: a fact deleted
upstream persists in the vault and a restore brings it back. Whether the
vault should mirror deletions is a retention decision for the repo owner;
the gap is worth stating either way.
2026-09-03 18:56:46 -04:00
rootiest 1c9cedb8f3 feat(agents-vault): back up agy and global Claude state
agy partitions by conversation UUID rather than by workspace, so it has no
per-project slice and is tracked globally. Its knowledge store is copied
rather than symlinked because it sits beside SQLite databases with WAL
sidecars. Claude's global memory directory is symlinked into the vault the
same way per-project memory is, including the emergent-restore direction.

Paths are allowlisted so credentials, transcripts, and session state cannot
be swept in.

Two variables keep the tests off the real home: the new
__fish_agent_vault_claude_home overrides ~/.claude (whose memory/
subdirectory is the global one), distinct from the existing
__fish_agent_vault_claude_root, which overrides ~/.claude/projects.
Without it a test run on a machine that has a real global memory directory
would move it into a mktemp vault and leave a dangling symlink behind. The
suite now points every run at a throwaway home by default and asserts the
real paths are untouched.

cp cannot report whether anything actually differed, so the copy is only
counted as a change when it leaves the vault's global/agy/ subtree dirty.
Marking it changed unconditionally would print a --quiet summary line on
every agent launch and make the flag meaningless.
2026-09-03 18:56:45 -04:00
rootiest a018d7997c fix(agents-vault): dedupe local-slug formula, drop dead code, widen migration coverage
The slug-migration fallback (used when there is no live symlink to read
the previous slug from) recomputed the local-* candidate by lowercasing
the basename only, while _agents_repo_slug sanitizes it. The two formulas
had drifted, so the fallback silently found nothing for any project
directory whose basename needed sanitizing.

Extract the formula into a single private helper,
_agents_repo_local_slug, and have both _agents_repo_slug's no-remote
branch and agents-vault's migration fallback call it, so there is one
place left to drift.

Also drop two dead lines the review flagged: an unused  local,
and an unreachable mkdir -p (path dirname ...) — slugs never contain a
path separator, so dirname always resolves to a directory that already
exists by that point.

Widen migration test coverage: the current-entry-present-but-empty case,
a remote URL rewrite, a remote removal, and a dirty-basename fallback
test that fails without the sanitization fix and passes with it.
2026-09-03 18:56:45 -04:00
rootiest b7ff4e0981 feat(agents-vault): migrate entries when a project's slug changes
Adding a remote to a previously remote-less project changes its slug. Left
unhandled, the link step repinned the live memory symlink to a fresh empty
entry and orphaned the real memory.

The previous slug is read from the live symlink target rather than guessed,
which covers a remote being added, rewritten, or removed. When both the old
and new entries hold content the migration is ambiguous, so nothing moves
and the user is directed to --adopt.
2026-09-03 18:56:45 -04:00
rootiest 19126316a7 fix(agents-vault): always link the current project's memory
The guard around the symlink step only linked when the live Claude
project directory already existed, which is exactly backwards for the
clone-onto-a-new-machine restore case: a freshly cloned vault entry
would be silently left unlinked and a starting agent would write fresh,
history-less memory instead. _agents_repo_ensure_symlink already makes
its own parent directories and is idempotent, so nothing depended on
the guard; it is removed and the link is now attempted unconditionally.

Also stop swallowing a refused or failed link as success: the helper's
exit status is now checked, and agents-vault reports its own error and
exits 1 instead of silently continuing with no link in place.

Smaller fixes from the same review pass:
- check the exit status of _agents_repo_install_tools and the
  core.hooksPath git config write, instead of discarding both
- give the vmem mkdir failure a stderr message like every other fatal
  in the function
- guard hostname with type -q and add it to DEPENDENCIES
- .version creation now sets changed, so --link (which skips the
  commit step) reports it in --quiet mode
- reword --link's help/doc text: it still scaffolds the vault and
  links memory, it only skips the final commit
- drop the unused c_dim color variable
- move the __fish_agent_vault_dir / __fish_agent_vault_autopush
  documentation below Opinionated Components so its NOTE: callout
  (now flush-left so it actually renders as a Starlight Aside, per
  review) doesn't become the first Note aside in the page and steal
  the existing test's assertions about the original 4-bullet one

Adds two tests: pre-seeded vault entry with no live directory at all
(the restore path the guard was breaking), and a forced link failure
asserting agents-vault now exits 1 instead of 0.
2026-09-03 18:56:45 -04:00
rootiest 2c185f7e23 feat(agents-vault): scaffold the vault and link project memory
Creates the vault repo on demand, reusing the AGENTS version bumper and
hook shims, then links the current project's live memory directory into
its slug-keyed entry and commits.

Because the live directory becomes a symlink into the vault, backup and
restore are the same operation: a cloned vault relinks itself on the next
run in each project, with no manifest and no batch restore step.

Also fixes _agents_repo_install_tools' progress messages, which hardcoded
the literal "AGENTS/.agents-tools/" even for callers writing elsewhere:
they now name repo_dir's own basename, so agents-vault reports its own
directory instead of a false AGENTS/ path.
2026-09-03 18:56:45 -04:00
rootiest b0585d00ad refactor(agents-init): use the shared repo helpers
Renames _agents_init_install_tools to _agents_repo_install_tools now that
the vault shares it, collapses the two duplicated root-symlink blocks into
one loop, and routes the auto-commit through _agents_repo_sync so a failed
rebase can no longer be committed as conflict markers.
2026-09-03 18:56:45 -04:00
rootiest b5d2c9ba87 fix(agents-vault): surface commit-hook rejection as exit 1
_agents_repo_sync fell off the end of its final if-block when git commit
failed, e.g. a pre-commit or commit-msg hook rejecting it (this repo runs
ggshield and Git-LFS hooks). fish's if construct sets status 0 when the
condition is false and there is no else branch, so a rejected commit was
being reported as success rather than as the documented "not a git
repository" exit 1 it was assumed to fall through to.

Add an explicit else branch that emits a stderr diagnostic and returns 1,
and widen the EXIT STATUS/DESCRIPTION docs to cover this path under the
existing code 1 rather than adding a fourth code, since later tasks
already consume the 0/1/2 contract.
2026-09-03 18:56:44 -04:00
rootiest 51543cb7ca feat(agents-vault): add sync helper that refuses to commit conflicts
agents-init currently swallows a failed rebase and then stages and commits
whatever is in the tree, which records conflict markers under a routine
message. No AGENTS repo has a remote today so the pull never runs, but the
vault gives these repos remotes and arms it.

The shared helper aborts the rebase, commits nothing, and returns 2. It
also redirects git's own stdout during the pull/abort: git prints
"CONFLICT (content): ..." to stdout, not stderr, so without this the
message would leak into the helper's own stdout instead of staying
diagnostic-only.

The conflict fixture commits "ours" locally before diverging, since an
uncommitted worktree change has nothing for --autostash's rebase step to
replay -- it fast-forwards cleanly and only the stash pop would conflict.
2026-09-03 18:56:44 -04:00
rootiest 3fd9476fbc feat(agents-vault): add directory-only symlink helper
Enforces the rails the vault depends on: only directories are linked
(agent editing tools refuse to write through a symlinked file), a missing
target is refused rather than turned into a dangling link, and adopting a
populated live directory copies without clobbering.

Also fixes slug sanitization in _agents_repo_slug to apply the same
[^a-z0-9._-] → - mapping to the fallback (no-remote) branch, ensuring
local project slugs are filesystem-safe and won't leak special chars like
spaces or exclamation marks.
2026-09-03 18:56:44 -04:00
rootiest 1dc0e5293d feat(agents-vault): derive vault slugs from normalized remote URLs
Keys a project by its remote rather than its path so the key survives a
machine change or a directory rename. Falls back to a path-derived
local-* key when no remote exists.

Adds a hermetic test harness that builds throwaway repos under mktemp.
2026-09-03 18:56:44 -04:00
rootiest 5328529e18 Merge pull request 'fix(help): full index audit and singular/plural keyword matching' (#122) from docs/index-full-audit into main
CI / github-mirror (push) Skipped
CI / test (push) Successful in 49s
CI / build-docs (push) Successful in 3m35s
2026-09-01 02:11:53 +00:00
rootiest 6cf690e637 fix(help): resolve singular and plural section keywords
The heading scan matches a keyword contained in a heading, so a plural
could never reach a singular heading: `customization` found "7.
CUSTOMIZATION" and `customizations` found nothing at all. The index
lookup was exact-match, so it could not cover the gap either without an
alias per word.

Both now try the keyword as typed first, then its singular/plural
forms. Each variant is tried against every heading before the next one
is considered, so a loose plural cannot beat an exact hit further down
the document.
2026-08-31 22:07:06 -04:00
rootiest 577ad993ea feat(help): render inline code spans instead of printing backticks
config-help pipes the manual through bat, which dims the backticks and
leaves the span content the same colour as the prose around it -- so a
delimiter carried no information and every span cost the reader two
literal characters. 1236 of them across the document.

Each span is now rendered bold and the delimiters dropped, in every
branch of the viewer chain:

- ov + bat, and ov alone, style the spans (bat's output wraps each
  backtick in its own SGR sequence; raw Markdown is matched directly)
- bat alone flattens them on the way in instead, because bat escapes
  any SGR sequence handed to it as input
- less and cat style them, less gaining -R to render the result
- man -l needs nothing; pandoc consumed the backticks at build time

Both substitutions are line-preserving, so the tail-slice that opens
the pager on a requested section still lands on it.
2026-08-31 22:02:37 -04:00
rootiest 2ce8bebf29 docs(functions): drop backticks from doc-headers
CONTRIBUTING states doc-headers are written as plain text -- the header
is read as-is by config-help, by funcsave, and by anyone opening the
file, and docs/codespans.py adds the site's inline code spans at render
time. 22 files had drifted from that, carrying 41 hand-written spans
that reached config-help and the man page as literal backtick
characters inside an otherwise verbatim block.

The one span whose content ended in a space is requoted rather than
dropped, so "read> " keeps reading as a prompt string.
2026-08-31 21:56:45 -04:00
rootiest 857faebeba Merge pull request 'feat(help): standardize colored --help output across functions' (#116) from feat/colored-help-text into main
CI / test (push) Successful in 56s
CI / build-docs (push) Successful in 3m26s
Reviewed-on: #116
2026-08-22 04:24:44 +00:00
rootiest 500dd8a735 feat(help): standardize colored --help output across functions
Add the established c_head/c_cmd/c_flag/c_dim/c_arg color scheme to
--help (or usage-on-error) output in play-media and 13 other functions
that lacked it or used an ad hoc scheme: bkg, detach, replay, p, y,
spark, wake-lock, open-url, repo-open, dng2avif, dockup, fish-deps
(__fish_deps_help), and scrub.

Also tweak the standard itself:
- c_cmd now uses plain `set_color --bold` instead of `--bold white`,
  so the command name adapts to the terminal's foreground instead of
  forcing white text that washes out on light-background themes.
  Applied across all functions already using the pattern.
- jobrunner's reset variable renamed from c_rst to c_reset to match
  the naming used everywhere else.
2026-08-22 00:19:43 -04:00
rootiest 9e74cb8f0a fix(scrub): restore missing line continuation in aggressive_patterns
A missing trailing backslash after the Thumbs.db:encryptable pattern
terminated the `set -l aggressive_patterns` array early. The AI-tool
patterns (.gemini*, .claude*, .antigravity*, .remember*) were then
executed as a bogus command instead of being appended to the array,
so scrub -a never actually purged them, and every scrub invocation
printed a spurious "Unknown command" error.
2026-08-22 00:18:55 -04:00
rootiest 51fc669c02 feat(media): add play-media fzf picker for audio/video files
Fuzzy-filters audio/video files under the current directory via fd,
previews them with thumbnails pulled from the freedesktop thumbnail
cache (or ffprobe metadata as a fallback), and plays the selection
through the best available player: --player flag, $play_media_player,
xdg-mime default, then a short mpv/vlc fallback list. Adds mpv/vlc as
optional fish-deps entries.
2026-08-21 02:47:45 -04:00
rootiest 616596726d feat(fzf): add preview, dirs, and image support to @@ inline picker
The @@ picker only listed files with no preview. It now lists both
files and directories via fd (matching _fzf_search_directory), and
shows a bat-highlighted or image-rendered preview through
_fzf_preview_file. Image previews use a kitty-graphics-protocol,
chafa, viu, timg fallback chain via the new _fzf_preview_image
helper, benefiting the Ctrl+F directory search and git-status
pickers as well since they share the same preview helper.
2026-08-21 00:46:29 -04:00
rootiest 9368a4864c fix(bindings): trigger fzf inline picker with a lookbehind @ instead of a @@ chord
Binding the raw @@ chord made a lone @ an ambiguous prefix, so fish
(with fish_sequence_key_delay_ms unset) held every typed @ indefinitely
until a disambiguating keystroke arrived, breaking things like
`ssh user@host`. Binding the single @ key instead and checking whether
the current token is already a bare @ removes the ambiguity entirely:
plain @ always self-inserts instantly, and a second consecutive @
triggers the picker in place, with no forced delimiter before you can
keep typing. Vi's normal/visual modes are left unbound, matching their
existing (no-op) @ behavior.
2026-08-20 00:38:02 -04:00
rootiest 8866236bda fix(config-settings): align Enter continuation line in --help output
The wrapped description for the Enter key was indented one column past
every other description in the Navigation block. Drop the extra leading
space so it lines up at column 17 with the rest.
2026-08-18 18:13:01 -04:00
rootiest 040c98c0a7 fix(config-settings,docs): address final whole-branch review findings
Bundled fix wave for six findings from the sub-category-granularity
whole-branch review:

- config-settings: reset in_subcat on Tab/Shift-Tab so up/down keep
  routing to cur_row instead of freezing on the Sponge/Paths pages
  after a drill-down
- verify-manual: _parsed_components() now delegates to
  generate_component_registry.collect_components() instead of a
  lossy dict.update() merge, closing a taxonomy-check blind spot on
  identity collisions
- verify-manual: add test_committed_registry_matches_headers to
  catch the committed registry drifting from current # COMPONENT
  headers
- build-docs workflow: trigger on conf.d/** and config.fish edits,
  not just functions/**
- __config_settings_draw_subcat: show (Universal)/(Session) in the
  drill-down title so the persistence scope is visible before toggling
- __config_settings_draw / config-settings: mention the Enter
  sub-category drill-down in the on-screen hint and --help text
- generate_component_registry: send the "wrote ..." progress line to
  stderr so it no longer corrupts --concat's stdout output
2026-08-18 16:46:46 -04:00
rootiest 3c0bba1737 fix(config-settings): fix silent varname resolution failure and stale panel_h in sub-category drill-down
Two bugs invisible to single-frame rendering, only reachable via the
interactive event loop:

- right/l and left/h resolved the sub-category variable name with
  "$toggle_vars[(math ...)]"_(...) inside a quoted string -- fish
  cannot expand a command-substitution index there ("Invalid index
  value"), so the set never ran and varname silently kept the parent
  category variable. Every arrow-key press on a sub-category row
  toggled the parent category instead. Fixed by hoisting the category
  variable into a plain local first, the same technique the down/j
  case already used for its page index.

- __cs_dispatch_draw left panel_h fixed at 16 regardless of what it
  actually drew, but the new sub-category page is n+7 lines (9-13,
  always < 16). Every redraw/cleanup erase computed its height from
  the stale constant, erasing too many rows and corrupting whatever
  was above the panel. Fixed by having the dispatcher record the
  actual printed height into panel_h after every draw, including
  resetting it back to 16 on the value pages even when in_subcat is
  stale from a Tab away without an intervening Escape.
2026-08-18 03:22:19 -04:00
rootiest 489e3cc7de feat(config-settings): render and toggle sub-categories in the drill-down page
Adds __config_settings_draw_subcat.fish, rendering a category's own
toggle plus its sub-category rows, sized dynamically from
__config_settings_subcats instead of the fixed 6-row category layout.
Wires the real per-category row count into config-settings.fish's
up/down handling (replacing Task 17's stub) and adds the
hyphen-to-underscore sub-category variable resolution to the
left/right toggle handlers, mirroring the translation the guard
function already applies.

Two box-drawing bugs surfaced while adapting the design doc's draft to
the real static sub-category data and were fixed rather than
transcribed: the title-dashes offset was off by 5, and several real
sub-category labels/descriptions (e.g. "Notifications", 13 chars;
several descriptions past 30 chars) exceed the narrower width tiers'
fixed field widths, so both fields are now defensively truncated
before padding to keep every row exactly iw+2 wide regardless of
content length.
2026-08-18 03:11:17 -04:00
rootiest 7ad3b90503 feat(config-settings): add sub-category drill-down navigation state 2026-08-18 03:01:22 -04:00
rootiest 6b2d3bf491 refactor(config): migrate multi-category files and config.fish to named sites
Adds multi-site `# COMPONENT` headers and converts every guard call site in
conf.d/tricks.fish, conf.d/abbr.fish, functions/smart_exit.fish,
conf.d/yay-wrapper.fish, conf.d/paru-wrapper.fish, conf.d/wakatime.fish,
conf.d/first_run.fish, and config.fish (9 sites across 3 categories) to the
self-identifying `(status current-function|basename) <site>` calling
convention. Regenerates conf.d/__fish_config_op_registry.fish.
2026-08-18 02:39:47 -04:00
rootiest c3a2e35465 refactor(config): migrate C5 logging call sites to self-identifying guard 2026-08-18 02:22:31 -04:00
rootiest e02919de21 fix(config): translate hyphens to underscores in sub-category guard variable names
__fish_config_op_enabled built the sub-category override variable name by
concatenating the tag's slug verbatim, e.g. __fish_config_op_integrations_window-mgmt.
Fish variable names cannot contain hyphens, so any hyphenated sub-category
slug (14 of the 24 in the taxonomy) silently could never be overridden --
an unset such variable safely fell through via set -q, but attempting to
set it always errored with "invalid variable name", masking the defect
since no prior smoke test exercised an explicit sub-category-level toggle.
The registry tag itself (as authored in # COMPONENT headers and the
taxonomy docs) stays hyphenated; only the derived fish variable name is
translated.
2026-08-17 21:53:48 -04:00
rootiest 516b2ba26c refactor(config): migrate C4 integrations call sites to self-identifying guard 2026-08-17 21:49:45 -04:00
rootiest a981fa7f0a refactor(config): migrate C3 overrides call sites to self-identifying guard 2026-08-17 21:37:35 -04:00
rootiest 760b8e68fd refactor(config): migrate C2 autoexec call sites to self-identifying guard 2026-08-17 21:23:12 -04:00
rootiest 04fd832974 refactor(config): migrate C1 aliases call sites to self-identifying guard 2026-08-17 21:12:56 -04:00
rootiest d2e07effc1 feat(config): add manual registry-rebuild command 2026-08-17 20:46:55 -04:00
rootiest 9642cd69db feat(config): rewrite guard as self-identifying with C0 and site support 2026-08-17 20:23:49 -04:00
rootiest 7fa5ca2568 feat(config): add component registry lookup helper 2026-08-17 20:19:42 -04:00
rootiest 94a4fbe45f feat(config): add sub-category cascade evaluator 2026-08-17 20:15:29 -04:00
rootiest c17f6663db feat(deps): add Optional/Terminal Emulator tiers, fix docker prompt hang and ov install path
Reworks fish-deps' dependency classification from three tiers to five:
Required, Recommended, Optional, Terminal Emulators, and Integrations.

- Add an Optional tier for single-purpose wrapper conveniences that only
  matter if you already use that specific tool (btop, dust, duf,
  prettyping, go, lazygit, lazydocker, docker, yt-dlp, screen). Skipped by
  `fish-deps install`/`sync` by default.
- Split kitty/wezterm into their own Terminal Emulators tier, since only
  the one matching $TERM is ever relevant to a given user.
- Add --optional, --terminals, and --all flags to `install`/`sync` to
  opt back into the skipped tiers, with a summary of how many were
  skipped and which flag restores them.
- Fix `_fish_deps_status` marking missing Integrations as critical (red)
  the same as Required — only Required is red now; Recommended stays
  yellow, everything else renders as a neutral dim note.

Also fixes two bugs surfaced during dependency testing on a fresh
install:

- `fish_right_prompt` called `docker context show` on every prompt with
  no `type -q docker` guard, unlike every other optional integration in
  this config — on a system without docker this printed a visible
  "Unknown command: docker" block on every single prompt render. Added
  the missing guard, and hardened the `ld` wrapper with explicit
  docker/lazydocker presence checks.
- `ov`'s catalog entry offered `cargo install ov` as its preferred
  install method, but crates.io's `ov` is an unrelated crate, not the
  noborus/ov pager — cargo would silently install the wrong package.
  Removed the cargo path; `ov` now prefers `go install
  github.com/noborus/ov@latest` (ahead of the system PM) when go is
  available, since not all distros package `ov` in their base repos,
  and falls back to the system PM otherwise.
2026-08-14 16:42:37 -04:00
rootiest adbcd1d3c0 fix(mv): bring header and completions up to repo conventions
The EXIT STATUS block used a single space before the ">0" row's
description, which falls below the doc pipeline's 2-space column
threshold and silently degrades the rendered site page from a table
(like rm's) to a plain indented block. Pad it to match. Also strip
trailing whitespace from two DESCRIPTION lines and add --wraps='mv'
so fish inherits real mv's completions, matching the bash/top wrapper
convention.
2026-08-14 16:02:07 -04:00
rootiest 80d4f5d9fa feat(mv): wrap the mv command to facilitate moving a directory up the tree 2026-08-14 15:24:40 -04:00
rootiest 742fd7941d docs: use markdown tables for abbreviations
Generates actual markdown tables instead of indented text blocks to ensure MDX/Starlight correctly renders abbreviations.
2026-08-06 15:04:06 -04:00
rootiest cb74cd84e2 fix: change jobrunner auto-naming to adjective-animal 2026-08-02 05:47:29 -04:00