Compare commits

..
1 Commits
Author SHA1 Message Date
rootiestandClaude Sonnet 5 8735e1b871 feat(functions): port key-crypt to a fish function
Bring key-crypt in from ~/scripts as functions/key-crypt.fish instead of a
standalone --no-config script. Converts exit-based control flow to return,
drops the fish_exit/signal traps (global-scope hijack risk in a shared
shell) in favor of an outer wrapper that cleans up the temp file and all
scratch globals after every call, and routes rm/mv/mkdir through `command`
so config-level wrapper functions can't intercept them.

--install now writes a small standalone wrapper script (source this
function file, call key-crypt) to ~/.local/bin/key-crypt instead of
copying the file itself, since a fish function can't be exec'd directly by
the .desktop Open With entries. --uninstall is unchanged aside from the
command-prefixed rm.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 01:07:07 -04:00
157 changed files with 788 additions and 5578 deletions
+16 -157
View File
@@ -4,7 +4,7 @@ on:
push:
branches:
- main
paths: &ci-paths
paths:
- "docs/manual/**"
- "docs/build-manual.py"
- "docs/manualtools.py"
@@ -16,17 +16,6 @@ on:
- "completions/**"
- "integrations/**"
- "tests/**"
- "scripts/**"
pull_request:
branches:
- main
# No `paths:` filter here (unlike push, above): a PR carrying
# Kind/Testing, Area/Docs, etc. must still trigger this workflow even
# when its diff touches nothing in ci-paths, or the label-based gates
# in the test/docs jobs below would never get a chance to evaluate.
# `labeled`/`unlabeled` cover a label added after the PR is already
# open, without a new commit.
types: [opened, synchronize, reopened, labeled, unlabeled]
workflow_dispatch:
inputs:
job:
@@ -37,7 +26,7 @@ on:
options:
- all
- test
- docs
- build-docs
jobs:
# This workflow file is mirrored to GitHub as-is, but the runner label
@@ -55,138 +44,42 @@ jobs:
uses: actions/checkout@v4
with:
token: ${{ secrets.GITEA_TOKEN }}
fetch-depth: 0
# Runs unconditionally and always sets an output, so every step
# after it can gate on a single `steps.relevance.outputs.run`
# check instead of repeating the label/path OR-chain everywhere.
# push/workflow_dispatch are always relevant -- push is already
# path-filtered above, and a manual dispatch is explicit intent.
# A pull_request is relevant if it carries a testing-related label
# (independent of what it touches -- see the `on.pull_request`
# comment above) or if its diff touches a ci-paths pattern (the
# same list the push trigger above filters on; duplicated here in
# shell glob form since a PR event isn't pre-filtered by paths).
- name: Determine relevance
id: relevance
env:
PR_LABELS: ${{ toJSON(github.event.pull_request.labels) }}
run: |
if [ "${{ github.event_name }}" != "pull_request" ]; then
echo "run=true" >>"$GITHUB_OUTPUT"
exit 0
fi
if printf '%s' "$PR_LABELS" | grep -qE '"name":[[:space:]]*"(Kind/Testing|Area/Tests|Area/CI|Area/Scripts)"'; then
echo "run=true" >>"$GITHUB_OUTPUT"
exit 0
fi
git fetch origin "${{ github.event.pull_request.base.ref }}"
if git diff --name-only "origin/${{ github.event.pull_request.base.ref }}...HEAD" \
| grep -qE '^(docs/manual/|docs/build-manual\.py$|docs/manualtools\.py$|docs/verify-manual\.py$|docs/site/|functions/|conf\.d/|config\.fish$|completions/|integrations/|tests/|scripts/)'; then
echo "run=true" >>"$GITHUB_OUTPUT"
else
echo "run=false" >>"$GITHUB_OUTPUT"
fi
- name: Install fish
if: steps.relevance.outputs.run == 'true'
run: |
sudo apt-get -o Acquire::Retries=3 update -qq
# apt-utils, so debconf has a target for the "delaying package
# configuration" notice's OWN follow-up run (irrelevant to
# anything else in this job). That notice still fires exactly
# once here regardless -- verified live against two orderings
# (bundled with another package, and installed fully alone,
# first) -- because it fires *during* apt-utils' own first
# install, before debconf considers it "installed". Nothing
# this workflow controls can pre-seed that; accepted as
# unavoidable, same as the runs-on/checkout hint noise below.
sudo DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y apt-utils software-properties-common
sudo DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y software-properties-common
sudo add-apt-repository -y ppa:fish-shell/release-4
sudo apt-get -o Acquire::Retries=3 update -qq
sudo DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y fish
- name: Run fish config tests
if: steps.relevance.outputs.run == 'true'
run: fish tests/run-tests.fish
# Documentation tests/build, and (push/dispatch only) publish. Split
# into two sections within one job rather than two jobs: the publish
# steps need the files the build steps just generated, and passing
# those between separate jobs would need upload/download-artifact for
# no real benefit here.
#
# Does NOT `need: test` (the old build-docs job did, gating publish
# on it). main now has branch protection requiring the test job to
# pass before a PR can merge, so by the time a push-to-main reaches
# this job, test has already passed as a condition of getting here --
# re-checking it in-workflow would be redundant. The one gap that
# leaves is a direct admin push bypassing the PR flow entirely; that's
# the same trust already extended by leaving block_admin_merge_override
# off on the branch protection rule, not a new hole.
docs:
build-docs:
needs: test
if: |
github.server_url != 'https://github.com' &&
(github.event_name != 'workflow_dispatch' || github.event.inputs.job == 'all' || github.event.inputs.job == 'docs')
always() &&
(github.event.inputs.job == 'build-docs' ||
((github.event_name != 'workflow_dispatch' || github.event.inputs.job == 'all') &&
needs.test.result == 'success'))
runs-on: racknerd-mini
env:
# Silences Node's internal "punycode module is deprecated" notice
# (astro's toolchain still requires it transitively) on every node
# invocation in this job, setup-node's own included.
NODE_OPTIONS: --no-deprecation
steps:
- name: Checkout
uses: actions/checkout@v4
with:
# The default GITEA_TOKEN is Gitea Actions' built-in synthetic
# bot identity, not a real account -- main's branch protection
# rejects its pushes outright (run 983, run 990), and it can't
# be whitelisted because it isn't an addable user. BOT_PUSH_TOKEN
# is a PAT on the rootiest account (already bypass-whitelisted)
# used only so this job's later push succeeds; commit authorship
# and GPG signing below still use the fishconfig-bot identity,
# which is unrelated to push auth.
token: ${{ secrets.BOT_PUSH_TOKEN }}
fetch-depth: 0
# Same shape as the test job's identical step; see its comment.
# Only the label set and path patterns differ, narrowed to the
# docs-specific subset of ci-paths.
- name: Determine relevance
id: relevance
env:
PR_LABELS: ${{ toJSON(github.event.pull_request.labels) }}
run: |
if [ "${{ github.event_name }}" != "pull_request" ]; then
echo "run=true" >>"$GITHUB_OUTPUT"
exit 0
fi
if printf '%s' "$PR_LABELS" | grep -qE '"name":[[:space:]]*"(Kind/Documentation|Area/Docs)"'; then
echo "run=true" >>"$GITHUB_OUTPUT"
exit 0
fi
git fetch origin "${{ github.event.pull_request.base.ref }}"
if git diff --name-only "origin/${{ github.event.pull_request.base.ref }}...HEAD" \
| grep -qE '^(docs/manual/|docs/build-manual\.py$|docs/manualtools\.py$|docs/verify-manual\.py$|docs/site/)'; then
echo "run=true" >>"$GITHUB_OUTPUT"
else
echo "run=false" >>"$GITHUB_OUTPUT"
fi
token: ${{ secrets.GITEA_TOKEN }}
- name: Install dependencies
if: steps.relevance.outputs.run == 'true'
run: |
sudo apt-get -o Acquire::Retries=3 update -qq
# apt-utils: see the "Install fish" step's identical comment in
# the test job for why its own debconf notice is accepted, not
# chased further.
sudo DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y apt-utils software-properties-common
sudo DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y software-properties-common
sudo add-apt-repository -y ppa:fish-shell/release-4
sudo apt-get -o Acquire::Retries=3 update -qq
sudo DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y pandoc python3-yaml fish
- name: Generate concatenated markdown
if: steps.relevance.outputs.run == 'true'
run: python3 docs/build-manual.py --concat -o docs/fish-config.md
# Regeneration MUST run before verification: verify-manual.py's
@@ -194,19 +87,13 @@ jobs:
# against docs/fish-config.md on disk. Before this step ran, that
# file was still the stale pre-push copy, so any ordinary edit under
# docs/manual/** failed the round-trip check before anything was
# regenerated. Do not reorder this back -- verification still gates
# regenerated. Do not reorder this back — verification still gates
# pandoc and the auto-commit below, it just no longer requires a
# contributor to hand-sync the generated file before pushing.
#
# This is the "documentation tests" section: on a PR, it runs
# (and can fail the job) whenever relevant, without needing the
# publish steps below to run at all.
- name: Verify manual integrity
if: steps.relevance.outputs.run == 'true'
run: python3 docs/verify-manual.py
- name: Compile man page
if: steps.relevance.outputs.run == 'true'
run: |
pandoc --standalone \
--from markdown \
@@ -214,30 +101,21 @@ jobs:
docs/fish-config.md \
-o docs/fish-config.1
# ──────────────────────── Publish only ───────────────────────
# Everything below deploys the production site and commits
# generated files straight to the checked-out branch. Never runs
# from a pull_request -- PR content isn't main yet, and a
# fork/branch push shouldn't touch prod.
- name: Set up Node
if: github.event_name != 'pull_request'
uses: actions/setup-node@v4
with:
node-version: "24"
- name: Generate site content
if: github.event_name != 'pull_request'
run: python3 docs/build-manual.py --site
- name: Build project wiki
if: github.event_name != 'pull_request'
working-directory: docs/site
run: |
npm ci --no-fund
npm ci
npx astro build
- name: Deploy to Cloudflare Pages
if: github.event_name != 'pull_request'
working-directory: docs/site
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
@@ -249,7 +127,6 @@ jobs:
--commit-dirty=true
- name: Commit generated docs
if: github.event_name != 'pull_request'
env:
BOT_GPG_KEY: ${{ secrets.CI_GPG_PRIVATE_KEY }}
run: |
@@ -269,7 +146,7 @@ jobs:
echo "pinentry-mode loopback" > "$GNUPGHOME/gpg.conf"
echo "allow-loopback-pinentry" > "$GNUPGHOME/gpg-agent.conf"
command -v gpg >/dev/null || sudo apt-get install -y --no-install-recommends gnupg
echo "$BOT_GPG_KEY" | base64 -d | gpg --batch --quiet --import
echo "$BOT_GPG_KEY" | base64 -d | gpg --batch --import
git config user.name "Gitea Actions Bot"
git config user.email "fishconfig-bot@git.rootiest.dev"
git config user.signingkey CAA082C2F3467E1F7217AD492075C120312D23F4
@@ -277,25 +154,7 @@ jobs:
git add docs/fish-config.md docs/fish-config.1 conf.d/__fish_config_op_registry.fish
git diff --cached --quiet && echo "No changes to commit" && exit 0
git commit -m "chore(docs): regenerate manual, man page, and component registry"
# npm ci + astro build + the Cloudflare deploy above can take
# several minutes, so main can move (another PR merges) before
# this push lands -- a bare `git push` was seen rejected as
# non-fast-forward for exactly that reason (run 976). This
# commit only ever touches generated files, so a rebase onto
# whatever landed is always mechanical; retry it a few times
# against a live race instead of failing the whole job.
pushed=0
for attempt in 1 2 3; do
if git push; then
pushed=1
break
fi
echo "push rejected (attempt $attempt/3), rebasing onto origin/main..." >&2
git fetch origin main
git rebase origin/main
done
test "$pushed" -eq 1
git push
# Stand-in for the GitHub mirror so the commit gets a completed status
# instead of the real jobs above sitting queued forever for a
@@ -307,4 +166,4 @@ jobs:
- name: Note that CI runs on Gitea
run: |
echo "This repository mirrors from Gitea (git.rootiest.dev), where CI actually runs."
echo "See the commit's status on the Gitea instance for the real test/docs results."
echo "See the commit's status on the Gitea instance for the real test/build-docs results."
+1 -78
View File
@@ -170,33 +170,6 @@ assumes you *do* have push access (maintainers, regular contributors).
approval before doing this** — there's no standing exception, no matter
how trivial the change looks.
### Branch protection on `main`
Gitea enforces these rules server-side — not just convention, so violating
one is a hard rejection, not a review comment:
- **No direct push**, except an allowlist: the repo owner and
`fishconfig-bot` (the docs-regen commit CI makes on every push — see
[Documentation Pipeline](#documentation-pipeline)). Everyone else merges
through a PR, which is what [the bullets above](#branching--pull-requests)
already describe.
- **Force-push is disabled outright** — no allowlist exception.
- **Signed commits are required.** An unsigned or bad-signature commit is
rejected at push time, which is what the tracked `pre-push` hook in
[Getting Started](#getting-started) catches locally before you find out
the hard way from Gitea.
- **`CI / test (pull_request)` must pass** before a PR can merge.
- **A PR with changes requested by a reviewer can't merge** until that's
resolved, even with enough approvals otherwise.
- **A PR behind `main` can't merge** — use Gitea's "Update branch" first.
This bites a stacked PR specifically: once the PR it was based on merges,
the stacked one now shows behind `main` and needs an update before it can
merge too, even though nothing about its own content changed.
- The repo owner is on the bypass allowlist and can override the above when
there's a genuine reason to (the emergency valve for [item 3 of keeping
feature branches focused](#branching--pull-requests)) — this is the
exception, not a way to routinely skip review.
### Pull request descriptions
Fill in `.github/PULL_REQUEST_TEMPLATE.md` — Gitea pre-loads it into the
@@ -417,8 +390,7 @@ all optional except where noted:
|---|---|
| `CATEGORY` | **Required to appear in the manual at all** — see below. |
| `COMPONENT` | Only for functions gated by the [opinionated-component system](#opinionated-components). |
| `DEPENDENCIES` | Other functions, and external CLI tools, this one needs for full functionality — required or optional-with-fallback alike. |
| `CLASSIFICATION` | Hazard/shadow-interaction tags, plus a couple of general-purpose ones (`manual-section`) — see below. |
| `DEPENDENCIES` | Other functions this one calls that a reader may want to look up. |
| `SYNOPSIS` | One-line usage form. |
| `DESCRIPTION` | Prose description; can span multiple paragraphs. |
| `ARGUMENTS` | Flags/positional args, one per line. |
@@ -442,9 +414,6 @@ A full example (`functions/claude.fish`):
# DEPENDENCIES
# agents-init
#
# CLASSIFICATION
# bypasses-shadow(claude)
#
# SYNOPSIS
# claude [ARGS...]
#
@@ -474,52 +443,6 @@ If your function genuinely doesn't fit any of these, add a new
`docs/manual/05-functions/NN-your-category.md` stub (with frontmatter
matching its siblings) rather than force-fitting it into an existing one.
**`CLASSIFICATION` is the general-purpose tag field, optional and omitted
when nothing applies:** mostly hazards and shadow interactions — whether
the function calls a
[C1-shadowed command](docs/manual/08-components-reference/01-c1-command-shadows.md)
bare wanting the override (`uses-shadow(ls)`) or bypasses it deliberately
via `command`/`builtin` (`bypasses-shadow(cat)`), and general hazards —
`destructive`, `network`, `blocking-prompt` — but not exclusively: a
function with its own dedicated manual section (below) carries
`manual-section(<slug>)` here too, so that fact is grep-able without
reading every `NOTES` field. Full tag definitions and placement rule:
[`docs/function-classification-schema.md`](docs/function-classification-schema.md).
### Dedicated manual sections for complex subsystems
A doc-header's `DESCRIPTION` is for that one function's usage — it stops
being the right place once a subsystem spans several functions, has its
own file layout, or has enough behavior (a decision table, a safety
model) that cramming it into one function's header would make that
header useless as a quick reference. When that happens, give the
subsystem its own numbered top-level section under `docs/manual/`
(follow the sibling sections' frontmatter shape: `title`, `manTitle`,
`sidebar.order`, `helpKeywords`) instead of stretching the header.
`docs/manual/16-agent-tooling.md` (`agents-init`/`agents-vault`/the
`AGENTS/` sub-repository) is the existing example — its own doc-headers
stay short and point there for the full picture, the same way this
document points at other reference files rather than repeating them.
A function with a dedicated section carries `manual-section(<slug>)` in
its own `# CLASSIFICATION` (see `functions/agents-init.fish`; full tag
definition in
[`docs/function-classification-schema.md`](docs/function-classification-schema.md))
— that's what makes the section discoverable without reading every
function's `NOTES` by hand, and it's what `docs/build-manual.py` reads to
render the "See also" line on the function's generated entry.
`docs/verify-manual.py` fails the build if the slug doesn't resolve to a
real page, so a typo or a renamed file can't go unnoticed — but it cannot
check the *content* is current. A `# NOTES` line pointing at the same page
(see the existing example) is worth adding too, for a reader who only
reads the header text rather than the generated docs, but the tag is the
part something else actually verifies.
This is a genuine exception to "the doc-header is the single source of
truth" above. **Whenever you change what one of these functions does,
update its dedicated section in the same commit or pull request** — not
as a follow-up.
### Private/internal helper functions
Functions named with a leading `_` (e.g. `_agents_init_ensure_gitignore`,
+5 -42
View File
@@ -1,9 +1,8 @@
# Rootiest Fish Configuration
# Fish Shell Configuration
This isn't a generic Fish shell configuration — it's the Rootiest Fish
Configuration: a feature-rich setup for CachyOS (Arch Linux), built
around a Catppuccin Mocha aesthetic with a curated set of modern CLI
tool integrations, smart shell functions, and a heavily customized
A feature-rich Fish shell configuration for CachyOS (Arch Linux),
built around a Catppuccin Mocha aesthetic with a curated set of modern
CLI tool integrations, smart shell functions, and a heavily customized
abbreviation system for keyboard-driven workflows.
📖 **[Project Wiki](https://fish.rootiest.fyi/)**
@@ -13,7 +12,6 @@ abbreviation system for keyboard-driven workflows.
- [Overview](#overview)
- [Session Logging](#session-logging)
- [Documentation](#documentation)
- [OS Compatibility](#os-compatibility)
- [Installation](#installation)
- [Personalization](#personalization)
- [Minimal Mode](#minimal-mode)
@@ -133,7 +131,7 @@ silent until you enable logging.
## Documentation
### [📖 Documentation Wiki](https://fish.rootiest.fyi/)
### [📖 Documentation Site](https://fish.rootiest.fyi/)
A Starlight-powered site rebuilt on every push to `main`. It covers
configuration variables, key bindings, abbreviations, all functions, the
@@ -172,41 +170,6 @@ The pager falls back through: **ov** → **bat** → **man -l** → **less** →
---
## OS Compatibility
This is a **Linux-only** configuration. It is developed and tested on an
Arch Linux system; `fish-deps` also detects `apt`, `dnf`, `zypper`, and `yum`
for broader distro support, but coverage outside Arch is thinner.
A number of functions call Linux-specific subsystems directly, with no
fallback: `systemd-inhibit` (`wake-lock`), `zramctl`/`swapon` (`swapstat`),
`sbctl` and UEFI Secure Boot state (`sbver`), and GNU coreutils flags like
`stat -c`/`numfmt` (`sudo-toggle`, `dng2avif`), which differ or don't exist
under a BSD userland.
Clipboard access (`y`, `p`, `paste`, `hist`) is the exception: it falls back
through `wl-copy`/`wl-paste` (Wayland), `xclip` (X11), and `win32yank.exe`
(WSL2), so it works on all three — there's still no `pbcopy`/`pbpaste`
fallback for macOS.
**macOS** is not supported — `_fish_deps_detect_pm` checks for `brew`, but
that alone doesn't make the functions above work; they have no macOS
equivalent path today.
**Windows** is not supported. Fish has no native Windows build, and this
config isn't tested under WSL either. WSL2 can run `systemd`, so basic shell
use may work; Secure Boot/zram state is still meaningless inside a VM, but
clipboard integration works via `win32yank.exe` once it's reachable through
WSL interop — `fish-deps install` can fetch it for you.
**Assumed present on any Linux system this runs on:** `git`, `gpg`, `tar`,
and GNU coreutils. These aren't tracked by `fish-deps` — they're base-system
utilities, not opt-in software with an install journey to manage.
See [OS Compatibility](https://fish.rootiest.fyi/10-installation/#os-compatibility) in the project wiki for more.
---
## Installation
This config is managed as a Git repository. To use it on a new machine:
+7
View File
@@ -0,0 +1,7 @@
complete --command fisher --exclusive --long help --description "Print help"
complete --command fisher --exclusive --long version --description "Print version"
complete --command fisher --exclusive --condition __fish_use_subcommand --arguments install --description "Install plugins"
complete --command fisher --exclusive --condition __fish_use_subcommand --arguments update --description "Update installed plugins"
complete --command fisher --exclusive --condition __fish_use_subcommand --arguments remove --description "Remove installed plugins"
complete --command fisher --exclusive --condition __fish_use_subcommand --arguments list --description "List installed plugins matching regex"
complete --command fisher --exclusive --condition "__fish_seen_subcommand_from update remove" --arguments "(fisher list)"
-3
View File
@@ -24,9 +24,6 @@ __fish_config_op_enabled (status basename); or exit
# COMPONENT
# autoexec/sync
#
# CLASSIFICATION
# bypasses-shadow(cat)
#
# SYNOPSIS
# __auto_pull_on_pwd (event handler, --on-variable PWD)
#
+4 -6
View File
@@ -97,13 +97,11 @@ if __fish_config_op_enabled (status basename) tricks-bang
end
end
# Timestamped history view. Named pretty-history (not history) so it never
# shadows the fish builtin -- every function in this config that expects
# stock `history` semantics (search, --max, merge, ...) would otherwise
# silently break, which has happened more than once. Opinionated (C1
# aliasing); when disabled, the function is never defined.
# Fish command history override to show timestamps
# Shadowing the history command is opinionated (C1 aliasing); when disabled,
# the function is never defined and fish's stock history behavior applies.
if __fish_config_op_enabled (status basename) aliases-tricks
function pretty-history --description 'History with timestamps prepended to every entry'
function history
builtin history --show-time='%F %T '
end
end
-119
View File
@@ -1,119 +0,0 @@
# ╭──────────────────────────────────────────────────────────╮
# │ GitIgnore Boilerplate Template │
# ╰──────────────────────────────────────────────────────────╯
#
# ──────────────────── OS-Generated Files ────────────────────
# automatic backup files created by some editors (e.g., Vim, Emacs)
*~
# temporary files created if a process still has a handle to a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# MacOS junk
.DS_Store
Thumbs.db
# Linux trash folder which might appear on any partition or disk
.Trash-*
# files created when an open file is removed but is still being accessed
.nfs*
# ─────────────────── Debug/Temporary/Testing ────────────────
# Matches OLD / .OLD
[Oo][Ll][Dd]/
.[Oo][Ll][Dd]/
# Matches DISABLE / .DISABLE
[Dd][Ii][Ss][Aa][Bb][Ll][Ee]/
.[Dd][Ii][Ss][Aa][Bb][Ll][Ee]/
# Matches DISABLED / .DISABLED
[Dd][Ii][Ss][Aa][Bb][Ll][Ee][Dd]/
.[Dd][Ii][Ss][Aa][Bb][Ll][Ee][Dd]/
# Matches DEBUG / .DEBUG
[Dd][Ee][Bb][Uu][Gg]/
.[Dd][Ee][Bb][Uu][Gg]/
# Matches TMP / .TMP
[Tt][Mm][Pp]/
.[Tt][Mm][Pp]/
# Matches TEMP / .TEMP
[Tt][Ee][Mm][Pp]/
.[Tt][Ee][Mm][Pp]/
# Matches TEMPORARY / .TEMPORARY
[Tt][Ee][Mm][Pp][Oo][Rr][Aa][Rr][Yy]/
.[Tt][Ee][Mm][Pp][Oo][Rr][Aa][Rr][Yy]/
# Matches TESTING / .TESTING
[Tt][Ee][Ss][Tt][Ii][Nn][Gg]/
.[Tt][Ee][Ss][Tt][Ii][Nn][Gg]/
# ─────────────────── Scratchpad / Scratch Files ───────────────
# Root-only files/folders starting with scratch or .scratch
/[Ss][Cc][Rr][Aa][Tt][Cc][Hh]*
/.[Ss][Cc][Rr][Aa][Tt][Cc][Hh]*
# Matches SCRATCH / .SCRATCH anywhere (directory)
[Ss][Cc][Rr][Aa][Tt][Cc][Hh]/
.[Ss][Cc][Rr][Aa][Tt][Cc][Hh]/
# Matches SCRATCHPAD / .SCRATCHPAD anywhere (file or directory)
[Ss][Cc][Rr][Aa][Tt][Cc][Hh][Pp][Aa][Dd]
.[Ss][Cc][Rr][Aa][Tt][Cc][Hh][Pp][Aa][Dd]
# Matches any directory starting with .SCRATCH anywhere
.[Ss][Cc][Rr][Aa][Tt][Cc][Hh]*/
# ─────────────────── Dev Notes / Working Notes ────────────────
# Root-only files/folders starting with devnote or .devnote
/[Dd][Ee][Vv][Nn][Oo][Tt][Ee]*
/.[Dd][Ee][Vv][Nn][Oo][Tt][Ee]*
# Matches DEVNOTE / .DEVNOTE anywhere (file or directory)
[Dd][Ee][Vv][Nn][Oo][Tt][Ee]
.[Dd][Ee][Vv][Nn][Oo][Tt][Ee]
# Matches DEVNOTES / .DEVNOTES anywhere (file or directory)
[Dd][Ee][Vv][Nn][Oo][Tt][Ee][Ss]
.[Dd][Ee][Vv][Nn][Oo][Tt][Ee][Ss]
# Matches any directory starting with .DEVNOTE anywhere
.[Dd][Ee][Vv][Nn][Oo][Tt][Ee]*/
# ─────────────────── AI Sessions and Rules ──────────────────
# CLAUDE.md / AGENTS.md / GEMINI.md / ANTIGRAVITY.md are deliberately NOT
# ignored here: an increasing number of projects commit these instruction
# files on purpose. Only the tool-owned session/state dirs are ignored.
# Matches .claude* anywhere (files or directories)
.[Cc][Ll][Aa][Uu][Dd][Ee]*
# Matches .gemini* anywhere (files or directories)
.[Gg][Ee][Mm][Ii][Nn][Ii]*
# Matches .antigravity* anywhere (files or directories)
.[Aa][Nn][Tt][Ii][Gg][Rr][Aa][Vv][Ii][Tt][Yy]*
# Matches .AGY* anywhere (files or directories)
.[Aa][Gg][Yy]*
# Matches .agents* anywhere (files or directories)
.[Aa][Gg][Ee][Nn][Tt][Ss]*
# Matches .REMEMBER* anywhere (files or directories)
.[Rr][Ee][Mm][Ee][Mm][Bb][Ee][Rr]*
# ──────────────────── Planning Artifacts ───────────────────
# Catalog files generated by pre-implementation analysis passes
.superpowers
docs/superpowers
docs/specs
docs/devlogs
# ──────────────────────────────────────────────────────────────
+6 -89
View File
@@ -202,7 +202,7 @@ def build_concat(root: Path) -> str:
Only bodies are passed: the pandoc metadata block above is not prose
and must survive byte-for-byte.
"""
entries = build_entries(mt.parse_functions(FUNCTIONS), root=root)
entries = build_entries(mt.parse_functions(FUNCTIONS))
chunks: list[str] = []
pandoc_path = root / "_pandoc.yml"
if pandoc_path.exists():
@@ -703,67 +703,7 @@ ENTRY_HEADS = {
}
def _classification_tags(raw: list[str]) -> list[str]:
"""Split a CLASSIFICATION body into its comma-separated tags.
A plain comma split (as `names()` uses for DEPENDENCIES) would break on
the commas inside `uses-shadow(rm, cp)`-style tags, so this only splits
on commas at paren depth 0.
"""
text = " ".join(raw)
tags: list[str] = []
depth = 0
start = 0
for i, ch in enumerate(text):
if ch == "(":
depth += 1
elif ch == ")":
depth = max(0, depth - 1)
elif ch == "," and depth == 0:
tags.append(text[start:i].strip())
start = i + 1
tags.append(text[start:].strip())
return [t for t in tags if t]
MANUAL_SECTION_RE = re.compile(r"^manual-section\(([\w./-]+)\)$")
def _manual_section_slug(tags: list[str]) -> str | None:
"""Pull the slug out of a `manual-section(<slug>)` CLASSIFICATION tag, if present."""
for tag in tags:
m = MANUAL_SECTION_RE.match(tag)
if m:
return m.group(1)
return None
def _resolve_manual_section(root: Path | None, slug: str) -> tuple[str, str, str] | None:
"""Resolve a manual-section(<slug>) tag to (display label, site link, doc-relative path).
Looks for <slug>.md (a top-level single-file section) or <slug>/index.md
(a directory-based section), matching the two shapes docs/manual/
actually uses. The display label is read fresh from the target's own
frontmatter (manTitle, falling back to title) rather than duplicated in
the tag, so a renumbered section never needs its tag updated -- only
the slug (the filename) does, and that only changes if the page itself
is renamed. Returns None -- silently, this is a build, not a check;
verify-manual.py is where a dangling slug is a real failure -- when
<root> is unset or neither candidate exists.
"""
if root is None:
return None
for relpath in (f"{slug}.md", f"{slug}/index.md"):
if (root / relpath).exists():
fm, _ = mt.parse(root / relpath)
label = fm.get("manTitle") or fm.get("title", slug)
return label, f"/{slug}/", relpath
return None
def render_entry(
fn: dict[str, list[str]], used_by: list[str], link=None, root: Path | None = None
) -> str:
def render_entry(fn: dict[str, list[str]], used_by: list[str], link=None) -> str:
"""Render one parsed function header as a manual entry body.
Emits the same man-page shape Section 5 was authored in — one 4-space
@@ -796,22 +736,14 @@ def render_entry(
def names(raw: list[str]) -> list[str]:
return [n for n in re.split(r"[,\s]+", " ".join(raw)) if n]
classification = _classification_tags(fn.get("CLASSIFICATION", []))
refs = []
for label, values in (
("Dependencies", names(fn.get("DEPENDENCIES", []))),
("Classification", classification),
("Used by", sorted(used_by)),
):
if values:
rendered = ", ".join(link(v) if link else f"`{v}`" for v in values)
refs.append(f"**{label}:** {rendered}")
slug = _manual_section_slug(classification)
if slug:
resolved = _resolve_manual_section(root, slug)
if resolved:
label, _href, relpath = resolved
refs.append(f"**See also:** {label} (`docs/manual/{relpath}`)")
if refs:
block += "\n\n" + "\n\n".join(refs)
return block
@@ -915,9 +847,7 @@ SITE_SECTIONS = (
)
def render_entry_site(
fn: dict[str, list[str]], used_by: list[str], link=None, root: Path | None = None
) -> str:
def render_entry_site(fn: dict[str, list[str]], used_by: list[str], link=None) -> str:
"""Render one parsed function header as a manual entry body for the site.
Unlike `render_entry` (the single indented man-page block pandoc wants,
@@ -951,22 +881,14 @@ def render_entry_site(
def names(raw: list[str]) -> list[str]:
return [n for n in re.split(r"[,\s]+", " ".join(raw)) if n]
classification = _classification_tags(fn.get("CLASSIFICATION", []))
refs = []
for label, values in (
("Dependencies", names(fn.get("DEPENDENCIES", []))),
("Classification", classification),
("Used by", sorted(used_by)),
):
if values:
rendered = ", ".join(link(v) if link else f"`{v}`" for v in values)
refs.append(f"**{label}:** {rendered}")
slug = _manual_section_slug(classification)
if slug:
resolved = _resolve_manual_section(root, slug)
if resolved:
label, href, _relpath = resolved
refs.append(f"**See also:** [{label}]({href})")
if refs:
parts.append("\n\n".join(refs))
@@ -974,7 +896,7 @@ def render_entry_site(
def build_entries(
functions: dict[str, dict], link=None, site: bool = False, root: Path | None = None
functions: dict[str, dict], link=None, site: bool = False
) -> dict[str, list[tuple[str, str]]]:
"""Group rendered entries by category stem, ordered by function name.
@@ -982,9 +904,6 @@ def build_entries(
authored: a bidirectional link maintained by hand drifts the moment one
side is edited. `site` selects `render_entry_site` (headings + tables)
over `render_entry` (the man-page indented block `build_concat` needs).
`root` (docs/manual/) resolves any `manual-section(<slug>)`
CLASSIFICATION tag to that page's own title -- omitted, the default,
an entry with the tag just gets no "See also" line rather than failing.
"""
used_by: dict[str, list[str]] = {}
for name, fn in functions.items():
@@ -996,7 +915,7 @@ def build_entries(
out: dict[str, list[tuple[str, str]]] = {}
for name in sorted(functions):
fn = functions[name]
body = render(fn, used_by.get(name, []), link, root=root)
body = render(fn, used_by.get(name, []), link)
out.setdefault(fn["CATEGORY"][0], []).append((name, body))
return out
@@ -1097,9 +1016,7 @@ def build_site(root: Path, out: Path) -> list[dict]:
out.mkdir(parents=True)
functions = mt.parse_functions(FUNCTIONS)
entries = build_entries(
functions, link=lambda n: _entry_link(n, functions), site=True, root=root
)
entries = build_entries(functions, link=lambda n: _entry_link(n, functions), site=True)
sidebar: list[dict] = [{"label": "Home", "link": "/"}]
standard_groups: dict = {}
+49 -1020
View File
File diff suppressed because it is too large Load Diff
+8 -26
View File
@@ -291,8 +291,6 @@ network-shadow=### network
monitor=### monitor
shell-tools=### shell-tools
dev-tools=### dev-tools
classification=### For function authors
shadow-classification=### For function authors
c2=## C2 — Startup Side-Effects
autoexec=## C2 — Startup Side-Effects
startup=## C2 — Startup Side-Effects
@@ -362,15 +360,6 @@ fish-plugins-manifest=## fish_plugins Manifest
# ── Section 10: Installation ────────────────────────────────────
installation=# 10. INSTALLATION
install=# 10. INSTALLATION
os=## OS Compatibility
os-compatibility=## OS Compatibility
operating-system=## OS Compatibility
compatibility=## OS Compatibility
linux=## OS Compatibility
macos=## OS Compatibility
mac=## OS Compatibility
windows=## OS Compatibility
wsl=## OS Compatibility
sentinel=## Return Sentinel
updating=## Updating
@@ -423,21 +412,14 @@ contributing=# 15. CONTRIBUTING
contribute=# 15. CONTRIBUTING
forge=# 15. CONTRIBUTING
# ── Section 16: AI Agent Tooling ──────────────────────────────
agent=# 16. AI AGENT TOOLING
agent-tooling=# 16. AI AGENT TOOLING
agents.md=# 16. AI AGENT TOOLING
claude-code=# 16. AI AGENT TOOLING
antigravity=# 16. AI AGENT TOOLING
# ── Section 16: Attribution ───────────────────────────────────
attribution=# 16. ATTRIBUTION
credits=# 16. ATTRIBUTION
# ── Section 17: Attribution ───────────────────────────────────
attribution=# 17. ATTRIBUTION
credits=# 17. ATTRIBUTION
# ── Section 18: License ────────────────────────────────────────
license=# 18. LICENSE
licensing=# 18. LICENSE
agpl=# 18. LICENSE
copyright=# 18. LICENSE
# ── Section 17: License ───────────────────────────────────────
license=# 17. LICENSE
licensing=# 17. LICENSE
agpl=# 17. LICENSE
copyright=# 17. LICENSE
+47 -907
View File
File diff suppressed because it is too large Load Diff
-125
View File
@@ -1,125 +0,0 @@
# Function CLASSIFICATION schema
This is the canonical definition of the `# CLASSIFICATION` function
doc-header label. It's referenced from code comments and commit messages —
link here, not to anything under `AGENTS/` (that tree is git-ignored local
agent state, not part of the repo).
`CLASSIFICATION` isn't limited to command-safety hazards, even though most
of the closed set below is exactly that — it's the general-purpose place to
tag what a function touches or how it behaves, whenever that's worth
surfacing without reading the function's own body. `manual-section(<slug>)`
is the one tag in the set that isn't a hazard at all: it marks a function
that has its own dedicated manual section beyond this header (see
[Dedicated manual sections for complex subsystems](../CONTRIBUTING.md#dedicated-manual-sections-for-complex-subsystems)).
See [Public function documentation header](../CONTRIBUTING.md#public-function-documentation-header)
in `CONTRIBUTING.md` for where `CLASSIFICATION` fits among the other header
labels, and [C1 — Command Shadows](manual/08-components-reference/01-c1-command-shadows.md)
for the full list of C1-shadowed commands this schema's shadow tags refer to.
## Format
Optional. Comma-separated tags from the closed set below, on the indented
body line directly under the label:
```fish
# CLASSIFICATION
# uses-shadow(ls), destructive
```
Omit the label entirely when nothing applies — omission means "nothing to
flag," not "not yet audited," so don't add it speculatively, and don't add
it empty as a placeholder.
## Tags
- **`uses-shadow(name[,name...])`** — calls a C1-shadowed command (see the
C1 doc linked above) bare, deliberately wanting the overridden behavior
(e.g. `ls` wanting eza's icons for a human to read).
- **`bypasses-shadow(name[,name...])`** — calls `command <name>`,
`builtin <name>`, or (for `help` specifically) `__original_help $argv`,
deliberately forcing stock behavior because the shadow's override would
break this function's logic: timestamps leaking into a parsed capture,
`-i` prompting on a path meant to run unattended, structural output
changes breaking a `string`/`sed` parse, etc.
- **`self-limiting(name[,name...])`** — calls a shadowed command bare, and
it's safe not because the caller did anything but because *the shadow's
own logic* already neutralizes the override for this call. Verify the
actual condition per shadow, it's not the same check for each one:
- `rm` falls back to `command rm` for any flag **except** a bare `-r`,
`-R`, or `--recursive` (those still route to `trash put`) — so
`rm -f`/`rm -rf` qualify, but `rm -r $dir` alone does not.
- `mkdir` falls back to `command mkdir -p` for *any* flag at all, no
exception.
- `--color=auto`/`bat`'s own tty auto-detection (`grep`, `fgrep`,
`egrep`, `dir`, `vdir`, `cat` — verified byte-identical to stock when
piped or captured, since none of these force color on a
non-terminal).
Document it explicitly rather than leaving the bare call untagged: if a
shadow's bypass condition is ever weakened, narrowed, or removed, every
`self-limiting` site is one grep away instead of silently wrong.
Don't use this for `ls` — eza's long-format/icon layout is structural,
not tty-gated, so it stays different from stock `ls` even piped; a
bare `ls` call still needs `uses-shadow(ls)` or a real bypass.
- **`destructive`** — can irreversibly delete or overwrite data: `rm -f`,
`rm -rf`, truncating or force-overwriting a file, `git push --force`.
Routine cleanup of the function's own `$tmpdir`/`$_tmpdir`/`mktemp`
output (or other output it just created in this same call) is expected
behavior, not a hazard — don't tag it.
- **`network`** — makes an outbound network call: `curl`, `wget`, `ssh`,
`git fetch`/`pull`/`push`/`clone`, `paru`/`yay` (package-manager network
ops), talking to an API, etc.
- **`blocking-prompt`** — can block waiting on interactive confirmation
with no non-interactive escape hatch: a shadow's forced `-i`, fish's
`read` (genuinely waiting on a terminal — not a `string split | read`
or `while read` consuming a pipe, which never blocks), a `confirm`-style
prompt with no `--yes`/`--force`/`--silent` bypass. Don't tag a function
that's only ever meant to be run interactively at a prompt (a keybinding
handler, an fzf-driven picker) — the hazard this tag exists for is a
script or another function calling it unexpectedly, not a human running
it themselves.
- **`manual-section(<slug>)`** — this function has a dedicated manual
section beyond its own header; `<slug>` is that page's filename under
`docs/manual/` with the extension dropped (`16-agent-tooling` for
`docs/manual/16-agent-tooling.md`; a directory-based section like
`08-components-reference` uses its directory name the same way, resolved
against its `index.md`). `docs/build-manual.py` reads the target page's
own `manTitle`/`title` at build time and renders a **See also** line on
the function's generated entry — the tag only needs to keep pointing at
the right *file*; the displayed section number is never duplicated into
the tag, so it can't go stale on its own if the manual gets renumbered.
`docs/verify-manual.py` fails if the slug doesn't resolve to a real page.
Multiple functions may carry the same slug (`agents-init` and
`agents-vault` both point at `16-agent-tooling`, one section covering
both). See [Dedicated manual sections for complex subsystems](../CONTRIBUTING.md#dedicated-manual-sections-for-complex-subsystems)
in `CONTRIBUTING.md` for when a function's behavior has outgrown its
header and belongs in one of these instead.
## Placement
Directly under `# DEPENDENCIES` if the header has one; otherwise directly
under `# COMPONENT`; otherwise directly under `# CATEGORY`; otherwise as
the first label in the header block (this is the common case for internal
`_`-prefixed helpers, which usually carry none of the three).
## Judgment calls
`uses-shadow` vs `bypasses-shadow` is the easiest place to get subtly
wrong — verify against the actual code, not just whether the name appears
in the file. A function that only calls a *helper* which itself interacts
with a shadow does not get the tag; the tag belongs on the helper. When
generating these tags in bulk (e.g. delegating the sweep to another
model), review every result against the source before trusting it — this
schema's own rollout caught several false positives this way: a piped
`read` misread as an interactive prompt, a documented `--yes` flag missed
as an escape hatch, and cleanup of a function's own temp output flagged
as `destructive` despite the explicit exclusion above.
`rm` specifically has its own internal flag check (any flag other than
`-r`/`-R`/`--recursive` falls back to `command rm` *inside the shadow
itself*, before it ever touches trash) — a caller writing plain `rm -f`
or `rm -rf` is not bypassing anything itself, the shadow is. Only tag
`bypasses-shadow(rm)` when the caller explicitly writes `command rm` or
`builtin rm`; a bare `rm -f`/`rm -rf` call gets no shadow tag at all.
+1 -4
View File
@@ -29,11 +29,8 @@ are active in Insert, Normal, and Visual modes unless noted.
Ctrl+Alt+U Strip the first token of the current command line,
leaving arguments in place with the cursor at the
start. Useful for quickly retyping the command. On
an empty command line, first recalls the most recent
history entry (like Up), then strips its first token.
start. Useful for quickly retyping the command.
Example: "mkdir new_folder" -> " new_folder"
Example: empty line after "cd /home/me" -> " /home/me"
Ctrl+Alt+= Evaluate the current command line buffer with
Qalculate! (qalc) and print the result inline.
-1
View File
@@ -59,7 +59,6 @@ matter if you already use that specific tool. Skipped by
| `screen` | GNU screen; fallback backend for `jobrunner` when `tmux` is unavailable. |
| `marktext` | Markdown editor; backs the `md` wrapper, which is the only thing that references it. No distro packages it under a common name, so `fish-deps` offers the AUR package (`marktext-bin`) on Arch and otherwise installs upstream's AppImage to `~/.local/bin/marktext`. |
| `firejail` | Sandbox; needed only by `md --read-only`, which uses it to make MarkText unable to save over the file it opened. Every other `md` invocation works without it. |
| `win32yank.exe` | Clipboard bridge for WSL2; backs the `y`/`p`/`paste`/`hist` clipboard fallback chain when neither `wl-copy`/`wl-paste` nor `xclip` are present. `fish-deps` only offers to install it when WSL2 is detected (`microsoft` in `/proc/sys/kernel/osrelease`), downloading the x86_64 binary from GitHub releases to `~/.local/bin`. |
## Terminal Emulators
@@ -19,22 +19,18 @@ all of these commands.
rg rg --hyperlink-format=kitty system rg
mkdir verbose path-tree display on creation mkdir -p silently
bash XDG bashrc + $SHELL reset on exit system bash
history timestamps prepended to every entry fish builtin history
cp / mv forced -i confirmation prompt cp / mv unmodified
wget forced --continue (resume downloads) system wget
grep/fgrep/egrep forced --color=auto system grep variants
dir / vdir forced --color=auto system dir / vdir
help config intercepts "help config" → config-help fish builtin help
claude ensures AGENTS/ is scaffolded before launch command claude
claude auto-links AGENTS.md as CLAUDE.md before launch command claude
edit multi-editor launcher (GUI/term + fallbacks) $EDITOR/nvim/nano/vi
When C1 is disabled, `rm` uses bare `command rm` with no wrapper — files
are permanently deleted, not trashed. There is no intermediate safety net.
`history` itself is never shadowed — every function in this config that
reads history depends on its stock builtin semantics. `pretty-history`
(same `aliases-tricks` toggle) is a separate command that prints history
with a timestamp prepended to every entry.
## Sub-categories
`__fish_config_op_aliases` sub-divides into six sub-categories, each with
@@ -64,51 +60,6 @@ and the `help config` interception.
## dev-tools
`claude` (AGENTS/ scaffolding) and `edit` (multi-editor
`claude` (AGENTS.md/CLAUDE.md auto-linking) and `edit` (multi-editor
launcher), plus `agy`.
## For function authors
Calling one of these names bare from inside your own function means the
override runs whenever C1 (or its sub-category) is on — which may not be
what your function wants: a shadow can change stdout (`cat`'s syntax
highlighting, `mkdir`'s tree display), prompt interactively where none is
expected (`cp`/`mv`'s forced `-i`), or reshape output structurally (`ls`'s
icons/columns, `rg`'s hyperlink markers). If your function's logic depends
on stock behavior, bypass the shadow deterministically, regardless of the
toggle state:
Shadow Bypass Why
─────────────────────────────────────────────────────────────────────────
ls, cat, rm, less, du, command <name> Real external
top, ping, ssh, rg, binaries — a
mkdir, bash, cp, mv, real system command
wget, grep/fgrep/egrep, exists to fall
dir/vdir, claude back to.
cd builtin cd The one true
fish builtin
in this table.
help config __original_help $argv `help` is neither
a builtin nor an
external binary
(embedded in the
fish binary
itself) — see
conf.d/help.fish
for why the
wrapper keeps its
own backup copy.
edit (nothing to bypass to) Purely our own
invention, no
stock command
exists. Call
$EDITOR/$VISUAL
yourself if you
want a plain
editor launch.
A function's own doc header records which of these it depends on: see the
`CLASSIFICATION` label (`uses-shadow(...)` / `bypasses-shadow(...)`),
documented in full at
[`docs/function-classification-schema.md`](https://git.rootiest.dev/rootiest/fish-config/src/branch/main/docs/function-classification-schema.md).
@@ -13,7 +13,7 @@ These features couple the shell to specific external tools. Disabling
spwin Kitty or WezTerm
tab Kitty, WezTerm, or Konsole
split Kitty or WezTerm
hist fzf + wl-copy, xclip, or win32yank.exe (WSL2)
hist fzf + wl-copy (Wayland clipboard)
logs fzf + ov; reads from ~/.terminal_history/
upgrade paru or yay (Arch Linux only)
WakaTime hook wakatime CLI and a configured API key
-45
View File
@@ -6,13 +6,6 @@ sidebar:
helpKeywords:
- installation
- install
- os
- operating system
- compatibility
- linux
- macos
- windows
- wsl
---
This configuration is managed as a git repository. To deploy on a new machine:
@@ -24,44 +17,6 @@ Then open a new Fish shell. Fisher installs automatically on first launch
and the Catppuccin Mocha theme is applied. All other plugin functionality is
bundled directly with this config and requires no additional installation.
## OS Compatibility
This is a **Linux-only** configuration. It is developed and tested on an
Arch Linux system; `fish-deps` also detects `apt`, `dnf`, `zypper`, and
`yum` for broader distro support, but coverage outside Arch is thinner.
IMPORTANT: A number of functions call Linux-specific subsystems directly, with
no fallback:
- `systemd-inhibit` (`wake-lock`)
- `zramctl` / `swapon` (`swapstat`)
- `sbctl` and UEFI Secure Boot state (`sbver`)
- GNU coreutils flags such as `stat -c` and `numfmt` (`sudo-toggle`,
`dng2avif`), which differ or don't exist under a BSD userland
Clipboard access (`y`, `p`, `paste`, `hist`) is the exception: it falls back
through `wl-copy`/`wl-paste` (Wayland), `xclip` (X11), and `win32yank.exe`
(WSL2), so it works on all three. There is still no `pbcopy`/`pbpaste`
fallback for macOS.
**macOS** is not supported. `_fish_deps_detect_pm` does check for `brew`, but
that alone does not make the functions above work — they have no macOS
equivalent path today.
**Windows** is not supported. Fish itself has no native Windows build;
upstream's own "Windows" install docs are Cygwin/WSL workarounds, not a real
port. This config is not tested under WSL either. WSL2 runs a real Linux
kernel and can run `systemd`, so basic shell use may work; `zramctl`,
`sbctl`, and Secure Boot state are still meaningless inside a VM, but
clipboard integration works via `win32yank.exe` (see above) once it's
installed on the Windows side and reachable through WSL interop.
**Assumed present on any Linux system this runs on:** `git`, `gpg`, `tar`,
and GNU coreutils (for `stat`, `date`, `numfmt`). These are not tracked by
`fish-deps` — see the [Dependency Catalog](/06-dependency-catalog/) — because
they are base-system utilities, not opt-in software with an install journey
to manage. A system missing any of them is missing basic Linux tooling, not
a `fish-deps` gap.
## Return Sentinel
`config.fish` ends with a return sentinel guard. Any lines appended after it by
-312
View File
@@ -1,312 +0,0 @@
---
title: AI Agent Tooling
manTitle: 16. AI AGENT TOOLING
sidebar:
order: 20
helpKeywords:
- agent
- agents-init
- agents-vault
- AGENTS.md
- claude-code
- agy
- antigravity
---
This section explains the machinery behind AI coding agents (Claude Code,
Antigravity/agy) working in a project checked out from this configuration:
where their instructions live, how they get there, and the safety rules
that keep an agent's launch-time bookkeeping from touching a repository's
own tracked history. Command-line usage for the functions named here
(`agents-init`, `agents-vault`) is generated from their own doc headers —
see Section 5.
## The problem this solves
An AI coding agent needs a persistent, project-scoped place to keep
instructions, memory, and working notes. Committing that material directly
into a project's normal history mixes two concerns that change at
different rates and for different reasons: the project's own code, and an
agent's evolving working state. It also means every project accumulates
its own copy of agent tooling (hooks, version files, convention
documents) that has nothing to do with that project's actual purpose.
`agents-init` and `agents-vault` exist to keep that material out of the
main repository while still making it feel local: an agent reads and
writes `AGENTS.md` exactly where it would expect to find it, but the real
content and its history live in a separate, self-contained git repository
that the main project never tracks.
## The AGENTS.md convention
`AGENTS.md` is a plain-text file at a project's root (and, as this
configuration extends the idea, at the root of any subdirectory with its
own scoped conventions) that an AI agent reads for repository-specific
instructions. It has become a convention shared across coding agents, not
one tool's proprietary format.
Claude Code originally required its own `CLAUDE.md` filename specifically.
It now reads `AGENTS.md` natively whenever no `CLAUDE.md` is present, which
retired the need for this configuration to create, maintain, or symlink
`CLAUDE.md` at all. A project scaffolded by `agents-init` today carries
only `AGENTS.md` — at the root, and in any subdirectory that has grown its
own scoped conventions (`functions/`, `docs/`, and so on, in this
repository's own case). A leftover `CLAUDE.md` from before this change is
retired automatically the next time `agents-init` runs: renamed, not
preserved under its old name, so nothing is ever left tracking two copies
of the same instructions under two different filenames.
## The AGENTS/ sub-repository
`agents-init` scaffolds a directory named `AGENTS/` at a project's root.
It is a self-contained git repository — its own `.git`, its own commit
history, its own hooks — and it is gitignored from the project it lives
inside. The project's own `AGENTS.md` (and every subdirectory's) is a
symlink into it:
$PROJECT/AGENTS/
├── AGENTS.md Canonical root agent spec (real file)
├── functions/AGENTS.md Canonical spec for functions/, and likewise for any other scoped subdirectory
├── plans/ Superpowers implementation plans
├── specs/ Superpowers design specs
├── devlogs/ Agent development logs
├── .version MAJOR.MINOR.PATCH structure version
└── .agents-tools/ Version-bump script and git hook shims (committed)
An agent editing `$PROJECT/AGENTS.md` is, transparently, editing
`$PROJECT/AGENTS/AGENTS.md` — the file-editing tools most agents ship with
resolve a symlinked directory's contents normally, but they cannot write
*through* a symlinked file itself, which is why the seed content
`agents-init` writes for a brand-new project spells this out directly to
the agent reading it.
IMPORTANT: This means an agent must never try to write to a *symlink
named* `AGENTS.md` directly. The seed instructions `agents-init` writes
for a fresh project tell the agent this explicitly, pointing it at the
real file inside `AGENTS/`.
### Version tracking and hooks
Every `AGENTS/` repository carries a `.version` file (seeded `1.0.0`) and
a self-contained version bumper, wired through `core.hooksPath` rather
than the ordinary `.git/hooks/` directory:
- A **pre-commit** hook bumps `.version` on every commit: the MINOR
field moves when the set of tracked top-level directories changes
(a new subdirectory convention was adopted, or one was dropped), the
PATCH field otherwise. The MAJOR field is manual-only.
- A **prepare-commit-msg** hook appends `(vX.Y.Z)` to the commit
subject, so the version history is legible from `git log` alone.
Each hook shim then chains to whatever hook of the same name the
project's *global* or *system* `core.hooksPath` already points at — a
credential scanner like ggshield, Git LFS, or anything else already
wired in ahead of this. Pointing `core.hooksPath` at `.agents-tools/hooks`
locally does not shadow those; it runs both.
The `.agents-tools/` scripts themselves are copied in from this
configuration's own `scripts/agents-tools/` and refreshed automatically
whenever their version marker moves, so every project's `AGENTS/`
repository stays current with this configuration without any manual step.
Downstream tooling that wants to know whether a project's `AGENTS/`
*structure* changed — as opposed to just its content — can read the
`.version` file's MINOR field directly rather than diffing the tree.
## Per-directory discovery
The convention is not limited to a project's root. Any directory that
carries its own `AGENTS.md` — `functions/`, `docs/`, or a subdirectory of
a much larger project with genuinely distinct conventions of its own —
gets the identical treatment: a real file inside `AGENTS/<that path>/`,
and a symlink at the project location pointing back to it. `agents-init`
finds these automatically on every run, rather than working from a fixed
list, by walking the project tree for any file literally named
`AGENTS.md` or `CLAUDE.md`.
Each directory found is settled into exactly one of four states, in
order, so a later run only ever sees a directory that is already
consistent:
1. **An inverted mirror** (an older layout, where `CLAUDE.md` was the
real file inside `AGENTS/` and `AGENTS.md` was symlinked to it) is
flipped in place — same bytes, new name.
2. **A real file at the project level, with no real file inside
`AGENTS/` yet**, is adopted: a lone `AGENTS.md` moves in as-is; a
lone `CLAUDE.md` is renamed on the way in, never preserved under its
own name. When both `AGENTS.md` and `CLAUDE.md` are real files at
once, byte-identical content is deduplicated (the `AGENTS.md` side is
kept); different content is left exactly as it is, with a warning —
this function has no way to know which one is authoritative, and
guessing wrong would silently discard the other.
3. **A stray `CLAUDE.md` inside `AGENTS/`** left over once `AGENTS.md`
is settled there is removed — nothing named `CLAUDE.md` survives
inside the mirror.
4. **The project-level symlink** is created or repaired if missing or
stale, and any `CLAUDE.md` still at the project level is removed. A
real file that turns up here *after* the mirror already settled (for
instance, an agent's own `/init`-style command writing a fresh
`CLAUDE.md`) is held to the same identical-or-differ rule as step 2:
a duplicate is dropped, anything different is left alone with a
warning rather than silently overwritten.
## Safety: what discovery will never touch
Because discovery walks the whole project tree rather than a fixed list,
it deliberately prunes several classes of directory before it ever
considers what's inside them:
- **Anything outside the project entirely.** A directory that has no
git repository of its own, but happens to carry a lone `AGENTS.md` or
`CLAUDE.md` (a home directory scaffolded this way, for instance), is
synced at that single location only — no recursive walk runs at all.
Recursive discovery only ever runs inside a real git repository.
- **Nested repositories.** Any subdirectory that is itself a git
repository — a submodule, a nested clone, a plugin checked out inside
a tool's own state directory — belongs to a different project and is
never walked into.
- **Dot-directories.** Anything named starting with `.` (`.git`,
`.claude`, `.gemini`, `.github`, and so on) is a tool's own state or
configuration, not a project's own scoped convention, and is skipped
unconditionally.
- **Generated output.** `build/`, `dist/`, `out/`, and `target/`
directories are never inspected — nothing generated by a build step
is a source of hand-authored instructions.
- **`node_modules/`**, and any directory literally named `AGENTS` other
than the current project's own mirror.
## Safety: deliberately tracked files are left alone
Discovery can reach a directory whose `AGENTS.md` or `CLAUDE.md` is
already committed to the project's own history on purpose — a team's
shared conventions file in a monorepo subdirectory, for instance, tracked
long before this configuration's owner ever cloned it. Replacing that
file with a symlink would change it from an ordinary tracked file into a
link pointing outside the repository the moment `agents-init` next runs,
which is not a decision this tool should make unattended on someone
else's behalf.
A real file is left untouched, instead of adopted or replaced, whenever
**both** of the following hold:
- it is tracked in git's index — staged or committed, checked with
`git ls-files`. A file that has never been `git add`ed is not tracked
by this definition, even if it sits right next to files that are.
- the project's `.gitignore` actually exists and has content in it.
Neither condition alone is enough to protect a file. An untracked file is
always safe to adopt, regardless of what `.gitignore` says about it
(nothing has been committed yet, so nothing is lost). A tracked file in a
project with *no* established ignore conventions at all — no
`.gitignore`, or an empty one — is treated as the very first time this
convention has been applied to that project, rather than a deliberate
choice to keep tracking it: `agents-init` adopts it the same way it would
adopt any other real file, which is the same behavior this tool has
always had for a project's own root file.
NOTE: In practice, this means a mature project with an established
`.gitignore` will have any already-committed `AGENTS.md`/`CLAUDE.md` left
alone across the board — root included — and will only ever adopt one
during that project's first encounter with this convention, before a
`.gitignore` entry for it exists yet.
When a directory is skipped for this reason, `agents-init` prints a
warning naming the file and explaining why, rather than staying silent
about a directory it chose not to touch.
## Scenario reference
Every combination of what a directory can hold, laid out directly. "No"
in the tracked column also covers a tracked file in a project with no
populated `.gitignore` (the bootstrap case, above) — both behave the same
way. Whenever the tracked column reads "Yes", that reason always wins
over the identical-or-different comparison below it, and the warning
printed names the file as tracked rather than as differing — the outcome
(left alone) is the same either way, only the explanation differs.
Settling a directory for the first time — a real `AGENTS.md`, a real
`CLAUDE.md`, both, or neither, discovered fresh:
Found Deliberately tracked? Result
-------------------------------- --------------------- -----------------------------------------------
Only AGENTS.md (real) No Adopted into AGENTS/, symlinked back.
Only AGENTS.md (real) Yes Left exactly as it is; not adopted.
Only CLAUDE.md (real) No Adopted, renamed to AGENTS.md, symlinked back.
Only CLAUDE.md (real) Yes Left exactly as it is; not adopted or renamed.
Both, byte-identical No AGENTS.md adopted; duplicate CLAUDE.md dropped.
Both, byte-identical Yes (either) Left exactly as they are; neither touched.
Both, different content n/a Neither touched; warns, resolve by hand.
Mirror has CLAUDE.md (real) n/a Flipped in place: renamed, nothing lost.
Correct AGENTS.md symlink exists n/a Nothing happens -- already settled.
A new real file appearing after a directory's mirror has already settled
— an agent's own `/init`-style command, for instance, writing a fresh
`CLAUDE.md` where an `AGENTS.md` is already symlinked:
New file vs. mirror Deliberately tracked? Result
------------------- --------------------- --------------------------------------------------------
Byte-identical No Adopted as a duplicate; the new file is dropped.
Byte-identical Yes Left as it is; not adopted, even though content matches.
Different content No Left as it is; warns that it differs, resolve by hand.
Different content Yes Left as it is; warns that it's tracked, not adopted.
And whatever a directory holds, it never gets this far at all if
discovery pruned it outright — see the containment rules above: nested
repositories, dot-directories, `node_modules/`, generated-output
directories, and any directory literally named `AGENTS`.
## plans/, specs/, and devlogs/
`agents-init --plugins` (the second half of what a bare `agents-init` run
does) wires up three more directories inside `AGENTS/`: `plans/` and
`specs/` for the superpowers skills' implementation plans and design
documents, and `devlogs/` for agent-authored development notes. Real
content from every legacy location this configuration has ever used for
these (`docs/plans`, `docs/superpowers/plans`, and an older
`AGENTS/plugins/` layer from before the sub-repository consolidated them)
is merged into the canonical `AGENTS/plans` and `AGENTS/specs` on first
run, and the legacy locations are removed once merged.
`docs/superpowers/plans` and `docs/superpowers/specs` are always
symlinked to their `AGENTS/` counterparts, because the superpowers skills
expect to find them there by default. `docs/plans`, `docs/specs`, and
`docs/devlogs` are only created as symlinks when a project already had a
real directory by that name — nothing forces those paths to exist for a
project that never used them.
## The launch lifecycle
The `claude` and `agy` wrapper functions each run `agents-init --quiet`
(full setup: both the `AGENTS.md` symlink step and the plans/specs/devlogs
wiring) before launching the real CLI, on every invocation. This is what
makes the whole system self-healing: a project that has drifted from the
expected layout — a stale symlink, a newly-added subdirectory's
instructions not yet adopted, a leftover `CLAUDE.md` — is corrected
automatically the next time an agent is launched there, with no separate
setup step for a person to remember.
At the end of every `agents-init` run, any uncommitted change inside
`AGENTS/` is committed automatically, so whatever an agent wrote during
its session is captured without anyone needing to run `git add` on a
repository they were never meant to think about directly. That commit is
strictly local: `agents-init` never fetches or pushes, because a network
round trip running synchronously ahead of every agent launch would block
the launch itself for as long as an unreachable remote takes to time out.
A project's `AGENTS/` repository that has its own upstream is pulled and
pushed by hand, on its owner's own schedule.
`agents-vault` is a related but distinct tool: where `AGENTS/` holds
*one project's* agent state, `agents-vault` backs up curated agent memory
that lives *outside* any project tree entirely — `~/.claude/projects/*/memory`
and similar host-scoped locations — into its own host-scoped repository.
The `claude`/`agy` wrappers sync both on every launch. See
`__fish_agent_vault_autopush` in Section 7 for its one user-facing
configuration variable; command-line usage for both tools is in Section 5.
@@ -1,8 +1,8 @@
---
title: Attribution
manTitle: 17. ATTRIBUTION
manTitle: 16. ATTRIBUTION
sidebar:
order: 21
order: 20
helpKeywords:
- attribution
- credits
@@ -1,8 +1,8 @@
---
title: License
manTitle: 18. LICENSE
manTitle: 17. LICENSE
sidebar:
order: 22
order: 21
helpKeywords:
- license
- licensing
+1 -1
View File
@@ -1,5 +1,5 @@
---
title: Rootiest Fish Configuration
title: Fish Shell Configuration
description: Reference manual for the rootiest fish configuration.
manTitle: DESCRIPTION
sidebar:
-1
View File
@@ -64,7 +64,6 @@ SECTIONS = (
"CATEGORY",
"COMPONENT",
"DEPENDENCIES",
"CLASSIFICATION",
"SYNOPSIS",
"DESCRIPTION",
"ARGUMENTS",
+2 -125
View File
@@ -202,25 +202,13 @@ def test_dependencies_resolve():
Catches typos, and catches stale entries when a dependency is renamed
or deleted. External binaries are accepted when some file in the tree
guards their availability -- `type -q name`, `command -q name`,
`command -v name`, or `which name`, this repo's four interchangeable
existence-check idioms -- either directly, or, for a multi-tool check
like dng2avif's `for cmd in magick ffmpeg avifenc exiftool; type -q
$cmd`, indirectly through a loop variable.
guards them with `type -q`, which is this repo's convention.
"""
repo = Path(__file__).parent.parent
functions = _parsed_functions()
known = {p.stem for p in (repo / "functions").glob("*.fish")} | set(functions)
# `type` takes its own flags (e.g. `-f` to exclude functions from the
# match, as in `type -q -f $p`) that can sit between `-q` and the name
# -- skip over any of those so the guard is still recognized.
guard_re = re.compile(r"(?:type -q(?:\s+-\w+)*|command -q|command -v|which)\s+([\w.\-]+)")
for path in list(repo.glob("conf.d/*.fish")) + list((repo / "functions").glob("*.fish")):
text = path.read_text(encoding="utf-8")
known |= set(guard_re.findall(text))
for var, names in re.findall(r"for\s+(\w+)\s+in\s+([^\n;]+)", text):
if re.search(rf"type -q(?:\s+-\w+)*\s+\$\{{?{re.escape(var)}\}}?\b", text):
known |= set(names.split())
known |= set(re.findall(r"type -q\s+(\S+)", path.read_text(encoding="utf-8")))
dangling = []
for name, fn in functions.items():
for dep in (d for d in re.split(r"[,\s]+", " ".join(fn.get("DEPENDENCIES", []))) if d):
@@ -1674,117 +1662,6 @@ def test_concat_section_five_stays_verbatim():
assert not offenders, f"backticks inside verbatim entries: {offenders[:3]}"
def test_manual_section_slug_extracts_tag():
"""`manual-section(<slug>)` is found among other CLASSIFICATION tags, or not at all."""
import build_manual
assert build_manual._manual_section_slug(["destructive", "manual-section(foo-bar)"]) == "foo-bar"
assert build_manual._manual_section_slug(["network"]) is None
assert build_manual._manual_section_slug([]) is None
def test_resolve_manual_section_reads_target_frontmatter():
"""Resolves both page shapes (top-level file, directory index) and reports None cleanly."""
import build_manual
with tempfile.TemporaryDirectory() as d:
root = Path(d)
(root / "solo.md").write_text(
"---\ntitle: Solo\nmanTitle: 9. SOLO\n---\nbody\n"
)
(root / "grouped").mkdir()
(root / "grouped" / "index.md").write_text(
"---\ntitle: Grouped\nmanTitle: 10. GROUPED\n---\nbody\n"
)
label, href, relpath = build_manual._resolve_manual_section(root, "solo")
assert label == "9. SOLO", label
assert href == "/solo/", href
assert relpath == "solo.md", relpath
label, href, relpath = build_manual._resolve_manual_section(root, "grouped")
assert label == "10. GROUPED", label
assert href == "/grouped/", href
assert relpath == "grouped/index.md", relpath
assert build_manual._resolve_manual_section(root, "missing") is None
assert build_manual._resolve_manual_section(None, "solo") is None
def test_render_entry_see_also_appears_only_when_root_resolves():
"""The man-page See-also line needs both the tag and a root that resolves it."""
import build_manual
fn = {
"SYNOPSIS": ["thing"],
"DESCRIPTION": ["Does a thing."],
"CLASSIFICATION": ["manual-section(deep-dive)"],
}
with tempfile.TemporaryDirectory() as d:
root = Path(d)
(root / "deep-dive.md").write_text(
"---\ntitle: Deep Dive\nmanTitle: 20. DEEP DIVE\n---\nbody\n"
)
out = build_manual.render_entry(fn, [], root=root)
assert "**See also:** 20. DEEP DIVE (`docs/manual/deep-dive.md`)" in out, out
# No root at all -- same as every other existing caller that never
# passes one -- silently omits the line rather than raising.
out_no_root = build_manual.render_entry(fn, [])
assert "See also" not in out_no_root, out_no_root
# A root that exists but doesn't have the target page: also silent.
with tempfile.TemporaryDirectory() as empty:
out_missing = build_manual.render_entry(fn, [], root=Path(empty))
assert "See also" not in out_missing, out_missing
def test_render_entry_site_see_also_is_a_real_link():
"""The site's See-also line is a markdown link to the resolved page's site path."""
import build_manual
fn = {
"SYNOPSIS": ["thing"],
"DESCRIPTION": ["Does a thing."],
"CLASSIFICATION": ["manual-section(deep-dive)"],
}
with tempfile.TemporaryDirectory() as d:
root = Path(d)
(root / "deep-dive.md").write_text(
"---\ntitle: Deep Dive\nmanTitle: 20. DEEP DIVE\n---\nbody\n"
)
out = build_manual.render_entry_site(fn, [], root=root)
assert "**See also:** [20. DEEP DIVE](/deep-dive/)" in out, out
def test_real_manual_section_tags_resolve():
"""Every manual-section(<slug>) tag on a real function points at a real page.
This is the enforcement half of the convention: build-manual.py stays
silent about a dangling slug (it just skips the See-also line), so this
is the only thing that turns a typo'd or stale slug into a failure.
"""
import build_manual
functions = mt.parse_functions(build_manual.FUNCTIONS)
checked = 0
for name, fn in functions.items():
tags = build_manual._classification_tags(fn.get("CLASSIFICATION", []))
slug = build_manual._manual_section_slug(tags)
if slug is None:
continue
checked += 1
resolved = build_manual._resolve_manual_section(build_manual.MANUAL, slug)
assert resolved is not None, (
f"{name}'s manual-section({slug}) tag doesn't resolve to "
f"docs/manual/{slug}.md or docs/manual/{slug}/index.md"
)
assert checked > 0, "expected at least one real function to carry manual-section(...)"
TESTS = [v for k, v in sorted(globals().items()) if k.startswith("test_")]
@@ -4,9 +4,6 @@
# COMPONENT
# logging/terminal-capture
#
# CLASSIFICATION
# self-limiting(rm,mkdir)
#
# SYNOPSIS
# __fish_config_sync_logging
#
+3 -7
View File
@@ -1,9 +1,6 @@
# Copyright (C) 2026 Rootiest
# SPDX-License-Identifier: AGPL-3.0-or-later
# CLASSIFICATION
# bypasses-shadow(cat)
#
# SYNOPSIS
# __fish_help_header <name> [args...]
#
@@ -48,10 +45,9 @@ function __fish_help_header --argument-names name
# First argument only -- see DESCRIPTION.
contains -- "$argv[2]" -h --help; or return 1
__fish_palette
set -l c_ttl $c_cmd
set -l c_sec $c_head
set -l c_rst $c_reset
set -l c_ttl (set_color --bold)
set -l c_sec (set_color --bold brblue)
set -l c_rst (set_color normal)
set -l miss " No documentation header found. Try: help config $name"
set -l file (functions -D -- $name 2>/dev/null)
+9 -28
View File
@@ -19,18 +19,11 @@
# TERM=dumb, which yields empty strings, and is unaffected by whether
# stdout is a tty or a pipe.)
#
# A role is a semantic slot, not a colour. c_flag and c_warn can both be
# yellow but stay separate, as do c_ok and c_accent -- merging either
# pair would foreclose ever restyling one without the other. c_accent is
# the command name in logs and smart_exit, which style it green where
# the rest of the config styles it bold.
#
# c_cmd, c_flag, c_arg, and c_dim are drawn from fish's own highlighter
# variables (fish_color_command/option/param/autosuggestion) when set,
# and c_head from the pager's fish_pager_color_prefix -- the same colour
# a real command, flag, argument, or pager heading gets at the prompt.
# Each falls back to its previous static default when the theme
# variable is unset or empty (e.g. a --no-config script).
# A role is a semantic slot, not a colour. c_flag and c_warn are both
# yellow but stay separate, as do c_ok and c_accent (both green) --
# merging either pair would foreclose ever restyling one without the
# other. c_accent is the command name in logs and smart_exit, which
# style it green where the rest of the config styles it bold.
#
# ARGUMENTS
# none
@@ -56,27 +49,15 @@
function __fish_palette --no-scope-shadowing --description 'Define the shared output colour palette in the caller scope'
set c_reset (set_color normal)
set c_cmd (set_color brblue)
set c_flag (set_color brgreen)
set c_arg (set_color cyan)
set c_dim (set_color brblack)
set c_head (set_color --bold cyan)
set c_cmd (set_color --bold)
set c_arg (set_color cyan)
set c_flag (set_color yellow)
set c_warn (set_color yellow)
set c_err (set_color red)
set c_ok (set_color green)
set c_accent (set_color green)
set c_dim (set_color brblack)
set c_sel (set_color --bold magenta)
set c_hi (set_color --bold white)
# Theme overrides: same roles fish's own highlighter/pager use, so
# --help text matches the prompt instead of a fixed guess at it.
set -l roles command option param autosuggestion
set -l vars c_cmd c_flag c_arg c_dim
for i in (seq (count $roles))
set -l themevar fish_color_$roles[$i]
set -q $themevar; and test (count $$themevar) -gt 0
and set $vars[$i] (set_color $$themevar)
end
set -q fish_pager_color_prefix; and test (count $fish_pager_color_prefix) -gt 0
and set c_head (set_color --bold $fish_pager_color_prefix)
end
-3
View File
@@ -1,9 +1,6 @@
# Copyright (C) 2026 Rootiest
# SPDX-License-Identifier: AGPL-3.0-or-later
# CLASSIFICATION
# self-limiting(grep)
#
# SYNOPSIS
# __fish_real_command <name>
#
-3
View File
@@ -4,9 +4,6 @@
# COMPONENT
# autoexec/sync
#
# CLASSIFICATION
# self-limiting(rm), destructive
#
# SYNOPSIS
# __fish_user_dots_link
#
@@ -1,9 +1,6 @@
# Copyright (C) 2026 Rootiest
# SPDX-License-Identifier: AGPL-3.0-or-later
# CLASSIFICATION
# bypasses-shadow(grep)
#
# SYNOPSIS
# __kitty_logging_has_watcher
#
@@ -1,9 +1,6 @@
# Copyright (C) 2026 Rootiest
# SPDX-License-Identifier: AGPL-3.0-or-later
# CLASSIFICATION
# self-limiting(grep)
#
# SYNOPSIS
# _agents_init_ensure_gitignore <root> <label> <pattern>...
#
@@ -1,45 +0,0 @@
# Copyright (C) 2026 Rootiest
# SPDX-License-Identifier: AGPL-3.0-or-later
# SYNOPSIS
# _agents_init_path_is_protected <root> <path>
#
# DESCRIPTION
# Decides whether a real (non-symlink) file should be left alone rather
# than adopted into the AGENTS/ mirror or replaced with a symlink,
# because it looks like a deliberately tracked project file rather than
# an incidental one this project hasn't yet engaged agents-init's
# convention for.
#
# A file is protected only when BOTH are true:
# - it is tracked in git's index at <root> -- staged or committed, via
# `git ls-files`. A file that has never been `git add`ed (even if it
# sits right next to tracked files) is not tracked by this
# definition, and neither is one that is merely gitignored.
# - <root>/.gitignore exists and is non-empty -- a project with no
# ignore rules at all has never engaged with the convention this
# tool manages, so a tracked file there is more likely incidental
# (e.g. the very first agents-init run, before anyone thought to
# ignore it) than a deliberate choice to keep tracking it.
#
# Neither check alone is enough: an untracked file is always safe
# regardless of .gitignore state (nothing has been committed to protect),
# and a tracked file in a project with no established ignore
# conventions is treated as adoptable rather than deliberate.
#
# ARGUMENTS
# root Absolute path to the project root (may or may not be a git repo)
# path Absolute path to the file being considered
#
# EXIT STATUS
# 0 Protected -- leave this file alone
# 1 Not protected -- safe to adopt/replace
#
# EXAMPLE
# _agents_init_path_is_protected /path/to/project /path/to/project/functions/CLAUDE.md
function _agents_init_path_is_protected --argument-names root path
test -n "$root" -a -n "$path"; or return 1
git -C "$root" --literal-pathspecs ls-files --error-unmatch -- "$path" >/dev/null 2>&1; or return 1
test -s "$root/.gitignore"; or return 1
return 0
end
@@ -1,251 +0,0 @@
# Copyright (C) 2026 Rootiest
# SPDX-License-Identifier: AGPL-3.0-or-later
# DEPENDENCIES
# _agents_init_path_is_protected
#
# CLASSIFICATION
# self-limiting(rm,mkdir), bypasses-shadow(mv)
#
# SYNOPSIS
# _agents_init_sync_instructions <root> <agents_dir> <rel>
#
# DESCRIPTION
# Normalizes one directory's agent instruction file(s) into the
# AGENTS.md-only shape: <root>/<rel>/AGENTS.md becomes a symlink to the
# real file at <agents_dir>/<rel>/AGENTS.md (or, for the root itself,
# <agents_dir>/AGENTS.md directly), and no CLAUDE.md survives anywhere
# for that directory -- neither at the project level nor inside the
# mirror.
#
# The exception is a real file that is deliberately git-tracked -- in
# git's index, in a project whose .gitignore is non-empty (see
# _agents_init_path_is_protected). Such a file is never adopted,
# relinked, or removed: whenever steps 2 or 4 find one, they leave that
# directory's instruction files exactly as they are and warn on stderr.
#
# Four states of <rel> are handled, in order, so later steps only ever
# see a settled mirror:
#
# 1. The mirror itself is inverted (CLAUDE.md real, AGENTS.md symlinked
# to it). Flipped in place: same bytes, new name.
# 2. The mirror has no real AGENTS.md yet, and the project directory
# has one or both files. If either real file is protected, both are
# left untouched, the mirror is not populated, and a warning naming
# the protected file(s) goes to stderr. Otherwise a lone real file
# (either name) is adopted as the mirror's AGENTS.md -- a lone
# CLAUDE.md is renamed, never preserved under its own name. Both real and byte-identical: the
# AGENTS.md side is adopted and the duplicate CLAUDE.md is dropped.
# Both real and different: neither is touched and a warning is
# printed to stderr -- this function has no way to know which side
# is authoritative, and silently keeping one would silently discard
# the other.
# 3. Any CLAUDE.md still left in the mirror once AGENTS.md is settled
# (belt-and-suspenders past step 1) is removed.
# 4. The project-level AGENTS.md symlink is (re)created if missing or
# stale, and any CLAUDE.md left at the project level is removed. A
# real project-level file found here (written after the mirror
# settled) is checked for protection first, as in 2 -- a protected
# one is left alone even if byte-identical to the mirror. An
# unprotected one is removed only if byte-identical to the mirror; if
# it differs, nothing is touched and a warning goes to stderr, as in 2.
#
# ARGUMENTS
# root Absolute path to the project root
# agents_dir Absolute path to the project's AGENTS/ sub-repo
# rel Path of the directory being synced, relative to root
# ("." for the root itself)
#
# EXIT STATUS
# 0 <rel> is settled (including the both-real-and-different and
# protected-file skips, which are not failures of this function)
# 1 A filesystem operation (mkdir/mv/rm/ln) failed
#
# RETURNS
# One "→ ..." line per change made, on stdout; nothing when <rel> was
# already settled. A skip warning goes to stderr, never stdout, so it is
# never mistaken for a change.
#
# EXAMPLE
# _agents_init_sync_instructions /path/to/project /path/to/project/AGENTS .
# _agents_init_sync_instructions /path/to/project /path/to/project/AGENTS functions
function _agents_init_sync_instructions --argument-names root agents_dir rel
test -n "$root" -a -n "$agents_dir" -a -n "$rel"; or return 1
set -l proj_dir "$root"
set -l mirror_dir "$agents_dir"
if test "$rel" != "."
set proj_dir "$root/$rel"
set mirror_dir "$agents_dir/$rel"
end
set -l proj_agents "$proj_dir/AGENTS.md"
set -l proj_claude "$proj_dir/CLAUDE.md"
set -l mirror_agents "$mirror_dir/AGENTS.md"
set -l mirror_claude "$mirror_dir/CLAUDE.md"
# Display names for progress lines: bare at the root, "<rel>/..." below it.
set -l disp_agents AGENTS.md
set -l disp_claude CLAUDE.md
set -l mirror_rel AGENTS
if test "$rel" != "."
set disp_agents "$rel/AGENTS.md"
set disp_claude "$rel/CLAUDE.md"
set mirror_rel "AGENTS/$rel"
end
mkdir -p "$mirror_dir"
or begin
echo "_agents_init_sync_instructions: could not create $mirror_dir" >&2
return 1
end
# ── 1: an inverted mirror (CLAUDE.md real, AGENTS.md symlinked to it) ──
if test -f "$mirror_claude"; and not test -L "$mirror_claude"
if test -L "$mirror_agents"
rm -f "$mirror_agents"
or begin
echo "_agents_init_sync_instructions: could not remove $mirror_agents" >&2
return 1
end
end
if not test -e "$mirror_agents"
command mv "$mirror_claude" "$mirror_agents"
or begin
echo "_agents_init_sync_instructions: could not rename $mirror_claude" >&2
return 1
end
echo "→ Renamed $mirror_rel/CLAUDE.md → AGENTS.md"
end
end
# ── 2: adopt real project-level files, only if the mirror has none yet ──
if not test -f "$mirror_agents"
set -l has_agents 0
set -l has_claude 0
test -f "$proj_agents"; and not test -L "$proj_agents"; and set has_agents 1
test -f "$proj_claude"; and not test -L "$proj_claude"; and set has_claude 1
# A deliberately git-tracked file is left alone -- and so is its
# sibling, since adopting one of a pair would still relink or drop
# the tracked one.
set -l protected
test $has_agents -eq 1; and _agents_init_path_is_protected "$root" "$proj_agents"; and set -a protected $disp_agents
test $has_claude -eq 1; and _agents_init_path_is_protected "$root" "$proj_claude"; and set -a protected $disp_claude
if set -q protected[1]
echo "_agents_init_sync_instructions: "(string join ', ' -- $protected)" tracked by git; leaving this directory's instruction files untouched" >&2
return 0
end
if test $has_agents -eq 1; and test $has_claude -eq 1
if command diff -q "$proj_agents" "$proj_claude" >/dev/null 2>&1
command mv "$proj_agents" "$mirror_agents"
or begin
echo "_agents_init_sync_instructions: could not move $proj_agents" >&2
return 1
end
rm -f "$proj_claude"
or begin
echo "_agents_init_sync_instructions: could not remove $proj_claude" >&2
return 1
end
echo "→ Moved $disp_agents → $mirror_rel/AGENTS.md (dropped identical CLAUDE.md)"
else
echo "_agents_init_sync_instructions: $proj_agents and $proj_claude differ; leaving both, resolve by hand" >&2
return 0
end
else if test $has_agents -eq 1
command mv "$proj_agents" "$mirror_agents"
or begin
echo "_agents_init_sync_instructions: could not move $proj_agents" >&2
return 1
end
echo "→ Moved $disp_agents → $mirror_rel/AGENTS.md"
else if test $has_claude -eq 1
command mv "$proj_claude" "$mirror_agents"
or begin
echo "_agents_init_sync_instructions: could not move $proj_claude" >&2
return 1
end
echo "→ Moved $disp_claude → $mirror_rel/AGENTS.md"
else if test "$rel" = "."
printf '%s\n' \
'# AGENTS.md' \
'' \
'> ⚠️ **SYSTEM DIRECTIVE FOR AI AGENTS: FILE EDITING**' \
'> You may be reading this file via a symlink (`AGENTS.md`) in' \
'> the root of the project. Your environment'\''s file-editing tools cannot write' \
'> through symlinks and will throw an error.' \
'>' \
'> **DO NOT** attempt to write to or edit `AGENTS.md` in the' \
'> project root. If you need to update these instructions, you **MUST write' \
'> directly to `AGENTS/AGENTS.md`**.' >"$mirror_agents"
echo "→ Created AGENTS/AGENTS.md with agent directive"
end
end
# ── 3: the mirror never carries a CLAUDE.md once AGENTS.md is settled ──
if test -f "$mirror_agents"; and test -e "$mirror_claude" -o -L "$mirror_claude"
rm -f "$mirror_claude"
or begin
echo "_agents_init_sync_instructions: could not remove $mirror_claude" >&2
return 1
end
echo "→ Removed $mirror_rel/CLAUDE.md"
end
# Nothing more to do for a conflicted or still-empty directory.
test -f "$mirror_agents"; or return 0
# ── 4: ensure the project-level AGENTS.md symlink, drop project CLAUDE.md ──
set -l target "AGENTS/AGENTS.md"
if test "$rel" != "."
set -l up (string repeat -n (count (string split / -- $rel)) "../")
set target "$up""AGENTS/$rel/AGENTS.md"
end
# A real (non-symlink) file here arrived after the mirror settled. Same
# rules as step 2. A deliberately git-tracked one is left alone first,
# even if byte-identical: turning a tracked regular file into a symlink
# is itself a change to it. Otherwise, byte-identical to the mirror is a
# duplicate and is replaced below; different means touch nothing and warn.
set -l protected
test -f "$proj_agents"; and not test -L "$proj_agents"; and _agents_init_path_is_protected "$root" "$proj_agents"; and set -a protected $disp_agents
test -f "$proj_claude"; and not test -L "$proj_claude"; and _agents_init_path_is_protected "$root" "$proj_claude"; and set -a protected $disp_claude
if set -q protected[1]
echo "_agents_init_sync_instructions: "(string join ', ' -- $protected)" tracked by git; leaving this directory's instruction files untouched" >&2
return 0
end
for f in $proj_agents $proj_claude
if test -f "$f"; and not test -L "$f"
if not command diff -q "$f" "$mirror_agents" >/dev/null 2>&1
echo "_agents_init_sync_instructions: $f and $mirror_agents differ; leaving both, resolve by hand" >&2
return 0
end
end
end
set -l need_link 1
if test -L "$proj_agents"
test (readlink "$proj_agents") = "$target"; and set need_link 0
end
if test $need_link -eq 1
rm -f "$proj_agents"
ln -s "$target" "$proj_agents"
or begin
echo "_agents_init_sync_instructions: could not link $proj_agents" >&2
return 1
end
echo "→ Linked $disp_agents → $target"
end
if test -e "$proj_claude" -o -L "$proj_claude"
rm -f "$proj_claude"
or begin
echo "_agents_init_sync_instructions: could not remove $proj_claude" >&2
return 1
end
echo "→ Removed $disp_claude"
end
return 0
end
+2 -5
View File
@@ -1,9 +1,6 @@
# Copyright (C) 2026 Rootiest
# SPDX-License-Identifier: AGPL-3.0-or-later
# CLASSIFICATION
# self-limiting(rm,mkdir)
#
# SYNOPSIS
# _agents_repo_ensure_symlink <link> <target>
#
@@ -20,7 +17,7 @@
# having no backup at all.
#
# When <link> is an existing real directory, its contents are copied into
# <target> without clobbering (cp --update=none) before the directory is replaced by
# <target> without clobbering (cp -n) before the directory is replaced by
# the link, so adopting a populated live directory never overwrites the
# copy already in the vault.
#
@@ -60,7 +57,7 @@ function _agents_repo_ensure_symlink --argument-names link target
else if test -d "$link"
set -l contents (command ls -A "$link" 2>/dev/null)
if test (count $contents) -gt 0
command cp -r --update=none "$link/." "$target/"; or return 1
command cp -rn "$link/." "$target/"; or return 1
end
rm -rf "$link"; or return 1
else if test -e "$link"
@@ -1,9 +1,6 @@
# Copyright (C) 2026 Rootiest
# SPDX-License-Identifier: AGPL-3.0-or-later
# CLASSIFICATION
# uses-shadow(mkdir), bypasses-shadow(cp,grep)
#
# SYNOPSIS
# _agents_repo_install_tools <repo_dir>
#
-28
View File
@@ -1,28 +0,0 @@
# Copyright (C) 2026 Rootiest
# SPDX-License-Identifier: AGPL-3.0-or-later
# SYNOPSIS
# _fish_clipboard_copy
#
# DESCRIPTION
# Copies stdin to the system clipboard. Tries wl-copy (Wayland), then
# xclip (X11), then win32yank.exe (WSL2).
#
# EXIT STATUS
# 0 Text copied to clipboard
# 1 No clipboard provider found
#
# EXAMPLE
# echo "hello" | _fish_clipboard_copy
function _fish_clipboard_copy --description 'Copy stdin to the system clipboard'
if type -q wl-copy
wl-copy
else if type -q xclip
xclip -selection clipboard
else if type -q win32yank.exe
win32yank.exe -i --crlf
else
echo "Error: No clipboard provider (wl-copy, xclip, or win32yank) found." >&2
return 1
end
end
-31
View File
@@ -1,31 +0,0 @@
# Copyright (C) 2026 Rootiest
# SPDX-License-Identifier: AGPL-3.0-or-later
# SYNOPSIS
# _fish_clipboard_paste [args...]
#
# DESCRIPTION
# Prints the system clipboard contents to stdout. Tries wl-paste
# (Wayland), then xclip (X11), then win32yank.exe (WSL2).
#
# ARGUMENTS
# args... Arguments forwarded to the clipboard tool
#
# EXIT STATUS
# 0 Clipboard contents read successfully
# 1 No clipboard provider found
#
# EXAMPLE
# _fish_clipboard_paste
function _fish_clipboard_paste --description 'Print the system clipboard contents'
if type -q wl-paste
wl-paste $argv
else if type -q xclip
xclip -selection clipboard -o $argv
else if type -q win32yank.exe
win32yank.exe -o --lf $argv
else
echo "Error: No clipboard provider (wl-paste, xclip, or win32yank) found." >&2
return 1
end
end
+5 -5
View File
@@ -36,27 +36,27 @@ function _fish_deps_catalog
set -g _fdc_bins \
uv cargo fish starship fzf zoxide direnv paru yay \
wakatime tailscale \
eza lsd bat btop dust duf prettyping go ov rg lazygit lazydocker docker trash kitty wezterm python3 yt-dlp screen mpv vlc marktext firejail win32yank.exe
eza lsd bat btop dust duf prettyping go ov rg lazygit lazydocker docker trash kitty wezterm python3 yt-dlp screen mpv vlc marktext firejail
set -g _fdc_tiers \
rec rec req rec req rec rec rec rec \
int int \
rec rec rec opt opt opt opt opt rec rec opt opt opt rec term term rec opt opt opt opt opt opt opt
rec rec rec opt opt opt opt opt rec rec opt opt opt rec term term rec opt opt opt opt opt opt
set -g _fdc_cargo \
"" "" "" starship "" zoxide "" "" "" \
"" "" \
eza lsd bat "" du-dust "" "" "" "" ripgrep "" "" "" trashy "" "" "" "" "" "" "" "" "" ""
eza lsd bat "" du-dust "" "" "" "" ripgrep "" "" "" trashy "" "" "" "" "" "" "" "" ""
set -g _fdc_pm \
uv cargo fish starship fzf zoxide direnv "" yay \
wakatime tailscale \
eza lsd bat btop dust duf prettyping go ov ripgrep lazygit lazydocker docker trash kitty wezterm python yt-dlp screen mpv vlc "" firejail ""
eza lsd bat btop dust duf prettyping go ov ripgrep lazygit lazydocker docker trash kitty wezterm python yt-dlp screen mpv vlc "" firejail
set -g _fdc_special \
curl-uv rustup-installer git-cargo-fish curl-installer fzf-update "" "" paru-build yay-build \
wakatime-binary "" \
"" "" "" "" "" "" "" "" go-ov "" "" curl-lazydocker "" "" "" "" "" "" "" "" "" marktext-release "" win32yank-release
"" "" "" "" "" "" "" "" go-ov "" "" curl-lazydocker "" "" "" "" "" "" "" "" "" marktext-release ""
end
# SYNOPSIS
+2 -34
View File
@@ -1,9 +1,6 @@
# Copyright (C) 2026 Rootiest
# SPDX-License-Identifier: AGPL-3.0-or-later
# CLASSIFICATION
# self-limiting(rm,mkdir,cat), bypasses-shadow(cp,bash), destructive, network, blocking-prompt
#
# SYNOPSIS
# _fish_deps_install
#
@@ -49,14 +46,6 @@ function _fish_deps_install
set -l i 1
for bin in $_fdc_bins
# win32yank only matters under WSL2 — skip the entry entirely
# elsewhere so a plain Linux box never sees it, not even as a
# "no install method available" note.
if test "$bin" = win32yank.exe; and not string match -qi '*microsoft*' (cat /proc/sys/kernel/osrelease 2>/dev/null)
set i (math $i + 1)
continue
end
# Optional-tier deps are opt-in: skip unless --optional/--all was passed.
if test "$_fdc_tiers[$i]" = opt; and test $include_optional -eq 0
if not command -q $bin
@@ -151,15 +140,6 @@ function _fish_deps_install
end
set -a methods special-marktext-appimage
set -a method_labels "AppImage download (~/.local/bin/marktext)"
case win32yank-release
if test (uname -m) = x86_64
set -a methods special-win32yank
set -a method_labels "binary download (github releases)"
else
set_color brblack
echo " note: win32yank only ships x86_64 builds (this is "(uname -m)")"
set_color normal
end
case go-ov
if type -q go
set -a methods special-go-ov
@@ -284,7 +264,7 @@ function _fish_deps_install
end
test $_go_status -eq 0
case special-lazydocker
curl https://raw.githubusercontent.com/jesseduffield/lazydocker/master/scripts/install_update_linux.sh | command bash
curl https://raw.githubusercontent.com/jesseduffield/lazydocker/master/scripts/install_update_linux.sh | bash
case special-marktext-paru
paru -S --noconfirm marktext-bin
case special-marktext-yay
@@ -312,22 +292,10 @@ function _fish_deps_install
-o "$_tmpdir/$_zip"
and unzip -o "$_tmpdir/$_zip" -d "$_tmpdir"
and mkdir -p "$_wt_dir" "$HOME/.local/bin"
and command cp "$_tmpdir/$_bin_src" "$_wt_bin"
and cp "$_tmpdir/$_bin_src" "$_wt_bin"
and chmod +x "$_wt_bin"
and ln -sf "$_wt_bin" "$HOME/.local/bin/wakatime"
rm -rf "$_tmpdir"
case special-win32yank
set -l _zip win32yank-x64.zip
set -l _tmpdir (mktemp -d)
mkdir -p "$HOME/.local/bin"
and curl -fL "https://github.com/equalsraf/win32yank/releases/latest/download/$_zip" \
-o "$_tmpdir/$_zip"
and unzip -o "$_tmpdir/$_zip" -d "$_tmpdir"
and command cp "$_tmpdir/win32yank.exe" "$HOME/.local/bin/win32yank.exe"
and chmod +x "$HOME/.local/bin/win32yank.exe"
set -l _dl_status $status
rm -rf "$_tmpdir"
test $_dl_status -eq 0
case special-fzf
fzf-update
case special-curl
+1 -4
View File
@@ -1,9 +1,6 @@
# Copyright (C) 2026 Rootiest
# SPDX-License-Identifier: AGPL-3.0-or-later
# CLASSIFICATION
# self-limiting(rm,mkdir), bypasses-shadow(mv), destructive, network
#
# SYNOPSIS
# _fish_deps_marktext_appimage
#
@@ -57,7 +54,7 @@ function _fish_deps_marktext_appimage
and chmod +x "$tmp/marktext"
# Replace via mv, not a write into $dest: overwriting a running AppImage
# in place corrupts the live mount.
and command mv -f "$tmp/marktext" "$dest"
and mv -f "$tmp/marktext" "$dest"
and set ok 1
rm -rf $tmp
+2 -24
View File
@@ -1,9 +1,6 @@
# Copyright (C) 2026 Rootiest
# SPDX-License-Identifier: AGPL-3.0-or-later
# CLASSIFICATION
# self-limiting(rm), bypasses-shadow(cp,bash), destructive, network
#
# SYNOPSIS
# _fish_deps_update
#
@@ -93,7 +90,7 @@ function _fish_deps_update
# lazydocker: re-run the official install/update script
if test "$special" = curl-lazydocker
echo "Updating $bin..."
curl https://raw.githubusercontent.com/jesseduffield/lazydocker/master/scripts/install_update_linux.sh | command bash
curl https://raw.githubusercontent.com/jesseduffield/lazydocker/master/scripts/install_update_linux.sh | bash
set updated_any 1
set i (math $i + 1)
continue
@@ -141,7 +138,7 @@ function _fish_deps_update
curl -L "https://github.com/wakatime/wakatime-cli/releases/latest/download/$_zip" \
-o "$_tmpdir/$_zip"
and unzip -o "$_tmpdir/$_zip" -d "$_tmpdir"
and command cp "$_tmpdir/$_bin_src" "$_wt_bin"
and cp "$_tmpdir/$_bin_src" "$_wt_bin"
and chmod +x "$_wt_bin"
rm -rf "$_tmpdir"
and set updated_any 1
@@ -149,25 +146,6 @@ function _fish_deps_update
continue
end
# win32yank: re-download the binary from github releases (WSL2 only;
# only reached if a copy is already on PATH, so no WSL check needed)
if test "$special" = win32yank-release
echo "Updating $bin..."
set -l _zip win32yank-x64.zip
set -l _tmpdir (mktemp -d)
curl -fL "https://github.com/equalsraf/win32yank/releases/latest/download/$_zip" \
-o "$_tmpdir/$_zip"
and unzip -o "$_tmpdir/$_zip" -d "$_tmpdir"
and command cp "$_tmpdir/win32yank.exe" "$HOME/.local/bin/win32yank.exe"
and chmod +x "$HOME/.local/bin/win32yank.exe"
set -l _up_status $status
rm -rf "$_tmpdir"
test $_up_status -eq 0
and set updated_any 1
set i (math $i + 1)
continue
end
# pipx tools
if test "$special" = pipx
if type -q pipx
-3
View File
@@ -1,9 +1,6 @@
# Copyright (C) 2026 Rootiest
# SPDX-License-Identifier: AGPL-3.0-or-later
# CLASSIFICATION
# uses-shadow(mkdir)
#
# SYNOPSIS
# _fish_mkdir_p [--path|--tree|--silent] <dir>
#
-3
View File
@@ -1,9 +1,6 @@
# Copyright (C) 2026 Rootiest
# SPDX-License-Identifier: AGPL-3.0-or-later
# CLASSIFICATION
# network
#
# SYNOPSIS
# _mkrep_default_remote_cmd <type>
#
-3
View File
@@ -4,9 +4,6 @@
# DEPENDENCIES
# gh, glab, tea
#
# CLASSIFICATION
# network
#
# SYNOPSIS
# _mkrep_repo_exists <type> <user> <name>
#
-3
View File
@@ -1,9 +1,6 @@
# Copyright (C) 2026 Rootiest
# SPDX-License-Identifier: AGPL-3.0-or-later
# CLASSIFICATION
# bypasses-shadow(ls,rm), destructive
#
# SYNOPSIS
# _prune_terminal_logs <prefix>
#
+1 -9
View File
@@ -7,9 +7,7 @@
# DESCRIPTION
# Removes the first command token from the commandline buffer and
# positions the cursor for immediate replacement. If the command starts
# with sudo, preserves sudo and removes the token after it instead. If
# the commandline is empty, first recalls the most recent history entry
# (like pressing Up) before doing the token replacement on it.
# with sudo, preserves sudo and removes the token after it instead.
# Intended to be bound to a key in key_bindings.fish.
#
# EXAMPLE
@@ -17,12 +15,6 @@
function _replace_command_token --description 'Remove first command token (or first after sudo) and place cursor for replacement'
set -l cmd (commandline)
# Empty prompt: recall the last history entry first, same as it would
# behave if that command were already on the commandline.
if string match -rq '^\s*$' -- "$cmd"
set cmd (builtin history --max 1)
end
# 1. Logic for commands starting with sudo
if string match -rq '^sudo\s+' -- "$cmd"
# regex explanation:
+3 -6
View File
@@ -1,9 +1,6 @@
# Copyright (C) 2026 Rootiest
# SPDX-License-Identifier: AGPL-3.0-or-later
# CLASSIFICATION
# bypasses-shadow(cat,rm), self-limiting(grep), destructive
#
# SYNOPSIS
# _scrollback_prune_junk [dir]
#
@@ -29,7 +26,7 @@ function _scrollback_prune_junk --description 'Remove empty, trivial, and Kitty
# Remove any completely empty log file regardless of source
for f in $dir/*.log $dir/*.txt
test -f $f || continue
not test -s $f; and command rm -f $f
not test -s $f; and rm $f
end
# Remove any log with only a single meaningful line (e.g. [exited], a lone prompt, or a trivial error)
@@ -37,7 +34,7 @@ function _scrollback_prune_junk --description 'Remove empty, trivial, and Kitty
test -f $f || continue
set -l line_count (command cat $f | sed 's/\x1b\[[0-9;:]*[a-zA-Z]//g' | grep -cv '^\s*$')
if test $line_count -le 1
command rm -f $f
rm $f
end
end
@@ -45,7 +42,7 @@ function _scrollback_prune_junk --description 'Remove empty, trivial, and Kitty
for f in $dir/scrollback_*.log $dir/scrollback_*.txt
test -f $f || continue
if command cat $f | sed 's/\x1b\[[0-9;:]*[a-zA-Z]//g' | grep -q 'Enter the new title for this tab below'
command rm -f $f
rm $f
end
end
end
-3
View File
@@ -1,9 +1,6 @@
# Copyright (C) 2026 Rootiest
# SPDX-License-Identifier: AGPL-3.0-or-later
# CLASSIFICATION
# uses-shadow(mkdir)
#
# SYNOPSIS
# _tmux_pipe_log
#
-3
View File
@@ -4,9 +4,6 @@
# COMPONENT
# logging/multiplexer-capture
#
# CLASSIFICATION
# uses-shadow(mkdir), bypasses-shadow(rm)
#
# SYNOPSIS
# _zellij_dump_log
#
+98 -109
View File
@@ -5,10 +5,7 @@
# 12-ai-and-developer-tools
#
# DEPENDENCIES
# _agents_init_sync_instructions, _agents_repo_install_tools, _agents_repo_sync, _agents_init_ensure_gitignore
#
# CLASSIFICATION
# self-limiting(rm,mkdir,grep), bypasses-shadow(mv), manual-section(16-agent-tooling)
# _agents_repo_install_tools, _agents_repo_sync, _agents_init_ensure_gitignore
#
# SYNOPSIS
# agents-init [-a | --agents] [-p | --plugins] [-v | --verbose]
@@ -17,18 +14,8 @@
# DESCRIPTION
# Scaffolds an AGENTS/ sub-repository inside a project directory. Creates
# a self-contained git repo for agent specifications, moves any existing
# agent-related files into it, and replaces them with symlinks so the
# outer project never tracks agent files directly. This applies at the
# project root and, automatically, to any subdirectory that carries its
# own scoped AGENTS.md or CLAUDE.md -- discovered by scanning the tree,
# not a hardcoded list. The scan prunes dot-directories (.git/, .claude/,
# ...), nested repos, AGENTS/ itself, node_modules/, and generated-output
# directories (build/, dist/, out/, target/).
#
# A real instruction file that the project deliberately tracks -- in
# git's index, in a project whose .gitignore is non-empty -- is left
# exactly where it is, with a warning, rather than moved into AGENTS/ and
# replaced by a symlink. See _agents_init_path_is_protected.
# agent-related files into it, and replaces them with symlinks so the outer
# project never tracks agent files directly.
#
# Scaffolding runs only inside a git repository, or in a directory that
# already has an AGENTS.md, CLAUDE.md, or AGENTS/. Elsewhere it is a
@@ -36,12 +23,11 @@
# create a repository there.
#
# File layout after setup:
# AGENTS/AGENTS.md canonical root agent spec (real file)
# AGENTS/<subdir>/AGENTS.md canonical spec for any subdir with its own
# scoped instructions (real file, discovered
# automatically -- see above)
# AGENTS/AGENTS.md canonical agent spec (real file)
# AGENTS/CLAUDE.md real file (if CLAUDE.md existed separately)
# or symlink → AGENTS.md (single-source case)
# <root>/AGENTS.md → AGENTS/AGENTS.md
# <root>/<subdir>/AGENTS.md → AGENTS/<subdir>/AGENTS.md
# <root>/CLAUDE.md → AGENTS/CLAUDE.md
# AGENTS/plans superpowers plans (real dir, .gitkeep)
# AGENTS/specs superpowers specs (real dir, .gitkeep)
# AGENTS/devlogs agent development logs (real dir, .gitkeep)
@@ -53,12 +39,6 @@
# docs/specs → ../AGENTS/specs (only if docs/specs existed)
# docs/devlogs → ../AGENTS/devlogs (only if docs/devlogs existed)
#
# No CLAUDE.md survives anywhere in a managed tree: claude-code reads
# AGENTS.md natively when CLAUDE.md is absent, so CLAUDE.md exists here
# purely as a retirement target -- any found (root or subdirectory, real
# file or leftover symlink) is folded into the AGENTS.md-only shape
# above by _agents_init_sync_instructions.
#
# plans/ and specs/ are merged from every legacy location (docs/<tgt>,
# docs/superpowers/<tgt>, and the old AGENTS/plugins/ layout) into the
# canonical AGENTS/<tgt>; the AGENTS/plugins/ layer is removed.
@@ -92,8 +72,7 @@
# Called automatically by the claude and agy wrappers on every invocation.
#
# ARGUMENTS
# -a, --agents Set up AGENTS/ repo + AGENTS.md symlinks (root and every
# discovered subdirectory) only
# -a, --agents Set up AGENTS/ repo + AGENTS.md / CLAUDE.md symlinks only
# -p, --plugins Set up AGENTS/ repo + plans/specs/devlogs dirs + docs/ symlinks only
# -v, --verbose Print all per-step output (default)
# -q, --quiet Print one summary line only if changes were made
@@ -110,15 +89,6 @@
# agents-init --agents
# agents-init --plugins
# agents-init --quiet
#
# NOTES
# This header covers usage only. The full concept/behavior/purpose
# write-up -- the AGENTS.md convention, the AGENTS/ sub-repository, the
# discovery and safety model, and a complete scenario-by-scenario
# reference table -- lives in its own manual section:
# docs/manual/16-agent-tooling.md. Update that section in the same
# change whenever this function's behavior changes; see "Dedicated
# manual sections for complex subsystems" in CONTRIBUTING.md.
function agents-init --description 'scaffold AGENTS/ sub-repo with agent spec files and plugin dirs'
__fish_palette
@@ -132,7 +102,7 @@ function agents-init --description 'scaffold AGENTS/ sub-repo with agent spec fi
echo
echo "$c_head""Options:$c_reset"
echo " $c_flag-h$c_reset, $c_flag--help$c_reset Show this help message"
echo " $c_flag-a$c_reset, $c_flag--agents$c_reset Set up AGENTS.md symlinks only"
echo " $c_flag-a$c_reset, $c_flag--agents$c_reset Set up AGENTS.md / CLAUDE.md symlinks only"
echo " $c_flag-p$c_reset, $c_flag--plugins$c_reset Set up plans/specs/devlogs dirs and docs/ symlinks only"
echo " $c_flag-v$c_reset, $c_flag--verbose$c_reset Print all per-step output (default)"
echo " $c_flag-q$c_reset, $c_flag--quiet$c_reset Print one summary line only if changes were made"
@@ -167,9 +137,7 @@ function agents-init --description 'scaffold AGENTS/ sub-repo with agent spec fi
# directory created an AGENTS/ repo, two root symlinks, and a docs/
# tree there.
set -l root (git rev-parse --show-toplevel 2>/dev/null)
set -l in_git 1
if test -z "$root"
set in_git 0
if test -e (pwd)/AGENTS.md -o -e (pwd)/CLAUDE.md -o -d (pwd)/AGENTS
set root (pwd)
else
@@ -230,84 +198,109 @@ function agents-init --description 'scaffold AGENTS/ sub-repo with agent spec fi
# ──────────────────────────── --agents mode ──────────────────────────────
if test $do_agents -eq 1
# Discover every directory carrying agent instructions -- root
# included, subdirectories found automatically rather than by a
# hardcoded list. A real file, an already-migrated symlink, or a
# leftover inverted-mirror survivor all match, so one pass covers
# fresh, migrated, and legacy state alike.
#
# Discovery stays inside this project: a non-git root (a lone
# agent file in, say, ~) syncs only itself -- walking it would
# reach into every unrelated tree below. In a git root, pruned:
# any AGENTS/ (a mirror, never a source), dot-directories (.git,
# .claude, .github: tool state, not scoped project dirs),
# node_modules, generated-output directories (build, dist, out,
# target: an instruction file there is a build artifact, never a
# source -- pruned outright, before tracked-file protection would
# even be consulted), and nested repos/submodules/worktrees (their
# own .git marks another project). -mindepth 1 keeps the root
# itself, which has a .git, from pruning the whole walk.
set -l found
if test $in_git -eq 1
set found (find "$root" -mindepth 1 \
-type d \( -name '.*' -o -name AGENTS -o -name node_modules \
-o -name build -o -name dist -o -name out -o -name target \
-o -exec test -e '{}/.git' \; \) -prune -o \
\( -name AGENTS.md -o -name CLAUDE.md \) -print)
# Detect which root-level files are real (not symlinks)
set -l has_agents 0
set -l has_claude 0
if test -f "$root/AGENTS.md"; and not test -L "$root/AGENTS.md"
set has_agents 1
end
set -l rels "."
for f in $found
set -l d (path dirname "$f")
set -l rel (string replace "$root/" "" "$d")
test "$rel" = "$d"; and set rel "."
contains -- "$rel" $rels; or set -a rels "$rel"
if test -f "$root/CLAUDE.md"; and not test -L "$root/CLAUDE.md"
set has_claude 1
end
for rel in $rels
set -l out (_agents_init_sync_instructions "$root" "$agents_dir" "$rel")
set -l rc $status
if test $rc -ne 0
echo "$c_err""Error: could not sync AGENTS.md for $rel$c_reset" >&2
# ── Move real files into AGENTS/ ──────────────────────────────────────
if test $has_agents -eq 1; and test $has_claude -eq 1
# Both exist: preserve each as its own file in AGENTS/
if not test -f "$agents_dir/AGENTS.md"
if not mv "$root/AGENTS.md" "$agents_dir/AGENTS.md"
echo "$c_err""Error: could not move AGENTS.md → AGENTS/AGENTS.md$c_reset" >&2
return 1
end
if test -n "$out"
set changed 1
if test $verbose -eq 1
for line in $out
echo "$c_ok$line$c_reset"
test $verbose -eq 1; and echo "$c_ok→ Moved AGENTS.md → AGENTS/AGENTS.md$c_reset"
end
if not test -f "$agents_dir/CLAUDE.md"; and not test -L "$agents_dir/CLAUDE.md"
if not mv "$root/CLAUDE.md" "$agents_dir/CLAUDE.md"
echo "$c_err""Error: could not move CLAUDE.md → AGENTS/CLAUDE.md$c_reset" >&2
return 1
end
set changed 1
test $verbose -eq 1; and echo "$c_ok→ Moved CLAUDE.md → AGENTS/CLAUDE.md$c_reset"
end
else if test $has_agents -eq 1
if not test -f "$agents_dir/AGENTS.md"
if not mv "$root/AGENTS.md" "$agents_dir/AGENTS.md"
echo "$c_err""Error: could not move AGENTS.md → AGENTS/AGENTS.md$c_reset" >&2
return 1
end
set changed 1
test $verbose -eq 1; and echo "$c_ok→ Moved AGENTS.md → AGENTS/AGENTS.md$c_reset"
end
else if test $has_claude -eq 1
# Only CLAUDE.md: treat it as the agent spec
if not test -f "$agents_dir/AGENTS.md"
if not mv "$root/CLAUDE.md" "$agents_dir/AGENTS.md"
echo "$c_err""Error: could not move CLAUDE.md → AGENTS/AGENTS.md$c_reset" >&2
return 1
end
set changed 1
test $verbose -eq 1; and echo "$c_ok→ Moved CLAUDE.md → AGENTS/AGENTS.md$c_reset"
end
else
# Neither exists: create AGENTS/AGENTS.md with the agent directive
if not test -f "$agents_dir/AGENTS.md"
printf '%s\n' \
'# AGENTS.md' \
'' \
'> ⚠️ **SYSTEM DIRECTIVE FOR AI AGENTS: FILE EDITING**' \
'> You may be reading this file via a symlink (`CLAUDE.md` or `AGENTS.md`) in' \
'> the root of the project. Your environment'\''s file-editing tools cannot write' \
'> through symlinks and will throw an error.' \
'>' \
'> **DO NOT** attempt to write to or edit `CLAUDE.md` or `AGENTS.md` in the' \
'> project root. If you need to update these instructions, you **MUST write' \
'> directly to `AGENTS/AGENTS.md`**.' >"$agents_dir/AGENTS.md"
set changed 1
test $verbose -eq 1; and echo "$c_ok→ Created AGENTS/AGENTS.md with agent directive$c_reset"
end
end
# ── Migrate stale anchored gitignore lines ──────────────────────────────
# A project scaffolded by the old agents-init already has anchored
# /AGENTS.md and/or /CLAUDE.md lines in .gitignore. git check-ignore
# sees those as covering the literal path "AGENTS.md", so the new
# unanchored pattern below would be judged already-covered and never
# added -- leaving any newly discovered subdirectory AGENTS.md with no
# gitignore coverage at all. Strip the stale exact lines first so the
# unanchored pattern always gets a chance to be added. No-op when
# neither stale line is present.
set -l gitignore "$root/.gitignore"
if test -f "$gitignore"
if grep -qxF "/AGENTS.md" "$gitignore"
sed -i '/^\/AGENTS\.md$/d' "$gitignore"
set changed 1
test $verbose -eq 1; and echo "$c_warn→ Removed stale /AGENTS.md line from .gitignore$c_reset"
# ── Ensure AGENTS/CLAUDE.md exists ────────────────────────────────────
# When both files existed, AGENTS/CLAUDE.md is already a real file.
# Otherwise, create it as a symlink → AGENTS.md (within AGENTS/).
if not test -f "$agents_dir/CLAUDE.md"; and not test -L "$agents_dir/CLAUDE.md"
if not ln -s AGENTS.md "$agents_dir/CLAUDE.md"
echo "$c_err""Error: could not create AGENTS/CLAUDE.md symlink$c_reset" >&2
return 1
end
if grep -qxF "/CLAUDE.md" "$gitignore"
sed -i '/^\/CLAUDE\.md$/d' "$gitignore"
set changed 1
test $verbose -eq 1; and echo "$c_warn→ Removed stale /CLAUDE.md line from .gitignore$c_reset"
test $verbose -eq 1; and echo "$c_ok→ Linked AGENTS/CLAUDE.md → AGENTS/AGENTS.md$c_reset"
end
# Root symlinks point at files, not directories, so they cannot use
# _agents_repo_ensure_symlink (which is directory-only by design).
for pair in "AGENTS.md:AGENTS/AGENTS.md" "CLAUDE.md:AGENTS/CLAUDE.md"
set -l name (string split -f1 ':' -- $pair)
set -l want (string split -f2 ':' -- $pair)
set -l need 0
if not test -L "$root/$name"
set need 1
else if test (readlink "$root/$name") != "$want"
rm -f "$root/$name"
set need 1
end
if test $need -eq 1
if not ln -s "$want" "$root/$name"
echo "$c_err""Error: could not create $name symlink$c_reset" >&2
return 1
end
set changed 1
test $verbose -eq 1; and echo "$c_ok→ Linked $name → $want$c_reset"
end
end
# ── .gitignore ────────────────────────────────────────────────────────
# Unanchored: matches AGENTS.md at every depth, so a newly
# discovered subdirectory needs no additional gitignore entry.
# CLAUDE.md is dropped entirely -- nothing creates one anymore.
set -l _gi (_agents_init_ensure_gitignore "$root" "agents-init --agents" "AGENTS/" "AGENTS.md")
set -l _gi (_agents_init_ensure_gitignore "$root" "agents-init --agents" "AGENTS/" "/AGENTS.md" "/CLAUDE.md")
if test -n "$_gi"
set changed 1
test $verbose -eq 1; and echo $_gi
@@ -358,7 +351,7 @@ function agents-init --description 'scaffold AGENTS/ sub-repo with agent spec fi
set -l rel (string replace "$root/" "" "$src")
set -l contents (command ls -A "$src" 2>/dev/null)
if test (count $contents) -gt 0
if not command cp -r --update=none "$src/." "$canonical/"
if not command cp -rn "$src/." "$canonical/"
echo "$c_err""Error: could not merge $rel → AGENTS/$tgt$c_reset" >&2
return 1
end
@@ -441,7 +434,7 @@ function agents-init --description 'scaffold AGENTS/ sub-repo with agent spec fi
test -d "$devlogs_dir"; or mkdir -p "$devlogs_dir"
set -l contents (command ls -A "$docs_devlogs" 2>/dev/null)
if test (count $contents) -gt 0
if not command cp -r --update=none "$docs_devlogs/." "$devlogs_dir/"
if not command cp -rn "$docs_devlogs/." "$devlogs_dir/"
echo "$c_err""Error: could not copy docs/devlogs → AGENTS/devlogs$c_reset" >&2
return 1
end
@@ -486,11 +479,7 @@ function agents-init --description 'scaffold AGENTS/ sub-repo with agent spec fi
# a hook-rejected commit fell straight through to a reported success.
set -l msg "chore: sync AGENTS repository"
test $did_init -eq 1; and set msg "chore: initialize AGENTS repository"
# 2>/dev/null: a command substitution's stderr does not inherit a
# caller-scoped redirect on this call (fish quirk), so _agents_repo_sync's
# own error message leaks past --silent regardless; it is redundant with
# the $sync_rc-driven echoes just below anyway.
set -l sync_out (_agents_repo_sync "$agents_dir" "$msg" 2>/dev/null)
set -l sync_out (_agents_repo_sync "$agents_dir" "$msg")
set -l sync_rc $status
set -l failed 0
if test $sync_rc -eq 2
+4 -29
View File
@@ -9,9 +9,6 @@
# _agents_repo_ensure_symlink, _agents_repo_sync,
# _agents_repo_install_tools, git, hostname
#
# CLASSIFICATION
# self-limiting(rm,mkdir), manual-section(16-agent-tooling)
#
# SYNOPSIS
# agents-vault [--link] [--push] [--restore] [--status]
# [--adopt=SLUG] [--remote=URL]
@@ -188,13 +185,6 @@
# machine that has a real global memory directory would move it into a
# throwaway directory and leave a dangling symlink behind, which is
# strictly worse than having had no backup at all.
#
# This header covers usage only. The full concept/behavior/purpose
# write-up -- how this relates to the per-project AGENTS/ repository
# agents-init manages, and where each kind of agent state actually lives
# -- is in docs/manual/16-agent-tooling.md. Update that section in the
# same change whenever this function's behavior changes; see "Dedicated
# manual sections for complex subsystems" in CONTRIBUTING.md.
function agents-vault --description 'track curated agent memory in a host-scoped vault repo'
__fish_palette
@@ -776,11 +766,7 @@ function agents-vault --description 'track curated agent memory in a host-scoped
if not mkdir -p "$gvault"
echo "$c_warn""agents-vault: could not create $gvault; skipping global memory$c_reset" >&2
else
# 2>/dev/null: see the same call's comment further down this
# function -- a command substitution's stderr bypasses this
# call's own caller-scoped redirect, and the raw message is
# always redundant with the $grc-driven echo just below.
set -l gmsg (_agents_repo_ensure_symlink "$glive" "$gvault" 2>/dev/null)
set -l gmsg (_agents_repo_ensure_symlink "$glive" "$gvault")
set -l grc $status
if test $grc -ne 0
echo "$c_warn""agents-vault: could not link $glive; global memory not backed up$c_reset" >&2
@@ -904,7 +890,7 @@ function agents-vault --description 'track curated agent memory in a host-scoped
if test -n "$stash"
test -f "$stash/origin"
and command cat "$stash/origin" >>"$entry/origin" 2>/dev/null
command cp -r --update=none "$stash/." "$entry/" 2>/dev/null
command cp -rn "$stash/." "$entry/" 2>/dev/null
rm -rf "$stash"
end
printf 'renamed: %s → %s (%s)\n' "$prev_slug" "$slug" (date -I) >>"$entry/origin"
@@ -950,14 +936,7 @@ function agents-vault --description 'track curated agent memory in a host-scoped
# idempotent and makes its own parent directories, so there is
# nothing this guard would protect that the helper does not already
# handle on its own.
#
# 2>/dev/null: a command substitution's stderr does not inherit a
# caller-scoped redirect on this call (fish quirk -- proven with a
# two-line repro: `outer 2>/dev/null` where outer does `set -l x
# (inner)` still leaks inner's stderr to the real terminal). The
# raw message below is always redundant: failure is re-announced on
# $link_rc below in this function's own voice.
set -l link_msg (_agents_repo_ensure_symlink "$live" "$vmem" 2>/dev/null)
set -l link_msg (_agents_repo_ensure_symlink "$live" "$vmem")
set -l link_rc $status
if test $link_rc -ne 0
echo "$c_err""agents-vault: could not link $live$c_reset" >&2
@@ -991,11 +970,7 @@ function agents-vault --description 'track curated agent memory in a host-scoped
if not set -q _flag_link
set -l msg "chore: sync agent memory vault"
test $did_init -eq 1; and set msg "chore: initialize agent memory vault"
# 2>/dev/null: command-substitution stderr bypasses this call's
# caller-scoped redirect (see the _agents_repo_ensure_symlink calls
# above for the proof); the raw message is redundant with the
# $sync_rc-driven echoes just below.
set -l sync_out (_agents_repo_sync "$vault" "$msg" 2>/dev/null)
set -l sync_out (_agents_repo_sync "$vault" "$msg")
set -l sync_rc $status
if test $sync_rc -eq 2
echo "$c_err""agents-vault: unresolved rebase in the vault; nothing committed$c_reset" >&2
+2 -2
View File
@@ -17,8 +17,8 @@
# Wrapper for the agy Antigravity AI CLI that ensures the AGENTS/
# sub-repository is initialized and any agent-made changes are committed
# before launch. Delegates all scaffold and commit logic to agents-init
# --quiet (full setup), which ensures AGENTS.md (root and every scoped
# subdirectory) is symlinked into AGENTS/ in the current project.
# --quiet (full setup), which ensures AGENTS/ is scaffolded and CLAUDE.md
# is symlinked to AGENTS/AGENTS.md in the current project.
#
# Also syncs the host-scoped agent memory vault (agents-vault). agy has
# no session-end hook, so its memory is captured on the next launch
-3
View File
@@ -4,9 +4,6 @@
# CATEGORY
# 12-ai-and-developer-tools
#
# CLASSIFICATION
# self-limiting(grep)
#
# SYNOPSIS
# antigravity-ide [args...]
#
-3
View File
@@ -7,9 +7,6 @@
# COMPONENT
# autoexec/sync
#
# DEPENDENCIES
# git
#
# SYNOPSIS
# auto-pull [list]
# auto-pull add [PATH]
-3
View File
@@ -7,9 +7,6 @@
# COMPONENT
# aliases/shell-tools
#
# CLASSIFICATION
# bypasses-shadow(bash)
#
# SYNOPSIS
# bash [args...]
#
-3
View File
@@ -4,9 +4,6 @@
# CATEGORY
# 14-miscellaneous
#
# DEPENDENCIES
# curl, jq, git
#
# SYNOPSIS
# bd-pull <owner/repo>
#
-8
View File
@@ -4,9 +4,6 @@
# CATEGORY
# 08-terminal-management
#
# DEPENDENCIES
# nohup
#
# SYNOPSIS
# bkg <command> [args...]
#
@@ -35,11 +32,6 @@ function bkg --description 'Execute bkg'
return 1
end
if not type -q nohup
echo (set_color red)"Error: nohup is not installed."(set_color normal) >&2
return 1
end
# Run the command using nohup to make it immune to hangups (like closing the terminal).
# Redirect both stdout and stderr to /dev/null to discard all output.
# The final ampersand (&) sends the entire process to the background.
-3
View File
@@ -4,9 +4,6 @@
# CATEGORY
# 04-git-and-version-control
#
# DEPENDENCIES
# git
#
# SYNOPSIS
# branch <branch_name>
#
-6
View File
@@ -7,12 +7,6 @@
# COMPONENT
# aliases/filesystem
#
# DEPENDENCIES
# bat
#
# CLASSIFICATION
# uses-shadow(ls), bypasses-shadow(cat)
#
# SYNOPSIS
# cat [args...]
#
-3
View File
@@ -4,9 +4,6 @@
# CATEGORY
# 02-navigation
#
# DEPENDENCIES
# zoxide
#
# SYNOPSIS
# cdi [query]
#
-6
View File
@@ -4,12 +4,6 @@
# CATEGORY
# 14-miscellaneous
#
# DEPENDENCIES
# fastfetch, neofetch
#
# CLASSIFICATION
# uses-shadow(ls)
#
# SYNOPSIS
# cffetch [args...]
#
-3
View File
@@ -4,9 +4,6 @@
# CATEGORY
# 14-miscellaneous
#
# DEPENDENCIES
# cheat, tldr, man
#
# SYNOPSIS
# cheat <topic> [args...]
#
-3
View File
@@ -4,9 +4,6 @@
# CATEGORY
# 06-dependency-management
#
# DEPENDENCIES
# fish-deps
#
# SYNOPSIS
# check_fish_deps
#
-3
View File
@@ -4,9 +4,6 @@
# CATEGORY
# 12-ai-and-developer-tools
#
# CLASSIFICATION
# uses-shadow(claude)
#
# SYNOPSIS
# claude-docs
#
-3
View File
@@ -4,9 +4,6 @@
# CATEGORY
# 12-ai-and-developer-tools
#
# CLASSIFICATION
# uses-shadow(claude)
#
# SYNOPSIS
# claude-pr
#
+3 -7
View File
@@ -10,9 +10,6 @@
# DEPENDENCIES
# agents-init, agents-vault
#
# CLASSIFICATION
# bypasses-shadow(claude)
#
# SYNOPSIS
# claude [ARGS...]
#
@@ -20,9 +17,8 @@
# Wrapper for the claude CLI that ensures the AGENTS/ sub-repository is
# initialized and any agent-made changes are committed before launch.
# Delegates all scaffold and commit logic to agents-init --quiet (full
# setup), which ensures AGENTS.md (root and every scoped subdirectory)
# is symlinked into AGENTS/ in the current project. claude-code reads
# AGENTS.md natively, so no CLAUDE.md is created or maintained.
# setup), which ensures AGENTS/ is scaffolded and CLAUDE.md is symlinked
# to AGENTS/AGENTS.md in the current project.
#
# Also syncs the host-scoped agent memory vault (agents-vault), which
# tracks curated memory living outside the project tree. The vault
@@ -45,7 +41,7 @@
# claude
# claude --resume
# claude "Explain the recent changes"
function claude --wraps=claude --description 'claude wrapper: ensures AGENTS/ is scaffolded before launch'
function claude --wraps=claude --description 'claude wrapper: auto-links AGENTS.md as CLAUDE.md'
if not __fish_config_op_enabled (status current-function)
command claude $argv
return $status
-6
View File
@@ -4,12 +4,6 @@
# CATEGORY
# 05-package-management
#
# DEPENDENCIES
# pacman
#
# CLASSIFICATION
# self-limiting(grep)
#
# SYNOPSIS
# cleanup
#
+1 -11
View File
@@ -4,9 +4,6 @@
# CATEGORY
# 02-navigation
#
# DEPENDENCIES
# clone-in-kitty
#
# SYNOPSIS
# clone [args...]
#
@@ -19,7 +16,7 @@
#
# EXIT STATUS
# 0 Repository cloned
# 1 Not running inside Kitty terminal, or clone-in-kitty isn't available
# 1 Not running inside Kitty terminal
#
# EXAMPLE
# clone https://github.com/user/repo.git
@@ -28,12 +25,5 @@ function clone --wraps='clone-in-kitty' --description 'alias clone=clone-in-kitt
echo "Error: The 'clone' command requires Kitty terminal." >&2
return 1
end
# $TERM only proves the terminal type -- clone-in-kitty is a function
# Kitty's own shell integration injects, which doesn't happen over an
# ssh session that merely inherits $TERM from the local Kitty.
if not type -q clone-in-kitty
echo "Error: 'clone' detected Kitty but clone-in-kitty isn't available (shell integration not loaded)." >&2
return 1
end
clone-in-kitty $argv
end
+1 -11
View File
@@ -4,9 +4,6 @@
# CATEGORY
# 02-navigation
#
# DEPENDENCIES
# clone-in-kitty
#
# SYNOPSIS
# clonet [args...]
#
@@ -19,7 +16,7 @@
#
# EXIT STATUS
# 0 Repository cloned
# 1 Not running inside Kitty terminal, or clone-in-kitty isn't available
# 1 Not running inside Kitty terminal
#
# EXAMPLE
# clonet https://github.com/user/repo.git
@@ -28,12 +25,5 @@ function clonet --wraps='clone-in-kitty --type=tab' --description 'alias clonet=
echo "Error: The 'clonet' command requires Kitty terminal." >&2
return 1
end
# $TERM only proves the terminal type -- clone-in-kitty is a function
# Kitty's own shell integration injects, which doesn't happen over an
# ssh session that merely inherits $TERM from the local Kitty.
if not type -q clone-in-kitty
echo "Error: 'clonet' detected Kitty but clone-in-kitty isn't available (shell integration not loaded)." >&2
return 1
end
clone-in-kitty --type=tab $argv
end
+47 -42
View File
@@ -4,12 +4,6 @@
# CATEGORY
# 14-miscellaneous
#
# DEPENDENCIES
# xdg-open, man, ov, bat
#
# CLASSIFICATION
# self-limiting(grep), bypasses-shadow(less)
#
# SYNOPSIS
# config-help [section]
# config-help --html
@@ -196,52 +190,63 @@ function config-help --description 'Open the offline fish shell configuration ma
# ── --help / -h ──────────────────────────────────────────────
if contains -- --help $argv; or contains -- -h $argv
__fish_palette
echo "$c_cmd""help config / config-help$c_reset"
set_color --bold
echo "help config / config-help"
set_color normal
echo " — view the offline fish shell configuration manual"
echo ""
echo "$c_head""USAGE$c_reset"
echo " $c_cmd""help config$c_reset $c_arg""[section]$c_reset"
echo " $c_cmd""help config$c_reset $c_arg""[section]$c_reset $c_flag--html$c_reset"
echo " $c_cmd""help config$c_reset $c_arg""[section]$c_reset $c_flag--man$c_reset"
echo " $c_cmd""help config$c_reset $c_flag--help$c_reset"
set_color --bold brblue
echo USAGE
set_color normal
echo " help config "(set_color yellow)"[section]"(set_color normal)
echo " help config "(set_color yellow)"[section] --html"(set_color normal)
echo " help config "(set_color yellow)"[section] --man"(set_color normal)
echo " help config "(set_color yellow)"--help"(set_color normal)
echo ""
echo "$c_head""ARGUMENTS$c_reset"
echo " $c_arg""section$c_reset Optional keyword to jump to a matching section heading."
set_color --bold brblue
echo ARGUMENTS
set_color normal
echo " "(set_color yellow)"section"(set_color normal)" Optional keyword to jump to a matching section heading."
echo " Searches docs/fish-config.index for aliases first, then"
echo " falls back to a normalized (case- and punctuation-insensitive)"
echo " scan of heading lines."
echo " $c_flag-w, --html$c_reset Open the published documentation website in the default browser."
echo " "(set_color yellow)"-w, --html"(set_color normal)" Open the published documentation website in the default browser."
echo " Deep links aren't supported — use the site's search box."
echo " $c_flag-m, --man$c_reset Open the compiled man page via man -l."
echo " "(set_color yellow)"-m, --man"(set_color normal)" Open the compiled man page via man -l."
echo " If a section keyword is given, jumps to the nearest match."
echo ""
echo "$c_head""EXAMPLES$c_reset"
echo " $c_cmd""help config$c_reset open at top"
echo " $c_cmd""help config$c_reset $c_arg""keybindings$c_reset jump to Key Bindings section"
echo " $c_cmd""help config$c_reset $c_arg""pkg$c_reset jump to the pkg function entry"
echo " $c_cmd""help config$c_reset $c_arg""fish-deps$c_reset jump to fish-deps"
echo " $c_cmd""help config$c_reset $c_arg""abbreviations$c_reset jump to Abbreviations section"
echo " $c_cmd""help config$c_reset $c_flag--html$c_reset open the documentation website"
echo " $c_cmd""help config$c_reset $c_flag--man$c_reset open compiled man page"
echo " $c_cmd""help config$c_reset $c_arg""pkg$c_reset $c_flag--man$c_reset open man page at pkg section"
set_color --bold brblue
echo EXAMPLES
set_color normal
echo " "(set_color green)"help config"(set_color normal)" open at top"
echo " "(set_color green)"help config keybindings"(set_color normal)" jump to Key Bindings section"
echo " "(set_color green)"help config pkg"(set_color normal)" jump to the pkg function entry"
echo " "(set_color green)"help config fish-deps"(set_color normal)" jump to fish-deps"
echo " "(set_color green)"help config abbreviations"(set_color normal)" jump to Abbreviations section"
echo " "(set_color green)"help config --html"(set_color normal)" open the documentation website"
echo " "(set_color green)"help config --man"(set_color normal)" open compiled man page"
echo " "(set_color green)"help config pkg --man"(set_color normal)" open man page at pkg section"
echo ""
echo "$c_head""NAVIGATION (ov pager)$c_reset"
echo " $c_arg""Space$c_reset next section"
echo " $c_arg""^$c_reset previous section"
echo " $c_arg""Alt+u$c_reset toggle section list sidebar"
echo " $c_arg""/$c_reset search forward"
echo " $c_arg""n$c_reset / $c_arg""N$c_reset next / previous search match"
echo " $c_arg""g$c_reset go to line number"
echo " $c_arg""q$c_reset quit"
set_color --bold brblue
echo "NAVIGATION (ov pager)"
set_color normal
echo " "(set_color cyan)"Space"(set_color normal)" next section"
echo " "(set_color cyan)"^"(set_color normal)" previous section"
echo " "(set_color cyan)"Alt+u"(set_color normal)" toggle section list sidebar"
echo " "(set_color cyan)"/"(set_color normal)" search forward"
echo " "(set_color cyan)"n"(set_color normal)" / "(set_color cyan)"N"(set_color normal)" next / previous search match"
echo " "(set_color cyan)"g"(set_color normal)" go to line number"
echo " "(set_color cyan)"q"(set_color normal)" quit"
echo ""
echo "$c_head""PAGER FALLBACK CHAIN$c_reset"
echo " $c_dim""1.$c_reset $c_cmd""ov$c_reset + $c_cmd""bat$c_reset section nav + syntax highlighting $c_dim""(best)$c_reset"
echo " $c_dim""2.$c_reset $c_cmd""ov$c_reset alone section nav, raw Markdown"
echo " $c_dim""3.$c_reset $c_cmd""bat$c_reset alone syntax highlighting, use / to search"
echo " $c_dim""4.$c_reset $c_cmd""man$c_reset $c_flag-l$c_reset pre-compiled man page (if available)"
echo " $c_dim""5.$c_reset $c_cmd""less$c_reset plain text with line-jump"
echo " $c_dim""6.$c_reset $c_cmd""cat$c_reset plain output"
set_color --bold brblue
echo "PAGER FALLBACK CHAIN"
set_color normal
echo " "(set_color brblack)"1."(set_color normal)" ov + bat section nav + syntax highlighting "(set_color brblack)"(best)"(set_color normal)
echo " "(set_color brblack)"2."(set_color normal)" ov alone section nav, raw Markdown"
echo " "(set_color brblack)"3."(set_color normal)" bat alone syntax highlighting, use / to search"
echo " "(set_color brblack)"4."(set_color normal)" man -l pre-compiled man page (if available)"
echo " "(set_color brblack)"5."(set_color normal)" less plain text with line-jump"
echo " "(set_color brblack)"6."(set_color normal)" cat plain output"
return 0
end
@@ -353,7 +358,7 @@ function config-help --description 'Open the offline fish shell configuration ma
else if type -q less
string replace -ra $span_raw $span_bold <"$doc_file" \
| command less -R +"$start_line"
| less -R +"$start_line"
else
string replace -ra $span_raw $span_bold <"$doc_file"
-3
View File
@@ -8,9 +8,6 @@
# __fish_palette, __config_settings_state, __config_settings_apply,
# __config_settings_set_value, python3
#
# CLASSIFICATION
# bypasses-shadow(rm)
#
# SYNOPSIS
# config-settings [-h | --help]
#
-3
View File
@@ -4,9 +4,6 @@
# CATEGORY
# 14-miscellaneous
#
# DEPENDENCIES
# git
#
# SYNOPSIS
# config-update [-h | --help] [-f | --force] [-n | --dry-run]
#
-3
View File
@@ -4,9 +4,6 @@
# CATEGORY
# 01-file-and-directory
#
# CLASSIFICATION
# bypasses-shadow(cp)
#
# SYNOPSIS
# copy <source> <dest>
#
-8
View File
@@ -4,9 +4,6 @@
# CATEGORY
# 08-terminal-management
#
# DEPENDENCIES
# nohup
#
# SYNOPSIS
# detach [-h] [--version] <command> [args...]
#
@@ -69,10 +66,5 @@ function detach --description 'Execute detach'
return 1
end
if not type -q nohup
echo (set_color red)"Error: nohup is not installed."(set_color normal) >&2
return 1
end
nohup $args >/dev/null 2>&1 &
end
+6 -12
View File
@@ -4,12 +4,6 @@
# CATEGORY
# 13-media-and-utilities
#
# DEPENDENCIES
# magick, ffmpeg, avifenc, exiftool
#
# CLASSIFICATION
# self-limiting(rm)
#
# SYNOPSIS
# dng2avif [-h] [-i <file>] [-o <file>] [-q <n>] [-s <n>] [input.dng]
#
@@ -45,13 +39,13 @@ function dng2avif --description 'Convert DNG raw to 10-bit HDR AVIF'
# Help Screen
if set -q _flag_help; or test (count $argv) -eq 0 -a -z "$_flag_input"
__fish_palette
echo "$c_head""Usage:$c_reset $c_cmd""dng2avif$c_reset $c_flag""[options]$c_reset $c_arg""[input.dng]$c_reset"
echo "$c_head""Usage:$c_reset $c_cmd""dng2avif$c_reset $c_flag""[options]$c_reset $c_dim""[input.dng]$c_reset"
echo ""
echo "$c_head""Options:$c_reset"
echo " $c_flag-i$c_reset, $c_flag--input$c_reset $c_arg""FILE$c_reset Input DNG file"
echo " $c_flag-o$c_reset, $c_flag--output$c_reset $c_arg""FILE$c_reset Output AVIF file (defaults to input name)"
echo " $c_flag-q$c_reset, $c_flag--quality$c_reset $c_arg""N$c_reset Encoding quality 0-100 (default: 92)"
echo " $c_flag-s$c_reset, $c_flag--speed$c_reset $c_arg""N$c_reset Encoder speed 0-10 (default: 3, 0=slowest)"
echo " $c_flag-i$c_reset, $c_flag--input$c_reset $c_dim""FILE$c_reset Input DNG file"
echo " $c_flag-o$c_reset, $c_flag--output$c_reset $c_dim""FILE$c_reset Output AVIF file (defaults to input name)"
echo " $c_flag-q$c_reset, $c_flag--quality$c_reset $c_dim""N$c_reset Encoding quality 0-100 (default: 92)"
echo " $c_flag-s$c_reset, $c_flag--speed$c_reset $c_dim""N$c_reset Encoder speed 0-10 (default: 3, 0=slowest)"
echo " $c_flag-h$c_reset, $c_flag--help$c_reset Show this help message"
return 0
end
@@ -125,7 +119,7 @@ function dng2avif --description 'Convert DNG raw to 10-bit HDR AVIF'
end
# Final Cleanup
test -f "$temp_pnm"; and rm -f "$temp_pnm"
test -f "$temp_pnm"; and rm "$temp_pnm"
set -l size (stat -c '%s' "$output" | numfmt --to=iec)
echo (set_color yellow)"Complete: $output ($size)"(set_color normal)
+2 -8
View File
@@ -4,12 +4,6 @@
# CATEGORY
# 14-miscellaneous
#
# DEPENDENCIES
# docker
#
# CLASSIFICATION
# network
#
# SYNOPSIS
# dockup [-h] [directory]
#
@@ -37,13 +31,13 @@ function dockup --description 'Pull and restart docker compose containers'
# Handle help flags
if contains -- -h $argv; or contains -- --help $argv
__fish_palette
echo "$c_head""Usage:$c_reset $c_cmd""dockup$c_reset $c_arg""[DIRECTORY]$c_reset"
echo "$c_head""Usage:$c_reset $c_cmd""dockup$c_reset $c_dim""[DIRECTORY]$c_reset"
echo ""
echo "$c_head""Options:$c_reset"
echo " $c_flag-h$c_reset, $c_flag--help$c_reset Show this help message"
echo ""
echo "$c_head""Arguments:$c_reset"
echo " $c_arg""DIRECTORY$c_reset Optional path to the compose project (defaults to current dir)"
echo " $c_dim""DIRECTORY$c_reset Optional path to the compose project (defaults to current dir)"
return 0
end
-6
View File
@@ -7,12 +7,6 @@
# COMPONENT
# aliases/filesystem
#
# DEPENDENCIES
# duf, dust, dua
#
# CLASSIFICATION
# bypasses-shadow(du)
#
# SYNOPSIS
# du [--disk|--dir|--dua] [args...]
#
-3
View File
@@ -4,9 +4,6 @@
# CATEGORY
# 01-file-and-directory
#
# CLASSIFICATION
# uses-shadow(du)
#
# SYNOPSIS
# dusize [dir]
#
-6
View File
@@ -7,12 +7,6 @@
# COMPONENT
# aliases/dev-tools
#
# DEPENDENCIES
# p, nano, nvim
#
# CLASSIFICATION
# bypasses-shadow(rm)
#
# SYNOPSIS
# edit [-V|-t] [-e EDITOR] [-c] [-x TEXT] [-n] [-v|-s] [FILE...]
#
-11
View File
@@ -4,9 +4,6 @@
# CATEGORY
# 10-network
#
# DEPENDENCIES
# fast
#
# SYNOPSIS
# fast-cli [args...]
#
@@ -16,16 +13,8 @@
# ARGUMENTS
# args... Arguments forwarded to the fast command
#
# EXIT STATUS
# 1 fast is not installed
# * Exit status of fast otherwise
#
# EXAMPLE
# fast-cli
function fast-cli --description "Run a speed test using fast.com"
if not type -q -f fast
echo (set_color red)"Error: fast is not installed."(set_color normal) >&2
return 1
end
command fast $argv
end
+3 -6
View File
@@ -4,9 +4,6 @@
# CATEGORY
# 03-editors-and-viewers
#
# CLASSIFICATION
# bypasses-shadow(rm), self-limiting(cat)
#
# SYNOPSIS
# fc [command_prefix]
#
@@ -53,15 +50,15 @@ function fc --description 'Edit and execute the last command (Bash-style fc)'
# Final check if user cleared the file in the editor
if test -s $tmpfile
set -l command (cat $tmpfile)
command rm -f $tmpfile
rm $tmpfile
commandline -r "$command"
commandline -f execute
else
command rm -f $tmpfile
rm $tmpfile
echo "fc: Aborted (empty file)"
end
else
command rm -f $tmpfile
rm $tmpfile
echo "fc: Could not retrieve history"
end
end
-6
View File
@@ -4,12 +4,6 @@
# CATEGORY
# 14-miscellaneous
#
# DEPENDENCIES
# fastfetch, neofetch
#
# CLASSIFICATION
# uses-shadow(ls)
#
# SYNOPSIS
# ffetch [args...]
#
+1 -4
View File
@@ -4,9 +4,6 @@
# CATEGORY
# 06-dependency-management
#
# DEPENDENCIES
# _fish_deps_status, _fish_deps_install, _fish_deps_update
#
# SYNOPSIS
# fish-deps [status|install|update|sync] [--optional] [--terminals] [--all]
#
@@ -108,7 +105,7 @@ function __fish_deps_help
echo "$c_head""fish-deps$c_reset — manage fish shell dependencies"
echo ""
echo "$c_head""Usage:$c_reset"
echo " $c_cmd""fish-deps$c_reset $c_arg""[status]$c_reset Check installed/missing deps (default)"
echo " $c_cmd""fish-deps$c_reset $c_dim""[status]$c_reset Check installed/missing deps (default)"
echo " $c_cmd""fish-deps$c_reset install Install missing deps interactively"
echo " $c_cmd""fish-deps$c_reset update Update all installed deps"
echo " $c_cmd""fish-deps$c_reset sync Install missing, then update all"
-3
View File
@@ -7,9 +7,6 @@
# COMPONENT
# overrides/prompt
#
# DEPENDENCIES
# docker, starship
#
# SYNOPSIS
# fish_right_prompt
#
+251
View File
@@ -0,0 +1,251 @@
function fisher --argument-names cmd --description "A plugin manager for Fish"
set --query fisher_path || set --local fisher_path $__fish_config_dir
set --local fisher_version 4.4.8
set --local fish_plugins $__fish_config_dir/fish_plugins
switch "$cmd"
case -v --version
echo "fisher, version $fisher_version"
case "" -h --help
echo "Usage: fisher install <plugins...> Install plugins"
echo " fisher remove <plugins...> Remove installed plugins"
echo " fisher uninstall <plugins...> Remove installed plugins (alias)"
echo " fisher update <plugins...> Update installed plugins"
echo " fisher update Update all installed plugins"
echo " fisher list [<regex>] List installed plugins matching regex"
echo "Options:"
echo " -v, --version Print version"
echo " -h, --help Print this help message"
echo "Variables:"
echo " \$fisher_path Plugin installation path. Default: $__fish_config_dir" | string replace --regex -- $HOME \~
case ls list
string match --entire --regex -- "$argv[2]" $_fisher_plugins
case install update remove uninstall
isatty || read --local --null --array stdin && set --append argv $stdin
test "$cmd" = uninstall && set cmd remove
set --local install_plugins
set --local update_plugins
set --local remove_plugins
set --local arg_plugins $argv[2..-1]
set --local old_plugins $_fisher_plugins
set --local new_plugins
test -e $fish_plugins && set --local file_plugins (string match --regex -- '^[^\s]+$' <$fish_plugins | string replace -- \~ ~)
if ! set --query argv[2]
if test "$cmd" != update
echo "fisher: Not enough arguments for command: \"$cmd\"" >&2 && return 1
else if ! set --query file_plugins
echo "fisher: \"$fish_plugins\" file not found: \"$cmd\"" >&2 && return 1
end
set arg_plugins $file_plugins
else if test "$cmd" = install && ! set --query old_plugins[1]
set --append arg_plugins $file_plugins
end
for plugin in $arg_plugins
set plugin (test -e "$plugin" && realpath $plugin || string lower -- $plugin)
contains -- "$plugin" $new_plugins || set --append new_plugins $plugin
end
if set --query argv[2]
for plugin in $new_plugins
if contains -- "$plugin" $old_plugins
test "$cmd" = remove &&
set --append remove_plugins $plugin ||
set --append update_plugins $plugin
else if test "$cmd" = install
set --append install_plugins $plugin
else
echo "fisher: Plugin not installed: \"$plugin\"" >&2 && return 1
end
end
else
for plugin in $new_plugins
contains -- "$plugin" $old_plugins &&
set --append update_plugins $plugin ||
set --append install_plugins $plugin
end
for plugin in $old_plugins
contains -- "$plugin" $new_plugins || set --append remove_plugins $plugin
end
end
set --local pid_list
set --local source_plugins
set --local fetch_plugins $update_plugins $install_plugins
set --local fish_path (status fish-path)
echo (set_color --bold)fisher $cmd version $fisher_version(set_color normal)
for plugin in $fetch_plugins
set --local source (command mktemp -d)
set --append source_plugins $source
command mkdir -p $source/{completions,conf.d,themes,functions}
$fish_path --command "
if test -e $plugin
command cp -Rf $plugin/* $source
else
set resp (command mktemp)
set temp (command mktemp -d)
set repo (string split -- \@ $plugin) || set repo[2] HEAD
if set path (string replace --regex -- '^(https://)?gitlab.com/' '' \$repo[1])
set name (string split -- / \$path)[-1]
set url https://gitlab.com/\$path/-/archive/\$repo[2]/\$name-\$repo[2].tar.gz
else
set url https://api.github.com/repos/\$repo[1]/tarball/\$repo[2]
end
echo Fetching (set_color --underline)\$url(set_color normal)
set http (command curl -q --silent -L -o \$resp -w %{http_code} \$url)
if test \"\$http\" = 200 && command tar -xzC \$temp -f \$resp 2>/dev/null
command cp -Rf \$temp/*/* $source
else if test \"\$http\" = 403
echo fisher: GitHub API rate limit exceeded \(HTTP 403\) >&2
command rm -rf $source
else
echo fisher: Invalid plugin name or host unavailable: \\\"$plugin\\\" >&2
command rm -rf $source
end
command rm -rf \$temp
end
set files $source/* && string match --quiet --regex -- .+\.fish\\\$ \$files
" &
set --append pid_list (jobs --last --pid)
end
wait $pid_list 2>/dev/null
for plugin in $fetch_plugins
if set --local source $source_plugins[(contains --index -- "$plugin" $fetch_plugins)] && test ! -e $source
if set --local index (contains --index -- "$plugin" $install_plugins)
set --erase install_plugins[$index]
else
set --erase update_plugins[(contains --index -- "$plugin" $update_plugins)]
end
end
end
for plugin in $update_plugins $remove_plugins
if set --local index (contains --index -- "$plugin" $_fisher_plugins)
set --local plugin_files_var _fisher_(string escape --style=var -- $plugin)_files
if contains -- "$plugin" $remove_plugins
for name in (string replace --filter --regex -- '.+/conf\.d/([^/]+)\.fish$' '$1' $$plugin_files_var)
emit {$name}_uninstall
end
printf "%s\n" Removing\ (set_color red --bold)$plugin(set_color normal) " "$$plugin_files_var | string replace -- \~ ~
set --erase _fisher_plugins[$index]
end
command rm -rf (string replace -- \~ ~ $$plugin_files_var)
functions --erase (string replace --filter --regex -- '.+/functions/([^/]+)\.fish$' '$1' $$plugin_files_var)
for name in (string replace --filter --regex -- '.+/completions/([^/]+)\.fish$' '$1' $$plugin_files_var)
complete --erase --command $name
end
set --erase $plugin_files_var
end
end
if set --query update_plugins[1] || set --query install_plugins[1]
command mkdir -p $fisher_path/{functions,themes,conf.d,completions}
end
for plugin in $update_plugins $install_plugins
set --local source $source_plugins[(contains --index -- "$plugin" $fetch_plugins)]
set --local files $source/{functions,themes,conf.d,completions}/*
if set --local index (contains --index -- $plugin $install_plugins)
set --local user_files $fisher_path/{functions,themes,conf.d,completions}/*
set --local conflict_files
for file in (string replace -- $source/ $fisher_path/ $files)
contains -- $file $user_files && set --append conflict_files $file
end
if set --query conflict_files[1] && set --erase install_plugins[$index]
echo -s "fisher: Cannot install \"$plugin\": please remove or move conflicting files first:" \n" "$conflict_files >&2
continue
end
end
for file in (string replace -- $source/ "" $files)
command cp -RLf $source/$file $fisher_path/$file
end
set --local plugin_files_var _fisher_(string escape --style=var -- $plugin)_files
set --query files[1] && set --universal $plugin_files_var (string replace -- $source $fisher_path $files | string replace -- ~ \~)
contains -- $plugin $_fisher_plugins || set --universal --append _fisher_plugins $plugin
contains -- $plugin $install_plugins && set --local event install || set --local event update
printf "%s\n" Installing\ (set_color --bold)$plugin(set_color normal) " "$$plugin_files_var | string replace -- \~ ~
for file in (string match --regex -- '.+/[^/]+\.fish$' $$plugin_files_var | string replace -- \~ ~)
source $file
if set --local name (string replace --regex -- '.+conf\.d/([^/]+)\.fish$' '$1' $file)
emit {$name}_$event
end
end
end
command rm -rf $source_plugins
if set --query _fisher_plugins[1]
set --local commit_plugins
for plugin in $file_plugins
contains -- (string lower -- $plugin) (string lower -- $_fisher_plugins) && set --append commit_plugins $plugin
end
for plugin in $_fisher_plugins
contains -- (string lower -- $plugin) (string lower -- $commit_plugins) || set --append commit_plugins $plugin
end
string replace --regex -- $HOME \~ $commit_plugins >$fish_plugins
else
set --erase _fisher_plugins
command rm -f $fish_plugins
end
set --local total (count $install_plugins) (count $update_plugins) (count $remove_plugins)
test "$total" != "0 0 0" && echo (string join ", " (
test $total[1] = 0 || echo "Installed $total[1]") (
test $total[2] = 0 || echo "Updated $total[2]") (
test $total[3] = 0 || echo "Removed $total[3]")
) plugin/s
case \*
echo "fisher: Unknown command: \"$cmd\"" >&2 && return 1
end
end
if ! set --query _fisher_upgraded_to_4_4
set --universal _fisher_upgraded_to_4_4
if functions --query _fisher_list
set --query XDG_DATA_HOME[1] || set --local XDG_DATA_HOME ~/.local/share
command rm -rf $XDG_DATA_HOME/fisher
functions --erase _fisher_{list,plugin_parse}
fisher update >/dev/null 2>/dev/null
else
for var in (set --names | string match --entire --regex '^_fisher_.+_files$')
set $var (string replace -- ~ \~ $$var)
end
functions --erase _fisher_fish_postexec
end
end
-6
View File
@@ -4,12 +4,6 @@
# CATEGORY
# 06-dependency-management
#
# DEPENDENCIES
# git, fzf
#
# CLASSIFICATION
# network
#
# SYNOPSIS
# fzf-update
#
-4
View File
@@ -4,10 +4,6 @@
# CATEGORY
# 14-miscellaneous
#
# DEPENDENCIES
# _fzf_search_directory, _fzf_search_git_log, _fzf_search_git_status,
# _fzf_search_history, _fzf_search_processes, _fzf_search_variables
#
# SYNOPSIS
# fzf_configure_bindings [--directory=<key>] [--git_log=<key>] [--git_status=<key>]
# [--history=<key>] [--processes=<key>] [--variables=<key>] [-h]
+47 -138
View File
@@ -4,90 +4,67 @@
# CATEGORY
# 04-git-and-version-control
#
# DEPENDENCIES
# curl, md5sum, md5, gitignore-scrub
#
# CLASSIFICATION
# self-limiting(grep,cat), network, blocking-prompt
#
# SYNOPSIS
# gi [-h] [-b] [-p] [-o] [-s] [-f] [-c TEMPLATE] [-l] [targets...]
# gi [-h] [-b] [-p] [-s] [-l] [targets...]
#
# DESCRIPTION
# Generates .gitignore content by querying the gitignore.io API. Appends
# results to the repository's .gitignore with MD5-based deduplication —
# patterns already present are not re-appended — or prints to stdout with
# -o/--stdout. Boilerplate mode uses $GITIGNORE_BOILERPLATE if set, a
# -c/--custom template if given, or falls back to the bundled standard
# template (data/gi/boilerplate.gitignore) when neither is configured.
# Supports generic boilerplate and interactive prompt modes.
# -s. Supports generic boilerplate and interactive prompt modes.
#
# ARGUMENTS
# -h, --help Show help message
# -d, --description Show the function description
# -l, --list List all supported targets from the API
# -b, --boilerplate Append boilerplate (implied by -c)
# -b, --boilerplate Append boilerplate from $GITIGNORE_BOILERPLATE
# -p, --prompt Prompt for patterns to append
# -o, --stdout Print generated content to stdout instead of .gitignore
# -s, --silent Suppress progress output (errors and prompts still show)
# -f, --force Bypass prompts, proceeding with the default action
# -c, --custom PATH Use PATH as the boilerplate template instead of
# $GITIGNORE_BOILERPLATE
# -s, --stdout Print API output to stdout instead of .gitignore
# targets Comma- or space-separated list of language/tool names
#
# EXIT STATUS
# 0 Patterns appended, or resolved with -o/--stdout or -l/--list
# 0 Patterns appended, or resolved with -s/--stdout or -l/--list
# 1 Not in a git repository or API fetch failed
#
# RETURNS
# With -o/--stdout, the fetched .gitignore pattern text, printed to stdout.
# With -s/--stdout, the fetched .gitignore pattern text, printed to stdout.
# With -l/--list, the supported target list, printed to stdout.
#
# EXAMPLE
# gi python,venv
# gi -b -p
# gi -o node > .gitignore
# gi -f # skip prompt, proceed with no patterns
# gi -c ~/my-template.gitignore
# gi -s node > .gitignore
function gi --description 'Generate .gitignore files using the gitignore.io API'
argparse h/help d/description l/list b/boilerplate p/prompt o/stdout s/silent f/force c/custom= -- $argv
argparse h/help d/description l/list b/boilerplate p/prompt s/stdout -- $argv
or return 1
if set -q _flag_help
__fish_palette
echo "$c_head""Usage:$c_reset $c_cmd""gi$c_reset $c_arg""[TARGETS...]$c_reset $c_arg""[FLAGS]$c_reset"
set_color --bold
echo "Usage:"(set_color normal)" gi "(set_color cyan)"[TARGETS...]"(set_color yellow)" [FLAGS]"(set_color normal)
echo ""
echo "$c_head""Arguments:$c_reset"
echo " $c_arg""TARGETS$c_reset Comma-separated list of languages or tools"
echo " $c_dim""e.g. c++,neovim,archlinux$c_reset"
set_color --bold
echo "Arguments:"(set_color normal)
echo " "(set_color cyan)"TARGETS"(set_color normal)" Comma-separated list of languages or tools"
echo " "(set_color brblack)"e.g. c++,neovim,archlinux"(set_color normal)
echo ""
echo "$c_head""Flags:$c_reset"
echo " $c_flag-h, --help $c_reset Show this help message"
echo " $c_flag-d, --description $c_reset Show the Fish function description"
echo " $c_flag-l, --list $c_reset List all supported targets from the API"
echo " $c_flag-b, --boilerplate $c_reset Append boilerplate (implied by "$c_flag"-c$c_reset)"
echo " $c_flag-p, --prompt $c_reset Prompt for patterns and append them to .gitignore"
echo " $c_flag-o, --stdout $c_reset Print generated content to stdout instead of .gitignore"
echo " $c_flag-s, --silent $c_reset Suppress progress output (errors and prompts still show)"
echo " $c_flag-f, --force $c_reset Bypass prompts, proceeding with the default action"
echo " $c_flag-c, --custom $c_reset $c_arg""PATH$c_reset Use PATH as the boilerplate template"
echo " $c_dim""instead of \$GITIGNORE_BOILERPLATE$c_reset"
set_color --bold
echo "Flags:"(set_color normal)
echo " "(set_color yellow)"-h, --help "(set_color normal)" Show this help message"
echo " "(set_color yellow)"-d, --description "(set_color normal)" Show the Fish function description"
echo " "(set_color yellow)"-l, --list "(set_color normal)" List all supported targets from the API"
echo " "(set_color yellow)"-b, --boilerplate "(set_color normal)" Append boilerplate from "(set_color cyan)"\$GITIGNORE_BOILERPLATE"(set_color normal)" to .gitignore"
echo " "(set_color yellow)"-p, --prompt "(set_color normal)" Prompt for patterns and append them to .gitignore"
echo " "(set_color yellow)"-s, --stdout "(set_color normal)" Print API output to stdout instead of appending to .gitignore"
echo ""
echo "$c_head""Boilerplate source (in priority order):$c_reset"
echo " 1. "$c_flag"-c/--custom$c_reset PATH, if given"
echo " 2. "$c_arg"\$GITIGNORE_BOILERPLATE$c_reset, if set"
echo " 3. "$c_dim"the bundled standard template$c_reset"
echo ""
echo "$c_head""Examples:$c_reset"
echo " $c_cmd""gi$c_reset $c_dim""# Append boilerplate and prompt for patterns (default)$c_reset"
echo " $c_cmd""gi -b$c_reset $c_dim""# Append boilerplate only$c_reset"
echo " $c_cmd""gi -p$c_reset $c_dim""# Prompt for patterns and append to .gitignore$c_reset"
echo " $c_cmd""gi$c_reset $c_arg""c++$c_reset $c_dim""# Append C++ patterns to .gitignore$c_reset"
echo " $c_cmd""gi$c_reset $c_arg""python,venv$c_reset $c_dim""# Append Python+venv patterns to .gitignore$c_reset"
echo " $c_cmd""gi -o$c_reset $c_arg""python,venv$c_reset $c_dim""# Print Python+venv patterns to stdout$c_reset"
echo " $c_cmd""gi -f$c_reset $c_dim""# Skip prompt, proceed with no patterns$c_reset"
echo " $c_cmd""gi -c$c_reset $c_arg""~/my.gitignore$c_reset $c_dim""# Append a custom boilerplate template$c_reset"
echo " $c_cmd""gi -l$c_reset | grep -i linux $c_dim""# Search for specific OS support$c_reset"
set_color --bold
echo "Examples:"(set_color normal)
echo " "(set_color green)"gi"(set_color normal)" "(set_color brblack)"# Append boilerplate and prompt for patterns (default)"(set_color normal)
echo " "(set_color green)"gi -b"(set_color normal)" "(set_color brblack)"# Append boilerplate only"(set_color normal)
echo " "(set_color green)"gi -p"(set_color normal)" "(set_color brblack)"# Prompt for patterns and append to .gitignore"(set_color normal)
echo " "(set_color green)"gi"(set_color normal)" "(set_color cyan)"c++"(set_color normal)" "(set_color brblack)"# Append C++ patterns to .gitignore"(set_color normal)
echo " "(set_color green)"gi"(set_color normal)" "(set_color cyan)"python,venv"(set_color normal)" "(set_color brblack)"# Append Python+venv patterns to .gitignore"(set_color normal)
echo " "(set_color green)"gi -s"(set_color normal)" "(set_color cyan)"python,venv"(set_color normal)" "(set_color brblack)"# Print Python+venv patterns to stdout"(set_color normal)
echo " "(set_color green)"gi -l"(set_color normal)" | grep -i linux "(set_color brblack)"# Search for specific OS support"(set_color normal)
return 0
end
@@ -101,14 +78,11 @@ function gi --description 'Generate .gitignore files using the gitignore.io API'
return 0
end
set -l silent_flag 0
set -q _flag_silent; and set silent_flag 1
# Determine which modes to run
set -l do_boilerplate 0
set -l do_prompt 0
if set -q _flag_boilerplate; or set -q _flag_custom
if set -q _flag_boilerplate
set do_boilerplate 1
end
if set -q _flag_prompt
@@ -121,18 +95,15 @@ function gi --description 'Generate .gitignore files using the gitignore.io API'
set do_prompt 1
end
# Resolve git context for anything that writes to .gitignore.
# --stdout never touches .gitignore, so it never needs a git repo.
# Resolve git context for anything that writes to .gitignore
set -l gitignore_path ""
set -l readable_path ""
set -l needs_git 0
if not set -q _flag_stdout
if test $do_boilerplate -eq 1; or test $do_prompt -eq 1
set needs_git 1
else if set -q argv[1]
else if set -q argv[1]; and not set -q _flag_stdout
set needs_git 1
end
end
if test $needs_git -eq 1
if not git rev-parse --is-inside-work-tree >/dev/null 2>&1
@@ -145,85 +116,39 @@ function gi --description 'Generate .gitignore files using the gitignore.io API'
set readable_path (string replace -r "^$HOME" "~" $gitignore_path)
end
# Boilerplate mode: resolve the template source, in priority order:
# 1. -c/--custom PATH
# 2. $GITIGNORE_BOILERPLATE
# 3. the bundled standard template (data/gi/boilerplate.gitignore)
# Boilerplate mode
if test $do_boilerplate -eq 1
set -l boilerplate_path ""
set -l boilerplate_ok 1
if set -q _flag_custom
if test -f "$_flag_custom"
set boilerplate_path "$_flag_custom"
else
if not set -q GITIGNORE_BOILERPLATE
set_color red --bold
echo "Error:" (set_color normal)"Custom boilerplate file not found at '$_flag_custom'" >&2
set boilerplate_ok 0
end
else if set -q GITIGNORE_BOILERPLATE
if test -f "$GITIGNORE_BOILERPLATE"
set boilerplate_path "$GITIGNORE_BOILERPLATE"
else
echo "Error:" (set_color normal)"\$GITIGNORE_BOILERPLATE environment variable is not defined" >&2
else if not test -f "$GITIGNORE_BOILERPLATE"
set_color red --bold
echo "Error:" (set_color normal)"Boilerplate file not found at '$GITIGNORE_BOILERPLATE'" >&2
set boilerplate_ok 0
end
else
if set -q __fish_config_dir
set boilerplate_path "$__fish_config_dir/data/gi/boilerplate.gitignore"
else
set boilerplate_path "$HOME/.config/fish/data/gi/boilerplate.gitignore"
end
if not test -f "$boilerplate_path"
set_color red --bold
echo "Error:" (set_color normal)"Bundled default boilerplate missing at '$boilerplate_path'" >&2
set boilerplate_ok 0
else if not set -q _flag_silent
set_color yellow --bold
echo "Notice:" (set_color normal)"\$GITIGNORE_BOILERPLATE not set; using the bundled default template."
end
end
if test $boilerplate_ok -eq 1
if set -q _flag_stdout
cat "$boilerplate_path"
else
set -l template_hash ""
if command -q md5sum
set template_hash (md5sum "$boilerplate_path" | string split ' ')[1]
set template_hash (md5sum "$GITIGNORE_BOILERPLATE" | string split ' ')[1]
else if command -q md5
set template_hash (md5 -q "$boilerplate_path")
set template_hash (md5 -q "$GITIGNORE_BOILERPLATE")
end
set -l sig "# id: gitig-boilerplate-$template_hash"
if test -f "$gitignore_path"; and grep -qF "$sig" "$gitignore_path"
if not set -q _flag_silent
set_color yellow --bold
echo "Notice:" (set_color normal)"Boilerplate already present in "(set_color cyan)"$readable_path"(set_color normal)"."
end
else
printf "\n%s\n" "$sig" >>"$gitignore_path"
cat "$boilerplate_path" >>"$gitignore_path"
if not set -q _flag_silent
cat "$GITIGNORE_BOILERPLATE" >>"$gitignore_path"
echo (set_color green)"✔"(set_color normal)" Appended boilerplate to "(set_color cyan)"$readable_path"(set_color normal)
end
end
end
end
end
# Prompt mode: ask for patterns, fetch and dedup (or print) each one individually
# Prompt mode: ask for patterns, fetch and dedup each one individually
if test $do_prompt -eq 1
set -l patterns ""
if set -q _flag_force
# Bypass the prompt: proceed with the default action (no patterns)
set patterns ""
else
read -P "Enter gitignore patterns (comma-separated, e.g. python,vim): " patterns
or return 0
end
set patterns (string trim -- $patterns)
if test -n "$patterns"
for pattern in (string split "," -- $patterns)
@@ -234,16 +159,11 @@ function gi --description 'Generate .gitignore files using the gitignore.io API'
echo "Error: Failed to fetch gitignore for '$pattern'. Is the target spelled correctly?" >&2
continue
end
if set -q _flag_stdout
echo "$content"
__gi_append_dedup "$content" "$pattern" "$gitignore_path" "$readable_path"
end
else
__gi_append_dedup "$content" "$pattern" "$gitignore_path" "$readable_path" $silent_flag
end
end
else if not set -q _flag_silent
echo (set_color brblack)"No patterns selected. Skipping API fetch."(set_color normal)
end
test $needs_git -eq 1; and gitignore-scrub
return 0
end
@@ -269,19 +189,14 @@ function gi --description 'Generate .gitignore files using the gitignore.io API'
echo "Error: Failed to fetch gitignore for '$target'. Is the target spelled correctly?" >&2
continue
end
__gi_append_dedup "$content" "$target" "$gitignore_path" "$readable_path" $silent_flag
__gi_append_dedup "$content" "$target" "$gitignore_path" "$readable_path"
end
end
end
if test $needs_git -eq 1
gitignore-scrub
end
return 0
end
# SYNOPSIS
# __gi_append_dedup <content> <label> <gitignore_path> <readable_path> [silent]
# __gi_append_dedup <content> <label> <gitignore_path> <readable_path>
#
# DESCRIPTION
# Appends gitignore content to a .gitignore file using MD5-based deduplication.
@@ -292,16 +207,14 @@ end
# label Human-readable label for the pattern set
# gitignore_path Absolute path to the .gitignore file
# readable_path Home-abbreviated path shown in output messages
# silent 1 to suppress progress output, 0/omitted to show it
#
# EXAMPLE
# __gi_append_dedup "$content" "python" "$root/.gitignore" "~/.gitignore" 0
# __gi_append_dedup "$content" "python" "$root/.gitignore" "~/.gitignore"
function __gi_append_dedup
set -l content $argv[1]
set -l label $argv[2]
set -l gitignore_path $argv[3]
set -l readable_path $argv[4]
set -l silent $argv[5]
set -l content_hash ""
if command -q md5sum
@@ -313,14 +226,10 @@ function __gi_append_dedup
set -l sig "# id: gi-patterns-$content_hash"
if test -f "$gitignore_path"; and grep -qF "$sig" "$gitignore_path"
if test "$silent" != 1
set_color yellow --bold
echo "Notice:" (set_color normal)"$label patterns already present in "(set_color cyan)"$readable_path"(set_color normal)"."
end
else
printf "\n%s\n%s\n" "$sig" "$content" >>"$gitignore_path"
if test "$silent" != 1
echo (set_color green)"✔"(set_color normal)" Appended $label patterns to "(set_color cyan)"$readable_path"(set_color normal)
end
end
end
-6
View File
@@ -4,12 +4,6 @@
# CATEGORY
# 10-network
#
# DEPENDENCIES
# curl
#
# CLASSIFICATION
# network
#
# SYNOPSIS
# gip
#
-6
View File
@@ -4,12 +4,6 @@
# CATEGORY
# 10-network
#
# DEPENDENCIES
# curl
#
# CLASSIFICATION
# network
#
# SYNOPSIS
# gip4
#
-6
View File
@@ -4,12 +4,6 @@
# CATEGORY
# 10-network
#
# DEPENDENCIES
# curl
#
# CLASSIFICATION
# network
#
# SYNOPSIS
# gip6
#
+2 -9
View File
@@ -4,12 +4,6 @@
# CATEGORY
# 04-git-and-version-control
#
# DEPENDENCIES
# git
#
# CLASSIFICATION
# network
#
# SYNOPSIS
# git-clean [-h] [-f]
#
@@ -35,10 +29,9 @@ function git-clean --description 'Sync main, prune remotes, and delete orphaned
or return
if set -q _flag_help
__fish_palette
echo "$c_head""Usage:$c_reset $c_cmd""git-clean$c_reset $c_arg""[OPTIONS]$c_reset"
echo (set_color --bold blue)"Usage: "(set_color normal)"git-clean [OPTIONS]"
echo
echo "$c_head""Steps taken:$c_reset"
echo "Steps taken:"
echo " 1. Fetches and prunes to find deleted remote branches."
echo " 2. Switches to main if you are on an orphaned branch."
echo " 3. Pulls the latest changes from the remote."
-145
View File
@@ -1,145 +0,0 @@
# Copyright (C) 2026 Rootiest
# SPDX-License-Identifier: AGPL-3.0-or-later
# CATEGORY
# 04-git-and-version-control
#
# DEPENDENCIES
# git
#
# CLASSIFICATION
# blocking-prompt
#
# SYNOPSIS
# gitignore-scrub [-h] [-r] [-w | -f | -i]
#
# DESCRIPTION
# Finds files that are tracked by git but now match a .gitignore pattern
# (git ls-files -ci --exclude-standard) and offers to untrack them. In the
# default interactive mode, prompts once for all matches and runs
# git rm --cached on confirmation; a decline is remembered per-path in the
# repo's local git config (gitignore-scrub.skip) so the same file is not
# asked about again. -w/--warn is read-only: prints a Warning line per
# match and makes no changes, meant for non-interactive callers such as a
# git hook. -f/--force skips the prompt and untracks every match
# immediately. -i/--individual prompts once per file instead of once for
# the whole group. -w, -f, and -i are mutually exclusive. -r/--reset
# clears the repo's skip list first (combinable with any mode), so
# previously declined files are reconsidered. Silently does nothing on a
# repo with more tracked files than $GITIGNORE_SCRUB_LIMIT (default
# 5000), to avoid adding latency to huge repos.
#
# ARGUMENTS
# -h, --help Show help message
# -r, --reset Clear the remembered skip list before checking
# -w, --warn Read-only: print warnings instead of prompting, make no changes
# -f, --force Untrack every match immediately, no prompt
# -i, --individual Prompt once per file instead of once for the whole group
#
# EXIT STATUS
# 0 Clean, or a prompt/force run was handled
# 1 Not a git repository, or (-w only) unconfirmed matches were found
#
# EXAMPLE
# gitignore-scrub
# gitignore-scrub --warn
# gitignore-scrub --force
# gitignore-scrub --individual
# gitignore-scrub --reset
function gitignore-scrub --description 'Find and optionally untrack files newly matched by .gitignore'
argparse --exclusive w,f,i h/help r/reset w/warn f/force i/individual -- $argv
or return 1
if set -q _flag_help
__fish_palette
echo "$c_head""Usage:$c_reset $c_cmd""gitignore-scrub$c_reset $c_arg""[FLAGS]$c_reset"
echo ""
echo "$c_head""Flags:$c_reset"
echo " $c_flag-h, --help$c_reset Show this help message"
echo " $c_flag-r, --reset$c_reset Clear the remembered skip list before checking"
echo " $c_flag-w, --warn$c_reset Read-only: print warnings instead of prompting"
echo " $c_flag-f, --force$c_reset Untrack every match immediately, no prompt"
echo " $c_flag-i, --individual$c_reset Prompt once per file instead of once for the group"
echo ""
echo "$c_head""Examples:$c_reset"
echo " $c_cmd""gitignore-scrub$c_reset $c_dim""# Interactive: prompt to untrack matches$c_reset"
echo " $c_cmd""gitignore-scrub --warn$c_reset $c_dim""# Read-only: for use in a git hook$c_reset"
echo " $c_cmd""gitignore-scrub --force$c_reset $c_dim""# Untrack every match, no prompt$c_reset"
echo " $c_cmd""gitignore-scrub --individual$c_reset $c_dim""# Prompt per file instead of as a group$c_reset"
echo " $c_cmd""gitignore-scrub --reset$c_reset $c_dim""# Reconsider previously declined files$c_reset"
return 0
end
if not git rev-parse --is-inside-work-tree >/dev/null 2>&1
set_color red --bold
echo "Error:" (set_color normal)"Not a git repository (or any parent directories)" >&2
return 1
end
set -l limit 5000
set -q GITIGNORE_SCRUB_LIMIT; and set limit $GITIGNORE_SCRUB_LIMIT
set -l tracked_count (git ls-files | count)
if test $tracked_count -gt $limit
return 0
end
set -l offenders (git ls-files -ci --exclude-standard)
set -q offenders[1]; or return 0
if set -q _flag_reset
git config --local --remove-section gitignore-scrub 2>/dev/null
end
set -l skip_list (git config --local --get-all gitignore-scrub.skip 2>/dev/null)
set -l pending
for f in $offenders
contains -- "$f" $skip_list; or set -a pending $f
end
set -q pending[1]; or return 0
if set -q _flag_warn
for f in $pending
set_color yellow --bold
echo -n "Warning: "
set_color normal
echo "$f is tracked but ignored"
end
return 1
end
if set -q _flag_force
git rm --cached -- $pending >/dev/null
echo (set_color green)"✔"(set_color normal)" Untracked "(count $pending)" file(s)."
return 0
end
if set -q _flag_individual
for f in $pending
read -P "Remove '$f' from git tracking? [y/N] " confirm
if string match -qir '^y' -- "$confirm"
git rm --cached -- "$f" >/dev/null
echo (set_color green)"✔"(set_color normal)" Untracked $f"
else
git config --local --add gitignore-scrub.skip "$f"
end
end
return 0
end
set_color yellow
echo (count $pending)" tracked file(s) now match .gitignore:"(set_color normal)
for f in $pending
echo " $f"
end
read -P "Remove from git tracking? [y/N] " confirm
if string match -qir '^y' -- "$confirm"
git rm --cached -- $pending >/dev/null
echo (set_color green)"✔"(set_color normal)" Untracked "(count $pending)" file(s)."
else
for f in $pending
git config --local --add gitignore-scrub.skip "$f"
end
echo (set_color brblack)"Remembered — won't ask again for these files."(set_color normal)
end
end
+1 -12
View File
@@ -4,9 +4,6 @@
# CATEGORY
# 04-git-and-version-control
#
# DEPENDENCIES
# gitui
#
# SYNOPSIS
# gitui [args...]
#
@@ -17,17 +14,9 @@
# ARGUMENTS
# args... Arguments forwarded to the gitui command
#
# EXIT STATUS
# 1 gitui is not installed
# * Exit status of gitui otherwise
#
# EXAMPLE
# gitui
function gitui --wraps='gitui' --description 'alias gitui=gitui -t mocha.ron'
if not type -q -f gitui
echo (set_color red)"Error: gitui is not installed."(set_color normal) >&2
return 1
end
command gitui -t frappe.ron $argv
end
-6
View File
@@ -4,12 +4,6 @@
# CATEGORY
# 04-git-and-version-control
#
# DEPENDENCIES
# git
#
# CLASSIFICATION
# network
#
# SYNOPSIS
# gitup [args...]
#
+3 -7
View File
@@ -12,11 +12,7 @@
#
# DESCRIPTION
# Searches fish history interactively using fzf, inserts the selected command
# into the command line, and copies it to the clipboard via wl-copy, xclip,
# or win32yank (WSL2).
#
# DEPENDENCIES
# _fish_clipboard_copy
# into the command line, and copies it to the clipboard via wl-copy.
#
# EXIT STATUS
# 0 Command selected and inserted, or fzf was cancelled
@@ -34,13 +30,13 @@ function hist --description 'Search fish history and put it in the prompt'
return 1
end
set -l selected (builtin history --show-time='%F %T ' | fzf --reverse --height 40% --with-nth 3..)
set -l selected (history | fzf --reverse --height 40% --with-nth 3..)
if test -n "$selected"
# Strip the timestamp for the final output
set -l command (echo $selected | string replace -r '^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} ' '')
echo $command | _fish_clipboard_copy 2>/dev/null
echo $command | wl-copy 2>/dev/null
commandline -r $command
end
end
-3
View File
@@ -7,9 +7,6 @@
# DEPENDENCIES
# tmux, screen, __jobrunner_sessions
#
# CLASSIFICATION
# bypasses-shadow(cat,rm)
#
# SYNOPSIS
# jobrunner [-t <tool>] [<subcommand>] [<name>] [<command>...]
# jr [-t <tool>] [<subcommand>] [<name>] [<command>...]
-3
View File
@@ -4,9 +4,6 @@
# CATEGORY
# 14-miscellaneous
#
# DEPENDENCIES
# joplin
#
# SYNOPSIS
# joplin [args...]
#
-3
View File
@@ -7,9 +7,6 @@
# DEPENDENCIES
# gpg, tar
#
# CLASSIFICATION
# bypasses-shadow(rm), destructive
#
# SYNOPSIS
# key-crypt [options] <input> [output]
# key-crypt -i <input> -o <output> [options]
-7
View File
@@ -7,13 +7,6 @@
# COMPONENT
# logging/terminal-capture
#
# DEPENDENCIES
# kitty, __kitty_logging_dir, __kitty_logging_has_watcher,
# __kitty_logging_version
#
# CLASSIFICATION
# bypasses-shadow(grep,mkdir,rm)
#
# SYNOPSIS
# kitty-logging [install | uninstall | status | dismiss] [-h]
#
-6
View File
@@ -4,12 +4,6 @@
# CATEGORY
# 01-file-and-directory
#
# DEPENDENCIES
# eza, lsd
#
# CLASSIFICATION
# bypasses-shadow(ls)
#
# SYNOPSIS
# lD [args...]
#

Some files were not shown because too many files have changed in this diff Show More